From 9c557e2b45406e7ace673941d92e100972e74140 Mon Sep 17 00:00:00 2001 From: JavidHaji-zada Date: Tue, 18 Jul 2023 11:57:49 +0400 Subject: [PATCH 001/509] [POC] add wallet setup --- src/appTypes/navigation/add-wallet.ts | 15 ++++++++++ src/appTypes/navigation/index.ts | 4 ++- src/appTypes/navigation/settings.ts | 18 ++++++++++++ src/navigation/stacks/Tabs/AddWalletStack.tsx | 22 ++++++++++++++ src/navigation/stacks/Tabs/SettingsStack.tsx | 9 +++--- src/screens/AddWallet/index.tsx | 29 +++++++++++++++++++ src/screens/AddWallet/styles.ts | 7 +++++ src/screens/CreateWallet/index.tsx | 12 ++++++++ src/screens/CreateWallet/styles.ts | 7 +++++ src/screens/Settings/index.tsx | 14 ++++++++- 10 files changed, 130 insertions(+), 7 deletions(-) create mode 100644 src/appTypes/navigation/add-wallet.ts create mode 100644 src/appTypes/navigation/settings.ts create mode 100644 src/navigation/stacks/Tabs/AddWalletStack.tsx create mode 100644 src/screens/AddWallet/index.tsx create mode 100644 src/screens/AddWallet/styles.ts create mode 100644 src/screens/CreateWallet/index.tsx create mode 100644 src/screens/CreateWallet/styles.ts diff --git a/src/appTypes/navigation/add-wallet.ts b/src/appTypes/navigation/add-wallet.ts new file mode 100644 index 000000000..189459ff7 --- /dev/null +++ b/src/appTypes/navigation/add-wallet.ts @@ -0,0 +1,15 @@ +import { BottomTabNavigationProp } from '@react-navigation/bottom-tabs'; +import { CompositeNavigationProp } from '@react-navigation/native'; +import { TabsParamsList } from './tabs'; +import { NativeStackNavigationProp } from '@react-navigation/native-stack'; + +export type AddWalletStackParamsList = { + AddWalletScreen: undefined; + RestoreWalletScreen: undefined; + CreateWalletScreen: undefined; +}; + +export type AddWalletStackNavigationProp = CompositeNavigationProp< + BottomTabNavigationProp, + NativeStackNavigationProp +>; diff --git a/src/appTypes/navigation/index.ts b/src/appTypes/navigation/index.ts index 5fa51feb7..eee96d1c5 100644 --- a/src/appTypes/navigation/index.ts +++ b/src/appTypes/navigation/index.ts @@ -1,5 +1,7 @@ -export * from './search'; +export * from './add-wallet'; export * from './lists'; export * from './root'; +export * from './search'; +export * from './settings'; export * from './tabs'; export * from './wallets'; diff --git a/src/appTypes/navigation/settings.ts b/src/appTypes/navigation/settings.ts new file mode 100644 index 000000000..4a0c1e865 --- /dev/null +++ b/src/appTypes/navigation/settings.ts @@ -0,0 +1,18 @@ +import { + CompositeNavigationProp, + NavigatorScreenParams +} from '@react-navigation/native'; +import { AddWalletStackParamsList } from './add-wallet'; +import { BottomTabNavigationProp } from '@react-navigation/bottom-tabs'; +import { TabsParamsList } from './tabs'; +import { NativeStackNavigationProp } from '@react-navigation/native-stack'; + +export type SettingsTabParamsList = { + SettingsScreen: undefined; + AddWalletStack: NavigatorScreenParams; +}; + +export type SettingsTabNavigationProp = CompositeNavigationProp< + BottomTabNavigationProp, + NativeStackNavigationProp +>; diff --git a/src/navigation/stacks/Tabs/AddWalletStack.tsx b/src/navigation/stacks/Tabs/AddWalletStack.tsx new file mode 100644 index 000000000..5f2928401 --- /dev/null +++ b/src/navigation/stacks/Tabs/AddWalletStack.tsx @@ -0,0 +1,22 @@ +import React from 'react'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; +import { SettingsScreen } from '@screens/Settings'; +import { AddWalletStackParamsList } from '@appTypes'; +import { AddWalletScreen } from '@screens/AddWallet'; +import { CreateWalletScreen } from '@screens/CreateWallet'; + +const Stack = createNativeStackNavigator(); +export const AddWalletStack = () => { + return ( + + + + + + ); +}; + +export default AddWalletStack; diff --git a/src/navigation/stacks/Tabs/SettingsStack.tsx b/src/navigation/stacks/Tabs/SettingsStack.tsx index a78c0cc03..778ee17c0 100644 --- a/src/navigation/stacks/Tabs/SettingsStack.tsx +++ b/src/navigation/stacks/Tabs/SettingsStack.tsx @@ -1,12 +1,10 @@ import React from 'react'; import { createNativeStackNavigator } from '@react-navigation/native-stack'; import { SettingsScreen } from '@screens/Settings'; +import { SettingsTabParamsList } from '@appTypes'; +import AddWalletStack from './AddWalletStack'; -export type ExploresParamsList = { - SettingsScreen: undefined; -}; - -const Stack = createNativeStackNavigator(); +const Stack = createNativeStackNavigator(); export const SettingsStack = () => { return ( { initialRouteName="SettingsScreen" > + ); }; diff --git a/src/screens/AddWallet/index.tsx b/src/screens/AddWallet/index.tsx new file mode 100644 index 000000000..4daedbebb --- /dev/null +++ b/src/screens/AddWallet/index.tsx @@ -0,0 +1,29 @@ +import React from 'react'; +import { SafeAreaView } from 'react-native-safe-area-context'; +import { styles } from './styles'; +import { Header } from '@components/composite'; +import { Button, Spacer, Text } from '@components/base'; +import { verticalScale } from '@utils/scaling'; +import { useNavigation } from '@react-navigation/native'; +import { AddWalletStackNavigationProp } from '@appTypes'; + +export const AddWalletScreen = () => { + const navigation = useNavigation(); + + const onCreatePress = () => { + navigation.navigate('CreateWalletScreen'); + }; + + return ( + +
+ + + + + ); +}; diff --git a/src/screens/AddWallet/styles.ts b/src/screens/AddWallet/styles.ts new file mode 100644 index 000000000..8aaa60e9b --- /dev/null +++ b/src/screens/AddWallet/styles.ts @@ -0,0 +1,7 @@ +import { StyleSheet } from 'react-native'; + +export const styles = StyleSheet.create({ + container: { + flex: 1 + } +}); diff --git a/src/screens/CreateWallet/index.tsx b/src/screens/CreateWallet/index.tsx new file mode 100644 index 000000000..4f490c644 --- /dev/null +++ b/src/screens/CreateWallet/index.tsx @@ -0,0 +1,12 @@ +import React from 'react'; +import { SafeAreaView } from 'react-native-safe-area-context'; +import { Header } from '@components/composite'; +import { styles } from './styles'; + +export const CreateWalletScreen = () => { + return ( + +
+ + ); +}; diff --git a/src/screens/CreateWallet/styles.ts b/src/screens/CreateWallet/styles.ts new file mode 100644 index 000000000..8aaa60e9b --- /dev/null +++ b/src/screens/CreateWallet/styles.ts @@ -0,0 +1,7 @@ +import { StyleSheet } from 'react-native'; + +export const styles = StyleSheet.create({ + container: { + flex: 1 + } +}); diff --git a/src/screens/Settings/index.tsx b/src/screens/Settings/index.tsx index cc05a5cb8..7df2ee220 100644 --- a/src/screens/Settings/index.tsx +++ b/src/screens/Settings/index.tsx @@ -3,16 +3,28 @@ import { StyleSheet, View } from 'react-native'; import { SettingsBlock } from '@screens/Settings/components/SettingsBlock'; import { COLORS } from '@constants/colors'; import { SettingsInfoBlock } from '@screens/Settings/components/SettingsInfoBlock'; -import { scale } from '@utils/scaling'; +import { scale, verticalScale } from '@utils/scaling'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; +import { Spacer, Text } from '@components/base'; +import { PrimaryButton } from '@components/modular'; +import { useNavigation } from '@react-navigation/native'; +import { SettingsTabNavigationProp } from '@appTypes'; export const SettingsScreen = () => { const { top } = useSafeAreaInsets(); + const navigation = useNavigation(); + const navigateToWalletConnect = () => { + navigation.navigate('AddWalletStack', { screen: 'AddWalletScreen' }); + }; return ( + + + Go to Wallet Connect + ); }; From fc6752202a7d629362ed905340184f72b4126baf Mon Sep 17 00:00:00 2001 From: JavidHaji-zada Date: Tue, 18 Jul 2023 15:20:22 +0400 Subject: [PATCH 002/509] [POC] implemented wallet mnemonic creation --- App.tsx | 3 +- Providers.tsx | 9 ++++-- package.json | 2 ++ src/contexts/AddWallet/AddWallet.types.ts | 17 +++++++++++ src/contexts/AddWallet/index.tsx | 36 +++++++++++++++++++++++ src/contexts/index.ts | 1 + src/prototypes/buffer.ts | 2 ++ src/screens/AddWallet/index.tsx | 6 ++++ src/utils/crypto.ts | 16 ++++++++++ src/utils/mnemonics.ts | 13 ++++++++ yarn.lock | 22 +++++++++++++- 11 files changed, 123 insertions(+), 4 deletions(-) create mode 100644 src/contexts/AddWallet/AddWallet.types.ts create mode 100644 src/contexts/AddWallet/index.tsx create mode 100644 src/prototypes/buffer.ts create mode 100644 src/utils/crypto.ts create mode 100644 src/utils/mnemonics.ts diff --git a/App.tsx b/App.tsx index fa63b56ac..2f1366d63 100644 --- a/App.tsx +++ b/App.tsx @@ -1,10 +1,11 @@ +import './src/prototypes/array'; +import './src/prototypes/buffer'; import React from 'react'; import Navigation from '@navigation/NavigationContainer'; import { useAppInit } from '@hooks/useAppInit'; import { GestureHandlerRootView } from 'react-native-gesture-handler'; import { Providers } from './Providers'; import { Toast } from '@components/modular'; -import './src/prototypes/array'; export default function App() { const { isAppReady } = useAppInit(); diff --git a/Providers.tsx b/Providers.tsx index 03b8242e7..033d97398 100644 --- a/Providers.tsx +++ b/Providers.tsx @@ -3,7 +3,11 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import React from 'react'; import { SafeAreaProvider } from 'react-native-safe-area-context'; import { ListsContextProvider } from '@contexts/ListsContext'; -import { AllAddressesProvider, OnboardingContextProvider } from '@contexts'; +import { + AddWalletProvider, + AllAddressesProvider, + OnboardingContextProvider +} from '@contexts'; const queryClient = new QueryClient(); @@ -26,7 +30,8 @@ const providers = [ ...independentProviders, AllAddressesProvider, ListsContextProvider, - OnboardingContextProvider + OnboardingContextProvider, + AddWalletProvider ]; export const Providers = combineComponents(...providers); diff --git a/package.json b/package.json index 576c619f4..5fc4aa34a 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,8 @@ "@testing-library/react-native": "^12.1.1", "@types/jest": "^29.5.1", "axios": "^1.3.4", + "bip39": "^3.1.0", + "buffer": "^6.0.3", "expo": "~48.0.18", "expo-barcode-scanner": "~12.3.2", "expo-build-properties": "~0.6.0", diff --git a/src/contexts/AddWallet/AddWallet.types.ts b/src/contexts/AddWallet/AddWallet.types.ts new file mode 100644 index 000000000..a48bfe95f --- /dev/null +++ b/src/contexts/AddWallet/AddWallet.types.ts @@ -0,0 +1,17 @@ +export enum AddWalletFlowType { + CREATE_WALLET = 'CREATE_WALLET', + RESTORE_WALLET = 'RESTORE_WALLET' +} + +export interface AddWalletContextState { + flowType: AddWalletFlowType; + setFlowType: React.Dispatch>; + walletHash: string; + setWalletHash: React.Dispatch>; + walletName: string; + setWalletName: React.Dispatch>; + walletMnemonic: string; + setWalletMnemonic: React.Dispatch>; + mnemonicLength: number; + setMnemonicLength: React.Dispatch>; +} diff --git a/src/contexts/AddWallet/index.tsx b/src/contexts/AddWallet/index.tsx new file mode 100644 index 000000000..bb285dd02 --- /dev/null +++ b/src/contexts/AddWallet/index.tsx @@ -0,0 +1,36 @@ +import { useState } from 'react'; +import { createContextSelector } from '@helpers/createContextSelector'; +import { AddWalletContextState, AddWalletFlowType } from './AddWallet.types'; + +const AddWalletContext = (): AddWalletContextState => { + const [flowType, setFlowType] = useState( + AddWalletFlowType.CREATE_WALLET + ); + const [walletHash, setWalletHash] = useState(''); + const [walletName, setWalletName] = useState(''); + const [walletMnemonic, setWalletMnemonic] = useState(''); + const [mnemonicLength, setMnemonicLength] = useState(0); + return { + flowType, + setFlowType, + walletHash, + setWalletHash, + walletName, + setWalletName, + walletMnemonic, + setWalletMnemonic, + mnemonicLength, + setMnemonicLength + }; +}; + +const [_AddWalletProvider, _useAddWalletContext] = + createContextSelector(AddWalletContext); + +export const useAddWalletContext = () => { + return _useAddWalletContext((v) => v); +}; + +export const AddWalletProvider = _AddWalletProvider; + +export * from './AddWallet.types'; diff --git a/src/contexts/index.ts b/src/contexts/index.ts index 9e10e82aa..96f409ede 100644 --- a/src/contexts/index.ts +++ b/src/contexts/index.ts @@ -1,3 +1,4 @@ +export * from './AddWallet'; export * from './AllAddresses'; export * from './ListsContext'; export * from './Navigation'; diff --git a/src/prototypes/buffer.ts b/src/prototypes/buffer.ts new file mode 100644 index 000000000..95f83ee88 --- /dev/null +++ b/src/prototypes/buffer.ts @@ -0,0 +1,2 @@ +// eslint-disable-next-line @typescript-eslint/no-var-requires +global.Buffer = require('buffer').Buffer; diff --git a/src/screens/AddWallet/index.tsx b/src/screens/AddWallet/index.tsx index 4daedbebb..99da4c8ca 100644 --- a/src/screens/AddWallet/index.tsx +++ b/src/screens/AddWallet/index.tsx @@ -6,11 +6,17 @@ import { Button, Spacer, Text } from '@components/base'; import { verticalScale } from '@utils/scaling'; import { useNavigation } from '@react-navigation/native'; import { AddWalletStackNavigationProp } from '@appTypes'; +import { AddWalletFlowType, useAddWalletContext } from '@contexts'; export const AddWalletScreen = () => { const navigation = useNavigation(); + const { setFlowType, setWalletName, setMnemonicLength } = + useAddWalletContext(); const onCreatePress = () => { + setFlowType(AddWalletFlowType.CREATE_WALLET); + setWalletName(''); + setMnemonicLength(128); navigation.navigate('CreateWalletScreen'); }; diff --git a/src/utils/crypto.ts b/src/utils/crypto.ts new file mode 100644 index 000000000..ef341907d --- /dev/null +++ b/src/utils/crypto.ts @@ -0,0 +1,16 @@ +import * as ExpoCrypto from 'expo-crypto'; + +const hashMnemonic = async (mnemonic: string) => { + return ( + await ExpoCrypto.digestStringAsync( + ExpoCrypto.CryptoDigestAlgorithm.SHA256, + mnemonic + ) + ).substring(0, 32); +}; + +const getRandomBytes = async (size: number) => { + return await ExpoCrypto.getRandomBytesAsync(size); +}; + +export const Crypto = { hashMnemonic, getRandomBytes }; diff --git a/src/utils/mnemonics.ts b/src/utils/mnemonics.ts new file mode 100644 index 000000000..3e0c8917e --- /dev/null +++ b/src/utils/mnemonics.ts @@ -0,0 +1,13 @@ +// eslint-disable-next-line @typescript-eslint/no-var-requires +const bip39 = require('bip39'); +import { Crypto } from './crypto'; + +const generateNewMnemonic = async (size = 256) => { + const random = await Crypto.getRandomBytes(size / 8); + //@ts-ignore + const mnemonic = bip39.entropyToMnemonic(Buffer.from(random, 'base64')); + const hash = await Crypto.hashMnemonic(mnemonic); + return { mnemonic, hash }; +}; + +export const MnemonicUtils = { generateNewMnemonic }; diff --git a/yarn.lock b/yarn.lock index 8da084fad..b1115f163 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2223,6 +2223,11 @@ dependencies: crypto-js "^3.1.9-1" +"@noble/hashes@^1.2.0": + version "1.3.1" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.1.tgz#8831ef002114670c603c458ab8b11328406953a9" + integrity sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA== + "@nodelib/fs.scandir@2.1.5": version "2.1.5" resolved "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz" @@ -3483,6 +3488,13 @@ big-integer@1.6.x: resolved "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz" integrity sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg== +bip39@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/bip39/-/bip39-3.1.0.tgz#c55a418deaf48826a6ceb34ac55b3ee1577e18a3" + integrity sha512-c9kiwdk45Do5GL0vJMe7tS95VjCii65mYAH7DfWl3uW8AVzXKQVUm64i3hzVybBDMp9r7j9iNxR85+ul8MdN/A== + dependencies: + "@noble/hashes" "^1.2.0" + bl@^4.1.0: version "4.1.0" resolved "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz" @@ -3620,6 +3632,14 @@ buffer@^5.5.0: base64-js "^1.3.1" ieee754 "^1.1.13" +buffer@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" + integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.2.1" + builtin-modules@^1.1.1: version "1.1.1" resolved "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz" @@ -6043,7 +6063,7 @@ iconv-lite@0.6.3, iconv-lite@^0.6.2: dependencies: safer-buffer ">= 2.1.2 < 3.0.0" -ieee754@^1.1.13: +ieee754@^1.1.13, ieee754@^1.2.1: version "1.2.1" resolved "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz" integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== From 4af1e4fc37655717c760386533f3a21b7accefb0 Mon Sep 17 00:00:00 2001 From: JavidHaji-zada Date: Tue, 18 Jul 2023 16:29:30 +0400 Subject: [PATCH 003/509] [POC] Mnemonic validation --- src/appTypes/navigation/add-wallet.ts | 3 +- src/navigation/stacks/Tabs/AddWalletStack.tsx | 5 +- src/screens/AddWallet/index.tsx | 2 +- .../CreateWallet/CreateWalletStep1.tsx | 100 ++++++++++++++++ .../CreateWallet/CreateWalletStep2.tsx | 110 ++++++++++++++++++ src/screens/CreateWallet/index.ts | 2 + src/screens/CreateWallet/index.tsx | 12 -- src/screens/CreateWallet/styles.ts | 7 -- 8 files changed, 218 insertions(+), 23 deletions(-) create mode 100644 src/screens/CreateWallet/CreateWalletStep1.tsx create mode 100644 src/screens/CreateWallet/CreateWalletStep2.tsx create mode 100644 src/screens/CreateWallet/index.ts delete mode 100644 src/screens/CreateWallet/index.tsx delete mode 100644 src/screens/CreateWallet/styles.ts diff --git a/src/appTypes/navigation/add-wallet.ts b/src/appTypes/navigation/add-wallet.ts index 189459ff7..c38975422 100644 --- a/src/appTypes/navigation/add-wallet.ts +++ b/src/appTypes/navigation/add-wallet.ts @@ -6,7 +6,8 @@ import { NativeStackNavigationProp } from '@react-navigation/native-stack'; export type AddWalletStackParamsList = { AddWalletScreen: undefined; RestoreWalletScreen: undefined; - CreateWalletScreen: undefined; + CreateWalletStep1: undefined; + CreateWalletStep2: undefined; }; export type AddWalletStackNavigationProp = CompositeNavigationProp< diff --git a/src/navigation/stacks/Tabs/AddWalletStack.tsx b/src/navigation/stacks/Tabs/AddWalletStack.tsx index 5f2928401..9ca01267f 100644 --- a/src/navigation/stacks/Tabs/AddWalletStack.tsx +++ b/src/navigation/stacks/Tabs/AddWalletStack.tsx @@ -3,7 +3,7 @@ import { createNativeStackNavigator } from '@react-navigation/native-stack'; import { SettingsScreen } from '@screens/Settings'; import { AddWalletStackParamsList } from '@appTypes'; import { AddWalletScreen } from '@screens/AddWallet'; -import { CreateWalletScreen } from '@screens/CreateWallet'; +import { CreateWalletStep1, CreateWalletStep2 } from '@screens/CreateWallet'; const Stack = createNativeStackNavigator(); export const AddWalletStack = () => { @@ -13,7 +13,8 @@ export const AddWalletStack = () => { initialRouteName="AddWalletScreen" > - + + ); diff --git a/src/screens/AddWallet/index.tsx b/src/screens/AddWallet/index.tsx index 99da4c8ca..8a95655a1 100644 --- a/src/screens/AddWallet/index.tsx +++ b/src/screens/AddWallet/index.tsx @@ -17,7 +17,7 @@ export const AddWalletScreen = () => { setFlowType(AddWalletFlowType.CREATE_WALLET); setWalletName(''); setMnemonicLength(128); - navigation.navigate('CreateWalletScreen'); + navigation.navigate('CreateWalletStep1'); }; return ( diff --git a/src/screens/CreateWallet/CreateWalletStep1.tsx b/src/screens/CreateWallet/CreateWalletStep1.tsx new file mode 100644 index 000000000..6762b8dc8 --- /dev/null +++ b/src/screens/CreateWallet/CreateWalletStep1.tsx @@ -0,0 +1,100 @@ +import React, { useEffect, useState } from 'react'; +import { View, StyleSheet } from 'react-native'; +import { SafeAreaView } from 'react-native-safe-area-context'; +import { Row, Spacer, Spinner, Text } from '@components/base'; +import { Header } from '@components/composite'; +import { MnemonicUtils } from '@utils/mnemonics'; +import { useAddWalletContext } from '@contexts'; +import { verticalScale, scale } from '@utils/scaling'; +import { PrimaryButton } from '@components/modular'; +import { useNavigation } from '@react-navigation/native'; +import { AddWalletStackNavigationProp } from '@appTypes'; +import { COLORS } from '@constants/colors'; + +export const CreateWalletStep1 = () => { + const navigation = useNavigation(); + const [loading, setLoading] = useState(false); + const { walletMnemonic, mnemonicLength, setWalletMnemonic } = + useAddWalletContext(); + const walletMnemonicArray = walletMnemonic.split(' '); + + const init = async () => { + setLoading(true); + // create mnemonic + const walletMnemonic = ( + await MnemonicUtils.generateNewMnemonic(mnemonicLength) + ).mnemonic; + setWalletMnemonic(walletMnemonic); + setLoading(false); + }; + + useEffect(() => { + if (mnemonicLength > 0) init(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [mnemonicLength]); + + const renderWord = (word: string, index: number) => { + return ( + + {index + 1}. + + {' '} + {word} + + + ); + }; + + const onNextPress = () => { + navigation.navigate('CreateWalletStep2'); + }; + + const halfArrayNum = Math.ceil(walletMnemonicArray.length / 2); + + return ( + +
+ {loading && ( + + + + )} + + + {Array.isArray(walletMnemonicArray) && ( + + + {walletMnemonicArray.slice(0, halfArrayNum).map(renderWord)} + + + {walletMnemonicArray + .slice(halfArrayNum) + .map((word, idx) => renderWord(word, idx + halfArrayNum))} + + + )} + + Next + + + + ); +}; + +const styles = StyleSheet.create({ + container: { + flex: 1 + }, + innerContainer: { + flex: 1, + paddingHorizontal: scale(16) + }, + loading: { + flex: 1, + justifyContent: 'center', + alignItems: 'center' + }, + column: { + flex: 1 + } +}); diff --git a/src/screens/CreateWallet/CreateWalletStep2.tsx b/src/screens/CreateWallet/CreateWalletStep2.tsx new file mode 100644 index 000000000..2f4c605cb --- /dev/null +++ b/src/screens/CreateWallet/CreateWalletStep2.tsx @@ -0,0 +1,110 @@ +import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import { Alert, StyleSheet, View } from 'react-native'; +import { SafeAreaView } from 'react-native-safe-area-context'; +import { Button, Row, Spacer, Spinner, Text } from '@components/base'; +import { Header } from '@components/composite'; +import { useAddWalletContext } from '@contexts'; +import { moderateScale, scale, verticalScale } from '@utils/scaling'; +import { COLORS } from '@constants/colors'; + +export const CreateWalletStep2 = () => { + const { walletMnemonic } = useAddWalletContext(); + const [walletMnemonicSelected, setWalletMnemonicSelected] = useState< + string[] + >([]); + const [loading, setLoading] = useState(false); + const walletMnemonicArrayDefault = walletMnemonic.split(' '); + const walletMnemonicRandomSorted = useMemo( + () => walletMnemonicArrayDefault.sort(() => 0.5 - Math.random()), + // eslint-disable-next-line react-hooks/exhaustive-deps + [walletMnemonicArrayDefault.length] + ); + + const validateMnemonic = useCallback(async () => { + if (walletMnemonicSelected.length !== walletMnemonicArrayDefault.length) { + return false; + } + if ( + JSON.stringify(walletMnemonicSelected) !== + JSON.stringify(walletMnemonicArrayDefault) + ) { + Alert.alert('Failed'); + return; + } + setLoading(true); + Alert.alert('success'); + //TODO implement + setLoading(false); + }, [walletMnemonicArrayDefault, walletMnemonicSelected]); + + useEffect(() => { + validateMnemonic(); + }, [validateMnemonic, walletMnemonicSelected]); + + const renderWord = (word: string) => { + const selectedIdx = walletMnemonicSelected.indexOf(word); + const onPress = () => { + if (selectedIdx > -1) { + walletMnemonicSelected.splice(selectedIdx, 1); + setWalletMnemonicSelected([...walletMnemonicSelected]); + } else { + walletMnemonicSelected.push(word); + setWalletMnemonicSelected([...walletMnemonicSelected]); + } + }; + return ( + + ); + }; + + return ( + +
+ + {loading && } + + {walletMnemonicSelected.map(renderWord)} + 0 ? 36 : 0)} + /> + {Array.isArray(walletMnemonicRandomSorted) && ( + + {walletMnemonicRandomSorted + .filter((word) => walletMnemonicSelected.indexOf(word) === -1) + .map(renderWord)} + + )} + + + ); +}; + +const styles = StyleSheet.create({ + container: { + flex: 1 + }, + innerContainer: { + flex: 1, + paddingHorizontal: scale(16) + }, + loading: { + flex: 1, + justifyContent: 'center', + alignItems: 'center' + }, + words: { + alignItems: 'center', + justifyContent: 'center', + flexWrap: 'wrap', + rowGap: scale(16), + columnGap: verticalScale(16) + }, + word: { + backgroundColor: COLORS.neutral900Alpha[5], + paddingHorizontal: scale(12), + borderRadius: moderateScale(16), + paddingVertical: verticalScale(4) + } +}); diff --git a/src/screens/CreateWallet/index.ts b/src/screens/CreateWallet/index.ts new file mode 100644 index 000000000..b3e35991e --- /dev/null +++ b/src/screens/CreateWallet/index.ts @@ -0,0 +1,2 @@ +export * from './CreateWalletStep1'; +export * from './CreateWalletStep2'; diff --git a/src/screens/CreateWallet/index.tsx b/src/screens/CreateWallet/index.tsx deleted file mode 100644 index 4f490c644..000000000 --- a/src/screens/CreateWallet/index.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import React from 'react'; -import { SafeAreaView } from 'react-native-safe-area-context'; -import { Header } from '@components/composite'; -import { styles } from './styles'; - -export const CreateWalletScreen = () => { - return ( - -
- - ); -}; diff --git a/src/screens/CreateWallet/styles.ts b/src/screens/CreateWallet/styles.ts deleted file mode 100644 index 8aaa60e9b..000000000 --- a/src/screens/CreateWallet/styles.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { StyleSheet } from 'react-native'; - -export const styles = StyleSheet.create({ - container: { - flex: 1 - } -}); From 8a1edac5eda3408ab10ccc466c6c4a2a4908eba0 Mon Sep 17 00:00:00 2001 From: JavidHaji-zada Date: Tue, 18 Jul 2023 16:33:20 +0400 Subject: [PATCH 004/509] renamed walletutils to addressutils --- src/hooks/cache/useWatchlist.ts | 4 ++-- src/utils/{wallet.ts => address.ts} | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) rename src/utils/{wallet.ts => address.ts} (59%) diff --git a/src/hooks/cache/useWatchlist.ts b/src/hooks/cache/useWatchlist.ts index 45d814c42..fff6d5260 100644 --- a/src/hooks/cache/useWatchlist.ts +++ b/src/hooks/cache/useWatchlist.ts @@ -4,7 +4,7 @@ import { useAllAddressesReducer } from '@contexts/AllAddresses'; import { ExplorerAccount } from '@models/Explorer'; -import { WALLET_UTILS } from '@utils/wallet'; +import { AddressUtils } from '@utils/address'; export const useWatchlist = () => { const allAddressesReducer = useAllAddressesReducer(); @@ -14,7 +14,7 @@ export const useWatchlist = () => { const newAddress = Object.assign({}, address); newAddress.isOnWatchlist = true; allAddressesReducer({ type: 'add-or-update', payload: newAddress }); - WALLET_UTILS.watchChangesOfWallet(address); + AddressUtils.watchChangesOfAddress(address); }; const removeFromWatchlist = async (address: ExplorerAccount) => { diff --git a/src/utils/wallet.ts b/src/utils/address.ts similarity index 59% rename from src/utils/wallet.ts rename to src/utils/address.ts index b8685f7b4..3419bedde 100644 --- a/src/utils/wallet.ts +++ b/src/utils/address.ts @@ -3,11 +3,11 @@ import { Permission } from '@appTypes'; import { PermissionService } from '@lib'; import { ExplorerAccount } from '@models'; -const watchChangesOfWallet = async (wallet: ExplorerAccount) => { +const watchChangesOfAddress = async (address: ExplorerAccount) => { await PermissionService.getPermission(Permission.Notifications, { requestAgain: true }); - API.watcherService.watchAddresses([wallet.address]); + API.watcherService.watchAddresses([address.address]); }; -export const WALLET_UTILS = { watchChangesOfWallet }; +export const AddressUtils = { watchChangesOfAddress }; From d081d96686a79ca88f6ae10bd59b9df457ca405b Mon Sep 17 00:00:00 2001 From: illiaa Date: Tue, 18 Jul 2023 23:30:56 +0300 Subject: [PATCH 005/509] added basic logic for wallet restore process --- src/components/modular/Button/Gradient.tsx | 3 + src/navigation/stacks/Tabs/AddWalletStack.tsx | 7 +- src/screens/AddWallet/index.tsx | 10 +- src/screens/RestoreWallet/index.tsx | 207 ++++++++++++++++++ 4 files changed, 224 insertions(+), 3 deletions(-) create mode 100644 src/screens/RestoreWallet/index.tsx diff --git a/src/components/modular/Button/Gradient.tsx b/src/components/modular/Button/Gradient.tsx index d82e2fc63..420449782 100644 --- a/src/components/modular/Button/Gradient.tsx +++ b/src/components/modular/Button/Gradient.tsx @@ -20,6 +20,7 @@ export interface GradientButtonProps extends React.PropsWithChildren { }; testID?: string; style?: ButtonProps['style']; + disabled?: boolean; } export const GradientButton = (props: GradientButtonProps) => { @@ -31,6 +32,7 @@ export const GradientButton = (props: GradientButtonProps) => { children, style = {}, testID, + disabled, onPress } = props; return ( @@ -39,6 +41,7 @@ export const GradientButton = (props: GradientButtonProps) => { // eslint-disable-next-line @typescript-eslint/ban-types style={{ ...styles.container, ...(style as {}) }} testID={testID} + disabled={disabled} > (); export const AddWalletStack = () => { @@ -15,7 +15,10 @@ export const AddWalletStack = () => { - + ); }; diff --git a/src/screens/AddWallet/index.tsx b/src/screens/AddWallet/index.tsx index 8a95655a1..19ed70c62 100644 --- a/src/screens/AddWallet/index.tsx +++ b/src/screens/AddWallet/index.tsx @@ -20,13 +20,21 @@ export const AddWalletScreen = () => { navigation.navigate('CreateWalletStep1'); }; + const onRestorePress = () => { + setFlowType(AddWalletFlowType.RESTORE_WALLET); + setWalletName(''); + setMnemonicLength(128); + navigation.navigate('RestoreWalletScreen'); + }; + return (
- + diff --git a/src/screens/RestoreWallet/index.tsx b/src/screens/RestoreWallet/index.tsx new file mode 100644 index 000000000..9168205ba --- /dev/null +++ b/src/screens/RestoreWallet/index.tsx @@ -0,0 +1,207 @@ +import React, { useMemo, useRef, useState } from 'react'; +import { SafeAreaView } from 'react-native-safe-area-context'; +import { + BottomSheet, + BottomSheetRef, + Header, + InputWithIcon +} from '@components/composite'; +import { Button, InputRef, Row, Spacer, Text } from '@components/base'; +import { moderateScale, scale, verticalScale } from '@utils/scaling'; +import { Alert, StyleSheet, useWindowDimensions, View } from 'react-native'; +import { COLORS } from '@constants/colors'; +import { CloseIcon, ScannerQRIcon } from '@components/svg/icons'; +import { PrimaryButton } from '@components/modular'; +import { BarcodeScanner } from '@components/templates'; +import { etherumAddressRegex } from '@constants/regex'; +import { useNavigation } from '@react-navigation/native'; +import { AddWalletStackNavigationProp } from '@appTypes'; +import { useAddWalletContext } from '@contexts'; + +export const RestoreWalletScreen = () => { + const inputRef = useRef(null); + const scannerModalRef = useRef(null); + const scanned = useRef(false); + const { height: WINDOW_HEIGHT } = useWindowDimensions(); + + const { walletMnemonic } = useAddWalletContext(); + + const [searchValue, setSearchValue] = useState(''); + const [showMnemonic, setShowMnemonic] = useState(false); + + const isMnemonicValid = searchValue.match(etherumAddressRegex); + const walletMnemonicArrayDefault = walletMnemonic.split(' '); + const walletMnemonicRandomSorted = useMemo( + () => walletMnemonicArrayDefault.sort(() => 0.5 - Math.random()), + // eslint-disable-next-line react-hooks/exhaustive-deps + [walletMnemonicArrayDefault.length] + ); + const [mnemonicWords, setMnemonicWords] = useState( + walletMnemonicRandomSorted + ); + + const navigation = useNavigation(); + const navigateToRestoreWallet = () => { + navigation.navigate('Settings'); + }; + + const clearSearch = () => { + if (searchValue.length > 0) { + const removedWords = searchValue.split(' '); + setMnemonicWords((prevMnemonicWords) => [ + ...prevMnemonicWords, + ...removedWords + ]); + setSearchValue(''); + } + }; + + const showScanner = () => { + scannerModalRef.current?.show(); + }; + + const hideScanner = () => { + scannerModalRef.current?.dismiss(); + }; + + const onQRCodeScanned = (data: string) => { + const res = data.match(etherumAddressRegex); + if (res && res?.length > 0) { + hideScanner(); + inputRef.current?.setText(res[0]); + setTimeout(() => { + setSearchValue(res[0]); + }, 500); + } else if (!scanned.current) { + scanned.current = true; + Alert.alert('Invalid QR code', '', [ + { + text: 'Scan again', + onPress: () => { + scanned.current = false; + } + } + ]); + } + }; + + const onChangeText = (text: string) => { + setSearchValue(text); + const words = text.split(' '); + const removedWords = mnemonicWords.filter((word) => !words.includes(word)); + setMnemonicWords(removedWords); + }; + + const renderWord = (word: string) => { + const onWordPress = () => { + if (searchValue.length > 0) { + setSearchValue(searchValue + ' ' + word); + } else { + setSearchValue(word); + } + setMnemonicWords(mnemonicWords.filter((item) => item !== word)); + }; + return ( + + ); + }; + + return ( + +
+ Restore wallet + + } + style={{ shadowColor: 'transparent' }} + /> + + + + {showMnemonic ? ( + <> + {mnemonicWords.map(renderWord)} + + + ) : ( + <> + + Insert the entire recovery phrase or enter all words manually + (usually 12 or 24 words) + + + + )} + + 0 ? ( + + ) : ( + + ) + } + placeholder="Recovery phrase" + value={searchValue} + onChangeText={onChangeText} + onFocus={() => setShowMnemonic(true)} + onBlur={() => setShowMnemonic(false)} + /> + + {isMnemonicValid && ( + + Restore wallet + + )} + + + + Restore wallet + + + + + + + + ); +}; + +const styles = StyleSheet.create({ + innerContainer: { + paddingHorizontal: scale(16) + }, + words: { + alignItems: 'center', + justifyContent: 'center', + flexWrap: 'wrap', + rowGap: scale(16), + columnGap: verticalScale(16) + }, + word: { + backgroundColor: COLORS.culturedWhite, + paddingHorizontal: scale(12), + borderRadius: moderateScale(16), + paddingVertical: verticalScale(4) + } +}); From ffd93e1b2f9048f00b11b2c4aa3509d414cf2a3e Mon Sep 17 00:00:00 2001 From: JavidHaji-zada Date: Wed, 19 Jul 2023 13:17:59 +0400 Subject: [PATCH 006/509] Wallet & WalletMetadata --- src/appTypes/Wallet.ts | 13 +++++++++++++ src/appTypes/index.ts | 1 + src/lib/index.ts | 1 + src/models/Wallet.ts | 17 +++++++++++++++++ 4 files changed, 32 insertions(+) create mode 100644 src/appTypes/Wallet.ts create mode 100644 src/models/Wallet.ts diff --git a/src/appTypes/Wallet.ts b/src/appTypes/Wallet.ts new file mode 100644 index 000000000..b870fb3ac --- /dev/null +++ b/src/appTypes/Wallet.ts @@ -0,0 +1,13 @@ +export interface WalletMetadata { + name: string; + mnemonic: string; + newMnemonic?: string; + number: number; + hash: string; + createdAt?: number; +} + +export enum WalletInitSource { + GENERATION = 'GENERATION', + IMPORT = 'IMPORT' +} diff --git a/src/appTypes/index.ts b/src/appTypes/index.ts index d5d75458e..c1015173a 100644 --- a/src/appTypes/index.ts +++ b/src/appTypes/index.ts @@ -10,3 +10,4 @@ export * from './OnboardingContent'; export * from './Permission'; export * from './QueryResponse'; export * from './Sharing'; +export * from './Wallet'; diff --git a/src/lib/index.ts b/src/lib/index.ts index 10005b365..c6dd94186 100644 --- a/src/lib/index.ts +++ b/src/lib/index.ts @@ -1,3 +1,4 @@ export * from './device'; export * from './notification'; +export * from './storage'; export { default as PermissionService } from './permission'; diff --git a/src/models/Wallet.ts b/src/models/Wallet.ts new file mode 100644 index 000000000..f99424a03 --- /dev/null +++ b/src/models/Wallet.ts @@ -0,0 +1,17 @@ +import { WalletMetadata } from '@appTypes'; + +export class Wallet { + hash: string; + name: string; + mnemonic: string; + createdAt: Date; + + constructor(details: WalletMetadata) { + this.hash = details.hash; + this.name = details.name; + this.mnemonic = details.mnemonic; + this.createdAt = details.createdAt + ? new Date(details.createdAt) + : new Date(); + } +} From df6a81748aae61b00ca43591440230ad028722a6 Mon Sep 17 00:00:00 2001 From: illiaa Date: Wed, 19 Jul 2023 15:57:39 +0300 Subject: [PATCH 007/509] added AirDAOStorage and refactored restore wallet screen --- src/appTypes/Wallet.ts | 1 + src/lib/AirDAOStorage.ts | 300 ++++++++++++++++++++++++++++ src/lib/index.ts | 2 +- src/screens/RestoreWallet/index.tsx | 44 ++-- src/screens/RestoreWallet/styles.ts | 22 ++ 5 files changed, 337 insertions(+), 32 deletions(-) create mode 100644 src/lib/AirDAOStorage.ts create mode 100644 src/screens/RestoreWallet/styles.ts diff --git a/src/appTypes/Wallet.ts b/src/appTypes/Wallet.ts index b870fb3ac..3d30fe3fb 100644 --- a/src/appTypes/Wallet.ts +++ b/src/appTypes/Wallet.ts @@ -1,4 +1,5 @@ export interface WalletMetadata { + pub: string; name: string; mnemonic: string; newMnemonic?: string; diff --git a/src/lib/AirDAOStorage.ts b/src/lib/AirDAOStorage.ts new file mode 100644 index 000000000..e76c48ee6 --- /dev/null +++ b/src/lib/AirDAOStorage.ts @@ -0,0 +1,300 @@ +import * as SecureStore from 'expo-secure-store'; +import { WalletMetadata } from '@appTypes'; + +export class AirDAOStorage { + private serviceName = ''; + private serviceWalletsCounter = 0; + private serviceWallets: { [key: string]: string } = {}; + private serviceWasInitialized = false; + public publicWallets: string[] = []; + public publicSelectedWallet: string | false = false; + + constructor(serviceName = 'AirDAOStorage') { + this.serviceName = serviceName; + } + + private sanitizeKey(key: string): string { + return key.replace(/[^A-Za-z0-9._-]/g, '_'); + } + + private async _getServiceName(name: string) { + this.serviceName = name; + this.serviceWasInitialized = false; + await this.init(); + } + + private async getKeyValue(key: string): Promise { + try { + const sanitizedKey = this.sanitizeKey(this.serviceName + '_' + key); + const credentials = await SecureStore.getItemAsync(sanitizedKey); + if (!credentials) return false; + return JSON.parse(credentials) as WalletMetadata; + } catch (e) { + console.error('AirDAOStorage getKeyValue error ', e); + return false; + } + } + + private async setKeyValue( + key: string, + data: WalletMetadata + ): Promise { + try { + const sanitizedKey = this.sanitizeKey(this.serviceName + '_' + key); + await SecureStore.setItemAsync(sanitizedKey, JSON.stringify(data)); + return true; + } catch (e) { + console.error('AirDAOStorage setKeyValue error ', e); + if (key.indexOf('wallet') !== -1) { + throw e; + } + return false; + } + } + + private async init() { + if (this.serviceWasInitialized) { + return true; + } + + const count = await this.getKeyValue('wallets_counter'); + + this.serviceWalletsCounter = count && count.number ? count.number : 0; + this.serviceWallets = {}; + this.publicWallets = []; + this.publicSelectedWallet = false; + if (this.serviceWalletsCounter > 0) { + for (let i = 1; i <= this.serviceWalletsCounter; i++) { + const wallet = await this.getKeyValue('wallet_' + i); + if ( + !wallet || + !wallet.mnemonic || + wallet.mnemonic === '' || + wallet.mnemonic === + 'new wallet is not generated - please reinstall and restart' || + wallet.mnemonic === wallet.hash + ) { + continue; + } + this.serviceWallets[wallet.hash] = wallet.mnemonic; + this.serviceWallets[i - 1] = wallet.mnemonic; + this.publicWallets.push(wallet.hash); + } + } + this.serviceWasInitialized = true; + } + + async getOldSelectedWallet() { + await this.init(); + const tmp = await this.getKeyValue('selected_hash'); + let publicSelectedWallet: string | boolean = false; + if (tmp && tmp.hash) { + publicSelectedWallet = tmp.hash; + } + if ( + !this.publicSelectedWallet || + !this.serviceWallets[this.publicSelectedWallet] + ) { + publicSelectedWallet = false; + } + return publicSelectedWallet; + } + + async reInit() { + this.serviceWasInitialized = false; + return this.init(); + } + + async getOneWalletText( + walletHash: string, + discoverPath: string | false, + currencyCode: string + ) { + try { + if (typeof discoverPath === 'undefined' || discoverPath === false) { + const res = await this.getKeyValue(walletHash); + if (!res) return false; + return res.mnemonic; + } else { + const key = this.getAddressCacheKey( + walletHash, + discoverPath, + currencyCode + ); + return this.getAddressCache(key); + } + } catch (e) { + console.error(e); + } + return false; + } + + async getAllWalletsText() { + let res = ''; + for (let i = 1; i <= 3; i++) { + try { + const wallet = await this.getKeyValue('wallet_' + i); + if ( + wallet && + typeof wallet.mnemonic !== 'undefined' && + wallet.mnemonic !== 'undefined' + ) { + res += ' WALLET#' + i + ' ' + wallet.mnemonic; + } + } catch (e) { + console.error(e); + } + } + if (res === '') { + return 'Nothing found by general search'; + } + return res; + } + + async countMnemonics() { + await this.init(); + return this.serviceWalletsCounter; + } + + async getWallets() { + await this.init(); + return this.publicWallets; + } + + async getWalletMnemonic(hashOrId: string, source = 'default') { + await this.init(); + if (!this.serviceWallets[hashOrId]) { + throw new Error( + 'undefined wallet with hash ' + hashOrId + ' source ' + source + ); + } + return this.serviceWallets[hashOrId]; + } + + async isMnemonicAlreadySaved(newMnemonic: { + mnemonic: string; + hash: string; + }) { + await this.init(); + if ( + typeof this.serviceWallets[newMnemonic.hash] === 'undefined' || + !this.serviceWallets[newMnemonic.hash] + ) { + return false; + } + if (this.serviceWallets[newMnemonic.hash] !== newMnemonic.mnemonic) { + throw new Error('something wrong with hash algorithm'); + } + return true; + } + + async saveMnemonic(newMnemonic: { + mnemonic: string; + hash: string; + pub: string; + name: string; + number: number; + }) { + await this.init(); + + const logData = { ...newMnemonic }; + if (typeof logData.mnemonic !== 'undefined') logData.mnemonic = '***'; + if (!newMnemonic.hash) { + throw new Error('unique hash required ' + JSON.stringify(newMnemonic)); + } + if ( + typeof this.serviceWallets[newMnemonic.hash] !== 'undefined' && + this.serviceWallets[newMnemonic.hash] + ) { + if (this.serviceWallets[newMnemonic.hash] !== newMnemonic.mnemonic) { + throw new Error('something wrong with hash algorithm'); + } + return newMnemonic.hash; + } + this.serviceWalletsCounter++; + + const unique = newMnemonic.hash; + + await this.setKeyValue(unique, newMnemonic); + await this.setKeyValue('wallet_' + this.serviceWalletsCounter, newMnemonic); + await this.setKeyValue('wallets_counter', { + pub: '', + hash: '', + mnemonic: '', + name: '', + number: this.serviceWalletsCounter + }); + this.serviceWallets[unique] = newMnemonic.mnemonic; + this.serviceWallets[this.serviceWalletsCounter - 1] = newMnemonic.mnemonic; + + this.publicWallets.push(unique); + this.publicSelectedWallet = unique; + + return newMnemonic.hash; + } + + getAddressCacheKey( + walletHash: string, + discoverPath: string, + currencyCode: string + ) { + return walletHash + '_' + discoverPath + '_' + currencyCode; + } + + async getAddressCache(hashOrId: string) { + try { + const res = await this.getKeyValue('ar4_' + hashOrId); + if (!res || !res.mnemonic || res.pub === res.mnemonic) return false; + return { address: res.pub, privateKey: res.mnemonic }; + } catch (e) { + return false; + } + } + + async setAddressCache( + hashOrId: string, + res: { + address: string; + privateKey: string; + pub: string; + name: string; + mnemonic: string; + number: number; + hash: string; + } + ) { + if (typeof res.privateKey === 'undefined' || !res.privateKey) { + return false; + } + return this.setKeyValue('ar4_' + hashOrId, res); + } + + // async getLoginCache(hashOrId: string) { + // const res = await this.getKeyValue('login_' + hashOrId); + // if (!res) return false; + // return { login: res.pub, pass: res.mnemonic }; + // } + // + // async setLoginCache(hashOrId: string, res: { login: string; pass: string }) { + // return this.setKeyValue('login_' + hashOrId, res); + // } + + async setSettingValue(hashOrId: string, value: number) { + return this.setKeyValue('setting_' + hashOrId, { + pub: '', + hash: hashOrId, + number: value, + mnemonic: '', + name: '' + }); + } + + async getSettingValue(hashOrId: string) { + const res = await this.getKeyValue('setting_' + hashOrId); + if (!res) return '0'; + return res.number.toString(); + } +} + +const singleAirDAOStorage = new AirDAOStorage(); +export default singleAirDAOStorage; diff --git a/src/lib/index.ts b/src/lib/index.ts index c6dd94186..1696fbc37 100644 --- a/src/lib/index.ts +++ b/src/lib/index.ts @@ -1,4 +1,4 @@ export * from './device'; export * from './notification'; -export * from './storage'; +export * from './AirDAOStorage'; export { default as PermissionService } from './permission'; diff --git a/src/screens/RestoreWallet/index.tsx b/src/screens/RestoreWallet/index.tsx index 9168205ba..7d62c3a02 100644 --- a/src/screens/RestoreWallet/index.tsx +++ b/src/screens/RestoreWallet/index.tsx @@ -7,8 +7,8 @@ import { InputWithIcon } from '@components/composite'; import { Button, InputRef, Row, Spacer, Text } from '@components/base'; -import { moderateScale, scale, verticalScale } from '@utils/scaling'; -import { Alert, StyleSheet, useWindowDimensions, View } from 'react-native'; +import { verticalScale } from '@utils/scaling'; +import { Alert, useWindowDimensions, View } from 'react-native'; import { COLORS } from '@constants/colors'; import { CloseIcon, ScannerQRIcon } from '@components/svg/icons'; import { PrimaryButton } from '@components/modular'; @@ -17,6 +17,7 @@ import { etherumAddressRegex } from '@constants/regex'; import { useNavigation } from '@react-navigation/native'; import { AddWalletStackNavigationProp } from '@appTypes'; import { useAddWalletContext } from '@contexts'; +import { styles } from '@screens/RestoreWallet/styles'; export const RestoreWalletScreen = () => { const inputRef = useRef(null); @@ -42,6 +43,7 @@ export const RestoreWalletScreen = () => { const navigation = useNavigation(); const navigateToRestoreWallet = () => { + Alert.alert('You have successfully restored your wallet!'); navigation.navigate('Settings'); }; @@ -86,10 +88,15 @@ export const RestoreWalletScreen = () => { }; const onChangeText = (text: string) => { - setSearchValue(text); - const words = text.split(' '); - const removedWords = mnemonicWords.filter((word) => !words.includes(word)); - setMnemonicWords(removedWords); + const newValue = text.charAt(0).toLowerCase() + text.slice(1); + setSearchValue(newValue); + const words = newValue.split(' '); + if (walletMnemonic.includes(newValue)) { + const removedWords = mnemonicWords.filter( + (word) => !words.includes(word) + ); + setMnemonicWords(removedWords); + } }; const renderWord = (word: string) => { @@ -161,12 +168,6 @@ export const RestoreWalletScreen = () => { onBlur={() => setShowMnemonic(false)} /> - {isMnemonicValid && ( - - Restore wallet - - )} - { ); }; - -const styles = StyleSheet.create({ - innerContainer: { - paddingHorizontal: scale(16) - }, - words: { - alignItems: 'center', - justifyContent: 'center', - flexWrap: 'wrap', - rowGap: scale(16), - columnGap: verticalScale(16) - }, - word: { - backgroundColor: COLORS.culturedWhite, - paddingHorizontal: scale(12), - borderRadius: moderateScale(16), - paddingVertical: verticalScale(4) - } -}); diff --git a/src/screens/RestoreWallet/styles.ts b/src/screens/RestoreWallet/styles.ts new file mode 100644 index 000000000..1f8711aa5 --- /dev/null +++ b/src/screens/RestoreWallet/styles.ts @@ -0,0 +1,22 @@ +import { StyleSheet } from 'react-native'; +import { moderateScale, scale, verticalScale } from '@utils/scaling'; +import { COLORS } from '@constants/colors'; + +export const styles = StyleSheet.create({ + innerContainer: { + paddingHorizontal: scale(16) + }, + words: { + alignItems: 'center', + justifyContent: 'center', + flexWrap: 'wrap', + rowGap: scale(16), + columnGap: verticalScale(16) + }, + word: { + backgroundColor: COLORS.culturedWhite, + paddingHorizontal: scale(12), + borderRadius: moderateScale(16), + paddingVertical: verticalScale(4) + } +}); From 421a112ff196e1deb84664c7aaf04cdb1b846070 Mon Sep 17 00:00:00 2001 From: JavidHaji-zada Date: Thu, 20 Jul 2023 10:25:27 +0400 Subject: [PATCH 008/509] added number & hash to Wallet class, connect walletutils --- src/appTypes/Wallet.ts | 2 +- src/constants/words.ts | 2050 +++++++++++++++++ src/lib/storage.ts | 13 + src/models/Wallet.ts | 4 +- .../CreateWallet/CreateWalletStep2.tsx | 10 +- src/utils/mnemonics.ts | 21 +- src/utils/wallet.ts | 49 + 7 files changed, 2144 insertions(+), 5 deletions(-) create mode 100644 src/constants/words.ts create mode 100644 src/lib/storage.ts create mode 100644 src/utils/wallet.ts diff --git a/src/appTypes/Wallet.ts b/src/appTypes/Wallet.ts index b870fb3ac..d8d6a526d 100644 --- a/src/appTypes/Wallet.ts +++ b/src/appTypes/Wallet.ts @@ -3,7 +3,7 @@ export interface WalletMetadata { mnemonic: string; newMnemonic?: string; number: number; - hash: string; + hash?: string; createdAt?: number; } diff --git a/src/constants/words.ts b/src/constants/words.ts new file mode 100644 index 000000000..5d9d94699 --- /dev/null +++ b/src/constants/words.ts @@ -0,0 +1,2050 @@ +export const DEFAULT_WORDS = [ + 'abandon', + 'ability', + 'able', + 'about', + 'above', + 'absent', + 'absorb', + 'abstract', + 'absurd', + 'abuse', + 'access', + 'accident', + 'account', + 'accuse', + 'achieve', + 'acid', + 'acoustic', + 'acquire', + 'across', + 'act', + 'action', + 'actor', + 'actress', + 'actual', + 'adapt', + 'add', + 'addict', + 'address', + 'adjust', + 'admit', + 'adult', + 'advance', + 'advice', + 'aerobic', + 'affair', + 'afford', + 'afraid', + 'again', + 'age', + 'agent', + 'agree', + 'ahead', + 'aim', + 'air', + 'airport', + 'aisle', + 'alarm', + 'album', + 'alcohol', + 'alert', + 'alien', + 'all', + 'alley', + 'allow', + 'almost', + 'alone', + 'alpha', + 'already', + 'also', + 'alter', + 'always', + 'amateur', + 'amazing', + 'among', + 'amount', + 'amused', + 'analyst', + 'anchor', + 'ancient', + 'anger', + 'angle', + 'angry', + 'animal', + 'ankle', + 'announce', + 'annual', + 'another', + 'answer', + 'antenna', + 'antique', + 'anxiety', + 'any', + 'apart', + 'apology', + 'appear', + 'apple', + 'approve', + 'april', + 'arch', + 'arctic', + 'area', + 'arena', + 'argue', + 'arm', + 'armed', + 'armor', + 'army', + 'around', + 'arrange', + 'arrest', + 'arrive', + 'arrow', + 'art', + 'artefact', + 'artist', + 'artwork', + 'ask', + 'aspect', + 'assault', + 'asset', + 'assist', + 'assume', + 'asthma', + 'athlete', + 'atom', + 'attack', + 'attend', + 'attitude', + 'attract', + 'auction', + 'audit', + 'august', + 'aunt', + 'author', + 'auto', + 'autumn', + 'average', + 'avocado', + 'avoid', + 'awake', + 'aware', + 'away', + 'awesome', + 'awful', + 'awkward', + 'axis', + 'baby', + 'bachelor', + 'bacon', + 'badge', + 'bag', + 'balance', + 'balcony', + 'ball', + 'bamboo', + 'banana', + 'banner', + 'bar', + 'barely', + 'bargain', + 'barrel', + 'base', + 'basic', + 'basket', + 'battle', + 'beach', + 'bean', + 'beauty', + 'because', + 'become', + 'beef', + 'before', + 'begin', + 'behave', + 'behind', + 'believe', + 'below', + 'belt', + 'bench', + 'benefit', + 'best', + 'betray', + 'better', + 'between', + 'beyond', + 'bicycle', + 'bid', + 'bike', + 'bind', + 'biology', + 'bird', + 'birth', + 'bitter', + 'black', + 'blade', + 'blame', + 'blanket', + 'blast', + 'bleak', + 'bless', + 'blind', + 'blood', + 'blossom', + 'blouse', + 'blue', + 'blur', + 'blush', + 'board', + 'boat', + 'body', + 'boil', + 'bomb', + 'bone', + 'bonus', + 'book', + 'boost', + 'border', + 'boring', + 'borrow', + 'boss', + 'bottom', + 'bounce', + 'box', + 'boy', + 'bracket', + 'brain', + 'brand', + 'brass', + 'brave', + 'bread', + 'breeze', + 'brick', + 'bridge', + 'brief', + 'bright', + 'bring', + 'brisk', + 'broccoli', + 'broken', + 'bronze', + 'broom', + 'brother', + 'brown', + 'brush', + 'bubble', + 'buddy', + 'budget', + 'buffalo', + 'build', + 'bulb', + 'bulk', + 'bullet', + 'bundle', + 'bunker', + 'burden', + 'burger', + 'burst', + 'bus', + 'business', + 'busy', + 'butter', + 'buyer', + 'buzz', + 'cabbage', + 'cabin', + 'cable', + 'cactus', + 'cage', + 'cake', + 'call', + 'calm', + 'camera', + 'camp', + 'can', + 'canal', + 'cancel', + 'candy', + 'cannon', + 'canoe', + 'canvas', + 'canyon', + 'capable', + 'capital', + 'captain', + 'car', + 'carbon', + 'card', + 'cargo', + 'carpet', + 'carry', + 'cart', + 'case', + 'cash', + 'casino', + 'castle', + 'casual', + 'cat', + 'catalog', + 'catch', + 'category', + 'cattle', + 'caught', + 'cause', + 'caution', + 'cave', + 'ceiling', + 'celery', + 'cement', + 'census', + 'century', + 'cereal', + 'certain', + 'chair', + 'chalk', + 'champion', + 'change', + 'chaos', + 'chapter', + 'charge', + 'chase', + 'chat', + 'cheap', + 'check', + 'cheese', + 'chef', + 'cherry', + 'chest', + 'chicken', + 'chief', + 'child', + 'chimney', + 'choice', + 'choose', + 'chronic', + 'chuckle', + 'chunk', + 'churn', + 'cigar', + 'cinnamon', + 'circle', + 'citizen', + 'city', + 'civil', + 'claim', + 'clap', + 'clarify', + 'claw', + 'clay', + 'clean', + 'clerk', + 'clever', + 'click', + 'client', + 'cliff', + 'climb', + 'clinic', + 'clip', + 'clock', + 'clog', + 'close', + 'cloth', + 'cloud', + 'clown', + 'club', + 'clump', + 'cluster', + 'clutch', + 'coach', + 'coast', + 'coconut', + 'code', + 'coffee', + 'coil', + 'coin', + 'collect', + 'color', + 'column', + 'combine', + 'come', + 'comfort', + 'comic', + 'common', + 'company', + 'concert', + 'conduct', + 'confirm', + 'congress', + 'connect', + 'consider', + 'control', + 'convince', + 'cook', + 'cool', + 'copper', + 'copy', + 'coral', + 'core', + 'corn', + 'correct', + 'cost', + 'cotton', + 'couch', + 'country', + 'couple', + 'course', + 'cousin', + 'cover', + 'coyote', + 'crack', + 'cradle', + 'craft', + 'cram', + 'crane', + 'crash', + 'crater', + 'crawl', + 'crazy', + 'cream', + 'credit', + 'creek', + 'crew', + 'cricket', + 'crime', + 'crisp', + 'critic', + 'crop', + 'cross', + 'crouch', + 'crowd', + 'crucial', + 'cruel', + 'cruise', + 'crumble', + 'crunch', + 'crush', + 'cry', + 'crystal', + 'cube', + 'culture', + 'cup', + 'cupboard', + 'curious', + 'current', + 'curtain', + 'curve', + 'cushion', + 'custom', + 'cute', + 'cycle', + 'dad', + 'damage', + 'damp', + 'dance', + 'danger', + 'daring', + 'dash', + 'daughter', + 'dawn', + 'day', + 'deal', + 'debate', + 'debris', + 'decade', + 'december', + 'decide', + 'decline', + 'decorate', + 'decrease', + 'deer', + 'defense', + 'define', + 'defy', + 'degree', + 'delay', + 'deliver', + 'demand', + 'demise', + 'denial', + 'dentist', + 'deny', + 'depart', + 'depend', + 'deposit', + 'depth', + 'deputy', + 'derive', + 'describe', + 'desert', + 'design', + 'desk', + 'despair', + 'destroy', + 'detail', + 'detect', + 'develop', + 'device', + 'devote', + 'diagram', + 'dial', + 'diamond', + 'diary', + 'dice', + 'diesel', + 'diet', + 'differ', + 'digital', + 'dignity', + 'dilemma', + 'dinner', + 'dinosaur', + 'direct', + 'dirt', + 'disagree', + 'discover', + 'disease', + 'dish', + 'dismiss', + 'disorder', + 'display', + 'distance', + 'divert', + 'divide', + 'divorce', + 'dizzy', + 'doctor', + 'document', + 'dog', + 'doll', + 'dolphin', + 'domain', + 'donate', + 'donkey', + 'donor', + 'door', + 'dose', + 'double', + 'dove', + 'draft', + 'dragon', + 'drama', + 'drastic', + 'draw', + 'dream', + 'dress', + 'drift', + 'drill', + 'drink', + 'drip', + 'drive', + 'drop', + 'drum', + 'dry', + 'duck', + 'dumb', + 'dune', + 'during', + 'dust', + 'dutch', + 'duty', + 'dwarf', + 'dynamic', + 'eager', + 'eagle', + 'early', + 'earn', + 'earth', + 'easily', + 'east', + 'easy', + 'echo', + 'ecology', + 'economy', + 'edge', + 'edit', + 'educate', + 'effort', + 'egg', + 'eight', + 'either', + 'elbow', + 'elder', + 'electric', + 'elegant', + 'element', + 'elephant', + 'elevator', + 'elite', + 'else', + 'embark', + 'embody', + 'embrace', + 'emerge', + 'emotion', + 'employ', + 'empower', + 'empty', + 'enable', + 'enact', + 'end', + 'endless', + 'endorse', + 'enemy', + 'energy', + 'enforce', + 'engage', + 'engine', + 'enhance', + 'enjoy', + 'enlist', + 'enough', + 'enrich', + 'enroll', + 'ensure', + 'enter', + 'entire', + 'entry', + 'envelope', + 'episode', + 'equal', + 'equip', + 'era', + 'erase', + 'erode', + 'erosion', + 'error', + 'erupt', + 'escape', + 'essay', + 'essence', + 'estate', + 'eternal', + 'ethics', + 'evidence', + 'evil', + 'evoke', + 'evolve', + 'exact', + 'example', + 'excess', + 'exchange', + 'excite', + 'exclude', + 'excuse', + 'execute', + 'exercise', + 'exhaust', + 'exhibit', + 'exile', + 'exist', + 'exit', + 'exotic', + 'expand', + 'expect', + 'expire', + 'explain', + 'expose', + 'express', + 'extend', + 'extra', + 'eye', + 'eyebrow', + 'fabric', + 'face', + 'faculty', + 'fade', + 'faint', + 'faith', + 'fall', + 'false', + 'fame', + 'family', + 'famous', + 'fan', + 'fancy', + 'fantasy', + 'farm', + 'fashion', + 'fat', + 'fatal', + 'father', + 'fatigue', + 'fault', + 'favorite', + 'feature', + 'february', + 'federal', + 'fee', + 'feed', + 'feel', + 'female', + 'fence', + 'festival', + 'fetch', + 'fever', + 'few', + 'fiber', + 'fiction', + 'field', + 'figure', + 'file', + 'film', + 'filter', + 'final', + 'find', + 'fine', + 'finger', + 'finish', + 'fire', + 'firm', + 'first', + 'fiscal', + 'fish', + 'fit', + 'fitness', + 'fix', + 'flag', + 'flame', + 'flash', + 'flat', + 'flavor', + 'flee', + 'flight', + 'flip', + 'float', + 'flock', + 'floor', + 'flower', + 'fluid', + 'flush', + 'fly', + 'foam', + 'focus', + 'fog', + 'foil', + 'fold', + 'follow', + 'food', + 'foot', + 'force', + 'forest', + 'forget', + 'fork', + 'fortune', + 'forum', + 'forward', + 'fossil', + 'foster', + 'found', + 'fox', + 'fragile', + 'frame', + 'frequent', + 'fresh', + 'friend', + 'fringe', + 'frog', + 'front', + 'frost', + 'frown', + 'frozen', + 'fruit', + 'fuel', + 'fun', + 'funny', + 'furnace', + 'fury', + 'future', + 'gadget', + 'gain', + 'galaxy', + 'gallery', + 'game', + 'gap', + 'garage', + 'garbage', + 'garden', + 'garlic', + 'garment', + 'gas', + 'gasp', + 'gate', + 'gather', + 'gauge', + 'gaze', + 'general', + 'genius', + 'genre', + 'gentle', + 'genuine', + 'gesture', + 'ghost', + 'giant', + 'gift', + 'giggle', + 'ginger', + 'giraffe', + 'girl', + 'give', + 'glad', + 'glance', + 'glare', + 'glass', + 'glide', + 'glimpse', + 'globe', + 'gloom', + 'glory', + 'glove', + 'glow', + 'glue', + 'goat', + 'goddess', + 'gold', + 'good', + 'goose', + 'gorilla', + 'gospel', + 'gossip', + 'govern', + 'gown', + 'grab', + 'grace', + 'grain', + 'grant', + 'grape', + 'grass', + 'gravity', + 'great', + 'green', + 'grid', + 'grief', + 'grit', + 'grocery', + 'group', + 'grow', + 'grunt', + 'guard', + 'guess', + 'guide', + 'guilt', + 'guitar', + 'gun', + 'gym', + 'habit', + 'hair', + 'half', + 'hammer', + 'hamster', + 'hand', + 'happy', + 'harbor', + 'hard', + 'harsh', + 'harvest', + 'hat', + 'have', + 'hawk', + 'hazard', + 'head', + 'health', + 'heart', + 'heavy', + 'hedgehog', + 'height', + 'hello', + 'helmet', + 'help', + 'hen', + 'hero', + 'hidden', + 'high', + 'hill', + 'hint', + 'hip', + 'hire', + 'history', + 'hobby', + 'hockey', + 'hold', + 'hole', + 'holiday', + 'hollow', + 'home', + 'honey', + 'hood', + 'hope', + 'horn', + 'horror', + 'horse', + 'hospital', + 'host', + 'hotel', + 'hour', + 'hover', + 'hub', + 'huge', + 'human', + 'humble', + 'humor', + 'hundred', + 'hungry', + 'hunt', + 'hurdle', + 'hurry', + 'hurt', + 'husband', + 'hybrid', + 'ice', + 'icon', + 'idea', + 'identify', + 'idle', + 'ignore', + 'ill', + 'illegal', + 'illness', + 'image', + 'imitate', + 'immense', + 'immune', + 'impact', + 'impose', + 'improve', + 'impulse', + 'inch', + 'include', + 'income', + 'increase', + 'index', + 'indicate', + 'indoor', + 'industry', + 'infant', + 'inflict', + 'inform', + 'inhale', + 'inherit', + 'initial', + 'inject', + 'injury', + 'inmate', + 'inner', + 'innocent', + 'input', + 'inquiry', + 'insane', + 'insect', + 'inside', + 'inspire', + 'install', + 'intact', + 'interest', + 'into', + 'invest', + 'invite', + 'involve', + 'iron', + 'island', + 'isolate', + 'issue', + 'item', + 'ivory', + 'jacket', + 'jaguar', + 'jar', + 'jazz', + 'jealous', + 'jeans', + 'jelly', + 'jewel', + 'job', + 'join', + 'joke', + 'journey', + 'joy', + 'judge', + 'juice', + 'jump', + 'jungle', + 'junior', + 'junk', + 'just', + 'kangaroo', + 'keen', + 'keep', + 'ketchup', + 'key', + 'kick', + 'kid', + 'kidney', + 'kind', + 'kingdom', + 'kiss', + 'kit', + 'kitchen', + 'kite', + 'kitten', + 'kiwi', + 'knee', + 'knife', + 'knock', + 'know', + 'lab', + 'label', + 'labor', + 'ladder', + 'lady', + 'lake', + 'lamp', + 'language', + 'laptop', + 'large', + 'later', + 'latin', + 'laugh', + 'laundry', + 'lava', + 'law', + 'lawn', + 'lawsuit', + 'layer', + 'lazy', + 'leader', + 'leaf', + 'learn', + 'leave', + 'lecture', + 'left', + 'leg', + 'legal', + 'legend', + 'leisure', + 'lemon', + 'lend', + 'length', + 'lens', + 'leopard', + 'lesson', + 'letter', + 'level', + 'liar', + 'liberty', + 'library', + 'license', + 'life', + 'lift', + 'light', + 'like', + 'limb', + 'limit', + 'link', + 'lion', + 'liquid', + 'list', + 'little', + 'live', + 'lizard', + 'load', + 'loan', + 'lobster', + 'local', + 'lock', + 'logic', + 'lonely', + 'long', + 'loop', + 'lottery', + 'loud', + 'lounge', + 'love', + 'loyal', + 'lucky', + 'luggage', + 'lumber', + 'lunar', + 'lunch', + 'luxury', + 'lyrics', + 'machine', + 'mad', + 'magic', + 'magnet', + 'maid', + 'mail', + 'main', + 'major', + 'make', + 'mammal', + 'man', + 'manage', + 'mandate', + 'mango', + 'mansion', + 'manual', + 'maple', + 'marble', + 'march', + 'margin', + 'marine', + 'market', + 'marriage', + 'mask', + 'mass', + 'master', + 'match', + 'material', + 'math', + 'matrix', + 'matter', + 'maximum', + 'maze', + 'meadow', + 'mean', + 'measure', + 'meat', + 'mechanic', + 'medal', + 'media', + 'melody', + 'melt', + 'member', + 'memory', + 'mention', + 'menu', + 'mercy', + 'merge', + 'merit', + 'merry', + 'mesh', + 'message', + 'metal', + 'method', + 'middle', + 'midnight', + 'milk', + 'million', + 'mimic', + 'mind', + 'minimum', + 'minor', + 'minute', + 'miracle', + 'mirror', + 'misery', + 'miss', + 'mistake', + 'mix', + 'mixed', + 'mixture', + 'mobile', + 'model', + 'modify', + 'mom', + 'moment', + 'monitor', + 'monkey', + 'monster', + 'month', + 'moon', + 'moral', + 'more', + 'morning', + 'mosquito', + 'mother', + 'motion', + 'motor', + 'mountain', + 'mouse', + 'move', + 'movie', + 'much', + 'muffin', + 'mule', + 'multiply', + 'muscle', + 'museum', + 'mushroom', + 'music', + 'must', + 'mutual', + 'myself', + 'mystery', + 'myth', + 'naive', + 'name', + 'napkin', + 'narrow', + 'nasty', + 'nation', + 'nature', + 'near', + 'neck', + 'need', + 'negative', + 'neglect', + 'neither', + 'nephew', + 'nerve', + 'nest', + 'net', + 'network', + 'neutral', + 'never', + 'news', + 'next', + 'nice', + 'night', + 'noble', + 'noise', + 'nominee', + 'noodle', + 'normal', + 'north', + 'nose', + 'notable', + 'note', + 'nothing', + 'notice', + 'novel', + 'now', + 'nuclear', + 'number', + 'nurse', + 'nut', + 'oak', + 'obey', + 'object', + 'oblige', + 'obscure', + 'observe', + 'obtain', + 'obvious', + 'occur', + 'ocean', + 'october', + 'odor', + 'off', + 'offer', + 'office', + 'often', + 'oil', + 'okay', + 'old', + 'olive', + 'olympic', + 'omit', + 'once', + 'one', + 'onion', + 'online', + 'only', + 'open', + 'opera', + 'opinion', + 'oppose', + 'option', + 'orange', + 'orbit', + 'orchard', + 'order', + 'ordinary', + 'organ', + 'orient', + 'original', + 'orphan', + 'ostrich', + 'other', + 'outdoor', + 'outer', + 'output', + 'outside', + 'oval', + 'oven', + 'over', + 'own', + 'owner', + 'oxygen', + 'oyster', + 'ozone', + 'pact', + 'paddle', + 'page', + 'pair', + 'palace', + 'palm', + 'panda', + 'panel', + 'panic', + 'panther', + 'paper', + 'parade', + 'parent', + 'park', + 'parrot', + 'party', + 'pass', + 'patch', + 'path', + 'patient', + 'patrol', + 'pattern', + 'pause', + 'pave', + 'payment', + 'peace', + 'peanut', + 'pear', + 'peasant', + 'pelican', + 'pen', + 'penalty', + 'pencil', + 'people', + 'pepper', + 'perfect', + 'permit', + 'person', + 'pet', + 'phone', + 'photo', + 'phrase', + 'physical', + 'piano', + 'picnic', + 'picture', + 'piece', + 'pig', + 'pigeon', + 'pill', + 'pilot', + 'pink', + 'pioneer', + 'pipe', + 'pistol', + 'pitch', + 'pizza', + 'place', + 'planet', + 'plastic', + 'plate', + 'play', + 'please', + 'pledge', + 'pluck', + 'plug', + 'plunge', + 'poem', + 'poet', + 'point', + 'polar', + 'pole', + 'police', + 'pond', + 'pony', + 'pool', + 'popular', + 'portion', + 'position', + 'possible', + 'post', + 'potato', + 'pottery', + 'poverty', + 'powder', + 'power', + 'practice', + 'praise', + 'predict', + 'prefer', + 'prepare', + 'present', + 'pretty', + 'prevent', + 'price', + 'pride', + 'primary', + 'print', + 'priority', + 'prison', + 'private', + 'prize', + 'problem', + 'process', + 'produce', + 'profit', + 'program', + 'project', + 'promote', + 'proof', + 'property', + 'prosper', + 'protect', + 'proud', + 'provide', + 'public', + 'pudding', + 'pull', + 'pulp', + 'pulse', + 'pumpkin', + 'punch', + 'pupil', + 'puppy', + 'purchase', + 'purity', + 'purpose', + 'purse', + 'push', + 'put', + 'puzzle', + 'pyramid', + 'quality', + 'quantum', + 'quarter', + 'question', + 'quick', + 'quit', + 'quiz', + 'quote', + 'rabbit', + 'raccoon', + 'race', + 'rack', + 'radar', + 'radio', + 'rail', + 'rain', + 'raise', + 'rally', + 'ramp', + 'ranch', + 'random', + 'range', + 'rapid', + 'rare', + 'rate', + 'rather', + 'raven', + 'raw', + 'razor', + 'ready', + 'real', + 'reason', + 'rebel', + 'rebuild', + 'recall', + 'receive', + 'recipe', + 'record', + 'recycle', + 'reduce', + 'reflect', + 'reform', + 'refuse', + 'region', + 'regret', + 'regular', + 'reject', + 'relax', + 'release', + 'relief', + 'rely', + 'remain', + 'remember', + 'remind', + 'remove', + 'render', + 'renew', + 'rent', + 'reopen', + 'repair', + 'repeat', + 'replace', + 'report', + 'require', + 'rescue', + 'resemble', + 'resist', + 'resource', + 'response', + 'result', + 'retire', + 'retreat', + 'return', + 'reunion', + 'reveal', + 'review', + 'reward', + 'rhythm', + 'rib', + 'ribbon', + 'rice', + 'rich', + 'ride', + 'ridge', + 'rifle', + 'right', + 'rigid', + 'ring', + 'riot', + 'ripple', + 'risk', + 'ritual', + 'rival', + 'river', + 'road', + 'roast', + 'robot', + 'robust', + 'rocket', + 'romance', + 'roof', + 'rookie', + 'room', + 'rose', + 'rotate', + 'rough', + 'round', + 'route', + 'royal', + 'rubber', + 'rude', + 'rug', + 'rule', + 'run', + 'runway', + 'rural', + 'sad', + 'saddle', + 'sadness', + 'safe', + 'sail', + 'salad', + 'salmon', + 'salon', + 'salt', + 'salute', + 'same', + 'sample', + 'sand', + 'satisfy', + 'satoshi', + 'sauce', + 'sausage', + 'save', + 'say', + 'scale', + 'scan', + 'scare', + 'scatter', + 'scene', + 'scheme', + 'school', + 'science', + 'scissors', + 'scorpion', + 'scout', + 'scrap', + 'screen', + 'script', + 'scrub', + 'sea', + 'search', + 'season', + 'seat', + 'second', + 'secret', + 'section', + 'security', + 'seed', + 'seek', + 'segment', + 'select', + 'sell', + 'seminar', + 'senior', + 'sense', + 'sentence', + 'series', + 'service', + 'session', + 'settle', + 'setup', + 'seven', + 'shadow', + 'shaft', + 'shallow', + 'share', + 'shed', + 'shell', + 'sheriff', + 'shield', + 'shift', + 'shine', + 'ship', + 'shiver', + 'shock', + 'shoe', + 'shoot', + 'shop', + 'short', + 'shoulder', + 'shove', + 'shrimp', + 'shrug', + 'shuffle', + 'shy', + 'sibling', + 'sick', + 'side', + 'siege', + 'sight', + 'sign', + 'silent', + 'silk', + 'silly', + 'silver', + 'similar', + 'simple', + 'since', + 'sing', + 'siren', + 'sister', + 'situate', + 'six', + 'size', + 'skate', + 'sketch', + 'ski', + 'skill', + 'skin', + 'skirt', + 'skull', + 'slab', + 'slam', + 'sleep', + 'slender', + 'slice', + 'slide', + 'slight', + 'slim', + 'slogan', + 'slot', + 'slow', + 'slush', + 'small', + 'smart', + 'smile', + 'smoke', + 'smooth', + 'snack', + 'snake', + 'snap', + 'sniff', + 'snow', + 'soap', + 'soccer', + 'social', + 'sock', + 'soda', + 'soft', + 'solar', + 'soldier', + 'solid', + 'solution', + 'solve', + 'someone', + 'song', + 'soon', + 'sorry', + 'sort', + 'soul', + 'sound', + 'soup', + 'source', + 'south', + 'space', + 'spare', + 'spatial', + 'spawn', + 'speak', + 'special', + 'speed', + 'spell', + 'spend', + 'sphere', + 'spice', + 'spider', + 'spike', + 'spin', + 'spirit', + 'split', + 'spoil', + 'sponsor', + 'spoon', + 'sport', + 'spot', + 'spray', + 'spread', + 'spring', + 'spy', + 'square', + 'squeeze', + 'squirrel', + 'stable', + 'stadium', + 'staff', + 'stage', + 'stairs', + 'stamp', + 'stand', + 'start', + 'state', + 'stay', + 'steak', + 'steel', + 'stem', + 'step', + 'stereo', + 'stick', + 'still', + 'sting', + 'stock', + 'stomach', + 'stone', + 'stool', + 'story', + 'stove', + 'strategy', + 'street', + 'strike', + 'strong', + 'struggle', + 'student', + 'stuff', + 'stumble', + 'style', + 'subject', + 'submit', + 'subway', + 'success', + 'such', + 'sudden', + 'suffer', + 'sugar', + 'suggest', + 'suit', + 'summer', + 'sun', + 'sunny', + 'sunset', + 'super', + 'supply', + 'supreme', + 'sure', + 'surface', + 'surge', + 'surprise', + 'surround', + 'survey', + 'suspect', + 'sustain', + 'swallow', + 'swamp', + 'swap', + 'swarm', + 'swear', + 'sweet', + 'swift', + 'swim', + 'swing', + 'switch', + 'sword', + 'symbol', + 'symptom', + 'syrup', + 'system', + 'table', + 'tackle', + 'tag', + 'tail', + 'talent', + 'talk', + 'tank', + 'tape', + 'target', + 'task', + 'taste', + 'tattoo', + 'taxi', + 'teach', + 'team', + 'tell', + 'ten', + 'tenant', + 'tennis', + 'tent', + 'term', + 'test', + 'text', + 'thank', + 'that', + 'theme', + 'then', + 'theory', + 'there', + 'they', + 'thing', + 'this', + 'thought', + 'three', + 'thrive', + 'throw', + 'thumb', + 'thunder', + 'ticket', + 'tide', + 'tiger', + 'tilt', + 'timber', + 'time', + 'tiny', + 'tip', + 'tired', + 'tissue', + 'title', + 'toast', + 'tobacco', + 'today', + 'toddler', + 'toe', + 'together', + 'toilet', + 'token', + 'tomato', + 'tomorrow', + 'tone', + 'tongue', + 'tonight', + 'tool', + 'tooth', + 'top', + 'topic', + 'topple', + 'torch', + 'tornado', + 'tortoise', + 'toss', + 'total', + 'tourist', + 'toward', + 'tower', + 'town', + 'toy', + 'track', + 'trade', + 'traffic', + 'tragic', + 'train', + 'transfer', + 'trap', + 'trash', + 'travel', + 'tray', + 'treat', + 'tree', + 'trend', + 'trial', + 'tribe', + 'trick', + 'trigger', + 'trim', + 'trip', + 'trophy', + 'trouble', + 'truck', + 'true', + 'truly', + 'trumpet', + 'trust', + 'truth', + 'try', + 'tube', + 'tuition', + 'tumble', + 'tuna', + 'tunnel', + 'turkey', + 'turn', + 'turtle', + 'twelve', + 'twenty', + 'twice', + 'twin', + 'twist', + 'two', + 'type', + 'typical', + 'ugly', + 'umbrella', + 'unable', + 'unaware', + 'uncle', + 'uncover', + 'under', + 'undo', + 'unfair', + 'unfold', + 'unhappy', + 'uniform', + 'unique', + 'unit', + 'universe', + 'unknown', + 'unlock', + 'until', + 'unusual', + 'unveil', + 'update', + 'upgrade', + 'uphold', + 'upon', + 'upper', + 'upset', + 'urban', + 'urge', + 'usage', + 'use', + 'used', + 'useful', + 'useless', + 'usual', + 'utility', + 'vacant', + 'vacuum', + 'vague', + 'valid', + 'valley', + 'valve', + 'van', + 'vanish', + 'vapor', + 'various', + 'vast', + 'vault', + 'vehicle', + 'velvet', + 'vendor', + 'venture', + 'venue', + 'verb', + 'verify', + 'version', + 'very', + 'vessel', + 'veteran', + 'viable', + 'vibrant', + 'vicious', + 'victory', + 'video', + 'view', + 'village', + 'vintage', + 'violin', + 'virtual', + 'virus', + 'visa', + 'visit', + 'visual', + 'vital', + 'vivid', + 'vocal', + 'voice', + 'void', + 'volcano', + 'volume', + 'vote', + 'voyage', + 'wage', + 'wagon', + 'wait', + 'walk', + 'wall', + 'walnut', + 'want', + 'warfare', + 'warm', + 'warrior', + 'wash', + 'wasp', + 'waste', + 'water', + 'wave', + 'way', + 'wealth', + 'weapon', + 'wear', + 'weasel', + 'weather', + 'web', + 'wedding', + 'weekend', + 'weird', + 'welcome', + 'west', + 'wet', + 'whale', + 'what', + 'wheat', + 'wheel', + 'when', + 'where', + 'whip', + 'whisper', + 'wide', + 'width', + 'wife', + 'wild', + 'will', + 'win', + 'window', + 'wine', + 'wing', + 'wink', + 'winner', + 'winter', + 'wire', + 'wisdom', + 'wise', + 'wish', + 'witness', + 'wolf', + 'woman', + 'wonder', + 'wood', + 'wool', + 'word', + 'work', + 'world', + 'worry', + 'worth', + 'wrap', + 'wreck', + 'wrestle', + 'wrist', + 'write', + 'wrong', + 'yard', + 'year', + 'yellow', + 'you', + 'young', + 'youth', + 'zebra', + 'zero', + 'zone', + 'zoo' +]; diff --git a/src/lib/storage.ts b/src/lib/storage.ts new file mode 100644 index 000000000..070ac6d76 --- /dev/null +++ b/src/lib/storage.ts @@ -0,0 +1,13 @@ +import { Wallet } from '@models/Wallet'; +import { Cache, CacheKey } from '@utils/cache'; + +export class AirDAOStorage { + async isMnemonicAlreadySaved(newMnemonic: any) { + return true; + } + async saveMnemonic(mnemonic: any): string { + return; + } +} + +export default new AirDAOStorage(); diff --git a/src/models/Wallet.ts b/src/models/Wallet.ts index f99424a03..6df0c264a 100644 --- a/src/models/Wallet.ts +++ b/src/models/Wallet.ts @@ -4,12 +4,14 @@ export class Wallet { hash: string; name: string; mnemonic: string; + number: number; createdAt: Date; constructor(details: WalletMetadata) { - this.hash = details.hash; + this.hash = details.hash || ''; this.name = details.name; this.mnemonic = details.mnemonic; + this.number = details.number; this.createdAt = details.createdAt ? new Date(details.createdAt) : new Date(); diff --git a/src/screens/CreateWallet/CreateWalletStep2.tsx b/src/screens/CreateWallet/CreateWalletStep2.tsx index 2f4c605cb..5589f1cf8 100644 --- a/src/screens/CreateWallet/CreateWalletStep2.tsx +++ b/src/screens/CreateWallet/CreateWalletStep2.tsx @@ -6,6 +6,7 @@ import { Header } from '@components/composite'; import { useAddWalletContext } from '@contexts'; import { moderateScale, scale, verticalScale } from '@utils/scaling'; import { COLORS } from '@constants/colors'; +import { WalletUtils } from '@utils/wallet'; export const CreateWalletStep2 = () => { const { walletMnemonic } = useAddWalletContext(); @@ -33,9 +34,14 @@ export const CreateWalletStep2 = () => { } setLoading(true); Alert.alert('success'); - //TODO implement + // TODO fix number + WalletUtils.processWallet({ + number: 0, + mnemonic: walletMnemonic, + name: '' + }); setLoading(false); - }, [walletMnemonicArrayDefault, walletMnemonicSelected]); + }, [walletMnemonic, walletMnemonicArrayDefault, walletMnemonicSelected]); useEffect(() => { validateMnemonic(); diff --git a/src/utils/mnemonics.ts b/src/utils/mnemonics.ts index 3e0c8917e..16ee84211 100644 --- a/src/utils/mnemonics.ts +++ b/src/utils/mnemonics.ts @@ -1,5 +1,6 @@ // eslint-disable-next-line @typescript-eslint/no-var-requires const bip39 = require('bip39'); +import { DEFAULT_WORDS } from '@constants/words'; import { Crypto } from './crypto'; const generateNewMnemonic = async (size = 256) => { @@ -10,4 +11,22 @@ const generateNewMnemonic = async (size = 256) => { return { mnemonic, hash }; }; -export const MnemonicUtils = { generateNewMnemonic }; +const recheckMnemonic = (mnemonic: string): string => { + const words = mnemonic.trim().toLowerCase().split(/\s+/g); + const checked = []; + let word; + for (word of words) { + if (!word || word.length < 2) continue; + const index = DEFAULT_WORDS.indexOf(word); + if (index === -1) { + throw new Error('BlocksoftKeysStorage invalid word ' + word); + } + checked.push(word); + } + if (checked.length <= 11) { + throw new Error('BlocksoftKeysStorage invalid words length ' + mnemonic); + } + return checked.join(' '); +}; + +export const MnemonicUtils = { generateNewMnemonic, recheckMnemonic }; diff --git a/src/utils/wallet.ts b/src/utils/wallet.ts new file mode 100644 index 000000000..4ae028ad7 --- /dev/null +++ b/src/utils/wallet.ts @@ -0,0 +1,49 @@ +import { WalletInitSource, WalletMetadata } from '@appTypes'; +import { Wallet } from '@models/Wallet'; +import { Crypto } from './crypto'; +import { MnemonicUtils } from './mnemonics'; +import AirDAOStorage from '@lib/storage'; + +const _saveWallet = async (wallet: WalletMetadata) => { + let storedKey = ''; + try { + const prepared = { + mnemonic: wallet.newMnemonic ? wallet.newMnemonic : wallet.mnemonic, + hash: '?' + }; + + prepared.mnemonic = MnemonicUtils.recheckMnemonic(prepared.mnemonic); + prepared.hash = await Crypto.hashMnemonic(prepared.mnemonic); + + const checkKey = await AirDAOStorage.isMnemonicAlreadySaved(prepared); + if (checkKey) { + // @misha should we do something or ui is enough + } + storedKey = await AirDAOStorage.saveMnemonic(prepared); + } catch (e) {} + return storedKey; +}; + +const _getWalletName = async () => { + return 'AirDAO Wallet'; +}; + +const processWallet = async ( + data: Pick, + source = WalletInitSource.GENERATION +) => { + const hash = await _saveWallet(data); + let tmpWalletName = data.name; + + if (!tmpWalletName || tmpWalletName === '') { + tmpWalletName = await _getWalletName(); + } + const fullWallet: Wallet = new Wallet({ ...data, hash, name: tmpWalletName }); + // TODO save to local db + try { + } catch (error) { + throw error; + } +}; + +export const WalletUtils = { processWallet }; From 5a4813c0655dede8d54614afa0620c616a40c900 Mon Sep 17 00:00:00 2001 From: JavidHaji-zada Date: Thu, 20 Jul 2023 10:54:15 +0400 Subject: [PATCH 009/509] added watermelondb --- Providers.tsx | 11 +- package.json | 3 + ...gital+watermelondb-expo-plugin+2.1.2.patch | 34 + patches/@nozbe+watermelondb+0.26.0.patch | 47647 ++++++++++++++++ src/appTypes/Wallet.ts | 1 + src/appTypes/database/DatabaseTable.ts | 2 +- src/database/Database.ts | 20 + src/database/index.ts | 1 + src/database/main.ts | 18 + src/database/models/index.ts | 1 + src/database/models/wallet.ts | 26 + src/database/schemas/index.ts | 7 + src/database/schemas/wallet.ts | 25 + src/models/Wallet.ts | 2 + yarn.lock | 50 +- 15 files changed, 47844 insertions(+), 4 deletions(-) create mode 100644 patches/@morrowdigital+watermelondb-expo-plugin+2.1.2.patch create mode 100644 patches/@nozbe+watermelondb+0.26.0.patch create mode 100644 src/database/Database.ts create mode 100644 src/database/index.ts create mode 100644 src/database/main.ts create mode 100644 src/database/models/index.ts create mode 100644 src/database/models/wallet.ts create mode 100644 src/database/schemas/index.ts create mode 100644 src/database/schemas/wallet.ts diff --git a/Providers.tsx b/Providers.tsx index 033d97398..3a2927171 100644 --- a/Providers.tsx +++ b/Providers.tsx @@ -1,6 +1,7 @@ +import React from 'react'; import { combineComponents } from '@helpers/combineComponents'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import React from 'react'; +import DatabaseProvider from '@nozbe/watermelondb/DatabaseProvider'; import { SafeAreaProvider } from 'react-native-safe-area-context'; import { ListsContextProvider } from '@contexts/ListsContext'; import { @@ -8,6 +9,7 @@ import { AllAddressesProvider, OnboardingContextProvider } from '@contexts'; +import { Database } from '@database'; const queryClient = new QueryClient(); @@ -19,6 +21,12 @@ const WrappedSafeAreaProvider: React.FC = ({ children }: any) => ( {children} ); +const LocalDBProvider: React.FC = ({ children }: any) => ( + + {children} + +); + const independentProviders = [ WrappedQueryClientProvider, WrappedSafeAreaProvider @@ -28,6 +36,7 @@ const independentProviders = [ */ const providers = [ ...independentProviders, + LocalDBProvider, AllAddressesProvider, ListsContextProvider, OnboardingContextProvider, diff --git a/package.json b/package.json index 5fc4aa34a..b3e7858bf 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,10 @@ }, "dependencies": { "@babel/preset-env": "^7.21.5", + "@morrowdigital/watermelondb-expo-plugin": "^2.1.2", "@neverdull-agency/expo-unlimited-secure-store": "^1.0.10", + "@nozbe/watermelondb": "^0.26.0", + "@nozbe/with-observables": "^1.6.0", "@react-native-firebase/analytics": "^17.5.0", "@react-native-firebase/app": "^17.5.0", "@react-native-firebase/crashlytics": "^17.5.0", diff --git a/patches/@morrowdigital+watermelondb-expo-plugin+2.1.2.patch b/patches/@morrowdigital+watermelondb-expo-plugin+2.1.2.patch new file mode 100644 index 000000000..a62941407 --- /dev/null +++ b/patches/@morrowdigital+watermelondb-expo-plugin+2.1.2.patch @@ -0,0 +1,34 @@ +diff --git a/node_modules/@morrowdigital/watermelondb-expo-plugin/build/withWatermelon.js b/node_modules/@morrowdigital/watermelondb-expo-plugin/build/withWatermelon.js +index 4b6fbdc..80618b0 100644 +--- a/node_modules/@morrowdigital/watermelondb-expo-plugin/build/withWatermelon.js ++++ b/node_modules/@morrowdigital/watermelondb-expo-plugin/build/withWatermelon.js +@@ -95,9 +95,26 @@ const withCocoaPods = (config) => { + const patchKey = "post_install"; + const slicedContent = contents.split(patchKey); + slicedContent[0] += `\n +- pod 'WatermelonDB', :path => '../node_modules/@nozbe/watermelondb' +- pod 'React-jsi', :path => '../node_modules/react-native/ReactCommon/jsi', :modular_headers => true +- pod 'simdjson', path: '../node_modules/@nozbe/simdjson', :modular_headers => true\n\n `; ++ # Make sure Watermelon builds with useFrameworks: true, ++ # required by some libraries, e.g react-native-firebase ++ # for more info: https://github.com/react-native-maps/react-native-maps/issues/3597#issuecomment-1168582636 \n ++ $static_pods = [ ++ 'WatermelonDB', ++ 'simdjson', ++ ] ++ pre_install do |installer| ++ Pod::Installer::Xcode::TargetValidator.send(:define_method, :verify_no_static_framework_transitive_dependencies) {} ++ installer.pod_targets.each do |pod| ++ if $static_pods.include?(pod.name) ++ def pod.build_type; ++ Pod::BuildType.static_library # >= 1.9 ++ end ++ end ++ end ++ end ++ pod 'WatermelonDB', :path => '../node_modules/@nozbe/watermelondb' ++ pod 'React-jsi', :path => '../node_modules/react-native/ReactCommon/jsi', :modular_headers => true ++ pod 'simdjson', path: '../node_modules/@nozbe/simdjson', modular_headers: true\n\n`; + await fs.writeFile(filePath, slicedContent.join(patchKey)); + } + else { \ No newline at end of file diff --git a/patches/@nozbe+watermelondb+0.26.0.patch b/patches/@nozbe+watermelondb+0.26.0.patch new file mode 100644 index 000000000..6f3078258 --- /dev/null +++ b/patches/@nozbe+watermelondb+0.26.0.patch @@ -0,0 +1,47647 @@ +diff --git a/node_modules/@nozbe/watermelondb/native/shared/simdjson.cpp b/node_modules/@nozbe/watermelondb/native/shared/simdjson.cpp +new file mode 100644 +index 0000000..74179d9 +--- /dev/null ++++ b/node_modules/@nozbe/watermelondb/native/shared/simdjson.cpp +@@ -0,0 +1,15981 @@ ++/* auto-generated on 2023-01-21 18:07:06 -0500. Do not edit! */ ++/* begin file src/simdjson.cpp */ ++#include "simdjson.h" ++ ++SIMDJSON_PUSH_DISABLE_WARNINGS ++SIMDJSON_DISABLE_UNDESIRED_WARNINGS ++ ++/* begin file src/to_chars.cpp */ ++#include ++#include ++#include ++#include ++ ++namespace simdjson { ++namespace internal { ++/*! ++implements the Grisu2 algorithm for binary to decimal floating-point ++conversion. ++Adapted from JSON for Modern C++ ++ ++This implementation is a slightly modified version of the reference ++implementation which may be obtained from ++http://florian.loitsch.com/publications (bench.tar.gz). ++The code is distributed under the MIT license, Copyright (c) 2009 Florian ++Loitsch. For a detailed description of the algorithm see: [1] Loitsch, "Printing ++Floating-Point Numbers Quickly and Accurately with Integers", Proceedings of the ++ACM SIGPLAN 2010 Conference on Programming Language Design and Implementation, ++PLDI 2010 [2] Burger, Dybvig, "Printing Floating-Point Numbers Quickly and ++Accurately", Proceedings of the ACM SIGPLAN 1996 Conference on Programming ++Language Design and Implementation, PLDI 1996 ++*/ ++namespace dtoa_impl { ++ ++template ++Target reinterpret_bits(const Source source) { ++ static_assert(sizeof(Target) == sizeof(Source), "size mismatch"); ++ ++ Target target; ++ std::memcpy(&target, &source, sizeof(Source)); ++ return target; ++} ++ ++struct diyfp // f * 2^e ++{ ++ static constexpr int kPrecision = 64; // = q ++ ++ std::uint64_t f = 0; ++ int e = 0; ++ ++ constexpr diyfp(std::uint64_t f_, int e_) noexcept : f(f_), e(e_) {} ++ ++ /*! ++ @brief returns x - y ++ @pre x.e == y.e and x.f >= y.f ++ */ ++ static diyfp sub(const diyfp &x, const diyfp &y) noexcept { ++ ++ return {x.f - y.f, x.e}; ++ } ++ ++ /*! ++ @brief returns x * y ++ @note The result is rounded. (Only the upper q bits are returned.) ++ */ ++ static diyfp mul(const diyfp &x, const diyfp &y) noexcept { ++ static_assert(kPrecision == 64, "internal error"); ++ ++ // Computes: ++ // f = round((x.f * y.f) / 2^q) ++ // e = x.e + y.e + q ++ ++ // Emulate the 64-bit * 64-bit multiplication: ++ // ++ // p = u * v ++ // = (u_lo + 2^32 u_hi) (v_lo + 2^32 v_hi) ++ // = (u_lo v_lo ) + 2^32 ((u_lo v_hi ) + (u_hi v_lo )) + ++ // 2^64 (u_hi v_hi ) = (p0 ) + 2^32 ((p1 ) + (p2 )) ++ // + 2^64 (p3 ) = (p0_lo + 2^32 p0_hi) + 2^32 ((p1_lo + ++ // 2^32 p1_hi) + (p2_lo + 2^32 p2_hi)) + 2^64 (p3 ) = ++ // (p0_lo ) + 2^32 (p0_hi + p1_lo + p2_lo ) + 2^64 (p1_hi + ++ // p2_hi + p3) = (p0_lo ) + 2^32 (Q ) + 2^64 (H ) = (p0_lo ) + ++ // 2^32 (Q_lo + 2^32 Q_hi ) + 2^64 (H ) ++ // ++ // (Since Q might be larger than 2^32 - 1) ++ // ++ // = (p0_lo + 2^32 Q_lo) + 2^64 (Q_hi + H) ++ // ++ // (Q_hi + H does not overflow a 64-bit int) ++ // ++ // = p_lo + 2^64 p_hi ++ ++ const std::uint64_t u_lo = x.f & 0xFFFFFFFFu; ++ const std::uint64_t u_hi = x.f >> 32u; ++ const std::uint64_t v_lo = y.f & 0xFFFFFFFFu; ++ const std::uint64_t v_hi = y.f >> 32u; ++ ++ const std::uint64_t p0 = u_lo * v_lo; ++ const std::uint64_t p1 = u_lo * v_hi; ++ const std::uint64_t p2 = u_hi * v_lo; ++ const std::uint64_t p3 = u_hi * v_hi; ++ ++ const std::uint64_t p0_hi = p0 >> 32u; ++ const std::uint64_t p1_lo = p1 & 0xFFFFFFFFu; ++ const std::uint64_t p1_hi = p1 >> 32u; ++ const std::uint64_t p2_lo = p2 & 0xFFFFFFFFu; ++ const std::uint64_t p2_hi = p2 >> 32u; ++ ++ std::uint64_t Q = p0_hi + p1_lo + p2_lo; ++ ++ // The full product might now be computed as ++ // ++ // p_hi = p3 + p2_hi + p1_hi + (Q >> 32) ++ // p_lo = p0_lo + (Q << 32) ++ // ++ // But in this particular case here, the full p_lo is not required. ++ // Effectively we only need to add the highest bit in p_lo to p_hi (and ++ // Q_hi + 1 does not overflow). ++ ++ Q += std::uint64_t{1} << (64u - 32u - 1u); // round, ties up ++ ++ const std::uint64_t h = p3 + p2_hi + p1_hi + (Q >> 32u); ++ ++ return {h, x.e + y.e + 64}; ++ } ++ ++ /*! ++ @brief normalize x such that the significand is >= 2^(q-1) ++ @pre x.f != 0 ++ */ ++ static diyfp normalize(diyfp x) noexcept { ++ ++ while ((x.f >> 63u) == 0) { ++ x.f <<= 1u; ++ x.e--; ++ } ++ ++ return x; ++ } ++ ++ /*! ++ @brief normalize x such that the result has the exponent E ++ @pre e >= x.e and the upper e - x.e bits of x.f must be zero. ++ */ ++ static diyfp normalize_to(const diyfp &x, ++ const int target_exponent) noexcept { ++ const int delta = x.e - target_exponent; ++ ++ return {x.f << delta, target_exponent}; ++ } ++}; ++ ++struct boundaries { ++ diyfp w; ++ diyfp minus; ++ diyfp plus; ++}; ++ ++/*! ++Compute the (normalized) diyfp representing the input number 'value' and its ++boundaries. ++@pre value must be finite and positive ++*/ ++template boundaries compute_boundaries(FloatType value) { ++ ++ // Convert the IEEE representation into a diyfp. ++ // ++ // If v is denormal: ++ // value = 0.F * 2^(1 - bias) = ( F) * 2^(1 - bias - (p-1)) ++ // If v is normalized: ++ // value = 1.F * 2^(E - bias) = (2^(p-1) + F) * 2^(E - bias - (p-1)) ++ ++ static_assert(std::numeric_limits::is_iec559, ++ "internal error: dtoa_short requires an IEEE-754 " ++ "floating-point implementation"); ++ ++ constexpr int kPrecision = ++ std::numeric_limits::digits; // = p (includes the hidden bit) ++ constexpr int kBias = ++ std::numeric_limits::max_exponent - 1 + (kPrecision - 1); ++ constexpr int kMinExp = 1 - kBias; ++ constexpr std::uint64_t kHiddenBit = std::uint64_t{1} ++ << (kPrecision - 1); // = 2^(p-1) ++ ++ using bits_type = typename std::conditional::type; ++ ++ const std::uint64_t bits = reinterpret_bits(value); ++ const std::uint64_t E = bits >> (kPrecision - 1); ++ const std::uint64_t F = bits & (kHiddenBit - 1); ++ ++ const bool is_denormal = E == 0; ++ const diyfp v = is_denormal ++ ? diyfp(F, kMinExp) ++ : diyfp(F + kHiddenBit, static_cast(E) - kBias); ++ ++ // Compute the boundaries m- and m+ of the floating-point value ++ // v = f * 2^e. ++ // ++ // Determine v- and v+, the floating-point predecessor and successor if v, ++ // respectively. ++ // ++ // v- = v - 2^e if f != 2^(p-1) or e == e_min (A) ++ // = v - 2^(e-1) if f == 2^(p-1) and e > e_min (B) ++ // ++ // v+ = v + 2^e ++ // ++ // Let m- = (v- + v) / 2 and m+ = (v + v+) / 2. All real numbers _strictly_ ++ // between m- and m+ round to v, regardless of how the input rounding ++ // algorithm breaks ties. ++ // ++ // ---+-------------+-------------+-------------+-------------+--- (A) ++ // v- m- v m+ v+ ++ // ++ // -----------------+------+------+-------------+-------------+--- (B) ++ // v- m- v m+ v+ ++ ++ const bool lower_boundary_is_closer = F == 0 && E > 1; ++ const diyfp m_plus = diyfp(2 * v.f + 1, v.e - 1); ++ const diyfp m_minus = lower_boundary_is_closer ++ ? diyfp(4 * v.f - 1, v.e - 2) // (B) ++ : diyfp(2 * v.f - 1, v.e - 1); // (A) ++ ++ // Determine the normalized w+ = m+. ++ const diyfp w_plus = diyfp::normalize(m_plus); ++ ++ // Determine w- = m- such that e_(w-) = e_(w+). ++ const diyfp w_minus = diyfp::normalize_to(m_minus, w_plus.e); ++ ++ return {diyfp::normalize(v), w_minus, w_plus}; ++} ++ ++// Given normalized diyfp w, Grisu needs to find a (normalized) cached ++// power-of-ten c, such that the exponent of the product c * w = f * 2^e lies ++// within a certain range [alpha, gamma] (Definition 3.2 from [1]) ++// ++// alpha <= e = e_c + e_w + q <= gamma ++// ++// or ++// ++// f_c * f_w * 2^alpha <= f_c 2^(e_c) * f_w 2^(e_w) * 2^q ++// <= f_c * f_w * 2^gamma ++// ++// Since c and w are normalized, i.e. 2^(q-1) <= f < 2^q, this implies ++// ++// 2^(q-1) * 2^(q-1) * 2^alpha <= c * w * 2^q < 2^q * 2^q * 2^gamma ++// ++// or ++// ++// 2^(q - 2 + alpha) <= c * w < 2^(q + gamma) ++// ++// The choice of (alpha,gamma) determines the size of the table and the form of ++// the digit generation procedure. Using (alpha,gamma)=(-60,-32) works out well ++// in practice: ++// ++// The idea is to cut the number c * w = f * 2^e into two parts, which can be ++// processed independently: An integral part p1, and a fractional part p2: ++// ++// f * 2^e = ( (f div 2^-e) * 2^-e + (f mod 2^-e) ) * 2^e ++// = (f div 2^-e) + (f mod 2^-e) * 2^e ++// = p1 + p2 * 2^e ++// ++// The conversion of p1 into decimal form requires a series of divisions and ++// modulos by (a power of) 10. These operations are faster for 32-bit than for ++// 64-bit integers, so p1 should ideally fit into a 32-bit integer. This can be ++// achieved by choosing ++// ++// -e >= 32 or e <= -32 := gamma ++// ++// In order to convert the fractional part ++// ++// p2 * 2^e = p2 / 2^-e = d[-1] / 10^1 + d[-2] / 10^2 + ... ++// ++// into decimal form, the fraction is repeatedly multiplied by 10 and the digits ++// d[-i] are extracted in order: ++// ++// (10 * p2) div 2^-e = d[-1] ++// (10 * p2) mod 2^-e = d[-2] / 10^1 + ... ++// ++// The multiplication by 10 must not overflow. It is sufficient to choose ++// ++// 10 * p2 < 16 * p2 = 2^4 * p2 <= 2^64. ++// ++// Since p2 = f mod 2^-e < 2^-e, ++// ++// -e <= 60 or e >= -60 := alpha ++ ++constexpr int kAlpha = -60; ++constexpr int kGamma = -32; ++ ++struct cached_power // c = f * 2^e ~= 10^k ++{ ++ std::uint64_t f; ++ int e; ++ int k; ++}; ++ ++/*! ++For a normalized diyfp w = f * 2^e, this function returns a (normalized) cached ++power-of-ten c = f_c * 2^e_c, such that the exponent of the product w * c ++satisfies (Definition 3.2 from [1]) ++ alpha <= e_c + e + q <= gamma. ++*/ ++inline cached_power get_cached_power_for_binary_exponent(int e) { ++ // Now ++ // ++ // alpha <= e_c + e + q <= gamma (1) ++ // ==> f_c * 2^alpha <= c * 2^e * 2^q ++ // ++ // and since the c's are normalized, 2^(q-1) <= f_c, ++ // ++ // ==> 2^(q - 1 + alpha) <= c * 2^(e + q) ++ // ==> 2^(alpha - e - 1) <= c ++ // ++ // If c were an exact power of ten, i.e. c = 10^k, one may determine k as ++ // ++ // k = ceil( log_10( 2^(alpha - e - 1) ) ) ++ // = ceil( (alpha - e - 1) * log_10(2) ) ++ // ++ // From the paper: ++ // "In theory the result of the procedure could be wrong since c is rounded, ++ // and the computation itself is approximated [...]. In practice, however, ++ // this simple function is sufficient." ++ // ++ // For IEEE double precision floating-point numbers converted into ++ // normalized diyfp's w = f * 2^e, with q = 64, ++ // ++ // e >= -1022 (min IEEE exponent) ++ // -52 (p - 1) ++ // -52 (p - 1, possibly normalize denormal IEEE numbers) ++ // -11 (normalize the diyfp) ++ // = -1137 ++ // ++ // and ++ // ++ // e <= +1023 (max IEEE exponent) ++ // -52 (p - 1) ++ // -11 (normalize the diyfp) ++ // = 960 ++ // ++ // This binary exponent range [-1137,960] results in a decimal exponent ++ // range [-307,324]. One does not need to store a cached power for each ++ // k in this range. For each such k it suffices to find a cached power ++ // such that the exponent of the product lies in [alpha,gamma]. ++ // This implies that the difference of the decimal exponents of adjacent ++ // table entries must be less than or equal to ++ // ++ // floor( (gamma - alpha) * log_10(2) ) = 8. ++ // ++ // (A smaller distance gamma-alpha would require a larger table.) ++ ++ // NB: ++ // Actually this function returns c, such that -60 <= e_c + e + 64 <= -34. ++ ++ constexpr int kCachedPowersMinDecExp = -300; ++ constexpr int kCachedPowersDecStep = 8; ++ ++ static constexpr std::array kCachedPowers = {{ ++ {0xAB70FE17C79AC6CA, -1060, -300}, {0xFF77B1FCBEBCDC4F, -1034, -292}, ++ {0xBE5691EF416BD60C, -1007, -284}, {0x8DD01FAD907FFC3C, -980, -276}, ++ {0xD3515C2831559A83, -954, -268}, {0x9D71AC8FADA6C9B5, -927, -260}, ++ {0xEA9C227723EE8BCB, -901, -252}, {0xAECC49914078536D, -874, -244}, ++ {0x823C12795DB6CE57, -847, -236}, {0xC21094364DFB5637, -821, -228}, ++ {0x9096EA6F3848984F, -794, -220}, {0xD77485CB25823AC7, -768, -212}, ++ {0xA086CFCD97BF97F4, -741, -204}, {0xEF340A98172AACE5, -715, -196}, ++ {0xB23867FB2A35B28E, -688, -188}, {0x84C8D4DFD2C63F3B, -661, -180}, ++ {0xC5DD44271AD3CDBA, -635, -172}, {0x936B9FCEBB25C996, -608, -164}, ++ {0xDBAC6C247D62A584, -582, -156}, {0xA3AB66580D5FDAF6, -555, -148}, ++ {0xF3E2F893DEC3F126, -529, -140}, {0xB5B5ADA8AAFF80B8, -502, -132}, ++ {0x87625F056C7C4A8B, -475, -124}, {0xC9BCFF6034C13053, -449, -116}, ++ {0x964E858C91BA2655, -422, -108}, {0xDFF9772470297EBD, -396, -100}, ++ {0xA6DFBD9FB8E5B88F, -369, -92}, {0xF8A95FCF88747D94, -343, -84}, ++ {0xB94470938FA89BCF, -316, -76}, {0x8A08F0F8BF0F156B, -289, -68}, ++ {0xCDB02555653131B6, -263, -60}, {0x993FE2C6D07B7FAC, -236, -52}, ++ {0xE45C10C42A2B3B06, -210, -44}, {0xAA242499697392D3, -183, -36}, ++ {0xFD87B5F28300CA0E, -157, -28}, {0xBCE5086492111AEB, -130, -20}, ++ {0x8CBCCC096F5088CC, -103, -12}, {0xD1B71758E219652C, -77, -4}, ++ {0x9C40000000000000, -50, 4}, {0xE8D4A51000000000, -24, 12}, ++ {0xAD78EBC5AC620000, 3, 20}, {0x813F3978F8940984, 30, 28}, ++ {0xC097CE7BC90715B3, 56, 36}, {0x8F7E32CE7BEA5C70, 83, 44}, ++ {0xD5D238A4ABE98068, 109, 52}, {0x9F4F2726179A2245, 136, 60}, ++ {0xED63A231D4C4FB27, 162, 68}, {0xB0DE65388CC8ADA8, 189, 76}, ++ {0x83C7088E1AAB65DB, 216, 84}, {0xC45D1DF942711D9A, 242, 92}, ++ {0x924D692CA61BE758, 269, 100}, {0xDA01EE641A708DEA, 295, 108}, ++ {0xA26DA3999AEF774A, 322, 116}, {0xF209787BB47D6B85, 348, 124}, ++ {0xB454E4A179DD1877, 375, 132}, {0x865B86925B9BC5C2, 402, 140}, ++ {0xC83553C5C8965D3D, 428, 148}, {0x952AB45CFA97A0B3, 455, 156}, ++ {0xDE469FBD99A05FE3, 481, 164}, {0xA59BC234DB398C25, 508, 172}, ++ {0xF6C69A72A3989F5C, 534, 180}, {0xB7DCBF5354E9BECE, 561, 188}, ++ {0x88FCF317F22241E2, 588, 196}, {0xCC20CE9BD35C78A5, 614, 204}, ++ {0x98165AF37B2153DF, 641, 212}, {0xE2A0B5DC971F303A, 667, 220}, ++ {0xA8D9D1535CE3B396, 694, 228}, {0xFB9B7CD9A4A7443C, 720, 236}, ++ {0xBB764C4CA7A44410, 747, 244}, {0x8BAB8EEFB6409C1A, 774, 252}, ++ {0xD01FEF10A657842C, 800, 260}, {0x9B10A4E5E9913129, 827, 268}, ++ {0xE7109BFBA19C0C9D, 853, 276}, {0xAC2820D9623BF429, 880, 284}, ++ {0x80444B5E7AA7CF85, 907, 292}, {0xBF21E44003ACDD2D, 933, 300}, ++ {0x8E679C2F5E44FF8F, 960, 308}, {0xD433179D9C8CB841, 986, 316}, ++ {0x9E19DB92B4E31BA9, 1013, 324}, ++ }}; ++ ++ // This computation gives exactly the same results for k as ++ // k = ceil((kAlpha - e - 1) * 0.30102999566398114) ++ // for |e| <= 1500, but doesn't require floating-point operations. ++ // NB: log_10(2) ~= 78913 / 2^18 ++ const int f = kAlpha - e - 1; ++ const int k = (f * 78913) / (1 << 18) + static_cast(f > 0); ++ ++ const int index = (-kCachedPowersMinDecExp + k + (kCachedPowersDecStep - 1)) / ++ kCachedPowersDecStep; ++ ++ const cached_power cached = kCachedPowers[static_cast(index)]; ++ ++ return cached; ++} ++ ++/*! ++For n != 0, returns k, such that pow10 := 10^(k-1) <= n < 10^k. ++For n == 0, returns 1 and sets pow10 := 1. ++*/ ++inline int find_largest_pow10(const std::uint32_t n, std::uint32_t &pow10) { ++ // LCOV_EXCL_START ++ if (n >= 1000000000) { ++ pow10 = 1000000000; ++ return 10; ++ } ++ // LCOV_EXCL_STOP ++ else if (n >= 100000000) { ++ pow10 = 100000000; ++ return 9; ++ } else if (n >= 10000000) { ++ pow10 = 10000000; ++ return 8; ++ } else if (n >= 1000000) { ++ pow10 = 1000000; ++ return 7; ++ } else if (n >= 100000) { ++ pow10 = 100000; ++ return 6; ++ } else if (n >= 10000) { ++ pow10 = 10000; ++ return 5; ++ } else if (n >= 1000) { ++ pow10 = 1000; ++ return 4; ++ } else if (n >= 100) { ++ pow10 = 100; ++ return 3; ++ } else if (n >= 10) { ++ pow10 = 10; ++ return 2; ++ } else { ++ pow10 = 1; ++ return 1; ++ } ++} ++ ++inline void grisu2_round(char *buf, int len, std::uint64_t dist, ++ std::uint64_t delta, std::uint64_t rest, ++ std::uint64_t ten_k) { ++ ++ // <--------------------------- delta ----> ++ // <---- dist ---------> ++ // --------------[------------------+-------------------]-------------- ++ // M- w M+ ++ // ++ // ten_k ++ // <------> ++ // <---- rest ----> ++ // --------------[------------------+----+--------------]-------------- ++ // w V ++ // = buf * 10^k ++ // ++ // ten_k represents a unit-in-the-last-place in the decimal representation ++ // stored in buf. ++ // Decrement buf by ten_k while this takes buf closer to w. ++ ++ // The tests are written in this order to avoid overflow in unsigned ++ // integer arithmetic. ++ ++ while (rest < dist && delta - rest >= ten_k && ++ (rest + ten_k < dist || dist - rest > rest + ten_k - dist)) { ++ buf[len - 1]--; ++ rest += ten_k; ++ } ++} ++ ++/*! ++Generates V = buffer * 10^decimal_exponent, such that M- <= V <= M+. ++M- and M+ must be normalized and share the same exponent -60 <= e <= -32. ++*/ ++inline void grisu2_digit_gen(char *buffer, int &length, int &decimal_exponent, ++ diyfp M_minus, diyfp w, diyfp M_plus) { ++ static_assert(kAlpha >= -60, "internal error"); ++ static_assert(kGamma <= -32, "internal error"); ++ ++ // Generates the digits (and the exponent) of a decimal floating-point ++ // number V = buffer * 10^decimal_exponent in the range [M-, M+]. The diyfp's ++ // w, M- and M+ share the same exponent e, which satisfies alpha <= e <= ++ // gamma. ++ // ++ // <--------------------------- delta ----> ++ // <---- dist ---------> ++ // --------------[------------------+-------------------]-------------- ++ // M- w M+ ++ // ++ // Grisu2 generates the digits of M+ from left to right and stops as soon as ++ // V is in [M-,M+]. ++ ++ std::uint64_t delta = ++ diyfp::sub(M_plus, M_minus) ++ .f; // (significand of (M+ - M-), implicit exponent is e) ++ std::uint64_t dist = ++ diyfp::sub(M_plus, w) ++ .f; // (significand of (M+ - w ), implicit exponent is e) ++ ++ // Split M+ = f * 2^e into two parts p1 and p2 (note: e < 0): ++ // ++ // M+ = f * 2^e ++ // = ((f div 2^-e) * 2^-e + (f mod 2^-e)) * 2^e ++ // = ((p1 ) * 2^-e + (p2 )) * 2^e ++ // = p1 + p2 * 2^e ++ ++ const diyfp one(std::uint64_t{1} << -M_plus.e, M_plus.e); ++ ++ auto p1 = static_cast( ++ M_plus.f >> ++ -one.e); // p1 = f div 2^-e (Since -e >= 32, p1 fits into a 32-bit int.) ++ std::uint64_t p2 = M_plus.f & (one.f - 1); // p2 = f mod 2^-e ++ ++ // 1) ++ // ++ // Generate the digits of the integral part p1 = d[n-1]...d[1]d[0] ++ ++ std::uint32_t pow10; ++ const int k = find_largest_pow10(p1, pow10); ++ ++ // 10^(k-1) <= p1 < 10^k, pow10 = 10^(k-1) ++ // ++ // p1 = (p1 div 10^(k-1)) * 10^(k-1) + (p1 mod 10^(k-1)) ++ // = (d[k-1] ) * 10^(k-1) + (p1 mod 10^(k-1)) ++ // ++ // M+ = p1 + p2 * 2^e ++ // = d[k-1] * 10^(k-1) + (p1 mod 10^(k-1)) + p2 * 2^e ++ // = d[k-1] * 10^(k-1) + ((p1 mod 10^(k-1)) * 2^-e + p2) * 2^e ++ // = d[k-1] * 10^(k-1) + ( rest) * 2^e ++ // ++ // Now generate the digits d[n] of p1 from left to right (n = k-1,...,0) ++ // ++ // p1 = d[k-1]...d[n] * 10^n + d[n-1]...d[0] ++ // ++ // but stop as soon as ++ // ++ // rest * 2^e = (d[n-1]...d[0] * 2^-e + p2) * 2^e <= delta * 2^e ++ ++ int n = k; ++ while (n > 0) { ++ // Invariants: ++ // M+ = buffer * 10^n + (p1 + p2 * 2^e) (buffer = 0 for n = k) ++ // pow10 = 10^(n-1) <= p1 < 10^n ++ // ++ const std::uint32_t d = p1 / pow10; // d = p1 div 10^(n-1) ++ const std::uint32_t r = p1 % pow10; // r = p1 mod 10^(n-1) ++ // ++ // M+ = buffer * 10^n + (d * 10^(n-1) + r) + p2 * 2^e ++ // = (buffer * 10 + d) * 10^(n-1) + (r + p2 * 2^e) ++ // ++ buffer[length++] = static_cast('0' + d); // buffer := buffer * 10 + d ++ // ++ // M+ = buffer * 10^(n-1) + (r + p2 * 2^e) ++ // ++ p1 = r; ++ n--; ++ // ++ // M+ = buffer * 10^n + (p1 + p2 * 2^e) ++ // pow10 = 10^n ++ // ++ ++ // Now check if enough digits have been generated. ++ // Compute ++ // ++ // p1 + p2 * 2^e = (p1 * 2^-e + p2) * 2^e = rest * 2^e ++ // ++ // Note: ++ // Since rest and delta share the same exponent e, it suffices to ++ // compare the significands. ++ const std::uint64_t rest = (std::uint64_t{p1} << -one.e) + p2; ++ if (rest <= delta) { ++ // V = buffer * 10^n, with M- <= V <= M+. ++ ++ decimal_exponent += n; ++ ++ // We may now just stop. But instead look if the buffer could be ++ // decremented to bring V closer to w. ++ // ++ // pow10 = 10^n is now 1 ulp in the decimal representation V. ++ // The rounding procedure works with diyfp's with an implicit ++ // exponent of e. ++ // ++ // 10^n = (10^n * 2^-e) * 2^e = ulp * 2^e ++ // ++ const std::uint64_t ten_n = std::uint64_t{pow10} << -one.e; ++ grisu2_round(buffer, length, dist, delta, rest, ten_n); ++ ++ return; ++ } ++ ++ pow10 /= 10; ++ // ++ // pow10 = 10^(n-1) <= p1 < 10^n ++ // Invariants restored. ++ } ++ ++ // 2) ++ // ++ // The digits of the integral part have been generated: ++ // ++ // M+ = d[k-1]...d[1]d[0] + p2 * 2^e ++ // = buffer + p2 * 2^e ++ // ++ // Now generate the digits of the fractional part p2 * 2^e. ++ // ++ // Note: ++ // No decimal point is generated: the exponent is adjusted instead. ++ // ++ // p2 actually represents the fraction ++ // ++ // p2 * 2^e ++ // = p2 / 2^-e ++ // = d[-1] / 10^1 + d[-2] / 10^2 + ... ++ // ++ // Now generate the digits d[-m] of p1 from left to right (m = 1,2,...) ++ // ++ // p2 * 2^e = d[-1]d[-2]...d[-m] * 10^-m ++ // + 10^-m * (d[-m-1] / 10^1 + d[-m-2] / 10^2 + ...) ++ // ++ // using ++ // ++ // 10^m * p2 = ((10^m * p2) div 2^-e) * 2^-e + ((10^m * p2) mod 2^-e) ++ // = ( d) * 2^-e + ( r) ++ // ++ // or ++ // 10^m * p2 * 2^e = d + r * 2^e ++ // ++ // i.e. ++ // ++ // M+ = buffer + p2 * 2^e ++ // = buffer + 10^-m * (d + r * 2^e) ++ // = (buffer * 10^m + d) * 10^-m + 10^-m * r * 2^e ++ // ++ // and stop as soon as 10^-m * r * 2^e <= delta * 2^e ++ ++ int m = 0; ++ for (;;) { ++ // Invariant: ++ // M+ = buffer * 10^-m + 10^-m * (d[-m-1] / 10 + d[-m-2] / 10^2 + ...) ++ // * 2^e ++ // = buffer * 10^-m + 10^-m * (p2 ) ++ // * 2^e = buffer * 10^-m + 10^-m * (1/10 * (10 * p2) ) * 2^e = ++ // buffer * 10^-m + 10^-m * (1/10 * ((10*p2 div 2^-e) * 2^-e + ++ // (10*p2 mod 2^-e)) * 2^e ++ // ++ p2 *= 10; ++ const std::uint64_t d = p2 >> -one.e; // d = (10 * p2) div 2^-e ++ const std::uint64_t r = p2 & (one.f - 1); // r = (10 * p2) mod 2^-e ++ // ++ // M+ = buffer * 10^-m + 10^-m * (1/10 * (d * 2^-e + r) * 2^e ++ // = buffer * 10^-m + 10^-m * (1/10 * (d + r * 2^e)) ++ // = (buffer * 10 + d) * 10^(-m-1) + 10^(-m-1) * r * 2^e ++ // ++ buffer[length++] = static_cast('0' + d); // buffer := buffer * 10 + d ++ // ++ // M+ = buffer * 10^(-m-1) + 10^(-m-1) * r * 2^e ++ // ++ p2 = r; ++ m++; ++ // ++ // M+ = buffer * 10^-m + 10^-m * p2 * 2^e ++ // Invariant restored. ++ ++ // Check if enough digits have been generated. ++ // ++ // 10^-m * p2 * 2^e <= delta * 2^e ++ // p2 * 2^e <= 10^m * delta * 2^e ++ // p2 <= 10^m * delta ++ delta *= 10; ++ dist *= 10; ++ if (p2 <= delta) { ++ break; ++ } ++ } ++ ++ // V = buffer * 10^-m, with M- <= V <= M+. ++ ++ decimal_exponent -= m; ++ ++ // 1 ulp in the decimal representation is now 10^-m. ++ // Since delta and dist are now scaled by 10^m, we need to do the ++ // same with ulp in order to keep the units in sync. ++ // ++ // 10^m * 10^-m = 1 = 2^-e * 2^e = ten_m * 2^e ++ // ++ const std::uint64_t ten_m = one.f; ++ grisu2_round(buffer, length, dist, delta, p2, ten_m); ++ ++ // By construction this algorithm generates the shortest possible decimal ++ // number (Loitsch, Theorem 6.2) which rounds back to w. ++ // For an input number of precision p, at least ++ // ++ // N = 1 + ceil(p * log_10(2)) ++ // ++ // decimal digits are sufficient to identify all binary floating-point ++ // numbers (Matula, "In-and-Out conversions"). ++ // This implies that the algorithm does not produce more than N decimal ++ // digits. ++ // ++ // N = 17 for p = 53 (IEEE double precision) ++ // N = 9 for p = 24 (IEEE single precision) ++} ++ ++/*! ++v = buf * 10^decimal_exponent ++len is the length of the buffer (number of decimal digits) ++The buffer must be large enough, i.e. >= max_digits10. ++*/ ++inline void grisu2(char *buf, int &len, int &decimal_exponent, diyfp m_minus, ++ diyfp v, diyfp m_plus) { ++ ++ // --------(-----------------------+-----------------------)-------- (A) ++ // m- v m+ ++ // ++ // --------------------(-----------+-----------------------)-------- (B) ++ // m- v m+ ++ // ++ // First scale v (and m- and m+) such that the exponent is in the range ++ // [alpha, gamma]. ++ ++ const cached_power cached = get_cached_power_for_binary_exponent(m_plus.e); ++ ++ const diyfp c_minus_k(cached.f, cached.e); // = c ~= 10^-k ++ ++ // The exponent of the products is = v.e + c_minus_k.e + q and is in the range ++ // [alpha,gamma] ++ const diyfp w = diyfp::mul(v, c_minus_k); ++ const diyfp w_minus = diyfp::mul(m_minus, c_minus_k); ++ const diyfp w_plus = diyfp::mul(m_plus, c_minus_k); ++ ++ // ----(---+---)---------------(---+---)---------------(---+---)---- ++ // w- w w+ ++ // = c*m- = c*v = c*m+ ++ // ++ // diyfp::mul rounds its result and c_minus_k is approximated too. w, w- and ++ // w+ are now off by a small amount. ++ // In fact: ++ // ++ // w - v * 10^k < 1 ulp ++ // ++ // To account for this inaccuracy, add resp. subtract 1 ulp. ++ // ++ // --------+---[---------------(---+---)---------------]---+-------- ++ // w- M- w M+ w+ ++ // ++ // Now any number in [M-, M+] (bounds included) will round to w when input, ++ // regardless of how the input rounding algorithm breaks ties. ++ // ++ // And digit_gen generates the shortest possible such number in [M-, M+]. ++ // Note that this does not mean that Grisu2 always generates the shortest ++ // possible number in the interval (m-, m+). ++ const diyfp M_minus(w_minus.f + 1, w_minus.e); ++ const diyfp M_plus(w_plus.f - 1, w_plus.e); ++ ++ decimal_exponent = -cached.k; // = -(-k) = k ++ ++ grisu2_digit_gen(buf, len, decimal_exponent, M_minus, w, M_plus); ++} ++ ++/*! ++v = buf * 10^decimal_exponent ++len is the length of the buffer (number of decimal digits) ++The buffer must be large enough, i.e. >= max_digits10. ++*/ ++template ++void grisu2(char *buf, int &len, int &decimal_exponent, FloatType value) { ++ static_assert(diyfp::kPrecision >= std::numeric_limits::digits + 3, ++ "internal error: not enough precision"); ++ ++ // If the neighbors (and boundaries) of 'value' are always computed for ++ // double-precision numbers, all float's can be recovered using strtod (and ++ // strtof). However, the resulting decimal representations are not exactly ++ // "short". ++ // ++ // The documentation for 'std::to_chars' ++ // (https://en.cppreference.com/w/cpp/utility/to_chars) says "value is ++ // converted to a string as if by std::sprintf in the default ("C") locale" ++ // and since sprintf promotes float's to double's, I think this is exactly ++ // what 'std::to_chars' does. On the other hand, the documentation for ++ // 'std::to_chars' requires that "parsing the representation using the ++ // corresponding std::from_chars function recovers value exactly". That ++ // indicates that single precision floating-point numbers should be recovered ++ // using 'std::strtof'. ++ // ++ // NB: If the neighbors are computed for single-precision numbers, there is a ++ // single float ++ // (7.0385307e-26f) which can't be recovered using strtod. The resulting ++ // double precision value is off by 1 ulp. ++#if 0 ++ const boundaries w = compute_boundaries(static_cast(value)); ++#else ++ const boundaries w = compute_boundaries(value); ++#endif ++ ++ grisu2(buf, len, decimal_exponent, w.minus, w.w, w.plus); ++} ++ ++/*! ++@brief appends a decimal representation of e to buf ++@return a pointer to the element following the exponent. ++@pre -1000 < e < 1000 ++*/ ++inline char *append_exponent(char *buf, int e) { ++ ++ if (e < 0) { ++ e = -e; ++ *buf++ = '-'; ++ } else { ++ *buf++ = '+'; ++ } ++ ++ auto k = static_cast(e); ++ if (k < 10) { ++ // Always print at least two digits in the exponent. ++ // This is for compatibility with printf("%g"). ++ *buf++ = '0'; ++ *buf++ = static_cast('0' + k); ++ } else if (k < 100) { ++ *buf++ = static_cast('0' + k / 10); ++ k %= 10; ++ *buf++ = static_cast('0' + k); ++ } else { ++ *buf++ = static_cast('0' + k / 100); ++ k %= 100; ++ *buf++ = static_cast('0' + k / 10); ++ k %= 10; ++ *buf++ = static_cast('0' + k); ++ } ++ ++ return buf; ++} ++ ++/*! ++@brief prettify v = buf * 10^decimal_exponent ++If v is in the range [10^min_exp, 10^max_exp) it will be printed in fixed-point ++notation. Otherwise it will be printed in exponential notation. ++@pre min_exp < 0 ++@pre max_exp > 0 ++*/ ++inline char *format_buffer(char *buf, int len, int decimal_exponent, ++ int min_exp, int max_exp) { ++ ++ const int k = len; ++ const int n = len + decimal_exponent; ++ ++ // v = buf * 10^(n-k) ++ // k is the length of the buffer (number of decimal digits) ++ // n is the position of the decimal point relative to the start of the buffer. ++ ++ if (k <= n && n <= max_exp) { ++ // digits[000] ++ // len <= max_exp + 2 ++ ++ std::memset(buf + k, '0', static_cast(n) - static_cast(k)); ++ // Make it look like a floating-point number (#362, #378) ++ buf[n + 0] = '.'; ++ buf[n + 1] = '0'; ++ return buf + (static_cast(n)) + 2; ++ } ++ ++ if (0 < n && n <= max_exp) { ++ // dig.its ++ // len <= max_digits10 + 1 ++ std::memmove(buf + (static_cast(n) + 1), buf + n, ++ static_cast(k) - static_cast(n)); ++ buf[n] = '.'; ++ return buf + (static_cast(k) + 1U); ++ } ++ ++ if (min_exp < n && n <= 0) { ++ // 0.[000]digits ++ // len <= 2 + (-min_exp - 1) + max_digits10 ++ ++ std::memmove(buf + (2 + static_cast(-n)), buf, ++ static_cast(k)); ++ buf[0] = '0'; ++ buf[1] = '.'; ++ std::memset(buf + 2, '0', static_cast(-n)); ++ return buf + (2U + static_cast(-n) + static_cast(k)); ++ } ++ ++ if (k == 1) { ++ // dE+123 ++ // len <= 1 + 5 ++ ++ buf += 1; ++ } else { ++ // d.igitsE+123 ++ // len <= max_digits10 + 1 + 5 ++ ++ std::memmove(buf + 2, buf + 1, static_cast(k) - 1); ++ buf[1] = '.'; ++ buf += 1 + static_cast(k); ++ } ++ ++ *buf++ = 'e'; ++ return append_exponent(buf, n - 1); ++} ++ ++} // namespace dtoa_impl ++ ++/*! ++The format of the resulting decimal representation is similar to printf's %g ++format. Returns an iterator pointing past-the-end of the decimal representation. ++@note The input number must be finite, i.e. NaN's and Inf's are not supported. ++@note The buffer must be large enough. ++@note The result is NOT null-terminated. ++*/ ++char *to_chars(char *first, const char *last, double value) { ++ static_cast(last); // maybe unused - fix warning ++ bool negative = std::signbit(value); ++ if (negative) { ++ value = -value; ++ *first++ = '-'; ++ } ++ ++ if (value == 0) // +-0 ++ { ++ *first++ = '0'; ++ // Make it look like a floating-point number (#362, #378) ++ *first++ = '.'; ++ *first++ = '0'; ++ return first; ++ } ++ // Compute v = buffer * 10^decimal_exponent. ++ // The decimal digits are stored in the buffer, which needs to be interpreted ++ // as an unsigned decimal integer. ++ // len is the length of the buffer, i.e. the number of decimal digits. ++ int len = 0; ++ int decimal_exponent = 0; ++ dtoa_impl::grisu2(first, len, decimal_exponent, value); ++ // Format the buffer like printf("%.*g", prec, value) ++ constexpr int kMinExp = -4; ++ constexpr int kMaxExp = std::numeric_limits::digits10; ++ ++ return dtoa_impl::format_buffer(first, len, decimal_exponent, kMinExp, ++ kMaxExp); ++} ++} // namespace internal ++} // namespace simdjson ++/* end file src/to_chars.cpp */ ++/* begin file src/from_chars.cpp */ ++#include ++namespace simdjson { ++namespace internal { ++ ++/** ++ * The code in the internal::from_chars function is meant to handle the floating-point number parsing ++ * when we have more than 19 digits in the decimal mantissa. This should only be seen ++ * in adversarial scenarios: we do not expect production systems to even produce ++ * such floating-point numbers. ++ * ++ * The parser is based on work by Nigel Tao (at https://github.com/google/wuffs/) ++ * who credits Ken Thompson for the design (via a reference to the Go source ++ * code). See ++ * https://github.com/google/wuffs/blob/aa46859ea40c72516deffa1b146121952d6dfd3b/internal/cgen/base/floatconv-submodule-data.c ++ * https://github.com/google/wuffs/blob/46cd8105f47ca07ae2ba8e6a7818ef9c0df6c152/internal/cgen/base/floatconv-submodule-code.c ++ * It is probably not very fast but it is a fallback that should almost never be ++ * called in real life. Google Wuffs is published under APL 2.0. ++ **/ ++ ++namespace { ++constexpr uint32_t max_digits = 768; ++constexpr int32_t decimal_point_range = 2047; ++} // namespace ++ ++struct adjusted_mantissa { ++ uint64_t mantissa; ++ int power2; ++ adjusted_mantissa() : mantissa(0), power2(0) {} ++}; ++ ++struct decimal { ++ uint32_t num_digits; ++ int32_t decimal_point; ++ bool negative; ++ bool truncated; ++ uint8_t digits[max_digits]; ++}; ++ ++template struct binary_format { ++ static constexpr int mantissa_explicit_bits(); ++ static constexpr int minimum_exponent(); ++ static constexpr int infinite_power(); ++ static constexpr int sign_index(); ++}; ++ ++template <> constexpr int binary_format::mantissa_explicit_bits() { ++ return 52; ++} ++ ++template <> constexpr int binary_format::minimum_exponent() { ++ return -1023; ++} ++template <> constexpr int binary_format::infinite_power() { ++ return 0x7FF; ++} ++ ++template <> constexpr int binary_format::sign_index() { return 63; } ++ ++bool is_integer(char c) noexcept { return (c >= '0' && c <= '9'); } ++ ++// This should always succeed since it follows a call to parse_number. ++decimal parse_decimal(const char *&p) noexcept { ++ decimal answer; ++ answer.num_digits = 0; ++ answer.decimal_point = 0; ++ answer.truncated = false; ++ answer.negative = (*p == '-'); ++ if ((*p == '-') || (*p == '+')) { ++ ++p; ++ } ++ ++ while (*p == '0') { ++ ++p; ++ } ++ while (is_integer(*p)) { ++ if (answer.num_digits < max_digits) { ++ answer.digits[answer.num_digits] = uint8_t(*p - '0'); ++ } ++ answer.num_digits++; ++ ++p; ++ } ++ if (*p == '.') { ++ ++p; ++ const char *first_after_period = p; ++ // if we have not yet encountered a zero, we have to skip it as well ++ if (answer.num_digits == 0) { ++ // skip zeros ++ while (*p == '0') { ++ ++p; ++ } ++ } ++ while (is_integer(*p)) { ++ if (answer.num_digits < max_digits) { ++ answer.digits[answer.num_digits] = uint8_t(*p - '0'); ++ } ++ answer.num_digits++; ++ ++p; ++ } ++ answer.decimal_point = int32_t(first_after_period - p); ++ } ++ if(answer.num_digits > 0) { ++ const char *preverse = p - 1; ++ int32_t trailing_zeros = 0; ++ while ((*preverse == '0') || (*preverse == '.')) { ++ if(*preverse == '0') { trailing_zeros++; }; ++ --preverse; ++ } ++ answer.decimal_point += int32_t(answer.num_digits); ++ answer.num_digits -= uint32_t(trailing_zeros); ++ } ++ if(answer.num_digits > max_digits ) { ++ answer.num_digits = max_digits; ++ answer.truncated = true; ++ } ++ if (('e' == *p) || ('E' == *p)) { ++ ++p; ++ bool neg_exp = false; ++ if ('-' == *p) { ++ neg_exp = true; ++ ++p; ++ } else if ('+' == *p) { ++ ++p; ++ } ++ int32_t exp_number = 0; // exponential part ++ while (is_integer(*p)) { ++ uint8_t digit = uint8_t(*p - '0'); ++ if (exp_number < 0x10000) { ++ exp_number = 10 * exp_number + digit; ++ } ++ ++p; ++ } ++ answer.decimal_point += (neg_exp ? -exp_number : exp_number); ++ } ++ return answer; ++} ++ ++// This should always succeed since it follows a call to parse_number. ++// Will not read at or beyond the "end" pointer. ++decimal parse_decimal(const char *&p, const char * end) noexcept { ++ decimal answer; ++ answer.num_digits = 0; ++ answer.decimal_point = 0; ++ answer.truncated = false; ++ if(p == end) { return answer; } // should never happen ++ answer.negative = (*p == '-'); ++ if ((*p == '-') || (*p == '+')) { ++ ++p; ++ } ++ ++ while ((p != end) && (*p == '0')) { ++ ++p; ++ } ++ while ((p != end) && is_integer(*p)) { ++ if (answer.num_digits < max_digits) { ++ answer.digits[answer.num_digits] = uint8_t(*p - '0'); ++ } ++ answer.num_digits++; ++ ++p; ++ } ++ if ((p != end) && (*p == '.')) { ++ ++p; ++ if(p == end) { return answer; } // should never happen ++ const char *first_after_period = p; ++ // if we have not yet encountered a zero, we have to skip it as well ++ if (answer.num_digits == 0) { ++ // skip zeros ++ while (*p == '0') { ++ ++p; ++ } ++ } ++ while ((p != end) && is_integer(*p)) { ++ if (answer.num_digits < max_digits) { ++ answer.digits[answer.num_digits] = uint8_t(*p - '0'); ++ } ++ answer.num_digits++; ++ ++p; ++ } ++ answer.decimal_point = int32_t(first_after_period - p); ++ } ++ if(answer.num_digits > 0) { ++ const char *preverse = p - 1; ++ int32_t trailing_zeros = 0; ++ while ((*preverse == '0') || (*preverse == '.')) { ++ if(*preverse == '0') { trailing_zeros++; }; ++ --preverse; ++ } ++ answer.decimal_point += int32_t(answer.num_digits); ++ answer.num_digits -= uint32_t(trailing_zeros); ++ } ++ if(answer.num_digits > max_digits ) { ++ answer.num_digits = max_digits; ++ answer.truncated = true; ++ } ++ if ((p != end) && (('e' == *p) || ('E' == *p))) { ++ ++p; ++ if(p == end) { return answer; } // should never happen ++ bool neg_exp = false; ++ if ('-' == *p) { ++ neg_exp = true; ++ ++p; ++ } else if ('+' == *p) { ++ ++p; ++ } ++ int32_t exp_number = 0; // exponential part ++ while ((p != end) && is_integer(*p)) { ++ uint8_t digit = uint8_t(*p - '0'); ++ if (exp_number < 0x10000) { ++ exp_number = 10 * exp_number + digit; ++ } ++ ++p; ++ } ++ answer.decimal_point += (neg_exp ? -exp_number : exp_number); ++ } ++ return answer; ++} ++ ++namespace { ++ ++// remove all final zeroes ++inline void trim(decimal &h) { ++ while ((h.num_digits > 0) && (h.digits[h.num_digits - 1] == 0)) { ++ h.num_digits--; ++ } ++} ++ ++uint32_t number_of_digits_decimal_left_shift(decimal &h, uint32_t shift) { ++ shift &= 63; ++ const static uint16_t number_of_digits_decimal_left_shift_table[65] = { ++ 0x0000, 0x0800, 0x0801, 0x0803, 0x1006, 0x1009, 0x100D, 0x1812, 0x1817, ++ 0x181D, 0x2024, 0x202B, 0x2033, 0x203C, 0x2846, 0x2850, 0x285B, 0x3067, ++ 0x3073, 0x3080, 0x388E, 0x389C, 0x38AB, 0x38BB, 0x40CC, 0x40DD, 0x40EF, ++ 0x4902, 0x4915, 0x4929, 0x513E, 0x5153, 0x5169, 0x5180, 0x5998, 0x59B0, ++ 0x59C9, 0x61E3, 0x61FD, 0x6218, 0x6A34, 0x6A50, 0x6A6D, 0x6A8B, 0x72AA, ++ 0x72C9, 0x72E9, 0x7B0A, 0x7B2B, 0x7B4D, 0x8370, 0x8393, 0x83B7, 0x83DC, ++ 0x8C02, 0x8C28, 0x8C4F, 0x9477, 0x949F, 0x94C8, 0x9CF2, 0x051C, 0x051C, ++ 0x051C, 0x051C, ++ }; ++ uint32_t x_a = number_of_digits_decimal_left_shift_table[shift]; ++ uint32_t x_b = number_of_digits_decimal_left_shift_table[shift + 1]; ++ uint32_t num_new_digits = x_a >> 11; ++ uint32_t pow5_a = 0x7FF & x_a; ++ uint32_t pow5_b = 0x7FF & x_b; ++ const static uint8_t ++ number_of_digits_decimal_left_shift_table_powers_of_5[0x051C] = { ++ 5, 2, 5, 1, 2, 5, 6, 2, 5, 3, 1, 2, 5, 1, 5, 6, 2, 5, 7, 8, 1, 2, 5, ++ 3, 9, 0, 6, 2, 5, 1, 9, 5, 3, 1, 2, 5, 9, 7, 6, 5, 6, 2, 5, 4, 8, 8, ++ 2, 8, 1, 2, 5, 2, 4, 4, 1, 4, 0, 6, 2, 5, 1, 2, 2, 0, 7, 0, 3, 1, 2, ++ 5, 6, 1, 0, 3, 5, 1, 5, 6, 2, 5, 3, 0, 5, 1, 7, 5, 7, 8, 1, 2, 5, 1, ++ 5, 2, 5, 8, 7, 8, 9, 0, 6, 2, 5, 7, 6, 2, 9, 3, 9, 4, 5, 3, 1, 2, 5, ++ 3, 8, 1, 4, 6, 9, 7, 2, 6, 5, 6, 2, 5, 1, 9, 0, 7, 3, 4, 8, 6, 3, 2, ++ 8, 1, 2, 5, 9, 5, 3, 6, 7, 4, 3, 1, 6, 4, 0, 6, 2, 5, 4, 7, 6, 8, 3, ++ 7, 1, 5, 8, 2, 0, 3, 1, 2, 5, 2, 3, 8, 4, 1, 8, 5, 7, 9, 1, 0, 1, 5, ++ 6, 2, 5, 1, 1, 9, 2, 0, 9, 2, 8, 9, 5, 5, 0, 7, 8, 1, 2, 5, 5, 9, 6, ++ 0, 4, 6, 4, 4, 7, 7, 5, 3, 9, 0, 6, 2, 5, 2, 9, 8, 0, 2, 3, 2, 2, 3, ++ 8, 7, 6, 9, 5, 3, 1, 2, 5, 1, 4, 9, 0, 1, 1, 6, 1, 1, 9, 3, 8, 4, 7, ++ 6, 5, 6, 2, 5, 7, 4, 5, 0, 5, 8, 0, 5, 9, 6, 9, 2, 3, 8, 2, 8, 1, 2, ++ 5, 3, 7, 2, 5, 2, 9, 0, 2, 9, 8, 4, 6, 1, 9, 1, 4, 0, 6, 2, 5, 1, 8, ++ 6, 2, 6, 4, 5, 1, 4, 9, 2, 3, 0, 9, 5, 7, 0, 3, 1, 2, 5, 9, 3, 1, 3, ++ 2, 2, 5, 7, 4, 6, 1, 5, 4, 7, 8, 5, 1, 5, 6, 2, 5, 4, 6, 5, 6, 6, 1, ++ 2, 8, 7, 3, 0, 7, 7, 3, 9, 2, 5, 7, 8, 1, 2, 5, 2, 3, 2, 8, 3, 0, 6, ++ 4, 3, 6, 5, 3, 8, 6, 9, 6, 2, 8, 9, 0, 6, 2, 5, 1, 1, 6, 4, 1, 5, 3, ++ 2, 1, 8, 2, 6, 9, 3, 4, 8, 1, 4, 4, 5, 3, 1, 2, 5, 5, 8, 2, 0, 7, 6, ++ 6, 0, 9, 1, 3, 4, 6, 7, 4, 0, 7, 2, 2, 6, 5, 6, 2, 5, 2, 9, 1, 0, 3, ++ 8, 3, 0, 4, 5, 6, 7, 3, 3, 7, 0, 3, 6, 1, 3, 2, 8, 1, 2, 5, 1, 4, 5, ++ 5, 1, 9, 1, 5, 2, 2, 8, 3, 6, 6, 8, 5, 1, 8, 0, 6, 6, 4, 0, 6, 2, 5, ++ 7, 2, 7, 5, 9, 5, 7, 6, 1, 4, 1, 8, 3, 4, 2, 5, 9, 0, 3, 3, 2, 0, 3, ++ 1, 2, 5, 3, 6, 3, 7, 9, 7, 8, 8, 0, 7, 0, 9, 1, 7, 1, 2, 9, 5, 1, 6, ++ 6, 0, 1, 5, 6, 2, 5, 1, 8, 1, 8, 9, 8, 9, 4, 0, 3, 5, 4, 5, 8, 5, 6, ++ 4, 7, 5, 8, 3, 0, 0, 7, 8, 1, 2, 5, 9, 0, 9, 4, 9, 4, 7, 0, 1, 7, 7, ++ 2, 9, 2, 8, 2, 3, 7, 9, 1, 5, 0, 3, 9, 0, 6, 2, 5, 4, 5, 4, 7, 4, 7, ++ 3, 5, 0, 8, 8, 6, 4, 6, 4, 1, 1, 8, 9, 5, 7, 5, 1, 9, 5, 3, 1, 2, 5, ++ 2, 2, 7, 3, 7, 3, 6, 7, 5, 4, 4, 3, 2, 3, 2, 0, 5, 9, 4, 7, 8, 7, 5, ++ 9, 7, 6, 5, 6, 2, 5, 1, 1, 3, 6, 8, 6, 8, 3, 7, 7, 2, 1, 6, 1, 6, 0, ++ 2, 9, 7, 3, 9, 3, 7, 9, 8, 8, 2, 8, 1, 2, 5, 5, 6, 8, 4, 3, 4, 1, 8, ++ 8, 6, 0, 8, 0, 8, 0, 1, 4, 8, 6, 9, 6, 8, 9, 9, 4, 1, 4, 0, 6, 2, 5, ++ 2, 8, 4, 2, 1, 7, 0, 9, 4, 3, 0, 4, 0, 4, 0, 0, 7, 4, 3, 4, 8, 4, 4, ++ 9, 7, 0, 7, 0, 3, 1, 2, 5, 1, 4, 2, 1, 0, 8, 5, 4, 7, 1, 5, 2, 0, 2, ++ 0, 0, 3, 7, 1, 7, 4, 2, 2, 4, 8, 5, 3, 5, 1, 5, 6, 2, 5, 7, 1, 0, 5, ++ 4, 2, 7, 3, 5, 7, 6, 0, 1, 0, 0, 1, 8, 5, 8, 7, 1, 1, 2, 4, 2, 6, 7, ++ 5, 7, 8, 1, 2, 5, 3, 5, 5, 2, 7, 1, 3, 6, 7, 8, 8, 0, 0, 5, 0, 0, 9, ++ 2, 9, 3, 5, 5, 6, 2, 1, 3, 3, 7, 8, 9, 0, 6, 2, 5, 1, 7, 7, 6, 3, 5, ++ 6, 8, 3, 9, 4, 0, 0, 2, 5, 0, 4, 6, 4, 6, 7, 7, 8, 1, 0, 6, 6, 8, 9, ++ 4, 5, 3, 1, 2, 5, 8, 8, 8, 1, 7, 8, 4, 1, 9, 7, 0, 0, 1, 2, 5, 2, 3, ++ 2, 3, 3, 8, 9, 0, 5, 3, 3, 4, 4, 7, 2, 6, 5, 6, 2, 5, 4, 4, 4, 0, 8, ++ 9, 2, 0, 9, 8, 5, 0, 0, 6, 2, 6, 1, 6, 1, 6, 9, 4, 5, 2, 6, 6, 7, 2, ++ 3, 6, 3, 2, 8, 1, 2, 5, 2, 2, 2, 0, 4, 4, 6, 0, 4, 9, 2, 5, 0, 3, 1, ++ 3, 0, 8, 0, 8, 4, 7, 2, 6, 3, 3, 3, 6, 1, 8, 1, 6, 4, 0, 6, 2, 5, 1, ++ 1, 1, 0, 2, 2, 3, 0, 2, 4, 6, 2, 5, 1, 5, 6, 5, 4, 0, 4, 2, 3, 6, 3, ++ 1, 6, 6, 8, 0, 9, 0, 8, 2, 0, 3, 1, 2, 5, 5, 5, 5, 1, 1, 1, 5, 1, 2, ++ 3, 1, 2, 5, 7, 8, 2, 7, 0, 2, 1, 1, 8, 1, 5, 8, 3, 4, 0, 4, 5, 4, 1, ++ 0, 1, 5, 6, 2, 5, 2, 7, 7, 5, 5, 5, 7, 5, 6, 1, 5, 6, 2, 8, 9, 1, 3, ++ 5, 1, 0, 5, 9, 0, 7, 9, 1, 7, 0, 2, 2, 7, 0, 5, 0, 7, 8, 1, 2, 5, 1, ++ 3, 8, 7, 7, 7, 8, 7, 8, 0, 7, 8, 1, 4, 4, 5, 6, 7, 5, 5, 2, 9, 5, 3, ++ 9, 5, 8, 5, 1, 1, 3, 5, 2, 5, 3, 9, 0, 6, 2, 5, 6, 9, 3, 8, 8, 9, 3, ++ 9, 0, 3, 9, 0, 7, 2, 2, 8, 3, 7, 7, 6, 4, 7, 6, 9, 7, 9, 2, 5, 5, 6, ++ 7, 6, 2, 6, 9, 5, 3, 1, 2, 5, 3, 4, 6, 9, 4, 4, 6, 9, 5, 1, 9, 5, 3, ++ 6, 1, 4, 1, 8, 8, 8, 2, 3, 8, 4, 8, 9, 6, 2, 7, 8, 3, 8, 1, 3, 4, 7, ++ 6, 5, 6, 2, 5, 1, 7, 3, 4, 7, 2, 3, 4, 7, 5, 9, 7, 6, 8, 0, 7, 0, 9, ++ 4, 4, 1, 1, 9, 2, 4, 4, 8, 1, 3, 9, 1, 9, 0, 6, 7, 3, 8, 2, 8, 1, 2, ++ 5, 8, 6, 7, 3, 6, 1, 7, 3, 7, 9, 8, 8, 4, 0, 3, 5, 4, 7, 2, 0, 5, 9, ++ 6, 2, 2, 4, 0, 6, 9, 5, 9, 5, 3, 3, 6, 9, 1, 4, 0, 6, 2, 5, ++ }; ++ const uint8_t *pow5 = ++ &number_of_digits_decimal_left_shift_table_powers_of_5[pow5_a]; ++ uint32_t i = 0; ++ uint32_t n = pow5_b - pow5_a; ++ for (; i < n; i++) { ++ if (i >= h.num_digits) { ++ return num_new_digits - 1; ++ } else if (h.digits[i] == pow5[i]) { ++ continue; ++ } else if (h.digits[i] < pow5[i]) { ++ return num_new_digits - 1; ++ } else { ++ return num_new_digits; ++ } ++ } ++ return num_new_digits; ++} ++ ++} // end of anonymous namespace ++ ++uint64_t round(decimal &h) { ++ if ((h.num_digits == 0) || (h.decimal_point < 0)) { ++ return 0; ++ } else if (h.decimal_point > 18) { ++ return UINT64_MAX; ++ } ++ // at this point, we know that h.decimal_point >= 0 ++ uint32_t dp = uint32_t(h.decimal_point); ++ uint64_t n = 0; ++ for (uint32_t i = 0; i < dp; i++) { ++ n = (10 * n) + ((i < h.num_digits) ? h.digits[i] : 0); ++ } ++ bool round_up = false; ++ if (dp < h.num_digits) { ++ round_up = h.digits[dp] >= 5; // normally, we round up ++ // but we may need to round to even! ++ if ((h.digits[dp] == 5) && (dp + 1 == h.num_digits)) { ++ round_up = h.truncated || ((dp > 0) && (1 & h.digits[dp - 1])); ++ } ++ } ++ if (round_up) { ++ n++; ++ } ++ return n; ++} ++ ++// computes h * 2^-shift ++void decimal_left_shift(decimal &h, uint32_t shift) { ++ if (h.num_digits == 0) { ++ return; ++ } ++ uint32_t num_new_digits = number_of_digits_decimal_left_shift(h, shift); ++ int32_t read_index = int32_t(h.num_digits - 1); ++ uint32_t write_index = h.num_digits - 1 + num_new_digits; ++ uint64_t n = 0; ++ ++ while (read_index >= 0) { ++ n += uint64_t(h.digits[read_index]) << shift; ++ uint64_t quotient = n / 10; ++ uint64_t remainder = n - (10 * quotient); ++ if (write_index < max_digits) { ++ h.digits[write_index] = uint8_t(remainder); ++ } else if (remainder > 0) { ++ h.truncated = true; ++ } ++ n = quotient; ++ write_index--; ++ read_index--; ++ } ++ while (n > 0) { ++ uint64_t quotient = n / 10; ++ uint64_t remainder = n - (10 * quotient); ++ if (write_index < max_digits) { ++ h.digits[write_index] = uint8_t(remainder); ++ } else if (remainder > 0) { ++ h.truncated = true; ++ } ++ n = quotient; ++ write_index--; ++ } ++ h.num_digits += num_new_digits; ++ if (h.num_digits > max_digits) { ++ h.num_digits = max_digits; ++ } ++ h.decimal_point += int32_t(num_new_digits); ++ trim(h); ++} ++ ++// computes h * 2^shift ++void decimal_right_shift(decimal &h, uint32_t shift) { ++ uint32_t read_index = 0; ++ uint32_t write_index = 0; ++ ++ uint64_t n = 0; ++ ++ while ((n >> shift) == 0) { ++ if (read_index < h.num_digits) { ++ n = (10 * n) + h.digits[read_index++]; ++ } else if (n == 0) { ++ return; ++ } else { ++ while ((n >> shift) == 0) { ++ n = 10 * n; ++ read_index++; ++ } ++ break; ++ } ++ } ++ h.decimal_point -= int32_t(read_index - 1); ++ if (h.decimal_point < -decimal_point_range) { // it is zero ++ h.num_digits = 0; ++ h.decimal_point = 0; ++ h.negative = false; ++ h.truncated = false; ++ return; ++ } ++ uint64_t mask = (uint64_t(1) << shift) - 1; ++ while (read_index < h.num_digits) { ++ uint8_t new_digit = uint8_t(n >> shift); ++ n = (10 * (n & mask)) + h.digits[read_index++]; ++ h.digits[write_index++] = new_digit; ++ } ++ while (n > 0) { ++ uint8_t new_digit = uint8_t(n >> shift); ++ n = 10 * (n & mask); ++ if (write_index < max_digits) { ++ h.digits[write_index++] = new_digit; ++ } else if (new_digit > 0) { ++ h.truncated = true; ++ } ++ } ++ h.num_digits = write_index; ++ trim(h); ++} ++ ++template adjusted_mantissa compute_float(decimal &d) { ++ adjusted_mantissa answer; ++ if (d.num_digits == 0) { ++ // should be zero ++ answer.power2 = 0; ++ answer.mantissa = 0; ++ return answer; ++ } ++ // At this point, going further, we can assume that d.num_digits > 0. ++ // We want to guard against excessive decimal point values because ++ // they can result in long running times. Indeed, we do ++ // shifts by at most 60 bits. We have that log(10**400)/log(2**60) ~= 22 ++ // which is fine, but log(10**299995)/log(2**60) ~= 16609 which is not ++ // fine (runs for a long time). ++ // ++ if(d.decimal_point < -324) { ++ // We have something smaller than 1e-324 which is always zero ++ // in binary64 and binary32. ++ // It should be zero. ++ answer.power2 = 0; ++ answer.mantissa = 0; ++ return answer; ++ } else if(d.decimal_point >= 310) { ++ // We have something at least as large as 0.1e310 which is ++ // always infinite. ++ answer.power2 = binary::infinite_power(); ++ answer.mantissa = 0; ++ return answer; ++ } ++ ++ static const uint32_t max_shift = 60; ++ static const uint32_t num_powers = 19; ++ static const uint8_t powers[19] = { ++ 0, 3, 6, 9, 13, 16, 19, 23, 26, 29, // ++ 33, 36, 39, 43, 46, 49, 53, 56, 59, // ++ }; ++ int32_t exp2 = 0; ++ while (d.decimal_point > 0) { ++ uint32_t n = uint32_t(d.decimal_point); ++ uint32_t shift = (n < num_powers) ? powers[n] : max_shift; ++ decimal_right_shift(d, shift); ++ if (d.decimal_point < -decimal_point_range) { ++ // should be zero ++ answer.power2 = 0; ++ answer.mantissa = 0; ++ return answer; ++ } ++ exp2 += int32_t(shift); ++ } ++ // We shift left toward [1/2 ... 1]. ++ while (d.decimal_point <= 0) { ++ uint32_t shift; ++ if (d.decimal_point == 0) { ++ if (d.digits[0] >= 5) { ++ break; ++ } ++ shift = (d.digits[0] < 2) ? 2 : 1; ++ } else { ++ uint32_t n = uint32_t(-d.decimal_point); ++ shift = (n < num_powers) ? powers[n] : max_shift; ++ } ++ decimal_left_shift(d, shift); ++ if (d.decimal_point > decimal_point_range) { ++ // we want to get infinity: ++ answer.power2 = 0xFF; ++ answer.mantissa = 0; ++ return answer; ++ } ++ exp2 -= int32_t(shift); ++ } ++ // We are now in the range [1/2 ... 1] but the binary format uses [1 ... 2]. ++ exp2--; ++ constexpr int32_t minimum_exponent = binary::minimum_exponent(); ++ while ((minimum_exponent + 1) > exp2) { ++ uint32_t n = uint32_t((minimum_exponent + 1) - exp2); ++ if (n > max_shift) { ++ n = max_shift; ++ } ++ decimal_right_shift(d, n); ++ exp2 += int32_t(n); ++ } ++ if ((exp2 - minimum_exponent) >= binary::infinite_power()) { ++ answer.power2 = binary::infinite_power(); ++ answer.mantissa = 0; ++ return answer; ++ } ++ ++ const int mantissa_size_in_bits = binary::mantissa_explicit_bits() + 1; ++ decimal_left_shift(d, mantissa_size_in_bits); ++ ++ uint64_t mantissa = round(d); ++ // It is possible that we have an overflow, in which case we need ++ // to shift back. ++ if (mantissa >= (uint64_t(1) << mantissa_size_in_bits)) { ++ decimal_right_shift(d, 1); ++ exp2 += 1; ++ mantissa = round(d); ++ if ((exp2 - minimum_exponent) >= binary::infinite_power()) { ++ answer.power2 = binary::infinite_power(); ++ answer.mantissa = 0; ++ return answer; ++ } ++ } ++ answer.power2 = exp2 - binary::minimum_exponent(); ++ if (mantissa < (uint64_t(1) << binary::mantissa_explicit_bits())) { ++ answer.power2--; ++ } ++ answer.mantissa = ++ mantissa & ((uint64_t(1) << binary::mantissa_explicit_bits()) - 1); ++ return answer; ++} ++ ++template ++adjusted_mantissa parse_long_mantissa(const char *first) { ++ decimal d = parse_decimal(first); ++ return compute_float(d); ++} ++ ++template ++adjusted_mantissa parse_long_mantissa(const char *first, const char *end) { ++ decimal d = parse_decimal(first, end); ++ return compute_float(d); ++} ++ ++double from_chars(const char *first) noexcept { ++ bool negative = first[0] == '-'; ++ if (negative) { ++ first++; ++ } ++ adjusted_mantissa am = parse_long_mantissa>(first); ++ uint64_t word = am.mantissa; ++ word |= uint64_t(am.power2) ++ << binary_format::mantissa_explicit_bits(); ++ word = negative ? word | (uint64_t(1) << binary_format::sign_index()) ++ : word; ++ double value; ++ std::memcpy(&value, &word, sizeof(double)); ++ return value; ++} ++ ++ ++double from_chars(const char *first, const char *end) noexcept { ++ bool negative = first[0] == '-'; ++ if (negative) { ++ first++; ++ } ++ adjusted_mantissa am = parse_long_mantissa>(first, end); ++ uint64_t word = am.mantissa; ++ word |= uint64_t(am.power2) ++ << binary_format::mantissa_explicit_bits(); ++ word = negative ? word | (uint64_t(1) << binary_format::sign_index()) ++ : word; ++ double value; ++ std::memcpy(&value, &word, sizeof(double)); ++ return value; ++} ++ ++} // internal ++} // simdjson ++/* end file src/from_chars.cpp */ ++/* begin file src/internal/error_tables.cpp */ ++ ++namespace simdjson { ++namespace internal { ++ ++ SIMDJSON_DLLIMPORTEXPORT const error_code_info error_codes[] { ++ { SUCCESS, "No error" }, ++ { CAPACITY, "This parser can't support a document that big" }, ++ { MEMALLOC, "Error allocating memory, we're most likely out of memory" }, ++ { TAPE_ERROR, "The JSON document has an improper structure: missing or superfluous commas, braces, missing keys, etc." }, ++ { DEPTH_ERROR, "The JSON document was too deep (too many nested objects and arrays)" }, ++ { STRING_ERROR, "Problem while parsing a string" }, ++ { T_ATOM_ERROR, "Problem while parsing an atom starting with the letter 't'" }, ++ { F_ATOM_ERROR, "Problem while parsing an atom starting with the letter 'f'" }, ++ { N_ATOM_ERROR, "Problem while parsing an atom starting with the letter 'n'" }, ++ { NUMBER_ERROR, "Problem while parsing a number" }, ++ { UTF8_ERROR, "The input is not valid UTF-8" }, ++ { UNINITIALIZED, "Uninitialized" }, ++ { EMPTY, "Empty: no JSON found" }, ++ { UNESCAPED_CHARS, "Within strings, some characters must be escaped, we found unescaped characters" }, ++ { UNCLOSED_STRING, "A string is opened, but never closed." }, ++ { UNSUPPORTED_ARCHITECTURE, "simdjson does not have an implementation supported by this CPU architecture (perhaps it's a non-SIMD CPU?)." }, ++ { INCORRECT_TYPE, "The JSON element does not have the requested type." }, ++ { NUMBER_OUT_OF_RANGE, "The JSON number is too large or too small to fit within the requested type." }, ++ { INDEX_OUT_OF_BOUNDS, "Attempted to access an element of a JSON array that is beyond its length." }, ++ { NO_SUCH_FIELD, "The JSON field referenced does not exist in this object." }, ++ { IO_ERROR, "Error reading the file." }, ++ { INVALID_JSON_POINTER, "Invalid JSON pointer syntax." }, ++ { INVALID_URI_FRAGMENT, "Invalid URI fragment syntax." }, ++ { UNEXPECTED_ERROR, "Unexpected error, consider reporting this problem as you may have found a bug in simdjson" }, ++ { PARSER_IN_USE, "Cannot parse a new document while a document is still in use." }, ++ { OUT_OF_ORDER_ITERATION, "Objects and arrays can only be iterated when they are first encountered." }, ++ { INSUFFICIENT_PADDING, "simdjson requires the input JSON string to have at least SIMDJSON_PADDING extra bytes allocated, beyond the string's length. Consider using the simdjson::padded_string class if needed." }, ++ { INCOMPLETE_ARRAY_OR_OBJECT, "JSON document ended early in the middle of an object or array." }, ++ { SCALAR_DOCUMENT_AS_VALUE, "A JSON document made of a scalar (number, Boolean, null or string) is treated as a value. Use get_bool(), get_double(), etc. on the document instead. "}, ++ { OUT_OF_BOUNDS, "Attempted to access location outside of document."}, ++ { TRAILING_CONTENT, "Unexpected trailing content in the JSON input."} ++ }; // error_messages[] ++ ++} // namespace internal ++} // namespace simdjson ++/* end file src/internal/error_tables.cpp */ ++/* begin file src/internal/jsoncharutils_tables.cpp */ ++ ++namespace simdjson { ++namespace internal { ++ ++// structural chars here are ++// they are { 0x7b } 0x7d : 0x3a [ 0x5b ] 0x5d , 0x2c (and NULL) ++// we are also interested in the four whitespace characters ++// space 0x20, linefeed 0x0a, horizontal tab 0x09 and carriage return 0x0d ++ ++SIMDJSON_DLLIMPORTEXPORT const bool structural_or_whitespace_negated[256] = { ++ 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, ++ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, ++ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, ++ ++ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, ++ 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, ++ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, ++ ++ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, ++ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, ++ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, ++ ++ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, ++ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, ++ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; ++ ++SIMDJSON_DLLIMPORTEXPORT const bool structural_or_whitespace[256] = { ++ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ++ 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, ++ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ++ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, ++ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ++ 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ++ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ++ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ++ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ++ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ++ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; ++ ++SIMDJSON_DLLIMPORTEXPORT const uint32_t digit_to_val32[886] = { ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, ++ 0x6, 0x7, 0x8, 0x9, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xa, ++ 0xb, 0xc, 0xd, 0xe, 0xf, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xa, 0xb, 0xc, 0xd, 0xe, ++ 0xf, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0x0, 0x10, 0x20, 0x30, 0x40, 0x50, ++ 0x60, 0x70, 0x80, 0x90, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xa0, ++ 0xb0, 0xc0, 0xd0, 0xe0, 0xf0, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xa0, 0xb0, 0xc0, 0xd0, 0xe0, ++ 0xf0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0x0, 0x100, 0x200, 0x300, 0x400, 0x500, ++ 0x600, 0x700, 0x800, 0x900, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xa00, ++ 0xb00, 0xc00, 0xd00, 0xe00, 0xf00, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xa00, 0xb00, 0xc00, 0xd00, 0xe00, ++ 0xf00, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0x0, 0x1000, 0x2000, 0x3000, 0x4000, 0x5000, ++ 0x6000, 0x7000, 0x8000, 0x9000, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xa000, ++ 0xb000, 0xc000, 0xd000, 0xe000, 0xf000, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xa000, 0xb000, 0xc000, 0xd000, 0xe000, ++ 0xf000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ++ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF}; ++ ++} // namespace internal ++} // namespace simdjson ++/* end file src/internal/jsoncharutils_tables.cpp */ ++/* begin file src/internal/numberparsing_tables.cpp */ ++ ++namespace simdjson { ++namespace internal { ++ ++// Precomputed powers of ten from 10^0 to 10^22. These ++// can be represented exactly using the double type. ++SIMDJSON_DLLIMPORTEXPORT const double power_of_ten[] = { ++ 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11, ++ 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22}; ++ ++/** ++ * When mapping numbers from decimal to binary, ++ * we go from w * 10^q to m * 2^p but we have ++ * 10^q = 5^q * 2^q, so effectively ++ * we are trying to match ++ * w * 2^q * 5^q to m * 2^p. Thus the powers of two ++ * are not a concern since they can be represented ++ * exactly using the binary notation, only the powers of five ++ * affect the binary significand. ++ */ ++ ++ ++// The truncated powers of five from 5^-342 all the way to 5^308 ++// The mantissa is truncated to 128 bits, and ++// never rounded up. Uses about 10KB. ++SIMDJSON_DLLIMPORTEXPORT const uint64_t power_of_five_128[]= { ++ 0xeef453d6923bd65a,0x113faa2906a13b3f, ++ 0x9558b4661b6565f8,0x4ac7ca59a424c507, ++ 0xbaaee17fa23ebf76,0x5d79bcf00d2df649, ++ 0xe95a99df8ace6f53,0xf4d82c2c107973dc, ++ 0x91d8a02bb6c10594,0x79071b9b8a4be869, ++ 0xb64ec836a47146f9,0x9748e2826cdee284, ++ 0xe3e27a444d8d98b7,0xfd1b1b2308169b25, ++ 0x8e6d8c6ab0787f72,0xfe30f0f5e50e20f7, ++ 0xb208ef855c969f4f,0xbdbd2d335e51a935, ++ 0xde8b2b66b3bc4723,0xad2c788035e61382, ++ 0x8b16fb203055ac76,0x4c3bcb5021afcc31, ++ 0xaddcb9e83c6b1793,0xdf4abe242a1bbf3d, ++ 0xd953e8624b85dd78,0xd71d6dad34a2af0d, ++ 0x87d4713d6f33aa6b,0x8672648c40e5ad68, ++ 0xa9c98d8ccb009506,0x680efdaf511f18c2, ++ 0xd43bf0effdc0ba48,0x212bd1b2566def2, ++ 0x84a57695fe98746d,0x14bb630f7604b57, ++ 0xa5ced43b7e3e9188,0x419ea3bd35385e2d, ++ 0xcf42894a5dce35ea,0x52064cac828675b9, ++ 0x818995ce7aa0e1b2,0x7343efebd1940993, ++ 0xa1ebfb4219491a1f,0x1014ebe6c5f90bf8, ++ 0xca66fa129f9b60a6,0xd41a26e077774ef6, ++ 0xfd00b897478238d0,0x8920b098955522b4, ++ 0x9e20735e8cb16382,0x55b46e5f5d5535b0, ++ 0xc5a890362fddbc62,0xeb2189f734aa831d, ++ 0xf712b443bbd52b7b,0xa5e9ec7501d523e4, ++ 0x9a6bb0aa55653b2d,0x47b233c92125366e, ++ 0xc1069cd4eabe89f8,0x999ec0bb696e840a, ++ 0xf148440a256e2c76,0xc00670ea43ca250d, ++ 0x96cd2a865764dbca,0x380406926a5e5728, ++ 0xbc807527ed3e12bc,0xc605083704f5ecf2, ++ 0xeba09271e88d976b,0xf7864a44c633682e, ++ 0x93445b8731587ea3,0x7ab3ee6afbe0211d, ++ 0xb8157268fdae9e4c,0x5960ea05bad82964, ++ 0xe61acf033d1a45df,0x6fb92487298e33bd, ++ 0x8fd0c16206306bab,0xa5d3b6d479f8e056, ++ 0xb3c4f1ba87bc8696,0x8f48a4899877186c, ++ 0xe0b62e2929aba83c,0x331acdabfe94de87, ++ 0x8c71dcd9ba0b4925,0x9ff0c08b7f1d0b14, ++ 0xaf8e5410288e1b6f,0x7ecf0ae5ee44dd9, ++ 0xdb71e91432b1a24a,0xc9e82cd9f69d6150, ++ 0x892731ac9faf056e,0xbe311c083a225cd2, ++ 0xab70fe17c79ac6ca,0x6dbd630a48aaf406, ++ 0xd64d3d9db981787d,0x92cbbccdad5b108, ++ 0x85f0468293f0eb4e,0x25bbf56008c58ea5, ++ 0xa76c582338ed2621,0xaf2af2b80af6f24e, ++ 0xd1476e2c07286faa,0x1af5af660db4aee1, ++ 0x82cca4db847945ca,0x50d98d9fc890ed4d, ++ 0xa37fce126597973c,0xe50ff107bab528a0, ++ 0xcc5fc196fefd7d0c,0x1e53ed49a96272c8, ++ 0xff77b1fcbebcdc4f,0x25e8e89c13bb0f7a, ++ 0x9faacf3df73609b1,0x77b191618c54e9ac, ++ 0xc795830d75038c1d,0xd59df5b9ef6a2417, ++ 0xf97ae3d0d2446f25,0x4b0573286b44ad1d, ++ 0x9becce62836ac577,0x4ee367f9430aec32, ++ 0xc2e801fb244576d5,0x229c41f793cda73f, ++ 0xf3a20279ed56d48a,0x6b43527578c1110f, ++ 0x9845418c345644d6,0x830a13896b78aaa9, ++ 0xbe5691ef416bd60c,0x23cc986bc656d553, ++ 0xedec366b11c6cb8f,0x2cbfbe86b7ec8aa8, ++ 0x94b3a202eb1c3f39,0x7bf7d71432f3d6a9, ++ 0xb9e08a83a5e34f07,0xdaf5ccd93fb0cc53, ++ 0xe858ad248f5c22c9,0xd1b3400f8f9cff68, ++ 0x91376c36d99995be,0x23100809b9c21fa1, ++ 0xb58547448ffffb2d,0xabd40a0c2832a78a, ++ 0xe2e69915b3fff9f9,0x16c90c8f323f516c, ++ 0x8dd01fad907ffc3b,0xae3da7d97f6792e3, ++ 0xb1442798f49ffb4a,0x99cd11cfdf41779c, ++ 0xdd95317f31c7fa1d,0x40405643d711d583, ++ 0x8a7d3eef7f1cfc52,0x482835ea666b2572, ++ 0xad1c8eab5ee43b66,0xda3243650005eecf, ++ 0xd863b256369d4a40,0x90bed43e40076a82, ++ 0x873e4f75e2224e68,0x5a7744a6e804a291, ++ 0xa90de3535aaae202,0x711515d0a205cb36, ++ 0xd3515c2831559a83,0xd5a5b44ca873e03, ++ 0x8412d9991ed58091,0xe858790afe9486c2, ++ 0xa5178fff668ae0b6,0x626e974dbe39a872, ++ 0xce5d73ff402d98e3,0xfb0a3d212dc8128f, ++ 0x80fa687f881c7f8e,0x7ce66634bc9d0b99, ++ 0xa139029f6a239f72,0x1c1fffc1ebc44e80, ++ 0xc987434744ac874e,0xa327ffb266b56220, ++ 0xfbe9141915d7a922,0x4bf1ff9f0062baa8, ++ 0x9d71ac8fada6c9b5,0x6f773fc3603db4a9, ++ 0xc4ce17b399107c22,0xcb550fb4384d21d3, ++ 0xf6019da07f549b2b,0x7e2a53a146606a48, ++ 0x99c102844f94e0fb,0x2eda7444cbfc426d, ++ 0xc0314325637a1939,0xfa911155fefb5308, ++ 0xf03d93eebc589f88,0x793555ab7eba27ca, ++ 0x96267c7535b763b5,0x4bc1558b2f3458de, ++ 0xbbb01b9283253ca2,0x9eb1aaedfb016f16, ++ 0xea9c227723ee8bcb,0x465e15a979c1cadc, ++ 0x92a1958a7675175f,0xbfacd89ec191ec9, ++ 0xb749faed14125d36,0xcef980ec671f667b, ++ 0xe51c79a85916f484,0x82b7e12780e7401a, ++ 0x8f31cc0937ae58d2,0xd1b2ecb8b0908810, ++ 0xb2fe3f0b8599ef07,0x861fa7e6dcb4aa15, ++ 0xdfbdcece67006ac9,0x67a791e093e1d49a, ++ 0x8bd6a141006042bd,0xe0c8bb2c5c6d24e0, ++ 0xaecc49914078536d,0x58fae9f773886e18, ++ 0xda7f5bf590966848,0xaf39a475506a899e, ++ 0x888f99797a5e012d,0x6d8406c952429603, ++ 0xaab37fd7d8f58178,0xc8e5087ba6d33b83, ++ 0xd5605fcdcf32e1d6,0xfb1e4a9a90880a64, ++ 0x855c3be0a17fcd26,0x5cf2eea09a55067f, ++ 0xa6b34ad8c9dfc06f,0xf42faa48c0ea481e, ++ 0xd0601d8efc57b08b,0xf13b94daf124da26, ++ 0x823c12795db6ce57,0x76c53d08d6b70858, ++ 0xa2cb1717b52481ed,0x54768c4b0c64ca6e, ++ 0xcb7ddcdda26da268,0xa9942f5dcf7dfd09, ++ 0xfe5d54150b090b02,0xd3f93b35435d7c4c, ++ 0x9efa548d26e5a6e1,0xc47bc5014a1a6daf, ++ 0xc6b8e9b0709f109a,0x359ab6419ca1091b, ++ 0xf867241c8cc6d4c0,0xc30163d203c94b62, ++ 0x9b407691d7fc44f8,0x79e0de63425dcf1d, ++ 0xc21094364dfb5636,0x985915fc12f542e4, ++ 0xf294b943e17a2bc4,0x3e6f5b7b17b2939d, ++ 0x979cf3ca6cec5b5a,0xa705992ceecf9c42, ++ 0xbd8430bd08277231,0x50c6ff782a838353, ++ 0xece53cec4a314ebd,0xa4f8bf5635246428, ++ 0x940f4613ae5ed136,0x871b7795e136be99, ++ 0xb913179899f68584,0x28e2557b59846e3f, ++ 0xe757dd7ec07426e5,0x331aeada2fe589cf, ++ 0x9096ea6f3848984f,0x3ff0d2c85def7621, ++ 0xb4bca50b065abe63,0xfed077a756b53a9, ++ 0xe1ebce4dc7f16dfb,0xd3e8495912c62894, ++ 0x8d3360f09cf6e4bd,0x64712dd7abbbd95c, ++ 0xb080392cc4349dec,0xbd8d794d96aacfb3, ++ 0xdca04777f541c567,0xecf0d7a0fc5583a0, ++ 0x89e42caaf9491b60,0xf41686c49db57244, ++ 0xac5d37d5b79b6239,0x311c2875c522ced5, ++ 0xd77485cb25823ac7,0x7d633293366b828b, ++ 0x86a8d39ef77164bc,0xae5dff9c02033197, ++ 0xa8530886b54dbdeb,0xd9f57f830283fdfc, ++ 0xd267caa862a12d66,0xd072df63c324fd7b, ++ 0x8380dea93da4bc60,0x4247cb9e59f71e6d, ++ 0xa46116538d0deb78,0x52d9be85f074e608, ++ 0xcd795be870516656,0x67902e276c921f8b, ++ 0x806bd9714632dff6,0xba1cd8a3db53b6, ++ 0xa086cfcd97bf97f3,0x80e8a40eccd228a4, ++ 0xc8a883c0fdaf7df0,0x6122cd128006b2cd, ++ 0xfad2a4b13d1b5d6c,0x796b805720085f81, ++ 0x9cc3a6eec6311a63,0xcbe3303674053bb0, ++ 0xc3f490aa77bd60fc,0xbedbfc4411068a9c, ++ 0xf4f1b4d515acb93b,0xee92fb5515482d44, ++ 0x991711052d8bf3c5,0x751bdd152d4d1c4a, ++ 0xbf5cd54678eef0b6,0xd262d45a78a0635d, ++ 0xef340a98172aace4,0x86fb897116c87c34, ++ 0x9580869f0e7aac0e,0xd45d35e6ae3d4da0, ++ 0xbae0a846d2195712,0x8974836059cca109, ++ 0xe998d258869facd7,0x2bd1a438703fc94b, ++ 0x91ff83775423cc06,0x7b6306a34627ddcf, ++ 0xb67f6455292cbf08,0x1a3bc84c17b1d542, ++ 0xe41f3d6a7377eeca,0x20caba5f1d9e4a93, ++ 0x8e938662882af53e,0x547eb47b7282ee9c, ++ 0xb23867fb2a35b28d,0xe99e619a4f23aa43, ++ 0xdec681f9f4c31f31,0x6405fa00e2ec94d4, ++ 0x8b3c113c38f9f37e,0xde83bc408dd3dd04, ++ 0xae0b158b4738705e,0x9624ab50b148d445, ++ 0xd98ddaee19068c76,0x3badd624dd9b0957, ++ 0x87f8a8d4cfa417c9,0xe54ca5d70a80e5d6, ++ 0xa9f6d30a038d1dbc,0x5e9fcf4ccd211f4c, ++ 0xd47487cc8470652b,0x7647c3200069671f, ++ 0x84c8d4dfd2c63f3b,0x29ecd9f40041e073, ++ 0xa5fb0a17c777cf09,0xf468107100525890, ++ 0xcf79cc9db955c2cc,0x7182148d4066eeb4, ++ 0x81ac1fe293d599bf,0xc6f14cd848405530, ++ 0xa21727db38cb002f,0xb8ada00e5a506a7c, ++ 0xca9cf1d206fdc03b,0xa6d90811f0e4851c, ++ 0xfd442e4688bd304a,0x908f4a166d1da663, ++ 0x9e4a9cec15763e2e,0x9a598e4e043287fe, ++ 0xc5dd44271ad3cdba,0x40eff1e1853f29fd, ++ 0xf7549530e188c128,0xd12bee59e68ef47c, ++ 0x9a94dd3e8cf578b9,0x82bb74f8301958ce, ++ 0xc13a148e3032d6e7,0xe36a52363c1faf01, ++ 0xf18899b1bc3f8ca1,0xdc44e6c3cb279ac1, ++ 0x96f5600f15a7b7e5,0x29ab103a5ef8c0b9, ++ 0xbcb2b812db11a5de,0x7415d448f6b6f0e7, ++ 0xebdf661791d60f56,0x111b495b3464ad21, ++ 0x936b9fcebb25c995,0xcab10dd900beec34, ++ 0xb84687c269ef3bfb,0x3d5d514f40eea742, ++ 0xe65829b3046b0afa,0xcb4a5a3112a5112, ++ 0x8ff71a0fe2c2e6dc,0x47f0e785eaba72ab, ++ 0xb3f4e093db73a093,0x59ed216765690f56, ++ 0xe0f218b8d25088b8,0x306869c13ec3532c, ++ 0x8c974f7383725573,0x1e414218c73a13fb, ++ 0xafbd2350644eeacf,0xe5d1929ef90898fa, ++ 0xdbac6c247d62a583,0xdf45f746b74abf39, ++ 0x894bc396ce5da772,0x6b8bba8c328eb783, ++ 0xab9eb47c81f5114f,0x66ea92f3f326564, ++ 0xd686619ba27255a2,0xc80a537b0efefebd, ++ 0x8613fd0145877585,0xbd06742ce95f5f36, ++ 0xa798fc4196e952e7,0x2c48113823b73704, ++ 0xd17f3b51fca3a7a0,0xf75a15862ca504c5, ++ 0x82ef85133de648c4,0x9a984d73dbe722fb, ++ 0xa3ab66580d5fdaf5,0xc13e60d0d2e0ebba, ++ 0xcc963fee10b7d1b3,0x318df905079926a8, ++ 0xffbbcfe994e5c61f,0xfdf17746497f7052, ++ 0x9fd561f1fd0f9bd3,0xfeb6ea8bedefa633, ++ 0xc7caba6e7c5382c8,0xfe64a52ee96b8fc0, ++ 0xf9bd690a1b68637b,0x3dfdce7aa3c673b0, ++ 0x9c1661a651213e2d,0x6bea10ca65c084e, ++ 0xc31bfa0fe5698db8,0x486e494fcff30a62, ++ 0xf3e2f893dec3f126,0x5a89dba3c3efccfa, ++ 0x986ddb5c6b3a76b7,0xf89629465a75e01c, ++ 0xbe89523386091465,0xf6bbb397f1135823, ++ 0xee2ba6c0678b597f,0x746aa07ded582e2c, ++ 0x94db483840b717ef,0xa8c2a44eb4571cdc, ++ 0xba121a4650e4ddeb,0x92f34d62616ce413, ++ 0xe896a0d7e51e1566,0x77b020baf9c81d17, ++ 0x915e2486ef32cd60,0xace1474dc1d122e, ++ 0xb5b5ada8aaff80b8,0xd819992132456ba, ++ 0xe3231912d5bf60e6,0x10e1fff697ed6c69, ++ 0x8df5efabc5979c8f,0xca8d3ffa1ef463c1, ++ 0xb1736b96b6fd83b3,0xbd308ff8a6b17cb2, ++ 0xddd0467c64bce4a0,0xac7cb3f6d05ddbde, ++ 0x8aa22c0dbef60ee4,0x6bcdf07a423aa96b, ++ 0xad4ab7112eb3929d,0x86c16c98d2c953c6, ++ 0xd89d64d57a607744,0xe871c7bf077ba8b7, ++ 0x87625f056c7c4a8b,0x11471cd764ad4972, ++ 0xa93af6c6c79b5d2d,0xd598e40d3dd89bcf, ++ 0xd389b47879823479,0x4aff1d108d4ec2c3, ++ 0x843610cb4bf160cb,0xcedf722a585139ba, ++ 0xa54394fe1eedb8fe,0xc2974eb4ee658828, ++ 0xce947a3da6a9273e,0x733d226229feea32, ++ 0x811ccc668829b887,0x806357d5a3f525f, ++ 0xa163ff802a3426a8,0xca07c2dcb0cf26f7, ++ 0xc9bcff6034c13052,0xfc89b393dd02f0b5, ++ 0xfc2c3f3841f17c67,0xbbac2078d443ace2, ++ 0x9d9ba7832936edc0,0xd54b944b84aa4c0d, ++ 0xc5029163f384a931,0xa9e795e65d4df11, ++ 0xf64335bcf065d37d,0x4d4617b5ff4a16d5, ++ 0x99ea0196163fa42e,0x504bced1bf8e4e45, ++ 0xc06481fb9bcf8d39,0xe45ec2862f71e1d6, ++ 0xf07da27a82c37088,0x5d767327bb4e5a4c, ++ 0x964e858c91ba2655,0x3a6a07f8d510f86f, ++ 0xbbe226efb628afea,0x890489f70a55368b, ++ 0xeadab0aba3b2dbe5,0x2b45ac74ccea842e, ++ 0x92c8ae6b464fc96f,0x3b0b8bc90012929d, ++ 0xb77ada0617e3bbcb,0x9ce6ebb40173744, ++ 0xe55990879ddcaabd,0xcc420a6a101d0515, ++ 0x8f57fa54c2a9eab6,0x9fa946824a12232d, ++ 0xb32df8e9f3546564,0x47939822dc96abf9, ++ 0xdff9772470297ebd,0x59787e2b93bc56f7, ++ 0x8bfbea76c619ef36,0x57eb4edb3c55b65a, ++ 0xaefae51477a06b03,0xede622920b6b23f1, ++ 0xdab99e59958885c4,0xe95fab368e45eced, ++ 0x88b402f7fd75539b,0x11dbcb0218ebb414, ++ 0xaae103b5fcd2a881,0xd652bdc29f26a119, ++ 0xd59944a37c0752a2,0x4be76d3346f0495f, ++ 0x857fcae62d8493a5,0x6f70a4400c562ddb, ++ 0xa6dfbd9fb8e5b88e,0xcb4ccd500f6bb952, ++ 0xd097ad07a71f26b2,0x7e2000a41346a7a7, ++ 0x825ecc24c873782f,0x8ed400668c0c28c8, ++ 0xa2f67f2dfa90563b,0x728900802f0f32fa, ++ 0xcbb41ef979346bca,0x4f2b40a03ad2ffb9, ++ 0xfea126b7d78186bc,0xe2f610c84987bfa8, ++ 0x9f24b832e6b0f436,0xdd9ca7d2df4d7c9, ++ 0xc6ede63fa05d3143,0x91503d1c79720dbb, ++ 0xf8a95fcf88747d94,0x75a44c6397ce912a, ++ 0x9b69dbe1b548ce7c,0xc986afbe3ee11aba, ++ 0xc24452da229b021b,0xfbe85badce996168, ++ 0xf2d56790ab41c2a2,0xfae27299423fb9c3, ++ 0x97c560ba6b0919a5,0xdccd879fc967d41a, ++ 0xbdb6b8e905cb600f,0x5400e987bbc1c920, ++ 0xed246723473e3813,0x290123e9aab23b68, ++ 0x9436c0760c86e30b,0xf9a0b6720aaf6521, ++ 0xb94470938fa89bce,0xf808e40e8d5b3e69, ++ 0xe7958cb87392c2c2,0xb60b1d1230b20e04, ++ 0x90bd77f3483bb9b9,0xb1c6f22b5e6f48c2, ++ 0xb4ecd5f01a4aa828,0x1e38aeb6360b1af3, ++ 0xe2280b6c20dd5232,0x25c6da63c38de1b0, ++ 0x8d590723948a535f,0x579c487e5a38ad0e, ++ 0xb0af48ec79ace837,0x2d835a9df0c6d851, ++ 0xdcdb1b2798182244,0xf8e431456cf88e65, ++ 0x8a08f0f8bf0f156b,0x1b8e9ecb641b58ff, ++ 0xac8b2d36eed2dac5,0xe272467e3d222f3f, ++ 0xd7adf884aa879177,0x5b0ed81dcc6abb0f, ++ 0x86ccbb52ea94baea,0x98e947129fc2b4e9, ++ 0xa87fea27a539e9a5,0x3f2398d747b36224, ++ 0xd29fe4b18e88640e,0x8eec7f0d19a03aad, ++ 0x83a3eeeef9153e89,0x1953cf68300424ac, ++ 0xa48ceaaab75a8e2b,0x5fa8c3423c052dd7, ++ 0xcdb02555653131b6,0x3792f412cb06794d, ++ 0x808e17555f3ebf11,0xe2bbd88bbee40bd0, ++ 0xa0b19d2ab70e6ed6,0x5b6aceaeae9d0ec4, ++ 0xc8de047564d20a8b,0xf245825a5a445275, ++ 0xfb158592be068d2e,0xeed6e2f0f0d56712, ++ 0x9ced737bb6c4183d,0x55464dd69685606b, ++ 0xc428d05aa4751e4c,0xaa97e14c3c26b886, ++ 0xf53304714d9265df,0xd53dd99f4b3066a8, ++ 0x993fe2c6d07b7fab,0xe546a8038efe4029, ++ 0xbf8fdb78849a5f96,0xde98520472bdd033, ++ 0xef73d256a5c0f77c,0x963e66858f6d4440, ++ 0x95a8637627989aad,0xdde7001379a44aa8, ++ 0xbb127c53b17ec159,0x5560c018580d5d52, ++ 0xe9d71b689dde71af,0xaab8f01e6e10b4a6, ++ 0x9226712162ab070d,0xcab3961304ca70e8, ++ 0xb6b00d69bb55c8d1,0x3d607b97c5fd0d22, ++ 0xe45c10c42a2b3b05,0x8cb89a7db77c506a, ++ 0x8eb98a7a9a5b04e3,0x77f3608e92adb242, ++ 0xb267ed1940f1c61c,0x55f038b237591ed3, ++ 0xdf01e85f912e37a3,0x6b6c46dec52f6688, ++ 0x8b61313bbabce2c6,0x2323ac4b3b3da015, ++ 0xae397d8aa96c1b77,0xabec975e0a0d081a, ++ 0xd9c7dced53c72255,0x96e7bd358c904a21, ++ 0x881cea14545c7575,0x7e50d64177da2e54, ++ 0xaa242499697392d2,0xdde50bd1d5d0b9e9, ++ 0xd4ad2dbfc3d07787,0x955e4ec64b44e864, ++ 0x84ec3c97da624ab4,0xbd5af13bef0b113e, ++ 0xa6274bbdd0fadd61,0xecb1ad8aeacdd58e, ++ 0xcfb11ead453994ba,0x67de18eda5814af2, ++ 0x81ceb32c4b43fcf4,0x80eacf948770ced7, ++ 0xa2425ff75e14fc31,0xa1258379a94d028d, ++ 0xcad2f7f5359a3b3e,0x96ee45813a04330, ++ 0xfd87b5f28300ca0d,0x8bca9d6e188853fc, ++ 0x9e74d1b791e07e48,0x775ea264cf55347e, ++ 0xc612062576589dda,0x95364afe032a81a0, ++ 0xf79687aed3eec551,0x3a83ddbd83f52210, ++ 0x9abe14cd44753b52,0xc4926a9672793580, ++ 0xc16d9a0095928a27,0x75b7053c0f178400, ++ 0xf1c90080baf72cb1,0x5324c68b12dd6800, ++ 0x971da05074da7bee,0xd3f6fc16ebca8000, ++ 0xbce5086492111aea,0x88f4bb1ca6bd0000, ++ 0xec1e4a7db69561a5,0x2b31e9e3d0700000, ++ 0x9392ee8e921d5d07,0x3aff322e62600000, ++ 0xb877aa3236a4b449,0x9befeb9fad487c3, ++ 0xe69594bec44de15b,0x4c2ebe687989a9b4, ++ 0x901d7cf73ab0acd9,0xf9d37014bf60a11, ++ 0xb424dc35095cd80f,0x538484c19ef38c95, ++ 0xe12e13424bb40e13,0x2865a5f206b06fba, ++ 0x8cbccc096f5088cb,0xf93f87b7442e45d4, ++ 0xafebff0bcb24aafe,0xf78f69a51539d749, ++ 0xdbe6fecebdedd5be,0xb573440e5a884d1c, ++ 0x89705f4136b4a597,0x31680a88f8953031, ++ 0xabcc77118461cefc,0xfdc20d2b36ba7c3e, ++ 0xd6bf94d5e57a42bc,0x3d32907604691b4d, ++ 0x8637bd05af6c69b5,0xa63f9a49c2c1b110, ++ 0xa7c5ac471b478423,0xfcf80dc33721d54, ++ 0xd1b71758e219652b,0xd3c36113404ea4a9, ++ 0x83126e978d4fdf3b,0x645a1cac083126ea, ++ 0xa3d70a3d70a3d70a,0x3d70a3d70a3d70a4, ++ 0xcccccccccccccccc,0xcccccccccccccccd, ++ 0x8000000000000000,0x0, ++ 0xa000000000000000,0x0, ++ 0xc800000000000000,0x0, ++ 0xfa00000000000000,0x0, ++ 0x9c40000000000000,0x0, ++ 0xc350000000000000,0x0, ++ 0xf424000000000000,0x0, ++ 0x9896800000000000,0x0, ++ 0xbebc200000000000,0x0, ++ 0xee6b280000000000,0x0, ++ 0x9502f90000000000,0x0, ++ 0xba43b74000000000,0x0, ++ 0xe8d4a51000000000,0x0, ++ 0x9184e72a00000000,0x0, ++ 0xb5e620f480000000,0x0, ++ 0xe35fa931a0000000,0x0, ++ 0x8e1bc9bf04000000,0x0, ++ 0xb1a2bc2ec5000000,0x0, ++ 0xde0b6b3a76400000,0x0, ++ 0x8ac7230489e80000,0x0, ++ 0xad78ebc5ac620000,0x0, ++ 0xd8d726b7177a8000,0x0, ++ 0x878678326eac9000,0x0, ++ 0xa968163f0a57b400,0x0, ++ 0xd3c21bcecceda100,0x0, ++ 0x84595161401484a0,0x0, ++ 0xa56fa5b99019a5c8,0x0, ++ 0xcecb8f27f4200f3a,0x0, ++ 0x813f3978f8940984,0x4000000000000000, ++ 0xa18f07d736b90be5,0x5000000000000000, ++ 0xc9f2c9cd04674ede,0xa400000000000000, ++ 0xfc6f7c4045812296,0x4d00000000000000, ++ 0x9dc5ada82b70b59d,0xf020000000000000, ++ 0xc5371912364ce305,0x6c28000000000000, ++ 0xf684df56c3e01bc6,0xc732000000000000, ++ 0x9a130b963a6c115c,0x3c7f400000000000, ++ 0xc097ce7bc90715b3,0x4b9f100000000000, ++ 0xf0bdc21abb48db20,0x1e86d40000000000, ++ 0x96769950b50d88f4,0x1314448000000000, ++ 0xbc143fa4e250eb31,0x17d955a000000000, ++ 0xeb194f8e1ae525fd,0x5dcfab0800000000, ++ 0x92efd1b8d0cf37be,0x5aa1cae500000000, ++ 0xb7abc627050305ad,0xf14a3d9e40000000, ++ 0xe596b7b0c643c719,0x6d9ccd05d0000000, ++ 0x8f7e32ce7bea5c6f,0xe4820023a2000000, ++ 0xb35dbf821ae4f38b,0xdda2802c8a800000, ++ 0xe0352f62a19e306e,0xd50b2037ad200000, ++ 0x8c213d9da502de45,0x4526f422cc340000, ++ 0xaf298d050e4395d6,0x9670b12b7f410000, ++ 0xdaf3f04651d47b4c,0x3c0cdd765f114000, ++ 0x88d8762bf324cd0f,0xa5880a69fb6ac800, ++ 0xab0e93b6efee0053,0x8eea0d047a457a00, ++ 0xd5d238a4abe98068,0x72a4904598d6d880, ++ 0x85a36366eb71f041,0x47a6da2b7f864750, ++ 0xa70c3c40a64e6c51,0x999090b65f67d924, ++ 0xd0cf4b50cfe20765,0xfff4b4e3f741cf6d, ++ 0x82818f1281ed449f,0xbff8f10e7a8921a4, ++ 0xa321f2d7226895c7,0xaff72d52192b6a0d, ++ 0xcbea6f8ceb02bb39,0x9bf4f8a69f764490, ++ 0xfee50b7025c36a08,0x2f236d04753d5b4, ++ 0x9f4f2726179a2245,0x1d762422c946590, ++ 0xc722f0ef9d80aad6,0x424d3ad2b7b97ef5, ++ 0xf8ebad2b84e0d58b,0xd2e0898765a7deb2, ++ 0x9b934c3b330c8577,0x63cc55f49f88eb2f, ++ 0xc2781f49ffcfa6d5,0x3cbf6b71c76b25fb, ++ 0xf316271c7fc3908a,0x8bef464e3945ef7a, ++ 0x97edd871cfda3a56,0x97758bf0e3cbb5ac, ++ 0xbde94e8e43d0c8ec,0x3d52eeed1cbea317, ++ 0xed63a231d4c4fb27,0x4ca7aaa863ee4bdd, ++ 0x945e455f24fb1cf8,0x8fe8caa93e74ef6a, ++ 0xb975d6b6ee39e436,0xb3e2fd538e122b44, ++ 0xe7d34c64a9c85d44,0x60dbbca87196b616, ++ 0x90e40fbeea1d3a4a,0xbc8955e946fe31cd, ++ 0xb51d13aea4a488dd,0x6babab6398bdbe41, ++ 0xe264589a4dcdab14,0xc696963c7eed2dd1, ++ 0x8d7eb76070a08aec,0xfc1e1de5cf543ca2, ++ 0xb0de65388cc8ada8,0x3b25a55f43294bcb, ++ 0xdd15fe86affad912,0x49ef0eb713f39ebe, ++ 0x8a2dbf142dfcc7ab,0x6e3569326c784337, ++ 0xacb92ed9397bf996,0x49c2c37f07965404, ++ 0xd7e77a8f87daf7fb,0xdc33745ec97be906, ++ 0x86f0ac99b4e8dafd,0x69a028bb3ded71a3, ++ 0xa8acd7c0222311bc,0xc40832ea0d68ce0c, ++ 0xd2d80db02aabd62b,0xf50a3fa490c30190, ++ 0x83c7088e1aab65db,0x792667c6da79e0fa, ++ 0xa4b8cab1a1563f52,0x577001b891185938, ++ 0xcde6fd5e09abcf26,0xed4c0226b55e6f86, ++ 0x80b05e5ac60b6178,0x544f8158315b05b4, ++ 0xa0dc75f1778e39d6,0x696361ae3db1c721, ++ 0xc913936dd571c84c,0x3bc3a19cd1e38e9, ++ 0xfb5878494ace3a5f,0x4ab48a04065c723, ++ 0x9d174b2dcec0e47b,0x62eb0d64283f9c76, ++ 0xc45d1df942711d9a,0x3ba5d0bd324f8394, ++ 0xf5746577930d6500,0xca8f44ec7ee36479, ++ 0x9968bf6abbe85f20,0x7e998b13cf4e1ecb, ++ 0xbfc2ef456ae276e8,0x9e3fedd8c321a67e, ++ 0xefb3ab16c59b14a2,0xc5cfe94ef3ea101e, ++ 0x95d04aee3b80ece5,0xbba1f1d158724a12, ++ 0xbb445da9ca61281f,0x2a8a6e45ae8edc97, ++ 0xea1575143cf97226,0xf52d09d71a3293bd, ++ 0x924d692ca61be758,0x593c2626705f9c56, ++ 0xb6e0c377cfa2e12e,0x6f8b2fb00c77836c, ++ 0xe498f455c38b997a,0xb6dfb9c0f956447, ++ 0x8edf98b59a373fec,0x4724bd4189bd5eac, ++ 0xb2977ee300c50fe7,0x58edec91ec2cb657, ++ 0xdf3d5e9bc0f653e1,0x2f2967b66737e3ed, ++ 0x8b865b215899f46c,0xbd79e0d20082ee74, ++ 0xae67f1e9aec07187,0xecd8590680a3aa11, ++ 0xda01ee641a708de9,0xe80e6f4820cc9495, ++ 0x884134fe908658b2,0x3109058d147fdcdd, ++ 0xaa51823e34a7eede,0xbd4b46f0599fd415, ++ 0xd4e5e2cdc1d1ea96,0x6c9e18ac7007c91a, ++ 0x850fadc09923329e,0x3e2cf6bc604ddb0, ++ 0xa6539930bf6bff45,0x84db8346b786151c, ++ 0xcfe87f7cef46ff16,0xe612641865679a63, ++ 0x81f14fae158c5f6e,0x4fcb7e8f3f60c07e, ++ 0xa26da3999aef7749,0xe3be5e330f38f09d, ++ 0xcb090c8001ab551c,0x5cadf5bfd3072cc5, ++ 0xfdcb4fa002162a63,0x73d9732fc7c8f7f6, ++ 0x9e9f11c4014dda7e,0x2867e7fddcdd9afa, ++ 0xc646d63501a1511d,0xb281e1fd541501b8, ++ 0xf7d88bc24209a565,0x1f225a7ca91a4226, ++ 0x9ae757596946075f,0x3375788de9b06958, ++ 0xc1a12d2fc3978937,0x52d6b1641c83ae, ++ 0xf209787bb47d6b84,0xc0678c5dbd23a49a, ++ 0x9745eb4d50ce6332,0xf840b7ba963646e0, ++ 0xbd176620a501fbff,0xb650e5a93bc3d898, ++ 0xec5d3fa8ce427aff,0xa3e51f138ab4cebe, ++ 0x93ba47c980e98cdf,0xc66f336c36b10137, ++ 0xb8a8d9bbe123f017,0xb80b0047445d4184, ++ 0xe6d3102ad96cec1d,0xa60dc059157491e5, ++ 0x9043ea1ac7e41392,0x87c89837ad68db2f, ++ 0xb454e4a179dd1877,0x29babe4598c311fb, ++ 0xe16a1dc9d8545e94,0xf4296dd6fef3d67a, ++ 0x8ce2529e2734bb1d,0x1899e4a65f58660c, ++ 0xb01ae745b101e9e4,0x5ec05dcff72e7f8f, ++ 0xdc21a1171d42645d,0x76707543f4fa1f73, ++ 0x899504ae72497eba,0x6a06494a791c53a8, ++ 0xabfa45da0edbde69,0x487db9d17636892, ++ 0xd6f8d7509292d603,0x45a9d2845d3c42b6, ++ 0x865b86925b9bc5c2,0xb8a2392ba45a9b2, ++ 0xa7f26836f282b732,0x8e6cac7768d7141e, ++ 0xd1ef0244af2364ff,0x3207d795430cd926, ++ 0x8335616aed761f1f,0x7f44e6bd49e807b8, ++ 0xa402b9c5a8d3a6e7,0x5f16206c9c6209a6, ++ 0xcd036837130890a1,0x36dba887c37a8c0f, ++ 0x802221226be55a64,0xc2494954da2c9789, ++ 0xa02aa96b06deb0fd,0xf2db9baa10b7bd6c, ++ 0xc83553c5c8965d3d,0x6f92829494e5acc7, ++ 0xfa42a8b73abbf48c,0xcb772339ba1f17f9, ++ 0x9c69a97284b578d7,0xff2a760414536efb, ++ 0xc38413cf25e2d70d,0xfef5138519684aba, ++ 0xf46518c2ef5b8cd1,0x7eb258665fc25d69, ++ 0x98bf2f79d5993802,0xef2f773ffbd97a61, ++ 0xbeeefb584aff8603,0xaafb550ffacfd8fa, ++ 0xeeaaba2e5dbf6784,0x95ba2a53f983cf38, ++ 0x952ab45cfa97a0b2,0xdd945a747bf26183, ++ 0xba756174393d88df,0x94f971119aeef9e4, ++ 0xe912b9d1478ceb17,0x7a37cd5601aab85d, ++ 0x91abb422ccb812ee,0xac62e055c10ab33a, ++ 0xb616a12b7fe617aa,0x577b986b314d6009, ++ 0xe39c49765fdf9d94,0xed5a7e85fda0b80b, ++ 0x8e41ade9fbebc27d,0x14588f13be847307, ++ 0xb1d219647ae6b31c,0x596eb2d8ae258fc8, ++ 0xde469fbd99a05fe3,0x6fca5f8ed9aef3bb, ++ 0x8aec23d680043bee,0x25de7bb9480d5854, ++ 0xada72ccc20054ae9,0xaf561aa79a10ae6a, ++ 0xd910f7ff28069da4,0x1b2ba1518094da04, ++ 0x87aa9aff79042286,0x90fb44d2f05d0842, ++ 0xa99541bf57452b28,0x353a1607ac744a53, ++ 0xd3fa922f2d1675f2,0x42889b8997915ce8, ++ 0x847c9b5d7c2e09b7,0x69956135febada11, ++ 0xa59bc234db398c25,0x43fab9837e699095, ++ 0xcf02b2c21207ef2e,0x94f967e45e03f4bb, ++ 0x8161afb94b44f57d,0x1d1be0eebac278f5, ++ 0xa1ba1ba79e1632dc,0x6462d92a69731732, ++ 0xca28a291859bbf93,0x7d7b8f7503cfdcfe, ++ 0xfcb2cb35e702af78,0x5cda735244c3d43e, ++ 0x9defbf01b061adab,0x3a0888136afa64a7, ++ 0xc56baec21c7a1916,0x88aaa1845b8fdd0, ++ 0xf6c69a72a3989f5b,0x8aad549e57273d45, ++ 0x9a3c2087a63f6399,0x36ac54e2f678864b, ++ 0xc0cb28a98fcf3c7f,0x84576a1bb416a7dd, ++ 0xf0fdf2d3f3c30b9f,0x656d44a2a11c51d5, ++ 0x969eb7c47859e743,0x9f644ae5a4b1b325, ++ 0xbc4665b596706114,0x873d5d9f0dde1fee, ++ 0xeb57ff22fc0c7959,0xa90cb506d155a7ea, ++ 0x9316ff75dd87cbd8,0x9a7f12442d588f2, ++ 0xb7dcbf5354e9bece,0xc11ed6d538aeb2f, ++ 0xe5d3ef282a242e81,0x8f1668c8a86da5fa, ++ 0x8fa475791a569d10,0xf96e017d694487bc, ++ 0xb38d92d760ec4455,0x37c981dcc395a9ac, ++ 0xe070f78d3927556a,0x85bbe253f47b1417, ++ 0x8c469ab843b89562,0x93956d7478ccec8e, ++ 0xaf58416654a6babb,0x387ac8d1970027b2, ++ 0xdb2e51bfe9d0696a,0x6997b05fcc0319e, ++ 0x88fcf317f22241e2,0x441fece3bdf81f03, ++ 0xab3c2fddeeaad25a,0xd527e81cad7626c3, ++ 0xd60b3bd56a5586f1,0x8a71e223d8d3b074, ++ 0x85c7056562757456,0xf6872d5667844e49, ++ 0xa738c6bebb12d16c,0xb428f8ac016561db, ++ 0xd106f86e69d785c7,0xe13336d701beba52, ++ 0x82a45b450226b39c,0xecc0024661173473, ++ 0xa34d721642b06084,0x27f002d7f95d0190, ++ 0xcc20ce9bd35c78a5,0x31ec038df7b441f4, ++ 0xff290242c83396ce,0x7e67047175a15271, ++ 0x9f79a169bd203e41,0xf0062c6e984d386, ++ 0xc75809c42c684dd1,0x52c07b78a3e60868, ++ 0xf92e0c3537826145,0xa7709a56ccdf8a82, ++ 0x9bbcc7a142b17ccb,0x88a66076400bb691, ++ 0xc2abf989935ddbfe,0x6acff893d00ea435, ++ 0xf356f7ebf83552fe,0x583f6b8c4124d43, ++ 0x98165af37b2153de,0xc3727a337a8b704a, ++ 0xbe1bf1b059e9a8d6,0x744f18c0592e4c5c, ++ 0xeda2ee1c7064130c,0x1162def06f79df73, ++ 0x9485d4d1c63e8be7,0x8addcb5645ac2ba8, ++ 0xb9a74a0637ce2ee1,0x6d953e2bd7173692, ++ 0xe8111c87c5c1ba99,0xc8fa8db6ccdd0437, ++ 0x910ab1d4db9914a0,0x1d9c9892400a22a2, ++ 0xb54d5e4a127f59c8,0x2503beb6d00cab4b, ++ 0xe2a0b5dc971f303a,0x2e44ae64840fd61d, ++ 0x8da471a9de737e24,0x5ceaecfed289e5d2, ++ 0xb10d8e1456105dad,0x7425a83e872c5f47, ++ 0xdd50f1996b947518,0xd12f124e28f77719, ++ 0x8a5296ffe33cc92f,0x82bd6b70d99aaa6f, ++ 0xace73cbfdc0bfb7b,0x636cc64d1001550b, ++ 0xd8210befd30efa5a,0x3c47f7e05401aa4e, ++ 0x8714a775e3e95c78,0x65acfaec34810a71, ++ 0xa8d9d1535ce3b396,0x7f1839a741a14d0d, ++ 0xd31045a8341ca07c,0x1ede48111209a050, ++ 0x83ea2b892091e44d,0x934aed0aab460432, ++ 0xa4e4b66b68b65d60,0xf81da84d5617853f, ++ 0xce1de40642e3f4b9,0x36251260ab9d668e, ++ 0x80d2ae83e9ce78f3,0xc1d72b7c6b426019, ++ 0xa1075a24e4421730,0xb24cf65b8612f81f, ++ 0xc94930ae1d529cfc,0xdee033f26797b627, ++ 0xfb9b7cd9a4a7443c,0x169840ef017da3b1, ++ 0x9d412e0806e88aa5,0x8e1f289560ee864e, ++ 0xc491798a08a2ad4e,0xf1a6f2bab92a27e2, ++ 0xf5b5d7ec8acb58a2,0xae10af696774b1db, ++ 0x9991a6f3d6bf1765,0xacca6da1e0a8ef29, ++ 0xbff610b0cc6edd3f,0x17fd090a58d32af3, ++ 0xeff394dcff8a948e,0xddfc4b4cef07f5b0, ++ 0x95f83d0a1fb69cd9,0x4abdaf101564f98e, ++ 0xbb764c4ca7a4440f,0x9d6d1ad41abe37f1, ++ 0xea53df5fd18d5513,0x84c86189216dc5ed, ++ 0x92746b9be2f8552c,0x32fd3cf5b4e49bb4, ++ 0xb7118682dbb66a77,0x3fbc8c33221dc2a1, ++ 0xe4d5e82392a40515,0xfabaf3feaa5334a, ++ 0x8f05b1163ba6832d,0x29cb4d87f2a7400e, ++ 0xb2c71d5bca9023f8,0x743e20e9ef511012, ++ 0xdf78e4b2bd342cf6,0x914da9246b255416, ++ 0x8bab8eefb6409c1a,0x1ad089b6c2f7548e, ++ 0xae9672aba3d0c320,0xa184ac2473b529b1, ++ 0xda3c0f568cc4f3e8,0xc9e5d72d90a2741e, ++ 0x8865899617fb1871,0x7e2fa67c7a658892, ++ 0xaa7eebfb9df9de8d,0xddbb901b98feeab7, ++ 0xd51ea6fa85785631,0x552a74227f3ea565, ++ 0x8533285c936b35de,0xd53a88958f87275f, ++ 0xa67ff273b8460356,0x8a892abaf368f137, ++ 0xd01fef10a657842c,0x2d2b7569b0432d85, ++ 0x8213f56a67f6b29b,0x9c3b29620e29fc73, ++ 0xa298f2c501f45f42,0x8349f3ba91b47b8f, ++ 0xcb3f2f7642717713,0x241c70a936219a73, ++ 0xfe0efb53d30dd4d7,0xed238cd383aa0110, ++ 0x9ec95d1463e8a506,0xf4363804324a40aa, ++ 0xc67bb4597ce2ce48,0xb143c6053edcd0d5, ++ 0xf81aa16fdc1b81da,0xdd94b7868e94050a, ++ 0x9b10a4e5e9913128,0xca7cf2b4191c8326, ++ 0xc1d4ce1f63f57d72,0xfd1c2f611f63a3f0, ++ 0xf24a01a73cf2dccf,0xbc633b39673c8cec, ++ 0x976e41088617ca01,0xd5be0503e085d813, ++ 0xbd49d14aa79dbc82,0x4b2d8644d8a74e18, ++ 0xec9c459d51852ba2,0xddf8e7d60ed1219e, ++ 0x93e1ab8252f33b45,0xcabb90e5c942b503, ++ 0xb8da1662e7b00a17,0x3d6a751f3b936243, ++ 0xe7109bfba19c0c9d,0xcc512670a783ad4, ++ 0x906a617d450187e2,0x27fb2b80668b24c5, ++ 0xb484f9dc9641e9da,0xb1f9f660802dedf6, ++ 0xe1a63853bbd26451,0x5e7873f8a0396973, ++ 0x8d07e33455637eb2,0xdb0b487b6423e1e8, ++ 0xb049dc016abc5e5f,0x91ce1a9a3d2cda62, ++ 0xdc5c5301c56b75f7,0x7641a140cc7810fb, ++ 0x89b9b3e11b6329ba,0xa9e904c87fcb0a9d, ++ 0xac2820d9623bf429,0x546345fa9fbdcd44, ++ 0xd732290fbacaf133,0xa97c177947ad4095, ++ 0x867f59a9d4bed6c0,0x49ed8eabcccc485d, ++ 0xa81f301449ee8c70,0x5c68f256bfff5a74, ++ 0xd226fc195c6a2f8c,0x73832eec6fff3111, ++ 0x83585d8fd9c25db7,0xc831fd53c5ff7eab, ++ 0xa42e74f3d032f525,0xba3e7ca8b77f5e55, ++ 0xcd3a1230c43fb26f,0x28ce1bd2e55f35eb, ++ 0x80444b5e7aa7cf85,0x7980d163cf5b81b3, ++ 0xa0555e361951c366,0xd7e105bcc332621f, ++ 0xc86ab5c39fa63440,0x8dd9472bf3fefaa7, ++ 0xfa856334878fc150,0xb14f98f6f0feb951, ++ 0x9c935e00d4b9d8d2,0x6ed1bf9a569f33d3, ++ 0xc3b8358109e84f07,0xa862f80ec4700c8, ++ 0xf4a642e14c6262c8,0xcd27bb612758c0fa, ++ 0x98e7e9cccfbd7dbd,0x8038d51cb897789c, ++ 0xbf21e44003acdd2c,0xe0470a63e6bd56c3, ++ 0xeeea5d5004981478,0x1858ccfce06cac74, ++ 0x95527a5202df0ccb,0xf37801e0c43ebc8, ++ 0xbaa718e68396cffd,0xd30560258f54e6ba, ++ 0xe950df20247c83fd,0x47c6b82ef32a2069, ++ 0x91d28b7416cdd27e,0x4cdc331d57fa5441, ++ 0xb6472e511c81471d,0xe0133fe4adf8e952, ++ 0xe3d8f9e563a198e5,0x58180fddd97723a6, ++ 0x8e679c2f5e44ff8f,0x570f09eaa7ea7648,}; ++ ++} // namespace internal ++} // namespace simdjson ++/* end file src/internal/numberparsing_tables.cpp */ ++/* begin file src/internal/simdprune_tables.cpp */ ++#if SIMDJSON_IMPLEMENTATION_ARM64 || SIMDJSON_IMPLEMENTATION_ICELAKE || SIMDJSON_IMPLEMENTATION_HASWELL || SIMDJSON_IMPLEMENTATION_WESTMERE || SIMDJSON_IMPLEMENTATION_PPC64 ++ ++#include ++ ++namespace simdjson { // table modified and copied from ++namespace internal { // http://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetTable ++SIMDJSON_DLLIMPORTEXPORT const unsigned char BitsSetTable256mul2[256] = { ++ 0, 2, 2, 4, 2, 4, 4, 6, 2, 4, 4, 6, 4, 6, 6, 8, 2, 4, 4, ++ 6, 4, 6, 6, 8, 4, 6, 6, 8, 6, 8, 8, 10, 2, 4, 4, 6, 4, 6, ++ 6, 8, 4, 6, 6, 8, 6, 8, 8, 10, 4, 6, 6, 8, 6, 8, 8, 10, 6, ++ 8, 8, 10, 8, 10, 10, 12, 2, 4, 4, 6, 4, 6, 6, 8, 4, 6, 6, 8, ++ 6, 8, 8, 10, 4, 6, 6, 8, 6, 8, 8, 10, 6, 8, 8, 10, 8, 10, 10, ++ 12, 4, 6, 6, 8, 6, 8, 8, 10, 6, 8, 8, 10, 8, 10, 10, 12, 6, 8, ++ 8, 10, 8, 10, 10, 12, 8, 10, 10, 12, 10, 12, 12, 14, 2, 4, 4, 6, 4, ++ 6, 6, 8, 4, 6, 6, 8, 6, 8, 8, 10, 4, 6, 6, 8, 6, 8, 8, 10, ++ 6, 8, 8, 10, 8, 10, 10, 12, 4, 6, 6, 8, 6, 8, 8, 10, 6, 8, 8, ++ 10, 8, 10, 10, 12, 6, 8, 8, 10, 8, 10, 10, 12, 8, 10, 10, 12, 10, 12, ++ 12, 14, 4, 6, 6, 8, 6, 8, 8, 10, 6, 8, 8, 10, 8, 10, 10, 12, 6, ++ 8, 8, 10, 8, 10, 10, 12, 8, 10, 10, 12, 10, 12, 12, 14, 6, 8, 8, 10, ++ 8, 10, 10, 12, 8, 10, 10, 12, 10, 12, 12, 14, 8, 10, 10, 12, 10, 12, 12, ++ 14, 10, 12, 12, 14, 12, 14, 14, 16}; ++ ++SIMDJSON_DLLIMPORTEXPORT const uint8_t pshufb_combine_table[272] = { ++ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, ++ 0x0c, 0x0d, 0x0e, 0x0f, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x08, ++ 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0xff, 0x00, 0x01, 0x02, 0x03, ++ 0x04, 0x05, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0xff, 0xff, ++ 0x00, 0x01, 0x02, 0x03, 0x04, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, ++ 0x0f, 0xff, 0xff, 0xff, 0x00, 0x01, 0x02, 0x03, 0x08, 0x09, 0x0a, 0x0b, ++ 0x0c, 0x0d, 0x0e, 0x0f, 0xff, 0xff, 0xff, 0xff, 0x00, 0x01, 0x02, 0x08, ++ 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, ++ 0x00, 0x01, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0xff, 0xff, ++ 0xff, 0xff, 0xff, 0xff, 0x00, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, ++ 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x08, 0x09, 0x0a, 0x0b, ++ 0x0c, 0x0d, 0x0e, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, ++}; ++ ++// 256 * 8 bytes = 2kB, easily fits in cache. ++SIMDJSON_DLLIMPORTEXPORT const uint64_t thintable_epi8[256] = { ++ 0x0706050403020100, 0x0007060504030201, 0x0007060504030200, ++ 0x0000070605040302, 0x0007060504030100, 0x0000070605040301, ++ 0x0000070605040300, 0x0000000706050403, 0x0007060504020100, ++ 0x0000070605040201, 0x0000070605040200, 0x0000000706050402, ++ 0x0000070605040100, 0x0000000706050401, 0x0000000706050400, ++ 0x0000000007060504, 0x0007060503020100, 0x0000070605030201, ++ 0x0000070605030200, 0x0000000706050302, 0x0000070605030100, ++ 0x0000000706050301, 0x0000000706050300, 0x0000000007060503, ++ 0x0000070605020100, 0x0000000706050201, 0x0000000706050200, ++ 0x0000000007060502, 0x0000000706050100, 0x0000000007060501, ++ 0x0000000007060500, 0x0000000000070605, 0x0007060403020100, ++ 0x0000070604030201, 0x0000070604030200, 0x0000000706040302, ++ 0x0000070604030100, 0x0000000706040301, 0x0000000706040300, ++ 0x0000000007060403, 0x0000070604020100, 0x0000000706040201, ++ 0x0000000706040200, 0x0000000007060402, 0x0000000706040100, ++ 0x0000000007060401, 0x0000000007060400, 0x0000000000070604, ++ 0x0000070603020100, 0x0000000706030201, 0x0000000706030200, ++ 0x0000000007060302, 0x0000000706030100, 0x0000000007060301, ++ 0x0000000007060300, 0x0000000000070603, 0x0000000706020100, ++ 0x0000000007060201, 0x0000000007060200, 0x0000000000070602, ++ 0x0000000007060100, 0x0000000000070601, 0x0000000000070600, ++ 0x0000000000000706, 0x0007050403020100, 0x0000070504030201, ++ 0x0000070504030200, 0x0000000705040302, 0x0000070504030100, ++ 0x0000000705040301, 0x0000000705040300, 0x0000000007050403, ++ 0x0000070504020100, 0x0000000705040201, 0x0000000705040200, ++ 0x0000000007050402, 0x0000000705040100, 0x0000000007050401, ++ 0x0000000007050400, 0x0000000000070504, 0x0000070503020100, ++ 0x0000000705030201, 0x0000000705030200, 0x0000000007050302, ++ 0x0000000705030100, 0x0000000007050301, 0x0000000007050300, ++ 0x0000000000070503, 0x0000000705020100, 0x0000000007050201, ++ 0x0000000007050200, 0x0000000000070502, 0x0000000007050100, ++ 0x0000000000070501, 0x0000000000070500, 0x0000000000000705, ++ 0x0000070403020100, 0x0000000704030201, 0x0000000704030200, ++ 0x0000000007040302, 0x0000000704030100, 0x0000000007040301, ++ 0x0000000007040300, 0x0000000000070403, 0x0000000704020100, ++ 0x0000000007040201, 0x0000000007040200, 0x0000000000070402, ++ 0x0000000007040100, 0x0000000000070401, 0x0000000000070400, ++ 0x0000000000000704, 0x0000000703020100, 0x0000000007030201, ++ 0x0000000007030200, 0x0000000000070302, 0x0000000007030100, ++ 0x0000000000070301, 0x0000000000070300, 0x0000000000000703, ++ 0x0000000007020100, 0x0000000000070201, 0x0000000000070200, ++ 0x0000000000000702, 0x0000000000070100, 0x0000000000000701, ++ 0x0000000000000700, 0x0000000000000007, 0x0006050403020100, ++ 0x0000060504030201, 0x0000060504030200, 0x0000000605040302, ++ 0x0000060504030100, 0x0000000605040301, 0x0000000605040300, ++ 0x0000000006050403, 0x0000060504020100, 0x0000000605040201, ++ 0x0000000605040200, 0x0000000006050402, 0x0000000605040100, ++ 0x0000000006050401, 0x0000000006050400, 0x0000000000060504, ++ 0x0000060503020100, 0x0000000605030201, 0x0000000605030200, ++ 0x0000000006050302, 0x0000000605030100, 0x0000000006050301, ++ 0x0000000006050300, 0x0000000000060503, 0x0000000605020100, ++ 0x0000000006050201, 0x0000000006050200, 0x0000000000060502, ++ 0x0000000006050100, 0x0000000000060501, 0x0000000000060500, ++ 0x0000000000000605, 0x0000060403020100, 0x0000000604030201, ++ 0x0000000604030200, 0x0000000006040302, 0x0000000604030100, ++ 0x0000000006040301, 0x0000000006040300, 0x0000000000060403, ++ 0x0000000604020100, 0x0000000006040201, 0x0000000006040200, ++ 0x0000000000060402, 0x0000000006040100, 0x0000000000060401, ++ 0x0000000000060400, 0x0000000000000604, 0x0000000603020100, ++ 0x0000000006030201, 0x0000000006030200, 0x0000000000060302, ++ 0x0000000006030100, 0x0000000000060301, 0x0000000000060300, ++ 0x0000000000000603, 0x0000000006020100, 0x0000000000060201, ++ 0x0000000000060200, 0x0000000000000602, 0x0000000000060100, ++ 0x0000000000000601, 0x0000000000000600, 0x0000000000000006, ++ 0x0000050403020100, 0x0000000504030201, 0x0000000504030200, ++ 0x0000000005040302, 0x0000000504030100, 0x0000000005040301, ++ 0x0000000005040300, 0x0000000000050403, 0x0000000504020100, ++ 0x0000000005040201, 0x0000000005040200, 0x0000000000050402, ++ 0x0000000005040100, 0x0000000000050401, 0x0000000000050400, ++ 0x0000000000000504, 0x0000000503020100, 0x0000000005030201, ++ 0x0000000005030200, 0x0000000000050302, 0x0000000005030100, ++ 0x0000000000050301, 0x0000000000050300, 0x0000000000000503, ++ 0x0000000005020100, 0x0000000000050201, 0x0000000000050200, ++ 0x0000000000000502, 0x0000000000050100, 0x0000000000000501, ++ 0x0000000000000500, 0x0000000000000005, 0x0000000403020100, ++ 0x0000000004030201, 0x0000000004030200, 0x0000000000040302, ++ 0x0000000004030100, 0x0000000000040301, 0x0000000000040300, ++ 0x0000000000000403, 0x0000000004020100, 0x0000000000040201, ++ 0x0000000000040200, 0x0000000000000402, 0x0000000000040100, ++ 0x0000000000000401, 0x0000000000000400, 0x0000000000000004, ++ 0x0000000003020100, 0x0000000000030201, 0x0000000000030200, ++ 0x0000000000000302, 0x0000000000030100, 0x0000000000000301, ++ 0x0000000000000300, 0x0000000000000003, 0x0000000000020100, ++ 0x0000000000000201, 0x0000000000000200, 0x0000000000000002, ++ 0x0000000000000100, 0x0000000000000001, 0x0000000000000000, ++ 0x0000000000000000, ++}; //static uint64_t thintable_epi8[256] ++ ++} // namespace internal ++} // namespace simdjson ++ ++#endif // SIMDJSON_IMPLEMENTATION_ARM64 || SIMDJSON_IMPLEMENTATION_ICELAKE || SIMDJSON_IMPLEMENTATION_HASWELL || SIMDJSON_IMPLEMENTATION_WESTMERE || SIMDJSON_IMPLEMENTATION_PPC64 ++/* end file src/internal/simdprune_tables.cpp */ ++/* begin file src/implementation.cpp */ ++#include ++ ++namespace simdjson { ++ ++bool implementation::supported_by_runtime_system() const { ++ uint32_t required_instruction_sets = this->required_instruction_sets(); ++ uint32_t supported_instruction_sets = internal::detect_supported_architectures(); ++ return ((supported_instruction_sets & required_instruction_sets) == required_instruction_sets); ++} ++ ++namespace internal { ++ ++// Static array of known implementations. We're hoping these get baked into the executable ++// without requiring a static initializer. ++ ++#if SIMDJSON_IMPLEMENTATION_ICELAKE ++static const icelake::implementation* get_icelake_singleton() { ++ static const icelake::implementation icelake_singleton{}; ++ return &icelake_singleton; ++} ++#endif ++#if SIMDJSON_IMPLEMENTATION_HASWELL ++static const haswell::implementation* get_haswell_singleton() { ++ static const haswell::implementation haswell_singleton{}; ++ return &haswell_singleton; ++} ++#endif ++#if SIMDJSON_IMPLEMENTATION_WESTMERE ++static const westmere::implementation* get_westmere_singleton() { ++ static const westmere::implementation westmere_singleton{}; ++ return &westmere_singleton; ++} ++#endif // SIMDJSON_IMPLEMENTATION_WESTMERE ++#if SIMDJSON_IMPLEMENTATION_ARM64 ++static const arm64::implementation* get_arm64_singleton() { ++ static const arm64::implementation arm64_singleton{}; ++ return &arm64_singleton; ++} ++#endif // SIMDJSON_IMPLEMENTATION_ARM64 ++#if SIMDJSON_IMPLEMENTATION_PPC64 ++static const ppc64::implementation* get_ppc64_singleton() { ++ static const ppc64::implementation ppc64_singleton{}; ++ return &ppc64_singleton; ++} ++#endif // SIMDJSON_IMPLEMENTATION_PPC64 ++#if SIMDJSON_IMPLEMENTATION_FALLBACK ++static const fallback::implementation* get_fallback_singleton() { ++ static const fallback::implementation fallback_singleton{}; ++ return &fallback_singleton; ++} ++#endif // SIMDJSON_IMPLEMENTATION_FALLBACK ++ ++/** ++ * @private Detects best supported implementation on first use, and sets it ++ */ ++class detect_best_supported_implementation_on_first_use final : public implementation { ++public: ++ const std::string &name() const noexcept final { return set_best()->name(); } ++ const std::string &description() const noexcept final { return set_best()->description(); } ++ uint32_t required_instruction_sets() const noexcept final { return set_best()->required_instruction_sets(); } ++ simdjson_warn_unused error_code create_dom_parser_implementation( ++ size_t capacity, ++ size_t max_length, ++ std::unique_ptr& dst ++ ) const noexcept final { ++ return set_best()->create_dom_parser_implementation(capacity, max_length, dst); ++ } ++ simdjson_warn_unused error_code minify(const uint8_t *buf, size_t len, uint8_t *dst, size_t &dst_len) const noexcept final { ++ return set_best()->minify(buf, len, dst, dst_len); ++ } ++ simdjson_warn_unused bool validate_utf8(const char * buf, size_t len) const noexcept final override { ++ return set_best()->validate_utf8(buf, len); ++ } ++ simdjson_inline detect_best_supported_implementation_on_first_use() noexcept : implementation("best_supported_detector", "Detects the best supported implementation and sets it", 0) {} ++private: ++ const implementation *set_best() const noexcept; ++}; ++ ++static const std::initializer_list& get_available_implementation_pointers() { ++ static const std::initializer_list available_implementation_pointers { ++#if SIMDJSON_IMPLEMENTATION_ICELAKE ++ get_icelake_singleton(), ++#endif ++#if SIMDJSON_IMPLEMENTATION_HASWELL ++ get_haswell_singleton(), ++#endif ++#if SIMDJSON_IMPLEMENTATION_WESTMERE ++ get_westmere_singleton(), ++#endif ++#if SIMDJSON_IMPLEMENTATION_ARM64 ++ get_arm64_singleton(), ++#endif ++#if SIMDJSON_IMPLEMENTATION_PPC64 ++ get_ppc64_singleton(), ++#endif ++#if SIMDJSON_IMPLEMENTATION_FALLBACK ++ get_fallback_singleton(), ++#endif ++ }; // available_implementation_pointers ++ return available_implementation_pointers; ++} ++ ++// So we can return UNSUPPORTED_ARCHITECTURE from the parser when there is no support ++class unsupported_implementation final : public implementation { ++public: ++ simdjson_warn_unused error_code create_dom_parser_implementation( ++ size_t, ++ size_t, ++ std::unique_ptr& ++ ) const noexcept final { ++ return UNSUPPORTED_ARCHITECTURE; ++ } ++ simdjson_warn_unused error_code minify(const uint8_t *, size_t, uint8_t *, size_t &) const noexcept final override { ++ return UNSUPPORTED_ARCHITECTURE; ++ } ++ simdjson_warn_unused bool validate_utf8(const char *, size_t) const noexcept final override { ++ return false; // Just refuse to validate. Given that we have a fallback implementation ++ // it seems unlikely that unsupported_implementation will ever be used. If it is used, ++ // then it will flag all strings as invalid. The alternative is to return an error_code ++ // from which the user has to figure out whether the string is valid UTF-8... which seems ++ // like a lot of work just to handle the very unlikely case that we have an unsupported ++ // implementation. And, when it does happen (that we have an unsupported implementation), ++ // what are the chances that the programmer has a fallback? Given that *we* provide the ++ // fallback, it implies that the programmer would need a fallback for our fallback. ++ } ++ unsupported_implementation() : implementation("unsupported", "Unsupported CPU (no detected SIMD instructions)", 0) {} ++}; ++ ++const unsupported_implementation* get_unsupported_singleton() { ++ static const unsupported_implementation unsupported_singleton{}; ++ return &unsupported_singleton; ++} ++ ++size_t available_implementation_list::size() const noexcept { ++ return internal::get_available_implementation_pointers().size(); ++} ++const implementation * const *available_implementation_list::begin() const noexcept { ++ return internal::get_available_implementation_pointers().begin(); ++} ++const implementation * const *available_implementation_list::end() const noexcept { ++ return internal::get_available_implementation_pointers().end(); ++} ++const implementation *available_implementation_list::detect_best_supported() const noexcept { ++ // They are prelisted in priority order, so we just go down the list ++ uint32_t supported_instruction_sets = internal::detect_supported_architectures(); ++ for (const implementation *impl : internal::get_available_implementation_pointers()) { ++ uint32_t required_instruction_sets = impl->required_instruction_sets(); ++ if ((supported_instruction_sets & required_instruction_sets) == required_instruction_sets) { return impl; } ++ } ++ return get_unsupported_singleton(); // this should never happen? ++} ++ ++const implementation *detect_best_supported_implementation_on_first_use::set_best() const noexcept { ++ SIMDJSON_PUSH_DISABLE_WARNINGS ++ SIMDJSON_DISABLE_DEPRECATED_WARNING // Disable CRT_SECURE warning on MSVC: manually verified this is safe ++ char *force_implementation_name = getenv("SIMDJSON_FORCE_IMPLEMENTATION"); ++ SIMDJSON_POP_DISABLE_WARNINGS ++ ++ if (force_implementation_name) { ++ auto force_implementation = get_available_implementations()[force_implementation_name]; ++ if (force_implementation) { ++ return get_active_implementation() = force_implementation; ++ } else { ++ // Note: abort() and stderr usage within the library is forbidden. ++ return get_active_implementation() = get_unsupported_singleton(); ++ } ++ } ++ return get_active_implementation() = get_available_implementations().detect_best_supported(); ++} ++ ++} // namespace internal ++ ++SIMDJSON_DLLIMPORTEXPORT const internal::available_implementation_list& get_available_implementations() { ++ static const internal::available_implementation_list available_implementations{}; ++ return available_implementations; ++} ++ ++SIMDJSON_DLLIMPORTEXPORT internal::atomic_ptr& get_active_implementation() { ++ static const internal::detect_best_supported_implementation_on_first_use detect_best_supported_implementation_on_first_use_singleton; ++ static internal::atomic_ptr active_implementation{&detect_best_supported_implementation_on_first_use_singleton}; ++ return active_implementation; ++} ++ ++simdjson_warn_unused error_code minify(const char *buf, size_t len, char *dst, size_t &dst_len) noexcept { ++ return get_active_implementation()->minify(reinterpret_cast(buf), len, reinterpret_cast(dst), dst_len); ++} ++simdjson_warn_unused bool validate_utf8(const char *buf, size_t len) noexcept { ++ return get_active_implementation()->validate_utf8(buf, len); ++} ++const implementation * builtin_implementation() { ++ static const implementation * builtin_impl = get_available_implementations()[SIMDJSON_STRINGIFY(SIMDJSON_BUILTIN_IMPLEMENTATION)]; ++ assert(builtin_impl); ++ return builtin_impl; ++} ++ ++ ++} // namespace simdjson ++/* end file src/implementation.cpp */ ++ ++#if SIMDJSON_IMPLEMENTATION_ARM64 ++/* begin file src/arm64/implementation.cpp */ ++/* begin file include/simdjson/arm64/begin.h */ ++// redefining SIMDJSON_IMPLEMENTATION to "arm64" ++// #define SIMDJSON_IMPLEMENTATION arm64 ++/* end file include/simdjson/arm64/begin.h */ ++ ++namespace simdjson { ++namespace arm64 { ++ ++simdjson_warn_unused error_code implementation::create_dom_parser_implementation( ++ size_t capacity, ++ size_t max_depth, ++ std::unique_ptr& dst ++) const noexcept { ++ dst.reset( new (std::nothrow) dom_parser_implementation() ); ++ if (!dst) { return MEMALLOC; } ++ if (auto err = dst->set_capacity(capacity)) ++ return err; ++ if (auto err = dst->set_max_depth(max_depth)) ++ return err; ++ return SUCCESS; ++} ++ ++} // namespace arm64 ++} // namespace simdjson ++ ++/* begin file include/simdjson/arm64/end.h */ ++/* end file include/simdjson/arm64/end.h */ ++/* end file src/arm64/implementation.cpp */ ++/* begin file src/arm64/dom_parser_implementation.cpp */ ++/* begin file include/simdjson/arm64/begin.h */ ++// redefining SIMDJSON_IMPLEMENTATION to "arm64" ++// #define SIMDJSON_IMPLEMENTATION arm64 ++/* end file include/simdjson/arm64/begin.h */ ++ ++// ++// Stage 1 ++// ++namespace simdjson { ++namespace arm64 { ++namespace { ++ ++using namespace simd; ++ ++struct json_character_block { ++ static simdjson_inline json_character_block classify(const simd::simd8x64& in); ++ ++ simdjson_inline uint64_t whitespace() const noexcept { return _whitespace; } ++ simdjson_inline uint64_t op() const noexcept { return _op; } ++ simdjson_inline uint64_t scalar() const noexcept { return ~(op() | whitespace()); } ++ ++ uint64_t _whitespace; ++ uint64_t _op; ++}; ++ ++simdjson_inline json_character_block json_character_block::classify(const simd::simd8x64& in) { ++ // Functional programming causes trouble with Visual Studio. ++ // Keeping this version in comments since it is much nicer: ++ // auto v = in.map([&](simd8 chunk) { ++ // auto nib_lo = chunk & 0xf; ++ // auto nib_hi = chunk.shr<4>(); ++ // auto shuf_lo = nib_lo.lookup_16(16, 0, 0, 0, 0, 0, 0, 0, 0, 8, 12, 1, 2, 9, 0, 0); ++ // auto shuf_hi = nib_hi.lookup_16(8, 0, 18, 4, 0, 1, 0, 1, 0, 0, 0, 3, 2, 1, 0, 0); ++ // return shuf_lo & shuf_hi; ++ // }); ++ const simd8 table1(16, 0, 0, 0, 0, 0, 0, 0, 0, 8, 12, 1, 2, 9, 0, 0); ++ const simd8 table2(8, 0, 18, 4, 0, 1, 0, 1, 0, 0, 0, 3, 2, 1, 0, 0); ++ ++ simd8x64 v( ++ (in.chunks[0] & 0xf).lookup_16(table1) & (in.chunks[0].shr<4>()).lookup_16(table2), ++ (in.chunks[1] & 0xf).lookup_16(table1) & (in.chunks[1].shr<4>()).lookup_16(table2), ++ (in.chunks[2] & 0xf).lookup_16(table1) & (in.chunks[2].shr<4>()).lookup_16(table2), ++ (in.chunks[3] & 0xf).lookup_16(table1) & (in.chunks[3].shr<4>()).lookup_16(table2) ++ ); ++ ++ ++ // We compute whitespace and op separately. If the code later only use one or the ++ // other, given the fact that all functions are aggressively inlined, we can ++ // hope that useless computations will be omitted. This is namely case when ++ // minifying (we only need whitespace). *However* if we only need spaces, ++ // it is likely that we will still compute 'v' above with two lookup_16: one ++ // could do it a bit cheaper. This is in contrast with the x64 implementations ++ // where we can, efficiently, do the white space and structural matching ++ // separately. One reason for this difference is that on ARM NEON, the table ++ // lookups either zero or leave unchanged the characters exceeding 0xF whereas ++ // on x64, the equivalent instruction (pshufb) automatically applies a mask, ++ // ignoring the 4 most significant bits. Thus the x64 implementation is ++ // optimized differently. This being said, if you use this code strictly ++ // just for minification (or just to identify the structural characters), ++ // there is a small untaken optimization opportunity here. We deliberately ++ // do not pick it up. ++ ++ uint64_t op = simd8x64( ++ v.chunks[0].any_bits_set(0x7), ++ v.chunks[1].any_bits_set(0x7), ++ v.chunks[2].any_bits_set(0x7), ++ v.chunks[3].any_bits_set(0x7) ++ ).to_bitmask(); ++ ++ uint64_t whitespace = simd8x64( ++ v.chunks[0].any_bits_set(0x18), ++ v.chunks[1].any_bits_set(0x18), ++ v.chunks[2].any_bits_set(0x18), ++ v.chunks[3].any_bits_set(0x18) ++ ).to_bitmask(); ++ ++ return { whitespace, op }; ++} ++ ++simdjson_inline bool is_ascii(const simd8x64& input) { ++ simd8 bits = input.reduce_or(); ++ return bits.max_val() < 0x80u; ++} ++ ++simdjson_unused simdjson_inline simd8 must_be_continuation(const simd8 prev1, const simd8 prev2, const simd8 prev3) { ++ simd8 is_second_byte = prev1 >= uint8_t(0xc0u); ++ simd8 is_third_byte = prev2 >= uint8_t(0xe0u); ++ simd8 is_fourth_byte = prev3 >= uint8_t(0xf0u); ++ // Use ^ instead of | for is_*_byte, because ^ is commutative, and the caller is using ^ as well. ++ // This will work fine because we only have to report errors for cases with 0-1 lead bytes. ++ // Multiple lead bytes implies 2 overlapping multibyte characters, and if that happens, there is ++ // guaranteed to be at least *one* lead byte that is part of only 1 other multibyte character. ++ // The error will be detected there. ++ return is_second_byte ^ is_third_byte ^ is_fourth_byte; ++} ++ ++simdjson_inline simd8 must_be_2_3_continuation(const simd8 prev2, const simd8 prev3) { ++ simd8 is_third_byte = prev2 >= uint8_t(0xe0u); ++ simd8 is_fourth_byte = prev3 >= uint8_t(0xf0u); ++ return is_third_byte ^ is_fourth_byte; ++} ++ ++} // unnamed namespace ++} // namespace arm64 ++} // namespace simdjson ++ ++/* begin file src/generic/stage1/utf8_lookup4_algorithm.h */ ++namespace simdjson { ++namespace arm64 { ++namespace { ++namespace utf8_validation { ++ ++using namespace simd; ++ ++ simdjson_inline simd8 check_special_cases(const simd8 input, const simd8 prev1) { ++// Bit 0 = Too Short (lead byte/ASCII followed by lead byte/ASCII) ++// Bit 1 = Too Long (ASCII followed by continuation) ++// Bit 2 = Overlong 3-byte ++// Bit 4 = Surrogate ++// Bit 5 = Overlong 2-byte ++// Bit 7 = Two Continuations ++ constexpr const uint8_t TOO_SHORT = 1<<0; // 11______ 0_______ ++ // 11______ 11______ ++ constexpr const uint8_t TOO_LONG = 1<<1; // 0_______ 10______ ++ constexpr const uint8_t OVERLONG_3 = 1<<2; // 11100000 100_____ ++ constexpr const uint8_t SURROGATE = 1<<4; // 11101101 101_____ ++ constexpr const uint8_t OVERLONG_2 = 1<<5; // 1100000_ 10______ ++ constexpr const uint8_t TWO_CONTS = 1<<7; // 10______ 10______ ++ constexpr const uint8_t TOO_LARGE = 1<<3; // 11110100 1001____ ++ // 11110100 101_____ ++ // 11110101 1001____ ++ // 11110101 101_____ ++ // 1111011_ 1001____ ++ // 1111011_ 101_____ ++ // 11111___ 1001____ ++ // 11111___ 101_____ ++ constexpr const uint8_t TOO_LARGE_1000 = 1<<6; ++ // 11110101 1000____ ++ // 1111011_ 1000____ ++ // 11111___ 1000____ ++ constexpr const uint8_t OVERLONG_4 = 1<<6; // 11110000 1000____ ++ ++ const simd8 byte_1_high = prev1.shr<4>().lookup_16( ++ // 0_______ ________ ++ TOO_LONG, TOO_LONG, TOO_LONG, TOO_LONG, ++ TOO_LONG, TOO_LONG, TOO_LONG, TOO_LONG, ++ // 10______ ________ ++ TWO_CONTS, TWO_CONTS, TWO_CONTS, TWO_CONTS, ++ // 1100____ ________ ++ TOO_SHORT | OVERLONG_2, ++ // 1101____ ________ ++ TOO_SHORT, ++ // 1110____ ________ ++ TOO_SHORT | OVERLONG_3 | SURROGATE, ++ // 1111____ ________ ++ TOO_SHORT | TOO_LARGE | TOO_LARGE_1000 | OVERLONG_4 ++ ); ++ constexpr const uint8_t CARRY = TOO_SHORT | TOO_LONG | TWO_CONTS; // These all have ____ in byte 1 . ++ const simd8 byte_1_low = (prev1 & 0x0F).lookup_16( ++ // ____0000 ________ ++ CARRY | OVERLONG_3 | OVERLONG_2 | OVERLONG_4, ++ // ____0001 ________ ++ CARRY | OVERLONG_2, ++ // ____001_ ________ ++ CARRY, ++ CARRY, ++ ++ // ____0100 ________ ++ CARRY | TOO_LARGE, ++ // ____0101 ________ ++ CARRY | TOO_LARGE | TOO_LARGE_1000, ++ // ____011_ ________ ++ CARRY | TOO_LARGE | TOO_LARGE_1000, ++ CARRY | TOO_LARGE | TOO_LARGE_1000, ++ ++ // ____1___ ________ ++ CARRY | TOO_LARGE | TOO_LARGE_1000, ++ CARRY | TOO_LARGE | TOO_LARGE_1000, ++ CARRY | TOO_LARGE | TOO_LARGE_1000, ++ CARRY | TOO_LARGE | TOO_LARGE_1000, ++ CARRY | TOO_LARGE | TOO_LARGE_1000, ++ // ____1101 ________ ++ CARRY | TOO_LARGE | TOO_LARGE_1000 | SURROGATE, ++ CARRY | TOO_LARGE | TOO_LARGE_1000, ++ CARRY | TOO_LARGE | TOO_LARGE_1000 ++ ); ++ const simd8 byte_2_high = input.shr<4>().lookup_16( ++ // ________ 0_______ ++ TOO_SHORT, TOO_SHORT, TOO_SHORT, TOO_SHORT, ++ TOO_SHORT, TOO_SHORT, TOO_SHORT, TOO_SHORT, ++ ++ // ________ 1000____ ++ TOO_LONG | OVERLONG_2 | TWO_CONTS | OVERLONG_3 | TOO_LARGE_1000 | OVERLONG_4, ++ // ________ 1001____ ++ TOO_LONG | OVERLONG_2 | TWO_CONTS | OVERLONG_3 | TOO_LARGE, ++ // ________ 101_____ ++ TOO_LONG | OVERLONG_2 | TWO_CONTS | SURROGATE | TOO_LARGE, ++ TOO_LONG | OVERLONG_2 | TWO_CONTS | SURROGATE | TOO_LARGE, ++ ++ // ________ 11______ ++ TOO_SHORT, TOO_SHORT, TOO_SHORT, TOO_SHORT ++ ); ++ return (byte_1_high & byte_1_low & byte_2_high); ++ } ++ simdjson_inline simd8 check_multibyte_lengths(const simd8 input, ++ const simd8 prev_input, const simd8 sc) { ++ simd8 prev2 = input.prev<2>(prev_input); ++ simd8 prev3 = input.prev<3>(prev_input); ++ simd8 must23 = simd8(must_be_2_3_continuation(prev2, prev3)); ++ simd8 must23_80 = must23 & uint8_t(0x80); ++ return must23_80 ^ sc; ++ } ++ ++ // ++ // Return nonzero if there are incomplete multibyte characters at the end of the block: ++ // e.g. if there is a 4-byte character, but it's 3 bytes from the end. ++ // ++ simdjson_inline simd8 is_incomplete(const simd8 input) { ++ // If the previous input's last 3 bytes match this, they're too short (they ended at EOF): ++ // ... 1111____ 111_____ 11______ ++#if SIMDJSON_IMPLEMENTATION_ICELAKE ++ static const uint8_t max_array[64] = { ++ 255, 255, 255, 255, 255, 255, 255, 255, ++ 255, 255, 255, 255, 255, 255, 255, 255, ++ 255, 255, 255, 255, 255, 255, 255, 255, ++ 255, 255, 255, 255, 255, 255, 255, 255, ++ 255, 255, 255, 255, 255, 255, 255, 255, ++ 255, 255, 255, 255, 255, 255, 255, 255, ++ 255, 255, 255, 255, 255, 255, 255, 255, ++ 255, 255, 255, 255, 255, 0xf0u-1, 0xe0u-1, 0xc0u-1 ++ }; ++#else ++ static const uint8_t max_array[32] = { ++ 255, 255, 255, 255, 255, 255, 255, 255, ++ 255, 255, 255, 255, 255, 255, 255, 255, ++ 255, 255, 255, 255, 255, 255, 255, 255, ++ 255, 255, 255, 255, 255, 0xf0u-1, 0xe0u-1, 0xc0u-1 ++ }; ++#endif ++ const simd8 max_value(&max_array[sizeof(max_array)-sizeof(simd8)]); ++ return input.gt_bits(max_value); ++ } ++ ++ struct utf8_checker { ++ // If this is nonzero, there has been a UTF-8 error. ++ simd8 error; ++ // The last input we received ++ simd8 prev_input_block; ++ // Whether the last input we received was incomplete (used for ASCII fast path) ++ simd8 prev_incomplete; ++ ++ // ++ // Check whether the current bytes are valid UTF-8. ++ // ++ simdjson_inline void check_utf8_bytes(const simd8 input, const simd8 prev_input) { ++ // Flip prev1...prev3 so we can easily determine if they are 2+, 3+ or 4+ lead bytes ++ // (2, 3, 4-byte leads become large positive numbers instead of small negative numbers) ++ simd8 prev1 = input.prev<1>(prev_input); ++ simd8 sc = check_special_cases(input, prev1); ++ this->error |= check_multibyte_lengths(input, prev_input, sc); ++ } ++ ++ // The only problem that can happen at EOF is that a multibyte character is too short ++ // or a byte value too large in the last bytes: check_special_cases only checks for bytes ++ // too large in the first of two bytes. ++ simdjson_inline void check_eof() { ++ // If the previous block had incomplete UTF-8 characters at the end, an ASCII block can't ++ // possibly finish them. ++ this->error |= this->prev_incomplete; ++ } ++ ++#ifndef SIMDJSON_IF_CONSTEXPR ++#if SIMDJSON_CPLUSPLUS17 ++#define SIMDJSON_IF_CONSTEXPR if constexpr ++#else ++#define SIMDJSON_IF_CONSTEXPR if ++#endif ++#endif ++ ++ simdjson_inline void check_next_input(const simd8x64& input) { ++ if(simdjson_likely(is_ascii(input))) { ++ this->error |= this->prev_incomplete; ++ } else { ++ // you might think that a for-loop would work, but under Visual Studio, it is not good enough. ++ static_assert((simd8x64::NUM_CHUNKS == 1) ++ ||(simd8x64::NUM_CHUNKS == 2) ++ || (simd8x64::NUM_CHUNKS == 4), ++ "We support one, two or four chunks per 64-byte block."); ++ SIMDJSON_IF_CONSTEXPR (simd8x64::NUM_CHUNKS == 1) { ++ this->check_utf8_bytes(input.chunks[0], this->prev_input_block); ++ } else SIMDJSON_IF_CONSTEXPR (simd8x64::NUM_CHUNKS == 2) { ++ this->check_utf8_bytes(input.chunks[0], this->prev_input_block); ++ this->check_utf8_bytes(input.chunks[1], input.chunks[0]); ++ } else SIMDJSON_IF_CONSTEXPR (simd8x64::NUM_CHUNKS == 4) { ++ this->check_utf8_bytes(input.chunks[0], this->prev_input_block); ++ this->check_utf8_bytes(input.chunks[1], input.chunks[0]); ++ this->check_utf8_bytes(input.chunks[2], input.chunks[1]); ++ this->check_utf8_bytes(input.chunks[3], input.chunks[2]); ++ } ++ this->prev_incomplete = is_incomplete(input.chunks[simd8x64::NUM_CHUNKS-1]); ++ this->prev_input_block = input.chunks[simd8x64::NUM_CHUNKS-1]; ++ } ++ } ++ // do not forget to call check_eof! ++ simdjson_inline error_code errors() { ++ return this->error.any_bits_set_anywhere() ? error_code::UTF8_ERROR : error_code::SUCCESS; ++ } ++ ++ }; // struct utf8_checker ++} // namespace utf8_validation ++ ++using utf8_validation::utf8_checker; ++ ++} // unnamed namespace ++} // namespace arm64 ++} // namespace simdjson ++/* end file src/generic/stage1/utf8_lookup4_algorithm.h */ ++/* begin file src/generic/stage1/json_structural_indexer.h */ ++// This file contains the common code every implementation uses in stage1 ++// It is intended to be included multiple times and compiled multiple times ++// We assume the file in which it is included already includes ++// "simdjson/stage1.h" (this simplifies amalgation) ++ ++/* begin file src/generic/stage1/buf_block_reader.h */ ++namespace simdjson { ++namespace arm64 { ++namespace { ++ ++// Walks through a buffer in block-sized increments, loading the last part with spaces ++template ++struct buf_block_reader { ++public: ++ simdjson_inline buf_block_reader(const uint8_t *_buf, size_t _len); ++ simdjson_inline size_t block_index(); ++ simdjson_inline bool has_full_block() const; ++ simdjson_inline const uint8_t *full_block() const; ++ /** ++ * Get the last block, padded with spaces. ++ * ++ * There will always be a last block, with at least 1 byte, unless len == 0 (in which case this ++ * function fills the buffer with spaces and returns 0. In particular, if len == STEP_SIZE there ++ * will be 0 full_blocks and 1 remainder block with STEP_SIZE bytes and no spaces for padding. ++ * ++ * @return the number of effective characters in the last block. ++ */ ++ simdjson_inline size_t get_remainder(uint8_t *dst) const; ++ simdjson_inline void advance(); ++private: ++ const uint8_t *buf; ++ const size_t len; ++ const size_t lenminusstep; ++ size_t idx; ++}; ++ ++// Routines to print masks and text for debugging bitmask operations ++simdjson_unused static char * format_input_text_64(const uint8_t *text) { ++ static char buf[sizeof(simd8x64) + 1]; ++ for (size_t i=0; i); i++) { ++ buf[i] = int8_t(text[i]) < ' ' ? '_' : int8_t(text[i]); ++ } ++ buf[sizeof(simd8x64)] = '\0'; ++ return buf; ++} ++ ++// Routines to print masks and text for debugging bitmask operations ++simdjson_unused static char * format_input_text(const simd8x64& in) { ++ static char buf[sizeof(simd8x64) + 1]; ++ in.store(reinterpret_cast(buf)); ++ for (size_t i=0; i); i++) { ++ if (buf[i] < ' ') { buf[i] = '_'; } ++ } ++ buf[sizeof(simd8x64)] = '\0'; ++ return buf; ++} ++ ++simdjson_unused static char * format_mask(uint64_t mask) { ++ static char buf[sizeof(simd8x64) + 1]; ++ for (size_t i=0; i<64; i++) { ++ buf[i] = (mask & (size_t(1) << i)) ? 'X' : ' '; ++ } ++ buf[64] = '\0'; ++ return buf; ++} ++ ++template ++simdjson_inline buf_block_reader::buf_block_reader(const uint8_t *_buf, size_t _len) : buf{_buf}, len{_len}, lenminusstep{len < STEP_SIZE ? 0 : len - STEP_SIZE}, idx{0} {} ++ ++template ++simdjson_inline size_t buf_block_reader::block_index() { return idx; } ++ ++template ++simdjson_inline bool buf_block_reader::has_full_block() const { ++ return idx < lenminusstep; ++} ++ ++template ++simdjson_inline const uint8_t *buf_block_reader::full_block() const { ++ return &buf[idx]; ++} ++ ++template ++simdjson_inline size_t buf_block_reader::get_remainder(uint8_t *dst) const { ++ if(len == idx) { return 0; } // memcpy(dst, null, 0) will trigger an error with some sanitizers ++ std::memset(dst, 0x20, STEP_SIZE); // std::memset STEP_SIZE because it's more efficient to write out 8 or 16 bytes at once. ++ std::memcpy(dst, buf + idx, len - idx); ++ return len - idx; ++} ++ ++template ++simdjson_inline void buf_block_reader::advance() { ++ idx += STEP_SIZE; ++} ++ ++} // unnamed namespace ++} // namespace arm64 ++} // namespace simdjson ++/* end file src/generic/stage1/buf_block_reader.h */ ++/* begin file src/generic/stage1/json_string_scanner.h */ ++namespace simdjson { ++namespace arm64 { ++namespace { ++namespace stage1 { ++ ++struct json_string_block { ++ // We spell out the constructors in the hope of resolving inlining issues with Visual Studio 2017 ++ simdjson_inline json_string_block(uint64_t backslash, uint64_t escaped, uint64_t quote, uint64_t in_string) : ++ _backslash(backslash), _escaped(escaped), _quote(quote), _in_string(in_string) {} ++ ++ // Escaped characters (characters following an escape() character) ++ simdjson_inline uint64_t escaped() const { return _escaped; } ++ // Escape characters (backslashes that are not escaped--i.e. in \\, includes only the first \) ++ simdjson_inline uint64_t escape() const { return _backslash & ~_escaped; } ++ // Real (non-backslashed) quotes ++ simdjson_inline uint64_t quote() const { return _quote; } ++ // Start quotes of strings ++ simdjson_inline uint64_t string_start() const { return _quote & _in_string; } ++ // End quotes of strings ++ simdjson_inline uint64_t string_end() const { return _quote & ~_in_string; } ++ // Only characters inside the string (not including the quotes) ++ simdjson_inline uint64_t string_content() const { return _in_string & ~_quote; } ++ // Return a mask of whether the given characters are inside a string (only works on non-quotes) ++ simdjson_inline uint64_t non_quote_inside_string(uint64_t mask) const { return mask & _in_string; } ++ // Return a mask of whether the given characters are inside a string (only works on non-quotes) ++ simdjson_inline uint64_t non_quote_outside_string(uint64_t mask) const { return mask & ~_in_string; } ++ // Tail of string (everything except the start quote) ++ simdjson_inline uint64_t string_tail() const { return _in_string ^ _quote; } ++ ++ // backslash characters ++ uint64_t _backslash; ++ // escaped characters (backslashed--does not include the hex characters after \u) ++ uint64_t _escaped; ++ // real quotes (non-backslashed ones) ++ uint64_t _quote; ++ // string characters (includes start quote but not end quote) ++ uint64_t _in_string; ++}; ++ ++// Scans blocks for string characters, storing the state necessary to do so ++class json_string_scanner { ++public: ++ simdjson_inline json_string_block next(const simd::simd8x64& in); ++ // Returns either UNCLOSED_STRING or SUCCESS ++ simdjson_inline error_code finish(); ++ ++private: ++ // Intended to be defined by the implementation ++ simdjson_inline uint64_t find_escaped(uint64_t escape); ++ simdjson_inline uint64_t find_escaped_branchless(uint64_t escape); ++ ++ // Whether the last iteration was still inside a string (all 1's = true, all 0's = false). ++ uint64_t prev_in_string = 0ULL; ++ // Whether the first character of the next iteration is escaped. ++ uint64_t prev_escaped = 0ULL; ++}; ++ ++// ++// Finds escaped characters (characters following \). ++// ++// Handles runs of backslashes like \\\" and \\\\" correctly (yielding 0101 and 01010, respectively). ++// ++// Does this by: ++// - Shift the escape mask to get potentially escaped characters (characters after backslashes). ++// - Mask escaped sequences that start on *even* bits with 1010101010 (odd bits are escaped, even bits are not) ++// - Mask escaped sequences that start on *odd* bits with 0101010101 (even bits are escaped, odd bits are not) ++// ++// To distinguish between escaped sequences starting on even/odd bits, it finds the start of all ++// escape sequences, filters out the ones that start on even bits, and adds that to the mask of ++// escape sequences. This causes the addition to clear out the sequences starting on odd bits (since ++// the start bit causes a carry), and leaves even-bit sequences alone. ++// ++// Example: ++// ++// text | \\\ | \\\"\\\" \\\" \\"\\" | ++// escape | xxx | xx xxx xxx xx xx | Removed overflow backslash; will | it into follows_escape ++// odd_starts | x | x x x | escape & ~even_bits & ~follows_escape ++// even_seq | c| cxxx c xx c | c = carry bit -- will be masked out later ++// invert_mask | | cxxx c xx c| even_seq << 1 ++// follows_escape | xx | x xx xxx xxx xx xx | Includes overflow bit ++// escaped | x | x x x x x x x x | ++// desired | x | x x x x x x x x | ++// text | \\\ | \\\"\\\" \\\" \\"\\" | ++// ++simdjson_inline uint64_t json_string_scanner::find_escaped_branchless(uint64_t backslash) { ++ // If there was overflow, pretend the first character isn't a backslash ++ backslash &= ~prev_escaped; ++ uint64_t follows_escape = backslash << 1 | prev_escaped; ++ ++ // Get sequences starting on even bits by clearing out the odd series using + ++ const uint64_t even_bits = 0x5555555555555555ULL; ++ uint64_t odd_sequence_starts = backslash & ~even_bits & ~follows_escape; ++ uint64_t sequences_starting_on_even_bits; ++ prev_escaped = add_overflow(odd_sequence_starts, backslash, &sequences_starting_on_even_bits); ++ uint64_t invert_mask = sequences_starting_on_even_bits << 1; // The mask we want to return is the *escaped* bits, not escapes. ++ ++ // Mask every other backslashed character as an escaped character ++ // Flip the mask for sequences that start on even bits, to correct them ++ return (even_bits ^ invert_mask) & follows_escape; ++} ++ ++// ++// Return a mask of all string characters plus end quotes. ++// ++// prev_escaped is overflow saying whether the next character is escaped. ++// prev_in_string is overflow saying whether we're still in a string. ++// ++// Backslash sequences outside of quotes will be detected in stage 2. ++// ++simdjson_inline json_string_block json_string_scanner::next(const simd::simd8x64& in) { ++ const uint64_t backslash = in.eq('\\'); ++ const uint64_t escaped = find_escaped(backslash); ++ const uint64_t quote = in.eq('"') & ~escaped; ++ ++ // ++ // prefix_xor flips on bits inside the string (and flips off the end quote). ++ // ++ // Then we xor with prev_in_string: if we were in a string already, its effect is flipped ++ // (characters inside strings are outside, and characters outside strings are inside). ++ // ++ const uint64_t in_string = prefix_xor(quote) ^ prev_in_string; ++ ++ // ++ // Check if we're still in a string at the end of the box so the next block will know ++ // ++ // right shift of a signed value expected to be well-defined and standard ++ // compliant as of C++20, John Regher from Utah U. says this is fine code ++ // ++ prev_in_string = uint64_t(static_cast(in_string) >> 63); ++ ++ // Use ^ to turn the beginning quote off, and the end quote on. ++ ++ // We are returning a function-local object so either we get a move constructor ++ // or we get copy elision. ++ return json_string_block( ++ backslash, ++ escaped, ++ quote, ++ in_string ++ ); ++} ++ ++simdjson_inline error_code json_string_scanner::finish() { ++ if (prev_in_string) { ++ return UNCLOSED_STRING; ++ } ++ return SUCCESS; ++} ++ ++} // namespace stage1 ++} // unnamed namespace ++} // namespace arm64 ++} // namespace simdjson ++/* end file src/generic/stage1/json_string_scanner.h */ ++/* begin file src/generic/stage1/json_scanner.h */ ++namespace simdjson { ++namespace arm64 { ++namespace { ++namespace stage1 { ++ ++/** ++ * A block of scanned json, with information on operators and scalars. ++ * ++ * We seek to identify pseudo-structural characters. Anything that is inside ++ * a string must be omitted (hence & ~_string.string_tail()). ++ * Otherwise, pseudo-structural characters come in two forms. ++ * 1. We have the structural characters ([,],{,},:, comma). The ++ * term 'structural character' is from the JSON RFC. ++ * 2. We have the 'scalar pseudo-structural characters'. ++ * Scalars are quotes, and any character except structural characters and white space. ++ * ++ * To identify the scalar pseudo-structural characters, we must look at what comes ++ * before them: it must be a space, a quote or a structural characters. ++ * Starting with simdjson v0.3, we identify them by ++ * negation: we identify everything that is followed by a non-quote scalar, ++ * and we negate that. Whatever remains must be a 'scalar pseudo-structural character'. ++ */ ++struct json_block { ++public: ++ // We spell out the constructors in the hope of resolving inlining issues with Visual Studio 2017 ++ simdjson_inline json_block(json_string_block&& string, json_character_block characters, uint64_t follows_potential_nonquote_scalar) : ++ _string(std::move(string)), _characters(characters), _follows_potential_nonquote_scalar(follows_potential_nonquote_scalar) {} ++ simdjson_inline json_block(json_string_block string, json_character_block characters, uint64_t follows_potential_nonquote_scalar) : ++ _string(string), _characters(characters), _follows_potential_nonquote_scalar(follows_potential_nonquote_scalar) {} ++ ++ /** ++ * The start of structurals. ++ * In simdjson prior to v0.3, these were called the pseudo-structural characters. ++ **/ ++ simdjson_inline uint64_t structural_start() const noexcept { return potential_structural_start() & ~_string.string_tail(); } ++ /** All JSON whitespace (i.e. not in a string) */ ++ simdjson_inline uint64_t whitespace() const noexcept { return non_quote_outside_string(_characters.whitespace()); } ++ ++ // Helpers ++ ++ /** Whether the given characters are inside a string (only works on non-quotes) */ ++ simdjson_inline uint64_t non_quote_inside_string(uint64_t mask) const noexcept { return _string.non_quote_inside_string(mask); } ++ /** Whether the given characters are outside a string (only works on non-quotes) */ ++ simdjson_inline uint64_t non_quote_outside_string(uint64_t mask) const noexcept { return _string.non_quote_outside_string(mask); } ++ ++ // string and escape characters ++ json_string_block _string; ++ // whitespace, structural characters ('operators'), scalars ++ json_character_block _characters; ++ // whether the previous character was a scalar ++ uint64_t _follows_potential_nonquote_scalar; ++private: ++ // Potential structurals (i.e. disregarding strings) ++ ++ /** ++ * structural elements ([,],{,},:, comma) plus scalar starts like 123, true and "abc". ++ * They may reside inside a string. ++ **/ ++ simdjson_inline uint64_t potential_structural_start() const noexcept { return _characters.op() | potential_scalar_start(); } ++ /** ++ * The start of non-operator runs, like 123, true and "abc". ++ * It main reside inside a string. ++ **/ ++ simdjson_inline uint64_t potential_scalar_start() const noexcept { ++ // The term "scalar" refers to anything except structural characters and white space ++ // (so letters, numbers, quotes). ++ // Whenever it is preceded by something that is not a structural element ({,},[,],:, ") nor a white-space ++ // then we know that it is irrelevant structurally. ++ return _characters.scalar() & ~follows_potential_scalar(); ++ } ++ /** ++ * Whether the given character is immediately after a non-operator like 123, true. ++ * The characters following a quote are not included. ++ */ ++ simdjson_inline uint64_t follows_potential_scalar() const noexcept { ++ // _follows_potential_nonquote_scalar: is defined as marking any character that follows a character ++ // that is not a structural element ({,},[,],:, comma) nor a quote (") and that is not a ++ // white space. ++ // It is understood that within quoted region, anything at all could be marked (irrelevant). ++ return _follows_potential_nonquote_scalar; ++ } ++}; ++ ++/** ++ * Scans JSON for important bits: structural characters or 'operators', strings, and scalars. ++ * ++ * The scanner starts by calculating two distinct things: ++ * - string characters (taking \" into account) ++ * - structural characters or 'operators' ([]{},:, comma) ++ * and scalars (runs of non-operators like 123, true and "abc") ++ * ++ * To minimize data dependency (a key component of the scanner's speed), it finds these in parallel: ++ * in particular, the operator/scalar bit will find plenty of things that are actually part of ++ * strings. When we're done, json_block will fuse the two together by masking out tokens that are ++ * part of a string. ++ */ ++class json_scanner { ++public: ++ json_scanner() = default; ++ simdjson_inline json_block next(const simd::simd8x64& in); ++ // Returns either UNCLOSED_STRING or SUCCESS ++ simdjson_inline error_code finish(); ++ ++private: ++ // Whether the last character of the previous iteration is part of a scalar token ++ // (anything except whitespace or a structural character/'operator'). ++ uint64_t prev_scalar = 0ULL; ++ json_string_scanner string_scanner{}; ++}; ++ ++ ++// ++// Check if the current character immediately follows a matching character. ++// ++// For example, this checks for quotes with backslashes in front of them: ++// ++// const uint64_t backslashed_quote = in.eq('"') & immediately_follows(in.eq('\'), prev_backslash); ++// ++simdjson_inline uint64_t follows(const uint64_t match, uint64_t &overflow) { ++ const uint64_t result = match << 1 | overflow; ++ overflow = match >> 63; ++ return result; ++} ++ ++simdjson_inline json_block json_scanner::next(const simd::simd8x64& in) { ++ json_string_block strings = string_scanner.next(in); ++ // identifies the white-space and the structural characters ++ json_character_block characters = json_character_block::classify(in); ++ // The term "scalar" refers to anything except structural characters and white space ++ // (so letters, numbers, quotes). ++ // We want follows_scalar to mark anything that follows a non-quote scalar (so letters and numbers). ++ // ++ // A terminal quote should either be followed by a structural character (comma, brace, bracket, colon) ++ // or nothing. However, we still want ' "a string"true ' to mark the 't' of 'true' as a potential ++ // pseudo-structural character just like we would if we had ' "a string" true '; otherwise we ++ // may need to add an extra check when parsing strings. ++ // ++ // Performance: there are many ways to skin this cat. ++ const uint64_t nonquote_scalar = characters.scalar() & ~strings.quote(); ++ uint64_t follows_nonquote_scalar = follows(nonquote_scalar, prev_scalar); ++ // We are returning a function-local object so either we get a move constructor ++ // or we get copy elision. ++ return json_block( ++ strings,// strings is a function-local object so either it moves or the copy is elided. ++ characters, ++ follows_nonquote_scalar ++ ); ++} ++ ++simdjson_inline error_code json_scanner::finish() { ++ return string_scanner.finish(); ++} ++ ++} // namespace stage1 ++} // unnamed namespace ++} // namespace arm64 ++} // namespace simdjson ++/* end file src/generic/stage1/json_scanner.h */ ++/* begin file src/generic/stage1/json_minifier.h */ ++// This file contains the common code every implementation uses in stage1 ++// It is intended to be included multiple times and compiled multiple times ++// We assume the file in which it is included already includes ++// "simdjson/stage1.h" (this simplifies amalgation) ++ ++namespace simdjson { ++namespace arm64 { ++namespace { ++namespace stage1 { ++ ++class json_minifier { ++public: ++ template ++ static error_code minify(const uint8_t *buf, size_t len, uint8_t *dst, size_t &dst_len) noexcept; ++ ++private: ++ simdjson_inline json_minifier(uint8_t *_dst) ++ : dst{_dst} ++ {} ++ template ++ simdjson_inline void step(const uint8_t *block_buf, buf_block_reader &reader) noexcept; ++ simdjson_inline void next(const simd::simd8x64& in, const json_block& block); ++ simdjson_inline error_code finish(uint8_t *dst_start, size_t &dst_len); ++ json_scanner scanner{}; ++ uint8_t *dst; ++}; ++ ++simdjson_inline void json_minifier::next(const simd::simd8x64& in, const json_block& block) { ++ uint64_t mask = block.whitespace(); ++ dst += in.compress(mask, dst); ++} ++ ++simdjson_inline error_code json_minifier::finish(uint8_t *dst_start, size_t &dst_len) { ++ error_code error = scanner.finish(); ++ if (error) { dst_len = 0; return error; } ++ dst_len = dst - dst_start; ++ return SUCCESS; ++} ++ ++template<> ++simdjson_inline void json_minifier::step<128>(const uint8_t *block_buf, buf_block_reader<128> &reader) noexcept { ++ simd::simd8x64 in_1(block_buf); ++ simd::simd8x64 in_2(block_buf+64); ++ json_block block_1 = scanner.next(in_1); ++ json_block block_2 = scanner.next(in_2); ++ this->next(in_1, block_1); ++ this->next(in_2, block_2); ++ reader.advance(); ++} ++ ++template<> ++simdjson_inline void json_minifier::step<64>(const uint8_t *block_buf, buf_block_reader<64> &reader) noexcept { ++ simd::simd8x64 in_1(block_buf); ++ json_block block_1 = scanner.next(in_1); ++ this->next(block_buf, block_1); ++ reader.advance(); ++} ++ ++template ++error_code json_minifier::minify(const uint8_t *buf, size_t len, uint8_t *dst, size_t &dst_len) noexcept { ++ buf_block_reader reader(buf, len); ++ json_minifier minifier(dst); ++ ++ // Index the first n-1 blocks ++ while (reader.has_full_block()) { ++ minifier.step(reader.full_block(), reader); ++ } ++ ++ // Index the last (remainder) block, padded with spaces ++ uint8_t block[STEP_SIZE]; ++ size_t remaining_bytes = reader.get_remainder(block); ++ if (remaining_bytes > 0) { ++ // We do not want to write directly to the output stream. Rather, we write ++ // to a local buffer (for safety). ++ uint8_t out_block[STEP_SIZE]; ++ uint8_t * const guarded_dst{minifier.dst}; ++ minifier.dst = out_block; ++ minifier.step(block, reader); ++ size_t to_write = minifier.dst - out_block; ++ // In some cases, we could be enticed to consider the padded spaces ++ // as part of the string. This is fine as long as we do not write more ++ // than we consumed. ++ if(to_write > remaining_bytes) { to_write = remaining_bytes; } ++ memcpy(guarded_dst, out_block, to_write); ++ minifier.dst = guarded_dst + to_write; ++ } ++ return minifier.finish(dst, dst_len); ++} ++ ++} // namespace stage1 ++} // unnamed namespace ++} // namespace arm64 ++} // namespace simdjson ++/* end file src/generic/stage1/json_minifier.h */ ++/* begin file src/generic/stage1/find_next_document_index.h */ ++namespace simdjson { ++namespace arm64 { ++namespace { ++ ++/** ++ * This algorithm is used to quickly identify the last structural position that ++ * makes up a complete document. ++ * ++ * It does this by going backwards and finding the last *document boundary* (a ++ * place where one value follows another without a comma between them). If the ++ * last document (the characters after the boundary) has an equal number of ++ * start and end brackets, it is considered complete. ++ * ++ * Simply put, we iterate over the structural characters, starting from ++ * the end. We consider that we found the end of a JSON document when the ++ * first element of the pair is NOT one of these characters: '{' '[' ':' ',' ++ * and when the second element is NOT one of these characters: '}' ']' ':' ','. ++ * ++ * This simple comparison works most of the time, but it does not cover cases ++ * where the batch's structural indexes contain a perfect amount of documents. ++ * In such a case, we do not have access to the structural index which follows ++ * the last document, therefore, we do not have access to the second element in ++ * the pair, and that means we cannot identify the last document. To fix this ++ * issue, we keep a count of the open and closed curly/square braces we found ++ * while searching for the pair. When we find a pair AND the count of open and ++ * closed curly/square braces is the same, we know that we just passed a ++ * complete document, therefore the last json buffer location is the end of the ++ * batch. ++ */ ++simdjson_inline uint32_t find_next_document_index(dom_parser_implementation &parser) { ++ // Variant: do not count separately, just figure out depth ++ if(parser.n_structural_indexes == 0) { return 0; } ++ auto arr_cnt = 0; ++ auto obj_cnt = 0; ++ for (auto i = parser.n_structural_indexes - 1; i > 0; i--) { ++ auto idxb = parser.structural_indexes[i]; ++ switch (parser.buf[idxb]) { ++ case ':': ++ case ',': ++ continue; ++ case '}': ++ obj_cnt--; ++ continue; ++ case ']': ++ arr_cnt--; ++ continue; ++ case '{': ++ obj_cnt++; ++ break; ++ case '[': ++ arr_cnt++; ++ break; ++ } ++ auto idxa = parser.structural_indexes[i - 1]; ++ switch (parser.buf[idxa]) { ++ case '{': ++ case '[': ++ case ':': ++ case ',': ++ continue; ++ } ++ // Last document is complete, so the next document will appear after! ++ if (!arr_cnt && !obj_cnt) { ++ return parser.n_structural_indexes; ++ } ++ // Last document is incomplete; mark the document at i + 1 as the next one ++ return i; ++ } ++ // If we made it to the end, we want to finish counting to see if we have a full document. ++ switch (parser.buf[parser.structural_indexes[0]]) { ++ case '}': ++ obj_cnt--; ++ break; ++ case ']': ++ arr_cnt--; ++ break; ++ case '{': ++ obj_cnt++; ++ break; ++ case '[': ++ arr_cnt++; ++ break; ++ } ++ if (!arr_cnt && !obj_cnt) { ++ // We have a complete document. ++ return parser.n_structural_indexes; ++ } ++ return 0; ++} ++ ++} // unnamed namespace ++} // namespace arm64 ++} // namespace simdjson ++/* end file src/generic/stage1/find_next_document_index.h */ ++ ++namespace simdjson { ++namespace arm64 { ++namespace { ++namespace stage1 { ++ ++class bit_indexer { ++public: ++ uint32_t *tail; ++ ++ simdjson_inline bit_indexer(uint32_t *index_buf) : tail(index_buf) {} ++ ++ // flatten out values in 'bits' assuming that they are are to have values of idx ++ // plus their position in the bitvector, and store these indexes at ++ // base_ptr[base] incrementing base as we go ++ // will potentially store extra values beyond end of valid bits, so base_ptr ++ // needs to be large enough to handle this ++ // ++ // If the kernel sets SIMDJSON_CUSTOM_BIT_INDEXER, then it will provide its own ++ // version of the code. ++#ifdef SIMDJSON_CUSTOM_BIT_INDEXER ++ simdjson_inline void write(uint32_t idx, uint64_t bits); ++#else ++ simdjson_inline void write(uint32_t idx, uint64_t bits) { ++ // In some instances, the next branch is expensive because it is mispredicted. ++ // Unfortunately, in other cases, ++ // it helps tremendously. ++ if (bits == 0) ++ return; ++#if SIMDJSON_PREFER_REVERSE_BITS ++ /** ++ * ARM lacks a fast trailing zero instruction, but it has a fast ++ * bit reversal instruction and a fast leading zero instruction. ++ * Thus it may be profitable to reverse the bits (once) and then ++ * to rely on a sequence of instructions that call the leading ++ * zero instruction. ++ * ++ * Performance notes: ++ * The chosen routine is not optimal in terms of data dependency ++ * since zero_leading_bit might require two instructions. However, ++ * it tends to minimize the total number of instructions which is ++ * beneficial. ++ */ ++ ++ uint64_t rev_bits = reverse_bits(bits); ++ int cnt = static_cast(count_ones(bits)); ++ int i = 0; ++ // Do the first 8 all together ++ for (; i<8; i++) { ++ int lz = leading_zeroes(rev_bits); ++ this->tail[i] = static_cast(idx) + lz; ++ rev_bits = zero_leading_bit(rev_bits, lz); ++ } ++ // Do the next 8 all together (we hope in most cases it won't happen at all ++ // and the branch is easily predicted). ++ if (simdjson_unlikely(cnt > 8)) { ++ i = 8; ++ for (; i<16; i++) { ++ int lz = leading_zeroes(rev_bits); ++ this->tail[i] = static_cast(idx) + lz; ++ rev_bits = zero_leading_bit(rev_bits, lz); ++ } ++ ++ ++ // Most files don't have 16+ structurals per block, so we take several basically guaranteed ++ // branch mispredictions here. 16+ structurals per block means either punctuation ({} [] , :) ++ // or the start of a value ("abc" true 123) every four characters. ++ if (simdjson_unlikely(cnt > 16)) { ++ i = 16; ++ while (rev_bits != 0) { ++ int lz = leading_zeroes(rev_bits); ++ this->tail[i++] = static_cast(idx) + lz; ++ rev_bits = zero_leading_bit(rev_bits, lz); ++ } ++ } ++ } ++ this->tail += cnt; ++#else // SIMDJSON_PREFER_REVERSE_BITS ++ /** ++ * Under recent x64 systems, we often have both a fast trailing zero ++ * instruction and a fast 'clear-lower-bit' instruction so the following ++ * algorithm can be competitive. ++ */ ++ ++ int cnt = static_cast(count_ones(bits)); ++ // Do the first 8 all together ++ for (int i=0; i<8; i++) { ++ this->tail[i] = idx + trailing_zeroes(bits); ++ bits = clear_lowest_bit(bits); ++ } ++ ++ // Do the next 8 all together (we hope in most cases it won't happen at all ++ // and the branch is easily predicted). ++ if (simdjson_unlikely(cnt > 8)) { ++ for (int i=8; i<16; i++) { ++ this->tail[i] = idx + trailing_zeroes(bits); ++ bits = clear_lowest_bit(bits); ++ } ++ ++ // Most files don't have 16+ structurals per block, so we take several basically guaranteed ++ // branch mispredictions here. 16+ structurals per block means either punctuation ({} [] , :) ++ // or the start of a value ("abc" true 123) every four characters. ++ if (simdjson_unlikely(cnt > 16)) { ++ int i = 16; ++ do { ++ this->tail[i] = idx + trailing_zeroes(bits); ++ bits = clear_lowest_bit(bits); ++ i++; ++ } while (i < cnt); ++ } ++ } ++ ++ this->tail += cnt; ++#endif ++ } ++#endif // SIMDJSON_CUSTOM_BIT_INDEXER ++ ++}; ++ ++class json_structural_indexer { ++public: ++ /** ++ * Find the important bits of JSON in a 128-byte chunk, and add them to structural_indexes. ++ * ++ * @param partial Setting the partial parameter to true allows the find_structural_bits to ++ * tolerate unclosed strings. The caller should still ensure that the input is valid UTF-8. If ++ * you are processing substrings, you may want to call on a function like trimmed_length_safe_utf8. ++ */ ++ template ++ static error_code index(const uint8_t *buf, size_t len, dom_parser_implementation &parser, stage1_mode partial) noexcept; ++ ++private: ++ simdjson_inline json_structural_indexer(uint32_t *structural_indexes); ++ template ++ simdjson_inline void step(const uint8_t *block, buf_block_reader &reader) noexcept; ++ simdjson_inline void next(const simd::simd8x64& in, const json_block& block, size_t idx); ++ simdjson_inline error_code finish(dom_parser_implementation &parser, size_t idx, size_t len, stage1_mode partial); ++ ++ json_scanner scanner{}; ++ utf8_checker checker{}; ++ bit_indexer indexer; ++ uint64_t prev_structurals = 0; ++ uint64_t unescaped_chars_error = 0; ++}; ++ ++simdjson_inline json_structural_indexer::json_structural_indexer(uint32_t *structural_indexes) : indexer{structural_indexes} {} ++ ++// Skip the last character if it is partial ++simdjson_inline size_t trim_partial_utf8(const uint8_t *buf, size_t len) { ++ if (simdjson_unlikely(len < 3)) { ++ switch (len) { ++ case 2: ++ if (buf[len-1] >= 0xc0) { return len-1; } // 2-, 3- and 4-byte characters with only 1 byte left ++ if (buf[len-2] >= 0xe0) { return len-2; } // 3- and 4-byte characters with only 2 bytes left ++ return len; ++ case 1: ++ if (buf[len-1] >= 0xc0) { return len-1; } // 2-, 3- and 4-byte characters with only 1 byte left ++ return len; ++ case 0: ++ return len; ++ } ++ } ++ if (buf[len-1] >= 0xc0) { return len-1; } // 2-, 3- and 4-byte characters with only 1 byte left ++ if (buf[len-2] >= 0xe0) { return len-2; } // 3- and 4-byte characters with only 1 byte left ++ if (buf[len-3] >= 0xf0) { return len-3; } // 4-byte characters with only 3 bytes left ++ return len; ++} ++ ++// ++// PERF NOTES: ++// We pipe 2 inputs through these stages: ++// 1. Load JSON into registers. This takes a long time and is highly parallelizable, so we load ++// 2 inputs' worth at once so that by the time step 2 is looking for them input, it's available. ++// 2. Scan the JSON for critical data: strings, scalars and operators. This is the critical path. ++// The output of step 1 depends entirely on this information. These functions don't quite use ++// up enough CPU: the second half of the functions is highly serial, only using 1 execution core ++// at a time. The second input's scans has some dependency on the first ones finishing it, but ++// they can make a lot of progress before they need that information. ++// 3. Step 1 doesn't use enough capacity, so we run some extra stuff while we're waiting for that ++// to finish: utf-8 checks and generating the output from the last iteration. ++// ++// The reason we run 2 inputs at a time, is steps 2 and 3 are *still* not enough to soak up all ++// available capacity with just one input. Running 2 at a time seems to give the CPU a good enough ++// workout. ++// ++template ++error_code json_structural_indexer::index(const uint8_t *buf, size_t len, dom_parser_implementation &parser, stage1_mode partial) noexcept { ++ if (simdjson_unlikely(len > parser.capacity())) { return CAPACITY; } ++ // We guard the rest of the code so that we can assume that len > 0 throughout. ++ if (len == 0) { return EMPTY; } ++ if (is_streaming(partial)) { ++ len = trim_partial_utf8(buf, len); ++ // If you end up with an empty window after trimming ++ // the partial UTF-8 bytes, then chances are good that you ++ // have an UTF-8 formatting error. ++ if(len == 0) { return UTF8_ERROR; } ++ } ++ buf_block_reader reader(buf, len); ++ json_structural_indexer indexer(parser.structural_indexes.get()); ++ ++ // Read all but the last block ++ while (reader.has_full_block()) { ++ indexer.step(reader.full_block(), reader); ++ } ++ // Take care of the last block (will always be there unless file is empty which is ++ // not supposed to happen.) ++ uint8_t block[STEP_SIZE]; ++ if (simdjson_unlikely(reader.get_remainder(block) == 0)) { return UNEXPECTED_ERROR; } ++ indexer.step(block, reader); ++ return indexer.finish(parser, reader.block_index(), len, partial); ++} ++ ++template<> ++simdjson_inline void json_structural_indexer::step<128>(const uint8_t *block, buf_block_reader<128> &reader) noexcept { ++ simd::simd8x64 in_1(block); ++ simd::simd8x64 in_2(block+64); ++ json_block block_1 = scanner.next(in_1); ++ json_block block_2 = scanner.next(in_2); ++ this->next(in_1, block_1, reader.block_index()); ++ this->next(in_2, block_2, reader.block_index()+64); ++ reader.advance(); ++} ++ ++template<> ++simdjson_inline void json_structural_indexer::step<64>(const uint8_t *block, buf_block_reader<64> &reader) noexcept { ++ simd::simd8x64 in_1(block); ++ json_block block_1 = scanner.next(in_1); ++ this->next(in_1, block_1, reader.block_index()); ++ reader.advance(); ++} ++ ++simdjson_inline void json_structural_indexer::next(const simd::simd8x64& in, const json_block& block, size_t idx) { ++ uint64_t unescaped = in.lteq(0x1F); ++ checker.check_next_input(in); ++ indexer.write(uint32_t(idx-64), prev_structurals); // Output *last* iteration's structurals to the parser ++ prev_structurals = block.structural_start(); ++ unescaped_chars_error |= block.non_quote_inside_string(unescaped); ++} ++ ++simdjson_inline error_code json_structural_indexer::finish(dom_parser_implementation &parser, size_t idx, size_t len, stage1_mode partial) { ++ // Write out the final iteration's structurals ++ indexer.write(uint32_t(idx-64), prev_structurals); ++ error_code error = scanner.finish(); ++ // We deliberately break down the next expression so that it is ++ // human readable. ++ const bool should_we_exit = is_streaming(partial) ? ++ ((error != SUCCESS) && (error != UNCLOSED_STRING)) // when partial we tolerate UNCLOSED_STRING ++ : (error != SUCCESS); // if partial is false, we must have SUCCESS ++ const bool have_unclosed_string = (error == UNCLOSED_STRING); ++ if (simdjson_unlikely(should_we_exit)) { return error; } ++ ++ if (unescaped_chars_error) { ++ return UNESCAPED_CHARS; ++ } ++ parser.n_structural_indexes = uint32_t(indexer.tail - parser.structural_indexes.get()); ++ /*** ++ * The On Demand API requires special padding. ++ * ++ * This is related to https://github.com/simdjson/simdjson/issues/906 ++ * Basically, we want to make sure that if the parsing continues beyond the last (valid) ++ * structural character, it quickly stops. ++ * Only three structural characters can be repeated without triggering an error in JSON: [,] and }. ++ * We repeat the padding character (at 'len'). We don't know what it is, but if the parsing ++ * continues, then it must be [,] or }. ++ * Suppose it is ] or }. We backtrack to the first character, what could it be that would ++ * not trigger an error? It could be ] or } but no, because you can't start a document that way. ++ * It can't be a comma, a colon or any simple value. So the only way we could continue is ++ * if the repeated character is [. But if so, the document must start with [. But if the document ++ * starts with [, it should end with ]. If we enforce that rule, then we would get ++ * ][[ which is invalid. ++ * ++ * This is illustrated with the test array_iterate_unclosed_error() on the following input: ++ * R"({ "a": [,,)" ++ **/ ++ parser.structural_indexes[parser.n_structural_indexes] = uint32_t(len); // used later in partial == stage1_mode::streaming_final ++ parser.structural_indexes[parser.n_structural_indexes + 1] = uint32_t(len); ++ parser.structural_indexes[parser.n_structural_indexes + 2] = 0; ++ parser.next_structural_index = 0; ++ // a valid JSON file cannot have zero structural indexes - we should have found something ++ if (simdjson_unlikely(parser.n_structural_indexes == 0u)) { ++ return EMPTY; ++ } ++ if (simdjson_unlikely(parser.structural_indexes[parser.n_structural_indexes - 1] > len)) { ++ return UNEXPECTED_ERROR; ++ } ++ if (partial == stage1_mode::streaming_partial) { ++ // If we have an unclosed string, then the last structural ++ // will be the quote and we want to make sure to omit it. ++ if(have_unclosed_string) { ++ parser.n_structural_indexes--; ++ // a valid JSON file cannot have zero structural indexes - we should have found something ++ if (simdjson_unlikely(parser.n_structural_indexes == 0u)) { return CAPACITY; } ++ } ++ // We truncate the input to the end of the last complete document (or zero). ++ auto new_structural_indexes = find_next_document_index(parser); ++ if (new_structural_indexes == 0 && parser.n_structural_indexes > 0) { ++ if(parser.structural_indexes[0] == 0) { ++ // If the buffer is partial and we started at index 0 but the document is ++ // incomplete, it's too big to parse. ++ return CAPACITY; ++ } else { ++ // It is possible that the document could be parsed, we just had a lot ++ // of white space. ++ parser.n_structural_indexes = 0; ++ return EMPTY; ++ } ++ } ++ ++ parser.n_structural_indexes = new_structural_indexes; ++ } else if (partial == stage1_mode::streaming_final) { ++ if(have_unclosed_string) { parser.n_structural_indexes--; } ++ // We truncate the input to the end of the last complete document (or zero). ++ // Because partial == stage1_mode::streaming_final, it means that we may ++ // silently ignore trailing garbage. Though it sounds bad, we do it ++ // deliberately because many people who have streams of JSON documents ++ // will truncate them for processing. E.g., imagine that you are uncompressing ++ // the data from a size file or receiving it in chunks from the network. You ++ // may not know where exactly the last document will be. Meanwhile the ++ // document_stream instances allow people to know the JSON documents they are ++ // parsing (see the iterator.source() method). ++ parser.n_structural_indexes = find_next_document_index(parser); ++ // We store the initial n_structural_indexes so that the client can see ++ // whether we used truncation. If initial_n_structural_indexes == parser.n_structural_indexes, ++ // then this will query parser.structural_indexes[parser.n_structural_indexes] which is len, ++ // otherwise, it will copy some prior index. ++ parser.structural_indexes[parser.n_structural_indexes + 1] = parser.structural_indexes[parser.n_structural_indexes]; ++ // This next line is critical, do not change it unless you understand what you are ++ // doing. ++ parser.structural_indexes[parser.n_structural_indexes] = uint32_t(len); ++ if (simdjson_unlikely(parser.n_structural_indexes == 0u)) { ++ // We tolerate an unclosed string at the very end of the stream. Indeed, users ++ // often load their data in bulk without being careful and they want us to ignore ++ // the trailing garbage. ++ return EMPTY; ++ } ++ } ++ checker.check_eof(); ++ return checker.errors(); ++} ++ ++} // namespace stage1 ++} // unnamed namespace ++} // namespace arm64 ++} // namespace simdjson ++/* end file src/generic/stage1/json_structural_indexer.h */ ++/* begin file src/generic/stage1/utf8_validator.h */ ++namespace simdjson { ++namespace arm64 { ++namespace { ++namespace stage1 { ++ ++/** ++ * Validates that the string is actual UTF-8. ++ */ ++template ++bool generic_validate_utf8(const uint8_t * input, size_t length) { ++ checker c{}; ++ buf_block_reader<64> reader(input, length); ++ while (reader.has_full_block()) { ++ simd::simd8x64 in(reader.full_block()); ++ c.check_next_input(in); ++ reader.advance(); ++ } ++ uint8_t block[64]{}; ++ reader.get_remainder(block); ++ simd::simd8x64 in(block); ++ c.check_next_input(in); ++ reader.advance(); ++ c.check_eof(); ++ return c.errors() == error_code::SUCCESS; ++} ++ ++bool generic_validate_utf8(const char * input, size_t length) { ++ return generic_validate_utf8(reinterpret_cast(input),length); ++} ++ ++} // namespace stage1 ++} // unnamed namespace ++} // namespace arm64 ++} // namespace simdjson ++/* end file src/generic/stage1/utf8_validator.h */ ++ ++// ++// Stage 2 ++// ++ ++/* begin file src/generic/stage2/stringparsing.h */ ++// This file contains the common code every implementation uses ++// It is intended to be included multiple times and compiled multiple times ++ ++namespace simdjson { ++namespace arm64 { ++namespace { ++/// @private ++namespace stringparsing { ++ ++// begin copypasta ++// These chars yield themselves: " \ / ++// b -> backspace, f -> formfeed, n -> newline, r -> cr, t -> horizontal tab ++// u not handled in this table as it's complex ++static const uint8_t escape_map[256] = { ++ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x0. ++ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ++ 0, 0, 0x22, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x2f, ++ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ++ ++ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x4. ++ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x5c, 0, 0, 0, // 0x5. ++ 0, 0, 0x08, 0, 0, 0, 0x0c, 0, 0, 0, 0, 0, 0, 0, 0x0a, 0, // 0x6. ++ 0, 0, 0x0d, 0, 0x09, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x7. ++ ++ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ++ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ++ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ++ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ++ ++ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ++ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ++ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ++ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ++}; ++ ++// handle a unicode codepoint ++// write appropriate values into dest ++// src will advance 6 bytes or 12 bytes ++// dest will advance a variable amount (return via pointer) ++// return true if the unicode codepoint was valid ++// We work in little-endian then swap at write time ++simdjson_warn_unused ++simdjson_inline bool handle_unicode_codepoint(const uint8_t **src_ptr, ++ uint8_t **dst_ptr) { ++ // jsoncharutils::hex_to_u32_nocheck fills high 16 bits of the return value with 1s if the ++ // conversion isn't valid; we defer the check for this to inside the ++ // multilingual plane check ++ uint32_t code_point = jsoncharutils::hex_to_u32_nocheck(*src_ptr + 2); ++ *src_ptr += 6; ++ ++ // If we found a high surrogate, we must ++ // check for low surrogate for characters ++ // outside the Basic ++ // Multilingual Plane. ++ if (code_point >= 0xd800 && code_point < 0xdc00) { ++ const uint8_t *src_data = *src_ptr; ++ /* Compiler optimizations convert this to a single 16-bit load and compare on most platforms */ ++ if (((src_data[0] << 8) | src_data[1]) != ((static_cast ('\\') << 8) | static_cast ('u'))) { ++ return false; ++ } ++ uint32_t code_point_2 = jsoncharutils::hex_to_u32_nocheck(src_data + 2); ++ ++ // We have already checked that the high surrogate is valid and ++ // (code_point - 0xd800) < 1024. ++ // ++ // Check that code_point_2 is in the range 0xdc00..0xdfff ++ // and that code_point_2 was parsed from valid hex. ++ uint32_t low_bit = code_point_2 - 0xdc00; ++ if (low_bit >> 10) { ++ return false; ++ } ++ ++ code_point = ++ (((code_point - 0xd800) << 10) | low_bit) + 0x10000; ++ *src_ptr += 6; ++ } else if (code_point >= 0xdc00 && code_point <= 0xdfff) { ++ // If we encounter a low surrogate (not preceded by a high surrogate) ++ // then we have an error. ++ return false; ++ } ++ size_t offset = jsoncharutils::codepoint_to_utf8(code_point, *dst_ptr); ++ *dst_ptr += offset; ++ return offset > 0; ++} ++ ++/** ++ * Unescape a valid UTF-8 string from src to dst, stopping at a final unescaped quote. There ++ * must be an unescaped quote terminating the string. It returns the final output ++ * position as pointer. In case of error (e.g., the string has bad escaped codes), ++ * then null_nullptrptr is returned. It is assumed that the output buffer is large ++ * enough. E.g., if src points at 'joe"', then dst needs to have four free bytes + ++ * SIMDJSON_PADDING bytes. ++ */ ++simdjson_warn_unused simdjson_inline uint8_t *parse_string(const uint8_t *src, uint8_t *dst) { ++ while (1) { ++ // Copy the next n bytes, and find the backslash and quote in them. ++ auto bs_quote = backslash_and_quote::copy_and_find(src, dst); ++ // If the next thing is the end quote, copy and return ++ if (bs_quote.has_quote_first()) { ++ // we encountered quotes first. Move dst to point to quotes and exit ++ return dst + bs_quote.quote_index(); ++ } ++ if (bs_quote.has_backslash()) { ++ /* find out where the backspace is */ ++ auto bs_dist = bs_quote.backslash_index(); ++ uint8_t escape_char = src[bs_dist + 1]; ++ /* we encountered backslash first. Handle backslash */ ++ if (escape_char == 'u') { ++ /* move src/dst up to the start; they will be further adjusted ++ within the unicode codepoint handling code. */ ++ src += bs_dist; ++ dst += bs_dist; ++ if (!handle_unicode_codepoint(&src, &dst)) { ++ return nullptr; ++ } ++ } else { ++ /* simple 1:1 conversion. Will eat bs_dist+2 characters in input and ++ * write bs_dist+1 characters to output ++ * note this may reach beyond the part of the buffer we've actually ++ * seen. I think this is ok */ ++ uint8_t escape_result = escape_map[escape_char]; ++ if (escape_result == 0u) { ++ return nullptr; /* bogus escape value is an error */ ++ } ++ dst[bs_dist] = escape_result; ++ src += bs_dist + 2; ++ dst += bs_dist + 1; ++ } ++ } else { ++ /* they are the same. Since they can't co-occur, it means we ++ * encountered neither. */ ++ src += backslash_and_quote::BYTES_PROCESSED; ++ dst += backslash_and_quote::BYTES_PROCESSED; ++ } ++ } ++ /* can't be reached */ ++ return nullptr; ++} ++ ++} // namespace stringparsing ++} // unnamed namespace ++} // namespace arm64 ++} // namespace simdjson ++/* end file src/generic/stage2/stringparsing.h */ ++/* begin file src/generic/stage2/tape_builder.h */ ++/* begin file src/generic/stage2/json_iterator.h */ ++/* begin file src/generic/stage2/logger.h */ ++// This is for an internal-only stage 2 specific logger. ++// Set LOG_ENABLED = true to log what stage 2 is doing! ++namespace simdjson { ++namespace arm64 { ++namespace { ++namespace logger { ++ ++ static constexpr const char * DASHES = "----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------"; ++ ++#if SIMDJSON_VERBOSE_LOGGING ++ static constexpr const bool LOG_ENABLED = true; ++#else ++ static constexpr const bool LOG_ENABLED = false; ++#endif ++ static constexpr const int LOG_EVENT_LEN = 20; ++ static constexpr const int LOG_BUFFER_LEN = 30; ++ static constexpr const int LOG_SMALL_BUFFER_LEN = 10; ++ static constexpr const int LOG_INDEX_LEN = 5; ++ ++ static int log_depth; // Not threadsafe. Log only. ++ ++ // Helper to turn unprintable or newline characters into spaces ++ static simdjson_inline char printable_char(char c) { ++ if (c >= 0x20) { ++ return c; ++ } else { ++ return ' '; ++ } ++ } ++ ++ // Print the header and set up log_start ++ static simdjson_inline void log_start() { ++ if (LOG_ENABLED) { ++ log_depth = 0; ++ printf("\n"); ++ printf("| %-*s | %-*s | %-*s | %-*s | Detail |\n", LOG_EVENT_LEN, "Event", LOG_BUFFER_LEN, "Buffer", LOG_SMALL_BUFFER_LEN, "Next", 5, "Next#"); ++ printf("|%.*s|%.*s|%.*s|%.*s|--------|\n", LOG_EVENT_LEN+2, DASHES, LOG_BUFFER_LEN+2, DASHES, LOG_SMALL_BUFFER_LEN+2, DASHES, 5+2, DASHES); ++ } ++ } ++ ++ simdjson_unused static simdjson_inline void log_string(const char *message) { ++ if (LOG_ENABLED) { ++ printf("%s\n", message); ++ } ++ } ++ ++ // Logs a single line from the stage 2 DOM parser ++ template ++ static simdjson_inline void log_line(S &structurals, const char *title_prefix, const char *title, const char *detail) { ++ if (LOG_ENABLED) { ++ printf("| %*s%s%-*s ", log_depth*2, "", title_prefix, LOG_EVENT_LEN - log_depth*2 - int(strlen(title_prefix)), title); ++ auto current_index = structurals.at_beginning() ? nullptr : structurals.next_structural-1; ++ auto next_index = structurals.next_structural; ++ auto current = current_index ? &structurals.buf[*current_index] : reinterpret_cast(" "); ++ auto next = &structurals.buf[*next_index]; ++ { ++ // Print the next N characters in the buffer. ++ printf("| "); ++ // Otherwise, print the characters starting from the buffer position. ++ // Print spaces for unprintable or newline characters. ++ for (int i=0;i ++ simdjson_warn_unused simdjson_inline error_code walk_document(V &visitor) noexcept; ++ ++ /** ++ * Create an iterator capable of walking a JSON document. ++ * ++ * The document must have already passed through stage 1. ++ */ ++ simdjson_inline json_iterator(dom_parser_implementation &_dom_parser, size_t start_structural_index); ++ ++ /** ++ * Look at the next token. ++ * ++ * Tokens can be strings, numbers, booleans, null, or operators (`[{]},:`)). ++ * ++ * They may include invalid JSON as well (such as `1.2.3` or `ture`). ++ */ ++ simdjson_inline const uint8_t *peek() const noexcept; ++ /** ++ * Advance to the next token. ++ * ++ * Tokens can be strings, numbers, booleans, null, or operators (`[{]},:`)). ++ * ++ * They may include invalid JSON as well (such as `1.2.3` or `ture`). ++ */ ++ simdjson_inline const uint8_t *advance() noexcept; ++ /** ++ * Get the remaining length of the document, from the start of the current token. ++ */ ++ simdjson_inline size_t remaining_len() const noexcept; ++ /** ++ * Check if we are at the end of the document. ++ * ++ * If this is true, there are no more tokens. ++ */ ++ simdjson_inline bool at_eof() const noexcept; ++ /** ++ * Check if we are at the beginning of the document. ++ */ ++ simdjson_inline bool at_beginning() const noexcept; ++ simdjson_inline uint8_t last_structural() const noexcept; ++ ++ /** ++ * Log that a value has been found. ++ * ++ * Set LOG_ENABLED=true in logger.h to see logging. ++ */ ++ simdjson_inline void log_value(const char *type) const noexcept; ++ /** ++ * Log the start of a multipart value. ++ * ++ * Set LOG_ENABLED=true in logger.h to see logging. ++ */ ++ simdjson_inline void log_start_value(const char *type) const noexcept; ++ /** ++ * Log the end of a multipart value. ++ * ++ * Set LOG_ENABLED=true in logger.h to see logging. ++ */ ++ simdjson_inline void log_end_value(const char *type) const noexcept; ++ /** ++ * Log an error. ++ * ++ * Set LOG_ENABLED=true in logger.h to see logging. ++ */ ++ simdjson_inline void log_error(const char *error) const noexcept; ++ ++ template ++ simdjson_warn_unused simdjson_inline error_code visit_root_primitive(V &visitor, const uint8_t *value) noexcept; ++ template ++ simdjson_warn_unused simdjson_inline error_code visit_primitive(V &visitor, const uint8_t *value) noexcept; ++}; ++ ++template ++simdjson_warn_unused simdjson_inline error_code json_iterator::walk_document(V &visitor) noexcept { ++ logger::log_start(); ++ ++ // ++ // Start the document ++ // ++ if (at_eof()) { return EMPTY; } ++ log_start_value("document"); ++ SIMDJSON_TRY( visitor.visit_document_start(*this) ); ++ ++ // ++ // Read first value ++ // ++ { ++ auto value = advance(); ++ ++ // Make sure the outer object or array is closed before continuing; otherwise, there are ways we ++ // could get into memory corruption. See https://github.com/simdjson/simdjson/issues/906 ++ if (!STREAMING) { ++ switch (*value) { ++ case '{': if (last_structural() != '}') { log_value("starting brace unmatched"); return TAPE_ERROR; }; break; ++ case '[': if (last_structural() != ']') { log_value("starting bracket unmatched"); return TAPE_ERROR; }; break; ++ } ++ } ++ ++ switch (*value) { ++ case '{': if (*peek() == '}') { advance(); log_value("empty object"); SIMDJSON_TRY( visitor.visit_empty_object(*this) ); break; } goto object_begin; ++ case '[': if (*peek() == ']') { advance(); log_value("empty array"); SIMDJSON_TRY( visitor.visit_empty_array(*this) ); break; } goto array_begin; ++ default: SIMDJSON_TRY( visitor.visit_root_primitive(*this, value) ); break; ++ } ++ } ++ goto document_end; ++ ++// ++// Object parser states ++// ++object_begin: ++ log_start_value("object"); ++ depth++; ++ if (depth >= dom_parser.max_depth()) { log_error("Exceeded max depth!"); return DEPTH_ERROR; } ++ dom_parser.is_array[depth] = false; ++ SIMDJSON_TRY( visitor.visit_object_start(*this) ); ++ ++ { ++ auto key = advance(); ++ if (*key != '"') { log_error("Object does not start with a key"); return TAPE_ERROR; } ++ SIMDJSON_TRY( visitor.increment_count(*this) ); ++ SIMDJSON_TRY( visitor.visit_key(*this, key) ); ++ } ++ ++object_field: ++ if (simdjson_unlikely( *advance() != ':' )) { log_error("Missing colon after key in object"); return TAPE_ERROR; } ++ { ++ auto value = advance(); ++ switch (*value) { ++ case '{': if (*peek() == '}') { advance(); log_value("empty object"); SIMDJSON_TRY( visitor.visit_empty_object(*this) ); break; } goto object_begin; ++ case '[': if (*peek() == ']') { advance(); log_value("empty array"); SIMDJSON_TRY( visitor.visit_empty_array(*this) ); break; } goto array_begin; ++ default: SIMDJSON_TRY( visitor.visit_primitive(*this, value) ); break; ++ } ++ } ++ ++object_continue: ++ switch (*advance()) { ++ case ',': ++ SIMDJSON_TRY( visitor.increment_count(*this) ); ++ { ++ auto key = advance(); ++ if (simdjson_unlikely( *key != '"' )) { log_error("Key string missing at beginning of field in object"); return TAPE_ERROR; } ++ SIMDJSON_TRY( visitor.visit_key(*this, key) ); ++ } ++ goto object_field; ++ case '}': log_end_value("object"); SIMDJSON_TRY( visitor.visit_object_end(*this) ); goto scope_end; ++ default: log_error("No comma between object fields"); return TAPE_ERROR; ++ } ++ ++scope_end: ++ depth--; ++ if (depth == 0) { goto document_end; } ++ if (dom_parser.is_array[depth]) { goto array_continue; } ++ goto object_continue; ++ ++// ++// Array parser states ++// ++array_begin: ++ log_start_value("array"); ++ depth++; ++ if (depth >= dom_parser.max_depth()) { log_error("Exceeded max depth!"); return DEPTH_ERROR; } ++ dom_parser.is_array[depth] = true; ++ SIMDJSON_TRY( visitor.visit_array_start(*this) ); ++ SIMDJSON_TRY( visitor.increment_count(*this) ); ++ ++array_value: ++ { ++ auto value = advance(); ++ switch (*value) { ++ case '{': if (*peek() == '}') { advance(); log_value("empty object"); SIMDJSON_TRY( visitor.visit_empty_object(*this) ); break; } goto object_begin; ++ case '[': if (*peek() == ']') { advance(); log_value("empty array"); SIMDJSON_TRY( visitor.visit_empty_array(*this) ); break; } goto array_begin; ++ default: SIMDJSON_TRY( visitor.visit_primitive(*this, value) ); break; ++ } ++ } ++ ++array_continue: ++ switch (*advance()) { ++ case ',': SIMDJSON_TRY( visitor.increment_count(*this) ); goto array_value; ++ case ']': log_end_value("array"); SIMDJSON_TRY( visitor.visit_array_end(*this) ); goto scope_end; ++ default: log_error("Missing comma between array values"); return TAPE_ERROR; ++ } ++ ++document_end: ++ log_end_value("document"); ++ SIMDJSON_TRY( visitor.visit_document_end(*this) ); ++ ++ dom_parser.next_structural_index = uint32_t(next_structural - &dom_parser.structural_indexes[0]); ++ ++ // If we didn't make it to the end, it's an error ++ if ( !STREAMING && dom_parser.next_structural_index != dom_parser.n_structural_indexes ) { ++ log_error("More than one JSON value at the root of the document, or extra characters at the end of the JSON!"); ++ return TAPE_ERROR; ++ } ++ ++ return SUCCESS; ++ ++} // walk_document() ++ ++simdjson_inline json_iterator::json_iterator(dom_parser_implementation &_dom_parser, size_t start_structural_index) ++ : buf{_dom_parser.buf}, ++ next_structural{&_dom_parser.structural_indexes[start_structural_index]}, ++ dom_parser{_dom_parser} { ++} ++ ++simdjson_inline const uint8_t *json_iterator::peek() const noexcept { ++ return &buf[*(next_structural)]; ++} ++simdjson_inline const uint8_t *json_iterator::advance() noexcept { ++ return &buf[*(next_structural++)]; ++} ++simdjson_inline size_t json_iterator::remaining_len() const noexcept { ++ return dom_parser.len - *(next_structural-1); ++} ++ ++simdjson_inline bool json_iterator::at_eof() const noexcept { ++ return next_structural == &dom_parser.structural_indexes[dom_parser.n_structural_indexes]; ++} ++simdjson_inline bool json_iterator::at_beginning() const noexcept { ++ return next_structural == dom_parser.structural_indexes.get(); ++} ++simdjson_inline uint8_t json_iterator::last_structural() const noexcept { ++ return buf[dom_parser.structural_indexes[dom_parser.n_structural_indexes - 1]]; ++} ++ ++simdjson_inline void json_iterator::log_value(const char *type) const noexcept { ++ logger::log_line(*this, "", type, ""); ++} ++ ++simdjson_inline void json_iterator::log_start_value(const char *type) const noexcept { ++ logger::log_line(*this, "+", type, ""); ++ if (logger::LOG_ENABLED) { logger::log_depth++; } ++} ++ ++simdjson_inline void json_iterator::log_end_value(const char *type) const noexcept { ++ if (logger::LOG_ENABLED) { logger::log_depth--; } ++ logger::log_line(*this, "-", type, ""); ++} ++ ++simdjson_inline void json_iterator::log_error(const char *error) const noexcept { ++ logger::log_line(*this, "", "ERROR", error); ++} ++ ++template ++simdjson_warn_unused simdjson_inline error_code json_iterator::visit_root_primitive(V &visitor, const uint8_t *value) noexcept { ++ switch (*value) { ++ case '"': return visitor.visit_root_string(*this, value); ++ case 't': return visitor.visit_root_true_atom(*this, value); ++ case 'f': return visitor.visit_root_false_atom(*this, value); ++ case 'n': return visitor.visit_root_null_atom(*this, value); ++ case '-': ++ case '0': case '1': case '2': case '3': case '4': ++ case '5': case '6': case '7': case '8': case '9': ++ return visitor.visit_root_number(*this, value); ++ default: ++ log_error("Document starts with a non-value character"); ++ return TAPE_ERROR; ++ } ++} ++template ++simdjson_warn_unused simdjson_inline error_code json_iterator::visit_primitive(V &visitor, const uint8_t *value) noexcept { ++ switch (*value) { ++ case '"': return visitor.visit_string(*this, value); ++ case 't': return visitor.visit_true_atom(*this, value); ++ case 'f': return visitor.visit_false_atom(*this, value); ++ case 'n': return visitor.visit_null_atom(*this, value); ++ case '-': ++ case '0': case '1': case '2': case '3': case '4': ++ case '5': case '6': case '7': case '8': case '9': ++ return visitor.visit_number(*this, value); ++ default: ++ log_error("Non-value found when value was expected!"); ++ return TAPE_ERROR; ++ } ++} ++ ++} // namespace stage2 ++} // unnamed namespace ++} // namespace arm64 ++} // namespace simdjson ++/* end file src/generic/stage2/json_iterator.h */ ++/* begin file src/generic/stage2/tape_writer.h */ ++namespace simdjson { ++namespace arm64 { ++namespace { ++namespace stage2 { ++ ++struct tape_writer { ++ /** The next place to write to tape */ ++ uint64_t *next_tape_loc; ++ ++ /** Write a signed 64-bit value to tape. */ ++ simdjson_inline void append_s64(int64_t value) noexcept; ++ ++ /** Write an unsigned 64-bit value to tape. */ ++ simdjson_inline void append_u64(uint64_t value) noexcept; ++ ++ /** Write a double value to tape. */ ++ simdjson_inline void append_double(double value) noexcept; ++ ++ /** ++ * Append a tape entry (an 8-bit type,and 56 bits worth of value). ++ */ ++ simdjson_inline void append(uint64_t val, internal::tape_type t) noexcept; ++ ++ /** ++ * Skip the current tape entry without writing. ++ * ++ * Used to skip the start of the container, since we'll come back later to fill it in when the ++ * container ends. ++ */ ++ simdjson_inline void skip() noexcept; ++ ++ /** ++ * Skip the number of tape entries necessary to write a large u64 or i64. ++ */ ++ simdjson_inline void skip_large_integer() noexcept; ++ ++ /** ++ * Skip the number of tape entries necessary to write a double. ++ */ ++ simdjson_inline void skip_double() noexcept; ++ ++ /** ++ * Write a value to a known location on tape. ++ * ++ * Used to go back and write out the start of a container after the container ends. ++ */ ++ simdjson_inline static void write(uint64_t &tape_loc, uint64_t val, internal::tape_type t) noexcept; ++ ++private: ++ /** ++ * Append both the tape entry, and a supplementary value following it. Used for types that need ++ * all 64 bits, such as double and uint64_t. ++ */ ++ template ++ simdjson_inline void append2(uint64_t val, T val2, internal::tape_type t) noexcept; ++}; // struct number_writer ++ ++simdjson_inline void tape_writer::append_s64(int64_t value) noexcept { ++ append2(0, value, internal::tape_type::INT64); ++} ++ ++simdjson_inline void tape_writer::append_u64(uint64_t value) noexcept { ++ append(0, internal::tape_type::UINT64); ++ *next_tape_loc = value; ++ next_tape_loc++; ++} ++ ++/** Write a double value to tape. */ ++simdjson_inline void tape_writer::append_double(double value) noexcept { ++ append2(0, value, internal::tape_type::DOUBLE); ++} ++ ++simdjson_inline void tape_writer::skip() noexcept { ++ next_tape_loc++; ++} ++ ++simdjson_inline void tape_writer::skip_large_integer() noexcept { ++ next_tape_loc += 2; ++} ++ ++simdjson_inline void tape_writer::skip_double() noexcept { ++ next_tape_loc += 2; ++} ++ ++simdjson_inline void tape_writer::append(uint64_t val, internal::tape_type t) noexcept { ++ *next_tape_loc = val | ((uint64_t(char(t))) << 56); ++ next_tape_loc++; ++} ++ ++template ++simdjson_inline void tape_writer::append2(uint64_t val, T val2, internal::tape_type t) noexcept { ++ append(val, t); ++ static_assert(sizeof(val2) == sizeof(*next_tape_loc), "Type is not 64 bits!"); ++ memcpy(next_tape_loc, &val2, sizeof(val2)); ++ next_tape_loc++; ++} ++ ++simdjson_inline void tape_writer::write(uint64_t &tape_loc, uint64_t val, internal::tape_type t) noexcept { ++ tape_loc = val | ((uint64_t(char(t))) << 56); ++} ++ ++} // namespace stage2 ++} // unnamed namespace ++} // namespace arm64 ++} // namespace simdjson ++/* end file src/generic/stage2/tape_writer.h */ ++ ++namespace simdjson { ++namespace arm64 { ++namespace { ++namespace stage2 { ++ ++struct tape_builder { ++ template ++ simdjson_warn_unused static simdjson_inline error_code parse_document( ++ dom_parser_implementation &dom_parser, ++ dom::document &doc) noexcept; ++ ++ /** Called when a non-empty document starts. */ ++ simdjson_warn_unused simdjson_inline error_code visit_document_start(json_iterator &iter) noexcept; ++ /** Called when a non-empty document ends without error. */ ++ simdjson_warn_unused simdjson_inline error_code visit_document_end(json_iterator &iter) noexcept; ++ ++ /** Called when a non-empty array starts. */ ++ simdjson_warn_unused simdjson_inline error_code visit_array_start(json_iterator &iter) noexcept; ++ /** Called when a non-empty array ends. */ ++ simdjson_warn_unused simdjson_inline error_code visit_array_end(json_iterator &iter) noexcept; ++ /** Called when an empty array is found. */ ++ simdjson_warn_unused simdjson_inline error_code visit_empty_array(json_iterator &iter) noexcept; ++ ++ /** Called when a non-empty object starts. */ ++ simdjson_warn_unused simdjson_inline error_code visit_object_start(json_iterator &iter) noexcept; ++ /** ++ * Called when a key in a field is encountered. ++ * ++ * primitive, visit_object_start, visit_empty_object, visit_array_start, or visit_empty_array ++ * will be called after this with the field value. ++ */ ++ simdjson_warn_unused simdjson_inline error_code visit_key(json_iterator &iter, const uint8_t *key) noexcept; ++ /** Called when a non-empty object ends. */ ++ simdjson_warn_unused simdjson_inline error_code visit_object_end(json_iterator &iter) noexcept; ++ /** Called when an empty object is found. */ ++ simdjson_warn_unused simdjson_inline error_code visit_empty_object(json_iterator &iter) noexcept; ++ ++ /** ++ * Called when a string, number, boolean or null is found. ++ */ ++ simdjson_warn_unused simdjson_inline error_code visit_primitive(json_iterator &iter, const uint8_t *value) noexcept; ++ /** ++ * Called when a string, number, boolean or null is found at the top level of a document (i.e. ++ * when there is no array or object and the entire document is a single string, number, boolean or ++ * null. ++ * ++ * This is separate from primitive() because simdjson's normal primitive parsing routines assume ++ * there is at least one more token after the value, which is only true in an array or object. ++ */ ++ simdjson_warn_unused simdjson_inline error_code visit_root_primitive(json_iterator &iter, const uint8_t *value) noexcept; ++ ++ simdjson_warn_unused simdjson_inline error_code visit_string(json_iterator &iter, const uint8_t *value, bool key = false) noexcept; ++ simdjson_warn_unused simdjson_inline error_code visit_number(json_iterator &iter, const uint8_t *value) noexcept; ++ simdjson_warn_unused simdjson_inline error_code visit_true_atom(json_iterator &iter, const uint8_t *value) noexcept; ++ simdjson_warn_unused simdjson_inline error_code visit_false_atom(json_iterator &iter, const uint8_t *value) noexcept; ++ simdjson_warn_unused simdjson_inline error_code visit_null_atom(json_iterator &iter, const uint8_t *value) noexcept; ++ ++ simdjson_warn_unused simdjson_inline error_code visit_root_string(json_iterator &iter, const uint8_t *value) noexcept; ++ simdjson_warn_unused simdjson_inline error_code visit_root_number(json_iterator &iter, const uint8_t *value) noexcept; ++ simdjson_warn_unused simdjson_inline error_code visit_root_true_atom(json_iterator &iter, const uint8_t *value) noexcept; ++ simdjson_warn_unused simdjson_inline error_code visit_root_false_atom(json_iterator &iter, const uint8_t *value) noexcept; ++ simdjson_warn_unused simdjson_inline error_code visit_root_null_atom(json_iterator &iter, const uint8_t *value) noexcept; ++ ++ /** Called each time a new field or element in an array or object is found. */ ++ simdjson_warn_unused simdjson_inline error_code increment_count(json_iterator &iter) noexcept; ++ ++ /** Next location to write to tape */ ++ tape_writer tape; ++private: ++ /** Next write location in the string buf for stage 2 parsing */ ++ uint8_t *current_string_buf_loc; ++ ++ simdjson_inline tape_builder(dom::document &doc) noexcept; ++ ++ simdjson_inline uint32_t next_tape_index(json_iterator &iter) const noexcept; ++ simdjson_inline void start_container(json_iterator &iter) noexcept; ++ simdjson_warn_unused simdjson_inline error_code end_container(json_iterator &iter, internal::tape_type start, internal::tape_type end) noexcept; ++ simdjson_warn_unused simdjson_inline error_code empty_container(json_iterator &iter, internal::tape_type start, internal::tape_type end) noexcept; ++ simdjson_inline uint8_t *on_start_string(json_iterator &iter) noexcept; ++ simdjson_inline void on_end_string(uint8_t *dst) noexcept; ++}; // class tape_builder ++ ++template ++simdjson_warn_unused simdjson_inline error_code tape_builder::parse_document( ++ dom_parser_implementation &dom_parser, ++ dom::document &doc) noexcept { ++ dom_parser.doc = &doc; ++ json_iterator iter(dom_parser, STREAMING ? dom_parser.next_structural_index : 0); ++ tape_builder builder(doc); ++ return iter.walk_document(builder); ++} ++ ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_root_primitive(json_iterator &iter, const uint8_t *value) noexcept { ++ return iter.visit_root_primitive(*this, value); ++} ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_primitive(json_iterator &iter, const uint8_t *value) noexcept { ++ return iter.visit_primitive(*this, value); ++} ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_empty_object(json_iterator &iter) noexcept { ++ return empty_container(iter, internal::tape_type::START_OBJECT, internal::tape_type::END_OBJECT); ++} ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_empty_array(json_iterator &iter) noexcept { ++ return empty_container(iter, internal::tape_type::START_ARRAY, internal::tape_type::END_ARRAY); ++} ++ ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_document_start(json_iterator &iter) noexcept { ++ start_container(iter); ++ return SUCCESS; ++} ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_object_start(json_iterator &iter) noexcept { ++ start_container(iter); ++ return SUCCESS; ++} ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_array_start(json_iterator &iter) noexcept { ++ start_container(iter); ++ return SUCCESS; ++} ++ ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_object_end(json_iterator &iter) noexcept { ++ return end_container(iter, internal::tape_type::START_OBJECT, internal::tape_type::END_OBJECT); ++} ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_array_end(json_iterator &iter) noexcept { ++ return end_container(iter, internal::tape_type::START_ARRAY, internal::tape_type::END_ARRAY); ++} ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_document_end(json_iterator &iter) noexcept { ++ constexpr uint32_t start_tape_index = 0; ++ tape.append(start_tape_index, internal::tape_type::ROOT); ++ tape_writer::write(iter.dom_parser.doc->tape[start_tape_index], next_tape_index(iter), internal::tape_type::ROOT); ++ return SUCCESS; ++} ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_key(json_iterator &iter, const uint8_t *key) noexcept { ++ return visit_string(iter, key, true); ++} ++ ++simdjson_warn_unused simdjson_inline error_code tape_builder::increment_count(json_iterator &iter) noexcept { ++ iter.dom_parser.open_containers[iter.depth].count++; // we have a key value pair in the object at parser.dom_parser.depth - 1 ++ return SUCCESS; ++} ++ ++simdjson_inline tape_builder::tape_builder(dom::document &doc) noexcept : tape{doc.tape.get()}, current_string_buf_loc{doc.string_buf.get()} {} ++ ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_string(json_iterator &iter, const uint8_t *value, bool key) noexcept { ++ iter.log_value(key ? "key" : "string"); ++ uint8_t *dst = on_start_string(iter); ++ dst = stringparsing::parse_string(value+1, dst); ++ if (dst == nullptr) { ++ iter.log_error("Invalid escape in string"); ++ return STRING_ERROR; ++ } ++ on_end_string(dst); ++ return SUCCESS; ++} ++ ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_root_string(json_iterator &iter, const uint8_t *value) noexcept { ++ return visit_string(iter, value); ++} ++ ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_number(json_iterator &iter, const uint8_t *value) noexcept { ++ iter.log_value("number"); ++ return numberparsing::parse_number(value, tape); ++} ++ ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_root_number(json_iterator &iter, const uint8_t *value) noexcept { ++ // ++ // We need to make a copy to make sure that the string is space terminated. ++ // This is not about padding the input, which should already padded up ++ // to len + SIMDJSON_PADDING. However, we have no control at this stage ++ // on how the padding was done. What if the input string was padded with nulls? ++ // It is quite common for an input string to have an extra null character (C string). ++ // We do not want to allow 9\0 (where \0 is the null character) inside a JSON ++ // document, but the string "9\0" by itself is fine. So we make a copy and ++ // pad the input with spaces when we know that there is just one input element. ++ // This copy is relatively expensive, but it will almost never be called in ++ // practice unless you are in the strange scenario where you have many JSON ++ // documents made of single atoms. ++ // ++ std::unique_ptrcopy(new (std::nothrow) uint8_t[iter.remaining_len() + SIMDJSON_PADDING]); ++ if (copy.get() == nullptr) { return MEMALLOC; } ++ std::memcpy(copy.get(), value, iter.remaining_len()); ++ std::memset(copy.get() + iter.remaining_len(), ' ', SIMDJSON_PADDING); ++ error_code error = visit_number(iter, copy.get()); ++ return error; ++} ++ ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_true_atom(json_iterator &iter, const uint8_t *value) noexcept { ++ iter.log_value("true"); ++ if (!atomparsing::is_valid_true_atom(value)) { return T_ATOM_ERROR; } ++ tape.append(0, internal::tape_type::TRUE_VALUE); ++ return SUCCESS; ++} ++ ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_root_true_atom(json_iterator &iter, const uint8_t *value) noexcept { ++ iter.log_value("true"); ++ if (!atomparsing::is_valid_true_atom(value, iter.remaining_len())) { return T_ATOM_ERROR; } ++ tape.append(0, internal::tape_type::TRUE_VALUE); ++ return SUCCESS; ++} ++ ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_false_atom(json_iterator &iter, const uint8_t *value) noexcept { ++ iter.log_value("false"); ++ if (!atomparsing::is_valid_false_atom(value)) { return F_ATOM_ERROR; } ++ tape.append(0, internal::tape_type::FALSE_VALUE); ++ return SUCCESS; ++} ++ ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_root_false_atom(json_iterator &iter, const uint8_t *value) noexcept { ++ iter.log_value("false"); ++ if (!atomparsing::is_valid_false_atom(value, iter.remaining_len())) { return F_ATOM_ERROR; } ++ tape.append(0, internal::tape_type::FALSE_VALUE); ++ return SUCCESS; ++} ++ ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_null_atom(json_iterator &iter, const uint8_t *value) noexcept { ++ iter.log_value("null"); ++ if (!atomparsing::is_valid_null_atom(value)) { return N_ATOM_ERROR; } ++ tape.append(0, internal::tape_type::NULL_VALUE); ++ return SUCCESS; ++} ++ ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_root_null_atom(json_iterator &iter, const uint8_t *value) noexcept { ++ iter.log_value("null"); ++ if (!atomparsing::is_valid_null_atom(value, iter.remaining_len())) { return N_ATOM_ERROR; } ++ tape.append(0, internal::tape_type::NULL_VALUE); ++ return SUCCESS; ++} ++ ++// private: ++ ++simdjson_inline uint32_t tape_builder::next_tape_index(json_iterator &iter) const noexcept { ++ return uint32_t(tape.next_tape_loc - iter.dom_parser.doc->tape.get()); ++} ++ ++simdjson_warn_unused simdjson_inline error_code tape_builder::empty_container(json_iterator &iter, internal::tape_type start, internal::tape_type end) noexcept { ++ auto start_index = next_tape_index(iter); ++ tape.append(start_index+2, start); ++ tape.append(start_index, end); ++ return SUCCESS; ++} ++ ++simdjson_inline void tape_builder::start_container(json_iterator &iter) noexcept { ++ iter.dom_parser.open_containers[iter.depth].tape_index = next_tape_index(iter); ++ iter.dom_parser.open_containers[iter.depth].count = 0; ++ tape.skip(); // We don't actually *write* the start element until the end. ++} ++ ++simdjson_warn_unused simdjson_inline error_code tape_builder::end_container(json_iterator &iter, internal::tape_type start, internal::tape_type end) noexcept { ++ // Write the ending tape element, pointing at the start location ++ const uint32_t start_tape_index = iter.dom_parser.open_containers[iter.depth].tape_index; ++ tape.append(start_tape_index, end); ++ // Write the start tape element, pointing at the end location (and including count) ++ // count can overflow if it exceeds 24 bits... so we saturate ++ // the convention being that a cnt of 0xffffff or more is undetermined in value (>= 0xffffff). ++ const uint32_t count = iter.dom_parser.open_containers[iter.depth].count; ++ const uint32_t cntsat = count > 0xFFFFFF ? 0xFFFFFF : count; ++ tape_writer::write(iter.dom_parser.doc->tape[start_tape_index], next_tape_index(iter) | (uint64_t(cntsat) << 32), start); ++ return SUCCESS; ++} ++ ++simdjson_inline uint8_t *tape_builder::on_start_string(json_iterator &iter) noexcept { ++ // we advance the point, accounting for the fact that we have a NULL termination ++ tape.append(current_string_buf_loc - iter.dom_parser.doc->string_buf.get(), internal::tape_type::STRING); ++ return current_string_buf_loc + sizeof(uint32_t); ++} ++ ++simdjson_inline void tape_builder::on_end_string(uint8_t *dst) noexcept { ++ uint32_t str_length = uint32_t(dst - (current_string_buf_loc + sizeof(uint32_t))); ++ // TODO check for overflow in case someone has a crazy string (>=4GB?) ++ // But only add the overflow check when the document itself exceeds 4GB ++ // Currently unneeded because we refuse to parse docs larger or equal to 4GB. ++ memcpy(current_string_buf_loc, &str_length, sizeof(uint32_t)); ++ // NULL termination is still handy if you expect all your strings to ++ // be NULL terminated? It comes at a small cost ++ *dst = 0; ++ current_string_buf_loc = dst + 1; ++} ++ ++} // namespace stage2 ++} // unnamed namespace ++} // namespace arm64 ++} // namespace simdjson ++/* end file src/generic/stage2/tape_builder.h */ ++ ++// ++// Implementation-specific overrides ++// ++namespace simdjson { ++namespace arm64 { ++namespace { ++namespace stage1 { ++ ++simdjson_inline uint64_t json_string_scanner::find_escaped(uint64_t backslash) { ++ // On ARM, we don't short-circuit this if there are no backslashes, because the branch gives us no ++ // benefit and therefore makes things worse. ++ // if (!backslash) { uint64_t escaped = prev_escaped; prev_escaped = 0; return escaped; } ++ return find_escaped_branchless(backslash); ++} ++ ++} // namespace stage1 ++} // unnamed namespace ++ ++simdjson_warn_unused error_code implementation::minify(const uint8_t *buf, size_t len, uint8_t *dst, size_t &dst_len) const noexcept { ++ return arm64::stage1::json_minifier::minify<64>(buf, len, dst, dst_len); ++} ++ ++simdjson_warn_unused error_code dom_parser_implementation::stage1(const uint8_t *_buf, size_t _len, stage1_mode streaming) noexcept { ++ this->buf = _buf; ++ this->len = _len; ++ return arm64::stage1::json_structural_indexer::index<64>(buf, len, *this, streaming); ++} ++ ++simdjson_warn_unused bool implementation::validate_utf8(const char *buf, size_t len) const noexcept { ++ return arm64::stage1::generic_validate_utf8(buf,len); ++} ++ ++simdjson_warn_unused error_code dom_parser_implementation::stage2(dom::document &_doc) noexcept { ++ return stage2::tape_builder::parse_document(*this, _doc); ++} ++ ++simdjson_warn_unused error_code dom_parser_implementation::stage2_next(dom::document &_doc) noexcept { ++ return stage2::tape_builder::parse_document(*this, _doc); ++} ++ ++simdjson_warn_unused uint8_t *dom_parser_implementation::parse_string(const uint8_t *src, uint8_t *dst) const noexcept { ++ return arm64::stringparsing::parse_string(src, dst); ++} ++ ++simdjson_warn_unused error_code dom_parser_implementation::parse(const uint8_t *_buf, size_t _len, dom::document &_doc) noexcept { ++ auto error = stage1(_buf, _len, stage1_mode::regular); ++ if (error) { return error; } ++ return stage2(_doc); ++} ++ ++} // namespace arm64 ++} // namespace simdjson ++ ++/* begin file include/simdjson/arm64/end.h */ ++/* end file include/simdjson/arm64/end.h */ ++/* end file src/arm64/dom_parser_implementation.cpp */ ++#endif ++#if SIMDJSON_IMPLEMENTATION_FALLBACK ++/* begin file src/fallback/implementation.cpp */ ++/* begin file include/simdjson/fallback/begin.h */ ++// redefining SIMDJSON_IMPLEMENTATION to "fallback" ++// #define SIMDJSON_IMPLEMENTATION fallback ++/* end file include/simdjson/fallback/begin.h */ ++namespace simdjson { ++namespace fallback { ++ ++simdjson_warn_unused error_code implementation::create_dom_parser_implementation( ++ size_t capacity, ++ size_t max_depth, ++ std::unique_ptr& dst ++) const noexcept { ++ dst.reset( new (std::nothrow) dom_parser_implementation() ); ++ if (!dst) { return MEMALLOC; } ++ if (auto err = dst->set_capacity(capacity)) ++ return err; ++ if (auto err = dst->set_max_depth(max_depth)) ++ return err; ++ return SUCCESS; ++} ++ ++} // namespace fallback ++} // namespace simdjson ++ ++/* begin file include/simdjson/fallback/end.h */ ++/* end file include/simdjson/fallback/end.h */ ++/* end file src/fallback/implementation.cpp */ ++/* begin file src/fallback/dom_parser_implementation.cpp */ ++/* begin file include/simdjson/fallback/begin.h */ ++// redefining SIMDJSON_IMPLEMENTATION to "fallback" ++// #define SIMDJSON_IMPLEMENTATION fallback ++/* end file include/simdjson/fallback/begin.h */ ++ ++// ++// Stage 1 ++// ++/* begin file src/generic/stage1/find_next_document_index.h */ ++namespace simdjson { ++namespace fallback { ++namespace { ++ ++/** ++ * This algorithm is used to quickly identify the last structural position that ++ * makes up a complete document. ++ * ++ * It does this by going backwards and finding the last *document boundary* (a ++ * place where one value follows another without a comma between them). If the ++ * last document (the characters after the boundary) has an equal number of ++ * start and end brackets, it is considered complete. ++ * ++ * Simply put, we iterate over the structural characters, starting from ++ * the end. We consider that we found the end of a JSON document when the ++ * first element of the pair is NOT one of these characters: '{' '[' ':' ',' ++ * and when the second element is NOT one of these characters: '}' ']' ':' ','. ++ * ++ * This simple comparison works most of the time, but it does not cover cases ++ * where the batch's structural indexes contain a perfect amount of documents. ++ * In such a case, we do not have access to the structural index which follows ++ * the last document, therefore, we do not have access to the second element in ++ * the pair, and that means we cannot identify the last document. To fix this ++ * issue, we keep a count of the open and closed curly/square braces we found ++ * while searching for the pair. When we find a pair AND the count of open and ++ * closed curly/square braces is the same, we know that we just passed a ++ * complete document, therefore the last json buffer location is the end of the ++ * batch. ++ */ ++simdjson_inline uint32_t find_next_document_index(dom_parser_implementation &parser) { ++ // Variant: do not count separately, just figure out depth ++ if(parser.n_structural_indexes == 0) { return 0; } ++ auto arr_cnt = 0; ++ auto obj_cnt = 0; ++ for (auto i = parser.n_structural_indexes - 1; i > 0; i--) { ++ auto idxb = parser.structural_indexes[i]; ++ switch (parser.buf[idxb]) { ++ case ':': ++ case ',': ++ continue; ++ case '}': ++ obj_cnt--; ++ continue; ++ case ']': ++ arr_cnt--; ++ continue; ++ case '{': ++ obj_cnt++; ++ break; ++ case '[': ++ arr_cnt++; ++ break; ++ } ++ auto idxa = parser.structural_indexes[i - 1]; ++ switch (parser.buf[idxa]) { ++ case '{': ++ case '[': ++ case ':': ++ case ',': ++ continue; ++ } ++ // Last document is complete, so the next document will appear after! ++ if (!arr_cnt && !obj_cnt) { ++ return parser.n_structural_indexes; ++ } ++ // Last document is incomplete; mark the document at i + 1 as the next one ++ return i; ++ } ++ // If we made it to the end, we want to finish counting to see if we have a full document. ++ switch (parser.buf[parser.structural_indexes[0]]) { ++ case '}': ++ obj_cnt--; ++ break; ++ case ']': ++ arr_cnt--; ++ break; ++ case '{': ++ obj_cnt++; ++ break; ++ case '[': ++ arr_cnt++; ++ break; ++ } ++ if (!arr_cnt && !obj_cnt) { ++ // We have a complete document. ++ return parser.n_structural_indexes; ++ } ++ return 0; ++} ++ ++} // unnamed namespace ++} // namespace fallback ++} // namespace simdjson ++/* end file src/generic/stage1/find_next_document_index.h */ ++ ++namespace simdjson { ++namespace fallback { ++namespace { ++namespace stage1 { ++ ++class structural_scanner { ++public: ++ ++simdjson_inline structural_scanner(dom_parser_implementation &_parser, stage1_mode _partial) ++ : buf{_parser.buf}, ++ next_structural_index{_parser.structural_indexes.get()}, ++ parser{_parser}, ++ len{static_cast(_parser.len)}, ++ partial{_partial} { ++} ++ ++simdjson_inline void add_structural() { ++ *next_structural_index = idx; ++ next_structural_index++; ++} ++ ++simdjson_inline bool is_continuation(uint8_t c) { ++ return (c & 0xc0) == 0x80; ++} ++ ++simdjson_inline void validate_utf8_character() { ++ // Continuation ++ if (simdjson_unlikely((buf[idx] & 0x40) == 0)) { ++ // extra continuation ++ error = UTF8_ERROR; ++ idx++; ++ return; ++ } ++ ++ // 2-byte ++ if ((buf[idx] & 0x20) == 0) { ++ // missing continuation ++ if (simdjson_unlikely(idx+1 > len || !is_continuation(buf[idx+1]))) { ++ if (idx+1 > len && is_streaming(partial)) { idx = len; return; } ++ error = UTF8_ERROR; ++ idx++; ++ return; ++ } ++ // overlong: 1100000_ 10______ ++ if (buf[idx] <= 0xc1) { error = UTF8_ERROR; } ++ idx += 2; ++ return; ++ } ++ ++ // 3-byte ++ if ((buf[idx] & 0x10) == 0) { ++ // missing continuation ++ if (simdjson_unlikely(idx+2 > len || !is_continuation(buf[idx+1]) || !is_continuation(buf[idx+2]))) { ++ if (idx+2 > len && is_streaming(partial)) { idx = len; return; } ++ error = UTF8_ERROR; ++ idx++; ++ return; ++ } ++ // overlong: 11100000 100_____ ________ ++ if (buf[idx] == 0xe0 && buf[idx+1] <= 0x9f) { error = UTF8_ERROR; } ++ // surrogates: U+D800-U+DFFF 11101101 101_____ ++ if (buf[idx] == 0xed && buf[idx+1] >= 0xa0) { error = UTF8_ERROR; } ++ idx += 3; ++ return; ++ } ++ ++ // 4-byte ++ // missing continuation ++ if (simdjson_unlikely(idx+3 > len || !is_continuation(buf[idx+1]) || !is_continuation(buf[idx+2]) || !is_continuation(buf[idx+3]))) { ++ if (idx+2 > len && is_streaming(partial)) { idx = len; return; } ++ error = UTF8_ERROR; ++ idx++; ++ return; ++ } ++ // overlong: 11110000 1000____ ________ ________ ++ if (buf[idx] == 0xf0 && buf[idx+1] <= 0x8f) { error = UTF8_ERROR; } ++ // too large: > U+10FFFF: ++ // 11110100 (1001|101_)____ ++ // 1111(1___|011_|0101) 10______ ++ // also includes 5, 6, 7 and 8 byte characters: ++ // 11111___ ++ if (buf[idx] == 0xf4 && buf[idx+1] >= 0x90) { error = UTF8_ERROR; } ++ if (buf[idx] >= 0xf5) { error = UTF8_ERROR; } ++ idx += 4; ++} ++ ++// Returns true if the string is unclosed. ++simdjson_inline bool validate_string() { ++ idx++; // skip first quote ++ while (idx < len && buf[idx] != '"') { ++ if (buf[idx] == '\\') { ++ idx += 2; ++ } else if (simdjson_unlikely(buf[idx] & 0x80)) { ++ validate_utf8_character(); ++ } else { ++ if (buf[idx] < 0x20) { error = UNESCAPED_CHARS; } ++ idx++; ++ } ++ } ++ if (idx >= len) { return true; } ++ return false; ++} ++ ++simdjson_inline bool is_whitespace_or_operator(uint8_t c) { ++ switch (c) { ++ case '{': case '}': case '[': case ']': case ',': case ':': ++ case ' ': case '\r': case '\n': case '\t': ++ return true; ++ default: ++ return false; ++ } ++} ++ ++// ++// Parse the entire input in STEP_SIZE-byte chunks. ++// ++simdjson_inline error_code scan() { ++ bool unclosed_string = false; ++ for (;idx 0) { ++ if(parser.structural_indexes[0] == 0) { ++ // If the buffer is partial and we started at index 0 but the document is ++ // incomplete, it's too big to parse. ++ return CAPACITY; ++ } else { ++ // It is possible that the document could be parsed, we just had a lot ++ // of white space. ++ parser.n_structural_indexes = 0; ++ return EMPTY; ++ } ++ } ++ parser.n_structural_indexes = new_structural_indexes; ++ } else if(partial == stage1_mode::streaming_final) { ++ if(unclosed_string) { parser.n_structural_indexes--; } ++ // We truncate the input to the end of the last complete document (or zero). ++ // Because partial == stage1_mode::streaming_final, it means that we may ++ // silently ignore trailing garbage. Though it sounds bad, we do it ++ // deliberately because many people who have streams of JSON documents ++ // will truncate them for processing. E.g., imagine that you are uncompressing ++ // the data from a size file or receiving it in chunks from the network. You ++ // may not know where exactly the last document will be. Meanwhile the ++ // document_stream instances allow people to know the JSON documents they are ++ // parsing (see the iterator.source() method). ++ parser.n_structural_indexes = find_next_document_index(parser); ++ // We store the initial n_structural_indexes so that the client can see ++ // whether we used truncation. If initial_n_structural_indexes == parser.n_structural_indexes, ++ // then this will query parser.structural_indexes[parser.n_structural_indexes] which is len, ++ // otherwise, it will copy some prior index. ++ parser.structural_indexes[parser.n_structural_indexes + 1] = parser.structural_indexes[parser.n_structural_indexes]; ++ // This next line is critical, do not change it unless you understand what you are ++ // doing. ++ parser.structural_indexes[parser.n_structural_indexes] = uint32_t(len); ++ if (parser.n_structural_indexes == 0) { return EMPTY; } ++ } else if(unclosed_string) { error = UNCLOSED_STRING; } ++ return error; ++} ++ ++private: ++ const uint8_t *buf; ++ uint32_t *next_structural_index; ++ dom_parser_implementation &parser; ++ uint32_t len; ++ uint32_t idx{0}; ++ error_code error{SUCCESS}; ++ stage1_mode partial; ++}; // structural_scanner ++ ++} // namespace stage1 ++} // unnamed namespace ++ ++simdjson_warn_unused error_code dom_parser_implementation::stage1(const uint8_t *_buf, size_t _len, stage1_mode partial) noexcept { ++ this->buf = _buf; ++ this->len = _len; ++ stage1::structural_scanner scanner(*this, partial); ++ return scanner.scan(); ++} ++ ++// big table for the minifier ++static uint8_t jump_table[256 * 3] = { ++ 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, ++ 1, 1, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, ++ 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, ++ 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, ++ 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, ++ 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, ++ 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, ++ 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, ++ 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, ++ 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, ++ 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, ++ 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, ++ 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, ++ 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, ++ 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, ++ 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, ++ 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, ++ 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, ++ 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, ++ 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, ++ 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, ++ 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, ++ 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, ++ 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, ++ 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, ++ 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, ++ 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, ++ 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, ++ 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, ++ 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, ++ 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, ++}; ++ ++simdjson_warn_unused error_code implementation::minify(const uint8_t *buf, size_t len, uint8_t *dst, size_t &dst_len) const noexcept { ++ size_t i = 0, pos = 0; ++ uint8_t quote = 0; ++ uint8_t nonescape = 1; ++ ++ while (i < len) { ++ unsigned char c = buf[i]; ++ uint8_t *meta = jump_table + 3 * c; ++ ++ quote = quote ^ (meta[0] & nonescape); ++ dst[pos] = c; ++ pos += meta[2] | quote; ++ ++ i += 1; ++ nonescape = uint8_t(~nonescape) | (meta[1]); ++ } ++ dst_len = pos; // we intentionally do not work with a reference ++ // for fear of aliasing ++ return quote ? UNCLOSED_STRING : SUCCESS; ++} ++ ++// credit: based on code from Google Fuchsia (Apache Licensed) ++simdjson_warn_unused bool implementation::validate_utf8(const char *buf, size_t len) const noexcept { ++ const uint8_t *data = reinterpret_cast(buf); ++ uint64_t pos = 0; ++ uint32_t code_point = 0; ++ while (pos < len) { ++ // check of the next 8 bytes are ascii. ++ uint64_t next_pos = pos + 16; ++ if (next_pos <= len) { // if it is safe to read 8 more bytes, check that they are ascii ++ uint64_t v1; ++ memcpy(&v1, data + pos, sizeof(uint64_t)); ++ uint64_t v2; ++ memcpy(&v2, data + pos + sizeof(uint64_t), sizeof(uint64_t)); ++ uint64_t v{v1 | v2}; ++ if ((v & 0x8080808080808080) == 0) { ++ pos = next_pos; ++ continue; ++ } ++ } ++ unsigned char byte = data[pos]; ++ if (byte < 0x80) { ++ pos++; ++ continue; ++ } else if ((byte & 0xe0) == 0xc0) { ++ next_pos = pos + 2; ++ if (next_pos > len) { return false; } ++ if ((data[pos + 1] & 0xc0) != 0x80) { return false; } ++ // range check ++ code_point = (byte & 0x1f) << 6 | (data[pos + 1] & 0x3f); ++ if (code_point < 0x80 || 0x7ff < code_point) { return false; } ++ } else if ((byte & 0xf0) == 0xe0) { ++ next_pos = pos + 3; ++ if (next_pos > len) { return false; } ++ if ((data[pos + 1] & 0xc0) != 0x80) { return false; } ++ if ((data[pos + 2] & 0xc0) != 0x80) { return false; } ++ // range check ++ code_point = (byte & 0x0f) << 12 | ++ (data[pos + 1] & 0x3f) << 6 | ++ (data[pos + 2] & 0x3f); ++ if (code_point < 0x800 || 0xffff < code_point || ++ (0xd7ff < code_point && code_point < 0xe000)) { ++ return false; ++ } ++ } else if ((byte & 0xf8) == 0xf0) { // 0b11110000 ++ next_pos = pos + 4; ++ if (next_pos > len) { return false; } ++ if ((data[pos + 1] & 0xc0) != 0x80) { return false; } ++ if ((data[pos + 2] & 0xc0) != 0x80) { return false; } ++ if ((data[pos + 3] & 0xc0) != 0x80) { return false; } ++ // range check ++ code_point = ++ (byte & 0x07) << 18 | (data[pos + 1] & 0x3f) << 12 | ++ (data[pos + 2] & 0x3f) << 6 | (data[pos + 3] & 0x3f); ++ if (code_point <= 0xffff || 0x10ffff < code_point) { return false; } ++ } else { ++ // we may have a continuation ++ return false; ++ } ++ pos = next_pos; ++ } ++ return true; ++} ++ ++} // namespace fallback ++} // namespace simdjson ++ ++// ++// Stage 2 ++// ++/* begin file src/generic/stage2/stringparsing.h */ ++// This file contains the common code every implementation uses ++// It is intended to be included multiple times and compiled multiple times ++ ++namespace simdjson { ++namespace fallback { ++namespace { ++/// @private ++namespace stringparsing { ++ ++// begin copypasta ++// These chars yield themselves: " \ / ++// b -> backspace, f -> formfeed, n -> newline, r -> cr, t -> horizontal tab ++// u not handled in this table as it's complex ++static const uint8_t escape_map[256] = { ++ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x0. ++ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ++ 0, 0, 0x22, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x2f, ++ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ++ ++ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x4. ++ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x5c, 0, 0, 0, // 0x5. ++ 0, 0, 0x08, 0, 0, 0, 0x0c, 0, 0, 0, 0, 0, 0, 0, 0x0a, 0, // 0x6. ++ 0, 0, 0x0d, 0, 0x09, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x7. ++ ++ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ++ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ++ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ++ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ++ ++ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ++ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ++ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ++ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ++}; ++ ++// handle a unicode codepoint ++// write appropriate values into dest ++// src will advance 6 bytes or 12 bytes ++// dest will advance a variable amount (return via pointer) ++// return true if the unicode codepoint was valid ++// We work in little-endian then swap at write time ++simdjson_warn_unused ++simdjson_inline bool handle_unicode_codepoint(const uint8_t **src_ptr, ++ uint8_t **dst_ptr) { ++ // jsoncharutils::hex_to_u32_nocheck fills high 16 bits of the return value with 1s if the ++ // conversion isn't valid; we defer the check for this to inside the ++ // multilingual plane check ++ uint32_t code_point = jsoncharutils::hex_to_u32_nocheck(*src_ptr + 2); ++ *src_ptr += 6; ++ ++ // If we found a high surrogate, we must ++ // check for low surrogate for characters ++ // outside the Basic ++ // Multilingual Plane. ++ if (code_point >= 0xd800 && code_point < 0xdc00) { ++ const uint8_t *src_data = *src_ptr; ++ /* Compiler optimizations convert this to a single 16-bit load and compare on most platforms */ ++ if (((src_data[0] << 8) | src_data[1]) != ((static_cast ('\\') << 8) | static_cast ('u'))) { ++ return false; ++ } ++ uint32_t code_point_2 = jsoncharutils::hex_to_u32_nocheck(src_data + 2); ++ ++ // We have already checked that the high surrogate is valid and ++ // (code_point - 0xd800) < 1024. ++ // ++ // Check that code_point_2 is in the range 0xdc00..0xdfff ++ // and that code_point_2 was parsed from valid hex. ++ uint32_t low_bit = code_point_2 - 0xdc00; ++ if (low_bit >> 10) { ++ return false; ++ } ++ ++ code_point = ++ (((code_point - 0xd800) << 10) | low_bit) + 0x10000; ++ *src_ptr += 6; ++ } else if (code_point >= 0xdc00 && code_point <= 0xdfff) { ++ // If we encounter a low surrogate (not preceded by a high surrogate) ++ // then we have an error. ++ return false; ++ } ++ size_t offset = jsoncharutils::codepoint_to_utf8(code_point, *dst_ptr); ++ *dst_ptr += offset; ++ return offset > 0; ++} ++ ++/** ++ * Unescape a valid UTF-8 string from src to dst, stopping at a final unescaped quote. There ++ * must be an unescaped quote terminating the string. It returns the final output ++ * position as pointer. In case of error (e.g., the string has bad escaped codes), ++ * then null_nullptrptr is returned. It is assumed that the output buffer is large ++ * enough. E.g., if src points at 'joe"', then dst needs to have four free bytes + ++ * SIMDJSON_PADDING bytes. ++ */ ++simdjson_warn_unused simdjson_inline uint8_t *parse_string(const uint8_t *src, uint8_t *dst) { ++ while (1) { ++ // Copy the next n bytes, and find the backslash and quote in them. ++ auto bs_quote = backslash_and_quote::copy_and_find(src, dst); ++ // If the next thing is the end quote, copy and return ++ if (bs_quote.has_quote_first()) { ++ // we encountered quotes first. Move dst to point to quotes and exit ++ return dst + bs_quote.quote_index(); ++ } ++ if (bs_quote.has_backslash()) { ++ /* find out where the backspace is */ ++ auto bs_dist = bs_quote.backslash_index(); ++ uint8_t escape_char = src[bs_dist + 1]; ++ /* we encountered backslash first. Handle backslash */ ++ if (escape_char == 'u') { ++ /* move src/dst up to the start; they will be further adjusted ++ within the unicode codepoint handling code. */ ++ src += bs_dist; ++ dst += bs_dist; ++ if (!handle_unicode_codepoint(&src, &dst)) { ++ return nullptr; ++ } ++ } else { ++ /* simple 1:1 conversion. Will eat bs_dist+2 characters in input and ++ * write bs_dist+1 characters to output ++ * note this may reach beyond the part of the buffer we've actually ++ * seen. I think this is ok */ ++ uint8_t escape_result = escape_map[escape_char]; ++ if (escape_result == 0u) { ++ return nullptr; /* bogus escape value is an error */ ++ } ++ dst[bs_dist] = escape_result; ++ src += bs_dist + 2; ++ dst += bs_dist + 1; ++ } ++ } else { ++ /* they are the same. Since they can't co-occur, it means we ++ * encountered neither. */ ++ src += backslash_and_quote::BYTES_PROCESSED; ++ dst += backslash_and_quote::BYTES_PROCESSED; ++ } ++ } ++ /* can't be reached */ ++ return nullptr; ++} ++ ++} // namespace stringparsing ++} // unnamed namespace ++} // namespace fallback ++} // namespace simdjson ++/* end file src/generic/stage2/stringparsing.h */ ++/* begin file src/generic/stage2/tape_builder.h */ ++/* begin file src/generic/stage2/json_iterator.h */ ++/* begin file src/generic/stage2/logger.h */ ++// This is for an internal-only stage 2 specific logger. ++// Set LOG_ENABLED = true to log what stage 2 is doing! ++namespace simdjson { ++namespace fallback { ++namespace { ++namespace logger { ++ ++ static constexpr const char * DASHES = "----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------"; ++ ++#if SIMDJSON_VERBOSE_LOGGING ++ static constexpr const bool LOG_ENABLED = true; ++#else ++ static constexpr const bool LOG_ENABLED = false; ++#endif ++ static constexpr const int LOG_EVENT_LEN = 20; ++ static constexpr const int LOG_BUFFER_LEN = 30; ++ static constexpr const int LOG_SMALL_BUFFER_LEN = 10; ++ static constexpr const int LOG_INDEX_LEN = 5; ++ ++ static int log_depth; // Not threadsafe. Log only. ++ ++ // Helper to turn unprintable or newline characters into spaces ++ static simdjson_inline char printable_char(char c) { ++ if (c >= 0x20) { ++ return c; ++ } else { ++ return ' '; ++ } ++ } ++ ++ // Print the header and set up log_start ++ static simdjson_inline void log_start() { ++ if (LOG_ENABLED) { ++ log_depth = 0; ++ printf("\n"); ++ printf("| %-*s | %-*s | %-*s | %-*s | Detail |\n", LOG_EVENT_LEN, "Event", LOG_BUFFER_LEN, "Buffer", LOG_SMALL_BUFFER_LEN, "Next", 5, "Next#"); ++ printf("|%.*s|%.*s|%.*s|%.*s|--------|\n", LOG_EVENT_LEN+2, DASHES, LOG_BUFFER_LEN+2, DASHES, LOG_SMALL_BUFFER_LEN+2, DASHES, 5+2, DASHES); ++ } ++ } ++ ++ simdjson_unused static simdjson_inline void log_string(const char *message) { ++ if (LOG_ENABLED) { ++ printf("%s\n", message); ++ } ++ } ++ ++ // Logs a single line from the stage 2 DOM parser ++ template ++ static simdjson_inline void log_line(S &structurals, const char *title_prefix, const char *title, const char *detail) { ++ if (LOG_ENABLED) { ++ printf("| %*s%s%-*s ", log_depth*2, "", title_prefix, LOG_EVENT_LEN - log_depth*2 - int(strlen(title_prefix)), title); ++ auto current_index = structurals.at_beginning() ? nullptr : structurals.next_structural-1; ++ auto next_index = structurals.next_structural; ++ auto current = current_index ? &structurals.buf[*current_index] : reinterpret_cast(" "); ++ auto next = &structurals.buf[*next_index]; ++ { ++ // Print the next N characters in the buffer. ++ printf("| "); ++ // Otherwise, print the characters starting from the buffer position. ++ // Print spaces for unprintable or newline characters. ++ for (int i=0;i ++ simdjson_warn_unused simdjson_inline error_code walk_document(V &visitor) noexcept; ++ ++ /** ++ * Create an iterator capable of walking a JSON document. ++ * ++ * The document must have already passed through stage 1. ++ */ ++ simdjson_inline json_iterator(dom_parser_implementation &_dom_parser, size_t start_structural_index); ++ ++ /** ++ * Look at the next token. ++ * ++ * Tokens can be strings, numbers, booleans, null, or operators (`[{]},:`)). ++ * ++ * They may include invalid JSON as well (such as `1.2.3` or `ture`). ++ */ ++ simdjson_inline const uint8_t *peek() const noexcept; ++ /** ++ * Advance to the next token. ++ * ++ * Tokens can be strings, numbers, booleans, null, or operators (`[{]},:`)). ++ * ++ * They may include invalid JSON as well (such as `1.2.3` or `ture`). ++ */ ++ simdjson_inline const uint8_t *advance() noexcept; ++ /** ++ * Get the remaining length of the document, from the start of the current token. ++ */ ++ simdjson_inline size_t remaining_len() const noexcept; ++ /** ++ * Check if we are at the end of the document. ++ * ++ * If this is true, there are no more tokens. ++ */ ++ simdjson_inline bool at_eof() const noexcept; ++ /** ++ * Check if we are at the beginning of the document. ++ */ ++ simdjson_inline bool at_beginning() const noexcept; ++ simdjson_inline uint8_t last_structural() const noexcept; ++ ++ /** ++ * Log that a value has been found. ++ * ++ * Set LOG_ENABLED=true in logger.h to see logging. ++ */ ++ simdjson_inline void log_value(const char *type) const noexcept; ++ /** ++ * Log the start of a multipart value. ++ * ++ * Set LOG_ENABLED=true in logger.h to see logging. ++ */ ++ simdjson_inline void log_start_value(const char *type) const noexcept; ++ /** ++ * Log the end of a multipart value. ++ * ++ * Set LOG_ENABLED=true in logger.h to see logging. ++ */ ++ simdjson_inline void log_end_value(const char *type) const noexcept; ++ /** ++ * Log an error. ++ * ++ * Set LOG_ENABLED=true in logger.h to see logging. ++ */ ++ simdjson_inline void log_error(const char *error) const noexcept; ++ ++ template ++ simdjson_warn_unused simdjson_inline error_code visit_root_primitive(V &visitor, const uint8_t *value) noexcept; ++ template ++ simdjson_warn_unused simdjson_inline error_code visit_primitive(V &visitor, const uint8_t *value) noexcept; ++}; ++ ++template ++simdjson_warn_unused simdjson_inline error_code json_iterator::walk_document(V &visitor) noexcept { ++ logger::log_start(); ++ ++ // ++ // Start the document ++ // ++ if (at_eof()) { return EMPTY; } ++ log_start_value("document"); ++ SIMDJSON_TRY( visitor.visit_document_start(*this) ); ++ ++ // ++ // Read first value ++ // ++ { ++ auto value = advance(); ++ ++ // Make sure the outer object or array is closed before continuing; otherwise, there are ways we ++ // could get into memory corruption. See https://github.com/simdjson/simdjson/issues/906 ++ if (!STREAMING) { ++ switch (*value) { ++ case '{': if (last_structural() != '}') { log_value("starting brace unmatched"); return TAPE_ERROR; }; break; ++ case '[': if (last_structural() != ']') { log_value("starting bracket unmatched"); return TAPE_ERROR; }; break; ++ } ++ } ++ ++ switch (*value) { ++ case '{': if (*peek() == '}') { advance(); log_value("empty object"); SIMDJSON_TRY( visitor.visit_empty_object(*this) ); break; } goto object_begin; ++ case '[': if (*peek() == ']') { advance(); log_value("empty array"); SIMDJSON_TRY( visitor.visit_empty_array(*this) ); break; } goto array_begin; ++ default: SIMDJSON_TRY( visitor.visit_root_primitive(*this, value) ); break; ++ } ++ } ++ goto document_end; ++ ++// ++// Object parser states ++// ++object_begin: ++ log_start_value("object"); ++ depth++; ++ if (depth >= dom_parser.max_depth()) { log_error("Exceeded max depth!"); return DEPTH_ERROR; } ++ dom_parser.is_array[depth] = false; ++ SIMDJSON_TRY( visitor.visit_object_start(*this) ); ++ ++ { ++ auto key = advance(); ++ if (*key != '"') { log_error("Object does not start with a key"); return TAPE_ERROR; } ++ SIMDJSON_TRY( visitor.increment_count(*this) ); ++ SIMDJSON_TRY( visitor.visit_key(*this, key) ); ++ } ++ ++object_field: ++ if (simdjson_unlikely( *advance() != ':' )) { log_error("Missing colon after key in object"); return TAPE_ERROR; } ++ { ++ auto value = advance(); ++ switch (*value) { ++ case '{': if (*peek() == '}') { advance(); log_value("empty object"); SIMDJSON_TRY( visitor.visit_empty_object(*this) ); break; } goto object_begin; ++ case '[': if (*peek() == ']') { advance(); log_value("empty array"); SIMDJSON_TRY( visitor.visit_empty_array(*this) ); break; } goto array_begin; ++ default: SIMDJSON_TRY( visitor.visit_primitive(*this, value) ); break; ++ } ++ } ++ ++object_continue: ++ switch (*advance()) { ++ case ',': ++ SIMDJSON_TRY( visitor.increment_count(*this) ); ++ { ++ auto key = advance(); ++ if (simdjson_unlikely( *key != '"' )) { log_error("Key string missing at beginning of field in object"); return TAPE_ERROR; } ++ SIMDJSON_TRY( visitor.visit_key(*this, key) ); ++ } ++ goto object_field; ++ case '}': log_end_value("object"); SIMDJSON_TRY( visitor.visit_object_end(*this) ); goto scope_end; ++ default: log_error("No comma between object fields"); return TAPE_ERROR; ++ } ++ ++scope_end: ++ depth--; ++ if (depth == 0) { goto document_end; } ++ if (dom_parser.is_array[depth]) { goto array_continue; } ++ goto object_continue; ++ ++// ++// Array parser states ++// ++array_begin: ++ log_start_value("array"); ++ depth++; ++ if (depth >= dom_parser.max_depth()) { log_error("Exceeded max depth!"); return DEPTH_ERROR; } ++ dom_parser.is_array[depth] = true; ++ SIMDJSON_TRY( visitor.visit_array_start(*this) ); ++ SIMDJSON_TRY( visitor.increment_count(*this) ); ++ ++array_value: ++ { ++ auto value = advance(); ++ switch (*value) { ++ case '{': if (*peek() == '}') { advance(); log_value("empty object"); SIMDJSON_TRY( visitor.visit_empty_object(*this) ); break; } goto object_begin; ++ case '[': if (*peek() == ']') { advance(); log_value("empty array"); SIMDJSON_TRY( visitor.visit_empty_array(*this) ); break; } goto array_begin; ++ default: SIMDJSON_TRY( visitor.visit_primitive(*this, value) ); break; ++ } ++ } ++ ++array_continue: ++ switch (*advance()) { ++ case ',': SIMDJSON_TRY( visitor.increment_count(*this) ); goto array_value; ++ case ']': log_end_value("array"); SIMDJSON_TRY( visitor.visit_array_end(*this) ); goto scope_end; ++ default: log_error("Missing comma between array values"); return TAPE_ERROR; ++ } ++ ++document_end: ++ log_end_value("document"); ++ SIMDJSON_TRY( visitor.visit_document_end(*this) ); ++ ++ dom_parser.next_structural_index = uint32_t(next_structural - &dom_parser.structural_indexes[0]); ++ ++ // If we didn't make it to the end, it's an error ++ if ( !STREAMING && dom_parser.next_structural_index != dom_parser.n_structural_indexes ) { ++ log_error("More than one JSON value at the root of the document, or extra characters at the end of the JSON!"); ++ return TAPE_ERROR; ++ } ++ ++ return SUCCESS; ++ ++} // walk_document() ++ ++simdjson_inline json_iterator::json_iterator(dom_parser_implementation &_dom_parser, size_t start_structural_index) ++ : buf{_dom_parser.buf}, ++ next_structural{&_dom_parser.structural_indexes[start_structural_index]}, ++ dom_parser{_dom_parser} { ++} ++ ++simdjson_inline const uint8_t *json_iterator::peek() const noexcept { ++ return &buf[*(next_structural)]; ++} ++simdjson_inline const uint8_t *json_iterator::advance() noexcept { ++ return &buf[*(next_structural++)]; ++} ++simdjson_inline size_t json_iterator::remaining_len() const noexcept { ++ return dom_parser.len - *(next_structural-1); ++} ++ ++simdjson_inline bool json_iterator::at_eof() const noexcept { ++ return next_structural == &dom_parser.structural_indexes[dom_parser.n_structural_indexes]; ++} ++simdjson_inline bool json_iterator::at_beginning() const noexcept { ++ return next_structural == dom_parser.structural_indexes.get(); ++} ++simdjson_inline uint8_t json_iterator::last_structural() const noexcept { ++ return buf[dom_parser.structural_indexes[dom_parser.n_structural_indexes - 1]]; ++} ++ ++simdjson_inline void json_iterator::log_value(const char *type) const noexcept { ++ logger::log_line(*this, "", type, ""); ++} ++ ++simdjson_inline void json_iterator::log_start_value(const char *type) const noexcept { ++ logger::log_line(*this, "+", type, ""); ++ if (logger::LOG_ENABLED) { logger::log_depth++; } ++} ++ ++simdjson_inline void json_iterator::log_end_value(const char *type) const noexcept { ++ if (logger::LOG_ENABLED) { logger::log_depth--; } ++ logger::log_line(*this, "-", type, ""); ++} ++ ++simdjson_inline void json_iterator::log_error(const char *error) const noexcept { ++ logger::log_line(*this, "", "ERROR", error); ++} ++ ++template ++simdjson_warn_unused simdjson_inline error_code json_iterator::visit_root_primitive(V &visitor, const uint8_t *value) noexcept { ++ switch (*value) { ++ case '"': return visitor.visit_root_string(*this, value); ++ case 't': return visitor.visit_root_true_atom(*this, value); ++ case 'f': return visitor.visit_root_false_atom(*this, value); ++ case 'n': return visitor.visit_root_null_atom(*this, value); ++ case '-': ++ case '0': case '1': case '2': case '3': case '4': ++ case '5': case '6': case '7': case '8': case '9': ++ return visitor.visit_root_number(*this, value); ++ default: ++ log_error("Document starts with a non-value character"); ++ return TAPE_ERROR; ++ } ++} ++template ++simdjson_warn_unused simdjson_inline error_code json_iterator::visit_primitive(V &visitor, const uint8_t *value) noexcept { ++ switch (*value) { ++ case '"': return visitor.visit_string(*this, value); ++ case 't': return visitor.visit_true_atom(*this, value); ++ case 'f': return visitor.visit_false_atom(*this, value); ++ case 'n': return visitor.visit_null_atom(*this, value); ++ case '-': ++ case '0': case '1': case '2': case '3': case '4': ++ case '5': case '6': case '7': case '8': case '9': ++ return visitor.visit_number(*this, value); ++ default: ++ log_error("Non-value found when value was expected!"); ++ return TAPE_ERROR; ++ } ++} ++ ++} // namespace stage2 ++} // unnamed namespace ++} // namespace fallback ++} // namespace simdjson ++/* end file src/generic/stage2/json_iterator.h */ ++/* begin file src/generic/stage2/tape_writer.h */ ++namespace simdjson { ++namespace fallback { ++namespace { ++namespace stage2 { ++ ++struct tape_writer { ++ /** The next place to write to tape */ ++ uint64_t *next_tape_loc; ++ ++ /** Write a signed 64-bit value to tape. */ ++ simdjson_inline void append_s64(int64_t value) noexcept; ++ ++ /** Write an unsigned 64-bit value to tape. */ ++ simdjson_inline void append_u64(uint64_t value) noexcept; ++ ++ /** Write a double value to tape. */ ++ simdjson_inline void append_double(double value) noexcept; ++ ++ /** ++ * Append a tape entry (an 8-bit type,and 56 bits worth of value). ++ */ ++ simdjson_inline void append(uint64_t val, internal::tape_type t) noexcept; ++ ++ /** ++ * Skip the current tape entry without writing. ++ * ++ * Used to skip the start of the container, since we'll come back later to fill it in when the ++ * container ends. ++ */ ++ simdjson_inline void skip() noexcept; ++ ++ /** ++ * Skip the number of tape entries necessary to write a large u64 or i64. ++ */ ++ simdjson_inline void skip_large_integer() noexcept; ++ ++ /** ++ * Skip the number of tape entries necessary to write a double. ++ */ ++ simdjson_inline void skip_double() noexcept; ++ ++ /** ++ * Write a value to a known location on tape. ++ * ++ * Used to go back and write out the start of a container after the container ends. ++ */ ++ simdjson_inline static void write(uint64_t &tape_loc, uint64_t val, internal::tape_type t) noexcept; ++ ++private: ++ /** ++ * Append both the tape entry, and a supplementary value following it. Used for types that need ++ * all 64 bits, such as double and uint64_t. ++ */ ++ template ++ simdjson_inline void append2(uint64_t val, T val2, internal::tape_type t) noexcept; ++}; // struct number_writer ++ ++simdjson_inline void tape_writer::append_s64(int64_t value) noexcept { ++ append2(0, value, internal::tape_type::INT64); ++} ++ ++simdjson_inline void tape_writer::append_u64(uint64_t value) noexcept { ++ append(0, internal::tape_type::UINT64); ++ *next_tape_loc = value; ++ next_tape_loc++; ++} ++ ++/** Write a double value to tape. */ ++simdjson_inline void tape_writer::append_double(double value) noexcept { ++ append2(0, value, internal::tape_type::DOUBLE); ++} ++ ++simdjson_inline void tape_writer::skip() noexcept { ++ next_tape_loc++; ++} ++ ++simdjson_inline void tape_writer::skip_large_integer() noexcept { ++ next_tape_loc += 2; ++} ++ ++simdjson_inline void tape_writer::skip_double() noexcept { ++ next_tape_loc += 2; ++} ++ ++simdjson_inline void tape_writer::append(uint64_t val, internal::tape_type t) noexcept { ++ *next_tape_loc = val | ((uint64_t(char(t))) << 56); ++ next_tape_loc++; ++} ++ ++template ++simdjson_inline void tape_writer::append2(uint64_t val, T val2, internal::tape_type t) noexcept { ++ append(val, t); ++ static_assert(sizeof(val2) == sizeof(*next_tape_loc), "Type is not 64 bits!"); ++ memcpy(next_tape_loc, &val2, sizeof(val2)); ++ next_tape_loc++; ++} ++ ++simdjson_inline void tape_writer::write(uint64_t &tape_loc, uint64_t val, internal::tape_type t) noexcept { ++ tape_loc = val | ((uint64_t(char(t))) << 56); ++} ++ ++} // namespace stage2 ++} // unnamed namespace ++} // namespace fallback ++} // namespace simdjson ++/* end file src/generic/stage2/tape_writer.h */ ++ ++namespace simdjson { ++namespace fallback { ++namespace { ++namespace stage2 { ++ ++struct tape_builder { ++ template ++ simdjson_warn_unused static simdjson_inline error_code parse_document( ++ dom_parser_implementation &dom_parser, ++ dom::document &doc) noexcept; ++ ++ /** Called when a non-empty document starts. */ ++ simdjson_warn_unused simdjson_inline error_code visit_document_start(json_iterator &iter) noexcept; ++ /** Called when a non-empty document ends without error. */ ++ simdjson_warn_unused simdjson_inline error_code visit_document_end(json_iterator &iter) noexcept; ++ ++ /** Called when a non-empty array starts. */ ++ simdjson_warn_unused simdjson_inline error_code visit_array_start(json_iterator &iter) noexcept; ++ /** Called when a non-empty array ends. */ ++ simdjson_warn_unused simdjson_inline error_code visit_array_end(json_iterator &iter) noexcept; ++ /** Called when an empty array is found. */ ++ simdjson_warn_unused simdjson_inline error_code visit_empty_array(json_iterator &iter) noexcept; ++ ++ /** Called when a non-empty object starts. */ ++ simdjson_warn_unused simdjson_inline error_code visit_object_start(json_iterator &iter) noexcept; ++ /** ++ * Called when a key in a field is encountered. ++ * ++ * primitive, visit_object_start, visit_empty_object, visit_array_start, or visit_empty_array ++ * will be called after this with the field value. ++ */ ++ simdjson_warn_unused simdjson_inline error_code visit_key(json_iterator &iter, const uint8_t *key) noexcept; ++ /** Called when a non-empty object ends. */ ++ simdjson_warn_unused simdjson_inline error_code visit_object_end(json_iterator &iter) noexcept; ++ /** Called when an empty object is found. */ ++ simdjson_warn_unused simdjson_inline error_code visit_empty_object(json_iterator &iter) noexcept; ++ ++ /** ++ * Called when a string, number, boolean or null is found. ++ */ ++ simdjson_warn_unused simdjson_inline error_code visit_primitive(json_iterator &iter, const uint8_t *value) noexcept; ++ /** ++ * Called when a string, number, boolean or null is found at the top level of a document (i.e. ++ * when there is no array or object and the entire document is a single string, number, boolean or ++ * null. ++ * ++ * This is separate from primitive() because simdjson's normal primitive parsing routines assume ++ * there is at least one more token after the value, which is only true in an array or object. ++ */ ++ simdjson_warn_unused simdjson_inline error_code visit_root_primitive(json_iterator &iter, const uint8_t *value) noexcept; ++ ++ simdjson_warn_unused simdjson_inline error_code visit_string(json_iterator &iter, const uint8_t *value, bool key = false) noexcept; ++ simdjson_warn_unused simdjson_inline error_code visit_number(json_iterator &iter, const uint8_t *value) noexcept; ++ simdjson_warn_unused simdjson_inline error_code visit_true_atom(json_iterator &iter, const uint8_t *value) noexcept; ++ simdjson_warn_unused simdjson_inline error_code visit_false_atom(json_iterator &iter, const uint8_t *value) noexcept; ++ simdjson_warn_unused simdjson_inline error_code visit_null_atom(json_iterator &iter, const uint8_t *value) noexcept; ++ ++ simdjson_warn_unused simdjson_inline error_code visit_root_string(json_iterator &iter, const uint8_t *value) noexcept; ++ simdjson_warn_unused simdjson_inline error_code visit_root_number(json_iterator &iter, const uint8_t *value) noexcept; ++ simdjson_warn_unused simdjson_inline error_code visit_root_true_atom(json_iterator &iter, const uint8_t *value) noexcept; ++ simdjson_warn_unused simdjson_inline error_code visit_root_false_atom(json_iterator &iter, const uint8_t *value) noexcept; ++ simdjson_warn_unused simdjson_inline error_code visit_root_null_atom(json_iterator &iter, const uint8_t *value) noexcept; ++ ++ /** Called each time a new field or element in an array or object is found. */ ++ simdjson_warn_unused simdjson_inline error_code increment_count(json_iterator &iter) noexcept; ++ ++ /** Next location to write to tape */ ++ tape_writer tape; ++private: ++ /** Next write location in the string buf for stage 2 parsing */ ++ uint8_t *current_string_buf_loc; ++ ++ simdjson_inline tape_builder(dom::document &doc) noexcept; ++ ++ simdjson_inline uint32_t next_tape_index(json_iterator &iter) const noexcept; ++ simdjson_inline void start_container(json_iterator &iter) noexcept; ++ simdjson_warn_unused simdjson_inline error_code end_container(json_iterator &iter, internal::tape_type start, internal::tape_type end) noexcept; ++ simdjson_warn_unused simdjson_inline error_code empty_container(json_iterator &iter, internal::tape_type start, internal::tape_type end) noexcept; ++ simdjson_inline uint8_t *on_start_string(json_iterator &iter) noexcept; ++ simdjson_inline void on_end_string(uint8_t *dst) noexcept; ++}; // class tape_builder ++ ++template ++simdjson_warn_unused simdjson_inline error_code tape_builder::parse_document( ++ dom_parser_implementation &dom_parser, ++ dom::document &doc) noexcept { ++ dom_parser.doc = &doc; ++ json_iterator iter(dom_parser, STREAMING ? dom_parser.next_structural_index : 0); ++ tape_builder builder(doc); ++ return iter.walk_document(builder); ++} ++ ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_root_primitive(json_iterator &iter, const uint8_t *value) noexcept { ++ return iter.visit_root_primitive(*this, value); ++} ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_primitive(json_iterator &iter, const uint8_t *value) noexcept { ++ return iter.visit_primitive(*this, value); ++} ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_empty_object(json_iterator &iter) noexcept { ++ return empty_container(iter, internal::tape_type::START_OBJECT, internal::tape_type::END_OBJECT); ++} ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_empty_array(json_iterator &iter) noexcept { ++ return empty_container(iter, internal::tape_type::START_ARRAY, internal::tape_type::END_ARRAY); ++} ++ ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_document_start(json_iterator &iter) noexcept { ++ start_container(iter); ++ return SUCCESS; ++} ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_object_start(json_iterator &iter) noexcept { ++ start_container(iter); ++ return SUCCESS; ++} ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_array_start(json_iterator &iter) noexcept { ++ start_container(iter); ++ return SUCCESS; ++} ++ ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_object_end(json_iterator &iter) noexcept { ++ return end_container(iter, internal::tape_type::START_OBJECT, internal::tape_type::END_OBJECT); ++} ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_array_end(json_iterator &iter) noexcept { ++ return end_container(iter, internal::tape_type::START_ARRAY, internal::tape_type::END_ARRAY); ++} ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_document_end(json_iterator &iter) noexcept { ++ constexpr uint32_t start_tape_index = 0; ++ tape.append(start_tape_index, internal::tape_type::ROOT); ++ tape_writer::write(iter.dom_parser.doc->tape[start_tape_index], next_tape_index(iter), internal::tape_type::ROOT); ++ return SUCCESS; ++} ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_key(json_iterator &iter, const uint8_t *key) noexcept { ++ return visit_string(iter, key, true); ++} ++ ++simdjson_warn_unused simdjson_inline error_code tape_builder::increment_count(json_iterator &iter) noexcept { ++ iter.dom_parser.open_containers[iter.depth].count++; // we have a key value pair in the object at parser.dom_parser.depth - 1 ++ return SUCCESS; ++} ++ ++simdjson_inline tape_builder::tape_builder(dom::document &doc) noexcept : tape{doc.tape.get()}, current_string_buf_loc{doc.string_buf.get()} {} ++ ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_string(json_iterator &iter, const uint8_t *value, bool key) noexcept { ++ iter.log_value(key ? "key" : "string"); ++ uint8_t *dst = on_start_string(iter); ++ dst = stringparsing::parse_string(value+1, dst); ++ if (dst == nullptr) { ++ iter.log_error("Invalid escape in string"); ++ return STRING_ERROR; ++ } ++ on_end_string(dst); ++ return SUCCESS; ++} ++ ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_root_string(json_iterator &iter, const uint8_t *value) noexcept { ++ return visit_string(iter, value); ++} ++ ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_number(json_iterator &iter, const uint8_t *value) noexcept { ++ iter.log_value("number"); ++ return numberparsing::parse_number(value, tape); ++} ++ ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_root_number(json_iterator &iter, const uint8_t *value) noexcept { ++ // ++ // We need to make a copy to make sure that the string is space terminated. ++ // This is not about padding the input, which should already padded up ++ // to len + SIMDJSON_PADDING. However, we have no control at this stage ++ // on how the padding was done. What if the input string was padded with nulls? ++ // It is quite common for an input string to have an extra null character (C string). ++ // We do not want to allow 9\0 (where \0 is the null character) inside a JSON ++ // document, but the string "9\0" by itself is fine. So we make a copy and ++ // pad the input with spaces when we know that there is just one input element. ++ // This copy is relatively expensive, but it will almost never be called in ++ // practice unless you are in the strange scenario where you have many JSON ++ // documents made of single atoms. ++ // ++ std::unique_ptrcopy(new (std::nothrow) uint8_t[iter.remaining_len() + SIMDJSON_PADDING]); ++ if (copy.get() == nullptr) { return MEMALLOC; } ++ std::memcpy(copy.get(), value, iter.remaining_len()); ++ std::memset(copy.get() + iter.remaining_len(), ' ', SIMDJSON_PADDING); ++ error_code error = visit_number(iter, copy.get()); ++ return error; ++} ++ ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_true_atom(json_iterator &iter, const uint8_t *value) noexcept { ++ iter.log_value("true"); ++ if (!atomparsing::is_valid_true_atom(value)) { return T_ATOM_ERROR; } ++ tape.append(0, internal::tape_type::TRUE_VALUE); ++ return SUCCESS; ++} ++ ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_root_true_atom(json_iterator &iter, const uint8_t *value) noexcept { ++ iter.log_value("true"); ++ if (!atomparsing::is_valid_true_atom(value, iter.remaining_len())) { return T_ATOM_ERROR; } ++ tape.append(0, internal::tape_type::TRUE_VALUE); ++ return SUCCESS; ++} ++ ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_false_atom(json_iterator &iter, const uint8_t *value) noexcept { ++ iter.log_value("false"); ++ if (!atomparsing::is_valid_false_atom(value)) { return F_ATOM_ERROR; } ++ tape.append(0, internal::tape_type::FALSE_VALUE); ++ return SUCCESS; ++} ++ ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_root_false_atom(json_iterator &iter, const uint8_t *value) noexcept { ++ iter.log_value("false"); ++ if (!atomparsing::is_valid_false_atom(value, iter.remaining_len())) { return F_ATOM_ERROR; } ++ tape.append(0, internal::tape_type::FALSE_VALUE); ++ return SUCCESS; ++} ++ ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_null_atom(json_iterator &iter, const uint8_t *value) noexcept { ++ iter.log_value("null"); ++ if (!atomparsing::is_valid_null_atom(value)) { return N_ATOM_ERROR; } ++ tape.append(0, internal::tape_type::NULL_VALUE); ++ return SUCCESS; ++} ++ ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_root_null_atom(json_iterator &iter, const uint8_t *value) noexcept { ++ iter.log_value("null"); ++ if (!atomparsing::is_valid_null_atom(value, iter.remaining_len())) { return N_ATOM_ERROR; } ++ tape.append(0, internal::tape_type::NULL_VALUE); ++ return SUCCESS; ++} ++ ++// private: ++ ++simdjson_inline uint32_t tape_builder::next_tape_index(json_iterator &iter) const noexcept { ++ return uint32_t(tape.next_tape_loc - iter.dom_parser.doc->tape.get()); ++} ++ ++simdjson_warn_unused simdjson_inline error_code tape_builder::empty_container(json_iterator &iter, internal::tape_type start, internal::tape_type end) noexcept { ++ auto start_index = next_tape_index(iter); ++ tape.append(start_index+2, start); ++ tape.append(start_index, end); ++ return SUCCESS; ++} ++ ++simdjson_inline void tape_builder::start_container(json_iterator &iter) noexcept { ++ iter.dom_parser.open_containers[iter.depth].tape_index = next_tape_index(iter); ++ iter.dom_parser.open_containers[iter.depth].count = 0; ++ tape.skip(); // We don't actually *write* the start element until the end. ++} ++ ++simdjson_warn_unused simdjson_inline error_code tape_builder::end_container(json_iterator &iter, internal::tape_type start, internal::tape_type end) noexcept { ++ // Write the ending tape element, pointing at the start location ++ const uint32_t start_tape_index = iter.dom_parser.open_containers[iter.depth].tape_index; ++ tape.append(start_tape_index, end); ++ // Write the start tape element, pointing at the end location (and including count) ++ // count can overflow if it exceeds 24 bits... so we saturate ++ // the convention being that a cnt of 0xffffff or more is undetermined in value (>= 0xffffff). ++ const uint32_t count = iter.dom_parser.open_containers[iter.depth].count; ++ const uint32_t cntsat = count > 0xFFFFFF ? 0xFFFFFF : count; ++ tape_writer::write(iter.dom_parser.doc->tape[start_tape_index], next_tape_index(iter) | (uint64_t(cntsat) << 32), start); ++ return SUCCESS; ++} ++ ++simdjson_inline uint8_t *tape_builder::on_start_string(json_iterator &iter) noexcept { ++ // we advance the point, accounting for the fact that we have a NULL termination ++ tape.append(current_string_buf_loc - iter.dom_parser.doc->string_buf.get(), internal::tape_type::STRING); ++ return current_string_buf_loc + sizeof(uint32_t); ++} ++ ++simdjson_inline void tape_builder::on_end_string(uint8_t *dst) noexcept { ++ uint32_t str_length = uint32_t(dst - (current_string_buf_loc + sizeof(uint32_t))); ++ // TODO check for overflow in case someone has a crazy string (>=4GB?) ++ // But only add the overflow check when the document itself exceeds 4GB ++ // Currently unneeded because we refuse to parse docs larger or equal to 4GB. ++ memcpy(current_string_buf_loc, &str_length, sizeof(uint32_t)); ++ // NULL termination is still handy if you expect all your strings to ++ // be NULL terminated? It comes at a small cost ++ *dst = 0; ++ current_string_buf_loc = dst + 1; ++} ++ ++} // namespace stage2 ++} // unnamed namespace ++} // namespace fallback ++} // namespace simdjson ++/* end file src/generic/stage2/tape_builder.h */ ++ ++namespace simdjson { ++namespace fallback { ++ ++simdjson_warn_unused error_code dom_parser_implementation::stage2(dom::document &_doc) noexcept { ++ return stage2::tape_builder::parse_document(*this, _doc); ++} ++ ++simdjson_warn_unused error_code dom_parser_implementation::stage2_next(dom::document &_doc) noexcept { ++ return stage2::tape_builder::parse_document(*this, _doc); ++} ++ ++simdjson_warn_unused uint8_t *dom_parser_implementation::parse_string(const uint8_t *src, uint8_t *dst) const noexcept { ++ return fallback::stringparsing::parse_string(src, dst); ++} ++ ++simdjson_warn_unused error_code dom_parser_implementation::parse(const uint8_t *_buf, size_t _len, dom::document &_doc) noexcept { ++ auto error = stage1(_buf, _len, stage1_mode::regular); ++ if (error) { return error; } ++ return stage2(_doc); ++} ++ ++} // namespace fallback ++} // namespace simdjson ++ ++/* begin file include/simdjson/fallback/end.h */ ++/* end file include/simdjson/fallback/end.h */ ++/* end file src/fallback/dom_parser_implementation.cpp */ ++#endif ++#if SIMDJSON_IMPLEMENTATION_ICELAKE ++/* begin file src/icelake/implementation.cpp */ ++/* begin file include/simdjson/icelake/begin.h */ ++// redefining SIMDJSON_IMPLEMENTATION to "icelake" ++// #define SIMDJSON_IMPLEMENTATION icelake ++SIMDJSON_TARGET_ICELAKE ++/* end file include/simdjson/icelake/begin.h */ ++ ++namespace simdjson { ++namespace icelake { ++ ++simdjson_warn_unused error_code implementation::create_dom_parser_implementation( ++ size_t capacity, ++ size_t max_depth, ++ std::unique_ptr& dst ++) const noexcept { ++ dst.reset( new (std::nothrow) dom_parser_implementation() ); ++ if (!dst) { return MEMALLOC; } ++ if (auto err = dst->set_capacity(capacity)) ++ return err; ++ if (auto err = dst->set_max_depth(max_depth)) ++ return err; ++ return SUCCESS; ++} ++ ++} // namespace icelake ++} // namespace simdjson ++ ++/* begin file include/simdjson/icelake/end.h */ ++SIMDJSON_UNTARGET_ICELAKE ++/* end file include/simdjson/icelake/end.h */ ++ ++/* end file src/icelake/implementation.cpp */ ++/* begin file src/icelake/dom_parser_implementation.cpp */ ++/* begin file include/simdjson/icelake/begin.h */ ++// redefining SIMDJSON_IMPLEMENTATION to "icelake" ++// #define SIMDJSON_IMPLEMENTATION icelake ++SIMDJSON_TARGET_ICELAKE ++/* end file include/simdjson/icelake/begin.h */ ++ ++// ++// Stage 1 ++// ++ ++namespace simdjson { ++namespace icelake { ++namespace { ++ ++using namespace simd; ++ ++struct json_character_block { ++ static simdjson_inline json_character_block classify(const simd::simd8x64& in); ++ // ASCII white-space ('\r','\n','\t',' ') ++ simdjson_inline uint64_t whitespace() const noexcept; ++ // non-quote structural characters (comma, colon, braces, brackets) ++ simdjson_inline uint64_t op() const noexcept; ++ // neither a structural character nor a white-space, so letters, numbers and quotes ++ simdjson_inline uint64_t scalar() const noexcept; ++ ++ uint64_t _whitespace; // ASCII white-space ('\r','\n','\t',' ') ++ uint64_t _op; // structural characters (comma, colon, braces, brackets but not quotes) ++}; ++ ++simdjson_inline uint64_t json_character_block::whitespace() const noexcept { return _whitespace; } ++simdjson_inline uint64_t json_character_block::op() const noexcept { return _op; } ++simdjson_inline uint64_t json_character_block::scalar() const noexcept { return ~(op() | whitespace()); } ++ ++// This identifies structural characters (comma, colon, braces, brackets), ++// and ASCII white-space ('\r','\n','\t',' '). ++simdjson_inline json_character_block json_character_block::classify(const simd::simd8x64& in) { ++ // These lookups rely on the fact that anything < 127 will match the lower 4 bits, which is why ++ // we can't use the generic lookup_16. ++ const auto whitespace_table = simd8::repeat_16(' ', 100, 100, 100, 17, 100, 113, 2, 100, '\t', '\n', 112, 100, '\r', 100, 100); ++ ++ // The 6 operators (:,[]{}) have these values: ++ // ++ // , 2C ++ // : 3A ++ // [ 5B ++ // { 7B ++ // ] 5D ++ // } 7D ++ // ++ // If you use | 0x20 to turn [ and ] into { and }, the lower 4 bits of each character is unique. ++ // We exploit this, using a simd 4-bit lookup to tell us which character match against, and then ++ // match it (against | 0x20). ++ // ++ // To prevent recognizing other characters, everything else gets compared with 0, which cannot ++ // match due to the | 0x20. ++ // ++ // NOTE: Due to the | 0x20, this ALSO treats and (control characters 0C and 1A) like , ++ // and :. This gets caught in stage 2, which checks the actual character to ensure the right ++ // operators are in the right places. ++ const auto op_table = simd8::repeat_16( ++ 0, 0, 0, 0, ++ 0, 0, 0, 0, ++ 0, 0, ':', '{', // : = 3A, [ = 5B, { = 7B ++ ',', '}', 0, 0 // , = 2C, ] = 5D, } = 7D ++ ); ++ ++ // We compute whitespace and op separately. If later code only uses one or the ++ // other, given the fact that all functions are aggressively inlined, we can ++ // hope that useless computations will be omitted. This is namely case when ++ // minifying (we only need whitespace). ++ ++ const uint64_t whitespace = in.eq({ ++ _mm512_shuffle_epi8(whitespace_table, in.chunks[0]) ++ }); ++ // Turn [ and ] into { and } ++ const simd8x64 curlified{ ++ in.chunks[0] | 0x20 ++ }; ++ const uint64_t op = curlified.eq({ ++ _mm512_shuffle_epi8(op_table, in.chunks[0]) ++ }); ++ ++ return { whitespace, op }; ++} ++ ++simdjson_inline bool is_ascii(const simd8x64& input) { ++ return input.reduce_or().is_ascii(); ++} ++ ++simdjson_unused simdjson_inline simd8 must_be_continuation(const simd8 prev1, const simd8 prev2, const simd8 prev3) { ++ simd8 is_second_byte = prev1.saturating_sub(0xc0u-1); // Only 11______ will be > 0 ++ simd8 is_third_byte = prev2.saturating_sub(0xe0u-1); // Only 111_____ will be > 0 ++ simd8 is_fourth_byte = prev3.saturating_sub(0xf0u-1); // Only 1111____ will be > 0 ++ // Caller requires a bool (all 1's). All values resulting from the subtraction will be <= 64, so signed comparison is fine. ++ return simd8(is_second_byte | is_third_byte | is_fourth_byte) > int8_t(0); ++} ++ ++simdjson_inline simd8 must_be_2_3_continuation(const simd8 prev2, const simd8 prev3) { ++ simd8 is_third_byte = prev2.saturating_sub(0xe0u-1); // Only 111_____ will be > 0 ++ simd8 is_fourth_byte = prev3.saturating_sub(0xf0u-1); // Only 1111____ will be > 0 ++ // Caller requires a bool (all 1's). All values resulting from the subtraction will be <= 64, so signed comparison is fine. ++ return simd8(is_third_byte | is_fourth_byte) > int8_t(0); ++} ++ ++} // unnamed namespace ++} // namespace icelake ++} // namespace simdjson ++ ++/* begin file src/generic/stage1/utf8_lookup4_algorithm.h */ ++namespace simdjson { ++namespace icelake { ++namespace { ++namespace utf8_validation { ++ ++using namespace simd; ++ ++ simdjson_inline simd8 check_special_cases(const simd8 input, const simd8 prev1) { ++// Bit 0 = Too Short (lead byte/ASCII followed by lead byte/ASCII) ++// Bit 1 = Too Long (ASCII followed by continuation) ++// Bit 2 = Overlong 3-byte ++// Bit 4 = Surrogate ++// Bit 5 = Overlong 2-byte ++// Bit 7 = Two Continuations ++ constexpr const uint8_t TOO_SHORT = 1<<0; // 11______ 0_______ ++ // 11______ 11______ ++ constexpr const uint8_t TOO_LONG = 1<<1; // 0_______ 10______ ++ constexpr const uint8_t OVERLONG_3 = 1<<2; // 11100000 100_____ ++ constexpr const uint8_t SURROGATE = 1<<4; // 11101101 101_____ ++ constexpr const uint8_t OVERLONG_2 = 1<<5; // 1100000_ 10______ ++ constexpr const uint8_t TWO_CONTS = 1<<7; // 10______ 10______ ++ constexpr const uint8_t TOO_LARGE = 1<<3; // 11110100 1001____ ++ // 11110100 101_____ ++ // 11110101 1001____ ++ // 11110101 101_____ ++ // 1111011_ 1001____ ++ // 1111011_ 101_____ ++ // 11111___ 1001____ ++ // 11111___ 101_____ ++ constexpr const uint8_t TOO_LARGE_1000 = 1<<6; ++ // 11110101 1000____ ++ // 1111011_ 1000____ ++ // 11111___ 1000____ ++ constexpr const uint8_t OVERLONG_4 = 1<<6; // 11110000 1000____ ++ ++ const simd8 byte_1_high = prev1.shr<4>().lookup_16( ++ // 0_______ ________ ++ TOO_LONG, TOO_LONG, TOO_LONG, TOO_LONG, ++ TOO_LONG, TOO_LONG, TOO_LONG, TOO_LONG, ++ // 10______ ________ ++ TWO_CONTS, TWO_CONTS, TWO_CONTS, TWO_CONTS, ++ // 1100____ ________ ++ TOO_SHORT | OVERLONG_2, ++ // 1101____ ________ ++ TOO_SHORT, ++ // 1110____ ________ ++ TOO_SHORT | OVERLONG_3 | SURROGATE, ++ // 1111____ ________ ++ TOO_SHORT | TOO_LARGE | TOO_LARGE_1000 | OVERLONG_4 ++ ); ++ constexpr const uint8_t CARRY = TOO_SHORT | TOO_LONG | TWO_CONTS; // These all have ____ in byte 1 . ++ const simd8 byte_1_low = (prev1 & 0x0F).lookup_16( ++ // ____0000 ________ ++ CARRY | OVERLONG_3 | OVERLONG_2 | OVERLONG_4, ++ // ____0001 ________ ++ CARRY | OVERLONG_2, ++ // ____001_ ________ ++ CARRY, ++ CARRY, ++ ++ // ____0100 ________ ++ CARRY | TOO_LARGE, ++ // ____0101 ________ ++ CARRY | TOO_LARGE | TOO_LARGE_1000, ++ // ____011_ ________ ++ CARRY | TOO_LARGE | TOO_LARGE_1000, ++ CARRY | TOO_LARGE | TOO_LARGE_1000, ++ ++ // ____1___ ________ ++ CARRY | TOO_LARGE | TOO_LARGE_1000, ++ CARRY | TOO_LARGE | TOO_LARGE_1000, ++ CARRY | TOO_LARGE | TOO_LARGE_1000, ++ CARRY | TOO_LARGE | TOO_LARGE_1000, ++ CARRY | TOO_LARGE | TOO_LARGE_1000, ++ // ____1101 ________ ++ CARRY | TOO_LARGE | TOO_LARGE_1000 | SURROGATE, ++ CARRY | TOO_LARGE | TOO_LARGE_1000, ++ CARRY | TOO_LARGE | TOO_LARGE_1000 ++ ); ++ const simd8 byte_2_high = input.shr<4>().lookup_16( ++ // ________ 0_______ ++ TOO_SHORT, TOO_SHORT, TOO_SHORT, TOO_SHORT, ++ TOO_SHORT, TOO_SHORT, TOO_SHORT, TOO_SHORT, ++ ++ // ________ 1000____ ++ TOO_LONG | OVERLONG_2 | TWO_CONTS | OVERLONG_3 | TOO_LARGE_1000 | OVERLONG_4, ++ // ________ 1001____ ++ TOO_LONG | OVERLONG_2 | TWO_CONTS | OVERLONG_3 | TOO_LARGE, ++ // ________ 101_____ ++ TOO_LONG | OVERLONG_2 | TWO_CONTS | SURROGATE | TOO_LARGE, ++ TOO_LONG | OVERLONG_2 | TWO_CONTS | SURROGATE | TOO_LARGE, ++ ++ // ________ 11______ ++ TOO_SHORT, TOO_SHORT, TOO_SHORT, TOO_SHORT ++ ); ++ return (byte_1_high & byte_1_low & byte_2_high); ++ } ++ simdjson_inline simd8 check_multibyte_lengths(const simd8 input, ++ const simd8 prev_input, const simd8 sc) { ++ simd8 prev2 = input.prev<2>(prev_input); ++ simd8 prev3 = input.prev<3>(prev_input); ++ simd8 must23 = simd8(must_be_2_3_continuation(prev2, prev3)); ++ simd8 must23_80 = must23 & uint8_t(0x80); ++ return must23_80 ^ sc; ++ } ++ ++ // ++ // Return nonzero if there are incomplete multibyte characters at the end of the block: ++ // e.g. if there is a 4-byte character, but it's 3 bytes from the end. ++ // ++ simdjson_inline simd8 is_incomplete(const simd8 input) { ++ // If the previous input's last 3 bytes match this, they're too short (they ended at EOF): ++ // ... 1111____ 111_____ 11______ ++#if SIMDJSON_IMPLEMENTATION_ICELAKE ++ static const uint8_t max_array[64] = { ++ 255, 255, 255, 255, 255, 255, 255, 255, ++ 255, 255, 255, 255, 255, 255, 255, 255, ++ 255, 255, 255, 255, 255, 255, 255, 255, ++ 255, 255, 255, 255, 255, 255, 255, 255, ++ 255, 255, 255, 255, 255, 255, 255, 255, ++ 255, 255, 255, 255, 255, 255, 255, 255, ++ 255, 255, 255, 255, 255, 255, 255, 255, ++ 255, 255, 255, 255, 255, 0xf0u-1, 0xe0u-1, 0xc0u-1 ++ }; ++#else ++ static const uint8_t max_array[32] = { ++ 255, 255, 255, 255, 255, 255, 255, 255, ++ 255, 255, 255, 255, 255, 255, 255, 255, ++ 255, 255, 255, 255, 255, 255, 255, 255, ++ 255, 255, 255, 255, 255, 0xf0u-1, 0xe0u-1, 0xc0u-1 ++ }; ++#endif ++ const simd8 max_value(&max_array[sizeof(max_array)-sizeof(simd8)]); ++ return input.gt_bits(max_value); ++ } ++ ++ struct utf8_checker { ++ // If this is nonzero, there has been a UTF-8 error. ++ simd8 error; ++ // The last input we received ++ simd8 prev_input_block; ++ // Whether the last input we received was incomplete (used for ASCII fast path) ++ simd8 prev_incomplete; ++ ++ // ++ // Check whether the current bytes are valid UTF-8. ++ // ++ simdjson_inline void check_utf8_bytes(const simd8 input, const simd8 prev_input) { ++ // Flip prev1...prev3 so we can easily determine if they are 2+, 3+ or 4+ lead bytes ++ // (2, 3, 4-byte leads become large positive numbers instead of small negative numbers) ++ simd8 prev1 = input.prev<1>(prev_input); ++ simd8 sc = check_special_cases(input, prev1); ++ this->error |= check_multibyte_lengths(input, prev_input, sc); ++ } ++ ++ // The only problem that can happen at EOF is that a multibyte character is too short ++ // or a byte value too large in the last bytes: check_special_cases only checks for bytes ++ // too large in the first of two bytes. ++ simdjson_inline void check_eof() { ++ // If the previous block had incomplete UTF-8 characters at the end, an ASCII block can't ++ // possibly finish them. ++ this->error |= this->prev_incomplete; ++ } ++ ++#ifndef SIMDJSON_IF_CONSTEXPR ++#if SIMDJSON_CPLUSPLUS17 ++#define SIMDJSON_IF_CONSTEXPR if constexpr ++#else ++#define SIMDJSON_IF_CONSTEXPR if ++#endif ++#endif ++ ++ simdjson_inline void check_next_input(const simd8x64& input) { ++ if(simdjson_likely(is_ascii(input))) { ++ this->error |= this->prev_incomplete; ++ } else { ++ // you might think that a for-loop would work, but under Visual Studio, it is not good enough. ++ static_assert((simd8x64::NUM_CHUNKS == 1) ++ ||(simd8x64::NUM_CHUNKS == 2) ++ || (simd8x64::NUM_CHUNKS == 4), ++ "We support one, two or four chunks per 64-byte block."); ++ SIMDJSON_IF_CONSTEXPR (simd8x64::NUM_CHUNKS == 1) { ++ this->check_utf8_bytes(input.chunks[0], this->prev_input_block); ++ } else SIMDJSON_IF_CONSTEXPR (simd8x64::NUM_CHUNKS == 2) { ++ this->check_utf8_bytes(input.chunks[0], this->prev_input_block); ++ this->check_utf8_bytes(input.chunks[1], input.chunks[0]); ++ } else SIMDJSON_IF_CONSTEXPR (simd8x64::NUM_CHUNKS == 4) { ++ this->check_utf8_bytes(input.chunks[0], this->prev_input_block); ++ this->check_utf8_bytes(input.chunks[1], input.chunks[0]); ++ this->check_utf8_bytes(input.chunks[2], input.chunks[1]); ++ this->check_utf8_bytes(input.chunks[3], input.chunks[2]); ++ } ++ this->prev_incomplete = is_incomplete(input.chunks[simd8x64::NUM_CHUNKS-1]); ++ this->prev_input_block = input.chunks[simd8x64::NUM_CHUNKS-1]; ++ } ++ } ++ // do not forget to call check_eof! ++ simdjson_inline error_code errors() { ++ return this->error.any_bits_set_anywhere() ? error_code::UTF8_ERROR : error_code::SUCCESS; ++ } ++ ++ }; // struct utf8_checker ++} // namespace utf8_validation ++ ++using utf8_validation::utf8_checker; ++ ++} // unnamed namespace ++} // namespace icelake ++} // namespace simdjson ++/* end file src/generic/stage1/utf8_lookup4_algorithm.h */ ++// defining SIMDJSON_CUSTOM_BIT_INDEXER allows us to provide our own bit_indexer::write ++#define SIMDJSON_CUSTOM_BIT_INDEXER ++/* begin file src/generic/stage1/json_structural_indexer.h */ ++// This file contains the common code every implementation uses in stage1 ++// It is intended to be included multiple times and compiled multiple times ++// We assume the file in which it is included already includes ++// "simdjson/stage1.h" (this simplifies amalgation) ++ ++/* begin file src/generic/stage1/buf_block_reader.h */ ++namespace simdjson { ++namespace icelake { ++namespace { ++ ++// Walks through a buffer in block-sized increments, loading the last part with spaces ++template ++struct buf_block_reader { ++public: ++ simdjson_inline buf_block_reader(const uint8_t *_buf, size_t _len); ++ simdjson_inline size_t block_index(); ++ simdjson_inline bool has_full_block() const; ++ simdjson_inline const uint8_t *full_block() const; ++ /** ++ * Get the last block, padded with spaces. ++ * ++ * There will always be a last block, with at least 1 byte, unless len == 0 (in which case this ++ * function fills the buffer with spaces and returns 0. In particular, if len == STEP_SIZE there ++ * will be 0 full_blocks and 1 remainder block with STEP_SIZE bytes and no spaces for padding. ++ * ++ * @return the number of effective characters in the last block. ++ */ ++ simdjson_inline size_t get_remainder(uint8_t *dst) const; ++ simdjson_inline void advance(); ++private: ++ const uint8_t *buf; ++ const size_t len; ++ const size_t lenminusstep; ++ size_t idx; ++}; ++ ++// Routines to print masks and text for debugging bitmask operations ++simdjson_unused static char * format_input_text_64(const uint8_t *text) { ++ static char buf[sizeof(simd8x64) + 1]; ++ for (size_t i=0; i); i++) { ++ buf[i] = int8_t(text[i]) < ' ' ? '_' : int8_t(text[i]); ++ } ++ buf[sizeof(simd8x64)] = '\0'; ++ return buf; ++} ++ ++// Routines to print masks and text for debugging bitmask operations ++simdjson_unused static char * format_input_text(const simd8x64& in) { ++ static char buf[sizeof(simd8x64) + 1]; ++ in.store(reinterpret_cast(buf)); ++ for (size_t i=0; i); i++) { ++ if (buf[i] < ' ') { buf[i] = '_'; } ++ } ++ buf[sizeof(simd8x64)] = '\0'; ++ return buf; ++} ++ ++simdjson_unused static char * format_mask(uint64_t mask) { ++ static char buf[sizeof(simd8x64) + 1]; ++ for (size_t i=0; i<64; i++) { ++ buf[i] = (mask & (size_t(1) << i)) ? 'X' : ' '; ++ } ++ buf[64] = '\0'; ++ return buf; ++} ++ ++template ++simdjson_inline buf_block_reader::buf_block_reader(const uint8_t *_buf, size_t _len) : buf{_buf}, len{_len}, lenminusstep{len < STEP_SIZE ? 0 : len - STEP_SIZE}, idx{0} {} ++ ++template ++simdjson_inline size_t buf_block_reader::block_index() { return idx; } ++ ++template ++simdjson_inline bool buf_block_reader::has_full_block() const { ++ return idx < lenminusstep; ++} ++ ++template ++simdjson_inline const uint8_t *buf_block_reader::full_block() const { ++ return &buf[idx]; ++} ++ ++template ++simdjson_inline size_t buf_block_reader::get_remainder(uint8_t *dst) const { ++ if(len == idx) { return 0; } // memcpy(dst, null, 0) will trigger an error with some sanitizers ++ std::memset(dst, 0x20, STEP_SIZE); // std::memset STEP_SIZE because it's more efficient to write out 8 or 16 bytes at once. ++ std::memcpy(dst, buf + idx, len - idx); ++ return len - idx; ++} ++ ++template ++simdjson_inline void buf_block_reader::advance() { ++ idx += STEP_SIZE; ++} ++ ++} // unnamed namespace ++} // namespace icelake ++} // namespace simdjson ++/* end file src/generic/stage1/buf_block_reader.h */ ++/* begin file src/generic/stage1/json_string_scanner.h */ ++namespace simdjson { ++namespace icelake { ++namespace { ++namespace stage1 { ++ ++struct json_string_block { ++ // We spell out the constructors in the hope of resolving inlining issues with Visual Studio 2017 ++ simdjson_inline json_string_block(uint64_t backslash, uint64_t escaped, uint64_t quote, uint64_t in_string) : ++ _backslash(backslash), _escaped(escaped), _quote(quote), _in_string(in_string) {} ++ ++ // Escaped characters (characters following an escape() character) ++ simdjson_inline uint64_t escaped() const { return _escaped; } ++ // Escape characters (backslashes that are not escaped--i.e. in \\, includes only the first \) ++ simdjson_inline uint64_t escape() const { return _backslash & ~_escaped; } ++ // Real (non-backslashed) quotes ++ simdjson_inline uint64_t quote() const { return _quote; } ++ // Start quotes of strings ++ simdjson_inline uint64_t string_start() const { return _quote & _in_string; } ++ // End quotes of strings ++ simdjson_inline uint64_t string_end() const { return _quote & ~_in_string; } ++ // Only characters inside the string (not including the quotes) ++ simdjson_inline uint64_t string_content() const { return _in_string & ~_quote; } ++ // Return a mask of whether the given characters are inside a string (only works on non-quotes) ++ simdjson_inline uint64_t non_quote_inside_string(uint64_t mask) const { return mask & _in_string; } ++ // Return a mask of whether the given characters are inside a string (only works on non-quotes) ++ simdjson_inline uint64_t non_quote_outside_string(uint64_t mask) const { return mask & ~_in_string; } ++ // Tail of string (everything except the start quote) ++ simdjson_inline uint64_t string_tail() const { return _in_string ^ _quote; } ++ ++ // backslash characters ++ uint64_t _backslash; ++ // escaped characters (backslashed--does not include the hex characters after \u) ++ uint64_t _escaped; ++ // real quotes (non-backslashed ones) ++ uint64_t _quote; ++ // string characters (includes start quote but not end quote) ++ uint64_t _in_string; ++}; ++ ++// Scans blocks for string characters, storing the state necessary to do so ++class json_string_scanner { ++public: ++ simdjson_inline json_string_block next(const simd::simd8x64& in); ++ // Returns either UNCLOSED_STRING or SUCCESS ++ simdjson_inline error_code finish(); ++ ++private: ++ // Intended to be defined by the implementation ++ simdjson_inline uint64_t find_escaped(uint64_t escape); ++ simdjson_inline uint64_t find_escaped_branchless(uint64_t escape); ++ ++ // Whether the last iteration was still inside a string (all 1's = true, all 0's = false). ++ uint64_t prev_in_string = 0ULL; ++ // Whether the first character of the next iteration is escaped. ++ uint64_t prev_escaped = 0ULL; ++}; ++ ++// ++// Finds escaped characters (characters following \). ++// ++// Handles runs of backslashes like \\\" and \\\\" correctly (yielding 0101 and 01010, respectively). ++// ++// Does this by: ++// - Shift the escape mask to get potentially escaped characters (characters after backslashes). ++// - Mask escaped sequences that start on *even* bits with 1010101010 (odd bits are escaped, even bits are not) ++// - Mask escaped sequences that start on *odd* bits with 0101010101 (even bits are escaped, odd bits are not) ++// ++// To distinguish between escaped sequences starting on even/odd bits, it finds the start of all ++// escape sequences, filters out the ones that start on even bits, and adds that to the mask of ++// escape sequences. This causes the addition to clear out the sequences starting on odd bits (since ++// the start bit causes a carry), and leaves even-bit sequences alone. ++// ++// Example: ++// ++// text | \\\ | \\\"\\\" \\\" \\"\\" | ++// escape | xxx | xx xxx xxx xx xx | Removed overflow backslash; will | it into follows_escape ++// odd_starts | x | x x x | escape & ~even_bits & ~follows_escape ++// even_seq | c| cxxx c xx c | c = carry bit -- will be masked out later ++// invert_mask | | cxxx c xx c| even_seq << 1 ++// follows_escape | xx | x xx xxx xxx xx xx | Includes overflow bit ++// escaped | x | x x x x x x x x | ++// desired | x | x x x x x x x x | ++// text | \\\ | \\\"\\\" \\\" \\"\\" | ++// ++simdjson_inline uint64_t json_string_scanner::find_escaped_branchless(uint64_t backslash) { ++ // If there was overflow, pretend the first character isn't a backslash ++ backslash &= ~prev_escaped; ++ uint64_t follows_escape = backslash << 1 | prev_escaped; ++ ++ // Get sequences starting on even bits by clearing out the odd series using + ++ const uint64_t even_bits = 0x5555555555555555ULL; ++ uint64_t odd_sequence_starts = backslash & ~even_bits & ~follows_escape; ++ uint64_t sequences_starting_on_even_bits; ++ prev_escaped = add_overflow(odd_sequence_starts, backslash, &sequences_starting_on_even_bits); ++ uint64_t invert_mask = sequences_starting_on_even_bits << 1; // The mask we want to return is the *escaped* bits, not escapes. ++ ++ // Mask every other backslashed character as an escaped character ++ // Flip the mask for sequences that start on even bits, to correct them ++ return (even_bits ^ invert_mask) & follows_escape; ++} ++ ++// ++// Return a mask of all string characters plus end quotes. ++// ++// prev_escaped is overflow saying whether the next character is escaped. ++// prev_in_string is overflow saying whether we're still in a string. ++// ++// Backslash sequences outside of quotes will be detected in stage 2. ++// ++simdjson_inline json_string_block json_string_scanner::next(const simd::simd8x64& in) { ++ const uint64_t backslash = in.eq('\\'); ++ const uint64_t escaped = find_escaped(backslash); ++ const uint64_t quote = in.eq('"') & ~escaped; ++ ++ // ++ // prefix_xor flips on bits inside the string (and flips off the end quote). ++ // ++ // Then we xor with prev_in_string: if we were in a string already, its effect is flipped ++ // (characters inside strings are outside, and characters outside strings are inside). ++ // ++ const uint64_t in_string = prefix_xor(quote) ^ prev_in_string; ++ ++ // ++ // Check if we're still in a string at the end of the box so the next block will know ++ // ++ // right shift of a signed value expected to be well-defined and standard ++ // compliant as of C++20, John Regher from Utah U. says this is fine code ++ // ++ prev_in_string = uint64_t(static_cast(in_string) >> 63); ++ ++ // Use ^ to turn the beginning quote off, and the end quote on. ++ ++ // We are returning a function-local object so either we get a move constructor ++ // or we get copy elision. ++ return json_string_block( ++ backslash, ++ escaped, ++ quote, ++ in_string ++ ); ++} ++ ++simdjson_inline error_code json_string_scanner::finish() { ++ if (prev_in_string) { ++ return UNCLOSED_STRING; ++ } ++ return SUCCESS; ++} ++ ++} // namespace stage1 ++} // unnamed namespace ++} // namespace icelake ++} // namespace simdjson ++/* end file src/generic/stage1/json_string_scanner.h */ ++/* begin file src/generic/stage1/json_scanner.h */ ++namespace simdjson { ++namespace icelake { ++namespace { ++namespace stage1 { ++ ++/** ++ * A block of scanned json, with information on operators and scalars. ++ * ++ * We seek to identify pseudo-structural characters. Anything that is inside ++ * a string must be omitted (hence & ~_string.string_tail()). ++ * Otherwise, pseudo-structural characters come in two forms. ++ * 1. We have the structural characters ([,],{,},:, comma). The ++ * term 'structural character' is from the JSON RFC. ++ * 2. We have the 'scalar pseudo-structural characters'. ++ * Scalars are quotes, and any character except structural characters and white space. ++ * ++ * To identify the scalar pseudo-structural characters, we must look at what comes ++ * before them: it must be a space, a quote or a structural characters. ++ * Starting with simdjson v0.3, we identify them by ++ * negation: we identify everything that is followed by a non-quote scalar, ++ * and we negate that. Whatever remains must be a 'scalar pseudo-structural character'. ++ */ ++struct json_block { ++public: ++ // We spell out the constructors in the hope of resolving inlining issues with Visual Studio 2017 ++ simdjson_inline json_block(json_string_block&& string, json_character_block characters, uint64_t follows_potential_nonquote_scalar) : ++ _string(std::move(string)), _characters(characters), _follows_potential_nonquote_scalar(follows_potential_nonquote_scalar) {} ++ simdjson_inline json_block(json_string_block string, json_character_block characters, uint64_t follows_potential_nonquote_scalar) : ++ _string(string), _characters(characters), _follows_potential_nonquote_scalar(follows_potential_nonquote_scalar) {} ++ ++ /** ++ * The start of structurals. ++ * In simdjson prior to v0.3, these were called the pseudo-structural characters. ++ **/ ++ simdjson_inline uint64_t structural_start() const noexcept { return potential_structural_start() & ~_string.string_tail(); } ++ /** All JSON whitespace (i.e. not in a string) */ ++ simdjson_inline uint64_t whitespace() const noexcept { return non_quote_outside_string(_characters.whitespace()); } ++ ++ // Helpers ++ ++ /** Whether the given characters are inside a string (only works on non-quotes) */ ++ simdjson_inline uint64_t non_quote_inside_string(uint64_t mask) const noexcept { return _string.non_quote_inside_string(mask); } ++ /** Whether the given characters are outside a string (only works on non-quotes) */ ++ simdjson_inline uint64_t non_quote_outside_string(uint64_t mask) const noexcept { return _string.non_quote_outside_string(mask); } ++ ++ // string and escape characters ++ json_string_block _string; ++ // whitespace, structural characters ('operators'), scalars ++ json_character_block _characters; ++ // whether the previous character was a scalar ++ uint64_t _follows_potential_nonquote_scalar; ++private: ++ // Potential structurals (i.e. disregarding strings) ++ ++ /** ++ * structural elements ([,],{,},:, comma) plus scalar starts like 123, true and "abc". ++ * They may reside inside a string. ++ **/ ++ simdjson_inline uint64_t potential_structural_start() const noexcept { return _characters.op() | potential_scalar_start(); } ++ /** ++ * The start of non-operator runs, like 123, true and "abc". ++ * It main reside inside a string. ++ **/ ++ simdjson_inline uint64_t potential_scalar_start() const noexcept { ++ // The term "scalar" refers to anything except structural characters and white space ++ // (so letters, numbers, quotes). ++ // Whenever it is preceded by something that is not a structural element ({,},[,],:, ") nor a white-space ++ // then we know that it is irrelevant structurally. ++ return _characters.scalar() & ~follows_potential_scalar(); ++ } ++ /** ++ * Whether the given character is immediately after a non-operator like 123, true. ++ * The characters following a quote are not included. ++ */ ++ simdjson_inline uint64_t follows_potential_scalar() const noexcept { ++ // _follows_potential_nonquote_scalar: is defined as marking any character that follows a character ++ // that is not a structural element ({,},[,],:, comma) nor a quote (") and that is not a ++ // white space. ++ // It is understood that within quoted region, anything at all could be marked (irrelevant). ++ return _follows_potential_nonquote_scalar; ++ } ++}; ++ ++/** ++ * Scans JSON for important bits: structural characters or 'operators', strings, and scalars. ++ * ++ * The scanner starts by calculating two distinct things: ++ * - string characters (taking \" into account) ++ * - structural characters or 'operators' ([]{},:, comma) ++ * and scalars (runs of non-operators like 123, true and "abc") ++ * ++ * To minimize data dependency (a key component of the scanner's speed), it finds these in parallel: ++ * in particular, the operator/scalar bit will find plenty of things that are actually part of ++ * strings. When we're done, json_block will fuse the two together by masking out tokens that are ++ * part of a string. ++ */ ++class json_scanner { ++public: ++ json_scanner() = default; ++ simdjson_inline json_block next(const simd::simd8x64& in); ++ // Returns either UNCLOSED_STRING or SUCCESS ++ simdjson_inline error_code finish(); ++ ++private: ++ // Whether the last character of the previous iteration is part of a scalar token ++ // (anything except whitespace or a structural character/'operator'). ++ uint64_t prev_scalar = 0ULL; ++ json_string_scanner string_scanner{}; ++}; ++ ++ ++// ++// Check if the current character immediately follows a matching character. ++// ++// For example, this checks for quotes with backslashes in front of them: ++// ++// const uint64_t backslashed_quote = in.eq('"') & immediately_follows(in.eq('\'), prev_backslash); ++// ++simdjson_inline uint64_t follows(const uint64_t match, uint64_t &overflow) { ++ const uint64_t result = match << 1 | overflow; ++ overflow = match >> 63; ++ return result; ++} ++ ++simdjson_inline json_block json_scanner::next(const simd::simd8x64& in) { ++ json_string_block strings = string_scanner.next(in); ++ // identifies the white-space and the structural characters ++ json_character_block characters = json_character_block::classify(in); ++ // The term "scalar" refers to anything except structural characters and white space ++ // (so letters, numbers, quotes). ++ // We want follows_scalar to mark anything that follows a non-quote scalar (so letters and numbers). ++ // ++ // A terminal quote should either be followed by a structural character (comma, brace, bracket, colon) ++ // or nothing. However, we still want ' "a string"true ' to mark the 't' of 'true' as a potential ++ // pseudo-structural character just like we would if we had ' "a string" true '; otherwise we ++ // may need to add an extra check when parsing strings. ++ // ++ // Performance: there are many ways to skin this cat. ++ const uint64_t nonquote_scalar = characters.scalar() & ~strings.quote(); ++ uint64_t follows_nonquote_scalar = follows(nonquote_scalar, prev_scalar); ++ // We are returning a function-local object so either we get a move constructor ++ // or we get copy elision. ++ return json_block( ++ strings,// strings is a function-local object so either it moves or the copy is elided. ++ characters, ++ follows_nonquote_scalar ++ ); ++} ++ ++simdjson_inline error_code json_scanner::finish() { ++ return string_scanner.finish(); ++} ++ ++} // namespace stage1 ++} // unnamed namespace ++} // namespace icelake ++} // namespace simdjson ++/* end file src/generic/stage1/json_scanner.h */ ++/* begin file src/generic/stage1/json_minifier.h */ ++// This file contains the common code every implementation uses in stage1 ++// It is intended to be included multiple times and compiled multiple times ++// We assume the file in which it is included already includes ++// "simdjson/stage1.h" (this simplifies amalgation) ++ ++namespace simdjson { ++namespace icelake { ++namespace { ++namespace stage1 { ++ ++class json_minifier { ++public: ++ template ++ static error_code minify(const uint8_t *buf, size_t len, uint8_t *dst, size_t &dst_len) noexcept; ++ ++private: ++ simdjson_inline json_minifier(uint8_t *_dst) ++ : dst{_dst} ++ {} ++ template ++ simdjson_inline void step(const uint8_t *block_buf, buf_block_reader &reader) noexcept; ++ simdjson_inline void next(const simd::simd8x64& in, const json_block& block); ++ simdjson_inline error_code finish(uint8_t *dst_start, size_t &dst_len); ++ json_scanner scanner{}; ++ uint8_t *dst; ++}; ++ ++simdjson_inline void json_minifier::next(const simd::simd8x64& in, const json_block& block) { ++ uint64_t mask = block.whitespace(); ++ dst += in.compress(mask, dst); ++} ++ ++simdjson_inline error_code json_minifier::finish(uint8_t *dst_start, size_t &dst_len) { ++ error_code error = scanner.finish(); ++ if (error) { dst_len = 0; return error; } ++ dst_len = dst - dst_start; ++ return SUCCESS; ++} ++ ++template<> ++simdjson_inline void json_minifier::step<128>(const uint8_t *block_buf, buf_block_reader<128> &reader) noexcept { ++ simd::simd8x64 in_1(block_buf); ++ simd::simd8x64 in_2(block_buf+64); ++ json_block block_1 = scanner.next(in_1); ++ json_block block_2 = scanner.next(in_2); ++ this->next(in_1, block_1); ++ this->next(in_2, block_2); ++ reader.advance(); ++} ++ ++template<> ++simdjson_inline void json_minifier::step<64>(const uint8_t *block_buf, buf_block_reader<64> &reader) noexcept { ++ simd::simd8x64 in_1(block_buf); ++ json_block block_1 = scanner.next(in_1); ++ this->next(block_buf, block_1); ++ reader.advance(); ++} ++ ++template ++error_code json_minifier::minify(const uint8_t *buf, size_t len, uint8_t *dst, size_t &dst_len) noexcept { ++ buf_block_reader reader(buf, len); ++ json_minifier minifier(dst); ++ ++ // Index the first n-1 blocks ++ while (reader.has_full_block()) { ++ minifier.step(reader.full_block(), reader); ++ } ++ ++ // Index the last (remainder) block, padded with spaces ++ uint8_t block[STEP_SIZE]; ++ size_t remaining_bytes = reader.get_remainder(block); ++ if (remaining_bytes > 0) { ++ // We do not want to write directly to the output stream. Rather, we write ++ // to a local buffer (for safety). ++ uint8_t out_block[STEP_SIZE]; ++ uint8_t * const guarded_dst{minifier.dst}; ++ minifier.dst = out_block; ++ minifier.step(block, reader); ++ size_t to_write = minifier.dst - out_block; ++ // In some cases, we could be enticed to consider the padded spaces ++ // as part of the string. This is fine as long as we do not write more ++ // than we consumed. ++ if(to_write > remaining_bytes) { to_write = remaining_bytes; } ++ memcpy(guarded_dst, out_block, to_write); ++ minifier.dst = guarded_dst + to_write; ++ } ++ return minifier.finish(dst, dst_len); ++} ++ ++} // namespace stage1 ++} // unnamed namespace ++} // namespace icelake ++} // namespace simdjson ++/* end file src/generic/stage1/json_minifier.h */ ++/* begin file src/generic/stage1/find_next_document_index.h */ ++namespace simdjson { ++namespace icelake { ++namespace { ++ ++/** ++ * This algorithm is used to quickly identify the last structural position that ++ * makes up a complete document. ++ * ++ * It does this by going backwards and finding the last *document boundary* (a ++ * place where one value follows another without a comma between them). If the ++ * last document (the characters after the boundary) has an equal number of ++ * start and end brackets, it is considered complete. ++ * ++ * Simply put, we iterate over the structural characters, starting from ++ * the end. We consider that we found the end of a JSON document when the ++ * first element of the pair is NOT one of these characters: '{' '[' ':' ',' ++ * and when the second element is NOT one of these characters: '}' ']' ':' ','. ++ * ++ * This simple comparison works most of the time, but it does not cover cases ++ * where the batch's structural indexes contain a perfect amount of documents. ++ * In such a case, we do not have access to the structural index which follows ++ * the last document, therefore, we do not have access to the second element in ++ * the pair, and that means we cannot identify the last document. To fix this ++ * issue, we keep a count of the open and closed curly/square braces we found ++ * while searching for the pair. When we find a pair AND the count of open and ++ * closed curly/square braces is the same, we know that we just passed a ++ * complete document, therefore the last json buffer location is the end of the ++ * batch. ++ */ ++simdjson_inline uint32_t find_next_document_index(dom_parser_implementation &parser) { ++ // Variant: do not count separately, just figure out depth ++ if(parser.n_structural_indexes == 0) { return 0; } ++ auto arr_cnt = 0; ++ auto obj_cnt = 0; ++ for (auto i = parser.n_structural_indexes - 1; i > 0; i--) { ++ auto idxb = parser.structural_indexes[i]; ++ switch (parser.buf[idxb]) { ++ case ':': ++ case ',': ++ continue; ++ case '}': ++ obj_cnt--; ++ continue; ++ case ']': ++ arr_cnt--; ++ continue; ++ case '{': ++ obj_cnt++; ++ break; ++ case '[': ++ arr_cnt++; ++ break; ++ } ++ auto idxa = parser.structural_indexes[i - 1]; ++ switch (parser.buf[idxa]) { ++ case '{': ++ case '[': ++ case ':': ++ case ',': ++ continue; ++ } ++ // Last document is complete, so the next document will appear after! ++ if (!arr_cnt && !obj_cnt) { ++ return parser.n_structural_indexes; ++ } ++ // Last document is incomplete; mark the document at i + 1 as the next one ++ return i; ++ } ++ // If we made it to the end, we want to finish counting to see if we have a full document. ++ switch (parser.buf[parser.structural_indexes[0]]) { ++ case '}': ++ obj_cnt--; ++ break; ++ case ']': ++ arr_cnt--; ++ break; ++ case '{': ++ obj_cnt++; ++ break; ++ case '[': ++ arr_cnt++; ++ break; ++ } ++ if (!arr_cnt && !obj_cnt) { ++ // We have a complete document. ++ return parser.n_structural_indexes; ++ } ++ return 0; ++} ++ ++} // unnamed namespace ++} // namespace icelake ++} // namespace simdjson ++/* end file src/generic/stage1/find_next_document_index.h */ ++ ++namespace simdjson { ++namespace icelake { ++namespace { ++namespace stage1 { ++ ++class bit_indexer { ++public: ++ uint32_t *tail; ++ ++ simdjson_inline bit_indexer(uint32_t *index_buf) : tail(index_buf) {} ++ ++ // flatten out values in 'bits' assuming that they are are to have values of idx ++ // plus their position in the bitvector, and store these indexes at ++ // base_ptr[base] incrementing base as we go ++ // will potentially store extra values beyond end of valid bits, so base_ptr ++ // needs to be large enough to handle this ++ // ++ // If the kernel sets SIMDJSON_CUSTOM_BIT_INDEXER, then it will provide its own ++ // version of the code. ++#ifdef SIMDJSON_CUSTOM_BIT_INDEXER ++ simdjson_inline void write(uint32_t idx, uint64_t bits); ++#else ++ simdjson_inline void write(uint32_t idx, uint64_t bits) { ++ // In some instances, the next branch is expensive because it is mispredicted. ++ // Unfortunately, in other cases, ++ // it helps tremendously. ++ if (bits == 0) ++ return; ++#if SIMDJSON_PREFER_REVERSE_BITS ++ /** ++ * ARM lacks a fast trailing zero instruction, but it has a fast ++ * bit reversal instruction and a fast leading zero instruction. ++ * Thus it may be profitable to reverse the bits (once) and then ++ * to rely on a sequence of instructions that call the leading ++ * zero instruction. ++ * ++ * Performance notes: ++ * The chosen routine is not optimal in terms of data dependency ++ * since zero_leading_bit might require two instructions. However, ++ * it tends to minimize the total number of instructions which is ++ * beneficial. ++ */ ++ ++ uint64_t rev_bits = reverse_bits(bits); ++ int cnt = static_cast(count_ones(bits)); ++ int i = 0; ++ // Do the first 8 all together ++ for (; i<8; i++) { ++ int lz = leading_zeroes(rev_bits); ++ this->tail[i] = static_cast(idx) + lz; ++ rev_bits = zero_leading_bit(rev_bits, lz); ++ } ++ // Do the next 8 all together (we hope in most cases it won't happen at all ++ // and the branch is easily predicted). ++ if (simdjson_unlikely(cnt > 8)) { ++ i = 8; ++ for (; i<16; i++) { ++ int lz = leading_zeroes(rev_bits); ++ this->tail[i] = static_cast(idx) + lz; ++ rev_bits = zero_leading_bit(rev_bits, lz); ++ } ++ ++ ++ // Most files don't have 16+ structurals per block, so we take several basically guaranteed ++ // branch mispredictions here. 16+ structurals per block means either punctuation ({} [] , :) ++ // or the start of a value ("abc" true 123) every four characters. ++ if (simdjson_unlikely(cnt > 16)) { ++ i = 16; ++ while (rev_bits != 0) { ++ int lz = leading_zeroes(rev_bits); ++ this->tail[i++] = static_cast(idx) + lz; ++ rev_bits = zero_leading_bit(rev_bits, lz); ++ } ++ } ++ } ++ this->tail += cnt; ++#else // SIMDJSON_PREFER_REVERSE_BITS ++ /** ++ * Under recent x64 systems, we often have both a fast trailing zero ++ * instruction and a fast 'clear-lower-bit' instruction so the following ++ * algorithm can be competitive. ++ */ ++ ++ int cnt = static_cast(count_ones(bits)); ++ // Do the first 8 all together ++ for (int i=0; i<8; i++) { ++ this->tail[i] = idx + trailing_zeroes(bits); ++ bits = clear_lowest_bit(bits); ++ } ++ ++ // Do the next 8 all together (we hope in most cases it won't happen at all ++ // and the branch is easily predicted). ++ if (simdjson_unlikely(cnt > 8)) { ++ for (int i=8; i<16; i++) { ++ this->tail[i] = idx + trailing_zeroes(bits); ++ bits = clear_lowest_bit(bits); ++ } ++ ++ // Most files don't have 16+ structurals per block, so we take several basically guaranteed ++ // branch mispredictions here. 16+ structurals per block means either punctuation ({} [] , :) ++ // or the start of a value ("abc" true 123) every four characters. ++ if (simdjson_unlikely(cnt > 16)) { ++ int i = 16; ++ do { ++ this->tail[i] = idx + trailing_zeroes(bits); ++ bits = clear_lowest_bit(bits); ++ i++; ++ } while (i < cnt); ++ } ++ } ++ ++ this->tail += cnt; ++#endif ++ } ++#endif // SIMDJSON_CUSTOM_BIT_INDEXER ++ ++}; ++ ++class json_structural_indexer { ++public: ++ /** ++ * Find the important bits of JSON in a 128-byte chunk, and add them to structural_indexes. ++ * ++ * @param partial Setting the partial parameter to true allows the find_structural_bits to ++ * tolerate unclosed strings. The caller should still ensure that the input is valid UTF-8. If ++ * you are processing substrings, you may want to call on a function like trimmed_length_safe_utf8. ++ */ ++ template ++ static error_code index(const uint8_t *buf, size_t len, dom_parser_implementation &parser, stage1_mode partial) noexcept; ++ ++private: ++ simdjson_inline json_structural_indexer(uint32_t *structural_indexes); ++ template ++ simdjson_inline void step(const uint8_t *block, buf_block_reader &reader) noexcept; ++ simdjson_inline void next(const simd::simd8x64& in, const json_block& block, size_t idx); ++ simdjson_inline error_code finish(dom_parser_implementation &parser, size_t idx, size_t len, stage1_mode partial); ++ ++ json_scanner scanner{}; ++ utf8_checker checker{}; ++ bit_indexer indexer; ++ uint64_t prev_structurals = 0; ++ uint64_t unescaped_chars_error = 0; ++}; ++ ++simdjson_inline json_structural_indexer::json_structural_indexer(uint32_t *structural_indexes) : indexer{structural_indexes} {} ++ ++// Skip the last character if it is partial ++simdjson_inline size_t trim_partial_utf8(const uint8_t *buf, size_t len) { ++ if (simdjson_unlikely(len < 3)) { ++ switch (len) { ++ case 2: ++ if (buf[len-1] >= 0xc0) { return len-1; } // 2-, 3- and 4-byte characters with only 1 byte left ++ if (buf[len-2] >= 0xe0) { return len-2; } // 3- and 4-byte characters with only 2 bytes left ++ return len; ++ case 1: ++ if (buf[len-1] >= 0xc0) { return len-1; } // 2-, 3- and 4-byte characters with only 1 byte left ++ return len; ++ case 0: ++ return len; ++ } ++ } ++ if (buf[len-1] >= 0xc0) { return len-1; } // 2-, 3- and 4-byte characters with only 1 byte left ++ if (buf[len-2] >= 0xe0) { return len-2; } // 3- and 4-byte characters with only 1 byte left ++ if (buf[len-3] >= 0xf0) { return len-3; } // 4-byte characters with only 3 bytes left ++ return len; ++} ++ ++// ++// PERF NOTES: ++// We pipe 2 inputs through these stages: ++// 1. Load JSON into registers. This takes a long time and is highly parallelizable, so we load ++// 2 inputs' worth at once so that by the time step 2 is looking for them input, it's available. ++// 2. Scan the JSON for critical data: strings, scalars and operators. This is the critical path. ++// The output of step 1 depends entirely on this information. These functions don't quite use ++// up enough CPU: the second half of the functions is highly serial, only using 1 execution core ++// at a time. The second input's scans has some dependency on the first ones finishing it, but ++// they can make a lot of progress before they need that information. ++// 3. Step 1 doesn't use enough capacity, so we run some extra stuff while we're waiting for that ++// to finish: utf-8 checks and generating the output from the last iteration. ++// ++// The reason we run 2 inputs at a time, is steps 2 and 3 are *still* not enough to soak up all ++// available capacity with just one input. Running 2 at a time seems to give the CPU a good enough ++// workout. ++// ++template ++error_code json_structural_indexer::index(const uint8_t *buf, size_t len, dom_parser_implementation &parser, stage1_mode partial) noexcept { ++ if (simdjson_unlikely(len > parser.capacity())) { return CAPACITY; } ++ // We guard the rest of the code so that we can assume that len > 0 throughout. ++ if (len == 0) { return EMPTY; } ++ if (is_streaming(partial)) { ++ len = trim_partial_utf8(buf, len); ++ // If you end up with an empty window after trimming ++ // the partial UTF-8 bytes, then chances are good that you ++ // have an UTF-8 formatting error. ++ if(len == 0) { return UTF8_ERROR; } ++ } ++ buf_block_reader reader(buf, len); ++ json_structural_indexer indexer(parser.structural_indexes.get()); ++ ++ // Read all but the last block ++ while (reader.has_full_block()) { ++ indexer.step(reader.full_block(), reader); ++ } ++ // Take care of the last block (will always be there unless file is empty which is ++ // not supposed to happen.) ++ uint8_t block[STEP_SIZE]; ++ if (simdjson_unlikely(reader.get_remainder(block) == 0)) { return UNEXPECTED_ERROR; } ++ indexer.step(block, reader); ++ return indexer.finish(parser, reader.block_index(), len, partial); ++} ++ ++template<> ++simdjson_inline void json_structural_indexer::step<128>(const uint8_t *block, buf_block_reader<128> &reader) noexcept { ++ simd::simd8x64 in_1(block); ++ simd::simd8x64 in_2(block+64); ++ json_block block_1 = scanner.next(in_1); ++ json_block block_2 = scanner.next(in_2); ++ this->next(in_1, block_1, reader.block_index()); ++ this->next(in_2, block_2, reader.block_index()+64); ++ reader.advance(); ++} ++ ++template<> ++simdjson_inline void json_structural_indexer::step<64>(const uint8_t *block, buf_block_reader<64> &reader) noexcept { ++ simd::simd8x64 in_1(block); ++ json_block block_1 = scanner.next(in_1); ++ this->next(in_1, block_1, reader.block_index()); ++ reader.advance(); ++} ++ ++simdjson_inline void json_structural_indexer::next(const simd::simd8x64& in, const json_block& block, size_t idx) { ++ uint64_t unescaped = in.lteq(0x1F); ++ checker.check_next_input(in); ++ indexer.write(uint32_t(idx-64), prev_structurals); // Output *last* iteration's structurals to the parser ++ prev_structurals = block.structural_start(); ++ unescaped_chars_error |= block.non_quote_inside_string(unescaped); ++} ++ ++simdjson_inline error_code json_structural_indexer::finish(dom_parser_implementation &parser, size_t idx, size_t len, stage1_mode partial) { ++ // Write out the final iteration's structurals ++ indexer.write(uint32_t(idx-64), prev_structurals); ++ error_code error = scanner.finish(); ++ // We deliberately break down the next expression so that it is ++ // human readable. ++ const bool should_we_exit = is_streaming(partial) ? ++ ((error != SUCCESS) && (error != UNCLOSED_STRING)) // when partial we tolerate UNCLOSED_STRING ++ : (error != SUCCESS); // if partial is false, we must have SUCCESS ++ const bool have_unclosed_string = (error == UNCLOSED_STRING); ++ if (simdjson_unlikely(should_we_exit)) { return error; } ++ ++ if (unescaped_chars_error) { ++ return UNESCAPED_CHARS; ++ } ++ parser.n_structural_indexes = uint32_t(indexer.tail - parser.structural_indexes.get()); ++ /*** ++ * The On Demand API requires special padding. ++ * ++ * This is related to https://github.com/simdjson/simdjson/issues/906 ++ * Basically, we want to make sure that if the parsing continues beyond the last (valid) ++ * structural character, it quickly stops. ++ * Only three structural characters can be repeated without triggering an error in JSON: [,] and }. ++ * We repeat the padding character (at 'len'). We don't know what it is, but if the parsing ++ * continues, then it must be [,] or }. ++ * Suppose it is ] or }. We backtrack to the first character, what could it be that would ++ * not trigger an error? It could be ] or } but no, because you can't start a document that way. ++ * It can't be a comma, a colon or any simple value. So the only way we could continue is ++ * if the repeated character is [. But if so, the document must start with [. But if the document ++ * starts with [, it should end with ]. If we enforce that rule, then we would get ++ * ][[ which is invalid. ++ * ++ * This is illustrated with the test array_iterate_unclosed_error() on the following input: ++ * R"({ "a": [,,)" ++ **/ ++ parser.structural_indexes[parser.n_structural_indexes] = uint32_t(len); // used later in partial == stage1_mode::streaming_final ++ parser.structural_indexes[parser.n_structural_indexes + 1] = uint32_t(len); ++ parser.structural_indexes[parser.n_structural_indexes + 2] = 0; ++ parser.next_structural_index = 0; ++ // a valid JSON file cannot have zero structural indexes - we should have found something ++ if (simdjson_unlikely(parser.n_structural_indexes == 0u)) { ++ return EMPTY; ++ } ++ if (simdjson_unlikely(parser.structural_indexes[parser.n_structural_indexes - 1] > len)) { ++ return UNEXPECTED_ERROR; ++ } ++ if (partial == stage1_mode::streaming_partial) { ++ // If we have an unclosed string, then the last structural ++ // will be the quote and we want to make sure to omit it. ++ if(have_unclosed_string) { ++ parser.n_structural_indexes--; ++ // a valid JSON file cannot have zero structural indexes - we should have found something ++ if (simdjson_unlikely(parser.n_structural_indexes == 0u)) { return CAPACITY; } ++ } ++ // We truncate the input to the end of the last complete document (or zero). ++ auto new_structural_indexes = find_next_document_index(parser); ++ if (new_structural_indexes == 0 && parser.n_structural_indexes > 0) { ++ if(parser.structural_indexes[0] == 0) { ++ // If the buffer is partial and we started at index 0 but the document is ++ // incomplete, it's too big to parse. ++ return CAPACITY; ++ } else { ++ // It is possible that the document could be parsed, we just had a lot ++ // of white space. ++ parser.n_structural_indexes = 0; ++ return EMPTY; ++ } ++ } ++ ++ parser.n_structural_indexes = new_structural_indexes; ++ } else if (partial == stage1_mode::streaming_final) { ++ if(have_unclosed_string) { parser.n_structural_indexes--; } ++ // We truncate the input to the end of the last complete document (or zero). ++ // Because partial == stage1_mode::streaming_final, it means that we may ++ // silently ignore trailing garbage. Though it sounds bad, we do it ++ // deliberately because many people who have streams of JSON documents ++ // will truncate them for processing. E.g., imagine that you are uncompressing ++ // the data from a size file or receiving it in chunks from the network. You ++ // may not know where exactly the last document will be. Meanwhile the ++ // document_stream instances allow people to know the JSON documents they are ++ // parsing (see the iterator.source() method). ++ parser.n_structural_indexes = find_next_document_index(parser); ++ // We store the initial n_structural_indexes so that the client can see ++ // whether we used truncation. If initial_n_structural_indexes == parser.n_structural_indexes, ++ // then this will query parser.structural_indexes[parser.n_structural_indexes] which is len, ++ // otherwise, it will copy some prior index. ++ parser.structural_indexes[parser.n_structural_indexes + 1] = parser.structural_indexes[parser.n_structural_indexes]; ++ // This next line is critical, do not change it unless you understand what you are ++ // doing. ++ parser.structural_indexes[parser.n_structural_indexes] = uint32_t(len); ++ if (simdjson_unlikely(parser.n_structural_indexes == 0u)) { ++ // We tolerate an unclosed string at the very end of the stream. Indeed, users ++ // often load their data in bulk without being careful and they want us to ignore ++ // the trailing garbage. ++ return EMPTY; ++ } ++ } ++ checker.check_eof(); ++ return checker.errors(); ++} ++ ++} // namespace stage1 ++} // unnamed namespace ++} // namespace icelake ++} // namespace simdjson ++/* end file src/generic/stage1/json_structural_indexer.h */ ++// We must not forget to undefine it now: ++#undef SIMDJSON_CUSTOM_BIT_INDEXER ++ ++/** ++ * We provide a custom version of bit_indexer::write using ++ * naked intrinsics. ++ * TODO: make this code more elegant. ++ */ ++// Under GCC 12, the intrinsic _mm512_extracti32x4_epi32 may generate 'maybe uninitialized'. ++// as a workaround, we disable warnings within the following function. ++SIMDJSON_PUSH_DISABLE_ALL_WARNINGS ++namespace simdjson { namespace icelake { namespace { namespace stage1 { ++simdjson_inline void bit_indexer::write(uint32_t idx, uint64_t bits) { ++ // In some instances, the next branch is expensive because it is mispredicted. ++ // Unfortunately, in other cases, ++ // it helps tremendously. ++ if (bits == 0) { return; } ++ ++ const __m512i indexes = _mm512_maskz_compress_epi8(bits, _mm512_set_epi32( ++ 0x3f3e3d3c, 0x3b3a3938, 0x37363534, 0x33323130, ++ 0x2f2e2d2c, 0x2b2a2928, 0x27262524, 0x23222120, ++ 0x1f1e1d1c, 0x1b1a1918, 0x17161514, 0x13121110, ++ 0x0f0e0d0c, 0x0b0a0908, 0x07060504, 0x03020100 ++ )); ++ const __m512i start_index = _mm512_set1_epi32(idx); ++ ++ const auto count = count_ones(bits); ++ __m512i t0 = _mm512_cvtepu8_epi32(_mm512_castsi512_si128(indexes)); ++ _mm512_storeu_si512(this->tail, _mm512_add_epi32(t0, start_index)); ++ ++ if(count > 16) { ++ const __m512i t1 = _mm512_cvtepu8_epi32(_mm512_extracti32x4_epi32(indexes, 1)); ++ _mm512_storeu_si512(this->tail + 16, _mm512_add_epi32(t1, start_index)); ++ if(count > 32) { ++ const __m512i t2 = _mm512_cvtepu8_epi32(_mm512_extracti32x4_epi32(indexes, 2)); ++ _mm512_storeu_si512(this->tail + 32, _mm512_add_epi32(t2, start_index)); ++ if(count > 48) { ++ const __m512i t3 = _mm512_cvtepu8_epi32(_mm512_extracti32x4_epi32(indexes, 3)); ++ _mm512_storeu_si512(this->tail + 48, _mm512_add_epi32(t3, start_index)); ++ } ++ } ++ } ++ this->tail += count; ++} ++}}}} ++SIMDJSON_POP_DISABLE_WARNINGS ++ ++/* begin file src/generic/stage1/utf8_validator.h */ ++namespace simdjson { ++namespace icelake { ++namespace { ++namespace stage1 { ++ ++/** ++ * Validates that the string is actual UTF-8. ++ */ ++template ++bool generic_validate_utf8(const uint8_t * input, size_t length) { ++ checker c{}; ++ buf_block_reader<64> reader(input, length); ++ while (reader.has_full_block()) { ++ simd::simd8x64 in(reader.full_block()); ++ c.check_next_input(in); ++ reader.advance(); ++ } ++ uint8_t block[64]{}; ++ reader.get_remainder(block); ++ simd::simd8x64 in(block); ++ c.check_next_input(in); ++ reader.advance(); ++ c.check_eof(); ++ return c.errors() == error_code::SUCCESS; ++} ++ ++bool generic_validate_utf8(const char * input, size_t length) { ++ return generic_validate_utf8(reinterpret_cast(input),length); ++} ++ ++} // namespace stage1 ++} // unnamed namespace ++} // namespace icelake ++} // namespace simdjson ++/* end file src/generic/stage1/utf8_validator.h */ ++ ++// ++// Stage 2 ++// ++/* begin file src/generic/stage2/stringparsing.h */ ++// This file contains the common code every implementation uses ++// It is intended to be included multiple times and compiled multiple times ++ ++namespace simdjson { ++namespace icelake { ++namespace { ++/// @private ++namespace stringparsing { ++ ++// begin copypasta ++// These chars yield themselves: " \ / ++// b -> backspace, f -> formfeed, n -> newline, r -> cr, t -> horizontal tab ++// u not handled in this table as it's complex ++static const uint8_t escape_map[256] = { ++ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x0. ++ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ++ 0, 0, 0x22, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x2f, ++ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ++ ++ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x4. ++ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x5c, 0, 0, 0, // 0x5. ++ 0, 0, 0x08, 0, 0, 0, 0x0c, 0, 0, 0, 0, 0, 0, 0, 0x0a, 0, // 0x6. ++ 0, 0, 0x0d, 0, 0x09, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x7. ++ ++ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ++ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ++ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ++ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ++ ++ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ++ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ++ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ++ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ++}; ++ ++// handle a unicode codepoint ++// write appropriate values into dest ++// src will advance 6 bytes or 12 bytes ++// dest will advance a variable amount (return via pointer) ++// return true if the unicode codepoint was valid ++// We work in little-endian then swap at write time ++simdjson_warn_unused ++simdjson_inline bool handle_unicode_codepoint(const uint8_t **src_ptr, ++ uint8_t **dst_ptr) { ++ // jsoncharutils::hex_to_u32_nocheck fills high 16 bits of the return value with 1s if the ++ // conversion isn't valid; we defer the check for this to inside the ++ // multilingual plane check ++ uint32_t code_point = jsoncharutils::hex_to_u32_nocheck(*src_ptr + 2); ++ *src_ptr += 6; ++ ++ // If we found a high surrogate, we must ++ // check for low surrogate for characters ++ // outside the Basic ++ // Multilingual Plane. ++ if (code_point >= 0xd800 && code_point < 0xdc00) { ++ const uint8_t *src_data = *src_ptr; ++ /* Compiler optimizations convert this to a single 16-bit load and compare on most platforms */ ++ if (((src_data[0] << 8) | src_data[1]) != ((static_cast ('\\') << 8) | static_cast ('u'))) { ++ return false; ++ } ++ uint32_t code_point_2 = jsoncharutils::hex_to_u32_nocheck(src_data + 2); ++ ++ // We have already checked that the high surrogate is valid and ++ // (code_point - 0xd800) < 1024. ++ // ++ // Check that code_point_2 is in the range 0xdc00..0xdfff ++ // and that code_point_2 was parsed from valid hex. ++ uint32_t low_bit = code_point_2 - 0xdc00; ++ if (low_bit >> 10) { ++ return false; ++ } ++ ++ code_point = ++ (((code_point - 0xd800) << 10) | low_bit) + 0x10000; ++ *src_ptr += 6; ++ } else if (code_point >= 0xdc00 && code_point <= 0xdfff) { ++ // If we encounter a low surrogate (not preceded by a high surrogate) ++ // then we have an error. ++ return false; ++ } ++ size_t offset = jsoncharutils::codepoint_to_utf8(code_point, *dst_ptr); ++ *dst_ptr += offset; ++ return offset > 0; ++} ++ ++/** ++ * Unescape a valid UTF-8 string from src to dst, stopping at a final unescaped quote. There ++ * must be an unescaped quote terminating the string. It returns the final output ++ * position as pointer. In case of error (e.g., the string has bad escaped codes), ++ * then null_nullptrptr is returned. It is assumed that the output buffer is large ++ * enough. E.g., if src points at 'joe"', then dst needs to have four free bytes + ++ * SIMDJSON_PADDING bytes. ++ */ ++simdjson_warn_unused simdjson_inline uint8_t *parse_string(const uint8_t *src, uint8_t *dst) { ++ while (1) { ++ // Copy the next n bytes, and find the backslash and quote in them. ++ auto bs_quote = backslash_and_quote::copy_and_find(src, dst); ++ // If the next thing is the end quote, copy and return ++ if (bs_quote.has_quote_first()) { ++ // we encountered quotes first. Move dst to point to quotes and exit ++ return dst + bs_quote.quote_index(); ++ } ++ if (bs_quote.has_backslash()) { ++ /* find out where the backspace is */ ++ auto bs_dist = bs_quote.backslash_index(); ++ uint8_t escape_char = src[bs_dist + 1]; ++ /* we encountered backslash first. Handle backslash */ ++ if (escape_char == 'u') { ++ /* move src/dst up to the start; they will be further adjusted ++ within the unicode codepoint handling code. */ ++ src += bs_dist; ++ dst += bs_dist; ++ if (!handle_unicode_codepoint(&src, &dst)) { ++ return nullptr; ++ } ++ } else { ++ /* simple 1:1 conversion. Will eat bs_dist+2 characters in input and ++ * write bs_dist+1 characters to output ++ * note this may reach beyond the part of the buffer we've actually ++ * seen. I think this is ok */ ++ uint8_t escape_result = escape_map[escape_char]; ++ if (escape_result == 0u) { ++ return nullptr; /* bogus escape value is an error */ ++ } ++ dst[bs_dist] = escape_result; ++ src += bs_dist + 2; ++ dst += bs_dist + 1; ++ } ++ } else { ++ /* they are the same. Since they can't co-occur, it means we ++ * encountered neither. */ ++ src += backslash_and_quote::BYTES_PROCESSED; ++ dst += backslash_and_quote::BYTES_PROCESSED; ++ } ++ } ++ /* can't be reached */ ++ return nullptr; ++} ++ ++} // namespace stringparsing ++} // unnamed namespace ++} // namespace icelake ++} // namespace simdjson ++/* end file src/generic/stage2/stringparsing.h */ ++/* begin file src/generic/stage2/tape_builder.h */ ++/* begin file src/generic/stage2/json_iterator.h */ ++/* begin file src/generic/stage2/logger.h */ ++// This is for an internal-only stage 2 specific logger. ++// Set LOG_ENABLED = true to log what stage 2 is doing! ++namespace simdjson { ++namespace icelake { ++namespace { ++namespace logger { ++ ++ static constexpr const char * DASHES = "----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------"; ++ ++#if SIMDJSON_VERBOSE_LOGGING ++ static constexpr const bool LOG_ENABLED = true; ++#else ++ static constexpr const bool LOG_ENABLED = false; ++#endif ++ static constexpr const int LOG_EVENT_LEN = 20; ++ static constexpr const int LOG_BUFFER_LEN = 30; ++ static constexpr const int LOG_SMALL_BUFFER_LEN = 10; ++ static constexpr const int LOG_INDEX_LEN = 5; ++ ++ static int log_depth; // Not threadsafe. Log only. ++ ++ // Helper to turn unprintable or newline characters into spaces ++ static simdjson_inline char printable_char(char c) { ++ if (c >= 0x20) { ++ return c; ++ } else { ++ return ' '; ++ } ++ } ++ ++ // Print the header and set up log_start ++ static simdjson_inline void log_start() { ++ if (LOG_ENABLED) { ++ log_depth = 0; ++ printf("\n"); ++ printf("| %-*s | %-*s | %-*s | %-*s | Detail |\n", LOG_EVENT_LEN, "Event", LOG_BUFFER_LEN, "Buffer", LOG_SMALL_BUFFER_LEN, "Next", 5, "Next#"); ++ printf("|%.*s|%.*s|%.*s|%.*s|--------|\n", LOG_EVENT_LEN+2, DASHES, LOG_BUFFER_LEN+2, DASHES, LOG_SMALL_BUFFER_LEN+2, DASHES, 5+2, DASHES); ++ } ++ } ++ ++ simdjson_unused static simdjson_inline void log_string(const char *message) { ++ if (LOG_ENABLED) { ++ printf("%s\n", message); ++ } ++ } ++ ++ // Logs a single line from the stage 2 DOM parser ++ template ++ static simdjson_inline void log_line(S &structurals, const char *title_prefix, const char *title, const char *detail) { ++ if (LOG_ENABLED) { ++ printf("| %*s%s%-*s ", log_depth*2, "", title_prefix, LOG_EVENT_LEN - log_depth*2 - int(strlen(title_prefix)), title); ++ auto current_index = structurals.at_beginning() ? nullptr : structurals.next_structural-1; ++ auto next_index = structurals.next_structural; ++ auto current = current_index ? &structurals.buf[*current_index] : reinterpret_cast(" "); ++ auto next = &structurals.buf[*next_index]; ++ { ++ // Print the next N characters in the buffer. ++ printf("| "); ++ // Otherwise, print the characters starting from the buffer position. ++ // Print spaces for unprintable or newline characters. ++ for (int i=0;i ++ simdjson_warn_unused simdjson_inline error_code walk_document(V &visitor) noexcept; ++ ++ /** ++ * Create an iterator capable of walking a JSON document. ++ * ++ * The document must have already passed through stage 1. ++ */ ++ simdjson_inline json_iterator(dom_parser_implementation &_dom_parser, size_t start_structural_index); ++ ++ /** ++ * Look at the next token. ++ * ++ * Tokens can be strings, numbers, booleans, null, or operators (`[{]},:`)). ++ * ++ * They may include invalid JSON as well (such as `1.2.3` or `ture`). ++ */ ++ simdjson_inline const uint8_t *peek() const noexcept; ++ /** ++ * Advance to the next token. ++ * ++ * Tokens can be strings, numbers, booleans, null, or operators (`[{]},:`)). ++ * ++ * They may include invalid JSON as well (such as `1.2.3` or `ture`). ++ */ ++ simdjson_inline const uint8_t *advance() noexcept; ++ /** ++ * Get the remaining length of the document, from the start of the current token. ++ */ ++ simdjson_inline size_t remaining_len() const noexcept; ++ /** ++ * Check if we are at the end of the document. ++ * ++ * If this is true, there are no more tokens. ++ */ ++ simdjson_inline bool at_eof() const noexcept; ++ /** ++ * Check if we are at the beginning of the document. ++ */ ++ simdjson_inline bool at_beginning() const noexcept; ++ simdjson_inline uint8_t last_structural() const noexcept; ++ ++ /** ++ * Log that a value has been found. ++ * ++ * Set LOG_ENABLED=true in logger.h to see logging. ++ */ ++ simdjson_inline void log_value(const char *type) const noexcept; ++ /** ++ * Log the start of a multipart value. ++ * ++ * Set LOG_ENABLED=true in logger.h to see logging. ++ */ ++ simdjson_inline void log_start_value(const char *type) const noexcept; ++ /** ++ * Log the end of a multipart value. ++ * ++ * Set LOG_ENABLED=true in logger.h to see logging. ++ */ ++ simdjson_inline void log_end_value(const char *type) const noexcept; ++ /** ++ * Log an error. ++ * ++ * Set LOG_ENABLED=true in logger.h to see logging. ++ */ ++ simdjson_inline void log_error(const char *error) const noexcept; ++ ++ template ++ simdjson_warn_unused simdjson_inline error_code visit_root_primitive(V &visitor, const uint8_t *value) noexcept; ++ template ++ simdjson_warn_unused simdjson_inline error_code visit_primitive(V &visitor, const uint8_t *value) noexcept; ++}; ++ ++template ++simdjson_warn_unused simdjson_inline error_code json_iterator::walk_document(V &visitor) noexcept { ++ logger::log_start(); ++ ++ // ++ // Start the document ++ // ++ if (at_eof()) { return EMPTY; } ++ log_start_value("document"); ++ SIMDJSON_TRY( visitor.visit_document_start(*this) ); ++ ++ // ++ // Read first value ++ // ++ { ++ auto value = advance(); ++ ++ // Make sure the outer object or array is closed before continuing; otherwise, there are ways we ++ // could get into memory corruption. See https://github.com/simdjson/simdjson/issues/906 ++ if (!STREAMING) { ++ switch (*value) { ++ case '{': if (last_structural() != '}') { log_value("starting brace unmatched"); return TAPE_ERROR; }; break; ++ case '[': if (last_structural() != ']') { log_value("starting bracket unmatched"); return TAPE_ERROR; }; break; ++ } ++ } ++ ++ switch (*value) { ++ case '{': if (*peek() == '}') { advance(); log_value("empty object"); SIMDJSON_TRY( visitor.visit_empty_object(*this) ); break; } goto object_begin; ++ case '[': if (*peek() == ']') { advance(); log_value("empty array"); SIMDJSON_TRY( visitor.visit_empty_array(*this) ); break; } goto array_begin; ++ default: SIMDJSON_TRY( visitor.visit_root_primitive(*this, value) ); break; ++ } ++ } ++ goto document_end; ++ ++// ++// Object parser states ++// ++object_begin: ++ log_start_value("object"); ++ depth++; ++ if (depth >= dom_parser.max_depth()) { log_error("Exceeded max depth!"); return DEPTH_ERROR; } ++ dom_parser.is_array[depth] = false; ++ SIMDJSON_TRY( visitor.visit_object_start(*this) ); ++ ++ { ++ auto key = advance(); ++ if (*key != '"') { log_error("Object does not start with a key"); return TAPE_ERROR; } ++ SIMDJSON_TRY( visitor.increment_count(*this) ); ++ SIMDJSON_TRY( visitor.visit_key(*this, key) ); ++ } ++ ++object_field: ++ if (simdjson_unlikely( *advance() != ':' )) { log_error("Missing colon after key in object"); return TAPE_ERROR; } ++ { ++ auto value = advance(); ++ switch (*value) { ++ case '{': if (*peek() == '}') { advance(); log_value("empty object"); SIMDJSON_TRY( visitor.visit_empty_object(*this) ); break; } goto object_begin; ++ case '[': if (*peek() == ']') { advance(); log_value("empty array"); SIMDJSON_TRY( visitor.visit_empty_array(*this) ); break; } goto array_begin; ++ default: SIMDJSON_TRY( visitor.visit_primitive(*this, value) ); break; ++ } ++ } ++ ++object_continue: ++ switch (*advance()) { ++ case ',': ++ SIMDJSON_TRY( visitor.increment_count(*this) ); ++ { ++ auto key = advance(); ++ if (simdjson_unlikely( *key != '"' )) { log_error("Key string missing at beginning of field in object"); return TAPE_ERROR; } ++ SIMDJSON_TRY( visitor.visit_key(*this, key) ); ++ } ++ goto object_field; ++ case '}': log_end_value("object"); SIMDJSON_TRY( visitor.visit_object_end(*this) ); goto scope_end; ++ default: log_error("No comma between object fields"); return TAPE_ERROR; ++ } ++ ++scope_end: ++ depth--; ++ if (depth == 0) { goto document_end; } ++ if (dom_parser.is_array[depth]) { goto array_continue; } ++ goto object_continue; ++ ++// ++// Array parser states ++// ++array_begin: ++ log_start_value("array"); ++ depth++; ++ if (depth >= dom_parser.max_depth()) { log_error("Exceeded max depth!"); return DEPTH_ERROR; } ++ dom_parser.is_array[depth] = true; ++ SIMDJSON_TRY( visitor.visit_array_start(*this) ); ++ SIMDJSON_TRY( visitor.increment_count(*this) ); ++ ++array_value: ++ { ++ auto value = advance(); ++ switch (*value) { ++ case '{': if (*peek() == '}') { advance(); log_value("empty object"); SIMDJSON_TRY( visitor.visit_empty_object(*this) ); break; } goto object_begin; ++ case '[': if (*peek() == ']') { advance(); log_value("empty array"); SIMDJSON_TRY( visitor.visit_empty_array(*this) ); break; } goto array_begin; ++ default: SIMDJSON_TRY( visitor.visit_primitive(*this, value) ); break; ++ } ++ } ++ ++array_continue: ++ switch (*advance()) { ++ case ',': SIMDJSON_TRY( visitor.increment_count(*this) ); goto array_value; ++ case ']': log_end_value("array"); SIMDJSON_TRY( visitor.visit_array_end(*this) ); goto scope_end; ++ default: log_error("Missing comma between array values"); return TAPE_ERROR; ++ } ++ ++document_end: ++ log_end_value("document"); ++ SIMDJSON_TRY( visitor.visit_document_end(*this) ); ++ ++ dom_parser.next_structural_index = uint32_t(next_structural - &dom_parser.structural_indexes[0]); ++ ++ // If we didn't make it to the end, it's an error ++ if ( !STREAMING && dom_parser.next_structural_index != dom_parser.n_structural_indexes ) { ++ log_error("More than one JSON value at the root of the document, or extra characters at the end of the JSON!"); ++ return TAPE_ERROR; ++ } ++ ++ return SUCCESS; ++ ++} // walk_document() ++ ++simdjson_inline json_iterator::json_iterator(dom_parser_implementation &_dom_parser, size_t start_structural_index) ++ : buf{_dom_parser.buf}, ++ next_structural{&_dom_parser.structural_indexes[start_structural_index]}, ++ dom_parser{_dom_parser} { ++} ++ ++simdjson_inline const uint8_t *json_iterator::peek() const noexcept { ++ return &buf[*(next_structural)]; ++} ++simdjson_inline const uint8_t *json_iterator::advance() noexcept { ++ return &buf[*(next_structural++)]; ++} ++simdjson_inline size_t json_iterator::remaining_len() const noexcept { ++ return dom_parser.len - *(next_structural-1); ++} ++ ++simdjson_inline bool json_iterator::at_eof() const noexcept { ++ return next_structural == &dom_parser.structural_indexes[dom_parser.n_structural_indexes]; ++} ++simdjson_inline bool json_iterator::at_beginning() const noexcept { ++ return next_structural == dom_parser.structural_indexes.get(); ++} ++simdjson_inline uint8_t json_iterator::last_structural() const noexcept { ++ return buf[dom_parser.structural_indexes[dom_parser.n_structural_indexes - 1]]; ++} ++ ++simdjson_inline void json_iterator::log_value(const char *type) const noexcept { ++ logger::log_line(*this, "", type, ""); ++} ++ ++simdjson_inline void json_iterator::log_start_value(const char *type) const noexcept { ++ logger::log_line(*this, "+", type, ""); ++ if (logger::LOG_ENABLED) { logger::log_depth++; } ++} ++ ++simdjson_inline void json_iterator::log_end_value(const char *type) const noexcept { ++ if (logger::LOG_ENABLED) { logger::log_depth--; } ++ logger::log_line(*this, "-", type, ""); ++} ++ ++simdjson_inline void json_iterator::log_error(const char *error) const noexcept { ++ logger::log_line(*this, "", "ERROR", error); ++} ++ ++template ++simdjson_warn_unused simdjson_inline error_code json_iterator::visit_root_primitive(V &visitor, const uint8_t *value) noexcept { ++ switch (*value) { ++ case '"': return visitor.visit_root_string(*this, value); ++ case 't': return visitor.visit_root_true_atom(*this, value); ++ case 'f': return visitor.visit_root_false_atom(*this, value); ++ case 'n': return visitor.visit_root_null_atom(*this, value); ++ case '-': ++ case '0': case '1': case '2': case '3': case '4': ++ case '5': case '6': case '7': case '8': case '9': ++ return visitor.visit_root_number(*this, value); ++ default: ++ log_error("Document starts with a non-value character"); ++ return TAPE_ERROR; ++ } ++} ++template ++simdjson_warn_unused simdjson_inline error_code json_iterator::visit_primitive(V &visitor, const uint8_t *value) noexcept { ++ switch (*value) { ++ case '"': return visitor.visit_string(*this, value); ++ case 't': return visitor.visit_true_atom(*this, value); ++ case 'f': return visitor.visit_false_atom(*this, value); ++ case 'n': return visitor.visit_null_atom(*this, value); ++ case '-': ++ case '0': case '1': case '2': case '3': case '4': ++ case '5': case '6': case '7': case '8': case '9': ++ return visitor.visit_number(*this, value); ++ default: ++ log_error("Non-value found when value was expected!"); ++ return TAPE_ERROR; ++ } ++} ++ ++} // namespace stage2 ++} // unnamed namespace ++} // namespace icelake ++} // namespace simdjson ++/* end file src/generic/stage2/json_iterator.h */ ++/* begin file src/generic/stage2/tape_writer.h */ ++namespace simdjson { ++namespace icelake { ++namespace { ++namespace stage2 { ++ ++struct tape_writer { ++ /** The next place to write to tape */ ++ uint64_t *next_tape_loc; ++ ++ /** Write a signed 64-bit value to tape. */ ++ simdjson_inline void append_s64(int64_t value) noexcept; ++ ++ /** Write an unsigned 64-bit value to tape. */ ++ simdjson_inline void append_u64(uint64_t value) noexcept; ++ ++ /** Write a double value to tape. */ ++ simdjson_inline void append_double(double value) noexcept; ++ ++ /** ++ * Append a tape entry (an 8-bit type,and 56 bits worth of value). ++ */ ++ simdjson_inline void append(uint64_t val, internal::tape_type t) noexcept; ++ ++ /** ++ * Skip the current tape entry without writing. ++ * ++ * Used to skip the start of the container, since we'll come back later to fill it in when the ++ * container ends. ++ */ ++ simdjson_inline void skip() noexcept; ++ ++ /** ++ * Skip the number of tape entries necessary to write a large u64 or i64. ++ */ ++ simdjson_inline void skip_large_integer() noexcept; ++ ++ /** ++ * Skip the number of tape entries necessary to write a double. ++ */ ++ simdjson_inline void skip_double() noexcept; ++ ++ /** ++ * Write a value to a known location on tape. ++ * ++ * Used to go back and write out the start of a container after the container ends. ++ */ ++ simdjson_inline static void write(uint64_t &tape_loc, uint64_t val, internal::tape_type t) noexcept; ++ ++private: ++ /** ++ * Append both the tape entry, and a supplementary value following it. Used for types that need ++ * all 64 bits, such as double and uint64_t. ++ */ ++ template ++ simdjson_inline void append2(uint64_t val, T val2, internal::tape_type t) noexcept; ++}; // struct number_writer ++ ++simdjson_inline void tape_writer::append_s64(int64_t value) noexcept { ++ append2(0, value, internal::tape_type::INT64); ++} ++ ++simdjson_inline void tape_writer::append_u64(uint64_t value) noexcept { ++ append(0, internal::tape_type::UINT64); ++ *next_tape_loc = value; ++ next_tape_loc++; ++} ++ ++/** Write a double value to tape. */ ++simdjson_inline void tape_writer::append_double(double value) noexcept { ++ append2(0, value, internal::tape_type::DOUBLE); ++} ++ ++simdjson_inline void tape_writer::skip() noexcept { ++ next_tape_loc++; ++} ++ ++simdjson_inline void tape_writer::skip_large_integer() noexcept { ++ next_tape_loc += 2; ++} ++ ++simdjson_inline void tape_writer::skip_double() noexcept { ++ next_tape_loc += 2; ++} ++ ++simdjson_inline void tape_writer::append(uint64_t val, internal::tape_type t) noexcept { ++ *next_tape_loc = val | ((uint64_t(char(t))) << 56); ++ next_tape_loc++; ++} ++ ++template ++simdjson_inline void tape_writer::append2(uint64_t val, T val2, internal::tape_type t) noexcept { ++ append(val, t); ++ static_assert(sizeof(val2) == sizeof(*next_tape_loc), "Type is not 64 bits!"); ++ memcpy(next_tape_loc, &val2, sizeof(val2)); ++ next_tape_loc++; ++} ++ ++simdjson_inline void tape_writer::write(uint64_t &tape_loc, uint64_t val, internal::tape_type t) noexcept { ++ tape_loc = val | ((uint64_t(char(t))) << 56); ++} ++ ++} // namespace stage2 ++} // unnamed namespace ++} // namespace icelake ++} // namespace simdjson ++/* end file src/generic/stage2/tape_writer.h */ ++ ++namespace simdjson { ++namespace icelake { ++namespace { ++namespace stage2 { ++ ++struct tape_builder { ++ template ++ simdjson_warn_unused static simdjson_inline error_code parse_document( ++ dom_parser_implementation &dom_parser, ++ dom::document &doc) noexcept; ++ ++ /** Called when a non-empty document starts. */ ++ simdjson_warn_unused simdjson_inline error_code visit_document_start(json_iterator &iter) noexcept; ++ /** Called when a non-empty document ends without error. */ ++ simdjson_warn_unused simdjson_inline error_code visit_document_end(json_iterator &iter) noexcept; ++ ++ /** Called when a non-empty array starts. */ ++ simdjson_warn_unused simdjson_inline error_code visit_array_start(json_iterator &iter) noexcept; ++ /** Called when a non-empty array ends. */ ++ simdjson_warn_unused simdjson_inline error_code visit_array_end(json_iterator &iter) noexcept; ++ /** Called when an empty array is found. */ ++ simdjson_warn_unused simdjson_inline error_code visit_empty_array(json_iterator &iter) noexcept; ++ ++ /** Called when a non-empty object starts. */ ++ simdjson_warn_unused simdjson_inline error_code visit_object_start(json_iterator &iter) noexcept; ++ /** ++ * Called when a key in a field is encountered. ++ * ++ * primitive, visit_object_start, visit_empty_object, visit_array_start, or visit_empty_array ++ * will be called after this with the field value. ++ */ ++ simdjson_warn_unused simdjson_inline error_code visit_key(json_iterator &iter, const uint8_t *key) noexcept; ++ /** Called when a non-empty object ends. */ ++ simdjson_warn_unused simdjson_inline error_code visit_object_end(json_iterator &iter) noexcept; ++ /** Called when an empty object is found. */ ++ simdjson_warn_unused simdjson_inline error_code visit_empty_object(json_iterator &iter) noexcept; ++ ++ /** ++ * Called when a string, number, boolean or null is found. ++ */ ++ simdjson_warn_unused simdjson_inline error_code visit_primitive(json_iterator &iter, const uint8_t *value) noexcept; ++ /** ++ * Called when a string, number, boolean or null is found at the top level of a document (i.e. ++ * when there is no array or object and the entire document is a single string, number, boolean or ++ * null. ++ * ++ * This is separate from primitive() because simdjson's normal primitive parsing routines assume ++ * there is at least one more token after the value, which is only true in an array or object. ++ */ ++ simdjson_warn_unused simdjson_inline error_code visit_root_primitive(json_iterator &iter, const uint8_t *value) noexcept; ++ ++ simdjson_warn_unused simdjson_inline error_code visit_string(json_iterator &iter, const uint8_t *value, bool key = false) noexcept; ++ simdjson_warn_unused simdjson_inline error_code visit_number(json_iterator &iter, const uint8_t *value) noexcept; ++ simdjson_warn_unused simdjson_inline error_code visit_true_atom(json_iterator &iter, const uint8_t *value) noexcept; ++ simdjson_warn_unused simdjson_inline error_code visit_false_atom(json_iterator &iter, const uint8_t *value) noexcept; ++ simdjson_warn_unused simdjson_inline error_code visit_null_atom(json_iterator &iter, const uint8_t *value) noexcept; ++ ++ simdjson_warn_unused simdjson_inline error_code visit_root_string(json_iterator &iter, const uint8_t *value) noexcept; ++ simdjson_warn_unused simdjson_inline error_code visit_root_number(json_iterator &iter, const uint8_t *value) noexcept; ++ simdjson_warn_unused simdjson_inline error_code visit_root_true_atom(json_iterator &iter, const uint8_t *value) noexcept; ++ simdjson_warn_unused simdjson_inline error_code visit_root_false_atom(json_iterator &iter, const uint8_t *value) noexcept; ++ simdjson_warn_unused simdjson_inline error_code visit_root_null_atom(json_iterator &iter, const uint8_t *value) noexcept; ++ ++ /** Called each time a new field or element in an array or object is found. */ ++ simdjson_warn_unused simdjson_inline error_code increment_count(json_iterator &iter) noexcept; ++ ++ /** Next location to write to tape */ ++ tape_writer tape; ++private: ++ /** Next write location in the string buf for stage 2 parsing */ ++ uint8_t *current_string_buf_loc; ++ ++ simdjson_inline tape_builder(dom::document &doc) noexcept; ++ ++ simdjson_inline uint32_t next_tape_index(json_iterator &iter) const noexcept; ++ simdjson_inline void start_container(json_iterator &iter) noexcept; ++ simdjson_warn_unused simdjson_inline error_code end_container(json_iterator &iter, internal::tape_type start, internal::tape_type end) noexcept; ++ simdjson_warn_unused simdjson_inline error_code empty_container(json_iterator &iter, internal::tape_type start, internal::tape_type end) noexcept; ++ simdjson_inline uint8_t *on_start_string(json_iterator &iter) noexcept; ++ simdjson_inline void on_end_string(uint8_t *dst) noexcept; ++}; // class tape_builder ++ ++template ++simdjson_warn_unused simdjson_inline error_code tape_builder::parse_document( ++ dom_parser_implementation &dom_parser, ++ dom::document &doc) noexcept { ++ dom_parser.doc = &doc; ++ json_iterator iter(dom_parser, STREAMING ? dom_parser.next_structural_index : 0); ++ tape_builder builder(doc); ++ return iter.walk_document(builder); ++} ++ ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_root_primitive(json_iterator &iter, const uint8_t *value) noexcept { ++ return iter.visit_root_primitive(*this, value); ++} ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_primitive(json_iterator &iter, const uint8_t *value) noexcept { ++ return iter.visit_primitive(*this, value); ++} ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_empty_object(json_iterator &iter) noexcept { ++ return empty_container(iter, internal::tape_type::START_OBJECT, internal::tape_type::END_OBJECT); ++} ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_empty_array(json_iterator &iter) noexcept { ++ return empty_container(iter, internal::tape_type::START_ARRAY, internal::tape_type::END_ARRAY); ++} ++ ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_document_start(json_iterator &iter) noexcept { ++ start_container(iter); ++ return SUCCESS; ++} ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_object_start(json_iterator &iter) noexcept { ++ start_container(iter); ++ return SUCCESS; ++} ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_array_start(json_iterator &iter) noexcept { ++ start_container(iter); ++ return SUCCESS; ++} ++ ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_object_end(json_iterator &iter) noexcept { ++ return end_container(iter, internal::tape_type::START_OBJECT, internal::tape_type::END_OBJECT); ++} ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_array_end(json_iterator &iter) noexcept { ++ return end_container(iter, internal::tape_type::START_ARRAY, internal::tape_type::END_ARRAY); ++} ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_document_end(json_iterator &iter) noexcept { ++ constexpr uint32_t start_tape_index = 0; ++ tape.append(start_tape_index, internal::tape_type::ROOT); ++ tape_writer::write(iter.dom_parser.doc->tape[start_tape_index], next_tape_index(iter), internal::tape_type::ROOT); ++ return SUCCESS; ++} ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_key(json_iterator &iter, const uint8_t *key) noexcept { ++ return visit_string(iter, key, true); ++} ++ ++simdjson_warn_unused simdjson_inline error_code tape_builder::increment_count(json_iterator &iter) noexcept { ++ iter.dom_parser.open_containers[iter.depth].count++; // we have a key value pair in the object at parser.dom_parser.depth - 1 ++ return SUCCESS; ++} ++ ++simdjson_inline tape_builder::tape_builder(dom::document &doc) noexcept : tape{doc.tape.get()}, current_string_buf_loc{doc.string_buf.get()} {} ++ ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_string(json_iterator &iter, const uint8_t *value, bool key) noexcept { ++ iter.log_value(key ? "key" : "string"); ++ uint8_t *dst = on_start_string(iter); ++ dst = stringparsing::parse_string(value+1, dst); ++ if (dst == nullptr) { ++ iter.log_error("Invalid escape in string"); ++ return STRING_ERROR; ++ } ++ on_end_string(dst); ++ return SUCCESS; ++} ++ ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_root_string(json_iterator &iter, const uint8_t *value) noexcept { ++ return visit_string(iter, value); ++} ++ ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_number(json_iterator &iter, const uint8_t *value) noexcept { ++ iter.log_value("number"); ++ return numberparsing::parse_number(value, tape); ++} ++ ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_root_number(json_iterator &iter, const uint8_t *value) noexcept { ++ // ++ // We need to make a copy to make sure that the string is space terminated. ++ // This is not about padding the input, which should already padded up ++ // to len + SIMDJSON_PADDING. However, we have no control at this stage ++ // on how the padding was done. What if the input string was padded with nulls? ++ // It is quite common for an input string to have an extra null character (C string). ++ // We do not want to allow 9\0 (where \0 is the null character) inside a JSON ++ // document, but the string "9\0" by itself is fine. So we make a copy and ++ // pad the input with spaces when we know that there is just one input element. ++ // This copy is relatively expensive, but it will almost never be called in ++ // practice unless you are in the strange scenario where you have many JSON ++ // documents made of single atoms. ++ // ++ std::unique_ptrcopy(new (std::nothrow) uint8_t[iter.remaining_len() + SIMDJSON_PADDING]); ++ if (copy.get() == nullptr) { return MEMALLOC; } ++ std::memcpy(copy.get(), value, iter.remaining_len()); ++ std::memset(copy.get() + iter.remaining_len(), ' ', SIMDJSON_PADDING); ++ error_code error = visit_number(iter, copy.get()); ++ return error; ++} ++ ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_true_atom(json_iterator &iter, const uint8_t *value) noexcept { ++ iter.log_value("true"); ++ if (!atomparsing::is_valid_true_atom(value)) { return T_ATOM_ERROR; } ++ tape.append(0, internal::tape_type::TRUE_VALUE); ++ return SUCCESS; ++} ++ ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_root_true_atom(json_iterator &iter, const uint8_t *value) noexcept { ++ iter.log_value("true"); ++ if (!atomparsing::is_valid_true_atom(value, iter.remaining_len())) { return T_ATOM_ERROR; } ++ tape.append(0, internal::tape_type::TRUE_VALUE); ++ return SUCCESS; ++} ++ ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_false_atom(json_iterator &iter, const uint8_t *value) noexcept { ++ iter.log_value("false"); ++ if (!atomparsing::is_valid_false_atom(value)) { return F_ATOM_ERROR; } ++ tape.append(0, internal::tape_type::FALSE_VALUE); ++ return SUCCESS; ++} ++ ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_root_false_atom(json_iterator &iter, const uint8_t *value) noexcept { ++ iter.log_value("false"); ++ if (!atomparsing::is_valid_false_atom(value, iter.remaining_len())) { return F_ATOM_ERROR; } ++ tape.append(0, internal::tape_type::FALSE_VALUE); ++ return SUCCESS; ++} ++ ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_null_atom(json_iterator &iter, const uint8_t *value) noexcept { ++ iter.log_value("null"); ++ if (!atomparsing::is_valid_null_atom(value)) { return N_ATOM_ERROR; } ++ tape.append(0, internal::tape_type::NULL_VALUE); ++ return SUCCESS; ++} ++ ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_root_null_atom(json_iterator &iter, const uint8_t *value) noexcept { ++ iter.log_value("null"); ++ if (!atomparsing::is_valid_null_atom(value, iter.remaining_len())) { return N_ATOM_ERROR; } ++ tape.append(0, internal::tape_type::NULL_VALUE); ++ return SUCCESS; ++} ++ ++// private: ++ ++simdjson_inline uint32_t tape_builder::next_tape_index(json_iterator &iter) const noexcept { ++ return uint32_t(tape.next_tape_loc - iter.dom_parser.doc->tape.get()); ++} ++ ++simdjson_warn_unused simdjson_inline error_code tape_builder::empty_container(json_iterator &iter, internal::tape_type start, internal::tape_type end) noexcept { ++ auto start_index = next_tape_index(iter); ++ tape.append(start_index+2, start); ++ tape.append(start_index, end); ++ return SUCCESS; ++} ++ ++simdjson_inline void tape_builder::start_container(json_iterator &iter) noexcept { ++ iter.dom_parser.open_containers[iter.depth].tape_index = next_tape_index(iter); ++ iter.dom_parser.open_containers[iter.depth].count = 0; ++ tape.skip(); // We don't actually *write* the start element until the end. ++} ++ ++simdjson_warn_unused simdjson_inline error_code tape_builder::end_container(json_iterator &iter, internal::tape_type start, internal::tape_type end) noexcept { ++ // Write the ending tape element, pointing at the start location ++ const uint32_t start_tape_index = iter.dom_parser.open_containers[iter.depth].tape_index; ++ tape.append(start_tape_index, end); ++ // Write the start tape element, pointing at the end location (and including count) ++ // count can overflow if it exceeds 24 bits... so we saturate ++ // the convention being that a cnt of 0xffffff or more is undetermined in value (>= 0xffffff). ++ const uint32_t count = iter.dom_parser.open_containers[iter.depth].count; ++ const uint32_t cntsat = count > 0xFFFFFF ? 0xFFFFFF : count; ++ tape_writer::write(iter.dom_parser.doc->tape[start_tape_index], next_tape_index(iter) | (uint64_t(cntsat) << 32), start); ++ return SUCCESS; ++} ++ ++simdjson_inline uint8_t *tape_builder::on_start_string(json_iterator &iter) noexcept { ++ // we advance the point, accounting for the fact that we have a NULL termination ++ tape.append(current_string_buf_loc - iter.dom_parser.doc->string_buf.get(), internal::tape_type::STRING); ++ return current_string_buf_loc + sizeof(uint32_t); ++} ++ ++simdjson_inline void tape_builder::on_end_string(uint8_t *dst) noexcept { ++ uint32_t str_length = uint32_t(dst - (current_string_buf_loc + sizeof(uint32_t))); ++ // TODO check for overflow in case someone has a crazy string (>=4GB?) ++ // But only add the overflow check when the document itself exceeds 4GB ++ // Currently unneeded because we refuse to parse docs larger or equal to 4GB. ++ memcpy(current_string_buf_loc, &str_length, sizeof(uint32_t)); ++ // NULL termination is still handy if you expect all your strings to ++ // be NULL terminated? It comes at a small cost ++ *dst = 0; ++ current_string_buf_loc = dst + 1; ++} ++ ++} // namespace stage2 ++} // unnamed namespace ++} // namespace icelake ++} // namespace simdjson ++/* end file src/generic/stage2/tape_builder.h */ ++ ++// ++// Implementation-specific overrides ++// ++namespace simdjson { ++namespace icelake { ++namespace { ++namespace stage1 { ++ ++simdjson_inline uint64_t json_string_scanner::find_escaped(uint64_t backslash) { ++ if (!backslash) { uint64_t escaped = prev_escaped; prev_escaped = 0; return escaped; } ++ return find_escaped_branchless(backslash); ++} ++ ++} // namespace stage1 ++} // unnamed namespace ++ ++simdjson_warn_unused error_code implementation::minify(const uint8_t *buf, size_t len, uint8_t *dst, size_t &dst_len) const noexcept { ++ return icelake::stage1::json_minifier::minify<128>(buf, len, dst, dst_len); ++} ++ ++simdjson_warn_unused error_code dom_parser_implementation::stage1(const uint8_t *_buf, size_t _len, stage1_mode streaming) noexcept { ++ this->buf = _buf; ++ this->len = _len; ++ return icelake::stage1::json_structural_indexer::index<128>(_buf, _len, *this, streaming); ++} ++ ++simdjson_warn_unused bool implementation::validate_utf8(const char *buf, size_t len) const noexcept { ++ return icelake::stage1::generic_validate_utf8(buf,len); ++} ++ ++simdjson_warn_unused error_code dom_parser_implementation::stage2(dom::document &_doc) noexcept { ++ return stage2::tape_builder::parse_document(*this, _doc); ++} ++ ++simdjson_warn_unused error_code dom_parser_implementation::stage2_next(dom::document &_doc) noexcept { ++ return stage2::tape_builder::parse_document(*this, _doc); ++} ++ ++simdjson_warn_unused uint8_t *dom_parser_implementation::parse_string(const uint8_t *src, uint8_t *dst) const noexcept { ++ return icelake::stringparsing::parse_string(src, dst); ++} ++ ++simdjson_warn_unused error_code dom_parser_implementation::parse(const uint8_t *_buf, size_t _len, dom::document &_doc) noexcept { ++ auto error = stage1(_buf, _len, stage1_mode::regular); ++ if (error) { return error; } ++ return stage2(_doc); ++} ++ ++} // namespace icelake ++} // namespace simdjson ++ ++/* begin file include/simdjson/icelake/end.h */ ++SIMDJSON_UNTARGET_ICELAKE ++/* end file include/simdjson/icelake/end.h */ ++/* end file src/icelake/dom_parser_implementation.cpp */ ++#endif ++#if SIMDJSON_IMPLEMENTATION_HASWELL ++/* begin file src/haswell/implementation.cpp */ ++/* begin file include/simdjson/haswell/begin.h */ ++// redefining SIMDJSON_IMPLEMENTATION to "haswell" ++// #define SIMDJSON_IMPLEMENTATION haswell ++SIMDJSON_TARGET_HASWELL ++/* end file include/simdjson/haswell/begin.h */ ++ ++namespace simdjson { ++namespace haswell { ++ ++simdjson_warn_unused error_code implementation::create_dom_parser_implementation( ++ size_t capacity, ++ size_t max_depth, ++ std::unique_ptr& dst ++) const noexcept { ++ dst.reset( new (std::nothrow) dom_parser_implementation() ); ++ if (!dst) { return MEMALLOC; } ++ if (auto err = dst->set_capacity(capacity)) ++ return err; ++ if (auto err = dst->set_max_depth(max_depth)) ++ return err; ++ return SUCCESS; ++} ++ ++} // namespace haswell ++} // namespace simdjson ++ ++/* begin file include/simdjson/haswell/end.h */ ++SIMDJSON_UNTARGET_HASWELL ++/* end file include/simdjson/haswell/end.h */ ++ ++/* end file src/haswell/implementation.cpp */ ++/* begin file src/haswell/dom_parser_implementation.cpp */ ++/* begin file include/simdjson/haswell/begin.h */ ++// redefining SIMDJSON_IMPLEMENTATION to "haswell" ++// #define SIMDJSON_IMPLEMENTATION haswell ++SIMDJSON_TARGET_HASWELL ++/* end file include/simdjson/haswell/begin.h */ ++ ++// ++// Stage 1 ++// ++ ++namespace simdjson { ++namespace haswell { ++namespace { ++ ++using namespace simd; ++ ++struct json_character_block { ++ static simdjson_inline json_character_block classify(const simd::simd8x64& in); ++ // ASCII white-space ('\r','\n','\t',' ') ++ simdjson_inline uint64_t whitespace() const noexcept; ++ // non-quote structural characters (comma, colon, braces, brackets) ++ simdjson_inline uint64_t op() const noexcept; ++ // neither a structural character nor a white-space, so letters, numbers and quotes ++ simdjson_inline uint64_t scalar() const noexcept; ++ ++ uint64_t _whitespace; // ASCII white-space ('\r','\n','\t',' ') ++ uint64_t _op; // structural characters (comma, colon, braces, brackets but not quotes) ++}; ++ ++simdjson_inline uint64_t json_character_block::whitespace() const noexcept { return _whitespace; } ++simdjson_inline uint64_t json_character_block::op() const noexcept { return _op; } ++simdjson_inline uint64_t json_character_block::scalar() const noexcept { return ~(op() | whitespace()); } ++ ++// This identifies structural characters (comma, colon, braces, brackets), ++// and ASCII white-space ('\r','\n','\t',' '). ++simdjson_inline json_character_block json_character_block::classify(const simd::simd8x64& in) { ++ // These lookups rely on the fact that anything < 127 will match the lower 4 bits, which is why ++ // we can't use the generic lookup_16. ++ const auto whitespace_table = simd8::repeat_16(' ', 100, 100, 100, 17, 100, 113, 2, 100, '\t', '\n', 112, 100, '\r', 100, 100); ++ ++ // The 6 operators (:,[]{}) have these values: ++ // ++ // , 2C ++ // : 3A ++ // [ 5B ++ // { 7B ++ // ] 5D ++ // } 7D ++ // ++ // If you use | 0x20 to turn [ and ] into { and }, the lower 4 bits of each character is unique. ++ // We exploit this, using a simd 4-bit lookup to tell us which character match against, and then ++ // match it (against | 0x20). ++ // ++ // To prevent recognizing other characters, everything else gets compared with 0, which cannot ++ // match due to the | 0x20. ++ // ++ // NOTE: Due to the | 0x20, this ALSO treats and (control characters 0C and 1A) like , ++ // and :. This gets caught in stage 2, which checks the actual character to ensure the right ++ // operators are in the right places. ++ const auto op_table = simd8::repeat_16( ++ 0, 0, 0, 0, ++ 0, 0, 0, 0, ++ 0, 0, ':', '{', // : = 3A, [ = 5B, { = 7B ++ ',', '}', 0, 0 // , = 2C, ] = 5D, } = 7D ++ ); ++ ++ // We compute whitespace and op separately. If later code only uses one or the ++ // other, given the fact that all functions are aggressively inlined, we can ++ // hope that useless computations will be omitted. This is namely case when ++ // minifying (we only need whitespace). ++ ++ const uint64_t whitespace = in.eq({ ++ _mm256_shuffle_epi8(whitespace_table, in.chunks[0]), ++ _mm256_shuffle_epi8(whitespace_table, in.chunks[1]) ++ }); ++ // Turn [ and ] into { and } ++ const simd8x64 curlified{ ++ in.chunks[0] | 0x20, ++ in.chunks[1] | 0x20 ++ }; ++ const uint64_t op = curlified.eq({ ++ _mm256_shuffle_epi8(op_table, in.chunks[0]), ++ _mm256_shuffle_epi8(op_table, in.chunks[1]) ++ }); ++ ++ return { whitespace, op }; ++} ++ ++simdjson_inline bool is_ascii(const simd8x64& input) { ++ return input.reduce_or().is_ascii(); ++} ++ ++simdjson_unused simdjson_inline simd8 must_be_continuation(const simd8 prev1, const simd8 prev2, const simd8 prev3) { ++ simd8 is_second_byte = prev1.saturating_sub(0xc0u-1); // Only 11______ will be > 0 ++ simd8 is_third_byte = prev2.saturating_sub(0xe0u-1); // Only 111_____ will be > 0 ++ simd8 is_fourth_byte = prev3.saturating_sub(0xf0u-1); // Only 1111____ will be > 0 ++ // Caller requires a bool (all 1's). All values resulting from the subtraction will be <= 64, so signed comparison is fine. ++ return simd8(is_second_byte | is_third_byte | is_fourth_byte) > int8_t(0); ++} ++ ++simdjson_inline simd8 must_be_2_3_continuation(const simd8 prev2, const simd8 prev3) { ++ simd8 is_third_byte = prev2.saturating_sub(0xe0u-1); // Only 111_____ will be > 0 ++ simd8 is_fourth_byte = prev3.saturating_sub(0xf0u-1); // Only 1111____ will be > 0 ++ // Caller requires a bool (all 1's). All values resulting from the subtraction will be <= 64, so signed comparison is fine. ++ return simd8(is_third_byte | is_fourth_byte) > int8_t(0); ++} ++ ++} // unnamed namespace ++} // namespace haswell ++} // namespace simdjson ++ ++/* begin file src/generic/stage1/utf8_lookup4_algorithm.h */ ++namespace simdjson { ++namespace haswell { ++namespace { ++namespace utf8_validation { ++ ++using namespace simd; ++ ++ simdjson_inline simd8 check_special_cases(const simd8 input, const simd8 prev1) { ++// Bit 0 = Too Short (lead byte/ASCII followed by lead byte/ASCII) ++// Bit 1 = Too Long (ASCII followed by continuation) ++// Bit 2 = Overlong 3-byte ++// Bit 4 = Surrogate ++// Bit 5 = Overlong 2-byte ++// Bit 7 = Two Continuations ++ constexpr const uint8_t TOO_SHORT = 1<<0; // 11______ 0_______ ++ // 11______ 11______ ++ constexpr const uint8_t TOO_LONG = 1<<1; // 0_______ 10______ ++ constexpr const uint8_t OVERLONG_3 = 1<<2; // 11100000 100_____ ++ constexpr const uint8_t SURROGATE = 1<<4; // 11101101 101_____ ++ constexpr const uint8_t OVERLONG_2 = 1<<5; // 1100000_ 10______ ++ constexpr const uint8_t TWO_CONTS = 1<<7; // 10______ 10______ ++ constexpr const uint8_t TOO_LARGE = 1<<3; // 11110100 1001____ ++ // 11110100 101_____ ++ // 11110101 1001____ ++ // 11110101 101_____ ++ // 1111011_ 1001____ ++ // 1111011_ 101_____ ++ // 11111___ 1001____ ++ // 11111___ 101_____ ++ constexpr const uint8_t TOO_LARGE_1000 = 1<<6; ++ // 11110101 1000____ ++ // 1111011_ 1000____ ++ // 11111___ 1000____ ++ constexpr const uint8_t OVERLONG_4 = 1<<6; // 11110000 1000____ ++ ++ const simd8 byte_1_high = prev1.shr<4>().lookup_16( ++ // 0_______ ________ ++ TOO_LONG, TOO_LONG, TOO_LONG, TOO_LONG, ++ TOO_LONG, TOO_LONG, TOO_LONG, TOO_LONG, ++ // 10______ ________ ++ TWO_CONTS, TWO_CONTS, TWO_CONTS, TWO_CONTS, ++ // 1100____ ________ ++ TOO_SHORT | OVERLONG_2, ++ // 1101____ ________ ++ TOO_SHORT, ++ // 1110____ ________ ++ TOO_SHORT | OVERLONG_3 | SURROGATE, ++ // 1111____ ________ ++ TOO_SHORT | TOO_LARGE | TOO_LARGE_1000 | OVERLONG_4 ++ ); ++ constexpr const uint8_t CARRY = TOO_SHORT | TOO_LONG | TWO_CONTS; // These all have ____ in byte 1 . ++ const simd8 byte_1_low = (prev1 & 0x0F).lookup_16( ++ // ____0000 ________ ++ CARRY | OVERLONG_3 | OVERLONG_2 | OVERLONG_4, ++ // ____0001 ________ ++ CARRY | OVERLONG_2, ++ // ____001_ ________ ++ CARRY, ++ CARRY, ++ ++ // ____0100 ________ ++ CARRY | TOO_LARGE, ++ // ____0101 ________ ++ CARRY | TOO_LARGE | TOO_LARGE_1000, ++ // ____011_ ________ ++ CARRY | TOO_LARGE | TOO_LARGE_1000, ++ CARRY | TOO_LARGE | TOO_LARGE_1000, ++ ++ // ____1___ ________ ++ CARRY | TOO_LARGE | TOO_LARGE_1000, ++ CARRY | TOO_LARGE | TOO_LARGE_1000, ++ CARRY | TOO_LARGE | TOO_LARGE_1000, ++ CARRY | TOO_LARGE | TOO_LARGE_1000, ++ CARRY | TOO_LARGE | TOO_LARGE_1000, ++ // ____1101 ________ ++ CARRY | TOO_LARGE | TOO_LARGE_1000 | SURROGATE, ++ CARRY | TOO_LARGE | TOO_LARGE_1000, ++ CARRY | TOO_LARGE | TOO_LARGE_1000 ++ ); ++ const simd8 byte_2_high = input.shr<4>().lookup_16( ++ // ________ 0_______ ++ TOO_SHORT, TOO_SHORT, TOO_SHORT, TOO_SHORT, ++ TOO_SHORT, TOO_SHORT, TOO_SHORT, TOO_SHORT, ++ ++ // ________ 1000____ ++ TOO_LONG | OVERLONG_2 | TWO_CONTS | OVERLONG_3 | TOO_LARGE_1000 | OVERLONG_4, ++ // ________ 1001____ ++ TOO_LONG | OVERLONG_2 | TWO_CONTS | OVERLONG_3 | TOO_LARGE, ++ // ________ 101_____ ++ TOO_LONG | OVERLONG_2 | TWO_CONTS | SURROGATE | TOO_LARGE, ++ TOO_LONG | OVERLONG_2 | TWO_CONTS | SURROGATE | TOO_LARGE, ++ ++ // ________ 11______ ++ TOO_SHORT, TOO_SHORT, TOO_SHORT, TOO_SHORT ++ ); ++ return (byte_1_high & byte_1_low & byte_2_high); ++ } ++ simdjson_inline simd8 check_multibyte_lengths(const simd8 input, ++ const simd8 prev_input, const simd8 sc) { ++ simd8 prev2 = input.prev<2>(prev_input); ++ simd8 prev3 = input.prev<3>(prev_input); ++ simd8 must23 = simd8(must_be_2_3_continuation(prev2, prev3)); ++ simd8 must23_80 = must23 & uint8_t(0x80); ++ return must23_80 ^ sc; ++ } ++ ++ // ++ // Return nonzero if there are incomplete multibyte characters at the end of the block: ++ // e.g. if there is a 4-byte character, but it's 3 bytes from the end. ++ // ++ simdjson_inline simd8 is_incomplete(const simd8 input) { ++ // If the previous input's last 3 bytes match this, they're too short (they ended at EOF): ++ // ... 1111____ 111_____ 11______ ++#if SIMDJSON_IMPLEMENTATION_ICELAKE ++ static const uint8_t max_array[64] = { ++ 255, 255, 255, 255, 255, 255, 255, 255, ++ 255, 255, 255, 255, 255, 255, 255, 255, ++ 255, 255, 255, 255, 255, 255, 255, 255, ++ 255, 255, 255, 255, 255, 255, 255, 255, ++ 255, 255, 255, 255, 255, 255, 255, 255, ++ 255, 255, 255, 255, 255, 255, 255, 255, ++ 255, 255, 255, 255, 255, 255, 255, 255, ++ 255, 255, 255, 255, 255, 0xf0u-1, 0xe0u-1, 0xc0u-1 ++ }; ++#else ++ static const uint8_t max_array[32] = { ++ 255, 255, 255, 255, 255, 255, 255, 255, ++ 255, 255, 255, 255, 255, 255, 255, 255, ++ 255, 255, 255, 255, 255, 255, 255, 255, ++ 255, 255, 255, 255, 255, 0xf0u-1, 0xe0u-1, 0xc0u-1 ++ }; ++#endif ++ const simd8 max_value(&max_array[sizeof(max_array)-sizeof(simd8)]); ++ return input.gt_bits(max_value); ++ } ++ ++ struct utf8_checker { ++ // If this is nonzero, there has been a UTF-8 error. ++ simd8 error; ++ // The last input we received ++ simd8 prev_input_block; ++ // Whether the last input we received was incomplete (used for ASCII fast path) ++ simd8 prev_incomplete; ++ ++ // ++ // Check whether the current bytes are valid UTF-8. ++ // ++ simdjson_inline void check_utf8_bytes(const simd8 input, const simd8 prev_input) { ++ // Flip prev1...prev3 so we can easily determine if they are 2+, 3+ or 4+ lead bytes ++ // (2, 3, 4-byte leads become large positive numbers instead of small negative numbers) ++ simd8 prev1 = input.prev<1>(prev_input); ++ simd8 sc = check_special_cases(input, prev1); ++ this->error |= check_multibyte_lengths(input, prev_input, sc); ++ } ++ ++ // The only problem that can happen at EOF is that a multibyte character is too short ++ // or a byte value too large in the last bytes: check_special_cases only checks for bytes ++ // too large in the first of two bytes. ++ simdjson_inline void check_eof() { ++ // If the previous block had incomplete UTF-8 characters at the end, an ASCII block can't ++ // possibly finish them. ++ this->error |= this->prev_incomplete; ++ } ++ ++#ifndef SIMDJSON_IF_CONSTEXPR ++#if SIMDJSON_CPLUSPLUS17 ++#define SIMDJSON_IF_CONSTEXPR if constexpr ++#else ++#define SIMDJSON_IF_CONSTEXPR if ++#endif ++#endif ++ ++ simdjson_inline void check_next_input(const simd8x64& input) { ++ if(simdjson_likely(is_ascii(input))) { ++ this->error |= this->prev_incomplete; ++ } else { ++ // you might think that a for-loop would work, but under Visual Studio, it is not good enough. ++ static_assert((simd8x64::NUM_CHUNKS == 1) ++ ||(simd8x64::NUM_CHUNKS == 2) ++ || (simd8x64::NUM_CHUNKS == 4), ++ "We support one, two or four chunks per 64-byte block."); ++ SIMDJSON_IF_CONSTEXPR (simd8x64::NUM_CHUNKS == 1) { ++ this->check_utf8_bytes(input.chunks[0], this->prev_input_block); ++ } else SIMDJSON_IF_CONSTEXPR (simd8x64::NUM_CHUNKS == 2) { ++ this->check_utf8_bytes(input.chunks[0], this->prev_input_block); ++ this->check_utf8_bytes(input.chunks[1], input.chunks[0]); ++ } else SIMDJSON_IF_CONSTEXPR (simd8x64::NUM_CHUNKS == 4) { ++ this->check_utf8_bytes(input.chunks[0], this->prev_input_block); ++ this->check_utf8_bytes(input.chunks[1], input.chunks[0]); ++ this->check_utf8_bytes(input.chunks[2], input.chunks[1]); ++ this->check_utf8_bytes(input.chunks[3], input.chunks[2]); ++ } ++ this->prev_incomplete = is_incomplete(input.chunks[simd8x64::NUM_CHUNKS-1]); ++ this->prev_input_block = input.chunks[simd8x64::NUM_CHUNKS-1]; ++ } ++ } ++ // do not forget to call check_eof! ++ simdjson_inline error_code errors() { ++ return this->error.any_bits_set_anywhere() ? error_code::UTF8_ERROR : error_code::SUCCESS; ++ } ++ ++ }; // struct utf8_checker ++} // namespace utf8_validation ++ ++using utf8_validation::utf8_checker; ++ ++} // unnamed namespace ++} // namespace haswell ++} // namespace simdjson ++/* end file src/generic/stage1/utf8_lookup4_algorithm.h */ ++/* begin file src/generic/stage1/json_structural_indexer.h */ ++// This file contains the common code every implementation uses in stage1 ++// It is intended to be included multiple times and compiled multiple times ++// We assume the file in which it is included already includes ++// "simdjson/stage1.h" (this simplifies amalgation) ++ ++/* begin file src/generic/stage1/buf_block_reader.h */ ++namespace simdjson { ++namespace haswell { ++namespace { ++ ++// Walks through a buffer in block-sized increments, loading the last part with spaces ++template ++struct buf_block_reader { ++public: ++ simdjson_inline buf_block_reader(const uint8_t *_buf, size_t _len); ++ simdjson_inline size_t block_index(); ++ simdjson_inline bool has_full_block() const; ++ simdjson_inline const uint8_t *full_block() const; ++ /** ++ * Get the last block, padded with spaces. ++ * ++ * There will always be a last block, with at least 1 byte, unless len == 0 (in which case this ++ * function fills the buffer with spaces and returns 0. In particular, if len == STEP_SIZE there ++ * will be 0 full_blocks and 1 remainder block with STEP_SIZE bytes and no spaces for padding. ++ * ++ * @return the number of effective characters in the last block. ++ */ ++ simdjson_inline size_t get_remainder(uint8_t *dst) const; ++ simdjson_inline void advance(); ++private: ++ const uint8_t *buf; ++ const size_t len; ++ const size_t lenminusstep; ++ size_t idx; ++}; ++ ++// Routines to print masks and text for debugging bitmask operations ++simdjson_unused static char * format_input_text_64(const uint8_t *text) { ++ static char buf[sizeof(simd8x64) + 1]; ++ for (size_t i=0; i); i++) { ++ buf[i] = int8_t(text[i]) < ' ' ? '_' : int8_t(text[i]); ++ } ++ buf[sizeof(simd8x64)] = '\0'; ++ return buf; ++} ++ ++// Routines to print masks and text for debugging bitmask operations ++simdjson_unused static char * format_input_text(const simd8x64& in) { ++ static char buf[sizeof(simd8x64) + 1]; ++ in.store(reinterpret_cast(buf)); ++ for (size_t i=0; i); i++) { ++ if (buf[i] < ' ') { buf[i] = '_'; } ++ } ++ buf[sizeof(simd8x64)] = '\0'; ++ return buf; ++} ++ ++simdjson_unused static char * format_mask(uint64_t mask) { ++ static char buf[sizeof(simd8x64) + 1]; ++ for (size_t i=0; i<64; i++) { ++ buf[i] = (mask & (size_t(1) << i)) ? 'X' : ' '; ++ } ++ buf[64] = '\0'; ++ return buf; ++} ++ ++template ++simdjson_inline buf_block_reader::buf_block_reader(const uint8_t *_buf, size_t _len) : buf{_buf}, len{_len}, lenminusstep{len < STEP_SIZE ? 0 : len - STEP_SIZE}, idx{0} {} ++ ++template ++simdjson_inline size_t buf_block_reader::block_index() { return idx; } ++ ++template ++simdjson_inline bool buf_block_reader::has_full_block() const { ++ return idx < lenminusstep; ++} ++ ++template ++simdjson_inline const uint8_t *buf_block_reader::full_block() const { ++ return &buf[idx]; ++} ++ ++template ++simdjson_inline size_t buf_block_reader::get_remainder(uint8_t *dst) const { ++ if(len == idx) { return 0; } // memcpy(dst, null, 0) will trigger an error with some sanitizers ++ std::memset(dst, 0x20, STEP_SIZE); // std::memset STEP_SIZE because it's more efficient to write out 8 or 16 bytes at once. ++ std::memcpy(dst, buf + idx, len - idx); ++ return len - idx; ++} ++ ++template ++simdjson_inline void buf_block_reader::advance() { ++ idx += STEP_SIZE; ++} ++ ++} // unnamed namespace ++} // namespace haswell ++} // namespace simdjson ++/* end file src/generic/stage1/buf_block_reader.h */ ++/* begin file src/generic/stage1/json_string_scanner.h */ ++namespace simdjson { ++namespace haswell { ++namespace { ++namespace stage1 { ++ ++struct json_string_block { ++ // We spell out the constructors in the hope of resolving inlining issues with Visual Studio 2017 ++ simdjson_inline json_string_block(uint64_t backslash, uint64_t escaped, uint64_t quote, uint64_t in_string) : ++ _backslash(backslash), _escaped(escaped), _quote(quote), _in_string(in_string) {} ++ ++ // Escaped characters (characters following an escape() character) ++ simdjson_inline uint64_t escaped() const { return _escaped; } ++ // Escape characters (backslashes that are not escaped--i.e. in \\, includes only the first \) ++ simdjson_inline uint64_t escape() const { return _backslash & ~_escaped; } ++ // Real (non-backslashed) quotes ++ simdjson_inline uint64_t quote() const { return _quote; } ++ // Start quotes of strings ++ simdjson_inline uint64_t string_start() const { return _quote & _in_string; } ++ // End quotes of strings ++ simdjson_inline uint64_t string_end() const { return _quote & ~_in_string; } ++ // Only characters inside the string (not including the quotes) ++ simdjson_inline uint64_t string_content() const { return _in_string & ~_quote; } ++ // Return a mask of whether the given characters are inside a string (only works on non-quotes) ++ simdjson_inline uint64_t non_quote_inside_string(uint64_t mask) const { return mask & _in_string; } ++ // Return a mask of whether the given characters are inside a string (only works on non-quotes) ++ simdjson_inline uint64_t non_quote_outside_string(uint64_t mask) const { return mask & ~_in_string; } ++ // Tail of string (everything except the start quote) ++ simdjson_inline uint64_t string_tail() const { return _in_string ^ _quote; } ++ ++ // backslash characters ++ uint64_t _backslash; ++ // escaped characters (backslashed--does not include the hex characters after \u) ++ uint64_t _escaped; ++ // real quotes (non-backslashed ones) ++ uint64_t _quote; ++ // string characters (includes start quote but not end quote) ++ uint64_t _in_string; ++}; ++ ++// Scans blocks for string characters, storing the state necessary to do so ++class json_string_scanner { ++public: ++ simdjson_inline json_string_block next(const simd::simd8x64& in); ++ // Returns either UNCLOSED_STRING or SUCCESS ++ simdjson_inline error_code finish(); ++ ++private: ++ // Intended to be defined by the implementation ++ simdjson_inline uint64_t find_escaped(uint64_t escape); ++ simdjson_inline uint64_t find_escaped_branchless(uint64_t escape); ++ ++ // Whether the last iteration was still inside a string (all 1's = true, all 0's = false). ++ uint64_t prev_in_string = 0ULL; ++ // Whether the first character of the next iteration is escaped. ++ uint64_t prev_escaped = 0ULL; ++}; ++ ++// ++// Finds escaped characters (characters following \). ++// ++// Handles runs of backslashes like \\\" and \\\\" correctly (yielding 0101 and 01010, respectively). ++// ++// Does this by: ++// - Shift the escape mask to get potentially escaped characters (characters after backslashes). ++// - Mask escaped sequences that start on *even* bits with 1010101010 (odd bits are escaped, even bits are not) ++// - Mask escaped sequences that start on *odd* bits with 0101010101 (even bits are escaped, odd bits are not) ++// ++// To distinguish between escaped sequences starting on even/odd bits, it finds the start of all ++// escape sequences, filters out the ones that start on even bits, and adds that to the mask of ++// escape sequences. This causes the addition to clear out the sequences starting on odd bits (since ++// the start bit causes a carry), and leaves even-bit sequences alone. ++// ++// Example: ++// ++// text | \\\ | \\\"\\\" \\\" \\"\\" | ++// escape | xxx | xx xxx xxx xx xx | Removed overflow backslash; will | it into follows_escape ++// odd_starts | x | x x x | escape & ~even_bits & ~follows_escape ++// even_seq | c| cxxx c xx c | c = carry bit -- will be masked out later ++// invert_mask | | cxxx c xx c| even_seq << 1 ++// follows_escape | xx | x xx xxx xxx xx xx | Includes overflow bit ++// escaped | x | x x x x x x x x | ++// desired | x | x x x x x x x x | ++// text | \\\ | \\\"\\\" \\\" \\"\\" | ++// ++simdjson_inline uint64_t json_string_scanner::find_escaped_branchless(uint64_t backslash) { ++ // If there was overflow, pretend the first character isn't a backslash ++ backslash &= ~prev_escaped; ++ uint64_t follows_escape = backslash << 1 | prev_escaped; ++ ++ // Get sequences starting on even bits by clearing out the odd series using + ++ const uint64_t even_bits = 0x5555555555555555ULL; ++ uint64_t odd_sequence_starts = backslash & ~even_bits & ~follows_escape; ++ uint64_t sequences_starting_on_even_bits; ++ prev_escaped = add_overflow(odd_sequence_starts, backslash, &sequences_starting_on_even_bits); ++ uint64_t invert_mask = sequences_starting_on_even_bits << 1; // The mask we want to return is the *escaped* bits, not escapes. ++ ++ // Mask every other backslashed character as an escaped character ++ // Flip the mask for sequences that start on even bits, to correct them ++ return (even_bits ^ invert_mask) & follows_escape; ++} ++ ++// ++// Return a mask of all string characters plus end quotes. ++// ++// prev_escaped is overflow saying whether the next character is escaped. ++// prev_in_string is overflow saying whether we're still in a string. ++// ++// Backslash sequences outside of quotes will be detected in stage 2. ++// ++simdjson_inline json_string_block json_string_scanner::next(const simd::simd8x64& in) { ++ const uint64_t backslash = in.eq('\\'); ++ const uint64_t escaped = find_escaped(backslash); ++ const uint64_t quote = in.eq('"') & ~escaped; ++ ++ // ++ // prefix_xor flips on bits inside the string (and flips off the end quote). ++ // ++ // Then we xor with prev_in_string: if we were in a string already, its effect is flipped ++ // (characters inside strings are outside, and characters outside strings are inside). ++ // ++ const uint64_t in_string = prefix_xor(quote) ^ prev_in_string; ++ ++ // ++ // Check if we're still in a string at the end of the box so the next block will know ++ // ++ // right shift of a signed value expected to be well-defined and standard ++ // compliant as of C++20, John Regher from Utah U. says this is fine code ++ // ++ prev_in_string = uint64_t(static_cast(in_string) >> 63); ++ ++ // Use ^ to turn the beginning quote off, and the end quote on. ++ ++ // We are returning a function-local object so either we get a move constructor ++ // or we get copy elision. ++ return json_string_block( ++ backslash, ++ escaped, ++ quote, ++ in_string ++ ); ++} ++ ++simdjson_inline error_code json_string_scanner::finish() { ++ if (prev_in_string) { ++ return UNCLOSED_STRING; ++ } ++ return SUCCESS; ++} ++ ++} // namespace stage1 ++} // unnamed namespace ++} // namespace haswell ++} // namespace simdjson ++/* end file src/generic/stage1/json_string_scanner.h */ ++/* begin file src/generic/stage1/json_scanner.h */ ++namespace simdjson { ++namespace haswell { ++namespace { ++namespace stage1 { ++ ++/** ++ * A block of scanned json, with information on operators and scalars. ++ * ++ * We seek to identify pseudo-structural characters. Anything that is inside ++ * a string must be omitted (hence & ~_string.string_tail()). ++ * Otherwise, pseudo-structural characters come in two forms. ++ * 1. We have the structural characters ([,],{,},:, comma). The ++ * term 'structural character' is from the JSON RFC. ++ * 2. We have the 'scalar pseudo-structural characters'. ++ * Scalars are quotes, and any character except structural characters and white space. ++ * ++ * To identify the scalar pseudo-structural characters, we must look at what comes ++ * before them: it must be a space, a quote or a structural characters. ++ * Starting with simdjson v0.3, we identify them by ++ * negation: we identify everything that is followed by a non-quote scalar, ++ * and we negate that. Whatever remains must be a 'scalar pseudo-structural character'. ++ */ ++struct json_block { ++public: ++ // We spell out the constructors in the hope of resolving inlining issues with Visual Studio 2017 ++ simdjson_inline json_block(json_string_block&& string, json_character_block characters, uint64_t follows_potential_nonquote_scalar) : ++ _string(std::move(string)), _characters(characters), _follows_potential_nonquote_scalar(follows_potential_nonquote_scalar) {} ++ simdjson_inline json_block(json_string_block string, json_character_block characters, uint64_t follows_potential_nonquote_scalar) : ++ _string(string), _characters(characters), _follows_potential_nonquote_scalar(follows_potential_nonquote_scalar) {} ++ ++ /** ++ * The start of structurals. ++ * In simdjson prior to v0.3, these were called the pseudo-structural characters. ++ **/ ++ simdjson_inline uint64_t structural_start() const noexcept { return potential_structural_start() & ~_string.string_tail(); } ++ /** All JSON whitespace (i.e. not in a string) */ ++ simdjson_inline uint64_t whitespace() const noexcept { return non_quote_outside_string(_characters.whitespace()); } ++ ++ // Helpers ++ ++ /** Whether the given characters are inside a string (only works on non-quotes) */ ++ simdjson_inline uint64_t non_quote_inside_string(uint64_t mask) const noexcept { return _string.non_quote_inside_string(mask); } ++ /** Whether the given characters are outside a string (only works on non-quotes) */ ++ simdjson_inline uint64_t non_quote_outside_string(uint64_t mask) const noexcept { return _string.non_quote_outside_string(mask); } ++ ++ // string and escape characters ++ json_string_block _string; ++ // whitespace, structural characters ('operators'), scalars ++ json_character_block _characters; ++ // whether the previous character was a scalar ++ uint64_t _follows_potential_nonquote_scalar; ++private: ++ // Potential structurals (i.e. disregarding strings) ++ ++ /** ++ * structural elements ([,],{,},:, comma) plus scalar starts like 123, true and "abc". ++ * They may reside inside a string. ++ **/ ++ simdjson_inline uint64_t potential_structural_start() const noexcept { return _characters.op() | potential_scalar_start(); } ++ /** ++ * The start of non-operator runs, like 123, true and "abc". ++ * It main reside inside a string. ++ **/ ++ simdjson_inline uint64_t potential_scalar_start() const noexcept { ++ // The term "scalar" refers to anything except structural characters and white space ++ // (so letters, numbers, quotes). ++ // Whenever it is preceded by something that is not a structural element ({,},[,],:, ") nor a white-space ++ // then we know that it is irrelevant structurally. ++ return _characters.scalar() & ~follows_potential_scalar(); ++ } ++ /** ++ * Whether the given character is immediately after a non-operator like 123, true. ++ * The characters following a quote are not included. ++ */ ++ simdjson_inline uint64_t follows_potential_scalar() const noexcept { ++ // _follows_potential_nonquote_scalar: is defined as marking any character that follows a character ++ // that is not a structural element ({,},[,],:, comma) nor a quote (") and that is not a ++ // white space. ++ // It is understood that within quoted region, anything at all could be marked (irrelevant). ++ return _follows_potential_nonquote_scalar; ++ } ++}; ++ ++/** ++ * Scans JSON for important bits: structural characters or 'operators', strings, and scalars. ++ * ++ * The scanner starts by calculating two distinct things: ++ * - string characters (taking \" into account) ++ * - structural characters or 'operators' ([]{},:, comma) ++ * and scalars (runs of non-operators like 123, true and "abc") ++ * ++ * To minimize data dependency (a key component of the scanner's speed), it finds these in parallel: ++ * in particular, the operator/scalar bit will find plenty of things that are actually part of ++ * strings. When we're done, json_block will fuse the two together by masking out tokens that are ++ * part of a string. ++ */ ++class json_scanner { ++public: ++ json_scanner() = default; ++ simdjson_inline json_block next(const simd::simd8x64& in); ++ // Returns either UNCLOSED_STRING or SUCCESS ++ simdjson_inline error_code finish(); ++ ++private: ++ // Whether the last character of the previous iteration is part of a scalar token ++ // (anything except whitespace or a structural character/'operator'). ++ uint64_t prev_scalar = 0ULL; ++ json_string_scanner string_scanner{}; ++}; ++ ++ ++// ++// Check if the current character immediately follows a matching character. ++// ++// For example, this checks for quotes with backslashes in front of them: ++// ++// const uint64_t backslashed_quote = in.eq('"') & immediately_follows(in.eq('\'), prev_backslash); ++// ++simdjson_inline uint64_t follows(const uint64_t match, uint64_t &overflow) { ++ const uint64_t result = match << 1 | overflow; ++ overflow = match >> 63; ++ return result; ++} ++ ++simdjson_inline json_block json_scanner::next(const simd::simd8x64& in) { ++ json_string_block strings = string_scanner.next(in); ++ // identifies the white-space and the structural characters ++ json_character_block characters = json_character_block::classify(in); ++ // The term "scalar" refers to anything except structural characters and white space ++ // (so letters, numbers, quotes). ++ // We want follows_scalar to mark anything that follows a non-quote scalar (so letters and numbers). ++ // ++ // A terminal quote should either be followed by a structural character (comma, brace, bracket, colon) ++ // or nothing. However, we still want ' "a string"true ' to mark the 't' of 'true' as a potential ++ // pseudo-structural character just like we would if we had ' "a string" true '; otherwise we ++ // may need to add an extra check when parsing strings. ++ // ++ // Performance: there are many ways to skin this cat. ++ const uint64_t nonquote_scalar = characters.scalar() & ~strings.quote(); ++ uint64_t follows_nonquote_scalar = follows(nonquote_scalar, prev_scalar); ++ // We are returning a function-local object so either we get a move constructor ++ // or we get copy elision. ++ return json_block( ++ strings,// strings is a function-local object so either it moves or the copy is elided. ++ characters, ++ follows_nonquote_scalar ++ ); ++} ++ ++simdjson_inline error_code json_scanner::finish() { ++ return string_scanner.finish(); ++} ++ ++} // namespace stage1 ++} // unnamed namespace ++} // namespace haswell ++} // namespace simdjson ++/* end file src/generic/stage1/json_scanner.h */ ++/* begin file src/generic/stage1/json_minifier.h */ ++// This file contains the common code every implementation uses in stage1 ++// It is intended to be included multiple times and compiled multiple times ++// We assume the file in which it is included already includes ++// "simdjson/stage1.h" (this simplifies amalgation) ++ ++namespace simdjson { ++namespace haswell { ++namespace { ++namespace stage1 { ++ ++class json_minifier { ++public: ++ template ++ static error_code minify(const uint8_t *buf, size_t len, uint8_t *dst, size_t &dst_len) noexcept; ++ ++private: ++ simdjson_inline json_minifier(uint8_t *_dst) ++ : dst{_dst} ++ {} ++ template ++ simdjson_inline void step(const uint8_t *block_buf, buf_block_reader &reader) noexcept; ++ simdjson_inline void next(const simd::simd8x64& in, const json_block& block); ++ simdjson_inline error_code finish(uint8_t *dst_start, size_t &dst_len); ++ json_scanner scanner{}; ++ uint8_t *dst; ++}; ++ ++simdjson_inline void json_minifier::next(const simd::simd8x64& in, const json_block& block) { ++ uint64_t mask = block.whitespace(); ++ dst += in.compress(mask, dst); ++} ++ ++simdjson_inline error_code json_minifier::finish(uint8_t *dst_start, size_t &dst_len) { ++ error_code error = scanner.finish(); ++ if (error) { dst_len = 0; return error; } ++ dst_len = dst - dst_start; ++ return SUCCESS; ++} ++ ++template<> ++simdjson_inline void json_minifier::step<128>(const uint8_t *block_buf, buf_block_reader<128> &reader) noexcept { ++ simd::simd8x64 in_1(block_buf); ++ simd::simd8x64 in_2(block_buf+64); ++ json_block block_1 = scanner.next(in_1); ++ json_block block_2 = scanner.next(in_2); ++ this->next(in_1, block_1); ++ this->next(in_2, block_2); ++ reader.advance(); ++} ++ ++template<> ++simdjson_inline void json_minifier::step<64>(const uint8_t *block_buf, buf_block_reader<64> &reader) noexcept { ++ simd::simd8x64 in_1(block_buf); ++ json_block block_1 = scanner.next(in_1); ++ this->next(block_buf, block_1); ++ reader.advance(); ++} ++ ++template ++error_code json_minifier::minify(const uint8_t *buf, size_t len, uint8_t *dst, size_t &dst_len) noexcept { ++ buf_block_reader reader(buf, len); ++ json_minifier minifier(dst); ++ ++ // Index the first n-1 blocks ++ while (reader.has_full_block()) { ++ minifier.step(reader.full_block(), reader); ++ } ++ ++ // Index the last (remainder) block, padded with spaces ++ uint8_t block[STEP_SIZE]; ++ size_t remaining_bytes = reader.get_remainder(block); ++ if (remaining_bytes > 0) { ++ // We do not want to write directly to the output stream. Rather, we write ++ // to a local buffer (for safety). ++ uint8_t out_block[STEP_SIZE]; ++ uint8_t * const guarded_dst{minifier.dst}; ++ minifier.dst = out_block; ++ minifier.step(block, reader); ++ size_t to_write = minifier.dst - out_block; ++ // In some cases, we could be enticed to consider the padded spaces ++ // as part of the string. This is fine as long as we do not write more ++ // than we consumed. ++ if(to_write > remaining_bytes) { to_write = remaining_bytes; } ++ memcpy(guarded_dst, out_block, to_write); ++ minifier.dst = guarded_dst + to_write; ++ } ++ return minifier.finish(dst, dst_len); ++} ++ ++} // namespace stage1 ++} // unnamed namespace ++} // namespace haswell ++} // namespace simdjson ++/* end file src/generic/stage1/json_minifier.h */ ++/* begin file src/generic/stage1/find_next_document_index.h */ ++namespace simdjson { ++namespace haswell { ++namespace { ++ ++/** ++ * This algorithm is used to quickly identify the last structural position that ++ * makes up a complete document. ++ * ++ * It does this by going backwards and finding the last *document boundary* (a ++ * place where one value follows another without a comma between them). If the ++ * last document (the characters after the boundary) has an equal number of ++ * start and end brackets, it is considered complete. ++ * ++ * Simply put, we iterate over the structural characters, starting from ++ * the end. We consider that we found the end of a JSON document when the ++ * first element of the pair is NOT one of these characters: '{' '[' ':' ',' ++ * and when the second element is NOT one of these characters: '}' ']' ':' ','. ++ * ++ * This simple comparison works most of the time, but it does not cover cases ++ * where the batch's structural indexes contain a perfect amount of documents. ++ * In such a case, we do not have access to the structural index which follows ++ * the last document, therefore, we do not have access to the second element in ++ * the pair, and that means we cannot identify the last document. To fix this ++ * issue, we keep a count of the open and closed curly/square braces we found ++ * while searching for the pair. When we find a pair AND the count of open and ++ * closed curly/square braces is the same, we know that we just passed a ++ * complete document, therefore the last json buffer location is the end of the ++ * batch. ++ */ ++simdjson_inline uint32_t find_next_document_index(dom_parser_implementation &parser) { ++ // Variant: do not count separately, just figure out depth ++ if(parser.n_structural_indexes == 0) { return 0; } ++ auto arr_cnt = 0; ++ auto obj_cnt = 0; ++ for (auto i = parser.n_structural_indexes - 1; i > 0; i--) { ++ auto idxb = parser.structural_indexes[i]; ++ switch (parser.buf[idxb]) { ++ case ':': ++ case ',': ++ continue; ++ case '}': ++ obj_cnt--; ++ continue; ++ case ']': ++ arr_cnt--; ++ continue; ++ case '{': ++ obj_cnt++; ++ break; ++ case '[': ++ arr_cnt++; ++ break; ++ } ++ auto idxa = parser.structural_indexes[i - 1]; ++ switch (parser.buf[idxa]) { ++ case '{': ++ case '[': ++ case ':': ++ case ',': ++ continue; ++ } ++ // Last document is complete, so the next document will appear after! ++ if (!arr_cnt && !obj_cnt) { ++ return parser.n_structural_indexes; ++ } ++ // Last document is incomplete; mark the document at i + 1 as the next one ++ return i; ++ } ++ // If we made it to the end, we want to finish counting to see if we have a full document. ++ switch (parser.buf[parser.structural_indexes[0]]) { ++ case '}': ++ obj_cnt--; ++ break; ++ case ']': ++ arr_cnt--; ++ break; ++ case '{': ++ obj_cnt++; ++ break; ++ case '[': ++ arr_cnt++; ++ break; ++ } ++ if (!arr_cnt && !obj_cnt) { ++ // We have a complete document. ++ return parser.n_structural_indexes; ++ } ++ return 0; ++} ++ ++} // unnamed namespace ++} // namespace haswell ++} // namespace simdjson ++/* end file src/generic/stage1/find_next_document_index.h */ ++ ++namespace simdjson { ++namespace haswell { ++namespace { ++namespace stage1 { ++ ++class bit_indexer { ++public: ++ uint32_t *tail; ++ ++ simdjson_inline bit_indexer(uint32_t *index_buf) : tail(index_buf) {} ++ ++ // flatten out values in 'bits' assuming that they are are to have values of idx ++ // plus their position in the bitvector, and store these indexes at ++ // base_ptr[base] incrementing base as we go ++ // will potentially store extra values beyond end of valid bits, so base_ptr ++ // needs to be large enough to handle this ++ // ++ // If the kernel sets SIMDJSON_CUSTOM_BIT_INDEXER, then it will provide its own ++ // version of the code. ++#ifdef SIMDJSON_CUSTOM_BIT_INDEXER ++ simdjson_inline void write(uint32_t idx, uint64_t bits); ++#else ++ simdjson_inline void write(uint32_t idx, uint64_t bits) { ++ // In some instances, the next branch is expensive because it is mispredicted. ++ // Unfortunately, in other cases, ++ // it helps tremendously. ++ if (bits == 0) ++ return; ++#if SIMDJSON_PREFER_REVERSE_BITS ++ /** ++ * ARM lacks a fast trailing zero instruction, but it has a fast ++ * bit reversal instruction and a fast leading zero instruction. ++ * Thus it may be profitable to reverse the bits (once) and then ++ * to rely on a sequence of instructions that call the leading ++ * zero instruction. ++ * ++ * Performance notes: ++ * The chosen routine is not optimal in terms of data dependency ++ * since zero_leading_bit might require two instructions. However, ++ * it tends to minimize the total number of instructions which is ++ * beneficial. ++ */ ++ ++ uint64_t rev_bits = reverse_bits(bits); ++ int cnt = static_cast(count_ones(bits)); ++ int i = 0; ++ // Do the first 8 all together ++ for (; i<8; i++) { ++ int lz = leading_zeroes(rev_bits); ++ this->tail[i] = static_cast(idx) + lz; ++ rev_bits = zero_leading_bit(rev_bits, lz); ++ } ++ // Do the next 8 all together (we hope in most cases it won't happen at all ++ // and the branch is easily predicted). ++ if (simdjson_unlikely(cnt > 8)) { ++ i = 8; ++ for (; i<16; i++) { ++ int lz = leading_zeroes(rev_bits); ++ this->tail[i] = static_cast(idx) + lz; ++ rev_bits = zero_leading_bit(rev_bits, lz); ++ } ++ ++ ++ // Most files don't have 16+ structurals per block, so we take several basically guaranteed ++ // branch mispredictions here. 16+ structurals per block means either punctuation ({} [] , :) ++ // or the start of a value ("abc" true 123) every four characters. ++ if (simdjson_unlikely(cnt > 16)) { ++ i = 16; ++ while (rev_bits != 0) { ++ int lz = leading_zeroes(rev_bits); ++ this->tail[i++] = static_cast(idx) + lz; ++ rev_bits = zero_leading_bit(rev_bits, lz); ++ } ++ } ++ } ++ this->tail += cnt; ++#else // SIMDJSON_PREFER_REVERSE_BITS ++ /** ++ * Under recent x64 systems, we often have both a fast trailing zero ++ * instruction and a fast 'clear-lower-bit' instruction so the following ++ * algorithm can be competitive. ++ */ ++ ++ int cnt = static_cast(count_ones(bits)); ++ // Do the first 8 all together ++ for (int i=0; i<8; i++) { ++ this->tail[i] = idx + trailing_zeroes(bits); ++ bits = clear_lowest_bit(bits); ++ } ++ ++ // Do the next 8 all together (we hope in most cases it won't happen at all ++ // and the branch is easily predicted). ++ if (simdjson_unlikely(cnt > 8)) { ++ for (int i=8; i<16; i++) { ++ this->tail[i] = idx + trailing_zeroes(bits); ++ bits = clear_lowest_bit(bits); ++ } ++ ++ // Most files don't have 16+ structurals per block, so we take several basically guaranteed ++ // branch mispredictions here. 16+ structurals per block means either punctuation ({} [] , :) ++ // or the start of a value ("abc" true 123) every four characters. ++ if (simdjson_unlikely(cnt > 16)) { ++ int i = 16; ++ do { ++ this->tail[i] = idx + trailing_zeroes(bits); ++ bits = clear_lowest_bit(bits); ++ i++; ++ } while (i < cnt); ++ } ++ } ++ ++ this->tail += cnt; ++#endif ++ } ++#endif // SIMDJSON_CUSTOM_BIT_INDEXER ++ ++}; ++ ++class json_structural_indexer { ++public: ++ /** ++ * Find the important bits of JSON in a 128-byte chunk, and add them to structural_indexes. ++ * ++ * @param partial Setting the partial parameter to true allows the find_structural_bits to ++ * tolerate unclosed strings. The caller should still ensure that the input is valid UTF-8. If ++ * you are processing substrings, you may want to call on a function like trimmed_length_safe_utf8. ++ */ ++ template ++ static error_code index(const uint8_t *buf, size_t len, dom_parser_implementation &parser, stage1_mode partial) noexcept; ++ ++private: ++ simdjson_inline json_structural_indexer(uint32_t *structural_indexes); ++ template ++ simdjson_inline void step(const uint8_t *block, buf_block_reader &reader) noexcept; ++ simdjson_inline void next(const simd::simd8x64& in, const json_block& block, size_t idx); ++ simdjson_inline error_code finish(dom_parser_implementation &parser, size_t idx, size_t len, stage1_mode partial); ++ ++ json_scanner scanner{}; ++ utf8_checker checker{}; ++ bit_indexer indexer; ++ uint64_t prev_structurals = 0; ++ uint64_t unescaped_chars_error = 0; ++}; ++ ++simdjson_inline json_structural_indexer::json_structural_indexer(uint32_t *structural_indexes) : indexer{structural_indexes} {} ++ ++// Skip the last character if it is partial ++simdjson_inline size_t trim_partial_utf8(const uint8_t *buf, size_t len) { ++ if (simdjson_unlikely(len < 3)) { ++ switch (len) { ++ case 2: ++ if (buf[len-1] >= 0xc0) { return len-1; } // 2-, 3- and 4-byte characters with only 1 byte left ++ if (buf[len-2] >= 0xe0) { return len-2; } // 3- and 4-byte characters with only 2 bytes left ++ return len; ++ case 1: ++ if (buf[len-1] >= 0xc0) { return len-1; } // 2-, 3- and 4-byte characters with only 1 byte left ++ return len; ++ case 0: ++ return len; ++ } ++ } ++ if (buf[len-1] >= 0xc0) { return len-1; } // 2-, 3- and 4-byte characters with only 1 byte left ++ if (buf[len-2] >= 0xe0) { return len-2; } // 3- and 4-byte characters with only 1 byte left ++ if (buf[len-3] >= 0xf0) { return len-3; } // 4-byte characters with only 3 bytes left ++ return len; ++} ++ ++// ++// PERF NOTES: ++// We pipe 2 inputs through these stages: ++// 1. Load JSON into registers. This takes a long time and is highly parallelizable, so we load ++// 2 inputs' worth at once so that by the time step 2 is looking for them input, it's available. ++// 2. Scan the JSON for critical data: strings, scalars and operators. This is the critical path. ++// The output of step 1 depends entirely on this information. These functions don't quite use ++// up enough CPU: the second half of the functions is highly serial, only using 1 execution core ++// at a time. The second input's scans has some dependency on the first ones finishing it, but ++// they can make a lot of progress before they need that information. ++// 3. Step 1 doesn't use enough capacity, so we run some extra stuff while we're waiting for that ++// to finish: utf-8 checks and generating the output from the last iteration. ++// ++// The reason we run 2 inputs at a time, is steps 2 and 3 are *still* not enough to soak up all ++// available capacity with just one input. Running 2 at a time seems to give the CPU a good enough ++// workout. ++// ++template ++error_code json_structural_indexer::index(const uint8_t *buf, size_t len, dom_parser_implementation &parser, stage1_mode partial) noexcept { ++ if (simdjson_unlikely(len > parser.capacity())) { return CAPACITY; } ++ // We guard the rest of the code so that we can assume that len > 0 throughout. ++ if (len == 0) { return EMPTY; } ++ if (is_streaming(partial)) { ++ len = trim_partial_utf8(buf, len); ++ // If you end up with an empty window after trimming ++ // the partial UTF-8 bytes, then chances are good that you ++ // have an UTF-8 formatting error. ++ if(len == 0) { return UTF8_ERROR; } ++ } ++ buf_block_reader reader(buf, len); ++ json_structural_indexer indexer(parser.structural_indexes.get()); ++ ++ // Read all but the last block ++ while (reader.has_full_block()) { ++ indexer.step(reader.full_block(), reader); ++ } ++ // Take care of the last block (will always be there unless file is empty which is ++ // not supposed to happen.) ++ uint8_t block[STEP_SIZE]; ++ if (simdjson_unlikely(reader.get_remainder(block) == 0)) { return UNEXPECTED_ERROR; } ++ indexer.step(block, reader); ++ return indexer.finish(parser, reader.block_index(), len, partial); ++} ++ ++template<> ++simdjson_inline void json_structural_indexer::step<128>(const uint8_t *block, buf_block_reader<128> &reader) noexcept { ++ simd::simd8x64 in_1(block); ++ simd::simd8x64 in_2(block+64); ++ json_block block_1 = scanner.next(in_1); ++ json_block block_2 = scanner.next(in_2); ++ this->next(in_1, block_1, reader.block_index()); ++ this->next(in_2, block_2, reader.block_index()+64); ++ reader.advance(); ++} ++ ++template<> ++simdjson_inline void json_structural_indexer::step<64>(const uint8_t *block, buf_block_reader<64> &reader) noexcept { ++ simd::simd8x64 in_1(block); ++ json_block block_1 = scanner.next(in_1); ++ this->next(in_1, block_1, reader.block_index()); ++ reader.advance(); ++} ++ ++simdjson_inline void json_structural_indexer::next(const simd::simd8x64& in, const json_block& block, size_t idx) { ++ uint64_t unescaped = in.lteq(0x1F); ++ checker.check_next_input(in); ++ indexer.write(uint32_t(idx-64), prev_structurals); // Output *last* iteration's structurals to the parser ++ prev_structurals = block.structural_start(); ++ unescaped_chars_error |= block.non_quote_inside_string(unescaped); ++} ++ ++simdjson_inline error_code json_structural_indexer::finish(dom_parser_implementation &parser, size_t idx, size_t len, stage1_mode partial) { ++ // Write out the final iteration's structurals ++ indexer.write(uint32_t(idx-64), prev_structurals); ++ error_code error = scanner.finish(); ++ // We deliberately break down the next expression so that it is ++ // human readable. ++ const bool should_we_exit = is_streaming(partial) ? ++ ((error != SUCCESS) && (error != UNCLOSED_STRING)) // when partial we tolerate UNCLOSED_STRING ++ : (error != SUCCESS); // if partial is false, we must have SUCCESS ++ const bool have_unclosed_string = (error == UNCLOSED_STRING); ++ if (simdjson_unlikely(should_we_exit)) { return error; } ++ ++ if (unescaped_chars_error) { ++ return UNESCAPED_CHARS; ++ } ++ parser.n_structural_indexes = uint32_t(indexer.tail - parser.structural_indexes.get()); ++ /*** ++ * The On Demand API requires special padding. ++ * ++ * This is related to https://github.com/simdjson/simdjson/issues/906 ++ * Basically, we want to make sure that if the parsing continues beyond the last (valid) ++ * structural character, it quickly stops. ++ * Only three structural characters can be repeated without triggering an error in JSON: [,] and }. ++ * We repeat the padding character (at 'len'). We don't know what it is, but if the parsing ++ * continues, then it must be [,] or }. ++ * Suppose it is ] or }. We backtrack to the first character, what could it be that would ++ * not trigger an error? It could be ] or } but no, because you can't start a document that way. ++ * It can't be a comma, a colon or any simple value. So the only way we could continue is ++ * if the repeated character is [. But if so, the document must start with [. But if the document ++ * starts with [, it should end with ]. If we enforce that rule, then we would get ++ * ][[ which is invalid. ++ * ++ * This is illustrated with the test array_iterate_unclosed_error() on the following input: ++ * R"({ "a": [,,)" ++ **/ ++ parser.structural_indexes[parser.n_structural_indexes] = uint32_t(len); // used later in partial == stage1_mode::streaming_final ++ parser.structural_indexes[parser.n_structural_indexes + 1] = uint32_t(len); ++ parser.structural_indexes[parser.n_structural_indexes + 2] = 0; ++ parser.next_structural_index = 0; ++ // a valid JSON file cannot have zero structural indexes - we should have found something ++ if (simdjson_unlikely(parser.n_structural_indexes == 0u)) { ++ return EMPTY; ++ } ++ if (simdjson_unlikely(parser.structural_indexes[parser.n_structural_indexes - 1] > len)) { ++ return UNEXPECTED_ERROR; ++ } ++ if (partial == stage1_mode::streaming_partial) { ++ // If we have an unclosed string, then the last structural ++ // will be the quote and we want to make sure to omit it. ++ if(have_unclosed_string) { ++ parser.n_structural_indexes--; ++ // a valid JSON file cannot have zero structural indexes - we should have found something ++ if (simdjson_unlikely(parser.n_structural_indexes == 0u)) { return CAPACITY; } ++ } ++ // We truncate the input to the end of the last complete document (or zero). ++ auto new_structural_indexes = find_next_document_index(parser); ++ if (new_structural_indexes == 0 && parser.n_structural_indexes > 0) { ++ if(parser.structural_indexes[0] == 0) { ++ // If the buffer is partial and we started at index 0 but the document is ++ // incomplete, it's too big to parse. ++ return CAPACITY; ++ } else { ++ // It is possible that the document could be parsed, we just had a lot ++ // of white space. ++ parser.n_structural_indexes = 0; ++ return EMPTY; ++ } ++ } ++ ++ parser.n_structural_indexes = new_structural_indexes; ++ } else if (partial == stage1_mode::streaming_final) { ++ if(have_unclosed_string) { parser.n_structural_indexes--; } ++ // We truncate the input to the end of the last complete document (or zero). ++ // Because partial == stage1_mode::streaming_final, it means that we may ++ // silently ignore trailing garbage. Though it sounds bad, we do it ++ // deliberately because many people who have streams of JSON documents ++ // will truncate them for processing. E.g., imagine that you are uncompressing ++ // the data from a size file or receiving it in chunks from the network. You ++ // may not know where exactly the last document will be. Meanwhile the ++ // document_stream instances allow people to know the JSON documents they are ++ // parsing (see the iterator.source() method). ++ parser.n_structural_indexes = find_next_document_index(parser); ++ // We store the initial n_structural_indexes so that the client can see ++ // whether we used truncation. If initial_n_structural_indexes == parser.n_structural_indexes, ++ // then this will query parser.structural_indexes[parser.n_structural_indexes] which is len, ++ // otherwise, it will copy some prior index. ++ parser.structural_indexes[parser.n_structural_indexes + 1] = parser.structural_indexes[parser.n_structural_indexes]; ++ // This next line is critical, do not change it unless you understand what you are ++ // doing. ++ parser.structural_indexes[parser.n_structural_indexes] = uint32_t(len); ++ if (simdjson_unlikely(parser.n_structural_indexes == 0u)) { ++ // We tolerate an unclosed string at the very end of the stream. Indeed, users ++ // often load their data in bulk without being careful and they want us to ignore ++ // the trailing garbage. ++ return EMPTY; ++ } ++ } ++ checker.check_eof(); ++ return checker.errors(); ++} ++ ++} // namespace stage1 ++} // unnamed namespace ++} // namespace haswell ++} // namespace simdjson ++/* end file src/generic/stage1/json_structural_indexer.h */ ++/* begin file src/generic/stage1/utf8_validator.h */ ++namespace simdjson { ++namespace haswell { ++namespace { ++namespace stage1 { ++ ++/** ++ * Validates that the string is actual UTF-8. ++ */ ++template ++bool generic_validate_utf8(const uint8_t * input, size_t length) { ++ checker c{}; ++ buf_block_reader<64> reader(input, length); ++ while (reader.has_full_block()) { ++ simd::simd8x64 in(reader.full_block()); ++ c.check_next_input(in); ++ reader.advance(); ++ } ++ uint8_t block[64]{}; ++ reader.get_remainder(block); ++ simd::simd8x64 in(block); ++ c.check_next_input(in); ++ reader.advance(); ++ c.check_eof(); ++ return c.errors() == error_code::SUCCESS; ++} ++ ++bool generic_validate_utf8(const char * input, size_t length) { ++ return generic_validate_utf8(reinterpret_cast(input),length); ++} ++ ++} // namespace stage1 ++} // unnamed namespace ++} // namespace haswell ++} // namespace simdjson ++/* end file src/generic/stage1/utf8_validator.h */ ++ ++// ++// Stage 2 ++// ++/* begin file src/generic/stage2/stringparsing.h */ ++// This file contains the common code every implementation uses ++// It is intended to be included multiple times and compiled multiple times ++ ++namespace simdjson { ++namespace haswell { ++namespace { ++/// @private ++namespace stringparsing { ++ ++// begin copypasta ++// These chars yield themselves: " \ / ++// b -> backspace, f -> formfeed, n -> newline, r -> cr, t -> horizontal tab ++// u not handled in this table as it's complex ++static const uint8_t escape_map[256] = { ++ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x0. ++ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ++ 0, 0, 0x22, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x2f, ++ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ++ ++ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x4. ++ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x5c, 0, 0, 0, // 0x5. ++ 0, 0, 0x08, 0, 0, 0, 0x0c, 0, 0, 0, 0, 0, 0, 0, 0x0a, 0, // 0x6. ++ 0, 0, 0x0d, 0, 0x09, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x7. ++ ++ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ++ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ++ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ++ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ++ ++ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ++ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ++ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ++ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ++}; ++ ++// handle a unicode codepoint ++// write appropriate values into dest ++// src will advance 6 bytes or 12 bytes ++// dest will advance a variable amount (return via pointer) ++// return true if the unicode codepoint was valid ++// We work in little-endian then swap at write time ++simdjson_warn_unused ++simdjson_inline bool handle_unicode_codepoint(const uint8_t **src_ptr, ++ uint8_t **dst_ptr) { ++ // jsoncharutils::hex_to_u32_nocheck fills high 16 bits of the return value with 1s if the ++ // conversion isn't valid; we defer the check for this to inside the ++ // multilingual plane check ++ uint32_t code_point = jsoncharutils::hex_to_u32_nocheck(*src_ptr + 2); ++ *src_ptr += 6; ++ ++ // If we found a high surrogate, we must ++ // check for low surrogate for characters ++ // outside the Basic ++ // Multilingual Plane. ++ if (code_point >= 0xd800 && code_point < 0xdc00) { ++ const uint8_t *src_data = *src_ptr; ++ /* Compiler optimizations convert this to a single 16-bit load and compare on most platforms */ ++ if (((src_data[0] << 8) | src_data[1]) != ((static_cast ('\\') << 8) | static_cast ('u'))) { ++ return false; ++ } ++ uint32_t code_point_2 = jsoncharutils::hex_to_u32_nocheck(src_data + 2); ++ ++ // We have already checked that the high surrogate is valid and ++ // (code_point - 0xd800) < 1024. ++ // ++ // Check that code_point_2 is in the range 0xdc00..0xdfff ++ // and that code_point_2 was parsed from valid hex. ++ uint32_t low_bit = code_point_2 - 0xdc00; ++ if (low_bit >> 10) { ++ return false; ++ } ++ ++ code_point = ++ (((code_point - 0xd800) << 10) | low_bit) + 0x10000; ++ *src_ptr += 6; ++ } else if (code_point >= 0xdc00 && code_point <= 0xdfff) { ++ // If we encounter a low surrogate (not preceded by a high surrogate) ++ // then we have an error. ++ return false; ++ } ++ size_t offset = jsoncharutils::codepoint_to_utf8(code_point, *dst_ptr); ++ *dst_ptr += offset; ++ return offset > 0; ++} ++ ++/** ++ * Unescape a valid UTF-8 string from src to dst, stopping at a final unescaped quote. There ++ * must be an unescaped quote terminating the string. It returns the final output ++ * position as pointer. In case of error (e.g., the string has bad escaped codes), ++ * then null_nullptrptr is returned. It is assumed that the output buffer is large ++ * enough. E.g., if src points at 'joe"', then dst needs to have four free bytes + ++ * SIMDJSON_PADDING bytes. ++ */ ++simdjson_warn_unused simdjson_inline uint8_t *parse_string(const uint8_t *src, uint8_t *dst) { ++ while (1) { ++ // Copy the next n bytes, and find the backslash and quote in them. ++ auto bs_quote = backslash_and_quote::copy_and_find(src, dst); ++ // If the next thing is the end quote, copy and return ++ if (bs_quote.has_quote_first()) { ++ // we encountered quotes first. Move dst to point to quotes and exit ++ return dst + bs_quote.quote_index(); ++ } ++ if (bs_quote.has_backslash()) { ++ /* find out where the backspace is */ ++ auto bs_dist = bs_quote.backslash_index(); ++ uint8_t escape_char = src[bs_dist + 1]; ++ /* we encountered backslash first. Handle backslash */ ++ if (escape_char == 'u') { ++ /* move src/dst up to the start; they will be further adjusted ++ within the unicode codepoint handling code. */ ++ src += bs_dist; ++ dst += bs_dist; ++ if (!handle_unicode_codepoint(&src, &dst)) { ++ return nullptr; ++ } ++ } else { ++ /* simple 1:1 conversion. Will eat bs_dist+2 characters in input and ++ * write bs_dist+1 characters to output ++ * note this may reach beyond the part of the buffer we've actually ++ * seen. I think this is ok */ ++ uint8_t escape_result = escape_map[escape_char]; ++ if (escape_result == 0u) { ++ return nullptr; /* bogus escape value is an error */ ++ } ++ dst[bs_dist] = escape_result; ++ src += bs_dist + 2; ++ dst += bs_dist + 1; ++ } ++ } else { ++ /* they are the same. Since they can't co-occur, it means we ++ * encountered neither. */ ++ src += backslash_and_quote::BYTES_PROCESSED; ++ dst += backslash_and_quote::BYTES_PROCESSED; ++ } ++ } ++ /* can't be reached */ ++ return nullptr; ++} ++ ++} // namespace stringparsing ++} // unnamed namespace ++} // namespace haswell ++} // namespace simdjson ++/* end file src/generic/stage2/stringparsing.h */ ++/* begin file src/generic/stage2/tape_builder.h */ ++/* begin file src/generic/stage2/json_iterator.h */ ++/* begin file src/generic/stage2/logger.h */ ++// This is for an internal-only stage 2 specific logger. ++// Set LOG_ENABLED = true to log what stage 2 is doing! ++namespace simdjson { ++namespace haswell { ++namespace { ++namespace logger { ++ ++ static constexpr const char * DASHES = "----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------"; ++ ++#if SIMDJSON_VERBOSE_LOGGING ++ static constexpr const bool LOG_ENABLED = true; ++#else ++ static constexpr const bool LOG_ENABLED = false; ++#endif ++ static constexpr const int LOG_EVENT_LEN = 20; ++ static constexpr const int LOG_BUFFER_LEN = 30; ++ static constexpr const int LOG_SMALL_BUFFER_LEN = 10; ++ static constexpr const int LOG_INDEX_LEN = 5; ++ ++ static int log_depth; // Not threadsafe. Log only. ++ ++ // Helper to turn unprintable or newline characters into spaces ++ static simdjson_inline char printable_char(char c) { ++ if (c >= 0x20) { ++ return c; ++ } else { ++ return ' '; ++ } ++ } ++ ++ // Print the header and set up log_start ++ static simdjson_inline void log_start() { ++ if (LOG_ENABLED) { ++ log_depth = 0; ++ printf("\n"); ++ printf("| %-*s | %-*s | %-*s | %-*s | Detail |\n", LOG_EVENT_LEN, "Event", LOG_BUFFER_LEN, "Buffer", LOG_SMALL_BUFFER_LEN, "Next", 5, "Next#"); ++ printf("|%.*s|%.*s|%.*s|%.*s|--------|\n", LOG_EVENT_LEN+2, DASHES, LOG_BUFFER_LEN+2, DASHES, LOG_SMALL_BUFFER_LEN+2, DASHES, 5+2, DASHES); ++ } ++ } ++ ++ simdjson_unused static simdjson_inline void log_string(const char *message) { ++ if (LOG_ENABLED) { ++ printf("%s\n", message); ++ } ++ } ++ ++ // Logs a single line from the stage 2 DOM parser ++ template ++ static simdjson_inline void log_line(S &structurals, const char *title_prefix, const char *title, const char *detail) { ++ if (LOG_ENABLED) { ++ printf("| %*s%s%-*s ", log_depth*2, "", title_prefix, LOG_EVENT_LEN - log_depth*2 - int(strlen(title_prefix)), title); ++ auto current_index = structurals.at_beginning() ? nullptr : structurals.next_structural-1; ++ auto next_index = structurals.next_structural; ++ auto current = current_index ? &structurals.buf[*current_index] : reinterpret_cast(" "); ++ auto next = &structurals.buf[*next_index]; ++ { ++ // Print the next N characters in the buffer. ++ printf("| "); ++ // Otherwise, print the characters starting from the buffer position. ++ // Print spaces for unprintable or newline characters. ++ for (int i=0;i ++ simdjson_warn_unused simdjson_inline error_code walk_document(V &visitor) noexcept; ++ ++ /** ++ * Create an iterator capable of walking a JSON document. ++ * ++ * The document must have already passed through stage 1. ++ */ ++ simdjson_inline json_iterator(dom_parser_implementation &_dom_parser, size_t start_structural_index); ++ ++ /** ++ * Look at the next token. ++ * ++ * Tokens can be strings, numbers, booleans, null, or operators (`[{]},:`)). ++ * ++ * They may include invalid JSON as well (such as `1.2.3` or `ture`). ++ */ ++ simdjson_inline const uint8_t *peek() const noexcept; ++ /** ++ * Advance to the next token. ++ * ++ * Tokens can be strings, numbers, booleans, null, or operators (`[{]},:`)). ++ * ++ * They may include invalid JSON as well (such as `1.2.3` or `ture`). ++ */ ++ simdjson_inline const uint8_t *advance() noexcept; ++ /** ++ * Get the remaining length of the document, from the start of the current token. ++ */ ++ simdjson_inline size_t remaining_len() const noexcept; ++ /** ++ * Check if we are at the end of the document. ++ * ++ * If this is true, there are no more tokens. ++ */ ++ simdjson_inline bool at_eof() const noexcept; ++ /** ++ * Check if we are at the beginning of the document. ++ */ ++ simdjson_inline bool at_beginning() const noexcept; ++ simdjson_inline uint8_t last_structural() const noexcept; ++ ++ /** ++ * Log that a value has been found. ++ * ++ * Set LOG_ENABLED=true in logger.h to see logging. ++ */ ++ simdjson_inline void log_value(const char *type) const noexcept; ++ /** ++ * Log the start of a multipart value. ++ * ++ * Set LOG_ENABLED=true in logger.h to see logging. ++ */ ++ simdjson_inline void log_start_value(const char *type) const noexcept; ++ /** ++ * Log the end of a multipart value. ++ * ++ * Set LOG_ENABLED=true in logger.h to see logging. ++ */ ++ simdjson_inline void log_end_value(const char *type) const noexcept; ++ /** ++ * Log an error. ++ * ++ * Set LOG_ENABLED=true in logger.h to see logging. ++ */ ++ simdjson_inline void log_error(const char *error) const noexcept; ++ ++ template ++ simdjson_warn_unused simdjson_inline error_code visit_root_primitive(V &visitor, const uint8_t *value) noexcept; ++ template ++ simdjson_warn_unused simdjson_inline error_code visit_primitive(V &visitor, const uint8_t *value) noexcept; ++}; ++ ++template ++simdjson_warn_unused simdjson_inline error_code json_iterator::walk_document(V &visitor) noexcept { ++ logger::log_start(); ++ ++ // ++ // Start the document ++ // ++ if (at_eof()) { return EMPTY; } ++ log_start_value("document"); ++ SIMDJSON_TRY( visitor.visit_document_start(*this) ); ++ ++ // ++ // Read first value ++ // ++ { ++ auto value = advance(); ++ ++ // Make sure the outer object or array is closed before continuing; otherwise, there are ways we ++ // could get into memory corruption. See https://github.com/simdjson/simdjson/issues/906 ++ if (!STREAMING) { ++ switch (*value) { ++ case '{': if (last_structural() != '}') { log_value("starting brace unmatched"); return TAPE_ERROR; }; break; ++ case '[': if (last_structural() != ']') { log_value("starting bracket unmatched"); return TAPE_ERROR; }; break; ++ } ++ } ++ ++ switch (*value) { ++ case '{': if (*peek() == '}') { advance(); log_value("empty object"); SIMDJSON_TRY( visitor.visit_empty_object(*this) ); break; } goto object_begin; ++ case '[': if (*peek() == ']') { advance(); log_value("empty array"); SIMDJSON_TRY( visitor.visit_empty_array(*this) ); break; } goto array_begin; ++ default: SIMDJSON_TRY( visitor.visit_root_primitive(*this, value) ); break; ++ } ++ } ++ goto document_end; ++ ++// ++// Object parser states ++// ++object_begin: ++ log_start_value("object"); ++ depth++; ++ if (depth >= dom_parser.max_depth()) { log_error("Exceeded max depth!"); return DEPTH_ERROR; } ++ dom_parser.is_array[depth] = false; ++ SIMDJSON_TRY( visitor.visit_object_start(*this) ); ++ ++ { ++ auto key = advance(); ++ if (*key != '"') { log_error("Object does not start with a key"); return TAPE_ERROR; } ++ SIMDJSON_TRY( visitor.increment_count(*this) ); ++ SIMDJSON_TRY( visitor.visit_key(*this, key) ); ++ } ++ ++object_field: ++ if (simdjson_unlikely( *advance() != ':' )) { log_error("Missing colon after key in object"); return TAPE_ERROR; } ++ { ++ auto value = advance(); ++ switch (*value) { ++ case '{': if (*peek() == '}') { advance(); log_value("empty object"); SIMDJSON_TRY( visitor.visit_empty_object(*this) ); break; } goto object_begin; ++ case '[': if (*peek() == ']') { advance(); log_value("empty array"); SIMDJSON_TRY( visitor.visit_empty_array(*this) ); break; } goto array_begin; ++ default: SIMDJSON_TRY( visitor.visit_primitive(*this, value) ); break; ++ } ++ } ++ ++object_continue: ++ switch (*advance()) { ++ case ',': ++ SIMDJSON_TRY( visitor.increment_count(*this) ); ++ { ++ auto key = advance(); ++ if (simdjson_unlikely( *key != '"' )) { log_error("Key string missing at beginning of field in object"); return TAPE_ERROR; } ++ SIMDJSON_TRY( visitor.visit_key(*this, key) ); ++ } ++ goto object_field; ++ case '}': log_end_value("object"); SIMDJSON_TRY( visitor.visit_object_end(*this) ); goto scope_end; ++ default: log_error("No comma between object fields"); return TAPE_ERROR; ++ } ++ ++scope_end: ++ depth--; ++ if (depth == 0) { goto document_end; } ++ if (dom_parser.is_array[depth]) { goto array_continue; } ++ goto object_continue; ++ ++// ++// Array parser states ++// ++array_begin: ++ log_start_value("array"); ++ depth++; ++ if (depth >= dom_parser.max_depth()) { log_error("Exceeded max depth!"); return DEPTH_ERROR; } ++ dom_parser.is_array[depth] = true; ++ SIMDJSON_TRY( visitor.visit_array_start(*this) ); ++ SIMDJSON_TRY( visitor.increment_count(*this) ); ++ ++array_value: ++ { ++ auto value = advance(); ++ switch (*value) { ++ case '{': if (*peek() == '}') { advance(); log_value("empty object"); SIMDJSON_TRY( visitor.visit_empty_object(*this) ); break; } goto object_begin; ++ case '[': if (*peek() == ']') { advance(); log_value("empty array"); SIMDJSON_TRY( visitor.visit_empty_array(*this) ); break; } goto array_begin; ++ default: SIMDJSON_TRY( visitor.visit_primitive(*this, value) ); break; ++ } ++ } ++ ++array_continue: ++ switch (*advance()) { ++ case ',': SIMDJSON_TRY( visitor.increment_count(*this) ); goto array_value; ++ case ']': log_end_value("array"); SIMDJSON_TRY( visitor.visit_array_end(*this) ); goto scope_end; ++ default: log_error("Missing comma between array values"); return TAPE_ERROR; ++ } ++ ++document_end: ++ log_end_value("document"); ++ SIMDJSON_TRY( visitor.visit_document_end(*this) ); ++ ++ dom_parser.next_structural_index = uint32_t(next_structural - &dom_parser.structural_indexes[0]); ++ ++ // If we didn't make it to the end, it's an error ++ if ( !STREAMING && dom_parser.next_structural_index != dom_parser.n_structural_indexes ) { ++ log_error("More than one JSON value at the root of the document, or extra characters at the end of the JSON!"); ++ return TAPE_ERROR; ++ } ++ ++ return SUCCESS; ++ ++} // walk_document() ++ ++simdjson_inline json_iterator::json_iterator(dom_parser_implementation &_dom_parser, size_t start_structural_index) ++ : buf{_dom_parser.buf}, ++ next_structural{&_dom_parser.structural_indexes[start_structural_index]}, ++ dom_parser{_dom_parser} { ++} ++ ++simdjson_inline const uint8_t *json_iterator::peek() const noexcept { ++ return &buf[*(next_structural)]; ++} ++simdjson_inline const uint8_t *json_iterator::advance() noexcept { ++ return &buf[*(next_structural++)]; ++} ++simdjson_inline size_t json_iterator::remaining_len() const noexcept { ++ return dom_parser.len - *(next_structural-1); ++} ++ ++simdjson_inline bool json_iterator::at_eof() const noexcept { ++ return next_structural == &dom_parser.structural_indexes[dom_parser.n_structural_indexes]; ++} ++simdjson_inline bool json_iterator::at_beginning() const noexcept { ++ return next_structural == dom_parser.structural_indexes.get(); ++} ++simdjson_inline uint8_t json_iterator::last_structural() const noexcept { ++ return buf[dom_parser.structural_indexes[dom_parser.n_structural_indexes - 1]]; ++} ++ ++simdjson_inline void json_iterator::log_value(const char *type) const noexcept { ++ logger::log_line(*this, "", type, ""); ++} ++ ++simdjson_inline void json_iterator::log_start_value(const char *type) const noexcept { ++ logger::log_line(*this, "+", type, ""); ++ if (logger::LOG_ENABLED) { logger::log_depth++; } ++} ++ ++simdjson_inline void json_iterator::log_end_value(const char *type) const noexcept { ++ if (logger::LOG_ENABLED) { logger::log_depth--; } ++ logger::log_line(*this, "-", type, ""); ++} ++ ++simdjson_inline void json_iterator::log_error(const char *error) const noexcept { ++ logger::log_line(*this, "", "ERROR", error); ++} ++ ++template ++simdjson_warn_unused simdjson_inline error_code json_iterator::visit_root_primitive(V &visitor, const uint8_t *value) noexcept { ++ switch (*value) { ++ case '"': return visitor.visit_root_string(*this, value); ++ case 't': return visitor.visit_root_true_atom(*this, value); ++ case 'f': return visitor.visit_root_false_atom(*this, value); ++ case 'n': return visitor.visit_root_null_atom(*this, value); ++ case '-': ++ case '0': case '1': case '2': case '3': case '4': ++ case '5': case '6': case '7': case '8': case '9': ++ return visitor.visit_root_number(*this, value); ++ default: ++ log_error("Document starts with a non-value character"); ++ return TAPE_ERROR; ++ } ++} ++template ++simdjson_warn_unused simdjson_inline error_code json_iterator::visit_primitive(V &visitor, const uint8_t *value) noexcept { ++ switch (*value) { ++ case '"': return visitor.visit_string(*this, value); ++ case 't': return visitor.visit_true_atom(*this, value); ++ case 'f': return visitor.visit_false_atom(*this, value); ++ case 'n': return visitor.visit_null_atom(*this, value); ++ case '-': ++ case '0': case '1': case '2': case '3': case '4': ++ case '5': case '6': case '7': case '8': case '9': ++ return visitor.visit_number(*this, value); ++ default: ++ log_error("Non-value found when value was expected!"); ++ return TAPE_ERROR; ++ } ++} ++ ++} // namespace stage2 ++} // unnamed namespace ++} // namespace haswell ++} // namespace simdjson ++/* end file src/generic/stage2/json_iterator.h */ ++/* begin file src/generic/stage2/tape_writer.h */ ++namespace simdjson { ++namespace haswell { ++namespace { ++namespace stage2 { ++ ++struct tape_writer { ++ /** The next place to write to tape */ ++ uint64_t *next_tape_loc; ++ ++ /** Write a signed 64-bit value to tape. */ ++ simdjson_inline void append_s64(int64_t value) noexcept; ++ ++ /** Write an unsigned 64-bit value to tape. */ ++ simdjson_inline void append_u64(uint64_t value) noexcept; ++ ++ /** Write a double value to tape. */ ++ simdjson_inline void append_double(double value) noexcept; ++ ++ /** ++ * Append a tape entry (an 8-bit type,and 56 bits worth of value). ++ */ ++ simdjson_inline void append(uint64_t val, internal::tape_type t) noexcept; ++ ++ /** ++ * Skip the current tape entry without writing. ++ * ++ * Used to skip the start of the container, since we'll come back later to fill it in when the ++ * container ends. ++ */ ++ simdjson_inline void skip() noexcept; ++ ++ /** ++ * Skip the number of tape entries necessary to write a large u64 or i64. ++ */ ++ simdjson_inline void skip_large_integer() noexcept; ++ ++ /** ++ * Skip the number of tape entries necessary to write a double. ++ */ ++ simdjson_inline void skip_double() noexcept; ++ ++ /** ++ * Write a value to a known location on tape. ++ * ++ * Used to go back and write out the start of a container after the container ends. ++ */ ++ simdjson_inline static void write(uint64_t &tape_loc, uint64_t val, internal::tape_type t) noexcept; ++ ++private: ++ /** ++ * Append both the tape entry, and a supplementary value following it. Used for types that need ++ * all 64 bits, such as double and uint64_t. ++ */ ++ template ++ simdjson_inline void append2(uint64_t val, T val2, internal::tape_type t) noexcept; ++}; // struct number_writer ++ ++simdjson_inline void tape_writer::append_s64(int64_t value) noexcept { ++ append2(0, value, internal::tape_type::INT64); ++} ++ ++simdjson_inline void tape_writer::append_u64(uint64_t value) noexcept { ++ append(0, internal::tape_type::UINT64); ++ *next_tape_loc = value; ++ next_tape_loc++; ++} ++ ++/** Write a double value to tape. */ ++simdjson_inline void tape_writer::append_double(double value) noexcept { ++ append2(0, value, internal::tape_type::DOUBLE); ++} ++ ++simdjson_inline void tape_writer::skip() noexcept { ++ next_tape_loc++; ++} ++ ++simdjson_inline void tape_writer::skip_large_integer() noexcept { ++ next_tape_loc += 2; ++} ++ ++simdjson_inline void tape_writer::skip_double() noexcept { ++ next_tape_loc += 2; ++} ++ ++simdjson_inline void tape_writer::append(uint64_t val, internal::tape_type t) noexcept { ++ *next_tape_loc = val | ((uint64_t(char(t))) << 56); ++ next_tape_loc++; ++} ++ ++template ++simdjson_inline void tape_writer::append2(uint64_t val, T val2, internal::tape_type t) noexcept { ++ append(val, t); ++ static_assert(sizeof(val2) == sizeof(*next_tape_loc), "Type is not 64 bits!"); ++ memcpy(next_tape_loc, &val2, sizeof(val2)); ++ next_tape_loc++; ++} ++ ++simdjson_inline void tape_writer::write(uint64_t &tape_loc, uint64_t val, internal::tape_type t) noexcept { ++ tape_loc = val | ((uint64_t(char(t))) << 56); ++} ++ ++} // namespace stage2 ++} // unnamed namespace ++} // namespace haswell ++} // namespace simdjson ++/* end file src/generic/stage2/tape_writer.h */ ++ ++namespace simdjson { ++namespace haswell { ++namespace { ++namespace stage2 { ++ ++struct tape_builder { ++ template ++ simdjson_warn_unused static simdjson_inline error_code parse_document( ++ dom_parser_implementation &dom_parser, ++ dom::document &doc) noexcept; ++ ++ /** Called when a non-empty document starts. */ ++ simdjson_warn_unused simdjson_inline error_code visit_document_start(json_iterator &iter) noexcept; ++ /** Called when a non-empty document ends without error. */ ++ simdjson_warn_unused simdjson_inline error_code visit_document_end(json_iterator &iter) noexcept; ++ ++ /** Called when a non-empty array starts. */ ++ simdjson_warn_unused simdjson_inline error_code visit_array_start(json_iterator &iter) noexcept; ++ /** Called when a non-empty array ends. */ ++ simdjson_warn_unused simdjson_inline error_code visit_array_end(json_iterator &iter) noexcept; ++ /** Called when an empty array is found. */ ++ simdjson_warn_unused simdjson_inline error_code visit_empty_array(json_iterator &iter) noexcept; ++ ++ /** Called when a non-empty object starts. */ ++ simdjson_warn_unused simdjson_inline error_code visit_object_start(json_iterator &iter) noexcept; ++ /** ++ * Called when a key in a field is encountered. ++ * ++ * primitive, visit_object_start, visit_empty_object, visit_array_start, or visit_empty_array ++ * will be called after this with the field value. ++ */ ++ simdjson_warn_unused simdjson_inline error_code visit_key(json_iterator &iter, const uint8_t *key) noexcept; ++ /** Called when a non-empty object ends. */ ++ simdjson_warn_unused simdjson_inline error_code visit_object_end(json_iterator &iter) noexcept; ++ /** Called when an empty object is found. */ ++ simdjson_warn_unused simdjson_inline error_code visit_empty_object(json_iterator &iter) noexcept; ++ ++ /** ++ * Called when a string, number, boolean or null is found. ++ */ ++ simdjson_warn_unused simdjson_inline error_code visit_primitive(json_iterator &iter, const uint8_t *value) noexcept; ++ /** ++ * Called when a string, number, boolean or null is found at the top level of a document (i.e. ++ * when there is no array or object and the entire document is a single string, number, boolean or ++ * null. ++ * ++ * This is separate from primitive() because simdjson's normal primitive parsing routines assume ++ * there is at least one more token after the value, which is only true in an array or object. ++ */ ++ simdjson_warn_unused simdjson_inline error_code visit_root_primitive(json_iterator &iter, const uint8_t *value) noexcept; ++ ++ simdjson_warn_unused simdjson_inline error_code visit_string(json_iterator &iter, const uint8_t *value, bool key = false) noexcept; ++ simdjson_warn_unused simdjson_inline error_code visit_number(json_iterator &iter, const uint8_t *value) noexcept; ++ simdjson_warn_unused simdjson_inline error_code visit_true_atom(json_iterator &iter, const uint8_t *value) noexcept; ++ simdjson_warn_unused simdjson_inline error_code visit_false_atom(json_iterator &iter, const uint8_t *value) noexcept; ++ simdjson_warn_unused simdjson_inline error_code visit_null_atom(json_iterator &iter, const uint8_t *value) noexcept; ++ ++ simdjson_warn_unused simdjson_inline error_code visit_root_string(json_iterator &iter, const uint8_t *value) noexcept; ++ simdjson_warn_unused simdjson_inline error_code visit_root_number(json_iterator &iter, const uint8_t *value) noexcept; ++ simdjson_warn_unused simdjson_inline error_code visit_root_true_atom(json_iterator &iter, const uint8_t *value) noexcept; ++ simdjson_warn_unused simdjson_inline error_code visit_root_false_atom(json_iterator &iter, const uint8_t *value) noexcept; ++ simdjson_warn_unused simdjson_inline error_code visit_root_null_atom(json_iterator &iter, const uint8_t *value) noexcept; ++ ++ /** Called each time a new field or element in an array or object is found. */ ++ simdjson_warn_unused simdjson_inline error_code increment_count(json_iterator &iter) noexcept; ++ ++ /** Next location to write to tape */ ++ tape_writer tape; ++private: ++ /** Next write location in the string buf for stage 2 parsing */ ++ uint8_t *current_string_buf_loc; ++ ++ simdjson_inline tape_builder(dom::document &doc) noexcept; ++ ++ simdjson_inline uint32_t next_tape_index(json_iterator &iter) const noexcept; ++ simdjson_inline void start_container(json_iterator &iter) noexcept; ++ simdjson_warn_unused simdjson_inline error_code end_container(json_iterator &iter, internal::tape_type start, internal::tape_type end) noexcept; ++ simdjson_warn_unused simdjson_inline error_code empty_container(json_iterator &iter, internal::tape_type start, internal::tape_type end) noexcept; ++ simdjson_inline uint8_t *on_start_string(json_iterator &iter) noexcept; ++ simdjson_inline void on_end_string(uint8_t *dst) noexcept; ++}; // class tape_builder ++ ++template ++simdjson_warn_unused simdjson_inline error_code tape_builder::parse_document( ++ dom_parser_implementation &dom_parser, ++ dom::document &doc) noexcept { ++ dom_parser.doc = &doc; ++ json_iterator iter(dom_parser, STREAMING ? dom_parser.next_structural_index : 0); ++ tape_builder builder(doc); ++ return iter.walk_document(builder); ++} ++ ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_root_primitive(json_iterator &iter, const uint8_t *value) noexcept { ++ return iter.visit_root_primitive(*this, value); ++} ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_primitive(json_iterator &iter, const uint8_t *value) noexcept { ++ return iter.visit_primitive(*this, value); ++} ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_empty_object(json_iterator &iter) noexcept { ++ return empty_container(iter, internal::tape_type::START_OBJECT, internal::tape_type::END_OBJECT); ++} ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_empty_array(json_iterator &iter) noexcept { ++ return empty_container(iter, internal::tape_type::START_ARRAY, internal::tape_type::END_ARRAY); ++} ++ ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_document_start(json_iterator &iter) noexcept { ++ start_container(iter); ++ return SUCCESS; ++} ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_object_start(json_iterator &iter) noexcept { ++ start_container(iter); ++ return SUCCESS; ++} ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_array_start(json_iterator &iter) noexcept { ++ start_container(iter); ++ return SUCCESS; ++} ++ ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_object_end(json_iterator &iter) noexcept { ++ return end_container(iter, internal::tape_type::START_OBJECT, internal::tape_type::END_OBJECT); ++} ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_array_end(json_iterator &iter) noexcept { ++ return end_container(iter, internal::tape_type::START_ARRAY, internal::tape_type::END_ARRAY); ++} ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_document_end(json_iterator &iter) noexcept { ++ constexpr uint32_t start_tape_index = 0; ++ tape.append(start_tape_index, internal::tape_type::ROOT); ++ tape_writer::write(iter.dom_parser.doc->tape[start_tape_index], next_tape_index(iter), internal::tape_type::ROOT); ++ return SUCCESS; ++} ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_key(json_iterator &iter, const uint8_t *key) noexcept { ++ return visit_string(iter, key, true); ++} ++ ++simdjson_warn_unused simdjson_inline error_code tape_builder::increment_count(json_iterator &iter) noexcept { ++ iter.dom_parser.open_containers[iter.depth].count++; // we have a key value pair in the object at parser.dom_parser.depth - 1 ++ return SUCCESS; ++} ++ ++simdjson_inline tape_builder::tape_builder(dom::document &doc) noexcept : tape{doc.tape.get()}, current_string_buf_loc{doc.string_buf.get()} {} ++ ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_string(json_iterator &iter, const uint8_t *value, bool key) noexcept { ++ iter.log_value(key ? "key" : "string"); ++ uint8_t *dst = on_start_string(iter); ++ dst = stringparsing::parse_string(value+1, dst); ++ if (dst == nullptr) { ++ iter.log_error("Invalid escape in string"); ++ return STRING_ERROR; ++ } ++ on_end_string(dst); ++ return SUCCESS; ++} ++ ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_root_string(json_iterator &iter, const uint8_t *value) noexcept { ++ return visit_string(iter, value); ++} ++ ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_number(json_iterator &iter, const uint8_t *value) noexcept { ++ iter.log_value("number"); ++ return numberparsing::parse_number(value, tape); ++} ++ ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_root_number(json_iterator &iter, const uint8_t *value) noexcept { ++ // ++ // We need to make a copy to make sure that the string is space terminated. ++ // This is not about padding the input, which should already padded up ++ // to len + SIMDJSON_PADDING. However, we have no control at this stage ++ // on how the padding was done. What if the input string was padded with nulls? ++ // It is quite common for an input string to have an extra null character (C string). ++ // We do not want to allow 9\0 (where \0 is the null character) inside a JSON ++ // document, but the string "9\0" by itself is fine. So we make a copy and ++ // pad the input with spaces when we know that there is just one input element. ++ // This copy is relatively expensive, but it will almost never be called in ++ // practice unless you are in the strange scenario where you have many JSON ++ // documents made of single atoms. ++ // ++ std::unique_ptrcopy(new (std::nothrow) uint8_t[iter.remaining_len() + SIMDJSON_PADDING]); ++ if (copy.get() == nullptr) { return MEMALLOC; } ++ std::memcpy(copy.get(), value, iter.remaining_len()); ++ std::memset(copy.get() + iter.remaining_len(), ' ', SIMDJSON_PADDING); ++ error_code error = visit_number(iter, copy.get()); ++ return error; ++} ++ ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_true_atom(json_iterator &iter, const uint8_t *value) noexcept { ++ iter.log_value("true"); ++ if (!atomparsing::is_valid_true_atom(value)) { return T_ATOM_ERROR; } ++ tape.append(0, internal::tape_type::TRUE_VALUE); ++ return SUCCESS; ++} ++ ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_root_true_atom(json_iterator &iter, const uint8_t *value) noexcept { ++ iter.log_value("true"); ++ if (!atomparsing::is_valid_true_atom(value, iter.remaining_len())) { return T_ATOM_ERROR; } ++ tape.append(0, internal::tape_type::TRUE_VALUE); ++ return SUCCESS; ++} ++ ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_false_atom(json_iterator &iter, const uint8_t *value) noexcept { ++ iter.log_value("false"); ++ if (!atomparsing::is_valid_false_atom(value)) { return F_ATOM_ERROR; } ++ tape.append(0, internal::tape_type::FALSE_VALUE); ++ return SUCCESS; ++} ++ ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_root_false_atom(json_iterator &iter, const uint8_t *value) noexcept { ++ iter.log_value("false"); ++ if (!atomparsing::is_valid_false_atom(value, iter.remaining_len())) { return F_ATOM_ERROR; } ++ tape.append(0, internal::tape_type::FALSE_VALUE); ++ return SUCCESS; ++} ++ ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_null_atom(json_iterator &iter, const uint8_t *value) noexcept { ++ iter.log_value("null"); ++ if (!atomparsing::is_valid_null_atom(value)) { return N_ATOM_ERROR; } ++ tape.append(0, internal::tape_type::NULL_VALUE); ++ return SUCCESS; ++} ++ ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_root_null_atom(json_iterator &iter, const uint8_t *value) noexcept { ++ iter.log_value("null"); ++ if (!atomparsing::is_valid_null_atom(value, iter.remaining_len())) { return N_ATOM_ERROR; } ++ tape.append(0, internal::tape_type::NULL_VALUE); ++ return SUCCESS; ++} ++ ++// private: ++ ++simdjson_inline uint32_t tape_builder::next_tape_index(json_iterator &iter) const noexcept { ++ return uint32_t(tape.next_tape_loc - iter.dom_parser.doc->tape.get()); ++} ++ ++simdjson_warn_unused simdjson_inline error_code tape_builder::empty_container(json_iterator &iter, internal::tape_type start, internal::tape_type end) noexcept { ++ auto start_index = next_tape_index(iter); ++ tape.append(start_index+2, start); ++ tape.append(start_index, end); ++ return SUCCESS; ++} ++ ++simdjson_inline void tape_builder::start_container(json_iterator &iter) noexcept { ++ iter.dom_parser.open_containers[iter.depth].tape_index = next_tape_index(iter); ++ iter.dom_parser.open_containers[iter.depth].count = 0; ++ tape.skip(); // We don't actually *write* the start element until the end. ++} ++ ++simdjson_warn_unused simdjson_inline error_code tape_builder::end_container(json_iterator &iter, internal::tape_type start, internal::tape_type end) noexcept { ++ // Write the ending tape element, pointing at the start location ++ const uint32_t start_tape_index = iter.dom_parser.open_containers[iter.depth].tape_index; ++ tape.append(start_tape_index, end); ++ // Write the start tape element, pointing at the end location (and including count) ++ // count can overflow if it exceeds 24 bits... so we saturate ++ // the convention being that a cnt of 0xffffff or more is undetermined in value (>= 0xffffff). ++ const uint32_t count = iter.dom_parser.open_containers[iter.depth].count; ++ const uint32_t cntsat = count > 0xFFFFFF ? 0xFFFFFF : count; ++ tape_writer::write(iter.dom_parser.doc->tape[start_tape_index], next_tape_index(iter) | (uint64_t(cntsat) << 32), start); ++ return SUCCESS; ++} ++ ++simdjson_inline uint8_t *tape_builder::on_start_string(json_iterator &iter) noexcept { ++ // we advance the point, accounting for the fact that we have a NULL termination ++ tape.append(current_string_buf_loc - iter.dom_parser.doc->string_buf.get(), internal::tape_type::STRING); ++ return current_string_buf_loc + sizeof(uint32_t); ++} ++ ++simdjson_inline void tape_builder::on_end_string(uint8_t *dst) noexcept { ++ uint32_t str_length = uint32_t(dst - (current_string_buf_loc + sizeof(uint32_t))); ++ // TODO check for overflow in case someone has a crazy string (>=4GB?) ++ // But only add the overflow check when the document itself exceeds 4GB ++ // Currently unneeded because we refuse to parse docs larger or equal to 4GB. ++ memcpy(current_string_buf_loc, &str_length, sizeof(uint32_t)); ++ // NULL termination is still handy if you expect all your strings to ++ // be NULL terminated? It comes at a small cost ++ *dst = 0; ++ current_string_buf_loc = dst + 1; ++} ++ ++} // namespace stage2 ++} // unnamed namespace ++} // namespace haswell ++} // namespace simdjson ++/* end file src/generic/stage2/tape_builder.h */ ++ ++// ++// Implementation-specific overrides ++// ++namespace simdjson { ++namespace haswell { ++namespace { ++namespace stage1 { ++ ++simdjson_inline uint64_t json_string_scanner::find_escaped(uint64_t backslash) { ++ if (!backslash) { uint64_t escaped = prev_escaped; prev_escaped = 0; return escaped; } ++ return find_escaped_branchless(backslash); ++} ++ ++} // namespace stage1 ++} // unnamed namespace ++ ++simdjson_warn_unused error_code implementation::minify(const uint8_t *buf, size_t len, uint8_t *dst, size_t &dst_len) const noexcept { ++ return haswell::stage1::json_minifier::minify<128>(buf, len, dst, dst_len); ++} ++ ++simdjson_warn_unused error_code dom_parser_implementation::stage1(const uint8_t *_buf, size_t _len, stage1_mode streaming) noexcept { ++ this->buf = _buf; ++ this->len = _len; ++ return haswell::stage1::json_structural_indexer::index<128>(_buf, _len, *this, streaming); ++} ++ ++simdjson_warn_unused bool implementation::validate_utf8(const char *buf, size_t len) const noexcept { ++ return haswell::stage1::generic_validate_utf8(buf,len); ++} ++ ++simdjson_warn_unused error_code dom_parser_implementation::stage2(dom::document &_doc) noexcept { ++ return stage2::tape_builder::parse_document(*this, _doc); ++} ++ ++simdjson_warn_unused error_code dom_parser_implementation::stage2_next(dom::document &_doc) noexcept { ++ return stage2::tape_builder::parse_document(*this, _doc); ++} ++ ++simdjson_warn_unused uint8_t *dom_parser_implementation::parse_string(const uint8_t *src, uint8_t *dst) const noexcept { ++ return haswell::stringparsing::parse_string(src, dst); ++} ++ ++simdjson_warn_unused error_code dom_parser_implementation::parse(const uint8_t *_buf, size_t _len, dom::document &_doc) noexcept { ++ auto error = stage1(_buf, _len, stage1_mode::regular); ++ if (error) { return error; } ++ return stage2(_doc); ++} ++ ++} // namespace haswell ++} // namespace simdjson ++ ++/* begin file include/simdjson/haswell/end.h */ ++SIMDJSON_UNTARGET_HASWELL ++/* end file include/simdjson/haswell/end.h */ ++/* end file src/haswell/dom_parser_implementation.cpp */ ++#endif ++#if SIMDJSON_IMPLEMENTATION_PPC64 ++/* begin file src/ppc64/implementation.cpp */ ++/* begin file include/simdjson/ppc64/begin.h */ ++// redefining SIMDJSON_IMPLEMENTATION to "ppc64" ++// #define SIMDJSON_IMPLEMENTATION ppc64 ++/* end file include/simdjson/ppc64/begin.h */ ++ ++namespace simdjson { ++namespace ppc64 { ++ ++simdjson_warn_unused error_code implementation::create_dom_parser_implementation( ++ size_t capacity, ++ size_t max_depth, ++ std::unique_ptr& dst ++) const noexcept { ++ dst.reset( new (std::nothrow) dom_parser_implementation() ); ++ if (!dst) { return MEMALLOC; } ++ if (auto err = dst->set_capacity(capacity)) ++ return err; ++ if (auto err = dst->set_max_depth(max_depth)) ++ return err; ++ return SUCCESS; ++} ++ ++} // namespace ppc64 ++} // namespace simdjson ++ ++/* begin file include/simdjson/ppc64/end.h */ ++/* end file include/simdjson/ppc64/end.h */ ++/* end file src/ppc64/implementation.cpp */ ++/* begin file src/ppc64/dom_parser_implementation.cpp */ ++/* begin file include/simdjson/ppc64/begin.h */ ++// redefining SIMDJSON_IMPLEMENTATION to "ppc64" ++// #define SIMDJSON_IMPLEMENTATION ppc64 ++/* end file include/simdjson/ppc64/begin.h */ ++ ++// ++// Stage 1 ++// ++namespace simdjson { ++namespace ppc64 { ++namespace { ++ ++using namespace simd; ++ ++struct json_character_block { ++ static simdjson_inline json_character_block classify(const simd::simd8x64& in); ++ ++ simdjson_inline uint64_t whitespace() const noexcept { return _whitespace; } ++ simdjson_inline uint64_t op() const noexcept { return _op; } ++ simdjson_inline uint64_t scalar() const noexcept { return ~(op() | whitespace()); } ++ ++ uint64_t _whitespace; ++ uint64_t _op; ++}; ++ ++simdjson_inline json_character_block json_character_block::classify(const simd::simd8x64& in) { ++ const simd8 table1(16, 0, 0, 0, 0, 0, 0, 0, 0, 8, 12, 1, 2, 9, 0, 0); ++ const simd8 table2(8, 0, 18, 4, 0, 1, 0, 1, 0, 0, 0, 3, 2, 1, 0, 0); ++ ++ simd8x64 v( ++ (in.chunks[0] & 0xf).lookup_16(table1) & (in.chunks[0].shr<4>()).lookup_16(table2), ++ (in.chunks[1] & 0xf).lookup_16(table1) & (in.chunks[1].shr<4>()).lookup_16(table2), ++ (in.chunks[2] & 0xf).lookup_16(table1) & (in.chunks[2].shr<4>()).lookup_16(table2), ++ (in.chunks[3] & 0xf).lookup_16(table1) & (in.chunks[3].shr<4>()).lookup_16(table2) ++ ); ++ ++ uint64_t op = simd8x64( ++ v.chunks[0].any_bits_set(0x7), ++ v.chunks[1].any_bits_set(0x7), ++ v.chunks[2].any_bits_set(0x7), ++ v.chunks[3].any_bits_set(0x7) ++ ).to_bitmask(); ++ ++ uint64_t whitespace = simd8x64( ++ v.chunks[0].any_bits_set(0x18), ++ v.chunks[1].any_bits_set(0x18), ++ v.chunks[2].any_bits_set(0x18), ++ v.chunks[3].any_bits_set(0x18) ++ ).to_bitmask(); ++ ++ return { whitespace, op }; ++} ++ ++simdjson_inline bool is_ascii(const simd8x64& input) { ++ // careful: 0x80 is not ascii. ++ return input.reduce_or().saturating_sub(0x7fu).bits_not_set_anywhere(); ++} ++ ++simdjson_unused simdjson_inline simd8 must_be_continuation(const simd8 prev1, const simd8 prev2, const simd8 prev3) { ++ simd8 is_second_byte = prev1.saturating_sub(0xc0u-1); // Only 11______ will be > 0 ++ simd8 is_third_byte = prev2.saturating_sub(0xe0u-1); // Only 111_____ will be > 0 ++ simd8 is_fourth_byte = prev3.saturating_sub(0xf0u-1); // Only 1111____ will be > 0 ++ // Caller requires a bool (all 1's). All values resulting from the subtraction will be <= 64, so signed comparison is fine. ++ return simd8(is_second_byte | is_third_byte | is_fourth_byte) > int8_t(0); ++} ++ ++simdjson_inline simd8 must_be_2_3_continuation(const simd8 prev2, const simd8 prev3) { ++ simd8 is_third_byte = prev2.saturating_sub(0xe0u-1); // Only 111_____ will be > 0 ++ simd8 is_fourth_byte = prev3.saturating_sub(0xf0u-1); // Only 1111____ will be > 0 ++ // Caller requires a bool (all 1's). All values resulting from the subtraction will be <= 64, so signed comparison is fine. ++ return simd8(is_third_byte | is_fourth_byte) > int8_t(0); ++} ++ ++} // unnamed namespace ++} // namespace ppc64 ++} // namespace simdjson ++ ++/* begin file src/generic/stage1/utf8_lookup4_algorithm.h */ ++namespace simdjson { ++namespace ppc64 { ++namespace { ++namespace utf8_validation { ++ ++using namespace simd; ++ ++ simdjson_inline simd8 check_special_cases(const simd8 input, const simd8 prev1) { ++// Bit 0 = Too Short (lead byte/ASCII followed by lead byte/ASCII) ++// Bit 1 = Too Long (ASCII followed by continuation) ++// Bit 2 = Overlong 3-byte ++// Bit 4 = Surrogate ++// Bit 5 = Overlong 2-byte ++// Bit 7 = Two Continuations ++ constexpr const uint8_t TOO_SHORT = 1<<0; // 11______ 0_______ ++ // 11______ 11______ ++ constexpr const uint8_t TOO_LONG = 1<<1; // 0_______ 10______ ++ constexpr const uint8_t OVERLONG_3 = 1<<2; // 11100000 100_____ ++ constexpr const uint8_t SURROGATE = 1<<4; // 11101101 101_____ ++ constexpr const uint8_t OVERLONG_2 = 1<<5; // 1100000_ 10______ ++ constexpr const uint8_t TWO_CONTS = 1<<7; // 10______ 10______ ++ constexpr const uint8_t TOO_LARGE = 1<<3; // 11110100 1001____ ++ // 11110100 101_____ ++ // 11110101 1001____ ++ // 11110101 101_____ ++ // 1111011_ 1001____ ++ // 1111011_ 101_____ ++ // 11111___ 1001____ ++ // 11111___ 101_____ ++ constexpr const uint8_t TOO_LARGE_1000 = 1<<6; ++ // 11110101 1000____ ++ // 1111011_ 1000____ ++ // 11111___ 1000____ ++ constexpr const uint8_t OVERLONG_4 = 1<<6; // 11110000 1000____ ++ ++ const simd8 byte_1_high = prev1.shr<4>().lookup_16( ++ // 0_______ ________ ++ TOO_LONG, TOO_LONG, TOO_LONG, TOO_LONG, ++ TOO_LONG, TOO_LONG, TOO_LONG, TOO_LONG, ++ // 10______ ________ ++ TWO_CONTS, TWO_CONTS, TWO_CONTS, TWO_CONTS, ++ // 1100____ ________ ++ TOO_SHORT | OVERLONG_2, ++ // 1101____ ________ ++ TOO_SHORT, ++ // 1110____ ________ ++ TOO_SHORT | OVERLONG_3 | SURROGATE, ++ // 1111____ ________ ++ TOO_SHORT | TOO_LARGE | TOO_LARGE_1000 | OVERLONG_4 ++ ); ++ constexpr const uint8_t CARRY = TOO_SHORT | TOO_LONG | TWO_CONTS; // These all have ____ in byte 1 . ++ const simd8 byte_1_low = (prev1 & 0x0F).lookup_16( ++ // ____0000 ________ ++ CARRY | OVERLONG_3 | OVERLONG_2 | OVERLONG_4, ++ // ____0001 ________ ++ CARRY | OVERLONG_2, ++ // ____001_ ________ ++ CARRY, ++ CARRY, ++ ++ // ____0100 ________ ++ CARRY | TOO_LARGE, ++ // ____0101 ________ ++ CARRY | TOO_LARGE | TOO_LARGE_1000, ++ // ____011_ ________ ++ CARRY | TOO_LARGE | TOO_LARGE_1000, ++ CARRY | TOO_LARGE | TOO_LARGE_1000, ++ ++ // ____1___ ________ ++ CARRY | TOO_LARGE | TOO_LARGE_1000, ++ CARRY | TOO_LARGE | TOO_LARGE_1000, ++ CARRY | TOO_LARGE | TOO_LARGE_1000, ++ CARRY | TOO_LARGE | TOO_LARGE_1000, ++ CARRY | TOO_LARGE | TOO_LARGE_1000, ++ // ____1101 ________ ++ CARRY | TOO_LARGE | TOO_LARGE_1000 | SURROGATE, ++ CARRY | TOO_LARGE | TOO_LARGE_1000, ++ CARRY | TOO_LARGE | TOO_LARGE_1000 ++ ); ++ const simd8 byte_2_high = input.shr<4>().lookup_16( ++ // ________ 0_______ ++ TOO_SHORT, TOO_SHORT, TOO_SHORT, TOO_SHORT, ++ TOO_SHORT, TOO_SHORT, TOO_SHORT, TOO_SHORT, ++ ++ // ________ 1000____ ++ TOO_LONG | OVERLONG_2 | TWO_CONTS | OVERLONG_3 | TOO_LARGE_1000 | OVERLONG_4, ++ // ________ 1001____ ++ TOO_LONG | OVERLONG_2 | TWO_CONTS | OVERLONG_3 | TOO_LARGE, ++ // ________ 101_____ ++ TOO_LONG | OVERLONG_2 | TWO_CONTS | SURROGATE | TOO_LARGE, ++ TOO_LONG | OVERLONG_2 | TWO_CONTS | SURROGATE | TOO_LARGE, ++ ++ // ________ 11______ ++ TOO_SHORT, TOO_SHORT, TOO_SHORT, TOO_SHORT ++ ); ++ return (byte_1_high & byte_1_low & byte_2_high); ++ } ++ simdjson_inline simd8 check_multibyte_lengths(const simd8 input, ++ const simd8 prev_input, const simd8 sc) { ++ simd8 prev2 = input.prev<2>(prev_input); ++ simd8 prev3 = input.prev<3>(prev_input); ++ simd8 must23 = simd8(must_be_2_3_continuation(prev2, prev3)); ++ simd8 must23_80 = must23 & uint8_t(0x80); ++ return must23_80 ^ sc; ++ } ++ ++ // ++ // Return nonzero if there are incomplete multibyte characters at the end of the block: ++ // e.g. if there is a 4-byte character, but it's 3 bytes from the end. ++ // ++ simdjson_inline simd8 is_incomplete(const simd8 input) { ++ // If the previous input's last 3 bytes match this, they're too short (they ended at EOF): ++ // ... 1111____ 111_____ 11______ ++#if SIMDJSON_IMPLEMENTATION_ICELAKE ++ static const uint8_t max_array[64] = { ++ 255, 255, 255, 255, 255, 255, 255, 255, ++ 255, 255, 255, 255, 255, 255, 255, 255, ++ 255, 255, 255, 255, 255, 255, 255, 255, ++ 255, 255, 255, 255, 255, 255, 255, 255, ++ 255, 255, 255, 255, 255, 255, 255, 255, ++ 255, 255, 255, 255, 255, 255, 255, 255, ++ 255, 255, 255, 255, 255, 255, 255, 255, ++ 255, 255, 255, 255, 255, 0xf0u-1, 0xe0u-1, 0xc0u-1 ++ }; ++#else ++ static const uint8_t max_array[32] = { ++ 255, 255, 255, 255, 255, 255, 255, 255, ++ 255, 255, 255, 255, 255, 255, 255, 255, ++ 255, 255, 255, 255, 255, 255, 255, 255, ++ 255, 255, 255, 255, 255, 0xf0u-1, 0xe0u-1, 0xc0u-1 ++ }; ++#endif ++ const simd8 max_value(&max_array[sizeof(max_array)-sizeof(simd8)]); ++ return input.gt_bits(max_value); ++ } ++ ++ struct utf8_checker { ++ // If this is nonzero, there has been a UTF-8 error. ++ simd8 error; ++ // The last input we received ++ simd8 prev_input_block; ++ // Whether the last input we received was incomplete (used for ASCII fast path) ++ simd8 prev_incomplete; ++ ++ // ++ // Check whether the current bytes are valid UTF-8. ++ // ++ simdjson_inline void check_utf8_bytes(const simd8 input, const simd8 prev_input) { ++ // Flip prev1...prev3 so we can easily determine if they are 2+, 3+ or 4+ lead bytes ++ // (2, 3, 4-byte leads become large positive numbers instead of small negative numbers) ++ simd8 prev1 = input.prev<1>(prev_input); ++ simd8 sc = check_special_cases(input, prev1); ++ this->error |= check_multibyte_lengths(input, prev_input, sc); ++ } ++ ++ // The only problem that can happen at EOF is that a multibyte character is too short ++ // or a byte value too large in the last bytes: check_special_cases only checks for bytes ++ // too large in the first of two bytes. ++ simdjson_inline void check_eof() { ++ // If the previous block had incomplete UTF-8 characters at the end, an ASCII block can't ++ // possibly finish them. ++ this->error |= this->prev_incomplete; ++ } ++ ++#ifndef SIMDJSON_IF_CONSTEXPR ++#if SIMDJSON_CPLUSPLUS17 ++#define SIMDJSON_IF_CONSTEXPR if constexpr ++#else ++#define SIMDJSON_IF_CONSTEXPR if ++#endif ++#endif ++ ++ simdjson_inline void check_next_input(const simd8x64& input) { ++ if(simdjson_likely(is_ascii(input))) { ++ this->error |= this->prev_incomplete; ++ } else { ++ // you might think that a for-loop would work, but under Visual Studio, it is not good enough. ++ static_assert((simd8x64::NUM_CHUNKS == 1) ++ ||(simd8x64::NUM_CHUNKS == 2) ++ || (simd8x64::NUM_CHUNKS == 4), ++ "We support one, two or four chunks per 64-byte block."); ++ SIMDJSON_IF_CONSTEXPR (simd8x64::NUM_CHUNKS == 1) { ++ this->check_utf8_bytes(input.chunks[0], this->prev_input_block); ++ } else SIMDJSON_IF_CONSTEXPR (simd8x64::NUM_CHUNKS == 2) { ++ this->check_utf8_bytes(input.chunks[0], this->prev_input_block); ++ this->check_utf8_bytes(input.chunks[1], input.chunks[0]); ++ } else SIMDJSON_IF_CONSTEXPR (simd8x64::NUM_CHUNKS == 4) { ++ this->check_utf8_bytes(input.chunks[0], this->prev_input_block); ++ this->check_utf8_bytes(input.chunks[1], input.chunks[0]); ++ this->check_utf8_bytes(input.chunks[2], input.chunks[1]); ++ this->check_utf8_bytes(input.chunks[3], input.chunks[2]); ++ } ++ this->prev_incomplete = is_incomplete(input.chunks[simd8x64::NUM_CHUNKS-1]); ++ this->prev_input_block = input.chunks[simd8x64::NUM_CHUNKS-1]; ++ } ++ } ++ // do not forget to call check_eof! ++ simdjson_inline error_code errors() { ++ return this->error.any_bits_set_anywhere() ? error_code::UTF8_ERROR : error_code::SUCCESS; ++ } ++ ++ }; // struct utf8_checker ++} // namespace utf8_validation ++ ++using utf8_validation::utf8_checker; ++ ++} // unnamed namespace ++} // namespace ppc64 ++} // namespace simdjson ++/* end file src/generic/stage1/utf8_lookup4_algorithm.h */ ++/* begin file src/generic/stage1/json_structural_indexer.h */ ++// This file contains the common code every implementation uses in stage1 ++// It is intended to be included multiple times and compiled multiple times ++// We assume the file in which it is included already includes ++// "simdjson/stage1.h" (this simplifies amalgation) ++ ++/* begin file src/generic/stage1/buf_block_reader.h */ ++namespace simdjson { ++namespace ppc64 { ++namespace { ++ ++// Walks through a buffer in block-sized increments, loading the last part with spaces ++template ++struct buf_block_reader { ++public: ++ simdjson_inline buf_block_reader(const uint8_t *_buf, size_t _len); ++ simdjson_inline size_t block_index(); ++ simdjson_inline bool has_full_block() const; ++ simdjson_inline const uint8_t *full_block() const; ++ /** ++ * Get the last block, padded with spaces. ++ * ++ * There will always be a last block, with at least 1 byte, unless len == 0 (in which case this ++ * function fills the buffer with spaces and returns 0. In particular, if len == STEP_SIZE there ++ * will be 0 full_blocks and 1 remainder block with STEP_SIZE bytes and no spaces for padding. ++ * ++ * @return the number of effective characters in the last block. ++ */ ++ simdjson_inline size_t get_remainder(uint8_t *dst) const; ++ simdjson_inline void advance(); ++private: ++ const uint8_t *buf; ++ const size_t len; ++ const size_t lenminusstep; ++ size_t idx; ++}; ++ ++// Routines to print masks and text for debugging bitmask operations ++simdjson_unused static char * format_input_text_64(const uint8_t *text) { ++ static char buf[sizeof(simd8x64) + 1]; ++ for (size_t i=0; i); i++) { ++ buf[i] = int8_t(text[i]) < ' ' ? '_' : int8_t(text[i]); ++ } ++ buf[sizeof(simd8x64)] = '\0'; ++ return buf; ++} ++ ++// Routines to print masks and text for debugging bitmask operations ++simdjson_unused static char * format_input_text(const simd8x64& in) { ++ static char buf[sizeof(simd8x64) + 1]; ++ in.store(reinterpret_cast(buf)); ++ for (size_t i=0; i); i++) { ++ if (buf[i] < ' ') { buf[i] = '_'; } ++ } ++ buf[sizeof(simd8x64)] = '\0'; ++ return buf; ++} ++ ++simdjson_unused static char * format_mask(uint64_t mask) { ++ static char buf[sizeof(simd8x64) + 1]; ++ for (size_t i=0; i<64; i++) { ++ buf[i] = (mask & (size_t(1) << i)) ? 'X' : ' '; ++ } ++ buf[64] = '\0'; ++ return buf; ++} ++ ++template ++simdjson_inline buf_block_reader::buf_block_reader(const uint8_t *_buf, size_t _len) : buf{_buf}, len{_len}, lenminusstep{len < STEP_SIZE ? 0 : len - STEP_SIZE}, idx{0} {} ++ ++template ++simdjson_inline size_t buf_block_reader::block_index() { return idx; } ++ ++template ++simdjson_inline bool buf_block_reader::has_full_block() const { ++ return idx < lenminusstep; ++} ++ ++template ++simdjson_inline const uint8_t *buf_block_reader::full_block() const { ++ return &buf[idx]; ++} ++ ++template ++simdjson_inline size_t buf_block_reader::get_remainder(uint8_t *dst) const { ++ if(len == idx) { return 0; } // memcpy(dst, null, 0) will trigger an error with some sanitizers ++ std::memset(dst, 0x20, STEP_SIZE); // std::memset STEP_SIZE because it's more efficient to write out 8 or 16 bytes at once. ++ std::memcpy(dst, buf + idx, len - idx); ++ return len - idx; ++} ++ ++template ++simdjson_inline void buf_block_reader::advance() { ++ idx += STEP_SIZE; ++} ++ ++} // unnamed namespace ++} // namespace ppc64 ++} // namespace simdjson ++/* end file src/generic/stage1/buf_block_reader.h */ ++/* begin file src/generic/stage1/json_string_scanner.h */ ++namespace simdjson { ++namespace ppc64 { ++namespace { ++namespace stage1 { ++ ++struct json_string_block { ++ // We spell out the constructors in the hope of resolving inlining issues with Visual Studio 2017 ++ simdjson_inline json_string_block(uint64_t backslash, uint64_t escaped, uint64_t quote, uint64_t in_string) : ++ _backslash(backslash), _escaped(escaped), _quote(quote), _in_string(in_string) {} ++ ++ // Escaped characters (characters following an escape() character) ++ simdjson_inline uint64_t escaped() const { return _escaped; } ++ // Escape characters (backslashes that are not escaped--i.e. in \\, includes only the first \) ++ simdjson_inline uint64_t escape() const { return _backslash & ~_escaped; } ++ // Real (non-backslashed) quotes ++ simdjson_inline uint64_t quote() const { return _quote; } ++ // Start quotes of strings ++ simdjson_inline uint64_t string_start() const { return _quote & _in_string; } ++ // End quotes of strings ++ simdjson_inline uint64_t string_end() const { return _quote & ~_in_string; } ++ // Only characters inside the string (not including the quotes) ++ simdjson_inline uint64_t string_content() const { return _in_string & ~_quote; } ++ // Return a mask of whether the given characters are inside a string (only works on non-quotes) ++ simdjson_inline uint64_t non_quote_inside_string(uint64_t mask) const { return mask & _in_string; } ++ // Return a mask of whether the given characters are inside a string (only works on non-quotes) ++ simdjson_inline uint64_t non_quote_outside_string(uint64_t mask) const { return mask & ~_in_string; } ++ // Tail of string (everything except the start quote) ++ simdjson_inline uint64_t string_tail() const { return _in_string ^ _quote; } ++ ++ // backslash characters ++ uint64_t _backslash; ++ // escaped characters (backslashed--does not include the hex characters after \u) ++ uint64_t _escaped; ++ // real quotes (non-backslashed ones) ++ uint64_t _quote; ++ // string characters (includes start quote but not end quote) ++ uint64_t _in_string; ++}; ++ ++// Scans blocks for string characters, storing the state necessary to do so ++class json_string_scanner { ++public: ++ simdjson_inline json_string_block next(const simd::simd8x64& in); ++ // Returns either UNCLOSED_STRING or SUCCESS ++ simdjson_inline error_code finish(); ++ ++private: ++ // Intended to be defined by the implementation ++ simdjson_inline uint64_t find_escaped(uint64_t escape); ++ simdjson_inline uint64_t find_escaped_branchless(uint64_t escape); ++ ++ // Whether the last iteration was still inside a string (all 1's = true, all 0's = false). ++ uint64_t prev_in_string = 0ULL; ++ // Whether the first character of the next iteration is escaped. ++ uint64_t prev_escaped = 0ULL; ++}; ++ ++// ++// Finds escaped characters (characters following \). ++// ++// Handles runs of backslashes like \\\" and \\\\" correctly (yielding 0101 and 01010, respectively). ++// ++// Does this by: ++// - Shift the escape mask to get potentially escaped characters (characters after backslashes). ++// - Mask escaped sequences that start on *even* bits with 1010101010 (odd bits are escaped, even bits are not) ++// - Mask escaped sequences that start on *odd* bits with 0101010101 (even bits are escaped, odd bits are not) ++// ++// To distinguish between escaped sequences starting on even/odd bits, it finds the start of all ++// escape sequences, filters out the ones that start on even bits, and adds that to the mask of ++// escape sequences. This causes the addition to clear out the sequences starting on odd bits (since ++// the start bit causes a carry), and leaves even-bit sequences alone. ++// ++// Example: ++// ++// text | \\\ | \\\"\\\" \\\" \\"\\" | ++// escape | xxx | xx xxx xxx xx xx | Removed overflow backslash; will | it into follows_escape ++// odd_starts | x | x x x | escape & ~even_bits & ~follows_escape ++// even_seq | c| cxxx c xx c | c = carry bit -- will be masked out later ++// invert_mask | | cxxx c xx c| even_seq << 1 ++// follows_escape | xx | x xx xxx xxx xx xx | Includes overflow bit ++// escaped | x | x x x x x x x x | ++// desired | x | x x x x x x x x | ++// text | \\\ | \\\"\\\" \\\" \\"\\" | ++// ++simdjson_inline uint64_t json_string_scanner::find_escaped_branchless(uint64_t backslash) { ++ // If there was overflow, pretend the first character isn't a backslash ++ backslash &= ~prev_escaped; ++ uint64_t follows_escape = backslash << 1 | prev_escaped; ++ ++ // Get sequences starting on even bits by clearing out the odd series using + ++ const uint64_t even_bits = 0x5555555555555555ULL; ++ uint64_t odd_sequence_starts = backslash & ~even_bits & ~follows_escape; ++ uint64_t sequences_starting_on_even_bits; ++ prev_escaped = add_overflow(odd_sequence_starts, backslash, &sequences_starting_on_even_bits); ++ uint64_t invert_mask = sequences_starting_on_even_bits << 1; // The mask we want to return is the *escaped* bits, not escapes. ++ ++ // Mask every other backslashed character as an escaped character ++ // Flip the mask for sequences that start on even bits, to correct them ++ return (even_bits ^ invert_mask) & follows_escape; ++} ++ ++// ++// Return a mask of all string characters plus end quotes. ++// ++// prev_escaped is overflow saying whether the next character is escaped. ++// prev_in_string is overflow saying whether we're still in a string. ++// ++// Backslash sequences outside of quotes will be detected in stage 2. ++// ++simdjson_inline json_string_block json_string_scanner::next(const simd::simd8x64& in) { ++ const uint64_t backslash = in.eq('\\'); ++ const uint64_t escaped = find_escaped(backslash); ++ const uint64_t quote = in.eq('"') & ~escaped; ++ ++ // ++ // prefix_xor flips on bits inside the string (and flips off the end quote). ++ // ++ // Then we xor with prev_in_string: if we were in a string already, its effect is flipped ++ // (characters inside strings are outside, and characters outside strings are inside). ++ // ++ const uint64_t in_string = prefix_xor(quote) ^ prev_in_string; ++ ++ // ++ // Check if we're still in a string at the end of the box so the next block will know ++ // ++ // right shift of a signed value expected to be well-defined and standard ++ // compliant as of C++20, John Regher from Utah U. says this is fine code ++ // ++ prev_in_string = uint64_t(static_cast(in_string) >> 63); ++ ++ // Use ^ to turn the beginning quote off, and the end quote on. ++ ++ // We are returning a function-local object so either we get a move constructor ++ // or we get copy elision. ++ return json_string_block( ++ backslash, ++ escaped, ++ quote, ++ in_string ++ ); ++} ++ ++simdjson_inline error_code json_string_scanner::finish() { ++ if (prev_in_string) { ++ return UNCLOSED_STRING; ++ } ++ return SUCCESS; ++} ++ ++} // namespace stage1 ++} // unnamed namespace ++} // namespace ppc64 ++} // namespace simdjson ++/* end file src/generic/stage1/json_string_scanner.h */ ++/* begin file src/generic/stage1/json_scanner.h */ ++namespace simdjson { ++namespace ppc64 { ++namespace { ++namespace stage1 { ++ ++/** ++ * A block of scanned json, with information on operators and scalars. ++ * ++ * We seek to identify pseudo-structural characters. Anything that is inside ++ * a string must be omitted (hence & ~_string.string_tail()). ++ * Otherwise, pseudo-structural characters come in two forms. ++ * 1. We have the structural characters ([,],{,},:, comma). The ++ * term 'structural character' is from the JSON RFC. ++ * 2. We have the 'scalar pseudo-structural characters'. ++ * Scalars are quotes, and any character except structural characters and white space. ++ * ++ * To identify the scalar pseudo-structural characters, we must look at what comes ++ * before them: it must be a space, a quote or a structural characters. ++ * Starting with simdjson v0.3, we identify them by ++ * negation: we identify everything that is followed by a non-quote scalar, ++ * and we negate that. Whatever remains must be a 'scalar pseudo-structural character'. ++ */ ++struct json_block { ++public: ++ // We spell out the constructors in the hope of resolving inlining issues with Visual Studio 2017 ++ simdjson_inline json_block(json_string_block&& string, json_character_block characters, uint64_t follows_potential_nonquote_scalar) : ++ _string(std::move(string)), _characters(characters), _follows_potential_nonquote_scalar(follows_potential_nonquote_scalar) {} ++ simdjson_inline json_block(json_string_block string, json_character_block characters, uint64_t follows_potential_nonquote_scalar) : ++ _string(string), _characters(characters), _follows_potential_nonquote_scalar(follows_potential_nonquote_scalar) {} ++ ++ /** ++ * The start of structurals. ++ * In simdjson prior to v0.3, these were called the pseudo-structural characters. ++ **/ ++ simdjson_inline uint64_t structural_start() const noexcept { return potential_structural_start() & ~_string.string_tail(); } ++ /** All JSON whitespace (i.e. not in a string) */ ++ simdjson_inline uint64_t whitespace() const noexcept { return non_quote_outside_string(_characters.whitespace()); } ++ ++ // Helpers ++ ++ /** Whether the given characters are inside a string (only works on non-quotes) */ ++ simdjson_inline uint64_t non_quote_inside_string(uint64_t mask) const noexcept { return _string.non_quote_inside_string(mask); } ++ /** Whether the given characters are outside a string (only works on non-quotes) */ ++ simdjson_inline uint64_t non_quote_outside_string(uint64_t mask) const noexcept { return _string.non_quote_outside_string(mask); } ++ ++ // string and escape characters ++ json_string_block _string; ++ // whitespace, structural characters ('operators'), scalars ++ json_character_block _characters; ++ // whether the previous character was a scalar ++ uint64_t _follows_potential_nonquote_scalar; ++private: ++ // Potential structurals (i.e. disregarding strings) ++ ++ /** ++ * structural elements ([,],{,},:, comma) plus scalar starts like 123, true and "abc". ++ * They may reside inside a string. ++ **/ ++ simdjson_inline uint64_t potential_structural_start() const noexcept { return _characters.op() | potential_scalar_start(); } ++ /** ++ * The start of non-operator runs, like 123, true and "abc". ++ * It main reside inside a string. ++ **/ ++ simdjson_inline uint64_t potential_scalar_start() const noexcept { ++ // The term "scalar" refers to anything except structural characters and white space ++ // (so letters, numbers, quotes). ++ // Whenever it is preceded by something that is not a structural element ({,},[,],:, ") nor a white-space ++ // then we know that it is irrelevant structurally. ++ return _characters.scalar() & ~follows_potential_scalar(); ++ } ++ /** ++ * Whether the given character is immediately after a non-operator like 123, true. ++ * The characters following a quote are not included. ++ */ ++ simdjson_inline uint64_t follows_potential_scalar() const noexcept { ++ // _follows_potential_nonquote_scalar: is defined as marking any character that follows a character ++ // that is not a structural element ({,},[,],:, comma) nor a quote (") and that is not a ++ // white space. ++ // It is understood that within quoted region, anything at all could be marked (irrelevant). ++ return _follows_potential_nonquote_scalar; ++ } ++}; ++ ++/** ++ * Scans JSON for important bits: structural characters or 'operators', strings, and scalars. ++ * ++ * The scanner starts by calculating two distinct things: ++ * - string characters (taking \" into account) ++ * - structural characters or 'operators' ([]{},:, comma) ++ * and scalars (runs of non-operators like 123, true and "abc") ++ * ++ * To minimize data dependency (a key component of the scanner's speed), it finds these in parallel: ++ * in particular, the operator/scalar bit will find plenty of things that are actually part of ++ * strings. When we're done, json_block will fuse the two together by masking out tokens that are ++ * part of a string. ++ */ ++class json_scanner { ++public: ++ json_scanner() = default; ++ simdjson_inline json_block next(const simd::simd8x64& in); ++ // Returns either UNCLOSED_STRING or SUCCESS ++ simdjson_inline error_code finish(); ++ ++private: ++ // Whether the last character of the previous iteration is part of a scalar token ++ // (anything except whitespace or a structural character/'operator'). ++ uint64_t prev_scalar = 0ULL; ++ json_string_scanner string_scanner{}; ++}; ++ ++ ++// ++// Check if the current character immediately follows a matching character. ++// ++// For example, this checks for quotes with backslashes in front of them: ++// ++// const uint64_t backslashed_quote = in.eq('"') & immediately_follows(in.eq('\'), prev_backslash); ++// ++simdjson_inline uint64_t follows(const uint64_t match, uint64_t &overflow) { ++ const uint64_t result = match << 1 | overflow; ++ overflow = match >> 63; ++ return result; ++} ++ ++simdjson_inline json_block json_scanner::next(const simd::simd8x64& in) { ++ json_string_block strings = string_scanner.next(in); ++ // identifies the white-space and the structural characters ++ json_character_block characters = json_character_block::classify(in); ++ // The term "scalar" refers to anything except structural characters and white space ++ // (so letters, numbers, quotes). ++ // We want follows_scalar to mark anything that follows a non-quote scalar (so letters and numbers). ++ // ++ // A terminal quote should either be followed by a structural character (comma, brace, bracket, colon) ++ // or nothing. However, we still want ' "a string"true ' to mark the 't' of 'true' as a potential ++ // pseudo-structural character just like we would if we had ' "a string" true '; otherwise we ++ // may need to add an extra check when parsing strings. ++ // ++ // Performance: there are many ways to skin this cat. ++ const uint64_t nonquote_scalar = characters.scalar() & ~strings.quote(); ++ uint64_t follows_nonquote_scalar = follows(nonquote_scalar, prev_scalar); ++ // We are returning a function-local object so either we get a move constructor ++ // or we get copy elision. ++ return json_block( ++ strings,// strings is a function-local object so either it moves or the copy is elided. ++ characters, ++ follows_nonquote_scalar ++ ); ++} ++ ++simdjson_inline error_code json_scanner::finish() { ++ return string_scanner.finish(); ++} ++ ++} // namespace stage1 ++} // unnamed namespace ++} // namespace ppc64 ++} // namespace simdjson ++/* end file src/generic/stage1/json_scanner.h */ ++/* begin file src/generic/stage1/json_minifier.h */ ++// This file contains the common code every implementation uses in stage1 ++// It is intended to be included multiple times and compiled multiple times ++// We assume the file in which it is included already includes ++// "simdjson/stage1.h" (this simplifies amalgation) ++ ++namespace simdjson { ++namespace ppc64 { ++namespace { ++namespace stage1 { ++ ++class json_minifier { ++public: ++ template ++ static error_code minify(const uint8_t *buf, size_t len, uint8_t *dst, size_t &dst_len) noexcept; ++ ++private: ++ simdjson_inline json_minifier(uint8_t *_dst) ++ : dst{_dst} ++ {} ++ template ++ simdjson_inline void step(const uint8_t *block_buf, buf_block_reader &reader) noexcept; ++ simdjson_inline void next(const simd::simd8x64& in, const json_block& block); ++ simdjson_inline error_code finish(uint8_t *dst_start, size_t &dst_len); ++ json_scanner scanner{}; ++ uint8_t *dst; ++}; ++ ++simdjson_inline void json_minifier::next(const simd::simd8x64& in, const json_block& block) { ++ uint64_t mask = block.whitespace(); ++ dst += in.compress(mask, dst); ++} ++ ++simdjson_inline error_code json_minifier::finish(uint8_t *dst_start, size_t &dst_len) { ++ error_code error = scanner.finish(); ++ if (error) { dst_len = 0; return error; } ++ dst_len = dst - dst_start; ++ return SUCCESS; ++} ++ ++template<> ++simdjson_inline void json_minifier::step<128>(const uint8_t *block_buf, buf_block_reader<128> &reader) noexcept { ++ simd::simd8x64 in_1(block_buf); ++ simd::simd8x64 in_2(block_buf+64); ++ json_block block_1 = scanner.next(in_1); ++ json_block block_2 = scanner.next(in_2); ++ this->next(in_1, block_1); ++ this->next(in_2, block_2); ++ reader.advance(); ++} ++ ++template<> ++simdjson_inline void json_minifier::step<64>(const uint8_t *block_buf, buf_block_reader<64> &reader) noexcept { ++ simd::simd8x64 in_1(block_buf); ++ json_block block_1 = scanner.next(in_1); ++ this->next(block_buf, block_1); ++ reader.advance(); ++} ++ ++template ++error_code json_minifier::minify(const uint8_t *buf, size_t len, uint8_t *dst, size_t &dst_len) noexcept { ++ buf_block_reader reader(buf, len); ++ json_minifier minifier(dst); ++ ++ // Index the first n-1 blocks ++ while (reader.has_full_block()) { ++ minifier.step(reader.full_block(), reader); ++ } ++ ++ // Index the last (remainder) block, padded with spaces ++ uint8_t block[STEP_SIZE]; ++ size_t remaining_bytes = reader.get_remainder(block); ++ if (remaining_bytes > 0) { ++ // We do not want to write directly to the output stream. Rather, we write ++ // to a local buffer (for safety). ++ uint8_t out_block[STEP_SIZE]; ++ uint8_t * const guarded_dst{minifier.dst}; ++ minifier.dst = out_block; ++ minifier.step(block, reader); ++ size_t to_write = minifier.dst - out_block; ++ // In some cases, we could be enticed to consider the padded spaces ++ // as part of the string. This is fine as long as we do not write more ++ // than we consumed. ++ if(to_write > remaining_bytes) { to_write = remaining_bytes; } ++ memcpy(guarded_dst, out_block, to_write); ++ minifier.dst = guarded_dst + to_write; ++ } ++ return minifier.finish(dst, dst_len); ++} ++ ++} // namespace stage1 ++} // unnamed namespace ++} // namespace ppc64 ++} // namespace simdjson ++/* end file src/generic/stage1/json_minifier.h */ ++/* begin file src/generic/stage1/find_next_document_index.h */ ++namespace simdjson { ++namespace ppc64 { ++namespace { ++ ++/** ++ * This algorithm is used to quickly identify the last structural position that ++ * makes up a complete document. ++ * ++ * It does this by going backwards and finding the last *document boundary* (a ++ * place where one value follows another without a comma between them). If the ++ * last document (the characters after the boundary) has an equal number of ++ * start and end brackets, it is considered complete. ++ * ++ * Simply put, we iterate over the structural characters, starting from ++ * the end. We consider that we found the end of a JSON document when the ++ * first element of the pair is NOT one of these characters: '{' '[' ':' ',' ++ * and when the second element is NOT one of these characters: '}' ']' ':' ','. ++ * ++ * This simple comparison works most of the time, but it does not cover cases ++ * where the batch's structural indexes contain a perfect amount of documents. ++ * In such a case, we do not have access to the structural index which follows ++ * the last document, therefore, we do not have access to the second element in ++ * the pair, and that means we cannot identify the last document. To fix this ++ * issue, we keep a count of the open and closed curly/square braces we found ++ * while searching for the pair. When we find a pair AND the count of open and ++ * closed curly/square braces is the same, we know that we just passed a ++ * complete document, therefore the last json buffer location is the end of the ++ * batch. ++ */ ++simdjson_inline uint32_t find_next_document_index(dom_parser_implementation &parser) { ++ // Variant: do not count separately, just figure out depth ++ if(parser.n_structural_indexes == 0) { return 0; } ++ auto arr_cnt = 0; ++ auto obj_cnt = 0; ++ for (auto i = parser.n_structural_indexes - 1; i > 0; i--) { ++ auto idxb = parser.structural_indexes[i]; ++ switch (parser.buf[idxb]) { ++ case ':': ++ case ',': ++ continue; ++ case '}': ++ obj_cnt--; ++ continue; ++ case ']': ++ arr_cnt--; ++ continue; ++ case '{': ++ obj_cnt++; ++ break; ++ case '[': ++ arr_cnt++; ++ break; ++ } ++ auto idxa = parser.structural_indexes[i - 1]; ++ switch (parser.buf[idxa]) { ++ case '{': ++ case '[': ++ case ':': ++ case ',': ++ continue; ++ } ++ // Last document is complete, so the next document will appear after! ++ if (!arr_cnt && !obj_cnt) { ++ return parser.n_structural_indexes; ++ } ++ // Last document is incomplete; mark the document at i + 1 as the next one ++ return i; ++ } ++ // If we made it to the end, we want to finish counting to see if we have a full document. ++ switch (parser.buf[parser.structural_indexes[0]]) { ++ case '}': ++ obj_cnt--; ++ break; ++ case ']': ++ arr_cnt--; ++ break; ++ case '{': ++ obj_cnt++; ++ break; ++ case '[': ++ arr_cnt++; ++ break; ++ } ++ if (!arr_cnt && !obj_cnt) { ++ // We have a complete document. ++ return parser.n_structural_indexes; ++ } ++ return 0; ++} ++ ++} // unnamed namespace ++} // namespace ppc64 ++} // namespace simdjson ++/* end file src/generic/stage1/find_next_document_index.h */ ++ ++namespace simdjson { ++namespace ppc64 { ++namespace { ++namespace stage1 { ++ ++class bit_indexer { ++public: ++ uint32_t *tail; ++ ++ simdjson_inline bit_indexer(uint32_t *index_buf) : tail(index_buf) {} ++ ++ // flatten out values in 'bits' assuming that they are are to have values of idx ++ // plus their position in the bitvector, and store these indexes at ++ // base_ptr[base] incrementing base as we go ++ // will potentially store extra values beyond end of valid bits, so base_ptr ++ // needs to be large enough to handle this ++ // ++ // If the kernel sets SIMDJSON_CUSTOM_BIT_INDEXER, then it will provide its own ++ // version of the code. ++#ifdef SIMDJSON_CUSTOM_BIT_INDEXER ++ simdjson_inline void write(uint32_t idx, uint64_t bits); ++#else ++ simdjson_inline void write(uint32_t idx, uint64_t bits) { ++ // In some instances, the next branch is expensive because it is mispredicted. ++ // Unfortunately, in other cases, ++ // it helps tremendously. ++ if (bits == 0) ++ return; ++#if SIMDJSON_PREFER_REVERSE_BITS ++ /** ++ * ARM lacks a fast trailing zero instruction, but it has a fast ++ * bit reversal instruction and a fast leading zero instruction. ++ * Thus it may be profitable to reverse the bits (once) and then ++ * to rely on a sequence of instructions that call the leading ++ * zero instruction. ++ * ++ * Performance notes: ++ * The chosen routine is not optimal in terms of data dependency ++ * since zero_leading_bit might require two instructions. However, ++ * it tends to minimize the total number of instructions which is ++ * beneficial. ++ */ ++ ++ uint64_t rev_bits = reverse_bits(bits); ++ int cnt = static_cast(count_ones(bits)); ++ int i = 0; ++ // Do the first 8 all together ++ for (; i<8; i++) { ++ int lz = leading_zeroes(rev_bits); ++ this->tail[i] = static_cast(idx) + lz; ++ rev_bits = zero_leading_bit(rev_bits, lz); ++ } ++ // Do the next 8 all together (we hope in most cases it won't happen at all ++ // and the branch is easily predicted). ++ if (simdjson_unlikely(cnt > 8)) { ++ i = 8; ++ for (; i<16; i++) { ++ int lz = leading_zeroes(rev_bits); ++ this->tail[i] = static_cast(idx) + lz; ++ rev_bits = zero_leading_bit(rev_bits, lz); ++ } ++ ++ ++ // Most files don't have 16+ structurals per block, so we take several basically guaranteed ++ // branch mispredictions here. 16+ structurals per block means either punctuation ({} [] , :) ++ // or the start of a value ("abc" true 123) every four characters. ++ if (simdjson_unlikely(cnt > 16)) { ++ i = 16; ++ while (rev_bits != 0) { ++ int lz = leading_zeroes(rev_bits); ++ this->tail[i++] = static_cast(idx) + lz; ++ rev_bits = zero_leading_bit(rev_bits, lz); ++ } ++ } ++ } ++ this->tail += cnt; ++#else // SIMDJSON_PREFER_REVERSE_BITS ++ /** ++ * Under recent x64 systems, we often have both a fast trailing zero ++ * instruction and a fast 'clear-lower-bit' instruction so the following ++ * algorithm can be competitive. ++ */ ++ ++ int cnt = static_cast(count_ones(bits)); ++ // Do the first 8 all together ++ for (int i=0; i<8; i++) { ++ this->tail[i] = idx + trailing_zeroes(bits); ++ bits = clear_lowest_bit(bits); ++ } ++ ++ // Do the next 8 all together (we hope in most cases it won't happen at all ++ // and the branch is easily predicted). ++ if (simdjson_unlikely(cnt > 8)) { ++ for (int i=8; i<16; i++) { ++ this->tail[i] = idx + trailing_zeroes(bits); ++ bits = clear_lowest_bit(bits); ++ } ++ ++ // Most files don't have 16+ structurals per block, so we take several basically guaranteed ++ // branch mispredictions here. 16+ structurals per block means either punctuation ({} [] , :) ++ // or the start of a value ("abc" true 123) every four characters. ++ if (simdjson_unlikely(cnt > 16)) { ++ int i = 16; ++ do { ++ this->tail[i] = idx + trailing_zeroes(bits); ++ bits = clear_lowest_bit(bits); ++ i++; ++ } while (i < cnt); ++ } ++ } ++ ++ this->tail += cnt; ++#endif ++ } ++#endif // SIMDJSON_CUSTOM_BIT_INDEXER ++ ++}; ++ ++class json_structural_indexer { ++public: ++ /** ++ * Find the important bits of JSON in a 128-byte chunk, and add them to structural_indexes. ++ * ++ * @param partial Setting the partial parameter to true allows the find_structural_bits to ++ * tolerate unclosed strings. The caller should still ensure that the input is valid UTF-8. If ++ * you are processing substrings, you may want to call on a function like trimmed_length_safe_utf8. ++ */ ++ template ++ static error_code index(const uint8_t *buf, size_t len, dom_parser_implementation &parser, stage1_mode partial) noexcept; ++ ++private: ++ simdjson_inline json_structural_indexer(uint32_t *structural_indexes); ++ template ++ simdjson_inline void step(const uint8_t *block, buf_block_reader &reader) noexcept; ++ simdjson_inline void next(const simd::simd8x64& in, const json_block& block, size_t idx); ++ simdjson_inline error_code finish(dom_parser_implementation &parser, size_t idx, size_t len, stage1_mode partial); ++ ++ json_scanner scanner{}; ++ utf8_checker checker{}; ++ bit_indexer indexer; ++ uint64_t prev_structurals = 0; ++ uint64_t unescaped_chars_error = 0; ++}; ++ ++simdjson_inline json_structural_indexer::json_structural_indexer(uint32_t *structural_indexes) : indexer{structural_indexes} {} ++ ++// Skip the last character if it is partial ++simdjson_inline size_t trim_partial_utf8(const uint8_t *buf, size_t len) { ++ if (simdjson_unlikely(len < 3)) { ++ switch (len) { ++ case 2: ++ if (buf[len-1] >= 0xc0) { return len-1; } // 2-, 3- and 4-byte characters with only 1 byte left ++ if (buf[len-2] >= 0xe0) { return len-2; } // 3- and 4-byte characters with only 2 bytes left ++ return len; ++ case 1: ++ if (buf[len-1] >= 0xc0) { return len-1; } // 2-, 3- and 4-byte characters with only 1 byte left ++ return len; ++ case 0: ++ return len; ++ } ++ } ++ if (buf[len-1] >= 0xc0) { return len-1; } // 2-, 3- and 4-byte characters with only 1 byte left ++ if (buf[len-2] >= 0xe0) { return len-2; } // 3- and 4-byte characters with only 1 byte left ++ if (buf[len-3] >= 0xf0) { return len-3; } // 4-byte characters with only 3 bytes left ++ return len; ++} ++ ++// ++// PERF NOTES: ++// We pipe 2 inputs through these stages: ++// 1. Load JSON into registers. This takes a long time and is highly parallelizable, so we load ++// 2 inputs' worth at once so that by the time step 2 is looking for them input, it's available. ++// 2. Scan the JSON for critical data: strings, scalars and operators. This is the critical path. ++// The output of step 1 depends entirely on this information. These functions don't quite use ++// up enough CPU: the second half of the functions is highly serial, only using 1 execution core ++// at a time. The second input's scans has some dependency on the first ones finishing it, but ++// they can make a lot of progress before they need that information. ++// 3. Step 1 doesn't use enough capacity, so we run some extra stuff while we're waiting for that ++// to finish: utf-8 checks and generating the output from the last iteration. ++// ++// The reason we run 2 inputs at a time, is steps 2 and 3 are *still* not enough to soak up all ++// available capacity with just one input. Running 2 at a time seems to give the CPU a good enough ++// workout. ++// ++template ++error_code json_structural_indexer::index(const uint8_t *buf, size_t len, dom_parser_implementation &parser, stage1_mode partial) noexcept { ++ if (simdjson_unlikely(len > parser.capacity())) { return CAPACITY; } ++ // We guard the rest of the code so that we can assume that len > 0 throughout. ++ if (len == 0) { return EMPTY; } ++ if (is_streaming(partial)) { ++ len = trim_partial_utf8(buf, len); ++ // If you end up with an empty window after trimming ++ // the partial UTF-8 bytes, then chances are good that you ++ // have an UTF-8 formatting error. ++ if(len == 0) { return UTF8_ERROR; } ++ } ++ buf_block_reader reader(buf, len); ++ json_structural_indexer indexer(parser.structural_indexes.get()); ++ ++ // Read all but the last block ++ while (reader.has_full_block()) { ++ indexer.step(reader.full_block(), reader); ++ } ++ // Take care of the last block (will always be there unless file is empty which is ++ // not supposed to happen.) ++ uint8_t block[STEP_SIZE]; ++ if (simdjson_unlikely(reader.get_remainder(block) == 0)) { return UNEXPECTED_ERROR; } ++ indexer.step(block, reader); ++ return indexer.finish(parser, reader.block_index(), len, partial); ++} ++ ++template<> ++simdjson_inline void json_structural_indexer::step<128>(const uint8_t *block, buf_block_reader<128> &reader) noexcept { ++ simd::simd8x64 in_1(block); ++ simd::simd8x64 in_2(block+64); ++ json_block block_1 = scanner.next(in_1); ++ json_block block_2 = scanner.next(in_2); ++ this->next(in_1, block_1, reader.block_index()); ++ this->next(in_2, block_2, reader.block_index()+64); ++ reader.advance(); ++} ++ ++template<> ++simdjson_inline void json_structural_indexer::step<64>(const uint8_t *block, buf_block_reader<64> &reader) noexcept { ++ simd::simd8x64 in_1(block); ++ json_block block_1 = scanner.next(in_1); ++ this->next(in_1, block_1, reader.block_index()); ++ reader.advance(); ++} ++ ++simdjson_inline void json_structural_indexer::next(const simd::simd8x64& in, const json_block& block, size_t idx) { ++ uint64_t unescaped = in.lteq(0x1F); ++ checker.check_next_input(in); ++ indexer.write(uint32_t(idx-64), prev_structurals); // Output *last* iteration's structurals to the parser ++ prev_structurals = block.structural_start(); ++ unescaped_chars_error |= block.non_quote_inside_string(unescaped); ++} ++ ++simdjson_inline error_code json_structural_indexer::finish(dom_parser_implementation &parser, size_t idx, size_t len, stage1_mode partial) { ++ // Write out the final iteration's structurals ++ indexer.write(uint32_t(idx-64), prev_structurals); ++ error_code error = scanner.finish(); ++ // We deliberately break down the next expression so that it is ++ // human readable. ++ const bool should_we_exit = is_streaming(partial) ? ++ ((error != SUCCESS) && (error != UNCLOSED_STRING)) // when partial we tolerate UNCLOSED_STRING ++ : (error != SUCCESS); // if partial is false, we must have SUCCESS ++ const bool have_unclosed_string = (error == UNCLOSED_STRING); ++ if (simdjson_unlikely(should_we_exit)) { return error; } ++ ++ if (unescaped_chars_error) { ++ return UNESCAPED_CHARS; ++ } ++ parser.n_structural_indexes = uint32_t(indexer.tail - parser.structural_indexes.get()); ++ /*** ++ * The On Demand API requires special padding. ++ * ++ * This is related to https://github.com/simdjson/simdjson/issues/906 ++ * Basically, we want to make sure that if the parsing continues beyond the last (valid) ++ * structural character, it quickly stops. ++ * Only three structural characters can be repeated without triggering an error in JSON: [,] and }. ++ * We repeat the padding character (at 'len'). We don't know what it is, but if the parsing ++ * continues, then it must be [,] or }. ++ * Suppose it is ] or }. We backtrack to the first character, what could it be that would ++ * not trigger an error? It could be ] or } but no, because you can't start a document that way. ++ * It can't be a comma, a colon or any simple value. So the only way we could continue is ++ * if the repeated character is [. But if so, the document must start with [. But if the document ++ * starts with [, it should end with ]. If we enforce that rule, then we would get ++ * ][[ which is invalid. ++ * ++ * This is illustrated with the test array_iterate_unclosed_error() on the following input: ++ * R"({ "a": [,,)" ++ **/ ++ parser.structural_indexes[parser.n_structural_indexes] = uint32_t(len); // used later in partial == stage1_mode::streaming_final ++ parser.structural_indexes[parser.n_structural_indexes + 1] = uint32_t(len); ++ parser.structural_indexes[parser.n_structural_indexes + 2] = 0; ++ parser.next_structural_index = 0; ++ // a valid JSON file cannot have zero structural indexes - we should have found something ++ if (simdjson_unlikely(parser.n_structural_indexes == 0u)) { ++ return EMPTY; ++ } ++ if (simdjson_unlikely(parser.structural_indexes[parser.n_structural_indexes - 1] > len)) { ++ return UNEXPECTED_ERROR; ++ } ++ if (partial == stage1_mode::streaming_partial) { ++ // If we have an unclosed string, then the last structural ++ // will be the quote and we want to make sure to omit it. ++ if(have_unclosed_string) { ++ parser.n_structural_indexes--; ++ // a valid JSON file cannot have zero structural indexes - we should have found something ++ if (simdjson_unlikely(parser.n_structural_indexes == 0u)) { return CAPACITY; } ++ } ++ // We truncate the input to the end of the last complete document (or zero). ++ auto new_structural_indexes = find_next_document_index(parser); ++ if (new_structural_indexes == 0 && parser.n_structural_indexes > 0) { ++ if(parser.structural_indexes[0] == 0) { ++ // If the buffer is partial and we started at index 0 but the document is ++ // incomplete, it's too big to parse. ++ return CAPACITY; ++ } else { ++ // It is possible that the document could be parsed, we just had a lot ++ // of white space. ++ parser.n_structural_indexes = 0; ++ return EMPTY; ++ } ++ } ++ ++ parser.n_structural_indexes = new_structural_indexes; ++ } else if (partial == stage1_mode::streaming_final) { ++ if(have_unclosed_string) { parser.n_structural_indexes--; } ++ // We truncate the input to the end of the last complete document (or zero). ++ // Because partial == stage1_mode::streaming_final, it means that we may ++ // silently ignore trailing garbage. Though it sounds bad, we do it ++ // deliberately because many people who have streams of JSON documents ++ // will truncate them for processing. E.g., imagine that you are uncompressing ++ // the data from a size file or receiving it in chunks from the network. You ++ // may not know where exactly the last document will be. Meanwhile the ++ // document_stream instances allow people to know the JSON documents they are ++ // parsing (see the iterator.source() method). ++ parser.n_structural_indexes = find_next_document_index(parser); ++ // We store the initial n_structural_indexes so that the client can see ++ // whether we used truncation. If initial_n_structural_indexes == parser.n_structural_indexes, ++ // then this will query parser.structural_indexes[parser.n_structural_indexes] which is len, ++ // otherwise, it will copy some prior index. ++ parser.structural_indexes[parser.n_structural_indexes + 1] = parser.structural_indexes[parser.n_structural_indexes]; ++ // This next line is critical, do not change it unless you understand what you are ++ // doing. ++ parser.structural_indexes[parser.n_structural_indexes] = uint32_t(len); ++ if (simdjson_unlikely(parser.n_structural_indexes == 0u)) { ++ // We tolerate an unclosed string at the very end of the stream. Indeed, users ++ // often load their data in bulk without being careful and they want us to ignore ++ // the trailing garbage. ++ return EMPTY; ++ } ++ } ++ checker.check_eof(); ++ return checker.errors(); ++} ++ ++} // namespace stage1 ++} // unnamed namespace ++} // namespace ppc64 ++} // namespace simdjson ++/* end file src/generic/stage1/json_structural_indexer.h */ ++/* begin file src/generic/stage1/utf8_validator.h */ ++namespace simdjson { ++namespace ppc64 { ++namespace { ++namespace stage1 { ++ ++/** ++ * Validates that the string is actual UTF-8. ++ */ ++template ++bool generic_validate_utf8(const uint8_t * input, size_t length) { ++ checker c{}; ++ buf_block_reader<64> reader(input, length); ++ while (reader.has_full_block()) { ++ simd::simd8x64 in(reader.full_block()); ++ c.check_next_input(in); ++ reader.advance(); ++ } ++ uint8_t block[64]{}; ++ reader.get_remainder(block); ++ simd::simd8x64 in(block); ++ c.check_next_input(in); ++ reader.advance(); ++ c.check_eof(); ++ return c.errors() == error_code::SUCCESS; ++} ++ ++bool generic_validate_utf8(const char * input, size_t length) { ++ return generic_validate_utf8(reinterpret_cast(input),length); ++} ++ ++} // namespace stage1 ++} // unnamed namespace ++} // namespace ppc64 ++} // namespace simdjson ++/* end file src/generic/stage1/utf8_validator.h */ ++ ++// ++// Stage 2 ++// ++/* begin file src/generic/stage2/stringparsing.h */ ++// This file contains the common code every implementation uses ++// It is intended to be included multiple times and compiled multiple times ++ ++namespace simdjson { ++namespace ppc64 { ++namespace { ++/// @private ++namespace stringparsing { ++ ++// begin copypasta ++// These chars yield themselves: " \ / ++// b -> backspace, f -> formfeed, n -> newline, r -> cr, t -> horizontal tab ++// u not handled in this table as it's complex ++static const uint8_t escape_map[256] = { ++ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x0. ++ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ++ 0, 0, 0x22, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x2f, ++ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ++ ++ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x4. ++ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x5c, 0, 0, 0, // 0x5. ++ 0, 0, 0x08, 0, 0, 0, 0x0c, 0, 0, 0, 0, 0, 0, 0, 0x0a, 0, // 0x6. ++ 0, 0, 0x0d, 0, 0x09, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x7. ++ ++ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ++ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ++ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ++ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ++ ++ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ++ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ++ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ++ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ++}; ++ ++// handle a unicode codepoint ++// write appropriate values into dest ++// src will advance 6 bytes or 12 bytes ++// dest will advance a variable amount (return via pointer) ++// return true if the unicode codepoint was valid ++// We work in little-endian then swap at write time ++simdjson_warn_unused ++simdjson_inline bool handle_unicode_codepoint(const uint8_t **src_ptr, ++ uint8_t **dst_ptr) { ++ // jsoncharutils::hex_to_u32_nocheck fills high 16 bits of the return value with 1s if the ++ // conversion isn't valid; we defer the check for this to inside the ++ // multilingual plane check ++ uint32_t code_point = jsoncharutils::hex_to_u32_nocheck(*src_ptr + 2); ++ *src_ptr += 6; ++ ++ // If we found a high surrogate, we must ++ // check for low surrogate for characters ++ // outside the Basic ++ // Multilingual Plane. ++ if (code_point >= 0xd800 && code_point < 0xdc00) { ++ const uint8_t *src_data = *src_ptr; ++ /* Compiler optimizations convert this to a single 16-bit load and compare on most platforms */ ++ if (((src_data[0] << 8) | src_data[1]) != ((static_cast ('\\') << 8) | static_cast ('u'))) { ++ return false; ++ } ++ uint32_t code_point_2 = jsoncharutils::hex_to_u32_nocheck(src_data + 2); ++ ++ // We have already checked that the high surrogate is valid and ++ // (code_point - 0xd800) < 1024. ++ // ++ // Check that code_point_2 is in the range 0xdc00..0xdfff ++ // and that code_point_2 was parsed from valid hex. ++ uint32_t low_bit = code_point_2 - 0xdc00; ++ if (low_bit >> 10) { ++ return false; ++ } ++ ++ code_point = ++ (((code_point - 0xd800) << 10) | low_bit) + 0x10000; ++ *src_ptr += 6; ++ } else if (code_point >= 0xdc00 && code_point <= 0xdfff) { ++ // If we encounter a low surrogate (not preceded by a high surrogate) ++ // then we have an error. ++ return false; ++ } ++ size_t offset = jsoncharutils::codepoint_to_utf8(code_point, *dst_ptr); ++ *dst_ptr += offset; ++ return offset > 0; ++} ++ ++/** ++ * Unescape a valid UTF-8 string from src to dst, stopping at a final unescaped quote. There ++ * must be an unescaped quote terminating the string. It returns the final output ++ * position as pointer. In case of error (e.g., the string has bad escaped codes), ++ * then null_nullptrptr is returned. It is assumed that the output buffer is large ++ * enough. E.g., if src points at 'joe"', then dst needs to have four free bytes + ++ * SIMDJSON_PADDING bytes. ++ */ ++simdjson_warn_unused simdjson_inline uint8_t *parse_string(const uint8_t *src, uint8_t *dst) { ++ while (1) { ++ // Copy the next n bytes, and find the backslash and quote in them. ++ auto bs_quote = backslash_and_quote::copy_and_find(src, dst); ++ // If the next thing is the end quote, copy and return ++ if (bs_quote.has_quote_first()) { ++ // we encountered quotes first. Move dst to point to quotes and exit ++ return dst + bs_quote.quote_index(); ++ } ++ if (bs_quote.has_backslash()) { ++ /* find out where the backspace is */ ++ auto bs_dist = bs_quote.backslash_index(); ++ uint8_t escape_char = src[bs_dist + 1]; ++ /* we encountered backslash first. Handle backslash */ ++ if (escape_char == 'u') { ++ /* move src/dst up to the start; they will be further adjusted ++ within the unicode codepoint handling code. */ ++ src += bs_dist; ++ dst += bs_dist; ++ if (!handle_unicode_codepoint(&src, &dst)) { ++ return nullptr; ++ } ++ } else { ++ /* simple 1:1 conversion. Will eat bs_dist+2 characters in input and ++ * write bs_dist+1 characters to output ++ * note this may reach beyond the part of the buffer we've actually ++ * seen. I think this is ok */ ++ uint8_t escape_result = escape_map[escape_char]; ++ if (escape_result == 0u) { ++ return nullptr; /* bogus escape value is an error */ ++ } ++ dst[bs_dist] = escape_result; ++ src += bs_dist + 2; ++ dst += bs_dist + 1; ++ } ++ } else { ++ /* they are the same. Since they can't co-occur, it means we ++ * encountered neither. */ ++ src += backslash_and_quote::BYTES_PROCESSED; ++ dst += backslash_and_quote::BYTES_PROCESSED; ++ } ++ } ++ /* can't be reached */ ++ return nullptr; ++} ++ ++} // namespace stringparsing ++} // unnamed namespace ++} // namespace ppc64 ++} // namespace simdjson ++/* end file src/generic/stage2/stringparsing.h */ ++/* begin file src/generic/stage2/tape_builder.h */ ++/* begin file src/generic/stage2/json_iterator.h */ ++/* begin file src/generic/stage2/logger.h */ ++// This is for an internal-only stage 2 specific logger. ++// Set LOG_ENABLED = true to log what stage 2 is doing! ++namespace simdjson { ++namespace ppc64 { ++namespace { ++namespace logger { ++ ++ static constexpr const char * DASHES = "----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------"; ++ ++#if SIMDJSON_VERBOSE_LOGGING ++ static constexpr const bool LOG_ENABLED = true; ++#else ++ static constexpr const bool LOG_ENABLED = false; ++#endif ++ static constexpr const int LOG_EVENT_LEN = 20; ++ static constexpr const int LOG_BUFFER_LEN = 30; ++ static constexpr const int LOG_SMALL_BUFFER_LEN = 10; ++ static constexpr const int LOG_INDEX_LEN = 5; ++ ++ static int log_depth; // Not threadsafe. Log only. ++ ++ // Helper to turn unprintable or newline characters into spaces ++ static simdjson_inline char printable_char(char c) { ++ if (c >= 0x20) { ++ return c; ++ } else { ++ return ' '; ++ } ++ } ++ ++ // Print the header and set up log_start ++ static simdjson_inline void log_start() { ++ if (LOG_ENABLED) { ++ log_depth = 0; ++ printf("\n"); ++ printf("| %-*s | %-*s | %-*s | %-*s | Detail |\n", LOG_EVENT_LEN, "Event", LOG_BUFFER_LEN, "Buffer", LOG_SMALL_BUFFER_LEN, "Next", 5, "Next#"); ++ printf("|%.*s|%.*s|%.*s|%.*s|--------|\n", LOG_EVENT_LEN+2, DASHES, LOG_BUFFER_LEN+2, DASHES, LOG_SMALL_BUFFER_LEN+2, DASHES, 5+2, DASHES); ++ } ++ } ++ ++ simdjson_unused static simdjson_inline void log_string(const char *message) { ++ if (LOG_ENABLED) { ++ printf("%s\n", message); ++ } ++ } ++ ++ // Logs a single line from the stage 2 DOM parser ++ template ++ static simdjson_inline void log_line(S &structurals, const char *title_prefix, const char *title, const char *detail) { ++ if (LOG_ENABLED) { ++ printf("| %*s%s%-*s ", log_depth*2, "", title_prefix, LOG_EVENT_LEN - log_depth*2 - int(strlen(title_prefix)), title); ++ auto current_index = structurals.at_beginning() ? nullptr : structurals.next_structural-1; ++ auto next_index = structurals.next_structural; ++ auto current = current_index ? &structurals.buf[*current_index] : reinterpret_cast(" "); ++ auto next = &structurals.buf[*next_index]; ++ { ++ // Print the next N characters in the buffer. ++ printf("| "); ++ // Otherwise, print the characters starting from the buffer position. ++ // Print spaces for unprintable or newline characters. ++ for (int i=0;i ++ simdjson_warn_unused simdjson_inline error_code walk_document(V &visitor) noexcept; ++ ++ /** ++ * Create an iterator capable of walking a JSON document. ++ * ++ * The document must have already passed through stage 1. ++ */ ++ simdjson_inline json_iterator(dom_parser_implementation &_dom_parser, size_t start_structural_index); ++ ++ /** ++ * Look at the next token. ++ * ++ * Tokens can be strings, numbers, booleans, null, or operators (`[{]},:`)). ++ * ++ * They may include invalid JSON as well (such as `1.2.3` or `ture`). ++ */ ++ simdjson_inline const uint8_t *peek() const noexcept; ++ /** ++ * Advance to the next token. ++ * ++ * Tokens can be strings, numbers, booleans, null, or operators (`[{]},:`)). ++ * ++ * They may include invalid JSON as well (such as `1.2.3` or `ture`). ++ */ ++ simdjson_inline const uint8_t *advance() noexcept; ++ /** ++ * Get the remaining length of the document, from the start of the current token. ++ */ ++ simdjson_inline size_t remaining_len() const noexcept; ++ /** ++ * Check if we are at the end of the document. ++ * ++ * If this is true, there are no more tokens. ++ */ ++ simdjson_inline bool at_eof() const noexcept; ++ /** ++ * Check if we are at the beginning of the document. ++ */ ++ simdjson_inline bool at_beginning() const noexcept; ++ simdjson_inline uint8_t last_structural() const noexcept; ++ ++ /** ++ * Log that a value has been found. ++ * ++ * Set LOG_ENABLED=true in logger.h to see logging. ++ */ ++ simdjson_inline void log_value(const char *type) const noexcept; ++ /** ++ * Log the start of a multipart value. ++ * ++ * Set LOG_ENABLED=true in logger.h to see logging. ++ */ ++ simdjson_inline void log_start_value(const char *type) const noexcept; ++ /** ++ * Log the end of a multipart value. ++ * ++ * Set LOG_ENABLED=true in logger.h to see logging. ++ */ ++ simdjson_inline void log_end_value(const char *type) const noexcept; ++ /** ++ * Log an error. ++ * ++ * Set LOG_ENABLED=true in logger.h to see logging. ++ */ ++ simdjson_inline void log_error(const char *error) const noexcept; ++ ++ template ++ simdjson_warn_unused simdjson_inline error_code visit_root_primitive(V &visitor, const uint8_t *value) noexcept; ++ template ++ simdjson_warn_unused simdjson_inline error_code visit_primitive(V &visitor, const uint8_t *value) noexcept; ++}; ++ ++template ++simdjson_warn_unused simdjson_inline error_code json_iterator::walk_document(V &visitor) noexcept { ++ logger::log_start(); ++ ++ // ++ // Start the document ++ // ++ if (at_eof()) { return EMPTY; } ++ log_start_value("document"); ++ SIMDJSON_TRY( visitor.visit_document_start(*this) ); ++ ++ // ++ // Read first value ++ // ++ { ++ auto value = advance(); ++ ++ // Make sure the outer object or array is closed before continuing; otherwise, there are ways we ++ // could get into memory corruption. See https://github.com/simdjson/simdjson/issues/906 ++ if (!STREAMING) { ++ switch (*value) { ++ case '{': if (last_structural() != '}') { log_value("starting brace unmatched"); return TAPE_ERROR; }; break; ++ case '[': if (last_structural() != ']') { log_value("starting bracket unmatched"); return TAPE_ERROR; }; break; ++ } ++ } ++ ++ switch (*value) { ++ case '{': if (*peek() == '}') { advance(); log_value("empty object"); SIMDJSON_TRY( visitor.visit_empty_object(*this) ); break; } goto object_begin; ++ case '[': if (*peek() == ']') { advance(); log_value("empty array"); SIMDJSON_TRY( visitor.visit_empty_array(*this) ); break; } goto array_begin; ++ default: SIMDJSON_TRY( visitor.visit_root_primitive(*this, value) ); break; ++ } ++ } ++ goto document_end; ++ ++// ++// Object parser states ++// ++object_begin: ++ log_start_value("object"); ++ depth++; ++ if (depth >= dom_parser.max_depth()) { log_error("Exceeded max depth!"); return DEPTH_ERROR; } ++ dom_parser.is_array[depth] = false; ++ SIMDJSON_TRY( visitor.visit_object_start(*this) ); ++ ++ { ++ auto key = advance(); ++ if (*key != '"') { log_error("Object does not start with a key"); return TAPE_ERROR; } ++ SIMDJSON_TRY( visitor.increment_count(*this) ); ++ SIMDJSON_TRY( visitor.visit_key(*this, key) ); ++ } ++ ++object_field: ++ if (simdjson_unlikely( *advance() != ':' )) { log_error("Missing colon after key in object"); return TAPE_ERROR; } ++ { ++ auto value = advance(); ++ switch (*value) { ++ case '{': if (*peek() == '}') { advance(); log_value("empty object"); SIMDJSON_TRY( visitor.visit_empty_object(*this) ); break; } goto object_begin; ++ case '[': if (*peek() == ']') { advance(); log_value("empty array"); SIMDJSON_TRY( visitor.visit_empty_array(*this) ); break; } goto array_begin; ++ default: SIMDJSON_TRY( visitor.visit_primitive(*this, value) ); break; ++ } ++ } ++ ++object_continue: ++ switch (*advance()) { ++ case ',': ++ SIMDJSON_TRY( visitor.increment_count(*this) ); ++ { ++ auto key = advance(); ++ if (simdjson_unlikely( *key != '"' )) { log_error("Key string missing at beginning of field in object"); return TAPE_ERROR; } ++ SIMDJSON_TRY( visitor.visit_key(*this, key) ); ++ } ++ goto object_field; ++ case '}': log_end_value("object"); SIMDJSON_TRY( visitor.visit_object_end(*this) ); goto scope_end; ++ default: log_error("No comma between object fields"); return TAPE_ERROR; ++ } ++ ++scope_end: ++ depth--; ++ if (depth == 0) { goto document_end; } ++ if (dom_parser.is_array[depth]) { goto array_continue; } ++ goto object_continue; ++ ++// ++// Array parser states ++// ++array_begin: ++ log_start_value("array"); ++ depth++; ++ if (depth >= dom_parser.max_depth()) { log_error("Exceeded max depth!"); return DEPTH_ERROR; } ++ dom_parser.is_array[depth] = true; ++ SIMDJSON_TRY( visitor.visit_array_start(*this) ); ++ SIMDJSON_TRY( visitor.increment_count(*this) ); ++ ++array_value: ++ { ++ auto value = advance(); ++ switch (*value) { ++ case '{': if (*peek() == '}') { advance(); log_value("empty object"); SIMDJSON_TRY( visitor.visit_empty_object(*this) ); break; } goto object_begin; ++ case '[': if (*peek() == ']') { advance(); log_value("empty array"); SIMDJSON_TRY( visitor.visit_empty_array(*this) ); break; } goto array_begin; ++ default: SIMDJSON_TRY( visitor.visit_primitive(*this, value) ); break; ++ } ++ } ++ ++array_continue: ++ switch (*advance()) { ++ case ',': SIMDJSON_TRY( visitor.increment_count(*this) ); goto array_value; ++ case ']': log_end_value("array"); SIMDJSON_TRY( visitor.visit_array_end(*this) ); goto scope_end; ++ default: log_error("Missing comma between array values"); return TAPE_ERROR; ++ } ++ ++document_end: ++ log_end_value("document"); ++ SIMDJSON_TRY( visitor.visit_document_end(*this) ); ++ ++ dom_parser.next_structural_index = uint32_t(next_structural - &dom_parser.structural_indexes[0]); ++ ++ // If we didn't make it to the end, it's an error ++ if ( !STREAMING && dom_parser.next_structural_index != dom_parser.n_structural_indexes ) { ++ log_error("More than one JSON value at the root of the document, or extra characters at the end of the JSON!"); ++ return TAPE_ERROR; ++ } ++ ++ return SUCCESS; ++ ++} // walk_document() ++ ++simdjson_inline json_iterator::json_iterator(dom_parser_implementation &_dom_parser, size_t start_structural_index) ++ : buf{_dom_parser.buf}, ++ next_structural{&_dom_parser.structural_indexes[start_structural_index]}, ++ dom_parser{_dom_parser} { ++} ++ ++simdjson_inline const uint8_t *json_iterator::peek() const noexcept { ++ return &buf[*(next_structural)]; ++} ++simdjson_inline const uint8_t *json_iterator::advance() noexcept { ++ return &buf[*(next_structural++)]; ++} ++simdjson_inline size_t json_iterator::remaining_len() const noexcept { ++ return dom_parser.len - *(next_structural-1); ++} ++ ++simdjson_inline bool json_iterator::at_eof() const noexcept { ++ return next_structural == &dom_parser.structural_indexes[dom_parser.n_structural_indexes]; ++} ++simdjson_inline bool json_iterator::at_beginning() const noexcept { ++ return next_structural == dom_parser.structural_indexes.get(); ++} ++simdjson_inline uint8_t json_iterator::last_structural() const noexcept { ++ return buf[dom_parser.structural_indexes[dom_parser.n_structural_indexes - 1]]; ++} ++ ++simdjson_inline void json_iterator::log_value(const char *type) const noexcept { ++ logger::log_line(*this, "", type, ""); ++} ++ ++simdjson_inline void json_iterator::log_start_value(const char *type) const noexcept { ++ logger::log_line(*this, "+", type, ""); ++ if (logger::LOG_ENABLED) { logger::log_depth++; } ++} ++ ++simdjson_inline void json_iterator::log_end_value(const char *type) const noexcept { ++ if (logger::LOG_ENABLED) { logger::log_depth--; } ++ logger::log_line(*this, "-", type, ""); ++} ++ ++simdjson_inline void json_iterator::log_error(const char *error) const noexcept { ++ logger::log_line(*this, "", "ERROR", error); ++} ++ ++template ++simdjson_warn_unused simdjson_inline error_code json_iterator::visit_root_primitive(V &visitor, const uint8_t *value) noexcept { ++ switch (*value) { ++ case '"': return visitor.visit_root_string(*this, value); ++ case 't': return visitor.visit_root_true_atom(*this, value); ++ case 'f': return visitor.visit_root_false_atom(*this, value); ++ case 'n': return visitor.visit_root_null_atom(*this, value); ++ case '-': ++ case '0': case '1': case '2': case '3': case '4': ++ case '5': case '6': case '7': case '8': case '9': ++ return visitor.visit_root_number(*this, value); ++ default: ++ log_error("Document starts with a non-value character"); ++ return TAPE_ERROR; ++ } ++} ++template ++simdjson_warn_unused simdjson_inline error_code json_iterator::visit_primitive(V &visitor, const uint8_t *value) noexcept { ++ switch (*value) { ++ case '"': return visitor.visit_string(*this, value); ++ case 't': return visitor.visit_true_atom(*this, value); ++ case 'f': return visitor.visit_false_atom(*this, value); ++ case 'n': return visitor.visit_null_atom(*this, value); ++ case '-': ++ case '0': case '1': case '2': case '3': case '4': ++ case '5': case '6': case '7': case '8': case '9': ++ return visitor.visit_number(*this, value); ++ default: ++ log_error("Non-value found when value was expected!"); ++ return TAPE_ERROR; ++ } ++} ++ ++} // namespace stage2 ++} // unnamed namespace ++} // namespace ppc64 ++} // namespace simdjson ++/* end file src/generic/stage2/json_iterator.h */ ++/* begin file src/generic/stage2/tape_writer.h */ ++namespace simdjson { ++namespace ppc64 { ++namespace { ++namespace stage2 { ++ ++struct tape_writer { ++ /** The next place to write to tape */ ++ uint64_t *next_tape_loc; ++ ++ /** Write a signed 64-bit value to tape. */ ++ simdjson_inline void append_s64(int64_t value) noexcept; ++ ++ /** Write an unsigned 64-bit value to tape. */ ++ simdjson_inline void append_u64(uint64_t value) noexcept; ++ ++ /** Write a double value to tape. */ ++ simdjson_inline void append_double(double value) noexcept; ++ ++ /** ++ * Append a tape entry (an 8-bit type,and 56 bits worth of value). ++ */ ++ simdjson_inline void append(uint64_t val, internal::tape_type t) noexcept; ++ ++ /** ++ * Skip the current tape entry without writing. ++ * ++ * Used to skip the start of the container, since we'll come back later to fill it in when the ++ * container ends. ++ */ ++ simdjson_inline void skip() noexcept; ++ ++ /** ++ * Skip the number of tape entries necessary to write a large u64 or i64. ++ */ ++ simdjson_inline void skip_large_integer() noexcept; ++ ++ /** ++ * Skip the number of tape entries necessary to write a double. ++ */ ++ simdjson_inline void skip_double() noexcept; ++ ++ /** ++ * Write a value to a known location on tape. ++ * ++ * Used to go back and write out the start of a container after the container ends. ++ */ ++ simdjson_inline static void write(uint64_t &tape_loc, uint64_t val, internal::tape_type t) noexcept; ++ ++private: ++ /** ++ * Append both the tape entry, and a supplementary value following it. Used for types that need ++ * all 64 bits, such as double and uint64_t. ++ */ ++ template ++ simdjson_inline void append2(uint64_t val, T val2, internal::tape_type t) noexcept; ++}; // struct number_writer ++ ++simdjson_inline void tape_writer::append_s64(int64_t value) noexcept { ++ append2(0, value, internal::tape_type::INT64); ++} ++ ++simdjson_inline void tape_writer::append_u64(uint64_t value) noexcept { ++ append(0, internal::tape_type::UINT64); ++ *next_tape_loc = value; ++ next_tape_loc++; ++} ++ ++/** Write a double value to tape. */ ++simdjson_inline void tape_writer::append_double(double value) noexcept { ++ append2(0, value, internal::tape_type::DOUBLE); ++} ++ ++simdjson_inline void tape_writer::skip() noexcept { ++ next_tape_loc++; ++} ++ ++simdjson_inline void tape_writer::skip_large_integer() noexcept { ++ next_tape_loc += 2; ++} ++ ++simdjson_inline void tape_writer::skip_double() noexcept { ++ next_tape_loc += 2; ++} ++ ++simdjson_inline void tape_writer::append(uint64_t val, internal::tape_type t) noexcept { ++ *next_tape_loc = val | ((uint64_t(char(t))) << 56); ++ next_tape_loc++; ++} ++ ++template ++simdjson_inline void tape_writer::append2(uint64_t val, T val2, internal::tape_type t) noexcept { ++ append(val, t); ++ static_assert(sizeof(val2) == sizeof(*next_tape_loc), "Type is not 64 bits!"); ++ memcpy(next_tape_loc, &val2, sizeof(val2)); ++ next_tape_loc++; ++} ++ ++simdjson_inline void tape_writer::write(uint64_t &tape_loc, uint64_t val, internal::tape_type t) noexcept { ++ tape_loc = val | ((uint64_t(char(t))) << 56); ++} ++ ++} // namespace stage2 ++} // unnamed namespace ++} // namespace ppc64 ++} // namespace simdjson ++/* end file src/generic/stage2/tape_writer.h */ ++ ++namespace simdjson { ++namespace ppc64 { ++namespace { ++namespace stage2 { ++ ++struct tape_builder { ++ template ++ simdjson_warn_unused static simdjson_inline error_code parse_document( ++ dom_parser_implementation &dom_parser, ++ dom::document &doc) noexcept; ++ ++ /** Called when a non-empty document starts. */ ++ simdjson_warn_unused simdjson_inline error_code visit_document_start(json_iterator &iter) noexcept; ++ /** Called when a non-empty document ends without error. */ ++ simdjson_warn_unused simdjson_inline error_code visit_document_end(json_iterator &iter) noexcept; ++ ++ /** Called when a non-empty array starts. */ ++ simdjson_warn_unused simdjson_inline error_code visit_array_start(json_iterator &iter) noexcept; ++ /** Called when a non-empty array ends. */ ++ simdjson_warn_unused simdjson_inline error_code visit_array_end(json_iterator &iter) noexcept; ++ /** Called when an empty array is found. */ ++ simdjson_warn_unused simdjson_inline error_code visit_empty_array(json_iterator &iter) noexcept; ++ ++ /** Called when a non-empty object starts. */ ++ simdjson_warn_unused simdjson_inline error_code visit_object_start(json_iterator &iter) noexcept; ++ /** ++ * Called when a key in a field is encountered. ++ * ++ * primitive, visit_object_start, visit_empty_object, visit_array_start, or visit_empty_array ++ * will be called after this with the field value. ++ */ ++ simdjson_warn_unused simdjson_inline error_code visit_key(json_iterator &iter, const uint8_t *key) noexcept; ++ /** Called when a non-empty object ends. */ ++ simdjson_warn_unused simdjson_inline error_code visit_object_end(json_iterator &iter) noexcept; ++ /** Called when an empty object is found. */ ++ simdjson_warn_unused simdjson_inline error_code visit_empty_object(json_iterator &iter) noexcept; ++ ++ /** ++ * Called when a string, number, boolean or null is found. ++ */ ++ simdjson_warn_unused simdjson_inline error_code visit_primitive(json_iterator &iter, const uint8_t *value) noexcept; ++ /** ++ * Called when a string, number, boolean or null is found at the top level of a document (i.e. ++ * when there is no array or object and the entire document is a single string, number, boolean or ++ * null. ++ * ++ * This is separate from primitive() because simdjson's normal primitive parsing routines assume ++ * there is at least one more token after the value, which is only true in an array or object. ++ */ ++ simdjson_warn_unused simdjson_inline error_code visit_root_primitive(json_iterator &iter, const uint8_t *value) noexcept; ++ ++ simdjson_warn_unused simdjson_inline error_code visit_string(json_iterator &iter, const uint8_t *value, bool key = false) noexcept; ++ simdjson_warn_unused simdjson_inline error_code visit_number(json_iterator &iter, const uint8_t *value) noexcept; ++ simdjson_warn_unused simdjson_inline error_code visit_true_atom(json_iterator &iter, const uint8_t *value) noexcept; ++ simdjson_warn_unused simdjson_inline error_code visit_false_atom(json_iterator &iter, const uint8_t *value) noexcept; ++ simdjson_warn_unused simdjson_inline error_code visit_null_atom(json_iterator &iter, const uint8_t *value) noexcept; ++ ++ simdjson_warn_unused simdjson_inline error_code visit_root_string(json_iterator &iter, const uint8_t *value) noexcept; ++ simdjson_warn_unused simdjson_inline error_code visit_root_number(json_iterator &iter, const uint8_t *value) noexcept; ++ simdjson_warn_unused simdjson_inline error_code visit_root_true_atom(json_iterator &iter, const uint8_t *value) noexcept; ++ simdjson_warn_unused simdjson_inline error_code visit_root_false_atom(json_iterator &iter, const uint8_t *value) noexcept; ++ simdjson_warn_unused simdjson_inline error_code visit_root_null_atom(json_iterator &iter, const uint8_t *value) noexcept; ++ ++ /** Called each time a new field or element in an array or object is found. */ ++ simdjson_warn_unused simdjson_inline error_code increment_count(json_iterator &iter) noexcept; ++ ++ /** Next location to write to tape */ ++ tape_writer tape; ++private: ++ /** Next write location in the string buf for stage 2 parsing */ ++ uint8_t *current_string_buf_loc; ++ ++ simdjson_inline tape_builder(dom::document &doc) noexcept; ++ ++ simdjson_inline uint32_t next_tape_index(json_iterator &iter) const noexcept; ++ simdjson_inline void start_container(json_iterator &iter) noexcept; ++ simdjson_warn_unused simdjson_inline error_code end_container(json_iterator &iter, internal::tape_type start, internal::tape_type end) noexcept; ++ simdjson_warn_unused simdjson_inline error_code empty_container(json_iterator &iter, internal::tape_type start, internal::tape_type end) noexcept; ++ simdjson_inline uint8_t *on_start_string(json_iterator &iter) noexcept; ++ simdjson_inline void on_end_string(uint8_t *dst) noexcept; ++}; // class tape_builder ++ ++template ++simdjson_warn_unused simdjson_inline error_code tape_builder::parse_document( ++ dom_parser_implementation &dom_parser, ++ dom::document &doc) noexcept { ++ dom_parser.doc = &doc; ++ json_iterator iter(dom_parser, STREAMING ? dom_parser.next_structural_index : 0); ++ tape_builder builder(doc); ++ return iter.walk_document(builder); ++} ++ ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_root_primitive(json_iterator &iter, const uint8_t *value) noexcept { ++ return iter.visit_root_primitive(*this, value); ++} ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_primitive(json_iterator &iter, const uint8_t *value) noexcept { ++ return iter.visit_primitive(*this, value); ++} ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_empty_object(json_iterator &iter) noexcept { ++ return empty_container(iter, internal::tape_type::START_OBJECT, internal::tape_type::END_OBJECT); ++} ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_empty_array(json_iterator &iter) noexcept { ++ return empty_container(iter, internal::tape_type::START_ARRAY, internal::tape_type::END_ARRAY); ++} ++ ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_document_start(json_iterator &iter) noexcept { ++ start_container(iter); ++ return SUCCESS; ++} ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_object_start(json_iterator &iter) noexcept { ++ start_container(iter); ++ return SUCCESS; ++} ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_array_start(json_iterator &iter) noexcept { ++ start_container(iter); ++ return SUCCESS; ++} ++ ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_object_end(json_iterator &iter) noexcept { ++ return end_container(iter, internal::tape_type::START_OBJECT, internal::tape_type::END_OBJECT); ++} ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_array_end(json_iterator &iter) noexcept { ++ return end_container(iter, internal::tape_type::START_ARRAY, internal::tape_type::END_ARRAY); ++} ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_document_end(json_iterator &iter) noexcept { ++ constexpr uint32_t start_tape_index = 0; ++ tape.append(start_tape_index, internal::tape_type::ROOT); ++ tape_writer::write(iter.dom_parser.doc->tape[start_tape_index], next_tape_index(iter), internal::tape_type::ROOT); ++ return SUCCESS; ++} ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_key(json_iterator &iter, const uint8_t *key) noexcept { ++ return visit_string(iter, key, true); ++} ++ ++simdjson_warn_unused simdjson_inline error_code tape_builder::increment_count(json_iterator &iter) noexcept { ++ iter.dom_parser.open_containers[iter.depth].count++; // we have a key value pair in the object at parser.dom_parser.depth - 1 ++ return SUCCESS; ++} ++ ++simdjson_inline tape_builder::tape_builder(dom::document &doc) noexcept : tape{doc.tape.get()}, current_string_buf_loc{doc.string_buf.get()} {} ++ ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_string(json_iterator &iter, const uint8_t *value, bool key) noexcept { ++ iter.log_value(key ? "key" : "string"); ++ uint8_t *dst = on_start_string(iter); ++ dst = stringparsing::parse_string(value+1, dst); ++ if (dst == nullptr) { ++ iter.log_error("Invalid escape in string"); ++ return STRING_ERROR; ++ } ++ on_end_string(dst); ++ return SUCCESS; ++} ++ ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_root_string(json_iterator &iter, const uint8_t *value) noexcept { ++ return visit_string(iter, value); ++} ++ ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_number(json_iterator &iter, const uint8_t *value) noexcept { ++ iter.log_value("number"); ++ return numberparsing::parse_number(value, tape); ++} ++ ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_root_number(json_iterator &iter, const uint8_t *value) noexcept { ++ // ++ // We need to make a copy to make sure that the string is space terminated. ++ // This is not about padding the input, which should already padded up ++ // to len + SIMDJSON_PADDING. However, we have no control at this stage ++ // on how the padding was done. What if the input string was padded with nulls? ++ // It is quite common for an input string to have an extra null character (C string). ++ // We do not want to allow 9\0 (where \0 is the null character) inside a JSON ++ // document, but the string "9\0" by itself is fine. So we make a copy and ++ // pad the input with spaces when we know that there is just one input element. ++ // This copy is relatively expensive, but it will almost never be called in ++ // practice unless you are in the strange scenario where you have many JSON ++ // documents made of single atoms. ++ // ++ std::unique_ptrcopy(new (std::nothrow) uint8_t[iter.remaining_len() + SIMDJSON_PADDING]); ++ if (copy.get() == nullptr) { return MEMALLOC; } ++ std::memcpy(copy.get(), value, iter.remaining_len()); ++ std::memset(copy.get() + iter.remaining_len(), ' ', SIMDJSON_PADDING); ++ error_code error = visit_number(iter, copy.get()); ++ return error; ++} ++ ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_true_atom(json_iterator &iter, const uint8_t *value) noexcept { ++ iter.log_value("true"); ++ if (!atomparsing::is_valid_true_atom(value)) { return T_ATOM_ERROR; } ++ tape.append(0, internal::tape_type::TRUE_VALUE); ++ return SUCCESS; ++} ++ ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_root_true_atom(json_iterator &iter, const uint8_t *value) noexcept { ++ iter.log_value("true"); ++ if (!atomparsing::is_valid_true_atom(value, iter.remaining_len())) { return T_ATOM_ERROR; } ++ tape.append(0, internal::tape_type::TRUE_VALUE); ++ return SUCCESS; ++} ++ ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_false_atom(json_iterator &iter, const uint8_t *value) noexcept { ++ iter.log_value("false"); ++ if (!atomparsing::is_valid_false_atom(value)) { return F_ATOM_ERROR; } ++ tape.append(0, internal::tape_type::FALSE_VALUE); ++ return SUCCESS; ++} ++ ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_root_false_atom(json_iterator &iter, const uint8_t *value) noexcept { ++ iter.log_value("false"); ++ if (!atomparsing::is_valid_false_atom(value, iter.remaining_len())) { return F_ATOM_ERROR; } ++ tape.append(0, internal::tape_type::FALSE_VALUE); ++ return SUCCESS; ++} ++ ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_null_atom(json_iterator &iter, const uint8_t *value) noexcept { ++ iter.log_value("null"); ++ if (!atomparsing::is_valid_null_atom(value)) { return N_ATOM_ERROR; } ++ tape.append(0, internal::tape_type::NULL_VALUE); ++ return SUCCESS; ++} ++ ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_root_null_atom(json_iterator &iter, const uint8_t *value) noexcept { ++ iter.log_value("null"); ++ if (!atomparsing::is_valid_null_atom(value, iter.remaining_len())) { return N_ATOM_ERROR; } ++ tape.append(0, internal::tape_type::NULL_VALUE); ++ return SUCCESS; ++} ++ ++// private: ++ ++simdjson_inline uint32_t tape_builder::next_tape_index(json_iterator &iter) const noexcept { ++ return uint32_t(tape.next_tape_loc - iter.dom_parser.doc->tape.get()); ++} ++ ++simdjson_warn_unused simdjson_inline error_code tape_builder::empty_container(json_iterator &iter, internal::tape_type start, internal::tape_type end) noexcept { ++ auto start_index = next_tape_index(iter); ++ tape.append(start_index+2, start); ++ tape.append(start_index, end); ++ return SUCCESS; ++} ++ ++simdjson_inline void tape_builder::start_container(json_iterator &iter) noexcept { ++ iter.dom_parser.open_containers[iter.depth].tape_index = next_tape_index(iter); ++ iter.dom_parser.open_containers[iter.depth].count = 0; ++ tape.skip(); // We don't actually *write* the start element until the end. ++} ++ ++simdjson_warn_unused simdjson_inline error_code tape_builder::end_container(json_iterator &iter, internal::tape_type start, internal::tape_type end) noexcept { ++ // Write the ending tape element, pointing at the start location ++ const uint32_t start_tape_index = iter.dom_parser.open_containers[iter.depth].tape_index; ++ tape.append(start_tape_index, end); ++ // Write the start tape element, pointing at the end location (and including count) ++ // count can overflow if it exceeds 24 bits... so we saturate ++ // the convention being that a cnt of 0xffffff or more is undetermined in value (>= 0xffffff). ++ const uint32_t count = iter.dom_parser.open_containers[iter.depth].count; ++ const uint32_t cntsat = count > 0xFFFFFF ? 0xFFFFFF : count; ++ tape_writer::write(iter.dom_parser.doc->tape[start_tape_index], next_tape_index(iter) | (uint64_t(cntsat) << 32), start); ++ return SUCCESS; ++} ++ ++simdjson_inline uint8_t *tape_builder::on_start_string(json_iterator &iter) noexcept { ++ // we advance the point, accounting for the fact that we have a NULL termination ++ tape.append(current_string_buf_loc - iter.dom_parser.doc->string_buf.get(), internal::tape_type::STRING); ++ return current_string_buf_loc + sizeof(uint32_t); ++} ++ ++simdjson_inline void tape_builder::on_end_string(uint8_t *dst) noexcept { ++ uint32_t str_length = uint32_t(dst - (current_string_buf_loc + sizeof(uint32_t))); ++ // TODO check for overflow in case someone has a crazy string (>=4GB?) ++ // But only add the overflow check when the document itself exceeds 4GB ++ // Currently unneeded because we refuse to parse docs larger or equal to 4GB. ++ memcpy(current_string_buf_loc, &str_length, sizeof(uint32_t)); ++ // NULL termination is still handy if you expect all your strings to ++ // be NULL terminated? It comes at a small cost ++ *dst = 0; ++ current_string_buf_loc = dst + 1; ++} ++ ++} // namespace stage2 ++} // unnamed namespace ++} // namespace ppc64 ++} // namespace simdjson ++/* end file src/generic/stage2/tape_builder.h */ ++ ++// ++// Implementation-specific overrides ++// ++namespace simdjson { ++namespace ppc64 { ++namespace { ++namespace stage1 { ++ ++simdjson_inline uint64_t json_string_scanner::find_escaped(uint64_t backslash) { ++ // On PPC, we don't short-circuit this if there are no backslashes, because the branch gives us no ++ // benefit and therefore makes things worse. ++ // if (!backslash) { uint64_t escaped = prev_escaped; prev_escaped = 0; return escaped; } ++ return find_escaped_branchless(backslash); ++} ++ ++} // namespace stage1 ++} // unnamed namespace ++ ++simdjson_warn_unused error_code implementation::minify(const uint8_t *buf, size_t len, uint8_t *dst, size_t &dst_len) const noexcept { ++ return ppc64::stage1::json_minifier::minify<64>(buf, len, dst, dst_len); ++} ++ ++simdjson_warn_unused error_code dom_parser_implementation::stage1(const uint8_t *_buf, size_t _len, stage1_mode streaming) noexcept { ++ this->buf = _buf; ++ this->len = _len; ++ return ppc64::stage1::json_structural_indexer::index<64>(buf, len, *this, streaming); ++} ++ ++simdjson_warn_unused bool implementation::validate_utf8(const char *buf, size_t len) const noexcept { ++ return ppc64::stage1::generic_validate_utf8(buf,len); ++} ++ ++simdjson_warn_unused error_code dom_parser_implementation::stage2(dom::document &_doc) noexcept { ++ return stage2::tape_builder::parse_document(*this, _doc); ++} ++ ++simdjson_warn_unused error_code dom_parser_implementation::stage2_next(dom::document &_doc) noexcept { ++ return stage2::tape_builder::parse_document(*this, _doc); ++} ++ ++simdjson_warn_unused uint8_t *dom_parser_implementation::parse_string(const uint8_t *src, uint8_t *dst) const noexcept { ++ return ppc64::stringparsing::parse_string(src, dst); ++} ++ ++simdjson_warn_unused error_code dom_parser_implementation::parse(const uint8_t *_buf, size_t _len, dom::document &_doc) noexcept { ++ auto error = stage1(_buf, _len, stage1_mode::regular); ++ if (error) { return error; } ++ return stage2(_doc); ++} ++ ++} // namespace ppc64 ++} // namespace simdjson ++ ++/* begin file include/simdjson/ppc64/end.h */ ++/* end file include/simdjson/ppc64/end.h */ ++/* end file src/ppc64/dom_parser_implementation.cpp */ ++#endif ++#if SIMDJSON_IMPLEMENTATION_WESTMERE ++/* begin file src/westmere/implementation.cpp */ ++/* begin file include/simdjson/westmere/begin.h */ ++// redefining SIMDJSON_IMPLEMENTATION to "westmere" ++// #define SIMDJSON_IMPLEMENTATION westmere ++SIMDJSON_TARGET_WESTMERE ++/* end file include/simdjson/westmere/begin.h */ ++ ++namespace simdjson { ++namespace westmere { ++ ++simdjson_warn_unused error_code implementation::create_dom_parser_implementation( ++ size_t capacity, ++ size_t max_depth, ++ std::unique_ptr& dst ++) const noexcept { ++ dst.reset( new (std::nothrow) dom_parser_implementation() ); ++ if (!dst) { return MEMALLOC; } ++ if (auto err = dst->set_capacity(capacity)) ++ return err; ++ if (auto err = dst->set_max_depth(max_depth)) ++ return err; ++ return SUCCESS; ++} ++ ++} // namespace westmere ++} // namespace simdjson ++ ++/* begin file include/simdjson/westmere/end.h */ ++SIMDJSON_UNTARGET_WESTMERE ++/* end file include/simdjson/westmere/end.h */ ++/* end file src/westmere/implementation.cpp */ ++/* begin file src/westmere/dom_parser_implementation.cpp */ ++/* begin file include/simdjson/westmere/begin.h */ ++// redefining SIMDJSON_IMPLEMENTATION to "westmere" ++// #define SIMDJSON_IMPLEMENTATION westmere ++SIMDJSON_TARGET_WESTMERE ++/* end file include/simdjson/westmere/begin.h */ ++ ++// ++// Stage 1 ++// ++ ++namespace simdjson { ++namespace westmere { ++namespace { ++ ++using namespace simd; ++ ++struct json_character_block { ++ static simdjson_inline json_character_block classify(const simd::simd8x64& in); ++ ++ simdjson_inline uint64_t whitespace() const noexcept { return _whitespace; } ++ simdjson_inline uint64_t op() const noexcept { return _op; } ++ simdjson_inline uint64_t scalar() const noexcept { return ~(op() | whitespace()); } ++ ++ uint64_t _whitespace; ++ uint64_t _op; ++}; ++ ++simdjson_inline json_character_block json_character_block::classify(const simd::simd8x64& in) { ++ // These lookups rely on the fact that anything < 127 will match the lower 4 bits, which is why ++ // we can't use the generic lookup_16. ++ auto whitespace_table = simd8::repeat_16(' ', 100, 100, 100, 17, 100, 113, 2, 100, '\t', '\n', 112, 100, '\r', 100, 100); ++ ++ // The 6 operators (:,[]{}) have these values: ++ // ++ // , 2C ++ // : 3A ++ // [ 5B ++ // { 7B ++ // ] 5D ++ // } 7D ++ // ++ // If you use | 0x20 to turn [ and ] into { and }, the lower 4 bits of each character is unique. ++ // We exploit this, using a simd 4-bit lookup to tell us which character match against, and then ++ // match it (against | 0x20). ++ // ++ // To prevent recognizing other characters, everything else gets compared with 0, which cannot ++ // match due to the | 0x20. ++ // ++ // NOTE: Due to the | 0x20, this ALSO treats and (control characters 0C and 1A) like , ++ // and :. This gets caught in stage 2, which checks the actual character to ensure the right ++ // operators are in the right places. ++ const auto op_table = simd8::repeat_16( ++ 0, 0, 0, 0, ++ 0, 0, 0, 0, ++ 0, 0, ':', '{', // : = 3A, [ = 5B, { = 7B ++ ',', '}', 0, 0 // , = 2C, ] = 5D, } = 7D ++ ); ++ ++ // We compute whitespace and op separately. If the code later only use one or the ++ // other, given the fact that all functions are aggressively inlined, we can ++ // hope that useless computations will be omitted. This is namely case when ++ // minifying (we only need whitespace). ++ ++ ++ const uint64_t whitespace = in.eq({ ++ _mm_shuffle_epi8(whitespace_table, in.chunks[0]), ++ _mm_shuffle_epi8(whitespace_table, in.chunks[1]), ++ _mm_shuffle_epi8(whitespace_table, in.chunks[2]), ++ _mm_shuffle_epi8(whitespace_table, in.chunks[3]) ++ }); ++ // Turn [ and ] into { and } ++ const simd8x64 curlified{ ++ in.chunks[0] | 0x20, ++ in.chunks[1] | 0x20, ++ in.chunks[2] | 0x20, ++ in.chunks[3] | 0x20 ++ }; ++ const uint64_t op = curlified.eq({ ++ _mm_shuffle_epi8(op_table, in.chunks[0]), ++ _mm_shuffle_epi8(op_table, in.chunks[1]), ++ _mm_shuffle_epi8(op_table, in.chunks[2]), ++ _mm_shuffle_epi8(op_table, in.chunks[3]) ++ }); ++ return { whitespace, op }; ++} ++ ++simdjson_inline bool is_ascii(const simd8x64& input) { ++ return input.reduce_or().is_ascii(); ++} ++ ++simdjson_unused simdjson_inline simd8 must_be_continuation(const simd8 prev1, const simd8 prev2, const simd8 prev3) { ++ simd8 is_second_byte = prev1.saturating_sub(0xc0u-1); // Only 11______ will be > 0 ++ simd8 is_third_byte = prev2.saturating_sub(0xe0u-1); // Only 111_____ will be > 0 ++ simd8 is_fourth_byte = prev3.saturating_sub(0xf0u-1); // Only 1111____ will be > 0 ++ // Caller requires a bool (all 1's). All values resulting from the subtraction will be <= 64, so signed comparison is fine. ++ return simd8(is_second_byte | is_third_byte | is_fourth_byte) > int8_t(0); ++} ++ ++simdjson_inline simd8 must_be_2_3_continuation(const simd8 prev2, const simd8 prev3) { ++ simd8 is_third_byte = prev2.saturating_sub(0xe0u-1); // Only 111_____ will be > 0 ++ simd8 is_fourth_byte = prev3.saturating_sub(0xf0u-1); // Only 1111____ will be > 0 ++ // Caller requires a bool (all 1's). All values resulting from the subtraction will be <= 64, so signed comparison is fine. ++ return simd8(is_third_byte | is_fourth_byte) > int8_t(0); ++} ++ ++} // unnamed namespace ++} // namespace westmere ++} // namespace simdjson ++ ++/* begin file src/generic/stage1/utf8_lookup4_algorithm.h */ ++namespace simdjson { ++namespace westmere { ++namespace { ++namespace utf8_validation { ++ ++using namespace simd; ++ ++ simdjson_inline simd8 check_special_cases(const simd8 input, const simd8 prev1) { ++// Bit 0 = Too Short (lead byte/ASCII followed by lead byte/ASCII) ++// Bit 1 = Too Long (ASCII followed by continuation) ++// Bit 2 = Overlong 3-byte ++// Bit 4 = Surrogate ++// Bit 5 = Overlong 2-byte ++// Bit 7 = Two Continuations ++ constexpr const uint8_t TOO_SHORT = 1<<0; // 11______ 0_______ ++ // 11______ 11______ ++ constexpr const uint8_t TOO_LONG = 1<<1; // 0_______ 10______ ++ constexpr const uint8_t OVERLONG_3 = 1<<2; // 11100000 100_____ ++ constexpr const uint8_t SURROGATE = 1<<4; // 11101101 101_____ ++ constexpr const uint8_t OVERLONG_2 = 1<<5; // 1100000_ 10______ ++ constexpr const uint8_t TWO_CONTS = 1<<7; // 10______ 10______ ++ constexpr const uint8_t TOO_LARGE = 1<<3; // 11110100 1001____ ++ // 11110100 101_____ ++ // 11110101 1001____ ++ // 11110101 101_____ ++ // 1111011_ 1001____ ++ // 1111011_ 101_____ ++ // 11111___ 1001____ ++ // 11111___ 101_____ ++ constexpr const uint8_t TOO_LARGE_1000 = 1<<6; ++ // 11110101 1000____ ++ // 1111011_ 1000____ ++ // 11111___ 1000____ ++ constexpr const uint8_t OVERLONG_4 = 1<<6; // 11110000 1000____ ++ ++ const simd8 byte_1_high = prev1.shr<4>().lookup_16( ++ // 0_______ ________ ++ TOO_LONG, TOO_LONG, TOO_LONG, TOO_LONG, ++ TOO_LONG, TOO_LONG, TOO_LONG, TOO_LONG, ++ // 10______ ________ ++ TWO_CONTS, TWO_CONTS, TWO_CONTS, TWO_CONTS, ++ // 1100____ ________ ++ TOO_SHORT | OVERLONG_2, ++ // 1101____ ________ ++ TOO_SHORT, ++ // 1110____ ________ ++ TOO_SHORT | OVERLONG_3 | SURROGATE, ++ // 1111____ ________ ++ TOO_SHORT | TOO_LARGE | TOO_LARGE_1000 | OVERLONG_4 ++ ); ++ constexpr const uint8_t CARRY = TOO_SHORT | TOO_LONG | TWO_CONTS; // These all have ____ in byte 1 . ++ const simd8 byte_1_low = (prev1 & 0x0F).lookup_16( ++ // ____0000 ________ ++ CARRY | OVERLONG_3 | OVERLONG_2 | OVERLONG_4, ++ // ____0001 ________ ++ CARRY | OVERLONG_2, ++ // ____001_ ________ ++ CARRY, ++ CARRY, ++ ++ // ____0100 ________ ++ CARRY | TOO_LARGE, ++ // ____0101 ________ ++ CARRY | TOO_LARGE | TOO_LARGE_1000, ++ // ____011_ ________ ++ CARRY | TOO_LARGE | TOO_LARGE_1000, ++ CARRY | TOO_LARGE | TOO_LARGE_1000, ++ ++ // ____1___ ________ ++ CARRY | TOO_LARGE | TOO_LARGE_1000, ++ CARRY | TOO_LARGE | TOO_LARGE_1000, ++ CARRY | TOO_LARGE | TOO_LARGE_1000, ++ CARRY | TOO_LARGE | TOO_LARGE_1000, ++ CARRY | TOO_LARGE | TOO_LARGE_1000, ++ // ____1101 ________ ++ CARRY | TOO_LARGE | TOO_LARGE_1000 | SURROGATE, ++ CARRY | TOO_LARGE | TOO_LARGE_1000, ++ CARRY | TOO_LARGE | TOO_LARGE_1000 ++ ); ++ const simd8 byte_2_high = input.shr<4>().lookup_16( ++ // ________ 0_______ ++ TOO_SHORT, TOO_SHORT, TOO_SHORT, TOO_SHORT, ++ TOO_SHORT, TOO_SHORT, TOO_SHORT, TOO_SHORT, ++ ++ // ________ 1000____ ++ TOO_LONG | OVERLONG_2 | TWO_CONTS | OVERLONG_3 | TOO_LARGE_1000 | OVERLONG_4, ++ // ________ 1001____ ++ TOO_LONG | OVERLONG_2 | TWO_CONTS | OVERLONG_3 | TOO_LARGE, ++ // ________ 101_____ ++ TOO_LONG | OVERLONG_2 | TWO_CONTS | SURROGATE | TOO_LARGE, ++ TOO_LONG | OVERLONG_2 | TWO_CONTS | SURROGATE | TOO_LARGE, ++ ++ // ________ 11______ ++ TOO_SHORT, TOO_SHORT, TOO_SHORT, TOO_SHORT ++ ); ++ return (byte_1_high & byte_1_low & byte_2_high); ++ } ++ simdjson_inline simd8 check_multibyte_lengths(const simd8 input, ++ const simd8 prev_input, const simd8 sc) { ++ simd8 prev2 = input.prev<2>(prev_input); ++ simd8 prev3 = input.prev<3>(prev_input); ++ simd8 must23 = simd8(must_be_2_3_continuation(prev2, prev3)); ++ simd8 must23_80 = must23 & uint8_t(0x80); ++ return must23_80 ^ sc; ++ } ++ ++ // ++ // Return nonzero if there are incomplete multibyte characters at the end of the block: ++ // e.g. if there is a 4-byte character, but it's 3 bytes from the end. ++ // ++ simdjson_inline simd8 is_incomplete(const simd8 input) { ++ // If the previous input's last 3 bytes match this, they're too short (they ended at EOF): ++ // ... 1111____ 111_____ 11______ ++#if SIMDJSON_IMPLEMENTATION_ICELAKE ++ static const uint8_t max_array[64] = { ++ 255, 255, 255, 255, 255, 255, 255, 255, ++ 255, 255, 255, 255, 255, 255, 255, 255, ++ 255, 255, 255, 255, 255, 255, 255, 255, ++ 255, 255, 255, 255, 255, 255, 255, 255, ++ 255, 255, 255, 255, 255, 255, 255, 255, ++ 255, 255, 255, 255, 255, 255, 255, 255, ++ 255, 255, 255, 255, 255, 255, 255, 255, ++ 255, 255, 255, 255, 255, 0xf0u-1, 0xe0u-1, 0xc0u-1 ++ }; ++#else ++ static const uint8_t max_array[32] = { ++ 255, 255, 255, 255, 255, 255, 255, 255, ++ 255, 255, 255, 255, 255, 255, 255, 255, ++ 255, 255, 255, 255, 255, 255, 255, 255, ++ 255, 255, 255, 255, 255, 0xf0u-1, 0xe0u-1, 0xc0u-1 ++ }; ++#endif ++ const simd8 max_value(&max_array[sizeof(max_array)-sizeof(simd8)]); ++ return input.gt_bits(max_value); ++ } ++ ++ struct utf8_checker { ++ // If this is nonzero, there has been a UTF-8 error. ++ simd8 error; ++ // The last input we received ++ simd8 prev_input_block; ++ // Whether the last input we received was incomplete (used for ASCII fast path) ++ simd8 prev_incomplete; ++ ++ // ++ // Check whether the current bytes are valid UTF-8. ++ // ++ simdjson_inline void check_utf8_bytes(const simd8 input, const simd8 prev_input) { ++ // Flip prev1...prev3 so we can easily determine if they are 2+, 3+ or 4+ lead bytes ++ // (2, 3, 4-byte leads become large positive numbers instead of small negative numbers) ++ simd8 prev1 = input.prev<1>(prev_input); ++ simd8 sc = check_special_cases(input, prev1); ++ this->error |= check_multibyte_lengths(input, prev_input, sc); ++ } ++ ++ // The only problem that can happen at EOF is that a multibyte character is too short ++ // or a byte value too large in the last bytes: check_special_cases only checks for bytes ++ // too large in the first of two bytes. ++ simdjson_inline void check_eof() { ++ // If the previous block had incomplete UTF-8 characters at the end, an ASCII block can't ++ // possibly finish them. ++ this->error |= this->prev_incomplete; ++ } ++ ++#ifndef SIMDJSON_IF_CONSTEXPR ++#if SIMDJSON_CPLUSPLUS17 ++#define SIMDJSON_IF_CONSTEXPR if constexpr ++#else ++#define SIMDJSON_IF_CONSTEXPR if ++#endif ++#endif ++ ++ simdjson_inline void check_next_input(const simd8x64& input) { ++ if(simdjson_likely(is_ascii(input))) { ++ this->error |= this->prev_incomplete; ++ } else { ++ // you might think that a for-loop would work, but under Visual Studio, it is not good enough. ++ static_assert((simd8x64::NUM_CHUNKS == 1) ++ ||(simd8x64::NUM_CHUNKS == 2) ++ || (simd8x64::NUM_CHUNKS == 4), ++ "We support one, two or four chunks per 64-byte block."); ++ SIMDJSON_IF_CONSTEXPR (simd8x64::NUM_CHUNKS == 1) { ++ this->check_utf8_bytes(input.chunks[0], this->prev_input_block); ++ } else SIMDJSON_IF_CONSTEXPR (simd8x64::NUM_CHUNKS == 2) { ++ this->check_utf8_bytes(input.chunks[0], this->prev_input_block); ++ this->check_utf8_bytes(input.chunks[1], input.chunks[0]); ++ } else SIMDJSON_IF_CONSTEXPR (simd8x64::NUM_CHUNKS == 4) { ++ this->check_utf8_bytes(input.chunks[0], this->prev_input_block); ++ this->check_utf8_bytes(input.chunks[1], input.chunks[0]); ++ this->check_utf8_bytes(input.chunks[2], input.chunks[1]); ++ this->check_utf8_bytes(input.chunks[3], input.chunks[2]); ++ } ++ this->prev_incomplete = is_incomplete(input.chunks[simd8x64::NUM_CHUNKS-1]); ++ this->prev_input_block = input.chunks[simd8x64::NUM_CHUNKS-1]; ++ } ++ } ++ // do not forget to call check_eof! ++ simdjson_inline error_code errors() { ++ return this->error.any_bits_set_anywhere() ? error_code::UTF8_ERROR : error_code::SUCCESS; ++ } ++ ++ }; // struct utf8_checker ++} // namespace utf8_validation ++ ++using utf8_validation::utf8_checker; ++ ++} // unnamed namespace ++} // namespace westmere ++} // namespace simdjson ++/* end file src/generic/stage1/utf8_lookup4_algorithm.h */ ++/* begin file src/generic/stage1/json_structural_indexer.h */ ++// This file contains the common code every implementation uses in stage1 ++// It is intended to be included multiple times and compiled multiple times ++// We assume the file in which it is included already includes ++// "simdjson/stage1.h" (this simplifies amalgation) ++ ++/* begin file src/generic/stage1/buf_block_reader.h */ ++namespace simdjson { ++namespace westmere { ++namespace { ++ ++// Walks through a buffer in block-sized increments, loading the last part with spaces ++template ++struct buf_block_reader { ++public: ++ simdjson_inline buf_block_reader(const uint8_t *_buf, size_t _len); ++ simdjson_inline size_t block_index(); ++ simdjson_inline bool has_full_block() const; ++ simdjson_inline const uint8_t *full_block() const; ++ /** ++ * Get the last block, padded with spaces. ++ * ++ * There will always be a last block, with at least 1 byte, unless len == 0 (in which case this ++ * function fills the buffer with spaces and returns 0. In particular, if len == STEP_SIZE there ++ * will be 0 full_blocks and 1 remainder block with STEP_SIZE bytes and no spaces for padding. ++ * ++ * @return the number of effective characters in the last block. ++ */ ++ simdjson_inline size_t get_remainder(uint8_t *dst) const; ++ simdjson_inline void advance(); ++private: ++ const uint8_t *buf; ++ const size_t len; ++ const size_t lenminusstep; ++ size_t idx; ++}; ++ ++// Routines to print masks and text for debugging bitmask operations ++simdjson_unused static char * format_input_text_64(const uint8_t *text) { ++ static char buf[sizeof(simd8x64) + 1]; ++ for (size_t i=0; i); i++) { ++ buf[i] = int8_t(text[i]) < ' ' ? '_' : int8_t(text[i]); ++ } ++ buf[sizeof(simd8x64)] = '\0'; ++ return buf; ++} ++ ++// Routines to print masks and text for debugging bitmask operations ++simdjson_unused static char * format_input_text(const simd8x64& in) { ++ static char buf[sizeof(simd8x64) + 1]; ++ in.store(reinterpret_cast(buf)); ++ for (size_t i=0; i); i++) { ++ if (buf[i] < ' ') { buf[i] = '_'; } ++ } ++ buf[sizeof(simd8x64)] = '\0'; ++ return buf; ++} ++ ++simdjson_unused static char * format_mask(uint64_t mask) { ++ static char buf[sizeof(simd8x64) + 1]; ++ for (size_t i=0; i<64; i++) { ++ buf[i] = (mask & (size_t(1) << i)) ? 'X' : ' '; ++ } ++ buf[64] = '\0'; ++ return buf; ++} ++ ++template ++simdjson_inline buf_block_reader::buf_block_reader(const uint8_t *_buf, size_t _len) : buf{_buf}, len{_len}, lenminusstep{len < STEP_SIZE ? 0 : len - STEP_SIZE}, idx{0} {} ++ ++template ++simdjson_inline size_t buf_block_reader::block_index() { return idx; } ++ ++template ++simdjson_inline bool buf_block_reader::has_full_block() const { ++ return idx < lenminusstep; ++} ++ ++template ++simdjson_inline const uint8_t *buf_block_reader::full_block() const { ++ return &buf[idx]; ++} ++ ++template ++simdjson_inline size_t buf_block_reader::get_remainder(uint8_t *dst) const { ++ if(len == idx) { return 0; } // memcpy(dst, null, 0) will trigger an error with some sanitizers ++ std::memset(dst, 0x20, STEP_SIZE); // std::memset STEP_SIZE because it's more efficient to write out 8 or 16 bytes at once. ++ std::memcpy(dst, buf + idx, len - idx); ++ return len - idx; ++} ++ ++template ++simdjson_inline void buf_block_reader::advance() { ++ idx += STEP_SIZE; ++} ++ ++} // unnamed namespace ++} // namespace westmere ++} // namespace simdjson ++/* end file src/generic/stage1/buf_block_reader.h */ ++/* begin file src/generic/stage1/json_string_scanner.h */ ++namespace simdjson { ++namespace westmere { ++namespace { ++namespace stage1 { ++ ++struct json_string_block { ++ // We spell out the constructors in the hope of resolving inlining issues with Visual Studio 2017 ++ simdjson_inline json_string_block(uint64_t backslash, uint64_t escaped, uint64_t quote, uint64_t in_string) : ++ _backslash(backslash), _escaped(escaped), _quote(quote), _in_string(in_string) {} ++ ++ // Escaped characters (characters following an escape() character) ++ simdjson_inline uint64_t escaped() const { return _escaped; } ++ // Escape characters (backslashes that are not escaped--i.e. in \\, includes only the first \) ++ simdjson_inline uint64_t escape() const { return _backslash & ~_escaped; } ++ // Real (non-backslashed) quotes ++ simdjson_inline uint64_t quote() const { return _quote; } ++ // Start quotes of strings ++ simdjson_inline uint64_t string_start() const { return _quote & _in_string; } ++ // End quotes of strings ++ simdjson_inline uint64_t string_end() const { return _quote & ~_in_string; } ++ // Only characters inside the string (not including the quotes) ++ simdjson_inline uint64_t string_content() const { return _in_string & ~_quote; } ++ // Return a mask of whether the given characters are inside a string (only works on non-quotes) ++ simdjson_inline uint64_t non_quote_inside_string(uint64_t mask) const { return mask & _in_string; } ++ // Return a mask of whether the given characters are inside a string (only works on non-quotes) ++ simdjson_inline uint64_t non_quote_outside_string(uint64_t mask) const { return mask & ~_in_string; } ++ // Tail of string (everything except the start quote) ++ simdjson_inline uint64_t string_tail() const { return _in_string ^ _quote; } ++ ++ // backslash characters ++ uint64_t _backslash; ++ // escaped characters (backslashed--does not include the hex characters after \u) ++ uint64_t _escaped; ++ // real quotes (non-backslashed ones) ++ uint64_t _quote; ++ // string characters (includes start quote but not end quote) ++ uint64_t _in_string; ++}; ++ ++// Scans blocks for string characters, storing the state necessary to do so ++class json_string_scanner { ++public: ++ simdjson_inline json_string_block next(const simd::simd8x64& in); ++ // Returns either UNCLOSED_STRING or SUCCESS ++ simdjson_inline error_code finish(); ++ ++private: ++ // Intended to be defined by the implementation ++ simdjson_inline uint64_t find_escaped(uint64_t escape); ++ simdjson_inline uint64_t find_escaped_branchless(uint64_t escape); ++ ++ // Whether the last iteration was still inside a string (all 1's = true, all 0's = false). ++ uint64_t prev_in_string = 0ULL; ++ // Whether the first character of the next iteration is escaped. ++ uint64_t prev_escaped = 0ULL; ++}; ++ ++// ++// Finds escaped characters (characters following \). ++// ++// Handles runs of backslashes like \\\" and \\\\" correctly (yielding 0101 and 01010, respectively). ++// ++// Does this by: ++// - Shift the escape mask to get potentially escaped characters (characters after backslashes). ++// - Mask escaped sequences that start on *even* bits with 1010101010 (odd bits are escaped, even bits are not) ++// - Mask escaped sequences that start on *odd* bits with 0101010101 (even bits are escaped, odd bits are not) ++// ++// To distinguish between escaped sequences starting on even/odd bits, it finds the start of all ++// escape sequences, filters out the ones that start on even bits, and adds that to the mask of ++// escape sequences. This causes the addition to clear out the sequences starting on odd bits (since ++// the start bit causes a carry), and leaves even-bit sequences alone. ++// ++// Example: ++// ++// text | \\\ | \\\"\\\" \\\" \\"\\" | ++// escape | xxx | xx xxx xxx xx xx | Removed overflow backslash; will | it into follows_escape ++// odd_starts | x | x x x | escape & ~even_bits & ~follows_escape ++// even_seq | c| cxxx c xx c | c = carry bit -- will be masked out later ++// invert_mask | | cxxx c xx c| even_seq << 1 ++// follows_escape | xx | x xx xxx xxx xx xx | Includes overflow bit ++// escaped | x | x x x x x x x x | ++// desired | x | x x x x x x x x | ++// text | \\\ | \\\"\\\" \\\" \\"\\" | ++// ++simdjson_inline uint64_t json_string_scanner::find_escaped_branchless(uint64_t backslash) { ++ // If there was overflow, pretend the first character isn't a backslash ++ backslash &= ~prev_escaped; ++ uint64_t follows_escape = backslash << 1 | prev_escaped; ++ ++ // Get sequences starting on even bits by clearing out the odd series using + ++ const uint64_t even_bits = 0x5555555555555555ULL; ++ uint64_t odd_sequence_starts = backslash & ~even_bits & ~follows_escape; ++ uint64_t sequences_starting_on_even_bits; ++ prev_escaped = add_overflow(odd_sequence_starts, backslash, &sequences_starting_on_even_bits); ++ uint64_t invert_mask = sequences_starting_on_even_bits << 1; // The mask we want to return is the *escaped* bits, not escapes. ++ ++ // Mask every other backslashed character as an escaped character ++ // Flip the mask for sequences that start on even bits, to correct them ++ return (even_bits ^ invert_mask) & follows_escape; ++} ++ ++// ++// Return a mask of all string characters plus end quotes. ++// ++// prev_escaped is overflow saying whether the next character is escaped. ++// prev_in_string is overflow saying whether we're still in a string. ++// ++// Backslash sequences outside of quotes will be detected in stage 2. ++// ++simdjson_inline json_string_block json_string_scanner::next(const simd::simd8x64& in) { ++ const uint64_t backslash = in.eq('\\'); ++ const uint64_t escaped = find_escaped(backslash); ++ const uint64_t quote = in.eq('"') & ~escaped; ++ ++ // ++ // prefix_xor flips on bits inside the string (and flips off the end quote). ++ // ++ // Then we xor with prev_in_string: if we were in a string already, its effect is flipped ++ // (characters inside strings are outside, and characters outside strings are inside). ++ // ++ const uint64_t in_string = prefix_xor(quote) ^ prev_in_string; ++ ++ // ++ // Check if we're still in a string at the end of the box so the next block will know ++ // ++ // right shift of a signed value expected to be well-defined and standard ++ // compliant as of C++20, John Regher from Utah U. says this is fine code ++ // ++ prev_in_string = uint64_t(static_cast(in_string) >> 63); ++ ++ // Use ^ to turn the beginning quote off, and the end quote on. ++ ++ // We are returning a function-local object so either we get a move constructor ++ // or we get copy elision. ++ return json_string_block( ++ backslash, ++ escaped, ++ quote, ++ in_string ++ ); ++} ++ ++simdjson_inline error_code json_string_scanner::finish() { ++ if (prev_in_string) { ++ return UNCLOSED_STRING; ++ } ++ return SUCCESS; ++} ++ ++} // namespace stage1 ++} // unnamed namespace ++} // namespace westmere ++} // namespace simdjson ++/* end file src/generic/stage1/json_string_scanner.h */ ++/* begin file src/generic/stage1/json_scanner.h */ ++namespace simdjson { ++namespace westmere { ++namespace { ++namespace stage1 { ++ ++/** ++ * A block of scanned json, with information on operators and scalars. ++ * ++ * We seek to identify pseudo-structural characters. Anything that is inside ++ * a string must be omitted (hence & ~_string.string_tail()). ++ * Otherwise, pseudo-structural characters come in two forms. ++ * 1. We have the structural characters ([,],{,},:, comma). The ++ * term 'structural character' is from the JSON RFC. ++ * 2. We have the 'scalar pseudo-structural characters'. ++ * Scalars are quotes, and any character except structural characters and white space. ++ * ++ * To identify the scalar pseudo-structural characters, we must look at what comes ++ * before them: it must be a space, a quote or a structural characters. ++ * Starting with simdjson v0.3, we identify them by ++ * negation: we identify everything that is followed by a non-quote scalar, ++ * and we negate that. Whatever remains must be a 'scalar pseudo-structural character'. ++ */ ++struct json_block { ++public: ++ // We spell out the constructors in the hope of resolving inlining issues with Visual Studio 2017 ++ simdjson_inline json_block(json_string_block&& string, json_character_block characters, uint64_t follows_potential_nonquote_scalar) : ++ _string(std::move(string)), _characters(characters), _follows_potential_nonquote_scalar(follows_potential_nonquote_scalar) {} ++ simdjson_inline json_block(json_string_block string, json_character_block characters, uint64_t follows_potential_nonquote_scalar) : ++ _string(string), _characters(characters), _follows_potential_nonquote_scalar(follows_potential_nonquote_scalar) {} ++ ++ /** ++ * The start of structurals. ++ * In simdjson prior to v0.3, these were called the pseudo-structural characters. ++ **/ ++ simdjson_inline uint64_t structural_start() const noexcept { return potential_structural_start() & ~_string.string_tail(); } ++ /** All JSON whitespace (i.e. not in a string) */ ++ simdjson_inline uint64_t whitespace() const noexcept { return non_quote_outside_string(_characters.whitespace()); } ++ ++ // Helpers ++ ++ /** Whether the given characters are inside a string (only works on non-quotes) */ ++ simdjson_inline uint64_t non_quote_inside_string(uint64_t mask) const noexcept { return _string.non_quote_inside_string(mask); } ++ /** Whether the given characters are outside a string (only works on non-quotes) */ ++ simdjson_inline uint64_t non_quote_outside_string(uint64_t mask) const noexcept { return _string.non_quote_outside_string(mask); } ++ ++ // string and escape characters ++ json_string_block _string; ++ // whitespace, structural characters ('operators'), scalars ++ json_character_block _characters; ++ // whether the previous character was a scalar ++ uint64_t _follows_potential_nonquote_scalar; ++private: ++ // Potential structurals (i.e. disregarding strings) ++ ++ /** ++ * structural elements ([,],{,},:, comma) plus scalar starts like 123, true and "abc". ++ * They may reside inside a string. ++ **/ ++ simdjson_inline uint64_t potential_structural_start() const noexcept { return _characters.op() | potential_scalar_start(); } ++ /** ++ * The start of non-operator runs, like 123, true and "abc". ++ * It main reside inside a string. ++ **/ ++ simdjson_inline uint64_t potential_scalar_start() const noexcept { ++ // The term "scalar" refers to anything except structural characters and white space ++ // (so letters, numbers, quotes). ++ // Whenever it is preceded by something that is not a structural element ({,},[,],:, ") nor a white-space ++ // then we know that it is irrelevant structurally. ++ return _characters.scalar() & ~follows_potential_scalar(); ++ } ++ /** ++ * Whether the given character is immediately after a non-operator like 123, true. ++ * The characters following a quote are not included. ++ */ ++ simdjson_inline uint64_t follows_potential_scalar() const noexcept { ++ // _follows_potential_nonquote_scalar: is defined as marking any character that follows a character ++ // that is not a structural element ({,},[,],:, comma) nor a quote (") and that is not a ++ // white space. ++ // It is understood that within quoted region, anything at all could be marked (irrelevant). ++ return _follows_potential_nonquote_scalar; ++ } ++}; ++ ++/** ++ * Scans JSON for important bits: structural characters or 'operators', strings, and scalars. ++ * ++ * The scanner starts by calculating two distinct things: ++ * - string characters (taking \" into account) ++ * - structural characters or 'operators' ([]{},:, comma) ++ * and scalars (runs of non-operators like 123, true and "abc") ++ * ++ * To minimize data dependency (a key component of the scanner's speed), it finds these in parallel: ++ * in particular, the operator/scalar bit will find plenty of things that are actually part of ++ * strings. When we're done, json_block will fuse the two together by masking out tokens that are ++ * part of a string. ++ */ ++class json_scanner { ++public: ++ json_scanner() = default; ++ simdjson_inline json_block next(const simd::simd8x64& in); ++ // Returns either UNCLOSED_STRING or SUCCESS ++ simdjson_inline error_code finish(); ++ ++private: ++ // Whether the last character of the previous iteration is part of a scalar token ++ // (anything except whitespace or a structural character/'operator'). ++ uint64_t prev_scalar = 0ULL; ++ json_string_scanner string_scanner{}; ++}; ++ ++ ++// ++// Check if the current character immediately follows a matching character. ++// ++// For example, this checks for quotes with backslashes in front of them: ++// ++// const uint64_t backslashed_quote = in.eq('"') & immediately_follows(in.eq('\'), prev_backslash); ++// ++simdjson_inline uint64_t follows(const uint64_t match, uint64_t &overflow) { ++ const uint64_t result = match << 1 | overflow; ++ overflow = match >> 63; ++ return result; ++} ++ ++simdjson_inline json_block json_scanner::next(const simd::simd8x64& in) { ++ json_string_block strings = string_scanner.next(in); ++ // identifies the white-space and the structural characters ++ json_character_block characters = json_character_block::classify(in); ++ // The term "scalar" refers to anything except structural characters and white space ++ // (so letters, numbers, quotes). ++ // We want follows_scalar to mark anything that follows a non-quote scalar (so letters and numbers). ++ // ++ // A terminal quote should either be followed by a structural character (comma, brace, bracket, colon) ++ // or nothing. However, we still want ' "a string"true ' to mark the 't' of 'true' as a potential ++ // pseudo-structural character just like we would if we had ' "a string" true '; otherwise we ++ // may need to add an extra check when parsing strings. ++ // ++ // Performance: there are many ways to skin this cat. ++ const uint64_t nonquote_scalar = characters.scalar() & ~strings.quote(); ++ uint64_t follows_nonquote_scalar = follows(nonquote_scalar, prev_scalar); ++ // We are returning a function-local object so either we get a move constructor ++ // or we get copy elision. ++ return json_block( ++ strings,// strings is a function-local object so either it moves or the copy is elided. ++ characters, ++ follows_nonquote_scalar ++ ); ++} ++ ++simdjson_inline error_code json_scanner::finish() { ++ return string_scanner.finish(); ++} ++ ++} // namespace stage1 ++} // unnamed namespace ++} // namespace westmere ++} // namespace simdjson ++/* end file src/generic/stage1/json_scanner.h */ ++/* begin file src/generic/stage1/json_minifier.h */ ++// This file contains the common code every implementation uses in stage1 ++// It is intended to be included multiple times and compiled multiple times ++// We assume the file in which it is included already includes ++// "simdjson/stage1.h" (this simplifies amalgation) ++ ++namespace simdjson { ++namespace westmere { ++namespace { ++namespace stage1 { ++ ++class json_minifier { ++public: ++ template ++ static error_code minify(const uint8_t *buf, size_t len, uint8_t *dst, size_t &dst_len) noexcept; ++ ++private: ++ simdjson_inline json_minifier(uint8_t *_dst) ++ : dst{_dst} ++ {} ++ template ++ simdjson_inline void step(const uint8_t *block_buf, buf_block_reader &reader) noexcept; ++ simdjson_inline void next(const simd::simd8x64& in, const json_block& block); ++ simdjson_inline error_code finish(uint8_t *dst_start, size_t &dst_len); ++ json_scanner scanner{}; ++ uint8_t *dst; ++}; ++ ++simdjson_inline void json_minifier::next(const simd::simd8x64& in, const json_block& block) { ++ uint64_t mask = block.whitespace(); ++ dst += in.compress(mask, dst); ++} ++ ++simdjson_inline error_code json_minifier::finish(uint8_t *dst_start, size_t &dst_len) { ++ error_code error = scanner.finish(); ++ if (error) { dst_len = 0; return error; } ++ dst_len = dst - dst_start; ++ return SUCCESS; ++} ++ ++template<> ++simdjson_inline void json_minifier::step<128>(const uint8_t *block_buf, buf_block_reader<128> &reader) noexcept { ++ simd::simd8x64 in_1(block_buf); ++ simd::simd8x64 in_2(block_buf+64); ++ json_block block_1 = scanner.next(in_1); ++ json_block block_2 = scanner.next(in_2); ++ this->next(in_1, block_1); ++ this->next(in_2, block_2); ++ reader.advance(); ++} ++ ++template<> ++simdjson_inline void json_minifier::step<64>(const uint8_t *block_buf, buf_block_reader<64> &reader) noexcept { ++ simd::simd8x64 in_1(block_buf); ++ json_block block_1 = scanner.next(in_1); ++ this->next(block_buf, block_1); ++ reader.advance(); ++} ++ ++template ++error_code json_minifier::minify(const uint8_t *buf, size_t len, uint8_t *dst, size_t &dst_len) noexcept { ++ buf_block_reader reader(buf, len); ++ json_minifier minifier(dst); ++ ++ // Index the first n-1 blocks ++ while (reader.has_full_block()) { ++ minifier.step(reader.full_block(), reader); ++ } ++ ++ // Index the last (remainder) block, padded with spaces ++ uint8_t block[STEP_SIZE]; ++ size_t remaining_bytes = reader.get_remainder(block); ++ if (remaining_bytes > 0) { ++ // We do not want to write directly to the output stream. Rather, we write ++ // to a local buffer (for safety). ++ uint8_t out_block[STEP_SIZE]; ++ uint8_t * const guarded_dst{minifier.dst}; ++ minifier.dst = out_block; ++ minifier.step(block, reader); ++ size_t to_write = minifier.dst - out_block; ++ // In some cases, we could be enticed to consider the padded spaces ++ // as part of the string. This is fine as long as we do not write more ++ // than we consumed. ++ if(to_write > remaining_bytes) { to_write = remaining_bytes; } ++ memcpy(guarded_dst, out_block, to_write); ++ minifier.dst = guarded_dst + to_write; ++ } ++ return minifier.finish(dst, dst_len); ++} ++ ++} // namespace stage1 ++} // unnamed namespace ++} // namespace westmere ++} // namespace simdjson ++/* end file src/generic/stage1/json_minifier.h */ ++/* begin file src/generic/stage1/find_next_document_index.h */ ++namespace simdjson { ++namespace westmere { ++namespace { ++ ++/** ++ * This algorithm is used to quickly identify the last structural position that ++ * makes up a complete document. ++ * ++ * It does this by going backwards and finding the last *document boundary* (a ++ * place where one value follows another without a comma between them). If the ++ * last document (the characters after the boundary) has an equal number of ++ * start and end brackets, it is considered complete. ++ * ++ * Simply put, we iterate over the structural characters, starting from ++ * the end. We consider that we found the end of a JSON document when the ++ * first element of the pair is NOT one of these characters: '{' '[' ':' ',' ++ * and when the second element is NOT one of these characters: '}' ']' ':' ','. ++ * ++ * This simple comparison works most of the time, but it does not cover cases ++ * where the batch's structural indexes contain a perfect amount of documents. ++ * In such a case, we do not have access to the structural index which follows ++ * the last document, therefore, we do not have access to the second element in ++ * the pair, and that means we cannot identify the last document. To fix this ++ * issue, we keep a count of the open and closed curly/square braces we found ++ * while searching for the pair. When we find a pair AND the count of open and ++ * closed curly/square braces is the same, we know that we just passed a ++ * complete document, therefore the last json buffer location is the end of the ++ * batch. ++ */ ++simdjson_inline uint32_t find_next_document_index(dom_parser_implementation &parser) { ++ // Variant: do not count separately, just figure out depth ++ if(parser.n_structural_indexes == 0) { return 0; } ++ auto arr_cnt = 0; ++ auto obj_cnt = 0; ++ for (auto i = parser.n_structural_indexes - 1; i > 0; i--) { ++ auto idxb = parser.structural_indexes[i]; ++ switch (parser.buf[idxb]) { ++ case ':': ++ case ',': ++ continue; ++ case '}': ++ obj_cnt--; ++ continue; ++ case ']': ++ arr_cnt--; ++ continue; ++ case '{': ++ obj_cnt++; ++ break; ++ case '[': ++ arr_cnt++; ++ break; ++ } ++ auto idxa = parser.structural_indexes[i - 1]; ++ switch (parser.buf[idxa]) { ++ case '{': ++ case '[': ++ case ':': ++ case ',': ++ continue; ++ } ++ // Last document is complete, so the next document will appear after! ++ if (!arr_cnt && !obj_cnt) { ++ return parser.n_structural_indexes; ++ } ++ // Last document is incomplete; mark the document at i + 1 as the next one ++ return i; ++ } ++ // If we made it to the end, we want to finish counting to see if we have a full document. ++ switch (parser.buf[parser.structural_indexes[0]]) { ++ case '}': ++ obj_cnt--; ++ break; ++ case ']': ++ arr_cnt--; ++ break; ++ case '{': ++ obj_cnt++; ++ break; ++ case '[': ++ arr_cnt++; ++ break; ++ } ++ if (!arr_cnt && !obj_cnt) { ++ // We have a complete document. ++ return parser.n_structural_indexes; ++ } ++ return 0; ++} ++ ++} // unnamed namespace ++} // namespace westmere ++} // namespace simdjson ++/* end file src/generic/stage1/find_next_document_index.h */ ++ ++namespace simdjson { ++namespace westmere { ++namespace { ++namespace stage1 { ++ ++class bit_indexer { ++public: ++ uint32_t *tail; ++ ++ simdjson_inline bit_indexer(uint32_t *index_buf) : tail(index_buf) {} ++ ++ // flatten out values in 'bits' assuming that they are are to have values of idx ++ // plus their position in the bitvector, and store these indexes at ++ // base_ptr[base] incrementing base as we go ++ // will potentially store extra values beyond end of valid bits, so base_ptr ++ // needs to be large enough to handle this ++ // ++ // If the kernel sets SIMDJSON_CUSTOM_BIT_INDEXER, then it will provide its own ++ // version of the code. ++#ifdef SIMDJSON_CUSTOM_BIT_INDEXER ++ simdjson_inline void write(uint32_t idx, uint64_t bits); ++#else ++ simdjson_inline void write(uint32_t idx, uint64_t bits) { ++ // In some instances, the next branch is expensive because it is mispredicted. ++ // Unfortunately, in other cases, ++ // it helps tremendously. ++ if (bits == 0) ++ return; ++#if SIMDJSON_PREFER_REVERSE_BITS ++ /** ++ * ARM lacks a fast trailing zero instruction, but it has a fast ++ * bit reversal instruction and a fast leading zero instruction. ++ * Thus it may be profitable to reverse the bits (once) and then ++ * to rely on a sequence of instructions that call the leading ++ * zero instruction. ++ * ++ * Performance notes: ++ * The chosen routine is not optimal in terms of data dependency ++ * since zero_leading_bit might require two instructions. However, ++ * it tends to minimize the total number of instructions which is ++ * beneficial. ++ */ ++ ++ uint64_t rev_bits = reverse_bits(bits); ++ int cnt = static_cast(count_ones(bits)); ++ int i = 0; ++ // Do the first 8 all together ++ for (; i<8; i++) { ++ int lz = leading_zeroes(rev_bits); ++ this->tail[i] = static_cast(idx) + lz; ++ rev_bits = zero_leading_bit(rev_bits, lz); ++ } ++ // Do the next 8 all together (we hope in most cases it won't happen at all ++ // and the branch is easily predicted). ++ if (simdjson_unlikely(cnt > 8)) { ++ i = 8; ++ for (; i<16; i++) { ++ int lz = leading_zeroes(rev_bits); ++ this->tail[i] = static_cast(idx) + lz; ++ rev_bits = zero_leading_bit(rev_bits, lz); ++ } ++ ++ ++ // Most files don't have 16+ structurals per block, so we take several basically guaranteed ++ // branch mispredictions here. 16+ structurals per block means either punctuation ({} [] , :) ++ // or the start of a value ("abc" true 123) every four characters. ++ if (simdjson_unlikely(cnt > 16)) { ++ i = 16; ++ while (rev_bits != 0) { ++ int lz = leading_zeroes(rev_bits); ++ this->tail[i++] = static_cast(idx) + lz; ++ rev_bits = zero_leading_bit(rev_bits, lz); ++ } ++ } ++ } ++ this->tail += cnt; ++#else // SIMDJSON_PREFER_REVERSE_BITS ++ /** ++ * Under recent x64 systems, we often have both a fast trailing zero ++ * instruction and a fast 'clear-lower-bit' instruction so the following ++ * algorithm can be competitive. ++ */ ++ ++ int cnt = static_cast(count_ones(bits)); ++ // Do the first 8 all together ++ for (int i=0; i<8; i++) { ++ this->tail[i] = idx + trailing_zeroes(bits); ++ bits = clear_lowest_bit(bits); ++ } ++ ++ // Do the next 8 all together (we hope in most cases it won't happen at all ++ // and the branch is easily predicted). ++ if (simdjson_unlikely(cnt > 8)) { ++ for (int i=8; i<16; i++) { ++ this->tail[i] = idx + trailing_zeroes(bits); ++ bits = clear_lowest_bit(bits); ++ } ++ ++ // Most files don't have 16+ structurals per block, so we take several basically guaranteed ++ // branch mispredictions here. 16+ structurals per block means either punctuation ({} [] , :) ++ // or the start of a value ("abc" true 123) every four characters. ++ if (simdjson_unlikely(cnt > 16)) { ++ int i = 16; ++ do { ++ this->tail[i] = idx + trailing_zeroes(bits); ++ bits = clear_lowest_bit(bits); ++ i++; ++ } while (i < cnt); ++ } ++ } ++ ++ this->tail += cnt; ++#endif ++ } ++#endif // SIMDJSON_CUSTOM_BIT_INDEXER ++ ++}; ++ ++class json_structural_indexer { ++public: ++ /** ++ * Find the important bits of JSON in a 128-byte chunk, and add them to structural_indexes. ++ * ++ * @param partial Setting the partial parameter to true allows the find_structural_bits to ++ * tolerate unclosed strings. The caller should still ensure that the input is valid UTF-8. If ++ * you are processing substrings, you may want to call on a function like trimmed_length_safe_utf8. ++ */ ++ template ++ static error_code index(const uint8_t *buf, size_t len, dom_parser_implementation &parser, stage1_mode partial) noexcept; ++ ++private: ++ simdjson_inline json_structural_indexer(uint32_t *structural_indexes); ++ template ++ simdjson_inline void step(const uint8_t *block, buf_block_reader &reader) noexcept; ++ simdjson_inline void next(const simd::simd8x64& in, const json_block& block, size_t idx); ++ simdjson_inline error_code finish(dom_parser_implementation &parser, size_t idx, size_t len, stage1_mode partial); ++ ++ json_scanner scanner{}; ++ utf8_checker checker{}; ++ bit_indexer indexer; ++ uint64_t prev_structurals = 0; ++ uint64_t unescaped_chars_error = 0; ++}; ++ ++simdjson_inline json_structural_indexer::json_structural_indexer(uint32_t *structural_indexes) : indexer{structural_indexes} {} ++ ++// Skip the last character if it is partial ++simdjson_inline size_t trim_partial_utf8(const uint8_t *buf, size_t len) { ++ if (simdjson_unlikely(len < 3)) { ++ switch (len) { ++ case 2: ++ if (buf[len-1] >= 0xc0) { return len-1; } // 2-, 3- and 4-byte characters with only 1 byte left ++ if (buf[len-2] >= 0xe0) { return len-2; } // 3- and 4-byte characters with only 2 bytes left ++ return len; ++ case 1: ++ if (buf[len-1] >= 0xc0) { return len-1; } // 2-, 3- and 4-byte characters with only 1 byte left ++ return len; ++ case 0: ++ return len; ++ } ++ } ++ if (buf[len-1] >= 0xc0) { return len-1; } // 2-, 3- and 4-byte characters with only 1 byte left ++ if (buf[len-2] >= 0xe0) { return len-2; } // 3- and 4-byte characters with only 1 byte left ++ if (buf[len-3] >= 0xf0) { return len-3; } // 4-byte characters with only 3 bytes left ++ return len; ++} ++ ++// ++// PERF NOTES: ++// We pipe 2 inputs through these stages: ++// 1. Load JSON into registers. This takes a long time and is highly parallelizable, so we load ++// 2 inputs' worth at once so that by the time step 2 is looking for them input, it's available. ++// 2. Scan the JSON for critical data: strings, scalars and operators. This is the critical path. ++// The output of step 1 depends entirely on this information. These functions don't quite use ++// up enough CPU: the second half of the functions is highly serial, only using 1 execution core ++// at a time. The second input's scans has some dependency on the first ones finishing it, but ++// they can make a lot of progress before they need that information. ++// 3. Step 1 doesn't use enough capacity, so we run some extra stuff while we're waiting for that ++// to finish: utf-8 checks and generating the output from the last iteration. ++// ++// The reason we run 2 inputs at a time, is steps 2 and 3 are *still* not enough to soak up all ++// available capacity with just one input. Running 2 at a time seems to give the CPU a good enough ++// workout. ++// ++template ++error_code json_structural_indexer::index(const uint8_t *buf, size_t len, dom_parser_implementation &parser, stage1_mode partial) noexcept { ++ if (simdjson_unlikely(len > parser.capacity())) { return CAPACITY; } ++ // We guard the rest of the code so that we can assume that len > 0 throughout. ++ if (len == 0) { return EMPTY; } ++ if (is_streaming(partial)) { ++ len = trim_partial_utf8(buf, len); ++ // If you end up with an empty window after trimming ++ // the partial UTF-8 bytes, then chances are good that you ++ // have an UTF-8 formatting error. ++ if(len == 0) { return UTF8_ERROR; } ++ } ++ buf_block_reader reader(buf, len); ++ json_structural_indexer indexer(parser.structural_indexes.get()); ++ ++ // Read all but the last block ++ while (reader.has_full_block()) { ++ indexer.step(reader.full_block(), reader); ++ } ++ // Take care of the last block (will always be there unless file is empty which is ++ // not supposed to happen.) ++ uint8_t block[STEP_SIZE]; ++ if (simdjson_unlikely(reader.get_remainder(block) == 0)) { return UNEXPECTED_ERROR; } ++ indexer.step(block, reader); ++ return indexer.finish(parser, reader.block_index(), len, partial); ++} ++ ++template<> ++simdjson_inline void json_structural_indexer::step<128>(const uint8_t *block, buf_block_reader<128> &reader) noexcept { ++ simd::simd8x64 in_1(block); ++ simd::simd8x64 in_2(block+64); ++ json_block block_1 = scanner.next(in_1); ++ json_block block_2 = scanner.next(in_2); ++ this->next(in_1, block_1, reader.block_index()); ++ this->next(in_2, block_2, reader.block_index()+64); ++ reader.advance(); ++} ++ ++template<> ++simdjson_inline void json_structural_indexer::step<64>(const uint8_t *block, buf_block_reader<64> &reader) noexcept { ++ simd::simd8x64 in_1(block); ++ json_block block_1 = scanner.next(in_1); ++ this->next(in_1, block_1, reader.block_index()); ++ reader.advance(); ++} ++ ++simdjson_inline void json_structural_indexer::next(const simd::simd8x64& in, const json_block& block, size_t idx) { ++ uint64_t unescaped = in.lteq(0x1F); ++ checker.check_next_input(in); ++ indexer.write(uint32_t(idx-64), prev_structurals); // Output *last* iteration's structurals to the parser ++ prev_structurals = block.structural_start(); ++ unescaped_chars_error |= block.non_quote_inside_string(unescaped); ++} ++ ++simdjson_inline error_code json_structural_indexer::finish(dom_parser_implementation &parser, size_t idx, size_t len, stage1_mode partial) { ++ // Write out the final iteration's structurals ++ indexer.write(uint32_t(idx-64), prev_structurals); ++ error_code error = scanner.finish(); ++ // We deliberately break down the next expression so that it is ++ // human readable. ++ const bool should_we_exit = is_streaming(partial) ? ++ ((error != SUCCESS) && (error != UNCLOSED_STRING)) // when partial we tolerate UNCLOSED_STRING ++ : (error != SUCCESS); // if partial is false, we must have SUCCESS ++ const bool have_unclosed_string = (error == UNCLOSED_STRING); ++ if (simdjson_unlikely(should_we_exit)) { return error; } ++ ++ if (unescaped_chars_error) { ++ return UNESCAPED_CHARS; ++ } ++ parser.n_structural_indexes = uint32_t(indexer.tail - parser.structural_indexes.get()); ++ /*** ++ * The On Demand API requires special padding. ++ * ++ * This is related to https://github.com/simdjson/simdjson/issues/906 ++ * Basically, we want to make sure that if the parsing continues beyond the last (valid) ++ * structural character, it quickly stops. ++ * Only three structural characters can be repeated without triggering an error in JSON: [,] and }. ++ * We repeat the padding character (at 'len'). We don't know what it is, but if the parsing ++ * continues, then it must be [,] or }. ++ * Suppose it is ] or }. We backtrack to the first character, what could it be that would ++ * not trigger an error? It could be ] or } but no, because you can't start a document that way. ++ * It can't be a comma, a colon or any simple value. So the only way we could continue is ++ * if the repeated character is [. But if so, the document must start with [. But if the document ++ * starts with [, it should end with ]. If we enforce that rule, then we would get ++ * ][[ which is invalid. ++ * ++ * This is illustrated with the test array_iterate_unclosed_error() on the following input: ++ * R"({ "a": [,,)" ++ **/ ++ parser.structural_indexes[parser.n_structural_indexes] = uint32_t(len); // used later in partial == stage1_mode::streaming_final ++ parser.structural_indexes[parser.n_structural_indexes + 1] = uint32_t(len); ++ parser.structural_indexes[parser.n_structural_indexes + 2] = 0; ++ parser.next_structural_index = 0; ++ // a valid JSON file cannot have zero structural indexes - we should have found something ++ if (simdjson_unlikely(parser.n_structural_indexes == 0u)) { ++ return EMPTY; ++ } ++ if (simdjson_unlikely(parser.structural_indexes[parser.n_structural_indexes - 1] > len)) { ++ return UNEXPECTED_ERROR; ++ } ++ if (partial == stage1_mode::streaming_partial) { ++ // If we have an unclosed string, then the last structural ++ // will be the quote and we want to make sure to omit it. ++ if(have_unclosed_string) { ++ parser.n_structural_indexes--; ++ // a valid JSON file cannot have zero structural indexes - we should have found something ++ if (simdjson_unlikely(parser.n_structural_indexes == 0u)) { return CAPACITY; } ++ } ++ // We truncate the input to the end of the last complete document (or zero). ++ auto new_structural_indexes = find_next_document_index(parser); ++ if (new_structural_indexes == 0 && parser.n_structural_indexes > 0) { ++ if(parser.structural_indexes[0] == 0) { ++ // If the buffer is partial and we started at index 0 but the document is ++ // incomplete, it's too big to parse. ++ return CAPACITY; ++ } else { ++ // It is possible that the document could be parsed, we just had a lot ++ // of white space. ++ parser.n_structural_indexes = 0; ++ return EMPTY; ++ } ++ } ++ ++ parser.n_structural_indexes = new_structural_indexes; ++ } else if (partial == stage1_mode::streaming_final) { ++ if(have_unclosed_string) { parser.n_structural_indexes--; } ++ // We truncate the input to the end of the last complete document (or zero). ++ // Because partial == stage1_mode::streaming_final, it means that we may ++ // silently ignore trailing garbage. Though it sounds bad, we do it ++ // deliberately because many people who have streams of JSON documents ++ // will truncate them for processing. E.g., imagine that you are uncompressing ++ // the data from a size file or receiving it in chunks from the network. You ++ // may not know where exactly the last document will be. Meanwhile the ++ // document_stream instances allow people to know the JSON documents they are ++ // parsing (see the iterator.source() method). ++ parser.n_structural_indexes = find_next_document_index(parser); ++ // We store the initial n_structural_indexes so that the client can see ++ // whether we used truncation. If initial_n_structural_indexes == parser.n_structural_indexes, ++ // then this will query parser.structural_indexes[parser.n_structural_indexes] which is len, ++ // otherwise, it will copy some prior index. ++ parser.structural_indexes[parser.n_structural_indexes + 1] = parser.structural_indexes[parser.n_structural_indexes]; ++ // This next line is critical, do not change it unless you understand what you are ++ // doing. ++ parser.structural_indexes[parser.n_structural_indexes] = uint32_t(len); ++ if (simdjson_unlikely(parser.n_structural_indexes == 0u)) { ++ // We tolerate an unclosed string at the very end of the stream. Indeed, users ++ // often load their data in bulk without being careful and they want us to ignore ++ // the trailing garbage. ++ return EMPTY; ++ } ++ } ++ checker.check_eof(); ++ return checker.errors(); ++} ++ ++} // namespace stage1 ++} // unnamed namespace ++} // namespace westmere ++} // namespace simdjson ++/* end file src/generic/stage1/json_structural_indexer.h */ ++/* begin file src/generic/stage1/utf8_validator.h */ ++namespace simdjson { ++namespace westmere { ++namespace { ++namespace stage1 { ++ ++/** ++ * Validates that the string is actual UTF-8. ++ */ ++template ++bool generic_validate_utf8(const uint8_t * input, size_t length) { ++ checker c{}; ++ buf_block_reader<64> reader(input, length); ++ while (reader.has_full_block()) { ++ simd::simd8x64 in(reader.full_block()); ++ c.check_next_input(in); ++ reader.advance(); ++ } ++ uint8_t block[64]{}; ++ reader.get_remainder(block); ++ simd::simd8x64 in(block); ++ c.check_next_input(in); ++ reader.advance(); ++ c.check_eof(); ++ return c.errors() == error_code::SUCCESS; ++} ++ ++bool generic_validate_utf8(const char * input, size_t length) { ++ return generic_validate_utf8(reinterpret_cast(input),length); ++} ++ ++} // namespace stage1 ++} // unnamed namespace ++} // namespace westmere ++} // namespace simdjson ++/* end file src/generic/stage1/utf8_validator.h */ ++ ++// ++// Stage 2 ++// ++/* begin file src/generic/stage2/stringparsing.h */ ++// This file contains the common code every implementation uses ++// It is intended to be included multiple times and compiled multiple times ++ ++namespace simdjson { ++namespace westmere { ++namespace { ++/// @private ++namespace stringparsing { ++ ++// begin copypasta ++// These chars yield themselves: " \ / ++// b -> backspace, f -> formfeed, n -> newline, r -> cr, t -> horizontal tab ++// u not handled in this table as it's complex ++static const uint8_t escape_map[256] = { ++ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x0. ++ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ++ 0, 0, 0x22, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x2f, ++ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ++ ++ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x4. ++ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x5c, 0, 0, 0, // 0x5. ++ 0, 0, 0x08, 0, 0, 0, 0x0c, 0, 0, 0, 0, 0, 0, 0, 0x0a, 0, // 0x6. ++ 0, 0, 0x0d, 0, 0x09, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x7. ++ ++ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ++ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ++ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ++ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ++ ++ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ++ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ++ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ++ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ++}; ++ ++// handle a unicode codepoint ++// write appropriate values into dest ++// src will advance 6 bytes or 12 bytes ++// dest will advance a variable amount (return via pointer) ++// return true if the unicode codepoint was valid ++// We work in little-endian then swap at write time ++simdjson_warn_unused ++simdjson_inline bool handle_unicode_codepoint(const uint8_t **src_ptr, ++ uint8_t **dst_ptr) { ++ // jsoncharutils::hex_to_u32_nocheck fills high 16 bits of the return value with 1s if the ++ // conversion isn't valid; we defer the check for this to inside the ++ // multilingual plane check ++ uint32_t code_point = jsoncharutils::hex_to_u32_nocheck(*src_ptr + 2); ++ *src_ptr += 6; ++ ++ // If we found a high surrogate, we must ++ // check for low surrogate for characters ++ // outside the Basic ++ // Multilingual Plane. ++ if (code_point >= 0xd800 && code_point < 0xdc00) { ++ const uint8_t *src_data = *src_ptr; ++ /* Compiler optimizations convert this to a single 16-bit load and compare on most platforms */ ++ if (((src_data[0] << 8) | src_data[1]) != ((static_cast ('\\') << 8) | static_cast ('u'))) { ++ return false; ++ } ++ uint32_t code_point_2 = jsoncharutils::hex_to_u32_nocheck(src_data + 2); ++ ++ // We have already checked that the high surrogate is valid and ++ // (code_point - 0xd800) < 1024. ++ // ++ // Check that code_point_2 is in the range 0xdc00..0xdfff ++ // and that code_point_2 was parsed from valid hex. ++ uint32_t low_bit = code_point_2 - 0xdc00; ++ if (low_bit >> 10) { ++ return false; ++ } ++ ++ code_point = ++ (((code_point - 0xd800) << 10) | low_bit) + 0x10000; ++ *src_ptr += 6; ++ } else if (code_point >= 0xdc00 && code_point <= 0xdfff) { ++ // If we encounter a low surrogate (not preceded by a high surrogate) ++ // then we have an error. ++ return false; ++ } ++ size_t offset = jsoncharutils::codepoint_to_utf8(code_point, *dst_ptr); ++ *dst_ptr += offset; ++ return offset > 0; ++} ++ ++/** ++ * Unescape a valid UTF-8 string from src to dst, stopping at a final unescaped quote. There ++ * must be an unescaped quote terminating the string. It returns the final output ++ * position as pointer. In case of error (e.g., the string has bad escaped codes), ++ * then null_nullptrptr is returned. It is assumed that the output buffer is large ++ * enough. E.g., if src points at 'joe"', then dst needs to have four free bytes + ++ * SIMDJSON_PADDING bytes. ++ */ ++simdjson_warn_unused simdjson_inline uint8_t *parse_string(const uint8_t *src, uint8_t *dst) { ++ while (1) { ++ // Copy the next n bytes, and find the backslash and quote in them. ++ auto bs_quote = backslash_and_quote::copy_and_find(src, dst); ++ // If the next thing is the end quote, copy and return ++ if (bs_quote.has_quote_first()) { ++ // we encountered quotes first. Move dst to point to quotes and exit ++ return dst + bs_quote.quote_index(); ++ } ++ if (bs_quote.has_backslash()) { ++ /* find out where the backspace is */ ++ auto bs_dist = bs_quote.backslash_index(); ++ uint8_t escape_char = src[bs_dist + 1]; ++ /* we encountered backslash first. Handle backslash */ ++ if (escape_char == 'u') { ++ /* move src/dst up to the start; they will be further adjusted ++ within the unicode codepoint handling code. */ ++ src += bs_dist; ++ dst += bs_dist; ++ if (!handle_unicode_codepoint(&src, &dst)) { ++ return nullptr; ++ } ++ } else { ++ /* simple 1:1 conversion. Will eat bs_dist+2 characters in input and ++ * write bs_dist+1 characters to output ++ * note this may reach beyond the part of the buffer we've actually ++ * seen. I think this is ok */ ++ uint8_t escape_result = escape_map[escape_char]; ++ if (escape_result == 0u) { ++ return nullptr; /* bogus escape value is an error */ ++ } ++ dst[bs_dist] = escape_result; ++ src += bs_dist + 2; ++ dst += bs_dist + 1; ++ } ++ } else { ++ /* they are the same. Since they can't co-occur, it means we ++ * encountered neither. */ ++ src += backslash_and_quote::BYTES_PROCESSED; ++ dst += backslash_and_quote::BYTES_PROCESSED; ++ } ++ } ++ /* can't be reached */ ++ return nullptr; ++} ++ ++} // namespace stringparsing ++} // unnamed namespace ++} // namespace westmere ++} // namespace simdjson ++/* end file src/generic/stage2/stringparsing.h */ ++/* begin file src/generic/stage2/tape_builder.h */ ++/* begin file src/generic/stage2/json_iterator.h */ ++/* begin file src/generic/stage2/logger.h */ ++// This is for an internal-only stage 2 specific logger. ++// Set LOG_ENABLED = true to log what stage 2 is doing! ++namespace simdjson { ++namespace westmere { ++namespace { ++namespace logger { ++ ++ static constexpr const char * DASHES = "----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------"; ++ ++#if SIMDJSON_VERBOSE_LOGGING ++ static constexpr const bool LOG_ENABLED = true; ++#else ++ static constexpr const bool LOG_ENABLED = false; ++#endif ++ static constexpr const int LOG_EVENT_LEN = 20; ++ static constexpr const int LOG_BUFFER_LEN = 30; ++ static constexpr const int LOG_SMALL_BUFFER_LEN = 10; ++ static constexpr const int LOG_INDEX_LEN = 5; ++ ++ static int log_depth; // Not threadsafe. Log only. ++ ++ // Helper to turn unprintable or newline characters into spaces ++ static simdjson_inline char printable_char(char c) { ++ if (c >= 0x20) { ++ return c; ++ } else { ++ return ' '; ++ } ++ } ++ ++ // Print the header and set up log_start ++ static simdjson_inline void log_start() { ++ if (LOG_ENABLED) { ++ log_depth = 0; ++ printf("\n"); ++ printf("| %-*s | %-*s | %-*s | %-*s | Detail |\n", LOG_EVENT_LEN, "Event", LOG_BUFFER_LEN, "Buffer", LOG_SMALL_BUFFER_LEN, "Next", 5, "Next#"); ++ printf("|%.*s|%.*s|%.*s|%.*s|--------|\n", LOG_EVENT_LEN+2, DASHES, LOG_BUFFER_LEN+2, DASHES, LOG_SMALL_BUFFER_LEN+2, DASHES, 5+2, DASHES); ++ } ++ } ++ ++ simdjson_unused static simdjson_inline void log_string(const char *message) { ++ if (LOG_ENABLED) { ++ printf("%s\n", message); ++ } ++ } ++ ++ // Logs a single line from the stage 2 DOM parser ++ template ++ static simdjson_inline void log_line(S &structurals, const char *title_prefix, const char *title, const char *detail) { ++ if (LOG_ENABLED) { ++ printf("| %*s%s%-*s ", log_depth*2, "", title_prefix, LOG_EVENT_LEN - log_depth*2 - int(strlen(title_prefix)), title); ++ auto current_index = structurals.at_beginning() ? nullptr : structurals.next_structural-1; ++ auto next_index = structurals.next_structural; ++ auto current = current_index ? &structurals.buf[*current_index] : reinterpret_cast(" "); ++ auto next = &structurals.buf[*next_index]; ++ { ++ // Print the next N characters in the buffer. ++ printf("| "); ++ // Otherwise, print the characters starting from the buffer position. ++ // Print spaces for unprintable or newline characters. ++ for (int i=0;i ++ simdjson_warn_unused simdjson_inline error_code walk_document(V &visitor) noexcept; ++ ++ /** ++ * Create an iterator capable of walking a JSON document. ++ * ++ * The document must have already passed through stage 1. ++ */ ++ simdjson_inline json_iterator(dom_parser_implementation &_dom_parser, size_t start_structural_index); ++ ++ /** ++ * Look at the next token. ++ * ++ * Tokens can be strings, numbers, booleans, null, or operators (`[{]},:`)). ++ * ++ * They may include invalid JSON as well (such as `1.2.3` or `ture`). ++ */ ++ simdjson_inline const uint8_t *peek() const noexcept; ++ /** ++ * Advance to the next token. ++ * ++ * Tokens can be strings, numbers, booleans, null, or operators (`[{]},:`)). ++ * ++ * They may include invalid JSON as well (such as `1.2.3` or `ture`). ++ */ ++ simdjson_inline const uint8_t *advance() noexcept; ++ /** ++ * Get the remaining length of the document, from the start of the current token. ++ */ ++ simdjson_inline size_t remaining_len() const noexcept; ++ /** ++ * Check if we are at the end of the document. ++ * ++ * If this is true, there are no more tokens. ++ */ ++ simdjson_inline bool at_eof() const noexcept; ++ /** ++ * Check if we are at the beginning of the document. ++ */ ++ simdjson_inline bool at_beginning() const noexcept; ++ simdjson_inline uint8_t last_structural() const noexcept; ++ ++ /** ++ * Log that a value has been found. ++ * ++ * Set LOG_ENABLED=true in logger.h to see logging. ++ */ ++ simdjson_inline void log_value(const char *type) const noexcept; ++ /** ++ * Log the start of a multipart value. ++ * ++ * Set LOG_ENABLED=true in logger.h to see logging. ++ */ ++ simdjson_inline void log_start_value(const char *type) const noexcept; ++ /** ++ * Log the end of a multipart value. ++ * ++ * Set LOG_ENABLED=true in logger.h to see logging. ++ */ ++ simdjson_inline void log_end_value(const char *type) const noexcept; ++ /** ++ * Log an error. ++ * ++ * Set LOG_ENABLED=true in logger.h to see logging. ++ */ ++ simdjson_inline void log_error(const char *error) const noexcept; ++ ++ template ++ simdjson_warn_unused simdjson_inline error_code visit_root_primitive(V &visitor, const uint8_t *value) noexcept; ++ template ++ simdjson_warn_unused simdjson_inline error_code visit_primitive(V &visitor, const uint8_t *value) noexcept; ++}; ++ ++template ++simdjson_warn_unused simdjson_inline error_code json_iterator::walk_document(V &visitor) noexcept { ++ logger::log_start(); ++ ++ // ++ // Start the document ++ // ++ if (at_eof()) { return EMPTY; } ++ log_start_value("document"); ++ SIMDJSON_TRY( visitor.visit_document_start(*this) ); ++ ++ // ++ // Read first value ++ // ++ { ++ auto value = advance(); ++ ++ // Make sure the outer object or array is closed before continuing; otherwise, there are ways we ++ // could get into memory corruption. See https://github.com/simdjson/simdjson/issues/906 ++ if (!STREAMING) { ++ switch (*value) { ++ case '{': if (last_structural() != '}') { log_value("starting brace unmatched"); return TAPE_ERROR; }; break; ++ case '[': if (last_structural() != ']') { log_value("starting bracket unmatched"); return TAPE_ERROR; }; break; ++ } ++ } ++ ++ switch (*value) { ++ case '{': if (*peek() == '}') { advance(); log_value("empty object"); SIMDJSON_TRY( visitor.visit_empty_object(*this) ); break; } goto object_begin; ++ case '[': if (*peek() == ']') { advance(); log_value("empty array"); SIMDJSON_TRY( visitor.visit_empty_array(*this) ); break; } goto array_begin; ++ default: SIMDJSON_TRY( visitor.visit_root_primitive(*this, value) ); break; ++ } ++ } ++ goto document_end; ++ ++// ++// Object parser states ++// ++object_begin: ++ log_start_value("object"); ++ depth++; ++ if (depth >= dom_parser.max_depth()) { log_error("Exceeded max depth!"); return DEPTH_ERROR; } ++ dom_parser.is_array[depth] = false; ++ SIMDJSON_TRY( visitor.visit_object_start(*this) ); ++ ++ { ++ auto key = advance(); ++ if (*key != '"') { log_error("Object does not start with a key"); return TAPE_ERROR; } ++ SIMDJSON_TRY( visitor.increment_count(*this) ); ++ SIMDJSON_TRY( visitor.visit_key(*this, key) ); ++ } ++ ++object_field: ++ if (simdjson_unlikely( *advance() != ':' )) { log_error("Missing colon after key in object"); return TAPE_ERROR; } ++ { ++ auto value = advance(); ++ switch (*value) { ++ case '{': if (*peek() == '}') { advance(); log_value("empty object"); SIMDJSON_TRY( visitor.visit_empty_object(*this) ); break; } goto object_begin; ++ case '[': if (*peek() == ']') { advance(); log_value("empty array"); SIMDJSON_TRY( visitor.visit_empty_array(*this) ); break; } goto array_begin; ++ default: SIMDJSON_TRY( visitor.visit_primitive(*this, value) ); break; ++ } ++ } ++ ++object_continue: ++ switch (*advance()) { ++ case ',': ++ SIMDJSON_TRY( visitor.increment_count(*this) ); ++ { ++ auto key = advance(); ++ if (simdjson_unlikely( *key != '"' )) { log_error("Key string missing at beginning of field in object"); return TAPE_ERROR; } ++ SIMDJSON_TRY( visitor.visit_key(*this, key) ); ++ } ++ goto object_field; ++ case '}': log_end_value("object"); SIMDJSON_TRY( visitor.visit_object_end(*this) ); goto scope_end; ++ default: log_error("No comma between object fields"); return TAPE_ERROR; ++ } ++ ++scope_end: ++ depth--; ++ if (depth == 0) { goto document_end; } ++ if (dom_parser.is_array[depth]) { goto array_continue; } ++ goto object_continue; ++ ++// ++// Array parser states ++// ++array_begin: ++ log_start_value("array"); ++ depth++; ++ if (depth >= dom_parser.max_depth()) { log_error("Exceeded max depth!"); return DEPTH_ERROR; } ++ dom_parser.is_array[depth] = true; ++ SIMDJSON_TRY( visitor.visit_array_start(*this) ); ++ SIMDJSON_TRY( visitor.increment_count(*this) ); ++ ++array_value: ++ { ++ auto value = advance(); ++ switch (*value) { ++ case '{': if (*peek() == '}') { advance(); log_value("empty object"); SIMDJSON_TRY( visitor.visit_empty_object(*this) ); break; } goto object_begin; ++ case '[': if (*peek() == ']') { advance(); log_value("empty array"); SIMDJSON_TRY( visitor.visit_empty_array(*this) ); break; } goto array_begin; ++ default: SIMDJSON_TRY( visitor.visit_primitive(*this, value) ); break; ++ } ++ } ++ ++array_continue: ++ switch (*advance()) { ++ case ',': SIMDJSON_TRY( visitor.increment_count(*this) ); goto array_value; ++ case ']': log_end_value("array"); SIMDJSON_TRY( visitor.visit_array_end(*this) ); goto scope_end; ++ default: log_error("Missing comma between array values"); return TAPE_ERROR; ++ } ++ ++document_end: ++ log_end_value("document"); ++ SIMDJSON_TRY( visitor.visit_document_end(*this) ); ++ ++ dom_parser.next_structural_index = uint32_t(next_structural - &dom_parser.structural_indexes[0]); ++ ++ // If we didn't make it to the end, it's an error ++ if ( !STREAMING && dom_parser.next_structural_index != dom_parser.n_structural_indexes ) { ++ log_error("More than one JSON value at the root of the document, or extra characters at the end of the JSON!"); ++ return TAPE_ERROR; ++ } ++ ++ return SUCCESS; ++ ++} // walk_document() ++ ++simdjson_inline json_iterator::json_iterator(dom_parser_implementation &_dom_parser, size_t start_structural_index) ++ : buf{_dom_parser.buf}, ++ next_structural{&_dom_parser.structural_indexes[start_structural_index]}, ++ dom_parser{_dom_parser} { ++} ++ ++simdjson_inline const uint8_t *json_iterator::peek() const noexcept { ++ return &buf[*(next_structural)]; ++} ++simdjson_inline const uint8_t *json_iterator::advance() noexcept { ++ return &buf[*(next_structural++)]; ++} ++simdjson_inline size_t json_iterator::remaining_len() const noexcept { ++ return dom_parser.len - *(next_structural-1); ++} ++ ++simdjson_inline bool json_iterator::at_eof() const noexcept { ++ return next_structural == &dom_parser.structural_indexes[dom_parser.n_structural_indexes]; ++} ++simdjson_inline bool json_iterator::at_beginning() const noexcept { ++ return next_structural == dom_parser.structural_indexes.get(); ++} ++simdjson_inline uint8_t json_iterator::last_structural() const noexcept { ++ return buf[dom_parser.structural_indexes[dom_parser.n_structural_indexes - 1]]; ++} ++ ++simdjson_inline void json_iterator::log_value(const char *type) const noexcept { ++ logger::log_line(*this, "", type, ""); ++} ++ ++simdjson_inline void json_iterator::log_start_value(const char *type) const noexcept { ++ logger::log_line(*this, "+", type, ""); ++ if (logger::LOG_ENABLED) { logger::log_depth++; } ++} ++ ++simdjson_inline void json_iterator::log_end_value(const char *type) const noexcept { ++ if (logger::LOG_ENABLED) { logger::log_depth--; } ++ logger::log_line(*this, "-", type, ""); ++} ++ ++simdjson_inline void json_iterator::log_error(const char *error) const noexcept { ++ logger::log_line(*this, "", "ERROR", error); ++} ++ ++template ++simdjson_warn_unused simdjson_inline error_code json_iterator::visit_root_primitive(V &visitor, const uint8_t *value) noexcept { ++ switch (*value) { ++ case '"': return visitor.visit_root_string(*this, value); ++ case 't': return visitor.visit_root_true_atom(*this, value); ++ case 'f': return visitor.visit_root_false_atom(*this, value); ++ case 'n': return visitor.visit_root_null_atom(*this, value); ++ case '-': ++ case '0': case '1': case '2': case '3': case '4': ++ case '5': case '6': case '7': case '8': case '9': ++ return visitor.visit_root_number(*this, value); ++ default: ++ log_error("Document starts with a non-value character"); ++ return TAPE_ERROR; ++ } ++} ++template ++simdjson_warn_unused simdjson_inline error_code json_iterator::visit_primitive(V &visitor, const uint8_t *value) noexcept { ++ switch (*value) { ++ case '"': return visitor.visit_string(*this, value); ++ case 't': return visitor.visit_true_atom(*this, value); ++ case 'f': return visitor.visit_false_atom(*this, value); ++ case 'n': return visitor.visit_null_atom(*this, value); ++ case '-': ++ case '0': case '1': case '2': case '3': case '4': ++ case '5': case '6': case '7': case '8': case '9': ++ return visitor.visit_number(*this, value); ++ default: ++ log_error("Non-value found when value was expected!"); ++ return TAPE_ERROR; ++ } ++} ++ ++} // namespace stage2 ++} // unnamed namespace ++} // namespace westmere ++} // namespace simdjson ++/* end file src/generic/stage2/json_iterator.h */ ++/* begin file src/generic/stage2/tape_writer.h */ ++namespace simdjson { ++namespace westmere { ++namespace { ++namespace stage2 { ++ ++struct tape_writer { ++ /** The next place to write to tape */ ++ uint64_t *next_tape_loc; ++ ++ /** Write a signed 64-bit value to tape. */ ++ simdjson_inline void append_s64(int64_t value) noexcept; ++ ++ /** Write an unsigned 64-bit value to tape. */ ++ simdjson_inline void append_u64(uint64_t value) noexcept; ++ ++ /** Write a double value to tape. */ ++ simdjson_inline void append_double(double value) noexcept; ++ ++ /** ++ * Append a tape entry (an 8-bit type,and 56 bits worth of value). ++ */ ++ simdjson_inline void append(uint64_t val, internal::tape_type t) noexcept; ++ ++ /** ++ * Skip the current tape entry without writing. ++ * ++ * Used to skip the start of the container, since we'll come back later to fill it in when the ++ * container ends. ++ */ ++ simdjson_inline void skip() noexcept; ++ ++ /** ++ * Skip the number of tape entries necessary to write a large u64 or i64. ++ */ ++ simdjson_inline void skip_large_integer() noexcept; ++ ++ /** ++ * Skip the number of tape entries necessary to write a double. ++ */ ++ simdjson_inline void skip_double() noexcept; ++ ++ /** ++ * Write a value to a known location on tape. ++ * ++ * Used to go back and write out the start of a container after the container ends. ++ */ ++ simdjson_inline static void write(uint64_t &tape_loc, uint64_t val, internal::tape_type t) noexcept; ++ ++private: ++ /** ++ * Append both the tape entry, and a supplementary value following it. Used for types that need ++ * all 64 bits, such as double and uint64_t. ++ */ ++ template ++ simdjson_inline void append2(uint64_t val, T val2, internal::tape_type t) noexcept; ++}; // struct number_writer ++ ++simdjson_inline void tape_writer::append_s64(int64_t value) noexcept { ++ append2(0, value, internal::tape_type::INT64); ++} ++ ++simdjson_inline void tape_writer::append_u64(uint64_t value) noexcept { ++ append(0, internal::tape_type::UINT64); ++ *next_tape_loc = value; ++ next_tape_loc++; ++} ++ ++/** Write a double value to tape. */ ++simdjson_inline void tape_writer::append_double(double value) noexcept { ++ append2(0, value, internal::tape_type::DOUBLE); ++} ++ ++simdjson_inline void tape_writer::skip() noexcept { ++ next_tape_loc++; ++} ++ ++simdjson_inline void tape_writer::skip_large_integer() noexcept { ++ next_tape_loc += 2; ++} ++ ++simdjson_inline void tape_writer::skip_double() noexcept { ++ next_tape_loc += 2; ++} ++ ++simdjson_inline void tape_writer::append(uint64_t val, internal::tape_type t) noexcept { ++ *next_tape_loc = val | ((uint64_t(char(t))) << 56); ++ next_tape_loc++; ++} ++ ++template ++simdjson_inline void tape_writer::append2(uint64_t val, T val2, internal::tape_type t) noexcept { ++ append(val, t); ++ static_assert(sizeof(val2) == sizeof(*next_tape_loc), "Type is not 64 bits!"); ++ memcpy(next_tape_loc, &val2, sizeof(val2)); ++ next_tape_loc++; ++} ++ ++simdjson_inline void tape_writer::write(uint64_t &tape_loc, uint64_t val, internal::tape_type t) noexcept { ++ tape_loc = val | ((uint64_t(char(t))) << 56); ++} ++ ++} // namespace stage2 ++} // unnamed namespace ++} // namespace westmere ++} // namespace simdjson ++/* end file src/generic/stage2/tape_writer.h */ ++ ++namespace simdjson { ++namespace westmere { ++namespace { ++namespace stage2 { ++ ++struct tape_builder { ++ template ++ simdjson_warn_unused static simdjson_inline error_code parse_document( ++ dom_parser_implementation &dom_parser, ++ dom::document &doc) noexcept; ++ ++ /** Called when a non-empty document starts. */ ++ simdjson_warn_unused simdjson_inline error_code visit_document_start(json_iterator &iter) noexcept; ++ /** Called when a non-empty document ends without error. */ ++ simdjson_warn_unused simdjson_inline error_code visit_document_end(json_iterator &iter) noexcept; ++ ++ /** Called when a non-empty array starts. */ ++ simdjson_warn_unused simdjson_inline error_code visit_array_start(json_iterator &iter) noexcept; ++ /** Called when a non-empty array ends. */ ++ simdjson_warn_unused simdjson_inline error_code visit_array_end(json_iterator &iter) noexcept; ++ /** Called when an empty array is found. */ ++ simdjson_warn_unused simdjson_inline error_code visit_empty_array(json_iterator &iter) noexcept; ++ ++ /** Called when a non-empty object starts. */ ++ simdjson_warn_unused simdjson_inline error_code visit_object_start(json_iterator &iter) noexcept; ++ /** ++ * Called when a key in a field is encountered. ++ * ++ * primitive, visit_object_start, visit_empty_object, visit_array_start, or visit_empty_array ++ * will be called after this with the field value. ++ */ ++ simdjson_warn_unused simdjson_inline error_code visit_key(json_iterator &iter, const uint8_t *key) noexcept; ++ /** Called when a non-empty object ends. */ ++ simdjson_warn_unused simdjson_inline error_code visit_object_end(json_iterator &iter) noexcept; ++ /** Called when an empty object is found. */ ++ simdjson_warn_unused simdjson_inline error_code visit_empty_object(json_iterator &iter) noexcept; ++ ++ /** ++ * Called when a string, number, boolean or null is found. ++ */ ++ simdjson_warn_unused simdjson_inline error_code visit_primitive(json_iterator &iter, const uint8_t *value) noexcept; ++ /** ++ * Called when a string, number, boolean or null is found at the top level of a document (i.e. ++ * when there is no array or object and the entire document is a single string, number, boolean or ++ * null. ++ * ++ * This is separate from primitive() because simdjson's normal primitive parsing routines assume ++ * there is at least one more token after the value, which is only true in an array or object. ++ */ ++ simdjson_warn_unused simdjson_inline error_code visit_root_primitive(json_iterator &iter, const uint8_t *value) noexcept; ++ ++ simdjson_warn_unused simdjson_inline error_code visit_string(json_iterator &iter, const uint8_t *value, bool key = false) noexcept; ++ simdjson_warn_unused simdjson_inline error_code visit_number(json_iterator &iter, const uint8_t *value) noexcept; ++ simdjson_warn_unused simdjson_inline error_code visit_true_atom(json_iterator &iter, const uint8_t *value) noexcept; ++ simdjson_warn_unused simdjson_inline error_code visit_false_atom(json_iterator &iter, const uint8_t *value) noexcept; ++ simdjson_warn_unused simdjson_inline error_code visit_null_atom(json_iterator &iter, const uint8_t *value) noexcept; ++ ++ simdjson_warn_unused simdjson_inline error_code visit_root_string(json_iterator &iter, const uint8_t *value) noexcept; ++ simdjson_warn_unused simdjson_inline error_code visit_root_number(json_iterator &iter, const uint8_t *value) noexcept; ++ simdjson_warn_unused simdjson_inline error_code visit_root_true_atom(json_iterator &iter, const uint8_t *value) noexcept; ++ simdjson_warn_unused simdjson_inline error_code visit_root_false_atom(json_iterator &iter, const uint8_t *value) noexcept; ++ simdjson_warn_unused simdjson_inline error_code visit_root_null_atom(json_iterator &iter, const uint8_t *value) noexcept; ++ ++ /** Called each time a new field or element in an array or object is found. */ ++ simdjson_warn_unused simdjson_inline error_code increment_count(json_iterator &iter) noexcept; ++ ++ /** Next location to write to tape */ ++ tape_writer tape; ++private: ++ /** Next write location in the string buf for stage 2 parsing */ ++ uint8_t *current_string_buf_loc; ++ ++ simdjson_inline tape_builder(dom::document &doc) noexcept; ++ ++ simdjson_inline uint32_t next_tape_index(json_iterator &iter) const noexcept; ++ simdjson_inline void start_container(json_iterator &iter) noexcept; ++ simdjson_warn_unused simdjson_inline error_code end_container(json_iterator &iter, internal::tape_type start, internal::tape_type end) noexcept; ++ simdjson_warn_unused simdjson_inline error_code empty_container(json_iterator &iter, internal::tape_type start, internal::tape_type end) noexcept; ++ simdjson_inline uint8_t *on_start_string(json_iterator &iter) noexcept; ++ simdjson_inline void on_end_string(uint8_t *dst) noexcept; ++}; // class tape_builder ++ ++template ++simdjson_warn_unused simdjson_inline error_code tape_builder::parse_document( ++ dom_parser_implementation &dom_parser, ++ dom::document &doc) noexcept { ++ dom_parser.doc = &doc; ++ json_iterator iter(dom_parser, STREAMING ? dom_parser.next_structural_index : 0); ++ tape_builder builder(doc); ++ return iter.walk_document(builder); ++} ++ ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_root_primitive(json_iterator &iter, const uint8_t *value) noexcept { ++ return iter.visit_root_primitive(*this, value); ++} ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_primitive(json_iterator &iter, const uint8_t *value) noexcept { ++ return iter.visit_primitive(*this, value); ++} ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_empty_object(json_iterator &iter) noexcept { ++ return empty_container(iter, internal::tape_type::START_OBJECT, internal::tape_type::END_OBJECT); ++} ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_empty_array(json_iterator &iter) noexcept { ++ return empty_container(iter, internal::tape_type::START_ARRAY, internal::tape_type::END_ARRAY); ++} ++ ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_document_start(json_iterator &iter) noexcept { ++ start_container(iter); ++ return SUCCESS; ++} ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_object_start(json_iterator &iter) noexcept { ++ start_container(iter); ++ return SUCCESS; ++} ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_array_start(json_iterator &iter) noexcept { ++ start_container(iter); ++ return SUCCESS; ++} ++ ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_object_end(json_iterator &iter) noexcept { ++ return end_container(iter, internal::tape_type::START_OBJECT, internal::tape_type::END_OBJECT); ++} ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_array_end(json_iterator &iter) noexcept { ++ return end_container(iter, internal::tape_type::START_ARRAY, internal::tape_type::END_ARRAY); ++} ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_document_end(json_iterator &iter) noexcept { ++ constexpr uint32_t start_tape_index = 0; ++ tape.append(start_tape_index, internal::tape_type::ROOT); ++ tape_writer::write(iter.dom_parser.doc->tape[start_tape_index], next_tape_index(iter), internal::tape_type::ROOT); ++ return SUCCESS; ++} ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_key(json_iterator &iter, const uint8_t *key) noexcept { ++ return visit_string(iter, key, true); ++} ++ ++simdjson_warn_unused simdjson_inline error_code tape_builder::increment_count(json_iterator &iter) noexcept { ++ iter.dom_parser.open_containers[iter.depth].count++; // we have a key value pair in the object at parser.dom_parser.depth - 1 ++ return SUCCESS; ++} ++ ++simdjson_inline tape_builder::tape_builder(dom::document &doc) noexcept : tape{doc.tape.get()}, current_string_buf_loc{doc.string_buf.get()} {} ++ ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_string(json_iterator &iter, const uint8_t *value, bool key) noexcept { ++ iter.log_value(key ? "key" : "string"); ++ uint8_t *dst = on_start_string(iter); ++ dst = stringparsing::parse_string(value+1, dst); ++ if (dst == nullptr) { ++ iter.log_error("Invalid escape in string"); ++ return STRING_ERROR; ++ } ++ on_end_string(dst); ++ return SUCCESS; ++} ++ ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_root_string(json_iterator &iter, const uint8_t *value) noexcept { ++ return visit_string(iter, value); ++} ++ ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_number(json_iterator &iter, const uint8_t *value) noexcept { ++ iter.log_value("number"); ++ return numberparsing::parse_number(value, tape); ++} ++ ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_root_number(json_iterator &iter, const uint8_t *value) noexcept { ++ // ++ // We need to make a copy to make sure that the string is space terminated. ++ // This is not about padding the input, which should already padded up ++ // to len + SIMDJSON_PADDING. However, we have no control at this stage ++ // on how the padding was done. What if the input string was padded with nulls? ++ // It is quite common for an input string to have an extra null character (C string). ++ // We do not want to allow 9\0 (where \0 is the null character) inside a JSON ++ // document, but the string "9\0" by itself is fine. So we make a copy and ++ // pad the input with spaces when we know that there is just one input element. ++ // This copy is relatively expensive, but it will almost never be called in ++ // practice unless you are in the strange scenario where you have many JSON ++ // documents made of single atoms. ++ // ++ std::unique_ptrcopy(new (std::nothrow) uint8_t[iter.remaining_len() + SIMDJSON_PADDING]); ++ if (copy.get() == nullptr) { return MEMALLOC; } ++ std::memcpy(copy.get(), value, iter.remaining_len()); ++ std::memset(copy.get() + iter.remaining_len(), ' ', SIMDJSON_PADDING); ++ error_code error = visit_number(iter, copy.get()); ++ return error; ++} ++ ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_true_atom(json_iterator &iter, const uint8_t *value) noexcept { ++ iter.log_value("true"); ++ if (!atomparsing::is_valid_true_atom(value)) { return T_ATOM_ERROR; } ++ tape.append(0, internal::tape_type::TRUE_VALUE); ++ return SUCCESS; ++} ++ ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_root_true_atom(json_iterator &iter, const uint8_t *value) noexcept { ++ iter.log_value("true"); ++ if (!atomparsing::is_valid_true_atom(value, iter.remaining_len())) { return T_ATOM_ERROR; } ++ tape.append(0, internal::tape_type::TRUE_VALUE); ++ return SUCCESS; ++} ++ ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_false_atom(json_iterator &iter, const uint8_t *value) noexcept { ++ iter.log_value("false"); ++ if (!atomparsing::is_valid_false_atom(value)) { return F_ATOM_ERROR; } ++ tape.append(0, internal::tape_type::FALSE_VALUE); ++ return SUCCESS; ++} ++ ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_root_false_atom(json_iterator &iter, const uint8_t *value) noexcept { ++ iter.log_value("false"); ++ if (!atomparsing::is_valid_false_atom(value, iter.remaining_len())) { return F_ATOM_ERROR; } ++ tape.append(0, internal::tape_type::FALSE_VALUE); ++ return SUCCESS; ++} ++ ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_null_atom(json_iterator &iter, const uint8_t *value) noexcept { ++ iter.log_value("null"); ++ if (!atomparsing::is_valid_null_atom(value)) { return N_ATOM_ERROR; } ++ tape.append(0, internal::tape_type::NULL_VALUE); ++ return SUCCESS; ++} ++ ++simdjson_warn_unused simdjson_inline error_code tape_builder::visit_root_null_atom(json_iterator &iter, const uint8_t *value) noexcept { ++ iter.log_value("null"); ++ if (!atomparsing::is_valid_null_atom(value, iter.remaining_len())) { return N_ATOM_ERROR; } ++ tape.append(0, internal::tape_type::NULL_VALUE); ++ return SUCCESS; ++} ++ ++// private: ++ ++simdjson_inline uint32_t tape_builder::next_tape_index(json_iterator &iter) const noexcept { ++ return uint32_t(tape.next_tape_loc - iter.dom_parser.doc->tape.get()); ++} ++ ++simdjson_warn_unused simdjson_inline error_code tape_builder::empty_container(json_iterator &iter, internal::tape_type start, internal::tape_type end) noexcept { ++ auto start_index = next_tape_index(iter); ++ tape.append(start_index+2, start); ++ tape.append(start_index, end); ++ return SUCCESS; ++} ++ ++simdjson_inline void tape_builder::start_container(json_iterator &iter) noexcept { ++ iter.dom_parser.open_containers[iter.depth].tape_index = next_tape_index(iter); ++ iter.dom_parser.open_containers[iter.depth].count = 0; ++ tape.skip(); // We don't actually *write* the start element until the end. ++} ++ ++simdjson_warn_unused simdjson_inline error_code tape_builder::end_container(json_iterator &iter, internal::tape_type start, internal::tape_type end) noexcept { ++ // Write the ending tape element, pointing at the start location ++ const uint32_t start_tape_index = iter.dom_parser.open_containers[iter.depth].tape_index; ++ tape.append(start_tape_index, end); ++ // Write the start tape element, pointing at the end location (and including count) ++ // count can overflow if it exceeds 24 bits... so we saturate ++ // the convention being that a cnt of 0xffffff or more is undetermined in value (>= 0xffffff). ++ const uint32_t count = iter.dom_parser.open_containers[iter.depth].count; ++ const uint32_t cntsat = count > 0xFFFFFF ? 0xFFFFFF : count; ++ tape_writer::write(iter.dom_parser.doc->tape[start_tape_index], next_tape_index(iter) | (uint64_t(cntsat) << 32), start); ++ return SUCCESS; ++} ++ ++simdjson_inline uint8_t *tape_builder::on_start_string(json_iterator &iter) noexcept { ++ // we advance the point, accounting for the fact that we have a NULL termination ++ tape.append(current_string_buf_loc - iter.dom_parser.doc->string_buf.get(), internal::tape_type::STRING); ++ return current_string_buf_loc + sizeof(uint32_t); ++} ++ ++simdjson_inline void tape_builder::on_end_string(uint8_t *dst) noexcept { ++ uint32_t str_length = uint32_t(dst - (current_string_buf_loc + sizeof(uint32_t))); ++ // TODO check for overflow in case someone has a crazy string (>=4GB?) ++ // But only add the overflow check when the document itself exceeds 4GB ++ // Currently unneeded because we refuse to parse docs larger or equal to 4GB. ++ memcpy(current_string_buf_loc, &str_length, sizeof(uint32_t)); ++ // NULL termination is still handy if you expect all your strings to ++ // be NULL terminated? It comes at a small cost ++ *dst = 0; ++ current_string_buf_loc = dst + 1; ++} ++ ++} // namespace stage2 ++} // unnamed namespace ++} // namespace westmere ++} // namespace simdjson ++/* end file src/generic/stage2/tape_builder.h */ ++ ++// ++// Implementation-specific overrides ++// ++ ++namespace simdjson { ++namespace westmere { ++namespace { ++namespace stage1 { ++ ++simdjson_inline uint64_t json_string_scanner::find_escaped(uint64_t backslash) { ++ if (!backslash) { uint64_t escaped = prev_escaped; prev_escaped = 0; return escaped; } ++ return find_escaped_branchless(backslash); ++} ++ ++} // namespace stage1 ++} // unnamed namespace ++ ++simdjson_warn_unused error_code implementation::minify(const uint8_t *buf, size_t len, uint8_t *dst, size_t &dst_len) const noexcept { ++ return westmere::stage1::json_minifier::minify<64>(buf, len, dst, dst_len); ++} ++ ++simdjson_warn_unused error_code dom_parser_implementation::stage1(const uint8_t *_buf, size_t _len, stage1_mode streaming) noexcept { ++ this->buf = _buf; ++ this->len = _len; ++ return westmere::stage1::json_structural_indexer::index<64>(_buf, _len, *this, streaming); ++} ++ ++simdjson_warn_unused bool implementation::validate_utf8(const char *buf, size_t len) const noexcept { ++ return westmere::stage1::generic_validate_utf8(buf,len); ++} ++ ++simdjson_warn_unused error_code dom_parser_implementation::stage2(dom::document &_doc) noexcept { ++ return stage2::tape_builder::parse_document(*this, _doc); ++} ++ ++simdjson_warn_unused error_code dom_parser_implementation::stage2_next(dom::document &_doc) noexcept { ++ return stage2::tape_builder::parse_document(*this, _doc); ++} ++ ++simdjson_warn_unused uint8_t *dom_parser_implementation::parse_string(const uint8_t *src, uint8_t *dst) const noexcept { ++ return westmere::stringparsing::parse_string(src, dst); ++} ++ ++simdjson_warn_unused error_code dom_parser_implementation::parse(const uint8_t *_buf, size_t _len, dom::document &_doc) noexcept { ++ auto error = stage1(_buf, _len, stage1_mode::regular); ++ if (error) { return error; } ++ return stage2(_doc); ++} ++ ++} // namespace westmere ++} // namespace simdjson ++ ++/* begin file include/simdjson/westmere/end.h */ ++SIMDJSON_UNTARGET_WESTMERE ++/* end file include/simdjson/westmere/end.h */ ++/* end file src/westmere/dom_parser_implementation.cpp */ ++#endif ++ ++SIMDJSON_POP_DISABLE_WARNINGS ++/* end file src/simdjson.cpp */ +diff --git a/node_modules/@nozbe/watermelondb/native/shared/simdjson.h b/node_modules/@nozbe/watermelondb/native/shared/simdjson.h +new file mode 100644 +index 0000000..01e4da6 +--- /dev/null ++++ b/node_modules/@nozbe/watermelondb/native/shared/simdjson.h +@@ -0,0 +1,31654 @@ ++/* auto-generated on 2023-01-21 18:07:06 -0500. Do not edit! */ ++/* begin file include/simdjson.h */ ++#ifndef SIMDJSON_H ++#define SIMDJSON_H ++ ++/** ++ * @mainpage ++ * ++ * Check the [README.md](https://github.com/simdjson/simdjson/blob/master/README.md#simdjson--parsing-gigabytes-of-json-per-second). ++ * ++ * Sample code. See https://github.com/simdjson/simdjson/blob/master/doc/basics.md for more examples. ++ ++ #include "simdjson.h" ++ ++ int main(void) { ++ // load from `twitter.json` file: ++ simdjson::dom::parser parser; ++ simdjson::dom::element tweets = parser.load("twitter.json"); ++ std::cout << tweets["search_metadata"]["count"] << " results." << std::endl; ++ ++ // Parse and iterate through an array of objects ++ auto abstract_json = R"( [ ++ { "12345" : {"a":12.34, "b":56.78, "c": 9998877} }, ++ { "12545" : {"a":11.44, "b":12.78, "c": 11111111} } ++ ] )"_padded; ++ ++ for (simdjson::dom::object obj : parser.parse(abstract_json)) { ++ for(const auto key_value : obj) { ++ cout << "key: " << key_value.key << " : "; ++ simdjson::dom::object innerobj = key_value.value; ++ cout << "a: " << double(innerobj["a"]) << ", "; ++ cout << "b: " << double(innerobj["b"]) << ", "; ++ cout << "c: " << int64_t(innerobj["c"]) << endl; ++ } ++ } ++ } ++ */ ++ ++/* begin file include/simdjson/simdjson_version.h */ ++// /include/simdjson/simdjson_version.h automatically generated by release.py, ++// do not change by hand ++#ifndef SIMDJSON_SIMDJSON_VERSION_H ++#define SIMDJSON_SIMDJSON_VERSION_H ++ ++/** The version of simdjson being used (major.minor.revision) */ ++#define SIMDJSON_VERSION "3.1.0" ++ ++namespace simdjson { ++enum { ++ /** ++ * The major version (MAJOR.minor.revision) of simdjson being used. ++ */ ++ SIMDJSON_VERSION_MAJOR = 3, ++ /** ++ * The minor version (major.MINOR.revision) of simdjson being used. ++ */ ++ SIMDJSON_VERSION_MINOR = 1, ++ /** ++ * The revision (major.minor.REVISION) of simdjson being used. ++ */ ++ SIMDJSON_VERSION_REVISION = 0 ++}; ++} // namespace simdjson ++ ++#endif // SIMDJSON_SIMDJSON_VERSION_H ++/* end file include/simdjson/simdjson_version.h */ ++/* begin file include/simdjson/dom.h */ ++#ifndef SIMDJSON_DOM_H ++#define SIMDJSON_DOM_H ++ ++/* begin file include/simdjson/base.h */ ++#ifndef SIMDJSON_BASE_H ++#define SIMDJSON_BASE_H ++ ++/* begin file include/simdjson/compiler_check.h */ ++#ifndef SIMDJSON_COMPILER_CHECK_H ++#define SIMDJSON_COMPILER_CHECK_H ++ ++#ifndef __cplusplus ++#error simdjson requires a C++ compiler ++#endif ++ ++#ifndef SIMDJSON_CPLUSPLUS ++#if defined(_MSVC_LANG) && !defined(__clang__) ++#define SIMDJSON_CPLUSPLUS (_MSC_VER == 1900 ? 201103L : _MSVC_LANG) ++#else ++#define SIMDJSON_CPLUSPLUS __cplusplus ++#endif ++#endif ++ ++// C++ 17 ++#if !defined(SIMDJSON_CPLUSPLUS17) && (SIMDJSON_CPLUSPLUS >= 201703L) ++#define SIMDJSON_CPLUSPLUS17 1 ++#endif ++ ++// C++ 14 ++#if !defined(SIMDJSON_CPLUSPLUS14) && (SIMDJSON_CPLUSPLUS >= 201402L) ++#define SIMDJSON_CPLUSPLUS14 1 ++#endif ++ ++// C++ 11 ++#if !defined(SIMDJSON_CPLUSPLUS11) && (SIMDJSON_CPLUSPLUS >= 201103L) ++#define SIMDJSON_CPLUSPLUS11 1 ++#endif ++ ++#ifndef SIMDJSON_CPLUSPLUS11 ++#error simdjson requires a compiler compliant with the C++11 standard ++#endif ++ ++#endif // SIMDJSON_COMPILER_CHECK_H ++/* end file include/simdjson/compiler_check.h */ ++/* begin file include/simdjson/common_defs.h */ ++#ifndef SIMDJSON_COMMON_DEFS_H ++#define SIMDJSON_COMMON_DEFS_H ++ ++#include ++/* begin file include/simdjson/portability.h */ ++#ifndef SIMDJSON_PORTABILITY_H ++#define SIMDJSON_PORTABILITY_H ++ ++#include ++#include ++#include ++#include ++#include ++#ifndef _WIN32 ++// strcasecmp, strncasecmp ++#include ++#endif ++ ++#ifdef _MSC_VER ++#define SIMDJSON_VISUAL_STUDIO 1 ++/** ++ * We want to differentiate carefully between ++ * clang under visual studio and regular visual ++ * studio. ++ * ++ * Under clang for Windows, we enable: ++ * * target pragmas so that part and only part of the ++ * code gets compiled for advanced instructions. ++ * ++ */ ++#ifdef __clang__ ++// clang under visual studio ++#define SIMDJSON_CLANG_VISUAL_STUDIO 1 ++#else ++// just regular visual studio (best guess) ++#define SIMDJSON_REGULAR_VISUAL_STUDIO 1 ++#endif // __clang__ ++#endif // _MSC_VER ++ ++#if SIMDJSON_REGULAR_VISUAL_STUDIO ++// https://en.wikipedia.org/wiki/C_alternative_tokens ++// This header should have no effect, except maybe ++// under Visual Studio. ++#include ++#endif ++ ++#if defined(__x86_64__) || defined(_M_AMD64) ++#define SIMDJSON_IS_X86_64 1 ++#elif defined(__aarch64__) || defined(_M_ARM64) ++#define SIMDJSON_IS_ARM64 1 ++#elif defined(__PPC64__) || defined(_M_PPC64) ++#define SIMDJSON_IS_PPC64 1 ++#else ++#define SIMDJSON_IS_32BITS 1 ++ ++// We do not support 32-bit platforms, but it can be ++// handy to identify them. ++#if defined(_M_IX86) || defined(__i386__) ++#define SIMDJSON_IS_X86_32BITS 1 ++#elif defined(__arm__) || defined(_M_ARM) ++#define SIMDJSON_IS_ARM_32BITS 1 ++#elif defined(__PPC__) || defined(_M_PPC) ++#define SIMDJSON_IS_PPC_32BITS 1 ++#endif ++ ++#endif // defined(__x86_64__) || defined(_M_AMD64) ++#ifndef SIMDJSON_IS_32BITS ++#define SIMDJSON_IS_32BITS 0 ++#endif ++ ++#if SIMDJSON_IS_32BITS ++#ifndef SIMDJSON_NO_PORTABILITY_WARNING ++#pragma message("The simdjson library is designed \ ++for 64-bit processors and it seems that you are not \ ++compiling for a known 64-bit platform. All fast kernels \ ++will be disabled and performance may be poor. Please \ ++use a 64-bit target such as x64, 64-bit ARM or 64-bit PPC.") ++#endif // SIMDJSON_NO_PORTABILITY_WARNING ++#endif // SIMDJSON_IS_32BITS ++ ++// this is almost standard? ++#undef SIMDJSON_STRINGIFY_IMPLEMENTATION_ ++#undef SIMDJSON_STRINGIFY ++#define SIMDJSON_STRINGIFY_IMPLEMENTATION_(a) #a ++#define SIMDJSON_STRINGIFY(a) SIMDJSON_STRINGIFY_IMPLEMENTATION_(a) ++ ++// Our fast kernels require 64-bit systems. ++// ++// On 32-bit x86, we lack 64-bit popcnt, lzcnt, blsr instructions. ++// Furthermore, the number of SIMD registers is reduced. ++// ++// On 32-bit ARM, we would have smaller registers. ++// ++// The simdjson users should still have the fallback kernel. It is ++// slower, but it should run everywhere. ++ ++// ++// Enable valid runtime implementations, and select SIMDJSON_BUILTIN_IMPLEMENTATION ++// ++ ++// We are going to use runtime dispatch. ++#if SIMDJSON_IS_X86_64 ++#ifdef __clang__ ++// clang does not have GCC push pop ++// warning: clang attribute push can't be used within a namespace in clang up ++// til 8.0 so SIMDJSON_TARGET_REGION and SIMDJSON_UNTARGET_REGION must be *outside* of a ++// namespace. ++#define SIMDJSON_TARGET_REGION(T) \ ++ _Pragma(SIMDJSON_STRINGIFY( \ ++ clang attribute push(__attribute__((target(T))), apply_to = function))) ++#define SIMDJSON_UNTARGET_REGION _Pragma("clang attribute pop") ++#elif defined(__GNUC__) ++// GCC is easier ++#define SIMDJSON_TARGET_REGION(T) \ ++ _Pragma("GCC push_options") _Pragma(SIMDJSON_STRINGIFY(GCC target(T))) ++#define SIMDJSON_UNTARGET_REGION _Pragma("GCC pop_options") ++#endif // clang then gcc ++ ++#endif // x86 ++ ++// Default target region macros don't do anything. ++#ifndef SIMDJSON_TARGET_REGION ++#define SIMDJSON_TARGET_REGION(T) ++#define SIMDJSON_UNTARGET_REGION ++#endif ++ ++// Is threading enabled? ++#if defined(_REENTRANT) || defined(_MT) ++#ifndef SIMDJSON_THREADS_ENABLED ++#define SIMDJSON_THREADS_ENABLED ++#endif ++#endif ++ ++// workaround for large stack sizes under -O0. ++// https://github.com/simdjson/simdjson/issues/691 ++#ifdef __APPLE__ ++#ifndef __OPTIMIZE__ ++// Apple systems have small stack sizes in secondary threads. ++// Lack of compiler optimization may generate high stack usage. ++// Users may want to disable threads for safety, but only when ++// in debug mode which we detect by the fact that the __OPTIMIZE__ ++// macro is not defined. ++#undef SIMDJSON_THREADS_ENABLED ++#endif ++#endif ++ ++ ++#if defined(__clang__) ++#define SIMDJSON_NO_SANITIZE_UNDEFINED __attribute__((no_sanitize("undefined"))) ++#elif defined(__GNUC__) ++#define SIMDJSON_NO_SANITIZE_UNDEFINED __attribute__((no_sanitize_undefined)) ++#else ++#define SIMDJSON_NO_SANITIZE_UNDEFINED ++#endif ++ ++#if SIMDJSON_VISUAL_STUDIO ++// This is one case where we do not distinguish between ++// regular visual studio and clang under visual studio. ++// clang under Windows has _stricmp (like visual studio) but not strcasecmp (as clang normally has) ++#define simdjson_strcasecmp _stricmp ++#define simdjson_strncasecmp _strnicmp ++#else ++// The strcasecmp, strncasecmp, and strcasestr functions do not work with multibyte strings (e.g. UTF-8). ++// So they are only useful for ASCII in our context. ++// https://www.gnu.org/software/libunistring/manual/libunistring.html#char-_002a-strings ++#define simdjson_strcasecmp strcasecmp ++#define simdjson_strncasecmp strncasecmp ++#endif ++ ++#ifdef NDEBUG ++ ++#if SIMDJSON_VISUAL_STUDIO ++#define SIMDJSON_UNREACHABLE() __assume(0) ++#define SIMDJSON_ASSUME(COND) __assume(COND) ++#else ++#define SIMDJSON_UNREACHABLE() __builtin_unreachable(); ++#define SIMDJSON_ASSUME(COND) do { if (!(COND)) __builtin_unreachable(); } while (0) ++#endif ++ ++#else // NDEBUG ++ ++#define SIMDJSON_UNREACHABLE() assert(0); ++#define SIMDJSON_ASSUME(COND) assert(COND) ++ ++#endif ++ ++#endif // SIMDJSON_PORTABILITY_H ++/* end file include/simdjson/portability.h */ ++ ++namespace simdjson { ++ ++namespace internal { ++/** ++ * @private ++ * Our own implementation of the C++17 to_chars function. ++ * Defined in src/to_chars ++ */ ++char *to_chars(char *first, const char *last, double value); ++/** ++ * @private ++ * A number parsing routine. ++ * Defined in src/from_chars ++ */ ++double from_chars(const char *first) noexcept; ++double from_chars(const char *first, const char* end) noexcept; ++ ++} ++ ++#ifndef SIMDJSON_EXCEPTIONS ++#if __cpp_exceptions ++#define SIMDJSON_EXCEPTIONS 1 ++#else ++#define SIMDJSON_EXCEPTIONS 0 ++#endif ++#endif ++ ++/** The maximum document size supported by simdjson. */ ++constexpr size_t SIMDJSON_MAXSIZE_BYTES = 0xFFFFFFFF; ++ ++/** ++ * The amount of padding needed in a buffer to parse JSON. ++ * ++ * The input buf should be readable up to buf + SIMDJSON_PADDING ++ * this is a stopgap; there should be a better description of the ++ * main loop and its behavior that abstracts over this ++ * See https://github.com/simdjson/simdjson/issues/174 ++ */ ++constexpr size_t SIMDJSON_PADDING = 64; ++ ++/** ++ * By default, simdjson supports this many nested objects and arrays. ++ * ++ * This is the default for parser::max_depth(). ++ */ ++constexpr size_t DEFAULT_MAX_DEPTH = 1024; ++ ++} // namespace simdjson ++ ++#if defined(__GNUC__) ++ // Marks a block with a name so that MCA analysis can see it. ++ #define SIMDJSON_BEGIN_DEBUG_BLOCK(name) __asm volatile("# LLVM-MCA-BEGIN " #name); ++ #define SIMDJSON_END_DEBUG_BLOCK(name) __asm volatile("# LLVM-MCA-END " #name); ++ #define SIMDJSON_DEBUG_BLOCK(name, block) BEGIN_DEBUG_BLOCK(name); block; END_DEBUG_BLOCK(name); ++#else ++ #define SIMDJSON_BEGIN_DEBUG_BLOCK(name) ++ #define SIMDJSON_END_DEBUG_BLOCK(name) ++ #define SIMDJSON_DEBUG_BLOCK(name, block) ++#endif ++ ++// Align to N-byte boundary ++#define SIMDJSON_ROUNDUP_N(a, n) (((a) + ((n)-1)) & ~((n)-1)) ++#define SIMDJSON_ROUNDDOWN_N(a, n) ((a) & ~((n)-1)) ++ ++#define SIMDJSON_ISALIGNED_N(ptr, n) (((uintptr_t)(ptr) & ((n)-1)) == 0) ++ ++#if SIMDJSON_REGULAR_VISUAL_STUDIO ++ ++ #define simdjson_really_inline __forceinline ++ #define simdjson_never_inline __declspec(noinline) ++ ++ #define simdjson_unused ++ #define simdjson_warn_unused ++ ++ #ifndef simdjson_likely ++ #define simdjson_likely(x) x ++ #endif ++ #ifndef simdjson_unlikely ++ #define simdjson_unlikely(x) x ++ #endif ++ ++ #define SIMDJSON_PUSH_DISABLE_WARNINGS __pragma(warning( push )) ++ #define SIMDJSON_PUSH_DISABLE_ALL_WARNINGS __pragma(warning( push, 0 )) ++ #define SIMDJSON_DISABLE_VS_WARNING(WARNING_NUMBER) __pragma(warning( disable : WARNING_NUMBER )) ++ // Get rid of Intellisense-only warnings (Code Analysis) ++ // Though __has_include is C++17, it is supported in Visual Studio 2017 or better (_MSC_VER>=1910). ++ #ifdef __has_include ++ #if __has_include() ++ #include ++ #define SIMDJSON_DISABLE_UNDESIRED_WARNINGS SIMDJSON_DISABLE_VS_WARNING(ALL_CPPCORECHECK_WARNINGS) ++ #endif ++ #endif ++ ++ #ifndef SIMDJSON_DISABLE_UNDESIRED_WARNINGS ++ #define SIMDJSON_DISABLE_UNDESIRED_WARNINGS ++ #endif ++ ++ #define SIMDJSON_DISABLE_DEPRECATED_WARNING SIMDJSON_DISABLE_VS_WARNING(4996) ++ #define SIMDJSON_DISABLE_STRICT_OVERFLOW_WARNING ++ #define SIMDJSON_POP_DISABLE_WARNINGS __pragma(warning( pop )) ++ ++#else // SIMDJSON_REGULAR_VISUAL_STUDIO ++ ++ #define simdjson_really_inline inline __attribute__((always_inline)) ++ #define simdjson_never_inline inline __attribute__((noinline)) ++ ++ #define simdjson_unused __attribute__((unused)) ++ #define simdjson_warn_unused __attribute__((warn_unused_result)) ++ ++ #ifndef simdjson_likely ++ #define simdjson_likely(x) __builtin_expect(!!(x), 1) ++ #endif ++ #ifndef simdjson_unlikely ++ #define simdjson_unlikely(x) __builtin_expect(!!(x), 0) ++ #endif ++ ++ #define SIMDJSON_PUSH_DISABLE_WARNINGS _Pragma("GCC diagnostic push") ++ // gcc doesn't seem to disable all warnings with all and extra, add warnings here as necessary ++ // We do it separately for clang since it has different warnings. ++ #ifdef __clang__ ++ // clang is missing -Wmaybe-uninitialized. ++ #define SIMDJSON_PUSH_DISABLE_ALL_WARNINGS SIMDJSON_PUSH_DISABLE_WARNINGS \ ++ SIMDJSON_DISABLE_GCC_WARNING(-Weffc++) \ ++ SIMDJSON_DISABLE_GCC_WARNING(-Wall) \ ++ SIMDJSON_DISABLE_GCC_WARNING(-Wconversion) \ ++ SIMDJSON_DISABLE_GCC_WARNING(-Wextra) \ ++ SIMDJSON_DISABLE_GCC_WARNING(-Wattributes) \ ++ SIMDJSON_DISABLE_GCC_WARNING(-Wimplicit-fallthrough) \ ++ SIMDJSON_DISABLE_GCC_WARNING(-Wnon-virtual-dtor) \ ++ SIMDJSON_DISABLE_GCC_WARNING(-Wreturn-type) \ ++ SIMDJSON_DISABLE_GCC_WARNING(-Wshadow) \ ++ SIMDJSON_DISABLE_GCC_WARNING(-Wunused-parameter) \ ++ SIMDJSON_DISABLE_GCC_WARNING(-Wunused-variable) ++ #else // __clang__ ++ #define SIMDJSON_PUSH_DISABLE_ALL_WARNINGS SIMDJSON_PUSH_DISABLE_WARNINGS \ ++ SIMDJSON_DISABLE_GCC_WARNING(-Weffc++) \ ++ SIMDJSON_DISABLE_GCC_WARNING(-Wall) \ ++ SIMDJSON_DISABLE_GCC_WARNING(-Wconversion) \ ++ SIMDJSON_DISABLE_GCC_WARNING(-Wextra) \ ++ SIMDJSON_DISABLE_GCC_WARNING(-Wattributes) \ ++ SIMDJSON_DISABLE_GCC_WARNING(-Wimplicit-fallthrough) \ ++ SIMDJSON_DISABLE_GCC_WARNING(-Wnon-virtual-dtor) \ ++ SIMDJSON_DISABLE_GCC_WARNING(-Wreturn-type) \ ++ SIMDJSON_DISABLE_GCC_WARNING(-Wshadow) \ ++ SIMDJSON_DISABLE_GCC_WARNING(-Wunused-parameter) \ ++ SIMDJSON_DISABLE_GCC_WARNING(-Wunused-variable) \ ++ SIMDJSON_DISABLE_GCC_WARNING(-Wmaybe-uninitialized) ++ #endif // __clang__ ++ ++ #define SIMDJSON_PRAGMA(P) _Pragma(#P) ++ #define SIMDJSON_DISABLE_GCC_WARNING(WARNING) SIMDJSON_PRAGMA(GCC diagnostic ignored #WARNING) ++ #if SIMDJSON_CLANG_VISUAL_STUDIO ++ #define SIMDJSON_DISABLE_UNDESIRED_WARNINGS SIMDJSON_DISABLE_GCC_WARNING(-Wmicrosoft-include) ++ #else ++ #define SIMDJSON_DISABLE_UNDESIRED_WARNINGS ++ #endif ++ #define SIMDJSON_DISABLE_DEPRECATED_WARNING SIMDJSON_DISABLE_GCC_WARNING(-Wdeprecated-declarations) ++ #define SIMDJSON_DISABLE_STRICT_OVERFLOW_WARNING SIMDJSON_DISABLE_GCC_WARNING(-Wstrict-overflow) ++ #define SIMDJSON_POP_DISABLE_WARNINGS _Pragma("GCC diagnostic pop") ++ ++ ++ ++#endif // MSC_VER ++ ++#if defined(simdjson_inline) ++ // Prefer the user's definition of simdjson_inline; don't define it ourselves. ++#elif defined(__GNUC__) && !defined(__OPTIMIZE__) ++ // If optimizations are disabled, forcing inlining can lead to significant ++ // code bloat and high compile times. Don't use simdjson_really_inline for ++ // unoptimized builds. ++ #define simdjson_inline inline ++#else ++ // Force inlining for most simdjson functions. ++ #define simdjson_inline simdjson_really_inline ++#endif ++ ++#if SIMDJSON_VISUAL_STUDIO ++ /** ++ * Windows users need to do some extra work when building ++ * or using a dynamic library (DLL). When building, we need ++ * to set SIMDJSON_DLLIMPORTEXPORT to __declspec(dllexport). ++ * When *using* the DLL, the user needs to set ++ * SIMDJSON_DLLIMPORTEXPORT __declspec(dllimport). ++ * ++ * Static libraries not need require such work. ++ * ++ * It does not matter here whether you are using ++ * the regular visual studio or clang under visual ++ * studio, you still need to handle these issues. ++ * ++ * Non-Windows systems do not have this complexity. ++ */ ++ #if SIMDJSON_BUILDING_WINDOWS_DYNAMIC_LIBRARY ++ // We set SIMDJSON_BUILDING_WINDOWS_DYNAMIC_LIBRARY when we build a DLL under Windows. ++ // It should never happen that both SIMDJSON_BUILDING_WINDOWS_DYNAMIC_LIBRARY and ++ // SIMDJSON_USING_WINDOWS_DYNAMIC_LIBRARY are set. ++ #define SIMDJSON_DLLIMPORTEXPORT __declspec(dllexport) ++ #elif SIMDJSON_USING_WINDOWS_DYNAMIC_LIBRARY ++ // Windows user who call a dynamic library should set SIMDJSON_USING_WINDOWS_DYNAMIC_LIBRARY to 1. ++ #define SIMDJSON_DLLIMPORTEXPORT __declspec(dllimport) ++ #else ++ // We assume by default static linkage ++ #define SIMDJSON_DLLIMPORTEXPORT ++ #endif ++ ++/** ++ * Workaround for the vcpkg package manager. Only vcpkg should ++ * ever touch the next line. The SIMDJSON_USING_LIBRARY macro is otherwise unused. ++ */ ++#if SIMDJSON_USING_LIBRARY ++#define SIMDJSON_DLLIMPORTEXPORT __declspec(dllimport) ++#endif ++/** ++ * End of workaround for the vcpkg package manager. ++ */ ++#else ++ #define SIMDJSON_DLLIMPORTEXPORT ++#endif ++ ++// C++17 requires string_view. ++#if SIMDJSON_CPLUSPLUS17 ++#define SIMDJSON_HAS_STRING_VIEW ++#include // by the standard, this has to be safe. ++#endif ++ ++// This macro (__cpp_lib_string_view) has to be defined ++// for C++17 and better, but if it is otherwise defined, ++// we are going to assume that string_view is available ++// even if we do not have C++17 support. ++#ifdef __cpp_lib_string_view ++#define SIMDJSON_HAS_STRING_VIEW ++#endif ++ ++// Some systems have string_view even if we do not have C++17 support, ++// and even if __cpp_lib_string_view is undefined, it is the case ++// with Apple clang version 11. ++// We must handle it. *This is important.* ++#ifndef SIMDJSON_HAS_STRING_VIEW ++#if defined __has_include ++// do not combine the next #if with the previous one (unsafe) ++#if __has_include () ++// now it is safe to trigger the include ++#include // though the file is there, it does not follow that we got the implementation ++#if defined(_LIBCPP_STRING_VIEW) ++// Ah! So we under libc++ which under its Library Fundamentals Technical Specification, which preceded C++17, ++// included string_view. ++// This means that we have string_view *even though* we may not have C++17. ++#define SIMDJSON_HAS_STRING_VIEW ++#endif // _LIBCPP_STRING_VIEW ++#endif // __has_include () ++#endif // defined __has_include ++#endif // def SIMDJSON_HAS_STRING_VIEW ++// end of complicated but important routine to try to detect string_view. ++ ++// ++// Backfill std::string_view using nonstd::string_view on systems where ++// we expect that string_view is missing. Important: if we get this wrong, ++// we will end up with two string_view definitions and potential trouble. ++// That is why we work so hard above to avoid it. ++// ++#ifndef SIMDJSON_HAS_STRING_VIEW ++SIMDJSON_PUSH_DISABLE_ALL_WARNINGS ++/* begin file include/simdjson/nonstd/string_view.hpp */ ++// Copyright 2017-2020 by Martin Moene ++// ++// string-view lite, a C++17-like string_view for C++98 and later. ++// For more information see https://github.com/martinmoene/string-view-lite ++// ++// Distributed under the Boost Software License, Version 1.0. ++// (See accompanying file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) ++ ++#pragma once ++ ++#ifndef NONSTD_SV_LITE_H_INCLUDED ++#define NONSTD_SV_LITE_H_INCLUDED ++ ++#define string_view_lite_MAJOR 1 ++#define string_view_lite_MINOR 6 ++#define string_view_lite_PATCH 0 ++ ++#define string_view_lite_VERSION nssv_STRINGIFY(string_view_lite_MAJOR) "." nssv_STRINGIFY(string_view_lite_MINOR) "." nssv_STRINGIFY(string_view_lite_PATCH) ++ ++#define nssv_STRINGIFY( x ) nssv_STRINGIFY_( x ) ++#define nssv_STRINGIFY_( x ) #x ++ ++// string-view lite configuration: ++ ++#define nssv_STRING_VIEW_DEFAULT 0 ++#define nssv_STRING_VIEW_NONSTD 1 ++#define nssv_STRING_VIEW_STD 2 ++ ++// tweak header support: ++ ++#ifdef __has_include ++# if __has_include() ++# include ++# endif ++#define nssv_HAVE_TWEAK_HEADER 1 ++#else ++#define nssv_HAVE_TWEAK_HEADER 0 ++//# pragma message("string_view.hpp: Note: Tweak header not supported.") ++#endif ++ ++// string_view selection and configuration: ++ ++#if !defined( nssv_CONFIG_SELECT_STRING_VIEW ) ++# define nssv_CONFIG_SELECT_STRING_VIEW ( nssv_HAVE_STD_STRING_VIEW ? nssv_STRING_VIEW_STD : nssv_STRING_VIEW_NONSTD ) ++#endif ++ ++#ifndef nssv_CONFIG_STD_SV_OPERATOR ++# define nssv_CONFIG_STD_SV_OPERATOR 0 ++#endif ++ ++#ifndef nssv_CONFIG_USR_SV_OPERATOR ++# define nssv_CONFIG_USR_SV_OPERATOR 1 ++#endif ++ ++#ifdef nssv_CONFIG_CONVERSION_STD_STRING ++# define nssv_CONFIG_CONVERSION_STD_STRING_CLASS_METHODS nssv_CONFIG_CONVERSION_STD_STRING ++# define nssv_CONFIG_CONVERSION_STD_STRING_FREE_FUNCTIONS nssv_CONFIG_CONVERSION_STD_STRING ++#endif ++ ++#ifndef nssv_CONFIG_CONVERSION_STD_STRING_CLASS_METHODS ++# define nssv_CONFIG_CONVERSION_STD_STRING_CLASS_METHODS 1 ++#endif ++ ++#ifndef nssv_CONFIG_CONVERSION_STD_STRING_FREE_FUNCTIONS ++# define nssv_CONFIG_CONVERSION_STD_STRING_FREE_FUNCTIONS 1 ++#endif ++ ++#ifndef nssv_CONFIG_NO_STREAM_INSERTION ++# define nssv_CONFIG_NO_STREAM_INSERTION 0 ++#endif ++ ++// Control presence of exception handling (try and auto discover): ++ ++#ifndef nssv_CONFIG_NO_EXCEPTIONS ++# if _MSC_VER ++# include // for _HAS_EXCEPTIONS ++# endif ++# if defined(__cpp_exceptions) || defined(__EXCEPTIONS) || (_HAS_EXCEPTIONS) ++# define nssv_CONFIG_NO_EXCEPTIONS 0 ++# else ++# define nssv_CONFIG_NO_EXCEPTIONS 1 ++# endif ++#endif ++ ++// C++ language version detection (C++20 is speculative): ++// Note: VC14.0/1900 (VS2015) lacks too much from C++14. ++ ++#ifndef nssv_CPLUSPLUS ++# if defined(_MSVC_LANG ) && !defined(__clang__) ++# define nssv_CPLUSPLUS (_MSC_VER == 1900 ? 201103L : _MSVC_LANG ) ++# else ++# define nssv_CPLUSPLUS __cplusplus ++# endif ++#endif ++ ++#define nssv_CPP98_OR_GREATER ( nssv_CPLUSPLUS >= 199711L ) ++#define nssv_CPP11_OR_GREATER ( nssv_CPLUSPLUS >= 201103L ) ++#define nssv_CPP11_OR_GREATER_ ( nssv_CPLUSPLUS >= 201103L ) ++#define nssv_CPP14_OR_GREATER ( nssv_CPLUSPLUS >= 201402L ) ++#define nssv_CPP17_OR_GREATER ( nssv_CPLUSPLUS >= 201703L ) ++#define nssv_CPP20_OR_GREATER ( nssv_CPLUSPLUS >= 202000L ) ++ ++// use C++17 std::string_view if available and requested: ++ ++#if nssv_CPP17_OR_GREATER && defined(__has_include ) ++# if __has_include( ) ++# define nssv_HAVE_STD_STRING_VIEW 1 ++# else ++# define nssv_HAVE_STD_STRING_VIEW 0 ++# endif ++#else ++# define nssv_HAVE_STD_STRING_VIEW 0 ++#endif ++ ++#define nssv_USES_STD_STRING_VIEW ( (nssv_CONFIG_SELECT_STRING_VIEW == nssv_STRING_VIEW_STD) || ((nssv_CONFIG_SELECT_STRING_VIEW == nssv_STRING_VIEW_DEFAULT) && nssv_HAVE_STD_STRING_VIEW) ) ++ ++#define nssv_HAVE_STARTS_WITH ( nssv_CPP20_OR_GREATER || !nssv_USES_STD_STRING_VIEW ) ++#define nssv_HAVE_ENDS_WITH nssv_HAVE_STARTS_WITH ++ ++// ++// Use C++17 std::string_view: ++// ++ ++#if nssv_USES_STD_STRING_VIEW ++ ++#include ++ ++// Extensions for std::string: ++ ++#if nssv_CONFIG_CONVERSION_STD_STRING_FREE_FUNCTIONS ++ ++namespace nonstd { ++ ++template< class CharT, class Traits, class Allocator = std::allocator > ++std::basic_string ++to_string( std::basic_string_view v, Allocator const & a = Allocator() ) ++{ ++ return std::basic_string( v.begin(), v.end(), a ); ++} ++ ++template< class CharT, class Traits, class Allocator > ++std::basic_string_view ++to_string_view( std::basic_string const & s ) ++{ ++ return std::basic_string_view( s.data(), s.size() ); ++} ++ ++// Literal operators sv and _sv: ++ ++#if nssv_CONFIG_STD_SV_OPERATOR ++ ++using namespace std::literals::string_view_literals; ++ ++#endif ++ ++#if nssv_CONFIG_USR_SV_OPERATOR ++ ++inline namespace literals { ++inline namespace string_view_literals { ++ ++ ++constexpr std::string_view operator "" _sv( const char* str, size_t len ) noexcept // (1) ++{ ++ return std::string_view{ str, len }; ++} ++ ++constexpr std::u16string_view operator "" _sv( const char16_t* str, size_t len ) noexcept // (2) ++{ ++ return std::u16string_view{ str, len }; ++} ++ ++constexpr std::u32string_view operator "" _sv( const char32_t* str, size_t len ) noexcept // (3) ++{ ++ return std::u32string_view{ str, len }; ++} ++ ++constexpr std::wstring_view operator "" _sv( const wchar_t* str, size_t len ) noexcept // (4) ++{ ++ return std::wstring_view{ str, len }; ++} ++ ++}} // namespace literals::string_view_literals ++ ++#endif // nssv_CONFIG_USR_SV_OPERATOR ++ ++} // namespace nonstd ++ ++#endif // nssv_CONFIG_CONVERSION_STD_STRING_FREE_FUNCTIONS ++ ++namespace nonstd { ++ ++using std::string_view; ++using std::wstring_view; ++using std::u16string_view; ++using std::u32string_view; ++using std::basic_string_view; ++ ++// literal "sv" and "_sv", see above ++ ++using std::operator==; ++using std::operator!=; ++using std::operator<; ++using std::operator<=; ++using std::operator>; ++using std::operator>=; ++ ++using std::operator<<; ++ ++} // namespace nonstd ++ ++#else // nssv_HAVE_STD_STRING_VIEW ++ ++// ++// Before C++17: use string_view lite: ++// ++ ++// Compiler versions: ++// ++// MSVC++ 6.0 _MSC_VER == 1200 nssv_COMPILER_MSVC_VERSION == 60 (Visual Studio 6.0) ++// MSVC++ 7.0 _MSC_VER == 1300 nssv_COMPILER_MSVC_VERSION == 70 (Visual Studio .NET 2002) ++// MSVC++ 7.1 _MSC_VER == 1310 nssv_COMPILER_MSVC_VERSION == 71 (Visual Studio .NET 2003) ++// MSVC++ 8.0 _MSC_VER == 1400 nssv_COMPILER_MSVC_VERSION == 80 (Visual Studio 2005) ++// MSVC++ 9.0 _MSC_VER == 1500 nssv_COMPILER_MSVC_VERSION == 90 (Visual Studio 2008) ++// MSVC++ 10.0 _MSC_VER == 1600 nssv_COMPILER_MSVC_VERSION == 100 (Visual Studio 2010) ++// MSVC++ 11.0 _MSC_VER == 1700 nssv_COMPILER_MSVC_VERSION == 110 (Visual Studio 2012) ++// MSVC++ 12.0 _MSC_VER == 1800 nssv_COMPILER_MSVC_VERSION == 120 (Visual Studio 2013) ++// MSVC++ 14.0 _MSC_VER == 1900 nssv_COMPILER_MSVC_VERSION == 140 (Visual Studio 2015) ++// MSVC++ 14.1 _MSC_VER >= 1910 nssv_COMPILER_MSVC_VERSION == 141 (Visual Studio 2017) ++// MSVC++ 14.2 _MSC_VER >= 1920 nssv_COMPILER_MSVC_VERSION == 142 (Visual Studio 2019) ++ ++#if defined(_MSC_VER ) && !defined(__clang__) ++# define nssv_COMPILER_MSVC_VER (_MSC_VER ) ++# define nssv_COMPILER_MSVC_VERSION (_MSC_VER / 10 - 10 * ( 5 + (_MSC_VER < 1900 ) ) ) ++#else ++# define nssv_COMPILER_MSVC_VER 0 ++# define nssv_COMPILER_MSVC_VERSION 0 ++#endif ++ ++#define nssv_COMPILER_VERSION( major, minor, patch ) ( 10 * ( 10 * (major) + (minor) ) + (patch) ) ++ ++#if defined( __apple_build_version__ ) ++# define nssv_COMPILER_APPLECLANG_VERSION nssv_COMPILER_VERSION(__clang_major__, __clang_minor__, __clang_patchlevel__) ++# define nssv_COMPILER_CLANG_VERSION 0 ++#elif defined( __clang__ ) ++# define nssv_COMPILER_APPLECLANG_VERSION 0 ++# define nssv_COMPILER_CLANG_VERSION nssv_COMPILER_VERSION(__clang_major__, __clang_minor__, __clang_patchlevel__) ++#else ++# define nssv_COMPILER_APPLECLANG_VERSION 0 ++# define nssv_COMPILER_CLANG_VERSION 0 ++#endif ++ ++#if defined(__GNUC__) && !defined(__clang__) ++# define nssv_COMPILER_GNUC_VERSION nssv_COMPILER_VERSION(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__) ++#else ++# define nssv_COMPILER_GNUC_VERSION 0 ++#endif ++ ++// half-open range [lo..hi): ++#define nssv_BETWEEN( v, lo, hi ) ( (lo) <= (v) && (v) < (hi) ) ++ ++// Presence of language and library features: ++ ++#ifdef _HAS_CPP0X ++# define nssv_HAS_CPP0X _HAS_CPP0X ++#else ++# define nssv_HAS_CPP0X 0 ++#endif ++ ++// Unless defined otherwise below, consider VC14 as C++11 for variant-lite: ++ ++#if nssv_COMPILER_MSVC_VER >= 1900 ++# undef nssv_CPP11_OR_GREATER ++# define nssv_CPP11_OR_GREATER 1 ++#endif ++ ++#define nssv_CPP11_90 (nssv_CPP11_OR_GREATER_ || nssv_COMPILER_MSVC_VER >= 1500) ++#define nssv_CPP11_100 (nssv_CPP11_OR_GREATER_ || nssv_COMPILER_MSVC_VER >= 1600) ++#define nssv_CPP11_110 (nssv_CPP11_OR_GREATER_ || nssv_COMPILER_MSVC_VER >= 1700) ++#define nssv_CPP11_120 (nssv_CPP11_OR_GREATER_ || nssv_COMPILER_MSVC_VER >= 1800) ++#define nssv_CPP11_140 (nssv_CPP11_OR_GREATER_ || nssv_COMPILER_MSVC_VER >= 1900) ++#define nssv_CPP11_141 (nssv_CPP11_OR_GREATER_ || nssv_COMPILER_MSVC_VER >= 1910) ++ ++#define nssv_CPP14_000 (nssv_CPP14_OR_GREATER) ++#define nssv_CPP17_000 (nssv_CPP17_OR_GREATER) ++ ++// Presence of C++11 language features: ++ ++#define nssv_HAVE_CONSTEXPR_11 nssv_CPP11_140 ++#define nssv_HAVE_EXPLICIT_CONVERSION nssv_CPP11_140 ++#define nssv_HAVE_INLINE_NAMESPACE nssv_CPP11_140 ++#define nssv_HAVE_NOEXCEPT nssv_CPP11_140 ++#define nssv_HAVE_NULLPTR nssv_CPP11_100 ++#define nssv_HAVE_REF_QUALIFIER nssv_CPP11_140 ++#define nssv_HAVE_UNICODE_LITERALS nssv_CPP11_140 ++#define nssv_HAVE_USER_DEFINED_LITERALS nssv_CPP11_140 ++#define nssv_HAVE_WCHAR16_T nssv_CPP11_100 ++#define nssv_HAVE_WCHAR32_T nssv_CPP11_100 ++ ++#if ! ( ( nssv_CPP11_OR_GREATER && nssv_COMPILER_CLANG_VERSION ) || nssv_BETWEEN( nssv_COMPILER_CLANG_VERSION, 300, 400 ) ) ++# define nssv_HAVE_STD_DEFINED_LITERALS nssv_CPP11_140 ++#else ++# define nssv_HAVE_STD_DEFINED_LITERALS 0 ++#endif ++ ++// Presence of C++14 language features: ++ ++#define nssv_HAVE_CONSTEXPR_14 nssv_CPP14_000 ++ ++// Presence of C++17 language features: ++ ++#define nssv_HAVE_NODISCARD nssv_CPP17_000 ++ ++// Presence of C++ library features: ++ ++#define nssv_HAVE_STD_HASH nssv_CPP11_120 ++ ++// Presence of compiler intrinsics: ++ ++// Providing char-type specializations for compare() and length() that ++// use compiler intrinsics can improve compile- and run-time performance. ++// ++// The challenge is in using the right combinations of builtin availability ++// and its constexpr-ness. ++// ++// | compiler | __builtin_memcmp (constexpr) | memcmp (constexpr) | ++// |----------|------------------------------|---------------------| ++// | clang | 4.0 (>= 4.0 ) | any (? ) | ++// | clang-a | 9.0 (>= 9.0 ) | any (? ) | ++// | gcc | any (constexpr) | any (? ) | ++// | msvc | >= 14.2 C++17 (>= 14.2 ) | any (? ) | ++ ++#define nssv_HAVE_BUILTIN_VER ( (nssv_CPP17_000 && nssv_COMPILER_MSVC_VERSION >= 142) || nssv_COMPILER_GNUC_VERSION > 0 || nssv_COMPILER_CLANG_VERSION >= 400 || nssv_COMPILER_APPLECLANG_VERSION >= 900 ) ++#define nssv_HAVE_BUILTIN_CE ( nssv_HAVE_BUILTIN_VER ) ++ ++#define nssv_HAVE_BUILTIN_MEMCMP ( (nssv_HAVE_CONSTEXPR_14 && nssv_HAVE_BUILTIN_CE) || !nssv_HAVE_CONSTEXPR_14 ) ++#define nssv_HAVE_BUILTIN_STRLEN ( (nssv_HAVE_CONSTEXPR_11 && nssv_HAVE_BUILTIN_CE) || !nssv_HAVE_CONSTEXPR_11 ) ++ ++#ifdef __has_builtin ++# define nssv_HAVE_BUILTIN( x ) __has_builtin( x ) ++#else ++# define nssv_HAVE_BUILTIN( x ) 0 ++#endif ++ ++#if nssv_HAVE_BUILTIN(__builtin_memcmp) || nssv_HAVE_BUILTIN_VER ++# define nssv_BUILTIN_MEMCMP __builtin_memcmp ++#else ++# define nssv_BUILTIN_MEMCMP memcmp ++#endif ++ ++#if nssv_HAVE_BUILTIN(__builtin_strlen) || nssv_HAVE_BUILTIN_VER ++# define nssv_BUILTIN_STRLEN __builtin_strlen ++#else ++# define nssv_BUILTIN_STRLEN strlen ++#endif ++ ++// C++ feature usage: ++ ++#if nssv_HAVE_CONSTEXPR_11 ++# define nssv_constexpr constexpr ++#else ++# define nssv_constexpr /*constexpr*/ ++#endif ++ ++#if nssv_HAVE_CONSTEXPR_14 ++# define nssv_constexpr14 constexpr ++#else ++# define nssv_constexpr14 /*constexpr*/ ++#endif ++ ++#if nssv_HAVE_EXPLICIT_CONVERSION ++# define nssv_explicit explicit ++#else ++# define nssv_explicit /*explicit*/ ++#endif ++ ++#if nssv_HAVE_INLINE_NAMESPACE ++# define nssv_inline_ns inline ++#else ++# define nssv_inline_ns /*inline*/ ++#endif ++ ++#if nssv_HAVE_NOEXCEPT ++# define nssv_noexcept noexcept ++#else ++# define nssv_noexcept /*noexcept*/ ++#endif ++ ++//#if nssv_HAVE_REF_QUALIFIER ++//# define nssv_ref_qual & ++//# define nssv_refref_qual && ++//#else ++//# define nssv_ref_qual /*&*/ ++//# define nssv_refref_qual /*&&*/ ++//#endif ++ ++#if nssv_HAVE_NULLPTR ++# define nssv_nullptr nullptr ++#else ++# define nssv_nullptr NULL ++#endif ++ ++#if nssv_HAVE_NODISCARD ++# define nssv_nodiscard [[nodiscard]] ++#else ++# define nssv_nodiscard /*[[nodiscard]]*/ ++#endif ++ ++// Additional includes: ++ ++#include ++#include ++#include ++#include ++#include // std::char_traits<> ++ ++#if ! nssv_CONFIG_NO_STREAM_INSERTION ++# include ++#endif ++ ++#if ! nssv_CONFIG_NO_EXCEPTIONS ++# include ++#endif ++ ++#if nssv_CPP11_OR_GREATER ++# include ++#endif ++ ++// Clang, GNUC, MSVC warning suppression macros: ++ ++#if defined(__clang__) ++# pragma clang diagnostic ignored "-Wreserved-user-defined-literal" ++# pragma clang diagnostic push ++# pragma clang diagnostic ignored "-Wuser-defined-literals" ++#elif defined(__GNUC__) ++# pragma GCC diagnostic push ++# pragma GCC diagnostic ignored "-Wliteral-suffix" ++#endif // __clang__ ++ ++#if nssv_COMPILER_MSVC_VERSION >= 140 ++# define nssv_SUPPRESS_MSGSL_WARNING(expr) [[gsl::suppress(expr)]] ++# define nssv_SUPPRESS_MSVC_WARNING(code, descr) __pragma(warning(suppress: code) ) ++# define nssv_DISABLE_MSVC_WARNINGS(codes) __pragma(warning(push)) __pragma(warning(disable: codes)) ++#else ++# define nssv_SUPPRESS_MSGSL_WARNING(expr) ++# define nssv_SUPPRESS_MSVC_WARNING(code, descr) ++# define nssv_DISABLE_MSVC_WARNINGS(codes) ++#endif ++ ++#if defined(__clang__) ++# define nssv_RESTORE_WARNINGS() _Pragma("clang diagnostic pop") ++#elif defined(__GNUC__) ++# define nssv_RESTORE_WARNINGS() _Pragma("GCC diagnostic pop") ++#elif nssv_COMPILER_MSVC_VERSION >= 140 ++# define nssv_RESTORE_WARNINGS() __pragma(warning(pop )) ++#else ++# define nssv_RESTORE_WARNINGS() ++#endif ++ ++// Suppress the following MSVC (GSL) warnings: ++// - C4455, non-gsl : 'operator ""sv': literal suffix identifiers that do not ++// start with an underscore are reserved ++// - C26472, gsl::t.1 : don't use a static_cast for arithmetic conversions; ++// use brace initialization, gsl::narrow_cast or gsl::narow ++// - C26481: gsl::b.1 : don't use pointer arithmetic. Use span instead ++ ++nssv_DISABLE_MSVC_WARNINGS( 4455 26481 26472 ) ++//nssv_DISABLE_CLANG_WARNINGS( "-Wuser-defined-literals" ) ++//nssv_DISABLE_GNUC_WARNINGS( -Wliteral-suffix ) ++ ++namespace nonstd { namespace sv_lite { ++ ++namespace detail { ++ ++// support constexpr comparison in C++14; ++// for C++17 and later, use provided traits: ++ ++template< typename CharT > ++inline nssv_constexpr14 int compare( CharT const * s1, CharT const * s2, std::size_t count ) ++{ ++ while ( count-- != 0 ) ++ { ++ if ( *s1 < *s2 ) return -1; ++ if ( *s1 > *s2 ) return +1; ++ ++s1; ++s2; ++ } ++ return 0; ++} ++ ++#if nssv_HAVE_BUILTIN_MEMCMP ++ ++// specialization of compare() for char, see also generic compare() above: ++ ++inline nssv_constexpr14 int compare( char const * s1, char const * s2, std::size_t count ) ++{ ++ return nssv_BUILTIN_MEMCMP( s1, s2, count ); ++} ++ ++#endif ++ ++#if nssv_HAVE_BUILTIN_STRLEN ++ ++// specialization of length() for char, see also generic length() further below: ++ ++inline nssv_constexpr std::size_t length( char const * s ) ++{ ++ return nssv_BUILTIN_STRLEN( s ); ++} ++ ++#endif ++ ++#if defined(__OPTIMIZE__) ++ ++// gcc, clang provide __OPTIMIZE__ ++// Expect tail call optimization to make length() non-recursive: ++ ++template< typename CharT > ++inline nssv_constexpr std::size_t length( CharT * s, std::size_t result = 0 ) ++{ ++ return *s == '\0' ? result : length( s + 1, result + 1 ); ++} ++ ++#else // OPTIMIZE ++ ++// non-recursive: ++ ++template< typename CharT > ++inline nssv_constexpr14 std::size_t length( CharT * s ) ++{ ++ std::size_t result = 0; ++ while ( *s++ != '\0' ) ++ { ++ ++result; ++ } ++ return result; ++} ++ ++#endif // OPTIMIZE ++ ++} // namespace detail ++ ++template ++< ++ class CharT, ++ class Traits = std::char_traits ++> ++class basic_string_view; ++ ++// ++// basic_string_view: ++// ++ ++template ++< ++ class CharT, ++ class Traits /* = std::char_traits */ ++> ++class basic_string_view ++{ ++public: ++ // Member types: ++ ++ typedef Traits traits_type; ++ typedef CharT value_type; ++ ++ typedef CharT * pointer; ++ typedef CharT const * const_pointer; ++ typedef CharT & reference; ++ typedef CharT const & const_reference; ++ ++ typedef const_pointer iterator; ++ typedef const_pointer const_iterator; ++ typedef std::reverse_iterator< const_iterator > reverse_iterator; ++ typedef std::reverse_iterator< const_iterator > const_reverse_iterator; ++ ++ typedef std::size_t size_type; ++ typedef std::ptrdiff_t difference_type; ++ ++ // 24.4.2.1 Construction and assignment: ++ ++ nssv_constexpr basic_string_view() nssv_noexcept ++ : data_( nssv_nullptr ) ++ , size_( 0 ) ++ {} ++ ++#if nssv_CPP11_OR_GREATER ++ nssv_constexpr basic_string_view( basic_string_view const & other ) nssv_noexcept = default; ++#else ++ nssv_constexpr basic_string_view( basic_string_view const & other ) nssv_noexcept ++ : data_( other.data_) ++ , size_( other.size_) ++ {} ++#endif ++ ++ nssv_constexpr basic_string_view( CharT const * s, size_type count ) nssv_noexcept // non-standard noexcept ++ : data_( s ) ++ , size_( count ) ++ {} ++ ++ nssv_constexpr basic_string_view( CharT const * s) nssv_noexcept // non-standard noexcept ++ : data_( s ) ++#if nssv_CPP17_OR_GREATER ++ , size_( Traits::length(s) ) ++#elif nssv_CPP11_OR_GREATER ++ , size_( detail::length(s) ) ++#else ++ , size_( Traits::length(s) ) ++#endif ++ {} ++ ++ // Assignment: ++ ++#if nssv_CPP11_OR_GREATER ++ nssv_constexpr14 basic_string_view & operator=( basic_string_view const & other ) nssv_noexcept = default; ++#else ++ nssv_constexpr14 basic_string_view & operator=( basic_string_view const & other ) nssv_noexcept ++ { ++ data_ = other.data_; ++ size_ = other.size_; ++ return *this; ++ } ++#endif ++ ++ // 24.4.2.2 Iterator support: ++ ++ nssv_constexpr const_iterator begin() const nssv_noexcept { return data_; } ++ nssv_constexpr const_iterator end() const nssv_noexcept { return data_ + size_; } ++ ++ nssv_constexpr const_iterator cbegin() const nssv_noexcept { return begin(); } ++ nssv_constexpr const_iterator cend() const nssv_noexcept { return end(); } ++ ++ nssv_constexpr const_reverse_iterator rbegin() const nssv_noexcept { return const_reverse_iterator( end() ); } ++ nssv_constexpr const_reverse_iterator rend() const nssv_noexcept { return const_reverse_iterator( begin() ); } ++ ++ nssv_constexpr const_reverse_iterator crbegin() const nssv_noexcept { return rbegin(); } ++ nssv_constexpr const_reverse_iterator crend() const nssv_noexcept { return rend(); } ++ ++ // 24.4.2.3 Capacity: ++ ++ nssv_constexpr size_type size() const nssv_noexcept { return size_; } ++ nssv_constexpr size_type length() const nssv_noexcept { return size_; } ++ nssv_constexpr size_type max_size() const nssv_noexcept { return (std::numeric_limits< size_type >::max)(); } ++ ++ // since C++20 ++ nssv_nodiscard nssv_constexpr bool empty() const nssv_noexcept ++ { ++ return 0 == size_; ++ } ++ ++ // 24.4.2.4 Element access: ++ ++ nssv_constexpr const_reference operator[]( size_type pos ) const ++ { ++ return data_at( pos ); ++ } ++ ++ nssv_constexpr14 const_reference at( size_type pos ) const ++ { ++#if nssv_CONFIG_NO_EXCEPTIONS ++ assert( pos < size() ); ++#else ++ if ( pos >= size() ) ++ { ++ throw std::out_of_range("nonstd::string_view::at()"); ++ } ++#endif ++ return data_at( pos ); ++ } ++ ++ nssv_constexpr const_reference front() const { return data_at( 0 ); } ++ nssv_constexpr const_reference back() const { return data_at( size() - 1 ); } ++ ++ nssv_constexpr const_pointer data() const nssv_noexcept { return data_; } ++ ++ // 24.4.2.5 Modifiers: ++ ++ nssv_constexpr14 void remove_prefix( size_type n ) ++ { ++ assert( n <= size() ); ++ data_ += n; ++ size_ -= n; ++ } ++ ++ nssv_constexpr14 void remove_suffix( size_type n ) ++ { ++ assert( n <= size() ); ++ size_ -= n; ++ } ++ ++ nssv_constexpr14 void swap( basic_string_view & other ) nssv_noexcept ++ { ++ const basic_string_view tmp(other); ++ other = *this; ++ *this = tmp; ++ } ++ ++ // 24.4.2.6 String operations: ++ ++ size_type copy( CharT * dest, size_type n, size_type pos = 0 ) const ++ { ++#if nssv_CONFIG_NO_EXCEPTIONS ++ assert( pos <= size() ); ++#else ++ if ( pos > size() ) ++ { ++ throw std::out_of_range("nonstd::string_view::copy()"); ++ } ++#endif ++ const size_type rlen = (std::min)( n, size() - pos ); ++ ++ (void) Traits::copy( dest, data() + pos, rlen ); ++ ++ return rlen; ++ } ++ ++ nssv_constexpr14 basic_string_view substr( size_type pos = 0, size_type n = npos ) const ++ { ++#if nssv_CONFIG_NO_EXCEPTIONS ++ assert( pos <= size() ); ++#else ++ if ( pos > size() ) ++ { ++ throw std::out_of_range("nonstd::string_view::substr()"); ++ } ++#endif ++ return basic_string_view( data() + pos, (std::min)( n, size() - pos ) ); ++ } ++ ++ // compare(), 6x: ++ ++ nssv_constexpr14 int compare( basic_string_view other ) const nssv_noexcept // (1) ++ { ++#if nssv_CPP17_OR_GREATER ++ if ( const int result = Traits::compare( data(), other.data(), (std::min)( size(), other.size() ) ) ) ++#else ++ if ( const int result = detail::compare( data(), other.data(), (std::min)( size(), other.size() ) ) ) ++#endif ++ { ++ return result; ++ } ++ ++ return size() == other.size() ? 0 : size() < other.size() ? -1 : 1; ++ } ++ ++ nssv_constexpr int compare( size_type pos1, size_type n1, basic_string_view other ) const // (2) ++ { ++ return substr( pos1, n1 ).compare( other ); ++ } ++ ++ nssv_constexpr int compare( size_type pos1, size_type n1, basic_string_view other, size_type pos2, size_type n2 ) const // (3) ++ { ++ return substr( pos1, n1 ).compare( other.substr( pos2, n2 ) ); ++ } ++ ++ nssv_constexpr int compare( CharT const * s ) const // (4) ++ { ++ return compare( basic_string_view( s ) ); ++ } ++ ++ nssv_constexpr int compare( size_type pos1, size_type n1, CharT const * s ) const // (5) ++ { ++ return substr( pos1, n1 ).compare( basic_string_view( s ) ); ++ } ++ ++ nssv_constexpr int compare( size_type pos1, size_type n1, CharT const * s, size_type n2 ) const // (6) ++ { ++ return substr( pos1, n1 ).compare( basic_string_view( s, n2 ) ); ++ } ++ ++ // 24.4.2.7 Searching: ++ ++ // starts_with(), 3x, since C++20: ++ ++ nssv_constexpr bool starts_with( basic_string_view v ) const nssv_noexcept // (1) ++ { ++ return size() >= v.size() && compare( 0, v.size(), v ) == 0; ++ } ++ ++ nssv_constexpr bool starts_with( CharT c ) const nssv_noexcept // (2) ++ { ++ return starts_with( basic_string_view( &c, 1 ) ); ++ } ++ ++ nssv_constexpr bool starts_with( CharT const * s ) const // (3) ++ { ++ return starts_with( basic_string_view( s ) ); ++ } ++ ++ // ends_with(), 3x, since C++20: ++ ++ nssv_constexpr bool ends_with( basic_string_view v ) const nssv_noexcept // (1) ++ { ++ return size() >= v.size() && compare( size() - v.size(), npos, v ) == 0; ++ } ++ ++ nssv_constexpr bool ends_with( CharT c ) const nssv_noexcept // (2) ++ { ++ return ends_with( basic_string_view( &c, 1 ) ); ++ } ++ ++ nssv_constexpr bool ends_with( CharT const * s ) const // (3) ++ { ++ return ends_with( basic_string_view( s ) ); ++ } ++ ++ // find(), 4x: ++ ++ nssv_constexpr14 size_type find( basic_string_view v, size_type pos = 0 ) const nssv_noexcept // (1) ++ { ++ return assert( v.size() == 0 || v.data() != nssv_nullptr ) ++ , pos >= size() ++ ? npos ++ : to_pos( std::search( cbegin() + pos, cend(), v.cbegin(), v.cend(), Traits::eq ) ); ++ } ++ ++ nssv_constexpr14 size_type find( CharT c, size_type pos = 0 ) const nssv_noexcept // (2) ++ { ++ return find( basic_string_view( &c, 1 ), pos ); ++ } ++ ++ nssv_constexpr14 size_type find( CharT const * s, size_type pos, size_type n ) const // (3) ++ { ++ return find( basic_string_view( s, n ), pos ); ++ } ++ ++ nssv_constexpr14 size_type find( CharT const * s, size_type pos = 0 ) const // (4) ++ { ++ return find( basic_string_view( s ), pos ); ++ } ++ ++ // rfind(), 4x: ++ ++ nssv_constexpr14 size_type rfind( basic_string_view v, size_type pos = npos ) const nssv_noexcept // (1) ++ { ++ if ( size() < v.size() ) ++ { ++ return npos; ++ } ++ ++ if ( v.empty() ) ++ { ++ return (std::min)( size(), pos ); ++ } ++ ++ const_iterator last = cbegin() + (std::min)( size() - v.size(), pos ) + v.size(); ++ const_iterator result = std::find_end( cbegin(), last, v.cbegin(), v.cend(), Traits::eq ); ++ ++ return result != last ? size_type( result - cbegin() ) : npos; ++ } ++ ++ nssv_constexpr14 size_type rfind( CharT c, size_type pos = npos ) const nssv_noexcept // (2) ++ { ++ return rfind( basic_string_view( &c, 1 ), pos ); ++ } ++ ++ nssv_constexpr14 size_type rfind( CharT const * s, size_type pos, size_type n ) const // (3) ++ { ++ return rfind( basic_string_view( s, n ), pos ); ++ } ++ ++ nssv_constexpr14 size_type rfind( CharT const * s, size_type pos = npos ) const // (4) ++ { ++ return rfind( basic_string_view( s ), pos ); ++ } ++ ++ // find_first_of(), 4x: ++ ++ nssv_constexpr size_type find_first_of( basic_string_view v, size_type pos = 0 ) const nssv_noexcept // (1) ++ { ++ return pos >= size() ++ ? npos ++ : to_pos( std::find_first_of( cbegin() + pos, cend(), v.cbegin(), v.cend(), Traits::eq ) ); ++ } ++ ++ nssv_constexpr size_type find_first_of( CharT c, size_type pos = 0 ) const nssv_noexcept // (2) ++ { ++ return find_first_of( basic_string_view( &c, 1 ), pos ); ++ } ++ ++ nssv_constexpr size_type find_first_of( CharT const * s, size_type pos, size_type n ) const // (3) ++ { ++ return find_first_of( basic_string_view( s, n ), pos ); ++ } ++ ++ nssv_constexpr size_type find_first_of( CharT const * s, size_type pos = 0 ) const // (4) ++ { ++ return find_first_of( basic_string_view( s ), pos ); ++ } ++ ++ // find_last_of(), 4x: ++ ++ nssv_constexpr size_type find_last_of( basic_string_view v, size_type pos = npos ) const nssv_noexcept // (1) ++ { ++ return empty() ++ ? npos ++ : pos >= size() ++ ? find_last_of( v, size() - 1 ) ++ : to_pos( std::find_first_of( const_reverse_iterator( cbegin() + pos + 1 ), crend(), v.cbegin(), v.cend(), Traits::eq ) ); ++ } ++ ++ nssv_constexpr size_type find_last_of( CharT c, size_type pos = npos ) const nssv_noexcept // (2) ++ { ++ return find_last_of( basic_string_view( &c, 1 ), pos ); ++ } ++ ++ nssv_constexpr size_type find_last_of( CharT const * s, size_type pos, size_type count ) const // (3) ++ { ++ return find_last_of( basic_string_view( s, count ), pos ); ++ } ++ ++ nssv_constexpr size_type find_last_of( CharT const * s, size_type pos = npos ) const // (4) ++ { ++ return find_last_of( basic_string_view( s ), pos ); ++ } ++ ++ // find_first_not_of(), 4x: ++ ++ nssv_constexpr size_type find_first_not_of( basic_string_view v, size_type pos = 0 ) const nssv_noexcept // (1) ++ { ++ return pos >= size() ++ ? npos ++ : to_pos( std::find_if( cbegin() + pos, cend(), not_in_view( v ) ) ); ++ } ++ ++ nssv_constexpr size_type find_first_not_of( CharT c, size_type pos = 0 ) const nssv_noexcept // (2) ++ { ++ return find_first_not_of( basic_string_view( &c, 1 ), pos ); ++ } ++ ++ nssv_constexpr size_type find_first_not_of( CharT const * s, size_type pos, size_type count ) const // (3) ++ { ++ return find_first_not_of( basic_string_view( s, count ), pos ); ++ } ++ ++ nssv_constexpr size_type find_first_not_of( CharT const * s, size_type pos = 0 ) const // (4) ++ { ++ return find_first_not_of( basic_string_view( s ), pos ); ++ } ++ ++ // find_last_not_of(), 4x: ++ ++ nssv_constexpr size_type find_last_not_of( basic_string_view v, size_type pos = npos ) const nssv_noexcept // (1) ++ { ++ return empty() ++ ? npos ++ : pos >= size() ++ ? find_last_not_of( v, size() - 1 ) ++ : to_pos( std::find_if( const_reverse_iterator( cbegin() + pos + 1 ), crend(), not_in_view( v ) ) ); ++ } ++ ++ nssv_constexpr size_type find_last_not_of( CharT c, size_type pos = npos ) const nssv_noexcept // (2) ++ { ++ return find_last_not_of( basic_string_view( &c, 1 ), pos ); ++ } ++ ++ nssv_constexpr size_type find_last_not_of( CharT const * s, size_type pos, size_type count ) const // (3) ++ { ++ return find_last_not_of( basic_string_view( s, count ), pos ); ++ } ++ ++ nssv_constexpr size_type find_last_not_of( CharT const * s, size_type pos = npos ) const // (4) ++ { ++ return find_last_not_of( basic_string_view( s ), pos ); ++ } ++ ++ // Constants: ++ ++#if nssv_CPP17_OR_GREATER ++ static nssv_constexpr size_type npos = size_type(-1); ++#elif nssv_CPP11_OR_GREATER ++ enum : size_type { npos = size_type(-1) }; ++#else ++ enum { npos = size_type(-1) }; ++#endif ++ ++private: ++ struct not_in_view ++ { ++ const basic_string_view v; ++ ++ nssv_constexpr explicit not_in_view( basic_string_view v_ ) : v( v_ ) {} ++ ++ nssv_constexpr bool operator()( CharT c ) const ++ { ++ return npos == v.find_first_of( c ); ++ } ++ }; ++ ++ nssv_constexpr size_type to_pos( const_iterator it ) const ++ { ++ return it == cend() ? npos : size_type( it - cbegin() ); ++ } ++ ++ nssv_constexpr size_type to_pos( const_reverse_iterator it ) const ++ { ++ return it == crend() ? npos : size_type( crend() - it - 1 ); ++ } ++ ++ nssv_constexpr const_reference data_at( size_type pos ) const ++ { ++#if nssv_BETWEEN( nssv_COMPILER_GNUC_VERSION, 1, 500 ) ++ return data_[pos]; ++#else ++ return assert( pos < size() ), data_[pos]; ++#endif ++ } ++ ++private: ++ const_pointer data_; ++ size_type size_; ++ ++public: ++#if nssv_CONFIG_CONVERSION_STD_STRING_CLASS_METHODS ++ ++ template< class Allocator > ++ basic_string_view( std::basic_string const & s ) nssv_noexcept ++ : data_( s.data() ) ++ , size_( s.size() ) ++ {} ++ ++#if nssv_HAVE_EXPLICIT_CONVERSION ++ ++ template< class Allocator > ++ explicit operator std::basic_string() const ++ { ++ return to_string( Allocator() ); ++ } ++ ++#endif // nssv_HAVE_EXPLICIT_CONVERSION ++ ++#if nssv_CPP11_OR_GREATER ++ ++ template< class Allocator = std::allocator > ++ std::basic_string ++ to_string( Allocator const & a = Allocator() ) const ++ { ++ return std::basic_string( begin(), end(), a ); ++ } ++ ++#else ++ ++ std::basic_string ++ to_string() const ++ { ++ return std::basic_string( begin(), end() ); ++ } ++ ++ template< class Allocator > ++ std::basic_string ++ to_string( Allocator const & a ) const ++ { ++ return std::basic_string( begin(), end(), a ); ++ } ++ ++#endif // nssv_CPP11_OR_GREATER ++ ++#endif // nssv_CONFIG_CONVERSION_STD_STRING_CLASS_METHODS ++}; ++ ++// ++// Non-member functions: ++// ++ ++// 24.4.3 Non-member comparison functions: ++// lexicographically compare two string views (function template): ++ ++template< class CharT, class Traits > ++nssv_constexpr bool operator== ( ++ basic_string_view lhs, ++ basic_string_view rhs ) nssv_noexcept ++{ return lhs.size() == rhs.size() && lhs.compare( rhs ) == 0; } ++ ++template< class CharT, class Traits > ++nssv_constexpr bool operator!= ( ++ basic_string_view lhs, ++ basic_string_view rhs ) nssv_noexcept ++{ return !( lhs == rhs ); } ++ ++template< class CharT, class Traits > ++nssv_constexpr bool operator< ( ++ basic_string_view lhs, ++ basic_string_view rhs ) nssv_noexcept ++{ return lhs.compare( rhs ) < 0; } ++ ++template< class CharT, class Traits > ++nssv_constexpr bool operator<= ( ++ basic_string_view lhs, ++ basic_string_view rhs ) nssv_noexcept ++{ return lhs.compare( rhs ) <= 0; } ++ ++template< class CharT, class Traits > ++nssv_constexpr bool operator> ( ++ basic_string_view lhs, ++ basic_string_view rhs ) nssv_noexcept ++{ return lhs.compare( rhs ) > 0; } ++ ++template< class CharT, class Traits > ++nssv_constexpr bool operator>= ( ++ basic_string_view lhs, ++ basic_string_view rhs ) nssv_noexcept ++{ return lhs.compare( rhs ) >= 0; } ++ ++// Let S be basic_string_view, and sv be an instance of S. ++// Implementations shall provide sufficient additional overloads marked ++// constexpr and noexcept so that an object t with an implicit conversion ++// to S can be compared according to Table 67. ++ ++#if ! nssv_CPP11_OR_GREATER || nssv_BETWEEN( nssv_COMPILER_MSVC_VERSION, 100, 141 ) ++ ++// accommodate for older compilers: ++ ++// == ++ ++template< class CharT, class Traits> ++nssv_constexpr bool operator==( ++ basic_string_view lhs, ++ CharT const * rhs ) nssv_noexcept ++{ return lhs.size() == detail::length( rhs ) && lhs.compare( rhs ) == 0; } ++ ++template< class CharT, class Traits> ++nssv_constexpr bool operator==( ++ CharT const * lhs, ++ basic_string_view rhs ) nssv_noexcept ++{ return detail::length( lhs ) == rhs.size() && rhs.compare( lhs ) == 0; } ++ ++template< class CharT, class Traits> ++nssv_constexpr bool operator==( ++ basic_string_view lhs, ++ std::basic_string rhs ) nssv_noexcept ++{ return lhs.size() == rhs.size() && lhs.compare( rhs ) == 0; } ++ ++template< class CharT, class Traits> ++nssv_constexpr bool operator==( ++ std::basic_string rhs, ++ basic_string_view lhs ) nssv_noexcept ++{ return lhs.size() == rhs.size() && lhs.compare( rhs ) == 0; } ++ ++// != ++ ++template< class CharT, class Traits> ++nssv_constexpr bool operator!=( ++ basic_string_view lhs, ++ CharT const * rhs ) nssv_noexcept ++{ return !( lhs == rhs ); } ++ ++template< class CharT, class Traits> ++nssv_constexpr bool operator!=( ++ CharT const * lhs, ++ basic_string_view rhs ) nssv_noexcept ++{ return !( lhs == rhs ); } ++ ++template< class CharT, class Traits> ++nssv_constexpr bool operator!=( ++ basic_string_view lhs, ++ std::basic_string rhs ) nssv_noexcept ++{ return !( lhs == rhs ); } ++ ++template< class CharT, class Traits> ++nssv_constexpr bool operator!=( ++ std::basic_string rhs, ++ basic_string_view lhs ) nssv_noexcept ++{ return !( lhs == rhs ); } ++ ++// < ++ ++template< class CharT, class Traits> ++nssv_constexpr bool operator<( ++ basic_string_view lhs, ++ CharT const * rhs ) nssv_noexcept ++{ return lhs.compare( rhs ) < 0; } ++ ++template< class CharT, class Traits> ++nssv_constexpr bool operator<( ++ CharT const * lhs, ++ basic_string_view rhs ) nssv_noexcept ++{ return rhs.compare( lhs ) > 0; } ++ ++template< class CharT, class Traits> ++nssv_constexpr bool operator<( ++ basic_string_view lhs, ++ std::basic_string rhs ) nssv_noexcept ++{ return lhs.compare( rhs ) < 0; } ++ ++template< class CharT, class Traits> ++nssv_constexpr bool operator<( ++ std::basic_string rhs, ++ basic_string_view lhs ) nssv_noexcept ++{ return rhs.compare( lhs ) > 0; } ++ ++// <= ++ ++template< class CharT, class Traits> ++nssv_constexpr bool operator<=( ++ basic_string_view lhs, ++ CharT const * rhs ) nssv_noexcept ++{ return lhs.compare( rhs ) <= 0; } ++ ++template< class CharT, class Traits> ++nssv_constexpr bool operator<=( ++ CharT const * lhs, ++ basic_string_view rhs ) nssv_noexcept ++{ return rhs.compare( lhs ) >= 0; } ++ ++template< class CharT, class Traits> ++nssv_constexpr bool operator<=( ++ basic_string_view lhs, ++ std::basic_string rhs ) nssv_noexcept ++{ return lhs.compare( rhs ) <= 0; } ++ ++template< class CharT, class Traits> ++nssv_constexpr bool operator<=( ++ std::basic_string rhs, ++ basic_string_view lhs ) nssv_noexcept ++{ return rhs.compare( lhs ) >= 0; } ++ ++// > ++ ++template< class CharT, class Traits> ++nssv_constexpr bool operator>( ++ basic_string_view lhs, ++ CharT const * rhs ) nssv_noexcept ++{ return lhs.compare( rhs ) > 0; } ++ ++template< class CharT, class Traits> ++nssv_constexpr bool operator>( ++ CharT const * lhs, ++ basic_string_view rhs ) nssv_noexcept ++{ return rhs.compare( lhs ) < 0; } ++ ++template< class CharT, class Traits> ++nssv_constexpr bool operator>( ++ basic_string_view lhs, ++ std::basic_string rhs ) nssv_noexcept ++{ return lhs.compare( rhs ) > 0; } ++ ++template< class CharT, class Traits> ++nssv_constexpr bool operator>( ++ std::basic_string rhs, ++ basic_string_view lhs ) nssv_noexcept ++{ return rhs.compare( lhs ) < 0; } ++ ++// >= ++ ++template< class CharT, class Traits> ++nssv_constexpr bool operator>=( ++ basic_string_view lhs, ++ CharT const * rhs ) nssv_noexcept ++{ return lhs.compare( rhs ) >= 0; } ++ ++template< class CharT, class Traits> ++nssv_constexpr bool operator>=( ++ CharT const * lhs, ++ basic_string_view rhs ) nssv_noexcept ++{ return rhs.compare( lhs ) <= 0; } ++ ++template< class CharT, class Traits> ++nssv_constexpr bool operator>=( ++ basic_string_view lhs, ++ std::basic_string rhs ) nssv_noexcept ++{ return lhs.compare( rhs ) >= 0; } ++ ++template< class CharT, class Traits> ++nssv_constexpr bool operator>=( ++ std::basic_string rhs, ++ basic_string_view lhs ) nssv_noexcept ++{ return rhs.compare( lhs ) <= 0; } ++ ++#else // newer compilers: ++ ++#define nssv_BASIC_STRING_VIEW_I(T,U) typename std::decay< basic_string_view >::type ++ ++#if defined(_MSC_VER) // issue 40 ++# define nssv_MSVC_ORDER(x) , int=x ++#else ++# define nssv_MSVC_ORDER(x) /*, int=x*/ ++#endif ++ ++// == ++ ++template< class CharT, class Traits nssv_MSVC_ORDER(1) > ++nssv_constexpr bool operator==( ++ basic_string_view lhs, ++ nssv_BASIC_STRING_VIEW_I(CharT, Traits) rhs ) nssv_noexcept ++{ return lhs.size() == rhs.size() && lhs.compare( rhs ) == 0; } ++ ++template< class CharT, class Traits nssv_MSVC_ORDER(2) > ++nssv_constexpr bool operator==( ++ nssv_BASIC_STRING_VIEW_I(CharT, Traits) lhs, ++ basic_string_view rhs ) nssv_noexcept ++{ return lhs.size() == rhs.size() && lhs.compare( rhs ) == 0; } ++ ++// != ++ ++template< class CharT, class Traits nssv_MSVC_ORDER(1) > ++nssv_constexpr bool operator!= ( ++ basic_string_view < CharT, Traits > lhs, ++ nssv_BASIC_STRING_VIEW_I( CharT, Traits ) rhs ) nssv_noexcept ++{ return !( lhs == rhs ); } ++ ++template< class CharT, class Traits nssv_MSVC_ORDER(2) > ++nssv_constexpr bool operator!= ( ++ nssv_BASIC_STRING_VIEW_I( CharT, Traits ) lhs, ++ basic_string_view < CharT, Traits > rhs ) nssv_noexcept ++{ return !( lhs == rhs ); } ++ ++// < ++ ++template< class CharT, class Traits nssv_MSVC_ORDER(1) > ++nssv_constexpr bool operator< ( ++ basic_string_view < CharT, Traits > lhs, ++ nssv_BASIC_STRING_VIEW_I( CharT, Traits ) rhs ) nssv_noexcept ++{ return lhs.compare( rhs ) < 0; } ++ ++template< class CharT, class Traits nssv_MSVC_ORDER(2) > ++nssv_constexpr bool operator< ( ++ nssv_BASIC_STRING_VIEW_I( CharT, Traits ) lhs, ++ basic_string_view < CharT, Traits > rhs ) nssv_noexcept ++{ return lhs.compare( rhs ) < 0; } ++ ++// <= ++ ++template< class CharT, class Traits nssv_MSVC_ORDER(1) > ++nssv_constexpr bool operator<= ( ++ basic_string_view < CharT, Traits > lhs, ++ nssv_BASIC_STRING_VIEW_I( CharT, Traits ) rhs ) nssv_noexcept ++{ return lhs.compare( rhs ) <= 0; } ++ ++template< class CharT, class Traits nssv_MSVC_ORDER(2) > ++nssv_constexpr bool operator<= ( ++ nssv_BASIC_STRING_VIEW_I( CharT, Traits ) lhs, ++ basic_string_view < CharT, Traits > rhs ) nssv_noexcept ++{ return lhs.compare( rhs ) <= 0; } ++ ++// > ++ ++template< class CharT, class Traits nssv_MSVC_ORDER(1) > ++nssv_constexpr bool operator> ( ++ basic_string_view < CharT, Traits > lhs, ++ nssv_BASIC_STRING_VIEW_I( CharT, Traits ) rhs ) nssv_noexcept ++{ return lhs.compare( rhs ) > 0; } ++ ++template< class CharT, class Traits nssv_MSVC_ORDER(2) > ++nssv_constexpr bool operator> ( ++ nssv_BASIC_STRING_VIEW_I( CharT, Traits ) lhs, ++ basic_string_view < CharT, Traits > rhs ) nssv_noexcept ++{ return lhs.compare( rhs ) > 0; } ++ ++// >= ++ ++template< class CharT, class Traits nssv_MSVC_ORDER(1) > ++nssv_constexpr bool operator>= ( ++ basic_string_view < CharT, Traits > lhs, ++ nssv_BASIC_STRING_VIEW_I( CharT, Traits ) rhs ) nssv_noexcept ++{ return lhs.compare( rhs ) >= 0; } ++ ++template< class CharT, class Traits nssv_MSVC_ORDER(2) > ++nssv_constexpr bool operator>= ( ++ nssv_BASIC_STRING_VIEW_I( CharT, Traits ) lhs, ++ basic_string_view < CharT, Traits > rhs ) nssv_noexcept ++{ return lhs.compare( rhs ) >= 0; } ++ ++#undef nssv_MSVC_ORDER ++#undef nssv_BASIC_STRING_VIEW_I ++ ++#endif // compiler-dependent approach to comparisons ++ ++// 24.4.4 Inserters and extractors: ++ ++#if ! nssv_CONFIG_NO_STREAM_INSERTION ++ ++namespace detail { ++ ++template< class Stream > ++void write_padding( Stream & os, std::streamsize n ) ++{ ++ for ( std::streamsize i = 0; i < n; ++i ) ++ os.rdbuf()->sputc( os.fill() ); ++} ++ ++template< class Stream, class View > ++Stream & write_to_stream( Stream & os, View const & sv ) ++{ ++ typename Stream::sentry sentry( os ); ++ ++ if ( !os ) ++ return os; ++ ++ const std::streamsize length = static_cast( sv.length() ); ++ ++ // Whether, and how, to pad: ++ const bool pad = ( length < os.width() ); ++ const bool left_pad = pad && ( os.flags() & std::ios_base::adjustfield ) == std::ios_base::right; ++ ++ if ( left_pad ) ++ write_padding( os, os.width() - length ); ++ ++ // Write span characters: ++ os.rdbuf()->sputn( sv.begin(), length ); ++ ++ if ( pad && !left_pad ) ++ write_padding( os, os.width() - length ); ++ ++ // Reset output stream width: ++ os.width( 0 ); ++ ++ return os; ++} ++ ++} // namespace detail ++ ++template< class CharT, class Traits > ++std::basic_ostream & ++operator<<( ++ std::basic_ostream& os, ++ basic_string_view sv ) ++{ ++ return detail::write_to_stream( os, sv ); ++} ++ ++#endif // nssv_CONFIG_NO_STREAM_INSERTION ++ ++// Several typedefs for common character types are provided: ++ ++typedef basic_string_view string_view; ++typedef basic_string_view wstring_view; ++#if nssv_HAVE_WCHAR16_T ++typedef basic_string_view u16string_view; ++typedef basic_string_view u32string_view; ++#endif ++ ++}} // namespace nonstd::sv_lite ++ ++// ++// 24.4.6 Suffix for basic_string_view literals: ++// ++ ++#if nssv_HAVE_USER_DEFINED_LITERALS ++ ++namespace nonstd { ++nssv_inline_ns namespace literals { ++nssv_inline_ns namespace string_view_literals { ++ ++#if nssv_CONFIG_STD_SV_OPERATOR && nssv_HAVE_STD_DEFINED_LITERALS ++ ++nssv_constexpr nonstd::sv_lite::string_view operator "" sv( const char* str, size_t len ) nssv_noexcept // (1) ++{ ++ return nonstd::sv_lite::string_view{ str, len }; ++} ++ ++nssv_constexpr nonstd::sv_lite::u16string_view operator "" sv( const char16_t* str, size_t len ) nssv_noexcept // (2) ++{ ++ return nonstd::sv_lite::u16string_view{ str, len }; ++} ++ ++nssv_constexpr nonstd::sv_lite::u32string_view operator "" sv( const char32_t* str, size_t len ) nssv_noexcept // (3) ++{ ++ return nonstd::sv_lite::u32string_view{ str, len }; ++} ++ ++nssv_constexpr nonstd::sv_lite::wstring_view operator "" sv( const wchar_t* str, size_t len ) nssv_noexcept // (4) ++{ ++ return nonstd::sv_lite::wstring_view{ str, len }; ++} ++ ++#endif // nssv_CONFIG_STD_SV_OPERATOR && nssv_HAVE_STD_DEFINED_LITERALS ++ ++#if nssv_CONFIG_USR_SV_OPERATOR ++ ++nssv_constexpr nonstd::sv_lite::string_view operator "" _sv( const char* str, size_t len ) nssv_noexcept // (1) ++{ ++ return nonstd::sv_lite::string_view{ str, len }; ++} ++ ++nssv_constexpr nonstd::sv_lite::u16string_view operator "" _sv( const char16_t* str, size_t len ) nssv_noexcept // (2) ++{ ++ return nonstd::sv_lite::u16string_view{ str, len }; ++} ++ ++nssv_constexpr nonstd::sv_lite::u32string_view operator "" _sv( const char32_t* str, size_t len ) nssv_noexcept // (3) ++{ ++ return nonstd::sv_lite::u32string_view{ str, len }; ++} ++ ++nssv_constexpr nonstd::sv_lite::wstring_view operator "" _sv( const wchar_t* str, size_t len ) nssv_noexcept // (4) ++{ ++ return nonstd::sv_lite::wstring_view{ str, len }; ++} ++ ++#endif // nssv_CONFIG_USR_SV_OPERATOR ++ ++}}} // namespace nonstd::literals::string_view_literals ++ ++#endif ++ ++// ++// Extensions for std::string: ++// ++ ++#if nssv_CONFIG_CONVERSION_STD_STRING_FREE_FUNCTIONS ++ ++namespace nonstd { ++namespace sv_lite { ++ ++// Exclude MSVC 14 (19.00): it yields ambiguous to_string(): ++ ++#if nssv_CPP11_OR_GREATER && nssv_COMPILER_MSVC_VERSION != 140 ++ ++template< class CharT, class Traits, class Allocator = std::allocator > ++std::basic_string ++to_string( basic_string_view v, Allocator const & a = Allocator() ) ++{ ++ return std::basic_string( v.begin(), v.end(), a ); ++} ++ ++#else ++ ++template< class CharT, class Traits > ++std::basic_string ++to_string( basic_string_view v ) ++{ ++ return std::basic_string( v.begin(), v.end() ); ++} ++ ++template< class CharT, class Traits, class Allocator > ++std::basic_string ++to_string( basic_string_view v, Allocator const & a ) ++{ ++ return std::basic_string( v.begin(), v.end(), a ); ++} ++ ++#endif // nssv_CPP11_OR_GREATER ++ ++template< class CharT, class Traits, class Allocator > ++basic_string_view ++to_string_view( std::basic_string const & s ) ++{ ++ return basic_string_view( s.data(), s.size() ); ++} ++ ++}} // namespace nonstd::sv_lite ++ ++#endif // nssv_CONFIG_CONVERSION_STD_STRING_FREE_FUNCTIONS ++ ++// ++// make types and algorithms available in namespace nonstd: ++// ++ ++namespace nonstd { ++ ++using sv_lite::basic_string_view; ++using sv_lite::string_view; ++using sv_lite::wstring_view; ++ ++#if nssv_HAVE_WCHAR16_T ++using sv_lite::u16string_view; ++#endif ++#if nssv_HAVE_WCHAR32_T ++using sv_lite::u32string_view; ++#endif ++ ++// literal "sv" ++ ++using sv_lite::operator==; ++using sv_lite::operator!=; ++using sv_lite::operator<; ++using sv_lite::operator<=; ++using sv_lite::operator>; ++using sv_lite::operator>=; ++ ++#if ! nssv_CONFIG_NO_STREAM_INSERTION ++using sv_lite::operator<<; ++#endif ++ ++#if nssv_CONFIG_CONVERSION_STD_STRING_FREE_FUNCTIONS ++using sv_lite::to_string; ++using sv_lite::to_string_view; ++#endif ++ ++} // namespace nonstd ++ ++// 24.4.5 Hash support (C++11): ++ ++// Note: The hash value of a string view object is equal to the hash value of ++// the corresponding string object. ++ ++#if nssv_HAVE_STD_HASH ++ ++#include ++ ++namespace std { ++ ++template<> ++struct hash< nonstd::string_view > ++{ ++public: ++ std::size_t operator()( nonstd::string_view v ) const nssv_noexcept ++ { ++ return std::hash()( std::string( v.data(), v.size() ) ); ++ } ++}; ++ ++template<> ++struct hash< nonstd::wstring_view > ++{ ++public: ++ std::size_t operator()( nonstd::wstring_view v ) const nssv_noexcept ++ { ++ return std::hash()( std::wstring( v.data(), v.size() ) ); ++ } ++}; ++ ++template<> ++struct hash< nonstd::u16string_view > ++{ ++public: ++ std::size_t operator()( nonstd::u16string_view v ) const nssv_noexcept ++ { ++ return std::hash()( std::u16string( v.data(), v.size() ) ); ++ } ++}; ++ ++template<> ++struct hash< nonstd::u32string_view > ++{ ++public: ++ std::size_t operator()( nonstd::u32string_view v ) const nssv_noexcept ++ { ++ return std::hash()( std::u32string( v.data(), v.size() ) ); ++ } ++}; ++ ++} // namespace std ++ ++#endif // nssv_HAVE_STD_HASH ++ ++nssv_RESTORE_WARNINGS() ++ ++#endif // nssv_HAVE_STD_STRING_VIEW ++#endif // NONSTD_SV_LITE_H_INCLUDED ++/* end file include/simdjson/nonstd/string_view.hpp */ ++SIMDJSON_POP_DISABLE_WARNINGS ++ ++namespace std { ++ using string_view = nonstd::string_view; ++} ++#endif // SIMDJSON_HAS_STRING_VIEW ++#undef SIMDJSON_HAS_STRING_VIEW // We are not going to need this macro anymore. ++ ++/// If EXPR is an error, returns it. ++#define SIMDJSON_TRY(EXPR) { auto _err = (EXPR); if (_err) { return _err; } } ++ ++// Unless the programmer has already set SIMDJSON_DEVELOPMENT_CHECKS, ++// we want to set it under debug builds. We detect a debug build ++// under Visual Studio when the _DEBUG macro is set. Under the other ++// compilers, we use the fact that they define __OPTIMIZE__ whenever ++// they allow optimizations. ++// It is possible that this could miss some cases where SIMDJSON_DEVELOPMENT_CHECKS ++// is helpful, but the programmer can set the macro SIMDJSON_DEVELOPMENT_CHECKS. ++// It could also wrongly set SIMDJSON_DEVELOPMENT_CHECKS (e.g., if the programmer ++// sets _DEBUG in a release build under Visual Studio, or if some compiler fails to ++// set the __OPTIMIZE__ macro). ++#ifndef SIMDJSON_DEVELOPMENT_CHECKS ++#ifdef _MSC_VER ++// Visual Studio seems to set _DEBUG for debug builds. ++#ifdef _DEBUG ++#define SIMDJSON_DEVELOPMENT_CHECKS 1 ++#endif // _DEBUG ++#else // _MSC_VER ++// All other compilers appear to set __OPTIMIZE__ to a positive integer ++// when the compiler is optimizing. ++#ifndef __OPTIMIZE__ ++#define SIMDJSON_DEVELOPMENT_CHECKS 1 ++#endif // __OPTIMIZE__ ++#endif // _MSC_VER ++#endif // SIMDJSON_DEVELOPMENT_CHECKS ++ ++// The SIMDJSON_CHECK_EOF macro is a feature flag for the "don't require padding" ++// feature. ++ ++#if SIMDJSON_CPLUSPLUS17 ++// if we have C++, then fallthrough is a default attribute ++# define simdjson_fallthrough [[fallthrough]] ++// check if we have __attribute__ support ++#elif defined(__has_attribute) ++// check if we have the __fallthrough__ attribute ++#if __has_attribute(__fallthrough__) ++// we are good to go: ++# define simdjson_fallthrough __attribute__((__fallthrough__)) ++#endif // __has_attribute(__fallthrough__) ++#endif // SIMDJSON_CPLUSPLUS17 ++// on some systems, we simply do not have support for fallthrough, so use a default: ++#ifndef simdjson_fallthrough ++# define simdjson_fallthrough do {} while (0) /* fallthrough */ ++#endif // simdjson_fallthrough ++ ++ ++#if SIMDJSON_DEVELOPMENT_CHECKS ++#define SIMDJSON_DEVELOPMENT_ASSERT(expr) do { assert ((expr)); } while (0) ++#else ++#define SIMDJSON_DEVELOPMENT_ASSERT(expr) do { } while (0) ++#endif ++ ++#endif // SIMDJSON_COMMON_DEFS_H ++/* end file include/simdjson/common_defs.h */ ++ ++SIMDJSON_PUSH_DISABLE_WARNINGS ++SIMDJSON_DISABLE_UNDESIRED_WARNINGS ++ ++// Public API ++/* begin file include/simdjson/error.h */ ++#ifndef SIMDJSON_ERROR_H ++#define SIMDJSON_ERROR_H ++ ++#include ++ ++namespace simdjson { ++ ++/** ++ * All possible errors returned by simdjson. These error codes are subject to change ++ * and not all simdjson kernel returns the same error code given the same input: it is not ++ * well defined which error a given input should produce. ++ * ++ * Only SUCCESS evaluates to false as a Boolean. All other error codes will evaluate ++ * to true as a Boolean. ++ */ ++enum error_code { ++ SUCCESS = 0, ///< No error ++ CAPACITY, ///< This parser can't support a document that big ++ MEMALLOC, ///< Error allocating memory, most likely out of memory ++ TAPE_ERROR, ///< Something went wrong, this is a generic error ++ DEPTH_ERROR, ///< Your document exceeds the user-specified depth limitation ++ STRING_ERROR, ///< Problem while parsing a string ++ T_ATOM_ERROR, ///< Problem while parsing an atom starting with the letter 't' ++ F_ATOM_ERROR, ///< Problem while parsing an atom starting with the letter 'f' ++ N_ATOM_ERROR, ///< Problem while parsing an atom starting with the letter 'n' ++ NUMBER_ERROR, ///< Problem while parsing a number ++ UTF8_ERROR, ///< the input is not valid UTF-8 ++ UNINITIALIZED, ///< unknown error, or uninitialized document ++ EMPTY, ///< no structural element found ++ UNESCAPED_CHARS, ///< found unescaped characters in a string. ++ UNCLOSED_STRING, ///< missing quote at the end ++ UNSUPPORTED_ARCHITECTURE, ///< unsupported architecture ++ INCORRECT_TYPE, ///< JSON element has a different type than user expected ++ NUMBER_OUT_OF_RANGE, ///< JSON number does not fit in 64 bits ++ INDEX_OUT_OF_BOUNDS, ///< JSON array index too large ++ NO_SUCH_FIELD, ///< JSON field not found in object ++ IO_ERROR, ///< Error reading a file ++ INVALID_JSON_POINTER, ///< Invalid JSON pointer reference ++ INVALID_URI_FRAGMENT, ///< Invalid URI fragment ++ UNEXPECTED_ERROR, ///< indicative of a bug in simdjson ++ PARSER_IN_USE, ///< parser is already in use. ++ OUT_OF_ORDER_ITERATION, ///< tried to iterate an array or object out of order ++ INSUFFICIENT_PADDING, ///< The JSON doesn't have enough padding for simdjson to safely parse it. ++ INCOMPLETE_ARRAY_OR_OBJECT, ///< The document ends early. ++ SCALAR_DOCUMENT_AS_VALUE, ///< A scalar document is treated as a value. ++ OUT_OF_BOUNDS, ///< Attempted to access location outside of document. ++ TRAILING_CONTENT, ///< Unexpected trailing content in the JSON input ++ NUM_ERROR_CODES ++}; ++ ++/** ++ * Get the error message for the given error code. ++ * ++ * dom::parser parser; ++ * dom::element doc; ++ * auto error = parser.parse("foo",3).get(doc); ++ * if (error) { printf("Error: %s\n", error_message(error)); } ++ * ++ * @return The error message. ++ */ ++inline const char *error_message(error_code error) noexcept; ++ ++/** ++ * Write the error message to the output stream ++ */ ++inline std::ostream& operator<<(std::ostream& out, error_code error) noexcept; ++ ++/** ++ * Exception thrown when an exception-supporting simdjson method is called ++ */ ++struct simdjson_error : public std::exception { ++ /** ++ * Create an exception from a simdjson error code. ++ * @param error The error code ++ */ ++ simdjson_error(error_code error) noexcept : _error{error} { } ++ /** The error message */ ++ const char *what() const noexcept { return error_message(error()); } ++ /** The error code */ ++ error_code error() const noexcept { return _error; } ++private: ++ /** The error code that was used */ ++ error_code _error; ++}; ++ ++namespace internal { ++ ++/** ++ * The result of a simdjson operation that could fail. ++ * ++ * Gives the option of reading error codes, or throwing an exception by casting to the desired result. ++ * ++ * This is a base class for implementations that want to add functions to the result type for ++ * chaining. ++ * ++ * Override like: ++ * ++ * struct simdjson_result : public internal::simdjson_result_base { ++ * simdjson_result() noexcept : internal::simdjson_result_base() {} ++ * simdjson_result(error_code error) noexcept : internal::simdjson_result_base(error) {} ++ * simdjson_result(T &&value) noexcept : internal::simdjson_result_base(std::forward(value)) {} ++ * simdjson_result(T &&value, error_code error) noexcept : internal::simdjson_result_base(value, error) {} ++ * // Your extra methods here ++ * } ++ * ++ * Then any method returning simdjson_result will be chainable with your methods. ++ */ ++template ++struct simdjson_result_base : protected std::pair { ++ ++ /** ++ * Create a new empty result with error = UNINITIALIZED. ++ */ ++ simdjson_inline simdjson_result_base() noexcept; ++ ++ /** ++ * Create a new error result. ++ */ ++ simdjson_inline simdjson_result_base(error_code error) noexcept; ++ ++ /** ++ * Create a new successful result. ++ */ ++ simdjson_inline simdjson_result_base(T &&value) noexcept; ++ ++ /** ++ * Create a new result with both things (use if you don't want to branch when creating the result). ++ */ ++ simdjson_inline simdjson_result_base(T &&value, error_code error) noexcept; ++ ++ /** ++ * Move the value and the error to the provided variables. ++ * ++ * @param value The variable to assign the value to. May not be set if there is an error. ++ * @param error The variable to assign the error to. Set to SUCCESS if there is no error. ++ */ ++ simdjson_inline void tie(T &value, error_code &error) && noexcept; ++ ++ /** ++ * Move the value to the provided variable. ++ * ++ * @param value The variable to assign the value to. May not be set if there is an error. ++ */ ++ simdjson_inline error_code get(T &value) && noexcept; ++ ++ /** ++ * The error. ++ */ ++ simdjson_inline error_code error() const noexcept; ++ ++#if SIMDJSON_EXCEPTIONS ++ ++ /** ++ * Get the result value. ++ * ++ * @throw simdjson_error if there was an error. ++ */ ++ simdjson_inline T& value() & noexcept(false); ++ ++ /** ++ * Take the result value (move it). ++ * ++ * @throw simdjson_error if there was an error. ++ */ ++ simdjson_inline T&& value() && noexcept(false); ++ ++ /** ++ * Take the result value (move it). ++ * ++ * @throw simdjson_error if there was an error. ++ */ ++ simdjson_inline T&& take_value() && noexcept(false); ++ ++ /** ++ * Cast to the value (will throw on error). ++ * ++ * @throw simdjson_error if there was an error. ++ */ ++ simdjson_inline operator T&&() && noexcept(false); ++#endif // SIMDJSON_EXCEPTIONS ++ ++ /** ++ * Get the result value. This function is safe if and only ++ * the error() method returns a value that evaluates to false. ++ */ ++ simdjson_inline const T& value_unsafe() const& noexcept; ++ ++ /** ++ * Take the result value (move it). This function is safe if and only ++ * the error() method returns a value that evaluates to false. ++ */ ++ simdjson_inline T&& value_unsafe() && noexcept; ++ ++}; // struct simdjson_result_base ++ ++} // namespace internal ++ ++/** ++ * The result of a simdjson operation that could fail. ++ * ++ * Gives the option of reading error codes, or throwing an exception by casting to the desired result. ++ */ ++template ++struct simdjson_result : public internal::simdjson_result_base { ++ /** ++ * @private Create a new empty result with error = UNINITIALIZED. ++ */ ++ simdjson_inline simdjson_result() noexcept; ++ /** ++ * @private Create a new error result. ++ */ ++ simdjson_inline simdjson_result(T &&value) noexcept; ++ /** ++ * @private Create a new successful result. ++ */ ++ simdjson_inline simdjson_result(error_code error_code) noexcept; ++ /** ++ * @private Create a new result with both things (use if you don't want to branch when creating the result). ++ */ ++ simdjson_inline simdjson_result(T &&value, error_code error) noexcept; ++ ++ /** ++ * Move the value and the error to the provided variables. ++ * ++ * @param value The variable to assign the value to. May not be set if there is an error. ++ * @param error The variable to assign the error to. Set to SUCCESS if there is no error. ++ */ ++ simdjson_inline void tie(T &value, error_code &error) && noexcept; ++ ++ /** ++ * Move the value to the provided variable. ++ * ++ * @param value The variable to assign the value to. May not be set if there is an error. ++ */ ++ simdjson_warn_unused simdjson_inline error_code get(T &value) && noexcept; ++ ++ /** ++ * The error. ++ */ ++ simdjson_inline error_code error() const noexcept; ++ ++#if SIMDJSON_EXCEPTIONS ++ ++ /** ++ * Get the result value. ++ * ++ * @throw simdjson_error if there was an error. ++ */ ++ simdjson_inline T& value() & noexcept(false); ++ ++ /** ++ * Take the result value (move it). ++ * ++ * @throw simdjson_error if there was an error. ++ */ ++ simdjson_inline T&& value() && noexcept(false); ++ ++ /** ++ * Take the result value (move it). ++ * ++ * @throw simdjson_error if there was an error. ++ */ ++ simdjson_inline T&& take_value() && noexcept(false); ++ ++ /** ++ * Cast to the value (will throw on error). ++ * ++ * @throw simdjson_error if there was an error. ++ */ ++ simdjson_inline operator T&&() && noexcept(false); ++#endif // SIMDJSON_EXCEPTIONS ++ ++ /** ++ * Get the result value. This function is safe if and only ++ * the error() method returns a value that evaluates to false. ++ */ ++ simdjson_inline const T& value_unsafe() const& noexcept; ++ ++ /** ++ * Take the result value (move it). This function is safe if and only ++ * the error() method returns a value that evaluates to false. ++ */ ++ simdjson_inline T&& value_unsafe() && noexcept; ++ ++}; // struct simdjson_result ++ ++#if SIMDJSON_EXCEPTIONS ++ ++template ++inline std::ostream& operator<<(std::ostream& out, simdjson_result value) { return out << value.value(); } ++#endif // SIMDJSON_EXCEPTIONS ++ ++#ifndef SIMDJSON_DISABLE_DEPRECATED_API ++/** ++ * @deprecated This is an alias and will be removed, use error_code instead ++ */ ++using ErrorValues [[deprecated("This is an alias and will be removed, use error_code instead")]] = error_code; ++ ++/** ++ * @deprecated Error codes should be stored and returned as `error_code`, use `error_message()` instead. ++ */ ++[[deprecated("Error codes should be stored and returned as `error_code`, use `error_message()` instead.")]] ++inline const std::string error_message(int error) noexcept; ++#endif // SIMDJSON_DISABLE_DEPRECATED_API ++} // namespace simdjson ++ ++#endif // SIMDJSON_ERROR_H ++/* end file include/simdjson/error.h */ ++/* begin file include/simdjson/minify.h */ ++#ifndef SIMDJSON_MINIFY_H ++#define SIMDJSON_MINIFY_H ++ ++/* begin file include/simdjson/padded_string.h */ ++#ifndef SIMDJSON_PADDED_STRING_H ++#define SIMDJSON_PADDED_STRING_H ++ ++#include ++#include ++#include ++#include ++ ++namespace simdjson { ++ ++class padded_string_view; ++ ++/** ++ * String with extra allocation for ease of use with parser::parse() ++ * ++ * This is a move-only class, it cannot be copied. ++ */ ++struct padded_string final { ++ ++ /** ++ * Create a new, empty padded string. ++ */ ++ explicit inline padded_string() noexcept; ++ /** ++ * Create a new padded string buffer. ++ * ++ * @param length the size of the string. ++ */ ++ explicit inline padded_string(size_t length) noexcept; ++ /** ++ * Create a new padded string by copying the given input. ++ * ++ * @param data the buffer to copy ++ * @param length the number of bytes to copy ++ */ ++ explicit inline padded_string(const char *data, size_t length) noexcept; ++ /** ++ * Create a new padded string by copying the given input. ++ * ++ * @param str_ the string to copy ++ */ ++ inline padded_string(const std::string & str_ ) noexcept; ++ /** ++ * Create a new padded string by copying the given input. ++ * ++ * @param sv_ the string to copy ++ */ ++ inline padded_string(std::string_view sv_) noexcept; ++ /** ++ * Move one padded string into another. ++ * ++ * The original padded string will be reduced to zero capacity. ++ * ++ * @param o the string to move. ++ */ ++ inline padded_string(padded_string &&o) noexcept; ++ /** ++ * Move one padded string into another. ++ * ++ * The original padded string will be reduced to zero capacity. ++ * ++ * @param o the string to move. ++ */ ++ inline padded_string &operator=(padded_string &&o) noexcept; ++ inline void swap(padded_string &o) noexcept; ++ ~padded_string() noexcept; ++ ++ /** ++ * The length of the string. ++ * ++ * Does not include padding. ++ */ ++ size_t size() const noexcept; ++ ++ /** ++ * The length of the string. ++ * ++ * Does not include padding. ++ */ ++ size_t length() const noexcept; ++ ++ /** ++ * The string data. ++ **/ ++ const char *data() const noexcept; ++ const uint8_t *u8data() const noexcept { return static_cast(static_cast(data_ptr));} ++ ++ /** ++ * The string data. ++ **/ ++ char *data() noexcept; ++ ++ /** ++ * Create a std::string_view with the same content. ++ */ ++ operator std::string_view() const; ++ ++ /** ++ * Create a padded_string_view with the same content. ++ */ ++ operator padded_string_view() const noexcept; ++ ++ /** ++ * Load this padded string from a file. ++ * ++ * @return IO_ERROR on error. Be mindful that on some 32-bit systems, ++ * the file size might be limited to 2 GB. ++ * ++ * @param path the path to the file. ++ **/ ++ inline static simdjson_result load(std::string_view path) noexcept; ++ ++private: ++ padded_string &operator=(const padded_string &o) = delete; ++ padded_string(const padded_string &o) = delete; ++ ++ size_t viable_size{0}; ++ char *data_ptr{nullptr}; ++ ++}; // padded_string ++ ++/** ++ * Send padded_string instance to an output stream. ++ * ++ * @param out The output stream. ++ * @param s The padded_string instance. ++ * @throw if there is an error with the underlying output stream. simdjson itself will not throw. ++ */ ++inline std::ostream& operator<<(std::ostream& out, const padded_string& s) { return out << s.data(); } ++ ++#if SIMDJSON_EXCEPTIONS ++/** ++ * Send padded_string instance to an output stream. ++ * ++ * @param out The output stream. ++ * @param s The padded_string instance. ++ * @throw simdjson_error if the result being printed has an error. If there is an error with the ++ * underlying output stream, that error will be propagated (simdjson_error will not be ++ * thrown). ++ */ ++inline std::ostream& operator<<(std::ostream& out, simdjson_result &s) noexcept(false) { return out << s.value(); } ++#endif ++ ++} // namespace simdjson ++ ++// This is deliberately outside of simdjson so that people get it without having to use the namespace ++inline simdjson::padded_string operator "" _padded(const char *str, size_t len) { ++ return simdjson::padded_string(str, len); ++} ++ ++namespace simdjson { ++namespace internal { ++ ++// The allocate_padded_buffer function is a low-level function to allocate memory ++// with padding so we can read past the "length" bytes safely. It is used by ++// the padded_string class automatically. It returns nullptr in case ++// of error: the caller should check for a null pointer. ++// The length parameter is the maximum size in bytes of the string. ++// The caller is responsible to free the memory (e.g., delete[] (...)). ++inline char *allocate_padded_buffer(size_t length) noexcept; ++ ++} // namespace internal ++} // namespace simdjson ++ ++#endif // SIMDJSON_PADDED_STRING_H ++/* end file include/simdjson/padded_string.h */ ++#include ++#include ++#include ++ ++namespace simdjson { ++ ++ ++ ++/** ++ * ++ * Minify the input string assuming that it represents a JSON string, does not parse or validate. ++ * This function is much faster than parsing a JSON string and then writing a minified version of it. ++ * However, it does not validate the input. It will merely return an error in simple cases (e.g., if ++ * there is a string that was never terminated). ++ * ++ * ++ * @param buf the json document to minify. ++ * @param len the length of the json document. ++ * @param dst the buffer to write the minified document to. *MUST* be allocated up to len bytes. ++ * @param dst_len the number of bytes written. Output only. ++ * @return the error code, or SUCCESS if there was no error. ++ */ ++simdjson_warn_unused error_code minify(const char *buf, size_t len, char *dst, size_t &dst_len) noexcept; ++ ++} // namespace simdjson ++ ++#endif // SIMDJSON_MINIFY_H ++/* end file include/simdjson/minify.h */ ++/* begin file include/simdjson/padded_string_view.h */ ++#ifndef SIMDJSON_PADDED_STRING_VIEW_H ++#define SIMDJSON_PADDED_STRING_VIEW_H ++ ++ ++#include ++#include ++#include ++#include ++ ++namespace simdjson { ++ ++/** ++ * User-provided string that promises it has extra padded bytes at the end for use with parser::parse(). ++ */ ++class padded_string_view : public std::string_view { ++private: ++ size_t _capacity; ++ ++public: ++ /** Create an empty padded_string_view. */ ++ inline padded_string_view() noexcept = default; ++ ++ /** ++ * Promise the given buffer has at least SIMDJSON_PADDING extra bytes allocated to it. ++ * ++ * @param s The string. ++ * @param len The length of the string (not including padding). ++ * @param capacity The allocated length of the string, including padding. ++ */ ++ explicit inline padded_string_view(const char* s, size_t len, size_t capacity) noexcept; ++ /** overload explicit inline padded_string_view(const char* s, size_t len) noexcept */ ++ explicit inline padded_string_view(const uint8_t* s, size_t len, size_t capacity) noexcept; ++ ++ /** ++ * Promise the given string has at least SIMDJSON_PADDING extra bytes allocated to it. ++ * ++ * The capacity of the string will be used to determine its padding. ++ * ++ * @param s The string. ++ */ ++ explicit inline padded_string_view(const std::string &s) noexcept; ++ ++ /** ++ * Promise the given string_view has at least SIMDJSON_PADDING extra bytes allocated to it. ++ * ++ * @param s The string. ++ * @param capacity The allocated length of the string, including padding. ++ */ ++ explicit inline padded_string_view(std::string_view s, size_t capacity) noexcept; ++ ++ /** The number of allocated bytes. */ ++ inline size_t capacity() const noexcept; ++ ++ /** The amount of padding on the string (capacity() - length()) */ ++ inline size_t padding() const noexcept; ++ ++}; // padded_string_view ++ ++#if SIMDJSON_EXCEPTIONS ++/** ++ * Send padded_string instance to an output stream. ++ * ++ * @param out The output stream. ++ * @param s The padded_string_view. ++ * @throw simdjson_error if the result being printed has an error. If there is an error with the ++ * underlying output stream, that error will be propagated (simdjson_error will not be ++ * thrown). ++ */ ++inline std::ostream& operator<<(std::ostream& out, simdjson_result &s) noexcept(false) { return out << s.value(); } ++#endif ++ ++} // namespace simdjson ++ ++#endif // SIMDJSON_PADDED_STRING_VIEW_H ++/* end file include/simdjson/padded_string_view.h */ ++/* begin file include/simdjson/implementation.h */ ++#ifndef SIMDJSON_IMPLEMENTATION_H ++#define SIMDJSON_IMPLEMENTATION_H ++ ++/* begin file include/simdjson/internal/dom_parser_implementation.h */ ++#ifndef SIMDJSON_INTERNAL_DOM_PARSER_IMPLEMENTATION_H ++#define SIMDJSON_INTERNAL_DOM_PARSER_IMPLEMENTATION_H ++ ++#include ++ ++namespace simdjson { ++ ++namespace dom { ++class document; ++} // namespace dom ++ ++/** ++* This enum is used with the dom_parser_implementation::stage1 function. ++* 1) The regular mode expects a fully formed JSON document. ++* 2) The streaming_partial mode expects a possibly truncated ++* input within a stream on JSON documents. ++* 3) The stream_final mode allows us to truncate final ++* unterminated strings. It is useful in conjunction with streaming_partial. ++*/ ++enum class stage1_mode { regular, streaming_partial, streaming_final}; ++ ++/** ++ * Returns true if mode == streaming_partial or mode == streaming_final ++ */ ++inline bool is_streaming(stage1_mode mode) { ++ // performance note: it is probably faster to check that mode is different ++ // from regular than checking that it is either streaming_partial or streaming_final. ++ return (mode != stage1_mode::regular); ++ // return (mode == stage1_mode::streaming_partial || mode == stage1_mode::streaming_final); ++} ++ ++ ++namespace internal { ++ ++ ++/** ++ * An implementation of simdjson's DOM parser for a particular CPU architecture. ++ * ++ * This class is expected to be accessed only by pointer, and never move in memory (though the ++ * pointer can move). ++ */ ++class dom_parser_implementation { ++public: ++ ++ /** ++ * @private For internal implementation use ++ * ++ * Run a full JSON parse on a single document (stage1 + stage2). ++ * ++ * Guaranteed only to be called when capacity > document length. ++ * ++ * Overridden by each implementation. ++ * ++ * @param buf The json document to parse. *MUST* be allocated up to len + SIMDJSON_PADDING bytes. ++ * @param len The length of the json document. ++ * @return The error code, or SUCCESS if there was no error. ++ */ ++ simdjson_warn_unused virtual error_code parse(const uint8_t *buf, size_t len, dom::document &doc) noexcept = 0; ++ ++ /** ++ * @private For internal implementation use ++ * ++ * Stage 1 of the document parser. ++ * ++ * Guaranteed only to be called when capacity > document length. ++ * ++ * Overridden by each implementation. ++ * ++ * @param buf The json document to parse. ++ * @param len The length of the json document. ++ * @param streaming Whether this is being called by parser::parse_many. ++ * @return The error code, or SUCCESS if there was no error. ++ */ ++ simdjson_warn_unused virtual error_code stage1(const uint8_t *buf, size_t len, stage1_mode streaming) noexcept = 0; ++ ++ /** ++ * @private For internal implementation use ++ * ++ * Stage 2 of the document parser. ++ * ++ * Called after stage1(). ++ * ++ * Overridden by each implementation. ++ * ++ * @param doc The document to output to. ++ * @return The error code, or SUCCESS if there was no error. ++ */ ++ simdjson_warn_unused virtual error_code stage2(dom::document &doc) noexcept = 0; ++ ++ /** ++ * @private For internal implementation use ++ * ++ * Stage 2 of the document parser for parser::parse_many. ++ * ++ * Guaranteed only to be called after stage1(). ++ * Overridden by each implementation. ++ * ++ * @param doc The document to output to. ++ * @return The error code, SUCCESS if there was no error, or EMPTY if all documents have been parsed. ++ */ ++ simdjson_warn_unused virtual error_code stage2_next(dom::document &doc) noexcept = 0; ++ ++ /** ++ * Unescape a valid UTF-8 string from src to dst, stopping at a final unescaped quote. There ++ * must be an unescaped quote terminating the string. It returns the final output ++ * position as pointer. In case of error (e.g., the string has bad escaped codes), ++ * then null_nullptrptr is returned. It is assumed that the output buffer is large ++ * enough. E.g., if src points at 'joe"', then dst needs to have four free bytes + ++ * SIMDJSON_PADDING bytes. ++ * ++ * Overridden by each implementation. ++ * ++ * @param str pointer to the beginning of a valid UTF-8 JSON string, must end with an unescaped quote. ++ * @param dst pointer to a destination buffer, it must point a region in memory of sufficient size. ++ * @return end of the of the written region (exclusive) or nullptr in case of error. ++ */ ++ simdjson_warn_unused virtual uint8_t *parse_string(const uint8_t *src, uint8_t *dst) const noexcept = 0; ++ ++ /** ++ * Change the capacity of this parser. ++ * ++ * The capacity can never exceed SIMDJSON_MAXSIZE_BYTES (e.g., 4 GB) ++ * and an CAPACITY error is returned if it is attempted. ++ * ++ * Generally used for reallocation. ++ * ++ * @param capacity The new capacity. ++ * @param max_depth The new max_depth. ++ * @return The error code, or SUCCESS if there was no error. ++ */ ++ virtual error_code set_capacity(size_t capacity) noexcept = 0; ++ ++ /** ++ * Change the max depth of this parser. ++ * ++ * Generally used for reallocation. ++ * ++ * @param capacity The new capacity. ++ * @param max_depth The new max_depth. ++ * @return The error code, or SUCCESS if there was no error. ++ */ ++ virtual error_code set_max_depth(size_t max_depth) noexcept = 0; ++ ++ /** ++ * Deallocate this parser. ++ */ ++ virtual ~dom_parser_implementation() = default; ++ ++ /** Number of structural indices passed from stage 1 to stage 2 */ ++ uint32_t n_structural_indexes{0}; ++ /** Structural indices passed from stage 1 to stage 2 */ ++ std::unique_ptr structural_indexes{}; ++ /** Next structural index to parse */ ++ uint32_t next_structural_index{0}; ++ ++ /** ++ * The largest document this parser can support without reallocating. ++ * ++ * @return Current capacity, in bytes. ++ */ ++ simdjson_inline size_t capacity() const noexcept; ++ ++ /** ++ * The maximum level of nested object and arrays supported by this parser. ++ * ++ * @return Maximum depth, in bytes. ++ */ ++ simdjson_inline size_t max_depth() const noexcept; ++ ++ /** ++ * Ensure this parser has enough memory to process JSON documents up to `capacity` bytes in length ++ * and `max_depth` depth. ++ * ++ * @param capacity The new capacity. ++ * @param max_depth The new max_depth. Defaults to DEFAULT_MAX_DEPTH. ++ * @return The error, if there is one. ++ */ ++ simdjson_warn_unused inline error_code allocate(size_t capacity, size_t max_depth) noexcept; ++ ++ ++protected: ++ /** ++ * The maximum document length this parser supports. ++ * ++ * Buffers are large enough to handle any document up to this length. ++ */ ++ size_t _capacity{0}; ++ ++ /** ++ * The maximum depth (number of nested objects and arrays) supported by this parser. ++ * ++ * Defaults to DEFAULT_MAX_DEPTH. ++ */ ++ size_t _max_depth{0}; ++ ++ // Declaring these so that subclasses can use them to implement their constructors. ++ simdjson_inline dom_parser_implementation() noexcept; ++ simdjson_inline dom_parser_implementation(dom_parser_implementation &&other) noexcept; ++ simdjson_inline dom_parser_implementation &operator=(dom_parser_implementation &&other) noexcept; ++ ++ simdjson_inline dom_parser_implementation(const dom_parser_implementation &) noexcept = delete; ++ simdjson_inline dom_parser_implementation &operator=(const dom_parser_implementation &other) noexcept = delete; ++}; // class dom_parser_implementation ++ ++simdjson_inline dom_parser_implementation::dom_parser_implementation() noexcept = default; ++simdjson_inline dom_parser_implementation::dom_parser_implementation(dom_parser_implementation &&other) noexcept = default; ++simdjson_inline dom_parser_implementation &dom_parser_implementation::operator=(dom_parser_implementation &&other) noexcept = default; ++ ++simdjson_inline size_t dom_parser_implementation::capacity() const noexcept { ++ return _capacity; ++} ++ ++simdjson_inline size_t dom_parser_implementation::max_depth() const noexcept { ++ return _max_depth; ++} ++ ++simdjson_warn_unused ++inline error_code dom_parser_implementation::allocate(size_t capacity, size_t max_depth) noexcept { ++ if (this->max_depth() != max_depth) { ++ error_code err = set_max_depth(max_depth); ++ if (err) { return err; } ++ } ++ if (_capacity != capacity) { ++ error_code err = set_capacity(capacity); ++ if (err) { return err; } ++ } ++ return SUCCESS; ++} ++ ++} // namespace internal ++} // namespace simdjson ++ ++#endif // SIMDJSON_INTERNAL_DOM_PARSER_IMPLEMENTATION_H ++/* end file include/simdjson/internal/dom_parser_implementation.h */ ++/* begin file include/simdjson/internal/isadetection.h */ ++/* From ++https://github.com/endorno/pytorch/blob/master/torch/lib/TH/generic/simd/simd.h ++Highly modified. ++ ++Copyright (c) 2016- Facebook, Inc (Adam Paszke) ++Copyright (c) 2014- Facebook, Inc (Soumith Chintala) ++Copyright (c) 2011-2014 Idiap Research Institute (Ronan Collobert) ++Copyright (c) 2012-2014 Deepmind Technologies (Koray Kavukcuoglu) ++Copyright (c) 2011-2012 NEC Laboratories America (Koray Kavukcuoglu) ++Copyright (c) 2011-2013 NYU (Clement Farabet) ++Copyright (c) 2006-2010 NEC Laboratories America (Ronan Collobert, Leon Bottou, ++Iain Melvin, Jason Weston) Copyright (c) 2006 Idiap Research Institute ++(Samy Bengio) Copyright (c) 2001-2004 Idiap Research Institute (Ronan Collobert, ++Samy Bengio, Johnny Mariethoz) ++ ++All rights reserved. ++ ++Redistribution and use in source and binary forms, with or without ++modification, are permitted provided that the following conditions are met: ++ ++1. Redistributions of source code must retain the above copyright ++ notice, this list of conditions and the following disclaimer. ++ ++2. Redistributions in binary form must reproduce the above copyright ++ notice, this list of conditions and the following disclaimer in the ++ documentation and/or other materials provided with the distribution. ++ ++3. Neither the names of Facebook, Deepmind Technologies, NYU, NEC Laboratories ++America and IDIAP Research Institute nor the names of its contributors may be ++ used to endorse or promote products derived from this software without ++ specific prior written permission. ++ ++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" ++AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ++IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ++ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE ++LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ++CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ++SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ++INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ++CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ++ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE ++POSSIBILITY OF SUCH DAMAGE. ++*/ ++ ++#ifndef SIMDJSON_INTERNAL_ISADETECTION_H ++#define SIMDJSON_INTERNAL_ISADETECTION_H ++ ++#include ++#include ++#if defined(_MSC_VER) ++#include ++#elif defined(HAVE_GCC_GET_CPUID) && defined(USE_GCC_GET_CPUID) ++#include ++#endif ++ ++namespace simdjson { ++namespace internal { ++ ++ ++enum instruction_set { ++ DEFAULT = 0x0, ++ NEON = 0x1, ++ AVX2 = 0x4, ++ SSE42 = 0x8, ++ PCLMULQDQ = 0x10, ++ BMI1 = 0x20, ++ BMI2 = 0x40, ++ ALTIVEC = 0x80, ++ AVX512F = 0x100, ++ AVX512DQ = 0x200, ++ AVX512IFMA = 0x400, ++ AVX512PF = 0x800, ++ AVX512ER = 0x1000, ++ AVX512CD = 0x2000, ++ AVX512BW = 0x4000, ++ AVX512VL = 0x8000, ++ AVX512VBMI2 = 0x10000 ++}; ++ ++#if defined(__PPC64__) ++ ++static inline uint32_t detect_supported_architectures() { ++ return instruction_set::ALTIVEC; ++} ++ ++#elif defined(__arm__) || defined(__aarch64__) // incl. armel, armhf, arm64 ++ ++#if defined(__ARM_NEON) ++ ++static inline uint32_t detect_supported_architectures() { ++ return instruction_set::NEON; ++} ++ ++#else // ARM without NEON ++ ++static inline uint32_t detect_supported_architectures() { ++ return instruction_set::DEFAULT; ++} ++ ++#endif ++ ++#elif defined(__x86_64__) || defined(_M_AMD64) // x64 ++ ++ ++namespace { ++// Can be found on Intel ISA Reference for CPUID ++constexpr uint32_t cpuid_avx2_bit = 1 << 5; ///< @private Bit 5 of EBX for EAX=0x7 ++constexpr uint32_t cpuid_bmi1_bit = 1 << 3; ///< @private bit 3 of EBX for EAX=0x7 ++constexpr uint32_t cpuid_bmi2_bit = 1 << 8; ///< @private bit 8 of EBX for EAX=0x7 ++constexpr uint32_t cpuid_avx512f_bit = 1 << 16; ///< @private bit 16 of EBX for EAX=0x7 ++constexpr uint32_t cpuid_avx512dq_bit = 1 << 17; ///< @private bit 17 of EBX for EAX=0x7 ++constexpr uint32_t cpuid_avx512ifma_bit = 1 << 21; ///< @private bit 21 of EBX for EAX=0x7 ++constexpr uint32_t cpuid_avx512pf_bit = 1 << 26; ///< @private bit 26 of EBX for EAX=0x7 ++constexpr uint32_t cpuid_avx512er_bit = 1 << 27; ///< @private bit 27 of EBX for EAX=0x7 ++constexpr uint32_t cpuid_avx512cd_bit = 1 << 28; ///< @private bit 28 of EBX for EAX=0x7 ++constexpr uint32_t cpuid_avx512bw_bit = 1 << 30; ///< @private bit 30 of EBX for EAX=0x7 ++constexpr uint32_t cpuid_avx512vl_bit = 1U << 31; ///< @private bit 31 of EBX for EAX=0x7 ++constexpr uint32_t cpuid_avx512vbmi2_bit = 1 << 6; ///< @private bit 6 of ECX for EAX=0x7 ++constexpr uint32_t cpuid_sse42_bit = 1 << 20; ///< @private bit 20 of ECX for EAX=0x1 ++constexpr uint32_t cpuid_pclmulqdq_bit = 1 << 1; ///< @private bit 1 of ECX for EAX=0x1 ++} ++ ++ ++ ++static inline void cpuid(uint32_t *eax, uint32_t *ebx, uint32_t *ecx, ++ uint32_t *edx) { ++#if defined(_MSC_VER) ++ int cpu_info[4]; ++ __cpuid(cpu_info, *eax); ++ *eax = cpu_info[0]; ++ *ebx = cpu_info[1]; ++ *ecx = cpu_info[2]; ++ *edx = cpu_info[3]; ++#elif defined(HAVE_GCC_GET_CPUID) && defined(USE_GCC_GET_CPUID) ++ uint32_t level = *eax; ++ __get_cpuid(level, eax, ebx, ecx, edx); ++#else ++ uint32_t a = *eax, b, c = *ecx, d; ++ asm volatile("cpuid\n\t" : "+a"(a), "=b"(b), "+c"(c), "=d"(d)); ++ *eax = a; ++ *ebx = b; ++ *ecx = c; ++ *edx = d; ++#endif ++} ++ ++static inline uint32_t detect_supported_architectures() { ++ uint32_t eax, ebx, ecx, edx; ++ uint32_t host_isa = 0x0; ++ ++ // ECX for EAX=0x7 ++ eax = 0x7; ++ ecx = 0x0; ++ cpuid(&eax, &ebx, &ecx, &edx); ++ if (ebx & cpuid_avx2_bit) { ++ host_isa |= instruction_set::AVX2; ++ } ++ if (ebx & cpuid_bmi1_bit) { ++ host_isa |= instruction_set::BMI1; ++ } ++ ++ if (ebx & cpuid_bmi2_bit) { ++ host_isa |= instruction_set::BMI2; ++ } ++ ++ if (ebx & cpuid_avx512f_bit) { ++ host_isa |= instruction_set::AVX512F; ++ } ++ ++ if (ebx & cpuid_avx512dq_bit) { ++ host_isa |= instruction_set::AVX512DQ; ++ } ++ ++ if (ebx & cpuid_avx512ifma_bit) { ++ host_isa |= instruction_set::AVX512IFMA; ++ } ++ ++ if (ebx & cpuid_avx512pf_bit) { ++ host_isa |= instruction_set::AVX512PF; ++ } ++ ++ if (ebx & cpuid_avx512er_bit) { ++ host_isa |= instruction_set::AVX512ER; ++ } ++ ++ if (ebx & cpuid_avx512cd_bit) { ++ host_isa |= instruction_set::AVX512CD; ++ } ++ ++ if (ebx & cpuid_avx512bw_bit) { ++ host_isa |= instruction_set::AVX512BW; ++ } ++ ++ if (ebx & cpuid_avx512vl_bit) { ++ host_isa |= instruction_set::AVX512VL; ++ } ++ ++ if (ecx & cpuid_avx512vbmi2_bit) { ++ host_isa |= instruction_set::AVX512VBMI2; ++ } ++ ++ // EBX for EAX=0x1 ++ eax = 0x1; ++ cpuid(&eax, &ebx, &ecx, &edx); ++ ++ if (ecx & cpuid_sse42_bit) { ++ host_isa |= instruction_set::SSE42; ++ } ++ ++ if (ecx & cpuid_pclmulqdq_bit) { ++ host_isa |= instruction_set::PCLMULQDQ; ++ } ++ ++ return host_isa; ++} ++#else // fallback ++ ++ ++static inline uint32_t detect_supported_architectures() { ++ return instruction_set::DEFAULT; ++} ++ ++ ++#endif // end SIMD extension detection code ++ ++} // namespace internal ++} // namespace simdjson ++ ++#endif // SIMDJSON_INTERNAL_ISADETECTION_H ++/* end file include/simdjson/internal/isadetection.h */ ++#include ++#include ++#include ++ ++namespace simdjson { ++ ++/** ++ * Validate the UTF-8 string. ++ * ++ * @param buf the string to validate. ++ * @param len the length of the string in bytes. ++ * @return true if the string is valid UTF-8. ++ */ ++simdjson_warn_unused bool validate_utf8(const char * buf, size_t len) noexcept; ++/** ++ * Validate the UTF-8 string. ++ * ++ * @param sv the string_view to validate. ++ * @return true if the string is valid UTF-8. ++ */ ++simdjson_inline simdjson_warn_unused bool validate_utf8(const std::string_view sv) noexcept { ++ return validate_utf8(sv.data(), sv.size()); ++} ++ ++/** ++ * Validate the UTF-8 string. ++ * ++ * @param p the string to validate. ++ * @return true if the string is valid UTF-8. ++ */ ++simdjson_inline simdjson_warn_unused bool validate_utf8(const std::string& s) noexcept { ++ return validate_utf8(s.data(), s.size()); ++} ++ ++namespace dom { ++ class document; ++} // namespace dom ++ ++/** ++ * An implementation of simdjson for a particular CPU architecture. ++ * ++ * Also used to maintain the currently active implementation. The active implementation is ++ * automatically initialized on first use to the most advanced implementation supported by the host. ++ */ ++class implementation { ++public: ++ ++ /** ++ * The name of this implementation. ++ * ++ * const implementation *impl = simdjson::get_active_implementation(); ++ * cout << "simdjson is optimized for " << impl->name() << "(" << impl->description() << ")" << endl; ++ * ++ * @return the name of the implementation, e.g. "haswell", "westmere", "arm64". ++ */ ++ virtual const std::string &name() const { return _name; } ++ ++ /** ++ * The description of this implementation. ++ * ++ * const implementation *impl = simdjson::get_active_implementation(); ++ * cout << "simdjson is optimized for " << impl->name() << "(" << impl->description() << ")" << endl; ++ * ++ * @return the description of the implementation, e.g. "Intel/AMD AVX2", "Intel/AMD SSE4.2", "ARM NEON". ++ */ ++ virtual const std::string &description() const { return _description; } ++ ++ /** ++ * The instruction sets this implementation is compiled against ++ * and the current CPU match. This function may poll the current CPU/system ++ * and should therefore not be called too often if performance is a concern. ++ * ++ * @return true if the implementation can be safely used on the current system (determined at runtime). ++ */ ++ bool supported_by_runtime_system() const; ++ ++ /** ++ * @private For internal implementation use ++ * ++ * The instruction sets this implementation is compiled against. ++ * ++ * @return a mask of all required `internal::instruction_set::` values. ++ */ ++ virtual uint32_t required_instruction_sets() const { return _required_instruction_sets; } ++ ++ /** ++ * @private For internal implementation use ++ * ++ * const implementation *impl = simdjson::get_active_implementation(); ++ * cout << "simdjson is optimized for " << impl->name() << "(" << impl->description() << ")" << endl; ++ * ++ * @param capacity The largest document that will be passed to the parser. ++ * @param max_depth The maximum JSON object/array nesting this parser is expected to handle. ++ * @param dst The place to put the resulting parser implementation. ++ * @return the error code, or SUCCESS if there was no error. ++ */ ++ virtual error_code create_dom_parser_implementation( ++ size_t capacity, ++ size_t max_depth, ++ std::unique_ptr &dst ++ ) const noexcept = 0; ++ ++ /** ++ * @private For internal implementation use ++ * ++ * Minify the input string assuming that it represents a JSON string, does not parse or validate. ++ * ++ * Overridden by each implementation. ++ * ++ * @param buf the json document to minify. ++ * @param len the length of the json document. ++ * @param dst the buffer to write the minified document to. *MUST* be allocated up to len + SIMDJSON_PADDING bytes. ++ * @param dst_len the number of bytes written. Output only. ++ * @return the error code, or SUCCESS if there was no error. ++ */ ++ simdjson_warn_unused virtual error_code minify(const uint8_t *buf, size_t len, uint8_t *dst, size_t &dst_len) const noexcept = 0; ++ ++ ++ /** ++ * Validate the UTF-8 string. ++ * ++ * Overridden by each implementation. ++ * ++ * @param buf the string to validate. ++ * @param len the length of the string in bytes. ++ * @return true if and only if the string is valid UTF-8. ++ */ ++ simdjson_warn_unused virtual bool validate_utf8(const char *buf, size_t len) const noexcept = 0; ++ ++protected: ++ /** @private Construct an implementation with the given name and description. For subclasses. */ ++ simdjson_inline implementation( ++ std::string_view name, ++ std::string_view description, ++ uint32_t required_instruction_sets ++ ) : ++ _name(name), ++ _description(description), ++ _required_instruction_sets(required_instruction_sets) ++ { ++ } ++ virtual ~implementation()=default; ++ ++private: ++ /** ++ * The name of this implementation. ++ */ ++ const std::string _name; ++ ++ /** ++ * The description of this implementation. ++ */ ++ const std::string _description; ++ ++ /** ++ * Instruction sets required for this implementation. ++ */ ++ const uint32_t _required_instruction_sets; ++}; ++ ++/** @private */ ++namespace internal { ++ ++/** ++ * The list of available implementations compiled into simdjson. ++ */ ++class available_implementation_list { ++public: ++ /** Get the list of available implementations compiled into simdjson */ ++ simdjson_inline available_implementation_list() {} ++ /** Number of implementations */ ++ size_t size() const noexcept; ++ /** STL const begin() iterator */ ++ const implementation * const *begin() const noexcept; ++ /** STL const end() iterator */ ++ const implementation * const *end() const noexcept; ++ ++ /** ++ * Get the implementation with the given name. ++ * ++ * Case sensitive. ++ * ++ * const implementation *impl = simdjson::get_available_implementations()["westmere"]; ++ * if (!impl) { exit(1); } ++ * if (!imp->supported_by_runtime_system()) { exit(1); } ++ * simdjson::get_active_implementation() = impl; ++ * ++ * @param name the implementation to find, e.g. "westmere", "haswell", "arm64" ++ * @return the implementation, or nullptr if the parse failed. ++ */ ++ const implementation * operator[](const std::string_view &name) const noexcept { ++ for (const implementation * impl : *this) { ++ if (impl->name() == name) { return impl; } ++ } ++ return nullptr; ++ } ++ ++ /** ++ * Detect the most advanced implementation supported by the current host. ++ * ++ * This is used to initialize the implementation on startup. ++ * ++ * const implementation *impl = simdjson::available_implementation::detect_best_supported(); ++ * simdjson::get_active_implementation() = impl; ++ * ++ * @return the most advanced supported implementation for the current host, or an ++ * implementation that returns UNSUPPORTED_ARCHITECTURE if there is no supported ++ * implementation. Will never return nullptr. ++ */ ++ const implementation *detect_best_supported() const noexcept; ++}; ++ ++template ++class atomic_ptr { ++public: ++ atomic_ptr(T *_ptr) : ptr{_ptr} {} ++ ++ operator const T*() const { return ptr.load(); } ++ const T& operator*() const { return *ptr; } ++ const T* operator->() const { return ptr.load(); } ++ ++ operator T*() { return ptr.load(); } ++ T& operator*() { return *ptr; } ++ T* operator->() { return ptr.load(); } ++ atomic_ptr& operator=(T *_ptr) { ptr = _ptr; return *this; } ++ ++private: ++ std::atomic ptr; ++}; ++ ++} // namespace internal ++ ++/** ++ * The list of available implementations compiled into simdjson. ++ */ ++extern SIMDJSON_DLLIMPORTEXPORT const internal::available_implementation_list& get_available_implementations(); ++ ++/** ++ * The active implementation. ++ * ++ * Automatically initialized on first use to the most advanced implementation supported by this hardware. ++ */ ++extern SIMDJSON_DLLIMPORTEXPORT internal::atomic_ptr& get_active_implementation(); ++ ++} // namespace simdjson ++ ++#endif // SIMDJSON_IMPLEMENTATION_H ++/* end file include/simdjson/implementation.h */ ++ ++// Inline functions ++/* begin file include/simdjson/error-inl.h */ ++#ifndef SIMDJSON_INLINE_ERROR_H ++#define SIMDJSON_INLINE_ERROR_H ++ ++#include ++#include ++#include ++ ++namespace simdjson { ++namespace internal { ++ // We store the error code so we can validate the error message is associated with the right code ++ struct error_code_info { ++ error_code code; ++ const char* message; // do not use a fancy std::string where a simple C string will do (no alloc, no destructor) ++ }; ++ // These MUST match the codes in error_code. We check this constraint in basictests. ++ extern SIMDJSON_DLLIMPORTEXPORT const error_code_info error_codes[]; ++} // namespace internal ++ ++ ++inline const char *error_message(error_code error) noexcept { ++ // If you're using error_code, we're trusting you got it from the enum. ++ return internal::error_codes[int(error)].message; ++} ++ ++// deprecated function ++#ifndef SIMDJSON_DISABLE_DEPRECATED_API ++inline const std::string error_message(int error) noexcept { ++ if (error < 0 || error >= error_code::NUM_ERROR_CODES) { ++ return internal::error_codes[UNEXPECTED_ERROR].message; ++ } ++ return internal::error_codes[error].message; ++} ++#endif // SIMDJSON_DISABLE_DEPRECATED_API ++ ++inline std::ostream& operator<<(std::ostream& out, error_code error) noexcept { ++ return out << error_message(error); ++} ++ ++namespace internal { ++ ++// ++// internal::simdjson_result_base inline implementation ++// ++ ++template ++simdjson_inline void simdjson_result_base::tie(T &value, error_code &error) && noexcept { ++ error = this->second; ++ if (!error) { ++ value = std::forward>(*this).first; ++ } ++} ++ ++template ++simdjson_warn_unused simdjson_inline error_code simdjson_result_base::get(T &value) && noexcept { ++ error_code error; ++ std::forward>(*this).tie(value, error); ++ return error; ++} ++ ++template ++simdjson_inline error_code simdjson_result_base::error() const noexcept { ++ return this->second; ++} ++ ++#if SIMDJSON_EXCEPTIONS ++ ++template ++simdjson_inline T& simdjson_result_base::value() & noexcept(false) { ++ if (error()) { throw simdjson_error(error()); } ++ return this->first; ++} ++ ++template ++simdjson_inline T&& simdjson_result_base::value() && noexcept(false) { ++ return std::forward>(*this).take_value(); ++} ++ ++template ++simdjson_inline T&& simdjson_result_base::take_value() && noexcept(false) { ++ if (error()) { throw simdjson_error(error()); } ++ return std::forward(this->first); ++} ++ ++template ++simdjson_inline simdjson_result_base::operator T&&() && noexcept(false) { ++ return std::forward>(*this).take_value(); ++} ++ ++#endif // SIMDJSON_EXCEPTIONS ++ ++template ++simdjson_inline const T& simdjson_result_base::value_unsafe() const& noexcept { ++ return this->first; ++} ++ ++template ++simdjson_inline T&& simdjson_result_base::value_unsafe() && noexcept { ++ return std::forward(this->first); ++} ++ ++template ++simdjson_inline simdjson_result_base::simdjson_result_base(T &&value, error_code error) noexcept ++ : std::pair(std::forward(value), error) {} ++template ++simdjson_inline simdjson_result_base::simdjson_result_base(error_code error) noexcept ++ : simdjson_result_base(T{}, error) {} ++template ++simdjson_inline simdjson_result_base::simdjson_result_base(T &&value) noexcept ++ : simdjson_result_base(std::forward(value), SUCCESS) {} ++template ++simdjson_inline simdjson_result_base::simdjson_result_base() noexcept ++ : simdjson_result_base(T{}, UNINITIALIZED) {} ++ ++} // namespace internal ++ ++/// ++/// simdjson_result inline implementation ++/// ++ ++template ++simdjson_inline void simdjson_result::tie(T &value, error_code &error) && noexcept { ++ std::forward>(*this).tie(value, error); ++} ++ ++template ++simdjson_warn_unused simdjson_inline error_code simdjson_result::get(T &value) && noexcept { ++ return std::forward>(*this).get(value); ++} ++ ++template ++simdjson_inline error_code simdjson_result::error() const noexcept { ++ return internal::simdjson_result_base::error(); ++} ++ ++#if SIMDJSON_EXCEPTIONS ++ ++template ++simdjson_inline T& simdjson_result::value() & noexcept(false) { ++ return internal::simdjson_result_base::value(); ++} ++ ++template ++simdjson_inline T&& simdjson_result::value() && noexcept(false) { ++ return std::forward>(*this).value(); ++} ++ ++template ++simdjson_inline T&& simdjson_result::take_value() && noexcept(false) { ++ return std::forward>(*this).take_value(); ++} ++ ++template ++simdjson_inline simdjson_result::operator T&&() && noexcept(false) { ++ return std::forward>(*this).take_value(); ++} ++ ++#endif // SIMDJSON_EXCEPTIONS ++ ++template ++simdjson_inline const T& simdjson_result::value_unsafe() const& noexcept { ++ return internal::simdjson_result_base::value_unsafe(); ++} ++ ++template ++simdjson_inline T&& simdjson_result::value_unsafe() && noexcept { ++ return std::forward>(*this).value_unsafe(); ++} ++ ++template ++simdjson_inline simdjson_result::simdjson_result(T &&value, error_code error) noexcept ++ : internal::simdjson_result_base(std::forward(value), error) {} ++template ++simdjson_inline simdjson_result::simdjson_result(error_code error) noexcept ++ : internal::simdjson_result_base(error) {} ++template ++simdjson_inline simdjson_result::simdjson_result(T &&value) noexcept ++ : internal::simdjson_result_base(std::forward(value)) {} ++template ++simdjson_inline simdjson_result::simdjson_result() noexcept ++ : internal::simdjson_result_base() {} ++ ++} // namespace simdjson ++ ++#endif // SIMDJSON_INLINE_ERROR_H ++/* end file include/simdjson/error-inl.h */ ++/* begin file include/simdjson/padded_string-inl.h */ ++#ifndef SIMDJSON_INLINE_PADDED_STRING_H ++#define SIMDJSON_INLINE_PADDED_STRING_H ++ ++ ++#include ++#include ++#include ++#include ++ ++namespace simdjson { ++namespace internal { ++ ++// The allocate_padded_buffer function is a low-level function to allocate memory ++// with padding so we can read past the "length" bytes safely. It is used by ++// the padded_string class automatically. It returns nullptr in case ++// of error: the caller should check for a null pointer. ++// The length parameter is the maximum size in bytes of the string. ++// The caller is responsible to free the memory (e.g., delete[] (...)). ++inline char *allocate_padded_buffer(size_t length) noexcept { ++ const size_t totalpaddedlength = length + SIMDJSON_PADDING; ++ if(totalpaddedlength(1UL<<20)) { ++ return nullptr; ++ } ++#endif ++ ++ char *padded_buffer = new (std::nothrow) char[totalpaddedlength]; ++ if (padded_buffer == nullptr) { ++ return nullptr; ++ } ++ // We write zeroes in the padded region to avoid having uninitized ++ // garbage. If nothing else, garbage getting read might trigger a ++ // warning in a memory checking. ++ std::memset(padded_buffer + length, 0, totalpaddedlength - length); ++ return padded_buffer; ++} // allocate_padded_buffer() ++ ++} // namespace internal ++ ++ ++inline padded_string::padded_string() noexcept = default; ++inline padded_string::padded_string(size_t length) noexcept ++ : viable_size(length), data_ptr(internal::allocate_padded_buffer(length)) { ++} ++inline padded_string::padded_string(const char *data, size_t length) noexcept ++ : viable_size(length), data_ptr(internal::allocate_padded_buffer(length)) { ++ if ((data != nullptr) && (data_ptr != nullptr)) { ++ std::memcpy(data_ptr, data, length); ++ } ++} ++// note: do not pass std::string arguments by value ++inline padded_string::padded_string(const std::string & str_ ) noexcept ++ : viable_size(str_.size()), data_ptr(internal::allocate_padded_buffer(str_.size())) { ++ if (data_ptr != nullptr) { ++ std::memcpy(data_ptr, str_.data(), str_.size()); ++ } ++} ++// note: do pass std::string_view arguments by value ++inline padded_string::padded_string(std::string_view sv_) noexcept ++ : viable_size(sv_.size()), data_ptr(internal::allocate_padded_buffer(sv_.size())) { ++ if(simdjson_unlikely(!data_ptr)) { ++ //allocation failed or zero size ++ viable_size=0; ++ return; ++ } ++ if (sv_.size()) { ++ std::memcpy(data_ptr, sv_.data(), sv_.size()); ++ } ++} ++inline padded_string::padded_string(padded_string &&o) noexcept ++ : viable_size(o.viable_size), data_ptr(o.data_ptr) { ++ o.data_ptr = nullptr; // we take ownership ++} ++ ++inline padded_string &padded_string::operator=(padded_string &&o) noexcept { ++ delete[] data_ptr; ++ data_ptr = o.data_ptr; ++ viable_size = o.viable_size; ++ o.data_ptr = nullptr; // we take ownership ++ o.viable_size = 0; ++ return *this; ++} ++ ++inline void padded_string::swap(padded_string &o) noexcept { ++ size_t tmp_viable_size = viable_size; ++ char *tmp_data_ptr = data_ptr; ++ viable_size = o.viable_size; ++ data_ptr = o.data_ptr; ++ o.data_ptr = tmp_data_ptr; ++ o.viable_size = tmp_viable_size; ++} ++ ++inline padded_string::~padded_string() noexcept { ++ delete[] data_ptr; ++} ++ ++inline size_t padded_string::size() const noexcept { return viable_size; } ++ ++inline size_t padded_string::length() const noexcept { return viable_size; } ++ ++inline const char *padded_string::data() const noexcept { return data_ptr; } ++ ++inline char *padded_string::data() noexcept { return data_ptr; } ++ ++inline padded_string::operator std::string_view() const { return std::string_view(data(), length()); } ++ ++inline padded_string::operator padded_string_view() const noexcept { ++ return padded_string_view(data(), length(), length() + SIMDJSON_PADDING); ++} ++ ++inline simdjson_result padded_string::load(std::string_view filename) noexcept { ++ // Open the file ++ SIMDJSON_PUSH_DISABLE_WARNINGS ++ SIMDJSON_DISABLE_DEPRECATED_WARNING // Disable CRT_SECURE warning on MSVC: manually verified this is safe ++ std::FILE *fp = std::fopen(filename.data(), "rb"); ++ SIMDJSON_POP_DISABLE_WARNINGS ++ ++ if (fp == nullptr) { ++ return IO_ERROR; ++ } ++ ++ // Get the file size ++ int ret; ++#if SIMDJSON_VISUAL_STUDIO && !SIMDJSON_IS_32BITS ++ ret = _fseeki64(fp, 0, SEEK_END); ++#else ++ ret = std::fseek(fp, 0, SEEK_END); ++#endif // _WIN64 ++ if(ret < 0) { ++ std::fclose(fp); ++ return IO_ERROR; ++ } ++#if SIMDJSON_VISUAL_STUDIO && !SIMDJSON_IS_32BITS ++ __int64 llen = _ftelli64(fp); ++ if(llen == -1L) { ++ std::fclose(fp); ++ return IO_ERROR; ++ } ++#else ++ long llen = std::ftell(fp); ++ if((llen < 0) || (llen == LONG_MAX)) { ++ std::fclose(fp); ++ return IO_ERROR; ++ } ++#endif ++ ++ // Allocate the padded_string ++ size_t len = static_cast(llen); ++ padded_string s(len); ++ if (s.data() == nullptr) { ++ std::fclose(fp); ++ return MEMALLOC; ++ } ++ ++ // Read the padded_string ++ std::rewind(fp); ++ size_t bytes_read = std::fread(s.data(), 1, len, fp); ++ if (std::fclose(fp) != 0 || bytes_read != len) { ++ return IO_ERROR; ++ } ++ ++ return s; ++} ++ ++} // namespace simdjson ++ ++#endif // SIMDJSON_INLINE_PADDED_STRING_H ++/* end file include/simdjson/padded_string-inl.h */ ++/* begin file include/simdjson/padded_string_view-inl.h */ ++#ifndef SIMDJSON_PADDED_STRING_VIEW_INL_H ++#define SIMDJSON_PADDED_STRING_VIEW_INL_H ++ ++ ++#include ++#include ++#include ++#include ++ ++namespace simdjson { ++ ++inline padded_string_view::padded_string_view(const char* s, size_t len, size_t capacity) noexcept ++ : std::string_view(s, len), _capacity(capacity) ++{ ++} ++ ++inline padded_string_view::padded_string_view(const uint8_t* s, size_t len, size_t capacity) noexcept ++ : padded_string_view(reinterpret_cast(s), len, capacity) ++{ ++} ++ ++inline padded_string_view::padded_string_view(const std::string &s) noexcept ++ : std::string_view(s), _capacity(s.capacity()) ++{ ++} ++ ++inline padded_string_view::padded_string_view(std::string_view s, size_t capacity) noexcept ++ : std::string_view(s), _capacity(capacity) ++{ ++} ++ ++inline size_t padded_string_view::capacity() const noexcept { return _capacity; } ++ ++inline size_t padded_string_view::padding() const noexcept { return capacity() - length(); } ++ ++} // namespace simdjson ++ ++#endif // SIMDJSON_PADDED_STRING_VIEW_INL_H ++/* end file include/simdjson/padded_string_view-inl.h */ ++ ++SIMDJSON_POP_DISABLE_WARNINGS ++ ++#endif // SIMDJSON_BASE_H ++/* end file include/simdjson/base.h */ ++ ++SIMDJSON_PUSH_DISABLE_WARNINGS ++SIMDJSON_DISABLE_UNDESIRED_WARNINGS ++ ++/* begin file include/simdjson/dom/array.h */ ++#ifndef SIMDJSON_DOM_ARRAY_H ++#define SIMDJSON_DOM_ARRAY_H ++ ++/* begin file include/simdjson/internal/tape_ref.h */ ++#ifndef SIMDJSON_INTERNAL_TAPE_REF_H ++#define SIMDJSON_INTERNAL_TAPE_REF_H ++ ++/* begin file include/simdjson/internal/tape_type.h */ ++#ifndef SIMDJSON_INTERNAL_TAPE_TYPE_H ++#define SIMDJSON_INTERNAL_TAPE_TYPE_H ++ ++namespace simdjson { ++namespace internal { ++ ++/** ++ * The possible types in the tape. ++ */ ++enum class tape_type { ++ ROOT = 'r', ++ START_ARRAY = '[', ++ START_OBJECT = '{', ++ END_ARRAY = ']', ++ END_OBJECT = '}', ++ STRING = '"', ++ INT64 = 'l', ++ UINT64 = 'u', ++ DOUBLE = 'd', ++ TRUE_VALUE = 't', ++ FALSE_VALUE = 'f', ++ NULL_VALUE = 'n' ++}; // enum class tape_type ++ ++} // namespace internal ++} // namespace simdjson ++ ++#endif // SIMDJSON_INTERNAL_TAPE_TYPE_H ++/* end file include/simdjson/internal/tape_type.h */ ++ ++namespace simdjson { ++ ++namespace dom { ++ class document; ++} ++ ++namespace internal { ++ ++constexpr const uint64_t JSON_VALUE_MASK = 0x00FFFFFFFFFFFFFF; ++constexpr const uint32_t JSON_COUNT_MASK = 0xFFFFFF; ++ ++/** ++ * A reference to an element on the tape. Internal only. ++ */ ++class tape_ref { ++public: ++ simdjson_inline tape_ref() noexcept; ++ simdjson_inline tape_ref(const dom::document *doc, size_t json_index) noexcept; ++ inline size_t after_element() const noexcept; ++ simdjson_inline tape_type tape_ref_type() const noexcept; ++ simdjson_inline uint64_t tape_value() const noexcept; ++ simdjson_inline bool is_double() const noexcept; ++ simdjson_inline bool is_int64() const noexcept; ++ simdjson_inline bool is_uint64() const noexcept; ++ simdjson_inline bool is_false() const noexcept; ++ simdjson_inline bool is_true() const noexcept; ++ simdjson_inline bool is_null_on_tape() const noexcept;// different name to avoid clash with is_null. ++ simdjson_inline uint32_t matching_brace_index() const noexcept; ++ simdjson_inline uint32_t scope_count() const noexcept; ++ template ++ simdjson_inline T next_tape_value() const noexcept; ++ simdjson_inline uint32_t get_string_length() const noexcept; ++ simdjson_inline const char * get_c_str() const noexcept; ++ inline std::string_view get_string_view() const noexcept; ++ simdjson_inline bool is_document_root() const noexcept; ++ simdjson_inline bool usable() const noexcept; ++ ++ /** The document this element references. */ ++ const dom::document *doc; ++ ++ /** The index of this element on `doc.tape[]` */ ++ size_t json_index; ++}; ++ ++} // namespace internal ++} // namespace simdjson ++ ++#endif // SIMDJSON_INTERNAL_TAPE_REF_H ++/* end file include/simdjson/internal/tape_ref.h */ ++ ++namespace simdjson { ++ ++namespace internal { ++template ++class string_builder; ++} ++namespace dom { ++ ++class document; ++class element; ++ ++/** ++ * JSON array. ++ */ ++class array { ++public: ++ /** Create a new, invalid array */ ++ simdjson_inline array() noexcept; ++ ++ class iterator { ++ public: ++ using value_type = element; ++ using difference_type = std::ptrdiff_t; ++ ++ /** ++ * Get the actual value ++ */ ++ inline value_type operator*() const noexcept; ++ /** ++ * Get the next value. ++ * ++ * Part of the std::iterator interface. ++ */ ++ inline iterator& operator++() noexcept; ++ /** ++ * Get the next value. ++ * ++ * Part of the std::iterator interface. ++ */ ++ inline iterator operator++(int) noexcept; ++ /** ++ * Check if these values come from the same place in the JSON. ++ * ++ * Part of the std::iterator interface. ++ */ ++ inline bool operator!=(const iterator& other) const noexcept; ++ inline bool operator==(const iterator& other) const noexcept; ++ ++ inline bool operator<(const iterator& other) const noexcept; ++ inline bool operator<=(const iterator& other) const noexcept; ++ inline bool operator>=(const iterator& other) const noexcept; ++ inline bool operator>(const iterator& other) const noexcept; ++ ++ iterator() noexcept = default; ++ iterator(const iterator&) noexcept = default; ++ iterator& operator=(const iterator&) noexcept = default; ++ private: ++ simdjson_inline iterator(const internal::tape_ref &tape) noexcept; ++ internal::tape_ref tape; ++ friend class array; ++ }; ++ ++ /** ++ * Return the first array element. ++ * ++ * Part of the std::iterable interface. ++ */ ++ inline iterator begin() const noexcept; ++ /** ++ * One past the last array element. ++ * ++ * Part of the std::iterable interface. ++ */ ++ inline iterator end() const noexcept; ++ /** ++ * Get the size of the array (number of immediate children). ++ * It is a saturated value with a maximum of 0xFFFFFF: if the value ++ * is 0xFFFFFF then the size is 0xFFFFFF or greater. ++ */ ++ inline size_t size() const noexcept; ++ /** ++ * Get the total number of slots used by this array on the tape. ++ * ++ * Note that this is not the same thing as `size()`, which reports the ++ * number of actual elements within an array (not counting its children). ++ * ++ * Since an element can use 1 or 2 slots on the tape, you can only use this ++ * to figure out the total size of an array (including its children, ++ * recursively) if you know its structure ahead of time. ++ **/ ++ inline size_t number_of_slots() const noexcept; ++ /** ++ * Get the value associated with the given JSON pointer. We use the RFC 6901 ++ * https://tools.ietf.org/html/rfc6901 standard, interpreting the current node ++ * as the root of its own JSON document. ++ * ++ * dom::parser parser; ++ * array a = parser.parse(R"([ { "foo": { "a": [ 10, 20, 30 ] }} ])"_padded); ++ * a.at_pointer("/0/foo/a/1") == 20 ++ * a.at_pointer("0")["foo"]["a"].at(1) == 20 ++ * ++ * @return The value associated with the given JSON pointer, or: ++ * - NO_SUCH_FIELD if a field does not exist in an object ++ * - INDEX_OUT_OF_BOUNDS if an array index is larger than an array length ++ * - INCORRECT_TYPE if a non-integer is used to access an array ++ * - INVALID_JSON_POINTER if the JSON pointer is invalid and cannot be parsed ++ */ ++ inline simdjson_result at_pointer(std::string_view json_pointer) const noexcept; ++ ++ /** ++ * Get the value at the given index. This function has linear-time complexity and ++ * is equivalent to the following: ++ * ++ * size_t i=0; ++ * for (auto element : *this) { ++ * if (i == index) { return element; } ++ * i++; ++ * } ++ * return INDEX_OUT_OF_BOUNDS; ++ * ++ * Avoid calling the at() function repeatedly. ++ * ++ * @return The value at the given index, or: ++ * - INDEX_OUT_OF_BOUNDS if the array index is larger than an array length ++ */ ++ inline simdjson_result at(size_t index) const noexcept; ++ ++private: ++ simdjson_inline array(const internal::tape_ref &tape) noexcept; ++ internal::tape_ref tape; ++ friend class element; ++ friend struct simdjson_result; ++ template ++ friend class simdjson::internal::string_builder; ++}; ++ ++ ++} // namespace dom ++ ++/** The result of a JSON conversion that may fail. */ ++template<> ++struct simdjson_result : public internal::simdjson_result_base { ++public: ++ simdjson_inline simdjson_result() noexcept; ///< @private ++ simdjson_inline simdjson_result(dom::array value) noexcept; ///< @private ++ simdjson_inline simdjson_result(error_code error) noexcept; ///< @private ++ ++ inline simdjson_result at_pointer(std::string_view json_pointer) const noexcept; ++ inline simdjson_result at(size_t index) const noexcept; ++ ++#if SIMDJSON_EXCEPTIONS ++ inline dom::array::iterator begin() const noexcept(false); ++ inline dom::array::iterator end() const noexcept(false); ++ inline size_t size() const noexcept(false); ++#endif // SIMDJSON_EXCEPTIONS ++}; ++ ++ ++ ++} // namespace simdjson ++ ++#if defined(__cpp_lib_ranges) ++#include ++ ++namespace std { ++namespace ranges { ++template<> ++inline constexpr bool enable_view = true; ++#if SIMDJSON_EXCEPTIONS ++template<> ++inline constexpr bool enable_view> = true; ++#endif // SIMDJSON_EXCEPTIONS ++} // namespace ranges ++} // namespace std ++#endif // defined(__cpp_lib_ranges) ++ ++#endif // SIMDJSON_DOM_ARRAY_H ++/* end file include/simdjson/dom/array.h */ ++/* begin file include/simdjson/dom/document_stream.h */ ++#ifndef SIMDJSON_DOCUMENT_STREAM_H ++#define SIMDJSON_DOCUMENT_STREAM_H ++ ++/* begin file include/simdjson/dom/parser.h */ ++#ifndef SIMDJSON_DOM_PARSER_H ++#define SIMDJSON_DOM_PARSER_H ++ ++/* begin file include/simdjson/dom/document.h */ ++#ifndef SIMDJSON_DOM_DOCUMENT_H ++#define SIMDJSON_DOM_DOCUMENT_H ++ ++#include ++#include ++ ++namespace simdjson { ++namespace dom { ++ ++class element; ++ ++/** ++ * A parsed JSON document. ++ * ++ * This class cannot be copied, only moved, to avoid unintended allocations. ++ */ ++class document { ++public: ++ /** ++ * Create a document container with zero capacity. ++ * ++ * The parser will allocate capacity as needed. ++ */ ++ document() noexcept = default; ++ ~document() noexcept = default; ++ ++ /** ++ * Take another document's buffers. ++ * ++ * @param other The document to take. Its capacity is zeroed and it is invalidated. ++ */ ++ document(document &&other) noexcept = default; ++ /** @private */ ++ document(const document &) = delete; // Disallow copying ++ /** ++ * Take another document's buffers. ++ * ++ * @param other The document to take. Its capacity is zeroed. ++ */ ++ document &operator=(document &&other) noexcept = default; ++ /** @private */ ++ document &operator=(const document &) = delete; // Disallow copying ++ ++ /** ++ * Get the root element of this document as a JSON array. ++ */ ++ element root() const noexcept; ++ ++ /** ++ * @private Dump the raw tape for debugging. ++ * ++ * @param os the stream to output to. ++ * @return false if the tape is likely wrong (e.g., you did not parse a valid JSON). ++ */ ++ bool dump_raw_tape(std::ostream &os) const noexcept; ++ ++ /** @private Structural values. */ ++ std::unique_ptr tape{}; ++ ++ /** @private String values. ++ * ++ * Should be at least byte_capacity. ++ */ ++ std::unique_ptr string_buf{}; ++ /** @private Allocate memory to support ++ * input JSON documents of up to len bytes. ++ * ++ * When calling this function, you lose ++ * all the data. ++ * ++ * The memory allocation is strict: you ++ * can you use this function to increase ++ * or lower the amount of allocated memory. ++ * Passsing zero clears the memory. ++ */ ++ error_code allocate(size_t len) noexcept; ++ /** @private Capacity in bytes, in terms ++ * of how many bytes of input JSON we can ++ * support. ++ */ ++ size_t capacity() const noexcept; ++ ++ ++private: ++ size_t allocated_capacity{0}; ++ friend class parser; ++}; // class document ++ ++} // namespace dom ++} // namespace simdjson ++ ++#endif // SIMDJSON_DOM_DOCUMENT_H ++/* end file include/simdjson/dom/document.h */ ++#include ++#include ++#include ++ ++namespace simdjson { ++ ++namespace dom { ++ ++class document_stream; ++class element; ++ ++/** The default batch size for parser.parse_many() and parser.load_many() */ ++static constexpr size_t DEFAULT_BATCH_SIZE = 1000000; ++/** ++ * Some adversary might try to set the batch size to 0 or 1, which might cause problems. ++ * We set a minimum of 32B since anything else is highly likely to be an error. In practice, ++ * most users will want a much larger batch size. ++ * ++ * All non-negative MINIMAL_BATCH_SIZE values should be 'safe' except that, obviously, no JSON ++ * document can ever span 0 or 1 byte and that very large values would create memory allocation issues. ++ */ ++static constexpr size_t MINIMAL_BATCH_SIZE = 32; ++ ++/** ++ * It is wasteful to allocate memory for tiny documents (e.g., 4 bytes). ++ */ ++static constexpr size_t MINIMAL_DOCUMENT_CAPACITY = 32; ++ ++/** ++ * A persistent document parser. ++ * ++ * The parser is designed to be reused, holding the internal buffers necessary to do parsing, ++ * as well as memory for a single document. The parsed document is overwritten on each parse. ++ * ++ * This class cannot be copied, only moved, to avoid unintended allocations. ++ * ++ * @note Moving a parser instance may invalidate "dom::element" instances. If you need to ++ * preserve both the "dom::element" instances and the parser, consider wrapping the parser ++ * instance in a std::unique_ptr instance: ++ * ++ * std::unique_ptr parser(new dom::parser{}); ++ * auto error = parser->load(f).get(root); ++ * ++ * You can then move std::unique_ptr safely. ++ * ++ * @note This is not thread safe: one parser cannot produce two documents at the same time! ++ */ ++class parser { ++public: ++ /** ++ * Create a JSON parser. ++ * ++ * The new parser will have zero capacity. ++ * ++ * @param max_capacity The maximum document length the parser can automatically handle. The parser ++ * will allocate more capacity on an as needed basis (when it sees documents too big to handle) ++ * up to this amount. The parser still starts with zero capacity no matter what this number is: ++ * to allocate an initial capacity, call allocate() after constructing the parser. ++ * Defaults to SIMDJSON_MAXSIZE_BYTES (the largest single document simdjson can process). ++ */ ++ simdjson_inline explicit parser(size_t max_capacity = SIMDJSON_MAXSIZE_BYTES) noexcept; ++ /** ++ * Take another parser's buffers and state. ++ * ++ * @param other The parser to take. Its capacity is zeroed. ++ */ ++ simdjson_inline parser(parser &&other) noexcept; ++ parser(const parser &) = delete; ///< @private Disallow copying ++ /** ++ * Take another parser's buffers and state. ++ * ++ * @param other The parser to take. Its capacity is zeroed. ++ */ ++ simdjson_inline parser &operator=(parser &&other) noexcept; ++ parser &operator=(const parser &) = delete; ///< @private Disallow copying ++ ++ /** Deallocate the JSON parser. */ ++ ~parser()=default; ++ ++ /** ++ * Load a JSON document from a file and return a reference to it. ++ * ++ * dom::parser parser; ++ * const element doc = parser.load("jsonexamples/twitter.json"); ++ * ++ * The function is eager: the file's content is loaded in memory inside the parser instance ++ * and immediately parsed. The file can be deleted after the `parser.load` call. ++ * ++ * ### IMPORTANT: Document Lifetime ++ * ++ * The JSON document still lives in the parser: this is the most efficient way to parse JSON ++ * documents because it reuses the same buffers, but you *must* use the document before you ++ * destroy the parser or call parse() again. ++ * ++ * Moving the parser instance is safe, but it invalidates the element instances. You may store ++ * the parser instance without moving it by wrapping it inside an `unique_ptr` instance like ++ * so: `std::unique_ptr parser(new dom::parser{});`. ++ * ++ * ### Parser Capacity ++ * ++ * If the parser's current capacity is less than the file length, it will allocate enough capacity ++ * to handle it (up to max_capacity). ++ * ++ * @param path The path to load. ++ * @return The document, or an error: ++ * - IO_ERROR if there was an error opening or reading the file. ++ * Be mindful that on some 32-bit systems, ++ * the file size might be limited to 2 GB. ++ * - MEMALLOC if the parser does not have enough capacity and memory allocation fails. ++ * - CAPACITY if the parser does not have enough capacity and len > max_capacity. ++ * - other json errors if parsing fails. You should not rely on these errors to always the same for the ++ * same document: they may vary under runtime dispatch (so they may vary depending on your system and hardware). ++ */ ++ inline simdjson_result load(const std::string &path) & noexcept; ++ inline simdjson_result load(const std::string &path) && = delete ; ++ /** ++ * Parse a JSON document and return a temporary reference to it. ++ * ++ * dom::parser parser; ++ * element doc_root = parser.parse(buf, len); ++ * ++ * The function eagerly parses the input: the input can be modified and discarded after ++ * the `parser.parse(buf, len)` call has completed. ++ * ++ * ### IMPORTANT: Document Lifetime ++ * ++ * The JSON document still lives in the parser: this is the most efficient way to parse JSON ++ * documents because it reuses the same buffers, but you *must* use the document before you ++ * destroy the parser or call parse() again. ++ * ++ * Moving the parser instance is safe, but it invalidates the element instances. You may store ++ * the parser instance without moving it by wrapping it inside an `unique_ptr` instance like ++ * so: `std::unique_ptr parser(new dom::parser{});`. ++ * ++ * ### REQUIRED: Buffer Padding ++ * ++ * The buffer must have at least SIMDJSON_PADDING extra allocated bytes. It does not matter what ++ * those bytes are initialized to, as long as they are allocated. ++ * ++ * If realloc_if_needed is true (the default), it is assumed that the buffer does *not* have enough padding, ++ * and it is copied into an enlarged temporary buffer before parsing. Thus the following is safe: ++ * ++ * const char *json = R"({"key":"value"})"; ++ * const size_t json_len = std::strlen(json); ++ * simdjson::dom::parser parser; ++ * simdjson::dom::element element = parser.parse(json, json_len); ++ * ++ * If you set realloc_if_needed to false (e.g., parser.parse(json, json_len, false)), ++ * you must provide a buffer with at least SIMDJSON_PADDING extra bytes at the end. ++ * The benefit of setting realloc_if_needed to false is that you avoid a temporary ++ * memory allocation and a copy. ++ * ++ * The padded bytes may be read. It is not important how you initialize ++ * these bytes though we recommend a sensible default like null character values or spaces. ++ * For example, the following low-level code is safe: ++ * ++ * const char *json = R"({"key":"value"})"; ++ * const size_t json_len = std::strlen(json); ++ * std::unique_ptr padded_json_copy{new char[json_len + SIMDJSON_PADDING]}; ++ * std::memcpy(padded_json_copy.get(), json, json_len); ++ * std::memset(padded_json_copy.get() + json_len, '\0', SIMDJSON_PADDING); ++ * simdjson::dom::parser parser; ++ * simdjson::dom::element element = parser.parse(padded_json_copy.get(), json_len, false); ++ * ++ * ### Parser Capacity ++ * ++ * If the parser's current capacity is less than len, it will allocate enough capacity ++ * to handle it (up to max_capacity). ++ * ++ * @param buf The JSON to parse. Must have at least len + SIMDJSON_PADDING allocated bytes, unless ++ * realloc_if_needed is true. ++ * @param len The length of the JSON. ++ * @param realloc_if_needed Whether to reallocate and enlarge the JSON buffer to add padding. ++ * @return An element pointing at the root of the document, or an error: ++ * - MEMALLOC if realloc_if_needed is true or the parser does not have enough capacity, ++ * and memory allocation fails. ++ * - CAPACITY if the parser does not have enough capacity and len > max_capacity. ++ * - other json errors if parsing fails. You should not rely on these errors to always the same for the ++ * same document: they may vary under runtime dispatch (so they may vary depending on your system and hardware). ++ */ ++ inline simdjson_result parse(const uint8_t *buf, size_t len, bool realloc_if_needed = true) & noexcept; ++ inline simdjson_result parse(const uint8_t *buf, size_t len, bool realloc_if_needed = true) && =delete; ++ /** @overload parse(const uint8_t *buf, size_t len, bool realloc_if_needed) */ ++ simdjson_inline simdjson_result parse(const char *buf, size_t len, bool realloc_if_needed = true) & noexcept; ++ simdjson_inline simdjson_result parse(const char *buf, size_t len, bool realloc_if_needed = true) && =delete; ++ /** @overload parse(const uint8_t *buf, size_t len, bool realloc_if_needed) */ ++ simdjson_inline simdjson_result parse(const std::string &s) & noexcept; ++ simdjson_inline simdjson_result parse(const std::string &s) && =delete; ++ /** @overload parse(const uint8_t *buf, size_t len, bool realloc_if_needed) */ ++ simdjson_inline simdjson_result parse(const padded_string &s) & noexcept; ++ simdjson_inline simdjson_result parse(const padded_string &s) && =delete; ++ /** @overload parse(const uint8_t *buf, size_t len, bool realloc_if_needed) */ ++ simdjson_inline simdjson_result parse(const padded_string_view &v) & noexcept; ++ simdjson_inline simdjson_result parse(const padded_string_view &v) && =delete; ++ ++ /** @private We do not want to allow implicit conversion from C string to std::string. */ ++ simdjson_inline simdjson_result parse(const char *buf) noexcept = delete; ++ ++ /** ++ * Parse a JSON document into a provide document instance and return a temporary reference to it. ++ * It is similar to the function `parse` except that instead of parsing into the internal ++ * `document` instance associated with the parser, it allows the user to provide a document ++ * instance. ++ * ++ * dom::parser parser; ++ * dom::document doc; ++ * element doc_root = parser.parse_into_document(doc, buf, len); ++ * ++ * The function eagerly parses the input: the input can be modified and discarded after ++ * the `parser.parse(buf, len)` call has completed. ++ * ++ * ### IMPORTANT: Document Lifetime ++ * ++ * After the call to parse_into_document, the parser is no longer needed. ++ * ++ * The JSON document lives in the document instance: you must keep the document ++ * instance alive while you navigate through it (i.e., used the returned value from ++ * parse_into_document). You are encourage to reuse the document instance ++ * many times with new data to avoid reallocations: ++ * ++ * dom::document doc; ++ * element doc_root1 = parser.parse_into_document(doc, buf1, len); ++ * //... doc_root1 is a pointer inside doc ++ * element doc_root2 = parser.parse_into_document(doc, buf1, len); ++ * //... doc_root2 is a pointer inside doc ++ * // at this point doc_root1 is no longer safe ++ * ++ * Moving the document instance is safe, but it invalidates the element instances. After ++ * moving a document, you can recover safe access to the document root with its `root()` method. ++ * ++ * @param doc The document instance where the parsed data will be stored (on success). ++ * @param buf The JSON to parse. Must have at least len + SIMDJSON_PADDING allocated bytes, unless ++ * realloc_if_needed is true. ++ * @param len The length of the JSON. ++ * @param realloc_if_needed Whether to reallocate and enlarge the JSON buffer to add padding. ++ * @return An element pointing at the root of document, or an error: ++ * - MEMALLOC if realloc_if_needed is true or the parser does not have enough capacity, ++ * and memory allocation fails. ++ * - CAPACITY if the parser does not have enough capacity and len > max_capacity. ++ * - other json errors if parsing fails. You should not rely on these errors to always the same for the ++ * same document: they may vary under runtime dispatch (so they may vary depending on your system and hardware). ++ */ ++ inline simdjson_result parse_into_document(document& doc, const uint8_t *buf, size_t len, bool realloc_if_needed = true) & noexcept; ++ inline simdjson_result parse_into_document(document& doc, const uint8_t *buf, size_t len, bool realloc_if_needed = true) && =delete; ++ /** @overload parse_into_document(const uint8_t *buf, size_t len, bool realloc_if_needed) */ ++ simdjson_inline simdjson_result parse_into_document(document& doc, const char *buf, size_t len, bool realloc_if_needed = true) & noexcept; ++ simdjson_inline simdjson_result parse_into_document(document& doc, const char *buf, size_t len, bool realloc_if_needed = true) && =delete; ++ /** @overload parse_into_document(const uint8_t *buf, size_t len, bool realloc_if_needed) */ ++ simdjson_inline simdjson_result parse_into_document(document& doc, const std::string &s) & noexcept; ++ simdjson_inline simdjson_result parse_into_document(document& doc, const std::string &s) && =delete; ++ /** @overload parse_into_document(const uint8_t *buf, size_t len, bool realloc_if_needed) */ ++ simdjson_inline simdjson_result parse_into_document(document& doc, const padded_string &s) & noexcept; ++ simdjson_inline simdjson_result parse_into_document(document& doc, const padded_string &s) && =delete; ++ ++ /** @private We do not want to allow implicit conversion from C string to std::string. */ ++ simdjson_inline simdjson_result parse_into_document(document& doc, const char *buf) noexcept = delete; ++ ++ /** ++ * Load a file containing many JSON documents. ++ * ++ * dom::parser parser; ++ * for (const element doc : parser.load_many(path)) { ++ * cout << std::string(doc["title"]) << endl; ++ * } ++ * ++ * The file is loaded in memory and can be safely deleted after the `parser.load_many(path)` ++ * function has returned. The memory is held by the `parser` instance. ++ * ++ * The function is lazy: it may be that no more than one JSON document at a time is parsed. ++ * And, possibly, no document many have been parsed when the `parser.load_many(path)` function ++ * returned. ++ * ++ * ### Format ++ * ++ * The file must contain a series of one or more JSON documents, concatenated into a single ++ * buffer, separated by whitespace. It effectively parses until it has a fully valid document, ++ * then starts parsing the next document at that point. (It does this with more parallelism and ++ * lookahead than you might think, though.) ++ * ++ * Documents that consist of an object or array may omit the whitespace between them, concatenating ++ * with no separator. documents that consist of a single primitive (i.e. documents that are not ++ * arrays or objects) MUST be separated with whitespace. ++ * ++ * The documents must not exceed batch_size bytes (by default 1MB) or they will fail to parse. ++ * Setting batch_size to excessively large or excesively small values may impact negatively the ++ * performance. ++ * ++ * ### Error Handling ++ * ++ * All errors are returned during iteration: if there is a global error such as memory allocation, ++ * it will be yielded as the first result. Iteration always stops after the first error. ++ * ++ * As with all other simdjson methods, non-exception error handling is readily available through ++ * the same interface, requiring you to check the error before using the document: ++ * ++ * dom::parser parser; ++ * dom::document_stream docs; ++ * auto error = parser.load_many(path).get(docs); ++ * if (error) { cerr << error << endl; exit(1); } ++ * for (auto doc : docs) { ++ * std::string_view title; ++ * if ((error = doc["title"].get(title)) { cerr << error << endl; exit(1); } ++ * cout << title << endl; ++ * } ++ * ++ * ### Threads ++ * ++ * When compiled with SIMDJSON_THREADS_ENABLED, this method will use a single thread under the ++ * hood to do some lookahead. ++ * ++ * ### Parser Capacity ++ * ++ * If the parser's current capacity is less than batch_size, it will allocate enough capacity ++ * to handle it (up to max_capacity). ++ * ++ * @param path File name pointing at the concatenated JSON to parse. ++ * @param batch_size The batch size to use. MUST be larger than the largest document. The sweet ++ * spot is cache-related: small enough to fit in cache, yet big enough to ++ * parse as many documents as possible in one tight loop. ++ * Defaults to 1MB (as simdjson::dom::DEFAULT_BATCH_SIZE), which has been a reasonable sweet ++ * spot in our tests. ++ * If you set the batch_size to a value smaller than simdjson::dom::MINIMAL_BATCH_SIZE ++ * (currently 32B), it will be replaced by simdjson::dom::MINIMAL_BATCH_SIZE. ++ * @return The stream, or an error. An empty input will yield 0 documents rather than an EMPTY error. Errors: ++ * - IO_ERROR if there was an error opening or reading the file. ++ * - MEMALLOC if the parser does not have enough capacity and memory allocation fails. ++ * - CAPACITY if the parser does not have enough capacity and batch_size > max_capacity. ++ * - other json errors if parsing fails. You should not rely on these errors to always the same for the ++ * same document: they may vary under runtime dispatch (so they may vary depending on your system and hardware). ++ */ ++ inline simdjson_result load_many(const std::string &path, size_t batch_size = dom::DEFAULT_BATCH_SIZE) noexcept; ++ ++ /** ++ * Parse a buffer containing many JSON documents. ++ * ++ * dom::parser parser; ++ * for (element doc : parser.parse_many(buf, len)) { ++ * cout << std::string(doc["title"]) << endl; ++ * } ++ * ++ * No copy of the input buffer is made. ++ * ++ * The function is lazy: it may be that no more than one JSON document at a time is parsed. ++ * And, possibly, no document many have been parsed when the `parser.load_many(path)` function ++ * returned. ++ * ++ * The caller is responsabile to ensure that the input string data remains unchanged and is ++ * not deleted during the loop. In particular, the following is unsafe and will not compile: ++ * ++ * auto docs = parser.parse_many("[\"temporary data\"]"_padded); ++ * // here the string "[\"temporary data\"]" may no longer exist in memory ++ * // the parser instance may not have even accessed the input yet ++ * for (element doc : docs) { ++ * cout << std::string(doc["title"]) << endl; ++ * } ++ * ++ * The following is safe: ++ * ++ * auto json = "[\"temporary data\"]"_padded; ++ * auto docs = parser.parse_many(json); ++ * for (element doc : docs) { ++ * cout << std::string(doc["title"]) << endl; ++ * } ++ * ++ * ### Format ++ * ++ * The buffer must contain a series of one or more JSON documents, concatenated into a single ++ * buffer, separated by whitespace. It effectively parses until it has a fully valid document, ++ * then starts parsing the next document at that point. (It does this with more parallelism and ++ * lookahead than you might think, though.) ++ * ++ * documents that consist of an object or array may omit the whitespace between them, concatenating ++ * with no separator. documents that consist of a single primitive (i.e. documents that are not ++ * arrays or objects) MUST be separated with whitespace. ++ * ++ * The documents must not exceed batch_size bytes (by default 1MB) or they will fail to parse. ++ * Setting batch_size to excessively large or excesively small values may impact negatively the ++ * performance. ++ * ++ * ### Error Handling ++ * ++ * All errors are returned during iteration: if there is a global error such as memory allocation, ++ * it will be yielded as the first result. Iteration always stops after the first error. ++ * ++ * As with all other simdjson methods, non-exception error handling is readily available through ++ * the same interface, requiring you to check the error before using the document: ++ * ++ * dom::parser parser; ++ * dom::document_stream docs; ++ * auto error = parser.load_many(path).get(docs); ++ * if (error) { cerr << error << endl; exit(1); } ++ * for (auto doc : docs) { ++ * std::string_view title; ++ * if ((error = doc["title"].get(title)) { cerr << error << endl; exit(1); } ++ * cout << title << endl; ++ * } ++ * ++ * ### REQUIRED: Buffer Padding ++ * ++ * The buffer must have at least SIMDJSON_PADDING extra allocated bytes. It does not matter what ++ * those bytes are initialized to, as long as they are allocated. ++ * ++ * ### Threads ++ * ++ * When compiled with SIMDJSON_THREADS_ENABLED, this method will use a single thread under the ++ * hood to do some lookahead. ++ * ++ * ### Parser Capacity ++ * ++ * If the parser's current capacity is less than batch_size, it will allocate enough capacity ++ * to handle it (up to max_capacity). ++ * ++ * @param buf The concatenated JSON to parse. Must have at least len + SIMDJSON_PADDING allocated bytes. ++ * @param len The length of the concatenated JSON. ++ * @param batch_size The batch size to use. MUST be larger than the largest document. The sweet ++ * spot is cache-related: small enough to fit in cache, yet big enough to ++ * parse as many documents as possible in one tight loop. ++ * Defaults to 10MB, which has been a reasonable sweet spot in our tests. ++ * @return The stream, or an error. An empty input will yield 0 documents rather than an EMPTY error. Errors: ++ * - MEMALLOC if the parser does not have enough capacity and memory allocation fails ++ * - CAPACITY if the parser does not have enough capacity and batch_size > max_capacity. ++ * - other json errors if parsing fails. You should not rely on these errors to always the same for the ++ * same document: they may vary under runtime dispatch (so they may vary depending on your system and hardware). ++ */ ++ inline simdjson_result parse_many(const uint8_t *buf, size_t len, size_t batch_size = dom::DEFAULT_BATCH_SIZE) noexcept; ++ /** @overload parse_many(const uint8_t *buf, size_t len, size_t batch_size) */ ++ inline simdjson_result parse_many(const char *buf, size_t len, size_t batch_size = dom::DEFAULT_BATCH_SIZE) noexcept; ++ /** @overload parse_many(const uint8_t *buf, size_t len, size_t batch_size) */ ++ inline simdjson_result parse_many(const std::string &s, size_t batch_size = dom::DEFAULT_BATCH_SIZE) noexcept; ++ inline simdjson_result parse_many(const std::string &&s, size_t batch_size) = delete;// unsafe ++ /** @overload parse_many(const uint8_t *buf, size_t len, size_t batch_size) */ ++ inline simdjson_result parse_many(const padded_string &s, size_t batch_size = dom::DEFAULT_BATCH_SIZE) noexcept; ++ inline simdjson_result parse_many(const padded_string &&s, size_t batch_size) = delete;// unsafe ++ ++ /** @private We do not want to allow implicit conversion from C string to std::string. */ ++ simdjson_result parse_many(const char *buf, size_t batch_size = dom::DEFAULT_BATCH_SIZE) noexcept = delete; ++ ++ /** ++ * Ensure this parser has enough memory to process JSON documents up to `capacity` bytes in length ++ * and `max_depth` depth. ++ * ++ * @param capacity The new capacity. ++ * @param max_depth The new max_depth. Defaults to DEFAULT_MAX_DEPTH. ++ * @return The error, if there is one. ++ */ ++ simdjson_warn_unused inline error_code allocate(size_t capacity, size_t max_depth = DEFAULT_MAX_DEPTH) noexcept; ++ ++#ifndef SIMDJSON_DISABLE_DEPRECATED_API ++ /** ++ * @private deprecated because it returns bool instead of error_code, which is our standard for ++ * failures. Use allocate() instead. ++ * ++ * Ensure this parser has enough memory to process JSON documents up to `capacity` bytes in length ++ * and `max_depth` depth. ++ * ++ * @param capacity The new capacity. ++ * @param max_depth The new max_depth. Defaults to DEFAULT_MAX_DEPTH. ++ * @return true if successful, false if allocation failed. ++ */ ++ [[deprecated("Use allocate() instead.")]] ++ simdjson_warn_unused inline bool allocate_capacity(size_t capacity, size_t max_depth = DEFAULT_MAX_DEPTH) noexcept; ++#endif // SIMDJSON_DISABLE_DEPRECATED_API ++ /** ++ * The largest document this parser can support without reallocating. ++ * ++ * @return Current capacity, in bytes. ++ */ ++ simdjson_inline size_t capacity() const noexcept; ++ ++ /** ++ * The largest document this parser can automatically support. ++ * ++ * The parser may reallocate internal buffers as needed up to this amount. ++ * ++ * @return Maximum capacity, in bytes. ++ */ ++ simdjson_inline size_t max_capacity() const noexcept; ++ ++ /** ++ * The maximum level of nested object and arrays supported by this parser. ++ * ++ * @return Maximum depth, in bytes. ++ */ ++ simdjson_inline size_t max_depth() const noexcept; ++ ++ /** ++ * Set max_capacity. This is the largest document this parser can automatically support. ++ * ++ * The parser may reallocate internal buffers as needed up to this amount as documents are passed ++ * to it. ++ * ++ * Note: To avoid limiting the memory to an absurd value, such as zero or two bytes, ++ * iff you try to set max_capacity to a value lower than MINIMAL_DOCUMENT_CAPACITY, ++ * then the maximal capacity is set to MINIMAL_DOCUMENT_CAPACITY. ++ * ++ * This call will not allocate or deallocate, even if capacity is currently above max_capacity. ++ * ++ * @param max_capacity The new maximum capacity, in bytes. ++ */ ++ simdjson_inline void set_max_capacity(size_t max_capacity) noexcept; ++ ++#ifdef SIMDJSON_THREADS_ENABLED ++ /** ++ * The parser instance can use threads when they are available to speed up some ++ * operations. It is enabled by default. Changing this attribute will change the ++ * behavior of the parser for future operations. ++ */ ++ bool threaded{true}; ++#endif ++ /** @private Use the new DOM API instead */ ++ class Iterator; ++ /** @private Use simdjson_error instead */ ++ using InvalidJSON [[deprecated("Use simdjson_error instead")]] = simdjson_error; ++ ++ /** @private [for benchmarking access] The implementation to use */ ++ std::unique_ptr implementation{}; ++ ++ /** @private Use `if (parser.parse(...).error())` instead */ ++ bool valid{false}; ++ /** @private Use `parser.parse(...).error()` instead */ ++ error_code error{UNINITIALIZED}; ++ ++ /** @private Use `parser.parse(...).value()` instead */ ++ document doc{}; ++ ++ /** @private returns true if the document parsed was valid */ ++ [[deprecated("Use the result of parser.parse() instead")]] ++ inline bool is_valid() const noexcept; ++ ++ /** ++ * @private return an error code corresponding to the last parsing attempt, see ++ * simdjson.h will return UNINITIALIZED if no parsing was attempted ++ */ ++ [[deprecated("Use the result of parser.parse() instead")]] ++ inline int get_error_code() const noexcept; ++ ++ /** @private return the string equivalent of "get_error_code" */ ++ [[deprecated("Use error_message() on the result of parser.parse() instead, or cout << error")]] ++ inline std::string get_error_message() const noexcept; ++ ++ /** @private */ ++ [[deprecated("Use cout << on the result of parser.parse() instead")]] ++ inline bool print_json(std::ostream &os) const noexcept; ++ ++ /** @private Private and deprecated: use `parser.parse(...).doc.dump_raw_tape()` instead */ ++ inline bool dump_raw_tape(std::ostream &os) const noexcept; ++ ++ ++private: ++ /** ++ * The maximum document length this parser will automatically support. ++ * ++ * The parser will not be automatically allocated above this amount. ++ */ ++ size_t _max_capacity; ++ ++ /** ++ * The loaded buffer (reused each time load() is called) ++ */ ++ std::unique_ptr loaded_bytes; ++ ++ /** Capacity of loaded_bytes buffer. */ ++ size_t _loaded_bytes_capacity{0}; ++ ++ // all nodes are stored on the doc.tape using a 64-bit word. ++ // ++ // strings, double and ints are stored as ++ // a 64-bit word with a pointer to the actual value ++ // ++ // ++ // ++ // for objects or arrays, store [ or { at the beginning and } and ] at the ++ // end. For the openings ([ or {), we annotate them with a reference to the ++ // location on the doc.tape of the end, and for then closings (} and ]), we ++ // annotate them with a reference to the location of the opening ++ // ++ // ++ ++ /** ++ * Ensure we have enough capacity to handle at least desired_capacity bytes, ++ * and auto-allocate if not. This also allocates memory if needed in the ++ * internal document. ++ */ ++ inline error_code ensure_capacity(size_t desired_capacity) noexcept; ++ /** ++ * Ensure we have enough capacity to handle at least desired_capacity bytes, ++ * and auto-allocate if not. This also allocates memory if needed in the ++ * provided document. ++ */ ++ inline error_code ensure_capacity(document& doc, size_t desired_capacity) noexcept; ++ ++ /** Read the file into loaded_bytes */ ++ inline simdjson_result read_file(const std::string &path) noexcept; ++ ++ friend class parser::Iterator; ++ friend class document_stream; ++ ++ ++}; // class parser ++ ++} // namespace dom ++} // namespace simdjson ++ ++#endif // SIMDJSON_DOM_PARSER_H ++/* end file include/simdjson/dom/parser.h */ ++#ifdef SIMDJSON_THREADS_ENABLED ++#include ++#include ++#include ++#endif ++ ++namespace simdjson { ++namespace dom { ++ ++ ++#ifdef SIMDJSON_THREADS_ENABLED ++/** @private Custom worker class **/ ++struct stage1_worker { ++ stage1_worker() noexcept = default; ++ stage1_worker(const stage1_worker&) = delete; ++ stage1_worker(stage1_worker&&) = delete; ++ stage1_worker operator=(const stage1_worker&) = delete; ++ ~stage1_worker(); ++ /** ++ * We only start the thread when it is needed, not at object construction, this may throw. ++ * You should only call this once. ++ **/ ++ void start_thread(); ++ /** ++ * Start a stage 1 job. You should first call 'run', then 'finish'. ++ * You must call start_thread once before. ++ */ ++ void run(document_stream * ds, dom::parser * stage1, size_t next_batch_start); ++ /** Wait for the run to finish (blocking). You should first call 'run', then 'finish'. **/ ++ void finish(); ++ ++private: ++ ++ /** ++ * Normally, we would never stop the thread. But we do in the destructor. ++ * This function is only safe assuming that you are not waiting for results. You ++ * should have called run, then finish, and be done. ++ **/ ++ void stop_thread(); ++ ++ std::thread thread{}; ++ /** These three variables define the work done by the thread. **/ ++ dom::parser * stage1_thread_parser{}; ++ size_t _next_batch_start{}; ++ document_stream * owner{}; ++ /** ++ * We have two state variables. This could be streamlined to one variable in the future but ++ * we use two for clarity. ++ */ ++ bool has_work{false}; ++ bool can_work{true}; ++ ++ /** ++ * We lock using a mutex. ++ */ ++ std::mutex locking_mutex{}; ++ std::condition_variable cond_var{}; ++}; ++#endif ++ ++/** ++ * A forward-only stream of documents. ++ * ++ * Produced by parser::parse_many. ++ * ++ */ ++class document_stream { ++public: ++ /** ++ * Construct an uninitialized document_stream. ++ * ++ * ```c++ ++ * document_stream docs; ++ * error = parser.parse_many(json).get(docs); ++ * ``` ++ */ ++ simdjson_inline document_stream() noexcept; ++ /** Move one document_stream to another. */ ++ simdjson_inline document_stream(document_stream &&other) noexcept = default; ++ /** Move one document_stream to another. */ ++ simdjson_inline document_stream &operator=(document_stream &&other) noexcept = default; ++ ++ simdjson_inline ~document_stream() noexcept; ++ /** ++ * Returns the input size in bytes. ++ */ ++ inline size_t size_in_bytes() const noexcept; ++ /** ++ * After iterating through the stream, this method ++ * returns the number of bytes that were not parsed at the end ++ * of the stream. If truncated_bytes() differs from zero, ++ * then the input was truncated maybe because incomplete JSON ++ * documents were found at the end of the stream. You ++ * may need to process the bytes in the interval [size_in_bytes()-truncated_bytes(), size_in_bytes()). ++ * ++ * You should only call truncated_bytes() after streaming through all ++ * documents, like so: ++ * ++ * document_stream stream = parser.parse_many(json,window); ++ * for(auto doc : stream) { ++ * // do something with doc ++ * } ++ * size_t truncated = stream.truncated_bytes(); ++ * ++ */ ++ inline size_t truncated_bytes() const noexcept; ++ /** ++ * An iterator through a forward-only stream of documents. ++ */ ++ class iterator { ++ public: ++ using value_type = simdjson_result; ++ using reference = value_type; ++ ++ using difference_type = std::ptrdiff_t; ++ ++ using iterator_category = std::input_iterator_tag; ++ ++ /** ++ * Default constructor. ++ */ ++ simdjson_inline iterator() noexcept; ++ /** ++ * Get the current document (or error). ++ */ ++ simdjson_inline reference operator*() noexcept; ++ /** ++ * Advance to the next document (prefix). ++ */ ++ inline iterator& operator++() noexcept; ++ /** ++ * Check if we're at the end yet. ++ * @param other the end iterator to compare to. ++ */ ++ simdjson_inline bool operator!=(const iterator &other) const noexcept; ++ /** ++ * @private ++ * ++ * Gives the current index in the input document in bytes. ++ * ++ * document_stream stream = parser.parse_many(json,window); ++ * for(auto i = stream.begin(); i != stream.end(); ++i) { ++ * auto doc = *i; ++ * size_t index = i.current_index(); ++ * } ++ * ++ * This function (current_index()) is experimental and the usage ++ * may change in future versions of simdjson: we find the API somewhat ++ * awkward and we would like to offer something friendlier. ++ */ ++ simdjson_inline size_t current_index() const noexcept; ++ /** ++ * @private ++ * ++ * Gives a view of the current document. ++ * ++ * document_stream stream = parser.parse_many(json,window); ++ * for(auto i = stream.begin(); i != stream.end(); ++i) { ++ * auto doc = *i; ++ * std::string_view v = i->source(); ++ * } ++ * ++ * The returned string_view instance is simply a map to the (unparsed) ++ * source string: it may thus include white-space characters and all manner ++ * of padding. ++ * ++ * This function (source()) is experimental and the usage ++ * may change in future versions of simdjson: we find the API somewhat ++ * awkward and we would like to offer something friendlier. ++ */ ++ simdjson_inline std::string_view source() const noexcept; ++ ++ private: ++ simdjson_inline iterator(document_stream *s, bool finished) noexcept; ++ /** The document_stream we're iterating through. */ ++ document_stream* stream; ++ /** Whether we're finished or not. */ ++ bool finished; ++ friend class document_stream; ++ }; ++ ++ /** ++ * Start iterating the documents in the stream. ++ */ ++ simdjson_inline iterator begin() noexcept; ++ /** ++ * The end of the stream, for iterator comparison purposes. ++ */ ++ simdjson_inline iterator end() noexcept; ++ ++private: ++ ++ document_stream &operator=(const document_stream &) = delete; // Disallow copying ++ document_stream(const document_stream &other) = delete; // Disallow copying ++ ++ /** ++ * Construct a document_stream. Does not allocate or parse anything until the iterator is ++ * used. ++ * ++ * @param parser is a reference to the parser instance used to generate this document_stream ++ * @param buf is the raw byte buffer we need to process ++ * @param len is the length of the raw byte buffer in bytes ++ * @param batch_size is the size of the windows (must be strictly greater or equal to the largest JSON document) ++ */ ++ simdjson_inline document_stream( ++ dom::parser &parser, ++ const uint8_t *buf, ++ size_t len, ++ size_t batch_size ++ ) noexcept; ++ ++ /** ++ * Parse the first document in the buffer. Used by begin(), to handle allocation and ++ * initialization. ++ */ ++ inline void start() noexcept; ++ ++ /** ++ * Parse the next document found in the buffer previously given to document_stream. ++ * ++ * The content should be a valid JSON document encoded as UTF-8. If there is a ++ * UTF-8 BOM, the caller is responsible for omitting it, UTF-8 BOM are ++ * discouraged. ++ * ++ * You do NOT need to pre-allocate a parser. This function takes care of ++ * pre-allocating a capacity defined by the batch_size defined when creating the ++ * document_stream object. ++ * ++ * The function returns simdjson::EMPTY if there is no more data to be parsed. ++ * ++ * The function returns simdjson::SUCCESS (as integer = 0) in case of success ++ * and indicates that the buffer has successfully been parsed to the end. ++ * Every document it contained has been parsed without error. ++ * ++ * The function returns an error code from simdjson/simdjson.h in case of failure ++ * such as simdjson::CAPACITY, simdjson::MEMALLOC, simdjson::DEPTH_ERROR and so forth; ++ * the simdjson::error_message function converts these error codes into a string). ++ * ++ * You can also check validity by calling parser.is_valid(). The same parser can ++ * and should be reused for the other documents in the buffer. ++ */ ++ inline void next() noexcept; ++ ++ /** ++ * Pass the next batch through stage 1 and return when finished. ++ * When threads are enabled, this may wait for the stage 1 thread to finish. ++ */ ++ inline void load_batch() noexcept; ++ ++ /** Get the next document index. */ ++ inline size_t next_batch_start() const noexcept; ++ ++ /** Pass the next batch through stage 1 with the given parser. */ ++ inline error_code run_stage1(dom::parser &p, size_t batch_start) noexcept; ++ ++ dom::parser *parser; ++ const uint8_t *buf; ++ size_t len; ++ size_t batch_size; ++ /** The error (or lack thereof) from the current document. */ ++ error_code error; ++ size_t batch_start{0}; ++ size_t doc_index{}; ++#ifdef SIMDJSON_THREADS_ENABLED ++ /** Indicates whether we use threads. Note that this needs to be a constant during the execution of the parsing. */ ++ bool use_thread; ++ ++ inline void load_from_stage1_thread() noexcept; ++ ++ /** Start a thread to run stage 1 on the next batch. */ ++ inline void start_stage1_thread() noexcept; ++ ++ /** Wait for the stage 1 thread to finish and capture the results. */ ++ inline void finish_stage1_thread() noexcept; ++ ++ /** The error returned from the stage 1 thread. */ ++ error_code stage1_thread_error{UNINITIALIZED}; ++ /** The thread used to run stage 1 against the next batch in the background. */ ++ friend struct stage1_worker; ++ std::unique_ptr worker{new(std::nothrow) stage1_worker()}; ++ /** ++ * The parser used to run stage 1 in the background. Will be swapped ++ * with the regular parser when finished. ++ */ ++ dom::parser stage1_thread_parser{}; ++#endif // SIMDJSON_THREADS_ENABLED ++ ++ friend class dom::parser; ++ friend struct simdjson_result; ++ friend struct internal::simdjson_result_base; ++ ++}; // class document_stream ++ ++} // namespace dom ++ ++template<> ++struct simdjson_result : public internal::simdjson_result_base { ++public: ++ simdjson_inline simdjson_result() noexcept; ///< @private ++ simdjson_inline simdjson_result(error_code error) noexcept; ///< @private ++ simdjson_inline simdjson_result(dom::document_stream &&value) noexcept; ///< @private ++ ++#if SIMDJSON_EXCEPTIONS ++ simdjson_inline dom::document_stream::iterator begin() noexcept(false); ++ simdjson_inline dom::document_stream::iterator end() noexcept(false); ++#else // SIMDJSON_EXCEPTIONS ++#ifndef SIMDJSON_DISABLE_DEPRECATED_API ++ [[deprecated("parse_many() and load_many() may return errors. Use document_stream stream; error = parser.parse_many().get(doc); instead.")]] ++ simdjson_inline dom::document_stream::iterator begin() noexcept; ++ [[deprecated("parse_many() and load_many() may return errors. Use document_stream stream; error = parser.parse_many().get(doc); instead.")]] ++ simdjson_inline dom::document_stream::iterator end() noexcept; ++#endif // SIMDJSON_DISABLE_DEPRECATED_API ++#endif // SIMDJSON_EXCEPTIONS ++}; // struct simdjson_result ++ ++} // namespace simdjson ++ ++#endif // SIMDJSON_DOCUMENT_STREAM_H ++/* end file include/simdjson/dom/document_stream.h */ ++/* begin file include/simdjson/dom/element.h */ ++#ifndef SIMDJSON_DOM_ELEMENT_H ++#define SIMDJSON_DOM_ELEMENT_H ++ ++#include ++ ++namespace simdjson { ++namespace internal { ++template ++class string_builder; ++} ++namespace dom { ++class array; ++class document; ++class object; ++ ++/** ++ * The actual concrete type of a JSON element ++ * This is the type it is most easily cast to with get<>. ++ */ ++enum class element_type { ++ ARRAY = '[', ///< dom::array ++ OBJECT = '{', ///< dom::object ++ INT64 = 'l', ///< int64_t ++ UINT64 = 'u', ///< uint64_t: any integer that fits in uint64_t but *not* int64_t ++ DOUBLE = 'd', ///< double: Any number with a "." or "e" that fits in double. ++ STRING = '"', ///< std::string_view ++ BOOL = 't', ///< bool ++ NULL_VALUE = 'n' ///< null ++}; ++ ++/** ++ * A JSON element. ++ * ++ * References an element in a JSON document, representing a JSON null, boolean, string, number, ++ * array or object. ++ */ ++class element { ++public: ++ /** Create a new, invalid element. */ ++ simdjson_inline element() noexcept; ++ ++ /** The type of this element. */ ++ simdjson_inline element_type type() const noexcept; ++ ++ /** ++ * Cast this element to an array. ++ * ++ * @returns An object that can be used to iterate the array, or: ++ * INCORRECT_TYPE if the JSON element is not an array. ++ */ ++ inline simdjson_result get_array() const noexcept; ++ /** ++ * Cast this element to an object. ++ * ++ * @returns An object that can be used to look up or iterate the object's fields, or: ++ * INCORRECT_TYPE if the JSON element is not an object. ++ */ ++ inline simdjson_result get_object() const noexcept; ++ /** ++ * Cast this element to a null-terminated C string. ++ * ++ * The string is guaranteed to be valid UTF-8. ++ * ++ * The length of the string is given by get_string_length(). Because JSON strings ++ * may contain null characters, it may be incorrect to use strlen to determine the ++ * string length. ++ * ++ * It is possible to get a single string_view instance which represents both the string ++ * content and its length: see get_string(). ++ * ++ * @returns A pointer to a null-terminated UTF-8 string. This string is stored in the parser and will ++ * be invalidated the next time it parses a document or when it is destroyed. ++ * Returns INCORRECT_TYPE if the JSON element is not a string. ++ */ ++ inline simdjson_result get_c_str() const noexcept; ++ /** ++ * Gives the length in bytes of the string. ++ * ++ * It is possible to get a single string_view instance which represents both the string ++ * content and its length: see get_string(). ++ * ++ * @returns A string length in bytes. ++ * Returns INCORRECT_TYPE if the JSON element is not a string. ++ */ ++ inline simdjson_result get_string_length() const noexcept; ++ /** ++ * Cast this element to a string. ++ * ++ * The string is guaranteed to be valid UTF-8. ++ * ++ * @returns An UTF-8 string. The string is stored in the parser and will be invalidated the next time it ++ * parses a document or when it is destroyed. ++ * Returns INCORRECT_TYPE if the JSON element is not a string. ++ */ ++ inline simdjson_result get_string() const noexcept; ++ /** ++ * Cast this element to a signed integer. ++ * ++ * @returns A signed 64-bit integer. ++ * Returns INCORRECT_TYPE if the JSON element is not an integer, or NUMBER_OUT_OF_RANGE ++ * if it is negative. ++ */ ++ inline simdjson_result get_int64() const noexcept; ++ /** ++ * Cast this element to an unsigned integer. ++ * ++ * @returns An unsigned 64-bit integer. ++ * Returns INCORRECT_TYPE if the JSON element is not an integer, or NUMBER_OUT_OF_RANGE ++ * if it is too large. ++ */ ++ inline simdjson_result get_uint64() const noexcept; ++ /** ++ * Cast this element to a double floating-point. ++ * ++ * @returns A double value. ++ * Returns INCORRECT_TYPE if the JSON element is not a number. ++ */ ++ inline simdjson_result get_double() const noexcept; ++ /** ++ * Cast this element to a bool. ++ * ++ * @returns A bool value. ++ * Returns INCORRECT_TYPE if the JSON element is not a boolean. ++ */ ++ inline simdjson_result get_bool() const noexcept; ++ ++ /** ++ * Whether this element is a json array. ++ * ++ * Equivalent to is(). ++ */ ++ inline bool is_array() const noexcept; ++ /** ++ * Whether this element is a json object. ++ * ++ * Equivalent to is(). ++ */ ++ inline bool is_object() const noexcept; ++ /** ++ * Whether this element is a json string. ++ * ++ * Equivalent to is() or is(). ++ */ ++ inline bool is_string() const noexcept; ++ /** ++ * Whether this element is a json number that fits in a signed 64-bit integer. ++ * ++ * Equivalent to is(). ++ */ ++ inline bool is_int64() const noexcept; ++ /** ++ * Whether this element is a json number that fits in an unsigned 64-bit integer. ++ * ++ * Equivalent to is(). ++ */ ++ inline bool is_uint64() const noexcept; ++ /** ++ * Whether this element is a json number that fits in a double. ++ * ++ * Equivalent to is(). ++ */ ++ inline bool is_double() const noexcept; ++ ++ /** ++ * Whether this element is a json number. ++ * ++ * Both integers and floating points will return true. ++ */ ++ inline bool is_number() const noexcept; ++ ++ /** ++ * Whether this element is a json `true` or `false`. ++ * ++ * Equivalent to is(). ++ */ ++ inline bool is_bool() const noexcept; ++ /** ++ * Whether this element is a json `null`. ++ */ ++ inline bool is_null() const noexcept; ++ ++ /** ++ * Tell whether the value can be cast to provided type (T). ++ * ++ * Supported types: ++ * - Boolean: bool ++ * - Number: double, uint64_t, int64_t ++ * - String: std::string_view, const char * ++ * - Array: dom::array ++ * - Object: dom::object ++ * ++ * @tparam T bool, double, uint64_t, int64_t, std::string_view, const char *, dom::array, dom::object ++ */ ++ template ++ simdjson_inline bool is() const noexcept; ++ ++ /** ++ * Get the value as the provided type (T). ++ * ++ * Supported types: ++ * - Boolean: bool ++ * - Number: double, uint64_t, int64_t ++ * - String: std::string_view, const char * ++ * - Array: dom::array ++ * - Object: dom::object ++ * ++ * You may use get_double(), get_bool(), get_uint64(), get_int64(), ++ * get_object(), get_array() or get_string() instead. ++ * ++ * @tparam T bool, double, uint64_t, int64_t, std::string_view, const char *, dom::array, dom::object ++ * ++ * @returns The value cast to the given type, or: ++ * INCORRECT_TYPE if the value cannot be cast to the given type. ++ */ ++ ++ template ++ inline simdjson_result get() const noexcept { ++ // Unless the simdjson library provides an inline implementation, calling this method should ++ // immediately fail. ++ static_assert(!sizeof(T), "The get method with given type is not implemented by the simdjson library."); ++ } ++ ++ /** ++ * Get the value as the provided type (T). ++ * ++ * Supported types: ++ * - Boolean: bool ++ * - Number: double, uint64_t, int64_t ++ * - String: std::string_view, const char * ++ * - Array: dom::array ++ * - Object: dom::object ++ * ++ * @tparam T bool, double, uint64_t, int64_t, std::string_view, const char *, dom::array, dom::object ++ * ++ * @param value The variable to set to the value. May not be set if there is an error. ++ * ++ * @returns The error that occurred, or SUCCESS if there was no error. ++ */ ++ template ++ simdjson_warn_unused simdjson_inline error_code get(T &value) const noexcept; ++ ++ /** ++ * Get the value as the provided type (T), setting error if it's not the given type. ++ * ++ * Supported types: ++ * - Boolean: bool ++ * - Number: double, uint64_t, int64_t ++ * - String: std::string_view, const char * ++ * - Array: dom::array ++ * - Object: dom::object ++ * ++ * @tparam T bool, double, uint64_t, int64_t, std::string_view, const char *, dom::array, dom::object ++ * ++ * @param value The variable to set to the given type. value is undefined if there is an error. ++ * @param error The variable to store the error. error is set to error_code::SUCCEED if there is an error. ++ */ ++ template ++ inline void tie(T &value, error_code &error) && noexcept; ++ ++#if SIMDJSON_EXCEPTIONS ++ /** ++ * Read this element as a boolean. ++ * ++ * @return The boolean value ++ * @exception simdjson_error(INCORRECT_TYPE) if the JSON element is not a boolean. ++ */ ++ inline operator bool() const noexcept(false); ++ ++ /** ++ * Read this element as a null-terminated UTF-8 string. ++ * ++ * Be mindful that JSON allows strings to contain null characters. ++ * ++ * Does *not* convert other types to a string; requires that the JSON type of the element was ++ * an actual string. ++ * ++ * @return The string value. ++ * @exception simdjson_error(INCORRECT_TYPE) if the JSON element is not a string. ++ */ ++ inline explicit operator const char*() const noexcept(false); ++ ++ /** ++ * Read this element as a null-terminated UTF-8 string. ++ * ++ * Does *not* convert other types to a string; requires that the JSON type of the element was ++ * an actual string. ++ * ++ * @return The string value. ++ * @exception simdjson_error(INCORRECT_TYPE) if the JSON element is not a string. ++ */ ++ inline operator std::string_view() const noexcept(false); ++ ++ /** ++ * Read this element as an unsigned integer. ++ * ++ * @return The integer value. ++ * @exception simdjson_error(INCORRECT_TYPE) if the JSON element is not an integer ++ * @exception simdjson_error(NUMBER_OUT_OF_RANGE) if the integer doesn't fit in 64 bits or is negative ++ */ ++ inline operator uint64_t() const noexcept(false); ++ /** ++ * Read this element as an signed integer. ++ * ++ * @return The integer value. ++ * @exception simdjson_error(INCORRECT_TYPE) if the JSON element is not an integer ++ * @exception simdjson_error(NUMBER_OUT_OF_RANGE) if the integer doesn't fit in 64 bits ++ */ ++ inline operator int64_t() const noexcept(false); ++ /** ++ * Read this element as an double. ++ * ++ * @return The double value. ++ * @exception simdjson_error(INCORRECT_TYPE) if the JSON element is not a number ++ * @exception simdjson_error(NUMBER_OUT_OF_RANGE) if the integer doesn't fit in 64 bits or is negative ++ */ ++ inline operator double() const noexcept(false); ++ /** ++ * Read this element as a JSON array. ++ * ++ * @return The JSON array. ++ * @exception simdjson_error(INCORRECT_TYPE) if the JSON element is not an array ++ */ ++ inline operator array() const noexcept(false); ++ /** ++ * Read this element as a JSON object (key/value pairs). ++ * ++ * @return The JSON object. ++ * @exception simdjson_error(INCORRECT_TYPE) if the JSON element is not an object ++ */ ++ inline operator object() const noexcept(false); ++ ++ /** ++ * Iterate over each element in this array. ++ * ++ * @return The beginning of the iteration. ++ * @exception simdjson_error(INCORRECT_TYPE) if the JSON element is not an array ++ */ ++ inline dom::array::iterator begin() const noexcept(false); ++ ++ /** ++ * Iterate over each element in this array. ++ * ++ * @return The end of the iteration. ++ * @exception simdjson_error(INCORRECT_TYPE) if the JSON element is not an array ++ */ ++ inline dom::array::iterator end() const noexcept(false); ++#endif // SIMDJSON_EXCEPTIONS ++ ++ /** ++ * Get the value associated with the given key. ++ * ++ * The key will be matched against **unescaped** JSON: ++ * ++ * dom::parser parser; ++ * int64_t(parser.parse(R"({ "a\n": 1 })"_padded)["a\n"]) == 1 ++ * parser.parse(R"({ "a\n": 1 })"_padded)["a\\n"].get_uint64().error() == NO_SUCH_FIELD ++ * ++ * @return The value associated with this field, or: ++ * - NO_SUCH_FIELD if the field does not exist in the object ++ * - INCORRECT_TYPE if this is not an object ++ */ ++ inline simdjson_result operator[](std::string_view key) const noexcept; ++ ++ /** ++ * Get the value associated with the given key. ++ * ++ * The key will be matched against **unescaped** JSON: ++ * ++ * dom::parser parser; ++ * int64_t(parser.parse(R"({ "a\n": 1 })"_padded)["a\n"]) == 1 ++ * parser.parse(R"({ "a\n": 1 })"_padded)["a\\n"].get_uint64().error() == NO_SUCH_FIELD ++ * ++ * @return The value associated with this field, or: ++ * - NO_SUCH_FIELD if the field does not exist in the object ++ * - INCORRECT_TYPE if this is not an object ++ */ ++ inline simdjson_result operator[](const char *key) const noexcept; ++ ++ /** ++ * Get the value associated with the given JSON pointer. We use the RFC 6901 ++ * https://tools.ietf.org/html/rfc6901 standard. ++ * ++ * dom::parser parser; ++ * element doc = parser.parse(R"({ "foo": { "a": [ 10, 20, 30 ] }})"_padded); ++ * doc.at_pointer("/foo/a/1") == 20 ++ * doc.at_pointer("/foo")["a"].at(1) == 20 ++ * doc.at_pointer("")["foo"]["a"].at(1) == 20 ++ * ++ * It is allowed for a key to be the empty string: ++ * ++ * dom::parser parser; ++ * object obj = parser.parse(R"({ "": { "a": [ 10, 20, 30 ] }})"_padded); ++ * obj.at_pointer("//a/1") == 20 ++ * ++ * @return The value associated with the given JSON pointer, or: ++ * - NO_SUCH_FIELD if a field does not exist in an object ++ * - INDEX_OUT_OF_BOUNDS if an array index is larger than an array length ++ * - INCORRECT_TYPE if a non-integer is used to access an array ++ * - INVALID_JSON_POINTER if the JSON pointer is invalid and cannot be parsed ++ */ ++ inline simdjson_result at_pointer(const std::string_view json_pointer) const noexcept; ++ ++#ifndef SIMDJSON_DISABLE_DEPRECATED_API ++ /** ++ * ++ * Version 0.4 of simdjson used an incorrect interpretation of the JSON Pointer standard ++ * and allowed the following : ++ * ++ * dom::parser parser; ++ * element doc = parser.parse(R"({ "foo": { "a": [ 10, 20, 30 ] }})"_padded); ++ * doc.at("foo/a/1") == 20 ++ * ++ * Though it is intuitive, it is not compliant with RFC 6901 ++ * https://tools.ietf.org/html/rfc6901 ++ * ++ * For standard compliance, use the at_pointer function instead. ++ * ++ * @return The value associated with the given JSON pointer, or: ++ * - NO_SUCH_FIELD if a field does not exist in an object ++ * - INDEX_OUT_OF_BOUNDS if an array index is larger than an array length ++ * - INCORRECT_TYPE if a non-integer is used to access an array ++ * - INVALID_JSON_POINTER if the JSON pointer is invalid and cannot be parsed ++ */ ++ [[deprecated("For standard compliance, use at_pointer instead, and prefix your pointers with a slash '/', see RFC6901 ")]] ++ inline simdjson_result at(const std::string_view json_pointer) const noexcept; ++#endif // SIMDJSON_DISABLE_DEPRECATED_API ++ ++ /** ++ * Get the value at the given index. ++ * ++ * @return The value at the given index, or: ++ * - INDEX_OUT_OF_BOUNDS if the array index is larger than an array length ++ */ ++ inline simdjson_result at(size_t index) const noexcept; ++ ++ /** ++ * Get the value associated with the given key. ++ * ++ * The key will be matched against **unescaped** JSON: ++ * ++ * dom::parser parser; ++ * int64_t(parser.parse(R"({ "a\n": 1 })"_padded)["a\n"]) == 1 ++ * parser.parse(R"({ "a\n": 1 })"_padded)["a\\n"].get_uint64().error() == NO_SUCH_FIELD ++ * ++ * @return The value associated with this field, or: ++ * - NO_SUCH_FIELD if the field does not exist in the object ++ */ ++ inline simdjson_result at_key(std::string_view key) const noexcept; ++ ++ /** ++ * Get the value associated with the given key in a case-insensitive manner. ++ * ++ * Note: The key will be matched against **unescaped** JSON. ++ * ++ * @return The value associated with this field, or: ++ * - NO_SUCH_FIELD if the field does not exist in the object ++ */ ++ inline simdjson_result at_key_case_insensitive(std::string_view key) const noexcept; ++ ++ /** @private for debugging. Prints out the root element. */ ++ inline bool dump_raw_tape(std::ostream &out) const noexcept; ++ ++private: ++ simdjson_inline element(const internal::tape_ref &tape) noexcept; ++ internal::tape_ref tape; ++ friend class document; ++ friend class object; ++ friend class array; ++ friend struct simdjson_result; ++ template ++ friend class simdjson::internal::string_builder; ++ ++}; ++ ++} // namespace dom ++ ++/** The result of a JSON navigation that may fail. */ ++template<> ++struct simdjson_result : public internal::simdjson_result_base { ++public: ++ simdjson_inline simdjson_result() noexcept; ///< @private ++ simdjson_inline simdjson_result(dom::element &&value) noexcept; ///< @private ++ simdjson_inline simdjson_result(error_code error) noexcept; ///< @private ++ ++ simdjson_inline simdjson_result type() const noexcept; ++ template ++ simdjson_inline bool is() const noexcept; ++ template ++ simdjson_inline simdjson_result get() const noexcept; ++ template ++ simdjson_warn_unused simdjson_inline error_code get(T &value) const noexcept; ++ ++ simdjson_inline simdjson_result get_array() const noexcept; ++ simdjson_inline simdjson_result get_object() const noexcept; ++ simdjson_inline simdjson_result get_c_str() const noexcept; ++ simdjson_inline simdjson_result get_string_length() const noexcept; ++ simdjson_inline simdjson_result get_string() const noexcept; ++ simdjson_inline simdjson_result get_int64() const noexcept; ++ simdjson_inline simdjson_result get_uint64() const noexcept; ++ simdjson_inline simdjson_result get_double() const noexcept; ++ simdjson_inline simdjson_result get_bool() const noexcept; ++ ++ simdjson_inline bool is_array() const noexcept; ++ simdjson_inline bool is_object() const noexcept; ++ simdjson_inline bool is_string() const noexcept; ++ simdjson_inline bool is_int64() const noexcept; ++ simdjson_inline bool is_uint64() const noexcept; ++ simdjson_inline bool is_double() const noexcept; ++ simdjson_inline bool is_number() const noexcept; ++ simdjson_inline bool is_bool() const noexcept; ++ simdjson_inline bool is_null() const noexcept; ++ ++ simdjson_inline simdjson_result operator[](std::string_view key) const noexcept; ++ simdjson_inline simdjson_result operator[](const char *key) const noexcept; ++ simdjson_inline simdjson_result at_pointer(const std::string_view json_pointer) const noexcept; ++ [[deprecated("For standard compliance, use at_pointer instead, and prefix your pointers with a slash '/', see RFC6901 ")]] ++ simdjson_inline simdjson_result at(const std::string_view json_pointer) const noexcept; ++ simdjson_inline simdjson_result at(size_t index) const noexcept; ++ simdjson_inline simdjson_result at_key(std::string_view key) const noexcept; ++ simdjson_inline simdjson_result at_key_case_insensitive(std::string_view key) const noexcept; ++ ++#if SIMDJSON_EXCEPTIONS ++ simdjson_inline operator bool() const noexcept(false); ++ simdjson_inline explicit operator const char*() const noexcept(false); ++ simdjson_inline operator std::string_view() const noexcept(false); ++ simdjson_inline operator uint64_t() const noexcept(false); ++ simdjson_inline operator int64_t() const noexcept(false); ++ simdjson_inline operator double() const noexcept(false); ++ simdjson_inline operator dom::array() const noexcept(false); ++ simdjson_inline operator dom::object() const noexcept(false); ++ ++ simdjson_inline dom::array::iterator begin() const noexcept(false); ++ simdjson_inline dom::array::iterator end() const noexcept(false); ++#endif // SIMDJSON_EXCEPTIONS ++}; ++ ++ ++} // namespace simdjson ++ ++#endif // SIMDJSON_DOM_DOCUMENT_H ++/* end file include/simdjson/dom/element.h */ ++/* begin file include/simdjson/dom/object.h */ ++#ifndef SIMDJSON_DOM_OBJECT_H ++#define SIMDJSON_DOM_OBJECT_H ++ ++ ++namespace simdjson { ++namespace internal { ++template ++class string_builder; ++} ++namespace dom { ++ ++class document; ++class element; ++class key_value_pair; ++ ++/** ++ * JSON object. ++ */ ++class object { ++public: ++ /** Create a new, invalid object */ ++ simdjson_inline object() noexcept; ++ ++ class iterator { ++ public: ++ using value_type = key_value_pair; ++ using difference_type = std::ptrdiff_t; ++ ++ /** ++ * Get the actual key/value pair ++ */ ++ inline const value_type operator*() const noexcept; ++ /** ++ * Get the next key/value pair. ++ * ++ * Part of the std::iterator interface. ++ * ++ */ ++ inline iterator& operator++() noexcept; ++ /** ++ * Get the next key/value pair. ++ * ++ * Part of the std::iterator interface. ++ * ++ */ ++ inline iterator operator++(int) noexcept; ++ /** ++ * Check if these values come from the same place in the JSON. ++ * ++ * Part of the std::iterator interface. ++ */ ++ inline bool operator!=(const iterator& other) const noexcept; ++ inline bool operator==(const iterator& other) const noexcept; ++ ++ inline bool operator<(const iterator& other) const noexcept; ++ inline bool operator<=(const iterator& other) const noexcept; ++ inline bool operator>=(const iterator& other) const noexcept; ++ inline bool operator>(const iterator& other) const noexcept; ++ /** ++ * Get the key of this key/value pair. ++ */ ++ inline std::string_view key() const noexcept; ++ /** ++ * Get the length (in bytes) of the key in this key/value pair. ++ * You should expect this function to be faster than key().size(). ++ */ ++ inline uint32_t key_length() const noexcept; ++ /** ++ * Returns true if the key in this key/value pair is equal ++ * to the provided string_view. ++ */ ++ inline bool key_equals(std::string_view o) const noexcept; ++ /** ++ * Returns true if the key in this key/value pair is equal ++ * to the provided string_view in a case-insensitive manner. ++ * Case comparisons may only be handled correctly for ASCII strings. ++ */ ++ inline bool key_equals_case_insensitive(std::string_view o) const noexcept; ++ /** ++ * Get the key of this key/value pair. ++ */ ++ inline const char *key_c_str() const noexcept; ++ /** ++ * Get the value of this key/value pair. ++ */ ++ inline element value() const noexcept; ++ ++ iterator() noexcept = default; ++ iterator(const iterator&) noexcept = default; ++ iterator& operator=(const iterator&) noexcept = default; ++ private: ++ simdjson_inline iterator(const internal::tape_ref &tape) noexcept; ++ ++ internal::tape_ref tape; ++ ++ friend class object; ++ }; ++ ++ /** ++ * Return the first key/value pair. ++ * ++ * Part of the std::iterable interface. ++ */ ++ inline iterator begin() const noexcept; ++ /** ++ * One past the last key/value pair. ++ * ++ * Part of the std::iterable interface. ++ */ ++ inline iterator end() const noexcept; ++ /** ++ * Get the size of the object (number of keys). ++ * It is a saturated value with a maximum of 0xFFFFFF: if the value ++ * is 0xFFFFFF then the size is 0xFFFFFF or greater. ++ */ ++ inline size_t size() const noexcept; ++ /** ++ * Get the value associated with the given key. ++ * ++ * The key will be matched against **unescaped** JSON: ++ * ++ * dom::parser parser; ++ * int64_t(parser.parse(R"({ "a\n": 1 })"_padded)["a\n"]) == 1 ++ * parser.parse(R"({ "a\n": 1 })"_padded)["a\\n"].get_uint64().error() == NO_SUCH_FIELD ++ * ++ * This function has linear-time complexity: the keys are checked one by one. ++ * ++ * @return The value associated with this field, or: ++ * - NO_SUCH_FIELD if the field does not exist in the object ++ * - INCORRECT_TYPE if this is not an object ++ */ ++ inline simdjson_result operator[](std::string_view key) const noexcept; ++ ++ /** ++ * Get the value associated with the given key. ++ * ++ * The key will be matched against **unescaped** JSON: ++ * ++ * dom::parser parser; ++ * int64_t(parser.parse(R"({ "a\n": 1 })"_padded)["a\n"]) == 1 ++ * parser.parse(R"({ "a\n": 1 })"_padded)["a\\n"].get_uint64().error() == NO_SUCH_FIELD ++ * ++ * This function has linear-time complexity: the keys are checked one by one. ++ * ++ * @return The value associated with this field, or: ++ * - NO_SUCH_FIELD if the field does not exist in the object ++ * - INCORRECT_TYPE if this is not an object ++ */ ++ inline simdjson_result operator[](const char *key) const noexcept; ++ ++ /** ++ * Get the value associated with the given JSON pointer. We use the RFC 6901 ++ * https://tools.ietf.org/html/rfc6901 standard, interpreting the current node ++ * as the root of its own JSON document. ++ * ++ * dom::parser parser; ++ * object obj = parser.parse(R"({ "foo": { "a": [ 10, 20, 30 ] }})"_padded); ++ * obj.at_pointer("/foo/a/1") == 20 ++ * obj.at_pointer("/foo")["a"].at(1) == 20 ++ * ++ * It is allowed for a key to be the empty string: ++ * ++ * dom::parser parser; ++ * object obj = parser.parse(R"({ "": { "a": [ 10, 20, 30 ] }})"_padded); ++ * obj.at_pointer("//a/1") == 20 ++ * obj.at_pointer("/")["a"].at(1) == 20 ++ * ++ * @return The value associated with the given JSON pointer, or: ++ * - NO_SUCH_FIELD if a field does not exist in an object ++ * - INDEX_OUT_OF_BOUNDS if an array index is larger than an array length ++ * - INCORRECT_TYPE if a non-integer is used to access an array ++ * - INVALID_JSON_POINTER if the JSON pointer is invalid and cannot be parsed ++ */ ++ inline simdjson_result at_pointer(std::string_view json_pointer) const noexcept; ++ ++ /** ++ * Get the value associated with the given key. ++ * ++ * The key will be matched against **unescaped** JSON: ++ * ++ * dom::parser parser; ++ * int64_t(parser.parse(R"({ "a\n": 1 })"_padded)["a\n"]) == 1 ++ * parser.parse(R"({ "a\n": 1 })"_padded)["a\\n"].get_uint64().error() == NO_SUCH_FIELD ++ * ++ * This function has linear-time complexity: the keys are checked one by one. ++ * ++ * @return The value associated with this field, or: ++ * - NO_SUCH_FIELD if the field does not exist in the object ++ */ ++ inline simdjson_result at_key(std::string_view key) const noexcept; ++ ++ /** ++ * Get the value associated with the given key in a case-insensitive manner. ++ * It is only guaranteed to work over ASCII inputs. ++ * ++ * Note: The key will be matched against **unescaped** JSON. ++ * ++ * This function has linear-time complexity: the keys are checked one by one. ++ * ++ * @return The value associated with this field, or: ++ * - NO_SUCH_FIELD if the field does not exist in the object ++ */ ++ inline simdjson_result at_key_case_insensitive(std::string_view key) const noexcept; ++ ++private: ++ simdjson_inline object(const internal::tape_ref &tape) noexcept; ++ ++ internal::tape_ref tape; ++ ++ friend class element; ++ friend struct simdjson_result; ++ template ++ friend class simdjson::internal::string_builder; ++}; ++ ++/** ++ * Key/value pair in an object. ++ */ ++class key_value_pair { ++public: ++ /** key in the key-value pair **/ ++ std::string_view key; ++ /** value in the key-value pair **/ ++ element value; ++ ++private: ++ simdjson_inline key_value_pair(std::string_view _key, element _value) noexcept; ++ friend class object; ++}; ++ ++} // namespace dom ++ ++/** The result of a JSON conversion that may fail. */ ++template<> ++struct simdjson_result : public internal::simdjson_result_base { ++public: ++ simdjson_inline simdjson_result() noexcept; ///< @private ++ simdjson_inline simdjson_result(dom::object value) noexcept; ///< @private ++ simdjson_inline simdjson_result(error_code error) noexcept; ///< @private ++ ++ inline simdjson_result operator[](std::string_view key) const noexcept; ++ inline simdjson_result operator[](const char *key) const noexcept; ++ inline simdjson_result at_pointer(std::string_view json_pointer) const noexcept; ++ inline simdjson_result at_key(std::string_view key) const noexcept; ++ inline simdjson_result at_key_case_insensitive(std::string_view key) const noexcept; ++ ++#if SIMDJSON_EXCEPTIONS ++ inline dom::object::iterator begin() const noexcept(false); ++ inline dom::object::iterator end() const noexcept(false); ++ inline size_t size() const noexcept(false); ++#endif // SIMDJSON_EXCEPTIONS ++}; ++ ++} // namespace simdjson ++ ++#if defined(__cpp_lib_ranges) ++#include ++ ++namespace std { ++namespace ranges { ++template<> ++inline constexpr bool enable_view = true; ++#if SIMDJSON_EXCEPTIONS ++template<> ++inline constexpr bool enable_view> = true; ++#endif // SIMDJSON_EXCEPTIONS ++} // namespace ranges ++} // namespace std ++#endif // defined(__cpp_lib_ranges) ++ ++#endif // SIMDJSON_DOM_OBJECT_H ++/* end file include/simdjson/dom/object.h */ ++/* begin file include/simdjson/dom/serialization.h */ ++#ifndef SIMDJSON_SERIALIZATION_H ++#define SIMDJSON_SERIALIZATION_H ++ ++#include ++ ++namespace simdjson { ++ ++/** ++ * The string_builder template and mini_formatter class ++ * are not part of our public API and are subject to change ++ * at any time! ++ */ ++namespace internal { ++ ++class mini_formatter; ++ ++/** ++ * @private The string_builder template allows us to construct ++ * a string from a document element. It is parametrized ++ * by a "formatter" which handles the details. Thus ++ * the string_builder template could support both minification ++ * and prettification, and various other tradeoffs. ++ */ ++template ++class string_builder { ++public: ++ /** Construct an initially empty builder, would print the empty string **/ ++ string_builder() = default; ++ /** Append an element to the builder (to be printed) **/ ++ inline void append(simdjson::dom::element value); ++ /** Append an array to the builder (to be printed) **/ ++ inline void append(simdjson::dom::array value); ++ /** Append an object to the builder (to be printed) **/ ++ inline void append(simdjson::dom::object value); ++ /** Reset the builder (so that it would print the empty string) **/ ++ simdjson_inline void clear(); ++ /** ++ * Get access to the string. The string_view is owned by the builder ++ * and it is invalid to use it after the string_builder has been ++ * destroyed. ++ * However you can make a copy of the string_view on memory that you ++ * own. ++ */ ++ simdjson_inline std::string_view str() const; ++ /** Append a key_value_pair to the builder (to be printed) **/ ++ simdjson_inline void append(simdjson::dom::key_value_pair value); ++private: ++ formatter format{}; ++}; ++ ++/** ++ * @private This is the class that we expect to use with the string_builder ++ * template. It tries to produce a compact version of the JSON element ++ * as quickly as possible. ++ */ ++class mini_formatter { ++public: ++ mini_formatter() = default; ++ /** Add a comma **/ ++ simdjson_inline void comma(); ++ /** Start an array, prints [ **/ ++ simdjson_inline void start_array(); ++ /** End an array, prints ] **/ ++ simdjson_inline void end_array(); ++ /** Start an array, prints { **/ ++ simdjson_inline void start_object(); ++ /** Start an array, prints } **/ ++ simdjson_inline void end_object(); ++ /** Prints a true **/ ++ simdjson_inline void true_atom(); ++ /** Prints a false **/ ++ simdjson_inline void false_atom(); ++ /** Prints a null **/ ++ simdjson_inline void null_atom(); ++ /** Prints a number **/ ++ simdjson_inline void number(int64_t x); ++ /** Prints a number **/ ++ simdjson_inline void number(uint64_t x); ++ /** Prints a number **/ ++ simdjson_inline void number(double x); ++ /** Prints a key (string + colon) **/ ++ simdjson_inline void key(std::string_view unescaped); ++ /** Prints a string. The string is escaped as needed. **/ ++ simdjson_inline void string(std::string_view unescaped); ++ /** Clears out the content. **/ ++ simdjson_inline void clear(); ++ /** ++ * Get access to the buffer, it is owned by the instance, but ++ * the user can make a copy. ++ **/ ++ simdjson_inline std::string_view str() const; ++ ++private: ++ // implementation details (subject to change) ++ /** Prints one character **/ ++ simdjson_inline void one_char(char c); ++ /** Backing buffer **/ ++ std::vector buffer{}; // not ideal! ++}; ++ ++} // internal ++ ++namespace dom { ++ ++/** ++ * Print JSON to an output stream. ++ * ++ * @param out The output stream. ++ * @param value The element. ++ * @throw if there is an error with the underlying output stream. simdjson itself will not throw. ++ */ ++inline std::ostream& operator<<(std::ostream& out, simdjson::dom::element value) { ++ simdjson::internal::string_builder<> sb; ++ sb.append(value); ++ return (out << sb.str()); ++} ++#if SIMDJSON_EXCEPTIONS ++inline std::ostream& operator<<(std::ostream& out, simdjson::simdjson_result x) { ++ if (x.error()) { throw simdjson::simdjson_error(x.error()); } ++ return (out << x.value()); ++} ++#endif ++/** ++ * Print JSON to an output stream. ++ * ++ * @param out The output stream. ++ * @param value The array. ++ * @throw if there is an error with the underlying output stream. simdjson itself will not throw. ++ */ ++inline std::ostream& operator<<(std::ostream& out, simdjson::dom::array value) { ++ simdjson::internal::string_builder<> sb; ++ sb.append(value); ++ return (out << sb.str()); ++} ++#if SIMDJSON_EXCEPTIONS ++inline std::ostream& operator<<(std::ostream& out, simdjson::simdjson_result x) { ++ if (x.error()) { throw simdjson::simdjson_error(x.error()); } ++ return (out << x.value()); ++} ++#endif ++/** ++ * Print JSON to an output stream. ++ * ++ * @param out The output stream. ++ * @param value The object. ++ * @throw if there is an error with the underlying output stream. simdjson itself will not throw. ++ */ ++inline std::ostream& operator<<(std::ostream& out, simdjson::dom::object value) { ++ simdjson::internal::string_builder<> sb; ++ sb.append(value); ++ return (out << sb.str()); ++} ++#if SIMDJSON_EXCEPTIONS ++inline std::ostream& operator<<(std::ostream& out, simdjson::simdjson_result x) { ++ if (x.error()) { throw simdjson::simdjson_error(x.error()); } ++ return (out << x.value()); ++} ++#endif ++} // namespace dom ++ ++/** ++ * Converts JSON to a string. ++ * ++ * dom::parser parser; ++ * element doc = parser.parse(" [ 1 , 2 , 3 ] "_padded); ++ * cout << to_string(doc) << endl; // prints [1,2,3] ++ * ++ */ ++template ++std::string to_string(T x) { ++ // in C++, to_string is standard: http://www.cplusplus.com/reference/string/to_string/ ++ // Currently minify and to_string are identical but in the future, they may ++ // differ. ++ simdjson::internal::string_builder<> sb; ++ sb.append(x); ++ std::string_view answer = sb.str(); ++ return std::string(answer.data(), answer.size()); ++} ++#if SIMDJSON_EXCEPTIONS ++template ++std::string to_string(simdjson_result x) { ++ if (x.error()) { throw simdjson_error(x.error()); } ++ return to_string(x.value()); ++} ++#endif ++ ++/** ++ * Minifies a JSON element or document, printing the smallest possible valid JSON. ++ * ++ * dom::parser parser; ++ * element doc = parser.parse(" [ 1 , 2 , 3 ] "_padded); ++ * cout << minify(doc) << endl; // prints [1,2,3] ++ * ++ */ ++template ++std::string minify(T x) { ++ return to_string(x); ++} ++ ++#if SIMDJSON_EXCEPTIONS ++template ++std::string minify(simdjson_result x) { ++ if (x.error()) { throw simdjson_error(x.error()); } ++ return to_string(x.value()); ++} ++#endif ++ ++ ++} // namespace simdjson ++ ++ ++#endif ++/* end file include/simdjson/dom/serialization.h */ ++ ++// Deprecated API ++/* begin file include/simdjson/dom/jsonparser.h */ ++// TODO Remove this -- deprecated API and files ++ ++#ifndef SIMDJSON_DOM_JSONPARSER_H ++#define SIMDJSON_DOM_JSONPARSER_H ++ ++/* begin file include/simdjson/dom/parsedjson.h */ ++// TODO Remove this -- deprecated API and files ++ ++#ifndef SIMDJSON_DOM_PARSEDJSON_H ++#define SIMDJSON_DOM_PARSEDJSON_H ++ ++ ++namespace simdjson { ++ ++/** ++ * @deprecated Use `dom::parser` instead. ++ */ ++using ParsedJson [[deprecated("Use dom::parser instead")]] = dom::parser; ++ ++} // namespace simdjson ++ ++#endif // SIMDJSON_DOM_PARSEDJSON_H ++/* end file include/simdjson/dom/parsedjson.h */ ++/* begin file include/simdjson/jsonioutil.h */ ++#ifndef SIMDJSON_JSONIOUTIL_H ++#define SIMDJSON_JSONIOUTIL_H ++ ++ ++namespace simdjson { ++ ++#if SIMDJSON_EXCEPTIONS ++#ifndef SIMDJSON_DISABLE_DEPRECATED_API ++[[deprecated("Use padded_string::load() instead")]] ++inline padded_string get_corpus(const char *path) { ++ return padded_string::load(path); ++} ++#endif // SIMDJSON_DISABLE_DEPRECATED_API ++#endif // SIMDJSON_EXCEPTIONS ++ ++} // namespace simdjson ++ ++#endif // SIMDJSON_JSONIOUTIL_H ++/* end file include/simdjson/jsonioutil.h */ ++ ++namespace simdjson { ++ ++// ++// C API (json_parse and build_parsed_json) declarations ++// ++ ++#ifndef SIMDJSON_DISABLE_DEPRECATED_API ++[[deprecated("Use parser.parse() instead")]] ++inline int json_parse(const uint8_t *buf, size_t len, dom::parser &parser, bool realloc_if_needed = true) noexcept { ++ error_code code = parser.parse(buf, len, realloc_if_needed).error(); ++ // The deprecated json_parse API is a signal that the user plans to *use* the error code / valid ++ // bits in the parser instead of heeding the result code. The normal parser unsets those in ++ // anticipation of making the error code ephemeral. ++ // Here we put the code back into the parser, until we've removed this method. ++ parser.valid = code == SUCCESS; ++ parser.error = code; ++ return code; ++} ++[[deprecated("Use parser.parse() instead")]] ++inline int json_parse(const char *buf, size_t len, dom::parser &parser, bool realloc_if_needed = true) noexcept { ++ error_code code = parser.parse(buf, len, realloc_if_needed).error(); ++ // The deprecated json_parse API is a signal that the user plans to *use* the error code / valid ++ // bits in the parser instead of heeding the result code. The normal parser unsets those in ++ // anticipation of making the error code ephemeral. ++ // Here we put the code back into the parser, until we've removed this method. ++ parser.valid = code == SUCCESS; ++ parser.error = code; ++ return code; ++} ++[[deprecated("Use parser.parse() instead")]] ++inline int json_parse(const std::string &s, dom::parser &parser, bool realloc_if_needed = true) noexcept { ++ error_code code = parser.parse(s.data(), s.length(), realloc_if_needed).error(); ++ // The deprecated json_parse API is a signal that the user plans to *use* the error code / valid ++ // bits in the parser instead of heeding the result code. The normal parser unsets those in ++ // anticipation of making the error code ephemeral. ++ // Here we put the code back into the parser, until we've removed this method. ++ parser.valid = code == SUCCESS; ++ parser.error = code; ++ return code; ++} ++[[deprecated("Use parser.parse() instead")]] ++inline int json_parse(const padded_string &s, dom::parser &parser) noexcept { ++ error_code code = parser.parse(s).error(); ++ // The deprecated json_parse API is a signal that the user plans to *use* the error code / valid ++ // bits in the parser instead of heeding the result code. The normal parser unsets those in ++ // anticipation of making the error code ephemeral. ++ // Here we put the code back into the parser, until we've removed this method. ++ parser.valid = code == SUCCESS; ++ parser.error = code; ++ return code; ++} ++ ++[[deprecated("Use parser.parse() instead")]] ++simdjson_warn_unused inline dom::parser build_parsed_json(const uint8_t *buf, size_t len, bool realloc_if_needed = true) noexcept { ++ dom::parser parser; ++ error_code code = parser.parse(buf, len, realloc_if_needed).error(); ++ // The deprecated json_parse API is a signal that the user plans to *use* the error code / valid ++ // bits in the parser instead of heeding the result code. The normal parser unsets those in ++ // anticipation of making the error code ephemeral. ++ // Here we put the code back into the parser, until we've removed this method. ++ parser.valid = code == SUCCESS; ++ parser.error = code; ++ return parser; ++} ++[[deprecated("Use parser.parse() instead")]] ++simdjson_warn_unused inline dom::parser build_parsed_json(const char *buf, size_t len, bool realloc_if_needed = true) noexcept { ++ dom::parser parser; ++ error_code code = parser.parse(buf, len, realloc_if_needed).error(); ++ // The deprecated json_parse API is a signal that the user plans to *use* the error code / valid ++ // bits in the parser instead of heeding the result code. The normal parser unsets those in ++ // anticipation of making the error code ephemeral. ++ // Here we put the code back into the parser, until we've removed this method. ++ parser.valid = code == SUCCESS; ++ parser.error = code; ++ return parser; ++} ++[[deprecated("Use parser.parse() instead")]] ++simdjson_warn_unused inline dom::parser build_parsed_json(const std::string &s, bool realloc_if_needed = true) noexcept { ++ dom::parser parser; ++ error_code code = parser.parse(s.data(), s.length(), realloc_if_needed).error(); ++ // The deprecated json_parse API is a signal that the user plans to *use* the error code / valid ++ // bits in the parser instead of heeding the result code. The normal parser unsets those in ++ // anticipation of making the error code ephemeral. ++ // Here we put the code back into the parser, until we've removed this method. ++ parser.valid = code == SUCCESS; ++ parser.error = code; ++ return parser; ++} ++[[deprecated("Use parser.parse() instead")]] ++simdjson_warn_unused inline dom::parser build_parsed_json(const padded_string &s) noexcept { ++ dom::parser parser; ++ error_code code = parser.parse(s).error(); ++ // The deprecated json_parse API is a signal that the user plans to *use* the error code / valid ++ // bits in the parser instead of heeding the result code. The normal parser unsets those in ++ // anticipation of making the error code ephemeral. ++ // Here we put the code back into the parser, until we've removed this method. ++ parser.valid = code == SUCCESS; ++ parser.error = code; ++ return parser; ++} ++#endif // SIMDJSON_DISABLE_DEPRECATED_API ++ ++/** @private We do not want to allow implicit conversion from C string to std::string. */ ++int json_parse(const char *buf, dom::parser &parser) noexcept = delete; ++/** @private We do not want to allow implicit conversion from C string to std::string. */ ++dom::parser build_parsed_json(const char *buf) noexcept = delete; ++ ++} // namespace simdjson ++ ++#endif // SIMDJSON_DOM_JSONPARSER_H ++/* end file include/simdjson/dom/jsonparser.h */ ++/* begin file include/simdjson/dom/parsedjson_iterator.h */ ++// TODO Remove this -- deprecated API and files ++ ++#ifndef SIMDJSON_DOM_PARSEDJSON_ITERATOR_H ++#define SIMDJSON_DOM_PARSEDJSON_ITERATOR_H ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++/* begin file include/simdjson/internal/jsonformatutils.h */ ++#ifndef SIMDJSON_INTERNAL_JSONFORMATUTILS_H ++#define SIMDJSON_INTERNAL_JSONFORMATUTILS_H ++ ++#include ++#include ++#include ++ ++namespace simdjson { ++namespace internal { ++ ++class escape_json_string; ++ ++inline std::ostream& operator<<(std::ostream& out, const escape_json_string &str); ++ ++class escape_json_string { ++public: ++ escape_json_string(std::string_view _str) noexcept : str{_str} {} ++ operator std::string() const noexcept { std::stringstream s; s << *this; return s.str(); } ++private: ++ std::string_view str; ++ friend std::ostream& operator<<(std::ostream& out, const escape_json_string &unescaped); ++}; ++ ++inline std::ostream& operator<<(std::ostream& out, const escape_json_string &unescaped) { ++ for (size_t i=0; i(unescaped.str[i]) <= 0x1F) { ++ // TODO can this be done once at the beginning, or will it mess up << char? ++ std::ios::fmtflags f(out.flags()); ++ out << "\\u" << std::hex << std::setw(4) << std::setfill('0') << int(unescaped.str[i]); ++ out.flags(f); ++ } else { ++ out << unescaped.str[i]; ++ } ++ } ++ } ++ return out; ++} ++ ++} // namespace internal ++} // namespace simdjson ++ ++#endif // SIMDJSON_INTERNAL_JSONFORMATUTILS_H ++/* end file include/simdjson/internal/jsonformatutils.h */ ++ ++#ifndef SIMDJSON_DISABLE_DEPRECATED_API ++ ++namespace simdjson { ++/** @private **/ ++class [[deprecated("Use the new DOM navigation API instead (see doc/basics.md)")]] dom::parser::Iterator { ++public: ++ inline Iterator(const dom::parser &parser) noexcept(false); ++ inline Iterator(const Iterator &o) noexcept; ++ inline ~Iterator() noexcept; ++ ++ inline Iterator& operator=(const Iterator&) = delete; ++ ++ inline bool is_ok() const; ++ ++ // useful for debugging purposes ++ inline size_t get_tape_location() const; ++ ++ // useful for debugging purposes ++ inline size_t get_tape_length() const; ++ ++ // returns the current depth (start at 1 with 0 reserved for the fictitious ++ // root node) ++ inline size_t get_depth() const; ++ ++ // A scope is a series of nodes at the same depth, typically it is either an ++ // object ({) or an array ([). The root node has type 'r'. ++ inline uint8_t get_scope_type() const; ++ ++ // move forward in document order ++ inline bool move_forward(); ++ ++ // retrieve the character code of what we're looking at: ++ // [{"slutfn are the possibilities ++ inline uint8_t get_type() const { ++ return current_type; // short functions should be inlined! ++ } ++ ++ // get the int64_t value at this node; valid only if get_type is "l" ++ inline int64_t get_integer() const { ++ if (location + 1 >= tape_length) { ++ return 0; // default value in case of error ++ } ++ return static_cast(doc.tape[location + 1]); ++ } ++ ++ // get the value as uint64; valid only if if get_type is "u" ++ inline uint64_t get_unsigned_integer() const { ++ if (location + 1 >= tape_length) { ++ return 0; // default value in case of error ++ } ++ return doc.tape[location + 1]; ++ } ++ ++ // get the string value at this node (NULL ended); valid only if get_type is " ++ // note that tabs, and line endings are escaped in the returned value (see ++ // print_with_escapes) return value is valid UTF-8, it may contain NULL chars ++ // within the string: get_string_length determines the true string length. ++ inline const char *get_string() const { ++ return reinterpret_cast( ++ doc.string_buf.get() + (current_val & internal::JSON_VALUE_MASK) + sizeof(uint32_t)); ++ } ++ ++ // return the length of the string in bytes ++ inline uint32_t get_string_length() const { ++ uint32_t answer; ++ std::memcpy(&answer, ++ reinterpret_cast(doc.string_buf.get() + ++ (current_val & internal::JSON_VALUE_MASK)), ++ sizeof(uint32_t)); ++ return answer; ++ } ++ ++ // get the double value at this node; valid only if ++ // get_type() is "d" ++ inline double get_double() const { ++ if (location + 1 >= tape_length) { ++ return std::numeric_limits::quiet_NaN(); // default value in ++ // case of error ++ } ++ double answer; ++ std::memcpy(&answer, &doc.tape[location + 1], sizeof(answer)); ++ return answer; ++ } ++ ++ inline bool is_object_or_array() const { return is_object() || is_array(); } ++ ++ inline bool is_object() const { return get_type() == '{'; } ++ ++ inline bool is_array() const { return get_type() == '['; } ++ ++ inline bool is_string() const { return get_type() == '"'; } ++ ++ // Returns true if the current type of the node is an signed integer. ++ // You can get its value with `get_integer()`. ++ inline bool is_integer() const { return get_type() == 'l'; } ++ ++ // Returns true if the current type of the node is an unsigned integer. ++ // You can get its value with `get_unsigned_integer()`. ++ // ++ // NOTE: ++ // Only a large value, which is out of range of a 64-bit signed integer, is ++ // represented internally as an unsigned node. On the other hand, a typical ++ // positive integer, such as 1, 42, or 1000000, is as a signed node. ++ // Be aware this function returns false for a signed node. ++ inline bool is_unsigned_integer() const { return get_type() == 'u'; } ++ // Returns true if the current type of the node is a double floating-point number. ++ inline bool is_double() const { return get_type() == 'd'; } ++ // Returns true if the current type of the node is a number (integer or floating-point). ++ inline bool is_number() const { ++ return is_integer() || is_unsigned_integer() || is_double(); ++ } ++ // Returns true if the current type of the node is a bool with true value. ++ inline bool is_true() const { return get_type() == 't'; } ++ // Returns true if the current type of the node is a bool with false value. ++ inline bool is_false() const { return get_type() == 'f'; } ++ // Returns true if the current type of the node is null. ++ inline bool is_null() const { return get_type() == 'n'; } ++ // Returns true if the type byte represents an object of an array ++ static bool is_object_or_array(uint8_t type) { ++ return ((type == '[') || (type == '{')); ++ } ++ ++ // when at {, go one level deep, looking for a given key ++ // if successful, we are left pointing at the value, ++ // if not, we are still pointing at the object ({) ++ // (in case of repeated keys, this only finds the first one). ++ // We seek the key using C's strcmp so if your JSON strings contain ++ // NULL chars, this would trigger a false positive: if you expect that ++ // to be the case, take extra precautions. ++ // Furthermore, we do the comparison character-by-character ++ // without taking into account Unicode equivalence. ++ inline bool move_to_key(const char *key); ++ ++ // as above, but case insensitive lookup (strcmpi instead of strcmp) ++ inline bool move_to_key_insensitive(const char *key); ++ ++ // when at {, go one level deep, looking for a given key ++ // if successful, we are left pointing at the value, ++ // if not, we are still pointing at the object ({) ++ // (in case of repeated keys, this only finds the first one). ++ // The string we search for can contain NULL values. ++ // Furthermore, we do the comparison character-by-character ++ // without taking into account Unicode equivalence. ++ inline bool move_to_key(const char *key, uint32_t length); ++ ++ // when at a key location within an object, this moves to the accompanying ++ // value (located next to it). This is equivalent but much faster than ++ // calling "next()". ++ inline void move_to_value(); ++ ++ // when at [, go one level deep, and advance to the given index. ++ // if successful, we are left pointing at the value, ++ // if not, we are still pointing at the array ([) ++ inline bool move_to_index(uint32_t index); ++ ++ // Moves the iterator to the value corresponding to the json pointer. ++ // Always search from the root of the document. ++ // if successful, we are left pointing at the value, ++ // if not, we are still pointing the same value we were pointing before the ++ // call. The json pointer follows the rfc6901 standard's syntax: ++ // https://tools.ietf.org/html/rfc6901 However, the standard says "If a ++ // referenced member name is not unique in an object, the member that is ++ // referenced is undefined, and evaluation fails". Here we just return the ++ // first corresponding value. The length parameter is the length of the ++ // jsonpointer string ('pointer'). ++ inline bool move_to(const char *pointer, uint32_t length); ++ ++ // Moves the iterator to the value corresponding to the json pointer. ++ // Always search from the root of the document. ++ // if successful, we are left pointing at the value, ++ // if not, we are still pointing the same value we were pointing before the ++ // call. The json pointer implementation follows the rfc6901 standard's ++ // syntax: https://tools.ietf.org/html/rfc6901 However, the standard says ++ // "If a referenced member name is not unique in an object, the member that ++ // is referenced is undefined, and evaluation fails". Here we just return ++ // the first corresponding value. ++ inline bool move_to(const std::string &pointer) { ++ return move_to(pointer.c_str(), uint32_t(pointer.length())); ++ } ++ ++ private: ++ // Almost the same as move_to(), except it searches from the current ++ // position. The pointer's syntax is identical, though that case is not ++ // handled by the rfc6901 standard. The '/' is still required at the ++ // beginning. However, contrary to move_to(), the URI Fragment Identifier ++ // Representation is not supported here. Also, in case of failure, we are ++ // left pointing at the closest value it could reach. For these reasons it ++ // is private. It exists because it is used by move_to(). ++ inline bool relative_move_to(const char *pointer, uint32_t length); ++ ++ public: ++ // throughout return true if we can do the navigation, false ++ // otherwise ++ ++ // Within a given scope (series of nodes at the same depth within either an ++ // array or an object), we move forward. ++ // Thus, given [true, null, {"a":1}, [1,2]], we would visit true, null, { ++ // and [. At the object ({) or at the array ([), you can issue a "down" to ++ // visit their content. valid if we're not at the end of a scope (returns ++ // true). ++ inline bool next(); ++ ++ // Within a given scope (series of nodes at the same depth within either an ++ // array or an object), we move backward. ++ // Thus, given [true, null, {"a":1}, [1,2]], we would visit ], }, null, true ++ // when starting at the end of the scope. At the object ({) or at the array ++ // ([), you can issue a "down" to visit their content. ++ // Performance warning: This function is implemented by starting again ++ // from the beginning of the scope and scanning forward. You should expect ++ // it to be relatively slow. ++ inline bool prev(); ++ ++ // Moves back to either the containing array or object (type { or [) from ++ // within a contained scope. ++ // Valid unless we are at the first level of the document ++ inline bool up(); ++ ++ // Valid if we're at a [ or { and it starts a non-empty scope; moves us to ++ // start of that deeper scope if it not empty. Thus, given [true, null, ++ // {"a":1}, [1,2]], if we are at the { node, we would move to the "a" node. ++ inline bool down(); ++ ++ // move us to the start of our current scope, ++ // a scope is a series of nodes at the same level ++ inline void to_start_scope(); ++ ++ inline void rewind() { ++ while (up()) ++ ; ++ } ++ ++ ++ ++ // print the node we are currently pointing at ++ inline bool print(std::ostream &os, bool escape_strings = true) const; ++ ++ private: ++ const document &doc; ++ size_t max_depth{}; ++ size_t depth{}; ++ size_t location{}; // our current location on a tape ++ size_t tape_length{}; ++ uint8_t current_type{}; ++ uint64_t current_val{}; ++ typedef struct { ++ size_t start_of_scope; ++ uint8_t scope_type; ++ } scopeindex_t; ++ ++ scopeindex_t *depth_index{}; ++}; ++ ++} // namespace simdjson ++#endif // SIMDJSON_DISABLE_DEPRECATED_API ++ ++#endif // SIMDJSON_DOM_PARSEDJSON_ITERATOR_H ++/* end file include/simdjson/dom/parsedjson_iterator.h */ ++ ++// Inline functions ++/* begin file include/simdjson/dom/array-inl.h */ ++#ifndef SIMDJSON_INLINE_ARRAY_H ++#define SIMDJSON_INLINE_ARRAY_H ++ ++// Inline implementations go in here. ++ ++#include ++ ++namespace simdjson { ++ ++// ++// simdjson_result inline implementation ++// ++simdjson_inline simdjson_result::simdjson_result() noexcept ++ : internal::simdjson_result_base() {} ++simdjson_inline simdjson_result::simdjson_result(dom::array value) noexcept ++ : internal::simdjson_result_base(std::forward(value)) {} ++simdjson_inline simdjson_result::simdjson_result(error_code error) noexcept ++ : internal::simdjson_result_base(error) {} ++ ++#if SIMDJSON_EXCEPTIONS ++ ++inline dom::array::iterator simdjson_result::begin() const noexcept(false) { ++ if (error()) { throw simdjson_error(error()); } ++ return first.begin(); ++} ++inline dom::array::iterator simdjson_result::end() const noexcept(false) { ++ if (error()) { throw simdjson_error(error()); } ++ return first.end(); ++} ++inline size_t simdjson_result::size() const noexcept(false) { ++ if (error()) { throw simdjson_error(error()); } ++ return first.size(); ++} ++ ++#endif // SIMDJSON_EXCEPTIONS ++ ++inline simdjson_result simdjson_result::at_pointer(std::string_view json_pointer) const noexcept { ++ if (error()) { return error(); } ++ return first.at_pointer(json_pointer); ++} ++inline simdjson_result simdjson_result::at(size_t index) const noexcept { ++ if (error()) { return error(); } ++ return first.at(index); ++} ++ ++namespace dom { ++ ++// ++// array inline implementation ++// ++simdjson_inline array::array() noexcept : tape{} {} ++simdjson_inline array::array(const internal::tape_ref &_tape) noexcept : tape{_tape} {} ++inline array::iterator array::begin() const noexcept { ++ SIMDJSON_DEVELOPMENT_ASSERT(tape.usable()); // https://github.com/simdjson/simdjson/issues/1914 ++ return internal::tape_ref(tape.doc, tape.json_index + 1); ++} ++inline array::iterator array::end() const noexcept { ++ SIMDJSON_DEVELOPMENT_ASSERT(tape.usable()); // https://github.com/simdjson/simdjson/issues/1914 ++ return internal::tape_ref(tape.doc, tape.after_element() - 1); ++} ++inline size_t array::size() const noexcept { ++ SIMDJSON_DEVELOPMENT_ASSERT(tape.usable()); // https://github.com/simdjson/simdjson/issues/1914 ++ return tape.scope_count(); ++} ++inline size_t array::number_of_slots() const noexcept { ++ SIMDJSON_DEVELOPMENT_ASSERT(tape.usable()); // https://github.com/simdjson/simdjson/issues/1914 ++ return tape.matching_brace_index() - tape.json_index; ++} ++inline simdjson_result array::at_pointer(std::string_view json_pointer) const noexcept { ++ SIMDJSON_DEVELOPMENT_ASSERT(tape.usable()); // https://github.com/simdjson/simdjson/issues/1914 ++ if(json_pointer.empty()) { // an empty string means that we return the current node ++ return element(this->tape); // copy the current node ++ } else if(json_pointer[0] != '/') { // otherwise there is an error ++ return INVALID_JSON_POINTER; ++ } ++ json_pointer = json_pointer.substr(1); ++ // - means "the append position" or "the element after the end of the array" ++ // We don't support this, because we're returning a real element, not a position. ++ if (json_pointer == "-") { return INDEX_OUT_OF_BOUNDS; } ++ ++ // Read the array index ++ size_t array_index = 0; ++ size_t i; ++ for (i = 0; i < json_pointer.length() && json_pointer[i] != '/'; i++) { ++ uint8_t digit = uint8_t(json_pointer[i] - '0'); ++ // Check for non-digit in array index. If it's there, we're trying to get a field in an object ++ if (digit > 9) { return INCORRECT_TYPE; } ++ array_index = array_index*10 + digit; ++ } ++ ++ // 0 followed by other digits is invalid ++ if (i > 1 && json_pointer[0] == '0') { return INVALID_JSON_POINTER; } // "JSON pointer array index has other characters after 0" ++ ++ // Empty string is invalid; so is a "/" with no digits before it ++ if (i == 0) { return INVALID_JSON_POINTER; } // "Empty string in JSON pointer array index" ++ ++ // Get the child ++ auto child = array(tape).at(array_index); ++ // If there is an error, it ends here ++ if(child.error()) { ++ return child; ++ } ++ // If there is a /, we're not done yet, call recursively. ++ if (i < json_pointer.length()) { ++ child = child.at_pointer(json_pointer.substr(i)); ++ } ++ return child; ++} ++ ++inline simdjson_result array::at(size_t index) const noexcept { ++ SIMDJSON_DEVELOPMENT_ASSERT(tape.usable()); // https://github.com/simdjson/simdjson/issues/1914 ++ size_t i=0; ++ for (auto element : *this) { ++ if (i == index) { return element; } ++ i++; ++ } ++ return INDEX_OUT_OF_BOUNDS; ++} ++ ++// ++// array::iterator inline implementation ++// ++simdjson_inline array::iterator::iterator(const internal::tape_ref &_tape) noexcept : tape{_tape} { } ++inline element array::iterator::operator*() const noexcept { ++ return element(tape); ++} ++inline array::iterator& array::iterator::operator++() noexcept { ++ tape.json_index = tape.after_element(); ++ return *this; ++} ++inline array::iterator array::iterator::operator++(int) noexcept { ++ array::iterator out = *this; ++ ++*this; ++ return out; ++} ++inline bool array::iterator::operator!=(const array::iterator& other) const noexcept { ++ return tape.json_index != other.tape.json_index; ++} ++inline bool array::iterator::operator==(const array::iterator& other) const noexcept { ++ return tape.json_index == other.tape.json_index; ++} ++inline bool array::iterator::operator<(const array::iterator& other) const noexcept { ++ return tape.json_index < other.tape.json_index; ++} ++inline bool array::iterator::operator<=(const array::iterator& other) const noexcept { ++ return tape.json_index <= other.tape.json_index; ++} ++inline bool array::iterator::operator>=(const array::iterator& other) const noexcept { ++ return tape.json_index >= other.tape.json_index; ++} ++inline bool array::iterator::operator>(const array::iterator& other) const noexcept { ++ return tape.json_index > other.tape.json_index; ++} ++ ++} // namespace dom ++ ++ ++} // namespace simdjson ++ ++/* begin file include/simdjson/dom/element-inl.h */ ++#ifndef SIMDJSON_INLINE_ELEMENT_H ++#define SIMDJSON_INLINE_ELEMENT_H ++ ++#include ++#include ++ ++namespace simdjson { ++ ++// ++// simdjson_result inline implementation ++// ++simdjson_inline simdjson_result::simdjson_result() noexcept ++ : internal::simdjson_result_base() {} ++simdjson_inline simdjson_result::simdjson_result(dom::element &&value) noexcept ++ : internal::simdjson_result_base(std::forward(value)) {} ++simdjson_inline simdjson_result::simdjson_result(error_code error) noexcept ++ : internal::simdjson_result_base(error) {} ++inline simdjson_result simdjson_result::type() const noexcept { ++ if (error()) { return error(); } ++ return first.type(); ++} ++ ++template ++simdjson_inline bool simdjson_result::is() const noexcept { ++ return !error() && first.is(); ++} ++template ++simdjson_inline simdjson_result simdjson_result::get() const noexcept { ++ if (error()) { return error(); } ++ return first.get(); ++} ++template ++simdjson_warn_unused simdjson_inline error_code simdjson_result::get(T &value) const noexcept { ++ if (error()) { return error(); } ++ return first.get(value); ++} ++ ++simdjson_inline simdjson_result simdjson_result::get_array() const noexcept { ++ if (error()) { return error(); } ++ return first.get_array(); ++} ++simdjson_inline simdjson_result simdjson_result::get_object() const noexcept { ++ if (error()) { return error(); } ++ return first.get_object(); ++} ++simdjson_inline simdjson_result simdjson_result::get_c_str() const noexcept { ++ if (error()) { return error(); } ++ return first.get_c_str(); ++} ++simdjson_inline simdjson_result simdjson_result::get_string_length() const noexcept { ++ if (error()) { return error(); } ++ return first.get_string_length(); ++} ++simdjson_inline simdjson_result simdjson_result::get_string() const noexcept { ++ if (error()) { return error(); } ++ return first.get_string(); ++} ++simdjson_inline simdjson_result simdjson_result::get_int64() const noexcept { ++ if (error()) { return error(); } ++ return first.get_int64(); ++} ++simdjson_inline simdjson_result simdjson_result::get_uint64() const noexcept { ++ if (error()) { return error(); } ++ return first.get_uint64(); ++} ++simdjson_inline simdjson_result simdjson_result::get_double() const noexcept { ++ if (error()) { return error(); } ++ return first.get_double(); ++} ++simdjson_inline simdjson_result simdjson_result::get_bool() const noexcept { ++ if (error()) { return error(); } ++ return first.get_bool(); ++} ++ ++simdjson_inline bool simdjson_result::is_array() const noexcept { ++ return !error() && first.is_array(); ++} ++simdjson_inline bool simdjson_result::is_object() const noexcept { ++ return !error() && first.is_object(); ++} ++simdjson_inline bool simdjson_result::is_string() const noexcept { ++ return !error() && first.is_string(); ++} ++simdjson_inline bool simdjson_result::is_int64() const noexcept { ++ return !error() && first.is_int64(); ++} ++simdjson_inline bool simdjson_result::is_uint64() const noexcept { ++ return !error() && first.is_uint64(); ++} ++simdjson_inline bool simdjson_result::is_double() const noexcept { ++ return !error() && first.is_double(); ++} ++simdjson_inline bool simdjson_result::is_number() const noexcept { ++ return !error() && first.is_number(); ++} ++simdjson_inline bool simdjson_result::is_bool() const noexcept { ++ return !error() && first.is_bool(); ++} ++ ++simdjson_inline bool simdjson_result::is_null() const noexcept { ++ return !error() && first.is_null(); ++} ++ ++simdjson_inline simdjson_result simdjson_result::operator[](std::string_view key) const noexcept { ++ if (error()) { return error(); } ++ return first[key]; ++} ++simdjson_inline simdjson_result simdjson_result::operator[](const char *key) const noexcept { ++ if (error()) { return error(); } ++ return first[key]; ++} ++simdjson_inline simdjson_result simdjson_result::at_pointer(const std::string_view json_pointer) const noexcept { ++ if (error()) { return error(); } ++ return first.at_pointer(json_pointer); ++} ++#ifndef SIMDJSON_DISABLE_DEPRECATED_API ++[[deprecated("For standard compliance, use at_pointer instead, and prefix your pointers with a slash '/', see RFC6901 ")]] ++simdjson_inline simdjson_result simdjson_result::at(const std::string_view json_pointer) const noexcept { ++SIMDJSON_PUSH_DISABLE_WARNINGS ++SIMDJSON_DISABLE_DEPRECATED_WARNING ++ if (error()) { return error(); } ++ return first.at(json_pointer); ++SIMDJSON_POP_DISABLE_WARNINGS ++} ++#endif // SIMDJSON_DISABLE_DEPRECATED_API ++simdjson_inline simdjson_result simdjson_result::at(size_t index) const noexcept { ++ if (error()) { return error(); } ++ return first.at(index); ++} ++simdjson_inline simdjson_result simdjson_result::at_key(std::string_view key) const noexcept { ++ if (error()) { return error(); } ++ return first.at_key(key); ++} ++simdjson_inline simdjson_result simdjson_result::at_key_case_insensitive(std::string_view key) const noexcept { ++ if (error()) { return error(); } ++ return first.at_key_case_insensitive(key); ++} ++ ++#if SIMDJSON_EXCEPTIONS ++ ++simdjson_inline simdjson_result::operator bool() const noexcept(false) { ++ return get(); ++} ++simdjson_inline simdjson_result::operator const char *() const noexcept(false) { ++ return get(); ++} ++simdjson_inline simdjson_result::operator std::string_view() const noexcept(false) { ++ return get(); ++} ++simdjson_inline simdjson_result::operator uint64_t() const noexcept(false) { ++ return get(); ++} ++simdjson_inline simdjson_result::operator int64_t() const noexcept(false) { ++ return get(); ++} ++simdjson_inline simdjson_result::operator double() const noexcept(false) { ++ return get(); ++} ++simdjson_inline simdjson_result::operator dom::array() const noexcept(false) { ++ return get(); ++} ++simdjson_inline simdjson_result::operator dom::object() const noexcept(false) { ++ return get(); ++} ++ ++simdjson_inline dom::array::iterator simdjson_result::begin() const noexcept(false) { ++ if (error()) { throw simdjson_error(error()); } ++ return first.begin(); ++} ++simdjson_inline dom::array::iterator simdjson_result::end() const noexcept(false) { ++ if (error()) { throw simdjson_error(error()); } ++ return first.end(); ++} ++ ++#endif // SIMDJSON_EXCEPTIONS ++ ++namespace dom { ++ ++// ++// element inline implementation ++// ++simdjson_inline element::element() noexcept : tape{} {} ++simdjson_inline element::element(const internal::tape_ref &_tape) noexcept : tape{_tape} { } ++ ++inline element_type element::type() const noexcept { ++ SIMDJSON_DEVELOPMENT_ASSERT(tape.usable()); // https://github.com/simdjson/simdjson/issues/1914 ++ auto tape_type = tape.tape_ref_type(); ++ return tape_type == internal::tape_type::FALSE_VALUE ? element_type::BOOL : static_cast(tape_type); ++} ++ ++inline simdjson_result element::get_bool() const noexcept { ++ SIMDJSON_DEVELOPMENT_ASSERT(tape.usable()); // https://github.com/simdjson/simdjson/issues/1914 ++ if(tape.is_true()) { ++ return true; ++ } else if(tape.is_false()) { ++ return false; ++ } ++ return INCORRECT_TYPE; ++} ++inline simdjson_result element::get_c_str() const noexcept { ++ SIMDJSON_DEVELOPMENT_ASSERT(tape.usable()); // https://github.com/simdjson/simdjson/issues/1914 ++ switch (tape.tape_ref_type()) { ++ case internal::tape_type::STRING: { ++ return tape.get_c_str(); ++ } ++ default: ++ return INCORRECT_TYPE; ++ } ++} ++inline simdjson_result element::get_string_length() const noexcept { ++ SIMDJSON_DEVELOPMENT_ASSERT(tape.usable()); // https://github.com/simdjson/simdjson/issues/1914 ++ switch (tape.tape_ref_type()) { ++ case internal::tape_type::STRING: { ++ return tape.get_string_length(); ++ } ++ default: ++ return INCORRECT_TYPE; ++ } ++} ++inline simdjson_result element::get_string() const noexcept { ++ SIMDJSON_DEVELOPMENT_ASSERT(tape.usable()); // https://github.com/simdjson/simdjson/issues/1914 ++ switch (tape.tape_ref_type()) { ++ case internal::tape_type::STRING: ++ return tape.get_string_view(); ++ default: ++ return INCORRECT_TYPE; ++ } ++} ++inline simdjson_result element::get_uint64() const noexcept { ++ SIMDJSON_DEVELOPMENT_ASSERT(tape.usable()); // https://github.com/simdjson/simdjson/issues/1914 ++ if(simdjson_unlikely(!tape.is_uint64())) { // branch rarely taken ++ if(tape.is_int64()) { ++ int64_t result = tape.next_tape_value(); ++ if (result < 0) { ++ return NUMBER_OUT_OF_RANGE; ++ } ++ return uint64_t(result); ++ } ++ return INCORRECT_TYPE; ++ } ++ return tape.next_tape_value(); ++} ++inline simdjson_result element::get_int64() const noexcept { ++ SIMDJSON_DEVELOPMENT_ASSERT(tape.usable()); // https://github.com/simdjson/simdjson/issues/1914 ++ if(simdjson_unlikely(!tape.is_int64())) { // branch rarely taken ++ if(tape.is_uint64()) { ++ uint64_t result = tape.next_tape_value(); ++ // Wrapping max in parens to handle Windows issue: https://stackoverflow.com/questions/11544073/how-do-i-deal-with-the-max-macro-in-windows-h-colliding-with-max-in-std ++ if (result > uint64_t((std::numeric_limits::max)())) { ++ return NUMBER_OUT_OF_RANGE; ++ } ++ return static_cast(result); ++ } ++ return INCORRECT_TYPE; ++ } ++ return tape.next_tape_value(); ++} ++inline simdjson_result element::get_double() const noexcept { ++ SIMDJSON_DEVELOPMENT_ASSERT(tape.usable()); // https://github.com/simdjson/simdjson/issues/1914 ++ // Performance considerations: ++ // 1. Querying tape_ref_type() implies doing a shift, it is fast to just do a straight ++ // comparison. ++ // 2. Using a switch-case relies on the compiler guessing what kind of code generation ++ // we want... But the compiler cannot know that we expect the type to be "double" ++ // most of the time. ++ // We can expect get to refer to a double type almost all the time. ++ // It is important to craft the code accordingly so that the compiler can use this ++ // information. (This could also be solved with profile-guided optimization.) ++ if(simdjson_unlikely(!tape.is_double())) { // branch rarely taken ++ if(tape.is_uint64()) { ++ return double(tape.next_tape_value()); ++ } else if(tape.is_int64()) { ++ return double(tape.next_tape_value()); ++ } ++ return INCORRECT_TYPE; ++ } ++ // this is common: ++ return tape.next_tape_value(); ++} ++inline simdjson_result element::get_array() const noexcept { ++ SIMDJSON_DEVELOPMENT_ASSERT(tape.usable()); // https://github.com/simdjson/simdjson/issues/1914 ++ switch (tape.tape_ref_type()) { ++ case internal::tape_type::START_ARRAY: ++ return array(tape); ++ default: ++ return INCORRECT_TYPE; ++ } ++} ++inline simdjson_result element::get_object() const noexcept { ++ SIMDJSON_DEVELOPMENT_ASSERT(tape.usable()); // https://github.com/simdjson/simdjson/issues/1914 ++ switch (tape.tape_ref_type()) { ++ case internal::tape_type::START_OBJECT: ++ return object(tape); ++ default: ++ return INCORRECT_TYPE; ++ } ++} ++ ++template ++simdjson_warn_unused simdjson_inline error_code element::get(T &value) const noexcept { ++ return get().get(value); ++} ++// An element-specific version prevents recursion with simdjson_result::get(value) ++template<> ++simdjson_warn_unused simdjson_inline error_code element::get(element &value) const noexcept { ++ value = element(tape); ++ return SUCCESS; ++} ++template ++inline void element::tie(T &value, error_code &error) && noexcept { ++ error = get(value); ++} ++ ++template ++simdjson_inline bool element::is() const noexcept { ++ auto result = get(); ++ return !result.error(); ++} ++ ++template<> inline simdjson_result element::get() const noexcept { return get_array(); } ++template<> inline simdjson_result element::get() const noexcept { return get_object(); } ++template<> inline simdjson_result element::get() const noexcept { return get_c_str(); } ++template<> inline simdjson_result element::get() const noexcept { return get_string(); } ++template<> inline simdjson_result element::get() const noexcept { return get_int64(); } ++template<> inline simdjson_result element::get() const noexcept { return get_uint64(); } ++template<> inline simdjson_result element::get() const noexcept { return get_double(); } ++template<> inline simdjson_result element::get() const noexcept { return get_bool(); } ++ ++inline bool element::is_array() const noexcept { return is(); } ++inline bool element::is_object() const noexcept { return is(); } ++inline bool element::is_string() const noexcept { return is(); } ++inline bool element::is_int64() const noexcept { return is(); } ++inline bool element::is_uint64() const noexcept { return is(); } ++inline bool element::is_double() const noexcept { return is(); } ++inline bool element::is_bool() const noexcept { return is(); } ++inline bool element::is_number() const noexcept { return is_int64() || is_uint64() || is_double(); } ++ ++inline bool element::is_null() const noexcept { ++ return tape.is_null_on_tape(); ++} ++ ++#if SIMDJSON_EXCEPTIONS ++ ++inline element::operator bool() const noexcept(false) { return get(); } ++inline element::operator const char*() const noexcept(false) { return get(); } ++inline element::operator std::string_view() const noexcept(false) { return get(); } ++inline element::operator uint64_t() const noexcept(false) { return get(); } ++inline element::operator int64_t() const noexcept(false) { return get(); } ++inline element::operator double() const noexcept(false) { return get(); } ++inline element::operator array() const noexcept(false) { return get(); } ++inline element::operator object() const noexcept(false) { return get(); } ++ ++inline array::iterator element::begin() const noexcept(false) { ++ return get().begin(); ++} ++inline array::iterator element::end() const noexcept(false) { ++ return get().end(); ++} ++ ++#endif // SIMDJSON_EXCEPTIONS ++ ++inline simdjson_result element::operator[](std::string_view key) const noexcept { ++ return at_key(key); ++} ++inline simdjson_result element::operator[](const char *key) const noexcept { ++ return at_key(key); ++} ++ ++inline simdjson_result element::at_pointer(std::string_view json_pointer) const noexcept { ++ SIMDJSON_DEVELOPMENT_ASSERT(tape.usable()); // https://github.com/simdjson/simdjson/issues/1914 ++ switch (tape.tape_ref_type()) { ++ case internal::tape_type::START_OBJECT: ++ return object(tape).at_pointer(json_pointer); ++ case internal::tape_type::START_ARRAY: ++ return array(tape).at_pointer(json_pointer); ++ default: { ++ if(!json_pointer.empty()) { // a non-empty string is invalid on an atom ++ return INVALID_JSON_POINTER; ++ } ++ // an empty string means that we return the current node ++ dom::element copy(*this); ++ return simdjson_result(std::move(copy)); ++ } ++ } ++} ++#ifndef SIMDJSON_DISABLE_DEPRECATED_API ++[[deprecated("For standard compliance, use at_pointer instead, and prefix your pointers with a slash '/', see RFC6901 ")]] ++inline simdjson_result element::at(std::string_view json_pointer) const noexcept { ++ // version 0.4 of simdjson allowed non-compliant pointers ++ auto std_pointer = (json_pointer.empty() ? "" : "/") + std::string(json_pointer.begin(), json_pointer.end()); ++ return at_pointer(std_pointer); ++} ++#endif // SIMDJSON_DISABLE_DEPRECATED_API ++ ++inline simdjson_result element::at(size_t index) const noexcept { ++ return get().at(index); ++} ++inline simdjson_result element::at_key(std::string_view key) const noexcept { ++ return get().at_key(key); ++} ++inline simdjson_result element::at_key_case_insensitive(std::string_view key) const noexcept { ++ return get().at_key_case_insensitive(key); ++} ++ ++inline bool element::dump_raw_tape(std::ostream &out) const noexcept { ++ SIMDJSON_DEVELOPMENT_ASSERT(tape.usable()); // https://github.com/simdjson/simdjson/issues/1914 ++ return tape.doc->dump_raw_tape(out); ++} ++ ++ ++inline std::ostream& operator<<(std::ostream& out, element_type type) { ++ switch (type) { ++ case element_type::ARRAY: ++ return out << "array"; ++ case element_type::OBJECT: ++ return out << "object"; ++ case element_type::INT64: ++ return out << "int64_t"; ++ case element_type::UINT64: ++ return out << "uint64_t"; ++ case element_type::DOUBLE: ++ return out << "double"; ++ case element_type::STRING: ++ return out << "string"; ++ case element_type::BOOL: ++ return out << "bool"; ++ case element_type::NULL_VALUE: ++ return out << "null"; ++ default: ++ return out << "unexpected content!!!"; // abort() usage is forbidden in the library ++ } ++} ++ ++} // namespace dom ++ ++} // namespace simdjson ++ ++#endif // SIMDJSON_INLINE_ELEMENT_H ++/* end file include/simdjson/dom/element-inl.h */ ++ ++#if defined(__cpp_lib_ranges) ++static_assert(std::ranges::view); ++static_assert(std::ranges::sized_range); ++#if SIMDJSON_EXCEPTIONS ++static_assert(std::ranges::view>); ++static_assert(std::ranges::sized_range>); ++#endif // SIMDJSON_EXCEPTIONS ++#endif // defined(__cpp_lib_ranges) ++ ++#endif // SIMDJSON_INLINE_ARRAY_H ++/* end file include/simdjson/dom/array-inl.h */ ++/* begin file include/simdjson/dom/document_stream-inl.h */ ++#ifndef SIMDJSON_INLINE_DOCUMENT_STREAM_H ++#define SIMDJSON_INLINE_DOCUMENT_STREAM_H ++ ++#include ++#include ++#include ++namespace simdjson { ++namespace dom { ++ ++#ifdef SIMDJSON_THREADS_ENABLED ++inline void stage1_worker::finish() { ++ // After calling "run" someone would call finish() to wait ++ // for the end of the processing. ++ // This function will wait until either the thread has done ++ // the processing or, else, the destructor has been called. ++ std::unique_lock lock(locking_mutex); ++ cond_var.wait(lock, [this]{return has_work == false;}); ++} ++ ++inline stage1_worker::~stage1_worker() { ++ // The thread may never outlive the stage1_worker instance ++ // and will always be stopped/joined before the stage1_worker ++ // instance is gone. ++ stop_thread(); ++} ++ ++inline void stage1_worker::start_thread() { ++ std::unique_lock lock(locking_mutex); ++ if(thread.joinable()) { ++ return; // This should never happen but we never want to create more than one thread. ++ } ++ thread = std::thread([this]{ ++ while(true) { ++ std::unique_lock thread_lock(locking_mutex); ++ // We wait for either "run" or "stop_thread" to be called. ++ cond_var.wait(thread_lock, [this]{return has_work || !can_work;}); ++ // If, for some reason, the stop_thread() method was called (i.e., the ++ // destructor of stage1_worker is called, then we want to immediately destroy ++ // the thread (and not do any more processing). ++ if(!can_work) { ++ break; ++ } ++ this->owner->stage1_thread_error = this->owner->run_stage1(*this->stage1_thread_parser, ++ this->_next_batch_start); ++ this->has_work = false; ++ // The condition variable call should be moved after thread_lock.unlock() for performance ++ // reasons but thread sanitizers may report it as a data race if we do. ++ // See https://stackoverflow.com/questions/35775501/c-should-condition-variable-be-notified-under-lock ++ cond_var.notify_one(); // will notify "finish" ++ thread_lock.unlock(); ++ } ++ } ++ ); ++} ++ ++ ++inline void stage1_worker::stop_thread() { ++ std::unique_lock lock(locking_mutex); ++ // We have to make sure that all locks can be released. ++ can_work = false; ++ has_work = false; ++ cond_var.notify_all(); ++ lock.unlock(); ++ if(thread.joinable()) { ++ thread.join(); ++ } ++} ++ ++inline void stage1_worker::run(document_stream * ds, dom::parser * stage1, size_t next_batch_start) { ++ std::unique_lock lock(locking_mutex); ++ owner = ds; ++ _next_batch_start = next_batch_start; ++ stage1_thread_parser = stage1; ++ has_work = true; ++ // The condition variable call should be moved after thread_lock.unlock() for performance ++ // reasons but thread sanitizers may report it as a data race if we do. ++ // See https://stackoverflow.com/questions/35775501/c-should-condition-variable-be-notified-under-lock ++ cond_var.notify_one(); // will notify the thread lock that we have work ++ lock.unlock(); ++} ++#endif ++ ++simdjson_inline document_stream::document_stream( ++ dom::parser &_parser, ++ const uint8_t *_buf, ++ size_t _len, ++ size_t _batch_size ++) noexcept ++ : parser{&_parser}, ++ buf{_buf}, ++ len{_len}, ++ batch_size{_batch_size <= MINIMAL_BATCH_SIZE ? MINIMAL_BATCH_SIZE : _batch_size}, ++ error{SUCCESS} ++#ifdef SIMDJSON_THREADS_ENABLED ++ , use_thread(_parser.threaded) // we need to make a copy because _parser.threaded can change ++#endif ++{ ++#ifdef SIMDJSON_THREADS_ENABLED ++ if(worker.get() == nullptr) { ++ error = MEMALLOC; ++ } ++#endif ++} ++ ++simdjson_inline document_stream::document_stream() noexcept ++ : parser{nullptr}, ++ buf{nullptr}, ++ len{0}, ++ batch_size{0}, ++ error{UNINITIALIZED} ++#ifdef SIMDJSON_THREADS_ENABLED ++ , use_thread(false) ++#endif ++{ ++} ++ ++simdjson_inline document_stream::~document_stream() noexcept { ++#ifdef SIMDJSON_THREADS_ENABLED ++ worker.reset(); ++#endif ++} ++ ++simdjson_inline document_stream::iterator::iterator() noexcept ++ : stream{nullptr}, finished{true} { ++} ++ ++simdjson_inline document_stream::iterator document_stream::begin() noexcept { ++ start(); ++ // If there are no documents, we're finished. ++ return iterator(this, error == EMPTY); ++} ++ ++simdjson_inline document_stream::iterator document_stream::end() noexcept { ++ return iterator(this, true); ++} ++ ++simdjson_inline document_stream::iterator::iterator(document_stream* _stream, bool is_end) noexcept ++ : stream{_stream}, finished{is_end} { ++} ++ ++simdjson_inline document_stream::iterator::reference document_stream::iterator::operator*() noexcept { ++ // Note that in case of error, we do not yet mark ++ // the iterator as "finished": this detection is done ++ // in the operator++ function since it is possible ++ // to call operator++ repeatedly while omitting ++ // calls to operator*. ++ if (stream->error) { return stream->error; } ++ return stream->parser->doc.root(); ++} ++ ++simdjson_inline document_stream::iterator& document_stream::iterator::operator++() noexcept { ++ // If there is an error, then we want the iterator ++ // to be finished, no matter what. (E.g., we do not ++ // keep generating documents with errors, or go beyond ++ // a document with errors.) ++ // ++ // Users do not have to call "operator*()" when they use operator++, ++ // so we need to end the stream in the operator++ function. ++ // ++ // Note that setting finished = true is essential otherwise ++ // we would enter an infinite loop. ++ if (stream->error) { finished = true; } ++ // Note that stream->error() is guarded against error conditions ++ // (it will immediately return if stream->error casts to false). ++ // In effect, this next function does nothing when (stream->error) ++ // is true (hence the risk of an infinite loop). ++ stream->next(); ++ // If that was the last document, we're finished. ++ // It is the only type of error we do not want to appear ++ // in operator*. ++ if (stream->error == EMPTY) { finished = true; } ++ // If we had any other kind of error (not EMPTY) then we want ++ // to pass it along to the operator* and we cannot mark the result ++ // as "finished" just yet. ++ return *this; ++} ++ ++simdjson_inline bool document_stream::iterator::operator!=(const document_stream::iterator &other) const noexcept { ++ return finished != other.finished; ++} ++ ++inline void document_stream::start() noexcept { ++ if (error) { return; } ++ error = parser->ensure_capacity(batch_size); ++ if (error) { return; } ++ // Always run the first stage 1 parse immediately ++ batch_start = 0; ++ error = run_stage1(*parser, batch_start); ++ while(error == EMPTY) { ++ // In exceptional cases, we may start with an empty block ++ batch_start = next_batch_start(); ++ if (batch_start >= len) { return; } ++ error = run_stage1(*parser, batch_start); ++ } ++ if (error) { return; } ++#ifdef SIMDJSON_THREADS_ENABLED ++ if (use_thread && next_batch_start() < len) { ++ // Kick off the first thread if needed ++ error = stage1_thread_parser.ensure_capacity(batch_size); ++ if (error) { return; } ++ worker->start_thread(); ++ start_stage1_thread(); ++ if (error) { return; } ++ } ++#endif // SIMDJSON_THREADS_ENABLED ++ next(); ++} ++ ++simdjson_inline size_t document_stream::iterator::current_index() const noexcept { ++ return stream->doc_index; ++} ++ ++simdjson_inline std::string_view document_stream::iterator::source() const noexcept { ++ const char* start = reinterpret_cast(stream->buf) + current_index(); ++ bool object_or_array = ((*start == '[') || (*start == '{')); ++ if(object_or_array) { ++ size_t next_doc_index = stream->batch_start + stream->parser->implementation->structural_indexes[stream->parser->implementation->next_structural_index - 1]; ++ return std::string_view(start, next_doc_index - current_index() + 1); ++ } else { ++ size_t next_doc_index = stream->batch_start + stream->parser->implementation->structural_indexes[stream->parser->implementation->next_structural_index]; ++ return std::string_view(reinterpret_cast(stream->buf) + current_index(), next_doc_index - current_index() - 1); ++ } ++} ++ ++ ++inline void document_stream::next() noexcept { ++ // We always exit at once, once in an error condition. ++ if (error) { return; } ++ ++ // Load the next document from the batch ++ doc_index = batch_start + parser->implementation->structural_indexes[parser->implementation->next_structural_index]; ++ error = parser->implementation->stage2_next(parser->doc); ++ // If that was the last document in the batch, load another batch (if available) ++ while (error == EMPTY) { ++ batch_start = next_batch_start(); ++ if (batch_start >= len) { break; } ++ ++#ifdef SIMDJSON_THREADS_ENABLED ++ if(use_thread) { ++ load_from_stage1_thread(); ++ } else { ++ error = run_stage1(*parser, batch_start); ++ } ++#else ++ error = run_stage1(*parser, batch_start); ++#endif ++ if (error) { continue; } // If the error was EMPTY, we may want to load another batch. ++ // Run stage 2 on the first document in the batch ++ doc_index = batch_start + parser->implementation->structural_indexes[parser->implementation->next_structural_index]; ++ error = parser->implementation->stage2_next(parser->doc); ++ } ++} ++inline size_t document_stream::size_in_bytes() const noexcept { ++ return len; ++} ++ ++inline size_t document_stream::truncated_bytes() const noexcept { ++ if(error == CAPACITY) { return len - batch_start; } ++ return parser->implementation->structural_indexes[parser->implementation->n_structural_indexes] - parser->implementation->structural_indexes[parser->implementation->n_structural_indexes + 1]; ++} ++ ++inline size_t document_stream::next_batch_start() const noexcept { ++ return batch_start + parser->implementation->structural_indexes[parser->implementation->n_structural_indexes]; ++} ++ ++inline error_code document_stream::run_stage1(dom::parser &p, size_t _batch_start) noexcept { ++ size_t remaining = len - _batch_start; ++ if (remaining <= batch_size) { ++ return p.implementation->stage1(&buf[_batch_start], remaining, stage1_mode::streaming_final); ++ } else { ++ return p.implementation->stage1(&buf[_batch_start], batch_size, stage1_mode::streaming_partial); ++ } ++} ++ ++#ifdef SIMDJSON_THREADS_ENABLED ++ ++inline void document_stream::load_from_stage1_thread() noexcept { ++ worker->finish(); ++ // Swap to the parser that was loaded up in the thread. Make sure the parser has ++ // enough memory to swap to, as well. ++ std::swap(*parser, stage1_thread_parser); ++ error = stage1_thread_error; ++ if (error) { return; } ++ ++ // If there's anything left, start the stage 1 thread! ++ if (next_batch_start() < len) { ++ start_stage1_thread(); ++ } ++} ++ ++inline void document_stream::start_stage1_thread() noexcept { ++ // we call the thread on a lambda that will update ++ // this->stage1_thread_error ++ // there is only one thread that may write to this value ++ // TODO this is NOT exception-safe. ++ this->stage1_thread_error = UNINITIALIZED; // In case something goes wrong, make sure it's an error ++ size_t _next_batch_start = this->next_batch_start(); ++ ++ worker->run(this, & this->stage1_thread_parser, _next_batch_start); ++} ++ ++#endif // SIMDJSON_THREADS_ENABLED ++ ++} // namespace dom ++ ++simdjson_inline simdjson_result::simdjson_result() noexcept ++ : simdjson_result_base() { ++} ++simdjson_inline simdjson_result::simdjson_result(error_code error) noexcept ++ : simdjson_result_base(error) { ++} ++simdjson_inline simdjson_result::simdjson_result(dom::document_stream &&value) noexcept ++ : simdjson_result_base(std::forward(value)) { ++} ++ ++#if SIMDJSON_EXCEPTIONS ++simdjson_inline dom::document_stream::iterator simdjson_result::begin() noexcept(false) { ++ if (error()) { throw simdjson_error(error()); } ++ return first.begin(); ++} ++simdjson_inline dom::document_stream::iterator simdjson_result::end() noexcept(false) { ++ if (error()) { throw simdjson_error(error()); } ++ return first.end(); ++} ++#else // SIMDJSON_EXCEPTIONS ++#ifndef SIMDJSON_DISABLE_DEPRECATED_API ++simdjson_inline dom::document_stream::iterator simdjson_result::begin() noexcept { ++ first.error = error(); ++ return first.begin(); ++} ++simdjson_inline dom::document_stream::iterator simdjson_result::end() noexcept { ++ first.error = error(); ++ return first.end(); ++} ++#endif // SIMDJSON_DISABLE_DEPRECATED_API ++#endif // SIMDJSON_EXCEPTIONS ++ ++} // namespace simdjson ++#endif // SIMDJSON_INLINE_DOCUMENT_STREAM_H ++/* end file include/simdjson/dom/document_stream-inl.h */ ++/* begin file include/simdjson/dom/document-inl.h */ ++#ifndef SIMDJSON_INLINE_DOCUMENT_H ++#define SIMDJSON_INLINE_DOCUMENT_H ++ ++// Inline implementations go in here. ++ ++#include ++#include ++ ++namespace simdjson { ++namespace dom { ++ ++// ++// document inline implementation ++// ++inline element document::root() const noexcept { ++ return element(internal::tape_ref(this, 1)); ++} ++simdjson_warn_unused ++inline size_t document::capacity() const noexcept { ++ return allocated_capacity; ++} ++ ++simdjson_warn_unused ++inline error_code document::allocate(size_t capacity) noexcept { ++ if (capacity == 0) { ++ string_buf.reset(); ++ tape.reset(); ++ allocated_capacity = 0; ++ return SUCCESS; ++ } ++ ++ // a pathological input like "[[[[..." would generate capacity tape elements, so ++ // need a capacity of at least capacity + 1, but it is also possible to do ++ // worse with "[7,7,7,7,6,7,7,7,6,7,7,6,[7,7,7,7,6,7,7,7,6,7,7,6,7,7,7,7,7,7,6" ++ //where capacity + 1 tape elements are ++ // generated, see issue https://github.com/simdjson/simdjson/issues/345 ++ size_t tape_capacity = SIMDJSON_ROUNDUP_N(capacity + 3, 64); ++ // a document with only zero-length strings... could have capacity/3 string ++ // and we would need capacity/3 * 5 bytes on the string buffer ++ size_t string_capacity = SIMDJSON_ROUNDUP_N(5 * capacity / 3 + SIMDJSON_PADDING, 64); ++ string_buf.reset( new (std::nothrow) uint8_t[string_capacity]); ++ tape.reset(new (std::nothrow) uint64_t[tape_capacity]); ++ if(!(string_buf && tape)) { ++ allocated_capacity = 0; ++ string_buf.reset(); ++ tape.reset(); ++ return MEMALLOC; ++ } ++ // Technically the allocated_capacity might be larger than capacity ++ // so the next line is pessimistic. ++ allocated_capacity = capacity; ++ return SUCCESS; ++} ++ ++inline bool document::dump_raw_tape(std::ostream &os) const noexcept { ++ uint32_t string_length; ++ size_t tape_idx = 0; ++ uint64_t tape_val = tape[tape_idx]; ++ uint8_t type = uint8_t(tape_val >> 56); ++ os << tape_idx << " : " << type; ++ tape_idx++; ++ size_t how_many = 0; ++ if (type == 'r') { ++ how_many = size_t(tape_val & internal::JSON_VALUE_MASK); ++ } else { ++ // Error: no starting root node? ++ return false; ++ } ++ os << "\t// pointing to " << how_many << " (right after last node)\n"; ++ uint64_t payload; ++ for (; tape_idx < how_many; tape_idx++) { ++ os << tape_idx << " : "; ++ tape_val = tape[tape_idx]; ++ payload = tape_val & internal::JSON_VALUE_MASK; ++ type = uint8_t(tape_val >> 56); ++ switch (type) { ++ case '"': // we have a string ++ os << "string \""; ++ std::memcpy(&string_length, string_buf.get() + payload, sizeof(uint32_t)); ++ os << internal::escape_json_string(std::string_view( ++ reinterpret_cast(string_buf.get() + payload + sizeof(uint32_t)), ++ string_length ++ )); ++ os << '"'; ++ os << '\n'; ++ break; ++ case 'l': // we have a long int ++ if (tape_idx + 1 >= how_many) { ++ return false; ++ } ++ os << "integer " << static_cast(tape[++tape_idx]) << "\n"; ++ break; ++ case 'u': // we have a long uint ++ if (tape_idx + 1 >= how_many) { ++ return false; ++ } ++ os << "unsigned integer " << tape[++tape_idx] << "\n"; ++ break; ++ case 'd': // we have a double ++ os << "float "; ++ if (tape_idx + 1 >= how_many) { ++ return false; ++ } ++ double answer; ++ std::memcpy(&answer, &tape[++tape_idx], sizeof(answer)); ++ os << answer << '\n'; ++ break; ++ case 'n': // we have a null ++ os << "null\n"; ++ break; ++ case 't': // we have a true ++ os << "true\n"; ++ break; ++ case 'f': // we have a false ++ os << "false\n"; ++ break; ++ case '{': // we have an object ++ os << "{\t// pointing to next tape location " << uint32_t(payload) ++ << " (first node after the scope), " ++ << " saturated count " ++ << ((payload >> 32) & internal::JSON_COUNT_MASK)<< "\n"; ++ break; case '}': // we end an object ++ os << "}\t// pointing to previous tape location " << uint32_t(payload) ++ << " (start of the scope)\n"; ++ break; ++ case '[': // we start an array ++ os << "[\t// pointing to next tape location " << uint32_t(payload) ++ << " (first node after the scope), " ++ << " saturated count " ++ << ((payload >> 32) & internal::JSON_COUNT_MASK)<< "\n"; ++ break; ++ case ']': // we end an array ++ os << "]\t// pointing to previous tape location " << uint32_t(payload) ++ << " (start of the scope)\n"; ++ break; ++ case 'r': // we start and end with the root node ++ // should we be hitting the root node? ++ return false; ++ default: ++ return false; ++ } ++ } ++ tape_val = tape[tape_idx]; ++ payload = tape_val & internal::JSON_VALUE_MASK; ++ type = uint8_t(tape_val >> 56); ++ os << tape_idx << " : " << type << "\t// pointing to " << payload ++ << " (start root)\n"; ++ return true; ++} ++ ++} // namespace dom ++} // namespace simdjson ++ ++#endif // SIMDJSON_INLINE_DOCUMENT_H ++/* end file include/simdjson/dom/document-inl.h */ ++/* begin file include/simdjson/dom/object-inl.h */ ++#ifndef SIMDJSON_INLINE_OBJECT_H ++#define SIMDJSON_INLINE_OBJECT_H ++ ++#include ++#include ++ ++namespace simdjson { ++ ++// ++// simdjson_result inline implementation ++// ++simdjson_inline simdjson_result::simdjson_result() noexcept ++ : internal::simdjson_result_base() {} ++simdjson_inline simdjson_result::simdjson_result(dom::object value) noexcept ++ : internal::simdjson_result_base(std::forward(value)) {} ++simdjson_inline simdjson_result::simdjson_result(error_code error) noexcept ++ : internal::simdjson_result_base(error) {} ++ ++inline simdjson_result simdjson_result::operator[](std::string_view key) const noexcept { ++ if (error()) { return error(); } ++ return first[key]; ++} ++inline simdjson_result simdjson_result::operator[](const char *key) const noexcept { ++ if (error()) { return error(); } ++ return first[key]; ++} ++inline simdjson_result simdjson_result::at_pointer(std::string_view json_pointer) const noexcept { ++ if (error()) { return error(); } ++ return first.at_pointer(json_pointer); ++} ++inline simdjson_result simdjson_result::at_key(std::string_view key) const noexcept { ++ if (error()) { return error(); } ++ return first.at_key(key); ++} ++inline simdjson_result simdjson_result::at_key_case_insensitive(std::string_view key) const noexcept { ++ if (error()) { return error(); } ++ return first.at_key_case_insensitive(key); ++} ++ ++#if SIMDJSON_EXCEPTIONS ++ ++inline dom::object::iterator simdjson_result::begin() const noexcept(false) { ++ if (error()) { throw simdjson_error(error()); } ++ return first.begin(); ++} ++inline dom::object::iterator simdjson_result::end() const noexcept(false) { ++ if (error()) { throw simdjson_error(error()); } ++ return first.end(); ++} ++inline size_t simdjson_result::size() const noexcept(false) { ++ if (error()) { throw simdjson_error(error()); } ++ return first.size(); ++} ++ ++#endif // SIMDJSON_EXCEPTIONS ++ ++namespace dom { ++ ++// ++// object inline implementation ++// ++simdjson_inline object::object() noexcept : tape{} {} ++simdjson_inline object::object(const internal::tape_ref &_tape) noexcept : tape{_tape} { } ++inline object::iterator object::begin() const noexcept { ++ SIMDJSON_DEVELOPMENT_ASSERT(tape.usable()); // https://github.com/simdjson/simdjson/issues/1914 ++ return internal::tape_ref(tape.doc, tape.json_index + 1); ++} ++inline object::iterator object::end() const noexcept { ++ SIMDJSON_DEVELOPMENT_ASSERT(tape.usable()); // https://github.com/simdjson/simdjson/issues/1914 ++ return internal::tape_ref(tape.doc, tape.after_element() - 1); ++} ++inline size_t object::size() const noexcept { ++ SIMDJSON_DEVELOPMENT_ASSERT(tape.usable()); // https://github.com/simdjson/simdjson/issues/1914 ++ return tape.scope_count(); ++} ++ ++inline simdjson_result object::operator[](std::string_view key) const noexcept { ++ return at_key(key); ++} ++inline simdjson_result object::operator[](const char *key) const noexcept { ++ return at_key(key); ++} ++inline simdjson_result object::at_pointer(std::string_view json_pointer) const noexcept { ++ SIMDJSON_DEVELOPMENT_ASSERT(tape.usable()); // https://github.com/simdjson/simdjson/issues/1914 ++ if(json_pointer.empty()) { // an empty string means that we return the current node ++ return element(this->tape); // copy the current node ++ } else if(json_pointer[0] != '/') { // otherwise there is an error ++ return INVALID_JSON_POINTER; ++ } ++ json_pointer = json_pointer.substr(1); ++ size_t slash = json_pointer.find('/'); ++ std::string_view key = json_pointer.substr(0, slash); ++ // Grab the child with the given key ++ simdjson_result child; ++ ++ // If there is an escape character in the key, unescape it and then get the child. ++ size_t escape = key.find('~'); ++ if (escape != std::string_view::npos) { ++ // Unescape the key ++ std::string unescaped(key); ++ do { ++ switch (unescaped[escape+1]) { ++ case '0': ++ unescaped.replace(escape, 2, "~"); ++ break; ++ case '1': ++ unescaped.replace(escape, 2, "/"); ++ break; ++ default: ++ return INVALID_JSON_POINTER; // "Unexpected ~ escape character in JSON pointer"); ++ } ++ escape = unescaped.find('~', escape+1); ++ } while (escape != std::string::npos); ++ child = at_key(unescaped); ++ } else { ++ child = at_key(key); ++ } ++ if(child.error()) { ++ return child; // we do not continue if there was an error ++ } ++ // If there is a /, we have to recurse and look up more of the path ++ if (slash != std::string_view::npos) { ++ child = child.at_pointer(json_pointer.substr(slash)); ++ } ++ return child; ++} ++ ++inline simdjson_result object::at_key(std::string_view key) const noexcept { ++ iterator end_field = end(); ++ for (iterator field = begin(); field != end_field; ++field) { ++ if (field.key_equals(key)) { ++ return field.value(); ++ } ++ } ++ return NO_SUCH_FIELD; ++} ++// In case you wonder why we need this, please see ++// https://github.com/simdjson/simdjson/issues/323 ++// People do seek keys in a case-insensitive manner. ++inline simdjson_result object::at_key_case_insensitive(std::string_view key) const noexcept { ++ iterator end_field = end(); ++ for (iterator field = begin(); field != end_field; ++field) { ++ if (field.key_equals_case_insensitive(key)) { ++ return field.value(); ++ } ++ } ++ return NO_SUCH_FIELD; ++} ++ ++// ++// object::iterator inline implementation ++// ++simdjson_inline object::iterator::iterator(const internal::tape_ref &_tape) noexcept : tape{_tape} { } ++inline const key_value_pair object::iterator::operator*() const noexcept { ++ return key_value_pair(key(), value()); ++} ++inline bool object::iterator::operator!=(const object::iterator& other) const noexcept { ++ return tape.json_index != other.tape.json_index; ++} ++inline bool object::iterator::operator==(const object::iterator& other) const noexcept { ++ return tape.json_index == other.tape.json_index; ++} ++inline bool object::iterator::operator<(const object::iterator& other) const noexcept { ++ return tape.json_index < other.tape.json_index; ++} ++inline bool object::iterator::operator<=(const object::iterator& other) const noexcept { ++ return tape.json_index <= other.tape.json_index; ++} ++inline bool object::iterator::operator>=(const object::iterator& other) const noexcept { ++ return tape.json_index >= other.tape.json_index; ++} ++inline bool object::iterator::operator>(const object::iterator& other) const noexcept { ++ return tape.json_index > other.tape.json_index; ++} ++inline object::iterator& object::iterator::operator++() noexcept { ++ tape.json_index++; ++ tape.json_index = tape.after_element(); ++ return *this; ++} ++inline object::iterator object::iterator::operator++(int) noexcept { ++ object::iterator out = *this; ++ ++*this; ++ return out; ++} ++inline std::string_view object::iterator::key() const noexcept { ++ return tape.get_string_view(); ++} ++inline uint32_t object::iterator::key_length() const noexcept { ++ return tape.get_string_length(); ++} ++inline const char* object::iterator::key_c_str() const noexcept { ++ return reinterpret_cast(&tape.doc->string_buf[size_t(tape.tape_value()) + sizeof(uint32_t)]); ++} ++inline element object::iterator::value() const noexcept { ++ return element(internal::tape_ref(tape.doc, tape.json_index + 1)); ++} ++ ++/** ++ * Design notes: ++ * Instead of constructing a string_view and then comparing it with a ++ * user-provided strings, it is probably more performant to have dedicated ++ * functions taking as a parameter the string we want to compare against ++ * and return true when they are equal. That avoids the creation of a temporary ++ * std::string_view. Though it is possible for the compiler to avoid entirely ++ * any overhead due to string_view, relying too much on compiler magic is ++ * problematic: compiler magic sometimes fail, and then what do you do? ++ * Also, enticing users to rely on high-performance function is probably better ++ * on the long run. ++ */ ++ ++inline bool object::iterator::key_equals(std::string_view o) const noexcept { ++ // We use the fact that the key length can be computed quickly ++ // without access to the string buffer. ++ const uint32_t len = key_length(); ++ if(o.size() == len) { ++ // We avoid construction of a temporary string_view instance. ++ return (memcmp(o.data(), key_c_str(), len) == 0); ++ } ++ return false; ++} ++ ++inline bool object::iterator::key_equals_case_insensitive(std::string_view o) const noexcept { ++ // We use the fact that the key length can be computed quickly ++ // without access to the string buffer. ++ const uint32_t len = key_length(); ++ if(o.size() == len) { ++ // See For case-insensitive string comparisons, avoid char-by-char functions ++ // https://lemire.me/blog/2020/04/30/for-case-insensitive-string-comparisons-avoid-char-by-char-functions/ ++ // Note that it might be worth rolling our own strncasecmp function, with vectorization. ++ return (simdjson_strncasecmp(o.data(), key_c_str(), len) == 0); ++ } ++ return false; ++} ++// ++// key_value_pair inline implementation ++// ++inline key_value_pair::key_value_pair(std::string_view _key, element _value) noexcept : ++ key(_key), value(_value) {} ++ ++} // namespace dom ++ ++} // namespace simdjson ++ ++#if defined(__cpp_lib_ranges) ++static_assert(std::ranges::view); ++static_assert(std::ranges::sized_range); ++#if SIMDJSON_EXCEPTIONS ++static_assert(std::ranges::view>); ++static_assert(std::ranges::sized_range>); ++#endif // SIMDJSON_EXCEPTIONS ++#endif // defined(__cpp_lib_ranges) ++ ++#endif // SIMDJSON_INLINE_OBJECT_H ++/* end file include/simdjson/dom/object-inl.h */ ++/* begin file include/simdjson/dom/parsedjson_iterator-inl.h */ ++#ifndef SIMDJSON_INLINE_PARSEDJSON_ITERATOR_H ++#define SIMDJSON_INLINE_PARSEDJSON_ITERATOR_H ++ ++#include ++ ++#ifndef SIMDJSON_DISABLE_DEPRECATED_API ++ ++namespace simdjson { ++ ++// VS2017 reports deprecated warnings when you define a deprecated class's methods. ++SIMDJSON_PUSH_DISABLE_WARNINGS ++SIMDJSON_DISABLE_DEPRECATED_WARNING ++ ++// Because of template weirdness, the actual class definition is inline in the document class ++simdjson_warn_unused bool dom::parser::Iterator::is_ok() const { ++ return location < tape_length; ++} ++ ++// useful for debugging purposes ++size_t dom::parser::Iterator::get_tape_location() const { ++ return location; ++} ++ ++// useful for debugging purposes ++size_t dom::parser::Iterator::get_tape_length() const { ++ return tape_length; ++} ++ ++// returns the current depth (start at 1 with 0 reserved for the fictitious root ++// node) ++size_t dom::parser::Iterator::get_depth() const { ++ return depth; ++} ++ ++// A scope is a series of nodes at the same depth, typically it is either an ++// object ({) or an array ([). The root node has type 'r'. ++uint8_t dom::parser::Iterator::get_scope_type() const { ++ return depth_index[depth].scope_type; ++} ++ ++bool dom::parser::Iterator::move_forward() { ++ if (location + 1 >= tape_length) { ++ return false; // we are at the end! ++ } ++ ++ if ((current_type == '[') || (current_type == '{')) { ++ // We are entering a new scope ++ depth++; ++ assert(depth < max_depth); ++ depth_index[depth].start_of_scope = location; ++ depth_index[depth].scope_type = current_type; ++ } else if ((current_type == ']') || (current_type == '}')) { ++ // Leaving a scope. ++ depth--; ++ } else if (is_number()) { ++ // these types use 2 locations on the tape, not just one. ++ location += 1; ++ } ++ ++ location += 1; ++ current_val = doc.tape[location]; ++ current_type = uint8_t(current_val >> 56); ++ return true; ++} ++ ++void dom::parser::Iterator::move_to_value() { ++ // assume that we are on a key, so move by 1. ++ location += 1; ++ current_val = doc.tape[location]; ++ current_type = uint8_t(current_val >> 56); ++} ++ ++bool dom::parser::Iterator::move_to_key(const char *key) { ++ if (down()) { ++ do { ++ const bool right_key = (strcmp(get_string(), key) == 0); ++ move_to_value(); ++ if (right_key) { ++ return true; ++ } ++ } while (next()); ++ up(); ++ } ++ return false; ++} ++ ++bool dom::parser::Iterator::move_to_key_insensitive( ++ const char *key) { ++ if (down()) { ++ do { ++ const bool right_key = (simdjson_strcasecmp(get_string(), key) == 0); ++ move_to_value(); ++ if (right_key) { ++ return true; ++ } ++ } while (next()); ++ up(); ++ } ++ return false; ++} ++ ++bool dom::parser::Iterator::move_to_key(const char *key, ++ uint32_t length) { ++ if (down()) { ++ do { ++ bool right_key = ((get_string_length() == length) && ++ (memcmp(get_string(), key, length) == 0)); ++ move_to_value(); ++ if (right_key) { ++ return true; ++ } ++ } while (next()); ++ up(); ++ } ++ return false; ++} ++ ++bool dom::parser::Iterator::move_to_index(uint32_t index) { ++ if (down()) { ++ uint32_t i = 0; ++ for (; i < index; i++) { ++ if (!next()) { ++ break; ++ } ++ } ++ if (i == index) { ++ return true; ++ } ++ up(); ++ } ++ return false; ++} ++ ++bool dom::parser::Iterator::prev() { ++ size_t target_location = location; ++ to_start_scope(); ++ size_t npos = location; ++ if (target_location == npos) { ++ return false; // we were already at the start ++ } ++ size_t oldnpos; ++ // we have that npos < target_location here ++ do { ++ oldnpos = npos; ++ if ((current_type == '[') || (current_type == '{')) { ++ // we need to jump ++ npos = uint32_t(current_val); ++ } else { ++ npos = npos + ((current_type == 'd' || current_type == 'l') ? 2 : 1); ++ } ++ } while (npos < target_location); ++ location = oldnpos; ++ current_val = doc.tape[location]; ++ current_type = uint8_t(current_val >> 56); ++ return true; ++} ++ ++bool dom::parser::Iterator::up() { ++ if (depth == 1) { ++ return false; // don't allow moving back to root ++ } ++ to_start_scope(); ++ // next we just move to the previous value ++ depth--; ++ location -= 1; ++ current_val = doc.tape[location]; ++ current_type = uint8_t(current_val >> 56); ++ return true; ++} ++ ++bool dom::parser::Iterator::down() { ++ if (location + 1 >= tape_length) { ++ return false; ++ } ++ if ((current_type == '[') || (current_type == '{')) { ++ size_t npos = uint32_t(current_val); ++ if (npos == location + 2) { ++ return false; // we have an empty scope ++ } ++ depth++; ++ assert(depth < max_depth); ++ location = location + 1; ++ depth_index[depth].start_of_scope = location; ++ depth_index[depth].scope_type = current_type; ++ current_val = doc.tape[location]; ++ current_type = uint8_t(current_val >> 56); ++ return true; ++ } ++ return false; ++} ++ ++void dom::parser::Iterator::to_start_scope() { ++ location = depth_index[depth].start_of_scope; ++ current_val = doc.tape[location]; ++ current_type = uint8_t(current_val >> 56); ++} ++ ++bool dom::parser::Iterator::next() { ++ size_t npos; ++ if ((current_type == '[') || (current_type == '{')) { ++ // we need to jump ++ npos = uint32_t(current_val); ++ } else { ++ npos = location + (is_number() ? 2 : 1); ++ } ++ uint64_t next_val = doc.tape[npos]; ++ uint8_t next_type = uint8_t(next_val >> 56); ++ if ((next_type == ']') || (next_type == '}')) { ++ return false; // we reached the end of the scope ++ } ++ location = npos; ++ current_val = next_val; ++ current_type = next_type; ++ return true; ++} ++dom::parser::Iterator::Iterator(const dom::parser &pj) noexcept(false) ++ : doc(pj.doc) ++{ ++#if SIMDJSON_EXCEPTIONS ++ if (!pj.valid) { throw simdjson_error(pj.error); } ++#else ++ if (!pj.valid) { return; } // abort() usage is forbidden in the library ++#endif ++ ++ max_depth = pj.max_depth(); ++ depth_index = new scopeindex_t[max_depth + 1]; ++ depth_index[0].start_of_scope = location; ++ current_val = doc.tape[location++]; ++ current_type = uint8_t(current_val >> 56); ++ depth_index[0].scope_type = current_type; ++ tape_length = size_t(current_val & internal::JSON_VALUE_MASK); ++ if (location < tape_length) { ++ // If we make it here, then depth_capacity must >=2, but the compiler ++ // may not know this. ++ current_val = doc.tape[location]; ++ current_type = uint8_t(current_val >> 56); ++ depth++; ++ assert(depth < max_depth); ++ depth_index[depth].start_of_scope = location; ++ depth_index[depth].scope_type = current_type; ++ } ++} ++dom::parser::Iterator::Iterator( ++ const dom::parser::Iterator &o) noexcept ++ : doc(o.doc), ++ max_depth(o.depth), ++ depth(o.depth), ++ location(o.location), ++ tape_length(o.tape_length), ++ current_type(o.current_type), ++ current_val(o.current_val) ++{ ++ depth_index = new scopeindex_t[max_depth+1]; ++ std::memcpy(depth_index, o.depth_index, (depth + 1) * sizeof(depth_index[0])); ++} ++ ++dom::parser::Iterator::~Iterator() noexcept { ++ if (depth_index) { delete[] depth_index; } ++} ++ ++bool dom::parser::Iterator::print(std::ostream &os, bool escape_strings) const { ++ if (!is_ok()) { ++ return false; ++ } ++ switch (current_type) { ++ case '"': // we have a string ++ os << '"'; ++ if (escape_strings) { ++ os << internal::escape_json_string(std::string_view(get_string(), get_string_length())); ++ } else { ++ // was: os << get_string();, but given that we can include null chars, we ++ // have to do something crazier: ++ std::copy(get_string(), get_string() + get_string_length(), std::ostream_iterator(os)); ++ } ++ os << '"'; ++ break; ++ case 'l': // we have a long int ++ os << get_integer(); ++ break; ++ case 'u': ++ os << get_unsigned_integer(); ++ break; ++ case 'd': ++ os << get_double(); ++ break; ++ case 'n': // we have a null ++ os << "null"; ++ break; ++ case 't': // we have a true ++ os << "true"; ++ break; ++ case 'f': // we have a false ++ os << "false"; ++ break; ++ case '{': // we have an object ++ case '}': // we end an object ++ case '[': // we start an array ++ case ']': // we end an array ++ os << char(current_type); ++ break; ++ default: ++ return false; ++ } ++ return true; ++} ++ ++bool dom::parser::Iterator::move_to(const char *pointer, ++ uint32_t length) { ++ char *new_pointer = nullptr; ++ if (pointer[0] == '#') { ++ // Converting fragment representation to string representation ++ new_pointer = new char[length]; ++ uint32_t new_length = 0; ++ for (uint32_t i = 1; i < length; i++) { ++ if (pointer[i] == '%' && pointer[i + 1] == 'x') { ++#if __cpp_exceptions ++ try { ++#endif ++ int fragment = ++ std::stoi(std::string(&pointer[i + 2], 2), nullptr, 16); ++ if (fragment == '\\' || fragment == '"' || (fragment <= 0x1F)) { ++ // escaping the character ++ new_pointer[new_length] = '\\'; ++ new_length++; ++ } ++ new_pointer[new_length] = char(fragment); ++ i += 3; ++#if __cpp_exceptions ++ } catch (std::invalid_argument &) { ++ delete[] new_pointer; ++ return false; // the fragment is invalid ++ } ++#endif ++ } else { ++ new_pointer[new_length] = pointer[i]; ++ } ++ new_length++; ++ } ++ length = new_length; ++ pointer = new_pointer; ++ } ++ ++ // saving the current state ++ size_t depth_s = depth; ++ size_t location_s = location; ++ uint8_t current_type_s = current_type; ++ uint64_t current_val_s = current_val; ++ ++ rewind(); // The json pointer is used from the root of the document. ++ ++ bool found = relative_move_to(pointer, length); ++ delete[] new_pointer; ++ ++ if (!found) { ++ // since the pointer has found nothing, we get back to the original ++ // position. ++ depth = depth_s; ++ location = location_s; ++ current_type = current_type_s; ++ current_val = current_val_s; ++ } ++ ++ return found; ++} ++ ++bool dom::parser::Iterator::relative_move_to(const char *pointer, ++ uint32_t length) { ++ if (length == 0) { ++ // returns the whole document ++ return true; ++ } ++ ++ if (pointer[0] != '/') { ++ // '/' must be the first character ++ return false; ++ } ++ ++ // finding the key in an object or the index in an array ++ std::string key_or_index; ++ uint32_t offset = 1; ++ ++ // checking for the "-" case ++ if (is_array() && pointer[1] == '-') { ++ if (length != 2) { ++ // the pointer must be exactly "/-" ++ // there can't be anything more after '-' as an index ++ return false; ++ } ++ key_or_index = '-'; ++ offset = length; // will skip the loop coming right after ++ } ++ ++ // We either transform the first reference token to a valid json key ++ // or we make sure it is a valid index in an array. ++ for (; offset < length; offset++) { ++ if (pointer[offset] == '/') { ++ // beginning of the next key or index ++ break; ++ } ++ if (is_array() && (pointer[offset] < '0' || pointer[offset] > '9')) { ++ // the index of an array must be an integer ++ // we also make sure std::stoi won't discard whitespaces later ++ return false; ++ } ++ if (pointer[offset] == '~') { ++ // "~1" represents "/" ++ if (pointer[offset + 1] == '1') { ++ key_or_index += '/'; ++ offset++; ++ continue; ++ } ++ // "~0" represents "~" ++ if (pointer[offset + 1] == '0') { ++ key_or_index += '~'; ++ offset++; ++ continue; ++ } ++ } ++ if (pointer[offset] == '\\') { ++ if (pointer[offset + 1] == '\\' || pointer[offset + 1] == '"' || ++ (pointer[offset + 1] <= 0x1F)) { ++ key_or_index += pointer[offset + 1]; ++ offset++; ++ continue; ++ } ++ return false; // invalid escaped character ++ } ++ if (pointer[offset] == '\"') { ++ // unescaped quote character. this is an invalid case. ++ // lets do nothing and assume most pointers will be valid. ++ // it won't find any corresponding json key anyway. ++ // return false; ++ } ++ key_or_index += pointer[offset]; ++ } ++ ++ bool found = false; ++ if (is_object()) { ++ if (move_to_key(key_or_index.c_str(), uint32_t(key_or_index.length()))) { ++ found = relative_move_to(pointer + offset, length - offset); ++ } ++ } else if (is_array()) { ++ if (key_or_index == "-") { // handling "-" case first ++ if (down()) { ++ while (next()) ++ ; // moving to the end of the array ++ // moving to the nonexistent value right after... ++ size_t npos; ++ if ((current_type == '[') || (current_type == '{')) { ++ // we need to jump ++ npos = uint32_t(current_val); ++ } else { ++ npos = ++ location + ((current_type == 'd' || current_type == 'l') ? 2 : 1); ++ } ++ location = npos; ++ current_val = doc.tape[npos]; ++ current_type = uint8_t(current_val >> 56); ++ return true; // how could it fail ? ++ } ++ } else { // regular numeric index ++ // The index can't have a leading '0' ++ if (key_or_index[0] == '0' && key_or_index.length() > 1) { ++ return false; ++ } ++ // it cannot be empty ++ if (key_or_index.length() == 0) { ++ return false; ++ } ++ // we already checked the index contains only valid digits ++ uint32_t index = std::stoi(key_or_index); ++ if (move_to_index(index)) { ++ found = relative_move_to(pointer + offset, length - offset); ++ } ++ } ++ } ++ ++ return found; ++} ++ ++SIMDJSON_POP_DISABLE_WARNINGS ++} // namespace simdjson ++ ++#endif // SIMDJSON_DISABLE_DEPRECATED_API ++ ++ ++#endif // SIMDJSON_INLINE_PARSEDJSON_ITERATOR_H ++/* end file include/simdjson/dom/parsedjson_iterator-inl.h */ ++/* begin file include/simdjson/dom/parser-inl.h */ ++#ifndef SIMDJSON_INLINE_PARSER_H ++#define SIMDJSON_INLINE_PARSER_H ++ ++#include ++#include ++ ++namespace simdjson { ++namespace dom { ++ ++// ++// parser inline implementation ++// ++simdjson_inline parser::parser(size_t max_capacity) noexcept ++ : _max_capacity{max_capacity}, ++ loaded_bytes(nullptr) { ++} ++simdjson_inline parser::parser(parser &&other) noexcept = default; ++simdjson_inline parser &parser::operator=(parser &&other) noexcept = default; ++ ++inline bool parser::is_valid() const noexcept { return valid; } ++inline int parser::get_error_code() const noexcept { return error; } ++inline std::string parser::get_error_message() const noexcept { return error_message(error); } ++ ++inline bool parser::dump_raw_tape(std::ostream &os) const noexcept { ++ return valid ? doc.dump_raw_tape(os) : false; ++} ++ ++inline simdjson_result parser::read_file(const std::string &path) noexcept { ++ // Open the file ++ SIMDJSON_PUSH_DISABLE_WARNINGS ++ SIMDJSON_DISABLE_DEPRECATED_WARNING // Disable CRT_SECURE warning on MSVC: manually verified this is safe ++ std::FILE *fp = std::fopen(path.c_str(), "rb"); ++ SIMDJSON_POP_DISABLE_WARNINGS ++ ++ if (fp == nullptr) { ++ return IO_ERROR; ++ } ++ ++ // Get the file size ++ int ret; ++#if SIMDJSON_VISUAL_STUDIO && !SIMDJSON_IS_32BITS ++ ret = _fseeki64(fp, 0, SEEK_END); ++#else ++ ret = std::fseek(fp, 0, SEEK_END); ++#endif // _WIN64 ++ if(ret < 0) { ++ std::fclose(fp); ++ return IO_ERROR; ++ } ++#if SIMDJSON_VISUAL_STUDIO && !SIMDJSON_IS_32BITS ++ __int64 len = _ftelli64(fp); ++ if(len == -1L) { ++ std::fclose(fp); ++ return IO_ERROR; ++ } ++#else ++ long len = std::ftell(fp); ++ if((len < 0) || (len == LONG_MAX)) { ++ std::fclose(fp); ++ return IO_ERROR; ++ } ++#endif ++ ++ // Make sure we have enough capacity to load the file ++ if (_loaded_bytes_capacity < size_t(len)) { ++ loaded_bytes.reset( internal::allocate_padded_buffer(len) ); ++ if (!loaded_bytes) { ++ std::fclose(fp); ++ return MEMALLOC; ++ } ++ _loaded_bytes_capacity = len; ++ } ++ ++ // Read the string ++ std::rewind(fp); ++ size_t bytes_read = std::fread(loaded_bytes.get(), 1, len, fp); ++ if (std::fclose(fp) != 0 || bytes_read != size_t(len)) { ++ return IO_ERROR; ++ } ++ ++ return bytes_read; ++} ++ ++inline simdjson_result parser::load(const std::string &path) & noexcept { ++ size_t len; ++ auto _error = read_file(path).get(len); ++ if (_error) { return _error; } ++ return parse(loaded_bytes.get(), len, false); ++} ++ ++inline simdjson_result parser::load_many(const std::string &path, size_t batch_size) noexcept { ++ size_t len; ++ auto _error = read_file(path).get(len); ++ if (_error) { return _error; } ++ if(batch_size < MINIMAL_BATCH_SIZE) { batch_size = MINIMAL_BATCH_SIZE; } ++ return document_stream(*this, reinterpret_cast(loaded_bytes.get()), len, batch_size); ++} ++ ++inline simdjson_result parser::parse_into_document(document& provided_doc, const uint8_t *buf, size_t len, bool realloc_if_needed) & noexcept { ++ // Important: we need to ensure that document has enough capacity. ++ // Important: It is possible that provided_doc is actually the internal 'doc' within the parser!!! ++ error_code _error = ensure_capacity(provided_doc, len); ++ if (_error) { return _error; } ++ if (realloc_if_needed) { ++ // Make sure we have enough capacity to copy len bytes ++ if (!loaded_bytes || _loaded_bytes_capacity < len) { ++ loaded_bytes.reset( internal::allocate_padded_buffer(len) ); ++ if (!loaded_bytes) { ++ return MEMALLOC; ++ } ++ _loaded_bytes_capacity = len; ++ } ++ std::memcpy(static_cast(loaded_bytes.get()), buf, len); ++ } ++ _error = implementation->parse(realloc_if_needed ? reinterpret_cast(loaded_bytes.get()): buf, len, provided_doc); ++ ++ if (_error) { return _error; } ++ ++ return provided_doc.root(); ++} ++ ++simdjson_inline simdjson_result parser::parse_into_document(document& provided_doc, const char *buf, size_t len, bool realloc_if_needed) & noexcept { ++ return parse_into_document(provided_doc, reinterpret_cast(buf), len, realloc_if_needed); ++} ++simdjson_inline simdjson_result parser::parse_into_document(document& provided_doc, const std::string &s) & noexcept { ++ return parse_into_document(provided_doc, s.data(), s.length(), s.capacity() - s.length() < SIMDJSON_PADDING); ++} ++simdjson_inline simdjson_result parser::parse_into_document(document& provided_doc, const padded_string &s) & noexcept { ++ return parse_into_document(provided_doc, s.data(), s.length(), false); ++} ++ ++ ++inline simdjson_result parser::parse(const uint8_t *buf, size_t len, bool realloc_if_needed) & noexcept { ++ return parse_into_document(doc, buf, len, realloc_if_needed); ++} ++ ++simdjson_inline simdjson_result parser::parse(const char *buf, size_t len, bool realloc_if_needed) & noexcept { ++ return parse(reinterpret_cast(buf), len, realloc_if_needed); ++} ++simdjson_inline simdjson_result parser::parse(const std::string &s) & noexcept { ++ return parse(s.data(), s.length(), s.capacity() - s.length() < SIMDJSON_PADDING); ++} ++simdjson_inline simdjson_result parser::parse(const padded_string &s) & noexcept { ++ return parse(s.data(), s.length(), false); ++} ++simdjson_inline simdjson_result parser::parse(const padded_string_view &v) & noexcept { ++ return parse(v.data(), v.length(), false); ++} ++ ++inline simdjson_result parser::parse_many(const uint8_t *buf, size_t len, size_t batch_size) noexcept { ++ if(batch_size < MINIMAL_BATCH_SIZE) { batch_size = MINIMAL_BATCH_SIZE; } ++ return document_stream(*this, buf, len, batch_size); ++} ++inline simdjson_result parser::parse_many(const char *buf, size_t len, size_t batch_size) noexcept { ++ return parse_many(reinterpret_cast(buf), len, batch_size); ++} ++inline simdjson_result parser::parse_many(const std::string &s, size_t batch_size) noexcept { ++ return parse_many(s.data(), s.length(), batch_size); ++} ++inline simdjson_result parser::parse_many(const padded_string &s, size_t batch_size) noexcept { ++ return parse_many(s.data(), s.length(), batch_size); ++} ++ ++simdjson_inline size_t parser::capacity() const noexcept { ++ return implementation ? implementation->capacity() : 0; ++} ++simdjson_inline size_t parser::max_capacity() const noexcept { ++ return _max_capacity; ++} ++simdjson_inline size_t parser::max_depth() const noexcept { ++ return implementation ? implementation->max_depth() : DEFAULT_MAX_DEPTH; ++} ++ ++simdjson_warn_unused ++inline error_code parser::allocate(size_t capacity, size_t max_depth) noexcept { ++ // ++ // Reallocate implementation if needed ++ // ++ error_code err; ++ if (implementation) { ++ err = implementation->allocate(capacity, max_depth); ++ } else { ++ err = simdjson::get_active_implementation()->create_dom_parser_implementation(capacity, max_depth, implementation); ++ } ++ if (err) { return err; } ++ return SUCCESS; ++} ++ ++#ifndef SIMDJSON_DISABLE_DEPRECATED_API ++simdjson_warn_unused ++inline bool parser::allocate_capacity(size_t capacity, size_t max_depth) noexcept { ++ return !allocate(capacity, max_depth); ++} ++#endif // SIMDJSON_DISABLE_DEPRECATED_API ++ ++inline error_code parser::ensure_capacity(size_t desired_capacity) noexcept { ++ return ensure_capacity(doc, desired_capacity); ++} ++ ++ ++inline error_code parser::ensure_capacity(document& target_document, size_t desired_capacity) noexcept { ++ // 1. It is wasteful to allocate a document and a parser for documents spanning less than MINIMAL_DOCUMENT_CAPACITY bytes. ++ // 2. If we allow desired_capacity = 0 then it is possible to exit this function with implementation == nullptr. ++ if(desired_capacity < MINIMAL_DOCUMENT_CAPACITY) { desired_capacity = MINIMAL_DOCUMENT_CAPACITY; } ++ // If we don't have enough capacity, (try to) automatically bump it. ++ // If the document needs allocation, do it too. ++ // Both in one if statement to minimize unlikely branching. ++ // ++ // Note: we must make sure that this function is called if capacity() == 0. We do so because we ++ // ensure that desired_capacity > 0. ++ if (simdjson_unlikely(capacity() < desired_capacity || target_document.capacity() < desired_capacity)) { ++ if (desired_capacity > max_capacity()) { ++ return error = CAPACITY; ++ } ++ error_code err1 = target_document.capacity() < desired_capacity ? target_document.allocate(desired_capacity) : SUCCESS; ++ error_code err2 = capacity() < desired_capacity ? allocate(desired_capacity, max_depth()) : SUCCESS; ++ if(err1 != SUCCESS) { return error = err1; } ++ if(err2 != SUCCESS) { return error = err2; } ++ } ++ return SUCCESS; ++} ++ ++simdjson_inline void parser::set_max_capacity(size_t max_capacity) noexcept { ++ if(max_capacity < MINIMAL_DOCUMENT_CAPACITY) { ++ _max_capacity = max_capacity; ++ } else { ++ _max_capacity = MINIMAL_DOCUMENT_CAPACITY; ++ } ++} ++ ++} // namespace dom ++} // namespace simdjson ++ ++#endif // SIMDJSON_INLINE_PARSER_H ++/* end file include/simdjson/dom/parser-inl.h */ ++/* begin file include/simdjson/internal/tape_ref-inl.h */ ++#ifndef SIMDJSON_INLINE_TAPE_REF_H ++#define SIMDJSON_INLINE_TAPE_REF_H ++ ++#include ++ ++namespace simdjson { ++namespace internal { ++ ++// ++// tape_ref inline implementation ++// ++simdjson_inline tape_ref::tape_ref() noexcept : doc{nullptr}, json_index{0} {} ++simdjson_inline tape_ref::tape_ref(const dom::document *_doc, size_t _json_index) noexcept : doc{_doc}, json_index{_json_index} {} ++ ++ ++simdjson_inline bool tape_ref::is_document_root() const noexcept { ++ return json_index == 1; // should we ever change the structure of the tape, this should get updated. ++} ++simdjson_inline bool tape_ref::usable() const noexcept { ++ return doc != nullptr; // when the document pointer is null, this tape_ref is uninitialized (should not be accessed). ++} ++// Some value types have a specific on-tape word value. It can be faster ++// to check the type by doing a word-to-word comparison instead of extracting the ++// most significant 8 bits. ++ ++simdjson_inline bool tape_ref::is_double() const noexcept { ++ constexpr uint64_t tape_double = uint64_t(tape_type::DOUBLE)<<56; ++ return doc->tape[json_index] == tape_double; ++} ++simdjson_inline bool tape_ref::is_int64() const noexcept { ++ constexpr uint64_t tape_int64 = uint64_t(tape_type::INT64)<<56; ++ return doc->tape[json_index] == tape_int64; ++} ++simdjson_inline bool tape_ref::is_uint64() const noexcept { ++ constexpr uint64_t tape_uint64 = uint64_t(tape_type::UINT64)<<56; ++ return doc->tape[json_index] == tape_uint64; ++} ++simdjson_inline bool tape_ref::is_false() const noexcept { ++ constexpr uint64_t tape_false = uint64_t(tape_type::FALSE_VALUE)<<56; ++ return doc->tape[json_index] == tape_false; ++} ++simdjson_inline bool tape_ref::is_true() const noexcept { ++ constexpr uint64_t tape_true = uint64_t(tape_type::TRUE_VALUE)<<56; ++ return doc->tape[json_index] == tape_true; ++} ++simdjson_inline bool tape_ref::is_null_on_tape() const noexcept { ++ constexpr uint64_t tape_null = uint64_t(tape_type::NULL_VALUE)<<56; ++ return doc->tape[json_index] == tape_null; ++} ++ ++inline size_t tape_ref::after_element() const noexcept { ++ switch (tape_ref_type()) { ++ case tape_type::START_ARRAY: ++ case tape_type::START_OBJECT: ++ return matching_brace_index(); ++ case tape_type::UINT64: ++ case tape_type::INT64: ++ case tape_type::DOUBLE: ++ return json_index + 2; ++ default: ++ return json_index + 1; ++ } ++} ++simdjson_inline tape_type tape_ref::tape_ref_type() const noexcept { ++ return static_cast(doc->tape[json_index] >> 56); ++} ++simdjson_inline uint64_t internal::tape_ref::tape_value() const noexcept { ++ return doc->tape[json_index] & internal::JSON_VALUE_MASK; ++} ++simdjson_inline uint32_t internal::tape_ref::matching_brace_index() const noexcept { ++ return uint32_t(doc->tape[json_index]); ++} ++simdjson_inline uint32_t internal::tape_ref::scope_count() const noexcept { ++ return uint32_t((doc->tape[json_index] >> 32) & internal::JSON_COUNT_MASK); ++} ++ ++template ++simdjson_inline T tape_ref::next_tape_value() const noexcept { ++ static_assert(sizeof(T) == sizeof(uint64_t), "next_tape_value() template parameter must be 64-bit"); ++ // Though the following is tempting... ++ // return *reinterpret_cast(&doc->tape[json_index + 1]); ++ // It is not generally safe. It is safer, and often faster to rely ++ // on memcpy. Yes, it is uglier, but it is also encapsulated. ++ T x; ++ std::memcpy(&x,&doc->tape[json_index + 1],sizeof(uint64_t)); ++ return x; ++} ++ ++simdjson_inline uint32_t internal::tape_ref::get_string_length() const noexcept { ++ size_t string_buf_index = size_t(tape_value()); ++ uint32_t len; ++ std::memcpy(&len, &doc->string_buf[string_buf_index], sizeof(len)); ++ return len; ++} ++ ++simdjson_inline const char * internal::tape_ref::get_c_str() const noexcept { ++ size_t string_buf_index = size_t(tape_value()); ++ return reinterpret_cast(&doc->string_buf[string_buf_index + sizeof(uint32_t)]); ++} ++ ++inline std::string_view internal::tape_ref::get_string_view() const noexcept { ++ return std::string_view( ++ get_c_str(), ++ get_string_length() ++ ); ++} ++ ++} // namespace internal ++} // namespace simdjson ++ ++#endif // SIMDJSON_INLINE_TAPE_REF_H ++/* end file include/simdjson/internal/tape_ref-inl.h */ ++/* begin file include/simdjson/dom/serialization-inl.h */ ++ ++#ifndef SIMDJSON_SERIALIZATION_INL_H ++#define SIMDJSON_SERIALIZATION_INL_H ++ ++ ++#include ++#include ++ ++namespace simdjson { ++namespace dom { ++inline bool parser::print_json(std::ostream &os) const noexcept { ++ if (!valid) { return false; } ++ simdjson::internal::string_builder<> sb; ++ sb.append(doc.root()); ++ std::string_view answer = sb.str(); ++ os << answer; ++ return true; ++} ++} ++/*** ++ * Number utility functions ++ **/ ++ ++ ++namespace { ++/**@private ++ * Escape sequence like \b or \u0001 ++ * We expect that most compilers will use 8 bytes for this data structure. ++ **/ ++struct escape_sequence { ++ uint8_t length; ++ const char string[7]; // technically, we only ever need 6 characters, we pad to 8 ++}; ++/**@private ++ * This converts a signed integer into a character sequence. ++ * The caller is responsible for providing enough memory (at least ++ * 20 characters.) ++ * Though various runtime libraries provide itoa functions, ++ * it is not part of the C++ standard. The C++17 standard ++ * adds the to_chars functions which would do as well, but ++ * we want to support C++11. ++ */ ++char *fast_itoa(char *output, int64_t value) noexcept { ++ // This is a standard implementation of itoa. ++ char buffer[20]; ++ uint64_t value_positive; ++ // In general, negating a signed integer is unsafe. ++ if(value < 0) { ++ *output++ = '-'; ++ // Doing value_positive = -value; while avoiding ++ // undefined behavior warnings. ++ // It assumes two complement's which is universal at this ++ // point in time. ++ std::memcpy(&value_positive, &value, sizeof(value)); ++ value_positive = (~value_positive) + 1; // this is a negation ++ } else { ++ value_positive = value; ++ } ++ // We work solely with value_positive. It *might* be easier ++ // for an optimizing compiler to deal with an unsigned variable ++ // as far as performance goes. ++ const char *const end_buffer = buffer + 20; ++ char *write_pointer = buffer + 19; ++ // A faster approach is possible if we expect large integers: ++ // unroll the loop (work in 100s, 1000s) and use some kind of ++ // memoization. ++ while(value_positive >= 10) { ++ *write_pointer-- = char('0' + (value_positive % 10)); ++ value_positive /= 10; ++ } ++ *write_pointer = char('0' + value_positive); ++ size_t len = end_buffer - write_pointer; ++ std::memcpy(output, write_pointer, len); ++ return output + len; ++} ++/**@private ++ * This converts an unsigned integer into a character sequence. ++ * The caller is responsible for providing enough memory (at least ++ * 19 characters.) ++ * Though various runtime libraries provide itoa functions, ++ * it is not part of the C++ standard. The C++17 standard ++ * adds the to_chars functions which would do as well, but ++ * we want to support C++11. ++ */ ++char *fast_itoa(char *output, uint64_t value) noexcept { ++ // This is a standard implementation of itoa. ++ char buffer[20]; ++ const char *const end_buffer = buffer + 20; ++ char *write_pointer = buffer + 19; ++ // A faster approach is possible if we expect large integers: ++ // unroll the loop (work in 100s, 1000s) and use some kind of ++ // memoization. ++ while(value >= 10) { ++ *write_pointer-- = char('0' + (value % 10)); ++ value /= 10; ++ }; ++ *write_pointer = char('0' + value); ++ size_t len = end_buffer - write_pointer; ++ std::memcpy(output, write_pointer, len); ++ return output + len; ++} ++} // anonymous namespace ++namespace internal { ++ ++/*** ++ * Minifier/formatter code. ++ **/ ++ ++simdjson_inline void mini_formatter::number(uint64_t x) { ++ char number_buffer[24]; ++ char *newp = fast_itoa(number_buffer, x); ++ buffer.insert(buffer.end(), number_buffer, newp); ++} ++ ++simdjson_inline void mini_formatter::number(int64_t x) { ++ char number_buffer[24]; ++ char *newp = fast_itoa(number_buffer, x); ++ buffer.insert(buffer.end(), number_buffer, newp); ++} ++ ++simdjson_inline void mini_formatter::number(double x) { ++ char number_buffer[24]; ++ // Currently, passing the nullptr to the second argument is ++ // safe because our implementation does not check the second ++ // argument. ++ char *newp = internal::to_chars(number_buffer, nullptr, x); ++ buffer.insert(buffer.end(), number_buffer, newp); ++} ++ ++simdjson_inline void mini_formatter::start_array() { one_char('['); } ++simdjson_inline void mini_formatter::end_array() { one_char(']'); } ++simdjson_inline void mini_formatter::start_object() { one_char('{'); } ++simdjson_inline void mini_formatter::end_object() { one_char('}'); } ++simdjson_inline void mini_formatter::comma() { one_char(','); } ++ ++ ++simdjson_inline void mini_formatter::true_atom() { ++ const char * s = "true"; ++ buffer.insert(buffer.end(), s, s + 4); ++} ++simdjson_inline void mini_formatter::false_atom() { ++ const char * s = "false"; ++ buffer.insert(buffer.end(), s, s + 5); ++} ++simdjson_inline void mini_formatter::null_atom() { ++ const char * s = "null"; ++ buffer.insert(buffer.end(), s, s + 4); ++} ++simdjson_inline void mini_formatter::one_char(char c) { buffer.push_back(c); } ++simdjson_inline void mini_formatter::key(std::string_view unescaped) { ++ string(unescaped); ++ one_char(':'); ++} ++simdjson_inline void mini_formatter::string(std::string_view unescaped) { ++ one_char('\"'); ++ size_t i = 0; ++ // Fast path for the case where we have no control character, no ", and no backslash. ++ // This should include most keys. ++ // ++ // We would like to use 'bool' but some compilers take offense to bitwise operation ++ // with bool types. ++ constexpr static char needs_escaping[] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, ++ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, ++ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ++ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, ++ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ++ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ++ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ++ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ++ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ++ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; ++ for(;i + 8 <= unescaped.length(); i += 8) { ++ // Poor's man vectorization. This could get much faster if we used SIMD. ++ // ++ // It is not the case that replacing '|' with '||' would be neutral performance-wise. ++ if(needs_escaping[uint8_t(unescaped[i])] | needs_escaping[uint8_t(unescaped[i+1])] ++ | needs_escaping[uint8_t(unescaped[i+2])] | needs_escaping[uint8_t(unescaped[i+3])] ++ | needs_escaping[uint8_t(unescaped[i+4])] | needs_escaping[uint8_t(unescaped[i+5])] ++ | needs_escaping[uint8_t(unescaped[i+6])] | needs_escaping[uint8_t(unescaped[i+7])] ++ ) { break; } ++ } ++ for(;i < unescaped.length(); i++) { ++ if(needs_escaping[uint8_t(unescaped[i])]) { break; } ++ } ++ // The following is also possible and omits a 256-byte table, but it is slower: ++ // for (; (i < unescaped.length()) && (uint8_t(unescaped[i]) > 0x1F) ++ // && (unescaped[i] != '\"') && (unescaped[i] != '\\'); i++) {} ++ ++ // At least for long strings, the following should be fast. We could ++ // do better by integrating the checks and the insertion. ++ buffer.insert(buffer.end(), unescaped.data(), unescaped.data() + i); ++ // We caught a control character if we enter this loop (slow). ++ // Note that we are do not restart from the beginning, but rather we continue ++ // from the point where we encountered something that requires escaping. ++ for (; i < unescaped.length(); i++) { ++ switch (unescaped[i]) { ++ case '\"': ++ { ++ const char * s = "\\\""; ++ buffer.insert(buffer.end(), s, s + 2); ++ } ++ break; ++ case '\\': ++ { ++ const char * s = "\\\\"; ++ buffer.insert(buffer.end(), s, s + 2); ++ } ++ break; ++ default: ++ if (uint8_t(unescaped[i]) <= 0x1F) { ++ // If packed, this uses 8 * 32 bytes. ++ // Note that we expect most compilers to embed this code in the data ++ // section. ++ constexpr static escape_sequence escaped[32] = { ++ {6, "\\u0000"}, {6, "\\u0001"}, {6, "\\u0002"}, {6, "\\u0003"}, ++ {6, "\\u0004"}, {6, "\\u0005"}, {6, "\\u0006"}, {6, "\\u0007"}, ++ {2, "\\b"}, {2, "\\t"}, {2, "\\n"}, {6, "\\u000b"}, ++ {2, "\\f"}, {2, "\\r"}, {6, "\\u000e"}, {6, "\\u000f"}, ++ {6, "\\u0010"}, {6, "\\u0011"}, {6, "\\u0012"}, {6, "\\u0013"}, ++ {6, "\\u0014"}, {6, "\\u0015"}, {6, "\\u0016"}, {6, "\\u0017"}, ++ {6, "\\u0018"}, {6, "\\u0019"}, {6, "\\u001a"}, {6, "\\u001b"}, ++ {6, "\\u001c"}, {6, "\\u001d"}, {6, "\\u001e"}, {6, "\\u001f"}}; ++ auto u = escaped[uint8_t(unescaped[i])]; ++ buffer.insert(buffer.end(), u.string, u.string + u.length); ++ } else { ++ one_char(unescaped[i]); ++ } ++ } // switch ++ } // for ++ one_char('\"'); ++} ++ ++inline void mini_formatter::clear() { ++ buffer.clear(); ++} ++ ++simdjson_inline std::string_view mini_formatter::str() const { ++ return std::string_view(buffer.data(), buffer.size()); ++} ++ ++ ++/*** ++ * String building code. ++ **/ ++ ++template ++inline void string_builder::append(simdjson::dom::element value) { ++ // using tape_type = simdjson::internal::tape_type; ++ size_t depth = 0; ++ constexpr size_t MAX_DEPTH = 16; ++ bool is_object[MAX_DEPTH]; ++ is_object[0] = false; ++ bool after_value = false; ++ ++ internal::tape_ref iter(value.tape); ++ do { ++ // print commas after each value ++ if (after_value) { ++ format.comma(); ++ } ++ // If we are in an object, print the next key and :, and skip to the next ++ // value. ++ if (is_object[depth]) { ++ format.key(iter.get_string_view()); ++ iter.json_index++; ++ } ++ switch (iter.tape_ref_type()) { ++ ++ // Arrays ++ case tape_type::START_ARRAY: { ++ // If we're too deep, we need to recurse to go deeper. ++ depth++; ++ if (simdjson_unlikely(depth >= MAX_DEPTH)) { ++ append(simdjson::dom::array(iter)); ++ iter.json_index = iter.matching_brace_index() - 1; // Jump to the ] ++ depth--; ++ break; ++ } ++ ++ // Output start [ ++ format.start_array(); ++ iter.json_index++; ++ ++ // Handle empty [] (we don't want to come back around and print commas) ++ if (iter.tape_ref_type() == tape_type::END_ARRAY) { ++ format.end_array(); ++ depth--; ++ break; ++ } ++ ++ is_object[depth] = false; ++ after_value = false; ++ continue; ++ } ++ ++ // Objects ++ case tape_type::START_OBJECT: { ++ // If we're too deep, we need to recurse to go deeper. ++ depth++; ++ if (simdjson_unlikely(depth >= MAX_DEPTH)) { ++ append(simdjson::dom::object(iter)); ++ iter.json_index = iter.matching_brace_index() - 1; // Jump to the } ++ depth--; ++ break; ++ } ++ ++ // Output start { ++ format.start_object(); ++ iter.json_index++; ++ ++ // Handle empty {} (we don't want to come back around and print commas) ++ if (iter.tape_ref_type() == tape_type::END_OBJECT) { ++ format.end_object(); ++ depth--; ++ break; ++ } ++ ++ is_object[depth] = true; ++ after_value = false; ++ continue; ++ } ++ ++ // Scalars ++ case tape_type::STRING: ++ format.string(iter.get_string_view()); ++ break; ++ case tape_type::INT64: ++ format.number(iter.next_tape_value()); ++ iter.json_index++; // numbers take up 2 spots, so we need to increment ++ // extra ++ break; ++ case tape_type::UINT64: ++ format.number(iter.next_tape_value()); ++ iter.json_index++; // numbers take up 2 spots, so we need to increment ++ // extra ++ break; ++ case tape_type::DOUBLE: ++ format.number(iter.next_tape_value()); ++ iter.json_index++; // numbers take up 2 spots, so we need to increment ++ // extra ++ break; ++ case tape_type::TRUE_VALUE: ++ format.true_atom(); ++ break; ++ case tape_type::FALSE_VALUE: ++ format.false_atom(); ++ break; ++ case tape_type::NULL_VALUE: ++ format.null_atom(); ++ break; ++ ++ // These are impossible ++ case tape_type::END_ARRAY: ++ case tape_type::END_OBJECT: ++ case tape_type::ROOT: ++ SIMDJSON_UNREACHABLE(); ++ } ++ iter.json_index++; ++ after_value = true; ++ ++ // Handle multiple ends in a row ++ while (depth != 0 && (iter.tape_ref_type() == tape_type::END_ARRAY || ++ iter.tape_ref_type() == tape_type::END_OBJECT)) { ++ if (iter.tape_ref_type() == tape_type::END_ARRAY) { ++ format.end_array(); ++ } else { ++ format.end_object(); ++ } ++ depth--; ++ iter.json_index++; ++ } ++ ++ // Stop when we're at depth 0 ++ } while (depth != 0); ++} ++ ++template ++inline void string_builder::append(simdjson::dom::object value) { ++ format.start_object(); ++ auto pair = value.begin(); ++ auto end = value.end(); ++ if (pair != end) { ++ append(*pair); ++ for (++pair; pair != end; ++pair) { ++ format.comma(); ++ append(*pair); ++ } ++ } ++ format.end_object(); ++} ++ ++template ++inline void string_builder::append(simdjson::dom::array value) { ++ format.start_array(); ++ auto iter = value.begin(); ++ auto end = value.end(); ++ if (iter != end) { ++ append(*iter); ++ for (++iter; iter != end; ++iter) { ++ format.comma(); ++ append(*iter); ++ } ++ } ++ format.end_array(); ++} ++ ++template ++simdjson_inline void string_builder::append(simdjson::dom::key_value_pair kv) { ++ format.key(kv.key); ++ append(kv.value); ++} ++ ++template ++simdjson_inline void string_builder::clear() { ++ format.clear(); ++} ++ ++template ++simdjson_inline std::string_view string_builder::str() const { ++ return format.str(); ++} ++ ++ ++} // namespace internal ++} // namespace simdjson ++ ++#endif ++/* end file include/simdjson/dom/serialization-inl.h */ ++ ++SIMDJSON_POP_DISABLE_WARNINGS ++ ++#endif // SIMDJSON_DOM_H ++/* end file include/simdjson/dom.h */ ++/* begin file include/simdjson/builtin.h */ ++#ifndef SIMDJSON_BUILTIN_H ++#define SIMDJSON_BUILTIN_H ++ ++/* begin file include/simdjson/implementations.h */ ++#ifndef SIMDJSON_IMPLEMENTATIONS_H ++#define SIMDJSON_IMPLEMENTATIONS_H ++ ++/* begin file include/simdjson/implementation-base.h */ ++#ifndef SIMDJSON_IMPLEMENTATION_BASE_H ++#define SIMDJSON_IMPLEMENTATION_BASE_H ++ ++/** ++ * @file ++ * ++ * Includes common stuff needed for implementations. ++ */ ++ ++ ++// Implementation-internal files (must be included before the implementations themselves, to keep ++// amalgamation working--otherwise, the first time a file is included, it might be put inside the ++// #ifdef SIMDJSON_IMPLEMENTATION_ARM64/FALLBACK/etc., which means the other implementations can't ++// compile unless that implementation is turned on). ++/* begin file include/simdjson/internal/jsoncharutils_tables.h */ ++#ifndef SIMDJSON_INTERNAL_JSONCHARUTILS_TABLES_H ++#define SIMDJSON_INTERNAL_JSONCHARUTILS_TABLES_H ++ ++ ++#ifdef JSON_TEST_STRINGS ++void found_string(const uint8_t *buf, const uint8_t *parsed_begin, ++ const uint8_t *parsed_end); ++void found_bad_string(const uint8_t *buf); ++#endif ++ ++namespace simdjson { ++namespace internal { ++// structural chars here are ++// they are { 0x7b } 0x7d : 0x3a [ 0x5b ] 0x5d , 0x2c (and NULL) ++// we are also interested in the four whitespace characters ++// space 0x20, linefeed 0x0a, horizontal tab 0x09 and carriage return 0x0d ++ ++extern SIMDJSON_DLLIMPORTEXPORT const bool structural_or_whitespace_negated[256]; ++extern SIMDJSON_DLLIMPORTEXPORT const bool structural_or_whitespace[256]; ++extern SIMDJSON_DLLIMPORTEXPORT const uint32_t digit_to_val32[886]; ++ ++} // namespace internal ++} // namespace simdjson ++ ++#endif // SIMDJSON_INTERNAL_JSONCHARUTILS_TABLES_H ++/* end file include/simdjson/internal/jsoncharutils_tables.h */ ++/* begin file include/simdjson/internal/numberparsing_tables.h */ ++#ifndef SIMDJSON_INTERNAL_NUMBERPARSING_TABLES_H ++#define SIMDJSON_INTERNAL_NUMBERPARSING_TABLES_H ++ ++ ++namespace simdjson { ++namespace internal { ++/** ++ * The smallest non-zero float (binary64) is 2^-1074. ++ * We take as input numbers of the form w x 10^q where w < 2^64. ++ * We have that w * 10^-343 < 2^(64-344) 5^-343 < 2^-1076. ++ * However, we have that ++ * (2^64-1) * 10^-342 = (2^64-1) * 2^-342 * 5^-342 > 2^-1074. ++ * Thus it is possible for a number of the form w * 10^-342 where ++ * w is a 64-bit value to be a non-zero floating-point number. ++ ********* ++ * Any number of form w * 10^309 where w>= 1 is going to be ++ * infinite in binary64 so we never need to worry about powers ++ * of 5 greater than 308. ++ */ ++constexpr int smallest_power = -342; ++constexpr int largest_power = 308; ++ ++/** ++ * Represents a 128-bit value. ++ * low: least significant 64 bits. ++ * high: most significant 64 bits. ++ */ ++struct value128 { ++ uint64_t low; ++ uint64_t high; ++}; ++ ++ ++// Precomputed powers of ten from 10^0 to 10^22. These ++// can be represented exactly using the double type. ++extern SIMDJSON_DLLIMPORTEXPORT const double power_of_ten[]; ++ ++ ++/** ++ * When mapping numbers from decimal to binary, ++ * we go from w * 10^q to m * 2^p but we have ++ * 10^q = 5^q * 2^q, so effectively ++ * we are trying to match ++ * w * 2^q * 5^q to m * 2^p. Thus the powers of two ++ * are not a concern since they can be represented ++ * exactly using the binary notation, only the powers of five ++ * affect the binary significand. ++ */ ++ ++ ++// The truncated powers of five from 5^-342 all the way to 5^308 ++// The mantissa is truncated to 128 bits, and ++// never rounded up. Uses about 10KB. ++extern SIMDJSON_DLLIMPORTEXPORT const uint64_t power_of_five_128[]; ++} // namespace internal ++} // namespace simdjson ++ ++#endif // SIMDJSON_INTERNAL_NUMBERPARSING_TABLES_H ++/* end file include/simdjson/internal/numberparsing_tables.h */ ++/* begin file include/simdjson/internal/simdprune_tables.h */ ++#ifndef SIMDJSON_INTERNAL_SIMDPRUNE_TABLES_H ++#define SIMDJSON_INTERNAL_SIMDPRUNE_TABLES_H ++ ++#include ++ ++namespace simdjson { // table modified and copied from ++namespace internal { // http://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetTable ++ ++extern SIMDJSON_DLLIMPORTEXPORT const unsigned char BitsSetTable256mul2[256]; ++ ++extern SIMDJSON_DLLIMPORTEXPORT const uint8_t pshufb_combine_table[272]; ++ ++// 256 * 8 bytes = 2kB, easily fits in cache. ++extern SIMDJSON_DLLIMPORTEXPORT const uint64_t thintable_epi8[256]; ++ ++} // namespace internal ++} // namespace simdjson ++ ++#endif // SIMDJSON_INTERNAL_SIMDPRUNE_TABLES_H ++/* end file include/simdjson/internal/simdprune_tables.h */ ++ ++#endif // SIMDJSON_IMPLEMENTATION_BASE_H ++/* end file include/simdjson/implementation-base.h */ ++ ++// ++// First, figure out which implementations can be run. Doing it here makes it so we don't have to worry about the order ++// in which we include them. ++// ++ ++#ifndef SIMDJSON_IMPLEMENTATION_ARM64 ++#define SIMDJSON_IMPLEMENTATION_ARM64 (SIMDJSON_IS_ARM64) ++#endif ++#define SIMDJSON_CAN_ALWAYS_RUN_ARM64 SIMDJSON_IMPLEMENTATION_ARM64 && SIMDJSON_IS_ARM64 ++ ++#ifdef __has_include ++// How do we detect that a compiler supports vbmi2? ++// For sure if the following header is found, we are ok? ++#if __has_include() ++#define SIMDJSON_COMPILER_SUPPORTS_VBMI2 1 ++#endif ++#endif ++ ++#ifdef _MSC_VER ++#if _MSC_VER >= 1920 ++// Visual Studio 2019 and up support VBMI2 under x64 even if the header ++// avx512vbmi2intrin.h is not found. ++#define SIMDJSON_COMPILER_SUPPORTS_VBMI2 1 ++#endif ++#endif ++ ++// By default, we allow AVX512. ++#ifndef SIMDJSON_AVX512_ALLOWED ++#define SIMDJSON_AVX512_ALLOWED 1 ++#endif ++ ++// Default Icelake to on if this is x86-64. Even if we're not compiled for it, it could be selected ++// at runtime. ++#ifndef SIMDJSON_IMPLEMENTATION_ICELAKE ++#define SIMDJSON_IMPLEMENTATION_ICELAKE ((SIMDJSON_IS_X86_64) && (SIMDJSON_AVX512_ALLOWED) && (SIMDJSON_COMPILER_SUPPORTS_VBMI2)) ++#endif ++ ++#ifdef _MSC_VER ++// To see why (__BMI__) && (__PCLMUL__) && (__LZCNT__) are not part of this next line, see ++// https://github.com/simdjson/simdjson/issues/1247 ++#define SIMDJSON_CAN_ALWAYS_RUN_ICELAKE ((SIMDJSON_IMPLEMENTATION_ICELAKE) && (__AVX2__) && (__AVX512F__) && (__AVX512DQ__) && (__AVX512CD__) && (__AVX512BW__) && (__AVX512VL__) && (__AVX512VBMI2__)) ++#else ++#define SIMDJSON_CAN_ALWAYS_RUN_ICELAKE ((SIMDJSON_IMPLEMENTATION_ICELAKE) && (__AVX2__) && (__BMI__) && (__PCLMUL__) && (__LZCNT__) && (__AVX512F__) && (__AVX512DQ__) && (__AVX512CD__) && (__AVX512BW__) && (__AVX512VL__) && (__AVX512VBMI2__)) ++#endif ++ ++// Default Haswell to on if this is x86-64. Even if we're not compiled for it, it could be selected ++// at runtime. ++#ifndef SIMDJSON_IMPLEMENTATION_HASWELL ++#if SIMDJSON_CAN_ALWAYS_RUN_ICELAKE ++// if icelake is always available, never enable haswell. ++#define SIMDJSON_IMPLEMENTATION_HASWELL 0 ++#else ++#define SIMDJSON_IMPLEMENTATION_HASWELL SIMDJSON_IS_X86_64 ++#endif ++#endif ++#ifdef _MSC_VER ++// To see why (__BMI__) && (__PCLMUL__) && (__LZCNT__) are not part of this next line, see ++// https://github.com/simdjson/simdjson/issues/1247 ++#define SIMDJSON_CAN_ALWAYS_RUN_HASWELL ((SIMDJSON_IMPLEMENTATION_HASWELL) && (SIMDJSON_IS_X86_64) && (__AVX2__)) ++#else ++#define SIMDJSON_CAN_ALWAYS_RUN_HASWELL ((SIMDJSON_IMPLEMENTATION_HASWELL) && (SIMDJSON_IS_X86_64) && (__AVX2__) && (__BMI__) && (__PCLMUL__) && (__LZCNT__)) ++#endif ++ ++// Default Westmere to on if this is x86-64. ++#ifndef SIMDJSON_IMPLEMENTATION_WESTMERE ++#if SIMDJSON_CAN_ALWAYS_RUN_ICELAKE || SIMDJSON_CAN_ALWAYS_RUN_HASWELL ++// if icelake or haswell are always available, never enable westmere. ++#define SIMDJSON_IMPLEMENTATION_WESTMERE 0 ++#else ++#define SIMDJSON_IMPLEMENTATION_WESTMERE SIMDJSON_IS_X86_64 ++#endif ++#endif ++#define SIMDJSON_CAN_ALWAYS_RUN_WESTMERE (SIMDJSON_IMPLEMENTATION_WESTMERE && SIMDJSON_IS_X86_64 && __SSE4_2__ && __PCLMUL__) ++ ++#ifndef SIMDJSON_IMPLEMENTATION_PPC64 ++#define SIMDJSON_IMPLEMENTATION_PPC64 (SIMDJSON_IS_PPC64) ++#endif ++#define SIMDJSON_CAN_ALWAYS_RUN_PPC64 SIMDJSON_IMPLEMENTATION_PPC64 && SIMDJSON_IS_PPC64 ++ ++// Default Fallback to on unless a builtin implementation has already been selected. ++#ifndef SIMDJSON_IMPLEMENTATION_FALLBACK ++#if SIMDJSON_CAN_ALWAYS_RUN_ARM64 || SIMDJSON_CAN_ALWAYS_RUN_ICELAKE || SIMDJSON_CAN_ALWAYS_RUN_HASWELL || SIMDJSON_CAN_ALWAYS_RUN_WESTMERE || SIMDJSON_CAN_ALWAYS_RUN_PPC64 ++// if anything at all except fallback can always run, then disable fallback. ++#define SIMDJSON_IMPLEMENTATION_FALLBACK 0 ++#else ++#define SIMDJSON_IMPLEMENTATION_FALLBACK 1 ++#endif ++#endif ++#define SIMDJSON_CAN_ALWAYS_RUN_FALLBACK SIMDJSON_IMPLEMENTATION_FALLBACK ++ ++SIMDJSON_PUSH_DISABLE_WARNINGS ++SIMDJSON_DISABLE_UNDESIRED_WARNINGS ++ ++// Implementations ++/* begin file include/simdjson/arm64.h */ ++#ifndef SIMDJSON_ARM64_H ++#define SIMDJSON_ARM64_H ++ ++ ++#if SIMDJSON_IMPLEMENTATION_ARM64 ++ ++namespace simdjson { ++/** ++ * Implementation for NEON (ARMv8). ++ */ ++namespace arm64 { ++} // namespace arm64 ++} // namespace simdjson ++ ++/* begin file include/simdjson/arm64/implementation.h */ ++#ifndef SIMDJSON_ARM64_IMPLEMENTATION_H ++#define SIMDJSON_ARM64_IMPLEMENTATION_H ++ ++ ++namespace simdjson { ++namespace arm64 { ++ ++namespace { ++using namespace simdjson; ++using namespace simdjson::dom; ++} ++ ++class implementation final : public simdjson::implementation { ++public: ++ simdjson_inline implementation() : simdjson::implementation("arm64", "ARM NEON", internal::instruction_set::NEON) {} ++ simdjson_warn_unused error_code create_dom_parser_implementation( ++ size_t capacity, ++ size_t max_length, ++ std::unique_ptr& dst ++ ) const noexcept final; ++ simdjson_warn_unused error_code minify(const uint8_t *buf, size_t len, uint8_t *dst, size_t &dst_len) const noexcept final; ++ simdjson_warn_unused bool validate_utf8(const char *buf, size_t len) const noexcept final; ++}; ++ ++} // namespace arm64 ++} // namespace simdjson ++ ++#endif // SIMDJSON_ARM64_IMPLEMENTATION_H ++/* end file include/simdjson/arm64/implementation.h */ ++ ++/* begin file include/simdjson/arm64/begin.h */ ++// redefining SIMDJSON_IMPLEMENTATION to "arm64" ++// #define SIMDJSON_IMPLEMENTATION arm64 ++/* end file include/simdjson/arm64/begin.h */ ++ ++// Declarations ++/* begin file include/simdjson/generic/dom_parser_implementation.h */ ++ ++namespace simdjson { ++namespace arm64 { ++ ++// expectation: sizeof(open_container) = 64/8. ++struct open_container { ++ uint32_t tape_index; // where, on the tape, does the scope ([,{) begins ++ uint32_t count; // how many elements in the scope ++}; // struct open_container ++ ++static_assert(sizeof(open_container) == 64/8, "Open container must be 64 bits"); ++ ++class dom_parser_implementation final : public internal::dom_parser_implementation { ++public: ++ /** Tape location of each open { or [ */ ++ std::unique_ptr open_containers{}; ++ /** Whether each open container is a [ or { */ ++ std::unique_ptr is_array{}; ++ /** Buffer passed to stage 1 */ ++ const uint8_t *buf{}; ++ /** Length passed to stage 1 */ ++ size_t len{0}; ++ /** Document passed to stage 2 */ ++ dom::document *doc{}; ++ ++ inline dom_parser_implementation() noexcept; ++ inline dom_parser_implementation(dom_parser_implementation &&other) noexcept; ++ inline dom_parser_implementation &operator=(dom_parser_implementation &&other) noexcept; ++ dom_parser_implementation(const dom_parser_implementation &) = delete; ++ dom_parser_implementation &operator=(const dom_parser_implementation &) = delete; ++ ++ simdjson_warn_unused error_code parse(const uint8_t *buf, size_t len, dom::document &doc) noexcept final; ++ simdjson_warn_unused error_code stage1(const uint8_t *buf, size_t len, stage1_mode partial) noexcept final; ++ simdjson_warn_unused error_code stage2(dom::document &doc) noexcept final; ++ simdjson_warn_unused error_code stage2_next(dom::document &doc) noexcept final; ++ simdjson_warn_unused uint8_t *parse_string(const uint8_t *src, uint8_t *dst) const noexcept final; ++ inline simdjson_warn_unused error_code set_capacity(size_t capacity) noexcept final; ++ inline simdjson_warn_unused error_code set_max_depth(size_t max_depth) noexcept final; ++private: ++ simdjson_inline simdjson_warn_unused error_code set_capacity_stage1(size_t capacity); ++ ++}; ++ ++} // namespace arm64 ++} // namespace simdjson ++ ++namespace simdjson { ++namespace arm64 { ++ ++inline dom_parser_implementation::dom_parser_implementation() noexcept = default; ++inline dom_parser_implementation::dom_parser_implementation(dom_parser_implementation &&other) noexcept = default; ++inline dom_parser_implementation &dom_parser_implementation::operator=(dom_parser_implementation &&other) noexcept = default; ++ ++// Leaving these here so they can be inlined if so desired ++inline simdjson_warn_unused error_code dom_parser_implementation::set_capacity(size_t capacity) noexcept { ++ if(capacity > SIMDJSON_MAXSIZE_BYTES) { return CAPACITY; } ++ // Stage 1 index output ++ size_t max_structures = SIMDJSON_ROUNDUP_N(capacity, 64) + 2 + 7; ++ structural_indexes.reset( new (std::nothrow) uint32_t[max_structures] ); ++ if (!structural_indexes) { _capacity = 0; return MEMALLOC; } ++ structural_indexes[0] = 0; ++ n_structural_indexes = 0; ++ ++ _capacity = capacity; ++ return SUCCESS; ++} ++ ++inline simdjson_warn_unused error_code dom_parser_implementation::set_max_depth(size_t max_depth) noexcept { ++ // Stage 2 stacks ++ open_containers.reset(new (std::nothrow) open_container[max_depth]); ++ is_array.reset(new (std::nothrow) bool[max_depth]); ++ if (!is_array || !open_containers) { _max_depth = 0; return MEMALLOC; } ++ ++ _max_depth = max_depth; ++ return SUCCESS; ++} ++ ++} // namespace arm64 ++} // namespace simdjson ++/* end file include/simdjson/generic/dom_parser_implementation.h */ ++/* begin file include/simdjson/arm64/intrinsics.h */ ++#ifndef SIMDJSON_ARM64_INTRINSICS_H ++#define SIMDJSON_ARM64_INTRINSICS_H ++ ++// This should be the correct header whether ++// you use visual studio or other compilers. ++#include ++ ++static_assert(sizeof(uint8x16_t) <= simdjson::SIMDJSON_PADDING, "insufficient padding for arm64"); ++ ++#endif // SIMDJSON_ARM64_INTRINSICS_H ++/* end file include/simdjson/arm64/intrinsics.h */ ++/* begin file include/simdjson/arm64/bitmanipulation.h */ ++#ifndef SIMDJSON_ARM64_BITMANIPULATION_H ++#define SIMDJSON_ARM64_BITMANIPULATION_H ++ ++namespace simdjson { ++namespace arm64 { ++namespace { ++ ++// We sometimes call trailing_zero on inputs that are zero, ++// but the algorithms do not end up using the returned value. ++// Sadly, sanitizers are not smart enough to figure it out. ++SIMDJSON_NO_SANITIZE_UNDEFINED ++simdjson_inline int trailing_zeroes(uint64_t input_num) { ++#ifdef SIMDJSON_REGULAR_VISUAL_STUDIO ++ unsigned long ret; ++ // Search the mask data from least significant bit (LSB) ++ // to the most significant bit (MSB) for a set bit (1). ++ _BitScanForward64(&ret, input_num); ++ return (int)ret; ++#else // SIMDJSON_REGULAR_VISUAL_STUDIO ++ return __builtin_ctzll(input_num); ++#endif // SIMDJSON_REGULAR_VISUAL_STUDIO ++} ++ ++/* result might be undefined when input_num is zero */ ++simdjson_inline uint64_t clear_lowest_bit(uint64_t input_num) { ++ return input_num & (input_num-1); ++} ++ ++/* result might be undefined when input_num is zero */ ++simdjson_inline int leading_zeroes(uint64_t input_num) { ++#ifdef SIMDJSON_REGULAR_VISUAL_STUDIO ++ unsigned long leading_zero = 0; ++ // Search the mask data from most significant bit (MSB) ++ // to least significant bit (LSB) for a set bit (1). ++ if (_BitScanReverse64(&leading_zero, input_num)) ++ return (int)(63 - leading_zero); ++ else ++ return 64; ++#else ++ return __builtin_clzll(input_num); ++#endif// SIMDJSON_REGULAR_VISUAL_STUDIO ++} ++ ++/* result might be undefined when input_num is zero */ ++simdjson_inline int count_ones(uint64_t input_num) { ++ return vaddv_u8(vcnt_u8(vcreate_u8(input_num))); ++} ++ ++ ++#if defined(__GNUC__) // catches clang and gcc ++/** ++ * ARM has a fast 64-bit "bit reversal function" that is handy. However, ++ * it is not generally available as an intrinsic function under Visual ++ * Studio (though this might be changing). Even under clang/gcc, we ++ * apparently need to invoke inline assembly. ++ */ ++/* ++ * We use SIMDJSON_PREFER_REVERSE_BITS as a hint that algorithms that ++ * work well with bit reversal may use it. ++ */ ++#define SIMDJSON_PREFER_REVERSE_BITS 1 ++ ++/* reverse the bits */ ++simdjson_inline uint64_t reverse_bits(uint64_t input_num) { ++ uint64_t rev_bits; ++ __asm("rbit %0, %1" : "=r"(rev_bits) : "r"(input_num)); ++ return rev_bits; ++} ++ ++/** ++ * Flips bit at index 63 - lz. Thus if you have 'leading_zeroes' leading zeroes, ++ * then this will set to zero the leading bit. It is possible for leading_zeroes to be ++ * greating or equal to 63 in which case we trigger undefined behavior, but the output ++ * of such undefined behavior is never used. ++ **/ ++SIMDJSON_NO_SANITIZE_UNDEFINED ++simdjson_inline uint64_t zero_leading_bit(uint64_t rev_bits, int leading_zeroes) { ++ return rev_bits ^ (uint64_t(0x8000000000000000) >> leading_zeroes); ++} ++ ++#endif ++ ++simdjson_inline bool add_overflow(uint64_t value1, uint64_t value2, uint64_t *result) { ++#ifdef SIMDJSON_REGULAR_VISUAL_STUDIO ++ *result = value1 + value2; ++ return *result < value1; ++#else ++ return __builtin_uaddll_overflow(value1, value2, ++ reinterpret_cast(result)); ++#endif ++} ++ ++} // unnamed namespace ++} // namespace arm64 ++} // namespace simdjson ++ ++#endif // SIMDJSON_ARM64_BITMANIPULATION_H ++/* end file include/simdjson/arm64/bitmanipulation.h */ ++/* begin file include/simdjson/arm64/bitmask.h */ ++#ifndef SIMDJSON_ARM64_BITMASK_H ++#define SIMDJSON_ARM64_BITMASK_H ++ ++namespace simdjson { ++namespace arm64 { ++namespace { ++ ++// ++// Perform a "cumulative bitwise xor," flipping bits each time a 1 is encountered. ++// ++// For example, prefix_xor(00100100) == 00011100 ++// ++simdjson_inline uint64_t prefix_xor(uint64_t bitmask) { ++ ///////////// ++ // We could do this with PMULL, but it is apparently slow. ++ // ++ //#ifdef __ARM_FEATURE_CRYPTO // some ARM processors lack this extension ++ //return vmull_p64(-1ULL, bitmask); ++ //#else ++ // Analysis by @sebpop: ++ // When diffing the assembly for src/stage1_find_marks.cpp I see that the eors are all spread out ++ // in between other vector code, so effectively the extra cycles of the sequence do not matter ++ // because the GPR units are idle otherwise and the critical path is on the FP side. ++ // Also the PMULL requires two extra fmovs: GPR->FP (3 cycles in N1, 5 cycles in A72 ) ++ // and FP->GPR (2 cycles on N1 and 5 cycles on A72.) ++ /////////// ++ bitmask ^= bitmask << 1; ++ bitmask ^= bitmask << 2; ++ bitmask ^= bitmask << 4; ++ bitmask ^= bitmask << 8; ++ bitmask ^= bitmask << 16; ++ bitmask ^= bitmask << 32; ++ return bitmask; ++} ++ ++} // unnamed namespace ++} // namespace arm64 ++} // namespace simdjson ++ ++#endif ++/* end file include/simdjson/arm64/bitmask.h */ ++/* begin file include/simdjson/arm64/simd.h */ ++#ifndef SIMDJSON_ARM64_SIMD_H ++#define SIMDJSON_ARM64_SIMD_H ++ ++#include ++ ++ ++namespace simdjson { ++namespace arm64 { ++namespace { ++namespace simd { ++ ++#ifdef SIMDJSON_REGULAR_VISUAL_STUDIO ++namespace { ++// Start of private section with Visual Studio workaround ++ ++ ++/** ++ * make_uint8x16_t initializes a SIMD register (uint8x16_t). ++ * This is needed because, incredibly, the syntax uint8x16_t x = {1,2,3...} ++ * is not recognized under Visual Studio! This is a workaround. ++ * Using a std::initializer_list as a parameter resulted in ++ * inefficient code. With the current approach, if the parameters are ++ * compile-time constants, ++ * GNU GCC compiles it to ldr, the same as uint8x16_t x = {1,2,3...}. ++ * You should not use this function except for compile-time constants: ++ * it is not efficient. ++ */ ++simdjson_inline uint8x16_t make_uint8x16_t(uint8_t x1, uint8_t x2, uint8_t x3, uint8_t x4, ++ uint8_t x5, uint8_t x6, uint8_t x7, uint8_t x8, ++ uint8_t x9, uint8_t x10, uint8_t x11, uint8_t x12, ++ uint8_t x13, uint8_t x14, uint8_t x15, uint8_t x16) { ++ // Doing a load like so end ups generating worse code. ++ // uint8_t array[16] = {x1, x2, x3, x4, x5, x6, x7, x8, ++ // x9, x10,x11,x12,x13,x14,x15,x16}; ++ // return vld1q_u8(array); ++ uint8x16_t x{}; ++ // incredibly, Visual Studio does not allow x[0] = x1 ++ x = vsetq_lane_u8(x1, x, 0); ++ x = vsetq_lane_u8(x2, x, 1); ++ x = vsetq_lane_u8(x3, x, 2); ++ x = vsetq_lane_u8(x4, x, 3); ++ x = vsetq_lane_u8(x5, x, 4); ++ x = vsetq_lane_u8(x6, x, 5); ++ x = vsetq_lane_u8(x7, x, 6); ++ x = vsetq_lane_u8(x8, x, 7); ++ x = vsetq_lane_u8(x9, x, 8); ++ x = vsetq_lane_u8(x10, x, 9); ++ x = vsetq_lane_u8(x11, x, 10); ++ x = vsetq_lane_u8(x12, x, 11); ++ x = vsetq_lane_u8(x13, x, 12); ++ x = vsetq_lane_u8(x14, x, 13); ++ x = vsetq_lane_u8(x15, x, 14); ++ x = vsetq_lane_u8(x16, x, 15); ++ return x; ++} ++ ++simdjson_inline uint8x8_t make_uint8x8_t(uint8_t x1, uint8_t x2, uint8_t x3, uint8_t x4, ++ uint8_t x5, uint8_t x6, uint8_t x7, uint8_t x8) { ++ uint8x8_t x{}; ++ x = vset_lane_u8(x1, x, 0); ++ x = vset_lane_u8(x2, x, 1); ++ x = vset_lane_u8(x3, x, 2); ++ x = vset_lane_u8(x4, x, 3); ++ x = vset_lane_u8(x5, x, 4); ++ x = vset_lane_u8(x6, x, 5); ++ x = vset_lane_u8(x7, x, 6); ++ x = vset_lane_u8(x8, x, 7); ++ return x; ++} ++ ++// We have to do the same work for make_int8x16_t ++simdjson_inline int8x16_t make_int8x16_t(int8_t x1, int8_t x2, int8_t x3, int8_t x4, ++ int8_t x5, int8_t x6, int8_t x7, int8_t x8, ++ int8_t x9, int8_t x10, int8_t x11, int8_t x12, ++ int8_t x13, int8_t x14, int8_t x15, int8_t x16) { ++ // Doing a load like so end ups generating worse code. ++ // int8_t array[16] = {x1, x2, x3, x4, x5, x6, x7, x8, ++ // x9, x10,x11,x12,x13,x14,x15,x16}; ++ // return vld1q_s8(array); ++ int8x16_t x{}; ++ // incredibly, Visual Studio does not allow x[0] = x1 ++ x = vsetq_lane_s8(x1, x, 0); ++ x = vsetq_lane_s8(x2, x, 1); ++ x = vsetq_lane_s8(x3, x, 2); ++ x = vsetq_lane_s8(x4, x, 3); ++ x = vsetq_lane_s8(x5, x, 4); ++ x = vsetq_lane_s8(x6, x, 5); ++ x = vsetq_lane_s8(x7, x, 6); ++ x = vsetq_lane_s8(x8, x, 7); ++ x = vsetq_lane_s8(x9, x, 8); ++ x = vsetq_lane_s8(x10, x, 9); ++ x = vsetq_lane_s8(x11, x, 10); ++ x = vsetq_lane_s8(x12, x, 11); ++ x = vsetq_lane_s8(x13, x, 12); ++ x = vsetq_lane_s8(x14, x, 13); ++ x = vsetq_lane_s8(x15, x, 14); ++ x = vsetq_lane_s8(x16, x, 15); ++ return x; ++} ++ ++// End of private section with Visual Studio workaround ++} // namespace ++#endif // SIMDJSON_REGULAR_VISUAL_STUDIO ++ ++ ++ template ++ struct simd8; ++ ++ // ++ // Base class of simd8 and simd8, both of which use uint8x16_t internally. ++ // ++ template> ++ struct base_u8 { ++ uint8x16_t value; ++ static const int SIZE = sizeof(value); ++ ++ // Conversion from/to SIMD register ++ simdjson_inline base_u8(const uint8x16_t _value) : value(_value) {} ++ simdjson_inline operator const uint8x16_t&() const { return this->value; } ++ simdjson_inline operator uint8x16_t&() { return this->value; } ++ ++ // Bit operations ++ simdjson_inline simd8 operator|(const simd8 other) const { return vorrq_u8(*this, other); } ++ simdjson_inline simd8 operator&(const simd8 other) const { return vandq_u8(*this, other); } ++ simdjson_inline simd8 operator^(const simd8 other) const { return veorq_u8(*this, other); } ++ simdjson_inline simd8 bit_andnot(const simd8 other) const { return vbicq_u8(*this, other); } ++ simdjson_inline simd8 operator~() const { return *this ^ 0xFFu; } ++ simdjson_inline simd8& operator|=(const simd8 other) { auto this_cast = static_cast*>(this); *this_cast = *this_cast | other; return *this_cast; } ++ simdjson_inline simd8& operator&=(const simd8 other) { auto this_cast = static_cast*>(this); *this_cast = *this_cast & other; return *this_cast; } ++ simdjson_inline simd8& operator^=(const simd8 other) { auto this_cast = static_cast*>(this); *this_cast = *this_cast ^ other; return *this_cast; } ++ ++ friend simdjson_inline Mask operator==(const simd8 lhs, const simd8 rhs) { return vceqq_u8(lhs, rhs); } ++ ++ template ++ simdjson_inline simd8 prev(const simd8 prev_chunk) const { ++ return vextq_u8(prev_chunk, *this, 16 - N); ++ } ++ }; ++ ++ // SIMD byte mask type (returned by things like eq and gt) ++ template<> ++ struct simd8: base_u8 { ++ typedef uint16_t bitmask_t; ++ typedef uint32_t bitmask2_t; ++ ++ static simdjson_inline simd8 splat(bool _value) { return vmovq_n_u8(uint8_t(-(!!_value))); } ++ ++ simdjson_inline simd8(const uint8x16_t _value) : base_u8(_value) {} ++ // False constructor ++ simdjson_inline simd8() : simd8(vdupq_n_u8(0)) {} ++ // Splat constructor ++ simdjson_inline simd8(bool _value) : simd8(splat(_value)) {} ++ ++ // We return uint32_t instead of uint16_t because that seems to be more efficient for most ++ // purposes (cutting it down to uint16_t costs performance in some compilers). ++ simdjson_inline uint32_t to_bitmask() const { ++#ifdef SIMDJSON_REGULAR_VISUAL_STUDIO ++ const uint8x16_t bit_mask = make_uint8x16_t(0x01, 0x02, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80, ++ 0x01, 0x02, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80); ++#else ++ const uint8x16_t bit_mask = {0x01, 0x02, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80, ++ 0x01, 0x02, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80}; ++#endif ++ auto minput = *this & bit_mask; ++ uint8x16_t tmp = vpaddq_u8(minput, minput); ++ tmp = vpaddq_u8(tmp, tmp); ++ tmp = vpaddq_u8(tmp, tmp); ++ return vgetq_lane_u16(vreinterpretq_u16_u8(tmp), 0); ++ } ++ simdjson_inline bool any() const { return vmaxvq_u8(*this) != 0; } ++ }; ++ ++ // Unsigned bytes ++ template<> ++ struct simd8: base_u8 { ++ static simdjson_inline uint8x16_t splat(uint8_t _value) { return vmovq_n_u8(_value); } ++ static simdjson_inline uint8x16_t zero() { return vdupq_n_u8(0); } ++ static simdjson_inline uint8x16_t load(const uint8_t* values) { return vld1q_u8(values); } ++ ++ simdjson_inline simd8(const uint8x16_t _value) : base_u8(_value) {} ++ // Zero constructor ++ simdjson_inline simd8() : simd8(zero()) {} ++ // Array constructor ++ simdjson_inline simd8(const uint8_t values[16]) : simd8(load(values)) {} ++ // Splat constructor ++ simdjson_inline simd8(uint8_t _value) : simd8(splat(_value)) {} ++ // Member-by-member initialization ++#ifdef SIMDJSON_REGULAR_VISUAL_STUDIO ++ simdjson_inline simd8( ++ uint8_t v0, uint8_t v1, uint8_t v2, uint8_t v3, uint8_t v4, uint8_t v5, uint8_t v6, uint8_t v7, ++ uint8_t v8, uint8_t v9, uint8_t v10, uint8_t v11, uint8_t v12, uint8_t v13, uint8_t v14, uint8_t v15 ++ ) : simd8(make_uint8x16_t( ++ v0, v1, v2, v3, v4, v5, v6, v7, ++ v8, v9, v10,v11,v12,v13,v14,v15 ++ )) {} ++#else ++ simdjson_inline simd8( ++ uint8_t v0, uint8_t v1, uint8_t v2, uint8_t v3, uint8_t v4, uint8_t v5, uint8_t v6, uint8_t v7, ++ uint8_t v8, uint8_t v9, uint8_t v10, uint8_t v11, uint8_t v12, uint8_t v13, uint8_t v14, uint8_t v15 ++ ) : simd8(uint8x16_t{ ++ v0, v1, v2, v3, v4, v5, v6, v7, ++ v8, v9, v10,v11,v12,v13,v14,v15 ++ }) {} ++#endif ++ ++ // Repeat 16 values as many times as necessary (usually for lookup tables) ++ simdjson_inline static simd8 repeat_16( ++ uint8_t v0, uint8_t v1, uint8_t v2, uint8_t v3, uint8_t v4, uint8_t v5, uint8_t v6, uint8_t v7, ++ uint8_t v8, uint8_t v9, uint8_t v10, uint8_t v11, uint8_t v12, uint8_t v13, uint8_t v14, uint8_t v15 ++ ) { ++ return simd8( ++ v0, v1, v2, v3, v4, v5, v6, v7, ++ v8, v9, v10,v11,v12,v13,v14,v15 ++ ); ++ } ++ ++ // Store to array ++ simdjson_inline void store(uint8_t dst[16]) const { return vst1q_u8(dst, *this); } ++ ++ // Saturated math ++ simdjson_inline simd8 saturating_add(const simd8 other) const { return vqaddq_u8(*this, other); } ++ simdjson_inline simd8 saturating_sub(const simd8 other) const { return vqsubq_u8(*this, other); } ++ ++ // Addition/subtraction are the same for signed and unsigned ++ simdjson_inline simd8 operator+(const simd8 other) const { return vaddq_u8(*this, other); } ++ simdjson_inline simd8 operator-(const simd8 other) const { return vsubq_u8(*this, other); } ++ simdjson_inline simd8& operator+=(const simd8 other) { *this = *this + other; return *this; } ++ simdjson_inline simd8& operator-=(const simd8 other) { *this = *this - other; return *this; } ++ ++ // Order-specific operations ++ simdjson_inline uint8_t max_val() const { return vmaxvq_u8(*this); } ++ simdjson_inline uint8_t min_val() const { return vminvq_u8(*this); } ++ simdjson_inline simd8 max_val(const simd8 other) const { return vmaxq_u8(*this, other); } ++ simdjson_inline simd8 min_val(const simd8 other) const { return vminq_u8(*this, other); } ++ simdjson_inline simd8 operator<=(const simd8 other) const { return vcleq_u8(*this, other); } ++ simdjson_inline simd8 operator>=(const simd8 other) const { return vcgeq_u8(*this, other); } ++ simdjson_inline simd8 operator<(const simd8 other) const { return vcltq_u8(*this, other); } ++ simdjson_inline simd8 operator>(const simd8 other) const { return vcgtq_u8(*this, other); } ++ // Same as >, but instead of guaranteeing all 1's == true, false = 0 and true = nonzero. For ARM, returns all 1's. ++ simdjson_inline simd8 gt_bits(const simd8 other) const { return simd8(*this > other); } ++ // Same as <, but instead of guaranteeing all 1's == true, false = 0 and true = nonzero. For ARM, returns all 1's. ++ simdjson_inline simd8 lt_bits(const simd8 other) const { return simd8(*this < other); } ++ ++ // Bit-specific operations ++ simdjson_inline simd8 any_bits_set(simd8 bits) const { return vtstq_u8(*this, bits); } ++ simdjson_inline bool any_bits_set_anywhere() const { return this->max_val() != 0; } ++ simdjson_inline bool any_bits_set_anywhere(simd8 bits) const { return (*this & bits).any_bits_set_anywhere(); } ++ template ++ simdjson_inline simd8 shr() const { return vshrq_n_u8(*this, N); } ++ template ++ simdjson_inline simd8 shl() const { return vshlq_n_u8(*this, N); } ++ ++ // Perform a lookup assuming the value is between 0 and 16 (undefined behavior for out of range values) ++ template ++ simdjson_inline simd8 lookup_16(simd8 lookup_table) const { ++ return lookup_table.apply_lookup_16_to(*this); ++ } ++ ++ ++ // Copies to 'output" all bytes corresponding to a 0 in the mask (interpreted as a bitset). ++ // Passing a 0 value for mask would be equivalent to writing out every byte to output. ++ // Only the first 16 - count_ones(mask) bytes of the result are significant but 16 bytes ++ // get written. ++ // Design consideration: it seems like a function with the ++ // signature simd8 compress(uint16_t mask) would be ++ // sensible, but the AVX ISA makes this kind of approach difficult. ++ template ++ simdjson_inline void compress(uint16_t mask, L * output) const { ++ using internal::thintable_epi8; ++ using internal::BitsSetTable256mul2; ++ using internal::pshufb_combine_table; ++ // this particular implementation was inspired by work done by @animetosho ++ // we do it in two steps, first 8 bytes and then second 8 bytes ++ uint8_t mask1 = uint8_t(mask); // least significant 8 bits ++ uint8_t mask2 = uint8_t(mask >> 8); // most significant 8 bits ++ // next line just loads the 64-bit values thintable_epi8[mask1] and ++ // thintable_epi8[mask2] into a 128-bit register, using only ++ // two instructions on most compilers. ++ uint64x2_t shufmask64 = {thintable_epi8[mask1], thintable_epi8[mask2]}; ++ uint8x16_t shufmask = vreinterpretq_u8_u64(shufmask64); ++ // we increment by 0x08 the second half of the mask ++#ifdef SIMDJSON_REGULAR_VISUAL_STUDIO ++ uint8x16_t inc = make_uint8x16_t(0, 0, 0, 0, 0, 0, 0, 0, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08); ++#else ++ uint8x16_t inc = {0, 0, 0, 0, 0, 0, 0, 0, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08}; ++#endif ++ shufmask = vaddq_u8(shufmask, inc); ++ // this is the version "nearly pruned" ++ uint8x16_t pruned = vqtbl1q_u8(*this, shufmask); ++ // we still need to put the two halves together. ++ // we compute the popcount of the first half: ++ int pop1 = BitsSetTable256mul2[mask1]; ++ // then load the corresponding mask, what it does is to write ++ // only the first pop1 bytes from the first 8 bytes, and then ++ // it fills in with the bytes from the second 8 bytes + some filling ++ // at the end. ++ uint8x16_t compactmask = vld1q_u8(reinterpret_cast(pshufb_combine_table + pop1 * 8)); ++ uint8x16_t answer = vqtbl1q_u8(pruned, compactmask); ++ vst1q_u8(reinterpret_cast(output), answer); ++ } ++ ++ // Copies all bytes corresponding to a 0 in the low half of the mask (interpreted as a ++ // bitset) to output1, then those corresponding to a 0 in the high half to output2. ++ template ++ simdjson_inline void compress_halves(uint16_t mask, L *output1, L *output2) const { ++ using internal::thintable_epi8; ++ uint8_t mask1 = uint8_t(mask); // least significant 8 bits ++ uint8_t mask2 = uint8_t(mask >> 8); // most significant 8 bits ++ uint8x8_t compactmask1 = vcreate_u8(thintable_epi8[mask1]); ++ uint8x8_t compactmask2 = vcreate_u8(thintable_epi8[mask2]); ++ // we increment by 0x08 the second half of the mask ++#ifdef SIMDJSON_REGULAR_VISUAL_STUDIO ++ uint8x8_t inc = make_uint8x8_t(0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08); ++#else ++ uint8x8_t inc = {0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08}; ++#endif ++ compactmask2 = vadd_u8(compactmask2, inc); ++ // store each result (with the second store possibly overlapping the first) ++ vst1_u8((uint8_t*)output1, vqtbl1_u8(*this, compactmask1)); ++ vst1_u8((uint8_t*)output2, vqtbl1_u8(*this, compactmask2)); ++ } ++ ++ template ++ simdjson_inline simd8 lookup_16( ++ L replace0, L replace1, L replace2, L replace3, ++ L replace4, L replace5, L replace6, L replace7, ++ L replace8, L replace9, L replace10, L replace11, ++ L replace12, L replace13, L replace14, L replace15) const { ++ return lookup_16(simd8::repeat_16( ++ replace0, replace1, replace2, replace3, ++ replace4, replace5, replace6, replace7, ++ replace8, replace9, replace10, replace11, ++ replace12, replace13, replace14, replace15 ++ )); ++ } ++ ++ template ++ simdjson_inline simd8 apply_lookup_16_to(const simd8 original) { ++ return vqtbl1q_u8(*this, simd8(original)); ++ } ++ }; ++ ++ // Signed bytes ++ template<> ++ struct simd8 { ++ int8x16_t value; ++ ++ static simdjson_inline simd8 splat(int8_t _value) { return vmovq_n_s8(_value); } ++ static simdjson_inline simd8 zero() { return vdupq_n_s8(0); } ++ static simdjson_inline simd8 load(const int8_t values[16]) { return vld1q_s8(values); } ++ ++ // Conversion from/to SIMD register ++ simdjson_inline simd8(const int8x16_t _value) : value{_value} {} ++ simdjson_inline operator const int8x16_t&() const { return this->value; } ++ simdjson_inline operator int8x16_t&() { return this->value; } ++ ++ // Zero constructor ++ simdjson_inline simd8() : simd8(zero()) {} ++ // Splat constructor ++ simdjson_inline simd8(int8_t _value) : simd8(splat(_value)) {} ++ // Array constructor ++ simdjson_inline simd8(const int8_t* values) : simd8(load(values)) {} ++ // Member-by-member initialization ++#ifdef SIMDJSON_REGULAR_VISUAL_STUDIO ++ simdjson_inline simd8( ++ int8_t v0, int8_t v1, int8_t v2, int8_t v3, int8_t v4, int8_t v5, int8_t v6, int8_t v7, ++ int8_t v8, int8_t v9, int8_t v10, int8_t v11, int8_t v12, int8_t v13, int8_t v14, int8_t v15 ++ ) : simd8(make_int8x16_t( ++ v0, v1, v2, v3, v4, v5, v6, v7, ++ v8, v9, v10,v11,v12,v13,v14,v15 ++ )) {} ++#else ++ simdjson_inline simd8( ++ int8_t v0, int8_t v1, int8_t v2, int8_t v3, int8_t v4, int8_t v5, int8_t v6, int8_t v7, ++ int8_t v8, int8_t v9, int8_t v10, int8_t v11, int8_t v12, int8_t v13, int8_t v14, int8_t v15 ++ ) : simd8(int8x16_t{ ++ v0, v1, v2, v3, v4, v5, v6, v7, ++ v8, v9, v10,v11,v12,v13,v14,v15 ++ }) {} ++#endif ++ // Repeat 16 values as many times as necessary (usually for lookup tables) ++ simdjson_inline static simd8 repeat_16( ++ int8_t v0, int8_t v1, int8_t v2, int8_t v3, int8_t v4, int8_t v5, int8_t v6, int8_t v7, ++ int8_t v8, int8_t v9, int8_t v10, int8_t v11, int8_t v12, int8_t v13, int8_t v14, int8_t v15 ++ ) { ++ return simd8( ++ v0, v1, v2, v3, v4, v5, v6, v7, ++ v8, v9, v10,v11,v12,v13,v14,v15 ++ ); ++ } ++ ++ // Store to array ++ simdjson_inline void store(int8_t dst[16]) const { return vst1q_s8(dst, *this); } ++ ++ // Explicit conversion to/from unsigned ++ // ++ // Under Visual Studio/ARM64 uint8x16_t and int8x16_t are apparently the same type. ++ // In theory, we could check this occurrence with std::same_as and std::enabled_if but it is C++14 ++ // and relatively ugly and hard to read. ++#ifndef SIMDJSON_REGULAR_VISUAL_STUDIO ++ simdjson_inline explicit simd8(const uint8x16_t other): simd8(vreinterpretq_s8_u8(other)) {} ++#endif ++ simdjson_inline explicit operator simd8() const { return vreinterpretq_u8_s8(this->value); } ++ ++ // Math ++ simdjson_inline simd8 operator+(const simd8 other) const { return vaddq_s8(*this, other); } ++ simdjson_inline simd8 operator-(const simd8 other) const { return vsubq_s8(*this, other); } ++ simdjson_inline simd8& operator+=(const simd8 other) { *this = *this + other; return *this; } ++ simdjson_inline simd8& operator-=(const simd8 other) { *this = *this - other; return *this; } ++ ++ // Order-sensitive comparisons ++ simdjson_inline simd8 max_val(const simd8 other) const { return vmaxq_s8(*this, other); } ++ simdjson_inline simd8 min_val(const simd8 other) const { return vminq_s8(*this, other); } ++ simdjson_inline simd8 operator>(const simd8 other) const { return vcgtq_s8(*this, other); } ++ simdjson_inline simd8 operator<(const simd8 other) const { return vcltq_s8(*this, other); } ++ simdjson_inline simd8 operator==(const simd8 other) const { return vceqq_s8(*this, other); } ++ ++ template ++ simdjson_inline simd8 prev(const simd8 prev_chunk) const { ++ return vextq_s8(prev_chunk, *this, 16 - N); ++ } ++ ++ // Perform a lookup assuming no value is larger than 16 ++ template ++ simdjson_inline simd8 lookup_16(simd8 lookup_table) const { ++ return lookup_table.apply_lookup_16_to(*this); ++ } ++ template ++ simdjson_inline simd8 lookup_16( ++ L replace0, L replace1, L replace2, L replace3, ++ L replace4, L replace5, L replace6, L replace7, ++ L replace8, L replace9, L replace10, L replace11, ++ L replace12, L replace13, L replace14, L replace15) const { ++ return lookup_16(simd8::repeat_16( ++ replace0, replace1, replace2, replace3, ++ replace4, replace5, replace6, replace7, ++ replace8, replace9, replace10, replace11, ++ replace12, replace13, replace14, replace15 ++ )); ++ } ++ ++ template ++ simdjson_inline simd8 apply_lookup_16_to(const simd8 original) { ++ return vqtbl1q_s8(*this, simd8(original)); ++ } ++ }; ++ ++ template ++ struct simd8x64 { ++ static constexpr int NUM_CHUNKS = 64 / sizeof(simd8); ++ static_assert(NUM_CHUNKS == 4, "ARM kernel should use four registers per 64-byte block."); ++ const simd8 chunks[NUM_CHUNKS]; ++ ++ simd8x64(const simd8x64& o) = delete; // no copy allowed ++ simd8x64& operator=(const simd8& other) = delete; // no assignment allowed ++ simd8x64() = delete; // no default constructor allowed ++ ++ simdjson_inline simd8x64(const simd8 chunk0, const simd8 chunk1, const simd8 chunk2, const simd8 chunk3) : chunks{chunk0, chunk1, chunk2, chunk3} {} ++ simdjson_inline simd8x64(const T ptr[64]) : chunks{simd8::load(ptr), simd8::load(ptr+16), simd8::load(ptr+32), simd8::load(ptr+48)} {} ++ ++ simdjson_inline void store(T ptr[64]) const { ++ this->chunks[0].store(ptr+sizeof(simd8)*0); ++ this->chunks[1].store(ptr+sizeof(simd8)*1); ++ this->chunks[2].store(ptr+sizeof(simd8)*2); ++ this->chunks[3].store(ptr+sizeof(simd8)*3); ++ } ++ ++ simdjson_inline simd8 reduce_or() const { ++ return (this->chunks[0] | this->chunks[1]) | (this->chunks[2] | this->chunks[3]); ++ } ++ ++ ++ simdjson_inline uint64_t compress(uint64_t mask, T * output) const { ++ uint64_t popcounts = vget_lane_u64(vreinterpret_u64_u8(vcnt_u8(vcreate_u8(~mask))), 0); ++ // compute the prefix sum of the popcounts of each byte ++ uint64_t offsets = popcounts * 0x0101010101010101; ++ this->chunks[0].compress_halves(uint16_t(mask), output, &output[popcounts & 0xFF]); ++ this->chunks[1].compress_halves(uint16_t(mask >> 16), &output[(offsets >> 8) & 0xFF], &output[(offsets >> 16) & 0xFF]); ++ this->chunks[2].compress_halves(uint16_t(mask >> 32), &output[(offsets >> 24) & 0xFF], &output[(offsets >> 32) & 0xFF]); ++ this->chunks[3].compress_halves(uint16_t(mask >> 48), &output[(offsets >> 40) & 0xFF], &output[(offsets >> 48) & 0xFF]); ++ return offsets >> 56; ++ } ++ ++ simdjson_inline uint64_t to_bitmask() const { ++#ifdef SIMDJSON_REGULAR_VISUAL_STUDIO ++ const uint8x16_t bit_mask = make_uint8x16_t( ++ 0x01, 0x02, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80, ++ 0x01, 0x02, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80 ++ ); ++#else ++ const uint8x16_t bit_mask = { ++ 0x01, 0x02, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80, ++ 0x01, 0x02, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80 ++ }; ++#endif ++ // Add each of the elements next to each other, successively, to stuff each 8 byte mask into one. ++ uint8x16_t sum0 = vpaddq_u8(this->chunks[0] & bit_mask, this->chunks[1] & bit_mask); ++ uint8x16_t sum1 = vpaddq_u8(this->chunks[2] & bit_mask, this->chunks[3] & bit_mask); ++ sum0 = vpaddq_u8(sum0, sum1); ++ sum0 = vpaddq_u8(sum0, sum0); ++ return vgetq_lane_u64(vreinterpretq_u64_u8(sum0), 0); ++ } ++ ++ simdjson_inline uint64_t eq(const T m) const { ++ const simd8 mask = simd8::splat(m); ++ return simd8x64( ++ this->chunks[0] == mask, ++ this->chunks[1] == mask, ++ this->chunks[2] == mask, ++ this->chunks[3] == mask ++ ).to_bitmask(); ++ } ++ ++ simdjson_inline uint64_t lteq(const T m) const { ++ const simd8 mask = simd8::splat(m); ++ return simd8x64( ++ this->chunks[0] <= mask, ++ this->chunks[1] <= mask, ++ this->chunks[2] <= mask, ++ this->chunks[3] <= mask ++ ).to_bitmask(); ++ } ++ }; // struct simd8x64 ++ ++} // namespace simd ++} // unnamed namespace ++} // namespace arm64 ++} // namespace simdjson ++ ++#endif // SIMDJSON_ARM64_SIMD_H ++/* end file include/simdjson/arm64/simd.h */ ++/* begin file include/simdjson/generic/jsoncharutils.h */ ++ ++namespace simdjson { ++namespace arm64 { ++namespace { ++namespace jsoncharutils { ++ ++// return non-zero if not a structural or whitespace char ++// zero otherwise ++simdjson_inline uint32_t is_not_structural_or_whitespace(uint8_t c) { ++ return internal::structural_or_whitespace_negated[c]; ++} ++ ++simdjson_inline uint32_t is_structural_or_whitespace(uint8_t c) { ++ return internal::structural_or_whitespace[c]; ++} ++ ++// returns a value with the high 16 bits set if not valid ++// otherwise returns the conversion of the 4 hex digits at src into the bottom ++// 16 bits of the 32-bit return register ++// ++// see ++// https://lemire.me/blog/2019/04/17/parsing-short-hexadecimal-strings-efficiently/ ++static inline uint32_t hex_to_u32_nocheck( ++ const uint8_t *src) { // strictly speaking, static inline is a C-ism ++ uint32_t v1 = internal::digit_to_val32[630 + src[0]]; ++ uint32_t v2 = internal::digit_to_val32[420 + src[1]]; ++ uint32_t v3 = internal::digit_to_val32[210 + src[2]]; ++ uint32_t v4 = internal::digit_to_val32[0 + src[3]]; ++ return v1 | v2 | v3 | v4; ++} ++ ++// given a code point cp, writes to c ++// the utf-8 code, outputting the length in ++// bytes, if the length is zero, the code point ++// is invalid ++// ++// This can possibly be made faster using pdep ++// and clz and table lookups, but JSON documents ++// have few escaped code points, and the following ++// function looks cheap. ++// ++// Note: we assume that surrogates are treated separately ++// ++simdjson_inline size_t codepoint_to_utf8(uint32_t cp, uint8_t *c) { ++ if (cp <= 0x7F) { ++ c[0] = uint8_t(cp); ++ return 1; // ascii ++ } ++ if (cp <= 0x7FF) { ++ c[0] = uint8_t((cp >> 6) + 192); ++ c[1] = uint8_t((cp & 63) + 128); ++ return 2; // universal plane ++ // Surrogates are treated elsewhere... ++ //} //else if (0xd800 <= cp && cp <= 0xdfff) { ++ // return 0; // surrogates // could put assert here ++ } else if (cp <= 0xFFFF) { ++ c[0] = uint8_t((cp >> 12) + 224); ++ c[1] = uint8_t(((cp >> 6) & 63) + 128); ++ c[2] = uint8_t((cp & 63) + 128); ++ return 3; ++ } else if (cp <= 0x10FFFF) { // if you know you have a valid code point, this ++ // is not needed ++ c[0] = uint8_t((cp >> 18) + 240); ++ c[1] = uint8_t(((cp >> 12) & 63) + 128); ++ c[2] = uint8_t(((cp >> 6) & 63) + 128); ++ c[3] = uint8_t((cp & 63) + 128); ++ return 4; ++ } ++ // will return 0 when the code point was too large. ++ return 0; // bad r ++} ++ ++#if SIMDJSON_IS_32BITS // _umul128 for x86, arm ++// this is a slow emulation routine for 32-bit ++// ++static simdjson_inline uint64_t __emulu(uint32_t x, uint32_t y) { ++ return x * (uint64_t)y; ++} ++static simdjson_inline uint64_t _umul128(uint64_t ab, uint64_t cd, uint64_t *hi) { ++ uint64_t ad = __emulu((uint32_t)(ab >> 32), (uint32_t)cd); ++ uint64_t bd = __emulu((uint32_t)ab, (uint32_t)cd); ++ uint64_t adbc = ad + __emulu((uint32_t)ab, (uint32_t)(cd >> 32)); ++ uint64_t adbc_carry = !!(adbc < ad); ++ uint64_t lo = bd + (adbc << 32); ++ *hi = __emulu((uint32_t)(ab >> 32), (uint32_t)(cd >> 32)) + (adbc >> 32) + ++ (adbc_carry << 32) + !!(lo < bd); ++ return lo; ++} ++#endif ++ ++using internal::value128; ++ ++simdjson_inline value128 full_multiplication(uint64_t value1, uint64_t value2) { ++ value128 answer; ++#if SIMDJSON_REGULAR_VISUAL_STUDIO || SIMDJSON_IS_32BITS ++#ifdef _M_ARM64 ++ // ARM64 has native support for 64-bit multiplications, no need to emultate ++ answer.high = __umulh(value1, value2); ++ answer.low = value1 * value2; ++#else ++ answer.low = _umul128(value1, value2, &answer.high); // _umul128 not available on ARM64 ++#endif // _M_ARM64 ++#else // SIMDJSON_REGULAR_VISUAL_STUDIO || SIMDJSON_IS_32BITS ++ __uint128_t r = (static_cast<__uint128_t>(value1)) * value2; ++ answer.low = uint64_t(r); ++ answer.high = uint64_t(r >> 64); ++#endif ++ return answer; ++} ++ ++} // namespace jsoncharutils ++} // unnamed namespace ++} // namespace arm64 ++} // namespace simdjson ++/* end file include/simdjson/generic/jsoncharutils.h */ ++/* begin file include/simdjson/generic/atomparsing.h */ ++namespace simdjson { ++namespace arm64 { ++namespace { ++/// @private ++namespace atomparsing { ++ ++// The string_to_uint32 is exclusively used to map literal strings to 32-bit values. ++// We use memcpy instead of a pointer cast to avoid undefined behaviors since we cannot ++// be certain that the character pointer will be properly aligned. ++// You might think that using memcpy makes this function expensive, but you'd be wrong. ++// All decent optimizing compilers (GCC, clang, Visual Studio) will compile string_to_uint32("false"); ++// to the compile-time constant 1936482662. ++simdjson_inline uint32_t string_to_uint32(const char* str) { uint32_t val; std::memcpy(&val, str, sizeof(uint32_t)); return val; } ++ ++ ++// Again in str4ncmp we use a memcpy to avoid undefined behavior. The memcpy may appear expensive. ++// Yet all decent optimizing compilers will compile memcpy to a single instruction, just about. ++simdjson_warn_unused ++simdjson_inline uint32_t str4ncmp(const uint8_t *src, const char* atom) { ++ uint32_t srcval; // we want to avoid unaligned 32-bit loads (undefined in C/C++) ++ static_assert(sizeof(uint32_t) <= SIMDJSON_PADDING, "SIMDJSON_PADDING must be larger than 4 bytes"); ++ std::memcpy(&srcval, src, sizeof(uint32_t)); ++ return srcval ^ string_to_uint32(atom); ++} ++ ++simdjson_warn_unused ++simdjson_inline bool is_valid_true_atom(const uint8_t *src) { ++ return (str4ncmp(src, "true") | jsoncharutils::is_not_structural_or_whitespace(src[4])) == 0; ++} ++ ++simdjson_warn_unused ++simdjson_inline bool is_valid_true_atom(const uint8_t *src, size_t len) { ++ if (len > 4) { return is_valid_true_atom(src); } ++ else if (len == 4) { return !str4ncmp(src, "true"); } ++ else { return false; } ++} ++ ++simdjson_warn_unused ++simdjson_inline bool is_valid_false_atom(const uint8_t *src) { ++ return (str4ncmp(src+1, "alse") | jsoncharutils::is_not_structural_or_whitespace(src[5])) == 0; ++} ++ ++simdjson_warn_unused ++simdjson_inline bool is_valid_false_atom(const uint8_t *src, size_t len) { ++ if (len > 5) { return is_valid_false_atom(src); } ++ else if (len == 5) { return !str4ncmp(src+1, "alse"); } ++ else { return false; } ++} ++ ++simdjson_warn_unused ++simdjson_inline bool is_valid_null_atom(const uint8_t *src) { ++ return (str4ncmp(src, "null") | jsoncharutils::is_not_structural_or_whitespace(src[4])) == 0; ++} ++ ++simdjson_warn_unused ++simdjson_inline bool is_valid_null_atom(const uint8_t *src, size_t len) { ++ if (len > 4) { return is_valid_null_atom(src); } ++ else if (len == 4) { return !str4ncmp(src, "null"); } ++ else { return false; } ++} ++ ++} // namespace atomparsing ++} // unnamed namespace ++} // namespace arm64 ++} // namespace simdjson ++/* end file include/simdjson/generic/atomparsing.h */ ++/* begin file include/simdjson/arm64/stringparsing.h */ ++#ifndef SIMDJSON_ARM64_STRINGPARSING_H ++#define SIMDJSON_ARM64_STRINGPARSING_H ++ ++ ++namespace simdjson { ++namespace arm64 { ++namespace { ++ ++using namespace simd; ++ ++// Holds backslashes and quotes locations. ++struct backslash_and_quote { ++public: ++ static constexpr uint32_t BYTES_PROCESSED = 32; ++ simdjson_inline static backslash_and_quote copy_and_find(const uint8_t *src, uint8_t *dst); ++ ++ simdjson_inline bool has_quote_first() { return ((bs_bits - 1) & quote_bits) != 0; } ++ simdjson_inline bool has_backslash() { return bs_bits != 0; } ++ simdjson_inline int quote_index() { return trailing_zeroes(quote_bits); } ++ simdjson_inline int backslash_index() { return trailing_zeroes(bs_bits); } ++ ++ uint32_t bs_bits; ++ uint32_t quote_bits; ++}; // struct backslash_and_quote ++ ++simdjson_inline backslash_and_quote backslash_and_quote::copy_and_find(const uint8_t *src, uint8_t *dst) { ++ // this can read up to 31 bytes beyond the buffer size, but we require ++ // SIMDJSON_PADDING of padding ++ static_assert(SIMDJSON_PADDING >= (BYTES_PROCESSED - 1), "backslash and quote finder must process fewer than SIMDJSON_PADDING bytes"); ++ simd8 v0(src); ++ simd8 v1(src + sizeof(v0)); ++ v0.store(dst); ++ v1.store(dst + sizeof(v0)); ++ ++ // Getting a 64-bit bitmask is much cheaper than multiple 16-bit bitmasks on ARM; therefore, we ++ // smash them together into a 64-byte mask and get the bitmask from there. ++ uint64_t bs_and_quote = simd8x64(v0 == '\\', v1 == '\\', v0 == '"', v1 == '"').to_bitmask(); ++ return { ++ uint32_t(bs_and_quote), // bs_bits ++ uint32_t(bs_and_quote >> 32) // quote_bits ++ }; ++} ++ ++} // unnamed namespace ++} // namespace arm64 ++} // namespace simdjson ++ ++#endif // SIMDJSON_ARM64_STRINGPARSING_H ++/* end file include/simdjson/arm64/stringparsing.h */ ++/* begin file include/simdjson/arm64/numberparsing.h */ ++#ifndef SIMDJSON_ARM64_NUMBERPARSING_H ++#define SIMDJSON_ARM64_NUMBERPARSING_H ++ ++namespace simdjson { ++namespace arm64 { ++namespace { ++ ++// we don't have SSE, so let us use a scalar function ++// credit: https://johnnylee-sde.github.io/Fast-numeric-string-to-int/ ++static simdjson_inline uint32_t parse_eight_digits_unrolled(const uint8_t *chars) { ++ uint64_t val; ++ std::memcpy(&val, chars, sizeof(uint64_t)); ++ val = (val & 0x0F0F0F0F0F0F0F0F) * 2561 >> 8; ++ val = (val & 0x00FF00FF00FF00FF) * 6553601 >> 16; ++ return uint32_t((val & 0x0000FFFF0000FFFF) * 42949672960001 >> 32); ++} ++ ++} // unnamed namespace ++} // namespace arm64 ++} // namespace simdjson ++ ++#define SIMDJSON_SWAR_NUMBER_PARSING 1 ++ ++/* begin file include/simdjson/generic/numberparsing.h */ ++#include ++ ++namespace simdjson { ++namespace arm64 { ++ ++namespace ondemand { ++/** ++ * The type of a JSON number ++ */ ++enum class number_type { ++ floating_point_number=1, /// a binary64 number ++ signed_integer, /// a signed integer that fits in a 64-bit word using two's complement ++ unsigned_integer /// a positive integer larger or equal to 1<<63 ++}; ++} ++ ++namespace { ++/// @private ++namespace numberparsing { ++ ++ ++ ++#ifdef JSON_TEST_NUMBERS ++#define INVALID_NUMBER(SRC) (found_invalid_number((SRC)), NUMBER_ERROR) ++#define WRITE_INTEGER(VALUE, SRC, WRITER) (found_integer((VALUE), (SRC)), (WRITER).append_s64((VALUE))) ++#define WRITE_UNSIGNED(VALUE, SRC, WRITER) (found_unsigned_integer((VALUE), (SRC)), (WRITER).append_u64((VALUE))) ++#define WRITE_DOUBLE(VALUE, SRC, WRITER) (found_float((VALUE), (SRC)), (WRITER).append_double((VALUE))) ++#else ++#define INVALID_NUMBER(SRC) (NUMBER_ERROR) ++#define WRITE_INTEGER(VALUE, SRC, WRITER) (WRITER).append_s64((VALUE)) ++#define WRITE_UNSIGNED(VALUE, SRC, WRITER) (WRITER).append_u64((VALUE)) ++#define WRITE_DOUBLE(VALUE, SRC, WRITER) (WRITER).append_double((VALUE)) ++#endif ++ ++namespace { ++// Convert a mantissa, an exponent and a sign bit into an ieee64 double. ++// The real_exponent needs to be in [0, 2046] (technically real_exponent = 2047 would be acceptable). ++// The mantissa should be in [0,1<<53). The bit at index (1ULL << 52) while be zeroed. ++simdjson_inline double to_double(uint64_t mantissa, uint64_t real_exponent, bool negative) { ++ double d; ++ mantissa &= ~(1ULL << 52); ++ mantissa |= real_exponent << 52; ++ mantissa |= ((static_cast(negative)) << 63); ++ std::memcpy(&d, &mantissa, sizeof(d)); ++ return d; ++} ++} ++// Attempts to compute i * 10^(power) exactly; and if "negative" is ++// true, negate the result. ++// This function will only work in some cases, when it does not work, success is ++// set to false. This should work *most of the time* (like 99% of the time). ++// We assume that power is in the [smallest_power, ++// largest_power] interval: the caller is responsible for this check. ++simdjson_inline bool compute_float_64(int64_t power, uint64_t i, bool negative, double &d) { ++ // we start with a fast path ++ // It was described in ++ // Clinger WD. How to read floating point numbers accurately. ++ // ACM SIGPLAN Notices. 1990 ++#ifndef FLT_EVAL_METHOD ++#error "FLT_EVAL_METHOD should be defined, please include cfloat." ++#endif ++#if (FLT_EVAL_METHOD != 1) && (FLT_EVAL_METHOD != 0) ++ // We cannot be certain that x/y is rounded to nearest. ++ if (0 <= power && power <= 22 && i <= 9007199254740991) { ++#else ++ if (-22 <= power && power <= 22 && i <= 9007199254740991) { ++#endif ++ // convert the integer into a double. This is lossless since ++ // 0 <= i <= 2^53 - 1. ++ d = double(i); ++ // ++ // The general idea is as follows. ++ // If 0 <= s < 2^53 and if 10^0 <= p <= 10^22 then ++ // 1) Both s and p can be represented exactly as 64-bit floating-point ++ // values ++ // (binary64). ++ // 2) Because s and p can be represented exactly as floating-point values, ++ // then s * p ++ // and s / p will produce correctly rounded values. ++ // ++ if (power < 0) { ++ d = d / simdjson::internal::power_of_ten[-power]; ++ } else { ++ d = d * simdjson::internal::power_of_ten[power]; ++ } ++ if (negative) { ++ d = -d; ++ } ++ return true; ++ } ++ // When 22 < power && power < 22 + 16, we could ++ // hope for another, secondary fast path. It was ++ // described by David M. Gay in "Correctly rounded ++ // binary-decimal and decimal-binary conversions." (1990) ++ // If you need to compute i * 10^(22 + x) for x < 16, ++ // first compute i * 10^x, if you know that result is exact ++ // (e.g., when i * 10^x < 2^53), ++ // then you can still proceed and do (i * 10^x) * 10^22. ++ // Is this worth your time? ++ // You need 22 < power *and* power < 22 + 16 *and* (i * 10^(x-22) < 2^53) ++ // for this second fast path to work. ++ // If you you have 22 < power *and* power < 22 + 16, and then you ++ // optimistically compute "i * 10^(x-22)", there is still a chance that you ++ // have wasted your time if i * 10^(x-22) >= 2^53. It makes the use cases of ++ // this optimization maybe less common than we would like. Source: ++ // http://www.exploringbinary.com/fast-path-decimal-to-floating-point-conversion/ ++ // also used in RapidJSON: https://rapidjson.org/strtod_8h_source.html ++ ++ // The fast path has now failed, so we are failing back on the slower path. ++ ++ // In the slow path, we need to adjust i so that it is > 1<<63 which is always ++ // possible, except if i == 0, so we handle i == 0 separately. ++ if(i == 0) { ++ d = negative ? -0.0 : 0.0; ++ return true; ++ } ++ ++ ++ // The exponent is 1024 + 63 + power ++ // + floor(log(5**power)/log(2)). ++ // The 1024 comes from the ieee64 standard. ++ // The 63 comes from the fact that we use a 64-bit word. ++ // ++ // Computing floor(log(5**power)/log(2)) could be ++ // slow. Instead we use a fast function. ++ // ++ // For power in (-400,350), we have that ++ // (((152170 + 65536) * power ) >> 16); ++ // is equal to ++ // floor(log(5**power)/log(2)) + power when power >= 0 ++ // and it is equal to ++ // ceil(log(5**-power)/log(2)) + power when power < 0 ++ // ++ // The 65536 is (1<<16) and corresponds to ++ // (65536 * power) >> 16 ---> power ++ // ++ // ((152170 * power ) >> 16) is equal to ++ // floor(log(5**power)/log(2)) ++ // ++ // Note that this is not magic: 152170/(1<<16) is ++ // approximatively equal to log(5)/log(2). ++ // The 1<<16 value is a power of two; we could use a ++ // larger power of 2 if we wanted to. ++ // ++ int64_t exponent = (((152170 + 65536) * power) >> 16) + 1024 + 63; ++ ++ ++ // We want the most significant bit of i to be 1. Shift if needed. ++ int lz = leading_zeroes(i); ++ i <<= lz; ++ ++ ++ // We are going to need to do some 64-bit arithmetic to get a precise product. ++ // We use a table lookup approach. ++ // It is safe because ++ // power >= smallest_power ++ // and power <= largest_power ++ // We recover the mantissa of the power, it has a leading 1. It is always ++ // rounded down. ++ // ++ // We want the most significant 64 bits of the product. We know ++ // this will be non-zero because the most significant bit of i is ++ // 1. ++ const uint32_t index = 2 * uint32_t(power - simdjson::internal::smallest_power); ++ // Optimization: It may be that materializing the index as a variable might confuse some compilers and prevent effective complex-addressing loads. (Done for code clarity.) ++ // ++ // The full_multiplication function computes the 128-bit product of two 64-bit words ++ // with a returned value of type value128 with a "low component" corresponding to the ++ // 64-bit least significant bits of the product and with a "high component" corresponding ++ // to the 64-bit most significant bits of the product. ++ simdjson::internal::value128 firstproduct = jsoncharutils::full_multiplication(i, simdjson::internal::power_of_five_128[index]); ++ // Both i and power_of_five_128[index] have their most significant bit set to 1 which ++ // implies that the either the most or the second most significant bit of the product ++ // is 1. We pack values in this manner for efficiency reasons: it maximizes the use ++ // we make of the product. It also makes it easy to reason about the product: there ++ // is 0 or 1 leading zero in the product. ++ ++ // Unless the least significant 9 bits of the high (64-bit) part of the full ++ // product are all 1s, then we know that the most significant 55 bits are ++ // exact and no further work is needed. Having 55 bits is necessary because ++ // we need 53 bits for the mantissa but we have to have one rounding bit and ++ // we can waste a bit if the most significant bit of the product is zero. ++ if((firstproduct.high & 0x1FF) == 0x1FF) { ++ // We want to compute i * 5^q, but only care about the top 55 bits at most. ++ // Consider the scenario where q>=0. Then 5^q may not fit in 64-bits. Doing ++ // the full computation is wasteful. So we do what is called a "truncated ++ // multiplication". ++ // We take the most significant 64-bits, and we put them in ++ // power_of_five_128[index]. Usually, that's good enough to approximate i * 5^q ++ // to the desired approximation using one multiplication. Sometimes it does not suffice. ++ // Then we store the next most significant 64 bits in power_of_five_128[index + 1], and ++ // then we get a better approximation to i * 5^q. In very rare cases, even that ++ // will not suffice, though it is seemingly very hard to find such a scenario. ++ // ++ // That's for when q>=0. The logic for q<0 is somewhat similar but it is somewhat ++ // more complicated. ++ // ++ // There is an extra layer of complexity in that we need more than 55 bits of ++ // accuracy in the round-to-even scenario. ++ // ++ // The full_multiplication function computes the 128-bit product of two 64-bit words ++ // with a returned value of type value128 with a "low component" corresponding to the ++ // 64-bit least significant bits of the product and with a "high component" corresponding ++ // to the 64-bit most significant bits of the product. ++ simdjson::internal::value128 secondproduct = jsoncharutils::full_multiplication(i, simdjson::internal::power_of_five_128[index + 1]); ++ firstproduct.low += secondproduct.high; ++ if(secondproduct.high > firstproduct.low) { firstproduct.high++; } ++ // At this point, we might need to add at most one to firstproduct, but this ++ // can only change the value of firstproduct.high if firstproduct.low is maximal. ++ if(simdjson_unlikely(firstproduct.low == 0xFFFFFFFFFFFFFFFF)) { ++ // This is very unlikely, but if so, we need to do much more work! ++ return false; ++ } ++ } ++ uint64_t lower = firstproduct.low; ++ uint64_t upper = firstproduct.high; ++ // The final mantissa should be 53 bits with a leading 1. ++ // We shift it so that it occupies 54 bits with a leading 1. ++ /////// ++ uint64_t upperbit = upper >> 63; ++ uint64_t mantissa = upper >> (upperbit + 9); ++ lz += int(1 ^ upperbit); ++ ++ // Here we have mantissa < (1<<54). ++ int64_t real_exponent = exponent - lz; ++ if (simdjson_unlikely(real_exponent <= 0)) { // we have a subnormal? ++ // Here have that real_exponent <= 0 so -real_exponent >= 0 ++ if(-real_exponent + 1 >= 64) { // if we have more than 64 bits below the minimum exponent, you have a zero for sure. ++ d = negative ? -0.0 : 0.0; ++ return true; ++ } ++ // next line is safe because -real_exponent + 1 < 0 ++ mantissa >>= -real_exponent + 1; ++ // Thankfully, we can't have both "round-to-even" and subnormals because ++ // "round-to-even" only occurs for powers close to 0. ++ mantissa += (mantissa & 1); // round up ++ mantissa >>= 1; ++ // There is a weird scenario where we don't have a subnormal but just. ++ // Suppose we start with 2.2250738585072013e-308, we end up ++ // with 0x3fffffffffffff x 2^-1023-53 which is technically subnormal ++ // whereas 0x40000000000000 x 2^-1023-53 is normal. Now, we need to round ++ // up 0x3fffffffffffff x 2^-1023-53 and once we do, we are no longer ++ // subnormal, but we can only know this after rounding. ++ // So we only declare a subnormal if we are smaller than the threshold. ++ real_exponent = (mantissa < (uint64_t(1) << 52)) ? 0 : 1; ++ d = to_double(mantissa, real_exponent, negative); ++ return true; ++ } ++ // We have to round to even. The "to even" part ++ // is only a problem when we are right in between two floats ++ // which we guard against. ++ // If we have lots of trailing zeros, we may fall right between two ++ // floating-point values. ++ // ++ // The round-to-even cases take the form of a number 2m+1 which is in (2^53,2^54] ++ // times a power of two. That is, it is right between a number with binary significand ++ // m and another number with binary significand m+1; and it must be the case ++ // that it cannot be represented by a float itself. ++ // ++ // We must have that w * 10 ^q == (2m+1) * 2^p for some power of two 2^p. ++ // Recall that 10^q = 5^q * 2^q. ++ // When q >= 0, we must have that (2m+1) is divible by 5^q, so 5^q <= 2^54. We have that ++ // 5^23 <= 2^54 and it is the last power of five to qualify, so q <= 23. ++ // When q<0, we have w >= (2m+1) x 5^{-q}. We must have that w<2^{64} so ++ // (2m+1) x 5^{-q} < 2^{64}. We have that 2m+1>2^{53}. Hence, we must have ++ // 2^{53} x 5^{-q} < 2^{64}. ++ // Hence we have 5^{-q} < 2^{11}$ or q>= -4. ++ // ++ // We require lower <= 1 and not lower == 0 because we could not prove that ++ // that lower == 0 is implied; but we could prove that lower <= 1 is a necessary and sufficient test. ++ if (simdjson_unlikely((lower <= 1) && (power >= -4) && (power <= 23) && ((mantissa & 3) == 1))) { ++ if((mantissa << (upperbit + 64 - 53 - 2)) == upper) { ++ mantissa &= ~1; // flip it so that we do not round up ++ } ++ } ++ ++ mantissa += mantissa & 1; ++ mantissa >>= 1; ++ ++ // Here we have mantissa < (1<<53), unless there was an overflow ++ if (mantissa >= (1ULL << 53)) { ++ ////////// ++ // This will happen when parsing values such as 7.2057594037927933e+16 ++ //////// ++ mantissa = (1ULL << 52); ++ real_exponent++; ++ } ++ mantissa &= ~(1ULL << 52); ++ // we have to check that real_exponent is in range, otherwise we bail out ++ if (simdjson_unlikely(real_exponent > 2046)) { ++ // We have an infinite value!!! We could actually throw an error here if we could. ++ return false; ++ } ++ d = to_double(mantissa, real_exponent, negative); ++ return true; ++} ++ ++// We call a fallback floating-point parser that might be slow. Note ++// it will accept JSON numbers, but the JSON spec. is more restrictive so ++// before you call parse_float_fallback, you need to have validated the input ++// string with the JSON grammar. ++// It will return an error (false) if the parsed number is infinite. ++// The string parsing itself always succeeds. We know that there is at least ++// one digit. ++static bool parse_float_fallback(const uint8_t *ptr, double *outDouble) { ++ *outDouble = simdjson::internal::from_chars(reinterpret_cast(ptr)); ++ // We do not accept infinite values. ++ ++ // Detecting finite values in a portable manner is ridiculously hard, ideally ++ // we would want to do: ++ // return !std::isfinite(*outDouble); ++ // but that mysteriously fails under legacy/old libc++ libraries, see ++ // https://github.com/simdjson/simdjson/issues/1286 ++ // ++ // Therefore, fall back to this solution (the extra parens are there ++ // to handle that max may be a macro on windows). ++ return !(*outDouble > (std::numeric_limits::max)() || *outDouble < std::numeric_limits::lowest()); ++} ++static bool parse_float_fallback(const uint8_t *ptr, const uint8_t *end_ptr, double *outDouble) { ++ *outDouble = simdjson::internal::from_chars(reinterpret_cast(ptr), reinterpret_cast(end_ptr)); ++ // We do not accept infinite values. ++ ++ // Detecting finite values in a portable manner is ridiculously hard, ideally ++ // we would want to do: ++ // return !std::isfinite(*outDouble); ++ // but that mysteriously fails under legacy/old libc++ libraries, see ++ // https://github.com/simdjson/simdjson/issues/1286 ++ // ++ // Therefore, fall back to this solution (the extra parens are there ++ // to handle that max may be a macro on windows). ++ return !(*outDouble > (std::numeric_limits::max)() || *outDouble < std::numeric_limits::lowest()); ++} ++ ++// check quickly whether the next 8 chars are made of digits ++// at a glance, it looks better than Mula's ++// http://0x80.pl/articles/swar-digits-validate.html ++simdjson_inline bool is_made_of_eight_digits_fast(const uint8_t *chars) { ++ uint64_t val; ++ // this can read up to 7 bytes beyond the buffer size, but we require ++ // SIMDJSON_PADDING of padding ++ static_assert(7 <= SIMDJSON_PADDING, "SIMDJSON_PADDING must be bigger than 7"); ++ std::memcpy(&val, chars, 8); ++ // a branchy method might be faster: ++ // return (( val & 0xF0F0F0F0F0F0F0F0 ) == 0x3030303030303030) ++ // && (( (val + 0x0606060606060606) & 0xF0F0F0F0F0F0F0F0 ) == ++ // 0x3030303030303030); ++ return (((val & 0xF0F0F0F0F0F0F0F0) | ++ (((val + 0x0606060606060606) & 0xF0F0F0F0F0F0F0F0) >> 4)) == ++ 0x3333333333333333); ++} ++ ++template ++error_code slow_float_parsing(simdjson_unused const uint8_t * src, W writer) { ++ double d; ++ if (parse_float_fallback(src, &d)) { ++ writer.append_double(d); ++ return SUCCESS; ++ } ++ return INVALID_NUMBER(src); ++} ++ ++template ++SIMDJSON_NO_SANITIZE_UNDEFINED // We deliberately allow overflow here and check later ++simdjson_inline bool parse_digit(const uint8_t c, I &i) { ++ const uint8_t digit = static_cast(c - '0'); ++ if (digit > 9) { ++ return false; ++ } ++ // PERF NOTE: multiplication by 10 is cheaper than arbitrary integer multiplication ++ i = 10 * i + digit; // might overflow, we will handle the overflow later ++ return true; ++} ++ ++simdjson_inline error_code parse_decimal(simdjson_unused const uint8_t *const src, const uint8_t *&p, uint64_t &i, int64_t &exponent) { ++ // we continue with the fiction that we have an integer. If the ++ // floating point number is representable as x * 10^z for some integer ++ // z that fits in 53 bits, then we will be able to convert back the ++ // the integer into a float in a lossless manner. ++ const uint8_t *const first_after_period = p; ++ ++#ifdef SIMDJSON_SWAR_NUMBER_PARSING ++#if SIMDJSON_SWAR_NUMBER_PARSING ++ // this helps if we have lots of decimals! ++ // this turns out to be frequent enough. ++ if (is_made_of_eight_digits_fast(p)) { ++ i = i * 100000000 + parse_eight_digits_unrolled(p); ++ p += 8; ++ } ++#endif // SIMDJSON_SWAR_NUMBER_PARSING ++#endif // #ifdef SIMDJSON_SWAR_NUMBER_PARSING ++ // Unrolling the first digit makes a small difference on some implementations (e.g. westmere) ++ if (parse_digit(*p, i)) { ++p; } ++ while (parse_digit(*p, i)) { p++; } ++ exponent = first_after_period - p; ++ // Decimal without digits (123.) is illegal ++ if (exponent == 0) { ++ return INVALID_NUMBER(src); ++ } ++ return SUCCESS; ++} ++ ++simdjson_inline error_code parse_exponent(simdjson_unused const uint8_t *const src, const uint8_t *&p, int64_t &exponent) { ++ // Exp Sign: -123.456e[-]78 ++ bool neg_exp = ('-' == *p); ++ if (neg_exp || '+' == *p) { p++; } // Skip + as well ++ ++ // Exponent: -123.456e-[78] ++ auto start_exp = p; ++ int64_t exp_number = 0; ++ while (parse_digit(*p, exp_number)) { ++p; } ++ // It is possible for parse_digit to overflow. ++ // In particular, it could overflow to INT64_MIN, and we cannot do - INT64_MIN. ++ // Thus we *must* check for possible overflow before we negate exp_number. ++ ++ // Performance notes: it may seem like combining the two "simdjson_unlikely checks" below into ++ // a single simdjson_unlikely path would be faster. The reasoning is sound, but the compiler may ++ // not oblige and may, in fact, generate two distinct paths in any case. It might be ++ // possible to do uint64_t(p - start_exp - 1) >= 18 but it could end up trading off ++ // instructions for a simdjson_likely branch, an unconclusive gain. ++ ++ // If there were no digits, it's an error. ++ if (simdjson_unlikely(p == start_exp)) { ++ return INVALID_NUMBER(src); ++ } ++ // We have a valid positive exponent in exp_number at this point, except that ++ // it may have overflowed. ++ ++ // If there were more than 18 digits, we may have overflowed the integer. We have to do ++ // something!!!! ++ if (simdjson_unlikely(p > start_exp+18)) { ++ // Skip leading zeroes: 1e000000000000000000001 is technically valid and doesn't overflow ++ while (*start_exp == '0') { start_exp++; } ++ // 19 digits could overflow int64_t and is kind of absurd anyway. We don't ++ // support exponents smaller than -999,999,999,999,999,999 and bigger ++ // than 999,999,999,999,999,999. ++ // We can truncate. ++ // Note that 999999999999999999 is assuredly too large. The maximal ieee64 value before ++ // infinity is ~1.8e308. The smallest subnormal is ~5e-324. So, actually, we could ++ // truncate at 324. ++ // Note that there is no reason to fail per se at this point in time. ++ // E.g., 0e999999999999999999999 is a fine number. ++ if (p > start_exp+18) { exp_number = 999999999999999999; } ++ } ++ // At this point, we know that exp_number is a sane, positive, signed integer. ++ // It is <= 999,999,999,999,999,999. As long as 'exponent' is in ++ // [-8223372036854775808, 8223372036854775808], we won't overflow. Because 'exponent' ++ // is bounded in magnitude by the size of the JSON input, we are fine in this universe. ++ // To sum it up: the next line should never overflow. ++ exponent += (neg_exp ? -exp_number : exp_number); ++ return SUCCESS; ++} ++ ++simdjson_inline size_t significant_digits(const uint8_t * start_digits, size_t digit_count) { ++ // It is possible that the integer had an overflow. ++ // We have to handle the case where we have 0.0000somenumber. ++ const uint8_t *start = start_digits; ++ while ((*start == '0') || (*start == '.')) { ++start; } ++ // we over-decrement by one when there is a '.' ++ return digit_count - size_t(start - start_digits); ++} ++ ++template ++simdjson_inline error_code write_float(const uint8_t *const src, bool negative, uint64_t i, const uint8_t * start_digits, size_t digit_count, int64_t exponent, W &writer) { ++ // If we frequently had to deal with long strings of digits, ++ // we could extend our code by using a 128-bit integer instead ++ // of a 64-bit integer. However, this is uncommon in practice. ++ // ++ // 9999999999999999999 < 2**64 so we can accommodate 19 digits. ++ // If we have a decimal separator, then digit_count - 1 is the number of digits, but we ++ // may not have a decimal separator! ++ if (simdjson_unlikely(digit_count > 19 && significant_digits(start_digits, digit_count) > 19)) { ++ // Ok, chances are good that we had an overflow! ++ // this is almost never going to get called!!! ++ // we start anew, going slowly!!! ++ // This will happen in the following examples: ++ // 10000000000000000000000000000000000000000000e+308 ++ // 3.1415926535897932384626433832795028841971693993751 ++ // ++ // NOTE: This makes a *copy* of the writer and passes it to slow_float_parsing. This happens ++ // because slow_float_parsing is a non-inlined function. If we passed our writer reference to ++ // it, it would force it to be stored in memory, preventing the compiler from picking it apart ++ // and putting into registers. i.e. if we pass it as reference, it gets slow. ++ // This is what forces the skip_double, as well. ++ error_code error = slow_float_parsing(src, writer); ++ writer.skip_double(); ++ return error; ++ } ++ // NOTE: it's weird that the simdjson_unlikely() only wraps half the if, but it seems to get slower any other ++ // way we've tried: https://github.com/simdjson/simdjson/pull/990#discussion_r448497331 ++ // To future reader: we'd love if someone found a better way, or at least could explain this result! ++ if (simdjson_unlikely(exponent < simdjson::internal::smallest_power) || (exponent > simdjson::internal::largest_power)) { ++ // ++ // Important: smallest_power is such that it leads to a zero value. ++ // Observe that 18446744073709551615e-343 == 0, i.e. (2**64 - 1) e -343 is zero ++ // so something x 10^-343 goes to zero, but not so with something x 10^-342. ++ static_assert(simdjson::internal::smallest_power <= -342, "smallest_power is not small enough"); ++ // ++ if((exponent < simdjson::internal::smallest_power) || (i == 0)) { ++ // E.g. Parse "-0.0e-999" into the same value as "-0.0". See https://en.wikipedia.org/wiki/Signed_zero ++ WRITE_DOUBLE(negative ? -0.0 : 0.0, src, writer); ++ return SUCCESS; ++ } else { // (exponent > largest_power) and (i != 0) ++ // We have, for sure, an infinite value and simdjson refuses to parse infinite values. ++ return INVALID_NUMBER(src); ++ } ++ } ++ double d; ++ if (!compute_float_64(exponent, i, negative, d)) { ++ // we are almost never going to get here. ++ if (!parse_float_fallback(src, &d)) { return INVALID_NUMBER(src); } ++ } ++ WRITE_DOUBLE(d, src, writer); ++ return SUCCESS; ++} ++ ++// for performance analysis, it is sometimes useful to skip parsing ++#ifdef SIMDJSON_SKIPNUMBERPARSING ++ ++template ++simdjson_inline error_code parse_number(const uint8_t *const, W &writer) { ++ writer.append_s64(0); // always write zero ++ return SUCCESS; // always succeeds ++} ++ ++simdjson_unused simdjson_inline simdjson_result parse_unsigned(const uint8_t * const src) noexcept { return 0; } ++simdjson_unused simdjson_inline simdjson_result parse_integer(const uint8_t * const src) noexcept { return 0; } ++simdjson_unused simdjson_inline simdjson_result parse_double(const uint8_t * const src) noexcept { return 0; } ++simdjson_unused simdjson_inline simdjson_result parse_unsigned_in_string(const uint8_t * const src) noexcept { return 0; } ++simdjson_unused simdjson_inline simdjson_result parse_integer_in_string(const uint8_t * const src) noexcept { return 0; } ++simdjson_unused simdjson_inline simdjson_result parse_double_in_string(const uint8_t * const src) noexcept { return 0; } ++simdjson_unused simdjson_inline bool is_negative(const uint8_t * src) noexcept { return false; } ++simdjson_unused simdjson_inline simdjson_result is_integer(const uint8_t * src) noexcept { return false; } ++simdjson_unused simdjson_inline simdjson_result get_number_type(const uint8_t * src) noexcept { return ondemand::number_type::signed_integer; } ++#else ++ ++// parse the number at src ++// define JSON_TEST_NUMBERS for unit testing ++// ++// It is assumed that the number is followed by a structural ({,},],[) character ++// or a white space character. If that is not the case (e.g., when the JSON ++// document is made of a single number), then it is necessary to copy the ++// content and append a space before calling this function. ++// ++// Our objective is accurate parsing (ULP of 0) at high speed. ++template ++simdjson_inline error_code parse_number(const uint8_t *const src, W &writer) { ++ ++ // ++ // Check for minus sign ++ // ++ bool negative = (*src == '-'); ++ const uint8_t *p = src + uint8_t(negative); ++ ++ // ++ // Parse the integer part. ++ // ++ // PERF NOTE: we don't use is_made_of_eight_digits_fast because large integers like 123456789 are rare ++ const uint8_t *const start_digits = p; ++ uint64_t i = 0; ++ while (parse_digit(*p, i)) { p++; } ++ ++ // If there were no digits, or if the integer starts with 0 and has more than one digit, it's an error. ++ // Optimization note: size_t is expected to be unsigned. ++ size_t digit_count = size_t(p - start_digits); ++ if (digit_count == 0 || ('0' == *start_digits && digit_count > 1)) { return INVALID_NUMBER(src); } ++ ++ // ++ // Handle floats if there is a . or e (or both) ++ // ++ int64_t exponent = 0; ++ bool is_float = false; ++ if ('.' == *p) { ++ is_float = true; ++ ++p; ++ SIMDJSON_TRY( parse_decimal(src, p, i, exponent) ); ++ digit_count = int(p - start_digits); // used later to guard against overflows ++ } ++ if (('e' == *p) || ('E' == *p)) { ++ is_float = true; ++ ++p; ++ SIMDJSON_TRY( parse_exponent(src, p, exponent) ); ++ } ++ if (is_float) { ++ const bool dirty_end = jsoncharutils::is_not_structural_or_whitespace(*p); ++ SIMDJSON_TRY( write_float(src, negative, i, start_digits, digit_count, exponent, writer) ); ++ if (dirty_end) { return INVALID_NUMBER(src); } ++ return SUCCESS; ++ } ++ ++ // The longest negative 64-bit number is 19 digits. ++ // The longest positive 64-bit number is 20 digits. ++ // We do it this way so we don't trigger this branch unless we must. ++ size_t longest_digit_count = negative ? 19 : 20; ++ if (digit_count > longest_digit_count) { return INVALID_NUMBER(src); } ++ if (digit_count == longest_digit_count) { ++ if (negative) { ++ // Anything negative above INT64_MAX+1 is invalid ++ if (i > uint64_t(INT64_MAX)+1) { return INVALID_NUMBER(src); } ++ WRITE_INTEGER(~i+1, src, writer); ++ if (jsoncharutils::is_not_structural_or_whitespace(*p)) { return INVALID_NUMBER(src); } ++ return SUCCESS; ++ // Positive overflow check: ++ // - A 20 digit number starting with 2-9 is overflow, because 18,446,744,073,709,551,615 is the ++ // biggest uint64_t. ++ // - A 20 digit number starting with 1 is overflow if it is less than INT64_MAX. ++ // If we got here, it's a 20 digit number starting with the digit "1". ++ // - If a 20 digit number starting with 1 overflowed (i*10+digit), the result will be smaller ++ // than 1,553,255,926,290,448,384. ++ // - That is smaller than the smallest possible 20-digit number the user could write: ++ // 10,000,000,000,000,000,000. ++ // - Therefore, if the number is positive and lower than that, it's overflow. ++ // - The value we are looking at is less than or equal to INT64_MAX. ++ // ++ } else if (src[0] != uint8_t('1') || i <= uint64_t(INT64_MAX)) { return INVALID_NUMBER(src); } ++ } ++ ++ // Write unsigned if it doesn't fit in a signed integer. ++ if (i > uint64_t(INT64_MAX)) { ++ WRITE_UNSIGNED(i, src, writer); ++ } else { ++ WRITE_INTEGER(negative ? (~i+1) : i, src, writer); ++ } ++ if (jsoncharutils::is_not_structural_or_whitespace(*p)) { return INVALID_NUMBER(src); } ++ return SUCCESS; ++} ++ ++// Inlineable functions ++namespace { ++ ++// This table can be used to characterize the final character of an integer ++// string. For JSON structural character and allowable white space characters, ++// we return SUCCESS. For 'e', '.' and 'E', we return INCORRECT_TYPE. Otherwise ++// we return NUMBER_ERROR. ++// Optimization note: we could easily reduce the size of the table by half (to 128) ++// at the cost of an extra branch. ++// Optimization note: we want the values to use at most 8 bits (not, e.g., 32 bits): ++static_assert(error_code(uint8_t(NUMBER_ERROR))== NUMBER_ERROR, "bad NUMBER_ERROR cast"); ++static_assert(error_code(uint8_t(SUCCESS))== SUCCESS, "bad NUMBER_ERROR cast"); ++static_assert(error_code(uint8_t(INCORRECT_TYPE))== INCORRECT_TYPE, "bad NUMBER_ERROR cast"); ++ ++const uint8_t integer_string_finisher[256] = { ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, SUCCESS, ++ SUCCESS, NUMBER_ERROR, NUMBER_ERROR, SUCCESS, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, SUCCESS, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, SUCCESS, ++ NUMBER_ERROR, INCORRECT_TYPE, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, SUCCESS, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, INCORRECT_TYPE, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, SUCCESS, NUMBER_ERROR, SUCCESS, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, INCORRECT_TYPE, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, SUCCESS, NUMBER_ERROR, ++ SUCCESS, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR}; ++ ++// Parse any number from 0 to 18,446,744,073,709,551,615 ++simdjson_unused simdjson_inline simdjson_result parse_unsigned(const uint8_t * const src) noexcept { ++ const uint8_t *p = src; ++ // ++ // Parse the integer part. ++ // ++ // PERF NOTE: we don't use is_made_of_eight_digits_fast because large integers like 123456789 are rare ++ const uint8_t *const start_digits = p; ++ uint64_t i = 0; ++ while (parse_digit(*p, i)) { p++; } ++ ++ // If there were no digits, or if the integer starts with 0 and has more than one digit, it's an error. ++ // Optimization note: size_t is expected to be unsigned. ++ size_t digit_count = size_t(p - start_digits); ++ // The longest positive 64-bit number is 20 digits. ++ // We do it this way so we don't trigger this branch unless we must. ++ // Optimization note: the compiler can probably merge ++ // ((digit_count == 0) || (digit_count > 20)) ++ // into a single branch since digit_count is unsigned. ++ if ((digit_count == 0) || (digit_count > 20)) { return INCORRECT_TYPE; } ++ // Here digit_count > 0. ++ if (('0' == *start_digits) && (digit_count > 1)) { return NUMBER_ERROR; } ++ // We can do the following... ++ // if (!jsoncharutils::is_structural_or_whitespace(*p)) { ++ // return (*p == '.' || *p == 'e' || *p == 'E') ? INCORRECT_TYPE : NUMBER_ERROR; ++ // } ++ // as a single table lookup: ++ if (integer_string_finisher[*p] != SUCCESS) { return error_code(integer_string_finisher[*p]); } ++ ++ if (digit_count == 20) { ++ // Positive overflow check: ++ // - A 20 digit number starting with 2-9 is overflow, because 18,446,744,073,709,551,615 is the ++ // biggest uint64_t. ++ // - A 20 digit number starting with 1 is overflow if it is less than INT64_MAX. ++ // If we got here, it's a 20 digit number starting with the digit "1". ++ // - If a 20 digit number starting with 1 overflowed (i*10+digit), the result will be smaller ++ // than 1,553,255,926,290,448,384. ++ // - That is smaller than the smallest possible 20-digit number the user could write: ++ // 10,000,000,000,000,000,000. ++ // - Therefore, if the number is positive and lower than that, it's overflow. ++ // - The value we are looking at is less than or equal to INT64_MAX. ++ // ++ if (src[0] != uint8_t('1') || i <= uint64_t(INT64_MAX)) { return INCORRECT_TYPE; } ++ } ++ ++ return i; ++} ++ ++ ++// Parse any number from 0 to 18,446,744,073,709,551,615 ++// Never read at src_end or beyond ++simdjson_unused simdjson_inline simdjson_result parse_unsigned(const uint8_t * const src, const uint8_t * const src_end) noexcept { ++ const uint8_t *p = src; ++ // ++ // Parse the integer part. ++ // ++ // PERF NOTE: we don't use is_made_of_eight_digits_fast because large integers like 123456789 are rare ++ const uint8_t *const start_digits = p; ++ uint64_t i = 0; ++ while ((p != src_end) && parse_digit(*p, i)) { p++; } ++ ++ // If there were no digits, or if the integer starts with 0 and has more than one digit, it's an error. ++ // Optimization note: size_t is expected to be unsigned. ++ size_t digit_count = size_t(p - start_digits); ++ // The longest positive 64-bit number is 20 digits. ++ // We do it this way so we don't trigger this branch unless we must. ++ // Optimization note: the compiler can probably merge ++ // ((digit_count == 0) || (digit_count > 20)) ++ // into a single branch since digit_count is unsigned. ++ if ((digit_count == 0) || (digit_count > 20)) { return INCORRECT_TYPE; } ++ // Here digit_count > 0. ++ if (('0' == *start_digits) && (digit_count > 1)) { return NUMBER_ERROR; } ++ // We can do the following... ++ // if (!jsoncharutils::is_structural_or_whitespace(*p)) { ++ // return (*p == '.' || *p == 'e' || *p == 'E') ? INCORRECT_TYPE : NUMBER_ERROR; ++ // } ++ // as a single table lookup: ++ if ((p != src_end) && integer_string_finisher[*p] != SUCCESS) { return error_code(integer_string_finisher[*p]); } ++ ++ if (digit_count == 20) { ++ // Positive overflow check: ++ // - A 20 digit number starting with 2-9 is overflow, because 18,446,744,073,709,551,615 is the ++ // biggest uint64_t. ++ // - A 20 digit number starting with 1 is overflow if it is less than INT64_MAX. ++ // If we got here, it's a 20 digit number starting with the digit "1". ++ // - If a 20 digit number starting with 1 overflowed (i*10+digit), the result will be smaller ++ // than 1,553,255,926,290,448,384. ++ // - That is smaller than the smallest possible 20-digit number the user could write: ++ // 10,000,000,000,000,000,000. ++ // - Therefore, if the number is positive and lower than that, it's overflow. ++ // - The value we are looking at is less than or equal to INT64_MAX. ++ // ++ if (src[0] != uint8_t('1') || i <= uint64_t(INT64_MAX)) { return INCORRECT_TYPE; } ++ } ++ ++ return i; ++} ++ ++// Parse any number from 0 to 18,446,744,073,709,551,615 ++simdjson_unused simdjson_inline simdjson_result parse_unsigned_in_string(const uint8_t * const src) noexcept { ++ const uint8_t *p = src + 1; ++ // ++ // Parse the integer part. ++ // ++ // PERF NOTE: we don't use is_made_of_eight_digits_fast because large integers like 123456789 are rare ++ const uint8_t *const start_digits = p; ++ uint64_t i = 0; ++ while (parse_digit(*p, i)) { p++; } ++ ++ // If there were no digits, or if the integer starts with 0 and has more than one digit, it's an error. ++ // Optimization note: size_t is expected to be unsigned. ++ size_t digit_count = size_t(p - start_digits); ++ // The longest positive 64-bit number is 20 digits. ++ // We do it this way so we don't trigger this branch unless we must. ++ // Optimization note: the compiler can probably merge ++ // ((digit_count == 0) || (digit_count > 20)) ++ // into a single branch since digit_count is unsigned. ++ if ((digit_count == 0) || (digit_count > 20)) { return INCORRECT_TYPE; } ++ // Here digit_count > 0. ++ if (('0' == *start_digits) && (digit_count > 1)) { return NUMBER_ERROR; } ++ // We can do the following... ++ // if (!jsoncharutils::is_structural_or_whitespace(*p)) { ++ // return (*p == '.' || *p == 'e' || *p == 'E') ? INCORRECT_TYPE : NUMBER_ERROR; ++ // } ++ // as a single table lookup: ++ if (*p != '"') { return NUMBER_ERROR; } ++ ++ if (digit_count == 20) { ++ // Positive overflow check: ++ // - A 20 digit number starting with 2-9 is overflow, because 18,446,744,073,709,551,615 is the ++ // biggest uint64_t. ++ // - A 20 digit number starting with 1 is overflow if it is less than INT64_MAX. ++ // If we got here, it's a 20 digit number starting with the digit "1". ++ // - If a 20 digit number starting with 1 overflowed (i*10+digit), the result will be smaller ++ // than 1,553,255,926,290,448,384. ++ // - That is smaller than the smallest possible 20-digit number the user could write: ++ // 10,000,000,000,000,000,000. ++ // - Therefore, if the number is positive and lower than that, it's overflow. ++ // - The value we are looking at is less than or equal to INT64_MAX. ++ // ++ // Note: we use src[1] and not src[0] because src[0] is the quote character in this ++ // instance. ++ if (src[1] != uint8_t('1') || i <= uint64_t(INT64_MAX)) { return INCORRECT_TYPE; } ++ } ++ ++ return i; ++} ++ ++// Parse any number from -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 ++simdjson_unused simdjson_inline simdjson_result parse_integer(const uint8_t *src) noexcept { ++ // ++ // Check for minus sign ++ // ++ bool negative = (*src == '-'); ++ const uint8_t *p = src + uint8_t(negative); ++ ++ // ++ // Parse the integer part. ++ // ++ // PERF NOTE: we don't use is_made_of_eight_digits_fast because large integers like 123456789 are rare ++ const uint8_t *const start_digits = p; ++ uint64_t i = 0; ++ while (parse_digit(*p, i)) { p++; } ++ ++ // If there were no digits, or if the integer starts with 0 and has more than one digit, it's an error. ++ // Optimization note: size_t is expected to be unsigned. ++ size_t digit_count = size_t(p - start_digits); ++ // We go from ++ // -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 ++ // so we can never represent numbers that have more than 19 digits. ++ size_t longest_digit_count = 19; ++ // Optimization note: the compiler can probably merge ++ // ((digit_count == 0) || (digit_count > longest_digit_count)) ++ // into a single branch since digit_count is unsigned. ++ if ((digit_count == 0) || (digit_count > longest_digit_count)) { return INCORRECT_TYPE; } ++ // Here digit_count > 0. ++ if (('0' == *start_digits) && (digit_count > 1)) { return NUMBER_ERROR; } ++ // We can do the following... ++ // if (!jsoncharutils::is_structural_or_whitespace(*p)) { ++ // return (*p == '.' || *p == 'e' || *p == 'E') ? INCORRECT_TYPE : NUMBER_ERROR; ++ // } ++ // as a single table lookup: ++ if(integer_string_finisher[*p] != SUCCESS) { return error_code(integer_string_finisher[*p]); } ++ // Negative numbers have can go down to - INT64_MAX - 1 whereas positive numbers are limited to INT64_MAX. ++ // Performance note: This check is only needed when digit_count == longest_digit_count but it is ++ // so cheap that we might as well always make it. ++ if(i > uint64_t(INT64_MAX) + uint64_t(negative)) { return INCORRECT_TYPE; } ++ return negative ? (~i+1) : i; ++} ++ ++// Parse any number from -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 ++// Never read at src_end or beyond ++simdjson_unused simdjson_inline simdjson_result parse_integer(const uint8_t * const src, const uint8_t * const src_end) noexcept { ++ // ++ // Check for minus sign ++ // ++ if(src == src_end) { return NUMBER_ERROR; } ++ bool negative = (*src == '-'); ++ const uint8_t *p = src + uint8_t(negative); ++ ++ // ++ // Parse the integer part. ++ // ++ // PERF NOTE: we don't use is_made_of_eight_digits_fast because large integers like 123456789 are rare ++ const uint8_t *const start_digits = p; ++ uint64_t i = 0; ++ while ((p != src_end) && parse_digit(*p, i)) { p++; } ++ ++ // If there were no digits, or if the integer starts with 0 and has more than one digit, it's an error. ++ // Optimization note: size_t is expected to be unsigned. ++ size_t digit_count = size_t(p - start_digits); ++ // We go from ++ // -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 ++ // so we can never represent numbers that have more than 19 digits. ++ size_t longest_digit_count = 19; ++ // Optimization note: the compiler can probably merge ++ // ((digit_count == 0) || (digit_count > longest_digit_count)) ++ // into a single branch since digit_count is unsigned. ++ if ((digit_count == 0) || (digit_count > longest_digit_count)) { return INCORRECT_TYPE; } ++ // Here digit_count > 0. ++ if (('0' == *start_digits) && (digit_count > 1)) { return NUMBER_ERROR; } ++ // We can do the following... ++ // if (!jsoncharutils::is_structural_or_whitespace(*p)) { ++ // return (*p == '.' || *p == 'e' || *p == 'E') ? INCORRECT_TYPE : NUMBER_ERROR; ++ // } ++ // as a single table lookup: ++ if((p != src_end) && integer_string_finisher[*p] != SUCCESS) { return error_code(integer_string_finisher[*p]); } ++ // Negative numbers have can go down to - INT64_MAX - 1 whereas positive numbers are limited to INT64_MAX. ++ // Performance note: This check is only needed when digit_count == longest_digit_count but it is ++ // so cheap that we might as well always make it. ++ if(i > uint64_t(INT64_MAX) + uint64_t(negative)) { return INCORRECT_TYPE; } ++ return negative ? (~i+1) : i; ++} ++ ++// Parse any number from -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 ++simdjson_unused simdjson_inline simdjson_result parse_integer_in_string(const uint8_t *src) noexcept { ++ // ++ // Check for minus sign ++ // ++ bool negative = (*(src + 1) == '-'); ++ src += uint8_t(negative) + 1; ++ ++ // ++ // Parse the integer part. ++ // ++ // PERF NOTE: we don't use is_made_of_eight_digits_fast because large integers like 123456789 are rare ++ const uint8_t *const start_digits = src; ++ uint64_t i = 0; ++ while (parse_digit(*src, i)) { src++; } ++ ++ // If there were no digits, or if the integer starts with 0 and has more than one digit, it's an error. ++ // Optimization note: size_t is expected to be unsigned. ++ size_t digit_count = size_t(src - start_digits); ++ // We go from ++ // -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 ++ // so we can never represent numbers that have more than 19 digits. ++ size_t longest_digit_count = 19; ++ // Optimization note: the compiler can probably merge ++ // ((digit_count == 0) || (digit_count > longest_digit_count)) ++ // into a single branch since digit_count is unsigned. ++ if ((digit_count == 0) || (digit_count > longest_digit_count)) { return INCORRECT_TYPE; } ++ // Here digit_count > 0. ++ if (('0' == *start_digits) && (digit_count > 1)) { return NUMBER_ERROR; } ++ // We can do the following... ++ // if (!jsoncharutils::is_structural_or_whitespace(*src)) { ++ // return (*src == '.' || *src == 'e' || *src == 'E') ? INCORRECT_TYPE : NUMBER_ERROR; ++ // } ++ // as a single table lookup: ++ if(*src != '"') { return NUMBER_ERROR; } ++ // Negative numbers have can go down to - INT64_MAX - 1 whereas positive numbers are limited to INT64_MAX. ++ // Performance note: This check is only needed when digit_count == longest_digit_count but it is ++ // so cheap that we might as well always make it. ++ if(i > uint64_t(INT64_MAX) + uint64_t(negative)) { return INCORRECT_TYPE; } ++ return negative ? (~i+1) : i; ++} ++ ++simdjson_unused simdjson_inline simdjson_result parse_double(const uint8_t * src) noexcept { ++ // ++ // Check for minus sign ++ // ++ bool negative = (*src == '-'); ++ src += uint8_t(negative); ++ ++ // ++ // Parse the integer part. ++ // ++ uint64_t i = 0; ++ const uint8_t *p = src; ++ p += parse_digit(*p, i); ++ bool leading_zero = (i == 0); ++ while (parse_digit(*p, i)) { p++; } ++ // no integer digits, or 0123 (zero must be solo) ++ if ( p == src ) { return INCORRECT_TYPE; } ++ if ( (leading_zero && p != src+1)) { return NUMBER_ERROR; } ++ ++ // ++ // Parse the decimal part. ++ // ++ int64_t exponent = 0; ++ bool overflow; ++ if (simdjson_likely(*p == '.')) { ++ p++; ++ const uint8_t *start_decimal_digits = p; ++ if (!parse_digit(*p, i)) { return NUMBER_ERROR; } // no decimal digits ++ p++; ++ while (parse_digit(*p, i)) { p++; } ++ exponent = -(p - start_decimal_digits); ++ ++ // Overflow check. More than 19 digits (minus the decimal) may be overflow. ++ overflow = p-src-1 > 19; ++ if (simdjson_unlikely(overflow && leading_zero)) { ++ // Skip leading 0.00000 and see if it still overflows ++ const uint8_t *start_digits = src + 2; ++ while (*start_digits == '0') { start_digits++; } ++ overflow = start_digits-src > 19; ++ } ++ } else { ++ overflow = p-src > 19; ++ } ++ ++ // ++ // Parse the exponent ++ // ++ if (*p == 'e' || *p == 'E') { ++ p++; ++ bool exp_neg = *p == '-'; ++ p += exp_neg || *p == '+'; ++ ++ uint64_t exp = 0; ++ const uint8_t *start_exp_digits = p; ++ while (parse_digit(*p, exp)) { p++; } ++ // no exp digits, or 20+ exp digits ++ if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } ++ ++ exponent += exp_neg ? 0-exp : exp; ++ } ++ ++ if (jsoncharutils::is_not_structural_or_whitespace(*p)) { return NUMBER_ERROR; } ++ ++ overflow = overflow || exponent < simdjson::internal::smallest_power || exponent > simdjson::internal::largest_power; ++ ++ // ++ // Assemble (or slow-parse) the float ++ // ++ double d; ++ if (simdjson_likely(!overflow)) { ++ if (compute_float_64(exponent, i, negative, d)) { return d; } ++ } ++ if (!parse_float_fallback(src - uint8_t(negative), &d)) { ++ return NUMBER_ERROR; ++ } ++ return d; ++} ++ ++simdjson_unused simdjson_inline bool is_negative(const uint8_t * src) noexcept { ++ return (*src == '-'); ++} ++ ++simdjson_unused simdjson_inline simdjson_result is_integer(const uint8_t * src) noexcept { ++ bool negative = (*src == '-'); ++ src += uint8_t(negative); ++ const uint8_t *p = src; ++ while(static_cast(*p - '0') <= 9) { p++; } ++ if ( p == src ) { return NUMBER_ERROR; } ++ if (jsoncharutils::is_structural_or_whitespace(*p)) { return true; } ++ return false; ++} ++ ++simdjson_unused simdjson_inline simdjson_result get_number_type(const uint8_t * src) noexcept { ++ bool negative = (*src == '-'); ++ src += uint8_t(negative); ++ const uint8_t *p = src; ++ while(static_cast(*p - '0') <= 9) { p++; } ++ if ( p == src ) { return NUMBER_ERROR; } ++ if (jsoncharutils::is_structural_or_whitespace(*p)) { ++ // We have an integer. ++ // If the number is negative and valid, it must be a signed integer. ++ if(negative) { return ondemand::number_type::signed_integer; } ++ // We want values larger or equal to 9223372036854775808 to be unsigned ++ // integers, and the other values to be signed integers. ++ int digit_count = int(p - src); ++ if(digit_count >= 19) { ++ const uint8_t * smaller_big_integer = reinterpret_cast("9223372036854775808"); ++ if((digit_count >= 20) || (memcmp(src, smaller_big_integer, 19) >= 0)) { ++ return ondemand::number_type::unsigned_integer; ++ } ++ } ++ return ondemand::number_type::signed_integer; ++ } ++ // Hopefully, we have 'e' or 'E' or '.'. ++ return ondemand::number_type::floating_point_number; ++} ++ ++// Never read at src_end or beyond ++simdjson_unused simdjson_inline simdjson_result parse_double(const uint8_t * src, const uint8_t * const src_end) noexcept { ++ if(src == src_end) { return NUMBER_ERROR; } ++ // ++ // Check for minus sign ++ // ++ bool negative = (*src == '-'); ++ src += uint8_t(negative); ++ ++ // ++ // Parse the integer part. ++ // ++ uint64_t i = 0; ++ const uint8_t *p = src; ++ if(p == src_end) { return NUMBER_ERROR; } ++ p += parse_digit(*p, i); ++ bool leading_zero = (i == 0); ++ while ((p != src_end) && parse_digit(*p, i)) { p++; } ++ // no integer digits, or 0123 (zero must be solo) ++ if ( p == src ) { return INCORRECT_TYPE; } ++ if ( (leading_zero && p != src+1)) { return NUMBER_ERROR; } ++ ++ // ++ // Parse the decimal part. ++ // ++ int64_t exponent = 0; ++ bool overflow; ++ if (simdjson_likely((p != src_end) && (*p == '.'))) { ++ p++; ++ const uint8_t *start_decimal_digits = p; ++ if ((p == src_end) || !parse_digit(*p, i)) { return NUMBER_ERROR; } // no decimal digits ++ p++; ++ while ((p != src_end) && parse_digit(*p, i)) { p++; } ++ exponent = -(p - start_decimal_digits); ++ ++ // Overflow check. More than 19 digits (minus the decimal) may be overflow. ++ overflow = p-src-1 > 19; ++ if (simdjson_unlikely(overflow && leading_zero)) { ++ // Skip leading 0.00000 and see if it still overflows ++ const uint8_t *start_digits = src + 2; ++ while (*start_digits == '0') { start_digits++; } ++ overflow = start_digits-src > 19; ++ } ++ } else { ++ overflow = p-src > 19; ++ } ++ ++ // ++ // Parse the exponent ++ // ++ if ((p != src_end) && (*p == 'e' || *p == 'E')) { ++ p++; ++ if(p == src_end) { return NUMBER_ERROR; } ++ bool exp_neg = *p == '-'; ++ p += exp_neg || *p == '+'; ++ ++ uint64_t exp = 0; ++ const uint8_t *start_exp_digits = p; ++ while ((p != src_end) && parse_digit(*p, exp)) { p++; } ++ // no exp digits, or 20+ exp digits ++ if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } ++ ++ exponent += exp_neg ? 0-exp : exp; ++ } ++ ++ if ((p != src_end) && jsoncharutils::is_not_structural_or_whitespace(*p)) { return NUMBER_ERROR; } ++ ++ overflow = overflow || exponent < simdjson::internal::smallest_power || exponent > simdjson::internal::largest_power; ++ ++ // ++ // Assemble (or slow-parse) the float ++ // ++ double d; ++ if (simdjson_likely(!overflow)) { ++ if (compute_float_64(exponent, i, negative, d)) { return d; } ++ } ++ if (!parse_float_fallback(src - uint8_t(negative), src_end, &d)) { ++ return NUMBER_ERROR; ++ } ++ return d; ++} ++ ++simdjson_unused simdjson_inline simdjson_result parse_double_in_string(const uint8_t * src) noexcept { ++ // ++ // Check for minus sign ++ // ++ bool negative = (*(src + 1) == '-'); ++ src += uint8_t(negative) + 1; ++ ++ // ++ // Parse the integer part. ++ // ++ uint64_t i = 0; ++ const uint8_t *p = src; ++ p += parse_digit(*p, i); ++ bool leading_zero = (i == 0); ++ while (parse_digit(*p, i)) { p++; } ++ // no integer digits, or 0123 (zero must be solo) ++ if ( p == src ) { return INCORRECT_TYPE; } ++ if ( (leading_zero && p != src+1)) { return NUMBER_ERROR; } ++ ++ // ++ // Parse the decimal part. ++ // ++ int64_t exponent = 0; ++ bool overflow; ++ if (simdjson_likely(*p == '.')) { ++ p++; ++ const uint8_t *start_decimal_digits = p; ++ if (!parse_digit(*p, i)) { return NUMBER_ERROR; } // no decimal digits ++ p++; ++ while (parse_digit(*p, i)) { p++; } ++ exponent = -(p - start_decimal_digits); ++ ++ // Overflow check. More than 19 digits (minus the decimal) may be overflow. ++ overflow = p-src-1 > 19; ++ if (simdjson_unlikely(overflow && leading_zero)) { ++ // Skip leading 0.00000 and see if it still overflows ++ const uint8_t *start_digits = src + 2; ++ while (*start_digits == '0') { start_digits++; } ++ overflow = start_digits-src > 19; ++ } ++ } else { ++ overflow = p-src > 19; ++ } ++ ++ // ++ // Parse the exponent ++ // ++ if (*p == 'e' || *p == 'E') { ++ p++; ++ bool exp_neg = *p == '-'; ++ p += exp_neg || *p == '+'; ++ ++ uint64_t exp = 0; ++ const uint8_t *start_exp_digits = p; ++ while (parse_digit(*p, exp)) { p++; } ++ // no exp digits, or 20+ exp digits ++ if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } ++ ++ exponent += exp_neg ? 0-exp : exp; ++ } ++ ++ if (*p != '"') { return NUMBER_ERROR; } ++ ++ overflow = overflow || exponent < simdjson::internal::smallest_power || exponent > simdjson::internal::largest_power; ++ ++ // ++ // Assemble (or slow-parse) the float ++ // ++ double d; ++ if (simdjson_likely(!overflow)) { ++ if (compute_float_64(exponent, i, negative, d)) { return d; } ++ } ++ if (!parse_float_fallback(src - uint8_t(negative), &d)) { ++ return NUMBER_ERROR; ++ } ++ return d; ++} ++} //namespace {} ++#endif // SIMDJSON_SKIPNUMBERPARSING ++ ++} // namespace numberparsing ++} // unnamed namespace ++} // namespace arm64 ++} // namespace simdjson ++/* end file include/simdjson/generic/numberparsing.h */ ++ ++#endif // SIMDJSON_ARM64_NUMBERPARSING_H ++/* end file include/simdjson/arm64/numberparsing.h */ ++/* begin file include/simdjson/arm64/end.h */ ++/* end file include/simdjson/arm64/end.h */ ++ ++#endif // SIMDJSON_IMPLEMENTATION_ARM64 ++ ++#endif // SIMDJSON_ARM64_H ++/* end file include/simdjson/arm64.h */ ++/* begin file include/simdjson/fallback.h */ ++#ifndef SIMDJSON_FALLBACK_H ++#define SIMDJSON_FALLBACK_H ++ ++ ++#if SIMDJSON_IMPLEMENTATION_FALLBACK ++ ++namespace simdjson { ++/** ++ * Fallback implementation (runs on any machine). ++ */ ++namespace fallback { ++} // namespace fallback ++} // namespace simdjson ++ ++/* begin file include/simdjson/fallback/implementation.h */ ++#ifndef SIMDJSON_FALLBACK_IMPLEMENTATION_H ++#define SIMDJSON_FALLBACK_IMPLEMENTATION_H ++ ++ ++namespace simdjson { ++namespace fallback { ++ ++namespace { ++using namespace simdjson; ++using namespace simdjson::dom; ++} ++ ++class implementation final : public simdjson::implementation { ++public: ++ simdjson_inline implementation() : simdjson::implementation( ++ "fallback", ++ "Generic fallback implementation", ++ 0 ++ ) {} ++ simdjson_warn_unused error_code create_dom_parser_implementation( ++ size_t capacity, ++ size_t max_length, ++ std::unique_ptr& dst ++ ) const noexcept final; ++ simdjson_warn_unused error_code minify(const uint8_t *buf, size_t len, uint8_t *dst, size_t &dst_len) const noexcept final; ++ simdjson_warn_unused bool validate_utf8(const char *buf, size_t len) const noexcept final; ++}; ++ ++} // namespace fallback ++} // namespace simdjson ++ ++#endif // SIMDJSON_FALLBACK_IMPLEMENTATION_H ++/* end file include/simdjson/fallback/implementation.h */ ++ ++/* begin file include/simdjson/fallback/begin.h */ ++// redefining SIMDJSON_IMPLEMENTATION to "fallback" ++// #define SIMDJSON_IMPLEMENTATION fallback ++/* end file include/simdjson/fallback/begin.h */ ++ ++// Declarations ++/* begin file include/simdjson/generic/dom_parser_implementation.h */ ++ ++namespace simdjson { ++namespace fallback { ++ ++// expectation: sizeof(open_container) = 64/8. ++struct open_container { ++ uint32_t tape_index; // where, on the tape, does the scope ([,{) begins ++ uint32_t count; // how many elements in the scope ++}; // struct open_container ++ ++static_assert(sizeof(open_container) == 64/8, "Open container must be 64 bits"); ++ ++class dom_parser_implementation final : public internal::dom_parser_implementation { ++public: ++ /** Tape location of each open { or [ */ ++ std::unique_ptr open_containers{}; ++ /** Whether each open container is a [ or { */ ++ std::unique_ptr is_array{}; ++ /** Buffer passed to stage 1 */ ++ const uint8_t *buf{}; ++ /** Length passed to stage 1 */ ++ size_t len{0}; ++ /** Document passed to stage 2 */ ++ dom::document *doc{}; ++ ++ inline dom_parser_implementation() noexcept; ++ inline dom_parser_implementation(dom_parser_implementation &&other) noexcept; ++ inline dom_parser_implementation &operator=(dom_parser_implementation &&other) noexcept; ++ dom_parser_implementation(const dom_parser_implementation &) = delete; ++ dom_parser_implementation &operator=(const dom_parser_implementation &) = delete; ++ ++ simdjson_warn_unused error_code parse(const uint8_t *buf, size_t len, dom::document &doc) noexcept final; ++ simdjson_warn_unused error_code stage1(const uint8_t *buf, size_t len, stage1_mode partial) noexcept final; ++ simdjson_warn_unused error_code stage2(dom::document &doc) noexcept final; ++ simdjson_warn_unused error_code stage2_next(dom::document &doc) noexcept final; ++ simdjson_warn_unused uint8_t *parse_string(const uint8_t *src, uint8_t *dst) const noexcept final; ++ inline simdjson_warn_unused error_code set_capacity(size_t capacity) noexcept final; ++ inline simdjson_warn_unused error_code set_max_depth(size_t max_depth) noexcept final; ++private: ++ simdjson_inline simdjson_warn_unused error_code set_capacity_stage1(size_t capacity); ++ ++}; ++ ++} // namespace fallback ++} // namespace simdjson ++ ++namespace simdjson { ++namespace fallback { ++ ++inline dom_parser_implementation::dom_parser_implementation() noexcept = default; ++inline dom_parser_implementation::dom_parser_implementation(dom_parser_implementation &&other) noexcept = default; ++inline dom_parser_implementation &dom_parser_implementation::operator=(dom_parser_implementation &&other) noexcept = default; ++ ++// Leaving these here so they can be inlined if so desired ++inline simdjson_warn_unused error_code dom_parser_implementation::set_capacity(size_t capacity) noexcept { ++ if(capacity > SIMDJSON_MAXSIZE_BYTES) { return CAPACITY; } ++ // Stage 1 index output ++ size_t max_structures = SIMDJSON_ROUNDUP_N(capacity, 64) + 2 + 7; ++ structural_indexes.reset( new (std::nothrow) uint32_t[max_structures] ); ++ if (!structural_indexes) { _capacity = 0; return MEMALLOC; } ++ structural_indexes[0] = 0; ++ n_structural_indexes = 0; ++ ++ _capacity = capacity; ++ return SUCCESS; ++} ++ ++inline simdjson_warn_unused error_code dom_parser_implementation::set_max_depth(size_t max_depth) noexcept { ++ // Stage 2 stacks ++ open_containers.reset(new (std::nothrow) open_container[max_depth]); ++ is_array.reset(new (std::nothrow) bool[max_depth]); ++ if (!is_array || !open_containers) { _max_depth = 0; return MEMALLOC; } ++ ++ _max_depth = max_depth; ++ return SUCCESS; ++} ++ ++} // namespace fallback ++} // namespace simdjson ++/* end file include/simdjson/generic/dom_parser_implementation.h */ ++/* begin file include/simdjson/fallback/bitmanipulation.h */ ++#ifndef SIMDJSON_FALLBACK_BITMANIPULATION_H ++#define SIMDJSON_FALLBACK_BITMANIPULATION_H ++ ++#include ++ ++namespace simdjson { ++namespace fallback { ++namespace { ++ ++#if defined(_MSC_VER) && !defined(_M_ARM64) && !defined(_M_X64) ++static inline unsigned char _BitScanForward64(unsigned long* ret, uint64_t x) { ++ unsigned long x0 = (unsigned long)x, top, bottom; ++ _BitScanForward(&top, (unsigned long)(x >> 32)); ++ _BitScanForward(&bottom, x0); ++ *ret = x0 ? bottom : 32 + top; ++ return x != 0; ++} ++static unsigned char _BitScanReverse64(unsigned long* ret, uint64_t x) { ++ unsigned long x1 = (unsigned long)(x >> 32), top, bottom; ++ _BitScanReverse(&top, x1); ++ _BitScanReverse(&bottom, (unsigned long)x); ++ *ret = x1 ? top + 32 : bottom; ++ return x != 0; ++} ++#endif ++ ++/* result might be undefined when input_num is zero */ ++simdjson_inline int leading_zeroes(uint64_t input_num) { ++#ifdef _MSC_VER ++ unsigned long leading_zero = 0; ++ // Search the mask data from most significant bit (MSB) ++ // to least significant bit (LSB) for a set bit (1). ++ if (_BitScanReverse64(&leading_zero, input_num)) ++ return (int)(63 - leading_zero); ++ else ++ return 64; ++#else ++ return __builtin_clzll(input_num); ++#endif// _MSC_VER ++} ++ ++} // unnamed namespace ++} // namespace fallback ++} // namespace simdjson ++ ++#endif // SIMDJSON_FALLBACK_BITMANIPULATION_H ++/* end file include/simdjson/fallback/bitmanipulation.h */ ++/* begin file include/simdjson/generic/jsoncharutils.h */ ++ ++namespace simdjson { ++namespace fallback { ++namespace { ++namespace jsoncharutils { ++ ++// return non-zero if not a structural or whitespace char ++// zero otherwise ++simdjson_inline uint32_t is_not_structural_or_whitespace(uint8_t c) { ++ return internal::structural_or_whitespace_negated[c]; ++} ++ ++simdjson_inline uint32_t is_structural_or_whitespace(uint8_t c) { ++ return internal::structural_or_whitespace[c]; ++} ++ ++// returns a value with the high 16 bits set if not valid ++// otherwise returns the conversion of the 4 hex digits at src into the bottom ++// 16 bits of the 32-bit return register ++// ++// see ++// https://lemire.me/blog/2019/04/17/parsing-short-hexadecimal-strings-efficiently/ ++static inline uint32_t hex_to_u32_nocheck( ++ const uint8_t *src) { // strictly speaking, static inline is a C-ism ++ uint32_t v1 = internal::digit_to_val32[630 + src[0]]; ++ uint32_t v2 = internal::digit_to_val32[420 + src[1]]; ++ uint32_t v3 = internal::digit_to_val32[210 + src[2]]; ++ uint32_t v4 = internal::digit_to_val32[0 + src[3]]; ++ return v1 | v2 | v3 | v4; ++} ++ ++// given a code point cp, writes to c ++// the utf-8 code, outputting the length in ++// bytes, if the length is zero, the code point ++// is invalid ++// ++// This can possibly be made faster using pdep ++// and clz and table lookups, but JSON documents ++// have few escaped code points, and the following ++// function looks cheap. ++// ++// Note: we assume that surrogates are treated separately ++// ++simdjson_inline size_t codepoint_to_utf8(uint32_t cp, uint8_t *c) { ++ if (cp <= 0x7F) { ++ c[0] = uint8_t(cp); ++ return 1; // ascii ++ } ++ if (cp <= 0x7FF) { ++ c[0] = uint8_t((cp >> 6) + 192); ++ c[1] = uint8_t((cp & 63) + 128); ++ return 2; // universal plane ++ // Surrogates are treated elsewhere... ++ //} //else if (0xd800 <= cp && cp <= 0xdfff) { ++ // return 0; // surrogates // could put assert here ++ } else if (cp <= 0xFFFF) { ++ c[0] = uint8_t((cp >> 12) + 224); ++ c[1] = uint8_t(((cp >> 6) & 63) + 128); ++ c[2] = uint8_t((cp & 63) + 128); ++ return 3; ++ } else if (cp <= 0x10FFFF) { // if you know you have a valid code point, this ++ // is not needed ++ c[0] = uint8_t((cp >> 18) + 240); ++ c[1] = uint8_t(((cp >> 12) & 63) + 128); ++ c[2] = uint8_t(((cp >> 6) & 63) + 128); ++ c[3] = uint8_t((cp & 63) + 128); ++ return 4; ++ } ++ // will return 0 when the code point was too large. ++ return 0; // bad r ++} ++ ++#if SIMDJSON_IS_32BITS // _umul128 for x86, arm ++// this is a slow emulation routine for 32-bit ++// ++static simdjson_inline uint64_t __emulu(uint32_t x, uint32_t y) { ++ return x * (uint64_t)y; ++} ++static simdjson_inline uint64_t _umul128(uint64_t ab, uint64_t cd, uint64_t *hi) { ++ uint64_t ad = __emulu((uint32_t)(ab >> 32), (uint32_t)cd); ++ uint64_t bd = __emulu((uint32_t)ab, (uint32_t)cd); ++ uint64_t adbc = ad + __emulu((uint32_t)ab, (uint32_t)(cd >> 32)); ++ uint64_t adbc_carry = !!(adbc < ad); ++ uint64_t lo = bd + (adbc << 32); ++ *hi = __emulu((uint32_t)(ab >> 32), (uint32_t)(cd >> 32)) + (adbc >> 32) + ++ (adbc_carry << 32) + !!(lo < bd); ++ return lo; ++} ++#endif ++ ++using internal::value128; ++ ++simdjson_inline value128 full_multiplication(uint64_t value1, uint64_t value2) { ++ value128 answer; ++#if SIMDJSON_REGULAR_VISUAL_STUDIO || SIMDJSON_IS_32BITS ++#ifdef _M_ARM64 ++ // ARM64 has native support for 64-bit multiplications, no need to emultate ++ answer.high = __umulh(value1, value2); ++ answer.low = value1 * value2; ++#else ++ answer.low = _umul128(value1, value2, &answer.high); // _umul128 not available on ARM64 ++#endif // _M_ARM64 ++#else // SIMDJSON_REGULAR_VISUAL_STUDIO || SIMDJSON_IS_32BITS ++ __uint128_t r = (static_cast<__uint128_t>(value1)) * value2; ++ answer.low = uint64_t(r); ++ answer.high = uint64_t(r >> 64); ++#endif ++ return answer; ++} ++ ++} // namespace jsoncharutils ++} // unnamed namespace ++} // namespace fallback ++} // namespace simdjson ++/* end file include/simdjson/generic/jsoncharutils.h */ ++/* begin file include/simdjson/generic/atomparsing.h */ ++namespace simdjson { ++namespace fallback { ++namespace { ++/// @private ++namespace atomparsing { ++ ++// The string_to_uint32 is exclusively used to map literal strings to 32-bit values. ++// We use memcpy instead of a pointer cast to avoid undefined behaviors since we cannot ++// be certain that the character pointer will be properly aligned. ++// You might think that using memcpy makes this function expensive, but you'd be wrong. ++// All decent optimizing compilers (GCC, clang, Visual Studio) will compile string_to_uint32("false"); ++// to the compile-time constant 1936482662. ++simdjson_inline uint32_t string_to_uint32(const char* str) { uint32_t val; std::memcpy(&val, str, sizeof(uint32_t)); return val; } ++ ++ ++// Again in str4ncmp we use a memcpy to avoid undefined behavior. The memcpy may appear expensive. ++// Yet all decent optimizing compilers will compile memcpy to a single instruction, just about. ++simdjson_warn_unused ++simdjson_inline uint32_t str4ncmp(const uint8_t *src, const char* atom) { ++ uint32_t srcval; // we want to avoid unaligned 32-bit loads (undefined in C/C++) ++ static_assert(sizeof(uint32_t) <= SIMDJSON_PADDING, "SIMDJSON_PADDING must be larger than 4 bytes"); ++ std::memcpy(&srcval, src, sizeof(uint32_t)); ++ return srcval ^ string_to_uint32(atom); ++} ++ ++simdjson_warn_unused ++simdjson_inline bool is_valid_true_atom(const uint8_t *src) { ++ return (str4ncmp(src, "true") | jsoncharutils::is_not_structural_or_whitespace(src[4])) == 0; ++} ++ ++simdjson_warn_unused ++simdjson_inline bool is_valid_true_atom(const uint8_t *src, size_t len) { ++ if (len > 4) { return is_valid_true_atom(src); } ++ else if (len == 4) { return !str4ncmp(src, "true"); } ++ else { return false; } ++} ++ ++simdjson_warn_unused ++simdjson_inline bool is_valid_false_atom(const uint8_t *src) { ++ return (str4ncmp(src+1, "alse") | jsoncharutils::is_not_structural_or_whitespace(src[5])) == 0; ++} ++ ++simdjson_warn_unused ++simdjson_inline bool is_valid_false_atom(const uint8_t *src, size_t len) { ++ if (len > 5) { return is_valid_false_atom(src); } ++ else if (len == 5) { return !str4ncmp(src+1, "alse"); } ++ else { return false; } ++} ++ ++simdjson_warn_unused ++simdjson_inline bool is_valid_null_atom(const uint8_t *src) { ++ return (str4ncmp(src, "null") | jsoncharutils::is_not_structural_or_whitespace(src[4])) == 0; ++} ++ ++simdjson_warn_unused ++simdjson_inline bool is_valid_null_atom(const uint8_t *src, size_t len) { ++ if (len > 4) { return is_valid_null_atom(src); } ++ else if (len == 4) { return !str4ncmp(src, "null"); } ++ else { return false; } ++} ++ ++} // namespace atomparsing ++} // unnamed namespace ++} // namespace fallback ++} // namespace simdjson ++/* end file include/simdjson/generic/atomparsing.h */ ++/* begin file include/simdjson/fallback/stringparsing.h */ ++#ifndef SIMDJSON_FALLBACK_STRINGPARSING_H ++#define SIMDJSON_FALLBACK_STRINGPARSING_H ++ ++ ++namespace simdjson { ++namespace fallback { ++namespace { ++ ++// Holds backslashes and quotes locations. ++struct backslash_and_quote { ++public: ++ static constexpr uint32_t BYTES_PROCESSED = 1; ++ simdjson_inline static backslash_and_quote copy_and_find(const uint8_t *src, uint8_t *dst); ++ ++ simdjson_inline bool has_quote_first() { return c == '"'; } ++ simdjson_inline bool has_backslash() { return c == '\\'; } ++ simdjson_inline int quote_index() { return c == '"' ? 0 : 1; } ++ simdjson_inline int backslash_index() { return c == '\\' ? 0 : 1; } ++ ++ uint8_t c; ++}; // struct backslash_and_quote ++ ++simdjson_inline backslash_and_quote backslash_and_quote::copy_and_find(const uint8_t *src, uint8_t *dst) { ++ // store to dest unconditionally - we can overwrite the bits we don't like later ++ dst[0] = src[0]; ++ return { src[0] }; ++} ++ ++} // unnamed namespace ++} // namespace fallback ++} // namespace simdjson ++ ++#endif // SIMDJSON_FALLBACK_STRINGPARSING_H ++/* end file include/simdjson/fallback/stringparsing.h */ ++/* begin file include/simdjson/fallback/numberparsing.h */ ++#ifndef SIMDJSON_FALLBACK_NUMBERPARSING_H ++#define SIMDJSON_FALLBACK_NUMBERPARSING_H ++ ++#ifdef JSON_TEST_NUMBERS // for unit testing ++void found_invalid_number(const uint8_t *buf); ++void found_integer(int64_t result, const uint8_t *buf); ++void found_unsigned_integer(uint64_t result, const uint8_t *buf); ++void found_float(double result, const uint8_t *buf); ++#endif ++ ++namespace simdjson { ++namespace fallback { ++namespace { ++// credit: https://johnnylee-sde.github.io/Fast-numeric-string-to-int/ ++static simdjson_inline uint32_t parse_eight_digits_unrolled(const char *chars) { ++ uint64_t val; ++ memcpy(&val, chars, sizeof(uint64_t)); ++ val = (val & 0x0F0F0F0F0F0F0F0F) * 2561 >> 8; ++ val = (val & 0x00FF00FF00FF00FF) * 6553601 >> 16; ++ return uint32_t((val & 0x0000FFFF0000FFFF) * 42949672960001 >> 32); ++} ++static simdjson_inline uint32_t parse_eight_digits_unrolled(const uint8_t *chars) { ++ return parse_eight_digits_unrolled(reinterpret_cast(chars)); ++} ++ ++} // unnamed namespace ++} // namespace fallback ++} // namespace simdjson ++ ++#define SIMDJSON_SWAR_NUMBER_PARSING 1 ++ ++/* begin file include/simdjson/generic/numberparsing.h */ ++#include ++ ++namespace simdjson { ++namespace fallback { ++ ++namespace ondemand { ++/** ++ * The type of a JSON number ++ */ ++enum class number_type { ++ floating_point_number=1, /// a binary64 number ++ signed_integer, /// a signed integer that fits in a 64-bit word using two's complement ++ unsigned_integer /// a positive integer larger or equal to 1<<63 ++}; ++} ++ ++namespace { ++/// @private ++namespace numberparsing { ++ ++ ++ ++#ifdef JSON_TEST_NUMBERS ++#define INVALID_NUMBER(SRC) (found_invalid_number((SRC)), NUMBER_ERROR) ++#define WRITE_INTEGER(VALUE, SRC, WRITER) (found_integer((VALUE), (SRC)), (WRITER).append_s64((VALUE))) ++#define WRITE_UNSIGNED(VALUE, SRC, WRITER) (found_unsigned_integer((VALUE), (SRC)), (WRITER).append_u64((VALUE))) ++#define WRITE_DOUBLE(VALUE, SRC, WRITER) (found_float((VALUE), (SRC)), (WRITER).append_double((VALUE))) ++#else ++#define INVALID_NUMBER(SRC) (NUMBER_ERROR) ++#define WRITE_INTEGER(VALUE, SRC, WRITER) (WRITER).append_s64((VALUE)) ++#define WRITE_UNSIGNED(VALUE, SRC, WRITER) (WRITER).append_u64((VALUE)) ++#define WRITE_DOUBLE(VALUE, SRC, WRITER) (WRITER).append_double((VALUE)) ++#endif ++ ++namespace { ++// Convert a mantissa, an exponent and a sign bit into an ieee64 double. ++// The real_exponent needs to be in [0, 2046] (technically real_exponent = 2047 would be acceptable). ++// The mantissa should be in [0,1<<53). The bit at index (1ULL << 52) while be zeroed. ++simdjson_inline double to_double(uint64_t mantissa, uint64_t real_exponent, bool negative) { ++ double d; ++ mantissa &= ~(1ULL << 52); ++ mantissa |= real_exponent << 52; ++ mantissa |= ((static_cast(negative)) << 63); ++ std::memcpy(&d, &mantissa, sizeof(d)); ++ return d; ++} ++} ++// Attempts to compute i * 10^(power) exactly; and if "negative" is ++// true, negate the result. ++// This function will only work in some cases, when it does not work, success is ++// set to false. This should work *most of the time* (like 99% of the time). ++// We assume that power is in the [smallest_power, ++// largest_power] interval: the caller is responsible for this check. ++simdjson_inline bool compute_float_64(int64_t power, uint64_t i, bool negative, double &d) { ++ // we start with a fast path ++ // It was described in ++ // Clinger WD. How to read floating point numbers accurately. ++ // ACM SIGPLAN Notices. 1990 ++#ifndef FLT_EVAL_METHOD ++#error "FLT_EVAL_METHOD should be defined, please include cfloat." ++#endif ++#if (FLT_EVAL_METHOD != 1) && (FLT_EVAL_METHOD != 0) ++ // We cannot be certain that x/y is rounded to nearest. ++ if (0 <= power && power <= 22 && i <= 9007199254740991) { ++#else ++ if (-22 <= power && power <= 22 && i <= 9007199254740991) { ++#endif ++ // convert the integer into a double. This is lossless since ++ // 0 <= i <= 2^53 - 1. ++ d = double(i); ++ // ++ // The general idea is as follows. ++ // If 0 <= s < 2^53 and if 10^0 <= p <= 10^22 then ++ // 1) Both s and p can be represented exactly as 64-bit floating-point ++ // values ++ // (binary64). ++ // 2) Because s and p can be represented exactly as floating-point values, ++ // then s * p ++ // and s / p will produce correctly rounded values. ++ // ++ if (power < 0) { ++ d = d / simdjson::internal::power_of_ten[-power]; ++ } else { ++ d = d * simdjson::internal::power_of_ten[power]; ++ } ++ if (negative) { ++ d = -d; ++ } ++ return true; ++ } ++ // When 22 < power && power < 22 + 16, we could ++ // hope for another, secondary fast path. It was ++ // described by David M. Gay in "Correctly rounded ++ // binary-decimal and decimal-binary conversions." (1990) ++ // If you need to compute i * 10^(22 + x) for x < 16, ++ // first compute i * 10^x, if you know that result is exact ++ // (e.g., when i * 10^x < 2^53), ++ // then you can still proceed and do (i * 10^x) * 10^22. ++ // Is this worth your time? ++ // You need 22 < power *and* power < 22 + 16 *and* (i * 10^(x-22) < 2^53) ++ // for this second fast path to work. ++ // If you you have 22 < power *and* power < 22 + 16, and then you ++ // optimistically compute "i * 10^(x-22)", there is still a chance that you ++ // have wasted your time if i * 10^(x-22) >= 2^53. It makes the use cases of ++ // this optimization maybe less common than we would like. Source: ++ // http://www.exploringbinary.com/fast-path-decimal-to-floating-point-conversion/ ++ // also used in RapidJSON: https://rapidjson.org/strtod_8h_source.html ++ ++ // The fast path has now failed, so we are failing back on the slower path. ++ ++ // In the slow path, we need to adjust i so that it is > 1<<63 which is always ++ // possible, except if i == 0, so we handle i == 0 separately. ++ if(i == 0) { ++ d = negative ? -0.0 : 0.0; ++ return true; ++ } ++ ++ ++ // The exponent is 1024 + 63 + power ++ // + floor(log(5**power)/log(2)). ++ // The 1024 comes from the ieee64 standard. ++ // The 63 comes from the fact that we use a 64-bit word. ++ // ++ // Computing floor(log(5**power)/log(2)) could be ++ // slow. Instead we use a fast function. ++ // ++ // For power in (-400,350), we have that ++ // (((152170 + 65536) * power ) >> 16); ++ // is equal to ++ // floor(log(5**power)/log(2)) + power when power >= 0 ++ // and it is equal to ++ // ceil(log(5**-power)/log(2)) + power when power < 0 ++ // ++ // The 65536 is (1<<16) and corresponds to ++ // (65536 * power) >> 16 ---> power ++ // ++ // ((152170 * power ) >> 16) is equal to ++ // floor(log(5**power)/log(2)) ++ // ++ // Note that this is not magic: 152170/(1<<16) is ++ // approximatively equal to log(5)/log(2). ++ // The 1<<16 value is a power of two; we could use a ++ // larger power of 2 if we wanted to. ++ // ++ int64_t exponent = (((152170 + 65536) * power) >> 16) + 1024 + 63; ++ ++ ++ // We want the most significant bit of i to be 1. Shift if needed. ++ int lz = leading_zeroes(i); ++ i <<= lz; ++ ++ ++ // We are going to need to do some 64-bit arithmetic to get a precise product. ++ // We use a table lookup approach. ++ // It is safe because ++ // power >= smallest_power ++ // and power <= largest_power ++ // We recover the mantissa of the power, it has a leading 1. It is always ++ // rounded down. ++ // ++ // We want the most significant 64 bits of the product. We know ++ // this will be non-zero because the most significant bit of i is ++ // 1. ++ const uint32_t index = 2 * uint32_t(power - simdjson::internal::smallest_power); ++ // Optimization: It may be that materializing the index as a variable might confuse some compilers and prevent effective complex-addressing loads. (Done for code clarity.) ++ // ++ // The full_multiplication function computes the 128-bit product of two 64-bit words ++ // with a returned value of type value128 with a "low component" corresponding to the ++ // 64-bit least significant bits of the product and with a "high component" corresponding ++ // to the 64-bit most significant bits of the product. ++ simdjson::internal::value128 firstproduct = jsoncharutils::full_multiplication(i, simdjson::internal::power_of_five_128[index]); ++ // Both i and power_of_five_128[index] have their most significant bit set to 1 which ++ // implies that the either the most or the second most significant bit of the product ++ // is 1. We pack values in this manner for efficiency reasons: it maximizes the use ++ // we make of the product. It also makes it easy to reason about the product: there ++ // is 0 or 1 leading zero in the product. ++ ++ // Unless the least significant 9 bits of the high (64-bit) part of the full ++ // product are all 1s, then we know that the most significant 55 bits are ++ // exact and no further work is needed. Having 55 bits is necessary because ++ // we need 53 bits for the mantissa but we have to have one rounding bit and ++ // we can waste a bit if the most significant bit of the product is zero. ++ if((firstproduct.high & 0x1FF) == 0x1FF) { ++ // We want to compute i * 5^q, but only care about the top 55 bits at most. ++ // Consider the scenario where q>=0. Then 5^q may not fit in 64-bits. Doing ++ // the full computation is wasteful. So we do what is called a "truncated ++ // multiplication". ++ // We take the most significant 64-bits, and we put them in ++ // power_of_five_128[index]. Usually, that's good enough to approximate i * 5^q ++ // to the desired approximation using one multiplication. Sometimes it does not suffice. ++ // Then we store the next most significant 64 bits in power_of_five_128[index + 1], and ++ // then we get a better approximation to i * 5^q. In very rare cases, even that ++ // will not suffice, though it is seemingly very hard to find such a scenario. ++ // ++ // That's for when q>=0. The logic for q<0 is somewhat similar but it is somewhat ++ // more complicated. ++ // ++ // There is an extra layer of complexity in that we need more than 55 bits of ++ // accuracy in the round-to-even scenario. ++ // ++ // The full_multiplication function computes the 128-bit product of two 64-bit words ++ // with a returned value of type value128 with a "low component" corresponding to the ++ // 64-bit least significant bits of the product and with a "high component" corresponding ++ // to the 64-bit most significant bits of the product. ++ simdjson::internal::value128 secondproduct = jsoncharutils::full_multiplication(i, simdjson::internal::power_of_five_128[index + 1]); ++ firstproduct.low += secondproduct.high; ++ if(secondproduct.high > firstproduct.low) { firstproduct.high++; } ++ // At this point, we might need to add at most one to firstproduct, but this ++ // can only change the value of firstproduct.high if firstproduct.low is maximal. ++ if(simdjson_unlikely(firstproduct.low == 0xFFFFFFFFFFFFFFFF)) { ++ // This is very unlikely, but if so, we need to do much more work! ++ return false; ++ } ++ } ++ uint64_t lower = firstproduct.low; ++ uint64_t upper = firstproduct.high; ++ // The final mantissa should be 53 bits with a leading 1. ++ // We shift it so that it occupies 54 bits with a leading 1. ++ /////// ++ uint64_t upperbit = upper >> 63; ++ uint64_t mantissa = upper >> (upperbit + 9); ++ lz += int(1 ^ upperbit); ++ ++ // Here we have mantissa < (1<<54). ++ int64_t real_exponent = exponent - lz; ++ if (simdjson_unlikely(real_exponent <= 0)) { // we have a subnormal? ++ // Here have that real_exponent <= 0 so -real_exponent >= 0 ++ if(-real_exponent + 1 >= 64) { // if we have more than 64 bits below the minimum exponent, you have a zero for sure. ++ d = negative ? -0.0 : 0.0; ++ return true; ++ } ++ // next line is safe because -real_exponent + 1 < 0 ++ mantissa >>= -real_exponent + 1; ++ // Thankfully, we can't have both "round-to-even" and subnormals because ++ // "round-to-even" only occurs for powers close to 0. ++ mantissa += (mantissa & 1); // round up ++ mantissa >>= 1; ++ // There is a weird scenario where we don't have a subnormal but just. ++ // Suppose we start with 2.2250738585072013e-308, we end up ++ // with 0x3fffffffffffff x 2^-1023-53 which is technically subnormal ++ // whereas 0x40000000000000 x 2^-1023-53 is normal. Now, we need to round ++ // up 0x3fffffffffffff x 2^-1023-53 and once we do, we are no longer ++ // subnormal, but we can only know this after rounding. ++ // So we only declare a subnormal if we are smaller than the threshold. ++ real_exponent = (mantissa < (uint64_t(1) << 52)) ? 0 : 1; ++ d = to_double(mantissa, real_exponent, negative); ++ return true; ++ } ++ // We have to round to even. The "to even" part ++ // is only a problem when we are right in between two floats ++ // which we guard against. ++ // If we have lots of trailing zeros, we may fall right between two ++ // floating-point values. ++ // ++ // The round-to-even cases take the form of a number 2m+1 which is in (2^53,2^54] ++ // times a power of two. That is, it is right between a number with binary significand ++ // m and another number with binary significand m+1; and it must be the case ++ // that it cannot be represented by a float itself. ++ // ++ // We must have that w * 10 ^q == (2m+1) * 2^p for some power of two 2^p. ++ // Recall that 10^q = 5^q * 2^q. ++ // When q >= 0, we must have that (2m+1) is divible by 5^q, so 5^q <= 2^54. We have that ++ // 5^23 <= 2^54 and it is the last power of five to qualify, so q <= 23. ++ // When q<0, we have w >= (2m+1) x 5^{-q}. We must have that w<2^{64} so ++ // (2m+1) x 5^{-q} < 2^{64}. We have that 2m+1>2^{53}. Hence, we must have ++ // 2^{53} x 5^{-q} < 2^{64}. ++ // Hence we have 5^{-q} < 2^{11}$ or q>= -4. ++ // ++ // We require lower <= 1 and not lower == 0 because we could not prove that ++ // that lower == 0 is implied; but we could prove that lower <= 1 is a necessary and sufficient test. ++ if (simdjson_unlikely((lower <= 1) && (power >= -4) && (power <= 23) && ((mantissa & 3) == 1))) { ++ if((mantissa << (upperbit + 64 - 53 - 2)) == upper) { ++ mantissa &= ~1; // flip it so that we do not round up ++ } ++ } ++ ++ mantissa += mantissa & 1; ++ mantissa >>= 1; ++ ++ // Here we have mantissa < (1<<53), unless there was an overflow ++ if (mantissa >= (1ULL << 53)) { ++ ////////// ++ // This will happen when parsing values such as 7.2057594037927933e+16 ++ //////// ++ mantissa = (1ULL << 52); ++ real_exponent++; ++ } ++ mantissa &= ~(1ULL << 52); ++ // we have to check that real_exponent is in range, otherwise we bail out ++ if (simdjson_unlikely(real_exponent > 2046)) { ++ // We have an infinite value!!! We could actually throw an error here if we could. ++ return false; ++ } ++ d = to_double(mantissa, real_exponent, negative); ++ return true; ++} ++ ++// We call a fallback floating-point parser that might be slow. Note ++// it will accept JSON numbers, but the JSON spec. is more restrictive so ++// before you call parse_float_fallback, you need to have validated the input ++// string with the JSON grammar. ++// It will return an error (false) if the parsed number is infinite. ++// The string parsing itself always succeeds. We know that there is at least ++// one digit. ++static bool parse_float_fallback(const uint8_t *ptr, double *outDouble) { ++ *outDouble = simdjson::internal::from_chars(reinterpret_cast(ptr)); ++ // We do not accept infinite values. ++ ++ // Detecting finite values in a portable manner is ridiculously hard, ideally ++ // we would want to do: ++ // return !std::isfinite(*outDouble); ++ // but that mysteriously fails under legacy/old libc++ libraries, see ++ // https://github.com/simdjson/simdjson/issues/1286 ++ // ++ // Therefore, fall back to this solution (the extra parens are there ++ // to handle that max may be a macro on windows). ++ return !(*outDouble > (std::numeric_limits::max)() || *outDouble < std::numeric_limits::lowest()); ++} ++static bool parse_float_fallback(const uint8_t *ptr, const uint8_t *end_ptr, double *outDouble) { ++ *outDouble = simdjson::internal::from_chars(reinterpret_cast(ptr), reinterpret_cast(end_ptr)); ++ // We do not accept infinite values. ++ ++ // Detecting finite values in a portable manner is ridiculously hard, ideally ++ // we would want to do: ++ // return !std::isfinite(*outDouble); ++ // but that mysteriously fails under legacy/old libc++ libraries, see ++ // https://github.com/simdjson/simdjson/issues/1286 ++ // ++ // Therefore, fall back to this solution (the extra parens are there ++ // to handle that max may be a macro on windows). ++ return !(*outDouble > (std::numeric_limits::max)() || *outDouble < std::numeric_limits::lowest()); ++} ++ ++// check quickly whether the next 8 chars are made of digits ++// at a glance, it looks better than Mula's ++// http://0x80.pl/articles/swar-digits-validate.html ++simdjson_inline bool is_made_of_eight_digits_fast(const uint8_t *chars) { ++ uint64_t val; ++ // this can read up to 7 bytes beyond the buffer size, but we require ++ // SIMDJSON_PADDING of padding ++ static_assert(7 <= SIMDJSON_PADDING, "SIMDJSON_PADDING must be bigger than 7"); ++ std::memcpy(&val, chars, 8); ++ // a branchy method might be faster: ++ // return (( val & 0xF0F0F0F0F0F0F0F0 ) == 0x3030303030303030) ++ // && (( (val + 0x0606060606060606) & 0xF0F0F0F0F0F0F0F0 ) == ++ // 0x3030303030303030); ++ return (((val & 0xF0F0F0F0F0F0F0F0) | ++ (((val + 0x0606060606060606) & 0xF0F0F0F0F0F0F0F0) >> 4)) == ++ 0x3333333333333333); ++} ++ ++template ++error_code slow_float_parsing(simdjson_unused const uint8_t * src, W writer) { ++ double d; ++ if (parse_float_fallback(src, &d)) { ++ writer.append_double(d); ++ return SUCCESS; ++ } ++ return INVALID_NUMBER(src); ++} ++ ++template ++SIMDJSON_NO_SANITIZE_UNDEFINED // We deliberately allow overflow here and check later ++simdjson_inline bool parse_digit(const uint8_t c, I &i) { ++ const uint8_t digit = static_cast(c - '0'); ++ if (digit > 9) { ++ return false; ++ } ++ // PERF NOTE: multiplication by 10 is cheaper than arbitrary integer multiplication ++ i = 10 * i + digit; // might overflow, we will handle the overflow later ++ return true; ++} ++ ++simdjson_inline error_code parse_decimal(simdjson_unused const uint8_t *const src, const uint8_t *&p, uint64_t &i, int64_t &exponent) { ++ // we continue with the fiction that we have an integer. If the ++ // floating point number is representable as x * 10^z for some integer ++ // z that fits in 53 bits, then we will be able to convert back the ++ // the integer into a float in a lossless manner. ++ const uint8_t *const first_after_period = p; ++ ++#ifdef SIMDJSON_SWAR_NUMBER_PARSING ++#if SIMDJSON_SWAR_NUMBER_PARSING ++ // this helps if we have lots of decimals! ++ // this turns out to be frequent enough. ++ if (is_made_of_eight_digits_fast(p)) { ++ i = i * 100000000 + parse_eight_digits_unrolled(p); ++ p += 8; ++ } ++#endif // SIMDJSON_SWAR_NUMBER_PARSING ++#endif // #ifdef SIMDJSON_SWAR_NUMBER_PARSING ++ // Unrolling the first digit makes a small difference on some implementations (e.g. westmere) ++ if (parse_digit(*p, i)) { ++p; } ++ while (parse_digit(*p, i)) { p++; } ++ exponent = first_after_period - p; ++ // Decimal without digits (123.) is illegal ++ if (exponent == 0) { ++ return INVALID_NUMBER(src); ++ } ++ return SUCCESS; ++} ++ ++simdjson_inline error_code parse_exponent(simdjson_unused const uint8_t *const src, const uint8_t *&p, int64_t &exponent) { ++ // Exp Sign: -123.456e[-]78 ++ bool neg_exp = ('-' == *p); ++ if (neg_exp || '+' == *p) { p++; } // Skip + as well ++ ++ // Exponent: -123.456e-[78] ++ auto start_exp = p; ++ int64_t exp_number = 0; ++ while (parse_digit(*p, exp_number)) { ++p; } ++ // It is possible for parse_digit to overflow. ++ // In particular, it could overflow to INT64_MIN, and we cannot do - INT64_MIN. ++ // Thus we *must* check for possible overflow before we negate exp_number. ++ ++ // Performance notes: it may seem like combining the two "simdjson_unlikely checks" below into ++ // a single simdjson_unlikely path would be faster. The reasoning is sound, but the compiler may ++ // not oblige and may, in fact, generate two distinct paths in any case. It might be ++ // possible to do uint64_t(p - start_exp - 1) >= 18 but it could end up trading off ++ // instructions for a simdjson_likely branch, an unconclusive gain. ++ ++ // If there were no digits, it's an error. ++ if (simdjson_unlikely(p == start_exp)) { ++ return INVALID_NUMBER(src); ++ } ++ // We have a valid positive exponent in exp_number at this point, except that ++ // it may have overflowed. ++ ++ // If there were more than 18 digits, we may have overflowed the integer. We have to do ++ // something!!!! ++ if (simdjson_unlikely(p > start_exp+18)) { ++ // Skip leading zeroes: 1e000000000000000000001 is technically valid and doesn't overflow ++ while (*start_exp == '0') { start_exp++; } ++ // 19 digits could overflow int64_t and is kind of absurd anyway. We don't ++ // support exponents smaller than -999,999,999,999,999,999 and bigger ++ // than 999,999,999,999,999,999. ++ // We can truncate. ++ // Note that 999999999999999999 is assuredly too large. The maximal ieee64 value before ++ // infinity is ~1.8e308. The smallest subnormal is ~5e-324. So, actually, we could ++ // truncate at 324. ++ // Note that there is no reason to fail per se at this point in time. ++ // E.g., 0e999999999999999999999 is a fine number. ++ if (p > start_exp+18) { exp_number = 999999999999999999; } ++ } ++ // At this point, we know that exp_number is a sane, positive, signed integer. ++ // It is <= 999,999,999,999,999,999. As long as 'exponent' is in ++ // [-8223372036854775808, 8223372036854775808], we won't overflow. Because 'exponent' ++ // is bounded in magnitude by the size of the JSON input, we are fine in this universe. ++ // To sum it up: the next line should never overflow. ++ exponent += (neg_exp ? -exp_number : exp_number); ++ return SUCCESS; ++} ++ ++simdjson_inline size_t significant_digits(const uint8_t * start_digits, size_t digit_count) { ++ // It is possible that the integer had an overflow. ++ // We have to handle the case where we have 0.0000somenumber. ++ const uint8_t *start = start_digits; ++ while ((*start == '0') || (*start == '.')) { ++start; } ++ // we over-decrement by one when there is a '.' ++ return digit_count - size_t(start - start_digits); ++} ++ ++template ++simdjson_inline error_code write_float(const uint8_t *const src, bool negative, uint64_t i, const uint8_t * start_digits, size_t digit_count, int64_t exponent, W &writer) { ++ // If we frequently had to deal with long strings of digits, ++ // we could extend our code by using a 128-bit integer instead ++ // of a 64-bit integer. However, this is uncommon in practice. ++ // ++ // 9999999999999999999 < 2**64 so we can accommodate 19 digits. ++ // If we have a decimal separator, then digit_count - 1 is the number of digits, but we ++ // may not have a decimal separator! ++ if (simdjson_unlikely(digit_count > 19 && significant_digits(start_digits, digit_count) > 19)) { ++ // Ok, chances are good that we had an overflow! ++ // this is almost never going to get called!!! ++ // we start anew, going slowly!!! ++ // This will happen in the following examples: ++ // 10000000000000000000000000000000000000000000e+308 ++ // 3.1415926535897932384626433832795028841971693993751 ++ // ++ // NOTE: This makes a *copy* of the writer and passes it to slow_float_parsing. This happens ++ // because slow_float_parsing is a non-inlined function. If we passed our writer reference to ++ // it, it would force it to be stored in memory, preventing the compiler from picking it apart ++ // and putting into registers. i.e. if we pass it as reference, it gets slow. ++ // This is what forces the skip_double, as well. ++ error_code error = slow_float_parsing(src, writer); ++ writer.skip_double(); ++ return error; ++ } ++ // NOTE: it's weird that the simdjson_unlikely() only wraps half the if, but it seems to get slower any other ++ // way we've tried: https://github.com/simdjson/simdjson/pull/990#discussion_r448497331 ++ // To future reader: we'd love if someone found a better way, or at least could explain this result! ++ if (simdjson_unlikely(exponent < simdjson::internal::smallest_power) || (exponent > simdjson::internal::largest_power)) { ++ // ++ // Important: smallest_power is such that it leads to a zero value. ++ // Observe that 18446744073709551615e-343 == 0, i.e. (2**64 - 1) e -343 is zero ++ // so something x 10^-343 goes to zero, but not so with something x 10^-342. ++ static_assert(simdjson::internal::smallest_power <= -342, "smallest_power is not small enough"); ++ // ++ if((exponent < simdjson::internal::smallest_power) || (i == 0)) { ++ // E.g. Parse "-0.0e-999" into the same value as "-0.0". See https://en.wikipedia.org/wiki/Signed_zero ++ WRITE_DOUBLE(negative ? -0.0 : 0.0, src, writer); ++ return SUCCESS; ++ } else { // (exponent > largest_power) and (i != 0) ++ // We have, for sure, an infinite value and simdjson refuses to parse infinite values. ++ return INVALID_NUMBER(src); ++ } ++ } ++ double d; ++ if (!compute_float_64(exponent, i, negative, d)) { ++ // we are almost never going to get here. ++ if (!parse_float_fallback(src, &d)) { return INVALID_NUMBER(src); } ++ } ++ WRITE_DOUBLE(d, src, writer); ++ return SUCCESS; ++} ++ ++// for performance analysis, it is sometimes useful to skip parsing ++#ifdef SIMDJSON_SKIPNUMBERPARSING ++ ++template ++simdjson_inline error_code parse_number(const uint8_t *const, W &writer) { ++ writer.append_s64(0); // always write zero ++ return SUCCESS; // always succeeds ++} ++ ++simdjson_unused simdjson_inline simdjson_result parse_unsigned(const uint8_t * const src) noexcept { return 0; } ++simdjson_unused simdjson_inline simdjson_result parse_integer(const uint8_t * const src) noexcept { return 0; } ++simdjson_unused simdjson_inline simdjson_result parse_double(const uint8_t * const src) noexcept { return 0; } ++simdjson_unused simdjson_inline simdjson_result parse_unsigned_in_string(const uint8_t * const src) noexcept { return 0; } ++simdjson_unused simdjson_inline simdjson_result parse_integer_in_string(const uint8_t * const src) noexcept { return 0; } ++simdjson_unused simdjson_inline simdjson_result parse_double_in_string(const uint8_t * const src) noexcept { return 0; } ++simdjson_unused simdjson_inline bool is_negative(const uint8_t * src) noexcept { return false; } ++simdjson_unused simdjson_inline simdjson_result is_integer(const uint8_t * src) noexcept { return false; } ++simdjson_unused simdjson_inline simdjson_result get_number_type(const uint8_t * src) noexcept { return ondemand::number_type::signed_integer; } ++#else ++ ++// parse the number at src ++// define JSON_TEST_NUMBERS for unit testing ++// ++// It is assumed that the number is followed by a structural ({,},],[) character ++// or a white space character. If that is not the case (e.g., when the JSON ++// document is made of a single number), then it is necessary to copy the ++// content and append a space before calling this function. ++// ++// Our objective is accurate parsing (ULP of 0) at high speed. ++template ++simdjson_inline error_code parse_number(const uint8_t *const src, W &writer) { ++ ++ // ++ // Check for minus sign ++ // ++ bool negative = (*src == '-'); ++ const uint8_t *p = src + uint8_t(negative); ++ ++ // ++ // Parse the integer part. ++ // ++ // PERF NOTE: we don't use is_made_of_eight_digits_fast because large integers like 123456789 are rare ++ const uint8_t *const start_digits = p; ++ uint64_t i = 0; ++ while (parse_digit(*p, i)) { p++; } ++ ++ // If there were no digits, or if the integer starts with 0 and has more than one digit, it's an error. ++ // Optimization note: size_t is expected to be unsigned. ++ size_t digit_count = size_t(p - start_digits); ++ if (digit_count == 0 || ('0' == *start_digits && digit_count > 1)) { return INVALID_NUMBER(src); } ++ ++ // ++ // Handle floats if there is a . or e (or both) ++ // ++ int64_t exponent = 0; ++ bool is_float = false; ++ if ('.' == *p) { ++ is_float = true; ++ ++p; ++ SIMDJSON_TRY( parse_decimal(src, p, i, exponent) ); ++ digit_count = int(p - start_digits); // used later to guard against overflows ++ } ++ if (('e' == *p) || ('E' == *p)) { ++ is_float = true; ++ ++p; ++ SIMDJSON_TRY( parse_exponent(src, p, exponent) ); ++ } ++ if (is_float) { ++ const bool dirty_end = jsoncharutils::is_not_structural_or_whitespace(*p); ++ SIMDJSON_TRY( write_float(src, negative, i, start_digits, digit_count, exponent, writer) ); ++ if (dirty_end) { return INVALID_NUMBER(src); } ++ return SUCCESS; ++ } ++ ++ // The longest negative 64-bit number is 19 digits. ++ // The longest positive 64-bit number is 20 digits. ++ // We do it this way so we don't trigger this branch unless we must. ++ size_t longest_digit_count = negative ? 19 : 20; ++ if (digit_count > longest_digit_count) { return INVALID_NUMBER(src); } ++ if (digit_count == longest_digit_count) { ++ if (negative) { ++ // Anything negative above INT64_MAX+1 is invalid ++ if (i > uint64_t(INT64_MAX)+1) { return INVALID_NUMBER(src); } ++ WRITE_INTEGER(~i+1, src, writer); ++ if (jsoncharutils::is_not_structural_or_whitespace(*p)) { return INVALID_NUMBER(src); } ++ return SUCCESS; ++ // Positive overflow check: ++ // - A 20 digit number starting with 2-9 is overflow, because 18,446,744,073,709,551,615 is the ++ // biggest uint64_t. ++ // - A 20 digit number starting with 1 is overflow if it is less than INT64_MAX. ++ // If we got here, it's a 20 digit number starting with the digit "1". ++ // - If a 20 digit number starting with 1 overflowed (i*10+digit), the result will be smaller ++ // than 1,553,255,926,290,448,384. ++ // - That is smaller than the smallest possible 20-digit number the user could write: ++ // 10,000,000,000,000,000,000. ++ // - Therefore, if the number is positive and lower than that, it's overflow. ++ // - The value we are looking at is less than or equal to INT64_MAX. ++ // ++ } else if (src[0] != uint8_t('1') || i <= uint64_t(INT64_MAX)) { return INVALID_NUMBER(src); } ++ } ++ ++ // Write unsigned if it doesn't fit in a signed integer. ++ if (i > uint64_t(INT64_MAX)) { ++ WRITE_UNSIGNED(i, src, writer); ++ } else { ++ WRITE_INTEGER(negative ? (~i+1) : i, src, writer); ++ } ++ if (jsoncharutils::is_not_structural_or_whitespace(*p)) { return INVALID_NUMBER(src); } ++ return SUCCESS; ++} ++ ++// Inlineable functions ++namespace { ++ ++// This table can be used to characterize the final character of an integer ++// string. For JSON structural character and allowable white space characters, ++// we return SUCCESS. For 'e', '.' and 'E', we return INCORRECT_TYPE. Otherwise ++// we return NUMBER_ERROR. ++// Optimization note: we could easily reduce the size of the table by half (to 128) ++// at the cost of an extra branch. ++// Optimization note: we want the values to use at most 8 bits (not, e.g., 32 bits): ++static_assert(error_code(uint8_t(NUMBER_ERROR))== NUMBER_ERROR, "bad NUMBER_ERROR cast"); ++static_assert(error_code(uint8_t(SUCCESS))== SUCCESS, "bad NUMBER_ERROR cast"); ++static_assert(error_code(uint8_t(INCORRECT_TYPE))== INCORRECT_TYPE, "bad NUMBER_ERROR cast"); ++ ++const uint8_t integer_string_finisher[256] = { ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, SUCCESS, ++ SUCCESS, NUMBER_ERROR, NUMBER_ERROR, SUCCESS, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, SUCCESS, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, SUCCESS, ++ NUMBER_ERROR, INCORRECT_TYPE, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, SUCCESS, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, INCORRECT_TYPE, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, SUCCESS, NUMBER_ERROR, SUCCESS, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, INCORRECT_TYPE, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, SUCCESS, NUMBER_ERROR, ++ SUCCESS, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR}; ++ ++// Parse any number from 0 to 18,446,744,073,709,551,615 ++simdjson_unused simdjson_inline simdjson_result parse_unsigned(const uint8_t * const src) noexcept { ++ const uint8_t *p = src; ++ // ++ // Parse the integer part. ++ // ++ // PERF NOTE: we don't use is_made_of_eight_digits_fast because large integers like 123456789 are rare ++ const uint8_t *const start_digits = p; ++ uint64_t i = 0; ++ while (parse_digit(*p, i)) { p++; } ++ ++ // If there were no digits, or if the integer starts with 0 and has more than one digit, it's an error. ++ // Optimization note: size_t is expected to be unsigned. ++ size_t digit_count = size_t(p - start_digits); ++ // The longest positive 64-bit number is 20 digits. ++ // We do it this way so we don't trigger this branch unless we must. ++ // Optimization note: the compiler can probably merge ++ // ((digit_count == 0) || (digit_count > 20)) ++ // into a single branch since digit_count is unsigned. ++ if ((digit_count == 0) || (digit_count > 20)) { return INCORRECT_TYPE; } ++ // Here digit_count > 0. ++ if (('0' == *start_digits) && (digit_count > 1)) { return NUMBER_ERROR; } ++ // We can do the following... ++ // if (!jsoncharutils::is_structural_or_whitespace(*p)) { ++ // return (*p == '.' || *p == 'e' || *p == 'E') ? INCORRECT_TYPE : NUMBER_ERROR; ++ // } ++ // as a single table lookup: ++ if (integer_string_finisher[*p] != SUCCESS) { return error_code(integer_string_finisher[*p]); } ++ ++ if (digit_count == 20) { ++ // Positive overflow check: ++ // - A 20 digit number starting with 2-9 is overflow, because 18,446,744,073,709,551,615 is the ++ // biggest uint64_t. ++ // - A 20 digit number starting with 1 is overflow if it is less than INT64_MAX. ++ // If we got here, it's a 20 digit number starting with the digit "1". ++ // - If a 20 digit number starting with 1 overflowed (i*10+digit), the result will be smaller ++ // than 1,553,255,926,290,448,384. ++ // - That is smaller than the smallest possible 20-digit number the user could write: ++ // 10,000,000,000,000,000,000. ++ // - Therefore, if the number is positive and lower than that, it's overflow. ++ // - The value we are looking at is less than or equal to INT64_MAX. ++ // ++ if (src[0] != uint8_t('1') || i <= uint64_t(INT64_MAX)) { return INCORRECT_TYPE; } ++ } ++ ++ return i; ++} ++ ++ ++// Parse any number from 0 to 18,446,744,073,709,551,615 ++// Never read at src_end or beyond ++simdjson_unused simdjson_inline simdjson_result parse_unsigned(const uint8_t * const src, const uint8_t * const src_end) noexcept { ++ const uint8_t *p = src; ++ // ++ // Parse the integer part. ++ // ++ // PERF NOTE: we don't use is_made_of_eight_digits_fast because large integers like 123456789 are rare ++ const uint8_t *const start_digits = p; ++ uint64_t i = 0; ++ while ((p != src_end) && parse_digit(*p, i)) { p++; } ++ ++ // If there were no digits, or if the integer starts with 0 and has more than one digit, it's an error. ++ // Optimization note: size_t is expected to be unsigned. ++ size_t digit_count = size_t(p - start_digits); ++ // The longest positive 64-bit number is 20 digits. ++ // We do it this way so we don't trigger this branch unless we must. ++ // Optimization note: the compiler can probably merge ++ // ((digit_count == 0) || (digit_count > 20)) ++ // into a single branch since digit_count is unsigned. ++ if ((digit_count == 0) || (digit_count > 20)) { return INCORRECT_TYPE; } ++ // Here digit_count > 0. ++ if (('0' == *start_digits) && (digit_count > 1)) { return NUMBER_ERROR; } ++ // We can do the following... ++ // if (!jsoncharutils::is_structural_or_whitespace(*p)) { ++ // return (*p == '.' || *p == 'e' || *p == 'E') ? INCORRECT_TYPE : NUMBER_ERROR; ++ // } ++ // as a single table lookup: ++ if ((p != src_end) && integer_string_finisher[*p] != SUCCESS) { return error_code(integer_string_finisher[*p]); } ++ ++ if (digit_count == 20) { ++ // Positive overflow check: ++ // - A 20 digit number starting with 2-9 is overflow, because 18,446,744,073,709,551,615 is the ++ // biggest uint64_t. ++ // - A 20 digit number starting with 1 is overflow if it is less than INT64_MAX. ++ // If we got here, it's a 20 digit number starting with the digit "1". ++ // - If a 20 digit number starting with 1 overflowed (i*10+digit), the result will be smaller ++ // than 1,553,255,926,290,448,384. ++ // - That is smaller than the smallest possible 20-digit number the user could write: ++ // 10,000,000,000,000,000,000. ++ // - Therefore, if the number is positive and lower than that, it's overflow. ++ // - The value we are looking at is less than or equal to INT64_MAX. ++ // ++ if (src[0] != uint8_t('1') || i <= uint64_t(INT64_MAX)) { return INCORRECT_TYPE; } ++ } ++ ++ return i; ++} ++ ++// Parse any number from 0 to 18,446,744,073,709,551,615 ++simdjson_unused simdjson_inline simdjson_result parse_unsigned_in_string(const uint8_t * const src) noexcept { ++ const uint8_t *p = src + 1; ++ // ++ // Parse the integer part. ++ // ++ // PERF NOTE: we don't use is_made_of_eight_digits_fast because large integers like 123456789 are rare ++ const uint8_t *const start_digits = p; ++ uint64_t i = 0; ++ while (parse_digit(*p, i)) { p++; } ++ ++ // If there were no digits, or if the integer starts with 0 and has more than one digit, it's an error. ++ // Optimization note: size_t is expected to be unsigned. ++ size_t digit_count = size_t(p - start_digits); ++ // The longest positive 64-bit number is 20 digits. ++ // We do it this way so we don't trigger this branch unless we must. ++ // Optimization note: the compiler can probably merge ++ // ((digit_count == 0) || (digit_count > 20)) ++ // into a single branch since digit_count is unsigned. ++ if ((digit_count == 0) || (digit_count > 20)) { return INCORRECT_TYPE; } ++ // Here digit_count > 0. ++ if (('0' == *start_digits) && (digit_count > 1)) { return NUMBER_ERROR; } ++ // We can do the following... ++ // if (!jsoncharutils::is_structural_or_whitespace(*p)) { ++ // return (*p == '.' || *p == 'e' || *p == 'E') ? INCORRECT_TYPE : NUMBER_ERROR; ++ // } ++ // as a single table lookup: ++ if (*p != '"') { return NUMBER_ERROR; } ++ ++ if (digit_count == 20) { ++ // Positive overflow check: ++ // - A 20 digit number starting with 2-9 is overflow, because 18,446,744,073,709,551,615 is the ++ // biggest uint64_t. ++ // - A 20 digit number starting with 1 is overflow if it is less than INT64_MAX. ++ // If we got here, it's a 20 digit number starting with the digit "1". ++ // - If a 20 digit number starting with 1 overflowed (i*10+digit), the result will be smaller ++ // than 1,553,255,926,290,448,384. ++ // - That is smaller than the smallest possible 20-digit number the user could write: ++ // 10,000,000,000,000,000,000. ++ // - Therefore, if the number is positive and lower than that, it's overflow. ++ // - The value we are looking at is less than or equal to INT64_MAX. ++ // ++ // Note: we use src[1] and not src[0] because src[0] is the quote character in this ++ // instance. ++ if (src[1] != uint8_t('1') || i <= uint64_t(INT64_MAX)) { return INCORRECT_TYPE; } ++ } ++ ++ return i; ++} ++ ++// Parse any number from -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 ++simdjson_unused simdjson_inline simdjson_result parse_integer(const uint8_t *src) noexcept { ++ // ++ // Check for minus sign ++ // ++ bool negative = (*src == '-'); ++ const uint8_t *p = src + uint8_t(negative); ++ ++ // ++ // Parse the integer part. ++ // ++ // PERF NOTE: we don't use is_made_of_eight_digits_fast because large integers like 123456789 are rare ++ const uint8_t *const start_digits = p; ++ uint64_t i = 0; ++ while (parse_digit(*p, i)) { p++; } ++ ++ // If there were no digits, or if the integer starts with 0 and has more than one digit, it's an error. ++ // Optimization note: size_t is expected to be unsigned. ++ size_t digit_count = size_t(p - start_digits); ++ // We go from ++ // -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 ++ // so we can never represent numbers that have more than 19 digits. ++ size_t longest_digit_count = 19; ++ // Optimization note: the compiler can probably merge ++ // ((digit_count == 0) || (digit_count > longest_digit_count)) ++ // into a single branch since digit_count is unsigned. ++ if ((digit_count == 0) || (digit_count > longest_digit_count)) { return INCORRECT_TYPE; } ++ // Here digit_count > 0. ++ if (('0' == *start_digits) && (digit_count > 1)) { return NUMBER_ERROR; } ++ // We can do the following... ++ // if (!jsoncharutils::is_structural_or_whitespace(*p)) { ++ // return (*p == '.' || *p == 'e' || *p == 'E') ? INCORRECT_TYPE : NUMBER_ERROR; ++ // } ++ // as a single table lookup: ++ if(integer_string_finisher[*p] != SUCCESS) { return error_code(integer_string_finisher[*p]); } ++ // Negative numbers have can go down to - INT64_MAX - 1 whereas positive numbers are limited to INT64_MAX. ++ // Performance note: This check is only needed when digit_count == longest_digit_count but it is ++ // so cheap that we might as well always make it. ++ if(i > uint64_t(INT64_MAX) + uint64_t(negative)) { return INCORRECT_TYPE; } ++ return negative ? (~i+1) : i; ++} ++ ++// Parse any number from -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 ++// Never read at src_end or beyond ++simdjson_unused simdjson_inline simdjson_result parse_integer(const uint8_t * const src, const uint8_t * const src_end) noexcept { ++ // ++ // Check for minus sign ++ // ++ if(src == src_end) { return NUMBER_ERROR; } ++ bool negative = (*src == '-'); ++ const uint8_t *p = src + uint8_t(negative); ++ ++ // ++ // Parse the integer part. ++ // ++ // PERF NOTE: we don't use is_made_of_eight_digits_fast because large integers like 123456789 are rare ++ const uint8_t *const start_digits = p; ++ uint64_t i = 0; ++ while ((p != src_end) && parse_digit(*p, i)) { p++; } ++ ++ // If there were no digits, or if the integer starts with 0 and has more than one digit, it's an error. ++ // Optimization note: size_t is expected to be unsigned. ++ size_t digit_count = size_t(p - start_digits); ++ // We go from ++ // -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 ++ // so we can never represent numbers that have more than 19 digits. ++ size_t longest_digit_count = 19; ++ // Optimization note: the compiler can probably merge ++ // ((digit_count == 0) || (digit_count > longest_digit_count)) ++ // into a single branch since digit_count is unsigned. ++ if ((digit_count == 0) || (digit_count > longest_digit_count)) { return INCORRECT_TYPE; } ++ // Here digit_count > 0. ++ if (('0' == *start_digits) && (digit_count > 1)) { return NUMBER_ERROR; } ++ // We can do the following... ++ // if (!jsoncharutils::is_structural_or_whitespace(*p)) { ++ // return (*p == '.' || *p == 'e' || *p == 'E') ? INCORRECT_TYPE : NUMBER_ERROR; ++ // } ++ // as a single table lookup: ++ if((p != src_end) && integer_string_finisher[*p] != SUCCESS) { return error_code(integer_string_finisher[*p]); } ++ // Negative numbers have can go down to - INT64_MAX - 1 whereas positive numbers are limited to INT64_MAX. ++ // Performance note: This check is only needed when digit_count == longest_digit_count but it is ++ // so cheap that we might as well always make it. ++ if(i > uint64_t(INT64_MAX) + uint64_t(negative)) { return INCORRECT_TYPE; } ++ return negative ? (~i+1) : i; ++} ++ ++// Parse any number from -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 ++simdjson_unused simdjson_inline simdjson_result parse_integer_in_string(const uint8_t *src) noexcept { ++ // ++ // Check for minus sign ++ // ++ bool negative = (*(src + 1) == '-'); ++ src += uint8_t(negative) + 1; ++ ++ // ++ // Parse the integer part. ++ // ++ // PERF NOTE: we don't use is_made_of_eight_digits_fast because large integers like 123456789 are rare ++ const uint8_t *const start_digits = src; ++ uint64_t i = 0; ++ while (parse_digit(*src, i)) { src++; } ++ ++ // If there were no digits, or if the integer starts with 0 and has more than one digit, it's an error. ++ // Optimization note: size_t is expected to be unsigned. ++ size_t digit_count = size_t(src - start_digits); ++ // We go from ++ // -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 ++ // so we can never represent numbers that have more than 19 digits. ++ size_t longest_digit_count = 19; ++ // Optimization note: the compiler can probably merge ++ // ((digit_count == 0) || (digit_count > longest_digit_count)) ++ // into a single branch since digit_count is unsigned. ++ if ((digit_count == 0) || (digit_count > longest_digit_count)) { return INCORRECT_TYPE; } ++ // Here digit_count > 0. ++ if (('0' == *start_digits) && (digit_count > 1)) { return NUMBER_ERROR; } ++ // We can do the following... ++ // if (!jsoncharutils::is_structural_or_whitespace(*src)) { ++ // return (*src == '.' || *src == 'e' || *src == 'E') ? INCORRECT_TYPE : NUMBER_ERROR; ++ // } ++ // as a single table lookup: ++ if(*src != '"') { return NUMBER_ERROR; } ++ // Negative numbers have can go down to - INT64_MAX - 1 whereas positive numbers are limited to INT64_MAX. ++ // Performance note: This check is only needed when digit_count == longest_digit_count but it is ++ // so cheap that we might as well always make it. ++ if(i > uint64_t(INT64_MAX) + uint64_t(negative)) { return INCORRECT_TYPE; } ++ return negative ? (~i+1) : i; ++} ++ ++simdjson_unused simdjson_inline simdjson_result parse_double(const uint8_t * src) noexcept { ++ // ++ // Check for minus sign ++ // ++ bool negative = (*src == '-'); ++ src += uint8_t(negative); ++ ++ // ++ // Parse the integer part. ++ // ++ uint64_t i = 0; ++ const uint8_t *p = src; ++ p += parse_digit(*p, i); ++ bool leading_zero = (i == 0); ++ while (parse_digit(*p, i)) { p++; } ++ // no integer digits, or 0123 (zero must be solo) ++ if ( p == src ) { return INCORRECT_TYPE; } ++ if ( (leading_zero && p != src+1)) { return NUMBER_ERROR; } ++ ++ // ++ // Parse the decimal part. ++ // ++ int64_t exponent = 0; ++ bool overflow; ++ if (simdjson_likely(*p == '.')) { ++ p++; ++ const uint8_t *start_decimal_digits = p; ++ if (!parse_digit(*p, i)) { return NUMBER_ERROR; } // no decimal digits ++ p++; ++ while (parse_digit(*p, i)) { p++; } ++ exponent = -(p - start_decimal_digits); ++ ++ // Overflow check. More than 19 digits (minus the decimal) may be overflow. ++ overflow = p-src-1 > 19; ++ if (simdjson_unlikely(overflow && leading_zero)) { ++ // Skip leading 0.00000 and see if it still overflows ++ const uint8_t *start_digits = src + 2; ++ while (*start_digits == '0') { start_digits++; } ++ overflow = start_digits-src > 19; ++ } ++ } else { ++ overflow = p-src > 19; ++ } ++ ++ // ++ // Parse the exponent ++ // ++ if (*p == 'e' || *p == 'E') { ++ p++; ++ bool exp_neg = *p == '-'; ++ p += exp_neg || *p == '+'; ++ ++ uint64_t exp = 0; ++ const uint8_t *start_exp_digits = p; ++ while (parse_digit(*p, exp)) { p++; } ++ // no exp digits, or 20+ exp digits ++ if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } ++ ++ exponent += exp_neg ? 0-exp : exp; ++ } ++ ++ if (jsoncharutils::is_not_structural_or_whitespace(*p)) { return NUMBER_ERROR; } ++ ++ overflow = overflow || exponent < simdjson::internal::smallest_power || exponent > simdjson::internal::largest_power; ++ ++ // ++ // Assemble (or slow-parse) the float ++ // ++ double d; ++ if (simdjson_likely(!overflow)) { ++ if (compute_float_64(exponent, i, negative, d)) { return d; } ++ } ++ if (!parse_float_fallback(src - uint8_t(negative), &d)) { ++ return NUMBER_ERROR; ++ } ++ return d; ++} ++ ++simdjson_unused simdjson_inline bool is_negative(const uint8_t * src) noexcept { ++ return (*src == '-'); ++} ++ ++simdjson_unused simdjson_inline simdjson_result is_integer(const uint8_t * src) noexcept { ++ bool negative = (*src == '-'); ++ src += uint8_t(negative); ++ const uint8_t *p = src; ++ while(static_cast(*p - '0') <= 9) { p++; } ++ if ( p == src ) { return NUMBER_ERROR; } ++ if (jsoncharutils::is_structural_or_whitespace(*p)) { return true; } ++ return false; ++} ++ ++simdjson_unused simdjson_inline simdjson_result get_number_type(const uint8_t * src) noexcept { ++ bool negative = (*src == '-'); ++ src += uint8_t(negative); ++ const uint8_t *p = src; ++ while(static_cast(*p - '0') <= 9) { p++; } ++ if ( p == src ) { return NUMBER_ERROR; } ++ if (jsoncharutils::is_structural_or_whitespace(*p)) { ++ // We have an integer. ++ // If the number is negative and valid, it must be a signed integer. ++ if(negative) { return ondemand::number_type::signed_integer; } ++ // We want values larger or equal to 9223372036854775808 to be unsigned ++ // integers, and the other values to be signed integers. ++ int digit_count = int(p - src); ++ if(digit_count >= 19) { ++ const uint8_t * smaller_big_integer = reinterpret_cast("9223372036854775808"); ++ if((digit_count >= 20) || (memcmp(src, smaller_big_integer, 19) >= 0)) { ++ return ondemand::number_type::unsigned_integer; ++ } ++ } ++ return ondemand::number_type::signed_integer; ++ } ++ // Hopefully, we have 'e' or 'E' or '.'. ++ return ondemand::number_type::floating_point_number; ++} ++ ++// Never read at src_end or beyond ++simdjson_unused simdjson_inline simdjson_result parse_double(const uint8_t * src, const uint8_t * const src_end) noexcept { ++ if(src == src_end) { return NUMBER_ERROR; } ++ // ++ // Check for minus sign ++ // ++ bool negative = (*src == '-'); ++ src += uint8_t(negative); ++ ++ // ++ // Parse the integer part. ++ // ++ uint64_t i = 0; ++ const uint8_t *p = src; ++ if(p == src_end) { return NUMBER_ERROR; } ++ p += parse_digit(*p, i); ++ bool leading_zero = (i == 0); ++ while ((p != src_end) && parse_digit(*p, i)) { p++; } ++ // no integer digits, or 0123 (zero must be solo) ++ if ( p == src ) { return INCORRECT_TYPE; } ++ if ( (leading_zero && p != src+1)) { return NUMBER_ERROR; } ++ ++ // ++ // Parse the decimal part. ++ // ++ int64_t exponent = 0; ++ bool overflow; ++ if (simdjson_likely((p != src_end) && (*p == '.'))) { ++ p++; ++ const uint8_t *start_decimal_digits = p; ++ if ((p == src_end) || !parse_digit(*p, i)) { return NUMBER_ERROR; } // no decimal digits ++ p++; ++ while ((p != src_end) && parse_digit(*p, i)) { p++; } ++ exponent = -(p - start_decimal_digits); ++ ++ // Overflow check. More than 19 digits (minus the decimal) may be overflow. ++ overflow = p-src-1 > 19; ++ if (simdjson_unlikely(overflow && leading_zero)) { ++ // Skip leading 0.00000 and see if it still overflows ++ const uint8_t *start_digits = src + 2; ++ while (*start_digits == '0') { start_digits++; } ++ overflow = start_digits-src > 19; ++ } ++ } else { ++ overflow = p-src > 19; ++ } ++ ++ // ++ // Parse the exponent ++ // ++ if ((p != src_end) && (*p == 'e' || *p == 'E')) { ++ p++; ++ if(p == src_end) { return NUMBER_ERROR; } ++ bool exp_neg = *p == '-'; ++ p += exp_neg || *p == '+'; ++ ++ uint64_t exp = 0; ++ const uint8_t *start_exp_digits = p; ++ while ((p != src_end) && parse_digit(*p, exp)) { p++; } ++ // no exp digits, or 20+ exp digits ++ if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } ++ ++ exponent += exp_neg ? 0-exp : exp; ++ } ++ ++ if ((p != src_end) && jsoncharutils::is_not_structural_or_whitespace(*p)) { return NUMBER_ERROR; } ++ ++ overflow = overflow || exponent < simdjson::internal::smallest_power || exponent > simdjson::internal::largest_power; ++ ++ // ++ // Assemble (or slow-parse) the float ++ // ++ double d; ++ if (simdjson_likely(!overflow)) { ++ if (compute_float_64(exponent, i, negative, d)) { return d; } ++ } ++ if (!parse_float_fallback(src - uint8_t(negative), src_end, &d)) { ++ return NUMBER_ERROR; ++ } ++ return d; ++} ++ ++simdjson_unused simdjson_inline simdjson_result parse_double_in_string(const uint8_t * src) noexcept { ++ // ++ // Check for minus sign ++ // ++ bool negative = (*(src + 1) == '-'); ++ src += uint8_t(negative) + 1; ++ ++ // ++ // Parse the integer part. ++ // ++ uint64_t i = 0; ++ const uint8_t *p = src; ++ p += parse_digit(*p, i); ++ bool leading_zero = (i == 0); ++ while (parse_digit(*p, i)) { p++; } ++ // no integer digits, or 0123 (zero must be solo) ++ if ( p == src ) { return INCORRECT_TYPE; } ++ if ( (leading_zero && p != src+1)) { return NUMBER_ERROR; } ++ ++ // ++ // Parse the decimal part. ++ // ++ int64_t exponent = 0; ++ bool overflow; ++ if (simdjson_likely(*p == '.')) { ++ p++; ++ const uint8_t *start_decimal_digits = p; ++ if (!parse_digit(*p, i)) { return NUMBER_ERROR; } // no decimal digits ++ p++; ++ while (parse_digit(*p, i)) { p++; } ++ exponent = -(p - start_decimal_digits); ++ ++ // Overflow check. More than 19 digits (minus the decimal) may be overflow. ++ overflow = p-src-1 > 19; ++ if (simdjson_unlikely(overflow && leading_zero)) { ++ // Skip leading 0.00000 and see if it still overflows ++ const uint8_t *start_digits = src + 2; ++ while (*start_digits == '0') { start_digits++; } ++ overflow = start_digits-src > 19; ++ } ++ } else { ++ overflow = p-src > 19; ++ } ++ ++ // ++ // Parse the exponent ++ // ++ if (*p == 'e' || *p == 'E') { ++ p++; ++ bool exp_neg = *p == '-'; ++ p += exp_neg || *p == '+'; ++ ++ uint64_t exp = 0; ++ const uint8_t *start_exp_digits = p; ++ while (parse_digit(*p, exp)) { p++; } ++ // no exp digits, or 20+ exp digits ++ if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } ++ ++ exponent += exp_neg ? 0-exp : exp; ++ } ++ ++ if (*p != '"') { return NUMBER_ERROR; } ++ ++ overflow = overflow || exponent < simdjson::internal::smallest_power || exponent > simdjson::internal::largest_power; ++ ++ // ++ // Assemble (or slow-parse) the float ++ // ++ double d; ++ if (simdjson_likely(!overflow)) { ++ if (compute_float_64(exponent, i, negative, d)) { return d; } ++ } ++ if (!parse_float_fallback(src - uint8_t(negative), &d)) { ++ return NUMBER_ERROR; ++ } ++ return d; ++} ++} //namespace {} ++#endif // SIMDJSON_SKIPNUMBERPARSING ++ ++} // namespace numberparsing ++} // unnamed namespace ++} // namespace fallback ++} // namespace simdjson ++/* end file include/simdjson/generic/numberparsing.h */ ++ ++#endif // SIMDJSON_FALLBACK_NUMBERPARSING_H ++/* end file include/simdjson/fallback/numberparsing.h */ ++/* begin file include/simdjson/fallback/end.h */ ++/* end file include/simdjson/fallback/end.h */ ++ ++#endif // SIMDJSON_IMPLEMENTATION_FALLBACK ++#endif // SIMDJSON_FALLBACK_H ++/* end file include/simdjson/fallback.h */ ++/* begin file include/simdjson/icelake.h */ ++#ifndef SIMDJSON_ICELAKE_H ++#define SIMDJSON_ICELAKE_H ++ ++ ++#if SIMDJSON_IMPLEMENTATION_ICELAKE ++ ++#if SIMDJSON_CAN_ALWAYS_RUN_ICELAKE ++#define SIMDJSON_TARGET_ICELAKE ++#define SIMDJSON_UNTARGET_ICELAKE ++#else ++#define SIMDJSON_TARGET_ICELAKE SIMDJSON_TARGET_REGION("avx512f,avx512dq,avx512cd,avx512bw,avx512vbmi,avx512vbmi2,avx512vl,avx2,bmi,pclmul,lzcnt") ++#define SIMDJSON_UNTARGET_ICELAKE SIMDJSON_UNTARGET_REGION ++#endif ++ ++namespace simdjson { ++/** ++ * Implementation for Icelake (Intel AVX512). ++ */ ++namespace icelake { ++} // namespace icelake ++} // namespace simdjson ++ ++// ++// These two need to be included outside SIMDJSON_TARGET_ICELAKE ++// ++/* begin file include/simdjson/icelake/implementation.h */ ++#ifndef SIMDJSON_ICELAKE_IMPLEMENTATION_H ++#define SIMDJSON_ICELAKE_IMPLEMENTATION_H ++ ++ ++// The constructor may be executed on any host, so we take care not to use SIMDJSON_TARGET_ICELAKE ++namespace simdjson { ++namespace icelake { ++ ++using namespace simdjson; ++ ++class implementation final : public simdjson::implementation { ++public: ++ simdjson_inline implementation() : simdjson::implementation( ++ "icelake", ++ "Intel/AMD AVX512", ++ internal::instruction_set::AVX2 | internal::instruction_set::PCLMULQDQ | internal::instruction_set::BMI1 | internal::instruction_set::BMI2 | internal::instruction_set::AVX512F | internal::instruction_set::AVX512DQ | internal::instruction_set::AVX512CD | internal::instruction_set::AVX512BW | internal::instruction_set::AVX512VL | internal::instruction_set::AVX512VBMI2 ++ ) {} ++ simdjson_warn_unused error_code create_dom_parser_implementation( ++ size_t capacity, ++ size_t max_length, ++ std::unique_ptr& dst ++ ) const noexcept final; ++ simdjson_warn_unused error_code minify(const uint8_t *buf, size_t len, uint8_t *dst, size_t &dst_len) const noexcept final; ++ simdjson_warn_unused bool validate_utf8(const char *buf, size_t len) const noexcept final; ++}; ++ ++} // namespace icelake ++} // namespace simdjson ++ ++#endif // SIMDJSON_ICELAKE_IMPLEMENTATION_H ++/* end file include/simdjson/icelake/implementation.h */ ++/* begin file include/simdjson/icelake/intrinsics.h */ ++#ifndef SIMDJSON_ICELAKE_INTRINSICS_H ++#define SIMDJSON_ICELAKE_INTRINSICS_H ++ ++ ++#if SIMDJSON_VISUAL_STUDIO ++// under clang within visual studio, this will include ++#include // visual studio or clang ++#else ++#include // elsewhere ++#endif // SIMDJSON_VISUAL_STUDIO ++ ++#if SIMDJSON_CLANG_VISUAL_STUDIO ++/** ++ * You are not supposed, normally, to include these ++ * headers directly. Instead you should either include intrin.h ++ * or x86intrin.h. However, when compiling with clang ++ * under Windows (i.e., when _MSC_VER is set), these headers ++ * only get included *if* the corresponding features are detected ++ * from macros: ++ * e.g., if __AVX2__ is set... in turn, we normally set these ++ * macros by compiling against the corresponding architecture ++ * (e.g., arch:AVX2, -mavx2, etc.) which compiles the whole ++ * software with these advanced instructions. In simdjson, we ++ * want to compile the whole program for a generic target, ++ * and only target our specific kernels. As a workaround, ++ * we directly include the needed headers. These headers would ++ * normally guard against such usage, but we carefully included ++ * (or ) before, so the headers ++ * are fooled. ++ */ ++#include // for _blsr_u64 ++#include // for __lzcnt64 ++#include // for most things (AVX2, AVX512, _popcnt64) ++#include ++#include ++#include ++#include ++#include // for _mm_clmulepi64_si128 ++// Important: we need the AVX-512 headers: ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++// unfortunately, we may not get _blsr_u64, but, thankfully, clang ++// has it as a macro. ++#ifndef _blsr_u64 ++// we roll our own ++#define _blsr_u64(n) ((n - 1) & n) ++#endif // _blsr_u64 ++#endif // SIMDJSON_CLANG_VISUAL_STUDIO ++ ++static_assert(sizeof(__m512i) <= simdjson::SIMDJSON_PADDING, "insufficient padding for icelake"); ++ ++#endif // SIMDJSON_ICELAKE_INTRINSICS_H ++/* end file include/simdjson/icelake/intrinsics.h */ ++ ++// ++// The rest need to be inside the region ++// ++/* begin file include/simdjson/icelake/begin.h */ ++// redefining SIMDJSON_IMPLEMENTATION to "icelake" ++// #define SIMDJSON_IMPLEMENTATION icelake ++SIMDJSON_TARGET_ICELAKE ++/* end file include/simdjson/icelake/begin.h */ ++ ++// Declarations ++/* begin file include/simdjson/generic/dom_parser_implementation.h */ ++ ++namespace simdjson { ++namespace icelake { ++ ++// expectation: sizeof(open_container) = 64/8. ++struct open_container { ++ uint32_t tape_index; // where, on the tape, does the scope ([,{) begins ++ uint32_t count; // how many elements in the scope ++}; // struct open_container ++ ++static_assert(sizeof(open_container) == 64/8, "Open container must be 64 bits"); ++ ++class dom_parser_implementation final : public internal::dom_parser_implementation { ++public: ++ /** Tape location of each open { or [ */ ++ std::unique_ptr open_containers{}; ++ /** Whether each open container is a [ or { */ ++ std::unique_ptr is_array{}; ++ /** Buffer passed to stage 1 */ ++ const uint8_t *buf{}; ++ /** Length passed to stage 1 */ ++ size_t len{0}; ++ /** Document passed to stage 2 */ ++ dom::document *doc{}; ++ ++ inline dom_parser_implementation() noexcept; ++ inline dom_parser_implementation(dom_parser_implementation &&other) noexcept; ++ inline dom_parser_implementation &operator=(dom_parser_implementation &&other) noexcept; ++ dom_parser_implementation(const dom_parser_implementation &) = delete; ++ dom_parser_implementation &operator=(const dom_parser_implementation &) = delete; ++ ++ simdjson_warn_unused error_code parse(const uint8_t *buf, size_t len, dom::document &doc) noexcept final; ++ simdjson_warn_unused error_code stage1(const uint8_t *buf, size_t len, stage1_mode partial) noexcept final; ++ simdjson_warn_unused error_code stage2(dom::document &doc) noexcept final; ++ simdjson_warn_unused error_code stage2_next(dom::document &doc) noexcept final; ++ simdjson_warn_unused uint8_t *parse_string(const uint8_t *src, uint8_t *dst) const noexcept final; ++ inline simdjson_warn_unused error_code set_capacity(size_t capacity) noexcept final; ++ inline simdjson_warn_unused error_code set_max_depth(size_t max_depth) noexcept final; ++private: ++ simdjson_inline simdjson_warn_unused error_code set_capacity_stage1(size_t capacity); ++ ++}; ++ ++} // namespace icelake ++} // namespace simdjson ++ ++namespace simdjson { ++namespace icelake { ++ ++inline dom_parser_implementation::dom_parser_implementation() noexcept = default; ++inline dom_parser_implementation::dom_parser_implementation(dom_parser_implementation &&other) noexcept = default; ++inline dom_parser_implementation &dom_parser_implementation::operator=(dom_parser_implementation &&other) noexcept = default; ++ ++// Leaving these here so they can be inlined if so desired ++inline simdjson_warn_unused error_code dom_parser_implementation::set_capacity(size_t capacity) noexcept { ++ if(capacity > SIMDJSON_MAXSIZE_BYTES) { return CAPACITY; } ++ // Stage 1 index output ++ size_t max_structures = SIMDJSON_ROUNDUP_N(capacity, 64) + 2 + 7; ++ structural_indexes.reset( new (std::nothrow) uint32_t[max_structures] ); ++ if (!structural_indexes) { _capacity = 0; return MEMALLOC; } ++ structural_indexes[0] = 0; ++ n_structural_indexes = 0; ++ ++ _capacity = capacity; ++ return SUCCESS; ++} ++ ++inline simdjson_warn_unused error_code dom_parser_implementation::set_max_depth(size_t max_depth) noexcept { ++ // Stage 2 stacks ++ open_containers.reset(new (std::nothrow) open_container[max_depth]); ++ is_array.reset(new (std::nothrow) bool[max_depth]); ++ if (!is_array || !open_containers) { _max_depth = 0; return MEMALLOC; } ++ ++ _max_depth = max_depth; ++ return SUCCESS; ++} ++ ++} // namespace icelake ++} // namespace simdjson ++/* end file include/simdjson/generic/dom_parser_implementation.h */ ++/* begin file include/simdjson/icelake/bitmanipulation.h */ ++#ifndef SIMDJSON_ICELAKE_BITMANIPULATION_H ++#define SIMDJSON_ICELAKE_BITMANIPULATION_H ++ ++namespace simdjson { ++namespace icelake { ++namespace { ++ ++// We sometimes call trailing_zero on inputs that are zero, ++// but the algorithms do not end up using the returned value. ++// Sadly, sanitizers are not smart enough to figure it out. ++SIMDJSON_NO_SANITIZE_UNDEFINED ++simdjson_inline int trailing_zeroes(uint64_t input_num) { ++#if SIMDJSON_REGULAR_VISUAL_STUDIO ++ return (int)_tzcnt_u64(input_num); ++#else // SIMDJSON_REGULAR_VISUAL_STUDIO ++ //////// ++ // You might expect the next line to be equivalent to ++ // return (int)_tzcnt_u64(input_num); ++ // but the generated code differs and might be less efficient? ++ //////// ++ return __builtin_ctzll(input_num); ++#endif // SIMDJSON_REGULAR_VISUAL_STUDIO ++} ++ ++/* result might be undefined when input_num is zero */ ++simdjson_inline uint64_t clear_lowest_bit(uint64_t input_num) { ++ return _blsr_u64(input_num); ++} ++ ++/* result might be undefined when input_num is zero */ ++simdjson_inline int leading_zeroes(uint64_t input_num) { ++ return int(_lzcnt_u64(input_num)); ++} ++ ++#if SIMDJSON_REGULAR_VISUAL_STUDIO ++simdjson_inline unsigned __int64 count_ones(uint64_t input_num) { ++ // note: we do not support legacy 32-bit Windows ++ return __popcnt64(input_num);// Visual Studio wants two underscores ++} ++#else ++simdjson_inline long long int count_ones(uint64_t input_num) { ++ return _popcnt64(input_num); ++} ++#endif ++ ++simdjson_inline bool add_overflow(uint64_t value1, uint64_t value2, ++ uint64_t *result) { ++#if SIMDJSON_REGULAR_VISUAL_STUDIO ++ return _addcarry_u64(0, value1, value2, ++ reinterpret_cast(result)); ++#else ++ return __builtin_uaddll_overflow(value1, value2, ++ reinterpret_cast(result)); ++#endif ++} ++ ++} // unnamed namespace ++} // namespace icelake ++} // namespace simdjson ++ ++#endif // SIMDJSON_ICELAKE_BITMANIPULATION_H ++/* end file include/simdjson/icelake/bitmanipulation.h */ ++/* begin file include/simdjson/icelake/bitmask.h */ ++#ifndef SIMDJSON_ICELAKE_BITMASK_H ++#define SIMDJSON_ICELAKE_BITMASK_H ++ ++namespace simdjson { ++namespace icelake { ++namespace { ++ ++// ++// Perform a "cumulative bitwise xor," flipping bits each time a 1 is encountered. ++// ++// For example, prefix_xor(00100100) == 00011100 ++// ++simdjson_inline uint64_t prefix_xor(const uint64_t bitmask) { ++ // There should be no such thing with a processor supporting avx2 ++ // but not clmul. ++ __m128i all_ones = _mm_set1_epi8('\xFF'); ++ __m128i result = _mm_clmulepi64_si128(_mm_set_epi64x(0ULL, bitmask), all_ones, 0); ++ return _mm_cvtsi128_si64(result); ++} ++ ++} // unnamed namespace ++} // namespace icelake ++} // namespace simdjson ++ ++#endif // SIMDJSON_ICELAKE_BITMASK_H ++/* end file include/simdjson/icelake/bitmask.h */ ++/* begin file include/simdjson/icelake/simd.h */ ++#ifndef SIMDJSON_ICELAKE_SIMD_H ++#define SIMDJSON_ICELAKE_SIMD_H ++ ++ ++ ++ ++#if defined(__GNUC__) && !defined(__clang__) ++#if __GNUC__ == 8 ++#define SIMDJSON_GCC8 1 ++#endif // __GNUC__ == 8 ++#endif // defined(__GNUC__) && !defined(__clang__) ++ ++#if SIMDJSON_GCC8 ++/** ++ * GCC 8 fails to provide _mm512_set_epi8. We roll our own. ++ */ ++inline __m512i _mm512_set_epi8(uint8_t a0, uint8_t a1, uint8_t a2, uint8_t a3, uint8_t a4, uint8_t a5, uint8_t a6, uint8_t a7, uint8_t a8, uint8_t a9, uint8_t a10, uint8_t a11, uint8_t a12, uint8_t a13, uint8_t a14, uint8_t a15, uint8_t a16, uint8_t a17, uint8_t a18, uint8_t a19, uint8_t a20, uint8_t a21, uint8_t a22, uint8_t a23, uint8_t a24, uint8_t a25, uint8_t a26, uint8_t a27, uint8_t a28, uint8_t a29, uint8_t a30, uint8_t a31, uint8_t a32, uint8_t a33, uint8_t a34, uint8_t a35, uint8_t a36, uint8_t a37, uint8_t a38, uint8_t a39, uint8_t a40, uint8_t a41, uint8_t a42, uint8_t a43, uint8_t a44, uint8_t a45, uint8_t a46, uint8_t a47, uint8_t a48, uint8_t a49, uint8_t a50, uint8_t a51, uint8_t a52, uint8_t a53, uint8_t a54, uint8_t a55, uint8_t a56, uint8_t a57, uint8_t a58, uint8_t a59, uint8_t a60, uint8_t a61, uint8_t a62, uint8_t a63) { ++ return _mm512_set_epi64(uint64_t(a7) + (uint64_t(a6) << 8) + (uint64_t(a5) << 16) + (uint64_t(a4) << 24) + (uint64_t(a3) << 32) + (uint64_t(a2) << 40) + (uint64_t(a1) << 48) + (uint64_t(a0) << 56), ++ uint64_t(a15) + (uint64_t(a14) << 8) + (uint64_t(a13) << 16) + (uint64_t(a12) << 24) + (uint64_t(a11) << 32) + (uint64_t(a10) << 40) + (uint64_t(a9) << 48) + (uint64_t(a8) << 56), ++ uint64_t(a23) + (uint64_t(a22) << 8) + (uint64_t(a21) << 16) + (uint64_t(a20) << 24) + (uint64_t(a19) << 32) + (uint64_t(a18) << 40) + (uint64_t(a17) << 48) + (uint64_t(a16) << 56), ++ uint64_t(a31) + (uint64_t(a30) << 8) + (uint64_t(a29) << 16) + (uint64_t(a28) << 24) + (uint64_t(a27) << 32) + (uint64_t(a26) << 40) + (uint64_t(a25) << 48) + (uint64_t(a24) << 56), ++ uint64_t(a39) + (uint64_t(a38) << 8) + (uint64_t(a37) << 16) + (uint64_t(a36) << 24) + (uint64_t(a35) << 32) + (uint64_t(a34) << 40) + (uint64_t(a33) << 48) + (uint64_t(a32) << 56), ++ uint64_t(a47) + (uint64_t(a46) << 8) + (uint64_t(a45) << 16) + (uint64_t(a44) << 24) + (uint64_t(a43) << 32) + (uint64_t(a42) << 40) + (uint64_t(a41) << 48) + (uint64_t(a40) << 56), ++ uint64_t(a55) + (uint64_t(a54) << 8) + (uint64_t(a53) << 16) + (uint64_t(a52) << 24) + (uint64_t(a51) << 32) + (uint64_t(a50) << 40) + (uint64_t(a49) << 48) + (uint64_t(a48) << 56), ++ uint64_t(a63) + (uint64_t(a62) << 8) + (uint64_t(a61) << 16) + (uint64_t(a60) << 24) + (uint64_t(a59) << 32) + (uint64_t(a58) << 40) + (uint64_t(a57) << 48) + (uint64_t(a56) << 56)); ++} ++#endif // SIMDJSON_GCC8 ++ ++ ++ ++namespace simdjson { ++namespace icelake { ++namespace { ++namespace simd { ++ ++ // Forward-declared so they can be used by splat and friends. ++ template ++ struct base { ++ __m512i value; ++ ++ // Zero constructor ++ simdjson_inline base() : value{__m512i()} {} ++ ++ // Conversion from SIMD register ++ simdjson_inline base(const __m512i _value) : value(_value) {} ++ ++ // Conversion to SIMD register ++ simdjson_inline operator const __m512i&() const { return this->value; } ++ simdjson_inline operator __m512i&() { return this->value; } ++ ++ // Bit operations ++ simdjson_inline Child operator|(const Child other) const { return _mm512_or_si512(*this, other); } ++ simdjson_inline Child operator&(const Child other) const { return _mm512_and_si512(*this, other); } ++ simdjson_inline Child operator^(const Child other) const { return _mm512_xor_si512(*this, other); } ++ simdjson_inline Child bit_andnot(const Child other) const { return _mm512_andnot_si512(other, *this); } ++ simdjson_inline Child& operator|=(const Child other) { auto this_cast = static_cast(this); *this_cast = *this_cast | other; return *this_cast; } ++ simdjson_inline Child& operator&=(const Child other) { auto this_cast = static_cast(this); *this_cast = *this_cast & other; return *this_cast; } ++ simdjson_inline Child& operator^=(const Child other) { auto this_cast = static_cast(this); *this_cast = *this_cast ^ other; return *this_cast; } ++ }; ++ ++ // Forward-declared so they can be used by splat and friends. ++ template ++ struct simd8; ++ ++ template> ++ struct base8: base> { ++ typedef uint32_t bitmask_t; ++ typedef uint64_t bitmask2_t; ++ ++ simdjson_inline base8() : base>() {} ++ simdjson_inline base8(const __m512i _value) : base>(_value) {} ++ ++ friend simdjson_really_inline uint64_t operator==(const simd8 lhs, const simd8 rhs) { ++ return _mm512_cmpeq_epi8_mask(lhs, rhs); ++ } ++ ++ static const int SIZE = sizeof(base::value); ++ ++ template ++ simdjson_inline simd8 prev(const simd8 prev_chunk) const { ++#if SIMDJSON_GCC8 ++ // workaround for compilers unable to figure out that 16 - N is a constant (GCC 8) ++ constexpr int shift = 16 - N; ++ return _mm512_alignr_epi8(*this, _mm512_permutex2var_epi64(prev_chunk, _mm512_set_epi64(13, 12, 11, 10, 9, 8, 7, 6), *this), shift); ++#else ++ return _mm512_alignr_epi8(*this, _mm512_permutex2var_epi64(prev_chunk, _mm512_set_epi64(13, 12, 11, 10, 9, 8, 7, 6), *this), 16 - N); ++#endif ++ } ++ }; ++ ++ // SIMD byte mask type (returned by things like eq and gt) ++ template<> ++ struct simd8: base8 { ++ static simdjson_inline simd8 splat(bool _value) { return _mm512_set1_epi8(uint8_t(-(!!_value))); } ++ ++ simdjson_inline simd8() : base8() {} ++ simdjson_inline simd8(const __m512i _value) : base8(_value) {} ++ // Splat constructor ++ simdjson_inline simd8(bool _value) : base8(splat(_value)) {} ++ simdjson_inline bool any() const { return !!_mm512_test_epi8_mask (*this, *this); } ++ simdjson_inline simd8 operator~() const { return *this ^ true; } ++ }; ++ ++ template ++ struct base8_numeric: base8 { ++ static simdjson_inline simd8 splat(T _value) { return _mm512_set1_epi8(_value); } ++ static simdjson_inline simd8 zero() { return _mm512_setzero_si512(); } ++ static simdjson_inline simd8 load(const T values[64]) { ++ return _mm512_loadu_si512(reinterpret_cast(values)); ++ } ++ // Repeat 16 values as many times as necessary (usually for lookup tables) ++ static simdjson_inline simd8 repeat_16( ++ T v0, T v1, T v2, T v3, T v4, T v5, T v6, T v7, ++ T v8, T v9, T v10, T v11, T v12, T v13, T v14, T v15 ++ ) { ++ return simd8( ++ v0, v1, v2, v3, v4, v5, v6, v7, ++ v8, v9, v10,v11,v12,v13,v14,v15, ++ v0, v1, v2, v3, v4, v5, v6, v7, ++ v8, v9, v10,v11,v12,v13,v14,v15, ++ v0, v1, v2, v3, v4, v5, v6, v7, ++ v8, v9, v10,v11,v12,v13,v14,v15, ++ v0, v1, v2, v3, v4, v5, v6, v7, ++ v8, v9, v10,v11,v12,v13,v14,v15 ++ ); ++ } ++ ++ simdjson_inline base8_numeric() : base8() {} ++ simdjson_inline base8_numeric(const __m512i _value) : base8(_value) {} ++ ++ // Store to array ++ simdjson_inline void store(T dst[64]) const { return _mm512_storeu_si512(reinterpret_cast<__m512i *>(dst), *this); } ++ ++ // Addition/subtraction are the same for signed and unsigned ++ simdjson_inline simd8 operator+(const simd8 other) const { return _mm512_add_epi8(*this, other); } ++ simdjson_inline simd8 operator-(const simd8 other) const { return _mm512_sub_epi8(*this, other); } ++ simdjson_inline simd8& operator+=(const simd8 other) { *this = *this + other; return *static_cast*>(this); } ++ simdjson_inline simd8& operator-=(const simd8 other) { *this = *this - other; return *static_cast*>(this); } ++ ++ // Override to distinguish from bool version ++ simdjson_inline simd8 operator~() const { return *this ^ 0xFFu; } ++ ++ // Perform a lookup assuming the value is between 0 and 16 (undefined behavior for out of range values) ++ template ++ simdjson_inline simd8 lookup_16(simd8 lookup_table) const { ++ return _mm512_shuffle_epi8(lookup_table, *this); ++ } ++ ++ // Copies to 'output" all bytes corresponding to a 0 in the mask (interpreted as a bitset). ++ // Passing a 0 value for mask would be equivalent to writing out every byte to output. ++ // Only the first 32 - count_ones(mask) bytes of the result are significant but 32 bytes ++ // get written. ++ // Design consideration: it seems like a function with the ++ // signature simd8 compress(uint32_t mask) would be ++ // sensible, but the AVX ISA makes this kind of approach difficult. ++ template ++ simdjson_inline void compress(uint64_t mask, L * output) const { ++ _mm512_mask_compressstoreu_epi8 (output,~mask,*this); ++ } ++ ++ template ++ simdjson_inline simd8 lookup_16( ++ L replace0, L replace1, L replace2, L replace3, ++ L replace4, L replace5, L replace6, L replace7, ++ L replace8, L replace9, L replace10, L replace11, ++ L replace12, L replace13, L replace14, L replace15) const { ++ return lookup_16(simd8::repeat_16( ++ replace0, replace1, replace2, replace3, ++ replace4, replace5, replace6, replace7, ++ replace8, replace9, replace10, replace11, ++ replace12, replace13, replace14, replace15 ++ )); ++ } ++ }; ++ ++ // Signed bytes ++ template<> ++ struct simd8 : base8_numeric { ++ simdjson_inline simd8() : base8_numeric() {} ++ simdjson_inline simd8(const __m512i _value) : base8_numeric(_value) {} ++ // Splat constructor ++ simdjson_inline simd8(int8_t _value) : simd8(splat(_value)) {} ++ // Array constructor ++ simdjson_inline simd8(const int8_t values[64]) : simd8(load(values)) {} ++ // Member-by-member initialization ++ simdjson_inline simd8( ++ int8_t v0, int8_t v1, int8_t v2, int8_t v3, int8_t v4, int8_t v5, int8_t v6, int8_t v7, ++ int8_t v8, int8_t v9, int8_t v10, int8_t v11, int8_t v12, int8_t v13, int8_t v14, int8_t v15, ++ int8_t v16, int8_t v17, int8_t v18, int8_t v19, int8_t v20, int8_t v21, int8_t v22, int8_t v23, ++ int8_t v24, int8_t v25, int8_t v26, int8_t v27, int8_t v28, int8_t v29, int8_t v30, int8_t v31, ++ int8_t v32, int8_t v33, int8_t v34, int8_t v35, int8_t v36, int8_t v37, int8_t v38, int8_t v39, ++ int8_t v40, int8_t v41, int8_t v42, int8_t v43, int8_t v44, int8_t v45, int8_t v46, int8_t v47, ++ int8_t v48, int8_t v49, int8_t v50, int8_t v51, int8_t v52, int8_t v53, int8_t v54, int8_t v55, ++ int8_t v56, int8_t v57, int8_t v58, int8_t v59, int8_t v60, int8_t v61, int8_t v62, int8_t v63 ++ ) : simd8(_mm512_set_epi8( ++ v63, v62, v61, v60, v59, v58, v57, v56, ++ v55, v54, v53, v52, v51, v50, v49, v48, ++ v47, v46, v45, v44, v43, v42, v41, v40, ++ v39, v38, v37, v36, v35, v34, v33, v32, ++ v31, v30, v29, v28, v27, v26, v25, v24, ++ v23, v22, v21, v20, v19, v18, v17, v16, ++ v15, v14, v13, v12, v11, v10, v9, v8, ++ v7, v6, v5, v4, v3, v2, v1, v0 ++ )) {} ++ ++ // Repeat 16 values as many times as necessary (usually for lookup tables) ++ simdjson_inline static simd8 repeat_16( ++ int8_t v0, int8_t v1, int8_t v2, int8_t v3, int8_t v4, int8_t v5, int8_t v6, int8_t v7, ++ int8_t v8, int8_t v9, int8_t v10, int8_t v11, int8_t v12, int8_t v13, int8_t v14, int8_t v15 ++ ) { ++ return simd8( ++ v0, v1, v2, v3, v4, v5, v6, v7, ++ v8, v9, v10,v11,v12,v13,v14,v15, ++ v0, v1, v2, v3, v4, v5, v6, v7, ++ v8, v9, v10,v11,v12,v13,v14,v15, ++ v0, v1, v2, v3, v4, v5, v6, v7, ++ v8, v9, v10,v11,v12,v13,v14,v15, ++ v0, v1, v2, v3, v4, v5, v6, v7, ++ v8, v9, v10,v11,v12,v13,v14,v15 ++ ); ++ } ++ ++ // Order-sensitive comparisons ++ simdjson_inline simd8 max_val(const simd8 other) const { return _mm512_max_epi8(*this, other); } ++ simdjson_inline simd8 min_val(const simd8 other) const { return _mm512_min_epi8(*this, other); } ++ ++ simdjson_inline simd8 operator>(const simd8 other) const { return _mm512_maskz_abs_epi8(_mm512_cmpgt_epi8_mask(*this, other),_mm512_set1_epi8(uint8_t(0x80))); } ++ simdjson_inline simd8 operator<(const simd8 other) const { return _mm512_maskz_abs_epi8(_mm512_cmpgt_epi8_mask(other, *this),_mm512_set1_epi8(uint8_t(0x80))); } ++ }; ++ ++ // Unsigned bytes ++ template<> ++ struct simd8: base8_numeric { ++ simdjson_inline simd8() : base8_numeric() {} ++ simdjson_inline simd8(const __m512i _value) : base8_numeric(_value) {} ++ // Splat constructor ++ simdjson_inline simd8(uint8_t _value) : simd8(splat(_value)) {} ++ // Array constructor ++ simdjson_inline simd8(const uint8_t values[64]) : simd8(load(values)) {} ++ // Member-by-member initialization ++ simdjson_inline simd8( ++ uint8_t v0, uint8_t v1, uint8_t v2, uint8_t v3, uint8_t v4, uint8_t v5, uint8_t v6, uint8_t v7, ++ uint8_t v8, uint8_t v9, uint8_t v10, uint8_t v11, uint8_t v12, uint8_t v13, uint8_t v14, uint8_t v15, ++ uint8_t v16, uint8_t v17, uint8_t v18, uint8_t v19, uint8_t v20, uint8_t v21, uint8_t v22, uint8_t v23, ++ uint8_t v24, uint8_t v25, uint8_t v26, uint8_t v27, uint8_t v28, uint8_t v29, uint8_t v30, uint8_t v31, ++ uint8_t v32, uint8_t v33, uint8_t v34, uint8_t v35, uint8_t v36, uint8_t v37, uint8_t v38, uint8_t v39, ++ uint8_t v40, uint8_t v41, uint8_t v42, uint8_t v43, uint8_t v44, uint8_t v45, uint8_t v46, uint8_t v47, ++ uint8_t v48, uint8_t v49, uint8_t v50, uint8_t v51, uint8_t v52, uint8_t v53, uint8_t v54, uint8_t v55, ++ uint8_t v56, uint8_t v57, uint8_t v58, uint8_t v59, uint8_t v60, uint8_t v61, uint8_t v62, uint8_t v63 ++ ) : simd8(_mm512_set_epi8( ++ v63, v62, v61, v60, v59, v58, v57, v56, ++ v55, v54, v53, v52, v51, v50, v49, v48, ++ v47, v46, v45, v44, v43, v42, v41, v40, ++ v39, v38, v37, v36, v35, v34, v33, v32, ++ v31, v30, v29, v28, v27, v26, v25, v24, ++ v23, v22, v21, v20, v19, v18, v17, v16, ++ v15, v14, v13, v12, v11, v10, v9, v8, ++ v7, v6, v5, v4, v3, v2, v1, v0 ++ )) {} ++ ++ // Repeat 16 values as many times as necessary (usually for lookup tables) ++ simdjson_inline static simd8 repeat_16( ++ uint8_t v0, uint8_t v1, uint8_t v2, uint8_t v3, uint8_t v4, uint8_t v5, uint8_t v6, uint8_t v7, ++ uint8_t v8, uint8_t v9, uint8_t v10, uint8_t v11, uint8_t v12, uint8_t v13, uint8_t v14, uint8_t v15 ++ ) { ++ return simd8( ++ v0, v1, v2, v3, v4, v5, v6, v7, ++ v8, v9, v10,v11,v12,v13,v14,v15, ++ v0, v1, v2, v3, v4, v5, v6, v7, ++ v8, v9, v10,v11,v12,v13,v14,v15, ++ v0, v1, v2, v3, v4, v5, v6, v7, ++ v8, v9, v10,v11,v12,v13,v14,v15, ++ v0, v1, v2, v3, v4, v5, v6, v7, ++ v8, v9, v10,v11,v12,v13,v14,v15 ++ ); ++ } ++ ++ // Saturated math ++ simdjson_inline simd8 saturating_add(const simd8 other) const { return _mm512_adds_epu8(*this, other); } ++ simdjson_inline simd8 saturating_sub(const simd8 other) const { return _mm512_subs_epu8(*this, other); } ++ ++ // Order-specific operations ++ simdjson_inline simd8 max_val(const simd8 other) const { return _mm512_max_epu8(*this, other); } ++ simdjson_inline simd8 min_val(const simd8 other) const { return _mm512_min_epu8(other, *this); } ++ // Same as >, but only guarantees true is nonzero (< guarantees true = -1) ++ simdjson_inline simd8 gt_bits(const simd8 other) const { return this->saturating_sub(other); } ++ // Same as <, but only guarantees true is nonzero (< guarantees true = -1) ++ simdjson_inline simd8 lt_bits(const simd8 other) const { return other.saturating_sub(*this); } ++ simdjson_inline uint64_t operator<=(const simd8 other) const { return other.max_val(*this) == other; } ++ simdjson_inline uint64_t operator>=(const simd8 other) const { return other.min_val(*this) == other; } ++ simdjson_inline simd8 operator>(const simd8 other) const { return this->gt_bits(other).any_bits_set(); } ++ simdjson_inline simd8 operator<(const simd8 other) const { return this->lt_bits(other).any_bits_set(); } ++ ++ // Bit-specific operations ++ simdjson_inline simd8 bits_not_set() const { return _mm512_mask_blend_epi8(*this == uint8_t(0), _mm512_set1_epi8(0), _mm512_set1_epi8(-1)); } ++ simdjson_inline simd8 bits_not_set(simd8 bits) const { return (*this & bits).bits_not_set(); } ++ simdjson_inline simd8 any_bits_set() const { return ~this->bits_not_set(); } ++ simdjson_inline simd8 any_bits_set(simd8 bits) const { return ~this->bits_not_set(bits); } ++ ++ simdjson_inline bool is_ascii() const { return _mm512_movepi8_mask(*this) == 0; } ++ simdjson_inline bool bits_not_set_anywhere() const { ++ return !_mm512_test_epi8_mask(*this, *this); ++ } ++ simdjson_inline bool any_bits_set_anywhere() const { return !bits_not_set_anywhere(); } ++ simdjson_inline bool bits_not_set_anywhere(simd8 bits) const { return !_mm512_test_epi8_mask(*this, bits); } ++ simdjson_inline bool any_bits_set_anywhere(simd8 bits) const { return !bits_not_set_anywhere(bits); } ++ template ++ simdjson_inline simd8 shr() const { return simd8(_mm512_srli_epi16(*this, N)) & uint8_t(0xFFu >> N); } ++ template ++ simdjson_inline simd8 shl() const { return simd8(_mm512_slli_epi16(*this, N)) & uint8_t(0xFFu << N); } ++ // Get one of the bits and make a bitmask out of it. ++ // e.g. value.get_bit<7>() gets the high bit ++ template ++ simdjson_inline uint64_t get_bit() const { return _mm512_movepi8_mask(_mm512_slli_epi16(*this, 7-N)); } ++ }; ++ ++ template ++ struct simd8x64 { ++ static constexpr int NUM_CHUNKS = 64 / sizeof(simd8); ++ static_assert(NUM_CHUNKS == 1, "Icelake kernel should use one register per 64-byte block."); ++ const simd8 chunks[NUM_CHUNKS]; ++ ++ simd8x64(const simd8x64& o) = delete; // no copy allowed ++ simd8x64& operator=(const simd8& other) = delete; // no assignment allowed ++ simd8x64() = delete; // no default constructor allowed ++ ++ simdjson_inline simd8x64(const simd8 chunk0, const simd8 chunk1) : chunks{chunk0, chunk1} {} ++ simdjson_inline simd8x64(const simd8 chunk0) : chunks{chunk0} {} ++ simdjson_inline simd8x64(const T ptr[64]) : chunks{simd8::load(ptr)} {} ++ ++ simdjson_inline uint64_t compress(uint64_t mask, T * output) const { ++ this->chunks[0].compress(mask, output); ++ return 64 - count_ones(mask); ++ } ++ ++ simdjson_inline void store(T ptr[64]) const { ++ this->chunks[0].store(ptr+sizeof(simd8)*0); ++ } ++ ++ simdjson_inline simd8 reduce_or() const { ++ return this->chunks[0]; ++ } ++ ++ simdjson_inline simd8x64 bit_or(const T m) const { ++ const simd8 mask = simd8::splat(m); ++ return simd8x64( ++ this->chunks[0] | mask ++ ); ++ } ++ ++ simdjson_inline uint64_t eq(const T m) const { ++ const simd8 mask = simd8::splat(m); ++ return this->chunks[0] == mask; ++ } ++ ++ simdjson_inline uint64_t eq(const simd8x64 &other) const { ++ return this->chunks[0] == other.chunks[0]; ++ } ++ ++ simdjson_inline uint64_t lteq(const T m) const { ++ const simd8 mask = simd8::splat(m); ++ return this->chunks[0] <= mask; ++ } ++ }; // struct simd8x64 ++ ++} // namespace simd ++ ++} // unnamed namespace ++} // namespace icelake ++} // namespace simdjson ++ ++#endif // SIMDJSON_ICELAKE_SIMD_H ++/* end file include/simdjson/icelake/simd.h */ ++/* begin file include/simdjson/generic/jsoncharutils.h */ ++ ++namespace simdjson { ++namespace icelake { ++namespace { ++namespace jsoncharutils { ++ ++// return non-zero if not a structural or whitespace char ++// zero otherwise ++simdjson_inline uint32_t is_not_structural_or_whitespace(uint8_t c) { ++ return internal::structural_or_whitespace_negated[c]; ++} ++ ++simdjson_inline uint32_t is_structural_or_whitespace(uint8_t c) { ++ return internal::structural_or_whitespace[c]; ++} ++ ++// returns a value with the high 16 bits set if not valid ++// otherwise returns the conversion of the 4 hex digits at src into the bottom ++// 16 bits of the 32-bit return register ++// ++// see ++// https://lemire.me/blog/2019/04/17/parsing-short-hexadecimal-strings-efficiently/ ++static inline uint32_t hex_to_u32_nocheck( ++ const uint8_t *src) { // strictly speaking, static inline is a C-ism ++ uint32_t v1 = internal::digit_to_val32[630 + src[0]]; ++ uint32_t v2 = internal::digit_to_val32[420 + src[1]]; ++ uint32_t v3 = internal::digit_to_val32[210 + src[2]]; ++ uint32_t v4 = internal::digit_to_val32[0 + src[3]]; ++ return v1 | v2 | v3 | v4; ++} ++ ++// given a code point cp, writes to c ++// the utf-8 code, outputting the length in ++// bytes, if the length is zero, the code point ++// is invalid ++// ++// This can possibly be made faster using pdep ++// and clz and table lookups, but JSON documents ++// have few escaped code points, and the following ++// function looks cheap. ++// ++// Note: we assume that surrogates are treated separately ++// ++simdjson_inline size_t codepoint_to_utf8(uint32_t cp, uint8_t *c) { ++ if (cp <= 0x7F) { ++ c[0] = uint8_t(cp); ++ return 1; // ascii ++ } ++ if (cp <= 0x7FF) { ++ c[0] = uint8_t((cp >> 6) + 192); ++ c[1] = uint8_t((cp & 63) + 128); ++ return 2; // universal plane ++ // Surrogates are treated elsewhere... ++ //} //else if (0xd800 <= cp && cp <= 0xdfff) { ++ // return 0; // surrogates // could put assert here ++ } else if (cp <= 0xFFFF) { ++ c[0] = uint8_t((cp >> 12) + 224); ++ c[1] = uint8_t(((cp >> 6) & 63) + 128); ++ c[2] = uint8_t((cp & 63) + 128); ++ return 3; ++ } else if (cp <= 0x10FFFF) { // if you know you have a valid code point, this ++ // is not needed ++ c[0] = uint8_t((cp >> 18) + 240); ++ c[1] = uint8_t(((cp >> 12) & 63) + 128); ++ c[2] = uint8_t(((cp >> 6) & 63) + 128); ++ c[3] = uint8_t((cp & 63) + 128); ++ return 4; ++ } ++ // will return 0 when the code point was too large. ++ return 0; // bad r ++} ++ ++#if SIMDJSON_IS_32BITS // _umul128 for x86, arm ++// this is a slow emulation routine for 32-bit ++// ++static simdjson_inline uint64_t __emulu(uint32_t x, uint32_t y) { ++ return x * (uint64_t)y; ++} ++static simdjson_inline uint64_t _umul128(uint64_t ab, uint64_t cd, uint64_t *hi) { ++ uint64_t ad = __emulu((uint32_t)(ab >> 32), (uint32_t)cd); ++ uint64_t bd = __emulu((uint32_t)ab, (uint32_t)cd); ++ uint64_t adbc = ad + __emulu((uint32_t)ab, (uint32_t)(cd >> 32)); ++ uint64_t adbc_carry = !!(adbc < ad); ++ uint64_t lo = bd + (adbc << 32); ++ *hi = __emulu((uint32_t)(ab >> 32), (uint32_t)(cd >> 32)) + (adbc >> 32) + ++ (adbc_carry << 32) + !!(lo < bd); ++ return lo; ++} ++#endif ++ ++using internal::value128; ++ ++simdjson_inline value128 full_multiplication(uint64_t value1, uint64_t value2) { ++ value128 answer; ++#if SIMDJSON_REGULAR_VISUAL_STUDIO || SIMDJSON_IS_32BITS ++#ifdef _M_ARM64 ++ // ARM64 has native support for 64-bit multiplications, no need to emultate ++ answer.high = __umulh(value1, value2); ++ answer.low = value1 * value2; ++#else ++ answer.low = _umul128(value1, value2, &answer.high); // _umul128 not available on ARM64 ++#endif // _M_ARM64 ++#else // SIMDJSON_REGULAR_VISUAL_STUDIO || SIMDJSON_IS_32BITS ++ __uint128_t r = (static_cast<__uint128_t>(value1)) * value2; ++ answer.low = uint64_t(r); ++ answer.high = uint64_t(r >> 64); ++#endif ++ return answer; ++} ++ ++} // namespace jsoncharutils ++} // unnamed namespace ++} // namespace icelake ++} // namespace simdjson ++/* end file include/simdjson/generic/jsoncharutils.h */ ++/* begin file include/simdjson/generic/atomparsing.h */ ++namespace simdjson { ++namespace icelake { ++namespace { ++/// @private ++namespace atomparsing { ++ ++// The string_to_uint32 is exclusively used to map literal strings to 32-bit values. ++// We use memcpy instead of a pointer cast to avoid undefined behaviors since we cannot ++// be certain that the character pointer will be properly aligned. ++// You might think that using memcpy makes this function expensive, but you'd be wrong. ++// All decent optimizing compilers (GCC, clang, Visual Studio) will compile string_to_uint32("false"); ++// to the compile-time constant 1936482662. ++simdjson_inline uint32_t string_to_uint32(const char* str) { uint32_t val; std::memcpy(&val, str, sizeof(uint32_t)); return val; } ++ ++ ++// Again in str4ncmp we use a memcpy to avoid undefined behavior. The memcpy may appear expensive. ++// Yet all decent optimizing compilers will compile memcpy to a single instruction, just about. ++simdjson_warn_unused ++simdjson_inline uint32_t str4ncmp(const uint8_t *src, const char* atom) { ++ uint32_t srcval; // we want to avoid unaligned 32-bit loads (undefined in C/C++) ++ static_assert(sizeof(uint32_t) <= SIMDJSON_PADDING, "SIMDJSON_PADDING must be larger than 4 bytes"); ++ std::memcpy(&srcval, src, sizeof(uint32_t)); ++ return srcval ^ string_to_uint32(atom); ++} ++ ++simdjson_warn_unused ++simdjson_inline bool is_valid_true_atom(const uint8_t *src) { ++ return (str4ncmp(src, "true") | jsoncharutils::is_not_structural_or_whitespace(src[4])) == 0; ++} ++ ++simdjson_warn_unused ++simdjson_inline bool is_valid_true_atom(const uint8_t *src, size_t len) { ++ if (len > 4) { return is_valid_true_atom(src); } ++ else if (len == 4) { return !str4ncmp(src, "true"); } ++ else { return false; } ++} ++ ++simdjson_warn_unused ++simdjson_inline bool is_valid_false_atom(const uint8_t *src) { ++ return (str4ncmp(src+1, "alse") | jsoncharutils::is_not_structural_or_whitespace(src[5])) == 0; ++} ++ ++simdjson_warn_unused ++simdjson_inline bool is_valid_false_atom(const uint8_t *src, size_t len) { ++ if (len > 5) { return is_valid_false_atom(src); } ++ else if (len == 5) { return !str4ncmp(src+1, "alse"); } ++ else { return false; } ++} ++ ++simdjson_warn_unused ++simdjson_inline bool is_valid_null_atom(const uint8_t *src) { ++ return (str4ncmp(src, "null") | jsoncharutils::is_not_structural_or_whitespace(src[4])) == 0; ++} ++ ++simdjson_warn_unused ++simdjson_inline bool is_valid_null_atom(const uint8_t *src, size_t len) { ++ if (len > 4) { return is_valid_null_atom(src); } ++ else if (len == 4) { return !str4ncmp(src, "null"); } ++ else { return false; } ++} ++ ++} // namespace atomparsing ++} // unnamed namespace ++} // namespace icelake ++} // namespace simdjson ++/* end file include/simdjson/generic/atomparsing.h */ ++/* begin file include/simdjson/icelake/stringparsing.h */ ++#ifndef SIMDJSON_ICELAKE_STRINGPARSING_H ++#define SIMDJSON_ICELAKE_STRINGPARSING_H ++ ++ ++namespace simdjson { ++namespace icelake { ++namespace { ++ ++using namespace simd; ++ ++// Holds backslashes and quotes locations. ++struct backslash_and_quote { ++public: ++ static constexpr uint32_t BYTES_PROCESSED = 32; ++ simdjson_inline static backslash_and_quote copy_and_find(const uint8_t *src, uint8_t *dst); ++ ++ simdjson_inline bool has_quote_first() { return ((bs_bits - 1) & quote_bits) != 0; } ++ simdjson_inline bool has_backslash() { return ((quote_bits - 1) & bs_bits) != 0; } ++ simdjson_inline int quote_index() { return trailing_zeroes(quote_bits); } ++ simdjson_inline int backslash_index() { return trailing_zeroes(bs_bits); } ++ ++ uint64_t bs_bits; ++ uint64_t quote_bits; ++}; // struct backslash_and_quote ++ ++simdjson_inline backslash_and_quote backslash_and_quote::copy_and_find(const uint8_t *src, uint8_t *dst) { ++ // this can read up to 15 bytes beyond the buffer size, but we require ++ // SIMDJSON_PADDING of padding ++ static_assert(SIMDJSON_PADDING >= (BYTES_PROCESSED - 1), "backslash and quote finder must process fewer than SIMDJSON_PADDING bytes"); ++ simd8 v(src); ++ // store to dest unconditionally - we can overwrite the bits we don't like later ++ v.store(dst); ++ return { ++ static_cast(v == '\\'), // bs_bits ++ static_cast(v == '"'), // quote_bits ++ }; ++} ++ ++} // unnamed namespace ++} // namespace icelake ++} // namespace simdjson ++ ++#endif // SIMDJSON_ICELAKE_STRINGPARSING_H ++/* end file include/simdjson/icelake/stringparsing.h */ ++/* begin file include/simdjson/icelake/numberparsing.h */ ++#ifndef SIMDJSON_ICELAKE_NUMBERPARSING_H ++#define SIMDJSON_ICELAKE_NUMBERPARSING_H ++ ++namespace simdjson { ++namespace icelake { ++namespace { ++ ++static simdjson_inline uint32_t parse_eight_digits_unrolled(const uint8_t *chars) { ++ // this actually computes *16* values so we are being wasteful. ++ const __m128i ascii0 = _mm_set1_epi8('0'); ++ const __m128i mul_1_10 = ++ _mm_setr_epi8(10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1); ++ const __m128i mul_1_100 = _mm_setr_epi16(100, 1, 100, 1, 100, 1, 100, 1); ++ const __m128i mul_1_10000 = ++ _mm_setr_epi16(10000, 1, 10000, 1, 10000, 1, 10000, 1); ++ const __m128i input = _mm_sub_epi8( ++ _mm_loadu_si128(reinterpret_cast(chars)), ascii0); ++ const __m128i t1 = _mm_maddubs_epi16(input, mul_1_10); ++ const __m128i t2 = _mm_madd_epi16(t1, mul_1_100); ++ const __m128i t3 = _mm_packus_epi32(t2, t2); ++ const __m128i t4 = _mm_madd_epi16(t3, mul_1_10000); ++ return _mm_cvtsi128_si32( ++ t4); // only captures the sum of the first 8 digits, drop the rest ++} ++ ++} // unnamed namespace ++} // namespace icelake ++} // namespace simdjson ++ ++#define SIMDJSON_SWAR_NUMBER_PARSING 1 ++ ++/* begin file include/simdjson/generic/numberparsing.h */ ++#include ++ ++namespace simdjson { ++namespace icelake { ++ ++namespace ondemand { ++/** ++ * The type of a JSON number ++ */ ++enum class number_type { ++ floating_point_number=1, /// a binary64 number ++ signed_integer, /// a signed integer that fits in a 64-bit word using two's complement ++ unsigned_integer /// a positive integer larger or equal to 1<<63 ++}; ++} ++ ++namespace { ++/// @private ++namespace numberparsing { ++ ++ ++ ++#ifdef JSON_TEST_NUMBERS ++#define INVALID_NUMBER(SRC) (found_invalid_number((SRC)), NUMBER_ERROR) ++#define WRITE_INTEGER(VALUE, SRC, WRITER) (found_integer((VALUE), (SRC)), (WRITER).append_s64((VALUE))) ++#define WRITE_UNSIGNED(VALUE, SRC, WRITER) (found_unsigned_integer((VALUE), (SRC)), (WRITER).append_u64((VALUE))) ++#define WRITE_DOUBLE(VALUE, SRC, WRITER) (found_float((VALUE), (SRC)), (WRITER).append_double((VALUE))) ++#else ++#define INVALID_NUMBER(SRC) (NUMBER_ERROR) ++#define WRITE_INTEGER(VALUE, SRC, WRITER) (WRITER).append_s64((VALUE)) ++#define WRITE_UNSIGNED(VALUE, SRC, WRITER) (WRITER).append_u64((VALUE)) ++#define WRITE_DOUBLE(VALUE, SRC, WRITER) (WRITER).append_double((VALUE)) ++#endif ++ ++namespace { ++// Convert a mantissa, an exponent and a sign bit into an ieee64 double. ++// The real_exponent needs to be in [0, 2046] (technically real_exponent = 2047 would be acceptable). ++// The mantissa should be in [0,1<<53). The bit at index (1ULL << 52) while be zeroed. ++simdjson_inline double to_double(uint64_t mantissa, uint64_t real_exponent, bool negative) { ++ double d; ++ mantissa &= ~(1ULL << 52); ++ mantissa |= real_exponent << 52; ++ mantissa |= ((static_cast(negative)) << 63); ++ std::memcpy(&d, &mantissa, sizeof(d)); ++ return d; ++} ++} ++// Attempts to compute i * 10^(power) exactly; and if "negative" is ++// true, negate the result. ++// This function will only work in some cases, when it does not work, success is ++// set to false. This should work *most of the time* (like 99% of the time). ++// We assume that power is in the [smallest_power, ++// largest_power] interval: the caller is responsible for this check. ++simdjson_inline bool compute_float_64(int64_t power, uint64_t i, bool negative, double &d) { ++ // we start with a fast path ++ // It was described in ++ // Clinger WD. How to read floating point numbers accurately. ++ // ACM SIGPLAN Notices. 1990 ++#ifndef FLT_EVAL_METHOD ++#error "FLT_EVAL_METHOD should be defined, please include cfloat." ++#endif ++#if (FLT_EVAL_METHOD != 1) && (FLT_EVAL_METHOD != 0) ++ // We cannot be certain that x/y is rounded to nearest. ++ if (0 <= power && power <= 22 && i <= 9007199254740991) { ++#else ++ if (-22 <= power && power <= 22 && i <= 9007199254740991) { ++#endif ++ // convert the integer into a double. This is lossless since ++ // 0 <= i <= 2^53 - 1. ++ d = double(i); ++ // ++ // The general idea is as follows. ++ // If 0 <= s < 2^53 and if 10^0 <= p <= 10^22 then ++ // 1) Both s and p can be represented exactly as 64-bit floating-point ++ // values ++ // (binary64). ++ // 2) Because s and p can be represented exactly as floating-point values, ++ // then s * p ++ // and s / p will produce correctly rounded values. ++ // ++ if (power < 0) { ++ d = d / simdjson::internal::power_of_ten[-power]; ++ } else { ++ d = d * simdjson::internal::power_of_ten[power]; ++ } ++ if (negative) { ++ d = -d; ++ } ++ return true; ++ } ++ // When 22 < power && power < 22 + 16, we could ++ // hope for another, secondary fast path. It was ++ // described by David M. Gay in "Correctly rounded ++ // binary-decimal and decimal-binary conversions." (1990) ++ // If you need to compute i * 10^(22 + x) for x < 16, ++ // first compute i * 10^x, if you know that result is exact ++ // (e.g., when i * 10^x < 2^53), ++ // then you can still proceed and do (i * 10^x) * 10^22. ++ // Is this worth your time? ++ // You need 22 < power *and* power < 22 + 16 *and* (i * 10^(x-22) < 2^53) ++ // for this second fast path to work. ++ // If you you have 22 < power *and* power < 22 + 16, and then you ++ // optimistically compute "i * 10^(x-22)", there is still a chance that you ++ // have wasted your time if i * 10^(x-22) >= 2^53. It makes the use cases of ++ // this optimization maybe less common than we would like. Source: ++ // http://www.exploringbinary.com/fast-path-decimal-to-floating-point-conversion/ ++ // also used in RapidJSON: https://rapidjson.org/strtod_8h_source.html ++ ++ // The fast path has now failed, so we are failing back on the slower path. ++ ++ // In the slow path, we need to adjust i so that it is > 1<<63 which is always ++ // possible, except if i == 0, so we handle i == 0 separately. ++ if(i == 0) { ++ d = negative ? -0.0 : 0.0; ++ return true; ++ } ++ ++ ++ // The exponent is 1024 + 63 + power ++ // + floor(log(5**power)/log(2)). ++ // The 1024 comes from the ieee64 standard. ++ // The 63 comes from the fact that we use a 64-bit word. ++ // ++ // Computing floor(log(5**power)/log(2)) could be ++ // slow. Instead we use a fast function. ++ // ++ // For power in (-400,350), we have that ++ // (((152170 + 65536) * power ) >> 16); ++ // is equal to ++ // floor(log(5**power)/log(2)) + power when power >= 0 ++ // and it is equal to ++ // ceil(log(5**-power)/log(2)) + power when power < 0 ++ // ++ // The 65536 is (1<<16) and corresponds to ++ // (65536 * power) >> 16 ---> power ++ // ++ // ((152170 * power ) >> 16) is equal to ++ // floor(log(5**power)/log(2)) ++ // ++ // Note that this is not magic: 152170/(1<<16) is ++ // approximatively equal to log(5)/log(2). ++ // The 1<<16 value is a power of two; we could use a ++ // larger power of 2 if we wanted to. ++ // ++ int64_t exponent = (((152170 + 65536) * power) >> 16) + 1024 + 63; ++ ++ ++ // We want the most significant bit of i to be 1. Shift if needed. ++ int lz = leading_zeroes(i); ++ i <<= lz; ++ ++ ++ // We are going to need to do some 64-bit arithmetic to get a precise product. ++ // We use a table lookup approach. ++ // It is safe because ++ // power >= smallest_power ++ // and power <= largest_power ++ // We recover the mantissa of the power, it has a leading 1. It is always ++ // rounded down. ++ // ++ // We want the most significant 64 bits of the product. We know ++ // this will be non-zero because the most significant bit of i is ++ // 1. ++ const uint32_t index = 2 * uint32_t(power - simdjson::internal::smallest_power); ++ // Optimization: It may be that materializing the index as a variable might confuse some compilers and prevent effective complex-addressing loads. (Done for code clarity.) ++ // ++ // The full_multiplication function computes the 128-bit product of two 64-bit words ++ // with a returned value of type value128 with a "low component" corresponding to the ++ // 64-bit least significant bits of the product and with a "high component" corresponding ++ // to the 64-bit most significant bits of the product. ++ simdjson::internal::value128 firstproduct = jsoncharutils::full_multiplication(i, simdjson::internal::power_of_five_128[index]); ++ // Both i and power_of_five_128[index] have their most significant bit set to 1 which ++ // implies that the either the most or the second most significant bit of the product ++ // is 1. We pack values in this manner for efficiency reasons: it maximizes the use ++ // we make of the product. It also makes it easy to reason about the product: there ++ // is 0 or 1 leading zero in the product. ++ ++ // Unless the least significant 9 bits of the high (64-bit) part of the full ++ // product are all 1s, then we know that the most significant 55 bits are ++ // exact and no further work is needed. Having 55 bits is necessary because ++ // we need 53 bits for the mantissa but we have to have one rounding bit and ++ // we can waste a bit if the most significant bit of the product is zero. ++ if((firstproduct.high & 0x1FF) == 0x1FF) { ++ // We want to compute i * 5^q, but only care about the top 55 bits at most. ++ // Consider the scenario where q>=0. Then 5^q may not fit in 64-bits. Doing ++ // the full computation is wasteful. So we do what is called a "truncated ++ // multiplication". ++ // We take the most significant 64-bits, and we put them in ++ // power_of_five_128[index]. Usually, that's good enough to approximate i * 5^q ++ // to the desired approximation using one multiplication. Sometimes it does not suffice. ++ // Then we store the next most significant 64 bits in power_of_five_128[index + 1], and ++ // then we get a better approximation to i * 5^q. In very rare cases, even that ++ // will not suffice, though it is seemingly very hard to find such a scenario. ++ // ++ // That's for when q>=0. The logic for q<0 is somewhat similar but it is somewhat ++ // more complicated. ++ // ++ // There is an extra layer of complexity in that we need more than 55 bits of ++ // accuracy in the round-to-even scenario. ++ // ++ // The full_multiplication function computes the 128-bit product of two 64-bit words ++ // with a returned value of type value128 with a "low component" corresponding to the ++ // 64-bit least significant bits of the product and with a "high component" corresponding ++ // to the 64-bit most significant bits of the product. ++ simdjson::internal::value128 secondproduct = jsoncharutils::full_multiplication(i, simdjson::internal::power_of_five_128[index + 1]); ++ firstproduct.low += secondproduct.high; ++ if(secondproduct.high > firstproduct.low) { firstproduct.high++; } ++ // At this point, we might need to add at most one to firstproduct, but this ++ // can only change the value of firstproduct.high if firstproduct.low is maximal. ++ if(simdjson_unlikely(firstproduct.low == 0xFFFFFFFFFFFFFFFF)) { ++ // This is very unlikely, but if so, we need to do much more work! ++ return false; ++ } ++ } ++ uint64_t lower = firstproduct.low; ++ uint64_t upper = firstproduct.high; ++ // The final mantissa should be 53 bits with a leading 1. ++ // We shift it so that it occupies 54 bits with a leading 1. ++ /////// ++ uint64_t upperbit = upper >> 63; ++ uint64_t mantissa = upper >> (upperbit + 9); ++ lz += int(1 ^ upperbit); ++ ++ // Here we have mantissa < (1<<54). ++ int64_t real_exponent = exponent - lz; ++ if (simdjson_unlikely(real_exponent <= 0)) { // we have a subnormal? ++ // Here have that real_exponent <= 0 so -real_exponent >= 0 ++ if(-real_exponent + 1 >= 64) { // if we have more than 64 bits below the minimum exponent, you have a zero for sure. ++ d = negative ? -0.0 : 0.0; ++ return true; ++ } ++ // next line is safe because -real_exponent + 1 < 0 ++ mantissa >>= -real_exponent + 1; ++ // Thankfully, we can't have both "round-to-even" and subnormals because ++ // "round-to-even" only occurs for powers close to 0. ++ mantissa += (mantissa & 1); // round up ++ mantissa >>= 1; ++ // There is a weird scenario where we don't have a subnormal but just. ++ // Suppose we start with 2.2250738585072013e-308, we end up ++ // with 0x3fffffffffffff x 2^-1023-53 which is technically subnormal ++ // whereas 0x40000000000000 x 2^-1023-53 is normal. Now, we need to round ++ // up 0x3fffffffffffff x 2^-1023-53 and once we do, we are no longer ++ // subnormal, but we can only know this after rounding. ++ // So we only declare a subnormal if we are smaller than the threshold. ++ real_exponent = (mantissa < (uint64_t(1) << 52)) ? 0 : 1; ++ d = to_double(mantissa, real_exponent, negative); ++ return true; ++ } ++ // We have to round to even. The "to even" part ++ // is only a problem when we are right in between two floats ++ // which we guard against. ++ // If we have lots of trailing zeros, we may fall right between two ++ // floating-point values. ++ // ++ // The round-to-even cases take the form of a number 2m+1 which is in (2^53,2^54] ++ // times a power of two. That is, it is right between a number with binary significand ++ // m and another number with binary significand m+1; and it must be the case ++ // that it cannot be represented by a float itself. ++ // ++ // We must have that w * 10 ^q == (2m+1) * 2^p for some power of two 2^p. ++ // Recall that 10^q = 5^q * 2^q. ++ // When q >= 0, we must have that (2m+1) is divible by 5^q, so 5^q <= 2^54. We have that ++ // 5^23 <= 2^54 and it is the last power of five to qualify, so q <= 23. ++ // When q<0, we have w >= (2m+1) x 5^{-q}. We must have that w<2^{64} so ++ // (2m+1) x 5^{-q} < 2^{64}. We have that 2m+1>2^{53}. Hence, we must have ++ // 2^{53} x 5^{-q} < 2^{64}. ++ // Hence we have 5^{-q} < 2^{11}$ or q>= -4. ++ // ++ // We require lower <= 1 and not lower == 0 because we could not prove that ++ // that lower == 0 is implied; but we could prove that lower <= 1 is a necessary and sufficient test. ++ if (simdjson_unlikely((lower <= 1) && (power >= -4) && (power <= 23) && ((mantissa & 3) == 1))) { ++ if((mantissa << (upperbit + 64 - 53 - 2)) == upper) { ++ mantissa &= ~1; // flip it so that we do not round up ++ } ++ } ++ ++ mantissa += mantissa & 1; ++ mantissa >>= 1; ++ ++ // Here we have mantissa < (1<<53), unless there was an overflow ++ if (mantissa >= (1ULL << 53)) { ++ ////////// ++ // This will happen when parsing values such as 7.2057594037927933e+16 ++ //////// ++ mantissa = (1ULL << 52); ++ real_exponent++; ++ } ++ mantissa &= ~(1ULL << 52); ++ // we have to check that real_exponent is in range, otherwise we bail out ++ if (simdjson_unlikely(real_exponent > 2046)) { ++ // We have an infinite value!!! We could actually throw an error here if we could. ++ return false; ++ } ++ d = to_double(mantissa, real_exponent, negative); ++ return true; ++} ++ ++// We call a fallback floating-point parser that might be slow. Note ++// it will accept JSON numbers, but the JSON spec. is more restrictive so ++// before you call parse_float_fallback, you need to have validated the input ++// string with the JSON grammar. ++// It will return an error (false) if the parsed number is infinite. ++// The string parsing itself always succeeds. We know that there is at least ++// one digit. ++static bool parse_float_fallback(const uint8_t *ptr, double *outDouble) { ++ *outDouble = simdjson::internal::from_chars(reinterpret_cast(ptr)); ++ // We do not accept infinite values. ++ ++ // Detecting finite values in a portable manner is ridiculously hard, ideally ++ // we would want to do: ++ // return !std::isfinite(*outDouble); ++ // but that mysteriously fails under legacy/old libc++ libraries, see ++ // https://github.com/simdjson/simdjson/issues/1286 ++ // ++ // Therefore, fall back to this solution (the extra parens are there ++ // to handle that max may be a macro on windows). ++ return !(*outDouble > (std::numeric_limits::max)() || *outDouble < std::numeric_limits::lowest()); ++} ++static bool parse_float_fallback(const uint8_t *ptr, const uint8_t *end_ptr, double *outDouble) { ++ *outDouble = simdjson::internal::from_chars(reinterpret_cast(ptr), reinterpret_cast(end_ptr)); ++ // We do not accept infinite values. ++ ++ // Detecting finite values in a portable manner is ridiculously hard, ideally ++ // we would want to do: ++ // return !std::isfinite(*outDouble); ++ // but that mysteriously fails under legacy/old libc++ libraries, see ++ // https://github.com/simdjson/simdjson/issues/1286 ++ // ++ // Therefore, fall back to this solution (the extra parens are there ++ // to handle that max may be a macro on windows). ++ return !(*outDouble > (std::numeric_limits::max)() || *outDouble < std::numeric_limits::lowest()); ++} ++ ++// check quickly whether the next 8 chars are made of digits ++// at a glance, it looks better than Mula's ++// http://0x80.pl/articles/swar-digits-validate.html ++simdjson_inline bool is_made_of_eight_digits_fast(const uint8_t *chars) { ++ uint64_t val; ++ // this can read up to 7 bytes beyond the buffer size, but we require ++ // SIMDJSON_PADDING of padding ++ static_assert(7 <= SIMDJSON_PADDING, "SIMDJSON_PADDING must be bigger than 7"); ++ std::memcpy(&val, chars, 8); ++ // a branchy method might be faster: ++ // return (( val & 0xF0F0F0F0F0F0F0F0 ) == 0x3030303030303030) ++ // && (( (val + 0x0606060606060606) & 0xF0F0F0F0F0F0F0F0 ) == ++ // 0x3030303030303030); ++ return (((val & 0xF0F0F0F0F0F0F0F0) | ++ (((val + 0x0606060606060606) & 0xF0F0F0F0F0F0F0F0) >> 4)) == ++ 0x3333333333333333); ++} ++ ++template ++error_code slow_float_parsing(simdjson_unused const uint8_t * src, W writer) { ++ double d; ++ if (parse_float_fallback(src, &d)) { ++ writer.append_double(d); ++ return SUCCESS; ++ } ++ return INVALID_NUMBER(src); ++} ++ ++template ++SIMDJSON_NO_SANITIZE_UNDEFINED // We deliberately allow overflow here and check later ++simdjson_inline bool parse_digit(const uint8_t c, I &i) { ++ const uint8_t digit = static_cast(c - '0'); ++ if (digit > 9) { ++ return false; ++ } ++ // PERF NOTE: multiplication by 10 is cheaper than arbitrary integer multiplication ++ i = 10 * i + digit; // might overflow, we will handle the overflow later ++ return true; ++} ++ ++simdjson_inline error_code parse_decimal(simdjson_unused const uint8_t *const src, const uint8_t *&p, uint64_t &i, int64_t &exponent) { ++ // we continue with the fiction that we have an integer. If the ++ // floating point number is representable as x * 10^z for some integer ++ // z that fits in 53 bits, then we will be able to convert back the ++ // the integer into a float in a lossless manner. ++ const uint8_t *const first_after_period = p; ++ ++#ifdef SIMDJSON_SWAR_NUMBER_PARSING ++#if SIMDJSON_SWAR_NUMBER_PARSING ++ // this helps if we have lots of decimals! ++ // this turns out to be frequent enough. ++ if (is_made_of_eight_digits_fast(p)) { ++ i = i * 100000000 + parse_eight_digits_unrolled(p); ++ p += 8; ++ } ++#endif // SIMDJSON_SWAR_NUMBER_PARSING ++#endif // #ifdef SIMDJSON_SWAR_NUMBER_PARSING ++ // Unrolling the first digit makes a small difference on some implementations (e.g. westmere) ++ if (parse_digit(*p, i)) { ++p; } ++ while (parse_digit(*p, i)) { p++; } ++ exponent = first_after_period - p; ++ // Decimal without digits (123.) is illegal ++ if (exponent == 0) { ++ return INVALID_NUMBER(src); ++ } ++ return SUCCESS; ++} ++ ++simdjson_inline error_code parse_exponent(simdjson_unused const uint8_t *const src, const uint8_t *&p, int64_t &exponent) { ++ // Exp Sign: -123.456e[-]78 ++ bool neg_exp = ('-' == *p); ++ if (neg_exp || '+' == *p) { p++; } // Skip + as well ++ ++ // Exponent: -123.456e-[78] ++ auto start_exp = p; ++ int64_t exp_number = 0; ++ while (parse_digit(*p, exp_number)) { ++p; } ++ // It is possible for parse_digit to overflow. ++ // In particular, it could overflow to INT64_MIN, and we cannot do - INT64_MIN. ++ // Thus we *must* check for possible overflow before we negate exp_number. ++ ++ // Performance notes: it may seem like combining the two "simdjson_unlikely checks" below into ++ // a single simdjson_unlikely path would be faster. The reasoning is sound, but the compiler may ++ // not oblige and may, in fact, generate two distinct paths in any case. It might be ++ // possible to do uint64_t(p - start_exp - 1) >= 18 but it could end up trading off ++ // instructions for a simdjson_likely branch, an unconclusive gain. ++ ++ // If there were no digits, it's an error. ++ if (simdjson_unlikely(p == start_exp)) { ++ return INVALID_NUMBER(src); ++ } ++ // We have a valid positive exponent in exp_number at this point, except that ++ // it may have overflowed. ++ ++ // If there were more than 18 digits, we may have overflowed the integer. We have to do ++ // something!!!! ++ if (simdjson_unlikely(p > start_exp+18)) { ++ // Skip leading zeroes: 1e000000000000000000001 is technically valid and doesn't overflow ++ while (*start_exp == '0') { start_exp++; } ++ // 19 digits could overflow int64_t and is kind of absurd anyway. We don't ++ // support exponents smaller than -999,999,999,999,999,999 and bigger ++ // than 999,999,999,999,999,999. ++ // We can truncate. ++ // Note that 999999999999999999 is assuredly too large. The maximal ieee64 value before ++ // infinity is ~1.8e308. The smallest subnormal is ~5e-324. So, actually, we could ++ // truncate at 324. ++ // Note that there is no reason to fail per se at this point in time. ++ // E.g., 0e999999999999999999999 is a fine number. ++ if (p > start_exp+18) { exp_number = 999999999999999999; } ++ } ++ // At this point, we know that exp_number is a sane, positive, signed integer. ++ // It is <= 999,999,999,999,999,999. As long as 'exponent' is in ++ // [-8223372036854775808, 8223372036854775808], we won't overflow. Because 'exponent' ++ // is bounded in magnitude by the size of the JSON input, we are fine in this universe. ++ // To sum it up: the next line should never overflow. ++ exponent += (neg_exp ? -exp_number : exp_number); ++ return SUCCESS; ++} ++ ++simdjson_inline size_t significant_digits(const uint8_t * start_digits, size_t digit_count) { ++ // It is possible that the integer had an overflow. ++ // We have to handle the case where we have 0.0000somenumber. ++ const uint8_t *start = start_digits; ++ while ((*start == '0') || (*start == '.')) { ++start; } ++ // we over-decrement by one when there is a '.' ++ return digit_count - size_t(start - start_digits); ++} ++ ++template ++simdjson_inline error_code write_float(const uint8_t *const src, bool negative, uint64_t i, const uint8_t * start_digits, size_t digit_count, int64_t exponent, W &writer) { ++ // If we frequently had to deal with long strings of digits, ++ // we could extend our code by using a 128-bit integer instead ++ // of a 64-bit integer. However, this is uncommon in practice. ++ // ++ // 9999999999999999999 < 2**64 so we can accommodate 19 digits. ++ // If we have a decimal separator, then digit_count - 1 is the number of digits, but we ++ // may not have a decimal separator! ++ if (simdjson_unlikely(digit_count > 19 && significant_digits(start_digits, digit_count) > 19)) { ++ // Ok, chances are good that we had an overflow! ++ // this is almost never going to get called!!! ++ // we start anew, going slowly!!! ++ // This will happen in the following examples: ++ // 10000000000000000000000000000000000000000000e+308 ++ // 3.1415926535897932384626433832795028841971693993751 ++ // ++ // NOTE: This makes a *copy* of the writer and passes it to slow_float_parsing. This happens ++ // because slow_float_parsing is a non-inlined function. If we passed our writer reference to ++ // it, it would force it to be stored in memory, preventing the compiler from picking it apart ++ // and putting into registers. i.e. if we pass it as reference, it gets slow. ++ // This is what forces the skip_double, as well. ++ error_code error = slow_float_parsing(src, writer); ++ writer.skip_double(); ++ return error; ++ } ++ // NOTE: it's weird that the simdjson_unlikely() only wraps half the if, but it seems to get slower any other ++ // way we've tried: https://github.com/simdjson/simdjson/pull/990#discussion_r448497331 ++ // To future reader: we'd love if someone found a better way, or at least could explain this result! ++ if (simdjson_unlikely(exponent < simdjson::internal::smallest_power) || (exponent > simdjson::internal::largest_power)) { ++ // ++ // Important: smallest_power is such that it leads to a zero value. ++ // Observe that 18446744073709551615e-343 == 0, i.e. (2**64 - 1) e -343 is zero ++ // so something x 10^-343 goes to zero, but not so with something x 10^-342. ++ static_assert(simdjson::internal::smallest_power <= -342, "smallest_power is not small enough"); ++ // ++ if((exponent < simdjson::internal::smallest_power) || (i == 0)) { ++ // E.g. Parse "-0.0e-999" into the same value as "-0.0". See https://en.wikipedia.org/wiki/Signed_zero ++ WRITE_DOUBLE(negative ? -0.0 : 0.0, src, writer); ++ return SUCCESS; ++ } else { // (exponent > largest_power) and (i != 0) ++ // We have, for sure, an infinite value and simdjson refuses to parse infinite values. ++ return INVALID_NUMBER(src); ++ } ++ } ++ double d; ++ if (!compute_float_64(exponent, i, negative, d)) { ++ // we are almost never going to get here. ++ if (!parse_float_fallback(src, &d)) { return INVALID_NUMBER(src); } ++ } ++ WRITE_DOUBLE(d, src, writer); ++ return SUCCESS; ++} ++ ++// for performance analysis, it is sometimes useful to skip parsing ++#ifdef SIMDJSON_SKIPNUMBERPARSING ++ ++template ++simdjson_inline error_code parse_number(const uint8_t *const, W &writer) { ++ writer.append_s64(0); // always write zero ++ return SUCCESS; // always succeeds ++} ++ ++simdjson_unused simdjson_inline simdjson_result parse_unsigned(const uint8_t * const src) noexcept { return 0; } ++simdjson_unused simdjson_inline simdjson_result parse_integer(const uint8_t * const src) noexcept { return 0; } ++simdjson_unused simdjson_inline simdjson_result parse_double(const uint8_t * const src) noexcept { return 0; } ++simdjson_unused simdjson_inline simdjson_result parse_unsigned_in_string(const uint8_t * const src) noexcept { return 0; } ++simdjson_unused simdjson_inline simdjson_result parse_integer_in_string(const uint8_t * const src) noexcept { return 0; } ++simdjson_unused simdjson_inline simdjson_result parse_double_in_string(const uint8_t * const src) noexcept { return 0; } ++simdjson_unused simdjson_inline bool is_negative(const uint8_t * src) noexcept { return false; } ++simdjson_unused simdjson_inline simdjson_result is_integer(const uint8_t * src) noexcept { return false; } ++simdjson_unused simdjson_inline simdjson_result get_number_type(const uint8_t * src) noexcept { return ondemand::number_type::signed_integer; } ++#else ++ ++// parse the number at src ++// define JSON_TEST_NUMBERS for unit testing ++// ++// It is assumed that the number is followed by a structural ({,},],[) character ++// or a white space character. If that is not the case (e.g., when the JSON ++// document is made of a single number), then it is necessary to copy the ++// content and append a space before calling this function. ++// ++// Our objective is accurate parsing (ULP of 0) at high speed. ++template ++simdjson_inline error_code parse_number(const uint8_t *const src, W &writer) { ++ ++ // ++ // Check for minus sign ++ // ++ bool negative = (*src == '-'); ++ const uint8_t *p = src + uint8_t(negative); ++ ++ // ++ // Parse the integer part. ++ // ++ // PERF NOTE: we don't use is_made_of_eight_digits_fast because large integers like 123456789 are rare ++ const uint8_t *const start_digits = p; ++ uint64_t i = 0; ++ while (parse_digit(*p, i)) { p++; } ++ ++ // If there were no digits, or if the integer starts with 0 and has more than one digit, it's an error. ++ // Optimization note: size_t is expected to be unsigned. ++ size_t digit_count = size_t(p - start_digits); ++ if (digit_count == 0 || ('0' == *start_digits && digit_count > 1)) { return INVALID_NUMBER(src); } ++ ++ // ++ // Handle floats if there is a . or e (or both) ++ // ++ int64_t exponent = 0; ++ bool is_float = false; ++ if ('.' == *p) { ++ is_float = true; ++ ++p; ++ SIMDJSON_TRY( parse_decimal(src, p, i, exponent) ); ++ digit_count = int(p - start_digits); // used later to guard against overflows ++ } ++ if (('e' == *p) || ('E' == *p)) { ++ is_float = true; ++ ++p; ++ SIMDJSON_TRY( parse_exponent(src, p, exponent) ); ++ } ++ if (is_float) { ++ const bool dirty_end = jsoncharutils::is_not_structural_or_whitespace(*p); ++ SIMDJSON_TRY( write_float(src, negative, i, start_digits, digit_count, exponent, writer) ); ++ if (dirty_end) { return INVALID_NUMBER(src); } ++ return SUCCESS; ++ } ++ ++ // The longest negative 64-bit number is 19 digits. ++ // The longest positive 64-bit number is 20 digits. ++ // We do it this way so we don't trigger this branch unless we must. ++ size_t longest_digit_count = negative ? 19 : 20; ++ if (digit_count > longest_digit_count) { return INVALID_NUMBER(src); } ++ if (digit_count == longest_digit_count) { ++ if (negative) { ++ // Anything negative above INT64_MAX+1 is invalid ++ if (i > uint64_t(INT64_MAX)+1) { return INVALID_NUMBER(src); } ++ WRITE_INTEGER(~i+1, src, writer); ++ if (jsoncharutils::is_not_structural_or_whitespace(*p)) { return INVALID_NUMBER(src); } ++ return SUCCESS; ++ // Positive overflow check: ++ // - A 20 digit number starting with 2-9 is overflow, because 18,446,744,073,709,551,615 is the ++ // biggest uint64_t. ++ // - A 20 digit number starting with 1 is overflow if it is less than INT64_MAX. ++ // If we got here, it's a 20 digit number starting with the digit "1". ++ // - If a 20 digit number starting with 1 overflowed (i*10+digit), the result will be smaller ++ // than 1,553,255,926,290,448,384. ++ // - That is smaller than the smallest possible 20-digit number the user could write: ++ // 10,000,000,000,000,000,000. ++ // - Therefore, if the number is positive and lower than that, it's overflow. ++ // - The value we are looking at is less than or equal to INT64_MAX. ++ // ++ } else if (src[0] != uint8_t('1') || i <= uint64_t(INT64_MAX)) { return INVALID_NUMBER(src); } ++ } ++ ++ // Write unsigned if it doesn't fit in a signed integer. ++ if (i > uint64_t(INT64_MAX)) { ++ WRITE_UNSIGNED(i, src, writer); ++ } else { ++ WRITE_INTEGER(negative ? (~i+1) : i, src, writer); ++ } ++ if (jsoncharutils::is_not_structural_or_whitespace(*p)) { return INVALID_NUMBER(src); } ++ return SUCCESS; ++} ++ ++// Inlineable functions ++namespace { ++ ++// This table can be used to characterize the final character of an integer ++// string. For JSON structural character and allowable white space characters, ++// we return SUCCESS. For 'e', '.' and 'E', we return INCORRECT_TYPE. Otherwise ++// we return NUMBER_ERROR. ++// Optimization note: we could easily reduce the size of the table by half (to 128) ++// at the cost of an extra branch. ++// Optimization note: we want the values to use at most 8 bits (not, e.g., 32 bits): ++static_assert(error_code(uint8_t(NUMBER_ERROR))== NUMBER_ERROR, "bad NUMBER_ERROR cast"); ++static_assert(error_code(uint8_t(SUCCESS))== SUCCESS, "bad NUMBER_ERROR cast"); ++static_assert(error_code(uint8_t(INCORRECT_TYPE))== INCORRECT_TYPE, "bad NUMBER_ERROR cast"); ++ ++const uint8_t integer_string_finisher[256] = { ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, SUCCESS, ++ SUCCESS, NUMBER_ERROR, NUMBER_ERROR, SUCCESS, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, SUCCESS, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, SUCCESS, ++ NUMBER_ERROR, INCORRECT_TYPE, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, SUCCESS, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, INCORRECT_TYPE, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, SUCCESS, NUMBER_ERROR, SUCCESS, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, INCORRECT_TYPE, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, SUCCESS, NUMBER_ERROR, ++ SUCCESS, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR}; ++ ++// Parse any number from 0 to 18,446,744,073,709,551,615 ++simdjson_unused simdjson_inline simdjson_result parse_unsigned(const uint8_t * const src) noexcept { ++ const uint8_t *p = src; ++ // ++ // Parse the integer part. ++ // ++ // PERF NOTE: we don't use is_made_of_eight_digits_fast because large integers like 123456789 are rare ++ const uint8_t *const start_digits = p; ++ uint64_t i = 0; ++ while (parse_digit(*p, i)) { p++; } ++ ++ // If there were no digits, or if the integer starts with 0 and has more than one digit, it's an error. ++ // Optimization note: size_t is expected to be unsigned. ++ size_t digit_count = size_t(p - start_digits); ++ // The longest positive 64-bit number is 20 digits. ++ // We do it this way so we don't trigger this branch unless we must. ++ // Optimization note: the compiler can probably merge ++ // ((digit_count == 0) || (digit_count > 20)) ++ // into a single branch since digit_count is unsigned. ++ if ((digit_count == 0) || (digit_count > 20)) { return INCORRECT_TYPE; } ++ // Here digit_count > 0. ++ if (('0' == *start_digits) && (digit_count > 1)) { return NUMBER_ERROR; } ++ // We can do the following... ++ // if (!jsoncharutils::is_structural_or_whitespace(*p)) { ++ // return (*p == '.' || *p == 'e' || *p == 'E') ? INCORRECT_TYPE : NUMBER_ERROR; ++ // } ++ // as a single table lookup: ++ if (integer_string_finisher[*p] != SUCCESS) { return error_code(integer_string_finisher[*p]); } ++ ++ if (digit_count == 20) { ++ // Positive overflow check: ++ // - A 20 digit number starting with 2-9 is overflow, because 18,446,744,073,709,551,615 is the ++ // biggest uint64_t. ++ // - A 20 digit number starting with 1 is overflow if it is less than INT64_MAX. ++ // If we got here, it's a 20 digit number starting with the digit "1". ++ // - If a 20 digit number starting with 1 overflowed (i*10+digit), the result will be smaller ++ // than 1,553,255,926,290,448,384. ++ // - That is smaller than the smallest possible 20-digit number the user could write: ++ // 10,000,000,000,000,000,000. ++ // - Therefore, if the number is positive and lower than that, it's overflow. ++ // - The value we are looking at is less than or equal to INT64_MAX. ++ // ++ if (src[0] != uint8_t('1') || i <= uint64_t(INT64_MAX)) { return INCORRECT_TYPE; } ++ } ++ ++ return i; ++} ++ ++ ++// Parse any number from 0 to 18,446,744,073,709,551,615 ++// Never read at src_end or beyond ++simdjson_unused simdjson_inline simdjson_result parse_unsigned(const uint8_t * const src, const uint8_t * const src_end) noexcept { ++ const uint8_t *p = src; ++ // ++ // Parse the integer part. ++ // ++ // PERF NOTE: we don't use is_made_of_eight_digits_fast because large integers like 123456789 are rare ++ const uint8_t *const start_digits = p; ++ uint64_t i = 0; ++ while ((p != src_end) && parse_digit(*p, i)) { p++; } ++ ++ // If there were no digits, or if the integer starts with 0 and has more than one digit, it's an error. ++ // Optimization note: size_t is expected to be unsigned. ++ size_t digit_count = size_t(p - start_digits); ++ // The longest positive 64-bit number is 20 digits. ++ // We do it this way so we don't trigger this branch unless we must. ++ // Optimization note: the compiler can probably merge ++ // ((digit_count == 0) || (digit_count > 20)) ++ // into a single branch since digit_count is unsigned. ++ if ((digit_count == 0) || (digit_count > 20)) { return INCORRECT_TYPE; } ++ // Here digit_count > 0. ++ if (('0' == *start_digits) && (digit_count > 1)) { return NUMBER_ERROR; } ++ // We can do the following... ++ // if (!jsoncharutils::is_structural_or_whitespace(*p)) { ++ // return (*p == '.' || *p == 'e' || *p == 'E') ? INCORRECT_TYPE : NUMBER_ERROR; ++ // } ++ // as a single table lookup: ++ if ((p != src_end) && integer_string_finisher[*p] != SUCCESS) { return error_code(integer_string_finisher[*p]); } ++ ++ if (digit_count == 20) { ++ // Positive overflow check: ++ // - A 20 digit number starting with 2-9 is overflow, because 18,446,744,073,709,551,615 is the ++ // biggest uint64_t. ++ // - A 20 digit number starting with 1 is overflow if it is less than INT64_MAX. ++ // If we got here, it's a 20 digit number starting with the digit "1". ++ // - If a 20 digit number starting with 1 overflowed (i*10+digit), the result will be smaller ++ // than 1,553,255,926,290,448,384. ++ // - That is smaller than the smallest possible 20-digit number the user could write: ++ // 10,000,000,000,000,000,000. ++ // - Therefore, if the number is positive and lower than that, it's overflow. ++ // - The value we are looking at is less than or equal to INT64_MAX. ++ // ++ if (src[0] != uint8_t('1') || i <= uint64_t(INT64_MAX)) { return INCORRECT_TYPE; } ++ } ++ ++ return i; ++} ++ ++// Parse any number from 0 to 18,446,744,073,709,551,615 ++simdjson_unused simdjson_inline simdjson_result parse_unsigned_in_string(const uint8_t * const src) noexcept { ++ const uint8_t *p = src + 1; ++ // ++ // Parse the integer part. ++ // ++ // PERF NOTE: we don't use is_made_of_eight_digits_fast because large integers like 123456789 are rare ++ const uint8_t *const start_digits = p; ++ uint64_t i = 0; ++ while (parse_digit(*p, i)) { p++; } ++ ++ // If there were no digits, or if the integer starts with 0 and has more than one digit, it's an error. ++ // Optimization note: size_t is expected to be unsigned. ++ size_t digit_count = size_t(p - start_digits); ++ // The longest positive 64-bit number is 20 digits. ++ // We do it this way so we don't trigger this branch unless we must. ++ // Optimization note: the compiler can probably merge ++ // ((digit_count == 0) || (digit_count > 20)) ++ // into a single branch since digit_count is unsigned. ++ if ((digit_count == 0) || (digit_count > 20)) { return INCORRECT_TYPE; } ++ // Here digit_count > 0. ++ if (('0' == *start_digits) && (digit_count > 1)) { return NUMBER_ERROR; } ++ // We can do the following... ++ // if (!jsoncharutils::is_structural_or_whitespace(*p)) { ++ // return (*p == '.' || *p == 'e' || *p == 'E') ? INCORRECT_TYPE : NUMBER_ERROR; ++ // } ++ // as a single table lookup: ++ if (*p != '"') { return NUMBER_ERROR; } ++ ++ if (digit_count == 20) { ++ // Positive overflow check: ++ // - A 20 digit number starting with 2-9 is overflow, because 18,446,744,073,709,551,615 is the ++ // biggest uint64_t. ++ // - A 20 digit number starting with 1 is overflow if it is less than INT64_MAX. ++ // If we got here, it's a 20 digit number starting with the digit "1". ++ // - If a 20 digit number starting with 1 overflowed (i*10+digit), the result will be smaller ++ // than 1,553,255,926,290,448,384. ++ // - That is smaller than the smallest possible 20-digit number the user could write: ++ // 10,000,000,000,000,000,000. ++ // - Therefore, if the number is positive and lower than that, it's overflow. ++ // - The value we are looking at is less than or equal to INT64_MAX. ++ // ++ // Note: we use src[1] and not src[0] because src[0] is the quote character in this ++ // instance. ++ if (src[1] != uint8_t('1') || i <= uint64_t(INT64_MAX)) { return INCORRECT_TYPE; } ++ } ++ ++ return i; ++} ++ ++// Parse any number from -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 ++simdjson_unused simdjson_inline simdjson_result parse_integer(const uint8_t *src) noexcept { ++ // ++ // Check for minus sign ++ // ++ bool negative = (*src == '-'); ++ const uint8_t *p = src + uint8_t(negative); ++ ++ // ++ // Parse the integer part. ++ // ++ // PERF NOTE: we don't use is_made_of_eight_digits_fast because large integers like 123456789 are rare ++ const uint8_t *const start_digits = p; ++ uint64_t i = 0; ++ while (parse_digit(*p, i)) { p++; } ++ ++ // If there were no digits, or if the integer starts with 0 and has more than one digit, it's an error. ++ // Optimization note: size_t is expected to be unsigned. ++ size_t digit_count = size_t(p - start_digits); ++ // We go from ++ // -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 ++ // so we can never represent numbers that have more than 19 digits. ++ size_t longest_digit_count = 19; ++ // Optimization note: the compiler can probably merge ++ // ((digit_count == 0) || (digit_count > longest_digit_count)) ++ // into a single branch since digit_count is unsigned. ++ if ((digit_count == 0) || (digit_count > longest_digit_count)) { return INCORRECT_TYPE; } ++ // Here digit_count > 0. ++ if (('0' == *start_digits) && (digit_count > 1)) { return NUMBER_ERROR; } ++ // We can do the following... ++ // if (!jsoncharutils::is_structural_or_whitespace(*p)) { ++ // return (*p == '.' || *p == 'e' || *p == 'E') ? INCORRECT_TYPE : NUMBER_ERROR; ++ // } ++ // as a single table lookup: ++ if(integer_string_finisher[*p] != SUCCESS) { return error_code(integer_string_finisher[*p]); } ++ // Negative numbers have can go down to - INT64_MAX - 1 whereas positive numbers are limited to INT64_MAX. ++ // Performance note: This check is only needed when digit_count == longest_digit_count but it is ++ // so cheap that we might as well always make it. ++ if(i > uint64_t(INT64_MAX) + uint64_t(negative)) { return INCORRECT_TYPE; } ++ return negative ? (~i+1) : i; ++} ++ ++// Parse any number from -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 ++// Never read at src_end or beyond ++simdjson_unused simdjson_inline simdjson_result parse_integer(const uint8_t * const src, const uint8_t * const src_end) noexcept { ++ // ++ // Check for minus sign ++ // ++ if(src == src_end) { return NUMBER_ERROR; } ++ bool negative = (*src == '-'); ++ const uint8_t *p = src + uint8_t(negative); ++ ++ // ++ // Parse the integer part. ++ // ++ // PERF NOTE: we don't use is_made_of_eight_digits_fast because large integers like 123456789 are rare ++ const uint8_t *const start_digits = p; ++ uint64_t i = 0; ++ while ((p != src_end) && parse_digit(*p, i)) { p++; } ++ ++ // If there were no digits, or if the integer starts with 0 and has more than one digit, it's an error. ++ // Optimization note: size_t is expected to be unsigned. ++ size_t digit_count = size_t(p - start_digits); ++ // We go from ++ // -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 ++ // so we can never represent numbers that have more than 19 digits. ++ size_t longest_digit_count = 19; ++ // Optimization note: the compiler can probably merge ++ // ((digit_count == 0) || (digit_count > longest_digit_count)) ++ // into a single branch since digit_count is unsigned. ++ if ((digit_count == 0) || (digit_count > longest_digit_count)) { return INCORRECT_TYPE; } ++ // Here digit_count > 0. ++ if (('0' == *start_digits) && (digit_count > 1)) { return NUMBER_ERROR; } ++ // We can do the following... ++ // if (!jsoncharutils::is_structural_or_whitespace(*p)) { ++ // return (*p == '.' || *p == 'e' || *p == 'E') ? INCORRECT_TYPE : NUMBER_ERROR; ++ // } ++ // as a single table lookup: ++ if((p != src_end) && integer_string_finisher[*p] != SUCCESS) { return error_code(integer_string_finisher[*p]); } ++ // Negative numbers have can go down to - INT64_MAX - 1 whereas positive numbers are limited to INT64_MAX. ++ // Performance note: This check is only needed when digit_count == longest_digit_count but it is ++ // so cheap that we might as well always make it. ++ if(i > uint64_t(INT64_MAX) + uint64_t(negative)) { return INCORRECT_TYPE; } ++ return negative ? (~i+1) : i; ++} ++ ++// Parse any number from -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 ++simdjson_unused simdjson_inline simdjson_result parse_integer_in_string(const uint8_t *src) noexcept { ++ // ++ // Check for minus sign ++ // ++ bool negative = (*(src + 1) == '-'); ++ src += uint8_t(negative) + 1; ++ ++ // ++ // Parse the integer part. ++ // ++ // PERF NOTE: we don't use is_made_of_eight_digits_fast because large integers like 123456789 are rare ++ const uint8_t *const start_digits = src; ++ uint64_t i = 0; ++ while (parse_digit(*src, i)) { src++; } ++ ++ // If there were no digits, or if the integer starts with 0 and has more than one digit, it's an error. ++ // Optimization note: size_t is expected to be unsigned. ++ size_t digit_count = size_t(src - start_digits); ++ // We go from ++ // -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 ++ // so we can never represent numbers that have more than 19 digits. ++ size_t longest_digit_count = 19; ++ // Optimization note: the compiler can probably merge ++ // ((digit_count == 0) || (digit_count > longest_digit_count)) ++ // into a single branch since digit_count is unsigned. ++ if ((digit_count == 0) || (digit_count > longest_digit_count)) { return INCORRECT_TYPE; } ++ // Here digit_count > 0. ++ if (('0' == *start_digits) && (digit_count > 1)) { return NUMBER_ERROR; } ++ // We can do the following... ++ // if (!jsoncharutils::is_structural_or_whitespace(*src)) { ++ // return (*src == '.' || *src == 'e' || *src == 'E') ? INCORRECT_TYPE : NUMBER_ERROR; ++ // } ++ // as a single table lookup: ++ if(*src != '"') { return NUMBER_ERROR; } ++ // Negative numbers have can go down to - INT64_MAX - 1 whereas positive numbers are limited to INT64_MAX. ++ // Performance note: This check is only needed when digit_count == longest_digit_count but it is ++ // so cheap that we might as well always make it. ++ if(i > uint64_t(INT64_MAX) + uint64_t(negative)) { return INCORRECT_TYPE; } ++ return negative ? (~i+1) : i; ++} ++ ++simdjson_unused simdjson_inline simdjson_result parse_double(const uint8_t * src) noexcept { ++ // ++ // Check for minus sign ++ // ++ bool negative = (*src == '-'); ++ src += uint8_t(negative); ++ ++ // ++ // Parse the integer part. ++ // ++ uint64_t i = 0; ++ const uint8_t *p = src; ++ p += parse_digit(*p, i); ++ bool leading_zero = (i == 0); ++ while (parse_digit(*p, i)) { p++; } ++ // no integer digits, or 0123 (zero must be solo) ++ if ( p == src ) { return INCORRECT_TYPE; } ++ if ( (leading_zero && p != src+1)) { return NUMBER_ERROR; } ++ ++ // ++ // Parse the decimal part. ++ // ++ int64_t exponent = 0; ++ bool overflow; ++ if (simdjson_likely(*p == '.')) { ++ p++; ++ const uint8_t *start_decimal_digits = p; ++ if (!parse_digit(*p, i)) { return NUMBER_ERROR; } // no decimal digits ++ p++; ++ while (parse_digit(*p, i)) { p++; } ++ exponent = -(p - start_decimal_digits); ++ ++ // Overflow check. More than 19 digits (minus the decimal) may be overflow. ++ overflow = p-src-1 > 19; ++ if (simdjson_unlikely(overflow && leading_zero)) { ++ // Skip leading 0.00000 and see if it still overflows ++ const uint8_t *start_digits = src + 2; ++ while (*start_digits == '0') { start_digits++; } ++ overflow = start_digits-src > 19; ++ } ++ } else { ++ overflow = p-src > 19; ++ } ++ ++ // ++ // Parse the exponent ++ // ++ if (*p == 'e' || *p == 'E') { ++ p++; ++ bool exp_neg = *p == '-'; ++ p += exp_neg || *p == '+'; ++ ++ uint64_t exp = 0; ++ const uint8_t *start_exp_digits = p; ++ while (parse_digit(*p, exp)) { p++; } ++ // no exp digits, or 20+ exp digits ++ if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } ++ ++ exponent += exp_neg ? 0-exp : exp; ++ } ++ ++ if (jsoncharutils::is_not_structural_or_whitespace(*p)) { return NUMBER_ERROR; } ++ ++ overflow = overflow || exponent < simdjson::internal::smallest_power || exponent > simdjson::internal::largest_power; ++ ++ // ++ // Assemble (or slow-parse) the float ++ // ++ double d; ++ if (simdjson_likely(!overflow)) { ++ if (compute_float_64(exponent, i, negative, d)) { return d; } ++ } ++ if (!parse_float_fallback(src - uint8_t(negative), &d)) { ++ return NUMBER_ERROR; ++ } ++ return d; ++} ++ ++simdjson_unused simdjson_inline bool is_negative(const uint8_t * src) noexcept { ++ return (*src == '-'); ++} ++ ++simdjson_unused simdjson_inline simdjson_result is_integer(const uint8_t * src) noexcept { ++ bool negative = (*src == '-'); ++ src += uint8_t(negative); ++ const uint8_t *p = src; ++ while(static_cast(*p - '0') <= 9) { p++; } ++ if ( p == src ) { return NUMBER_ERROR; } ++ if (jsoncharutils::is_structural_or_whitespace(*p)) { return true; } ++ return false; ++} ++ ++simdjson_unused simdjson_inline simdjson_result get_number_type(const uint8_t * src) noexcept { ++ bool negative = (*src == '-'); ++ src += uint8_t(negative); ++ const uint8_t *p = src; ++ while(static_cast(*p - '0') <= 9) { p++; } ++ if ( p == src ) { return NUMBER_ERROR; } ++ if (jsoncharutils::is_structural_or_whitespace(*p)) { ++ // We have an integer. ++ // If the number is negative and valid, it must be a signed integer. ++ if(negative) { return ondemand::number_type::signed_integer; } ++ // We want values larger or equal to 9223372036854775808 to be unsigned ++ // integers, and the other values to be signed integers. ++ int digit_count = int(p - src); ++ if(digit_count >= 19) { ++ const uint8_t * smaller_big_integer = reinterpret_cast("9223372036854775808"); ++ if((digit_count >= 20) || (memcmp(src, smaller_big_integer, 19) >= 0)) { ++ return ondemand::number_type::unsigned_integer; ++ } ++ } ++ return ondemand::number_type::signed_integer; ++ } ++ // Hopefully, we have 'e' or 'E' or '.'. ++ return ondemand::number_type::floating_point_number; ++} ++ ++// Never read at src_end or beyond ++simdjson_unused simdjson_inline simdjson_result parse_double(const uint8_t * src, const uint8_t * const src_end) noexcept { ++ if(src == src_end) { return NUMBER_ERROR; } ++ // ++ // Check for minus sign ++ // ++ bool negative = (*src == '-'); ++ src += uint8_t(negative); ++ ++ // ++ // Parse the integer part. ++ // ++ uint64_t i = 0; ++ const uint8_t *p = src; ++ if(p == src_end) { return NUMBER_ERROR; } ++ p += parse_digit(*p, i); ++ bool leading_zero = (i == 0); ++ while ((p != src_end) && parse_digit(*p, i)) { p++; } ++ // no integer digits, or 0123 (zero must be solo) ++ if ( p == src ) { return INCORRECT_TYPE; } ++ if ( (leading_zero && p != src+1)) { return NUMBER_ERROR; } ++ ++ // ++ // Parse the decimal part. ++ // ++ int64_t exponent = 0; ++ bool overflow; ++ if (simdjson_likely((p != src_end) && (*p == '.'))) { ++ p++; ++ const uint8_t *start_decimal_digits = p; ++ if ((p == src_end) || !parse_digit(*p, i)) { return NUMBER_ERROR; } // no decimal digits ++ p++; ++ while ((p != src_end) && parse_digit(*p, i)) { p++; } ++ exponent = -(p - start_decimal_digits); ++ ++ // Overflow check. More than 19 digits (minus the decimal) may be overflow. ++ overflow = p-src-1 > 19; ++ if (simdjson_unlikely(overflow && leading_zero)) { ++ // Skip leading 0.00000 and see if it still overflows ++ const uint8_t *start_digits = src + 2; ++ while (*start_digits == '0') { start_digits++; } ++ overflow = start_digits-src > 19; ++ } ++ } else { ++ overflow = p-src > 19; ++ } ++ ++ // ++ // Parse the exponent ++ // ++ if ((p != src_end) && (*p == 'e' || *p == 'E')) { ++ p++; ++ if(p == src_end) { return NUMBER_ERROR; } ++ bool exp_neg = *p == '-'; ++ p += exp_neg || *p == '+'; ++ ++ uint64_t exp = 0; ++ const uint8_t *start_exp_digits = p; ++ while ((p != src_end) && parse_digit(*p, exp)) { p++; } ++ // no exp digits, or 20+ exp digits ++ if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } ++ ++ exponent += exp_neg ? 0-exp : exp; ++ } ++ ++ if ((p != src_end) && jsoncharutils::is_not_structural_or_whitespace(*p)) { return NUMBER_ERROR; } ++ ++ overflow = overflow || exponent < simdjson::internal::smallest_power || exponent > simdjson::internal::largest_power; ++ ++ // ++ // Assemble (or slow-parse) the float ++ // ++ double d; ++ if (simdjson_likely(!overflow)) { ++ if (compute_float_64(exponent, i, negative, d)) { return d; } ++ } ++ if (!parse_float_fallback(src - uint8_t(negative), src_end, &d)) { ++ return NUMBER_ERROR; ++ } ++ return d; ++} ++ ++simdjson_unused simdjson_inline simdjson_result parse_double_in_string(const uint8_t * src) noexcept { ++ // ++ // Check for minus sign ++ // ++ bool negative = (*(src + 1) == '-'); ++ src += uint8_t(negative) + 1; ++ ++ // ++ // Parse the integer part. ++ // ++ uint64_t i = 0; ++ const uint8_t *p = src; ++ p += parse_digit(*p, i); ++ bool leading_zero = (i == 0); ++ while (parse_digit(*p, i)) { p++; } ++ // no integer digits, or 0123 (zero must be solo) ++ if ( p == src ) { return INCORRECT_TYPE; } ++ if ( (leading_zero && p != src+1)) { return NUMBER_ERROR; } ++ ++ // ++ // Parse the decimal part. ++ // ++ int64_t exponent = 0; ++ bool overflow; ++ if (simdjson_likely(*p == '.')) { ++ p++; ++ const uint8_t *start_decimal_digits = p; ++ if (!parse_digit(*p, i)) { return NUMBER_ERROR; } // no decimal digits ++ p++; ++ while (parse_digit(*p, i)) { p++; } ++ exponent = -(p - start_decimal_digits); ++ ++ // Overflow check. More than 19 digits (minus the decimal) may be overflow. ++ overflow = p-src-1 > 19; ++ if (simdjson_unlikely(overflow && leading_zero)) { ++ // Skip leading 0.00000 and see if it still overflows ++ const uint8_t *start_digits = src + 2; ++ while (*start_digits == '0') { start_digits++; } ++ overflow = start_digits-src > 19; ++ } ++ } else { ++ overflow = p-src > 19; ++ } ++ ++ // ++ // Parse the exponent ++ // ++ if (*p == 'e' || *p == 'E') { ++ p++; ++ bool exp_neg = *p == '-'; ++ p += exp_neg || *p == '+'; ++ ++ uint64_t exp = 0; ++ const uint8_t *start_exp_digits = p; ++ while (parse_digit(*p, exp)) { p++; } ++ // no exp digits, or 20+ exp digits ++ if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } ++ ++ exponent += exp_neg ? 0-exp : exp; ++ } ++ ++ if (*p != '"') { return NUMBER_ERROR; } ++ ++ overflow = overflow || exponent < simdjson::internal::smallest_power || exponent > simdjson::internal::largest_power; ++ ++ // ++ // Assemble (or slow-parse) the float ++ // ++ double d; ++ if (simdjson_likely(!overflow)) { ++ if (compute_float_64(exponent, i, negative, d)) { return d; } ++ } ++ if (!parse_float_fallback(src - uint8_t(negative), &d)) { ++ return NUMBER_ERROR; ++ } ++ return d; ++} ++} //namespace {} ++#endif // SIMDJSON_SKIPNUMBERPARSING ++ ++} // namespace numberparsing ++} // unnamed namespace ++} // namespace icelake ++} // namespace simdjson ++/* end file include/simdjson/generic/numberparsing.h */ ++ ++#endif // SIMDJSON_ICELAKE_NUMBERPARSING_H ++/* end file include/simdjson/icelake/numberparsing.h */ ++/* begin file include/simdjson/icelake/end.h */ ++SIMDJSON_UNTARGET_ICELAKE ++/* end file include/simdjson/icelake/end.h */ ++ ++#endif // SIMDJSON_IMPLEMENTATION_ICELAKE ++#endif // SIMDJSON_ICELAKE_H ++/* end file include/simdjson/icelake.h */ ++/* begin file include/simdjson/haswell.h */ ++#ifndef SIMDJSON_HASWELL_H ++#define SIMDJSON_HASWELL_H ++ ++ ++#if SIMDJSON_IMPLEMENTATION_HASWELL ++ ++#if SIMDJSON_CAN_ALWAYS_RUN_HASWELL ++#define SIMDJSON_TARGET_HASWELL ++#define SIMDJSON_UNTARGET_HASWELL ++#else ++#define SIMDJSON_TARGET_HASWELL SIMDJSON_TARGET_REGION("avx2,bmi,pclmul,lzcnt") ++#define SIMDJSON_UNTARGET_HASWELL SIMDJSON_UNTARGET_REGION ++#endif ++ ++namespace simdjson { ++/** ++ * Implementation for Haswell (Intel AVX2). ++ */ ++namespace haswell { ++} // namespace haswell ++} // namespace simdjson ++ ++// ++// These two need to be included outside SIMDJSON_TARGET_HASWELL ++// ++/* begin file include/simdjson/haswell/implementation.h */ ++#ifndef SIMDJSON_HASWELL_IMPLEMENTATION_H ++#define SIMDJSON_HASWELL_IMPLEMENTATION_H ++ ++ ++// The constructor may be executed on any host, so we take care not to use SIMDJSON_TARGET_HASWELL ++namespace simdjson { ++namespace haswell { ++ ++using namespace simdjson; ++ ++class implementation final : public simdjson::implementation { ++public: ++ simdjson_inline implementation() : simdjson::implementation( ++ "haswell", ++ "Intel/AMD AVX2", ++ internal::instruction_set::AVX2 | internal::instruction_set::PCLMULQDQ | internal::instruction_set::BMI1 | internal::instruction_set::BMI2 ++ ) {} ++ simdjson_warn_unused error_code create_dom_parser_implementation( ++ size_t capacity, ++ size_t max_length, ++ std::unique_ptr& dst ++ ) const noexcept final; ++ simdjson_warn_unused error_code minify(const uint8_t *buf, size_t len, uint8_t *dst, size_t &dst_len) const noexcept final; ++ simdjson_warn_unused bool validate_utf8(const char *buf, size_t len) const noexcept final; ++}; ++ ++} // namespace haswell ++} // namespace simdjson ++ ++#endif // SIMDJSON_HASWELL_IMPLEMENTATION_H ++/* end file include/simdjson/haswell/implementation.h */ ++/* begin file include/simdjson/haswell/intrinsics.h */ ++#ifndef SIMDJSON_HASWELL_INTRINSICS_H ++#define SIMDJSON_HASWELL_INTRINSICS_H ++ ++ ++#if SIMDJSON_VISUAL_STUDIO ++// under clang within visual studio, this will include ++#include // visual studio or clang ++#else ++#include // elsewhere ++#endif // SIMDJSON_VISUAL_STUDIO ++ ++#if SIMDJSON_CLANG_VISUAL_STUDIO ++/** ++ * You are not supposed, normally, to include these ++ * headers directly. Instead you should either include intrin.h ++ * or x86intrin.h. However, when compiling with clang ++ * under Windows (i.e., when _MSC_VER is set), these headers ++ * only get included *if* the corresponding features are detected ++ * from macros: ++ * e.g., if __AVX2__ is set... in turn, we normally set these ++ * macros by compiling against the corresponding architecture ++ * (e.g., arch:AVX2, -mavx2, etc.) which compiles the whole ++ * software with these advanced instructions. In simdjson, we ++ * want to compile the whole program for a generic target, ++ * and only target our specific kernels. As a workaround, ++ * we directly include the needed headers. These headers would ++ * normally guard against such usage, but we carefully included ++ * (or ) before, so the headers ++ * are fooled. ++ */ ++#include // for _blsr_u64 ++#include // for __lzcnt64 ++#include // for most things (AVX2, AVX512, _popcnt64) ++#include ++#include ++#include ++#include ++#include // for _mm_clmulepi64_si128 ++// unfortunately, we may not get _blsr_u64, but, thankfully, clang ++// has it as a macro. ++#ifndef _blsr_u64 ++// we roll our own ++#define _blsr_u64(n) ((n - 1) & n) ++#endif // _blsr_u64 ++#endif // SIMDJSON_CLANG_VISUAL_STUDIO ++ ++static_assert(sizeof(__m256i) <= simdjson::SIMDJSON_PADDING, "insufficient padding for haswell kernel."); ++ ++#endif // SIMDJSON_HASWELL_INTRINSICS_H ++/* end file include/simdjson/haswell/intrinsics.h */ ++ ++// ++// The rest need to be inside the region ++// ++/* begin file include/simdjson/haswell/begin.h */ ++// redefining SIMDJSON_IMPLEMENTATION to "haswell" ++// #define SIMDJSON_IMPLEMENTATION haswell ++SIMDJSON_TARGET_HASWELL ++/* end file include/simdjson/haswell/begin.h */ ++ ++// Declarations ++/* begin file include/simdjson/generic/dom_parser_implementation.h */ ++ ++namespace simdjson { ++namespace haswell { ++ ++// expectation: sizeof(open_container) = 64/8. ++struct open_container { ++ uint32_t tape_index; // where, on the tape, does the scope ([,{) begins ++ uint32_t count; // how many elements in the scope ++}; // struct open_container ++ ++static_assert(sizeof(open_container) == 64/8, "Open container must be 64 bits"); ++ ++class dom_parser_implementation final : public internal::dom_parser_implementation { ++public: ++ /** Tape location of each open { or [ */ ++ std::unique_ptr open_containers{}; ++ /** Whether each open container is a [ or { */ ++ std::unique_ptr is_array{}; ++ /** Buffer passed to stage 1 */ ++ const uint8_t *buf{}; ++ /** Length passed to stage 1 */ ++ size_t len{0}; ++ /** Document passed to stage 2 */ ++ dom::document *doc{}; ++ ++ inline dom_parser_implementation() noexcept; ++ inline dom_parser_implementation(dom_parser_implementation &&other) noexcept; ++ inline dom_parser_implementation &operator=(dom_parser_implementation &&other) noexcept; ++ dom_parser_implementation(const dom_parser_implementation &) = delete; ++ dom_parser_implementation &operator=(const dom_parser_implementation &) = delete; ++ ++ simdjson_warn_unused error_code parse(const uint8_t *buf, size_t len, dom::document &doc) noexcept final; ++ simdjson_warn_unused error_code stage1(const uint8_t *buf, size_t len, stage1_mode partial) noexcept final; ++ simdjson_warn_unused error_code stage2(dom::document &doc) noexcept final; ++ simdjson_warn_unused error_code stage2_next(dom::document &doc) noexcept final; ++ simdjson_warn_unused uint8_t *parse_string(const uint8_t *src, uint8_t *dst) const noexcept final; ++ inline simdjson_warn_unused error_code set_capacity(size_t capacity) noexcept final; ++ inline simdjson_warn_unused error_code set_max_depth(size_t max_depth) noexcept final; ++private: ++ simdjson_inline simdjson_warn_unused error_code set_capacity_stage1(size_t capacity); ++ ++}; ++ ++} // namespace haswell ++} // namespace simdjson ++ ++namespace simdjson { ++namespace haswell { ++ ++inline dom_parser_implementation::dom_parser_implementation() noexcept = default; ++inline dom_parser_implementation::dom_parser_implementation(dom_parser_implementation &&other) noexcept = default; ++inline dom_parser_implementation &dom_parser_implementation::operator=(dom_parser_implementation &&other) noexcept = default; ++ ++// Leaving these here so they can be inlined if so desired ++inline simdjson_warn_unused error_code dom_parser_implementation::set_capacity(size_t capacity) noexcept { ++ if(capacity > SIMDJSON_MAXSIZE_BYTES) { return CAPACITY; } ++ // Stage 1 index output ++ size_t max_structures = SIMDJSON_ROUNDUP_N(capacity, 64) + 2 + 7; ++ structural_indexes.reset( new (std::nothrow) uint32_t[max_structures] ); ++ if (!structural_indexes) { _capacity = 0; return MEMALLOC; } ++ structural_indexes[0] = 0; ++ n_structural_indexes = 0; ++ ++ _capacity = capacity; ++ return SUCCESS; ++} ++ ++inline simdjson_warn_unused error_code dom_parser_implementation::set_max_depth(size_t max_depth) noexcept { ++ // Stage 2 stacks ++ open_containers.reset(new (std::nothrow) open_container[max_depth]); ++ is_array.reset(new (std::nothrow) bool[max_depth]); ++ if (!is_array || !open_containers) { _max_depth = 0; return MEMALLOC; } ++ ++ _max_depth = max_depth; ++ return SUCCESS; ++} ++ ++} // namespace haswell ++} // namespace simdjson ++/* end file include/simdjson/generic/dom_parser_implementation.h */ ++/* begin file include/simdjson/haswell/bitmanipulation.h */ ++#ifndef SIMDJSON_HASWELL_BITMANIPULATION_H ++#define SIMDJSON_HASWELL_BITMANIPULATION_H ++ ++namespace simdjson { ++namespace haswell { ++namespace { ++ ++// We sometimes call trailing_zero on inputs that are zero, ++// but the algorithms do not end up using the returned value. ++// Sadly, sanitizers are not smart enough to figure it out. ++SIMDJSON_NO_SANITIZE_UNDEFINED ++simdjson_inline int trailing_zeroes(uint64_t input_num) { ++#if SIMDJSON_REGULAR_VISUAL_STUDIO ++ return (int)_tzcnt_u64(input_num); ++#else // SIMDJSON_REGULAR_VISUAL_STUDIO ++ //////// ++ // You might expect the next line to be equivalent to ++ // return (int)_tzcnt_u64(input_num); ++ // but the generated code differs and might be less efficient? ++ //////// ++ return __builtin_ctzll(input_num); ++#endif // SIMDJSON_REGULAR_VISUAL_STUDIO ++} ++ ++/* result might be undefined when input_num is zero */ ++simdjson_inline uint64_t clear_lowest_bit(uint64_t input_num) { ++ return _blsr_u64(input_num); ++} ++ ++/* result might be undefined when input_num is zero */ ++simdjson_inline int leading_zeroes(uint64_t input_num) { ++ return int(_lzcnt_u64(input_num)); ++} ++ ++#if SIMDJSON_REGULAR_VISUAL_STUDIO ++simdjson_inline unsigned __int64 count_ones(uint64_t input_num) { ++ // note: we do not support legacy 32-bit Windows ++ return __popcnt64(input_num);// Visual Studio wants two underscores ++} ++#else ++simdjson_inline long long int count_ones(uint64_t input_num) { ++ return _popcnt64(input_num); ++} ++#endif ++ ++simdjson_inline bool add_overflow(uint64_t value1, uint64_t value2, ++ uint64_t *result) { ++#if SIMDJSON_REGULAR_VISUAL_STUDIO ++ return _addcarry_u64(0, value1, value2, ++ reinterpret_cast(result)); ++#else ++ return __builtin_uaddll_overflow(value1, value2, ++ reinterpret_cast(result)); ++#endif ++} ++ ++} // unnamed namespace ++} // namespace haswell ++} // namespace simdjson ++ ++#endif // SIMDJSON_HASWELL_BITMANIPULATION_H ++/* end file include/simdjson/haswell/bitmanipulation.h */ ++/* begin file include/simdjson/haswell/bitmask.h */ ++#ifndef SIMDJSON_HASWELL_BITMASK_H ++#define SIMDJSON_HASWELL_BITMASK_H ++ ++namespace simdjson { ++namespace haswell { ++namespace { ++ ++// ++// Perform a "cumulative bitwise xor," flipping bits each time a 1 is encountered. ++// ++// For example, prefix_xor(00100100) == 00011100 ++// ++simdjson_inline uint64_t prefix_xor(const uint64_t bitmask) { ++ // There should be no such thing with a processor supporting avx2 ++ // but not clmul. ++ __m128i all_ones = _mm_set1_epi8('\xFF'); ++ __m128i result = _mm_clmulepi64_si128(_mm_set_epi64x(0ULL, bitmask), all_ones, 0); ++ return _mm_cvtsi128_si64(result); ++} ++ ++} // unnamed namespace ++} // namespace haswell ++} // namespace simdjson ++ ++#endif // SIMDJSON_HASWELL_BITMASK_H ++/* end file include/simdjson/haswell/bitmask.h */ ++/* begin file include/simdjson/haswell/simd.h */ ++#ifndef SIMDJSON_HASWELL_SIMD_H ++#define SIMDJSON_HASWELL_SIMD_H ++ ++ ++namespace simdjson { ++namespace haswell { ++namespace { ++namespace simd { ++ ++ // Forward-declared so they can be used by splat and friends. ++ template ++ struct base { ++ __m256i value; ++ ++ // Zero constructor ++ simdjson_inline base() : value{__m256i()} {} ++ ++ // Conversion from SIMD register ++ simdjson_inline base(const __m256i _value) : value(_value) {} ++ ++ // Conversion to SIMD register ++ simdjson_inline operator const __m256i&() const { return this->value; } ++ simdjson_inline operator __m256i&() { return this->value; } ++ ++ // Bit operations ++ simdjson_inline Child operator|(const Child other) const { return _mm256_or_si256(*this, other); } ++ simdjson_inline Child operator&(const Child other) const { return _mm256_and_si256(*this, other); } ++ simdjson_inline Child operator^(const Child other) const { return _mm256_xor_si256(*this, other); } ++ simdjson_inline Child bit_andnot(const Child other) const { return _mm256_andnot_si256(other, *this); } ++ simdjson_inline Child& operator|=(const Child other) { auto this_cast = static_cast(this); *this_cast = *this_cast | other; return *this_cast; } ++ simdjson_inline Child& operator&=(const Child other) { auto this_cast = static_cast(this); *this_cast = *this_cast & other; return *this_cast; } ++ simdjson_inline Child& operator^=(const Child other) { auto this_cast = static_cast(this); *this_cast = *this_cast ^ other; return *this_cast; } ++ }; ++ ++ // Forward-declared so they can be used by splat and friends. ++ template ++ struct simd8; ++ ++ template> ++ struct base8: base> { ++ typedef uint32_t bitmask_t; ++ typedef uint64_t bitmask2_t; ++ ++ simdjson_inline base8() : base>() {} ++ simdjson_inline base8(const __m256i _value) : base>(_value) {} ++ ++ friend simdjson_really_inline Mask operator==(const simd8 lhs, const simd8 rhs) { return _mm256_cmpeq_epi8(lhs, rhs); } ++ ++ static const int SIZE = sizeof(base::value); ++ ++ template ++ simdjson_inline simd8 prev(const simd8 prev_chunk) const { ++ return _mm256_alignr_epi8(*this, _mm256_permute2x128_si256(prev_chunk, *this, 0x21), 16 - N); ++ } ++ }; ++ ++ // SIMD byte mask type (returned by things like eq and gt) ++ template<> ++ struct simd8: base8 { ++ static simdjson_inline simd8 splat(bool _value) { return _mm256_set1_epi8(uint8_t(-(!!_value))); } ++ ++ simdjson_inline simd8() : base8() {} ++ simdjson_inline simd8(const __m256i _value) : base8(_value) {} ++ // Splat constructor ++ simdjson_inline simd8(bool _value) : base8(splat(_value)) {} ++ ++ simdjson_inline int to_bitmask() const { return _mm256_movemask_epi8(*this); } ++ simdjson_inline bool any() const { return !_mm256_testz_si256(*this, *this); } ++ simdjson_inline simd8 operator~() const { return *this ^ true; } ++ }; ++ ++ template ++ struct base8_numeric: base8 { ++ static simdjson_inline simd8 splat(T _value) { return _mm256_set1_epi8(_value); } ++ static simdjson_inline simd8 zero() { return _mm256_setzero_si256(); } ++ static simdjson_inline simd8 load(const T values[32]) { ++ return _mm256_loadu_si256(reinterpret_cast(values)); ++ } ++ // Repeat 16 values as many times as necessary (usually for lookup tables) ++ static simdjson_inline simd8 repeat_16( ++ T v0, T v1, T v2, T v3, T v4, T v5, T v6, T v7, ++ T v8, T v9, T v10, T v11, T v12, T v13, T v14, T v15 ++ ) { ++ return simd8( ++ v0, v1, v2, v3, v4, v5, v6, v7, ++ v8, v9, v10,v11,v12,v13,v14,v15, ++ v0, v1, v2, v3, v4, v5, v6, v7, ++ v8, v9, v10,v11,v12,v13,v14,v15 ++ ); ++ } ++ ++ simdjson_inline base8_numeric() : base8() {} ++ simdjson_inline base8_numeric(const __m256i _value) : base8(_value) {} ++ ++ // Store to array ++ simdjson_inline void store(T dst[32]) const { return _mm256_storeu_si256(reinterpret_cast<__m256i *>(dst), *this); } ++ ++ // Addition/subtraction are the same for signed and unsigned ++ simdjson_inline simd8 operator+(const simd8 other) const { return _mm256_add_epi8(*this, other); } ++ simdjson_inline simd8 operator-(const simd8 other) const { return _mm256_sub_epi8(*this, other); } ++ simdjson_inline simd8& operator+=(const simd8 other) { *this = *this + other; return *static_cast*>(this); } ++ simdjson_inline simd8& operator-=(const simd8 other) { *this = *this - other; return *static_cast*>(this); } ++ ++ // Override to distinguish from bool version ++ simdjson_inline simd8 operator~() const { return *this ^ 0xFFu; } ++ ++ // Perform a lookup assuming the value is between 0 and 16 (undefined behavior for out of range values) ++ template ++ simdjson_inline simd8 lookup_16(simd8 lookup_table) const { ++ return _mm256_shuffle_epi8(lookup_table, *this); ++ } ++ ++ // Copies to 'output" all bytes corresponding to a 0 in the mask (interpreted as a bitset). ++ // Passing a 0 value for mask would be equivalent to writing out every byte to output. ++ // Only the first 32 - count_ones(mask) bytes of the result are significant but 32 bytes ++ // get written. ++ // Design consideration: it seems like a function with the ++ // signature simd8 compress(uint32_t mask) would be ++ // sensible, but the AVX ISA makes this kind of approach difficult. ++ template ++ simdjson_inline void compress(uint32_t mask, L * output) const { ++ using internal::thintable_epi8; ++ using internal::BitsSetTable256mul2; ++ using internal::pshufb_combine_table; ++ // this particular implementation was inspired by work done by @animetosho ++ // we do it in four steps, first 8 bytes and then second 8 bytes... ++ uint8_t mask1 = uint8_t(mask); // least significant 8 bits ++ uint8_t mask2 = uint8_t(mask >> 8); // second least significant 8 bits ++ uint8_t mask3 = uint8_t(mask >> 16); // ... ++ uint8_t mask4 = uint8_t(mask >> 24); // ... ++ // next line just loads the 64-bit values thintable_epi8[mask1] and ++ // thintable_epi8[mask2] into a 128-bit register, using only ++ // two instructions on most compilers. ++ __m256i shufmask = _mm256_set_epi64x(thintable_epi8[mask4], thintable_epi8[mask3], ++ thintable_epi8[mask2], thintable_epi8[mask1]); ++ // we increment by 0x08 the second half of the mask and so forth ++ shufmask = ++ _mm256_add_epi8(shufmask, _mm256_set_epi32(0x18181818, 0x18181818, ++ 0x10101010, 0x10101010, 0x08080808, 0x08080808, 0, 0)); ++ // this is the version "nearly pruned" ++ __m256i pruned = _mm256_shuffle_epi8(*this, shufmask); ++ // we still need to put the pieces back together. ++ // we compute the popcount of the first words: ++ int pop1 = BitsSetTable256mul2[mask1]; ++ int pop3 = BitsSetTable256mul2[mask3]; ++ ++ // then load the corresponding mask ++ // could be done with _mm256_loadu2_m128i but many standard libraries omit this intrinsic. ++ __m256i v256 = _mm256_castsi128_si256( ++ _mm_loadu_si128(reinterpret_cast(pshufb_combine_table + pop1 * 8))); ++ __m256i compactmask = _mm256_insertf128_si256(v256, ++ _mm_loadu_si128(reinterpret_cast(pshufb_combine_table + pop3 * 8)), 1); ++ __m256i almostthere = _mm256_shuffle_epi8(pruned, compactmask); ++ // We just need to write out the result. ++ // This is the tricky bit that is hard to do ++ // if we want to return a SIMD register, since there ++ // is no single-instruction approach to recombine ++ // the two 128-bit lanes with an offset. ++ __m128i v128; ++ v128 = _mm256_castsi256_si128(almostthere); ++ _mm_storeu_si128( reinterpret_cast<__m128i *>(output), v128); ++ v128 = _mm256_extractf128_si256(almostthere, 1); ++ _mm_storeu_si128( reinterpret_cast<__m128i *>(output + 16 - count_ones(mask & 0xFFFF)), v128); ++ } ++ ++ template ++ simdjson_inline simd8 lookup_16( ++ L replace0, L replace1, L replace2, L replace3, ++ L replace4, L replace5, L replace6, L replace7, ++ L replace8, L replace9, L replace10, L replace11, ++ L replace12, L replace13, L replace14, L replace15) const { ++ return lookup_16(simd8::repeat_16( ++ replace0, replace1, replace2, replace3, ++ replace4, replace5, replace6, replace7, ++ replace8, replace9, replace10, replace11, ++ replace12, replace13, replace14, replace15 ++ )); ++ } ++ }; ++ ++ // Signed bytes ++ template<> ++ struct simd8 : base8_numeric { ++ simdjson_inline simd8() : base8_numeric() {} ++ simdjson_inline simd8(const __m256i _value) : base8_numeric(_value) {} ++ // Splat constructor ++ simdjson_inline simd8(int8_t _value) : simd8(splat(_value)) {} ++ // Array constructor ++ simdjson_inline simd8(const int8_t values[32]) : simd8(load(values)) {} ++ // Member-by-member initialization ++ simdjson_inline simd8( ++ int8_t v0, int8_t v1, int8_t v2, int8_t v3, int8_t v4, int8_t v5, int8_t v6, int8_t v7, ++ int8_t v8, int8_t v9, int8_t v10, int8_t v11, int8_t v12, int8_t v13, int8_t v14, int8_t v15, ++ int8_t v16, int8_t v17, int8_t v18, int8_t v19, int8_t v20, int8_t v21, int8_t v22, int8_t v23, ++ int8_t v24, int8_t v25, int8_t v26, int8_t v27, int8_t v28, int8_t v29, int8_t v30, int8_t v31 ++ ) : simd8(_mm256_setr_epi8( ++ v0, v1, v2, v3, v4, v5, v6, v7, ++ v8, v9, v10,v11,v12,v13,v14,v15, ++ v16,v17,v18,v19,v20,v21,v22,v23, ++ v24,v25,v26,v27,v28,v29,v30,v31 ++ )) {} ++ // Repeat 16 values as many times as necessary (usually for lookup tables) ++ simdjson_inline static simd8 repeat_16( ++ int8_t v0, int8_t v1, int8_t v2, int8_t v3, int8_t v4, int8_t v5, int8_t v6, int8_t v7, ++ int8_t v8, int8_t v9, int8_t v10, int8_t v11, int8_t v12, int8_t v13, int8_t v14, int8_t v15 ++ ) { ++ return simd8( ++ v0, v1, v2, v3, v4, v5, v6, v7, ++ v8, v9, v10,v11,v12,v13,v14,v15, ++ v0, v1, v2, v3, v4, v5, v6, v7, ++ v8, v9, v10,v11,v12,v13,v14,v15 ++ ); ++ } ++ ++ // Order-sensitive comparisons ++ simdjson_inline simd8 max_val(const simd8 other) const { return _mm256_max_epi8(*this, other); } ++ simdjson_inline simd8 min_val(const simd8 other) const { return _mm256_min_epi8(*this, other); } ++ simdjson_inline simd8 operator>(const simd8 other) const { return _mm256_cmpgt_epi8(*this, other); } ++ simdjson_inline simd8 operator<(const simd8 other) const { return _mm256_cmpgt_epi8(other, *this); } ++ }; ++ ++ // Unsigned bytes ++ template<> ++ struct simd8: base8_numeric { ++ simdjson_inline simd8() : base8_numeric() {} ++ simdjson_inline simd8(const __m256i _value) : base8_numeric(_value) {} ++ // Splat constructor ++ simdjson_inline simd8(uint8_t _value) : simd8(splat(_value)) {} ++ // Array constructor ++ simdjson_inline simd8(const uint8_t values[32]) : simd8(load(values)) {} ++ // Member-by-member initialization ++ simdjson_inline simd8( ++ uint8_t v0, uint8_t v1, uint8_t v2, uint8_t v3, uint8_t v4, uint8_t v5, uint8_t v6, uint8_t v7, ++ uint8_t v8, uint8_t v9, uint8_t v10, uint8_t v11, uint8_t v12, uint8_t v13, uint8_t v14, uint8_t v15, ++ uint8_t v16, uint8_t v17, uint8_t v18, uint8_t v19, uint8_t v20, uint8_t v21, uint8_t v22, uint8_t v23, ++ uint8_t v24, uint8_t v25, uint8_t v26, uint8_t v27, uint8_t v28, uint8_t v29, uint8_t v30, uint8_t v31 ++ ) : simd8(_mm256_setr_epi8( ++ v0, v1, v2, v3, v4, v5, v6, v7, ++ v8, v9, v10,v11,v12,v13,v14,v15, ++ v16,v17,v18,v19,v20,v21,v22,v23, ++ v24,v25,v26,v27,v28,v29,v30,v31 ++ )) {} ++ // Repeat 16 values as many times as necessary (usually for lookup tables) ++ simdjson_inline static simd8 repeat_16( ++ uint8_t v0, uint8_t v1, uint8_t v2, uint8_t v3, uint8_t v4, uint8_t v5, uint8_t v6, uint8_t v7, ++ uint8_t v8, uint8_t v9, uint8_t v10, uint8_t v11, uint8_t v12, uint8_t v13, uint8_t v14, uint8_t v15 ++ ) { ++ return simd8( ++ v0, v1, v2, v3, v4, v5, v6, v7, ++ v8, v9, v10,v11,v12,v13,v14,v15, ++ v0, v1, v2, v3, v4, v5, v6, v7, ++ v8, v9, v10,v11,v12,v13,v14,v15 ++ ); ++ } ++ ++ // Saturated math ++ simdjson_inline simd8 saturating_add(const simd8 other) const { return _mm256_adds_epu8(*this, other); } ++ simdjson_inline simd8 saturating_sub(const simd8 other) const { return _mm256_subs_epu8(*this, other); } ++ ++ // Order-specific operations ++ simdjson_inline simd8 max_val(const simd8 other) const { return _mm256_max_epu8(*this, other); } ++ simdjson_inline simd8 min_val(const simd8 other) const { return _mm256_min_epu8(other, *this); } ++ // Same as >, but only guarantees true is nonzero (< guarantees true = -1) ++ simdjson_inline simd8 gt_bits(const simd8 other) const { return this->saturating_sub(other); } ++ // Same as <, but only guarantees true is nonzero (< guarantees true = -1) ++ simdjson_inline simd8 lt_bits(const simd8 other) const { return other.saturating_sub(*this); } ++ simdjson_inline simd8 operator<=(const simd8 other) const { return other.max_val(*this) == other; } ++ simdjson_inline simd8 operator>=(const simd8 other) const { return other.min_val(*this) == other; } ++ simdjson_inline simd8 operator>(const simd8 other) const { return this->gt_bits(other).any_bits_set(); } ++ simdjson_inline simd8 operator<(const simd8 other) const { return this->lt_bits(other).any_bits_set(); } ++ ++ // Bit-specific operations ++ simdjson_inline simd8 bits_not_set() const { return *this == uint8_t(0); } ++ simdjson_inline simd8 bits_not_set(simd8 bits) const { return (*this & bits).bits_not_set(); } ++ simdjson_inline simd8 any_bits_set() const { return ~this->bits_not_set(); } ++ simdjson_inline simd8 any_bits_set(simd8 bits) const { return ~this->bits_not_set(bits); } ++ simdjson_inline bool is_ascii() const { return _mm256_movemask_epi8(*this) == 0; } ++ simdjson_inline bool bits_not_set_anywhere() const { return _mm256_testz_si256(*this, *this); } ++ simdjson_inline bool any_bits_set_anywhere() const { return !bits_not_set_anywhere(); } ++ simdjson_inline bool bits_not_set_anywhere(simd8 bits) const { return _mm256_testz_si256(*this, bits); } ++ simdjson_inline bool any_bits_set_anywhere(simd8 bits) const { return !bits_not_set_anywhere(bits); } ++ template ++ simdjson_inline simd8 shr() const { return simd8(_mm256_srli_epi16(*this, N)) & uint8_t(0xFFu >> N); } ++ template ++ simdjson_inline simd8 shl() const { return simd8(_mm256_slli_epi16(*this, N)) & uint8_t(0xFFu << N); } ++ // Get one of the bits and make a bitmask out of it. ++ // e.g. value.get_bit<7>() gets the high bit ++ template ++ simdjson_inline int get_bit() const { return _mm256_movemask_epi8(_mm256_slli_epi16(*this, 7-N)); } ++ }; ++ ++ template ++ struct simd8x64 { ++ static constexpr int NUM_CHUNKS = 64 / sizeof(simd8); ++ static_assert(NUM_CHUNKS == 2, "Haswell kernel should use two registers per 64-byte block."); ++ const simd8 chunks[NUM_CHUNKS]; ++ ++ simd8x64(const simd8x64& o) = delete; // no copy allowed ++ simd8x64& operator=(const simd8& other) = delete; // no assignment allowed ++ simd8x64() = delete; // no default constructor allowed ++ ++ simdjson_inline simd8x64(const simd8 chunk0, const simd8 chunk1) : chunks{chunk0, chunk1} {} ++ simdjson_inline simd8x64(const T ptr[64]) : chunks{simd8::load(ptr), simd8::load(ptr+32)} {} ++ ++ simdjson_inline uint64_t compress(uint64_t mask, T * output) const { ++ uint32_t mask1 = uint32_t(mask); ++ uint32_t mask2 = uint32_t(mask >> 32); ++ this->chunks[0].compress(mask1, output); ++ this->chunks[1].compress(mask2, output + 32 - count_ones(mask1)); ++ return 64 - count_ones(mask); ++ } ++ ++ simdjson_inline void store(T ptr[64]) const { ++ this->chunks[0].store(ptr+sizeof(simd8)*0); ++ this->chunks[1].store(ptr+sizeof(simd8)*1); ++ } ++ ++ simdjson_inline uint64_t to_bitmask() const { ++ uint64_t r_lo = uint32_t(this->chunks[0].to_bitmask()); ++ uint64_t r_hi = this->chunks[1].to_bitmask(); ++ return r_lo | (r_hi << 32); ++ } ++ ++ simdjson_inline simd8 reduce_or() const { ++ return this->chunks[0] | this->chunks[1]; ++ } ++ ++ simdjson_inline simd8x64 bit_or(const T m) const { ++ const simd8 mask = simd8::splat(m); ++ return simd8x64( ++ this->chunks[0] | mask, ++ this->chunks[1] | mask ++ ); ++ } ++ ++ simdjson_inline uint64_t eq(const T m) const { ++ const simd8 mask = simd8::splat(m); ++ return simd8x64( ++ this->chunks[0] == mask, ++ this->chunks[1] == mask ++ ).to_bitmask(); ++ } ++ ++ simdjson_inline uint64_t eq(const simd8x64 &other) const { ++ return simd8x64( ++ this->chunks[0] == other.chunks[0], ++ this->chunks[1] == other.chunks[1] ++ ).to_bitmask(); ++ } ++ ++ simdjson_inline uint64_t lteq(const T m) const { ++ const simd8 mask = simd8::splat(m); ++ return simd8x64( ++ this->chunks[0] <= mask, ++ this->chunks[1] <= mask ++ ).to_bitmask(); ++ } ++ }; // struct simd8x64 ++ ++} // namespace simd ++ ++} // unnamed namespace ++} // namespace haswell ++} // namespace simdjson ++ ++#endif // SIMDJSON_HASWELL_SIMD_H ++/* end file include/simdjson/haswell/simd.h */ ++/* begin file include/simdjson/generic/jsoncharutils.h */ ++ ++namespace simdjson { ++namespace haswell { ++namespace { ++namespace jsoncharutils { ++ ++// return non-zero if not a structural or whitespace char ++// zero otherwise ++simdjson_inline uint32_t is_not_structural_or_whitespace(uint8_t c) { ++ return internal::structural_or_whitespace_negated[c]; ++} ++ ++simdjson_inline uint32_t is_structural_or_whitespace(uint8_t c) { ++ return internal::structural_or_whitespace[c]; ++} ++ ++// returns a value with the high 16 bits set if not valid ++// otherwise returns the conversion of the 4 hex digits at src into the bottom ++// 16 bits of the 32-bit return register ++// ++// see ++// https://lemire.me/blog/2019/04/17/parsing-short-hexadecimal-strings-efficiently/ ++static inline uint32_t hex_to_u32_nocheck( ++ const uint8_t *src) { // strictly speaking, static inline is a C-ism ++ uint32_t v1 = internal::digit_to_val32[630 + src[0]]; ++ uint32_t v2 = internal::digit_to_val32[420 + src[1]]; ++ uint32_t v3 = internal::digit_to_val32[210 + src[2]]; ++ uint32_t v4 = internal::digit_to_val32[0 + src[3]]; ++ return v1 | v2 | v3 | v4; ++} ++ ++// given a code point cp, writes to c ++// the utf-8 code, outputting the length in ++// bytes, if the length is zero, the code point ++// is invalid ++// ++// This can possibly be made faster using pdep ++// and clz and table lookups, but JSON documents ++// have few escaped code points, and the following ++// function looks cheap. ++// ++// Note: we assume that surrogates are treated separately ++// ++simdjson_inline size_t codepoint_to_utf8(uint32_t cp, uint8_t *c) { ++ if (cp <= 0x7F) { ++ c[0] = uint8_t(cp); ++ return 1; // ascii ++ } ++ if (cp <= 0x7FF) { ++ c[0] = uint8_t((cp >> 6) + 192); ++ c[1] = uint8_t((cp & 63) + 128); ++ return 2; // universal plane ++ // Surrogates are treated elsewhere... ++ //} //else if (0xd800 <= cp && cp <= 0xdfff) { ++ // return 0; // surrogates // could put assert here ++ } else if (cp <= 0xFFFF) { ++ c[0] = uint8_t((cp >> 12) + 224); ++ c[1] = uint8_t(((cp >> 6) & 63) + 128); ++ c[2] = uint8_t((cp & 63) + 128); ++ return 3; ++ } else if (cp <= 0x10FFFF) { // if you know you have a valid code point, this ++ // is not needed ++ c[0] = uint8_t((cp >> 18) + 240); ++ c[1] = uint8_t(((cp >> 12) & 63) + 128); ++ c[2] = uint8_t(((cp >> 6) & 63) + 128); ++ c[3] = uint8_t((cp & 63) + 128); ++ return 4; ++ } ++ // will return 0 when the code point was too large. ++ return 0; // bad r ++} ++ ++#if SIMDJSON_IS_32BITS // _umul128 for x86, arm ++// this is a slow emulation routine for 32-bit ++// ++static simdjson_inline uint64_t __emulu(uint32_t x, uint32_t y) { ++ return x * (uint64_t)y; ++} ++static simdjson_inline uint64_t _umul128(uint64_t ab, uint64_t cd, uint64_t *hi) { ++ uint64_t ad = __emulu((uint32_t)(ab >> 32), (uint32_t)cd); ++ uint64_t bd = __emulu((uint32_t)ab, (uint32_t)cd); ++ uint64_t adbc = ad + __emulu((uint32_t)ab, (uint32_t)(cd >> 32)); ++ uint64_t adbc_carry = !!(adbc < ad); ++ uint64_t lo = bd + (adbc << 32); ++ *hi = __emulu((uint32_t)(ab >> 32), (uint32_t)(cd >> 32)) + (adbc >> 32) + ++ (adbc_carry << 32) + !!(lo < bd); ++ return lo; ++} ++#endif ++ ++using internal::value128; ++ ++simdjson_inline value128 full_multiplication(uint64_t value1, uint64_t value2) { ++ value128 answer; ++#if SIMDJSON_REGULAR_VISUAL_STUDIO || SIMDJSON_IS_32BITS ++#ifdef _M_ARM64 ++ // ARM64 has native support for 64-bit multiplications, no need to emultate ++ answer.high = __umulh(value1, value2); ++ answer.low = value1 * value2; ++#else ++ answer.low = _umul128(value1, value2, &answer.high); // _umul128 not available on ARM64 ++#endif // _M_ARM64 ++#else // SIMDJSON_REGULAR_VISUAL_STUDIO || SIMDJSON_IS_32BITS ++ __uint128_t r = (static_cast<__uint128_t>(value1)) * value2; ++ answer.low = uint64_t(r); ++ answer.high = uint64_t(r >> 64); ++#endif ++ return answer; ++} ++ ++} // namespace jsoncharutils ++} // unnamed namespace ++} // namespace haswell ++} // namespace simdjson ++/* end file include/simdjson/generic/jsoncharutils.h */ ++/* begin file include/simdjson/generic/atomparsing.h */ ++namespace simdjson { ++namespace haswell { ++namespace { ++/// @private ++namespace atomparsing { ++ ++// The string_to_uint32 is exclusively used to map literal strings to 32-bit values. ++// We use memcpy instead of a pointer cast to avoid undefined behaviors since we cannot ++// be certain that the character pointer will be properly aligned. ++// You might think that using memcpy makes this function expensive, but you'd be wrong. ++// All decent optimizing compilers (GCC, clang, Visual Studio) will compile string_to_uint32("false"); ++// to the compile-time constant 1936482662. ++simdjson_inline uint32_t string_to_uint32(const char* str) { uint32_t val; std::memcpy(&val, str, sizeof(uint32_t)); return val; } ++ ++ ++// Again in str4ncmp we use a memcpy to avoid undefined behavior. The memcpy may appear expensive. ++// Yet all decent optimizing compilers will compile memcpy to a single instruction, just about. ++simdjson_warn_unused ++simdjson_inline uint32_t str4ncmp(const uint8_t *src, const char* atom) { ++ uint32_t srcval; // we want to avoid unaligned 32-bit loads (undefined in C/C++) ++ static_assert(sizeof(uint32_t) <= SIMDJSON_PADDING, "SIMDJSON_PADDING must be larger than 4 bytes"); ++ std::memcpy(&srcval, src, sizeof(uint32_t)); ++ return srcval ^ string_to_uint32(atom); ++} ++ ++simdjson_warn_unused ++simdjson_inline bool is_valid_true_atom(const uint8_t *src) { ++ return (str4ncmp(src, "true") | jsoncharutils::is_not_structural_or_whitespace(src[4])) == 0; ++} ++ ++simdjson_warn_unused ++simdjson_inline bool is_valid_true_atom(const uint8_t *src, size_t len) { ++ if (len > 4) { return is_valid_true_atom(src); } ++ else if (len == 4) { return !str4ncmp(src, "true"); } ++ else { return false; } ++} ++ ++simdjson_warn_unused ++simdjson_inline bool is_valid_false_atom(const uint8_t *src) { ++ return (str4ncmp(src+1, "alse") | jsoncharutils::is_not_structural_or_whitespace(src[5])) == 0; ++} ++ ++simdjson_warn_unused ++simdjson_inline bool is_valid_false_atom(const uint8_t *src, size_t len) { ++ if (len > 5) { return is_valid_false_atom(src); } ++ else if (len == 5) { return !str4ncmp(src+1, "alse"); } ++ else { return false; } ++} ++ ++simdjson_warn_unused ++simdjson_inline bool is_valid_null_atom(const uint8_t *src) { ++ return (str4ncmp(src, "null") | jsoncharutils::is_not_structural_or_whitespace(src[4])) == 0; ++} ++ ++simdjson_warn_unused ++simdjson_inline bool is_valid_null_atom(const uint8_t *src, size_t len) { ++ if (len > 4) { return is_valid_null_atom(src); } ++ else if (len == 4) { return !str4ncmp(src, "null"); } ++ else { return false; } ++} ++ ++} // namespace atomparsing ++} // unnamed namespace ++} // namespace haswell ++} // namespace simdjson ++/* end file include/simdjson/generic/atomparsing.h */ ++/* begin file include/simdjson/haswell/stringparsing.h */ ++#ifndef SIMDJSON_HASWELL_STRINGPARSING_H ++#define SIMDJSON_HASWELL_STRINGPARSING_H ++ ++ ++namespace simdjson { ++namespace haswell { ++namespace { ++ ++using namespace simd; ++ ++// Holds backslashes and quotes locations. ++struct backslash_and_quote { ++public: ++ static constexpr uint32_t BYTES_PROCESSED = 32; ++ simdjson_inline static backslash_and_quote copy_and_find(const uint8_t *src, uint8_t *dst); ++ ++ simdjson_inline bool has_quote_first() { return ((bs_bits - 1) & quote_bits) != 0; } ++ simdjson_inline bool has_backslash() { return ((quote_bits - 1) & bs_bits) != 0; } ++ simdjson_inline int quote_index() { return trailing_zeroes(quote_bits); } ++ simdjson_inline int backslash_index() { return trailing_zeroes(bs_bits); } ++ ++ uint32_t bs_bits; ++ uint32_t quote_bits; ++}; // struct backslash_and_quote ++ ++simdjson_inline backslash_and_quote backslash_and_quote::copy_and_find(const uint8_t *src, uint8_t *dst) { ++ // this can read up to 15 bytes beyond the buffer size, but we require ++ // SIMDJSON_PADDING of padding ++ static_assert(SIMDJSON_PADDING >= (BYTES_PROCESSED - 1), "backslash and quote finder must process fewer than SIMDJSON_PADDING bytes"); ++ simd8 v(src); ++ // store to dest unconditionally - we can overwrite the bits we don't like later ++ v.store(dst); ++ return { ++ static_cast((v == '\\').to_bitmask()), // bs_bits ++ static_cast((v == '"').to_bitmask()), // quote_bits ++ }; ++} ++ ++} // unnamed namespace ++} // namespace haswell ++} // namespace simdjson ++ ++#endif // SIMDJSON_HASWELL_STRINGPARSING_H ++/* end file include/simdjson/haswell/stringparsing.h */ ++/* begin file include/simdjson/haswell/numberparsing.h */ ++#ifndef SIMDJSON_HASWELL_NUMBERPARSING_H ++#define SIMDJSON_HASWELL_NUMBERPARSING_H ++ ++namespace simdjson { ++namespace haswell { ++namespace { ++ ++static simdjson_inline uint32_t parse_eight_digits_unrolled(const uint8_t *chars) { ++ // this actually computes *16* values so we are being wasteful. ++ const __m128i ascii0 = _mm_set1_epi8('0'); ++ const __m128i mul_1_10 = ++ _mm_setr_epi8(10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1); ++ const __m128i mul_1_100 = _mm_setr_epi16(100, 1, 100, 1, 100, 1, 100, 1); ++ const __m128i mul_1_10000 = ++ _mm_setr_epi16(10000, 1, 10000, 1, 10000, 1, 10000, 1); ++ const __m128i input = _mm_sub_epi8( ++ _mm_loadu_si128(reinterpret_cast(chars)), ascii0); ++ const __m128i t1 = _mm_maddubs_epi16(input, mul_1_10); ++ const __m128i t2 = _mm_madd_epi16(t1, mul_1_100); ++ const __m128i t3 = _mm_packus_epi32(t2, t2); ++ const __m128i t4 = _mm_madd_epi16(t3, mul_1_10000); ++ return _mm_cvtsi128_si32( ++ t4); // only captures the sum of the first 8 digits, drop the rest ++} ++ ++} // unnamed namespace ++} // namespace haswell ++} // namespace simdjson ++ ++#define SIMDJSON_SWAR_NUMBER_PARSING 1 ++ ++/* begin file include/simdjson/generic/numberparsing.h */ ++#include ++ ++namespace simdjson { ++namespace haswell { ++ ++namespace ondemand { ++/** ++ * The type of a JSON number ++ */ ++enum class number_type { ++ floating_point_number=1, /// a binary64 number ++ signed_integer, /// a signed integer that fits in a 64-bit word using two's complement ++ unsigned_integer /// a positive integer larger or equal to 1<<63 ++}; ++} ++ ++namespace { ++/// @private ++namespace numberparsing { ++ ++ ++ ++#ifdef JSON_TEST_NUMBERS ++#define INVALID_NUMBER(SRC) (found_invalid_number((SRC)), NUMBER_ERROR) ++#define WRITE_INTEGER(VALUE, SRC, WRITER) (found_integer((VALUE), (SRC)), (WRITER).append_s64((VALUE))) ++#define WRITE_UNSIGNED(VALUE, SRC, WRITER) (found_unsigned_integer((VALUE), (SRC)), (WRITER).append_u64((VALUE))) ++#define WRITE_DOUBLE(VALUE, SRC, WRITER) (found_float((VALUE), (SRC)), (WRITER).append_double((VALUE))) ++#else ++#define INVALID_NUMBER(SRC) (NUMBER_ERROR) ++#define WRITE_INTEGER(VALUE, SRC, WRITER) (WRITER).append_s64((VALUE)) ++#define WRITE_UNSIGNED(VALUE, SRC, WRITER) (WRITER).append_u64((VALUE)) ++#define WRITE_DOUBLE(VALUE, SRC, WRITER) (WRITER).append_double((VALUE)) ++#endif ++ ++namespace { ++// Convert a mantissa, an exponent and a sign bit into an ieee64 double. ++// The real_exponent needs to be in [0, 2046] (technically real_exponent = 2047 would be acceptable). ++// The mantissa should be in [0,1<<53). The bit at index (1ULL << 52) while be zeroed. ++simdjson_inline double to_double(uint64_t mantissa, uint64_t real_exponent, bool negative) { ++ double d; ++ mantissa &= ~(1ULL << 52); ++ mantissa |= real_exponent << 52; ++ mantissa |= ((static_cast(negative)) << 63); ++ std::memcpy(&d, &mantissa, sizeof(d)); ++ return d; ++} ++} ++// Attempts to compute i * 10^(power) exactly; and if "negative" is ++// true, negate the result. ++// This function will only work in some cases, when it does not work, success is ++// set to false. This should work *most of the time* (like 99% of the time). ++// We assume that power is in the [smallest_power, ++// largest_power] interval: the caller is responsible for this check. ++simdjson_inline bool compute_float_64(int64_t power, uint64_t i, bool negative, double &d) { ++ // we start with a fast path ++ // It was described in ++ // Clinger WD. How to read floating point numbers accurately. ++ // ACM SIGPLAN Notices. 1990 ++#ifndef FLT_EVAL_METHOD ++#error "FLT_EVAL_METHOD should be defined, please include cfloat." ++#endif ++#if (FLT_EVAL_METHOD != 1) && (FLT_EVAL_METHOD != 0) ++ // We cannot be certain that x/y is rounded to nearest. ++ if (0 <= power && power <= 22 && i <= 9007199254740991) { ++#else ++ if (-22 <= power && power <= 22 && i <= 9007199254740991) { ++#endif ++ // convert the integer into a double. This is lossless since ++ // 0 <= i <= 2^53 - 1. ++ d = double(i); ++ // ++ // The general idea is as follows. ++ // If 0 <= s < 2^53 and if 10^0 <= p <= 10^22 then ++ // 1) Both s and p can be represented exactly as 64-bit floating-point ++ // values ++ // (binary64). ++ // 2) Because s and p can be represented exactly as floating-point values, ++ // then s * p ++ // and s / p will produce correctly rounded values. ++ // ++ if (power < 0) { ++ d = d / simdjson::internal::power_of_ten[-power]; ++ } else { ++ d = d * simdjson::internal::power_of_ten[power]; ++ } ++ if (negative) { ++ d = -d; ++ } ++ return true; ++ } ++ // When 22 < power && power < 22 + 16, we could ++ // hope for another, secondary fast path. It was ++ // described by David M. Gay in "Correctly rounded ++ // binary-decimal and decimal-binary conversions." (1990) ++ // If you need to compute i * 10^(22 + x) for x < 16, ++ // first compute i * 10^x, if you know that result is exact ++ // (e.g., when i * 10^x < 2^53), ++ // then you can still proceed and do (i * 10^x) * 10^22. ++ // Is this worth your time? ++ // You need 22 < power *and* power < 22 + 16 *and* (i * 10^(x-22) < 2^53) ++ // for this second fast path to work. ++ // If you you have 22 < power *and* power < 22 + 16, and then you ++ // optimistically compute "i * 10^(x-22)", there is still a chance that you ++ // have wasted your time if i * 10^(x-22) >= 2^53. It makes the use cases of ++ // this optimization maybe less common than we would like. Source: ++ // http://www.exploringbinary.com/fast-path-decimal-to-floating-point-conversion/ ++ // also used in RapidJSON: https://rapidjson.org/strtod_8h_source.html ++ ++ // The fast path has now failed, so we are failing back on the slower path. ++ ++ // In the slow path, we need to adjust i so that it is > 1<<63 which is always ++ // possible, except if i == 0, so we handle i == 0 separately. ++ if(i == 0) { ++ d = negative ? -0.0 : 0.0; ++ return true; ++ } ++ ++ ++ // The exponent is 1024 + 63 + power ++ // + floor(log(5**power)/log(2)). ++ // The 1024 comes from the ieee64 standard. ++ // The 63 comes from the fact that we use a 64-bit word. ++ // ++ // Computing floor(log(5**power)/log(2)) could be ++ // slow. Instead we use a fast function. ++ // ++ // For power in (-400,350), we have that ++ // (((152170 + 65536) * power ) >> 16); ++ // is equal to ++ // floor(log(5**power)/log(2)) + power when power >= 0 ++ // and it is equal to ++ // ceil(log(5**-power)/log(2)) + power when power < 0 ++ // ++ // The 65536 is (1<<16) and corresponds to ++ // (65536 * power) >> 16 ---> power ++ // ++ // ((152170 * power ) >> 16) is equal to ++ // floor(log(5**power)/log(2)) ++ // ++ // Note that this is not magic: 152170/(1<<16) is ++ // approximatively equal to log(5)/log(2). ++ // The 1<<16 value is a power of two; we could use a ++ // larger power of 2 if we wanted to. ++ // ++ int64_t exponent = (((152170 + 65536) * power) >> 16) + 1024 + 63; ++ ++ ++ // We want the most significant bit of i to be 1. Shift if needed. ++ int lz = leading_zeroes(i); ++ i <<= lz; ++ ++ ++ // We are going to need to do some 64-bit arithmetic to get a precise product. ++ // We use a table lookup approach. ++ // It is safe because ++ // power >= smallest_power ++ // and power <= largest_power ++ // We recover the mantissa of the power, it has a leading 1. It is always ++ // rounded down. ++ // ++ // We want the most significant 64 bits of the product. We know ++ // this will be non-zero because the most significant bit of i is ++ // 1. ++ const uint32_t index = 2 * uint32_t(power - simdjson::internal::smallest_power); ++ // Optimization: It may be that materializing the index as a variable might confuse some compilers and prevent effective complex-addressing loads. (Done for code clarity.) ++ // ++ // The full_multiplication function computes the 128-bit product of two 64-bit words ++ // with a returned value of type value128 with a "low component" corresponding to the ++ // 64-bit least significant bits of the product and with a "high component" corresponding ++ // to the 64-bit most significant bits of the product. ++ simdjson::internal::value128 firstproduct = jsoncharutils::full_multiplication(i, simdjson::internal::power_of_five_128[index]); ++ // Both i and power_of_five_128[index] have their most significant bit set to 1 which ++ // implies that the either the most or the second most significant bit of the product ++ // is 1. We pack values in this manner for efficiency reasons: it maximizes the use ++ // we make of the product. It also makes it easy to reason about the product: there ++ // is 0 or 1 leading zero in the product. ++ ++ // Unless the least significant 9 bits of the high (64-bit) part of the full ++ // product are all 1s, then we know that the most significant 55 bits are ++ // exact and no further work is needed. Having 55 bits is necessary because ++ // we need 53 bits for the mantissa but we have to have one rounding bit and ++ // we can waste a bit if the most significant bit of the product is zero. ++ if((firstproduct.high & 0x1FF) == 0x1FF) { ++ // We want to compute i * 5^q, but only care about the top 55 bits at most. ++ // Consider the scenario where q>=0. Then 5^q may not fit in 64-bits. Doing ++ // the full computation is wasteful. So we do what is called a "truncated ++ // multiplication". ++ // We take the most significant 64-bits, and we put them in ++ // power_of_five_128[index]. Usually, that's good enough to approximate i * 5^q ++ // to the desired approximation using one multiplication. Sometimes it does not suffice. ++ // Then we store the next most significant 64 bits in power_of_five_128[index + 1], and ++ // then we get a better approximation to i * 5^q. In very rare cases, even that ++ // will not suffice, though it is seemingly very hard to find such a scenario. ++ // ++ // That's for when q>=0. The logic for q<0 is somewhat similar but it is somewhat ++ // more complicated. ++ // ++ // There is an extra layer of complexity in that we need more than 55 bits of ++ // accuracy in the round-to-even scenario. ++ // ++ // The full_multiplication function computes the 128-bit product of two 64-bit words ++ // with a returned value of type value128 with a "low component" corresponding to the ++ // 64-bit least significant bits of the product and with a "high component" corresponding ++ // to the 64-bit most significant bits of the product. ++ simdjson::internal::value128 secondproduct = jsoncharutils::full_multiplication(i, simdjson::internal::power_of_five_128[index + 1]); ++ firstproduct.low += secondproduct.high; ++ if(secondproduct.high > firstproduct.low) { firstproduct.high++; } ++ // At this point, we might need to add at most one to firstproduct, but this ++ // can only change the value of firstproduct.high if firstproduct.low is maximal. ++ if(simdjson_unlikely(firstproduct.low == 0xFFFFFFFFFFFFFFFF)) { ++ // This is very unlikely, but if so, we need to do much more work! ++ return false; ++ } ++ } ++ uint64_t lower = firstproduct.low; ++ uint64_t upper = firstproduct.high; ++ // The final mantissa should be 53 bits with a leading 1. ++ // We shift it so that it occupies 54 bits with a leading 1. ++ /////// ++ uint64_t upperbit = upper >> 63; ++ uint64_t mantissa = upper >> (upperbit + 9); ++ lz += int(1 ^ upperbit); ++ ++ // Here we have mantissa < (1<<54). ++ int64_t real_exponent = exponent - lz; ++ if (simdjson_unlikely(real_exponent <= 0)) { // we have a subnormal? ++ // Here have that real_exponent <= 0 so -real_exponent >= 0 ++ if(-real_exponent + 1 >= 64) { // if we have more than 64 bits below the minimum exponent, you have a zero for sure. ++ d = negative ? -0.0 : 0.0; ++ return true; ++ } ++ // next line is safe because -real_exponent + 1 < 0 ++ mantissa >>= -real_exponent + 1; ++ // Thankfully, we can't have both "round-to-even" and subnormals because ++ // "round-to-even" only occurs for powers close to 0. ++ mantissa += (mantissa & 1); // round up ++ mantissa >>= 1; ++ // There is a weird scenario where we don't have a subnormal but just. ++ // Suppose we start with 2.2250738585072013e-308, we end up ++ // with 0x3fffffffffffff x 2^-1023-53 which is technically subnormal ++ // whereas 0x40000000000000 x 2^-1023-53 is normal. Now, we need to round ++ // up 0x3fffffffffffff x 2^-1023-53 and once we do, we are no longer ++ // subnormal, but we can only know this after rounding. ++ // So we only declare a subnormal if we are smaller than the threshold. ++ real_exponent = (mantissa < (uint64_t(1) << 52)) ? 0 : 1; ++ d = to_double(mantissa, real_exponent, negative); ++ return true; ++ } ++ // We have to round to even. The "to even" part ++ // is only a problem when we are right in between two floats ++ // which we guard against. ++ // If we have lots of trailing zeros, we may fall right between two ++ // floating-point values. ++ // ++ // The round-to-even cases take the form of a number 2m+1 which is in (2^53,2^54] ++ // times a power of two. That is, it is right between a number with binary significand ++ // m and another number with binary significand m+1; and it must be the case ++ // that it cannot be represented by a float itself. ++ // ++ // We must have that w * 10 ^q == (2m+1) * 2^p for some power of two 2^p. ++ // Recall that 10^q = 5^q * 2^q. ++ // When q >= 0, we must have that (2m+1) is divible by 5^q, so 5^q <= 2^54. We have that ++ // 5^23 <= 2^54 and it is the last power of five to qualify, so q <= 23. ++ // When q<0, we have w >= (2m+1) x 5^{-q}. We must have that w<2^{64} so ++ // (2m+1) x 5^{-q} < 2^{64}. We have that 2m+1>2^{53}. Hence, we must have ++ // 2^{53} x 5^{-q} < 2^{64}. ++ // Hence we have 5^{-q} < 2^{11}$ or q>= -4. ++ // ++ // We require lower <= 1 and not lower == 0 because we could not prove that ++ // that lower == 0 is implied; but we could prove that lower <= 1 is a necessary and sufficient test. ++ if (simdjson_unlikely((lower <= 1) && (power >= -4) && (power <= 23) && ((mantissa & 3) == 1))) { ++ if((mantissa << (upperbit + 64 - 53 - 2)) == upper) { ++ mantissa &= ~1; // flip it so that we do not round up ++ } ++ } ++ ++ mantissa += mantissa & 1; ++ mantissa >>= 1; ++ ++ // Here we have mantissa < (1<<53), unless there was an overflow ++ if (mantissa >= (1ULL << 53)) { ++ ////////// ++ // This will happen when parsing values such as 7.2057594037927933e+16 ++ //////// ++ mantissa = (1ULL << 52); ++ real_exponent++; ++ } ++ mantissa &= ~(1ULL << 52); ++ // we have to check that real_exponent is in range, otherwise we bail out ++ if (simdjson_unlikely(real_exponent > 2046)) { ++ // We have an infinite value!!! We could actually throw an error here if we could. ++ return false; ++ } ++ d = to_double(mantissa, real_exponent, negative); ++ return true; ++} ++ ++// We call a fallback floating-point parser that might be slow. Note ++// it will accept JSON numbers, but the JSON spec. is more restrictive so ++// before you call parse_float_fallback, you need to have validated the input ++// string with the JSON grammar. ++// It will return an error (false) if the parsed number is infinite. ++// The string parsing itself always succeeds. We know that there is at least ++// one digit. ++static bool parse_float_fallback(const uint8_t *ptr, double *outDouble) { ++ *outDouble = simdjson::internal::from_chars(reinterpret_cast(ptr)); ++ // We do not accept infinite values. ++ ++ // Detecting finite values in a portable manner is ridiculously hard, ideally ++ // we would want to do: ++ // return !std::isfinite(*outDouble); ++ // but that mysteriously fails under legacy/old libc++ libraries, see ++ // https://github.com/simdjson/simdjson/issues/1286 ++ // ++ // Therefore, fall back to this solution (the extra parens are there ++ // to handle that max may be a macro on windows). ++ return !(*outDouble > (std::numeric_limits::max)() || *outDouble < std::numeric_limits::lowest()); ++} ++static bool parse_float_fallback(const uint8_t *ptr, const uint8_t *end_ptr, double *outDouble) { ++ *outDouble = simdjson::internal::from_chars(reinterpret_cast(ptr), reinterpret_cast(end_ptr)); ++ // We do not accept infinite values. ++ ++ // Detecting finite values in a portable manner is ridiculously hard, ideally ++ // we would want to do: ++ // return !std::isfinite(*outDouble); ++ // but that mysteriously fails under legacy/old libc++ libraries, see ++ // https://github.com/simdjson/simdjson/issues/1286 ++ // ++ // Therefore, fall back to this solution (the extra parens are there ++ // to handle that max may be a macro on windows). ++ return !(*outDouble > (std::numeric_limits::max)() || *outDouble < std::numeric_limits::lowest()); ++} ++ ++// check quickly whether the next 8 chars are made of digits ++// at a glance, it looks better than Mula's ++// http://0x80.pl/articles/swar-digits-validate.html ++simdjson_inline bool is_made_of_eight_digits_fast(const uint8_t *chars) { ++ uint64_t val; ++ // this can read up to 7 bytes beyond the buffer size, but we require ++ // SIMDJSON_PADDING of padding ++ static_assert(7 <= SIMDJSON_PADDING, "SIMDJSON_PADDING must be bigger than 7"); ++ std::memcpy(&val, chars, 8); ++ // a branchy method might be faster: ++ // return (( val & 0xF0F0F0F0F0F0F0F0 ) == 0x3030303030303030) ++ // && (( (val + 0x0606060606060606) & 0xF0F0F0F0F0F0F0F0 ) == ++ // 0x3030303030303030); ++ return (((val & 0xF0F0F0F0F0F0F0F0) | ++ (((val + 0x0606060606060606) & 0xF0F0F0F0F0F0F0F0) >> 4)) == ++ 0x3333333333333333); ++} ++ ++template ++error_code slow_float_parsing(simdjson_unused const uint8_t * src, W writer) { ++ double d; ++ if (parse_float_fallback(src, &d)) { ++ writer.append_double(d); ++ return SUCCESS; ++ } ++ return INVALID_NUMBER(src); ++} ++ ++template ++SIMDJSON_NO_SANITIZE_UNDEFINED // We deliberately allow overflow here and check later ++simdjson_inline bool parse_digit(const uint8_t c, I &i) { ++ const uint8_t digit = static_cast(c - '0'); ++ if (digit > 9) { ++ return false; ++ } ++ // PERF NOTE: multiplication by 10 is cheaper than arbitrary integer multiplication ++ i = 10 * i + digit; // might overflow, we will handle the overflow later ++ return true; ++} ++ ++simdjson_inline error_code parse_decimal(simdjson_unused const uint8_t *const src, const uint8_t *&p, uint64_t &i, int64_t &exponent) { ++ // we continue with the fiction that we have an integer. If the ++ // floating point number is representable as x * 10^z for some integer ++ // z that fits in 53 bits, then we will be able to convert back the ++ // the integer into a float in a lossless manner. ++ const uint8_t *const first_after_period = p; ++ ++#ifdef SIMDJSON_SWAR_NUMBER_PARSING ++#if SIMDJSON_SWAR_NUMBER_PARSING ++ // this helps if we have lots of decimals! ++ // this turns out to be frequent enough. ++ if (is_made_of_eight_digits_fast(p)) { ++ i = i * 100000000 + parse_eight_digits_unrolled(p); ++ p += 8; ++ } ++#endif // SIMDJSON_SWAR_NUMBER_PARSING ++#endif // #ifdef SIMDJSON_SWAR_NUMBER_PARSING ++ // Unrolling the first digit makes a small difference on some implementations (e.g. westmere) ++ if (parse_digit(*p, i)) { ++p; } ++ while (parse_digit(*p, i)) { p++; } ++ exponent = first_after_period - p; ++ // Decimal without digits (123.) is illegal ++ if (exponent == 0) { ++ return INVALID_NUMBER(src); ++ } ++ return SUCCESS; ++} ++ ++simdjson_inline error_code parse_exponent(simdjson_unused const uint8_t *const src, const uint8_t *&p, int64_t &exponent) { ++ // Exp Sign: -123.456e[-]78 ++ bool neg_exp = ('-' == *p); ++ if (neg_exp || '+' == *p) { p++; } // Skip + as well ++ ++ // Exponent: -123.456e-[78] ++ auto start_exp = p; ++ int64_t exp_number = 0; ++ while (parse_digit(*p, exp_number)) { ++p; } ++ // It is possible for parse_digit to overflow. ++ // In particular, it could overflow to INT64_MIN, and we cannot do - INT64_MIN. ++ // Thus we *must* check for possible overflow before we negate exp_number. ++ ++ // Performance notes: it may seem like combining the two "simdjson_unlikely checks" below into ++ // a single simdjson_unlikely path would be faster. The reasoning is sound, but the compiler may ++ // not oblige and may, in fact, generate two distinct paths in any case. It might be ++ // possible to do uint64_t(p - start_exp - 1) >= 18 but it could end up trading off ++ // instructions for a simdjson_likely branch, an unconclusive gain. ++ ++ // If there were no digits, it's an error. ++ if (simdjson_unlikely(p == start_exp)) { ++ return INVALID_NUMBER(src); ++ } ++ // We have a valid positive exponent in exp_number at this point, except that ++ // it may have overflowed. ++ ++ // If there were more than 18 digits, we may have overflowed the integer. We have to do ++ // something!!!! ++ if (simdjson_unlikely(p > start_exp+18)) { ++ // Skip leading zeroes: 1e000000000000000000001 is technically valid and doesn't overflow ++ while (*start_exp == '0') { start_exp++; } ++ // 19 digits could overflow int64_t and is kind of absurd anyway. We don't ++ // support exponents smaller than -999,999,999,999,999,999 and bigger ++ // than 999,999,999,999,999,999. ++ // We can truncate. ++ // Note that 999999999999999999 is assuredly too large. The maximal ieee64 value before ++ // infinity is ~1.8e308. The smallest subnormal is ~5e-324. So, actually, we could ++ // truncate at 324. ++ // Note that there is no reason to fail per se at this point in time. ++ // E.g., 0e999999999999999999999 is a fine number. ++ if (p > start_exp+18) { exp_number = 999999999999999999; } ++ } ++ // At this point, we know that exp_number is a sane, positive, signed integer. ++ // It is <= 999,999,999,999,999,999. As long as 'exponent' is in ++ // [-8223372036854775808, 8223372036854775808], we won't overflow. Because 'exponent' ++ // is bounded in magnitude by the size of the JSON input, we are fine in this universe. ++ // To sum it up: the next line should never overflow. ++ exponent += (neg_exp ? -exp_number : exp_number); ++ return SUCCESS; ++} ++ ++simdjson_inline size_t significant_digits(const uint8_t * start_digits, size_t digit_count) { ++ // It is possible that the integer had an overflow. ++ // We have to handle the case where we have 0.0000somenumber. ++ const uint8_t *start = start_digits; ++ while ((*start == '0') || (*start == '.')) { ++start; } ++ // we over-decrement by one when there is a '.' ++ return digit_count - size_t(start - start_digits); ++} ++ ++template ++simdjson_inline error_code write_float(const uint8_t *const src, bool negative, uint64_t i, const uint8_t * start_digits, size_t digit_count, int64_t exponent, W &writer) { ++ // If we frequently had to deal with long strings of digits, ++ // we could extend our code by using a 128-bit integer instead ++ // of a 64-bit integer. However, this is uncommon in practice. ++ // ++ // 9999999999999999999 < 2**64 so we can accommodate 19 digits. ++ // If we have a decimal separator, then digit_count - 1 is the number of digits, but we ++ // may not have a decimal separator! ++ if (simdjson_unlikely(digit_count > 19 && significant_digits(start_digits, digit_count) > 19)) { ++ // Ok, chances are good that we had an overflow! ++ // this is almost never going to get called!!! ++ // we start anew, going slowly!!! ++ // This will happen in the following examples: ++ // 10000000000000000000000000000000000000000000e+308 ++ // 3.1415926535897932384626433832795028841971693993751 ++ // ++ // NOTE: This makes a *copy* of the writer and passes it to slow_float_parsing. This happens ++ // because slow_float_parsing is a non-inlined function. If we passed our writer reference to ++ // it, it would force it to be stored in memory, preventing the compiler from picking it apart ++ // and putting into registers. i.e. if we pass it as reference, it gets slow. ++ // This is what forces the skip_double, as well. ++ error_code error = slow_float_parsing(src, writer); ++ writer.skip_double(); ++ return error; ++ } ++ // NOTE: it's weird that the simdjson_unlikely() only wraps half the if, but it seems to get slower any other ++ // way we've tried: https://github.com/simdjson/simdjson/pull/990#discussion_r448497331 ++ // To future reader: we'd love if someone found a better way, or at least could explain this result! ++ if (simdjson_unlikely(exponent < simdjson::internal::smallest_power) || (exponent > simdjson::internal::largest_power)) { ++ // ++ // Important: smallest_power is such that it leads to a zero value. ++ // Observe that 18446744073709551615e-343 == 0, i.e. (2**64 - 1) e -343 is zero ++ // so something x 10^-343 goes to zero, but not so with something x 10^-342. ++ static_assert(simdjson::internal::smallest_power <= -342, "smallest_power is not small enough"); ++ // ++ if((exponent < simdjson::internal::smallest_power) || (i == 0)) { ++ // E.g. Parse "-0.0e-999" into the same value as "-0.0". See https://en.wikipedia.org/wiki/Signed_zero ++ WRITE_DOUBLE(negative ? -0.0 : 0.0, src, writer); ++ return SUCCESS; ++ } else { // (exponent > largest_power) and (i != 0) ++ // We have, for sure, an infinite value and simdjson refuses to parse infinite values. ++ return INVALID_NUMBER(src); ++ } ++ } ++ double d; ++ if (!compute_float_64(exponent, i, negative, d)) { ++ // we are almost never going to get here. ++ if (!parse_float_fallback(src, &d)) { return INVALID_NUMBER(src); } ++ } ++ WRITE_DOUBLE(d, src, writer); ++ return SUCCESS; ++} ++ ++// for performance analysis, it is sometimes useful to skip parsing ++#ifdef SIMDJSON_SKIPNUMBERPARSING ++ ++template ++simdjson_inline error_code parse_number(const uint8_t *const, W &writer) { ++ writer.append_s64(0); // always write zero ++ return SUCCESS; // always succeeds ++} ++ ++simdjson_unused simdjson_inline simdjson_result parse_unsigned(const uint8_t * const src) noexcept { return 0; } ++simdjson_unused simdjson_inline simdjson_result parse_integer(const uint8_t * const src) noexcept { return 0; } ++simdjson_unused simdjson_inline simdjson_result parse_double(const uint8_t * const src) noexcept { return 0; } ++simdjson_unused simdjson_inline simdjson_result parse_unsigned_in_string(const uint8_t * const src) noexcept { return 0; } ++simdjson_unused simdjson_inline simdjson_result parse_integer_in_string(const uint8_t * const src) noexcept { return 0; } ++simdjson_unused simdjson_inline simdjson_result parse_double_in_string(const uint8_t * const src) noexcept { return 0; } ++simdjson_unused simdjson_inline bool is_negative(const uint8_t * src) noexcept { return false; } ++simdjson_unused simdjson_inline simdjson_result is_integer(const uint8_t * src) noexcept { return false; } ++simdjson_unused simdjson_inline simdjson_result get_number_type(const uint8_t * src) noexcept { return ondemand::number_type::signed_integer; } ++#else ++ ++// parse the number at src ++// define JSON_TEST_NUMBERS for unit testing ++// ++// It is assumed that the number is followed by a structural ({,},],[) character ++// or a white space character. If that is not the case (e.g., when the JSON ++// document is made of a single number), then it is necessary to copy the ++// content and append a space before calling this function. ++// ++// Our objective is accurate parsing (ULP of 0) at high speed. ++template ++simdjson_inline error_code parse_number(const uint8_t *const src, W &writer) { ++ ++ // ++ // Check for minus sign ++ // ++ bool negative = (*src == '-'); ++ const uint8_t *p = src + uint8_t(negative); ++ ++ // ++ // Parse the integer part. ++ // ++ // PERF NOTE: we don't use is_made_of_eight_digits_fast because large integers like 123456789 are rare ++ const uint8_t *const start_digits = p; ++ uint64_t i = 0; ++ while (parse_digit(*p, i)) { p++; } ++ ++ // If there were no digits, or if the integer starts with 0 and has more than one digit, it's an error. ++ // Optimization note: size_t is expected to be unsigned. ++ size_t digit_count = size_t(p - start_digits); ++ if (digit_count == 0 || ('0' == *start_digits && digit_count > 1)) { return INVALID_NUMBER(src); } ++ ++ // ++ // Handle floats if there is a . or e (or both) ++ // ++ int64_t exponent = 0; ++ bool is_float = false; ++ if ('.' == *p) { ++ is_float = true; ++ ++p; ++ SIMDJSON_TRY( parse_decimal(src, p, i, exponent) ); ++ digit_count = int(p - start_digits); // used later to guard against overflows ++ } ++ if (('e' == *p) || ('E' == *p)) { ++ is_float = true; ++ ++p; ++ SIMDJSON_TRY( parse_exponent(src, p, exponent) ); ++ } ++ if (is_float) { ++ const bool dirty_end = jsoncharutils::is_not_structural_or_whitespace(*p); ++ SIMDJSON_TRY( write_float(src, negative, i, start_digits, digit_count, exponent, writer) ); ++ if (dirty_end) { return INVALID_NUMBER(src); } ++ return SUCCESS; ++ } ++ ++ // The longest negative 64-bit number is 19 digits. ++ // The longest positive 64-bit number is 20 digits. ++ // We do it this way so we don't trigger this branch unless we must. ++ size_t longest_digit_count = negative ? 19 : 20; ++ if (digit_count > longest_digit_count) { return INVALID_NUMBER(src); } ++ if (digit_count == longest_digit_count) { ++ if (negative) { ++ // Anything negative above INT64_MAX+1 is invalid ++ if (i > uint64_t(INT64_MAX)+1) { return INVALID_NUMBER(src); } ++ WRITE_INTEGER(~i+1, src, writer); ++ if (jsoncharutils::is_not_structural_or_whitespace(*p)) { return INVALID_NUMBER(src); } ++ return SUCCESS; ++ // Positive overflow check: ++ // - A 20 digit number starting with 2-9 is overflow, because 18,446,744,073,709,551,615 is the ++ // biggest uint64_t. ++ // - A 20 digit number starting with 1 is overflow if it is less than INT64_MAX. ++ // If we got here, it's a 20 digit number starting with the digit "1". ++ // - If a 20 digit number starting with 1 overflowed (i*10+digit), the result will be smaller ++ // than 1,553,255,926,290,448,384. ++ // - That is smaller than the smallest possible 20-digit number the user could write: ++ // 10,000,000,000,000,000,000. ++ // - Therefore, if the number is positive and lower than that, it's overflow. ++ // - The value we are looking at is less than or equal to INT64_MAX. ++ // ++ } else if (src[0] != uint8_t('1') || i <= uint64_t(INT64_MAX)) { return INVALID_NUMBER(src); } ++ } ++ ++ // Write unsigned if it doesn't fit in a signed integer. ++ if (i > uint64_t(INT64_MAX)) { ++ WRITE_UNSIGNED(i, src, writer); ++ } else { ++ WRITE_INTEGER(negative ? (~i+1) : i, src, writer); ++ } ++ if (jsoncharutils::is_not_structural_or_whitespace(*p)) { return INVALID_NUMBER(src); } ++ return SUCCESS; ++} ++ ++// Inlineable functions ++namespace { ++ ++// This table can be used to characterize the final character of an integer ++// string. For JSON structural character and allowable white space characters, ++// we return SUCCESS. For 'e', '.' and 'E', we return INCORRECT_TYPE. Otherwise ++// we return NUMBER_ERROR. ++// Optimization note: we could easily reduce the size of the table by half (to 128) ++// at the cost of an extra branch. ++// Optimization note: we want the values to use at most 8 bits (not, e.g., 32 bits): ++static_assert(error_code(uint8_t(NUMBER_ERROR))== NUMBER_ERROR, "bad NUMBER_ERROR cast"); ++static_assert(error_code(uint8_t(SUCCESS))== SUCCESS, "bad NUMBER_ERROR cast"); ++static_assert(error_code(uint8_t(INCORRECT_TYPE))== INCORRECT_TYPE, "bad NUMBER_ERROR cast"); ++ ++const uint8_t integer_string_finisher[256] = { ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, SUCCESS, ++ SUCCESS, NUMBER_ERROR, NUMBER_ERROR, SUCCESS, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, SUCCESS, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, SUCCESS, ++ NUMBER_ERROR, INCORRECT_TYPE, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, SUCCESS, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, INCORRECT_TYPE, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, SUCCESS, NUMBER_ERROR, SUCCESS, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, INCORRECT_TYPE, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, SUCCESS, NUMBER_ERROR, ++ SUCCESS, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR}; ++ ++// Parse any number from 0 to 18,446,744,073,709,551,615 ++simdjson_unused simdjson_inline simdjson_result parse_unsigned(const uint8_t * const src) noexcept { ++ const uint8_t *p = src; ++ // ++ // Parse the integer part. ++ // ++ // PERF NOTE: we don't use is_made_of_eight_digits_fast because large integers like 123456789 are rare ++ const uint8_t *const start_digits = p; ++ uint64_t i = 0; ++ while (parse_digit(*p, i)) { p++; } ++ ++ // If there were no digits, or if the integer starts with 0 and has more than one digit, it's an error. ++ // Optimization note: size_t is expected to be unsigned. ++ size_t digit_count = size_t(p - start_digits); ++ // The longest positive 64-bit number is 20 digits. ++ // We do it this way so we don't trigger this branch unless we must. ++ // Optimization note: the compiler can probably merge ++ // ((digit_count == 0) || (digit_count > 20)) ++ // into a single branch since digit_count is unsigned. ++ if ((digit_count == 0) || (digit_count > 20)) { return INCORRECT_TYPE; } ++ // Here digit_count > 0. ++ if (('0' == *start_digits) && (digit_count > 1)) { return NUMBER_ERROR; } ++ // We can do the following... ++ // if (!jsoncharutils::is_structural_or_whitespace(*p)) { ++ // return (*p == '.' || *p == 'e' || *p == 'E') ? INCORRECT_TYPE : NUMBER_ERROR; ++ // } ++ // as a single table lookup: ++ if (integer_string_finisher[*p] != SUCCESS) { return error_code(integer_string_finisher[*p]); } ++ ++ if (digit_count == 20) { ++ // Positive overflow check: ++ // - A 20 digit number starting with 2-9 is overflow, because 18,446,744,073,709,551,615 is the ++ // biggest uint64_t. ++ // - A 20 digit number starting with 1 is overflow if it is less than INT64_MAX. ++ // If we got here, it's a 20 digit number starting with the digit "1". ++ // - If a 20 digit number starting with 1 overflowed (i*10+digit), the result will be smaller ++ // than 1,553,255,926,290,448,384. ++ // - That is smaller than the smallest possible 20-digit number the user could write: ++ // 10,000,000,000,000,000,000. ++ // - Therefore, if the number is positive and lower than that, it's overflow. ++ // - The value we are looking at is less than or equal to INT64_MAX. ++ // ++ if (src[0] != uint8_t('1') || i <= uint64_t(INT64_MAX)) { return INCORRECT_TYPE; } ++ } ++ ++ return i; ++} ++ ++ ++// Parse any number from 0 to 18,446,744,073,709,551,615 ++// Never read at src_end or beyond ++simdjson_unused simdjson_inline simdjson_result parse_unsigned(const uint8_t * const src, const uint8_t * const src_end) noexcept { ++ const uint8_t *p = src; ++ // ++ // Parse the integer part. ++ // ++ // PERF NOTE: we don't use is_made_of_eight_digits_fast because large integers like 123456789 are rare ++ const uint8_t *const start_digits = p; ++ uint64_t i = 0; ++ while ((p != src_end) && parse_digit(*p, i)) { p++; } ++ ++ // If there were no digits, or if the integer starts with 0 and has more than one digit, it's an error. ++ // Optimization note: size_t is expected to be unsigned. ++ size_t digit_count = size_t(p - start_digits); ++ // The longest positive 64-bit number is 20 digits. ++ // We do it this way so we don't trigger this branch unless we must. ++ // Optimization note: the compiler can probably merge ++ // ((digit_count == 0) || (digit_count > 20)) ++ // into a single branch since digit_count is unsigned. ++ if ((digit_count == 0) || (digit_count > 20)) { return INCORRECT_TYPE; } ++ // Here digit_count > 0. ++ if (('0' == *start_digits) && (digit_count > 1)) { return NUMBER_ERROR; } ++ // We can do the following... ++ // if (!jsoncharutils::is_structural_or_whitespace(*p)) { ++ // return (*p == '.' || *p == 'e' || *p == 'E') ? INCORRECT_TYPE : NUMBER_ERROR; ++ // } ++ // as a single table lookup: ++ if ((p != src_end) && integer_string_finisher[*p] != SUCCESS) { return error_code(integer_string_finisher[*p]); } ++ ++ if (digit_count == 20) { ++ // Positive overflow check: ++ // - A 20 digit number starting with 2-9 is overflow, because 18,446,744,073,709,551,615 is the ++ // biggest uint64_t. ++ // - A 20 digit number starting with 1 is overflow if it is less than INT64_MAX. ++ // If we got here, it's a 20 digit number starting with the digit "1". ++ // - If a 20 digit number starting with 1 overflowed (i*10+digit), the result will be smaller ++ // than 1,553,255,926,290,448,384. ++ // - That is smaller than the smallest possible 20-digit number the user could write: ++ // 10,000,000,000,000,000,000. ++ // - Therefore, if the number is positive and lower than that, it's overflow. ++ // - The value we are looking at is less than or equal to INT64_MAX. ++ // ++ if (src[0] != uint8_t('1') || i <= uint64_t(INT64_MAX)) { return INCORRECT_TYPE; } ++ } ++ ++ return i; ++} ++ ++// Parse any number from 0 to 18,446,744,073,709,551,615 ++simdjson_unused simdjson_inline simdjson_result parse_unsigned_in_string(const uint8_t * const src) noexcept { ++ const uint8_t *p = src + 1; ++ // ++ // Parse the integer part. ++ // ++ // PERF NOTE: we don't use is_made_of_eight_digits_fast because large integers like 123456789 are rare ++ const uint8_t *const start_digits = p; ++ uint64_t i = 0; ++ while (parse_digit(*p, i)) { p++; } ++ ++ // If there were no digits, or if the integer starts with 0 and has more than one digit, it's an error. ++ // Optimization note: size_t is expected to be unsigned. ++ size_t digit_count = size_t(p - start_digits); ++ // The longest positive 64-bit number is 20 digits. ++ // We do it this way so we don't trigger this branch unless we must. ++ // Optimization note: the compiler can probably merge ++ // ((digit_count == 0) || (digit_count > 20)) ++ // into a single branch since digit_count is unsigned. ++ if ((digit_count == 0) || (digit_count > 20)) { return INCORRECT_TYPE; } ++ // Here digit_count > 0. ++ if (('0' == *start_digits) && (digit_count > 1)) { return NUMBER_ERROR; } ++ // We can do the following... ++ // if (!jsoncharutils::is_structural_or_whitespace(*p)) { ++ // return (*p == '.' || *p == 'e' || *p == 'E') ? INCORRECT_TYPE : NUMBER_ERROR; ++ // } ++ // as a single table lookup: ++ if (*p != '"') { return NUMBER_ERROR; } ++ ++ if (digit_count == 20) { ++ // Positive overflow check: ++ // - A 20 digit number starting with 2-9 is overflow, because 18,446,744,073,709,551,615 is the ++ // biggest uint64_t. ++ // - A 20 digit number starting with 1 is overflow if it is less than INT64_MAX. ++ // If we got here, it's a 20 digit number starting with the digit "1". ++ // - If a 20 digit number starting with 1 overflowed (i*10+digit), the result will be smaller ++ // than 1,553,255,926,290,448,384. ++ // - That is smaller than the smallest possible 20-digit number the user could write: ++ // 10,000,000,000,000,000,000. ++ // - Therefore, if the number is positive and lower than that, it's overflow. ++ // - The value we are looking at is less than or equal to INT64_MAX. ++ // ++ // Note: we use src[1] and not src[0] because src[0] is the quote character in this ++ // instance. ++ if (src[1] != uint8_t('1') || i <= uint64_t(INT64_MAX)) { return INCORRECT_TYPE; } ++ } ++ ++ return i; ++} ++ ++// Parse any number from -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 ++simdjson_unused simdjson_inline simdjson_result parse_integer(const uint8_t *src) noexcept { ++ // ++ // Check for minus sign ++ // ++ bool negative = (*src == '-'); ++ const uint8_t *p = src + uint8_t(negative); ++ ++ // ++ // Parse the integer part. ++ // ++ // PERF NOTE: we don't use is_made_of_eight_digits_fast because large integers like 123456789 are rare ++ const uint8_t *const start_digits = p; ++ uint64_t i = 0; ++ while (parse_digit(*p, i)) { p++; } ++ ++ // If there were no digits, or if the integer starts with 0 and has more than one digit, it's an error. ++ // Optimization note: size_t is expected to be unsigned. ++ size_t digit_count = size_t(p - start_digits); ++ // We go from ++ // -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 ++ // so we can never represent numbers that have more than 19 digits. ++ size_t longest_digit_count = 19; ++ // Optimization note: the compiler can probably merge ++ // ((digit_count == 0) || (digit_count > longest_digit_count)) ++ // into a single branch since digit_count is unsigned. ++ if ((digit_count == 0) || (digit_count > longest_digit_count)) { return INCORRECT_TYPE; } ++ // Here digit_count > 0. ++ if (('0' == *start_digits) && (digit_count > 1)) { return NUMBER_ERROR; } ++ // We can do the following... ++ // if (!jsoncharutils::is_structural_or_whitespace(*p)) { ++ // return (*p == '.' || *p == 'e' || *p == 'E') ? INCORRECT_TYPE : NUMBER_ERROR; ++ // } ++ // as a single table lookup: ++ if(integer_string_finisher[*p] != SUCCESS) { return error_code(integer_string_finisher[*p]); } ++ // Negative numbers have can go down to - INT64_MAX - 1 whereas positive numbers are limited to INT64_MAX. ++ // Performance note: This check is only needed when digit_count == longest_digit_count but it is ++ // so cheap that we might as well always make it. ++ if(i > uint64_t(INT64_MAX) + uint64_t(negative)) { return INCORRECT_TYPE; } ++ return negative ? (~i+1) : i; ++} ++ ++// Parse any number from -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 ++// Never read at src_end or beyond ++simdjson_unused simdjson_inline simdjson_result parse_integer(const uint8_t * const src, const uint8_t * const src_end) noexcept { ++ // ++ // Check for minus sign ++ // ++ if(src == src_end) { return NUMBER_ERROR; } ++ bool negative = (*src == '-'); ++ const uint8_t *p = src + uint8_t(negative); ++ ++ // ++ // Parse the integer part. ++ // ++ // PERF NOTE: we don't use is_made_of_eight_digits_fast because large integers like 123456789 are rare ++ const uint8_t *const start_digits = p; ++ uint64_t i = 0; ++ while ((p != src_end) && parse_digit(*p, i)) { p++; } ++ ++ // If there were no digits, or if the integer starts with 0 and has more than one digit, it's an error. ++ // Optimization note: size_t is expected to be unsigned. ++ size_t digit_count = size_t(p - start_digits); ++ // We go from ++ // -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 ++ // so we can never represent numbers that have more than 19 digits. ++ size_t longest_digit_count = 19; ++ // Optimization note: the compiler can probably merge ++ // ((digit_count == 0) || (digit_count > longest_digit_count)) ++ // into a single branch since digit_count is unsigned. ++ if ((digit_count == 0) || (digit_count > longest_digit_count)) { return INCORRECT_TYPE; } ++ // Here digit_count > 0. ++ if (('0' == *start_digits) && (digit_count > 1)) { return NUMBER_ERROR; } ++ // We can do the following... ++ // if (!jsoncharutils::is_structural_or_whitespace(*p)) { ++ // return (*p == '.' || *p == 'e' || *p == 'E') ? INCORRECT_TYPE : NUMBER_ERROR; ++ // } ++ // as a single table lookup: ++ if((p != src_end) && integer_string_finisher[*p] != SUCCESS) { return error_code(integer_string_finisher[*p]); } ++ // Negative numbers have can go down to - INT64_MAX - 1 whereas positive numbers are limited to INT64_MAX. ++ // Performance note: This check is only needed when digit_count == longest_digit_count but it is ++ // so cheap that we might as well always make it. ++ if(i > uint64_t(INT64_MAX) + uint64_t(negative)) { return INCORRECT_TYPE; } ++ return negative ? (~i+1) : i; ++} ++ ++// Parse any number from -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 ++simdjson_unused simdjson_inline simdjson_result parse_integer_in_string(const uint8_t *src) noexcept { ++ // ++ // Check for minus sign ++ // ++ bool negative = (*(src + 1) == '-'); ++ src += uint8_t(negative) + 1; ++ ++ // ++ // Parse the integer part. ++ // ++ // PERF NOTE: we don't use is_made_of_eight_digits_fast because large integers like 123456789 are rare ++ const uint8_t *const start_digits = src; ++ uint64_t i = 0; ++ while (parse_digit(*src, i)) { src++; } ++ ++ // If there were no digits, or if the integer starts with 0 and has more than one digit, it's an error. ++ // Optimization note: size_t is expected to be unsigned. ++ size_t digit_count = size_t(src - start_digits); ++ // We go from ++ // -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 ++ // so we can never represent numbers that have more than 19 digits. ++ size_t longest_digit_count = 19; ++ // Optimization note: the compiler can probably merge ++ // ((digit_count == 0) || (digit_count > longest_digit_count)) ++ // into a single branch since digit_count is unsigned. ++ if ((digit_count == 0) || (digit_count > longest_digit_count)) { return INCORRECT_TYPE; } ++ // Here digit_count > 0. ++ if (('0' == *start_digits) && (digit_count > 1)) { return NUMBER_ERROR; } ++ // We can do the following... ++ // if (!jsoncharutils::is_structural_or_whitespace(*src)) { ++ // return (*src == '.' || *src == 'e' || *src == 'E') ? INCORRECT_TYPE : NUMBER_ERROR; ++ // } ++ // as a single table lookup: ++ if(*src != '"') { return NUMBER_ERROR; } ++ // Negative numbers have can go down to - INT64_MAX - 1 whereas positive numbers are limited to INT64_MAX. ++ // Performance note: This check is only needed when digit_count == longest_digit_count but it is ++ // so cheap that we might as well always make it. ++ if(i > uint64_t(INT64_MAX) + uint64_t(negative)) { return INCORRECT_TYPE; } ++ return negative ? (~i+1) : i; ++} ++ ++simdjson_unused simdjson_inline simdjson_result parse_double(const uint8_t * src) noexcept { ++ // ++ // Check for minus sign ++ // ++ bool negative = (*src == '-'); ++ src += uint8_t(negative); ++ ++ // ++ // Parse the integer part. ++ // ++ uint64_t i = 0; ++ const uint8_t *p = src; ++ p += parse_digit(*p, i); ++ bool leading_zero = (i == 0); ++ while (parse_digit(*p, i)) { p++; } ++ // no integer digits, or 0123 (zero must be solo) ++ if ( p == src ) { return INCORRECT_TYPE; } ++ if ( (leading_zero && p != src+1)) { return NUMBER_ERROR; } ++ ++ // ++ // Parse the decimal part. ++ // ++ int64_t exponent = 0; ++ bool overflow; ++ if (simdjson_likely(*p == '.')) { ++ p++; ++ const uint8_t *start_decimal_digits = p; ++ if (!parse_digit(*p, i)) { return NUMBER_ERROR; } // no decimal digits ++ p++; ++ while (parse_digit(*p, i)) { p++; } ++ exponent = -(p - start_decimal_digits); ++ ++ // Overflow check. More than 19 digits (minus the decimal) may be overflow. ++ overflow = p-src-1 > 19; ++ if (simdjson_unlikely(overflow && leading_zero)) { ++ // Skip leading 0.00000 and see if it still overflows ++ const uint8_t *start_digits = src + 2; ++ while (*start_digits == '0') { start_digits++; } ++ overflow = start_digits-src > 19; ++ } ++ } else { ++ overflow = p-src > 19; ++ } ++ ++ // ++ // Parse the exponent ++ // ++ if (*p == 'e' || *p == 'E') { ++ p++; ++ bool exp_neg = *p == '-'; ++ p += exp_neg || *p == '+'; ++ ++ uint64_t exp = 0; ++ const uint8_t *start_exp_digits = p; ++ while (parse_digit(*p, exp)) { p++; } ++ // no exp digits, or 20+ exp digits ++ if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } ++ ++ exponent += exp_neg ? 0-exp : exp; ++ } ++ ++ if (jsoncharutils::is_not_structural_or_whitespace(*p)) { return NUMBER_ERROR; } ++ ++ overflow = overflow || exponent < simdjson::internal::smallest_power || exponent > simdjson::internal::largest_power; ++ ++ // ++ // Assemble (or slow-parse) the float ++ // ++ double d; ++ if (simdjson_likely(!overflow)) { ++ if (compute_float_64(exponent, i, negative, d)) { return d; } ++ } ++ if (!parse_float_fallback(src - uint8_t(negative), &d)) { ++ return NUMBER_ERROR; ++ } ++ return d; ++} ++ ++simdjson_unused simdjson_inline bool is_negative(const uint8_t * src) noexcept { ++ return (*src == '-'); ++} ++ ++simdjson_unused simdjson_inline simdjson_result is_integer(const uint8_t * src) noexcept { ++ bool negative = (*src == '-'); ++ src += uint8_t(negative); ++ const uint8_t *p = src; ++ while(static_cast(*p - '0') <= 9) { p++; } ++ if ( p == src ) { return NUMBER_ERROR; } ++ if (jsoncharutils::is_structural_or_whitespace(*p)) { return true; } ++ return false; ++} ++ ++simdjson_unused simdjson_inline simdjson_result get_number_type(const uint8_t * src) noexcept { ++ bool negative = (*src == '-'); ++ src += uint8_t(negative); ++ const uint8_t *p = src; ++ while(static_cast(*p - '0') <= 9) { p++; } ++ if ( p == src ) { return NUMBER_ERROR; } ++ if (jsoncharutils::is_structural_or_whitespace(*p)) { ++ // We have an integer. ++ // If the number is negative and valid, it must be a signed integer. ++ if(negative) { return ondemand::number_type::signed_integer; } ++ // We want values larger or equal to 9223372036854775808 to be unsigned ++ // integers, and the other values to be signed integers. ++ int digit_count = int(p - src); ++ if(digit_count >= 19) { ++ const uint8_t * smaller_big_integer = reinterpret_cast("9223372036854775808"); ++ if((digit_count >= 20) || (memcmp(src, smaller_big_integer, 19) >= 0)) { ++ return ondemand::number_type::unsigned_integer; ++ } ++ } ++ return ondemand::number_type::signed_integer; ++ } ++ // Hopefully, we have 'e' or 'E' or '.'. ++ return ondemand::number_type::floating_point_number; ++} ++ ++// Never read at src_end or beyond ++simdjson_unused simdjson_inline simdjson_result parse_double(const uint8_t * src, const uint8_t * const src_end) noexcept { ++ if(src == src_end) { return NUMBER_ERROR; } ++ // ++ // Check for minus sign ++ // ++ bool negative = (*src == '-'); ++ src += uint8_t(negative); ++ ++ // ++ // Parse the integer part. ++ // ++ uint64_t i = 0; ++ const uint8_t *p = src; ++ if(p == src_end) { return NUMBER_ERROR; } ++ p += parse_digit(*p, i); ++ bool leading_zero = (i == 0); ++ while ((p != src_end) && parse_digit(*p, i)) { p++; } ++ // no integer digits, or 0123 (zero must be solo) ++ if ( p == src ) { return INCORRECT_TYPE; } ++ if ( (leading_zero && p != src+1)) { return NUMBER_ERROR; } ++ ++ // ++ // Parse the decimal part. ++ // ++ int64_t exponent = 0; ++ bool overflow; ++ if (simdjson_likely((p != src_end) && (*p == '.'))) { ++ p++; ++ const uint8_t *start_decimal_digits = p; ++ if ((p == src_end) || !parse_digit(*p, i)) { return NUMBER_ERROR; } // no decimal digits ++ p++; ++ while ((p != src_end) && parse_digit(*p, i)) { p++; } ++ exponent = -(p - start_decimal_digits); ++ ++ // Overflow check. More than 19 digits (minus the decimal) may be overflow. ++ overflow = p-src-1 > 19; ++ if (simdjson_unlikely(overflow && leading_zero)) { ++ // Skip leading 0.00000 and see if it still overflows ++ const uint8_t *start_digits = src + 2; ++ while (*start_digits == '0') { start_digits++; } ++ overflow = start_digits-src > 19; ++ } ++ } else { ++ overflow = p-src > 19; ++ } ++ ++ // ++ // Parse the exponent ++ // ++ if ((p != src_end) && (*p == 'e' || *p == 'E')) { ++ p++; ++ if(p == src_end) { return NUMBER_ERROR; } ++ bool exp_neg = *p == '-'; ++ p += exp_neg || *p == '+'; ++ ++ uint64_t exp = 0; ++ const uint8_t *start_exp_digits = p; ++ while ((p != src_end) && parse_digit(*p, exp)) { p++; } ++ // no exp digits, or 20+ exp digits ++ if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } ++ ++ exponent += exp_neg ? 0-exp : exp; ++ } ++ ++ if ((p != src_end) && jsoncharutils::is_not_structural_or_whitespace(*p)) { return NUMBER_ERROR; } ++ ++ overflow = overflow || exponent < simdjson::internal::smallest_power || exponent > simdjson::internal::largest_power; ++ ++ // ++ // Assemble (or slow-parse) the float ++ // ++ double d; ++ if (simdjson_likely(!overflow)) { ++ if (compute_float_64(exponent, i, negative, d)) { return d; } ++ } ++ if (!parse_float_fallback(src - uint8_t(negative), src_end, &d)) { ++ return NUMBER_ERROR; ++ } ++ return d; ++} ++ ++simdjson_unused simdjson_inline simdjson_result parse_double_in_string(const uint8_t * src) noexcept { ++ // ++ // Check for minus sign ++ // ++ bool negative = (*(src + 1) == '-'); ++ src += uint8_t(negative) + 1; ++ ++ // ++ // Parse the integer part. ++ // ++ uint64_t i = 0; ++ const uint8_t *p = src; ++ p += parse_digit(*p, i); ++ bool leading_zero = (i == 0); ++ while (parse_digit(*p, i)) { p++; } ++ // no integer digits, or 0123 (zero must be solo) ++ if ( p == src ) { return INCORRECT_TYPE; } ++ if ( (leading_zero && p != src+1)) { return NUMBER_ERROR; } ++ ++ // ++ // Parse the decimal part. ++ // ++ int64_t exponent = 0; ++ bool overflow; ++ if (simdjson_likely(*p == '.')) { ++ p++; ++ const uint8_t *start_decimal_digits = p; ++ if (!parse_digit(*p, i)) { return NUMBER_ERROR; } // no decimal digits ++ p++; ++ while (parse_digit(*p, i)) { p++; } ++ exponent = -(p - start_decimal_digits); ++ ++ // Overflow check. More than 19 digits (minus the decimal) may be overflow. ++ overflow = p-src-1 > 19; ++ if (simdjson_unlikely(overflow && leading_zero)) { ++ // Skip leading 0.00000 and see if it still overflows ++ const uint8_t *start_digits = src + 2; ++ while (*start_digits == '0') { start_digits++; } ++ overflow = start_digits-src > 19; ++ } ++ } else { ++ overflow = p-src > 19; ++ } ++ ++ // ++ // Parse the exponent ++ // ++ if (*p == 'e' || *p == 'E') { ++ p++; ++ bool exp_neg = *p == '-'; ++ p += exp_neg || *p == '+'; ++ ++ uint64_t exp = 0; ++ const uint8_t *start_exp_digits = p; ++ while (parse_digit(*p, exp)) { p++; } ++ // no exp digits, or 20+ exp digits ++ if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } ++ ++ exponent += exp_neg ? 0-exp : exp; ++ } ++ ++ if (*p != '"') { return NUMBER_ERROR; } ++ ++ overflow = overflow || exponent < simdjson::internal::smallest_power || exponent > simdjson::internal::largest_power; ++ ++ // ++ // Assemble (or slow-parse) the float ++ // ++ double d; ++ if (simdjson_likely(!overflow)) { ++ if (compute_float_64(exponent, i, negative, d)) { return d; } ++ } ++ if (!parse_float_fallback(src - uint8_t(negative), &d)) { ++ return NUMBER_ERROR; ++ } ++ return d; ++} ++} //namespace {} ++#endif // SIMDJSON_SKIPNUMBERPARSING ++ ++} // namespace numberparsing ++} // unnamed namespace ++} // namespace haswell ++} // namespace simdjson ++/* end file include/simdjson/generic/numberparsing.h */ ++ ++#endif // SIMDJSON_HASWELL_NUMBERPARSING_H ++/* end file include/simdjson/haswell/numberparsing.h */ ++/* begin file include/simdjson/haswell/end.h */ ++SIMDJSON_UNTARGET_HASWELL ++/* end file include/simdjson/haswell/end.h */ ++ ++#endif // SIMDJSON_IMPLEMENTATION_HASWELL ++#endif // SIMDJSON_HASWELL_COMMON_H ++/* end file include/simdjson/haswell.h */ ++/* begin file include/simdjson/ppc64.h */ ++#ifndef SIMDJSON_PPC64_H ++#define SIMDJSON_PPC64_H ++ ++ ++#if SIMDJSON_IMPLEMENTATION_PPC64 ++ ++namespace simdjson { ++/** ++ * Implementation for ALTIVEC (PPC64). ++ */ ++namespace ppc64 { ++} // namespace ppc64 ++} // namespace simdjson ++ ++/* begin file include/simdjson/ppc64/implementation.h */ ++#ifndef SIMDJSON_PPC64_IMPLEMENTATION_H ++#define SIMDJSON_PPC64_IMPLEMENTATION_H ++ ++ ++namespace simdjson { ++namespace ppc64 { ++ ++namespace { ++using namespace simdjson; ++using namespace simdjson::dom; ++} // namespace ++ ++class implementation final : public simdjson::implementation { ++public: ++ simdjson_inline implementation() ++ : simdjson::implementation("ppc64", "PPC64 ALTIVEC", ++ internal::instruction_set::ALTIVEC) {} ++ simdjson_warn_unused error_code create_dom_parser_implementation( ++ size_t capacity, size_t max_length, ++ std::unique_ptr &dst) ++ const noexcept final; ++ simdjson_warn_unused error_code minify(const uint8_t *buf, size_t len, ++ uint8_t *dst, ++ size_t &dst_len) const noexcept final; ++ simdjson_warn_unused bool validate_utf8(const char *buf, ++ size_t len) const noexcept final; ++}; ++ ++} // namespace ppc64 ++} // namespace simdjson ++ ++#endif // SIMDJSON_PPC64_IMPLEMENTATION_H ++/* end file include/simdjson/ppc64/implementation.h */ ++ ++/* begin file include/simdjson/ppc64/begin.h */ ++// redefining SIMDJSON_IMPLEMENTATION to "ppc64" ++// #define SIMDJSON_IMPLEMENTATION ppc64 ++/* end file include/simdjson/ppc64/begin.h */ ++ ++// Declarations ++/* begin file include/simdjson/generic/dom_parser_implementation.h */ ++ ++namespace simdjson { ++namespace ppc64 { ++ ++// expectation: sizeof(open_container) = 64/8. ++struct open_container { ++ uint32_t tape_index; // where, on the tape, does the scope ([,{) begins ++ uint32_t count; // how many elements in the scope ++}; // struct open_container ++ ++static_assert(sizeof(open_container) == 64/8, "Open container must be 64 bits"); ++ ++class dom_parser_implementation final : public internal::dom_parser_implementation { ++public: ++ /** Tape location of each open { or [ */ ++ std::unique_ptr open_containers{}; ++ /** Whether each open container is a [ or { */ ++ std::unique_ptr is_array{}; ++ /** Buffer passed to stage 1 */ ++ const uint8_t *buf{}; ++ /** Length passed to stage 1 */ ++ size_t len{0}; ++ /** Document passed to stage 2 */ ++ dom::document *doc{}; ++ ++ inline dom_parser_implementation() noexcept; ++ inline dom_parser_implementation(dom_parser_implementation &&other) noexcept; ++ inline dom_parser_implementation &operator=(dom_parser_implementation &&other) noexcept; ++ dom_parser_implementation(const dom_parser_implementation &) = delete; ++ dom_parser_implementation &operator=(const dom_parser_implementation &) = delete; ++ ++ simdjson_warn_unused error_code parse(const uint8_t *buf, size_t len, dom::document &doc) noexcept final; ++ simdjson_warn_unused error_code stage1(const uint8_t *buf, size_t len, stage1_mode partial) noexcept final; ++ simdjson_warn_unused error_code stage2(dom::document &doc) noexcept final; ++ simdjson_warn_unused error_code stage2_next(dom::document &doc) noexcept final; ++ simdjson_warn_unused uint8_t *parse_string(const uint8_t *src, uint8_t *dst) const noexcept final; ++ inline simdjson_warn_unused error_code set_capacity(size_t capacity) noexcept final; ++ inline simdjson_warn_unused error_code set_max_depth(size_t max_depth) noexcept final; ++private: ++ simdjson_inline simdjson_warn_unused error_code set_capacity_stage1(size_t capacity); ++ ++}; ++ ++} // namespace ppc64 ++} // namespace simdjson ++ ++namespace simdjson { ++namespace ppc64 { ++ ++inline dom_parser_implementation::dom_parser_implementation() noexcept = default; ++inline dom_parser_implementation::dom_parser_implementation(dom_parser_implementation &&other) noexcept = default; ++inline dom_parser_implementation &dom_parser_implementation::operator=(dom_parser_implementation &&other) noexcept = default; ++ ++// Leaving these here so they can be inlined if so desired ++inline simdjson_warn_unused error_code dom_parser_implementation::set_capacity(size_t capacity) noexcept { ++ if(capacity > SIMDJSON_MAXSIZE_BYTES) { return CAPACITY; } ++ // Stage 1 index output ++ size_t max_structures = SIMDJSON_ROUNDUP_N(capacity, 64) + 2 + 7; ++ structural_indexes.reset( new (std::nothrow) uint32_t[max_structures] ); ++ if (!structural_indexes) { _capacity = 0; return MEMALLOC; } ++ structural_indexes[0] = 0; ++ n_structural_indexes = 0; ++ ++ _capacity = capacity; ++ return SUCCESS; ++} ++ ++inline simdjson_warn_unused error_code dom_parser_implementation::set_max_depth(size_t max_depth) noexcept { ++ // Stage 2 stacks ++ open_containers.reset(new (std::nothrow) open_container[max_depth]); ++ is_array.reset(new (std::nothrow) bool[max_depth]); ++ if (!is_array || !open_containers) { _max_depth = 0; return MEMALLOC; } ++ ++ _max_depth = max_depth; ++ return SUCCESS; ++} ++ ++} // namespace ppc64 ++} // namespace simdjson ++/* end file include/simdjson/generic/dom_parser_implementation.h */ ++/* begin file include/simdjson/ppc64/intrinsics.h */ ++#ifndef SIMDJSON_PPC64_INTRINSICS_H ++#define SIMDJSON_PPC64_INTRINSICS_H ++ ++ ++// This should be the correct header whether ++// you use visual studio or other compilers. ++#include ++ ++// These are defined by altivec.h in GCC toolchain, it is safe to undef them. ++#ifdef bool ++#undef bool ++#endif ++ ++#ifdef vector ++#undef vector ++#endif ++ ++static_assert(sizeof(__vector unsigned char) <= simdjson::SIMDJSON_PADDING, "insufficient padding for ppc64"); ++ ++#endif // SIMDJSON_PPC64_INTRINSICS_H ++/* end file include/simdjson/ppc64/intrinsics.h */ ++/* begin file include/simdjson/ppc64/bitmanipulation.h */ ++#ifndef SIMDJSON_PPC64_BITMANIPULATION_H ++#define SIMDJSON_PPC64_BITMANIPULATION_H ++ ++namespace simdjson { ++namespace ppc64 { ++namespace { ++ ++// We sometimes call trailing_zero on inputs that are zero, ++// but the algorithms do not end up using the returned value. ++// Sadly, sanitizers are not smart enough to figure it out. ++SIMDJSON_NO_SANITIZE_UNDEFINED ++simdjson_inline int trailing_zeroes(uint64_t input_num) { ++#if SIMDJSON_REGULAR_VISUAL_STUDIO ++ unsigned long ret; ++ // Search the mask data from least significant bit (LSB) ++ // to the most significant bit (MSB) for a set bit (1). ++ _BitScanForward64(&ret, input_num); ++ return (int)ret; ++#else // SIMDJSON_REGULAR_VISUAL_STUDIO ++ return __builtin_ctzll(input_num); ++#endif // SIMDJSON_REGULAR_VISUAL_STUDIO ++} ++ ++/* result might be undefined when input_num is zero */ ++simdjson_inline uint64_t clear_lowest_bit(uint64_t input_num) { ++ return input_num & (input_num - 1); ++} ++ ++/* result might be undefined when input_num is zero */ ++simdjson_inline int leading_zeroes(uint64_t input_num) { ++#if SIMDJSON_REGULAR_VISUAL_STUDIO ++ unsigned long leading_zero = 0; ++ // Search the mask data from most significant bit (MSB) ++ // to least significant bit (LSB) for a set bit (1). ++ if (_BitScanReverse64(&leading_zero, input_num)) ++ return (int)(63 - leading_zero); ++ else ++ return 64; ++#else ++ return __builtin_clzll(input_num); ++#endif // SIMDJSON_REGULAR_VISUAL_STUDIO ++} ++ ++#if SIMDJSON_REGULAR_VISUAL_STUDIO ++simdjson_inline int count_ones(uint64_t input_num) { ++ // note: we do not support legacy 32-bit Windows ++ return __popcnt64(input_num); // Visual Studio wants two underscores ++} ++#else ++simdjson_inline int count_ones(uint64_t input_num) { ++ return __builtin_popcountll(input_num); ++} ++#endif ++ ++simdjson_inline bool add_overflow(uint64_t value1, uint64_t value2, ++ uint64_t *result) { ++#if SIMDJSON_REGULAR_VISUAL_STUDIO ++ *result = value1 + value2; ++ return *result < value1; ++#else ++ return __builtin_uaddll_overflow(value1, value2, ++ reinterpret_cast(result)); ++#endif ++} ++ ++} // unnamed namespace ++} // namespace ppc64 ++} // namespace simdjson ++ ++#endif // SIMDJSON_PPC64_BITMANIPULATION_H ++/* end file include/simdjson/ppc64/bitmanipulation.h */ ++/* begin file include/simdjson/ppc64/bitmask.h */ ++#ifndef SIMDJSON_PPC64_BITMASK_H ++#define SIMDJSON_PPC64_BITMASK_H ++ ++namespace simdjson { ++namespace ppc64 { ++namespace { ++ ++// ++// Perform a "cumulative bitwise xor," flipping bits each time a 1 is ++// encountered. ++// ++// For example, prefix_xor(00100100) == 00011100 ++// ++simdjson_inline uint64_t prefix_xor(uint64_t bitmask) { ++ // You can use the version below, however gcc sometimes miscompiles ++ // vec_pmsum_be, it happens somewhere around between 8 and 9th version. ++ // The performance boost was not noticeable, falling back to a usual ++ // implementation. ++ // __vector unsigned long long all_ones = {~0ull, ~0ull}; ++ // __vector unsigned long long mask = {bitmask, 0}; ++ // // Clang and GCC return different values for pmsum for ull so cast it to one. ++ // // Generally it is not specified by ALTIVEC ISA what is returned by ++ // // vec_pmsum_be. ++ // #if defined(__LITTLE_ENDIAN__) ++ // return (uint64_t)(((__vector unsigned long long)vec_pmsum_be(all_ones, mask))[0]); ++ // #else ++ // return (uint64_t)(((__vector unsigned long long)vec_pmsum_be(all_ones, mask))[1]); ++ // #endif ++ bitmask ^= bitmask << 1; ++ bitmask ^= bitmask << 2; ++ bitmask ^= bitmask << 4; ++ bitmask ^= bitmask << 8; ++ bitmask ^= bitmask << 16; ++ bitmask ^= bitmask << 32; ++ return bitmask; ++} ++ ++} // unnamed namespace ++} // namespace ppc64 ++} // namespace simdjson ++ ++#endif ++/* end file include/simdjson/ppc64/bitmask.h */ ++/* begin file include/simdjson/ppc64/simd.h */ ++#ifndef SIMDJSON_PPC64_SIMD_H ++#define SIMDJSON_PPC64_SIMD_H ++ ++#include ++ ++namespace simdjson { ++namespace ppc64 { ++namespace { ++namespace simd { ++ ++using __m128i = __vector unsigned char; ++ ++template struct base { ++ __m128i value; ++ ++ // Zero constructor ++ simdjson_inline base() : value{__m128i()} {} ++ ++ // Conversion from SIMD register ++ simdjson_inline base(const __m128i _value) : value(_value) {} ++ ++ // Conversion to SIMD register ++ simdjson_inline operator const __m128i &() const { ++ return this->value; ++ } ++ simdjson_inline operator __m128i &() { return this->value; } ++ ++ // Bit operations ++ simdjson_inline Child operator|(const Child other) const { ++ return vec_or(this->value, (__m128i)other); ++ } ++ simdjson_inline Child operator&(const Child other) const { ++ return vec_and(this->value, (__m128i)other); ++ } ++ simdjson_inline Child operator^(const Child other) const { ++ return vec_xor(this->value, (__m128i)other); ++ } ++ simdjson_inline Child bit_andnot(const Child other) const { ++ return vec_andc(this->value, (__m128i)other); ++ } ++ simdjson_inline Child &operator|=(const Child other) { ++ auto this_cast = static_cast(this); ++ *this_cast = *this_cast | other; ++ return *this_cast; ++ } ++ simdjson_inline Child &operator&=(const Child other) { ++ auto this_cast = static_cast(this); ++ *this_cast = *this_cast & other; ++ return *this_cast; ++ } ++ simdjson_inline Child &operator^=(const Child other) { ++ auto this_cast = static_cast(this); ++ *this_cast = *this_cast ^ other; ++ return *this_cast; ++ } ++}; ++ ++// Forward-declared so they can be used by splat and friends. ++template struct simd8; ++ ++template > ++struct base8 : base> { ++ typedef uint16_t bitmask_t; ++ typedef uint32_t bitmask2_t; ++ ++ simdjson_inline base8() : base>() {} ++ simdjson_inline base8(const __m128i _value) : base>(_value) {} ++ ++ friend simdjson_inline Mask operator==(const simd8 lhs, const simd8 rhs) { ++ return (__m128i)vec_cmpeq(lhs.value, (__m128i)rhs); ++ } ++ ++ static const int SIZE = sizeof(base>::value); ++ ++ template ++ simdjson_inline simd8 prev(simd8 prev_chunk) const { ++ __m128i chunk = this->value; ++#ifdef __LITTLE_ENDIAN__ ++ chunk = (__m128i)vec_reve(this->value); ++ prev_chunk = (__m128i)vec_reve((__m128i)prev_chunk); ++#endif ++ chunk = (__m128i)vec_sld((__m128i)prev_chunk, (__m128i)chunk, 16 - N); ++#ifdef __LITTLE_ENDIAN__ ++ chunk = (__m128i)vec_reve((__m128i)chunk); ++#endif ++ return chunk; ++ } ++}; ++ ++// SIMD byte mask type (returned by things like eq and gt) ++template <> struct simd8 : base8 { ++ static simdjson_inline simd8 splat(bool _value) { ++ return (__m128i)vec_splats((unsigned char)(-(!!_value))); ++ } ++ ++ simdjson_inline simd8() : base8() {} ++ simdjson_inline simd8(const __m128i _value) ++ : base8(_value) {} ++ // Splat constructor ++ simdjson_inline simd8(bool _value) ++ : base8(splat(_value)) {} ++ ++ simdjson_inline int to_bitmask() const { ++ __vector unsigned long long result; ++ const __m128i perm_mask = {0x78, 0x70, 0x68, 0x60, 0x58, 0x50, 0x48, 0x40, ++ 0x38, 0x30, 0x28, 0x20, 0x18, 0x10, 0x08, 0x00}; ++ ++ result = ((__vector unsigned long long)vec_vbpermq((__m128i)this->value, ++ (__m128i)perm_mask)); ++#ifdef __LITTLE_ENDIAN__ ++ return static_cast(result[1]); ++#else ++ return static_cast(result[0]); ++#endif ++ } ++ simdjson_inline bool any() const { ++ return !vec_all_eq(this->value, (__m128i)vec_splats(0)); ++ } ++ simdjson_inline simd8 operator~() const { ++ return this->value ^ (__m128i)splat(true); ++ } ++}; ++ ++template struct base8_numeric : base8 { ++ static simdjson_inline simd8 splat(T value) { ++ (void)value; ++ return (__m128i)vec_splats(value); ++ } ++ static simdjson_inline simd8 zero() { return splat(0); } ++ static simdjson_inline simd8 load(const T values[16]) { ++ return (__m128i)(vec_vsx_ld(0, reinterpret_cast(values))); ++ } ++ // Repeat 16 values as many times as necessary (usually for lookup tables) ++ static simdjson_inline simd8 repeat_16(T v0, T v1, T v2, T v3, T v4, ++ T v5, T v6, T v7, T v8, T v9, ++ T v10, T v11, T v12, T v13, ++ T v14, T v15) { ++ return simd8(v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, ++ v14, v15); ++ } ++ ++ simdjson_inline base8_numeric() : base8() {} ++ simdjson_inline base8_numeric(const __m128i _value) ++ : base8(_value) {} ++ ++ // Store to array ++ simdjson_inline void store(T dst[16]) const { ++ vec_vsx_st(this->value, 0, reinterpret_cast<__m128i *>(dst)); ++ } ++ ++ // Override to distinguish from bool version ++ simdjson_inline simd8 operator~() const { return *this ^ 0xFFu; } ++ ++ // Addition/subtraction are the same for signed and unsigned ++ simdjson_inline simd8 operator+(const simd8 other) const { ++ return (__m128i)((__m128i)this->value + (__m128i)other); ++ } ++ simdjson_inline simd8 operator-(const simd8 other) const { ++ return (__m128i)((__m128i)this->value - (__m128i)other); ++ } ++ simdjson_inline simd8 &operator+=(const simd8 other) { ++ *this = *this + other; ++ return *static_cast *>(this); ++ } ++ simdjson_inline simd8 &operator-=(const simd8 other) { ++ *this = *this - other; ++ return *static_cast *>(this); ++ } ++ ++ // Perform a lookup assuming the value is between 0 and 16 (undefined behavior ++ // for out of range values) ++ template ++ simdjson_inline simd8 lookup_16(simd8 lookup_table) const { ++ return (__m128i)vec_perm((__m128i)lookup_table, (__m128i)lookup_table, this->value); ++ } ++ ++ // Copies to 'output" all bytes corresponding to a 0 in the mask (interpreted ++ // as a bitset). Passing a 0 value for mask would be equivalent to writing out ++ // every byte to output. Only the first 16 - count_ones(mask) bytes of the ++ // result are significant but 16 bytes get written. Design consideration: it ++ // seems like a function with the signature simd8 compress(uint32_t mask) ++ // would be sensible, but the AVX ISA makes this kind of approach difficult. ++ template ++ simdjson_inline void compress(uint16_t mask, L *output) const { ++ using internal::BitsSetTable256mul2; ++ using internal::pshufb_combine_table; ++ using internal::thintable_epi8; ++ // this particular implementation was inspired by work done by @animetosho ++ // we do it in two steps, first 8 bytes and then second 8 bytes ++ uint8_t mask1 = uint8_t(mask); // least significant 8 bits ++ uint8_t mask2 = uint8_t(mask >> 8); // most significant 8 bits ++ // next line just loads the 64-bit values thintable_epi8[mask1] and ++ // thintable_epi8[mask2] into a 128-bit register, using only ++ // two instructions on most compilers. ++#ifdef __LITTLE_ENDIAN__ ++ __m128i shufmask = (__m128i)(__vector unsigned long long){ ++ thintable_epi8[mask1], thintable_epi8[mask2]}; ++#else ++ __m128i shufmask = (__m128i)(__vector unsigned long long){ ++ thintable_epi8[mask2], thintable_epi8[mask1]}; ++ shufmask = (__m128i)vec_reve((__m128i)shufmask); ++#endif ++ // we increment by 0x08 the second half of the mask ++ shufmask = ((__m128i)shufmask) + ++ ((__m128i)(__vector int){0, 0, 0x08080808, 0x08080808}); ++ ++ // this is the version "nearly pruned" ++ __m128i pruned = vec_perm(this->value, this->value, shufmask); ++ // we still need to put the two halves together. ++ // we compute the popcount of the first half: ++ int pop1 = BitsSetTable256mul2[mask1]; ++ // then load the corresponding mask, what it does is to write ++ // only the first pop1 bytes from the first 8 bytes, and then ++ // it fills in with the bytes from the second 8 bytes + some filling ++ // at the end. ++ __m128i compactmask = ++ vec_vsx_ld(0, reinterpret_cast(pshufb_combine_table + pop1 * 8)); ++ __m128i answer = vec_perm(pruned, (__m128i)vec_splats(0), compactmask); ++ vec_vsx_st(answer, 0, reinterpret_cast<__m128i *>(output)); ++ } ++ ++ template ++ simdjson_inline simd8 ++ lookup_16(L replace0, L replace1, L replace2, L replace3, L replace4, ++ L replace5, L replace6, L replace7, L replace8, L replace9, ++ L replace10, L replace11, L replace12, L replace13, L replace14, ++ L replace15) const { ++ return lookup_16(simd8::repeat_16( ++ replace0, replace1, replace2, replace3, replace4, replace5, replace6, ++ replace7, replace8, replace9, replace10, replace11, replace12, ++ replace13, replace14, replace15)); ++ } ++}; ++ ++// Signed bytes ++template <> struct simd8 : base8_numeric { ++ simdjson_inline simd8() : base8_numeric() {} ++ simdjson_inline simd8(const __m128i _value) ++ : base8_numeric(_value) {} ++ // Splat constructor ++ simdjson_inline simd8(int8_t _value) : simd8(splat(_value)) {} ++ // Array constructor ++ simdjson_inline simd8(const int8_t *values) : simd8(load(values)) {} ++ // Member-by-member initialization ++ simdjson_inline simd8(int8_t v0, int8_t v1, int8_t v2, int8_t v3, ++ int8_t v4, int8_t v5, int8_t v6, int8_t v7, ++ int8_t v8, int8_t v9, int8_t v10, int8_t v11, ++ int8_t v12, int8_t v13, int8_t v14, int8_t v15) ++ : simd8((__m128i)(__vector signed char){v0, v1, v2, v3, v4, v5, v6, v7, ++ v8, v9, v10, v11, v12, v13, v14, ++ v15}) {} ++ // Repeat 16 values as many times as necessary (usually for lookup tables) ++ simdjson_inline static simd8 ++ repeat_16(int8_t v0, int8_t v1, int8_t v2, int8_t v3, int8_t v4, int8_t v5, ++ int8_t v6, int8_t v7, int8_t v8, int8_t v9, int8_t v10, int8_t v11, ++ int8_t v12, int8_t v13, int8_t v14, int8_t v15) { ++ return simd8(v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, ++ v13, v14, v15); ++ } ++ ++ // Order-sensitive comparisons ++ simdjson_inline simd8 ++ max_val(const simd8 other) const { ++ return (__m128i)vec_max((__vector signed char)this->value, ++ (__vector signed char)(__m128i)other); ++ } ++ simdjson_inline simd8 ++ min_val(const simd8 other) const { ++ return (__m128i)vec_min((__vector signed char)this->value, ++ (__vector signed char)(__m128i)other); ++ } ++ simdjson_inline simd8 ++ operator>(const simd8 other) const { ++ return (__m128i)vec_cmpgt((__vector signed char)this->value, ++ (__vector signed char)(__m128i)other); ++ } ++ simdjson_inline simd8 ++ operator<(const simd8 other) const { ++ return (__m128i)vec_cmplt((__vector signed char)this->value, ++ (__vector signed char)(__m128i)other); ++ } ++}; ++ ++// Unsigned bytes ++template <> struct simd8 : base8_numeric { ++ simdjson_inline simd8() : base8_numeric() {} ++ simdjson_inline simd8(const __m128i _value) ++ : base8_numeric(_value) {} ++ // Splat constructor ++ simdjson_inline simd8(uint8_t _value) : simd8(splat(_value)) {} ++ // Array constructor ++ simdjson_inline simd8(const uint8_t *values) : simd8(load(values)) {} ++ // Member-by-member initialization ++ simdjson_inline ++ simd8(uint8_t v0, uint8_t v1, uint8_t v2, uint8_t v3, uint8_t v4, uint8_t v5, ++ uint8_t v6, uint8_t v7, uint8_t v8, uint8_t v9, uint8_t v10, ++ uint8_t v11, uint8_t v12, uint8_t v13, uint8_t v14, uint8_t v15) ++ : simd8((__m128i){v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, ++ v13, v14, v15}) {} ++ // Repeat 16 values as many times as necessary (usually for lookup tables) ++ simdjson_inline static simd8 ++ repeat_16(uint8_t v0, uint8_t v1, uint8_t v2, uint8_t v3, uint8_t v4, ++ uint8_t v5, uint8_t v6, uint8_t v7, uint8_t v8, uint8_t v9, ++ uint8_t v10, uint8_t v11, uint8_t v12, uint8_t v13, uint8_t v14, ++ uint8_t v15) { ++ return simd8(v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, ++ v13, v14, v15); ++ } ++ ++ // Saturated math ++ simdjson_inline simd8 ++ saturating_add(const simd8 other) const { ++ return (__m128i)vec_adds(this->value, (__m128i)other); ++ } ++ simdjson_inline simd8 ++ saturating_sub(const simd8 other) const { ++ return (__m128i)vec_subs(this->value, (__m128i)other); ++ } ++ ++ // Order-specific operations ++ simdjson_inline simd8 ++ max_val(const simd8 other) const { ++ return (__m128i)vec_max(this->value, (__m128i)other); ++ } ++ simdjson_inline simd8 ++ min_val(const simd8 other) const { ++ return (__m128i)vec_min(this->value, (__m128i)other); ++ } ++ // Same as >, but only guarantees true is nonzero (< guarantees true = -1) ++ simdjson_inline simd8 ++ gt_bits(const simd8 other) const { ++ return this->saturating_sub(other); ++ } ++ // Same as <, but only guarantees true is nonzero (< guarantees true = -1) ++ simdjson_inline simd8 ++ lt_bits(const simd8 other) const { ++ return other.saturating_sub(*this); ++ } ++ simdjson_inline simd8 ++ operator<=(const simd8 other) const { ++ return other.max_val(*this) == other; ++ } ++ simdjson_inline simd8 ++ operator>=(const simd8 other) const { ++ return other.min_val(*this) == other; ++ } ++ simdjson_inline simd8 ++ operator>(const simd8 other) const { ++ return this->gt_bits(other).any_bits_set(); ++ } ++ simdjson_inline simd8 ++ operator<(const simd8 other) const { ++ return this->gt_bits(other).any_bits_set(); ++ } ++ ++ // Bit-specific operations ++ simdjson_inline simd8 bits_not_set() const { ++ return (__m128i)vec_cmpeq(this->value, (__m128i)vec_splats(uint8_t(0))); ++ } ++ simdjson_inline simd8 bits_not_set(simd8 bits) const { ++ return (*this & bits).bits_not_set(); ++ } ++ simdjson_inline simd8 any_bits_set() const { ++ return ~this->bits_not_set(); ++ } ++ simdjson_inline simd8 any_bits_set(simd8 bits) const { ++ return ~this->bits_not_set(bits); ++ } ++ simdjson_inline bool bits_not_set_anywhere() const { ++ return vec_all_eq(this->value, (__m128i)vec_splats(0)); ++ } ++ simdjson_inline bool any_bits_set_anywhere() const { ++ return !bits_not_set_anywhere(); ++ } ++ simdjson_inline bool bits_not_set_anywhere(simd8 bits) const { ++ return vec_all_eq(vec_and(this->value, (__m128i)bits), ++ (__m128i)vec_splats(0)); ++ } ++ simdjson_inline bool any_bits_set_anywhere(simd8 bits) const { ++ return !bits_not_set_anywhere(bits); ++ } ++ template simdjson_inline simd8 shr() const { ++ return simd8( ++ (__m128i)vec_sr(this->value, (__m128i)vec_splat_u8(N))); ++ } ++ template simdjson_inline simd8 shl() const { ++ return simd8( ++ (__m128i)vec_sl(this->value, (__m128i)vec_splat_u8(N))); ++ } ++}; ++ ++template struct simd8x64 { ++ static constexpr int NUM_CHUNKS = 64 / sizeof(simd8); ++ static_assert(NUM_CHUNKS == 4, ++ "PPC64 kernel should use four registers per 64-byte block."); ++ const simd8 chunks[NUM_CHUNKS]; ++ ++ simd8x64(const simd8x64 &o) = delete; // no copy allowed ++ simd8x64 & ++ operator=(const simd8& other) = delete; // no assignment allowed ++ simd8x64() = delete; // no default constructor allowed ++ ++ simdjson_inline simd8x64(const simd8 chunk0, const simd8 chunk1, ++ const simd8 chunk2, const simd8 chunk3) ++ : chunks{chunk0, chunk1, chunk2, chunk3} {} ++ simdjson_inline simd8x64(const T ptr[64]) ++ : chunks{simd8::load(ptr), simd8::load(ptr + 16), ++ simd8::load(ptr + 32), simd8::load(ptr + 48)} {} ++ ++ simdjson_inline void store(T ptr[64]) const { ++ this->chunks[0].store(ptr + sizeof(simd8) * 0); ++ this->chunks[1].store(ptr + sizeof(simd8) * 1); ++ this->chunks[2].store(ptr + sizeof(simd8) * 2); ++ this->chunks[3].store(ptr + sizeof(simd8) * 3); ++ } ++ ++ simdjson_inline simd8 reduce_or() const { ++ return (this->chunks[0] | this->chunks[1]) | ++ (this->chunks[2] | this->chunks[3]); ++ } ++ ++ simdjson_inline uint64_t compress(uint64_t mask, T *output) const { ++ this->chunks[0].compress(uint16_t(mask), output); ++ this->chunks[1].compress(uint16_t(mask >> 16), ++ output + 16 - count_ones(mask & 0xFFFF)); ++ this->chunks[2].compress(uint16_t(mask >> 32), ++ output + 32 - count_ones(mask & 0xFFFFFFFF)); ++ this->chunks[3].compress(uint16_t(mask >> 48), ++ output + 48 - count_ones(mask & 0xFFFFFFFFFFFF)); ++ return 64 - count_ones(mask); ++ } ++ ++ simdjson_inline uint64_t to_bitmask() const { ++ uint64_t r0 = uint32_t(this->chunks[0].to_bitmask()); ++ uint64_t r1 = this->chunks[1].to_bitmask(); ++ uint64_t r2 = this->chunks[2].to_bitmask(); ++ uint64_t r3 = this->chunks[3].to_bitmask(); ++ return r0 | (r1 << 16) | (r2 << 32) | (r3 << 48); ++ } ++ ++ simdjson_inline uint64_t eq(const T m) const { ++ const simd8 mask = simd8::splat(m); ++ return simd8x64(this->chunks[0] == mask, this->chunks[1] == mask, ++ this->chunks[2] == mask, this->chunks[3] == mask) ++ .to_bitmask(); ++ } ++ ++ simdjson_inline uint64_t eq(const simd8x64 &other) const { ++ return simd8x64(this->chunks[0] == other.chunks[0], ++ this->chunks[1] == other.chunks[1], ++ this->chunks[2] == other.chunks[2], ++ this->chunks[3] == other.chunks[3]) ++ .to_bitmask(); ++ } ++ ++ simdjson_inline uint64_t lteq(const T m) const { ++ const simd8 mask = simd8::splat(m); ++ return simd8x64(this->chunks[0] <= mask, this->chunks[1] <= mask, ++ this->chunks[2] <= mask, this->chunks[3] <= mask) ++ .to_bitmask(); ++ } ++}; // struct simd8x64 ++ ++} // namespace simd ++} // unnamed namespace ++} // namespace ppc64 ++} // namespace simdjson ++ ++#endif // SIMDJSON_PPC64_SIMD_INPUT_H ++/* end file include/simdjson/ppc64/simd.h */ ++/* begin file include/simdjson/generic/jsoncharutils.h */ ++ ++namespace simdjson { ++namespace ppc64 { ++namespace { ++namespace jsoncharutils { ++ ++// return non-zero if not a structural or whitespace char ++// zero otherwise ++simdjson_inline uint32_t is_not_structural_or_whitespace(uint8_t c) { ++ return internal::structural_or_whitespace_negated[c]; ++} ++ ++simdjson_inline uint32_t is_structural_or_whitespace(uint8_t c) { ++ return internal::structural_or_whitespace[c]; ++} ++ ++// returns a value with the high 16 bits set if not valid ++// otherwise returns the conversion of the 4 hex digits at src into the bottom ++// 16 bits of the 32-bit return register ++// ++// see ++// https://lemire.me/blog/2019/04/17/parsing-short-hexadecimal-strings-efficiently/ ++static inline uint32_t hex_to_u32_nocheck( ++ const uint8_t *src) { // strictly speaking, static inline is a C-ism ++ uint32_t v1 = internal::digit_to_val32[630 + src[0]]; ++ uint32_t v2 = internal::digit_to_val32[420 + src[1]]; ++ uint32_t v3 = internal::digit_to_val32[210 + src[2]]; ++ uint32_t v4 = internal::digit_to_val32[0 + src[3]]; ++ return v1 | v2 | v3 | v4; ++} ++ ++// given a code point cp, writes to c ++// the utf-8 code, outputting the length in ++// bytes, if the length is zero, the code point ++// is invalid ++// ++// This can possibly be made faster using pdep ++// and clz and table lookups, but JSON documents ++// have few escaped code points, and the following ++// function looks cheap. ++// ++// Note: we assume that surrogates are treated separately ++// ++simdjson_inline size_t codepoint_to_utf8(uint32_t cp, uint8_t *c) { ++ if (cp <= 0x7F) { ++ c[0] = uint8_t(cp); ++ return 1; // ascii ++ } ++ if (cp <= 0x7FF) { ++ c[0] = uint8_t((cp >> 6) + 192); ++ c[1] = uint8_t((cp & 63) + 128); ++ return 2; // universal plane ++ // Surrogates are treated elsewhere... ++ //} //else if (0xd800 <= cp && cp <= 0xdfff) { ++ // return 0; // surrogates // could put assert here ++ } else if (cp <= 0xFFFF) { ++ c[0] = uint8_t((cp >> 12) + 224); ++ c[1] = uint8_t(((cp >> 6) & 63) + 128); ++ c[2] = uint8_t((cp & 63) + 128); ++ return 3; ++ } else if (cp <= 0x10FFFF) { // if you know you have a valid code point, this ++ // is not needed ++ c[0] = uint8_t((cp >> 18) + 240); ++ c[1] = uint8_t(((cp >> 12) & 63) + 128); ++ c[2] = uint8_t(((cp >> 6) & 63) + 128); ++ c[3] = uint8_t((cp & 63) + 128); ++ return 4; ++ } ++ // will return 0 when the code point was too large. ++ return 0; // bad r ++} ++ ++#if SIMDJSON_IS_32BITS // _umul128 for x86, arm ++// this is a slow emulation routine for 32-bit ++// ++static simdjson_inline uint64_t __emulu(uint32_t x, uint32_t y) { ++ return x * (uint64_t)y; ++} ++static simdjson_inline uint64_t _umul128(uint64_t ab, uint64_t cd, uint64_t *hi) { ++ uint64_t ad = __emulu((uint32_t)(ab >> 32), (uint32_t)cd); ++ uint64_t bd = __emulu((uint32_t)ab, (uint32_t)cd); ++ uint64_t adbc = ad + __emulu((uint32_t)ab, (uint32_t)(cd >> 32)); ++ uint64_t adbc_carry = !!(adbc < ad); ++ uint64_t lo = bd + (adbc << 32); ++ *hi = __emulu((uint32_t)(ab >> 32), (uint32_t)(cd >> 32)) + (adbc >> 32) + ++ (adbc_carry << 32) + !!(lo < bd); ++ return lo; ++} ++#endif ++ ++using internal::value128; ++ ++simdjson_inline value128 full_multiplication(uint64_t value1, uint64_t value2) { ++ value128 answer; ++#if SIMDJSON_REGULAR_VISUAL_STUDIO || SIMDJSON_IS_32BITS ++#ifdef _M_ARM64 ++ // ARM64 has native support for 64-bit multiplications, no need to emultate ++ answer.high = __umulh(value1, value2); ++ answer.low = value1 * value2; ++#else ++ answer.low = _umul128(value1, value2, &answer.high); // _umul128 not available on ARM64 ++#endif // _M_ARM64 ++#else // SIMDJSON_REGULAR_VISUAL_STUDIO || SIMDJSON_IS_32BITS ++ __uint128_t r = (static_cast<__uint128_t>(value1)) * value2; ++ answer.low = uint64_t(r); ++ answer.high = uint64_t(r >> 64); ++#endif ++ return answer; ++} ++ ++} // namespace jsoncharutils ++} // unnamed namespace ++} // namespace ppc64 ++} // namespace simdjson ++/* end file include/simdjson/generic/jsoncharutils.h */ ++/* begin file include/simdjson/generic/atomparsing.h */ ++namespace simdjson { ++namespace ppc64 { ++namespace { ++/// @private ++namespace atomparsing { ++ ++// The string_to_uint32 is exclusively used to map literal strings to 32-bit values. ++// We use memcpy instead of a pointer cast to avoid undefined behaviors since we cannot ++// be certain that the character pointer will be properly aligned. ++// You might think that using memcpy makes this function expensive, but you'd be wrong. ++// All decent optimizing compilers (GCC, clang, Visual Studio) will compile string_to_uint32("false"); ++// to the compile-time constant 1936482662. ++simdjson_inline uint32_t string_to_uint32(const char* str) { uint32_t val; std::memcpy(&val, str, sizeof(uint32_t)); return val; } ++ ++ ++// Again in str4ncmp we use a memcpy to avoid undefined behavior. The memcpy may appear expensive. ++// Yet all decent optimizing compilers will compile memcpy to a single instruction, just about. ++simdjson_warn_unused ++simdjson_inline uint32_t str4ncmp(const uint8_t *src, const char* atom) { ++ uint32_t srcval; // we want to avoid unaligned 32-bit loads (undefined in C/C++) ++ static_assert(sizeof(uint32_t) <= SIMDJSON_PADDING, "SIMDJSON_PADDING must be larger than 4 bytes"); ++ std::memcpy(&srcval, src, sizeof(uint32_t)); ++ return srcval ^ string_to_uint32(atom); ++} ++ ++simdjson_warn_unused ++simdjson_inline bool is_valid_true_atom(const uint8_t *src) { ++ return (str4ncmp(src, "true") | jsoncharutils::is_not_structural_or_whitespace(src[4])) == 0; ++} ++ ++simdjson_warn_unused ++simdjson_inline bool is_valid_true_atom(const uint8_t *src, size_t len) { ++ if (len > 4) { return is_valid_true_atom(src); } ++ else if (len == 4) { return !str4ncmp(src, "true"); } ++ else { return false; } ++} ++ ++simdjson_warn_unused ++simdjson_inline bool is_valid_false_atom(const uint8_t *src) { ++ return (str4ncmp(src+1, "alse") | jsoncharutils::is_not_structural_or_whitespace(src[5])) == 0; ++} ++ ++simdjson_warn_unused ++simdjson_inline bool is_valid_false_atom(const uint8_t *src, size_t len) { ++ if (len > 5) { return is_valid_false_atom(src); } ++ else if (len == 5) { return !str4ncmp(src+1, "alse"); } ++ else { return false; } ++} ++ ++simdjson_warn_unused ++simdjson_inline bool is_valid_null_atom(const uint8_t *src) { ++ return (str4ncmp(src, "null") | jsoncharutils::is_not_structural_or_whitespace(src[4])) == 0; ++} ++ ++simdjson_warn_unused ++simdjson_inline bool is_valid_null_atom(const uint8_t *src, size_t len) { ++ if (len > 4) { return is_valid_null_atom(src); } ++ else if (len == 4) { return !str4ncmp(src, "null"); } ++ else { return false; } ++} ++ ++} // namespace atomparsing ++} // unnamed namespace ++} // namespace ppc64 ++} // namespace simdjson ++/* end file include/simdjson/generic/atomparsing.h */ ++/* begin file include/simdjson/ppc64/stringparsing.h */ ++#ifndef SIMDJSON_PPC64_STRINGPARSING_H ++#define SIMDJSON_PPC64_STRINGPARSING_H ++ ++ ++namespace simdjson { ++namespace ppc64 { ++namespace { ++ ++using namespace simd; ++ ++// Holds backslashes and quotes locations. ++struct backslash_and_quote { ++public: ++ static constexpr uint32_t BYTES_PROCESSED = 32; ++ simdjson_inline static backslash_and_quote ++ copy_and_find(const uint8_t *src, uint8_t *dst); ++ ++ simdjson_inline bool has_quote_first() { ++ return ((bs_bits - 1) & quote_bits) != 0; ++ } ++ simdjson_inline bool has_backslash() { return bs_bits != 0; } ++ simdjson_inline int quote_index() { ++ return trailing_zeroes(quote_bits); ++ } ++ simdjson_inline int backslash_index() { ++ return trailing_zeroes(bs_bits); ++ } ++ ++ uint32_t bs_bits; ++ uint32_t quote_bits; ++}; // struct backslash_and_quote ++ ++simdjson_inline backslash_and_quote ++backslash_and_quote::copy_and_find(const uint8_t *src, uint8_t *dst) { ++ // this can read up to 31 bytes beyond the buffer size, but we require ++ // SIMDJSON_PADDING of padding ++ static_assert(SIMDJSON_PADDING >= (BYTES_PROCESSED - 1), ++ "backslash and quote finder must process fewer than " ++ "SIMDJSON_PADDING bytes"); ++ simd8 v0(src); ++ simd8 v1(src + sizeof(v0)); ++ v0.store(dst); ++ v1.store(dst + sizeof(v0)); ++ ++ // Getting a 64-bit bitmask is much cheaper than multiple 16-bit bitmasks on ++ // PPC; therefore, we smash them together into a 64-byte mask and get the ++ // bitmask from there. ++ uint64_t bs_and_quote = ++ simd8x64(v0 == '\\', v1 == '\\', v0 == '"', v1 == '"').to_bitmask(); ++ return { ++ uint32_t(bs_and_quote), // bs_bits ++ uint32_t(bs_and_quote >> 32) // quote_bits ++ }; ++} ++ ++} // unnamed namespace ++} // namespace ppc64 ++} // namespace simdjson ++ ++#endif // SIMDJSON_PPC64_STRINGPARSING_H ++/* end file include/simdjson/ppc64/stringparsing.h */ ++/* begin file include/simdjson/ppc64/numberparsing.h */ ++#ifndef SIMDJSON_PPC64_NUMBERPARSING_H ++#define SIMDJSON_PPC64_NUMBERPARSING_H ++ ++#if defined(__linux__) ++#include ++#elif defined(__FreeBSD__) ++#include ++#endif ++ ++namespace simdjson { ++namespace ppc64 { ++namespace { ++ ++// we don't have appropriate instructions, so let us use a scalar function ++// credit: https://johnnylee-sde.github.io/Fast-numeric-string-to-int/ ++static simdjson_inline uint32_t ++parse_eight_digits_unrolled(const uint8_t *chars) { ++ uint64_t val; ++ std::memcpy(&val, chars, sizeof(uint64_t)); ++#ifdef __BIG_ENDIAN__ ++#if defined(__linux__) ++ val = bswap_64(val); ++#elif defined(__FreeBSD__) ++ val = bswap64(val); ++#endif ++#endif ++ val = (val & 0x0F0F0F0F0F0F0F0F) * 2561 >> 8; ++ val = (val & 0x00FF00FF00FF00FF) * 6553601 >> 16; ++ return uint32_t((val & 0x0000FFFF0000FFFF) * 42949672960001 >> 32); ++} ++ ++} // unnamed namespace ++} // namespace ppc64 ++} // namespace simdjson ++ ++#define SIMDJSON_SWAR_NUMBER_PARSING 1 ++ ++/* begin file include/simdjson/generic/numberparsing.h */ ++#include ++ ++namespace simdjson { ++namespace ppc64 { ++ ++namespace ondemand { ++/** ++ * The type of a JSON number ++ */ ++enum class number_type { ++ floating_point_number=1, /// a binary64 number ++ signed_integer, /// a signed integer that fits in a 64-bit word using two's complement ++ unsigned_integer /// a positive integer larger or equal to 1<<63 ++}; ++} ++ ++namespace { ++/// @private ++namespace numberparsing { ++ ++ ++ ++#ifdef JSON_TEST_NUMBERS ++#define INVALID_NUMBER(SRC) (found_invalid_number((SRC)), NUMBER_ERROR) ++#define WRITE_INTEGER(VALUE, SRC, WRITER) (found_integer((VALUE), (SRC)), (WRITER).append_s64((VALUE))) ++#define WRITE_UNSIGNED(VALUE, SRC, WRITER) (found_unsigned_integer((VALUE), (SRC)), (WRITER).append_u64((VALUE))) ++#define WRITE_DOUBLE(VALUE, SRC, WRITER) (found_float((VALUE), (SRC)), (WRITER).append_double((VALUE))) ++#else ++#define INVALID_NUMBER(SRC) (NUMBER_ERROR) ++#define WRITE_INTEGER(VALUE, SRC, WRITER) (WRITER).append_s64((VALUE)) ++#define WRITE_UNSIGNED(VALUE, SRC, WRITER) (WRITER).append_u64((VALUE)) ++#define WRITE_DOUBLE(VALUE, SRC, WRITER) (WRITER).append_double((VALUE)) ++#endif ++ ++namespace { ++// Convert a mantissa, an exponent and a sign bit into an ieee64 double. ++// The real_exponent needs to be in [0, 2046] (technically real_exponent = 2047 would be acceptable). ++// The mantissa should be in [0,1<<53). The bit at index (1ULL << 52) while be zeroed. ++simdjson_inline double to_double(uint64_t mantissa, uint64_t real_exponent, bool negative) { ++ double d; ++ mantissa &= ~(1ULL << 52); ++ mantissa |= real_exponent << 52; ++ mantissa |= ((static_cast(negative)) << 63); ++ std::memcpy(&d, &mantissa, sizeof(d)); ++ return d; ++} ++} ++// Attempts to compute i * 10^(power) exactly; and if "negative" is ++// true, negate the result. ++// This function will only work in some cases, when it does not work, success is ++// set to false. This should work *most of the time* (like 99% of the time). ++// We assume that power is in the [smallest_power, ++// largest_power] interval: the caller is responsible for this check. ++simdjson_inline bool compute_float_64(int64_t power, uint64_t i, bool negative, double &d) { ++ // we start with a fast path ++ // It was described in ++ // Clinger WD. How to read floating point numbers accurately. ++ // ACM SIGPLAN Notices. 1990 ++#ifndef FLT_EVAL_METHOD ++#error "FLT_EVAL_METHOD should be defined, please include cfloat." ++#endif ++#if (FLT_EVAL_METHOD != 1) && (FLT_EVAL_METHOD != 0) ++ // We cannot be certain that x/y is rounded to nearest. ++ if (0 <= power && power <= 22 && i <= 9007199254740991) { ++#else ++ if (-22 <= power && power <= 22 && i <= 9007199254740991) { ++#endif ++ // convert the integer into a double. This is lossless since ++ // 0 <= i <= 2^53 - 1. ++ d = double(i); ++ // ++ // The general idea is as follows. ++ // If 0 <= s < 2^53 and if 10^0 <= p <= 10^22 then ++ // 1) Both s and p can be represented exactly as 64-bit floating-point ++ // values ++ // (binary64). ++ // 2) Because s and p can be represented exactly as floating-point values, ++ // then s * p ++ // and s / p will produce correctly rounded values. ++ // ++ if (power < 0) { ++ d = d / simdjson::internal::power_of_ten[-power]; ++ } else { ++ d = d * simdjson::internal::power_of_ten[power]; ++ } ++ if (negative) { ++ d = -d; ++ } ++ return true; ++ } ++ // When 22 < power && power < 22 + 16, we could ++ // hope for another, secondary fast path. It was ++ // described by David M. Gay in "Correctly rounded ++ // binary-decimal and decimal-binary conversions." (1990) ++ // If you need to compute i * 10^(22 + x) for x < 16, ++ // first compute i * 10^x, if you know that result is exact ++ // (e.g., when i * 10^x < 2^53), ++ // then you can still proceed and do (i * 10^x) * 10^22. ++ // Is this worth your time? ++ // You need 22 < power *and* power < 22 + 16 *and* (i * 10^(x-22) < 2^53) ++ // for this second fast path to work. ++ // If you you have 22 < power *and* power < 22 + 16, and then you ++ // optimistically compute "i * 10^(x-22)", there is still a chance that you ++ // have wasted your time if i * 10^(x-22) >= 2^53. It makes the use cases of ++ // this optimization maybe less common than we would like. Source: ++ // http://www.exploringbinary.com/fast-path-decimal-to-floating-point-conversion/ ++ // also used in RapidJSON: https://rapidjson.org/strtod_8h_source.html ++ ++ // The fast path has now failed, so we are failing back on the slower path. ++ ++ // In the slow path, we need to adjust i so that it is > 1<<63 which is always ++ // possible, except if i == 0, so we handle i == 0 separately. ++ if(i == 0) { ++ d = negative ? -0.0 : 0.0; ++ return true; ++ } ++ ++ ++ // The exponent is 1024 + 63 + power ++ // + floor(log(5**power)/log(2)). ++ // The 1024 comes from the ieee64 standard. ++ // The 63 comes from the fact that we use a 64-bit word. ++ // ++ // Computing floor(log(5**power)/log(2)) could be ++ // slow. Instead we use a fast function. ++ // ++ // For power in (-400,350), we have that ++ // (((152170 + 65536) * power ) >> 16); ++ // is equal to ++ // floor(log(5**power)/log(2)) + power when power >= 0 ++ // and it is equal to ++ // ceil(log(5**-power)/log(2)) + power when power < 0 ++ // ++ // The 65536 is (1<<16) and corresponds to ++ // (65536 * power) >> 16 ---> power ++ // ++ // ((152170 * power ) >> 16) is equal to ++ // floor(log(5**power)/log(2)) ++ // ++ // Note that this is not magic: 152170/(1<<16) is ++ // approximatively equal to log(5)/log(2). ++ // The 1<<16 value is a power of two; we could use a ++ // larger power of 2 if we wanted to. ++ // ++ int64_t exponent = (((152170 + 65536) * power) >> 16) + 1024 + 63; ++ ++ ++ // We want the most significant bit of i to be 1. Shift if needed. ++ int lz = leading_zeroes(i); ++ i <<= lz; ++ ++ ++ // We are going to need to do some 64-bit arithmetic to get a precise product. ++ // We use a table lookup approach. ++ // It is safe because ++ // power >= smallest_power ++ // and power <= largest_power ++ // We recover the mantissa of the power, it has a leading 1. It is always ++ // rounded down. ++ // ++ // We want the most significant 64 bits of the product. We know ++ // this will be non-zero because the most significant bit of i is ++ // 1. ++ const uint32_t index = 2 * uint32_t(power - simdjson::internal::smallest_power); ++ // Optimization: It may be that materializing the index as a variable might confuse some compilers and prevent effective complex-addressing loads. (Done for code clarity.) ++ // ++ // The full_multiplication function computes the 128-bit product of two 64-bit words ++ // with a returned value of type value128 with a "low component" corresponding to the ++ // 64-bit least significant bits of the product and with a "high component" corresponding ++ // to the 64-bit most significant bits of the product. ++ simdjson::internal::value128 firstproduct = jsoncharutils::full_multiplication(i, simdjson::internal::power_of_five_128[index]); ++ // Both i and power_of_five_128[index] have their most significant bit set to 1 which ++ // implies that the either the most or the second most significant bit of the product ++ // is 1. We pack values in this manner for efficiency reasons: it maximizes the use ++ // we make of the product. It also makes it easy to reason about the product: there ++ // is 0 or 1 leading zero in the product. ++ ++ // Unless the least significant 9 bits of the high (64-bit) part of the full ++ // product are all 1s, then we know that the most significant 55 bits are ++ // exact and no further work is needed. Having 55 bits is necessary because ++ // we need 53 bits for the mantissa but we have to have one rounding bit and ++ // we can waste a bit if the most significant bit of the product is zero. ++ if((firstproduct.high & 0x1FF) == 0x1FF) { ++ // We want to compute i * 5^q, but only care about the top 55 bits at most. ++ // Consider the scenario where q>=0. Then 5^q may not fit in 64-bits. Doing ++ // the full computation is wasteful. So we do what is called a "truncated ++ // multiplication". ++ // We take the most significant 64-bits, and we put them in ++ // power_of_five_128[index]. Usually, that's good enough to approximate i * 5^q ++ // to the desired approximation using one multiplication. Sometimes it does not suffice. ++ // Then we store the next most significant 64 bits in power_of_five_128[index + 1], and ++ // then we get a better approximation to i * 5^q. In very rare cases, even that ++ // will not suffice, though it is seemingly very hard to find such a scenario. ++ // ++ // That's for when q>=0. The logic for q<0 is somewhat similar but it is somewhat ++ // more complicated. ++ // ++ // There is an extra layer of complexity in that we need more than 55 bits of ++ // accuracy in the round-to-even scenario. ++ // ++ // The full_multiplication function computes the 128-bit product of two 64-bit words ++ // with a returned value of type value128 with a "low component" corresponding to the ++ // 64-bit least significant bits of the product and with a "high component" corresponding ++ // to the 64-bit most significant bits of the product. ++ simdjson::internal::value128 secondproduct = jsoncharutils::full_multiplication(i, simdjson::internal::power_of_five_128[index + 1]); ++ firstproduct.low += secondproduct.high; ++ if(secondproduct.high > firstproduct.low) { firstproduct.high++; } ++ // At this point, we might need to add at most one to firstproduct, but this ++ // can only change the value of firstproduct.high if firstproduct.low is maximal. ++ if(simdjson_unlikely(firstproduct.low == 0xFFFFFFFFFFFFFFFF)) { ++ // This is very unlikely, but if so, we need to do much more work! ++ return false; ++ } ++ } ++ uint64_t lower = firstproduct.low; ++ uint64_t upper = firstproduct.high; ++ // The final mantissa should be 53 bits with a leading 1. ++ // We shift it so that it occupies 54 bits with a leading 1. ++ /////// ++ uint64_t upperbit = upper >> 63; ++ uint64_t mantissa = upper >> (upperbit + 9); ++ lz += int(1 ^ upperbit); ++ ++ // Here we have mantissa < (1<<54). ++ int64_t real_exponent = exponent - lz; ++ if (simdjson_unlikely(real_exponent <= 0)) { // we have a subnormal? ++ // Here have that real_exponent <= 0 so -real_exponent >= 0 ++ if(-real_exponent + 1 >= 64) { // if we have more than 64 bits below the minimum exponent, you have a zero for sure. ++ d = negative ? -0.0 : 0.0; ++ return true; ++ } ++ // next line is safe because -real_exponent + 1 < 0 ++ mantissa >>= -real_exponent + 1; ++ // Thankfully, we can't have both "round-to-even" and subnormals because ++ // "round-to-even" only occurs for powers close to 0. ++ mantissa += (mantissa & 1); // round up ++ mantissa >>= 1; ++ // There is a weird scenario where we don't have a subnormal but just. ++ // Suppose we start with 2.2250738585072013e-308, we end up ++ // with 0x3fffffffffffff x 2^-1023-53 which is technically subnormal ++ // whereas 0x40000000000000 x 2^-1023-53 is normal. Now, we need to round ++ // up 0x3fffffffffffff x 2^-1023-53 and once we do, we are no longer ++ // subnormal, but we can only know this after rounding. ++ // So we only declare a subnormal if we are smaller than the threshold. ++ real_exponent = (mantissa < (uint64_t(1) << 52)) ? 0 : 1; ++ d = to_double(mantissa, real_exponent, negative); ++ return true; ++ } ++ // We have to round to even. The "to even" part ++ // is only a problem when we are right in between two floats ++ // which we guard against. ++ // If we have lots of trailing zeros, we may fall right between two ++ // floating-point values. ++ // ++ // The round-to-even cases take the form of a number 2m+1 which is in (2^53,2^54] ++ // times a power of two. That is, it is right between a number with binary significand ++ // m and another number with binary significand m+1; and it must be the case ++ // that it cannot be represented by a float itself. ++ // ++ // We must have that w * 10 ^q == (2m+1) * 2^p for some power of two 2^p. ++ // Recall that 10^q = 5^q * 2^q. ++ // When q >= 0, we must have that (2m+1) is divible by 5^q, so 5^q <= 2^54. We have that ++ // 5^23 <= 2^54 and it is the last power of five to qualify, so q <= 23. ++ // When q<0, we have w >= (2m+1) x 5^{-q}. We must have that w<2^{64} so ++ // (2m+1) x 5^{-q} < 2^{64}. We have that 2m+1>2^{53}. Hence, we must have ++ // 2^{53} x 5^{-q} < 2^{64}. ++ // Hence we have 5^{-q} < 2^{11}$ or q>= -4. ++ // ++ // We require lower <= 1 and not lower == 0 because we could not prove that ++ // that lower == 0 is implied; but we could prove that lower <= 1 is a necessary and sufficient test. ++ if (simdjson_unlikely((lower <= 1) && (power >= -4) && (power <= 23) && ((mantissa & 3) == 1))) { ++ if((mantissa << (upperbit + 64 - 53 - 2)) == upper) { ++ mantissa &= ~1; // flip it so that we do not round up ++ } ++ } ++ ++ mantissa += mantissa & 1; ++ mantissa >>= 1; ++ ++ // Here we have mantissa < (1<<53), unless there was an overflow ++ if (mantissa >= (1ULL << 53)) { ++ ////////// ++ // This will happen when parsing values such as 7.2057594037927933e+16 ++ //////// ++ mantissa = (1ULL << 52); ++ real_exponent++; ++ } ++ mantissa &= ~(1ULL << 52); ++ // we have to check that real_exponent is in range, otherwise we bail out ++ if (simdjson_unlikely(real_exponent > 2046)) { ++ // We have an infinite value!!! We could actually throw an error here if we could. ++ return false; ++ } ++ d = to_double(mantissa, real_exponent, negative); ++ return true; ++} ++ ++// We call a fallback floating-point parser that might be slow. Note ++// it will accept JSON numbers, but the JSON spec. is more restrictive so ++// before you call parse_float_fallback, you need to have validated the input ++// string with the JSON grammar. ++// It will return an error (false) if the parsed number is infinite. ++// The string parsing itself always succeeds. We know that there is at least ++// one digit. ++static bool parse_float_fallback(const uint8_t *ptr, double *outDouble) { ++ *outDouble = simdjson::internal::from_chars(reinterpret_cast(ptr)); ++ // We do not accept infinite values. ++ ++ // Detecting finite values in a portable manner is ridiculously hard, ideally ++ // we would want to do: ++ // return !std::isfinite(*outDouble); ++ // but that mysteriously fails under legacy/old libc++ libraries, see ++ // https://github.com/simdjson/simdjson/issues/1286 ++ // ++ // Therefore, fall back to this solution (the extra parens are there ++ // to handle that max may be a macro on windows). ++ return !(*outDouble > (std::numeric_limits::max)() || *outDouble < std::numeric_limits::lowest()); ++} ++static bool parse_float_fallback(const uint8_t *ptr, const uint8_t *end_ptr, double *outDouble) { ++ *outDouble = simdjson::internal::from_chars(reinterpret_cast(ptr), reinterpret_cast(end_ptr)); ++ // We do not accept infinite values. ++ ++ // Detecting finite values in a portable manner is ridiculously hard, ideally ++ // we would want to do: ++ // return !std::isfinite(*outDouble); ++ // but that mysteriously fails under legacy/old libc++ libraries, see ++ // https://github.com/simdjson/simdjson/issues/1286 ++ // ++ // Therefore, fall back to this solution (the extra parens are there ++ // to handle that max may be a macro on windows). ++ return !(*outDouble > (std::numeric_limits::max)() || *outDouble < std::numeric_limits::lowest()); ++} ++ ++// check quickly whether the next 8 chars are made of digits ++// at a glance, it looks better than Mula's ++// http://0x80.pl/articles/swar-digits-validate.html ++simdjson_inline bool is_made_of_eight_digits_fast(const uint8_t *chars) { ++ uint64_t val; ++ // this can read up to 7 bytes beyond the buffer size, but we require ++ // SIMDJSON_PADDING of padding ++ static_assert(7 <= SIMDJSON_PADDING, "SIMDJSON_PADDING must be bigger than 7"); ++ std::memcpy(&val, chars, 8); ++ // a branchy method might be faster: ++ // return (( val & 0xF0F0F0F0F0F0F0F0 ) == 0x3030303030303030) ++ // && (( (val + 0x0606060606060606) & 0xF0F0F0F0F0F0F0F0 ) == ++ // 0x3030303030303030); ++ return (((val & 0xF0F0F0F0F0F0F0F0) | ++ (((val + 0x0606060606060606) & 0xF0F0F0F0F0F0F0F0) >> 4)) == ++ 0x3333333333333333); ++} ++ ++template ++error_code slow_float_parsing(simdjson_unused const uint8_t * src, W writer) { ++ double d; ++ if (parse_float_fallback(src, &d)) { ++ writer.append_double(d); ++ return SUCCESS; ++ } ++ return INVALID_NUMBER(src); ++} ++ ++template ++SIMDJSON_NO_SANITIZE_UNDEFINED // We deliberately allow overflow here and check later ++simdjson_inline bool parse_digit(const uint8_t c, I &i) { ++ const uint8_t digit = static_cast(c - '0'); ++ if (digit > 9) { ++ return false; ++ } ++ // PERF NOTE: multiplication by 10 is cheaper than arbitrary integer multiplication ++ i = 10 * i + digit; // might overflow, we will handle the overflow later ++ return true; ++} ++ ++simdjson_inline error_code parse_decimal(simdjson_unused const uint8_t *const src, const uint8_t *&p, uint64_t &i, int64_t &exponent) { ++ // we continue with the fiction that we have an integer. If the ++ // floating point number is representable as x * 10^z for some integer ++ // z that fits in 53 bits, then we will be able to convert back the ++ // the integer into a float in a lossless manner. ++ const uint8_t *const first_after_period = p; ++ ++#ifdef SIMDJSON_SWAR_NUMBER_PARSING ++#if SIMDJSON_SWAR_NUMBER_PARSING ++ // this helps if we have lots of decimals! ++ // this turns out to be frequent enough. ++ if (is_made_of_eight_digits_fast(p)) { ++ i = i * 100000000 + parse_eight_digits_unrolled(p); ++ p += 8; ++ } ++#endif // SIMDJSON_SWAR_NUMBER_PARSING ++#endif // #ifdef SIMDJSON_SWAR_NUMBER_PARSING ++ // Unrolling the first digit makes a small difference on some implementations (e.g. westmere) ++ if (parse_digit(*p, i)) { ++p; } ++ while (parse_digit(*p, i)) { p++; } ++ exponent = first_after_period - p; ++ // Decimal without digits (123.) is illegal ++ if (exponent == 0) { ++ return INVALID_NUMBER(src); ++ } ++ return SUCCESS; ++} ++ ++simdjson_inline error_code parse_exponent(simdjson_unused const uint8_t *const src, const uint8_t *&p, int64_t &exponent) { ++ // Exp Sign: -123.456e[-]78 ++ bool neg_exp = ('-' == *p); ++ if (neg_exp || '+' == *p) { p++; } // Skip + as well ++ ++ // Exponent: -123.456e-[78] ++ auto start_exp = p; ++ int64_t exp_number = 0; ++ while (parse_digit(*p, exp_number)) { ++p; } ++ // It is possible for parse_digit to overflow. ++ // In particular, it could overflow to INT64_MIN, and we cannot do - INT64_MIN. ++ // Thus we *must* check for possible overflow before we negate exp_number. ++ ++ // Performance notes: it may seem like combining the two "simdjson_unlikely checks" below into ++ // a single simdjson_unlikely path would be faster. The reasoning is sound, but the compiler may ++ // not oblige and may, in fact, generate two distinct paths in any case. It might be ++ // possible to do uint64_t(p - start_exp - 1) >= 18 but it could end up trading off ++ // instructions for a simdjson_likely branch, an unconclusive gain. ++ ++ // If there were no digits, it's an error. ++ if (simdjson_unlikely(p == start_exp)) { ++ return INVALID_NUMBER(src); ++ } ++ // We have a valid positive exponent in exp_number at this point, except that ++ // it may have overflowed. ++ ++ // If there were more than 18 digits, we may have overflowed the integer. We have to do ++ // something!!!! ++ if (simdjson_unlikely(p > start_exp+18)) { ++ // Skip leading zeroes: 1e000000000000000000001 is technically valid and doesn't overflow ++ while (*start_exp == '0') { start_exp++; } ++ // 19 digits could overflow int64_t and is kind of absurd anyway. We don't ++ // support exponents smaller than -999,999,999,999,999,999 and bigger ++ // than 999,999,999,999,999,999. ++ // We can truncate. ++ // Note that 999999999999999999 is assuredly too large. The maximal ieee64 value before ++ // infinity is ~1.8e308. The smallest subnormal is ~5e-324. So, actually, we could ++ // truncate at 324. ++ // Note that there is no reason to fail per se at this point in time. ++ // E.g., 0e999999999999999999999 is a fine number. ++ if (p > start_exp+18) { exp_number = 999999999999999999; } ++ } ++ // At this point, we know that exp_number is a sane, positive, signed integer. ++ // It is <= 999,999,999,999,999,999. As long as 'exponent' is in ++ // [-8223372036854775808, 8223372036854775808], we won't overflow. Because 'exponent' ++ // is bounded in magnitude by the size of the JSON input, we are fine in this universe. ++ // To sum it up: the next line should never overflow. ++ exponent += (neg_exp ? -exp_number : exp_number); ++ return SUCCESS; ++} ++ ++simdjson_inline size_t significant_digits(const uint8_t * start_digits, size_t digit_count) { ++ // It is possible that the integer had an overflow. ++ // We have to handle the case where we have 0.0000somenumber. ++ const uint8_t *start = start_digits; ++ while ((*start == '0') || (*start == '.')) { ++start; } ++ // we over-decrement by one when there is a '.' ++ return digit_count - size_t(start - start_digits); ++} ++ ++template ++simdjson_inline error_code write_float(const uint8_t *const src, bool negative, uint64_t i, const uint8_t * start_digits, size_t digit_count, int64_t exponent, W &writer) { ++ // If we frequently had to deal with long strings of digits, ++ // we could extend our code by using a 128-bit integer instead ++ // of a 64-bit integer. However, this is uncommon in practice. ++ // ++ // 9999999999999999999 < 2**64 so we can accommodate 19 digits. ++ // If we have a decimal separator, then digit_count - 1 is the number of digits, but we ++ // may not have a decimal separator! ++ if (simdjson_unlikely(digit_count > 19 && significant_digits(start_digits, digit_count) > 19)) { ++ // Ok, chances are good that we had an overflow! ++ // this is almost never going to get called!!! ++ // we start anew, going slowly!!! ++ // This will happen in the following examples: ++ // 10000000000000000000000000000000000000000000e+308 ++ // 3.1415926535897932384626433832795028841971693993751 ++ // ++ // NOTE: This makes a *copy* of the writer and passes it to slow_float_parsing. This happens ++ // because slow_float_parsing is a non-inlined function. If we passed our writer reference to ++ // it, it would force it to be stored in memory, preventing the compiler from picking it apart ++ // and putting into registers. i.e. if we pass it as reference, it gets slow. ++ // This is what forces the skip_double, as well. ++ error_code error = slow_float_parsing(src, writer); ++ writer.skip_double(); ++ return error; ++ } ++ // NOTE: it's weird that the simdjson_unlikely() only wraps half the if, but it seems to get slower any other ++ // way we've tried: https://github.com/simdjson/simdjson/pull/990#discussion_r448497331 ++ // To future reader: we'd love if someone found a better way, or at least could explain this result! ++ if (simdjson_unlikely(exponent < simdjson::internal::smallest_power) || (exponent > simdjson::internal::largest_power)) { ++ // ++ // Important: smallest_power is such that it leads to a zero value. ++ // Observe that 18446744073709551615e-343 == 0, i.e. (2**64 - 1) e -343 is zero ++ // so something x 10^-343 goes to zero, but not so with something x 10^-342. ++ static_assert(simdjson::internal::smallest_power <= -342, "smallest_power is not small enough"); ++ // ++ if((exponent < simdjson::internal::smallest_power) || (i == 0)) { ++ // E.g. Parse "-0.0e-999" into the same value as "-0.0". See https://en.wikipedia.org/wiki/Signed_zero ++ WRITE_DOUBLE(negative ? -0.0 : 0.0, src, writer); ++ return SUCCESS; ++ } else { // (exponent > largest_power) and (i != 0) ++ // We have, for sure, an infinite value and simdjson refuses to parse infinite values. ++ return INVALID_NUMBER(src); ++ } ++ } ++ double d; ++ if (!compute_float_64(exponent, i, negative, d)) { ++ // we are almost never going to get here. ++ if (!parse_float_fallback(src, &d)) { return INVALID_NUMBER(src); } ++ } ++ WRITE_DOUBLE(d, src, writer); ++ return SUCCESS; ++} ++ ++// for performance analysis, it is sometimes useful to skip parsing ++#ifdef SIMDJSON_SKIPNUMBERPARSING ++ ++template ++simdjson_inline error_code parse_number(const uint8_t *const, W &writer) { ++ writer.append_s64(0); // always write zero ++ return SUCCESS; // always succeeds ++} ++ ++simdjson_unused simdjson_inline simdjson_result parse_unsigned(const uint8_t * const src) noexcept { return 0; } ++simdjson_unused simdjson_inline simdjson_result parse_integer(const uint8_t * const src) noexcept { return 0; } ++simdjson_unused simdjson_inline simdjson_result parse_double(const uint8_t * const src) noexcept { return 0; } ++simdjson_unused simdjson_inline simdjson_result parse_unsigned_in_string(const uint8_t * const src) noexcept { return 0; } ++simdjson_unused simdjson_inline simdjson_result parse_integer_in_string(const uint8_t * const src) noexcept { return 0; } ++simdjson_unused simdjson_inline simdjson_result parse_double_in_string(const uint8_t * const src) noexcept { return 0; } ++simdjson_unused simdjson_inline bool is_negative(const uint8_t * src) noexcept { return false; } ++simdjson_unused simdjson_inline simdjson_result is_integer(const uint8_t * src) noexcept { return false; } ++simdjson_unused simdjson_inline simdjson_result get_number_type(const uint8_t * src) noexcept { return ondemand::number_type::signed_integer; } ++#else ++ ++// parse the number at src ++// define JSON_TEST_NUMBERS for unit testing ++// ++// It is assumed that the number is followed by a structural ({,},],[) character ++// or a white space character. If that is not the case (e.g., when the JSON ++// document is made of a single number), then it is necessary to copy the ++// content and append a space before calling this function. ++// ++// Our objective is accurate parsing (ULP of 0) at high speed. ++template ++simdjson_inline error_code parse_number(const uint8_t *const src, W &writer) { ++ ++ // ++ // Check for minus sign ++ // ++ bool negative = (*src == '-'); ++ const uint8_t *p = src + uint8_t(negative); ++ ++ // ++ // Parse the integer part. ++ // ++ // PERF NOTE: we don't use is_made_of_eight_digits_fast because large integers like 123456789 are rare ++ const uint8_t *const start_digits = p; ++ uint64_t i = 0; ++ while (parse_digit(*p, i)) { p++; } ++ ++ // If there were no digits, or if the integer starts with 0 and has more than one digit, it's an error. ++ // Optimization note: size_t is expected to be unsigned. ++ size_t digit_count = size_t(p - start_digits); ++ if (digit_count == 0 || ('0' == *start_digits && digit_count > 1)) { return INVALID_NUMBER(src); } ++ ++ // ++ // Handle floats if there is a . or e (or both) ++ // ++ int64_t exponent = 0; ++ bool is_float = false; ++ if ('.' == *p) { ++ is_float = true; ++ ++p; ++ SIMDJSON_TRY( parse_decimal(src, p, i, exponent) ); ++ digit_count = int(p - start_digits); // used later to guard against overflows ++ } ++ if (('e' == *p) || ('E' == *p)) { ++ is_float = true; ++ ++p; ++ SIMDJSON_TRY( parse_exponent(src, p, exponent) ); ++ } ++ if (is_float) { ++ const bool dirty_end = jsoncharutils::is_not_structural_or_whitespace(*p); ++ SIMDJSON_TRY( write_float(src, negative, i, start_digits, digit_count, exponent, writer) ); ++ if (dirty_end) { return INVALID_NUMBER(src); } ++ return SUCCESS; ++ } ++ ++ // The longest negative 64-bit number is 19 digits. ++ // The longest positive 64-bit number is 20 digits. ++ // We do it this way so we don't trigger this branch unless we must. ++ size_t longest_digit_count = negative ? 19 : 20; ++ if (digit_count > longest_digit_count) { return INVALID_NUMBER(src); } ++ if (digit_count == longest_digit_count) { ++ if (negative) { ++ // Anything negative above INT64_MAX+1 is invalid ++ if (i > uint64_t(INT64_MAX)+1) { return INVALID_NUMBER(src); } ++ WRITE_INTEGER(~i+1, src, writer); ++ if (jsoncharutils::is_not_structural_or_whitespace(*p)) { return INVALID_NUMBER(src); } ++ return SUCCESS; ++ // Positive overflow check: ++ // - A 20 digit number starting with 2-9 is overflow, because 18,446,744,073,709,551,615 is the ++ // biggest uint64_t. ++ // - A 20 digit number starting with 1 is overflow if it is less than INT64_MAX. ++ // If we got here, it's a 20 digit number starting with the digit "1". ++ // - If a 20 digit number starting with 1 overflowed (i*10+digit), the result will be smaller ++ // than 1,553,255,926,290,448,384. ++ // - That is smaller than the smallest possible 20-digit number the user could write: ++ // 10,000,000,000,000,000,000. ++ // - Therefore, if the number is positive and lower than that, it's overflow. ++ // - The value we are looking at is less than or equal to INT64_MAX. ++ // ++ } else if (src[0] != uint8_t('1') || i <= uint64_t(INT64_MAX)) { return INVALID_NUMBER(src); } ++ } ++ ++ // Write unsigned if it doesn't fit in a signed integer. ++ if (i > uint64_t(INT64_MAX)) { ++ WRITE_UNSIGNED(i, src, writer); ++ } else { ++ WRITE_INTEGER(negative ? (~i+1) : i, src, writer); ++ } ++ if (jsoncharutils::is_not_structural_or_whitespace(*p)) { return INVALID_NUMBER(src); } ++ return SUCCESS; ++} ++ ++// Inlineable functions ++namespace { ++ ++// This table can be used to characterize the final character of an integer ++// string. For JSON structural character and allowable white space characters, ++// we return SUCCESS. For 'e', '.' and 'E', we return INCORRECT_TYPE. Otherwise ++// we return NUMBER_ERROR. ++// Optimization note: we could easily reduce the size of the table by half (to 128) ++// at the cost of an extra branch. ++// Optimization note: we want the values to use at most 8 bits (not, e.g., 32 bits): ++static_assert(error_code(uint8_t(NUMBER_ERROR))== NUMBER_ERROR, "bad NUMBER_ERROR cast"); ++static_assert(error_code(uint8_t(SUCCESS))== SUCCESS, "bad NUMBER_ERROR cast"); ++static_assert(error_code(uint8_t(INCORRECT_TYPE))== INCORRECT_TYPE, "bad NUMBER_ERROR cast"); ++ ++const uint8_t integer_string_finisher[256] = { ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, SUCCESS, ++ SUCCESS, NUMBER_ERROR, NUMBER_ERROR, SUCCESS, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, SUCCESS, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, SUCCESS, ++ NUMBER_ERROR, INCORRECT_TYPE, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, SUCCESS, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, INCORRECT_TYPE, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, SUCCESS, NUMBER_ERROR, SUCCESS, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, INCORRECT_TYPE, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, SUCCESS, NUMBER_ERROR, ++ SUCCESS, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR}; ++ ++// Parse any number from 0 to 18,446,744,073,709,551,615 ++simdjson_unused simdjson_inline simdjson_result parse_unsigned(const uint8_t * const src) noexcept { ++ const uint8_t *p = src; ++ // ++ // Parse the integer part. ++ // ++ // PERF NOTE: we don't use is_made_of_eight_digits_fast because large integers like 123456789 are rare ++ const uint8_t *const start_digits = p; ++ uint64_t i = 0; ++ while (parse_digit(*p, i)) { p++; } ++ ++ // If there were no digits, or if the integer starts with 0 and has more than one digit, it's an error. ++ // Optimization note: size_t is expected to be unsigned. ++ size_t digit_count = size_t(p - start_digits); ++ // The longest positive 64-bit number is 20 digits. ++ // We do it this way so we don't trigger this branch unless we must. ++ // Optimization note: the compiler can probably merge ++ // ((digit_count == 0) || (digit_count > 20)) ++ // into a single branch since digit_count is unsigned. ++ if ((digit_count == 0) || (digit_count > 20)) { return INCORRECT_TYPE; } ++ // Here digit_count > 0. ++ if (('0' == *start_digits) && (digit_count > 1)) { return NUMBER_ERROR; } ++ // We can do the following... ++ // if (!jsoncharutils::is_structural_or_whitespace(*p)) { ++ // return (*p == '.' || *p == 'e' || *p == 'E') ? INCORRECT_TYPE : NUMBER_ERROR; ++ // } ++ // as a single table lookup: ++ if (integer_string_finisher[*p] != SUCCESS) { return error_code(integer_string_finisher[*p]); } ++ ++ if (digit_count == 20) { ++ // Positive overflow check: ++ // - A 20 digit number starting with 2-9 is overflow, because 18,446,744,073,709,551,615 is the ++ // biggest uint64_t. ++ // - A 20 digit number starting with 1 is overflow if it is less than INT64_MAX. ++ // If we got here, it's a 20 digit number starting with the digit "1". ++ // - If a 20 digit number starting with 1 overflowed (i*10+digit), the result will be smaller ++ // than 1,553,255,926,290,448,384. ++ // - That is smaller than the smallest possible 20-digit number the user could write: ++ // 10,000,000,000,000,000,000. ++ // - Therefore, if the number is positive and lower than that, it's overflow. ++ // - The value we are looking at is less than or equal to INT64_MAX. ++ // ++ if (src[0] != uint8_t('1') || i <= uint64_t(INT64_MAX)) { return INCORRECT_TYPE; } ++ } ++ ++ return i; ++} ++ ++ ++// Parse any number from 0 to 18,446,744,073,709,551,615 ++// Never read at src_end or beyond ++simdjson_unused simdjson_inline simdjson_result parse_unsigned(const uint8_t * const src, const uint8_t * const src_end) noexcept { ++ const uint8_t *p = src; ++ // ++ // Parse the integer part. ++ // ++ // PERF NOTE: we don't use is_made_of_eight_digits_fast because large integers like 123456789 are rare ++ const uint8_t *const start_digits = p; ++ uint64_t i = 0; ++ while ((p != src_end) && parse_digit(*p, i)) { p++; } ++ ++ // If there were no digits, or if the integer starts with 0 and has more than one digit, it's an error. ++ // Optimization note: size_t is expected to be unsigned. ++ size_t digit_count = size_t(p - start_digits); ++ // The longest positive 64-bit number is 20 digits. ++ // We do it this way so we don't trigger this branch unless we must. ++ // Optimization note: the compiler can probably merge ++ // ((digit_count == 0) || (digit_count > 20)) ++ // into a single branch since digit_count is unsigned. ++ if ((digit_count == 0) || (digit_count > 20)) { return INCORRECT_TYPE; } ++ // Here digit_count > 0. ++ if (('0' == *start_digits) && (digit_count > 1)) { return NUMBER_ERROR; } ++ // We can do the following... ++ // if (!jsoncharutils::is_structural_or_whitespace(*p)) { ++ // return (*p == '.' || *p == 'e' || *p == 'E') ? INCORRECT_TYPE : NUMBER_ERROR; ++ // } ++ // as a single table lookup: ++ if ((p != src_end) && integer_string_finisher[*p] != SUCCESS) { return error_code(integer_string_finisher[*p]); } ++ ++ if (digit_count == 20) { ++ // Positive overflow check: ++ // - A 20 digit number starting with 2-9 is overflow, because 18,446,744,073,709,551,615 is the ++ // biggest uint64_t. ++ // - A 20 digit number starting with 1 is overflow if it is less than INT64_MAX. ++ // If we got here, it's a 20 digit number starting with the digit "1". ++ // - If a 20 digit number starting with 1 overflowed (i*10+digit), the result will be smaller ++ // than 1,553,255,926,290,448,384. ++ // - That is smaller than the smallest possible 20-digit number the user could write: ++ // 10,000,000,000,000,000,000. ++ // - Therefore, if the number is positive and lower than that, it's overflow. ++ // - The value we are looking at is less than or equal to INT64_MAX. ++ // ++ if (src[0] != uint8_t('1') || i <= uint64_t(INT64_MAX)) { return INCORRECT_TYPE; } ++ } ++ ++ return i; ++} ++ ++// Parse any number from 0 to 18,446,744,073,709,551,615 ++simdjson_unused simdjson_inline simdjson_result parse_unsigned_in_string(const uint8_t * const src) noexcept { ++ const uint8_t *p = src + 1; ++ // ++ // Parse the integer part. ++ // ++ // PERF NOTE: we don't use is_made_of_eight_digits_fast because large integers like 123456789 are rare ++ const uint8_t *const start_digits = p; ++ uint64_t i = 0; ++ while (parse_digit(*p, i)) { p++; } ++ ++ // If there were no digits, or if the integer starts with 0 and has more than one digit, it's an error. ++ // Optimization note: size_t is expected to be unsigned. ++ size_t digit_count = size_t(p - start_digits); ++ // The longest positive 64-bit number is 20 digits. ++ // We do it this way so we don't trigger this branch unless we must. ++ // Optimization note: the compiler can probably merge ++ // ((digit_count == 0) || (digit_count > 20)) ++ // into a single branch since digit_count is unsigned. ++ if ((digit_count == 0) || (digit_count > 20)) { return INCORRECT_TYPE; } ++ // Here digit_count > 0. ++ if (('0' == *start_digits) && (digit_count > 1)) { return NUMBER_ERROR; } ++ // We can do the following... ++ // if (!jsoncharutils::is_structural_or_whitespace(*p)) { ++ // return (*p == '.' || *p == 'e' || *p == 'E') ? INCORRECT_TYPE : NUMBER_ERROR; ++ // } ++ // as a single table lookup: ++ if (*p != '"') { return NUMBER_ERROR; } ++ ++ if (digit_count == 20) { ++ // Positive overflow check: ++ // - A 20 digit number starting with 2-9 is overflow, because 18,446,744,073,709,551,615 is the ++ // biggest uint64_t. ++ // - A 20 digit number starting with 1 is overflow if it is less than INT64_MAX. ++ // If we got here, it's a 20 digit number starting with the digit "1". ++ // - If a 20 digit number starting with 1 overflowed (i*10+digit), the result will be smaller ++ // than 1,553,255,926,290,448,384. ++ // - That is smaller than the smallest possible 20-digit number the user could write: ++ // 10,000,000,000,000,000,000. ++ // - Therefore, if the number is positive and lower than that, it's overflow. ++ // - The value we are looking at is less than or equal to INT64_MAX. ++ // ++ // Note: we use src[1] and not src[0] because src[0] is the quote character in this ++ // instance. ++ if (src[1] != uint8_t('1') || i <= uint64_t(INT64_MAX)) { return INCORRECT_TYPE; } ++ } ++ ++ return i; ++} ++ ++// Parse any number from -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 ++simdjson_unused simdjson_inline simdjson_result parse_integer(const uint8_t *src) noexcept { ++ // ++ // Check for minus sign ++ // ++ bool negative = (*src == '-'); ++ const uint8_t *p = src + uint8_t(negative); ++ ++ // ++ // Parse the integer part. ++ // ++ // PERF NOTE: we don't use is_made_of_eight_digits_fast because large integers like 123456789 are rare ++ const uint8_t *const start_digits = p; ++ uint64_t i = 0; ++ while (parse_digit(*p, i)) { p++; } ++ ++ // If there were no digits, or if the integer starts with 0 and has more than one digit, it's an error. ++ // Optimization note: size_t is expected to be unsigned. ++ size_t digit_count = size_t(p - start_digits); ++ // We go from ++ // -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 ++ // so we can never represent numbers that have more than 19 digits. ++ size_t longest_digit_count = 19; ++ // Optimization note: the compiler can probably merge ++ // ((digit_count == 0) || (digit_count > longest_digit_count)) ++ // into a single branch since digit_count is unsigned. ++ if ((digit_count == 0) || (digit_count > longest_digit_count)) { return INCORRECT_TYPE; } ++ // Here digit_count > 0. ++ if (('0' == *start_digits) && (digit_count > 1)) { return NUMBER_ERROR; } ++ // We can do the following... ++ // if (!jsoncharutils::is_structural_or_whitespace(*p)) { ++ // return (*p == '.' || *p == 'e' || *p == 'E') ? INCORRECT_TYPE : NUMBER_ERROR; ++ // } ++ // as a single table lookup: ++ if(integer_string_finisher[*p] != SUCCESS) { return error_code(integer_string_finisher[*p]); } ++ // Negative numbers have can go down to - INT64_MAX - 1 whereas positive numbers are limited to INT64_MAX. ++ // Performance note: This check is only needed when digit_count == longest_digit_count but it is ++ // so cheap that we might as well always make it. ++ if(i > uint64_t(INT64_MAX) + uint64_t(negative)) { return INCORRECT_TYPE; } ++ return negative ? (~i+1) : i; ++} ++ ++// Parse any number from -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 ++// Never read at src_end or beyond ++simdjson_unused simdjson_inline simdjson_result parse_integer(const uint8_t * const src, const uint8_t * const src_end) noexcept { ++ // ++ // Check for minus sign ++ // ++ if(src == src_end) { return NUMBER_ERROR; } ++ bool negative = (*src == '-'); ++ const uint8_t *p = src + uint8_t(negative); ++ ++ // ++ // Parse the integer part. ++ // ++ // PERF NOTE: we don't use is_made_of_eight_digits_fast because large integers like 123456789 are rare ++ const uint8_t *const start_digits = p; ++ uint64_t i = 0; ++ while ((p != src_end) && parse_digit(*p, i)) { p++; } ++ ++ // If there were no digits, or if the integer starts with 0 and has more than one digit, it's an error. ++ // Optimization note: size_t is expected to be unsigned. ++ size_t digit_count = size_t(p - start_digits); ++ // We go from ++ // -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 ++ // so we can never represent numbers that have more than 19 digits. ++ size_t longest_digit_count = 19; ++ // Optimization note: the compiler can probably merge ++ // ((digit_count == 0) || (digit_count > longest_digit_count)) ++ // into a single branch since digit_count is unsigned. ++ if ((digit_count == 0) || (digit_count > longest_digit_count)) { return INCORRECT_TYPE; } ++ // Here digit_count > 0. ++ if (('0' == *start_digits) && (digit_count > 1)) { return NUMBER_ERROR; } ++ // We can do the following... ++ // if (!jsoncharutils::is_structural_or_whitespace(*p)) { ++ // return (*p == '.' || *p == 'e' || *p == 'E') ? INCORRECT_TYPE : NUMBER_ERROR; ++ // } ++ // as a single table lookup: ++ if((p != src_end) && integer_string_finisher[*p] != SUCCESS) { return error_code(integer_string_finisher[*p]); } ++ // Negative numbers have can go down to - INT64_MAX - 1 whereas positive numbers are limited to INT64_MAX. ++ // Performance note: This check is only needed when digit_count == longest_digit_count but it is ++ // so cheap that we might as well always make it. ++ if(i > uint64_t(INT64_MAX) + uint64_t(negative)) { return INCORRECT_TYPE; } ++ return negative ? (~i+1) : i; ++} ++ ++// Parse any number from -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 ++simdjson_unused simdjson_inline simdjson_result parse_integer_in_string(const uint8_t *src) noexcept { ++ // ++ // Check for minus sign ++ // ++ bool negative = (*(src + 1) == '-'); ++ src += uint8_t(negative) + 1; ++ ++ // ++ // Parse the integer part. ++ // ++ // PERF NOTE: we don't use is_made_of_eight_digits_fast because large integers like 123456789 are rare ++ const uint8_t *const start_digits = src; ++ uint64_t i = 0; ++ while (parse_digit(*src, i)) { src++; } ++ ++ // If there were no digits, or if the integer starts with 0 and has more than one digit, it's an error. ++ // Optimization note: size_t is expected to be unsigned. ++ size_t digit_count = size_t(src - start_digits); ++ // We go from ++ // -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 ++ // so we can never represent numbers that have more than 19 digits. ++ size_t longest_digit_count = 19; ++ // Optimization note: the compiler can probably merge ++ // ((digit_count == 0) || (digit_count > longest_digit_count)) ++ // into a single branch since digit_count is unsigned. ++ if ((digit_count == 0) || (digit_count > longest_digit_count)) { return INCORRECT_TYPE; } ++ // Here digit_count > 0. ++ if (('0' == *start_digits) && (digit_count > 1)) { return NUMBER_ERROR; } ++ // We can do the following... ++ // if (!jsoncharutils::is_structural_or_whitespace(*src)) { ++ // return (*src == '.' || *src == 'e' || *src == 'E') ? INCORRECT_TYPE : NUMBER_ERROR; ++ // } ++ // as a single table lookup: ++ if(*src != '"') { return NUMBER_ERROR; } ++ // Negative numbers have can go down to - INT64_MAX - 1 whereas positive numbers are limited to INT64_MAX. ++ // Performance note: This check is only needed when digit_count == longest_digit_count but it is ++ // so cheap that we might as well always make it. ++ if(i > uint64_t(INT64_MAX) + uint64_t(negative)) { return INCORRECT_TYPE; } ++ return negative ? (~i+1) : i; ++} ++ ++simdjson_unused simdjson_inline simdjson_result parse_double(const uint8_t * src) noexcept { ++ // ++ // Check for minus sign ++ // ++ bool negative = (*src == '-'); ++ src += uint8_t(negative); ++ ++ // ++ // Parse the integer part. ++ // ++ uint64_t i = 0; ++ const uint8_t *p = src; ++ p += parse_digit(*p, i); ++ bool leading_zero = (i == 0); ++ while (parse_digit(*p, i)) { p++; } ++ // no integer digits, or 0123 (zero must be solo) ++ if ( p == src ) { return INCORRECT_TYPE; } ++ if ( (leading_zero && p != src+1)) { return NUMBER_ERROR; } ++ ++ // ++ // Parse the decimal part. ++ // ++ int64_t exponent = 0; ++ bool overflow; ++ if (simdjson_likely(*p == '.')) { ++ p++; ++ const uint8_t *start_decimal_digits = p; ++ if (!parse_digit(*p, i)) { return NUMBER_ERROR; } // no decimal digits ++ p++; ++ while (parse_digit(*p, i)) { p++; } ++ exponent = -(p - start_decimal_digits); ++ ++ // Overflow check. More than 19 digits (minus the decimal) may be overflow. ++ overflow = p-src-1 > 19; ++ if (simdjson_unlikely(overflow && leading_zero)) { ++ // Skip leading 0.00000 and see if it still overflows ++ const uint8_t *start_digits = src + 2; ++ while (*start_digits == '0') { start_digits++; } ++ overflow = start_digits-src > 19; ++ } ++ } else { ++ overflow = p-src > 19; ++ } ++ ++ // ++ // Parse the exponent ++ // ++ if (*p == 'e' || *p == 'E') { ++ p++; ++ bool exp_neg = *p == '-'; ++ p += exp_neg || *p == '+'; ++ ++ uint64_t exp = 0; ++ const uint8_t *start_exp_digits = p; ++ while (parse_digit(*p, exp)) { p++; } ++ // no exp digits, or 20+ exp digits ++ if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } ++ ++ exponent += exp_neg ? 0-exp : exp; ++ } ++ ++ if (jsoncharutils::is_not_structural_or_whitespace(*p)) { return NUMBER_ERROR; } ++ ++ overflow = overflow || exponent < simdjson::internal::smallest_power || exponent > simdjson::internal::largest_power; ++ ++ // ++ // Assemble (or slow-parse) the float ++ // ++ double d; ++ if (simdjson_likely(!overflow)) { ++ if (compute_float_64(exponent, i, negative, d)) { return d; } ++ } ++ if (!parse_float_fallback(src - uint8_t(negative), &d)) { ++ return NUMBER_ERROR; ++ } ++ return d; ++} ++ ++simdjson_unused simdjson_inline bool is_negative(const uint8_t * src) noexcept { ++ return (*src == '-'); ++} ++ ++simdjson_unused simdjson_inline simdjson_result is_integer(const uint8_t * src) noexcept { ++ bool negative = (*src == '-'); ++ src += uint8_t(negative); ++ const uint8_t *p = src; ++ while(static_cast(*p - '0') <= 9) { p++; } ++ if ( p == src ) { return NUMBER_ERROR; } ++ if (jsoncharutils::is_structural_or_whitespace(*p)) { return true; } ++ return false; ++} ++ ++simdjson_unused simdjson_inline simdjson_result get_number_type(const uint8_t * src) noexcept { ++ bool negative = (*src == '-'); ++ src += uint8_t(negative); ++ const uint8_t *p = src; ++ while(static_cast(*p - '0') <= 9) { p++; } ++ if ( p == src ) { return NUMBER_ERROR; } ++ if (jsoncharutils::is_structural_or_whitespace(*p)) { ++ // We have an integer. ++ // If the number is negative and valid, it must be a signed integer. ++ if(negative) { return ondemand::number_type::signed_integer; } ++ // We want values larger or equal to 9223372036854775808 to be unsigned ++ // integers, and the other values to be signed integers. ++ int digit_count = int(p - src); ++ if(digit_count >= 19) { ++ const uint8_t * smaller_big_integer = reinterpret_cast("9223372036854775808"); ++ if((digit_count >= 20) || (memcmp(src, smaller_big_integer, 19) >= 0)) { ++ return ondemand::number_type::unsigned_integer; ++ } ++ } ++ return ondemand::number_type::signed_integer; ++ } ++ // Hopefully, we have 'e' or 'E' or '.'. ++ return ondemand::number_type::floating_point_number; ++} ++ ++// Never read at src_end or beyond ++simdjson_unused simdjson_inline simdjson_result parse_double(const uint8_t * src, const uint8_t * const src_end) noexcept { ++ if(src == src_end) { return NUMBER_ERROR; } ++ // ++ // Check for minus sign ++ // ++ bool negative = (*src == '-'); ++ src += uint8_t(negative); ++ ++ // ++ // Parse the integer part. ++ // ++ uint64_t i = 0; ++ const uint8_t *p = src; ++ if(p == src_end) { return NUMBER_ERROR; } ++ p += parse_digit(*p, i); ++ bool leading_zero = (i == 0); ++ while ((p != src_end) && parse_digit(*p, i)) { p++; } ++ // no integer digits, or 0123 (zero must be solo) ++ if ( p == src ) { return INCORRECT_TYPE; } ++ if ( (leading_zero && p != src+1)) { return NUMBER_ERROR; } ++ ++ // ++ // Parse the decimal part. ++ // ++ int64_t exponent = 0; ++ bool overflow; ++ if (simdjson_likely((p != src_end) && (*p == '.'))) { ++ p++; ++ const uint8_t *start_decimal_digits = p; ++ if ((p == src_end) || !parse_digit(*p, i)) { return NUMBER_ERROR; } // no decimal digits ++ p++; ++ while ((p != src_end) && parse_digit(*p, i)) { p++; } ++ exponent = -(p - start_decimal_digits); ++ ++ // Overflow check. More than 19 digits (minus the decimal) may be overflow. ++ overflow = p-src-1 > 19; ++ if (simdjson_unlikely(overflow && leading_zero)) { ++ // Skip leading 0.00000 and see if it still overflows ++ const uint8_t *start_digits = src + 2; ++ while (*start_digits == '0') { start_digits++; } ++ overflow = start_digits-src > 19; ++ } ++ } else { ++ overflow = p-src > 19; ++ } ++ ++ // ++ // Parse the exponent ++ // ++ if ((p != src_end) && (*p == 'e' || *p == 'E')) { ++ p++; ++ if(p == src_end) { return NUMBER_ERROR; } ++ bool exp_neg = *p == '-'; ++ p += exp_neg || *p == '+'; ++ ++ uint64_t exp = 0; ++ const uint8_t *start_exp_digits = p; ++ while ((p != src_end) && parse_digit(*p, exp)) { p++; } ++ // no exp digits, or 20+ exp digits ++ if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } ++ ++ exponent += exp_neg ? 0-exp : exp; ++ } ++ ++ if ((p != src_end) && jsoncharutils::is_not_structural_or_whitespace(*p)) { return NUMBER_ERROR; } ++ ++ overflow = overflow || exponent < simdjson::internal::smallest_power || exponent > simdjson::internal::largest_power; ++ ++ // ++ // Assemble (or slow-parse) the float ++ // ++ double d; ++ if (simdjson_likely(!overflow)) { ++ if (compute_float_64(exponent, i, negative, d)) { return d; } ++ } ++ if (!parse_float_fallback(src - uint8_t(negative), src_end, &d)) { ++ return NUMBER_ERROR; ++ } ++ return d; ++} ++ ++simdjson_unused simdjson_inline simdjson_result parse_double_in_string(const uint8_t * src) noexcept { ++ // ++ // Check for minus sign ++ // ++ bool negative = (*(src + 1) == '-'); ++ src += uint8_t(negative) + 1; ++ ++ // ++ // Parse the integer part. ++ // ++ uint64_t i = 0; ++ const uint8_t *p = src; ++ p += parse_digit(*p, i); ++ bool leading_zero = (i == 0); ++ while (parse_digit(*p, i)) { p++; } ++ // no integer digits, or 0123 (zero must be solo) ++ if ( p == src ) { return INCORRECT_TYPE; } ++ if ( (leading_zero && p != src+1)) { return NUMBER_ERROR; } ++ ++ // ++ // Parse the decimal part. ++ // ++ int64_t exponent = 0; ++ bool overflow; ++ if (simdjson_likely(*p == '.')) { ++ p++; ++ const uint8_t *start_decimal_digits = p; ++ if (!parse_digit(*p, i)) { return NUMBER_ERROR; } // no decimal digits ++ p++; ++ while (parse_digit(*p, i)) { p++; } ++ exponent = -(p - start_decimal_digits); ++ ++ // Overflow check. More than 19 digits (minus the decimal) may be overflow. ++ overflow = p-src-1 > 19; ++ if (simdjson_unlikely(overflow && leading_zero)) { ++ // Skip leading 0.00000 and see if it still overflows ++ const uint8_t *start_digits = src + 2; ++ while (*start_digits == '0') { start_digits++; } ++ overflow = start_digits-src > 19; ++ } ++ } else { ++ overflow = p-src > 19; ++ } ++ ++ // ++ // Parse the exponent ++ // ++ if (*p == 'e' || *p == 'E') { ++ p++; ++ bool exp_neg = *p == '-'; ++ p += exp_neg || *p == '+'; ++ ++ uint64_t exp = 0; ++ const uint8_t *start_exp_digits = p; ++ while (parse_digit(*p, exp)) { p++; } ++ // no exp digits, or 20+ exp digits ++ if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } ++ ++ exponent += exp_neg ? 0-exp : exp; ++ } ++ ++ if (*p != '"') { return NUMBER_ERROR; } ++ ++ overflow = overflow || exponent < simdjson::internal::smallest_power || exponent > simdjson::internal::largest_power; ++ ++ // ++ // Assemble (or slow-parse) the float ++ // ++ double d; ++ if (simdjson_likely(!overflow)) { ++ if (compute_float_64(exponent, i, negative, d)) { return d; } ++ } ++ if (!parse_float_fallback(src - uint8_t(negative), &d)) { ++ return NUMBER_ERROR; ++ } ++ return d; ++} ++} //namespace {} ++#endif // SIMDJSON_SKIPNUMBERPARSING ++ ++} // namespace numberparsing ++} // unnamed namespace ++} // namespace ppc64 ++} // namespace simdjson ++/* end file include/simdjson/generic/numberparsing.h */ ++ ++#endif // SIMDJSON_PPC64_NUMBERPARSING_H ++/* end file include/simdjson/ppc64/numberparsing.h */ ++/* begin file include/simdjson/ppc64/end.h */ ++/* end file include/simdjson/ppc64/end.h */ ++ ++#endif // SIMDJSON_IMPLEMENTATION_PPC64 ++ ++#endif // SIMDJSON_PPC64_H ++/* end file include/simdjson/ppc64.h */ ++/* begin file include/simdjson/westmere.h */ ++#ifndef SIMDJSON_WESTMERE_H ++#define SIMDJSON_WESTMERE_H ++ ++ ++#if SIMDJSON_IMPLEMENTATION_WESTMERE ++ ++#if SIMDJSON_CAN_ALWAYS_RUN_WESTMERE ++#define SIMDJSON_TARGET_WESTMERE ++#define SIMDJSON_UNTARGET_WESTMERE ++#else ++#define SIMDJSON_TARGET_WESTMERE SIMDJSON_TARGET_REGION("sse4.2,pclmul") ++#define SIMDJSON_UNTARGET_WESTMERE SIMDJSON_UNTARGET_REGION ++#endif ++ ++namespace simdjson { ++/** ++ * Implementation for Westmere (Intel SSE4.2). ++ */ ++namespace westmere { ++} // namespace westmere ++} // namespace simdjson ++ ++// ++// These two need to be included outside SIMDJSON_TARGET_WESTMERE ++// ++/* begin file include/simdjson/westmere/implementation.h */ ++#ifndef SIMDJSON_WESTMERE_IMPLEMENTATION_H ++#define SIMDJSON_WESTMERE_IMPLEMENTATION_H ++ ++ ++// The constructor may be executed on any host, so we take care not to use SIMDJSON_TARGET_WESTMERE ++namespace simdjson { ++namespace westmere { ++ ++namespace { ++using namespace simdjson; ++using namespace simdjson::dom; ++} ++ ++class implementation final : public simdjson::implementation { ++public: ++ simdjson_inline implementation() : simdjson::implementation("westmere", "Intel/AMD SSE4.2", internal::instruction_set::SSE42 | internal::instruction_set::PCLMULQDQ) {} ++ simdjson_warn_unused error_code create_dom_parser_implementation( ++ size_t capacity, ++ size_t max_length, ++ std::unique_ptr& dst ++ ) const noexcept final; ++ simdjson_warn_unused error_code minify(const uint8_t *buf, size_t len, uint8_t *dst, size_t &dst_len) const noexcept final; ++ simdjson_warn_unused bool validate_utf8(const char *buf, size_t len) const noexcept final; ++}; ++ ++} // namespace westmere ++} // namespace simdjson ++ ++#endif // SIMDJSON_WESTMERE_IMPLEMENTATION_H ++/* end file include/simdjson/westmere/implementation.h */ ++/* begin file include/simdjson/westmere/intrinsics.h */ ++#ifndef SIMDJSON_WESTMERE_INTRINSICS_H ++#define SIMDJSON_WESTMERE_INTRINSICS_H ++ ++#if SIMDJSON_VISUAL_STUDIO ++// under clang within visual studio, this will include ++#include // visual studio or clang ++#else ++#include // elsewhere ++#endif // SIMDJSON_VISUAL_STUDIO ++ ++ ++#if SIMDJSON_CLANG_VISUAL_STUDIO ++/** ++ * You are not supposed, normally, to include these ++ * headers directly. Instead you should either include intrin.h ++ * or x86intrin.h. However, when compiling with clang ++ * under Windows (i.e., when _MSC_VER is set), these headers ++ * only get included *if* the corresponding features are detected ++ * from macros: ++ */ ++#include // for _mm_alignr_epi8 ++#include // for _mm_clmulepi64_si128 ++#endif ++ ++static_assert(sizeof(__m128i) <= simdjson::SIMDJSON_PADDING, "insufficient padding for westmere"); ++ ++#endif // SIMDJSON_WESTMERE_INTRINSICS_H ++/* end file include/simdjson/westmere/intrinsics.h */ ++ ++// ++// The rest need to be inside the region ++// ++/* begin file include/simdjson/westmere/begin.h */ ++// redefining SIMDJSON_IMPLEMENTATION to "westmere" ++// #define SIMDJSON_IMPLEMENTATION westmere ++SIMDJSON_TARGET_WESTMERE ++/* end file include/simdjson/westmere/begin.h */ ++ ++// Declarations ++/* begin file include/simdjson/generic/dom_parser_implementation.h */ ++ ++namespace simdjson { ++namespace westmere { ++ ++// expectation: sizeof(open_container) = 64/8. ++struct open_container { ++ uint32_t tape_index; // where, on the tape, does the scope ([,{) begins ++ uint32_t count; // how many elements in the scope ++}; // struct open_container ++ ++static_assert(sizeof(open_container) == 64/8, "Open container must be 64 bits"); ++ ++class dom_parser_implementation final : public internal::dom_parser_implementation { ++public: ++ /** Tape location of each open { or [ */ ++ std::unique_ptr open_containers{}; ++ /** Whether each open container is a [ or { */ ++ std::unique_ptr is_array{}; ++ /** Buffer passed to stage 1 */ ++ const uint8_t *buf{}; ++ /** Length passed to stage 1 */ ++ size_t len{0}; ++ /** Document passed to stage 2 */ ++ dom::document *doc{}; ++ ++ inline dom_parser_implementation() noexcept; ++ inline dom_parser_implementation(dom_parser_implementation &&other) noexcept; ++ inline dom_parser_implementation &operator=(dom_parser_implementation &&other) noexcept; ++ dom_parser_implementation(const dom_parser_implementation &) = delete; ++ dom_parser_implementation &operator=(const dom_parser_implementation &) = delete; ++ ++ simdjson_warn_unused error_code parse(const uint8_t *buf, size_t len, dom::document &doc) noexcept final; ++ simdjson_warn_unused error_code stage1(const uint8_t *buf, size_t len, stage1_mode partial) noexcept final; ++ simdjson_warn_unused error_code stage2(dom::document &doc) noexcept final; ++ simdjson_warn_unused error_code stage2_next(dom::document &doc) noexcept final; ++ simdjson_warn_unused uint8_t *parse_string(const uint8_t *src, uint8_t *dst) const noexcept final; ++ inline simdjson_warn_unused error_code set_capacity(size_t capacity) noexcept final; ++ inline simdjson_warn_unused error_code set_max_depth(size_t max_depth) noexcept final; ++private: ++ simdjson_inline simdjson_warn_unused error_code set_capacity_stage1(size_t capacity); ++ ++}; ++ ++} // namespace westmere ++} // namespace simdjson ++ ++namespace simdjson { ++namespace westmere { ++ ++inline dom_parser_implementation::dom_parser_implementation() noexcept = default; ++inline dom_parser_implementation::dom_parser_implementation(dom_parser_implementation &&other) noexcept = default; ++inline dom_parser_implementation &dom_parser_implementation::operator=(dom_parser_implementation &&other) noexcept = default; ++ ++// Leaving these here so they can be inlined if so desired ++inline simdjson_warn_unused error_code dom_parser_implementation::set_capacity(size_t capacity) noexcept { ++ if(capacity > SIMDJSON_MAXSIZE_BYTES) { return CAPACITY; } ++ // Stage 1 index output ++ size_t max_structures = SIMDJSON_ROUNDUP_N(capacity, 64) + 2 + 7; ++ structural_indexes.reset( new (std::nothrow) uint32_t[max_structures] ); ++ if (!structural_indexes) { _capacity = 0; return MEMALLOC; } ++ structural_indexes[0] = 0; ++ n_structural_indexes = 0; ++ ++ _capacity = capacity; ++ return SUCCESS; ++} ++ ++inline simdjson_warn_unused error_code dom_parser_implementation::set_max_depth(size_t max_depth) noexcept { ++ // Stage 2 stacks ++ open_containers.reset(new (std::nothrow) open_container[max_depth]); ++ is_array.reset(new (std::nothrow) bool[max_depth]); ++ if (!is_array || !open_containers) { _max_depth = 0; return MEMALLOC; } ++ ++ _max_depth = max_depth; ++ return SUCCESS; ++} ++ ++} // namespace westmere ++} // namespace simdjson ++/* end file include/simdjson/generic/dom_parser_implementation.h */ ++/* begin file include/simdjson/westmere/bitmanipulation.h */ ++#ifndef SIMDJSON_WESTMERE_BITMANIPULATION_H ++#define SIMDJSON_WESTMERE_BITMANIPULATION_H ++ ++namespace simdjson { ++namespace westmere { ++namespace { ++ ++// We sometimes call trailing_zero on inputs that are zero, ++// but the algorithms do not end up using the returned value. ++// Sadly, sanitizers are not smart enough to figure it out. ++SIMDJSON_NO_SANITIZE_UNDEFINED ++simdjson_inline int trailing_zeroes(uint64_t input_num) { ++#if SIMDJSON_REGULAR_VISUAL_STUDIO ++ unsigned long ret; ++ // Search the mask data from least significant bit (LSB) ++ // to the most significant bit (MSB) for a set bit (1). ++ _BitScanForward64(&ret, input_num); ++ return (int)ret; ++#else // SIMDJSON_REGULAR_VISUAL_STUDIO ++ return __builtin_ctzll(input_num); ++#endif // SIMDJSON_REGULAR_VISUAL_STUDIO ++} ++ ++/* result might be undefined when input_num is zero */ ++simdjson_inline uint64_t clear_lowest_bit(uint64_t input_num) { ++ return input_num & (input_num-1); ++} ++ ++/* result might be undefined when input_num is zero */ ++simdjson_inline int leading_zeroes(uint64_t input_num) { ++#if SIMDJSON_REGULAR_VISUAL_STUDIO ++ unsigned long leading_zero = 0; ++ // Search the mask data from most significant bit (MSB) ++ // to least significant bit (LSB) for a set bit (1). ++ if (_BitScanReverse64(&leading_zero, input_num)) ++ return (int)(63 - leading_zero); ++ else ++ return 64; ++#else ++ return __builtin_clzll(input_num); ++#endif// SIMDJSON_REGULAR_VISUAL_STUDIO ++} ++ ++#if SIMDJSON_REGULAR_VISUAL_STUDIO ++simdjson_inline unsigned __int64 count_ones(uint64_t input_num) { ++ // note: we do not support legacy 32-bit Windows ++ return __popcnt64(input_num);// Visual Studio wants two underscores ++} ++#else ++simdjson_inline long long int count_ones(uint64_t input_num) { ++ return _popcnt64(input_num); ++} ++#endif ++ ++simdjson_inline bool add_overflow(uint64_t value1, uint64_t value2, ++ uint64_t *result) { ++#if SIMDJSON_REGULAR_VISUAL_STUDIO ++ return _addcarry_u64(0, value1, value2, ++ reinterpret_cast(result)); ++#else ++ return __builtin_uaddll_overflow(value1, value2, ++ reinterpret_cast(result)); ++#endif ++} ++ ++} // unnamed namespace ++} // namespace westmere ++} // namespace simdjson ++ ++#endif // SIMDJSON_WESTMERE_BITMANIPULATION_H ++/* end file include/simdjson/westmere/bitmanipulation.h */ ++/* begin file include/simdjson/westmere/bitmask.h */ ++#ifndef SIMDJSON_WESTMERE_BITMASK_H ++#define SIMDJSON_WESTMERE_BITMASK_H ++ ++namespace simdjson { ++namespace westmere { ++namespace { ++ ++// ++// Perform a "cumulative bitwise xor," flipping bits each time a 1 is encountered. ++// ++// For example, prefix_xor(00100100) == 00011100 ++// ++simdjson_inline uint64_t prefix_xor(const uint64_t bitmask) { ++ // There should be no such thing with a processing supporting avx2 ++ // but not clmul. ++ __m128i all_ones = _mm_set1_epi8('\xFF'); ++ __m128i result = _mm_clmulepi64_si128(_mm_set_epi64x(0ULL, bitmask), all_ones, 0); ++ return _mm_cvtsi128_si64(result); ++} ++ ++} // unnamed namespace ++} // namespace westmere ++} // namespace simdjson ++ ++#endif // SIMDJSON_WESTMERE_BITMASK_H ++/* end file include/simdjson/westmere/bitmask.h */ ++/* begin file include/simdjson/westmere/simd.h */ ++#ifndef SIMDJSON_WESTMERE_SIMD_H ++#define SIMDJSON_WESTMERE_SIMD_H ++ ++ ++namespace simdjson { ++namespace westmere { ++namespace { ++namespace simd { ++ ++ template ++ struct base { ++ __m128i value; ++ ++ // Zero constructor ++ simdjson_inline base() : value{__m128i()} {} ++ ++ // Conversion from SIMD register ++ simdjson_inline base(const __m128i _value) : value(_value) {} ++ ++ // Conversion to SIMD register ++ simdjson_inline operator const __m128i&() const { return this->value; } ++ simdjson_inline operator __m128i&() { return this->value; } ++ ++ // Bit operations ++ simdjson_inline Child operator|(const Child other) const { return _mm_or_si128(*this, other); } ++ simdjson_inline Child operator&(const Child other) const { return _mm_and_si128(*this, other); } ++ simdjson_inline Child operator^(const Child other) const { return _mm_xor_si128(*this, other); } ++ simdjson_inline Child bit_andnot(const Child other) const { return _mm_andnot_si128(other, *this); } ++ simdjson_inline Child& operator|=(const Child other) { auto this_cast = static_cast(this); *this_cast = *this_cast | other; return *this_cast; } ++ simdjson_inline Child& operator&=(const Child other) { auto this_cast = static_cast(this); *this_cast = *this_cast & other; return *this_cast; } ++ simdjson_inline Child& operator^=(const Child other) { auto this_cast = static_cast(this); *this_cast = *this_cast ^ other; return *this_cast; } ++ }; ++ ++ // Forward-declared so they can be used by splat and friends. ++ template ++ struct simd8; ++ ++ template> ++ struct base8: base> { ++ typedef uint16_t bitmask_t; ++ typedef uint32_t bitmask2_t; ++ ++ simdjson_inline base8() : base>() {} ++ simdjson_inline base8(const __m128i _value) : base>(_value) {} ++ ++ friend simdjson_inline Mask operator==(const simd8 lhs, const simd8 rhs) { return _mm_cmpeq_epi8(lhs, rhs); } ++ ++ static const int SIZE = sizeof(base>::value); ++ ++ template ++ simdjson_inline simd8 prev(const simd8 prev_chunk) const { ++ return _mm_alignr_epi8(*this, prev_chunk, 16 - N); ++ } ++ }; ++ ++ // SIMD byte mask type (returned by things like eq and gt) ++ template<> ++ struct simd8: base8 { ++ static simdjson_inline simd8 splat(bool _value) { return _mm_set1_epi8(uint8_t(-(!!_value))); } ++ ++ simdjson_inline simd8() : base8() {} ++ simdjson_inline simd8(const __m128i _value) : base8(_value) {} ++ // Splat constructor ++ simdjson_inline simd8(bool _value) : base8(splat(_value)) {} ++ ++ simdjson_inline int to_bitmask() const { return _mm_movemask_epi8(*this); } ++ simdjson_inline bool any() const { return !_mm_testz_si128(*this, *this); } ++ simdjson_inline simd8 operator~() const { return *this ^ true; } ++ }; ++ ++ template ++ struct base8_numeric: base8 { ++ static simdjson_inline simd8 splat(T _value) { return _mm_set1_epi8(_value); } ++ static simdjson_inline simd8 zero() { return _mm_setzero_si128(); } ++ static simdjson_inline simd8 load(const T values[16]) { ++ return _mm_loadu_si128(reinterpret_cast(values)); ++ } ++ // Repeat 16 values as many times as necessary (usually for lookup tables) ++ static simdjson_inline simd8 repeat_16( ++ T v0, T v1, T v2, T v3, T v4, T v5, T v6, T v7, ++ T v8, T v9, T v10, T v11, T v12, T v13, T v14, T v15 ++ ) { ++ return simd8( ++ v0, v1, v2, v3, v4, v5, v6, v7, ++ v8, v9, v10,v11,v12,v13,v14,v15 ++ ); ++ } ++ ++ simdjson_inline base8_numeric() : base8() {} ++ simdjson_inline base8_numeric(const __m128i _value) : base8(_value) {} ++ ++ // Store to array ++ simdjson_inline void store(T dst[16]) const { return _mm_storeu_si128(reinterpret_cast<__m128i *>(dst), *this); } ++ ++ // Override to distinguish from bool version ++ simdjson_inline simd8 operator~() const { return *this ^ 0xFFu; } ++ ++ // Addition/subtraction are the same for signed and unsigned ++ simdjson_inline simd8 operator+(const simd8 other) const { return _mm_add_epi8(*this, other); } ++ simdjson_inline simd8 operator-(const simd8 other) const { return _mm_sub_epi8(*this, other); } ++ simdjson_inline simd8& operator+=(const simd8 other) { *this = *this + other; return *static_cast*>(this); } ++ simdjson_inline simd8& operator-=(const simd8 other) { *this = *this - other; return *static_cast*>(this); } ++ ++ // Perform a lookup assuming the value is between 0 and 16 (undefined behavior for out of range values) ++ template ++ simdjson_inline simd8 lookup_16(simd8 lookup_table) const { ++ return _mm_shuffle_epi8(lookup_table, *this); ++ } ++ ++ // Copies to 'output" all bytes corresponding to a 0 in the mask (interpreted as a bitset). ++ // Passing a 0 value for mask would be equivalent to writing out every byte to output. ++ // Only the first 16 - count_ones(mask) bytes of the result are significant but 16 bytes ++ // get written. ++ // Design consideration: it seems like a function with the ++ // signature simd8 compress(uint32_t mask) would be ++ // sensible, but the AVX ISA makes this kind of approach difficult. ++ template ++ simdjson_inline void compress(uint16_t mask, L * output) const { ++ using internal::thintable_epi8; ++ using internal::BitsSetTable256mul2; ++ using internal::pshufb_combine_table; ++ // this particular implementation was inspired by work done by @animetosho ++ // we do it in two steps, first 8 bytes and then second 8 bytes ++ uint8_t mask1 = uint8_t(mask); // least significant 8 bits ++ uint8_t mask2 = uint8_t(mask >> 8); // most significant 8 bits ++ // next line just loads the 64-bit values thintable_epi8[mask1] and ++ // thintable_epi8[mask2] into a 128-bit register, using only ++ // two instructions on most compilers. ++ __m128i shufmask = _mm_set_epi64x(thintable_epi8[mask2], thintable_epi8[mask1]); ++ // we increment by 0x08 the second half of the mask ++ shufmask = ++ _mm_add_epi8(shufmask, _mm_set_epi32(0x08080808, 0x08080808, 0, 0)); ++ // this is the version "nearly pruned" ++ __m128i pruned = _mm_shuffle_epi8(*this, shufmask); ++ // we still need to put the two halves together. ++ // we compute the popcount of the first half: ++ int pop1 = BitsSetTable256mul2[mask1]; ++ // then load the corresponding mask, what it does is to write ++ // only the first pop1 bytes from the first 8 bytes, and then ++ // it fills in with the bytes from the second 8 bytes + some filling ++ // at the end. ++ __m128i compactmask = ++ _mm_loadu_si128(reinterpret_cast(pshufb_combine_table + pop1 * 8)); ++ __m128i answer = _mm_shuffle_epi8(pruned, compactmask); ++ _mm_storeu_si128(reinterpret_cast<__m128i *>(output), answer); ++ } ++ ++ template ++ simdjson_inline simd8 lookup_16( ++ L replace0, L replace1, L replace2, L replace3, ++ L replace4, L replace5, L replace6, L replace7, ++ L replace8, L replace9, L replace10, L replace11, ++ L replace12, L replace13, L replace14, L replace15) const { ++ return lookup_16(simd8::repeat_16( ++ replace0, replace1, replace2, replace3, ++ replace4, replace5, replace6, replace7, ++ replace8, replace9, replace10, replace11, ++ replace12, replace13, replace14, replace15 ++ )); ++ } ++ }; ++ ++ // Signed bytes ++ template<> ++ struct simd8 : base8_numeric { ++ simdjson_inline simd8() : base8_numeric() {} ++ simdjson_inline simd8(const __m128i _value) : base8_numeric(_value) {} ++ // Splat constructor ++ simdjson_inline simd8(int8_t _value) : simd8(splat(_value)) {} ++ // Array constructor ++ simdjson_inline simd8(const int8_t* values) : simd8(load(values)) {} ++ // Member-by-member initialization ++ simdjson_inline simd8( ++ int8_t v0, int8_t v1, int8_t v2, int8_t v3, int8_t v4, int8_t v5, int8_t v6, int8_t v7, ++ int8_t v8, int8_t v9, int8_t v10, int8_t v11, int8_t v12, int8_t v13, int8_t v14, int8_t v15 ++ ) : simd8(_mm_setr_epi8( ++ v0, v1, v2, v3, v4, v5, v6, v7, ++ v8, v9, v10,v11,v12,v13,v14,v15 ++ )) {} ++ // Repeat 16 values as many times as necessary (usually for lookup tables) ++ simdjson_inline static simd8 repeat_16( ++ int8_t v0, int8_t v1, int8_t v2, int8_t v3, int8_t v4, int8_t v5, int8_t v6, int8_t v7, ++ int8_t v8, int8_t v9, int8_t v10, int8_t v11, int8_t v12, int8_t v13, int8_t v14, int8_t v15 ++ ) { ++ return simd8( ++ v0, v1, v2, v3, v4, v5, v6, v7, ++ v8, v9, v10,v11,v12,v13,v14,v15 ++ ); ++ } ++ ++ // Order-sensitive comparisons ++ simdjson_inline simd8 max_val(const simd8 other) const { return _mm_max_epi8(*this, other); } ++ simdjson_inline simd8 min_val(const simd8 other) const { return _mm_min_epi8(*this, other); } ++ simdjson_inline simd8 operator>(const simd8 other) const { return _mm_cmpgt_epi8(*this, other); } ++ simdjson_inline simd8 operator<(const simd8 other) const { return _mm_cmpgt_epi8(other, *this); } ++ }; ++ ++ // Unsigned bytes ++ template<> ++ struct simd8: base8_numeric { ++ simdjson_inline simd8() : base8_numeric() {} ++ simdjson_inline simd8(const __m128i _value) : base8_numeric(_value) {} ++ // Splat constructor ++ simdjson_inline simd8(uint8_t _value) : simd8(splat(_value)) {} ++ // Array constructor ++ simdjson_inline simd8(const uint8_t* values) : simd8(load(values)) {} ++ // Member-by-member initialization ++ simdjson_inline simd8( ++ uint8_t v0, uint8_t v1, uint8_t v2, uint8_t v3, uint8_t v4, uint8_t v5, uint8_t v6, uint8_t v7, ++ uint8_t v8, uint8_t v9, uint8_t v10, uint8_t v11, uint8_t v12, uint8_t v13, uint8_t v14, uint8_t v15 ++ ) : simd8(_mm_setr_epi8( ++ v0, v1, v2, v3, v4, v5, v6, v7, ++ v8, v9, v10,v11,v12,v13,v14,v15 ++ )) {} ++ // Repeat 16 values as many times as necessary (usually for lookup tables) ++ simdjson_inline static simd8 repeat_16( ++ uint8_t v0, uint8_t v1, uint8_t v2, uint8_t v3, uint8_t v4, uint8_t v5, uint8_t v6, uint8_t v7, ++ uint8_t v8, uint8_t v9, uint8_t v10, uint8_t v11, uint8_t v12, uint8_t v13, uint8_t v14, uint8_t v15 ++ ) { ++ return simd8( ++ v0, v1, v2, v3, v4, v5, v6, v7, ++ v8, v9, v10,v11,v12,v13,v14,v15 ++ ); ++ } ++ ++ // Saturated math ++ simdjson_inline simd8 saturating_add(const simd8 other) const { return _mm_adds_epu8(*this, other); } ++ simdjson_inline simd8 saturating_sub(const simd8 other) const { return _mm_subs_epu8(*this, other); } ++ ++ // Order-specific operations ++ simdjson_inline simd8 max_val(const simd8 other) const { return _mm_max_epu8(*this, other); } ++ simdjson_inline simd8 min_val(const simd8 other) const { return _mm_min_epu8(*this, other); } ++ // Same as >, but only guarantees true is nonzero (< guarantees true = -1) ++ simdjson_inline simd8 gt_bits(const simd8 other) const { return this->saturating_sub(other); } ++ // Same as <, but only guarantees true is nonzero (< guarantees true = -1) ++ simdjson_inline simd8 lt_bits(const simd8 other) const { return other.saturating_sub(*this); } ++ simdjson_inline simd8 operator<=(const simd8 other) const { return other.max_val(*this) == other; } ++ simdjson_inline simd8 operator>=(const simd8 other) const { return other.min_val(*this) == other; } ++ simdjson_inline simd8 operator>(const simd8 other) const { return this->gt_bits(other).any_bits_set(); } ++ simdjson_inline simd8 operator<(const simd8 other) const { return this->gt_bits(other).any_bits_set(); } ++ ++ // Bit-specific operations ++ simdjson_inline simd8 bits_not_set() const { return *this == uint8_t(0); } ++ simdjson_inline simd8 bits_not_set(simd8 bits) const { return (*this & bits).bits_not_set(); } ++ simdjson_inline simd8 any_bits_set() const { return ~this->bits_not_set(); } ++ simdjson_inline simd8 any_bits_set(simd8 bits) const { return ~this->bits_not_set(bits); } ++ simdjson_inline bool is_ascii() const { return _mm_movemask_epi8(*this) == 0; } ++ simdjson_inline bool bits_not_set_anywhere() const { return _mm_testz_si128(*this, *this); } ++ simdjson_inline bool any_bits_set_anywhere() const { return !bits_not_set_anywhere(); } ++ simdjson_inline bool bits_not_set_anywhere(simd8 bits) const { return _mm_testz_si128(*this, bits); } ++ simdjson_inline bool any_bits_set_anywhere(simd8 bits) const { return !bits_not_set_anywhere(bits); } ++ template ++ simdjson_inline simd8 shr() const { return simd8(_mm_srli_epi16(*this, N)) & uint8_t(0xFFu >> N); } ++ template ++ simdjson_inline simd8 shl() const { return simd8(_mm_slli_epi16(*this, N)) & uint8_t(0xFFu << N); } ++ // Get one of the bits and make a bitmask out of it. ++ // e.g. value.get_bit<7>() gets the high bit ++ template ++ simdjson_inline int get_bit() const { return _mm_movemask_epi8(_mm_slli_epi16(*this, 7-N)); } ++ }; ++ ++ template ++ struct simd8x64 { ++ static constexpr int NUM_CHUNKS = 64 / sizeof(simd8); ++ static_assert(NUM_CHUNKS == 4, "Westmere kernel should use four registers per 64-byte block."); ++ const simd8 chunks[NUM_CHUNKS]; ++ ++ simd8x64(const simd8x64& o) = delete; // no copy allowed ++ simd8x64& operator=(const simd8& other) = delete; // no assignment allowed ++ simd8x64() = delete; // no default constructor allowed ++ ++ simdjson_inline simd8x64(const simd8 chunk0, const simd8 chunk1, const simd8 chunk2, const simd8 chunk3) : chunks{chunk0, chunk1, chunk2, chunk3} {} ++ simdjson_inline simd8x64(const T ptr[64]) : chunks{simd8::load(ptr), simd8::load(ptr+16), simd8::load(ptr+32), simd8::load(ptr+48)} {} ++ ++ simdjson_inline void store(T ptr[64]) const { ++ this->chunks[0].store(ptr+sizeof(simd8)*0); ++ this->chunks[1].store(ptr+sizeof(simd8)*1); ++ this->chunks[2].store(ptr+sizeof(simd8)*2); ++ this->chunks[3].store(ptr+sizeof(simd8)*3); ++ } ++ ++ simdjson_inline simd8 reduce_or() const { ++ return (this->chunks[0] | this->chunks[1]) | (this->chunks[2] | this->chunks[3]); ++ } ++ ++ simdjson_inline uint64_t compress(uint64_t mask, T * output) const { ++ this->chunks[0].compress(uint16_t(mask), output); ++ this->chunks[1].compress(uint16_t(mask >> 16), output + 16 - count_ones(mask & 0xFFFF)); ++ this->chunks[2].compress(uint16_t(mask >> 32), output + 32 - count_ones(mask & 0xFFFFFFFF)); ++ this->chunks[3].compress(uint16_t(mask >> 48), output + 48 - count_ones(mask & 0xFFFFFFFFFFFF)); ++ return 64 - count_ones(mask); ++ } ++ ++ simdjson_inline uint64_t to_bitmask() const { ++ uint64_t r0 = uint32_t(this->chunks[0].to_bitmask() ); ++ uint64_t r1 = this->chunks[1].to_bitmask() ; ++ uint64_t r2 = this->chunks[2].to_bitmask() ; ++ uint64_t r3 = this->chunks[3].to_bitmask() ; ++ return r0 | (r1 << 16) | (r2 << 32) | (r3 << 48); ++ } ++ ++ simdjson_inline uint64_t eq(const T m) const { ++ const simd8 mask = simd8::splat(m); ++ return simd8x64( ++ this->chunks[0] == mask, ++ this->chunks[1] == mask, ++ this->chunks[2] == mask, ++ this->chunks[3] == mask ++ ).to_bitmask(); ++ } ++ ++ simdjson_inline uint64_t eq(const simd8x64 &other) const { ++ return simd8x64( ++ this->chunks[0] == other.chunks[0], ++ this->chunks[1] == other.chunks[1], ++ this->chunks[2] == other.chunks[2], ++ this->chunks[3] == other.chunks[3] ++ ).to_bitmask(); ++ } ++ ++ simdjson_inline uint64_t lteq(const T m) const { ++ const simd8 mask = simd8::splat(m); ++ return simd8x64( ++ this->chunks[0] <= mask, ++ this->chunks[1] <= mask, ++ this->chunks[2] <= mask, ++ this->chunks[3] <= mask ++ ).to_bitmask(); ++ } ++ }; // struct simd8x64 ++ ++} // namespace simd ++} // unnamed namespace ++} // namespace westmere ++} // namespace simdjson ++ ++#endif // SIMDJSON_WESTMERE_SIMD_INPUT_H ++/* end file include/simdjson/westmere/simd.h */ ++/* begin file include/simdjson/generic/jsoncharutils.h */ ++ ++namespace simdjson { ++namespace westmere { ++namespace { ++namespace jsoncharutils { ++ ++// return non-zero if not a structural or whitespace char ++// zero otherwise ++simdjson_inline uint32_t is_not_structural_or_whitespace(uint8_t c) { ++ return internal::structural_or_whitespace_negated[c]; ++} ++ ++simdjson_inline uint32_t is_structural_or_whitespace(uint8_t c) { ++ return internal::structural_or_whitespace[c]; ++} ++ ++// returns a value with the high 16 bits set if not valid ++// otherwise returns the conversion of the 4 hex digits at src into the bottom ++// 16 bits of the 32-bit return register ++// ++// see ++// https://lemire.me/blog/2019/04/17/parsing-short-hexadecimal-strings-efficiently/ ++static inline uint32_t hex_to_u32_nocheck( ++ const uint8_t *src) { // strictly speaking, static inline is a C-ism ++ uint32_t v1 = internal::digit_to_val32[630 + src[0]]; ++ uint32_t v2 = internal::digit_to_val32[420 + src[1]]; ++ uint32_t v3 = internal::digit_to_val32[210 + src[2]]; ++ uint32_t v4 = internal::digit_to_val32[0 + src[3]]; ++ return v1 | v2 | v3 | v4; ++} ++ ++// given a code point cp, writes to c ++// the utf-8 code, outputting the length in ++// bytes, if the length is zero, the code point ++// is invalid ++// ++// This can possibly be made faster using pdep ++// and clz and table lookups, but JSON documents ++// have few escaped code points, and the following ++// function looks cheap. ++// ++// Note: we assume that surrogates are treated separately ++// ++simdjson_inline size_t codepoint_to_utf8(uint32_t cp, uint8_t *c) { ++ if (cp <= 0x7F) { ++ c[0] = uint8_t(cp); ++ return 1; // ascii ++ } ++ if (cp <= 0x7FF) { ++ c[0] = uint8_t((cp >> 6) + 192); ++ c[1] = uint8_t((cp & 63) + 128); ++ return 2; // universal plane ++ // Surrogates are treated elsewhere... ++ //} //else if (0xd800 <= cp && cp <= 0xdfff) { ++ // return 0; // surrogates // could put assert here ++ } else if (cp <= 0xFFFF) { ++ c[0] = uint8_t((cp >> 12) + 224); ++ c[1] = uint8_t(((cp >> 6) & 63) + 128); ++ c[2] = uint8_t((cp & 63) + 128); ++ return 3; ++ } else if (cp <= 0x10FFFF) { // if you know you have a valid code point, this ++ // is not needed ++ c[0] = uint8_t((cp >> 18) + 240); ++ c[1] = uint8_t(((cp >> 12) & 63) + 128); ++ c[2] = uint8_t(((cp >> 6) & 63) + 128); ++ c[3] = uint8_t((cp & 63) + 128); ++ return 4; ++ } ++ // will return 0 when the code point was too large. ++ return 0; // bad r ++} ++ ++#if SIMDJSON_IS_32BITS // _umul128 for x86, arm ++// this is a slow emulation routine for 32-bit ++// ++static simdjson_inline uint64_t __emulu(uint32_t x, uint32_t y) { ++ return x * (uint64_t)y; ++} ++static simdjson_inline uint64_t _umul128(uint64_t ab, uint64_t cd, uint64_t *hi) { ++ uint64_t ad = __emulu((uint32_t)(ab >> 32), (uint32_t)cd); ++ uint64_t bd = __emulu((uint32_t)ab, (uint32_t)cd); ++ uint64_t adbc = ad + __emulu((uint32_t)ab, (uint32_t)(cd >> 32)); ++ uint64_t adbc_carry = !!(adbc < ad); ++ uint64_t lo = bd + (adbc << 32); ++ *hi = __emulu((uint32_t)(ab >> 32), (uint32_t)(cd >> 32)) + (adbc >> 32) + ++ (adbc_carry << 32) + !!(lo < bd); ++ return lo; ++} ++#endif ++ ++using internal::value128; ++ ++simdjson_inline value128 full_multiplication(uint64_t value1, uint64_t value2) { ++ value128 answer; ++#if SIMDJSON_REGULAR_VISUAL_STUDIO || SIMDJSON_IS_32BITS ++#ifdef _M_ARM64 ++ // ARM64 has native support for 64-bit multiplications, no need to emultate ++ answer.high = __umulh(value1, value2); ++ answer.low = value1 * value2; ++#else ++ answer.low = _umul128(value1, value2, &answer.high); // _umul128 not available on ARM64 ++#endif // _M_ARM64 ++#else // SIMDJSON_REGULAR_VISUAL_STUDIO || SIMDJSON_IS_32BITS ++ __uint128_t r = (static_cast<__uint128_t>(value1)) * value2; ++ answer.low = uint64_t(r); ++ answer.high = uint64_t(r >> 64); ++#endif ++ return answer; ++} ++ ++} // namespace jsoncharutils ++} // unnamed namespace ++} // namespace westmere ++} // namespace simdjson ++/* end file include/simdjson/generic/jsoncharutils.h */ ++/* begin file include/simdjson/generic/atomparsing.h */ ++namespace simdjson { ++namespace westmere { ++namespace { ++/// @private ++namespace atomparsing { ++ ++// The string_to_uint32 is exclusively used to map literal strings to 32-bit values. ++// We use memcpy instead of a pointer cast to avoid undefined behaviors since we cannot ++// be certain that the character pointer will be properly aligned. ++// You might think that using memcpy makes this function expensive, but you'd be wrong. ++// All decent optimizing compilers (GCC, clang, Visual Studio) will compile string_to_uint32("false"); ++// to the compile-time constant 1936482662. ++simdjson_inline uint32_t string_to_uint32(const char* str) { uint32_t val; std::memcpy(&val, str, sizeof(uint32_t)); return val; } ++ ++ ++// Again in str4ncmp we use a memcpy to avoid undefined behavior. The memcpy may appear expensive. ++// Yet all decent optimizing compilers will compile memcpy to a single instruction, just about. ++simdjson_warn_unused ++simdjson_inline uint32_t str4ncmp(const uint8_t *src, const char* atom) { ++ uint32_t srcval; // we want to avoid unaligned 32-bit loads (undefined in C/C++) ++ static_assert(sizeof(uint32_t) <= SIMDJSON_PADDING, "SIMDJSON_PADDING must be larger than 4 bytes"); ++ std::memcpy(&srcval, src, sizeof(uint32_t)); ++ return srcval ^ string_to_uint32(atom); ++} ++ ++simdjson_warn_unused ++simdjson_inline bool is_valid_true_atom(const uint8_t *src) { ++ return (str4ncmp(src, "true") | jsoncharutils::is_not_structural_or_whitespace(src[4])) == 0; ++} ++ ++simdjson_warn_unused ++simdjson_inline bool is_valid_true_atom(const uint8_t *src, size_t len) { ++ if (len > 4) { return is_valid_true_atom(src); } ++ else if (len == 4) { return !str4ncmp(src, "true"); } ++ else { return false; } ++} ++ ++simdjson_warn_unused ++simdjson_inline bool is_valid_false_atom(const uint8_t *src) { ++ return (str4ncmp(src+1, "alse") | jsoncharutils::is_not_structural_or_whitespace(src[5])) == 0; ++} ++ ++simdjson_warn_unused ++simdjson_inline bool is_valid_false_atom(const uint8_t *src, size_t len) { ++ if (len > 5) { return is_valid_false_atom(src); } ++ else if (len == 5) { return !str4ncmp(src+1, "alse"); } ++ else { return false; } ++} ++ ++simdjson_warn_unused ++simdjson_inline bool is_valid_null_atom(const uint8_t *src) { ++ return (str4ncmp(src, "null") | jsoncharutils::is_not_structural_or_whitespace(src[4])) == 0; ++} ++ ++simdjson_warn_unused ++simdjson_inline bool is_valid_null_atom(const uint8_t *src, size_t len) { ++ if (len > 4) { return is_valid_null_atom(src); } ++ else if (len == 4) { return !str4ncmp(src, "null"); } ++ else { return false; } ++} ++ ++} // namespace atomparsing ++} // unnamed namespace ++} // namespace westmere ++} // namespace simdjson ++/* end file include/simdjson/generic/atomparsing.h */ ++/* begin file include/simdjson/westmere/stringparsing.h */ ++#ifndef SIMDJSON_WESTMERE_STRINGPARSING_H ++#define SIMDJSON_WESTMERE_STRINGPARSING_H ++ ++namespace simdjson { ++namespace westmere { ++namespace { ++ ++using namespace simd; ++ ++// Holds backslashes and quotes locations. ++struct backslash_and_quote { ++public: ++ static constexpr uint32_t BYTES_PROCESSED = 32; ++ simdjson_inline static backslash_and_quote copy_and_find(const uint8_t *src, uint8_t *dst); ++ ++ simdjson_inline bool has_quote_first() { return ((bs_bits - 1) & quote_bits) != 0; } ++ simdjson_inline bool has_backslash() { return bs_bits != 0; } ++ simdjson_inline int quote_index() { return trailing_zeroes(quote_bits); } ++ simdjson_inline int backslash_index() { return trailing_zeroes(bs_bits); } ++ ++ uint32_t bs_bits; ++ uint32_t quote_bits; ++}; // struct backslash_and_quote ++ ++simdjson_inline backslash_and_quote backslash_and_quote::copy_and_find(const uint8_t *src, uint8_t *dst) { ++ // this can read up to 31 bytes beyond the buffer size, but we require ++ // SIMDJSON_PADDING of padding ++ static_assert(SIMDJSON_PADDING >= (BYTES_PROCESSED - 1), "backslash and quote finder must process fewer than SIMDJSON_PADDING bytes"); ++ simd8 v0(src); ++ simd8 v1(src + 16); ++ v0.store(dst); ++ v1.store(dst + 16); ++ uint64_t bs_and_quote = simd8x64(v0 == '\\', v1 == '\\', v0 == '"', v1 == '"').to_bitmask(); ++ return { ++ uint32_t(bs_and_quote), // bs_bits ++ uint32_t(bs_and_quote >> 32) // quote_bits ++ }; ++} ++ ++} // unnamed namespace ++} // namespace westmere ++} // namespace simdjson ++ ++#endif // SIMDJSON_WESTMERE_STRINGPARSING_H ++/* end file include/simdjson/westmere/stringparsing.h */ ++/* begin file include/simdjson/westmere/numberparsing.h */ ++#ifndef SIMDJSON_WESTMERE_NUMBERPARSING_H ++#define SIMDJSON_WESTMERE_NUMBERPARSING_H ++ ++namespace simdjson { ++namespace westmere { ++namespace { ++ ++static simdjson_inline uint32_t parse_eight_digits_unrolled(const uint8_t *chars) { ++ // this actually computes *16* values so we are being wasteful. ++ const __m128i ascii0 = _mm_set1_epi8('0'); ++ const __m128i mul_1_10 = ++ _mm_setr_epi8(10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1); ++ const __m128i mul_1_100 = _mm_setr_epi16(100, 1, 100, 1, 100, 1, 100, 1); ++ const __m128i mul_1_10000 = ++ _mm_setr_epi16(10000, 1, 10000, 1, 10000, 1, 10000, 1); ++ const __m128i input = _mm_sub_epi8( ++ _mm_loadu_si128(reinterpret_cast(chars)), ascii0); ++ const __m128i t1 = _mm_maddubs_epi16(input, mul_1_10); ++ const __m128i t2 = _mm_madd_epi16(t1, mul_1_100); ++ const __m128i t3 = _mm_packus_epi32(t2, t2); ++ const __m128i t4 = _mm_madd_epi16(t3, mul_1_10000); ++ return _mm_cvtsi128_si32( ++ t4); // only captures the sum of the first 8 digits, drop the rest ++} ++ ++} // unnamed namespace ++} // namespace westmere ++} // namespace simdjson ++ ++#define SIMDJSON_SWAR_NUMBER_PARSING 1 ++ ++/* begin file include/simdjson/generic/numberparsing.h */ ++#include ++ ++namespace simdjson { ++namespace westmere { ++ ++namespace ondemand { ++/** ++ * The type of a JSON number ++ */ ++enum class number_type { ++ floating_point_number=1, /// a binary64 number ++ signed_integer, /// a signed integer that fits in a 64-bit word using two's complement ++ unsigned_integer /// a positive integer larger or equal to 1<<63 ++}; ++} ++ ++namespace { ++/// @private ++namespace numberparsing { ++ ++ ++ ++#ifdef JSON_TEST_NUMBERS ++#define INVALID_NUMBER(SRC) (found_invalid_number((SRC)), NUMBER_ERROR) ++#define WRITE_INTEGER(VALUE, SRC, WRITER) (found_integer((VALUE), (SRC)), (WRITER).append_s64((VALUE))) ++#define WRITE_UNSIGNED(VALUE, SRC, WRITER) (found_unsigned_integer((VALUE), (SRC)), (WRITER).append_u64((VALUE))) ++#define WRITE_DOUBLE(VALUE, SRC, WRITER) (found_float((VALUE), (SRC)), (WRITER).append_double((VALUE))) ++#else ++#define INVALID_NUMBER(SRC) (NUMBER_ERROR) ++#define WRITE_INTEGER(VALUE, SRC, WRITER) (WRITER).append_s64((VALUE)) ++#define WRITE_UNSIGNED(VALUE, SRC, WRITER) (WRITER).append_u64((VALUE)) ++#define WRITE_DOUBLE(VALUE, SRC, WRITER) (WRITER).append_double((VALUE)) ++#endif ++ ++namespace { ++// Convert a mantissa, an exponent and a sign bit into an ieee64 double. ++// The real_exponent needs to be in [0, 2046] (technically real_exponent = 2047 would be acceptable). ++// The mantissa should be in [0,1<<53). The bit at index (1ULL << 52) while be zeroed. ++simdjson_inline double to_double(uint64_t mantissa, uint64_t real_exponent, bool negative) { ++ double d; ++ mantissa &= ~(1ULL << 52); ++ mantissa |= real_exponent << 52; ++ mantissa |= ((static_cast(negative)) << 63); ++ std::memcpy(&d, &mantissa, sizeof(d)); ++ return d; ++} ++} ++// Attempts to compute i * 10^(power) exactly; and if "negative" is ++// true, negate the result. ++// This function will only work in some cases, when it does not work, success is ++// set to false. This should work *most of the time* (like 99% of the time). ++// We assume that power is in the [smallest_power, ++// largest_power] interval: the caller is responsible for this check. ++simdjson_inline bool compute_float_64(int64_t power, uint64_t i, bool negative, double &d) { ++ // we start with a fast path ++ // It was described in ++ // Clinger WD. How to read floating point numbers accurately. ++ // ACM SIGPLAN Notices. 1990 ++#ifndef FLT_EVAL_METHOD ++#error "FLT_EVAL_METHOD should be defined, please include cfloat." ++#endif ++#if (FLT_EVAL_METHOD != 1) && (FLT_EVAL_METHOD != 0) ++ // We cannot be certain that x/y is rounded to nearest. ++ if (0 <= power && power <= 22 && i <= 9007199254740991) { ++#else ++ if (-22 <= power && power <= 22 && i <= 9007199254740991) { ++#endif ++ // convert the integer into a double. This is lossless since ++ // 0 <= i <= 2^53 - 1. ++ d = double(i); ++ // ++ // The general idea is as follows. ++ // If 0 <= s < 2^53 and if 10^0 <= p <= 10^22 then ++ // 1) Both s and p can be represented exactly as 64-bit floating-point ++ // values ++ // (binary64). ++ // 2) Because s and p can be represented exactly as floating-point values, ++ // then s * p ++ // and s / p will produce correctly rounded values. ++ // ++ if (power < 0) { ++ d = d / simdjson::internal::power_of_ten[-power]; ++ } else { ++ d = d * simdjson::internal::power_of_ten[power]; ++ } ++ if (negative) { ++ d = -d; ++ } ++ return true; ++ } ++ // When 22 < power && power < 22 + 16, we could ++ // hope for another, secondary fast path. It was ++ // described by David M. Gay in "Correctly rounded ++ // binary-decimal and decimal-binary conversions." (1990) ++ // If you need to compute i * 10^(22 + x) for x < 16, ++ // first compute i * 10^x, if you know that result is exact ++ // (e.g., when i * 10^x < 2^53), ++ // then you can still proceed and do (i * 10^x) * 10^22. ++ // Is this worth your time? ++ // You need 22 < power *and* power < 22 + 16 *and* (i * 10^(x-22) < 2^53) ++ // for this second fast path to work. ++ // If you you have 22 < power *and* power < 22 + 16, and then you ++ // optimistically compute "i * 10^(x-22)", there is still a chance that you ++ // have wasted your time if i * 10^(x-22) >= 2^53. It makes the use cases of ++ // this optimization maybe less common than we would like. Source: ++ // http://www.exploringbinary.com/fast-path-decimal-to-floating-point-conversion/ ++ // also used in RapidJSON: https://rapidjson.org/strtod_8h_source.html ++ ++ // The fast path has now failed, so we are failing back on the slower path. ++ ++ // In the slow path, we need to adjust i so that it is > 1<<63 which is always ++ // possible, except if i == 0, so we handle i == 0 separately. ++ if(i == 0) { ++ d = negative ? -0.0 : 0.0; ++ return true; ++ } ++ ++ ++ // The exponent is 1024 + 63 + power ++ // + floor(log(5**power)/log(2)). ++ // The 1024 comes from the ieee64 standard. ++ // The 63 comes from the fact that we use a 64-bit word. ++ // ++ // Computing floor(log(5**power)/log(2)) could be ++ // slow. Instead we use a fast function. ++ // ++ // For power in (-400,350), we have that ++ // (((152170 + 65536) * power ) >> 16); ++ // is equal to ++ // floor(log(5**power)/log(2)) + power when power >= 0 ++ // and it is equal to ++ // ceil(log(5**-power)/log(2)) + power when power < 0 ++ // ++ // The 65536 is (1<<16) and corresponds to ++ // (65536 * power) >> 16 ---> power ++ // ++ // ((152170 * power ) >> 16) is equal to ++ // floor(log(5**power)/log(2)) ++ // ++ // Note that this is not magic: 152170/(1<<16) is ++ // approximatively equal to log(5)/log(2). ++ // The 1<<16 value is a power of two; we could use a ++ // larger power of 2 if we wanted to. ++ // ++ int64_t exponent = (((152170 + 65536) * power) >> 16) + 1024 + 63; ++ ++ ++ // We want the most significant bit of i to be 1. Shift if needed. ++ int lz = leading_zeroes(i); ++ i <<= lz; ++ ++ ++ // We are going to need to do some 64-bit arithmetic to get a precise product. ++ // We use a table lookup approach. ++ // It is safe because ++ // power >= smallest_power ++ // and power <= largest_power ++ // We recover the mantissa of the power, it has a leading 1. It is always ++ // rounded down. ++ // ++ // We want the most significant 64 bits of the product. We know ++ // this will be non-zero because the most significant bit of i is ++ // 1. ++ const uint32_t index = 2 * uint32_t(power - simdjson::internal::smallest_power); ++ // Optimization: It may be that materializing the index as a variable might confuse some compilers and prevent effective complex-addressing loads. (Done for code clarity.) ++ // ++ // The full_multiplication function computes the 128-bit product of two 64-bit words ++ // with a returned value of type value128 with a "low component" corresponding to the ++ // 64-bit least significant bits of the product and with a "high component" corresponding ++ // to the 64-bit most significant bits of the product. ++ simdjson::internal::value128 firstproduct = jsoncharutils::full_multiplication(i, simdjson::internal::power_of_five_128[index]); ++ // Both i and power_of_five_128[index] have their most significant bit set to 1 which ++ // implies that the either the most or the second most significant bit of the product ++ // is 1. We pack values in this manner for efficiency reasons: it maximizes the use ++ // we make of the product. It also makes it easy to reason about the product: there ++ // is 0 or 1 leading zero in the product. ++ ++ // Unless the least significant 9 bits of the high (64-bit) part of the full ++ // product are all 1s, then we know that the most significant 55 bits are ++ // exact and no further work is needed. Having 55 bits is necessary because ++ // we need 53 bits for the mantissa but we have to have one rounding bit and ++ // we can waste a bit if the most significant bit of the product is zero. ++ if((firstproduct.high & 0x1FF) == 0x1FF) { ++ // We want to compute i * 5^q, but only care about the top 55 bits at most. ++ // Consider the scenario where q>=0. Then 5^q may not fit in 64-bits. Doing ++ // the full computation is wasteful. So we do what is called a "truncated ++ // multiplication". ++ // We take the most significant 64-bits, and we put them in ++ // power_of_five_128[index]. Usually, that's good enough to approximate i * 5^q ++ // to the desired approximation using one multiplication. Sometimes it does not suffice. ++ // Then we store the next most significant 64 bits in power_of_five_128[index + 1], and ++ // then we get a better approximation to i * 5^q. In very rare cases, even that ++ // will not suffice, though it is seemingly very hard to find such a scenario. ++ // ++ // That's for when q>=0. The logic for q<0 is somewhat similar but it is somewhat ++ // more complicated. ++ // ++ // There is an extra layer of complexity in that we need more than 55 bits of ++ // accuracy in the round-to-even scenario. ++ // ++ // The full_multiplication function computes the 128-bit product of two 64-bit words ++ // with a returned value of type value128 with a "low component" corresponding to the ++ // 64-bit least significant bits of the product and with a "high component" corresponding ++ // to the 64-bit most significant bits of the product. ++ simdjson::internal::value128 secondproduct = jsoncharutils::full_multiplication(i, simdjson::internal::power_of_five_128[index + 1]); ++ firstproduct.low += secondproduct.high; ++ if(secondproduct.high > firstproduct.low) { firstproduct.high++; } ++ // At this point, we might need to add at most one to firstproduct, but this ++ // can only change the value of firstproduct.high if firstproduct.low is maximal. ++ if(simdjson_unlikely(firstproduct.low == 0xFFFFFFFFFFFFFFFF)) { ++ // This is very unlikely, but if so, we need to do much more work! ++ return false; ++ } ++ } ++ uint64_t lower = firstproduct.low; ++ uint64_t upper = firstproduct.high; ++ // The final mantissa should be 53 bits with a leading 1. ++ // We shift it so that it occupies 54 bits with a leading 1. ++ /////// ++ uint64_t upperbit = upper >> 63; ++ uint64_t mantissa = upper >> (upperbit + 9); ++ lz += int(1 ^ upperbit); ++ ++ // Here we have mantissa < (1<<54). ++ int64_t real_exponent = exponent - lz; ++ if (simdjson_unlikely(real_exponent <= 0)) { // we have a subnormal? ++ // Here have that real_exponent <= 0 so -real_exponent >= 0 ++ if(-real_exponent + 1 >= 64) { // if we have more than 64 bits below the minimum exponent, you have a zero for sure. ++ d = negative ? -0.0 : 0.0; ++ return true; ++ } ++ // next line is safe because -real_exponent + 1 < 0 ++ mantissa >>= -real_exponent + 1; ++ // Thankfully, we can't have both "round-to-even" and subnormals because ++ // "round-to-even" only occurs for powers close to 0. ++ mantissa += (mantissa & 1); // round up ++ mantissa >>= 1; ++ // There is a weird scenario where we don't have a subnormal but just. ++ // Suppose we start with 2.2250738585072013e-308, we end up ++ // with 0x3fffffffffffff x 2^-1023-53 which is technically subnormal ++ // whereas 0x40000000000000 x 2^-1023-53 is normal. Now, we need to round ++ // up 0x3fffffffffffff x 2^-1023-53 and once we do, we are no longer ++ // subnormal, but we can only know this after rounding. ++ // So we only declare a subnormal if we are smaller than the threshold. ++ real_exponent = (mantissa < (uint64_t(1) << 52)) ? 0 : 1; ++ d = to_double(mantissa, real_exponent, negative); ++ return true; ++ } ++ // We have to round to even. The "to even" part ++ // is only a problem when we are right in between two floats ++ // which we guard against. ++ // If we have lots of trailing zeros, we may fall right between two ++ // floating-point values. ++ // ++ // The round-to-even cases take the form of a number 2m+1 which is in (2^53,2^54] ++ // times a power of two. That is, it is right between a number with binary significand ++ // m and another number with binary significand m+1; and it must be the case ++ // that it cannot be represented by a float itself. ++ // ++ // We must have that w * 10 ^q == (2m+1) * 2^p for some power of two 2^p. ++ // Recall that 10^q = 5^q * 2^q. ++ // When q >= 0, we must have that (2m+1) is divible by 5^q, so 5^q <= 2^54. We have that ++ // 5^23 <= 2^54 and it is the last power of five to qualify, so q <= 23. ++ // When q<0, we have w >= (2m+1) x 5^{-q}. We must have that w<2^{64} so ++ // (2m+1) x 5^{-q} < 2^{64}. We have that 2m+1>2^{53}. Hence, we must have ++ // 2^{53} x 5^{-q} < 2^{64}. ++ // Hence we have 5^{-q} < 2^{11}$ or q>= -4. ++ // ++ // We require lower <= 1 and not lower == 0 because we could not prove that ++ // that lower == 0 is implied; but we could prove that lower <= 1 is a necessary and sufficient test. ++ if (simdjson_unlikely((lower <= 1) && (power >= -4) && (power <= 23) && ((mantissa & 3) == 1))) { ++ if((mantissa << (upperbit + 64 - 53 - 2)) == upper) { ++ mantissa &= ~1; // flip it so that we do not round up ++ } ++ } ++ ++ mantissa += mantissa & 1; ++ mantissa >>= 1; ++ ++ // Here we have mantissa < (1<<53), unless there was an overflow ++ if (mantissa >= (1ULL << 53)) { ++ ////////// ++ // This will happen when parsing values such as 7.2057594037927933e+16 ++ //////// ++ mantissa = (1ULL << 52); ++ real_exponent++; ++ } ++ mantissa &= ~(1ULL << 52); ++ // we have to check that real_exponent is in range, otherwise we bail out ++ if (simdjson_unlikely(real_exponent > 2046)) { ++ // We have an infinite value!!! We could actually throw an error here if we could. ++ return false; ++ } ++ d = to_double(mantissa, real_exponent, negative); ++ return true; ++} ++ ++// We call a fallback floating-point parser that might be slow. Note ++// it will accept JSON numbers, but the JSON spec. is more restrictive so ++// before you call parse_float_fallback, you need to have validated the input ++// string with the JSON grammar. ++// It will return an error (false) if the parsed number is infinite. ++// The string parsing itself always succeeds. We know that there is at least ++// one digit. ++static bool parse_float_fallback(const uint8_t *ptr, double *outDouble) { ++ *outDouble = simdjson::internal::from_chars(reinterpret_cast(ptr)); ++ // We do not accept infinite values. ++ ++ // Detecting finite values in a portable manner is ridiculously hard, ideally ++ // we would want to do: ++ // return !std::isfinite(*outDouble); ++ // but that mysteriously fails under legacy/old libc++ libraries, see ++ // https://github.com/simdjson/simdjson/issues/1286 ++ // ++ // Therefore, fall back to this solution (the extra parens are there ++ // to handle that max may be a macro on windows). ++ return !(*outDouble > (std::numeric_limits::max)() || *outDouble < std::numeric_limits::lowest()); ++} ++static bool parse_float_fallback(const uint8_t *ptr, const uint8_t *end_ptr, double *outDouble) { ++ *outDouble = simdjson::internal::from_chars(reinterpret_cast(ptr), reinterpret_cast(end_ptr)); ++ // We do not accept infinite values. ++ ++ // Detecting finite values in a portable manner is ridiculously hard, ideally ++ // we would want to do: ++ // return !std::isfinite(*outDouble); ++ // but that mysteriously fails under legacy/old libc++ libraries, see ++ // https://github.com/simdjson/simdjson/issues/1286 ++ // ++ // Therefore, fall back to this solution (the extra parens are there ++ // to handle that max may be a macro on windows). ++ return !(*outDouble > (std::numeric_limits::max)() || *outDouble < std::numeric_limits::lowest()); ++} ++ ++// check quickly whether the next 8 chars are made of digits ++// at a glance, it looks better than Mula's ++// http://0x80.pl/articles/swar-digits-validate.html ++simdjson_inline bool is_made_of_eight_digits_fast(const uint8_t *chars) { ++ uint64_t val; ++ // this can read up to 7 bytes beyond the buffer size, but we require ++ // SIMDJSON_PADDING of padding ++ static_assert(7 <= SIMDJSON_PADDING, "SIMDJSON_PADDING must be bigger than 7"); ++ std::memcpy(&val, chars, 8); ++ // a branchy method might be faster: ++ // return (( val & 0xF0F0F0F0F0F0F0F0 ) == 0x3030303030303030) ++ // && (( (val + 0x0606060606060606) & 0xF0F0F0F0F0F0F0F0 ) == ++ // 0x3030303030303030); ++ return (((val & 0xF0F0F0F0F0F0F0F0) | ++ (((val + 0x0606060606060606) & 0xF0F0F0F0F0F0F0F0) >> 4)) == ++ 0x3333333333333333); ++} ++ ++template ++error_code slow_float_parsing(simdjson_unused const uint8_t * src, W writer) { ++ double d; ++ if (parse_float_fallback(src, &d)) { ++ writer.append_double(d); ++ return SUCCESS; ++ } ++ return INVALID_NUMBER(src); ++} ++ ++template ++SIMDJSON_NO_SANITIZE_UNDEFINED // We deliberately allow overflow here and check later ++simdjson_inline bool parse_digit(const uint8_t c, I &i) { ++ const uint8_t digit = static_cast(c - '0'); ++ if (digit > 9) { ++ return false; ++ } ++ // PERF NOTE: multiplication by 10 is cheaper than arbitrary integer multiplication ++ i = 10 * i + digit; // might overflow, we will handle the overflow later ++ return true; ++} ++ ++simdjson_inline error_code parse_decimal(simdjson_unused const uint8_t *const src, const uint8_t *&p, uint64_t &i, int64_t &exponent) { ++ // we continue with the fiction that we have an integer. If the ++ // floating point number is representable as x * 10^z for some integer ++ // z that fits in 53 bits, then we will be able to convert back the ++ // the integer into a float in a lossless manner. ++ const uint8_t *const first_after_period = p; ++ ++#ifdef SIMDJSON_SWAR_NUMBER_PARSING ++#if SIMDJSON_SWAR_NUMBER_PARSING ++ // this helps if we have lots of decimals! ++ // this turns out to be frequent enough. ++ if (is_made_of_eight_digits_fast(p)) { ++ i = i * 100000000 + parse_eight_digits_unrolled(p); ++ p += 8; ++ } ++#endif // SIMDJSON_SWAR_NUMBER_PARSING ++#endif // #ifdef SIMDJSON_SWAR_NUMBER_PARSING ++ // Unrolling the first digit makes a small difference on some implementations (e.g. westmere) ++ if (parse_digit(*p, i)) { ++p; } ++ while (parse_digit(*p, i)) { p++; } ++ exponent = first_after_period - p; ++ // Decimal without digits (123.) is illegal ++ if (exponent == 0) { ++ return INVALID_NUMBER(src); ++ } ++ return SUCCESS; ++} ++ ++simdjson_inline error_code parse_exponent(simdjson_unused const uint8_t *const src, const uint8_t *&p, int64_t &exponent) { ++ // Exp Sign: -123.456e[-]78 ++ bool neg_exp = ('-' == *p); ++ if (neg_exp || '+' == *p) { p++; } // Skip + as well ++ ++ // Exponent: -123.456e-[78] ++ auto start_exp = p; ++ int64_t exp_number = 0; ++ while (parse_digit(*p, exp_number)) { ++p; } ++ // It is possible for parse_digit to overflow. ++ // In particular, it could overflow to INT64_MIN, and we cannot do - INT64_MIN. ++ // Thus we *must* check for possible overflow before we negate exp_number. ++ ++ // Performance notes: it may seem like combining the two "simdjson_unlikely checks" below into ++ // a single simdjson_unlikely path would be faster. The reasoning is sound, but the compiler may ++ // not oblige and may, in fact, generate two distinct paths in any case. It might be ++ // possible to do uint64_t(p - start_exp - 1) >= 18 but it could end up trading off ++ // instructions for a simdjson_likely branch, an unconclusive gain. ++ ++ // If there were no digits, it's an error. ++ if (simdjson_unlikely(p == start_exp)) { ++ return INVALID_NUMBER(src); ++ } ++ // We have a valid positive exponent in exp_number at this point, except that ++ // it may have overflowed. ++ ++ // If there were more than 18 digits, we may have overflowed the integer. We have to do ++ // something!!!! ++ if (simdjson_unlikely(p > start_exp+18)) { ++ // Skip leading zeroes: 1e000000000000000000001 is technically valid and doesn't overflow ++ while (*start_exp == '0') { start_exp++; } ++ // 19 digits could overflow int64_t and is kind of absurd anyway. We don't ++ // support exponents smaller than -999,999,999,999,999,999 and bigger ++ // than 999,999,999,999,999,999. ++ // We can truncate. ++ // Note that 999999999999999999 is assuredly too large. The maximal ieee64 value before ++ // infinity is ~1.8e308. The smallest subnormal is ~5e-324. So, actually, we could ++ // truncate at 324. ++ // Note that there is no reason to fail per se at this point in time. ++ // E.g., 0e999999999999999999999 is a fine number. ++ if (p > start_exp+18) { exp_number = 999999999999999999; } ++ } ++ // At this point, we know that exp_number is a sane, positive, signed integer. ++ // It is <= 999,999,999,999,999,999. As long as 'exponent' is in ++ // [-8223372036854775808, 8223372036854775808], we won't overflow. Because 'exponent' ++ // is bounded in magnitude by the size of the JSON input, we are fine in this universe. ++ // To sum it up: the next line should never overflow. ++ exponent += (neg_exp ? -exp_number : exp_number); ++ return SUCCESS; ++} ++ ++simdjson_inline size_t significant_digits(const uint8_t * start_digits, size_t digit_count) { ++ // It is possible that the integer had an overflow. ++ // We have to handle the case where we have 0.0000somenumber. ++ const uint8_t *start = start_digits; ++ while ((*start == '0') || (*start == '.')) { ++start; } ++ // we over-decrement by one when there is a '.' ++ return digit_count - size_t(start - start_digits); ++} ++ ++template ++simdjson_inline error_code write_float(const uint8_t *const src, bool negative, uint64_t i, const uint8_t * start_digits, size_t digit_count, int64_t exponent, W &writer) { ++ // If we frequently had to deal with long strings of digits, ++ // we could extend our code by using a 128-bit integer instead ++ // of a 64-bit integer. However, this is uncommon in practice. ++ // ++ // 9999999999999999999 < 2**64 so we can accommodate 19 digits. ++ // If we have a decimal separator, then digit_count - 1 is the number of digits, but we ++ // may not have a decimal separator! ++ if (simdjson_unlikely(digit_count > 19 && significant_digits(start_digits, digit_count) > 19)) { ++ // Ok, chances are good that we had an overflow! ++ // this is almost never going to get called!!! ++ // we start anew, going slowly!!! ++ // This will happen in the following examples: ++ // 10000000000000000000000000000000000000000000e+308 ++ // 3.1415926535897932384626433832795028841971693993751 ++ // ++ // NOTE: This makes a *copy* of the writer and passes it to slow_float_parsing. This happens ++ // because slow_float_parsing is a non-inlined function. If we passed our writer reference to ++ // it, it would force it to be stored in memory, preventing the compiler from picking it apart ++ // and putting into registers. i.e. if we pass it as reference, it gets slow. ++ // This is what forces the skip_double, as well. ++ error_code error = slow_float_parsing(src, writer); ++ writer.skip_double(); ++ return error; ++ } ++ // NOTE: it's weird that the simdjson_unlikely() only wraps half the if, but it seems to get slower any other ++ // way we've tried: https://github.com/simdjson/simdjson/pull/990#discussion_r448497331 ++ // To future reader: we'd love if someone found a better way, or at least could explain this result! ++ if (simdjson_unlikely(exponent < simdjson::internal::smallest_power) || (exponent > simdjson::internal::largest_power)) { ++ // ++ // Important: smallest_power is such that it leads to a zero value. ++ // Observe that 18446744073709551615e-343 == 0, i.e. (2**64 - 1) e -343 is zero ++ // so something x 10^-343 goes to zero, but not so with something x 10^-342. ++ static_assert(simdjson::internal::smallest_power <= -342, "smallest_power is not small enough"); ++ // ++ if((exponent < simdjson::internal::smallest_power) || (i == 0)) { ++ // E.g. Parse "-0.0e-999" into the same value as "-0.0". See https://en.wikipedia.org/wiki/Signed_zero ++ WRITE_DOUBLE(negative ? -0.0 : 0.0, src, writer); ++ return SUCCESS; ++ } else { // (exponent > largest_power) and (i != 0) ++ // We have, for sure, an infinite value and simdjson refuses to parse infinite values. ++ return INVALID_NUMBER(src); ++ } ++ } ++ double d; ++ if (!compute_float_64(exponent, i, negative, d)) { ++ // we are almost never going to get here. ++ if (!parse_float_fallback(src, &d)) { return INVALID_NUMBER(src); } ++ } ++ WRITE_DOUBLE(d, src, writer); ++ return SUCCESS; ++} ++ ++// for performance analysis, it is sometimes useful to skip parsing ++#ifdef SIMDJSON_SKIPNUMBERPARSING ++ ++template ++simdjson_inline error_code parse_number(const uint8_t *const, W &writer) { ++ writer.append_s64(0); // always write zero ++ return SUCCESS; // always succeeds ++} ++ ++simdjson_unused simdjson_inline simdjson_result parse_unsigned(const uint8_t * const src) noexcept { return 0; } ++simdjson_unused simdjson_inline simdjson_result parse_integer(const uint8_t * const src) noexcept { return 0; } ++simdjson_unused simdjson_inline simdjson_result parse_double(const uint8_t * const src) noexcept { return 0; } ++simdjson_unused simdjson_inline simdjson_result parse_unsigned_in_string(const uint8_t * const src) noexcept { return 0; } ++simdjson_unused simdjson_inline simdjson_result parse_integer_in_string(const uint8_t * const src) noexcept { return 0; } ++simdjson_unused simdjson_inline simdjson_result parse_double_in_string(const uint8_t * const src) noexcept { return 0; } ++simdjson_unused simdjson_inline bool is_negative(const uint8_t * src) noexcept { return false; } ++simdjson_unused simdjson_inline simdjson_result is_integer(const uint8_t * src) noexcept { return false; } ++simdjson_unused simdjson_inline simdjson_result get_number_type(const uint8_t * src) noexcept { return ondemand::number_type::signed_integer; } ++#else ++ ++// parse the number at src ++// define JSON_TEST_NUMBERS for unit testing ++// ++// It is assumed that the number is followed by a structural ({,},],[) character ++// or a white space character. If that is not the case (e.g., when the JSON ++// document is made of a single number), then it is necessary to copy the ++// content and append a space before calling this function. ++// ++// Our objective is accurate parsing (ULP of 0) at high speed. ++template ++simdjson_inline error_code parse_number(const uint8_t *const src, W &writer) { ++ ++ // ++ // Check for minus sign ++ // ++ bool negative = (*src == '-'); ++ const uint8_t *p = src + uint8_t(negative); ++ ++ // ++ // Parse the integer part. ++ // ++ // PERF NOTE: we don't use is_made_of_eight_digits_fast because large integers like 123456789 are rare ++ const uint8_t *const start_digits = p; ++ uint64_t i = 0; ++ while (parse_digit(*p, i)) { p++; } ++ ++ // If there were no digits, or if the integer starts with 0 and has more than one digit, it's an error. ++ // Optimization note: size_t is expected to be unsigned. ++ size_t digit_count = size_t(p - start_digits); ++ if (digit_count == 0 || ('0' == *start_digits && digit_count > 1)) { return INVALID_NUMBER(src); } ++ ++ // ++ // Handle floats if there is a . or e (or both) ++ // ++ int64_t exponent = 0; ++ bool is_float = false; ++ if ('.' == *p) { ++ is_float = true; ++ ++p; ++ SIMDJSON_TRY( parse_decimal(src, p, i, exponent) ); ++ digit_count = int(p - start_digits); // used later to guard against overflows ++ } ++ if (('e' == *p) || ('E' == *p)) { ++ is_float = true; ++ ++p; ++ SIMDJSON_TRY( parse_exponent(src, p, exponent) ); ++ } ++ if (is_float) { ++ const bool dirty_end = jsoncharutils::is_not_structural_or_whitespace(*p); ++ SIMDJSON_TRY( write_float(src, negative, i, start_digits, digit_count, exponent, writer) ); ++ if (dirty_end) { return INVALID_NUMBER(src); } ++ return SUCCESS; ++ } ++ ++ // The longest negative 64-bit number is 19 digits. ++ // The longest positive 64-bit number is 20 digits. ++ // We do it this way so we don't trigger this branch unless we must. ++ size_t longest_digit_count = negative ? 19 : 20; ++ if (digit_count > longest_digit_count) { return INVALID_NUMBER(src); } ++ if (digit_count == longest_digit_count) { ++ if (negative) { ++ // Anything negative above INT64_MAX+1 is invalid ++ if (i > uint64_t(INT64_MAX)+1) { return INVALID_NUMBER(src); } ++ WRITE_INTEGER(~i+1, src, writer); ++ if (jsoncharutils::is_not_structural_or_whitespace(*p)) { return INVALID_NUMBER(src); } ++ return SUCCESS; ++ // Positive overflow check: ++ // - A 20 digit number starting with 2-9 is overflow, because 18,446,744,073,709,551,615 is the ++ // biggest uint64_t. ++ // - A 20 digit number starting with 1 is overflow if it is less than INT64_MAX. ++ // If we got here, it's a 20 digit number starting with the digit "1". ++ // - If a 20 digit number starting with 1 overflowed (i*10+digit), the result will be smaller ++ // than 1,553,255,926,290,448,384. ++ // - That is smaller than the smallest possible 20-digit number the user could write: ++ // 10,000,000,000,000,000,000. ++ // - Therefore, if the number is positive and lower than that, it's overflow. ++ // - The value we are looking at is less than or equal to INT64_MAX. ++ // ++ } else if (src[0] != uint8_t('1') || i <= uint64_t(INT64_MAX)) { return INVALID_NUMBER(src); } ++ } ++ ++ // Write unsigned if it doesn't fit in a signed integer. ++ if (i > uint64_t(INT64_MAX)) { ++ WRITE_UNSIGNED(i, src, writer); ++ } else { ++ WRITE_INTEGER(negative ? (~i+1) : i, src, writer); ++ } ++ if (jsoncharutils::is_not_structural_or_whitespace(*p)) { return INVALID_NUMBER(src); } ++ return SUCCESS; ++} ++ ++// Inlineable functions ++namespace { ++ ++// This table can be used to characterize the final character of an integer ++// string. For JSON structural character and allowable white space characters, ++// we return SUCCESS. For 'e', '.' and 'E', we return INCORRECT_TYPE. Otherwise ++// we return NUMBER_ERROR. ++// Optimization note: we could easily reduce the size of the table by half (to 128) ++// at the cost of an extra branch. ++// Optimization note: we want the values to use at most 8 bits (not, e.g., 32 bits): ++static_assert(error_code(uint8_t(NUMBER_ERROR))== NUMBER_ERROR, "bad NUMBER_ERROR cast"); ++static_assert(error_code(uint8_t(SUCCESS))== SUCCESS, "bad NUMBER_ERROR cast"); ++static_assert(error_code(uint8_t(INCORRECT_TYPE))== INCORRECT_TYPE, "bad NUMBER_ERROR cast"); ++ ++const uint8_t integer_string_finisher[256] = { ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, SUCCESS, ++ SUCCESS, NUMBER_ERROR, NUMBER_ERROR, SUCCESS, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, SUCCESS, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, SUCCESS, ++ NUMBER_ERROR, INCORRECT_TYPE, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, SUCCESS, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, INCORRECT_TYPE, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, SUCCESS, NUMBER_ERROR, SUCCESS, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, INCORRECT_TYPE, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, SUCCESS, NUMBER_ERROR, ++ SUCCESS, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, NUMBER_ERROR, ++ NUMBER_ERROR}; ++ ++// Parse any number from 0 to 18,446,744,073,709,551,615 ++simdjson_unused simdjson_inline simdjson_result parse_unsigned(const uint8_t * const src) noexcept { ++ const uint8_t *p = src; ++ // ++ // Parse the integer part. ++ // ++ // PERF NOTE: we don't use is_made_of_eight_digits_fast because large integers like 123456789 are rare ++ const uint8_t *const start_digits = p; ++ uint64_t i = 0; ++ while (parse_digit(*p, i)) { p++; } ++ ++ // If there were no digits, or if the integer starts with 0 and has more than one digit, it's an error. ++ // Optimization note: size_t is expected to be unsigned. ++ size_t digit_count = size_t(p - start_digits); ++ // The longest positive 64-bit number is 20 digits. ++ // We do it this way so we don't trigger this branch unless we must. ++ // Optimization note: the compiler can probably merge ++ // ((digit_count == 0) || (digit_count > 20)) ++ // into a single branch since digit_count is unsigned. ++ if ((digit_count == 0) || (digit_count > 20)) { return INCORRECT_TYPE; } ++ // Here digit_count > 0. ++ if (('0' == *start_digits) && (digit_count > 1)) { return NUMBER_ERROR; } ++ // We can do the following... ++ // if (!jsoncharutils::is_structural_or_whitespace(*p)) { ++ // return (*p == '.' || *p == 'e' || *p == 'E') ? INCORRECT_TYPE : NUMBER_ERROR; ++ // } ++ // as a single table lookup: ++ if (integer_string_finisher[*p] != SUCCESS) { return error_code(integer_string_finisher[*p]); } ++ ++ if (digit_count == 20) { ++ // Positive overflow check: ++ // - A 20 digit number starting with 2-9 is overflow, because 18,446,744,073,709,551,615 is the ++ // biggest uint64_t. ++ // - A 20 digit number starting with 1 is overflow if it is less than INT64_MAX. ++ // If we got here, it's a 20 digit number starting with the digit "1". ++ // - If a 20 digit number starting with 1 overflowed (i*10+digit), the result will be smaller ++ // than 1,553,255,926,290,448,384. ++ // - That is smaller than the smallest possible 20-digit number the user could write: ++ // 10,000,000,000,000,000,000. ++ // - Therefore, if the number is positive and lower than that, it's overflow. ++ // - The value we are looking at is less than or equal to INT64_MAX. ++ // ++ if (src[0] != uint8_t('1') || i <= uint64_t(INT64_MAX)) { return INCORRECT_TYPE; } ++ } ++ ++ return i; ++} ++ ++ ++// Parse any number from 0 to 18,446,744,073,709,551,615 ++// Never read at src_end or beyond ++simdjson_unused simdjson_inline simdjson_result parse_unsigned(const uint8_t * const src, const uint8_t * const src_end) noexcept { ++ const uint8_t *p = src; ++ // ++ // Parse the integer part. ++ // ++ // PERF NOTE: we don't use is_made_of_eight_digits_fast because large integers like 123456789 are rare ++ const uint8_t *const start_digits = p; ++ uint64_t i = 0; ++ while ((p != src_end) && parse_digit(*p, i)) { p++; } ++ ++ // If there were no digits, or if the integer starts with 0 and has more than one digit, it's an error. ++ // Optimization note: size_t is expected to be unsigned. ++ size_t digit_count = size_t(p - start_digits); ++ // The longest positive 64-bit number is 20 digits. ++ // We do it this way so we don't trigger this branch unless we must. ++ // Optimization note: the compiler can probably merge ++ // ((digit_count == 0) || (digit_count > 20)) ++ // into a single branch since digit_count is unsigned. ++ if ((digit_count == 0) || (digit_count > 20)) { return INCORRECT_TYPE; } ++ // Here digit_count > 0. ++ if (('0' == *start_digits) && (digit_count > 1)) { return NUMBER_ERROR; } ++ // We can do the following... ++ // if (!jsoncharutils::is_structural_or_whitespace(*p)) { ++ // return (*p == '.' || *p == 'e' || *p == 'E') ? INCORRECT_TYPE : NUMBER_ERROR; ++ // } ++ // as a single table lookup: ++ if ((p != src_end) && integer_string_finisher[*p] != SUCCESS) { return error_code(integer_string_finisher[*p]); } ++ ++ if (digit_count == 20) { ++ // Positive overflow check: ++ // - A 20 digit number starting with 2-9 is overflow, because 18,446,744,073,709,551,615 is the ++ // biggest uint64_t. ++ // - A 20 digit number starting with 1 is overflow if it is less than INT64_MAX. ++ // If we got here, it's a 20 digit number starting with the digit "1". ++ // - If a 20 digit number starting with 1 overflowed (i*10+digit), the result will be smaller ++ // than 1,553,255,926,290,448,384. ++ // - That is smaller than the smallest possible 20-digit number the user could write: ++ // 10,000,000,000,000,000,000. ++ // - Therefore, if the number is positive and lower than that, it's overflow. ++ // - The value we are looking at is less than or equal to INT64_MAX. ++ // ++ if (src[0] != uint8_t('1') || i <= uint64_t(INT64_MAX)) { return INCORRECT_TYPE; } ++ } ++ ++ return i; ++} ++ ++// Parse any number from 0 to 18,446,744,073,709,551,615 ++simdjson_unused simdjson_inline simdjson_result parse_unsigned_in_string(const uint8_t * const src) noexcept { ++ const uint8_t *p = src + 1; ++ // ++ // Parse the integer part. ++ // ++ // PERF NOTE: we don't use is_made_of_eight_digits_fast because large integers like 123456789 are rare ++ const uint8_t *const start_digits = p; ++ uint64_t i = 0; ++ while (parse_digit(*p, i)) { p++; } ++ ++ // If there were no digits, or if the integer starts with 0 and has more than one digit, it's an error. ++ // Optimization note: size_t is expected to be unsigned. ++ size_t digit_count = size_t(p - start_digits); ++ // The longest positive 64-bit number is 20 digits. ++ // We do it this way so we don't trigger this branch unless we must. ++ // Optimization note: the compiler can probably merge ++ // ((digit_count == 0) || (digit_count > 20)) ++ // into a single branch since digit_count is unsigned. ++ if ((digit_count == 0) || (digit_count > 20)) { return INCORRECT_TYPE; } ++ // Here digit_count > 0. ++ if (('0' == *start_digits) && (digit_count > 1)) { return NUMBER_ERROR; } ++ // We can do the following... ++ // if (!jsoncharutils::is_structural_or_whitespace(*p)) { ++ // return (*p == '.' || *p == 'e' || *p == 'E') ? INCORRECT_TYPE : NUMBER_ERROR; ++ // } ++ // as a single table lookup: ++ if (*p != '"') { return NUMBER_ERROR; } ++ ++ if (digit_count == 20) { ++ // Positive overflow check: ++ // - A 20 digit number starting with 2-9 is overflow, because 18,446,744,073,709,551,615 is the ++ // biggest uint64_t. ++ // - A 20 digit number starting with 1 is overflow if it is less than INT64_MAX. ++ // If we got here, it's a 20 digit number starting with the digit "1". ++ // - If a 20 digit number starting with 1 overflowed (i*10+digit), the result will be smaller ++ // than 1,553,255,926,290,448,384. ++ // - That is smaller than the smallest possible 20-digit number the user could write: ++ // 10,000,000,000,000,000,000. ++ // - Therefore, if the number is positive and lower than that, it's overflow. ++ // - The value we are looking at is less than or equal to INT64_MAX. ++ // ++ // Note: we use src[1] and not src[0] because src[0] is the quote character in this ++ // instance. ++ if (src[1] != uint8_t('1') || i <= uint64_t(INT64_MAX)) { return INCORRECT_TYPE; } ++ } ++ ++ return i; ++} ++ ++// Parse any number from -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 ++simdjson_unused simdjson_inline simdjson_result parse_integer(const uint8_t *src) noexcept { ++ // ++ // Check for minus sign ++ // ++ bool negative = (*src == '-'); ++ const uint8_t *p = src + uint8_t(negative); ++ ++ // ++ // Parse the integer part. ++ // ++ // PERF NOTE: we don't use is_made_of_eight_digits_fast because large integers like 123456789 are rare ++ const uint8_t *const start_digits = p; ++ uint64_t i = 0; ++ while (parse_digit(*p, i)) { p++; } ++ ++ // If there were no digits, or if the integer starts with 0 and has more than one digit, it's an error. ++ // Optimization note: size_t is expected to be unsigned. ++ size_t digit_count = size_t(p - start_digits); ++ // We go from ++ // -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 ++ // so we can never represent numbers that have more than 19 digits. ++ size_t longest_digit_count = 19; ++ // Optimization note: the compiler can probably merge ++ // ((digit_count == 0) || (digit_count > longest_digit_count)) ++ // into a single branch since digit_count is unsigned. ++ if ((digit_count == 0) || (digit_count > longest_digit_count)) { return INCORRECT_TYPE; } ++ // Here digit_count > 0. ++ if (('0' == *start_digits) && (digit_count > 1)) { return NUMBER_ERROR; } ++ // We can do the following... ++ // if (!jsoncharutils::is_structural_or_whitespace(*p)) { ++ // return (*p == '.' || *p == 'e' || *p == 'E') ? INCORRECT_TYPE : NUMBER_ERROR; ++ // } ++ // as a single table lookup: ++ if(integer_string_finisher[*p] != SUCCESS) { return error_code(integer_string_finisher[*p]); } ++ // Negative numbers have can go down to - INT64_MAX - 1 whereas positive numbers are limited to INT64_MAX. ++ // Performance note: This check is only needed when digit_count == longest_digit_count but it is ++ // so cheap that we might as well always make it. ++ if(i > uint64_t(INT64_MAX) + uint64_t(negative)) { return INCORRECT_TYPE; } ++ return negative ? (~i+1) : i; ++} ++ ++// Parse any number from -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 ++// Never read at src_end or beyond ++simdjson_unused simdjson_inline simdjson_result parse_integer(const uint8_t * const src, const uint8_t * const src_end) noexcept { ++ // ++ // Check for minus sign ++ // ++ if(src == src_end) { return NUMBER_ERROR; } ++ bool negative = (*src == '-'); ++ const uint8_t *p = src + uint8_t(negative); ++ ++ // ++ // Parse the integer part. ++ // ++ // PERF NOTE: we don't use is_made_of_eight_digits_fast because large integers like 123456789 are rare ++ const uint8_t *const start_digits = p; ++ uint64_t i = 0; ++ while ((p != src_end) && parse_digit(*p, i)) { p++; } ++ ++ // If there were no digits, or if the integer starts with 0 and has more than one digit, it's an error. ++ // Optimization note: size_t is expected to be unsigned. ++ size_t digit_count = size_t(p - start_digits); ++ // We go from ++ // -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 ++ // so we can never represent numbers that have more than 19 digits. ++ size_t longest_digit_count = 19; ++ // Optimization note: the compiler can probably merge ++ // ((digit_count == 0) || (digit_count > longest_digit_count)) ++ // into a single branch since digit_count is unsigned. ++ if ((digit_count == 0) || (digit_count > longest_digit_count)) { return INCORRECT_TYPE; } ++ // Here digit_count > 0. ++ if (('0' == *start_digits) && (digit_count > 1)) { return NUMBER_ERROR; } ++ // We can do the following... ++ // if (!jsoncharutils::is_structural_or_whitespace(*p)) { ++ // return (*p == '.' || *p == 'e' || *p == 'E') ? INCORRECT_TYPE : NUMBER_ERROR; ++ // } ++ // as a single table lookup: ++ if((p != src_end) && integer_string_finisher[*p] != SUCCESS) { return error_code(integer_string_finisher[*p]); } ++ // Negative numbers have can go down to - INT64_MAX - 1 whereas positive numbers are limited to INT64_MAX. ++ // Performance note: This check is only needed when digit_count == longest_digit_count but it is ++ // so cheap that we might as well always make it. ++ if(i > uint64_t(INT64_MAX) + uint64_t(negative)) { return INCORRECT_TYPE; } ++ return negative ? (~i+1) : i; ++} ++ ++// Parse any number from -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 ++simdjson_unused simdjson_inline simdjson_result parse_integer_in_string(const uint8_t *src) noexcept { ++ // ++ // Check for minus sign ++ // ++ bool negative = (*(src + 1) == '-'); ++ src += uint8_t(negative) + 1; ++ ++ // ++ // Parse the integer part. ++ // ++ // PERF NOTE: we don't use is_made_of_eight_digits_fast because large integers like 123456789 are rare ++ const uint8_t *const start_digits = src; ++ uint64_t i = 0; ++ while (parse_digit(*src, i)) { src++; } ++ ++ // If there were no digits, or if the integer starts with 0 and has more than one digit, it's an error. ++ // Optimization note: size_t is expected to be unsigned. ++ size_t digit_count = size_t(src - start_digits); ++ // We go from ++ // -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 ++ // so we can never represent numbers that have more than 19 digits. ++ size_t longest_digit_count = 19; ++ // Optimization note: the compiler can probably merge ++ // ((digit_count == 0) || (digit_count > longest_digit_count)) ++ // into a single branch since digit_count is unsigned. ++ if ((digit_count == 0) || (digit_count > longest_digit_count)) { return INCORRECT_TYPE; } ++ // Here digit_count > 0. ++ if (('0' == *start_digits) && (digit_count > 1)) { return NUMBER_ERROR; } ++ // We can do the following... ++ // if (!jsoncharutils::is_structural_or_whitespace(*src)) { ++ // return (*src == '.' || *src == 'e' || *src == 'E') ? INCORRECT_TYPE : NUMBER_ERROR; ++ // } ++ // as a single table lookup: ++ if(*src != '"') { return NUMBER_ERROR; } ++ // Negative numbers have can go down to - INT64_MAX - 1 whereas positive numbers are limited to INT64_MAX. ++ // Performance note: This check is only needed when digit_count == longest_digit_count but it is ++ // so cheap that we might as well always make it. ++ if(i > uint64_t(INT64_MAX) + uint64_t(negative)) { return INCORRECT_TYPE; } ++ return negative ? (~i+1) : i; ++} ++ ++simdjson_unused simdjson_inline simdjson_result parse_double(const uint8_t * src) noexcept { ++ // ++ // Check for minus sign ++ // ++ bool negative = (*src == '-'); ++ src += uint8_t(negative); ++ ++ // ++ // Parse the integer part. ++ // ++ uint64_t i = 0; ++ const uint8_t *p = src; ++ p += parse_digit(*p, i); ++ bool leading_zero = (i == 0); ++ while (parse_digit(*p, i)) { p++; } ++ // no integer digits, or 0123 (zero must be solo) ++ if ( p == src ) { return INCORRECT_TYPE; } ++ if ( (leading_zero && p != src+1)) { return NUMBER_ERROR; } ++ ++ // ++ // Parse the decimal part. ++ // ++ int64_t exponent = 0; ++ bool overflow; ++ if (simdjson_likely(*p == '.')) { ++ p++; ++ const uint8_t *start_decimal_digits = p; ++ if (!parse_digit(*p, i)) { return NUMBER_ERROR; } // no decimal digits ++ p++; ++ while (parse_digit(*p, i)) { p++; } ++ exponent = -(p - start_decimal_digits); ++ ++ // Overflow check. More than 19 digits (minus the decimal) may be overflow. ++ overflow = p-src-1 > 19; ++ if (simdjson_unlikely(overflow && leading_zero)) { ++ // Skip leading 0.00000 and see if it still overflows ++ const uint8_t *start_digits = src + 2; ++ while (*start_digits == '0') { start_digits++; } ++ overflow = start_digits-src > 19; ++ } ++ } else { ++ overflow = p-src > 19; ++ } ++ ++ // ++ // Parse the exponent ++ // ++ if (*p == 'e' || *p == 'E') { ++ p++; ++ bool exp_neg = *p == '-'; ++ p += exp_neg || *p == '+'; ++ ++ uint64_t exp = 0; ++ const uint8_t *start_exp_digits = p; ++ while (parse_digit(*p, exp)) { p++; } ++ // no exp digits, or 20+ exp digits ++ if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } ++ ++ exponent += exp_neg ? 0-exp : exp; ++ } ++ ++ if (jsoncharutils::is_not_structural_or_whitespace(*p)) { return NUMBER_ERROR; } ++ ++ overflow = overflow || exponent < simdjson::internal::smallest_power || exponent > simdjson::internal::largest_power; ++ ++ // ++ // Assemble (or slow-parse) the float ++ // ++ double d; ++ if (simdjson_likely(!overflow)) { ++ if (compute_float_64(exponent, i, negative, d)) { return d; } ++ } ++ if (!parse_float_fallback(src - uint8_t(negative), &d)) { ++ return NUMBER_ERROR; ++ } ++ return d; ++} ++ ++simdjson_unused simdjson_inline bool is_negative(const uint8_t * src) noexcept { ++ return (*src == '-'); ++} ++ ++simdjson_unused simdjson_inline simdjson_result is_integer(const uint8_t * src) noexcept { ++ bool negative = (*src == '-'); ++ src += uint8_t(negative); ++ const uint8_t *p = src; ++ while(static_cast(*p - '0') <= 9) { p++; } ++ if ( p == src ) { return NUMBER_ERROR; } ++ if (jsoncharutils::is_structural_or_whitespace(*p)) { return true; } ++ return false; ++} ++ ++simdjson_unused simdjson_inline simdjson_result get_number_type(const uint8_t * src) noexcept { ++ bool negative = (*src == '-'); ++ src += uint8_t(negative); ++ const uint8_t *p = src; ++ while(static_cast(*p - '0') <= 9) { p++; } ++ if ( p == src ) { return NUMBER_ERROR; } ++ if (jsoncharutils::is_structural_or_whitespace(*p)) { ++ // We have an integer. ++ // If the number is negative and valid, it must be a signed integer. ++ if(negative) { return ondemand::number_type::signed_integer; } ++ // We want values larger or equal to 9223372036854775808 to be unsigned ++ // integers, and the other values to be signed integers. ++ int digit_count = int(p - src); ++ if(digit_count >= 19) { ++ const uint8_t * smaller_big_integer = reinterpret_cast("9223372036854775808"); ++ if((digit_count >= 20) || (memcmp(src, smaller_big_integer, 19) >= 0)) { ++ return ondemand::number_type::unsigned_integer; ++ } ++ } ++ return ondemand::number_type::signed_integer; ++ } ++ // Hopefully, we have 'e' or 'E' or '.'. ++ return ondemand::number_type::floating_point_number; ++} ++ ++// Never read at src_end or beyond ++simdjson_unused simdjson_inline simdjson_result parse_double(const uint8_t * src, const uint8_t * const src_end) noexcept { ++ if(src == src_end) { return NUMBER_ERROR; } ++ // ++ // Check for minus sign ++ // ++ bool negative = (*src == '-'); ++ src += uint8_t(negative); ++ ++ // ++ // Parse the integer part. ++ // ++ uint64_t i = 0; ++ const uint8_t *p = src; ++ if(p == src_end) { return NUMBER_ERROR; } ++ p += parse_digit(*p, i); ++ bool leading_zero = (i == 0); ++ while ((p != src_end) && parse_digit(*p, i)) { p++; } ++ // no integer digits, or 0123 (zero must be solo) ++ if ( p == src ) { return INCORRECT_TYPE; } ++ if ( (leading_zero && p != src+1)) { return NUMBER_ERROR; } ++ ++ // ++ // Parse the decimal part. ++ // ++ int64_t exponent = 0; ++ bool overflow; ++ if (simdjson_likely((p != src_end) && (*p == '.'))) { ++ p++; ++ const uint8_t *start_decimal_digits = p; ++ if ((p == src_end) || !parse_digit(*p, i)) { return NUMBER_ERROR; } // no decimal digits ++ p++; ++ while ((p != src_end) && parse_digit(*p, i)) { p++; } ++ exponent = -(p - start_decimal_digits); ++ ++ // Overflow check. More than 19 digits (minus the decimal) may be overflow. ++ overflow = p-src-1 > 19; ++ if (simdjson_unlikely(overflow && leading_zero)) { ++ // Skip leading 0.00000 and see if it still overflows ++ const uint8_t *start_digits = src + 2; ++ while (*start_digits == '0') { start_digits++; } ++ overflow = start_digits-src > 19; ++ } ++ } else { ++ overflow = p-src > 19; ++ } ++ ++ // ++ // Parse the exponent ++ // ++ if ((p != src_end) && (*p == 'e' || *p == 'E')) { ++ p++; ++ if(p == src_end) { return NUMBER_ERROR; } ++ bool exp_neg = *p == '-'; ++ p += exp_neg || *p == '+'; ++ ++ uint64_t exp = 0; ++ const uint8_t *start_exp_digits = p; ++ while ((p != src_end) && parse_digit(*p, exp)) { p++; } ++ // no exp digits, or 20+ exp digits ++ if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } ++ ++ exponent += exp_neg ? 0-exp : exp; ++ } ++ ++ if ((p != src_end) && jsoncharutils::is_not_structural_or_whitespace(*p)) { return NUMBER_ERROR; } ++ ++ overflow = overflow || exponent < simdjson::internal::smallest_power || exponent > simdjson::internal::largest_power; ++ ++ // ++ // Assemble (or slow-parse) the float ++ // ++ double d; ++ if (simdjson_likely(!overflow)) { ++ if (compute_float_64(exponent, i, negative, d)) { return d; } ++ } ++ if (!parse_float_fallback(src - uint8_t(negative), src_end, &d)) { ++ return NUMBER_ERROR; ++ } ++ return d; ++} ++ ++simdjson_unused simdjson_inline simdjson_result parse_double_in_string(const uint8_t * src) noexcept { ++ // ++ // Check for minus sign ++ // ++ bool negative = (*(src + 1) == '-'); ++ src += uint8_t(negative) + 1; ++ ++ // ++ // Parse the integer part. ++ // ++ uint64_t i = 0; ++ const uint8_t *p = src; ++ p += parse_digit(*p, i); ++ bool leading_zero = (i == 0); ++ while (parse_digit(*p, i)) { p++; } ++ // no integer digits, or 0123 (zero must be solo) ++ if ( p == src ) { return INCORRECT_TYPE; } ++ if ( (leading_zero && p != src+1)) { return NUMBER_ERROR; } ++ ++ // ++ // Parse the decimal part. ++ // ++ int64_t exponent = 0; ++ bool overflow; ++ if (simdjson_likely(*p == '.')) { ++ p++; ++ const uint8_t *start_decimal_digits = p; ++ if (!parse_digit(*p, i)) { return NUMBER_ERROR; } // no decimal digits ++ p++; ++ while (parse_digit(*p, i)) { p++; } ++ exponent = -(p - start_decimal_digits); ++ ++ // Overflow check. More than 19 digits (minus the decimal) may be overflow. ++ overflow = p-src-1 > 19; ++ if (simdjson_unlikely(overflow && leading_zero)) { ++ // Skip leading 0.00000 and see if it still overflows ++ const uint8_t *start_digits = src + 2; ++ while (*start_digits == '0') { start_digits++; } ++ overflow = start_digits-src > 19; ++ } ++ } else { ++ overflow = p-src > 19; ++ } ++ ++ // ++ // Parse the exponent ++ // ++ if (*p == 'e' || *p == 'E') { ++ p++; ++ bool exp_neg = *p == '-'; ++ p += exp_neg || *p == '+'; ++ ++ uint64_t exp = 0; ++ const uint8_t *start_exp_digits = p; ++ while (parse_digit(*p, exp)) { p++; } ++ // no exp digits, or 20+ exp digits ++ if (p-start_exp_digits == 0 || p-start_exp_digits > 19) { return NUMBER_ERROR; } ++ ++ exponent += exp_neg ? 0-exp : exp; ++ } ++ ++ if (*p != '"') { return NUMBER_ERROR; } ++ ++ overflow = overflow || exponent < simdjson::internal::smallest_power || exponent > simdjson::internal::largest_power; ++ ++ // ++ // Assemble (or slow-parse) the float ++ // ++ double d; ++ if (simdjson_likely(!overflow)) { ++ if (compute_float_64(exponent, i, negative, d)) { return d; } ++ } ++ if (!parse_float_fallback(src - uint8_t(negative), &d)) { ++ return NUMBER_ERROR; ++ } ++ return d; ++} ++} //namespace {} ++#endif // SIMDJSON_SKIPNUMBERPARSING ++ ++} // namespace numberparsing ++} // unnamed namespace ++} // namespace westmere ++} // namespace simdjson ++/* end file include/simdjson/generic/numberparsing.h */ ++ ++#endif // SIMDJSON_WESTMERE_NUMBERPARSING_H ++/* end file include/simdjson/westmere/numberparsing.h */ ++/* begin file include/simdjson/westmere/end.h */ ++SIMDJSON_UNTARGET_WESTMERE ++/* end file include/simdjson/westmere/end.h */ ++ ++#endif // SIMDJSON_IMPLEMENTATION_WESTMERE ++#endif // SIMDJSON_WESTMERE_COMMON_H ++/* end file include/simdjson/westmere.h */ ++ ++// Builtin implementation ++ ++SIMDJSON_POP_DISABLE_WARNINGS ++ ++#endif // SIMDJSON_IMPLEMENTATIONS_H ++/* end file include/simdjson/implementations.h */ ++ ++// Determine the best builtin implementation ++#ifndef SIMDJSON_BUILTIN_IMPLEMENTATION ++#if SIMDJSON_CAN_ALWAYS_RUN_ICELAKE ++#define SIMDJSON_BUILTIN_IMPLEMENTATION icelake ++#elif SIMDJSON_CAN_ALWAYS_RUN_HASWELL ++#define SIMDJSON_BUILTIN_IMPLEMENTATION haswell ++#elif SIMDJSON_CAN_ALWAYS_RUN_WESTMERE ++#define SIMDJSON_BUILTIN_IMPLEMENTATION westmere ++#elif SIMDJSON_CAN_ALWAYS_RUN_ARM64 ++#define SIMDJSON_BUILTIN_IMPLEMENTATION arm64 ++#elif SIMDJSON_CAN_ALWAYS_RUN_PPC64 ++#define SIMDJSON_BUILTIN_IMPLEMENTATION ppc64 ++#elif SIMDJSON_CAN_ALWAYS_RUN_FALLBACK ++#define SIMDJSON_BUILTIN_IMPLEMENTATION fallback ++#else ++#error "All possible implementations (including fallback) have been disabled! simdjson will not run." ++#endif ++#endif // SIMDJSON_BUILTIN_IMPLEMENTATION ++ ++// redefining SIMDJSON_IMPLEMENTATION to "SIMDJSON_BUILTIN_IMPLEMENTATION" ++// #define SIMDJSON_IMPLEMENTATION SIMDJSON_BUILTIN_IMPLEMENTATION ++ ++// ondemand is only compiled as part of the builtin implementation at present ++ ++// Interface declarations ++/* begin file include/simdjson/generic/implementation_simdjson_result_base.h */ ++namespace simdjson { ++namespace SIMDJSON_BUILTIN_IMPLEMENTATION { ++ ++// This is a near copy of include/error.h's implementation_simdjson_result_base, except it doesn't use std::pair ++// so we can avoid inlining errors ++// TODO reconcile these! ++/** ++ * The result of a simdjson operation that could fail. ++ * ++ * Gives the option of reading error codes, or throwing an exception by casting to the desired result. ++ * ++ * This is a base class for implementations that want to add functions to the result type for ++ * chaining. ++ * ++ * Override like: ++ * ++ * struct simdjson_result : public internal::implementation_simdjson_result_base { ++ * simdjson_result() noexcept : internal::implementation_simdjson_result_base() {} ++ * simdjson_result(error_code error) noexcept : internal::implementation_simdjson_result_base(error) {} ++ * simdjson_result(T &&value) noexcept : internal::implementation_simdjson_result_base(std::forward(value)) {} ++ * simdjson_result(T &&value, error_code error) noexcept : internal::implementation_simdjson_result_base(value, error) {} ++ * // Your extra methods here ++ * } ++ * ++ * Then any method returning simdjson_result will be chainable with your methods. ++ */ ++template ++struct implementation_simdjson_result_base { ++ ++ /** ++ * Create a new empty result with error = UNINITIALIZED. ++ */ ++ simdjson_inline implementation_simdjson_result_base() noexcept = default; ++ ++ /** ++ * Create a new error result. ++ */ ++ simdjson_inline implementation_simdjson_result_base(error_code error) noexcept; ++ ++ /** ++ * Create a new successful result. ++ */ ++ simdjson_inline implementation_simdjson_result_base(T &&value) noexcept; ++ ++ /** ++ * Create a new result with both things (use if you don't want to branch when creating the result). ++ */ ++ simdjson_inline implementation_simdjson_result_base(T &&value, error_code error) noexcept; ++ ++ /** ++ * Move the value and the error to the provided variables. ++ * ++ * @param value The variable to assign the value to. May not be set if there is an error. ++ * @param error The variable to assign the error to. Set to SUCCESS if there is no error. ++ */ ++ simdjson_inline void tie(T &value, error_code &error) && noexcept; ++ ++ /** ++ * Move the value to the provided variable. ++ * ++ * @param value The variable to assign the value to. May not be set if there is an error. ++ */ ++ simdjson_inline error_code get(T &value) && noexcept; ++ ++ /** ++ * The error. ++ */ ++ simdjson_inline error_code error() const noexcept; ++ ++#if SIMDJSON_EXCEPTIONS ++ ++ /** ++ * Get the result value. ++ * ++ * @throw simdjson_error if there was an error. ++ */ ++ simdjson_inline T& value() & noexcept(false); ++ ++ /** ++ * Take the result value (move it). ++ * ++ * @throw simdjson_error if there was an error. ++ */ ++ simdjson_inline T&& value() && noexcept(false); ++ ++ /** ++ * Take the result value (move it). ++ * ++ * @throw simdjson_error if there was an error. ++ */ ++ simdjson_inline T&& take_value() && noexcept(false); ++ ++ /** ++ * Cast to the value (will throw on error). ++ * ++ * @throw simdjson_error if there was an error. ++ */ ++ simdjson_inline operator T&&() && noexcept(false); ++ ++ ++#endif // SIMDJSON_EXCEPTIONS ++ ++ /** ++ * Get the result value. This function is safe if and only ++ * the error() method returns a value that evaluates to false. ++ */ ++ simdjson_inline const T& value_unsafe() const& noexcept; ++ /** ++ * Get the result value. This function is safe if and only ++ * the error() method returns a value that evaluates to false. ++ */ ++ simdjson_inline T& value_unsafe() & noexcept; ++ /** ++ * Take the result value (move it). This function is safe if and only ++ * the error() method returns a value that evaluates to false. ++ */ ++ simdjson_inline T&& value_unsafe() && noexcept; ++protected: ++ /** users should never directly access first and second. **/ ++ T first{}; /** Users should never directly access 'first'. **/ ++ error_code second{UNINITIALIZED}; /** Users should never directly access 'second'. **/ ++}; // struct implementation_simdjson_result_base ++ ++} // namespace SIMDJSON_BUILTIN_IMPLEMENTATION ++} // namespace simdjson ++/* end file include/simdjson/generic/implementation_simdjson_result_base.h */ ++/* begin file include/simdjson/generic/ondemand.h */ ++namespace simdjson { ++namespace SIMDJSON_BUILTIN_IMPLEMENTATION { ++/** ++ * A fast, simple, DOM-like interface that parses JSON as you use it. ++ * ++ * Designed for maximum speed and a lower memory profile. ++ */ ++namespace ondemand { ++ ++/** Represents the depth of a JSON value (number of nested arrays/objects). */ ++using depth_t = int32_t; ++ ++} // namespace ondemand ++} // namespace SIMDJSON_BUILTIN_IMPLEMENTATION ++} // namespace simdjson ++ ++/* begin file include/simdjson/generic/ondemand/json_type.h */ ++namespace simdjson { ++namespace SIMDJSON_BUILTIN_IMPLEMENTATION { ++namespace ondemand { ++/** ++ * The type of a JSON value. ++ */ ++enum class json_type { ++ // Start at 1 to catch uninitialized / default values more easily ++ array=1, ///< A JSON array ( [ 1, 2, 3 ... ] ) ++ object, ///< A JSON object ( { "a": 1, "b" 2, ... } ) ++ number, ///< A JSON number ( 1 or -2.3 or 4.5e6 ...) ++ string, ///< A JSON string ( "a" or "hello world\n" ...) ++ boolean, ///< A JSON boolean (true or false) ++ null ///< A JSON null (null) ++}; ++ ++class value_iterator; ++ ++/** ++ * A type representing a JSON number. ++ * The design of the struct is deliberately straight-forward. All ++ * functions return standard values with no error check. ++ */ ++struct number { ++ ++ /** ++ * return the automatically determined type of ++ * the number: number_type::floating_point_number, ++ * number_type::signed_integer or number_type::unsigned_integer. ++ * ++ * enum class number_type { ++ * floating_point_number=1, /// a binary64 number ++ * signed_integer, /// a signed integer that fits in a 64-bit word using two's complement ++ * unsigned_integer /// a positive integer larger or equal to 1<<63 ++ * }; ++ */ ++ simdjson_inline number_type get_number_type() const noexcept; ++ /** ++ * return true if the automatically determined type of ++ * the number is number_type::unsigned_integer. ++ */ ++ simdjson_inline bool is_uint64() const noexcept; ++ /** ++ * return the value as a uint64_t, only valid if is_uint64() is true. ++ */ ++ simdjson_inline uint64_t get_uint64() const noexcept; ++ simdjson_inline operator uint64_t() const noexcept; ++ ++ /** ++ * return true if the automatically determined type of ++ * the number is number_type::signed_integer. ++ */ ++ simdjson_inline bool is_int64() const noexcept; ++ /** ++ * return the value as a int64_t, only valid if is_int64() is true. ++ */ ++ simdjson_inline int64_t get_int64() const noexcept; ++ simdjson_inline operator int64_t() const noexcept; ++ ++ ++ /** ++ * return true if the automatically determined type of ++ * the number is number_type::floating_point_number. ++ */ ++ simdjson_inline bool is_double() const noexcept; ++ /** ++ * return the value as a double, only valid if is_double() is true. ++ */ ++ simdjson_inline double get_double() const noexcept; ++ simdjson_inline operator double() const noexcept; ++ ++ /** ++ * Convert the number to a double. Though it always succeed, the conversion ++ * may be lossy if the number cannot be represented exactly. ++ */ ++ simdjson_inline double as_double() const noexcept; ++ ++ ++protected: ++ /** ++ * The next block of declaration is designed so that we can call the number parsing ++ * functions on a number type. They are protected and should never be used outside ++ * of the core simdjson library. ++ */ ++ friend class value_iterator; ++ template ++ friend error_code numberparsing::write_float(const uint8_t *const src, bool negative, uint64_t i, const uint8_t * start_digits, size_t digit_count, int64_t exponent, W &writer); ++ template ++ friend error_code numberparsing::parse_number(const uint8_t *const src, W &writer); ++ template ++ friend error_code numberparsing::slow_float_parsing(simdjson_unused const uint8_t * src, W writer); ++ /** Store a signed 64-bit value to the number. */ ++ simdjson_inline void append_s64(int64_t value) noexcept; ++ /** Store an unsigned 64-bit value to the number. */ ++ simdjson_inline void append_u64(uint64_t value) noexcept; ++ /** Store a double value to the number. */ ++ simdjson_inline void append_double(double value) noexcept; ++ /** Specifies that the value is a double, but leave it undefined. */ ++ simdjson_inline void skip_double() noexcept; ++ /** ++ * End of friend declarations. ++ */ ++ ++ /** ++ * Our attributes are a union type (size = 64 bits) ++ * followed by a type indicator. ++ */ ++ union { ++ double floating_point_number; ++ int64_t signed_integer; ++ uint64_t unsigned_integer; ++ } payload{0}; ++ number_type type{number_type::signed_integer}; ++}; ++ ++/** ++ * Write the JSON type to the output stream ++ * ++ * @param out The output stream. ++ * @param type The json_type. ++ */ ++inline std::ostream& operator<<(std::ostream& out, json_type type) noexcept; ++inline std::ostream& operator<<(std::ostream& out, number_type type) noexcept; ++ ++#if SIMDJSON_EXCEPTIONS ++/** ++ * Send JSON type to an output stream. ++ * ++ * @param out The output stream. ++ * @param type The json_type. ++ * @throw simdjson_error if the result being printed has an error. If there is an error with the ++ * underlying output stream, that error will be propagated (simdjson_error will not be ++ * thrown). ++ */ ++inline std::ostream& operator<<(std::ostream& out, simdjson_result &type) noexcept(false); ++#endif ++ ++} // namespace ondemand ++} // namespace SIMDJSON_BUILTIN_IMPLEMENTATION ++} // namespace simdjson ++ ++namespace simdjson { ++ ++template<> ++struct simdjson_result : public SIMDJSON_BUILTIN_IMPLEMENTATION::implementation_simdjson_result_base { ++public: ++ simdjson_inline simdjson_result(SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand::json_type &&value) noexcept; ///< @private ++ simdjson_inline simdjson_result(error_code error) noexcept; ///< @private ++ simdjson_inline simdjson_result() noexcept = default; ++ simdjson_inline ~simdjson_result() noexcept = default; ///< @private ++}; ++ ++} // namespace simdjson ++/* end file include/simdjson/generic/ondemand/json_type.h */ ++/* begin file include/simdjson/generic/ondemand/token_position.h */ ++namespace simdjson { ++namespace SIMDJSON_BUILTIN_IMPLEMENTATION { ++namespace ondemand { ++ ++/** @private Position in the JSON buffer indexes */ ++using token_position = const uint32_t *; ++ ++} // namespace ondemand ++} // namespace SIMDJSON_BUILTIN_IMPLEMENTATION ++} // namespace simdjson ++/* end file include/simdjson/generic/ondemand/token_position.h */ ++/* begin file include/simdjson/generic/ondemand/logger.h */ ++namespace simdjson { ++namespace SIMDJSON_BUILTIN_IMPLEMENTATION { ++namespace ondemand { ++ ++class json_iterator; ++class value_iterator; ++ ++namespace logger { ++ ++#if SIMDJSON_VERBOSE_LOGGING ++ static constexpr const bool LOG_ENABLED = true; ++#else ++ static constexpr const bool LOG_ENABLED = false; ++#endif ++ ++// We do not want these functions to be 'really inlined' since real inlining is ++// for performance purposes and if you are using the loggers, you do not care about ++// performance (or should not). ++static inline void log_headers() noexcept; ++static inline void log_line(const json_iterator &iter, token_position index, depth_t depth, const char *title_prefix, const char *title, std::string_view detail) noexcept; ++static inline void log_line(const json_iterator &iter, const char *title_prefix, const char *title, std::string_view detail, int delta, int depth_delta) noexcept; ++static inline void log_event(const json_iterator &iter, const char *type, std::string_view detail="", int delta=0, int depth_delta=0) noexcept; ++static inline void log_value(const json_iterator &iter, token_position index, depth_t depth, const char *type, std::string_view detail="") noexcept; ++static inline void log_value(const json_iterator &iter, const char *type, std::string_view detail="", int delta=-1, int depth_delta=0) noexcept; ++static inline void log_start_value(const json_iterator &iter, token_position index, depth_t depth, const char *type, std::string_view detail="") noexcept; ++static inline void log_start_value(const json_iterator &iter, const char *type, int delta=-1, int depth_delta=0) noexcept; ++static inline void log_end_value(const json_iterator &iter, const char *type, int delta=-1, int depth_delta=0) noexcept; ++static inline void log_error(const json_iterator &iter, token_position index, depth_t depth, const char *error, const char *detail="") noexcept; ++static inline void log_error(const json_iterator &iter, const char *error, const char *detail="", int delta=-1, int depth_delta=0) noexcept; ++ ++static inline void log_event(const value_iterator &iter, const char *type, std::string_view detail="", int delta=0, int depth_delta=0) noexcept; ++static inline void log_value(const value_iterator &iter, const char *type, std::string_view detail="", int delta=-1, int depth_delta=0) noexcept; ++static inline void log_start_value(const value_iterator &iter, const char *type, int delta=-1, int depth_delta=0) noexcept; ++static inline void log_end_value(const value_iterator &iter, const char *type, int delta=-1, int depth_delta=0) noexcept; ++static inline void log_error(const value_iterator &iter, const char *error, const char *detail="", int delta=-1, int depth_delta=0) noexcept; ++ ++} // namespace logger ++} // namespace ondemand ++} // namespace SIMDJSON_BUILTIN_IMPLEMENTATION ++} // namespace simdjson ++/* end file include/simdjson/generic/ondemand/logger.h */ ++/* begin file include/simdjson/generic/ondemand/raw_json_string.h */ ++ ++namespace simdjson { ++namespace SIMDJSON_BUILTIN_IMPLEMENTATION { ++namespace ondemand { ++ ++class object; ++class parser; ++class json_iterator; ++ ++/** ++ * A string escaped per JSON rules, terminated with quote ("). They are used to represent ++ * unescaped keys inside JSON documents. ++ * ++ * (In other words, a pointer to the beginning of a string, just after the start quote, inside a ++ * JSON file.) ++ * ++ * This class is deliberately simplistic and has little functionality. You can ++ * compare a raw_json_string instance with an unescaped C string, but ++ * that is nearly all you can do. ++ * ++ * The raw_json_string is unescaped. If you wish to write an unescaped version of it to your own ++ * buffer, you may do so using the parser.unescape(string, buff) method, using an ondemand::parser ++ * instance. Doing so requires you to have a sufficiently large buffer. ++ * ++ * The raw_json_string instances originate typically from field instance which in turn represent ++ * key-value pairs from object instances. From a field instance, you get the raw_json_string ++ * instance by calling key(). You can, if you want a more usable string_view instance, call ++ * the unescaped_key() method on the field instance. You may also create a raw_json_string from ++ * any other string value, with the value.get_raw_json_string() method. Again, you can get ++ * a more usable string_view instance by calling get_string(). ++ * ++ */ ++class raw_json_string { ++public: ++ /** ++ * Create a new invalid raw_json_string. ++ * ++ * Exists so you can declare a variable and later assign to it before use. ++ */ ++ simdjson_inline raw_json_string() noexcept = default; ++ ++ /** ++ * Create a new invalid raw_json_string pointed at the given location in the JSON. ++ * ++ * The given location must be just *after* the beginning quote (") in the JSON file. ++ * ++ * It *must* be terminated by a ", and be a valid JSON string. ++ */ ++ simdjson_inline raw_json_string(const uint8_t * _buf) noexcept; ++ /** ++ * Get the raw pointer to the beginning of the string in the JSON (just after the "). ++ * ++ * It is possible for this function to return a null pointer if the instance ++ * has outlived its existence. ++ */ ++ simdjson_inline const char * raw() const noexcept; ++ ++ /** ++ * This compares the current instance to the std::string_view target: returns true if ++ * they are byte-by-byte equal (no escaping is done) on target.size() characters, ++ * and if the raw_json_string instance has a quote character at byte index target.size(). ++ * We never read more than length + 1 bytes in the raw_json_string instance. ++ * If length is smaller than target.size(), this will return false. ++ * ++ * The std::string_view instance may contain any characters. However, the caller ++ * is responsible for setting length so that length bytes may be read in the ++ * raw_json_string. ++ * ++ * Performance: the comparison may be done using memcmp which may be efficient ++ * for long strings. ++ */ ++ simdjson_inline bool unsafe_is_equal(size_t length, std::string_view target) const noexcept; ++ ++ /** ++ * This compares the current instance to the std::string_view target: returns true if ++ * they are byte-by-byte equal (no escaping is done). ++ * The std::string_view instance should not contain unescaped quote characters: ++ * the caller is responsible for this check. See is_free_from_unescaped_quote. ++ * ++ * Performance: the comparison is done byte-by-byte which might be inefficient for ++ * long strings. ++ * ++ * If target is a compile-time constant, and your compiler likes you, ++ * you should be able to do the following without performance penalty... ++ * ++ * static_assert(raw_json_string::is_free_from_unescaped_quote(target), ""); ++ * s.unsafe_is_equal(target); ++ */ ++ simdjson_inline bool unsafe_is_equal(std::string_view target) const noexcept; ++ ++ /** ++ * This compares the current instance to the C string target: returns true if ++ * they are byte-by-byte equal (no escaping is done). ++ * The provided C string should not contain an unescaped quote character: ++ * the caller is responsible for this check. See is_free_from_unescaped_quote. ++ * ++ * If target is a compile-time constant, and your compiler likes you, ++ * you should be able to do the following without performance penalty... ++ * ++ * static_assert(raw_json_string::is_free_from_unescaped_quote(target), ""); ++ * s.unsafe_is_equal(target); ++ */ ++ simdjson_inline bool unsafe_is_equal(const char* target) const noexcept; ++ ++ /** ++ * This compares the current instance to the std::string_view target: returns true if ++ * they are byte-by-byte equal (no escaping is done). ++ */ ++ simdjson_inline bool is_equal(std::string_view target) const noexcept; ++ ++ /** ++ * This compares the current instance to the C string target: returns true if ++ * they are byte-by-byte equal (no escaping is done). ++ */ ++ simdjson_inline bool is_equal(const char* target) const noexcept; ++ ++ /** ++ * Returns true if target is free from unescaped quote. If target is known at ++ * compile-time, we might expect the computation to happen at compile time with ++ * many compilers (not all!). ++ */ ++ static simdjson_inline bool is_free_from_unescaped_quote(std::string_view target) noexcept; ++ static simdjson_inline bool is_free_from_unescaped_quote(const char* target) noexcept; ++ ++private: ++ ++ ++ /** ++ * This will set the inner pointer to zero, effectively making ++ * this instance unusable. ++ */ ++ simdjson_inline void consume() noexcept { buf = nullptr; } ++ ++ /** ++ * Checks whether the inner pointer is non-null and thus usable. ++ */ ++ simdjson_inline simdjson_warn_unused bool alive() const noexcept { return buf != nullptr; } ++ ++ /** ++ * Unescape this JSON string, replacing \\ with \, \n with newline, etc. ++ * ++ * ## IMPORTANT: string_view lifetime ++ * ++ * The string_view is only valid until the next parse() call on the parser. ++ * ++ * @param iter A json_iterator, which contains a buffer where the string will be written. ++ */ ++ simdjson_inline simdjson_warn_unused simdjson_result unescape(json_iterator &iter) const noexcept; ++ ++ const uint8_t * buf{}; ++ friend class object; ++ friend class field; ++ friend class parser; ++ friend struct simdjson_result; ++}; ++ ++simdjson_unused simdjson_inline std::ostream &operator<<(std::ostream &, const raw_json_string &) noexcept; ++ ++/** ++ * Comparisons between raw_json_string and std::string_view instances are potentially unsafe: the user is responsible ++ * for providing a string with no unescaped quote. Note that unescaped quotes cannot be present in valid JSON strings. ++ */ ++simdjson_unused simdjson_inline bool operator==(const raw_json_string &a, std::string_view c) noexcept; ++simdjson_unused simdjson_inline bool operator==(std::string_view c, const raw_json_string &a) noexcept; ++simdjson_unused simdjson_inline bool operator!=(const raw_json_string &a, std::string_view c) noexcept; ++simdjson_unused simdjson_inline bool operator!=(std::string_view c, const raw_json_string &a) noexcept; ++ ++ ++} // namespace ondemand ++} // namespace SIMDJSON_BUILTIN_IMPLEMENTATION ++} // namespace simdjson ++ ++namespace simdjson { ++ ++template<> ++struct simdjson_result : public SIMDJSON_BUILTIN_IMPLEMENTATION::implementation_simdjson_result_base { ++public: ++ simdjson_inline simdjson_result(SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand::raw_json_string &&value) noexcept; ///< @private ++ simdjson_inline simdjson_result(error_code error) noexcept; ///< @private ++ simdjson_inline simdjson_result() noexcept = default; ++ simdjson_inline ~simdjson_result() noexcept = default; ///< @private ++ ++ simdjson_inline simdjson_result raw() const noexcept; ++ simdjson_inline simdjson_warn_unused simdjson_result unescape(SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand::json_iterator &iter) const noexcept; ++}; ++ ++} // namespace simdjson ++/* end file include/simdjson/generic/ondemand/raw_json_string.h */ ++/* begin file include/simdjson/generic/ondemand/token_iterator.h */ ++namespace simdjson { ++namespace SIMDJSON_BUILTIN_IMPLEMENTATION { ++namespace ondemand { ++ ++/** ++ * Iterates through JSON tokens (`{` `}` `[` `]` `,` `:` `""` `123` `true` `false` `null`) ++ * detected by stage 1. ++ * ++ * @private This is not intended for external use. ++ */ ++class token_iterator { ++public: ++ /** ++ * Create a new invalid token_iterator. ++ * ++ * Exists so you can declare a variable and later assign to it before use. ++ */ ++ simdjson_inline token_iterator() noexcept = default; ++ simdjson_inline token_iterator(token_iterator &&other) noexcept = default; ++ simdjson_inline token_iterator &operator=(token_iterator &&other) noexcept = default; ++ simdjson_inline token_iterator(const token_iterator &other) noexcept = default; ++ simdjson_inline token_iterator &operator=(const token_iterator &other) noexcept = default; ++ ++ /** ++ * Advance to the next token (returning the current one). ++ */ ++ simdjson_inline const uint8_t *return_current_and_advance() noexcept; ++ /** ++ * Reports the current offset in bytes from the start of the underlying buffer. ++ */ ++ simdjson_inline uint32_t current_offset() const noexcept; ++ /** ++ * Get the JSON text for a given token (relative). ++ * ++ * This is not null-terminated; it is a view into the JSON. ++ * ++ * @param delta The relative position of the token to retrieve. e.g. 0 = current token, ++ * 1 = next token, -1 = prev token. ++ * ++ * TODO consider a string_view, assuming the length will get stripped out by the optimizer when ++ * it isn't used ... ++ */ ++ simdjson_inline const uint8_t *peek(int32_t delta=0) const noexcept; ++ /** ++ * Get the maximum length of the JSON text for a given token. ++ * ++ * The length will include any whitespace at the end of the token. ++ * ++ * @param delta The relative position of the token to retrieve. e.g. 0 = current token, ++ * 1 = next token, -1 = prev token. ++ */ ++ simdjson_inline uint32_t peek_length(int32_t delta=0) const noexcept; ++ ++ /** ++ * Get the JSON text for a given token. ++ * ++ * This is not null-terminated; it is a view into the JSON. ++ * ++ * @param position The position of the token. ++ * ++ */ ++ simdjson_inline const uint8_t *peek(token_position position) const noexcept; ++ /** ++ * Get the maximum length of the JSON text for a given token. ++ * ++ * The length will include any whitespace at the end of the token. ++ * ++ * @param position The position of the token. ++ */ ++ simdjson_inline uint32_t peek_length(token_position position) const noexcept; ++ ++ /** ++ * Return the current index. ++ */ ++ simdjson_inline token_position position() const noexcept; ++ /** ++ * Reset to a previously saved index. ++ */ ++ simdjson_inline void set_position(token_position target_position) noexcept; ++ ++ // NOTE: we don't support a full C++ iterator interface, because we expect people to make ++ // different calls to advance the iterator based on *their own* state. ++ ++ simdjson_inline bool operator==(const token_iterator &other) const noexcept; ++ simdjson_inline bool operator!=(const token_iterator &other) const noexcept; ++ simdjson_inline bool operator>(const token_iterator &other) const noexcept; ++ simdjson_inline bool operator>=(const token_iterator &other) const noexcept; ++ simdjson_inline bool operator<(const token_iterator &other) const noexcept; ++ simdjson_inline bool operator<=(const token_iterator &other) const noexcept; ++ ++protected: ++ simdjson_inline token_iterator(const uint8_t *buf, token_position position) noexcept; ++ ++ /** ++ * Get the index of the JSON text for a given token (relative). ++ * ++ * This is not null-terminated; it is a view into the JSON. ++ * ++ * @param delta The relative position of the token to retrieve. e.g. 0 = current token, ++ * 1 = next token, -1 = prev token. ++ */ ++ simdjson_inline uint32_t peek_index(int32_t delta=0) const noexcept; ++ /** ++ * Get the index of the JSON text for a given token. ++ * ++ * This is not null-terminated; it is a view into the JSON. ++ * ++ * @param position The position of the token. ++ * ++ */ ++ simdjson_inline uint32_t peek_index(token_position position) const noexcept; ++ ++ const uint8_t *buf{}; ++ token_position _position{}; ++ ++ friend class json_iterator; ++ friend class value_iterator; ++ friend class object; ++ friend simdjson_inline void logger::log_line(const json_iterator &iter, const char *title_prefix, const char *title, std::string_view detail, int delta, int depth_delta) noexcept; ++ friend simdjson_inline void logger::log_line(const json_iterator &iter, token_position index, depth_t depth, const char *title_prefix, const char *title, std::string_view detail) noexcept; ++}; ++ ++} // namespace ondemand ++} // namespace SIMDJSON_BUILTIN_IMPLEMENTATION ++} // namespace simdjson ++ ++namespace simdjson { ++ ++template<> ++struct simdjson_result : public SIMDJSON_BUILTIN_IMPLEMENTATION::implementation_simdjson_result_base { ++public: ++ simdjson_inline simdjson_result(SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand::token_iterator &&value) noexcept; ///< @private ++ simdjson_inline simdjson_result(error_code error) noexcept; ///< @private ++ simdjson_inline simdjson_result() noexcept = default; ++ simdjson_inline ~simdjson_result() noexcept = default; ///< @private ++}; ++ ++} // namespace simdjson ++/* end file include/simdjson/generic/ondemand/token_iterator.h */ ++/* begin file include/simdjson/generic/ondemand/json_iterator.h */ ++namespace simdjson { ++namespace SIMDJSON_BUILTIN_IMPLEMENTATION { ++namespace ondemand { ++ ++class document; ++class document_stream; ++class object; ++class array; ++class value; ++class raw_json_string; ++class parser; ++ ++/** ++ * Iterates through JSON tokens, keeping track of depth and string buffer. ++ * ++ * @private This is not intended for external use. ++ */ ++class json_iterator { ++protected: ++ token_iterator token{}; ++ ondemand::parser *parser{}; ++ /** ++ * Next free location in the string buffer. ++ * ++ * Used by raw_json_string::unescape() to have a place to unescape strings to. ++ */ ++ uint8_t *_string_buf_loc{}; ++ /** ++ * JSON error, if there is one. ++ * ++ * INCORRECT_TYPE and NO_SUCH_FIELD are *not* stored here, ever. ++ * ++ * PERF NOTE: we *hope* this will be elided into control flow, as it is only used (a) in the first ++ * iteration of the loop, or (b) for the final iteration after a missing comma is found in ++. If ++ * this is not elided, we should make sure it's at least not using up a register. Failing that, ++ * we should store it in document so there's only one of them. ++ */ ++ error_code error{SUCCESS}; ++ /** ++ * Depth of the current token in the JSON. ++ * ++ * - 0 = finished with document ++ * - 1 = document root value (could be [ or {, not yet known) ++ * - 2 = , or } inside root array/object ++ * - 3 = key or value inside root array/object. ++ */ ++ depth_t _depth{}; ++ /** ++ * Beginning of the document indexes. ++ * Normally we have root == parser->implementation->structural_indexes.get() ++ * but this may differ, especially in streaming mode (where we have several ++ * documents); ++ */ ++ token_position _root{}; ++ /** ++ * Normally, a json_iterator operates over a single document, but in ++ * some cases, we may have a stream of documents. This attribute is meant ++ * as meta-data: the json_iterator works the same irrespective of the ++ * value of this attribute. ++ */ ++ bool _streaming{false}; ++ ++public: ++ simdjson_inline json_iterator() noexcept = default; ++ simdjson_inline json_iterator(json_iterator &&other) noexcept; ++ simdjson_inline json_iterator &operator=(json_iterator &&other) noexcept; ++ simdjson_inline explicit json_iterator(const json_iterator &other) noexcept = default; ++ simdjson_inline json_iterator &operator=(const json_iterator &other) noexcept = default; ++ /** ++ * Skips a JSON value, whether it is a scalar, array or object. ++ */ ++ simdjson_warn_unused simdjson_inline error_code skip_child(depth_t parent_depth) noexcept; ++ ++ /** ++ * Tell whether the iterator is still at the start ++ */ ++ simdjson_inline bool at_root() const noexcept; ++ ++ /** ++ * Tell whether we should be expected to run in streaming ++ * mode (iterating over many documents). It is pure metadata ++ * that does not affect how the iterator works. It is used by ++ * start_root_array() and start_root_object(). ++ */ ++ simdjson_inline bool streaming() const noexcept; ++ ++ /** ++ * Get the root value iterator ++ */ ++ simdjson_inline token_position root_position() const noexcept; ++ /** ++ * Assert that we are at the document depth (== 1) ++ */ ++ simdjson_inline void assert_at_document_depth() const noexcept; ++ /** ++ * Assert that we are at the root of the document ++ */ ++ simdjson_inline void assert_at_root() const noexcept; ++ ++ /** ++ * Tell whether the iterator is at the EOF mark ++ */ ++ simdjson_inline bool at_end() const noexcept; ++ ++ /** ++ * Tell whether the iterator is live (has not been moved). ++ */ ++ simdjson_inline bool is_alive() const noexcept; ++ ++ /** ++ * Abandon this iterator, setting depth to 0 (as if the document is finished). ++ */ ++ simdjson_inline void abandon() noexcept; ++ ++ /** ++ * Advance the current token without modifying depth. ++ */ ++ simdjson_inline const uint8_t *return_current_and_advance() noexcept; ++ ++ /** ++ * Returns true if there is a single token in the index (i.e., it is ++ * a JSON with a scalar value such as a single number). ++ * ++ * @return whether there is a single token ++ */ ++ simdjson_inline bool is_single_token() const noexcept; ++ ++ /** ++ * Assert that there are at least the given number of tokens left. ++ * ++ * Has no effect in release builds. ++ */ ++ simdjson_inline void assert_more_tokens(uint32_t required_tokens=1) const noexcept; ++ /** ++ * Assert that the given position addresses an actual token (is within bounds). ++ * ++ * Has no effect in release builds. ++ */ ++ simdjson_inline void assert_valid_position(token_position position) const noexcept; ++ /** ++ * Get the JSON text for a given token (relative). ++ * ++ * This is not null-terminated; it is a view into the JSON. ++ * ++ * @param delta The relative position of the token to retrieve. e.g. 0 = next token, -1 = prev token. ++ * ++ * TODO consider a string_view, assuming the length will get stripped out by the optimizer when ++ * it isn't used ... ++ */ ++ simdjson_inline const uint8_t *peek(int32_t delta=0) const noexcept; ++ /** ++ * Get the maximum length of the JSON text for the current token (or relative). ++ * ++ * The length will include any whitespace at the end of the token. ++ * ++ * @param delta The relative position of the token to retrieve. e.g. 0 = next token, -1 = prev token. ++ */ ++ simdjson_inline uint32_t peek_length(int32_t delta=0) const noexcept; ++ /** ++ * Get a pointer to the current location in the input buffer. ++ * ++ * This is not null-terminated; it is a view into the JSON. ++ * ++ * You may be pointing outside of the input buffer: it is not generally ++ * safe to dereference this pointer. ++ */ ++ simdjson_inline const uint8_t *unsafe_pointer() const noexcept; ++ /** ++ * Get the JSON text for a given token. ++ * ++ * This is not null-terminated; it is a view into the JSON. ++ * ++ * @param position The position of the token to retrieve. ++ * ++ * TODO consider a string_view, assuming the length will get stripped out by the optimizer when ++ * it isn't used ... ++ */ ++ simdjson_inline const uint8_t *peek(token_position position) const noexcept; ++ /** ++ * Get the maximum length of the JSON text for the current token (or relative). ++ * ++ * The length will include any whitespace at the end of the token. ++ * ++ * @param position The position of the token to retrieve. ++ */ ++ simdjson_inline uint32_t peek_length(token_position position) const noexcept; ++ /** ++ * Get the JSON text for the last token in the document. ++ * ++ * This is not null-terminated; it is a view into the JSON. ++ * ++ * TODO consider a string_view, assuming the length will get stripped out by the optimizer when ++ * it isn't used ... ++ */ ++ simdjson_inline const uint8_t *peek_last() const noexcept; ++ ++ /** ++ * Ascend one level. ++ * ++ * Validates that the depth - 1 == parent_depth. ++ * ++ * @param parent_depth the expected parent depth. ++ */ ++ simdjson_inline void ascend_to(depth_t parent_depth) noexcept; ++ ++ /** ++ * Descend one level. ++ * ++ * Validates that the new depth == child_depth. ++ * ++ * @param child_depth the expected child depth. ++ */ ++ simdjson_inline void descend_to(depth_t child_depth) noexcept; ++ simdjson_inline void descend_to(depth_t child_depth, int32_t delta) noexcept; ++ ++ /** ++ * Get current depth. ++ */ ++ simdjson_inline depth_t depth() const noexcept; ++ ++ /** ++ * Get current (writeable) location in the string buffer. ++ */ ++ simdjson_inline uint8_t *&string_buf_loc() noexcept; ++ ++ /** ++ * Report an unrecoverable error, preventing further iteration. ++ * ++ * @param error The error to report. Must not be SUCCESS, UNINITIALIZED, INCORRECT_TYPE, or NO_SUCH_FIELD. ++ * @param message An error message to report with the error. ++ */ ++ simdjson_inline error_code report_error(error_code error, const char *message) noexcept; ++ ++ /** ++ * Log error, but don't stop iteration. ++ * @param error The error to report. Must be INCORRECT_TYPE, or NO_SUCH_FIELD. ++ * @param message An error message to report with the error. ++ */ ++ simdjson_inline error_code optional_error(error_code error, const char *message) noexcept; ++ ++ template simdjson_warn_unused simdjson_inline bool copy_to_buffer(const uint8_t *json, uint32_t max_len, uint8_t (&tmpbuf)[N]) noexcept; ++ ++ simdjson_inline token_position position() const noexcept; ++ /** ++ * Write the raw_json_string to the string buffer and return a string_view. ++ * Each raw_json_string should be unescaped once, or else the string buffer might ++ * overflow. ++ */ ++ simdjson_inline simdjson_result unescape(raw_json_string in) noexcept; ++ simdjson_inline void reenter_child(token_position position, depth_t child_depth) noexcept; ++ ++#if SIMDJSON_DEVELOPMENT_CHECKS ++ simdjson_inline token_position start_position(depth_t depth) const noexcept; ++ simdjson_inline void set_start_position(depth_t depth, token_position position) noexcept; ++#endif ++ ++ /* Useful for debugging and logging purposes. */ ++ inline std::string to_string() const noexcept; ++ ++ /** ++ * Returns the current location in the document if in bounds. ++ */ ++ inline simdjson_result current_location() noexcept; ++ ++ /** ++ * Updates this json iterator so that it is back at the beginning of the document, ++ * as if it had just been created. ++ */ ++ inline void rewind() noexcept; ++ /** ++ * This checks whether the {,},[,] are balanced so that the document ++ * ends with proper zero depth. This requires scanning the whole document ++ * and it may be expensive. It is expected that it will be rarely called. ++ * It does not attempt to match { with } and [ with ]. ++ */ ++ inline bool balanced() const noexcept; ++protected: ++ simdjson_inline json_iterator(const uint8_t *buf, ondemand::parser *parser) noexcept; ++ /// The last token before the end ++ simdjson_inline token_position last_position() const noexcept; ++ /// The token *at* the end. This points at gibberish and should only be used for comparison. ++ simdjson_inline token_position end_position() const noexcept; ++ /// The end of the buffer. ++ simdjson_inline token_position end() const noexcept; ++ ++ friend class document; ++ friend class document_stream; ++ friend class object; ++ friend class array; ++ friend class value; ++ friend class raw_json_string; ++ friend class parser; ++ friend class value_iterator; ++ friend simdjson_inline void logger::log_line(const json_iterator &iter, const char *title_prefix, const char *title, std::string_view detail, int delta, int depth_delta) noexcept; ++ friend simdjson_inline void logger::log_line(const json_iterator &iter, token_position index, depth_t depth, const char *title_prefix, const char *title, std::string_view detail) noexcept; ++}; // json_iterator ++ ++} // namespace ondemand ++} // namespace SIMDJSON_BUILTIN_IMPLEMENTATION ++} // namespace simdjson ++ ++namespace simdjson { ++ ++template<> ++struct simdjson_result : public SIMDJSON_BUILTIN_IMPLEMENTATION::implementation_simdjson_result_base { ++public: ++ simdjson_inline simdjson_result(SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand::json_iterator &&value) noexcept; ///< @private ++ simdjson_inline simdjson_result(error_code error) noexcept; ///< @private ++ ++ simdjson_inline simdjson_result() noexcept = default; ++}; ++ ++} // namespace simdjson ++/* end file include/simdjson/generic/ondemand/json_iterator.h */ ++/* begin file include/simdjson/generic/ondemand/value_iterator.h */ ++namespace simdjson { ++namespace SIMDJSON_BUILTIN_IMPLEMENTATION { ++namespace ondemand { ++ ++class document; ++class object; ++class array; ++class value; ++class raw_json_string; ++class parser; ++ ++/** ++ * Iterates through a single JSON value at a particular depth. ++ * ++ * Does not keep track of the type of value: provides methods for objects, arrays and scalars and expects ++ * the caller to call the right ones. ++ * ++ * @private This is not intended for external use. ++ */ ++class value_iterator { ++protected: ++ /** The underlying JSON iterator */ ++ json_iterator *_json_iter{}; ++ /** The depth of this value */ ++ depth_t _depth{}; ++ /** ++ * The starting token index for this value ++ */ ++ token_position _start_position{}; ++ ++public: ++ simdjson_inline value_iterator() noexcept = default; ++ ++ /** ++ * Denote that we're starting a document. ++ */ ++ simdjson_inline void start_document() noexcept; ++ ++ /** ++ * Skips a non-iterated or partially-iterated JSON value, whether it is a scalar, array or object. ++ * ++ * Optimized for scalars. ++ */ ++ simdjson_warn_unused simdjson_inline error_code skip_child() noexcept; ++ ++ /** ++ * Tell whether the iterator is at the EOF mark ++ */ ++ simdjson_inline bool at_end() const noexcept; ++ ++ /** ++ * Tell whether the iterator is at the start of the value ++ */ ++ simdjson_inline bool at_start() const noexcept; ++ ++ /** ++ * Tell whether the value is open--if the value has not been used, or the array/object is still open. ++ */ ++ simdjson_inline bool is_open() const noexcept; ++ ++ /** ++ * Tell whether the value is at an object's first field (just after the {). ++ */ ++ simdjson_inline bool at_first_field() const noexcept; ++ ++ /** ++ * Abandon all iteration. ++ */ ++ simdjson_inline void abandon() noexcept; ++ ++ /** ++ * Get the child value as a value_iterator. ++ */ ++ simdjson_inline value_iterator child_value() const noexcept; ++ ++ /** ++ * Get the depth of this value. ++ */ ++ simdjson_inline int32_t depth() const noexcept; ++ ++ /** ++ * Get the JSON type of this value. ++ * ++ * @error TAPE_ERROR when the JSON value is a bad token like "}" "," or "alse". ++ */ ++ simdjson_inline simdjson_result type() const noexcept; ++ ++ /** ++ * @addtogroup object Object iteration ++ * ++ * Methods to iterate and find object fields. These methods generally *assume* the value is ++ * actually an object; the caller is responsible for keeping track of that fact. ++ * ++ * @{ ++ */ ++ ++ /** ++ * Start an object iteration. ++ * ++ * @returns Whether the object had any fields (returns false for empty). ++ * @error INCORRECT_TYPE if there is no opening { ++ */ ++ simdjson_warn_unused simdjson_inline simdjson_result start_object() noexcept; ++ /** ++ * Start an object iteration from the root. ++ * ++ * @returns Whether the object had any fields (returns false for empty). ++ * @error INCORRECT_TYPE if there is no opening { ++ * @error TAPE_ERROR if there is no matching } at end of document ++ */ ++ simdjson_warn_unused simdjson_inline simdjson_result start_root_object() noexcept; ++ ++ /** ++ * Start an object iteration after the user has already checked and moved past the {. ++ * ++ * Does not move the iterator unless the object is empty ({}). ++ * ++ * @returns Whether the object had any fields (returns false for empty). ++ * @error INCOMPLETE_ARRAY_OR_OBJECT If there are no more tokens (implying the *parent* ++ * array or object is incomplete). ++ */ ++ simdjson_warn_unused simdjson_inline simdjson_result started_object() noexcept; ++ /** ++ * Start an object iteration from the root, after the user has already checked and moved past the {. ++ * ++ * Does not move the iterator unless the object is empty ({}). ++ * ++ * @returns Whether the object had any fields (returns false for empty). ++ * @error INCOMPLETE_ARRAY_OR_OBJECT If there are no more tokens (implying the *parent* ++ * array or object is incomplete). ++ */ ++ simdjson_warn_unused simdjson_inline simdjson_result started_root_object() noexcept; ++ ++ /** ++ * Moves to the next field in an object. ++ * ++ * Looks for , and }. If } is found, the object is finished and the iterator advances past it. ++ * Otherwise, it advances to the next value. ++ * ++ * @return whether there is another field in the object. ++ * @error TAPE_ERROR If there is a comma missing between fields. ++ * @error TAPE_ERROR If there is a comma, but not enough tokens remaining to have a key, :, and value. ++ */ ++ simdjson_warn_unused simdjson_inline simdjson_result has_next_field() noexcept; ++ ++ /** ++ * Get the current field's key. ++ */ ++ simdjson_warn_unused simdjson_inline simdjson_result field_key() noexcept; ++ ++ /** ++ * Pass the : in the field and move to its value. ++ */ ++ simdjson_warn_unused simdjson_inline error_code field_value() noexcept; ++ ++ /** ++ * Find the next field with the given key. ++ * ++ * Assumes you have called next_field() or otherwise matched the previous value. ++ * ++ * This means the iterator must be sitting at the next key: ++ * ++ * ``` ++ * { "a": 1, "b": 2 } ++ * ^ ++ * ``` ++ * ++ * Key is *raw JSON,* meaning it will be matched against the verbatim JSON without attempting to ++ * unescape it. This works well for typical ASCII and UTF-8 keys (almost all of them), but may ++ * fail to match some keys with escapes (\u, \n, etc.). ++ */ ++ simdjson_warn_unused simdjson_inline error_code find_field(const std::string_view key) noexcept; ++ ++ /** ++ * Find the next field with the given key, *without* unescaping. This assumes object order: it ++ * will not find the field if it was already passed when looking for some *other* field. ++ * ++ * Assumes you have called next_field() or otherwise matched the previous value. ++ * ++ * This means the iterator must be sitting at the next key: ++ * ++ * ``` ++ * { "a": 1, "b": 2 } ++ * ^ ++ * ``` ++ * ++ * Key is *raw JSON,* meaning it will be matched against the verbatim JSON without attempting to ++ * unescape it. This works well for typical ASCII and UTF-8 keys (almost all of them), but may ++ * fail to match some keys with escapes (\u, \n, etc.). ++ */ ++ simdjson_warn_unused simdjson_inline simdjson_result find_field_raw(const std::string_view key) noexcept; ++ ++ /** ++ * Find the field with the given key without regard to order, and *without* unescaping. ++ * ++ * This is an unordered object lookup: if the field is not found initially, it will cycle around and scan from the beginning. ++ * ++ * Assumes you have called next_field() or otherwise matched the previous value. ++ * ++ * This means the iterator must be sitting at the next key: ++ * ++ * ``` ++ * { "a": 1, "b": 2 } ++ * ^ ++ * ``` ++ * ++ * Key is *raw JSON,* meaning it will be matched against the verbatim JSON without attempting to ++ * unescape it. This works well for typical ASCII and UTF-8 keys (almost all of them), but may ++ * fail to match some keys with escapes (\u, \n, etc.). ++ */ ++ simdjson_warn_unused simdjson_inline simdjson_result find_field_unordered_raw(const std::string_view key) noexcept; ++ ++ /** @} */ ++ ++ /** ++ * @addtogroup array Array iteration ++ * Methods to iterate over array elements. These methods generally *assume* the value is actually ++ * an object; the caller is responsible for keeping track of that fact. ++ * @{ ++ */ ++ ++ /** ++ * Check for an opening [ and start an array iteration. ++ * ++ * @returns Whether the array had any elements (returns false for empty). ++ * @error INCORRECT_TYPE If there is no [. ++ */ ++ simdjson_warn_unused simdjson_inline simdjson_result start_array() noexcept; ++ /** ++ * Check for an opening [ and start an array iteration while at the root. ++ * ++ * @returns Whether the array had any elements (returns false for empty). ++ * @error INCORRECT_TYPE If there is no [. ++ * @error TAPE_ERROR if there is no matching ] at end of document ++ */ ++ simdjson_warn_unused simdjson_inline simdjson_result start_root_array() noexcept; ++ ++ /** ++ * Start an array iteration, after the user has already checked and moved past the [. ++ * ++ * Does not move the iterator unless the array is empty ([]). ++ * ++ * @returns Whether the array had any elements (returns false for empty). ++ * @error INCOMPLETE_ARRAY_OR_OBJECT If there are no more tokens (implying the *parent* ++ * array or object is incomplete). ++ */ ++ simdjson_warn_unused simdjson_inline simdjson_result started_array() noexcept; ++ /** ++ * Start an array iteration from the root, after the user has already checked and moved past the [. ++ * ++ * Does not move the iterator unless the array is empty ([]). ++ * ++ * @returns Whether the array had any elements (returns false for empty). ++ * @error INCOMPLETE_ARRAY_OR_OBJECT If there are no more tokens (implying the *parent* ++ * array or object is incomplete). ++ */ ++ simdjson_warn_unused simdjson_inline simdjson_result started_root_array() noexcept; ++ ++ /** ++ * Moves to the next element in an array. ++ * ++ * Looks for , and ]. If ] is found, the array is finished and the iterator advances past it. ++ * Otherwise, it advances to the next value. ++ * ++ * @return Whether there is another element in the array. ++ * @error TAPE_ERROR If there is a comma missing between elements. ++ */ ++ simdjson_warn_unused simdjson_inline simdjson_result has_next_element() noexcept; ++ ++ /** ++ * Get a child value iterator. ++ */ ++ simdjson_warn_unused simdjson_inline value_iterator child() const noexcept; ++ ++ /** @} */ ++ ++ /** ++ * @defgroup scalar Scalar values ++ * @addtogroup scalar ++ * @{ ++ */ ++ ++ simdjson_warn_unused simdjson_inline simdjson_result get_string() noexcept; ++ simdjson_warn_unused simdjson_inline simdjson_result get_raw_json_string() noexcept; ++ simdjson_warn_unused simdjson_inline simdjson_result get_uint64() noexcept; ++ simdjson_warn_unused simdjson_inline simdjson_result get_uint64_in_string() noexcept; ++ simdjson_warn_unused simdjson_inline simdjson_result get_int64() noexcept; ++ simdjson_warn_unused simdjson_inline simdjson_result get_int64_in_string() noexcept; ++ simdjson_warn_unused simdjson_inline simdjson_result get_double() noexcept; ++ simdjson_warn_unused simdjson_inline simdjson_result get_double_in_string() noexcept; ++ simdjson_warn_unused simdjson_inline simdjson_result get_bool() noexcept; ++ simdjson_warn_unused simdjson_inline simdjson_result is_null() noexcept; ++ simdjson_warn_unused simdjson_inline bool is_negative() noexcept; ++ simdjson_warn_unused simdjson_inline simdjson_result is_integer() noexcept; ++ simdjson_warn_unused simdjson_inline simdjson_result get_number_type() noexcept; ++ simdjson_warn_unused simdjson_inline simdjson_result get_number() noexcept; ++ ++ simdjson_warn_unused simdjson_inline simdjson_result get_root_string(bool check_trailing) noexcept; ++ simdjson_warn_unused simdjson_inline simdjson_result get_root_raw_json_string(bool check_trailing) noexcept; ++ simdjson_warn_unused simdjson_inline simdjson_result get_root_uint64(bool check_trailing) noexcept; ++ simdjson_warn_unused simdjson_inline simdjson_result get_root_uint64_in_string(bool check_trailing) noexcept; ++ simdjson_warn_unused simdjson_inline simdjson_result get_root_int64(bool check_trailing) noexcept; ++ simdjson_warn_unused simdjson_inline simdjson_result get_root_int64_in_string(bool check_trailing) noexcept; ++ simdjson_warn_unused simdjson_inline simdjson_result get_root_double(bool check_trailing) noexcept; ++ simdjson_warn_unused simdjson_inline simdjson_result get_root_double_in_string(bool check_trailing) noexcept; ++ simdjson_warn_unused simdjson_inline simdjson_result get_root_bool(bool check_trailing) noexcept; ++ simdjson_warn_unused simdjson_inline bool is_root_negative() noexcept; ++ simdjson_warn_unused simdjson_inline simdjson_result is_root_integer(bool check_trailing) noexcept; ++ simdjson_warn_unused simdjson_inline simdjson_result get_root_number_type(bool check_trailing) noexcept; ++ simdjson_warn_unused simdjson_inline simdjson_result get_root_number(bool check_trailing) noexcept; ++ simdjson_warn_unused simdjson_inline simdjson_result is_root_null(bool check_trailing) noexcept; ++ ++ simdjson_inline error_code error() const noexcept; ++ simdjson_inline uint8_t *&string_buf_loc() noexcept; ++ simdjson_inline const json_iterator &json_iter() const noexcept; ++ simdjson_inline json_iterator &json_iter() noexcept; ++ ++ simdjson_inline void assert_is_valid() const noexcept; ++ simdjson_inline bool is_valid() const noexcept; ++ ++ /** @} */ ++protected: ++ /** ++ * Restarts an array iteration. ++ * @returns Whether the array has any elements (returns false for empty). ++ */ ++ simdjson_inline simdjson_result reset_array() noexcept; ++ /** ++ * Restarts an object iteration. ++ * @returns Whether the object has any fields (returns false for empty). ++ */ ++ simdjson_inline simdjson_result reset_object() noexcept; ++ /** ++ * move_at_start(): moves us so that we are pointing at the beginning of ++ * the container. It updates the index so that at_start() is true and it ++ * syncs the depth. The user can then create a new container instance. ++ * ++ * Usage: used with value::count_elements(). ++ **/ ++ simdjson_inline void move_at_start() noexcept; ++ ++ /** ++ * move_at_container_start(): moves us so that we are pointing at the beginning of ++ * the container so that assert_at_container_start() passes. ++ * ++ * Usage: used with reset_array() and reset_object(). ++ **/ ++ simdjson_inline void move_at_container_start() noexcept; ++ /* Useful for debugging and logging purposes. */ ++ inline std::string to_string() const noexcept; ++ simdjson_inline value_iterator(json_iterator *json_iter, depth_t depth, token_position start_index) noexcept; ++ ++ simdjson_inline simdjson_result parse_null(const uint8_t *json) const noexcept; ++ simdjson_inline simdjson_result parse_bool(const uint8_t *json) const noexcept; ++ simdjson_inline const uint8_t *peek_start() const noexcept; ++ simdjson_inline uint32_t peek_start_length() const noexcept; ++ ++ /** ++ * The general idea of the advance_... methods and the peek_* methods ++ * is that you first peek and check that you have desired type. If you do, ++ * and only if you do, then you advance. ++ * ++ * We used to unconditionally advance. But this made reasoning about our ++ * current state difficult. ++ * Suppose you always advance. Look at the 'value' matching the key ++ * "shadowable" in the following example... ++ * ++ * ({"globals":{"a":{"shadowable":[}}}}) ++ * ++ * If the user thinks it is a Boolean and asks for it, then we check the '[', ++ * decide it is not a Boolean, but still move into the next character ('}'). Now ++ * we are left pointing at '}' right after a '['. And we have not yet reported ++ * an error, only that we do not have a Boolean. ++ * ++ * If, instead, you just stand your ground until it is content that you know, then ++ * you will only even move beyond the '[' if the user tells you that you have an ++ * array. So you will be at the '}' character inside the array and, hopefully, you ++ * will then catch the error because an array cannot start with '}', but the code ++ * processing Boolean values does not know this. ++ * ++ * So the contract is: first call 'peek_...' and then call 'advance_...' only ++ * if you have determined that it is a type you can handle. ++ * ++ * Unfortunately, it makes the code more verbose, longer and maybe more error prone. ++ */ ++ ++ simdjson_inline void advance_scalar(const char *type) noexcept; ++ simdjson_inline void advance_root_scalar(const char *type) noexcept; ++ simdjson_inline void advance_non_root_scalar(const char *type) noexcept; ++ ++ simdjson_inline const uint8_t *peek_scalar(const char *type) noexcept; ++ simdjson_inline const uint8_t *peek_root_scalar(const char *type) noexcept; ++ simdjson_inline const uint8_t *peek_non_root_scalar(const char *type) noexcept; ++ ++ ++ simdjson_inline error_code start_container(uint8_t start_char, const char *incorrect_type_message, const char *type) noexcept; ++ simdjson_inline error_code end_container() noexcept; ++ ++ /** ++ * Advance to a place expecting a value (increasing depth). ++ * ++ * @return The current token (the one left behind). ++ * @error TAPE_ERROR If the document ended early. ++ */ ++ simdjson_inline simdjson_result advance_to_value() noexcept; ++ ++ simdjson_inline error_code incorrect_type_error(const char *message) const noexcept; ++ simdjson_inline error_code error_unless_more_tokens(uint32_t tokens=1) const noexcept; ++ ++ simdjson_inline bool is_at_start() const noexcept; ++ /** ++ * is_at_iterator_start() returns true on an array or object after it has just been ++ * created, whether the instance is empty or not. ++ * ++ * Usage: used by array::begin() in debug mode (SIMDJSON_DEVELOPMENT_CHECKS) ++ */ ++ simdjson_inline bool is_at_iterator_start() const noexcept; ++ ++ /** ++ * Assuming that we are within an object, this returns true if we ++ * are pointing at a key. ++ * ++ * Usage: the skip_child() method should never be used while we are pointing ++ * at a key inside an object. ++ */ ++ simdjson_inline bool is_at_key() const noexcept; ++ ++ inline void assert_at_start() const noexcept; ++ inline void assert_at_container_start() const noexcept; ++ inline void assert_at_root() const noexcept; ++ inline void assert_at_child() const noexcept; ++ inline void assert_at_next() const noexcept; ++ inline void assert_at_non_root_start() const noexcept; ++ ++ /** Get the starting position of this value */ ++ simdjson_inline token_position start_position() const noexcept; ++ ++ /** @copydoc error_code json_iterator::position() const noexcept; */ ++ simdjson_inline token_position position() const noexcept; ++ /** @copydoc error_code json_iterator::end_position() const noexcept; */ ++ simdjson_inline token_position last_position() const noexcept; ++ /** @copydoc error_code json_iterator::end_position() const noexcept; */ ++ simdjson_inline token_position end_position() const noexcept; ++ /** @copydoc error_code json_iterator::report_error(error_code error, const char *message) noexcept; */ ++ simdjson_inline error_code report_error(error_code error, const char *message) noexcept; ++ ++ friend class document; ++ friend class object; ++ friend class array; ++ friend class value; ++}; // value_iterator ++ ++} // namespace ondemand ++} // namespace SIMDJSON_BUILTIN_IMPLEMENTATION ++} // namespace simdjson ++ ++namespace simdjson { ++ ++template<> ++struct simdjson_result : public SIMDJSON_BUILTIN_IMPLEMENTATION::implementation_simdjson_result_base { ++public: ++ simdjson_inline simdjson_result(SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand::value_iterator &&value) noexcept; ///< @private ++ simdjson_inline simdjson_result(error_code error) noexcept; ///< @private ++ simdjson_inline simdjson_result() noexcept = default; ++}; ++ ++} // namespace simdjson ++/* end file include/simdjson/generic/ondemand/value_iterator.h */ ++/* begin file include/simdjson/generic/ondemand/array_iterator.h */ ++ ++namespace simdjson { ++namespace SIMDJSON_BUILTIN_IMPLEMENTATION { ++namespace ondemand { ++ ++class array; ++class value; ++class document; ++ ++/** ++ * A forward-only JSON array. ++ * ++ * This is an input_iterator, meaning: ++ * - It is forward-only ++ * - * must be called exactly once per element. ++ * - ++ must be called exactly once in between each * (*, ++, *, ++, * ...) ++ */ ++class array_iterator { ++public: ++ /** Create a new, invalid array iterator. */ ++ simdjson_inline array_iterator() noexcept = default; ++ ++ // ++ // Iterator interface ++ // ++ ++ /** ++ * Get the current element. ++ * ++ * Part of the std::iterator interface. ++ */ ++ simdjson_inline simdjson_result operator*() noexcept; // MUST ONLY BE CALLED ONCE PER ITERATION. ++ /** ++ * Check if we are at the end of the JSON. ++ * ++ * Part of the std::iterator interface. ++ * ++ * @return true if there are no more elements in the JSON array. ++ */ ++ simdjson_inline bool operator==(const array_iterator &) const noexcept; ++ /** ++ * Check if there are more elements in the JSON array. ++ * ++ * Part of the std::iterator interface. ++ * ++ * @return true if there are more elements in the JSON array. ++ */ ++ simdjson_inline bool operator!=(const array_iterator &) const noexcept; ++ /** ++ * Move to the next element. ++ * ++ * Part of the std::iterator interface. ++ */ ++ simdjson_inline array_iterator &operator++() noexcept; ++ ++private: ++ value_iterator iter{}; ++ ++ simdjson_inline array_iterator(const value_iterator &iter) noexcept; ++ ++ friend class array; ++ friend class value; ++ friend struct simdjson_result; ++}; ++ ++} // namespace ondemand ++} // namespace SIMDJSON_BUILTIN_IMPLEMENTATION ++} // namespace simdjson ++ ++namespace simdjson { ++ ++template<> ++struct simdjson_result : public SIMDJSON_BUILTIN_IMPLEMENTATION::implementation_simdjson_result_base { ++public: ++ simdjson_inline simdjson_result(SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand::array_iterator &&value) noexcept; ///< @private ++ simdjson_inline simdjson_result(error_code error) noexcept; ///< @private ++ simdjson_inline simdjson_result() noexcept = default; ++ ++ // ++ // Iterator interface ++ // ++ ++ simdjson_inline simdjson_result operator*() noexcept; // MUST ONLY BE CALLED ONCE PER ITERATION. ++ simdjson_inline bool operator==(const simdjson_result &) const noexcept; ++ simdjson_inline bool operator!=(const simdjson_result &) const noexcept; ++ simdjson_inline simdjson_result &operator++() noexcept; ++}; ++ ++} // namespace simdjson ++/* end file include/simdjson/generic/ondemand/array_iterator.h */ ++/* begin file include/simdjson/generic/ondemand/object_iterator.h */ ++ ++namespace simdjson { ++namespace SIMDJSON_BUILTIN_IMPLEMENTATION { ++namespace ondemand { ++ ++class field; ++ ++class object_iterator { ++public: ++ /** ++ * Create a new invalid object_iterator. ++ * ++ * Exists so you can declare a variable and later assign to it before use. ++ */ ++ simdjson_inline object_iterator() noexcept = default; ++ ++ // ++ // Iterator interface ++ // ++ ++ // Reads key and value, yielding them to the user. ++ // MUST ONLY BE CALLED ONCE PER ITERATION. ++ simdjson_inline simdjson_result operator*() noexcept; ++ // Assumes it's being compared with the end. true if depth < iter->depth. ++ simdjson_inline bool operator==(const object_iterator &) const noexcept; ++ // Assumes it's being compared with the end. true if depth >= iter->depth. ++ simdjson_inline bool operator!=(const object_iterator &) const noexcept; ++ // Checks for ']' and ',' ++ simdjson_inline object_iterator &operator++() noexcept; ++ ++private: ++ /** ++ * The underlying JSON iterator. ++ * ++ * PERF NOTE: expected to be elided in favor of the parent document: this is set when the object ++ * is first used, and never changes afterwards. ++ */ ++ value_iterator iter{}; ++ ++ simdjson_inline object_iterator(const value_iterator &iter) noexcept; ++ friend struct simdjson_result; ++ friend class object; ++}; ++ ++} // namespace ondemand ++} // namespace SIMDJSON_BUILTIN_IMPLEMENTATION ++} // namespace simdjson ++ ++namespace simdjson { ++ ++template<> ++struct simdjson_result : public SIMDJSON_BUILTIN_IMPLEMENTATION::implementation_simdjson_result_base { ++public: ++ simdjson_inline simdjson_result(SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand::object_iterator &&value) noexcept; ///< @private ++ simdjson_inline simdjson_result(error_code error) noexcept; ///< @private ++ simdjson_inline simdjson_result() noexcept = default; ++ ++ // ++ // Iterator interface ++ // ++ ++ // Reads key and value, yielding them to the user. ++ simdjson_inline simdjson_result operator*() noexcept; // MUST ONLY BE CALLED ONCE PER ITERATION. ++ // Assumes it's being compared with the end. true if depth < iter->depth. ++ simdjson_inline bool operator==(const simdjson_result &) const noexcept; ++ // Assumes it's being compared with the end. true if depth >= iter->depth. ++ simdjson_inline bool operator!=(const simdjson_result &) const noexcept; ++ // Checks for ']' and ',' ++ simdjson_inline simdjson_result &operator++() noexcept; ++}; ++ ++} // namespace simdjson ++/* end file include/simdjson/generic/ondemand/object_iterator.h */ ++/* begin file include/simdjson/generic/ondemand/array.h */ ++ ++namespace simdjson { ++namespace SIMDJSON_BUILTIN_IMPLEMENTATION { ++namespace ondemand { ++ ++class value; ++class document; ++ ++/** ++ * A forward-only JSON array. ++ */ ++class array { ++public: ++ /** ++ * Create a new invalid array. ++ * ++ * Exists so you can declare a variable and later assign to it before use. ++ */ ++ simdjson_inline array() noexcept = default; ++ ++ /** ++ * Begin array iteration. ++ * ++ * Part of the std::iterable interface. ++ */ ++ simdjson_inline simdjson_result begin() noexcept; ++ /** ++ * Sentinel representing the end of the array. ++ * ++ * Part of the std::iterable interface. ++ */ ++ simdjson_inline simdjson_result end() noexcept; ++ /** ++ * This method scans the array and counts the number of elements. ++ * The count_elements method should always be called before you have begun ++ * iterating through the array: it is expected that you are pointing at ++ * the beginning of the array. ++ * The runtime complexity is linear in the size of the array. After ++ * calling this function, if successful, the array is 'rewinded' at its ++ * beginning as if it had never been accessed. If the JSON is malformed (e.g., ++ * there is a missing comma), then an error is returned and it is no longer ++ * safe to continue. ++ * ++ * To check that an array is empty, it is more performant to use ++ * the is_empty() method. ++ */ ++ simdjson_inline simdjson_result count_elements() & noexcept; ++ /** ++ * This method scans the beginning of the array and checks whether the ++ * array is empty. ++ * The runtime complexity is constant time. After ++ * calling this function, if successful, the array is 'rewinded' at its ++ * beginning as if it had never been accessed. If the JSON is malformed (e.g., ++ * there is a missing comma), then an error is returned and it is no longer ++ * safe to continue. ++ */ ++ simdjson_inline simdjson_result is_empty() & noexcept; ++ /** ++ * Reset the iterator so that we are pointing back at the ++ * beginning of the array. You should still consume values only once even if you ++ * can iterate through the array more than once. If you unescape a string ++ * within the array more than once, you have unsafe code. Note that rewinding ++ * an array means that you may need to reparse it anew: it is not a free ++ * operation. ++ * ++ * @returns true if the array contains some elements (not empty) ++ */ ++ inline simdjson_result reset() & noexcept; ++ /** ++ * Get the value associated with the given JSON pointer. We use the RFC 6901 ++ * https://tools.ietf.org/html/rfc6901 standard, interpreting the current node ++ * as the root of its own JSON document. ++ * ++ * ondemand::parser parser; ++ * auto json = R"([ { "foo": { "a": [ 10, 20, 30 ] }} ])"_padded; ++ * auto doc = parser.iterate(json); ++ * doc.at_pointer("/0/foo/a/1") == 20 ++ * ++ * Note that at_pointer() called on the document automatically calls the document's rewind ++ * method between each call. It invalidates all previously accessed arrays, objects and values ++ * that have not been consumed. Yet it is not the case when calling at_pointer on an array ++ * instance: there is no rewind and no invalidation. ++ * ++ * You may only call at_pointer on an array after it has been created, but before it has ++ * been first accessed. When calling at_pointer on an array, the pointer is advanced to ++ * the location indicated by the JSON pointer (in case of success). It is no longer possible ++ * to call at_pointer on the same array. ++ * ++ * Also note that at_pointer() relies on find_field() which implies that we do not unescape keys when matching. ++ * ++ * @return The value associated with the given JSON pointer, or: ++ * - NO_SUCH_FIELD if a field does not exist in an object ++ * - INDEX_OUT_OF_BOUNDS if an array index is larger than an array length ++ * - INCORRECT_TYPE if a non-integer is used to access an array ++ * - INVALID_JSON_POINTER if the JSON pointer is invalid and cannot be parsed ++ */ ++ inline simdjson_result at_pointer(std::string_view json_pointer) noexcept; ++ /** ++ * Consumes the array and returns a string_view instance corresponding to the ++ * array as represented in JSON. It points inside the original document. ++ */ ++ simdjson_inline simdjson_result raw_json() noexcept; ++ ++ /** ++ * Get the value at the given index. This function has linear-time complexity. ++ * This function should only be called once on an array instance since the array iterator is not reset between each call. ++ * ++ * @return The value at the given index, or: ++ * - INDEX_OUT_OF_BOUNDS if the array index is larger than an array length ++ */ ++ simdjson_inline simdjson_result at(size_t index) noexcept; ++protected: ++ /** ++ * Go to the end of the array, no matter where you are right now. ++ */ ++ simdjson_inline error_code consume() noexcept; ++ ++ /** ++ * Begin array iteration. ++ * ++ * @param iter The iterator. Must be where the initial [ is expected. Will be *moved* into the ++ * resulting array. ++ * @error INCORRECT_TYPE if the iterator is not at [. ++ */ ++ static simdjson_inline simdjson_result start(value_iterator &iter) noexcept; ++ /** ++ * Begin array iteration from the root. ++ * ++ * @param iter The iterator. Must be where the initial [ is expected. Will be *moved* into the ++ * resulting array. ++ * @error INCORRECT_TYPE if the iterator is not at [. ++ * @error TAPE_ERROR if there is no closing ] at the end of the document. ++ */ ++ static simdjson_inline simdjson_result start_root(value_iterator &iter) noexcept; ++ /** ++ * Begin array iteration. ++ * ++ * This version of the method should be called after the initial [ has been verified, and is ++ * intended for use by switch statements that check the type of a value. ++ * ++ * @param iter The iterator. Must be after the initial [. Will be *moved* into the resulting array. ++ */ ++ static simdjson_inline simdjson_result started(value_iterator &iter) noexcept; ++ ++ /** ++ * Create an array at the given Internal array creation. Call array::start() or array::started() instead of this. ++ * ++ * @param iter The iterator. Must either be at the start of the first element with iter.is_alive() ++ * == true, or past the [] with is_alive() == false if the array is empty. Will be *moved* ++ * into the resulting array. ++ */ ++ simdjson_inline array(const value_iterator &iter) noexcept; ++ ++ /** ++ * Iterator marking current position. ++ * ++ * iter.is_alive() == false indicates iteration is complete. ++ */ ++ value_iterator iter{}; ++ ++ friend class value; ++ friend class document; ++ friend struct simdjson_result; ++ friend struct simdjson_result; ++ friend class array_iterator; ++}; ++ ++} // namespace ondemand ++} // namespace SIMDJSON_BUILTIN_IMPLEMENTATION ++} // namespace simdjson ++ ++namespace simdjson { ++ ++template<> ++struct simdjson_result : public SIMDJSON_BUILTIN_IMPLEMENTATION::implementation_simdjson_result_base { ++public: ++ simdjson_inline simdjson_result(SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand::array &&value) noexcept; ///< @private ++ simdjson_inline simdjson_result(error_code error) noexcept; ///< @private ++ simdjson_inline simdjson_result() noexcept = default; ++ ++ simdjson_inline simdjson_result begin() noexcept; ++ simdjson_inline simdjson_result end() noexcept; ++ inline simdjson_result count_elements() & noexcept; ++ inline simdjson_result is_empty() & noexcept; ++ inline simdjson_result reset() & noexcept; ++ simdjson_inline simdjson_result at(size_t index) noexcept; ++ simdjson_inline simdjson_result at_pointer(std::string_view json_pointer) noexcept; ++}; ++ ++} // namespace simdjson ++/* end file include/simdjson/generic/ondemand/array.h */ ++/* begin file include/simdjson/generic/ondemand/document.h */ ++ ++namespace simdjson { ++namespace SIMDJSON_BUILTIN_IMPLEMENTATION { ++namespace ondemand { ++ ++class parser; ++class array; ++class object; ++class value; ++class raw_json_string; ++class array_iterator; ++class document_stream; ++ ++/** ++ * A JSON document. It holds a json_iterator instance. ++ * ++ * Used by tokens to get text, and string buffer location. ++ * ++ * You must keep the document around during iteration. ++ */ ++class document { ++public: ++ /** ++ * Create a new invalid document. ++ * ++ * Exists so you can declare a variable and later assign to it before use. ++ */ ++ simdjson_inline document() noexcept = default; ++ simdjson_inline document(const document &other) noexcept = delete; // pass your documents by reference, not by copy ++ simdjson_inline document(document &&other) noexcept = default; ++ simdjson_inline document &operator=(const document &other) noexcept = delete; ++ simdjson_inline document &operator=(document &&other) noexcept = default; ++ ++ /** ++ * Cast this JSON value to an array. ++ * ++ * @returns An object that can be used to iterate the array. ++ * @returns INCORRECT_TYPE If the JSON value is not an array. ++ */ ++ simdjson_inline simdjson_result get_array() & noexcept; ++ /** ++ * Cast this JSON value to an object. ++ * ++ * @returns An object that can be used to look up or iterate fields. ++ * @returns INCORRECT_TYPE If the JSON value is not an object. ++ */ ++ simdjson_inline simdjson_result get_object() & noexcept; ++ /** ++ * Cast this JSON value to an unsigned integer. ++ * ++ * @returns A signed 64-bit integer. ++ * @returns INCORRECT_TYPE If the JSON value is not a 64-bit unsigned integer. ++ */ ++ simdjson_inline simdjson_result get_uint64() noexcept; ++ /** ++ * Cast this JSON value (inside string) to an unsigned integer. ++ * ++ * @returns A signed 64-bit integer. ++ * @returns INCORRECT_TYPE If the JSON value is not a 64-bit unsigned integer. ++ */ ++ simdjson_inline simdjson_result get_uint64_in_string() noexcept; ++ /** ++ * Cast this JSON value to a signed integer. ++ * ++ * @returns A signed 64-bit integer. ++ * @returns INCORRECT_TYPE If the JSON value is not a 64-bit integer. ++ */ ++ simdjson_inline simdjson_result get_int64() noexcept; ++ /** ++ * Cast this JSON value (inside string) to a signed integer. ++ * ++ * @returns A signed 64-bit integer. ++ * @returns INCORRECT_TYPE If the JSON value is not a 64-bit integer. ++ */ ++ simdjson_inline simdjson_result get_int64_in_string() noexcept; ++ /** ++ * Cast this JSON value to a double. ++ * ++ * @returns A double. ++ * @returns INCORRECT_TYPE If the JSON value is not a valid floating-point number. ++ */ ++ simdjson_inline simdjson_result get_double() noexcept; ++ ++ /** ++ * Cast this JSON value (inside string) to a double. ++ * ++ * @returns A double. ++ * @returns INCORRECT_TYPE If the JSON value is not a valid floating-point number. ++ */ ++ simdjson_inline simdjson_result get_double_in_string() noexcept; ++ /** ++ * Cast this JSON value to a string. ++ * ++ * The string is guaranteed to be valid UTF-8. ++ * ++ * Important: Calling get_string() twice on the same document is an error. ++ * ++ * @returns An UTF-8 string. The string is stored in the parser and will be invalidated the next ++ * time it parses a document or when it is destroyed. ++ * @returns INCORRECT_TYPE if the JSON value is not a string. ++ */ ++ simdjson_inline simdjson_result get_string() noexcept; ++ /** ++ * Cast this JSON value to a raw_json_string. ++ * ++ * The string is guaranteed to be valid UTF-8, and may have escapes in it (e.g. \\ or \n). ++ * ++ * @returns A pointer to the raw JSON for the given string. ++ * @returns INCORRECT_TYPE if the JSON value is not a string. ++ */ ++ simdjson_inline simdjson_result get_raw_json_string() noexcept; ++ /** ++ * Cast this JSON value to a bool. ++ * ++ * @returns A bool value. ++ * @returns INCORRECT_TYPE if the JSON value is not true or false. ++ */ ++ simdjson_inline simdjson_result get_bool() noexcept; ++ /** ++ * Cast this JSON value to a value when the document is an object or an array. ++ * ++ * @returns A value if a JSON array or object cannot be found. ++ * @returns SCALAR_DOCUMENT_AS_VALUE error is the document is a scalar (see is_scalar() function). ++ */ ++ simdjson_inline simdjson_result get_value() noexcept; ++ ++ /** ++ * Checks if this JSON value is null. If and only if the value is ++ * null, then it is consumed (we advance). If we find a token that ++ * begins with 'n' but is not 'null', then an error is returned. ++ * ++ * @returns Whether the value is null. ++ * @returns INCORRECT_TYPE If the JSON value begins with 'n' and is not 'null'. ++ */ ++ simdjson_inline simdjson_result is_null() noexcept; ++ ++ /** ++ * Get this value as the given type. ++ * ++ * Supported types: object, array, raw_json_string, string_view, uint64_t, int64_t, double, bool ++ * ++ * You may use get_double(), get_bool(), get_uint64(), get_int64(), ++ * get_object(), get_array(), get_raw_json_string(), or get_string() instead. ++ * ++ * @returns A value of the given type, parsed from the JSON. ++ * @returns INCORRECT_TYPE If the JSON value is not the given type. ++ */ ++ template simdjson_inline simdjson_result get() & noexcept { ++ // Unless the simdjson library provides an inline implementation, calling this method should ++ // immediately fail. ++ static_assert(!sizeof(T), "The get method with given type is not implemented by the simdjson library."); ++ } ++ /** @overload template simdjson_result get() & noexcept */ ++ template simdjson_inline simdjson_result get() && noexcept { ++ // Unless the simdjson library provides an inline implementation, calling this method should ++ // immediately fail. ++ static_assert(!sizeof(T), "The get method with given type is not implemented by the simdjson library."); ++ } ++ ++ /** ++ * Get this value as the given type. ++ * ++ * Supported types: object, array, raw_json_string, string_view, uint64_t, int64_t, double, bool, value ++ * ++ * Be mindful that the document instance must remain in scope while you are accessing object, array and value instances. ++ * ++ * @param out This is set to a value of the given type, parsed from the JSON. If there is an error, this may not be initialized. ++ * @returns INCORRECT_TYPE If the JSON value is not an object. ++ * @returns SUCCESS If the parse succeeded and the out parameter was set to the value. ++ */ ++ template simdjson_inline error_code get(T &out) & noexcept; ++ /** @overload template error_code get(T &out) & noexcept */ ++ template simdjson_inline error_code get(T &out) && noexcept; ++ ++#if SIMDJSON_EXCEPTIONS ++ /** ++ * Cast this JSON value to an array. ++ * ++ * @returns An object that can be used to iterate the array. ++ * @exception simdjson_error(INCORRECT_TYPE) If the JSON value is not an array. ++ */ ++ simdjson_inline operator array() & noexcept(false); ++ /** ++ * Cast this JSON value to an object. ++ * ++ * @returns An object that can be used to look up or iterate fields. ++ * @exception simdjson_error(INCORRECT_TYPE) If the JSON value is not an object. ++ */ ++ simdjson_inline operator object() & noexcept(false); ++ /** ++ * Cast this JSON value to an unsigned integer. ++ * ++ * @returns A signed 64-bit integer. ++ * @exception simdjson_error(INCORRECT_TYPE) If the JSON value is not a 64-bit unsigned integer. ++ */ ++ simdjson_inline operator uint64_t() noexcept(false); ++ /** ++ * Cast this JSON value to a signed integer. ++ * ++ * @returns A signed 64-bit integer. ++ * @exception simdjson_error(INCORRECT_TYPE) If the JSON value is not a 64-bit integer. ++ */ ++ simdjson_inline operator int64_t() noexcept(false); ++ /** ++ * Cast this JSON value to a double. ++ * ++ * @returns A double. ++ * @exception simdjson_error(INCORRECT_TYPE) If the JSON value is not a valid floating-point number. ++ */ ++ simdjson_inline operator double() noexcept(false); ++ /** ++ * Cast this JSON value to a string. ++ * ++ * The string is guaranteed to be valid UTF-8. ++ * ++ * @returns An UTF-8 string. The string is stored in the parser and will be invalidated the next ++ * time it parses a document or when it is destroyed. ++ * @exception simdjson_error(INCORRECT_TYPE) if the JSON value is not a string. ++ */ ++ simdjson_inline operator std::string_view() noexcept(false); ++ /** ++ * Cast this JSON value to a raw_json_string. ++ * ++ * The string is guaranteed to be valid UTF-8, and may have escapes in it (e.g. \\ or \n). ++ * ++ * @returns A pointer to the raw JSON for the given string. ++ * @exception simdjson_error(INCORRECT_TYPE) if the JSON value is not a string. ++ */ ++ simdjson_inline operator raw_json_string() noexcept(false); ++ /** ++ * Cast this JSON value to a bool. ++ * ++ * @returns A bool value. ++ * @exception simdjson_error(INCORRECT_TYPE) if the JSON value is not true or false. ++ */ ++ simdjson_inline operator bool() noexcept(false); ++ /** ++ * Cast this JSON value to a value. ++ * ++ * @returns A value value. ++ * @exception if a JSON value cannot be found ++ */ ++ simdjson_inline operator value() noexcept(false); ++#endif ++ /** ++ * This method scans the array and counts the number of elements. ++ * The count_elements method should always be called before you have begun ++ * iterating through the array: it is expected that you are pointing at ++ * the beginning of the array. ++ * The runtime complexity is linear in the size of the array. After ++ * calling this function, if successful, the array is 'rewinded' at its ++ * beginning as if it had never been accessed. If the JSON is malformed (e.g., ++ * there is a missing comma), then an error is returned and it is no longer ++ * safe to continue. ++ */ ++ simdjson_inline simdjson_result count_elements() & noexcept; ++ /** ++ * This method scans the object and counts the number of key-value pairs. ++ * The count_fields method should always be called before you have begun ++ * iterating through the object: it is expected that you are pointing at ++ * the beginning of the object. ++ * The runtime complexity is linear in the size of the object. After ++ * calling this function, if successful, the object is 'rewinded' at its ++ * beginning as if it had never been accessed. If the JSON is malformed (e.g., ++ * there is a missing comma), then an error is returned and it is no longer ++ * safe to continue. ++ * ++ * To check that an object is empty, it is more performant to use ++ * the is_empty() method. ++ */ ++ simdjson_inline simdjson_result count_fields() & noexcept; ++ /** ++ * Get the value at the given index in the array. This function has linear-time complexity. ++ * This function should only be called once on an array instance since the array iterator is not reset between each call. ++ * ++ * @return The value at the given index, or: ++ * - INDEX_OUT_OF_BOUNDS if the array index is larger than an array length ++ */ ++ simdjson_inline simdjson_result at(size_t index) & noexcept; ++ /** ++ * Begin array iteration. ++ * ++ * Part of the std::iterable interface. ++ */ ++ simdjson_inline simdjson_result begin() & noexcept; ++ /** ++ * Sentinel representing the end of the array. ++ * ++ * Part of the std::iterable interface. ++ */ ++ simdjson_inline simdjson_result end() & noexcept; ++ ++ /** ++ * Look up a field by name on an object (order-sensitive). ++ * ++ * The following code reads z, then y, then x, and thus will not retrieve x or y if fed the ++ * JSON `{ "x": 1, "y": 2, "z": 3 }`: ++ * ++ * ```c++ ++ * simdjson::ondemand::parser parser; ++ * auto obj = parser.parse(R"( { "x": 1, "y": 2, "z": 3 } )"_padded); ++ * double z = obj.find_field("z"); ++ * double y = obj.find_field("y"); ++ * double x = obj.find_field("x"); ++ * ``` ++ * ++ * **Raw Keys:** The lookup will be done against the *raw* key, and will not unescape keys. ++ * e.g. `object["a"]` will match `{ "a": 1 }`, but will *not* match `{ "\u0061": 1 }`. ++ * ++ * ++ * You must consume the fields on an object one at a time. A request for a new key ++ * invalidates previous field values: it makes them unsafe. E.g., the array ++ * given by content["bids"].get_array() should not be accessed after you have called ++ * content["asks"].get_array(). You can detect such mistakes by first compiling and running ++ * the code in Debug mode (or with the macro `SIMDJSON_DEVELOPMENT_CHECKS` set to 1): an ++ * OUT_OF_ORDER_ITERATION error is generated. ++ * ++ * You are expected to access keys only once. You should access the value corresponding to ++ * a key a single time. Doing object["mykey"].to_string()and then again object["mykey"].to_string() ++ * is an error. ++ * ++ * @param key The key to look up. ++ * @returns The value of the field, or NO_SUCH_FIELD if the field is not in the object. ++ */ ++ simdjson_inline simdjson_result find_field(std::string_view key) & noexcept; ++ /** @overload simdjson_inline simdjson_result find_field(std::string_view key) & noexcept; */ ++ simdjson_inline simdjson_result find_field(const char *key) & noexcept; ++ ++ /** ++ * Look up a field by name on an object, without regard to key order. ++ * ++ * **Performance Notes:** This is a bit less performant than find_field(), though its effect varies ++ * and often appears negligible. It starts out normally, starting out at the last field; but if ++ * the field is not found, it scans from the beginning of the object to see if it missed it. That ++ * missing case has a non-cache-friendly bump and lots of extra scanning, especially if the object ++ * in question is large. The fact that the extra code is there also bumps the executable size. ++ * ++ * It is the default, however, because it would be highly surprising (and hard to debug) if the ++ * default behavior failed to look up a field just because it was in the wrong order--and many ++ * APIs assume this. Therefore, you must be explicit if you want to treat objects as out of order. ++ * ++ * Use find_field() if you are sure fields will be in order (or are willing to treat it as if the ++ * field wasn't there when they aren't). ++ * ++ * You must consume the fields on an object one at a time. A request for a new key ++ * invalidates previous field values: it makes them unsafe. E.g., the array ++ * given by content["bids"].get_array() should not be accessed after you have called ++ * content["asks"].get_array(). You can detect such mistakes by first compiling and running ++ * the code in Debug mode (or with the macro `SIMDJSON_DEVELOPMENT_CHECKS` set to 1): an ++ * OUT_OF_ORDER_ITERATION error is generated. ++ * ++ * You are expected to access keys only once. You should access the value corresponding to a key ++ * a single time. Doing object["mykey"].to_string() and then again object["mykey"].to_string() ++ * is an error. ++ * ++ * @param key The key to look up. ++ * @returns The value of the field, or NO_SUCH_FIELD if the field is not in the object. ++ */ ++ simdjson_inline simdjson_result find_field_unordered(std::string_view key) & noexcept; ++ /** @overload simdjson_inline simdjson_result find_field_unordered(std::string_view key) & noexcept; */ ++ simdjson_inline simdjson_result find_field_unordered(const char *key) & noexcept; ++ /** @overload simdjson_inline simdjson_result find_field_unordered(std::string_view key) & noexcept; */ ++ simdjson_inline simdjson_result operator[](std::string_view key) & noexcept; ++ /** @overload simdjson_inline simdjson_result find_field_unordered(std::string_view key) & noexcept; */ ++ simdjson_inline simdjson_result operator[](const char *key) & noexcept; ++ ++ /** ++ * Get the type of this JSON value. It does not validate or consume the value. ++ * E.g., you must still call "is_null()" to check that a value is null even if ++ * "type()" returns json_type::null. ++ * ++ * NOTE: If you're only expecting a value to be one type (a typical case), it's generally ++ * better to just call .get_double, .get_string, etc. and check for INCORRECT_TYPE (or just ++ * let it throw an exception). ++ * ++ * @error TAPE_ERROR when the JSON value is a bad token like "}" "," or "alse". ++ */ ++ simdjson_inline simdjson_result type() noexcept; ++ ++ /** ++ * Checks whether the document is a scalar (string, number, null, Boolean). ++ * Returns false when there it is an array or object. ++ * ++ * @returns true if the type is string, number, null, Boolean ++ * @error TAPE_ERROR when the JSON value is a bad token like "}" "," or "alse". ++ */ ++ simdjson_inline simdjson_result is_scalar() noexcept; ++ ++ /** ++ * Checks whether the document is a negative number. ++ * ++ * @returns true if the number if negative. ++ */ ++ simdjson_inline bool is_negative() noexcept; ++ /** ++ * Checks whether the document is an integer number. Note that ++ * this requires to partially parse the number string. If ++ * the value is determined to be an integer, it may still ++ * not parse properly as an integer in subsequent steps ++ * (e.g., it might overflow). ++ * ++ * @returns true if the number if negative. ++ */ ++ simdjson_inline simdjson_result is_integer() noexcept; ++ /** ++ * Determine the number type (integer or floating-point number) as quickly ++ * as possible. This function does not fully validate the input. It is ++ * useful when you only need to classify the numbers, without parsing them. ++ * ++ * If you are planning to retrieve the value or you need full validation, ++ * consider using the get_number() method instead: it will fully parse ++ * and validate the input, and give you access to the type: ++ * get_number().get_number_type(). ++ * ++ * get_number_type() is number_type::unsigned_integer if we have ++ * an integer greater or equal to 9223372036854775808 ++ * get_number_type() is number_type::signed_integer if we have an ++ * integer that is less than 9223372036854775808 ++ * Otherwise, get_number_type() has value number_type::floating_point_number ++ * ++ * This function requires processing the number string, but it is expected ++ * to be faster than get_number().get_number_type() because it is does not ++ * parse the number value. ++ * ++ * @returns the type of the number ++ */ ++ simdjson_inline simdjson_result get_number_type() noexcept; ++ ++ /** ++ * Attempt to parse an ondemand::number. An ondemand::number may ++ * contain an integer value or a floating-point value, the simdjson ++ * library will autodetect the type. Thus it is a dynamically typed ++ * number. Before accessing the value, you must determine the detected ++ * type. ++ * ++ * number.get_number_type() is number_type::signed_integer if we have ++ * an integer in [-9223372036854775808,9223372036854775808) ++ * You can recover the value by calling number.get_int64() and you ++ * have that number.is_int64() is true. ++ * ++ * number.get_number_type() is number_type::unsigned_integer if we have ++ * an integer in [9223372036854775808,18446744073709551616) ++ * You can recover the value by calling number.get_uint64() and you ++ * have that number.is_uint64() is true. ++ * ++ * Otherwise, number.get_number_type() has value number_type::floating_point_number ++ * and we have a binary64 number. ++ * You can recover the value by calling number.get_double() and you ++ * have that number.is_double() is true. ++ * ++ * You must check the type before accessing the value: it is an error ++ * to call "get_int64()" when number.get_number_type() is not ++ * number_type::signed_integer and when number.is_int64() is false. ++ */ ++ simdjson_warn_unused simdjson_inline simdjson_result get_number() noexcept; ++ ++ /** ++ * Get the raw JSON for this token. ++ * ++ * The string_view will always point into the input buffer. ++ * ++ * The string_view will start at the beginning of the token, and include the entire token ++ * *as well as all spaces until the next token (or EOF).* This means, for example, that a ++ * string token always begins with a " and is always terminated by the final ", possibly ++ * followed by a number of spaces. ++ * ++ * The string_view is *not* null-terminated. If this is a scalar (string, number, ++ * boolean, or null), the character after the end of the string_view may be the padded buffer. ++ * ++ * Tokens include: ++ * - { ++ * - [ ++ * - "a string (possibly with UTF-8 or backslashed characters like \\\")". ++ * - -1.2e-100 ++ * - true ++ * - false ++ * - null ++ */ ++ simdjson_inline simdjson_result raw_json_token() noexcept; ++ ++ /** ++ * Reset the iterator inside the document instance so we are pointing back at the ++ * beginning of the document, as if it had just been created. It invalidates all ++ * values, objects and arrays that you have created so far (including unescaped strings). ++ */ ++ inline void rewind() noexcept; ++ /** ++ * Returns debugging information. ++ */ ++ inline std::string to_debug_string() noexcept; ++ /** ++ * Some unrecoverable error conditions may render the document instance unusable. ++ * The is_alive() method returns true when the document is still suitable. ++ */ ++ inline bool is_alive() noexcept; ++ ++ /** ++ * Returns the current location in the document if in bounds. ++ */ ++ inline simdjson_result current_location() noexcept; ++ ++ /** ++ * Returns the current depth in the document if in bounds. ++ * ++ * E.g., ++ * 0 = finished with document ++ * 1 = document root value (could be [ or {, not yet known) ++ * 2 = , or } inside root array/object ++ * 3 = key or value inside root array/object. ++ */ ++ simdjson_inline int32_t current_depth() const noexcept; ++ ++ /** ++ * Get the value associated with the given JSON pointer. We use the RFC 6901 ++ * https://tools.ietf.org/html/rfc6901 standard. ++ * ++ * ondemand::parser parser; ++ * auto json = R"({ "foo": { "a": [ 10, 20, 30 ] }})"_padded; ++ * auto doc = parser.iterate(json); ++ * doc.at_pointer("/foo/a/1") == 20 ++ * ++ * It is allowed for a key to be the empty string: ++ * ++ * ondemand::parser parser; ++ * auto json = R"({ "": { "a": [ 10, 20, 30 ] }})"_padded; ++ * auto doc = parser.iterate(json); ++ * doc.at_pointer("//a/1") == 20 ++ * ++ * Note that at_pointer() automatically calls rewind between each call. Thus ++ * all values, objects and arrays that you have created so far (including unescaped strings) ++ * are invalidated. After calling at_pointer, you need to consume the result: string values ++ * should be stored in your own variables, arrays should be decoded and stored in your own array-like ++ * structures and so forth. ++ * ++ * Also note that at_pointer() relies on find_field() which implies that we do not unescape keys when matching ++ * ++ * @return The value associated with the given JSON pointer, or: ++ * - NO_SUCH_FIELD if a field does not exist in an object ++ * - INDEX_OUT_OF_BOUNDS if an array index is larger than an array length ++ * - INCORRECT_TYPE if a non-integer is used to access an array ++ * - INVALID_JSON_POINTER if the JSON pointer is invalid and cannot be parsed ++ * - SCALAR_DOCUMENT_AS_VALUE if the json_pointer is empty and the document is not a scalar (see is_scalar() function). ++ */ ++ simdjson_inline simdjson_result at_pointer(std::string_view json_pointer) noexcept; ++ /** ++ * Consumes the document and returns a string_view instance corresponding to the ++ * document as represented in JSON. It points inside the original byte array containing ++ * the JSON document. ++ */ ++ simdjson_inline simdjson_result raw_json() noexcept; ++protected: ++ /** ++ * Consumes the document. ++ */ ++ simdjson_inline error_code consume() noexcept; ++ ++ simdjson_inline document(ondemand::json_iterator &&iter) noexcept; ++ simdjson_inline const uint8_t *text(uint32_t idx) const noexcept; ++ ++ simdjson_inline value_iterator resume_value_iterator() noexcept; ++ simdjson_inline value_iterator get_root_value_iterator() noexcept; ++ simdjson_inline simdjson_result start_or_resume_object() noexcept; ++ static simdjson_inline document start(ondemand::json_iterator &&iter) noexcept; ++ ++ // ++ // Fields ++ // ++ json_iterator iter{}; ///< Current position in the document ++ static constexpr depth_t DOCUMENT_DEPTH = 0; ///< document depth is always 0 ++ ++ friend class array_iterator; ++ friend class value; ++ friend class ondemand::parser; ++ friend class object; ++ friend class array; ++ friend class field; ++ friend class token; ++ friend class document_stream; ++ friend class document_reference; ++}; ++ ++ ++/** ++ * A document_reference is a thin wrapper around a document reference instance. ++ */ ++class document_reference { ++public: ++ simdjson_inline document_reference() noexcept; ++ simdjson_inline document_reference(document &d) noexcept; ++ simdjson_inline document_reference(const document_reference &other) noexcept = default; ++ simdjson_inline document_reference& operator=(const document_reference &other) noexcept = default; ++ simdjson_inline void rewind() noexcept; ++ simdjson_inline simdjson_result get_array() & noexcept; ++ simdjson_inline simdjson_result get_object() & noexcept; ++ simdjson_inline simdjson_result get_uint64() noexcept; ++ simdjson_inline simdjson_result get_uint64_in_string() noexcept; ++ simdjson_inline simdjson_result get_int64() noexcept; ++ simdjson_inline simdjson_result get_int64_in_string() noexcept; ++ simdjson_inline simdjson_result get_double() noexcept; ++ simdjson_inline simdjson_result get_double_in_string() noexcept; ++ simdjson_inline simdjson_result get_string() noexcept; ++ simdjson_inline simdjson_result get_raw_json_string() noexcept; ++ simdjson_inline simdjson_result get_bool() noexcept; ++ simdjson_inline simdjson_result get_value() noexcept; ++ ++ simdjson_inline simdjson_result is_null() noexcept; ++ simdjson_inline simdjson_result raw_json() noexcept; ++ simdjson_inline operator document&() const noexcept; ++ ++#if SIMDJSON_EXCEPTIONS ++ simdjson_inline operator array() & noexcept(false); ++ simdjson_inline operator object() & noexcept(false); ++ simdjson_inline operator uint64_t() noexcept(false); ++ simdjson_inline operator int64_t() noexcept(false); ++ simdjson_inline operator double() noexcept(false); ++ simdjson_inline operator std::string_view() noexcept(false); ++ simdjson_inline operator raw_json_string() noexcept(false); ++ simdjson_inline operator bool() noexcept(false); ++ simdjson_inline operator value() noexcept(false); ++#endif ++ simdjson_inline simdjson_result count_elements() & noexcept; ++ simdjson_inline simdjson_result count_fields() & noexcept; ++ simdjson_inline simdjson_result at(size_t index) & noexcept; ++ simdjson_inline simdjson_result begin() & noexcept; ++ simdjson_inline simdjson_result end() & noexcept; ++ simdjson_inline simdjson_result find_field(std::string_view key) & noexcept; ++ simdjson_inline simdjson_result find_field(const char *key) & noexcept; ++ simdjson_inline simdjson_result operator[](std::string_view key) & noexcept; ++ simdjson_inline simdjson_result operator[](const char *key) & noexcept; ++ simdjson_inline simdjson_result find_field_unordered(std::string_view key) & noexcept; ++ simdjson_inline simdjson_result find_field_unordered(const char *key) & noexcept; ++ ++ simdjson_inline simdjson_result type() noexcept; ++ simdjson_inline simdjson_result is_scalar() noexcept; ++ ++ simdjson_inline simdjson_result current_location() noexcept; ++ simdjson_inline int32_t current_depth() const noexcept; ++ simdjson_inline bool is_negative() noexcept; ++ simdjson_inline simdjson_result is_integer() noexcept; ++ simdjson_inline simdjson_result get_number_type() noexcept; ++ simdjson_inline simdjson_result get_number() noexcept; ++ simdjson_inline simdjson_result raw_json_token() noexcept; ++ simdjson_inline simdjson_result at_pointer(std::string_view json_pointer) noexcept; ++private: ++ document *doc{nullptr}; ++}; ++} // namespace ondemand ++} // namespace SIMDJSON_BUILTIN_IMPLEMENTATION ++} // namespace simdjson ++ ++namespace simdjson { ++ ++template<> ++struct simdjson_result : public SIMDJSON_BUILTIN_IMPLEMENTATION::implementation_simdjson_result_base { ++public: ++ simdjson_inline simdjson_result(SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand::document &&value) noexcept; ///< @private ++ simdjson_inline simdjson_result(error_code error) noexcept; ///< @private ++ simdjson_inline simdjson_result() noexcept = default; ++ simdjson_inline error_code rewind() noexcept; ++ ++ simdjson_inline simdjson_result get_array() & noexcept; ++ simdjson_inline simdjson_result get_object() & noexcept; ++ simdjson_inline simdjson_result get_uint64() noexcept; ++ simdjson_inline simdjson_result get_uint64_in_string() noexcept; ++ simdjson_inline simdjson_result get_int64() noexcept; ++ simdjson_inline simdjson_result get_int64_in_string() noexcept; ++ simdjson_inline simdjson_result get_double() noexcept; ++ simdjson_inline simdjson_result get_double_in_string() noexcept; ++ simdjson_inline simdjson_result get_string() noexcept; ++ simdjson_inline simdjson_result get_raw_json_string() noexcept; ++ simdjson_inline simdjson_result get_bool() noexcept; ++ simdjson_inline simdjson_result get_value() noexcept; ++ simdjson_inline simdjson_result is_null() noexcept; ++ ++ template simdjson_inline simdjson_result get() & noexcept; ++ template simdjson_inline simdjson_result get() && noexcept; ++ ++ template simdjson_inline error_code get(T &out) & noexcept; ++ template simdjson_inline error_code get(T &out) && noexcept; ++ ++#if SIMDJSON_EXCEPTIONS ++ simdjson_inline operator SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand::array() & noexcept(false); ++ simdjson_inline operator SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand::object() & noexcept(false); ++ simdjson_inline operator uint64_t() noexcept(false); ++ simdjson_inline operator int64_t() noexcept(false); ++ simdjson_inline operator double() noexcept(false); ++ simdjson_inline operator std::string_view() noexcept(false); ++ simdjson_inline operator SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand::raw_json_string() noexcept(false); ++ simdjson_inline operator bool() noexcept(false); ++ simdjson_inline operator SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand::value() noexcept(false); ++#endif ++ simdjson_inline simdjson_result count_elements() & noexcept; ++ simdjson_inline simdjson_result count_fields() & noexcept; ++ simdjson_inline simdjson_result at(size_t index) & noexcept; ++ simdjson_inline simdjson_result begin() & noexcept; ++ simdjson_inline simdjson_result end() & noexcept; ++ simdjson_inline simdjson_result find_field(std::string_view key) & noexcept; ++ simdjson_inline simdjson_result find_field(const char *key) & noexcept; ++ simdjson_inline simdjson_result operator[](std::string_view key) & noexcept; ++ simdjson_inline simdjson_result operator[](const char *key) & noexcept; ++ simdjson_inline simdjson_result find_field_unordered(std::string_view key) & noexcept; ++ simdjson_inline simdjson_result find_field_unordered(const char *key) & noexcept; ++ simdjson_inline simdjson_result type() noexcept; ++ simdjson_inline simdjson_result is_scalar() noexcept; ++ simdjson_inline simdjson_result current_location() noexcept; ++ simdjson_inline int32_t current_depth() const noexcept; ++ simdjson_inline bool is_negative() noexcept; ++ simdjson_inline simdjson_result is_integer() noexcept; ++ simdjson_inline simdjson_result get_number_type() noexcept; ++ simdjson_inline simdjson_result get_number() noexcept; ++ /** @copydoc simdjson_inline std::string_view document::raw_json_token() const noexcept */ ++ simdjson_inline simdjson_result raw_json_token() noexcept; ++ ++ simdjson_inline simdjson_result at_pointer(std::string_view json_pointer) noexcept; ++}; ++ ++ ++} // namespace simdjson ++ ++ ++ ++namespace simdjson { ++ ++template<> ++struct simdjson_result : public SIMDJSON_BUILTIN_IMPLEMENTATION::implementation_simdjson_result_base { ++public: ++ simdjson_inline simdjson_result(SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand::document_reference value, error_code error) noexcept; ++ simdjson_inline simdjson_result() noexcept = default; ++ simdjson_inline error_code rewind() noexcept; ++ ++ simdjson_inline simdjson_result get_array() & noexcept; ++ simdjson_inline simdjson_result get_object() & noexcept; ++ simdjson_inline simdjson_result get_uint64() noexcept; ++ simdjson_inline simdjson_result get_uint64_in_string() noexcept; ++ simdjson_inline simdjson_result get_int64() noexcept; ++ simdjson_inline simdjson_result get_int64_in_string() noexcept; ++ simdjson_inline simdjson_result get_double() noexcept; ++ simdjson_inline simdjson_result get_double_in_string() noexcept; ++ simdjson_inline simdjson_result get_string() noexcept; ++ simdjson_inline simdjson_result get_raw_json_string() noexcept; ++ simdjson_inline simdjson_result get_bool() noexcept; ++ simdjson_inline simdjson_result get_value() noexcept; ++ simdjson_inline simdjson_result is_null() noexcept; ++ ++#if SIMDJSON_EXCEPTIONS ++ simdjson_inline operator SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand::array() & noexcept(false); ++ simdjson_inline operator SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand::object() & noexcept(false); ++ simdjson_inline operator uint64_t() noexcept(false); ++ simdjson_inline operator int64_t() noexcept(false); ++ simdjson_inline operator double() noexcept(false); ++ simdjson_inline operator std::string_view() noexcept(false); ++ simdjson_inline operator SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand::raw_json_string() noexcept(false); ++ simdjson_inline operator bool() noexcept(false); ++ simdjson_inline operator SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand::value() noexcept(false); ++#endif ++ simdjson_inline simdjson_result count_elements() & noexcept; ++ simdjson_inline simdjson_result count_fields() & noexcept; ++ simdjson_inline simdjson_result at(size_t index) & noexcept; ++ simdjson_inline simdjson_result begin() & noexcept; ++ simdjson_inline simdjson_result end() & noexcept; ++ simdjson_inline simdjson_result find_field(std::string_view key) & noexcept; ++ simdjson_inline simdjson_result find_field(const char *key) & noexcept; ++ simdjson_inline simdjson_result operator[](std::string_view key) & noexcept; ++ simdjson_inline simdjson_result operator[](const char *key) & noexcept; ++ simdjson_inline simdjson_result find_field_unordered(std::string_view key) & noexcept; ++ simdjson_inline simdjson_result find_field_unordered(const char *key) & noexcept; ++ simdjson_inline simdjson_result type() noexcept; ++ simdjson_inline simdjson_result is_scalar() noexcept; ++ simdjson_inline simdjson_result current_location() noexcept; ++ simdjson_inline simdjson_result current_depth() const noexcept; ++ simdjson_inline simdjson_result is_negative() noexcept; ++ simdjson_inline simdjson_result is_integer() noexcept; ++ simdjson_inline simdjson_result get_number_type() noexcept; ++ simdjson_inline simdjson_result get_number() noexcept; ++ /** @copydoc simdjson_inline std::string_view document_reference::raw_json_token() const noexcept */ ++ simdjson_inline simdjson_result raw_json_token() noexcept; ++ ++ simdjson_inline simdjson_result at_pointer(std::string_view json_pointer) noexcept; ++}; ++ ++ ++} // namespace simdjson ++/* end file include/simdjson/generic/ondemand/document.h */ ++/* begin file include/simdjson/generic/ondemand/value.h */ ++ ++namespace simdjson { ++namespace SIMDJSON_BUILTIN_IMPLEMENTATION { ++namespace ondemand { ++ ++class array; ++class document; ++class field; ++class object; ++class raw_json_string; ++ ++/** ++ * An ephemeral JSON value returned during iteration. It is only valid for as long as you do ++ * not access more data in the JSON document. ++ */ ++class value { ++public: ++ /** ++ * Create a new invalid value. ++ * ++ * Exists so you can declare a variable and later assign to it before use. ++ */ ++ simdjson_inline value() noexcept = default; ++ ++ /** ++ * Get this value as the given type. ++ * ++ * Supported types: object, array, raw_json_string, string_view, uint64_t, int64_t, double, bool ++ * ++ * You may use get_double(), get_bool(), get_uint64(), get_int64(), ++ * get_object(), get_array(), get_raw_json_string(), or get_string() instead. ++ * ++ * @returns A value of the given type, parsed from the JSON. ++ * @returns INCORRECT_TYPE If the JSON value is not the given type. ++ */ ++ template simdjson_inline simdjson_result get() noexcept { ++ // Unless the simdjson library provides an inline implementation, calling this method should ++ // immediately fail. ++ static_assert(!sizeof(T), "The get method with given type is not implemented by the simdjson library."); ++ } ++ ++ /** ++ * Get this value as the given type. ++ * ++ * Supported types: object, array, raw_json_string, string_view, uint64_t, int64_t, double, bool ++ * ++ * @param out This is set to a value of the given type, parsed from the JSON. If there is an error, this may not be initialized. ++ * @returns INCORRECT_TYPE If the JSON value is not an object. ++ * @returns SUCCESS If the parse succeeded and the out parameter was set to the value. ++ */ ++ template simdjson_inline error_code get(T &out) noexcept; ++ ++ /** ++ * Cast this JSON value to an array. ++ * ++ * @returns An object that can be used to iterate the array. ++ * @returns INCORRECT_TYPE If the JSON value is not an array. ++ */ ++ simdjson_inline simdjson_result get_array() noexcept; ++ ++ /** ++ * Cast this JSON value to an object. ++ * ++ * @returns An object that can be used to look up or iterate fields. ++ * @returns INCORRECT_TYPE If the JSON value is not an object. ++ */ ++ simdjson_inline simdjson_result get_object() noexcept; ++ ++ /** ++ * Cast this JSON value to an unsigned integer. ++ * ++ * @returns A unsigned 64-bit integer. ++ * @returns INCORRECT_TYPE If the JSON value is not a 64-bit unsigned integer. ++ */ ++ simdjson_inline simdjson_result get_uint64() noexcept; ++ ++ /** ++ * Cast this JSON value (inside string) to a unsigned integer. ++ * ++ * @returns A unsigned 64-bit integer. ++ * @returns INCORRECT_TYPE If the JSON value is not a 64-bit unsigned integer. ++ */ ++ simdjson_inline simdjson_result get_uint64_in_string() noexcept; ++ ++ /** ++ * Cast this JSON value to a signed integer. ++ * ++ * @returns A signed 64-bit integer. ++ * @returns INCORRECT_TYPE If the JSON value is not a 64-bit integer. ++ */ ++ simdjson_inline simdjson_result get_int64() noexcept; ++ ++ /** ++ * Cast this JSON value (inside string) to a signed integer. ++ * ++ * @returns A signed 64-bit integer. ++ * @returns INCORRECT_TYPE If the JSON value is not a 64-bit integer. ++ */ ++ simdjson_inline simdjson_result get_int64_in_string() noexcept; ++ ++ /** ++ * Cast this JSON value to a double. ++ * ++ * @returns A double. ++ * @returns INCORRECT_TYPE If the JSON value is not a valid floating-point number. ++ */ ++ simdjson_inline simdjson_result get_double() noexcept; ++ ++ /** ++ * Cast this JSON value (inside string) to a double ++ * ++ * @returns A double. ++ * @returns INCORRECT_TYPE If the JSON value is not a valid floating-point number. ++ */ ++ simdjson_inline simdjson_result get_double_in_string() noexcept; ++ ++ /** ++ * Cast this JSON value to a string. ++ * ++ * The string is guaranteed to be valid UTF-8. ++ * ++ * Equivalent to get(). ++ * ++ * Important: a value should be consumed once. Calling get_string() twice on the same value ++ * is an error. ++ * ++ * @returns An UTF-8 string. The string is stored in the parser and will be invalidated the next ++ * time it parses a document or when it is destroyed. ++ * @returns INCORRECT_TYPE if the JSON value is not a string. ++ */ ++ simdjson_inline simdjson_result get_string() noexcept; ++ ++ /** ++ * Cast this JSON value to a raw_json_string. ++ * ++ * The string is guaranteed to be valid UTF-8, and may have escapes in it (e.g. \\ or \n). ++ * ++ * @returns A pointer to the raw JSON for the given string. ++ * @returns INCORRECT_TYPE if the JSON value is not a string. ++ */ ++ simdjson_inline simdjson_result get_raw_json_string() noexcept; ++ ++ /** ++ * Cast this JSON value to a bool. ++ * ++ * @returns A bool value. ++ * @returns INCORRECT_TYPE if the JSON value is not true or false. ++ */ ++ simdjson_inline simdjson_result get_bool() noexcept; ++ ++ /** ++ * Checks if this JSON value is null. If and only if the value is ++ * null, then it is consumed (we advance). If we find a token that ++ * begins with 'n' but is not 'null', then an error is returned. ++ * ++ * @returns Whether the value is null. ++ * @returns INCORRECT_TYPE If the JSON value begins with 'n' and is not 'null'. ++ */ ++ simdjson_inline simdjson_result is_null() noexcept; ++ ++#if SIMDJSON_EXCEPTIONS ++ /** ++ * Cast this JSON value to an array. ++ * ++ * @returns An object that can be used to iterate the array. ++ * @exception simdjson_error(INCORRECT_TYPE) If the JSON value is not an array. ++ */ ++ simdjson_inline operator array() noexcept(false); ++ /** ++ * Cast this JSON value to an object. ++ * ++ * @returns An object that can be used to look up or iterate fields. ++ * @exception simdjson_error(INCORRECT_TYPE) If the JSON value is not an object. ++ */ ++ simdjson_inline operator object() noexcept(false); ++ /** ++ * Cast this JSON value to an unsigned integer. ++ * ++ * @returns A signed 64-bit integer. ++ * @exception simdjson_error(INCORRECT_TYPE) If the JSON value is not a 64-bit unsigned integer. ++ */ ++ simdjson_inline operator uint64_t() noexcept(false); ++ /** ++ * Cast this JSON value to a signed integer. ++ * ++ * @returns A signed 64-bit integer. ++ * @exception simdjson_error(INCORRECT_TYPE) If the JSON value is not a 64-bit integer. ++ */ ++ simdjson_inline operator int64_t() noexcept(false); ++ /** ++ * Cast this JSON value to a double. ++ * ++ * @returns A double. ++ * @exception simdjson_error(INCORRECT_TYPE) If the JSON value is not a valid floating-point number. ++ */ ++ simdjson_inline operator double() noexcept(false); ++ /** ++ * Cast this JSON value to a string. ++ * ++ * The string is guaranteed to be valid UTF-8. ++ * ++ * Equivalent to get(). ++ * ++ * @returns An UTF-8 string. The string is stored in the parser and will be invalidated the next ++ * time it parses a document or when it is destroyed. ++ * @exception simdjson_error(INCORRECT_TYPE) if the JSON value is not a string. ++ */ ++ simdjson_inline operator std::string_view() noexcept(false); ++ /** ++ * Cast this JSON value to a raw_json_string. ++ * ++ * The string is guaranteed to be valid UTF-8, and may have escapes in it (e.g. \\ or \n). ++ * ++ * @returns A pointer to the raw JSON for the given string. ++ * @exception simdjson_error(INCORRECT_TYPE) if the JSON value is not a string. ++ */ ++ simdjson_inline operator raw_json_string() noexcept(false); ++ /** ++ * Cast this JSON value to a bool. ++ * ++ * @returns A bool value. ++ * @exception simdjson_error(INCORRECT_TYPE) if the JSON value is not true or false. ++ */ ++ simdjson_inline operator bool() noexcept(false); ++#endif ++ ++ /** ++ * Begin array iteration. ++ * ++ * Part of the std::iterable interface. ++ * ++ * @returns INCORRECT_TYPE If the JSON value is not an array. ++ */ ++ simdjson_inline simdjson_result begin() & noexcept; ++ /** ++ * Sentinel representing the end of the array. ++ * ++ * Part of the std::iterable interface. ++ */ ++ simdjson_inline simdjson_result end() & noexcept; ++ /** ++ * This method scans the array and counts the number of elements. ++ * The count_elements method should always be called before you have begun ++ * iterating through the array: it is expected that you are pointing at ++ * the beginning of the array. ++ * The runtime complexity is linear in the size of the array. After ++ * calling this function, if successful, the array is 'rewinded' at its ++ * beginning as if it had never been accessed. If the JSON is malformed (e.g., ++ * there is a missing comma), then an error is returned and it is no longer ++ * safe to continue. ++ * ++ * Performance hint: You should only call count_elements() as a last ++ * resort as it may require scanning the document twice or more. ++ */ ++ simdjson_inline simdjson_result count_elements() & noexcept; ++ /** ++ * This method scans the object and counts the number of key-value pairs. ++ * The count_fields method should always be called before you have begun ++ * iterating through the object: it is expected that you are pointing at ++ * the beginning of the object. ++ * The runtime complexity is linear in the size of the object. After ++ * calling this function, if successful, the object is 'rewinded' at its ++ * beginning as if it had never been accessed. If the JSON is malformed (e.g., ++ * there is a missing comma), then an error is returned and it is no longer ++ * safe to continue. ++ * ++ * To check that an object is empty, it is more performant to use ++ * the is_empty() method on the object instance. ++ * ++ * Performance hint: You should only call count_fields() as a last ++ * resort as it may require scanning the document twice or more. ++ */ ++ simdjson_inline simdjson_result count_fields() & noexcept; ++ /** ++ * Get the value at the given index in the array. This function has linear-time complexity. ++ * This function should only be called once on an array instance since the array iterator is not reset between each call. ++ * ++ * @return The value at the given index, or: ++ * - INDEX_OUT_OF_BOUNDS if the array index is larger than an array length ++ */ ++ simdjson_inline simdjson_result at(size_t index) noexcept; ++ /** ++ * Look up a field by name on an object (order-sensitive). ++ * ++ * The following code reads z, then y, then x, and thus will not retrieve x or y if fed the ++ * JSON `{ "x": 1, "y": 2, "z": 3 }`: ++ * ++ * ```c++ ++ * simdjson::ondemand::parser parser; ++ * auto obj = parser.parse(R"( { "x": 1, "y": 2, "z": 3 } )"_padded); ++ * double z = obj.find_field("z"); ++ * double y = obj.find_field("y"); ++ * double x = obj.find_field("x"); ++ * ``` ++ * If you have multiple fields with a matching key ({"x": 1, "x": 1}) be mindful ++ * that only one field is returned. ++ ++ * **Raw Keys:** The lookup will be done against the *raw* key, and will not unescape keys. ++ * e.g. `object["a"]` will match `{ "a": 1 }`, but will *not* match `{ "\u0061": 1 }`. ++ * ++ * @param key The key to look up. ++ * @returns The value of the field, or NO_SUCH_FIELD if the field is not in the object. ++ */ ++ simdjson_inline simdjson_result find_field(std::string_view key) noexcept; ++ /** @overload simdjson_inline simdjson_result find_field(std::string_view key) noexcept; */ ++ simdjson_inline simdjson_result find_field(const char *key) noexcept; ++ ++ /** ++ * Look up a field by name on an object, without regard to key order. ++ * ++ * **Performance Notes:** This is a bit less performant than find_field(), though its effect varies ++ * and often appears negligible. It starts out normally, starting out at the last field; but if ++ * the field is not found, it scans from the beginning of the object to see if it missed it. That ++ * missing case has a non-cache-friendly bump and lots of extra scanning, especially if the object ++ * in question is large. The fact that the extra code is there also bumps the executable size. ++ * ++ * It is the default, however, because it would be highly surprising (and hard to debug) if the ++ * default behavior failed to look up a field just because it was in the wrong order--and many ++ * APIs assume this. Therefore, you must be explicit if you want to treat objects as out of order. ++ * ++ * If you have multiple fields with a matching key ({"x": 1, "x": 1}) be mindful ++ * that only one field is returned. ++ * ++ * Use find_field() if you are sure fields will be in order (or are willing to treat it as if the ++ * field wasn't there when they aren't). ++ * ++ * @param key The key to look up. ++ * @returns The value of the field, or NO_SUCH_FIELD if the field is not in the object. ++ */ ++ simdjson_inline simdjson_result find_field_unordered(std::string_view key) noexcept; ++ /** @overload simdjson_inline simdjson_result find_field_unordered(std::string_view key) noexcept; */ ++ simdjson_inline simdjson_result find_field_unordered(const char *key) noexcept; ++ /** @overload simdjson_inline simdjson_result find_field_unordered(std::string_view key) noexcept; */ ++ simdjson_inline simdjson_result operator[](std::string_view key) noexcept; ++ /** @overload simdjson_inline simdjson_result find_field_unordered(std::string_view key) noexcept; */ ++ simdjson_inline simdjson_result operator[](const char *key) noexcept; ++ ++ /** ++ * Get the type of this JSON value. It does not validate or consume the value. ++ * E.g., you must still call "is_null()" to check that a value is null even if ++ * "type()" returns json_type::null. ++ * ++ * NOTE: If you're only expecting a value to be one type (a typical case), it's generally ++ * better to just call .get_double, .get_string, etc. and check for INCORRECT_TYPE (or just ++ * let it throw an exception). ++ * ++ * @return The type of JSON value (json_type::array, json_type::object, json_type::string, ++ * json_type::number, json_type::boolean, or json_type::null). ++ * @error TAPE_ERROR when the JSON value is a bad token like "}" "," or "alse". ++ */ ++ simdjson_inline simdjson_result type() noexcept; ++ ++ /** ++ * Checks whether the value is a scalar (string, number, null, Boolean). ++ * Returns false when there it is an array or object. ++ * ++ * @returns true if the type is string, number, null, Boolean ++ * @error TAPE_ERROR when the JSON value is a bad token like "}" "," or "alse". ++ */ ++ simdjson_inline simdjson_result is_scalar() noexcept; ++ ++ /** ++ * Checks whether the value is a negative number. ++ * ++ * @returns true if the number if negative. ++ */ ++ simdjson_inline bool is_negative() noexcept; ++ /** ++ * Checks whether the value is an integer number. Note that ++ * this requires to partially parse the number string. If ++ * the value is determined to be an integer, it may still ++ * not parse properly as an integer in subsequent steps ++ * (e.g., it might overflow). ++ * ++ * Performance note: if you call this function systematically ++ * before parsing a number, you may have fallen for a performance ++ * anti-pattern. ++ * ++ * @returns true if the number if negative. ++ */ ++ simdjson_inline simdjson_result is_integer() noexcept; ++ /** ++ * Determine the number type (integer or floating-point number) as quickly ++ * as possible. This function does not fully validate the input. It is ++ * useful when you only need to classify the numbers, without parsing them. ++ * ++ * If you are planning to retrieve the value or you need full validation, ++ * consider using the get_number() method instead: it will fully parse ++ * and validate the input, and give you access to the type: ++ * get_number().get_number_type(). ++ * ++ * get_number_type() is number_type::unsigned_integer if we have ++ * an integer greater or equal to 9223372036854775808 ++ * get_number_type() is number_type::signed_integer if we have an ++ * integer that is less than 9223372036854775808 ++ * Otherwise, get_number_type() has value number_type::floating_point_number ++ * ++ * This function requires processing the number string, but it is expected ++ * to be faster than get_number().get_number_type() because it is does not ++ * parse the number value. ++ * ++ * @returns the type of the number ++ */ ++ simdjson_inline simdjson_result get_number_type() noexcept; ++ ++ /** ++ * Attempt to parse an ondemand::number. An ondemand::number may ++ * contain an integer value or a floating-point value, the simdjson ++ * library will autodetect the type. Thus it is a dynamically typed ++ * number. Before accessing the value, you must determine the detected ++ * type. ++ * ++ * number.get_number_type() is number_type::signed_integer if we have ++ * an integer in [-9223372036854775808,9223372036854775808) ++ * You can recover the value by calling number.get_int64() and you ++ * have that number.is_int64() is true. ++ * ++ * number.get_number_type() is number_type::unsigned_integer if we have ++ * an integer in [9223372036854775808,18446744073709551616) ++ * You can recover the value by calling number.get_uint64() and you ++ * have that number.is_uint64() is true. ++ * ++ * Otherwise, number.get_number_type() has value number_type::floating_point_number ++ * and we have a binary64 number. ++ * You can recover the value by calling number.get_double() and you ++ * have that number.is_double() is true. ++ * ++ * You must check the type before accessing the value: it is an error ++ * to call "get_int64()" when number.get_number_type() is not ++ * number_type::signed_integer and when number.is_int64() is false. ++ * ++ * Performance note: this is designed with performance in mind. When ++ * calling 'get_number()', you scan the number string only once, determining ++ * efficiently the type and storing it in an efficient manner. ++ */ ++ simdjson_warn_unused simdjson_inline simdjson_result get_number() noexcept; ++ ++ ++ /** ++ * Get the raw JSON for this token. ++ * ++ * The string_view will always point into the input buffer. ++ * ++ * The string_view will start at the beginning of the token, and include the entire token ++ * *as well as all spaces until the next token (or EOF).* This means, for example, that a ++ * string token always begins with a " and is always terminated by the final ", possibly ++ * followed by a number of spaces. ++ * ++ * The string_view is *not* null-terminated. However, if this is a scalar (string, number, ++ * boolean, or null), the character after the end of the string_view is guaranteed to be ++ * a non-space token. ++ * ++ * Tokens include: ++ * - { ++ * - [ ++ * - "a string (possibly with UTF-8 or backslashed characters like \\\")". ++ * - -1.2e-100 ++ * - true ++ * - false ++ * - null ++ */ ++ simdjson_inline std::string_view raw_json_token() noexcept; ++ ++ /** ++ * Returns the current location in the document if in bounds. ++ */ ++ simdjson_inline simdjson_result current_location() noexcept; ++ ++ /** ++ * Returns the current depth in the document if in bounds. ++ * ++ * E.g., ++ * 0 = finished with document ++ * 1 = document root value (could be [ or {, not yet known) ++ * 2 = , or } inside root array/object ++ * 3 = key or value inside root array/object. ++ */ ++ simdjson_inline int32_t current_depth() const noexcept; ++ ++ /** ++ * Get the value associated with the given JSON pointer. We use the RFC 6901 ++ * https://tools.ietf.org/html/rfc6901 standard. ++ * ++ * ondemand::parser parser; ++ * auto json = R"({ "foo": { "a": [ 10, 20, 30 ] }})"_padded; ++ * auto doc = parser.iterate(json); ++ * doc.at_pointer("/foo/a/1") == 20 ++ * ++ * It is allowed for a key to be the empty string: ++ * ++ * ondemand::parser parser; ++ * auto json = R"({ "": { "a": [ 10, 20, 30 ] }})"_padded; ++ * auto doc = parser.iterate(json); ++ * doc.at_pointer("//a/1") == 20 ++ * ++ * Note that at_pointer() called on the document automatically calls the document's rewind ++ * method between each call. It invalidates all previously accessed arrays, objects and values ++ * that have not been consumed. ++ * ++ * Calling at_pointer() on non-document instances (e.g., arrays and objects) is not ++ * standardized (by RFC 6901). We provide some experimental support for JSON pointers ++ * on non-document instances. Yet it is not the case when calling at_pointer on an array ++ * or an object instance: there is no rewind and no invalidation. ++ * ++ * You may only call at_pointer on an array after it has been created, but before it has ++ * been first accessed. When calling at_pointer on an array, the pointer is advanced to ++ * the location indicated by the JSON pointer (in case of success). It is no longer possible ++ * to call at_pointer on the same array. ++ * ++ * You may call at_pointer more than once on an object, but each time the pointer is advanced ++ * to be within the value matched by the key indicated by the JSON pointer query. Thus any preceding ++ * key (as well as the current key) can no longer be used with following JSON pointer calls. ++ * ++ * Also note that at_pointer() relies on find_field() which implies that we do not unescape keys when matching ++ * ++ * @return The value associated with the given JSON pointer, or: ++ * - NO_SUCH_FIELD if a field does not exist in an object ++ * - INDEX_OUT_OF_BOUNDS if an array index is larger than an array length ++ * - INCORRECT_TYPE if a non-integer is used to access an array ++ * - INVALID_JSON_POINTER if the JSON pointer is invalid and cannot be parsed ++ */ ++ simdjson_inline simdjson_result at_pointer(std::string_view json_pointer) noexcept; ++ ++protected: ++ /** ++ * Create a value. ++ */ ++ simdjson_inline value(const value_iterator &iter) noexcept; ++ ++ /** ++ * Skip this value, allowing iteration to continue. ++ */ ++ simdjson_inline void skip() noexcept; ++ ++ /** ++ * Start a value at the current position. ++ * ++ * (It should already be started; this is just a self-documentation method.) ++ */ ++ static simdjson_inline value start(const value_iterator &iter) noexcept; ++ ++ /** ++ * Resume a value. ++ */ ++ static simdjson_inline value resume(const value_iterator &iter) noexcept; ++ ++ /** ++ * Get the object, starting or resuming it as necessary ++ */ ++ simdjson_inline simdjson_result start_or_resume_object() noexcept; ++ ++ // simdjson_inline void log_value(const char *type) const noexcept; ++ // simdjson_inline void log_error(const char *message) const noexcept; ++ ++ value_iterator iter{}; ++ ++ friend class document; ++ friend class array_iterator; ++ friend class field; ++ friend class object; ++ friend struct simdjson_result; ++ friend struct simdjson_result; ++}; ++ ++} // namespace ondemand ++} // namespace SIMDJSON_BUILTIN_IMPLEMENTATION ++} // namespace simdjson ++ ++namespace simdjson { ++ ++template<> ++struct simdjson_result : public SIMDJSON_BUILTIN_IMPLEMENTATION::implementation_simdjson_result_base { ++public: ++ simdjson_inline simdjson_result(SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand::value &&value) noexcept; ///< @private ++ simdjson_inline simdjson_result(error_code error) noexcept; ///< @private ++ simdjson_inline simdjson_result() noexcept = default; ++ ++ simdjson_inline simdjson_result get_array() noexcept; ++ simdjson_inline simdjson_result get_object() noexcept; ++ ++ simdjson_inline simdjson_result get_uint64() noexcept; ++ simdjson_inline simdjson_result get_uint64_in_string() noexcept; ++ simdjson_inline simdjson_result get_int64() noexcept; ++ simdjson_inline simdjson_result get_int64_in_string() noexcept; ++ simdjson_inline simdjson_result get_double() noexcept; ++ simdjson_inline simdjson_result get_double_in_string() noexcept; ++ simdjson_inline simdjson_result get_string() noexcept; ++ simdjson_inline simdjson_result get_raw_json_string() noexcept; ++ simdjson_inline simdjson_result get_bool() noexcept; ++ simdjson_inline simdjson_result is_null() noexcept; ++ ++ template simdjson_inline simdjson_result get() noexcept; ++ ++ template simdjson_inline error_code get(T &out) noexcept; ++ ++#if SIMDJSON_EXCEPTIONS ++ simdjson_inline operator SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand::array() noexcept(false); ++ simdjson_inline operator SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand::object() noexcept(false); ++ simdjson_inline operator uint64_t() noexcept(false); ++ simdjson_inline operator int64_t() noexcept(false); ++ simdjson_inline operator double() noexcept(false); ++ simdjson_inline operator std::string_view() noexcept(false); ++ simdjson_inline operator SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand::raw_json_string() noexcept(false); ++ simdjson_inline operator bool() noexcept(false); ++#endif ++ simdjson_inline simdjson_result count_elements() & noexcept; ++ simdjson_inline simdjson_result count_fields() & noexcept; ++ simdjson_inline simdjson_result at(size_t index) noexcept; ++ simdjson_inline simdjson_result begin() & noexcept; ++ simdjson_inline simdjson_result end() & noexcept; ++ ++ /** ++ * Look up a field by name on an object (order-sensitive). ++ * ++ * The following code reads z, then y, then x, and thus will not retrieve x or y if fed the ++ * JSON `{ "x": 1, "y": 2, "z": 3 }`: ++ * ++ * ```c++ ++ * simdjson::ondemand::parser parser; ++ * auto obj = parser.parse(R"( { "x": 1, "y": 2, "z": 3 } )"_padded); ++ * double z = obj.find_field("z"); ++ * double y = obj.find_field("y"); ++ * double x = obj.find_field("x"); ++ * ``` ++ * ++ * **Raw Keys:** The lookup will be done against the *raw* key, and will not unescape keys. ++ * e.g. `object["a"]` will match `{ "a": 1 }`, but will *not* match `{ "\u0061": 1 }`. ++ * ++ * @param key The key to look up. ++ * @returns The value of the field, or NO_SUCH_FIELD if the field is not in the object. ++ */ ++ simdjson_inline simdjson_result find_field(std::string_view key) noexcept; ++ /** @overload simdjson_inline simdjson_result find_field(std::string_view key) noexcept; */ ++ simdjson_inline simdjson_result find_field(const char *key) noexcept; ++ ++ /** ++ * Look up a field by name on an object, without regard to key order. ++ * ++ * **Performance Notes:** This is a bit less performant than find_field(), though its effect varies ++ * and often appears negligible. It starts out normally, starting out at the last field; but if ++ * the field is not found, it scans from the beginning of the object to see if it missed it. That ++ * missing case has a non-cache-friendly bump and lots of extra scanning, especially if the object ++ * in question is large. The fact that the extra code is there also bumps the executable size. ++ * ++ * It is the default, however, because it would be highly surprising (and hard to debug) if the ++ * default behavior failed to look up a field just because it was in the wrong order--and many ++ * APIs assume this. Therefore, you must be explicit if you want to treat objects as out of order. ++ * ++ * Use find_field() if you are sure fields will be in order (or are willing to treat it as if the ++ * field wasn't there when they aren't). ++ * ++ * @param key The key to look up. ++ * @returns The value of the field, or NO_SUCH_FIELD if the field is not in the object. ++ */ ++ simdjson_inline simdjson_result find_field_unordered(std::string_view key) noexcept; ++ /** @overload simdjson_inline simdjson_result find_field_unordered(std::string_view key) noexcept; */ ++ simdjson_inline simdjson_result find_field_unordered(const char *key) noexcept; ++ /** @overload simdjson_inline simdjson_result find_field_unordered(std::string_view key) noexcept; */ ++ simdjson_inline simdjson_result operator[](std::string_view key) noexcept; ++ /** @overload simdjson_inline simdjson_result find_field_unordered(std::string_view key) noexcept; */ ++ simdjson_inline simdjson_result operator[](const char *key) noexcept; ++ ++ /** ++ * Get the type of this JSON value. ++ * ++ * NOTE: If you're only expecting a value to be one type (a typical case), it's generally ++ * better to just call .get_double, .get_string, etc. and check for INCORRECT_TYPE (or just ++ * let it throw an exception). ++ */ ++ simdjson_inline simdjson_result type() noexcept; ++ simdjson_inline simdjson_result is_scalar() noexcept; ++ simdjson_inline simdjson_result is_negative() noexcept; ++ simdjson_inline simdjson_result is_integer() noexcept; ++ simdjson_inline simdjson_result get_number_type() noexcept; ++ simdjson_inline simdjson_result get_number() noexcept; ++ ++ /** @copydoc simdjson_inline std::string_view value::raw_json_token() const noexcept */ ++ simdjson_inline simdjson_result raw_json_token() noexcept; ++ ++ /** @copydoc simdjson_inline simdjson_result current_location() noexcept */ ++ simdjson_inline simdjson_result current_location() noexcept; ++ /** @copydoc simdjson_inline int32_t current_depth() const noexcept */ ++ simdjson_inline simdjson_result current_depth() const noexcept; ++ simdjson_inline simdjson_result at_pointer(std::string_view json_pointer) noexcept; ++}; ++ ++} // namespace simdjson ++/* end file include/simdjson/generic/ondemand/value.h */ ++/* begin file include/simdjson/generic/ondemand/field.h */ ++ ++namespace simdjson { ++namespace SIMDJSON_BUILTIN_IMPLEMENTATION { ++namespace ondemand { ++ ++/** ++ * A JSON field (key/value pair) in an object. ++ * ++ * Returned from object iteration. ++ * ++ * Extends from std::pair so you can use C++ algorithms that rely on pairs. ++ */ ++class field : public std::pair { ++public: ++ /** ++ * Create a new invalid field. ++ * ++ * Exists so you can declare a variable and later assign to it before use. ++ */ ++ simdjson_inline field() noexcept; ++ ++ /** ++ * Get the key as a string_view (for higher speed, consider raw_key). ++ * We deliberately use a more cumbersome name (unescaped_key) to force users ++ * to think twice about using it. ++ * ++ * This consumes the key: once you have called unescaped_key(), you cannot ++ * call it again nor can you call key(). ++ */ ++ simdjson_inline simdjson_warn_unused simdjson_result unescaped_key() noexcept; ++ /** ++ * Get the key as a raw_json_string. Can be used for direct comparison with ++ * an unescaped C string: e.g., key() == "test". ++ */ ++ simdjson_inline raw_json_string key() const noexcept; ++ /** ++ * Get the field value. ++ */ ++ simdjson_inline ondemand::value &value() & noexcept; ++ /** ++ * @overload ondemand::value &ondemand::value() & noexcept ++ */ ++ simdjson_inline ondemand::value value() && noexcept; ++ ++protected: ++ simdjson_inline field(raw_json_string key, ondemand::value &&value) noexcept; ++ static simdjson_inline simdjson_result start(value_iterator &parent_iter) noexcept; ++ static simdjson_inline simdjson_result start(const value_iterator &parent_iter, raw_json_string key) noexcept; ++ friend struct simdjson_result; ++ friend class object_iterator; ++}; ++ ++} // namespace ondemand ++} // namespace SIMDJSON_BUILTIN_IMPLEMENTATION ++} // namespace simdjson ++ ++namespace simdjson { ++ ++template<> ++struct simdjson_result : public SIMDJSON_BUILTIN_IMPLEMENTATION::implementation_simdjson_result_base { ++public: ++ simdjson_inline simdjson_result(SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand::field &&value) noexcept; ///< @private ++ simdjson_inline simdjson_result(error_code error) noexcept; ///< @private ++ simdjson_inline simdjson_result() noexcept = default; ++ ++ simdjson_inline simdjson_result unescaped_key() noexcept; ++ simdjson_inline simdjson_result key() noexcept; ++ simdjson_inline simdjson_result value() noexcept; ++}; ++ ++} // namespace simdjson ++/* end file include/simdjson/generic/ondemand/field.h */ ++/* begin file include/simdjson/generic/ondemand/object.h */ ++ ++namespace simdjson { ++namespace SIMDJSON_BUILTIN_IMPLEMENTATION { ++namespace ondemand { ++ ++/** ++ * A forward-only JSON object field iterator. ++ */ ++class object { ++public: ++ /** ++ * Create a new invalid object. ++ * ++ * Exists so you can declare a variable and later assign to it before use. ++ */ ++ simdjson_inline object() noexcept = default; ++ ++ simdjson_inline simdjson_result begin() noexcept; ++ simdjson_inline simdjson_result end() noexcept; ++ /** ++ * Look up a field by name on an object (order-sensitive). ++ * ++ * The following code reads z, then y, then x, and thus will not retrieve x or y if fed the ++ * JSON `{ "x": 1, "y": 2, "z": 3 }`: ++ * ++ * ```c++ ++ * simdjson::ondemand::parser parser; ++ * auto obj = parser.parse(R"( { "x": 1, "y": 2, "z": 3 } )"_padded); ++ * double z = obj.find_field("z"); ++ * double y = obj.find_field("y"); ++ * double x = obj.find_field("x"); ++ * ``` ++ * If you have multiple fields with a matching key ({"x": 1, "x": 1}) be mindful ++ * that only one field is returned. ++ * ++ * **Raw Keys:** The lookup will be done against the *raw* key, and will not unescape keys. ++ * e.g. `object["a"]` will match `{ "a": 1 }`, but will *not* match `{ "\u0061": 1 }`. ++ * ++ * You must consume the fields on an object one at a time. A request for a new key ++ * invalidates previous field values: it makes them unsafe. The value instance you get ++ * from `content["bids"]` becomes invalid when you call `content["asks"]`. The array ++ * given by content["bids"].get_array() should not be accessed after you have called ++ * content["asks"].get_array(). You can detect such mistakes by first compiling and running ++ * the code in Debug mode (or with the macro `SIMDJSON_DEVELOPMENT_CHECKS` set to 1): an ++ * OUT_OF_ORDER_ITERATION error is generated. ++ * ++ * You are expected to access keys only once. You should access the value corresponding to a ++ * key a single time. Doing object["mykey"].to_string() and then again object["mykey"].to_string() ++ * is an error. ++ * ++ * @param key The key to look up. ++ * @returns The value of the field, or NO_SUCH_FIELD if the field is not in the object. ++ */ ++ simdjson_inline simdjson_result find_field(std::string_view key) & noexcept; ++ /** @overload simdjson_inline simdjson_result find_field(std::string_view key) & noexcept; */ ++ simdjson_inline simdjson_result find_field(std::string_view key) && noexcept; ++ ++ /** ++ * Look up a field by name on an object, without regard to key order. ++ * ++ * **Performance Notes:** This is a bit less performant than find_field(), though its effect varies ++ * and often appears negligible. It starts out normally, starting out at the last field; but if ++ * the field is not found, it scans from the beginning of the object to see if it missed it. That ++ * missing case has a non-cache-friendly bump and lots of extra scanning, especially if the object ++ * in question is large. The fact that the extra code is there also bumps the executable size. ++ * ++ * It is the default, however, because it would be highly surprising (and hard to debug) if the ++ * default behavior failed to look up a field just because it was in the wrong order--and many ++ * APIs assume this. Therefore, you must be explicit if you want to treat objects as out of order. ++ * ++ * Use find_field() if you are sure fields will be in order (or are willing to treat it as if the ++ * field wasn't there when they aren't). ++ * ++ * If you have multiple fields with a matching key ({"x": 1, "x": 1}) be mindful ++ * that only one field is returned. ++ * ++ * You must consume the fields on an object one at a time. A request for a new key ++ * invalidates previous field values: it makes them unsafe. The value instance you get ++ * from `content["bids"]` becomes invalid when you call `content["asks"]`. The array ++ * given by content["bids"].get_array() should not be accessed after you have called ++ * content["asks"].get_array(). You can detect such mistakes by first compiling and running ++ * the code in Debug mode (or with the macro `SIMDJSON_DEVELOPMENT_CHECKS` set to 1): an ++ * OUT_OF_ORDER_ITERATION error is generated. ++ * ++ * You are expected to access keys only once. You should access the value corresponding to a key ++ * a single time. Doing object["mykey"].to_string() and then again object["mykey"].to_string() is an error. ++ * ++ * @param key The key to look up. ++ * @returns The value of the field, or NO_SUCH_FIELD if the field is not in the object. ++ */ ++ simdjson_inline simdjson_result find_field_unordered(std::string_view key) & noexcept; ++ /** @overload simdjson_inline simdjson_result find_field_unordered(std::string_view key) & noexcept; */ ++ simdjson_inline simdjson_result find_field_unordered(std::string_view key) && noexcept; ++ /** @overload simdjson_inline simdjson_result find_field_unordered(std::string_view key) & noexcept; */ ++ simdjson_inline simdjson_result operator[](std::string_view key) & noexcept; ++ /** @overload simdjson_inline simdjson_result find_field_unordered(std::string_view key) & noexcept; */ ++ simdjson_inline simdjson_result operator[](std::string_view key) && noexcept; ++ ++ /** ++ * Get the value associated with the given JSON pointer. We use the RFC 6901 ++ * https://tools.ietf.org/html/rfc6901 standard, interpreting the current node ++ * as the root of its own JSON document. ++ * ++ * ondemand::parser parser; ++ * auto json = R"({ "foo": { "a": [ 10, 20, 30 ] }})"_padded; ++ * auto doc = parser.iterate(json); ++ * doc.at_pointer("/foo/a/1") == 20 ++ * ++ * It is allowed for a key to be the empty string: ++ * ++ * ondemand::parser parser; ++ * auto json = R"({ "": { "a": [ 10, 20, 30 ] }})"_padded; ++ * auto doc = parser.iterate(json); ++ * doc.at_pointer("//a/1") == 20 ++ * ++ * Note that at_pointer() called on the document automatically calls the document's rewind ++ * method between each call. It invalidates all previously accessed arrays, objects and values ++ * that have not been consumed. Yet it is not the case when calling at_pointer on an object ++ * instance: there is no rewind and no invalidation. ++ * ++ * You may call at_pointer more than once on an object, but each time the pointer is advanced ++ * to be within the value matched by the key indicated by the JSON pointer query. Thus any preceding ++ * key (as well as the current key) can no longer be used with following JSON pointer calls. ++ * ++ * Also note that at_pointer() relies on find_field() which implies that we do not unescape keys when matching. ++ * ++ * @return The value associated with the given JSON pointer, or: ++ * - NO_SUCH_FIELD if a field does not exist in an object ++ * - INDEX_OUT_OF_BOUNDS if an array index is larger than an array length ++ * - INCORRECT_TYPE if a non-integer is used to access an array ++ * - INVALID_JSON_POINTER if the JSON pointer is invalid and cannot be parsed ++ */ ++ inline simdjson_result at_pointer(std::string_view json_pointer) noexcept; ++ ++ /** ++ * Reset the iterator so that we are pointing back at the ++ * beginning of the object. You should still consume values only once even if you ++ * can iterate through the object more than once. If you unescape a string within ++ * the object more than once, you have unsafe code. Note that rewinding an object ++ * means that you may need to reparse it anew: it is not a free operation. ++ * ++ * @returns true if the object contains some elements (not empty) ++ */ ++ inline simdjson_result reset() & noexcept; ++ /** ++ * This method scans the beginning of the object and checks whether the ++ * object is empty. ++ * The runtime complexity is constant time. After ++ * calling this function, if successful, the object is 'rewinded' at its ++ * beginning as if it had never been accessed. If the JSON is malformed (e.g., ++ * there is a missing comma), then an error is returned and it is no longer ++ * safe to continue. ++ */ ++ inline simdjson_result is_empty() & noexcept; ++ /** ++ * This method scans the object and counts the number of key-value pairs. ++ * The count_fields method should always be called before you have begun ++ * iterating through the object: it is expected that you are pointing at ++ * the beginning of the object. ++ * The runtime complexity is linear in the size of the object. After ++ * calling this function, if successful, the object is 'rewinded' at its ++ * beginning as if it had never been accessed. If the JSON is malformed (e.g., ++ * there is a missing comma), then an error is returned and it is no longer ++ * safe to continue. ++ * ++ * To check that an object is empty, it is more performant to use ++ * the is_empty() method. ++ * ++ * Performance hint: You should only call count_fields() as a last ++ * resort as it may require scanning the document twice or more. ++ */ ++ simdjson_inline simdjson_result count_fields() & noexcept; ++ /** ++ * Consumes the object and returns a string_view instance corresponding to the ++ * object as represented in JSON. It points inside the original byte array containing ++ * the JSON document. ++ */ ++ simdjson_inline simdjson_result raw_json() noexcept; ++ ++protected: ++ /** ++ * Go to the end of the object, no matter where you are right now. ++ */ ++ simdjson_inline error_code consume() noexcept; ++ static simdjson_inline simdjson_result start(value_iterator &iter) noexcept; ++ static simdjson_inline simdjson_result start_root(value_iterator &iter) noexcept; ++ static simdjson_inline simdjson_result started(value_iterator &iter) noexcept; ++ static simdjson_inline object resume(const value_iterator &iter) noexcept; ++ simdjson_inline object(const value_iterator &iter) noexcept; ++ ++ simdjson_warn_unused simdjson_inline error_code find_field_raw(const std::string_view key) noexcept; ++ ++ value_iterator iter{}; ++ ++ friend class value; ++ friend class document; ++ friend struct simdjson_result; ++}; ++ ++} // namespace ondemand ++} // namespace SIMDJSON_BUILTIN_IMPLEMENTATION ++} // namespace simdjson ++ ++namespace simdjson { ++ ++template<> ++struct simdjson_result : public SIMDJSON_BUILTIN_IMPLEMENTATION::implementation_simdjson_result_base { ++public: ++ simdjson_inline simdjson_result(SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand::object &&value) noexcept; ///< @private ++ simdjson_inline simdjson_result(error_code error) noexcept; ///< @private ++ simdjson_inline simdjson_result() noexcept = default; ++ ++ simdjson_inline simdjson_result begin() noexcept; ++ simdjson_inline simdjson_result end() noexcept; ++ simdjson_inline simdjson_result find_field(std::string_view key) & noexcept; ++ simdjson_inline simdjson_result find_field(std::string_view key) && noexcept; ++ simdjson_inline simdjson_result find_field_unordered(std::string_view key) & noexcept; ++ simdjson_inline simdjson_result find_field_unordered(std::string_view key) && noexcept; ++ simdjson_inline simdjson_result operator[](std::string_view key) & noexcept; ++ simdjson_inline simdjson_result operator[](std::string_view key) && noexcept; ++ simdjson_inline simdjson_result at_pointer(std::string_view json_pointer) noexcept; ++ inline simdjson_result reset() noexcept; ++ inline simdjson_result is_empty() noexcept; ++ inline simdjson_result count_fields() & noexcept; ++ ++}; ++ ++} // namespace simdjson ++/* end file include/simdjson/generic/ondemand/object.h */ ++/* begin file include/simdjson/generic/ondemand/parser.h */ ++ ++namespace simdjson { ++namespace SIMDJSON_BUILTIN_IMPLEMENTATION { ++namespace ondemand { ++ ++class array; ++class object; ++class value; ++class raw_json_string; ++class document_stream; ++ ++/** ++ * The default batch size for document_stream instances for this On Demand kernel. ++ * Note that different On Demand kernel may use a different DEFAULT_BATCH_SIZE value ++ * in the future. ++ */ ++static constexpr size_t DEFAULT_BATCH_SIZE = 1000000; ++/** ++ * Some adversary might try to set the batch size to 0 or 1, which might cause problems. ++ * We set a minimum of 32B since anything else is highly likely to be an error. In practice, ++ * most users will want a much larger batch size. ++ * ++ * All non-negative MINIMAL_BATCH_SIZE values should be 'safe' except that, obviously, no JSON ++ * document can ever span 0 or 1 byte and that very large values would create memory allocation issues. ++ */ ++static constexpr size_t MINIMAL_BATCH_SIZE = 32; ++ ++/** ++ * A JSON fragment iterator. ++ * ++ * This holds the actual iterator as well as the buffer for writing strings. ++ */ ++class parser { ++public: ++ /** ++ * Create a JSON parser. ++ * ++ * The new parser will have zero capacity. ++ */ ++ inline explicit parser(size_t max_capacity = SIMDJSON_MAXSIZE_BYTES) noexcept; ++ ++ inline parser(parser &&other) noexcept = default; ++ simdjson_inline parser(const parser &other) = delete; ++ simdjson_inline parser &operator=(const parser &other) = delete; ++ simdjson_inline parser &operator=(parser &&other) noexcept = default; ++ ++ /** Deallocate the JSON parser. */ ++ inline ~parser() noexcept = default; ++ ++ /** ++ * Start iterating an on-demand JSON document. ++ * ++ * ondemand::parser parser; ++ * document doc = parser.iterate(json); ++ * ++ * It is expected that the content is a valid UTF-8 file, containing a valid JSON document. ++ * Otherwise the iterate method may return an error. In particular, the whole input should be ++ * valid: we do not attempt to tolerate incorrect content either before or after a JSON ++ * document. ++ * ++ * ### IMPORTANT: Validate what you use ++ * ++ * Calling iterate on an invalid JSON document may not immediately trigger an error. The call to ++ * iterate does not parse and validate the whole document. ++ * ++ * ### IMPORTANT: Buffer Lifetime ++ * ++ * Because parsing is done while you iterate, you *must* keep the JSON buffer around at least as ++ * long as the document iteration. ++ * ++ * ### IMPORTANT: Document Lifetime ++ * ++ * Only one iteration at a time can happen per parser, and the parser *must* be kept alive during ++ * iteration to ensure intermediate buffers can be accessed. Any document must be destroyed before ++ * you call parse() again or destroy the parser. ++ * ++ * ### REQUIRED: Buffer Padding ++ * ++ * The buffer must have at least SIMDJSON_PADDING extra allocated bytes. It does not matter what ++ * those bytes are initialized to, as long as they are allocated. ++ * ++ * @param json The JSON to parse. ++ * @param len The length of the JSON. ++ * @param capacity The number of bytes allocated in the JSON (must be at least len+SIMDJSON_PADDING). ++ * ++ * @return The document, or an error: ++ * - INSUFFICIENT_PADDING if the input has less than SIMDJSON_PADDING extra bytes. ++ * - MEMALLOC if realloc_if_needed the parser does not have enough capacity, and memory ++ * allocation fails. ++ * - EMPTY if the document is all whitespace. ++ * - UTF8_ERROR if the document is not valid UTF-8. ++ * - UNESCAPED_CHARS if a string contains control characters that must be escaped ++ * - UNCLOSED_STRING if there is an unclosed string in the document. ++ */ ++ simdjson_warn_unused simdjson_result iterate(padded_string_view json) & noexcept; ++ /** @overload simdjson_result iterate(padded_string_view json) & noexcept */ ++ simdjson_warn_unused simdjson_result iterate(const char *json, size_t len, size_t capacity) & noexcept; ++ /** @overload simdjson_result iterate(padded_string_view json) & noexcept */ ++ simdjson_warn_unused simdjson_result iterate(const uint8_t *json, size_t len, size_t capacity) & noexcept; ++ /** @overload simdjson_result iterate(padded_string_view json) & noexcept */ ++ simdjson_warn_unused simdjson_result iterate(std::string_view json, size_t capacity) & noexcept; ++ /** @overload simdjson_result iterate(padded_string_view json) & noexcept */ ++ simdjson_warn_unused simdjson_result iterate(const std::string &json) & noexcept; ++ /** @overload simdjson_result iterate(padded_string_view json) & noexcept */ ++ simdjson_warn_unused simdjson_result iterate(const simdjson_result &json) & noexcept; ++ /** @overload simdjson_result iterate(padded_string_view json) & noexcept */ ++ simdjson_warn_unused simdjson_result iterate(const simdjson_result &json) & noexcept; ++ /** @overload simdjson_result iterate(padded_string_view json) & noexcept */ ++ simdjson_warn_unused simdjson_result iterate(padded_string &&json) & noexcept = delete; ++ ++ /** ++ * @private ++ * ++ * Start iterating an on-demand JSON document. ++ * ++ * ondemand::parser parser; ++ * json_iterator doc = parser.iterate(json); ++ * ++ * ### IMPORTANT: Buffer Lifetime ++ * ++ * Because parsing is done while you iterate, you *must* keep the JSON buffer around at least as ++ * long as the document iteration. ++ * ++ * ### IMPORTANT: Document Lifetime ++ * ++ * Only one iteration at a time can happen per parser, and the parser *must* be kept alive during ++ * iteration to ensure intermediate buffers can be accessed. Any document must be destroyed before ++ * you call parse() again or destroy the parser. ++ * ++ * The ondemand::document instance holds the iterator. The document must remain in scope ++ * while you are accessing instances of ondemand::value, ondemand::object, ondemand::array. ++ * ++ * ### REQUIRED: Buffer Padding ++ * ++ * The buffer must have at least SIMDJSON_PADDING extra allocated bytes. It does not matter what ++ * those bytes are initialized to, as long as they are allocated. ++ * ++ * @param json The JSON to parse. ++ * ++ * @return The iterator, or an error: ++ * - INSUFFICIENT_PADDING if the input has less than SIMDJSON_PADDING extra bytes. ++ * - MEMALLOC if realloc_if_needed the parser does not have enough capacity, and memory ++ * allocation fails. ++ * - EMPTY if the document is all whitespace. ++ * - UTF8_ERROR if the document is not valid UTF-8. ++ * - UNESCAPED_CHARS if a string contains control characters that must be escaped ++ * - UNCLOSED_STRING if there is an unclosed string in the document. ++ */ ++ simdjson_warn_unused simdjson_result iterate_raw(padded_string_view json) & noexcept; ++ ++ ++ /** ++ * Parse a buffer containing many JSON documents. ++ * ++ * auto json = R"({ "foo": 1 } { "foo": 2 } { "foo": 3 } )"_padded; ++ * ondemand::parser parser; ++ * ondemand::document_stream docs = parser.iterate_many(json); ++ * for (auto & doc : docs) { ++ * std::cout << doc["foo"] << std::endl; ++ * } ++ * // Prints 1 2 3 ++ * ++ * No copy of the input buffer is made. ++ * ++ * The function is lazy: it may be that no more than one JSON document at a time is parsed. ++ * ++ * The caller is responsabile to ensure that the input string data remains unchanged and is ++ * not deleted during the loop. ++ * ++ * ### Format ++ * ++ * The buffer must contain a series of one or more JSON documents, concatenated into a single ++ * buffer, separated by ASCII whitespace. It effectively parses until it has a fully valid document, ++ * then starts parsing the next document at that point. (It does this with more parallelism and ++ * lookahead than you might think, though.) ++ * ++ * documents that consist of an object or array may omit the whitespace between them, concatenating ++ * with no separator. Documents that consist of a single primitive (i.e. documents that are not ++ * arrays or objects) MUST be separated with ASCII whitespace. ++ * ++ * The characters inside a JSON document, and between JSON documents, must be valid Unicode (UTF-8). ++ * ++ * The documents must not exceed batch_size bytes (by default 1MB) or they will fail to parse. ++ * Setting batch_size to excessively large or excessively small values may impact negatively the ++ * performance. ++ * ++ * ### REQUIRED: Buffer Padding ++ * ++ * The buffer must have at least SIMDJSON_PADDING extra allocated bytes. It does not matter what ++ * those bytes are initialized to, as long as they are allocated. ++ * ++ * ### Threads ++ * ++ * When compiled with SIMDJSON_THREADS_ENABLED, this method will use a single thread under the ++ * hood to do some lookahead. ++ * ++ * ### Parser Capacity ++ * ++ * If the parser's current capacity is less than batch_size, it will allocate enough capacity ++ * to handle it (up to max_capacity). ++ * ++ * @param buf The concatenated JSON to parse. ++ * @param len The length of the concatenated JSON. ++ * @param batch_size The batch size to use. MUST be larger than the largest document. The sweet ++ * spot is cache-related: small enough to fit in cache, yet big enough to ++ * parse as many documents as possible in one tight loop. ++ * Defaults to 10MB, which has been a reasonable sweet spot in our tests. ++ * @return The stream, or an error. An empty input will yield 0 documents rather than an EMPTY error. Errors: ++ * - MEMALLOC if the parser does not have enough capacity and memory allocation fails ++ * - CAPACITY if the parser does not have enough capacity and batch_size > max_capacity. ++ * - other json errors if parsing fails. You should not rely on these errors to always the same for the ++ * same document: they may vary under runtime dispatch (so they may vary depending on your system and hardware). ++ */ ++ inline simdjson_result iterate_many(const uint8_t *buf, size_t len, size_t batch_size = DEFAULT_BATCH_SIZE) noexcept; ++ /** @overload parse_many(const uint8_t *buf, size_t len, size_t batch_size) */ ++ inline simdjson_result iterate_many(const char *buf, size_t len, size_t batch_size = DEFAULT_BATCH_SIZE) noexcept; ++ /** @overload parse_many(const uint8_t *buf, size_t len, size_t batch_size) */ ++ inline simdjson_result iterate_many(const std::string &s, size_t batch_size = DEFAULT_BATCH_SIZE) noexcept; ++ inline simdjson_result iterate_many(const std::string &&s, size_t batch_size) = delete;// unsafe ++ /** @overload parse_many(const uint8_t *buf, size_t len, size_t batch_size) */ ++ inline simdjson_result iterate_many(const padded_string &s, size_t batch_size = DEFAULT_BATCH_SIZE) noexcept; ++ inline simdjson_result iterate_many(const padded_string &&s, size_t batch_size) = delete;// unsafe ++ ++ /** @private We do not want to allow implicit conversion from C string to std::string. */ ++ simdjson_result iterate_many(const char *buf, size_t batch_size = DEFAULT_BATCH_SIZE) noexcept = delete; ++ ++ /** The capacity of this parser (the largest document it can process). */ ++ simdjson_inline size_t capacity() const noexcept; ++ /** The maximum capacity of this parser (the largest document it is allowed to process). */ ++ simdjson_inline size_t max_capacity() const noexcept; ++ simdjson_inline void set_max_capacity(size_t max_capacity) noexcept; ++ /** ++ * The maximum depth of this parser (the most deeply nested objects and arrays it can process). ++ * This parameter is only relevant when the macro SIMDJSON_DEVELOPMENT_CHECKS is set to true. ++ * The document's instance current_depth() method should be used to monitor the parsing ++ * depth and limit it if desired. ++ */ ++ simdjson_inline size_t max_depth() const noexcept; ++ ++ /** ++ * Ensure this parser has enough memory to process JSON documents up to `capacity` bytes in length ++ * and `max_depth` depth. ++ * ++ * The max_depth parameter is only relevant when the macro SIMDJSON_DEVELOPMENT_CHECKS is set to true. ++ * The document's instance current_depth() method should be used to monitor the parsing ++ * depth and limit it if desired. ++ * ++ * @param capacity The new capacity. ++ * @param max_depth The new max_depth. Defaults to DEFAULT_MAX_DEPTH. ++ * @return The error, if there is one. ++ */ ++ simdjson_warn_unused error_code allocate(size_t capacity, size_t max_depth=DEFAULT_MAX_DEPTH) noexcept; ++ ++ #ifdef SIMDJSON_THREADS_ENABLED ++ /** ++ * The parser instance can use threads when they are available to speed up some ++ * operations. It is enabled by default. Changing this attribute will change the ++ * behavior of the parser for future operations. ++ */ ++ bool threaded{true}; ++ #endif ++ ++ /** ++ * Unescape this JSON string, replacing \\ with \, \n with newline, etc. to a user-provided buffer. ++ * The provided pointer is advanced to the end of the string by reference, and a string_view instance ++ * is returned. You can ensure that your buffer is large enough by allocating a block of memory at least ++ * as large as the input JSON plus SIMDJSON_PADDING and then unescape all strings to this one buffer. ++ * ++ * This unescape function is a low-level function. If you want a more user-friendly approach, you should ++ * avoid raw_json_string instances (e.g., by calling unescaped_key() instead of key() or get_string() ++ * instead of get_raw_json_string()). ++ * ++ * ## IMPORTANT: string_view lifetime ++ * ++ * The string_view is only valid as long as the bytes in dst. ++ * ++ * @param raw_json_string input ++ * @param dst A pointer to a buffer at least large enough to write this string as well as ++ * an additional SIMDJSON_PADDING bytes. ++ * @return A string_view pointing at the unescaped string in dst ++ * @error STRING_ERROR if escapes are incorrect. ++ */ ++ simdjson_inline simdjson_result unescape(raw_json_string in, uint8_t *&dst) const noexcept; ++private: ++ /** @private [for benchmarking access] The implementation to use */ ++ std::unique_ptr implementation{}; ++ size_t _capacity{0}; ++ size_t _max_capacity; ++ size_t _max_depth{DEFAULT_MAX_DEPTH}; ++ std::unique_ptr string_buf{}; ++#if SIMDJSON_DEVELOPMENT_CHECKS ++ std::unique_ptr start_positions{}; ++#endif ++ ++ friend class json_iterator; ++ friend class document_stream; ++}; ++ ++} // namespace ondemand ++} // namespace SIMDJSON_BUILTIN_IMPLEMENTATION ++} // namespace simdjson ++ ++namespace simdjson { ++ ++template<> ++struct simdjson_result : public SIMDJSON_BUILTIN_IMPLEMENTATION::implementation_simdjson_result_base { ++public: ++ simdjson_inline simdjson_result(SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand::parser &&value) noexcept; ///< @private ++ simdjson_inline simdjson_result(error_code error) noexcept; ///< @private ++ simdjson_inline simdjson_result() noexcept = default; ++}; ++ ++} // namespace simdjson ++/* end file include/simdjson/generic/ondemand/parser.h */ ++/* begin file include/simdjson/generic/ondemand/document_stream.h */ ++#ifdef SIMDJSON_THREADS_ENABLED ++#include ++#include ++#include ++#endif ++ ++namespace simdjson { ++namespace SIMDJSON_BUILTIN_IMPLEMENTATION { ++namespace ondemand { ++ ++class parser; ++class json_iterator; ++class document; ++ ++#ifdef SIMDJSON_THREADS_ENABLED ++/** @private Custom worker class **/ ++struct stage1_worker { ++ stage1_worker() noexcept = default; ++ stage1_worker(const stage1_worker&) = delete; ++ stage1_worker(stage1_worker&&) = delete; ++ stage1_worker operator=(const stage1_worker&) = delete; ++ ~stage1_worker(); ++ /** ++ * We only start the thread when it is needed, not at object construction, this may throw. ++ * You should only call this once. ++ **/ ++ void start_thread(); ++ /** ++ * Start a stage 1 job. You should first call 'run', then 'finish'. ++ * You must call start_thread once before. ++ */ ++ void run(document_stream * ds, parser * stage1, size_t next_batch_start); ++ /** Wait for the run to finish (blocking). You should first call 'run', then 'finish'. **/ ++ void finish(); ++ ++private: ++ ++ /** ++ * Normally, we would never stop the thread. But we do in the destructor. ++ * This function is only safe assuming that you are not waiting for results. You ++ * should have called run, then finish, and be done. ++ **/ ++ void stop_thread(); ++ ++ std::thread thread{}; ++ /** These three variables define the work done by the thread. **/ ++ ondemand::parser * stage1_thread_parser{}; ++ size_t _next_batch_start{}; ++ document_stream * owner{}; ++ /** ++ * We have two state variables. This could be streamlined to one variable in the future but ++ * we use two for clarity. ++ */ ++ bool has_work{false}; ++ bool can_work{true}; ++ ++ /** ++ * We lock using a mutex. ++ */ ++ std::mutex locking_mutex{}; ++ std::condition_variable cond_var{}; ++ ++ friend class document_stream; ++}; ++#endif // SIMDJSON_THREADS_ENABLED ++ ++/** ++ * A forward-only stream of documents. ++ * ++ * Produced by parser::iterate_many. ++ * ++ */ ++class document_stream { ++public: ++ /** ++ * Construct an uninitialized document_stream. ++ * ++ * ```c++ ++ * document_stream docs; ++ * auto error = parser.iterate_many(json).get(docs); ++ * ``` ++ */ ++ simdjson_inline document_stream() noexcept; ++ /** Move one document_stream to another. */ ++ simdjson_inline document_stream(document_stream &&other) noexcept = default; ++ /** Move one document_stream to another. */ ++ simdjson_inline document_stream &operator=(document_stream &&other) noexcept = default; ++ ++ simdjson_inline ~document_stream() noexcept; ++ ++ /** ++ * Returns the input size in bytes. ++ */ ++ inline size_t size_in_bytes() const noexcept; ++ ++ /** ++ * After iterating through the stream, this method ++ * returns the number of bytes that were not parsed at the end ++ * of the stream. If truncated_bytes() differs from zero, ++ * then the input was truncated maybe because incomplete JSON ++ * documents were found at the end of the stream. You ++ * may need to process the bytes in the interval [size_in_bytes()-truncated_bytes(), size_in_bytes()). ++ * ++ * You should only call truncated_bytes() after streaming through all ++ * documents, like so: ++ * ++ * document_stream stream = parser.iterate_many(json,window); ++ * for(auto & doc : stream) { ++ * // do something with doc ++ * } ++ * size_t truncated = stream.truncated_bytes(); ++ * ++ */ ++ inline size_t truncated_bytes() const noexcept; ++ ++ class iterator { ++ public: ++ using value_type = simdjson_result; ++ using reference = value_type; ++ ++ using difference_type = std::ptrdiff_t; ++ ++ using iterator_category = std::input_iterator_tag; ++ ++ /** ++ * Default constructor. ++ */ ++ simdjson_inline iterator() noexcept; ++ /** ++ * Get the current document (or error). ++ */ ++ simdjson_inline simdjson_result operator*() noexcept; ++ /** ++ * Advance to the next document (prefix). ++ */ ++ inline iterator& operator++() noexcept; ++ /** ++ * Check if we're at the end yet. ++ * @param other the end iterator to compare to. ++ */ ++ simdjson_inline bool operator!=(const iterator &other) const noexcept; ++ /** ++ * @private ++ * ++ * Gives the current index in the input document in bytes. ++ * ++ * document_stream stream = parser.parse_many(json,window); ++ * for(auto i = stream.begin(); i != stream.end(); ++i) { ++ * auto doc = *i; ++ * size_t index = i.current_index(); ++ * } ++ * ++ * This function (current_index()) is experimental and the usage ++ * may change in future versions of simdjson: we find the API somewhat ++ * awkward and we would like to offer something friendlier. ++ */ ++ simdjson_inline size_t current_index() const noexcept; ++ ++ /** ++ * @private ++ * ++ * Gives a view of the current document at the current position. ++ * ++ * document_stream stream = parser.iterate_many(json,window); ++ * for(auto i = stream.begin(); i != stream.end(); ++i) { ++ * std::string_view v = i.source(); ++ * } ++ * ++ * The returned string_view instance is simply a map to the (unparsed) ++ * source string: it may thus include white-space characters and all manner ++ * of padding. ++ * ++ * This function (source()) is experimental and the usage ++ * may change in future versions of simdjson: we find the API somewhat ++ * awkward and we would like to offer something friendlier. ++ * ++ */ ++ simdjson_inline std::string_view source() const noexcept; ++ ++ /** ++ * Returns error of the stream (if any). ++ */ ++ inline error_code error() const noexcept; ++ ++ private: ++ simdjson_inline iterator(document_stream *s, bool finished) noexcept; ++ /** The document_stream we're iterating through. */ ++ document_stream* stream; ++ /** Whether we're finished or not. */ ++ bool finished; ++ ++ friend class document; ++ friend class document_stream; ++ friend class json_iterator; ++ }; ++ ++ /** ++ * Start iterating the documents in the stream. ++ */ ++ simdjson_inline iterator begin() noexcept; ++ /** ++ * The end of the stream, for iterator comparison purposes. ++ */ ++ simdjson_inline iterator end() noexcept; ++ ++private: ++ ++ document_stream &operator=(const document_stream &) = delete; // Disallow copying ++ document_stream(const document_stream &other) = delete; // Disallow copying ++ ++ /** ++ * Construct a document_stream. Does not allocate or parse anything until the iterator is ++ * used. ++ * ++ * @param parser is a reference to the parser instance used to generate this document_stream ++ * @param buf is the raw byte buffer we need to process ++ * @param len is the length of the raw byte buffer in bytes ++ * @param batch_size is the size of the windows (must be strictly greater or equal to the largest JSON document) ++ */ ++ simdjson_inline document_stream( ++ ondemand::parser &parser, ++ const uint8_t *buf, ++ size_t len, ++ size_t batch_size ++ ) noexcept; ++ ++ /** ++ * Parse the first document in the buffer. Used by begin(), to handle allocation and ++ * initialization. ++ */ ++ inline void start() noexcept; ++ ++ /** ++ * Parse the next document found in the buffer previously given to document_stream. ++ * ++ * The content should be a valid JSON document encoded as UTF-8. If there is a ++ * UTF-8 BOM, the caller is responsible for omitting it, UTF-8 BOM are ++ * discouraged. ++ * ++ * You do NOT need to pre-allocate a parser. This function takes care of ++ * pre-allocating a capacity defined by the batch_size defined when creating the ++ * document_stream object. ++ * ++ * The function returns simdjson::EMPTY if there is no more data to be parsed. ++ * ++ * The function returns simdjson::SUCCESS (as integer = 0) in case of success ++ * and indicates that the buffer has successfully been parsed to the end. ++ * Every document it contained has been parsed without error. ++ * ++ * The function returns an error code from simdjson/simdjson.h in case of failure ++ * such as simdjson::CAPACITY, simdjson::MEMALLOC, simdjson::DEPTH_ERROR and so forth; ++ * the simdjson::error_message function converts these error codes into a string). ++ * ++ * You can also check validity by calling parser.is_valid(). The same parser can ++ * and should be reused for the other documents in the buffer. ++ */ ++ inline void next() noexcept; ++ ++ /** Move the json_iterator of the document to the location of the next document in the stream. */ ++ inline void next_document() noexcept; ++ ++ /** Get the next document index. */ ++ inline size_t next_batch_start() const noexcept; ++ ++ /** Pass the next batch through stage 1 with the given parser. */ ++ inline error_code run_stage1(ondemand::parser &p, size_t batch_start) noexcept; ++ ++ // Fields ++ ondemand::parser *parser; ++ const uint8_t *buf; ++ size_t len; ++ size_t batch_size; ++ /** ++ * We are going to use just one document instance. The document owns ++ * the json_iterator. It implies that we only ever pass a reference ++ * to the document to the users. ++ */ ++ document doc{}; ++ /** The error (or lack thereof) from the current document. */ ++ error_code error; ++ size_t batch_start{0}; ++ size_t doc_index{}; ++ ++ #ifdef SIMDJSON_THREADS_ENABLED ++ /** Indicates whether we use threads. Note that this needs to be a constant during the execution of the parsing. */ ++ bool use_thread; ++ ++ inline void load_from_stage1_thread() noexcept; ++ ++ /** Start a thread to run stage 1 on the next batch. */ ++ inline void start_stage1_thread() noexcept; ++ ++ /** Wait for the stage 1 thread to finish and capture the results. */ ++ inline void finish_stage1_thread() noexcept; ++ ++ /** The error returned from the stage 1 thread. */ ++ error_code stage1_thread_error{UNINITIALIZED}; ++ /** The thread used to run stage 1 against the next batch in the background. */ ++ std::unique_ptr worker{new(std::nothrow) stage1_worker()}; ++ /** ++ * The parser used to run stage 1 in the background. Will be swapped ++ * with the regular parser when finished. ++ */ ++ ondemand::parser stage1_thread_parser{}; ++ ++ friend struct stage1_worker; ++ #endif // SIMDJSON_THREADS_ENABLED ++ ++ friend class parser; ++ friend class document; ++ friend class json_iterator; ++ friend struct simdjson_result; ++ friend struct internal::simdjson_result_base; ++}; // document_stream ++ ++} // namespace ondemand ++} // namespace SIMDJSON_BUILTIN_IMPLEMENTATION ++} // namespace simdjson ++ ++namespace simdjson { ++template<> ++struct simdjson_result : public SIMDJSON_BUILTIN_IMPLEMENTATION::implementation_simdjson_result_base { ++public: ++ simdjson_inline simdjson_result(SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand::document_stream &&value) noexcept; ///< @private ++ simdjson_inline simdjson_result(error_code error) noexcept; ///< @private ++ simdjson_inline simdjson_result() noexcept = default; ++}; ++ ++} // namespace simdjson ++/* end file include/simdjson/generic/ondemand/document_stream.h */ ++/* begin file include/simdjson/generic/ondemand/serialization.h */ ++ ++namespace simdjson { ++/** ++ * Create a string-view instance out of a document instance. The string-view instance ++ * contains JSON text that is suitable to be parsed as JSON again. It does not ++ * validate the content. ++ */ ++inline simdjson_result to_json_string(SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand::document& x) noexcept; ++/** ++ * Create a string-view instance out of a value instance. The string-view instance ++ * contains JSON text that is suitable to be parsed as JSON again. The value must ++ * not have been accessed previously. It does not ++ * validate the content. ++ */ ++inline simdjson_result to_json_string(SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand::value& x) noexcept; ++/** ++ * Create a string-view instance out of an object instance. The string-view instance ++ * contains JSON text that is suitable to be parsed as JSON again. It does not ++ * validate the content. ++ */ ++inline simdjson_result to_json_string(SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand::object& x) noexcept; ++/** ++ * Create a string-view instance out of an array instance. The string-view instance ++ * contains JSON text that is suitable to be parsed as JSON again. It does not ++ * validate the content. ++ */ ++inline simdjson_result to_json_string(SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand::array& x) noexcept; ++inline simdjson_result to_json_string(simdjson_result x); ++inline simdjson_result to_json_string(simdjson_result x); ++inline simdjson_result to_json_string(simdjson_result x); ++inline simdjson_result to_json_string(simdjson_result x); ++} // namespace simdjson ++ ++/** ++ * We want to support argument-dependent lookup (ADL). ++ * Hence we should define operator<< in the namespace ++ * where the argument (here value, object, etc.) resides. ++ * Credit: @madhur4127 ++ * See https://github.com/simdjson/simdjson/issues/1768 ++ */ ++namespace simdjson { namespace SIMDJSON_BUILTIN_IMPLEMENTATION { namespace ondemand { ++ ++/** ++ * Print JSON to an output stream. It does not ++ * validate the content. ++ * ++ * @param out The output stream. ++ * @param value The element. ++ * @throw if there is an error with the underlying output stream. simdjson itself will not throw. ++ */ ++inline std::ostream& operator<<(std::ostream& out, simdjson::SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand::value x); ++#if SIMDJSON_EXCEPTIONS ++inline std::ostream& operator<<(std::ostream& out, simdjson::simdjson_result x); ++#endif ++/** ++ * Print JSON to an output stream. It does not ++ * validate the content. ++ * ++ * @param out The output stream. ++ * @param value The array. ++ * @throw if there is an error with the underlying output stream. simdjson itself will not throw. ++ */ ++inline std::ostream& operator<<(std::ostream& out, simdjson::SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand::array value); ++#if SIMDJSON_EXCEPTIONS ++inline std::ostream& operator<<(std::ostream& out, simdjson::simdjson_result x); ++#endif ++/** ++ * Print JSON to an output stream. It does not ++ * validate the content. ++ * ++ * @param out The output stream. ++ * @param value The array. ++ * @throw if there is an error with the underlying output stream. simdjson itself will not throw. ++ */ ++inline std::ostream& operator<<(std::ostream& out, simdjson::SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand::document& value); ++#if SIMDJSON_EXCEPTIONS ++inline std::ostream& operator<<(std::ostream& out, simdjson::simdjson_result&& x); ++#endif ++inline std::ostream& operator<<(std::ostream& out, simdjson::SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand::document_reference& value); ++#if SIMDJSON_EXCEPTIONS ++inline std::ostream& operator<<(std::ostream& out, simdjson::simdjson_result&& x); ++#endif ++/** ++ * Print JSON to an output stream. It does not ++ * validate the content. ++ * ++ * @param out The output stream. ++ * @param value The object. ++ * @throw if there is an error with the underlying output stream. simdjson itself will not throw. ++ */ ++inline std::ostream& operator<<(std::ostream& out, simdjson::SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand::object value); ++#if SIMDJSON_EXCEPTIONS ++inline std::ostream& operator<<(std::ostream& out, simdjson::simdjson_result x); ++#endif ++}}} // namespace simdjson::SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand ++/* end file include/simdjson/generic/ondemand/serialization.h */ ++/* end file include/simdjson/generic/ondemand.h */ ++ ++// Inline definitions ++/* begin file include/simdjson/generic/implementation_simdjson_result_base-inl.h */ ++namespace simdjson { ++namespace SIMDJSON_BUILTIN_IMPLEMENTATION { ++ ++// ++// internal::implementation_simdjson_result_base inline implementation ++// ++ ++template ++simdjson_inline void implementation_simdjson_result_base::tie(T &value, error_code &error) && noexcept { ++ error = this->second; ++ if (!error) { ++ value = std::forward>(*this).first; ++ } ++} ++ ++template ++simdjson_warn_unused simdjson_inline error_code implementation_simdjson_result_base::get(T &value) && noexcept { ++ error_code error; ++ std::forward>(*this).tie(value, error); ++ return error; ++} ++ ++template ++simdjson_inline error_code implementation_simdjson_result_base::error() const noexcept { ++ return this->second; ++} ++ ++#if SIMDJSON_EXCEPTIONS ++ ++template ++simdjson_inline T& implementation_simdjson_result_base::value() & noexcept(false) { ++ if (error()) { throw simdjson_error(error()); } ++ return this->first; ++} ++ ++template ++simdjson_inline T&& implementation_simdjson_result_base::value() && noexcept(false) { ++ return std::forward>(*this).take_value(); ++} ++ ++template ++simdjson_inline T&& implementation_simdjson_result_base::take_value() && noexcept(false) { ++ if (error()) { throw simdjson_error(error()); } ++ return std::forward(this->first); ++} ++ ++template ++simdjson_inline implementation_simdjson_result_base::operator T&&() && noexcept(false) { ++ return std::forward>(*this).take_value(); ++} ++ ++#endif // SIMDJSON_EXCEPTIONS ++ ++template ++simdjson_inline const T& implementation_simdjson_result_base::value_unsafe() const& noexcept { ++ return this->first; ++} ++ ++template ++simdjson_inline T& implementation_simdjson_result_base::value_unsafe() & noexcept { ++ return this->first; ++} ++ ++template ++simdjson_inline T&& implementation_simdjson_result_base::value_unsafe() && noexcept { ++ return std::forward(this->first); ++} ++ ++template ++simdjson_inline implementation_simdjson_result_base::implementation_simdjson_result_base(T &&value, error_code error) noexcept ++ : first{std::forward(value)}, second{error} {} ++template ++simdjson_inline implementation_simdjson_result_base::implementation_simdjson_result_base(error_code error) noexcept ++ : implementation_simdjson_result_base(T{}, error) {} ++template ++simdjson_inline implementation_simdjson_result_base::implementation_simdjson_result_base(T &&value) noexcept ++ : implementation_simdjson_result_base(std::forward(value), SUCCESS) {} ++ ++} // namespace SIMDJSON_BUILTIN_IMPLEMENTATION ++} // namespace simdjson ++/* end file include/simdjson/generic/implementation_simdjson_result_base-inl.h */ ++/* begin file include/simdjson/generic/ondemand-inl.h */ ++/* begin file include/simdjson/generic/ondemand/json_type-inl.h */ ++namespace simdjson { ++namespace SIMDJSON_BUILTIN_IMPLEMENTATION { ++namespace ondemand { ++ ++inline std::ostream& operator<<(std::ostream& out, json_type type) noexcept { ++ switch (type) { ++ case json_type::array: out << "array"; break; ++ case json_type::object: out << "object"; break; ++ case json_type::number: out << "number"; break; ++ case json_type::string: out << "string"; break; ++ case json_type::boolean: out << "boolean"; break; ++ case json_type::null: out << "null"; break; ++ default: SIMDJSON_UNREACHABLE(); ++ } ++ return out; ++} ++ ++inline std::ostream& operator<<(std::ostream& out, number_type type) noexcept { ++ switch (type) { ++ case number_type::signed_integer: out << "integer in [-9223372036854775808,9223372036854775808)"; break; ++ case number_type::unsigned_integer: out << "unsigned integer in [9223372036854775808,18446744073709551616)"; break; ++ case number_type::floating_point_number: out << "floating-point number (binary64)"; break; ++ default: SIMDJSON_UNREACHABLE(); ++ } ++ return out; ++} ++#if SIMDJSON_EXCEPTIONS ++inline std::ostream& operator<<(std::ostream& out, simdjson_result &type) noexcept(false) { ++ return out << type.value(); ++} ++#endif ++ ++ ++ ++simdjson_inline number_type number::get_number_type() const noexcept { ++ return type; ++} ++ ++simdjson_inline bool number::is_uint64() const noexcept { ++ return get_number_type() == number_type::unsigned_integer; ++} ++ ++simdjson_inline uint64_t number::get_uint64() const noexcept { ++ return payload.unsigned_integer; ++} ++ ++simdjson_inline number::operator uint64_t() const noexcept { ++ return get_uint64(); ++} ++ ++ ++simdjson_inline bool number::is_int64() const noexcept { ++ return get_number_type() == number_type::signed_integer; ++} ++ ++simdjson_inline int64_t number::get_int64() const noexcept { ++ return payload.signed_integer; ++} ++ ++simdjson_inline number::operator int64_t() const noexcept { ++ return get_int64(); ++} ++ ++simdjson_inline bool number::is_double() const noexcept { ++ return get_number_type() == number_type::floating_point_number; ++} ++ ++simdjson_inline double number::get_double() const noexcept { ++ return payload.floating_point_number; ++} ++ ++simdjson_inline number::operator double() const noexcept { ++ return get_double(); ++} ++ ++simdjson_inline double number::as_double() const noexcept { ++ if(is_double()) { ++ return payload.floating_point_number; ++ } ++ if(is_int64()) { ++ return double(payload.signed_integer); ++ } ++ return double(payload.unsigned_integer); ++} ++ ++simdjson_inline void number::append_s64(int64_t value) noexcept { ++ payload.signed_integer = value; ++ type = number_type::signed_integer; ++} ++ ++simdjson_inline void number::append_u64(uint64_t value) noexcept { ++ payload.unsigned_integer = value; ++ type = number_type::unsigned_integer; ++} ++ ++simdjson_inline void number::append_double(double value) noexcept { ++ payload.floating_point_number = value; ++ type = number_type::floating_point_number; ++} ++ ++simdjson_inline void number::skip_double() noexcept { ++ type = number_type::floating_point_number; ++} ++ ++} // namespace ondemand ++} // namespace SIMDJSON_BUILTIN_IMPLEMENTATION ++} // namespace simdjson ++ ++namespace simdjson { ++ ++simdjson_inline simdjson_result::simdjson_result(SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand::json_type &&value) noexcept ++ : implementation_simdjson_result_base(std::forward(value)) {} ++simdjson_inline simdjson_result::simdjson_result(error_code error) noexcept ++ : implementation_simdjson_result_base(error) {} ++ ++} // namespace simdjson ++/* end file include/simdjson/generic/ondemand/json_type-inl.h */ ++/* begin file include/simdjson/generic/ondemand/logger-inl.h */ ++namespace simdjson { ++namespace SIMDJSON_BUILTIN_IMPLEMENTATION { ++namespace ondemand { ++namespace logger { ++ ++static constexpr const char * DASHES = "----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------"; ++static constexpr const int LOG_EVENT_LEN = 20; ++static constexpr const int LOG_BUFFER_LEN = 30; ++static constexpr const int LOG_SMALL_BUFFER_LEN = 10; ++static int log_depth = 0; // Not threadsafe. Log only. ++ ++// Helper to turn unprintable or newline characters into spaces ++static inline char printable_char(char c) { ++ if (c >= 0x20) { ++ return c; ++ } else { ++ return ' '; ++ } ++} ++ ++inline void log_event(const json_iterator &iter, const char *type, std::string_view detail, int delta, int depth_delta) noexcept { ++ log_line(iter, "", type, detail, delta, depth_delta); ++} ++ ++inline void log_value(const json_iterator &iter, token_position index, depth_t depth, const char *type, std::string_view detail) noexcept { ++ log_line(iter, index, depth, "", type, detail); ++} ++inline void log_value(const json_iterator &iter, const char *type, std::string_view detail, int delta, int depth_delta) noexcept { ++ log_line(iter, "", type, detail, delta, depth_delta); ++} ++ ++inline void log_start_value(const json_iterator &iter, token_position index, depth_t depth, const char *type, std::string_view detail) noexcept { ++ log_line(iter, index, depth, "+", type, detail); ++ if (LOG_ENABLED) { log_depth++; } ++} ++inline void log_start_value(const json_iterator &iter, const char *type, int delta, int depth_delta) noexcept { ++ log_line(iter, "+", type, "", delta, depth_delta); ++ if (LOG_ENABLED) { log_depth++; } ++} ++ ++inline void log_end_value(const json_iterator &iter, const char *type, int delta, int depth_delta) noexcept { ++ if (LOG_ENABLED) { log_depth--; } ++ log_line(iter, "-", type, "", delta, depth_delta); ++} ++ ++inline void log_error(const json_iterator &iter, const char *error, const char *detail, int delta, int depth_delta) noexcept { ++ log_line(iter, "ERROR: ", error, detail, delta, depth_delta); ++} ++inline void log_error(const json_iterator &iter, token_position index, depth_t depth, const char *error, const char *detail) noexcept { ++ log_line(iter, index, depth, "ERROR: ", error, detail); ++} ++ ++inline void log_event(const value_iterator &iter, const char *type, std::string_view detail, int delta, int depth_delta) noexcept { ++ log_event(iter.json_iter(), type, detail, delta, depth_delta); ++} ++ ++inline void log_value(const value_iterator &iter, const char *type, std::string_view detail, int delta, int depth_delta) noexcept { ++ log_value(iter.json_iter(), type, detail, delta, depth_delta); ++} ++ ++inline void log_start_value(const value_iterator &iter, const char *type, int delta, int depth_delta) noexcept { ++ log_start_value(iter.json_iter(), type, delta, depth_delta); ++} ++ ++inline void log_end_value(const value_iterator &iter, const char *type, int delta, int depth_delta) noexcept { ++ log_end_value(iter.json_iter(), type, delta, depth_delta); ++} ++ ++inline void log_error(const value_iterator &iter, const char *error, const char *detail, int delta, int depth_delta) noexcept { ++ log_error(iter.json_iter(), error, detail, delta, depth_delta); ++} ++ ++inline void log_headers() noexcept { ++ if (LOG_ENABLED) { ++ // Technically a static variable is not thread-safe, but if you are using threads ++ // and logging... well... ++ static bool displayed_hint{false}; ++ log_depth = 0; ++ printf("\n"); ++ if(!displayed_hint) { ++ // We only print this helpful header once. ++ printf("# Logging provides the depth and position of the iterator user-visible steps:\n"); ++ printf("# +array says 'this is where we were when we discovered the start array'\n"); ++ printf("# -array says 'this is where we were when we ended the array'\n"); ++ printf("# skip says 'this is a structural or value I am skipping'\n"); ++ printf("# +/-skip says 'this is a start/end array or object I am skipping'\n"); ++ printf("#\n"); ++ printf("# The indentation of the terms (array, string,...) indicates the depth,\n"); ++ printf("# in addition to the depth being displayed.\n"); ++ printf("#\n"); ++ printf("# Every token in the document has a single depth determined by the tokens before it,\n"); ++ printf("# and is not affected by what the token actually is.\n"); ++ printf("#\n"); ++ printf("# Not all structural elements are presented as tokens in the logs.\n"); ++ printf("#\n"); ++ printf("# We never give control to the user within an empty array or an empty object.\n"); ++ printf("#\n"); ++ printf("# Inside an array, having a depth greater than the array's depth means that\n"); ++ printf("# we are pointing inside a value.\n"); ++ printf("# Having a depth equal to the array means that we are pointing right before a value.\n"); ++ printf("# Having a depth smaller than the array means that we have moved beyond the array.\n"); ++ displayed_hint = true; ++ } ++ printf("\n"); ++ printf("| %-*s ", LOG_EVENT_LEN, "Event"); ++ printf("| %-*s ", LOG_BUFFER_LEN, "Buffer"); ++ printf("| %-*s ", LOG_SMALL_BUFFER_LEN, "Next"); ++ // printf("| %-*s ", 5, "Next#"); ++ printf("| %-*s ", 5, "Depth"); ++ printf("| Detail "); ++ printf("|\n"); ++ ++ printf("|%.*s", LOG_EVENT_LEN+2, DASHES); ++ printf("|%.*s", LOG_BUFFER_LEN+2, DASHES); ++ printf("|%.*s", LOG_SMALL_BUFFER_LEN+2, DASHES); ++ // printf("|%.*s", 5+2, DASHES); ++ printf("|%.*s", 5+2, DASHES); ++ printf("|--------"); ++ printf("|\n"); ++ fflush(stdout); ++ } ++} ++ ++inline void log_line(const json_iterator &iter, const char *title_prefix, const char *title, std::string_view detail, int delta, int depth_delta) noexcept { ++ log_line(iter, iter.position()+delta, depth_t(iter.depth()+depth_delta), title_prefix, title, detail); ++} ++inline void log_line(const json_iterator &iter, token_position index, depth_t depth, const char *title_prefix, const char *title, std::string_view detail) noexcept { ++ if (LOG_ENABLED) { ++ const int indent = depth*2; ++ const auto buf = iter.token.buf; ++ printf("| %*s%s%-*s ", ++ indent, "", ++ title_prefix, ++ LOG_EVENT_LEN - indent - int(strlen(title_prefix)), title ++ ); ++ { ++ // Print the current structural. ++ printf("| "); ++ // Before we begin, the index might point right before the document. ++ // This could be unsafe, see https://github.com/simdjson/simdjson/discussions/1938 ++ if(index < iter._root) { ++ printf("%*s", LOG_BUFFER_LEN, ""); ++ } else { ++ auto current_structural = &buf[*index]; ++ for (int i=0;i(buf); } ++ ++ ++simdjson_inline bool raw_json_string::is_free_from_unescaped_quote(std::string_view target) noexcept { ++ size_t pos{0}; ++ // if the content has no escape character, just scan through it quickly! ++ for(;pos < target.size() && target[pos] != '\\';pos++) {} ++ // slow path may begin. ++ bool escaping{false}; ++ for(;pos < target.size();pos++) { ++ if((target[pos] == '"') && !escaping) { ++ return false; ++ } else if(target[pos] == '\\') { ++ escaping = !escaping; ++ } else { ++ escaping = false; ++ } ++ } ++ return true; ++} ++ ++simdjson_inline bool raw_json_string::is_free_from_unescaped_quote(const char* target) noexcept { ++ size_t pos{0}; ++ // if the content has no escape character, just scan through it quickly! ++ for(;target[pos] && target[pos] != '\\';pos++) {} ++ // slow path may begin. ++ bool escaping{false}; ++ for(;target[pos];pos++) { ++ if((target[pos] == '"') && !escaping) { ++ return false; ++ } else if(target[pos] == '\\') { ++ escaping = !escaping; ++ } else { ++ escaping = false; ++ } ++ } ++ return true; ++} ++ ++ ++simdjson_inline bool raw_json_string::unsafe_is_equal(size_t length, std::string_view target) const noexcept { ++ // If we are going to call memcmp, then we must know something about the length of the raw_json_string. ++ return (length >= target.size()) && (raw()[target.size()] == '"') && !memcmp(raw(), target.data(), target.size()); ++} ++ ++simdjson_inline bool raw_json_string::unsafe_is_equal(std::string_view target) const noexcept { ++ // Assumptions: does not contain unescaped quote characters, and ++ // the raw content is quote terminated within a valid JSON string. ++ if(target.size() <= SIMDJSON_PADDING) { ++ return (raw()[target.size()] == '"') && !memcmp(raw(), target.data(), target.size()); ++ } ++ const char * r{raw()}; ++ size_t pos{0}; ++ for(;pos < target.size();pos++) { ++ if(r[pos] != target[pos]) { return false; } ++ } ++ if(r[pos] != '"') { return false; } ++ return true; ++} ++ ++simdjson_inline bool raw_json_string::is_equal(std::string_view target) const noexcept { ++ const char * r{raw()}; ++ size_t pos{0}; ++ bool escaping{false}; ++ for(;pos < target.size();pos++) { ++ if(r[pos] != target[pos]) { return false; } ++ // if target is a compile-time constant and it is free from ++ // quotes, then the next part could get optimized away through ++ // inlining. ++ if((target[pos] == '"') && !escaping) { ++ // We have reached the end of the raw_json_string but ++ // the target is not done. ++ return false; ++ } else if(target[pos] == '\\') { ++ escaping = !escaping; ++ } else { ++ escaping = false; ++ } ++ } ++ if(r[pos] != '"') { return false; } ++ return true; ++} ++ ++ ++simdjson_inline bool raw_json_string::unsafe_is_equal(const char * target) const noexcept { ++ // Assumptions: 'target' does not contain unescaped quote characters, is null terminated and ++ // the raw content is quote terminated within a valid JSON string. ++ const char * r{raw()}; ++ size_t pos{0}; ++ for(;target[pos];pos++) { ++ if(r[pos] != target[pos]) { return false; } ++ } ++ if(r[pos] != '"') { return false; } ++ return true; ++} ++ ++simdjson_inline bool raw_json_string::is_equal(const char* target) const noexcept { ++ // Assumptions: does not contain unescaped quote characters, and ++ // the raw content is quote terminated within a valid JSON string. ++ const char * r{raw()}; ++ size_t pos{0}; ++ bool escaping{false}; ++ for(;target[pos];pos++) { ++ if(r[pos] != target[pos]) { return false; } ++ // if target is a compile-time constant and it is free from ++ // quotes, then the next part could get optimized away through ++ // inlining. ++ if((target[pos] == '"') && !escaping) { ++ // We have reached the end of the raw_json_string but ++ // the target is not done. ++ return false; ++ } else if(target[pos] == '\\') { ++ escaping = !escaping; ++ } else { ++ escaping = false; ++ } ++ } ++ if(r[pos] != '"') { return false; } ++ return true; ++} ++ ++simdjson_unused simdjson_inline bool operator==(const raw_json_string &a, std::string_view c) noexcept { ++ return a.unsafe_is_equal(c); ++} ++ ++simdjson_unused simdjson_inline bool operator==(std::string_view c, const raw_json_string &a) noexcept { ++ return a == c; ++} ++ ++simdjson_unused simdjson_inline bool operator!=(const raw_json_string &a, std::string_view c) noexcept { ++ return !(a == c); ++} ++ ++simdjson_unused simdjson_inline bool operator!=(std::string_view c, const raw_json_string &a) noexcept { ++ return !(a == c); ++} ++ ++ ++simdjson_inline simdjson_warn_unused simdjson_result raw_json_string::unescape(json_iterator &iter) const noexcept { ++ return iter.unescape(*this); ++} ++ ++ ++simdjson_unused simdjson_inline std::ostream &operator<<(std::ostream &out, const raw_json_string &str) noexcept { ++ bool in_escape = false; ++ const char *s = str.raw(); ++ while (true) { ++ switch (*s) { ++ case '\\': in_escape = !in_escape; break; ++ case '"': if (in_escape) { in_escape = false; } else { return out; } break; ++ default: if (in_escape) { in_escape = false; } ++ } ++ out << *s; ++ s++; ++ } ++} ++ ++} // namespace ondemand ++} // namespace SIMDJSON_BUILTIN_IMPLEMENTATION ++} // namespace simdjson ++ ++namespace simdjson { ++ ++simdjson_inline simdjson_result::simdjson_result(SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand::raw_json_string &&value) noexcept ++ : implementation_simdjson_result_base(std::forward(value)) {} ++simdjson_inline simdjson_result::simdjson_result(error_code error) noexcept ++ : implementation_simdjson_result_base(error) {} ++ ++simdjson_inline simdjson_result simdjson_result::raw() const noexcept { ++ if (error()) { return error(); } ++ return first.raw(); ++} ++simdjson_inline simdjson_warn_unused simdjson_result simdjson_result::unescape(SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand::json_iterator &iter) const noexcept { ++ if (error()) { return error(); } ++ return first.unescape(iter); ++} ++ ++} // namespace simdjson ++/* end file include/simdjson/generic/ondemand/raw_json_string-inl.h */ ++/* begin file include/simdjson/generic/ondemand/token_iterator-inl.h */ ++namespace simdjson { ++namespace SIMDJSON_BUILTIN_IMPLEMENTATION { ++namespace ondemand { ++ ++simdjson_inline token_iterator::token_iterator( ++ const uint8_t *_buf, ++ token_position position ++) noexcept : buf{_buf}, _position{position} ++{ ++} ++ ++simdjson_inline uint32_t token_iterator::current_offset() const noexcept { ++ return *(_position); ++} ++ ++ ++simdjson_inline const uint8_t *token_iterator::return_current_and_advance() noexcept { ++ return &buf[*(_position++)]; ++} ++ ++simdjson_inline const uint8_t *token_iterator::peek(token_position position) const noexcept { ++ return &buf[*position]; ++} ++simdjson_inline uint32_t token_iterator::peek_index(token_position position) const noexcept { ++ return *position; ++} ++simdjson_inline uint32_t token_iterator::peek_length(token_position position) const noexcept { ++ return *(position+1) - *position; ++} ++ ++simdjson_inline const uint8_t *token_iterator::peek(int32_t delta) const noexcept { ++ return &buf[*(_position+delta)]; ++} ++simdjson_inline uint32_t token_iterator::peek_index(int32_t delta) const noexcept { ++ return *(_position+delta); ++} ++simdjson_inline uint32_t token_iterator::peek_length(int32_t delta) const noexcept { ++ return *(_position+delta+1) - *(_position+delta); ++} ++ ++simdjson_inline token_position token_iterator::position() const noexcept { ++ return _position; ++} ++simdjson_inline void token_iterator::set_position(token_position target_position) noexcept { ++ _position = target_position; ++} ++ ++simdjson_inline bool token_iterator::operator==(const token_iterator &other) const noexcept { ++ return _position == other._position; ++} ++simdjson_inline bool token_iterator::operator!=(const token_iterator &other) const noexcept { ++ return _position != other._position; ++} ++simdjson_inline bool token_iterator::operator>(const token_iterator &other) const noexcept { ++ return _position > other._position; ++} ++simdjson_inline bool token_iterator::operator>=(const token_iterator &other) const noexcept { ++ return _position >= other._position; ++} ++simdjson_inline bool token_iterator::operator<(const token_iterator &other) const noexcept { ++ return _position < other._position; ++} ++simdjson_inline bool token_iterator::operator<=(const token_iterator &other) const noexcept { ++ return _position <= other._position; ++} ++ ++} // namespace ondemand ++} // namespace SIMDJSON_BUILTIN_IMPLEMENTATION ++} // namespace simdjson ++ ++namespace simdjson { ++ ++simdjson_inline simdjson_result::simdjson_result(SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand::token_iterator &&value) noexcept ++ : implementation_simdjson_result_base(std::forward(value)) {} ++simdjson_inline simdjson_result::simdjson_result(error_code error) noexcept ++ : implementation_simdjson_result_base(error) {} ++ ++} // namespace simdjson ++/* end file include/simdjson/generic/ondemand/token_iterator-inl.h */ ++/* begin file include/simdjson/generic/ondemand/json_iterator-inl.h */ ++namespace simdjson { ++namespace SIMDJSON_BUILTIN_IMPLEMENTATION { ++namespace ondemand { ++ ++simdjson_inline json_iterator::json_iterator(json_iterator &&other) noexcept ++ : token(std::forward(other.token)), ++ parser{other.parser}, ++ _string_buf_loc{other._string_buf_loc}, ++ error{other.error}, ++ _depth{other._depth}, ++ _root{other._root}, ++ _streaming{other._streaming} ++{ ++ other.parser = nullptr; ++} ++simdjson_inline json_iterator &json_iterator::operator=(json_iterator &&other) noexcept { ++ token = other.token; ++ parser = other.parser; ++ _string_buf_loc = other._string_buf_loc; ++ error = other.error; ++ _depth = other._depth; ++ _root = other._root; ++ _streaming = other._streaming; ++ other.parser = nullptr; ++ return *this; ++} ++ ++simdjson_inline json_iterator::json_iterator(const uint8_t *buf, ondemand::parser *_parser) noexcept ++ : token(buf, &_parser->implementation->structural_indexes[0]), ++ parser{_parser}, ++ _string_buf_loc{parser->string_buf.get()}, ++ _depth{1}, ++ _root{parser->implementation->structural_indexes.get()}, ++ _streaming{false} ++ ++{ ++ logger::log_headers(); ++#if SIMDJSON_CHECK_EOF ++ assert_more_tokens(); ++#endif ++} ++ ++inline void json_iterator::rewind() noexcept { ++ token.set_position( root_position() ); ++ logger::log_headers(); // We start again ++ _string_buf_loc = parser->string_buf.get(); ++ _depth = 1; ++} ++ ++inline bool json_iterator::balanced() const noexcept { ++ token_iterator ti(token); ++ int32_t count{0}; ++ ti.set_position( root_position() ); ++ while(ti.peek() <= peek_last()) { ++ switch (*ti.return_current_and_advance()) ++ { ++ case '[': case '{': ++ count++; ++ break; ++ case ']': case '}': ++ count--; ++ break; ++ default: ++ break; ++ } ++ } ++ return count == 0; ++} ++ ++ ++// GCC 7 warns when the first line of this function is inlined away into oblivion due to the caller ++// relating depth and parent_depth, which is a desired effect. The warning does not show up if the ++// skip_child() function is not marked inline). ++SIMDJSON_PUSH_DISABLE_WARNINGS ++SIMDJSON_DISABLE_STRICT_OVERFLOW_WARNING ++simdjson_warn_unused simdjson_inline error_code json_iterator::skip_child(depth_t parent_depth) noexcept { ++ if (depth() <= parent_depth) { return SUCCESS; } ++ switch (*return_current_and_advance()) { ++ // TODO consider whether matching braces is a requirement: if non-matching braces indicates ++ // *missing* braces, then future lookups are not in the object/arrays they think they are, ++ // violating the rule "validate enough structure that the user can be confident they are ++ // looking at the right values." ++ // PERF TODO we can eliminate the switch here with a lookup of how much to add to depth ++ ++ // For the first open array/object in a value, we've already incremented depth, so keep it the same ++ // We never stop at colon, but if we did, it wouldn't affect depth ++ case '[': case '{': case ':': ++ logger::log_start_value(*this, "skip"); ++ break; ++ // If there is a comma, we have just finished a value in an array/object, and need to get back in ++ case ',': ++ logger::log_value(*this, "skip"); ++ break; ++ // ] or } means we just finished a value and need to jump out of the array/object ++ case ']': case '}': ++ logger::log_end_value(*this, "skip"); ++ _depth--; ++ if (depth() <= parent_depth) { return SUCCESS; } ++#if SIMDJSON_CHECK_EOF ++ // If there are no more tokens, the parent is incomplete. ++ if (at_end()) { return report_error(INCOMPLETE_ARRAY_OR_OBJECT, "Missing [ or { at start"); } ++#endif // SIMDJSON_CHECK_EOF ++ break; ++ case '"': ++ if(*peek() == ':') { ++ // We are at a key!!! ++ // This might happen if you just started an object and you skip it immediately. ++ // Performance note: it would be nice to get rid of this check as it is somewhat ++ // expensive. ++ // https://github.com/simdjson/simdjson/issues/1742 ++ logger::log_value(*this, "key"); ++ return_current_and_advance(); // eat up the ':' ++ break; // important!!! ++ } ++ simdjson_fallthrough; ++ // Anything else must be a scalar value ++ default: ++ // For the first scalar, we will have incremented depth already, so we decrement it here. ++ logger::log_value(*this, "skip"); ++ _depth--; ++ if (depth() <= parent_depth) { return SUCCESS; } ++ break; ++ } ++ ++ // Now that we've considered the first value, we only increment/decrement for arrays/objects ++ while (position() < end_position()) { ++ switch (*return_current_and_advance()) { ++ case '[': case '{': ++ logger::log_start_value(*this, "skip"); ++ _depth++; ++ break; ++ // TODO consider whether matching braces is a requirement: if non-matching braces indicates ++ // *missing* braces, then future lookups are not in the object/arrays they think they are, ++ // violating the rule "validate enough structure that the user can be confident they are ++ // looking at the right values." ++ // PERF TODO we can eliminate the switch here with a lookup of how much to add to depth ++ case ']': case '}': ++ logger::log_end_value(*this, "skip"); ++ _depth--; ++ if (depth() <= parent_depth) { return SUCCESS; } ++ break; ++ default: ++ logger::log_value(*this, "skip", ""); ++ break; ++ } ++ } ++ ++ return report_error(TAPE_ERROR, "not enough close braces"); ++} ++ ++SIMDJSON_POP_DISABLE_WARNINGS ++ ++simdjson_inline bool json_iterator::at_root() const noexcept { ++ return position() == root_position(); ++} ++ ++simdjson_inline bool json_iterator::is_single_token() const noexcept { ++ return parser->implementation->n_structural_indexes == 1; ++} ++ ++simdjson_inline bool json_iterator::streaming() const noexcept { ++ return _streaming; ++} ++ ++simdjson_inline token_position json_iterator::root_position() const noexcept { ++ return _root; ++} ++ ++simdjson_inline void json_iterator::assert_at_document_depth() const noexcept { ++ SIMDJSON_ASSUME( _depth == 1 ); ++} ++ ++simdjson_inline void json_iterator::assert_at_root() const noexcept { ++ SIMDJSON_ASSUME( _depth == 1 ); ++#ifndef SIMDJSON_CLANG_VISUAL_STUDIO ++ // Under Visual Studio, the next SIMDJSON_ASSUME fails with: the argument ++ // has side effects that will be discarded. ++ SIMDJSON_ASSUME( token.position() == _root ); ++#endif ++} ++ ++simdjson_inline void json_iterator::assert_more_tokens(uint32_t required_tokens) const noexcept { ++ assert_valid_position(token._position + required_tokens - 1); ++} ++ ++simdjson_inline void json_iterator::assert_valid_position(token_position position) const noexcept { ++#ifndef SIMDJSON_CLANG_VISUAL_STUDIO ++ SIMDJSON_ASSUME( position >= &parser->implementation->structural_indexes[0] ); ++ SIMDJSON_ASSUME( position < &parser->implementation->structural_indexes[parser->implementation->n_structural_indexes] ); ++#endif ++} ++ ++simdjson_inline bool json_iterator::at_end() const noexcept { ++ return position() == end_position(); ++} ++simdjson_inline token_position json_iterator::end_position() const noexcept { ++ uint32_t n_structural_indexes{parser->implementation->n_structural_indexes}; ++ return &parser->implementation->structural_indexes[n_structural_indexes]; ++} ++ ++inline std::string json_iterator::to_string() const noexcept { ++ if( !is_alive() ) { return "dead json_iterator instance"; } ++ const char * current_structural = reinterpret_cast(token.peek()); ++ return std::string("json_iterator [ depth : ") + std::to_string(_depth) ++ + std::string(", structural : '") + std::string(current_structural,1) ++ + std::string("', offset : ") + std::to_string(token.current_offset()) ++ + std::string("', error : ") + error_message(error) ++ + std::string(" ]"); ++} ++ ++inline simdjson_result json_iterator::current_location() noexcept { ++ if (!is_alive()) { // Unrecoverable error ++ if (!at_root()) { ++ return reinterpret_cast(token.peek(-1)); ++ } else { ++ return reinterpret_cast(token.peek()); ++ } ++ } ++ if (at_end()) { ++ return OUT_OF_BOUNDS; ++ } ++ return reinterpret_cast(token.peek()); ++} ++ ++simdjson_inline bool json_iterator::is_alive() const noexcept { ++ return parser; ++} ++ ++simdjson_inline void json_iterator::abandon() noexcept { ++ parser = nullptr; ++ _depth = 0; ++} ++ ++simdjson_inline const uint8_t *json_iterator::return_current_and_advance() noexcept { ++#if SIMDJSON_CHECK_EOF ++ assert_more_tokens(); ++#endif // SIMDJSON_CHECK_EOF ++ return token.return_current_and_advance(); ++} ++ ++simdjson_inline const uint8_t *json_iterator::unsafe_pointer() const noexcept { ++ // deliberately done without safety guard: ++ return token.peek(0); ++} ++ ++simdjson_inline const uint8_t *json_iterator::peek(int32_t delta) const noexcept { ++#if SIMDJSON_CHECK_EOF ++ assert_more_tokens(delta+1); ++#endif // SIMDJSON_CHECK_EOF ++ return token.peek(delta); ++} ++ ++simdjson_inline uint32_t json_iterator::peek_length(int32_t delta) const noexcept { ++#if SIMDJSON_CHECK_EOF ++ assert_more_tokens(delta+1); ++#endif // #if SIMDJSON_CHECK_EOF ++ return token.peek_length(delta); ++} ++ ++simdjson_inline const uint8_t *json_iterator::peek(token_position position) const noexcept { ++ // todo: currently we require end-of-string buffering, but the following ++ // assert_valid_position should be turned on if/when we lift that condition. ++ // assert_valid_position(position); ++ // This is almost surely related to SIMDJSON_CHECK_EOF but given that SIMDJSON_CHECK_EOF ++ // is ON by default, we have no choice but to disable it for real with a comment. ++ return token.peek(position); ++} ++ ++simdjson_inline uint32_t json_iterator::peek_length(token_position position) const noexcept { ++#if SIMDJSON_CHECK_EOF ++ assert_valid_position(position); ++#endif // SIMDJSON_CHECK_EOF ++ return token.peek_length(position); ++} ++ ++simdjson_inline token_position json_iterator::last_position() const noexcept { ++ // The following line fails under some compilers... ++ // SIMDJSON_ASSUME(parser->implementation->n_structural_indexes > 0); ++ // since it has side-effects. ++ uint32_t n_structural_indexes{parser->implementation->n_structural_indexes}; ++ SIMDJSON_ASSUME(n_structural_indexes > 0); ++ return &parser->implementation->structural_indexes[n_structural_indexes - 1]; ++} ++simdjson_inline const uint8_t *json_iterator::peek_last() const noexcept { ++ return token.peek(last_position()); ++} ++ ++simdjson_inline void json_iterator::ascend_to(depth_t parent_depth) noexcept { ++ SIMDJSON_ASSUME(parent_depth >= 0 && parent_depth < INT32_MAX - 1); ++ SIMDJSON_ASSUME(_depth == parent_depth + 1); ++ _depth = parent_depth; ++} ++ ++simdjson_inline void json_iterator::descend_to(depth_t child_depth) noexcept { ++ SIMDJSON_ASSUME(child_depth >= 1 && child_depth < INT32_MAX); ++ SIMDJSON_ASSUME(_depth == child_depth - 1); ++ _depth = child_depth; ++} ++ ++simdjson_inline depth_t json_iterator::depth() const noexcept { ++ return _depth; ++} ++ ++simdjson_inline uint8_t *&json_iterator::string_buf_loc() noexcept { ++ return _string_buf_loc; ++} ++ ++simdjson_inline error_code json_iterator::report_error(error_code _error, const char *message) noexcept { ++ SIMDJSON_ASSUME(_error != SUCCESS && _error != UNINITIALIZED && _error != INCORRECT_TYPE && _error != NO_SUCH_FIELD); ++ logger::log_error(*this, message); ++ error = _error; ++ return error; ++} ++ ++simdjson_inline token_position json_iterator::position() const noexcept { ++ return token.position(); ++} ++ ++simdjson_inline simdjson_result json_iterator::unescape(raw_json_string in) noexcept { ++ return parser->unescape(in, _string_buf_loc); ++} ++ ++simdjson_inline void json_iterator::reenter_child(token_position position, depth_t child_depth) noexcept { ++ SIMDJSON_ASSUME(child_depth >= 1 && child_depth < INT32_MAX); ++ SIMDJSON_ASSUME(_depth == child_depth - 1); ++#if SIMDJSON_DEVELOPMENT_CHECKS ++#ifndef SIMDJSON_CLANG_VISUAL_STUDIO ++ SIMDJSON_ASSUME(size_t(child_depth) < parser->max_depth()); ++ SIMDJSON_ASSUME(position >= parser->start_positions[child_depth]); ++#endif ++#endif ++ token.set_position(position); ++ _depth = child_depth; ++} ++ ++#if SIMDJSON_DEVELOPMENT_CHECKS ++ ++simdjson_inline token_position json_iterator::start_position(depth_t depth) const noexcept { ++ SIMDJSON_ASSUME(size_t(depth) < parser->max_depth()); ++ return size_t(depth) < parser->max_depth() ? parser->start_positions[depth] : 0; ++} ++ ++simdjson_inline void json_iterator::set_start_position(depth_t depth, token_position position) noexcept { ++ SIMDJSON_ASSUME(size_t(depth) < parser->max_depth()); ++ if(size_t(depth) < parser->max_depth()) { parser->start_positions[depth] = position; } ++} ++ ++#endif ++ ++ ++simdjson_inline error_code json_iterator::optional_error(error_code _error, const char *message) noexcept { ++ SIMDJSON_ASSUME(_error == INCORRECT_TYPE || _error == NO_SUCH_FIELD); ++ logger::log_error(*this, message); ++ return _error; ++} ++ ++template ++simdjson_warn_unused simdjson_inline bool json_iterator::copy_to_buffer(const uint8_t *json, uint32_t max_len, uint8_t (&tmpbuf)[N]) noexcept { ++ // Let us guard against silly cases: ++ if((N < max_len) || (N == 0)) { return false; } ++ // Truncate whitespace to fit the buffer. ++ if (max_len > N-1) { ++ // if (jsoncharutils::is_not_structural_or_whitespace(json[N-1])) { return false; } ++ max_len = N-1; ++ } ++ ++ // Copy to the buffer. ++ std::memcpy(tmpbuf, json, max_len); ++ tmpbuf[max_len] = ' '; ++ return true; ++} ++ ++} // namespace ondemand ++} // namespace SIMDJSON_BUILTIN_IMPLEMENTATION ++} // namespace simdjson ++ ++namespace simdjson { ++ ++simdjson_inline simdjson_result::simdjson_result(SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand::json_iterator &&value) noexcept ++ : implementation_simdjson_result_base(std::forward(value)) {} ++simdjson_inline simdjson_result::simdjson_result(error_code error) noexcept ++ : implementation_simdjson_result_base(error) {} ++ ++} // namespace simdjson ++/* end file include/simdjson/generic/ondemand/json_iterator-inl.h */ ++/* begin file include/simdjson/generic/ondemand/value_iterator-inl.h */ ++namespace simdjson { ++namespace SIMDJSON_BUILTIN_IMPLEMENTATION { ++namespace ondemand { ++ ++simdjson_inline value_iterator::value_iterator( ++ json_iterator *json_iter, ++ depth_t depth, ++ token_position start_position ++) noexcept : _json_iter{json_iter}, _depth{depth}, _start_position{start_position} ++{ ++} ++ ++simdjson_warn_unused simdjson_inline simdjson_result value_iterator::start_object() noexcept { ++ SIMDJSON_TRY( start_container('{', "Not an object", "object") ); ++ return started_object(); ++} ++ ++simdjson_warn_unused simdjson_inline simdjson_result value_iterator::start_root_object() noexcept { ++ SIMDJSON_TRY( start_container('{', "Not an object", "object") ); ++ return started_root_object(); ++} ++ ++simdjson_warn_unused simdjson_inline simdjson_result value_iterator::started_object() noexcept { ++ assert_at_container_start(); ++#if SIMDJSON_DEVELOPMENT_CHECKS ++ _json_iter->set_start_position(_depth, start_position()); ++#endif ++ if (*_json_iter->peek() == '}') { ++ logger::log_value(*_json_iter, "empty object"); ++ _json_iter->return_current_and_advance(); ++ end_container(); ++ return false; ++ } ++ return true; ++} ++ ++simdjson_warn_unused simdjson_inline simdjson_result value_iterator::started_root_object() noexcept { ++ // When in streaming mode, we cannot expect peek_last() to be the last structural element of the ++ // current document. It only works in the normal mode where we have indexed a single document. ++ // Note that adding a check for 'streaming' is not expensive since we only have at most ++ // one root element. ++ if ( ! _json_iter->streaming() ) { ++ if (*_json_iter->peek_last() != '}') { ++ _json_iter->abandon(); ++ return report_error(INCOMPLETE_ARRAY_OR_OBJECT, "missing } at end"); ++ } ++ // If the last character is } *and* the first gibberish character is also '}' ++ // then on-demand could accidentally go over. So we need additional checks. ++ // https://github.com/simdjson/simdjson/issues/1834 ++ // Checking that the document is balanced requires a full scan which is potentially ++ // expensive, but it only happens in edge cases where the first padding character is ++ // a closing bracket. ++ if ((*_json_iter->peek(_json_iter->end_position()) == '}') && (!_json_iter->balanced())) { ++ _json_iter->abandon(); ++ // The exact error would require more work. It will typically be an unclosed object. ++ return report_error(INCOMPLETE_ARRAY_OR_OBJECT, "the document is unbalanced"); ++ } ++ } ++ return started_object(); ++} ++ ++simdjson_warn_unused simdjson_inline error_code value_iterator::end_container() noexcept { ++#if SIMDJSON_CHECK_EOF ++ if (depth() > 1 && at_end()) { return report_error(INCOMPLETE_ARRAY_OR_OBJECT, "missing parent ] or }"); } ++ // if (depth() <= 1 && !at_end()) { return report_error(INCOMPLETE_ARRAY_OR_OBJECT, "missing [ or { at start"); } ++#endif // SIMDJSON_CHECK_EOF ++ _json_iter->ascend_to(depth()-1); ++ return SUCCESS; ++} ++ ++simdjson_warn_unused simdjson_inline simdjson_result value_iterator::has_next_field() noexcept { ++ assert_at_next(); ++ ++ // It's illegal to call this unless there are more tokens: anything that ends in } or ] is ++ // obligated to verify there are more tokens if they are not the top level. ++ switch (*_json_iter->return_current_and_advance()) { ++ case '}': ++ logger::log_end_value(*_json_iter, "object"); ++ SIMDJSON_TRY( end_container() ); ++ return false; ++ case ',': ++ return true; ++ default: ++ return report_error(TAPE_ERROR, "Missing comma between object fields"); ++ } ++} ++ ++simdjson_warn_unused simdjson_inline simdjson_result value_iterator::find_field_raw(const std::string_view key) noexcept { ++ error_code error; ++ bool has_value; ++ // ++ // Initially, the object can be in one of a few different places: ++ // ++ // 1. The start of the object, at the first field: ++ // ++ // ``` ++ // { "a": [ 1, 2 ], "b": [ 3, 4 ] } ++ // ^ (depth 2, index 1) ++ // ``` ++ if (at_first_field()) { ++ has_value = true; ++ ++ // ++ // 2. When a previous search did not yield a value or the object is empty: ++ // ++ // ``` ++ // { "a": [ 1, 2 ], "b": [ 3, 4 ] } ++ // ^ (depth 0) ++ // { } ++ // ^ (depth 0, index 2) ++ // ``` ++ // ++ } else if (!is_open()) { ++#if SIMDJSON_DEVELOPMENT_CHECKS ++ // If we're past the end of the object, we're being iterated out of order. ++ // Note: this isn't perfect detection. It's possible the user is inside some other object; if so, ++ // this object iterator will blithely scan that object for fields. ++ if (_json_iter->depth() < depth() - 1) { return OUT_OF_ORDER_ITERATION; } ++#endif ++ return false; ++ ++ // 3. When a previous search found a field or an iterator yielded a value: ++ // ++ // ``` ++ // // When a field was not fully consumed (or not even touched at all) ++ // { "a": [ 1, 2 ], "b": [ 3, 4 ] } ++ // ^ (depth 2) ++ // // When a field was fully consumed ++ // { "a": [ 1, 2 ], "b": [ 3, 4 ] } ++ // ^ (depth 1) ++ // // When the last field was fully consumed ++ // { "a": [ 1, 2 ], "b": [ 3, 4 ] } ++ // ^ (depth 1) ++ // ``` ++ // ++ } else { ++ if ((error = skip_child() )) { abandon(); return error; } ++ if ((error = has_next_field().get(has_value) )) { abandon(); return error; } ++#if SIMDJSON_DEVELOPMENT_CHECKS ++ if (_json_iter->start_position(_depth) != start_position()) { return OUT_OF_ORDER_ITERATION; } ++#endif ++ } ++ while (has_value) { ++ // Get the key and colon, stopping at the value. ++ raw_json_string actual_key; ++ // size_t max_key_length = _json_iter->peek_length() - 2; // -2 for the two quotes ++ // Note: _json_iter->peek_length() - 2 might overflow if _json_iter->peek_length() < 2. ++ // field_key() advances the pointer and checks that '"' is found (corresponding to a key). ++ // The depth is left unchanged by field_key(). ++ if ((error = field_key().get(actual_key) )) { abandon(); return error; }; ++ // field_value() will advance and check that we find a ':' separating the ++ // key and the value. It will also increment the depth by one. ++ if ((error = field_value() )) { abandon(); return error; } ++ // If it matches, stop and return ++ // We could do it this way if we wanted to allow arbitrary ++ // key content (including escaped quotes). ++ //if (actual_key.unsafe_is_equal(max_key_length, key)) { ++ // Instead we do the following which may trigger buffer overruns if the ++ // user provides an adversarial key (containing a well placed unescaped quote ++ // character and being longer than the number of bytes remaining in the JSON ++ // input). ++ if (actual_key.unsafe_is_equal(key)) { ++ logger::log_event(*this, "match", key, -2); ++ // If we return here, then we return while pointing at the ':' that we just checked. ++ return true; ++ } ++ ++ // No match: skip the value and see if , or } is next ++ logger::log_event(*this, "no match", key, -2); ++ // The call to skip_child is meant to skip over the value corresponding to the key. ++ // After skip_child(), we are right before the next comma (',') or the final brace ('}'). ++ SIMDJSON_TRY( skip_child() ); // Skip the value entirely ++ // The has_next_field() advances the pointer and check that either ',' or '}' is found. ++ // It returns true if ',' is found, false otherwise. If anything other than ',' or '}' is found, ++ // then we are in error and we abort. ++ if ((error = has_next_field().get(has_value) )) { abandon(); return error; } ++ } ++ ++ // If the loop ended, we're out of fields to look at. ++ return false; ++} ++ ++SIMDJSON_PUSH_DISABLE_WARNINGS ++SIMDJSON_DISABLE_STRICT_OVERFLOW_WARNING ++simdjson_warn_unused simdjson_inline simdjson_result value_iterator::find_field_unordered_raw(const std::string_view key) noexcept { ++ /** ++ * When find_field_unordered_raw is called, we can either be pointing at the ++ * first key, pointing outside (at the closing brace) or if a key was matched ++ * we can be either pointing right afterthe ':' right before the value (that we need skip), ++ * or we may have consumed the value and we might be at a comma or at the ++ * final brace (ready for a call to has_next_field()). ++ */ ++ error_code error; ++ bool has_value; ++ ++ // First, we scan from that point to the end. ++ // If we don't find a match, we may loop back around, and scan from the beginning to that point. ++ token_position search_start = _json_iter->position(); ++ ++ // We want to know whether we need to go back to the beginning. ++ bool at_first = at_first_field(); ++ /////////////// ++ // Initially, the object can be in one of a few different places: ++ // ++ // 1. At the first key: ++ // ++ // ``` ++ // { "a": [ 1, 2 ], "b": [ 3, 4 ] } ++ // ^ (depth 2, index 1) ++ // ``` ++ // ++ if (at_first) { ++ has_value = true; ++ ++ // 2. When a previous search did not yield a value or the object is empty: ++ // ++ // ``` ++ // { "a": [ 1, 2 ], "b": [ 3, 4 ] } ++ // ^ (depth 0) ++ // { } ++ // ^ (depth 0, index 2) ++ // ``` ++ // ++ } else if (!is_open()) { ++ ++#if SIMDJSON_DEVELOPMENT_CHECKS ++ // If we're past the end of the object, we're being iterated out of order. ++ // Note: this isn't perfect detection. It's possible the user is inside some other object; if so, ++ // this object iterator will blithely scan that object for fields. ++ if (_json_iter->depth() < depth() - 1) { return OUT_OF_ORDER_ITERATION; } ++#endif ++ SIMDJSON_TRY(reset_object().get(has_value)); ++ at_first = true; ++ // 3. When a previous search found a field or an iterator yielded a value: ++ // ++ // ``` ++ // // When a field was not fully consumed (or not even touched at all) ++ // { "a": [ 1, 2 ], "b": [ 3, 4 ] } ++ // ^ (depth 2) ++ // // When a field was fully consumed ++ // { "a": [ 1, 2 ], "b": [ 3, 4 ] } ++ // ^ (depth 1) ++ // // When the last field was fully consumed ++ // { "a": [ 1, 2 ], "b": [ 3, 4 ] } ++ // ^ (depth 1) ++ // ``` ++ // ++ } else { ++ // If someone queried a key but they not did access the value, then we are left pointing ++ // at the ':' and we need to move forward through the value... If the value was ++ // processed then skip_child() does not move the iterator (but may adjust the depth). ++ if ((error = skip_child() )) { abandon(); return error; } ++ search_start = _json_iter->position(); ++ if ((error = has_next_field().get(has_value) )) { abandon(); return error; } ++#if SIMDJSON_DEVELOPMENT_CHECKS ++ if (_json_iter->start_position(_depth) != start_position()) { return OUT_OF_ORDER_ITERATION; } ++#endif ++ } ++ ++ // After initial processing, we will be in one of two states: ++ // ++ // ``` ++ // // At the beginning of a field ++ // { "a": [ 1, 2 ], "b": [ 3, 4 ] } ++ // ^ (depth 1) ++ // { "a": [ 1, 2 ], "b": [ 3, 4 ] } ++ // ^ (depth 1) ++ // // At the end of the object ++ // { "a": [ 1, 2 ], "b": [ 3, 4 ] } ++ // ^ (depth 0) ++ // ``` ++ // ++ // Next, we find a match starting from the current position. ++ while (has_value) { ++ SIMDJSON_ASSUME( _json_iter->_depth == _depth ); // We must be at the start of a field ++ ++ // Get the key and colon, stopping at the value. ++ raw_json_string actual_key; ++ // size_t max_key_length = _json_iter->peek_length() - 2; // -2 for the two quotes ++ // Note: _json_iter->peek_length() - 2 might overflow if _json_iter->peek_length() < 2. ++ // field_key() advances the pointer and checks that '"' is found (corresponding to a key). ++ // The depth is left unchanged by field_key(). ++ if ((error = field_key().get(actual_key) )) { abandon(); return error; }; ++ // field_value() will advance and check that we find a ':' separating the ++ // key and the value. It will also increment the depth by one. ++ if ((error = field_value() )) { abandon(); return error; } ++ ++ // If it matches, stop and return ++ // We could do it this way if we wanted to allow arbitrary ++ // key content (including escaped quotes). ++ // if (actual_key.unsafe_is_equal(max_key_length, key)) { ++ // Instead we do the following which may trigger buffer overruns if the ++ // user provides an adversarial key (containing a well placed unescaped quote ++ // character and being longer than the number of bytes remaining in the JSON ++ // input). ++ if (actual_key.unsafe_is_equal(key)) { ++ logger::log_event(*this, "match", key, -2); ++ // If we return here, then we return while pointing at the ':' that we just checked. ++ return true; ++ } ++ ++ // No match: skip the value and see if , or } is next ++ logger::log_event(*this, "no match", key, -2); ++ // The call to skip_child is meant to skip over the value corresponding to the key. ++ // After skip_child(), we are right before the next comma (',') or the final brace ('}'). ++ SIMDJSON_TRY( skip_child() ); ++ // The has_next_field() advances the pointer and check that either ',' or '}' is found. ++ // It returns true if ',' is found, false otherwise. If anything other than ',' or '}' is found, ++ // then we are in error and we abort. ++ if ((error = has_next_field().get(has_value) )) { abandon(); return error; } ++ } ++ // Performance note: it maybe wasteful to rewind to the beginning when there might be ++ // no other query following. Indeed, it would require reskipping the whole object. ++ // Instead, you can just stay where you are. If there is a new query, there is always time ++ // to rewind. ++ if(at_first) { return false; } ++ ++ // If we reach the end without finding a match, search the rest of the fields starting at the ++ // beginning of the object. ++ // (We have already run through the object before, so we've already validated its structure. We ++ // don't check errors in this bit.) ++ SIMDJSON_TRY(reset_object().get(has_value)); ++ while (true) { ++ SIMDJSON_ASSUME(has_value); // we should reach search_start before ever reaching the end of the object ++ SIMDJSON_ASSUME( _json_iter->_depth == _depth ); // We must be at the start of a field ++ ++ // Get the key and colon, stopping at the value. ++ raw_json_string actual_key; ++ // size_t max_key_length = _json_iter->peek_length() - 2; // -2 for the two quotes ++ // Note: _json_iter->peek_length() - 2 might overflow if _json_iter->peek_length() < 2. ++ // field_key() advances the pointer and checks that '"' is found (corresponding to a key). ++ // The depth is left unchanged by field_key(). ++ error = field_key().get(actual_key); SIMDJSON_ASSUME(!error); ++ // field_value() will advance and check that we find a ':' separating the ++ // key and the value. It will also increment the depth by one. ++ error = field_value(); SIMDJSON_ASSUME(!error); ++ ++ // If it matches, stop and return ++ // We could do it this way if we wanted to allow arbitrary ++ // key content (including escaped quotes). ++ // if (actual_key.unsafe_is_equal(max_key_length, key)) { ++ // Instead we do the following which may trigger buffer overruns if the ++ // user provides an adversarial key (containing a well placed unescaped quote ++ // character and being longer than the number of bytes remaining in the JSON ++ // input). ++ if (actual_key.unsafe_is_equal(key)) { ++ logger::log_event(*this, "match", key, -2); ++ // If we return here, then we return while pointing at the ':' that we just checked. ++ return true; ++ } ++ ++ // No match: skip the value and see if , or } is next ++ logger::log_event(*this, "no match", key, -2); ++ // The call to skip_child is meant to skip over the value corresponding to the key. ++ // After skip_child(), we are right before the next comma (',') or the final brace ('}'). ++ SIMDJSON_TRY( skip_child() ); ++ // If we reached the end of the key-value pair we started from, then we know ++ // that the key is not there so we return false. We are either right before ++ // the next comma or the final brace. ++ if(_json_iter->position() == search_start) { return false; } ++ // The has_next_field() advances the pointer and check that either ',' or '}' is found. ++ // It returns true if ',' is found, false otherwise. If anything other than ',' or '}' is found, ++ // then we are in error and we abort. ++ error = has_next_field().get(has_value); SIMDJSON_ASSUME(!error); ++ // If we make the mistake of exiting here, then we could be left pointing at a key ++ // in the middle of an object. That's not an allowable state. ++ } ++ // If the loop ended, we're out of fields to look at. The program should ++ // never reach this point. ++ return false; ++} ++SIMDJSON_POP_DISABLE_WARNINGS ++ ++simdjson_warn_unused simdjson_inline simdjson_result value_iterator::field_key() noexcept { ++ assert_at_next(); ++ ++ const uint8_t *key = _json_iter->return_current_and_advance(); ++ if (*(key++) != '"') { return report_error(TAPE_ERROR, "Object key is not a string"); } ++ return raw_json_string(key); ++} ++ ++simdjson_warn_unused simdjson_inline error_code value_iterator::field_value() noexcept { ++ assert_at_next(); ++ ++ if (*_json_iter->return_current_and_advance() != ':') { return report_error(TAPE_ERROR, "Missing colon in object field"); } ++ _json_iter->descend_to(depth()+1); ++ return SUCCESS; ++} ++ ++simdjson_warn_unused simdjson_inline simdjson_result value_iterator::start_array() noexcept { ++ SIMDJSON_TRY( start_container('[', "Not an array", "array") ); ++ return started_array(); ++} ++ ++simdjson_warn_unused simdjson_inline simdjson_result value_iterator::start_root_array() noexcept { ++ SIMDJSON_TRY( start_container('[', "Not an array", "array") ); ++ return started_root_array(); ++} ++ ++inline std::string value_iterator::to_string() const noexcept { ++ auto answer = std::string("value_iterator [ depth : ") + std::to_string(_depth) + std::string(", "); ++ if(_json_iter != nullptr) { answer += _json_iter->to_string(); } ++ answer += std::string(" ]"); ++ return answer; ++} ++ ++simdjson_warn_unused simdjson_inline simdjson_result value_iterator::started_array() noexcept { ++ assert_at_container_start(); ++ if (*_json_iter->peek() == ']') { ++ logger::log_value(*_json_iter, "empty array"); ++ _json_iter->return_current_and_advance(); ++ SIMDJSON_TRY( end_container() ); ++ return false; ++ } ++ _json_iter->descend_to(depth()+1); ++#if SIMDJSON_DEVELOPMENT_CHECKS ++ _json_iter->set_start_position(_depth, start_position()); ++#endif ++ return true; ++} ++ ++simdjson_warn_unused simdjson_inline simdjson_result value_iterator::started_root_array() noexcept { ++ // When in streaming mode, we cannot expect peek_last() to be the last structural element of the ++ // current document. It only works in the normal mode where we have indexed a single document. ++ // Note that adding a check for 'streaming' is not expensive since we only have at most ++ // one root element. ++ if ( ! _json_iter->streaming() ) { ++ if (*_json_iter->peek_last() != ']') { ++ _json_iter->abandon(); ++ return report_error(INCOMPLETE_ARRAY_OR_OBJECT, "missing ] at end"); ++ } ++ // If the last character is ] *and* the first gibberish character is also ']' ++ // then on-demand could accidentally go over. So we need additional checks. ++ // https://github.com/simdjson/simdjson/issues/1834 ++ // Checking that the document is balanced requires a full scan which is potentially ++ // expensive, but it only happens in edge cases where the first padding character is ++ // a closing bracket. ++ if ((*_json_iter->peek(_json_iter->end_position()) == ']') && (!_json_iter->balanced())) { ++ _json_iter->abandon(); ++ // The exact error would require more work. It will typically be an unclosed array. ++ return report_error(INCOMPLETE_ARRAY_OR_OBJECT, "the document is unbalanced"); ++ } ++ } ++ return started_array(); ++} ++ ++simdjson_warn_unused simdjson_inline simdjson_result value_iterator::has_next_element() noexcept { ++ assert_at_next(); ++ ++ logger::log_event(*this, "has_next_element"); ++ switch (*_json_iter->return_current_and_advance()) { ++ case ']': ++ logger::log_end_value(*_json_iter, "array"); ++ SIMDJSON_TRY( end_container() ); ++ return false; ++ case ',': ++ _json_iter->descend_to(depth()+1); ++ return true; ++ default: ++ return report_error(TAPE_ERROR, "Missing comma between array elements"); ++ } ++} ++ ++simdjson_warn_unused simdjson_inline simdjson_result value_iterator::parse_bool(const uint8_t *json) const noexcept { ++ auto not_true = atomparsing::str4ncmp(json, "true"); ++ auto not_false = atomparsing::str4ncmp(json, "fals") | (json[4] ^ 'e'); ++ bool error = (not_true && not_false) || jsoncharutils::is_not_structural_or_whitespace(json[not_true ? 5 : 4]); ++ if (error) { return incorrect_type_error("Not a boolean"); } ++ return simdjson_result(!not_true); ++} ++simdjson_warn_unused simdjson_inline simdjson_result value_iterator::parse_null(const uint8_t *json) const noexcept { ++ bool is_null_string = !atomparsing::str4ncmp(json, "null") && jsoncharutils::is_structural_or_whitespace(json[4]); ++ // if we start with 'n', we must be a null ++ if(!is_null_string && json[0]=='n') { return incorrect_type_error("Not a null but starts with n"); } ++ return is_null_string; ++} ++ ++simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get_string() noexcept { ++ return get_raw_json_string().unescape(json_iter()); ++} ++simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get_raw_json_string() noexcept { ++ auto json = peek_scalar("string"); ++ if (*json != '"') { return incorrect_type_error("Not a string"); } ++ advance_scalar("string"); ++ return raw_json_string(json+1); ++} ++simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get_uint64() noexcept { ++ auto result = numberparsing::parse_unsigned(peek_non_root_scalar("uint64")); ++ if(result.error() == SUCCESS) { advance_non_root_scalar("uint64"); } ++ return result; ++} ++simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get_uint64_in_string() noexcept { ++ auto result = numberparsing::parse_unsigned_in_string(peek_non_root_scalar("uint64")); ++ if(result.error() == SUCCESS) { advance_non_root_scalar("uint64"); } ++ return result; ++} ++simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get_int64() noexcept { ++ auto result = numberparsing::parse_integer(peek_non_root_scalar("int64")); ++ if(result.error() == SUCCESS) { advance_non_root_scalar("int64"); } ++ return result; ++} ++simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get_int64_in_string() noexcept { ++ auto result = numberparsing::parse_integer_in_string(peek_non_root_scalar("int64")); ++ if(result.error() == SUCCESS) { advance_non_root_scalar("int64"); } ++ return result; ++} ++simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get_double() noexcept { ++ auto result = numberparsing::parse_double(peek_non_root_scalar("double")); ++ if(result.error() == SUCCESS) { advance_non_root_scalar("double"); } ++ return result; ++} ++simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get_double_in_string() noexcept { ++ auto result = numberparsing::parse_double_in_string(peek_non_root_scalar("double")); ++ if(result.error() == SUCCESS) { advance_non_root_scalar("double"); } ++ return result; ++} ++simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get_bool() noexcept { ++ auto result = parse_bool(peek_non_root_scalar("bool")); ++ if(result.error() == SUCCESS) { advance_non_root_scalar("bool"); } ++ return result; ++} ++simdjson_inline simdjson_result value_iterator::is_null() noexcept { ++ bool is_null_value; ++ SIMDJSON_TRY(parse_null(peek_non_root_scalar("null")).get(is_null_value)); ++ if(is_null_value) { advance_non_root_scalar("null"); } ++ return is_null_value; ++} ++simdjson_inline bool value_iterator::is_negative() noexcept { ++ return numberparsing::is_negative(peek_non_root_scalar("numbersign")); ++} ++simdjson_inline bool value_iterator::is_root_negative() noexcept { ++ return numberparsing::is_negative(peek_root_scalar("numbersign")); ++} ++simdjson_inline simdjson_result value_iterator::is_integer() noexcept { ++ return numberparsing::is_integer(peek_non_root_scalar("integer")); ++} ++simdjson_inline simdjson_result value_iterator::get_number_type() noexcept { ++ return numberparsing::get_number_type(peek_non_root_scalar("integer")); ++} ++simdjson_inline simdjson_result value_iterator::get_number() noexcept { ++ number num; ++ error_code error = numberparsing::parse_number(peek_non_root_scalar("number"), num); ++ if(error) { return error; } ++ return num; ++} ++ ++simdjson_inline simdjson_result value_iterator::is_root_integer(bool check_trailing) noexcept { ++ auto max_len = peek_start_length(); ++ auto json = peek_root_scalar("is_root_integer"); ++ uint8_t tmpbuf[20+1]; // <20 digits> is the longest possible unsigned integer ++ if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf)) { ++ return false; // if there are more than 20 characters, it cannot be represented as an integer. ++ } ++ auto answer = numberparsing::is_integer(tmpbuf); ++ // If the parsing was a success, we must still check that it is ++ // a single scalar. Note that we parse first because of cases like '[]' where ++ // getting TRAILING_CONTENT is wrong. ++ if(check_trailing && (answer.error() == SUCCESS) && (!_json_iter->is_single_token())) { return TRAILING_CONTENT; } ++ return answer; ++} ++ ++simdjson_inline simdjson_result value_iterator::get_root_number_type(bool check_trailing) noexcept { ++ auto max_len = peek_start_length(); ++ auto json = peek_root_scalar("number"); ++ // Per https://www.exploringbinary.com/maximum-number-of-decimal-digits-in-binary-floating-point-numbers/, ++ // 1074 is the maximum number of significant fractional digits. Add 8 more digits for the biggest ++ // number: -0.e-308. ++ uint8_t tmpbuf[1074+8+1]; ++ if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf)) { ++ logger::log_error(*_json_iter, start_position(), depth(), "Root number more than 1082 characters"); ++ return NUMBER_ERROR; ++ } ++ auto answer = numberparsing::get_number_type(tmpbuf); ++ if (check_trailing && (answer.error() == SUCCESS) && !_json_iter->is_single_token()) { return TRAILING_CONTENT; } ++ return answer; ++} ++simdjson_inline simdjson_result value_iterator::get_root_number(bool check_trailing) noexcept { ++ auto max_len = peek_start_length(); ++ auto json = peek_root_scalar("number"); ++ // Per https://www.exploringbinary.com/maximum-number-of-decimal-digits-in-binary-floating-point-numbers/, ++ // 1074 is the maximum number of significant fractional digits. Add 8 more digits for the biggest ++ // number: -0.e-308. ++ uint8_t tmpbuf[1074+8+1]; ++ if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf)) { ++ logger::log_error(*_json_iter, start_position(), depth(), "Root number more than 1082 characters"); ++ return NUMBER_ERROR; ++ } ++ number num; ++ error_code error = numberparsing::parse_number(tmpbuf, num); ++ if(error) { return error; } ++ if (check_trailing && !_json_iter->is_single_token()) { return TRAILING_CONTENT; } ++ advance_root_scalar("number"); ++ return num; ++} ++simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get_root_string(bool check_trailing) noexcept { ++ return get_root_raw_json_string(check_trailing).unescape(json_iter()); ++} ++simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get_root_raw_json_string(bool check_trailing) noexcept { ++ auto json = peek_scalar("string"); ++ if (*json != '"') { return incorrect_type_error("Not a string"); } ++ if (check_trailing && !_json_iter->is_single_token()) { return TRAILING_CONTENT; } ++ advance_scalar("string"); ++ return raw_json_string(json+1); ++} ++simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get_root_uint64(bool check_trailing) noexcept { ++ auto max_len = peek_start_length(); ++ auto json = peek_root_scalar("uint64"); ++ uint8_t tmpbuf[20+1]; // <20 digits> is the longest possible unsigned integer ++ if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf)) { ++ logger::log_error(*_json_iter, start_position(), depth(), "Root number more than 20 characters"); ++ return NUMBER_ERROR; ++ } ++ auto result = numberparsing::parse_unsigned(tmpbuf); ++ if(result.error() == SUCCESS) { ++ if (check_trailing && !_json_iter->is_single_token()) { return TRAILING_CONTENT; } ++ advance_root_scalar("uint64"); ++ } ++ return result; ++} ++simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get_root_uint64_in_string(bool check_trailing) noexcept { ++ auto max_len = peek_start_length(); ++ auto json = peek_root_scalar("uint64"); ++ uint8_t tmpbuf[20+1]; // <20 digits> is the longest possible unsigned integer ++ if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf)) { ++ logger::log_error(*_json_iter, start_position(), depth(), "Root number more than 20 characters"); ++ return NUMBER_ERROR; ++ } ++ auto result = numberparsing::parse_unsigned_in_string(tmpbuf); ++ if(result.error() == SUCCESS) { ++ if (check_trailing && !_json_iter->is_single_token()) { return TRAILING_CONTENT; } ++ advance_root_scalar("uint64"); ++ } ++ return result; ++} ++simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get_root_int64(bool check_trailing) noexcept { ++ auto max_len = peek_start_length(); ++ auto json = peek_root_scalar("int64"); ++ uint8_t tmpbuf[20+1]; // -<19 digits> is the longest possible integer ++ if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf)) { ++ logger::log_error(*_json_iter, start_position(), depth(), "Root number more than 20 characters"); ++ return NUMBER_ERROR; ++ } ++ ++ auto result = numberparsing::parse_integer(tmpbuf); ++ if(result.error() == SUCCESS) { ++ if (check_trailing && !_json_iter->is_single_token()) { return TRAILING_CONTENT; } ++ advance_root_scalar("int64"); ++ } ++ return result; ++} ++simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get_root_int64_in_string(bool check_trailing) noexcept { ++ auto max_len = peek_start_length(); ++ auto json = peek_root_scalar("int64"); ++ uint8_t tmpbuf[20+1]; // -<19 digits> is the longest possible integer ++ if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf)) { ++ logger::log_error(*_json_iter, start_position(), depth(), "Root number more than 20 characters"); ++ return NUMBER_ERROR; ++ } ++ ++ auto result = numberparsing::parse_integer_in_string(tmpbuf); ++ if(result.error() == SUCCESS) { ++ if (check_trailing && !_json_iter->is_single_token()) { return TRAILING_CONTENT; } ++ advance_root_scalar("int64"); ++ } ++ return result; ++} ++simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get_root_double(bool check_trailing) noexcept { ++ auto max_len = peek_start_length(); ++ auto json = peek_root_scalar("double"); ++ // Per https://www.exploringbinary.com/maximum-number-of-decimal-digits-in-binary-floating-point-numbers/, ++ // 1074 is the maximum number of significant fractional digits. Add 8 more digits for the biggest ++ // number: -0.e-308. ++ uint8_t tmpbuf[1074+8+1]; ++ if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf)) { ++ logger::log_error(*_json_iter, start_position(), depth(), "Root number more than 1082 characters"); ++ return NUMBER_ERROR; ++ } ++ auto result = numberparsing::parse_double(tmpbuf); ++ if(result.error() == SUCCESS) { ++ if (check_trailing && !_json_iter->is_single_token()) { return TRAILING_CONTENT; } ++ advance_root_scalar("double"); ++ } ++ return result; ++} ++ ++simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get_root_double_in_string(bool check_trailing) noexcept { ++ auto max_len = peek_start_length(); ++ auto json = peek_root_scalar("double"); ++ // Per https://www.exploringbinary.com/maximum-number-of-decimal-digits-in-binary-floating-point-numbers/, ++ // 1074 is the maximum number of significant fractional digits. Add 8 more digits for the biggest ++ // number: -0.e-308. ++ uint8_t tmpbuf[1074+8+1]; ++ if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf)) { ++ logger::log_error(*_json_iter, start_position(), depth(), "Root number more than 1082 characters"); ++ return NUMBER_ERROR; ++ } ++ auto result = numberparsing::parse_double_in_string(tmpbuf); ++ if(result.error() == SUCCESS) { ++ if (check_trailing && !_json_iter->is_single_token()) { return TRAILING_CONTENT; } ++ advance_root_scalar("double"); ++ } ++ return result; ++} ++simdjson_warn_unused simdjson_inline simdjson_result value_iterator::get_root_bool(bool check_trailing) noexcept { ++ auto max_len = peek_start_length(); ++ auto json = peek_root_scalar("bool"); ++ uint8_t tmpbuf[5+1]; ++ if (!_json_iter->copy_to_buffer(json, max_len, tmpbuf)) { return incorrect_type_error("Not a boolean"); } ++ auto result = parse_bool(tmpbuf); ++ if(result.error() == SUCCESS) { ++ if (check_trailing && !_json_iter->is_single_token()) { return TRAILING_CONTENT; } ++ advance_root_scalar("bool"); ++ } ++ return result; ++} ++simdjson_inline simdjson_result value_iterator::is_root_null(bool check_trailing) noexcept { ++ auto max_len = peek_start_length(); ++ auto json = peek_root_scalar("null"); ++ bool result = (max_len >= 4 && !atomparsing::str4ncmp(json, "null") && ++ (max_len == 4 || jsoncharutils::is_structural_or_whitespace(json[4]))); ++ if(result) { // we have something that looks like a null. ++ if (check_trailing && !_json_iter->is_single_token()) { return TRAILING_CONTENT; } ++ advance_root_scalar("null"); ++ } ++ return result; ++} ++ ++simdjson_warn_unused simdjson_inline error_code value_iterator::skip_child() noexcept { ++ SIMDJSON_ASSUME( _json_iter->token._position > _start_position ); ++ SIMDJSON_ASSUME( _json_iter->_depth >= _depth ); ++ ++ return _json_iter->skip_child(depth()); ++} ++ ++simdjson_inline value_iterator value_iterator::child() const noexcept { ++ assert_at_child(); ++ return { _json_iter, depth()+1, _json_iter->token.position() }; ++} ++ ++// GCC 7 warns when the first line of this function is inlined away into oblivion due to the caller ++// relating depth and iterator depth, which is a desired effect. It does not happen if is_open is ++// marked non-inline. ++SIMDJSON_PUSH_DISABLE_WARNINGS ++SIMDJSON_DISABLE_STRICT_OVERFLOW_WARNING ++simdjson_inline bool value_iterator::is_open() const noexcept { ++ return _json_iter->depth() >= depth(); ++} ++SIMDJSON_POP_DISABLE_WARNINGS ++ ++simdjson_inline bool value_iterator::at_end() const noexcept { ++ return _json_iter->at_end(); ++} ++ ++simdjson_inline bool value_iterator::at_start() const noexcept { ++ return _json_iter->token.position() == start_position(); ++} ++ ++simdjson_inline bool value_iterator::at_first_field() const noexcept { ++ SIMDJSON_ASSUME( _json_iter->token._position > _start_position ); ++ return _json_iter->token.position() == start_position() + 1; ++} ++ ++simdjson_inline void value_iterator::abandon() noexcept { ++ _json_iter->abandon(); ++} ++ ++simdjson_warn_unused simdjson_inline depth_t value_iterator::depth() const noexcept { ++ return _depth; ++} ++simdjson_warn_unused simdjson_inline error_code value_iterator::error() const noexcept { ++ return _json_iter->error; ++} ++simdjson_warn_unused simdjson_inline uint8_t *&value_iterator::string_buf_loc() noexcept { ++ return _json_iter->string_buf_loc(); ++} ++simdjson_warn_unused simdjson_inline const json_iterator &value_iterator::json_iter() const noexcept { ++ return *_json_iter; ++} ++simdjson_warn_unused simdjson_inline json_iterator &value_iterator::json_iter() noexcept { ++ return *_json_iter; ++} ++ ++simdjson_inline const uint8_t *value_iterator::peek_start() const noexcept { ++ return _json_iter->peek(start_position()); ++} ++simdjson_inline uint32_t value_iterator::peek_start_length() const noexcept { ++ return _json_iter->peek_length(start_position()); ++} ++ ++simdjson_inline const uint8_t *value_iterator::peek_scalar(const char *type) noexcept { ++ logger::log_value(*_json_iter, start_position(), depth(), type); ++ // If we're not at the position anymore, we don't want to advance the cursor. ++ if (!is_at_start()) { return peek_start(); } ++ ++ // Get the JSON and advance the cursor, decreasing depth to signify that we have retrieved the value. ++ assert_at_start(); ++ return _json_iter->peek(); ++} ++ ++simdjson_inline void value_iterator::advance_scalar(const char *type) noexcept { ++ logger::log_value(*_json_iter, start_position(), depth(), type); ++ // If we're not at the position anymore, we don't want to advance the cursor. ++ if (!is_at_start()) { return; } ++ ++ // Get the JSON and advance the cursor, decreasing depth to signify that we have retrieved the value. ++ assert_at_start(); ++ _json_iter->return_current_and_advance(); ++ _json_iter->ascend_to(depth()-1); ++} ++ ++simdjson_inline error_code value_iterator::start_container(uint8_t start_char, const char *incorrect_type_message, const char *type) noexcept { ++ logger::log_start_value(*_json_iter, start_position(), depth(), type); ++ // If we're not at the position anymore, we don't want to advance the cursor. ++ const uint8_t *json; ++ if (!is_at_start()) { ++#if SIMDJSON_DEVELOPMENT_CHECKS ++ if (!is_at_iterator_start()) { return OUT_OF_ORDER_ITERATION; } ++#endif ++ json = peek_start(); ++ if (*json != start_char) { return incorrect_type_error(incorrect_type_message); } ++ } else { ++ assert_at_start(); ++ /** ++ * We should be prudent. Let us peek. If it is not the right type, we ++ * return an error. Only once we have determined that we have the right ++ * type are we allowed to advance! ++ */ ++ json = _json_iter->peek(); ++ if (*json != start_char) { return incorrect_type_error(incorrect_type_message); } ++ _json_iter->return_current_and_advance(); ++ } ++ ++ ++ return SUCCESS; ++} ++ ++ ++simdjson_inline const uint8_t *value_iterator::peek_root_scalar(const char *type) noexcept { ++ logger::log_value(*_json_iter, start_position(), depth(), type); ++ if (!is_at_start()) { return peek_start(); } ++ ++ assert_at_root(); ++ return _json_iter->peek(); ++} ++simdjson_inline const uint8_t *value_iterator::peek_non_root_scalar(const char *type) noexcept { ++ logger::log_value(*_json_iter, start_position(), depth(), type); ++ if (!is_at_start()) { return peek_start(); } ++ ++ assert_at_non_root_start(); ++ return _json_iter->peek(); ++} ++ ++simdjson_inline void value_iterator::advance_root_scalar(const char *type) noexcept { ++ logger::log_value(*_json_iter, start_position(), depth(), type); ++ if (!is_at_start()) { return; } ++ ++ assert_at_root(); ++ _json_iter->return_current_and_advance(); ++ _json_iter->ascend_to(depth()-1); ++} ++simdjson_inline void value_iterator::advance_non_root_scalar(const char *type) noexcept { ++ logger::log_value(*_json_iter, start_position(), depth(), type); ++ if (!is_at_start()) { return; } ++ ++ assert_at_non_root_start(); ++ _json_iter->return_current_and_advance(); ++ _json_iter->ascend_to(depth()-1); ++} ++ ++simdjson_inline error_code value_iterator::incorrect_type_error(const char *message) const noexcept { ++ logger::log_error(*_json_iter, start_position(), depth(), message); ++ return INCORRECT_TYPE; ++} ++ ++simdjson_inline bool value_iterator::is_at_start() const noexcept { ++ return position() == start_position(); ++} ++ ++simdjson_inline bool value_iterator::is_at_key() const noexcept { ++ // Keys are at the same depth as the object. ++ // Note here that we could be safer and check that we are within an object, ++ // but we do not. ++ return _depth == _json_iter->_depth && *_json_iter->peek() == '"'; ++} ++ ++simdjson_inline bool value_iterator::is_at_iterator_start() const noexcept { ++ // We can legitimately be either at the first value ([1]), or after the array if it's empty ([]). ++ auto delta = position() - start_position(); ++ return delta == 1 || delta == 2; ++} ++ ++inline void value_iterator::assert_at_start() const noexcept { ++ SIMDJSON_ASSUME( _json_iter->token._position == _start_position ); ++ SIMDJSON_ASSUME( _json_iter->_depth == _depth ); ++ SIMDJSON_ASSUME( _depth > 0 ); ++} ++ ++inline void value_iterator::assert_at_container_start() const noexcept { ++ SIMDJSON_ASSUME( _json_iter->token._position == _start_position + 1 ); ++ SIMDJSON_ASSUME( _json_iter->_depth == _depth ); ++ SIMDJSON_ASSUME( _depth > 0 ); ++} ++ ++inline void value_iterator::assert_at_next() const noexcept { ++ SIMDJSON_ASSUME( _json_iter->token._position > _start_position ); ++ SIMDJSON_ASSUME( _json_iter->_depth == _depth ); ++ SIMDJSON_ASSUME( _depth > 0 ); ++} ++ ++simdjson_inline void value_iterator::move_at_start() noexcept { ++ _json_iter->_depth = _depth; ++ _json_iter->token.set_position(_start_position); ++} ++ ++simdjson_inline void value_iterator::move_at_container_start() noexcept { ++ _json_iter->_depth = _depth; ++ _json_iter->token.set_position(_start_position + 1); ++} ++ ++simdjson_inline simdjson_result value_iterator::reset_array() noexcept { ++ move_at_container_start(); ++ return started_array(); ++} ++ ++simdjson_inline simdjson_result value_iterator::reset_object() noexcept { ++ move_at_container_start(); ++ return started_object(); ++} ++ ++inline void value_iterator::assert_at_child() const noexcept { ++ SIMDJSON_ASSUME( _json_iter->token._position > _start_position ); ++ SIMDJSON_ASSUME( _json_iter->_depth == _depth + 1 ); ++ SIMDJSON_ASSUME( _depth > 0 ); ++} ++ ++inline void value_iterator::assert_at_root() const noexcept { ++ assert_at_start(); ++ SIMDJSON_ASSUME( _depth == 1 ); ++} ++ ++inline void value_iterator::assert_at_non_root_start() const noexcept { ++ assert_at_start(); ++ SIMDJSON_ASSUME( _depth > 1 ); ++} ++ ++inline void value_iterator::assert_is_valid() const noexcept { ++ SIMDJSON_ASSUME( _json_iter != nullptr ); ++} ++ ++simdjson_inline bool value_iterator::is_valid() const noexcept { ++ return _json_iter != nullptr; ++} ++ ++simdjson_inline simdjson_result value_iterator::type() const noexcept { ++ switch (*peek_start()) { ++ case '{': ++ return json_type::object; ++ case '[': ++ return json_type::array; ++ case '"': ++ return json_type::string; ++ case 'n': ++ return json_type::null; ++ case 't': case 'f': ++ return json_type::boolean; ++ case '-': ++ case '0': case '1': case '2': case '3': case '4': ++ case '5': case '6': case '7': case '8': case '9': ++ return json_type::number; ++ default: ++ return TAPE_ERROR; ++ } ++} ++ ++simdjson_inline token_position value_iterator::start_position() const noexcept { ++ return _start_position; ++} ++ ++simdjson_inline token_position value_iterator::position() const noexcept { ++ return _json_iter->position(); ++} ++ ++simdjson_inline token_position value_iterator::end_position() const noexcept { ++ return _json_iter->end_position(); ++} ++ ++simdjson_inline token_position value_iterator::last_position() const noexcept { ++ return _json_iter->last_position(); ++} ++ ++simdjson_inline error_code value_iterator::report_error(error_code error, const char *message) noexcept { ++ return _json_iter->report_error(error, message); ++} ++ ++} // namespace ondemand ++} // namespace SIMDJSON_BUILTIN_IMPLEMENTATION ++} // namespace simdjson ++ ++namespace simdjson { ++ ++simdjson_inline simdjson_result::simdjson_result(SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand::value_iterator &&value) noexcept ++ : implementation_simdjson_result_base(std::forward(value)) {} ++simdjson_inline simdjson_result::simdjson_result(error_code error) noexcept ++ : implementation_simdjson_result_base(error) {} ++ ++} // namespace simdjson ++/* end file include/simdjson/generic/ondemand/value_iterator-inl.h */ ++/* begin file include/simdjson/generic/ondemand/array_iterator-inl.h */ ++namespace simdjson { ++namespace SIMDJSON_BUILTIN_IMPLEMENTATION { ++namespace ondemand { ++ ++simdjson_inline array_iterator::array_iterator(const value_iterator &_iter) noexcept ++ : iter{_iter} ++{} ++ ++simdjson_inline simdjson_result array_iterator::operator*() noexcept { ++ if (iter.error()) { iter.abandon(); return iter.error(); } ++ return value(iter.child()); ++} ++simdjson_inline bool array_iterator::operator==(const array_iterator &other) const noexcept { ++ return !(*this != other); ++} ++simdjson_inline bool array_iterator::operator!=(const array_iterator &) const noexcept { ++ return iter.is_open(); ++} ++simdjson_inline array_iterator &array_iterator::operator++() noexcept { ++ error_code error; ++ // PERF NOTE this is a safety rail ... users should exit loops as soon as they receive an error, so we'll never get here. ++ // However, it does not seem to make a perf difference, so we add it out of an abundance of caution. ++ if (( error = iter.error() )) { return *this; } ++ if (( error = iter.skip_child() )) { return *this; } ++ if (( error = iter.has_next_element().error() )) { return *this; } ++ return *this; ++} ++ ++} // namespace ondemand ++} // namespace SIMDJSON_BUILTIN_IMPLEMENTATION ++} // namespace simdjson ++ ++namespace simdjson { ++ ++simdjson_inline simdjson_result::simdjson_result( ++ SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand::array_iterator &&value ++) noexcept ++ : SIMDJSON_BUILTIN_IMPLEMENTATION::implementation_simdjson_result_base(std::forward(value)) ++{ ++ first.iter.assert_is_valid(); ++} ++simdjson_inline simdjson_result::simdjson_result(error_code error) noexcept ++ : SIMDJSON_BUILTIN_IMPLEMENTATION::implementation_simdjson_result_base({}, error) ++{ ++} ++ ++simdjson_inline simdjson_result simdjson_result::operator*() noexcept { ++ if (error()) { return error(); } ++ return *first; ++} ++simdjson_inline bool simdjson_result::operator==(const simdjson_result &other) const noexcept { ++ if (!first.iter.is_valid()) { return !error(); } ++ return first == other.first; ++} ++simdjson_inline bool simdjson_result::operator!=(const simdjson_result &other) const noexcept { ++ if (!first.iter.is_valid()) { return error(); } ++ return first != other.first; ++} ++simdjson_inline simdjson_result &simdjson_result::operator++() noexcept { ++ // Clear the error if there is one, so we don't yield it twice ++ if (error()) { second = SUCCESS; return *this; } ++ ++(first); ++ return *this; ++} ++ ++} // namespace simdjson ++/* end file include/simdjson/generic/ondemand/array_iterator-inl.h */ ++/* begin file include/simdjson/generic/ondemand/object_iterator-inl.h */ ++namespace simdjson { ++namespace SIMDJSON_BUILTIN_IMPLEMENTATION { ++namespace ondemand { ++ ++// ++// object_iterator ++// ++ ++simdjson_inline object_iterator::object_iterator(const value_iterator &_iter) noexcept ++ : iter{_iter} ++{} ++ ++simdjson_inline simdjson_result object_iterator::operator*() noexcept { ++ error_code error = iter.error(); ++ if (error) { iter.abandon(); return error; } ++ auto result = field::start(iter); ++ // TODO this is a safety rail ... users should exit loops as soon as they receive an error. ++ // Nonetheless, let's see if performance is OK with this if statement--the compiler may give it to us for free. ++ if (result.error()) { iter.abandon(); } ++ return result; ++} ++simdjson_inline bool object_iterator::operator==(const object_iterator &other) const noexcept { ++ return !(*this != other); ++} ++simdjson_inline bool object_iterator::operator!=(const object_iterator &) const noexcept { ++ return iter.is_open(); ++} ++ ++SIMDJSON_PUSH_DISABLE_WARNINGS ++SIMDJSON_DISABLE_STRICT_OVERFLOW_WARNING ++simdjson_inline object_iterator &object_iterator::operator++() noexcept { ++ // TODO this is a safety rail ... users should exit loops as soon as they receive an error. ++ // Nonetheless, let's see if performance is OK with this if statement--the compiler may give it to us for free. ++ if (!iter.is_open()) { return *this; } // Iterator will be released if there is an error ++ ++ simdjson_unused error_code error; ++ if ((error = iter.skip_child() )) { return *this; } ++ ++ simdjson_unused bool has_value; ++ if ((error = iter.has_next_field().get(has_value) )) { return *this; }; ++ return *this; ++} ++SIMDJSON_POP_DISABLE_WARNINGS ++ ++// ++// ### Live States ++// ++// While iterating or looking up values, depth >= iter.depth. at_start may vary. Error is ++// always SUCCESS: ++// ++// - Start: This is the state when the object is first found and the iterator is just past the {. ++// In this state, at_start == true. ++// - Next: After we hand a scalar value to the user, or an array/object which they then fully ++// iterate over, the iterator is at the , or } before the next value. In this state, ++// depth == iter.depth, at_start == false, and error == SUCCESS. ++// - Unfinished Business: When we hand an array/object to the user which they do not fully ++// iterate over, we need to finish that iteration by skipping child values until we reach the ++// Next state. In this state, depth > iter.depth, at_start == false, and error == SUCCESS. ++// ++// ## Error States ++// ++// In error states, we will yield exactly one more value before stopping. iter.depth == depth ++// and at_start is always false. We decrement after yielding the error, moving to the Finished ++// state. ++// ++// - Chained Error: When the object iterator is part of an error chain--for example, in ++// `for (auto tweet : doc["tweets"])`, where the tweet field may be missing or not be an ++// object--we yield that error in the loop, exactly once. In this state, error != SUCCESS and ++// iter.depth == depth, and at_start == false. We decrement depth when we yield the error. ++// - Missing Comma Error: When the iterator ++ method discovers there is no comma between fields, ++// we flag that as an error and treat it exactly the same as a Chained Error. In this state, ++// error == TAPE_ERROR, iter.depth == depth, and at_start == false. ++// ++// Errors that occur while reading a field to give to the user (such as when the key is not a ++// string or the field is missing a colon) are yielded immediately. Depth is then decremented, ++// moving to the Finished state without transitioning through an Error state at all. ++// ++// ## Terminal State ++// ++// The terminal state has iter.depth < depth. at_start is always false. ++// ++// - Finished: When we have reached a }, we are finished. We signal this by decrementing depth. ++// In this state, iter.depth < depth, at_start == false, and error == SUCCESS. ++// ++ ++} // namespace ondemand ++} // namespace SIMDJSON_BUILTIN_IMPLEMENTATION ++} // namespace simdjson ++ ++namespace simdjson { ++ ++simdjson_inline simdjson_result::simdjson_result( ++ SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand::object_iterator &&value ++) noexcept ++ : implementation_simdjson_result_base(std::forward(value)) ++{ ++ first.iter.assert_is_valid(); ++} ++simdjson_inline simdjson_result::simdjson_result(error_code error) noexcept ++ : implementation_simdjson_result_base({}, error) ++{ ++} ++ ++simdjson_inline simdjson_result simdjson_result::operator*() noexcept { ++ if (error()) { return error(); } ++ return *first; ++} ++// If we're iterating and there is an error, return the error once. ++simdjson_inline bool simdjson_result::operator==(const simdjson_result &other) const noexcept { ++ if (!first.iter.is_valid()) { return !error(); } ++ return first == other.first; ++} ++// If we're iterating and there is an error, return the error once. ++simdjson_inline bool simdjson_result::operator!=(const simdjson_result &other) const noexcept { ++ if (!first.iter.is_valid()) { return error(); } ++ return first != other.first; ++} ++// Checks for ']' and ',' ++simdjson_inline simdjson_result &simdjson_result::operator++() noexcept { ++ // Clear the error if there is one, so we don't yield it twice ++ if (error()) { second = SUCCESS; return *this; } ++ ++first; ++ return *this; ++} ++ ++} // namespace simdjson ++/* end file include/simdjson/generic/ondemand/object_iterator-inl.h */ ++/* begin file include/simdjson/generic/ondemand/array-inl.h */ ++namespace simdjson { ++namespace SIMDJSON_BUILTIN_IMPLEMENTATION { ++namespace ondemand { ++ ++// ++// ### Live States ++// ++// While iterating or looking up values, depth >= iter->depth. at_start may vary. Error is ++// always SUCCESS: ++// ++// - Start: This is the state when the array is first found and the iterator is just past the `{`. ++// In this state, at_start == true. ++// - Next: After we hand a scalar value to the user, or an array/object which they then fully ++// iterate over, the iterator is at the `,` before the next value (or `]`). In this state, ++// depth == iter->depth, at_start == false, and error == SUCCESS. ++// - Unfinished Business: When we hand an array/object to the user which they do not fully ++// iterate over, we need to finish that iteration by skipping child values until we reach the ++// Next state. In this state, depth > iter->depth, at_start == false, and error == SUCCESS. ++// ++// ## Error States ++// ++// In error states, we will yield exactly one more value before stopping. iter->depth == depth ++// and at_start is always false. We decrement after yielding the error, moving to the Finished ++// state. ++// ++// - Chained Error: When the array iterator is part of an error chain--for example, in ++// `for (auto tweet : doc["tweets"])`, where the tweet element may be missing or not be an ++// array--we yield that error in the loop, exactly once. In this state, error != SUCCESS and ++// iter->depth == depth, and at_start == false. We decrement depth when we yield the error. ++// - Missing Comma Error: When the iterator ++ method discovers there is no comma between elements, ++// we flag that as an error and treat it exactly the same as a Chained Error. In this state, ++// error == TAPE_ERROR, iter->depth == depth, and at_start == false. ++// ++// ## Terminal State ++// ++// The terminal state has iter->depth < depth. at_start is always false. ++// ++// - Finished: When we have reached a `]` or have reported an error, we are finished. We signal this ++// by decrementing depth. In this state, iter->depth < depth, at_start == false, and ++// error == SUCCESS. ++// ++ ++simdjson_inline array::array(const value_iterator &_iter) noexcept ++ : iter{_iter} ++{ ++} ++ ++simdjson_inline simdjson_result array::start(value_iterator &iter) noexcept { ++ // We don't need to know if the array is empty to start iteration, but we do want to know if there ++ // is an error--thus `simdjson_unused`. ++ simdjson_unused bool has_value; ++ SIMDJSON_TRY( iter.start_array().get(has_value) ); ++ return array(iter); ++} ++simdjson_inline simdjson_result array::start_root(value_iterator &iter) noexcept { ++ simdjson_unused bool has_value; ++ SIMDJSON_TRY( iter.start_root_array().get(has_value) ); ++ return array(iter); ++} ++simdjson_inline simdjson_result array::started(value_iterator &iter) noexcept { ++ bool has_value; ++ SIMDJSON_TRY(iter.started_array().get(has_value)); ++ return array(iter); ++} ++ ++simdjson_inline simdjson_result array::begin() noexcept { ++#if SIMDJSON_DEVELOPMENT_CHECKS ++ if (!iter.is_at_iterator_start()) { return OUT_OF_ORDER_ITERATION; } ++#endif ++ return array_iterator(iter); ++} ++simdjson_inline simdjson_result array::end() noexcept { ++ return array_iterator(iter); ++} ++simdjson_inline error_code array::consume() noexcept { ++ auto error = iter.json_iter().skip_child(iter.depth()-1); ++ if(error) { iter.abandon(); } ++ return error; ++} ++ ++simdjson_inline simdjson_result array::raw_json() noexcept { ++ const uint8_t * starting_point{iter.peek_start()}; ++ auto error = consume(); ++ if(error) { return error; } ++ // After 'consume()', we could be left pointing just beyond the document, but that ++ // is ok because we are not going to dereference the final pointer position, we just ++ // use it to compute the length in bytes. ++ const uint8_t * final_point{iter._json_iter->unsafe_pointer()}; ++ return std::string_view(reinterpret_cast(starting_point), size_t(final_point - starting_point)); ++} ++ ++SIMDJSON_PUSH_DISABLE_WARNINGS ++SIMDJSON_DISABLE_STRICT_OVERFLOW_WARNING ++simdjson_inline simdjson_result array::count_elements() & noexcept { ++ size_t count{0}; ++ // Important: we do not consume any of the values. ++ for(simdjson_unused auto v : *this) { count++; } ++ // The above loop will always succeed, but we want to report errors. ++ if(iter.error()) { return iter.error(); } ++ // We need to move back at the start because we expect users to iterate through ++ // the array after counting the number of elements. ++ iter.reset_array(); ++ return count; ++} ++SIMDJSON_POP_DISABLE_WARNINGS ++ ++simdjson_inline simdjson_result array::is_empty() & noexcept { ++ bool is_not_empty; ++ auto error = iter.reset_array().get(is_not_empty); ++ if(error) { return error; } ++ return !is_not_empty; ++} ++ ++inline simdjson_result array::reset() & noexcept { ++ return iter.reset_array(); ++} ++ ++inline simdjson_result array::at_pointer(std::string_view json_pointer) noexcept { ++ if (json_pointer[0] != '/') { return INVALID_JSON_POINTER; } ++ json_pointer = json_pointer.substr(1); ++ // - means "the append position" or "the element after the end of the array" ++ // We don't support this, because we're returning a real element, not a position. ++ if (json_pointer == "-") { return INDEX_OUT_OF_BOUNDS; } ++ ++ // Read the array index ++ size_t array_index = 0; ++ size_t i; ++ for (i = 0; i < json_pointer.length() && json_pointer[i] != '/'; i++) { ++ uint8_t digit = uint8_t(json_pointer[i] - '0'); ++ // Check for non-digit in array index. If it's there, we're trying to get a field in an object ++ if (digit > 9) { return INCORRECT_TYPE; } ++ array_index = array_index*10 + digit; ++ } ++ ++ // 0 followed by other digits is invalid ++ if (i > 1 && json_pointer[0] == '0') { return INVALID_JSON_POINTER; } // "JSON pointer array index has other characters after 0" ++ ++ // Empty string is invalid; so is a "/" with no digits before it ++ if (i == 0) { return INVALID_JSON_POINTER; } // "Empty string in JSON pointer array index" ++ // Get the child ++ auto child = at(array_index); ++ // If there is an error, it ends here ++ if(child.error()) { ++ return child; ++ } ++ ++ // If there is a /, we're not done yet, call recursively. ++ if (i < json_pointer.length()) { ++ child = child.at_pointer(json_pointer.substr(i)); ++ } ++ return child; ++} ++ ++simdjson_inline simdjson_result array::at(size_t index) noexcept { ++ size_t i = 0; ++ for (auto value : *this) { ++ if (i == index) { return value; } ++ i++; ++ } ++ return INDEX_OUT_OF_BOUNDS; ++} ++ ++} // namespace ondemand ++} // namespace SIMDJSON_BUILTIN_IMPLEMENTATION ++} // namespace simdjson ++ ++namespace simdjson { ++ ++simdjson_inline simdjson_result::simdjson_result( ++ SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand::array &&value ++) noexcept ++ : implementation_simdjson_result_base( ++ std::forward(value) ++ ) ++{ ++} ++simdjson_inline simdjson_result::simdjson_result( ++ error_code error ++) noexcept ++ : implementation_simdjson_result_base(error) ++{ ++} ++ ++simdjson_inline simdjson_result simdjson_result::begin() noexcept { ++ if (error()) { return error(); } ++ return first.begin(); ++} ++simdjson_inline simdjson_result simdjson_result::end() noexcept { ++ if (error()) { return error(); } ++ return first.end(); ++} ++simdjson_inline simdjson_result simdjson_result::count_elements() & noexcept { ++ if (error()) { return error(); } ++ return first.count_elements(); ++} ++simdjson_inline simdjson_result simdjson_result::is_empty() & noexcept { ++ if (error()) { return error(); } ++ return first.is_empty(); ++} ++simdjson_inline simdjson_result simdjson_result::at(size_t index) noexcept { ++ if (error()) { return error(); } ++ return first.at(index); ++} ++simdjson_inline simdjson_result simdjson_result::at_pointer(std::string_view json_pointer) noexcept { ++ if (error()) { return error(); } ++ return first.at_pointer(json_pointer); ++} ++} // namespace simdjson ++/* end file include/simdjson/generic/ondemand/array-inl.h */ ++/* begin file include/simdjson/generic/ondemand/document-inl.h */ ++namespace simdjson { ++namespace SIMDJSON_BUILTIN_IMPLEMENTATION { ++namespace ondemand { ++ ++simdjson_inline document::document(ondemand::json_iterator &&_iter) noexcept ++ : iter{std::forward(_iter)} ++{ ++ logger::log_start_value(iter, "document"); ++} ++ ++simdjson_inline document document::start(json_iterator &&iter) noexcept { ++ return document(std::forward(iter)); ++} ++ ++inline void document::rewind() noexcept { ++ iter.rewind(); ++} ++ ++inline std::string document::to_debug_string() noexcept { ++ return iter.to_string(); ++} ++ ++inline simdjson_result document::current_location() noexcept { ++ return iter.current_location(); ++} ++ ++inline int32_t document::current_depth() const noexcept { ++ return iter.depth(); ++} ++ ++inline bool document::is_alive() noexcept { ++ return iter.is_alive(); ++} ++simdjson_inline value_iterator document::resume_value_iterator() noexcept { ++ return value_iterator(&iter, 1, iter.root_position()); ++} ++simdjson_inline value_iterator document::get_root_value_iterator() noexcept { ++ return resume_value_iterator(); ++} ++simdjson_inline simdjson_result document::start_or_resume_object() noexcept { ++ if (iter.at_root()) { ++ return get_object(); ++ } else { ++ return object::resume(resume_value_iterator()); ++ } ++} ++simdjson_inline simdjson_result document::get_value() noexcept { ++ // Make sure we start any arrays or objects before returning, so that start_root_() ++ // gets called. ++ iter.assert_at_document_depth(); ++ switch (*iter.peek()) { ++ case '[': ++ case '{': ++ return value(get_root_value_iterator()); ++ default: ++ // Unfortunately, scalar documents are a special case in simdjson and they cannot ++ // be safely converted to value instances. ++ return SCALAR_DOCUMENT_AS_VALUE; ++ // return value(get_root_value_iterator()); ++ } ++} ++simdjson_inline simdjson_result document::get_array() & noexcept { ++ auto value = get_root_value_iterator(); ++ return array::start_root(value); ++} ++simdjson_inline simdjson_result document::get_object() & noexcept { ++ auto value = get_root_value_iterator(); ++ return object::start_root(value); ++} ++ ++/** ++ * We decided that calling 'get_double()' on the JSON document '1.233 blabla' should ++ * give an error, so we check for trailing content. We want to disallow trailing ++ * content. ++ * Thus, in several implementations below, we pass a 'true' parameter value to ++ * a get_root_value_iterator() method: this indicates that we disallow trailing content. ++ */ ++ ++simdjson_inline simdjson_result document::get_uint64() noexcept { ++ return get_root_value_iterator().get_root_uint64(true); ++} ++simdjson_inline simdjson_result document::get_uint64_in_string() noexcept { ++ return get_root_value_iterator().get_root_uint64_in_string(true); ++} ++simdjson_inline simdjson_result document::get_int64() noexcept { ++ return get_root_value_iterator().get_root_int64(true); ++} ++simdjson_inline simdjson_result document::get_int64_in_string() noexcept { ++ return get_root_value_iterator().get_root_int64_in_string(true); ++} ++simdjson_inline simdjson_result document::get_double() noexcept { ++ return get_root_value_iterator().get_root_double(true); ++} ++simdjson_inline simdjson_result document::get_double_in_string() noexcept { ++ return get_root_value_iterator().get_root_double_in_string(true); ++} ++simdjson_inline simdjson_result document::get_string() noexcept { ++ return get_root_value_iterator().get_root_string(true); ++} ++simdjson_inline simdjson_result document::get_raw_json_string() noexcept { ++ return get_root_value_iterator().get_root_raw_json_string(true); ++} ++simdjson_inline simdjson_result document::get_bool() noexcept { ++ return get_root_value_iterator().get_root_bool(true); ++} ++simdjson_inline simdjson_result document::is_null() noexcept { ++ return get_root_value_iterator().is_root_null(true); ++} ++ ++template<> simdjson_inline simdjson_result document::get() & noexcept { return get_array(); } ++template<> simdjson_inline simdjson_result document::get() & noexcept { return get_object(); } ++template<> simdjson_inline simdjson_result document::get() & noexcept { return get_raw_json_string(); } ++template<> simdjson_inline simdjson_result document::get() & noexcept { return get_string(); } ++template<> simdjson_inline simdjson_result document::get() & noexcept { return get_double(); } ++template<> simdjson_inline simdjson_result document::get() & noexcept { return get_uint64(); } ++template<> simdjson_inline simdjson_result document::get() & noexcept { return get_int64(); } ++template<> simdjson_inline simdjson_result document::get() & noexcept { return get_bool(); } ++template<> simdjson_inline simdjson_result document::get() & noexcept { return get_value(); } ++ ++template<> simdjson_inline simdjson_result document::get() && noexcept { return get_raw_json_string(); } ++template<> simdjson_inline simdjson_result document::get() && noexcept { return get_string(); } ++template<> simdjson_inline simdjson_result document::get() && noexcept { return std::forward(*this).get_double(); } ++template<> simdjson_inline simdjson_result document::get() && noexcept { return std::forward(*this).get_uint64(); } ++template<> simdjson_inline simdjson_result document::get() && noexcept { return std::forward(*this).get_int64(); } ++template<> simdjson_inline simdjson_result document::get() && noexcept { return std::forward(*this).get_bool(); } ++template<> simdjson_inline simdjson_result document::get() && noexcept { return get_value(); } ++ ++template simdjson_inline error_code document::get(T &out) & noexcept { ++ return get().get(out); ++} ++template simdjson_inline error_code document::get(T &out) && noexcept { ++ return std::forward(*this).get().get(out); ++} ++ ++#if SIMDJSON_EXCEPTIONS ++simdjson_inline document::operator array() & noexcept(false) { return get_array(); } ++simdjson_inline document::operator object() & noexcept(false) { return get_object(); } ++simdjson_inline document::operator uint64_t() noexcept(false) { return get_uint64(); } ++simdjson_inline document::operator int64_t() noexcept(false) { return get_int64(); } ++simdjson_inline document::operator double() noexcept(false) { return get_double(); } ++simdjson_inline document::operator std::string_view() noexcept(false) { return get_string(); } ++simdjson_inline document::operator raw_json_string() noexcept(false) { return get_raw_json_string(); } ++simdjson_inline document::operator bool() noexcept(false) { return get_bool(); } ++simdjson_inline document::operator value() noexcept(false) { return get_value(); } ++ ++#endif ++simdjson_inline simdjson_result document::count_elements() & noexcept { ++ auto a = get_array(); ++ simdjson_result answer = a.count_elements(); ++ /* If there was an array, we are now left pointing at its first element. */ ++ if(answer.error() == SUCCESS) { rewind(); } ++ return answer; ++} ++simdjson_inline simdjson_result document::count_fields() & noexcept { ++ auto a = get_object(); ++ simdjson_result answer = a.count_fields(); ++ /* If there was an object, we are now left pointing at its first element. */ ++ if(answer.error() == SUCCESS) { rewind(); } ++ return answer; ++} ++simdjson_inline simdjson_result document::at(size_t index) & noexcept { ++ auto a = get_array(); ++ return a.at(index); ++} ++simdjson_inline simdjson_result document::begin() & noexcept { ++ return get_array().begin(); ++} ++simdjson_inline simdjson_result document::end() & noexcept { ++ return {}; ++} ++ ++simdjson_inline simdjson_result document::find_field(std::string_view key) & noexcept { ++ return start_or_resume_object().find_field(key); ++} ++simdjson_inline simdjson_result document::find_field(const char *key) & noexcept { ++ return start_or_resume_object().find_field(key); ++} ++simdjson_inline simdjson_result document::find_field_unordered(std::string_view key) & noexcept { ++ return start_or_resume_object().find_field_unordered(key); ++} ++simdjson_inline simdjson_result document::find_field_unordered(const char *key) & noexcept { ++ return start_or_resume_object().find_field_unordered(key); ++} ++simdjson_inline simdjson_result document::operator[](std::string_view key) & noexcept { ++ return start_or_resume_object()[key]; ++} ++simdjson_inline simdjson_result document::operator[](const char *key) & noexcept { ++ return start_or_resume_object()[key]; ++} ++ ++simdjson_inline error_code document::consume() noexcept { ++ auto error = iter.skip_child(0); ++ if(error) { iter.abandon(); } ++ return error; ++} ++ ++simdjson_inline simdjson_result document::raw_json() noexcept { ++ auto _iter = get_root_value_iterator(); ++ const uint8_t * starting_point{_iter.peek_start()}; ++ auto error = consume(); ++ if(error) { return error; } ++ // After 'consume()', we could be left pointing just beyond the document, but that ++ // is ok because we are not going to dereference the final pointer position, we just ++ // use it to compute the length in bytes. ++ const uint8_t * final_point{iter.unsafe_pointer()}; ++ return std::string_view(reinterpret_cast(starting_point), size_t(final_point - starting_point)); ++} ++ ++simdjson_inline simdjson_result document::type() noexcept { ++ return get_root_value_iterator().type(); ++} ++ ++simdjson_inline simdjson_result document::is_scalar() noexcept { ++ json_type this_type; ++ auto error = type().get(this_type); ++ if(error) { return error; } ++ return ! ((this_type == json_type::array) || (this_type == json_type::object)); ++} ++ ++simdjson_inline bool document::is_negative() noexcept { ++ return get_root_value_iterator().is_root_negative(); ++} ++ ++simdjson_inline simdjson_result document::is_integer() noexcept { ++ return get_root_value_iterator().is_root_integer(true); ++} ++ ++simdjson_inline simdjson_result document::get_number_type() noexcept { ++ return get_root_value_iterator().get_root_number_type(true); ++} ++ ++simdjson_inline simdjson_result document::get_number() noexcept { ++ return get_root_value_iterator().get_root_number(true); ++} ++ ++ ++simdjson_inline simdjson_result document::raw_json_token() noexcept { ++ auto _iter = get_root_value_iterator(); ++ return std::string_view(reinterpret_cast(_iter.peek_start()), _iter.peek_start_length()); ++} ++ ++simdjson_inline simdjson_result document::at_pointer(std::string_view json_pointer) noexcept { ++ rewind(); // Rewind the document each time at_pointer is called ++ if (json_pointer.empty()) { ++ return this->get_value(); ++ } ++ json_type t; ++ SIMDJSON_TRY(type().get(t)); ++ switch (t) ++ { ++ case json_type::array: ++ return (*this).get_array().at_pointer(json_pointer); ++ case json_type::object: ++ return (*this).get_object().at_pointer(json_pointer); ++ default: ++ return INVALID_JSON_POINTER; ++ } ++} ++ ++} // namespace ondemand ++} // namespace SIMDJSON_BUILTIN_IMPLEMENTATION ++} // namespace simdjson ++ ++namespace simdjson { ++ ++simdjson_inline simdjson_result::simdjson_result( ++ SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand::document &&value ++) noexcept : ++ implementation_simdjson_result_base( ++ std::forward(value) ++ ) ++{ ++} ++simdjson_inline simdjson_result::simdjson_result( ++ error_code error ++) noexcept : ++ implementation_simdjson_result_base( ++ error ++ ) ++{ ++} ++simdjson_inline simdjson_result simdjson_result::count_elements() & noexcept { ++ if (error()) { return error(); } ++ return first.count_elements(); ++} ++simdjson_inline simdjson_result simdjson_result::count_fields() & noexcept { ++ if (error()) { return error(); } ++ return first.count_fields(); ++} ++simdjson_inline simdjson_result simdjson_result::at(size_t index) & noexcept { ++ if (error()) { return error(); } ++ return first.at(index); ++} ++simdjson_inline error_code simdjson_result::rewind() noexcept { ++ if (error()) { return error(); } ++ first.rewind(); ++ return SUCCESS; ++} ++simdjson_inline simdjson_result simdjson_result::begin() & noexcept { ++ if (error()) { return error(); } ++ return first.begin(); ++} ++simdjson_inline simdjson_result simdjson_result::end() & noexcept { ++ return {}; ++} ++simdjson_inline simdjson_result simdjson_result::find_field_unordered(std::string_view key) & noexcept { ++ if (error()) { return error(); } ++ return first.find_field_unordered(key); ++} ++simdjson_inline simdjson_result simdjson_result::find_field_unordered(const char *key) & noexcept { ++ if (error()) { return error(); } ++ return first.find_field_unordered(key); ++} ++simdjson_inline simdjson_result simdjson_result::operator[](std::string_view key) & noexcept { ++ if (error()) { return error(); } ++ return first[key]; ++} ++simdjson_inline simdjson_result simdjson_result::operator[](const char *key) & noexcept { ++ if (error()) { return error(); } ++ return first[key]; ++} ++simdjson_inline simdjson_result simdjson_result::find_field(std::string_view key) & noexcept { ++ if (error()) { return error(); } ++ return first.find_field(key); ++} ++simdjson_inline simdjson_result simdjson_result::find_field(const char *key) & noexcept { ++ if (error()) { return error(); } ++ return first.find_field(key); ++} ++simdjson_inline simdjson_result simdjson_result::get_array() & noexcept { ++ if (error()) { return error(); } ++ return first.get_array(); ++} ++simdjson_inline simdjson_result simdjson_result::get_object() & noexcept { ++ if (error()) { return error(); } ++ return first.get_object(); ++} ++simdjson_inline simdjson_result simdjson_result::get_uint64() noexcept { ++ if (error()) { return error(); } ++ return first.get_uint64(); ++} ++simdjson_inline simdjson_result simdjson_result::get_uint64_in_string() noexcept { ++ if (error()) { return error(); } ++ return first.get_uint64_in_string(); ++} ++simdjson_inline simdjson_result simdjson_result::get_int64() noexcept { ++ if (error()) { return error(); } ++ return first.get_int64(); ++} ++simdjson_inline simdjson_result simdjson_result::get_int64_in_string() noexcept { ++ if (error()) { return error(); } ++ return first.get_int64_in_string(); ++} ++simdjson_inline simdjson_result simdjson_result::get_double() noexcept { ++ if (error()) { return error(); } ++ return first.get_double(); ++} ++simdjson_inline simdjson_result simdjson_result::get_double_in_string() noexcept { ++ if (error()) { return error(); } ++ return first.get_double_in_string(); ++} ++simdjson_inline simdjson_result simdjson_result::get_string() noexcept { ++ if (error()) { return error(); } ++ return first.get_string(); ++} ++simdjson_inline simdjson_result simdjson_result::get_raw_json_string() noexcept { ++ if (error()) { return error(); } ++ return first.get_raw_json_string(); ++} ++simdjson_inline simdjson_result simdjson_result::get_bool() noexcept { ++ if (error()) { return error(); } ++ return first.get_bool(); ++} ++simdjson_inline simdjson_result simdjson_result::get_value() noexcept { ++ if (error()) { return error(); } ++ return first.get_value(); ++} ++simdjson_inline simdjson_result simdjson_result::is_null() noexcept { ++ if (error()) { return error(); } ++ return first.is_null(); ++} ++ ++template ++simdjson_inline simdjson_result simdjson_result::get() & noexcept { ++ if (error()) { return error(); } ++ return first.get(); ++} ++template ++simdjson_inline simdjson_result simdjson_result::get() && noexcept { ++ if (error()) { return error(); } ++ return std::forward(first).get(); ++} ++template ++simdjson_inline error_code simdjson_result::get(T &out) & noexcept { ++ if (error()) { return error(); } ++ return first.get(out); ++} ++template ++simdjson_inline error_code simdjson_result::get(T &out) && noexcept { ++ if (error()) { return error(); } ++ return std::forward(first).get(out); ++} ++ ++template<> simdjson_inline simdjson_result simdjson_result::get() & noexcept = delete; ++template<> simdjson_inline simdjson_result simdjson_result::get() && noexcept { ++ if (error()) { return error(); } ++ return std::forward(first); ++} ++template<> simdjson_inline error_code simdjson_result::get(SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand::document &out) & noexcept = delete; ++template<> simdjson_inline error_code simdjson_result::get(SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand::document &out) && noexcept { ++ if (error()) { return error(); } ++ out = std::forward(first); ++ return SUCCESS; ++} ++ ++simdjson_inline simdjson_result simdjson_result::type() noexcept { ++ if (error()) { return error(); } ++ return first.type(); ++} ++ ++simdjson_inline simdjson_result simdjson_result::is_scalar() noexcept { ++ if (error()) { return error(); } ++ return first.is_scalar(); ++} ++ ++ ++simdjson_inline bool simdjson_result::is_negative() noexcept { ++ if (error()) { return error(); } ++ return first.is_negative(); ++} ++ ++simdjson_inline simdjson_result simdjson_result::is_integer() noexcept { ++ if (error()) { return error(); } ++ return first.is_integer(); ++} ++ ++simdjson_inline simdjson_result simdjson_result::get_number_type() noexcept { ++ if (error()) { return error(); } ++ return first.get_number_type(); ++} ++ ++simdjson_inline simdjson_result simdjson_result::get_number() noexcept { ++ if (error()) { return error(); } ++ return first.get_number(); ++} ++ ++ ++#if SIMDJSON_EXCEPTIONS ++simdjson_inline simdjson_result::operator SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand::array() & noexcept(false) { ++ if (error()) { throw simdjson_error(error()); } ++ return first; ++} ++simdjson_inline simdjson_result::operator SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand::object() & noexcept(false) { ++ if (error()) { throw simdjson_error(error()); } ++ return first; ++} ++simdjson_inline simdjson_result::operator uint64_t() noexcept(false) { ++ if (error()) { throw simdjson_error(error()); } ++ return first; ++} ++simdjson_inline simdjson_result::operator int64_t() noexcept(false) { ++ if (error()) { throw simdjson_error(error()); } ++ return first; ++} ++simdjson_inline simdjson_result::operator double() noexcept(false) { ++ if (error()) { throw simdjson_error(error()); } ++ return first; ++} ++simdjson_inline simdjson_result::operator std::string_view() noexcept(false) { ++ if (error()) { throw simdjson_error(error()); } ++ return first; ++} ++simdjson_inline simdjson_result::operator SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand::raw_json_string() noexcept(false) { ++ if (error()) { throw simdjson_error(error()); } ++ return first; ++} ++simdjson_inline simdjson_result::operator bool() noexcept(false) { ++ if (error()) { throw simdjson_error(error()); } ++ return first; ++} ++simdjson_inline simdjson_result::operator SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand::value() noexcept(false) { ++ if (error()) { throw simdjson_error(error()); } ++ return first; ++} ++#endif ++ ++ ++simdjson_inline simdjson_result simdjson_result::current_location() noexcept { ++ if (error()) { return error(); } ++ return first.current_location(); ++} ++ ++simdjson_inline int32_t simdjson_result::current_depth() const noexcept { ++ if (error()) { return error(); } ++ return first.current_depth(); ++} ++ ++simdjson_inline simdjson_result simdjson_result::raw_json_token() noexcept { ++ if (error()) { return error(); } ++ return first.raw_json_token(); ++} ++ ++simdjson_inline simdjson_result simdjson_result::at_pointer(std::string_view json_pointer) noexcept { ++ if (error()) { return error(); } ++ return first.at_pointer(json_pointer); ++} ++ ++ ++} // namespace simdjson ++ ++ ++namespace simdjson { ++namespace SIMDJSON_BUILTIN_IMPLEMENTATION { ++namespace ondemand { ++ ++simdjson_inline document_reference::document_reference() noexcept : doc{nullptr} {} ++simdjson_inline document_reference::document_reference(document &d) noexcept : doc(&d) {} ++simdjson_inline void document_reference::rewind() noexcept { doc->rewind(); } ++simdjson_inline simdjson_result document_reference::get_array() & noexcept { return doc->get_array(); } ++simdjson_inline simdjson_result document_reference::get_object() & noexcept { return doc->get_object(); } ++/** ++ * The document_reference instances are used primarily/solely for streams of JSON ++ * documents. ++ * We decided that calling 'get_double()' on the JSON document '1.233 blabla' should ++ * give an error, so we check for trailing content. ++ * ++ * However, for streams of JSON documents, we want to be able to start from ++ * "321" "321" "321" ++ * and parse it successfully as a stream of JSON documents, calling get_uint64_in_string() ++ * successfully each time. ++ * ++ * To achieve this result, we pass a 'false' to a get_root_value_iterator() method: ++ * this indicates that we allow trailing content. ++ */ ++simdjson_inline simdjson_result document_reference::get_uint64() noexcept { return doc->get_root_value_iterator().get_root_uint64(false); } ++simdjson_inline simdjson_result document_reference::get_uint64_in_string() noexcept { return doc->get_root_value_iterator().get_root_uint64_in_string(false); } ++simdjson_inline simdjson_result document_reference::get_int64() noexcept { return doc->get_root_value_iterator().get_root_int64(false); } ++simdjson_inline simdjson_result document_reference::get_int64_in_string() noexcept { return doc->get_root_value_iterator().get_root_int64_in_string(false); } ++simdjson_inline simdjson_result document_reference::get_double() noexcept { return doc->get_root_value_iterator().get_root_double(false); } ++simdjson_inline simdjson_result document_reference::get_double_in_string() noexcept { return doc->get_root_value_iterator().get_root_double(false); } ++simdjson_inline simdjson_result document_reference::get_string() noexcept { return doc->get_root_value_iterator().get_root_string(false); } ++simdjson_inline simdjson_result document_reference::get_raw_json_string() noexcept { return doc->get_root_value_iterator().get_root_raw_json_string(false); } ++simdjson_inline simdjson_result document_reference::get_bool() noexcept { return doc->get_root_value_iterator().get_root_bool(false); } ++simdjson_inline simdjson_result document_reference::get_value() noexcept { return doc->get_value(); } ++simdjson_inline simdjson_result document_reference::is_null() noexcept { return doc->get_root_value_iterator().is_root_null(false); } ++ ++#if SIMDJSON_EXCEPTIONS ++simdjson_inline document_reference::operator array() & noexcept(false) { return array(*doc); } ++simdjson_inline document_reference::operator object() & noexcept(false) { return object(*doc); } ++simdjson_inline document_reference::operator uint64_t() noexcept(false) { return get_uint64(); } ++simdjson_inline document_reference::operator int64_t() noexcept(false) { return get_int64(); } ++simdjson_inline document_reference::operator double() noexcept(false) { return get_double(); } ++simdjson_inline document_reference::operator std::string_view() noexcept(false) { return std::string_view(*doc); } ++simdjson_inline document_reference::operator raw_json_string() noexcept(false) { return raw_json_string(*doc); } ++simdjson_inline document_reference::operator bool() noexcept(false) { return get_bool(); } ++simdjson_inline document_reference::operator value() noexcept(false) { return value(*doc); } ++#endif ++simdjson_inline simdjson_result document_reference::count_elements() & noexcept { return doc->count_elements(); } ++simdjson_inline simdjson_result document_reference::count_fields() & noexcept { return doc->count_fields(); } ++simdjson_inline simdjson_result document_reference::at(size_t index) & noexcept { return doc->at(index); } ++simdjson_inline simdjson_result document_reference::begin() & noexcept { return doc->begin(); } ++simdjson_inline simdjson_result document_reference::end() & noexcept { return doc->end(); } ++simdjson_inline simdjson_result document_reference::find_field(std::string_view key) & noexcept { return doc->find_field(key); } ++simdjson_inline simdjson_result document_reference::find_field(const char *key) & noexcept { return doc->find_field(key); } ++simdjson_inline simdjson_result document_reference::operator[](std::string_view key) & noexcept { return (*doc)[key]; } ++simdjson_inline simdjson_result document_reference::operator[](const char *key) & noexcept { return (*doc)[key]; } ++simdjson_inline simdjson_result document_reference::find_field_unordered(std::string_view key) & noexcept { return doc->find_field_unordered(key); } ++simdjson_inline simdjson_result document_reference::find_field_unordered(const char *key) & noexcept { return doc->find_field_unordered(key); } ++simdjson_inline simdjson_result document_reference::type() noexcept { return doc->type(); } ++simdjson_inline simdjson_result document_reference::is_scalar() noexcept { return doc->is_scalar(); } ++simdjson_inline simdjson_result document_reference::current_location() noexcept { return doc->current_location(); } ++simdjson_inline int32_t document_reference::current_depth() const noexcept { return doc->current_depth(); } ++simdjson_inline bool document_reference::is_negative() noexcept { return doc->is_negative(); } ++simdjson_inline simdjson_result document_reference::is_integer() noexcept { return doc->get_root_value_iterator().is_root_integer(false); } ++simdjson_inline simdjson_result document_reference::get_number_type() noexcept { return doc->get_root_value_iterator().get_root_number_type(false); } ++simdjson_inline simdjson_result document_reference::get_number() noexcept { return doc->get_root_value_iterator().get_root_number(false); } ++simdjson_inline simdjson_result document_reference::raw_json_token() noexcept { return doc->raw_json_token(); } ++simdjson_inline simdjson_result document_reference::at_pointer(std::string_view json_pointer) noexcept { return doc->at_pointer(json_pointer); } ++simdjson_inline simdjson_result document_reference::raw_json() noexcept { return doc->raw_json();} ++simdjson_inline document_reference::operator document&() const noexcept { return *doc; } ++ ++} // namespace ondemand ++} // namespace SIMDJSON_BUILTIN_IMPLEMENTATION ++} // namespace simdjson ++ ++ ++ ++namespace simdjson { ++simdjson_inline simdjson_result::simdjson_result(SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand::document_reference value, error_code error) ++ noexcept : implementation_simdjson_result_base(std::forward(value), error) {} ++ ++ ++simdjson_inline simdjson_result simdjson_result::count_elements() & noexcept { ++ if (error()) { return error(); } ++ return first.count_elements(); ++} ++simdjson_inline simdjson_result simdjson_result::count_fields() & noexcept { ++ if (error()) { return error(); } ++ return first.count_fields(); ++} ++simdjson_inline simdjson_result simdjson_result::at(size_t index) & noexcept { ++ if (error()) { return error(); } ++ return first.at(index); ++} ++simdjson_inline error_code simdjson_result::rewind() noexcept { ++ if (error()) { return error(); } ++ first.rewind(); ++ return SUCCESS; ++} ++simdjson_inline simdjson_result simdjson_result::begin() & noexcept { ++ if (error()) { return error(); } ++ return first.begin(); ++} ++simdjson_inline simdjson_result simdjson_result::end() & noexcept { ++ return {}; ++} ++simdjson_inline simdjson_result simdjson_result::find_field_unordered(std::string_view key) & noexcept { ++ if (error()) { return error(); } ++ return first.find_field_unordered(key); ++} ++simdjson_inline simdjson_result simdjson_result::find_field_unordered(const char *key) & noexcept { ++ if (error()) { return error(); } ++ return first.find_field_unordered(key); ++} ++simdjson_inline simdjson_result simdjson_result::operator[](std::string_view key) & noexcept { ++ if (error()) { return error(); } ++ return first[key]; ++} ++simdjson_inline simdjson_result simdjson_result::operator[](const char *key) & noexcept { ++ if (error()) { return error(); } ++ return first[key]; ++} ++simdjson_inline simdjson_result simdjson_result::find_field(std::string_view key) & noexcept { ++ if (error()) { return error(); } ++ return first.find_field(key); ++} ++simdjson_inline simdjson_result simdjson_result::find_field(const char *key) & noexcept { ++ if (error()) { return error(); } ++ return first.find_field(key); ++} ++simdjson_inline simdjson_result simdjson_result::get_array() & noexcept { ++ if (error()) { return error(); } ++ return first.get_array(); ++} ++simdjson_inline simdjson_result simdjson_result::get_object() & noexcept { ++ if (error()) { return error(); } ++ return first.get_object(); ++} ++simdjson_inline simdjson_result simdjson_result::get_uint64() noexcept { ++ if (error()) { return error(); } ++ return first.get_uint64(); ++} ++simdjson_inline simdjson_result simdjson_result::get_uint64_in_string() noexcept { ++ if (error()) { return error(); } ++ return first.get_uint64_in_string(); ++} ++simdjson_inline simdjson_result simdjson_result::get_int64() noexcept { ++ if (error()) { return error(); } ++ return first.get_int64(); ++} ++simdjson_inline simdjson_result simdjson_result::get_int64_in_string() noexcept { ++ if (error()) { return error(); } ++ return first.get_int64_in_string(); ++} ++simdjson_inline simdjson_result simdjson_result::get_double() noexcept { ++ if (error()) { return error(); } ++ return first.get_double(); ++} ++simdjson_inline simdjson_result simdjson_result::get_double_in_string() noexcept { ++ if (error()) { return error(); } ++ return first.get_double_in_string(); ++} ++simdjson_inline simdjson_result simdjson_result::get_string() noexcept { ++ if (error()) { return error(); } ++ return first.get_string(); ++} ++simdjson_inline simdjson_result simdjson_result::get_raw_json_string() noexcept { ++ if (error()) { return error(); } ++ return first.get_raw_json_string(); ++} ++simdjson_inline simdjson_result simdjson_result::get_bool() noexcept { ++ if (error()) { return error(); } ++ return first.get_bool(); ++} ++simdjson_inline simdjson_result simdjson_result::get_value() noexcept { ++ if (error()) { return error(); } ++ return first.get_value(); ++} ++simdjson_inline simdjson_result simdjson_result::is_null() noexcept { ++ if (error()) { return error(); } ++ return first.is_null(); ++} ++simdjson_inline simdjson_result simdjson_result::type() noexcept { ++ if (error()) { return error(); } ++ return first.type(); ++} ++simdjson_inline simdjson_result simdjson_result::is_scalar() noexcept { ++ if (error()) { return error(); } ++ return first.is_scalar(); ++} ++simdjson_inline simdjson_result simdjson_result::is_negative() noexcept { ++ if (error()) { return error(); } ++ return first.is_negative(); ++} ++simdjson_inline simdjson_result simdjson_result::is_integer() noexcept { ++ if (error()) { return error(); } ++ return first.is_integer(); ++} ++simdjson_inline simdjson_result simdjson_result::get_number_type() noexcept { ++ if (error()) { return error(); } ++ return first.get_number_type(); ++} ++simdjson_inline simdjson_result simdjson_result::get_number() noexcept { ++ if (error()) { return error(); } ++ return first.get_number(); ++} ++#if SIMDJSON_EXCEPTIONS ++simdjson_inline simdjson_result::operator SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand::array() & noexcept(false) { ++ if (error()) { throw simdjson_error(error()); } ++ return first; ++} ++simdjson_inline simdjson_result::operator SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand::object() & noexcept(false) { ++ if (error()) { throw simdjson_error(error()); } ++ return first; ++} ++simdjson_inline simdjson_result::operator uint64_t() noexcept(false) { ++ if (error()) { throw simdjson_error(error()); } ++ return first; ++} ++simdjson_inline simdjson_result::operator int64_t() noexcept(false) { ++ if (error()) { throw simdjson_error(error()); } ++ return first; ++} ++simdjson_inline simdjson_result::operator double() noexcept(false) { ++ if (error()) { throw simdjson_error(error()); } ++ return first; ++} ++simdjson_inline simdjson_result::operator std::string_view() noexcept(false) { ++ if (error()) { throw simdjson_error(error()); } ++ return first; ++} ++simdjson_inline simdjson_result::operator SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand::raw_json_string() noexcept(false) { ++ if (error()) { throw simdjson_error(error()); } ++ return first; ++} ++simdjson_inline simdjson_result::operator bool() noexcept(false) { ++ if (error()) { throw simdjson_error(error()); } ++ return first; ++} ++simdjson_inline simdjson_result::operator SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand::value() noexcept(false) { ++ if (error()) { throw simdjson_error(error()); } ++ return first; ++} ++#endif ++ ++simdjson_inline simdjson_result simdjson_result::current_location() noexcept { ++ if (error()) { return error(); } ++ return first.current_location(); ++} ++ ++simdjson_inline simdjson_result simdjson_result::raw_json_token() noexcept { ++ if (error()) { return error(); } ++ return first.raw_json_token(); ++} ++ ++simdjson_inline simdjson_result simdjson_result::at_pointer(std::string_view json_pointer) noexcept { ++ if (error()) { return error(); } ++ return first.at_pointer(json_pointer); ++} ++ ++ ++} // namespace simdjson ++/* end file include/simdjson/generic/ondemand/document-inl.h */ ++/* begin file include/simdjson/generic/ondemand/value-inl.h */ ++namespace simdjson { ++namespace SIMDJSON_BUILTIN_IMPLEMENTATION { ++namespace ondemand { ++ ++simdjson_inline value::value(const value_iterator &_iter) noexcept ++ : iter{_iter} ++{ ++} ++simdjson_inline value value::start(const value_iterator &iter) noexcept { ++ return iter; ++} ++simdjson_inline value value::resume(const value_iterator &iter) noexcept { ++ return iter; ++} ++ ++simdjson_inline simdjson_result value::get_array() noexcept { ++ return array::start(iter); ++} ++simdjson_inline simdjson_result value::get_object() noexcept { ++ return object::start(iter); ++} ++simdjson_inline simdjson_result value::start_or_resume_object() noexcept { ++ if (iter.at_start()) { ++ return get_object(); ++ } else { ++ return object::resume(iter); ++ } ++} ++ ++simdjson_inline simdjson_result value::get_raw_json_string() noexcept { ++ return iter.get_raw_json_string(); ++} ++simdjson_inline simdjson_result value::get_string() noexcept { ++ return iter.get_string(); ++} ++simdjson_inline simdjson_result value::get_double() noexcept { ++ return iter.get_double(); ++} ++simdjson_inline simdjson_result value::get_double_in_string() noexcept { ++ return iter.get_double_in_string(); ++} ++simdjson_inline simdjson_result value::get_uint64() noexcept { ++ return iter.get_uint64(); ++} ++simdjson_inline simdjson_result value::get_uint64_in_string() noexcept { ++ return iter.get_uint64_in_string(); ++} ++simdjson_inline simdjson_result value::get_int64() noexcept { ++ return iter.get_int64(); ++} ++simdjson_inline simdjson_result value::get_int64_in_string() noexcept { ++ return iter.get_int64_in_string(); ++} ++simdjson_inline simdjson_result value::get_bool() noexcept { ++ return iter.get_bool(); ++} ++simdjson_inline simdjson_result value::is_null() noexcept { ++ return iter.is_null(); ++} ++template<> simdjson_inline simdjson_result value::get() noexcept { return get_array(); } ++template<> simdjson_inline simdjson_result value::get() noexcept { return get_object(); } ++template<> simdjson_inline simdjson_result value::get() noexcept { return get_raw_json_string(); } ++template<> simdjson_inline simdjson_result value::get() noexcept { return get_string(); } ++template<> simdjson_inline simdjson_result value::get() noexcept { return get_number(); } ++template<> simdjson_inline simdjson_result value::get() noexcept { return get_double(); } ++template<> simdjson_inline simdjson_result value::get() noexcept { return get_uint64(); } ++template<> simdjson_inline simdjson_result value::get() noexcept { return get_int64(); } ++template<> simdjson_inline simdjson_result value::get() noexcept { return get_bool(); } ++ ++template simdjson_inline error_code value::get(T &out) noexcept { ++ return get().get(out); ++} ++ ++#if SIMDJSON_EXCEPTIONS ++simdjson_inline value::operator array() noexcept(false) { ++ return get_array(); ++} ++simdjson_inline value::operator object() noexcept(false) { ++ return get_object(); ++} ++simdjson_inline value::operator uint64_t() noexcept(false) { ++ return get_uint64(); ++} ++simdjson_inline value::operator int64_t() noexcept(false) { ++ return get_int64(); ++} ++simdjson_inline value::operator double() noexcept(false) { ++ return get_double(); ++} ++simdjson_inline value::operator std::string_view() noexcept(false) { ++ return get_string(); ++} ++simdjson_inline value::operator raw_json_string() noexcept(false) { ++ return get_raw_json_string(); ++} ++simdjson_inline value::operator bool() noexcept(false) { ++ return get_bool(); ++} ++#endif ++ ++simdjson_inline simdjson_result value::begin() & noexcept { ++ return get_array().begin(); ++} ++simdjson_inline simdjson_result value::end() & noexcept { ++ return {}; ++} ++simdjson_inline simdjson_result value::count_elements() & noexcept { ++ simdjson_result answer; ++ auto a = get_array(); ++ answer = a.count_elements(); ++ // count_elements leaves you pointing inside the array, at the first element. ++ // We need to move back so that the user can create a new array (which requires that ++ // we point at '['). ++ iter.move_at_start(); ++ return answer; ++} ++simdjson_inline simdjson_result value::count_fields() & noexcept { ++ simdjson_result answer; ++ auto a = get_object(); ++ answer = a.count_fields(); ++ iter.move_at_start(); ++ return answer; ++} ++simdjson_inline simdjson_result value::at(size_t index) noexcept { ++ auto a = get_array(); ++ return a.at(index); ++} ++ ++simdjson_inline simdjson_result value::find_field(std::string_view key) noexcept { ++ return start_or_resume_object().find_field(key); ++} ++simdjson_inline simdjson_result value::find_field(const char *key) noexcept { ++ return start_or_resume_object().find_field(key); ++} ++ ++simdjson_inline simdjson_result value::find_field_unordered(std::string_view key) noexcept { ++ return start_or_resume_object().find_field_unordered(key); ++} ++simdjson_inline simdjson_result value::find_field_unordered(const char *key) noexcept { ++ return start_or_resume_object().find_field_unordered(key); ++} ++ ++simdjson_inline simdjson_result value::operator[](std::string_view key) noexcept { ++ return start_or_resume_object()[key]; ++} ++simdjson_inline simdjson_result value::operator[](const char *key) noexcept { ++ return start_or_resume_object()[key]; ++} ++ ++simdjson_inline simdjson_result value::type() noexcept { ++ return iter.type(); ++} ++ ++simdjson_inline simdjson_result value::is_scalar() noexcept { ++ json_type this_type; ++ auto error = type().get(this_type); ++ if(error) { return error; } ++ return ! ((this_type == json_type::array) || (this_type == json_type::object)); ++} ++ ++simdjson_inline bool value::is_negative() noexcept { ++ return iter.is_negative(); ++} ++ ++simdjson_inline simdjson_result value::is_integer() noexcept { ++ return iter.is_integer(); ++} ++simdjson_warn_unused simdjson_inline simdjson_result value::get_number_type() noexcept { ++ return iter.get_number_type(); ++} ++simdjson_warn_unused simdjson_inline simdjson_result value::get_number() noexcept { ++ return iter.get_number(); ++} ++ ++simdjson_inline std::string_view value::raw_json_token() noexcept { ++ return std::string_view(reinterpret_cast(iter.peek_start()), iter.peek_start_length()); ++} ++ ++simdjson_inline simdjson_result value::current_location() noexcept { ++ return iter.json_iter().current_location(); ++} ++ ++simdjson_inline int32_t value::current_depth() const noexcept{ ++ return iter.json_iter().depth(); ++} ++ ++simdjson_inline simdjson_result value::at_pointer(std::string_view json_pointer) noexcept { ++ json_type t; ++ SIMDJSON_TRY(type().get(t)); ++ switch (t) ++ { ++ case json_type::array: ++ return (*this).get_array().at_pointer(json_pointer); ++ case json_type::object: ++ return (*this).get_object().at_pointer(json_pointer); ++ default: ++ return INVALID_JSON_POINTER; ++ } ++} ++ ++} // namespace ondemand ++} // namespace SIMDJSON_BUILTIN_IMPLEMENTATION ++} // namespace simdjson ++ ++namespace simdjson { ++ ++simdjson_inline simdjson_result::simdjson_result( ++ SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand::value &&value ++) noexcept : ++ implementation_simdjson_result_base( ++ std::forward(value) ++ ) ++{ ++} ++simdjson_inline simdjson_result::simdjson_result( ++ error_code error ++) noexcept : ++ implementation_simdjson_result_base(error) ++{ ++} ++simdjson_inline simdjson_result simdjson_result::count_elements() & noexcept { ++ if (error()) { return error(); } ++ return first.count_elements(); ++} ++simdjson_inline simdjson_result simdjson_result::count_fields() & noexcept { ++ if (error()) { return error(); } ++ return first.count_fields(); ++} ++simdjson_inline simdjson_result simdjson_result::at(size_t index) noexcept { ++ if (error()) { return error(); } ++ return first.at(index); ++} ++simdjson_inline simdjson_result simdjson_result::begin() & noexcept { ++ if (error()) { return error(); } ++ return first.begin(); ++} ++simdjson_inline simdjson_result simdjson_result::end() & noexcept { ++ if (error()) { return error(); } ++ return {}; ++} ++ ++simdjson_inline simdjson_result simdjson_result::find_field(std::string_view key) noexcept { ++ if (error()) { return error(); } ++ return first.find_field(key); ++} ++simdjson_inline simdjson_result simdjson_result::find_field(const char *key) noexcept { ++ if (error()) { return error(); } ++ return first.find_field(key); ++} ++ ++simdjson_inline simdjson_result simdjson_result::find_field_unordered(std::string_view key) noexcept { ++ if (error()) { return error(); } ++ return first.find_field_unordered(key); ++} ++simdjson_inline simdjson_result simdjson_result::find_field_unordered(const char *key) noexcept { ++ if (error()) { return error(); } ++ return first.find_field_unordered(key); ++} ++ ++simdjson_inline simdjson_result simdjson_result::operator[](std::string_view key) noexcept { ++ if (error()) { return error(); } ++ return first[key]; ++} ++simdjson_inline simdjson_result simdjson_result::operator[](const char *key) noexcept { ++ if (error()) { return error(); } ++ return first[key]; ++} ++ ++simdjson_inline simdjson_result simdjson_result::get_array() noexcept { ++ if (error()) { return error(); } ++ return first.get_array(); ++} ++simdjson_inline simdjson_result simdjson_result::get_object() noexcept { ++ if (error()) { return error(); } ++ return first.get_object(); ++} ++simdjson_inline simdjson_result simdjson_result::get_uint64() noexcept { ++ if (error()) { return error(); } ++ return first.get_uint64(); ++} ++simdjson_inline simdjson_result simdjson_result::get_uint64_in_string() noexcept { ++ if (error()) { return error(); } ++ return first.get_uint64_in_string(); ++} ++simdjson_inline simdjson_result simdjson_result::get_int64() noexcept { ++ if (error()) { return error(); } ++ return first.get_int64(); ++} ++simdjson_inline simdjson_result simdjson_result::get_int64_in_string() noexcept { ++ if (error()) { return error(); } ++ return first.get_int64_in_string(); ++} ++simdjson_inline simdjson_result simdjson_result::get_double() noexcept { ++ if (error()) { return error(); } ++ return first.get_double(); ++} ++simdjson_inline simdjson_result simdjson_result::get_double_in_string() noexcept { ++ if (error()) { return error(); } ++ return first.get_double_in_string(); ++} ++simdjson_inline simdjson_result simdjson_result::get_string() noexcept { ++ if (error()) { return error(); } ++ return first.get_string(); ++} ++simdjson_inline simdjson_result simdjson_result::get_raw_json_string() noexcept { ++ if (error()) { return error(); } ++ return first.get_raw_json_string(); ++} ++simdjson_inline simdjson_result simdjson_result::get_bool() noexcept { ++ if (error()) { return error(); } ++ return first.get_bool(); ++} ++simdjson_inline simdjson_result simdjson_result::is_null() noexcept { ++ if (error()) { return error(); } ++ return first.is_null(); ++} ++ ++template simdjson_inline simdjson_result simdjson_result::get() noexcept { ++ if (error()) { return error(); } ++ return first.get(); ++} ++template simdjson_inline error_code simdjson_result::get(T &out) noexcept { ++ if (error()) { return error(); } ++ return first.get(out); ++} ++ ++template<> simdjson_inline simdjson_result simdjson_result::get() noexcept { ++ if (error()) { return error(); } ++ return std::move(first); ++} ++template<> simdjson_inline error_code simdjson_result::get(SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand::value &out) noexcept { ++ if (error()) { return error(); } ++ out = first; ++ return SUCCESS; ++} ++ ++simdjson_inline simdjson_result simdjson_result::type() noexcept { ++ if (error()) { return error(); } ++ return first.type(); ++} ++simdjson_inline simdjson_result simdjson_result::is_scalar() noexcept { ++ if (error()) { return error(); } ++ return first.is_scalar(); ++} ++simdjson_inline simdjson_result simdjson_result::is_negative() noexcept { ++ if (error()) { return error(); } ++ return first.is_negative(); ++} ++simdjson_inline simdjson_result simdjson_result::is_integer() noexcept { ++ if (error()) { return error(); } ++ return first.is_integer(); ++} ++simdjson_inline simdjson_result simdjson_result::get_number_type() noexcept { ++ if (error()) { return error(); } ++ return first.get_number_type(); ++} ++simdjson_inline simdjson_result simdjson_result::get_number() noexcept { ++ if (error()) { return error(); } ++ return first.get_number(); ++} ++#if SIMDJSON_EXCEPTIONS ++simdjson_inline simdjson_result::operator SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand::array() noexcept(false) { ++ if (error()) { throw simdjson_error(error()); } ++ return first; ++} ++simdjson_inline simdjson_result::operator SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand::object() noexcept(false) { ++ if (error()) { throw simdjson_error(error()); } ++ return first; ++} ++simdjson_inline simdjson_result::operator uint64_t() noexcept(false) { ++ if (error()) { throw simdjson_error(error()); } ++ return first; ++} ++simdjson_inline simdjson_result::operator int64_t() noexcept(false) { ++ if (error()) { throw simdjson_error(error()); } ++ return first; ++} ++simdjson_inline simdjson_result::operator double() noexcept(false) { ++ if (error()) { throw simdjson_error(error()); } ++ return first; ++} ++simdjson_inline simdjson_result::operator std::string_view() noexcept(false) { ++ if (error()) { throw simdjson_error(error()); } ++ return first; ++} ++simdjson_inline simdjson_result::operator SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand::raw_json_string() noexcept(false) { ++ if (error()) { throw simdjson_error(error()); } ++ return first; ++} ++simdjson_inline simdjson_result::operator bool() noexcept(false) { ++ if (error()) { throw simdjson_error(error()); } ++ return first; ++} ++#endif ++ ++simdjson_inline simdjson_result simdjson_result::raw_json_token() noexcept { ++ if (error()) { return error(); } ++ return first.raw_json_token(); ++} ++ ++simdjson_inline simdjson_result simdjson_result::current_location() noexcept { ++ if (error()) { return error(); } ++ return first.current_location(); ++} ++ ++simdjson_inline simdjson_result simdjson_result::current_depth() const noexcept { ++ if (error()) { return error(); } ++ return first.current_depth(); ++} ++ ++simdjson_inline simdjson_result simdjson_result::at_pointer(std::string_view json_pointer) noexcept { ++ if (error()) { return error(); } ++ return first.at_pointer(json_pointer); ++} ++ ++} // namespace simdjson ++/* end file include/simdjson/generic/ondemand/value-inl.h */ ++/* begin file include/simdjson/generic/ondemand/field-inl.h */ ++namespace simdjson { ++namespace SIMDJSON_BUILTIN_IMPLEMENTATION { ++namespace ondemand { ++ ++// clang 6 doesn't think the default constructor can be noexcept, so we make it explicit ++simdjson_inline field::field() noexcept : std::pair() {} ++ ++simdjson_inline field::field(raw_json_string key, ondemand::value &&value) noexcept ++ : std::pair(key, std::forward(value)) ++{ ++} ++ ++simdjson_inline simdjson_result field::start(value_iterator &parent_iter) noexcept { ++ raw_json_string key; ++ SIMDJSON_TRY( parent_iter.field_key().get(key) ); ++ SIMDJSON_TRY( parent_iter.field_value() ); ++ return field::start(parent_iter, key); ++} ++ ++simdjson_inline simdjson_result field::start(const value_iterator &parent_iter, raw_json_string key) noexcept { ++ return field(key, parent_iter.child()); ++} ++ ++simdjson_inline simdjson_warn_unused simdjson_result field::unescaped_key() noexcept { ++ SIMDJSON_ASSUME(first.buf != nullptr); // We would like to call .alive() but Visual Studio won't let us. ++ simdjson_result answer = first.unescape(second.iter.json_iter()); ++ first.consume(); ++ return answer; ++} ++ ++simdjson_inline raw_json_string field::key() const noexcept { ++ SIMDJSON_ASSUME(first.buf != nullptr); // We would like to call .alive() by Visual Studio won't let us. ++ return first; ++} ++ ++simdjson_inline value &field::value() & noexcept { ++ return second; ++} ++ ++simdjson_inline value field::value() && noexcept { ++ return std::forward(*this).second; ++} ++ ++} // namespace ondemand ++} // namespace SIMDJSON_BUILTIN_IMPLEMENTATION ++} // namespace simdjson ++ ++namespace simdjson { ++ ++simdjson_inline simdjson_result::simdjson_result( ++ SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand::field &&value ++) noexcept : ++ implementation_simdjson_result_base( ++ std::forward(value) ++ ) ++{ ++} ++simdjson_inline simdjson_result::simdjson_result( ++ error_code error ++) noexcept : ++ implementation_simdjson_result_base(error) ++{ ++} ++ ++simdjson_inline simdjson_result simdjson_result::key() noexcept { ++ if (error()) { return error(); } ++ return first.key(); ++} ++simdjson_inline simdjson_result simdjson_result::unescaped_key() noexcept { ++ if (error()) { return error(); } ++ return first.unescaped_key(); ++} ++simdjson_inline simdjson_result simdjson_result::value() noexcept { ++ if (error()) { return error(); } ++ return std::move(first.value()); ++} ++ ++} // namespace simdjson ++/* end file include/simdjson/generic/ondemand/field-inl.h */ ++/* begin file include/simdjson/generic/ondemand/object-inl.h */ ++namespace simdjson { ++namespace SIMDJSON_BUILTIN_IMPLEMENTATION { ++namespace ondemand { ++ ++simdjson_inline simdjson_result object::find_field_unordered(const std::string_view key) & noexcept { ++ bool has_value; ++ SIMDJSON_TRY( iter.find_field_unordered_raw(key).get(has_value) ); ++ if (!has_value) { return NO_SUCH_FIELD; } ++ return value(iter.child()); ++} ++simdjson_inline simdjson_result object::find_field_unordered(const std::string_view key) && noexcept { ++ bool has_value; ++ SIMDJSON_TRY( iter.find_field_unordered_raw(key).get(has_value) ); ++ if (!has_value) { return NO_SUCH_FIELD; } ++ return value(iter.child()); ++} ++simdjson_inline simdjson_result object::operator[](const std::string_view key) & noexcept { ++ return find_field_unordered(key); ++} ++simdjson_inline simdjson_result object::operator[](const std::string_view key) && noexcept { ++ return std::forward(*this).find_field_unordered(key); ++} ++simdjson_inline simdjson_result object::find_field(const std::string_view key) & noexcept { ++ bool has_value; ++ SIMDJSON_TRY( iter.find_field_raw(key).get(has_value) ); ++ if (!has_value) { return NO_SUCH_FIELD; } ++ return value(iter.child()); ++} ++simdjson_inline simdjson_result object::find_field(const std::string_view key) && noexcept { ++ bool has_value; ++ SIMDJSON_TRY( iter.find_field_raw(key).get(has_value) ); ++ if (!has_value) { return NO_SUCH_FIELD; } ++ return value(iter.child()); ++} ++ ++simdjson_inline simdjson_result object::start(value_iterator &iter) noexcept { ++ SIMDJSON_TRY( iter.start_object().error() ); ++ return object(iter); ++} ++simdjson_inline simdjson_result object::start_root(value_iterator &iter) noexcept { ++ SIMDJSON_TRY( iter.start_root_object().error() ); ++ return object(iter); ++} ++simdjson_inline error_code object::consume() noexcept { ++ if(iter.is_at_key()) { ++ /** ++ * whenever you are pointing at a key, calling skip_child() is ++ * unsafe because you will hit a string and you will assume that ++ * it is string value, and this mistake will lead you to make bad ++ * depth computation. ++ */ ++ /** ++ * We want to 'consume' the key. We could really ++ * just do _json_iter->return_current_and_advance(); at this ++ * point, but, for clarity, we will use the high-level API to ++ * eat the key. We assume that the compiler optimizes away ++ * most of the work. ++ */ ++ simdjson_unused raw_json_string actual_key; ++ auto error = iter.field_key().get(actual_key); ++ if (error) { iter.abandon(); return error; }; ++ // Let us move to the value while we are at it. ++ if ((error = iter.field_value())) { iter.abandon(); return error; } ++ } ++ auto error_skip = iter.json_iter().skip_child(iter.depth()-1); ++ if(error_skip) { iter.abandon(); } ++ return error_skip; ++} ++ ++simdjson_inline simdjson_result object::raw_json() noexcept { ++ const uint8_t * starting_point{iter.peek_start()}; ++ auto error = consume(); ++ if(error) { return error; } ++ const uint8_t * final_point{iter._json_iter->peek(0)}; ++ return std::string_view(reinterpret_cast(starting_point), size_t(final_point - starting_point)); ++} ++ ++simdjson_inline simdjson_result object::started(value_iterator &iter) noexcept { ++ SIMDJSON_TRY( iter.started_object().error() ); ++ return object(iter); ++} ++ ++simdjson_inline object object::resume(const value_iterator &iter) noexcept { ++ return iter; ++} ++ ++simdjson_inline object::object(const value_iterator &_iter) noexcept ++ : iter{_iter} ++{ ++} ++ ++simdjson_inline simdjson_result object::begin() noexcept { ++#if SIMDJSON_DEVELOPMENT_CHECKS ++ if (!iter.is_at_iterator_start()) { return OUT_OF_ORDER_ITERATION; } ++#endif ++ return object_iterator(iter); ++} ++simdjson_inline simdjson_result object::end() noexcept { ++ return object_iterator(iter); ++} ++ ++inline simdjson_result object::at_pointer(std::string_view json_pointer) noexcept { ++ if (json_pointer[0] != '/') { return INVALID_JSON_POINTER; } ++ json_pointer = json_pointer.substr(1); ++ size_t slash = json_pointer.find('/'); ++ std::string_view key = json_pointer.substr(0, slash); ++ // Grab the child with the given key ++ simdjson_result child; ++ ++ // If there is an escape character in the key, unescape it and then get the child. ++ size_t escape = key.find('~'); ++ if (escape != std::string_view::npos) { ++ // Unescape the key ++ std::string unescaped(key); ++ do { ++ switch (unescaped[escape+1]) { ++ case '0': ++ unescaped.replace(escape, 2, "~"); ++ break; ++ case '1': ++ unescaped.replace(escape, 2, "/"); ++ break; ++ default: ++ return INVALID_JSON_POINTER; // "Unexpected ~ escape character in JSON pointer"); ++ } ++ escape = unescaped.find('~', escape+1); ++ } while (escape != std::string::npos); ++ child = find_field(unescaped); // Take note find_field does not unescape keys when matching ++ } else { ++ child = find_field(key); ++ } ++ if(child.error()) { ++ return child; // we do not continue if there was an error ++ } ++ // If there is a /, we have to recurse and look up more of the path ++ if (slash != std::string_view::npos) { ++ child = child.at_pointer(json_pointer.substr(slash)); ++ } ++ return child; ++} ++ ++simdjson_inline simdjson_result object::count_fields() & noexcept { ++ size_t count{0}; ++ // Important: we do not consume any of the values. ++ for(simdjson_unused auto v : *this) { count++; } ++ // The above loop will always succeed, but we want to report errors. ++ if(iter.error()) { return iter.error(); } ++ // We need to move back at the start because we expect users to iterate through ++ // the object after counting the number of elements. ++ iter.reset_object(); ++ return count; ++} ++ ++simdjson_inline simdjson_result object::is_empty() & noexcept { ++ bool is_not_empty; ++ auto error = iter.reset_object().get(is_not_empty); ++ if(error) { return error; } ++ return !is_not_empty; ++} ++ ++simdjson_inline simdjson_result object::reset() & noexcept { ++ return iter.reset_object(); ++} ++ ++} // namespace ondemand ++} // namespace SIMDJSON_BUILTIN_IMPLEMENTATION ++} // namespace simdjson ++ ++namespace simdjson { ++ ++simdjson_inline simdjson_result::simdjson_result(SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand::object &&value) noexcept ++ : implementation_simdjson_result_base(std::forward(value)) {} ++simdjson_inline simdjson_result::simdjson_result(error_code error) noexcept ++ : implementation_simdjson_result_base(error) {} ++ ++simdjson_inline simdjson_result simdjson_result::begin() noexcept { ++ if (error()) { return error(); } ++ return first.begin(); ++} ++simdjson_inline simdjson_result simdjson_result::end() noexcept { ++ if (error()) { return error(); } ++ return first.end(); ++} ++simdjson_inline simdjson_result simdjson_result::find_field_unordered(std::string_view key) & noexcept { ++ if (error()) { return error(); } ++ return first.find_field_unordered(key); ++} ++simdjson_inline simdjson_result simdjson_result::find_field_unordered(std::string_view key) && noexcept { ++ if (error()) { return error(); } ++ return std::forward(first).find_field_unordered(key); ++} ++simdjson_inline simdjson_result simdjson_result::operator[](std::string_view key) & noexcept { ++ if (error()) { return error(); } ++ return first[key]; ++} ++simdjson_inline simdjson_result simdjson_result::operator[](std::string_view key) && noexcept { ++ if (error()) { return error(); } ++ return std::forward(first)[key]; ++} ++simdjson_inline simdjson_result simdjson_result::find_field(std::string_view key) & noexcept { ++ if (error()) { return error(); } ++ return first.find_field(key); ++} ++simdjson_inline simdjson_result simdjson_result::find_field(std::string_view key) && noexcept { ++ if (error()) { return error(); } ++ return std::forward(first).find_field(key); ++} ++ ++simdjson_inline simdjson_result simdjson_result::at_pointer(std::string_view json_pointer) noexcept { ++ if (error()) { return error(); } ++ return first.at_pointer(json_pointer); ++} ++ ++inline simdjson_result simdjson_result::reset() noexcept { ++ if (error()) { return error(); } ++ return first.reset(); ++} ++ ++inline simdjson_result simdjson_result::is_empty() noexcept { ++ if (error()) { return error(); } ++ return first.is_empty(); ++} ++ ++simdjson_inline simdjson_result simdjson_result::count_fields() & noexcept { ++ if (error()) { return error(); } ++ return first.count_fields(); ++} ++ ++} // namespace simdjson ++/* end file include/simdjson/generic/ondemand/object-inl.h */ ++/* begin file include/simdjson/generic/ondemand/parser-inl.h */ ++namespace simdjson { ++namespace SIMDJSON_BUILTIN_IMPLEMENTATION { ++namespace ondemand { ++ ++simdjson_inline parser::parser(size_t max_capacity) noexcept ++ : _max_capacity{max_capacity} { ++} ++ ++simdjson_warn_unused simdjson_inline error_code parser::allocate(size_t new_capacity, size_t new_max_depth) noexcept { ++ if (new_capacity > max_capacity()) { return CAPACITY; } ++ if (string_buf && new_capacity == capacity() && new_max_depth == max_depth()) { return SUCCESS; } ++ ++ // string_capacity copied from document::allocate ++ _capacity = 0; ++ size_t string_capacity = SIMDJSON_ROUNDUP_N(5 * new_capacity / 3 + SIMDJSON_PADDING, 64); ++ string_buf.reset(new (std::nothrow) uint8_t[string_capacity]); ++#if SIMDJSON_DEVELOPMENT_CHECKS ++ start_positions.reset(new (std::nothrow) token_position[new_max_depth]); ++#endif ++ if (implementation) { ++ SIMDJSON_TRY( implementation->set_capacity(new_capacity) ); ++ SIMDJSON_TRY( implementation->set_max_depth(new_max_depth) ); ++ } else { ++ SIMDJSON_TRY( simdjson::get_active_implementation()->create_dom_parser_implementation(new_capacity, new_max_depth, implementation) ); ++ } ++ _capacity = new_capacity; ++ _max_depth = new_max_depth; ++ return SUCCESS; ++} ++ ++simdjson_warn_unused simdjson_inline simdjson_result parser::iterate(padded_string_view json) & noexcept { ++ if (json.padding() < SIMDJSON_PADDING) { return INSUFFICIENT_PADDING; } ++ ++ // Allocate if needed ++ if (capacity() < json.length() || !string_buf) { ++ SIMDJSON_TRY( allocate(json.length(), max_depth()) ); ++ } ++ ++ // Run stage 1. ++ SIMDJSON_TRY( implementation->stage1(reinterpret_cast(json.data()), json.length(), stage1_mode::regular) ); ++ return document::start({ reinterpret_cast(json.data()), this }); ++} ++ ++simdjson_warn_unused simdjson_inline simdjson_result parser::iterate(const char *json, size_t len, size_t allocated) & noexcept { ++ return iterate(padded_string_view(json, len, allocated)); ++} ++ ++simdjson_warn_unused simdjson_inline simdjson_result parser::iterate(const uint8_t *json, size_t len, size_t allocated) & noexcept { ++ return iterate(padded_string_view(json, len, allocated)); ++} ++ ++simdjson_warn_unused simdjson_inline simdjson_result parser::iterate(std::string_view json, size_t allocated) & noexcept { ++ return iterate(padded_string_view(json, allocated)); ++} ++ ++simdjson_warn_unused simdjson_inline simdjson_result parser::iterate(const std::string &json) & noexcept { ++ return iterate(padded_string_view(json)); ++} ++ ++simdjson_warn_unused simdjson_inline simdjson_result parser::iterate(const simdjson_result &result) & noexcept { ++ // We don't presently have a way to temporarily get a const T& from a simdjson_result without throwing an exception ++ SIMDJSON_TRY( result.error() ); ++ padded_string_view json = result.value_unsafe(); ++ return iterate(json); ++} ++ ++simdjson_warn_unused simdjson_inline simdjson_result parser::iterate(const simdjson_result &result) & noexcept { ++ // We don't presently have a way to temporarily get a const T& from a simdjson_result without throwing an exception ++ SIMDJSON_TRY( result.error() ); ++ const padded_string &json = result.value_unsafe(); ++ return iterate(json); ++} ++ ++simdjson_warn_unused simdjson_inline simdjson_result parser::iterate_raw(padded_string_view json) & noexcept { ++ if (json.padding() < SIMDJSON_PADDING) { return INSUFFICIENT_PADDING; } ++ ++ // Allocate if needed ++ if (capacity() < json.length()) { ++ SIMDJSON_TRY( allocate(json.length(), max_depth()) ); ++ } ++ ++ // Run stage 1. ++ SIMDJSON_TRY( implementation->stage1(reinterpret_cast(json.data()), json.length(), stage1_mode::regular) ); ++ return json_iterator(reinterpret_cast(json.data()), this); ++} ++ ++inline simdjson_result parser::iterate_many(const uint8_t *buf, size_t len, size_t batch_size) noexcept { ++ if(batch_size < MINIMAL_BATCH_SIZE) { batch_size = MINIMAL_BATCH_SIZE; } ++ return document_stream(*this, buf, len, batch_size); ++} ++inline simdjson_result parser::iterate_many(const char *buf, size_t len, size_t batch_size) noexcept { ++ return iterate_many(reinterpret_cast(buf), len, batch_size); ++} ++inline simdjson_result parser::iterate_many(const std::string &s, size_t batch_size) noexcept { ++ return iterate_many(s.data(), s.length(), batch_size); ++} ++inline simdjson_result parser::iterate_many(const padded_string &s, size_t batch_size) noexcept { ++ return iterate_many(s.data(), s.length(), batch_size); ++} ++ ++simdjson_inline size_t parser::capacity() const noexcept { ++ return _capacity; ++} ++simdjson_inline size_t parser::max_capacity() const noexcept { ++ return _max_capacity; ++} ++simdjson_inline size_t parser::max_depth() const noexcept { ++ return _max_depth; ++} ++ ++simdjson_inline void parser::set_max_capacity(size_t max_capacity) noexcept { ++ if(max_capacity < dom::MINIMAL_DOCUMENT_CAPACITY) { ++ _max_capacity = max_capacity; ++ } else { ++ _max_capacity = dom::MINIMAL_DOCUMENT_CAPACITY; ++ } ++} ++ ++simdjson_inline simdjson_warn_unused simdjson_result parser::unescape(raw_json_string in, uint8_t *&dst) const noexcept { ++ uint8_t *end = implementation->parse_string(in.buf, dst); ++ if (!end) { return STRING_ERROR; } ++ std::string_view result(reinterpret_cast(dst), end-dst); ++ dst = end; ++ return result; ++} ++ ++} // namespace ondemand ++} // namespace SIMDJSON_BUILTIN_IMPLEMENTATION ++} // namespace simdjson ++ ++namespace simdjson { ++ ++simdjson_inline simdjson_result::simdjson_result(SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand::parser &&value) noexcept ++ : implementation_simdjson_result_base(std::forward(value)) {} ++simdjson_inline simdjson_result::simdjson_result(error_code error) noexcept ++ : implementation_simdjson_result_base(error) {} ++ ++} // namespace simdjson ++/* end file include/simdjson/generic/ondemand/parser-inl.h */ ++/* begin file include/simdjson/generic/ondemand/document_stream-inl.h */ ++#include ++#include ++#include ++namespace simdjson { ++namespace SIMDJSON_BUILTIN_IMPLEMENTATION { ++namespace ondemand { ++ ++#ifdef SIMDJSON_THREADS_ENABLED ++ ++inline void stage1_worker::finish() { ++ // After calling "run" someone would call finish() to wait ++ // for the end of the processing. ++ // This function will wait until either the thread has done ++ // the processing or, else, the destructor has been called. ++ std::unique_lock lock(locking_mutex); ++ cond_var.wait(lock, [this]{return has_work == false;}); ++} ++ ++inline stage1_worker::~stage1_worker() { ++ // The thread may never outlive the stage1_worker instance ++ // and will always be stopped/joined before the stage1_worker ++ // instance is gone. ++ stop_thread(); ++} ++ ++inline void stage1_worker::start_thread() { ++ std::unique_lock lock(locking_mutex); ++ if(thread.joinable()) { ++ return; // This should never happen but we never want to create more than one thread. ++ } ++ thread = std::thread([this]{ ++ while(true) { ++ std::unique_lock thread_lock(locking_mutex); ++ // We wait for either "run" or "stop_thread" to be called. ++ cond_var.wait(thread_lock, [this]{return has_work || !can_work;}); ++ // If, for some reason, the stop_thread() method was called (i.e., the ++ // destructor of stage1_worker is called, then we want to immediately destroy ++ // the thread (and not do any more processing). ++ if(!can_work) { ++ break; ++ } ++ this->owner->stage1_thread_error = this->owner->run_stage1(*this->stage1_thread_parser, ++ this->_next_batch_start); ++ this->has_work = false; ++ // The condition variable call should be moved after thread_lock.unlock() for performance ++ // reasons but thread sanitizers may report it as a data race if we do. ++ // See https://stackoverflow.com/questions/35775501/c-should-condition-variable-be-notified-under-lock ++ cond_var.notify_one(); // will notify "finish" ++ thread_lock.unlock(); ++ } ++ } ++ ); ++} ++ ++ ++inline void stage1_worker::stop_thread() { ++ std::unique_lock lock(locking_mutex); ++ // We have to make sure that all locks can be released. ++ can_work = false; ++ has_work = false; ++ cond_var.notify_all(); ++ lock.unlock(); ++ if(thread.joinable()) { ++ thread.join(); ++ } ++} ++ ++inline void stage1_worker::run(document_stream * ds, parser * stage1, size_t next_batch_start) { ++ std::unique_lock lock(locking_mutex); ++ owner = ds; ++ _next_batch_start = next_batch_start; ++ stage1_thread_parser = stage1; ++ has_work = true; ++ // The condition variable call should be moved after thread_lock.unlock() for performance ++ // reasons but thread sanitizers may report it as a data race if we do. ++ // See https://stackoverflow.com/questions/35775501/c-should-condition-variable-be-notified-under-lock ++ cond_var.notify_one(); // will notify the thread lock that we have work ++ lock.unlock(); ++} ++ ++#endif // SIMDJSON_THREADS_ENABLED ++ ++simdjson_inline document_stream::document_stream( ++ ondemand::parser &_parser, ++ const uint8_t *_buf, ++ size_t _len, ++ size_t _batch_size ++) noexcept ++ : parser{&_parser}, ++ buf{_buf}, ++ len{_len}, ++ batch_size{_batch_size <= MINIMAL_BATCH_SIZE ? MINIMAL_BATCH_SIZE : _batch_size}, ++ error{SUCCESS} ++ #ifdef SIMDJSON_THREADS_ENABLED ++ , use_thread(_parser.threaded) // we need to make a copy because _parser.threaded can change ++ #endif ++{ ++#ifdef SIMDJSON_THREADS_ENABLED ++ if(worker.get() == nullptr) { ++ error = MEMALLOC; ++ } ++#endif ++} ++ ++simdjson_inline document_stream::document_stream() noexcept ++ : parser{nullptr}, ++ buf{nullptr}, ++ len{0}, ++ batch_size{0}, ++ error{UNINITIALIZED} ++ #ifdef SIMDJSON_THREADS_ENABLED ++ , use_thread(false) ++ #endif ++{ ++} ++ ++simdjson_inline document_stream::~document_stream() noexcept ++{ ++ #ifdef SIMDJSON_THREADS_ENABLED ++ worker.reset(); ++ #endif ++} ++ ++inline size_t document_stream::size_in_bytes() const noexcept { ++ return len; ++} ++ ++inline size_t document_stream::truncated_bytes() const noexcept { ++ if(error == CAPACITY) { return len - batch_start; } ++ return parser->implementation->structural_indexes[parser->implementation->n_structural_indexes] - parser->implementation->structural_indexes[parser->implementation->n_structural_indexes + 1]; ++} ++ ++simdjson_inline document_stream::iterator::iterator() noexcept ++ : stream{nullptr}, finished{true} { ++} ++ ++simdjson_inline document_stream::iterator::iterator(document_stream* _stream, bool is_end) noexcept ++ : stream{_stream}, finished{is_end} { ++} ++ ++simdjson_inline simdjson_result document_stream::iterator::operator*() noexcept { ++ //if(stream->error) { return stream->error; } ++ return simdjson_result(stream->doc, stream->error); ++} ++ ++simdjson_inline document_stream::iterator& document_stream::iterator::operator++() noexcept { ++ // If there is an error, then we want the iterator ++ // to be finished, no matter what. (E.g., we do not ++ // keep generating documents with errors, or go beyond ++ // a document with errors.) ++ // ++ // Users do not have to call "operator*()" when they use operator++, ++ // so we need to end the stream in the operator++ function. ++ // ++ // Note that setting finished = true is essential otherwise ++ // we would enter an infinite loop. ++ if (stream->error) { finished = true; } ++ // Note that stream->error() is guarded against error conditions ++ // (it will immediately return if stream->error casts to false). ++ // In effect, this next function does nothing when (stream->error) ++ // is true (hence the risk of an infinite loop). ++ stream->next(); ++ // If that was the last document, we're finished. ++ // It is the only type of error we do not want to appear ++ // in operator*. ++ if (stream->error == EMPTY) { finished = true; } ++ // If we had any other kind of error (not EMPTY) then we want ++ // to pass it along to the operator* and we cannot mark the result ++ // as "finished" just yet. ++ return *this; ++} ++ ++simdjson_inline bool document_stream::iterator::operator!=(const document_stream::iterator &other) const noexcept { ++ return finished != other.finished; ++} ++ ++simdjson_inline document_stream::iterator document_stream::begin() noexcept { ++ start(); ++ // If there are no documents, we're finished. ++ return iterator(this, error == EMPTY); ++} ++ ++simdjson_inline document_stream::iterator document_stream::end() noexcept { ++ return iterator(this, true); ++} ++ ++inline void document_stream::start() noexcept { ++ if (error) { return; } ++ error = parser->allocate(batch_size); ++ if (error) { return; } ++ // Always run the first stage 1 parse immediately ++ batch_start = 0; ++ error = run_stage1(*parser, batch_start); ++ while(error == EMPTY) { ++ // In exceptional cases, we may start with an empty block ++ batch_start = next_batch_start(); ++ if (batch_start >= len) { return; } ++ error = run_stage1(*parser, batch_start); ++ } ++ if (error) { return; } ++ doc_index = batch_start; ++ doc = document(json_iterator(&buf[batch_start], parser)); ++ doc.iter._streaming = true; ++ ++ #ifdef SIMDJSON_THREADS_ENABLED ++ if (use_thread && next_batch_start() < len) { ++ // Kick off the first thread on next batch if needed ++ error = stage1_thread_parser.allocate(batch_size); ++ if (error) { return; } ++ worker->start_thread(); ++ start_stage1_thread(); ++ if (error) { return; } ++ } ++ #endif // SIMDJSON_THREADS_ENABLED ++} ++ ++inline void document_stream::next() noexcept { ++ // We always enter at once once in an error condition. ++ if (error) { return; } ++ next_document(); ++ if (error) { return; } ++ auto cur_struct_index = doc.iter._root - parser->implementation->structural_indexes.get(); ++ doc_index = batch_start + parser->implementation->structural_indexes[cur_struct_index]; ++ ++ // Check if at end of structural indexes (i.e. at end of batch) ++ if(cur_struct_index >= static_cast(parser->implementation->n_structural_indexes)) { ++ error = EMPTY; ++ // Load another batch (if available) ++ while (error == EMPTY) { ++ batch_start = next_batch_start(); ++ if (batch_start >= len) { break; } ++ #ifdef SIMDJSON_THREADS_ENABLED ++ if(use_thread) { ++ load_from_stage1_thread(); ++ } else { ++ error = run_stage1(*parser, batch_start); ++ } ++ #else ++ error = run_stage1(*parser, batch_start); ++ #endif ++ /** ++ * Whenever we move to another window, we need to update all pointers to make ++ * it appear as if the input buffer started at the beginning of the window. ++ * ++ * Take this input: ++ * ++ * {"z":5} {"1":1,"2":2,"4":4} [7, 10, 9] [15, 11, 12, 13] [154, 110, 112, 1311] ++ * ++ * Say you process the following window... ++ * ++ * '{"z":5} {"1":1,"2":2,"4":4} [7, 10, 9]' ++ * ++ * When you do so, the json_iterator has a pointer at the beginning of the memory region ++ * (pointing at the beginning of '{"z"...'. ++ * ++ * When you move to the window that starts at... ++ * ++ * '[7, 10, 9] [15, 11, 12, 13] ... ++ * ++ * then it is not sufficient to just run stage 1. You also need to re-anchor the ++ * json_iterator so that it believes we are starting at '[7, 10, 9]...'. ++ * ++ * Under the DOM front-end, this gets done automatically because the parser owns ++ * the pointer the data, and when you call stage1 and then stage2 on the same ++ * parser, then stage2 will run on the pointer acquired by stage1. ++ * ++ * That is, stage1 calls "this->buf = _buf" so the parser remembers the buffer that ++ * we used. But json_iterator has no callback when stage1 is called on the parser. ++ * In fact, I think that the parser is unaware of json_iterator. ++ * ++ * ++ * So we need to re-anchor the json_iterator after each call to stage 1 so that ++ * all of the pointers are in sync. ++ */ ++ doc.iter = json_iterator(&buf[batch_start], parser); ++ doc.iter._streaming = true; ++ /** ++ * End of resync. ++ */ ++ ++ if (error) { continue; } // If the error was EMPTY, we may want to load another batch. ++ doc_index = batch_start; ++ } ++ } ++} ++ ++inline void document_stream::next_document() noexcept { ++ // Go to next place where depth=0 (document depth) ++ error = doc.iter.skip_child(0); ++ if (error) { return; } ++ // Always set depth=1 at the start of document ++ doc.iter._depth = 1; ++ // Resets the string buffer at the beginning, thus invalidating the strings. ++ doc.iter._string_buf_loc = parser->string_buf.get(); ++ doc.iter._root = doc.iter.position(); ++} ++ ++inline size_t document_stream::next_batch_start() const noexcept { ++ return batch_start + parser->implementation->structural_indexes[parser->implementation->n_structural_indexes]; ++} ++ ++inline error_code document_stream::run_stage1(ondemand::parser &p, size_t _batch_start) noexcept { ++ // This code only updates the structural index in the parser, it does not update any json_iterator ++ // instance. ++ size_t remaining = len - _batch_start; ++ if (remaining <= batch_size) { ++ return p.implementation->stage1(&buf[_batch_start], remaining, stage1_mode::streaming_final); ++ } else { ++ return p.implementation->stage1(&buf[_batch_start], batch_size, stage1_mode::streaming_partial); ++ } ++} ++ ++simdjson_inline size_t document_stream::iterator::current_index() const noexcept { ++ return stream->doc_index; ++} ++ ++simdjson_inline std::string_view document_stream::iterator::source() const noexcept { ++ auto depth = stream->doc.iter.depth(); ++ auto cur_struct_index = stream->doc.iter._root - stream->parser->implementation->structural_indexes.get(); ++ ++ // If at root, process the first token to determine if scalar value ++ if (stream->doc.iter.at_root()) { ++ switch (stream->buf[stream->batch_start + stream->parser->implementation->structural_indexes[cur_struct_index]]) { ++ case '{': case '[': // Depth=1 already at start of document ++ break; ++ case '}': case ']': ++ depth--; ++ break; ++ default: // Scalar value document ++ // TODO: Remove any trailing whitespaces ++ // This returns a string spanning from start of value to the beginning of the next document (excluded) ++ return std::string_view(reinterpret_cast(stream->buf) + current_index(), stream->parser->implementation->structural_indexes[++cur_struct_index] - current_index() - 1); ++ } ++ cur_struct_index++; ++ } ++ ++ while (cur_struct_index <= static_cast(stream->parser->implementation->n_structural_indexes)) { ++ switch (stream->buf[stream->batch_start + stream->parser->implementation->structural_indexes[cur_struct_index]]) { ++ case '{': case '[': ++ depth++; ++ break; ++ case '}': case ']': ++ depth--; ++ break; ++ } ++ if (depth == 0) { break; } ++ cur_struct_index++; ++ } ++ ++ return std::string_view(reinterpret_cast(stream->buf) + current_index(), stream->parser->implementation->structural_indexes[cur_struct_index] - current_index() + stream->batch_start + 1);; ++} ++ ++inline error_code document_stream::iterator::error() const noexcept { ++ return stream->error; ++} ++ ++#ifdef SIMDJSON_THREADS_ENABLED ++ ++inline void document_stream::load_from_stage1_thread() noexcept { ++ worker->finish(); ++ // Swap to the parser that was loaded up in the thread. Make sure the parser has ++ // enough memory to swap to, as well. ++ std::swap(stage1_thread_parser,*parser); ++ error = stage1_thread_error; ++ if (error) { return; } ++ ++ // If there's anything left, start the stage 1 thread! ++ if (next_batch_start() < len) { ++ start_stage1_thread(); ++ } ++} ++ ++inline void document_stream::start_stage1_thread() noexcept { ++ // we call the thread on a lambda that will update ++ // this->stage1_thread_error ++ // there is only one thread that may write to this value ++ // TODO this is NOT exception-safe. ++ this->stage1_thread_error = UNINITIALIZED; // In case something goes wrong, make sure it's an error ++ size_t _next_batch_start = this->next_batch_start(); ++ ++ worker->run(this, & this->stage1_thread_parser, _next_batch_start); ++} ++ ++#endif // SIMDJSON_THREADS_ENABLED ++ ++} // namespace ondemand ++} // namespace SIMDJSON_BUILTIN_IMPLEMENTATION ++} // namespace simdjson ++ ++namespace simdjson { ++ ++simdjson_inline simdjson_result::simdjson_result( ++ error_code error ++) noexcept : ++ implementation_simdjson_result_base(error) ++{ ++} ++simdjson_inline simdjson_result::simdjson_result( ++ SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand::document_stream &&value ++) noexcept : ++ implementation_simdjson_result_base( ++ std::forward(value) ++ ) ++{ ++} ++ ++} ++/* end file include/simdjson/generic/ondemand/document_stream-inl.h */ ++/* begin file include/simdjson/generic/ondemand/serialization-inl.h */ ++ ++ ++namespace simdjson { ++ ++inline std::string_view trim(const std::string_view str) noexcept { ++ // We can almost surely do better by rolling our own find_first_not_of function. ++ size_t first = str.find_first_not_of(" \t\n\r"); ++ // If we have the empty string (just white space), then no trimming is possible, and ++ // we return the empty string_view. ++ if (std::string_view::npos == first) { return std::string_view(); } ++ size_t last = str.find_last_not_of(" \t\n\r"); ++ return str.substr(first, (last - first + 1)); ++} ++ ++ ++inline simdjson_result to_json_string(SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand::document& x) noexcept { ++ std::string_view v; ++ auto error = x.raw_json().get(v); ++ if(error) {return error; } ++ return trim(v); ++} ++ ++inline simdjson_result to_json_string(SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand::document_reference& x) noexcept { ++ std::string_view v; ++ auto error = x.raw_json().get(v); ++ if(error) {return error; } ++ return trim(v); ++} ++ ++inline simdjson_result to_json_string(SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand::value& x) noexcept { ++ /** ++ * If we somehow receive a value that has already been consumed, ++ * then the following code could be in trouble. E.g., we create ++ * an array as needed, but if an array was already created, then ++ * it could be bad. ++ */ ++ using namespace SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand; ++ SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand::json_type t; ++ auto error = x.type().get(t); ++ if(error != SUCCESS) { return error; } ++ switch (t) ++ { ++ case json_type::array: ++ { ++ SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand::array array; ++ error = x.get_array().get(array); ++ if(error) { return error; } ++ return to_json_string(array); ++ } ++ case json_type::object: ++ { ++ SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand::object object; ++ error = x.get_object().get(object); ++ if(error) { return error; } ++ return to_json_string(object); ++ } ++ default: ++ return trim(x.raw_json_token()); ++ } ++} ++ ++inline simdjson_result to_json_string(SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand::object& x) noexcept { ++ std::string_view v; ++ auto error = x.raw_json().get(v); ++ if(error) {return error; } ++ return trim(v); ++} ++ ++inline simdjson_result to_json_string(SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand::array& x) noexcept { ++ std::string_view v; ++ auto error = x.raw_json().get(v); ++ if(error) {return error; } ++ return trim(v); ++} ++ ++inline simdjson_result to_json_string(simdjson_result x) { ++ if (x.error()) { return x.error(); } ++ return to_json_string(x.value_unsafe()); ++} ++ ++inline simdjson_result to_json_string(simdjson_result x) { ++ if (x.error()) { return x.error(); } ++ return to_json_string(x.value_unsafe()); ++} ++ ++inline simdjson_result to_json_string(simdjson_result x) { ++ if (x.error()) { return x.error(); } ++ return to_json_string(x.value_unsafe()); ++} ++ ++inline simdjson_result to_json_string(simdjson_result x) { ++ if (x.error()) { return x.error(); } ++ return to_json_string(x.value_unsafe()); ++} ++ ++inline simdjson_result to_json_string(simdjson_result x) { ++ if (x.error()) { return x.error(); } ++ return to_json_string(x.value_unsafe()); ++} ++} // namespace simdjson ++ ++namespace simdjson { namespace SIMDJSON_BUILTIN_IMPLEMENTATION { namespace ondemand { ++ ++#if SIMDJSON_EXCEPTIONS ++inline std::ostream& operator<<(std::ostream& out, simdjson::SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand::value x) { ++ std::string_view v; ++ auto error = simdjson::to_json_string(x).get(v); ++ if(error == simdjson::SUCCESS) { ++ return (out << v); ++ } else { ++ throw simdjson::simdjson_error(error); ++ } ++} ++inline std::ostream& operator<<(std::ostream& out, simdjson::simdjson_result x) { ++ if (x.error()) { throw simdjson::simdjson_error(x.error()); } ++ return (out << x.value()); ++} ++#else ++inline std::ostream& operator<<(std::ostream& out, simdjson::SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand::value x) { ++ std::string_view v; ++ auto error = simdjson::to_json_string(x).get(v); ++ if(error == simdjson::SUCCESS) { ++ return (out << v); ++ } else { ++ return (out << error); ++ } ++} ++#endif ++ ++#if SIMDJSON_EXCEPTIONS ++inline std::ostream& operator<<(std::ostream& out, simdjson::SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand::array value) { ++ std::string_view v; ++ auto error = simdjson::to_json_string(value).get(v); ++ if(error == simdjson::SUCCESS) { ++ return (out << v); ++ } else { ++ throw simdjson::simdjson_error(error); ++ } ++} ++inline std::ostream& operator<<(std::ostream& out, simdjson::simdjson_result x) { ++ if (x.error()) { throw simdjson::simdjson_error(x.error()); } ++ return (out << x.value()); ++} ++#else ++inline std::ostream& operator<<(std::ostream& out, simdjson::SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand::array value) { ++ std::string_view v; ++ auto error = simdjson::to_json_string(value).get(v); ++ if(error == simdjson::SUCCESS) { ++ return (out << v); ++ } else { ++ return (out << error); ++ } ++} ++#endif ++ ++#if SIMDJSON_EXCEPTIONS ++inline std::ostream& operator<<(std::ostream& out, simdjson::SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand::document& value) { ++ std::string_view v; ++ auto error = simdjson::to_json_string(value).get(v); ++ if(error == simdjson::SUCCESS) { ++ return (out << v); ++ } else { ++ throw simdjson::simdjson_error(error); ++ } ++} ++inline std::ostream& operator<<(std::ostream& out, simdjson::SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand::document_reference& value) { ++ std::string_view v; ++ auto error = simdjson::to_json_string(value).get(v); ++ if(error == simdjson::SUCCESS) { ++ return (out << v); ++ } else { ++ throw simdjson::simdjson_error(error); ++ } ++} ++inline std::ostream& operator<<(std::ostream& out, simdjson::simdjson_result&& x) { ++ if (x.error()) { throw simdjson::simdjson_error(x.error()); } ++ return (out << x.value()); ++} ++inline std::ostream& operator<<(std::ostream& out, simdjson::simdjson_result&& x) { ++ if (x.error()) { throw simdjson::simdjson_error(x.error()); } ++ return (out << x.value()); ++} ++#else ++inline std::ostream& operator<<(std::ostream& out, simdjson::SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand::document& value) { ++ std::string_view v; ++ auto error = simdjson::to_json_string(value).get(v); ++ if(error == simdjson::SUCCESS) { ++ return (out << v); ++ } else { ++ return (out << error); ++ } ++} ++#endif ++ ++#if SIMDJSON_EXCEPTIONS ++inline std::ostream& operator<<(std::ostream& out, simdjson::SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand::object value) { ++ std::string_view v; ++ auto error = simdjson::to_json_string(value).get(v); ++ if(error == simdjson::SUCCESS) { ++ return (out << v); ++ } else { ++ throw simdjson::simdjson_error(error); ++ } ++} ++inline std::ostream& operator<<(std::ostream& out, simdjson::simdjson_result x) { ++ if (x.error()) { throw simdjson::simdjson_error(x.error()); } ++ return (out << x.value()); ++} ++#else ++inline std::ostream& operator<<(std::ostream& out, simdjson::SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand::object value) { ++ std::string_view v; ++ auto error = simdjson::to_json_string(value).get(v); ++ if(error == simdjson::SUCCESS) { ++ return (out << v); ++ } else { ++ return (out << error); ++ } ++} ++#endif ++}}} // namespace simdjson::SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand ++/* end file include/simdjson/generic/ondemand/serialization-inl.h */ ++/* end file include/simdjson/generic/ondemand-inl.h */ ++ ++ ++namespace simdjson { ++ /** ++ * Represents the best statically linked simdjson implementation that can be used by the compiling ++ * program. ++ * ++ * Detects what options the program is compiled against, and picks the minimum implementation that ++ * will work on any computer that can run the program. For example, if you compile with g++ ++ * -march=westmere, it will pick the westmere implementation. The haswell implementation will ++ * still be available, and can be selected at runtime, but the builtin implementation (and any ++ * code that uses it) will use westmere. ++ */ ++ namespace builtin = SIMDJSON_BUILTIN_IMPLEMENTATION; ++ /** ++ * @copydoc simdjson::SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand ++ */ ++ namespace ondemand = SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand; ++ /** ++ * Function which returns a pointer to an implementation matching the "builtin" implementation. ++ * The builtin implementation is the best statically linked simdjson implementation that can be used by the compiling ++ * program. If you compile with g++ -march=haswell, this will return the haswell implementation. ++ * It is handy to be able to check what builtin was used: builtin_implementation()->name(). ++ */ ++ const implementation * builtin_implementation(); ++} // namespace simdjson ++ ++#endif // SIMDJSON_BUILTIN_H ++/* end file include/simdjson/builtin.h */ ++ ++#endif // SIMDJSON_H ++/* end file include/simdjson.h */ \ No newline at end of file diff --git a/src/appTypes/Wallet.ts b/src/appTypes/Wallet.ts index d8d6a526d..dea3c44ee 100644 --- a/src/appTypes/Wallet.ts +++ b/src/appTypes/Wallet.ts @@ -1,4 +1,5 @@ export interface WalletMetadata { + _id?: string; name: string; mnemonic: string; newMnemonic?: string; diff --git a/src/appTypes/database/DatabaseTable.ts b/src/appTypes/database/DatabaseTable.ts index aacffd446..b833b166a 100644 --- a/src/appTypes/database/DatabaseTable.ts +++ b/src/appTypes/database/DatabaseTable.ts @@ -1,3 +1,3 @@ export enum DatabaseTable { - Notifications = 'notifications' + Wallets = 'wallets' } diff --git a/src/database/Database.ts b/src/database/Database.ts new file mode 100644 index 000000000..bcd4c1e5a --- /dev/null +++ b/src/database/Database.ts @@ -0,0 +1,20 @@ +import { database } from './main'; +import { Database as WDB } from '@nozbe/watermelondb'; +class Database { + db: WDB | undefined; + + constructor() { + this.init(); + } + + private init() { + this.db = database; + } + + getDatabase() { + if (this.db) return this.db; + return database; + } +} + +export default new Database(); diff --git a/src/database/index.ts b/src/database/index.ts new file mode 100644 index 000000000..eb7789c1a --- /dev/null +++ b/src/database/index.ts @@ -0,0 +1 @@ +export { default as Database } from './Database'; diff --git a/src/database/main.ts b/src/database/main.ts new file mode 100644 index 000000000..699178615 --- /dev/null +++ b/src/database/main.ts @@ -0,0 +1,18 @@ +import SQLiteAdapter from '@nozbe/watermelondb/adapters/sqlite'; +import { Database } from '@nozbe/watermelondb'; +import { WalletDBModel } from './models'; +import { schema } from './schemas'; +import { Platform } from 'react-native'; + +const adapter = new SQLiteAdapter({ + schema, + dbName: 'airado', + jsi: Platform.OS === 'ios', + // TODO (optional, but we should implement this method) + onSetUpError: () => null +}); + +export const database = new Database({ + adapter, + modelClasses: [WalletDBModel] +}); diff --git a/src/database/models/index.ts b/src/database/models/index.ts new file mode 100644 index 000000000..3c5958cf6 --- /dev/null +++ b/src/database/models/index.ts @@ -0,0 +1 @@ +export * from './wallet'; diff --git a/src/database/models/wallet.ts b/src/database/models/wallet.ts new file mode 100644 index 000000000..0cc03fb02 --- /dev/null +++ b/src/database/models/wallet.ts @@ -0,0 +1,26 @@ +import { DatabaseTable } from '@appTypes'; +import { Wallet } from '@models/Wallet'; +import { Model } from '@nozbe/watermelondb'; +import { text, date, readonly, field } from '@nozbe/watermelondb/decorators'; + +export class WalletDBModel extends Model { + static table = DatabaseTable.Wallets; + + // define fields + @text('name') name?: string; + @text('hash') hash?: string; + @text('mnemonic') mnemonic?: string; + @field('number') number?: number; + @readonly @date('created_at') createdAt?: Date; + + hydrate(): Wallet { + return { + _id: this.id, + hash: this.hash || '', + name: this.name || '', + mnemonic: this.mnemonic || '', + number: this.number || 0, + createdAt: this.createdAt ? new Date(this.createdAt) : new Date() + }; + } +} diff --git a/src/database/schemas/index.ts b/src/database/schemas/index.ts new file mode 100644 index 000000000..98c607e7c --- /dev/null +++ b/src/database/schemas/index.ts @@ -0,0 +1,7 @@ +import { appSchema } from '@nozbe/watermelondb'; +import { WalletTable } from './wallet'; + +export const schema = appSchema({ + version: 1, + tables: [WalletTable] +}); diff --git a/src/database/schemas/wallet.ts b/src/database/schemas/wallet.ts new file mode 100644 index 000000000..689856e85 --- /dev/null +++ b/src/database/schemas/wallet.ts @@ -0,0 +1,25 @@ +import { DatabaseTable } from '@appTypes'; +import { tableSchema } from '@nozbe/watermelondb'; + +export const WalletTable = tableSchema({ + name: DatabaseTable.Wallets, + columns: [ + { + name: 'name', + type: 'string' + }, + { + name: 'mnemonic', + type: 'string' + }, + { + name: 'hash', + type: 'string' + }, + { + name: 'number', + type: 'number' + }, + { name: 'created_at', type: 'number' } + ] +}); diff --git a/src/models/Wallet.ts b/src/models/Wallet.ts index 6df0c264a..ec87af66c 100644 --- a/src/models/Wallet.ts +++ b/src/models/Wallet.ts @@ -1,6 +1,7 @@ import { WalletMetadata } from '@appTypes'; export class Wallet { + _id: string; hash: string; name: string; mnemonic: string; @@ -8,6 +9,7 @@ export class Wallet { createdAt: Date; constructor(details: WalletMetadata) { + this._id = details._id || ''; this.hash = details.hash || ''; this.name = details.name; this.mnemonic = details.mnemonic; diff --git a/yarn.lock b/yarn.lock index b1115f163..ffdf08b9b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1362,7 +1362,7 @@ resolved "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz" integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA== -"@babel/runtime@^7.0.0", "@babel/runtime@^7.20.0", "@babel/runtime@^7.8.4": +"@babel/runtime@7.21.0", "@babel/runtime@^7.0.0", "@babel/runtime@^7.20.0", "@babel/runtime@^7.8.4": version "7.21.0" resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.0.tgz" integrity sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw== @@ -2216,6 +2216,11 @@ dependencies: dequal "^1.0.0" +"@morrowdigital/watermelondb-expo-plugin@^2.1.2": + version "2.1.2" + resolved "https://registry.yarnpkg.com/@morrowdigital/watermelondb-expo-plugin/-/watermelondb-expo-plugin-2.1.2.tgz#7c511d671cba68984d5b77a78d3b998bfb57a750" + integrity sha512-mN/CISFEoA4w9HH62gQ7hHefpoNEGSp3eEs8kRUwtNpumBEr1Of8EEN1sNGmfB0IhB62Rj5gbFC+eBVhas4YXQ== + "@neverdull-agency/expo-unlimited-secure-store@^1.0.10": version "1.0.10" resolved "https://registry.yarnpkg.com/@neverdull-agency/expo-unlimited-secure-store/-/expo-unlimited-secure-store-1.0.10.tgz#1e78b502257b267fc918a85eaa41aa01a46d2007" @@ -2249,6 +2254,37 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" +"@nozbe/simdjson@3.1.0-wmelon1": + version "3.1.0-wmelon1" + resolved "https://registry.yarnpkg.com/@nozbe/simdjson/-/simdjson-3.1.0-wmelon1.tgz#e02048b41d2b3662ddf1dc8c979a3a36fd389dfb" + integrity sha512-PQaHHQyvASrcrfzqkZ4ona43m0UjN81NuTWt6rJkOUePGDjxc8MNp2Q7jcod1CIdTsXJ13wRWeFbquwNfhpIQQ== + +"@nozbe/sqlite@3.40.1": + version "3.40.1" + resolved "https://registry.yarnpkg.com/@nozbe/sqlite/-/sqlite-3.40.1.tgz#4218074ce8c87c859465dd2db28cd4b2fc7192b9" + integrity sha512-uKJOW4sQi3neCmgKhqLr0IJKlb2y5q2p05U5CEDJrCxSyD2uVYvSdh7IMrPjF4sWtzc/Lnk462M4vde7Dn5NSw== + +"@nozbe/watermelondb@^0.26.0": + version "0.26.0" + resolved "https://registry.yarnpkg.com/@nozbe/watermelondb/-/watermelondb-0.26.0.tgz#0b7472a0b885a51237529c9cabc6227aec6c227e" + integrity sha512-MIbCIE7Djyn5gNmO6OtAmf7IJHuFq2biKSDzK2ymMhHSJE5L5QjZPnrv3UFoGdTfEPUUGfi/87gwWwZecGiT3A== + dependencies: + "@babel/runtime" "7.21.0" + "@nozbe/simdjson" "3.1.0-wmelon1" + "@nozbe/sqlite" "3.40.1" + "@nozbe/with-observables" "^1.5.0-2" + hoist-non-react-statics "^3.3.2" + lokijs "npm:@nozbe/lokijs@1.5.12-wmelon6" + rxjs "^7.8.0" + sql-escape-string "^1.1.0" + +"@nozbe/with-observables@^1.5.0-2", "@nozbe/with-observables@^1.6.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@nozbe/with-observables/-/with-observables-1.6.0.tgz#498bcabcf0106f8adc135fd1ed7a77e5c95ad7a0" + integrity sha512-X/qGRBrmXLBVP3pqGQKD461UNx4sKfNoKWe4dlM/Gvtd12BOmv+nYOxw8PXiUr28yXxVYi03LpwDBd+JFo1Adg== + dependencies: + hoist-non-react-statics "^3.3.2" + "@npmcli/fs@^1.0.0": version "1.1.1" resolved "https://registry.npmjs.org/@npmcli/fs/-/fs-1.1.1.tgz" @@ -5980,7 +6016,7 @@ hermes-profile-transformer@^0.0.6: dependencies: source-map "^0.7.3" -hoist-non-react-statics@^3.3.0: +hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2: version "3.3.2" resolved "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz" integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== @@ -7464,6 +7500,11 @@ logkitty@^0.7.1: dayjs "^1.8.15" yargs "^15.1.0" +"lokijs@npm:@nozbe/lokijs@1.5.12-wmelon6": + version "1.5.12-wmelon6" + resolved "https://registry.yarnpkg.com/@nozbe/lokijs/-/lokijs-1.5.12-wmelon6.tgz#e457d934d614d5df80105c86314252a6e614df9b" + integrity sha512-GXsaqY8qTJ6xdCrGyno2t+ON2aj6PrUDdvhbrkxK/0Fp12C4FGvDg1wS+voLU9BANYHEnr7KRWfItDZnQkjoAg== + loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0: version "1.4.0" resolved "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz" @@ -9854,6 +9895,11 @@ sprintf-js@~1.0.2: resolved "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz" integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== +sql-escape-string@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/sql-escape-string/-/sql-escape-string-1.1.0.tgz#fe744b8514868c0eb4bfb9e4a989271d40f30eb9" + integrity sha512-/kqO4pLZSLfV0KsBM2xkVh2S3GbjJJone37d7gYwLyP0c+REh3vnmkhQ7VwNrX76igC0OhJWpTg0ukkdef9vvA== + ssri@^8.0.1: version "8.0.1" resolved "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz" From 759d47c44979d26d46b0b916a21c5a92a8a0a5fc Mon Sep 17 00:00:00 2001 From: JavidHaji-zada Date: Thu, 20 Jul 2023 15:01:37 +0400 Subject: [PATCH 010/509] save wallet to local db --- app.json | 6 ++ src/appTypes/Wallet.ts | 13 ++++- src/database/Database.ts | 35 +++++++++++- src/database/index.ts | 1 + src/database/main.ts | 4 +- src/database/models/wallet.ts | 45 +++++++++------ src/database/schemas/wallet.ts | 37 ++++++++++++- src/models/Wallet.ts | 55 ++++++++++++++++--- .../CreateWallet/CreateWalletStep2.tsx | 3 +- src/utils/wallet.ts | 1 + 10 files changed, 163 insertions(+), 37 deletions(-) diff --git a/app.json b/app.json index 49954b0e9..a2e348e60 100644 --- a/app.json +++ b/app.json @@ -63,6 +63,12 @@ "cameraPermission": "Allow $(PRODUCT_NAME) to access your camera." } ], + [ + "@morrowdigital/watermelondb-expo-plugin", + { + "databases": ["airdao_dev.db"] + } + ], [ "expo-build-properties", { diff --git a/src/appTypes/Wallet.ts b/src/appTypes/Wallet.ts index dea3c44ee..4e0c2aceb 100644 --- a/src/appTypes/Wallet.ts +++ b/src/appTypes/Wallet.ts @@ -1,11 +1,18 @@ export interface WalletMetadata { - _id?: string; name: string; mnemonic: string; - newMnemonic?: string; number: number; hash?: string; - createdAt?: number; + cashback?: string; + isBackedUp?: number; + isHideTransactionForFee?: number; + allowReplaceByFee?: number; + useLegacy?: number; + useUnconfirmed?: number; + isHd?: number; + isCreatedHere?: number; + toSendStatus?: number; + newMnemonic?: string; } export enum WalletInitSource { diff --git a/src/database/Database.ts b/src/database/Database.ts index bcd4c1e5a..c684503bc 100644 --- a/src/database/Database.ts +++ b/src/database/Database.ts @@ -1,13 +1,23 @@ +/* eslint-disable @typescript-eslint/no-non-null-assertion */ +import { DatabaseTable } from '@appTypes'; import { database } from './main'; import { Database as WDB } from '@nozbe/watermelondb'; + class Database { - db: WDB | undefined; + private db: WDB | undefined; constructor() { this.init(); } - private init() { + private async reset() { + if (!this.db) this.init(); + await this.db?.write(async () => { + await this.db?.unsafeResetDatabase(); + }); + } + + private async init() { this.db = database; } @@ -15,6 +25,27 @@ class Database { if (this.db) return this.db; return database; } + + async createModel(table: DatabaseTable, model: any) { + if (!this.db) this.init(); + try { + return await this.db!.write(async () => { + try { + return await this.db!.get(table).create((newModel) => { + for (const key in model) { + // @ts-ignore + newModel[key] = model[key]; + } + return newModel; + }); + } catch (error) { + // ignore + } + }); + } catch (error) { + // ignore + } + } } export default new Database(); diff --git a/src/database/index.ts b/src/database/index.ts index eb7789c1a..b09819862 100644 --- a/src/database/index.ts +++ b/src/database/index.ts @@ -1 +1,2 @@ export { default as Database } from './Database'; +export * from './models'; diff --git a/src/database/main.ts b/src/database/main.ts index 699178615..2f0b68d14 100644 --- a/src/database/main.ts +++ b/src/database/main.ts @@ -1,12 +1,12 @@ +import { Platform } from 'react-native'; import SQLiteAdapter from '@nozbe/watermelondb/adapters/sqlite'; import { Database } from '@nozbe/watermelondb'; import { WalletDBModel } from './models'; import { schema } from './schemas'; -import { Platform } from 'react-native'; const adapter = new SQLiteAdapter({ schema, - dbName: 'airado', + dbName: 'airdao_dev', jsi: Platform.OS === 'ios', // TODO (optional, but we should implement this method) onSetUpError: () => null diff --git a/src/database/models/wallet.ts b/src/database/models/wallet.ts index 0cc03fb02..9d8fb90b5 100644 --- a/src/database/models/wallet.ts +++ b/src/database/models/wallet.ts @@ -1,26 +1,35 @@ import { DatabaseTable } from '@appTypes'; -import { Wallet } from '@models/Wallet'; import { Model } from '@nozbe/watermelondb'; -import { text, date, readonly, field } from '@nozbe/watermelondb/decorators'; +import { text, field } from '@nozbe/watermelondb/decorators'; export class WalletDBModel extends Model { static table = DatabaseTable.Wallets; // define fields - @text('name') name?: string; - @text('hash') hash?: string; - @text('mnemonic') mnemonic?: string; - @field('number') number?: number; - @readonly @date('created_at') createdAt?: Date; - - hydrate(): Wallet { - return { - _id: this.id, - hash: this.hash || '', - name: this.name || '', - mnemonic: this.mnemonic || '', - number: this.number || 0, - createdAt: this.createdAt ? new Date(this.createdAt) : new Date() - }; - } + // @ts-ignore + @text('name') name: string; + // @ts-ignore + @text('mnemonic') mnemonic: string; + // @ts-ignore + @text('hash') hash: string; + // @ts-ignore + @field('number') number: number; + // @ts-ignore + @text('cashback') cashback: string; + // @ts-ignore + @field('is_backed_up') isBackedUp: number; + // @ts-ignore + @field('is_hide_transaction_for_free') isHideTransactionForFee: number; + // @ts-ignore + @field('allow_replace_by_fee') allowReplaceByFee: number; + // @ts-ignore + @field('use_legacy') useLegacy: number; + // @ts-ignore + @field('use_unconfirmed') useUnconfirmed: number; + // @ts-ignore + @field('is_hd') isHd: number; + // @ts-ignore + @field('is_created_here') isCreatedHere: number; + // @ts-ignore + @field('to_send_status') toSendStatus: number; } diff --git a/src/database/schemas/wallet.ts b/src/database/schemas/wallet.ts index 689856e85..317551b00 100644 --- a/src/database/schemas/wallet.ts +++ b/src/database/schemas/wallet.ts @@ -20,6 +20,41 @@ export const WalletTable = tableSchema({ name: 'number', type: 'number' }, - { name: 'created_at', type: 'number' } + { + name: 'cashback', + type: 'string' + }, + { + name: 'is_backed_up', + type: 'number' + }, + { + name: 'is_hide_transaction_for_free', + type: 'number' + }, + { + name: 'allow_replace_by_fee', + type: 'number' + }, + { + name: 'use_legacy', + type: 'number' + }, + { + name: 'use_unconfirmed', + type: 'number' + }, + { + name: 'is_hd', + type: 'number' + }, + { + name: 'is_created_here', + type: 'number' + }, + { + name: 'to_send_status', + type: 'number' + } ] }); diff --git a/src/models/Wallet.ts b/src/models/Wallet.ts index ec87af66c..284a142ae 100644 --- a/src/models/Wallet.ts +++ b/src/models/Wallet.ts @@ -1,21 +1,58 @@ -import { WalletMetadata } from '@appTypes'; +/* eslint-disable camelcase */ +import { DatabaseTable, WalletMetadata } from '@appTypes'; +import { Database, WalletDBModel } from '@database'; export class Wallet { - _id: string; - hash: string; name: string; mnemonic: string; + hash: string; number: number; - createdAt: Date; + cashback: string; + isBackedUp: number; + isHideTransactionForFee: number; + allowReplaceByFee: number; + useLegacy: number; + useUnconfirmed: number; + isHd: number; + isCreatedHere: number; + toSendStatus: number; constructor(details: WalletMetadata) { - this._id = details._id || ''; - this.hash = details.hash || ''; + this.hash = details.hash || 'empty_hash'; this.name = details.name; this.mnemonic = details.mnemonic; this.number = details.number; - this.createdAt = details.createdAt - ? new Date(details.createdAt) - : new Date(); + // @ilya I have no idea what below fields correspond + this.cashback = details.cashback || 'empty_cashback'; + this.isBackedUp = details.isBackedUp || 0; + this.isHideTransactionForFee = details.isHideTransactionForFee || 1; + this.allowReplaceByFee = details.allowReplaceByFee || 1; + this.useLegacy = details.useLegacy || 2; + this.useUnconfirmed = details.useUnconfirmed || 1; + this.isHd = details.isHd || 0; + this.isCreatedHere = details.isCreatedHere || 0; + this.toSendStatus = details.toSendStatus || 0; + } + + static async saveWallet(wallet: WalletMetadata) { + return await Database.createModel(DatabaseTable.Wallets, wallet); + } + + static fromDBModel(model: WalletDBModel): Wallet { + return new Wallet({ + name: model.name, + mnemonic: model.mnemonic, + hash: model.hash, + number: model.number, + cashback: model.cashback, + isBackedUp: model.isBackedUp, + isHideTransactionForFee: model.isHideTransactionForFee, + allowReplaceByFee: model.allowReplaceByFee, + useLegacy: model.useLegacy, + useUnconfirmed: model.useUnconfirmed, + isHd: model.isHd, + isCreatedHere: model.isCreatedHere, + toSendStatus: model.toSendStatus + }); } } diff --git a/src/screens/CreateWallet/CreateWalletStep2.tsx b/src/screens/CreateWallet/CreateWalletStep2.tsx index 5589f1cf8..a34bae6cd 100644 --- a/src/screens/CreateWallet/CreateWalletStep2.tsx +++ b/src/screens/CreateWallet/CreateWalletStep2.tsx @@ -33,9 +33,8 @@ export const CreateWalletStep2 = () => { return; } setLoading(true); - Alert.alert('success'); // TODO fix number - WalletUtils.processWallet({ + await WalletUtils.processWallet({ number: 0, mnemonic: walletMnemonic, name: '' diff --git a/src/utils/wallet.ts b/src/utils/wallet.ts index 4ae028ad7..44fa5c4b8 100644 --- a/src/utils/wallet.ts +++ b/src/utils/wallet.ts @@ -40,6 +40,7 @@ const processWallet = async ( } const fullWallet: Wallet = new Wallet({ ...data, hash, name: tmpWalletName }); // TODO save to local db + await Wallet.saveWallet(fullWallet); try { } catch (error) { throw error; From 71e9c9fc68ea109ff4ac140797e7fb645745136e Mon Sep 17 00:00:00 2001 From: illiaa Date: Fri, 21 Jul 2023 16:00:19 +0300 Subject: [PATCH 011/509] added storage files --- package.json | 12 +- src/constants/config.ts | 10 +- src/lib/AirDAOKeys.ts | 377 +++++++++++++++++++++++++++++ src/lib/AirDAOKeysForRefStorage.ts | 25 ++ src/utils/keys.ts | 111 +++++++++ 5 files changed, 528 insertions(+), 7 deletions(-) create mode 100644 src/lib/AirDAOKeys.ts create mode 100644 src/lib/AirDAOKeysForRefStorage.ts create mode 100644 src/utils/keys.ts diff --git a/package.json b/package.json index 5fc4aa34a..f2b270e47 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,6 @@ { "name": "airdao", "version": "1.0.0", - "main": "node_modules/expo/AppEntry.js", "scripts": { "build:android:dev": "eas build -p android --profile dev", "build:ios:dev": "eas build -p ios --profile dev", @@ -9,10 +8,10 @@ "build:ios:stage": "eas build -p ios --profile stage", "build:android:prod": "eas build -p android --profile prod", "build:ios:prod": "eas build -p ios --profile prod", - "start": "expo start", + "start": "expo start --dev-client", "start:dev": "expo start --dev-client", - "android": "expo start --android", - "ios": "expo start --ios", + "android": "expo run:android", + "ios": "expo run:ios", "web": "expo start --web", "lint": "eslint 'src/**/*.{js,ts,tsx}'", "prepare": "husky install", @@ -36,14 +35,17 @@ "@testing-library/react-native": "^12.1.1", "@types/jest": "^29.5.1", "axios": "^1.3.4", + "bip32": "^4.0.0", "bip39": "^3.1.0", + "bitcoinjs-lib": "^6.1.3", + "bs58check": "^3.0.1", "buffer": "^6.0.3", "expo": "~48.0.18", "expo-barcode-scanner": "~12.3.2", "expo-build-properties": "~0.6.0", "expo-camera": "~13.2.1", "expo-clipboard": "~4.1.2", - "expo-crypto": "~12.2.1", + "expo-crypto": "^12.4.1", "expo-dev-client": "~2.2.1", "expo-device": "~5.2.1", "expo-file-system": "~15.2.2", diff --git a/src/constants/config.ts b/src/constants/config.ts index 4c9ad30e7..53a25d460 100644 --- a/src/constants/config.ts +++ b/src/constants/config.ts @@ -4,13 +4,19 @@ const envs = { CMC_API_URL: 'https://sandbox-api.coinmarketcap.com', WALLET_API_URL: 'https://wallet-api-api.ambrosus.io', EXPLORER_API_URL: 'https://explorer-api.ambrosus.io', - env: 'prod' + env: 'prod', + debug: { + appBuildVersion: '1.0.0' + } }, stage: { CMC_API_URL: 'https://sandbox-api.coinmarketcap.com', WALLET_API_URL: 'https://wallet-api.ambrosus-test.io', EXPLORER_API_URL: 'https://explorer-api.ambrosus-test.io', - env: 'stage' + env: 'stage', + debug: { + appBuildVersion: '1.0.0' + } } }; diff --git a/src/lib/AirDAOKeys.ts b/src/lib/AirDAOKeys.ts new file mode 100644 index 000000000..73e13462a --- /dev/null +++ b/src/lib/AirDAOKeys.ts @@ -0,0 +1,377 @@ +import * as SecureStore from 'expo-secure-store'; +import KeysUtills from '@utils/keys'; +import config from '@constants/config'; +const bip39 = require('bip39'); +const bip32 = require('bip32'); +const bs58check = require('bs58check'); + +const appCacheConfig = { + lifeTime: 3600 +}; + +type Network = { + wif: number; + bip32: { + public: number; + private: number; + }; + messagePrefix: string; + bech32?: string; + pubKeyHash: number; + scriptHash: number; + coin: string; +}; + +class AirDAOKeys { + private async getRandomBytes(size: number): Promise { + // TODO implement the function to generate random bytes + return ''; + } + + private async _tonCheckRevert(mnemonic: string): Promise { + // TODO implement the function to check TON mnemonic + return false; + } + + public async newMnemonic(size = 256): Promise { + let mnemonic: string; + try { + const random = await this.getRandomBytes(size / 8); + mnemonic = bip39.entropyToMnemonic(Buffer.from(random, 'base64')); + if (await this._tonCheckRevert(mnemonic)) { + throw new Error('TON Mnemonic is not validating'); + } + } catch (e: any) { + throw new Error(`AirDAOKeys newMnemonic error ${e.message}`); + } + return mnemonic; + } + + async validateMnemonic(mnemonic: string): Promise { + const result = bip39.validateMnemonic(mnemonic); + if (!result) { + throw new Error('invalid mnemonic for bip39'); + } + return result; + } + + async getSeedCached(mnemonic: string): Promise { + const mnemonicCache = mnemonic.toLowerCase(); + let seed: Buffer; + try { + seed = await SecureStore.getItemAsync(mnemonicCache); + if (!seed) { + throw new Error(); + } + } catch { + seed = await KeysUtills.bip39MnemonicToSeed(mnemonic.toLowerCase()); + await SecureStore.setItemAsync(mnemonicCache, seed.toString()); + } + return seed; + } + + async setEthCached(mnemonic: string, result: any): Promise { + const mnemonicCache = mnemonic.toLowerCase(); + await SecureStore.setItemAsync(mnemonicCache, JSON.stringify(result)); + } + + async getEthCached(mnemonic: string): Promise { + const mnemonicCache = mnemonic.toLowerCase(); + const result = await SecureStore.getItemAsync(mnemonicCache); + if (!result) { + return false; + } + return JSON.parse(result); + } + + async getBip32Cached( + mnemonic: string, + network?: Network, + seed?: Buffer + ): Promise { + const mnemonicCache = mnemonic.toLowerCase(); + let root: bip32.BIP32Interface | null = null; + const cachedRoot = await SecureStore.getItemAsync(mnemonicCache); + if (cachedRoot) { + root = bip32.fromBase58(cachedRoot); + } else { + if (!seed) { + seed = await this.getSeedCached(mnemonic); + } + root = network ? bip32.fromSeed(seed, network) : bip32.fromSeed(seed); + await SecureStore.setItemAsync(mnemonicCache, root.toBase58()); + } + return root; + } + + // TODO discoverAddresses + + // TODO discoverOne + + async discoverOne(data) { + const seed = await KeysUtills.bip39MnemonicToSeed( + data.mnemonic.toLowerCase() + ); + const root = bip32.fromSeed(seed); + const child = root.derivePath(data.derivationPath.replace(/quote/g, "'")); + /** + * @type {EthAddressProcessor|BtcAddressProcessor} + */ + // const processor = await BlocksoftDispatcher.getAddressProcessor( + // data.currencyCode + // ); + // processor.setBasicRoot(root); + // return processor.getAddress( + // child.privateKey, + // { + // derivationPath: data.derivationPath, + // derivationIndex: data.derivationIndex, + // derivationType: data.derivationType, + // publicKey: child.publicKey + // }, + // data, + // seed, + // 'discoverOne' + // ); + } + + async discoverXpub(data: { + mnemonic: string; + currencyCode: string; + }): Promise { + try { + const seed = await KeysUtills.bip39MnemonicToSeed( + data.mnemonic.toLowerCase() + ); + const root = bip32.fromSeed(seed); + let path = `m/44'/0'/0'`; + let version = 0x0488b21e; + if (data.currencyCode === 'BTC_SEGWIT_COMPATIBLE') { + path = `m/49'/0'/0'`; + version = 0x049d7cb2; + } + const rootWallet = root.derivePath(path); + const res = bs58check.encode( + this.serialize(rootWallet, version, rootWallet.publicKey) + ); + return res; + } catch (error) { + throw error; + } + } + + serialize(hdkey: any, version: number, key: Buffer, LEN = 78): Buffer { + try { + const buffer = Buffer.allocUnsafe(LEN); + buffer.writeUInt32BE(version, 0); + buffer.writeUInt8(hdkey.depth, 4); + + const fingerprint = hdkey.depth ? hdkey.parentFingerprint : 0x00000000; + buffer.writeUInt32BE(fingerprint, 5); + buffer.writeUInt32BE(hdkey.index, 9); + buffer.copy(hdkey.chainCode, 0, 13); + key.copy(buffer, 45); + + return buffer; + } catch (error) { + throw error; + } + } + + async setSecureKeyValue(key: string, value: string): Promise { + let text = ''; + try { + text = await KeysUtills.hashMnemonic(value); + await SecureStore.setItemAsync(key, text); + } catch (e: any) { + throw new Error(`AirDAOKeys setSecureKeyValue error ${e.message}`); + } + } + + async getSecureKeyValue(key: string): Promise { + let text: string | null = ''; + try { + text = await SecureStore.getItemAsync(key); + if (text) { + const result = KeysUtills.hashMnemonic(text); + return result; + } else { + return null; + } + } catch (e: any) { + throw new Error(`AirDAOKeys getSecureKeyValue error ${e.message}`); + } + } + + async removeSecureKeyValue(key: string): Promise { + try { + await SecureStore.deleteItemAsync(key); + } catch (e: any) { + throw new Error(`AirDAOKeys removeSecureKeyValue error ${e.message}`); + } + } + + async setMnemonic(value: string, walletHash: string): Promise { + const key = `mnemonic_${walletHash}`; + await this.setSecureKeyValue(key, value); + } + + async getMnemonic(currencyCode: string): Promise { + const key = `mnemonic_${currencyCode}`; + return await this.getSecureKeyValue(key); + } + + async removeMnemonic(currencyCode: string): Promise { + const key = `mnemonic_${currencyCode}`; + await this.removeSecureKeyValue(key); + } + + async setWallet( + walletHash: string, + walletPubId: number, + currencyCode: string, + walletData: any + ): Promise { + const key = `wallet_${walletHash}_${walletPubId}_${currencyCode}`; + await this.setSecureKeyValue(key, JSON.stringify(walletData)); + } + + async getWallet( + walletHash: string, + walletPubId: number, + currencyCode: string + ): Promise { + const key = `wallet_${walletHash}_${walletPubId}_${currencyCode}`; + const value = await this.getSecureKeyValue(key); + if (value) { + return JSON.parse(value); + } else { + return null; + } + } + + async removeWallet( + walletHash: string, + walletPubId: number, + currencyCode: string + ): Promise { + const key = `wallet_${walletHash}_${walletPubId}_${currencyCode}`; + await this.removeSecureKeyValue(key); + } + + async setSelectedWallet( + walletHash: string, + walletPubId: number, + currencyCode: string + ): Promise { + const key = `selected_wallet_${currencyCode}`; + await this.setSecureKeyValue(key, `${walletHash}_${walletPubId}`); + } + + async getSelectedWallet( + currencyCode: string + ): Promise<{ walletHash: string; walletPubId: number } | null> { + const key = `selected_wallet_${currencyCode}`; + const value = await this.getSecureKeyValue(key); + if (value) { + const values = value.split('_'); + return { + walletHash: values[0], + walletPubId: parseInt(values[1], 10) + }; + } else { + return null; + } + } + + async removeSelectedWallet(currencyCode: string): Promise { + const key = `selected_wallet_${currencyCode}`; + await this.removeSecureKeyValue(key); + } + + async setCache(key: string, value: any): Promise { + const cacheSettings = appCacheConfig; + if (cacheSettings && cacheSettings.lifeTime && cacheSettings.lifeTime > 0) { + const cacheKey = `cache_${key}`; + await this.setSecureKeyValue( + cacheKey, + JSON.stringify({ value, timestamp: new Date().getTime() }) + ); + } + } + + async getCache(key: string): Promise { + const cacheSettings = appCacheConfig; + if (cacheSettings && cacheSettings.lifeTime && cacheSettings.lifeTime > 0) { + const cacheKey = `cache_${key}`; + const value = await this.getSecureKeyValue(cacheKey); + if (value) { + const { value: cachedValue, timestamp } = JSON.parse(value); + const currentTime = new Date().getTime(); + if (currentTime - timestamp < cacheSettings.lifeTime * 1000) { + return cachedValue; + } else { + await this.removeSecureKeyValue(cacheKey); + return null; + } + } else { + return null; + } + } else { + return null; + } + } + + async removeCache(key: string): Promise { + const cacheSettings = appCacheConfig; + if (cacheSettings && cacheSettings.lifeTime && cacheSettings.lifeTime > 0) { + const cacheKey = `cache_${key}`; + await this.removeSecureKeyValue(cacheKey); + } + } + + async setAppVersion(version: string): Promise { + const key = 'app_version'; + await this.setSecureKeyValue(key, version); + } + + async getAppVersion(): Promise { + const key = 'app_version'; + return await this.getSecureKeyValue(key); + } + + async setLog(logData: any): Promise { + const key = `logs_${config.debug.appBuildVersion}_${logData.type}_${logData.hash}`; + await this.setSecureKeyValue(key, JSON.stringify(logData)); + } + + async getLogs(logType: string): Promise { + const logs = []; + const keys = await SecureStore.getItemAsync('keys'); + if (keys) { + for (const key of keys) { + if (key.includes(`logs_${config.debug.appBuildVersion}_${logType}`)) { + const value = await this.getSecureKeyValue(key); + if (value) { + logs.push(JSON.parse(value)); + } + } + } + } + logs.sort((a, b) => b.time - a.time); + return logs; + } + + async removeLogs(logType: string): Promise { + const keys = await SecureStore.getItemAsync('keys'); + if (keys) { + for (const key of keys) { + if (key.includes(`logs_${config.debug.appBuildVersion}_${logType}`)) { + await this.removeSecureKeyValue(key); + } + } + } + } +} + +export default new AirDAOKeys(); diff --git a/src/lib/AirDAOKeysForRefStorage.ts b/src/lib/AirDAOKeysForRefStorage.ts new file mode 100644 index 000000000..4223b1021 --- /dev/null +++ b/src/lib/AirDAOKeysForRefStorage.ts @@ -0,0 +1,25 @@ +import * as SecureStore from 'expo-secure-store'; + +class AirDAOKeysForRefStorage { + private serviceName: string; + + constructor(serviceName = 'AirDAOKeysRefStorage') { + this.serviceName = serviceName; + } + + async getPublicAndPrivateResultForHash(hash: string): Promise { + const res = await SecureStore.getItemAsync('cd_' + hash); + if (!res) return false; + return JSON.parse(res); + } + + async setPublicAndPrivateResultForHash( + hash: string, + data: any + ): Promise { + return SecureStore.setItemAsync('cd_' + hash, JSON.stringify(data)); + } +} + +const singleAirDAOKeysRefStorage = new AirDAOKeysForRefStorage(); +export default singleAirDAOKeysRefStorage; diff --git a/src/utils/keys.ts b/src/utils/keys.ts new file mode 100644 index 000000000..ad736a427 --- /dev/null +++ b/src/utils/keys.ts @@ -0,0 +1,111 @@ +import * as Crypto from 'expo-crypto'; +import { DEFAULT_WORDS } from '@constants/words'; +import { createHmac } from 'crypto'; + +interface CreateHmacPDFK2Sizes { + [key: string]: number; +} + +const createHmacPDFK2Sizes: CreateHmacPDFK2Sizes = { + md5: 16, + sha1: 20, + sha224: 28, + sha256: 32, + sha384: 48, + sha512: 64, + rmd160: 20, + ripemd160: 20 +}; + +class KeysUtills { + static _pbkdf2( + password: string, + salt: Buffer, + iterations: number, + keylen: number, + digest?: string + ): Buffer { + digest = digest || 'sha1'; + + const DK = Buffer.allocUnsafe(keylen); + const block1 = Buffer.allocUnsafe(salt.length + 4); + salt.copy(block1, 0, 0, salt.length); + + let destPos = 0; + const hLen = createHmacPDFK2Sizes[digest]; + const l = Math.ceil(keylen / hLen); + + for (let i = 1; i <= l; i++) { + block1.writeUInt32BE(i, salt.length); + + const T = createHmac(digest, password).update(block1).digest(); + let U = T; + + for (let j = 1; j < iterations; j++) { + U = createHmac(digest, password).update(U).digest(); + for (let k = 0; k < hLen; k++) { + T[k] = T[k] + U[k]; + } + } + + T.copy(DK, destPos); + destPos += hLen; + } + + return DK; + } + + static recheckMnemonic(mnemonic: string): string { + const words = mnemonic.trim().toLowerCase().split(/\s+/g); + const checked = []; + let word; + + for (word of words) { + if (!word || word.length < 2) { + continue; + } + + const index = DEFAULT_WORDS.indexOf(word); + + if (index === -1) { + throw new Error('BlocksoftKeysStorage invalid word ' + word); + } + + checked.push(word); + } + + if (checked.length <= 11) { + throw new Error('BlocksoftKeysStorage invalid words length ' + mnemonic); + } + + return checked.join(' '); + } + + static async hashMnemonic(mnemonic: string): Promise { + return await Crypto.digestStringAsync( + Crypto.CryptoDigestAlgorithm.SHA256, + mnemonic + ).then((hash: string) => hash.substr(0, 32)); + } + + static async bip39MnemonicToSeed( + mnemonic: string, + password?: string + ): Promise { + if (!mnemonic) { + throw new Error('bip39MnemonicToSeed is empty'); + } + + // tslint:disable-next-line:no-shadowed-variable + function salt(password: string): string { + return 'mnemonic' + (password || ''); + } + + const saltBuffer = Buffer.from(salt(password || ''), 'utf8'); + + const tmp2 = KeysUtills._pbkdf2(mnemonic, saltBuffer, 2048, 64, 'sha512'); + return Buffer.from(tmp2); + } +} + +export default KeysUtills; From 9157c7a17c1da5577fb1ee802d6000bfbbc40000 Mon Sep 17 00:00:00 2001 From: illiaa Date: Sat, 22 Jul 2023 16:14:42 +0300 Subject: [PATCH 012/509] renamed --- src/lib/{AirDAOStorage.ts => AirDAOKeysStorage.ts} | 4 ++-- src/lib/index.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) rename src/lib/{AirDAOStorage.ts => AirDAOKeysStorage.ts} (98%) diff --git a/src/lib/AirDAOStorage.ts b/src/lib/AirDAOKeysStorage.ts similarity index 98% rename from src/lib/AirDAOStorage.ts rename to src/lib/AirDAOKeysStorage.ts index e76c48ee6..c55077bf7 100644 --- a/src/lib/AirDAOStorage.ts +++ b/src/lib/AirDAOKeysStorage.ts @@ -1,7 +1,7 @@ import * as SecureStore from 'expo-secure-store'; import { WalletMetadata } from '@appTypes'; -export class AirDAOStorage { +export class AirDAOKeysStorage { private serviceName = ''; private serviceWalletsCounter = 0; private serviceWallets: { [key: string]: string } = {}; @@ -296,5 +296,5 @@ export class AirDAOStorage { } } -const singleAirDAOStorage = new AirDAOStorage(); +const singleAirDAOStorage = new AirDAOKeysStorage(); export default singleAirDAOStorage; diff --git a/src/lib/index.ts b/src/lib/index.ts index 1696fbc37..6ba37fb94 100644 --- a/src/lib/index.ts +++ b/src/lib/index.ts @@ -1,4 +1,4 @@ export * from './device'; export * from './notification'; -export * from './AirDAOStorage'; +export * from './AirDAOKeysStorage'; export { default as PermissionService } from './permission'; From 9aaf4e349539bfa4175af26c585a25698e857daf Mon Sep 17 00:00:00 2001 From: JavidHaji-zada Date: Mon, 24 Jul 2023 11:43:26 +0400 Subject: [PATCH 013/509] blockchains folder --- babel.config.js | 1 + .../BlocksoftBalances/BlocksoftBalances.js | 198 ++ .../BlocksoftInvoice/BlocksoftInvoice.js | 116 + crypto/actions/BlocksoftKeys/BlocksoftKeys.js | 419 +++ .../BlocksoftKeys/BlocksoftKeysScam.js | 58 + .../BlocksoftKeys/BlocksoftKeysUtils.js | 106 + .../actions/BlocksoftKeys/_words/english.json | 2050 +++++++++++++ .../BlocksoftKeysForRef.js | 69 + .../BlocksoftKeysForRefServerSide.js | 80 + .../BlocksoftKeysForRefStorage.js | 26 + .../BlocksoftKeysStorage.js | 325 ++ .../BlocksoftSecrets/BlocksoftSecrets.js | 51 + .../BlocksoftTokenChecks.js | 59 + .../BlocksoftTokenNfts/BlocksoftTokenNfts.js | 35 + .../BlocksoftTransactions.js | 126 + .../BlocksoftTransfer/BlocksoftTransfer.ts | 297 ++ .../BlocksoftTransferPrivate.ts | 48 + .../BlocksoftTransferUtils.ts | 67 + crypto/assets/coinBlocksoftDict.json | 2662 +++++++++++++++++ crypto/assets/dappsBlocksoftDict.json | 66 + .../assets/tokenBlockchainBlocksoftDict.json | 40 + crypto/blockchains/ash/AshAddressProcessor.js | 48 + crypto/blockchains/bch/BchAddressProcessor.js | 31 + crypto/blockchains/bch/BchScannerProcessor.js | 41 + .../blockchains/bch/BchTransferProcessor.ts | 48 + crypto/blockchains/bch/ext/BtcCashUtils.js | 205 ++ .../bch/providers/BchSendProvider.ts | 67 + .../bch/providers/BchUnspentsProvider.ts | 63 + crypto/blockchains/bch/tx/BchTxBuilder.ts | 53 + crypto/blockchains/bnb/BnbAddressProcessor.js | 52 + crypto/blockchains/bnb/BnbScannerProcessor.js | 169 ++ .../blockchains/bnb/BnbTransferProcessor.ts | 153 + .../blockchains/bnb/basic/BnbNetworkPrices.js | 64 + .../bnb/basic/BnbTxSendProvider.ts | 272 ++ crypto/blockchains/bnb/utils/Encode.js | 262 ++ crypto/blockchains/bnb/utils/EncoderHelper.js | 44 + crypto/blockchains/bnb/utils/IsJs.js | 906 ++++++ crypto/blockchains/bnb/utils/UVarInt.js | 81 + .../bnb_smart/BnbSmartTransferProcessor.ts | 37 + .../BnbSmartTransferProcessorErc20.ts | 48 + .../bnb_smart/basic/BnbSmartNetworkPrices.js | 54 + crypto/blockchains/bsv/BsvScannerProcessor.js | 318 ++ .../blockchains/bsv/BsvTransferProcessor.ts | 39 + .../bsv/providers/BsvSendProvider.ts | 75 + .../bsv/providers/BsvUnspentsProvider.ts | 42 + crypto/blockchains/bsv/stores/BsvTmpDS.js | 59 + crypto/blockchains/bsv/tx/BsvTxBuilder.ts | 35 + crypto/blockchains/btc/BtcScannerProcessor.js | 411 +++ .../blockchains/btc/BtcTransferProcessor.ts | 37 + .../btc/address/BtcAddressProcessor.js | 36 + .../btc/address/BtcSegwitAddressProcessor.js | 19 + .../BtcSegwitCompatibleAddressProcessor.js | 19 + .../btc/basic/BtcFindAddressFunction.js | 126 + .../blockchains/btc/basic/BtcNetworkPrices.ts | 245 ++ .../btc/providers/BtcUnspentsProvider.ts | 207 ++ crypto/blockchains/btc/tx/BtcTxBuilder.ts | 161 + .../blockchains/btc/tx/BtcTxInputsOutputs.ts | 130 + .../btc_test/BtcTestScannerProcessor.js | 114 + .../btc_test/BtcTestTransferProcessor.ts | 39 + .../btc_test/providers/BtcTestSendProvider.ts | 80 + .../providers/BtcTestUnspentsProvider.ts | 61 + crypto/blockchains/btg/BtgScannerProcessor.js | 21 + .../blockchains/btg/BtgTransferProcessor.ts | 31 + .../blockchains/doge/DogeScannerProcessor.js | 253 ++ .../blockchains/doge/DogeTransferProcessor.ts | 666 +++++ .../doge/basic/DogeFindAddressFunction.js | 129 + crypto/blockchains/doge/basic/DogeLogs.ts | 86 + .../doge/basic/DogeNetworkPrices.ts | 31 + .../doge/providers/DogeSendProvider.ts | 226 ++ .../doge/providers/DogeUnspentsProvider.ts | 201 ++ crypto/blockchains/doge/stores/DogeRawDS.js | 216 ++ crypto/blockchains/doge/tx/DogeTxBuilder.ts | 195 ++ .../doge/tx/DogeTxInputsOutputs.ts | 493 +++ .../blockchains/etc/EtcTransferProcessor.ts | 13 + crypto/blockchains/eth/EthAddressProcessor.js | 39 + crypto/blockchains/eth/EthScannerProcessor.js | 785 +++++ .../eth/EthScannerProcessorErc20.js | 79 + .../blockchains/eth/EthTokenProcessorErc20.js | 72 + .../blockchains/eth/EthTokenProcessorNft.js | 67 + .../blockchains/eth/EthTransferProcessor.ts | 972 ++++++ .../eth/EthTransferProcessorErc20.ts | 201 ++ crypto/blockchains/eth/apis/EthNftMatic.js | 101 + crypto/blockchains/eth/apis/EthNftOpensea.js | 143 + crypto/blockchains/eth/basic/EthBasic.js | 341 +++ .../blockchains/eth/basic/EthNetworkPrices.js | 269 ++ .../eth/basic/EthTxSendProvider.ts | 305 ++ crypto/blockchains/eth/ext/EthEstimateGas.js | 24 + crypto/blockchains/eth/ext/erc1155.js | 106 + crypto/blockchains/eth/ext/erc20.js | 237 ++ crypto/blockchains/eth/ext/erc721.js | 61 + .../blockchains/eth/ext/estimateGas/enums.js | 20 + .../blockchains/eth/ext/estimateGas/index.js | 40 + .../eth/ext/estimateGas/tx-gas-utils.js | 147 + .../blockchains/eth/ext/estimateGas/util.js | 159 + .../eth/forks/EthScannerProcessorSoul.js | 55 + crypto/blockchains/eth/stores/EthRawDS.js | 283 ++ crypto/blockchains/eth/stores/EthTmpDS.js | 194 ++ crypto/blockchains/fio/FioAddressProcessor.js | 40 + crypto/blockchains/fio/FioScannerProcessor.js | 130 + crypto/blockchains/fio/FioSdkWrapper.js | 57 + .../blockchains/fio/FioTransferProcessor.ts | 84 + crypto/blockchains/fio/FioUtils.js | 369 +++ crypto/blockchains/ltc/LtcScannerProcessor.js | 20 + .../blockchains/ltc/LtcTransferProcessor.ts | 40 + .../metis/MetisScannerProcessor.js | 34 + .../metis/MetisTransferProcessor.ts | 58 + crypto/blockchains/one/OneScannerProcessor.js | 179 ++ .../one/OneScannerProcessorErc20.js | 181 ++ crypto/blockchains/one/ext/OneUtils.js | 239 ++ crypto/blockchains/one/stores/OneTmpDS.js | 45 + crypto/blockchains/sol/SolAddressProcessor.js | 53 + crypto/blockchains/sol/SolScannerProcessor.js | 337 +++ .../blockchains/sol/SolScannerProcessorSpl.js | 67 + crypto/blockchains/sol/SolTokenProcessor.js | 89 + .../blockchains/sol/SolTransferProcessor.ts | 286 ++ .../sol/SolTransferProcessorSpl.ts | 198 ++ crypto/blockchains/sol/ext/SolInstructions.js | 79 + crypto/blockchains/sol/ext/SolStakeUtils.js | 307 ++ crypto/blockchains/sol/ext/SolUtils.js | 228 ++ crypto/blockchains/sol/ext/validators.js | 243 ++ crypto/blockchains/sol/stores/SolTmpDS.js | 54 + crypto/blockchains/trx/TrxAddressProcessor.js | 22 + crypto/blockchains/trx/TrxScannerProcessor.js | 301 ++ crypto/blockchains/trx/TrxTokenProcessor.js | 55 + .../blockchains/trx/TrxTransferProcessor.ts | 634 ++++ .../trx/basic/TrxNodeInfoProvider.js | 33 + .../trx/basic/TrxTransactionsProvider.js | 209 ++ .../trx/basic/TrxTransactionsTrc20Provider.js | 160 + .../trx/basic/TrxTrongridProvider.js | 183 ++ .../trx/basic/TrxTronscanProvider.js | 81 + crypto/blockchains/trx/dict/swaps.js | 12 + crypto/blockchains/trx/ext/TronStakeUtils.js | 116 + crypto/blockchains/trx/ext/TronUtils.js | 92 + .../trx/providers/TrxSendProvider.ts | 144 + .../blockchains/usdt/UsdtScannerProcessor.js | 228 ++ .../blockchains/usdt/UsdtTransferProcessor.ts | 102 + crypto/blockchains/usdt/tx/UsdtTxBuilder.ts | 45 + .../usdt/tx/UsdtTxInputsOutputs.ts | 271 ++ crypto/blockchains/vet/VetScannerProcessor.js | 267 ++ .../blockchains/vet/VetTransferProcessor.ts | 211 ++ .../waves/WavesAddressProcessor.js | 27 + .../waves/WavesScannerProcessor.js | 162 + .../waves/WavesScannerProcessorErc20.js | 113 + .../waves/WavesTransferProcessor.ts | 159 + .../providers/WavesTransactionsProvider.js | 42 + crypto/blockchains/xlm/XlmAddressProcessor.js | 31 + crypto/blockchains/xlm/XlmScannerProcessor.js | 212 ++ .../blockchains/xlm/XlmTransferProcessor.ts | 222 ++ .../xlm/basic/XlmTxSendProvider.ts | 159 + crypto/blockchains/xlm/ext/XlmDerivePath.js | 49 + crypto/blockchains/xmr/XmrAddressProcessor.js | 137 + crypto/blockchains/xmr/XmrScannerProcessor.js | 329 ++ crypto/blockchains/xmr/XmrSecretsProcessor.js | 66 + .../blockchains/xmr/XmrTransferProcessor.ts | 280 ++ crypto/blockchains/xmr/ext/MoneroDict.js | 1634 ++++++++++ crypto/blockchains/xmr/ext/MoneroMnemonic.js | 43 + crypto/blockchains/xmr/ext/MoneroUtils.js | 168 ++ .../blockchains/xmr/ext/MoneroUtilsParser.js | 200 ++ .../xmr/ext/MoneroUtilsParser.oldAndroid.js | 28 + .../xmr/ext/vendor/KeyimageCache.js | 73 + .../xmr/ext/vendor/ResponseParser.js | 333 +++ .../blockchains/xmr/ext/vendor/biginteger.js | 1667 +++++++++++ .../xmr/providers/XmrSendProvider.js | 86 + .../xmr/providers/XmrUnspentsProvider.js | 109 + crypto/blockchains/xrp/XrpAddressProcessor.js | 34 + crypto/blockchains/xrp/XrpScannerProcessor.js | 89 + .../blockchains/xrp/XrpTransferProcessor.ts | 229 ++ .../xrp/basic/XrpDataRippleProvider.js | 216 ++ .../xrp/basic/XrpDataScanProvider.js | 263 ++ .../xrp/basic/XrpTxSendProvider.ts | 183 ++ crypto/blockchains/xrp/basic/XrpTxUtils.ts | 9 + crypto/blockchains/xrp/stores/XrpTmpDS.js | 67 + crypto/blockchains/xvg/XvgScannerProcessor.js | 241 ++ .../blockchains/xvg/XvgTransferProcessor.ts | 41 + .../xvg/basic/XvgFindAddressFunction.js | 95 + .../xvg/providers/XvgSendProvider.ts | 51 + .../xvg/providers/XvgUnspentsProvider.ts | 68 + crypto/blockchains/xvg/stores/XvgTmpDS.js | 49 + crypto/common/BlocksoftAxios.js | 466 +++ crypto/common/BlocksoftBN.js | 98 + crypto/common/BlocksoftCryptoLog.js | 160 + crypto/common/BlocksoftCryptoUtils.js | 11 + crypto/common/BlocksoftCustomLinks.js | 29 + crypto/common/BlocksoftDict.js | 215 ++ crypto/common/BlocksoftDictNfts.js | 119 + crypto/common/BlocksoftDictTypes.ts | 56 + crypto/common/BlocksoftExplorerDict.js | 39 + crypto/common/BlocksoftExternalSettings.js | 299 ++ crypto/common/BlocksoftFixBalance.js | 28 + crypto/common/BlocksoftPrettyDates.js | 20 + crypto/common/BlocksoftPrettyLocalize.js | 16 + crypto/common/BlocksoftPrettyNumbers.js | 165 + crypto/common/BlocksoftPrettyStrings.js | 26 + crypto/common/BlocksoftPrivateKeysUtils.js | 100 + crypto/common/BlocksoftQrScanDict.js | 91 + crypto/common/BlocksoftUtils.js | 375 +++ crypto/common/ext/bip44-constants.js | 516 ++++ crypto/common/ext/networks-constants.js | 104 + crypto/common/ext/scam-seeds.js | 4 + crypto/readme.md | 43 + crypto/services/EnsUtils.js | 25 + crypto/services/UnstoppableUtils.js | 18 + crypto/services/Web3Injected.js | 87 + package.json | 2 + src/lib/BlocksoftDispatcher.js | 254 ++ src/lib/BlocksoftKeys.js | 419 +++ src/lib/BlocksoftKeysForRef.js | 82 + .../blockchains/BlocksoftBlockchainTypes.ts | 357 +++ src/lib/blockchains/BlocksoftDispatcher.ts | 338 +++ .../BlocksoftTransferDispatcher.ts | 135 + src/lib/helpers/AirDAOKeys.ts | 497 +++ src/lib/helpers/AirDAOKeysForRef.ts | 65 + src/lib/{ => helpers}/AirDAOStorage.ts | 0 src/lib/index.ts | 2 +- src/utils/blockchain.js | 255 ++ src/utils/cashback.ts | 1 + src/utils/wallet.ts | 12 +- tsconfig.json | 1 + yarn.lock | 300 +- 219 files changed, 39478 insertions(+), 10 deletions(-) create mode 100644 crypto/actions/BlocksoftBalances/BlocksoftBalances.js create mode 100644 crypto/actions/BlocksoftInvoice/BlocksoftInvoice.js create mode 100644 crypto/actions/BlocksoftKeys/BlocksoftKeys.js create mode 100644 crypto/actions/BlocksoftKeys/BlocksoftKeysScam.js create mode 100644 crypto/actions/BlocksoftKeys/BlocksoftKeysUtils.js create mode 100644 crypto/actions/BlocksoftKeys/_words/english.json create mode 100644 crypto/actions/BlocksoftKeysForRef/BlocksoftKeysForRef.js create mode 100644 crypto/actions/BlocksoftKeysForRef/BlocksoftKeysForRefServerSide.js create mode 100644 crypto/actions/BlocksoftKeysForRef/BlocksoftKeysForRefStorage.js create mode 100644 crypto/actions/BlocksoftKeysStorage/BlocksoftKeysStorage.js create mode 100644 crypto/actions/BlocksoftSecrets/BlocksoftSecrets.js create mode 100644 crypto/actions/BlocksoftTokenChecks/BlocksoftTokenChecks.js create mode 100644 crypto/actions/BlocksoftTokenNfts/BlocksoftTokenNfts.js create mode 100644 crypto/actions/BlocksoftTransactions/BlocksoftTransactions.js create mode 100644 crypto/actions/BlocksoftTransfer/BlocksoftTransfer.ts create mode 100644 crypto/actions/BlocksoftTransfer/BlocksoftTransferPrivate.ts create mode 100644 crypto/actions/BlocksoftTransfer/BlocksoftTransferUtils.ts create mode 100644 crypto/assets/coinBlocksoftDict.json create mode 100644 crypto/assets/dappsBlocksoftDict.json create mode 100644 crypto/assets/tokenBlockchainBlocksoftDict.json create mode 100644 crypto/blockchains/ash/AshAddressProcessor.js create mode 100644 crypto/blockchains/bch/BchAddressProcessor.js create mode 100644 crypto/blockchains/bch/BchScannerProcessor.js create mode 100644 crypto/blockchains/bch/BchTransferProcessor.ts create mode 100644 crypto/blockchains/bch/ext/BtcCashUtils.js create mode 100644 crypto/blockchains/bch/providers/BchSendProvider.ts create mode 100644 crypto/blockchains/bch/providers/BchUnspentsProvider.ts create mode 100644 crypto/blockchains/bch/tx/BchTxBuilder.ts create mode 100644 crypto/blockchains/bnb/BnbAddressProcessor.js create mode 100644 crypto/blockchains/bnb/BnbScannerProcessor.js create mode 100644 crypto/blockchains/bnb/BnbTransferProcessor.ts create mode 100644 crypto/blockchains/bnb/basic/BnbNetworkPrices.js create mode 100644 crypto/blockchains/bnb/basic/BnbTxSendProvider.ts create mode 100644 crypto/blockchains/bnb/utils/Encode.js create mode 100644 crypto/blockchains/bnb/utils/EncoderHelper.js create mode 100644 crypto/blockchains/bnb/utils/IsJs.js create mode 100644 crypto/blockchains/bnb/utils/UVarInt.js create mode 100644 crypto/blockchains/bnb_smart/BnbSmartTransferProcessor.ts create mode 100644 crypto/blockchains/bnb_smart/BnbSmartTransferProcessorErc20.ts create mode 100644 crypto/blockchains/bnb_smart/basic/BnbSmartNetworkPrices.js create mode 100644 crypto/blockchains/bsv/BsvScannerProcessor.js create mode 100644 crypto/blockchains/bsv/BsvTransferProcessor.ts create mode 100644 crypto/blockchains/bsv/providers/BsvSendProvider.ts create mode 100644 crypto/blockchains/bsv/providers/BsvUnspentsProvider.ts create mode 100644 crypto/blockchains/bsv/stores/BsvTmpDS.js create mode 100644 crypto/blockchains/bsv/tx/BsvTxBuilder.ts create mode 100644 crypto/blockchains/btc/BtcScannerProcessor.js create mode 100644 crypto/blockchains/btc/BtcTransferProcessor.ts create mode 100644 crypto/blockchains/btc/address/BtcAddressProcessor.js create mode 100644 crypto/blockchains/btc/address/BtcSegwitAddressProcessor.js create mode 100644 crypto/blockchains/btc/address/BtcSegwitCompatibleAddressProcessor.js create mode 100644 crypto/blockchains/btc/basic/BtcFindAddressFunction.js create mode 100644 crypto/blockchains/btc/basic/BtcNetworkPrices.ts create mode 100644 crypto/blockchains/btc/providers/BtcUnspentsProvider.ts create mode 100644 crypto/blockchains/btc/tx/BtcTxBuilder.ts create mode 100644 crypto/blockchains/btc/tx/BtcTxInputsOutputs.ts create mode 100644 crypto/blockchains/btc_test/BtcTestScannerProcessor.js create mode 100644 crypto/blockchains/btc_test/BtcTestTransferProcessor.ts create mode 100644 crypto/blockchains/btc_test/providers/BtcTestSendProvider.ts create mode 100644 crypto/blockchains/btc_test/providers/BtcTestUnspentsProvider.ts create mode 100644 crypto/blockchains/btg/BtgScannerProcessor.js create mode 100644 crypto/blockchains/btg/BtgTransferProcessor.ts create mode 100644 crypto/blockchains/doge/DogeScannerProcessor.js create mode 100644 crypto/blockchains/doge/DogeTransferProcessor.ts create mode 100644 crypto/blockchains/doge/basic/DogeFindAddressFunction.js create mode 100644 crypto/blockchains/doge/basic/DogeLogs.ts create mode 100644 crypto/blockchains/doge/basic/DogeNetworkPrices.ts create mode 100644 crypto/blockchains/doge/providers/DogeSendProvider.ts create mode 100644 crypto/blockchains/doge/providers/DogeUnspentsProvider.ts create mode 100644 crypto/blockchains/doge/stores/DogeRawDS.js create mode 100644 crypto/blockchains/doge/tx/DogeTxBuilder.ts create mode 100644 crypto/blockchains/doge/tx/DogeTxInputsOutputs.ts create mode 100644 crypto/blockchains/etc/EtcTransferProcessor.ts create mode 100644 crypto/blockchains/eth/EthAddressProcessor.js create mode 100644 crypto/blockchains/eth/EthScannerProcessor.js create mode 100644 crypto/blockchains/eth/EthScannerProcessorErc20.js create mode 100644 crypto/blockchains/eth/EthTokenProcessorErc20.js create mode 100644 crypto/blockchains/eth/EthTokenProcessorNft.js create mode 100644 crypto/blockchains/eth/EthTransferProcessor.ts create mode 100644 crypto/blockchains/eth/EthTransferProcessorErc20.ts create mode 100644 crypto/blockchains/eth/apis/EthNftMatic.js create mode 100644 crypto/blockchains/eth/apis/EthNftOpensea.js create mode 100644 crypto/blockchains/eth/basic/EthBasic.js create mode 100644 crypto/blockchains/eth/basic/EthNetworkPrices.js create mode 100644 crypto/blockchains/eth/basic/EthTxSendProvider.ts create mode 100644 crypto/blockchains/eth/ext/EthEstimateGas.js create mode 100644 crypto/blockchains/eth/ext/erc1155.js create mode 100644 crypto/blockchains/eth/ext/erc20.js create mode 100644 crypto/blockchains/eth/ext/erc721.js create mode 100644 crypto/blockchains/eth/ext/estimateGas/enums.js create mode 100644 crypto/blockchains/eth/ext/estimateGas/index.js create mode 100644 crypto/blockchains/eth/ext/estimateGas/tx-gas-utils.js create mode 100644 crypto/blockchains/eth/ext/estimateGas/util.js create mode 100644 crypto/blockchains/eth/forks/EthScannerProcessorSoul.js create mode 100644 crypto/blockchains/eth/stores/EthRawDS.js create mode 100644 crypto/blockchains/eth/stores/EthTmpDS.js create mode 100644 crypto/blockchains/fio/FioAddressProcessor.js create mode 100644 crypto/blockchains/fio/FioScannerProcessor.js create mode 100644 crypto/blockchains/fio/FioSdkWrapper.js create mode 100644 crypto/blockchains/fio/FioTransferProcessor.ts create mode 100644 crypto/blockchains/fio/FioUtils.js create mode 100644 crypto/blockchains/ltc/LtcScannerProcessor.js create mode 100644 crypto/blockchains/ltc/LtcTransferProcessor.ts create mode 100644 crypto/blockchains/metis/MetisScannerProcessor.js create mode 100644 crypto/blockchains/metis/MetisTransferProcessor.ts create mode 100644 crypto/blockchains/one/OneScannerProcessor.js create mode 100644 crypto/blockchains/one/OneScannerProcessorErc20.js create mode 100644 crypto/blockchains/one/ext/OneUtils.js create mode 100644 crypto/blockchains/one/stores/OneTmpDS.js create mode 100644 crypto/blockchains/sol/SolAddressProcessor.js create mode 100644 crypto/blockchains/sol/SolScannerProcessor.js create mode 100644 crypto/blockchains/sol/SolScannerProcessorSpl.js create mode 100644 crypto/blockchains/sol/SolTokenProcessor.js create mode 100644 crypto/blockchains/sol/SolTransferProcessor.ts create mode 100644 crypto/blockchains/sol/SolTransferProcessorSpl.ts create mode 100644 crypto/blockchains/sol/ext/SolInstructions.js create mode 100644 crypto/blockchains/sol/ext/SolStakeUtils.js create mode 100644 crypto/blockchains/sol/ext/SolUtils.js create mode 100644 crypto/blockchains/sol/ext/validators.js create mode 100644 crypto/blockchains/sol/stores/SolTmpDS.js create mode 100644 crypto/blockchains/trx/TrxAddressProcessor.js create mode 100644 crypto/blockchains/trx/TrxScannerProcessor.js create mode 100644 crypto/blockchains/trx/TrxTokenProcessor.js create mode 100644 crypto/blockchains/trx/TrxTransferProcessor.ts create mode 100644 crypto/blockchains/trx/basic/TrxNodeInfoProvider.js create mode 100644 crypto/blockchains/trx/basic/TrxTransactionsProvider.js create mode 100644 crypto/blockchains/trx/basic/TrxTransactionsTrc20Provider.js create mode 100644 crypto/blockchains/trx/basic/TrxTrongridProvider.js create mode 100644 crypto/blockchains/trx/basic/TrxTronscanProvider.js create mode 100644 crypto/blockchains/trx/dict/swaps.js create mode 100644 crypto/blockchains/trx/ext/TronStakeUtils.js create mode 100644 crypto/blockchains/trx/ext/TronUtils.js create mode 100644 crypto/blockchains/trx/providers/TrxSendProvider.ts create mode 100644 crypto/blockchains/usdt/UsdtScannerProcessor.js create mode 100644 crypto/blockchains/usdt/UsdtTransferProcessor.ts create mode 100644 crypto/blockchains/usdt/tx/UsdtTxBuilder.ts create mode 100644 crypto/blockchains/usdt/tx/UsdtTxInputsOutputs.ts create mode 100644 crypto/blockchains/vet/VetScannerProcessor.js create mode 100644 crypto/blockchains/vet/VetTransferProcessor.ts create mode 100644 crypto/blockchains/waves/WavesAddressProcessor.js create mode 100644 crypto/blockchains/waves/WavesScannerProcessor.js create mode 100644 crypto/blockchains/waves/WavesScannerProcessorErc20.js create mode 100644 crypto/blockchains/waves/WavesTransferProcessor.ts create mode 100644 crypto/blockchains/waves/providers/WavesTransactionsProvider.js create mode 100644 crypto/blockchains/xlm/XlmAddressProcessor.js create mode 100644 crypto/blockchains/xlm/XlmScannerProcessor.js create mode 100644 crypto/blockchains/xlm/XlmTransferProcessor.ts create mode 100644 crypto/blockchains/xlm/basic/XlmTxSendProvider.ts create mode 100644 crypto/blockchains/xlm/ext/XlmDerivePath.js create mode 100644 crypto/blockchains/xmr/XmrAddressProcessor.js create mode 100644 crypto/blockchains/xmr/XmrScannerProcessor.js create mode 100644 crypto/blockchains/xmr/XmrSecretsProcessor.js create mode 100644 crypto/blockchains/xmr/XmrTransferProcessor.ts create mode 100644 crypto/blockchains/xmr/ext/MoneroDict.js create mode 100644 crypto/blockchains/xmr/ext/MoneroMnemonic.js create mode 100644 crypto/blockchains/xmr/ext/MoneroUtils.js create mode 100644 crypto/blockchains/xmr/ext/MoneroUtilsParser.js create mode 100644 crypto/blockchains/xmr/ext/MoneroUtilsParser.oldAndroid.js create mode 100644 crypto/blockchains/xmr/ext/vendor/KeyimageCache.js create mode 100644 crypto/blockchains/xmr/ext/vendor/ResponseParser.js create mode 100644 crypto/blockchains/xmr/ext/vendor/biginteger.js create mode 100644 crypto/blockchains/xmr/providers/XmrSendProvider.js create mode 100644 crypto/blockchains/xmr/providers/XmrUnspentsProvider.js create mode 100644 crypto/blockchains/xrp/XrpAddressProcessor.js create mode 100644 crypto/blockchains/xrp/XrpScannerProcessor.js create mode 100644 crypto/blockchains/xrp/XrpTransferProcessor.ts create mode 100644 crypto/blockchains/xrp/basic/XrpDataRippleProvider.js create mode 100644 crypto/blockchains/xrp/basic/XrpDataScanProvider.js create mode 100644 crypto/blockchains/xrp/basic/XrpTxSendProvider.ts create mode 100644 crypto/blockchains/xrp/basic/XrpTxUtils.ts create mode 100644 crypto/blockchains/xrp/stores/XrpTmpDS.js create mode 100644 crypto/blockchains/xvg/XvgScannerProcessor.js create mode 100644 crypto/blockchains/xvg/XvgTransferProcessor.ts create mode 100644 crypto/blockchains/xvg/basic/XvgFindAddressFunction.js create mode 100644 crypto/blockchains/xvg/providers/XvgSendProvider.ts create mode 100644 crypto/blockchains/xvg/providers/XvgUnspentsProvider.ts create mode 100644 crypto/blockchains/xvg/stores/XvgTmpDS.js create mode 100644 crypto/common/BlocksoftAxios.js create mode 100644 crypto/common/BlocksoftBN.js create mode 100644 crypto/common/BlocksoftCryptoLog.js create mode 100644 crypto/common/BlocksoftCryptoUtils.js create mode 100644 crypto/common/BlocksoftCustomLinks.js create mode 100644 crypto/common/BlocksoftDict.js create mode 100644 crypto/common/BlocksoftDictNfts.js create mode 100644 crypto/common/BlocksoftDictTypes.ts create mode 100644 crypto/common/BlocksoftExplorerDict.js create mode 100644 crypto/common/BlocksoftExternalSettings.js create mode 100644 crypto/common/BlocksoftFixBalance.js create mode 100644 crypto/common/BlocksoftPrettyDates.js create mode 100644 crypto/common/BlocksoftPrettyLocalize.js create mode 100644 crypto/common/BlocksoftPrettyNumbers.js create mode 100644 crypto/common/BlocksoftPrettyStrings.js create mode 100644 crypto/common/BlocksoftPrivateKeysUtils.js create mode 100644 crypto/common/BlocksoftQrScanDict.js create mode 100644 crypto/common/BlocksoftUtils.js create mode 100644 crypto/common/ext/bip44-constants.js create mode 100644 crypto/common/ext/networks-constants.js create mode 100644 crypto/common/ext/scam-seeds.js create mode 100644 crypto/readme.md create mode 100644 crypto/services/EnsUtils.js create mode 100644 crypto/services/UnstoppableUtils.js create mode 100644 crypto/services/Web3Injected.js create mode 100644 src/lib/BlocksoftDispatcher.js create mode 100644 src/lib/BlocksoftKeys.js create mode 100644 src/lib/BlocksoftKeysForRef.js create mode 100644 src/lib/blockchains/BlocksoftBlockchainTypes.ts create mode 100644 src/lib/blockchains/BlocksoftDispatcher.ts create mode 100644 src/lib/blockchains/BlocksoftTransferDispatcher.ts create mode 100644 src/lib/helpers/AirDAOKeys.ts create mode 100644 src/lib/helpers/AirDAOKeysForRef.ts rename src/lib/{ => helpers}/AirDAOStorage.ts (100%) create mode 100644 src/utils/blockchain.js create mode 100644 src/utils/cashback.ts diff --git a/babel.config.js b/babel.config.js index 4782f12c0..b57f2dc6f 100644 --- a/babel.config.js +++ b/babel.config.js @@ -15,6 +15,7 @@ module.exports = function (api) { '@components': './src/components', '@constants': './src/constants', '@contexts': './src/contexts', + '@crypto': './crypto', '@database': './src/database', '@hooks': './src/hooks', '@helpers': './src/helpers', diff --git a/crypto/actions/BlocksoftBalances/BlocksoftBalances.js b/crypto/actions/BlocksoftBalances/BlocksoftBalances.js new file mode 100644 index 000000000..14ae42430 --- /dev/null +++ b/crypto/actions/BlocksoftBalances/BlocksoftBalances.js @@ -0,0 +1,198 @@ +/** + * @author Ksu + * @version 0.5 + */ +import BlocksoftDispatcher from '../../blockchains/BlocksoftDispatcher'; +import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog'; +import BlocksoftDict from '../../common/BlocksoftDict'; +import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings'; + +class BlocksoftBalances { + /** + * @type {{}} + * @private + */ + _processor = {}; + + /** + * @type {{}} + * @private + */ + _allSettings = {}; + + /** + * @type {{currencyCode, address, fee, jsonData, walletHash}} + * @private + */ + _data = {}; + + /** + * @type {{}} + * @private + */ + _currencySettings = {}; + + /** + * @param {string} currencyCode + * @return {BlocksoftBalances} + */ + setCurrencyCode(currencyCode) { + this._data.currencyCode = currencyCode; + if (!this._processor[currencyCode]) { + /** + * @type {EthScannerProcessor|BtcScannerProcessor|UsdtScannerProcessor} + */ + this._processor[currencyCode] = + BlocksoftDispatcher.getScannerProcessor(currencyCode); + this._allSettings[currencyCode] = + BlocksoftDict.getCurrencyAllSettings(currencyCode); + } + this._currencySettings = this._allSettings[currencyCode]; + return this; + } + + setWalletHash(walletHash) { + this._data.walletHash = walletHash; + return this; + } + + /** + * @param {string|string[]} address + * @return {BlocksoftBalances} + */ + setAddress(address) { + this._data.address = + typeof address.trim !== 'undefined' ? address.trim() : address; + return this; + } + + /** + * @param {*} jsonData + * @return {BlocksoftBalances} + */ + setAdditional(jsonData) { + this._data.jsonData = jsonData; + return this; + } + + /** + * @return {Promise<{balance:*, frozen: *, frozenEnergy: *, balanceAvailable: *, balanceStaked: *, provider:*, unconfirmed:*, addresses : *, balanceScanBlock : *}>} + */ + async getBalance(source) { + BlocksoftCryptoLog.log( + 'BlocksoftBalances.getBalance ' + + this._data.currencyCode + + ' ' + + this._data.address + + ' started' + ); + const currencyCode = this._data.currencyCode; + if (!currencyCode) { + throw new Error('plz set currencyCode before calling'); + } + if ( + currencyCode === 'BTC' && + this._data.address.toString().substr(0, 1) === 'm' + ) { + throw new Error( + 'plz check btc address as its testnet and mainnet is selected' + ); + } + let res; + try { + res = await this._processor[currencyCode].getBalanceBlockchain( + this._data.address, + this._data.jsonData, + this._data.walletHash, + source + ); + res.address = this._data.address; + res.currencyCode = currencyCode; + } catch (e) { + e.code = 'ERROR_SYSTEM'; + throw e; + } + BlocksoftCryptoLog.log( + 'BlocksoftBalances.getBalance ' + + this._data.currencyCode + + ' ' + + this._data.address + + ' ended ' + + JSON.stringify(res) + ); + return res; + } + + getBalanceHodl() { + const currencyCode = this._data.currencyCode; + if (!currencyCode) { + throw new Error('plz set currencyCode before calling'); + } + let hodl = 0; + if (currencyCode === 'XRP') { + hodl = BlocksoftExternalSettings.getStatic('XRP_MIN'); + } else if (currencyCode === 'XLM') { + hodl = 1; + } + return hodl; + } + + async getResources(source) { + const currencyCode = this._data.currencyCode; + if (!currencyCode) { + throw new Error('plz set currencyCode before calling'); + } + let res; + try { + res = await this._processor[currencyCode].getResourcesBlockchain( + this._data.address, + this._data.jsonData, + this._data.walletHash, + source + ); + } catch (e) { + e.code = 'ERROR_SYSTEM'; + throw e; + } + BlocksoftCryptoLog.log( + 'BlocksoftBalances.getResources ' + + this._data.currencyCode + + ' ' + + this._data.address + + ' ended ' + + JSON.stringify(res) + ); + return res; + } + + async isMultisig(source) { + const currencyCode = this._data.currencyCode; + if (!currencyCode) { + throw new Error('plz set currencyCode before calling'); + } + let res; + try { + res = await this._processor[currencyCode].isMultisigBlockchain( + this._data.address, + this._data.jsonData, + this._data.walletHash, + source + ); + } catch (e) { + e.code = 'ERROR_SYSTEM'; + throw e; + } + BlocksoftCryptoLog.log( + 'BlocksoftBalances.isMultisigBlockchain ' + + this._data.currencyCode + + ' ' + + this._data.address + + ' ended ' + + JSON.stringify(res) + ); + return res; + } +} + +const singleBlocksoftBalances = new BlocksoftBalances(); +export default singleBlocksoftBalances; diff --git a/crypto/actions/BlocksoftInvoice/BlocksoftInvoice.js b/crypto/actions/BlocksoftInvoice/BlocksoftInvoice.js new file mode 100644 index 000000000..16fc55945 --- /dev/null +++ b/crypto/actions/BlocksoftInvoice/BlocksoftInvoice.js @@ -0,0 +1,116 @@ +/** + * @author Ksu + * @version 0.5 + */ +import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog' +import BlocksoftDispatcher from '../../blockchains/BlocksoftDispatcher' + +class BlocksoftInvoice { + + /** + * @type {{}} + * @private + */ + _processor = {} + /** + * @type {{privateKey, address, amount, feeForTx, currencyCode, addressForChange, nSequence, jsonData, memo}} + * @private + */ + _data = {} + + /** + * @param address + * @return {BlocksoftInvoice} + */ + setAddress(address) { + this._data.address = address + return this + } + + /** + * @param jsonData + * @return {BlocksoftInvoice} + */ + setAdditional(jsonData) { + this._data.jsonData = jsonData + return this + } + + /** + * @param amount + * @return {BlocksoftInvoice} + */ + setAmount(amount) { + this._data.amount = amount + return this + } + + /** + * @param memo + * @return {BlocksoftInvoice} + */ + setMemo(memo) { + this._data.memo = memo + return this + } + + /** + * @param currencyCode + * @return {BlocksoftInvoice} + */ + setCurrencyCode(currencyCode) { + this._data.currencyCode = currencyCode + if (!this._processor[currencyCode]) { + + } + return this + } + + + /** + * @return {Promise<{hash}>} + */ + async createInvoice() { + const currencyCode = this._data.currencyCode + if (!currencyCode) { + throw new Error('plz set currencyCode before calling') + } + let res = '' + try { + BlocksoftCryptoLog.log(`BlocksoftInvoice.createInvoice ${currencyCode} started`, this._data) + res = await this._processor[currencyCode].createInvoice(this._data) + BlocksoftCryptoLog.log(`BlocksoftInvoice.createInvoice ${currencyCode} finished`, res) + } catch (e) { + // noinspection ES6MissingAwait + BlocksoftCryptoLog.err(`BlocksoftInvoice.createInvoice ${currencyCode} error ` + e.message, e.data ? e.data : e) + throw e + } + + return res + } + + async checkInvoice(hash) { + const currencyCode = this._data.currencyCode + if (!currencyCode) { + throw new Error('plz set currencyCode before calling') + } + let res = '' + try { + BlocksoftCryptoLog.log(`BlocksoftInvoice.checkInvoice ${currencyCode} started`, this._data) + res = await this._processor[currencyCode].checkInvoice(hash, this._data) + BlocksoftCryptoLog.log(`BlocksoftInvoice.checkInvoice ${currencyCode} finished`, res) + } catch (e) { + if (e.message.indexOf('not a valid invoice') === -1) { + // noinspection ES6MissingAwait + BlocksoftCryptoLog.err(`BlocksoftInvoice.checkInvoice ${currencyCode} error ` + e.message, e.data ? e.data : e) + } + throw e + } + + return res + } +} + +const singleBlocksoftInvoice = new BlocksoftInvoice() + +export default singleBlocksoftInvoice diff --git a/crypto/actions/BlocksoftKeys/BlocksoftKeys.js b/crypto/actions/BlocksoftKeys/BlocksoftKeys.js new file mode 100644 index 000000000..251022c05 --- /dev/null +++ b/crypto/actions/BlocksoftKeys/BlocksoftKeys.js @@ -0,0 +1,419 @@ +/** + * @author Ksu + * @version 0.5 + */ +import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog' +import BlocksoftDict from '@crypto/common/BlocksoftDict' +import BlocksoftKeysUtils from '@crypto/actions/BlocksoftKeys/BlocksoftKeysUtils' + + +import * as BlocksoftRandom from 'react-native-blocksoft-random' +import BlocksoftDispatcher from '../../blockchains/BlocksoftDispatcher' +import BlocksoftKeysScam from '@crypto/actions/BlocksoftKeys/BlocksoftKeysScam' +import { strings } from '@app/services/i18n' + +const bip32 = require('bip32') +const bip39 = require('bip39') +const bip44Constants = require('../../common/ext/bip44-constants') +const networksConstants = require('../../common/ext/networks-constants') + +const bs58check = require('bs58check') + + +const ETH_CACHE = {} +const CACHE = {} +const CACHE_ROOTS = {} + + +class BlocksoftKeys { + + constructor() { + this._bipHex = {} + let currency + for (currency of bip44Constants) { + this._bipHex[currency[1]] = currency[0] + } + this._getRandomBytesFunction = BlocksoftRandom.getRandomBytes + } + + /** + * create new mnemonic object (also gives hash to store in public db) + * @param {int} size + * @return {Promise<{mnemonic: string, hash: string}>} + */ + async newMnemonic(size = 256) { + BlocksoftCryptoLog.log(`BlocksoftKeys newMnemonic called`) + + /* let mnemonic = false + if (USE_TON) { + for (let i = 0; i < 10000; i++) { + let random = await this._getRandomBytesFunction(size / 8) + random = Buffer.from(random, 'base64') + let testMnemonic = bip39.entropyToMnemonic(random) + if (await BlocksoftKeysUtils.tonCheckRevert(testMnemonic)) { + mnemonic = testMnemonic + break + } + BlocksoftCryptoLog.log('step ' + i + ' not valid ton ' + testMnemonic) + } + if (!mnemonic) { + throw new Error('TON Mnemonic is not validating') + } + } else { + let random = await this._getRandomBytesFunction(size / 8) + random = Buffer.from(random, 'base64') + mnemonic = bip39.entropyToMnemonic(random) + } */ + let random = await this._getRandomBytesFunction(size / 8) + random = Buffer.from(random, 'base64') + const mnemonic = bip39.entropyToMnemonic(random) + const hash = BlocksoftKeysUtils.hashMnemonic(mnemonic) + BlocksoftCryptoLog.log(`BlocksoftKeys newMnemonic finished`) + return { mnemonic, hash } + } + + /** + * @param {string} mnemonic + * @return {Promise} + */ + async validateMnemonic(mnemonic) { + BlocksoftCryptoLog.log(`BlocksoftKeys validateMnemonic called`) + if (await BlocksoftKeysScam.isScamMnemonic(mnemonic)) { + throw new Error(strings('settings.walletList.scamImport')) + } + const result = await bip39.validateMnemonic(mnemonic) + if (!result) { + throw new Error('invalid mnemonic for bip39') + } + return result + } + + + /** + * @param {string} data.mnemonic + * @param {string} data.walletHash + * @param {string|string[]} data.currencyCode = all + * @param {int} data.fromIndex = 0 + * @param {int} data.toIndex = 100 + * @param {boolean} data.fullTree = false + * @param {array} data.derivations.BTC + * @param {array} data.derivations.BTC_SEGWIT + * @return {Promise<{currencyCode:[{address, privateKey, path, index, type}]}>} + */ + async discoverAddresses(data, source) { + + const logData = { ...data } + if (typeof logData.mnemonic !== 'undefined') logData.mnemonic = '***' + + BlocksoftCryptoLog.log(`BlocksoftKeys discoverAddresses called from ${source}`, JSON.stringify(logData).substring(0, 200)) + + let toDiscover = BlocksoftDict.Codes + if (data.currencyCode) { + if (typeof data.currencyCode === 'string') { + toDiscover = [data.currencyCode] + } else { + toDiscover = data.currencyCode + } + } + const fromIndex = data.fromIndex ? data.fromIndex : 0 + const toIndex = data.toIndex ? data.toIndex : 10 + const fullTree = data.fullTree ? data.fullTree : false + + + const results = {} + + + const mnemonicCache = data.mnemonic.toLowerCase() + let bitcoinRoot = false + let currencyCode + let settings + const seed = await BlocksoftKeysUtils.bip39MnemonicToSeed(data.mnemonic.toLowerCase()) + for (currencyCode of toDiscover) { + results[currencyCode] = [] + try { + settings = BlocksoftDict.getCurrencyAllSettings(currencyCode, 'BlocksoftKeys') + } catch (e) { + // do nothing for now + continue + } + + let hexes = [] + if (settings.addressCurrencyCode) { + hexes.push(this._bipHex[settings.addressCurrencyCode]) + if (!this._bipHex[settings.addressCurrencyCode]) { + throw new Error('UNKNOWN_CURRENCY_CODE SETTED ' + settings.addressCurrencyCode) + } + } + + if (this._bipHex[currencyCode]) { + hexes.push(this._bipHex[currencyCode]) + } else if (!settings.addressCurrencyCode) { + if (settings.extendsProcessor && this._bipHex[settings.extendsProcessor]) { + hexes.push(this._bipHex[settings.extendsProcessor]) + } else { + throw new Error('UNKNOWN_CURRENCY_CODE ' + currencyCode + ' in bipHex AND NO SETTED addressCurrencyCode') + } + } + + let isAlreadyMain = false + + if (!data.fullTree) { + hexes = [hexes[0]] + } + + const hexesCache = mnemonicCache + '_' + settings.addressProcessor + '_fromINDEX_' + fromIndex + '_' + JSON.stringify(hexes) + let hasDerivations = false + if (typeof data.derivations !== 'undefined' && typeof data.derivations[currencyCode] !== 'undefined' && data.derivations[currencyCode]) { + hasDerivations = true + } + if (typeof CACHE[hexesCache] === 'undefined' || hasDerivations) { + BlocksoftCryptoLog.log(`BlocksoftKeys will discover ${settings.addressProcessor}`) + let root = false + if (typeof networksConstants[currencyCode] !== 'undefined') { + root = await this.getBip32Cached(data.mnemonic, networksConstants[currencyCode], seed) + } else { + if (!bitcoinRoot) { + bitcoinRoot = await this.getBip32Cached(data.mnemonic) + } + root = bitcoinRoot + } + // BIP32 Extended Private Key to check - uncomment + // let childFirst = root.derivePath('m/44\'/2\'/0\'/0') + // BlocksoftCryptoLog.log(childFirst.toBase58()) + + /** + * @type {EthAddressProcessor|BtcAddressProcessor} + */ + const processor = await BlocksoftDispatcher.innerGetAddressProcessor(settings) + + try { + await processor.setBasicRoot(root) + } catch (e) { + e.message += ' while doing ' + JSON.stringify(settings) + throw e + } + let currentFromIndex = fromIndex + let currentToIndex = toIndex + let currentFullTree = fullTree + + BlocksoftCryptoLog.log(`BlocksoftKeys discoverAddresses ${currencyCode} currentFromIndex.1 ${currentFromIndex}`) + if (hasDerivations) { + let derivation = {path : '', alreadyShown : 0, walletPubId : 0} + let maxIndex = 0 + for (derivation of data.derivations[currencyCode]) { + const child = root.derivePath(derivation.path) + const tmp = derivation.path.split('/') + const result = await processor.getAddress(child.privateKey, { + publicKey: child.publicKey, + walletHash: data.walletHash, + derivationPath : derivation.path, + }, data, seed, 'discoverAddresses') + result.basicPrivateKey = child.privateKey.toString('hex') + result.basicPublicKey = child.publicKey.toString('hex') + result.path = derivation.path + result.alreadyShown = derivation.alreadyShown + result.walletPubId = derivation.walletPubId || 0 + result.index = tmp[5] + if (maxIndex < result.index) { + maxIndex = result.index*1 + } + result.type = 'main' + results[currencyCode].push(result) + } + if (maxIndex > 0) { + // noinspection PointlessArithmeticExpressionJS + currentFromIndex = maxIndex*1 + 1 + currentToIndex = currentFromIndex*1+ 10 + currentFullTree = true + BlocksoftCryptoLog.log(`BlocksoftKeys ${currencyCode} discoverAddresses currentFromIndex.2 ${currentFromIndex}`) + } + } + + + let suffixes + if (currencyCode === 'SOL') { + suffixes = [ + { 'type': 'main', 'suffix': false, after : `'/0'` }, + { 'type': 'no_scan', 'suffix': false, after: `'` } + ] + } else if (currentFullTree) { + suffixes = [ + { 'type': 'main', 'suffix': `0'/0` }, + { 'type': 'change', 'suffix': `0'/1` } + // { 'type': 'second', 'suffix': `1'/0` }, + // { 'type': 'secondchange', 'suffix': `1'/1` } + ] + } else { + suffixes = [ + { 'type': 'main', 'suffix': `0'/0` } + ] + if (currencyCode === 'BTC_SEGWIT_COMPATIBLE') { + suffixes = [ + { 'type': 'main', 'suffix': '0/1' } // heh + ] + } + hexes = [hexes[0]] + } + + + let hex + for (hex of hexes) { + if (isAlreadyMain) { + suffixes[0].type = 'second' + } + isAlreadyMain = true + + if (currentFromIndex >= 0 && currentToIndex >= 0) { + for (let index = currentFromIndex; index < currentToIndex; index++) { + let suffix + for (suffix of suffixes) { + const path = suffix.suffix ? `m/${hex}'/${suffix.suffix}/${index}` : `m/${hex}'/${index}${suffix.after}` + let privateKey = false + let publicKey = false + if (currencyCode === 'SOL' || currencyCode === 'XLM' || currencyCode === 'WAVES' || currencyCode === 'ASH') { + // @todo move to coin address processor + } else { + const child = root.derivePath(path) + privateKey = child.privateKey + publicKey = child.publicKey + } + const result = await processor.getAddress(privateKey, { + publicKey, + walletHash: data.walletHash, + derivationPath : path, + derivationIndex : index, + derivationType : suffix.type + }, data, seed, 'discoverAddresses2') + if (result) { + if (privateKey) { + result.basicPrivateKey = privateKey.toString('hex') + result.basicPublicKey = publicKey.toString('hex') + } + result.path = path + result.index = index + result.alreadyShown = 0 + result.type = suffix.type + results[currencyCode].push(result) + } + } + } + } + } + CACHE[hexesCache] = results[currencyCode] + if (currencyCode === 'ETH') { + ETH_CACHE[mnemonicCache] = results[currencyCode][0] + } + } else { + BlocksoftCryptoLog.log(`BlocksoftKeys will be from cache ${settings.addressProcessor}`) + results[currencyCode] = CACHE[hexesCache] + if (currencyCode === 'USDT') { + results[currencyCode] = [results[currencyCode][0]] + } + } + } + BlocksoftCryptoLog.log(`BlocksoftKeys discoverAddresses finished`) + return results + } + + async getSeedCached(mnemonic) { + BlocksoftCryptoLog.log(`BlocksoftKeys bip39MnemonicToSeed started`) + const mnemonicCache = mnemonic.toLowerCase() + if (typeof CACHE[mnemonicCache] === 'undefined') { + CACHE[mnemonicCache] = await BlocksoftKeysUtils.bip39MnemonicToSeed(mnemonic.toLowerCase()) + } + const seed = CACHE[mnemonicCache] // will be rechecked on saving + BlocksoftCryptoLog.log(`BlocksoftKeys bip39MnemonicToSeed ended`) + return seed + } + + async getBip32Cached(mnemonic, network = false, seed = false) { + const mnemonicCache = mnemonic.toLowerCase() + '_' + (network || 'btc') + if (typeof CACHE_ROOTS[mnemonicCache] === 'undefined') { + if (!seed) { + seed = await this.getSeedCached(mnemonic) + } + CACHE_ROOTS[mnemonicCache] = network ? bip32.fromSeed(seed, network) : bip32.fromSeed(seed) + } + return CACHE_ROOTS[mnemonicCache] + } + + getEthCached(mnemonicCache) { + if (typeof ETH_CACHE[mnemonicCache] === 'undefined') return false + return ETH_CACHE[mnemonicCache] + } + + setEthCached(mnemonic, result) { + const mnemonicCache = mnemonic.toLowerCase() + ETH_CACHE[mnemonicCache] = result + } + + /** + * @param {string} data.mnemonic + * @param {string} data.currencyCode + * @param {string} data.derivationPath + * @param {string} data.derivationIndex + * @param {string} data.derivationType + * @return {Promise<{address, privateKey}>} + */ + async discoverOne(data) { + const seed = await BlocksoftKeysUtils.bip39MnemonicToSeed(data.mnemonic.toLowerCase()) + const root = bip32.fromSeed(seed) + const child = root.derivePath(data.derivationPath.replace(/quote/g, '\'')) + /** + * @type {EthAddressProcessor|BtcAddressProcessor} + */ + const processor = await BlocksoftDispatcher.getAddressProcessor(data.currencyCode) + processor.setBasicRoot(root) + return processor.getAddress(child.privateKey, { + derivationPath : data.derivationPath, + derivationIndex: data.derivationIndex, + derivationType: data.derivationType, + publicKey : child.publicKey + }, data, seed, 'discoverOne') + } + + /** + * @param {string} data.mnemonic + * @param {string} data.currencyCode + * @return {Promise<{address, privateKey}>} + */ + async discoverXpub(data) { + const seed = await BlocksoftKeysUtils.bip39MnemonicToSeed(data.mnemonic.toLowerCase()) + const root = bip32.fromSeed(seed) + let path = `m/44'/0'/0'` + let version = 0x0488B21E // xpub + if (data.currencyCode === 'BTC_SEGWIT') { + path = `m/84'/0'/0'` + version = 0x04b24746 // https://github.com/satoshilabs/slips/blob/master/slip-0132.md + } else if (data.currencyCode === 'BTC_SEGWIT_COMPATIBLE') { + path = `m/49'/0'/0'` + version = 0x049d7cb2 + } + BlocksoftCryptoLog.log('BlocksoftKeys.discoverXpub derive started ' + JSON.stringify(path)) + const rootWallet = root.derivePath(path) + BlocksoftCryptoLog.log('BlocksoftKeys.discoverXpub derived, bs58 started ' + JSON.stringify(path)) + const res = bs58check.encode(serialize(rootWallet, version, rootWallet.publicKey)) + BlocksoftCryptoLog.log('BlocksoftKeys.discoverXpub res ' + JSON.stringify(path), res) + return res + } +} + +function serialize(hdkey, version, key, LEN = 78) { + // => version(4) || depth(1) || fingerprint(4) || index(4) || chain(32) || key(33) + const buffer = Buffer.allocUnsafe(LEN) + + buffer.writeUInt32BE(version, 0) + buffer.writeUInt8(hdkey.depth, 4) + + const fingerprint = hdkey.depth ? hdkey.parentFingerprint : 0x00000000 + buffer.writeUInt32BE(fingerprint, 5) + buffer.writeUInt32BE(hdkey.index, 9) + + hdkey.chainCode.copy(buffer, 13) + key.copy(buffer, 45) + + return buffer +} + +const singleBlocksoftKeys = new BlocksoftKeys() +export default singleBlocksoftKeys diff --git a/crypto/actions/BlocksoftKeys/BlocksoftKeysScam.js b/crypto/actions/BlocksoftKeys/BlocksoftKeysScam.js new file mode 100644 index 000000000..4adf26ca9 --- /dev/null +++ b/crypto/actions/BlocksoftKeys/BlocksoftKeysScam.js @@ -0,0 +1,58 @@ +/** + * @author Ksu + * @version 0.5 + */ +import BlocksoftAxios from '@crypto/common/BlocksoftAxios' + +const scamSeeds = require('@crypto/common/ext/scam-seeds') + +let CACHE_SEEDS = scamSeeds +let CACHE_CASHBACKS = {} +let CACHE_SEEDS_TIME = 0 +const TIMEOUT_SEEDS = 60000 +const PROXY_SEEDS = 'https://proxy.trustee.deals/seeds/getScam' + +const _get = async () => { + const now = new Date().getTime() + if ((now - CACHE_SEEDS_TIME) > TIMEOUT_SEEDS) { + const tmp = await BlocksoftAxios.get(PROXY_SEEDS) + CACHE_SEEDS_TIME = now + if (tmp.data?.data?.seeds) { + for (const seed of tmp.data.data.seeds) { + CACHE_SEEDS[seed] = 1 + } + } + if (tmp.data?.data?.cashbacks) { + for (const cb of tmp.data.data.cashbacks) { + CACHE_CASHBACKS[cb] = 1 + } + } + } +} +const isScamMnemonic = async (mnemonic) => { + await _get() + if (typeof CACHE_SEEDS[mnemonic] !== 'undefined') { + return true + } + return false +} +const isScamCashback = async (cb) => { + await _get() + if (typeof CACHE_CASHBACKS[cb] !== 'undefined') { + return true + } + return false +} + +const isScamCashbackStatic = (cb) => { + if (typeof CACHE_CASHBACKS[cb] !== 'undefined') { + return true + } + return false +} + +export default { + isScamMnemonic, + isScamCashback, + isScamCashbackStatic +} diff --git a/crypto/actions/BlocksoftKeys/BlocksoftKeysUtils.js b/crypto/actions/BlocksoftKeys/BlocksoftKeysUtils.js new file mode 100644 index 000000000..9eaf2a1b2 --- /dev/null +++ b/crypto/actions/BlocksoftKeys/BlocksoftKeysUtils.js @@ -0,0 +1,106 @@ +/** + * @author Ksu + * @version 0.5 + */ +const createHash = require('create-hash') +const createHmac = require('create-hmac') + +const createHmacPDFK2Sizes = { + md5: 16, + sha1: 20, + sha224: 28, + sha256: 32, + sha384: 48, + sha512: 64, + rmd160: 20, + ripemd160: 20 +} + +const {pbkdf2} = require('react-native-fast-crypto') + +const DEFAULT_WORDS = require('./_words/english.json') + +class BlocksoftKeysUtils { + + static _pbkdf2(password, salt, iterations, keylen, digest) { + + digest = digest || 'sha1' + + const DK = Buffer.allocUnsafe(keylen) + const block1 = Buffer.allocUnsafe(salt.length + 4) + salt.copy(block1, 0, 0, salt.length) + + let destPos = 0 + const hLen = createHmacPDFK2Sizes[digest] + const l = Math.ceil(keylen / hLen) + + for (let i = 1; i <= l; i++) { + block1.writeUInt32BE(i, salt.length) + + // noinspection JSUnresolvedFunction + const T = createHmac(digest, password).update(block1).digest() + let U = T + + for (let j = 1; j < iterations; j++) { + // noinspection JSUnresolvedFunction + U = createHmac(digest, password).update(U).digest() + for (let k = 0; k < hLen; k++) T[k] ^= U[k] + } + + T.copy(DK, destPos) + destPos += hLen + } + + return DK + } + + static recheckMnemonic(mnemonic) { + const words = mnemonic.trim().toLowerCase().split(/\s+/g) + const checked = [] + let word + for (word of words) { + if (!word || word.length < 2) continue + // noinspection JSUnresolvedFunction + const index = DEFAULT_WORDS.indexOf(word) + if (index === -1) { + throw new Error('BlocksoftKeysStorage invalid word ' + word) + } + checked.push(word) + } + if (checked.length <= 11) { + throw new Error('BlocksoftKeysStorage invalid words length ' + mnemonic) + } + return checked.join(' ') + } + + /** + * make hash for mnemonic string + * @param {string} mnemonic + * @return {string} + */ + static hashMnemonic(mnemonic) { + // noinspection JSUnresolvedFunction + return createHash('sha256').update(mnemonic).digest('hex').substr(0, 32) + } + + static async bip39MnemonicToSeed(mnemonic, password) { + if (!mnemonic) { + throw new Error('bip39MnemonicToSeed is empty') + } + + function salt(password) { + return 'mnemonic' + (password || '') + } + + const mnemonicBuffer = Buffer.from((mnemonic || ''), 'utf8') + const saltBuffer = Buffer.from(salt(password || ''), 'utf8') + try { + const tmp2 = await pbkdf2.deriveAsync(mnemonicBuffer, saltBuffer, 2048, 64, 'sha512') + return Buffer.from(tmp2) + } catch (e) { + return BlocksoftKeysUtils._pbkdf2(mnemonicBuffer, saltBuffer, 2048, 64, 'sha512') + } + } +} + +export default BlocksoftKeysUtils diff --git a/crypto/actions/BlocksoftKeys/_words/english.json b/crypto/actions/BlocksoftKeys/_words/english.json new file mode 100644 index 000000000..cbc9d9b7c --- /dev/null +++ b/crypto/actions/BlocksoftKeys/_words/english.json @@ -0,0 +1,2050 @@ +[ + "abandon", + "ability", + "able", + "about", + "above", + "absent", + "absorb", + "abstract", + "absurd", + "abuse", + "access", + "accident", + "account", + "accuse", + "achieve", + "acid", + "acoustic", + "acquire", + "across", + "act", + "action", + "actor", + "actress", + "actual", + "adapt", + "add", + "addict", + "address", + "adjust", + "admit", + "adult", + "advance", + "advice", + "aerobic", + "affair", + "afford", + "afraid", + "again", + "age", + "agent", + "agree", + "ahead", + "aim", + "air", + "airport", + "aisle", + "alarm", + "album", + "alcohol", + "alert", + "alien", + "all", + "alley", + "allow", + "almost", + "alone", + "alpha", + "already", + "also", + "alter", + "always", + "amateur", + "amazing", + "among", + "amount", + "amused", + "analyst", + "anchor", + "ancient", + "anger", + "angle", + "angry", + "animal", + "ankle", + "announce", + "annual", + "another", + "answer", + "antenna", + "antique", + "anxiety", + "any", + "apart", + "apology", + "appear", + "apple", + "approve", + "april", + "arch", + "arctic", + "area", + "arena", + "argue", + "arm", + "armed", + "armor", + "army", + "around", + "arrange", + "arrest", + "arrive", + "arrow", + "art", + "artefact", + "artist", + "artwork", + "ask", + "aspect", + "assault", + "asset", + "assist", + "assume", + "asthma", + "athlete", + "atom", + "attack", + "attend", + "attitude", + "attract", + "auction", + "audit", + "august", + "aunt", + "author", + "auto", + "autumn", + "average", + "avocado", + "avoid", + "awake", + "aware", + "away", + "awesome", + "awful", + "awkward", + "axis", + "baby", + "bachelor", + "bacon", + "badge", + "bag", + "balance", + "balcony", + "ball", + "bamboo", + "banana", + "banner", + "bar", + "barely", + "bargain", + "barrel", + "base", + "basic", + "basket", + "battle", + "beach", + "bean", + "beauty", + "because", + "become", + "beef", + "before", + "begin", + "behave", + "behind", + "believe", + "below", + "belt", + "bench", + "benefit", + "best", + "betray", + "better", + "between", + "beyond", + "bicycle", + "bid", + "bike", + "bind", + "biology", + "bird", + "birth", + "bitter", + "black", + "blade", + "blame", + "blanket", + "blast", + "bleak", + "bless", + "blind", + "blood", + "blossom", + "blouse", + "blue", + "blur", + "blush", + "board", + "boat", + "body", + "boil", + "bomb", + "bone", + "bonus", + "book", + "boost", + "border", + "boring", + "borrow", + "boss", + "bottom", + "bounce", + "box", + "boy", + "bracket", + "brain", + "brand", + "brass", + "brave", + "bread", + "breeze", + "brick", + "bridge", + "brief", + "bright", + "bring", + "brisk", + "broccoli", + "broken", + "bronze", + "broom", + "brother", + "brown", + "brush", + "bubble", + "buddy", + "budget", + "buffalo", + "build", + "bulb", + "bulk", + "bullet", + "bundle", + "bunker", + "burden", + "burger", + "burst", + "bus", + "business", + "busy", + "butter", + "buyer", + "buzz", + "cabbage", + "cabin", + "cable", + "cactus", + "cage", + "cake", + "call", + "calm", + "camera", + "camp", + "can", + "canal", + "cancel", + "candy", + "cannon", + "canoe", + "canvas", + "canyon", + "capable", + "capital", + "captain", + "car", + "carbon", + "card", + "cargo", + "carpet", + "carry", + "cart", + "case", + "cash", + "casino", + "castle", + "casual", + "cat", + "catalog", + "catch", + "category", + "cattle", + "caught", + "cause", + "caution", + "cave", + "ceiling", + "celery", + "cement", + "census", + "century", + "cereal", + "certain", + "chair", + "chalk", + "champion", + "change", + "chaos", + "chapter", + "charge", + "chase", + "chat", + "cheap", + "check", + "cheese", + "chef", + "cherry", + "chest", + "chicken", + "chief", + "child", + "chimney", + "choice", + "choose", + "chronic", + "chuckle", + "chunk", + "churn", + "cigar", + "cinnamon", + "circle", + "citizen", + "city", + "civil", + "claim", + "clap", + "clarify", + "claw", + "clay", + "clean", + "clerk", + "clever", + "click", + "client", + "cliff", + "climb", + "clinic", + "clip", + "clock", + "clog", + "close", + "cloth", + "cloud", + "clown", + "club", + "clump", + "cluster", + "clutch", + "coach", + "coast", + "coconut", + "code", + "coffee", + "coil", + "coin", + "collect", + "color", + "column", + "combine", + "come", + "comfort", + "comic", + "common", + "company", + "concert", + "conduct", + "confirm", + "congress", + "connect", + "consider", + "control", + "convince", + "cook", + "cool", + "copper", + "copy", + "coral", + "core", + "corn", + "correct", + "cost", + "cotton", + "couch", + "country", + "couple", + "course", + "cousin", + "cover", + "coyote", + "crack", + "cradle", + "craft", + "cram", + "crane", + "crash", + "crater", + "crawl", + "crazy", + "cream", + "credit", + "creek", + "crew", + "cricket", + "crime", + "crisp", + "critic", + "crop", + "cross", + "crouch", + "crowd", + "crucial", + "cruel", + "cruise", + "crumble", + "crunch", + "crush", + "cry", + "crystal", + "cube", + "culture", + "cup", + "cupboard", + "curious", + "current", + "curtain", + "curve", + "cushion", + "custom", + "cute", + "cycle", + "dad", + "damage", + "damp", + "dance", + "danger", + "daring", + "dash", + "daughter", + "dawn", + "day", + "deal", + "debate", + "debris", + "decade", + "december", + "decide", + "decline", + "decorate", + "decrease", + "deer", + "defense", + "define", + "defy", + "degree", + "delay", + "deliver", + "demand", + "demise", + "denial", + "dentist", + "deny", + "depart", + "depend", + "deposit", + "depth", + "deputy", + "derive", + "describe", + "desert", + "design", + "desk", + "despair", + "destroy", + "detail", + "detect", + "develop", + "device", + "devote", + "diagram", + "dial", + "diamond", + "diary", + "dice", + "diesel", + "diet", + "differ", + "digital", + "dignity", + "dilemma", + "dinner", + "dinosaur", + "direct", + "dirt", + "disagree", + "discover", + "disease", + "dish", + "dismiss", + "disorder", + "display", + "distance", + "divert", + "divide", + "divorce", + "dizzy", + "doctor", + "document", + "dog", + "doll", + "dolphin", + "domain", + "donate", + "donkey", + "donor", + "door", + "dose", + "double", + "dove", + "draft", + "dragon", + "drama", + "drastic", + "draw", + "dream", + "dress", + "drift", + "drill", + "drink", + "drip", + "drive", + "drop", + "drum", + "dry", + "duck", + "dumb", + "dune", + "during", + "dust", + "dutch", + "duty", + "dwarf", + "dynamic", + "eager", + "eagle", + "early", + "earn", + "earth", + "easily", + "east", + "easy", + "echo", + "ecology", + "economy", + "edge", + "edit", + "educate", + "effort", + "egg", + "eight", + "either", + "elbow", + "elder", + "electric", + "elegant", + "element", + "elephant", + "elevator", + "elite", + "else", + "embark", + "embody", + "embrace", + "emerge", + "emotion", + "employ", + "empower", + "empty", + "enable", + "enact", + "end", + "endless", + "endorse", + "enemy", + "energy", + "enforce", + "engage", + "engine", + "enhance", + "enjoy", + "enlist", + "enough", + "enrich", + "enroll", + "ensure", + "enter", + "entire", + "entry", + "envelope", + "episode", + "equal", + "equip", + "era", + "erase", + "erode", + "erosion", + "error", + "erupt", + "escape", + "essay", + "essence", + "estate", + "eternal", + "ethics", + "evidence", + "evil", + "evoke", + "evolve", + "exact", + "example", + "excess", + "exchange", + "excite", + "exclude", + "excuse", + "execute", + "exercise", + "exhaust", + "exhibit", + "exile", + "exist", + "exit", + "exotic", + "expand", + "expect", + "expire", + "explain", + "expose", + "express", + "extend", + "extra", + "eye", + "eyebrow", + "fabric", + "face", + "faculty", + "fade", + "faint", + "faith", + "fall", + "false", + "fame", + "family", + "famous", + "fan", + "fancy", + "fantasy", + "farm", + "fashion", + "fat", + "fatal", + "father", + "fatigue", + "fault", + "favorite", + "feature", + "february", + "federal", + "fee", + "feed", + "feel", + "female", + "fence", + "festival", + "fetch", + "fever", + "few", + "fiber", + "fiction", + "field", + "figure", + "file", + "film", + "filter", + "final", + "find", + "fine", + "finger", + "finish", + "fire", + "firm", + "first", + "fiscal", + "fish", + "fit", + "fitness", + "fix", + "flag", + "flame", + "flash", + "flat", + "flavor", + "flee", + "flight", + "flip", + "float", + "flock", + "floor", + "flower", + "fluid", + "flush", + "fly", + "foam", + "focus", + "fog", + "foil", + "fold", + "follow", + "food", + "foot", + "force", + "forest", + "forget", + "fork", + "fortune", + "forum", + "forward", + "fossil", + "foster", + "found", + "fox", + "fragile", + "frame", + "frequent", + "fresh", + "friend", + "fringe", + "frog", + "front", + "frost", + "frown", + "frozen", + "fruit", + "fuel", + "fun", + "funny", + "furnace", + "fury", + "future", + "gadget", + "gain", + "galaxy", + "gallery", + "game", + "gap", + "garage", + "garbage", + "garden", + "garlic", + "garment", + "gas", + "gasp", + "gate", + "gather", + "gauge", + "gaze", + "general", + "genius", + "genre", + "gentle", + "genuine", + "gesture", + "ghost", + "giant", + "gift", + "giggle", + "ginger", + "giraffe", + "girl", + "give", + "glad", + "glance", + "glare", + "glass", + "glide", + "glimpse", + "globe", + "gloom", + "glory", + "glove", + "glow", + "glue", + "goat", + "goddess", + "gold", + "good", + "goose", + "gorilla", + "gospel", + "gossip", + "govern", + "gown", + "grab", + "grace", + "grain", + "grant", + "grape", + "grass", + "gravity", + "great", + "green", + "grid", + "grief", + "grit", + "grocery", + "group", + "grow", + "grunt", + "guard", + "guess", + "guide", + "guilt", + "guitar", + "gun", + "gym", + "habit", + "hair", + "half", + "hammer", + "hamster", + "hand", + "happy", + "harbor", + "hard", + "harsh", + "harvest", + "hat", + "have", + "hawk", + "hazard", + "head", + "health", + "heart", + "heavy", + "hedgehog", + "height", + "hello", + "helmet", + "help", + "hen", + "hero", + "hidden", + "high", + "hill", + "hint", + "hip", + "hire", + "history", + "hobby", + "hockey", + "hold", + "hole", + "holiday", + "hollow", + "home", + "honey", + "hood", + "hope", + "horn", + "horror", + "horse", + "hospital", + "host", + "hotel", + "hour", + "hover", + "hub", + "huge", + "human", + "humble", + "humor", + "hundred", + "hungry", + "hunt", + "hurdle", + "hurry", + "hurt", + "husband", + "hybrid", + "ice", + "icon", + "idea", + "identify", + "idle", + "ignore", + "ill", + "illegal", + "illness", + "image", + "imitate", + "immense", + "immune", + "impact", + "impose", + "improve", + "impulse", + "inch", + "include", + "income", + "increase", + "index", + "indicate", + "indoor", + "industry", + "infant", + "inflict", + "inform", + "inhale", + "inherit", + "initial", + "inject", + "injury", + "inmate", + "inner", + "innocent", + "input", + "inquiry", + "insane", + "insect", + "inside", + "inspire", + "install", + "intact", + "interest", + "into", + "invest", + "invite", + "involve", + "iron", + "island", + "isolate", + "issue", + "item", + "ivory", + "jacket", + "jaguar", + "jar", + "jazz", + "jealous", + "jeans", + "jelly", + "jewel", + "job", + "join", + "joke", + "journey", + "joy", + "judge", + "juice", + "jump", + "jungle", + "junior", + "junk", + "just", + "kangaroo", + "keen", + "keep", + "ketchup", + "key", + "kick", + "kid", + "kidney", + "kind", + "kingdom", + "kiss", + "kit", + "kitchen", + "kite", + "kitten", + "kiwi", + "knee", + "knife", + "knock", + "know", + "lab", + "label", + "labor", + "ladder", + "lady", + "lake", + "lamp", + "language", + "laptop", + "large", + "later", + "latin", + "laugh", + "laundry", + "lava", + "law", + "lawn", + "lawsuit", + "layer", + "lazy", + "leader", + "leaf", + "learn", + "leave", + "lecture", + "left", + "leg", + "legal", + "legend", + "leisure", + "lemon", + "lend", + "length", + "lens", + "leopard", + "lesson", + "letter", + "level", + "liar", + "liberty", + "library", + "license", + "life", + "lift", + "light", + "like", + "limb", + "limit", + "link", + "lion", + "liquid", + "list", + "little", + "live", + "lizard", + "load", + "loan", + "lobster", + "local", + "lock", + "logic", + "lonely", + "long", + "loop", + "lottery", + "loud", + "lounge", + "love", + "loyal", + "lucky", + "luggage", + "lumber", + "lunar", + "lunch", + "luxury", + "lyrics", + "machine", + "mad", + "magic", + "magnet", + "maid", + "mail", + "main", + "major", + "make", + "mammal", + "man", + "manage", + "mandate", + "mango", + "mansion", + "manual", + "maple", + "marble", + "march", + "margin", + "marine", + "market", + "marriage", + "mask", + "mass", + "master", + "match", + "material", + "math", + "matrix", + "matter", + "maximum", + "maze", + "meadow", + "mean", + "measure", + "meat", + "mechanic", + "medal", + "media", + "melody", + "melt", + "member", + "memory", + "mention", + "menu", + "mercy", + "merge", + "merit", + "merry", + "mesh", + "message", + "metal", + "method", + "middle", + "midnight", + "milk", + "million", + "mimic", + "mind", + "minimum", + "minor", + "minute", + "miracle", + "mirror", + "misery", + "miss", + "mistake", + "mix", + "mixed", + "mixture", + "mobile", + "model", + "modify", + "mom", + "moment", + "monitor", + "monkey", + "monster", + "month", + "moon", + "moral", + "more", + "morning", + "mosquito", + "mother", + "motion", + "motor", + "mountain", + "mouse", + "move", + "movie", + "much", + "muffin", + "mule", + "multiply", + "muscle", + "museum", + "mushroom", + "music", + "must", + "mutual", + "myself", + "mystery", + "myth", + "naive", + "name", + "napkin", + "narrow", + "nasty", + "nation", + "nature", + "near", + "neck", + "need", + "negative", + "neglect", + "neither", + "nephew", + "nerve", + "nest", + "net", + "network", + "neutral", + "never", + "news", + "next", + "nice", + "night", + "noble", + "noise", + "nominee", + "noodle", + "normal", + "north", + "nose", + "notable", + "note", + "nothing", + "notice", + "novel", + "now", + "nuclear", + "number", + "nurse", + "nut", + "oak", + "obey", + "object", + "oblige", + "obscure", + "observe", + "obtain", + "obvious", + "occur", + "ocean", + "october", + "odor", + "off", + "offer", + "office", + "often", + "oil", + "okay", + "old", + "olive", + "olympic", + "omit", + "once", + "one", + "onion", + "online", + "only", + "open", + "opera", + "opinion", + "oppose", + "option", + "orange", + "orbit", + "orchard", + "order", + "ordinary", + "organ", + "orient", + "original", + "orphan", + "ostrich", + "other", + "outdoor", + "outer", + "output", + "outside", + "oval", + "oven", + "over", + "own", + "owner", + "oxygen", + "oyster", + "ozone", + "pact", + "paddle", + "page", + "pair", + "palace", + "palm", + "panda", + "panel", + "panic", + "panther", + "paper", + "parade", + "parent", + "park", + "parrot", + "party", + "pass", + "patch", + "path", + "patient", + "patrol", + "pattern", + "pause", + "pave", + "payment", + "peace", + "peanut", + "pear", + "peasant", + "pelican", + "pen", + "penalty", + "pencil", + "people", + "pepper", + "perfect", + "permit", + "person", + "pet", + "phone", + "photo", + "phrase", + "physical", + "piano", + "picnic", + "picture", + "piece", + "pig", + "pigeon", + "pill", + "pilot", + "pink", + "pioneer", + "pipe", + "pistol", + "pitch", + "pizza", + "place", + "planet", + "plastic", + "plate", + "play", + "please", + "pledge", + "pluck", + "plug", + "plunge", + "poem", + "poet", + "point", + "polar", + "pole", + "police", + "pond", + "pony", + "pool", + "popular", + "portion", + "position", + "possible", + "post", + "potato", + "pottery", + "poverty", + "powder", + "power", + "practice", + "praise", + "predict", + "prefer", + "prepare", + "present", + "pretty", + "prevent", + "price", + "pride", + "primary", + "print", + "priority", + "prison", + "private", + "prize", + "problem", + "process", + "produce", + "profit", + "program", + "project", + "promote", + "proof", + "property", + "prosper", + "protect", + "proud", + "provide", + "public", + "pudding", + "pull", + "pulp", + "pulse", + "pumpkin", + "punch", + "pupil", + "puppy", + "purchase", + "purity", + "purpose", + "purse", + "push", + "put", + "puzzle", + "pyramid", + "quality", + "quantum", + "quarter", + "question", + "quick", + "quit", + "quiz", + "quote", + "rabbit", + "raccoon", + "race", + "rack", + "radar", + "radio", + "rail", + "rain", + "raise", + "rally", + "ramp", + "ranch", + "random", + "range", + "rapid", + "rare", + "rate", + "rather", + "raven", + "raw", + "razor", + "ready", + "real", + "reason", + "rebel", + "rebuild", + "recall", + "receive", + "recipe", + "record", + "recycle", + "reduce", + "reflect", + "reform", + "refuse", + "region", + "regret", + "regular", + "reject", + "relax", + "release", + "relief", + "rely", + "remain", + "remember", + "remind", + "remove", + "render", + "renew", + "rent", + "reopen", + "repair", + "repeat", + "replace", + "report", + "require", + "rescue", + "resemble", + "resist", + "resource", + "response", + "result", + "retire", + "retreat", + "return", + "reunion", + "reveal", + "review", + "reward", + "rhythm", + "rib", + "ribbon", + "rice", + "rich", + "ride", + "ridge", + "rifle", + "right", + "rigid", + "ring", + "riot", + "ripple", + "risk", + "ritual", + "rival", + "river", + "road", + "roast", + "robot", + "robust", + "rocket", + "romance", + "roof", + "rookie", + "room", + "rose", + "rotate", + "rough", + "round", + "route", + "royal", + "rubber", + "rude", + "rug", + "rule", + "run", + "runway", + "rural", + "sad", + "saddle", + "sadness", + "safe", + "sail", + "salad", + "salmon", + "salon", + "salt", + "salute", + "same", + "sample", + "sand", + "satisfy", + "satoshi", + "sauce", + "sausage", + "save", + "say", + "scale", + "scan", + "scare", + "scatter", + "scene", + "scheme", + "school", + "science", + "scissors", + "scorpion", + "scout", + "scrap", + "screen", + "script", + "scrub", + "sea", + "search", + "season", + "seat", + "second", + "secret", + "section", + "security", + "seed", + "seek", + "segment", + "select", + "sell", + "seminar", + "senior", + "sense", + "sentence", + "series", + "service", + "session", + "settle", + "setup", + "seven", + "shadow", + "shaft", + "shallow", + "share", + "shed", + "shell", + "sheriff", + "shield", + "shift", + "shine", + "ship", + "shiver", + "shock", + "shoe", + "shoot", + "shop", + "short", + "shoulder", + "shove", + "shrimp", + "shrug", + "shuffle", + "shy", + "sibling", + "sick", + "side", + "siege", + "sight", + "sign", + "silent", + "silk", + "silly", + "silver", + "similar", + "simple", + "since", + "sing", + "siren", + "sister", + "situate", + "six", + "size", + "skate", + "sketch", + "ski", + "skill", + "skin", + "skirt", + "skull", + "slab", + "slam", + "sleep", + "slender", + "slice", + "slide", + "slight", + "slim", + "slogan", + "slot", + "slow", + "slush", + "small", + "smart", + "smile", + "smoke", + "smooth", + "snack", + "snake", + "snap", + "sniff", + "snow", + "soap", + "soccer", + "social", + "sock", + "soda", + "soft", + "solar", + "soldier", + "solid", + "solution", + "solve", + "someone", + "song", + "soon", + "sorry", + "sort", + "soul", + "sound", + "soup", + "source", + "south", + "space", + "spare", + "spatial", + "spawn", + "speak", + "special", + "speed", + "spell", + "spend", + "sphere", + "spice", + "spider", + "spike", + "spin", + "spirit", + "split", + "spoil", + "sponsor", + "spoon", + "sport", + "spot", + "spray", + "spread", + "spring", + "spy", + "square", + "squeeze", + "squirrel", + "stable", + "stadium", + "staff", + "stage", + "stairs", + "stamp", + "stand", + "start", + "state", + "stay", + "steak", + "steel", + "stem", + "step", + "stereo", + "stick", + "still", + "sting", + "stock", + "stomach", + "stone", + "stool", + "story", + "stove", + "strategy", + "street", + "strike", + "strong", + "struggle", + "student", + "stuff", + "stumble", + "style", + "subject", + "submit", + "subway", + "success", + "such", + "sudden", + "suffer", + "sugar", + "suggest", + "suit", + "summer", + "sun", + "sunny", + "sunset", + "super", + "supply", + "supreme", + "sure", + "surface", + "surge", + "surprise", + "surround", + "survey", + "suspect", + "sustain", + "swallow", + "swamp", + "swap", + "swarm", + "swear", + "sweet", + "swift", + "swim", + "swing", + "switch", + "sword", + "symbol", + "symptom", + "syrup", + "system", + "table", + "tackle", + "tag", + "tail", + "talent", + "talk", + "tank", + "tape", + "target", + "task", + "taste", + "tattoo", + "taxi", + "teach", + "team", + "tell", + "ten", + "tenant", + "tennis", + "tent", + "term", + "test", + "text", + "thank", + "that", + "theme", + "then", + "theory", + "there", + "they", + "thing", + "this", + "thought", + "three", + "thrive", + "throw", + "thumb", + "thunder", + "ticket", + "tide", + "tiger", + "tilt", + "timber", + "time", + "tiny", + "tip", + "tired", + "tissue", + "title", + "toast", + "tobacco", + "today", + "toddler", + "toe", + "together", + "toilet", + "token", + "tomato", + "tomorrow", + "tone", + "tongue", + "tonight", + "tool", + "tooth", + "top", + "topic", + "topple", + "torch", + "tornado", + "tortoise", + "toss", + "total", + "tourist", + "toward", + "tower", + "town", + "toy", + "track", + "trade", + "traffic", + "tragic", + "train", + "transfer", + "trap", + "trash", + "travel", + "tray", + "treat", + "tree", + "trend", + "trial", + "tribe", + "trick", + "trigger", + "trim", + "trip", + "trophy", + "trouble", + "truck", + "true", + "truly", + "trumpet", + "trust", + "truth", + "try", + "tube", + "tuition", + "tumble", + "tuna", + "tunnel", + "turkey", + "turn", + "turtle", + "twelve", + "twenty", + "twice", + "twin", + "twist", + "two", + "type", + "typical", + "ugly", + "umbrella", + "unable", + "unaware", + "uncle", + "uncover", + "under", + "undo", + "unfair", + "unfold", + "unhappy", + "uniform", + "unique", + "unit", + "universe", + "unknown", + "unlock", + "until", + "unusual", + "unveil", + "update", + "upgrade", + "uphold", + "upon", + "upper", + "upset", + "urban", + "urge", + "usage", + "use", + "used", + "useful", + "useless", + "usual", + "utility", + "vacant", + "vacuum", + "vague", + "valid", + "valley", + "valve", + "van", + "vanish", + "vapor", + "various", + "vast", + "vault", + "vehicle", + "velvet", + "vendor", + "venture", + "venue", + "verb", + "verify", + "version", + "very", + "vessel", + "veteran", + "viable", + "vibrant", + "vicious", + "victory", + "video", + "view", + "village", + "vintage", + "violin", + "virtual", + "virus", + "visa", + "visit", + "visual", + "vital", + "vivid", + "vocal", + "voice", + "void", + "volcano", + "volume", + "vote", + "voyage", + "wage", + "wagon", + "wait", + "walk", + "wall", + "walnut", + "want", + "warfare", + "warm", + "warrior", + "wash", + "wasp", + "waste", + "water", + "wave", + "way", + "wealth", + "weapon", + "wear", + "weasel", + "weather", + "web", + "wedding", + "weekend", + "weird", + "welcome", + "west", + "wet", + "whale", + "what", + "wheat", + "wheel", + "when", + "where", + "whip", + "whisper", + "wide", + "width", + "wife", + "wild", + "will", + "win", + "window", + "wine", + "wing", + "wink", + "winner", + "winter", + "wire", + "wisdom", + "wise", + "wish", + "witness", + "wolf", + "woman", + "wonder", + "wood", + "wool", + "word", + "work", + "world", + "worry", + "worth", + "wrap", + "wreck", + "wrestle", + "wrist", + "write", + "wrong", + "yard", + "year", + "yellow", + "you", + "young", + "youth", + "zebra", + "zero", + "zone", + "zoo" +] diff --git a/crypto/actions/BlocksoftKeysForRef/BlocksoftKeysForRef.js b/crypto/actions/BlocksoftKeysForRef/BlocksoftKeysForRef.js new file mode 100644 index 000000000..d512bc99f --- /dev/null +++ b/crypto/actions/BlocksoftKeysForRef/BlocksoftKeysForRef.js @@ -0,0 +1,69 @@ +/** + * @author Ksu + * @version 0.5 + */ +import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog' +import BlocksoftKeysForRefServerSide from './BlocksoftKeysForRefServerSide' +import BlocksoftKeys from '../BlocksoftKeys/BlocksoftKeys' +import BlocksoftDispatcher from '../../blockchains/BlocksoftDispatcher' + + +const CACHE = {} + +class BlocksoftKeysForRef { + + /** + * @param {string} data.mnemonic + * @param {int} data.index + * @return {Promise<{currencyCode:[{address, privateKey, path, index, type}]}>} + */ + async discoverPublicAndPrivate(data) { + const logData = { ...data } + const mnemonicCache = data.mnemonic.toLowerCase() + + if (typeof logData.mnemonic !== 'undefined') logData.mnemonic = '***' + if (typeof CACHE[mnemonicCache] !== 'undefined') return CACHE[mnemonicCache] + BlocksoftCryptoLog.log(`BlocksoftKeysForRef discoverPublicAndPrivate called ` + JSON.stringify(logData)) + + let result = BlocksoftKeys.getEthCached(mnemonicCache) + if (!result) { + BlocksoftCryptoLog.log(`BlocksoftKeysForRef discoverPublicAndPrivate no cache ` + JSON.stringify(logData)) + let index = 0 + if (typeof data.index !== 'undefined') { + index = data.index + } + const root = await BlocksoftKeys.getBip32Cached(data.mnemonic) + const path = `m/44'/60'/${index}'/0/0` + const child = root.derivePath(path) + + const processor = await BlocksoftDispatcher.getAddressProcessor('ETH') + result = await processor.getAddress(child.privateKey) + result.index = index + result.path = path + if (index === 0) { + BlocksoftKeys.setEthCached(data.mnemonic, result) + } + BlocksoftCryptoLog.log(`BlocksoftKeysForRef discoverPublicAndPrivate finished no cache ` + JSON.stringify(logData)) + } + // noinspection JSPrimitiveTypeWrapperUsage + result.cashbackToken = BlocksoftKeysForRefServerSide.addressToToken(result.address) + BlocksoftCryptoLog.log(`BlocksoftKeysForRef discoverPublicAndPrivate finished ` + JSON.stringify(logData)) + CACHE[mnemonicCache] = result + return result + } + + async signDataForApi(msg, privateKey) { + const processor = await BlocksoftDispatcher.getAddressProcessor('ETH') + if (privateKey.substr(0, 2) !== '0x') { + privateKey = '0x' + privateKey + } + const signedData = await processor.signMessage(msg, privateKey) + delete signedData.v + delete signedData.r + delete signedData.s + return signedData + } +} + +const singleBlocksoftKeysForRef = new BlocksoftKeysForRef() +export default singleBlocksoftKeysForRef diff --git a/crypto/actions/BlocksoftKeysForRef/BlocksoftKeysForRefServerSide.js b/crypto/actions/BlocksoftKeysForRef/BlocksoftKeysForRefServerSide.js new file mode 100644 index 000000000..ea022744c --- /dev/null +++ b/crypto/actions/BlocksoftKeysForRef/BlocksoftKeysForRefServerSide.js @@ -0,0 +1,80 @@ +/** + * @author Ksu + * @version 0.5 + */ +const Web3 = require('web3') + +function stripHexPrefix(value) { + return value.replace('0x', '') +} + +function stripHexPrefixAndLower(value) { + return stripHexPrefix(value).toLowerCase() +} + +class BlocksoftKeysForRefServerSide { + + constructor() { + this._link = `https://mainnet.infura.io/v3/e69df96932bd4e9db7451fab8d6e0c85` + // noinspection JSUnresolvedVariable + this._web3 = new Web3(new Web3.providers.HttpProvider(this._link)) + } + + /** + * used recovery of v, r and s from https://github.com/MyCryptoHQ/MyCrypto/blob/develop/common/libs/signing.ts + * @param {Object} signedData + * @param {string} signedData.signature + * @param {string} signedData.message + * @param {string} signedData.messageHash + * @param {string} signedData.v + * @param {string} signedData.r + * @param {string} signedData.s + * @param {string} cashbackToken + * @return {Promise} + */ + async checkDataByApi(signedData, cashbackToken) { + if (typeof(signedData.signature) === 'undefined' || !signedData.signature) { + throw new Error('BlocksoftKeysForRefServerSide.checkDataByApi no signature' + JSON.stringify(signedData)) + } + if (typeof(signedData.message) === 'undefined') { + throw new Error('BlocksoftKeysForRefServerSide.checkDataByApi no message ' + JSON.stringify(signedData)) + } + if (typeof(signedData.messageHash) === 'undefined' || !signedData.messageHash) { + throw new Error('BlocksoftKeysForRefServerSide.checkDataByApi no messageHash ' + JSON.stringify(signedData)) + } + + const clonedData = { + signature : signedData.signature, + messageHash : signedData.messageHash + } + + // noinspection JSUnresolvedFunction,JSUnresolvedVariable + const signedDataHash = await this._web3.eth.accounts.hashMessage(signedData.message) + if (signedDataHash !== signedData.messageHash) { + return false + } + if (typeof clonedData.v === 'undefined' || typeof clonedData.r === 'undefined' || typeof clonedData.s === 'undefined') { + const sigb = Buffer.from(stripHexPrefixAndLower(clonedData.signature), 'hex') + if (sigb.length !== 65) { + return false + } + sigb[64] = sigb[64] === 0 || sigb[64] === 1 ? sigb[64] + 27 : sigb[64] + + clonedData.v = '0x' + sigb[64].toString(16) + clonedData.r = '0x' + sigb.slice(0, 32).toString('hex') + clonedData.s = '0x' + sigb.slice(32, 64).toString('hex') + } + + // noinspection JSUnresolvedVariable + const signedBy = await this._web3.eth.accounts.recover(clonedData) + return cashbackToken === this.addressToToken(signedBy) + } + + addressToToken(address) { + // any algo could be used to "hide" actual address + return Buffer.from(address).toString('base64').slice(3, 11) + } +} + +const singleBlocksoftKeysForRefServerSide = new BlocksoftKeysForRefServerSide() +export default singleBlocksoftKeysForRefServerSide diff --git a/crypto/actions/BlocksoftKeysForRef/BlocksoftKeysForRefStorage.js b/crypto/actions/BlocksoftKeysForRef/BlocksoftKeysForRefStorage.js new file mode 100644 index 000000000..1b5e44537 --- /dev/null +++ b/crypto/actions/BlocksoftKeysForRef/BlocksoftKeysForRefStorage.js @@ -0,0 +1,26 @@ +/** + * @author Ksu + * @version 0.5 + */ +import { BlocksoftKeysStorage } from '../BlocksoftKeysStorage/BlocksoftKeysStorage' + +class BlocksoftKeysForRefStorage extends BlocksoftKeysStorage { + + constructor(_serviceName = 'BlocksoftKeysRefStorage') { + super(_serviceName) + } + + async getPublicAndPrivateResultForHash(hash) { + const res = await this._getKeyValue('cd_' + hash) + if (!res && !res.priv) return false + return JSON.parse(res.priv) + } + + async setPublicAndPrivateResultForHash(hash, data) { + return this._setKeyValue('cd_' + hash, hash, JSON.stringify(data)) + } + +} + +const singleBlocksoftKeysRefStorage = new BlocksoftKeysForRefStorage() +export default singleBlocksoftKeysRefStorage diff --git a/crypto/actions/BlocksoftKeysStorage/BlocksoftKeysStorage.js b/crypto/actions/BlocksoftKeysStorage/BlocksoftKeysStorage.js new file mode 100644 index 000000000..60787f675 --- /dev/null +++ b/crypto/actions/BlocksoftKeysStorage/BlocksoftKeysStorage.js @@ -0,0 +1,325 @@ +/** + * @author Ksu + * @version 0.5 + * @docs https://www.npmjs.com/package/react-native-keychain + */ +import 'react-native' +import * as Keychain from 'react-native-keychain' + +import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog' +import config from '@app/config/config' +import BlocksoftDict from '@crypto/common/BlocksoftDict' + +export class BlocksoftKeysStorage { + + /** + * @type {string} + * @private + */ + _serviceName = '' + + /** + * @type {number} + * @private + */ + _serviceWalletsCounter = 0 + + /** + * @type {{}|array} + * @private + */ + _serviceWallets = {} + + /** + * @type {boolean} + * @private + */ + _serviceWasInited = false + + /** + * @type {array} + */ + publicWallets = [] + + constructor(_serviceName = 'BlocksoftKeysStorage3') { + this._serviceName = _serviceName + } + + /** + * @private + */ + async _getServiceName(name) { + this._serviceName = name + this._serviceWasInited = false + await this._init() + } + + /** + * @param {string} key + * @return {Promise} + * @private + */ + async _getKeyValue(key) { + const res = await Keychain.getInternetCredentials(this._serviceName + '_' + key, { + authenticationPrompt: { + title: 'Fingerprint title', + cancel: 'Cancel' + } + }) + if (!res) return false + return { 'pub': res.username, 'priv': res.password } + } + + /** + * @param {string} key + * @param {string|int} pub + * @param {string|boolean} priv + * @return {Promise<*>} + * @private + */ + async _setKeyValue(key, pub, priv = false) { + pub = pub + '' + if (!priv) priv = pub + let res = false + try { + res = await Keychain.setInternetCredentials(this._serviceName + '_' + key, pub, priv, { + authenticationPrompt: { + title: 'Fingerprint title', + cancel: 'Cancel' + }, + // if will be breaking again try accessControl : 'BiometryAnyOrDevicePasscode' + }) + } catch (e) { + if (config.debug.cryptoErrors) { + console.log('BlocksoftKeysStorage _setKeyValue error ', e) + } + if (key.indexOf('wallet') !== -1) { + throw e + } + } + return res + } + + /** + * @private + */ + async _init() { + if (this._serviceWasInited) { + return true + } + + const count = await this._getKeyValue('wallets_counter') + + this._serviceWalletsCounter = count && count.priv ? count.priv * 1 : 0 + this._serviceWallets = {} + this.publicWallets = [] + this.publicSelectedWallet = false + if (this._serviceWalletsCounter > 0) { + for (let i = 1; i <= this._serviceWalletsCounter; i++) { + const wallet = await this._getKeyValue('wallet_' + i) + if (!wallet.priv || wallet.priv === '' || wallet.priv === 'new wallet is not generated - please reinstall and restart' || wallet.priv === wallet.pub) { + continue + } + this._serviceWallets[wallet.pub] = wallet.priv + this._serviceWallets[i - 1] = wallet.priv + this.publicWallets.push(wallet.pub) + } + } + this._serviceWasInited = true + } + + async getOldSelectedWallet() { + this._init() + const tmp = await this._getKeyValue('selected_hash') + let publicSelectedWallet = false + if (tmp && tmp.pub) { + publicSelectedWallet = tmp.pub + } + if (!this.publicSelectedWallet || !this._serviceWallets[this.publicSelectedWallet]) { + publicSelectedWallet = false + } + return publicSelectedWallet + } + + async reInit() { + this._serviceWasInited = false + return this._init() + } + + async getOneWalletText(walletHash, discoverPath, currencyCode) { + try { + if (typeof discoverPath === 'undefined' || discoverPath === false) { + const res = await this._getKeyValue(walletHash) + if (!res) return false + return res.priv + } else { + const key = this.getAddressCacheKey(walletHash, discoverPath, currencyCode) + return this.getAddressCache(key) + } + } catch (e) { + // do nothing + } + return false + } + + async getAllWalletsText() { + let res = '' + for (let i = 1; i <= 3; i++) { + try { + const wallet = await this._getKeyValue('wallet_' + i) + if (typeof wallet.priv !== 'undefined' && wallet.priv !== 'undefined') { + res += ' WALLET#' + i + ' ' + wallet.priv + } + } catch (e) { + // do nothing + } + } + if (res === '') { + return 'Nothing found by general search' + } + return res + } + + /** + * @return {Promise} + */ + async countMnemonics() { + await this._init() + return this._serviceWalletsCounter + } + + + /** + * public list of wallets hashes + * @return {Array} + */ + async getWallets() { + await this._init() + return this.publicWallets + } + + /** + * public wallet mnemonic by hash + * @return {string} + */ + async getWalletMnemonic(hashOrId, source = 'default') { + await this._init() + if (!this._serviceWallets[hashOrId]) { + throw new Error('undefined wallet with hash ' + hashOrId + ' source ' + source) + } + return this._serviceWallets[hashOrId] + } + + /** + * @param {string} newMnemonic.mnemonic + * @param {string} newMnemonic.hash + * @return {Promise} + */ + async isMnemonicAlreadySaved(newMnemonic) { + await this._init() + if (typeof this._serviceWallets[newMnemonic.hash] === 'undefined' || !this._serviceWallets[newMnemonic.hash]) { + return false + } + if (this._serviceWallets[newMnemonic.hash] !== newMnemonic.mnemonic) { + throw new Error('something wrong with hash algorithm') + } + return true + } + + /** + * @param {string} newMnemonic.mnemonic + * @param {string} newMnemonic.hash + * @return {Promise} + */ + async saveMnemonic(newMnemonic) { + await this._init() + + const logData = { ...newMnemonic } + if (typeof logData.mnemonic !== 'undefined') logData.mnemonic = '***' + BlocksoftCryptoLog.log('BlocksoftKeysStorage saveMnemonic', logData) + + if (!newMnemonic.hash) { + throw new Error('unique hash required ' + JSON.stringify(newMnemonic)) + } + if (typeof this._serviceWallets[newMnemonic.hash] !== 'undefined' && this._serviceWallets[newMnemonic.hash]) { + if (this._serviceWallets[newMnemonic.hash] !== newMnemonic.mnemonic) { + throw new Error('something wrong with hash algorithm') + } + return newMnemonic.hash // its ok + } + this._serviceWalletsCounter++ + + const unique = newMnemonic.hash + + /* + hash should give unique values for different mnemonics + let i = 0 + while (this._serviceWallets[unique]) { + unique = newMnemonic.hash + '_' + i + i++ + if (i > 1000) { + throw new Error('unique hash is not reachable for ' + newMnemonic.hash) + } + } + */ + + await this._setKeyValue(unique, unique, newMnemonic.mnemonic) + await this._setKeyValue('wallet_' + this._serviceWalletsCounter, unique, newMnemonic.mnemonic) + await this._setKeyValue('wallets_counter', this._serviceWalletsCounter) + this._serviceWallets[unique] = newMnemonic.mnemonic + this._serviceWallets[this._serviceWalletsCounter - 1] = newMnemonic.mnemonic + + this.publicWallets.push(unique) + this.publicSelectedWallet = unique + + return newMnemonic.hash + } + + getAddressCacheKey(walletHash, discoverPath, currencyCode) { + const settings = BlocksoftDict.getCurrencyAllSettings(currencyCode) + if (typeof settings.addressCurrencyCode !== 'undefined' && typeof settings.tokenBlockchain !== 'undefined' && settings.tokenBlockchain !== 'BITCOIN' ) { + return walletHash + '_' + discoverPath + '_' + settings.addressCurrencyCode + } + return walletHash + '_' + discoverPath + '_' + currencyCode + } + + async getAddressCache(hashOrId) { + try { + const res = await this._getKeyValue('ar4_' + hashOrId) + if (!res || !res.priv || res.pub === res.priv) return false + return { address: res.pub, privateKey: res.priv } + } catch (e) { + return false + } + } + + async setAddressCache(hashOrId, res) { + if (typeof res.privateKey === 'undefined' || !res.privateKey) { + return false + } + return this._setKeyValue('ar4_' + hashOrId, res.address, res.privateKey) + } + + async getLoginCache(hashOrId) { + const res = await this._getKeyValue('login_' + hashOrId) + if (!res) return false + return { login: res.pub, pass: res.priv } + } + + async setLoginCache(hashOrId, res) { + return this._setKeyValue('login_' + hashOrId, res.login, res.pass) + } + + async setSettingValue(hashOrId, value) { + return this._setKeyValue('setting_' + hashOrId, hashOrId, value) + } + + async getSettingValue(hashOrId) { + const res = await this._getKeyValue('setting_' + hashOrId) + if (!res) return '0' + return res.priv.toString() + } +} + +const singleBlocksoftKeysStorage = new BlocksoftKeysStorage() +export default singleBlocksoftKeysStorage diff --git a/crypto/actions/BlocksoftSecrets/BlocksoftSecrets.js b/crypto/actions/BlocksoftSecrets/BlocksoftSecrets.js new file mode 100644 index 000000000..4df9b8f67 --- /dev/null +++ b/crypto/actions/BlocksoftSecrets/BlocksoftSecrets.js @@ -0,0 +1,51 @@ +/** + * @author Ksu + * @version 0.11 + */ +import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog' +import BlocksoftDispatcher from '../../blockchains/BlocksoftDispatcher' + +class BlocksoftSecrets { + + /** + * @type {{}} + * @private + */ + _processor = {} + + /** + * @param {string} params.currencyCode + * @param {string} params.mnemonic + * @return {Promise<{hash}>} + */ + async getWords(params) { + const currencyCode = params.currencyCode + if (!currencyCode) { + throw new Error('plz set currencyCode before calling') + } + + if (!this._processor[currencyCode]) { + /** + * @type {XmrSecretsProcessor} + */ + this._processor[currencyCode] = BlocksoftDispatcher.getSecretsProcessor(currencyCode) + } + + let res = '' + try { + BlocksoftCryptoLog.log(`BlocksoftSecrets.getWords ${currencyCode} started`) + res = await this._processor[currencyCode].getWords(params) + BlocksoftCryptoLog.log(`BlocksoftSecrets.getWords ${currencyCode} finished`) + } catch (e) { + // noinspection ES6MissingAwait + BlocksoftCryptoLog.err(`BlocksoftSecrets.getWords ${currencyCode} error ` + e.message, e.data ? e.data : e) + throw e + } + + return res + } +} + +const singleBlocksoftSecrets = new BlocksoftSecrets() + +export default singleBlocksoftSecrets diff --git a/crypto/actions/BlocksoftTokenChecks/BlocksoftTokenChecks.js b/crypto/actions/BlocksoftTokenChecks/BlocksoftTokenChecks.js new file mode 100644 index 000000000..693d4e85b --- /dev/null +++ b/crypto/actions/BlocksoftTokenChecks/BlocksoftTokenChecks.js @@ -0,0 +1,59 @@ +/** + * @author Ksu + * @version 0.5 + */ +import BlocksoftDispatcher from '../../blockchains/BlocksoftDispatcher' +import config from '@app/config/config' + +class BlocksoftTokenChecks { + + /** + * @type {{}} + * @private + */ + _processor = {} + + /** + * + * @param data.tokenType + * @param data.tokenAddress + * @returns {Promise<{tokenAddress: *, currencyName: *, provider: string, tokenDecimals: *, icon: *, description: *, tokenType: string, currencyCode: *}|boolean|{tokenAddress: *, currencyName: *, provider: string, tokenDecimals: *, icon: boolean, description: boolean, tokenType: string, currencyCode: *}|*|undefined>} + */ + async getTokenDetails(data) { + try { + if (!this._processor[data.tokenType]) { + this._processor[data.tokenType] = BlocksoftDispatcher.getTokenProcessor(data.tokenType) + } + const res = await this._processor[data.tokenType].getTokenDetails(data.tokenAddress) + // can log if needed + return res + } catch (e) { + if (config.debug.cryptoErrors) { + console.log('BlocksoftTokenChecks.getTokenDetails error ' + e.message) + } + e.code = 'ERROR_SYSTEM' + throw e + } + } + + /** + * + * @param data.nftType + * @param data.nftAddress + */ + async getNftDetails(data) { + try { + if (!this._processor[data.nftType]) { + this._processor[data.nftType] = BlocksoftDispatcher.getTokenNftsProcessor(data.nftType) + } + return this._processor[data.nftType].getNftDetails(data.nftAddress, data.nftType) + } catch (e) { + e.code = 'ERROR_SYSTEM' + throw e + } + } + +} + +const singleBlocksoftTokenChecks = new BlocksoftTokenChecks() +export default singleBlocksoftTokenChecks diff --git a/crypto/actions/BlocksoftTokenNfts/BlocksoftTokenNfts.js b/crypto/actions/BlocksoftTokenNfts/BlocksoftTokenNfts.js new file mode 100644 index 000000000..14250bef6 --- /dev/null +++ b/crypto/actions/BlocksoftTokenNfts/BlocksoftTokenNfts.js @@ -0,0 +1,35 @@ +/** + * @author Ksu + * @version 0.5 + */ +import BlocksoftDispatcher from '../../blockchains/BlocksoftDispatcher' + +class BlocksoftTokenNfts { + + /** + * @type {{}} + * @private + */ + _processor = {} + + /** + * + * @param data.tokenBlockchainCode + * @param data.address + */ + async getList(data) { + try { + if (!this._processor[data.tokenBlockchainCode]) { + this._processor[data.tokenBlockchainCode] = BlocksoftDispatcher.getTokenNftsProcessor(data.tokenBlockchainCode) + } + return this._processor[data.tokenBlockchainCode].getListBlockchain(data) + } catch (e) { + e.code = 'ERROR_SYSTEM' + throw e + } + } + +} + +const singleBlocksoftTokenNfts = new BlocksoftTokenNfts() +export default singleBlocksoftTokenNfts diff --git a/crypto/actions/BlocksoftTransactions/BlocksoftTransactions.js b/crypto/actions/BlocksoftTransactions/BlocksoftTransactions.js new file mode 100644 index 000000000..740891f3a --- /dev/null +++ b/crypto/actions/BlocksoftTransactions/BlocksoftTransactions.js @@ -0,0 +1,126 @@ +/** + * @author Ksu + * @version 0.5 + */ +import BlocksoftDispatcher from '../../blockchains/BlocksoftDispatcher' + +class BlocksoftTransactions { + + /** + * @type {{}} + * @private + */ + _processor = {} + + /** + * @return {Promise} + */ + async getTransactions(data, source = '') { + const currencyCode = data.account.currencyCode + if (!currencyCode) { + throw new Error('plz set currencyCode before calling') + } + if (!this._processor[currencyCode]) { + /** + * @type {EthScannerProcessor|BtcScannerProcessor|UsdtScannerProcessor} + */ + this._processor[currencyCode] = BlocksoftDispatcher.getScannerProcessor(currencyCode) + } + let resultData = [] + try { + resultData = await this._processor[currencyCode].getTransactionsBlockchain(data, source) + } catch (e) { + e.code = 'ERROR_SYSTEM' + e.message += ' on actual getTransactions step ' + throw e + } + + return resultData + } + + /** + * @return {Promise} + */ + async getTransactionsPending(data, source = '') { + const currencyCode = data.account.currencyCode + if (!currencyCode) { + throw new Error('plz set currencyCode before calling') + } + if (!this._processor[currencyCode]) { + /** + * @type {TrxScannerProcessor} + */ + this._processor[currencyCode] = BlocksoftDispatcher.getScannerProcessor(currencyCode) + } + if (typeof this._processor[currencyCode].getTransactionsPendingBlockchain === 'undefined') { + return false + } + let resultData = false + try { + resultData = await this._processor[currencyCode].getTransactionsPendingBlockchain(data, source, false) + } catch (e) { + e.message += ' on actual getTransactionsPending step ' + throw e + } + + return resultData + } + + /** + * @return {Promise} + */ + async resetTransactionsPending(data, source = '') { + const currencyCode = data.account.currencyCode + if (!currencyCode) { + throw new Error('plz set currencyCode before calling') + } + if (!this._processor[currencyCode]) { + /** + * @type {TrxScannerProcessor} + */ + this._processor[currencyCode] = BlocksoftDispatcher.getScannerProcessor(currencyCode) + } + if (typeof this._processor[currencyCode].resetTransactionsPendingBlockchain === 'undefined') { + return false + } + let resultData = false + try { + resultData = await this._processor[currencyCode].resetTransactionsPendingBlockchain(data, source, false) + } catch (e) { + e.message += ' on actual resetTransactionsPending step ' + throw e + } + + return resultData + } + + /** + * @return {Promise} + */ + async getAddresses(data, source = '') { + const currencyCode = data.account.currencyCode + if (!currencyCode) { + throw new Error('plz set currencyCode before calling') + } + if (!this._processor[currencyCode]) { + /** + * @type {EthScannerProcessor|BtcScannerProcessor|UsdtScannerProcessor} + */ + this._processor[currencyCode] = BlocksoftDispatcher.getScannerProcessor(currencyCode) + } + let resultData = [] + try { + resultData = await this._processor[currencyCode].getAddressesBlockchain(data, source) + } catch (e) { + e.code = 'ERROR_SYSTEM' + e.message += ' on actual getAddressesBlockchain step ' + throw e + } + + return resultData + } +} + +const singleBlocksoftTransactions = new BlocksoftTransactions() + +export default singleBlocksoftTransactions diff --git a/crypto/actions/BlocksoftTransfer/BlocksoftTransfer.ts b/crypto/actions/BlocksoftTransfer/BlocksoftTransfer.ts new file mode 100644 index 000000000..e2c165db8 --- /dev/null +++ b/crypto/actions/BlocksoftTransfer/BlocksoftTransfer.ts @@ -0,0 +1,297 @@ +/** + * @author Ksu + * @version 0.20 + */ +import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog' +import { BlocksoftBlockchainTypes } from '../../blockchains/BlocksoftBlockchainTypes' +import { BlocksoftTransferDispatcher } from '../../blockchains/BlocksoftTransferDispatcher' +import { BlocksoftTransferPrivate } from './BlocksoftTransferPrivate' +import { BlocksoftDictTypes } from '../../common/BlocksoftDictTypes' + +import CoinBlocksoftDict from '@crypto/assets/coinBlocksoftDict.json' + +import config from '../../../app/config/config' + + +type DataCache = { + [key in BlocksoftDictTypes.Code]: { + key: string, + memo : string | boolean, + time: number + } +} + +const CACHE_DOUBLE_TO: DataCache = {} as DataCache +const CACHE_VALID_TIME = 120000 // 2 minute +const CACHE_DOUBLE_BSE = {} + +export namespace BlocksoftTransfer { + + export const getTransferAllBalance = async function(data: BlocksoftBlockchainTypes.TransferData, additionalData: BlocksoftBlockchainTypes.TransferAdditionalData = {}): Promise { + if (config.debug.sendLogs) { + console.log(`${data.currencyCode} BlocksoftTransfer.getTransferAllBalance`, JSON.parse(JSON.stringify(data)), JSON.parse(JSON.stringify(additionalData))) + } + if (typeof data.derivationPath !== 'undefined' && data.derivationPath) { + data.derivationPath = data.derivationPath.replace(/quote/g, '\'') + } + data.isTransferAll = true + let transferAllCount + try { + BlocksoftCryptoLog.log(`${data.currencyCode} BlocksoftTransfer.getTransferAllBalance started ${data.addressFrom} `) + const processor = BlocksoftTransferDispatcher.getTransferProcessor(data.currencyCode) + const additionalDataTmp = { ...additionalData } + let privateData = {} as BlocksoftBlockchainTypes.TransferPrivateData + if (processor.needPrivateForFee()) { + privateData = await BlocksoftTransferPrivate.initTransferPrivate(data, additionalData) + } + additionalDataTmp.mnemonic = '***' + transferAllCount = await (BlocksoftTransferDispatcher.getTransferProcessor(data.currencyCode)).getTransferAllBalance(data, privateData, additionalDataTmp) + + BlocksoftCryptoLog.log(`${data.currencyCode} BlocksoftTransfer.getTransferAllBalance got ${data.addressFrom} result is ok`) + if (config.debug.sendLogs) { + console.log(`${data.currencyCode} BlocksoftTransfer.getTransferAllBalance result`, JSON.parse(JSON.stringify(transferAllCount))) + } + } catch (e) { + if (e.message.indexOf('SERVER_RESPONSE_') === -1 && e.message.indexOf('UI_') === -1) { + // noinspection ES6MissingAwait + BlocksoftCryptoLog.err(`${data.currencyCode} BlocksoftTransfer.getTransferAllBalance ` + e.message) + throw new Error('server.not.responding.all.balance.' + data.currencyCode + ' ' + e.message) + } else { + if (config.debug.appErrors) { + console.log(`${data.currencyCode} BlocksoftTransfer.getTransferAllBalance error ` + e.message) + } + throw e + } + } + return transferAllCount + } + + export const getFeeRate = async function(data: BlocksoftBlockchainTypes.TransferData, additionalData: BlocksoftBlockchainTypes.TransferAdditionalData = {}): Promise { + const lower = data.addressTo.toLowerCase() + if (!data?.walletConnectData?.data) { + for (const key in CoinBlocksoftDict) { + const tmp = CoinBlocksoftDict[key] + if (typeof tmp.canBeDestination !== 'undefined' && tmp.canBeDestination) { + continue + } + if (tmp?.tokenName && tmp?.tokenName.toLowerCase() === lower) { + throw new Error('SERVER_RESPONSE_CONTRACT_DESTINATION_INVALID') + } + if (tmp?.tokenAddress && tmp?.tokenAddress.toLowerCase() === lower) { + throw new Error('SERVER_RESPONSE_CONTRACT_DESTINATION_INVALID') + } + } + } + + if (config.debug.sendLogs) { + console.log('BlocksoftTransfer.getFeeRate', JSON.parse(JSON.stringify(data)), JSON.parse(JSON.stringify(additionalData))) + } + if (typeof data.derivationPath === 'undefined' || !data.derivationPath) { + throw new Error('BlocksoftTransfer.getFeeRate requires derivationPath ' + JSON.stringify(data)) + } + data.derivationPath = data.derivationPath.replace(/quote/g, '\'') + let feesCount + try { + BlocksoftCryptoLog.log(`${data.currencyCode} BlocksoftTransfer.getFeeRate started ${data.addressFrom} `) + const processor = BlocksoftTransferDispatcher.getTransferProcessor(data.currencyCode) + const additionalDataTmp = { ...additionalData } + + let privateData = {} as BlocksoftBlockchainTypes.TransferPrivateData + if (processor.needPrivateForFee()) { + privateData = await BlocksoftTransferPrivate.initTransferPrivate(data, additionalData) + } + additionalDataTmp.mnemonic = '***' + feesCount = await processor.getFeeRate(data, privateData, additionalDataTmp) + feesCount.countedTime = new Date().getTime() + } catch (e) { + if (config.debug.cryptoErrors) { + console.log('BlocksoftTransfer.getFeeRate error ', e) + } + if (typeof e.message === 'undefined' ) { + await BlocksoftCryptoLog.log('BlocksoftTransfer.getFeeRate strange error') + } else if (e.message.indexOf('SERVER_RESPONSE_') === -1 && e.message.indexOf('UI_') === -1) { + // noinspection ES6MissingAwait + await BlocksoftCryptoLog.err(`${data.currencyCode} BlocksoftTransfer.getFeeRate error ` + data.addressFrom + ' => ' + data.addressTo + ' ' + data.amount + ' ' + e.message) + throw new Error('server.not.responding.network.prices.' + data.currencyCode + ' ' + e.message) + } else { + await BlocksoftCryptoLog.log('BlocksoftTransfer.getFeeRate inner error ' + e.message) + throw e + } + } + return feesCount + } + + export const sendTx = async function(data: BlocksoftBlockchainTypes.TransferData, uiData: BlocksoftBlockchainTypes.TransferUiData, additionalData: BlocksoftBlockchainTypes.TransferAdditionalData): Promise { + if (config.debug.sendLogs) { + console.log('BlocksoftTransfer.sendTx', data, uiData) + } + + const lower = data.addressTo.toLowerCase() + if (!data?.walletConnectData?.data) { + for (const key in CoinBlocksoftDict) { + const tmp = CoinBlocksoftDict[key] + if (typeof tmp.canBeDestination !== 'undefined' && tmp.canBeDestination) { + continue + } + if (tmp?.tokenName && tmp?.tokenName.toLowerCase() === lower) { + throw new Error('SERVER_RESPONSE_CONTRACT_DESTINATION_INVALID') + } + if (tmp?.tokenAddress && tmp?.tokenAddress.toLowerCase() === lower) { + throw new Error('SERVER_RESPONSE_CONTRACT_DESTINATION_INVALID') + } + } + } + + data.derivationPath = data.derivationPath.replace(/quote/g, '\'') + + const bseOrderId = typeof uiData !== 'undefined' && uiData && typeof uiData.selectedFee !== 'undefined' && typeof uiData.selectedFee.bseOrderId !== 'undefined' ? uiData.selectedFee.bseOrderId : false + const uiErrorConfirmed = typeof uiData !== 'undefined' && uiData && typeof uiData.uiErrorConfirmed !== 'undefined' && uiData.uiErrorConfirmed + const memo = typeof data !== 'undefined' && data && typeof data.memo !== 'undefined' ? data.memo : false + + + try { + if (data.transactionReplaceByFee || data.transactionRemoveByFee || data.transactionSpeedUp) { + // do nothing + } else { + if (bseOrderId) { + // bse order + if (typeof CACHE_DOUBLE_BSE[bseOrderId] !== 'undefined') { + if (!uiErrorConfirmed) { + throw new Error('UI_CONFIRM_DOUBLE_BSE_SEND') + } + } + } + // usual tx + if (typeof CACHE_DOUBLE_TO[data.currencyCode] !== 'undefined') { + if (!uiErrorConfirmed) { + if (data.addressTo && CACHE_DOUBLE_TO[data.currencyCode].key === data.addressTo && CACHE_DOUBLE_TO[data.currencyCode].memo === memo) { + const time = new Date().getTime() + const diff = time - CACHE_DOUBLE_TO[data.currencyCode].time + if (diff < CACHE_VALID_TIME) { + CACHE_DOUBLE_TO[data.currencyCode].time = time + throw new Error('UI_CONFIRM_DOUBLE_SEND') + } + } + } + } + + } + } catch (e) { + if (config.debug.cryptoErrors) { + console.log(`${data.currencyCode} BlocksoftTransfer.sendTx check double error ` + e.message, e) + } + if (e.message.indexOf('SERVER_RESPONSE_') === -1 && e.message.indexOf('UI_') === -1) { + BlocksoftCryptoLog.log(`${data.currencyCode} BlocksoftTransfer.sendTx error ` + e.message) + } + throw e + } + + let txResult + try { + BlocksoftCryptoLog.log(`${data.currencyCode} BlocksoftTransfer.sendTx started ${data.addressFrom} `) + const processor = BlocksoftTransferDispatcher.getTransferProcessor(data.currencyCode) + const privateData = await BlocksoftTransferPrivate.initTransferPrivate(data, additionalData) + txResult = await processor.sendTx(data, privateData, uiData) + BlocksoftCryptoLog.log(`${data.currencyCode} BlocksoftTransfer.sendTx got ${data.addressFrom} result is ok`) + if (typeof uiData === 'undefined' || typeof uiData.selectedFee === 'undefined' || typeof uiData.selectedFee.rawOnly === 'undefined' || !uiData.selectedFee.rawOnly) { + CACHE_DOUBLE_TO[data.currencyCode] = { + key: data.addressTo, + memo, + time: new Date().getTime() + } + } + if (bseOrderId) { + CACHE_DOUBLE_BSE[bseOrderId] = true + } + // if (typeof uiData.selectedFee !== 'undefined') + } catch (e) { + if (config.debug.cryptoErrors) { + console.log('BlocksoftTransfer.sendTx error ' + e.message, e) + } + if (e.message.indexOf('SERVER_RESPONSE_') === -1 && e.message.indexOf('UI_') === -1 && e.message.indexOf('connect() timed') === -1) { + // noinspection ES6MissingAwait + BlocksoftCryptoLog.err(`${data.currencyCode} BlocksoftTransfer.sendTx ` + e.message) + } + + if (e.message.indexOf('imeout') !== -1 || e.message.indexOf('network error') !== -1 || e.message==='SERVER_RESPONSE_BAD_INTERNET') { + throw new Error('SERVER_RESPONSE_NOT_CONNECTED') + } + if (e.message.indexOf('SERVER_RESPONSE_NOT_CONNECTED') !== -1 && (data.currencyCode === 'TRX' || data.currencyCode.indexOf('TRX_') !== -1)) { + BlocksoftCryptoLog.log(`${data.currencyCode} BlocksoftTransfer.sendTx ` + e.message) + } else { + throw e + } + } + return txResult + } + + + export const sendRawTx = async function(data: BlocksoftBlockchainTypes.DbAccount, rawTxHex: string, txRBF: any, logData: any): Promise { + let txResult = '' + try { + BlocksoftCryptoLog.log(`${data.currencyCode} BlocksoftTransfer.sendRawTx started ${data.address} `) + const processor = BlocksoftTransferDispatcher.getTransferProcessor(data.currencyCode) + if (typeof processor.sendRawTx === 'undefined') { + return 'none' + } + txResult = await processor.sendRawTx(data, rawTxHex, txRBF, logData) + BlocksoftCryptoLog.log(`${data.currencyCode} BlocksoftTransfer.sendRawTx got ${data.address} result is ok`) + } catch (e) { + if (config.debug.cryptoErrors) { + console.log(`${data.currencyCode} BlocksoftTransfer.sendRawTx error `, e) + } + BlocksoftCryptoLog.log(`${data.currencyCode} BlocksoftTransfer.sendRawTx error ` + e.message) + throw e + } + return txResult + } + + + export const setMissingTx = async function(data: BlocksoftBlockchainTypes.DbAccount, dbTransaction: BlocksoftBlockchainTypes.DbTransaction): Promise { + let txResult = false + try { + BlocksoftCryptoLog.log(`${data.currencyCode} BlocksoftTransfer.setMissing started ${data.address} `) + const processor = BlocksoftTransferDispatcher.getTransferProcessor(data.currencyCode) + if (typeof processor.setMissingTx === 'undefined') { + return false + } + txResult = await processor.setMissingTx(data, dbTransaction) + BlocksoftCryptoLog.log(`${data.currencyCode} BlocksoftTransfer.setMissing got ${data.address} result is ok`) + } catch (e) { + BlocksoftCryptoLog.err(`${data.currencyCode} BlocksoftTransfer.setMissing error ` + e.message) + } + return txResult + } + + export const canRBF = function(data: BlocksoftBlockchainTypes.DbAccount, dbTransaction: BlocksoftBlockchainTypes.DbTransaction, source: string): boolean { + let txResult = false + try { + // BlocksoftCryptoLog.log(`BlocksoftTransfer.canRBF ${data.currencyCode} from ${source} started ${data.address} `) + const processor = BlocksoftTransferDispatcher.getTransferProcessor(typeof data.currencyCode !== 'undefined' ? data.currencyCode : dbTransaction.currencyCode) + if (typeof processor.canRBF === 'undefined') { + return false + } + txResult = processor.canRBF(data, dbTransaction, source) + // BlocksoftCryptoLog.log(`BlocksoftTransfer.canRBF ${data.currencyCode} from ${source} got ${data.address} result is ${JSON.stringify(txResult)}`) + } catch (e) { + BlocksoftCryptoLog.err(`${data.currencyCode} BlocksoftTransfer.canRBF error from ${source} ` + e.message) + } + return txResult + } + + export const checkSendAllModal = function(data: { currencyCode: any }) { + let checkSendAllModalResult = false + try { + // BlocksoftCryptoLog.log(`BlocksoftTransfer.checkSendAllModal ${data.currencyCode} started `) + const processor = BlocksoftTransferDispatcher.getTransferProcessor(data.currencyCode) + if (typeof processor.checkSendAllModal === 'undefined') { + return false + } + checkSendAllModalResult = processor.checkSendAllModal(data) + // BlocksoftCryptoLog.log(`BlocksoftTransfer.checkSendAllModal ${data.currencyCode} got result is ok ` + JSON.stringify(checkSendAllModalResult)) + } catch (e) { + BlocksoftCryptoLog.err(`${data.currencyCode} BlocksoftTransfer.checkSendAllModal error ` + e.message) + } + return checkSendAllModalResult + } +} diff --git a/crypto/actions/BlocksoftTransfer/BlocksoftTransferPrivate.ts b/crypto/actions/BlocksoftTransfer/BlocksoftTransferPrivate.ts new file mode 100644 index 000000000..804d54f83 --- /dev/null +++ b/crypto/actions/BlocksoftTransfer/BlocksoftTransferPrivate.ts @@ -0,0 +1,48 @@ +/** + * @author Ksu + * @version 0.20 + */ +import { BlocksoftBlockchainTypes } from '../../blockchains/BlocksoftBlockchainTypes' +import BlocksoftPrivateKeysUtils from '../../common/BlocksoftPrivateKeysUtils' +import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog' +import BlocksoftKeysStorage from '../BlocksoftKeysStorage/BlocksoftKeysStorage' + +export namespace BlocksoftTransferPrivate { + + const CACHE_PRIVATE: any = {} + + const initTransferPrivateBTC = async function(data: BlocksoftBlockchainTypes.TransferData, mnemonic : string): Promise { + const privateData = { + privateKey : mnemonic + } as BlocksoftBlockchainTypes.TransferPrivateData + return privateData + } + export const initTransferPrivate = async function(data: BlocksoftBlockchainTypes.TransferData, additionalData: BlocksoftBlockchainTypes.TransferAdditionalData): Promise { + const privateData = {} as BlocksoftBlockchainTypes.TransferPrivateData + let mnemonic = (typeof additionalData !== 'undefined' && typeof additionalData.mnemonic !== 'undefined') ? additionalData.mnemonic : CACHE_PRIVATE[data.walletHash] + if (!mnemonic) { + mnemonic = await BlocksoftKeysStorage.getWalletMnemonic(data.walletHash, 'initTransferPrivate') + CACHE_PRIVATE[data.walletHash] = mnemonic + } + if (!mnemonic) { + throw new Error('no mnemonic for hash ' + data.walletHash) + } + if (data.currencyCode === 'BTC' || data.currencyCode === 'LTC' || data.currencyCode === 'USDT') { + return initTransferPrivateBTC(data, mnemonic) + } + const discoverFor = { + mnemonic, + addressToCheck: data.addressFrom, + walletHash: data.walletHash, + derivationPath: data.derivationPath, + currencyCode: data.currencyCode + } + const result = await BlocksoftPrivateKeysUtils.getPrivateKey(discoverFor, 'initTransferPrivate') + // @ts-ignore + privateData.privateKey = result.privateKey + // @ts-ignore + privateData.addedData = result.addedData + BlocksoftCryptoLog.log(`${data.currencyCode} BlocksoftTransferPrivate.initTransferPrivate finished for ${data.addressFrom}`) + return privateData + } +} diff --git a/crypto/actions/BlocksoftTransfer/BlocksoftTransferUtils.ts b/crypto/actions/BlocksoftTransfer/BlocksoftTransferUtils.ts new file mode 100644 index 000000000..00e179404 --- /dev/null +++ b/crypto/actions/BlocksoftTransfer/BlocksoftTransferUtils.ts @@ -0,0 +1,67 @@ +/** + * @author Ksu + * @version 0.20 + */ +import { BlocksoftDictTypes } from '../../common/BlocksoftDictTypes' +import { BlocksoftTransferDispatcher } from '../../blockchains/BlocksoftTransferDispatcher' +import { BlocksoftBlockchainTypes } from '../../blockchains/BlocksoftBlockchainTypes' +import BlocksoftUtils from '../../common/BlocksoftUtils' + +export namespace BlocksoftTransferUtils { + + export const getAddressToForTransferAll = function(data : {currencyCode: BlocksoftDictTypes.Code, address : string} ) : string { + if (data.currencyCode === BlocksoftDictTypes.Code.BTC_TEST) { + return 'mjojEgUSi68PqNHoAyjhVkwdqQyLv9dTfV' + } + if (data.currencyCode === BlocksoftDictTypes.Code.XRP) { + const tmp1 = 'rEAgA9B8U8RCkwn6MprHqE1ZfXoeGQxz4P' + const tmp2 = 'rnyWPfJ7dk2X15N7jqwmqo3Nspu1oYiRZ3' + return data.address === tmp1 ? tmp2 : tmp1 + } + if (data.currencyCode === BlocksoftDictTypes.Code.XLM) { + const tmp1 = 'GCVPV3D4PAYFA7H2CHGFRTSPAHMSU4KQR4CHBUBUR4X23JUDJWHYZDYZ' + const tmp2 = 'GAQA5FITDUZW36J6VFXAH4YDNTTDEGRNWIXHIOR3FNN4DVJCXCNHUR4E' + return data.address === tmp1 ? tmp2 : tmp1 + + } + return data.address + } + + export const checkTransferHasError = async function( data : BlocksoftBlockchainTypes.CheckTransferHasErrorData) : Promise<{isOk : boolean, code ?: string}> { + const processor = BlocksoftTransferDispatcher.getTransferProcessor(data.currencyCode) + if (typeof processor.checkTransferHasError === 'undefined') { + return {isOk : true} + } + return processor.checkTransferHasError(data) + } + + export const getBalanceForTransfer = function(data : { + balance : string, + unconfirmed : string | boolean, + balanceStaked : string, + currencyCode: BlocksoftDictTypes.Code + }) : string { + if (data.currencyCode === BlocksoftDictTypes.Code.TRX) { + if (data.balanceStaked && data.balanceStaked !== '') { + return BlocksoftUtils.diff(data.balance, data.balanceStaked) + } + } + if (data.unconfirmed === false) { + return data.balance + } + // @ts-ignore + if (data.unconfirmed * 1 < 0) { + return data.balance + } + if (data.currencyCode === BlocksoftDictTypes.Code.XRP) { + return data.balance + } + if (data.currencyCode === BlocksoftDictTypes.Code.ETH || data.currencyCode.indexOf('ETH_') === 0) { + return data.balance + } + if (data.currencyCode === BlocksoftDictTypes.Code.TRX || data.currencyCode.indexOf('TRX_') === 0) { + return data.balance + } + return BlocksoftUtils.add(data.balance, data.unconfirmed).toString() + } +} diff --git a/crypto/assets/coinBlocksoftDict.json b/crypto/assets/coinBlocksoftDict.json new file mode 100644 index 000000000..9c635f81d --- /dev/null +++ b/crypto/assets/coinBlocksoftDict.json @@ -0,0 +1,2662 @@ +{ + "BTC": { + "currencyType": "coin", + "currencyName": "Bitcoin", + "currencyCode": "BTC", + "currencySymbol": "BTC", + "addressProcessor": "BTC", + "scannerProcessor": "BTC", + "extendsProcessor": "BTC", + "prettyNumberProcessor": "BTC", + "network": "mainnet", + "decimals": 8, + "currencyExplorerLink": "https://blockchair.com/bitcoin/address/", + "currencyExplorerTxLink": "https://blockchair.com/bitcoin/transaction/", + "addressPrefix": "1", + "defaultPath": "m/44'/0'/0'/0/0" + }, + "ETH": { + "currencyType": "coin", + "currencyName": "Ethereum", + "currencyCode": "ETH", + "currencySymbol": "ETH", + "addressProcessor": "ETH", + "scannerProcessor": "ETH", + "extendsProcessor": "ETH", + "prettyNumberProcessor": "ETH", + "network": "mainnet", + "decimals": 18, + "currencyExplorerLink": "https://blockchair.com/ethereum/address/", + "currencyExplorerTxLink": "https://blockchair.com/ethereum/transaction/" + }, + "USDT": { + "currencyType": "token", + "currencyName": "Tether OMNI", + "currencyCode": "USDT", + "currencySymbol": "USDT", + "extendsProcessor": "BTC", + "addressCurrencyCode": "BTC", + "scannerProcessor": "USDT", + "prettyNumberProcessor": "USDT", + "addressUiChecker": "BTC_LEGACY", + "feesCurrencyCode": "BTC", + "network": "mainnet", + "decimals": 8, + "tokenBlockchain": "BITCOIN", + "currencyExplorerLink": "https://blockchair.com/bitcoin/address/", + "currencyExplorerTxLink": "https://blockchair.com/bitcoin/transaction/" + }, + "LTC": { + "currencyType": "coin", + "currencyName": "Litecoin", + "currencyCode": "LTC", + "currencySymbol": "LTC", + "scannerProcessor": "LTC", + "extendsProcessor": "BTC", + "addressUiChecker": "BTC_BY_NETWORK", + "rateUiScanner": "LTC", + "network": "litecoin", + "decimals": 8, + "currencyExplorerLink": "https://blockchair.com/litecoin/address/", + "currencyExplorerTxLink": "https://blockchair.com/litecoin/transaction/", + "addressPrefix": "L", + "defaultPath": "m/44'/2'/0'/0/0" + }, + "ETH_USDT": { + "currencyType": "token", + "currencyName": "Tether ERC20", + "currencyCode": "ETH_USDT", + "currencySymbol": "USDT", + "extendsProcessor": "ETH_TRUE_USD", + "addressUiChecker": "ETH", + "ratesCurrencyCode": "USDT", + "decimals": 6, + "tokenBlockchain": "ETHEREUM", + "tokenAddress": "0xdac17f958d2ee523a2206206994597c13d831ec7", + "currencyExplorerLink": "https://etherscan.io/token/0xdac17f958d2ee523a2206206994597c13d831ec7?a=" + }, + "ETH_UAX": { + "currencyType": "token", + "currencyName": "Crypto UAX", + "currencyCode": "ETH_UAX", + "currencySymbol": "UAX", + "extendsProcessor": "ETH_TRUE_USD", + "delegatedTransfer": true, + "addressUiChecker": "ETH", + "ratesCurrencyCode": "UAH", + "decimals": 2, + "tokenBlockchain": "ETHEREUM", + "tokenAddress": "0x1Fc31488f28ac846588FFA201cDe0669168471bD", + "currencyExplorerLink": "https://etherscan.io/token/0x1Fc31488f28ac846588FFA201cDe0669168471bD?a=" + }, + "TRX": { + "currencyType": "coin", + "currencyName": "Tron", + "currencyCode": "TRX", + "currencySymbol": "TRX", + "addressProcessor": "TRX", + "scannerProcessor": "TRX", + "extendsProcessor": "TRX", + "prettyNumberProcessor": "UNIFIED", + "network": "mainnet", + "decimals": 6, + "currencyExplorerLink": "https://tronscan.org/#/address/", + "currencyExplorerTxLink": "https://tronscan.org/#/transaction/" + }, + "TRX_USDT": { + "currencyType": "token", + "transferProcessor": "TRX", + "currencyName": "Tether TRC20", + "currencyCode": "TRX_USDT", + "currencyIcon": "TRX", + "currencySymbol": "USDT", + "ratesCurrencyCode": "USDT", + "addressProcessor": "TRX", + "scannerProcessor": "TRX", + "prettyNumberProcessor": "UNIFIED", + "addressCurrencyCode": "TRX", + "addressUiChecker": "TRX", + "feesCurrencyCode": "TRX", + "network": "trx", + "decimals": 6, + "tokenBlockchain": "TRON", + "tokenName": "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t", + "currencyExplorerLink": "https://tronscan.org/#/address/", + "currencyExplorerTxLink": "https://tronscan.org/#/transaction/" + }, + "BNB": { + "currencyType": "coin", + "currencyName": "Binance Coin", + "currencyCode": "BNB", + "currencySymbol": "BNB", + "addressProcessor": "BNB", + "scannerProcessor": "BNB", + "extendsProcessor": "BNB", + "prettyNumberProcessor": "USDT", + "network": "mainnet", + "decimals": 8, + "currencyExplorerLink": "https://explorer.binance.org/address/", + "currencyExplorerTxLink": "https://explorer.binance.org/tx/" + }, + "BNB_SMART": { + "currencyType": "coin", + "currencyName": "BNB Smart Chain", + "currencyCode": "BNB_SMART", + "currencySymbol": "BNB", + "ratesCurrencyCode": "BNB", + "addressCurrencyCode": "ETH", + "addressProcessor": "ETH", + "addressUiChecker": "ETH", + "scannerProcessor": "ETH", + "extendsProcessor": "ETH", + "prettyNumberProcessor": "ETH", + "transferProcessor": "BNB_SMART", + "network": "mainnet", + "decimals": 18, + "currencyExplorerLink": "https://bscscan.com/address/", + "currencyExplorerTxLink": "https://bscscan.com/tx/" + }, + "ETH_TRUE_USD": { + "currencyType": "token", + "currencyName": "TrueUSD ERC20", + "currencyCode": "ETH_TRUE_USD", + "currencyIcon": "ETH", + "currencySymbol": "TUSD", + "addressProcessor": "ETH", + "scannerProcessor": "ETH_ERC_20", + "transferProcessor": "ETH_ERC_20", + "prettyNumberProcessor": "ETH_ERC_20", + "addressCurrencyCode": "ETH", + "addressUiChecker": "ETH", + "ratesCurrencyCode": "TUSD", + "feesCurrencyCode": "ETH", + "network": "mainnet", + "decimals": 18, + "tokenBlockchain": "ETHEREUM", + "tokenAddress": "0x0000000000085d4780B73119b644AE5ecd22b376", + "currencyExplorerLink": "https://etherscan.io/token/0x0000000000085d4780B73119b644AE5ecd22b376?a=", + "currencyExplorerTxLink": "https://etherscan.io/tx/" + }, + "TRX_TUSD": { + "currencyType": "token", + "currencyName": "TrueUSD TRC20", + "currencyCode": "TRX_TUSD", + "currencySymbol": "TUSD", + "extendsProcessor": "TRX_USDT", + "addressUiChecker": "TRX", + "ratesCurrencyCode": "TUSD", + "network": "trx", + "decimals": 18, + "tokenBlockchain": "TRON", + "tokenName": "TUpMhErZL2fhh4sVNULAbNKLokS4GjC1F4", + "currencyExplorerLink": "https://tronscan.org/#/address/", + "currencyExplorerTxLink": "https://tronscan.org/#/transaction/" + }, + "ETH_BNB": { + "currencyType": "token", + "currencyName": "BNB ERC20", + "currencyCode": "ETH_BNB", + "currencySymbol": "BNB", + "extendsProcessor": "ETH_TRUE_USD", + "addressUiChecker": "ETH", + "ratesCurrencyCode": "BNB", + "decimals": 18, + "tokenBlockchain": "ETHEREUM", + "tokenAddress": "0xB8c77482e45F1F44dE1745F52C74426C631bDD52", + "currencyExplorerLink": "https://etherscan.io/token/0xB8c77482e45F1F44dE1745F52C74426C631bDD52?a=" + }, + "ETH_USDC": { + "currencyType": "token", + "currencyName": "USDC ERC20", + "currencyCode": "ETH_USDC", + "currencySymbol": "USDC", + "extendsProcessor": "ETH_TRUE_USD", + "addressUiChecker": "ETH", + "ratesCurrencyCode": "USDC", + "decimals": 6, + "tokenBlockchain": "ETHEREUM", + "tokenAddress": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", + "currencyExplorerLink": "https://etherscan.io/token/0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48?a=", + "currencyExplorerTxLink": "https://etherscan.io/tx/" + }, + "TRX_USDC": { + "currencyType": "token", + "currencyName": "USDC TRC20", + "currencyCode": "TRX_USDC", + "currencySymbol": "USDC", + "extendsProcessor": "TRX_USDT", + "addressUiChecker": "TRX", + "ratesCurrencyCode": "USDC", + "network": "trx", + "decimals": 6, + "tokenBlockchain": "TRON", + "tokenName": "TEkxiTehnzSmSe2XqrBj4w32RUN966rdz8", + "currencyExplorerLink": "https://tronscan.org/#/address/", + "currencyExplorerTxLink": "https://tronscan.org/#/transaction/" + }, + "ONE_USDC": { + "currencyType": "token", + "currencyName": "USDC Harmony", + "currencyCode": "ONE_USDC", + "currencyIcon": "ETH", + "currencySymbol": "USDC", + "addressProcessor": "ETH", + "scannerProcessor": "ONE_ERC_20", + "transferProcessor": "BNB_SMART_20", + "prettyNumberProcessor": "ETH_ERC_20", + "addressCurrencyCode": "ETH", + "addressUiChecker": "ETH_ONE", + "ratesCurrencyCode": "USDC", + "feesCurrencyCode": "ONE", + "network": "mainnet", + "decimals": 6, + "tokenBlockchain": "ONE", + "tokenAddress": "0x985458e523db3d53125813ed68c274899e9dfab4", + "currencyExplorerLink": "https://explorer.harmony.one/address/0x985458e523db3d53125813ed68c274899e9dfab4", + "currencyExplorerTxLink": "https://explorer.harmony.one/tx/" + }, + "ETH_PAX": { + "currencyType": "token", + "currencyName": "Pax Dollar", + "currencyCode": "ETH_PAX", + "currencySymbol": "USDP", + "extendsProcessor": "ETH_TRUE_USD", + "addressUiChecker": "ETH", + "ratesCurrencyCode": "USDP", + "decimals": 18, + "tokenBlockchain": "ETHEREUM", + "tokenAddress": "0x8e870d67f660d95d5be530380d0ec0bd388289e1", + "currencyExplorerLink": "https://etherscan.io/token/0x8e870d67f660d95d5be530380d0ec0bd388289e1?a=" + }, + "ETH_DAI": { + "currencyType": "token", + "currencyName": "Sai Stablecoin v1.0", + "currencyCode": "ETH_DAI", + "currencySymbol": "SAI", + "extendsProcessor": "ETH_TRUE_USD", + "addressUiChecker": "ETH", + "ratesCurrencyCode": "DAI", + "decimals": 18, + "tokenBlockchain": "ETHEREUM", + "tokenAddress": "0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359", + "currencyExplorerLink": "https://etherscan.io/token/0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359?a=" + }, + "FIO": { + "currencyType": "coin", + "currencyName": "FIO", + "currencyCode": "FIO", + "currencySymbol": "FIO", + "addressProcessor": "FIO", + "scannerProcessor": "FIO", + "extendsProcessor": "FIO", + "prettyNumberProcessor": "UNIFIED", + "network": "mainnet", + "decimals": 9, + "currencyExplorerLink": "https://fio.bloks.io/key/", + "currencyExplorerTxLink": "https://fio.bloks.io/transaction/" + }, + "ETH_ZRX": { + "currencyType": "token", + "currencyName": "0x", + "currencyCode": "ETH_ZRX", + "currencySymbol": "ZRX", + "extendsProcessor": "ETH_TRUE_USD", + "addressUiChecker": "ETH", + "ratesCurrencyCode": "ZRX", + "decimals": 18, + "tokenBlockchain": "ETHEREUM", + "tokenAddress": "0xe41d2489571d322189246dafa5ebde1f4699f498", + "currencyExplorerLink": "https://etherscan.io/token/0xe41d2489571d322189246dafa5ebde1f4699f498?a=", + "currencyExplorerTxLink": "https://etherscan.io/tx/" + }, + "ETH_1INCH": { + "currencyType": "token", + "currencyName": "1inch ERC20", + "currencyCode": "ETH_1INCH", + "currencySymbol": "1INCH", + "extendsProcessor": "ETH_TRUE_USD", + "addressUiChecker": "ETH", + "ratesCurrencyCode": "1INCH", + "decimals": 18, + "tokenBlockchain": "ETHEREUM", + "tokenAddress": "0x111111111117dc0aa78b770fa6a738034120c302", + "currencyExplorerLink": "https://etherscan.io/token/0x111111111117dc0aa78b770fa6a738034120c302?a=", + "currencyExplorerTxLink": "https://etherscan.io/tx/" + }, + "BNB_SMART_1INCH": { + "currencyType": "token", + "currencyName": "1inch BEP20", + "currencyCode": "BNB_SMART_1INCH", + "currencySymbol": "1INCH", + "ratesCurrencyCode": "1INCH", + "extendsProcessor": "BNB_SMART_CAKE", + "addressUiChecker": "ETH", + "tokenAddress": "0x111111111117dc0aa78b770fa6a738034120c302", + "decimals": 18, + "tokenBlockchain": "BNB", + "currencyExplorerLink": "https://bscscan.com/token/0x111111111117dc0aa78b770fa6a738034120c302?a=" + }, + "ETH_AAVE": { + "currencyType": "token", + "currencyName": "Aave ERC20", + "currencyCode": "ETH_AAVE", + "currencySymbol": "AAVE", + "extendsProcessor": "ETH_TRUE_USD", + "addressUiChecker": "ETH", + "ratesCurrencyCode": "AAVE", + "decimals": 18, + "tokenBlockchain": "ETHEREUM", + "tokenAddress": "0x7fc66500c84a76ad7e9c93437bfc5ac33e2ddae9", + "currencyExplorerLink": "https://etherscan.io/token/0x7fc66500c84a76ad7e9c93437bfc5ac33e2ddae9?a=", + "currencyExplorerTxLink": "https://etherscan.io/tx/" + }, + "MATIC_AAVE": { + "currencyType": "token", + "currencyName": "AAVE Matic (Polygon)", + "currencyCode": "MATIC_AAVE", + "currencySymbol": "AAVE", + "ratesCurrencyCode": "AAVE", + "extendsProcessor": "MATIC_USDT", + "addressUiChecker": "ETH", + "tokenAddress": "0xd6df932a45c0f255f85145f286ea0b292b21c90b", + "decimals": 18, + "tokenBlockchain": "MATIC", + "currencyExplorerLink": "https://polygonscan.com/token/0xd6df932a45c0f255f85145f286ea0b292b21c90b?a=" + }, + "FTM_AAVE": { + "currencyType": "token", + "currencyName": "AAVE Fantom", + "currencyCode": "FTM_AAVE", + "currencySymbol": "AAVE", + "ratesCurrencyCode": "AAVE", + "extendsProcessor": "FTM_USDC", + "addressUiChecker": "ETH", + "tokenAddress": "0x6a07A792ab2965C72a5B8088d3a069A7aC3a993B", + "decimals": 18, + "tokenBlockchain": "FTM", + "currencyExplorerLink": "https://polygonscan.com/token/0x6a07A792ab2965C72a5B8088d3a069A7aC3a993B?a=" + }, + "ASH": { + "currencyType": "coin", + "currencyName": "Aeneas", + "currencyCode": "ASH", + "currencySymbol": "ASH", + "ratesCurrencyCode": "ASH", + "addressProcessor": "ASH", + "scannerProcessor": "WAVES", + "prettyNumberProcessor": "UNIFIED", + "transferProcessor": "WAVES", + "network": "mainnet", + "decimals": 8, + "currencyExplorerLink": "http://explorer.aeneas.id/address/", + "currencyExplorerTxLink": "http://explorer.aeneas.id/tx/" + }, + "AMB": { + "currencyType": "coin", + "currencyName": "Ambrosus", + "currencyCode": "AMB", + "currencySymbol": "AMB", + "ratesCurrencyCode": "AMB", + "addressProcessor": "ETH", + "addressUiChecker": "ETH", + "scannerProcessor": "ETH", + "extendsProcessor": "ETH", + "prettyNumberProcessor": "ETH", + "transferProcessor": "ETC", + "network": "mainnet", + "decimals": 18, + "currencyExplorerLink": "https://explorer.ambrosus.io/address/", + "currencyExplorerTxLink": "https://explorer.ambrosus.io/tx/" + }, + "BNB_SMART_ALPHA": { + "currencyType": "token", + "currencyName": "Alpha Finance BEP20", + "currencyCode": "BNB_SMART_ALPHA", + "currencySymbol": "ALPHA", + "ratesCurrencyCode": "ALPHA", + "extendsProcessor": "BNB_SMART_CAKE", + "addressUiChecker": "ETH", + "tokenAddress": "0xa1faa113cbe53436df28ff0aee54275c13b40975", + "tokenBlockchain": "BNB", + "currencyExplorerLink": "https://bscscan.com/token/0xa1faa113cbe53436df28ff0aee54275c13b40975?a=" + }, + "ETH_ALPHA": { + "currencyType": "token", + "currencyName": "Alpha Finance ERC20", + "currencyCode": "ETH_ALPHA", + "currencySymbol": "ALPHA", + "extendsProcessor": "ETH_TRUE_USD", + "addressUiChecker": "ETH", + "ratesCurrencyCode": "ALPHA", + "decimals": 18, + "tokenBlockchain": "ETHEREUM", + "tokenAddress": "0xa1faa113cbe53436df28ff0aee54275c13b40975", + "currencyExplorerLink": "https://etherscan.io/token/0xa1faa113cbe53436df28ff0aee54275c13b40975?a=", + "currencyExplorerTxLink": "https://etherscan.io/tx/" + }, + "TRX_APE": { + "currencyType": "token", + "currencyName": "APENFT TRC20", + "currencyCode": "TRX_APE", + "currencySymbol": "APENFT", + "extendsProcessor": "TRX_USDT", + "addressUiChecker": "TRX", + "ratesCurrencyCode": "NFT", + "network": "trx", + "decimals": 6, + "tokenBlockchain": "TRON", + "tokenName": "TFczxzPhnThNSqr5by8tvxsdCFRRz6cPNq", + "currencyExplorerLink": "https://tronscan.org/#/address/", + "currencyExplorerTxLink": "https://tronscan.org/#/transaction/" + }, + "ETH_BADGER": { + "currencyType": "token", + "currencyName": "Badger DAO", + "currencyCode": "ETH_BADGER", + "currencySymbol": "BADGER", + "extendsProcessor": "ETH_TRUE_USD", + "addressUiChecker": "ETH", + "ratesCurrencyCode": "BADGER", + "decimals": 18, + "tokenBlockchain": "ETHEREUM", + "tokenAddress": "0x3472a5a71965499acd81997a54bba8d852c6e53d", + "currencyExplorerLink": "https://etherscan.io/token/0x3472a5a71965499acd81997a54bba8d852c6e53d?a=", + "currencyExplorerTxLink": "https://etherscan.io/tx/" + }, + "ETH_BAL": { + "currencyType": "token", + "currencyName": "Balancer", + "currencyCode": "ETH_BAL", + "currencySymbol": "BAL", + "extendsProcessor": "ETH_TRUE_USD", + "addressUiChecker": "ETH", + "ratesCurrencyCode": "BAL", + "decimals": 18, + "tokenBlockchain": "ETHEREUM", + "tokenAddress": "0xba100000625a3754423978a60c9317c58a424e3d", + "currencyExplorerLink": "https://etherscan.io/token/0xba100000625a3754423978a60c9317c58a424e3d?a=" + }, + "ETH_BNT": { + "currencyType": "token", + "currencyName": "Bancor", + "currencyCode": "ETH_BNT", + "currencySymbol": "BNT", + "extendsProcessor": "ETH_TRUE_USD", + "addressUiChecker": "ETH", + "ratesCurrencyCode": "BNT", + "decimals": 18, + "tokenBlockchain": "ETHEREUM", + "tokenAddress": "0x1f573d6fb3f13d689ff844b4ce37794d79a7ff1c", + "currencyExplorerLink": "https://etherscan.io/token/0x1f573d6fb3f13d689ff844b4ce37794d79a7ff1c?a=" + }, + "ETH_BAT": { + "currencyType": "token", + "currencyName": "Basic Attention Token ERC20", + "currencyCode": "ETH_BAT", + "currencySymbol": "BAT", + "extendsProcessor": "ETH_TRUE_USD", + "addressUiChecker": "ETH", + "ratesCurrencyCode": "BAT", + "decimals": 18, + "tokenBlockchain": "ETHEREUM", + "tokenAddress": "0x0d8775f648430679a709e98d2b0cb6250d2887ef", + "currencyExplorerLink": "https://etherscan.io/token/0x0d8775f648430679a709e98d2b0cb6250d2887ef?a=", + "currencyExplorerTxLink": "https://etherscan.io/tx/" + }, + "BNB_SMART_BAT": { + "currencyType": "token", + "currencyName": "Basic Attention Token BEP20", + "currencyCode": "BNB_SMART_BAT", + "currencySymbol": "BAT", + "ratesCurrencyCode": "BAT", + "extendsProcessor": "BNB_SMART_CAKE", + "addressUiChecker": "ETH", + "tokenAddress": "0x101d82428437127bf1608f699cd651e6abf9766e", + "tokenBlockchain": "BNB", + "currencyExplorerLink": "https://bscscan.com/token/0x101d82428437127bf1608f699cd651e6abf9766e?a=" + }, + "ETH_BUSD": { + "currencyType": "token", + "currencyName": "Binance USD ERC20", + "currencyCode": "ETH_BUSD", + "currencySymbol": "BUSD", + "extendsProcessor": "ETH_TRUE_USD", + "addressUiChecker": "ETH", + "ratesCurrencyCode": "BUSD", + "decimals": 18, + "tokenBlockchain": "ETHEREUM", + "tokenAddress": "0x4fabb145d64652a948d72533023f6e7a623c7c53", + "currencyExplorerLink": "https://etherscan.io/token/0x4fabb145d64652a948d72533023f6e7a623c7c53?a=", + "currencyExplorerTxLink": "https://etherscan.io/tx/" + }, + "BTC_TEST": { + "currencyType": "coin", + "currencyName": "Bitcoin (Testnet)", + "currencyCode": "BTC_TEST", + "currencySymbol": "BTC TEST", + "scannerProcessor": "BTC_TEST", + "ratesCurrencyCode": "SKIP", + "extendsProcessor": "BTC", + "network": "testnet", + "decimals": 8, + "currencyExplorerLink": "https://live.blockcypher.com/btc-testnet/address/", + "currencyExplorerTxLink": "https://live.blockcypher.com/btc-testnet/tx/" + }, + "BCH": { + "currencyType": "coin", + "currencyName": "Bitcoin Cash", + "currencyCode": "BCH", + "currencySymbol": "BCH", + "addressProcessor": "BCH", + "scannerProcessor": "BCH", + "extendsProcessor": "BTC", + "addressUiChecker": "BTC_BY_NETWORK", + "rateUiScanner": "BCH", + "network": "bitcoincash", + "decimals": 8, + "currencyExplorerLink": "https://blockchair.com/bitcoin-cash/address/", + "currencyExplorerTxLink": "https://blockchair.com/bitcoin-cash/transaction/" + }, + "BTG": { + "currencyType": "coin", + "currencyName": "Bitcoin Gold", + "currencyCode": "BTG", + "currencySymbol": "BTG", + "scannerProcessor": "BTG", + "extendsProcessor": "BTC", + "addressUiChecker": "BTC_BY_NETWORK", + "rateUiScanner": "BTG", + "network": "bitcoingold", + "decimals": 8, + "currencyExplorerLink": "https://explorer.bitcoingold.org/insight/address/", + "currencyExplorerTxLink": "https://explorer.bitcoingold.org/insight/tx/" + }, + "BSV": { + "currencyType": "coin", + "currencyName": "BitcoinSV", + "currencyCode": "BSV", + "currencySymbol": "BSV", + "scannerProcessor": "BSV", + "extendsProcessor": "BTC", + "addressUiChecker": "BTC_BY_NETWORK", + "rateUiScanner": "BSV", + "network": "bitcoinsv", + "decimals": 8, + "currencyExplorerLink": "https://blockchair.com/bitcoin-sv/address/", + "currencyExplorerTxLink": "https://blockchair.com/bitcoin-sv/transaction/" + }, + "TRX_BTT_NEW": { + "currencyType": "token", + "currencyName": "BitTorrent New", + "currencyCode": "TRX_BTT_NEW", + "currencySymbol": "BTT", + "extendsProcessor": "TRX_USDT", + "addressUiChecker": "TRX", + "ratesCurrencyCode": "BTT_NEW", + "network": "trx", + "decimals": 18, + "tokenBlockchain": "TRON", + "tokenName": "TAFjULxiVgT4qWk6UZwjqwZXTSaGaqnVp4", + "currencyExplorerLink": "https://tronscan.org/#/address/", + "currencyExplorerTxLink": "https://tronscan.org/#/transaction/" + }, + "TRX_BTT": { + "currencyType": "token", + "currencyName": "BitTorrent Old", + "currencyCode": "TRX_BTT", + "currencySymbol": "BTT", + "extendsProcessor": "TRX_USDT", + "addressUiChecker": "TRX", + "ratesCurrencyCode": "BTT", + "network": "trx", + "skipParentBalanceCheck": true, + "decimals": 6, + "tokenBlockchain": "TRON", + "tokenName": "1002000", + "currencyExplorerLink": "https://tronscan.org/#/address/", + "currencyExplorerTxLink": "https://tronscan.org/#/transaction/" + }, + "TRX_WBTT": { + "currencyType": "token", + "currencyName": "BitTorrent Wrapped TRC20", + "currencyCode": "TRX_WBTT", + "currencySymbol": "WBTT", + "extendsProcessor": "TRX_USDT", + "addressUiChecker": "TRX", + "ratesCurrencyCode": "BTT", + "network": "trx", + "decimals": 6, + "tokenBlockchain": "TRON", + "tokenName": "TKfjV9RNKJJCqPvBtK8L7Knykh7DNWvnYt", + "currencyExplorerLink": "https://tronscan.org/#/address/", + "currencyExplorerTxLink": "https://tronscan.org/#/transaction/" + }, + "BNB_SMART_BTT": { + "currencyType": "token", + "currencyName": "BTT BEP20", + "currencyCode": "BNB_SMART_BTT", + "currencySymbol": "BTT", + "ratesCurrencyCode": "BTT", + "extendsProcessor": "BNB_SMART_CAKE", + "addressUiChecker": "ETH", + "tokenAddress": "0x8595f9da7b868b1822194faed312235e43007b49", + "tokenBlockchain": "BNB", + "currencyExplorerLink": "https://bscscan.com/token/0x8595f9da7b868b1822194faed312235e43007b49?a=" + }, + "ETH_BTT": { + "currencyType": "token", + "currencyName": "BTT ERC20 (Old)", + "currencyCode": "ETH_BTT", + "currencySymbol": "BTT", + "extendsProcessor": "ETH_TRUE_USD", + "addressUiChecker": "ETH", + "ratesCurrencyCode": "BTT", + "decimals": 6, + "tokenBlockchain": "ETHEREUM", + "tokenAddress": "0xe83cccfabd4ed148903bf36d4283ee7c8b3494d1", + "currencyExplorerLink": "https://etherscan.io/token/0xe83cccfabd4ed148903bf36d4283ee7c8b3494d1?a=" + }, + "ETH_BTT_NEW": { + "currencyType": "token", + "currencyName": "BTT ERC20", + "currencyCode": "ETH_BTT_NEW", + "currencySymbol": "BTT", + "extendsProcessor": "ETH_TRUE_USD", + "addressUiChecker": "ETH", + "ratesCurrencyCode": "BTT_NEW", + "decimals": 18, + "tokenBlockchain": "ETHEREUM", + "tokenAddress": "0xc669928185dbce49d2230cc9b0979be6dc797957", + "currencyExplorerLink": "https://etherscan.io/token/0xc669928185dbce49d2230cc9b0979be6dc797957?a=" + }, + + "BNB_SMART_BTC": { + "currencyType": "token", + "currencyName": "BTC BEP20", + "currencyCode": "BNB_SMART_BTC", + "currencySymbol": "BTC", + "ratesCurrencyCode": "BTC", + "extendsProcessor": "BNB_SMART_CAKE", + "addressUiChecker": "ETH", + "tokenAddress": "0x7130d2a12b9bcbfae4f2634d864a1ee1ce3ead9c", + "tokenBlockchain": "BNB", + "currencyExplorerLink": "https://bscscan.com/token/0x7130d2a12b9bcbfae4f2634d864a1ee1ce3ead9c?a=" + }, + "TRX_BTC": { + "currencyType": "token", + "currencyName": "BTC TRC20", + "currencyCode": "TRX_BTC", + "currencySymbol": "BTC", + "extendsProcessor": "TRX_USDT", + "addressUiChecker": "TRX", + "ratesCurrencyCode": "BTC", + "network": "trx", + "decimals": 8, + "tokenBlockchain": "TRON", + "tokenName": "TN3W4H6rK2ce4vX9YnFQHwKENnHjoxb3m9", + "currencyExplorerLink": "https://tronscan.org/#/address/", + "currencyExplorerTxLink": "https://tronscan.org/#/transaction/" + }, + "FTM_BTC": { + "currencyType": "token", + "currencyName": "BTC Fantom", + "currencyCode": "FTM_BTC", + "currencySymbol": "BTC", + "ratesCurrencyCode": "BTC", + "extendsProcessor": "FTM_USDC", + "addressUiChecker": "ETH", + "tokenAddress": "0x321162Cd933E2Be498Cd2267a90534A804051b11", + "decimals": 8, + "tokenBlockchain": "FTM", + "currencyExplorerLink": "https://polygonscan.com/token/0x321162Cd933E2Be498Cd2267a90534A804051b11?a=" + }, + "FTM_BOO": { + "currencyType": "token", + "currencyName": "SpookyToken", + "currencyCode": "FTM_BOO", + "currencySymbol": "BOO", + "ratesCurrencyCode": "BOO", + "extendsProcessor": "FTM_USDC", + "addressUiChecker": "ETH", + "tokenAddress": "0x841fad6eae12c286d1fd18d1d525dffa75c7effe", + "decimals": 18, + "tokenBlockchain": "FTM", + "currencyExplorerLink": "https://polygonscan.com/token/0x841fad6eae12c286d1fd18d1d525dffa75c7effe?a=" + }, + "BNB_SMART_ADA": { + "currencyType": "token", + "currencyName": "Cardano BEP20", + "currencyCode": "BNB_SMART_ADA", + "currencySymbol": "ADA", + "ratesCurrencyCode": "ADA", + "extendsProcessor": "BNB_SMART_CAKE", + "addressUiChecker": "ETH", + "tokenAddress": "0x3ee2200efb3400fabb9aacf31297cbdd1d435d47", + "tokenBlockchain": "BNB", + "currencyExplorerLink": "https://bscscan.com/token/0x3ee2200efb3400fabb9aacf31297cbdd1d435d47?a=" + }, + "BNB_SMART_LINK": { + "currencyType": "token", + "currencyName": "Chainlink BEP20", + "currencyCode": "BNB_SMART_LINK", + "currencySymbol": "LINK", + "ratesCurrencyCode": "LINK", + "extendsProcessor": "BNB_SMART_CAKE", + "addressUiChecker": "ETH", + "tokenAddress": "0xf8a0bf9cf54bb92f17374d9e9a321e6a111a51bd", + "tokenBlockchain": "BNB", + "currencyExplorerLink": "https://bscscan.com/token/0xf8a0bf9cf54bb92f17374d9e9a321e6a111a51bd?a=" + }, + "ETH_LINK": { + "currencyType": "token", + "currencyName": "Chainlink ERC20", + "currencyCode": "ETH_LINK", + "currencySymbol": "LINK", + "extendsProcessor": "ETH_TRUE_USD", + "addressUiChecker": "ETH", + "ratesCurrencyCode": "LINK", + "decimals": 18, + "tokenBlockchain": "ETHEREUM", + "tokenAddress": "0x514910771af9ca656af840dff83e8264ecf986ca", + "currencyExplorerLink": "https://etherscan.io/token/0x514910771af9ca656af840dff83e8264ecf986ca?a=", + "currencyExplorerTxLink": "https://etherscan.io/tx/" + }, + "FTM_LINK": { + "currencyType": "token", + "currencyName": "Chainlink Fantom", + "currencyCode": "FTM_LINK", + "currencySymbol": "LINK", + "ratesCurrencyCode": "LINK", + "extendsProcessor": "FTM_USDC", + "addressUiChecker": "ETH", + "tokenAddress": "0xb3654dc3d10ea7645f8319668e8f54d2574fbdc8", + "decimals": 18, + "tokenBlockchain": "FTM", + "currencyExplorerLink": "https://polygonscan.com/token/0xb3654dc3d10ea7645f8319668e8f54d2574fbdc8?a=" + }, + "ETH_NOW": { + "currencyType": "token", + "currencyName": "ChangeNOW", + "currencyCode": "ETH_NOW", + "currencySymbol": "NOW", + "extendsProcessor": "ETH_TRUE_USD", + "addressUiChecker": "ETH", + "ratesCurrencyCode": "NOW", + "decimals": 8, + "tokenBlockchain": "ETHEREUM", + "tokenAddress": "0xe9a95d175a5f4c9369f3b74222402eb1b837693b", + "currencyExplorerLink": "https://etherscan.io/token/0xe9a95d175a5f4c9369f3b74222402eb1b837693b?a=" + }, + "ETH_CHZ": { + "currencyType": "token", + "currencyName": "Chiliz", + "currencyCode": "ETH_CHZ", + "currencySymbol": "CHZ", + "extendsProcessor": "ETH_TRUE_USD", + "addressUiChecker": "ETH", + "ratesCurrencyCode": "CHZ", + "decimals": 18, + "tokenBlockchain": "ETHEREUM", + "tokenAddress": "0x3506424f91fd33084466f402d5d97f05f8e3b4af", + "currencyExplorerLink": "https://etherscan.io/tokEN/0X3506424F91FD33084466F402D5D97F05F8E3B4AF?A=", + "currencyExplorerTxLink": "https://etherscan.io/tx/" + }, + "ETH_COMP": { + "currencyType": "token", + "currencyName": "Compound", + "currencyCode": "ETH_COMP", + "currencySymbol": "COMP", + "extendsProcessor": "ETH_TRUE_USD", + "addressUiChecker": "ETH", + "ratesCurrencyCode": "COMP", + "decimals": 18, + "tokenBlockchain": "ETHEREUM", + "tokenAddress": "0xc00e94cb662c3520282e6f5717214004a7f26888", + "currencyExplorerLink": "https://etherscan.io/token/0xc00e94cb662c3520282e6f5717214004a7f26888?a=" + }, + "SOL_COPE": { + "currencyType": "token", + "currencyName": "COPE", + "currencyCode": "SOL_COPE", + "currencySymbol": "COPE", + "extendsProcessor": "SOL_RAY", + "addressUiChecker": "SOL", + "ratesCurrencyCode": "COPE", + "decimals": 6, + "tokenBlockchain": "SOLANA", + "tokenAddress": "8HGyAAB1yoM1ttS7pXjHMa3dukTFGQggnFFH3hJZgzQh", + "currencyExplorerLink": "https://explorer.solana.com/address/", + "currencyExplorerTxLink": "https://explorer.solana.com/tx/" + }, + "ETH_CRO": { + "currencyType": "token", + "currencyName": "Crypto.com Coin", + "currencyCode": "ETH_CRO", + "currencySymbol": "CRO", + "extendsProcessor": "ETH_TRUE_USD", + "addressUiChecker": "ETH", + "ratesCurrencyCode": "CRO", + "decimals": 8, + "tokenBlockchain": "ETHEREUM", + "tokenAddress": "0xa0b73e1ff0b80914ab6fe0444e65848c4c34450b", + "currencyExplorerLink": "https://etherscan.io/token/0xa0b73e1ff0b80914ab6fe0444e65848c4c34450b?a=", + "currencyExplorerTxLink": "https://etherscan.io/tx/" + }, + "ETH_SOUL": { + "currencyType": "token", + "currencyName": "CryptoSoul", + "currencyCode": "ETH_SOUL", + "currencySymbol": "SOUL", + "extendsProcessor": "ETH_TRUE_USD", + "scannerProcessor": "ETH_SOUL", + "addressUiChecker": "ETH", + "ratesCurrencyCode": "SOUL", + "decimals": 18, + "tokenBlockchain": "ETHEREUM", + "tokenAddress": "0xbb1f24c0c1554b9990222f036b0aad6ee4caec29", + "currencyExplorerLink": "https://etherscan.io/token/0xbb1f24c0c1554b9990222f036b0aad6ee4caec29?a=" + }, + "ETH_CRV": { + "currencyType": "token", + "currencyName": "Curve DAO Token", + "currencyCode": "ETH_CRV", + "currencySymbol": "CRV", + "extendsProcessor": "ETH_TRUE_USD", + "addressUiChecker": "ETH", + "ratesCurrencyCode": "CRV", + "decimals": 18, + "tokenBlockchain": "ETHEREUM", + "tokenAddress": "0xd533a949740bb3306d119cc777fa900ba034cd52", + "currencyExplorerLink": "https://etherscan.io/token/0xd533a949740bb3306d119cc777fa900ba034cd52?a=", + "currencyExplorerTxLink": "https://etherscan.io/tx/" + }, + "ETH_DAIM": { + "currencyType": "token", + "currencyName": "Dai Stablecoin", + "currencyCode": "ETH_DAIM", + "currencySymbol": "DAI", + "extendsProcessor": "ETH_TRUE_USD", + "addressUiChecker": "ETH", + "ratesCurrencyCode": "DAI", + "decimals": 18, + "tokenBlockchain": "ETHEREUM", + "tokenAddress": "0x6b175474e89094c44da98b954eedeac495271d0f", + "currencyExplorerLink": "https://etherscan.io/token/0x6b175474e89094c44da98b954eedeac495271d0f?a=" + }, + "DOGE": { + "currencyType": "coin", + "currencyName": "Dogecoin", + "currencyCode": "DOGE", + "currencySymbol": "DOGE", + "extendsProcessor": "BTC", + "scannerProcessor": "DOGE", + "addressUiChecker": "BTC_BY_NETWORK", + "rateUiScanner": "DOGE", + "network": "dogecoin", + "decimals": 8, + "currencyExplorerLink": "https://blockchair.com/dogecoin/address/", + "currencyExplorerTxLink": "https://blockchair.com/dogecoin/transaction/" + }, + "BNB_SMART_DOGE": { + "currencyType": "token", + "currencyName": "DOGE BEP20", + "currencyCode": "BNB_SMART_DOGE", + "currencySymbol": "DOGE", + "ratesCurrencyCode": "DOGE", + "extendsProcessor": "BNB_SMART_CAKE", + "addressUiChecker": "ETH", + "decimals": 8, + "tokenAddress": "0xba2ae424d960c26247dd6c32edc70b295c744c43", + "tokenBlockchain": "BNB", + "currencyExplorerLink": "https://bscscan.com/token/0xba2ae424d960c26247dd6c32edc70b295c744c43?a=" + }, + "TRX_DOGE": { + "currencyType": "token", + "currencyName": "DOGE TRC20", + "currencyCode": "TRX_DOGE", + "currencySymbol": "DOGE", + "extendsProcessor": "TRX_USDT", + "addressUiChecker": "TRX", + "ratesCurrencyCode": "DOGE", + "network": "trx", + "decimals": 8, + "tokenBlockchain": "TRON", + "tokenName": "THbVQp8kMjStKNnf2iCY6NEzThKMK5aBHg", + "currencyExplorerLink": "https://tronscan.org/#/address/", + "currencyExplorerTxLink": "https://tronscan.org/#/transaction/" + }, + "ETH_ENJ": { + "currencyType": "token", + "currencyName": "EnjinCoin", + "currencyCode": "ETH_ENJ", + "currencySymbol": "ENJ", + "extendsProcessor": "ETH_TRUE_USD", + "addressUiChecker": "ETH", + "ratesCurrencyCode": "ENJ", + "decimals": 18, + "tokenBlockchain": "ETHEREUM", + "tokenAddress": "0xf629cbd94d3791c9250152bd8dfbdf380e2a3b9c", + "currencyExplorerLink": "https://etherscan.io/token/0xf629cbd94d3791c9250152bd8dfbdf380e2a3b9c?a=", + "currencyExplorerTxLink": "https://etherscan.io/tx/" + }, + "BNB_SMART_ETH": { + "currencyType": "token", + "currencyName": "ETH BEP20", + "currencyCode": "BNB_SMART_ETH", + "currencySymbol": "ETH", + "ratesCurrencyCode": "ETH", + "extendsProcessor": "BNB_SMART_CAKE", + "addressUiChecker": "ETH", + "tokenAddress": "0x2170ed0880ac9a755fd29b2688956bd959f933f8", + "tokenBlockchain": "BNB", + "currencyExplorerLink": "https://bscscan.com/token/0x2170ed0880ac9a755fd29b2688956bd959f933f8?a=" + }, + "MATIC_ETH": { + "currencyType": "token", + "currencyName": "ETH Matic (Polygon)", + "currencyCode": "MATIC_ETH", + "currencySymbol": "ETH", + "ratesCurrencyCode": "ETH", + "extendsProcessor": "MATIC_USDT", + "addressUiChecker": "ETH", + "tokenAddress": "0x7ceb23fd6bc0add59e62ac25578270cff1b9f619", + "decimals": 18, + "tokenBlockchain": "MATIC", + "currencyExplorerLink": "https://polygonscan.com/token/0x7ceb23fd6bc0add59e62ac25578270cff1b9f619?a=" + }, + "TRX_ETH": { + "currencyType": "token", + "currencyName": "ETH TRC20", + "currencyCode": "TRX_ETH", + "currencySymbol": "ETH", + "extendsProcessor": "TRX_USDT", + "addressUiChecker": "TRX", + "ratesCurrencyCode": "ETH", + "network": "trx", + "decimals": 18, + "tokenBlockchain": "TRON", + "tokenName": "THb4CqiFdwNHsWsQCs4JhzwjMWys4aqCbF", + "currencyExplorerLink": "https://tronscan.org/#/address/", + "currencyExplorerTxLink": "https://tronscan.org/#/transaction/" + }, + "ETH_POW": { + "currencyType": "coin", + "currencyName": "Ethereum POW", + "currencyCode": "ETH_POW", + "currencySymbol": "ETHW", + "ratesCurrencyCode": "ETHW", + "addressCurrencyCode": "ETH", + "addressProcessor": "ETH", + "addressUiChecker": "ETH", + "scannerProcessor": "ETH", + "extendsProcessor": "ETH", + "prettyNumberProcessor": "ETH", + "transferProcessor": "ETH", + "network": "mainnet", + "decimals": 18, + "currencyExplorerLink": "https://www.oklink.com/en/ethw/address/", + "currencyExplorerTxLink": "https://www.oklink.com/en/ethw/tx/" + }, + "ETC": { + "currencyType": "coin", + "currencyName": "Ethereum Classic", + "currencyCode": "ETC", + "currencySymbol": "ETC", + "ratesCurrencyCode": "ETC", + "addressCurrencyCode": "ETC", + "addressProcessor": "ETH", + "addressUiChecker": "ETH", + "scannerProcessor": "ETH", + "extendsProcessor": "ETH", + "prettyNumberProcessor": "ETH", + "transferProcessor": "ETC", + "network": "mainnet", + "decimals": 18, + "currencyExplorerLink": "https://blockscout.com/etc/mainnet/address/", + "currencyExplorerTxLink": "https://blockscout.com/etc/mainnet/tx/" + }, + "ETH_ROPSTEN": { + "currencyType": "coin", + "currencyName": "Ethereum Ropsten", + "currencyCode": "ETH_ROPSTEN", + "currencySymbol": "ETH", + "currencyIcon": "ETH", + "ratesCurrencyCode": "SKIP", + "extendsProcessor": "ETH", + "transferProcessor": "ETH", + "network": "ropsten", + "decimals": 18, + "currencyExplorerLink": "https://ropsten.etherscan.io/address/", + "currencyExplorerTxLink": "https://ropsten.etherscan.io/tx/" + }, + "ETH_RINKEBY": { + "currencyType": "coin", + "currencyName": "Ethereum Rinkeby", + "currencyCode": "ETH_RINKEBY", + "currencySymbol": "ETH", + "currencyIcon": "ETH", + "ratesCurrencyCode": "SKIP", + "extendsProcessor": "ETH", + "transferProcessor": "ETH", + "network": "rinkeby", + "decimals": 18, + "currencyExplorerLink": "https://rinkeby.etherscan.io/address/", + "currencyExplorerTxLink": "https://rinkeby.etherscan.io/tx/" + }, + "ETH_LEND": { + "currencyType": "token", + "currencyName": "EthLend", + "currencyCode": "ETH_LEND", + "currencySymbol": "LEND", + "extendsProcessor": "ETH_TRUE_USD", + "addressUiChecker": "ETH", + "ratesCurrencyCode": "LEND", + "decimals": 18, + "tokenBlockchain": "ETHEREUM", + "tokenAddress": "0x80fb784b7ed66730e8b1dbd9820afd29931aab03", + "currencyExplorerLink": "https://etherscan.io/token/0x80fb784b7ed66730e8b1dbd9820afd29931aab03?a=" + }, + "SOL_FIDA": { + "currencyType": "token", + "currencyName": "FIDA", + "currencyCode": "SOL_FIDA", + "currencySymbol": "FIDA", + "extendsProcessor": "SOL_RAY", + "addressUiChecker": "SOL", + "ratesCurrencyCode": "FIDA", + "decimals": 6, + "tokenBlockchain": "SOLANA", + "tokenAddress": "EchesyfXePKdLtoiZSL8pBe8Myagyy8ZRqsACNCFGnvp", + "currencyExplorerLink": "https://explorer.solana.com/address/", + "currencyExplorerTxLink": "https://explorer.solana.com/tx/" + }, + "FTM": { + "currencyType": "coin", + "currencyName": "Fantom Network", + "currencyCode": "FTM", + "currencySymbol": "FTM", + "ratesCurrencyCode": "FTM", + "addressProcessor": "ETH", + "addressUiChecker": "ETH", + "scannerProcessor": "ETH", + "extendsProcessor": "ETH", + "prettyNumberProcessor": "ETH", + "transferProcessor": "ETC", + "network": "mainnet", + "decimals": 18, + "currencyExplorerLink": "https://ftmscan.com/address/", + "currencyExplorerTxLink": "https://ftmscan.com/tx/" + }, + "ETH_FTM": { + "currencyType": "token", + "currencyName": "FTM ERC20", + "currencyCode": "ETH_FTM", + "currencySymbol": "FTM", + "extendsProcessor": "ETH_TRUE_USD", + "addressUiChecker": "ETH", + "ratesCurrencyCode": "FTM", + "decimals": 18, + "tokenBlockchain": "ETHEREUM", + "tokenAddress": "0x4e15361fd6b4bb609fa63c81a2be19d873717870", + "currencyExplorerLink": "https://etherscan.io/token/0x4e15361fd6b4bb609fa63c81a2be19d873717870?a=", + "currencyExplorerTxLink": "https://etherscan.io/tx/" + }, + "BNB_SMART_FTM": { + "currencyType": "token", + "currencyName": "FTM BEP20", + "currencyCode": "BNB_SMART_FTM", + "currencySymbol": "FTM", + "ratesCurrencyCode": "FTM", + "extendsProcessor": "BNB_SMART_CAKE", + "addressUiChecker": "ETH", + "tokenAddress": "0xad29abb318791d579433d831ed122afeaf29dcfe", + "tokenBlockchain": "BNB", + "currencyExplorerLink": "https://bscscan.com/token/0xad29abb318791d579433d831ed122afeaf29dcfe?a=" + }, + "MATIC_FTM" : { + "currencyType": "token", + "currencyName": "FTM Polygon", + "currencyCode": "MATIC_FTM", + "currencySymbol": "FTM", + "ratesCurrencyCode": "MATIC", + "extendsProcessor": "MATIC_USDT", + "addressUiChecker": "ETH", + "tokenAddress": "0xc9c1c1c20b3658f8787cc2fd702267791f224ce1", + "decimals": 18, + "tokenBlockchain": "MATIC", + "currencyExplorerLink": "https://polygonscan.com/token/0xc9c1c1c20b3658f8787cc2fd702267791f224ce1?a=" + }, + "ETH_FTT": { + "currencyType": "token", + "currencyName": "FTX Token", + "currencyCode": "ETH_FTT", + "currencySymbol": "FTT", + "extendsProcessor": "ETH_TRUE_USD", + "addressUiChecker": "ETH", + "ratesCurrencyCode": "FTT", + "decimals": 18, + "tokenBlockchain": "ETHEREUM", + "tokenAddress": "0x50d1c9771902476076ecfc8b2a83ad6b9355a4c9", + "currencyExplorerLink": "https://etherscan.io/token/0x50d1c9771902476076ecfc8b2a83ad6b9355a4c9?a=", + "currencyExplorerTxLink": "https://etherscan.io/tx/" + }, + "ETH_GRT": { + "currencyType": "token", + "currencyName": "Graph Token", + "currencyCode": "ETH_GRT", + "currencySymbol": "GRT", + "extendsProcessor": "ETH_TRUE_USD", + "addressUiChecker": "ETH", + "ratesCurrencyCode": "GRT", + "decimals": 18, + "tokenBlockchain": "ETHEREUM", + "tokenAddress": "0xc944e90c64b2c07662a292be6244bdf05cda44a7", + "currencyExplorerLink": "https://etherscan.io/token/0xc944e90c64b2c07662a292be6244bdf05cda44a7?a=", + "currencyExplorerTxLink": "https://etherscan.io/tx/" + }, + "ETH_ONE": { + "currencyType": "token", + "currencyName": "Harmony ERC20", + "currencyCode": "ETH_ONE", + "currencySymbol": "ONE", + "extendsProcessor": "ETH_TRUE_USD", + "addressUiChecker": "ETH", + "ratesCurrencyCode": "ONE", + "decimals": 18, + "tokenBlockchain": "ETHEREUM", + "tokenAddress": "0x799a4202c12ca952cb311598a024c80ed371a41e", + "currencyExplorerLink": "https://etherscan.io/token/0x799a4202c12ca952cb311598a024c80ed371a41e?a=" + }, + "ETH_HUOBI": { + "currencyType": "token", + "currencyName": "Huobi Token", + "currencyCode": "ETH_HUOBI", + "currencySymbol": "HT", + "extendsProcessor": "ETH_TRUE_USD", + "addressUiChecker": "ETH", + "ratesCurrencyCode": "HT", + "decimals": 18, + "tokenBlockchain": "ETHEREUM", + "tokenAddress": "0x6f259637dcd74c767781e37bc6133cd6a68aa161", + "currencyExplorerLink": "https://etherscan.io/token/0x6f259637dcd74c767781e37bc6133cd6a68aa161?a=", + "currencyExplorerTxLink": "https://etherscan.io/tx/" + }, + "TRX_JST": { + "currencyType": "token", + "currencyName": "JUST", + "currencyCode": "TRX_JST", + "currencySymbol": "JST", + "extendsProcessor": "TRX_USDT", + "addressUiChecker": "TRX", + "ratesCurrencyCode": "JST", + "network": "trx", + "decimals": 18, + "tokenBlockchain": "TRON", + "tokenName": "TCFLL5dx5ZJdKnWuesXxi1VPwjLVmWZZy9", + "currencyExplorerLink": "https://tronscan.org/#/address/", + "currencyExplorerTxLink": "https://tronscan.org/#/transaction/" + }, + "TRX_USDJ": { + "currencyType": "token", + "currencyName": "JUST Stablecoin", + "currencyCode": "TRX_USDJ", + "currencySymbol": "USDJ", + "extendsProcessor": "TRX_USDT", + "addressUiChecker": "TRX", + "ratesCurrencyCode": "USDJ", + "network": "trx", + "decimals": 18, + "tokenBlockchain": "TRON", + "tokenName": "TMwFHYXLJaRUPeW6421aqXL4ZEzPRFGkGT", + "currencyExplorerLink": "https://tronscan.org/#/address/", + "currencyExplorerTxLink": "https://tronscan.org/#/transaction/" + }, + "SOL_KIN": { + "currencyType": "token", + "currencyName": "KIN", + "currencyCode": "SOL_KIN", + "currencySymbol": "KIN", + "extendsProcessor": "SOL_RAY", + "addressUiChecker": "SOL", + "ratesCurrencyCode": "KIN", + "decimals": 5, + "tokenBlockchain": "SOLANA", + "tokenAddress": "kinXdEcpDQeHPEuQnqmUgtYykqKGVFq6CeVX5iAHJq6", + "currencyExplorerLink": "https://explorer.solana.com/address/", + "currencyExplorerTxLink": "https://explorer.solana.com/tx/" + }, + "ETH_KNC": { + "currencyType": "token", + "currencyName": "KyberNetwork (Old)", + "currencyCode": "ETH_KNC", + "currencySymbol": "KNC", + "extendsProcessor": "ETH_TRUE_USD", + "addressUiChecker": "ETH", + "ratesCurrencyCode": "KNC", + "decimals": 18, + "tokenBlockchain": "ETHEREUM", + "tokenAddress": "0xdd974d5c2e2928dea5f71b9825b8b646686bd200", + "currencyExplorerLink": "https://etherscan.io/token/0xdd974d5c2e2928dea5f71b9825b8b646686bd200?a=" + }, + "ETH_KNC_NEW": { + "currencyType": "token", + "currencyName": "KyberNetwork", + "currencyCode": "ETH_KNC_NEW", + "currencySymbol": "KNC", + "extendsProcessor": "ETH_TRUE_USD", + "addressUiChecker": "ETH", + "ratesCurrencyCode": "KNC", + "decimals": 18, + "tokenBlockchain": "ETHEREUM", + "tokenAddress": "0xdefa4e8a7bcba345f687a2f1456f5edd9ce97202", + "currencyExplorerLink": "https://etherscan.io/token/0xdefa4e8a7bcba345f687a2f1456f5edd9ce97202?a=" + }, + "BNB_SMART_LTC": { + "currencyType": "token", + "currencyName": "LTC BEP20", + "currencyCode": "BNB_SMART_LTC", + "currencySymbol": "LTC", + "ratesCurrencyCode": "LTC", + "extendsProcessor": "BNB_SMART_CAKE", + "addressUiChecker": "ETH", + "tokenAddress": "0x4338665cbb7b2485a8855a139b75d5e34ab0db94", + "tokenBlockchain": "BNB", + "currencyExplorerLink": "https://bscscan.com/token/0x4338665cbb7b2485a8855a139b75d5e34ab0db94?a=" + }, + "ETH_MKR": { + "currencyType": "token", + "currencyName": "Maker", + "currencyCode": "ETH_MKR", + "currencySymbol": "MKR", + "extendsProcessor": "ETH_TRUE_USD", + "addressUiChecker": "ETH", + "ratesCurrencyCode": "MKR", + "decimals": 18, + "tokenBlockchain": "ETHEREUM", + "tokenAddress": "0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2", + "currencyExplorerLink": "https://etherscan.io/token/0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2?a=" + }, + "SOL_MNGO": { + "currencyType": "token", + "currencyName": "MANGO Markets", + "currencyCode": "SOL_MNGO", + "currencySymbol": "MNGO", + "extendsProcessor": "SOL_RAY", + "addressUiChecker": "SOL", + "ratesCurrencyCode": "MNGO", + "decimals": 6, + "tokenBlockchain": "SOLANA", + "tokenAddress": "MangoCzJ36AjZyKwVj3VnYU4GTonjfVEnJmvvWaxLac", + "currencyExplorerLink": "https://explorer.solana.com/address/", + "currencyExplorerTxLink": "https://explorer.solana.com/tx/" + }, + "SOL_MAPS": { + "currencyType": "token", + "currencyName": "MAPS", + "currencyCode": "SOL_MAPS", + "currencySymbol": "MAPS", + "extendsProcessor": "SOL_RAY", + "addressUiChecker": "SOL", + "ratesCurrencyCode": "MAPS", + "decimals": 6, + "tokenBlockchain": "SOLANA", + "tokenAddress": "MAPS41MDahZ9QdKXhVa4dWB9RuyfV4XqhyAZ8XcYepb", + "currencyExplorerLink": "https://explorer.solana.com/address/", + "currencyExplorerTxLink": "https://explorer.solana.com/tx/" + }, + "SOL_MEDIA": { + "currencyType": "token", + "currencyName": "Media Network", + "currencyCode": "SOL_MEDIA", + "currencySymbol": "MEDIA", + "extendsProcessor": "SOL_RAY", + "addressUiChecker": "SOL", + "ratesCurrencyCode": "MEDIA", + "decimals": 6, + "tokenBlockchain": "SOLANA", + "tokenAddress": "ETAtLmCmsoiEEKfNrHKJ2kYy3MoABhU6NQvpSfij5tDs", + "currencyExplorerLink": "https://explorer.solana.com/address/", + "currencyExplorerTxLink": "https://explorer.solana.com/tx/" + }, + "SOL_MER": { + "currencyType": "token", + "currencyName": "Mercurial Finance", + "currencyCode": "SOL_MER", + "currencySymbol": "MER", + "extendsProcessor": "SOL_RAY", + "addressUiChecker": "SOL", + "ratesCurrencyCode": "MER", + "decimals": 6, + "tokenBlockchain": "SOLANA", + "tokenAddress": "MERt85fc5boKw3BW1eYdxonEuJNvXbiMbs6hvheau5K", + "currencyExplorerLink": "https://explorer.solana.com/address/", + "currencyExplorerTxLink": "https://explorer.solana.com/tx/" + }, + "XMR": { + "currencyType": "coin", + "currencyName": "Monero", + "currencyCode": "XMR", + "currencySymbol": "XMR", + "addressProcessor": "XMR", + "scannerProcessor": "XMR", + "extendsProcessor": "XMR", + "prettyNumberProcessor": "UNIFIED", + "network": "mainnet", + "decimals": 12, + "currencyExplorerLink": "https://xmrchain.net/search?value=", + "currencyExplorerTxLink": "https://blockchair.com/monero/transaction/" + }, + "ETH_NEXO": { + "currencyType": "token", + "currencyName": "Nexo", + "currencyCode": "ETH_NEXO", + "currencySymbol": "NEXO", + "extendsProcessor": "ETH_TRUE_USD", + "addressUiChecker": "ETH", + "ratesCurrencyCode": "NEXO", + "decimals": 18, + "tokenBlockchain": "ETHEREUM", + "tokenAddress": "0xb62132e35a6c13ee1ee0f84dc5d40bad8d815206", + "currencyExplorerLink": "https://etherscan.io/token/0xb62132e35a6c13ee1ee0f84dc5d40bad8d815206?a=", + "currencyExplorerTxLink": "https://etherscan.io/tx/" + }, + "ETH_OKB": { + "currencyType": "token", + "currencyName": "OKB", + "currencyCode": "ETH_OKB", + "currencySymbol": "OKB", + "extendsProcessor": "ETH_TRUE_USD", + "addressUiChecker": "ETH", + "ratesCurrencyCode": "OKB", + "decimals": 18, + "tokenBlockchain": "ETHEREUM", + "tokenAddress": "0x75231f58b43240c9718dd58b4967c5114342a86c", + "currencyExplorerLink": "https://etherscan.io/token/0x75231f58b43240c9718dd58b4967c5114342a86c?a=" + }, + "ETH_OMG": { + "currencyType": "token", + "currencyName": "OMG Network", + "currencyCode": "ETH_OMG", + "currencySymbol": "OMG", + "extendsProcessor": "ETH_TRUE_USD", + "addressUiChecker": "ETH", + "ratesCurrencyCode": "OMG", + "decimals": 18, + "tokenBlockchain": "ETHEREUM", + "tokenAddress": "0xd26114cd6ee289accf82350c8d8487fedb8a0c07", + "currencyExplorerLink": "https://etherscan.io/token/0xd26114cd6ee289accf82350c8d8487fedb8a0c07?a=", + "currencyExplorerTxLink": "https://etherscan.io/tx/" + }, + "OPTIMISM": { + "currencyType": "coin", + "currencyName": "Optimistic Ethereum", + "currencyCode": "OPTIMISM", + "currencySymbol": "ETH", + "ratesCurrencyCode": "ETH", + "addressProcessor": "ETH", + "addressUiChecker": "ETH", + "scannerProcessor": "ETH", + "extendsProcessor": "ETH", + "prettyNumberProcessor": "ETH", + "transferProcessor": "ETC", + "network": "mainnet", + "decimals": 18, + "currencyExplorerLink": "https://optimistic.etherscan.io/address/", + "currencyExplorerTxLink": "https://optimistic.etherscan.io/tx/" + }, + "SOL_ORCA": { + "currencyType": "token", + "currencyName": "ORCA", + "currencyCode": "SOL_ORCA", + "currencySymbol": "ORCA", + "extendsProcessor": "SOL_RAY", + "addressUiChecker": "SOL", + "ratesCurrencyCode": "ORCA", + "decimals": 6, + "tokenBlockchain": "SOLANA", + "tokenAddress": "orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE", + "currencyExplorerLink": "https://explorer.solana.com/address/", + "currencyExplorerTxLink": "https://explorer.solana.com/tx/" + }, + "SOL_OXY": { + "currencyType": "token", + "currencyName": "Oxygen Protocol", + "currencyCode": "SOL_OXY", + "currencySymbol": "OXY", + "extendsProcessor": "SOL_RAY", + "addressUiChecker": "SOL", + "ratesCurrencyCode": "OXY", + "decimals": 6, + "tokenBlockchain": "SOLANA", + "tokenAddress": "z3dn17yLaGMKffVogeFHQ9zWVcXgqgf3PQnDsNs2g6M", + "currencyExplorerLink": "https://explorer.solana.com/address/", + "currencyExplorerTxLink": "https://explorer.solana.com/tx/" + }, + "SOL_PAI": { + "currencyType": "token", + "currencyName": "PAI (Parrot)", + "currencyCode": "SOL_PAI", + "currencySymbol": "PAI", + "extendsProcessor": "SOL_RAY", + "addressUiChecker": "SOL", + "ratesCurrencyCode": "PAI", + "decimals": 6, + "tokenBlockchain": "SOLANA", + "tokenAddress": "Ea5SjE2Y6yvCeW5dYTn7PYMuW5ikXkvbGdcmSnXeaLjS", + "currencyExplorerLink": "https://explorer.solana.com/address/", + "currencyExplorerTxLink": "https://explorer.solana.com/tx/" + }, + "BNB_SMART_CAKE": { + "currencyType": "token", + "currencyName": "PancakeSwap Token", + "currencyCode": "BNB_SMART_CAKE", + "currencyIcon": "BNB", + "currencySymbol": "CAKE", + "addressProcessor": "ETH", + "scannerProcessor": "ETH_ERC_20", + "transferProcessor": "BNB_SMART_20", + "prettyNumberProcessor": "ETH_ERC_20", + "addressCurrencyCode": "ETH", + "addressUiChecker": "ETH", + "ratesCurrencyCode": "CAKE", + "feesCurrencyCode": "BNB_SMART", + "network": "mainnet", + "decimals": 18, + "tokenBlockchain": "BNB", + "tokenAddress": "0x0e09fabb73bd3ade0a17ecc321fd13a19e81ce82", + "currencyExplorerLink": "https://bscscan.com/token/0x0e09fabb73bd3ade0a17ecc321fd13a19e81ce82?a=", + "currencyExplorerTxLink": "https://bscscan.com/tx/" + }, + "ETH_MATIC": { + "currencyType": "token", + "currencyName": "Polygon (Matic) ERC20", + "currencyCode": "ETH_MATIC", + "currencySymbol": "MATIC", + "extendsProcessor": "ETH_TRUE_USD", + "addressUiChecker": "ETH", + "ratesCurrencyCode": "MATIC", + "decimals": 18, + "tokenBlockchain": "ETHEREUM", + "tokenAddress": "0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0", + "currencyExplorerLink": "https://etherscan.io/token/0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0?a=", + "currencyExplorerTxLink": "https://etherscan.io/tx/" + }, + "MATIC": { + "currencyType": "coin", + "currencyName": "Polygon (Matic) Network", + "currencyCode": "MATIC", + "currencySymbol": "MATIC", + "ratesCurrencyCode": "MATIC", + "addressProcessor": "ETH", + "addressUiChecker": "ETH", + "scannerProcessor": "ETH", + "extendsProcessor": "ETH", + "prettyNumberProcessor": "ETH", + "transferProcessor": "ETC", + "network": "mainnet", + "decimals": 18, + "currencyExplorerLink": "https://polygonscan.com/address/", + "currencyExplorerTxLink": "https://polygonscan.com/tx/" + }, + "MATIC_WMATIC": { + "currencyType": "token", + "currencyName": "Wrapped Matic (Polygon)", + "currencyCode": "MATIC_WMATIC", + "currencySymbol": "WMATIC", + "ratesCurrencyCode": "MATIC", + "extendsProcessor": "MATIC_USDT", + "addressUiChecker": "ETH", + "tokenAddress": "0x0d500b1d8e8ef31e21c99d1db9a6444d3adf1270", + "decimals": 18, + "tokenBlockchain": "MATIC", + "currencyExplorerLink": "https://polygonscan.com/token/0x0d500b1d8e8ef31e21c99d1db9a6444d3adf1270?a=" + }, + "SOL_RAY": { + "currencyType": "token", + "transferProcessor": "SOL_SPL", + "currencyName": "Raydium", + "currencyCode": "SOL_RAY", + "currencySymbol": "RAY", + "ratesCurrencyCode": "RAY", + "addressProcessor": "SOL", + "scannerProcessor": "SOL_SPL", + "prettyNumberProcessor": "UNIFIED", + "addressCurrencyCode": "SOL", + "addressUiChecker": "SOL", + "feesCurrencyCode": "SOL", + "network": "sol", + "decimals": 6, + "tokenBlockchain": "SOLANA", + "tokenAddress": "4k3Dyjzvzp8eMZWUXbBCjEvwSkkk59S5iCNLY3QrkX6R", + "currencyExplorerLink": "https://explorer.solana.com/address/", + "currencyExplorerTxLink": "https://explorer.solana.com/tx/" + }, + "ETH_RSR": { + "currencyType": "token", + "currencyName": "Reserve Rights (OLD)", + "currencyCode": "ETH_RSR", + "currencySymbol": "RSR", + "extendsProcessor": "ETH_TRUE_USD", + "addressUiChecker": "ETH", + "ratesCurrencyCode": "RSR", + "decimals": 18, + "tokenBlockchain": "ETHEREUM", + "tokenAddress": "0x8762db106b2c2a0bccb3a80d1ed41273552616e8", + "currencyExplorerLink": "https://etherscan.io/token/0x8762db106b2c2a0bccb3a80d1ed41273552616e8?a=", + "currencyExplorerTxLink": "https://etherscan.io/tx/" + }, + "ETH_RSR_NEW": { + "currencyType": "token", + "currencyName": "Reserve Rights", + "currencyCode": "ETH_RSR_NEW", + "currencySymbol": "RSR", + "extendsProcessor": "ETH_TRUE_USD", + "addressUiChecker": "ETH", + "ratesCurrencyCode": "RSR", + "decimals": 18, + "tokenBlockchain": "ETHEREUM", + "tokenAddress": "0x320623b8e4ff03373931769a31fc52a4e78b5d70", + "currencyExplorerLink": "https://etherscan.io/token/0x320623b8e4ff03373931769a31fc52a4e78b5d70?a=", + "currencyExplorerTxLink": "https://etherscan.io/tx/" + }, + "XRP": { + "currencyType": "coin", + "currencyName": "Ripple", + "currencyCode": "XRP", + "currencySymbol": "XRP", + "addressProcessor": "XRP", + "scannerProcessor": "XRP", + "extendsProcessor": "XRP", + "prettyNumberProcessor": "USDT", + "network": "mainnet", + "decimals": 8, + "currencyExplorerLink": "https://blockchair.com/ripple/account/", + "currencyExplorerTxLink": "https://blockchair.com/ripple/transaction/" + }, + "SOL_SBR": { + "currencyType": "token", + "currencyName": "Saber Protocol Token", + "currencyCode": "SOL_SBR", + "currencySymbol": "SBR", + "extendsProcessor": "SOL_RAY", + "addressUiChecker": "SOL", + "ratesCurrencyCode": "SBR", + "decimals": 6, + "tokenBlockchain": "SOLANA", + "tokenAddress": "Saber2gLauYim4Mvftnrasomsv6NvAuncvMEZwcLpD1", + "currencyExplorerLink": "https://explorer.solana.com/address/", + "currencyExplorerTxLink": "https://explorer.solana.com/tx/" + }, + "SOL_SRM": { + "currencyType": "token", + "currencyName": "Serum", + "currencyCode": "SOL_SRM", + "currencySymbol": "SRM", + "extendsProcessor": "SOL_RAY", + "addressUiChecker": "SOL", + "ratesCurrencyCode": "SRM", + "decimals": 6, + "tokenBlockchain": "SOLANA", + "tokenAddress": "SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt", + "currencyExplorerLink": "https://explorer.solana.com/address/", + "currencyExplorerTxLink": "https://explorer.solana.com/tx/" + }, + "BNB_SMART_SHIB": { + "currencyType": "token", + "currencyName": "Shiba BEP20", + "currencyCode": "BNB_SMART_SHIB", + "currencySymbol": "SHIB", + "extendsProcessor": "BNB_SMART_CAKE", + "addressUiChecker": "ETH", + "ratesCurrencyCode": "SHIB", + "tokenBlockchain": "BNB", + "tokenAddress": "0x2859e4544c4bb03966803b044a93563bd2d0dd4d", + "currencyExplorerLink": "https://bscscan.com/address/0x2859e4544c4bb03966803b044a93563bd2d0dd4d?a=" + }, + "ETH_SHIB": { + "currencyType": "token", + "currencyName": "Shiba ERC20", + "currencyCode": "ETH_SHIB", + "currencySymbol": "SHIB", + "extendsProcessor": "ETH_TRUE_USD", + "addressUiChecker": "ETH", + "ratesCurrencyCode": "SHIB", + "decimals": 18, + "tokenBlockchain": "ETHEREUM", + "tokenAddress": "0x95ad61b0a150d79219dcf64e1e6cc01f0b64c4ce", + "currencyExplorerLink": "https://etherscan.io/token/0x95ad61b0a150d79219dcf64e1e6cc01f0b64c4ce?a=", + "currencyExplorerTxLink": "https://etherscan.io/tx/" + }, + "SOL": { + "currencyType": "coin", + "currencyName": "Solana", + "currencyCode": "SOL", + "currencySymbol": "SOL", + "addressProcessor": "SOL", + "addressUiChecker": "SOL", + "scannerProcessor": "SOL", + "prettyNumberProcessor": "UNIFIED", + "network": "mainnet", + "decimals": 9, + "currencyExplorerLink": "https://explorer.solana.com/address/", + "currencyExplorerTxLink": "https://explorer.solana.com/tx/" + }, + "SOL_SLIM": { + "currencyType": "token", + "currencyName": "Solanium", + "currencyCode": "SOL_SLIM", + "currencySymbol": "SLIM", + "extendsProcessor": "SOL_RAY", + "addressUiChecker": "SOL", + "ratesCurrencyCode": "SLIM", + "decimals": 6, + "tokenBlockchain": "SOLANA", + "tokenAddress": "xxxxa1sKNGwFtw2kFn8XauW9xq8hBZ5kVtcSesTT9fW", + "currencyExplorerLink": "https://explorer.solana.com/address/", + "currencyExplorerTxLink": "https://explorer.solana.com/tx/" + }, + "XLM": { + "currencyType": "coin", + "currencyName": "Stellar", + "currencyCode": "XLM", + "currencySymbol": "XLM", + "addressProcessor": "XLM", + "scannerProcessor": "XLM", + "extendsProcessor": "XLM", + "prettyNumberProcessor": "USDT", + "network": "mainnet", + "decimals": 8, + "currencyExplorerLink": "https://blockchair.com/stellar/account/", + "currencyExplorerTxLink": "https://blockchair.com/stellar/transaction/" + }, + "SOL_STEP": { + "currencyType": "token", + "currencyName": "Step", + "currencyCode": "SOL_STEP", + "currencySymbol": "STEP", + "extendsProcessor": "SOL_RAY", + "addressUiChecker": "SOL", + "ratesCurrencyCode": "STEP", + "decimals": 9, + "tokenBlockchain": "SOLANA", + "tokenAddress": "StepAscQoEioFxxWGnh2sLBDFp9d8rvKz2Yp39iDpyT", + "currencyExplorerLink": "https://explorer.solana.com/address/", + "currencyExplorerTxLink": "https://explorer.solana.com/tx/" + }, + "TRX_SUN_NEW": { + "currencyType": "token", + "currencyName": "SUN", + "currencyCode": "TRX_SUN_NEW", + "currencySymbol": "SUN", + "extendsProcessor": "TRX_USDT", + "addressUiChecker": "TRX", + "ratesCurrencyCode": "SUN_NEW", + "network": "trx", + "decimals": 18, + "tokenBlockchain": "TRON", + "tokenName": "TSSMHYeV2uE9qYH95DqyoCuNCzEL1NvU3S", + "currencyExplorerLink": "https://tronscan.org/#/address/", + "currencyExplorerTxLink": "https://tronscan.org/#/transaction/" + }, + "TRX_SUN": { + "currencyType": "token", + "currencyName": "SUN Old", + "currencyCode": "TRX_SUN", + "currencySymbol": "SUN", + "extendsProcessor": "TRX_USDT", + "addressUiChecker": "TRX", + "ratesCurrencyCode": "SUN", + "network": "trx", + "decimals": 18, + "tokenBlockchain": "TRON", + "tokenName": "TKkeiboTkxXKJpbmVFbv4a8ov5rAfRDMf9", + "currencyExplorerLink": "https://tronscan.org/#/address/", + "currencyExplorerTxLink": "https://tronscan.org/#/transaction/" + }, + "ETH_SUSHI": { + "currencyType": "token", + "currencyName": "Sushi Token", + "currencyCode": "ETH_SUSHI", + "currencySymbol": "SUSHI", + "extendsProcessor": "ETH_TRUE_USD", + "addressUiChecker": "ETH", + "ratesCurrencyCode": "SUSHI", + "decimals": 18, + "tokenBlockchain": "ETHEREUM", + "tokenAddress": "0x6b3595068778dd592e39a122f4f5a5cf09c90fe2", + "currencyExplorerLink": "https://etherscan.io/token/0x6b3595068778dd592e39a122f4f5a5cf09c90fe2?a=", + "currencyExplorerTxLink": "https://etherscan.io/tx/" + }, + "ETH_SXP": { + "currencyType": "token", + "currencyName": "Swipe ERC20", + "currencyCode": "ETH_SXP", + "currencySymbol": "SXP", + "extendsProcessor": "ETH_TRUE_USD", + "addressUiChecker": "ETH", + "ratesCurrencyCode": "SXP", + "decimals": 18, + "tokenBlockchain": "ETHEREUM", + "tokenAddress": "0x8ce9137d39326ad0cd6491fb5cc0cba0e089b6a9", + "currencyExplorerLink": "https://etherscan.io/token/0x8ce9137d39326ad0cd6491fb5cc0cba0e089b6a9?a=", + "currencyExplorerTxLink": "https://etherscan.io/tx/" + }, + "BNB_SMART_SXP": { + "currencyType": "token", + "currencyName": "Swipe BEP20", + "currencyCode": "BNB_SMART_SXP", + "currencySymbol": "SXP", + "ratesCurrencyCode": "SXP", + "extendsProcessor": "BNB_SMART_CAKE", + "addressUiChecker": "ETH", + "tokenAddress": "0x47bead2563dcbf3bf2c9407fea4dc236faba485a", + "tokenBlockchain": "BNB", + "currencyExplorerLink": "https://bscscan.com/token/0x47bead2563dcbf3bf2c9407fea4dc236faba485a?a=" + }, + "SOL_SNY": { + "currencyType": "token", + "currencyName": "Synthetify", + "currencyCode": "SOL_SNY", + "currencySymbol": "SNY", + "extendsProcessor": "SOL_RAY", + "addressUiChecker": "SOL", + "ratesCurrencyCode": "SNY", + "decimals": 6, + "tokenBlockchain": "SOLANA", + "tokenAddress": "4dmKkXNHdgYsXqBHCuMikNQWwVomZURhYvkkX5c4pQ7y", + "currencyExplorerLink": "https://explorer.solana.com/address/", + "currencyExplorerTxLink": "https://explorer.solana.com/tx/" + }, + "ETH_SNX": { + "currencyType": "token", + "currencyName": "Synthetix", + "currencyCode": "ETH_SNX", + "currencySymbol": "SNX", + "extendsProcessor": "ETH_TRUE_USD", + "addressUiChecker": "ETH", + "ratesCurrencyCode": "SNX", + "decimals": 18, + "tokenBlockchain": "ETHEREUM", + "tokenAddress": "0xc011a73ee8576fb46f5e1c5751ca3b9fe0af2a6f", + "currencyExplorerLink": "https://etherscan.io/token/0xc011a73ee8576fb46f5e1c5751ca3b9fe0af2a6f?a=", + "currencyExplorerTxLink": "https://etherscan.io/tx/" + }, + "BNB_SMART_USDT": { + "currencyType": "token", + "currencyName": "Tether BEP20", + "currencyCode": "BNB_SMART_USDT", + "currencySymbol": "USDT (BSC-USD)", + "ratesCurrencyCode": "USDT", + "extendsProcessor": "BNB_SMART_CAKE", + "addressUiChecker": "ETH", + "tokenAddress": "0x55d398326f99059ff775485246999027b3197955", + "tokenBlockchain": "BNB", + "currencyExplorerLink": "https://bscscan.com/token/0x55d398326f99059ff775485246999027b3197955?a=" + }, + "BNB_SMART_TRX": { + "currencyType": "token", + "currencyName": "TRX BEP20", + "currencyCode": "BNB_SMART_TRX", + "currencySymbol": "TRX", + "ratesCurrencyCode": "TRX", + "extendsProcessor": "BNB_SMART_CAKE", + "addressUiChecker": "ETH", + "tokenAddress": "0x85eac5ac2f758618dfa09bdbe0cf174e7d574d5b", + "tokenBlockchain": "BNB", + "currencyExplorerLink": "https://bscscan.com/token/0x85eac5ac2f758618dfa09bdbe0cf174e7d574d5b?a=" + }, + "ETH_TRX": { + "currencyType": "token", + "currencyName": "TRX ERC20", + "currencyCode": "ETH_TRX", + "currencySymbol": "TRX", + "extendsProcessor": "ETH_TRUE_USD", + "addressUiChecker": "ETH", + "ratesCurrencyCode": "TRX", + "decimals": 8, + "tokenBlockchain": "ETHEREUM", + "tokenAddress": "0xe1be5d3f34e89de342ee97e6e90d405884da6c67", + "currencyExplorerLink": "https://etherscan.io/token/0xe1be5d3f34e89de342ee97e6e90d405884da6c677?a=" + }, + "SOL_TULIP": { + "currencyType": "token", + "currencyName": "TULIP (Parrot)", + "currencyCode": "SOL_TULIP", + "currencySymbol": "TULIP", + "extendsProcessor": "SOL_RAY", + "addressUiChecker": "SOL", + "ratesCurrencyCode": "TULIP", + "decimals": 6, + "tokenBlockchain": "SOLANA", + "tokenAddress": "TuLipcqtGVXP9XR62wM8WWCm6a9vhLs7T1uoWBk6FDs", + "currencyExplorerLink": "https://explorer.solana.com/address/", + "currencyExplorerTxLink": "https://explorer.solana.com/tx/" + }, + "BNB_SMART_UNI": { + "currencyType": "token", + "currencyName": "Uniswap BEP20", + "currencyCode": "BNB_SMART_UNI", + "currencySymbol": "UNI", + "ratesCurrencyCode": "UNI", + "extendsProcessor": "BNB_SMART_CAKE", + "addressUiChecker": "ETH", + "tokenAddress": "0xbf5140a22578168fd562dccf235e5d43a02ce9b1", + "tokenBlockchain": "BNB", + "currencyExplorerLink": "https://bscscan.com/token/0xbf5140a22578168fd562dccf235e5d43a02ce9b1?a=" + }, + "ETH_UNI": { + "currencyType": "token", + "currencyName": "Uniswap ERC20", + "currencyCode": "ETH_UNI", + "currencySymbol": "UNI", + "extendsProcessor": "ETH_TRUE_USD", + "addressUiChecker": "ETH", + "ratesCurrencyCode": "UNI", + "decimals": 18, + "tokenBlockchain": "ETHEREUM", + "tokenAddress": "0x1f9840a85d5af5bf1d1762f925bdaddc4201f984", + "currencyExplorerLink": "https://etherscan.io/token/0x1f9840a85d5af5bf1d1762f925bdaddc4201f984?a=", + "currencyExplorerTxLink": "https://etherscan.io/tx/" + }, + "BNB_SMART_USD": { + "currencyType": "token", + "currencyName": "Binance USD BEP20", + "currencyCode": "BNB_SMART_USD", + "currencySymbol": "BUSD", + "ratesCurrencyCode": "BUSD", + "extendsProcessor": "BNB_SMART_CAKE", + "addressUiChecker": "ETH", + "tokenAddress": "0xe9e7cea3dedca5984780bafc599bd69add087d56", + "tokenBlockchain": "BNB", + "currencyExplorerLink": "https://bscscan.com/token/0xe9e7cea3dedca5984780bafc599bd69add087d56?a=" + }, + "BNB_SMART_USDC": { + "currencyType": "token", + "currencyName": "USDC BEP20", + "currencyCode": "BNB_SMART_USDC", + "currencySymbol": "USDC", + "ratesCurrencyCode": "USDC", + "extendsProcessor": "BNB_SMART_CAKE", + "addressUiChecker": "ETH", + "tokenAddress": "0x8ac76a51cc950d9822d68b83fe1ad97b32cd580d", + "tokenBlockchain": "BNB", + "currencyExplorerLink": "https://bscscan.com/token/0x8ac76a51cc950d9822d68b83fe1ad97b32cd580d?a=" + }, + "MATIC_USDC": { + "currencyType": "token", + "currencyName": "USDC Matic (Polygon)", + "currencyCode": "MATIC_USDC", + "currencySymbol": "USDC", + "ratesCurrencyCode": "USDC", + "extendsProcessor": "MATIC_USDT", + "addressUiChecker": "ETH", + "tokenAddress": "0x2791bca1f2de4661ed88a30c99a7a9449aa84174", + "decimals": 6, + "tokenBlockchain": "MATIC", + "currencyExplorerLink": "https://polygonscan.com/token/0x2791bca1f2de4661ed88a30c99a7a9449aa84174?a=" + }, + "FTM_USDC": { + "currencyType": "token", + "currencyName": "USDC Fantom", + "currencyCode": "FTM_USDC", + "currencyIcon": "USDC", + "currencySymbol": "USDC", + "addressProcessor": "ETH", + "scannerProcessor": "ETH_ERC_20", + "transferProcessor": "BNB_SMART_20", + "prettyNumberProcessor": "ETH_ERC_20", + "addressCurrencyCode": "ETH", + "addressUiChecker": "ETH", + "ratesCurrencyCode": "USDC", + "feesCurrencyCode": "FTM", + "network": "mainnet", + "decimals": 6, + "tokenBlockchain": "FTM", + "tokenAddress": "0x04068da6c83afcfa0e13ba15a6696662335d5b75", + "currencyExplorerLink": "https://polygonscan.com/token/0x04068da6c83afcfa0e13ba15a6696662335d5b75?a=", + "currencyExplorerTxLink": "https://polygonscan.com/tx/" + }, + "SOL_USDC": { + "currencyType": "token", + "currencyName": "USDC Solana", + "currencyCode": "SOL_USDC", + "currencySymbol": "USDC", + "extendsProcessor": "SOL_RAY", + "addressUiChecker": "SOL", + "ratesCurrencyCode": "USDC", + "decimals": 6, + "tokenBlockchain": "SOLANA", + "tokenAddress": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "currencyExplorerLink": "https://explorer.solana.com/address/", + "currencyExplorerTxLink": "https://explorer.solana.com/tx/" + }, + "MATIC_USDT": { + "currencyType": "token", + "currencyName": "Tether Matic (Polygon)", + "currencyCode": "MATIC_USDT", + "currencyIcon": "USDT", + "currencySymbol": "USDT", + "addressProcessor": "ETH", + "scannerProcessor": "ETH_ERC_20", + "transferProcessor": "BNB_SMART_20", + "prettyNumberProcessor": "ETH_ERC_20", + "addressCurrencyCode": "ETH", + "addressUiChecker": "ETH", + "ratesCurrencyCode": "USDT", + "feesCurrencyCode": "MATIC", + "network": "mainnet", + "decimals": 6, + "tokenBlockchain": "MATIC", + "tokenAddress": "0xc2132d05d31c914a87c6611c10748aeb04b58e8f", + "currencyExplorerLink": "https://polygonscan.com/token/0xc2132d05d31c914a87c6611c10748aeb04b58e8f?a=", + "currencyExplorerTxLink": "https://polygonscan.com/tx/" + }, + "SOL_USDT": { + "currencyType": "token", + "currencyName": "Tether Solana", + "currencyCode": "SOL_USDT", + "currencySymbol": "USDT", + "extendsProcessor": "SOL_RAY", + "addressUiChecker": "SOL", + "ratesCurrencyCode": "USDT", + "decimals": 6, + "tokenBlockchain": "SOLANA", + "tokenAddress": "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB", + "currencyExplorerLink": "https://explorer.solana.com/address/", + "currencyExplorerTxLink": "https://explorer.solana.com/tx/" + }, + "VET": { + "currencyType": "coin", + "currencyName": "VeChain Thor", + "currencyCode": "VET", + "currencySymbol": "VET", + "ratesCurrencyCode": "VET", + "addressCurrencyCode": "VET", + "addressProcessor": "ETH", + "addressUiChecker": "ETH", + "scannerProcessor": "VET", + "prettyNumberProcessor": "ETH", + "transferProcessor": "VET", + "network": "mainnet", + "decimals": 18, + "currencyExplorerLink": "https://explore.vechain.org/accounts/", + "currencyExplorerTxLink": "https://explore.vechain.org/transactions/" + }, + "VTHO": { + "currencyType": "coin", + "currencyName": "VeChain Thor Token", + "currencyCode": "VTHO", + "currencySymbol": "VTHO", + "ratesCurrencyCode": "VTHO", + "addressCurrencyCode": "VET", + "addressProcessor": "ETH", + "addressUiChecker": "ETH", + "scannerProcessor": "VET", + "prettyNumberProcessor": "ETH", + "transferProcessor": "VET", + "network": "mainnet", + "decimals": 18, + "currencyExplorerLink": "https://explore.vechain.org/accounts/", + "currencyExplorerTxLink": "https://explore.vechain.org/transactions/" + }, + "XVG": { + "currencyType": "coin", + "currencyName": "Verge", + "currencyCode": "XVG", + "currencySymbol": "XVG", + "extendsProcessor": "BTC", + "scannerProcessor": "XVG", + "addressUiChecker": "BTC_BY_NETWORK", + "rateUiScanner": "XVG", + "network": "verge", + "prettyNumberProcessor": "UNIFIED", + "decimals": 6, + "currencyExplorerLink": "https://verge-blockchain.info/address/", + "currencyExplorerTxLink": "https://verge-blockchain.info/tx/" + }, + "WAVES": { + "currencyType": "coin", + "currencyName": "Waves", + "currencyCode": "WAVES", + "currencySymbol": "WAVES", + "addressProcessor": "WAVES", + "scannerProcessor": "WAVES", + "prettyNumberProcessor": "UNIFIED", + "decimals": 8, + "currencyExplorerLink": "https://wavesexplorer.com/address/", + "currencyExplorerTxLink": "https://wavesexplorer.com/tx/" + }, + "TRX_WINK": { + "currencyType": "token", + "currencyName": "WIN Token", + "currencyCode": "TRX_WINK", + "currencySymbol": "WINK", + "extendsProcessor": "TRX_USDT", + "addressUiChecker": "TRX", + "ratesCurrencyCode": "WIN", + "network": "trx", + "decimals": 6, + "tokenBlockchain": "TRON", + "tokenName": "TLa2f6VPqDgRE67v1736s7bJ8Ray5wYjU7", + "currencyExplorerLink": "https://tronscan.org/#/address/", + "currencyExplorerTxLink": "https://tronscan.org/#/transaction/" + }, + "BNB_SMART_WINK": { + "currencyType": "token", + "currencyName": "WINK BEP20", + "currencyCode": "BNB_SMART_WINK", + "currencySymbol": "WINK", + "ratesCurrencyCode": "WINK", + "extendsProcessor": "BNB_SMART_CAKE", + "addressUiChecker": "ETH", + "tokenAddress": "0xaef0d72a118ce24fee3cd1d43d383897d05b4e99", + "tokenBlockchain": "BNB", + "currencyExplorerLink": "https://bscscan.com/token/0xaef0d72a118ce24fee3cd1d43d383897d05b4e99?a=" + }, + "ETH_BTC": { + "currencyType": "token", + "currencyName": "Wrapped BTC ERC20", + "currencyCode": "ETH_BTC", + "currencySymbol": "BTC", + "extendsProcessor": "ETH_TRUE_USD", + "addressUiChecker": "ETH", + "ratesCurrencyCode": "BTC", + "decimals": 18, + "tokenBlockchain": "ETHEREUM", + "tokenAddress": "0x2260fac5e5542a773aa44fbcfedf7c193bc2c599", + "currencyExplorerLink": "https://etherscan.io/token/0x2260fac5e5542a773aa44fbcfedf7c193bc2c599?a=", + "currencyExplorerTxLink": "https://etherscan.io/tx/" + }, + "ETH_YFI": { + "currencyType": "token", + "currencyName": "yearn.finance", + "currencyCode": "ETH_YFI", + "currencySymbol": "YFI", + "extendsProcessor": "ETH_TRUE_USD", + "addressUiChecker": "ETH", + "ratesCurrencyCode": "YFI", + "decimals": 18, + "tokenBlockchain": "ETHEREUM", + "tokenAddress": "0x0bc529c00c6401aef6d220be8c6ea1667f6ad93e", + "currencyExplorerLink": "https://etherscan.io/token/0x0bc529c00c6401aef6d220be8c6ea1667f6ad93e?a=", + "currencyExplorerTxLink": "https://etherscan.io/tx/" + }, + "TRX_EXON": { + "currencyType": "token", + "currencyName": "Exon Center", + "currencyCode": "TRX_EXON", + "currencySymbol": "EXON", + "extendsProcessor": "TRX_USDT", + "addressUiChecker": "TRX", + "ratesCurrencyCode": "EXON", + "network": "trx", + "decimals": 6, + "tokenBlockchain": "TRON", + "tokenName": "TRpfGv87u8tmdNW9rst5bpw6N7aEAE94Uh", + "currencyExplorerLink": "https://tronscan.org/#/address/", + "currencyExplorerTxLink": "https://tronscan.org/#/transaction/" + }, + "NFT" : { + "extendsProcessor": "ETH", + "currencyType": "special", + "currencyName": "NFT", + "currencyCode": "NFT", + "currencySymbol": "NFT", + "isHidden": false + }, + "CASHBACK" : { + "currencyType": "special", + "currencyName": "Cashback", + "currencyCode": "CASHBACK", + "currencySymbol": "USDT", + "isHidden": false + }, + "METIS": { + "currencyType": "coin", + "currencyName": "Metis", + "currencyCode": "METIS", + "currencySymbol": "METIS", + "ratesCurrencyCode": "METIS", + "addressCurrencyCode": "ETH", + "addressProcessor": "ETH", + "addressUiChecker": "ETH", + "scannerProcessor": "METIS", + "extendsProcessor": "ETH", + "prettyNumberProcessor": "ETH", + "transferProcessor": "METIS", + "network": "mainnet", + "decimals": 18, + "currencyExplorerLink": "https://andromeda-explorer.metis.io/address/", + "currencyExplorerTxLink": "https://andromeda-explorer.metis.io/tx/" + }, + "METIS_METIS": { + "currencyType": "token", + "currencyName": "Wrapped Metis (METIS)", + "currencyCode": "METIS_METIS", + "currencyIcon": "METIS", + "currencySymbol": "METIS", + "addressProcessor": "ETH", + "scannerProcessor": "ETH_ERC_20", + "transferProcessor": "BNB_SMART_20", + "prettyNumberProcessor": "ETH_ERC_20", + "addressCurrencyCode": "ETH", + "addressUiChecker": "ETH", + "ratesCurrencyCode": "METIS", + "feesCurrencyCode": "METIS", + "network": "mainnet", + "decimals": 18, + "tokenBlockchain": "METIS", + "tokenAddress": "0x75cb093E4D61d2A2e65D8e0BBb01DE8d89b53481", + "currencyExplorerLink": "https://andromeda-explorer.metis.io/address/", + "currencyExplorerTxLink": "https://andromeda-explorer.metis.io/tx/" + }, + "ETH_METIS": { + "currencyType": "token", + "currencyName": "Metis ERC20", + "currencyCode": "ETH_METIS", + "currencySymbol": "METIS", + "extendsProcessor": "ETH_TRUE_USD", + "addressUiChecker": "ETH", + "ratesCurrencyCode": "METIS", + "decimals": 18, + "tokenBlockchain": "ETHEREUM", + "tokenAddress": "0x9E32b13ce7f2E80A01932B42553652E053D6ed8e", + "currencyExplorerLink": "https://etherscan.io/token/0x9E32b13ce7f2E80A01932B42553652E053D6ed8e?a=" + }, + "BNB_SMART_METIS": { + "currencyType": "token", + "currencyName": "Metis BEP20", + "currencyCode": "BNB_SMART_METIS", + "currencySymbol": "METIS", + "ratesCurrencyCode": "METIS", + "extendsProcessor": "BNB_SMART_CAKE", + "addressUiChecker": "ETH", + "tokenAddress": "0xe552fb52a4f19e44ef5a967632dbc320b0820639", + "tokenBlockchain": "BNB", + "currencyExplorerLink": "https://bscscan.com/token/0xe552fb52a4f19e44ef5a967632dbc320b0820639?a=" + }, + "ETH_STORJ": { + "currencyType": "token", + "currencyName": "STORJ ERC20", + "currencyCode": "ETH_STORJ", + "currencySymbol": "STORJ", + "extendsProcessor": "ETH_TRUE_USD", + "addressUiChecker": "ETH", + "ratesCurrencyCode": "STORJ", + "decimals": 8, + "tokenBlockchain": "ETHEREUM", + "tokenAddress": "0xb64ef51c888972c908cfacf59b47c1afbc0ab8ac", + "currencyExplorerLink": "https://etherscan.io/token/0xb64ef51c888972c908cfacf59b47c1afbc0ab8ac?a=" + }, + "VLX": { + "currencyType": "coin", + "currencyName": "Velas", + "currencyCode": "VLX", + "currencySymbol": "VLX", + "ratesCurrencyCode": "VLX", + "addressCurrencyCode": "ETH", + "addressProcessor": "ETH", + "addressUiChecker": "ETH", + "scannerProcessor": "ETH", + "extendsProcessor": "ETH", + "prettyNumberProcessor": "ETH", + "transferProcessor": "ETC", + "network": "mainnet", + "decimals": 18, + "currencyExplorerLink": "https://evmexplorer.velas.com/address/", + "currencyExplorerTxLink": "https://evmexplorer.velas.com/tx/" + }, + "VLX_USDT": { + "currencyType": "token", + "currencyName": "Tether Velas VLX20", + "currencyCode": "VLX_USDT", + "currencyIcon": "USDT", + "currencySymbol": "USDT", + "addressProcessor": "ETH", + "scannerProcessor": "ETH_ERC_20", + "transferProcessor": "BNB_SMART_20", + "prettyNumberProcessor": "ETH_ERC_20", + "addressCurrencyCode": "ETH", + "addressUiChecker": "ETH", + "ratesCurrencyCode": "USDT", + "feesCurrencyCode": "VLX", + "network": "mainnet", + "decimals": 6, + "tokenBlockchain": "VLX", + "tokenAddress": "0x01445C31581c354b7338AC35693AB2001B50b9aE", + "currencyExplorerLink": "https://evmexplorer.velas.com/tokens/0x01445C31581c354b7338AC35693AB2001B50b9aE/token-transfers", + "currencyExplorerTxLink": "https://evmexplorer.velas.com/tx/" + }, + + "ONE": { + "currencyType": "coin", + "currencyName": "Harmony", + "currencyCode": "ONE", + "currencySymbol": "ONE", + "ratesCurrencyCode": "ONE", + "addressCurrencyCode": "ETH", + "addressProcessor": "ETH", + "addressUiChecker": "ETH_ONE", + "scannerProcessor": "ONE", + "extendsProcessor": "ETH", + "prettyNumberProcessor": "ETH", + "transferProcessor": "ETC", + "network": "mainnet", + "decimals": 18, + "currencyExplorerLink": "https://explorer.harmony.one/address/", + "currencyExplorerTxLink": "https://explorer.harmony.one/tx/" + }, + + "ETH_UFI": { + "currencyType": "token", + "currencyName": "PureFi Token ERC20", + "currencyCode": "ETH_UFI", + "currencySymbol": "UFI", + "extendsProcessor": "ETH_TRUE_USD", + "addressUiChecker": "ETH", + "ratesCurrencyCode": "PUREFI", + "decimals": 18, + "tokenBlockchain": "ETHEREUM", + "tokenAddress": "0xcDa4e840411C00a614aD9205CAEC807c7458a0E3", + "currencyExplorerLink": "https://etherscan.io/token/0xcDa4e840411C00a614aD9205CAEC807c7458a0E3?a=" + }, + "BNB_SMART_UFI": { + "currencyType": "token", + "currencyName": "PureFi Token BEP20", + "currencyCode": "BNB_SMART_UFI", + "currencySymbol": "UFI", + "ratesCurrencyCode": "PUREFI", + "extendsProcessor": "BNB_SMART_CAKE", + "addressUiChecker": "ETH", + "tokenAddress": "0xe2a59D5E33c6540E18aAA46BF98917aC3158Db0D", + "tokenBlockchain": "BNB", + "currencyExplorerLink": "https://bscscan.com/token/0xe2a59D5E33c6540E18aAA46BF98917aC3158Db0D?a=" + }, + "ETH_CLOUD": { + "currencyType": "token", + "currencyName": "Cloud NFT ERC20", + "currencyCode": "ETH_CLOUD", + "currencySymbol": "CLOUD", + "extendsProcessor": "ETH_TRUE_USD", + "addressUiChecker": "ETH", + "ratesCurrencyCode": "CLOUD", + "decimals": 18, + "tokenBlockchain": "ETHEREUM", + "tokenAddress": "0x810B56F6eC21eb5d24FcEd5c104A7dCa56f95e5d", + "currencyExplorerLink": "https://etherscan.io/token/0x810B56F6eC21eb5d24FcEd5c104A7dCa56f95e5d?a=" + }, + "BNB_SMART_CLOUD": { + "currencyType": "token", + "currencyName": "Cloud NFT BEP20", + "currencyCode": "BNB_SMART_CLOUD", + "currencySymbol": "CLOUD", + "ratesCurrencyCode": "CLOUD", + "extendsProcessor": "BNB_SMART_CAKE", + "addressUiChecker": "ETH", + "tokenAddress": "0x810B56F6eC21eb5d24FcEd5c104A7dCa56f95e5d", + "tokenBlockchain": "BNB", + "currencyExplorerLink": "https://bscscan.com/token/0x810B56F6eC21eb5d24FcEd5c104A7dCa56f95e5d?a=" + }, + "ETH_HEX": { + "currencyType": "token", + "currencyName": "HEX ERC20", + "currencyCode": "ETH_HEX", + "currencySymbol": "HEX", + "extendsProcessor": "ETH_TRUE_USD", + "addressUiChecker": "ETH", + "ratesCurrencyCode": "HEX", + "decimals": 8, + "tokenBlockchain": "ETHEREUM", + "tokenAddress": "0x2b591e99afe9f32eaa6214f7b7629768c40eeb39", + "currencyExplorerLink": "https://etherscan.io/token/0x2b591e99afe9f32eaa6214f7b7629768c40eeb39?a=" + }, + "ETH_PAXG": { + "currencyType": "token", + "currencyName": "PAX Gold ERC20", + "currencyCode": "ETH_PAXG", + "currencySymbol": "PAXG", + "extendsProcessor": "ETH_TRUE_USD", + "addressUiChecker": "ETH", + "ratesCurrencyCode": "PAXG", + "decimals": 18, + "tokenBlockchain": "ETHEREUM", + "tokenAddress": "0x45804880de22913dafe09f4980848ece6ecbaf78", + "currencyExplorerLink": "https://etherscan.io/token/0x45804880de22913dafe09f4980848ece6ecbaf78?a=" + }, + "ETH_ANT": { + "currencyType": "token", + "currencyName": "Aragon ERC20", + "currencyCode": "ETH_ANT", + "currencySymbol": "ANT", + "extendsProcessor": "ETH_TRUE_USD", + "addressUiChecker": "ETH", + "ratesCurrencyCode": "ANT", + "decimals": 18, + "tokenBlockchain": "ETHEREUM", + "tokenAddress": "0xa117000000f279d81a1d3cc75430faa017fa5a2e", + "currencyExplorerLink": "https://etherscan.io/token/0xa117000000f279d81a1d3cc75430faa017fa5a2e?a=" + }, + "ETH_COTI": { + "currencyType": "token", + "currencyName": "COTI ERC20", + "currencyCode": "ETH_COTI", + "currencySymbol": "COTI", + "extendsProcessor": "ETH_TRUE_USD", + "addressUiChecker": "ETH", + "ratesCurrencyCode": "COTI", + "decimals": 18, + "tokenBlockchain": "ETHEREUM", + "tokenAddress": "0xDDB3422497E61e13543BeA06989C0789117555c5", + "currencyExplorerLink": "https://etherscan.io/token/0xDDB3422497E61e13543BeA06989C0789117555c5?a=" + }, + "BNB_SMART_C98": { + "currencyType": "token", + "currencyName": "Coin98 BEP20", + "currencyCode": "BNB_SMART_C98", + "currencySymbol": "C98", + "ratesCurrencyCode": "COIN98", + "extendsProcessor": "BNB_SMART_CAKE", + "addressUiChecker": "ETH", + "tokenAddress": "0xaec945e04baf28b135fa7c640f624f8d90f1c3a6", + "tokenBlockchain": "BNB", + "currencyExplorerLink": "https://bscscan.com/token/0xaec945e04baf28b135fa7c640f624f8d90f1c3a6?a=" + }, + "ETH_C98": { + "currencyType": "token", + "currencyName": "Coin98 ERC20", + "currencyCode": "ETH_C98", + "currencySymbol": "C98", + "extendsProcessor": "ETH_TRUE_USD", + "addressUiChecker": "ETH", + "ratesCurrencyCode": "COIN98", + "decimals": 18, + "tokenBlockchain": "ETHEREUM", + "tokenAddress": "0xae12c5930881c53715b369cec7606b70d8eb229f", + "currencyExplorerLink": "https://etherscan.io/token/0xae12c5930881c53715b369cec7606b70d8eb229f?a=" + }, + "ETH_REN": { + "currencyType": "token", + "currencyName": "Ren ERC20", + "currencyCode": "ETH_REN", + "currencySymbol": "REN", + "extendsProcessor": "ETH_TRUE_USD", + "addressUiChecker": "ETH", + "ratesCurrencyCode": "REN", + "decimals": 18, + "tokenBlockchain": "ETHEREUM", + "tokenAddress": "0x408e41876cccdc0f92210600ef50372656052a38", + "currencyExplorerLink": "https://etherscan.io/token/0x408e41876cccdc0f92210600ef50372656052a38?a=" + }, + "ETH_CVC": { + "currencyType": "token", + "currencyName": "Civic ERC20", + "currencyCode": "ETH_CVC", + "currencySymbol": "CVC", + "extendsProcessor": "ETH_TRUE_USD", + "addressUiChecker": "ETH", + "ratesCurrencyCode": "CVC", + "decimals": 8, + "tokenBlockchain": "ETHEREUM", + "tokenAddress": "0x41e5560054824ea6b0732e656e3ad64e20e94e45", + "currencyExplorerLink": "https://etherscan.io/token/0x41e5560054824ea6b0732e656e3ad64e20e94e45?a=" + }, + "ETH_DENT": { + "currencyType": "token", + "currencyName": "Dent ERC20", + "currencyCode": "ETH_DENT", + "currencySymbol": "DENT", + "extendsProcessor": "ETH_TRUE_USD", + "addressUiChecker": "ETH", + "ratesCurrencyCode": "DENT", + "decimals": 8, + "tokenBlockchain": "ETHEREUM", + "tokenAddress": "0x3597bfd533a99c9aa083587b074434e61eb0a258", + "currencyExplorerLink": "https://etherscan.io/token/0x3597bfd533a99c9aa083587b074434e61eb0a258?a=" + }, + "ETH_GNO": { + "currencyType": "token", + "currencyName": "Gnosis ERC20", + "currencyCode": "ETH_GNO", + "currencySymbol": "GNO", + "extendsProcessor": "ETH_TRUE_USD", + "addressUiChecker": "ETH", + "ratesCurrencyCode": "GNO", + "decimals": 18, + "tokenBlockchain": "ETHEREUM", + "tokenAddress": "0x6810e776880c02933d47db1b9fc05908e5386b96", + "currencyExplorerLink": "https://etherscan.io/token/0x6810e776880c02933d47db1b9fc05908e5386b96?a=" + }, + "ETH_BRD": { + "currencyType": "token", + "currencyName": "Bread ERC20", + "currencyCode": "ETH_BRD", + "currencySymbol": "BRD", + "extendsProcessor": "ETH_TRUE_USD", + "addressUiChecker": "ETH", + "ratesCurrencyCode": "BRD", + "decimals": 18, + "tokenBlockchain": "ETHEREUM", + "tokenAddress": "0x558ec3152e2eb2174905cd19aea4e34a23de9ad6", + "currencyExplorerLink": "https://etherscan.io/token/0x558ec3152e2eb2174905cd19aea4e34a23de9ad6?a=" + }, + "WAVES_USDN": { + "currencyType": "token", + "currencyName": "USDN Waves", + "currencyCode": "WAVES_USDN", + "currencyIcon": "WAVES", + "currencySymbol": "USDN", + "addressProcessor": "WAVES", + "scannerProcessor": "WAVES_ERC_20", + "transferProcessor": "WAVES", + "prettyNumberProcessor": "UNIFIED", + "addressCurrencyCode": "WAVES", + "addressUiChecker": "WAVES", + "ratesCurrencyCode": "USDN", + "feesCurrencyCode": "WAVES", + "decimals": 6, + "tokenBlockchain": "WAVES", + "tokenAddress": "DG2xFkPdDwKUoBkzGAhQtLpSGzfXLiCYPEzeKH2Ad24p", + "currencyExplorerLink": "https://wavesexplorer.com/address/", + "currencyExplorerTxLink": "https://wavesexplorer.com/tx/" + }, + "WAVES_USDT": { + "currencyType": "token", + "currencyName": "Tether Waves", + "currencyCode": "WAVES_USDT", + "currencySymbol": "USDT", + "extendsProcessor": "WAVES_USDN", + "addressUiChecker": "WAVES", + "ratesCurrencyCode": "USDT", + "decimals": 6, + "tokenBlockchain": "WAVES", + "tokenAddress": "34N9YcEETLWn93qYQ64EsP1x89tSruJU44RrEMSXXEPJ", + "currencyExplorerLink": "https://wavesexplorer.com/address/", + "currencyExplorerTxLink": "https://wavesexplorer.com/tx/" + }, + "WAVES_USDC": { + "currencyType": "token", + "currencyName": "USDC Waves", + "currencyCode": "WAVES_USDC", + "currencySymbol": "USDС", + "extendsProcessor": "WAVES_USDN", + "addressUiChecker": "WAVES", + "ratesCurrencyCode": "USDC", + "decimals": 6, + "tokenBlockchain": "WAVES", + "tokenAddress": "6XtHjpXbs9RRJP2Sr9GUyVqzACcby9TkThHXnjVC5CDJ", + "currencyExplorerLink": "https://wavesexplorer.com/address/", + "currencyExplorerTxLink": "https://wavesexplorer.com/tx/" + }, + "ASH_SKRYPIN": { + "currencyType": "token", + "currencyName": "Skrypin", + "currencyCode": "ASH_SKRYPIN", + "currencyIcon": "ASH", + "currencySymbol": "SKRYPIN", + "addressProcessor": "ASH", + "scannerProcessor": "WAVES_ERC_20", + "transferProcessor": "WAVES", + "prettyNumberProcessor": "UNIFIED", + "addressCurrencyCode": "ASH", + "addressUiChecker": "ASH", + "ratesCurrencyCode": "SKRYPIN", + "feesCurrencyCode": "ASH", + "decimals": 8, + "tokenBlockchain": "ASH", + "tokenAddress": "H4hpkUDJGVr1HGXvDq8gYpoQ4zT94quVj4AYK2GihbHW", + "currencyExplorerLink": "http://explorer.aeneas.id/address/", + "currencyExplorerTxLink": "http://explorer.aeneas.id/tx/" + }, + "ASH_CRYPTEX": { + "currencyType": "token", + "currencyName": "CRYPTEX", + "currencyCode": "ASH_CRYPTEX", + "currencyIcon": "ASH", + "currencySymbol": "C24", + "addressProcessor": "ASH", + "scannerProcessor": "WAVES_ERC_20", + "transferProcessor": "WAVES", + "prettyNumberProcessor": "UNIFIED", + "addressCurrencyCode": "ASH", + "addressUiChecker": "ASH", + "ratesCurrencyCode": "C24", + "feesCurrencyCode": "ASH", + "decimals": 8, + "tokenBlockchain": "ASH", + "tokenAddress": "H4fw6LA9vHMEM4DdqrJGWNpvokg9xqDZzaRTuSSXRkzs", + "currencyExplorerLink": "http://explorer.aeneas.id/address/", + "currencyExplorerTxLink": "http://explorer.aeneas.id/tx/" + }, + "ETH_APE": { + "currencyType": "token", + "currencyName": "APE ERC20", + "currencyCode": "ETH_APE", + "currencySymbol": "APE", + "extendsProcessor": "ETH_TRUE_USD", + "addressUiChecker": "ETH", + "ratesCurrencyCode": "APE", + "decimals": 18, + "tokenBlockchain": "ETHEREUM", + "tokenAddress": "0x4d224452801aced8b2f0aebe155379bb5d594381", + "currencyExplorerLink": "https://etherscan.io/token/0x4d224452801aced8b2f0aebe155379bb5d594381?a=", + "currencyExplorerTxLink": "https://etherscan.io/tx/" + }, + "ETH_ATOM": { + "currencyType": "token", + "currencyName": "ATOM ERC20", + "currencyCode": "ETH_ATOM", + "currencySymbol": "ATOM", + "extendsProcessor": "ETH_TRUE_USD", + "addressUiChecker": "ETH", + "ratesCurrencyCode": "ATOM", + "decimals": 6, + "tokenBlockchain": "ETHEREUM", + "tokenAddress": "0x8D983cb9388EaC77af0474fA441C4815500Cb7BB", + "currencyExplorerLink": "https://etherscan.io/token/0x8D983cb9388EaC77af0474fA441C4815500Cb7BB?a=", + "currencyExplorerTxLink": "https://etherscan.io/tx/" + }, + "BNB_SMART_ATOM": { + "currencyType": "token", + "currencyName": "ATOM BEP20", + "currencyCode": "BNB_SMART_ATOM", + "currencySymbol": "ATOM", + "ratesCurrencyCode": "ATOM", + "extendsProcessor": "BNB_SMART_CAKE", + "addressUiChecker": "ETH", + "tokenAddress": "0x0eb3a705fc54725037cc9e008bdede697f62f335", + "decimals": 18, + "tokenBlockchain": "BNB", + "currencyExplorerLink": "https://bscscan.com/token/0x0eb3a705fc54725037cc9e008bdede697f62f335?a=" + }, + "BNB_SMART_GMT": { + "currencyType": "token", + "currencyName": "Green Metaverse Token", + "currencyCode": "BNB_SMART_GMT", + "currencySymbol": "GMT", + "ratesCurrencyCode": "GMT", + "extendsProcessor": "BNB_SMART_CAKE", + "addressUiChecker": "ETH", + "tokenAddress": "0x3019BF2a2eF8040C242C9a4c5c4BD4C81678b2A1", + "decimals": 8, + "tokenBlockchain": "BNB", + "currencyExplorerLink": "https://bscscan.com/token/0x3019BF2a2eF8040C242C9a4c5c4BD4C81678b2A1?a=" + }, + "BNB_SMART_TWT": { + "currencyType": "token", + "currencyName": "Trust Wallet", + "currencyCode": "BNB_SMART_TWT", + "currencySymbol": "TWT", + "ratesCurrencyCode": "TWT", + "extendsProcessor": "BNB_SMART_CAKE", + "addressUiChecker": "ETH", + "tokenAddress": "0x4b0f1812e5df2a09796481ff14017e6005508003", + "decimals": 18, + "tokenBlockchain": "BNB", + "currencyExplorerLink": "https://bscscan.com/token/0x4b0f1812e5df2a09796481ff14017e6005508003?a=" + }, + "VLX_USDV": { + "currencyType": "token", + "currencyName": "USD Velero VLX20", + "currencyCode": "VLX_USDV", + "currencyIcon": "USDV", + "currencySymbol": "USDV", + "addressProcessor": "ETH", + "scannerProcessor": "ETH_ERC_20", + "transferProcessor": "BNB_SMART_20", + "prettyNumberProcessor": "ETH_ERC_20", + "addressCurrencyCode": "ETH", + "addressUiChecker": "ETH", + "ratesCurrencyCode": "USDV", + "feesCurrencyCode": "VLX", + "network": "mainnet", + "decimals": 18, + "tokenBlockchain": "VLX", + "tokenAddress": "0xcd7509b76281223f5b7d3ad5d47f8d7aa5c2b9bf", + "currencyExplorerLink": "https://evmexplorer.velas.com/tokens/0xcd7509b76281223f5b7d3ad5d47f8d7aa5c2b9bf/token-transfers", + "currencyExplorerTxLink": "https://evmexplorer.velas.com/tx/" + }, + "ETH_VERSE": { + "currencyType": "token", + "currencyName": "Verse ERC20", + "currencyCode": "ETH_VERSE", + "currencySymbol": "VERSE", + "extendsProcessor": "ETH_TRUE_USD", + "addressUiChecker": "ETH", + "ratesCurrencyCode": "VERSE", + "decimals": 18, + "tokenBlockchain": "ETHEREUM", + "tokenAddress": "0x249cA82617eC3DfB2589c4c17ab7EC9765350a18", + "currencyExplorerLink": "https://etherscan.io/token/0x249cA82617eC3DfB2589c4c17ab7EC9765350a18?a=", + "currencyExplorerTxLink": "https://etherscan.io/tx/" + }, + "ETH_STETH": { + "currencyType": "token", + "currencyName": "stETH ERC20", + "currencyCode": "ETH_STETH", + "currencySymbol": "stETH", + "extendsProcessor": "ETH_TRUE_USD", + "addressUiChecker": "ETH", + "ratesCurrencyCode": "STETH", + "decimals": 18, + "tokenBlockchain": "ETHEREUM", + "tokenAddress": "0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84", + "currencyExplorerLink": "https://etherscan.io/token/0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84?a=", + "currencyExplorerTxLink": "https://etherscan.io/tx/", + "canBeDestination": 1 + }, + "ETH_STMATIC": { + "currencyType": "token", + "currencyName": "stMATIC ERC20", + "currencyCode": "ETH_STMATIC", + "currencySymbol": "stMATIC", + "extendsProcessor": "ETH_TRUE_USD", + "addressUiChecker": "ETH", + "ratesCurrencyCode": "STMATIC", + "decimals": 18, + "tokenBlockchain": "ETHEREUM", + "tokenAddress": "0x9ee91F9f426fA633d227f7a9b000E28b9dfd8599", + "currencyExplorerLink": "https://etherscan.io/token/0x9ee91F9f426fA633d227f7a9b000E28b9dfd8599?a=", + "currencyExplorerTxLink": "https://etherscan.io/tx/", + "canBeDestination": 1 + } +} diff --git a/crypto/assets/dappsBlocksoftDict.json b/crypto/assets/dappsBlocksoftDict.json new file mode 100644 index 000000000..0d66d3087 --- /dev/null +++ b/crypto/assets/dappsBlocksoftDict.json @@ -0,0 +1,66 @@ +{ + "OPENSEA": { + "dappCode" : "OPENSEA", + "dappName" : "OpenSea", + "dappUrl" : "https://opensea.io/account", + "dappNetworks" : ["ETH", "MATIC"], + "dappsDomenName": "opensea.io" + }, + "PANCAKE" : { + "dappCode" : "PANCAKE", + "dappName" : "Pancake Swap", + "dappUrl" : "https://pancakeswap.finance/swap", + "dappNetworks" : ["BNB_SMART"], + "disableInjected" : true, + "dappsDomenName": "pancakeswap.finance" + }, + "ETH_LIDO" : { + "dappCode" : "ETH_LIDO", + "dappName" : "Ethereum Liquid Staking (LIDO)", + "dappUrl" : "https://stake.lido.fi", + "dappNetworks" : ["ETH"], + "disableInjected" : true, + "dappsDomenName": "lido.fi" + }, + "MATIC_LIDO" : { + "dappCode" : "MATIC_LIDO", + "dappName" : "Polygon Liquid Staking (LIDO)", + "dappUrl" : "https://polygon.lido.fi", + "dappNetworks" : ["MATIC"], + "disableInjected" : true, + "dappsDomenName": "lido.fi" + }, + "NFTRADE" : { + "dappCode" : "NFTRADE", + "dappName" : "NFTrade", + "dappUrl" : "https://nftrade.com/", + "dappNetworks" : ["ETH", "BNB_SMART", "MATIC"], + "disableInjected" : true, + "dappsDomenName": "nftrade.com" + }, + "JUSTLEND" : { + "dappCode": "JUSTLEND", + "dappName" : "JustLend", + "dappUrl" : "https://justlend.org/#/home", + "dappNetworks" : ["TRX"], + "dappCoins" : ["TRX", "TRX_USDT"], + "dappsDomenName": "justlend.org" + }, + "BTT" : { + "dappCode": "BTT", + "dappName" : "BitTorrent Chain Wallet", + "dappUrl" : "https://wallet.bt.io/staking", + "dappNetworks" : ["TRX", "BTTC"], + "dappCoins" : ["TRX", "BTT"], + "dappsDomenName": "wallet.bt.io" + }, + "EXON" : { + "dappCode" : "EXON", + "dappName" : "Exon Center", + "dappUrl" : "https://app.exoncenter.com/430", + "dappNetworks" : ["TRX"], + "dappCoins" : ["TRX_EXON"], + "dappsDomenName": "app.exoncenter.com" + } + +} diff --git a/crypto/assets/tokenBlockchainBlocksoftDict.json b/crypto/assets/tokenBlockchainBlocksoftDict.json new file mode 100644 index 000000000..911fdd13e --- /dev/null +++ b/crypto/assets/tokenBlockchainBlocksoftDict.json @@ -0,0 +1,40 @@ +{ + "BITCOIN": { + "currencyCode" : "BTC", + "blockchainName": "Bitcoin" + }, + "ETHEREUM" : { + "currencyCode" : "ETH", + "blockchainName" : "Ethereum" + }, + "TRON" : { + "currencyCode" : "TRX", + "blockchainName" : "Tron" + }, + "BNB" : { + "currencyCode": "BNB_SMART", + "blockchainName" : "BSC (BEP-20)", + "dappsListName" : "BNB Smart Chain" + }, + "MATIC" : { + "currencyCode" : "MATIC", + "blockchainName" : "Matic", + "dappsListName" : "Polygon Network" + }, + "FTM" : { + "currencyCode": "FTM", + "blockchainName" : "FTM" + }, + "SOLANA" : { + "currencyCode" : "SOL", + "blockchainName" : "Solana" + }, + "METIS" : { + "currencyCode" : "METIS", + "blockchainName": "Metis" + }, + "ONE" : { + "currencyCode" : "ONE", + "blockchainName" : "Harmony" + } +} diff --git a/crypto/blockchains/ash/AshAddressProcessor.js b/crypto/blockchains/ash/AshAddressProcessor.js new file mode 100644 index 000000000..a2103434b --- /dev/null +++ b/crypto/blockchains/ash/AshAddressProcessor.js @@ -0,0 +1,48 @@ +/** + * @version 0.5 + */ +import { crypto, base58Encode } from '@waves/ts-lib-crypto'; + +export default class AshAddressProcessor { + async setBasicRoot() { + // empty + } + + /** + * @param {string|Buffer} _privateKey + * @param {*} data + * @returns {Promise<{privateKey: string, address: string, addedData: *}>} + */ + // eslint-disable-next-line @typescript-eslint/no-unused-vars + async getAddress(_privateKey, _data = {}, superPrivateData = {}) { + const all = crypto({ seed: superPrivateData.mnemonic }); + const key2 = all.keyPair(); + + // nope: console.log(` buff2 ` + base58Encode(buildAddress(key2.publicKey, 'A'))) + + // 1. take the byte array representation of generated public key + // 2. create new byte array XX + // 3. first two byte values in array XX would be [1, 65] which stands for address version and chainId (character ‘A’) + const prefix = [1, 65]; + + // 4. calculate Keccak256.hash(Blake2b256.hash(pk)) where pk is public key array of bytes + const blaked = all.blake2b(key2.publicKey); + const keccaked = all.keccak(blaked); + + // 5. append result of step 4 to array XX + const appended = all.concat(prefix, keccaked); + + // 6. generate checksum of array XX and append it to XX. Checksum algorithm: + // 1. generate Blake2b256 hash of given array + // 2. take the first 4 elements of generated Blake2b256 hash + const hash = all.blake2b(appended).slice(0, 4); + const hashed = all.concat(appended, hash); + + // 7. Encode the result array XX to Base58 string + // 8. Append 'Æx' character to the end of encoded string + const address = 'Æx' + base58Encode(hashed); + const pubKey = base58Encode(key2.publicKey); + const privKey = base58Encode(key2.privateKey); + return { address, privateKey: privKey, addedData: { pubKey } }; + } +} diff --git a/crypto/blockchains/bch/BchAddressProcessor.js b/crypto/blockchains/bch/BchAddressProcessor.js new file mode 100644 index 000000000..7e950da9a --- /dev/null +++ b/crypto/blockchains/bch/BchAddressProcessor.js @@ -0,0 +1,31 @@ +/** + * @version 0.5 + */ +import BtcCashUtils from './ext/BtcCashUtils'; +import BtcAddressProcessor from '../btc/address/BtcAddressProcessor'; +import bitcoin from 'bitcoinjs-lib'; + +export default class BchAddressProcessor extends BtcAddressProcessor { + /** + * @param {string|Buffer} privateKey + * @param {*} data + * @returns {Promise<{privateKey: string, address: string, addedData: *}>} + */ + // eslint-disable-next-line @typescript-eslint/no-unused-vars + async getAddress(privateKey, _data = {}) { + const keyPair = bitcoin.ECPair.fromPrivateKey(privateKey, { + network: this._currentBitcoinNetwork + }); + const publicKey = keyPair.publicKey; + const address = BtcCashUtils.fromPublicKeyToAddress(publicKey); + const legacyAddress = bitcoin.payments.p2pkh({ + pubkey: keyPair.publicKey, + network: this._currentBitcoinNetwork + }).address; + return { + address, + privateKey: keyPair.toWIF(), + addedData: { legacyAddress } + }; + } +} diff --git a/crypto/blockchains/bch/BchScannerProcessor.js b/crypto/blockchains/bch/BchScannerProcessor.js new file mode 100644 index 000000000..522872d2b --- /dev/null +++ b/crypto/blockchains/bch/BchScannerProcessor.js @@ -0,0 +1,41 @@ +/** + * @version 0.11 + * https://developer.bitcoin.com/rest/docs/address => trezor + */ +import DogeScannerProcessor from '../doge/DogeScannerProcessor'; +import BtcCashUtils from './ext/BtcCashUtils'; + +export default class BchScannerProcessor extends DogeScannerProcessor { + /** + * @type {number} + * @private + */ + _blocksToConfirm = 5; + + /** + * @type {string} + * @private + */ + _trezorServerCode = 'BCH_TREZOR_SERVER'; + + async _get(address, jsonData) { + let legacyAddress; + if (typeof jsonData.legacyAddress !== 'undefined') { + legacyAddress = jsonData.legacyAddress; + } else { + legacyAddress = BtcCashUtils.toLegacyAddress(address); + } + return super._get(legacyAddress); + } + + _addressesForFind(address, jsonData) { + let legacyAddress; + if (typeof jsonData.legacyAddress !== 'undefined') { + legacyAddress = jsonData.legacyAddress; + } else { + legacyAddress = BtcCashUtils.toLegacyAddress(address); + } + + return [address, legacyAddress, 'bitcoincash:' + address]; + } +} diff --git a/crypto/blockchains/bch/BchTransferProcessor.ts b/crypto/blockchains/bch/BchTransferProcessor.ts new file mode 100644 index 000000000..2dd70c4c1 --- /dev/null +++ b/crypto/blockchains/bch/BchTransferProcessor.ts @@ -0,0 +1,48 @@ +/** + * @version 0.20 + */ +import { BlocksoftBlockchainTypes } from '../BlocksoftBlockchainTypes'; +import DogeTransferProcessor from '../doge/DogeTransferProcessor'; +import BchUnspentsProvider from './providers/BchUnspentsProvider'; +import BchSendProvider from './providers/BchSendProvider'; +import DogeTxInputsOutputs from '../doge/tx/DogeTxInputsOutputs'; +import BchTxBuilder from './tx/BchTxBuilder'; + +export default class BchTransferProcessor + extends DogeTransferProcessor + implements BlocksoftBlockchainTypes.TransferProcessor +{ + _trezorServerCode = 'BCH_TREZOR_SERVER'; + + _builderSettings: BlocksoftBlockchainTypes.BuilderSettings = { + minOutputDustReadable: 0.000005, + minChangeDustReadable: 0.00001, + feeMaxForByteSatoshi: 10000, // for tx builder + feeMaxAutoReadable2: 0.2, // for fee calc, + feeMaxAutoReadable6: 0.1, // for fee calc + feeMaxAutoReadable12: 0.05, // for fee calc + changeTogether: true, + minRbfStepSatoshi: 10, + minSpeedUpMulti: 1.5 + }; + /** + * @private + */ + _initProviders() { + if (this._initedProviders) return false; + this.unspentsProvider = new BchUnspentsProvider( + this._settings, + this._trezorServerCode + ); + this.sendProvider = new BchSendProvider( + this._settings, + this._trezorServerCode + ); + this.txPrepareInputsOutputs = new DogeTxInputsOutputs( + this._settings, + this._builderSettings + ); + this.txBuilder = new BchTxBuilder(this._settings, this._builderSettings); + this._initedProviders = true; + } +} diff --git a/crypto/blockchains/bch/ext/BtcCashUtils.js b/crypto/blockchains/bch/ext/BtcCashUtils.js new file mode 100644 index 000000000..e18d08ced --- /dev/null +++ b/crypto/blockchains/bch/ext/BtcCashUtils.js @@ -0,0 +1,205 @@ +/** + * @author Ksu + * @version 0.5 + */ +const bs58check = require('bs58check') + +const VERSION_BYTE = { + 'P2PKH': 0, + 'P2SH': 5 +} + +// for legacy + +const CHARSET_INVERSE_INDEX = { + 'q': 0, 'p': 1, 'z': 2, 'r': 3, 'y': 4, '9': 5, 'x': 6, '8': 7, + 'g': 8, 'f': 9, '2': 10, 't': 11, 'v': 12, 'd': 13, 'w': 14, '0': 15, + 's': 16, '3': 17, 'j': 18, 'n': 19, '5': 20, '4': 21, 'k': 22, 'h': 23, + 'c': 24, 'e': 25, '6': 26, 'm': 27, 'u': 28, 'a': 29, '7': 30, 'l': 31, +} + +function fromUint5Array(data) { + return convertBits(data, 5, 8, true); +} + +function getType(versionByte) { + switch (versionByte & 120) { + case 0: + return 'P2PKH'; + case 8: + return 'P2SH'; + default: + throw new Error('Invalid address type in version byte: ' + versionByte + '.'); + } +} + +function base32decode(string) { + const data = new Uint8Array(string.length) + for (let i = 0; i < string.length; ++i) { + const value = string[i] + data[i] = CHARSET_INVERSE_INDEX[value] + } + return data +} + + +// for bitcoincash + +const createHash = require('create-hash') + +const GENERATOR1 = [0x98, 0x79, 0xf3, 0xae, 0x1e] +const GENERATOR2 = [0xf2bc8e61, 0xb76d99e2, 0x3e5fb3c4, 0x2eabe2a8, 0x4f43e470] + +const CHARSET = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l' + +function base32encode(data) { + let base32 = '' + for (let i = 0; i < data.length; i++) { + const value = data[i] + base32 += CHARSET[value] + } + return base32 +} + + +function polymod(data) { + // Treat c as 8 bits + 32 bits + let c0 = 0 + let c1 = 1 + let C = 0 + for (let j = 0; j < data.length; j++) { + // Set C to c shifted by 35 + C = c0 >>> 3 + // 0x[07]ffffffff + c0 &= 0x07 + // Shift as a whole number + c0 <<= 5 + c0 |= c1 >>> 27 + // 0xffffffff >>> 5 + c1 &= 0x07ffffff + c1 <<= 5 + // xor the last 5 bits + c1 ^= data[j] + for (let i = 0; i < GENERATOR1.length; ++i) { + if (C & (1 << i)) { + c0 ^= GENERATOR1[i] + c1 ^= GENERATOR2[i] + } + } + } + c1 ^= 1 + // Negative numbers -> large positive numbers + if (c1 < 0) { + c1 ^= 1 << 31 + c1 += (1 << 30) * 2 + } + // Unless bitwise operations are used, + // numbers are consisting of 52 bits, except + // the sign bit. The result is max 40 bits, + // so it fits perfectly in one number! + return c0 * (1 << 30) * 4 + c1 +} + +function checksumToArray(checksum) { + const result = [] + for (let i = 0; i < 8; ++i) { + result.push(checksum & 31) + checksum /= 32 + } + return result.reverse() +} + +function getHashSizeBits(hash) { + switch (hash.length * 8) { + case 160: + return 0 + case 192: + return 1 + case 224: + return 2 + case 256: + return 3 + case 320: + return 4 + case 384: + return 5 + case 448: + return 6 + case 512: + return 7 + default: + throw new Error('Invalid hash size:' + hash.length) + } +} + +function convertBits(data, from, to, strict) { + strict = strict || false + let accumulator = 0 + let bits = 0 + const result = [] + const mask = (1 << to) - 1 + for (let i = 0; i < data.length; i++) { + const value = data[i] + accumulator = (accumulator << from) | value + bits += from + while (bits >= to) { + bits -= to + result.push((accumulator >> bits) & mask) + } + } + if (!strict) { + if (bits > 0) { + result.push((accumulator << (to - bits)) & mask) + } + } + return result +} + +function fromHashToAddress(hash) { + const eight0 = [0, 0, 0, 0, 0, 0, 0, 0] + // noinspection PointlessArithmeticExpressionJS + const versionByte = 0 + getHashSizeBits(hash) // getTypeBits(this.type) + const arr = Array.prototype.slice.call(hash, 0) + const payloadData = convertBits([versionByte].concat(arr), 8, 5) + + const prefixData = [2, 9, 20, 3, 15, 9, 14, 3, 1, 19, 8, 0] + const checksumData = prefixData.concat(payloadData).concat(eight0) + const payload = payloadData.concat(checksumToArray(polymod(checksumData))) + return base32encode(payload) +} + +export default { + fromPublicKeyToAddress(publicKey) { + const one = createHash('sha256').update(publicKey, 'hex').digest() + const hash = createHash('ripemd160').update(one).digest() + return fromHashToAddress(hash) + }, + toLegacyAddress(address) { + if (address.indexOf('bitcoincash:') === 0) { + address = address.substr(12) + } + if (address.substr(0, 1) !== 'q' && address.substr(0,1) !== 'p') { + return address + } + const payloadBack = base32decode(address) + const payloadDataBack = fromUint5Array(payloadBack.subarray(0, -8)); + const versionByteBack = payloadDataBack[0]; + const hashBack = payloadDataBack.slice(1); + const typeBack = getType(versionByteBack); + const buffer = Buffer.alloc(1 + hashBack.length) + buffer[0] = VERSION_BYTE[typeBack] + buffer.set(hashBack, 1) + return bs58check.encode(buffer) + }, + fromLegacyAddress(address) { + if (!address || address === '') { + return '' + } + if (address.substr(0, 1) === 'q') { + return address + } + let hash = bs58check.decode(address) + hash = hash.subarray(1) + return fromHashToAddress(hash) + } +} diff --git a/crypto/blockchains/bch/providers/BchSendProvider.ts b/crypto/blockchains/bch/providers/BchSendProvider.ts new file mode 100644 index 000000000..fa98f91e9 --- /dev/null +++ b/crypto/blockchains/bch/providers/BchSendProvider.ts @@ -0,0 +1,67 @@ +/** + * @version 0.20 + */ +import { BlocksoftBlockchainTypes } from '../../BlocksoftBlockchainTypes'; +import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; +import BlocksoftAxios from '../../../common/BlocksoftAxios'; +import DogeSendProvider from '../../doge/providers/DogeSendProvider'; + +export default class BchSendProvider + extends DogeSendProvider + implements BlocksoftBlockchainTypes.SendProvider +{ + _apiPath = 'https://rest.bitcoin.com/v2/rawtransactions/sendRawTransaction/'; + + async sendTx( + hex: string, + subtitle: string, + txRBF: any, + logData: any + ): Promise<{ transactionHash: string; transactionJson: any }> { + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' BchSendProvider.sendTx ' + + subtitle + + ' started ' + + subtitle + ); + + try { + const trezor = await super.sendTx(hex, subtitle, txRBF, logData); + if (trezor) { + return trezor; + } + } catch (e) { + if (e.message.indexOf('SERVER_RESPONSE_') !== -1) { + throw e; + } else { + // do nothing + } + } + + let res; + try { + res = await BlocksoftAxios.get(this._apiPath + hex); + } catch (e) { + if (e.message.indexOf('dust') !== -1) { + throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_DUST'); + } else if ( + e.message.indexOf('bad-txns-inputs-spent') !== -1 || + e.message.indexOf('txn-mempool-conflict') !== -1 + ) { + throw new Error('SERVER_RESPONSE_NO_RESPONSE'); + } else if ( + e.message.indexOf('fee for relay') !== -1 || + e.message.indexOf('insufficient priority') !== -1 + ) { + throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_FEE'); + } else { + throw e; + } + } + if (typeof res.data === 'undefined' || !res.data) { + throw new Error('SERVER_RESPONSE_NOT_CONNECTED'); + } + return { transactionHash: res.data, transactionJson: {} }; + } +} diff --git a/crypto/blockchains/bch/providers/BchUnspentsProvider.ts b/crypto/blockchains/bch/providers/BchUnspentsProvider.ts new file mode 100644 index 000000000..39504ebe7 --- /dev/null +++ b/crypto/blockchains/bch/providers/BchUnspentsProvider.ts @@ -0,0 +1,63 @@ +/** + * @version 0.20 + * + * https://developer.bitcoin.com/rest/docs/address + * https://rest.bitcoin.com/v2/address/utxo/bitcoincash:qz6qh4304stgwpqxp6gwsucma30fewp7z5cs4yuvdf + */ +import { BlocksoftBlockchainTypes } from '../../BlocksoftBlockchainTypes' +import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog' +import BlocksoftAxios from '../../../common/BlocksoftAxios' +import BtcCashUtils from '../ext/BtcCashUtils' +import DogeUnspentsProvider from '../../doge/providers/DogeUnspentsProvider' + +export default class BchUnspentsProvider extends DogeUnspentsProvider implements BlocksoftBlockchainTypes.UnspentsProvider { + + _apiPath = 'https://rest.bitcoin.com/v2/address/utxo/bitcoincash:' + + async getUnspents(address: string): Promise { + try { + const trezor = await super.getUnspents('bitcoincash:' + address) + if (trezor && trezor.length > 0) { + return trezor + } + } catch (e) { + // do nothing + } + // @ts-ignore + BlocksoftCryptoLog.log(this._settings.currencyCode + ' BchUnspentsProvider.getUnspents started', address) + address = BtcCashUtils.fromLegacyAddress(address) + const link = this._apiPath + address + const res = await BlocksoftAxios.getWithoutBraking(link) + if (!res || !res.data || typeof res.data === 'undefined') { + BlocksoftCryptoLog.log(this._settings.currencyCode + ' BchUnspentsProvider.getUnspents nothing loaded for address ' + address + ' link ' + link) + throw new Error('SERVER_RESPONSE_NOT_CONNECTED') + } + if (!res.data || typeof res.data.utxos === 'undefined' || !res.data.utxos) { + return [] + } + const sortedUnspents = [] + /** + * https://rest.bitcoin.com/v2/address/utxo/bitcoincash:qz6qh4304stgwpqxp6gwsucma30fewp7z5cs4yuvdf + * @param {*} res.data.utxos[] + * @param {string} res.data.utxos[].txid 5be83026d82b56e8df7fa309e0b50132cb5cac228f83103532b20e0c991a3f9b + * @param {string} res.data.utxos[].vout 1 + * @param {string} res.data.utxos[].amount 0.04373313 + * @param {string} res.data.utxos[].satoshis 4373313 + * @param {string} res.data.utxos[].height 615754 + * @param {string} res.data.utxos[].confirmations + */ + let unspent + for (unspent of res.data.utxos) { + sortedUnspents.push({ + txid: unspent.txid, + vout: unspent.vout, + value: unspent.satoshis.toString(), + height: unspent.height, + confirmations : unspent.confirmations, + isRequired : false + }) + } + + return sortedUnspents + } +} diff --git a/crypto/blockchains/bch/tx/BchTxBuilder.ts b/crypto/blockchains/bch/tx/BchTxBuilder.ts new file mode 100644 index 000000000..e7e64392a --- /dev/null +++ b/crypto/blockchains/bch/tx/BchTxBuilder.ts @@ -0,0 +1,53 @@ +/** + * @version 0.20 + */ +import { BlocksoftBlockchainTypes } from '../../BlocksoftBlockchainTypes'; +import DogeTxBuilder from '../../doge/tx/DogeTxBuilder'; +import BtcCashUtils from '../ext/BtcCashUtils'; +import bitcoin, { TransactionBuilder } from 'bitcoinjs-lib'; + +export default class BchTxBuilder + extends DogeTxBuilder + implements BlocksoftBlockchainTypes.TxBuilder +{ + _getRawTxValidateKeyPair( + privateData: BlocksoftBlockchainTypes.TransferPrivateData, + data: BlocksoftBlockchainTypes.TransferData + ): void { + this.keyPair = false; + try { + this.keyPair = bitcoin.ECPair.fromWIF( + privateData.privateKey, + this._bitcoinNetwork + ); + const address = bitcoin.payments.p2pkh({ + pubkey: this.keyPair.publicKey, + network: this._bitcoinNetwork + }).address; + const legacyAddress = BtcCashUtils.toLegacyAddress(data.addressFrom); + if (address !== data.addressFrom && address !== legacyAddress) { + // noinspection ExceptionCaughtLocallyJS + throw new Error( + 'not valid signing address ' + + data.addressFrom + + ' != ' + + address + + ' != ' + + legacyAddress + ); + } + } catch (e) { + // @ts-ignore + e.message += ' in privateKey BCH signature check '; + throw e; + } + } + + _getRawTxAddOutput( + txb: TransactionBuilder, + output: BlocksoftBlockchainTypes.OutputTx + ): void { + const to = BtcCashUtils.toLegacyAddress(output.to); + txb.addOutput(to, Number(output.amount)); + } +} diff --git a/crypto/blockchains/bnb/BnbAddressProcessor.js b/crypto/blockchains/bnb/BnbAddressProcessor.js new file mode 100644 index 000000000..b16a66dbc --- /dev/null +++ b/crypto/blockchains/bnb/BnbAddressProcessor.js @@ -0,0 +1,52 @@ +/** + * @version 0.20 + * https://github.com/binance-chain/javascript-sdk/blob/aa1947b696f984aa931f5f029e4a439c45d5e853/src/client/index.ts#L208 + */ +const elliptic = require('elliptic') +const ec = new elliptic.ec('secp256k1') +const createHash = require('create-hash') +const bech = require('bech32') + +const Web3 = require('web3') + +function ab2hexstring(arr) { + let result = '' + for (let i = 0; i < arr.length; i++) { + let str = arr[i].toString(16) + str = str.length === 0 ? '00' : str.length === 1 ? '0' + str : str + result += str + } + return result +} + +export default class BnbAddressProcessor { + + constructor() { + this._web3Link = 'https://bsc-dataseed1.binance.org:443' + this._web3 = new Web3(new Web3.providers.HttpProvider(this._web3Link)) + } + + async setBasicRoot(root) { + + } + + /** + * @param {string|Buffer} privateKey + * @param {*} data + * @returns {Promise<{privateKey: string, address: string}>} + */ + async getAddress(privateKey, data = {}, superPrivateData = {}) { + const keypair = ec.keyFromPrivate(privateKey.toString('hex'), 'hex') + const pubPoint = keypair.getPublic() + const compressed = pubPoint.encodeCompressed() + const hexed = ab2hexstring(compressed) + const one = createHash('sha256').update(hexed, 'hex').digest() + const hash = createHash('ripemd160').update(one).digest() + const words = bech.toWords(hash) + const address = bech.encode('bnb', words) + + const account = this._web3.eth.accounts.privateKeyToAccount( '0x' + privateKey.toString('hex')) + + return { address, privateKey : privateKey.toString('hex'), addedData : { ethAddress : account.address} } + } +} diff --git a/crypto/blockchains/bnb/BnbScannerProcessor.js b/crypto/blockchains/bnb/BnbScannerProcessor.js new file mode 100644 index 000000000..30b080ec8 --- /dev/null +++ b/crypto/blockchains/bnb/BnbScannerProcessor.js @@ -0,0 +1,169 @@ +/** + * @version 0.20 + * https://docs.binance.org/api-reference/dex-api/paths.html#apiv1transactions + */ +import BlocksoftAxios from '../../common/BlocksoftAxios' +import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog' +import BlocksoftUtils from '../../common/BlocksoftUtils' +import config from '../../../app/config/config' +import BlocksoftExternalSettings from '../../common/BlocksoftExternalSettings' + +export default class BnbScannerProcessor { + + + /** + * https://dex.binance.org/api/v1/account/bnb146lec0enyzz2x5kpna8kwelky8kumfhj05aspz + * @param {string} address + * @return {Promise<{balance, unconfirmed, provider}>} + */ + async getBalanceBlockchain(address) { + const apiServer = await BlocksoftExternalSettings.getStatic('BNB_SERVER') + const link = `${apiServer}/api/v1/account/${address}` + let balance = 0 + let frozen = 0 + const res = await BlocksoftAxios.getWithoutBraking(link) + // "balances":[{"free":"0.00100000","frozen":"0.00000000","locked":"0.00000000","symbol":"BNB"}] + if (res && typeof res.data !== 'undefined' && res.data && typeof res.data.balances !== 'undefined') { + let row + for (row of res.data.balances) { + if (row.symbol === 'BNB') { + balance = row.free + frozen = row.frozen + break + } + } + } else { + await BlocksoftCryptoLog.log('BnbScannerProcessor.getBalanceBlockchain ' + address + ' no actual balance ' + link, res) + return false + } + + + return { balance, unconfirmed: 0, frozen, provider: 'dex.binance' } + } + + + /** + * https://dex.binance.org/api/v1/transactions/?address=bnb146lec0enyzz2x5kpna8kwelky8kumfhj05aspz + * https://dex.binance.org/api/v1/transactions/?address=bnb146lec0enyzz2x5kpna8kwelky8kumfhj05aspz&startTime=1609452000000&limit=100 + * https://docs.binance.org/api-reference/dex-api/paths.html#apiv1transactions + * https://github.com/trustwallet/blockatlas/blob/b4f6dc360bed412ff555aa981d83e4421380f104/platform/binance/client.go#L43 + * @param {string} scanData.account.address + * @return {Promise<[UnifiedTransaction]>} + */ + async getTransactionsBlockchain(scanData) { + const address = scanData.account.address.trim() + BlocksoftCryptoLog.log('BnbScannerProcessor.getTransactions started', address) + + const apiServer = await BlocksoftExternalSettings.getStatic('BNB_SERVER') + + let linkTxs = `${apiServer}/api/v1/transactions/?address=${address}&txAsset=BNB` // 2021-01-01 + if (scanData.account.balanceScanTime && scanData.account.balanceScanTime * 1 > 0) { + linkTxs += '&startTime=' + (scanData.account.balanceScanTime - 86400) * 1000 // 1 day + } + + const res = await BlocksoftAxios.getWithoutBraking(linkTxs) + if (!res || typeof res.data === 'undefined' || !res.data) { + return false + } + + const transactions = await this._unifyTransactions(address, res.data.tx) + BlocksoftCryptoLog.log('BnbScannerProcessor.getTransactions finished', address) + return transactions + } + + async _unifyTransactions(address, result) { + const transactions = [] + let tx + for (tx of result) { + const transaction = await this._unifyTransaction(address, tx) + if (transaction) { + transactions.push(transaction) + } + } + return transactions + } + + /** + * @param {string} address + * @param {Object} transaction + * @param {string} transaction.blockHeight 144626977 + * @param {string} transaction.code 0 + * @param {string} transaction.confirmBlocks + * @param {string} transaction.data + * @param {string} transaction.fromAddr bnb1jxfh2g85q3v0tdq56fnevx6xcxtcnhtsmcu64m + * @param {string} transaction.memo + * @param {string} transaction.orderId + * @param {string} transaction.proposalId + * @param {string} transaction.sequence 1950767 + * @param {string} transaction.source 0 + * @param {string} transaction.timeStamp 2021-02-15T21:40:00.232Z + * @param {string} transaction.toAddr bnb146lec0enyzz2x5kpna8kwelky8kumfhj05aspz + * @param {string} transaction.txAge 428893 + * @param {string} transaction.txAsset "BNB" + * @param {string} transaction.txFee "0.00037500" + * @param {string} transaction.txHash "D160D16A01998B023EC3ABBCD1D1064F23AC1D17715ECAE1E895DC0AA9D12B5A" + * @param {string} transaction.txType: "TRANSFER" + * @param {string} transaction.value: "0.00100000" + * @return {UnifiedTransaction} + * @private + **/ + async _unifyTransaction(address, transaction) { + try { + let tx + if (transaction.txType === 'TRANSFER') { + tx = { + transactionHash: transaction.txHash, + blockHash: transaction.blockHeight, + blockNumber: transaction.blockHeight, + blockTime: transaction.timeStamp, + blockConfirmations: transaction.txAge, + transactionDirection: '?', + addressFrom: transaction.fromAddr === address ? '' : transaction.fromAddr, + addressTo: transaction.toAddr === address ? '' : transaction.toAddr, + addressAmount: transaction.value, + transactionStatus: transaction.code === 0 ? 'success' : (transaction.txAge === 0 ? 'new' : 'fail'), + transactionFee: transaction.txFee + } + } else if (transaction.txType === 'CROSS_TRANSFER_OUT' && typeof transaction.data !== 'undefined') { + const tmp = JSON.parse(transaction.data) + if (typeof tmp.amount.denom === 'undefined' || tmp.amount.denom !== 'BNB') return false + tx = { + transactionHash: transaction.txHash, + blockHash: transaction.blockHeight, + blockNumber: transaction.blockHeight, + blockTime: transaction.timeStamp, + blockConfirmations: transaction.txAge, + transactionDirection: '?', + addressFrom: tmp.from === address ? '' : tmp.from, + addressTo: tmp.to === address ? '' : tmp.to, + addressAmount: BlocksoftUtils.toUnified(tmp.amount.amount, 8), + transactionStatus: transaction.code === 0 ? 'success' : (transaction.txAge === 0 ? 'new' : 'fail'), + transactionFee: transaction.txFee + } + } else { + return false + } + + if (tx.addressTo === '' || !tx.addressTo) { + if (tx.addressFrom === '') { + tx.transactionDirection = 'self' + } else { + tx.transactionDirection = 'income' + } + } else { + tx.transactionDirection = 'outcome' + } + + if (typeof transaction.memo !== 'undefined' && transaction.memo !== '') { + tx.transactionJson = { memo: transaction.memo } + } + + return tx + } catch (e) { + if (config.debug.cryptoErrors) { + console.log('BnbScannerProcessor _unifyTransaction error ' + e.message) + } + throw e + } + } +} diff --git a/crypto/blockchains/bnb/BnbTransferProcessor.ts b/crypto/blockchains/bnb/BnbTransferProcessor.ts new file mode 100644 index 000000000..6dfe3d3b0 --- /dev/null +++ b/crypto/blockchains/bnb/BnbTransferProcessor.ts @@ -0,0 +1,153 @@ +/** + * @version 0.20 + * https://docs.binance.org/smart-chain/developer/create-wallet.html + * https://docs.binance.org/guides/concepts/encoding/amino-example.html#transfer + */ +import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog' +import MarketingEvent from '../../../app/services/Marketing/MarketingEvent' + +import { BlocksoftBlockchainTypes } from '../BlocksoftBlockchainTypes' +import { BnbTxSendProvider } from './basic/BnbTxSendProvider' +import BlocksoftUtils from '../../common/BlocksoftUtils' +import BnbNetworkPrices from './basic/BnbNetworkPrices' + +export default class BnbTransferProcessor implements BlocksoftBlockchainTypes.TransferProcessor { + private _settings: { network: string; currencyCode: string } + private _provider: BnbTxSendProvider + + constructor(settings: { network: string; currencyCode: string }) { + this._settings = settings + this._provider = new BnbTxSendProvider() + } + + needPrivateForFee(): boolean { + return false + } + + checkSendAllModal(data: { currencyCode: any }): boolean { + return false + } + + async getFeeRate(data: BlocksoftBlockchainTypes.TransferData, privateData: BlocksoftBlockchainTypes.TransferPrivateData, additionalData: {} = {}): Promise { + + const fees = await BnbNetworkPrices.getFees() + const feeForTx = BlocksoftUtils.toUnified(fees.send.fee, 8) + const result: BlocksoftBlockchainTypes.FeeRateResult = { + selectedFeeIndex: 0, + shouldShowFees : false, + fees : [ + { + langMsg: 'xrp_speed_one', + feeForTx, + amountForTx: data.amount + } + ] + } as BlocksoftBlockchainTypes.FeeRateResult + return result + } + + async getTransferAllBalance(data: BlocksoftBlockchainTypes.TransferData, privateData: BlocksoftBlockchainTypes.TransferPrivateData, additionalData: BlocksoftBlockchainTypes.TransferAdditionalData = {}): Promise { + + const balance = data.amount + // @ts-ignore + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' BnbTransferProcessor.getTransferAllBalance ', data.addressFrom + ' => ' + balance) + + const result = await this.getFeeRate(data, privateData, additionalData) + // @ts-ignore + if (!result || result.selectedFeeIndex < 0) { + return { + selectedTransferAllBalance: '0', + selectedFeeIndex: -2, + fees: [], + countedForBasicBalance: balance + } + } + // @ts-ignore + const newAmount = BlocksoftUtils.diff(result.fees[result.selectedFeeIndex].amountForTx, result.fees[result.selectedFeeIndex].feeForTx).toString() + if (newAmount*1 < 0) { + throw new Error('SERVER_RESPONSE_NOTHING_TO_TRANSFER') + } + result.fees[result.selectedFeeIndex].amountForTx = newAmount + const tmp = { + ...result, + shouldShowFees : false, + selectedTransferAllBalance: result.fees[result.selectedFeeIndex].amountForTx + } + // console.log('tmp', JSON.stringify(tmp)) + return tmp + } + + async sendTx(data: BlocksoftBlockchainTypes.TransferData, privateData: BlocksoftBlockchainTypes.TransferPrivateData, uiData: BlocksoftBlockchainTypes.TransferUiData): Promise { + + if (typeof privateData.privateKey === 'undefined') { + throw new Error('BNB transaction required privateKey') + } + if (typeof data.addressTo === 'undefined') { + throw new Error('BNB transaction required addressTo') + } + + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' BnbTransferProcessor.sendTx start') + + let transaction + try { + transaction = await this._provider.getPrepared(data, privateData, uiData) + } catch (e) { + throw new Error(e.message + ' in BNB getPrepared') + } + // @ts-ignore + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' BnbTransferProcessor.sendTx tx', transaction) + + + let raw + try { + raw = this._provider.serializeTx(transaction) + } catch (e) { + throw new Error(e.message + ' in BNB serializeTx') + } + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' BnbTransferProcessor.sendTx raw', raw) + if (typeof uiData !== 'undefined' && typeof uiData.selectedFee !== 'undefined' && typeof uiData.selectedFee.rawOnly !== 'undefined' && uiData.selectedFee.rawOnly) { + return { rawOnly: uiData.selectedFee.rawOnly, raw } + } + + let result + try { + result = await this._provider.sendRaw(raw) + } catch (e) { + if (e.message.indexOf('SERVER_RESPONSE_') === -1) { + throw new Error(e.message + ' in BNB sendRaw1') + } else { + throw e + } + } + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' BnbTransferProcessor.sendTx result', result) + + try { + if (typeof result.message !== 'undefined') { + if (result.message.indexOf('insufficient fund') !== -1 || result.message.indexOf('BNB <') !== -1) { + throw new Error('SERVER_RESPONSE_NOTHING_TO_TRANSFER') + } else { + throw new Error(result.message) + } + } + if (typeof result[0] === 'undefined' || typeof result[0].hash === 'undefined' || typeof result[0].ok === 'undefined' || !result[0].ok || !result[0].hash) { + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' BnbTransferProcessor.sendTx result', result) + MarketingEvent.logOnlyRealTime('v20_bnb_no_result ' + data.addressFrom + ' => ' + data.addressTo, { + result, + raw + }) + throw new Error('SERVER_RESPONSE_NO_RESPONSE') + } + + MarketingEvent.logOnlyRealTime('v20_bnb_success_result ' + data.addressFrom + ' => ' + data.addressTo + ' ' + result[0].hash, { + result + }) + return { transactionHash: result[0].hash } + } catch (e) { + if (e.message.indexOf('SERVER_RESPONSE_') === -1) { + throw new Error(e.message + ' in BNB sendRaw1 parse result') + } else { + throw e + } + } + } +} diff --git a/crypto/blockchains/bnb/basic/BnbNetworkPrices.js b/crypto/blockchains/bnb/basic/BnbNetworkPrices.js new file mode 100644 index 000000000..e6d4043a3 --- /dev/null +++ b/crypto/blockchains/bnb/basic/BnbNetworkPrices.js @@ -0,0 +1,64 @@ +/** + * @version 0.20 + * + * https://beptools.org/fees + * https://dex.binance.org/api/v1/fees + * https://docs.binance.org/api-reference/dex-api/paths.html#apiv1fees + */ +import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog' +import BlocksoftAxios from '../../../common/BlocksoftAxios' +import BlocksoftExternalSettings from '../../../common/BlocksoftExternalSettings' + +const CACHE_VALID_TIME = 60000 // 1 minute +let CACHE_FEES = { + send : { + fee: 37500 + } +} +let CACHE_FEES_TIME = 0 + +class BnbNetworkPrices { + + + async getFees() { + const now = new Date().getTime() + if (CACHE_FEES && now - CACHE_FEES_TIME < CACHE_VALID_TIME) { + return CACHE_FEES + } + + BlocksoftCryptoLog.log('BnbNetworkPricesProvider.getFees no cache load') + + const apiServer = await BlocksoftExternalSettings.getStatic('BNB_SERVER') + const link = `${apiServer}/api/v1/fees` + + try { + const tmp = await BlocksoftAxios.getWithoutBraking(link, 2) + if (tmp.data) { + // {"fee": 1000000, "fee_for": 1, "msg_type": "transferOwnership"}] + const result = {} + for (const row of tmp.data) { + if (typeof row.fixed_fee_params !== 'undefined') { + result[row.fixed_fee_params.msg_type] = { fee: row.fixed_fee_params.fee, for: row.fixed_fee_params.fee_for } + } else { + result[row.msg_type] = { fee: row.fee, for: row.fee_for } + } + } + if (typeof result['send'] !== 'undefined') { + CACHE_FEES = result + CACHE_FEES_TIME = now + } + } + } catch (e) { + BlocksoftCryptoLog.log('BnbNetworkPricesProvider.getOnlyFees loaded prev fee as error' + e.message) + // do nothing + } + + return CACHE_FEES + } + + +} + +const singleton = new BnbNetworkPrices() +export default singleton + diff --git a/crypto/blockchains/bnb/basic/BnbTxSendProvider.ts b/crypto/blockchains/bnb/basic/BnbTxSendProvider.ts new file mode 100644 index 000000000..c31a3182b --- /dev/null +++ b/crypto/blockchains/bnb/basic/BnbTxSendProvider.ts @@ -0,0 +1,272 @@ +import BlocksoftAxios from '../../../common/BlocksoftAxios' +import { BlocksoftBlockchainTypes } from '../../BlocksoftBlockchainTypes' +import config from '../../../../app/config/config' +import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog' +import BlocksoftUtils from '../../../common/BlocksoftUtils' +import BlocksoftExternalSettings from '../../../common/BlocksoftExternalSettings' + +const elliptic = require('elliptic') +const ec = new elliptic.ec('secp256k1') +const createHash = require('create-hash') +const bech = require('bech32') + + +const UVarInt = require('../utils/UVarInt').UVarInt +const Encode = require('../utils/Encode') +const _tinySecp256k = require('tiny-secp256k1') + +function decodeAddress(value: any) { + try { + const decodeAddress = bech.decode(value) + return Buffer.from(bech.fromWords(decodeAddress.words)) + } catch (e) { + throw new Error(e.message + ' while decodeAddress ' + value) + } +} + +function convertObjectToSignBytes(obj: any) { + try { + return Buffer.from(JSON.stringify(Encode.sortObject(obj))) + } catch (e) { + throw new Error(e.message + ' in convertObjectToSignBytes') + } +} + +function encodeBinaryByteArray(bytes: any) { + try { + const lenPrefix = bytes.length + return Buffer.concat([UVarInt.encode(lenPrefix), bytes]) + } catch (e) { + throw new Error(e.message + ' in encodeBinaryByteArray') + } +} + +function serializePubKey(unencodedPubKey: any) { + try { + let format = 0x2 + const y = unencodedPubKey.getY() + const x = unencodedPubKey.getX() + + if (y && y.isOdd()) { + format |= 0x1 + } + + let pubBz = Buffer.concat([UVarInt.encode(format), x.toArrayLike(Buffer, 'be', 32)]) // prefixed with length + + pubBz = encodeBinaryByteArray(pubBz) // add the amino prefix + + pubBz = Buffer.concat([Buffer.from('EB5AE987', 'hex'), pubBz]) + return pubBz + } catch (e) { + throw new Error(e.message + ' in serializePubKey') + } +} + +function marshalBinary(obj: any) { + try { + return Encode.encodeBinary(obj, -1, true).toString('hex') + } catch (e) { + throw new Error(e.message + ' in marshalBinary') + } +} + + +export class BnbTxSendProvider { + + async getPrepared(data: BlocksoftBlockchainTypes.TransferData, privateData: BlocksoftBlockchainTypes.TransferPrivateData, uiData: BlocksoftBlockchainTypes.TransferUiData, type = 'usual') { + const apiServer = await BlocksoftExternalSettings.getStatic('BNB_SERVER') + const res = await BlocksoftAxios.getWithoutBraking(apiServer + '/api/v1/account/' + data.addressFrom) + if (!res.data) { + throw new Error('no data') + } + const account = res.data + + const unified = BlocksoftUtils.fromUnified(data.amount, 8) * 1 + let msg + let txMsg + + let decodedFrom + let decodedTo + try { + decodedFrom = decodeAddress(data.addressFrom) + } catch (e) { + throw new Error(e.message + ' in BNB decodeAddress ' + JSON.stringify(data.addressFrom)) + } + try { + if (data.addressTo.indexOf('0x') === -1) { + decodedTo = decodeAddress(data.addressTo) + } else { + decodedTo = data.addressTo + } + } catch (e) { + throw new Error(e.message + ' in BNB decodeAddress ' + JSON.stringify(data.addressTo)) + } + + if (typeof data.blockchainData !== 'undefined' && typeof data.blockchainData.action !== 'undefined' && data.blockchainData.action === 'BnbToSmart') { + let addressTo = data.addressTo + if (addressTo.substr(0,2) === '0x') { + addressTo = addressTo.substr(2) + } + msg = { + from: data.addressFrom, + to: data.addressTo, + amount: { 'denom': 'BNB', 'amount': unified }, + expire_time: data.blockchainData.expire_time + } + txMsg = { + from: decodedFrom, + to: Buffer.from(addressTo, 'hex'), + amount: { 'denom': 'BNB', 'amount': unified }, + expire_time: data.blockchainData.expire_time, + aminoPrefix: '800819C0' + } + } else { + msg = { + 'inputs': [{ + 'address': data.addressFrom, 'coins': [{ + 'amount': unified, + 'denom': 'BNB' + }] + }], + 'outputs': [{ + 'address': data.addressTo, 'coins': [{ + 'amount': unified, + 'denom': 'BNB' + }] + }] + } + txMsg = { + 'inputs': [{ + 'address': decodedFrom, 'coins': [{ + 'denom': 'BNB', + 'amount': unified + }] + }], + 'outputs': [{ + 'address': decodedTo, 'coins': [{ + 'denom': 'BNB', + 'amount': unified + }] + }], + aminoPrefix: '2A2C87FA' + } + } + const memo = (typeof data.memo === 'undefined' || !data.memo) ? '' : data.memo + const signMsg = { + account_number: account.account_number + '', + chain_id: 'Binance-Chain-Tigris', + data: null, + memo, + msgs: [msg], + sequence: account.sequence + '', + source: '0' + } + let buff + try { + buff = convertObjectToSignBytes(signMsg) + } catch (e) { + throw new Error(e.message + ' in BNB convertObjectToSignBytes ' + JSON.stringify(signMsg)) + } + const signBytesHex = buff.toString('hex') + let msgHash + try { + msgHash = createHash('sha256').update(signBytesHex, 'hex').digest() + } catch (e) { + throw new Error(e.message + ' in BNB createHash') + } + let keypair + try { + keypair = ec.keyFromPrivate(privateData.privateKey, 'hex') + } catch (e) { + throw new Error(e.message + ' in BNB ec.keyFromPrivate') + } + let signature + try { + signature = _tinySecp256k.sign(msgHash, Buffer.from(privateData.privateKey, 'hex')) + } catch (e) { + throw new Error(e.message + ' in BNB _tinySecp256k.sign') + } + const signatureHex = signature.toString('hex') + let pubKey + try { + pubKey = keypair.getPublic() + } catch (e) { + throw new Error(e.message + ' in BNB keypair.getPublic()') + } + let pubSerialize + try { + pubSerialize = serializePubKey(pubKey) + } catch (e) { + throw new Error(e.message + ' in BNB serializePubKey') + } + + const signatures = [{ + pub_key: pubSerialize, + signature: Buffer.from(signatureHex, 'hex'), + account_number: account.account_number, + sequence: account.sequence + }] + const transaction = { + sequence: account.sequence + '', + accountNumber: account.account_number + '', + chainId: 'Binance-Chain-Tigris', + msg: txMsg, + baseMsg: undefined, + memo: memo, + source: 0, + signatures + } + return transaction + } + + serializeTx(tx: any) { + if (!tx.signatures) { + throw new Error('need signature') + } + + const msg = tx.msg || tx.baseMsg && tx.baseMsg.getMsg() + const stdTx = { + msg: [msg], + signatures: tx.signatures, + memo: tx.memo, + source: tx.source, + // sdk value is 0, web wallet value is 1 + data: '', + aminoPrefix: 'F0625DEE' + } + const bytes = marshalBinary(stdTx) + return bytes.toString('hex') + } + + async sendRaw(raw: string) { + let result = false + try { + // console.log(`curl -X POST -F "tx=${raw}" "https://dex.binance.org/api/v1/broadcast"`) + const apiServer = await BlocksoftExternalSettings.getStatic('BNB_SERVER') + const response = await fetch(apiServer + '/api/v1/broadcast?sync=true', { + method: 'POST', + credentials: 'same-origin', + mode: 'same-origin', + headers: { + 'Content-Type': 'text/plain' + }, + body: raw + }) + result = await response.json() + + if (typeof result.status !== 'undefined') { + if (result.status === 406 || result.status === 400 || result.status === 504) { + throw new Error(result.title) + } + } + } catch (e) { + if (config.debug.cryptoErrors) { + console.log('BnbTransferProcessor.sendTx error ', e) + } + await BlocksoftCryptoLog.log('BnbTransferProcessor.sendTx error ' + e.message) + throw e + } + await BlocksoftCryptoLog.log('BnbTransferProcessor.sendTx result ', result) + return result + } +} diff --git a/crypto/blockchains/bnb/utils/Encode.js b/crypto/blockchains/bnb/utils/Encode.js new file mode 100644 index 000000000..5b1346e3f --- /dev/null +++ b/crypto/blockchains/bnb/utils/Encode.js @@ -0,0 +1,262 @@ +const protocolBuffersEncodings = require("protocol-buffers-encodings") + +const IsJs = require('./IsJs.js') +const UVarInt = require('./UVarInt').UVarInt +const EncoderHelper = require('./EncoderHelper') + + +const sortObject = function sortObject(obj) { + if (obj === null) return null + if (Array.isArray(obj)) return obj.map(sortObject) + if (typeof (obj) !== 'object') return obj + + var sortedKeys = Object.keys(obj).sort() + var result = {} + sortedKeys.forEach(function(key) { + // @ts-ignore + result[key] = sortObject(obj[key]) + }) + return result +} +exports.sortObject = sortObject + +/** + * encode number + * @category amino + * @param num + */ + +var encodeNumber = function encodeNumber(num) { + return UVarInt.encode(num) +} +/** + * encode bool + * @category amino + * @param b + */ + +exports.encodeNumber = encodeNumber + +var encodeBool = function encodeBool(b) { + return b ? UVarInt.encode(1) : UVarInt.encode(0) +} +/** + * encode string + * @category amino + * @param str + */ + + +exports.encodeBool = encodeBool + +var encodeString = function encodeString(str) { + try { + var buf = Buffer.alloc(protocolBuffersEncodings.string.encodingLength(str)) + return protocolBuffersEncodings.string.encode(str, buf, 0) + } catch (e) { + throw new Error(e.message + ' in protocolBuffersEncodings.string.encode') + } +} +/** + * encode time + * @category amino + * @param value + */ + + +exports.encodeString = encodeString + +var encodeTime = function encodeTime(value) { + var millis = new Date(value).getTime() + var seconds = Math.floor(millis / 1000) + var nanos = Number(seconds.toString().padEnd(9, '0')) + var buffer = Buffer.alloc(14) // buffer[0] = (1 << 3) | 1 // field 1, typ3 1 + + buffer.writeInt32LE(1 << 3 | 1, 0) + buffer.writeUInt32LE(seconds, 1) // buffer[9] = (2 << 3) | 5 // field 2, typ3 5 + + buffer.writeInt32LE(2 << 3 | 5, 9) + buffer.writeUInt32LE(nanos, 10) + return buffer +} +/** + * @category amino + * @param obj -- {object} + * @return bytes {Buffer} + */ + + +exports.encodeTime = encodeTime + +var convertObjectToSignBytes = function convertObjectToSignBytes(obj) { + return Buffer.from(JSON.stringify(sortObject(obj))) +} +/** + * js amino MarshalBinary + * @category amino + * @param {Object} obj + * */ + + +exports.convertObjectToSignBytes = convertObjectToSignBytes + +var marshalBinary = function marshalBinary(obj) { + if (!IsJs.object(obj)) throw new TypeError('data must be an object') + return encodeBinary(obj, -1, true).toString('hex') +} +/** + * js amino MarshalBinaryBare + * @category amino + * @param {Object} obj + * */ + + +exports.marshalBinary = marshalBinary + +var marshalBinaryBare = function marshalBinaryBare(obj) { + if (!IsJs.object(obj)) throw new TypeError('data must be an object') + return encodeBinary(obj).toString('hex') +} +/** + * This is the main entrypoint for encoding all types in binary form. + * @category amino + * @param {*} js data type (not null, not undefined) + * @param {Number} field index of object + * @param {Boolean} isByteLenPrefix + * @return {Buffer} binary of object. + */ + + +exports.marshalBinaryBare = marshalBinaryBare + +var encodeBinary = function encodeBinary(val, fieldNum, isByteLenPrefix) { + if (val === null || val === undefined) throw new TypeError('unsupported type') + + if (Buffer.isBuffer(val)) { + if (isByteLenPrefix) { + return Buffer.concat([UVarInt.encode(val.length), val]) + } + + return val + } + + if (IsJs.array(val)) { + return encodeArrayBinary(fieldNum, val, isByteLenPrefix) + } + + if (IsJs.number(val)) { + return encodeNumber(val) + } + + if (IsJs['boolean'](val)) { + return encodeBool(val) + } + + if (IsJs.string(val)) { + return encodeString(val) + } + + if (IsJs.object(val)) { + return encodeObjectBinary(val, isByteLenPrefix) + } + + return +} +/** + * prefixed with bytes length + * @category amino + * @param {Buffer} bytes + * @return {Buffer} with bytes length prefixed + */ + + +exports.encodeBinary = encodeBinary + +var encodeBinaryByteArray = function encodeBinaryByteArray(bytes) { + var lenPrefix = bytes.length + return Buffer.concat([UVarInt.encode(lenPrefix), bytes]) +} +/** + * @category amino + * @param {Object} obj + * @return {Buffer} with bytes length prefixed + */ + + +exports.encodeBinaryByteArray = encodeBinaryByteArray + +var encodeObjectBinary = function encodeObjectBinary(obj, isByteLenPrefix) { + var bufferArr = [] + Object.keys(obj).forEach(function(key, index) { + if (key === 'aminoPrefix' || key === 'version') return + if (isDefaultValue(obj[key])) return + + if (IsJs.array(obj[key]) && obj[key].length > 0) { + bufferArr.push(encodeArrayBinary(index, obj[key])) + } else { + bufferArr.push(encodeTypeAndField(index, obj[key])) + bufferArr.push(encodeBinary(obj[key], index, true)) + } + }) + var bytes = Buffer.concat(bufferArr) // add prefix + + if (obj.aminoPrefix) { + var prefix = Buffer.from(obj.aminoPrefix, 'hex') + bytes = Buffer.concat([prefix, bytes]) + } // Write byte-length prefixed. + + + if (isByteLenPrefix) { + var lenBytes = UVarInt.encode(bytes.length) + + bytes = Buffer.concat([lenBytes, bytes]) + } + return bytes +} +/** + * @category amino + * @param {Number} fieldNum object field index + * @param {Array} arr + * @param {Boolean} isByteLenPrefix + * @return {Buffer} bytes of array + */ + + +exports.encodeObjectBinary = encodeObjectBinary + +var encodeArrayBinary = function encodeArrayBinary(fieldNum, arr, isByteLenPrefix) { + var result = [] + arr.forEach(function(item) { + result.push(encodeTypeAndField(fieldNum, item)) + + if (isDefaultValue(item)) { + result.push(Buffer.from('00', 'hex')) + return + } + + result.push(encodeBinary(item, fieldNum, true)) + }) //encode length + + if (isByteLenPrefix) { + var length = result.reduce(function(prev, item) { + return prev + item.length + }, 0) + result.unshift(UVarInt.encode(length)) + } + + return Buffer.concat(result) +} // Write field key. + + +exports.encodeArrayBinary = encodeArrayBinary + +var encodeTypeAndField = function encodeTypeAndField(index, field) { + index = Number(index) + var value = index + 1 << 3 | EncoderHelper._default(field) + return UVarInt.encode(value) +} + +var isDefaultValue = function isDefaultValue(obj) { + if (obj === null) return false; + return IsJs.number(obj) && obj === 0 || IsJs.string(obj) && obj === "" || IsJs.array(obj) && obj.length === 0 || IsJs["boolean"](obj) && !obj; +}; diff --git a/crypto/blockchains/bnb/utils/EncoderHelper.js b/crypto/blockchains/bnb/utils/EncoderHelper.js new file mode 100644 index 000000000..7cbf25884 --- /dev/null +++ b/crypto/blockchains/bnb/utils/EncoderHelper.js @@ -0,0 +1,44 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.size = exports["default"] = void 0; + +const IsJs = require('./IsJs.js') + +// typeToTyp3 +//amino type convert +var _default = function _default(type) { + if (IsJs["boolean"](type)) { + return 0; + } + + if (IsJs.number(type)) { + if (IsJs.integer(type)) { + return 0; + } else { + return 1; + } + } + + if (IsJs.string(type) || IsJs.array(type) || IsJs.object(type)) { + return 2; + } + + throw new Error("Invalid type \"".concat(type, "\"")); // Is this what's expected? +}; + +exports._default = _default + +var size = function size(items, iter, acc) { + if (acc === undefined) acc = 0; + + for (var i = 0; i < items.length; ++i) { + acc += iter(items[i], i, acc); + } + + return acc; +}; + +exports.size = size; diff --git a/crypto/blockchains/bnb/utils/IsJs.js b/crypto/blockchains/bnb/utils/IsJs.js new file mode 100644 index 000000000..bf131de84 --- /dev/null +++ b/crypto/blockchains/bnb/utils/IsJs.js @@ -0,0 +1,906 @@ +/*! + * is.js 0.8.0 + * Author: Aras Atasaygin + */ + +// AMD with global, Node, or global +;(function(root, factory) { // eslint-disable-line no-extra-semi + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define(function() { + // Also create a global in case some scripts + // that are loaded still are looking for + // a global even when an AMD loader is in use. + return (root.is = factory()); + }); + } else if (typeof exports === 'object') { + // Node. Does not work with strict CommonJS, but + // only CommonJS-like enviroments that support module.exports, + // like Node. + module.exports = factory(); + } else { + // Browser globals (root is self) + root.is = factory(); + } +}(this, function() { + + // Baseline + /* -------------------------------------------------------------------------- */ + + // define 'is' object and current version + var is = {}; + is.VERSION = '0.8.0'; + + // define interfaces + is.not = {}; + is.all = {}; + is.any = {}; + + // cache some methods to call later on + var toString = Object.prototype.toString; + var slice = Array.prototype.slice; + var hasOwnProperty = Object.prototype.hasOwnProperty; + + // helper function which reverses the sense of predicate result + function not(func) { + return function() { + return !func.apply(null, slice.call(arguments)); + }; + } + + // helper function which call predicate function per parameter and return true if all pass + function all(func) { + return function() { + var params = getParams(arguments); + var length = params.length; + for (var i = 0; i < length; i++) { + if (!func.call(null, params[i])) { + return false; + } + } + return true; + }; + } + + // helper function which call predicate function per parameter and return true if any pass + function any(func) { + return function() { + var params = getParams(arguments); + var length = params.length; + for (var i = 0; i < length; i++) { + if (func.call(null, params[i])) { + return true; + } + } + return false; + }; + } + + // build a 'comparator' object for various comparison checks + var comparator = { + '<': function(a, b) { return a < b; }, + '<=': function(a, b) { return a <= b; }, + '>': function(a, b) { return a > b; }, + '>=': function(a, b) { return a >= b; } + } + + // helper function which compares a version to a range + function compareVersion(version, range) { + var string = (range + ''); + var n = +(string.match(/\d+/) || NaN); + var op = string.match(/^[<>]=?|/)[0]; + return comparator[op] ? comparator[op](version, n) : (version == n || n !== n); + } + + // helper function which extracts params from arguments + function getParams(args) { + var params = slice.call(args); + var length = params.length; + if (length === 1 && is.array(params[0])) { // support array + params = params[0]; + } + return params; + } + + // Type checks + /* -------------------------------------------------------------------------- */ + + // is a given value Arguments? + is.arguments = function(value) { // fallback check is for IE + return toString.call(value) === '[object Arguments]' || + (value != null && typeof value === 'object' && 'callee' in value); + }; + + // is a given value Array? + is.array = Array.isArray || function(value) { // check native isArray first + return toString.call(value) === '[object Array]'; + }; + + // is a given value Boolean? + is.boolean = function(value) { + return value === true || value === false || toString.call(value) === '[object Boolean]'; + }; + + // is a given value Char? + is.char = function(value) { + return is.string(value) && value.length === 1; + }; + + // is a given value Date Object? + is.date = function(value) { + return toString.call(value) === '[object Date]'; + }; + + // is a given object a DOM node? + is.domNode = function(object) { + return is.object(object) && object.nodeType > 0; + }; + + // is a given value Error object? + is.error = function(value) { + return toString.call(value) === '[object Error]'; + }; + + // is a given value function? + is['function'] = function(value) { // fallback check is for IE + return toString.call(value) === '[object Function]' || typeof value === 'function'; + }; + + // is given value a pure JSON object? + is.json = function(value) { + return toString.call(value) === '[object Object]'; + }; + + // is a given value NaN? + is.nan = function(value) { // NaN is number :) Also it is the only value which does not equal itself + return value !== value; + }; + + // is a given value null? + is['null'] = function(value) { + return value === null; + }; + + // is a given value number? + is.number = function(value) { + return is.not.nan(value) && toString.call(value) === '[object Number]'; + }; + + // is a given value object? + is.object = function(value) { + return Object(value) === value; + }; + + // is a given value RegExp? + is.regexp = function(value) { + return toString.call(value) === '[object RegExp]'; + }; + + // are given values same type? + // prevent NaN, Number same type check + is.sameType = function(value, other) { + var tag = toString.call(value); + if (tag !== toString.call(other)) { + return false; + } + if (tag === '[object Number]') { + return !is.any.nan(value, other) || is.all.nan(value, other); + } + return true; + }; + // sameType method does not support 'all' and 'any' interfaces + is.sameType.api = ['not']; + + // is a given value String? + is.string = function(value) { + return toString.call(value) === '[object String]'; + }; + + // is a given value undefined? + is.undefined = function(value) { + return value === void 0; + }; + + // is a given value window? + // setInterval method is only available for window object + is.windowObject = function(value) { + return value != null && typeof value === 'object' && 'setInterval' in value; + }; + + // Presence checks + /* -------------------------------------------------------------------------- */ + + //is a given value empty? Objects, arrays, strings + is.empty = function(value) { + if (is.object(value)) { + var length = Object.getOwnPropertyNames(value).length; + if (length === 0 || (length === 1 && is.array(value)) || + (length === 2 && is.arguments(value))) { + return true; + } + return false; + } + return value === ''; + }; + + // is a given value existy? + is.existy = function(value) { + return value != null; + }; + + // is a given value falsy? + is.falsy = function(value) { + return !value; + }; + + // is a given value truthy? + is.truthy = not(is.falsy); + + // Arithmetic checks + /* -------------------------------------------------------------------------- */ + + // is a given number above minimum parameter? + is.above = function(n, min) { + return is.all.number(n, min) && n > min; + }; + // above method does not support 'all' and 'any' interfaces + is.above.api = ['not']; + + // is a given number decimal? + is.decimal = function(n) { + return is.number(n) && n % 1 !== 0; + }; + + // are given values equal? supports numbers, strings, regexes, booleans + // TODO: Add object and array support + is.equal = function(value, other) { + // check 0 and -0 equity with Infinity and -Infinity + if (is.all.number(value, other)) { + return value === other && 1 / value === 1 / other; + } + // check regexes as strings too + if (is.all.string(value, other) || is.all.regexp(value, other)) { + return '' + value === '' + other; + } + if (is.all.boolean(value, other)) { + return value === other; + } + return false; + }; + // equal method does not support 'all' and 'any' interfaces + is.equal.api = ['not']; + + // is a given number even? + is.even = function(n) { + return is.number(n) && n % 2 === 0; + }; + + // is a given number finite? + is.finite = isFinite || function(n) { + return is.not.infinite(n) && is.not.nan(n); + }; + + // is a given number infinite? + is.infinite = function(n) { + return n === Infinity || n === -Infinity; + }; + + // is a given number integer? + is.integer = function(n) { + return is.number(n) && n % 1 === 0; + }; + + // is a given number negative? + is.negative = function(n) { + return is.number(n) && n < 0; + }; + + // is a given number odd? + is.odd = function(n) { + return is.number(n) && n % 2 === 1; + }; + + // is a given number positive? + is.positive = function(n) { + return is.number(n) && n > 0; + }; + + // is a given number above maximum parameter? + is.under = function(n, max) { + return is.all.number(n, max) && n < max; + }; + // least method does not support 'all' and 'any' interfaces + is.under.api = ['not']; + + // is a given number within minimum and maximum parameters? + is.within = function(n, min, max) { + return is.all.number(n, min, max) && n > min && n < max; + }; + // within method does not support 'all' and 'any' interfaces + is.within.api = ['not']; + + // Regexp checks + /* -------------------------------------------------------------------------- */ + // Steven Levithan, Jan Goyvaerts: Regular Expressions Cookbook + // Scott Gonzalez: Email address validation + + // dateString match m/d/yy and mm/dd/yyyy, allowing any combination of one or two digits for the day and month, and two or four digits for the year + // eppPhone match extensible provisioning protocol format + // nanpPhone match north american number plan format + // time match hours, minutes, and seconds, 24-hour clock + var regexes = { + affirmative: /^(?:1|t(?:rue)?|y(?:es)?|ok(?:ay)?)$/, + alphaNumeric: /^[A-Za-z0-9]+$/, + caPostalCode: /^(?!.*[DFIOQU])[A-VXY][0-9][A-Z]\s?[0-9][A-Z][0-9]$/, + creditCard: /^(?:(4[0-9]{12}(?:[0-9]{3})?)|(5[1-5][0-9]{14})|(6(?:011|5[0-9]{2})[0-9]{12})|(3[47][0-9]{13})|(3(?:0[0-5]|[68][0-9])[0-9]{11})|((?:2131|1800|35[0-9]{3})[0-9]{11}))$/, + dateString: /^(1[0-2]|0?[1-9])([\/-])(3[01]|[12][0-9]|0?[1-9])(?:\2)(?:[0-9]{2})?[0-9]{2}$/, + email: /^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$/i, // eslint-disable-line no-control-regex + eppPhone: /^\+[0-9]{1,3}\.[0-9]{4,14}(?:x.+)?$/, + hexadecimal: /^(?:0x)?[0-9a-fA-F]+$/, + hexColor: /^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/, + ipv4: /^(?:(?:\d|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5])\.){3}(?:\d|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5])$/, + ipv6: /^((?=.*::)(?!.*::.+::)(::)?([\dA-F]{1,4}:(:|\b)|){5}|([\dA-F]{1,4}:){6})((([\dA-F]{1,4}((?!\3)::|:\b|$))|(?!\2\3)){2}|(((2[0-4]|1\d|[1-9])?\d|25[0-5])\.?\b){4})$/i, + nanpPhone: /^\(?([0-9]{3})\)?[-. ]?([0-9]{3})[-. ]?([0-9]{4})$/, + socialSecurityNumber: /^(?!000|666)[0-8][0-9]{2}-?(?!00)[0-9]{2}-?(?!0000)[0-9]{4}$/, + timeString: /^(2[0-3]|[01]?[0-9]):([0-5]?[0-9]):([0-5]?[0-9])$/, + ukPostCode: /^[A-Z]{1,2}[0-9RCHNQ][0-9A-Z]?\s?[0-9][ABD-HJLNP-UW-Z]{2}$|^[A-Z]{2}-?[0-9]{4}$/, + url: /^(?:(?:https?|ftp):\/\/)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})))(?::\d{2,5})?(?:\/\S*)?$/i, + usZipCode: /^[0-9]{5}(?:-[0-9]{4})?$/ + }; + + function regexpCheck(regexp, regexes) { + is[regexp] = function(value) { + return regexes[regexp].test(value); + }; + } + + // create regexp checks methods from 'regexes' object + for (var regexp in regexes) { + if (regexes.hasOwnProperty(regexp)) { + regexpCheck(regexp, regexes); + } + } + + // simplify IP checks by calling the regex helpers for IPv4 and IPv6 + is.ip = function(value) { + return is.ipv4(value) || is.ipv6(value); + }; + + // String checks + /* -------------------------------------------------------------------------- */ + + // is a given string or sentence capitalized? + is.capitalized = function(string) { + if (is.not.string(string)) { + return false; + } + var words = string.split(' '); + for (var i = 0; i < words.length; i++) { + var word = words[i]; + if (word.length) { + var chr = word.charAt(0); + if (chr !== chr.toUpperCase()) { + return false; + } + } + } + return true; + }; + + // is string end with a given target parameter? + is.endWith = function(string, target) { + if (is.not.string(string)) { + return false; + } + target += ''; + var position = string.length - target.length; + return position >= 0 && string.indexOf(target, position) === position; + }; + // endWith method does not support 'all' and 'any' interfaces + is.endWith.api = ['not']; + + // is a given string include parameter target? + is.include = function(string, target) { + return string.indexOf(target) > -1; + }; + // include method does not support 'all' and 'any' interfaces + is.include.api = ['not']; + + // is a given string all lowercase? + is.lowerCase = function(string) { + return is.string(string) && string === string.toLowerCase(); + }; + + // is a given string palindrome? + is.palindrome = function(string) { + if (is.not.string(string)) { + return false; + } + string = string.replace(/[^a-zA-Z0-9]+/g, '').toLowerCase(); + var length = string.length - 1; + for (var i = 0, half = Math.floor(length / 2); i <= half; i++) { + if (string.charAt(i) !== string.charAt(length - i)) { + return false; + } + } + return true; + }; + + // is a given value space? + // horizantal tab: 9, line feed: 10, vertical tab: 11, form feed: 12, carriage return: 13, space: 32 + is.space = function(value) { + if (is.not.char(value)) { + return false; + } + var charCode = value.charCodeAt(0); + return (charCode > 8 && charCode < 14) || charCode === 32; + }; + + // is string start with a given target parameter? + is.startWith = function(string, target) { + return is.string(string) && string.indexOf(target) === 0; + }; + // startWith method does not support 'all' and 'any' interfaces + is.startWith.api = ['not']; + + // is a given string all uppercase? + is.upperCase = function(string) { + return is.string(string) && string === string.toUpperCase(); + }; + + // Time checks + /* -------------------------------------------------------------------------- */ + + var days = ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday']; + var months = ['january', 'february', 'march', 'april', 'may', 'june', 'july', 'august', 'september', 'october', 'november', 'december']; + + // is a given dates day equal given day parameter? + is.day = function(date, day) { + return is.date(date) && day.toLowerCase() === days[date.getDay()]; + }; + // day method does not support 'all' and 'any' interfaces + is.day.api = ['not']; + + // is a given date in daylight saving time? + is.dayLightSavingTime = function(date) { + var january = new Date(date.getFullYear(), 0, 1); + var july = new Date(date.getFullYear(), 6, 1); + var stdTimezoneOffset = Math.max(january.getTimezoneOffset(), july.getTimezoneOffset()); + return date.getTimezoneOffset() < stdTimezoneOffset; + }; + + // is a given date future? + is.future = function(date) { + var now = new Date(); + return is.date(date) && date.getTime() > now.getTime(); + }; + + // is date within given range? + is.inDateRange = function(date, start, end) { + if (is.not.date(date) || is.not.date(start) || is.not.date(end)) { + return false; + } + var stamp = date.getTime(); + return stamp > start.getTime() && stamp < end.getTime(); + }; + // inDateRange method does not support 'all' and 'any' interfaces + is.inDateRange.api = ['not']; + + // is a given date in last month range? + is.inLastMonth = function(date) { + return is.inDateRange(date, new Date(new Date().setMonth(new Date().getMonth() - 1)), new Date()); + }; + + // is a given date in last week range? + is.inLastWeek = function(date) { + return is.inDateRange(date, new Date(new Date().setDate(new Date().getDate() - 7)), new Date()); + }; + + // is a given date in last year range? + is.inLastYear = function(date) { + return is.inDateRange(date, new Date(new Date().setFullYear(new Date().getFullYear() - 1)), new Date()); + }; + + // is a given date in next month range? + is.inNextMonth = function(date) { + return is.inDateRange(date, new Date(), new Date(new Date().setMonth(new Date().getMonth() + 1))); + }; + + // is a given date in next week range? + is.inNextWeek = function(date) { + return is.inDateRange(date, new Date(), new Date(new Date().setDate(new Date().getDate() + 7))); + }; + + // is a given date in next year range? + is.inNextYear = function(date) { + return is.inDateRange(date, new Date(), new Date(new Date().setFullYear(new Date().getFullYear() + 1))); + }; + + // is the given year a leap year? + is.leapYear = function(year) { + return is.number(year) && ((year % 4 === 0 && year % 100 !== 0) || year % 400 === 0); + }; + + // is a given dates month equal given month parameter? + is.month = function(date, month) { + return is.date(date) && month.toLowerCase() === months[date.getMonth()]; + }; + // month method does not support 'all' and 'any' interfaces + is.month.api = ['not']; + + // is a given date past? + is.past = function(date) { + var now = new Date(); + return is.date(date) && date.getTime() < now.getTime(); + }; + + // is a given date in the parameter quarter? + is.quarterOfYear = function(date, quarter) { + return is.date(date) && is.number(quarter) && quarter === Math.floor((date.getMonth() + 3) / 3); + }; + // quarterOfYear method does not support 'all' and 'any' interfaces + is.quarterOfYear.api = ['not']; + + // is a given date indicate today? + is.today = function(date) { + var now = new Date(); + var todayString = now.toDateString(); + return is.date(date) && date.toDateString() === todayString; + }; + + // is a given date indicate tomorrow? + is.tomorrow = function(date) { + var now = new Date(); + var tomorrowString = new Date(now.setDate(now.getDate() + 1)).toDateString(); + return is.date(date) && date.toDateString() === tomorrowString; + }; + + // is a given date weekend? + // 6: Saturday, 0: Sunday + is.weekend = function(date) { + return is.date(date) && (date.getDay() === 6 || date.getDay() === 0); + }; + + // is a given date weekday? + is.weekday = not(is.weekend); + + // is a given dates year equal given year parameter? + is.year = function(date, year) { + return is.date(date) && is.number(year) && year === date.getFullYear(); + }; + // year method does not support 'all' and 'any' interfaces + is.year.api = ['not']; + + // is a given date indicate yesterday? + is.yesterday = function(date) { + var now = new Date(); + var yesterdayString = new Date(now.setDate(now.getDate() - 1)).toDateString(); + return is.date(date) && date.toDateString() === yesterdayString; + }; + + // Environment checks + /* -------------------------------------------------------------------------- */ + + var freeGlobal = is.windowObject(typeof global == 'object' && global) && global; + var freeSelf = is.windowObject(typeof self == 'object' && self) && self; + var thisGlobal = is.windowObject(typeof this == 'object' && this) && this; + var root = freeGlobal || freeSelf || thisGlobal || Function('return this')(); + + var document = freeSelf && freeSelf.document; + var previousIs = root.is; + + // store navigator properties to use later + var navigator = freeSelf && freeSelf.navigator; + var appVersion = (navigator && navigator.appVersion || '').toLowerCase(); + var userAgent = (navigator && navigator.userAgent || '').toLowerCase(); + var vendor = (navigator && navigator.vendor || '').toLowerCase(); + + // is current device android? + is.android = function() { + return /android/.test(userAgent); + }; + // android method does not support 'all' and 'any' interfaces + is.android.api = ['not']; + + // is current device android phone? + is.androidPhone = function() { + return /android/.test(userAgent) && /mobile/.test(userAgent); + }; + // androidPhone method does not support 'all' and 'any' interfaces + is.androidPhone.api = ['not']; + + // is current device android tablet? + is.androidTablet = function() { + return /android/.test(userAgent) && !/mobile/.test(userAgent); + }; + // androidTablet method does not support 'all' and 'any' interfaces + is.androidTablet.api = ['not']; + + // is current device blackberry? + is.blackberry = function() { + return /blackberry/.test(userAgent) || /bb10/.test(userAgent); + }; + // blackberry method does not support 'all' and 'any' interfaces + is.blackberry.api = ['not']; + + // is current browser chrome? + // parameter is optional + is.chrome = function(range) { + var match = /google inc/.test(vendor) ? userAgent.match(/(?:chrome|crios)\/(\d+)/) : null; + return match !== null && compareVersion(match[1], range); + }; + // chrome method does not support 'all' and 'any' interfaces + is.chrome.api = ['not']; + + // is current device desktop? + is.desktop = function() { + return is.not.mobile() && is.not.tablet(); + }; + // desktop method does not support 'all' and 'any' interfaces + is.desktop.api = ['not']; + + // is current browser edge? + // parameter is optional + is.edge = function(range) { + var match = userAgent.match(/edge\/(\d+)/); + return match !== null && compareVersion(match[1], range); + }; + // edge method does not support 'all' and 'any' interfaces + is.edge.api = ['not']; + + // is current browser firefox? + // parameter is optional + is.firefox = function(range) { + var match = userAgent.match(/(?:firefox|fxios)\/(\d+)/); + return match !== null && compareVersion(match[1], range); + }; + // firefox method does not support 'all' and 'any' interfaces + is.firefox.api = ['not']; + + // is current browser internet explorer? + // parameter is optional + is.ie = function(range) { + var match = userAgent.match(/(?:msie |trident.+?; rv:)(\d+)/); + return match !== null && compareVersion(match[1], range); + }; + // ie method does not support 'all' and 'any' interfaces + is.ie.api = ['not']; + + // is current device ios? + is.ios = function() { + return is.iphone() || is.ipad() || is.ipod(); + }; + // ios method does not support 'all' and 'any' interfaces + is.ios.api = ['not']; + + // is current device ipad? + // parameter is optional + is.ipad = function(range) { + var match = userAgent.match(/ipad.+?os (\d+)/); + return match !== null && compareVersion(match[1], range); + }; + // ipad method does not support 'all' and 'any' interfaces + is.ipad.api = ['not']; + + // is current device iphone? + // parameter is optional + is.iphone = function(range) { + // original iPhone doesn't have the os portion of the UA + var match = userAgent.match(/iphone(?:.+?os (\d+))?/); + return match !== null && compareVersion(match[1] || 1, range); + }; + // iphone method does not support 'all' and 'any' interfaces + is.iphone.api = ['not']; + + // is current device ipod? + // parameter is optional + is.ipod = function(range) { + var match = userAgent.match(/ipod.+?os (\d+)/); + return match !== null && compareVersion(match[1], range); + }; + // ipod method does not support 'all' and 'any' interfaces + is.ipod.api = ['not']; + + // is current operating system linux? + is.linux = function() { + return /linux/.test(appVersion); + }; + // linux method does not support 'all' and 'any' interfaces + is.linux.api = ['not']; + + // is current operating system mac? + is.mac = function() { + return /mac/.test(appVersion); + }; + // mac method does not support 'all' and 'any' interfaces + is.mac.api = ['not']; + + // is current device mobile? + is.mobile = function() { + return is.iphone() || is.ipod() || is.androidPhone() || is.blackberry() || is.windowsPhone(); + }; + // mobile method does not support 'all' and 'any' interfaces + is.mobile.api = ['not']; + + // is current state offline? + is.offline = not(is.online); + // offline method does not support 'all' and 'any' interfaces + is.offline.api = ['not']; + + // is current state online? + is.online = function() { + return !navigator || navigator.onLine === true; + }; + // online method does not support 'all' and 'any' interfaces + is.online.api = ['not']; + + // is current browser opera? + // parameter is optional + is.opera = function(range) { + var match = userAgent.match(/(?:^opera.+?version|opr)\/(\d+)/); + return match !== null && compareVersion(match[1], range); + }; + // opera method does not support 'all' and 'any' interfaces + is.opera.api = ['not']; + + // is current browser phantomjs? + // parameter is optional + is.phantom = function(range) { + var match = userAgent.match(/phantomjs\/(\d+)/); + return match !== null && compareVersion(match[1], range); + }; + // phantom method does not support 'all' and 'any' interfaces + is.phantom.api = ['not']; + + // is current browser safari? + // parameter is optional + is.safari = function(range) { + var match = userAgent.match(/version\/(\d+).+?safari/); + return match !== null && compareVersion(match[1], range); + }; + // safari method does not support 'all' and 'any' interfaces + is.safari.api = ['not']; + + // is current device tablet? + is.tablet = function() { + return is.ipad() || is.androidTablet() || is.windowsTablet(); + }; + // tablet method does not support 'all' and 'any' interfaces + is.tablet.api = ['not']; + + // is current device supports touch? + is.touchDevice = function() { + return !!document && ('ontouchstart' in freeSelf || + ('DocumentTouch' in freeSelf && document instanceof DocumentTouch)); + }; + // touchDevice method does not support 'all' and 'any' interfaces + is.touchDevice.api = ['not']; + + // is current operating system windows? + is.windows = function() { + return /win/.test(appVersion); + }; + // windows method does not support 'all' and 'any' interfaces + is.windows.api = ['not']; + + // is current device windows phone? + is.windowsPhone = function() { + return is.windows() && /phone/.test(userAgent); + }; + // windowsPhone method does not support 'all' and 'any' interfaces + is.windowsPhone.api = ['not']; + + // is current device windows tablet? + is.windowsTablet = function() { + return is.windows() && is.not.windowsPhone() && /touch/.test(userAgent); + }; + // windowsTablet method does not support 'all' and 'any' interfaces + is.windowsTablet.api = ['not']; + + // Object checks + /* -------------------------------------------------------------------------- */ + + // has a given object got parameterized count property? + is.propertyCount = function(object, count) { + if (is.not.object(object) || is.not.number(count)) { + return false; + } + var n = 0; + for (var property in object) { + if (hasOwnProperty.call(object, property) && ++n > count) { + return false; + } + } + return n === count; + }; + // propertyCount method does not support 'all' and 'any' interfaces + is.propertyCount.api = ['not']; + + // is given object has parameterized property? + is.propertyDefined = function(object, property) { + return is.object(object) && is.string(property) && property in object; + }; + // propertyDefined method does not support 'all' and 'any' interfaces + is.propertyDefined.api = ['not']; + + // Array checks + /* -------------------------------------------------------------------------- */ + + // is a given item in an array? + is.inArray = function(value, array) { + if (is.not.array(array)) { + return false; + } + for (var i = 0; i < array.length; i++) { + if (array[i] === value) { + return true; + } + } + return false; + }; + // inArray method does not support 'all' and 'any' interfaces + is.inArray.api = ['not']; + + // is a given array sorted? + is.sorted = function(array, sign) { + if (is.not.array(array)) { + return false; + } + var predicate = comparator[sign] || comparator['>=']; + for (var i = 1; i < array.length; i++) { + if (!predicate(array[i], array[i - 1])) { + return false; + } + } + return true; + }; + + // API + // Set 'not', 'all' and 'any' interfaces to methods based on their api property + /* -------------------------------------------------------------------------- */ + + function setInterfaces() { + var options = is; + for (var option in options) { + if (hasOwnProperty.call(options, option) && is['function'](options[option])) { + var interfaces = options[option].api || ['not', 'all', 'any']; + for (var i = 0; i < interfaces.length; i++) { + if (interfaces[i] === 'not') { + is.not[option] = not(is[option]); + } + if (interfaces[i] === 'all') { + is.all[option] = all(is[option]); + } + if (interfaces[i] === 'any') { + is.any[option] = any(is[option]); + } + } + } + } + } + setInterfaces(); + + // Configuration methods + // Intentionally added after setInterfaces function + /* -------------------------------------------------------------------------- */ + + // change namespace of library to prevent name collisions + // var preferredName = is.setNamespace(); + // preferredName.odd(3); + // => true + is.setNamespace = function() { + root.is = previousIs; + return this; + }; + + // set optional regexes to methods + is.setRegexp = function(regexp, name) { + for (var r in regexes) { + if (hasOwnProperty.call(regexes, r) && (name === r)) { + regexes[r] = regexp; + } + } + }; + + return is; +})); diff --git a/crypto/blockchains/bnb/utils/UVarInt.js b/crypto/blockchains/bnb/utils/UVarInt.js new file mode 100644 index 000000000..715f2821d --- /dev/null +++ b/crypto/blockchains/bnb/utils/UVarInt.js @@ -0,0 +1,81 @@ +const BN = require('bn.js') + +function VarIntFunc(signed) { + var encodingLength = function encodingLength(n) { + if (signed) n *= 2 + + if (n < 0) { + throw Error('varint value is out of bounds') + } + + var bits = Math.log2(n + 1) + return Math.ceil(bits / 7) || 1 + } + + var encode = function encode(n, buffer, offset) { + if (n < 0) { + throw Error('varint value is out of bounds') + } + + buffer = buffer || Buffer.alloc(encodingLength(n)) + offset = offset || 0 + var nStr = n.toString() + var bn = new BN(nStr, 10) + var num255 = new BN(0xff) + var num128 = new BN(0x80) // amino signed varint is multiplied by 2 + + if (signed) { + bn = bn.muln(2) + } + + var i = 0 + + while (bn.gten(0x80)) { + buffer[offset + i] = bn.and(num255).or(num128).toNumber() + bn = bn.shrn(7) + i++ + } + + buffer[offset + i] = bn.andln(0xff) // TODO + // encode.bytes = i + 1 + + return buffer + } + /** + * https://github.com/golang/go/blob/master/src/encoding/binary/varint.go#L60 + */ + + + var decode = function decode(bytes) { + var x = 0 + var s = 0 + + for (var i = 0, len = bytes.length; i < len; i++) { + var b = bytes[i] + + if (b < 0x80) { + if (i > 9 || i === 9 && b > 1) { + return 0 + } + + return x | b << s + } + + x |= (b & 0x7f) << s + s += 7 + } + + return 0 + } + + return { + encode: encode, + decode: decode, + encodingLength: encodingLength + } +} + +var UVarInt = VarIntFunc(false) +exports.UVarInt = UVarInt +var VarInt = VarIntFunc(true) +exports.VarInt = VarInt diff --git a/crypto/blockchains/bnb_smart/BnbSmartTransferProcessor.ts b/crypto/blockchains/bnb_smart/BnbSmartTransferProcessor.ts new file mode 100644 index 000000000..95659fa5d --- /dev/null +++ b/crypto/blockchains/bnb_smart/BnbSmartTransferProcessor.ts @@ -0,0 +1,37 @@ +/** + * @author Ksu + * @version 0.20 + * https://api.etherscan.io/api?module=gastracker&action=gasoracle&apikey=YourApiKeyToken + */ +import { BlocksoftBlockchainTypes } from '../BlocksoftBlockchainTypes' +import EthTransferProcessor from '../eth/EthTransferProcessor' + +import BnbSmartNetworkPrices from './basic/BnbSmartNetworkPrices' +import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings' + +export default class BnbSmartTransferProcessor extends EthTransferProcessor implements BlocksoftBlockchainTypes.TransferProcessor { + + async getFeeRate(data: BlocksoftBlockchainTypes.TransferData, privateData: BlocksoftBlockchainTypes.TransferPrivateData, additionalData: {} = {}): Promise { + if (typeof additionalData.gasPrice === 'undefined' || !additionalData.gasPrice) { + let minFee = BlocksoftExternalSettings.getStatic(this._mainCurrencyCode + '_FORCE_PRICE') + if (typeof minFee !== 'undefined' && minFee > 1) { + additionalData.gasPrice = minFee + additionalData.gasPriceTitle = 'speed_blocks_2' + } else { + let defaultFee = BlocksoftExternalSettings.getStatic(this._mainCurrencyCode + '_PRICE') + if (typeof defaultFee === 'undefined' || !defaultFee) { + defaultFee = 5000000000 + } + if (this._etherscanApiPathForFee) { + const tmpPrice = await BnbSmartNetworkPrices.getFees(this._mainCurrencyCode, this._etherscanApiPathForFee, defaultFee, 'BnbSmartTransferProcessor.getFeeRate') + if (tmpPrice * 1 > defaultFee * 1) { + defaultFee = tmpPrice * 1 + } + } + additionalData.gasPrice = defaultFee + additionalData.gasPriceTitle = 'speed_blocks_2' + } + } + return super.getFeeRate(data, privateData, additionalData) + } +} diff --git a/crypto/blockchains/bnb_smart/BnbSmartTransferProcessorErc20.ts b/crypto/blockchains/bnb_smart/BnbSmartTransferProcessorErc20.ts new file mode 100644 index 000000000..07296929c --- /dev/null +++ b/crypto/blockchains/bnb_smart/BnbSmartTransferProcessorErc20.ts @@ -0,0 +1,48 @@ +/** + * @version 0.20 + */ +import { BlocksoftBlockchainTypes } from '../BlocksoftBlockchainTypes' +import EthTransferProcessorErc20 from '../eth/EthTransferProcessorErc20' +import BnbSmartNetworkPrices from './basic/BnbSmartNetworkPrices' +import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings' + +export default class BnbSmartTransferProcessorErc20 extends EthTransferProcessorErc20 implements BlocksoftBlockchainTypes.TransferProcessor { + + async getFeeRate(data: BlocksoftBlockchainTypes.TransferData, privateData: BlocksoftBlockchainTypes.TransferPrivateData, additionalData: {} = {}): Promise { + if (typeof additionalData.gasPrice === 'undefined' || !additionalData.gasPrice) { + let minFee = BlocksoftExternalSettings.getStatic(this._mainCurrencyCode + '_FORCE_PRICE_ERC20') + if (typeof minFee !== 'undefined' && minFee > 1) { + additionalData.gasPrice = minFee + additionalData.gasPriceTitle = 'speed_blocks_2' + } else { + let defaultFee = BlocksoftExternalSettings.getStatic(this._mainCurrencyCode + '_PRICE') + if (typeof defaultFee === 'undefined' || !defaultFee) { + defaultFee = 5000000000 + } + if (!this._etherscanApiPathForFee) { + additionalData.gasPrice = defaultFee + additionalData.gasPriceTitle = 'speed_blocks_2' + } else { + additionalData.gasPrice = await BnbSmartNetworkPrices.getFees(this._mainCurrencyCode, this._etherscanApiPathForFee, defaultFee, 'BnbSmartTransferProcessorErc20.getFeeRate') + additionalData.gasPriceTitle = 'speed_blocks_2' + } + } + } + const result = await super.getFeeRate(data, privateData, additionalData) + result.shouldShowFees = true + return result + } + + async checkTransferHasError(data: BlocksoftBlockchainTypes.CheckTransferHasErrorData): Promise { + // @ts-ignore + const balance = data.addressFrom && data.addressFrom !== '' ? await this._web3.eth.getBalance(data.addressFrom) : 0 + if (balance > 0) { + return { isOk: true } + } else { + const title = this._mainCurrencyCode === 'BNB' ? 'BNB Smart Chain' : this._mainCurrencyCode + // @ts-ignore + return { isOk: false, code: 'TOKEN', parentBlockchain: title, parentCurrency: title } + } + } + +} diff --git a/crypto/blockchains/bnb_smart/basic/BnbSmartNetworkPrices.js b/crypto/blockchains/bnb_smart/basic/BnbSmartNetworkPrices.js new file mode 100644 index 000000000..b307c99a8 --- /dev/null +++ b/crypto/blockchains/bnb_smart/basic/BnbSmartNetworkPrices.js @@ -0,0 +1,54 @@ +/** + * @version 0.20 + */ +import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog' +import BlocksoftAxios from '../../../common/BlocksoftAxios' +import BlocksoftUtils from '../../../common/BlocksoftUtils' +import BlocksoftExternalSettings from '../../../common/BlocksoftExternalSettings' +import config from '@app/config/config' + +const CACHE_VALID_TIME = 120000 // 2 minute +const CACHE_FEES = { + 'BNB' : { + fee : 5000000000, + ts : 0 + } +} + +class BnbSmartNetworkPrices { + async getFees(mainCurrencyCode, etherscanApiPath, defaultFee = 5000000000, source = '') { + const now = new Date().getTime() + if (typeof CACHE_FEES[mainCurrencyCode] !== 'undefined' && now - CACHE_FEES[mainCurrencyCode].ts < CACHE_VALID_TIME) { + return CACHE_FEES[mainCurrencyCode].fee + } + const tmp = etherscanApiPath.split('/') + const feesApiPath = `https://${tmp[2]}/api?module=proxy&action=eth_gasPrice&apikey=YourApiKeyToken` + BlocksoftCryptoLog.log(mainCurrencyCode + ' BnbSmartNetworkPricesProvider.getFees no cache load') + try { + const res = await BlocksoftAxios.getWithoutBraking(feesApiPath) + if (res && typeof res.data !== 'undefined' && typeof res.data.result !== 'undefined') { + const tmp = BlocksoftUtils.hexToDecimal(res.data.result) + if (tmp * 1 > 0) { + CACHE_FEES[mainCurrencyCode] = { + fee : (tmp * 1).toString().substr(0, 11), + time : now + } + } else if (typeof CACHE_FEES[mainCurrencyCode] === 'undefined' || !CACHE_FEES[mainCurrencyCode].fee) { + CACHE_FEES[mainCurrencyCode].fee = await BlocksoftExternalSettings.getStatic('BNB_SMART_PRICE') + } + } + } catch (e) { + if (config.debug.cryptoErrors) { + console.log(mainCurrencyCode + ' BnbSmartNetworkPricesProvider.getOnlyFees loaded prev fee as error' + e.message) + } + BlocksoftCryptoLog.log(mainCurrencyCode + ' BnbSmartNetworkPricesProvider.getOnlyFees loaded prev fee as error' + e.message) + // do nothing + } + + return typeof CACHE_FEES[mainCurrencyCode] !== 'undefined' ? CACHE_FEES[mainCurrencyCode].fee : defaultFee + } +} + + +const singleton = new BnbSmartNetworkPrices() +export default singleton diff --git a/crypto/blockchains/bsv/BsvScannerProcessor.js b/crypto/blockchains/bsv/BsvScannerProcessor.js new file mode 100644 index 000000000..2ad6d81a6 --- /dev/null +++ b/crypto/blockchains/bsv/BsvScannerProcessor.js @@ -0,0 +1,318 @@ +/** + * @version 0.5 + */ + +import config from "@app/config/config" +import BlocksoftAxios from "@crypto/common/BlocksoftAxios" +import BlocksoftCryptoLog from "@crypto/common/BlocksoftCryptoLog" +import BlocksoftUtils from "@crypto/common/BlocksoftUtils"; +import BlocksoftPrettyNumbers from "@crypto/common/BlocksoftPrettyNumbers"; +import BsvTmpDS from "@crypto/blockchains/bsv/stores/BsvTmpDS"; + +const API_PATH = 'https://api.whatsonchain.com/v1/bsv/main' +const CACHE_TXS = {} +const CACHE_ASKED = {} + +export default class BsvScannerProcessor { + + /** + * @type {number} + * @private + */ + _blocksToConfirm = 10 + + /** + * https://api.whatsonchain.com/v1/bsv/main/address/1BcHq66j64juMffHc4Sc5XQ59wWrcSygoZ/balance + * @param {string} address + * @return {Promise<{balance, unconfirmed, provider}>} + */ + async getBalanceBlockchain(address) { + const link = `${API_PATH}/address/${address}/balance` + let res = false + let balance = 0 + try { + res = await BlocksoftAxios.getWithoutBraking(link) + if (res && typeof res.data !== 'undefined' && res.data && typeof res.data.confirmed !== 'undefined') { + balance = res.data.confirmed + } else { + return false + } + } catch (e) { + // ? + throw e + } + return {balance: balance, unconfirmed: 0, provider: 'api.whatsonchain.com'} + } + + async _saveTxToCache(tx) { + if (typeof CACHE_TXS[tx.txid] === 'undefined' && tx.confirmations > 100) { + await BsvTmpDS.saveCache(tx.txid, tx) + } + + CACHE_TXS[tx.txid] = tx + } + + /** + * https://api.whatsonchain.com/v1/bsv/main/address/1BcHq66j64juMffHc4Sc5XQ59wWrcSygoZ/history + * @param scanData + * @param source + * @returns {Promise<*[]>} + */ + async getTransactionsBlockchain(scanData, source = '') { + const address = scanData.account.address.trim() + if (!CACHE_ASKED[address]) { + try { + const asked = await BsvTmpDS.getCache(address) + if (asked) { + for (let txid in asked) { + CACHE_TXS[txid] = asked[txid] + } + } + CACHE_ASKED[address] = true + } catch (e) { + throw new Error(e.message + ' in BsvTmpDS.getCache') + } + } + BlocksoftCryptoLog.log('BsvScannerProcessor.getTransactions started ' + address) + const linkTxs = `${API_PATH}/address/${address}/history` + let res = false + try { + res = await BlocksoftAxios.getWithoutBraking(linkTxs) + } catch (e) { + throw e + } + + if (!res || typeof res.data === 'undefined' || !res.data) { + return false + } + const basicTxs = [] + for (const row of res.data) { + if (row.height * 1 > 0) { + basicTxs.push(row) + } else { + BlocksoftCryptoLog.log('BsvScannerProcessor.getTransactions strange one ' + JSON.stringify(row)) + } + } + + let index = basicTxs.length + let bulkTxs = [] + let otherTxs = [] + let transactions = [] + for (let i = 0; i < 20; i++) { + index-- + if (index < 0) { + break + } + let txid = basicTxs[index].tx_hash + if (typeof CACHE_TXS[txid] === 'undefined') { + bulkTxs.push(txid) + } else { + otherTxs.push(CACHE_TXS[txid]) + } + } + + if (bulkTxs.length > 0) { + BlocksoftCryptoLog.log('BsvScannerProcessor.getTransactions will ask ' + JSON.stringify(bulkTxs)) + res = false + try { + res = await BlocksoftAxios.post(API_PATH + '/txs', {txids: bulkTxs}) + } catch (e) { + throw e + } + if (typeof res !== 'undefined' && res && typeof res.data !== 'undefined' && res.data) { + transactions = await this._unifyTransactions(address, res.data, otherTxs) + } else { + transactions = await this._unifyTransactions(address, [], otherTxs) + } + } else { + transactions = await this._unifyTransactions(address, [], otherTxs) + } + + + BlocksoftCryptoLog.log('BsvScannerProcessor.getTransactions finished ' + address) + return transactions + } + + async _precheckVins(result) { + let vins = [] + for (let tx of result) { + for (let vin of tx.vin) { + if (typeof CACHE_TXS[vin.txid] === 'undefined') { + vins.push(vin.txid) + if (vins.length > 19) { + BlocksoftCryptoLog.log('BsvScannerProcessor.getTransactions will ask vins ' + JSON.stringify(vins)) + const res = await BlocksoftAxios.post(API_PATH + '/txs', {txids: vins}) + if (res && typeof res.data !== 'undefined' && res.data) { + for (const tx of res.data) { + await this._saveTxToCache(tx) + } + } + vins = [] + } + } + } + } + if (vins && vins.length > 0) { + BlocksoftCryptoLog.log('BsvScannerProcessor.getTransactions will ask vins1 ' + JSON.stringify(vins)) + const res = await BlocksoftAxios.post(API_PATH + '/txs', {txids: vins}) + if (res && typeof res.data !== 'undefined' && res.data) { + for (const tx of res.data) { + await this._saveTxToCache(tx) + } + } + } + } + + async _unifyTransactions(address, result, otherTxs) { + const transactions = [] + if (result && result.length > 0) { + for (let tx of result) { + await this._saveTxToCache(tx) + } + await this._precheckVins(result) + for (let tx of result) { + const transaction = await this._unifyTransaction(address, tx) + if (transaction) { + transactions.push(transaction) + } + } + } + if (otherTxs && otherTxs.length > 0) { + for (let tx of otherTxs) { + await this._precheckVins(otherTxs) + try { + const transaction = await this._unifyTransaction(address, tx) + if (transaction) { + transactions.push(transaction) + } + } catch (e) { + throw new Error(e.message + ' in otherTxs _unifyTransaction ') + } + } + } + return transactions + } + + + /** + * @param {string} address + * @param {Object} transaction + * @param {string} transaction.txid "d1eecd2a07b9712644bcec3d7285fdc444691da6701d0032ebf6b0f823ab1727" + * @param {string} transaction.hash "d1eecd2a07b9712644bcec3d7285fdc444691da6701d0032ebf6b0f823ab1727" + * @param {string} transaction.version 2 + * @param {string} transaction.size 521 + * @param {string} transaction.locktime 0 + * @param {string} transaction.blockhash "000000000000000006d66f40fbe37ccc1854c3d13167e41485716b217ead8d6a" + * @param {string} transaction.confirmations 151964 + * @param {string} transaction.time 1576793232, + * @param {string} transaction.blocktime 1576793232 + * @param {string} transaction.blockheight 613832 + * @param {string} transaction.vin[].vout + * @param {string} transaction.vin[].txid + * @param {string} transaction.vin[].scriptSig.hex + * @param {string} transaction.vout[].value + * @param {string} transaction.vout[].n + * @param {string} transaction.vout[].scriptPubKey.addresses[] + * @return {UnifiedTransaction} + * @private + **/ + async _unifyTransaction(address, transaction) { + try { + const tx = { + transactionHash: transaction.txid, + blockHash: transaction.blockhash, + blockNumber: transaction.blockheight, + blockTime: new Date(transaction.blocktime * 1000).toISOString(), + blockConfirmations: transaction.confirmations, + transactionDirection: '?', + addressFrom: '', + addressTo: '', + addressAmount: 0, + transactionStatus: transaction.confirmations > this._blocksToConfirm ? 'success' : 'new', + transactionFee: '0' + } + + const vins = [] + for (let vin of transaction.vin) { + if (typeof CACHE_TXS[vin.txid] === 'undefined') { + vins.push(vin.txid) + } + } + if (vins && vins.length > 0) { + BlocksoftCryptoLog.log('BsvScannerProcessor.getTransactions will ask vins2 ' + JSON.stringify(vins)) + const res = await BlocksoftAxios.post(API_PATH + '/txs', {txids: vins}) + if (res && typeof res.data !== 'undefined' && res.data) { + for (const tx of res.data) { + await this._saveTxToCache(tx) + } + } + } + let othersSumIn = 0 + let mySumIn = 0 + let othersSumOut = 0 + let mySumOut = 0 + let othersAddressIn = false + let othersAddressOut = false + for (let vin of transaction.vin) { + if (typeof CACHE_TXS[vin.txid] === 'undefined') { + BlocksoftCryptoLog.log('BsvScannerProcessor _unifyTransaction error cant find vin ' + vin.txid + ' for tx ' + transaction.txid) + } else { + let found = false + for (let vinToCheck of CACHE_TXS[vin.txid].vout) { + if (vinToCheck.n === vin.vout) { + if (typeof vinToCheck.scriptPubKey.addresses !== 'undefined') { + const addressToCheck = vinToCheck.scriptPubKey.addresses[0] + const valueToCheck = vinToCheck.value + if (addressToCheck.toLowerCase() === address.toLowerCase()) { + mySumIn += valueToCheck * 1 + } else { + othersSumIn += valueToCheck * 1 + othersAddressIn = addressToCheck + } + } + found = true + } + } + if (!found) { + BlocksoftCryptoLog.log('BsvScannerProcessor _unifyTransaction error cant find vin ' + vin.txid + ' n ' + vin.vout + ' for tx ' + transaction.txid) + } + } + } + + for (let vout of transaction.vout) { + const addressToCheck = vout.scriptPubKey.addresses[0] + const valueToCheck = vout.value + if (addressToCheck.toLowerCase() === address.toLowerCase()) { + mySumOut += valueToCheck * 1 + } else { + othersSumOut += valueToCheck * 1 + othersAddressOut = addressToCheck + } + } + + if (!othersSumIn && !othersSumOut) { + tx.transactionDirection = 'self' + tx.transactionFee = BlocksoftPrettyNumbers.setCurrencyCode('BSV').makeUnPretty(BlocksoftUtils.diff(mySumOut, mySumIn)) + } else if (!othersSumIn) { + tx.transactionDirection = 'outcome' + tx.addressTo = othersAddressOut + const amount = BlocksoftUtils.diff(othersSumOut, othersSumIn) + const fee = BlocksoftUtils.diff(mySumOut + othersSumOut, mySumIn + othersSumIn) + tx.addressAmount = BlocksoftPrettyNumbers.setCurrencyCode('BSV').makeUnPretty(amount) + tx.transactionFee = BlocksoftPrettyNumbers.setCurrencyCode('BSV').makeUnPretty(fee) + } else { + tx.transactionDirection = 'income' + tx.addressFrom = othersAddressIn + const amount = BlocksoftUtils.diff(mySumOut, mySumIn) + tx.addressAmount = BlocksoftPrettyNumbers.setCurrencyCode('BSV').makeUnPretty(amount) + } + return tx + } catch (e) { + if (config.debug.cryptoErrors) { + console.log('BsvScannerProcessor _unifyTransaction error ' + e.message) + } + throw e + } + } +} + diff --git a/crypto/blockchains/bsv/BsvTransferProcessor.ts b/crypto/blockchains/bsv/BsvTransferProcessor.ts new file mode 100644 index 000000000..327652c36 --- /dev/null +++ b/crypto/blockchains/bsv/BsvTransferProcessor.ts @@ -0,0 +1,39 @@ +/** + * @version 0.5 + */ +import { BlocksoftBlockchainTypes } from '../BlocksoftBlockchainTypes' +import DogeTransferProcessor from '../doge/DogeTransferProcessor' +import DogeTxInputsOutputs from '../doge/tx/DogeTxInputsOutputs' +import BsvTxBuilder from './tx/BsvTxBuilder' +import BsvUnspentsProvider from './providers/BsvUnspentsProvider' +import BsvSendProvider from "@crypto/blockchains/bsv/providers/BsvSendProvider"; + +export default class BsvTransferProcessor extends DogeTransferProcessor implements BlocksoftBlockchainTypes.TransferProcessor { + + _trezorServerCode = '' + + _builderSettings: BlocksoftBlockchainTypes.BuilderSettings = { + minOutputDustReadable: 0.000005, + minChangeDustReadable: 0.00001, + feeMaxForByteSatoshi: 10000, // for tx builder + feeMaxAutoReadable2: 0.2, // for fee calc, + feeMaxAutoReadable6: 0.1, // for fee calc + feeMaxAutoReadable12: 0.05, // for fee calc + changeTogether: true, + minRbfStepSatoshi: 10, + minSpeedUpMulti : 1.5 + } + + canRBF(data: BlocksoftBlockchainTypes.DbAccount, transaction: BlocksoftBlockchainTypes.DbTransaction): boolean { + return false + } + + _initProviders() { + if (this._initedProviders) return false + this.unspentsProvider = new BsvUnspentsProvider(this._settings, this._trezorServerCode) + this.sendProvider = new BsvSendProvider(this._settings, this._trezorServerCode) + this.txPrepareInputsOutputs = new DogeTxInputsOutputs(this._settings, this._builderSettings) + this.txBuilder = new BsvTxBuilder(this._settings, this._builderSettings) + this._initedProviders = true + } +} diff --git a/crypto/blockchains/bsv/providers/BsvSendProvider.ts b/crypto/blockchains/bsv/providers/BsvSendProvider.ts new file mode 100644 index 000000000..fe50558a9 --- /dev/null +++ b/crypto/blockchains/bsv/providers/BsvSendProvider.ts @@ -0,0 +1,75 @@ +/** + * @version 0.5 + */ +import {BlocksoftBlockchainTypes} from "@crypto/blockchains/BlocksoftBlockchainTypes" +import DogeSendProvider from '@crypto/blockchains/doge/providers/DogeSendProvider' +import BlocksoftCryptoLog from "@crypto/common/BlocksoftCryptoLog" +import BlocksoftAxios from "@crypto/common/BlocksoftAxios" +import config from "@app/config/config" + +export default class BsvSendProvider extends DogeSendProvider implements BlocksoftBlockchainTypes.SendProvider { + + async sendTx(hex: string, subtitle: string, txRBF: any, logData: any): Promise<{ transactionHash: string, transactionJson: any }> { + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' BsvSendProvider.sendTx ' + subtitle + ' started ', logData) + + let link = 'https://api.whatsonchain.com/v1/bsv/main/tx/raw' + + //logData = await this._check(hex, subtitle, txRBF, logData) + + let res + try { + res = await BlocksoftAxios.post(link, {txhex: hex}) + } catch (e) { + if (config.debug.cryptoErrors) { + console.log(this._settings.currencyCode + ' BsvSendProvider.sendTx error ', e) + } + if (subtitle.indexOf('rawSend') !== -1) { + throw e + } + try { + logData.error = e.message + await this._checkError(hex, subtitle, txRBF, logData) + } catch (e2) { + if (config.debug.cryptoErrors) { + console.log(this._settings.currencyCode + ' DogeSendProvider.send proxy error errorTx ' + e.message) + } + BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeSendProvider.send proxy error errorTx ' + e2.message) + } + if (this._settings.currencyCode === 'USDT' && e.message.indexOf('bad-txns-in-belowout') !== -1) { + throw new Error('SERVER_RESPONSE_NOT_ENOUGH_FEE') + } else if (e.message.indexOf('transaction already in block') !== -1) { + throw new Error('SERVER_RESPONSE_TRANSACTION_ALREADY_MINED') + } else if (e.message.indexOf('inputs-missingorspent') !== -1) { + throw new Error('SERVER_RESPONSE_TRANSACTION_ALREADY_MINED') + } else if (e.message.indexOf('insufficient priority') !== -1) { + throw new Error('SERVER_RESPONSE_NO_RESPONSE_OR_MORE_FEE') + } else if (e.message.indexOf('dust') !== -1) { + throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_DUST') + } else if (e.message.indexOf('bad-txns-inputs-spent') !== -1 || e.message.indexOf('txn-mempool-conflict') !== -1) { + throw new Error('SERVER_RESPONSE_NO_RESPONSE') + } else if (e.message.indexOf('min relay fee not met') !== -1 || e.message.indexOf('fee for relay') !== -1) { + throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_FEE') + } else if (e.message.indexOf('insufficient fee, rejecting replacement') !== -1) { + throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_FEE_FOR_REPLACEMENT') + } else if (e.message.indexOf('insufficient fee') !== -1) { + throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_FEE') + } else if (e.message.indexOf('too-long-mempool-chain') !== -1) { + throw new Error('SERVER_RESPONSE_NO_RESPONSE') + } else { + e.message += ' link: ' + link + throw e + } + } + if (typeof res.data === 'undefined' || !res.data) { + if (config.debug.cryptoErrors) { + console.log(this._settings.currencyCode + 'BsvSendProvider.send no txid', res.data) + } + throw new Error('SERVER_RESPONSE_NOT_CONNECTED') + } + + const transactionHash = res.data + logData = await this._checkSuccess(transactionHash, hex, subtitle, txRBF, logData) + + return { transactionHash, transactionJson: {}, logData } + } +} diff --git a/crypto/blockchains/bsv/providers/BsvUnspentsProvider.ts b/crypto/blockchains/bsv/providers/BsvUnspentsProvider.ts new file mode 100644 index 000000000..4faef4273 --- /dev/null +++ b/crypto/blockchains/bsv/providers/BsvUnspentsProvider.ts @@ -0,0 +1,42 @@ +/** + * @version 0.5 + */ +import { BlocksoftBlockchainTypes } from '@crypto/blockchains/BlocksoftBlockchainTypes' +import DogeUnspentsProvider from '@crypto/blockchains/doge/providers/DogeUnspentsProvider' +import BtcCashUtils from '@crypto/blockchains/bch/ext/BtcCashUtils' +import BlocksoftCryptoLog from "@crypto/common/BlocksoftCryptoLog" +import BlocksoftExternalSettings from "@crypto/common/BlocksoftExternalSettings" +import BlocksoftAxios from "@crypto/common/BlocksoftAxios" +export default class BsvUnspentsProvider extends DogeUnspentsProvider implements BlocksoftBlockchainTypes.UnspentsProvider { + + _isMyAddress(voutAddress: string, address: string, walletHash: string): string { + const address2 = BtcCashUtils.fromLegacyAddress(address) + const address3 = 'bitcoincash:' + address2 + return (voutAddress === address || voutAddress === address2 || voutAddress === address3) ? address : '' + } + + async getUnspents(address: string): Promise { + // @ts-ignore + BlocksoftCryptoLog.log(this._settings.currencyCode + ' BsvUnspentsProvider.getUnspents started ' + address) + let link = 'https://api.whatsonchain.com/v1/bsv/main/address/' + address + '/unspent' + + const res = await BlocksoftAxios.getWithoutBraking(link) + if (!res.data || typeof res.data[0] === 'undefined') { + return [] + } + const sortedUnspents = [] + for (let unspent of res.data) { + let unspentFormatted = { + confirmations: unspent.height, + height: unspent.height, + derivationPath: false, + vout: unspent?.tx_pos, + isRequired : false, + txid: unspent.tx_hash, + value: unspent.value + } + sortedUnspents.push(unspentFormatted) + } + return sortedUnspents + } +} diff --git a/crypto/blockchains/bsv/stores/BsvTmpDS.js b/crypto/blockchains/bsv/stores/BsvTmpDS.js new file mode 100644 index 000000000..6bff8a13e --- /dev/null +++ b/crypto/blockchains/bsv/stores/BsvTmpDS.js @@ -0,0 +1,59 @@ +import Database from '@app/appstores/DataSource/Database'; + +const tableName = 'transactions_scanners_tmp' + +class BsvTmpDS { + /** + * @type {string} + * @private + */ + _currencyCode = 'BSV' + + _valKey = 'txs' + + _isSaved = false + + async getCache() { + const res = await Database.query(` + SELECT id, tmp_key, tmp_sub_key, tmp_val + FROM ${tableName} + WHERE currency_code='${this._currencyCode}' + AND tmp_key='${this._valKey}' + `) + let tmp = {} + const idsToRemove = [] + if (res.array) { + let row + for (row of res.array) { + if (typeof tmp[row.tmp_sub_key] !== 'undefined') { + idsToRemove.push(row.id) + } else { + try { + tmp[row.tmp_sub_key] = JSON.parse(Database.unEscapeString(row.tmp_val)) + } catch (e) { + idsToRemove.push(row.id) + } + } + } + if (idsToRemove.length > 0) { + await Database.query(`DELETE FROM ${tableName} WHERE id IN (${idsToRemove.join(',')})`) + } + } + return tmp + } + + async saveCache(txid, value) { + const tmp = Database.escapeString(JSON.stringify(value)) + const now = new Date().toISOString() + const prepared = [{ + currency_code: this._currencyCode, + tmp_key: this._valKey, + tmp_sub_key: txid, + tmp_val: tmp, + created_at: now + }] + await Database.setTableName(tableName).setInsertData({insertObjs: prepared}).insert() + } +} + +export default new BsvTmpDS() diff --git a/crypto/blockchains/bsv/tx/BsvTxBuilder.ts b/crypto/blockchains/bsv/tx/BsvTxBuilder.ts new file mode 100644 index 000000000..4887aa6e3 --- /dev/null +++ b/crypto/blockchains/bsv/tx/BsvTxBuilder.ts @@ -0,0 +1,35 @@ +/** + * @version 0.5 + */ +import { BlocksoftBlockchainTypes } from '../../BlocksoftBlockchainTypes' +import DogeTxBuilder from '../../doge/tx/DogeTxBuilder' +import BtcCashUtils from '../../bch/ext/BtcCashUtils' +import { ECPair, payments, TransactionBuilder } from 'bitcoinjs-lib' + + +export default class BsvTxBuilder extends DogeTxBuilder implements BlocksoftBlockchainTypes.TxBuilder { + + _getRawTxValidateKeyPair(privateData: BlocksoftBlockchainTypes.TransferPrivateData, data: BlocksoftBlockchainTypes.TransferData): void { + this.keyPair = false + try { + this.keyPair = ECPair.fromWIF(privateData.privateKey, this._bitcoinNetwork) + const address = payments.p2pkh({ + pubkey: this.keyPair.publicKey, + network: this._bitcoinNetwork + }).address + const legacyAddress = BtcCashUtils.toLegacyAddress(data.addressFrom) + if (address !== data.addressFrom && address !== legacyAddress) { + // noinspection ExceptionCaughtLocallyJS + throw new Error('not valid signing address ' + data.addressFrom + ' != ' + address + ' != ' + legacyAddress) + } + } catch (e) { + e.message += ' in privateKey BSV signature check ' + throw e + } + } + + _getRawTxAddOutput(txb: TransactionBuilder, output: BlocksoftBlockchainTypes.OutputTx): void { + const to = BtcCashUtils.toLegacyAddress(output.to) + txb.addOutput(to, output.amount * 1) + } +} diff --git a/crypto/blockchains/btc/BtcScannerProcessor.js b/crypto/blockchains/btc/BtcScannerProcessor.js new file mode 100644 index 000000000..bfa8dcc4b --- /dev/null +++ b/crypto/blockchains/btc/BtcScannerProcessor.js @@ -0,0 +1,411 @@ +/** + * @version 0.9 + */ + +import BlocksoftUtils from '@crypto/common/BlocksoftUtils' +import BlocksoftAxios from '@crypto/common/BlocksoftAxios' +import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog' +import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings' + +import BtcFindAddressFunction from './basic/BtcFindAddressFunction' +import config from '@app/config/config' +import Database from '@app/appstores/DataSource/Database' +import TransactionFilterTypeDict from '@appV2/dicts/transactionFilterTypeDict' + +const CACHE_VALID_TIME = 60000 // 60 seconds +const CACHE = {} +const CACHE_WALLET_PUBS = {} + +const TIMEOUT_BTC = 60000 +const PROXY_TXS = 'https://proxy.trustee.deals/btc/getTxs' + +export default class BtcScannerProcessor { + + /** + * @type {number} + * @private + */ + _blocksToConfirm = 1 + + /** + * @type {string} + * @private + */ + _trezorServerCode = 'BTC_TREZOR_SERVER' + + /** + * @private + */ + _trezorServer = false + + constructor(settings) { + this._settings = settings + } + + /** + * @param address + * @param additionalData + * @returns {Promise} + * @private + */ + async _get(address, additionalData, source = '') { + const now = new Date().getTime() + if (typeof CACHE[address] !== 'undefined' && (now - CACHE[address].time < CACHE_VALID_TIME)) { + CACHE[address].provider = 'trezor-cache' + return CACHE[address] + } + BlocksoftCryptoLog.log('BtcScannerProcessor._get ' + address + ' from ' + source + ' started') + + this._trezorServer = await BlocksoftExternalSettings.getTrezorServer(this._trezorServerCode, 'BTC.Scanner._get') + + const prefix = address.substr(0, 4) + + let link = '' + let res = false + if (prefix === 'xpub' || prefix === 'zpub' || prefix === 'ypub') { + link = PROXY_TXS + '?address=' + address + '&type=xpub¤cyCode=' + this._settings['currencyCode'] + res = await BlocksoftAxios.getWithoutBraking(link, 5, TIMEOUT_BTC) + if (res && typeof res.data !== 'undefined' && res.data && typeof res.data.data !== 'undefined') { + res.data = res.data.data + } else { + link = this._trezorServer + '/api/v2/xpub/' + address + '?details=txs&gap=9999&tokens=used&pageSize=40' + try { + res = await BlocksoftAxios._request(link, 'get', false, false, true, TIMEOUT_BTC) + } catch (e) { + if (e.message.indexOf('"error":"internal server error"') !== -1) { + CACHE[address] = { + data: { + balance: 0, + unconfirmedBalance: 0, + addresses: [], + specialMark: 'badServer' + }, + time: now, + provider: 'trezor-badserver' + } + return CACHE[address] + } + } + } + } else { + link = PROXY_TXS + '?address=' + address + '¤cyCode=' + this._settings['currencyCode'] + res = await BlocksoftAxios.getWithoutBraking(link, 5, TIMEOUT_BTC) + if (res && typeof res.data !== 'undefined' && res.data && typeof res.data.data !== 'undefined') { + res.data = res.data.data + } else { + link = this._trezorServer + '/api/v2/address/' + address + '?details=txs&gap=9999&pageSize=80' + res = await BlocksoftAxios.getWithoutBraking(link, 5, TIMEOUT_BTC) + } + } + + if (!res || !res.data) { + await BlocksoftExternalSettings.setTrezorServerInvalid(this._trezorServerCode, this._trezorServer) + CACHE[address] = { + data: false, + time: now, + provider: 'trezor-empty' + } + return false + } + if (typeof res.data.balance === 'undefined') { + throw new Error(this._settings.currencyCode + ' BtcScannerProcessor._get nothing loaded for address ' + link) + } + + const addresses = {} + let plainAddresses = {} + if (additionalData && additionalData.addresses) { + plainAddresses = additionalData.addresses + } + if (typeof res.data.tokens !== 'undefined') { + let token + for (token of res.data.tokens) { + addresses[token.name] = { + balance: token.balance, + transactions: token.transfers, + path : token.path + } + plainAddresses[token.name] = token.path + } + } else { + plainAddresses[address] = 1 + } + res.data.addresses = addresses + res.data.plainAddresses = plainAddresses + CACHE[address] = { + data: res.data, + time: now, + provider: 'trezor' + } + return CACHE[address] + } + + async _getPubs(walletHash) { + if (typeof CACHE_WALLET_PUBS[walletHash] !== 'undefined') { + return CACHE_WALLET_PUBS[walletHash] + } + const sqlPub = `SELECT wallet_pub_value as walletPub + FROM wallet_pub + WHERE wallet_hash = '${walletHash}' + AND currency_code='BTC'` + const resPub = await Database.query(sqlPub) + CACHE_WALLET_PUBS[walletHash] = {} + if (resPub && resPub.array && resPub.array.length > 0) { + for (const row of resPub.array) { + const scanAddress = row.walletPub + CACHE_WALLET_PUBS[walletHash][scanAddress] = 1 + } + } + return CACHE_WALLET_PUBS[walletHash] + } + + /** + * @param {string} address + * @return {Promise<{balance:*, unconfirmed:*, provider:string}>} + */ + async getBalanceBlockchain(address, data, walletHash, source = '') { + BlocksoftCryptoLog.log(this._settings.currencyCode + ' BtcScannerProcessor.getBalance started ' + address) + const res = await this._get(address, data, source) + if (!res) { + return false + } + return { + address : address, + balance: res.data.balance, + unconfirmed: res.data.unconfirmedBalance, + provider: res.provider, + time: res.time, + addresses: res.data.addresses, + specialMark : typeof res.data.specialMark !== 'undefined' ? res.data.specialMark : false + } + } + + async getAddressesBlockchain(scanData, source = '') { + const address = scanData.account.address.trim() + const data = scanData.additional + const withBalances = typeof scanData.withBalances !== 'undefined' && scanData.withBalances + if (!withBalances) { + if (config.debug.cryptoErrors) { + console.log(this._settings.currencyCode + ' BtcScannerProcessor.getAddresses started withoutBalances (KSU!)', address) + } + BlocksoftCryptoLog.log(this._settings.currencyCode + ' BtcScannerProcessor.getAddresses started withoutBalances (KSU!)', address) + } else { + BlocksoftCryptoLog.log(this._settings.currencyCode + ' BtcScannerProcessor.getAddresses started withBalances', address) + } + let res = await this._get(address, data, source) + if (typeof res.data !== 'undefined') { + res = JSON.parse(JSON.stringify(res.data)) + } else { + res = false + } + try { + if (typeof data.walletPub !== 'undefined') { + const resPub = await this._getPubs(data.walletPub.walletHash) + for (const scanAddress in resPub) { + if (scanAddress === address) continue + const tmp = await this._get(scanAddress, data, source + ' _getPubs1') + if (typeof tmp.data === 'undefined' || typeof tmp.data.plainAddresses === 'undefined') continue + if (res === false || typeof res.plainAddresses === 'undefined') { + res = JSON.parse(JSON.stringify(tmp.data)) + } else { + if (withBalances) { + for (const row in tmp.data.addresses) { + res.addresses[row] = tmp.data.addresses[row] + } + } else { + for (const row in tmp.data.plainAddresses) { + res.plainAddresses[row] = tmp.data.plainAddresses[row] + } + } + } + + } + } + } catch (e) { + if (config.debug.cryptoErrors) { + console.log(this._settings.currencyCode + ' BtcScannerProcessor.getAddresses load from all addresses error ' + e.message, e) + } + BlocksoftCryptoLog.log(this._settings.currencyCode + ' BtcScannerProcessor.getAddresses load from all addresses error ' + e.message) + } + return withBalances ? res.addresses : res.plainAddresses + } + + /** + * @param {string} scanData.account.address + * @param {*} scanData.additional + * @param {string} scanData.account.walletHash + * @return {Promise} + */ + async getTransactionsBlockchain(scanData, source = '') { + const address = scanData.account.address.trim() + const data = scanData.additional + + BlocksoftCryptoLog.log(this._settings.currencyCode + ' BtcScannerProcessor.getTransactions started ' + address) + let res = await this._get(address, data, source) + if (typeof res.data !== 'undefined') { + res = JSON.parse(JSON.stringify(res.data)) + } else { + res = false + } + try { + if (typeof data.walletPub !== 'undefined') { + const resPub = await this._getPubs(data.walletPub.walletHash) + for (const scanAddress in resPub) { + if (scanAddress === address) continue + const tmp = await this._get(scanAddress, data, source + ' _getPubs2') + if (typeof tmp.data === 'undefined' || typeof tmp.data.transactions === 'undefined') continue + if (res === false || typeof res.transactions === 'undefined') { + res = JSON.parse(JSON.stringify(tmp.data)) + } else { + for (const row of tmp.data.transactions) { + res.transactions.push(row) + } + } + } + } else { + for (const scanAddress in data.addresses) { + if (scanAddress === address) continue + const tmp = await this._get(scanAddress, data, source + ' _getOnes2') + if (typeof tmp.data === 'undefined' || typeof tmp.data.transactions === 'undefined') continue + if (res === false || typeof res.transactions === 'undefined') { + res = tmp.data + } else { + for (const row of tmp.data.transactions) { + res.transactions.push(row) + } + } + } + } + } catch (e) { + if (config.debug.cryptoErrors) { + console.log(this._settings.currencyCode + ' BtcScannerProcessor.getTransactions load from all addresses error ' + e.message, e) + } + BlocksoftCryptoLog.log(this._settings.currencyCode + ' BtcScannerProcessor.getTransactions load from all addresses error ' + e.message) + } + BlocksoftCryptoLog.log(this._settings.currencyCode + ' BtcScannerProcessor.getTransactions loaded from ' + res.provider + ' ' + res.time) + + if (typeof res.transactions === 'undefined' || !res.transactions) return [] + const transactions = [] + const addresses = res.plainAddresses + if (typeof data !== 'undefined' && data && typeof data.addresses !== 'undefined') { + for (const tmp in data.addresses) { + addresses[tmp] = data.addresses[tmp] + } + } + if (typeof scanData.additional.addresses !== 'undefined') { + for (const tmp in scanData.additional.addresses) { + address[tmp] = tmp + } + } + + const vinsOrder = {} + for (const tx of res.transactions) { + vinsOrder[tx.txid] = tx.blockTime + } + + let plussed = false + let i = 0 + do { + for (const tx of res.transactions) { + if (typeof tx.vin === 'undefined' || tx.vin.length === 0) continue + for (const vin of tx.vin) { + if (typeof vinsOrder[vin.txid] === 'undefined') { + continue + } + const newTime = vinsOrder[vin.txid] + 1 + if (tx.blockTime < newTime) { + tx.blockTime = newTime + plussed = true + } + vinsOrder[tx.txid] = tx.blockTime + } + } + i++ + } while (plussed && i < 100) + + const uniqueTxs = {} + for (const tx of res.transactions) { + const transaction = await this._unifyTransaction(address, addresses, tx) + if (transaction) { + if (typeof uniqueTxs[transaction.transactionHash] !== 'undefined') continue + uniqueTxs[transaction.transactionHash] = 1 + transactions.push(transaction) + } + } + BlocksoftCryptoLog.log(this._settings.currencyCode + ' BtcScannerProcessor.getTransactions finished ' + address + ' total: ' + transactions.length) + return transactions + } + + /** + * + * @param {string} address + * @param {string} addresses + * @param {Object} transaction + * @param {string} transaction.txid c6b4c3879196857bed7fd5b553dd0049486c032d6a1be72b98fda967ca54b2da + * @param {string} transaction.version 1 + * @param {string} transaction.vin[].txid aa31777a9db759f57fd243ef47419939f233d16bc3e535e9a1c5af3ace87cb54 + * @param {string} transaction.vin[].sequence 4294967294 + * @param {string} transaction.vin[].n 0 + * @param {string} transaction.vin[].addresses [ 'DFDn5QyHH9DiFBNFGMcyJT5uUpDvmBRDqH' ] + * @param {string} transaction.vin[].value 44400000000 + * @param {string} transaction.vin[].hex 47304402200826f97d3432452abedd4346553de0b0c2d401ad7056b155e6462484afd98aa902202b5fb3166b96ded33249aecad7c667c0870c1 + * @param {string} transaction.vout[].value 59999824800 + * @param {string} transaction.vout[].n 0 + * @param {string} transaction.vout[].spent true + * @param {string} transaction.vout[].hex 76a91456d49605503d4770cf1f32fbfb69676d9a72554f88ac + * @param {string} transaction.vout[].addresses [ 'DD4DKVTEkRUGs7qzN8b7q5LKmoE9mXsJk4' ] + * @param {string} transaction.blockHash fc590834c04812e1c7818024a94021e12c4d8ab905724b4a4fdb4d4732878f69 + * @param {string} transaction.blockHeight 3036225 + * @param {string} transaction.confirmations 8568 + * @param {string} transaction.blockTime 1577362993 + * @param {string} transaction.value 59999917700 + * @param {string} transaction.valueIn 59999917700 + * @param {string} transaction.fees 0 + * @param {string} transaction.hex 010000000654cb87ce3aafc5a1e935e5c36bd133f239 + * @return {Promise} + * @private + */ + async _unifyTransaction(address, addresses, transaction) { + let showAddresses = false + try { + showAddresses = await BtcFindAddressFunction(addresses, transaction) + } catch (e) { + e.message += ' transaction hash ' + JSON.stringify(transaction) + ' address ' + address + throw e + } + + let transactionStatus = 'new' + if (transaction.confirmations >= this._blocksToConfirm) { + transactionStatus = 'success' + } else if (transaction.confirmations > 0) { + transactionStatus = 'confirming' + } + + let transactionFilterType = TransactionFilterTypeDict.USUAL + if (typeof showAddresses.to !== 'undefined' && showAddresses.to.toLowerCase().indexOf('simple send') !== -1) { + transactionFilterType = TransactionFilterTypeDict.FEE + } + + let formattedTime + try { + formattedTime = BlocksoftUtils.toDate(transaction.blockTime) + } catch (e) { + e.message += ' timestamp error transaction data ' + JSON.stringify(transaction) + throw e + } + + return { + transactionHash: transaction.txid, + blockHash: transaction.blockHash, + blockNumber: +transaction.blockHeight, + blockTime: formattedTime, + blockConfirmations: transaction.confirmations, + transactionDirection: showAddresses.direction, + addressFrom: showAddresses.from, + addressTo: showAddresses.to, + addressAmount: showAddresses.value, + transactionStatus: transactionStatus, + transactionFee: transaction.fees, + transactionFilterType + } + } +} diff --git a/crypto/blockchains/btc/BtcTransferProcessor.ts b/crypto/blockchains/btc/BtcTransferProcessor.ts new file mode 100644 index 000000000..12c8a8257 --- /dev/null +++ b/crypto/blockchains/btc/BtcTransferProcessor.ts @@ -0,0 +1,37 @@ +/** + * @version 0.20 + */ +import { BlocksoftBlockchainTypes } from '../BlocksoftBlockchainTypes' +import DogeTransferProcessor from '../doge/DogeTransferProcessor' +import BtcUnspentsProvider from './providers/BtcUnspentsProvider' +import DogeSendProvider from '../doge/providers/DogeSendProvider' +import BtcTxInputsOutputs from './tx/BtcTxInputsOutputs' +import BtcTxBuilder from './tx/BtcTxBuilder' +import BtcNetworkPrices from './basic/BtcNetworkPrices' + +export default class BtcTransferProcessor extends DogeTransferProcessor implements BlocksoftBlockchainTypes.TransferProcessor { + + _trezorServerCode = 'BTC_TREZOR_SERVER' + + _builderSettings: BlocksoftBlockchainTypes.BuilderSettings = { + minOutputDustReadable: 0.000001, + minChangeDustReadable: 0.000001, + feeMaxForByteSatoshi: 1000, // for tx builder + feeMaxAutoReadable2: 0.01, // for fee calc, + feeMaxAutoReadable6: 0.005, // for fee calc + feeMaxAutoReadable12: 0.001, // for fee calc + changeTogether: true, + minRbfStepSatoshi: 30, + minSpeedUpMulti : 1.7 + } + + _initProviders() { + if (this._initedProviders) return false + this.unspentsProvider = new BtcUnspentsProvider(this._settings, this._trezorServerCode) + this.sendProvider = new DogeSendProvider(this._settings, this._trezorServerCode) + this.txPrepareInputsOutputs = new BtcTxInputsOutputs(this._settings, this._builderSettings) + this.txBuilder = new BtcTxBuilder(this._settings, this._builderSettings) + this.networkPrices = new BtcNetworkPrices() + this._initedProviders = true + } +} diff --git a/crypto/blockchains/btc/address/BtcAddressProcessor.js b/crypto/blockchains/btc/address/BtcAddressProcessor.js new file mode 100644 index 000000000..e89f98c35 --- /dev/null +++ b/crypto/blockchains/btc/address/BtcAddressProcessor.js @@ -0,0 +1,36 @@ +/** + * @version 0.5 + */ +const bitcoin = require('bitcoinjs-lib') + +const networksConstants = require('../../../common/ext/networks-constants') + +export default class BtcAddressProcessor { + constructor(settings) { + if (typeof settings === 'undefined' || !settings) { + throw new Error('BtcAddressProcessor requires settings') + } + if (typeof settings.network === 'undefined') { + throw new Error('BtcAddressProcessor requires settings.network') + } + if (typeof networksConstants[settings.network] === 'undefined') { + throw new Error('while retrieving Bitcoin address - unknown Bitcoin network specified. Got : ' + settings.network) + } + this._currentBitcoinNetwork = networksConstants[settings.network].network + } + + setBasicRoot(root) { + + } + + /** + * @param {string|Buffer} privateKey + * @param {*} data + * @returns {Promise<{privateKey: string, address: string, addedData: *}>} + */ + async getAddress(privateKey, data = {}) { + const keyPair = bitcoin.ECPair.fromPrivateKey(privateKey, { network: this._currentBitcoinNetwork }) + const address = bitcoin.payments.p2pkh({ pubkey: keyPair.publicKey, network: this._currentBitcoinNetwork}).address + return { address, privateKey: keyPair.toWIF() } + } +} diff --git a/crypto/blockchains/btc/address/BtcSegwitAddressProcessor.js b/crypto/blockchains/btc/address/BtcSegwitAddressProcessor.js new file mode 100644 index 000000000..73f1251c3 --- /dev/null +++ b/crypto/blockchains/btc/address/BtcSegwitAddressProcessor.js @@ -0,0 +1,19 @@ +/** + * @version 0.5 + */ +import BtcAddressProcessor from './BtcAddressProcessor' + +const bitcoin = require('bitcoinjs-lib') + +export default class BtcSegwitAddressProcessor extends BtcAddressProcessor { + /** + * @param {string|Buffer} privateKey + * @param {*} data + * @returns {Promise<{privateKey: string, address: string, addedData: *}>} + */ + async getAddress(privateKey, data = {}) { + const keyPair = bitcoin.ECPair.fromPrivateKey(privateKey, { network: this._currentBitcoinNetwork }) + const address = bitcoin.payments.p2wpkh({pubkey: keyPair.publicKey, network: this._currentBitcoinNetwork}).address + return { address, privateKey: keyPair.toWIF() } + } +} diff --git a/crypto/blockchains/btc/address/BtcSegwitCompatibleAddressProcessor.js b/crypto/blockchains/btc/address/BtcSegwitCompatibleAddressProcessor.js new file mode 100644 index 000000000..6dd004958 --- /dev/null +++ b/crypto/blockchains/btc/address/BtcSegwitCompatibleAddressProcessor.js @@ -0,0 +1,19 @@ +/** + * @version 0.5 + */ +import BtcAddressProcessor from './BtcAddressProcessor' + +const bitcoin = require('bitcoinjs-lib') + +export default class BtcSegwitCompatibleAddressProcessor extends BtcAddressProcessor { + /** + * @param {string|Buffer} privateKey + * @param {*} data + * @returns {Promise<{privateKey: string, address: string, addedData: *}>} + */ + async getAddress(privateKey, data = {}) { + const keyPair = bitcoin.ECPair.fromPrivateKey(privateKey, { network: this._currentBitcoinNetwork }) + const address = bitcoin.payments.p2sh({ redeem: bitcoin.payments.p2wpkh({ pubkey: keyPair.publicKey }) }).address + return { address, privateKey: keyPair.toWIF() } + } +} diff --git a/crypto/blockchains/btc/basic/BtcFindAddressFunction.js b/crypto/blockchains/btc/basic/BtcFindAddressFunction.js new file mode 100644 index 000000000..91a51ae94 --- /dev/null +++ b/crypto/blockchains/btc/basic/BtcFindAddressFunction.js @@ -0,0 +1,126 @@ +/** + * @version 0.5 + * @param {string} addresses[] + * @param {string} transaction.hex + * @param {string} transaction.address + * @param {string} transaction.vin[].txid + * @param {string} transaction.vin[].sequence + * @param {string} transaction.vin[].n 0 + * @param {string} transaction.vin[].addresses[] + * @param {string} transaction.vin[].addr + * @param {string} transaction.vin[].value + * @param {string} transaction.vin[].hex + * @param {string} transaction.vout[].value + * @param {string} transaction.vout[].n 0 + * @param {string} transaction.vout[].spent + * @param {string} transaction.vout[].hex + * @param {string} transaction.vout[].addresses[] + * @param {string} transaction.vout[].scriptPubKey.addresses[] + * @returns {Promise<{from: string, to: string, value: number, direction: string}>} + * @constructor + */ +import BlocksoftBN from '../../../common/BlocksoftBN' + +export default async function BtcFindAddressFunction(indexedAddresses, transaction) { + const inputMyBN = new BlocksoftBN(0) + const inputOthersBN = new BlocksoftBN(0) + let inputMyAddress = '' + const inputOthersAddresses = [] + const uniqueTmp = {} + if (transaction.vin) { + for (let i = 0, ic = transaction.vin.length; i < ic; i++) { + let vinAddress + const vinValue = transaction.vin[i].value + if (typeof transaction.vin[i].addresses !== 'undefined') { + vinAddress = transaction.vin[i].addresses[0] + } else if (typeof transaction.vin[i].addr !== 'undefined') { + vinAddress = transaction.vin[i].addr + } + if (!vinAddress) continue + if (vinAddress.indexOf('OP_RETURN (omni') !== -1) { + vinAddress = 'OMNI' + } + if (typeof indexedAddresses[vinAddress] !== 'undefined') { + inputMyBN.add(vinValue) + inputMyAddress = vinAddress + } else { + if (typeof uniqueTmp[vinAddress] === 'undefined') { + uniqueTmp[vinAddress] = 1 + inputOthersAddresses.push(vinAddress) + } + inputOthersBN.add(vinValue) + } + } + } + + const outputMyBN = new BlocksoftBN(0) + const outputOthersBN = new BlocksoftBN(0) + + let outputMyAddress = '' + const allMyAddresses = [] + const outputOthersAddresses = [] + const uniqueTmp2 = {} + if (transaction.vout) { + for (let j = 0, jc = transaction.vout.length; j < jc; j++) { + let voutAddress + const voutValue = transaction.vout[j].value + if (typeof transaction.vout[j].addresses !== 'undefined') { + voutAddress = transaction.vout[j].addresses[0] + } else if (typeof transaction.vout[j].scriptPubKey !== 'undefined' && typeof transaction.vout[j].scriptPubKey.addresses !== 'undefined') { + voutAddress = transaction.vout[j].scriptPubKey.addresses[0] + } + if (voutAddress.indexOf('OP_RETURN (omni') !== -1) { + voutAddress = 'OMNI' + } + + if (typeof indexedAddresses[voutAddress] !== 'undefined') { + outputMyBN.add(voutValue) + outputMyAddress = voutAddress + allMyAddresses.push(outputMyAddress) + } else { + if (typeof uniqueTmp2[voutAddress] === 'undefined') { + uniqueTmp2[voutAddress] = 1 + outputOthersAddresses.push(voutAddress) + } + outputOthersBN.add(voutValue) + } + } + } + + let output + if (inputMyBN.get() === '0') { // my only in output + output = { + direction: 'income', + from: inputOthersAddresses.length > 0 ? inputOthersAddresses.join(',') : '', + to: '', // outputMyAddress, + value: outputMyBN.get() + } + } else if (outputMyBN.get() === '0') { // my only in input + output = { + direction: 'outcome', + from: '', // inputMyAddress, + to: outputOthersAddresses.length > 0 ? outputOthersAddresses.join(',') : '', + value: (inputOthersBN.get() === '0') ? outputOthersBN.get() : inputMyBN.get() + } + } else { // both input and output + if (outputOthersAddresses.length > 0) {// there are other address + output = { + direction: 'outcome', + from: '', // inputMyAddress, + to: outputOthersAddresses.join(','), + value: outputOthersBN.get() + } + } else { + output = { + direction: 'self', + from: '', // inputMyAddress, + to: '', // outputMyAddress, + value: Math.abs(inputMyBN.diff(outputMyBN).get()) + } + } + } + output.from = output.from.substr(0, 255) + output.to = output.to.substr(0, 255) + output.allMyAddresses = allMyAddresses + return output +} diff --git a/crypto/blockchains/btc/basic/BtcNetworkPrices.ts b/crypto/blockchains/btc/basic/BtcNetworkPrices.ts new file mode 100644 index 000000000..0ea27bd46 --- /dev/null +++ b/crypto/blockchains/btc/basic/BtcNetworkPrices.ts @@ -0,0 +1,245 @@ +/** + * @version 0.20 + **/ +import { BlocksoftBlockchainTypes } from '../../BlocksoftBlockchainTypes' +import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog' +import BlocksoftAxios from '../../../common/BlocksoftAxios' +import BlocksoftExternalSettings from '../../../common/BlocksoftExternalSettings' + +import MarketingEvent from '../../../../app/services/Marketing/MarketingEvent' +import BlocksoftUtils from '../../../common/BlocksoftUtils' + +const ESTIMATE_PATH = 'https://mempool.space/api/v1/fees/recommended' + +const CACHE_VALID_TIME = 60000 // 1 minute + +let CACHE_FEES_BTC_TIME = 0 +let CACHE_FEES_BTC = { + 'speed_blocks_2': 0, + 'speed_blocks_6': 0, + 'speed_blocks_12': 0 +} + +let CACHE_PREV_DATA = { + fastestFee: 19, + halfHourFee: 3, + hourFee: 2, + lastBlock: 0, + timeFromBlock: 0, + timeFromBlockDiff: 0, + mempoolSize: 0 +} + +let CACHE_PREV_PREV_DATA = { + fastestFee: 19, + halfHourFee: 3, + hourFee: 2, + lastBlock: 0, + timeFromBlock: 0, + timeFromBlockDiff: 0, + mempoolSize: 0 +} + + +export default class BtcNetworkPrices implements BlocksoftBlockchainTypes.NetworkPrices { + + private _trezorServerCode = 'BTC_TREZOR_SERVER' + private _trezorServer: any + + async getNetworkPrices(currencyCode: string): Promise<{ 'speed_blocks_2': number, 'speed_blocks_6': number, 'speed_blocks_12': number }> { + BlocksoftCryptoLog.log('BtcNetworkPricesProvider ' + currencyCode) + const logData = { + currencyCode, + source: 'fromCache', + cacheTime: CACHE_FEES_BTC_TIME + '', + fee: JSON.stringify(CACHE_FEES_BTC) + } + const now = new Date().getTime() + if (CACHE_FEES_BTC && now - CACHE_FEES_BTC_TIME < CACHE_VALID_TIME) { + // noinspection ES6MissingAwait + MarketingEvent.logEvent('estimate_fee_btc_result', logData) + // @ts-ignore + BlocksoftCryptoLog.log('BtcNetworkPricesProvider ' + currencyCode + ' used cache ' + JSON.stringify(CACHE_FEES_BTC)) + return CACHE_FEES_BTC + } + + BlocksoftCryptoLog.log('BtcNetworkPricesProvider ' + currencyCode + ' no cache load') + + let link = `${ESTIMATE_PATH}` + let timeFromBlock = false + let timeFromBlockDiff = false + let lastBlock = 0 + let mempoolSize = 0 + if (currencyCode !== 'BTC_TEST') { + + this._trezorServer = await BlocksoftExternalSettings.getTrezorServer(this._trezorServerCode, 'BTC.NetworkPrices') + const linkTrezor = this._trezorServer + '/api/' + + let tmp = false + try { + tmp = await BlocksoftAxios.getWithoutBraking(linkTrezor) + if (tmp && tmp.data) { + lastBlock = tmp.data.blockbook.bestHeight + mempoolSize = tmp.data.blockbook.mempoolSize + timeFromBlock = tmp.data.blockbook.lastBlockTime + timeFromBlockDiff = now - new Date(timeFromBlock).getTime() + } + } catch (e) { + + } + + BlocksoftCryptoLog.log('BtcNetworkPricesProvider lastBlock ' + lastBlock + ' mempool ' + mempoolSize + ' timeFromBlock ' + timeFromBlock + ' diff ' + timeFromBlockDiff) + + tmp = false + try { + tmp = await BlocksoftAxios.getWithoutBraking(link) + if (tmp && tmp.data) { + logData.source = 'reloaded' + if (lastBlock > CACHE_PREV_DATA.lastBlock) { + CACHE_PREV_PREV_DATA = CACHE_PREV_DATA + } + CACHE_PREV_DATA = tmp.data + CACHE_PREV_DATA.lastBlock = lastBlock + CACHE_PREV_DATA.mempoolSize = mempoolSize + CACHE_PREV_DATA.timeFromBlock = timeFromBlock + CACHE_PREV_DATA.timeFromBlockDiff = timeFromBlockDiff + } else { + logData.source = 'fromLoadCache' + link = 'prev' + } + } catch (e) { + // noinspection ES6MissingAwait + MarketingEvent.logEvent('estimate_fee_btc_load_error', { currencyCode, link, data: e.toString() }) + // do nothing + } + } + // @ts-ignore + BlocksoftCryptoLog.log('BtcNetworkPricesProvider CACHE_FEES', { + CACHE_PREV_DATA, + CACHE_PREV_PREV_DATA, ...logData + }) + + try { + const cachedWithTime = CACHE_PREV_DATA + if (timeFromBlock) { + if (timeFromBlockDiff < 1000 && cachedWithTime.fastestFee === '4') { // 1 minute from block + if (cachedWithTime.fastestFee < CACHE_PREV_PREV_DATA.fastestFee) { + cachedWithTime.fastestFee = CACHE_PREV_PREV_DATA.fastestFee + // @ts-ignore + BlocksoftCryptoLog.log('BtcNetworkPricesProvider change as block 1 minute ago and fastest no ok - used prev ', CACHE_PREV_PREV_DATA) + } else if (CACHE_PREV_DATA.mempoolSize < 10000) { + cachedWithTime.fastestFee = cachedWithTime.fastestFee * 1.5 + BlocksoftCryptoLog.log('BtcNetworkPricesProvider change as block 1 minute ago and fastest no ok - mempool is small') + } else { + cachedWithTime.fastestFee = cachedWithTime.fastestFee * 3 + BlocksoftCryptoLog.log('BtcNetworkPricesProvider change as block 1 minute ago and fastest no ok - mempool is ok') + } + } else if (timeFromBlockDiff < 5000) { // 2 minute from block + if (cachedWithTime.fastestFee < CACHE_PREV_PREV_DATA.fastestFee) { + cachedWithTime.fastestFee = CACHE_PREV_PREV_DATA.fastestFee + // @ts-ignore + BlocksoftCryptoLog.log('BtcNetworkPricesProvider change as block 5 minute ago and fastest no ok - used prev ', CACHE_PREV_PREV_DATA) + } + } + } + await this._parseLoaded(currencyCode, cachedWithTime, link) + } catch (e) { + // noinspection ES6MissingAwait + MarketingEvent.logEvent('estimate_fee_btc_parse_error', { currencyCode, link, data: e.toString() }) + // do nothing + } + // noinspection ES6MissingAwait + MarketingEvent.logEvent('estimate_fee_btc_result', logData) + return CACHE_FEES_BTC + } + + async _parseLoaded(currencyCode: string, json: { fastestFee: any; halfHourFee: any; hourFee: any; lastBlock?: number; timeFromBlock?: number; timeFromBlockDiff?: number; mempoolSize?: number }, link: string) { + + CACHE_FEES_BTC = { + 'speed_blocks_2': 0, + 'speed_blocks_6': 0, + 'speed_blocks_12': 0 + } + + const externalSettings = await BlocksoftExternalSettings.getAll('BTC.getNetworkPrices') + addMultiply(2, json.fastestFee * 1, externalSettings) + addMultiply(6, json.halfHourFee * 1, externalSettings) + addMultiply(12, json.hourFee * 1, externalSettings) + + if (CACHE_FEES_BTC.speed_blocks_2 === 1) { + CACHE_FEES_BTC.speed_blocks_2 = 4 + } + if (CACHE_FEES_BTC.speed_blocks_6 === CACHE_FEES_BTC.speed_blocks_2) { + if (CACHE_FEES_BTC.speed_blocks_12 === CACHE_FEES_BTC.speed_blocks_6) { + CACHE_FEES_BTC.speed_blocks_6 = Math.round(CACHE_FEES_BTC.speed_blocks_6 / 2) + CACHE_FEES_BTC.speed_blocks_12 = Math.round(CACHE_FEES_BTC.speed_blocks_6 / 2) + } else { + CACHE_FEES_BTC.speed_blocks_6 = Math.round(CACHE_FEES_BTC.speed_blocks_2 / 2) + } + } else if (CACHE_FEES_BTC.speed_blocks_12 === CACHE_FEES_BTC.speed_blocks_6) { + CACHE_FEES_BTC.speed_blocks_12 = Math.round(CACHE_FEES_BTC.speed_blocks_6 / 2) + } + + + if (CACHE_FEES_BTC.speed_blocks_12 === 0 || CACHE_FEES_BTC.speed_blocks_6 === 1) { + CACHE_FEES_BTC.speed_blocks_12 = 1 + if (CACHE_FEES_BTC.speed_blocks_6 === 1) { + CACHE_FEES_BTC.speed_blocks_6 = 2 + } + } + + if (CACHE_FEES_BTC.speed_blocks_6 < CACHE_FEES_BTC.speed_blocks_12) { + const t = CACHE_FEES_BTC.speed_blocks_6 + CACHE_FEES_BTC.speed_blocks_6 = CACHE_FEES_BTC.speed_blocks_12 + CACHE_FEES_BTC.speed_blocks_12 = t + } + + if (CACHE_FEES_BTC.speed_blocks_2 < CACHE_FEES_BTC.speed_blocks_6) { + const t = CACHE_FEES_BTC.speed_blocks_6 + CACHE_FEES_BTC.speed_blocks_6 = CACHE_FEES_BTC.speed_blocks_2 + CACHE_FEES_BTC.speed_blocks_2 = t + } + + if (CACHE_FEES_BTC.speed_blocks_6 < CACHE_FEES_BTC.speed_blocks_12) { + const t = CACHE_FEES_BTC.speed_blocks_6 + CACHE_FEES_BTC.speed_blocks_6 = CACHE_FEES_BTC.speed_blocks_12 + CACHE_FEES_BTC.speed_blocks_12 = t + } + + CACHE_FEES_BTC_TIME = new Date().getTime() + if (CACHE_FEES_BTC.speed_blocks_2 > 0) { + BlocksoftCryptoLog.log('BtcNetworkPricesProvider ' + currencyCode + ' new cache fees', CACHE_FEES_BTC) + } else { + // noinspection ES6MissingAwait + MarketingEvent.logEvent('estimate_fee_btc_error', { currencyCode, link, json, externalSettings }) + } + } +} + +function addMultiply(blocks, fee, externalSettings) { + const key = 'speed_blocks_' + blocks + if (typeof externalSettings['BTC_CURRENT_PRICE_' + blocks] !== 'undefined' && externalSettings['BTC_CURRENT_PRICE_' + blocks] > 0) { + CACHE_FEES_BTC[key] = externalSettings['BTC_CURRENT_PRICE_' + blocks] + } else if (typeof externalSettings['BTC_MULTI_V3_' + blocks] !== 'undefined' && externalSettings['BTC_MULTI_V3_' + blocks] > 0) { + CACHE_FEES_BTC[key] = BlocksoftUtils.mul(fee, externalSettings['BTC_MULTI_V3_' + blocks]) * 1 + } else if (typeof externalSettings.BTC_MULTI_V3 !== 'undefined' && externalSettings.BTC_MULTI_V3 > 0) { + CACHE_FEES_BTC[key] = BlocksoftUtils.mul(fee, externalSettings.BTC_MULTI_V3) * 1 + BlocksoftCryptoLog.log('BtcNetworkPricesProvider addMultiply result', { + blocks, + fee, + mul: externalSettings.BTC_MULTI_V3, + res: CACHE_FEES_BTC[key] + }) + } else { + CACHE_FEES_BTC[key] = fee + } + if (typeof externalSettings['BTC_MIN_' + blocks] !== 'undefined' && externalSettings['BTC_MIN_' + blocks] > 0) { + if (externalSettings['BTC_MIN_' + blocks] > CACHE_FEES_BTC[key]) { + CACHE_FEES_BTC[key] = externalSettings['BTC_MIN_' + blocks] + } + } else if (typeof externalSettings.BTC_MIN !== 'undefined' && externalSettings.BTC_MIN > 0) { + if (externalSettings.BTC_MIN > CACHE_FEES_BTC[key]) { + CACHE_FEES_BTC[key] = externalSettings.BTC_MIN + } + } +} diff --git a/crypto/blockchains/btc/providers/BtcUnspentsProvider.ts b/crypto/blockchains/btc/providers/BtcUnspentsProvider.ts new file mode 100644 index 000000000..dc20dc761 --- /dev/null +++ b/crypto/blockchains/btc/providers/BtcUnspentsProvider.ts @@ -0,0 +1,207 @@ +/** + * @version 0.20 + * https://github.com/trezor/blockbook/blob/master/docs/api.md + * https://doge1.trezor.io/api/v2/utxo/D5oKvWEibVe74CXLASmhpkRpLoyjgZhm71 + */ +import { BlocksoftBlockchainTypes } from '../../BlocksoftBlockchainTypes' +import DogeUnspentsProvider from '../../doge/providers/DogeUnspentsProvider' + +import Database from '@app/appstores/DataSource/Database'; +import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog' +import BlocksoftDict from '@crypto/common/BlocksoftDict' +import main from '@app/appstores/DataSource/Database' + +const CACHE_FOR_CHANGE = {} + +export default class BtcUnspentsProvider extends DogeUnspentsProvider implements BlocksoftBlockchainTypes.UnspentsProvider { + + static async getCache(walletHash : string, currencyCode = 'BTC') { + if (typeof CACHE_FOR_CHANGE[walletHash] !== 'undefined') { + return CACHE_FOR_CHANGE[walletHash] + } + const mainCurrencyCode = currencyCode === 'LTC' ? 'LTC' : 'BTC' + const segwitPrefix = BlocksoftDict.CurrenciesForTests[mainCurrencyCode + '_SEGWIT'].addressPrefix + + BlocksoftCryptoLog.log(currencyCode + ' ' + mainCurrencyCode + ' BtcUnspentsProvider.getCache ' + walletHash + ' started as ' + JSON.stringify(CACHE_FOR_CHANGE[walletHash])) + + const sqlPub = `SELECT wallet_pub_value as walletPub + FROM wallet_pub + WHERE wallet_hash = '${walletHash} + AND currency_code='${mainCurrencyCode}' + ` + const resPub = await Database.query(sqlPub) + if (resPub && resPub.array && resPub.array.length > 0) { + + const sql = `SELECT account.address + FROM account + WHERE account.wallet_hash = '${walletHash} + AND currency_code='${mainCurrencyCode}' AND (already_shown IS NULL OR already_shown=0) + AND derivation_type!='main' + ORDER BY derivation_index ASC + ` + const res = await Database.query(sql) + for (const row of res.array) { + const prefix = row.address.indexOf(segwitPrefix) === 0 ? segwitPrefix : row.address.substr(0, 1) + await BlocksoftCryptoLog.log(currencyCode + ' ' + mainCurrencyCode + ' BtcUnspentsProvider.getCache started HD CACHE_FOR_CHANGE ' + walletHash) + // @ts-ignore + if (typeof CACHE_FOR_CHANGE[walletHash] === 'undefined') { + // @ts-ignore + CACHE_FOR_CHANGE[walletHash] = {} + } + // @ts-ignore + if (typeof CACHE_FOR_CHANGE[walletHash][prefix] === 'undefined' || CACHE_FOR_CHANGE[walletHash][prefix] === '') { + // @ts-ignore + CACHE_FOR_CHANGE[walletHash][prefix] = row.address + // @ts-ignore + await BlocksoftCryptoLog.log(currencyCode + ' ' + mainCurrencyCode + ' BtcUnspentsProvider.getCache started HD CACHE_FOR_CHANGE ' + + walletHash + ' ' + prefix + ' changed ' + JSON.stringify(CACHE_FOR_CHANGE[walletHash])) + } + } + + } else { + + const sql = `SELECT account.address + FROM account + WHERE account.wallet_hash = '${walletHash}' + AND currency_code='${mainCurrencyCode}' + ` + const res = await Database.query(sql) + for (const row of res.array) { + // @ts-ignore + await BlocksoftCryptoLog.log(currencyCode + '/' + mainCurrencyCode + ' BtcUnspentsProvider.getUnspents started CACHE_FOR_CHANGE ' + walletHash) + if (typeof CACHE_FOR_CHANGE[walletHash] === 'undefined') { + // @ts-ignore + CACHE_FOR_CHANGE[walletHash] = {} + } + const prefix = row.address.indexOf(segwitPrefix) === 0 ? segwitPrefix : row.address.substr(0, 1) + // @ts-ignore + CACHE_FOR_CHANGE[walletHash][prefix] = row.address + } + } + if (typeof CACHE_FOR_CHANGE[walletHash] === 'undefined') { + throw new Error(currencyCode + '/' + mainCurrencyCode + ' BtcUnspentsProvider no CACHE_FOR_CHANGE retry for ' + walletHash) + } + return CACHE_FOR_CHANGE[walletHash] + } + + _isMyAddress(voutAddress: string, address: string, walletHash: string): string { + // @ts-ignore + if (typeof CACHE_FOR_CHANGE[walletHash] === 'undefined' || !CACHE_FOR_CHANGE[walletHash]) { + return '' + } + // @ts-ignore + let found = '' + for (const key in CACHE_FOR_CHANGE[walletHash]) { + BlocksoftCryptoLog.log('CACHE_FOR_CHANGE[walletHash][key]', key + '_' + CACHE_FOR_CHANGE[walletHash][key]) + if (voutAddress === CACHE_FOR_CHANGE[walletHash][key]) { + found = voutAddress + } + } + return found + } + + async getUnspents(address: string): Promise { + const mainCurrencyCode = this._settings.currencyCode === 'LTC' ? 'LTC' : 'BTC' + const segwitPrefix = BlocksoftDict.CurrenciesForTests[mainCurrencyCode + '_SEGWIT'].addressPrefix + + const sqlPub = `SELECT wallet_pub_value as walletPub + FROM wallet_pub + WHERE wallet_hash = (SELECT wallet_hash FROM account WHERE address='${address}') + AND currency_code='${mainCurrencyCode}' + ` + const totalUnspents = [] + const resPub = await Database.query(sqlPub) + if (resPub && resPub.array && resPub.array.length > 0) { + for (const row of resPub.array) { + const unspents = await super.getUnspents(row.walletPub) + if (unspents) { + for (const unspent of unspents) { + totalUnspents.push(unspent) + } + } + } + const sqlAdditional = `SELECT account.address, account.derivation_path as derivationPath, wallet_hash AS walletHash + FROM account + WHERE account.wallet_hash = (SELECT wallet_hash FROM account WHERE address='${address}') + AND account.derivation_path = 'm/49quote/0quote/0/1/0' + AND currency_code='${mainCurrencyCode}' + ` + const resAdditional = await Database.query(sqlAdditional) + if (resAdditional && resAdditional.array && resAdditional.array.length > 0) { + for (const row of resAdditional.array) { + const unspents = await super.getUnspents(row.address) + if (unspents) { + for (const unspent of unspents) { + unspent.address = row.address + unspent.derivationPath = Database.unEscapeString(row.derivationPath) + totalUnspents.push(unspent) + } + } + } + } + + const sql = `SELECT account.address, account.derivation_path as derivationPath, wallet_hash AS walletHash + FROM account + WHERE account.wallet_hash = (SELECT wallet_hash FROM account WHERE address='${address}') + AND currency_code='${mainCurrencyCode}' AND (already_shown IS NULL OR already_shown=0) + AND derivation_type!='main' + ORDER BY derivation_index ASC + ` + const res = await Database.query(sql) + for (const row of res.array) { + const walletHash = row.walletHash + const prefix = row.address.indexOf(segwitPrefix) === 0 ? segwitPrefix : row.address.substr(0, 1) + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' ' + mainCurrencyCode + ' BtcUnspentsProvider.getUnspents started HD CACHE_FOR_CHANGE ' + address + ' walletHash ' + walletHash) + // @ts-ignore + if (typeof CACHE_FOR_CHANGE[walletHash] === 'undefined') { + // @ts-ignore + CACHE_FOR_CHANGE[walletHash] = {} + } + // @ts-ignore + if (typeof CACHE_FOR_CHANGE[walletHash][prefix] === 'undefined' || CACHE_FOR_CHANGE[walletHash][prefix] === '') { + // @ts-ignore + CACHE_FOR_CHANGE[walletHash][prefix] = row.address + // @ts-ignore + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' ' + mainCurrencyCode + ' BtcUnspentsProvider.getUnspents started HD CACHE_FOR_CHANGE ' + + address + ' walletHash ' + walletHash + ' ' + prefix + ' changed ' + JSON.stringify(CACHE_FOR_CHANGE[walletHash])) + } + } + + } else { + + const sql = `SELECT account.address, account.derivation_path as derivationPath, wallet_hash AS walletHash + FROM account + WHERE account.wallet_hash = (SELECT wallet_hash FROM account WHERE address='${address}') + AND currency_code='${mainCurrencyCode}' + ` + const res = await Database.query(sql) + for (const row of res.array) { + const walletHash = row.walletHash + const unspents = await super.getUnspents(row.address) + // @ts-ignore + await BlocksoftCryptoLog.log(this._settings.currencyCode + '/' + mainCurrencyCode + ' BtcUnspentsProvider.getUnspents started CACHE_FOR_CHANGE ' + address + ' ' + row.address + ' walletHash ' + walletHash) + if (typeof CACHE_FOR_CHANGE[walletHash] === 'undefined') { + // @ts-ignore + CACHE_FOR_CHANGE[walletHash] = {} + } + const prefix = row.address.indexOf(segwitPrefix) === 0 ? segwitPrefix : row.address.substr(0, 1) + // @ts-ignore + CACHE_FOR_CHANGE[walletHash][prefix] = row.address + if (unspents) { + for (const unspent of unspents) { + unspent.address = row.address + unspent.derivationPath = Database.unEscapeString(row.derivationPath) + totalUnspents.push(unspent) + } + } + } + } + // @ts-ignore + if (totalUnspents.length > 10) { + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' ' + mainCurrencyCode + ' BtcUnspentsProvider.getUnspents finished ' + address + ' total ' + totalUnspents.length, totalUnspents.slice(0, 10)) + } else { + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' ' + mainCurrencyCode + ' BtcUnspentsProvider.getUnspents finished ' + address + ' total ' + totalUnspents.length, totalUnspents) + } + return totalUnspents + } +} diff --git a/crypto/blockchains/btc/tx/BtcTxBuilder.ts b/crypto/blockchains/btc/tx/BtcTxBuilder.ts new file mode 100644 index 000000000..8eae14e40 --- /dev/null +++ b/crypto/blockchains/btc/tx/BtcTxBuilder.ts @@ -0,0 +1,161 @@ +/** + * @version 0.20 + */ +import { BlocksoftBlockchainTypes } from '../../BlocksoftBlockchainTypes' +import DogeTxBuilder from '../../doge/tx/DogeTxBuilder' +import BlocksoftPrivateKeysUtils from '../../../common/BlocksoftPrivateKeysUtils' +import { ECPair, payments, TransactionBuilder } from 'bitcoinjs-lib' +import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog' +import BlocksoftDict from '@crypto/common/BlocksoftDict' +import main from '@app/appstores/DataSource/Database' + +export default class BtcTxBuilder extends DogeTxBuilder implements BlocksoftBlockchainTypes.TxBuilder { + private mnemonic: string = '' + private walletHash : string = '' + private keyPairBTC : any = {} + private p2wpkhBTC: any = {} + private p2shBTC: any ={} + + + _getRawTxValidateKeyPair(privateData: BlocksoftBlockchainTypes.TransferPrivateData, data: BlocksoftBlockchainTypes.TransferData): void { + if (this.mnemonic === privateData.privateKey) return + + this.mnemonic = privateData.privateKey + this.walletHash = data.walletHash + this.keyPairBTC = {} + this.p2wpkhBTC = {} + this.p2shBTC = {} + } + + async _getRawTxAddInput(txb: TransactionBuilder, i: number, input: BlocksoftBlockchainTypes.UnspentTx, nSequence: number): Promise { + if (typeof input.address === 'undefined') { + throw new Error('no address in input ' + JSON.stringify(input)) + } + + if (!input.derivationPath || input.derivationPath === "false") { + // @ts-ignore + if (typeof input.path !== 'undefined') { + // @ts-ignore + input.derivationPath = input.path + } + } + + // @ts-ignore + const mainCurrencyCode = this._settings.currencyCode === 'LTC' ? 'LTC' : 'BTC' + + const segwitPrefix = BlocksoftDict.CurrenciesForTests[mainCurrencyCode + '_SEGWIT'].addressPrefix + const segwitCompatiblePrefix = typeof (BlocksoftDict.CurrenciesForTests[mainCurrencyCode + '_SEGWIT_COMPATIBLE']) !== 'undefined' ? + BlocksoftDict.CurrenciesForTests[mainCurrencyCode + '_SEGWIT_COMPATIBLE'].addressPrefix : false + + if (typeof this.keyPairBTC[input.address] === 'undefined') { + let currencyCode = mainCurrencyCode + if (input.address.indexOf(segwitPrefix) === 0) { + currencyCode += '_SEGWIT' + if (!input.derivationPath || input.derivationPath === "false") { + input.derivationPath = BlocksoftDict.CurrenciesForTests[currencyCode].defaultPath + } + } else if (segwitCompatiblePrefix && input.address.indexOf(segwitCompatiblePrefix) === 0) { + currencyCode += '_SEGWIT_COMPATIBLE' + if (!input.derivationPath || input.derivationPath === "false") { + input.derivationPath = BlocksoftDict.CurrenciesForTests[currencyCode].defaultPath + } + } else if (!input.derivationPath || input.derivationPath === "false") { + input.derivationPath = BlocksoftDict.CurrenciesForTests[mainCurrencyCode].defaultPath + } + const discoverFor = { + mnemonic: this.mnemonic, + addressToCheck: input.address, + walletHash: this.walletHash, + derivationPath: input.derivationPath, + currencyCode + } + const result = await BlocksoftPrivateKeysUtils.getPrivateKey(discoverFor, 'BtcTxBuilder') + + try { + this.keyPairBTC[input.address] = ECPair.fromWIF(result.privateKey, this._bitcoinNetwork) + let address + if (currencyCode === mainCurrencyCode + '_SEGWIT') { + try { + this.p2wpkhBTC[input.address] = payments.p2wpkh({ + pubkey: this.keyPairBTC[input.address].publicKey, + network: this._bitcoinNetwork + }) + } catch (e) { + e.message += ' in privateKey ' + currencyCode + ' Segwit signature create' + // noinspection ExceptionCaughtLocallyJS + throw e + } + + if (typeof this.p2wpkhBTC[input.address].address === 'undefined') { + // noinspection ExceptionCaughtLocallyJS + throw new Error('not valid ' + currencyCode + ' segwit p2sh') + } + address = this.p2wpkhBTC[input.address].address + } else if (currencyCode === mainCurrencyCode + '_SEGWIT_COMPATIBLE') { + try { + this.p2wpkhBTC[input.address] = payments.p2wpkh({ + pubkey: this.keyPairBTC[input.address].publicKey, + network: this._bitcoinNetwork + }) + this.p2shBTC[input.address] = payments.p2sh({ + redeem: this.p2wpkhBTC[input.address], + network: this._bitcoinNetwork + }) + } catch (e) { + e.message += ' in privateKey ' + currencyCode + ' SegwitCompatible signature create' + // noinspection ExceptionCaughtLocallyJS + throw e + } + if (typeof this.p2shBTC[input.address].address === 'undefined') { + // noinspection ExceptionCaughtLocallyJS + throw new Error('not valid ' + currencyCode + ' segwit compatible p2sh') + } + address = this.p2shBTC[input.address].address + } else { + address = payments.p2pkh({ + pubkey: this.keyPairBTC[input.address].publicKey, + network: this._bitcoinNetwork + }).address + } + + if (address !== input.address) { + // noinspection ExceptionCaughtLocallyJS + throw new Error('not valid ' + currencyCode + ' signing path ' + input.derivationPath + ' address ' + input.address + ' != ' + address + ' segwit type = ' + currencyCode) + } + } catch (e) { + e.message += ' in privateKey ' + currencyCode + ' signature check ' + throw e + } + } + + if (typeof this.p2wpkhBTC[input.address] === 'undefined') { + txb.addInput(input.txid, input.vout, nSequence) + } else if (typeof this.p2shBTC[input.address] === 'undefined') { + txb.addInput(input.txid, input.vout, nSequence, this.p2wpkhBTC[input.address].output) + } else { + txb.addInput(input.txid, input.vout, nSequence) + } + } + + async _getRawTxSign(txb: TransactionBuilder, i: number, input: BlocksoftBlockchainTypes.UnspentTx): Promise { + if (typeof input.address === 'undefined') { + throw new Error('no address in input ' + JSON.stringify(input)) + } + if (typeof this.p2wpkhBTC[input.address] === 'undefined') { + // @ts-ignore + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' BtcTxBuilder.getRawTx sign usual', input) + // @ts-ignore + txb.sign(i, this.keyPairBTC[input.address], null, null, input.value * 1) + } else if (typeof this.p2shBTC[input.address] === 'undefined') { + // @ts-ignore + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' BtcTxBuilder.getRawTx sign segwit', input) + // @ts-ignore + txb.sign(i, this.keyPairBTC[input.address], null, null, input.value * 1) + } else { + // @ts-ignore + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' BtcTxBuilder.getRawTx sign segwit compatible', input) + // @ts-ignore + txb.sign(i, this.keyPairBTC[input.address], this.p2shBTC[input.address].redeem.output, null, input.value * 1) + } + } +} diff --git a/crypto/blockchains/btc/tx/BtcTxInputsOutputs.ts b/crypto/blockchains/btc/tx/BtcTxInputsOutputs.ts new file mode 100644 index 000000000..b7a3c2cd6 --- /dev/null +++ b/crypto/blockchains/btc/tx/BtcTxInputsOutputs.ts @@ -0,0 +1,130 @@ +/** + * @version 0.20 + */ +import { BlocksoftBlockchainTypes } from '../../BlocksoftBlockchainTypes' +import BtcUnspentsProvider from '../providers/BtcUnspentsProvider' +import DogeTxInputsOutputs from '../../doge/tx/DogeTxInputsOutputs' +import settingsActions from '../../../../app/appstores/Stores/Settings/SettingsActions' +import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog' +import DaemonCache from '../../../../app/daemons/DaemonCache' +import BlocksoftDict from '@crypto/common/BlocksoftDict' + +export default class BtcTxInputsOutputs extends DogeTxInputsOutputs implements BlocksoftBlockchainTypes.TxInputsOutputs { + + async _addressForChange(data: BlocksoftBlockchainTypes.TransferData): string { + const btcShowTwoAddress = await settingsActions.getSetting('btcShowTwoAddress') + const btcLegacyOrSegwit = await settingsActions.getSetting('btc_legacy_or_segwit') + + const mainCurrencyCode = this._settings.currencyCode === 'LTC' ? 'LTC' : 'BTC' + const legacyPrefix = BlocksoftDict.Currencies[mainCurrencyCode].addressPrefix + const segwitPrefix = BlocksoftDict.CurrenciesForTests[mainCurrencyCode + '_SEGWIT'].addressPrefix + + let needFindSegwit = true + if (btcShowTwoAddress === '1' || data.useLegacy === 1) { + // @todo as btcShowTwoAddress this will be deprecated simplify the code + // its only for wallets with old setting of two addresses where there was useLegacy on + // console.log('will legacy') + needFindSegwit = false + } else if (btcShowTwoAddress === '1' || btcLegacyOrSegwit === 'segwit') { + needFindSegwit = true + } else if (btcLegacyOrSegwit === 'legacy') { + needFindSegwit = false + // console.log('will legacy 2') + } + + BlocksoftCryptoLog.log('BtcTxInputsOutputs needFindSegwit ' + JSON.stringify(needFindSegwit)) + try { + const CACHE_FOR_CHANGE = await BtcUnspentsProvider.getCache(data.walletHash, this._settings.currencyCode) + BlocksoftCryptoLog.log('BtcTxInputsOutputs CACHE_FOR_CHANGE ' + data.walletHash, CACHE_FOR_CHANGE) + + let addressForChange = false + if (needFindSegwit) { + addressForChange = CACHE_FOR_CHANGE[segwitPrefix] + } else { + addressForChange = CACHE_FOR_CHANGE[legacyPrefix] + } + // @ts-ignore + BlocksoftCryptoLog.log(this._settings.currencyCode + ' ' + mainCurrencyCode + ' BtcTxInputsOutputs _addressForChange addressForChange logic ', { + needFindSegwit, + addressForChange, + CACHE: CACHE_FOR_CHANGE + }) + if (addressForChange && addressForChange !== '') { + return addressForChange + } + } catch (e) { + BlocksoftCryptoLog.err(this._settings.currencyCode + ' ' + mainCurrencyCode + ' BtcTxInputsOutputs _addressForChange error ' + e.message) + } + + return data.addressFrom + } + + async getInputsOutputs(data: BlocksoftBlockchainTypes.TransferData, unspents: BlocksoftBlockchainTypes.UnspentTx[], + feeToCount: { feeForByte?: string, feeForAll?: string, autoFeeLimitReadable?: string | number }, + additionalData : BlocksoftBlockchainTypes.TransferAdditionalData, + subtitle: string = 'default') + : Promise + { + const res = await super._getInputsOutputs(data, unspents, feeToCount, additionalData, subtitle + ' btced') + + if (this._settings.currencyCode !== 'BTC') { + return res + } + + const tmp = DaemonCache.getCacheAccountStatic(data.walletHash, 'USDT') + if (tmp.balance === '0' || tmp.balance === 0) { + return res + } + + let usdtCount = 0 + for (const unspent of unspents) { + if (unspent.address === tmp.address) { + usdtCount++ + } + } + if (usdtCount === 0) { + res.outputs.push({ to: tmp.address, amount: '546', isChange: true, logType : 'FOR_LEGACY_USDT_KEEP_FROM_BTC' }) + return res + } + + let usdtUsed = 0 + for (const input of res.inputs) { + if (input.address === tmp.address) { + usdtUsed++ + } + } + BlocksoftCryptoLog.log('BtxTxInputsOutputs for ' + tmp.address + ' usdtUsed ' + usdtUsed + ' usdtCount ' + usdtCount) + + if (usdtUsed >= usdtCount) { + let found = false + for (const input of res.inputs) { + if (input.address === tmp.address && !found && input.value === '546') { + input.value = '0' + found = true + } + } + if (!found) { + for (const input of res.inputs) { + if (input.address === tmp.address) { + res.outputs.push({ to: tmp.address, amount: '546', isChange: true, logType : 'FOR_LEGACY_USDT_KEEP_FROM_BTC' }) + break + } + } + } + + if (found) { + const inputs = [] + for (const input of res.inputs) { + if (input.value !== '0') { + inputs.push(input) + } + } + res.inputs = inputs + } + } + + res.countedFor = 'BTC' + + return res + } +} diff --git a/crypto/blockchains/btc_test/BtcTestScannerProcessor.js b/crypto/blockchains/btc_test/BtcTestScannerProcessor.js new file mode 100644 index 000000000..cabd65cb9 --- /dev/null +++ b/crypto/blockchains/btc_test/BtcTestScannerProcessor.js @@ -0,0 +1,114 @@ +/** + * @version 0.52 + * https://github.com/Blockstream/esplora/blob/master/API.md + */ +import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog' +import BlocksoftAxios from '@crypto/common/BlocksoftAxios' +import BlocksoftUtils from '@crypto/common/BlocksoftUtils' +import DogeFindAddressFunction from '@crypto/blockchains/doge/basic/DogeFindAddressFunction' + +const API_PATH = 'https://blockstream.info/testnet/api/' + +export default class BtcTestScannerProcessor { + + + /* + * https://blockstream.info/testnet/api/address/mtU4mYXfBRiTx1iUBWcCvUTr4CgRnRALaL + * @param {string} address + * @return {Promise<{int:balance, int:provider}>} + */ + async getBalanceBlockchain(address) { + BlocksoftCryptoLog.log('BtcTestScannerProcessor.getBalance started ' + address) + const res = await BlocksoftAxios.getWithoutBraking(API_PATH + 'address/' + address) + // console.log('res', res.data.chain_stats.funded_txo_sum) // spent_txo_sum + if (!res || typeof res.data === 'undefined' || typeof res.data.chain_stats === 'undefined' || typeof res.data.chain_stats.funded_txo_sum === 'undefined') { + return false + } + return { balance: res.data.chain_stats.funded_txo_sum, unconfirmed: 0, provider: 'blockstream.info' } + } + + /** + * https://blockstream.info/testnet/api/address/mtU4mYXfBRiTx1iUBWcCvUTr4CgRnRALaL/txs + * @param {string} scanData.account.address + * @return {Promise} + */ + async getTransactionsBlockchain(scanData) { + const address = scanData.account.address.trim() + BlocksoftCryptoLog.log('BtcTestScannerProcessor.getTransactions started ' + address) + const res = await BlocksoftAxios.getWithoutBraking(API_PATH + 'address/' + address + '/txs') + if (!res || typeof res.data === 'undefined' || !res.data) { + return [] + } + const transactions = [] + let tx + for (tx of res.data) { + const transaction = await this._unifyTransaction(address, tx) + transactions.push(transaction) + } + BlocksoftCryptoLog.log('BtcTestScannerProcessor.getTransactions finished ' + address + ' total: ' + transactions.length) + return transactions + } + + /** + * + * @param {string} address + * @param {Object} transaction + * @return {Promise} + * @private + */ + async _unifyTransaction(address, transaction) { + + let showAddresses = false + try { + showAddresses = await DogeFindAddressFunction([address], transaction) + } catch (e) { + e.message += ' transaction hash ' + JSON.stringify(transaction) + ' address ' + address + throw e + } + let transactionStatus = 'new' + let blockConfirmations = 0 + let blockHash = '' + let blockNumber = '' + let formattedTime = 0 + if (typeof transaction.status !== 'undefined') { + if (typeof transaction.status.block_hash !== 'undefined') { + blockHash = transaction.status.block_hash + } + if (typeof transaction.status.block_height !== 'undefined') { + blockNumber = transaction.status.block_height + } + if (typeof transaction.status.confirmed !== 'undefined') { + if (transaction.status.confirmed) { + transactionStatus = 'success' + blockConfirmations = 100 + } else { + transactionStatus = 'confirming' + } + } + if (typeof transaction.status.block_time !== 'undefined') { + try { + formattedTime = BlocksoftUtils.toDate(transaction.status.block_time) + } catch (e) { + e.message += ' timestamp error transaction data ' + JSON.stringify(transaction) + throw e + } + } + } + + + + return { + transactionHash: transaction.txid, + blockHash, + blockNumber, + blockTime: formattedTime, + blockConfirmations, + transactionDirection: showAddresses.direction, + addressFrom: showAddresses.from, + addressTo: showAddresses.to, + addressAmount: showAddresses.value, + transactionStatus: transactionStatus, + transactionFee: transaction.fee + } + } +} diff --git a/crypto/blockchains/btc_test/BtcTestTransferProcessor.ts b/crypto/blockchains/btc_test/BtcTestTransferProcessor.ts new file mode 100644 index 000000000..551ed4d15 --- /dev/null +++ b/crypto/blockchains/btc_test/BtcTestTransferProcessor.ts @@ -0,0 +1,39 @@ +/** + * @version 0.52 + */ +import { BlocksoftBlockchainTypes } from '@crypto/blockchains/BlocksoftBlockchainTypes' +import DogeTransferProcessor from '@crypto/blockchains/doge/DogeTransferProcessor' +import BtcTestUnspentsProvider from '@crypto/blockchains/btc_test/providers/BtcTestUnspentsProvider' +import BtcTestSendProvider from '@crypto/blockchains/btc_test/providers/BtcTestSendProvider' +import DogeTxInputsOutputs from '@crypto/blockchains/doge/tx/DogeTxInputsOutputs' +import DogeTxBuilder from '@crypto/blockchains/doge/tx/DogeTxBuilder' + +export default class BtcTestTransferProcessor extends DogeTransferProcessor implements BlocksoftBlockchainTypes.TransferProcessor { + + _trezorServerCode = 'NONE' + + _builderSettings: BlocksoftBlockchainTypes.BuilderSettings = { + minOutputDustReadable: 0.000005, + minChangeDustReadable: 0.00001, + feeMaxForByteSatoshi: 10000, // for tx builder + feeMaxAutoReadable2: 0.2, // for fee calc, + feeMaxAutoReadable6: 0.1, // for fee calc + feeMaxAutoReadable12: 0.05, // for fee calc + changeTogether: true, + minRbfStepSatoshi: 10, + minSpeedUpMulti : 1.5 + } + + canRBF(data: BlocksoftBlockchainTypes.DbAccount, transaction: BlocksoftBlockchainTypes.DbTransaction): boolean { + return false + } + + _initProviders() { + if (this._initedProviders) return false + this.unspentsProvider = new BtcTestUnspentsProvider(this._settings, this._trezorServerCode) + this.sendProvider = new BtcTestSendProvider(this._settings, this._trezorServerCode) + this.txPrepareInputsOutputs = new DogeTxInputsOutputs(this._settings, this._builderSettings) + this.txBuilder = new DogeTxBuilder(this._settings, this._builderSettings) + this._initedProviders = true + } +} diff --git a/crypto/blockchains/btc_test/providers/BtcTestSendProvider.ts b/crypto/blockchains/btc_test/providers/BtcTestSendProvider.ts new file mode 100644 index 000000000..1dffcdcf3 --- /dev/null +++ b/crypto/blockchains/btc_test/providers/BtcTestSendProvider.ts @@ -0,0 +1,80 @@ +/** + * @version 0.52 + */ +import { BlocksoftBlockchainTypes } from '@crypto/blockchains/BlocksoftBlockchainTypes' + +import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog' +import BlocksoftAxios from '@crypto/common/BlocksoftAxios' +import DogeSendProvider from '@crypto/blockchains/doge/providers/DogeSendProvider' +import config from '@app/config/config' + +const API_URL = 'https://api.blockchair.com/bitcoin/testnet/push/transaction' + +export default class BtcTestSendProvider extends DogeSendProvider implements BlocksoftBlockchainTypes.SendProvider { + + async sendTx(hex: string, subtitle: string, txRBF : any, logData : any) : Promise<{transactionHash: string, transactionJson:any, logData: any}> { + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' BtcTestSendProvider.sendTx ' + subtitle + ' started ', logData) + + // logData = await this._check(hex, subtitle, txRBF, logData) + + let res + try { + res = await BlocksoftAxios.post(API_URL, {data : hex}) + } catch (e) { + if (config.debug.cryptoErrors) { + console.log(this._settings.currencyCode + ' BtcTestSendProvider.sendTx error ', e) + } + try { + logData.error = e.message + // await this._checkError(hex, subtitle, txRBF, logData) + } catch (e2) { + if (config.debug.cryptoErrors) { + console.log(this._settings.currencyCode + ' BtcTestSendProvider.send proxy error errorTx ' + e.message) + } + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' BtcTestSendProvider.send proxy error errorTx ' + e2.message) + } + if (e.message.indexOf('transaction already in the mempool') !== -1 || e.message.indexOf('TXN-MEMPOOL-CONFLICT')) { + throw new Error('SERVER_RESPONSE_NO_RESPONSE') + } else if (e.message.indexOf('dust') !== -1) { + throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_DUST') + } else if (e.message.indexOf('bad-txns-inputs-spent') !== -1 || e.message.indexOf('txn-mempool-conflict') !== -1) { + throw new Error('SERVER_RESPONSE_NO_RESPONSE') + } else if (e.message.indexOf('fee for relay') !== -1 || e.message.indexOf('insufficient priority') !== -1) { + throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_FEE') + } else { + throw e + } + } + let txid = '' + // @ts-ignore + if (typeof res.data === 'undefined' || !res.data) { + throw new Error('SERVER_RESPONSE_NOT_CONNECTED') + } + // @ts-ignore + if (typeof res.data !== 'undefined' && typeof res.data.txid !== 'undefined') { + // @ts-ignore + txid = res.data.txid + } + // @ts-ignore + if (typeof res.data.data !== 'undefined') { + // @ts-ignore + if (typeof res.data.data.transaction_hash !== 'undefined') { + // @ts-ignore + txid = res.data.data.transaction_hash + } + } + if (txid === '') { + if (config.debug.cryptoErrors) { + // @ts-ignore + console.log(this._settings.currencyCode + ' BtcTestSendProvider.send no txid', res.data) + } + throw new Error('SERVER_RESPONSE_NOT_CONNECTED') + } + + // logData = await this._checkSuccess(txid, hex, subtitle, txRBF, logData) + + return {transactionHash : txid, transactionJson: {}, logData } + } +} + + diff --git a/crypto/blockchains/btc_test/providers/BtcTestUnspentsProvider.ts b/crypto/blockchains/btc_test/providers/BtcTestUnspentsProvider.ts new file mode 100644 index 000000000..fd42175fa --- /dev/null +++ b/crypto/blockchains/btc_test/providers/BtcTestUnspentsProvider.ts @@ -0,0 +1,61 @@ +/** + * @version 0.52 + */ +import { BlocksoftBlockchainTypes } from '@crypto/blockchains/BlocksoftBlockchainTypes' + +import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog' +import BlocksoftAxios from '@crypto/common/BlocksoftAxios' + +const API_PATH = 'https://blockstream.info/testnet/api/' + +export default class BtcTestUnspentsProvider implements BlocksoftBlockchainTypes.UnspentsProvider { + + protected _settings: BlocksoftBlockchainTypes.CurrencySettings + + constructor(settings: BlocksoftBlockchainTypes.CurrencySettings, serverCode: string) { + this._settings = settings + } + + /** + * https://blockstream.info/testnet/api/address/mtU4mYXfBRiTx1iUBWcCvUTr4CgRnRALaL/utxo + * @param address + */ + async getUnspents(address: string): Promise { + // @ts-ignore + BlocksoftCryptoLog.log(this._settings.currencyCode + ' BtcTestUnspentsProvider.getUnspents started', address) + + const link = API_PATH + `address/${address}/utxo` + const res = await BlocksoftAxios.getWithoutBraking(link) + + if (!res || typeof res.data === 'undefined') { + BlocksoftCryptoLog.log(this._settings.currencyCode + ' BtcTestUnspentsProvider.getUnspents nothing loaded for address ' + address + ' link ' + link) + throw new Error('SERVER_RESPONSE_NOT_CONNECTED') + } + if (!res.data || typeof res.data.length === 'undefined' || !res.data || !res.data.length) { + return [] + } + const sortedUnspents = [] + /** + * @param {*} res.data[] + * @param {string} res.data[].txid + * @param {string} res.data[].vout + * @param {string} res.data[].status + * @param {string} res.data[].status.confirmed + * @param {string} res.data[].status.block_height + * @param {string} res.data[].status.block_hash + * @param {string} res.data[].status.block_time + * @param {string} res.data[].value + */ + for (const unspent of res.data) { + sortedUnspents.push({ + txid: unspent.txid, + vout: typeof unspent.vout === 'undefined' ? 0 : unspent.vout, + value: unspent.value.toString(), + height: 0, + confirmations : typeof unspent.status !== 'undefined' && typeof unspent.status.confirmed !== 'undefined' && unspent.status.confirmed ? 100 : 0, + isRequired : false + }) + } + return sortedUnspents + } +} diff --git a/crypto/blockchains/btg/BtgScannerProcessor.js b/crypto/blockchains/btg/BtgScannerProcessor.js new file mode 100644 index 000000000..fda35b4f5 --- /dev/null +++ b/crypto/blockchains/btg/BtgScannerProcessor.js @@ -0,0 +1,21 @@ +/** + * @version 0.5 + * https://github.com/trezor/blockbook/blob/master/docs/api.md + */ + +import DogeScannerProcessor from '../doge/DogeScannerProcessor' + +export default class BtgScannerProcessor extends DogeScannerProcessor { + + /** + * @type {number} + * @private + */ + _blocksToConfirm = 10 + + /** + * @type {string} + * @private + */ + _trezorServerCode = 'BTG_TREZOR_SERVER' +} diff --git a/crypto/blockchains/btg/BtgTransferProcessor.ts b/crypto/blockchains/btg/BtgTransferProcessor.ts new file mode 100644 index 000000000..fd7ffb5e8 --- /dev/null +++ b/crypto/blockchains/btg/BtgTransferProcessor.ts @@ -0,0 +1,31 @@ +/** + * @version 0.20 + */ + +import DogeTransferProcessor from '../doge/DogeTransferProcessor' +import { BlocksoftBlockchainTypes } from '../BlocksoftBlockchainTypes' + +export default class BtgTransferProcessor extends DogeTransferProcessor implements BlocksoftBlockchainTypes.TransferProcessor { + + /** + * @type {string} + * @private + */ + _trezorServerCode = 'BTG_TREZOR_SERVER' + + _builderSettings: BlocksoftBlockchainTypes.BuilderSettings = { + minOutputDustReadable: 0.00001, + minChangeDustReadable: 0.00001, + feeMaxForByteSatoshi: 10000, // for tx builder + feeMaxAutoReadable2: 1, // for fee calc, + feeMaxAutoReadable6: 0.5, // for fee calc + feeMaxAutoReadable12: 0.2, // for fee calc + changeTogether: true, + minRbfStepSatoshi: 10, + minSpeedUpMulti : 1.5 + } + + canRBF(data: BlocksoftBlockchainTypes.DbAccount, transaction: BlocksoftBlockchainTypes.DbTransaction): boolean { + return false + } +} diff --git a/crypto/blockchains/doge/DogeScannerProcessor.js b/crypto/blockchains/doge/DogeScannerProcessor.js new file mode 100644 index 000000000..b6a629f1e --- /dev/null +++ b/crypto/blockchains/doge/DogeScannerProcessor.js @@ -0,0 +1,253 @@ +/** + * @version 0.5 + * https://github.com/trezor/blockbook/blob/master/docs/api.md + * https://doge1.trezor.io/api/v2/address/D5oKvWEibVe74CXLASmhpkRpLoyjgZhm71?details=txs + * + * @typedef {Object} UnifiedTransaction + * @property {*} transactionHash + * @property {*} blockHash + * @property {*} blockNumber + * @property {*} blockTime + * @property {*} blockConfirmations + * @property {*} transactionDirection + * @property {*} addressFrom + * @property {*} addressTo + * @property {*} addressAmount + * @property {*} transactionStatus + * @property {*} transactionFee + * @property {*} transactionFeeCurrencyCode + * @property {*} contractAddress + * @property {*} inputValue + * @property {*} transactionJson + */ +import BlocksoftUtils from '../../common/BlocksoftUtils' +import BlocksoftAxios from '../../common/BlocksoftAxios' +import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog' + +import DogeFindAddressFunction from './basic/DogeFindAddressFunction' +import BlocksoftExternalSettings from '../../common/BlocksoftExternalSettings' +import DogeRawDS from './stores/DogeRawDS' +import EthRawDS from '../eth/stores/EthRawDS' + +const CACHE_VALID_TIME = 30000 // 30 seconds +const CACHE = {} + +const TIMEOUT_DOGE = 60000 +const PROXY_TXS = 'https://proxy.trustee.deals/btc/getTxs' + +export default class DogeScannerProcessor { + + /** + * @type {number} + * @private + */ + _blocksToConfirm = 5 + + /** + * @type {string} + * @private + */ + _trezorServerCode = 'DOGE_TREZOR_SERVER' + + /** + * @private + */ + _trezorServer = false + + constructor(settings) { + this._settings = settings + } + + _addressesForFind(address, jsonData = {}) { + return [address] + } + + /** + * @param address + * @returns {Promise} + * @private + */ + async _get(address, jsonData) { + const now = new Date().getTime() + if (typeof CACHE[address] !== 'undefined' && (now - CACHE[address].time < CACHE_VALID_TIME)) { + CACHE[address].provider = 'trezor-cache' + return CACHE[address] + } + + let link + let res = false + // remove later after tests + if (this._settings['currencyCode'] === 'DOGE' || this._settings['currencyCode'] === 'LTC' || this._settings['currencyCode'] === 'BSV' || this._settings['currencyCode'] === 'BCH') { + link = PROXY_TXS + '?address=' + address + '¤cyCode=' + this._settings['currencyCode'] + res = await BlocksoftAxios.getWithoutBraking(link, 5, TIMEOUT_DOGE) + } + if (res && typeof res.data !== 'undefined' && res.data && typeof res.data.data !== 'undefined') { + res.data = res.data.data + } else { + this._trezorServer = await BlocksoftExternalSettings.getTrezorServer(this._trezorServerCode, 'DOGE.Scanner._get') + link = this._trezorServer + '/api/v2/address/' + address + '?details=txs&pageSize=40' + res = await BlocksoftAxios.getWithoutBraking(link, 5, TIMEOUT_DOGE) + } + + if (!res || !res.data) { + await BlocksoftExternalSettings.setTrezorServerInvalid(this._trezorServerCode, this._trezorServer) + return false + } + if (typeof res.data.balance === 'undefined') { + throw new Error(this._settings.currencyCode + ' DogeScannerProcessor._get nothing loaded for address ' + link) + } + CACHE[address] = { + data: res.data, + time: now, + provider : 'trezor' + } + return CACHE[address] + } + + /** + * @param {string} address + * @return {Promise<{balance:*, unconfirmed:*, provider:string}>} + */ + async getBalanceBlockchain(address, jsonData = {}) { + BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeScannerProcessor.getBalance started ' + address) + const res = await this._get(address, jsonData) + if (!res) { + return false + } + return { balance: res.data.balance, unconfirmed: res.data.unconfirmedBalance, provider: res.provider, time : res.time } + } + + /** + * @param {string} scanData.account.address + * @param {*} scanData.additional + * @return {Promise} + */ + async getTransactionsBlockchain(scanData) { + const address = scanData.account.address.trim() + const jsonData = scanData.additional + BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeScannerProcessor.getTransactions started ' + address) + + const notBroadcasted = await DogeRawDS.getForAddress({ address, currencyCode: this._settings.currencyCode }) + + let res = await this._get(address, jsonData) + if (!res || typeof res.data === 'undefined') return [] + BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeScannerProcessor.getTransactions loaded from ' + res.provider + ' ' + res.time) + res = res.data + if (typeof res.transactions === 'undefined' || !res.transactions) return [] + + const vinsOrder = {} + for (const tx of res.transactions) { + vinsOrder[tx.txid] = tx.blockTime + } + + let plussed = false + let i = 0 + do { + for (const tx of res.transactions) { + if (typeof tx.vin === 'undefined' || tx.vin.length === 0) continue + for (const vin of tx.vin) { + if (typeof vinsOrder[vin.txid] === 'undefined') { + continue + } + const newTime = vinsOrder[vin.txid] + 1 + if (tx.blockTime < newTime) { + tx.blockTime = newTime + plussed = true + } + vinsOrder[tx.txid] = tx.blockTime + } + } + i++ + } while (plussed && i < 100) + + const transactions = [] + for (const tx of res.transactions) { + const transaction = await this._unifyTransaction(address, tx, jsonData) + if (transaction) { + transactions.push(transaction) + if ( + transaction.transactionDirection === 'outcome' || transaction.transactionDirection === 'self' + ) { + const uniqueFrom = address.toLowerCase() + '_' + transaction.transactionHash + if (notBroadcasted && typeof notBroadcasted[uniqueFrom] !== 'undefined' && transaction.transactionStatus !== 'new') { + DogeRawDS.cleanRaw({ + address, + transactionUnique: uniqueFrom, + currencyCode: this._settings.currencyCode + }) + } + } + } + } + BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeScannerProcessor.getTransactions finished ' + address + ' total: ' + transactions.length) + return transactions + } + + /** + * + * @param {string} address + * @param {Object} transaction + * @param {string} transaction.txid c6b4c3879196857bed7fd5b553dd0049486c032d6a1be72b98fda967ca54b2da + * @param {string} transaction.version 1 + * @param {string} transaction.vin[].txid aa31777a9db759f57fd243ef47419939f233d16bc3e535e9a1c5af3ace87cb54 + * @param {string} transaction.vin[].sequence 4294967294 + * @param {string} transaction.vin[].n 0 + * @param {string} transaction.vin[].addresses [ 'DFDn5QyHH9DiFBNFGMcyJT5uUpDvmBRDqH' ] + * @param {string} transaction.vin[].value 44400000000 + * @param {string} transaction.vin[].hex 47304402200826f97d3432452abedd4346553de0b0c2d401ad7056b155e6462484afd98aa902202b5fb3166b96ded33249aecad7c667c0870c1 + * @param {string} transaction.vout[].value 59999824800 + * @param {string} transaction.vout[].n 0 + * @param {string} transaction.vout[].spent true + * @param {string} transaction.vout[].hex 76a91456d49605503d4770cf1f32fbfb69676d9a72554f88ac + * @param {string} transaction.vout[].addresses [ 'DD4DKVTEkRUGs7qzN8b7q5LKmoE9mXsJk4' ] + * @param {string} transaction.blockHash fc590834c04812e1c7818024a94021e12c4d8ab905724b4a4fdb4d4732878f69 + * @param {string} transaction.blockHeight 3036225 + * @param {string} transaction.confirmations 8568 + * @param {string} transaction.blockTime 1577362993 + * @param {string} transaction.value 59999917700 + * @param {string} transaction.valueIn 59999917700 + * @param {string} transaction.fees 0 + * @param {string} transaction.hex 010000000654cb87ce3aafc5a1e935e5c36bd133f239 + * @return {Promise} + * @private + */ + async _unifyTransaction(address, transaction, jsonData = {}) { + let showAddresses = false + try { + const tmp = this._addressesForFind(address, jsonData) + showAddresses = await DogeFindAddressFunction(tmp, transaction) + } catch (e) { + e.message += ' transaction hash ' + JSON.stringify(transaction) + ' address ' + address + throw e + } + + let transactionStatus = 'new' + if (transaction.confirmations > this._blocksToConfirm) { + transactionStatus = 'success' + } else if (transaction.confirmations > 0) { + transactionStatus = 'confirming' + } + + let formattedTime + try { + formattedTime = BlocksoftUtils.toDate(transaction.blockTime) + } catch (e) { + e.message += ' timestamp error transaction data ' + JSON.stringify(transaction) + throw e + } + + return { + transactionHash: transaction.txid, + blockHash: transaction.blockHash, + blockNumber: +transaction.blockHeight, + blockTime: formattedTime, + blockConfirmations: transaction.confirmations, + transactionDirection: showAddresses.direction, + addressFrom: showAddresses.from, + addressTo: showAddresses.to, + addressAmount: showAddresses.value, + transactionStatus: transactionStatus, + transactionFee: transaction.fees + } + } +} diff --git a/crypto/blockchains/doge/DogeTransferProcessor.ts b/crypto/blockchains/doge/DogeTransferProcessor.ts new file mode 100644 index 000000000..18223a5cd --- /dev/null +++ b/crypto/blockchains/doge/DogeTransferProcessor.ts @@ -0,0 +1,666 @@ +/** + * @version 0.20 + */ +import { BlocksoftBlockchainTypes } from '../BlocksoftBlockchainTypes' +import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog' +import BlocksoftUtils from '../../common/BlocksoftUtils' + +import DogeNetworkPrices from './basic/DogeNetworkPrices' +import DogeUnspentsProvider from './providers/DogeUnspentsProvider' +import DogeTxInputsOutputs from './tx/DogeTxInputsOutputs' +import DogeTxBuilder from './tx/DogeTxBuilder' +import DogeSendProvider from './providers/DogeSendProvider' +import DogeRawDS from './stores/DogeRawDS' +import { DogeLogs } from './basic/DogeLogs' + +import MarketingEvent from '../../../app/services/Marketing/MarketingEvent' +import config from '../../../app/config/config' +import { err } from 'react-native-svg/lib/typescript/xml' +import { sublocale } from '../../../app/services/i18n' +import settingsActions from '../../../app/appstores/Stores/Settings/SettingsActions' +import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings' + + +const networksConstants = require('../../common/ext/networks-constants') +const MAX_UNSPENTS = 100 + +export default class DogeTransferProcessor implements BlocksoftBlockchainTypes.TransferProcessor { + + _trezorServerCode = 'DOGE_TREZOR_SERVER' + + _builderSettings: BlocksoftBlockchainTypes.BuilderSettings = { + minOutputDustReadable: 0.001, + minChangeDustReadable: 0.5, + feeMaxForByteSatoshi: 100000000, // for tx builder + feeMaxAutoReadable2: 300, // for fee calc, + feeMaxAutoReadable6: 150, // for fee calc + feeMaxAutoReadable12: 100, // for fee calc + + changeTogether: true, + minRbfStepSatoshi: 50, + minSpeedUpMulti: 1.5, + feeMinTotalReadable : 1 + } + + _initedProviders: boolean = false + + _settings: BlocksoftBlockchainTypes.CurrencySettings + + _langPrefix: string + + // @ts-ignore + networkPrices: BlocksoftBlockchainTypes.NetworkPrices + + // @ts-ignore + unspentsProvider: BlocksoftBlockchainTypes.UnspentsProvider + + // @ts-ignore + sendProvider: BlocksoftBlockchainTypes.SendProvider + + // @ts-ignore + txPrepareInputsOutputs: BlocksoftBlockchainTypes.TxInputsOutputs + + // @ts-ignore + txBuilder: BlocksoftBlockchainTypes.TxBuilder + + constructor(settings: BlocksoftBlockchainTypes.CurrencySettings) { + this._settings = settings + this._langPrefix = networksConstants[settings.network].langPrefix + this.networkPrices = new DogeNetworkPrices() + } + + _initProviders() { + if (this._initedProviders) return false + this.unspentsProvider = new DogeUnspentsProvider(this._settings, this._trezorServerCode) + this.sendProvider = new DogeSendProvider(this._settings, this._trezorServerCode) + this.txPrepareInputsOutputs = new DogeTxInputsOutputs(this._settings, this._builderSettings) + this.txBuilder = new DogeTxBuilder(this._settings, this._builderSettings) + this._initedProviders = true + } + + needPrivateForFee(): boolean { + return true + } + + checkSendAllModal(data: { currencyCode: any }): boolean { + return true + } + + async getFeeRate(data: BlocksoftBlockchainTypes.TransferData, privateData: BlocksoftBlockchainTypes.TransferPrivateData, additionalData: BlocksoftBlockchainTypes.TransferAdditionalData = {}) + : Promise { + this._initProviders() + + let isStaticFee = this._settings.currencyCode === 'DOGE' && (typeof additionalData.isCustomFee === 'undefined' || !additionalData.isCustomFee) + let feeStaticReadable + if (isStaticFee) { + feeStaticReadable = BlocksoftExternalSettings.getStatic('DOGE_STATIC') + if (!feeStaticReadable['useStatic']) { + isStaticFee = false + } + } + let txRBF = false + let transactionSpeedUp = false + let transactionReplaceByFee = false + let transactionRemoveByFee = false + if (typeof data.transactionRemoveByFee !== 'undefined' && data.transactionRemoveByFee) { + BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeTransferProcessor.getFeeRate remove started ' + data.addressFrom + ' => ' + data.amount) + transactionRemoveByFee = data.transactionRemoveByFee + txRBF = transactionRemoveByFee + isStaticFee = false + } else if (typeof data.transactionReplaceByFee !== 'undefined' && data.transactionReplaceByFee) { + BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeTransferProcessor.getFeeRate resend started ' + data.addressFrom + ' => ' + data.amount) + transactionReplaceByFee = data.transactionReplaceByFee + txRBF = transactionReplaceByFee + isStaticFee = false + } else if (typeof data.transactionSpeedUp !== 'undefined' && data.transactionSpeedUp) { + BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeTransferProcessor.getFeeRate speedup started ' + data.addressFrom + ' => ' + data.amount) + transactionSpeedUp = data.transactionSpeedUp + txRBF = transactionSpeedUp + } else { + BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeTransferProcessor.getFeeRate started ' + data.addressFrom + ' => ' + data.amount) + } + + if (txRBF) { + const savedData = await DogeRawDS.getJson({ + address: data.addressFrom, + currencyCode: this._settings.currencyCode, + transactionHash: txRBF + }) // sometimes replaced in db or got from server + if (savedData) { + if (typeof data.transactionJson === 'undefined') { + data.transactionJson = {} + } + for (const key in savedData) { + // @ts-ignore + data.transactionJson[key] = savedData[key] + } + } + } + let prices = {} + let autocorrectFee = false + if (typeof additionalData.feeForByte === 'undefined') { + prices = typeof additionalData.prices !== 'undefined' ? additionalData.prices : await this.networkPrices.getNetworkPrices(this._settings.currencyCode) + } else { + // @ts-ignore + prices.speed_blocks_12 = additionalData.feeForByte + autocorrectFee = true + } + + let unspents = [] + let totalUnspents = 0 + if (transactionRemoveByFee) { + if (typeof this.unspentsProvider.getTx === 'undefined') { + throw new Error('No DogeTransferProcessor unspentsProvider.getTx for transactionRemoveByFee') + } + unspents = await this.unspentsProvider.getTx(data.transactionRemoveByFee, data.addressFrom, [], data.walletHash) + data.isTransferAll = true + } else { + unspents = typeof additionalData.unspents !== 'undefined' ? additionalData.unspents : await this.unspentsProvider.getUnspents(data.addressFrom) + if (transactionReplaceByFee) { + if (typeof this.unspentsProvider.getTx === 'undefined') { + throw new Error('No DogeTransferProcessor unspentsProvider.getTx for transactionReplaceByFee') + } + unspents = await this.unspentsProvider.getTx(data.transactionReplaceByFee, data.addressFrom, unspents, data.walletHash) + } + + totalUnspents = unspents.length + if (totalUnspents > MAX_UNSPENTS) { + unspents = unspents.slice(0, MAX_UNSPENTS) + } + } + + if (unspents.length > 1) { + unspents.sort((a, b) => { + return BlocksoftUtils.diff(b.value, a.value) * 1 + }) + // @ts-ignore + BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeTransferProcessor.getFeeRate unspents sorted', unspents) + } else { + // @ts-ignore + BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeTransferProcessor.getFeeRate unspents no need to sort', unspents) + } + + const result: BlocksoftBlockchainTypes.FeeRateResult = { + selectedFeeIndex: -1, + fees: [] as BlocksoftBlockchainTypes.Fee[] + } as BlocksoftBlockchainTypes.FeeRateResult + + const keys = ['speed_blocks_12', 'speed_blocks_6', 'speed_blocks_2'] + const checkedPrices = {} + let prevFeeForByte = 0 + + if (transactionSpeedUp) { + autocorrectFee = true + } + + if (isStaticFee && feeStaticReadable) { + const newPrices = {} + for (const key of keys) { + if (typeof feeStaticReadable[key] === 'undefined') continue + newPrices[key] = prices[key] + } + prices = newPrices + } + + const stepSatoshi = transactionRemoveByFee ? this._builderSettings.minRbfStepSatoshi * 2 : this._builderSettings.minRbfStepSatoshi + let pricesTotal = 0 + for (const key of keys) { + // @ts-ignore + if (typeof prices[key] === 'undefined' || !prices[key]) continue + pricesTotal++ + // @ts-ignore + let feeForByte = prices[key] + if (typeof additionalData.feeForByte === 'undefined') { + if (transactionReplaceByFee || transactionRemoveByFee) { + if (typeof data.transactionJson !== 'undefined' && data.transactionJson !== null && typeof data.transactionJson.feeForByte !== 'undefined') { + if (feeForByte * 1 < data.transactionJson.feeForByte * 1) { + feeForByte = Math.ceil(data.transactionJson.feeForByte * 1 + stepSatoshi) + } + } else { + feeForByte = Math.ceil(feeForByte * 1 + stepSatoshi) + } + if (feeForByte * 1 <= prevFeeForByte * 1) { + feeForByte = Math.ceil(prevFeeForByte * 1 + stepSatoshi) + } + } else if (transactionSpeedUp) { + feeForByte = Math.ceil(feeForByte * this._builderSettings.minSpeedUpMulti) + if (feeForByte * 1 <= prevFeeForByte * 1) { + feeForByte = Math.ceil(prevFeeForByte * 1.2) + } + } + } + // @ts-ignore + checkedPrices[key] = feeForByte + prevFeeForByte = feeForByte + } + + let uniqueFees = {} + let allFees = {} + let isError = false + for (const key of keys) { + // @ts-ignore + if (typeof checkedPrices[key] === 'undefined' || !checkedPrices[key]) continue + // @ts-ignore + const feeForByte = checkedPrices[key] + let preparedInputsOutputs + const subtitle = 'getFeeRate_' + key + ' ' + feeForByte + let blocks = '2' + let autoFeeLimitReadable = this._builderSettings.feeMaxAutoReadable2 + if (key === 'speed_blocks_6') { + blocks = '6' + autoFeeLimitReadable = this._builderSettings.feeMaxAutoReadable6 + } else if (key === 'speed_blocks_12') { + blocks = '12' + autoFeeLimitReadable = this._builderSettings.feeMaxAutoReadable12 + } + + let logInputsOutputs, blockchainData, txSize, actualFeeForByte, actualFeeForByteNotRounded + try { + if (isStaticFee) { + preparedInputsOutputs = await this.txPrepareInputsOutputs.getInputsOutputs(data, unspents, { + feeForByte : 'none', + feeForAll : BlocksoftUtils.fromUnified(feeStaticReadable['speed_blocks_' + blocks], this._settings.decimals), + feeForAllInputs : feeStaticReadable.feeForAllInputs, + autoFeeLimitReadable + }, + additionalData, + subtitle) + let newStatic = 0 + if (!data.isTransferAll && preparedInputsOutputs.inputs && preparedInputsOutputs.inputs.length > feeStaticReadable.feeForAllInputs * 1) { + newStatic = BlocksoftUtils.mul(feeStaticReadable['speed_blocks_' + blocks], Math.ceil(preparedInputsOutputs.inputs.length / feeStaticReadable.feeForAllInputs)) + BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeTransferProcessor.getFeeRate_' + key + ' inputs ' + preparedInputsOutputs.inputs.length + ' newStatic ' + newStatic) + preparedInputsOutputs = await this.txPrepareInputsOutputs.getInputsOutputs(data, unspents, { + feeForByte : 'none', + feeForAll : BlocksoftUtils.fromUnified(newStatic, this._settings.decimals), + feeForAllInputs : feeStaticReadable.feeForAllInputs, + autoFeeLimitReadable + }, + additionalData, + subtitle) + } + } else { + preparedInputsOutputs = await this.txPrepareInputsOutputs.getInputsOutputs(data, unspents, { + feeForByte, + autoFeeLimitReadable + }, + additionalData, + subtitle) + } + + if (typeof additionalData.feeForByte === 'undefined' && typeof this._builderSettings.feeMinTotalReadable !== 'undefined') { + logInputsOutputs = DogeLogs.logInputsOutputs(data, unspents, preparedInputsOutputs, this._settings, subtitle) + if (logInputsOutputs.diffInOutReadable * 1 < this._builderSettings.feeMinTotalReadable) { + BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeTransferProcessor.getFeeRate_' + key + ' ' + feeForByte + ' less minTotalReadable ' + logInputsOutputs.diffInOutReadable ) + preparedInputsOutputs = await this.txPrepareInputsOutputs.getInputsOutputs(data, unspents, { + feeForAll : BlocksoftUtils.fromUnified(this._builderSettings.feeMinTotalReadable, this._settings.decimals), + autoFeeLimitReadable + }, + additionalData, + subtitle) + autocorrectFee = false + } + } + // @ts-ignore + BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeTransferProcessor.getFeeRate_' + key + ' ' + feeForByte + + ' preparedInputsOutputs addressTo' + data.addressTo, preparedInputsOutputs) + if (preparedInputsOutputs.inputs.length === 0) { + // do noting + continue + } + } catch (e) { + if (config.debug.cryptoErrors) { + console.log(this._settings.currencyCode + ' DogeTransferProcessor.getFeeRate_' + key + ' ' + feeForByte + ' getInputsOutputs error', e) + } + // noinspection ES6MissingAwait + MarketingEvent.logOnlyRealTime('v20_doge_error_getfeerate_' + key + ' ' + feeForByte + ' ' + this._settings.currencyCode + ' ' + data.addressFrom + ' => ' + data.addressTo + ' ' + e.message, unspents) + throw e + } + + try { + let doBuild = false + let actualFeeRebuild = false + do { + doBuild = false + logInputsOutputs = DogeLogs.logInputsOutputs(data, unspents, preparedInputsOutputs, this._settings, subtitle) + blockchainData = await this.txBuilder.getRawTx(data, privateData, preparedInputsOutputs) + txSize = Math.ceil(blockchainData.rawTxHex.length / 2) + actualFeeForByteNotRounded = BlocksoftUtils.div(logInputsOutputs.diffInOut, txSize) + actualFeeForByte = Math.floor(actualFeeForByteNotRounded) + let needAutoCorrect = false + if (autocorrectFee && !isStaticFee) { + needAutoCorrect = actualFeeForByte.toString() !== feeForByte.toString() + } + if (!actualFeeRebuild && needAutoCorrect) { + BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeTransferProcessor.getFeeRate will correct as ' + actualFeeForByte.toString() + ' != ' + feeForByte.toString()) + let outputForCorrecting = -1 + for (let i = 0, ic = preparedInputsOutputs.outputs.length; i < ic; i++) { + const output = preparedInputsOutputs.outputs[i] + if (typeof output.isUsdt !== 'undefined') continue + if (typeof output.isChange !== 'undefined' && output.isChange) { + outputForCorrecting = i + } + } + if (outputForCorrecting >= 0) { + const diff = BlocksoftUtils.diff(actualFeeForByteNotRounded.toString(), feeForByte.toString()) + + const part = BlocksoftUtils.mul(txSize.toString(), diff.toString()).toString() + let newAmount + if (part.indexOf('-') === 0) { + newAmount = BlocksoftUtils.diff(preparedInputsOutputs.outputs[outputForCorrecting].amount, part.replace('-', '')) + } else { + newAmount = BlocksoftUtils.add(preparedInputsOutputs.outputs[outputForCorrecting].amount, part) + } + + + BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeTransferProcessor.getFeeRate diff ' + diff + ' part ' + part + ' amount ' + preparedInputsOutputs.outputs[outputForCorrecting].amount + ' => ' + newAmount) + + // @ts-ignore + if (newAmount * 1 < 0) { + preparedInputsOutputs.outputs[outputForCorrecting].amount = 'removed' + outputForCorrecting = -1 + } else { + preparedInputsOutputs.outputs[outputForCorrecting].amount = newAmount + } + } + doBuild = true + actualFeeRebuild = true + if (outputForCorrecting === -1) { + let foundToMore = false + for (let i = 0, ic = unspents.length; i < ic; i++) { + const unspent = unspents[i] + if (unspent.confirmations > 0 && !unspent.isRequired) { + unspents[i].isRequired = true + foundToMore = true + } + } + if (!foundToMore) { + for (let i = 0, ic = unspents.length; i < ic; i++) { + const unspent = unspents[i] + if (!unspent.isRequired) { + unspents[i].isRequired = true + foundToMore = true + } + } + } + BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeTransferProcessor.getFeeRate foundToMore ' + JSON.stringify(foundToMore)) + if (foundToMore) { + try { + const preparedInputsOutputs2 = await this.txPrepareInputsOutputs.getInputsOutputs(data, unspents, { + feeForByte, + autoFeeLimitReadable + }, + additionalData, subtitle + ' foundToMore') + actualFeeRebuild = false + if (preparedInputsOutputs2.inputs.length > 0) { + preparedInputsOutputs = preparedInputsOutputs2 + } + } catch (e) { + // do nothing + } + } + } + } + } while (doBuild) + } catch (e) { + if (config.debug.cryptoErrors) { + console.log(this._settings.currencyCode + ' DogeTransferProcessor.getRawTx error ' + e.message) + /* + console.log('') + console.log('') + if (preparedInputsOutputs.inputs) { + let i = 0 + for (let input of preparedInputsOutputs.inputs) { + console.log('ERR inputs [' + i + ']', JSON.parse(JSON.stringify(input))) + i++ + } + } + if (preparedInputsOutputs.outputs) { + let i = 0 + for (let output of preparedInputsOutputs.outputs) { + console.log('ERR outputs [' + i + ']', JSON.parse(JSON.stringify(output))) + i++ + } + } + console.log('ERR fee msg ', preparedInputsOutputs.msg) + console.log('ERR diffInOutS', logInputsOutputs.diffInOut) + console.log('ERR diffInOutR', logInputsOutputs.diffInOutReadable) + console.log('---------------------') + console.log('') + */ + } + BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeTransferProcessor.getRawTx error '+ e.message) + MarketingEvent.logOnlyRealTime('v20_doge_error_tx_builder_fees ' + this._settings.currencyCode + ' ' + data.addressFrom + ' => ' + data.addressTo + ' ' + e.message.toString(), logInputsOutputs) + + if (e.message.indexOf('Transaction has absurd fees') !== -1) { + isError = 'SERVER_RESPONSE_TOO_BIG_FEE_PER_BYTE_FOR_TRANSACTION' + continue + } else { + throw e + } + } + + isError = false + // @ts-ignore + blockchainData.isTransferAll = data.isTransferAll + blockchainData.isRBFed = { transactionRemoveByFee, transactionReplaceByFee, transactionSpeedUp } + + + allFees[this._langPrefix + '_' + key] = logInputsOutputs.diffInOut + if (typeof uniqueFees[logInputsOutputs.diffInOut] !== 'undefined') { + continue + } + result.fees.push( + { + langMsg: this._langPrefix + '_' + key, + feeForByte: actualFeeForByte.toString(), + needSpeed: feeForByte.toString(), + feeForTx: logInputsOutputs.diffInOut, + amountForTx: logInputsOutputs.sendBalance, + addressToTx: data.addressTo, + blockchainData + } + ) + uniqueFees[logInputsOutputs.diffInOut] = true + } + if (isError) { + throw new Error(isError) + } + result.selectedFeeIndex = result.fees.length - 1 + + if (!transactionReplaceByFee && !transactionRemoveByFee && !isStaticFee) { + + if (typeof allFees[this._langPrefix + '_speed_blocks_2'] === 'undefined') { + + result.showSmallFeeNotice = new Date().getTime() + } + } + if (result.fees.length === 0) { + result.amountForTx = 0 + } + result.additionalData = { unspents } + const logResult = { + selectedFeeIndex: result.selectedFeeIndex ? result.selectedFeeIndex : 'none', + showSmallFeeNotice: result.showSmallFeeNotice ? result.showSmallFeeNotice : 'none', + allFees: allFees, + fees: [] + } + if (result.fees) { + for (const fee of result.fees) { + if (totalUnspents && totalUnspents > unspents.length) { + fee.blockchainData.countedForLessOutputs = totalUnspents + } + const logFee = { ...fee } + delete logFee.blockchainData + logResult.fees.push(logFee) + } + } + BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeTransferProcessor.getFees ' + JSON.stringify(logResult)) + return result + } + + async getTransferAllBalance(data: BlocksoftBlockchainTypes.TransferData, privateData: BlocksoftBlockchainTypes.TransferPrivateData, additionalData: BlocksoftBlockchainTypes.TransferAdditionalData = {}): Promise { + data.isTransferAll = true + const result = await this.getFeeRate(data, privateData, additionalData) + // @ts-ignore + if (!result || result.selectedFeeIndex < 0) { + return { + selectedTransferAllBalance: '0', + selectedFeeIndex: -2, + fees: [], + countedForBasicBalance: data.amount + } + } + // @ts-ignore + return { + ...result, + selectedTransferAllBalance: result.fees[result.selectedFeeIndex].amountForTx, + countedForBasicBalance: data.amount + } + } + + async sendTx(data: BlocksoftBlockchainTypes.TransferData, privateData: BlocksoftBlockchainTypes.TransferPrivateData, uiData: BlocksoftBlockchainTypes.TransferUiData): Promise { + if (typeof uiData.selectedFee.blockchainData === 'undefined' && typeof uiData.selectedFee.feeForTx === 'undefined') { + throw new Error('SERVER_RESPONSE_PLEASE_SELECT_FEE') + } + + if (typeof privateData.privateKey === 'undefined') { + throw new Error('DOGE transaction required privateKey') + } + if (typeof data.addressTo === 'undefined') { + throw new Error('DOGE transaction required addressTo') + } + + let txRBFed = '' + let txRBF = false + if (typeof data.transactionRemoveByFee !== 'undefined' && data.transactionRemoveByFee) { + BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeTransferProcessor.sendTx remove started ' + data.transactionRemoveByFee) + txRBF = data.transactionRemoveByFee + txRBFed = 'RBFremoved' + } else if (typeof data.transactionReplaceByFee !== 'undefined' && data.transactionReplaceByFee) { + BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeTransferProcessor.sendTx resend started ' + data.transactionReplaceByFee) + txRBF = data.transactionReplaceByFee + txRBFed = 'RBFed' + } else { + BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeTransferProcessor.sendTx started') + txRBFed = 'usualSend' + } + + + const logData = {} + logData.currencyCode = this._settings.currencyCode + logData.selectedFee = uiData.selectedFee + logData.from = data.addressFrom + logData.basicAddressTo = data.addressTo + logData.basicAmount = data.amount + logData.pushLocale = sublocale() + logData.pushSetting = await settingsActions.getSetting('transactionsNotifs') + + if (typeof uiData !== 'undefined' && typeof uiData.selectedFee !== 'undefined' && typeof uiData.selectedFee.rawOnly !== 'undefined' && uiData.selectedFee.rawOnly) { + return { rawOnly: uiData.selectedFee.rawOnly, raw : uiData.selectedFee.blockchainData.rawTxHex } + } + + + let result = {} as BlocksoftBlockchainTypes.SendTxResult + try { + result = await this.sendProvider.sendTx(uiData.selectedFee.blockchainData.rawTxHex, txRBFed, txRBF, logData) + } catch (e) { + if (config.debug.cryptoErrors) { + console.log(this._settings.currencyCode + ' DogeTransferProcessor.sent error', e) + } + BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeTransferProcessor.sent error '+ e.message) + // noinspection ES6MissingAwait + MarketingEvent.logOnlyRealTime('v20_doge_tx_error ' + this._settings.currencyCode + ' ' + data.addressFrom + ' => ' + data.addressTo + ' ' + e.message, logData) + throw e + } + + try { + result.transactionFee = uiData.selectedFee.feeForTx + result.transactionFeeCurrencyCode = this._settings.currencyCode + result.transactionJson = { + nSequence: uiData.selectedFee.blockchainData.nSequence, + txAllowReplaceByFee: uiData.selectedFee.blockchainData.txAllowReplaceByFee, + feeForByte: uiData.selectedFee.feeForByte + } + if (typeof uiData.selectedFee.amountForTx !== 'undefined' && uiData.selectedFee.amountForTx) { + result.amountForTx = uiData.selectedFee.amountForTx + } + if (txRBF) { + await DogeRawDS.cleanRaw({ + address: data.addressFrom, + currencyCode: this._settings.currencyCode, + transactionHash: txRBF + }) + } + const transactionLog = typeof result.logData !== 'undefined' ? result.logData : logData + const inputsLog = JSON.stringify(uiData.selectedFee.blockchainData.preparedInputsOutputs.inputs) + const transactionRaw = uiData.selectedFee.blockchainData.rawTxHex + '' + //if (typeof transactionLog.selectedFee !== 'undefined' && typeof transactionLog.selectedFee.blockchainData !== 'undefined') { + // transactionLog.selectedFee.blockchainData = '*' + //} + await DogeRawDS.saveRaw({ + address: data.addressFrom, + currencyCode: this._settings.currencyCode, + transactionHash: result.transactionHash, + transactionRaw, + transactionLog + }) + BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeTransferProcessor.sendTx hex ', uiData.selectedFee.blockchainData.rawTxHex) + // @ts-ignore + BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeTransferProcessor.sendTx result ', result) + await DogeRawDS.saveInputs({ + address: data.addressFrom, + currencyCode: this._settings.currencyCode, + transactionHash: result.transactionHash, + transactionRaw: inputsLog + }) + await DogeRawDS.saveJson({ + address: data.addressFrom, + currencyCode: this._settings.currencyCode, + transactionHash: result.transactionHash, + transactionRaw: JSON.stringify(result.transactionJson) + }) + + BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeTransferProcessor.sent ' + data.addressFrom + ' done ' + JSON.stringify(result.transactionJson)) + } catch (e) { + if (config.debug.cryptoErrors) { + console.log(this._settings.currencyCode + ' DogeTransferProcessor.sent error additional', e, uiData) + } + BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeTransferProcessor.sent error additional'+ e.message) + // noinspection ES6MissingAwait + MarketingEvent.logOnlyRealTime('v20_doge_tx_error2 ' + this._settings.currencyCode + ' ' + data.addressFrom + ' => ' + data.addressTo + ' ' + e.message, logData) + } + // noinspection ES6MissingAwait + MarketingEvent.logOnlyRealTime('v20_doge_tx_success ' + this._settings.currencyCode + ' ' + data.addressFrom + ' => ' + data.addressTo, logData) + + if (config.debug.cryptoErrors) { + console.log(this._settings.currencyCode + ' DogeTransferProcessor.sendTx result', JSON.parse(JSON.stringify(result))) + } + return result + } + + async sendRawTx(data: BlocksoftBlockchainTypes.DbAccount, rawTxHex: string, txRBF : any, logData : any): Promise { + this._initProviders() + const result = await this.sendProvider.sendTx(rawTxHex, 'rawSend', txRBF, logData) + return result.transactionHash + } + + async setMissingTx(data: BlocksoftBlockchainTypes.DbAccount, transaction: BlocksoftBlockchainTypes.DbTransaction): Promise { + DogeRawDS.cleanRaw({ + address: data.address, + transactionHash: transaction.transactionHash, + currencyCode: this._settings.currencyCode + }) + MarketingEvent.logOnlyRealTime('v20_doge_tx_set_missing ' + this._settings.currencyCode + ' ' + data.address + ' => ' + transaction.addressTo, transaction) + return true + } + + canRBF(data: BlocksoftBlockchainTypes.DbAccount, transaction: BlocksoftBlockchainTypes.DbTransaction): boolean { + if (transaction.transactionDirection === 'income') { + return true + } + if (typeof transaction.transactionJson !== 'undefined') { + // console.log('transaction.transactionJson', JSON.stringify(transaction.transactionJson)) + } + return true + } +} diff --git a/crypto/blockchains/doge/basic/DogeFindAddressFunction.js b/crypto/blockchains/doge/basic/DogeFindAddressFunction.js new file mode 100644 index 000000000..1d94ff168 --- /dev/null +++ b/crypto/blockchains/doge/basic/DogeFindAddressFunction.js @@ -0,0 +1,129 @@ +/** + * @version 0.5 + * @param {string} addresses[] + * @param {string} transaction.hex + * @param {string} transaction.address + * @param {string} transaction.vin[].txid aa31777a9db759f57fd243ef47419939f233d16bc3e535e9a1c5af3ace87cb54 + * @param {string} transaction.vin[].sequence 4294967294 + * @param {string} transaction.vin[].n 0 + * @param {string} transaction.vin[].addresses [ 'DFDn5QyHH9DiFBNFGMcyJT5uUpDvmBRDqH' ] + * @param {string} transaction.vin[].addr '1HQzoxQsbjm44hc9rcJyX9KVmNAsyWyswB' + * @param {string} transaction.vin[].value 44400000000 + * @param {string} transaction.vin[].hex 47304402200826f97d3432452abedd4346553de0b0c2d401ad7056b155e6462484afd98aa902202b5fb3166b96ded33249aecad7c667c0870c1 + * @param {string} transaction.vout[].value 59999824800 + * @param {string} transaction.vout[].n 0 + * @param {string} transaction.vout[].spent true + * @param {string} transaction.vout[].hex 76a91456d49605503d4770cf1f32fbfb69676d9a72554f88ac + * @param {string} transaction.vout[].addresses [ 'DD4DKVTEkRUGs7qzN8b7q5LKmoE9mXsJk4' ] + * @param {string} transaction.vout[].scriptPubKey.addresses[] [ '1HXmWQShG2VZ2GXb8J2CZmVTEoDUmeKyAQ' ] + * @returns {Promise<{from: string, to: string, value: number, direction: string}>} + * @constructor + */ +import BlocksoftUtils from '../../../common/BlocksoftUtils' +import BlocksoftBN from '../../../common/BlocksoftBN' + +export default async function DogeFindAddressFunction(addresses, transaction) { + + const inputMyBN = new BlocksoftBN(0) + const inputOthersBN = new BlocksoftBN(0) + const inputOthersAddresses = [] + const uniqueTmp = {} + + const address1 = addresses[0] + const address2 = typeof addresses[1] !== 'undefined' ? addresses[1] : addresses[0] + const address3 = addresses[addresses.length - 1] // three is max now + if (transaction.vin) { + for (let i = 0, ic = transaction.vin.length; i < ic; i++) { + let vinAddress + let vinValue = transaction.vin[i].value + if (typeof transaction.vin[i].addresses !== 'undefined') { + vinAddress = transaction.vin[i].addresses[0] + } else if (typeof transaction.vin[i].addr !== 'undefined') { + vinAddress = transaction.vin[i].addr + } else if ( + typeof transaction.vin[i].prevout !== 'undefined' + ) { + if (typeof transaction.vin[i].prevout.scriptpubkey_address !== 'undefined') { + vinAddress = transaction.vin[i].prevout.scriptpubkey_address + } + if (typeof transaction.vin[i].prevout.value !== 'undefined') { + vinValue = transaction.vin[i].prevout.value + } + } + if (vinAddress === address1 || vinAddress === address2 || vinAddress === address3) { + inputMyBN.add(vinValue) + } else { + if (typeof uniqueTmp[vinAddress] === 'undefined') { + uniqueTmp[vinAddress] = 1 + inputOthersAddresses.push(vinAddress) + } + inputOthersBN.add(vinValue) + } + } + } + + const outputMyBN = new BlocksoftBN(0) + const outputOthersBN = new BlocksoftBN(0) + const outputOthersAddresses = [] + const uniqueTmp2 = {} + + if (transaction.vout) { + for (let j = 0, jc = transaction.vout.length; j < jc; j++) { + let voutAddress + const voutValue = transaction.vout[j].value + if (typeof transaction.vout[j].addresses !== 'undefined') { + voutAddress = transaction.vout[j].addresses[0] + } else if (typeof transaction.vout[j].scriptPubKey !== 'undefined' && typeof transaction.vout[j].scriptPubKey.addresses !== 'undefined') { + voutAddress = transaction.vout[j].scriptPubKey.addresses[0] + } else if (typeof transaction.vout[j].scriptpubkey_address !== 'undefined') { + voutAddress = transaction.vout[j].scriptpubkey_address + } + if (voutAddress === address1 || voutAddress === address2 || voutAddress === address3) { + outputMyBN.add(voutValue) + } else { + if (typeof uniqueTmp2[voutAddress] === 'undefined') { + uniqueTmp2[voutAddress] = 1 + outputOthersAddresses.push(voutAddress) + } + outputOthersBN.add(voutValue) + } + } + } + + let output + if (inputMyBN.get() === '0') { // my only in output + output = { + direction: 'income', + from: inputOthersAddresses.length > 0 ? inputOthersAddresses.join(',') : '', + to: '', // address1, + value: outputMyBN.get() + } + } else if (outputMyBN.get() === '0') { // my only in input + output = { + direction: 'outcome', + from: '', // address1, + to: outputOthersAddresses.length > 0 ? outputOthersAddresses.join(',') : '', + value: (inputOthersBN.get() === '0') ? outputOthersBN.get() : inputMyBN.get() + } + } else { // both input and output + if (outputOthersAddresses.length > 0) {// there are other address + output = { + direction: 'outcome', + from: '', // address1, + to: outputOthersAddresses.join(','), + value: outputOthersBN.get() + } + } else { + output = { + direction: 'self', + from: '', // address1, + to: '', // address1, + value: inputMyBN.diff(outputMyBN).get() + } + } + } + output.from = output.from.substr(0, 255) + output.to = output.to.substr(0, 255) + + return output +} diff --git a/crypto/blockchains/doge/basic/DogeLogs.ts b/crypto/blockchains/doge/basic/DogeLogs.ts new file mode 100644 index 000000000..4a5025baf --- /dev/null +++ b/crypto/blockchains/doge/basic/DogeLogs.ts @@ -0,0 +1,86 @@ +/** + * @version 0.20 + */ +import { BlocksoftBlockchainTypes } from '../../BlocksoftBlockchainTypes' +import BlocksoftBN from '../../../common/BlocksoftBN' +import BlocksoftUtils from '../../../common/BlocksoftUtils' +import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog' +import MarketingEvent from '../../../../app/services/Marketing/MarketingEvent' + +export namespace DogeLogs { + export const logInputsOutputs = function (data: BlocksoftBlockchainTypes.TransferData, + unspents: BlocksoftBlockchainTypes.UnspentTx[], + preparedInputsOutputs: { + inputs: BlocksoftBlockchainTypes.UnspentTx[], + outputs: BlocksoftBlockchainTypes.OutputTx[], + multiAddress: [], + msg: string, + }, settings: any, title: string): any { + const logInputsOutputs = { + inputs: [], + outputs: [], + totalIn: 0, + totalOut: 0, + diffInOut: 0, + msg: preparedInputsOutputs.msg || 'none' + } + const totalInBN = new BlocksoftBN(0) + const totalOutBN = new BlocksoftBN(0) + const totalBalanceBN = new BlocksoftBN(0) + if (typeof unspents !== 'undefined' && unspents && unspents.length > 0) { + for (const unspent of unspents) { + totalBalanceBN.add(unspent.value) + } + } + + const leftBalanceBN = new BlocksoftBN(totalBalanceBN) + const sendBalanceBN = new BlocksoftBN(0) + let input, output + if (preparedInputsOutputs) { + for (input of preparedInputsOutputs.inputs) { + logInputsOutputs.inputs.push({ + txid: input.txid, + vout: input.vout, + value: input.value, + confirmations: input.confirmations, + address: input.address || 'none' + }) + totalInBN.add(input.value) + leftBalanceBN.diff(input.value) + } + for (output of preparedInputsOutputs.outputs) { + if (output.amount === 'removed') continue + logInputsOutputs.outputs.push(output) + totalOutBN.add(output.amount) + if (typeof output.isChange === 'undefined' || !output.isChange) { + sendBalanceBN.add(output.amount) + } + } + } + logInputsOutputs.totalIn = totalInBN.get() + logInputsOutputs.totalOut = totalOutBN.get() + logInputsOutputs.diffInOut = totalInBN.diff(totalOutBN).get() + logInputsOutputs.diffInOutReadable = BlocksoftUtils.toUnified(logInputsOutputs.diffInOut, settings.decimals) + + const tmpBN = new BlocksoftBN(totalOutBN).diff(data.amount) + if (logInputsOutputs.diffInOut > 0) { + tmpBN.add(logInputsOutputs.diffInOut) + } + logInputsOutputs.totalOutMinusAmount = tmpBN.get() + logInputsOutputs.totalBalance = totalBalanceBN.get() + logInputsOutputs.leftBalance = leftBalanceBN.get() + logInputsOutputs.leftBalanceAndChange = BlocksoftUtils.add(leftBalanceBN, tmpBN) + logInputsOutputs.sendBalance = sendBalanceBN.get() + + logInputsOutputs.data = JSON.parse(JSON.stringify(data)) + if (typeof data.feeForTx === 'undefined' || typeof data.feeForTx.feeForByte === 'undefined' || data.feeForTx.feeForByte < 0) { + BlocksoftCryptoLog.log(title + ' preparedInputsOutputs with autofee ', logInputsOutputs) + } else { + BlocksoftCryptoLog.log(title + ' preparedInputsOutputs with fee ' + data.feeForTx.feeForTx, logInputsOutputs) + } + // console.log('btc_info ' + this._settings.currencyCode + ' ' + data.addressFrom + ' => ' + data.addressTo, logInputsOutputs) + // noinspection JSIgnoredPromiseFromCall + MarketingEvent.logOnlyRealTime('v20_doge_info ' + settings.currencyCode + ' ' + data.addressFrom + ' => ' + data.addressTo, logInputsOutputs) + return logInputsOutputs + } +} diff --git a/crypto/blockchains/doge/basic/DogeNetworkPrices.ts b/crypto/blockchains/doge/basic/DogeNetworkPrices.ts new file mode 100644 index 000000000..0931eb05c --- /dev/null +++ b/crypto/blockchains/doge/basic/DogeNetworkPrices.ts @@ -0,0 +1,31 @@ +/** + * @version 0.20 + **/ +import { BlocksoftBlockchainTypes } from '../../BlocksoftBlockchainTypes' +import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog' +import BlocksoftExternalSettings from '../../../common/BlocksoftExternalSettings' + +export default class DogeNetworkPrices implements BlocksoftBlockchainTypes.NetworkPrices { + + async getNetworkPrices(currencyCode: string) : Promise<{ 'speed_blocks_2': number, 'speed_blocks_6': number, 'speed_blocks_12': number }>{ + + BlocksoftCryptoLog.log(currencyCode + ' DogeNetworkPricesProvider ') + + const externalSettings = await BlocksoftExternalSettings.getAll('DOGE.getNetworkPrices') + + // @ts-ignore + if (!externalSettings || typeof externalSettings[currencyCode] === 'undefined') { + throw new Error(currencyCode + ' DogeNetworkPricesProvider ' + currencyCode + ' not defined') + } + + const prices = { + // @ts-ignore + 'speed_blocks_2': externalSettings[currencyCode]['2'] || 0, + // @ts-ignore + 'speed_blocks_6': externalSettings[currencyCode]['6'] || 0, + // @ts-ignore + 'speed_blocks_12': externalSettings[currencyCode]['12'] || 0 + } + return prices + } +} diff --git a/crypto/blockchains/doge/providers/DogeSendProvider.ts b/crypto/blockchains/doge/providers/DogeSendProvider.ts new file mode 100644 index 000000000..1421a8150 --- /dev/null +++ b/crypto/blockchains/doge/providers/DogeSendProvider.ts @@ -0,0 +1,226 @@ +/** + * @version 0.20 + * https://github.com/trezor/blockbook/blob/master/docs/api.md + */ +import { BlocksoftBlockchainTypes } from '../../BlocksoftBlockchainTypes' +import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog' +import BlocksoftAxios from '../../../common/BlocksoftAxios' +import BlocksoftExternalSettings from '../../../common/BlocksoftExternalSettings' +import config from '../../../../app/config/config' +import MarketingEvent from '../../../../app/services/Marketing/MarketingEvent' + +export default class DogeSendProvider implements BlocksoftBlockchainTypes.SendProvider { + + protected _trezorServerCode: string = '' + + private _trezorServer: string = '' + + protected _settings: BlocksoftBlockchainTypes.CurrencySettings + + private _proxy: string + + private _errorProxy: string + + private _successProxy: string + + constructor(settings: BlocksoftBlockchainTypes.CurrencySettings, serverCode: string) { + this._settings = settings + this._trezorServerCode = serverCode + + const { apiEndpoints } = config.proxy + const baseURL = MarketingEvent.DATA.LOG_TESTER ? apiEndpoints.baseURLTest : apiEndpoints.baseURL + this._proxy = baseURL + '/send/checktx' + this._errorProxy = baseURL + '/send/errortx' + this._successProxy = baseURL + '/send/sendtx' + } + + async _check(hex: string, subtitle: string, txRBF: any, logData: any) { + let checkResult = false + try { + BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeSendProvider.sendTx ' + subtitle + ' proxy checkResult start ' + this._proxy, logData) + if (config.debug.cryptoErrors) { + console.log(new Date().toISOString() + ' ' + this._settings.currencyCode + ' DogeSendProvider.sendTx ' + subtitle + ' proxy checkResult start ' + this._proxy) + } + checkResult = await BlocksoftAxios.post(this._proxy, { + raw: hex, + txRBF, + logData, + marketingData: MarketingEvent.DATA + }) + if (config.debug.cryptoErrors) { + console.log(new Date().toISOString() + ' ' + this._settings.currencyCode + ' DogeSendProvider.sendTx ' + subtitle + ' proxy checkResult end ' + this._proxy) + } + } catch (e) { + if (config.debug.cryptoErrors) { + console.log(this._settings.currencyCode + ' DogeSendProvider.send proxy error checkResult ' + e.message) + } + BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeSendProvider.send proxy error checkResult ' + e.message) + } + + if (checkResult !== false) { + if (typeof checkResult.data !== 'undefined') { + BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeSendProvider.send proxy checkResult1 ', checkResult.data) + if (typeof checkResult.data.status === 'undefined' || checkResult.data.status === 'error') { + if (config.debug.cryptoErrors) { + console.log(this._settings.currencyCode + ' DogeSendProvider.send proxy error checkResult1 ', checkResult) + } + checkResult = false + } else if (checkResult.data.status === 'notice') { + throw new Error(checkResult.data.msg) + } + } else { + BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeSendProvider.send proxy checkResult2 ', checkResult) + if (config.debug.cryptoErrors) { + console.log(this._settings.currencyCode + ' DogeSendProvider.send proxy error checkResult2 ', checkResult) + } + } + } else { + if (config.debug.cryptoErrors) { + console.log(this._settings.currencyCode + ' DogeSendProvider.send proxy error checkResultEmpty ', checkResult) + } + } + if (typeof logData === 'undefined' || !logData) { + logData = {} + } + logData.checkResult = checkResult && typeof checkResult.data !== 'undefined' && checkResult.data ? JSON.parse(JSON.stringify(checkResult.data)) : false + return logData + } + + async _checkError(hex: string, subtitle: string, txRBF: any, logData: any) { + BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeSendProvider.send proxy errorTx start ' + this._errorProxy, logData) + if (config.debug.cryptoErrors) { + console.log(new Date().toISOString() + ' ' + this._settings.currencyCode + ' DogeSendProvider.sendTx ' + subtitle + ' proxy errorTx start ' + this._errorProxy) + } + const res2 = await BlocksoftAxios.post(this._errorProxy, { + raw: hex, + txRBF, + logData, + marketingData: MarketingEvent.DATA + }) + if (config.debug.cryptoErrors) { + console.log(new Date().toISOString() + ' ' + this._settings.currencyCode + ' DogeSendProvider.sendTx ' + subtitle + ' proxy errorTx result ', JSON.parse(JSON.stringify(res2.data))) + } + BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeSendProvider.send proxy errorTx', typeof res2.data !== 'undefined' ? res2.data : res2) + } + + async _checkSuccess(transactionHash: string, hex: string, subtitle: string, txRBF: any, logData: any) { + let checkResult = false + try { + logData.txHash = transactionHash + BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeSendProvider.send proxy successTx start ' + this._successProxy, logData) + if (config.debug.cryptoErrors) { + console.log(new Date().toISOString() + ' ' + this._settings.currencyCode + ' DogeSendProvider.sendTx ' + subtitle + ' proxy successTx start ' + this._successProxy) + } + checkResult = await BlocksoftAxios.post(this._successProxy, { + raw: hex, + txRBF, + logData, + marketingData: MarketingEvent.DATA + }) + if (config.debug.cryptoErrors) { + console.log(new Date().toISOString() + ' ' + this._settings.currencyCode + ' DogeSendProvider.sendTx ' + subtitle + ' proxy successTx result ', JSON.parse(JSON.stringify(checkResult.data))) + } + } catch (e3) { + if (config.debug.cryptoErrors) { + console.log(this._settings.currencyCode + ' DogeSendProvider.send proxy error successTx ' + e3.message) + } + BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeSendProvider.send proxy error successTx ' + e3.message) + } + + if (checkResult !== false) { + if (typeof checkResult.data !== 'undefined') { + BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeSendProvider.send proxy successResult1 ', checkResult.data) + if (typeof checkResult.data.status === 'undefined' || checkResult.data.status === 'error') { + if (config.debug.cryptoErrors) { + console.log(this._settings.currencyCode + ' DogeSendProvider.send proxy error successResult1 ', checkResult) + } + checkResult = false + } else if (checkResult.data.status === 'notice') { + throw new Error(checkResult.data.msg) + } + } else { + BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeSendProvider.send proxy successResult2 ', checkResult) + if (config.debug.cryptoErrors) { + console.log(this._settings.currencyCode + ' DogeSendProvider.send proxy error successResult2 ', checkResult) + } + } + } else { + if (config.debug.cryptoErrors) { + console.log(this._settings.currencyCode + ' DogeSendProvider.send proxy error successResultEmpty ', checkResult) + } + } + logData.successResult = checkResult && typeof checkResult.data !== 'undefined' && checkResult.data ? JSON.parse(JSON.stringify(checkResult.data)) : false + logData.txRBF = txRBF + return logData + } + + async sendTx(hex: string, subtitle: string, txRBF: any, logData: any): Promise<{ transactionHash: string, transactionJson: any }> { + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeSendProvider.sendTx ' + subtitle + ' started ', logData) + + let link = BlocksoftExternalSettings.getStatic(this._trezorServerCode + '_SEND_LINK') + if (!link || link === '') { + this._trezorServer = await BlocksoftExternalSettings.getTrezorServer(this._trezorServerCode, 'DOGE.Send.sendTx') + link = this._trezorServer + '/api/v2/sendtx/' + } + + logData = await this._check(hex, subtitle, txRBF, logData) + + let res + try { + res = await BlocksoftAxios.post(link, hex) + } catch (e) { + if (config.debug.cryptoErrors) { + console.log(this._settings.currencyCode + ' DogeSendProvider.sendTx error ', e) + } + if (subtitle.indexOf('rawSend') !== -1) { + throw e + } + try { + logData.error = e.message + await this._checkError(hex, subtitle, txRBF, logData) + } catch (e2) { + if (config.debug.cryptoErrors) { + console.log(this._settings.currencyCode + ' DogeSendProvider.send proxy error errorTx ' + e.message) + } + BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeSendProvider.send proxy error errorTx ' + e2.message) + } + if (this._settings.currencyCode === 'USDT' && e.message.indexOf('bad-txns-in-belowout') !== -1) { + throw new Error('SERVER_RESPONSE_NOT_ENOUGH_FEE') + } else if (e.message.indexOf('transaction already in block') !== -1) { + throw new Error('SERVER_RESPONSE_TRANSACTION_ALREADY_MINED') + } else if (e.message.indexOf('inputs-missingorspent') !== -1) { + throw new Error('SERVER_RESPONSE_TRANSACTION_ALREADY_MINED') + } else if (e.message.indexOf('insufficient priority') !== -1) { + throw new Error('SERVER_RESPONSE_NO_RESPONSE_OR_MORE_FEE') + } else if (e.message.indexOf('dust') !== -1) { + throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_DUST') + } else if (e.message.indexOf('bad-txns-inputs-spent') !== -1 || e.message.indexOf('txn-mempool-conflict') !== -1) { + throw new Error('SERVER_RESPONSE_NO_RESPONSE') + } else if (e.message.indexOf('min relay fee not met') !== -1 || e.message.indexOf('fee for relay') !== -1) { + throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_FEE') + } else if (e.message.indexOf('insufficient fee, rejecting replacement') !== -1) { + throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_FEE_FOR_REPLACEMENT') + } else if (e.message.indexOf('insufficient fee') !== -1) { + throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_FEE') + } else if (e.message.indexOf('too-long-mempool-chain') !== -1) { + throw new Error('SERVER_RESPONSE_NO_RESPONSE') + } else { + await BlocksoftExternalSettings.setTrezorServerInvalid(this._trezorServerCode, this._trezorServer) + e.message += ' link: ' + link + throw e + } + } + if (typeof res.data.result === 'undefined' || !res.data.result) { + if (config.debug.cryptoErrors) { + console.log(this._settings.currencyCode + 'DogeSendProvider.send no txid', res.data) + } + throw new Error('SERVER_RESPONSE_NOT_CONNECTED') + } + + const transactionHash = res.data.result + logData = await this._checkSuccess(transactionHash, hex, subtitle, txRBF, logData) + + return { transactionHash, transactionJson: {}, logData } + } +} + diff --git a/crypto/blockchains/doge/providers/DogeUnspentsProvider.ts b/crypto/blockchains/doge/providers/DogeUnspentsProvider.ts new file mode 100644 index 000000000..20a2a6f4a --- /dev/null +++ b/crypto/blockchains/doge/providers/DogeUnspentsProvider.ts @@ -0,0 +1,201 @@ +/** + * @version 0.20 + * https://github.com/trezor/blockbook/blob/master/docs/api.md + * https://doge1.trezor.io/api/v2/utxo/D5oKvWEibVe74CXLASmhpkRpLoyjgZhm71 + */ +import { BlocksoftBlockchainTypes } from '../../BlocksoftBlockchainTypes' +import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog' +import BlocksoftAxios from '../../../common/BlocksoftAxios' +import BlocksoftExternalSettings from '../../../common/BlocksoftExternalSettings' +import DogeRawDS from '../stores/DogeRawDS' + +export default class DogeUnspentsProvider implements BlocksoftBlockchainTypes.UnspentsProvider { + + private _trezorServerCode: string = '' + + private _trezorServer: string = '' + + protected _settings: BlocksoftBlockchainTypes.CurrencySettings + + constructor(settings: BlocksoftBlockchainTypes.CurrencySettings, serverCode: string) { + this._settings = settings + this._trezorServerCode = serverCode + } + + async getUnspents(address: string): Promise { + // @ts-ignore + BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeUnspentsProvider.getUnspents started ' + address) + this._trezorServer = await BlocksoftExternalSettings.getTrezorServer(this._trezorServerCode, 'DOGE.Unspents.getUnspents') + let link = BlocksoftExternalSettings.getStatic(this._trezorServerCode + '_UNSPENDS_LINK') + if (!link || link === '') { + link = this._trezorServer + '/api/v2/utxo/' + address + '?gap=9999' + } + + const res = await BlocksoftAxios.getWithoutBraking(link) + // @ts-ignore + if (!res || typeof res.data === 'undefined') { + await BlocksoftExternalSettings.setTrezorServerInvalid(this._trezorServerCode, this._trezorServer) + BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeUnspentsProvider.getUnspents nothing loaded for address ' + address + ' link ' + link) + throw new Error('SERVER_RESPONSE_NOT_CONNECTED') + } + // @ts-ignore + if (!res.data || typeof res.data[0] === 'undefined') { + return [] + } + const sortedUnspents = [] + let unspent + // @ts-ignore + for (unspent of res.data) { + if (typeof unspent.path !== 'undefined') { + unspent.derivationPath = unspent.path + } + sortedUnspents.push(unspent) + } + return sortedUnspents + } + + _isMyAddress(voutAddress: string, address: string, walletHash: string): string { + return (voutAddress === address) ? address : '' + } + + async getTx(tx: string, address: string, allUnspents: BlocksoftBlockchainTypes.UnspentTx[], walletHash: string): Promise { + BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeUnspentsProvider.getTx started ' + tx) + + this._trezorServer = await BlocksoftExternalSettings.getTrezorServer(this._trezorServerCode, 'Doge.Unspents.getTx') + + let saved = await DogeRawDS.getInputs({ + currencyCode: this._settings.currencyCode, + transactionHash: tx + }) + BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeUnspentsProvider.getTx inputs ' + tx, saved) + + let recheckInputs = false + if (saved) { + if (typeof saved.inputs !== 'undefined') { + saved = saved.inputs + } + } else { + const link = this._trezorServer + '/api/v2/tx/' + tx + const res = await BlocksoftAxios.getWithoutBraking(link) + // @ts-ignore + if (!res || typeof res.data === 'undefined' || !res.data) { + BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeUnspentsProvider.getTx no tx ' + tx) + throw new Error('SERVER_RESPONSE_BAD_TX_TO_REPLACE') + } + // @ts-ignore + saved = res.data.vin + recheckInputs = true + } + + const sortedUnspents = [] + const unique = {} + if (allUnspents) { + for (const unspent of allUnspents) { + if (unspent.txid === tx) continue + if (typeof unspent.vout === 'undefined') { + // @ts-ignore + unspent.vout = unspent.n + } + const key = unspent.txid + '_' + unspent.vout + // @ts-ignore + if (typeof unique[key] !== 'undefined') continue + // @ts-ignore + unique[key] = sortedUnspents.length + sortedUnspents.push(unspent) + } + } + let txIn = false + for (const unspent of saved) { + if (unspent.txid === tx) continue + + try { + const link2 = this._trezorServer + '/api/v2/tx/' + unspent.txid + const res2 = await BlocksoftAxios.getWithoutBraking(link2) + // @ts-ignore + if (res2 && typeof res2.data !== 'undefined' && res2.data) { + // @ts-ignore + if (typeof res2.data.confirmations !== 'undefined' && res2.data.confirmations * 1 > 0) { + // @ts-ignore + unspent.confirmations = res2.data.confirmations * 1 + } else { + unspent.confirmations = 0 + } + // @ts-ignore + if (recheckInputs && typeof res2.data.vout !== 'undefined' && res2.data.vout) { + // @ts-ignore + BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeUnspentsProvider.getTx loading output data ' + JSON.stringify(unspent) + ' success', res2.data.vout) + let tmp + // @ts-ignore + if (res2.data.vout.length > 0) { + // @ts-ignore + unspent.vout = false + // @ts-ignore + for (tmp of res2.data.vout) { + if (typeof tmp.addresses !== 'undefined' && tmp.addresses) { + if (typeof tmp.addresses[0] !== 'undefined') { + const found = this._isMyAddress(tmp.addresses[0], address, walletHash) + if (found !== '') { + unspent.vout = tmp.n + unspent.address = found + break // 1 is enough + } + } + } + } + } + } + } else { + BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeUnspentsProvider.getTx loading output data ' + JSON.stringify(unspent) + ' no res') + } + } catch (e) { + BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeUnspentsProvider.getTx while loading output data ' + JSON.stringify(unspent)) + } + + + if (typeof unspent.vout === 'undefined' || unspent.vout === false) { + continue + } + + const key = unspent.txid + '_' + unspent.vout + // @ts-ignore + if (typeof unique[key] !== 'undefined') { + // @ts-ignore + const index = unique[key] + sortedUnspents[index].isRequired = true + } else { + // @ts-ignore + unique[key] = sortedUnspents.length + unspent.isRequired = true + if (typeof unspent.confirmations === 'undefined') { + unspent.confirmations = 1 + } + sortedUnspents.push(unspent) + } + txIn = true + } + + let foundRequired = false + for (const unspent of sortedUnspents) { + if (unspent.isRequired && unspent.confirmations > 0) { + foundRequired = true + break + } + } + if (!foundRequired) { + for (const unspent of sortedUnspents) { + if (unspent.isRequired) { + unspent.confirmations = 1 + break + } + } + } + + if (!txIn) { + throw new Error('SERVER_RESPONSE_BAD_TX_TO_REPLACE') + } + + // @ts-ignore + BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeUnspentsProvider.getTx found ' + tx, sortedUnspents) + return sortedUnspents + } +} diff --git a/crypto/blockchains/doge/stores/DogeRawDS.js b/crypto/blockchains/doge/stores/DogeRawDS.js new file mode 100644 index 000000000..7b6a14a05 --- /dev/null +++ b/crypto/blockchains/doge/stores/DogeRawDS.js @@ -0,0 +1,216 @@ + +import Database from '@app/appstores/DataSource/Database'; +import BlocksoftExternalSettings from '../../../common/BlocksoftExternalSettings' +import BlocksoftAxios from '../../../common/BlocksoftAxios' +import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog' +import { BlocksoftTransfer } from '../../../actions/BlocksoftTransfer/BlocksoftTransfer' +import config from '../../../../app/config/config' + +const tableName = 'transactions_raw' + +class DogeRawDS { + + _trezorServer = 'none' + + _canUpdate = true + async getForAddress(data) { + return false + // @todo refactor from server side + if (!this._canUpdate) return false + try { + const sql = ` + SELECT id, + transaction_unique_key AS transactionUnique, + transaction_hash AS transactionHash, + transaction_raw AS transactionRaw, + transaction_log AS transactionLog, + broadcast_log AS broadcastLog, + broadcast_updated AS broadcastUpdated, + created_at AS transactionCreated, + is_removed, removed_at + FROM transactions_raw + WHERE currency_code='${data.currencyCode}' + AND address='${data.address.toLowerCase()}' + AND transaction_unique_key NOT LIKE 'inputs_%' + AND transaction_unique_key NOT LIKE 'json_%' + AND (is_removed=0 OR is_removed IS NULL) + ` + const result = await Database.query(sql) + if (!result || !result.array || result.array.length === 0) { + return {} + } + const ret = {} + + const now = new Date().toISOString() + + for (const row of result.array) { + try { + if (typeof ret[row.transactionUnique] !== 'undefined') { + continue + } + ret[row.transactionUnique] = row + let transactionLog + try { + transactionLog = row.transactionLog ? JSON.parse(Database.unEscapeString(row.transactionLog)) : row.transactionLog + } catch (e) { + // do nothing + } + + let broadcastLog = '' + const updateObj = { broadcastUpdated: now } + let broad + try { + broad = await BlocksoftTransfer.sendRawTx(data, row.transactionRaw, + typeof transactionLog !== 'undefined' && transactionLog && typeof transactionLog.txRBF !== 'undefined' ? transactionLog.txRBF : false, + transactionLog + ) + if (broad === '') { + throw new Error('not broadcasted') + } + broadcastLog = ' broadcasted ok ' + JSON.stringify(broad) + updateObj.is_removed = 1 + updateObj.removed_at = now + } catch (e) { + if (config.debug.cryptoErrors) { + const dbTx = await Database.query(`SELECT * FROM transactions WHERE transaction_hash='${row.transactionHash}'`) + if (config.debug.cryptoErrors) { + console.log('DogeRawDS.getForAddress send error ' + e.message, JSON.parse(JSON.stringify(row)), dbTx, e) + } + + } + if (e.message.indexOf('bad-txns-inputs-spent') !== -1 || e.message.indexOf('missing-inputs') !== -1 || e.message.indexOf('insufficient fee') !== -1) { + broadcastLog = ' sub-spent ' + e.message + updateObj.is_removed = 3 + const sql = `UPDATE transactions + SET transaction_status='replaced', hidden_at='${now}' + WHERE transaction_hash='${row.transactionHash}' + AND (transaction_status='missing' OR transaction_status='new') + ` + await Database.query(sql) + } else if (e.message.indexOf('already known') !== -1) { + broadcastLog = ' already known' + } else { + broadcastLog = e.message + } + } + broadcastLog = new Date().toISOString() + ' ' + broadcastLog + ' ' + (row.broadcastLog ? row.broadcastLog.substr(0, 1000) : '') + updateObj.broadcastLog = broadcastLog + await Database.setTableName('transactions_raw').setUpdateData({ + updateObj, + key: { id: row.id } + }).update() + + } catch (e) { + if (config.debug.cryptoErrors) { + console.log('DogeRawDS.getForAddress error ' + e.message + ' in ' + row.transactionHash, e) + } + throw new Error('DogeRawDS.getForAddress error ' + e.message + ' in ' + row.transactionHash) + } + } + this._canUpdate = true + return ret + } catch (e) { + this._canUpdate = true + throw new Error(e.message + ' on DogeRawDS.getAddress') + } + } + + async cleanRaw(data) { + if (typeof data.transactionUnique === 'undefined') { + data.transactionUnique = data.address.toLowerCase() + '_' + data.transactionHash + } + BlocksoftCryptoLog.log('DogeRawDS cleanRaw ', data) + const now = new Date().toISOString() + const sql = `UPDATE transactions_raw + SET is_removed=1, removed_at = '${now}' + WHERE + (is_removed=0 OR is_removed IS NULL) + AND currency_code='${data.currencyCode}' + AND address='${data.address.toLowerCase()}' + AND transaction_unique_key='${data.transactionUnique}'` + await Database.query(sql) + } + + async saveRaw(data) { + if (typeof data.transactionUnique === 'undefined') { + data.transactionUnique = data.address.toLowerCase() + '_' + data.transactionHash + } + const now = new Date().toISOString() + + const sql = `UPDATE transactions_raw + SET is_removed=1, removed_at = '${now}' + WHERE + (is_removed=0 OR is_removed IS NULL) + AND currency_code='${data.currencyCode}' + AND address='${data.address.toLowerCase()}' + AND transaction_unique_key='${data.transactionUnique}'` + await Database.query(sql) + + const prepared = [{ + currency_code: data.currencyCode, + address: data.address.toLowerCase(), + transaction_unique_key: data.transactionUnique, + transaction_hash: data.transactionHash, + transaction_raw: data.transactionRaw, + created_at: now + }] + if (typeof data.transactionLog !== 'undefined' && data.transactionLog) { + prepared[0].transaction_log = Database.escapeString(JSON.stringify(data.transactionLog)) + } + await Database.setTableName(tableName).setInsertData({ insertObjs: prepared }).insert() + } + + async savePrefixed(data, prefix) { + const now = new Date().toISOString() + + const prepared = [{ + currency_code: data.currencyCode, + address: data.address.toLowerCase(), + transaction_unique_key: prefix + '_' + data.transactionHash, + transaction_hash: data.transactionHash, + transaction_raw: Database.escapeString(data.transactionRaw), + is_removed: 2, + created_at: now + }] + if (typeof data.transactionLog !== 'undefined' && data.transactionLog) { + prepared[0].transaction_log = Database.escapeString(JSON.stringify(data.transactionLog)) + } + await Database.setTableName(tableName).setInsertData({ insertObjs: prepared }).insert() + } + + async getPrefixed(data, prefix) { + const sql = `SELECT transaction_raw AS transactionRaw + FROM ${tableName} + WHERE currency_code='${data.currencyCode}' + AND transaction_unique_key='${prefix}_${data.transactionHash}' LIMIT 1` + const res = await Database.query(sql) + if (!res || !res.array || typeof res.array[0] === 'undefined' || typeof res.array[0].transactionRaw === 'undefined') { + return false + } + try { + const str = Database.unEscapeString(res.array[0].transactionRaw) + return JSON.parse(str) + } catch (e) { + BlocksoftCryptoLog.err('DogeRawDS getInputs error ' + e.message) + return false + } + } + + async saveInputs(data) { + return this.savePrefixed(data, 'inputs') + } + + async getInputs(data) { + return this.getPrefixed(data, 'inputs') + } + + async saveJson(data) { + return this.savePrefixed(data, 'json') + } + + async getJson(data) { + return this.getPrefixed(data, 'json') + } +} + +export default new DogeRawDS() diff --git a/crypto/blockchains/doge/tx/DogeTxBuilder.ts b/crypto/blockchains/doge/tx/DogeTxBuilder.ts new file mode 100644 index 000000000..48af913c3 --- /dev/null +++ b/crypto/blockchains/doge/tx/DogeTxBuilder.ts @@ -0,0 +1,195 @@ +/** + * @version 0.20 + */ +import { BlocksoftBlockchainTypes } from '../../BlocksoftBlockchainTypes' +import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog' +import BlocksoftUtils from '../../../common/BlocksoftUtils' +import { TransactionBuilder, ECPair, payments } from 'bitcoinjs-lib' +import BlocksoftExternalSettings from '../../../common/BlocksoftExternalSettings' +import config from '../../../../app/config/config' + +const networksConstants = require('../../../common/ext/networks-constants') + +const MAX_SEQ = 4294967294 // 0xfffffffe // no replace by fee +const MIN_SEQ = 4294960000 // for RBF + +export default class DogeTxBuilder implements BlocksoftBlockchainTypes.TxBuilder { + + protected _settings: BlocksoftBlockchainTypes.CurrencySettings + private _builderSettings: BlocksoftBlockchainTypes.BuilderSettings + protected _bitcoinNetwork: any + private _feeMaxForByteSatoshi: number | any + + protected keyPair: any + + constructor(settings: BlocksoftBlockchainTypes.CurrencySettings, builderSettings: BlocksoftBlockchainTypes.BuilderSettings) { + this._settings = settings + this._builderSettings = builderSettings + this._bitcoinNetwork = networksConstants[settings.network].network + } + + async _reInit() { + const fromExt = await BlocksoftExternalSettings.get(this._settings.currencyCode + '_MAX_FOR_BYTE_TX_BUILDER', 'DogeTxBuilder._reInit') + this._feeMaxForByteSatoshi = fromExt && fromExt * 1 > 0 ? fromExt * 1 : this._builderSettings.feeMaxForByteSatoshi + await BlocksoftCryptoLog.log('DogeTxBuilder.getRawTx ' + this._settings.currencyCode + ' _feeMaxForByteSatoshi ' + this._feeMaxForByteSatoshi + ' fromExt ' + fromExt ) + } + + _getRawTxValidateKeyPair(privateData: BlocksoftBlockchainTypes.TransferPrivateData, data: BlocksoftBlockchainTypes.TransferData): void { + if (typeof privateData.privateKey === 'undefined') { + throw new Error('DogeTxBuilder.getRawTx requires privateKey') + } + try { + this.keyPair = ECPair.fromWIF(privateData.privateKey, this._bitcoinNetwork) + const address = payments.p2pkh({ + pubkey: this.keyPair.publicKey, + network: this._bitcoinNetwork + }).address + if (address !== data.addressFrom) { + // noinspection ExceptionCaughtLocallyJS + throw new Error('not valid signing address ' + data.addressFrom + ' != ' + address) + } + } catch (e) { + e.message += ' in privateKey ' + this._settings.currencyCode + ' DogeTxBuilder signature check ' + throw e + } + } + + async _getRawTxAddInput(txb: TransactionBuilder, i: number, input: BlocksoftBlockchainTypes.UnspentTx, nSequence: number): Promise { + if (typeof input.vout === 'undefined') { + throw new Error('no input.vout') + } + if (typeof nSequence === 'undefined') { + throw new Error('no nSequence') + } + txb.addInput(input.txid, input.vout, nSequence) + } + + async _getRawTxSign(txb: TransactionBuilder, i: number, input: BlocksoftBlockchainTypes.UnspentTx): Promise { + await BlocksoftCryptoLog.log('DogeTxBuilder.getRawTx sign', input) + // @ts-ignore + txb.sign(i, this.keyPair, null, null, input.value * 1) + } + + _getRawTxAddOutput(txb: TransactionBuilder, output: BlocksoftBlockchainTypes.OutputTx): void { + // @ts-ignore + const amount = Math.round(output.amount * 1) + if (amount === 0) { + // do nothing or txb.addOutput(output.to, 546) + } else { + txb.addOutput(output.to, amount) + } + } + + async getRawTx(data: BlocksoftBlockchainTypes.TransferData, privateData: BlocksoftBlockchainTypes.TransferPrivateData, preparedInputsOutputs: BlocksoftBlockchainTypes.PreparedInputsOutputsTx): + Promise<{ + rawTxHex: string, + nSequence: number, + txAllowReplaceByFee: boolean, + preparedInputsOutputs: BlocksoftBlockchainTypes.PreparedInputsOutputsTx + }> { + + await this._reInit() + + this._getRawTxValidateKeyPair(privateData, data) + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeTxBuilder.getRawTx validated address private key') + + let nSequence = 0 + let txAllowReplaceByFee = false + if (typeof data.transactionJson === 'undefined' || !data.transactionJson || typeof data.transactionJson.nSequence === 'undefined') { + if (data.allowReplaceByFee) { + nSequence = MIN_SEQ + txAllowReplaceByFee = true + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeTxBuilder.getRawTx allow RBF ' + nSequence) + } else { + nSequence = MAX_SEQ + txAllowReplaceByFee = false + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeTxBuilder.getRawTx no RBF ' + nSequence) + } + } else { + nSequence = data.transactionJson.nSequence * 1 + 1 + txAllowReplaceByFee = true + + if (nSequence >= MAX_SEQ) { + nSequence = MAX_SEQ + txAllowReplaceByFee = false + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeTxBuilder.getRawTx no RBF by old nSeq ' + data.transactionJson.nSequence + ' +1 => ' + nSequence) + } else { + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeTxBuilder.getRawTx allow RBF by old nSeq ' + data.transactionJson.nSequence + ' +1 => ' + nSequence) + } + } + + const txb = new TransactionBuilder(this._bitcoinNetwork, this._feeMaxForByteSatoshi) + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeTxBuilder.getRawTx started max4Bytes ' + this._feeMaxForByteSatoshi) + + txb.setVersion(1) + + const log = { inputs: [], outputs: [] } + for (let i = 0, ic = preparedInputsOutputs.inputs.length; i < ic; i++) { + const input = preparedInputsOutputs.inputs[i] + try { + await this._getRawTxAddInput(txb, i, input, nSequence) + // @ts-ignore + log.inputs.push({ txid: input.txid, vout: input.vout, nSequence }) + // @ts-ignore + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeTxBuilder.getRawTx input added', input) + } catch (e) { + if (config.debug.cryptoErrors) { + console.log(this._settings.currencyCode + ' DogeTxBuilder.getRawTx input add error ', e, JSON.parse(JSON.stringify(input))) + } + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeTxBuilder.getRawTx input add error ', input) + throw e + } + } + + let output + for (output of preparedInputsOutputs.outputs) { + try { + if (output.amount !== 'removed') { + this._getRawTxAddOutput(txb, output) + } + // @ts-ignore + log.outputs.push({ addressTo: output.to, amount: output.amount }) + // @ts-ignore + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeTxBuilder.getRawTx output added ', output) + } catch (e) { + if (config.debug.cryptoErrors) { + console.log(this._settings.currencyCode + ' DogeTxBuilder.getRawTx output add error ', e, JSON.parse(JSON.stringify(output))) + } + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeTxBuilder.getRawTx output add error ', output) + throw e + } + } + + for (let i = 0, ic = preparedInputsOutputs.inputs.length; i < ic; i++) { + const input = preparedInputsOutputs.inputs[i] + try { + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeTxBuilder.getRawTx sign adding') + await this._getRawTxSign(txb, i, input) + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeTxBuilder.getRawTx sign added') + } catch (e) { + if (config.debug.cryptoErrors) { + if (e.message.indexOf('Transaction needs outputs') !== -1) { + console.log(this._settings.currencyCode + ' DogeTxBuilder.getRawTx input sign error ' + e.message, JSON.parse(JSON.stringify(preparedInputsOutputs))) + } else { + console.log(this._settings.currencyCode + ' DogeTxBuilder.getRawTx input sign error ', e, JSON.parse(JSON.stringify(input))) + } + } + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeTxBuilder.getRawTx input sign error ', input) + e.message = ' transaction ' + this._settings.currencyCode + ' DogeTxBuilder sign error: ' + e.message + throw e + } + } + let rawTxHex + try { + rawTxHex = txb.build().toHex() + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeTxBuilder.getRawTx size ' + rawTxHex.length) + // @ts-ignore + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeTxBuilder.getRawTx hex', rawTxHex) + } catch (e) { + e.message = ' transaction ' + this._settings.currencyCode + ' DogeTxBuilder build error: ' + e.message + throw e + } + + return { rawTxHex, nSequence, txAllowReplaceByFee, preparedInputsOutputs } + } +} diff --git a/crypto/blockchains/doge/tx/DogeTxInputsOutputs.ts b/crypto/blockchains/doge/tx/DogeTxInputsOutputs.ts new file mode 100644 index 000000000..5871154db --- /dev/null +++ b/crypto/blockchains/doge/tx/DogeTxInputsOutputs.ts @@ -0,0 +1,493 @@ +/** + * @version 0.20 + */ +import { BlocksoftBlockchainTypes } from '../../BlocksoftBlockchainTypes' +import BlocksoftBN from '../../../common/BlocksoftBN' +import BlocksoftUtils from '../../../common/BlocksoftUtils' +import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog' +import BlocksoftDict from '@crypto/common/BlocksoftDict' + +const coinSelect = require('coinselect') +const coinSplit = require('coinselect/split') + +export default class DogeTxInputsOutputs implements BlocksoftBlockchainTypes.TxInputsOutputs { + private _builderSettings: BlocksoftBlockchainTypes.BuilderSettings + protected _settings: BlocksoftBlockchainTypes.CurrencySettings + private _minOutputDust: any + private _minChangeDust: any + + // in*148 + out*34 + 10 plus or minus 'in' + SIZE_FOR_BASIC = 34 + SIZE_FOR_INPUT = 148 // TX_INPUT_PUBKEYHASH = 107 + SIZE_FOR_BC = 75 + + constructor(settings: BlocksoftBlockchainTypes.CurrencySettings, builderSettings: BlocksoftBlockchainTypes.BuilderSettings) { + this._settings = settings + this._builderSettings = builderSettings + this._minOutputDust = BlocksoftUtils.fromUnified(this._builderSettings.minOutputDustReadable, settings.decimals) // output amount that will be considered as "dust" so we dont need it + this._minChangeDust = BlocksoftUtils.fromUnified(this._builderSettings.minChangeDustReadable, settings.decimals) // change amount that will be considered as "dust" so we dont need it + } + + _coinSelectTargets(data: BlocksoftBlockchainTypes.TransferData, unspents: BlocksoftBlockchainTypes.UnspentTx[], feeForByte: string, multiAddress: string[], subtitle: string) { + let targets + if (data.isTransferAll) { + targets = [{ + address: data.addressTo + }] + } else if (multiAddress.length === 0) { + targets = [{ + address: data.addressTo, + // @ts-ignore + value: data.amount * 1 + }] + } else { + targets = [] + for (const address of multiAddress) { + targets.push({ + address: address, + // @ts-ignore + value: data.amount * 1 + }) + } + } + // @ts-ignore + BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeTxInputsOutputs.getInputsOutputs _coinSelectTargets', { + amount: data.amount, + isTransferAll: data.isTransferAll, + multiAddress, + address : data.addressTo + }, targets) + return targets + } + + _addressForChange(data: BlocksoftBlockchainTypes.TransferData): string { + return data.addressFrom + } + + _usualTargets(data: BlocksoftBlockchainTypes.TransferData, unspents: BlocksoftBlockchainTypes.UnspentTx[]) { + const multiAddress = [] + const basicWishedAmountBN = new BlocksoftBN(data.amount) + const wishedAmountBN = new BlocksoftBN(basicWishedAmountBN) + + const outputs = [] + if (data.addressTo.indexOf(';') === -1) { + outputs.push({ + 'to': data.addressTo, + 'amount': data.amount.toString() + }) + } else { + const addresses = data.addressTo.replace(/\s+/g, ';').split(';') + let total = 0 + for (let i = 0, ic = addresses.length; i < ic; i++) { + const address = addresses[i].trim() + if (!address) continue + outputs.push({ + 'to': address, + 'amount': data.amount.toString() + }) + multiAddress.push(address) + if (total > 0) { + wishedAmountBN.add(basicWishedAmountBN) + } + total++ + } + } + return { + multiAddress, + basicWishedAmountBN, + wishedAmountBN, + outputs + } + } + + async _coinSelect(data: BlocksoftBlockchainTypes.TransferData, unspents: BlocksoftBlockchainTypes.UnspentTx[], feeForByte: string, multiAddress: string[], subtitle: string) + : Promise { + const utxos = [] + const isRequired: any = {} + let isAllRequired: boolean = true + const segwitPrefix = typeof BlocksoftDict.CurrenciesForTests[this._settings.currencyCode + '_SEGWIT'] !== 'undefined' ? BlocksoftDict.CurrenciesForTests[this._settings.currencyCode + '_SEGWIT'].addressPrefix : false + for (const unspent of unspents) { + const input = { + txId: unspent.txid, + vout: unspent.vout, + // @ts-ignore + value: unspent.value * 1, + my: unspent + }// script + if (typeof unspent.address !== 'undefined' && unspent.address.indexOf(segwitPrefix) === 0) { + input.isSegwit = true + // https://github.com/bitcoinjs/coinselect/pull/63 wait for it to be merged + } + utxos.push(input) + if (unspent.isRequired) { + if (typeof isRequired[unspent.txid] === 'undefined') { + isRequired[unspent.txid] = unspent + } + } else { + isAllRequired = false + } + } + if (isAllRequired) { + if (data.addressFrom === data.addressTo) { + data.isTransferAll = true + } + } + const targets = this._coinSelectTargets(data, unspents, feeForByte, multiAddress, subtitle) + let res + if (data.isTransferAll) { + res = coinSplit(utxos, targets, feeForByte) + } else { + res = coinSelect(utxos, targets, feeForByte) + } + const { inputs, outputs, fee } = res + /* + console.log('CS feeForByte ' + feeForByte) + console.log('CS isAllRequired ', JSON.stringify(isAllRequired)) + console.log('CS targets ' + feeForByte, JSON.parse(JSON.stringify(targets))) + if (inputs) { + let i = 0 + for (let input of inputs) { + console.log('CS inputs [' + i + ']', JSON.parse(JSON.stringify(input))) + i++ + } + } + if (outputs) { + let i = 0 + for (let output of outputs) { + console.log('CS outputs [' + i + ']', JSON.parse(JSON.stringify(output))) + i++ + } + } + console.log('CS fee ', fee ? JSON.parse(JSON.stringify(fee)) : 'none') + console.log('---------------------') + console.log('') + */ + + const formatted = { + inputs: [], + outputs: [], + multiAddress, + msg: ' coinselect for ' + feeForByte + ' fee ' + fee + ' ' + subtitle + ' all data ' + JSON.stringify(inputs) + ' ' + JSON.stringify(outputs), + countedFor: 'DOGE' + } + if (!inputs || typeof inputs === 'undefined') { + // @ts-ignore + return formatted + } + + let input, output + for (input of inputs) { + // @ts-ignore + formatted.inputs.push(input.my) + if (typeof isRequired[input.my.txid] !== 'undefined') { + delete isRequired[input.my.txid] + } + } + const changeBN = new BlocksoftBN(0) + let changeIsNeeded = false + for (const txid in isRequired) { + formatted.msg += ' txidAdded ' + txid + // @ts-ignore + formatted.inputs.push(isRequired[txid]) + changeBN.add(isRequired[txid].value * 1) + changeIsNeeded = true + } + + + const addressForChange = await this._addressForChange(data) + for (output of outputs) { + if (output.address) { + formatted.outputs.push({ + // @ts-ignore + to: output.address, + // @ts-ignore + amount: output.value.toString() + }) + } else if (addressForChange === data.addressTo) { + changeIsNeeded = true + changeBN.add(output.value) + } else if (changeIsNeeded) { + changeIsNeeded = false + changeBN.add(output.value) + formatted.outputs.push({ + // @ts-ignore + to: addressForChange, + // @ts-ignore + amount: changeBN.toString(), + // @ts-ignore + isChange: true + }) + } else { + formatted.outputs.push({ + // @ts-ignore + to: addressForChange, + // @ts-ignore + amount: output.value.toString(), + // @ts-ignore + isChange: true + }) + } + } + + if (changeIsNeeded) { + // @ts-ignore + if (this._builderSettings.changeTogether && typeof formatted.outputs[0] !== 'undefined' && addressForChange === data.addressTo && addressForChange === formatted.outputs[0].to) { + // @ts-ignore + changeBN.add(formatted.outputs[0].amount) + // @ts-ignore + formatted.outputs[0].amount = changeBN.toString() + } else { + formatted.outputs.push({ + // @ts-ignore + to: addressForChange, + // @ts-ignore + amount: changeBN.toString(), + // @ts-ignore + isChange: true + }) + } + } + + // @ts-ignore + return formatted + } + + async getInputsOutputs(data: BlocksoftBlockchainTypes.TransferData, + unspents: BlocksoftBlockchainTypes.UnspentTx[], + feeToCount: { feeForByte?: string, feeForAll?: string, autoFeeLimitReadable?: string | number }, + additionalData: BlocksoftBlockchainTypes.TransferAdditionalData, + subtitle: string = 'default') + : Promise { + return this._getInputsOutputs(data, unspents, feeToCount, additionalData, subtitle) + } + + async _getInputsOutputs(data: BlocksoftBlockchainTypes.TransferData, + unspents: BlocksoftBlockchainTypes.UnspentTx[], + feeToCount: { feeForByte?: string, feeForAll?: string, autoFeeLimitReadable?: string | number }, + additionalData: BlocksoftBlockchainTypes.TransferAdditionalData, + subtitle: string = 'default') + : Promise { + if (typeof data.addressFrom === 'undefined') { + throw new Error('DogeTxInputsOutputs.getInputsOutputs requires addressFrom') + } + if (typeof data.addressTo === 'undefined') { + throw new Error('DogeTxInputsOutputs.getInputsOutputs requires addressTo') + } + if (typeof data.amount === 'undefined') { + throw new Error('DogeTxInputsOutputs.getInputsOutputs requires amount') + } + + const filteredUnspents = [] + const unconfirmedBN = new BlocksoftBN(0) + + const isRequired: any = {} + let isFoundSpeedUp = false + const filteredBN = new BlocksoftBN(0) + for (const unspent of unspents) { + if (typeof data.transactionSpeedUp !== 'undefined' && unspent.txid === data.transactionSpeedUp) { + unspent.isRequired = true + isFoundSpeedUp = true + } + if (unspent.isRequired) { + filteredUnspents.push(unspent) + filteredBN.add(unspent.value) + if (unspent.isRequired && typeof isRequired[unspent.txid] === 'undefined') { + isRequired[unspent.txid] = unspent + } + } else { + const diff = BlocksoftUtils.diff(unspent.value, this._minOutputDust) + if (diff * 1 < 0) { + // skip as dust + // @ts-ignore + BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeTxInputsOutputs unspent skipped as dust ' + this._minOutputDust + ' diff ' + diff, unspent) + } else if (!data.useOnlyConfirmed || unspent.confirmations > 0) { + filteredUnspents.push(unspent) + filteredBN.add(unspent.value) + } else { + unconfirmedBN.add(unspent.value) + } + } + } + + if (typeof data.transactionSpeedUp !== 'undefined' && !isFoundSpeedUp) { + throw new Error('SERVER_RESPONSE_NO_TX_TO_SPEEDUP') + } + + if (filteredUnspents.length === 0 && unspents.length !== 0) { + throw new Error('SERVER_RESPONSE_WAIT_FOR_CONFIRM') + } + + const totalBalanceBN = new BlocksoftBN(0) + for (const unspent of filteredUnspents) { + totalBalanceBN.add(unspent.value) + } + + let { + multiAddress, + wishedAmountBN, + outputs + } = this._usualTargets(data, unspents) + + if (typeof feeToCount.feeForByte !== 'undefined' && feeToCount.feeForByte !== 'none') { + const result = await this._coinSelect(data, filteredUnspents, feeToCount.feeForByte, multiAddress, subtitle) + if (result.inputs.length > 0) { + return result + } + } + + + const ic = filteredUnspents.length + let msg = 'v20 ' + subtitle + ' totalInputs ' + ic + + ' totalBalance ' + totalBalanceBN.get() + ' = ' + BlocksoftUtils.toUnified(totalBalanceBN.get(), this._settings.decimals) + + ' for wishedAmount ' + wishedAmountBN.get() + ' = ' + BlocksoftUtils.toUnified(wishedAmountBN.get(), this._settings.decimals) + let autocalculateFee = false + if (typeof feeToCount.feeForAll === 'undefined') { + autocalculateFee = true + msg += ' and autocalculate feeForByte ' + feeToCount.feeForByte + } else { + if (data.isTransferAll && typeof feeToCount.feeForAllInputs !== 'undefined' && feeToCount.feeForAllInputs * 1 > 0 ) { + feeToCount.feeForAll = BlocksoftUtils.mul(feeToCount.feeForAll, Math.ceil(filteredUnspents.length / feeToCount.feeForAllInputs)) + wishedAmountBN = new BlocksoftBN(BlocksoftUtils.diff(totalBalanceBN, feeToCount.feeForAll)) + outputs[0].amount = wishedAmountBN.toString() + msg += ' and isTransferAll inputs counted ' + } + msg += ' and prefee ' + feeToCount.feeForAll + ' = ' + BlocksoftUtils.toUnified(feeToCount.feeForAll.toString(), this._settings.decimals) + } + const inputs = [] + const inputsBalanceBN = new BlocksoftBN(0) + + const wishedAmountWithFeeBN = new BlocksoftBN(wishedAmountBN) + const autoFeeBN = new BlocksoftBN(0) + if (autocalculateFee) { + const tmp = BlocksoftUtils.mul(this.SIZE_FOR_BASIC, feeToCount.feeForByte) + wishedAmountWithFeeBN.add(tmp) + autoFeeBN.add(tmp) + msg += ' auto => ' + BlocksoftUtils.toUnified(autoFeeBN.get(), this._settings.decimals) + } else { + wishedAmountWithFeeBN.add(feeToCount.feeForAll) + } + + for (let i = 0; i < ic; i++) { + if (!data.isTransferAll) { + const tmp = new BlocksoftBN(wishedAmountWithFeeBN).diff(inputsBalanceBN) + if (tmp.lessThanZero()) { + msg += ' finished by collectedAmount ' + inputsBalanceBN.get() + ' = ' + BlocksoftUtils.toUnified(inputsBalanceBN.get(), this._settings.decimals) + msg += ' on wishedAmountWithFee ' + wishedAmountWithFeeBN.get() + ' = ' + BlocksoftUtils.toUnified(wishedAmountWithFeeBN.get(), this._settings.decimals) + break + } + } + const unspent = filteredUnspents[i] + inputs.push(unspent) + inputsBalanceBN.add(unspent.value) + if (typeof isRequired[unspent.txid] !== 'undefined') { + delete isRequired[unspent.txid] + } + if (autocalculateFee) { + let size = this.SIZE_FOR_INPUT + if (typeof unspent.address !== 'undefined' && unspent.address && unspent.address.toString().indexOf('bc1') === 0) { + size = this.SIZE_FOR_BC + } + const tmp2 = BlocksoftUtils.mul(size, feeToCount.feeForByte) + autoFeeBN.add(tmp2) + wishedAmountWithFeeBN.add(tmp2) + msg += ' auto => ' + BlocksoftUtils.toUnified(autoFeeBN.get(), this._settings.decimals) + } + } + + for (const txid in isRequired) { + msg += ' txidAdded ' + txid + inputs.push(isRequired[txid]) + inputsBalanceBN.add(isRequired[txid].value) + } + + const leftForChangeDiff = new BlocksoftBN(inputsBalanceBN).diff(wishedAmountWithFeeBN) + + if (leftForChangeDiff.lessThanZero()) { + if (autocalculateFee) { + const newData = JSON.parse(JSON.stringify(data)) + const autoFeeLimit = BlocksoftUtils.fromUnified(feeToCount.autoFeeLimitReadable, this._settings.decimals) + const autoDiff = new BlocksoftBN(autoFeeLimit).diff(autoFeeBN) + + let recountWithFee = autoFeeBN.get() + if (autoDiff.lessThanZero()) { + recountWithFee = autoFeeLimit.toString() + } + const res = await this._getInputsOutputs(newData, unspents, { feeForAll: recountWithFee }, additionalData, subtitle + ' notEnough1 leftForChangeDiff ' + leftForChangeDiff.toString() + ' //// ') + if (res.msg.indexOf('RECHECK') === -1) { + return res + } + } else if (subtitle.indexOf('notEnough1') !== -1) { + const newData = JSON.parse(JSON.stringify(data)) + const tmp = leftForChangeDiff.get().replace('-', '') + const tmp2 = new BlocksoftBN(data.amount).diff(tmp) + if (!tmp2.lessThanZero()) { + if (this._settings.currencyCode === 'USDT') { + console.log('adsfgadfgadfg') + console.log('tmp2', tmp2) + } else { + newData.amount = tmp2.get() + return this._getInputsOutputs(newData, unspents, feeToCount, additionalData, subtitle + ' notEnough3 ' + data.amount + ' => ' + newData.amount + ' leftForChangeDiff ' + leftForChangeDiff.toString() + ' //// ') + } + } else { + // @ts-ignore + return { + inputs: [], + outputs: [], + msg: subtitle + ' notEnough3Stop' + data.amount + ' => ' + newData.amount + ' leftForChangeDiff ' + leftForChangeDiff.toString() + ' ' + msg, + // @ts-ignore + multiAddress, + countedFor: 'DOGE' + } + } + } + // no change + msg += ' will transfer all but later will RECHECK as change ' + leftForChangeDiff.toString() + return { + inputs, + outputs, + msg, + // @ts-ignore + multiAddress, + countedFor: 'DOGE' + } + } + + const changeDiff = new BlocksoftBN(leftForChangeDiff).diff(this._minChangeDust) + if (changeDiff.lessThanZero()) { + // no change + msg += ' will transfer all as change ' + leftForChangeDiff.toString() + ' - dust = ' + changeDiff.toString() + return { + inputs, + outputs, + msg, + // @ts-ignore + multiAddress, + countedFor: 'DOGE' + } + } + + + msg += ' will have change as change ' + leftForChangeDiff.toString() + ' = ' + BlocksoftUtils.toUnified(leftForChangeDiff.toString(), this._settings.decimals) + const addressForChange = await this._addressForChange(data) + if (this._builderSettings.changeTogether && addressForChange === data.addressTo) { + leftForChangeDiff.add(outputs[0].amount) + outputs[0].amount = leftForChangeDiff.toString() + } else { + outputs.push( + { + to: addressForChange, + amount: leftForChangeDiff.toString(), + isChange: true + } as BlocksoftBlockchainTypes.OutputTx + ) + } + return { + inputs, + outputs, + msg, + // @ts-ignore + multiAddress, + countedFor: 'DOGE' + } + } +} diff --git a/crypto/blockchains/etc/EtcTransferProcessor.ts b/crypto/blockchains/etc/EtcTransferProcessor.ts new file mode 100644 index 000000000..604fbca5c --- /dev/null +++ b/crypto/blockchains/etc/EtcTransferProcessor.ts @@ -0,0 +1,13 @@ +/** + * @author Ksu + * @version 0.43 + */ +import { BlocksoftBlockchainTypes } from '@crypto/blockchains/BlocksoftBlockchainTypes' +import BnbSmartTransferProcessor from '@crypto/blockchains/bnb_smart/BnbSmartTransferProcessor' + +export default class EtcTransferProcessor extends BnbSmartTransferProcessor implements BlocksoftBlockchainTypes.TransferProcessor { + + canRBF(data: BlocksoftBlockchainTypes.DbAccount, transaction: BlocksoftBlockchainTypes.DbTransaction, source: string): boolean { + return false + } +} diff --git a/crypto/blockchains/eth/EthAddressProcessor.js b/crypto/blockchains/eth/EthAddressProcessor.js new file mode 100644 index 000000000..4cf7b25f5 --- /dev/null +++ b/crypto/blockchains/eth/EthAddressProcessor.js @@ -0,0 +1,39 @@ +/** + * @version 0.5 + */ +import EthBasic from './basic/EthBasic' + +export default class EthAddressProcessor extends EthBasic{ + + async setBasicRoot(root) { + + } + + /** + * @param {string|Buffer} privateKey + * @param {*} data + * @returns {Promise<{privateKey: string, address: string, addedData: *}>} + */ + async getAddress(privateKey, data = {}) { + // noinspection JSCheckFunctionSignatures + privateKey = '0x' + privateKey.toString('hex') + // noinspection JSUnresolvedVariable + const account = this._web3.eth.accounts.privateKeyToAccount(privateKey) + return { address: account.address, privateKey, addedData : false} + } + + /** + * @param {string} msg + * @param {string} privateKey + * @returns {Promise<{message: string, messageHash: string, v: string, r: string,s: string, signature: string}>} + */ + async signMessage(msg, privateKey) { + // noinspection JSUnresolvedVariable + if (privateKey.substr(0, 2) !== '0x') { + privateKey = '0x' + privateKey + } + // noinspection JSUnresolvedVariable + const signData = await this._web3.eth.accounts.sign(msg, privateKey) + return signData + } +} diff --git a/crypto/blockchains/eth/EthScannerProcessor.js b/crypto/blockchains/eth/EthScannerProcessor.js new file mode 100644 index 000000000..799c331fd --- /dev/null +++ b/crypto/blockchains/eth/EthScannerProcessor.js @@ -0,0 +1,785 @@ +/** + * @version 0.5 + */ +import BlocksoftUtils from '../../common/BlocksoftUtils' +import BlocksoftAxios from '../../common/BlocksoftAxios' +import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog' +import EthBasic from './basic/EthBasic' +import BlocksoftExternalSettings from '../../common/BlocksoftExternalSettings' +import BlocksoftBN from '../../common/BlocksoftBN' + +import EthTmpDS from './stores/EthTmpDS' +import EthRawDS from './stores/EthRawDS' +import config from '@app/config/config' +import BlocksoftPrettyNumbers from '@crypto/common/BlocksoftPrettyNumbers' + +const CACHE_GET_MAX_BLOCK = { + ETH: { max_block_number: 0, confirmations: 0 }, + BNB: { max_block_number: 0, confirmations: 0 }, + ETC: { max_block_number: 0, confirmations: 0 }, + AMB: { max_block_number: 0, confirmations: 0 }, + MATIC : { max_block_number: 0, confirmations: 0 }, + FTM : { max_block_number: 0, confirmations: 0 }, + RSK : { max_block_number: 0, confirmations: 0 }, + OPTIMISM : { max_block_number: 0, confirmations: 0 }, + METIS : { max_block_number: 0, confirmations: 0 }, + VLX : { max_block_number: 0, confirmations: 0 }, +} +const CACHE_BLOCK_NUMBER_TO_HASH = { + ETH: {}, + BNB: {}, + ETC : {}, + AMB : {}, + MATIC : {}, + FTM : {}, + RSK : {}, + OPTIMISM : {}, + METIS : {}, + VLX : {} +} + +const CACHE_VALID_TIME = 30000 // 30 seconds +const CACHE = { + ETH: {}, + BNB: {}, + ETC : {}, + AMB : {}, + MATIC : {}, + FTM : {}, + RSK : {}, + OPTIMISM : {}, + METIS : {}, + VLX : {} +} + +export default class EthScannerProcessor extends EthBasic { + /** + * @type {number} + * @private + */ + _blocksToConfirm = 10 + + /** + * @type {boolean} + * @private + */ + _useInternal = true + + /** + * https://eth1.trezor.io/api/v2/address/0x8b661361Be29E688Dda65b323526aD536c8B3997?details=txs + * @param address + * @returns {Promise} + * @private + */ + async _get(_address) { + const address = _address.toLowerCase() + + try { + this._trezorServer = await BlocksoftExternalSettings.getTrezorServer(this._trezorServerCode, 'ETH.Scanner._get') + } catch (e) { + throw new Error(e.message + ' while getTrezorServer ' + this._trezorServerCode) + } + + if (typeof this._trezorServer === 'undefined') { + BlocksoftCryptoLog.err(this._settings.currencyCode + ' EthScannerProcessor._get empty trezorServer') + throw new Error(this._settings.currencyCode + ' EthScannerProcessor._get empty trezorServer') + } + + if (!this._trezorServer) { + return false + } + + const now = new Date().getTime() + if (typeof CACHE[this._mainCurrencyCode] !== 'undefined' && typeof CACHE[this._mainCurrencyCode][address] !== 'undefined' && (now - CACHE[this._mainCurrencyCode][address].time < CACHE_VALID_TIME)) { + CACHE[this._mainCurrencyCode][address].provider = 'trezor-cache' + return CACHE[this._mainCurrencyCode][address] + } + + let link = this._trezorServer + '/api/v2/address/' + address + '?details=txs' + let res = await BlocksoftAxios.getWithoutBraking(link) + + if (!res || !res.data) { + BlocksoftExternalSettings.setTrezorServerInvalid(this._trezorServerCode, this._trezorServer) + this._trezorServer = await BlocksoftExternalSettings.getTrezorServer(this._trezorServerCode, this._settings.currencyCode + ' ETH.Scanner._get') + if (typeof this._trezorServer === 'undefined') { + BlocksoftCryptoLog.err(this._settings.currencyCode + ' EthScannerProcessor._get empty trezorServer2') + throw new Error(this._settings.currencyCode + ' EthScannerProcessor._get empty trezorServer2') + } + link = this._trezorServer + '/api/v2/address/' + address + '?details=txs' + res = await BlocksoftAxios.getWithoutBraking(link) + if (!res || !res.data) { + BlocksoftExternalSettings.setTrezorServerInvalid(this._trezorServerCode, this._trezorServer) + return false + } + } + + if (typeof res.data.balance === 'undefined') { + throw new Error(this._settings.currencyCode + ' EthScannerProcessor._get nothing loaded for address ' + link) + } + const data = res.data + data.totalTokens = 0 + data.formattedTokens = {} + //await BlocksoftCryptoLog.log('EthScannerProcessor._get ERC20 tokens ' + JSON.stringify(data.tokens)) + if (typeof data.tokens !== 'undefined') { + let token + for (token of data.tokens) { + data.formattedTokens[token.contract.toLowerCase()] = token + } + } + if (typeof CACHE[this._mainCurrencyCode][address] !== 'undefined') { + if (CACHE[this._mainCurrencyCode][address].data.nonce > res.data.nonce) { + return false + } + } + CACHE[this._mainCurrencyCode][address] = { + data, + provider: 'trezor', + time: now + } + return CACHE[this._mainCurrencyCode][address] + } + + /** + * @param {string} address + * @return {Promise<{balance, unconfirmed, provider}>} + */ + async getBalanceBlockchain(address) { + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthScannerProcessor.getBalance started ' + address) + + this.checkWeb3CurrentServerUpdated() + // noinspection JSUnresolvedVariable + let balance = 0 + let provider = '' + let time = 0 + try { + if (this._trezorServerCode && this._trezorServerCode.indexOf('http') === -1) { + const res = await this._get(address) + + if (res && typeof res.data !== 'undefined' && res.data && typeof res.data.balance !== 'undefined') { + balance = res.data.balance + provider = res.provider + time = res.time + return { balance, unconfirmed: 0, provider, time, balanceScanBlock: res.data.nonce } + } + } + } catch (e) { + if (config.debug.cryptoErrors) { + console.log(this._settings.currencyCode + ' EthScannerProcessor.getBalance ' + address + ' error ' + e.message) + } + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthScannerProcessor.getBalance ' + address + ' trezor error ' + e.message) + return false + } + + try { + balance = await this._web3.eth.getBalance(address) + BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthScannerProcessor.getBalance ' + address + ' result ' + JSON.stringify(balance)) + provider = 'web3' + time = 'now()' + return { balance, unconfirmed: 0, provider, time } + } catch (e) { + if (config.debug.cryptoErrors) { + console.log(this._settings.currencyCode + ' EthScannerProcessor.getBalance ' + address + ' ' + this._web3.LINK + ' rpc error ' + e.message) + } + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthScannerProcessor.getBalance ' + address + ' ' + this._web3.LINK + ' rpc error ' + e.message) + return false + } + } + + /** + * @param {string} scanData.account.address + * @return {Promise<[UnifiedTransaction]>} + */ + async getTransactionsBlockchain(scanData) { + const address = scanData.account.address + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthScannerProcessor.getTransactions started ' + address) + let res = false + if (this._settings.currencyCode !== 'ETH_ROPSTEN' && this._settings.currencyCode !== 'ETH_RINKEBY' && this._trezorServerCode) { + try { + res = await this._get(address) + } catch (e) { + throw new Error(e.message + ' in EthScannerProcessor._get') + } + } + + let transactions + if (res && typeof res.data !== 'undefined' && res.data) { + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthScannerProcessor.getBalance loaded from ' + res.provider + ' ' + res.time) + if (this._tokenAddress && typeof res.data.formattedTokens[this._tokenAddress] === 'undefined') { + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthScannerProcessor.getTransactions skipped token ' + this._tokenAddress + ' ' + address) + return false + } + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthScannerProcessor.getTransactions trezor unify started ' + address) + transactions = await this._unifyTransactions(address, res.data.transactions, false, true, {}) + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthScannerProcessor.getTransactions trezor finished ' + address) + } else if (this._oklinkAPI) { + let logTitle = this._settings.currencyCode + ' EthScannerProcessor.getTransactions oklink ' + transactions = await this._getFromOklink(address, this._oklinkAPI, logTitle, {}) + } else { + if (!this._etherscanApiPath) { + BlocksoftCryptoLog.err(this._settings.currencyCode + ' EthScannerProcessor.getTransactions no _etherscanApiPath') + } + let link = this._etherscanApiPath + '&address=' + address + let logTitle = this._settings.currencyCode + ' EthScannerProcessor.getTransactions etherscan ' + transactions = await this._getFromEtherscan(address, link, logTitle, false, {}) + if (this._etherscanApiPathDeposits) { + link = this._etherscanApiPathDeposits + '&address=' + address + logTitle = this._settings.currencyCode + ' EthScannerProcessor.getTransactions etherscan deposits' + transactions = await this._getFromEtherscan(address, link, logTitle, false, transactions) + } + + if (this._useInternal && this._etherscanApiPathInternal) { + link = this._etherscanApiPathInternal + '&address=' + address + logTitle = this._settings.currencyCode + ' EthScannerProcessor.getTransactions etherscan forInternal' + transactions = await this._getFromEtherscan(address, link, logTitle, true, transactions) + } + } + if (!transactions) { + return [] + } + const reformatted = [] + for (const key in transactions) { + reformatted.push(transactions[key]) + } + return reformatted + } + + async _getFromOklink(address, key, logTitle, transactions = {}) { + const link = 'https://www.oklink.com/api/v5/explorer/address/transaction-list?chainShortName=ethw&address=' + address + const tmp = await BlocksoftAxios.getWithHeaders(link, {'Ok-Access-Key': key}) + if (typeof tmp?.data?.data === 'undefined' || typeof tmp?.data?.data[0] === 'undefined' || typeof tmp?.data?.data[0]?.transactionLists === 'undefined') { + return [] + } + await BlocksoftCryptoLog.log(logTitle + ' started ', link) + const list = tmp.data.data[0].transactionLists + for (const tx of list) { + const transaction = await this._unifyTransactionOklink(address, tx) + if (transaction) { + transactions[transaction.transactionHash] = transaction + } + } + await BlocksoftCryptoLog.log(logTitle + ' finished ' + address) + return transactions + } + + + async _getFromEtherscan(address, link, logTitle, isInternal, transactions = {}) { + + await BlocksoftCryptoLog.log(logTitle + ' started ' + JSON.stringify(isInternal), link) + const tmp = await BlocksoftAxios.getWithoutBraking(link) + if (!tmp || typeof tmp.data === 'undefined' || !tmp.data || typeof tmp.data.result === 'undefined') { + return transactions + } + if (typeof tmp.data.result === 'string') { + if (tmp.data.result.indexOf('API Key') === -1) { + throw new Error('Undefined txs etherscan ' + link + ' ' + tmp.data.result) + } else { + return transactions + } + } + + transactions = await this._unifyTransactions(address, tmp.data.result, isInternal, false, transactions) + await BlocksoftCryptoLog.log(logTitle + ' finished ' + address) + return transactions + } + + + /** + * @param {string} txHash + * @return {Promise<[UnifiedTransaction]>} + */ + async getTransactionBlockchain(txHash) { + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthScannerProcessor.getTransaction started ' + txHash) + + + this._trezorServer = await BlocksoftExternalSettings.getTrezorServer(this._trezorServerCode, this._settings.currencyCode + ' ETH.Scanner.getTransaction') + + if (typeof this._trezorServer === 'undefined') { + BlocksoftCryptoLog.err(this._settings.currencyCode + ' EthScannerProcessor.getTransaction empty trezorServer') + throw new Error(this._settings.currencyCode + ' EthScannerProcessor.getTransaction empty trezorServer') + } + + if (!this._trezorServer) { + return false + } + + let link = this._trezorServer + '/api/v2/tx-specific/' + txHash + let res = await BlocksoftAxios.getWithoutBraking(link) + + if (!res || !res.data) { + BlocksoftExternalSettings.setTrezorServerInvalid(this._trezorServerCode, this._trezorServer) + this._trezorServer = await BlocksoftExternalSettings.getTrezorServer(this._trezorServerCode, this._settings.currencyCode + ' ETH.Scanner._get') + if (typeof this._trezorServer === 'undefined') { + BlocksoftCryptoLog.err(this._settings.currencyCode + ' EthScannerProcessor._get empty trezorServer2') + throw new Error(this._settings.currencyCode + ' EthScannerProcessor._get empty trezorServer2') + } + link = this._trezorServer + '/api/v2/tx-specific/' + txHash + res = await BlocksoftAxios.getWithoutBraking(link) + if (!res || !res.data) { + BlocksoftExternalSettings.setTrezorServerInvalid(this._trezorServerCode, this._trezorServer) + return false + } + } + + if (typeof res.data.tx === 'undefined') { + return false + } + + let tx + if (typeof res.data.receipt === 'undefined') { + tx = { ...{ status: 0x0 }, ...res.data.tx } + } else { + tx = { ...res.data.receipt, ...res.data.tx } + } + + tx.nonce = BlocksoftUtils.hexToDecimal(tx.nonce) + if (tx.nonce * 1 === 0) { + tx.nonce = 0 + } + tx.status = BlocksoftUtils.hexToDecimal(tx.status) + tx.gas = BlocksoftUtils.hexToDecimal(tx.gas) + tx.gasPrice = BlocksoftUtils.hexToDecimal(tx.gasPrice) + tx.gasUsed = BlocksoftUtils.hexToDecimal(tx.gasUsed) + return tx + } + + + /** + * @param {string} address + * @param {*} result[] + * @param {boolean} isInternal + * @returns {Promise<[{UnifiedTransaction}]>} + * @private + */ + async _unifyTransactions(_address, result, isInternal, isTrezor = false, transactions = {}) { + if (!result) { + return transactions + } + const address = _address.toLowerCase() + let tx + let maxNonce = -1 + let maxSuccessNonce = -1 + + const notBroadcasted = await EthRawDS.getForAddress({ address, currencyCode: this._settings.currencyCode }) + for (tx of result) { + try { + + let transaction + const key = typeof tx.hash !== 'undefined' ? tx.hash : tx.txid + if (typeof transactions[key] !== 'undefined') { + continue + } + if (isTrezor) { + transaction = await this._unifyTransactionTrezor(address, tx, isInternal) + } else { + transaction = await this._unifyTransaction(address, tx, isInternal) + } + if (transaction) { + transactions[key] = transaction + if ( + typeof transaction.transactionJson !== 'undefined' + && typeof transaction.transactionJson.feeType === 'undefined' + && (transaction.transactionDirection === 'outcome' || transaction.transactionDirection === 'self') + && typeof transaction.transactionJson.nonce !== 'undefined') { + + const uniqueFrom = address.toLowerCase() + '_' + transaction.transactionJson.nonce + if (notBroadcasted && typeof notBroadcasted[uniqueFrom] !== 'undefined' && transaction.transactionStatus !== 'new') { + EthRawDS.cleanRaw({ + address, + transactionUnique: uniqueFrom, + currencyCode: this._settings.currencyCode + }) + } + if (transaction.transactionJson.nonce * 1 > maxNonce) { + maxNonce = transaction.transactionJson.nonce * 1 + } + if ((transaction.transactionStatus === 'success' || transaction.transactionStatus === 'confirming')) { + if (transaction.transactionJson.nonce * 1 > maxSuccessNonce) { + maxSuccessNonce = transaction.transactionJson.nonce * 1 + } + } + } + } + } catch (e) { + BlocksoftCryptoLog.err(this._settings.currencyCode + ' EthScannerProcessor._unifyTransaction error ' + e.message + ' on ' + (isTrezor ? 'Trezor' : 'usual') + ' tx ' + JSON.stringify(tx)) + } + } + + if (maxNonce > -1) { + await EthTmpDS.saveNonce(this._mainCurrencyCode, address, 'maxScanned', maxNonce) + } + + if (maxSuccessNonce > -1) { + await EthTmpDS.saveNonce(this._mainCurrencyCode, address, 'maxSuccess', maxSuccessNonce) + } + + return transactions + } + + /** + * @param {string} address + * @param {Object} transaction + * @param {string} transaction.txid: "0xdbbce8ace9ecfa2bcd2a5ff54590a9f3b9c445c1111f1b9404ec33ef2314a864" + * @param {string} transaction.vin[].addresses[] + * @param {string} transaction.vin[].isAddress + * @param {string} transaction.vout[].addresses[] + * @param {string} transaction.vout[].isAddress + * @param {string} transaction.blockHash: "0xf31e629dea39a96da1ff977fc991552c5f131c4b544a87fbea0533e985ce7e69" + * @param {string} transaction.blockHeight: 9409918 + * @param {string} transaction.confirmations: 78610 + * @param {string} transaction.blockTime: 1580737403 + * @param {string} transaction.value: "12559200000000000" + * @param {string} transaction.fees: "69854400000000" + * @param {string} transaction.ethereumSpecific.status 1 + * @param {string} transaction.ethereumSpecific.nonce 42458 + * @param {string} transaction.ethereumSpecific.gasLimit 150000 + * @param {string} transaction.ethereumSpecific.gasUsed 21000 + * @param {string} transaction.ethereumSpecific.gasPrice "3326400000" + * @param {array} transaction.tokenTransfers + * @param {string} transaction.tokenTransfers[].type "ERC20" + * @param {string} transaction.tokenTransfers[].from "0x8b661361be29e688dda65b323526ad536c8b3997" + * @param {string} transaction.tokenTransfers[].to "0xa00ed7686c380740fe2adb141136c217b90c5ca5" + * @param {string} transaction.tokenTransfers[].token "0xdac17f958d2ee523a2206206994597c13d831ec7" + * @param {string} transaction.tokenTransfers[].name "Tether USD" + * @param {string} transaction.tokenTransfers[].symbol "USDT" + * @param {string} transaction.tokenTransfers[].decimals 6 + * @param {string} transaction.tokenTransfers[].value "1000000" + * @private + */ + async _unifyTransactionTrezor(_address, transaction, isInternal = false) { + let fromAddress = '' + const address = _address.toLowerCase() + if (typeof transaction.vin[0] !== 'undefined' && transaction.vin[0].addresses && typeof transaction.vin[0].addresses[0] !== 'undefined') { + fromAddress = transaction.vin[0].addresses[0].toLowerCase() + } + let toAddress = '' + if (typeof transaction.vout[0] !== 'undefined' && transaction.vout[0].addresses && typeof transaction.vout[0].addresses[0] !== 'undefined') { + toAddress = transaction.vout[0].addresses[0].toLowerCase() + } + let amount = transaction.value + + let nonce = transaction.ethereumSpecific.nonce + if (nonce * 1 === 0) { + nonce = 0 + } + const additional = { + nonce, + gas: transaction.ethereumSpecific.gasLimit || '', + gasPrice: transaction.ethereumSpecific.gasPrice || '', + gasUsed: transaction.ethereumSpecific.gasUsed || '' + } + let fee = transaction.fees || 0 + let feeCurrencyCode = this._mainCurrencyCode + + if (this._tokenAddress) { + let failToken = false + if (typeof transaction.tokenTransfers === 'undefined') { + if (this._tokenAddress === toAddress) { + failToken = true + } else { + return false + } + } + if (!failToken) { + let tmp + let found = false + amount = new BlocksoftBN(0) + for (tmp of transaction.tokenTransfers) { + if (tmp.token.toLowerCase() === this._tokenAddress.toLowerCase()) { + tmp.from = tmp.from.toLowerCase() + tmp.to = tmp.to.toLowerCase() + if (tmp.to !== address && tmp.from !== address) { + continue + } + if (tmp.to === address) { + fromAddress = tmp.from + amount.add(tmp.value) + } else if (tmp.from === address) { + if (this._delegateAddress && tmp.to.toLowerCase() === this._delegateAddress.toLowerCase()) { + fee = tmp.value + additional.feeType = 'DELEGATE' + feeCurrencyCode = this._settings.currencyCode || 'DELEGATE' + } else { + toAddress = tmp.to + amount.diff(tmp.value) + } + } + found = true + } + } + amount = amount.get() + if (amount < 0) { + amount = -1 * amount + fromAddress = address + } else { + toAddress = address + } + if (!found) { + return false + } + } + } + + if (typeof transaction.blockTime === 'undefined') { + throw new Error(' no transaction.blockTime error transaction data ' + JSON.stringify(transaction)) + } + let formattedTime = transaction.blockTime + try { + formattedTime = BlocksoftUtils.toDate(transaction.blockTime) + } catch (e) { + e.message += ' timestamp error transaction data ' + JSON.stringify(transaction) + throw e + } + + let blockHash = false + const confirmations = transaction.confirmations + try { + CACHE_BLOCK_NUMBER_TO_HASH[this._mainCurrencyCode][transaction.blockHeight] = transaction.blockHash + if (typeof transaction.blockHash !== 'undefined') { + blockHash = transaction.blockHash + } + if (confirmations > 0 && transaction.blockHeight > CACHE_GET_MAX_BLOCK[this._mainCurrencyCode].max_block_number) { + CACHE_GET_MAX_BLOCK[this._mainCurrencyCode].max_block_number = transaction.blockHeight + CACHE_GET_MAX_BLOCK[this._mainCurrencyCode].confirmations = confirmations + } + } catch (e) { + throw new Error(e.message + ' in CACHE_GET_MAX_BLOCK ' + this._mainCurrencyCode ) + } + let transactionStatus = 'new' + + if (blockHash) { + if (transaction.ethereumSpecific.status === 1) { + if (confirmations > this._blocksToConfirm) { + transactionStatus = 'success' + } else if (confirmations > 0) { + transactionStatus = 'confirming' + } + } else { + transactionStatus = 'fail' + } + } + + const tx = { + transactionHash: transaction.txid.toLowerCase(), + blockHash: blockHash || '', + blockNumber: +transaction.blockHeight, + blockTime: formattedTime, + blockConfirmations: confirmations, + transactionDirection: (address.toLowerCase() === fromAddress.toLowerCase()) ? 'outcome' : 'income', + addressFrom: address.toLowerCase() === fromAddress.toLowerCase() ? '' : fromAddress, + addressTo: address.toLowerCase() === toAddress.toLowerCase() ? '' : toAddress, + addressFromBasic: fromAddress.toLowerCase(), + addressAmount: amount, + transactionStatus: transactionStatus, + transactionFee: fee, + transactionFeeCurrencyCode: feeCurrencyCode, + contractAddress: '', + inputValue: '' + } + if (tx.addressFrom === '' && tx.addressTo === '') { + tx.transactionDirection = 'self' + // self zero will not shown if uncomment! tx.addressAmount = 0 + } + if (additional) { + tx.transactionJson = additional + } + return tx + } + + /** + * @param {string} address + * @param {Object} transaction + * @param {string} transaction.amount 0.001852572296633876 + * @param {string} transaction.blockHash 0x3661deb1c783baed6b1917a1f58ad460ad9cbaac5977919cfd75019e6a12363a + * @param {string} transaction.challengeStatus + * @param {string} transaction.from 0xf1cff704c6e6ce459e3e1544a9533cccbdad7b99 + * @param {string} transaction.height 5495990 + * @param {string} transaction.isFromContract false + * @param {string} transaction.isToContract false + * @param {string} transaction.l1OriginHash '' + * @param {string} transaction.methodId '' + * @param {string} transaction.state success + * @param {string} transaction.to 0xf1cff704c6e6ce459e3e1544a9533cccbdad7b99 + * @param {string} transaction.tokenContractAddress '' + * @param {string} transaction.tokenId '' + * @param {string} transaction.transactionSymbol ETHW + * @param {string} transaction.transactionTime 1662632042000 + * @param {string} transaction.txFee 0.000231 + * @param {string} transaction.txId 0x3cc404044f96f07bee0af9717d49ce72dba645c5bb5af4846be84dafa68127b1 + * @return {UnifiedTransaction} + * @protected + */ + async _unifyTransactionOklink(_address, transaction) { + if (typeof transaction.transactionTime === 'undefined') { + throw new Error(' no transaction.timeStamp error transaction data ' + JSON.stringify(transaction)) + } + + const address = _address.toLowerCase() + const formattedTime = transaction.transactionTime + + let transactionStatus = 'new' + let confirmations = 0 + if (transaction.state === 'success') { + const diff = new Date().getTime() - transaction.transactionTime + confirmations = Math.round(diff / 60000) + if (confirmations > 120) { + transactionStatus = 'success' + } else { + transactionStatus = 'confirming' + } + } else if (transaction.state === 'fail') { + transactionStatus = 'fail' + } else if (transaction.state === 'pending') { + transactionStatus = 'confirming' + } + + const tx = { + transactionHash: transaction.txId.toLowerCase() + '_1', + blockHash: transaction.blockHash, + blockNumber: +transaction.height, + blockTime: formattedTime, + blockConfirmations: confirmations, + transactionDirection: (address.toLowerCase() === transaction.from.toLowerCase()) ? 'outcome' : 'income', + addressFrom: (address.toLowerCase() === transaction.from.toLowerCase()) ? '' : transaction.from, + addressFromBasic: transaction.from.toLowerCase(), + addressTo: (address.toLowerCase() === transaction.to.toLowerCase()) ? '' : transaction.to, + addressAmount : BlocksoftPrettyNumbers.setCurrencyCode('ETH').makeUnPretty(transaction.amount), + transactionStatus: transactionStatus, + contractAddress : '', + inputValue: '', + transactionFee: BlocksoftPrettyNumbers.setCurrencyCode('ETH').makeUnPretty(transaction.txFee) + } + if (tx.addressFrom === '' && tx.addressTo === '') { + tx.transactionDirection = 'self' + tx.addressAmount = 0 + } + return tx + } + + /** + * @param {string} address + * @param {Object} transaction + * @param {string} transaction.blockNumber 4673230 + * @param {string} transaction.timeStamp 1512376529 + * @param {string} transaction.hash + * @param {string} transaction.nonce + * @param {string} transaction.blockHash + * @param {string} transaction.transactionIndex + * @param {string} transaction.from + * @param {string} transaction.to + * @param {string} transaction.value + * @param {string} transaction.gas + * @param {string} transaction.gasPrice + * @param {string} transaction.isError + * @param {string} transaction.txreceipt_status + * @param {string} transaction.input + * @param {string} transaction.type + * @param {string} transaction.contractAddress + * @param {string} transaction.cumulativeGasUsed + * @param {string} transaction.gasUsed + * @param {string} transaction.confirmations + * @param {boolean} isInternal + * @return {UnifiedTransaction} + * @protected + */ + async _unifyTransaction(_address, transaction, isInternal = false) { + if (typeof transaction.timeStamp === 'undefined') { + throw new Error(' no transaction.timeStamp error transaction data ' + JSON.stringify(transaction)) + } + + const address = _address.toLowerCase() + let formattedTime = transaction.timeStamp + try { + formattedTime = BlocksoftUtils.toDate(transaction.timeStamp) + } catch (e) { + console.log('no timestamp2') + e.message += ' timestamp error transaction data ' + JSON.stringify(transaction) + throw e + } + + let addressAmount = transaction.value + if (typeof transaction.L1TxOrigin !== 'undefined') { + if (transaction.from === '0x0000000000000000000000000000000000000000') { + transaction.from = 'ETH: ' + transaction.L1TxOrigin + } + CACHE_BLOCK_NUMBER_TO_HASH[transaction.blockNumber] = transaction.blockHash + addressAmount = transaction.tokenValue + transaction.confirmations = 100 + } else if (isInternal) { + if (transaction.contractAddress !== '') { + return false + } + if (transaction.type !== 'call') { + return false + } + + if (typeof CACHE_BLOCK_NUMBER_TO_HASH[this._mainCurrencyCode][transaction.blockNumber] === 'undefined') { + const data = await this._web3.eth.getTransaction(transaction.hash) + CACHE_BLOCK_NUMBER_TO_HASH[this._mainCurrencyCode][transaction.blockNumber] = data?.blockHash + } + transaction.blockHash = CACHE_BLOCK_NUMBER_TO_HASH[this._mainCurrencyCode][transaction.blockNumber] || transaction.blockNumber + // noinspection PointlessArithmeticExpressionJS + transaction.confirmations = CACHE_GET_MAX_BLOCK[this._mainCurrencyCode].max_block_number - transaction.blockNumber + 1 * CACHE_GET_MAX_BLOCK[this._mainCurrencyCode].confirmations + } else { + CACHE_BLOCK_NUMBER_TO_HASH[transaction.blockNumber] = transaction.blockHash + } + + const confirmations = transaction.confirmations + if (confirmations > 0 && transaction.blockNumber > CACHE_GET_MAX_BLOCK[this._mainCurrencyCode].max_block_number) { + CACHE_GET_MAX_BLOCK[this._mainCurrencyCode].max_block_number = transaction.blockNumber + CACHE_GET_MAX_BLOCK[this._mainCurrencyCode].confirmations = confirmations + } + let transactionStatus = 'new' + if (typeof transaction.txreceipt_status === 'undefined' || transaction.txreceipt_status === '1') { + if (confirmations > this._blocksToConfirm) { + transactionStatus = 'success' + } else if (confirmations > 0) { + transactionStatus = 'confirming' + } + } else if (transaction.isError !== '0') { + transactionStatus = 'fail' + } + // if (isInternal) { + // transactionStatus = 'internal_' + transactionStatus + // } + let contractAddress = false + if (typeof transaction.contractAddress !== 'undefined') { + contractAddress = transaction.contractAddress.toLowerCase() + } + const tx = { + transactionHash: transaction.hash.toLowerCase(), + blockHash: transaction.blockHash, + blockNumber: +transaction.blockNumber, + blockTime: formattedTime, + blockConfirmations: confirmations, + transactionDirection: (address.toLowerCase() === transaction.from.toLowerCase()) ? 'outcome' : 'income', + addressFrom: (address.toLowerCase() === transaction.from.toLowerCase()) ? '' : transaction.from, + addressFromBasic: transaction.from.toLowerCase(), + addressTo: (address.toLowerCase() === transaction.to.toLowerCase()) ? '' : transaction.to, + addressAmount, + transactionStatus: transactionStatus, + contractAddress, + inputValue: transaction.input + } + let nonce = transaction.nonce + if (nonce * 1 === 0) { + nonce = 0 + } + if (!isInternal) { + const additional = { + nonce, + gas: transaction.gas, + gasPrice: transaction.gasPrice, + cumulativeGasUsed: transaction.cumulativeGasUsed, + gasUsed: transaction.gasUsed, + transactionIndex: transaction.transactionIndex + } + tx.transactionJson = additional + tx.transactionFee = BlocksoftUtils.mul(transaction.gasUsed, transaction.gasPrice).toString() + } + if (tx.addressFrom === '' && tx.addressTo === '') { + tx.transactionDirection = 'self' + tx.addressAmount = 0 + } + return tx + } +} diff --git a/crypto/blockchains/eth/EthScannerProcessorErc20.js b/crypto/blockchains/eth/EthScannerProcessorErc20.js new file mode 100644 index 000000000..a56f6f8ba --- /dev/null +++ b/crypto/blockchains/eth/EthScannerProcessorErc20.js @@ -0,0 +1,79 @@ +/** + * @version 0.5 + */ +import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog' +import EthScannerProcessor from './EthScannerProcessor' +import config from '@app/config/config' + +const abi = require('./ext/erc20') + +export default class EthScannerProcessorErc20 extends EthScannerProcessor { + + /** + * @type {boolean} + * @private + */ + _useInternal = false + + constructor(settings) { + super(settings) + // noinspection JSUnresolvedVariable + this._token = new this._web3.eth.Contract(abi.ERC20, settings.tokenAddress) + this._tokenAddress = settings.tokenAddress.toLowerCase() + this._delegateAddress = (settings.delegateAddress || '').toLowerCase() + + if (this._etherscanApiPath && typeof this._etherscanApiPath !== 'undefined') { + const tmp = this._etherscanApiPath.split('/') + this._etherscanApiPath = `https://${tmp[2]}/api?module=account&action=tokentx&sort=desc&contractaddress=${settings.tokenAddress}&apikey=YourApiKeyToken` + } + } + + /** + * @param {string} address + * @return {Promise<{balance, unconfirmed, provider}>} + */ + async getBalanceBlockchain(address) { + BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthScannerProcessorErc20.getBalance started ' + address) + if (this.checkWeb3CurrentServerUpdated()) { + this._token = new this._web3.eth.Contract(abi.ERC20, this._settings.tokenAddress) + } + + // noinspection JSUnresolvedVariable + try { + let balance = 0 + let provider = '' + let time = 0 + + if (this._trezorServerCode) { + const res = await this._get(address) + if (!res || typeof res.data === 'undefined') return false + BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthScannerProcessorErc20.getBalance loaded from ' + res.provider + ' ' + res.time) + const data = res.data + + if (data && this._tokenAddress && typeof data.formattedTokens[this._tokenAddress] !== 'undefined' && typeof typeof data.formattedTokens[this._tokenAddress].balance !== 'undefined') { + balance = data.formattedTokens[this._tokenAddress].balance + if (balance === []) return false + provider = res.provider + time = res.time + return { balance, unconfirmed: 0, provider, time, balanceScanBlock: res.data.nonce } + } + } + balance = await this._token.methods.balanceOf(address).call() + BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthScannerProcessorErc20.getBalance ' + address + ' result ' + JSON.stringify(balance)) + if (balance === []) return false + + provider = 'web3' + time = 'now()' + return { balance, unconfirmed: 0, provider, time } + + } catch (e) { + if (config.debug.appErrors) { + console.log( this._settings.currencyCode + ' EthScannerProcessorErc20.getBalance ' + address + ' error ' + e.message) + } + BlocksoftCryptoLog.log( this._settings.currencyCode + ' EthScannerProcessorErc20.getBalance ' + address + ' error ' + e.message) + return false + } + + + } +} diff --git a/crypto/blockchains/eth/EthTokenProcessorErc20.js b/crypto/blockchains/eth/EthTokenProcessorErc20.js new file mode 100644 index 000000000..e07b14c59 --- /dev/null +++ b/crypto/blockchains/eth/EthTokenProcessorErc20.js @@ -0,0 +1,72 @@ +/** + * @version 0.5 + */ +import EthBasic from './basic/EthBasic' +import config from '../../../app/config/config' + +const abi = require('./ext/erc20.js') + +export default class EthTokenProcessorErc20 extends EthBasic { + /** + * @param {string} tokenAddress + * @returns {Promise<{tokenAddress: *, currencyName: *, provider: string, tokenDecimals: *, icon: boolean, description: boolean, tokenType: string, currencyCode: *}>} + */ + async getTokenDetails(tokenAddress) { + let token, name, symbol, decimals + + this.checkWeb3CurrentServerUpdated() + + try { + // noinspection JSUnresolvedVariable + token = new this._web3.eth.Contract(abi.ERC20, tokenAddress.toLowerCase()) + } catch (e) { + if (config.debug.appErrors) { + console.log('EthTokenProcessorErc20 erc20 init token error ' + e.message) + } + e.message = 'erc20 init token ' + e.message + throw e + } + try { + name = await token.methods.name().call() + } catch (e) { + e.message = 'erc20.name ' + e.message + throw e + } + + try { + symbol = await token.methods.symbol().call() + } catch (e) { + if (config.debug.appErrors) { + console.log('EthTokenProcessorErc20 erc20.symbol error ' + e.message) + } + e.message = 'erc20.symbol ' + e.message + throw e + } + + try { + decimals = await token.methods.decimals().call() + } catch (e) { + if (config.debug.appErrors) { + console.log('EthTokenProcessorErc20 erc20.decimals error ' + e.message) + } + e.message = 'erc20.decimals ' + e.message + throw e + } + + const res = { + currencyCodePrefix : 'CUSTOM_', + currencyCode: symbol, + currencyName: name, + tokenType : this._mainTokenType, + tokenAddress: tokenAddress.toLowerCase(), + tokenDecimals: decimals, + icon: false, + description: false, + provider : 'web3' + } + if (this._mainCurrencyCode !== 'ETH') { + res.currencyCodePrefix = 'CUSTOM_' + this._mainTokenType + '_' + } + return res + } +} diff --git a/crypto/blockchains/eth/EthTokenProcessorNft.js b/crypto/blockchains/eth/EthTokenProcessorNft.js new file mode 100644 index 000000000..a44d7c640 --- /dev/null +++ b/crypto/blockchains/eth/EthTokenProcessorNft.js @@ -0,0 +1,67 @@ +/** + * @version 0.50 + */ +import EthBasic from './basic/EthBasic' +import EthNftOpensea from '@crypto/blockchains/eth/apis/EthNftOpensea' +import EthNftMatic from '@crypto/blockchains/eth/apis/EthNftMatic' +import abi from './ext/erc721.js' +import config from '@app/config/config' +import BlocksoftDictNfts from '@crypto/common/BlocksoftDictNfts' + +export default class EthTokenProcessorNft extends EthBasic { + + + /** + * @param data.address + * @param data.tokenBlockchainCode + */ + async getListBlockchain(data) { + + const settings = BlocksoftDictNfts.NftsIndexed[data.tokenBlockchainCode] + if ( + typeof settings !== 'undefined' && typeof settings.apiType !== 'undefined' && settings.apiType === 'OPENSEA' + ) { + return EthNftOpensea(data) + } else { + return EthNftMatic(data) + } + } + + async getNftDetails(nftAddress, nftType) { + this.checkWeb3CurrentServerUpdated() + + let token, name, symbol + try { + token = new this._web3.eth.Contract(abi.ERC721, nftAddress.toLowerCase()) + } catch (e) { + if (config.debug.appErrors) { + console.log('EthTokenProcessorNft erc721 init token error ' + e.message) + } + e.message = 'erc721 init token ' + e.message + throw e + } + + // @todo more checks! + try { + name = await token.methods.name().call() + } catch (e) { + name = nftAddress.substr(0, 32) + } + + try { + symbol = await token.methods.symbol().call() + } catch (e) { + symbol = name.substr(0, 5) + } + + const res = { + nftSymbol: symbol, + nftCode: symbol, + nftName: name, + nftType: nftType, + nftAddress: nftAddress.toLowerCase(), + provider: 'web3' + } + return res + } +} diff --git a/crypto/blockchains/eth/EthTransferProcessor.ts b/crypto/blockchains/eth/EthTransferProcessor.ts new file mode 100644 index 000000000..48a9ef80c --- /dev/null +++ b/crypto/blockchains/eth/EthTransferProcessor.ts @@ -0,0 +1,972 @@ +/** + * @author Ksu + * @version 0.20 + */ +import BlocksoftUtils from '../../common/BlocksoftUtils' +import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog' + +import EthTmpDS from './stores/EthTmpDS' + +import EthEstimateGas from './ext/EthEstimateGas' +import EthBasic from './basic/EthBasic' +import EthNetworkPrices from './basic/EthNetworkPrices' +import EthTxSendProvider from './basic/EthTxSendProvider' + +import MarketingEvent from '../../../app/services/Marketing/MarketingEvent' +import BlocksoftDispatcher from '../BlocksoftDispatcher' +import { BlocksoftBlockchainTypes } from '../BlocksoftBlockchainTypes' + +import config from '../../../app/config/config' +import settingsActions from '../../../app/appstores/Stores/Settings/SettingsActions' +import BlocksoftExternalSettings from '../../common/BlocksoftExternalSettings' +import { sublocale } from '../../../app/services/i18n' +import abi721 from './ext/erc721.js' +import abi1155 from './ext/erc1155' +import BlocksoftAxios from '@crypto/common/BlocksoftAxios' +import OneUtils from '@crypto/blockchains/one/ext/OneUtils' + +export default class EthTransferProcessor extends EthBasic implements BlocksoftBlockchainTypes.TransferProcessor { + + + _useThisBalance: boolean = true + + needPrivateForFee(): boolean { + return false + } + + checkSendAllModal(data: { currencyCode: any }): boolean { + return true + } + + async getFeeRate(data: BlocksoftBlockchainTypes.TransferData, privateData?: BlocksoftBlockchainTypes.TransferPrivateData, additionalData: BlocksoftBlockchainTypes.TransferAdditionalData = {}): Promise { + let txRBFed = '' + let txRBF = false + + this.checkWeb3CurrentServerUpdated() + + let realAddressTo = data.addressTo + if (realAddressTo !== '' && OneUtils.isOneAddress(realAddressTo)) { + realAddressTo = OneUtils.fromOneAddress(realAddressTo) + } + if (typeof data.transactionRemoveByFee !== 'undefined' && data.transactionRemoveByFee) { + BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTransferProcessor.getFeeRate remove started ' + data.transactionRemoveByFee) + txRBF = data.transactionRemoveByFee + txRBFed = 'RBFremoved' + } else if (typeof data.transactionReplaceByFee !== 'undefined' && data.transactionReplaceByFee) { + BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTransferProcessor.getFeeRate resend started ' + data.transactionReplaceByFee) + txRBF = data.transactionReplaceByFee + txRBFed = 'RBFed' + } else if (typeof data.dexOrderData !== 'undefined' && data.dexOrderData) { + BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTransferProcessor.getFeeRate dex ' + data.addressFrom + ' started') + } else { + const realAddressToLower = realAddressTo.toLowerCase() + BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTransferProcessor.getFeeRate ' + data.addressFrom + ' started') + txRBFed = 'usualSend' + if (realAddressTo !== '' && (realAddressToLower.indexOf('0x') === -1 || realAddressToLower.indexOf('0x') !== 0)) { + throw new Error('SERVER_RESPONSE_BAD_DESTINATION') + } + } + + let oldGasPrice = -1 + let oldNonce = -1 + let nonceLog = '' + if (txRBF) { + oldGasPrice = typeof data.transactionJson !== 'undefined' && typeof data.transactionJson.gasPrice !== 'undefined' ? data.transactionJson.gasPrice : false + oldNonce = typeof data.transactionJson !== 'undefined' && typeof data.transactionJson.nonce !== 'undefined' ? data.transactionJson.nonce : false + BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTransferProcessor.getFeeRate ' + data.addressFrom + ' rbf preset nonceForTx ' + oldNonce) + if (oldGasPrice === false) { + try { + const ethProvider = BlocksoftDispatcher.getScannerProcessor(data.currencyCode) + const scannedTx = await ethProvider.getTransactionBlockchain(txRBF) + if (scannedTx) { + oldGasPrice = scannedTx.gasPrice + oldNonce = scannedTx.nonce + BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTransferProcessor.getFeeRate ' + data.addressFrom + ' rbf reloaded nonceForTx ' + oldNonce) + } + if (!oldGasPrice) { + BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTransferProcessor.getFeeRate ' + txRBFed + ' no gasPrice for ' + txRBF) + } + } catch (e) { + BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTransferProcessor.getFeeRate ' + txRBFed + 'not loaded gasPrice for ' + txRBF + ' ' + e.message) + } + } + nonceLog += ' txRBFNonce ' + oldNonce + } else if (typeof additionalData.nonceForTx !== 'undefined' && additionalData.nonceForTx !== -1) { + oldNonce = additionalData.nonceForTx + nonceLog += ' customFeeNonce ' + oldNonce + BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTransferProcessor.getFeeRate ' + data.addressFrom + ' custom nonceForTx ' + additionalData.nonceForTx) + } else { + BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTransferProcessor.getFeeRate ' + data.addressFrom + ' no nonceForTx') + } + + let gasPrice = {} + + let maxNonceLocal = await EthTmpDS.getMaxNonce(this._mainCurrencyCode, data.addressFrom) + const ethAllowBlockedBalance = await settingsActions.getSetting('ethAllowBlockedBalance') + const ethAllowLongQuery = await settingsActions.getSetting('ethAllowLongQuery') + + const proxyPriceCheck = await EthNetworkPrices.getWithProxy(this._mainCurrencyCode, this._isTestnet, typeof data.addressFrom !== 'undefined' ? data.addressFrom : 'none', { + data, + additionalData, + feesSource: 'EthTransferProcessor', + feesOldNonce: oldNonce, + ethAllowBlockedBalance, + ethAllowLongQuery, + maxNonceLocal + }) + + if (typeof additionalData.gasPrice !== 'undefined' && additionalData.gasPrice) { + if (typeof additionalData.gasPriceTitle !== 'undefined') { + // @ts-ignore + gasPrice[additionalData.gasPriceTitle] = additionalData.gasPrice + } else { + gasPrice = { 'speed_blocks_12': additionalData.gasPrice } + } + } else if (typeof additionalData.prices !== 'undefined' && additionalData.prices) { + gasPrice = additionalData.prices + } else if (proxyPriceCheck) { + let tmp = 0 + if (typeof data.dexOrderData !== 'undefined' && data.dexOrderData) { + tmp = typeof data.dexOrderData[0].params.gasPrice !== 'undefined' ? data.dexOrderData[0].params.gasPrice : 0 + if (tmp > 0) { + gasPrice = { 'speed_blocks_2': tmp } + } + } else if (typeof data.walletConnectData !== 'undefined' && typeof data.walletConnectData.gasPrice !== 'undefined' && data.walletConnectData.gasPrice) { + tmp = BlocksoftUtils.hexToDecimalWalletConnect(data.walletConnectData.gasPrice) + if (tmp > 0) { + gasPrice = { 'speed_blocks_2': tmp } + } + } + if (!tmp) { + gasPrice = typeof proxyPriceCheck.gasPrice !== 'undefined' && proxyPriceCheck.gasPrice ? proxyPriceCheck.gasPrice : { 'speed_blocks_12': '10' } + } + if (typeof proxyPriceCheck.maxNonceLocal !== 'undefined' && proxyPriceCheck.maxNonceLocal) { + maxNonceLocal = proxyPriceCheck.maxNonceLocal + } + BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTransferProcessor.getFeeRate ' + data.addressFrom + ' proxyPriceCheck', proxyPriceCheck) + } + + if (typeof this._web3.LINK === 'undefined') { + throw new Error('EthTransferProcessor need this._web3.LINK') + } + + let gasLimit = 0 + try { + + if (typeof additionalData === 'undefined' || typeof additionalData.gasLimit === 'undefined' || !additionalData.gasLimit) { + if (typeof data.dexOrderData !== 'undefined' && data.dexOrderData) { + gasLimit = typeof data.dexOrderData[0].params.gas !== 'undefined' ? data.dexOrderData[0].params.gas : 0 + } else if (typeof data.walletConnectData !== 'undefined' && typeof data.walletConnectData.gas !== 'undefined' && data.walletConnectData.gas && data.walletConnectData.gas !== '0x0') { + gasLimit = BlocksoftUtils.hexToDecimalWalletConnect(data.walletConnectData.gas) + } else if (typeof data.walletConnectData !== 'undefined') { + const params = { + 'jsonrpc': '2.0', + 'method': 'eth_estimateGas', + 'params': [ + { + 'from': typeof data.walletConnectData.from && data.walletConnectData.from ? data.walletConnectData.from : data.addressFrom, + 'to': typeof data.walletConnectData.to && data.walletConnectData.to ? data.walletConnectData.to : realAddressTo, + 'value': typeof data.walletConnectData.value !== 'undefined' && data.walletConnectData.value + ? data.walletConnectData.value + : data.amount.indexOf('0x') === 0 ? data.amount : ('0x' + BlocksoftUtils.decimalToHex(data.amount)), + 'data': typeof data.walletConnectData.data !== 'undefined' && data.walletConnectData.data ? data.walletConnectData.data : '0x' + } + ], + 'id': 1 + } + BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTransferProcessor.getFeeRate estimatedGas for WalletConnect start') + const tmp = await BlocksoftAxios.postWithoutBraking(this._web3.LINK, params) + if (typeof tmp !== 'undefined' && typeof tmp.data !== 'undefined') { + if (typeof tmp.data.result !== 'undefined') { + gasLimit = BlocksoftUtils.hexToDecimalWalletConnect(tmp.data.result) * 1 + 150000 + } else if (typeof tmp.data.error !== 'undefined') { + throw new Error(tmp.data.error.message) + } + } else { + gasLimit = 500000 + } + BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTransferProcessor.getFeeRate estimatedGas for WalletConnect result ' + gasLimit) + } else if (typeof data.contractCallData !== 'undefined' && typeof data.contractCallData.contractAddress !== 'undefined') { + const schema = data.contractCallData.contractSchema + let abiCode + if (schema === 'ERC721') { + abiCode = abi721.ERC721 + } else if (schema === 'ERC1155') { + abiCode = abi1155.ERC1155 + } else { + throw new Error('Contract abi not found ' + schema) + } + const token = new this._web3.eth.Contract(abiCode, data.contractCallData.contractAddress) + + gasLimit = 150000 + try { + const tmpParams = data.contractCallData.contractActionParams + for (let i = 0, ic = tmpParams.length; i 3) { + throw e1 + } + } + } while (!ok && i <= 5) + if (gasLimitNew && typeof gasLimitNew !== 'undefined' && gasLimitNew * 1 > 0) { + gasLimit = gasLimitNew * 1 + } + } catch (e) { + if (e.message.indexOf('resolve host') !== -1) { + throw new Error('SERVER_RESPONSE_NOT_CONNECTED') + } else { + gasLimit = BlocksoftExternalSettings.getStatic('ETH_MIN_GAS_LIMIT') + // e.message += ' in EthEstimateGas in getFeeRate' + // throw e + } + } + if (!gasLimit || typeof gasLimit !== 'undefined') { + gasLimit = BlocksoftExternalSettings.getStatic('ETH_MIN_GAS_LIMIT') + } + + const minGasLimit = BlocksoftExternalSettings.getStatic(this._mainCurrencyCode + '_MIN_GAS_LIMIT') * 1 + if (minGasLimit > 0 && gasLimit < minGasLimit) { + gasLimit = minGasLimit + } + + } + } else { + gasLimit = additionalData.gasLimit + BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTransferProcessor.getFeeRate preestimatedGas ' + gasLimit) + } + } catch (e) { + throw new Error(e.message + ' in get gasLimit') + } + + let showBigGasNotice = false + if (typeof additionalData === 'undefined' || typeof additionalData.isCustomFee === 'undefined' || !additionalData.isCustomFee) { + try { + const limit = BlocksoftExternalSettings.getStatic(this._mainCurrencyCode + '_GAS_LIMIT') + if (gasLimit * 1 > limit * 1) { + showBigGasNotice = true + } + } catch (e) { + throw new Error(e.message + ' in get showBigGasNotice') + } + } + + if (!gasLimit) { + throw new Error('invalid transaction (no gas limit.2)') + } + + // @ts-ignore + BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTransferProcessor.getFeeRate prefinished', { + gasPrice, + gasLimit + }) + + const result: BlocksoftBlockchainTypes.FeeRateResult = {} as BlocksoftBlockchainTypes.FeeRateResult + result.fees = [] + + let balance = '0' + let actualCheckBalance + let nonceForTx = -1 + let isNewNonce = true + if (typeof data.transactionJson !== 'undefined' && typeof data.transactionJson.nonce !== 'undefined' && data.transactionJson.nonce) { + nonceForTx = data.transactionJson.nonce + nonceLog += ' from transactionJSON' + } else { + let nonceForTxBasic = maxNonceLocal.maxValue * 1 > maxNonceLocal.maxScanned * 1 ? maxNonceLocal.maxValue : maxNonceLocal.maxScanned + if (proxyPriceCheck && typeof proxyPriceCheck.newNonce !== 'undefined') { + nonceForTxBasic = proxyPriceCheck.newNonce + if (typeof proxyPriceCheck.logNonce !== 'undefined') { + nonceLog += ' from serverCheck ' + proxyPriceCheck.logNonce + } else { + nonceLog += ' from serverCheckNoLog ' + } + if (MarketingEvent.DATA.LOG_TESTER && typeof proxyPriceCheck.newNonceDEBUG !== 'undefined') { + nonceForTxBasic = proxyPriceCheck.newNonceDEBUG + nonceLog += ' used newNonceDEBUG' + } else { + nonceLog += ' used newNonce ' + } + + if (nonceForTxBasic === 'maxValue+1') { + if (maxNonceLocal.maxValue * 1 > -1) { + nonceForTxBasic = maxNonceLocal.maxValue + 1 + nonceLog += ' usedMax ' + JSON.stringify(maxNonceLocal) + } else { + nonceForTxBasic = -1 + nonceLog += ' noMax => -1 ' + } + } + nonceLog += ' nonceForTxBasic ' + nonceForTxBasic + + } else if (nonceForTxBasic * 1 >= 0) { + nonceLog += ' used localNonce ' + JSON.stringify(maxNonceLocal) + nonceLog += ' nonceForTxBasic ' + nonceForTxBasic + nonceForTxBasic = nonceForTxBasic * 1 + 1 + } else { + nonceLog += ' used noLocalNoServer ' + JSON.stringify(nonceForTxBasic) + } + + if (oldNonce !== false && oldNonce * 1 > -1 && oldNonce !== nonceForTxBasic) { + nonceForTx = oldNonce + isNewNonce = false + nonceLog = 'recheck oldNonce ' + oldNonce + ' with basic ' + nonceForTxBasic + nonceLog + BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTransferProcessor.getFeeRate ' + data.addressFrom + ' ' + nonceLog) + } else { + nonceForTx = nonceForTxBasic + isNewNonce = true + nonceLog = 'recheck nonce ' + oldNonce + ' replaced by basic ' + nonceForTxBasic + nonceLog + BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTransferProcessor.getFeeRate ' + data.addressFrom + ' ' + nonceLog) + } + } + + if (data.isTransferAll && this._useThisBalance) { + balance = data.amount + actualCheckBalance = true + } else { + try { + // @ts-ignore + balance = additionalData.balance || await this._web3.eth.getBalance(data.addressFrom) + // @ts-ignore + if (!balance || balance * 1 === 0) { + BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTxProcessor.getFeeRate balanceFromWeb3 is empty ' + balance) + actualCheckBalance = false + } else { + actualCheckBalance = true + } + } catch (e) { + actualCheckBalance = false + } + } + const titles = ['eth_speed_slow', 'eth_speed_medium', 'eth_speed_fast'] + const titlesChanged = ['eth_speed_slowest', 'eth_speed_medium_to_slow', 'eth_speed_fast_to_medium'] + const keys = ['speed_blocks_12', 'speed_blocks_6', 'speed_blocks_2'] + let skippedByOld = false + let prevGasPrice = 0 + const feesOK = {} + for (let index = 0; index <= 2; index++) { + const key = keys[index] + if (typeof gasPrice[key] === 'undefined') continue + if (gasPrice[key] <= oldGasPrice) { + skippedByOld = true + continue + } + let fee = BlocksoftUtils.mul(gasPrice[key], gasLimit) + let amount = data.amount + let needSpeed = gasPrice[key].toString() + let newGasPrice = needSpeed + let changedFeeByBalance = false + if (actualCheckBalance) { + const tmp = BlocksoftUtils.diff(balance, fee).toString() + if (this._useThisBalance) { + if (tmp * 1 < 0) { + continue + } + if (data.isTransferAll) { + amount = tmp + } else { + const tmp2 = BlocksoftUtils.diff(tmp, amount).toString() + if (tmp2 * 1 < 0) { + continue + } + } + } else { + if (tmp * 1 < 0) { + const tmpGasPrice = BlocksoftUtils.div(balance, gasLimit).toString() + if (tmpGasPrice && BlocksoftUtils.diff(tmpGasPrice, prevGasPrice).toString() * 1 > 0) { + fee = balance + newGasPrice = tmpGasPrice.split('.')[0] + changedFeeByBalance = true + } else { + continue + } + } + } + } + if (typeof newGasPrice === 'undefined' || newGasPrice === 'undefined') { + newGasPrice = '0' + } else { + newGasPrice = newGasPrice.toString() + } + + let langMsg = titles[index] + if (changedFeeByBalance) { + if (index > 0) { + langMsg = titlesChanged[index - 1] + } + } + + let gweiFee = 0 + try { + gweiFee = newGasPrice !== '0' ? BlocksoftUtils.toGwei(newGasPrice).toString() : newGasPrice + } catch (e) { + BlocksoftCryptoLog.err('EthTxProcessor.getFeeRate newGasPrice to gwei error ' + e.message) + } + + const tmp = { + langMsg, + gasPrice: newGasPrice, + gasPriceGwei: gweiFee, + gasLimit: gasLimit.toString(), + feeForTx: fee.toString(), + nonceForTx, + nonceLog, + needSpeed, + ethAllowBlockedBalance, + ethAllowLongQuery, + isTransferAll: data.isTransferAll, + amountForTx: amount + } + + const diff = needSpeed - newGasPrice + if (!changedFeeByBalance || diff * 1 < 1000) { + feesOK[titles[index]] = tmp.gasPrice + } else { + BlocksoftCryptoLog.log('EthTxProcessor.getFeeRate skipped feesOk ' + titles[index] + ' as diff ' + diff + ' gasPrice ' + tmp.gasPrice + ' / gasLimit ' + tmp.gasLimit) + } + if (BlocksoftUtils.diff(newGasPrice, prevGasPrice).indexOf('-') === -1 && newGasPrice !== prevGasPrice) { + prevGasPrice = tmp.gasPrice + BlocksoftCryptoLog.log('EthTxProcessor.getFeeRate added feeForTx ' + titles[index] + ' ' + tmp.feeForTx + ' with gasPrice ' + tmp.gasPrice + ' / gasLimit ' + tmp.gasLimit) + result.fees.push(tmp) + } else { + BlocksoftCryptoLog.log('EthTxProcessor.getFeeRate skipped feeForTx ' + titles[index] + ' ' + tmp.feeForTx + ' with gasPrice ' + tmp.gasPrice + ' / gasLimit ' + tmp.gasLimit) + } + } + + prevGasPrice = 0 + if (txRBF) { + let recheck = result.fees.length < 2 + if (typeof additionalData.isCustomFee !== 'undefined' && additionalData.isCustomFee) { + recheck = result.fees.length === 0 + } + if (recheck) { + for (let index = 0; index <= 2; index++) { + if (typeof result.fees[index] !== 'undefined') { + result.fees[index].langMsg = titles[index] + continue + } + let newGasPrice = Math.round(oldGasPrice * (10 + index + 1) / 10) + const title = titles[index] + const key = keys[index] + const needSpeed = gasPrice[key] + if (newGasPrice < gasPrice[key]) { + newGasPrice = gasPrice[key] + } + let fee = BlocksoftUtils.mul(newGasPrice, gasLimit) + let amount = data.amount + if (actualCheckBalance) { + const tmp = BlocksoftUtils.diff(balance, fee).toString() + if (this._useThisBalance) { + if (tmp * 1 < 0) { + continue + } + if (data.isTransferAll) { + amount = tmp + } else { + const tmp2 = BlocksoftUtils.diff(tmp, amount).toString() + if (tmp2 * 1 < 0) { + amount = tmp + } + } + } else { + if (tmp * 1 < 0) { + const tmpGasPrice = BlocksoftUtils.div(balance, gasLimit).toString() + if (BlocksoftUtils.diff(tmpGasPrice, prevGasPrice).toString() * 1 > 0) { + fee = balance + newGasPrice = tmpGasPrice + } else { + continue + } + } + } + } + + if (typeof newGasPrice === 'undefined') { + newGasPrice = '0' + } else { + newGasPrice = newGasPrice.toString() + newGasPrice = BlocksoftUtils.round(newGasPrice) + } + + let gweiFee = 0 + try { + gweiFee = newGasPrice !== '0' ? BlocksoftUtils.toGwei(newGasPrice).toString() : newGasPrice + } catch (e) { + BlocksoftCryptoLog.err('EthTxProcessor.getFeeRate newGasPrice2 to gwei error ' + e.message) + } + + const tmp = { + langMsg: title, + gasPrice: newGasPrice, + gasPriceGwei: gweiFee, + gasLimit: gasLimit.toString(), + feeForTx: fee.toString(), + amountForTx: amount, + nonceForTx, + nonceLog, + needSpeed, + ethAllowBlockedBalance, + ethAllowLongQuery, + isTransferAll: data.isTransferAll + } + // @ts-ignore + prevGasPrice = tmp.gasPrice + BlocksoftCryptoLog.log('EthTxProcessor.getFeeRate feeForTx rbfFaster ' + tmp.feeForTx + ' with gasPrice ' + tmp.gasPrice + ' / gasLimit ' + tmp.gasLimit) + result.fees.push(tmp) + } + } + } + + if (balance !== '0' && result.fees.length === 0) { + const index = 0 + let feeForTx + let amountForTx = data.amount + let leftForFee = balance + if (this._useThisBalance && !data.isTransferAll) { + if (txRBF) { + leftForFee = BlocksoftUtils.diff(balance, BlocksoftUtils.div(data.amount, 2)) // if eth is transferred and paid in eth - so amount is not changed except send all + } else { + leftForFee = BlocksoftUtils.diff(balance, data.amount) // if eth is transferred and paid in eth - so amount is not changed except send all + } + } + + let fee = BlocksoftUtils.div(leftForFee, gasLimit) + if (fee) { + const tmp = fee.split('.') + if (tmp) { + fee = tmp[0] + feeForTx = BlocksoftUtils.mul(fee, gasLimit) + if (this._useThisBalance && (data.isTransferAll || txRBF)) { + amountForTx = BlocksoftUtils.diff(balance, feeForTx) // change amount for send all calculations + } + } else { + feeForTx = 0 + } + } else { + feeForTx = 0 + } + if (!feeForTx) { + throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_FOR_ANY_FEE') + } + if (typeof fee === 'undefined') { + fee = '0' + } else { + fee = fee.toString() + } + const needSpeed = typeof keys[index] !== 'undefined' && typeof gasPrice[keys[index]] !== 'undefined' ? gasPrice[keys[index]].toString() : '?' + let gweiFee = 0 + try { + gweiFee = fee !== '0' ? BlocksoftUtils.toGwei(fee).toString() : fee + } catch (e) { + BlocksoftCryptoLog.err('EthTxProcessor.getFeeRate fee to gwei error ' + e.message) + } + const tmp = { + langMsg: 'eth_speed_slowest', + gasPrice: fee, + gasPriceGwei: gweiFee, + gasLimit: gasLimit.toString(), + feeForTx, + amountForTx, + nonceForTx, + nonceLog, + needSpeed, + ethAllowBlockedBalance, + ethAllowLongQuery, + isTransferAll: data.isTransferAll + } + + BlocksoftCryptoLog.log('EthTxProcessor.getFeeRate feeForTx ' + titles[index] + ' ' + tmp.feeForTx + ' corrected for balance ' + balance + ' with gasPrice ' + tmp.gasPrice + ' / gasLimit ' + tmp.gasLimit) + if (tmp.gasPrice > 0) { + result.fees.push(tmp) + } else { + throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_FOR_ANY_FEE') + } + } + + + + if (!skippedByOld) { + if (typeof feesOK['eth_speed_fast'] === 'undefined') { + BlocksoftCryptoLog.log('EthTxProcessor.getFeeRate showSmallFeeNotice reason ' + JSON.stringify(feesOK)) + result.showSmallFeeNotice = new Date().getTime() + } + } + + result.selectedFeeIndex = result.fees.length - 1 + result.countedForBasicBalance = actualCheckBalance ? balance : '0' + if (!txRBF) { + let check = ethAllowBlockedBalance !== '1' && isNewNonce && maxNonceLocal.amountBlocked && typeof maxNonceLocal.amountBlocked[this._settings.currencyCode] !== 'undefined' + BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTransferProcessor.getFees ethAllowBlockedBalance ' + + ethAllowBlockedBalance + ' isNewNonce ' + isNewNonce + ' oldNonce ' + oldNonce + + ' amountBlocked ' + JSON.stringify(maxNonceLocal.amountBlocked) + ' => ' + + (check ? 'true' : 'false')) + if (check) { + try { + const diff = BlocksoftUtils.diff(result.countedForBasicBalance, maxNonceLocal.amountBlocked[this._settings.currencyCode]).toString() + const diffAmount = BlocksoftUtils.diff(diff, data.amount).toString() + BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTransferProcessor.getFees balance ' + + result.countedForBasicBalance + ' - blocked ' + maxNonceLocal.amountBlocked[this._settings.currencyCode] + ' = left Balance ' + diff + ' => left Amount ' + diffAmount) + if (diff.indexOf('-') !== -1) { + result.showBlockedBalanceNotice = new Date().getTime() + result.showBlockedBalanceFree = '0 ' + this._settings.currencySymbol + } else { + if (diffAmount.indexOf('-') !== -1) { + result.showBlockedBalanceNotice = new Date().getTime() + result.showBlockedBalanceFree = BlocksoftUtils.toUnified(diff, this._settings.decimals) + ' ' + this._settings.currencySymbol + } + } + } catch (e) { + if (config.debug.cryptoErrors) { + BlocksoftCryptoLog.log(' EthTransferProcessor.getFees ethAllowBlockedBalance inner error ' + e.message) + } + BlocksoftCryptoLog.log(' EthTransferProcessor.getFees ethAllowBlockedBalance inner error ' + e.message) + } + } + const LONG_QUERY = await BlocksoftExternalSettings.getStatic('ETH_LONG_QUERY') + check = maxNonceLocal.queryLength * 1 >= LONG_QUERY * 1 + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTransferProcessor.getFees ethAllowLongQuery ' + + ethAllowLongQuery + ' Query scanned ' + maxNonceLocal.maxScanned + ' success ' + maxNonceLocal.maxSuccess + ' length ' + maxNonceLocal.queryLength + + ' txs ' + JSON.stringify(maxNonceLocal.queryTxs) + ' => ' + + (check ? 'true' : 'false') + ) + if (check) { + result.showLongQueryNotice = new Date().getTime() + result.showLongQueryNoticeTxs = maxNonceLocal.queryTxs + } + + } else { + for (const fee of result.fees) { + fee.showNonce = true + } + } + result.showBigGasNotice = showBigGasNotice ? new Date().getTime() : 0 + return result + } + + + async getTransferAllBalance(data: BlocksoftBlockchainTypes.TransferData, privateData?: BlocksoftBlockchainTypes.TransferPrivateData, additionalData: BlocksoftBlockchainTypes.TransferAdditionalData = {}): Promise { + if (!data.amount || data.amount === '0') { + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTransferProcessor.getTransferAllBalance ' + data.addressFrom + ' started with load balance needed') + try { + // @ts-ignore + data.amount = await this._web3.eth.getBalance(data.addressFrom).toString() + } catch (e) { + this.checkError(e, data) + } + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTransferProcessor.getTransferAllBalance ' + data.addressFrom + ' started with loaded balance ' + data.amount) + } else { + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTransferProcessor.getTransferAllBalance ' + data.addressFrom + ' started with preset balance ' + data.amount) + } + + // noinspection EqualityComparisonWithCoercionJS + if (data.amount === '0' || data.amount === '') { + return { + selectedTransferAllBalance: '0', + selectedFeeIndex: -1, + fees: [], + countedForBasicBalance: '0' + } + } + + + const fees = await this.getFeeRate(data, privateData, additionalData) + + if (!fees || fees.selectedFeeIndex < 0) { + return { + selectedTransferAllBalance: '0', + selectedFeeIndex: -2, + fees: [], + countedForBasicBalance: '0' + } + } + if (this._useThisBalance) { + try { + for (const fee of fees.fees) { + fee.totalFeePlusAmountETH = BlocksoftUtils.toEther(BlocksoftUtils.add(fee.amountForTx, fee.feeForTx)) + } + fees.selectedTransferAllBalanceETH = BlocksoftUtils.toEther(fees.fees[fees.selectedFeeIndex].amountForTx) + } catch (e) { + BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTransferProcessor.getTransferAllBalance ' + data.addressFrom + ' => ' + data.addressTo + ' error on logging ' + e.message) + } + } + return { + ...fees, + selectedTransferAllBalance: fees.fees[fees.selectedFeeIndex].amountForTx + } + } + + async sendTx(data: BlocksoftBlockchainTypes.TransferData, privateData: BlocksoftBlockchainTypes.TransferPrivateData, uiData: BlocksoftBlockchainTypes.TransferUiData): Promise { + if (typeof privateData.privateKey === 'undefined') { + throw new Error('ETH transaction required privateKey') + } + if (typeof data.addressTo === 'undefined') { + throw new Error('ETH transaction required addressTo') + } + + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTransferProcessor sendTx started', JSON.parse(JSON.stringify(data))) + + let txRBFed = '' + let txRBF = false + let realAddressTo = data.addressTo + if (realAddressTo !== '' && OneUtils.isOneAddress(realAddressTo)) { + realAddressTo = OneUtils.fromOneAddress(realAddressTo) + } + const realAddressToLower = realAddressTo.toLowerCase() + if (typeof data.transactionRemoveByFee !== 'undefined' && data.transactionRemoveByFee) { + BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTransferProcessor.sendTx started ' + data.transactionRemoveByFee) + txRBF = data.transactionRemoveByFee + txRBFed = 'RBFremoved' + } else if (typeof data.transactionReplaceByFee !== 'undefined' && data.transactionReplaceByFee) { + BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTransferProcessor.sendTx resend started ' + data.transactionReplaceByFee) + txRBF = data.transactionReplaceByFee + txRBFed = 'RBFed' + } else if (typeof data.dexOrderData !== 'undefined' && data.dexOrderData) { + BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTransferProcessor.sendTx dex ' + data.addressFrom + ' started') + txRBFed = 'dexSend' + } else { + BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTransferProcessor.sendTx ' + data.addressFrom + ' started') + txRBFed = 'usualSend' + if (realAddressTo !== '' && (realAddressToLower.indexOf('0x') === -1 || realAddressToLower.indexOf('0x') !== 0)) { + throw new Error('SERVER_RESPONSE_BAD_DESTINATION') + } + } + + let finalGasPrice = 0 + let finalGasLimit = 0 + + let selectedFee + if (typeof uiData.selectedFee !== 'undefined' && typeof uiData.selectedFee.gasPrice !== 'undefined') { + selectedFee = uiData.selectedFee + // @ts-ignore + finalGasPrice = uiData.selectedFee.gasPrice * 1 + // @ts-ignore + finalGasLimit = Math.ceil(uiData.selectedFee.gasLimit * 1) + } else { + const fees = await this.getFeeRate(data, privateData) + if (fees.selectedFeeIndex < 0) { + throw new Error('SERVER_RESPONSE_NOTHING_LEFT_FOR_FEE') + } + selectedFee = fees.fees[fees.selectedFeeIndex] + // @ts-ignore + finalGasPrice = selectedFee.gasPrice * 1 + // @ts-ignoreф + finalGasLimit = Math.ceil(selectedFee.gasLimit * 1) + } + + // @ts-ignore + BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTransferProcessor.sendTx ' + txRBFed + ' feeForTx', { + uiData, + finalGasPrice, + finalGasLimit + }) + if (finalGasLimit === 0 || !finalGasLimit) { + throw new Error('SERVER_PLEASE_SELECT_FEE') + } + if (finalGasPrice === 0 || !finalGasPrice) { + throw new Error('SERVER_PLEASE_SELECT_FEE') + } + + const tx: BlocksoftBlockchainTypes.EthTx = { + from: data.addressFrom, + to: realAddressToLower, + gasPrice: finalGasPrice, + gas: finalGasLimit, + value: data.amount + } + + if (typeof data.dexOrderData !== 'undefined' && data.dexOrderData) { + if (typeof data.dexOrderData[0].params.data !== 'undefined') { + tx.data = data.dexOrderData[0].params.data + if (typeof data.dexOrderData[0].params.value !== 'undefined') { + tx.value = data.dexOrderData[0].params.value + } else { + tx.value = 0 + } + } + if (typeof data.dexOrderData[0].params.to !== 'undefined') { + tx.to = data.dexOrderData[0].params.to + } + } else if (typeof data.contractCallData !== 'undefined' && typeof data.contractCallData.contractAddress !== 'undefined') { + const schema = data.contractCallData.contractSchema + try { + let abiCode + if (schema === 'ERC721') { + abiCode = abi721.ERC721 + } else if (schema === 'ERC1155') { + abiCode = abi1155.ERC1155 + } else { + throw new Error('Contract abi not found ' + schema) + } + const token = new this._web3.eth.Contract(abiCode, data.contractCallData.contractAddress) + + const tmpParams = data.contractCallData.contractActionParams + for (let i = 0, ic = tmpParams.length; i < ic; i++) { + if (tmpParams[i] === 'addressTo') { + tmpParams[i] = realAddressTo + } + } + tx.to = data.contractCallData.contractAddress + tx.data = token.methods[data.contractCallData.contractAction](...tmpParams).encodeABI() + } catch (e) { + throw new Error(e.message + ' while encodeABI for ' + schema) + } + } else if (typeof data.walletConnectData !== 'undefined' && typeof data.walletConnectData.data !== 'undefined') { + tx.data = data.walletConnectData.data + } else if (typeof data.blockchainData !== 'undefined') { + tx.data = data.blockchainData // actual value for erc20 etc + } else if (typeof data?.transactionJson?.txData !== 'undefined') { + tx.data = data?.transactionJson?.txData + } + + const sender = new EthTxSendProvider(this._web3, this._trezorServerCode, this._mainCurrencyCode, this._mainChainId, this._settings) + const logData = JSON.parse(JSON.stringify(tx)) + logData.currencyCode = this._settings.currencyCode + logData.selectedFee = selectedFee + logData.basicAddressTo = typeof data.basicAddressTo !== 'undefined' ? data.basicAddressTo.toLowerCase() : realAddressToLower + logData.basicAmount = typeof data.basicAmount !== 'undefined' ? data.basicAmount : data.amount + logData.basicToken = typeof data.basicToken !== 'undefined' ? data.basicToken : '' + logData.pushLocale = sublocale() + logData.pushSetting = await settingsActions.getSetting('transactionsNotifs') + + let result = {} as BlocksoftBlockchainTypes.SendTxResult + try { + if (txRBF) { + let oldNonce = typeof uiData.selectedFee.nonceForTx !== 'undefined' ? uiData.selectedFee.nonceForTx : false + if (oldNonce === false || oldNonce === -1) { + // actually could remove as not used without receipt fee + oldNonce = typeof data.transactionJson !== 'undefined' && typeof data.transactionJson.nonce !== 'undefined' ? data.transactionJson.nonce : false + } + if (oldNonce === false || oldNonce === -1) { + try { + const ethProvider = BlocksoftDispatcher.getScannerProcessor(data.currencyCode) + const scannedTx = await ethProvider.getTransactionBlockchain(txRBF) + if (scannedTx) { + oldNonce = scannedTx.nonce + } + } catch (e) { + BlocksoftCryptoLog.err(this._settings.currencyCode + ' EthTransferProcessor.sent rbf not loaded nonce for ' + txRBF + ' ' + e.message) + throw new Error('System error: not loaded nonce for ' + txRBF) + } + if (oldNonce === false || oldNonce === -1) { + BlocksoftCryptoLog.err(this._settings.currencyCode + ' EthTransferProcessor.sent rbf no nonce for ' + txRBF) + throw new Error('System error: no nonce for ' + txRBF) + } + } + logData.setNonce = oldNonce + tx.nonce = oldNonce + } else { + // @ts-ignore + if (typeof uiData.selectedFee.nonceForTx !== 'undefined' + && (uiData.selectedFee.nonceForTx.toString() === '0' || uiData.selectedFee.nonceForTx) + && uiData.selectedFee.nonceForTx !== '' + && uiData.selectedFee.nonceForTx * 1 >= 0 + ) { + // @ts-ignore + tx.nonce = uiData.selectedFee.nonceForTx * 1 + logData.setNonce = tx.nonce + logData.selectedFee.nonceLog = 'replacedByUi ' + uiData.selectedFee.nonceForTx + ' ' + (typeof logData.selectedFee.nonceLog !== 'undefined' ? logData.selectedFee.nonceLog : '') + } + BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTransferProcessor.sent ' + data.addressFrom + ' nonceLog ' + logData.selectedFee.nonceLog) + } + + if (typeof uiData !== 'undefined' && typeof uiData.selectedFee !== 'undefined' && typeof uiData.selectedFee.rawOnly !== 'undefined' && uiData.selectedFee.rawOnly) { + return { rawOnly: uiData.selectedFee.rawOnly, raw : await sender.sign(tx, privateData, txRBF, logData)} + } + + try { + result = await sender.send(tx, privateData, txRBF, logData) + } catch (e) { + if (config.debug.cryptoErrors) { + BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTransferProcessor.sent while ' + (txRBF ? 'txRbf':'usual') + ' sender.send error ' + e.message) + } + throw e + } + + result.transactionFee = BlocksoftUtils.mul(finalGasPrice, finalGasLimit) + result.transactionFeeCurrencyCode = this._mainCurrencyCode === 'BNB' ? 'BNB_SMART' : this._mainCurrencyCode + if (txRBF) { + if (typeof data.blockchainData === 'undefined' || !data.blockchainData) { + result.amountForTx = data.amount + } + result.addressTo = data.addressTo === data.addressFrom || realAddressTo === data.addressFrom ? '' : realAddressTo + } else { + result.transactionJson.txData = tx.data + await EthTmpDS.getCache(this._mainCurrencyCode, data.addressFrom) + } + BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTransferProcessor.sent ' + data.addressFrom + ' done ' + JSON.stringify(result.transactionJson)) + } catch (e) { + if (config.debug.cryptoErrors) { + BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTransferProcessor.sent error ' + e.message, tx) + } + this.checkError(e, data, txRBF, logData) + } + // @ts-ignore + logData.result = result + // noinspection ES6MissingAwait + MarketingEvent.logOnlyRealTime('v20_eth_tx_success ' + this._settings.currencyCode + ' ' + data.addressFrom + ' => ' + realAddressTo, logData) + + return result + } + + async setMissingTx(data: BlocksoftBlockchainTypes.DbAccount, transaction: BlocksoftBlockchainTypes.DbTransaction): Promise { + if (typeof transaction.transactionJson !== 'undefined' && transaction.transactionJson && typeof transaction.transactionJson.nonce !== 'undefined') { + BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTransferPRocessor.setMissingTx remove nonce ' + transaction.transactionJson.nonce + ' ' + transaction.transactionHash) + await EthTmpDS.removeNonce(this._mainCurrencyCode, data.address, 'send_' + transaction.transactionHash) + } + MarketingEvent.logOnlyRealTime('v20_eth_tx_set_missing ' + this._settings.currencyCode + ' ' + data.address + ' => ' + transaction.addressTo, transaction) + return true + } + + canRBF(data: BlocksoftBlockchainTypes.DbAccount, transaction: BlocksoftBlockchainTypes.DbTransaction, source: string): boolean { + if (transaction.transactionDirection === 'income') { + BlocksoftCryptoLog.log('EthTransferProcessor.canRBF ' + transaction.transactionHash + ' false by income') + return false + } + if (typeof transaction.transactionJson !== 'undefined') { + if (typeof transaction.transactionJson.delegatedNonce !== 'undefined') { + BlocksoftCryptoLog.log('EthTransferProcessor.canRBF ' + transaction.transactionHash + ' false by delegated') + return false + } + /*if (typeof transaction.transactionJson.nonce !== 'undefined') { + const max = EthTmpDS.getMaxStatic(data.address) + if (max.success > -1) { + // @ts-ignore + if (transaction.transactionJson.nonce * 1 > max.success * 1) return true + BlocksoftCryptoLog.log('EthTransferProcessor.canRBF ' + transaction.transactionHash + ' false by maxSuccess', + {'nonce' : transaction.transactionJson.nonce, 'max' : max.success}) + return false + } + }*/ + } + return true + } +} diff --git a/crypto/blockchains/eth/EthTransferProcessorErc20.ts b/crypto/blockchains/eth/EthTransferProcessorErc20.ts new file mode 100644 index 000000000..5fd891c0d --- /dev/null +++ b/crypto/blockchains/eth/EthTransferProcessorErc20.ts @@ -0,0 +1,201 @@ +/** + * @author Ksu + * @version 0.20 + */ +import EthTransferProcessor from './EthTransferProcessor' +import config from '@app/config/config' +import MarketingEvent from '@app/services/Marketing/MarketingEvent' + +import { BlocksoftBlockchainTypes } from '../BlocksoftBlockchainTypes' + +import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog' +import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings' + + +const abi = require('./ext/erc20.js') + +export default class EthTransferProcessorErc20 extends EthTransferProcessor implements BlocksoftBlockchainTypes.TransferProcessor { + + + constructor(settings: { network?: string; tokenAddress: any }) { + super(settings) + // @ts-ignore + this._token = new this._web3.eth.Contract(abi.ERC20, settings.tokenAddress) + this._tokenAddress = settings.tokenAddress.toLowerCase() + this._useThisBalance = false + } + + _token: any = false + _tokenAddress: string = '' + _useThisBalance: boolean = false + + async checkTransferHasError(data: BlocksoftBlockchainTypes.CheckTransferHasErrorData): Promise { + // @ts-ignore + const balance = data.addressFrom && data.addressFrom !== '' ? await this._web3.eth.getBalance(data.addressFrom) : 0 + if (balance > 0) { + return { isOk: true } + } else { + // @ts-ignore + return { isOk: false, code: 'TOKEN', parentBlockchain: this._mainTokenBlockchain, parentCurrency: this._mainCurrencyCode } + } + } + + checkSendAllModal(data: { currencyCode: any }): boolean { + return false + } + + + async getFeeRate(data: BlocksoftBlockchainTypes.TransferData, privateData?: BlocksoftBlockchainTypes.TransferPrivateData, additionalData: BlocksoftBlockchainTypes.TransferAdditionalData = {}): Promise { + if (this.checkWeb3CurrentServerUpdated()) { + this._token = new this._web3.eth.Contract(abi.ERC20, this._settings.tokenAddress) + } + + if (typeof data.dexOrderData !== 'undefined' && data.dexOrderData) { + BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTransferProcessor.getFeeRate dex ' + data.addressFrom + ' started') + return super.getFeeRate(data, privateData, additionalData) + } + + const tmpData = { ...data } + if (typeof data.transactionRemoveByFee !== 'undefined' && data.transactionRemoveByFee) { + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTxProcessorErc20.getFeeRate removeByFee no token ' + this._tokenAddress) + tmpData.amount = '0' + return super.getFeeRate(tmpData, privateData, additionalData) + } + // @ts-ignore + BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTxProcessorErc20.getFeeRate estimateGas started token ' + this._tokenAddress) + let estimatedGas + + try { + const basicAddressTo = data.addressTo.toLowerCase() + let firstAddressTo = basicAddressTo + let eventTitle = 'v20_' + this._mainCurrencyCode.toLowerCase() + '_gas_limit_token1 ' + if (basicAddressTo === data.addressFrom.toLowerCase()) { + const tmp1 = '0xA09fe17Cb49D7c8A7858C8F9fCac954f82a9f487' + const tmp2 = '0xf1Cff704c6E6ce459e3E1544a9533cCcBDAD7B99' + firstAddressTo = data.addressFrom === tmp1 ? tmp2 : tmp1 + // @ts-ignore + BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTxProcessorErc20.getFeeRate estimateGas addressToChanged ' + basicAddressTo + ' => ' + firstAddressTo) + eventTitle = 'v20_' + this._mainCurrencyCode.toLowerCase() + '_gas_limit_token2 ' + } + + let serverEstimatedGas = 0 + let serverEstimatedGas2 = 0 + let serverEstimatedGas3 = 0 + try { + serverEstimatedGas = await this._token.methods.transfer(firstAddressTo, data.amount).estimateGas({ from: data.addressFrom }) + } catch (e) { + e.message += ' while transfer check1 ' + data.amount + ' firstAddressTo ' + firstAddressTo + ' from ' + data.addressFrom + if (config.debug.cryptoErrors) { + console.log(this._settings.currencyCode + ' EthTxProcessorErc20.getFeeRate estimateGas error1 ' + e.message) + } + BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTxProcessorErc20.getFeeRate estimateGas error1 ' + e.message) + } + try { + serverEstimatedGas2 = await this._token.methods.transfer(basicAddressTo, data.amount).estimateGas({ from: data.addressFrom }) + } catch (e) { + e.message += ' while transfer check2 ' + data.amount +' basicAddressTo ' + firstAddressTo + ' from ' + data.addressFrom + if (config.debug.cryptoErrors) { + console.log(this._settings.currencyCode + ' EthTxProcessorErc20.getFeeRate estimateGas error2 ' + e.message) + } + BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTxProcessorErc20.getFeeRate estimateGas error2 ' + e.message) + } + // @ts-ignore + const tmp3 = data.amount * 1 + try { + serverEstimatedGas3 = await this._token.methods.transfer(basicAddressTo, tmp3).estimateGas({ from: data.addressFrom }) + } catch (e) { + e.message += ' while transfer check3 ' + tmp3 +' basicAddressTo ' + firstAddressTo + ' from ' + data.addressFrom + if (config.debug.cryptoErrors) { + console.log(this._settings.currencyCode + ' EthTxProcessorErc20.getFeeRate estimateGas error3 ' + e.message) + } + BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTxProcessorErc20.getFeeRate estimateGas error3 ' + e.message) + } + + + if (serverEstimatedGas2 > serverEstimatedGas) { + estimatedGas = serverEstimatedGas2 + } else { + estimatedGas = serverEstimatedGas + } + if (estimatedGas < serverEstimatedGas3) { + estimatedGas = serverEstimatedGas3 + } + + let minGas = BlocksoftExternalSettings.getStatic(this._settings.tokenBlockchain + '_MIN_GAS_ERC20') + if (typeof minGas === 'undefined' || !minGas) { + minGas = BlocksoftExternalSettings.getStatic('ETH_MIN_GAS_ERC20') + } + if (typeof minGas === 'undefined' || !minGas) { + minGas = 70200 + } + if (estimatedGas < minGas) { + estimatedGas = minGas + } + MarketingEvent.logOnlyRealTime(eventTitle + this._settings.currencyCode + ' ' + data.addressFrom + ' => ' + data.addressTo, + { + amount: data.amount + '', + estimatedGas, + serverEstimatedGas, + serverEstimatedGas2, + serverEstimatedGas3 + }) + + } catch (e) { + this.checkError(e, data) + } + + + BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTxProcessorErc20.getFeeRate estimateGas finished ' + estimatedGas) + const result = await super.getFeeRate(tmpData, privateData, { ...additionalData, ...{ gasLimit : estimatedGas } }) + return result + } + + async getTransferAllBalance(data: BlocksoftBlockchainTypes.TransferData, privateData?: BlocksoftBlockchainTypes.TransferPrivateData, additionalData: BlocksoftBlockchainTypes.TransferAdditionalData = {}): Promise { + const tmpData = { ...data } + if (!tmpData.amount || tmpData.amount === '0') { + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTransferProcessorErc20.getTransferAllBalance ' + data.addressFrom + ' token ' + this._tokenAddress + ' started with load balance needed') + try { + // @ts-ignore + tmpData.amount = await this._token.methods.balanceOf(data.addressFrom).call() + } catch (e) { + this.checkError(e, data) + } + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTransferProcessorErc20.getTransferAllBalance ' + data.addressFrom + ' token ' + this._tokenAddress + ' started with loaded balance ' + tmpData.amount) + } else { + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTransferProcessorErc20.getTransferAllBalance ' + data.addressFrom + ' token ' + this._tokenAddress + ' started with preset balance ' + tmpData.amount) + } + + const result = await super.getTransferAllBalance(tmpData, privateData, additionalData) + return result + } + + async sendTx(data: BlocksoftBlockchainTypes.TransferData, privateData: BlocksoftBlockchainTypes.TransferPrivateData, uiData: BlocksoftBlockchainTypes.TransferUiData): Promise { + if (typeof data.dexOrderData !== 'undefined' && data.dexOrderData) { + BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTransferProcessor.sendTx dex ' + data.addressFrom + ' started') + return super.sendTx(data, privateData, uiData) + } + const tmpData = { ...data } + if (typeof data.transactionRemoveByFee !== 'undefined' && data.transactionRemoveByFee) { + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTxProcessorErc20.sendTx removeByFee no token ' + this._tokenAddress) + tmpData.amount = '0' + return super.sendTx(tmpData, privateData, uiData) + } + + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTxProcessorErc20.sendTx started token ' + this._tokenAddress) + + try { + const basicAddressTo = data.addressTo.toLowerCase() + tmpData.blockchainData = this._token.methods.transfer(basicAddressTo, data.amount).encodeABI() + tmpData.basicAddressTo = basicAddressTo + tmpData.basicAmount = data.amount + tmpData.basicToken = this._tokenAddress + } catch (e) { + this.checkError(e, data) + } + // @ts-ignore + BlocksoftCryptoLog.log('EthTxProcessorErc20 encodeABI finished', tmpData.blockchainData) + tmpData.amount = '0' + tmpData.addressTo = this._tokenAddress + return super.sendTx(tmpData, privateData, uiData) + } +} diff --git a/crypto/blockchains/eth/apis/EthNftMatic.js b/crypto/blockchains/eth/apis/EthNftMatic.js new file mode 100644 index 000000000..e8a79c9ba --- /dev/null +++ b/crypto/blockchains/eth/apis/EthNftMatic.js @@ -0,0 +1,101 @@ +/** + * @version 0.50 + */ +import BlocksoftAxios from '@crypto/common/BlocksoftAxios' +import BlocksoftUtils from '@crypto/common/BlocksoftUtils' +import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog' + +const API_PATH = 'https://microscanners.trustee.deals/getAllNfts/' + +/** + * https://microscanners.trustee.deals/getMaticNfts/0xf1Cff704c6E6ce459e3E1544a9533cCcBDAD7B99 + * https://microscanners.trustee.deals/getAllNfts/0xf1Cff704c6E6ce459e3E1544a9533cCcBDAD7B99? + * @param data.address + * @param data.tokenBlockchainCode + * @param data.customAssets + */ +export default async function(data) { + if (!data.address) return false + + const link = API_PATH + data.address + '?tokenBlockchainCode=' + data.tokenBlockchainCode + '&tokens=' + data.customAssets.join(',') + const result = await BlocksoftAxios.get(link) + + /** + * @var tmp.animation_url + * @var tmp.image + * @var tmp.name + * @var tmp.description + * @var tmp.token_index + * @var tmp.contract_address + */ + const formatted = [] + const collections = [] + let usdTotal = 0 + + + if (result && result.data && typeof result.data !== 'undefined' && result.data && result.data.length) { + for (const tmp of result.data) { + const one = { + id: tmp.id, + tokenId: tmp.token_index, + contractAddress: tmp.contract_address, + contractSchema: tmp.contract_schema || 'ERC721', + tokenBlockchainCode: tmp.token_blockchain_code || data.tokenBlockchainCode, + tokenBlockchain: tmp.token_blockchain || data.tokenBlockchain, + tokenQty : tmp.qty || 1, + img: tmp.image, + title: tmp.name || tmp.title, + subTitle: tmp.subtitle || '', + desc: tmp.description || '', + cryptoCurrencySymbol: tmp.crypto_currency_symbol || '', + cryptoValue: tmp.crypto_value || '', + usdValue: tmp.usd_value || '', + permalink: tmp.permalink || false + } + try { + if (one.desc && !one.subTitle) { + one.subTitle = one.desc.length > 20 ? (one.desc.substring(0, 20) + '...') : one.desc + } + } catch (e) { + BlocksoftCryptoLog.log('EthTokenProcessorNft EthNftMatic name error ' + e.message) + } + + if (one.usdValue && one.usdValue * 1 > 0) { + usdTotal = usdTotal + one.usdValue * 1 + } + + + let collectionKey = '' + try { + if (typeof tmp.collection !== 'undefined') { + collectionKey = tmp.collection.name + one.contractAddress + if (typeof collections[collectionKey] === 'undefined') { + collections[collectionKey] = { + numberAssets: 1, + title: tmp.collection.name, + img: tmp.collection.image, + walletCurrency: one.tokenBlockchainCode, + assets: [one] + } + } else { + collections[collectionKey].numberAssets++ + collections[collectionKey].assets.push(one) + } + } + } catch (e) { + console.log('EthTokenProcessorNft EthNftMatic collection error ' + e.message) + } + + + formatted.push(one) + } + } + + const formattedCollections = [] + if (collections) { + for (const key in collections) { + formattedCollections.push(collections[key]) + } + } + return { assets: formatted, collections : formattedCollections, usdTotal} +} diff --git a/crypto/blockchains/eth/apis/EthNftOpensea.js b/crypto/blockchains/eth/apis/EthNftOpensea.js new file mode 100644 index 000000000..ca9b18607 --- /dev/null +++ b/crypto/blockchains/eth/apis/EthNftOpensea.js @@ -0,0 +1,143 @@ +/** + * @version 0.50 + */ +import BlocksoftAxios from '@crypto/common/BlocksoftAxios' +import BlocksoftUtils from '@crypto/common/BlocksoftUtils' +import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog' + +const API_PATH = 'https://api.opensea.io/api/v1/' +const API_TEST_PATH = 'https://testnets-api.opensea.io/api/v1/' +/** + * https://docs.opensea.io/reference/getting-assets + * curl --request GET --url https://api.opensea.io/api/v1/assets?order_direction=desc&offset=0&limit=20&owner=0x6cdb97bf46d77233cc943264633c2ed56bcf6f1f + * curl --request GET --url https://testnets-api.opensea.io/api/v1/assets?order_direction=desc&offset=0&limit=20&owner=0x6cdb97bf46d77233cc943264633c2ed56bcf6f1f + * @param data.address + * @param data.tokenBlockchainCode + */ +export default async function(data) { + + let link + if (data.tokenBlockchainCode === 'ETH_RINKEBY') { + link = API_TEST_PATH + } else { + link = API_PATH + } + if (!data.address) return false + link += 'assets?order_direction=desc&owner=' + data.address + const result = await BlocksoftAxios.getWithoutBraking(link) + + + /** + * @var tmp.id + * @var tmp.token_id + * @var tmp.image_thumbnail_url + * @var tmp.name + * @var tmp.title + * @var tmp.last_sale + * @var tmp.last_sale.total_price + * @var tmp.last_sale.payment_token + * @var tmp.last_sale.payment_token.symbol + * @var tmp.last_sale.payment_token.name + * @var tmp.last_sale.payment_token.decimals + * @var tmp.last_sale.payment_token.usd_price + * @var tmp.asset_contract.address + * @var tmp.asset_contract.schema_name ERC721 + */ + const formatted = [] + const collections = [] + let usdTotal = 0 + + + if (result && result.data && typeof result.data.assets !== 'undefined' && result.data.assets && result.data.assets.length) { + for (const tmp of result.data.assets) { + const one = { + id: tmp.id, + tokenId: tmp.token_id, + contractAddress: '', + contractSchema: 'ERC721', + tokenBlockchainCode: data.tokenBlockchainCode, + tokenBlockchain: data.tokenBlockchain, + tokenQty : 1, + img: tmp.image_preview_url, + title: tmp.name || tmp.title, + subTitle: '', + desc: '', + cryptoCurrencySymbol: '', + cryptoValue: '', + usdValue: '', + permalink: tmp.permalink || false + } + try { + if (!one.title || typeof one.title === 'undefined') { + if (typeof tmp.asset_contract.name !== 'undefined') { + one.title = tmp.asset_contract.name + } + } + if (typeof tmp.asset_contract.description !== 'undefined' && tmp.asset_contract.description) { + one.desc = tmp.asset_contract.description + } + if (one.title.indexOf(tmp.token_id) === -1) { + one.subTitle = '#' + tmp.token_id + } else if (one.desc) { + one.subTitle = one.desc.length > 20 ? (one.desc.substring(0, 20) + '...') : one.desc + } + } catch (e) { + BlocksoftCryptoLog.log('EthTokenProcessorNft EthNftOpensea name error ' + e.message) + } + + + try { + if (typeof tmp.asset_contract.address !== 'undefined' && tmp.asset_contract.address) { + one.contractAddress = tmp.asset_contract.address + } + if (typeof tmp.asset_contract.schema_name !== 'undefined' && tmp.asset_contract.schema_name) { + one.contractSchema = tmp.asset_contract.schema_name + } + } catch (e) { + BlocksoftCryptoLog.log('EthTokenProcessorNft EthNftOpensea contract error ' + e.message) + } + + try { + if (typeof tmp.last_sale !== 'undefined' && tmp.last_sale) { + one.cryptoCurrencySymbol = tmp.last_sale.payment_token.symbol + one.cryptoValue = BlocksoftUtils.toUnified(tmp.last_sale.total_price, tmp.last_sale.payment_token.decimals) + one.usdValue = tmp.last_sale.payment_token.usd_price + usdTotal = usdTotal + tmp.last_sale.payment_token.usd_price * 1 + } + } catch (e) { + BlocksoftCryptoLog.log('EthTokenProcessorNft EthNftOpensealast_sale error ' + e.message, JSON.stringify(tmp)) + } + + let collectionKey = '' + try { + if (typeof tmp.collection !== 'undefined') { + collectionKey = tmp.collection.name + '_' + tmp.collection.payout_address + if (typeof collections[collectionKey] === 'undefined') { + collections[collectionKey] = { + numberAssets: 1, + title: tmp.collection.name, + img: tmp.collection.banner_image_url || tmp.collection.image_url, + walletCurrency: data.tokenBlockchainCode, + assets: [one] + } + } else { + collections[collectionKey].numberAssets++ + collections[collectionKey].assets.push(one) + } + } + } catch (e) { + BlocksoftCryptoLog.log('EthTokenProcessorNft EthNftOpensea collection error ' + e.message) + } + + formatted.push(one) + } + } + + const formattedCollections = [] + if (collections) { + for (const key in collections) { + formattedCollections.push(collections[key]) + } + } + return { assets: formatted, collections : formattedCollections, usdTotal} +} diff --git a/crypto/blockchains/eth/basic/EthBasic.js b/crypto/blockchains/eth/basic/EthBasic.js new file mode 100644 index 000000000..67e4f2176 --- /dev/null +++ b/crypto/blockchains/eth/basic/EthBasic.js @@ -0,0 +1,341 @@ +/** + * @version 0.5 + * https://etherscan.io/apis#accounts + */ +import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog' +import MarketingEvent from '@app/services/Marketing/MarketingEvent' +import { Web3Injected } from '@crypto/services/Web3Injected' +import config from '@app/config/config' + +export default class EthBasic { + /** + * @type {Web3} + * @public + */ + _web3 + + /** + * @type {string} + * @public + */ + _etherscanSuffix + + /** + * @type {string} + * @public + */ + _etherscanApiPath + + /** + * @type {string} + * @public + */ + _etherscanApiPathInternal + + /** + * @type {string} + * @public + */ + _oklinkAPI + + /** + * @type {string} + * @public + */ + _trezorServer + + /** + * @type {string} + * @public + */ + _trezorServerCode = 'ETH_TREZOR_SERVER' + + /** + * @type {string} + * @public + */ + _tokenAddress + + /** + * @type {string} + * @public + */ + _delegateAddress + + + /** + * @param {string} settings.network + * @param {string} settings.currencyCode + */ + constructor(settings) { + if (typeof settings === 'undefined' || !settings) { + throw new Error('EthNetworked requires settings') + } + if (typeof settings.network === 'undefined') { + throw new Error('EthNetworked requires settings.network') + } + + + this._settings = settings + this._etherscanApiPathDeposits = false + this._isTestnet = false + + this._oklinkAPI = false + if (settings.currencyCode === 'BNB_SMART' || (typeof settings.tokenBlockchain !== 'undefined' && settings.tokenBlockchain === 'BNB')) { + + this._etherscanSuffix = '' + this._etherscanApiPath = `https://api.bscscan.com/api?module=account&sort=desc&action=txlist&apikey=YourApiKeyToken` + this._etherscanApiPathInternal = `https://api.bscscan.com/api?module=account&sort=desc&action=txlistinternal&apikey=YourApiKeyToken` + this._etherscanApiPathForFee = `https://api.bscscan.com/api?module=proxy&action=eth_gasPrice&apikey=YourApiKeyToken` + + this._trezorServer = false + this._trezorServerCode = false + + this._mainCurrencyCode = 'BNB' + this._mainTokenType = 'BNB_SMART_20' + this._mainTokenBlockchain = 'Binance' + this._mainChainId = 56 + + } else if (settings.currencyCode === 'ETC') { + + this._etherscanSuffix = false + this._etherscanApiPath = false + this._etherscanApiPathInternal = false + this._etherscanApiPathForFee = false + + this._trezorServer = 'to_load' + this._trezorServerCode = 'ETC_TREZOR_SERVER' + + this._mainCurrencyCode = 'ETC' + this._mainTokenType = 'ETC_ERC_20' + this._mainTokenBlockchain = 'Ethereum Classic' + this._mainChainId = 61 // https://ethereumclassic.org/development/porting + } else if (settings.currencyCode === 'ETH_POW' || (typeof settings.tokenBlockchain !== 'undefined' && settings.tokenBlockchain === 'ETH_POW')) { + + this._etherscanSuffix = '' + this._etherscanApiPath = false + this._etherscanApiPathInternal = false + this._etherscanApiPathForFee = false + + this._trezorServer = false + this._trezorServerCode = false + + this._oklinkAPI = 'e11964ac-cfb9-406f-b2c5-3db76f91aebd' + + this._mainCurrencyCode = 'ETH_POW' + this._mainTokenType = 'ETH_POW_ERC_20' + this._mainTokenBlockchain = 'ETH_POW' + this._mainChainId = 10001 + } else if (settings.currencyCode === 'VLX' || (typeof settings.tokenBlockchain !== 'undefined' && settings.tokenBlockchain === 'VLX')) { + + this._etherscanSuffix = '' + this._etherscanApiPath = `https://evmexplorer.velas.com/api?module=account&sort=desc&action=txlist` + this._etherscanApiPathInternal = false + this._etherscanApiPathForFee = false + + this._trezorServer = false + this._trezorServerCode = false + + this._mainCurrencyCode = 'VLX' + this._mainTokenType = 'VLX_ERC_20' + this._mainTokenBlockchain = 'VLX' + this._mainChainId = 106 + } else if (settings.currencyCode === 'ONE' || (typeof settings.tokenBlockchain !== 'undefined' && settings.tokenBlockchain === 'ONE')) { + + this._etherscanSuffix = '' + this._etherscanApiPath = false + this._etherscanApiPathInternal = false + this._etherscanApiPathForFee = false + + this._trezorServer = false + this._trezorServerCode = false + + this._mainCurrencyCode = 'ONE' + this._mainTokenType = 'ONE_ERC_20' + this._mainTokenBlockchain = 'ONE' + this._mainChainId = 1666600000 + } else if (settings.currencyCode === 'METIS' || (typeof settings.tokenBlockchain !== 'undefined' && settings.tokenBlockchain === 'METIS')) { + + this._etherscanSuffix = '' + this._etherscanApiPath = `https://andromeda-explorer.metis.io/api?module=account&sort=desc&action=txlist` + this._etherscanApiPathInternal = `https://andromeda-explorer.metis.io/api?module=account&sort=desc&action=txlistinternal` + this._etherscanApiPathForFee = false + + this._trezorServer = false + this._trezorServerCode = false + + this._mainCurrencyCode = 'METIS' + this._mainTokenType = 'METIS_ERC_20' + this._mainTokenBlockchain = 'METIS' + this._mainChainId = 1088 + } else if (settings.currencyCode === 'OPTIMISM') { + + this._etherscanSuffix = '' + this._etherscanApiPath = `https://api.optimistic.etherscan.io/api?module=account&sort=desc&action=txlist&apikey=YourApiKeyToken` + this._etherscanApiPathInternal = `https://api.optimistic.etherscan.io/api?module=account&sort=desc&action=txlistinternal&apikey=YourApiKeyToken` + this._etherscanApiPathDeposits = 'https://api-optimistic.etherscan.io/api?module=account&action=getdeposittxs' + this._etherscanApiPathForFee = `https://api.optimistic.etherscan.io/api?module=proxy&action=eth_gasPrice&apikey=YourApiKeyToken` + + this._trezorServer = false + this._trezorServerCode = false + + this._mainCurrencyCode = 'OPTIMISM' + this._mainTokenType = 'OPTI_ERC_20' + this._mainTokenBlockchain = 'Optimistic Ethereum' + this._mainChainId = 10 // https://community.optimism.io/docs/developers/metamask.html#connecting-with-chainid-link + } else if (settings.currencyCode === 'AMB') { + + this._etherscanSuffix = false + this._etherscanApiPath = false + this._etherscanApiPathInternal = false + this._etherscanApiPathForFee = false + + this._trezorServer = 'to_load' + this._trezorServerCode = 'AMB_TREZOR_SERVER' + + this._mainCurrencyCode = 'AMB' + this._mainTokenType = 'AMB_ERC_20' + this._mainTokenBlockchain = 'Ambrosus Network' + this._mainChainId = 16718 // 0x414e + } else if (settings.currencyCode === 'MATIC' || (typeof settings.tokenBlockchain !== 'undefined' && settings.tokenBlockchain === 'MATIC')) { + + this._etherscanSuffix = '' + this._etherscanApiPath = `https://api.polygonscan.com/api?module=account&sort=desc&action=txlist&apikey=YourApiKeyToken` + this._etherscanApiPathInternal = `https://api.polygonscan.com/api?module=account&sort=desc&action=txlistinternal&apikey=YourApiKeyToken` + this._etherscanApiPathForFee = `https://api.polygonscan.com/api?module=proxy&action=eth_gasPrice&apikey=YourApiKeyToken` + + this._trezorServer = false + this._trezorServerCode = false + + this._mainCurrencyCode = 'MATIC' + this._mainTokenType = 'MATIC_ERC_20' + this._mainTokenBlockchain = 'Polygon Network' + this._mainChainId = 137 + } else if (settings.currencyCode === 'FTM' || (typeof settings.tokenBlockchain !== 'undefined' && settings.tokenBlockchain === 'FTM')) { + + this._etherscanSuffix = '' + this._etherscanApiPath = `https://api.ftmscan.com/api?module=account&sort=desc&action=txlist&apikey=YourApiKeyToken` + this._etherscanApiPathInternal = `https://api.ftmscan.com/api?module=account&sort=desc&action=txlistinternal&apikey=YourApiKeyToken` + this._etherscanApiPathForFee = false // invalid now `https://api.ftmscan.com/api?module=proxy&action=eth_gasPrice&apikey=YourApiKeyToken` + + this._trezorServer = false + this._trezorServerCode = false + + this._mainCurrencyCode = 'FTM' + this._mainTokenType = 'FTM_ERC_20' + this._mainTokenBlockchain = 'Fantom Network' + this._mainChainId = 250 + } else if (settings.currencyCode === 'BTTC' || (typeof settings.tokenBlockchain !== 'undefined' && settings.tokenBlockchain === 'BTTC')) { + + this._etherscanSuffix = '' + this._etherscanApiPath = `https://api.bttcscan.com/api?module=account&sort=desc&action=txlist&apikey=YourApiKeyToken` + this._etherscanApiPathInternal = `https://api.bttcscan.com/api?module=account&sort=desc&action=txlistinternal&apikey=YourApiKeyToken` + this._etherscanApiPathForFee = `https://api.bttcscan.com/api?module=proxy&action=eth_gasPrice&apikey=YourApiKeyToken` + + this._trezorServer = false + this._trezorServerCode = false + + this._mainCurrencyCode = 'BTTC' + this._mainTokenType = 'BTTC_ERC_20' + this._mainTokenBlockchain = 'BTTC Network' + this._mainChainId = 199 + } else if (settings.currencyCode === 'RSK') { + this._etherscanSuffix = false + this._etherscanApiPath = false + this._etherscanApiPathInternal = false + + this._trezorServer = false + this._trezorServerCode = false + + this._mainCurrencyCode = 'RSK' + this._mainTokenType = 'RSK_ERC_20' + this._mainTokenBlockchain = 'RSK Network' + this._mainChainId = 30 + } else { + + this._etherscanSuffix = (settings.network === 'mainnet') ? '' : ('-' + settings.network) + this._etherscanApiPath = `https://api${this._etherscanSuffix}.etherscan.io/api?module=account&sort=desc&action=txlist&apikey=YourApiKeyToken` + this._etherscanApiPathInternal = `https://api${this._etherscanSuffix}.etherscan.io/api?module=account&sort=desc&action=txlistinternal&apikey=YourApiKeyToken` + this._etherscanApiPathForFee = false + + if (settings.network === 'mainnet') { + this._trezorServer = 'to_load' + this._trezorServerCode = 'ETH_TREZOR_SERVER' + } else if (settings.network === 'ropsten') { + this._trezorServer = 'to_load' + this._trezorServerCode = 'ETH_ROPSTEN_TREZOR_SERVER' + this._isTestnet = true + } else { + this._trezorServer = false + this._trezorServerCode = false + this._isTestnet = true + } + + this._mainCurrencyCode = 'ETH' + this._mainTokenType = 'ETH_ERC_20' + this._mainTokenBlockchain = 'Ethereum' + this._mainChainId = false + } + + const type = this._mainChainId ? this._mainChainId : settings.network + this._web3 = Web3Injected(type) + this._tokenAddress = false + } + + checkWeb3CurrentServerUpdated() { + const type = this._mainChainId ? this._mainChainId : this._settings.network + const oldWeb3Link = this._web3.LINK + this._web3 = Web3Injected(type) + return !(this._web3.LINK === oldWeb3Link) + } + + checkError(e, data, txRBF = false, logData = {}) { + if (config.debug.cryptoErrors) { + console.log('EthBasic Error ' + e.message) + } + if (e.message.indexOf('Transaction has been reverted by the EVM') !== -1) { + BlocksoftCryptoLog.log('EthBasic checkError0.0 ' + e.message + ' for ' + data.addressFrom, logData) + throw new Error('SERVER_RESPONSE_REVERTED_BY_EVM') + } else if (e.message.indexOf('nonce too low') !== -1) { + BlocksoftCryptoLog.log('EthBasic checkError0.1 ' + e.message + ' for ' + data.addressFrom, logData) + let e2 + if (txRBF) { + e2 = new Error('SERVER_RESPONSE_TRANSACTION_ALREADY_MINED') + } else { + e2 = new Error('SERVER_RESPONSE_NONCE_ALREADY_MINED') + } + let nonce = logData.nonce || logData.setNonce + if (typeof nonce === 'undefined') { + nonce = '' + } + e2.logData = {nonce} + throw e2 + } else if (e.message.indexOf('gas required exceeds allowance') !== -1) { + BlocksoftCryptoLog.log('EthBasic checkError0.2 ' + e.message + ' for ' + data.addressFrom, logData) + if (this._settings.tokenAddress === 'undefined' || !this._settings.tokenAddress) { + throw new Error('SERVER_RESPONSE_TOO_MUCH_GAS_ETH') + } else { + throw new Error('SERVER_RESPONSE_TOO_MUCH_GAS_ETH_ERC20') + } + } else if (e.message.indexOf('insufficient funds') !== -1) { + BlocksoftCryptoLog.log('EthBasic checkError0.3 ' + e.message + ' for ' + data.addressFrom, logData) + if ((this._settings.currencyCode === 'ETH' || this._settings.currencyCode === 'BNB_SMART') && data.amount * 1 > 0) { + throw new Error('SERVER_RESPONSE_NOTHING_LEFT_FOR_FEE') + } else { + throw new Error('SERVER_RESPONSE_NOT_ENOUGH_FEE') + } + } else if (e.message.indexOf('underpriced') !== -1) { + BlocksoftCryptoLog.log('EthBasic checkError0.4 ' + e.message + ' for ' + data.addressFrom, logData) + throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_FEE') + } else if (e.message.indexOf('already known') !== -1) { + BlocksoftCryptoLog.log('EthBasic checkError0.5 ' + e.message + ' for ' + data.addressFrom, logData) + throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_FEE_FOR_REPLACEMENT') + } else if (e.message.indexOf('infura') !== -1) { + BlocksoftCryptoLog.log('EthBasic checkError0.6 ' + e.message + ' for ' + data.addressFrom, logData) + throw new Error('SERVER_RESPONSE_BAD_INTERNET') + } else { + MarketingEvent.logOnlyRealTime('v20_' + this._mainCurrencyCode.toLowerCase() + '_tx_error ' + this._settings.currencyCode + ' ' + data.addressFrom + ' => ' + data.addressTo + ' ' + e.message, logData) + throw e + } + } +} diff --git a/crypto/blockchains/eth/basic/EthNetworkPrices.js b/crypto/blockchains/eth/basic/EthNetworkPrices.js new file mode 100644 index 000000000..6ab0ef5b4 --- /dev/null +++ b/crypto/blockchains/eth/basic/EthNetworkPrices.js @@ -0,0 +1,269 @@ +/** + * @version 0.5 + */ +import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog' +import BlocksoftAxios from '../../../common/BlocksoftAxios' +import BlocksoftUtils from '../../../common/BlocksoftUtils' +import BlocksoftExternalSettings from '../../../common/BlocksoftExternalSettings' +import MarketingEvent from '../../../../app/services/Marketing/MarketingEvent' +import config from '../../../../app/config/config' +import EthRawDS from '../stores/EthRawDS' +import EthTmpDS from '../stores/EthTmpDS' + + +const ESTIMATE_PATH = 'https://ethgasstation.info/json/ethgasAPI.json' +const ESTIMATE_MAX_TRY = 50 // max tries before error appear in axios get +const MAGIC_TX_DIVIDER = 10 + +const CACHE_VALID_TIME = 60000 // 1 minute +let CACHE_FEES_ETH = false +let CACHE_FEES_ETH_TIME = 0 + +let CACHE_PREV_DATA = { 'fastest': 100.0, 'safeLow': 13.0, 'average': 30.0 } + +const CACHE_PROXY_VALID_TIME = 10000 // 10 seconds +let CACHE_PROXY_DATA = { + result : '', address : '' +} +let CACHE_PROXY_TIME = 0 +class EthNetworkPrices { + + + async getWithProxy(mainCurrencyCode, isTestnet, address, logData = {}) { + if (mainCurrencyCode !== 'ETH' || isTestnet) { + return false + } + const { apiEndpoints } = config.proxy + const baseURL = MarketingEvent.DATA.LOG_TESTER ? apiEndpoints.baseURLTest : apiEndpoints.baseURL + const proxy = baseURL + '/eth/getFees' + const now = new Date().getTime() + if (CACHE_PROXY_DATA.address === address && now - CACHE_PROXY_TIME < CACHE_PROXY_VALID_TIME) { + BlocksoftCryptoLog.log(mainCurrencyCode + ' EthNetworkPricesProvider.getWithProxy from cache', logData) + return CACHE_PROXY_DATA.result + } + + BlocksoftCryptoLog.log(mainCurrencyCode + ' EthNetworkPricesProvider.getWithProxy started', logData) + let checkResult = false + let index = 0 + do { + try { + checkResult = await BlocksoftAxios.post(proxy, { + address, + logData, + marketingData: MarketingEvent.DATA + }, 20000) + } catch (e) { + if (config.debug.cryptoErrors) { + console.log('EthNetworkPricesProvider.getWithProxy proxy error checkError ' + e.message) + } + } + index++ + } while(index < 3 && !checkResult) + + if (checkResult !== false) { + if (typeof checkResult.data !== 'undefined') { + await BlocksoftCryptoLog.log(mainCurrencyCode + ' EthNetworkPricesProvider.getWithProxy proxy checkResult1 ', checkResult.data) + if (typeof checkResult.data.status === 'undefined' || checkResult.data.status === 'error') { + if (config.debug.cryptoErrors) { + console.log('EthNetworkPricesProvider.getWithProxy proxy error checkResult1 ', checkResult) + } + checkResult = false + } else if (checkResult.data.status === 'notice') { + throw new Error(checkResult.data.msg) + } + } else { + await BlocksoftCryptoLog.log(mainCurrencyCode + ' EthNetworkPricesProvider.getWithProxy proxy checkResult2 ', checkResult) + if (config.debug.cryptoErrors) { + console.log('EthNetworkPricesProvider.getWithProxy proxy error checkResult2 ', checkResult) + } + } + } else { + if (config.debug.cryptoErrors) { + console.log('EthNetworkPricesProvider.getWithProxy proxy error checkResultEmpty ', checkResult) + } + } + + if (checkResult === false) { + return { + gasPrice: await this.getOnlyFees(mainCurrencyCode, isTestnet, address, logData) + } + } + + const result = checkResult.data + if (typeof result.gasPrice !== 'undefined') { + for (const key in result.gasPrice) { + result.gasPrice[key] = BlocksoftUtils.div(BlocksoftUtils.toWei(result.gasPrice[key], 'gwei'), MAGIC_TX_DIVIDER) // in gwei to wei + magic + } + } + const indexed = {} + let updatedCache = false + if (typeof checkResult.data.maxScanned !== 'undefined') { + await EthTmpDS.saveNonce(this._mainCurrencyCode, address, 'maxScanned', checkResult.data.maxScanned) + updatedCache = true + } + if (typeof checkResult.data.maxSuccess !== 'undefined') { + await EthTmpDS.saveNonce(this._mainCurrencyCode, address, 'maxSuccess', checkResult.data.maxSuccess) + updatedCache = true + } + if (typeof checkResult.data.txsToRemove !== 'undefined' && checkResult.data.txsToRemove) { + for (const transactionHash of checkResult.data.txsToRemove) { + await EthRawDS.cleanRawHash({ transactionHash }) + indexed[transactionHash] = 1 + } + updatedCache = true + } + if (typeof checkResult.data.txsToSuccess !== 'undefined' && checkResult.data.txsToSuccess) { + for (const transactionHash of checkResult.data.txsToSuccess) { + await EthTmpDS.setSuccess(transactionHash) + } + updatedCache = true + } + if (typeof checkResult.data.queryTxs !== 'undefined' && typeof checkResult.data.queryLength !== 'undefined') { + updatedCache = true + } + if (updatedCache) { + result.maxNonceLocal = await EthTmpDS.getCache(mainCurrencyCode, address, indexed) + } + if (typeof checkResult.data.queryTxs !== 'undefined') { + result.maxNonceLocal.queryTxs = checkResult.data.queryTxs + } + if (typeof checkResult.data.queryLength !== 'undefined') { + result.maxNonceLocal.queryLength = checkResult.data.queryLength + } + CACHE_PROXY_DATA = { result, address } + CACHE_PROXY_TIME = now + + return result + } + + async getOnlyFees(mainCurrencyCode, isTestnet, address, logData = {}) { + logData.resultFeeSource = 'fromCache' + const now = new Date().getTime() + if (CACHE_FEES_ETH && (now - CACHE_FEES_ETH_TIME) < CACHE_VALID_TIME) { + logData.resultFeeCacheTime = CACHE_FEES_ETH_TIME + logData.resultFee = JSON.stringify(CACHE_FEES_ETH) + // noinspection ES6MissingAwait + MarketingEvent.logEvent('v20_estimate_fee_' + mainCurrencyCode.toLowerCase() + '_result', logData) + BlocksoftCryptoLog.log(mainCurrencyCode + ' EthNetworkPricesProvider.getOnlyFees used cache => ' + JSON.stringify(CACHE_FEES_ETH)) + return this._format() + } + + BlocksoftCryptoLog.log(mainCurrencyCode + ' EthNetworkPricesProvider.getOnlyFees no cache load') + + let link = `${ESTIMATE_PATH}` + let tmp = false + try { + tmp = await BlocksoftAxios.getWithoutBraking(link, ESTIMATE_MAX_TRY) + if (tmp.data && tmp.data.fastest) { + if (typeof tmp.data.gasPriceRange !== 'undefined') { + delete tmp.data.gasPriceRange + } + logData.resultFeeSource = 'reloaded' + CACHE_PREV_DATA = tmp.data + BlocksoftCryptoLog.log(mainCurrencyCode + ' EthNetworkPricesProvider.getOnlyFees loaded new fee', CACHE_PREV_DATA) + } else { + logData.resultFeeSource = 'fromLoadCache' + link = 'prev' + BlocksoftCryptoLog.log(mainCurrencyCode + ' EthNetworkPricesProvider.getOnlyFees loaded prev fee as no fastest', CACHE_PREV_DATA) + } + } catch (e) { + // noinspection ES6MissingAwait + MarketingEvent.logEvent('estimate_fee_eth_load_error', { link, data: e.message }) + BlocksoftCryptoLog.log(mainCurrencyCode + ' EthNetworkPricesProvider.getOnlyFees loaded prev fee as error', CACHE_PREV_DATA) + // do nothing + } + + try { + await this._parseLoaded(mainCurrencyCode, CACHE_PREV_DATA, link) + } catch (e) { + if (config.debug.cryptoErrors) { + console.log(mainCurrencyCode + ' EthNetworkPricesProvider.getOnlyFees _parseLoaded error ' + e.message) + } + // noinspection ES6MissingAwait + MarketingEvent.logEvent('estimate_fee_eth_parse_error', { link, data: e.message }) + BlocksoftCryptoLog.log(mainCurrencyCode + ' EthNetworkPricesProvider.getOnlyFees _parseLoaded error ' + e.message) + // do nothing + } + logData.resultFeeCacheTime = CACHE_FEES_ETH_TIME + logData.resultFee = JSON.stringify(CACHE_FEES_ETH) + MarketingEvent.logEvent('estimate_fee_eth_result', logData) + + return this._format() + } + + _format() { + return { 'speed_blocks_2': CACHE_FEES_ETH[2], 'speed_blocks_6': CACHE_FEES_ETH[6], 'speed_blocks_12': CACHE_FEES_ETH[12] } + } + + + /** + * @param {int} json.safeLow + * @param {int} json.average + * @param {int} json.fastest + * @private + */ + async _parseLoaded(mainCurrencyCode, json) { + CACHE_FEES_ETH = {} + + const externalSettings = await BlocksoftExternalSettings.getAll('ETH.getNetworkPrices') + addMultiply(mainCurrencyCode,2, json.fastest * 1, externalSettings) + addMultiply(mainCurrencyCode,6, json.average * 1, externalSettings) + addMultiply(mainCurrencyCode,12, json.safeLow * 1, externalSettings) + + if (CACHE_FEES_ETH[12] === CACHE_FEES_ETH[6]) { + if (CACHE_FEES_ETH[6] === CACHE_FEES_ETH[2]) { + CACHE_FEES_ETH[6] = Math.round(CACHE_FEES_ETH[12] * 1.1) + CACHE_FEES_ETH[2] = Math.round(CACHE_FEES_ETH[6] * 1.1) + } else { + CACHE_FEES_ETH[6] = Math.round(CACHE_FEES_ETH[12] * 1.1) + } + } else if (CACHE_FEES_ETH[6] === CACHE_FEES_ETH[2]) { + CACHE_FEES_ETH[2] = Math.round(CACHE_FEES_ETH[6] * 1.1) + } + if (CACHE_FEES_ETH[6] > CACHE_FEES_ETH[2]) { + const tmp = CACHE_FEES_ETH[6] + CACHE_FEES_ETH[6] = CACHE_FEES_ETH[2] + CACHE_FEES_ETH[2] = tmp + } + + try { + CACHE_FEES_ETH[12] = BlocksoftUtils.div(BlocksoftUtils.toWei(CACHE_FEES_ETH[12], 'gwei'), MAGIC_TX_DIVIDER) // in gwei to wei + magic + CACHE_FEES_ETH[6] = BlocksoftUtils.div(BlocksoftUtils.toWei(CACHE_FEES_ETH[6], 'gwei'), MAGIC_TX_DIVIDER) // in gwei to wei + magic + CACHE_FEES_ETH[2] = BlocksoftUtils.div(BlocksoftUtils.toWei(CACHE_FEES_ETH[2], 'gwei'), MAGIC_TX_DIVIDER) // in gwei to wei + magic + } catch (e) { + e.message += ' in EthPrice Magic divider' + throw e + } + + CACHE_FEES_ETH_TIME = new Date().getTime() + } +} + +function addMultiply(mainCurrencyCode, blocks, fee, externalSettings) { + if (typeof externalSettings['ETH_CURRENT_PRICE_' + blocks] !== 'undefined' && externalSettings['ETH_CURRENT_PRICE_' + blocks] > 0) { + CACHE_FEES_ETH[blocks] = externalSettings['ETH_CURRENT_PRICE_' + blocks] + BlocksoftCryptoLog.log(mainCurrencyCode + ' EthNetworkPricesProvider current price result', { blocks, fee, current: externalSettings['ETH_CURRENT_PRICE_' + blocks], res: CACHE_FEES_ETH[blocks] }) + } else if (typeof externalSettings['ETH_MULTI_' + blocks] !== 'undefined' && externalSettings['ETH_MULTI_' + blocks] > 0) { + CACHE_FEES_ETH[blocks] = BlocksoftUtils.mul(fee, externalSettings['ETH_MULTI_' + blocks]) + BlocksoftCryptoLog.log(mainCurrencyCode + ' EthNetworkPricesProvider addMultiply' + blocks + ' result', { blocks, fee, mul: externalSettings['ETH_MULTI_' + blocks], res: CACHE_FEES_ETH[blocks] }) + } else if (typeof externalSettings.ETH_MULTI !== 'undefined' && externalSettings.ETH_MULTI > 0) { + CACHE_FEES_ETH[blocks] = BlocksoftUtils.mul(fee, externalSettings.ETH_MULTI) * 1 + BlocksoftCryptoLog.log(mainCurrencyCode + ' EthNetworkPricesProvider addMultiply result', { blocks, fee, mul: externalSettings.ETH_MULTI, res: CACHE_FEES_ETH[blocks] }) + } else { + CACHE_FEES_ETH[blocks] = fee + } + if (typeof externalSettings['ETH_MIN_' + blocks] !== 'undefined' && externalSettings['ETH_MIN_' + blocks] > 0) { + if (externalSettings['ETH_MIN_' + blocks] > CACHE_FEES_ETH[blocks]) { + CACHE_FEES_ETH[blocks] = externalSettings['ETH_MIN_' + blocks] + } + } else if (typeof externalSettings.ETH_MIN !== 'undefined' && externalSettings.ETH_MIN > 0) { + if (externalSettings.ETH_MIN > CACHE_FEES_ETH[blocks]) { + CACHE_FEES_ETH[blocks] = externalSettings.ETH_MIN + } + } +} + + +const singleton = new EthNetworkPrices() +export default singleton + diff --git a/crypto/blockchains/eth/basic/EthTxSendProvider.ts b/crypto/blockchains/eth/basic/EthTxSendProvider.ts new file mode 100644 index 000000000..443b59e80 --- /dev/null +++ b/crypto/blockchains/eth/basic/EthTxSendProvider.ts @@ -0,0 +1,305 @@ +/** + * @author Ksu + * @version 0.32 + */ +import { BlocksoftBlockchainTypes } from '../../BlocksoftBlockchainTypes' +import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog' +import BlocksoftUtils from '../../../common/BlocksoftUtils' +import EthTmpDS from '../stores/EthTmpDS' +import EthRawDS from '../stores/EthRawDS' +import BlocksoftExternalSettings from '../../../common/BlocksoftExternalSettings' +import BlocksoftAxios from '../../../common/BlocksoftAxios' +import config from '../../../../app/config/config' +import MarketingEvent from '../../../../app/services/Marketing/MarketingEvent' + + +export default class EthTxSendProvider { + + private _web3: any + private _trezorServerCode: any + private _trezorServer: any + private _settings: any + private _mainCurrencyCode: string + private _mainChainId: any + + constructor(web3: any, trezorServerCode: any, mainCurrencyCode : string, mainChainId : any, settings: any) { + this._web3 = web3 + this._trezorServerCode = trezorServerCode + this._trezorServer = 'to_load' + this._settings = settings + + this._mainCurrencyCode = mainCurrencyCode + this._mainChainId = mainChainId + } + + + async sign(tx: BlocksoftBlockchainTypes.EthTx, privateData: BlocksoftBlockchainTypes.TransferPrivateData, txRBF: any, logData: any): Promise<{ transactionHash: string, transactionJson: any }> { + // @ts-ignore + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTxSendProvider._innerSign started', logData) + // noinspection JSUnresolvedVariable + if (privateData.privateKey.substr(0, 2) !== '0x') { + privateData.privateKey = '0x' + privateData.privateKey + } + if (tx.value.toString().substr(0, 1) === '-') { + throw new Error('SERVER_RESPONSE_NOTHING_LEFT_FOR_FEE') + } + // noinspection JSUnresolvedVariable + if (this._mainChainId) { + tx.chainId = this._mainChainId + } + let signData = false + try { + signData = await this._web3.eth.accounts.signTransaction(tx, privateData.privateKey) + } catch (e) { + throw new Error(this._settings.currencyCode + ' EthTxSendProvider._innerSign signTransaction error ' + e.message) + } + return signData.rawTransaction + } + + async send(tx: BlocksoftBlockchainTypes.EthTx, privateData: BlocksoftBlockchainTypes.TransferPrivateData, txRBF: any, logData: any): Promise<{ transactionHash: string, transactionJson: any }> { + // @ts-ignore + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTxSendProvider._innerSendTx started', logData) + + const rawTransaction = await this.sign(tx, privateData, txRBF, logData) + + // @ts-ignore + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTxSendProvider._innerSendTx signed', tx) + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTxSendProvider._innerSendTx hex', rawTransaction) + + let link = BlocksoftExternalSettings.getStatic(this._trezorServerCode + '_SEND_LINK') + if (!link || link === '') { + if (this._trezorServerCode) { + if (this._trezorServerCode === 'TRX') { + link = this._trezorServerCode + } else if (this._trezorServerCode.indexOf('http') === -1) { + this._trezorServer = await BlocksoftExternalSettings.getTrezorServer(this._trezorServerCode, 'ETH.Send.sendTx') + link = this._trezorServer + '/api/v2/sendtx/' + } + } else { + link = this._trezorServerCode // actually is direct url like link = 'https://dex.binance.org/api/v1/broadcast' + } + } + + const { apiEndpoints } = config.proxy + const baseURL = MarketingEvent.DATA.LOG_TESTER ? apiEndpoints.baseURLTest : apiEndpoints.baseURL + const proxy = baseURL + '/send/checktx' + const errorProxy = baseURL + '/send/errortx' + const successProxy = baseURL + '/send/sendtx' + let checkResult = false + try { + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTxSendProvider.send proxy checkResult start ' + proxy, logData) + checkResult = await BlocksoftAxios.post(proxy, { + raw: rawTransaction, + txRBF, + logData, + marketingData: MarketingEvent.DATA + }) + } catch (e) { + if (config.debug.cryptoErrors) { + console.log(this._settings.currencyCode + ' EthTxSendProvider.send proxy error checkResult ' + e.message) + } + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTxSendProvider.send proxy error checkResult ' + e.message) + } + + if (checkResult !== false) { + if (typeof checkResult.data !== 'undefined') { + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTxSendProvider.send proxy checkResult1 ', checkResult.data) + if (typeof checkResult.data.status === 'undefined' || checkResult.data.status === 'error') { + if (config.debug.cryptoErrors) { + console.log(this._settings.currencyCode + ' EthTxSendProvider.send proxy error checkResult1 ', JSON.parse(JSON.stringify(checkResult.data))) + } + checkResult = false + } else if (checkResult.data.status === 'notice') { + throw new Error(checkResult.data.msg) + } + } else { + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTxSendProvider.send proxy checkResult2 ', checkResult) + if (config.debug.cryptoErrors) { + console.log(this._settings.currencyCode + ' EthTxSendProvider.send proxy error checkResult2 ', JSON.parse(JSON.stringify(checkResult.data))) + } + } + } else { + if (config.debug.cryptoErrors) { + console.log(this._settings.currencyCode + ' EthTxSendProvider.send proxy error checkResultEmpty ', JSON.stringify(checkResult.data)) + } + } + logData.checkResult = checkResult && typeof checkResult.data !== 'undefined' && checkResult.data ? JSON.parse(JSON.stringify(checkResult.data)) : false + + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTxSendProvider.send will send') + let result + let sendLink + try { + if (this._mainCurrencyCode === 'MATIC' || this._mainCurrencyCode === 'FTM' || !link) { + /** + * curl http://matic.trusteeglobal.com:8545 -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","method":"eth_sendRawTransaction","params":["0x..."],"id":83}' + */ + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTxSendProvider.send sendSignedTransaction to ' + this._web3.LINK, rawTransaction) + sendLink = this._web3.LINK + const tmp = await BlocksoftAxios.postWithoutBraking(sendLink, { + jsonrpc: '2.0', + method: 'eth_sendRawTransaction', + params: [rawTransaction], + id: 1 + }) + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTxSendProvider.send sendSignedTransaction to ' + this._web3.LINK + ' result ', tmp) + if (!tmp || typeof tmp.data === 'undefined') { + throw new Error('SERVER_RESPONSE_NOT_CONNECTED') + } + if (typeof tmp.data.error !== 'undefined' && tmp.data.error) { + throw new Error(typeof tmp.data.error.message !== 'undefined' ? tmp.data.error.message : tmp.data.error) + } + result = { + data: { + result: typeof tmp.data.result !== 'undefined' ? tmp.data.result : false + } + } + } else if (this._mainCurrencyCode === 'BNB') { + /** + * {"blockHash": "0x01d48fd5de1ebb62275096f749acb6849bd97f3c050acb07358222cea0a527bc", + * "blockNumber": 5223318, "contractAddress": null, + * "cumulativeGasUsed": 14465279, "from": "0xf1cff704c6e6ce459e3e1544a9533cccbdad7b99", + * "gasUsed": 21000, "logs": [], + * "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + * "status": true, "to": "0xf1cff704c6e6ce459e3e1544a9533cccbdad7b99", "transactionHash": "0x1fa5646517b625d422863e6c27082104e1697543a6f912421527bb171c6173f2", "transactionIndex": 95} + */ + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTxSendProvider.send sendSignedTransaction ', rawTransaction) + sendLink = this._web3.LINK + const tmp = await this._web3.eth.sendSignedTransaction(rawTransaction) + result = { + data: { + result: typeof tmp.transactionHash !== 'undefined' ? tmp.transactionHash : false + } + } + + } else { + sendLink = link + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTxSendProvider.send post ', rawTransaction) + result = await BlocksoftAxios.post(sendLink, rawTransaction) + } + // @ts-ignore + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTxSendProvider.send result ', typeof result !== 'undefined' && result ? result.data : 'NO RESULT') + } catch (e) { + if (config.debug.cryptoErrors) { + console.log(this._settings.currencyCode + ' ' + this._mainCurrencyCode + ' EthTxSendProvider.send trezor ' + sendLink + ' error ' + e.message, JSON.parse(JSON.stringify(logData))) + } + try { + logData.error = e.message + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTxSendProvider.send proxy errorTx start ' + errorProxy, logData) + const res2 = await BlocksoftAxios.post(errorProxy, { + raw: rawTransaction, + txRBF, + logData, + marketingData: MarketingEvent.DATA + }) + if (config.debug.cryptoErrors) { + console.log(this._settings.currencyCode + ' EthTxSendProvider.send proxy errorTx result', JSON.parse(JSON.stringify(res2.data))) + } + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTxSendProvider.send proxy errorTx', typeof res2.data !== 'undefined' ? res2.data : res2) + throw new Error('res2.data : ' + res2.data) + } catch (e2) { + if (config.debug.cryptoErrors) { + console.log(this._settings.currencyCode + ' EthTxSendProvider.send proxy error errorTx ' + e.message) + } + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTxSendProvider.send proxy error errorTx ' + e2.message) + } + if (this._settings.currencyCode !== 'ETH' && this._settings.currencyCode !== 'ETH_ROPSTEN' && e.message.indexOf('bad-txns-in-belowout') !== -1) { + throw new Error('SERVER_RESPONSE_NOT_ENOUGH_FEE') + } else if (e.message.indexOf('dust') !== -1) { + throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_DUST') + } else if (e.message.indexOf('bad-txns-inputs-spent') !== -1 || e.message.indexOf('txn-mempool-conflict') !== -1) { + throw new Error('SERVER_RESPONSE_NO_RESPONSE') + } else if (e.message.indexOf('min relay fee not met') !== -1 || e.message.indexOf('fee for relay') !== -1 || e.message.indexOf('insufficient priority') !== -1) { + throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_FEE') + } else if (e.message.indexOf('insufficient fee, rejecting replacement') !== -1) { + if (this._settings.currencyCode !== 'ETH' && this._settings.currencyCode !== 'ETH_ROPSTEN') { + throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_FEE_FOR_REPLACEMENT') + } else { + throw new Error('UI_CONFIRM_CHANGE_AMOUNT_FOR_REPLACEMENT') + } + } else if (e.message.indexOf('too-long-mempool-chain') !== -1) { + throw new Error('SERVER_RESPONSE_NO_RESPONSE') + } else { + await BlocksoftExternalSettings.setTrezorServerInvalid(this._trezorServerCode, this._trezorServer) + e.message += ' link: ' + link + throw e + } + } + + // @ts-ignore + if (typeof result.data.result === 'undefined' || !result.data.result) { + throw new Error('SERVER_RESPONSE_NOT_CONNECTED') + } + + // @ts-ignore + const transactionHash = result.data.result + if (transactionHash === '') { + throw new Error('SERVER_RESPONSE_BAD_CODE') + } + + checkResult = false + try { + logData.txHash = transactionHash + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTxSendProvider.send proxy successTx start ' + successProxy, logData) + checkResult = await BlocksoftAxios.post(successProxy, { + raw: rawTransaction, + txRBF, + logData, + marketingData: MarketingEvent.DATA + }) + if (config.debug.cryptoErrors) { + console.log(this._settings.currencyCode + ' EthTxSendProvider.send proxy successTx result ', JSON.parse(JSON.stringify(checkResult.data))) + } + } catch (e3) { + if (config.debug.cryptoErrors) { + console.log(this._settings.currencyCode + ' EthTxSendProvider.send proxy error successTx ' + e3.message) + } + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTxSendProvider.send proxy error successTx ' + e3.message) + } + + if (checkResult !== false) { + if (typeof checkResult.data !== 'undefined') { + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTxSendProvider.send proxy successResult1 ', checkResult.data) + if (typeof checkResult.data.status === 'undefined' || checkResult.data.status === 'error') { + if (config.debug.cryptoErrors) { + console.log(this._settings.currencyCode + ' EthTxSendProvider.send proxy error successResult1 ', checkResult) + } + checkResult = false + } else if (checkResult.data.status === 'notice') { + throw new Error(checkResult.data.msg) + } + } else { + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTxSendProvider.send proxy successResult2 ', checkResult) + if (config.debug.cryptoErrors) { + console.log(this._settings.currencyCode + ' EthTxSendProvider.send proxy error successResult2 ', checkResult) + } + } + } else { + if (config.debug.cryptoErrors) { + console.log(this._settings.currencyCode + ' EthTxSendProvider.send proxy error successResultEmpty ', checkResult) + } + } + logData.successResult = checkResult && typeof checkResult.data !== 'undefined' && checkResult.data ? JSON.parse(JSON.stringify(checkResult.data)) : false + logData.txRBF = txRBF + + const nonce = typeof logData.setNonce !== 'undefined' ? logData.setNonce : BlocksoftUtils.hexToDecimal(tx.nonce) + + const transactionJson = { + nonce, + gasPrice: typeof logData.gasPrice !== 'undefined' ? logData.gasPrice : BlocksoftUtils.hexToDecimal(tx.gasPrice) + } + + await EthRawDS.saveRaw({ + address: tx.from, + currencyCode: this._settings.currencyCode, + transactionUnique: tx.from + '_' + nonce, + transactionHash, + transactionRaw: rawTransaction, + transactionLog: logData + }) + + BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTxSendProvider.send save nonce ' + nonce + ' from ' + tx.from + ' ' + transactionHash) + await EthTmpDS.saveNonce(this._mainCurrencyCode, tx.from, 'send_' + transactionHash, nonce) + + return { transactionHash, transactionJson } + } +} diff --git a/crypto/blockchains/eth/ext/EthEstimateGas.js b/crypto/blockchains/eth/ext/EthEstimateGas.js new file mode 100644 index 000000000..b2f08281d --- /dev/null +++ b/crypto/blockchains/eth/ext/EthEstimateGas.js @@ -0,0 +1,24 @@ +const TransactionController = require('./estimateGas/index.js'); + +export default async function(provider, gasPrice, from, to, value) { + if (from === to) return '21000' + + const gasPriceHex = '0x' + (+gasPrice).toString(16) + const valueHex = '0x' + (+value).toString(16) + + const txController = new TransactionController({ + provider: provider, + getGasPrice: gasPriceHex + }) + + const res = await txController.addTxGasDefaults({ + txParams: { + from: from, + to: to, + value: valueHex + }, + history: [{}] + }) + + return parseInt(res.txParams.gas, 16) +} diff --git a/crypto/blockchains/eth/ext/erc1155.js b/crypto/blockchains/eth/ext/erc1155.js new file mode 100644 index 000000000..211250743 --- /dev/null +++ b/crypto/blockchains/eth/ext/erc1155.js @@ -0,0 +1,106 @@ +const ERC1155 = [{ 'inputs': [{ 'internalType': 'string', 'name': '_name', 'type': 'string' }, { 'internalType': 'string', 'name': '_symbol', 'type': 'string' }, { 'internalType': 'address', 'name': '_proxyRegistryAddress', 'type': 'address' }, { 'internalType': 'string', 'name': '_templateURI', 'type': 'string' }, { 'internalType': 'address', 'name': '_migrationAddress', 'type': 'address' }], 'stateMutability': 'nonpayable', 'type': 'constructor' }, { + 'anonymous': false, + 'inputs': [{ 'indexed': true, 'internalType': 'address', 'name': 'account', 'type': 'address' }, { 'indexed': true, 'internalType': 'address', 'name': 'operator', 'type': 'address' }, { 'indexed': false, 'internalType': 'bool', 'name': 'approved', 'type': 'bool' }], + 'name': 'ApprovalForAll', + 'type': 'event' +}, { 'anonymous': false, 'inputs': [{ 'indexed': true, 'internalType': 'uint256', 'name': '_id', 'type': 'uint256' }, { 'indexed': true, 'internalType': 'address', 'name': '_creator', 'type': 'address' }], 'name': 'CreatorChanged', 'type': 'event' }, { 'anonymous': false, 'inputs': [{ 'indexed': false, 'internalType': 'address', 'name': 'userAddress', 'type': 'address' }, { 'indexed': false, 'internalType': 'address payable', 'name': 'relayerAddress', 'type': 'address' }, { 'indexed': false, 'internalType': 'bytes', 'name': 'functionSignature', 'type': 'bytes' }], 'name': 'MetaTransactionExecuted', 'type': 'event' }, { + 'anonymous': false, + 'inputs': [{ 'indexed': true, 'internalType': 'address', 'name': 'previousOwner', 'type': 'address' }, { 'indexed': true, 'internalType': 'address', 'name': 'newOwner', 'type': 'address' }], + 'name': 'OwnershipTransferred', + 'type': 'event' +}, { 'anonymous': false, 'inputs': [{ 'indexed': false, 'internalType': 'address', 'name': 'account', 'type': 'address' }], 'name': 'Paused', 'type': 'event' }, { 'anonymous': false, 'inputs': [{ 'indexed': false, 'internalType': 'string', 'name': '_value', 'type': 'string' }, { 'indexed': true, 'internalType': 'uint256', 'name': '_id', 'type': 'uint256' }], 'name': 'PermanentURI', 'type': 'event' }, { + 'anonymous': false, + 'inputs': [{ 'indexed': true, 'internalType': 'address', 'name': 'operator', 'type': 'address' }, { 'indexed': true, 'internalType': 'address', 'name': 'from', 'type': 'address' }, { 'indexed': true, 'internalType': 'address', 'name': 'to', 'type': 'address' }, { 'indexed': false, 'internalType': 'uint256[]', 'name': 'ids', 'type': 'uint256[]' }, { 'indexed': false, 'internalType': 'uint256[]', 'name': 'values', 'type': 'uint256[]' }], + 'name': 'TransferBatch', + 'type': 'event' +}, { 'anonymous': false, 'inputs': [{ 'indexed': true, 'internalType': 'address', 'name': 'operator', 'type': 'address' }, { 'indexed': true, 'internalType': 'address', 'name': 'from', 'type': 'address' }, { 'indexed': true, 'internalType': 'address', 'name': 'to', 'type': 'address' }, { 'indexed': false, 'internalType': 'uint256', 'name': 'id', 'type': 'uint256' }, { 'indexed': false, 'internalType': 'uint256', 'name': 'value', 'type': 'uint256' }], 'name': 'TransferSingle', 'type': 'event' }, { + 'anonymous': false, + 'inputs': [{ 'indexed': false, 'internalType': 'string', 'name': 'value', 'type': 'string' }, { 'indexed': true, 'internalType': 'uint256', 'name': 'id', 'type': 'uint256' }], + 'name': 'URI', + 'type': 'event' +}, { 'anonymous': false, 'inputs': [{ 'indexed': false, 'internalType': 'address', 'name': 'account', 'type': 'address' }], 'name': 'Unpaused', 'type': 'event' }, { 'inputs': [], 'name': 'ERC712_VERSION', 'outputs': [{ 'internalType': 'string', 'name': '', 'type': 'string' }], 'stateMutability': 'view', 'type': 'function' }, { 'inputs': [{ 'internalType': 'address', 'name': '_address', 'type': 'address' }], 'name': 'addSharedProxyAddress', 'outputs': [], 'stateMutability': 'nonpayable', 'type': 'function' }, { + 'inputs': [{ 'internalType': 'address', 'name': '_owner', 'type': 'address' }, { + 'internalType': 'uint256', + 'name': '_id', + 'type': 'uint256' + }], 'name': 'balanceOf', 'outputs': [{ 'internalType': 'uint256', 'name': '', 'type': 'uint256' }], 'stateMutability': 'view', 'type': 'function' +}, { 'inputs': [{ 'internalType': 'address[]', 'name': 'accounts', 'type': 'address[]' }, { 'internalType': 'uint256[]', 'name': 'ids', 'type': 'uint256[]' }], 'name': 'balanceOfBatch', 'outputs': [{ 'internalType': 'uint256[]', 'name': '', 'type': 'uint256[]' }], 'stateMutability': 'view', 'type': 'function' }, { + 'inputs': [{ 'internalType': 'address', 'name': '_from', 'type': 'address' }, { 'internalType': 'uint256[]', 'name': '_ids', 'type': 'uint256[]' }, { 'internalType': 'uint256[]', 'name': '_quantities', 'type': 'uint256[]' }], + 'name': 'batchBurn', + 'outputs': [], + 'stateMutability': 'nonpayable', + 'type': 'function' +}, { 'inputs': [{ 'internalType': 'address', 'name': '_to', 'type': 'address' }, { 'internalType': 'uint256[]', 'name': '_ids', 'type': 'uint256[]' }, { 'internalType': 'uint256[]', 'name': '_quantities', 'type': 'uint256[]' }, { 'internalType': 'bytes', 'name': '_data', 'type': 'bytes' }], 'name': 'batchMint', 'outputs': [], 'stateMutability': 'nonpayable', 'type': 'function' }, { + 'inputs': [{ 'internalType': 'address', 'name': '_from', 'type': 'address' }, { 'internalType': 'uint256', 'name': '_id', 'type': 'uint256' }, { 'internalType': 'uint256', 'name': '_quantity', 'type': 'uint256' }], + 'name': 'burn', + 'outputs': [], + 'stateMutability': 'nonpayable', + 'type': 'function' +}, { 'inputs': [{ 'internalType': 'uint256', 'name': '_id', 'type': 'uint256' }], 'name': 'creator', 'outputs': [{ 'internalType': 'address', 'name': '', 'type': 'address' }], 'stateMutability': 'view', 'type': 'function' }, { 'inputs': [], 'name': 'disableMigrate', 'outputs': [], 'stateMutability': 'nonpayable', 'type': 'function' }, { + 'inputs': [{ 'internalType': 'address', 'name': 'userAddress', 'type': 'address' }, { 'internalType': 'bytes', 'name': 'functionSignature', 'type': 'bytes' }, { 'internalType': 'bytes32', 'name': 'sigR', 'type': 'bytes32' }, { + 'internalType': 'bytes32', + 'name': 'sigS', + 'type': 'bytes32' + }, { 'internalType': 'uint8', 'name': 'sigV', 'type': 'uint8' }], 'name': 'executeMetaTransaction', 'outputs': [{ 'internalType': 'bytes', 'name': '', 'type': 'bytes' }], 'stateMutability': 'payable', 'type': 'function' +}, { 'inputs': [{ 'internalType': 'uint256', 'name': '_id', 'type': 'uint256' }], 'name': 'exists', 'outputs': [{ 'internalType': 'bool', 'name': '', 'type': 'bool' }], 'stateMutability': 'view', 'type': 'function' }, { 'inputs': [], 'name': 'getChainId', 'outputs': [{ 'internalType': 'uint256', 'name': '', 'type': 'uint256' }], 'stateMutability': 'view', 'type': 'function' }, { 'inputs': [], 'name': 'getDomainSeperator', 'outputs': [{ 'internalType': 'bytes32', 'name': '', 'type': 'bytes32' }], 'stateMutability': 'view', 'type': 'function' }, { + 'inputs': [{ 'internalType': 'address', 'name': 'user', 'type': 'address' }], + 'name': 'getNonce', + 'outputs': [{ 'internalType': 'uint256', 'name': 'nonce', 'type': 'uint256' }], + 'stateMutability': 'view', + 'type': 'function' +}, { 'inputs': [{ 'internalType': 'address', 'name': '_owner', 'type': 'address' }, { 'internalType': 'address', 'name': '_operator', 'type': 'address' }], 'name': 'isApprovedForAll', 'outputs': [{ 'internalType': 'bool', 'name': 'isOperator', 'type': 'bool' }], 'stateMutability': 'view', 'type': 'function' }, { 'inputs': [{ 'internalType': 'uint256', 'name': '_id', 'type': 'uint256' }], 'name': 'isPermanentURI', 'outputs': [{ 'internalType': 'bool', 'name': '', 'type': 'bool' }], 'stateMutability': 'view', 'type': 'function' }, { + 'inputs': [{ 'internalType': 'uint256', 'name': '_id', 'type': 'uint256' }], + 'name': 'maxSupply', + 'outputs': [{ 'internalType': 'uint256', 'name': '', 'type': 'uint256' }], + 'stateMutability': 'pure', + 'type': 'function' +}, { 'inputs': [{ 'components': [{ 'internalType': 'uint256', 'name': 'id', 'type': 'uint256' }, { 'internalType': 'address', 'name': 'owner', 'type': 'address' }], 'internalType': 'struct AssetContractShared.Ownership[]', 'name': '_ownerships', 'type': 'tuple[]' }], 'name': 'migrate', 'outputs': [], 'stateMutability': 'nonpayable', 'type': 'function' }, { 'inputs': [], 'name': 'migrationTarget', 'outputs': [{ 'internalType': 'contract AssetContractShared', 'name': '', 'type': 'address' }], 'stateMutability': 'view', 'type': 'function' }, { + 'inputs': [{ + 'internalType': 'address', + 'name': '_to', + 'type': 'address' + }, { 'internalType': 'uint256', 'name': '_id', 'type': 'uint256' }, { 'internalType': 'uint256', 'name': '_quantity', 'type': 'uint256' }, { 'internalType': 'bytes', 'name': '_data', 'type': 'bytes' }], 'name': 'mint', 'outputs': [], 'stateMutability': 'nonpayable', 'type': 'function' +}, { 'inputs': [], 'name': 'name', 'outputs': [{ 'internalType': 'string', 'name': '', 'type': 'string' }], 'stateMutability': 'view', 'type': 'function' }, { 'inputs': [], 'name': 'openSeaVersion', 'outputs': [{ 'internalType': 'string', 'name': '', 'type': 'string' }], 'stateMutability': 'pure', 'type': 'function' }, { 'inputs': [], 'name': 'owner', 'outputs': [{ 'internalType': 'address', 'name': '', 'type': 'address' }], 'stateMutability': 'view', 'type': 'function' }, { 'inputs': [], 'name': 'pause', 'outputs': [], 'stateMutability': 'nonpayable', 'type': 'function' }, { + 'inputs': [], + 'name': 'paused', + 'outputs': [{ 'internalType': 'bool', 'name': '', 'type': 'bool' }], + 'stateMutability': 'view', + 'type': 'function' +}, { 'inputs': [], 'name': 'proxyRegistryAddress', 'outputs': [{ 'internalType': 'address', 'name': '', 'type': 'address' }], 'stateMutability': 'view', 'type': 'function' }, { 'inputs': [{ 'internalType': 'address', 'name': '_address', 'type': 'address' }], 'name': 'removeSharedProxyAddress', 'outputs': [], 'stateMutability': 'nonpayable', 'type': 'function' }, { 'inputs': [], 'name': 'renounceOwnership', 'outputs': [], 'stateMutability': 'nonpayable', 'type': 'function' }, { + 'inputs': [{ 'internalType': 'address', 'name': '_from', 'type': 'address' }, { + 'internalType': 'address', + 'name': '_to', + 'type': 'address' + }, { 'internalType': 'uint256[]', 'name': '_ids', 'type': 'uint256[]' }, { 'internalType': 'uint256[]', 'name': '_amounts', 'type': 'uint256[]' }, { 'internalType': 'bytes', 'name': '_data', 'type': 'bytes' }], 'name': 'safeBatchTransferFrom', 'outputs': [], 'stateMutability': 'nonpayable', 'type': 'function' +}, { 'inputs': [{ 'internalType': 'address', 'name': '_from', 'type': 'address' }, { 'internalType': 'address', 'name': '_to', 'type': 'address' }, { 'internalType': 'uint256', 'name': '_id', 'type': 'uint256' }, { 'internalType': 'uint256', 'name': '_amount', 'type': 'uint256' }, { 'internalType': 'bytes', 'name': '_data', 'type': 'bytes' }], 'name': 'safeTransferFrom', 'outputs': [], 'stateMutability': 'nonpayable', 'type': 'function' }, { + 'inputs': [{ 'internalType': 'address', 'name': 'operator', 'type': 'address' }, { 'internalType': 'bool', 'name': 'approved', 'type': 'bool' }], + 'name': 'setApprovalForAll', + 'outputs': [], + 'stateMutability': 'nonpayable', + 'type': 'function' +}, { 'inputs': [{ 'internalType': 'uint256', 'name': '_id', 'type': 'uint256' }, { 'internalType': 'address', 'name': '_to', 'type': 'address' }], 'name': 'setCreator', 'outputs': [], 'stateMutability': 'nonpayable', 'type': 'function' }, { 'inputs': [{ 'internalType': 'uint256', 'name': '_id', 'type': 'uint256' }, { 'internalType': 'string', 'name': '_uri', 'type': 'string' }], 'name': 'setPermanentURI', 'outputs': [], 'stateMutability': 'nonpayable', 'type': 'function' }, { + 'inputs': [{ 'internalType': 'address', 'name': '_address', 'type': 'address' }], + 'name': 'setProxyRegistryAddress', + 'outputs': [], + 'stateMutability': 'nonpayable', + 'type': 'function' +}, { 'inputs': [{ 'internalType': 'string', 'name': '_uri', 'type': 'string' }], 'name': 'setTemplateURI', 'outputs': [], 'stateMutability': 'nonpayable', 'type': 'function' }, { 'inputs': [{ 'internalType': 'uint256', 'name': '_id', 'type': 'uint256' }, { 'internalType': 'string', 'name': '_uri', 'type': 'string' }], 'name': 'setURI', 'outputs': [], 'stateMutability': 'nonpayable', 'type': 'function' }, { 'inputs': [{ 'internalType': 'address', 'name': '', 'type': 'address' }], 'name': 'sharedProxyAddresses', 'outputs': [{ 'internalType': 'bool', 'name': '', 'type': 'bool' }], 'stateMutability': 'view', 'type': 'function' }, { + 'inputs': [], + 'name': 'supportsFactoryInterface', + 'outputs': [{ 'internalType': 'bool', 'name': '', 'type': 'bool' }], + 'stateMutability': 'pure', + 'type': 'function' +}, { 'inputs': [{ 'internalType': 'bytes4', 'name': 'interfaceId', 'type': 'bytes4' }], 'name': 'supportsInterface', 'outputs': [{ 'internalType': 'bool', 'name': '', 'type': 'bool' }], 'stateMutability': 'view', 'type': 'function' }, { 'inputs': [], 'name': 'symbol', 'outputs': [{ 'internalType': 'string', 'name': '', 'type': 'string' }], 'stateMutability': 'view', 'type': 'function' }, { 'inputs': [], 'name': 'templateURI', 'outputs': [{ 'internalType': 'string', 'name': '', 'type': 'string' }], 'stateMutability': 'view', 'type': 'function' }, { + 'inputs': [{ 'internalType': 'uint256', 'name': '_id', 'type': 'uint256' }], + 'name': 'totalSupply', + 'outputs': [{ 'internalType': 'uint256', 'name': '', 'type': 'uint256' }], + 'stateMutability': 'view', + 'type': 'function' +}, { 'inputs': [{ 'internalType': 'address', 'name': 'newOwner', 'type': 'address' }], 'name': 'transferOwnership', 'outputs': [], 'stateMutability': 'nonpayable', 'type': 'function' }, { 'inputs': [], 'name': 'unpause', 'outputs': [], 'stateMutability': 'nonpayable', 'type': 'function' }, { 'inputs': [{ 'internalType': 'uint256', 'name': '_id', 'type': 'uint256' }], 'name': 'uri', 'outputs': [{ 'internalType': 'string', 'name': '', 'type': 'string' }], 'stateMutability': 'view', 'type': 'function' }] + +/** *********************Exports begin******************************************/ + +module.exports = { + ERC1155 +} +/** ********************Exports end*********************************************/ diff --git a/crypto/blockchains/eth/ext/erc20.js b/crypto/blockchains/eth/ext/erc20.js new file mode 100644 index 000000000..4eb0ed99d --- /dev/null +++ b/crypto/blockchains/eth/ext/erc20.js @@ -0,0 +1,237 @@ +const ERC20 = [ + { + "constant": true, + "inputs": [], + "name": "name", + "outputs": [ + { + "name": "", + "type": "string" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "decimals", + "outputs": [ + { + "name": "", + "type": "uint8" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "symbol", + "outputs": [ + { + "name": "", + "type": "string" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + 'constant': true, + 'inputs': [], + 'name': 'totalSupply', + 'outputs': [ + { + 'name': '', + 'type': 'uint256' + } + ], + 'payable': false, + 'stateMutability': 'view', + 'type': 'function' + }, + { + 'constant': true, + 'inputs': [], + 'name': 'decimals', + 'outputs': [ + { + 'name': '', + 'type': 'uint8' + } + ], + 'payable': false, + 'stateMutability': 'view', + 'type': 'function' + }, + { + 'anonymous': false, + 'inputs': [ + { + 'indexed': true, + 'name': 'from', + 'type': 'address' + }, + { + 'indexed': true, + 'name': 'to', + 'type': 'address' + }, + { + 'indexed': false, + 'name': 'value', + 'type': 'uint256' + } + ], + 'name': 'Transfer', + 'type': 'event' + }, + { + 'anonymous': false, + 'inputs': [ + { + 'indexed': true, + 'name': 'owner', + 'type': 'address' + }, + { + 'indexed': true, + 'name': 'spender', + 'type': 'address' + }, + { + 'indexed': false, + 'name': 'value', + 'type': 'uint256' + } + ], + 'name': 'Approval', + 'type': 'event' + }, + { + 'constant': true, + 'inputs': [ + { + 'name': 'who', + 'type': 'address' + } + ], + 'name': 'balanceOf', + 'outputs': [ + { + 'name': '', + 'type': 'uint256' + } + ], + 'payable': false, + 'stateMutability': 'view', + 'type': 'function' + }, + { + 'constant': true, + 'inputs': [ + { + 'name': 'owner', + 'type': 'address' + }, + { + 'name': 'spender', + 'type': 'address' + } + ], + 'name': 'allowance', + 'outputs': [ + { + 'name': '', + 'type': 'uint256' + } + ], + 'payable': false, + 'stateMutability': 'view', + 'type': 'function' + }, + { + 'constant': false, + 'inputs': [ + { + 'name': 'to', + 'type': 'address' + }, + { + 'name': 'value', + 'type': 'uint256' + } + ], + 'name': 'transfer', + 'outputs': [ + { + 'name': 'ok', + 'type': 'bool' + } + ], + 'payable': false, + 'stateMutability': 'nonpayable', + 'type': 'function' + }, + { + 'constant': false, + 'inputs': [ + { + 'name': 'from', + 'type': 'address' + }, + { + 'name': 'to', + 'type': 'address' + }, + { + 'name': 'value', + 'type': 'uint256' + } + ], + 'name': 'transferFrom', + 'outputs': [ + { + 'name': 'ok', + 'type': 'bool' + } + ], + 'payable': false, + 'stateMutability': 'nonpayable', + 'type': 'function' + }, + { + 'constant': false, + 'inputs': [ + { + 'name': 'spender', + 'type': 'address' + }, + { + 'name': 'value', + 'type': 'uint256' + } + ], + 'name': 'approve', + 'outputs': [ + { + 'name': 'ok', + 'type': 'bool' + } + ], + 'payable': false, + 'stateMutability': 'nonpayable', + 'type': 'function' + } +] + +/** *********************Exports begin******************************************/ +module.exports = { + ERC20 +} +/** ********************Exports end*********************************************/ diff --git a/crypto/blockchains/eth/ext/erc721.js b/crypto/blockchains/eth/ext/erc721.js new file mode 100644 index 000000000..57349a691 --- /dev/null +++ b/crypto/blockchains/eth/ext/erc721.js @@ -0,0 +1,61 @@ +const ERC721 = [ + { 'inputs': [], 'stateMutability': 'nonpayable', 'type': 'constructor' }, { 'anonymous': false, 'inputs': [{ 'indexed': true, 'internalType': 'address', 'name': 'owner', 'type': 'address' }, { 'indexed': true, 'internalType': 'address', 'name': 'approved', 'type': 'address' }, { 'indexed': true, 'internalType': 'uint256', 'name': 'tokenId', 'type': 'uint256' }], 'name': 'Approval', 'type': 'event' }, { + 'anonymous': false, + 'inputs': [{ 'indexed': true, 'internalType': 'address', 'name': 'owner', 'type': 'address' }, { 'indexed': true, 'internalType': 'address', 'name': 'operator', 'type': 'address' }, { 'indexed': false, 'internalType': 'bool', 'name': 'approved', 'type': 'bool' }], + 'name': 'ApprovalForAll', + 'type': 'event' + }, { 'anonymous': false, 'inputs': [{ 'indexed': true, 'internalType': 'address', 'name': 'previousOwner', 'type': 'address' }, { 'indexed': true, 'internalType': 'address', 'name': 'newOwner', 'type': 'address' }], 'name': 'OwnershipTransferred', 'type': 'event' }, { 'anonymous': false, 'inputs': [{ 'indexed': true, 'internalType': 'address', 'name': 'from', 'type': 'address' }, { 'indexed': true, 'internalType': 'address', 'name': 'to', 'type': 'address' }, { 'indexed': true, 'internalType': 'uint256', 'name': 'tokenId', 'type': 'uint256' }], 'name': 'Transfer', 'type': 'event' }, { + 'inputs': [], + 'name': 'BASE_TOKEN_URI', + 'outputs': [{ 'internalType': 'string', 'name': '', 'type': 'string' }], + 'stateMutability': 'view', + 'type': 'function' + }, { 'inputs': [], 'name': 'BASE_TOKEN_URI_AFTERPARTY', 'outputs': [{ 'internalType': 'string', 'name': '', 'type': 'string' }], 'stateMutability': 'view', 'type': 'function' }, { 'inputs': [], 'name': 'BASE_TOKEN_URI_VIP', 'outputs': [{ 'internalType': 'string', 'name': '', 'type': 'string' }], 'stateMutability': 'view', 'type': 'function' }, { 'inputs': [], 'name': 'MAX_AFTERPARTY', 'outputs': [{ 'internalType': 'uint256', 'name': '', 'type': 'uint256' }], 'stateMutability': 'view', 'type': 'function' }, { + 'inputs': [], + 'name': 'MAX_NUMBER', + 'outputs': [{ 'internalType': 'uint256', 'name': '', 'type': 'uint256' }], + 'stateMutability': 'view', + 'type': 'function' + }, { 'inputs': [], 'name': 'MAX_VIP', 'outputs': [{ 'internalType': 'uint256', 'name': '', 'type': 'uint256' }], 'stateMutability': 'view', 'type': 'function' }, { 'inputs': [{ 'internalType': 'address', 'name': 'to', 'type': 'address' }, { 'internalType': 'uint256', 'name': 'tokenId', 'type': 'uint256' }], 'name': 'approve', 'outputs': [], 'stateMutability': 'nonpayable', 'type': 'function' }, { 'inputs': [{ 'internalType': 'address', 'name': 'owner', 'type': 'address' }], 'name': 'balanceOf', 'outputs': [{ 'internalType': 'uint256', 'name': '', 'type': 'uint256' }], 'stateMutability': 'view', 'type': 'function' }, { + 'inputs': [], + 'name': 'contractOwner', + 'outputs': [{ 'internalType': 'address', 'name': '', 'type': 'address' }], + 'stateMutability': 'view', + 'type': 'function' + }, { 'inputs': [], 'name': 'currentTokenAmount', 'outputs': [{ 'internalType': 'uint256', 'name': '', 'type': 'uint256' }], 'stateMutability': 'view', 'type': 'function' }, { 'inputs': [{ 'internalType': 'uint256', 'name': 'tokenId', 'type': 'uint256' }], 'name': 'getApproved', 'outputs': [{ 'internalType': 'address', 'name': '', 'type': 'address' }], 'stateMutability': 'view', 'type': 'function' }, { + 'inputs': [{ 'internalType': 'address', 'name': 'owner', 'type': 'address' }, { 'internalType': 'address', 'name': 'operator', 'type': 'address' }], + 'name': 'isApprovedForAll', + 'outputs': [{ 'internalType': 'bool', 'name': '', 'type': 'bool' }], + 'stateMutability': 'view', + 'type': 'function' + }, { 'inputs': [{ 'internalType': 'address', 'name': 'to', 'type': 'address' }, { 'internalType': 'uint256', 'name': 'tokenId', 'type': 'uint256' }, { 'internalType': 'uint256', 'name': 'ticketType', 'type': 'uint256' }], 'name': 'mint', 'outputs': [], 'stateMutability': 'nonpayable', 'type': 'function' }, { 'inputs': [], 'name': 'name', 'outputs': [{ 'internalType': 'string', 'name': '', 'type': 'string' }], 'stateMutability': 'view', 'type': 'function' }, { + 'inputs': [], + 'name': 'owner', + 'outputs': [{ 'internalType': 'address', 'name': '', 'type': 'address' }], + 'stateMutability': 'view', + 'type': 'function' + }, { 'inputs': [{ 'internalType': 'uint256', 'name': 'tokenId', 'type': 'uint256' }], 'name': 'ownerOf', 'outputs': [{ 'internalType': 'address', 'name': '', 'type': 'address' }], 'stateMutability': 'view', 'type': 'function' }, { 'inputs': [], 'name': 'renounceOwnership', 'outputs': [], 'stateMutability': 'nonpayable', 'type': 'function' }, { + 'inputs': [{ 'internalType': 'address', 'name': 'from', 'type': 'address' }, { 'internalType': 'address', 'name': 'to', 'type': 'address' }, { 'internalType': 'uint256', 'name': 'tokenId', 'type': 'uint256' }], + 'name': 'safeTransferFrom', + 'outputs': [], + 'stateMutability': 'nonpayable', + 'type': 'function' + }, { 'inputs': [{ 'internalType': 'address', 'name': 'from', 'type': 'address' }, { 'internalType': 'address', 'name': 'to', 'type': 'address' }, { 'internalType': 'uint256', 'name': 'tokenId', 'type': 'uint256' }, { 'internalType': 'bytes', 'name': '_data', 'type': 'bytes' }], 'name': 'safeTransferFrom', 'outputs': [], 'stateMutability': 'nonpayable', 'type': 'function' }, { + 'inputs': [{ 'internalType': 'address', 'name': 'operator', 'type': 'address' }, { 'internalType': 'bool', 'name': 'approved', 'type': 'bool' }], + 'name': 'setApprovalForAll', + 'outputs': [], + 'stateMutability': 'nonpayable', + 'type': 'function' + }, { 'inputs': [{ 'internalType': 'bytes4', 'name': 'interfaceId', 'type': 'bytes4' }], 'name': 'supportsInterface', 'outputs': [{ 'internalType': 'bool', 'name': '', 'type': 'bool' }], 'stateMutability': 'view', 'type': 'function' }, { 'inputs': [], 'name': 'symbol', 'outputs': [{ 'internalType': 'string', 'name': '', 'type': 'string' }], 'stateMutability': 'view', 'type': 'function' }, { + 'inputs': [{ 'internalType': 'uint256', 'name': 'tokenId', 'type': 'uint256' }], + 'name': 'tokenURI', + 'outputs': [{ 'internalType': 'string', 'name': '', 'type': 'string' }], + 'stateMutability': 'view', + 'type': 'function' + }, { 'inputs': [{ 'internalType': 'address', 'name': 'from', 'type': 'address' }, { 'internalType': 'address', 'name': 'to', 'type': 'address' }, { 'internalType': 'uint256', 'name': 'tokenId', 'type': 'uint256' }], 'name': 'transferFrom', 'outputs': [], 'stateMutability': 'nonpayable', 'type': 'function' }, { 'inputs': [{ 'internalType': 'address', 'name': 'newOwner', 'type': 'address' }], 'name': 'transferOwnership', 'outputs': [], 'stateMutability': 'nonpayable', 'type': 'function' }] +/** *********************Exports begin******************************************/ + +module.exports = { + ERC721 +} +/** ********************Exports end*********************************************/ diff --git a/crypto/blockchains/eth/ext/estimateGas/enums.js b/crypto/blockchains/eth/ext/estimateGas/enums.js new file mode 100644 index 000000000..c0e115fe8 --- /dev/null +++ b/crypto/blockchains/eth/ext/estimateGas/enums.js @@ -0,0 +1,20 @@ +const ENVIRONMENT_TYPE_POPUP = 'popup' +const ENVIRONMENT_TYPE_NOTIFICATION = 'notification' +const ENVIRONMENT_TYPE_FULLSCREEN = 'fullscreen' + +const PLATFORM_BRAVE = 'Brave' +const PLATFORM_CHROME = 'Chrome' +const PLATFORM_EDGE = 'Edge' +const PLATFORM_FIREFOX = 'Firefox' +const PLATFORM_OPERA = 'Opera' + +module.exports = { + ENVIRONMENT_TYPE_POPUP, + ENVIRONMENT_TYPE_NOTIFICATION, + ENVIRONMENT_TYPE_FULLSCREEN, + PLATFORM_BRAVE, + PLATFORM_CHROME, + PLATFORM_EDGE, + PLATFORM_FIREFOX, + PLATFORM_OPERA +} diff --git a/crypto/blockchains/eth/ext/estimateGas/index.js b/crypto/blockchains/eth/ext/estimateGas/index.js new file mode 100644 index 000000000..a174e8599 --- /dev/null +++ b/crypto/blockchains/eth/ext/estimateGas/index.js @@ -0,0 +1,40 @@ +const EventEmitter = require('safe-event-emitter') +const ethUtil = require('ethereumjs-util') +const EthQuery = require('ethjs-query') +const TxGasUtil = require('./tx-gas-utils') +const HttpProvider = require('ethjs-provider-http') + + +class TransactionController extends EventEmitter { + constructor(opts) { + super(opts) + this.provider = new HttpProvider(opts.provider) + this.getGasPrice = opts.getGasPrice + + this.query = new EthQuery(this.provider) + this.txGasUtil = new TxGasUtil(this.provider) + } + + /** + adds the tx gas defaults: gas && gasPrice + @param txMeta {Object} - the txMeta object + @returns {Promise} resolves with txMeta + */ + async addTxGasDefaults(txMeta) { + const txParams = txMeta.txParams + // ensure value + txParams.value = txParams.value ? ethUtil.addHexPrefix(txParams.value) : '0x0' + txMeta.gasPriceSpecified = Boolean(txParams.gasPrice) + // let gasPrice = txParams.gasPrice + const gasPrice = this.getGasPrice + /* if (!gasPrice) { + gasPrice = this.getGasPrice ? this.getGasPrice() : await this.query.gasPrice() + } */ + // noinspection JSCheckFunctionSignatures + txParams.gasPrice = ethUtil.addHexPrefix(gasPrice.toString(16)) + // set gasLimit + return this.txGasUtil.analyzeGasUsage(txMeta) + } +} + +module.exports = TransactionController diff --git a/crypto/blockchains/eth/ext/estimateGas/tx-gas-utils.js b/crypto/blockchains/eth/ext/estimateGas/tx-gas-utils.js new file mode 100644 index 000000000..886cdd3bc --- /dev/null +++ b/crypto/blockchains/eth/ext/estimateGas/tx-gas-utils.js @@ -0,0 +1,147 @@ +const EthQuery = require('ethjs-query') +const { + hexToBn, + BnMultiplyByFraction, + bnToHex +} = require('./util') +const { addHexPrefix } = require('ethereumjs-util') +const SIMPLE_GAS_COST = '0x5208' // Hex for 21000, cost of a simple send. + +const TRANSACTION_NO_CONTRACT_ERROR_KEY = 'transactionErrorNoContract' + +/** + tx-gas-utils are gas utility methods for Transaction manager + its passed ethquery + and used to do things like calculate gas of a tx. + @param {Object} provider - A network provider. + */ + +class TxGasUtil { + + constructor(provider) { + this.query = new EthQuery(provider) + } + + /** + @param txMeta {Object} - the txMeta object + @returns {object} the txMeta object with the gas written to the txParams + */ + async analyzeGasUsage(txMeta) { + // noinspection JSUnresolvedFunction + const block = await this.query.getBlockByNumber('latest', false) + let estimatedGasHex + try { + estimatedGasHex = await this.estimateTxGas(txMeta, block.gasLimit) + } catch (err) { + txMeta.simulationFails = { + reason: err.message, + errorKey: err.errorKey, + debug: { blockNumber: block.number, blockGasLimit: block.gasLimit } + } + + if (err.errorKey === TRANSACTION_NO_CONTRACT_ERROR_KEY) { + txMeta.simulationFails.debug.getCodeResponse = err.getCodeResponse + } + + return txMeta + } + this.setTxGas(txMeta, block.gasLimit, estimatedGasHex) + return txMeta + } + + /** + Estimates the tx's gas usage + @param txMeta {Object} - the txMeta object + @param blockGasLimitHex {string} - hex string of the block's gas limit + @returns {string} the estimated gas limit as a hex string + */ + async estimateTxGas(txMeta, blockGasLimitHex) { + const txParams = txMeta.txParams + + // check if gasLimit is already specified + txMeta.gasLimitSpecified = Boolean(txParams.gas) + + // if it is, use that value + if (txMeta.gasLimitSpecified) { + return txParams.gas + } + + const recipient = txParams.to + const hasRecipient = Boolean(recipient) + + // see if we can set the gas based on the recipient + if (hasRecipient) { + const code = await this.query.getCode(recipient) + // For an address with no code, geth will return '0x', and ganache-core v2.2.1 will return '0x0' + const codeIsEmpty = !code || code === '0x' || code === '0x0' + + if (codeIsEmpty) { + // if there's data in the params, but there's no contract code, it's not a valid transaction + if (txParams.data) { + throw new Error('TxGasUtil - Trying to call a function on a non-contract address') + } + + // This is a standard ether simple send, gas requirement is exactly 21k + txParams.gas = SIMPLE_GAS_COST + // prevents buffer addition + txMeta.simpleSend = true + return SIMPLE_GAS_COST + } + } + + // fallback to block gasLimit + const blockGasLimitBN = hexToBn(blockGasLimitHex) + const saferGasLimitBN = BnMultiplyByFraction(blockGasLimitBN, 19, 20) + txParams.gas = bnToHex(saferGasLimitBN) + + // estimate tx gas requirements + return this.query.estimateGas(txParams) + } + + /** + Writes the gas on the txParams in the txMeta + @param txMeta {Object} - the txMeta object to write to + @param blockGasLimitHex {string} - the block gas limit hex + @param estimatedGasHex {string} - the estimated gas hex + */ + setTxGas(txMeta, blockGasLimitHex, estimatedGasHex) { + txMeta.estimatedGas = addHexPrefix(estimatedGasHex) + const txParams = txMeta.txParams + + // if gasLimit was specified and doesnt OOG, + // use original specified amount + if (txMeta.gasLimitSpecified || txMeta.simpleSend) { + txMeta.estimatedGas = txParams.gas + return + } + // if gasLimit not originally specified, + // try adding an additional gas buffer to our estimation for safety + txParams.gas = this.addGasBuffer(txMeta.estimatedGas, blockGasLimitHex) + return txParams + } + + /** + Adds a gas buffer with out exceeding the block gas limit + + @param initialGasLimitHex {string} - the initial gas limit to add the buffer too + @param blockGasLimitHex {string} - the block gas limit + @returns {string} the buffered gas limit as a hex string + */ + addGasBuffer(initialGasLimitHex, blockGasLimitHex) { + const initialGasLimitBn = hexToBn(initialGasLimitHex) + const blockGasLimitBn = hexToBn(blockGasLimitHex) + // noinspection JSUnresolvedFunction + const upperGasLimitBn = blockGasLimitBn.muln(0.9) + // noinspection JSUnresolvedFunction + const bufferedGasLimitBn = initialGasLimitBn.muln(1.5) + + // if initialGasLimit is above blockGasLimit, dont modify it + if (initialGasLimitBn.gt(upperGasLimitBn)) return bnToHex(initialGasLimitBn) + // if bufferedGasLimit is below blockGasLimit, use bufferedGasLimit + if (bufferedGasLimitBn.lt(upperGasLimitBn)) return bnToHex(bufferedGasLimitBn) + // otherwise use blockGasLimit + return bnToHex(upperGasLimitBn) + } +} + +module.exports = TxGasUtil diff --git a/crypto/blockchains/eth/ext/estimateGas/util.js b/crypto/blockchains/eth/ext/estimateGas/util.js new file mode 100644 index 000000000..81f3fbdaf --- /dev/null +++ b/crypto/blockchains/eth/ext/estimateGas/util.js @@ -0,0 +1,159 @@ +const ethUtil = require('ethereumjs-util') +const assert = require('assert') +const BN = require('bn.js') +const { + ENVIRONMENT_TYPE_POPUP, + ENVIRONMENT_TYPE_NOTIFICATION, + ENVIRONMENT_TYPE_FULLSCREEN, + PLATFORM_FIREFOX, + PLATFORM_OPERA, + PLATFORM_CHROME, + PLATFORM_EDGE, + PLATFORM_BRAVE +} = require('./enums') + +/** + * Generates an example stack trace + * + * @returns {string} A stack trace + * + */ +function getStack() { + return new Error('Stack trace generator - not an error').stack +} + +/** + * Used to determine the window type through which the app is being viewed. + * - 'popup' refers to the extension opened through the browser app icon (in top right corner in chrome and firefox) + * - 'responsive' refers to the main browser window + * - 'notification' refers to the popup that appears in its own window when taking action outside of metamask + * + * @returns {string} A single word label that represents the type of window through which the app is being viewed + * + */ +const getEnvironmentType = (url = window.location.href) => { + if (url.match(/popup.html(?:#.*)*$/)) { + return ENVIRONMENT_TYPE_POPUP + } else if (url.match(/home.html(?:\?.+)*$/) || url.match(/home.html(?:#.*)*$/)) { + return ENVIRONMENT_TYPE_FULLSCREEN + } else { + return ENVIRONMENT_TYPE_NOTIFICATION + } +} + +// noinspection JSUnusedLocalSymbols +/** + * Returns the platform (browser) where the extension is running. + * + * @returns {string} the platform ENUM + * + */ +const getPlatform = _ => { + const ua = navigator.userAgent + if (ua.search('Firefox') !== -1) { + return PLATFORM_FIREFOX + } else { + // noinspection JSUnresolvedVariable + if (window && window.chrome && window.chrome.ipcRenderer) { + return PLATFORM_BRAVE + } else if (ua.search('Edge') !== -1) { + return PLATFORM_EDGE + } else if (ua.search('OPR') !== -1) { + return PLATFORM_OPERA + } else { + return PLATFORM_CHROME + } + } +} + +/** + * Checks whether a given balance of ETH, represented as a hex string, is sufficient to pay a value plus a gas fee + * + * @param {object} txParams Contains data about a transaction + * @param {string} txParams.gas The gas for a transaction + * @param {string} txParams.gasPrice The price per gas for the transaction + * @param {string} txParams.value The value of ETH to send + * @param {string} hexBalance A balance of ETH represented as a hex string + * @returns {boolean} Whether the balance is greater than or equal to the value plus the value of gas times gasPrice + * + */ +function sufficientBalance(txParams, hexBalance) { + // validate hexBalance is a hex string + assert.strictEqual(typeof hexBalance, 'string', 'sufficientBalance - hexBalance is not a hex string') + assert.strictEqual(hexBalance.slice(0, 2), '0x', 'sufficientBalance - hexBalance is not a hex string') + + const balance = hexToBn(hexBalance) + const value = hexToBn(txParams.value) + const gasLimit = hexToBn(txParams.gas) + const gasPrice = hexToBn(txParams.gasPrice) + + // noinspection JSUnresolvedFunction + const maxCost = value.add(gasLimit.mul(gasPrice)) + // noinspection JSUnresolvedFunction + return balance.gte(maxCost) +} + +/** + * Converts a BN object to a hex string with a '0x' prefix + * + * @param {BN} inputBn The BN to convert to a hex string + * @returns {string} A '0x' prefixed hex string + * + */ +function bnToHex(inputBn) { + // noinspection JSCheckFunctionSignatures + return ethUtil.addHexPrefix(inputBn.toString(16)) +} + +/** + * Converts a hex string to a BN object + * + * @param {string} inputHex A number represented as a hex string + * @returns {Object} A BN object + * + */ +function hexToBn(inputHex) { + // noinspection JSUnresolvedFunction + return new BN(ethUtil.stripHexPrefix(inputHex), 16) +} + +/** + * Used to multiply a BN by a fraction + * + * @param {BN} targetBN The number to multiply by a fraction + * @param {number|string} numerator The numerator of the fraction multiplier + * @param {number|string} denominator The denominator of the fraction multiplier + * @returns {BN} The product of the multiplication + * + */ +function BnMultiplyByFraction(targetBN, numerator, denominator) { + const numBN = new BN(numerator) + const denomBN = new BN(denominator) + // noinspection JSUnresolvedFunction + return targetBN.mul(numBN).div(denomBN) +} + +function applyListeners(listeners, emitter) { + Object.keys(listeners).forEach((key) => { + emitter.on(key, listeners[key]) + }) +} + +function removeListeners(listeners, emitter) { + Object.keys(listeners).forEach((key) => { + emitter.removeListener(key, listeners[key]) + }) +} + +// noinspection JSUnusedGlobalSymbols +module.exports = { + removeListeners, + applyListeners, + getPlatform, + getStack, + getEnvironmentType, + sufficientBalance, + hexToBn, + bnToHex, + BnMultiplyByFraction +} diff --git a/crypto/blockchains/eth/forks/EthScannerProcessorSoul.js b/crypto/blockchains/eth/forks/EthScannerProcessorSoul.js new file mode 100644 index 000000000..5decb8ab9 --- /dev/null +++ b/crypto/blockchains/eth/forks/EthScannerProcessorSoul.js @@ -0,0 +1,55 @@ +/** + * @version 0.5 + */ +import BlocksoftUtils from '../../../common/BlocksoftUtils' +import BlocksoftBN from '../../../common/BlocksoftBN' +import EthScannerProcessorErc20 from '../EthScannerProcessorErc20' +import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog' + +export default class EthScannerProcessorSoul extends EthScannerProcessorErc20 { + + /** + * some fix for soul double tx + * @param {string} address + * @param {*} result + * @param {boolean} isInternal + * @param {boolean} isTrezor + * @returns {Promise<[]>} + * @private + */ + async _unifyTransactions(address, result, isInternal, isTrezor = true) { + const transactions = [] + const alreadyTransactions = {} + let count = 0 + let tx + for (tx of result) { + + let transaction + try { + if (isTrezor) { + transaction = await this._unifyTransactionTrezor(address, tx, isInternal) + } else { + transaction = await this._unifyTransaction(address, tx, isInternal) + } + } catch (e) { + BlocksoftCryptoLog.error('EthScannerProcessorSoul._unifyTransaction error ' + e.message + ' on ' + (isTrezor ? 'Trezor' : 'usual') + ' tx ' + JSON.stringify(tx)) + } + if (!transaction) { + continue + } + transaction.addressAmount = new BlocksoftBN(BlocksoftUtils.fromENumber(transaction.addressAmount)) + if (typeof (alreadyTransactions[transaction.transactionHash]) !== 'undefined') { + const already = alreadyTransactions[transaction.transactionHash] + transactions[already].addressAmount.add(transaction.addressAmount) + } else { + alreadyTransactions[transaction.transactionHash] = count + count++ + transactions.push(transaction) + } + } + for (let i = 0, ic = transactions.length; i < ic; i++) { + transactions[i].addressAmount = transactions[i].addressAmount.get() + } + return transactions + } +} diff --git a/crypto/blockchains/eth/stores/EthRawDS.js b/crypto/blockchains/eth/stores/EthRawDS.js new file mode 100644 index 000000000..e0ccc7fb1 --- /dev/null +++ b/crypto/blockchains/eth/stores/EthRawDS.js @@ -0,0 +1,283 @@ +/** + * @author Ksu + * @version 0.32 + */ +import Database from '@app/appstores/DataSource/Database'; +import EthTxSendProvider from '../basic/EthTxSendProvider' +import BlocksoftExternalSettings from '../../../common/BlocksoftExternalSettings' +import BlocksoftAxios from '../../../common/BlocksoftAxios' +import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog' +import MarketingEvent from '../../../../app/services/Marketing/MarketingEvent' +import config from '../../../../app/config/config' + +const tableName = 'transactions_raw' + +class EthRawDS { + /** + * @type {string} + * @private + */ + _currencyCode = 'ETH' + + _trezorServer = 'none' + + async getForAddress(data) { + try { + if (typeof data.currencyCode !== 'undefined') { + this._currencyCode = data.currencyCode === 'ETH_ROPSTEN' ? 'ETH_ROPSTEN' : 'ETH' + } + const sql = ` + SELECT id, + transaction_unique_key AS transactionUnique, + transaction_hash AS transactionHash, + transaction_raw AS transactionRaw, + transaction_log AS transactionLog, + broadcast_log AS broadcastLog, + broadcast_updated AS broadcastUpdated, + created_at AS transactionCreated, + is_removed, removed_at + FROM transactions_raw + WHERE + (is_removed=0 OR is_removed IS NULL) + AND currency_code='${this._currencyCode}' + AND address='${data.address.toLowerCase()}'` + const result = await Database.query(sql) + if (!result || !result.array || result.array.length === 0) { + return {} + } + + const ret = {} + + if (this._trezorServer === 'none') { + if (this._currencyCode === 'ETH') { + try { + this._trezorServer = await BlocksoftExternalSettings.getTrezorServer('ETH_TREZOR_SERVER', 'ETH.Broadcast') + this._infuraProjectId = BlocksoftExternalSettings.getStatic('ETH_INFURA_PROJECT_ID', 'ETH.Broadcast') + } catch (e) { + throw new Error(e.message + ' inside trezorServer') + } + } else { + this._trezorServer = await BlocksoftExternalSettings.getTrezorServer('ETH_ROPSTEN_TREZOR_SERVER', 'ETH.Broadcast') + } + } + + const now = new Date().toISOString() + + for (const row of result.array) { + try { + ret[row.transactionUnique] = row + let transactionLog + try { + transactionLog = row.transactionLog ? JSON.parse(Database.unEscapeString(row.transactionLog)) : row.transactionLog + } catch (e) { + // do nothing + } + + if (transactionLog && typeof transactionLog.currencyCode !== 'undefined' && (typeof transactionLog.successResult === 'undefined' || !transactionLog.successResult)) { + const { apiEndpoints } = config.proxy + const baseURL = MarketingEvent.DATA.LOG_TESTER ? apiEndpoints.baseURLTest : apiEndpoints.baseURL + const successProxy = baseURL + '/send/sendtx' + let checkResult = false + try { + transactionLog.selectedFee.isRebroadcast = true + checkResult = await BlocksoftAxios.post(successProxy, { + raw: row.transactionRaw, + txRBF: typeof transactionLog.txRBF !== 'undefined' ? transactionLog.txRBF : false, + logData: transactionLog, + marketingData: MarketingEvent.DATA + }) + await BlocksoftCryptoLog.log(this._currencyCode + ' EthRawDS.send proxy success result', JSON.parse(JSON.stringify(checkResult))) + } catch (e3) { + if (config.debug.cryptoErrors) { + console.log(this._currencyCode + ' EthRawDS.send proxy success error ' + e3.message) + } + await BlocksoftCryptoLog.log(this._currencyCode + ' EthRawDS.send proxy success error ' + e3.message) + } + if (checkResult && typeof checkResult.data !== 'undefined') { + transactionLog.successResult = checkResult.data + } + + await Database.setTableName('transactions_raw').setUpdateData({ + updateObj: { transactionLog: Database.escapeString(JSON.stringify(transactionLog)) }, + key: { id: row.id } + }).update() + } + + let broadcastLog = '' + let link = '' + let broad + const updateObj = { + broadcastUpdated: now, + is_removed: '0' + } + if (this._currencyCode === 'ETH' || this._currencyCode === 'ETH_ROPSTEN') { + link = this._trezorServer + '/api/v2/sendtx/' + try { + broad = await BlocksoftAxios.post(link, row.transactionRaw) + broadcastLog = ' broadcasted ok ' + JSON.stringify(broad.data) + updateObj.is_removed = '1' + updateObj.removed_at = now + } catch (e) { + if (e.message.indexOf('transaction underpriced') !== -1 || e.message.indexOf('already known') !== -1) { + updateObj.is_removed = '1' + broadcastLog += ' already known' + } else { + updateObj.is_removed = '0' + broadcastLog += e.message + } + } + broadcastLog += ' ' + link + '; ' + MarketingEvent.logOnlyRealTime('v20_eth_resend_0 ' + row.transactionHash, { broadcastLog, ...updateObj }) + } + + if (this._currencyCode === 'ETH') { + link = 'https://api.etherscan.io/api?module=proxy&action=eth_sendRawTransaction&apikey=YourApiKeyToken&hex=' + let broadcastLog1 = '' + try { + broad = await BlocksoftAxios.get(link + row.transactionRaw) + if (typeof broad.data.error !== 'undefined') { + throw new Error(JSON.stringify(broad.data.error)) + } + broadcastLog1 = ' broadcasted ok ' + JSON.stringify(broad.data) + updateObj.is_removed += '1' + updateObj.removed_at = now + } catch (e) { + if (e.message.indexOf('transaction underpriced') !== -1 || e.message.indexOf('already known') !== -1) { + updateObj.is_removed += '1' + broadcastLog1 += ' already known' + } else { + updateObj.is_removed += '0' + broadcastLog1 += e.message + } + } + broadcastLog1 += ' ' + link + '; ' + MarketingEvent.logOnlyRealTime('v20_eth_resend_1 ' + row.transactionHash, { broadcastLog1, ...updateObj }) + + + link = 'https://mainnet.infura.io/v3/' + this._infuraProjectId + let broadcastLog2 = '' + try { + broad = await BlocksoftAxios.post(link, + { + 'jsonrpc': '2.0', + 'method': 'eth_sendRawTransaction', + 'params': [row.transactionRaw], + 'id': 1 + } + ) + if (typeof broad.data.error !== 'undefined') { + throw new Error(JSON.stringify(broad.data.error)) + } + broadcastLog2 = ' broadcasted ok ' + JSON.stringify(broad.data) + updateObj.is_removed += '1' + updateObj.removed_at = now + } catch (e) { + if (e.message.indexOf('transaction underpriced') !== -1 || e.message.indexOf('already known') !== -1) { + updateObj.is_removed += '1' + broadcastLog2 += ' already known' + } else { + updateObj.is_removed += '0' + broadcastLog2 += e.message + } + + } + broadcastLog2 += ' ' + link + '; ' + MarketingEvent.logOnlyRealTime('v20_eth_resend_2 ' + row.transactionHash, { broadcastLog2, ...updateObj }) + + if (updateObj.is_removed === '111') { // do ALL! + updateObj.is_removed = 1 + } else { + updateObj.is_removed = 0 + } + + broadcastLog = new Date().toISOString() + ' ' + broadcastLog + ' ' + broadcastLog1 + ' ' + broadcastLog2 + ' ' + (row.broadcastLog ? row.broadcastLog.substr(0, 1000) : '') + } else if (this._currencyCode === 'ETH_ROPSTEN') { + if (updateObj.is_removed === '1') { // do ALL! + updateObj.is_removed = 1 + } else { + updateObj.is_removed = 0 + } + + broadcastLog = new Date().toISOString() + ' ' + broadcastLog + ' ' + (row.broadcastLog ? row.broadcastLog.substr(0, 1000) : '') + } + + if (this._currencyCode === 'ETH' || this._currencyCode === 'ETH_ROPSTEN') { + updateObj.broadcastLog = broadcastLog + + await Database.setTableName('transactions_raw').setUpdateData({ + updateObj, + key: { id: row.id } + }).update() + } + + } catch (e) { + throw new Error(e.message + ' inside row ' + row.transactionHash) + } + } + return ret + } catch (e) { + throw new Error(e.message + ' on EthRawDS.getAddress') + } + } + + async cleanRawHash(data) { + BlocksoftCryptoLog.log('EthRawDS cleanRawHash ', data) + + const now = new Date().toISOString() + const sql = `UPDATE transactions_raw + SET is_removed=1, removed_at = '${now}' + WHERE + (is_removed=0 OR is_removed IS NULL) + AND (currency_code='ETH' OR currency_code='ETH_ROPSTEN') + AND transaction_hash='${data.transactionHash}'` + await Database.query(sql) + } + + async cleanRaw(data) { + BlocksoftCryptoLog.log('EthRawDS cleanRaw ', data) + + if (typeof data.currencyCode !== 'undefined') { + this._currencyCode = data.currencyCode === 'ETH_ROPSTEN' ? 'ETH_ROPSTEN' : 'ETH' + } + + const now = new Date().toISOString() + const sql = `UPDATE transactions_raw + SET is_removed=1, removed_at = '${now}' + WHERE + (is_removed=0 OR is_removed IS NULL) + AND currency_code='${this._currencyCode}' + AND address='${data.address.toLowerCase()}' + AND transaction_unique_key='${data.transactionUnique}'` + await Database.query(sql) + } + + async saveRaw(data) { + if (typeof data.currencyCode !== 'undefined') { + this._currencyCode = data.currencyCode === 'ETH_ROPSTEN' ? 'ETH_ROPSTEN' : 'ETH' + } + const now = new Date().toISOString() + + const sql = `UPDATE transactions_raw + SET is_removed=1, removed_at = '${now}' + WHERE + (is_removed=0 OR is_removed IS NULL) + AND currency_code='${this._currencyCode}' + AND address='${data.address.toLowerCase()}' + AND transaction_unique_key='${data.transactionUnique.toLowerCase()}'` + await Database.query(sql) + + const prepared = [{ + currency_code: this._currencyCode, + address: data.address.toLowerCase(), + transaction_unique_key: data.transactionUnique.toLowerCase(), + transaction_hash: data.transactionHash, + transaction_raw: data.transactionRaw, + transaction_log: Database.escapeString(JSON.stringify(data.transactionLog)), + created_at: now, + is_removed: 0 + }] + await Database.setTableName(tableName).setInsertData({ insertObjs: prepared }).insert() + } +} + +export default new EthRawDS() diff --git a/crypto/blockchains/eth/stores/EthTmpDS.js b/crypto/blockchains/eth/stores/EthTmpDS.js new file mode 100644 index 000000000..17509817b --- /dev/null +++ b/crypto/blockchains/eth/stores/EthTmpDS.js @@ -0,0 +1,194 @@ + +import Database from '@app/appstores/DataSource/Database'; +import BlocksoftBN from '../../../common/BlocksoftBN' + +const tableName = 'transactions_scanners_tmp' + +const CACHE_TMP = {} + +class EthTmpDS { + async setSuccess(txHash) { + await Database.query(`UPDATE transactions SET transaction_status = 'success' WHERE transaction_hash='${txHash}'`) + } + + async getCache(mainCurrencyCode, scanAddress, toRemove = false) { + const address = scanAddress.toLowerCase() + const res = await Database.query(` + SELECT id, tmp_key, tmp_sub_key, tmp_val, created_at + FROM ${tableName} + WHERE currency_code='${mainCurrencyCode}' + AND address='${address}' + AND tmp_key='nonces' + `) + CACHE_TMP[address] = {} + let maxValue = -1 + let maxScanned = -1 + let maxSuccess = -1 + const forBalances = {} + + if (res.array) { + for (const row of res.array) { + const val = row.tmp_val * 1 + if (row.tmp_sub_key === 'maxScanned') { + if (val > maxScanned) { + maxScanned = val + } + } else if (row.tmp_sub_key === 'maxSuccess') { + if (val > maxSuccess) { + maxSuccess = val + } + } else { + if (val > maxValue) { + maxValue = val + } + } + CACHE_TMP[address][row.tmp_sub_key] = val + } + for (const row of res.array) { + const val = row.tmp_val * 1 + if (row.tmp_sub_key === 'maxScanned' || row.tmp_sub_key === 'maxSuccess' || !row.tmp_sub_key || typeof row.tmp_sub_key === 'undefined') { + // do nothing + } else { + const tmp = row.tmp_sub_key.split('_') + if (typeof tmp[1] !== 'undefined') { + const txHash = tmp[1] + if (toRemove && typeof toRemove[txHash] !== 'undefined') { + await Database.query(`DELETE FROM ${tableName} WHERE id=${row.id}`) + } else { + if (val > maxSuccess) { + forBalances[txHash] = val + } + } + } + } + } + } + + const amountBN = {} + let queryLength = 0 + let queryTxs = [] + for (const txHash in forBalances) { + const tmps = await Database.query(`SELECT currency_code AS currencyCode, + address_amount as addressAmount, + transaction_status as transactionStatus + FROM transactions + WHERE transaction_hash='${txHash}' + AND (currency_code LIKE '%ETH%' OR currency_code LIKE 'CUSTOM_%') + `) + if (tmps && tmps.array && typeof tmps.array[0] !== 'undefined') { + let txCurrencyCode = '' + for (const tmp of tmps.array) { + if (tmp.currencyCode === 'ETH' || tmp.currencyCode === 'ETH_ROPSTEN') { + if (txCurrencyCode === '') { + txCurrencyCode = tmp.currencyCode + } + } else { + txCurrencyCode = tmp.currencyCode + } + } + if (txCurrencyCode !== '') { + for (const tmp of tmps.array) { + if (tmp.currencyCode !== txCurrencyCode) continue + + let recheckRBFStatus = 'none' + if (tmp.transactionStatus === 'new' || tmp.transactionStatus === 'confirming') { + const recheckTmp = await Database.query(`SELECT transaction_status as transactionStatus FROM transactions WHERE transactions_other_hashes LIKE '%${txHash}%'`) + if (recheckTmp && recheckTmp.array && typeof recheckTmp.array[0] !== 'undefined') { + recheckRBFStatus = recheckTmp.array[0].transactionStatus + if (recheckRBFStatus !== 'new') { + await Database.query(`UPDATE transactions SET transaction_status='${recheckRBFStatus}' + WHERE transaction_hash='${txHash}' + AND (currency_code LIKE '%ETH%' OR currency_code LIKE 'CUSTOM_%') + `) + } + tmp.transactionStatus = recheckRBFStatus + } + } + if (tmp.transactionStatus === 'new') { + const amount = tmp.addressAmount + if (typeof amountBN[tmp.currencyCode] === 'undefined') { + amountBN[tmp.currencyCode] = new BlocksoftBN(0) + } + queryLength++ + queryTxs.push({ currencyCode: tmp.currencyCode, txHash, recheckRBFStatus }) + amountBN[tmp.currencyCode].add(amount) + } else if (tmp.transactionStatus === 'missing') { + if (maxSuccess > forBalances[txHash]) { + maxSuccess = forBalances[txHash] - 1 + } + } + } + } + } + } + CACHE_TMP[address]['maxValue'] = maxValue + CACHE_TMP[address]['maxScanned'] = maxScanned + CACHE_TMP[address]['maxSuccess'] = maxSuccess > maxScanned ? maxSuccess : maxScanned + CACHE_TMP[address]['amountBlocked'] = {} + CACHE_TMP[address]['queryLength'] = queryLength + CACHE_TMP[address]['queryTxs'] = queryTxs + for (const key in amountBN) { + CACHE_TMP[address]['amountBlocked'][key] = amountBN[key].toString() + } + return CACHE_TMP[address] + } + + async getMaxNonce(mainCurrencyCode, scanAddress) { + if (mainCurrencyCode !== 'ETH') { + return false + } + const address = scanAddress.toLowerCase() + // if (typeof CACHE_TMP[address] === 'undefined' || typeof CACHE_TMP[address]['maxValue'] === 'undefined') { + await this.getCache(mainCurrencyCode, address) + //} + return { + maxValue: CACHE_TMP[address]['maxValue'], + maxScanned: CACHE_TMP[address]['maxScanned'], + maxSuccess: CACHE_TMP[address]['maxSuccess'], + amountBlocked: CACHE_TMP[address]['amountBlocked'], + queryLength: CACHE_TMP[address]['queryLength'], + queryTxs: CACHE_TMP[address]['queryTxs'] + } + } + + async removeNonce(mainCurrencyCode, scanAddress, key) { + if (mainCurrencyCode !== 'ETH') { + return false + } + const address = scanAddress.toLowerCase() + const where = `WHERE currency_code='${mainCurrencyCode}' AND address='${address}' AND tmp_key='nonces' AND tmp_sub_key='${key}'` + await Database.query(`DELETE FROM ${tableName} ${where}`) + await this.getCache(mainCurrencyCode, address) + } + + async saveNonce(mainCurrencyCode, scanAddress, key, value) { + if (mainCurrencyCode !== 'ETH') { + return false + } + const address = scanAddress.toLowerCase() + const now = new Date().toISOString() + value = value * 1 + + const where = `WHERE currency_code='${mainCurrencyCode}' AND address='${address}' AND tmp_key='nonces' AND tmp_sub_key='${key}'` + const res = await Database.query(`SELECT tmp_val FROM ${tableName} ${where}`) + if (res && res.array && res.array.length > 0) { + if (res.array[0].tmp_val * 1 >= value) { + return true + } + await Database.query(`DELETE FROM ${tableName} ${where}`) + } + + const prepared = [{ + currency_code: mainCurrencyCode, + address: address.toLowerCase(), + tmp_key: 'nonces', + tmp_sub_key: key, + tmp_val: value, + created_at: now + }] + await Database.setTableName(tableName).setInsertData({ insertObjs: prepared }).insert() + await this.getCache(mainCurrencyCode, address) + } +} + +export default new EthTmpDS() diff --git a/crypto/blockchains/fio/FioAddressProcessor.js b/crypto/blockchains/fio/FioAddressProcessor.js new file mode 100644 index 000000000..14e8d038a --- /dev/null +++ b/crypto/blockchains/fio/FioAddressProcessor.js @@ -0,0 +1,40 @@ +/** + * @version 0.11 + * https://developers.fioprotocol.io/fio-protocol/accounts + * + * + * let mnemonic = '' + * let results = await BlocksoftKeys.discoverAddresses({ mnemonic, fullTree: false, fromIndex: 0, toIndex: 1, currencyCode: ['XMR'] }) + * console.log('r', results['XMR'][0]) + */ + +import { Ecc } from '@fioprotocol/fiojs' +import { FIOSDK } from '@fioprotocol/fiosdk' + +export default class FioAddressProcessor { + + _root = false + + async setBasicRoot(root) { + this._root = root + } + + /** + * @param {string|Buffer} privateKey + * @param data + * @param {*} data.publicKey + * @param {*} data.walletHash + * @param {*} data.derivationPath + * @param {*} data.derivationIndex + * @param {*} data.derivationType + * @returns {Promise<{privateKey: string, address: string, addedData: *}>} + */ + async getAddress(privateKey, data = {}) { + const pvt = await Ecc.PrivateKey(privateKey) + return { + address: FIOSDK.derivedPublicKey(privateKey).publicKey, + privateKey: pvt.toWif(), + addedData: false + } + } +} diff --git a/crypto/blockchains/fio/FioScannerProcessor.js b/crypto/blockchains/fio/FioScannerProcessor.js new file mode 100644 index 000000000..acb8fe657 --- /dev/null +++ b/crypto/blockchains/fio/FioScannerProcessor.js @@ -0,0 +1,130 @@ +import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog' +import { getFioBalance, getTransactions } from './FioUtils' + +export default class FioScannerProcessor { + + /** + * @private + */ + _serverUrl = false + + _blocksToConfirm = 10 + + _maxBlockNumber = 500000000 + + constructor(settings) { + this._settings = settings + } + + async _getCache(address, additionalData, walletHash) { + return false + } + + /** + * @param address + * @param additionalData + * @param walletHash + * @returns {Promise} + * @private + */ + async _get(address, additionalData, walletHash) { + return false + } + + /** + * @param {string} address + * @param {*} additionalData + * @param {string} walletHash + * @return {Promise<{balance:*, unconfirmed:*, provider:string}>} + */ + async getBalanceBlockchainCache(address, additionalData, walletHash) { + BlocksoftCryptoLog.log(this._settings.currencyCode + ' FioScannerProcessor.getBalance (cache) started ' + address + ' of ' + walletHash) + return false + } + + /** + * @param {string} address + * @param {*} additionalData + * @param {string} walletHash + * @return {Promise<{balance:*, unconfirmed:*, provider:string}>} + */ + async getBalanceBlockchain(address, additionalData, walletHash) { + BlocksoftCryptoLog.log(this._settings.currencyCode + ' FioScannerProcessor.getBalance started ' + address + ' of ' + walletHash) + const balance = await getFioBalance(address) + return { + balance, + unconfirmed: 0, + } + } + + /** + * @param {string} scanData.account.address + * @param {string} scanData.account.walletHash + * @return {Promise<[UnifiedTransaction]>} + */ + async getTransactionsBlockchain(scanData) { + const address = scanData.account.address.trim() + const walletHash = scanData.account.walletHash + BlocksoftCryptoLog.log(this._settings.currencyCode + ' FioScannerProcessor.getTransactionsBlockchain started ' + address + ' of ' + walletHash) + const response = await getTransactions(address) + const actions = response['actions'] || [] + const lastBlock = response['last_irreversible_block'] + + const transactions = [] + let tx + for (tx of actions) { + const transaction = await this._unifyTransaction(address, lastBlock, tx) + if (transaction) { + transactions.push(transaction) + } + } + return transactions + } + + /** + * + * @param {string} address + * @param {string} lastBlock + * @param {Object} transaction + * @param {BigInteger} transaction.amount BigInteger {_d: Array(2), _s: -1} + * @param {string} transaction.approx_float_amount -0.00002724 + * @param {string} transaction.coinbase false + * @param {string} transaction.fee "27240000" + * @param {string} transaction.hash "ac319a3240f15dab342102fe248d3b95636f8a0bbfa962a5645521fac8fb86d3" + * @param {string} transaction.height 2152183 + * @param {string} transaction.id 10506991 + * @param {string} transaction.mempool: false + * @param {string} transaction.mixin 10 + * @param {string} transaction.payment_id "" + * @param {string} transaction.spent_outputs [{…}] + * @param {string} transaction.timestamp Tue Jul 28 2020 18:10:26 GMT+0300 (Восточная Европа, летнее время) {} + * @param {string} transaction.total_received "12354721582" + * @param {BigInteger} transaction.total_sent BigInteger {_d: Array(2), _s: 1} + * @param {string} transaction.unlock_time + * @return {Promise} + * @private + */ + async _unifyTransaction(address, lastBlock, transaction) { + const txData = transaction.action_trace?.act?.data + if (!txData?.payee_public_key || transaction.action_trace.receiver !== 'fio.token') { + return false + } + + const transactionStatus = lastBlock - transaction['block_num'] > 5 ? 'success' : 'new' + const direction = (address === txData?.payee_public_key) ? 'income' : 'outcome' + + return { + transactionHash: transaction['action_trace']['trx_id'], + blockHash: '', + blockNumber: transaction['block_num'], + blockTime: transaction['block_time'], + blockConfirmations: lastBlock - transaction['block_num'], + transactionDirection: direction, + addressFrom: direction === 'income' ? '-' : txData?.payee_public_key, + addressTo: direction === 'income' ? txData?.payee_public_key : address, + addressAmount: txData?.amount, + transactionStatus: transactionStatus, + transactionFee: txData?.max_fee || 0 + } + } +} diff --git a/crypto/blockchains/fio/FioSdkWrapper.js b/crypto/blockchains/fio/FioSdkWrapper.js new file mode 100644 index 000000000..a35d72456 --- /dev/null +++ b/crypto/blockchains/fio/FioSdkWrapper.js @@ -0,0 +1,57 @@ +/** + * @version 0.77 + */ +import { FIOSDK } from '@fioprotocol/fiosdk' + +import BlocksoftKeysStorage from '@crypto/actions/BlocksoftKeysStorage/BlocksoftKeysStorage' +import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings' +import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog' + +import config from '@app/config/config' + +const fetchJson = async (uri, opts = {}) => { + // eslint-disable-next-line no-undef + return fetch(uri, opts) +} + +export class FioSdkWrapper { + sdk + walletHash = false + async init(walletHash, source) { + if (this.walletHash === walletHash) return false + try { + const res = await BlocksoftKeysStorage.getAddressCache(walletHash + 'SpecialFio') + let publicKey, fioKey + if (res) { + publicKey = res.address + fioKey = res.privateKey + } else { + const mnemonic = await BlocksoftKeysStorage.getWalletMnemonic(walletHash, source + ' setSelectedWallet init for Fio') + let tmp = await FIOSDK.createPrivateKeyMnemonic(mnemonic) + fioKey = tmp.fioKey + tmp = FIOSDK.derivedPublicKey(fioKey) + publicKey = tmp.publicKey + await BlocksoftKeysStorage.setAddressCache(walletHash + 'SpecialFio', {address : publicKey, privateKey : fioKey}) + } + const link = BlocksoftExternalSettings.getStatic('FIO_BASE_URL') + this.sdk = new FIOSDK(fioKey, publicKey, link, fetchJson) + this.walletHash = walletHash + BlocksoftCryptoLog.log(`FioSdkWrapper.inited for ${walletHash}`) + } catch (e) { + if (config.debug.fioErrors) { + console.log('FioSdkWrapper.init error') + } + } + return true + } +} + +export const fioSdkWrapper = new FioSdkWrapper() + +export const getFioSdk = () => { + if (typeof fioSdkWrapper?.sdk !== 'undefined' && fioSdkWrapper?.sdk) { + return fioSdkWrapper?.sdk + } + const link = BlocksoftExternalSettings.getStatic('FIO_BASE_URL') + return new FIOSDK(null, null, link, fetchJson) +} diff --git a/crypto/blockchains/fio/FioTransferProcessor.ts b/crypto/blockchains/fio/FioTransferProcessor.ts new file mode 100644 index 000000000..0a23a11eb --- /dev/null +++ b/crypto/blockchains/fio/FioTransferProcessor.ts @@ -0,0 +1,84 @@ +/** + * @version 0.20 + */ +import { getFioSdk } from './FioSdkWrapper' +import { getFioBalance, transferTokens } from './FioUtils' +import { BlocksoftBlockchainTypes } from '../BlocksoftBlockchainTypes' +import BlocksoftUtils from '../../common/BlocksoftUtils' + +export default class FioTransferProcessor implements BlocksoftBlockchainTypes.TransferProcessor { + private _settings: any + + constructor(settings: any) { + this._settings = settings + } + + needPrivateForFee(): boolean { + return false + } + + checkSendAllModal(data: { currencyCode: any }): boolean { + return false + } + + async getFeeRate(data: BlocksoftBlockchainTypes.TransferData, privateData: BlocksoftBlockchainTypes.TransferPrivateData, additionalData: {} = {}): Promise { + const { fee = 0 } = await getFioSdk().getFee('transfer_tokens_pub_key') + const result: BlocksoftBlockchainTypes.FeeRateResult = { + selectedFeeIndex: 0, + shouldShowFees : false, + fees : [ + { + langMsg: 'xrp_speed_one', + feeForTx: fee, + amountForTx: data.amount + } + ] + } as BlocksoftBlockchainTypes.FeeRateResult + return result + } + + async getTransferAllBalance(data: BlocksoftBlockchainTypes.TransferData, privateData: BlocksoftBlockchainTypes.TransferPrivateData, additionalData: any = {}): Promise { + const { fee = 0 } = await getFioSdk().getFee('transfer_tokens_pub_key') + const balance = await getFioBalance(data.addressFrom) + if (balance === 0) { + return { + selectedTransferAllBalance: '0', + selectedFeeIndex: -1, + fees: [], + countedForBasicBalance: '0' + } + } + + const diff = BlocksoftUtils.diff(balance, fee) + if (diff*1 < 0) { + return { + selectedTransferAllBalance: '0', + selectedFeeIndex: -2, + fees: [], + countedForBasicBalance: '0' + } + } + + const result: BlocksoftBlockchainTypes.TransferAllBalanceResult = { + selectedFeeIndex: 0, + fees : [ + { + langMsg: 'xrp_speed_one', + feeForTx: fee, + amountForTx: diff + } + ], + selectedTransferAllBalance: diff + } as BlocksoftBlockchainTypes.TransferAllBalanceResult + return result + } + + async sendTx(data: BlocksoftBlockchainTypes.TransferData, privateData: BlocksoftBlockchainTypes.TransferPrivateData, uiData: BlocksoftBlockchainTypes.TransferUiData): Promise { + if (typeof uiData !== 'undefined' && typeof uiData.selectedFee !== 'undefined' && typeof uiData.selectedFee.rawOnly !== 'undefined' && uiData.selectedFee.rawOnly) { + // @todo ksu + return { rawOnly: uiData.selectedFee.rawOnly, raw : 'feature in development'} + } + const txId = await transferTokens(data.addressTo, data.amount) + return { transactionHash: txId, transactionJson : {} } + } +} diff --git a/crypto/blockchains/fio/FioUtils.js b/crypto/blockchains/fio/FioUtils.js new file mode 100644 index 000000000..b003d7d7d --- /dev/null +++ b/crypto/blockchains/fio/FioUtils.js @@ -0,0 +1,369 @@ +import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog' +import { getFioSdk } from './FioSdkWrapper' +import config from '../../../app/config/config' +import BlocksoftAxios from '../../common/BlocksoftAxios' +import { Fio } from '@fioprotocol/fiojs' +import { FIOSDK } from '@fioprotocol/fiosdk/src/FIOSDK' +import chunk from 'lodash/chunk' +import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings' + +export const resolveChainCode = (currencyCode, currencySymbol) => { + let chainCode = currencyCode + if (typeof currencyCode !== 'undefined' && currencyCode !== currencySymbol) { + const tmp = currencyCode.split('_') + if (typeof tmp[0] !== 'undefined' && tmp[0]) { + chainCode = tmp[0] + } + } + return chainCode +} + +export const resolveChainToken = (currencyCode, extend) => { + if (extend.currencyCode === 'BNB_SMART') { + return 'SMART' + } + if (typeof extend.tokenBlockchain !== 'undefined') { + if (extend.tokenBlockchain === 'BNB') { + return 'SMART' + } + } + return extend.currencySymbol +} + +export const resolveCryptoCodes = (currencyCode) => { + let chainCode = currencyCode + let currencySymbol = currencyCode + const tmp = currencyCode.split('_') + if (typeof tmp[0] !== 'undefined' && tmp[0] && tmp[1]) { + chainCode = tmp[0] + currencySymbol = tmp[1] + } + return { + chain_code: chainCode, + token_code: currencySymbol + } +} + +export const isFioAddressValid = (address) => { + if (address) { + try { + FIOSDK.isFioAddressValid(address) + return true + } catch (e) { + } + } + return false +} + +export const isFioAddressRegistered = async (address) => { + if (!isFioAddressValid(address)) { + return false + } + + try { + const response = await getFioSdk().isAvailable(address) + return response['is_registered'] === 1 + } catch (e) { + formatError('FIO isFioAddressRegistered ', e) + return false + } +} + +export const getPubAddress = async (fioAddress, chainCode, tokenCode) => { + try { + const response = await getFioSdk().getPublicAddress(fioAddress, chainCode, tokenCode) + return response['public_address'] + } catch (e) { + formatError('FIO getPubAddress', e) + return null + } +} + +export const getAccountFioName = async () => { + try { + const sdk = getFioSdk() + const fioPublicKey = sdk.getFioPublicKey() + const response = await getFioSdk().getFioNames(fioPublicKey) + const addresses = response['fio_addresses'] || [] + return addresses[0]?.fio_address + } catch (e) { + formatError('FIO.getAccountFioName ', e) + return null + } +} + +/** + * Returns FIO Addresses and FIO Domains owned by this public key. + * + * @param fioPublicKey FIO public key of owner. + * @return Promise<[ { fio_address:*, expiration:* } ]> + */ +export const getFioNames = async (fioPublicKey) => { + try { + const response = await getFioSdk().getFioNames(fioPublicKey) + return response['fio_addresses'] || [] + } catch (e) { + formatError('FIO getFioNames', e) + return [] + } +} + +export const getFioBalance = async (fioPublicKey) => { + try { + const response = await getFioSdk().getFioBalance(fioPublicKey) + return response['balance'] || 0 + } catch (e) { + formatError('FIO getFioBalance', e) + return 0 + } +} + +export const getSentFioRequests = async (fioPublicKey, limit = 100, offset = 0) => { + try { + BlocksoftCryptoLog.log(`FIO getSentFioRequests started ${fioPublicKey}`) + const response = await getFioSdk().getSentFioRequests(limit, offset) + const requests = response['requests'] || [] + return requests.sort((a, b) => new Date(b.time_stamp) - new Date(a.time_stamp)) + } catch (e) { + formatError('FIO getSentFioRequests', e) + return [] + } +} + +export const getPendingFioRequests = async (fioPublicKey, limit = 100, offset = 0) => { + try { + BlocksoftCryptoLog.log(`FIO getPendingFioRequests started ${fioPublicKey}`) + const response = await getFioSdk().getPendingFioRequests(limit, offset) + const requests = response['requests'] || [] + return requests.sort((a, b) => new Date(b.time_stamp) - new Date(a.time_stamp)) + } catch (e) { + formatError('FIO getPendingFioRequests', e) + return [] + } +} + +/** + * This call allows a public address of the specific blockchain type to be added to the FIO Address. + * + * @param fioName FIO Address which will be mapped to public address. + * @param chainCode Blockchain code for blockchain hosting this token. + * @param tokenCode Token code to be used with that public address. + * @param publicAddress The public address to be added to the FIO Address for the specified token. + */ +export const addCryptoPublicAddress = async ({ fioName, chainCode, tokenCode, publicAddress }) => { + try { + const { fee = 0 } = await getFioSdk().getFeeForAddPublicAddress(fioName) + const response = await getFioSdk().addPublicAddress( + fioName, + chainCode, + tokenCode, + publicAddress, + fee, + null + ) + const isOK = response['status'] === 'OK' + if (!isOK) { + await BlocksoftCryptoLog.log('FIO addPublicAddress error', response) + } + return isOK + } catch (e) { + formatError('FIO addPubAddress', e) + } +} + +export const addCryptoPublicAddresses = async ({ fioName, publicAddresses }) => { + if (!publicAddresses || Object.keys(publicAddresses).length === 0) return true + + let isOK = true + for await (const publicAddressesChunk of chunk(publicAddresses, 5)) { + try { + const { fee = 0 } = await getFioSdk().getFeeForAddPublicAddress(fioName) + const response = await getFioSdk().addPublicAddresses( + fioName, + publicAddressesChunk, + fee, + null + ) + + if (response['status'] !== 'OK') { + await BlocksoftCryptoLog.log('FIO addPublicAddress error', response) + isOK = false + } + } catch (e) { + formatError('FIO addPubAddress', e) + } + } + return isOK +} + +export const removeCryptoPublicAddresses = async ({ fioName, publicAddresses }) => { + if (!publicAddresses || Object.keys(publicAddresses).length === 0) return true + + let isOK = true + for await (const publicAddressesChunk of chunk(publicAddresses, 5)) { + try { + const { fee = 0 } = await getFioSdk().getFeeForRemovePublicAddresses(fioName) + const response = await getFioSdk().removePublicAddresses( + fioName, + publicAddressesChunk, + fee, + null + ) + + if (response['status'] !== 'OK') { + await BlocksoftCryptoLog.log('FIO removeCryptoPublicAddresses error', response) + isOK = false + } + } catch (e) { + formatError('FIO removeCryptoPublicAddresses', e) + } + } + return isOK +} + +/** + * Create a new funds request on the FIO chain. + * + * @param payerFioAddress FIO Address of the payer. This address will receive the request and will initiate payment. + * @param payeeFioAddress FIO Address of the payee. This address is sending the request and will receive payment. + * @param payeeTokenPublicAddress Payee's public address where they want funds sent. + * @param amount Amount requested. + * @param chainCode Blockchain code for blockchain hosting this token. + * @param tokenCode Code of the token represented in amount requested. + * @param memo + */ +export const requestFunds = async ({ payerFioAddress, payeeFioAddress, payeeTokenPublicAddress, amount, chainCode, tokenCode, memo }) => { + try { + BlocksoftCryptoLog.log(`FIO requestFunds started ${payerFioAddress} -> ${payeeFioAddress} ${amount} ${tokenCode} (${chainCode})`) + const { fee = 0 } = await getFioSdk().getFeeForNewFundsRequest(payeeFioAddress) + const response = await getFioSdk().requestFunds( + payerFioAddress, + payeeFioAddress, + payeeTokenPublicAddress, + amount, + chainCode, + tokenCode, + memo, + fee, + null, + null, + null, + null + ) + + await BlocksoftCryptoLog.log('FIO requestFunds result', response) + return response + } catch (e) { + formatError('FIO requestFunds', e) + const errorMessage = e.json?.fields + ? e.json?.fields[0].error + : e.json?.message + + return { + error: errorMessage || 'FIO request creation error' + } + } +} + +export const getTransactions = async (publicKey) => { + + try { + const link = BlocksoftExternalSettings.getStatic('FIO_HISTORY_URL') + const accountHash = Fio.accountHash(publicKey) + const response = await BlocksoftAxios.post(link + 'get_actions', { + 'account_name': accountHash, + 'pos': -1 + }) + return response?.data + } catch (e) { + formatError('FIO getTransactions', e) + return [] + } +} + +export const transferTokens = async (addressTo, amount) => { + try { + const { fee = 0 } = await getFioSdk().getFee('transfer_tokens_pub_key') + const result = await getFioSdk().transferTokens(addressTo, amount, fee, null) + return result['transaction_id'] + } catch (e) { + formatError('FIO transferTokens', e) + const errorMessage = e.json?.fields + ? e.json?.fields[0].error + : e.json?.message + throw new Error(errorMessage || 'FIO token transfer error') + } +} + +export const rejectFioFundsRequest = async (fioRequestId, payerFioAddress) => { + try { + const sdk = getFioSdk() + const { fee = 0 } = await sdk.getFeeForRejectFundsRequest(payerFioAddress) + const result = await sdk.rejectFundsRequest(`${fioRequestId}`, fee, null) + return result['status'] === 'request_rejected' + } catch (e) { + formatError('FIO rejectFioRequest', e) + } +} + +/** + * + * Records information on the FIO blockchain about a transaction that occurred on other blockchain, i.e. 1 BTC was sent on Bitcoin Blockchain, and both + * sender and receiver have FIO Addresses. OBT stands for Other Blockchain Transaction + * + * @param fioRequestId ID of funds request, if this Record Send transaction is in response to a previously received funds request. Send empty if no FIO Request ID + * @param payerFioAddress FIO Address of the payer. This address initiated payment. + * @param payeeFioAddress FIO Address of the payee. This address is receiving payment. + * @param payerTokenPublicAddress Public address on other blockchain of user sending funds. + * @param payeeTokenPublicAddress Public address on other blockchain of user receiving funds. + * @param amount Amount sent. + * @param chainCode Blockchain code for blockchain hosting this token. + * @param tokenCode Code of the token represented in Amount requested, i.e. BTC. + * @param obtId Other Blockchain Transaction ID (OBT ID), i.e Bitcoin transaction ID. + * @param memo + */ +export const recordFioObtData = async ({ + fioRequestId = '', + payerFioAddress = '', + payeeFioAddress = '', + payerTokenPublicAddress, + payeeTokenPublicAddress, + amount, + chainCode, + tokenCode, + obtId, + memo + }) => { + try { + const sdk = getFioSdk() + const { fee = 0 } = await sdk.getFeeForRecordObtData(payerFioAddress) + const result = await sdk.recordObtData(fioRequestId, payerFioAddress, payeeFioAddress, payerTokenPublicAddress, payeeTokenPublicAddress, amount, chainCode, tokenCode, 'sent_to_blockchain', obtId, fee, null, null, memo, null, null) + return !!result['status'] + } catch (e) { + formatError('FIO recordFioObtData', e) + } +} + +export const getFioObtData = async (tokenCode, offset = 0, limit = 100) => { + let res = false + try { + res = await getFioSdk().getObtData(limit, offset, tokenCode) + } catch (e) { + formatError('FIO.getFioObtData ' + tokenCode + ' ' + limit + ' ' + offset, e) + } + return res +} + +const formatError = (title, e) => { + if (config.debug.fioErrors) { + console.log(title + ' error', e.json, e) + } + if (e.message.indexOf('Error 404') === -1 && e.message.indexOf('Network request failed') === -1) { + BlocksoftCryptoLog.err(title + ' error ' + e.message, e.json || false) + } else { + const msg = title + ' 404 notice ' + e.message + ' ' + JSON.stringify(e.json) + if (msg.indexOf('No FIO Requests') === -1) { + BlocksoftCryptoLog.log(msg) + } + } +} diff --git a/crypto/blockchains/ltc/LtcScannerProcessor.js b/crypto/blockchains/ltc/LtcScannerProcessor.js new file mode 100644 index 000000000..5198d20b0 --- /dev/null +++ b/crypto/blockchains/ltc/LtcScannerProcessor.js @@ -0,0 +1,20 @@ +/** + * @version 0.5 + */ + +import BtcScannerProcessor from '../btc/BtcScannerProcessor' + +export default class LtcScannerProcessor extends BtcScannerProcessor { + + /** + * @type {number} + * @private + */ + _blocksToConfirm = 10 + + /** + * @type {string} + * @private + */ + _trezorServerCode = 'LTC_TREZOR_SERVER' +} diff --git a/crypto/blockchains/ltc/LtcTransferProcessor.ts b/crypto/blockchains/ltc/LtcTransferProcessor.ts new file mode 100644 index 000000000..70c7336bd --- /dev/null +++ b/crypto/blockchains/ltc/LtcTransferProcessor.ts @@ -0,0 +1,40 @@ +/** + * @version 0.20 + */ + +import BtcTransferProcessor from '@crypto/blockchains/btc/BtcTransferProcessor' +import { BlocksoftBlockchainTypes } from '@crypto/blockchains/BlocksoftBlockchainTypes' +import DogeNetworkPrices from '@crypto/blockchains/doge/basic/DogeNetworkPrices' +import BtcUnspentsProvider from '@crypto/blockchains/btc/providers/BtcUnspentsProvider' +import DogeSendProvider from '@crypto/blockchains/doge/providers/DogeSendProvider' +import BtcTxInputsOutputs from '@crypto/blockchains/btc/tx/BtcTxInputsOutputs' +import BtcTxBuilder from '@crypto/blockchains/btc/tx/BtcTxBuilder' + +export default class LtcTransferProcessor extends BtcTransferProcessor implements BlocksoftBlockchainTypes.TransferProcessor { + + _trezorServerCode = 'LTC_TREZOR_SERVER' + + _builderSettings: BlocksoftBlockchainTypes.BuilderSettings = { + minOutputDustReadable: 0.000005, + minChangeDustReadable: 0.00001, + feeMaxForByteSatoshi: 10000, // for tx builder + feeMaxAutoReadable2: 0.2, // for fee calc, + feeMaxAutoReadable6: 0.1, // for fee calc + feeMaxAutoReadable12: 0.05, // for fee calc + changeTogether: true + } + + _initProviders() { + if (this._initedProviders) return false + this.unspentsProvider = new BtcUnspentsProvider(this._settings, this._trezorServerCode) + this.sendProvider = new DogeSendProvider(this._settings, this._trezorServerCode) + this.txPrepareInputsOutputs = new BtcTxInputsOutputs(this._settings, this._builderSettings) + this.txBuilder = new BtcTxBuilder(this._settings, this._builderSettings) + this.networkPrices = new DogeNetworkPrices() + this._initedProviders = true + } + + canRBF(data: BlocksoftBlockchainTypes.DbAccount, transaction: BlocksoftBlockchainTypes.DbTransaction): boolean { + return false + } +} diff --git a/crypto/blockchains/metis/MetisScannerProcessor.js b/crypto/blockchains/metis/MetisScannerProcessor.js new file mode 100644 index 000000000..111fc1455 --- /dev/null +++ b/crypto/blockchains/metis/MetisScannerProcessor.js @@ -0,0 +1,34 @@ +/** + * @version 0.5 + */ +import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog' + + +import EthScannerProcessor from '@crypto/blockchains/eth/EthScannerProcessor' + +export default class MetisScannerProcessor extends EthScannerProcessor { + + /** + * @param {string} scanData.account.address + * @return {Promise<[UnifiedTransaction]>} + */ + async getTransactionsBlockchain(scanData) { + const address = scanData.account.address + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' MetisScannerProcessor.getTransactions started ' + address) + const transactions = await super.getTransactionsBlockchain(scanData) + + // https://andromeda-explorer.metis.io/token/0xDeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD0000/token-transfers + // actual deposits and withdrawals done as erc20 token transfer + const tmp = this._etherscanApiPath.split('/') + const depositLink = `https://${tmp[2]}/api?module=account&action=tokentx&sort=desc&contractaddress=0xDeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD0000&address=${address}` + const depositLogTitle = this._settings.currencyCode + ' EthScannerProcessor.getTransactions etherscan deposits' + const depositTransactions = await this._getFromEtherscan(address, depositLink, depositLogTitle, false, {}) + if (depositTransactions) { + for (const transactionHash in depositTransactions) { + transactions.push(depositTransactions[transactionHash]) + } + } + + return transactions + } +} diff --git a/crypto/blockchains/metis/MetisTransferProcessor.ts b/crypto/blockchains/metis/MetisTransferProcessor.ts new file mode 100644 index 000000000..f2acb3d9f --- /dev/null +++ b/crypto/blockchains/metis/MetisTransferProcessor.ts @@ -0,0 +1,58 @@ +/** + * @author Ksu + * @version 0.43 + */ +import { BlocksoftBlockchainTypes } from '@crypto/blockchains/BlocksoftBlockchainTypes' +import EthTransferProcessor from '@crypto/blockchains/eth/EthTransferProcessor' +import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings' +import BlocksoftAxios from '@crypto/common/BlocksoftAxios' +import BlocksoftUtils from '@crypto/common/BlocksoftUtils' + + +export default class MetisTransferProcessor extends EthTransferProcessor implements BlocksoftBlockchainTypes.TransferProcessor { + + async getFeeRate(data: BlocksoftBlockchainTypes.TransferData, privateData: BlocksoftBlockchainTypes.TransferPrivateData, additionalData: {} = {}): Promise { + if (typeof additionalData.gasPrice === 'undefined' || !additionalData.gasPrice) { + additionalData.gasPrice = BlocksoftExternalSettings.getStatic(this._mainCurrencyCode + '_PRICE') + additionalData.gasPriceTitle = 'speed_blocks_2' + } + + let value = 0 + try { + if (data.amount.indexOf('0x') === 0) { + value = data.amount + } else { + value = '0x' + BlocksoftUtils.decimalToHex(data.amount) + } + } catch (e) { + throw new Error(e.message + ' with data.amount ' + data.amount) + } + const params = { + 'jsonrpc': '2.0', + 'method': 'eth_estimateGas', + 'params': [ + { + 'from': data.addressFrom, + 'to': data.addressTo, + 'value': value, + 'data': '0x' + } + ], + 'id': 1 + } + const tmp = await BlocksoftAxios.post( BlocksoftExternalSettings.getStatic('METIS_SERVER'), params) + + if (typeof tmp !== 'undefined' && typeof tmp.data !== 'undefined') { + if (typeof tmp.data.result !== 'undefined') { + additionalData.gasLimit = BlocksoftUtils.hexToDecimalWalletConnect(tmp.data.result) + } else if (typeof tmp.data.error !== 'undefined') { + throw new Error(tmp.data.error.message) + } + } + return super.getFeeRate(data, privateData, additionalData) + } + + canRBF(data: BlocksoftBlockchainTypes.DbAccount, transaction: BlocksoftBlockchainTypes.DbTransaction, source: string): boolean { + return false + } +} diff --git a/crypto/blockchains/one/OneScannerProcessor.js b/crypto/blockchains/one/OneScannerProcessor.js new file mode 100644 index 000000000..13c76f6f5 --- /dev/null +++ b/crypto/blockchains/one/OneScannerProcessor.js @@ -0,0 +1,179 @@ +/** + * https://docs.harmony.one/home/developers/api/methods/account-methods/hmy_getbalance + * https://docs.harmony.one/home/developers/api/methods/transaction-related-methods/hmy_gettransactionshistory#api-v2 + */ +import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog' +import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings' +import BlocksoftAxios from '@crypto/common/BlocksoftAxios' +import OneUtils from '@crypto/blockchains/one/ext/OneUtils' +import BlocksoftUtils from '@crypto/common/BlocksoftUtils' +import config from '@app/config/config' + +export default class OneScannerProcessor { + + _blocksToConfirm = 10 + + constructor(settings) { + this._settings = settings + } + + /** + * @param {string} address + * @param {*} additionalData + * @param {string} walletHash + * @return {Promise<{balance:*, unconfirmed:*, provider:string}>} + */ + async getBalanceBlockchain(address, additionalData, walletHash) { + const oneAddress = OneUtils.toOneAddress(address) + BlocksoftCryptoLog.log(this._settings.currencyCode + ' OneScannerProcessor.getBalanceBlockchain started ' + address + ' ' + oneAddress) + try { + + const apiPath = BlocksoftExternalSettings.getStatic('ONE_SERVER') + const data = { + 'jsonrpc': '2.0', + 'id': 1, + 'method': 'hmy_getBalance', + 'params': [ + oneAddress, + 'latest' + ] + } + const res = await BlocksoftAxios._request(apiPath, 'POST', data) + if (typeof res.data === 'undefined') { + return false + } + if (typeof res.data.error !== 'undefined') { + throw new Error(JSON.stringify(res.data.error)) + } + if (typeof res.data.result === 'undefined') { + return false + } + const balance = BlocksoftUtils.hexToDecimalBigger(res.data.result) + return { + balance, + unconfirmed: 0 + } + } catch (e) { + if (config.debug.cryptoErrors) { + console.log(this._settings.currencyCode + ' OneScannerProcessor.getBalanceBlockchain address ' + address + ' ' + oneAddress + ' error ' + e.message) + } + BlocksoftCryptoLog.log(this._settings.currencyCode + ' OneScannerProcessor.getBalanceBlockchain address ' + address + ' ' + oneAddress + ' error ' + e.message) + return false + } + } + + /** + * @param {string} scanData.account.address + * @param {string} scanData.account.walletHash + * @return {Promise<[UnifiedTransaction]>} + */ + async getTransactionsBlockchain(scanData) { + const { address } = scanData.account + const oneAddress = OneUtils.toOneAddress(address) + BlocksoftCryptoLog.log(this._settings.currencyCode + ' OneScannerProcessor.getTransactionsBlockchain started ' + address + ' ' + oneAddress) + try { + + const apiPath = BlocksoftExternalSettings.getStatic('ONE_SERVER') + const data = { + 'jsonrpc': '2.0', + 'id': 1, + 'method': 'hmyv2_getTransactionsHistory', + 'params': [{ + 'address': oneAddress, + 'pageIndex': 0, + 'pageSize': 20, + 'fullTx': true, + 'txType': 'ALL', + 'order': 'DESC' + }] + } + const res = await BlocksoftAxios._request(apiPath, 'POST', data) + if (typeof res.data === 'undefined' || typeof res.data.result === 'undefined' || typeof res.data.result.transactions === 'undefined') { + return false + } + const transactions = [] + for (const tx of res.data.result.transactions) { + const transaction = await this._unifyTransaction(address, oneAddress, tx) + if (transaction) { + transactions.push(transaction) + } + } + return transactions + } catch (e) { + if (config.debug.cryptoErrors) { + console.log(this._settings.currencyCode + ' OneScannerProcessor.getTransactionsBlockchain address ' + address + ' error ' + e.message) + } + BlocksoftCryptoLog.log(this._settings.currencyCode + ' OneScannerProcessor.getTransactionsBlockchain address ' + address + ' error ' + e.message) + return false + } + } + + /** + * + * @param {string} address + * @param {string} oneAddress + * @param {Object} transaction + * @param {string} transaction.blockHash + * @param {string} transaction.blockNumber + * @param {string} transaction.ethHash + * @param {string} transaction.from + * @param {string} transaction.gas + * @param {string} transaction.gasPrice + * @param {string} transaction.hash + * @param {string} transaction.input "0x095ea7b3000000000000000000000000d0cb3e55449646c9735d53e83eea5eb7e97a52dcffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + * @param {string} transaction.nonce + * @param {string} transaction.shardID + * @param {string} transaction.timestamp + * @param {string} transaction.to + * @param {string} transaction.toShardID + * @param {string} transaction.value + * @return {Promise} + * @private + */ + async _unifyTransaction(address, oneAddress, transaction) { + + let formattedTime = transaction.timestamp + try { + formattedTime = BlocksoftUtils.toDate(transaction.timestamp) + } catch (e) { + e.message += ' timestamp error transaction data ' + JSON.stringify(transaction) + throw e + } + + const confirmations = (new Date().getTime() - transaction.timestamp) / 60 + const addressAmount = transaction.value + + let transactionStatus = 'confirming' + if (confirmations > 2) { + transactionStatus = 'success' + } + + const isOutcome = address.toLowerCase() === transaction.from.toLowerCase() || oneAddress.toLowerCase() === transaction.from.toLowerCase() + const isIncome = address.toLowerCase() === transaction.to.toLowerCase() || oneAddress.toLowerCase() === transaction.to.toLowerCase() + const tx = { + transactionHash: transaction.ethHash.toLowerCase(), + blockHash: transaction.blockHash, + blockNumber: +transaction.blockNumber, + blockTime: formattedTime, + blockConfirmations: confirmations, + transactionDirection: isOutcome ? (isIncome ? 'self' : 'outcome') : 'income', + addressFrom: isOutcome ? '' : transaction.from, + addressFromBasic: transaction.from.toLowerCase(), + addressTo: isIncome ? '' : transaction.to, + addressToBasic : transaction.to, + addressAmount, + transactionStatus: transactionStatus, + inputValue: transaction.input + } + const additional = { + nonce : transaction.nonce, + gas: transaction.gas, + gasPrice: transaction.gasPrice, + transactionIndex: transaction.transactionIndex + } + tx.transactionJson = additional + tx.transactionFee = BlocksoftUtils.mul(transaction.gasUsed, transaction.gasPrice).toString() + + return tx + } +} diff --git a/crypto/blockchains/one/OneScannerProcessorErc20.js b/crypto/blockchains/one/OneScannerProcessorErc20.js new file mode 100644 index 000000000..1298452fe --- /dev/null +++ b/crypto/blockchains/one/OneScannerProcessorErc20.js @@ -0,0 +1,181 @@ +/** + * https://docs.harmony.one/home/developers/api/methods/account-methods/hmy_getbalance + * https://docs.harmony.one/home/developers/api/methods/transaction-related-methods/hmy_gettransactionshistory#api-v2 + */ +import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog' +import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings' +import BlocksoftAxios from '@crypto/common/BlocksoftAxios' +import OneUtils from '@crypto/blockchains/one/ext/OneUtils' +import BlocksoftUtils from '@crypto/common/BlocksoftUtils' +import EthScannerProcessorErc20 from '@crypto/blockchains/eth/EthScannerProcessorErc20' + +import config from '@app/config/config' +import OneTmpDS from './stores/OneTmpDS' + +const CACHE_TOKENS = {} + +export default class OneScannerProcessorErc20 extends EthScannerProcessorErc20 { + + _blocksToConfirm = 10 + + /** + * @param {string} scanData.account.address + * @param {string} scanData.account.walletHash + * @return {Promise<[UnifiedTransaction]>} + */ + async getTransactionsBlockchain(scanData) { + const { address } = scanData.account + const oneAddress = OneUtils.toOneAddress(address) + BlocksoftCryptoLog.log(this._settings.currencyCode + ' OneScannerProcessorErc20.getTransactionsBlockchain started ' + address + ' ' + oneAddress) + try { + + CACHE_TOKENS[address] = await OneTmpDS.getCache(address) + const apiPath = BlocksoftExternalSettings.getStatic('ONE_SERVER') + const data = { + 'jsonrpc': '2.0', + 'id': 1, + 'method': 'hmyv2_getTransactionsHistory', + 'params': [{ + 'address': oneAddress, + 'pageIndex': 0, + 'pageSize': 20, + 'fullTx': true, + 'txType': 'ALL', + 'order': 'DESC' + }] + } + const res = await BlocksoftAxios._request(apiPath, 'POST', data) + if (typeof res.data === 'undefined' || typeof res.data.result === 'undefined' || typeof res.data.result.transactions === 'undefined') { + return false + } + const transactions = [] + let firstTransaction = false + for (const tx of res.data.result.transactions) { + if (typeof CACHE_TOKENS[address] !== 'undefined' && typeof CACHE_TOKENS[address][this._tokenAddress] !== 'undefined') { + const diff = tx.timestamp - CACHE_TOKENS[address][this._tokenAddress] + const diffNow = (new Date().getTime() - tx.timestamp) / 60 + if (diff < -20) { + continue + } + if (diff <= 0) { + if (diffNow > 100) { + continue + } + } + } + if (!firstTransaction) { + firstTransaction = tx.timestamp + } + const transaction = await this._unifyTransaction(address, oneAddress, tx) + if (transaction) { + transactions.push(transaction) + } + } + CACHE_TOKENS[address][this._tokenAddress] = firstTransaction + await OneTmpDS.saveCache(address, this._tokenAddress, firstTransaction) + return transactions + } catch (e) { + if (config.debug.cryptoErrors) { + console.log(this._settings.currencyCode + ' OneScannerProcessorErc20.getTransactionsBlockchain address ' + address + ' error ' + e.message) + } + BlocksoftCryptoLog.log(this._settings.currencyCode + ' OneScannerProcessorErc20.getTransactionsBlockchain address ' + address + ' error ' + e.message) + return false + } + } + + /** + * + * @param {string} address + * @param {string} oneAddress + * @param {Object} transaction + * @param {string} transaction.blockHash + * @param {string} transaction.blockNumber + * @param {string} transaction.ethHash + * @param {string} transaction.from + * @param {string} transaction.gas + * @param {string} transaction.gasPrice + * @param {string} transaction.hash + * @param {string} transaction.input "0x095ea7b3000000000000000000000000d0cb3e55449646c9735d53e83eea5eb7e97a52dcffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + * @param {string} transaction.nonce + * @param {string} transaction.shardID + * @param {string} transaction.timestamp + * @param {string} transaction.to + * @param {string} transaction.toShardID + * @param {string} transaction.value + * @return {Promise} + * @private + */ + async _unifyTransaction(address, oneAddress, transaction) { + + const contractEvents = await this._token.getPastEvents('Transfer', { + fromBlock: transaction.blockNumber, + toBlock: transaction.blockNumber + }) + if (!contractEvents) { + return false + } + let foundEventFrom = false + let foundEventTo = false + let foundEventSelf = false + let addressAmount = 0 + for (const tmp of contractEvents) { + if (tmp.transactionHash !== transaction.ethHash) { + continue + } + if (tmp.returnValues.to.toLowerCase() === address.toLowerCase()) { + if (tmp.returnValues.from.toLowerCase() === address.toLowerCase()) { + foundEventSelf = tmp + } else { + foundEventTo = tmp + addressAmount = addressAmount * 1 + tmp.returnValues.value * 1 + } + } else if (tmp.returnValues.from.toLowerCase() === address.toLowerCase()) { + foundEventFrom = tmp + addressAmount = addressAmount * 1 - tmp.returnValues.value * 1 + } + } + if (!foundEventSelf && !foundEventTo && !foundEventFrom) { + return false + } + + let formattedTime = transaction.timestamp + try { + formattedTime = BlocksoftUtils.toDate(transaction.timestamp) + } catch (e) { + e.message += ' timestamp error transaction data ' + JSON.stringify(transaction) + throw e + } + + const confirmations = (new Date().getTime() - transaction.timestamp) / 60 + let transactionStatus = 'confirming' + if (confirmations > 2) { + transactionStatus = 'success' + } + + const tx = { + transactionHash: transaction.ethHash.toLowerCase(), + blockHash: transaction.blockHash, + blockNumber: +transaction.blockNumber, + blockTime: formattedTime, + blockConfirmations: confirmations, + transactionDirection: addressAmount * 1 <= 0 ? (foundEventTo ? 'outcome' : 'self') : 'income', + addressFrom: foundEventFrom ? transaction.from : '', + addressFromBasic: transaction.from.toLowerCase(), + addressTo: foundEventTo ? transaction.to : '', + addressToBasic: transaction.to, + addressAmount, + transactionStatus: transactionStatus, + inputValue: transaction.input + } + const additional = { + nonce: transaction.nonce, + gas: transaction.gas, + gasPrice: transaction.gasPrice, + transactionIndex: transaction.transactionIndex + } + tx.transactionJson = additional + tx.transactionFee = BlocksoftUtils.mul(transaction.gasUsed, transaction.gasPrice).toString() + + return tx + } +} diff --git a/crypto/blockchains/one/ext/OneUtils.js b/crypto/blockchains/one/ext/OneUtils.js new file mode 100644 index 000000000..531b9209a --- /dev/null +++ b/crypto/blockchains/one/ext/OneUtils.js @@ -0,0 +1,239 @@ +/** + * @harmony-js/crypto/src/bech32.ts + * https://github.com/harmony-one/sdk/blob/master/packages/harmony-crypto/src/bech32.ts + * const { toBech32 } = require("@harmony-js/crypto"); + * console.log("Using account: " + toBech32("0xxxxx", "one1")) + */ + +const CHARSET = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l' +const GENERATOR = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3] + +const polymod = (values) => { + let chk = 1 + // tslint:disable-next-line + for (let p = 0; p < values.length; ++p) { + const top = chk >> 25 + chk = ((chk & 0x1ffffff) << 5) ^ values[p] + for (let i = 0; i < 5; ++i) { + if ((top >> i) & 1) { + chk ^= GENERATOR[i] + } + } + } + return chk +} + +const hrpExpand = (hrp) => { + const ret = [] + let p + for (p = 0; p < hrp.length; ++p) { + ret.push(hrp.charCodeAt(p) >> 5) + } + ret.push(0) + for (p = 0; p < hrp.length; ++p) { + ret.push(hrp.charCodeAt(p) & 31) + } + return Buffer.from(ret) +} + +function createChecksum(hrp, data) { + const values = Buffer.concat([ + Buffer.from(hrpExpand(hrp)), + data, + Buffer.from([0, 0, 0, 0, 0, 0]) + ]) + // var values = hrpExpand(hrp).concat(data).concat([0, 0, 0, 0, 0, 0]); + const mod = polymod(values) ^ 1 + const ret = [] + for (let p = 0; p < 6; ++p) { + ret.push((mod >> (5 * (5 - p))) & 31) + } + return Buffer.from(ret) +} + +const bech32Encode = (hrp, data) => { + const combined = Buffer.concat([data, createChecksum(hrp, data)]) + let ret = hrp + '1' + // tslint:disable-next-line + for (let p = 0; p < combined.length; ++p) { + ret += CHARSET.charAt(combined[p]) + } + return ret +} + + +const convertBits = ( + data, + fromWidth, + toWidth, + pad = true +) => { + let acc = 0 + let bits = 0 + const ret = [] + const maxv = (1 << toWidth) - 1 + // tslint:disable-next-line + for (let p = 0; p < data.length; ++p) { + const value = data[p] + if (value < 0 || value >> fromWidth !== 0) { + return null + } + acc = (acc << fromWidth) | value + bits += fromWidth + while (bits >= toWidth) { + bits -= toWidth + ret.push((acc >> bits) & maxv) + } + } + + if (pad) { + if (bits > 0) { + ret.push((acc << (toWidth - bits)) & maxv) + } + } else if (bits >= fromWidth || (acc << (toWidth - bits)) & maxv) { + return null + } + + return Buffer.from(ret) +} + +const bech32Decode = (bechString) => { + let p; + let hasLower = false; + let hasUpper = false; + for (p = 0; p < bechString.length; ++p) { + if (bechString.charCodeAt(p) < 33 || bechString.charCodeAt(p) > 126) { + return null; + } + if (bechString.charCodeAt(p) >= 97 && bechString.charCodeAt(p) <= 122) { + hasLower = true; + } + if (bechString.charCodeAt(p) >= 65 && bechString.charCodeAt(p) <= 90) { + hasUpper = true; + } + } + if (hasLower && hasUpper) { + return null; + } + bechString = bechString.toLowerCase(); + const pos = bechString.lastIndexOf('1'); + if (pos < 1 || pos + 7 > bechString.length || bechString.length > 90) { + return null; + } + const hrp = bechString.substring(0, pos); + const data = []; + for (p = pos + 1; p < bechString.length; ++p) { + const d = CHARSET.indexOf(bechString.charAt(p)); + if (d === -1) { + return null; + } + data.push(d); + } + + try { + if (!verifyChecksum(hrp, Buffer.from(data))) { + return null; + } + } catch (e) { + e.message += ' in verifyChecksum' + throw e + } + + return { hrp, data: Buffer.from(data.slice(0, data.length - 6)) }; +}; + +function verifyChecksum(hrp, data) { + return polymod(Buffer.concat([hrpExpand(hrp), data])) === 1; +} + +const toChecksumAddress = (address)=> { + if (typeof address !== 'string' || !address.match(/^0x[0-9A-Fa-f]{40}$/)) { + throw new Error('invalid address ' + address); + } + + address = address.toLowerCase(); + + const chars = address.substring(2).split(''); + + let hashed = new Uint8Array(40); + for (let i = 0; i < 40; i++) { + hashed[i] = chars[i].charCodeAt(0); + } + // hashed = bytes.arrayify(keccak256(hashed)) || hashed; + + for (let i = 0; i < 40; i += 2) { + if (hashed[i >> 1] >> 4 >= 8) { + chars[i] = chars[i].toUpperCase(); + } + if ((hashed[i >> 1] & 0x0f) >= 8) { + chars[i + 1] = chars[i + 1].toUpperCase(); + } + } + + return '0x' + chars.join(''); +}; + + +export default { + isOneAddress(address) { + if (typeof address === 'undefined' || typeof address.match === 'undefined') { + return false + } + try { + return !!address.match(/^one1[qpzry9x8gf2tvdw0s3jn54khce6mua7l]{38}/); + } catch (e) { + e.message += ' in match - address ' + JSON.stringify(address) + throw e + } + }, + + toOneAddress(address, useHRP = 'one') { + if (address.indexOf('one') === 0) { + return address + } + + const prepAddr = address.replace('0x', '').toLowerCase() + + const addrBz = convertBits( Buffer.from(prepAddr, 'hex'), 8, 5) + + if (addrBz === null) { + throw new Error('Could not convert byte Buffer to 5-bit Buffer') + } + + return bech32Encode(useHRP, addrBz) + }, + + fromOneAddress(address, useHRP = 'one') { + let res + try { + res = bech32Decode(address) + } catch (e) { + e.message += ' in bech32Decode - address ' + JSON.stringify(address) + throw e + } + + if (res === null) { + throw new Error('Invalid bech32 address') + } + + const { hrp, data } = res + + if (hrp !== useHRP) { + throw new Error(`Expected hrp to be ${useHRP} but got ${hrp}`) + } + + const buf = convertBits(data, 5, 8, false) + + if (buf === null) { + throw new Error('Could not convert buffer to bytes') + } + + try { + const tmp = toChecksumAddress('0x' + buf.toString('hex')) + return tmp + } catch (e) { + e.message += ' in toChecksumAddress' + throw e + } + } +} diff --git a/crypto/blockchains/one/stores/OneTmpDS.js b/crypto/blockchains/one/stores/OneTmpDS.js new file mode 100644 index 000000000..bf7742b09 --- /dev/null +++ b/crypto/blockchains/one/stores/OneTmpDS.js @@ -0,0 +1,45 @@ + +import Database from '@app/appstores/DataSource/Database'; + +const tableName = ' transactions_scanners_tmp' + +class OneTmpDS { + /** + * @type {string} + * @private + */ + _currencyCode = 'ONE' + + async getCache(address) { + const res = await Database.query(` + SELECT tmp_key, tmp_sub_key, tmp_val + FROM ${tableName} + WHERE currency_code='${this._currencyCode}' + AND address='${address}' + AND tmp_key='last_tx' + `) + const tmp = {} + if (res.array) { + let row + for (row of res.array) { + tmp[row.tmp_sub_key] = row.tmp_val + } + } + return tmp + } + + async saveCache(address, subKey, value) { + const now = new Date().toISOString() + const prepared = [{ + currency_code : this._currencyCode, + address : address, + tmp_key : 'last_tx', + tmp_sub_key : subKey, + tmp_val : value, + created_at : now + }] + await Database.setTableName(tableName).setInsertData({insertObjs : prepared}).insert() + } +} + +export default new OneTmpDS() diff --git a/crypto/blockchains/sol/SolAddressProcessor.js b/crypto/blockchains/sol/SolAddressProcessor.js new file mode 100644 index 000000000..a64a4af9b --- /dev/null +++ b/crypto/blockchains/sol/SolAddressProcessor.js @@ -0,0 +1,53 @@ +/** + * @version 0.52 + * not ok + * https://github.com/solana-labs/browser-extension/blob/master/app/background/lib/wallet.ts#L37 + * ok + * https://github.com/project-serum/spl-token-wallet + * https://github.com/project-serum/spl-token-wallet/blob/master/src/utils/walletProvider/localStorage.js#L30 + */ +import BlocksoftKeys from '@crypto/actions/BlocksoftKeys/BlocksoftKeys' +import XlmDerivePath from '@crypto/blockchains/xlm/ext/XlmDerivePath' + +const nacl = require('tweetnacl') +const bs58 = require('bs58') + +export default class SolAddressProcessor { + + async setBasicRoot(root) { + this.root = root + } + + /** + * @param {string|Buffer} privateKey - not used as bip32 private is outdated + * @param {*} data + * @returns {Promise<{privateKey: string, address: string}>} + */ + async getAddress(privateKey, data = {}, superPrivateData = {}, seed, source = '') { + if (typeof superPrivateData.mnemonic === 'undefined' || !superPrivateData.mnemonic) { + throw new Error('need mnemonic') + } + if (!seed) { + seed = await BlocksoftKeys.getSeedCached(superPrivateData.mnemonic) + } + const seedHex = seed.toString('hex') + let derivationPath = data.derivationPath + if (derivationPath !== `m/44'/501'/0'` && derivationPath !== `m/44'/501'/0'/0'`) { + derivationPath = `m/44'/501'/0'/0'` + } + + if (seedHex.length < 128) { + throw new Error('bad seedHex') + } + + const res = XlmDerivePath(seedHex, derivationPath) + const key = nacl.sign.keyPair.fromSeed(res.key) + + const naclPubKey = Buffer.from(key.publicKey).toString('hex') + const naclSecretKey = Buffer.from(key.secretKey).toString('hex') + const address = bs58.encode(key.publicKey) + + const ret = { address, privateKey : naclSecretKey, addedData: { naclPubKey, derivationPath : derivationPath.replace(/[']/g, 'quote'), seedHexPart: seedHex.substr(0, 5) } } + return ret + } +} diff --git a/crypto/blockchains/sol/SolScannerProcessor.js b/crypto/blockchains/sol/SolScannerProcessor.js new file mode 100644 index 000000000..411f73ff2 --- /dev/null +++ b/crypto/blockchains/sol/SolScannerProcessor.js @@ -0,0 +1,337 @@ +/** + * @version 0.52 + */ +import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog' +import BlocksoftAxios from '@crypto/common/BlocksoftAxios' +import BlocksoftUtils from '@crypto/common/BlocksoftUtils' +import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings' + +import SolTmpDS from '@crypto/blockchains/sol/stores/SolTmpDS' + +import config from '@app/config/config' +import SolUtils from '@crypto/blockchains/sol/ext/SolUtils' + +const CACHE_FROM_DB = {} +const CACHE_TXS = {} +const CACHE_VALID_TIME = 120000 +let CACHE_LAST_BLOCK = 0 + +export default class SolScannerProcessor { + + constructor(settings) { + this._settings = settings + this.tokenAddress = typeof settings.tokenAddress !== 'undefined' ? settings.tokenAddress : '' + } + + /** + * @param {string} address + * @return {Promise<{balance, provider}>} + * https://docs.solana.com/developing/clients/jsonrpc-api#getaccountinfo + * https://docs.solana.com/developing/clients/jsonrpc-api#getconfirmedsignaturesforaddress2 + * curl https://solana-api.projectserum.com -X POST -H "Content-Type: application/json" -d '{'jsonrpc":"2.0", "id":1, "method":"getBalance", "params":["9mnBdsuL1x24HbU4oeNDBAYVAGg2vVndkRAc18kPNqCJ']}' + */ + async getBalanceBlockchain(address) { + address = address.trim() + BlocksoftCryptoLog.log(this._settings.currencyCode + ' SolScannerProcessor getBalanceBlockchain address ' + address) + + let balance = 0 + try { + await SolUtils.getEpoch() + + const apiPath = BlocksoftExternalSettings.getStatic('SOL_SERVER') + const data = { + 'jsonrpc': '2.0', + 'id': 1, + 'method': 'getBalance', + 'params': [address] + } + const res = await BlocksoftAxios._request(apiPath, 'POST', data) + + if (typeof res.data.result === 'undefined' || typeof res.data.result.value === 'undefined') { + return false + } + if (typeof res.data.result.context !== 'undefined' && typeof res.data.result.context.slot !== 'undefined') { + CACHE_LAST_BLOCK = res.data.result.context.slot * 1 + } + balance = res.data.result.value + } catch (e) { + BlocksoftCryptoLog.log(this._settings.currencyCode + ' SolScannerProcessor getBalanceBlockchain address ' + address + ' error ' + e.message) + return false + } + return { balance, unconfirmed: 0, provider: 'solana-api' } + } + + + /** + * @param {string} scanData.account.address + * @return {Promise<[UnifiedTransaction]>} + * https://docs.solana.com/developing/clients/jsonrpc-api#getsignaturesforaddress + * curl https://api.mainnet-beta.solana.com -X POST -H "Content-Type: application/json" -d '{'jsonrpc": "2.0","id": 1,"method": "getConfirmedSignaturesForAddress2","params": ["9mnBdsuL1x24HbU4oeNDBAYVAGg2vVndkRAc18kPNqCJ",{"limit': 1}]}' + */ + async getTransactionsBlockchain(scanData, source) { + const address = scanData.account.address.trim() + const lastHashVar = address + this.tokenAddress + this._cleanCache() + try { + if (typeof CACHE_FROM_DB[lastHashVar] === 'undefined') { + CACHE_FROM_DB[lastHashVar] = await SolTmpDS.getCache(lastHashVar) + } + + const data = { + 'jsonrpc': '2.0', + 'id': 1, + 'method': 'getConfirmedSignaturesForAddress2', + 'params': [ + address, + { + 'limit': 100 + } + ] + } + if (CACHE_FROM_DB[lastHashVar] && typeof CACHE_FROM_DB[lastHashVar]['last_hash'] !== 'undefined') { + data.params[1].until = CACHE_FROM_DB[lastHashVar]['last_hash'] + } + const apiPath = BlocksoftExternalSettings.getStatic('SOL_SERVER') + const res = await BlocksoftAxios._request(apiPath, 'POST', data) + if (typeof res.data.result === 'undefined' || !res.data.result) { + return false + } + + const transactions = await this._unifyTransactions(address, res.data.result, lastHashVar) + BlocksoftCryptoLog.log(this._settings.currencyCode + ' SolScannerProcessor.getTransactions finished ' + address) + return transactions + } catch (e) { + BlocksoftCryptoLog.log(this._settings.currencyCode + ' SolScannerProcessor getTransactionsBlockchain address ' + address + ' error ' + e.message) + return false + } + } + + async _unifyTransactions(address, result, lastHashVar) { + const transactions = [] + let lastHash = false + let hasError = false + for (const tx of result) { + try { + const transaction = await this._unifyTransaction(address, tx) + if (transaction) { + transactions.push(transaction) + if (transaction.transactionStatus === 'success' && !lastHash && !hasError) { + lastHash = transaction.transactionHash + } + } + } catch (e) { + hasError = true + if (e.message.indexOf('request failed') === -1) { + if (config.debug.appErrors) { + console.log(this._settings.currencyCode + ' SolScannerProcessor._unifyTransactions ' + tx.signature + ' error ' + e.message) + } + BlocksoftCryptoLog.log(this._settings.currencyCode + ' SolScannerProcessor._unifyTransactions ' + tx.signature + ' error ' + e.message) + } + } + } + + if (lastHash) { + if (!CACHE_FROM_DB[lastHashVar]) { + CACHE_FROM_DB[lastHashVar] = { 'last_hash': lastHash } + await SolTmpDS.saveCache(lastHashVar, 'last_hash', lastHash) + } else if (typeof CACHE_FROM_DB[lastHashVar]['last_hash'] === 'undefined') { + CACHE_FROM_DB[lastHashVar]['last_hash'] = lastHash + await SolTmpDS.saveCache(lastHashVar, 'last_hash', lastHash) + } else { + CACHE_FROM_DB[lastHashVar]['last_hash'] = lastHash + await SolTmpDS.updateCache(lastHashVar, 'last_hash', lastHash) + } + } + return transactions + } + + _cleanCache() { + const now = new Date().getTime() + for (const key in CACHE_TXS) { + const t = (now - CACHE_TXS[key].now) + if (t > CACHE_VALID_TIME) { + delete CACHE_TXS[key] + } + } + } + + async _unifyTransaction(address, transaction) { + + const data = { + 'jsonrpc': '2.0', + 'id': 1, + 'method': 'getConfirmedTransaction', + 'params': [ + transaction.signature, + {encoding : 'jsonParsed'} + ] + } + + let additional + if (typeof CACHE_TXS[transaction.signature] === 'undefined') { + const apiPath = BlocksoftExternalSettings.getStatic('SOL_SERVER') + try { + const res = await BlocksoftAxios._request(apiPath, 'POST', data) + if (typeof res.data.result === 'undefined' || !res.data.result) { + return false + } + additional = res.data.result + CACHE_TXS[transaction.signature] = {data : additional, now : new Date().getTime() } + } catch (e) { + if (config.debug.cryptoErrors) { + console.log(this._settings.currencyCode + ' SolScannerProcessor._unifyTransaction ' + transaction.signature + ' request error ' + e.message) + } + throw e + } + } else { + additional = CACHE_TXS[transaction.signature].data + } + + let addressFrom = false + let addressTo = false + let addressAmount = 0 + let anyFromAddress = false + let anyToAddress = false + + const indexedPre = {} + const indexedPost = {} + const indexedCreated = {} + const indexedAssociated = {} + + if (this.tokenAddress) { + for (const tmp of additional.meta.preTokenBalances) { + if (tmp.mint !== this.tokenAddress) continue + const realIndex = tmp.accountIndex + indexedPre[realIndex] = tmp.uiTokenAmount.amount + } + + for (const tmp of additional.meta.postTokenBalances) { + if (tmp.mint !== this.tokenAddress) continue + const realIndex = tmp.accountIndex + indexedPost[realIndex] = tmp.uiTokenAmount.amount + } + + for (const tmp of additional.transaction.message.instructions) { + if (tmp.program !== 'spl-associated-token-account') continue + indexedCreated[tmp.parsed.info.account] = tmp.parsed.info.wallet + } + + for (let i = 0, ic = additional.transaction.message.accountKeys.length; i < ic; i++) { + const tmpAddress = additional.transaction.message.accountKeys[i] + if (tmpAddress.pubkey === '11111111111111111111111111111111') continue + const sourceAssociatedTokenAddress = await SolUtils.findAssociatedTokenAddress( + tmpAddress.pubkey, + this.tokenAddress + ) + indexedAssociated[sourceAssociatedTokenAddress] = tmpAddress + } + } else { + // do nothing! + } + + let anySigner = false + let addressAmountPlus = false + for (let i = 0, ic = additional.transaction.message.accountKeys.length; i < ic; i++) { + let tmpAddress = additional.transaction.message.accountKeys[i] + if (tmpAddress.pubkey === '11111111111111111111111111111111') continue + let tmpAmount = '0' + if (typeof indexedAssociated[tmpAddress.pubkey] !== 'undefined') { + tmpAddress = indexedAssociated[tmpAddress.pubkey] + } + if (this.tokenAddress) { + const to = typeof indexedPost[i] !== 'undefined' ? indexedPost[i] : 0 + const from = typeof indexedPre[i] !== 'undefined' ? indexedPre[i] : 0 + tmpAmount = BlocksoftUtils.diff(to, from).toString() + } else { + tmpAmount = BlocksoftUtils.diff(additional.meta.postBalances[i], additional.meta.preBalances[i]).toString() + } + + if (tmpAddress.pubkey && tmpAddress.signer) { + anySigner = tmpAddress.pubkey + } + + if (tmpAmount === '0') continue + + if (tmpAddress.pubkey === address || + ( + typeof indexedCreated[tmpAddress.pubkey] !== 'undefined' && indexedCreated[tmpAddress.pubkey] === address + ) + ) { + if (tmpAmount.indexOf('-') === -1) { + addressTo = tmpAddress.pubkey + addressAmount = tmpAmount + addressAmountPlus = true + } else { + addressFrom = tmpAddress.pubkey + addressAmount = tmpAmount.replace('-', '') + } + } else { + if (tmpAddress.signer) { + anyFromAddress = tmpAddress.pubkey + } else { + anyToAddress = tmpAddress.pubkey + } + } + } + + if (!addressFrom && anySigner !== addressTo) { + addressFrom = anySigner + } + if (!addressFrom && !addressTo) { + return false + } + if (anyFromAddress && !addressFrom) { + addressFrom = anyFromAddress + } + if (anyToAddress && !addressTo) { + addressTo = anyToAddress + } + if (!addressTo) { + addressTo = 'System' + } + + let formattedTime = transaction.blockTime + try { + formattedTime = BlocksoftUtils.toDate(transaction.blockTime) + } catch (e) { + e.message += ' timestamp error transaction2 data ' + JSON.stringify(transaction) + throw e + } + let transactionStatus = 'new' + if (transaction.confirmationStatus === 'finalized') { + transactionStatus = 'success' + } else if (transaction.confirmationStatus === 'confirmed') { + transactionStatus = 'confirming' + } + if (typeof transaction.err !== 'undefined' && transaction.err) { + transactionStatus = 'fail' + } + + let transactionDirection = addressFrom === address ? 'outcome' : 'income' + if (!addressFrom && anySigner === addressTo) { + if (addressAmountPlus) { + transactionDirection = 'swap_income' + } else { + transactionDirection = 'swap_outcome' + } + } + const blockConfirmations = CACHE_LAST_BLOCK > 0 ? Math.round(CACHE_LAST_BLOCK - additional.slot * 1) : 0 + const tx = { + transactionHash: transaction.signature, + blockHash: additional.transaction.message.recentBlockhash, + blockNumber: transaction.slot, + blockTime: formattedTime, + blockConfirmations, + transactionDirection, + addressFrom: addressFrom === address ? '' : addressFrom, + addressTo: addressTo === address ? '' : addressTo, + addressAmount, + transactionStatus, + transactionFee: additional.meta.fee + } + if (typeof transaction.memo !== 'undefined' && transaction.memo) { + tx.transactionJson = { memo: transaction.memo } + } + return tx + } +} diff --git a/crypto/blockchains/sol/SolScannerProcessorSpl.js b/crypto/blockchains/sol/SolScannerProcessorSpl.js new file mode 100644 index 000000000..09ba22400 --- /dev/null +++ b/crypto/blockchains/sol/SolScannerProcessorSpl.js @@ -0,0 +1,67 @@ +/** + * @version 0.52 + */ +import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog' +import BlocksoftAxios from '@crypto/common/BlocksoftAxios' +import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings' + +import SolScannerProcessor from '@crypto/blockchains/sol/SolScannerProcessor' + +const CACHE_BALANCES = {} +const CACHE_VALID_TIME = 30000 // 30 seconds + +export default class SolScannerProcessorSpl extends SolScannerProcessor { + + /** + * @param {string} address + * @return {Promise<{balance, provider}>} + */ + async getBalanceBlockchain(address) { + address = address.trim() + BlocksoftCryptoLog.log(this._settings.currencyCode + ' SolScannerProcessorSpl getBalanceBlockchain address ' + address) + + const now = new Date().getTime() + let balance = 0 + try { + if (typeof CACHE_BALANCES[address] === 'undefined' || typeof CACHE_BALANCES[address].time === 'undefined' || (now - CACHE_BALANCES[address].time < CACHE_VALID_TIME)) { + CACHE_BALANCES[address] = {} + const apiPath = BlocksoftExternalSettings.getStatic('SOL_SERVER') + + const data = { + 'jsonrpc': '2.0', + 'id': 1, + 'method': 'getTokenAccountsByOwner', + 'params': [ + address, + { + programId: 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA' + }, + { encoding: 'jsonParsed', commitment: 'processed' } + ] + } + + const res = await BlocksoftAxios._request(apiPath, 'POST', data) + if (typeof res.data.result === 'undefined' || typeof res.data.result.value === 'undefined') { + return false + } + for (const account of res.data.result.value) { + if (typeof account.account === 'undefined') continue + if (typeof account.account.data.program === 'undefined' || account.account.data.program !== 'spl-token') continue + const parsed = account.account.data.parsed.info + if (typeof parsed.state === 'undefined' && parsed.state !== 'initialized') continue + CACHE_BALANCES[address][parsed.mint] = parsed.tokenAmount // "amount": "1606300", "decimals": 6, "uiAmount": 1.6063, "uiAmountString": "1.6063" + } + CACHE_BALANCES[address].time = now + } + if (typeof CACHE_BALANCES[address][this.tokenAddress] === 'undefined' || typeof CACHE_BALANCES[address][this.tokenAddress].amount === 'undefined') { + balance = 0 + } else { + balance = CACHE_BALANCES[address][this.tokenAddress].amount * 1 + } + } catch (e) { + BlocksoftCryptoLog.log(this._settings.currencyCode + ' SolScannerProcessorSpl getBalanceBlockchain address ' + address + ' error ' + e.message) + return false + } + return { balance, unconfirmed: 0, provider: 'solana-api' } + } +} diff --git a/crypto/blockchains/sol/SolTokenProcessor.js b/crypto/blockchains/sol/SolTokenProcessor.js new file mode 100644 index 000000000..d13943b6f --- /dev/null +++ b/crypto/blockchains/sol/SolTokenProcessor.js @@ -0,0 +1,89 @@ +/** + * @version 0.52 + */ +import BlocksoftAxios from '@crypto/common/BlocksoftAxios' +import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings' +import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog' + +export default class SolTokenProcessor { + + /** + * @param {string} tokenAddress + * @returns {Promise<{tokenAddress: *, currencyName: *, provider: string, tokenDecimals: *, icon: *, description: *, tokenType: string, currencyCode: *}|boolean>} + */ + async getTokenDetails(tokenAddress) { + const link = await BlocksoftExternalSettings.get('SOL_TOKENS_LIST') + const res = await BlocksoftAxios.get(link) + if (!res || typeof res.data.tokens === 'undefined' || !res.data.tokens) { + return false + } + + let tmp = false + for (const token of res.data.tokens) { + if (token.address === tokenAddress) { + if (token.chainId !== 101) continue + tmp = token + break + } + } + if (tmp) { + + + return { + currencyCodePrefix: 'CUSTOM_SOL_', + currencyCode: tmp.symbol, + currencyName: tmp.name, + tokenType: 'SOL', + tokenAddress: tokenAddress, + tokenDecimals: tmp.decimals, + icon: tmp.logoURI, + description: tmp.website, + coingeckoId: tmp.coingeckoId, + provider: 'sol' + } + } + + + let decimals = 6 + try { + const apiPath = BlocksoftExternalSettings.getStatic('SOL_SERVER') + const data = { + 'jsonrpc': '2.0', + 'id': 1, + 'method': 'getAccountInfo', + 'params': [ + tokenAddress, + { + 'encoding': 'jsonParsed' + } + ] + } + const res = await BlocksoftAxios._request(apiPath, 'POST', data) + if (typeof res.data.result === 'undefined' || typeof res.data.result.value === 'undefined') { + return false + } + if (typeof res.data.result.value.owner === 'undefined' || res.data.result.value.owner !== 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA') { + return false + } + if (typeof res.data.result.value.data.program === 'undefined' || res.data.result.value.data.program !== 'spl-token') { + return false + } + decimals = res.data.result.value.data.parsed.info.decimals + + } catch (e) { + BlocksoftCryptoLog.log(this._settings.currencyCode + ' SolTokenProcessor getTokenDetails tokenAddress ' + tokenAddress + ' error ' + e.message) + return false + } + + return { + currencyCodePrefix: 'CUSTOM_SOL_', + currencyCode: 'UNKNOWN_TOKEN_' + tokenAddress, + currencySymbol: 'UNKNOWN', + currencyName: tokenAddress, + tokenType: 'SOL', + tokenAddress: tokenAddress, + tokenDecimals: decimals, + provider: 'sol' + } + } +} diff --git a/crypto/blockchains/sol/SolTransferProcessor.ts b/crypto/blockchains/sol/SolTransferProcessor.ts new file mode 100644 index 000000000..9461deb3d --- /dev/null +++ b/crypto/blockchains/sol/SolTransferProcessor.ts @@ -0,0 +1,286 @@ +/** + * @version 0.52 + */ +import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog' +import BlocksoftUtils from '@crypto/common/BlocksoftUtils' +import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings' +import BlocksoftBalances from '@crypto/actions/BlocksoftBalances/BlocksoftBalances' + +// eslint-disable-next-line no-unused-vars +import { BlocksoftBlockchainTypes } from '@crypto/blockchains/BlocksoftBlockchainTypes' + +import config from '@app/config/config' + +import { PublicKey, SystemProgram, Transaction, StakeProgram, Authorized } from '@solana/web3.js/src/index' +import SolUtils from '@crypto/blockchains/sol/ext/SolUtils' +import SolTmpDS from '@crypto/blockchains/sol/stores/SolTmpDS' +import SolStakeUtils from '@crypto/blockchains/sol/ext/SolStakeUtils' +import { Buffer } from 'buffer' +import BlocksoftCryptoUtils from '@crypto/common/BlocksoftCryptoUtils' +import BlocksoftAxios from '@crypto/common/BlocksoftAxios' + +export default class SolTransferProcessor implements BlocksoftBlockchainTypes.TransferProcessor { + private _settings: { network: string; currencyCode: string } + + constructor(settings: { network: string; currencyCode: string }) { + this._settings = settings + } + + needPrivateForFee(): boolean { + return false + } + + checkSendAllModal(data: { currencyCode: any }): boolean { + return false + } + + async getFeeRate(data: BlocksoftBlockchainTypes.TransferData, privateData: BlocksoftBlockchainTypes.TransferPrivateData, additionalData: {} = {}): Promise { + const result: BlocksoftBlockchainTypes.FeeRateResult = { + selectedFeeIndex: -3, + shouldShowFees: false + } as BlocksoftBlockchainTypes.FeeRateResult + + const feeForTx = BlocksoftExternalSettings.getStatic('SOL_PRICE') + result.fees = [ + { + langMsg: 'xrp_speed_one', + feeForTx, + amountForTx: data.amount + } + ] + result.selectedFeeIndex = 0 + + + return result + } + + async getTransferAllBalance(data: BlocksoftBlockchainTypes.TransferData, privateData: BlocksoftBlockchainTypes.TransferPrivateData, additionalData: BlocksoftBlockchainTypes.TransferAdditionalData = {}): Promise { + const address = data.addressFrom.trim() + let rent = 0 + let balance = data.amount + try { + const beachPath = 'https://public-api.solanabeach.io/v1/account/' + address + '?' + BlocksoftCryptoLog.log(this._settings.currencyCode + ' SolTransferProcessor.getTransferAllBalance address ' + address + ' beach link ' + beachPath) + const res = await BlocksoftAxios.get(beachPath) + BlocksoftCryptoLog.log(this._settings.currencyCode + ' SolTransferProcessor.getTransferAllBalance address ' + address + ' beach res ', res?.data) + if (typeof res?.data?.value !== 'undefined' && typeof res?.data?.value?.base !== 'undefined') { + if (res?.data?.value?.base?.address?.address !== address) { + throw new Error('wrong value address ' + res?.data?.value?.base?.address?.address) + } + balance = res?.data?.value?.base?.balance || data.amount + rent = res?.data?.value?.base?.rentExemptReserve || 0 + if (rent) { + balance = BlocksoftUtils.diff(balance, rent) + } + } + } catch (e) { + BlocksoftCryptoLog.log(this._settings.currencyCode + ' SolTransferProcessor.getTransferAllBalance address ' + address + ' beach error ' + e.message) + } + + // @ts-ignore + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' SolTransferProcessor.getTransferAllBalance ', data.addressFrom + ' => ' + balance) + + const fees = await this.getFeeRate(data, privateData, additionalData) + + const amount = BlocksoftUtils.diff(balance, fees.fees[0].feeForTx).toString() + + return { + ...fees, + shouldShowFees: false, + selectedTransferAllBalance: amount + } + } + + /** + * @param data + * @param privateData + * @param uiData + */ + async sendTx(data: BlocksoftBlockchainTypes.TransferData, privateData: BlocksoftBlockchainTypes.TransferPrivateData, uiData: BlocksoftBlockchainTypes.TransferUiData): Promise { + + if (typeof privateData.privateKey === 'undefined') { + throw new Error('SOL transaction required privateKey (derivedSeed)') + } + if (typeof data.addressTo === 'undefined') { + throw new Error('SOL transaction required addressTo') + } + + if (uiData && typeof uiData.uiErrorConfirmed !== 'undefined' && + ( + uiData.uiErrorConfirmed === 'UI_CONFIRM_ADDRESS_TO_EMPTY_BALANCE' + || uiData.uiErrorConfirmed === 'UI_CONFIRM_DOUBLE_SEND' + ) + ) { + // do nothing + } else if (data.addressTo !== 'STAKE' && data.addressTo.indexOf('UNSTAKE') === -1) { + if (typeof uiData?.selectedFee?.bseOrderId === 'undefined') { + const balance = await (BlocksoftBalances.setCurrencyCode('SOL').setAddress(data.addressTo)).getBalance('SolSendTx') + if (!balance || typeof balance.balance === 'undefined' || balance.balance === 0) { + throw new Error('UI_CONFIRM_ADDRESS_TO_EMPTY_BALANCE') + } + } + } + + const tx = new Transaction() + + let seed, stakeAddress = false + try { + const fromPubkey = new PublicKey(data.addressFrom) + if (data.addressTo.indexOf('UNSTAKE') === 0) { + if (data.amount === 'ALL') { + tx.add(StakeProgram.deactivate({ + authorizedPubkey: fromPubkey, + stakePubkey: new PublicKey(data.blockchainData.stakeAddress), + })); + } else { + tx.add(StakeProgram.withdraw({ + authorizedPubkey: fromPubkey, + stakePubkey: new PublicKey(data.blockchainData.stakeAddress), + lamports: data.amount * 1, + toPubkey: fromPubkey + })) + } + } else if (data.addressTo === 'STAKE') { + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' SolTransferProcessor.sendTx ' + data.addressFrom + ' => ' + data.addressTo + ' ' + data.amount + ' build start') + const validator = data.blockchainData.voteAddress + const authorized = new Authorized(fromPubkey, fromPubkey) + if (typeof validator === 'undefined' || !validator) { + throw new Error('no validator field') + } + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' SolTransferProcessor.sendTx ' + data.addressFrom + ' => ' + data.addressTo + ' ' + data.amount + ' build authorized') + + // https://github.com/velas/JsWallet/blob/251ad92bb5c2cd9a62477746a3db934b6dce0c4b/velas/velas-staking.js + // https://explorer.solana.com/tx/2ffmtkj3Yj51ZWCEHG6jb6s78F73eoiQdqURV7z65kSVLiPcm8Y9NE45FgfgwbddJD8kfgCiTpmrEu7J8WKpAQeE + await SolStakeUtils.getAccountStaked(data.addressFrom) + + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' SolTransferProcessor.sendTx ' + data.addressFrom + ' => ' + data.addressTo + ' ' + data.amount + ' build createWithSeed started') + let start = 0 + let lastSeed = await SolTmpDS.getCache(data.addressFrom) + if (typeof lastSeed !== 'undefined' && lastSeed && typeof lastSeed.seed !== 'undefined' && lastSeed.seed) { + start = lastSeed.seed * 1 + } + for (let i = 1; i <= 10000; i++) { + const tmpSeed = (i + start).toString() + + /*try { + stakeAccount = await PublicKey.createWithSeed( + fromPubkey, + tmpSeed, + StakeProgram.programId + ) + } catch (e1) { + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' SolTransferProcessor.sendTx ' + data.addressFrom + ' => ' + data.addressTo + ' ' + data.amount + ' build createWithSeed error ' + e1.message) + }*/ + + const buffer = Buffer.concat([ + fromPubkey.toBuffer(), + Buffer.from(tmpSeed), + StakeProgram.programId.toBuffer(), + ]) + const hash = Buffer.from(await BlocksoftCryptoUtils.sha256(buffer.toString('hex')), 'hex') + const stakeAccount = new PublicKey(Buffer.from(hash, 'hex')) + + stakeAddress = stakeAccount.toBase58() + const isUsed = SolStakeUtils.checkAccountStaked(data.addressFrom, stakeAddress) + if (!isUsed) { + await SolTmpDS.saveCache(data.addressFrom, 'seed', tmpSeed) + seed = tmpSeed + break + } + } + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' SolTransferProcessor.sendTx ' + data.addressFrom + ' => ' + data.addressTo + ' ' + data.amount + ' build createWithSeed finished') + + if (!stakeAddress) { + throw new Error('Stake address seed is not found') + } + + BlocksoftCryptoLog.log(this._settings.currencyCode + ' SolTransferProcessor.sendTx ' + data.addressFrom + ' => ' + data.addressTo + ' ' + data.amount + ' stakeAddress ' + stakeAddress + ' seed ' + seed) + + const amount = data.amount * 1 + tx.add(StakeProgram.createAccountWithSeed({ + authorized, + fromPubkey, + stakePubkey: new PublicKey(stakeAddress), + basePubkey: fromPubkey, + seed, + lamports: amount + })) + + // https://github.com/solana-labs/solana-web3.js/blob/35f0608a8363d3878d045bdb09cdd13af696bc6b/test/transaction.test.ts + tx.add( + StakeProgram.delegate({ + stakePubkey: new PublicKey(stakeAddress), + authorizedPubkey: new PublicKey(data.addressFrom), + votePubkey: new PublicKey(validator) + }) + ) + + } else { + // @ts-ignore + tx.add( + SystemProgram.transfer({ + fromPubkey: new PublicKey(data.addressFrom), + toPubkey: new PublicKey(data.addressTo), + lamports: data.amount * 1 + }) + ) + } + } catch (e) { + if (config.debug.cryptoErrors) { + console.log(this._settings.currencyCode + ' SolTransferProcessor.sendTx ' + data.addressFrom + ' => ' + data.addressTo + ' ' + data.amount + ' build error ') + console.log(e) + } + BlocksoftCryptoLog.log(this._settings.currencyCode + ' SolTransferProcessor.sendTx ' + data.addressFrom + ' => ' + data.addressTo + ' ' + data.amount + ' build error ' + e.message) + this.trxError(e.message) + } + + await SolUtils.signTransaction(tx, privateData.privateKey, data.addressFrom) + + // @ts-ignore + const signedData = tx.serialize().toString('base64') + if (typeof uiData !== 'undefined' && typeof uiData.selectedFee !== 'undefined' && typeof uiData.selectedFee.rawOnly !== 'undefined' && uiData.selectedFee.rawOnly) { + return { rawOnly: uiData.selectedFee.rawOnly, raw : signedData} + } + + BlocksoftCryptoLog.log(this._settings.currencyCode + ' SolTransferProcessor.sendTx ' + data.addressFrom + ' => ' + data.addressTo + ' ' + data.amount, signedData) + + const result = {} as BlocksoftBlockchainTypes.SendTxResult + try { + const sendRes = await SolUtils.sendTransaction(signedData) + BlocksoftCryptoLog.log(this._settings.currencyCode + ' SolTransferProcessor.sendTx ' + data.addressFrom + ' => ' + data.addressTo + ' ' + data.amount, sendRes) + if (typeof sendRes === 'undefined' || !sendRes || typeof sendRes === 'undefined') { + throw new Error('SYSTEM_ERROR') + } + result.transactionHash = sendRes + if (stakeAddress) { + SolStakeUtils.setAccountStaked(data.addressFrom, stakeAddress) + } + if (data.addressTo.indexOf('UNSTAKE') === 0) { + await SolStakeUtils.getAccountStaked(data.addressFrom, true) + } + } catch (e) { + if (config.debug.cryptoErrors) { + console.log(this._settings.currencyCode + ' SolTransferProcessor.sendTx ' + data.addressFrom + ' => ' + data.addressTo + ' ' + data.amount + ' send error ') + console.log(e) + } + BlocksoftCryptoLog.log(this._settings.currencyCode + ' SolTransferProcessor.sendTx ' + data.addressFrom + ' => ' + data.addressTo + ' ' + data.amount + ' send error ' + e.message) + this.trxError(e.message, data.addressTo) + } + return result + } + + + trxError(msg: string, addressTo : string) { + if (msg.indexOf('insufficient funds for instruction') !== -1) { + if (addressTo === 'STAKE') { + throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_STAKE_SOL') + } else { + throw new Error('SERVER_RESPONSE_NOT_ENOUGH_BALANCE_SOL') + } + } else if (msg.indexOf('incorrect program id for instruction') !== -1 && addressTo === 'STAKE') { + throw new Error('SERVER_RESPONSE_NOT_VALIDATOR_STAKE_SOL') + } else { + throw new Error(msg) + } + } +} diff --git a/crypto/blockchains/sol/SolTransferProcessorSpl.ts b/crypto/blockchains/sol/SolTransferProcessorSpl.ts new file mode 100644 index 000000000..6416ea2e8 --- /dev/null +++ b/crypto/blockchains/sol/SolTransferProcessorSpl.ts @@ -0,0 +1,198 @@ +/** + * @version 0.52 + */ + +// eslint-disable-next-line no-unused-vars +import { BlocksoftBlockchainTypes } from '@crypto/blockchains/BlocksoftBlockchainTypes' +import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog' +import BlocksoftBalances from '@crypto/actions/BlocksoftBalances/BlocksoftBalances' + +import { PublicKey, TransactionInstruction, Transaction } from '@solana/web3.js/src/index' +import SolUtils from '@crypto/blockchains/sol/ext/SolUtils' +import SolTransferProcessor from '@crypto/blockchains/sol/SolTransferProcessor' +import SolInstructions from '@crypto/blockchains/sol/ext/SolInstructions' +import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings' + + +export default class SolTransferProcessorSpl extends SolTransferProcessor implements BlocksoftBlockchainTypes.TransferProcessor { + + async getFeeRate(data: BlocksoftBlockchainTypes.TransferData, privateData: BlocksoftBlockchainTypes.TransferPrivateData, additionalData: {} = {}): Promise { + const result: BlocksoftBlockchainTypes.FeeRateResult = { + selectedFeeIndex: -3, + shouldShowFees: false + } as BlocksoftBlockchainTypes.FeeRateResult + + + const destinationAssociatedTokenAddress = await SolUtils.findAssociatedTokenAddress( + data.addressTo, + this._settings.tokenAddress + ) + const destinationAccountInfo = await SolUtils.getAccountInfo(destinationAssociatedTokenAddress) + let feeForTx = BlocksoftExternalSettings.getStatic('SOL_PRICE') // ◎0.000005 + if ( + destinationAccountInfo && typeof destinationAccountInfo.owner !== 'undefined' && destinationAccountInfo.owner === SolUtils.getTokenProgramID() + ) { + // do nothing + } else { + // will create new account + feeForTx = BlocksoftExternalSettings.getStatic('SOL_PRICE_NEW_SPL') // // ◎0.00203928 + 0.000005 = 0.00204428 + } + + result.fees = [ + { + langMsg: 'xrp_speed_one', + feeForTx, + amountForTx: data.amount + } + ] + result.selectedFeeIndex = 0 + + + return result + } + + async getTransferAllBalance(data: BlocksoftBlockchainTypes.TransferData, privateData: BlocksoftBlockchainTypes.TransferPrivateData, additionalData: BlocksoftBlockchainTypes.TransferAdditionalData = {}): Promise { + const balance = data.amount + // @ts-ignore + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' SolTransferProcessorSpl.getTransferAllBalance ', data.addressFrom + ' => ' + balance) + + const fees = await this.getFeeRate(data, privateData, additionalData) + + return { + ...fees, + shouldShowFees: false, + selectedTransferAllBalance: balance + } + } + + /** + * @param data + * @param privateData + * @param uiData + */ + async sendTx(data: BlocksoftBlockchainTypes.TransferData, privateData: BlocksoftBlockchainTypes.TransferPrivateData, uiData: BlocksoftBlockchainTypes.TransferUiData): Promise { + + if (typeof privateData.privateKey === 'undefined') { + throw new Error('SPL transaction required privateKey (derivedSeed)') + } + if (typeof data.addressTo === 'undefined') { + throw new Error('SPL transaction required addressTo') + } + + const sourceAssociatedTokenAddress = await SolUtils.findAssociatedTokenAddress( + data.addressFrom, + this._settings.tokenAddress + ) + const sourceAccountInfo = await SolUtils.getAccountInfo(sourceAssociatedTokenAddress) + if (!sourceAccountInfo || typeof sourceAccountInfo.lamports === 'undefined' || sourceAccountInfo.lamports * 1 === 0) { + throw new Error('Cannot send from address with zero SOL balance') + } + + + + const tx = new Transaction() + let txData = false + let destinationAccountInfo = await SolUtils.getAccountInfo(data.addressTo) + if ( + destinationAccountInfo && typeof destinationAccountInfo.owner !== 'undefined' && destinationAccountInfo.owner === SolUtils.getTokenProgramID() + ) { + txData = { + mint: this._settings.tokenAddress, + decimals: this._settings.decimals, + to: data.addressTo, + amount: data.amount + } + } else { + + if (!destinationAccountInfo || typeof destinationAccountInfo.lamports === 'undefined' || destinationAccountInfo.lamports * 1 === 0) { + throw new Error('SERVER_RESPONSE_RECEIVER_EMPTY_BALANCE') + } + + const destinationAssociatedTokenAddress = await SolUtils.findAssociatedTokenAddress( + data.addressTo, + this._settings.tokenAddress + ) + destinationAccountInfo = await SolUtils.getAccountInfo(destinationAssociatedTokenAddress) + if ( + destinationAccountInfo && typeof destinationAccountInfo.owner !== 'undefined' && destinationAccountInfo.owner === SolUtils.getTokenProgramID() + ) { + // do nothing + } else { + const tmp1 = new TransactionInstruction({ + keys: [{ pubkey: new PublicKey(data.addressTo), isSigner: false, isWritable: false }], + data: SolInstructions.encodeOwnerValidationInstruction({ account: new PublicKey('11111111111111111111111111111111') }), + programId: SolUtils.getOwnerValidationProgramId() + }) + tx.add(tmp1) + + const keys = [ + { pubkey: new PublicKey(data.addressFrom), isSigner: true, isWritable: true }, + { pubkey: new PublicKey(destinationAssociatedTokenAddress), isSigner: false, isWritable: true }, + { pubkey: new PublicKey(data.addressTo), isSigner: false, isWritable: false }, + { pubkey: new PublicKey(this._settings.tokenAddress), isSigner: false, isWritable: false }, + { pubkey: new PublicKey('11111111111111111111111111111111'), isSigner: false, isWritable: false }, + { pubkey: new PublicKey(SolUtils.getTokenProgramID()), isSigner: false, isWritable: false }, + { pubkey: new PublicKey('SysvarRent111111111111111111111111111111111'), isSigner: false, isWritable: false } + ] + const tmp2 = new TransactionInstruction({ + keys, + programId: SolUtils.getAssociatedTokenProgramId(), + data: Buffer.from([]) + }) + tx.add(tmp2) + // add owner + } + txData = { + mint: this._settings.tokenAddress, + decimals: this._settings.decimals, + to: destinationAssociatedTokenAddress, + amount: data.amount + } + } + + BlocksoftCryptoLog.log(this._settings.currencyCode + ' SolTransferProcessorSpl.sendTx ' + data.addressFrom + ' => ' + data.addressTo + ' ' + data.amount, txData) + + + try { + // https://github.com/project-serum/spl-token-wallet/blob/eda316d30bb7be4250dc622e41b6fda6f54ca7d8/src/utils/tokens/instructions.js#L99 + const keys = [ + { pubkey: new PublicKey(sourceAssociatedTokenAddress), isSigner: false, isWritable: true }, + { pubkey: new PublicKey(txData.mint), isSigner: false, isWritable: false }, + { pubkey: new PublicKey(txData.to), isSigner: false, isWritable: true }, + { pubkey: new PublicKey(data.addressFrom), isSigner: true, isWritable: false } + ] + tx.add(new TransactionInstruction({ + keys, + data: SolInstructions.encodeTokenInstructionData({ + transferChecked: { amount: txData.amount, decimals: txData.decimals } + }), + programId: SolUtils.getTokenProgramID() + })) + + } catch (e) { + BlocksoftCryptoLog.log(this._settings.currencyCode + ' SolTransferProcessorSpl.sendTx ' + data.addressFrom + ' => ' + data.addressTo + ' ' + data.amount + ' error ' + e.message) + throw new Error(e.message) + } + + await SolUtils.signTransaction(tx, privateData.privateKey, data.addressFrom) + + // @ts-ignore + const signedData = tx.serialize().toString('base64') + BlocksoftCryptoLog.log(this._settings.currencyCode + ' SolTransferProcessorSpl.sendTx ' + data.addressFrom + ' => ' + data.addressTo + ' ' + data.amount, signedData) + + + const result = {} as BlocksoftBlockchainTypes.SendTxResult + try { + const sendRes = await SolUtils.sendTransaction(signedData) + BlocksoftCryptoLog.log(this._settings.currencyCode + ' SolTransferProcessorSpl.sendTx ' + data.addressFrom + ' => ' + data.addressTo + ' ' + data.amount, sendRes) + if (typeof sendRes === 'undefined' || !sendRes || typeof sendRes === 'undefined') { + throw new Error('SYSTEM_ERROR') + } + result.transactionHash = sendRes + } catch (e) { + BlocksoftCryptoLog.log(this._settings.currencyCode + ' SolTransferProcessorSpl.sendTx ' + data.addressFrom + ' => ' + data.addressTo + ' ' + data.amount + ' send error ' + e.message) + throw e + } + return result + } +} diff --git a/crypto/blockchains/sol/ext/SolInstructions.js b/crypto/blockchains/sol/ext/SolInstructions.js new file mode 100644 index 000000000..b4ce98064 --- /dev/null +++ b/crypto/blockchains/sol/ext/SolInstructions.js @@ -0,0 +1,79 @@ +/** + * @version 0.52 + * https://github.com/project-serum/spl-token-wallet/blob/master/src/utils/tokens/instructions.js + */ +import * as BufferLayout from '@solana/buffer-layout' +import { PublicKey } from '@solana/web3.js/src/index' + +const LAYOUT = BufferLayout.union(BufferLayout.u8('instruction')) +LAYOUT.addVariant( + 0, + BufferLayout.struct([ + BufferLayout.u8('decimals'), + BufferLayout.blob(32, 'mintAuthority'), + BufferLayout.u8('freezeAuthorityOption'), + BufferLayout.blob(32, 'freezeAuthority') + ]), + 'initializeMint' +) +LAYOUT.addVariant(1, BufferLayout.struct([]), 'initializeAccount') +LAYOUT.addVariant( + 7, + BufferLayout.struct([BufferLayout.nu64('amount')]), + 'mintTo' +) +LAYOUT.addVariant( + 8, + BufferLayout.struct([BufferLayout.nu64('amount')]), + 'burn' +) +LAYOUT.addVariant(9, BufferLayout.struct([]), 'closeAccount') +LAYOUT.addVariant( + 12, + BufferLayout.struct([BufferLayout.nu64('amount'), BufferLayout.u8('decimals')]), + 'transferChecked' +) + +const instructionMaxSpan = Math.max( + ...Object.values(LAYOUT.registry).map((r) => r.span) +) + +class PublicKeyLayout extends BufferLayout.Blob { + constructor(property) { + super(32, property) + } + + decode(b, offset) { + return new PublicKey(super.decode(b, offset)) + } + + encode(src, b, offset) { + return super.encode(src.toBuffer(), b, offset) + } +} + +function publicKeyLayout(property) { + return new PublicKeyLayout(property) +} + + +export const OWNER_VALIDATION_LAYOUT = BufferLayout.struct([ + publicKeyLayout('account') +]) + +export default { + + encodeTokenInstructionData(instruction) { + let b = Buffer.alloc(instructionMaxSpan) + let span = LAYOUT.encode(instruction, b) + const res = b.slice(0, span) + return res + }, + + encodeOwnerValidationInstruction(instruction) { + const b = Buffer.alloc(OWNER_VALIDATION_LAYOUT.span) + const span = OWNER_VALIDATION_LAYOUT.encode(instruction, b) + return b.slice(0, span) + } + +} diff --git a/crypto/blockchains/sol/ext/SolStakeUtils.js b/crypto/blockchains/sol/ext/SolStakeUtils.js new file mode 100644 index 000000000..cb402d996 --- /dev/null +++ b/crypto/blockchains/sol/ext/SolStakeUtils.js @@ -0,0 +1,307 @@ +/** + * @version 0.52 + */ +import BlocksoftAxios from '@crypto/common/BlocksoftAxios' +import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings' + +import config from '@app/config/config' +import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog' +import BlocksoftUtils from '@crypto/common/BlocksoftUtils' +import SolUtils from '@crypto/blockchains/sol/ext/SolUtils' + + +const CACHE_STAKED = {} +const CACHE_VOTES = { + data: [], + time: 0 +} +const CACHE_VALID_TIME = 12000000 // 200 minute + +const validatorsConstants = require('@crypto/blockchains/sol/ext/validators') + +const validators = { + time: 0, + data: {} +} +for (const tmp of validatorsConstants) { + validators.data[tmp.id] = tmp +} + + +const init = async () => { + const link = await BlocksoftExternalSettings.get('SOL_VALIDATORS_LIST') + const res = await BlocksoftAxios.get(link) + if (!res.data || typeof res.data[0] === 'undefined' || !res.data[0]) { + return false + } + validators.data = {} + for (const tmp of res.data) { + if (typeof tmp.id === 'undefined') continue + validators.data[tmp.id] = tmp + } + validators.time = new Date().getTime() +} + +export default { + + + // https://docs.solana.com/developing/clients/jsonrpc-api#getvoteaccounts + async getVoteAddresses() { + try { + const now = new Date().getTime() + if (CACHE_VOTES.time && now - CACHE_VOTES.time < CACHE_VALID_TIME) { + return CACHE_VOTES.data + } + if (!validators.time) { + await init() + } + + const apiPath = BlocksoftExternalSettings.getStatic('SOL_SERVER') + const getVote = { 'jsonrpc': '2.0', 'id': 1, 'method': 'getVoteAccounts' } + const resVote = await BlocksoftAxios._request(apiPath, 'POST', getVote) + if (!resVote || typeof resVote.data === 'undefined' || typeof resVote.data.result === 'undefined' || !resVote.data.result || typeof resVote.data.result.current === 'undefined') { + return CACHE_VOTES.data + } + CACHE_VOTES.data = [] + for (const tmp of resVote.data.result.current) { + const address = tmp.votePubkey + if (typeof validators.data[address] === 'undefined') continue + + const validator = { address, commission: tmp.commission, activatedStake: tmp.activatedStake, name: '', description: '', website: '', index : 100 } + validator.index = typeof validators.data[validator.address].index !== 'undefined' ? validators.data[validator.address].index : 0 + validator.name = validators.data[validator.address].name + validator.description = validators.data[validator.address].description + validator.website = validators.data[validator.address].website + + CACHE_VOTES.data.push(validator) + } + CACHE_VOTES.data.sort((a, b) => { + if (a.index*1 === b.index*1) { + const diff = a.commission - b.commission + if (diff <= 0.1 && diff >= -0.1) { + return b.activatedStake - a.activatedStake + } + return diff + } else { + return b.index - a.index + } + }) + CACHE_VOTES.time = now + } catch (e) { + if (config.debug.cryptoErrors) { + console.log('SolStakeUtils.getVoteAddresses error ' + e.message) + } + BlocksoftCryptoLog.log('SolStakeUtils.getVoteAddresses error ' + e.message) + } + return CACHE_VOTES.data + }, + + checkAccountStaked(address, subaddress) { + return typeof CACHE_STAKED[address].all[subaddress] !== 'undefined' + }, + + setAccountStaked(address, subaddress) { + CACHE_STAKED[address].all[subaddress] = true + }, + + // https://prod-api.solana.surf/v1/account/3siLSmroYvPHPZCrK5VYR3gmFhQFWefVGGpasXdzSPnn/stake-rewards + async getAccountRewards(address, stakedAddresses) { + if (typeof CACHE_STAKED[address] === 'undefined') { + CACHE_STAKED[address] = { + all: {}, + active: [], + rewards: [] + } + } + const askAddresses = [ address ] + if (stakedAddresses) { + for(let tmp of stakedAddresses) { + askAddresses.push(tmp.stakeAddress) + } + } + try { + const link = BlocksoftExternalSettings.getStatic('SOL_SERVER') + const data = { + 'method': 'getInflationReward', + 'jsonrpc': '2.0', + 'params': [ + askAddresses, + { + 'commitment': 'confirmed' + } + ], + 'id': '1' + } + + /*console.log(` + + + curl ${link} -X POST -H "Content-Type: application/json" -d '${JSON.stringify(data)}' + + `) + */ + + const res = await BlocksoftAxios.post(link, data) + if (res.data && typeof res.data.result !== 'undefined' && typeof res.data.result[0] !== 'undefined') { + CACHE_STAKED[address].rewards = res.data.result[0] + if (stakedAddresses) { + let count = 0 + if (typeof CACHE_STAKED[address].rewards !== 'undefined') { + count = 100; + } else { + for(let tmp of res.data.result) { + if (tmp && typeof tmp.amount !== 'undefined' && tmp.amount * 1 > 0) { + CACHE_STAKED[address].rewards = tmp + count++ + } + } + } + if (count > 1) { + if (typeof CACHE_STAKED[address].rewards !== 'undefined') { + CACHE_STAKED[address].rewards.amount = 0; + } + for (let tmp of res.data.result) { + if (tmp && typeof tmp.amount !== 'undefined' && tmp.amount * 1 > 0) { + CACHE_STAKED[address].rewards.amount += tmp.amount * 1 + } + } + } + } + } + } catch (e) { + if (config.debug.cryptoErrors) { + console.log('SolStakeUtils.getAccountRewards ' + address + ' error ' + e.message) + } + BlocksoftCryptoLog.log('SolStakeUtils.getAccountRewards ' + address + ' error ' + e.message) + } + //{"amount": 96096, "apr": 7.044036109546499, "effectiveSlot": 99360012, "epoch": 229, "percentChange": 0.05205832165890872, "postBalance": 184689062, "timestamp": 1633153114}, + return CACHE_STAKED[address].rewards + }, + + // https://docs.solana.com/developing/clients/jsonrpc-api#getprogramaccounts + async getAccountStaked(address, isForce = false) { + let accountInfo = false + if (typeof CACHE_STAKED[address] === 'undefined' || isForce) { + CACHE_STAKED[address] = { + all: {}, + active: [], + rewards: [] + } + } + try { + const apiPath = BlocksoftExternalSettings.getStatic('SOL_SERVER') + const currentEpoch = await SolUtils.getEpoch() + + const checkData = { + 'jsonrpc': '2.0', + 'id': 1, + 'method': 'getProgramAccounts', + 'params': [ + 'Stake11111111111111111111111111111111111111', + { + 'encoding': 'jsonParsed', + filters: + [{ + memcmp: { + offset: 0xc, + bytes: address + } + }] + } + ] + } + + /* + console.log(` + + + curl ${apiPath} -X POST -H "Content-Type: application/json" -d '${JSON.stringify(checkData)}' + + `) + */ + let res + accountInfo = [] + try { + res = await BlocksoftAxios._request(apiPath, 'POST', checkData) + + for (const tmp of res.data.result) { + const parsed = tmp.account.data.parsed + const item = { amount: tmp.account.lamports, stakeAddress: tmp.pubkey, reserved: 0, active: true, status: '' } + if (typeof parsed.info !== 'undefined') { + if (typeof parsed.info.meta !== 'undefined') { + if (typeof parsed.info.meta.rentExemptReserve !== 'undefined') { + item.reserved = parsed.info.meta.rentExemptReserve + } + } + const deactivationEpoch = parsed.info.stake.delegation.deactivationEpoch || 0 + const activationEpoch = parsed.info.stake.delegation.activationEpoch || 0 + if (currentEpoch && currentEpoch * 1 >= deactivationEpoch * 1) { + item.order = 1 + item.active = false + item.status = 'inactive' + } else if (currentEpoch && currentEpoch === activationEpoch) { + item.order = 3 + item.status = 'activating' + } else { + item.order = 2 + item.status = 'staked' + } + } + item.diff = BlocksoftUtils.diff(item.amount, item.reserved).toString() + accountInfo.push(item) + CACHE_STAKED[address].all[item.stakeAddress] = true + } + + } catch (e) { + BlocksoftCryptoLog.log('SolStakeUtils getAccountStaked request ' + apiPath + ' error ' + e.message) + + const apiPath2 = 'https://prod-api.solana.surf/v1/account/' + address + '/stakes?limit=10&offset=0' + const res2 = await BlocksoftAxios.get(apiPath2) + + for (const tmp of res2.data.data) { + const item = { amount: tmp.lamports, stakeAddress: tmp.pubkey.address, reserved: 0, active: true, status: '' } + + if (typeof tmp.data !== 'undefined') { + if (typeof tmp.data.meta !== 'undefined') { + if (typeof tmp.data.meta.rent_exempt_reserve !== 'undefined') { + item.reserved = tmp.data.meta.rent_exempt_reserve + } + } + const deactivationEpoch = tmp.data.stake.delegation.deactivation_epoch || 0 + const activationEpoch = tmp.data.stake.delegation.activation_epoch|| 0 + if (currentEpoch && currentEpoch * 1 >= deactivationEpoch * 1) { + item.order = 1 + item.active = false + item.status = 'inactive' + } else if (currentEpoch && currentEpoch === activationEpoch) { + item.order = 3 + item.status = 'activating' + } else { + item.order = 2 + item.status = 'staked' + } + } + item.diff = BlocksoftUtils.diff(item.amount, item.reserved).toString() + accountInfo.push(item) + CACHE_STAKED[address].all[item.stakeAddress] = true + } + } + + accountInfo.sort((a, b) => { + if (b.order === a.order) { + return BlocksoftUtils.diff(b.diff, a.diff) * 1 + } else { + return b.order - a.order + } + }) + CACHE_STAKED[address].active = accountInfo + } catch (e) { + if (config.debug.cryptoErrors) { + console.log('SolStakeUtils.getAccountStaked ' + address + ' error ' + e.message) + } + BlocksoftCryptoLog.log('SolStakeUtils.getAccountStaked ' + address + ' error ' + e.message) + return CACHE_STAKED[address].active + } + return accountInfo + } +} diff --git a/crypto/blockchains/sol/ext/SolUtils.js b/crypto/blockchains/sol/ext/SolUtils.js new file mode 100644 index 000000000..6ac3fa6d9 --- /dev/null +++ b/crypto/blockchains/sol/ext/SolUtils.js @@ -0,0 +1,228 @@ +/** + * @version 0.52 + */ +import BlocksoftAxios from '@crypto/common/BlocksoftAxios' +import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings' + +import { PublicKey } from '@solana/web3.js/src/index' +import { Account } from '@solana/web3.js/src/account' + +import config from '@app/config/config' +import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog' +import settingsActions from '@app/appstores/Stores/Settings/SettingsActions' + +const TOKEN_PROGRAM_ID = 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA' +const ASSOCIATED_TOKEN_PROGRAM_ID = 'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL' +const OWNER_VALIDATION_PROGRAM_ID = '4MNPdKu9wFMvEeZBMt3Eipfs5ovVWTJb31pEXDJAAxX5' + +const CACHE_VALID_TIME = 12000000 // 200 minute + +const CACHE_EPOCH = { + ts: 0, + value: 240 +} + + +export default { + + getTokenProgramID() { + return TOKEN_PROGRAM_ID + }, + + getOwnerValidationProgramId() { + return OWNER_VALIDATION_PROGRAM_ID + }, + + getAssociatedTokenProgramId() { + return ASSOCIATED_TOKEN_PROGRAM_ID + }, + + async findAssociatedTokenAddress(walletAddress, tokenMintAddress) { + try { + const seeds = [ + new PublicKey(walletAddress).toBuffer(), + new PublicKey(TOKEN_PROGRAM_ID).toBuffer(), + new PublicKey(tokenMintAddress).toBuffer() + ] + const res = + await PublicKey.findProgramAddress( + seeds, + new PublicKey( + ASSOCIATED_TOKEN_PROGRAM_ID + ) + ) + return res[0].toBase58() + } catch (e) { + if (config.debug.cryptoErrors) { + console.log('SolUtils.findAssociatedTokenAddress ' + e.message) + } + throw new Error('SYSTEM_ERROR') + } + }, + + async getAccountInfo(address) { + let accountInfo = false + try { + + const apiPath = BlocksoftExternalSettings.getStatic('SOL_SERVER') + const checkData = { + 'jsonrpc': '2.0', + 'id': 1, + 'method': 'getAccountInfo', + 'params': [ + address, + { + 'encoding': 'jsonParsed' + } + ] + } + const res = await BlocksoftAxios._request(apiPath, 'POST', checkData) + accountInfo = res.data.result.value + } catch (e) { + if (config.debug.cryptoErrors) { + console.log('SolUtils.getAccountInfo ' + address + ' error ' + e.message) + } + BlocksoftCryptoLog.log('SolUtils.getAccountInfo ' + address + ' error ' + e.message) + } + return accountInfo + }, + + isAddressValid(value) { + new PublicKey(value) + return true + }, + + /** + * https://docs.solana.com/developing/clients/jsonrpc-api#sendtransaction + * @param raw + * @returns {Promise} + */ + async sendTransaction(raw) { + const sendData = { + 'jsonrpc': '2.0', + 'id': 1, + 'method': 'sendTransaction', + 'params': [ + raw, + { + encoding: 'base64' + } + ] + } + const apiPath = BlocksoftExternalSettings.getStatic('SOL_SERVER') + const apiPath_2 = BlocksoftExternalSettings.getStatic('SOL_SERVER_2') + let try_2 = false + let sendRes + try { + sendRes = await BlocksoftAxios._request(apiPath, 'POST', sendData) + if (!sendRes || typeof sendRes.data === 'undefined') { + if (apiPath_2) { + try_2 = true + } else { + throw new Error('SERVER_RESPONSE_BAD_INTERNET') + } + } + if (typeof sendRes.data.error !== 'undefined' && typeof sendRes.data.error.message !== 'undefined') { + if (sendRes.data.error.message === 'Node is unhealthy') { + try_2 = true + } else { + throw new Error(sendRes.data.error.message) + } + } + } catch (e) { + try_2 = true + } + + if (try_2 && apiPath_2 && apiPath_2 !== apiPath) { + const sendRes_2 = await BlocksoftAxios._request(apiPath_2, 'POST', sendData) + if (!sendRes_2 || typeof sendRes_2.data === 'undefined') { + throw new Error('SERVER_RESPONSE_BAD_INTERNET') + } + if (typeof sendRes_2.data.error !== 'undefined' && typeof sendRes_2.data.error.message !== 'undefined') { + throw new Error(sendRes_2.data.error.message) + } + return sendRes_2.data.result + } + + return sendRes.data.result + }, + + /** + * @returns {Promise<{blockhash: string, feeCalculator: {lamportsPerSignature: number}}>} + */ + async getBlockData() { + const getRecentBlockhashData = { + 'jsonrpc': '2.0', + 'id': 1, + 'method': 'getRecentBlockhash' + } + const apiPath = BlocksoftExternalSettings.getStatic('SOL_SERVER') + const getRecentBlockhashRes = await BlocksoftAxios._request(apiPath, 'POST', getRecentBlockhashData) + return getRecentBlockhashRes.data.result.value + }, + + async getEpoch() { + const now = new Date().getTime() + if (CACHE_EPOCH.ts > 0) { + if (now - CACHE_EPOCH.ts < CACHE_VALID_TIME) { + return CACHE_EPOCH.value + } + } else { + const tmp = settingsActions.getSettings('SOL_epoch') + if (tmp * 1 > CACHE_EPOCH.value) { + CACHE_EPOCH.value = tmp * 1 + } + } + const apiPath = BlocksoftExternalSettings.getStatic('SOL_SERVER') + const getEpoch = { 'jsonrpc': '2.0', 'id': 1, 'method': 'getEpochInfo' } + try { + const resEpoch = await BlocksoftAxios._request(apiPath, 'POST', getEpoch) + const tmp = resEpoch.data.result.epoch * 1 + if (tmp > 0 && tmp !== CACHE_EPOCH.value) { + CACHE_EPOCH.value = tmp + settingsActions.setSettings('SOL_epoch', tmp) + } + CACHE_EPOCH.ts = now + } catch (e) { + BlocksoftCryptoLog.log('SolUtils.getEpoch error ' + e.message) + // nothing + } + return CACHE_EPOCH.value + }, + + async signTransaction(transaction, walletPrivKey, walletPubKey) { + + let account = false + try { + account = new Account(Buffer.from(walletPrivKey, 'hex')) + } catch (e) { + e.message += ' while create account' + throw e + } + + try { + const data = await this.getBlockData() + transaction.recentBlockhash = data.blockhash + } catch (e) { + e.message += ' while getBlockData' + throw e + } + try { + transaction.setSigners( + new PublicKey(walletPubKey) + ) + } catch (e) { + e.message += ' while setSigners' + throw e + } + + try { + transaction.partialSign(account) + } catch (e) { + e.message += ' while transaction.partialSign with account' + throw e + } + + return transaction + } +} diff --git a/crypto/blockchains/sol/ext/validators.js b/crypto/blockchains/sol/ext/validators.js new file mode 100644 index 000000000..bc533dc8b --- /dev/null +++ b/crypto/blockchains/sol/ext/validators.js @@ -0,0 +1,243 @@ +module.exports = [{ + "id": "GA2t11gJcmuZ4y7pShTzgYDkxVaJaVQJqkVUqojhPPsT", + "index": 1, + "name": "SolBrothers", + "description": "", + "website": "" + }, + { + "id": "9QU2QSxhb24FUX3Tu2FpczXjpK3VYrvRudywSZaM29mF", + "index": 2, + "name": "Everstake", + "description": "Everstake is a team of seasoned developers, financial experts and blockchain enthusiasts. We run secure and reliable nodes for PoS protocols", + "website": "https://everstake.one" + }, + { + "id": "2het6nBRLq9LLZER8fqUEk7j5pbLxq2mVGqSse2nS3tf", + "name": "Maggie's Crypto Farm", + "description": "Making the blocks, all day and all night.", + "website": "https://mcf.rocks/" + }, + { + "id": "3r5ZXC1yFqMmk8VwDdUJbEdPmZ8KZvEkzd5ThEYRetTk", + "name": "Vnode", + "description": "We are the staking service provider for blockchain projects.", + "website": "" + }, + { + "id": "49DJjUX3cwFvaZD5rCAwubiz7qdRWDez9xmB381XdHru", + "name": "Staker Space", + "description": "Secure Validating Services", + "website": "https://staker.space" + }, + { + "id": "4PsiLMyoUQ7QRn1FFiFCvej4hsUTFzfvJnyN4bj1tmSN", + "name": "Stakin", + "description": "Your Trusted Crypto Rewards", + "website": "https://stakin.com/" + }, + { + "id": "51JBzSTU5rAM8gLAVQKgp4WoZerQcSqWC7BitBzgUNAm", + "name": "Staked", + "description": "Staked operates highly reliable and secure staking infrastructure for 20+ PoS protocols on behalf of the leading investors in the market.", + "website": "https://staked.us" + }, + { + "id": "6UDU4Z9TTbYy8gcRKBd7RX3Lm2qMsSR4PMuzoyYPzLma", + "name": "dcipher", + "description": "dCipher is a company that has the purpose to combine emerging technologies like Blockchain, IoT and Artificial Intelligence.", + "website": "https://dcipher.io" + }, + { + "id": "76nwV8zz8tLz97SBRXH6uwHvgHXtqJDLQfF66jZhQ857", + "name": "Forbole Limited", + "description": "We are a pioneer in blockchain technology and UX solution. We provide enterprise-level network infrastructure and software development.", + "website": "https://forbole.com" + }, + { + "id": "7Hs9z4qsGCbQE9cy2aqgsvWupeZZGiKJgeb1eG4ZKYUH", + "name": "Kytzu", + "description": "Tendermint tech consultant and developer", + "website": "http://kytzu.com/" + }, + { + "id": "7PmWxxiTneGteGxEYvzj5pGDVMQ4nuN9DfUypEXmaA8o", + "name": "Syncnode SRL", + "description": "Syncnode is a software development and cyber security company with global presence and headquarters in Switzerland and Romania.", + "website": "https://wallet.syncnode.ro/" + }, + { + "id": "7VGU4ZwR1e1AFekqbqv2gvjeg47e1PwMPm4BfLt6rxNk", + "name": "stake.fish", + "description": "We are the leading staking service provider for blockchain projects. Join our community to stake and earn rewards. We know staking.", + "website": "https://stake.fish/" + }, + { + "id": "9GJmEHGom9eWo4np4L5vC6b6ri1Df2xN8KFoWixvD1Bs", + "name": "BL", + "description": "", + "website": "" + }, + { + "id": "9iEFfC6qCU6DBTWxL84YQdpNmpZ9yBBu4sW62yeiEVKe", + "name": "Izo Data Network", + "description": "If we could describe the business relationship we have with our clients in one word, it would be TRUST.", + "website": "http://www.izo.ro/" + }, + { + "id": "9sWYTuuR4s12Q4SuSfo5CfWaFggQwA6Z8pf8dWowN5rk", + "name": "Ubik Capital", + "description": "Ubik Capital's team priority is to provide a highly resilient and secure validator for the Solana community.", + "website": "https://ubik.capital/" + }, + { + "id": "9tedbEYypEKXAMkHcg42rn3fXY1B8hB6cdE3ZTFouXLL", + "name": "Stake Systems", + "description": "Building infrastructure to support awesome projects running in the blockchain landscape", + "website": "https://stake.systems" + }, + { + "id": "9v5gci7uDiaGKRmQ2dn6WJMB94YqFaVFBTiFzBzNhyaw", + "name": "Inotel", + "description": "", + "website": "" + }, + { + "id": "AGXZemZbyZjz5NBhufcob2pf8AXnr9HaGFUGNCfooWrB", + "name": "RockX", + "description": "RockX is a professional digital asset platform that aims to bring crypto investing to the mainstream in the smartest way.", + "website": "https://rockx.com/" + }, + { + "id": "ateamaZDqNWDztxnVKZhRsp4ac53KvT1rVKyU5LnL6o", + "name": "Node A-Team", + "description": "", + "website": "https://nodeateam.com/" + }, + { + "id": "beefKGBWeSpHzYBHZXwp5So7wdQGX6mu4ZHCsH3uTar", + "name": "Bison Trails", + "description": "We provide secure and reliable enterprise-grade infrastructure with multi-cloud / region distribution and a 99% node uptime guarantee", + "website": "https://bisontrails.co/" + }, + { + "id": "BH7asDZbKkTmT3UWiNfmMVRgQEEpXoVThGPmQfgWwDhg", + "name": "01Node", + "description": "01node | Professional Staking Services for Cosmos, Iris, Terra, Solana, Kava, Polkadot, Skale", + "website": "https://01node.com" + }, + { + "id": "BxFf75Vtzro2Hy3coFHKxFMZo5au8W7J8BmLC3gCMotU", + "name": "Chainode", + "description": "Chainode Tech is your reliable partner for distributed ledger technology (DLT) validation services and blockchain consultancy.", + "website": "https://chainode.tech/" + }, + { + "id": "CAf8jfgqhia5VNrEF4A7Y9VLD3numMq9DVSceq7cPhNY", + "name": "Chainflow", + "description": "Chainflow's a small, independent, capable & dedicated validator operator, started in 2017 by @cjremus, working to keep stake decentralized.", + "website": "https://chainflow.io/staking" + }, + { + "id": "CcaHc2L43ZWjwCHART3oZoJvHLAe9hzT2DJNUpBzoTN1", + "name": "Figment Networks", + "description": "", + "website": "https://figment.network/" + }, + { + "id": "CertusDeBmqN8ZawdkxK5kFGMwBXdudvWHYwtNgNhvLu", + "name": "Certus One", + "description": "", + "website": "https://certus.one" + }, + { + "id": "Chorus6Kis8tFHA7AowrPMcRJk3LbApHTYpgSNXzY5KE", + "name": "Chorus One", + "description": "Secure Solana and shape its future by delegating to Chorus One, a leading provider of validation infrastructure and staking services.", + "website": "https://chorus.one" + }, + { + "id": "CRzMxdyS56N2vkb55X5q155sSdVkjZhiFedWcbscCf7K", + "name": "HashQuark", + "description": "HashQuark is a new generation Proof-of-Stake pool focused on POS, DPOS and other public chains.", + "website": "https://hashquark.io/" + }, + { + "id": "D3DfFvmLBKkX9JJNEpJRXpM1pYTVPQ5dpPQRc9F49xk4", + "name": "Easy 2 Stake", + "description": "Professional PoS staking services.", + "website": "https://easy2stake.com/" + }, + { + "id": "DQ7D6ZRtKbBSxCcAunEkoTzQhCBKLPdzTjPRRnM6wo1f", + "name": "Stakewolf", + "description": "", + "website": "" + }, + { + "id": "DumiCKHVqoCQKD8roLApzR5Fit8qGV5fVQsJV9sTZk4a", + "name": "Staking Facilities", + "description": "Staking Facilities is a Munich-based staking service provider, who operates institutional-grade, custom built validator architecture.", + "website": "https://stakingfacilities.com/" + }, + { + "id": "edu1fZt5i82cFm6ujUoyXLMdujWxZyWYC8fkydWHRNT", + "name": "Solstaking", + "description": "Specialized in Solana Staking", + "website": "https://solstaking.com/" + }, + { + "id": "FKsC411dik9ktS6xPADxs4Fk2SCENvAiuccQHLAPndvk", + "name": "P2P.ORG - P2P Validator", + "description": "Secure Non-Custodial Staking", + "website": "https://p2p.org/" + }, + { + "id": "GB44NXtM7zGm6QnzQjzHZcRKSswkJbox8aJsKiXGbFJr", + "name": "Rustiq Technology", + "description": "", + "website": "" + }, + { + "id": "GhKEDkvGkf2kceG45ppzqnPD6BPXi1PyW1xGNWJdh5QW", + "name": "Stefan Condurachi", + "description": "I build & design intuitive blockchain tools.", + "website": "http://stefancondurachi.com/" + }, + { + "id": "GMpKrAwQ9oa4sJqEYQezLr8Z2TUAU72tXD4iMyfoJjbh", + "name": "Moonlet", + "description": "Moonlet operates nodes for next-generation blockchain networks and is committed to support the decentralisation movement forward.", + "website": "https://moonlet.xyz/" + }, + { + "id": "H3GhqPMwvGLdxWg3QJGjXDSkFSJCsFk3Wx9XBTdYZykc", + "name": "DokiaCapital", + "description": "We operate an enterprise-grade infrastructure that is robust and secure. Downtime is not an option for us. Stake now and manage your reward.", + "website": "https://staking.dokia.cloud/" + }, + { + "id": "LunaFpQkZsZVJL2P2BUqNDJqyVYqrw9buQnjQtMLXdK", + "name": "LunaNova Technologies", + "description": "Experienced team runs Solana-optimised, monitored hardware in top-tier UK datacentres - uptime guaranteed - 10% of our profits to charity", + "website": "https://lunanova.tech/" + }, + { + "id": "SFund7s2YPS7iCu7W2TobbuQEpVEAv9ZU7zHKiN1Gow", + "name": "Staking Fund", + "description": "", + "website": "https://staking.fund" + }, + { + "id": "D3DfFvmLBKkX9JJNEpJRXpM1pYTVPQ5dpPQRc9F49xk4", + "name": "Easy 2 Stake", + "description": "Easy.Stake.Trust. As easy and as simple as you would click next. Complete transparency and trust with a secure and stable validator.", + "website": "https://easy2stake.com" + }, + { + "id": "EFEKcHrUBsRoQkuTSJAQNZnj1u8h9oE4LoCYerednc3F", + "name": "Genesis Lab", + "description": "Genesis Lab is a validation nodes operator in PoS networks and blockchain-focused software development company", + "website": "https://genesislab.net" + } +] diff --git a/crypto/blockchains/sol/stores/SolTmpDS.js b/crypto/blockchains/sol/stores/SolTmpDS.js new file mode 100644 index 000000000..7d1449644 --- /dev/null +++ b/crypto/blockchains/sol/stores/SolTmpDS.js @@ -0,0 +1,54 @@ +/** + * @version 0.52 + */ +import Database from '@app/appstores/DataSource/Database'; + +const tableName = ' transactions_scanners_tmp' + +class SolTmpDS { + /** + * @type {string} + * @private + */ + _currencyCode = 'SOL' + + async getCache(address) { + const sql = ` + SELECT tmp_key, tmp_val + FROM ${tableName} + WHERE currency_code='${this._currencyCode}' + AND address='${address}' + ` + const res = await Database.query(sql) + const tmp = {} + if (res.array) { + for (const row of res.array) { + tmp[row.tmp_key] = row.tmp_val + } + } + return tmp + } + + async saveCache(address, key, value) { + const now = new Date().toISOString() + const prepared = [{ + currency_code : this._currencyCode, + address : address, + tmp_key : key, + tmp_val : value, + created_at : now + }] + await Database.setTableName(tableName).setInsertData({insertObjs : prepared}).insert() + } + + async updateCache(address, key, value) { + const sql = ` + UPDATE ${tableName} SET tmp_val='${value}' + WHERE address='${address}' AND tmp_key='${key}' + AND currency_code='${this._currencyCode}' + ` + await Database.query(sql) + } +} + +export default new SolTmpDS() diff --git a/crypto/blockchains/trx/TrxAddressProcessor.js b/crypto/blockchains/trx/TrxAddressProcessor.js new file mode 100644 index 000000000..b5971a854 --- /dev/null +++ b/crypto/blockchains/trx/TrxAddressProcessor.js @@ -0,0 +1,22 @@ +/** + * @version 0.5 + */ +import TronUtils from './ext/TronUtils' + +export default class TrxAddressProcessor { + + async setBasicRoot(root) { + + } + /** + * @param {string|Buffer} privateKey + * @param {*} data + * @returns {Promise<{privateKey: string, address: string, addedData: *}>} + */ + async getAddress(privateKey, data = {}) { + const pubKey = TronUtils.privHexToPubHex(privateKey) + const addressHex = TronUtils.pubHexToAddressHex(pubKey) + const address = TronUtils.addressHexToStr(addressHex) + return { address, privateKey : privateKey.toString('hex'), addedData: {addressHex, pubKey} } + } +} diff --git a/crypto/blockchains/trx/TrxScannerProcessor.js b/crypto/blockchains/trx/TrxScannerProcessor.js new file mode 100644 index 000000000..dada23ab6 --- /dev/null +++ b/crypto/blockchains/trx/TrxScannerProcessor.js @@ -0,0 +1,301 @@ +/** + * @version 0.5 + * https://github.com/tronscan/tronscan-frontend/wiki/TRONSCAN-API + */ +import TronUtils from './ext/TronUtils' +import TrxTronscanProvider from './basic/TrxTronscanProvider' +import TrxTrongridProvider from './basic/TrxTrongridProvider' +import TrxTransactionsProvider from './basic/TrxTransactionsProvider' +import TrxTransactionsTrc20Provider from './basic/TrxTransactionsTrc20Provider' +import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog' +import Database from '@app/appstores/DataSource/Database/main' +import BlocksoftAxios from '@crypto/common/BlocksoftAxios' +import config from '@app/config/config' +import BlocksoftUtils from '@crypto/common/BlocksoftUtils' +import transactionDS from '@app/appstores/DataSource/Transaction/Transaction' +import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings' +import TronStakeUtils from '@crypto/blockchains/trx/ext/TronStakeUtils' + + +let CACHE_PENDING_TXS = false + +export default class TrxScannerProcessor { + + constructor(settings) { + this._settings = settings + this._tokenName = '_' + if (typeof settings.tokenName !== 'undefined') { + this._tokenName = settings.tokenName + } + this._tronscanProvider = new TrxTronscanProvider() + this._trongridProvider = new TrxTrongridProvider() + this._transactionsProvider = new TrxTransactionsProvider() + this._transactionsTrc20Provider = new TrxTransactionsTrc20Provider() + } + + async isMultisigBlockchain(address) { + address = address.trim() + let addressHex = address + if (address.substr(0, 1) === 'T') { + addressHex = await TronUtils.addressToHex(address) + } + return this._trongridProvider.isMultisigTrongrid(addressHex) + } + + /** + * https://developers.tron.network/reference#addresses-accounts + * @param {string} address + * @return {Promise<{balance, frozen, frozenEnergy, balanceStaked, unconfirmed, provider}>} + */ + async getBalanceBlockchain(address, jsonData, walletHash, source) { + address = address.trim() + BlocksoftCryptoLog.log(this._tokenName + ' TrxScannerProcessor getBalanceBlockchain address ' + address + ' from ' + source) + let addressHex = address + if (address.substr(0, 1) === 'T') { + addressHex = await TronUtils.addressToHex(address) + } else { + address = await TronUtils.addressHexToStr(addressHex) + } + const useTronscan = BlocksoftExternalSettings.getStatic('TRX_USE_TRONSCAN') * 1 > 0 + let result = false + let subresult = false + if (useTronscan) { + result = await this._tronscanProvider.get(address, this._tokenName, source === 'AccountScreen') + BlocksoftCryptoLog.log(this._tokenName + ' TrxScannerProcessor getBalanceBlockchain address ' + address + ' result tronScan ' + JSON.stringify(result) + ' from ' + source) + } + + if (result === false || result === 0) { + if (this._tokenName !== '_' && this._tokenName.substr(0, 1) === 'T') { + // https://developers.tron.network/docs/trc20-contract-interaction#balanceof + try { + const sendLink = BlocksoftExternalSettings.getStatic('TRX_SEND_LINK') + const params = { + "contract_address": await TronUtils.addressToHex(this._tokenName), + "function_selector": "balanceOf(address)", + "parameter": "0000000000000000000000" + addressHex, + "owner_address": addressHex + } + const tmp = await BlocksoftAxios.post(sendLink + '/wallet/triggerconstantcontract', params) + if (typeof tmp.data !== 'undefined' && typeof tmp.data.constant_result !== 'undefined') { + BlocksoftCryptoLog.log(this._tokenName + ' TrxScannerProcessor getBalanceBlockchain address ' + address + ' result tronwallet ' + JSON.stringify(tmp.data) + ' from ' + source) + return { balance: BlocksoftUtils.hexToDecimal('0x' + tmp.data.constant_result), unconfirmed: 0, provider: 'tronwallet-raw-call' } + } + } catch (e) { + BlocksoftCryptoLog.log(this._tokenName + ' TrxScannerProcessor getBalanceBlockchain address ' + address + ' error tronwallet ' + e.message) + } + result = await this._tronscanProvider.get(address, this._tokenName) + } else { + result = await this._trongridProvider.get(addressHex, this._tokenName, source === 'AccountScreen') + } + BlocksoftCryptoLog.log(this._tokenName + ' TrxScannerProcessor getBalanceBlockchain address ' + address + ' result tronGrid ' + JSON.stringify(result) + ' from ' + source) + } + + if (result === false && this._tokenName !== '_') { + + subresult = await this._tronscanProvider.get(address, '_', source === 'AccountScreen') + BlocksoftCryptoLog.log(this._tokenName + ' TrxScannerProcessor getBalanceBlockchain address ' + address + ' result tronScan2 ' + JSON.stringify(result) + ' from ' + source) + + if (subresult !== false) { + BlocksoftCryptoLog.log(this._tokenName + ' TrxScannerProcessor getBalanceBlockchain address ' + address + ' subresult tronScan ' + JSON.stringify(subresult) + ' from ' + source) + return { balance: 0, unconfirmed: 0, balanceStaked : 0, balanceAvailable : 0, provider: 'tronscan-ok-but-no-token' } + } + } + result.balanceStaked = typeof result.frozen !== 'undefined' ? (result.frozen * 1 + result.frozenEnergy * 1) : 0 + result.balanceAvailable = result.balance + if (result.balanceStaked * 1 > 0) { + result.balance = result.balance * 1 + result.balanceStaked * 1 + } + return result + } + + + /** + * @param {string} address + * @return {Promise<*>} + */ + async getResourcesBlockchain(address) { + address = address.trim() + BlocksoftCryptoLog.log(this._tokenName + ' TrxScannerProcessor getResourcesBlockchain address ' + address) + let addressHex = address + if (address.substr(0, 1) === 'T') { + addressHex = await TronUtils.addressToHex(address) + } + const result = await this._trongridProvider.getResources(addressHex, this._tokenName) + return result + } + + /** + * https://github.com/jakeonchain/tron-wallet-chrome/blob/fecea42771cc5cbda3fada4a1c8cfe8de251c008/src/App.js + * @param {string} scanData.account.address + * @return {Promise<[UnifiedTransaction]>} + */ + async getTransactionsBlockchain(scanData, source) { + let result + let lastBlock = false + if (this._tokenName[0] === 'T') { + this._transactionsTrc20Provider.setLink(this._tokenName) + result = await this._transactionsTrc20Provider.get(scanData, this._tokenName) + lastBlock = this._transactionsTrc20Provider._lastBlock + } else { + result = await this._transactionsProvider.get(scanData, this._tokenName) + lastBlock = this._transactionsProvider._lastBlock + } + await this.getTransactionsPendingBlockchain(scanData, source, lastBlock) + return result + } + + async resetTransactionsPendingBlockchain(scanData, source, lastBlock = false) { + CACHE_PENDING_TXS = scanData.resetTime || 0 + if (typeof scanData.specialActionNeeded !== 'undefined' && scanData.specialActionNeeded && typeof scanData.account.address !== 'undefined') { + await Database.query(` + UPDATE transactions SET special_action_needed='' + WHERE special_action_needed='${scanData.specialActionNeeded}' + AND address_from_basic='${scanData.account.address}' + `) + } + return false + } + + async getTransactionsPendingBlockchain(scanData, source, lastBlock = false) { + if (CACHE_PENDING_TXS > 0 && CACHE_PENDING_TXS - new Date().getTime() < 60000) { + return false + } + // id, transaction_hash, block_number, block_confirmations, transaction_status, + const sql = `SELECT t.id, + t.wallet_hash AS walletHash, + t.transaction_hash AS transactionHash, + t.transactions_scan_log AS transactionsScanLog, + t.address_from_basic AS addressFromBasic, + t.special_action_needed AS specialActionNeeded, + a.derivation_path AS derivationPath + FROM transactions AS t + LEFT JOIN account AS a ON a.address = t.address_from_basic + WHERE + (t.currency_code='${this._settings.currencyCode}' OR t.currency_code LIKE 'TRX%') + AND t.transaction_of_trustee_wallet=1 + AND (t.block_number IS NULL OR t.block_number<20 OR t.special_action_needed='vote' OR t.special_action_needed='vote_after_unfreeze') + + ORDER BY created_at DESC + LIMIT 10 + ` + const res = await Database.query(sql) + if (!res || typeof res.array === 'undefined' || !res.array || res.array.length === 0) { + CACHE_PENDING_TXS = new Date().getTime() + return false + } + + const sendLink = BlocksoftExternalSettings.getStatic('TRX_SEND_LINK') + let needUpdateBalance = -1 + if (lastBlock === false) { + needUpdateBalance = 0 + try { + const link2 = sendLink + '/wallet/getnowblock' + const block = await BlocksoftAxios.get(link2) + if (typeof block !== 'undefined' && block && typeof block.data !== 'undefined') { + lastBlock = block.data.block_header.raw_data.number + } + } catch (e1) { + if (config.debug.cryptoErrors) { + console.log(this._settings.currencyCode + ' TrxScannerProcessor.getTransactionsPendingBlockchain lastBlock', e1) + } + } + } + + const unique = {} + for (const row of res.array) { + const linkRecheck = sendLink + '/wallet/gettransactioninfobyid' + try { + const recheck = await BlocksoftAxios.post(linkRecheck, { + value: row.transactionHash + }) + if (typeof recheck.data !== 'undefined') { + const isSuccess = await this._unifyFromReceipt(recheck.data, row, lastBlock) + if (isSuccess && needUpdateBalance === 0) { + needUpdateBalance = 1 + } + if (isSuccess && row.specialActionNeeded && row.addressFromBasic) { + row.confirmations = lastBlock - recheck.data.blockNumber + if (typeof unique[row.addressFromBasic] === 'undefined') { + unique[row.addressFromBasic] = row + } else { + if (unique[row.addressFromBasic].confirmations > row.confirmations) { + unique[row.addressFromBasic].confirmations = row.confirmations + } + if (unique[row.addressFromBasic].specialActionNeeded === 'vote_after_unfreeze') { + unique[row.addressFromBasic].specialActionNeeded = row.specialActionNeeded + } + } + + } + } + } catch (e1) { + if (config.debug.cryptoErrors) { + console.log(this._settings.currencyCode + ' TrxScannerProcessor.getTransactionsPendingBlockchain recheck', e1) + } + } + } + + if (unique) { + for (const address in unique) { + const {walletHash, derivationPath, confirmations, specialActionNeeded } = unique[address] + if (confirmations < 20) { + BlocksoftCryptoLog.log(this._settings.currencyCode + ' TrxScannerProcessor.getTransactionsPendingBlockchain vote all skipped by ' + confirmations + ' for ' + address) + continue + } + + BlocksoftCryptoLog.log(this._settings.currencyCode + ' TrxScannerProcessor.getTransactionsPendingBlockchain vote all inited for ' + address + ' action ' + specialActionNeeded) + try { + if (await TronStakeUtils.sendVoteAll(address, derivationPath, walletHash, specialActionNeeded)) { + await Database.query(` + UPDATE transactions SET special_action_needed='' WHERE special_action_needed='vote' OR special_action_needed='vote_after_unfreeze' + AND address_from_basic='${address}' + `) + BlocksoftCryptoLog.log(this._settings.currencyCode + ' TrxScannerProcessor.getTransactionsPendingBlockchain vote all finished for ' + address) + } + } catch (e) { + if (config.debug.cryptoErrors) { + console.log(this._settings.currencyCode + ' TrxScannerProcessor.getTransactionsPendingBlockchain vote all error for ' + address + ' ' + e.message) + } + BlocksoftCryptoLog.log(this._settings.currencyCode + ' TrxScannerProcessor.getTransactionsPendingBlockchain vote all error for ' + address + ' ' + e.message) + } + } + } + + return needUpdateBalance > 0 + } + + async _unifyFromReceipt(transaction, row, lastBlock) { + /** + * {"id":"fb7580e4bb6161e0812beb05cf4a1b6463ba55e33def5dd7f3f5c1561c91a49e","blockNumber":29134019,"blockTimeStamp":1617823467000, + * "receipt":{'origin_energy_usage":4783,"energy_usage_total":4783,"net_usage":345,"result":"OUT_OF_ENERGY'}, + * "result":"FAILED" + */ + if (typeof transaction.blockNumber === 'undefined' || transaction.blockNumber * 1 <= 1) return false + + let transactionStatus = 'success' + if (typeof transaction.result !== 'undefined' && transaction.result === 'FAILED') { + transactionStatus = 'fail' + if (typeof transaction.receipt !== 'undefined' && typeof transaction.receipt.result !== 'undefined') { + if (transaction.receipt.result === 'OUT_OF_ENERGY') { + transactionStatus = 'out_of_energy' + } + } + } + let formattedTime + try { + formattedTime = BlocksoftUtils.toDate(transaction.blockTimeStamp / 1000) + } catch (e) { + e.message += ' timestamp error transaction2 data ' + JSON.stringify(transaction) + throw e + } + + await transactionDS.saveTransaction({ + blockNumber: transaction.blockNumber, + blockTime: formattedTime, + blockConfirmations: lastBlock - transaction.blockNumber, + transactionStatus, + transactionsScanLog: new Date().toISOString() + ' RECEIPT RECHECK ' + JSON.stringify(transaction) + ' ' + row.transactionsScanLog + }, row.id, 'receipt') + return transactionStatus === 'success' + } +} diff --git a/crypto/blockchains/trx/TrxTokenProcessor.js b/crypto/blockchains/trx/TrxTokenProcessor.js new file mode 100644 index 000000000..4770bdddc --- /dev/null +++ b/crypto/blockchains/trx/TrxTokenProcessor.js @@ -0,0 +1,55 @@ +/** + * @version 0.5 + * https://apilist.tronscan.org/api/contract?contract=TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t + * [ { address: 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t', balance: 7208332710, verify_status: 0, balanceInUsd: 0, trxCount: 758742, date_created: 1555400628000, creator: [Object] } ] } + */ +import BlocksoftAxios from '../../common/BlocksoftAxios' + +export default class TrxTokenProcessor { + constructor() { + this._tokenTronscanPath20 = 'https://apilist.tronscan.org/api/token_trc20?contract=' + this._tokenTronscanPath10 = 'https://apilist.tronscan.org/api/token?id=' + } + + /** + * https://apilist.tronscan.org/api/token_trc20?contract=TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t + * @param {string} tokenAddress + * @returns {Promise<{tokenAddress: *, currencyName: *, provider: string, tokenDecimals: *, icon: *, description: *, tokenType: string, currencyCode: *}|boolean>} + */ + async getTokenDetails(tokenAddress) { + if (tokenAddress[0] === 'T') { + const res = await BlocksoftAxios.get(this._tokenTronscanPath20 + tokenAddress) + if (typeof res.data.trc20_tokens[0] !== 'undefined') { + const tmp = res.data.trc20_tokens[0] + return { + currencyCodePrefix : 'CUSTOM_TRX_', + currencyCode: tmp.symbol, + currencyName: tmp.name, + tokenType : 'TRX', // 'TRX' + tokenAddress: tmp.contract_address, + tokenDecimals: tmp.decimals, + icon: tmp.icon_url, + description: tmp.token_desc, + provider : 'tronscan20' + } + } + } else { + const res = await BlocksoftAxios.get(this._tokenTronscanPath10 + tokenAddress) + if (typeof res.data.data[0] !== 'undefined') { + const tmp = res.data.data[0] + return { + currencyCodePrefix : 'CUSTOM_TRX_', + currencyCode: tmp.abbr, + currencyName: tmp.name, + tokenType : 'TRX', // 'TRX' + tokenAddress: tmp.tokenID, + tokenDecimals: tmp.precision, + icon: tmp.imgUrl, + description: tmp.description, + provider : 'tronscan10' + } + } + } + return false + } +} diff --git a/crypto/blockchains/trx/TrxTransferProcessor.ts b/crypto/blockchains/trx/TrxTransferProcessor.ts new file mode 100644 index 000000000..6b7a4ddf3 --- /dev/null +++ b/crypto/blockchains/trx/TrxTransferProcessor.ts @@ -0,0 +1,634 @@ +/** + * @version 0.20 + */ +import BlocksoftAxios from '../../common/BlocksoftAxios' +import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog' +import BlocksoftUtils from '../../common/BlocksoftUtils' +import { BlocksoftBlockchainTypes } from '../BlocksoftBlockchainTypes' + +import TronUtils from './ext/TronUtils' +import TrxTronscanProvider from './basic/TrxTronscanProvider' +import TrxTrongridProvider from './basic/TrxTrongridProvider' +import TrxSendProvider from '@crypto/blockchains/trx/providers/TrxSendProvider' + + +import BlocksoftDispatcher from '../BlocksoftDispatcher' +import config from '@app/config/config' +import { strings, sublocale } from '@app/services/i18n' + +import settingsActions from '@app/appstores/Stores/Settings/SettingsActions' +import MarketingEvent from '@app/services/Marketing/MarketingEvent' +import BlocksoftTransactions from '@crypto/actions/BlocksoftTransactions/BlocksoftTransactions' +import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings' +import BlocksoftBalances from '@crypto/actions/BlocksoftBalances/BlocksoftBalances' + +// https://developers.tron.network/docs/parameter-and-return-value-encoding-and-decoding +const ethers = require('ethers') +const ADDRESS_PREFIX_REGEX = /^(41)/ +const AbiCoder = ethers.utils.AbiCoder +const PROXY_FEE = 'https://proxy.trustee.deals/trx/countFee' + +export default class TrxTransferProcessor implements BlocksoftBlockchainTypes.TransferProcessor { + private _settings: any + private _tronscanProvider: TrxTronscanProvider + private _trongridProvider: TrxTrongridProvider + private _tokenName: string + private _isToken20: boolean + private sendProvider: TrxSendProvider + + constructor(settings: any) { + this._settings = settings + this._tronscanProvider = new TrxTronscanProvider() + this._trongridProvider = new TrxTrongridProvider() + this._tokenName = '_' + this._isToken20 = false + if (typeof settings.tokenName !== 'undefined') { + this._tokenName = settings.tokenName + if (this._tokenName[0] === 'T') { + this._isToken20 = true + } + } + this.sendProvider = new TrxSendProvider(this._settings, 'TRX') + } + + needPrivateForFee(): boolean { + return false + } + + checkSendAllModal(data: { currencyCode: any }): boolean { + return false + } + + async checkTransferHasError(data: BlocksoftBlockchainTypes.CheckTransferHasErrorData): Promise { + // @ts-ignore + if (!this._isToken20 || data.amount && data.amount * 1 > 0) { + return { isOk: true } + } + /** + * @type {TrxScannerProcessor} + */ + const balanceProvider = BlocksoftDispatcher.getScannerProcessor(this._settings.currencyCode) + const balanceRaw = await balanceProvider.getBalanceBlockchain(data.addressTo) + if (balanceRaw && typeof balanceRaw.balance !== 'undefined' && balanceRaw.balance > 0) { + return { isOk: true } + } + + const balanceProviderBasic = BlocksoftDispatcher.getScannerProcessor('TRX') + const balanceRawBasic = await balanceProviderBasic.getBalanceBlockchain(data.addressTo) + if (balanceRawBasic && typeof balanceRawBasic.balance !== 'undefined' && balanceRawBasic.balance > 0) { + return { isOk: true } + } + + const transactionsBasic = await balanceProviderBasic.getTransactionsBlockchain({ account: { address: data.addressTo } }) + if (transactionsBasic !== false) { + return { isOk: true } + } + return { isOk: false, code: 'TRX_20', address: data.addressTo } + } + + async getFeeRate(data: BlocksoftBlockchainTypes.TransferData, privateData: BlocksoftBlockchainTypes.TransferPrivateData, additionalData: {} = {}): Promise { + const addressHexTo = TronUtils.addressToHex(data.addressTo) + if (TronUtils.addressHexToStr(addressHexTo) !== data.addressTo) { + BlocksoftCryptoLog.log('TrxTransferProcessor.getFeeRateOld check address ' + data.addressTo + ' hex ' + addressHexTo + ' => ' + TronUtils.addressHexToStr(addressHexTo)) + throw new Error('TRX SYSTEM ERROR - Please check address ' + data.addressTo) + } + + try { + const link = PROXY_FEE + '?from=' + data.addressFrom + '&fromHex=' + TronUtils.addressToHex(data.addressFrom) + '&to=' + data.addressTo + '&toHex=' + addressHexTo + + '&token=' + this._tokenName + '&tokenHex=' + (this._isToken20 ? TronUtils.addressToHex( this._tokenName) : '') + + '&amount=' + data.amount + '&isTransferAll=' + (data.isTransferAll ? 1 : 0) + let res = false + try { + res = await BlocksoftAxios.get(link) + } catch (e) { + throw new Error('no proxy fee for ' + link) + } + res = res.data + if (config.debug.cryptoErrors) { + console.log(this._settings.currencyCode + ' TrxTransferProcessor.getFeeRate ' + link + ' res ', res) + } + BlocksoftCryptoLog.log(this._settings.currencyCode + ' TrxTransferProcessor.getFeeRate ' + link + ' res ', res) + if (typeof res.feeForTx === 'undefined') { + throw new Error('no res?.feeForTx') + } + + let result + if (res.feeForTx * 1 > 0) { + result = { + selectedFeeIndex: 0, + shouldShowFees: false, + fees: [ + { + langMsg: 'xrp_speed_one', + feeForTx: Math.round(res.feeForTx).toString(), + amountForTx: res?.amountForTx, + selectedTransferAllBalance: res?.selectedTransferAllBalance, + isErrorFee: res?.isErrorFee + } + ] + } + } else { + result = { + selectedFeeIndex: -3, + shouldShowFees: false + } + } + return result as BlocksoftBlockchainTypes.FeeRateResult + } catch (e) { + if (e.message.indexOf('SERVER_RESPONSE_') === 0) { + throw e + } + if (config.debug.cryptoErrors) { + console.log(this._settings.currencyCode + ' TrxTransferProcessor.getFeeRate new error ' + e.message) + } + BlocksoftCryptoLog.log(this._settings.currencyCode + ' TrxTransferProcessor.getFeeRate new error ' + e.message) + return this.getFeeRateOld(data, privateData, additionalData) + } + } + + async getFeeRateOld(data: BlocksoftBlockchainTypes.TransferData, privateData: BlocksoftBlockchainTypes.TransferPrivateData, additionalData: {} = {}): Promise { + const addressHexTo = TronUtils.addressToHex(data.addressTo) + const result: BlocksoftBlockchainTypes.FeeRateResult = { + selectedFeeIndex: -3, + shouldShowFees: false + } as BlocksoftBlockchainTypes.FeeRateResult + + const sendLink = BlocksoftExternalSettings.getStatic('TRX_SEND_LINK') + const link = sendLink + '/wallet/getaccountresource' // http://trx.trusteeglobal.com:8090/wallet + + try { + let feeForTx = 0 + try { + const res = await (BlocksoftBalances.setCurrencyCode('TRX').setAddress(data.addressFrom)).getResources('TrxSendTx') + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' TrxTransferProcessor.getFeeRate result resources from ' + data.addressFrom, res) + if (this._isToken20) { + const bandForTx = BlocksoftExternalSettings.getStatic('TRX_TRC20_BAND_PER_TX') + const priceForBand = BlocksoftExternalSettings.getStatic('TRX_TRC20_PRICE_PER_BAND') + const fullPriceBand = bandForTx * priceForBand + let feeLog = '' + if (res.leftBand <= 0) { + feeForTx = fullPriceBand + feeLog += ' res.leftBand<=0 bandFee=' + bandForTx + '*' + priceForBand + '=' + fullPriceBand + } else { + const diffB = bandForTx - res.leftBand + feeLog += ' diffB=' + bandForTx + '-' + res.leftBand + '=' + diffB + if (diffB > 0) { + feeForTx = BlocksoftUtils.mul(fullPriceBand, BlocksoftUtils.div(diffB, bandForTx)) + feeLog += ' fullPriceBand=' + bandForTx + '*' + priceForBand + '=' + fullPriceBand + feeLog += ' bandFee=' + fullPriceBand + '*' + diffB + '/' + bandForTx + '=' + feeForTx + } + } + const energyForTx = BlocksoftExternalSettings.getStatic('TRX_TRC20_ENERGY_PER_TX') + const priceForEnergy = BlocksoftExternalSettings.getStatic('TRX_TRC20_PRICE_PER_ENERGY') + const fullPriceEnergy = energyForTx * priceForEnergy + if (res.leftEnergy <= 0) { + feeForTx = feeForTx * 1 + fullPriceEnergy + feeLog += ' res.leftEnergy<=0 energyFee=' + energyForTx + '*' + priceForEnergy + '=' + fullPriceEnergy + } else { + const diffE = energyForTx - res.leftEnergy + feeLog += ' diffE=' + energyForTx + '-' + res.leftEnergy + '=' + diffE + if (diffE > 0) { + const energyFee = BlocksoftUtils.mul(fullPriceEnergy, BlocksoftUtils.div(diffE / energyForTx)) * 1 + feeForTx = feeForTx * 1 + energyFee + feeLog += ' fullPriceEnergy=' + energyForTx + '*' + priceForEnergy + '=' + fullPriceEnergy + feeLog += ' energyFee=' + fullPriceEnergy + '*' + diffE + '/' + bandForTx + '=' + energyFee + } + } + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' TrxTransferProcessor.getFeeRate feeForTx ' + feeForTx + ' calculated by ' + feeLog) + } else { + // @ts-ignore + if (res.leftBand <= 0) { + feeForTx = BlocksoftExternalSettings.getStatic('TRX_BASIC_PRICE_WHEN_NO_BAND') + } + } + } catch (e) { + // do nothing + if (config.debug.cryptoErrors) { + console.log(this._settings.currencyCode + ' TrxTransferProcessor.getFeeRate addressFrom data error ' + e.message) + } + BlocksoftCryptoLog.log(this._settings.currencyCode + ' TrxTransferProcessor.getFeeRate addressFrom data error ' + e.message) + } + + + let isErrorFee = false + const balance = await (BlocksoftBalances.setCurrencyCode('TRX').setAddress(data.addressFrom)).getBalance('TrxSendTx') + if (this._isToken20) { + if (!balance || balance.balanceAvailable <= feeForTx) { + isErrorFee = true + // throw new Error('SERVER_RESPONSE_NOT_ENOUGH_FEE') + } + } else if (this._tokenName === '_') { + if (!balance || (balance.balanceAvailable <= feeForTx * 1 + data.amount * 1)) { + isErrorFee = true + // throw new Error('SERVER_RESPONSE_NOT_ENOUGH_FEE') + } + } + + if (typeof data.dexOrderData === 'undefined' || !data.dexOrderData) { + try { + const res2 = await BlocksoftAxios.post(link, { address: addressHexTo }) + const tronData2 = res2.data + delete tronData2.assetNetUsed + delete tronData2.assetNetLimit + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' TrxTransferProcessor.getFeeRate result ' + link + ' to ' + data.addressTo, tronData2) + if (typeof tronData2.freeNetLimit === 'undefined') { + feeForTx = feeForTx * 1 + 1100000 + } + } catch (e) { + // do nothing + if (config.debug.cryptoErrors) { + console.log(this._settings.currencyCode + ' TrxTransferProcessor.getFeeRate addressTo data error ' + e.message) + } + BlocksoftCryptoLog.log(this._settings.currencyCode + ' TrxTransferProcessor.getFeeRate addressTo data error ' + e.message) + } + } + + if (feeForTx !== 0) { + let amountForTx = data.amount + let selectedTransferAllBalance = data.amount + if (this._tokenName === '_') { + const balance = await (BlocksoftBalances.setCurrencyCode('TRX').setAddress(data.addressFrom)).getBalance('TrxSendTx') + if (balance && typeof balance.balance !== 'undefined') { + if (balance.balance === 0) { + amountForTx = 0 + selectedTransferAllBalance = 0 + } else { + selectedTransferAllBalance = BlocksoftUtils.diff(balance.balance, feeForTx) + let test = BlocksoftUtils.diff(data.amount, feeForTx) + if (test * 1 > balance.balance * 1) { + amountForTx = selectedTransferAllBalance + } + } + } + } + + result.fees = [ + { + langMsg: 'xrp_speed_one', + feeForTx: Math.round(feeForTx).toString(), + amountForTx, + selectedTransferAllBalance, + isErrorFee + } + ] + /* + if (res.data.balance * 1 < feeForTx * 1) { + throw new Error('SERVER_RESPONSE_BANDWITH_ERROR_TRX') + } + */ + result.selectedFeeIndex = 0 + } + } catch (e) { + if (e.message.indexOf('SERVER_RESPONSE_') === 0) { + throw e + } + if (config.debug.cryptoErrors) { + console.log(this._settings.currencyCode + ' TrxTransferProcessor.getFeeRate error ' + e.message) + } + BlocksoftCryptoLog.log(this._settings.currencyCode + ' TrxTransferProcessor.getFeeRate error ' + e.message) + } + return result + } + + async getTransferAllBalance(data: BlocksoftBlockchainTypes.TransferData, privateData: BlocksoftBlockchainTypes.TransferPrivateData, additionalData: BlocksoftBlockchainTypes.TransferAdditionalData = {}): Promise { + data.isTransferAll = true + const balance = data.amount + // @ts-ignore + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' TrxTransferProcessor.getTransferAllBalance ', data.addressFrom + ' => ' + balance) + // noinspection EqualityComparisonWithCoercionJS + if (balance === '0') { + return { + selectedTransferAllBalance: '0', + selectedFeeIndex: -1, + fees: [], + shouldShowFees: false, + countedForBasicBalance: '0' + } + } + const fees = await this.getFeeRate(data, privateData, additionalData) + if (!fees || fees.selectedFeeIndex < 0) { + return { + selectedTransferAllBalance: balance, + selectedFeeIndex: -3, + fees: [], + shouldShowFees: false, + countedForBasicBalance: balance + } + } + return { + ...fees, + shouldShowFees: false, + selectedTransferAllBalance: fees.fees[fees.selectedFeeIndex].selectedTransferAllBalance + } + } + + /** + * https://developers.tron.network/reference#walletcreatetransaction + * https://developers.tron.network/docs/trc20-introduction#section-8usdt-transfer + */ + async sendTx(data: BlocksoftBlockchainTypes.TransferData, privateData: BlocksoftBlockchainTypes.TransferPrivateData, uiData: BlocksoftBlockchainTypes.TransferUiData): Promise { + if (typeof privateData.privateKey === 'undefined') { + throw new Error('TRX transaction required privateKey') + } + if (uiData.selectedFee.isErrorFee && (typeof uiData.uiErrorConfirmed === 'undefined' || !uiData.uiErrorConfirmed)) { + if (config.debug.cryptoErrors) { + console.log(`uiData.selectedFee.isErrorFee`, uiData) + } + throw new Error('SERVER_RESPONSE_NOT_ENOUGH_FEE') + } + + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' TrxTransferProcessor.sendTx started ' + data.addressFrom + ' => ' + data.addressTo) + + + const logData = {} + logData.currencyCode = this._settings.currencyCode + logData.selectedFee = uiData.selectedFee + logData.from = data.addressFrom + logData.basicAddressTo = data.addressTo + logData.basicAmount = data.amount + logData.pushLocale = sublocale() + logData.pushSetting = await settingsActions.getSetting('transactionsNotifs') + logData.basicToken = this._tokenName + + + const sendLink = BlocksoftExternalSettings.getStatic('TRX_SEND_LINK') + let tx + if (typeof data.blockchainData !== 'undefined' && data.blockchainData) { + tx = data.blockchainData + } else { + + let link, res, params + + if (typeof data.dexOrderData !== 'undefined' && data.dexOrderData) { + // {"tokenContract":"41a2726afbecbd8e936000ed684cef5e2f5cf43008","contractMethod":"trxToTokenSwapInput(uint256)","options":{"callValue":"1000000"},"params":[{"type":"uint256","value":"116256"}]} + let ownerAddress + + const abiCoder = new AbiCoder() + try { + ownerAddress = TronUtils.addressToHex(data.addressFrom) + } catch (e) { + e.message += ' inside TronUtils.addressToHex owner_address ' + data.addressFrom + throw e + } + + const link = sendLink + '/wallet/triggersmartcontract' + const total = data.dexOrderData.length + let index = 0 + for (const order of data.dexOrderData) { + index++ + let parameter = '' + + if (order.params) { + const types = [] + const values = [] + try { + for (const tmp of order.params) { + let type, value + try { + type = tmp.type + value = tmp.value + if (type === 'address') { + value = TronUtils.addressToHex(value).replace(ADDRESS_PREFIX_REGEX, '0x') + } else if (type === 'address[]') { + value = value.map(v => TronUtils.addressToHex(v).replace(ADDRESS_PREFIX_REGEX, '0x')) + } + types.push(type) + values.push(value) + } catch (e) { + throw new Error(e.message + ' type ' + type + ' tmp.value ' + tmp.value + ' value ' + value) + } + } + parameter = abiCoder.encode(types, values).replace(/^(0x)/, '') + } catch (e) { + throw new Error(e.message + ' in abiCoder') + } + } + + let params + try { + params = { + owner_address: ownerAddress, + contract_address: order.tokenContract, + function_selector: order.contractMethod, + // @ts-ignore + parameter, + fee_limit: BlocksoftExternalSettings.getStatic('TRX_TRC20_MAX_LIMIT') + } + if (typeof order.options !== 'undefined' && typeof order.options.callValue !== 'undefined') { + params.call_value = order.options.callValue * 1 + } + } catch (e1) { + throw new Error(e1.message + ' in params build') + } + if (index < total) { + res = await BlocksoftAxios.post(link, params) + + tx = res.data.transaction + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' TrxTxProcessor.sendSubTx tx', tx) + + tx.signature = [TronUtils.ECKeySign(Buffer.from(tx.txID, 'hex'), Buffer.from(privateData.privateKey, 'hex'))] + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' TrxTxProcessor.sendSubTx signed', tx) + + let resultSub = {} as BlocksoftBlockchainTypes.SendTxResult + try { + resultSub = await this.sendProvider.sendTx(tx, '', false, logData) + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' TrxTxProcessor.sendSubTx broadcasted') + } catch (e) { + if (config.debug.cryptoErrors) { + console.log(this._settings.currencyCode + ' TrxTransferProcessor.sendSubTx error', e, uiData) + } + BlocksoftCryptoLog.log(this._settings.currencyCode + ' TrxTransferProcessor.sendSubTx error ' + e.message) + // noinspection ES6MissingAwait + MarketingEvent.logOnlyRealTime('v20_trx_tx_sub_error ' + this._settings.currencyCode + ' ' + data.addressFrom + ' => ' + data.addressTo + ' ' + e.message, logData) + throw e + } + + const linkRecheck = sendLink + '/wallet/gettransactioninfobyid' + let checks = 0 + let mined = false + do { + checks++ + try { + const recheck = await BlocksoftAxios.post(linkRecheck, { + value: tx.txID + }) + if (typeof recheck.data !== 'undefined') { + if (typeof recheck.data.id !== 'undefined' && typeof recheck.data.blockNumber !== 'undefined' + && typeof recheck.data.receipt !== 'undefined' && typeof recheck.data.receipt.result !== 'undefined' + ) { + + // @ts-ignore + BlocksoftCryptoLog.log(this._settings.currencyCode + ' TrxTransferProcessor.sendSubTx recheck ', { + id: recheck.data.id, + blockNumber: recheck.data.blockNumber, + receipt: recheck.data.receipt + }) + mined = true + const minedStatus = recheck.data.receipt.result.toUpperCase() + if (minedStatus === 'OUT_OF_ENERGY') { + strings(`account.transactionStatuses.out_of_energy`) + } else if (minedStatus === 'FAILED') { + strings(`account.transactionStatuses.fail`) + } else if (minedStatus !== 'SUCCESS') { + throw new Error('Bad tx status ' + JSON.stringify(recheck.data.receipt)) + } + break + } + } + } catch (e1) { + if (config.debug.cryptoErrors) { + console.log(this._settings.currencyCode + ' TRX transaction recheck error ', e1) + } + BlocksoftCryptoLog.log(this._settings.currencyCode + ' TRX transaction recheck error ' + e1.message) + } + } while (checks < 100 && !mined) + + } else { + res = await BlocksoftAxios.post(link, params) + } + } + } else { + + if (typeof data.addressTo === 'undefined') { + throw new Error('TRX transaction required addressTo') + } + if (data.addressFrom === data.addressTo) { + throw new Error('SERVER_RESPONSE_SELF_TX_FORBIDDEN') + } + // check error + await this.getFeeRate(data, privateData) + + let toAddress, ownerAddress + + try { + toAddress = TronUtils.addressToHex(data.addressTo) + } catch (e) { + e.message += ' inside TronUtils.addressToHex to_address ' + data.addressTo + throw e + } + + if (TronUtils.addressHexToStr(toAddress) !== data.addressTo) { + BlocksoftCryptoLog.log('TrxTransferProcessor.sendTx heck address ' + data.addressTo + ' hex ' + toAddress + ' => ' + TronUtils.addressHexToStr(toAddress)) + throw new Error('TRX SYSTEM ERROR - Please check address ' + data.addressTo) + } + + + try { + ownerAddress = TronUtils.addressToHex(data.addressFrom) + } catch (e) { + e.message += ' inside TronUtils.addressToHex owner_address ' + data.addressFrom + throw e + } + + if (this._tokenName[0] === 'T') { + link = sendLink + '/wallet/triggersmartcontract' + const parameter = '0000000000000000000000' + toAddress.toUpperCase() + '000000000000000000000000' + BlocksoftUtils.decimalToHex(BlocksoftUtils.round(data.amount), 40) + params = { + owner_address: ownerAddress, + contract_address: TronUtils.addressToHex(this._tokenName), + function_selector: 'transfer(address,uint256)', + parameter, + fee_limit: BlocksoftExternalSettings.getStatic('TRX_TRC20_MAX_LIMIT'), + call_value: 0 + } + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' TrxTransferProcessor.sendTx inited1' + data.addressFrom + ' => ' + data.addressTo + ' ' + link, params) + res = await BlocksoftAxios.post(link, params) + } else { + params = { + owner_address: ownerAddress, + to_address: toAddress, + // @ts-ignore + amount: BlocksoftUtils.round(data.amount) * 1 + } + + if (this._tokenName === '_') { + link = sendLink + '/wallet/createtransaction' + } else { + // @ts-ignore + params.asset_name = '0x' + Buffer.from(this._tokenName).toString('hex') + link = sendLink + '/wallet/transferasset' + } + + try { + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' TrxTransferProcessor.sendTx inited2 ' + data.addressFrom + ' => ' + data.addressTo + ' ' + link, params) + res = await BlocksoftAxios.post(link, params) + } catch (e) { + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' TrxTransferProcessor.sendTx result2' + data.addressFrom + ' => ' + data.addressTo + ' ' + link + ' ' + e.message) + if (e.message.indexOf('timeout of') !== -1 || e.message.indexOf('network') !== -1) { + throw new Error('SERVER_RESPONSE_NOT_CONNECTED') + } else { + throw e + } + } + } + } + + // @ts-ignore + if (typeof res.data.Error !== 'undefined') { + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' TrxTransferProcessor.sendTx error ' + data.addressFrom + ' => ' + data.addressTo + ' ', res.data) + // @ts-ignore + this.sendProvider.trxError(res.data.Error.message || res.data.Error) + } + + // @ts-ignore + tx = res.data + if ((typeof data.dexOrderData !== 'undefined' && data.dexOrderData) || (this._tokenName[0] === 'T')) { + // @ts-ignore + if (typeof res.data.transaction === 'undefined' || typeof res.data.result === 'undefined') { + // @ts-ignore + if (typeof res.data.result.message !== 'undefined') { + // @ts-ignore + res.data.result.message = BlocksoftUtils.hexToUtf('0x' + res.data.result.message) + } + // @ts-ignore + this.sendProvider.trxError('No tx in contract data ' + JSON.stringify(res.data)) + } + // @ts-ignore + tx = res.data.transaction + } else { + // @ts-ignore + if (typeof res.data.txID === 'undefined') { + // @ts-ignore + if (typeof res.data.result.message !== 'undefined') { + // @ts-ignore + res.data.result.message = BlocksoftUtils.hexToUtf('0x' + res.data.result.message) + } + // @ts-ignore + this.sendProvider.trxError('No txID in data ' + JSON.stringify(res.data)) + } + } + } + + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' TrxTxProcessor.sendTx token ' + this._tokenName + ' tx', tx) + + tx.signature = [TronUtils.ECKeySign(Buffer.from(tx.txID, 'hex'), Buffer.from(privateData.privateKey, 'hex'))] + if (typeof uiData !== 'undefined' && typeof uiData.selectedFee !== 'undefined' && typeof uiData.selectedFee.rawOnly !== 'undefined' && uiData.selectedFee.rawOnly) { + return { rawOnly: uiData.selectedFee.rawOnly, raw: JSON.stringify(tx) } + } + + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' TrxTxProcessor.sendTx signed', tx) + + let result = {} as BlocksoftBlockchainTypes.SendTxResult + try { + result = await this.sendProvider.sendTx(tx, '', false, logData) + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' TrxTxProcessor.sendTx broadcasted') + } catch (e) { + if (config.debug.cryptoErrors) { + console.log(this._settings.currencyCode + ' TrxTransferProcessor.sendTx error', e, uiData) + } + BlocksoftCryptoLog.log(this._settings.currencyCode + ' TrxTransferProcessor.sendTx error ' + e.message) + // noinspection ES6MissingAwait + MarketingEvent.logOnlyRealTime('v20_trx_tx_error ' + this._settings.currencyCode + ' ' + data.addressFrom + ' => ' + data.addressTo + ' ' + e.message, logData) + throw e + } + // noinspection ES6MissingAwait + MarketingEvent.logOnlyRealTime('v20_trx_tx_success ' + this._settings.currencyCode + ' ' + data.addressFrom + ' => ' + data.addressTo, logData) + + await (BlocksoftTransactions.resetTransactionsPending({ account: { currencyCode: 'TRX' } }, 'AccountRunPending')) + + if (config.debug.cryptoErrors) { + console.log(this._settings.currencyCode + ' TrxTransferProcessor.sendTx result', JSON.parse(JSON.stringify(result))) + } + return result + } +} diff --git a/crypto/blockchains/trx/basic/TrxNodeInfoProvider.js b/crypto/blockchains/trx/basic/TrxNodeInfoProvider.js new file mode 100644 index 000000000..33de59653 --- /dev/null +++ b/crypto/blockchains/trx/basic/TrxNodeInfoProvider.js @@ -0,0 +1,33 @@ +import BlocksoftAxios from '../../../common/BlocksoftAxios' +import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog' +import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings' + +const INFO_MAX_TRY = 50 // max tries before error appear in axios get + +let CACHE_LAST_BLOCK = 0 + +export default class TrxNodeInfoProvider { + /** + * @returns {Promise} + */ + async getLastBlock() { + try { + const sendLink = BlocksoftExternalSettings.getStatic('TRX_SEND_LINK') + const link = sendLink + '/wallet/getnodeinfo' + let info = await BlocksoftAxios.getWithoutBraking(link, INFO_MAX_TRY) + if (info && typeof info.data !== 'undefined' && typeof info.data.block !== 'undefined') { + info = info.data.block.split(',ID') + info = info[0].substr(4) * 1 + if (info > CACHE_LAST_BLOCK) { + CACHE_LAST_BLOCK = info + } + BlocksoftCryptoLog.log('TrxNodeInfoProvider.getLastBlock currentBlock ' + JSON.stringify(info)) + } else { + BlocksoftCryptoLog.log('TrxNodeInfoProvider.getLastBlock currentBlock warning ' + JSON.stringify(info)) + } + } catch (e) { + BlocksoftCryptoLog.log('TrxNodeInfoProvider.getLastBlock currentBlock error ' + e.message) + } + return CACHE_LAST_BLOCK + } +} diff --git a/crypto/blockchains/trx/basic/TrxTransactionsProvider.js b/crypto/blockchains/trx/basic/TrxTransactionsProvider.js new file mode 100644 index 000000000..e045a70da --- /dev/null +++ b/crypto/blockchains/trx/basic/TrxTransactionsProvider.js @@ -0,0 +1,209 @@ +/** + * @version 0.5 + */ +import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog' +import BlocksoftAxios from '../../../common/BlocksoftAxios' +import BlocksoftUtils from '../../../common/BlocksoftUtils' +import TrxNodeInfoProvider from './TrxNodeInfoProvider' +import TransactionFilterTypeDict from '@appV2/dicts/transactionFilterTypeDict' +import BlocksoftPrettyNumbers from '@crypto/common/BlocksoftPrettyNumbers' +import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings' + +const TXS_MAX_TRY = 10 + +const CACHE_OF_TRANSACTIONS = {} +const CACHE_VALID_TIME = 30000 // 30 seconds + +export default class TrxTransactionsProvider { + + /** + * @type {number} + * @private + */ + _lastBlock = 15850641 + + /** + * @type {string} + * @private + */ + _tronscanLink = 'https://api.tronscan.org/api/transaction?sort=-timestamp&count=true&limit=50&address=' + + constructor() { + this._nodeInfo = new TrxNodeInfoProvider() + } + + /** + * @param scanData.account.address + * @param tokenName + * @returns {Promise} + */ + async get(scanData, tokenName) { + const address = scanData.account.address.trim() + const now = new Date().getTime() + if (typeof CACHE_OF_TRANSACTIONS[address] !== 'undefined' && (now - CACHE_OF_TRANSACTIONS[address].time) < CACHE_VALID_TIME) { + if (typeof CACHE_OF_TRANSACTIONS[address][tokenName] !== 'undefined') { + BlocksoftCryptoLog.log(' TrxTransactionsProvider.get from cache', address + ' => ' + tokenName) + return CACHE_OF_TRANSACTIONS[address][tokenName] + } + } + + const res = await BlocksoftAxios.getWithoutBraking(this._tronscanLink + address, TXS_MAX_TRY) + + if (!res || !res.data || typeof res.data.data === 'undefined' || res.data.data.length === 0) return false + + this._lastBlock = await this._nodeInfo.getLastBlock() + + CACHE_OF_TRANSACTIONS[address] = {} + CACHE_OF_TRANSACTIONS[address].time = new Date().getTime() + CACHE_OF_TRANSACTIONS[address][tokenName] = [] + let tx + for (tx of res.data.data) { + let tmp = false + try { + tmp = await this._unifyTransaction(scanData, tx, tokenName) + } catch (e) { + BlocksoftCryptoLog.log('TrxTransactionsProvider.get unify error ' + e.message + ' tx ' + tx?.transactionHash) + } + if (!tmp) continue + + const transaction = tmp?.res + + let txTokenName = '_' + if (typeof tmp.txTokenName !== 'undefined' && tmp.txTokenName) { + txTokenName = tmp.txTokenName + } else if (typeof tx.contractData === 'undefined') { + txTokenName = tokenName + } else if (typeof tx.contractData.contract_address !== 'undefined') { + txTokenName = tx.contractData.contract_address + } else if (typeof tx.contractData.asset_name !== 'undefined') { + txTokenName = tx.contractData.asset_name + } + if (typeof CACHE_OF_TRANSACTIONS[address][txTokenName] === 'undefined') { + CACHE_OF_TRANSACTIONS[address][txTokenName] = [] + } + CACHE_OF_TRANSACTIONS[address][txTokenName].push(transaction) + } + return CACHE_OF_TRANSACTIONS[address][tokenName] + + } + + /** + * @param {string} scanData.address.address + * @param {Object} transaction + * @param {string} transaction.amount 1000000 + * @param {string} transaction.ownerAddress 'TJcnzHwXiFvMsmGDwBstDmwQ5AWVWFPxTM' + * @param {string} transaction.data '' + * @param {string} transaction.contractData.amount '' + * @param {string} transaction.toAddress 'TGk5Nkv8gf7HShzLw7rHzJsQLzsALvPPnF' + * @param {string} transaction.block 14129705 + * @param {string} transaction.confirmed true + * @param {string} transaction.contractRet 'SUCCESS' + * @param {string} transaction.hash '74d0f84322b1ba1478ce3f272d7b4524563e5a44b1270325cc6cce7e600601e2' + * @param {string} transaction.timestamp 1572636390000 + * @return {UnifiedTransaction} + * @private + */ + async _unifyTransaction(scanData, transaction, tokenName) { + const address = scanData.account.address.trim() + let transactionStatus = 'new' + const now = new Date().getTime() + transaction.diffSeconds = Math.round((now - transaction.timestamp) / 1000) + if (transaction.confirmed) { + if (typeof transaction.contractRet === 'undefined') { + transactionStatus = 'success' + } else if (transaction.contractRet === 'SUCCESS') { + transactionStatus = 'success' + } else { + transactionStatus = 'fail' + } + } else if (transaction.block > 0) { + if (transaction.diffSeconds > 120) { + transactionStatus = 'fail' + } else { + transactionStatus = 'confirming' + } + } + if (transaction.block > this._lastBlock) { + this._lastBlock = transaction.block + } + + let blockConfirmations = this._lastBlock - transaction.block + if (blockConfirmations > 100 && transaction.diffSeconds < 600) { + blockConfirmations = transaction.diffSeconds + } + + if (typeof transaction.timestamp === 'undefined') { + throw new Error(' no transaction.timeStamp error transaction data ' + JSON.stringify(transaction)) + } + let formattedTime = transaction.timestamp + try { + formattedTime = BlocksoftUtils.toDate(transaction.timestamp / 1000) + } catch (e) { + e.message += ' timestamp error transaction data ' + JSON.stringify(transaction) + throw e + } + let addressAmount = 0 + let transactionDirection = 'self' + let txTokenName = false + let addressFrom = (address.toLowerCase() === transaction.ownerAddress.toLowerCase()) ? '' : transaction.ownerAddress + let transactionFilterType = TransactionFilterTypeDict.USUAL + if (typeof transaction.contractData.amount === 'undefined') { + if (typeof transaction.contractData !== 'undefined' && typeof transaction.contractData.frozen_balance !== 'undefined') { + addressAmount = transaction.contractData.frozen_balance + transactionDirection = 'freeze' + transactionFilterType = TransactionFilterTypeDict.STAKE + } else if (typeof transaction.amount !== 'undefined' && typeof transaction.contractType !== 'undefined' && transaction.contractType === 13) { + addressAmount = transaction.amount + transactionDirection = 'claim' + transactionFilterType = TransactionFilterTypeDict.STAKE + } else if (typeof transaction.contractType !== 'undefined' && transaction.contractType === 12) { + addressAmount = transaction.amount + addressFrom = transaction.ownerAddress + transactionDirection = 'unfreeze' + transactionFilterType = TransactionFilterTypeDict.STAKE + } else if (typeof transaction.contractType !== 'undefined' && transaction.contractType === 4) { + // no vote tx + return false + addressAmount = BlocksoftPrettyNumbers.setCurrencyCode('TRX').makeUnPretty(transaction.amount) + addressFrom = transaction.ownerAddress + transactionDirection = 'vote' + transactionFilterType = TransactionFilterTypeDict.STAKE + } else { + if (transaction.contractType === 11 || transaction.contractType === 4 || transaction.contractType === 13) { + // freeze = 11, vote = 4, claim = 13 + } else { + // noinspection ES6MissingAwait + BlocksoftCryptoLog.log('TrxTransactionsProvider._unifyTransaction buggy tx ' + JSON.stringify(transaction)) + } + return false + } + } else { + addressAmount = transaction.contractData.amount + transactionDirection = (address.toLowerCase() === transaction.ownerAddress.toLowerCase()) ? 'outcome' : 'income' + } + let transactionFee = 0 + if (typeof transaction.cost !== 'undefined' && typeof transaction.cost.fee !== 'undefined' && transaction.cost.fee) { + transactionFee = transaction.cost.fee * 1 + } + const res = { + transactionHash: transaction.hash, + blockHash: '', + blockNumber: transaction.block, + blockTime: formattedTime, + blockConfirmations: blockConfirmations, + transactionDirection, + addressFrom, + addressTo: (address.toLowerCase() === transaction.toAddress.toLowerCase()) ? '' : transaction.toAddress, + addressAmount, + transactionStatus, + transactionFee, + transactionFilterType, + inputValue: transaction.data + } + if (!res.addressTo && (!res.addressFrom || res.addressFrom.toLowerCase() === address.toLowerCase())) { + return false + } + + return { res, txTokenName } + } +} diff --git a/crypto/blockchains/trx/basic/TrxTransactionsTrc20Provider.js b/crypto/blockchains/trx/basic/TrxTransactionsTrc20Provider.js new file mode 100644 index 000000000..378218cc6 --- /dev/null +++ b/crypto/blockchains/trx/basic/TrxTransactionsTrc20Provider.js @@ -0,0 +1,160 @@ +/** + * @version 0.5 + * https://github.com/tronscan/tronscan-frontend/wiki/TRONSCAN-API + */ +import TrxTransactionsProvider from './TrxTransactionsProvider' +import BlocksoftUtils from '../../../common/BlocksoftUtils' +import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog' +import BlocksoftAxios from '@crypto/common/BlocksoftAxios' +import Database from '@app/appstores/DataSource/Database/main' +import TransactionFilterTypeDict from '@appV2/dicts/transactionFilterTypeDict' + +const SWAPS = require('../dict/swaps') +export default class TrxTransactionsTrc20Provider extends TrxTransactionsProvider { + _token = false + + setLink(token) { + this._token = token + this._tronscanLink = 'https://apilist.tronscan.org/api/contract/events?sort=-timestamp&count=true&limit=50&contract=' + token + '&address=' + } + + /** + * @param {string} scanData.account.address + * @param {Object} transaction + * @param {string} transaction.amount 1000000 + * @param {string} transaction.transferFromAddress 'TUbHxAdhPk9ykkc7SDP5e9zUBEN14K65wk' + * @param {string} transaction.data '' + * @param {string} transaction.decimals 6 + * @param {string} transaction.tokenName 'Tether USD' + * @param {string} transaction.transferToAddress 'TUoyiQH9wSfYdJRhsXtgmgDvpWipPrQN8a' + * @param {string} transaction.block 15847100 + * @param {string} transaction.id '' + * @param {string} transaction.confirmed true + * @param {string} transaction.transactionHash '4999b0965c1a5b17cbaa862b9357a32c9b8d096e170f4eecee929159b0b73ad3' + * @param {string} transaction.timestamp: 1577796345000 + * @return {UnifiedTransaction} + * @private + */ + async _unifyTransaction(scanData, transaction) { + const address = scanData.account.address.trim() + let transactionStatus = 'new' + if (transaction.confirmed) { + transactionStatus = 'success' + } else if (transaction.block > 0) { + transactionStatus = 'fail' + } + + let txTokenName = false + let formattedTime + try { + formattedTime = BlocksoftUtils.toDate(transaction.timestamp / 1000) + } catch (e) { + e.message += ' timestamp error transaction data ' + JSON.stringify(transaction) + throw e + } + if (typeof transaction.amount === 'undefined') { + // noinspection ES6MissingAwait + BlocksoftCryptoLog.err('TrxTransactionsTrc20Provider._unifyTransaction buggy tx ' + JSON.stringify(transaction)) + } + + const res = { + transactionHash: transaction.transactionHash, + blockHash: '', + blockNumber: transaction.block, + blockTime: formattedTime, + blockConfirmations: this._lastBlock - transaction.block, + transactionDirection: (address.toLowerCase() === transaction.transferFromAddress.toLowerCase()) ? 'outcome' : 'income', + addressFrom: (address.toLowerCase() === transaction.transferFromAddress.toLowerCase()) ? '' : transaction.transferFromAddress, + addressTo: (address.toLowerCase() === transaction.transferToAddress.toLowerCase()) ? '' : transaction.transferToAddress, + addressAmount: typeof transaction.amount !== 'undefined' ? transaction.amount.toString() : '0', + transactionStatus: transactionStatus, + transactionFee: 0, + inputValue: transaction.data + } + + let needData = false + if (res.addressAmount.indexOf('115792089237316195423570985008687907853269984665640564039457') === 0) { + res.addressAmount = '0' + needData = true + } + if (typeof SWAPS[res.addressTo] !== 'undefined') { + res.addressTo = SWAPS[res.addressTo] + res.transactionDirection = 'swap_outcome' + res.addressAmount = '0' + needData = true + } else if (typeof SWAPS[res.addressFrom] !== 'undefined') { + res.addressFrom = SWAPS[res.addressFrom] + res.transactionDirection = 'swap_income' + res.addressAmount = '0' + needData = true + } else if (res.transactionDirection === 'outcome') { + needData = true + } + + + + if (needData) { + const diff = scanData.account.transactionsScanTime - transaction.timestamp / 1000 + if (diff > 6000) { + return false + } + } + + + if (needData) { + const tmp = await BlocksoftAxios.get('https://apilist.tronscan.org/api/transaction-info?hash=' + res.transactionHash) + res.transactionFee = tmp.data.cost.fee * 1 + tmp.data.cost.energy_fee * 1 + + if (res.transactionFee * 1 > 0 && res.addressAmount * 1 > 0) { + const savedTRX = await Database.query(` SELECT * FROM transactions WHERE transaction_hash='${res.transactionHash}' AND currency_code='TRX' `) + if (!savedTRX || !savedTRX.array || savedTRX.array.length === 0) { + BlocksoftCryptoLog.log('TrxTransactionsTrc20Provider._unifyTransaction added fee for ' + res.transactionHash + ' amount ' + res.addressAmount + ' fee ' + res.transactionFee) + const saveFee = { + 'account_id': 0, + 'address_amount': 0, + 'address_from': res.addressFrom, + 'address_to': res.addressTo, + 'block_confirmations': res.blockConfirmations, + 'block_number': res.blockNumber, + 'block_time': res.blockTime, + 'created_at': res.blockTime, + 'currency_code': 'TRX', + 'mined_at': res.blockTime, + 'transaction_direction': res.transactionDirection, + 'transaction_fee': res.transactionFee, + 'transaction_filter_type': TransactionFilterTypeDict.FEE, + 'transaction_hash': res.transactionHash, + 'transaction_status': res.transactionStatus, + 'transactions_scan_time': new Date().getTime(), + 'wallet_hash': scanData.account.walletHash + } + await Database.setTableName('transactions').setInsertData({ insertObjs: [saveFee] }).insert() + } + } + if (typeof tmp.data.trc20TransferInfo !== 'undefined') { + for (const info of tmp.data.trc20TransferInfo) { + if (info.contract_address !== this._token) continue + if (info.from_address === address) { + if (info.to_address === address) { + res.transactionDirection = 'self' + } else { + res.transactionDirection = 'outcome' + } + } else if (info.to_address === address) { + res.transactionDirection = 'income' + res.addressAmount = info.amount_str + } else { + continue + } + } + } + if (res.transactionFee * 1 === 0 || res.addressAmount * 1 === 0) { + return false + } + } else if (res.addressAmount * 1 === 0) { + return false + } + + return { res, txTokenName } + } +} diff --git a/crypto/blockchains/trx/basic/TrxTrongridProvider.js b/crypto/blockchains/trx/basic/TrxTrongridProvider.js new file mode 100644 index 000000000..d00ab3237 --- /dev/null +++ b/crypto/blockchains/trx/basic/TrxTrongridProvider.js @@ -0,0 +1,183 @@ +/** + * @version 0.5 + * https://github.com/tronscan/tronscan-frontend/wiki/TRONSCAN-API + */ +import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog' +import BlocksoftAxios from '../../../common/BlocksoftAxios' +import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings' +import TronUtils from '@crypto/blockchains/trx/ext/TronUtils' + +const BALANCE_MAX_TRY = 10 + +const CACHE_TRONGRID = {} +const CACHE_VALID_TIME = 3000 // 3 seconds + +export default class TrxTrongridProvider { + + async isMultisigTrongrid(address) { + if (typeof CACHE_TRONGRID[address] !== 'undefined') { + return CACHE_TRONGRID[address].isMultisig + } + const res = await this.get(address, '_', true) + return res?.isMultisig || false + } + + /** + * https://api.trongrid.io/walletsolidity/getaccount?address=41d4eead2ea047881ce54cae1a765dfe92a8bfdbe9 + * @param {string} address + * @param {string} tokenName + * @returns {Promise} + */ + async get(address, tokenName, useCache = true) { + const now = new Date().getTime() + if (useCache && typeof CACHE_TRONGRID[address] !== 'undefined' && (now - CACHE_TRONGRID[address].time) < CACHE_VALID_TIME) { + if (typeof CACHE_TRONGRID[address][tokenName] !== 'undefined') { + BlocksoftCryptoLog.log('TrxTrongridProvider.get from cache', address + ' => ' + tokenName + ' : ' + CACHE_TRONGRID[address][tokenName]) + const voteTotal = typeof CACHE_TRONGRID[address].voteTotal !== 'undefined' ? CACHE_TRONGRID[address].voteTotal : 0 + const frozen = typeof CACHE_TRONGRID[address][tokenName + 'frozen'] !== 'undefined' ? CACHE_TRONGRID[address][tokenName + 'frozen'] : 0 + const frozenExpireTime = typeof CACHE_TRONGRID[address][tokenName + 'frozenExpireTime'] !== 'undefined' ? CACHE_TRONGRID[address][tokenName + 'frozenExpireTime'] : 0 + const frozenOthers = typeof CACHE_TRONGRID[address][tokenName + 'frozenOthers'] !== 'undefined' ? CACHE_TRONGRID[address][tokenName + 'frozenOthers'] : 0 + const frozenEnergy = typeof CACHE_TRONGRID[address][tokenName + 'frozenEnergy'] !== 'undefined' ? CACHE_TRONGRID[address][tokenName + 'frozenEnergy'] : 0 + const frozenEnergyExpireTime = typeof CACHE_TRONGRID[address][tokenName + 'frozenEnergyExpireTime'] !== 'undefined' ? CACHE_TRONGRID[address][tokenName + 'frozenEnergyExpireTime'] : 0 + const frozenEnergyOthers = typeof CACHE_TRONGRID[address][tokenName + 'frozenEnergyOthers'] !== 'undefined' ? CACHE_TRONGRID[address][tokenName + 'frozenEnergyOthers'] : 0 + return { + isMultisig: CACHE_TRONGRID[address].isMultisig, + balance: CACHE_TRONGRID[address][tokenName], + voteTotal, + frozen, + frozenExpireTime, + frozenOthers, + frozenEnergy, + frozenEnergyExpireTime, + frozenEnergyOthers, + unconfirmed: 0, + provider: 'trongrid-cache', + time: CACHE_TRONGRID[address].time + } + } else if (tokenName !== '_') { + return false + // return { balance: 0, unconfirmed : 0, provider: 'trongrid-cache' } + } + } + + // curl -X POST http://trx.trusteeglobal.com:8091/walletsolidity/getassetissuebyname -d + const nodeLink = BlocksoftExternalSettings.getStatic('TRX_SOLIDITY_NODE') + const link = nodeLink + '/walletsolidity/getaccount' + const params = { address } + BlocksoftCryptoLog.log('TrxTrongridProvider.get ' + link + ' ' + JSON.stringify(params)) + const res = await BlocksoftAxios.postWithoutBraking(link, params, BALANCE_MAX_TRY) + if (!res || !res.data) { + return false + } + + let isMultisig = false + if (res.data.active_permission) { + for (const perm of res.data.active_permission) { + if (perm.keys[0].address !== address) { + isMultisig = TronUtils.addressHexToStr(perm.keys[0].address) + } + } + } + + + CACHE_TRONGRID[address] = {} + CACHE_TRONGRID[address].time = now + CACHE_TRONGRID[address]._ = typeof res.data.balance !== 'undefined' ? res.data.balance : 0 + CACHE_TRONGRID[address].isMultisig = isMultisig + CACHE_TRONGRID[address]._frozen = typeof res.data.frozen !== 'undefined' && typeof res.data.frozen[0] !== 'undefined' ? res.data.frozen[0].frozen_balance : 0 + CACHE_TRONGRID[address]._frozenExpireTime = typeof res.data.frozen !== 'undefined' && typeof res.data.frozen[0] !== 'undefined' ? res.data.frozen[0].expire_time : 0 + CACHE_TRONGRID[address]._frozenOthers = typeof res.data.delegated_frozen_balance_for_bandwidth !== 'undefined' ? res.data.delegated_frozen_balance_for_bandwidth : 0 + CACHE_TRONGRID[address]._frozenEnergy = typeof res.data.account_resource !== 'undefined' + && typeof res.data.account_resource.frozen_balance_for_energy !== 'undefined' + && typeof res.data.account_resource.frozen_balance_for_energy.frozen_balance !== 'undefined' + ? res.data.account_resource.frozen_balance_for_energy.frozen_balance : 0 + CACHE_TRONGRID[address]._frozenEnergyExpireTime = typeof res.data.account_resource !== 'undefined' + && typeof res.data.account_resource.frozen_balance_for_energy !== 'undefined' + && typeof res.data.account_resource.frozen_balance_for_energy.expire_time !== 'undefined' + ? res.data.account_resource.frozen_balance_for_energy.expire_time : 0 + + CACHE_TRONGRID[address]._frozenEnergyOthers = 0 + if (typeof res.data.account_resource !== 'undefined' && typeof res.data.account_resource.delegated_frozen_balance_for_energy !== 'undefined' && res.data.account_resource.delegated_frozen_balance_for_energy * 1 > 0) { + CACHE_TRONGRID[address]._frozenEnergyOthers = res.data.account_resource.delegated_frozen_balance_for_energy * 1 + } + CACHE_TRONGRID[address].voteTotal = typeof res.data.votes !== 'undefined' && typeof res.data.votes[0] !== 'undefined' ? res.data.votes[0].vote_count : 0 + + if (res.data.assetV2) { + let token + for (token of res.data.assetV2) { + CACHE_TRONGRID[address][token.key] = token.value + } + } + + if (typeof CACHE_TRONGRID[address][tokenName] === 'undefined') { + return false + // return { balance: 0, unconfirmed : 0, provider: 'trongrid' } + } + + const balance = CACHE_TRONGRID[address][tokenName] + const frozen = typeof CACHE_TRONGRID[address][tokenName + 'frozen'] !== 'undefined' ? CACHE_TRONGRID[address][tokenName + 'frozen'] : 0 + const frozenExpireTime = typeof CACHE_TRONGRID[address][tokenName + 'frozenExpireTime'] !== 'undefined' ? CACHE_TRONGRID[address][tokenName + 'frozenExpireTime'] : 0 + const frozenOthers = typeof CACHE_TRONGRID[address][tokenName + 'frozenOthers'] !== 'undefined' ? CACHE_TRONGRID[address][tokenName + 'frozenOthers'] : 0 + const frozenEnergy = typeof CACHE_TRONGRID[address][tokenName + 'frozenEnergy'] !== 'undefined' ? CACHE_TRONGRID[address][tokenName + 'frozenEnergy'] : 0 + const frozenEnergyExpireTime = typeof CACHE_TRONGRID[address][tokenName + 'frozenEnergyExpireTime'] !== 'undefined' ? CACHE_TRONGRID[address][tokenName + 'frozenEnergyExpireTime'] : 0 + const frozenEnergyOthers = typeof CACHE_TRONGRID[address][tokenName + 'frozenEnergy'] !== 'undefined' ? CACHE_TRONGRID[address][tokenName + 'frozenEnergyOthers'] : 0 + const voteTotal = typeof CACHE_TRONGRID[address].voteTotal !== 'undefined' ? CACHE_TRONGRID[address].voteTotal : 0 + return { + isMultisig: CACHE_TRONGRID[address].isMultisig, + balance, + voteTotal, + frozen, + frozenExpireTime, + frozenOthers, + frozenEnergy, + frozenEnergyExpireTime, + frozenEnergyOthers, + unconfirmed: 0, + provider: 'trongrid ' + nodeLink, + time: CACHE_TRONGRID[address].time + } + } + + async getResources(address) { + const sendLink = BlocksoftExternalSettings.getStatic('TRX_SEND_LINK') + const link = sendLink + '/wallet/getaccountresource' + let leftBand = false + let totalBand = false + let leftEnergy = false + let totalEnergy = false + try { + const res = await BlocksoftAxios.post(link, { address }) + const tronData = res.data + delete tronData.assetNetUsed + delete tronData.assetNetLimit + await BlocksoftCryptoLog.log('TrxTrongridProvider.assets result ' + link + ' from ' + address, tronData) + totalBand = typeof tronData.freeNetLimit !== 'undefined' && tronData.freeNetLimit ? tronData.freeNetLimit : 0 + if (typeof tronData.NetLimit !== 'undefined' && tronData.NetLimit && tronData.NetLimit * 1 > 0) { + totalBand = totalBand * 1 + tronData.NetLimit * 1 + } + + leftBand = totalBand + if (typeof tronData.freeNetUsed !== 'undefined' && tronData.freeNetUsed) { + leftBand = leftBand - tronData.freeNetUsed * 1 + } + if (typeof tronData.NetUsed !== 'undefined' && tronData.NetUsed) { + leftBand = leftBand - tronData.NetUsed * 1 + } + + totalEnergy = typeof tronData.EnergyLimit !== 'undefined' && tronData.EnergyLimit ? tronData.EnergyLimit : 0 + leftEnergy = totalEnergy + if (typeof tronData.EnergyUsed !== 'undefined' && tronData.EnergyUsed) { + leftEnergy = leftEnergy - tronData.EnergyUsed * 1 + } + + } catch (e) { + + } + return { + leftBand, + totalBand, + leftEnergy, + totalEnergy + } + } +} diff --git a/crypto/blockchains/trx/basic/TrxTronscanProvider.js b/crypto/blockchains/trx/basic/TrxTronscanProvider.js new file mode 100644 index 000000000..14aa49feb --- /dev/null +++ b/crypto/blockchains/trx/basic/TrxTronscanProvider.js @@ -0,0 +1,81 @@ +/** + * @version 0.5 + * https://github.com/tronscan/tronscan-frontend/wiki/TRONSCAN-API + */ +import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog' +import BlocksoftAxios from '../../../common/BlocksoftAxios' + +const BALANCE_PATH = 'https://apilist.tronscan.org/api/account?address=' +const BALANCE_MAX_TRY = 10 + +const CACHE_TRONSCAN = {} +const CACHE_VALID_TIME = 3000 // 3 seconds + +export default class TrxTronscanProvider { + + /** + * https://apilist.tronscan.org/api/account?address=TUbHxAdhPk9ykkc7SDP5e9zUBEN14K65wk + * @param {string} address + * @param {string} tokenName + * @returns {Promise} + */ + async get(address, tokenName, useCache = true) { + const now = new Date().getTime() + if (useCache && typeof CACHE_TRONSCAN[address] !== 'undefined' && (now - CACHE_TRONSCAN[address].time) < CACHE_VALID_TIME) { + if (typeof CACHE_TRONSCAN[address][tokenName] !== 'undefined') { + BlocksoftCryptoLog.log('TrxTronscanProvider.get from cache', address + ' => ' + tokenName + ' : ' + CACHE_TRONSCAN[address][tokenName]) + const frozen = typeof CACHE_TRONSCAN[address][tokenName + 'frozen'] !== 'undefined' ? CACHE_TRONSCAN[address][tokenName + 'frozen'] : 0 + const frozenEnergy = typeof CACHE_TRONSCAN[address][tokenName + 'frozenEnergy'] !== 'undefined' ? CACHE_TRONSCAN[address][tokenName + 'frozenEnergy'] : 0 + const voteTotal = typeof CACHE_TRONSCAN[address].voteTotal !== 'undefined' ? CACHE_TRONSCAN[address].voteTotal : 0 + return { balance: CACHE_TRONSCAN[address][tokenName], voteTotal, frozen, frozenEnergy, unconfirmed : 0, provider: 'tronscan-cache', time : CACHE_TRONSCAN[address].time } + } else if (tokenName !== '_') { + return false + } + } + + const link = BALANCE_PATH + address + BlocksoftCryptoLog.log('TrxTronscanProvider.get ' + link) + const res = await BlocksoftAxios.getWithoutBraking(link, BALANCE_MAX_TRY) + if (!res || !res.data) { + return false + } + + CACHE_TRONSCAN[address] = {} + CACHE_TRONSCAN[address].time = now + CACHE_TRONSCAN[address]._ = res.data.balance + CACHE_TRONSCAN[address]._frozen = typeof res.data.frozen.total !== 'undefined' ? res.data.frozen.total : 0 + CACHE_TRONSCAN[address]._frozenEnergy = typeof res.data.accountResource !== 'undefined' + && typeof res.data.accountResource.frozen_balance_for_energy !== 'undefined' + && typeof res.data.accountResource.frozen_balance_for_energy.frozen_balance !== 'undefined' ? res.data.accountResource.frozen_balance_for_energy.frozen_balance : 0 + + CACHE_TRONSCAN[address].voteTotal = typeof res.data.voteTotal !== 'undefined' ? res.data.voteTotal : 0 + let token + if (res.data.tokenBalances) { + for (token of res.data.tokenBalances) { + const id = typeof token.name !== 'undefined' ? token.name : token.tokenId + CACHE_TRONSCAN[address][id] = token.balance + } + } + + if (res.data.trc20token_balances) { + for (token of res.data.trc20token_balances) { + const id = typeof token.name !== 'undefined' ? token.name : token.tokenId + CACHE_TRONSCAN[address][id] = token.balance + } + } + + if (typeof CACHE_TRONSCAN[address][tokenName] === 'undefined') { + if (tokenName.indexOf('T') === 0) { + return 0 + } else { + return false + } + } + + const balance = CACHE_TRONSCAN[address][tokenName] + const frozen = typeof CACHE_TRONSCAN[address][tokenName + 'frozen'] !== 'undefined' ? CACHE_TRONSCAN[address][tokenName + 'frozen'] : 0 + const frozenEnergy = typeof CACHE_TRONSCAN[address][tokenName + 'frozenEnergy'] !== 'undefined' ? CACHE_TRONSCAN[address][tokenName + 'frozenEnergy'] : 0 + const voteTotal = typeof CACHE_TRONSCAN[address].voteTotal !== 'undefined' ? CACHE_TRONSCAN[address].voteTotal : 0 + return { balance, frozen, frozenEnergy, voteTotal, unconfirmed: 0, provider: 'tronscan', time : CACHE_TRONSCAN[address].time } + } +} diff --git a/crypto/blockchains/trx/dict/swaps.js b/crypto/blockchains/trx/dict/swaps.js new file mode 100644 index 000000000..51da6f4a9 --- /dev/null +++ b/crypto/blockchains/trx/dict/swaps.js @@ -0,0 +1,12 @@ +module.exports = { + TTnSHzUoho1CU6zFYVzVSCKq8EX8ZddkVv: 'JUSTSWAP-WTRX-TRX', + TJmTeYk5zmg8pNPGYbDb2psadwVLYDDYDr: 'JUSTSWAP-DICE-TRX', + TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE: 'JUSTSWAP-USDT-TRX', + TQcia2H2TU3WrFk9sKtdK9qCfkW8XirfPQ: 'JUSTSWAP-USDJ-TRX', + TLLBBiX3HqVZZsUQTBXgurA3pdw317PmjM: 'JUSTSWAP-HT-TRX', + TYN6Wh11maRfzgG7n5B6nM5VW1jfGs9chu: 'JUSTSWAP-WIN-TRX', + TUEYcyPAqc4hTg1fSuBCPc18vGWcJDECVw: 'JUSTSWAP-SUN-TRX', + TKAtLoCB529zusLfLVkGvLNis6okwjB7jf: 'JUSTSWAP-BTC-TRX', + TYukBQZ2XXCcRCReAUguyXncCWNY9CEiDQ: 'JUSTSWAP-JST-TRX', + TH2mEwTKNgtg8psR6Qx2RBUXZ48Lon1ygu: 'JUSTSWAP-WBTT-TRX' +} diff --git a/crypto/blockchains/trx/ext/TronStakeUtils.js b/crypto/blockchains/trx/ext/TronStakeUtils.js new file mode 100644 index 000000000..bef1e70f7 --- /dev/null +++ b/crypto/blockchains/trx/ext/TronStakeUtils.js @@ -0,0 +1,116 @@ +import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings' +import BlocksoftPrettyNumbers from '@crypto/common/BlocksoftPrettyNumbers' +import BlocksoftBalances from '@crypto/actions/BlocksoftBalances/BlocksoftBalances' +import TronUtils from '@crypto/blockchains/trx/ext/TronUtils' + +import BlocksoftAxios from '@crypto/common/BlocksoftAxios' +import Log from '@app/services/Log/Log' +import { BlocksoftTransfer } from '@crypto/actions/BlocksoftTransfer/BlocksoftTransfer' +import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog' + +const TronStakeUtils = { + + async getVoteAddresses() { + return BlocksoftExternalSettings.getStatic('TRX_VOTE_BEST') + }, + + async getPrettyBalance(address) { + const balance = await (BlocksoftBalances.setCurrencyCode('TRX').setAddress(address).getBalance('TronStakeUtils')) + if (!balance) { + return false + } + balance.prettyBalanceAvailable = BlocksoftPrettyNumbers.setCurrencyCode('TRX').makePretty(balance.balanceAvailable) + balance.prettyFrozen = BlocksoftPrettyNumbers.setCurrencyCode('TRX').makePretty(balance.frozen) + balance.prettyFrozenOthers = BlocksoftPrettyNumbers.setCurrencyCode('TRX').makePretty(balance.frozenOthers) + balance.prettyFrozenEnergy = BlocksoftPrettyNumbers.setCurrencyCode('TRX').makePretty(balance.frozenEnergy) + balance.prettyFrozenEnergyOthers = BlocksoftPrettyNumbers.setCurrencyCode('TRX').makePretty(balance.frozenEnergyOthers) + balance.prettyVote = (balance.prettyFrozen * 1 + balance.prettyFrozenOthers * 1 + balance.prettyFrozenEnergy * 1 + balance.prettyFrozenEnergyOthers * 1).toString().split('.')[0] + + const maxExpire = balance.frozenEnergyExpireTime && balance.frozenEnergyExpireTime > balance.frozenExpireTime ? + balance.frozenEnergyExpireTime : balance.frozenExpireTime + if (maxExpire > 0) { + balance.diffLastStakeMinutes = 24 * 3 * 60 - (maxExpire - new Date().getTime()) / 60000 // default time = 3 days, so thats how many minutes from last stake + } else { + balance.diffLastStakeMinutes = -1 + } + return balance + }, + + async sendVoteAll(address, derivationPath, walletHash, specialActionNeeded) { + + const { prettyVote, diffLastStakeMinutes, voteTotal } = await TronStakeUtils.getPrettyBalance(address) + if (diffLastStakeMinutes === -1 && specialActionNeeded === 'vote_after_unfreeze') { + BlocksoftCryptoLog.log('TronStake.sendVoteAll ' + address + ' continue ' + diffLastStakeMinutes) + } else if (!diffLastStakeMinutes || diffLastStakeMinutes < 3) { + BlocksoftCryptoLog.log('TronStake.sendVoteAll ' + address + ' skipped vote1 by ' + diffLastStakeMinutes) + return false + } + if (!prettyVote || typeof prettyVote === 'undefined') { + BlocksoftCryptoLog.log('TronStake.sendVoteAll ' + address + ' skipped vote2') + return false + } else if (voteTotal * 1 === prettyVote * 1) { + if (diffLastStakeMinutes > 100) { + BlocksoftCryptoLog.log('TronStake.sendVoteAll ' + address + ' skipped vote3 ' + voteTotal + ' by ' + diffLastStakeMinutes) + return true // all done + } + BlocksoftCryptoLog.log('TronStake.sendVoteAll ' + address + ' skipped vote4 ' + voteTotal) + return false + } + + BlocksoftCryptoLog.log('TronStake.sendVoteAll ' + address + ' started vote ' + prettyVote + ' by ' + diffLastStakeMinutes) + + const voteAddress = await TronStakeUtils.getVoteAddresses() + return TronStakeUtils._send('/wallet/votewitnessaccount', { + owner_address: TronUtils.addressToHex(address), + votes: [ + { + vote_address: TronUtils.addressToHex(voteAddress), + vote_count: prettyVote * 1 + } + ] + }, 'vote ' + prettyVote + ' for ' + voteAddress, { + walletHash, + address, + derivationPath, + type: 'vote', + cryptoValue: BlocksoftPrettyNumbers.setCurrencyCode('TRX').makeUnPretty(prettyVote * 1), + callback: () => { + } + }) + }, + + async _send(shortLink, params, langMsg, uiParams) { + + const sendLink = BlocksoftExternalSettings.getStatic('TRX_SEND_LINK') + const link = sendLink + shortLink + const tmp = await BlocksoftAxios.post(link, params) + let blockchainData + + if (typeof tmp.data !== 'undefined') { + if (typeof tmp.data.raw_data_hex !== 'undefined') { + blockchainData = tmp.data + } else { + Log.log('TronStakeUtils._send no rawHex ' + link, params, tmp.data) + throw new Error(JSON.stringify(tmp.data)) + } + } else { + Log.log('TronStakeUtils rawHex empty data ' + link, params) + throw new Error('Empty data') + } + + const txData = { + currencyCode: 'TRX', + walletHash: uiParams.walletHash, + derivationPath: uiParams.derivationPath, + addressFrom: uiParams.address, + addressTo: '', + blockchainData + } + + const result = await BlocksoftTransfer.sendTx(txData, { selectedFee: { langMsg } }) + return result + + } +} + +export default TronStakeUtils diff --git a/crypto/blockchains/trx/ext/TronUtils.js b/crypto/blockchains/trx/ext/TronUtils.js new file mode 100644 index 000000000..2d09adfab --- /dev/null +++ b/crypto/blockchains/trx/ext/TronUtils.js @@ -0,0 +1,92 @@ +/** + * @version 0.9 + */ +const elliptic = require('elliptic') +const ec = new elliptic.ec('secp256k1') +const createHash = require('create-hash') + +const EthUtil = require('ethereumjs-util') + +function byte2hexStr(byte) { + if (typeof byte !== 'number') + throw new Error('Input must be a number') + + if (byte < 0 || byte > 255) + throw new Error('Input must be a byte') + + const hexByteMap = '0123456789ABCDEF' + + let str = '' + str += hexByteMap.charAt(byte >> 4) + str += hexByteMap.charAt(byte & 0x0f) + return str +} + +// noinspection JSConstructorReturnsPrimitive +export default { + // noinspection JSConstructorReturnsPrimitive + ECKeySign : function(hashBytes, privateBytes) { + const key = ec.keyFromPrivate(privateBytes, 'bytes') + const signature = key.sign(hashBytes) + const r = signature.r + const s = signature.s + const id = signature.recoveryParam + + let rHex = r.toString('hex') + + while (rHex.length < 64) { + rHex = `0${rHex}` + } + + let sHex = s.toString('hex') + + while (sHex.length < 64) { + sHex = `0${sHex}` + } + + const idHex = byte2hexStr(id) + return rHex + sHex + idHex + }, + + addressToHex: function(address) { + if (address.substr(0, 2) === '41') { + return address + } + const bs58 = require('bs58') + const decoded = bs58.decode(address.trim()) + return decoded.slice(0,21).toString('hex') + }, + + privHexToPubHex: function(privateHex) { + const key = ec.keyFromPrivate(privateHex, 'hex') + const pubkey = key.getPublic() + const x = pubkey.x + const y = pubkey.y + + let xHex = x.toString('hex') + while (xHex.length < 64) { + xHex = `0${xHex}` + } + let yHex = y.toString('hex') + while (yHex.length < 64) { + yHex = `0${yHex}` + } + return `04${xHex}${yHex}` + }, + + pubHexToAddressHex: function(pubHex) { // actually the same as direct but better code + if (pubHex.substr(0, 2) === '04') { + pubHex = '0x' + pubHex.substr(2) + } + return '41' + EthUtil.publicToAddress(pubHex).toString('hex') + }, + + addressHexToStr: function(addressHex) { + const one = createHash('sha256').update(addressHex, 'hex').digest('hex') + const hash = createHash('sha256').update(one, 'hex').digest() + const checksum = hash.slice(0, 4) // checkSum = the first 4 bytes of hash + const checkSummed = addressHex + checksum.toString('hex') + const bs58 = require('bs58') + return bs58.encode(Buffer.from(checkSummed, 'hex')) + } +} diff --git a/crypto/blockchains/trx/providers/TrxSendProvider.ts b/crypto/blockchains/trx/providers/TrxSendProvider.ts new file mode 100644 index 000000000..2b77e0064 --- /dev/null +++ b/crypto/blockchains/trx/providers/TrxSendProvider.ts @@ -0,0 +1,144 @@ +/** + * @version 0.41 + */ +import { BlocksoftBlockchainTypes } from '@crypto/blockchains/BlocksoftBlockchainTypes' +import DogeSendProvider from '@crypto/blockchains/doge/providers/DogeSendProvider' +import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog' + +import BlocksoftAxios from '@crypto/common/BlocksoftAxios' +import config from '@app/config/config' +import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings' + +export default class TrxSendProvider extends DogeSendProvider implements BlocksoftBlockchainTypes.SendProvider { + + + trxError(msg: string) { + if (config.debug.cryptoErrors) { + console.log(this._settings.currencyCode + ' TrxSendProvider ' + msg) + } + if (this._settings.currencyCode !== 'TRX' && msg.indexOf('AccountResourceInsufficient') !== -1) { + throw new Error('SERVER_RESPONSE_NOT_ENOUGH_FEE') + } else if (msg.indexOf('Validate TransferContract error, balance is not sufficient.') !== -1) { + throw new Error('SERVER_RESPONSE_NOTHING_TO_TRANSFER_FROM_ACTUAL_NODE') + } else if (msg.indexOf('balance is not sufficient') !== -1) { + throw new Error('SERVER_RESPONSE_NOT_ENOUGH_FEE') + } else if (msg.indexOf('account not exist') !== -1) { + throw new Error('SERVER_RESPONSE_NOT_ENOUGH_FEE') + } else if (msg.indexOf('Amount must greater than 0') !== -1) { + throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_DUST') + } else if (msg.indexOf('assetBalance must be greater than 0') !== -1 || msg.indexOf('assetBalance is not sufficient') !== -1) { + throw new Error('SERVER_RESPONSE_NOTHING_TO_TRANSFER_FROM_ACTUAL_NODE') + } else { + throw new Error(msg) + } + } + + async _sendTx(tx: any, subtitle: string, txRBF: any, logData: any): Promise<{ transactionHash: string, logData : any }> { + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' TrxSendProvider._sendTx ' + subtitle + ' started ', logData) + + const sendLink = BlocksoftExternalSettings.getStatic('TRX_SEND_LINK') + const link = sendLink + '/wallet/broadcasttransaction' + if (config.debug.cryptoErrors) { + console.log(new Date().toISOString() + ' ' + this._settings.currencyCode + ' TrxSendProvider._sendTx ' + subtitle + ' started check ') + } + logData = await this._check(tx.raw_data_hex, subtitle, txRBF, logData) + if (config.debug.cryptoErrors) { + BlocksoftCryptoLog.log(new Date().toISOString() + ' ' + this._settings.currencyCode + ' TrxSendProvider._sendTx ' + subtitle + ' ended check ') + } + + let send = false + try { + send = await BlocksoftAxios.post(link, tx) + } catch (e) { + if (config.debug.cryptoErrors) { + console.log(this._settings.currencyCode + ' TrxSendProvider._sendTx broadcast error ' + e.message) + } + } + + // @ts-ignore + if (!send || typeof send.data === 'undefined' || !send.data) { + throw new Error('SERVER_RESPONSE_NOT_CONNECTED') + } + + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' TrxSendProvider._sendTx ' + subtitle + ' result ', send.data) + + if (typeof send.data.code !== 'undefined') { + if (send.data.code === 'BANDWITH_ERROR') { + throw new Error('SERVER_RESPONSE_BANDWITH_ERROR_TRX') + } else if (send.data.code === 'SERVER_BUSY') { + throw new Error('SERVER_RESPONSE_NOT_CONNECTED') + } + } + + // @ts-ignore + if (typeof send.data.Error !== 'undefined') { + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' TrxSendProvider._sendTx error ' + send.data.Error) + // @ts-ignore + throw new Error(send.data.Error) + } + // @ts-ignore + if (typeof send.data.result === 'undefined') { + // @ts-ignore + if (typeof send.data.message !== 'undefined') { + let msg = false + try { + // @ts-ignore + const buf = Buffer.from(send.data.message, 'hex') + // @ts-ignore + msg = buf.toString('') + } catch (e) { + // do nothing + } + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' TrxSendProvider._sendTx msg ' + msg) + if (msg) { + // @ts-ignore + send.data.decoded = msg + // @ts-ignore + this.trxError(msg) + } + } + // @ts-ignore + this.trxError('no transaction result ' + JSON.stringify(send.data)) + } else { + // @ts-ignore + if (send.data.result !== true) { + // @ts-ignore + this.trxError('transaction result is false ' + JSON.stringify(send.data)) + } + } + + return {transactionHash : tx.txID, logData} + } + + async sendTx(tx: any, subtitle: string, txRBF: any, logData: any): Promise<{ transactionHash: string, transactionJson: any, logData }> { + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' TrxSendProvider.sendTx ' + subtitle + ' started ', logData) + + let send, transactionHash + try { + send = await this._sendTx(tx, subtitle, txRBF, logData) + transactionHash = send.transactionHash + } catch (e) { + if (config.debug.cryptoErrors) { + console.log(this._settings.currencyCode + ' TrxSendProvider.sendTx error ', e) + } + try { + logData.error = e.message + await this._checkError(tx.raw_data_hex, subtitle, txRBF, logData) + } catch (e2) { + if (config.debug.cryptoErrors) { + console.log(this._settings.currencyCode + ' TrxSendProvider.send proxy error errorTx ' + e.message) + } + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' TrxSendProvider.send proxy error errorTx ' + e2.message) + } + throw e + } + + + try { + logData = await this._checkSuccess(transactionHash, tx.raw_data_hex, subtitle, txRBF, logData) + } catch (e) { + throw new Error(e.message + ' in _checkSuccess wrapped TRX') + } + return { transactionHash, transactionJson: {}, logData } + } +} diff --git a/crypto/blockchains/usdt/UsdtScannerProcessor.js b/crypto/blockchains/usdt/UsdtScannerProcessor.js new file mode 100644 index 000000000..7563121bf --- /dev/null +++ b/crypto/blockchains/usdt/UsdtScannerProcessor.js @@ -0,0 +1,228 @@ +/** + * @version 0.5 + */ +import BlocksoftUtils from '../../common/BlocksoftUtils' +import BlocksoftAxios from '../../common/BlocksoftAxios' +import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog' +import BlocksoftDispatcher from '../BlocksoftDispatcher' + +const USDT_API = 'https://microscanners.trustee.deals/usdt' // https://microscanners.trustee.deals/usdt/1CmAoxq8BTxANRDwheJUpaGy6ngWNYX85 +const USDT_API_MASS = 'https://microscanners.trustee.deals/balanceMass' + +const CACHE_VALID_TIME = 30000 // 30 seconds +const CACHE = {} + +export default class UsdtScannerProcessor { + /** + * @type {number} + */ + lastBlock = 0 + + /** + * @type {number} + * @private + */ + _blocksToConfirm = 1 + + /** + * @type {boolean|BtcScannerProcessor} + * @private + */ + _btcProvider = false + + + /** + * @param address + * @returns {Promise} + * @private + */ + async _get(address) { + const now = new Date().getTime() + if (typeof CACHE[address] !== 'undefined' && (now - CACHE[address].time < CACHE_VALID_TIME)) { + CACHE[address].provider = 'usdt-cache' + return CACHE[address] + } + const link = `${USDT_API}/${address}` + const res = await BlocksoftAxios.getWithoutBraking(link) + if (!res || typeof res.data === 'undefined' || !res.data) { + return false + } + if (typeof res.data.status === 'undefined') { + throw new Error(' UsdtScannerProcessor._get bad status loaded for address ' + link, res) + } + if (typeof res.data.data === 'undefined' || typeof res.data.data.balance === 'undefined') { + throw new Error(' UsdtScannerProcessor._get nothing loaded for address ' + link) + } + if (typeof CACHE[address] !== 'undefined') { + if (CACHE[address].data.block > res.data.data.block) { + return false + } + } + CACHE[address] = { + data: res.data.data, + time: now, + provider: 'usdt' + } + return CACHE[address] + } + + /** + * @param address + * @returns {Promise} + * @private + */ + async _getMass(address) { + const now = new Date().getTime() + + // mass ask + const link = `${USDT_API_MASS}` + const res = await BlocksoftAxios.postWithoutBraking(link, address) + + if (!res || typeof res.data === 'undefined') { + return false + } + return { + data: res.data.data, + time: now, + provider: 'usdt' + } + } + + /** + * @param {string} address + * @return {Promise<{int:balance, int:provider}>} + */ + async getBalanceBlockchain(address) { + if (typeof address === 'object') { + BlocksoftCryptoLog.log('UsdtScannerProcessor.getBalance started MASS ' + JSON.stringify(address)) + return this._getMass(address) + } + + BlocksoftCryptoLog.log('UsdtScannerProcessor.getBalance started ' + address) + const tmp = await this._get(address) + if (typeof tmp === 'undefined' || !tmp || typeof tmp.data === 'undefined') { + BlocksoftCryptoLog.log('UsdtScannerProcessor.getBalance bad tmp ', tmp) + return false + } + if (!tmp.data || typeof tmp.data.balance === 'undefined' || tmp.data.balance === false) { + BlocksoftCryptoLog.log('UsdtScannerProcessor.getBalance bad tmp.data ', tmp.data) + return false + } + const balance = tmp.data.balance + BlocksoftCryptoLog.log('UsdtScannerProcessor.getBalance finished', address + ' => ' + balance) + return { balance, provider: tmp.provider, time: tmp.time, unconfirmed: 0, balanceScanBlock: tmp.data.block } + } + + /** + * @param {string} scanData.account.address + * @return {Promise} + */ + async getTransactionsBlockchain(scanData, source = '') { + const address = scanData.account.address.trim() + BlocksoftCryptoLog.log('UsdtScannerProcessor.getTransactions started ' + address) + let tmp = await this._get(address) + if (!tmp || typeof tmp.data === 'undefined') { + BlocksoftCryptoLog.log('UsdtScannerProcessor.getTransactions bad tmp ', tmp) + return [] + } + if (!tmp.data) { + BlocksoftCryptoLog.log('UsdtScannerProcessor.getTransactions bad tmp.data ', tmp.data) + return [] + } + + tmp = tmp.data + if (typeof tmp.data !== 'undefined') { + tmp = tmp.data // wtf but ok to support old wallets + } + if (typeof tmp.txs === 'undefined') { + throw new Error('Undefined txs ' + JSON.stringify(tmp)) + } + + const transactions = [] + if (tmp.block > this.lastBlock) { + this.lastBlock = tmp.block + } + let tx + const unique = {} + if (tmp.txs && tmp.txs.length > 0) { + for (tx of tmp.txs) { + const transaction = await this._unifyTransaction(address, tx) + transactions.push(transaction) + unique[transaction.transactionHash] = 1 + } + } + let btcTxs = false + try { + if (!this._btcProvider) { + this._btcProvider = await (new BlocksoftDispatcher()).getScannerProcessor({ currencyCode: 'BTC' }) + } + btcTxs = await this._btcProvider.getTransactionsBlockchain(scanData, source + ' UsdtScannerProcessor') + } catch (e) { + + } + if (btcTxs && btcTxs.length > 0) { + for (tx of btcTxs) { + if (typeof unique[tx.transactionHash] !== 'undefined') continue + transactions.push({ + blockConfirmations: tx.blockConfirmations, + blockTime: tx.blockTime, + transactionDirection: tx.transactionDirection, + transactionHash: tx.transactionHash, + transactionStatus: tx.transactionStatus + }) + } + } + BlocksoftCryptoLog.log('UsdtScannerProcessor.getTransactions finished ' + address + ' total: ' + transactions.length) + return transactions + } + + /** + * + * @param {string} address + * @param {Object} transaction + * @param {string} transaction.block_number: 467352, + * @param {string} transaction.transaction_block_hash: '0000000000000000018e86423804e917c75348090419a46e506bc2d4818c2827', + * @param {string} transaction.transaction_hash: '7daaa478c829445c967d4607345227286a23acd20f5bc80709e418d0e286ecf1', + * @param {string} transaction.transaction_txid: '7daaa478c829445c967d4607345227286a23acd20f5bc80709e418d0e286ecf1', + * @param {string} transaction.from_address: '1GYmxyavRvjCMsmfDR2uZLMsCPoFNYw9zM', + * @param {string} transaction.to_address: '1Po1oWkD2LmodfkBYiAktwh76vkF93LKnh', + * @param {string} transaction.amount: 0.744019, + * @param {string} transaction.fee: 0.0008, + * @param {string} transaction.custom_type: '', + * @param {string} transaction.custom_valid: '', + * @param {string} transaction.created_time: '2017-05-20T22:28:15.000Z', + * @param {string} transaction.updated_time: null, + * @param {string} transaction.removed_time: null, + * @param {string} transaction._removed: 0, + * @return {UnifiedTransaction} + * @private + */ + async _unifyTransaction(address, transaction) { + const confirmations = this.lastBlock - transaction.block_number + let transactionStatus = 'new' + if (confirmations >= this._blocksToConfirm) { + transactionStatus = 'success' + } else if (confirmations > 0) { + transactionStatus = 'confirming' + } + const tx = { + transactionHash: transaction.transaction_txid, + blockHash: transaction.transaction_block_hash, + blockNumber: +transaction.block_number, + blockTime: transaction.created_time, + blockConfirmations: confirmations, + transactionDirection: (address.toLowerCase() === transaction.from_address.toLowerCase()) ? 'outcome' : 'income', + addressFrom: transaction.from_address === address ? '' : transaction.from_address, + addressTo: transaction.to_address === address ? '' : transaction.to_address, + addressAmount: transaction.amount, + transactionStatus: (transaction.custom_valid.toString() === '1' && transaction._removed.toString() === '0') ? transactionStatus : 'fail', + transactionFee: BlocksoftUtils.toSatoshi(transaction.fee), + inputValue: transaction.custom_type + } + if (tx.addressTo === '' && tx.addressFrom === '') { + tx.transactionDirection = 'self' + tx.addressAmount = 0 + } + return tx + } +} diff --git a/crypto/blockchains/usdt/UsdtTransferProcessor.ts b/crypto/blockchains/usdt/UsdtTransferProcessor.ts new file mode 100644 index 000000000..d31d77a91 --- /dev/null +++ b/crypto/blockchains/usdt/UsdtTransferProcessor.ts @@ -0,0 +1,102 @@ +/** + * @version 0.20 + */ +import { BlocksoftBlockchainTypes } from '../BlocksoftBlockchainTypes' +import BtcUnspentsProvider from '../btc/providers/BtcUnspentsProvider' +import DogeSendProvider from '../doge/providers/DogeSendProvider' +import UsdtTxInputsOutputs from './tx/UsdtTxInputsOutputs' +import UsdtTxBuilder from './tx/UsdtTxBuilder' +import BtcNetworkPrices from '../btc/basic/BtcNetworkPrices' +import BtcTransferProcessor from '../btc/BtcTransferProcessor' +import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog' +import DaemonCache from '../../../app/daemons/DaemonCache' + +export default class UsdtTransferProcessor extends BtcTransferProcessor implements BlocksoftBlockchainTypes.TransferProcessor { + + _trezorServerCode = 'BTC_TREZOR_SERVER' + + _builderSettings: BlocksoftBlockchainTypes.BuilderSettings = { + minOutputDustReadable: 0.000001, + minChangeDustReadable: 0.000001, + feeMaxForByteSatoshi: 1000, // for tx builder + feeMaxAutoReadable2: 0.01, // for fee calc, + feeMaxAutoReadable6: 0.005, // for fee calc + feeMaxAutoReadable12: 0.001, // for fee calc + changeTogether: false, + minRbfStepSatoshi: 50, + minSpeedUpMulti : 1.5 + } + + _initProviders() { + if (this._initedProviders) return false + this.unspentsProvider = new BtcUnspentsProvider(this._settings, this._trezorServerCode) + this.sendProvider = new DogeSendProvider(this._settings, this._trezorServerCode) + this.txPrepareInputsOutputs = new UsdtTxInputsOutputs(this._settings, this._builderSettings) + this.txBuilder = new UsdtTxBuilder(this._settings, this._builderSettings) + this.networkPrices = new BtcNetworkPrices() + this._initedProviders = true + } + + async checkTransferHasError(data: BlocksoftBlockchainTypes.CheckTransferHasErrorData): Promise { + // @ts-ignore + const tmp = await DaemonCache.getCacheAccount(data.walletHash, 'BTC') + if (tmp.balance * 1 > 0) { + return { isOk: true } + } else { + return { isOk: false, code: 'TOKEN', parentBlockchain: 'Bitcoin', parentCurrency: 'BTC' } + } + } + + checkSendAllModal(data: { currencyCode: any }): boolean { + return true + } + + async getFeeRate(data: BlocksoftBlockchainTypes.TransferData, privateData: BlocksoftBlockchainTypes.TransferPrivateData, additionalData: BlocksoftBlockchainTypes.TransferAdditionalData = {}) + : Promise { + const tmpData = { ...data } + tmpData.isTransferAll = false + // @ts-ignore + BlocksoftCryptoLog.log(this._settings.currencyCode + ' UsdtTxProcessor.getFeeRate started') + const result = await super.getFeeRate(tmpData, privateData, additionalData) + for (const fee of result.fees) { + fee.amountForTx = data.amount + } + return result + } + + async getTransferAllBalance(data: BlocksoftBlockchainTypes.TransferData, privateData: BlocksoftBlockchainTypes.TransferPrivateData, additionalData: BlocksoftBlockchainTypes.TransferAdditionalData = {}): Promise { + const balance = data.amount + // @ts-ignore + BlocksoftCryptoLog.log(this._settings.currencyCode + ' UsdtTransferProcessor.getTransferAllBalance ', data.addressFrom + ' => ' + balance) + // noinspection EqualityComparisonWithCoercionJS + if (balance === '0') { + return { + selectedTransferAllBalance: '0', + selectedFeeIndex: -1, + fees: [], + countedForBasicBalance: '0', + countedTime : new Date().getTime() + } + } + const fees = await this.getFeeRate(data, privateData, additionalData) + if (!fees || fees.selectedFeeIndex < 0) { + return { + selectedTransferAllBalance: balance, + selectedFeeIndex: -2, + fees: [], + countedForBasicBalance: balance, + countedTime : new Date().getTime() + } + } + return { + ...fees, + selectedTransferAllBalance: balance + } + } + + async sendTx(data: BlocksoftBlockchainTypes.TransferData, privateData: BlocksoftBlockchainTypes.TransferPrivateData, uiData: BlocksoftBlockchainTypes.TransferUiData): Promise { + const result = await super.sendTx(data, privateData, uiData) + result.transactionFeeCurrencyCode = 'BTC' + return result + } +} diff --git a/crypto/blockchains/usdt/tx/UsdtTxBuilder.ts b/crypto/blockchains/usdt/tx/UsdtTxBuilder.ts new file mode 100644 index 000000000..abdcf7b9c --- /dev/null +++ b/crypto/blockchains/usdt/tx/UsdtTxBuilder.ts @@ -0,0 +1,45 @@ +/** + * @version 0.20 + */ +import { BlocksoftBlockchainTypes } from '../../BlocksoftBlockchainTypes' +import BtcTxBuilder from '../../btc/tx/BtcTxBuilder' +import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog' + +import { TransactionBuilder, script, opcodes } from 'bitcoinjs-lib' + +const USDT_TOKEN_ID = 31 + +function toPaddedHexString(num, len) { + const str = num.toString(16) + return "0".repeat(len - str.length) + str +} + +function createOmniSimpleSend(amountInUSD: string, propertyID = USDT_TOKEN_ID) { + BlocksoftCryptoLog.log('UsdtTxBuilder.createOmniSimpleSend started') + const simpleSend = [ + '6f6d6e69', // omni + '0000', // tx type + '0000', // version + toPaddedHexString(propertyID, 8), + toPaddedHexString(Math.floor(amountInUSD * 100000000), 16) + ].join('') + + return script.compile([ + opcodes.OP_RETURN, + Buffer.from(simpleSend, 'hex') + ]) +} + +export default class UsdtTxBuilder extends BtcTxBuilder implements BlocksoftBlockchainTypes.TxBuilder { + _getRawTxAddOutput(txb: TransactionBuilder, output: BlocksoftBlockchainTypes.OutputTx): void { + if (typeof output.tokenAmount !== 'undefined' && output.tokenAmount && output.tokenAmount !== '0') { + const omniOutput = createOmniSimpleSend(output.tokenAmount) + txb.addOutput(omniOutput, 0) + } else { + if (typeof output.amount !== 'undefined' && output.amount.toString() === '0') { + output.amount = '546' + } + super._getRawTxAddOutput(txb, output) + } + } +} diff --git a/crypto/blockchains/usdt/tx/UsdtTxInputsOutputs.ts b/crypto/blockchains/usdt/tx/UsdtTxInputsOutputs.ts new file mode 100644 index 000000000..e35e5cc8e --- /dev/null +++ b/crypto/blockchains/usdt/tx/UsdtTxInputsOutputs.ts @@ -0,0 +1,271 @@ +/** + * @version 0.20 + */ +import { BlocksoftBlockchainTypes } from '../../BlocksoftBlockchainTypes' +import BtcTxInputsOutputs from '../../btc/tx/BtcTxInputsOutputs' +import BlocksoftBN from '../../../common/BlocksoftBN' +import BlocksoftUtils from '../../../common/BlocksoftUtils' +import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog' +import DaemonCache from '../../../../app/daemons/DaemonCache' + +export default class UsdtTxInputsOutputs extends BtcTxInputsOutputs implements BlocksoftBlockchainTypes.TxInputsOutputs { + + DUST_FIRST_TRY = 546 + SIZE_FOR_BASIC = 442 + + _coinSelectTargets(data: BlocksoftBlockchainTypes.TransferData, unspents: BlocksoftBlockchainTypes.UnspentTx[], feeForByte: string, multiAddress: string[], subtitle: string) { + const targets = [ + { address: data.addressTo, value: 0, logType: 'FOR_USDT_AMOUNT' }, + { address: data.addressTo, value: this.DUST_FIRST_TRY, logType: 'FOR_USDT_BTC_OUTPUT' } + ] + return targets + } + + _usualTargets(data: BlocksoftBlockchainTypes.TransferData, unspents: BlocksoftBlockchainTypes.UnspentTx[]) { + const basicWishedAmountBN = new BlocksoftBN(0) + const wishedAmountBN = new BlocksoftBN(basicWishedAmountBN) + + const outputs = [] + + outputs.push({ + to: data.addressTo, + amount: 0, + logType: 'FOR_USDT_AMOUNT' + }) + + return { + multiAddress: [], + basicWishedAmountBN, + wishedAmountBN, + outputs + } + } + + _addressForChange(data: BlocksoftBlockchainTypes.TransferData): string { + return data.addressFrom + } + + async getInputsOutputs(data: BlocksoftBlockchainTypes.TransferData, unspents: BlocksoftBlockchainTypes.UnspentTx[], + feeToCount: { feeForByte?: string, feeForAll?: string, autoFeeLimitReadable?: string | number }, + additionalData: BlocksoftBlockchainTypes.TransferAdditionalData, + subtitle: string = 'default') + : Promise { + let res = await super._getInputsOutputs(data, unspents, feeToCount, additionalData, subtitle + ' usdted') + let inputIsFound = false + let newInputs = [] + let oldInputs = [] + let addressFromUsdtOutputs = 0 + let newInputAdded = false + for (const input of res.inputs) { + if (input.address === data.addressFrom) { + if (!inputIsFound) { + newInputs.push(input) + } else { + oldInputs.push(input) + } + inputIsFound = true + addressFromUsdtOutputs++ + } else { + oldInputs.push(input) + } + } + if (!inputIsFound) { + for (const unspent of unspents) { + if (unspent.address === data.addressFrom) { + if (!inputIsFound) { + newInputs.push(unspent) + newInputAdded = unspent + } + inputIsFound = true + addressFromUsdtOutputs++ + } + } + } + for (const input of oldInputs) { + newInputs.push(input) + } + if (newInputAdded) { + let changeIsFound = false + for (const output of res.outputs) { + if (typeof output.isChange !== 'undefined' && output.isChange) { + output.amount = BlocksoftUtils.add(output.amount, newInputAdded.value) + changeIsFound = true + } + } + if (!changeIsFound && newInputAdded.value !== '546') { + res.outputs.push({ + // @ts-ignore + to: data.addressFrom, + // @ts-ignore + amount: newInputAdded.value.toString(), + // @ts-ignore + isChange: true + }) + } + } + const tmp = typeof additionalData.balance !== 'undefined' && additionalData.balance ? { balance: additionalData.balance } : DaemonCache.getCacheAccountStatic(data.walletHash, 'USDT') + let needOneOutput = false + if (tmp.balance > 0) { + const diff = BlocksoftUtils.diff(tmp.balance, data.amount) + BlocksoftCryptoLog.log('USDT addressFromUsdtOutputs = ' + addressFromUsdtOutputs + ' balance ' + tmp.balance + ' diff ' + diff + '>0=' + (diff > 0 ? 'true' : 'false')) + if (addressFromUsdtOutputs < 2 && diff > 0) { + needOneOutput = true + } + } + if (res.inputs.length === 0 && (!inputIsFound || newInputAdded.value === '546')) { + throw new Error('SERVER_RESPONSE_NOT_ENOUGH_FEE_JUST_DUST') + } + res.inputs = newInputs + if (res.inputs.length === 0 || !inputIsFound) { + throw new Error('SERVER_RESPONSE_LEGACY_BALANCE_NEEDED_USDT') + } + + const totalOuts = res.outputs.length + if (totalOuts === 0) { + const newRes = JSON.parse(JSON.stringify(res)) + newRes.outputs = [] + if (needOneOutput && + ( + res.inputs.length > 1 || res.inputs[0].value * 1 >= this.DUST_FIRST_TRY * 2 + ) + ) { + newRes.outputs.push({ + isUsdt: true, + amount: this.DUST_FIRST_TRY.toString(), + to: data.addressFrom, + logType: 'FOR_LEGACY_USDT_KEEP' + }) + } + newRes.outputs.push({ + isUsdt: true, + amount: this.DUST_FIRST_TRY.toString(), + to: data.addressTo, + logType: 'FOR_USDT_AMOUNT' + }) + newRes.outputs.push({ + isUsdt: true, + tokenAmount: data.amount, + amount: '0', + to: data.addressTo, + logType: 'FOR_USDT_BTC_OUTPUT' + }) + newRes.countedFor = 'USDT' + return newRes + } else { + BlocksoftCryptoLog.log('UsdtTxInputsOutputs ' + data.addressFrom + ' => ' + data.addressTo + ' old outputs ' + JSON.stringify(res.outputs) + ' needOneOutput ' + needOneOutput ? 'true' : 'false') + res = this._innerResort(res, needOneOutput, data.addressFrom, data.addressTo, data.amount, 'getInputsOutputs ' + subtitle) + BlocksoftCryptoLog.log('UsdtTxInputsOutputs ' + data.addressFrom + ' => ' + data.addressTo + ' new outputs ' + JSON.stringify(res.outputs)) + res.countedFor = 'USDT' + return res + } + } + + _innerResort(res: any, needOneOutput: boolean, addressFrom: string, addressTo: string, amount: string, source: string = '') { + const totalOuts = res.outputs.length + + res.outputs = this._innerToUp(res.outputs, addressTo) + if (res.outputs[0].amount !== '0') { + if (totalOuts > 1) { + if (res.outputs[0].to !== addressTo) { + throw new Error('usdt addressTo is invalid1.1 ' + JSON.stringify(res.outputs)) + } else if (res.outputs[1].to !== res.outputs[0].to) { + throw new Error('usdt addressTo is invalid1.2 ' + JSON.stringify(res)) + } + if (res.outputs[1].to !== res.outputs[0].to) { + throw new Error('usdt addressTo is invalid1.2 ' + JSON.stringify(res.outputs)) + } + res.outputs[1].amount = BlocksoftUtils.add(res.outputs[0].amount, res.outputs[1].amount).toString() + } else { + if (res.outputs[0].to !== addressTo) { + throw new Error('usdt addressTo is invalid2 ' + JSON.stringify(res.outputs)) + } + res.outputs[1] = JSON.parse(JSON.stringify(res.outputs[0])) + } + res.outputs[1].isChange = true + res.outputs[1].isUsdt = true + res.outputs[0].isChange = false + res.outputs[0].isUsdt = true + res.outputs[0].tokenAmount = amount + res.outputs[1].tokenAmount = '0' + res.outputs[0].amount = '0' + } else { + res.outputs[0].isUsdt = true + res.outputs[0].tokenAmount = amount + res.outputs[0].amount = '0' + if (totalOuts > 1) { + if (res.outputs[1].to !== addressTo) { + throw new Error('usdt addressTo is invalid3 ' + JSON.stringify(res.outputs)) + } + res.outputs[1].isUsdt = true + } else { + throw new Error('usdt addressTo is empty ' + JSON.stringify(res.outputs)) + } + } + const newOutputs = [] + if (needOneOutput) { + if (typeof res.outputs[2] === 'undefined' || res.outputs[2].to !== addressFrom) { + newOutputs.push({ + isUsdt: true, + amount: this.DUST_FIRST_TRY.toString(), + to: addressFrom, + logType: 'FOR_LEGACY_USDT_KEEP3' + }) + } + } + for (let i = res.outputs.length - 1; i >= 0; i--) { + newOutputs.push(res.outputs[i]) + } + res.outputs = newOutputs + return res + } + + _innerToUp(outputs, addressTo) { + let result = [] + for (const output of outputs) { + if (output.to === addressTo) { + result.push(output) + } + } + if (result.length === 1) { + let amount = result[0].amount.toString() + if (amount === '0' || BlocksoftUtils.diff(amount, this.DUST_FIRST_TRY.toString()).toString().indexOf('-') !== -1) { + amount = this.DUST_FIRST_TRY.toString() + } + result = [] + result.push({ + isUsdt: true, + tokenAmount: '?', + amount: '0', + to: addressTo, + logType: 'FOR_USDT_BTC_OUTPUT1' + }) + result.push({ + isUsdt: true, + amount, + to: addressTo, + logType: 'FOR_USDT_AMOUNT1' + }) + } else if (result.length === 0) { + result.push({ + isUsdt: true, + tokenAmount: '?', + amount: '0', + to: addressTo, + logType: 'FOR_USDT_BTC_OUTPUT2' + }) + result.push({ + isUsdt: true, + amount: this.DUST_FIRST_TRY.toString(), + to: addressTo, + logType: 'FOR_USDT_AMOUNT2' + }) + } + for (const output of outputs) { + if (output.to !== addressTo) { + result.push(output) + } + } + BlocksoftCryptoLog.log('USDT addressTo upTo', result) + return result + } +} diff --git a/crypto/blockchains/vet/VetScannerProcessor.js b/crypto/blockchains/vet/VetScannerProcessor.js new file mode 100644 index 000000000..81f63cc9f --- /dev/null +++ b/crypto/blockchains/vet/VetScannerProcessor.js @@ -0,0 +1,267 @@ +/** + * @version 0.5 + * https://docs.vechain.org/thor/get-started/api.html + */ +import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog' +import BlocksoftAxios from '@crypto/common/BlocksoftAxios' +import BlocksoftUtils from '@crypto/common/BlocksoftUtils' + +const API_PATH = 'https://sync-mainnet.vechain.org' +const CACHE_VALID_TIME = 30000 // 30 seconds +const CACHE = {} +export default class VetScannerProcessor { + + constructor(settings) { + this._settings = settings + this._lastBlock = 9243725 + } + + async _get(_address) { + const address = _address.toLowerCase() + + const now = new Date().getTime() + if (typeof CACHE[address] !== 'undefined' && (now - CACHE[address].time < CACHE_VALID_TIME)) { + CACHE[address].provider = 'cache' + return CACHE[address] + } + + const link = API_PATH + '/accounts/' + address + const res = await BlocksoftAxios.getWithoutBraking(link) + if (!res || typeof res.data === 'undefined' || typeof res.data.balance === 'undefined') { + BlocksoftCryptoLog.log(this._settings.currencyCode + ' VeChain ScannerProcessor getBalanceBlockchain ' + link + ' error ', res) + return false + } + + CACHE[address] = { + balance: res.data.balance, + energy: res.data.energy, + provider: 'loaded', + time: now + } + return CACHE[address] + } + + /** + * https://docs.vechain.org/thor/get-started/api.html#get-accounts-address + * https://vethor-node.vechain.com/doc/swagger-ui/#/ + * @param {string} address + * @return {Promise<{balance, provider}>} + */ + async getBalanceBlockchain(address) { + address = address.trim() + BlocksoftCryptoLog.log(this._settings.currencyCode + ' VeChain ScannerProcessor getBalanceBlockchain address ' + address) + + const res = await this._get(address) + if (!res) { + return false + } + try { + const hex = this._settings.currencyCode === 'VET' ? res.balance : res.energy + const balance = BlocksoftUtils.hexToDecimalBigger(hex) + return { balance, unconfirmed: 0, provider: 'vethor-node.vechain.com' } + } catch (e) { + BlocksoftCryptoLog.log(this._settings.currencyCode + ' VeChain ScannerProcessor getBalanceBlockchain address ' + address + ' balance ' + JSON.stringify(res) + ' to hex error ' + e.message) + } + return false + + } + + + /** + * https://docs.vechain.org/thor/get-started/api.html#post-logs-transfer + * https://github.com/trustwallet/blockatlas/blob/696fb97b7b3197a7da4bb692122a8028ea4e07cf/platform/vechain/client.go + * @param {string} scanData.account.address + * @return {Promise<[UnifiedTransaction]>} + */ + async getTransactionsBlockchain(scanData, source) { + const address = scanData.account.address.trim() + + if (this._settings.currencyCode === 'VET') { + try { + const linkNumber = API_PATH + '/blocks/best' + const resultNumber = await BlocksoftAxios.get(linkNumber) + if (resultNumber && typeof resultNumber.data !== 'undefined' && typeof resultNumber.data.number !== 'undefined') { + const tmp = resultNumber.data.number * 1 + if (tmp > this._lastBlock) { + this._lastBlock = tmp + } + } + } catch (e) { + BlocksoftCryptoLog.log('VetScannerProcessor.getTransactions lastBlock ' + e.message) + } + + const link = API_PATH + '/logs/transfer' + const result = await BlocksoftAxios.post(link, { + 'options': { + 'offset': 0, + 'limit': 100 + }, + 'criteriaSet': [ + { + 'txOrigin': address + }, + { + 'sender': address + }, + { + 'recipient': address + } + ], + 'order': 'desc' + }) + + if (!result.data || result.data.length === 0) return false + + const transactions = await this._unifyTransactions(address, result.data) + BlocksoftCryptoLog.log('VetScannerProcessor.getTransactions finished ' + address) + return transactions + } else { + const link = API_PATH + '/logs/event' + const tokenHex = '0x000000000000000000000000' + address.toLowerCase().substr(2) + const result = await BlocksoftAxios.post(link, { + 'options': { + 'offset': 0, + 'limit': 100 + }, + 'criteriaSet': [ + { + address : '0x0000000000000000000000000000456e65726779', + Topic1: tokenHex + }, + { + address : '0x0000000000000000000000000000456e65726779', + Topic2: tokenHex + }, + ], + 'order': 'desc' + }) + if (!result.data || result.data.length === 0) return false + const transactions = await this._unifyTransactionsToken(address, result.data) + BlocksoftCryptoLog.log('VetScannerProcessor.getTransactions finished ' + tokenHex) + return transactions + } + + } + + async _unifyTransactions(address, result) { + const transactions = [] + let tx + for (tx of result) { + const transaction = await this._unifyTransaction(address, tx) + if (transaction) { + transactions.push(transaction) + } + } + return transactions + } + + async _unifyTransactionsToken(address, result) { + const transactions = [] + let tx + for (tx of result) { + const transaction = await this._unifyTransactionToken(address, tx) + if (transaction) { + transactions.push(transaction) + } + } + return transactions + } + + async _unifyTransaction(address, transaction) { + /** + * "sender":"0xa4adafaef9ec07bc4dc6de146934c7119341ee25", + * "recipient":"0xf3ce5d5d8ff44cb6b4f77512b94ddd6e04d820a0", + * "amount":"0x352e15037328a70000", + * "meta":{ + * "blockID":"0x008c4643ee0fb483412b2b6aa34c76c7925093cd1749f33238fa14f6ab340046", + * "blockNumber":9193027, + * "blockTimestamp":1622441190, + * "txID":"0x46bb9fb1e71845fee9289e7626aa4eba26fb834d1a17661c6fbbb333958fcc67", + * "txOrigin":"0xa4adafaef9ec07bc4dc6de146934c7119341ee25", + * "clauseIndex":0 + */ + + const amount = BlocksoftUtils.hexToDecimalBigger(transaction.amount) + let formattedTime = transaction.meta.blockTimestamp + try { + formattedTime = BlocksoftUtils.toDate(transaction.meta.blockTimestamp) + } catch (e) { + e.message += ' timestamp error transaction2 data ' + JSON.stringify(transaction) + throw e + } + const now = new Date().getTime() + const diffSeconds = Math.round((now - transaction.meta.blockTimestamp) / 1000) + let blockConfirmations = this._lastBlock - transaction.meta.blockNumber + if (blockConfirmations > 100 && diffSeconds < 600) { + blockConfirmations = diffSeconds + } else if (blockConfirmations < 0) { + blockConfirmations = diffSeconds > 60 ? 2 : 0 + } + const tx = { + transactionHash: transaction.meta.txID, + blockHash: transaction.meta.blockID, + blockNumber: transaction.meta.blockNumber, + blockTime: formattedTime, + blockConfirmations, + transactionDirection: (address.toLowerCase() === transaction.sender.toLowerCase()) ? 'outcome' : 'income', + addressFrom: transaction.sender === address ? '' : transaction.sender, + addressTo: transaction.recipient === address ? '' : transaction.recipient, + addressAmount: amount, + transactionStatus: blockConfirmations > 20 ? 'success' : 'new', + transactionFee: '0' + } + return tx + } + + async _unifyTransactionToken(address, transaction) { + /** + * "address": "0x0000000000000000000000000000456e65726779", + * "topics": [ + * "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + * "0x000000000000000000000000a4adafaef9ec07bc4dc6de146934c7119341ee25", + * "0x000000000000000000000000f3ce5d5d8ff44cb6b4f77512b94ddd6e04d820a0" + * ], + * "data": "0x00000000000000000000000000000000000000000000006a227cf2eb4ac80000", + * "meta": { + * "blockID": "0x008c525b58528178e3e2ea89e6a6864c0356127a21b5b06aeaa26f531690abf8", + * "blockNumber": 9196123, + * "blockTimestamp": 1622472160, + * "txID": "0x190406bc9491484ca763774dfb074118ef7b5d6f594ac484402da265d00c3eff", + * "txOrigin": "0xa4adafaef9ec07bc4dc6de146934c7119341ee25", + * "clauseIndex": 5 + */ + + const amount = BlocksoftUtils.hexToDecimalBigger(transaction.data) + let formattedTime = transaction.meta.blockTimestamp + try { + formattedTime = BlocksoftUtils.toDate(transaction.meta.blockTimestamp) + } catch (e) { + e.message += ' timestamp error transaction2 data ' + JSON.stringify(transaction) + throw e + } + const now = new Date().getTime() + const diffSeconds = Math.round((now - transaction.meta.blockTimestamp) / 1000) + let blockConfirmations = this._lastBlock - transaction.meta.blockNumber + if (blockConfirmations > 100 && diffSeconds < 600) { + blockConfirmations = diffSeconds + } else if (blockConfirmations < 0) { + blockConfirmations = diffSeconds > 60 ? 2 : 0 + } + const addressFrom = typeof transaction.topics[1] !== 'undefined' ? transaction.topics[1].replace('0x000000000000000000000000', '0x') : '' + const addressTo = typeof transaction.topics[2] !== 'undefined' ? transaction.topics[2].replace('0x000000000000000000000000', '0x') : '' + const tx = { + transactionHash: transaction.meta.txID, + blockHash: transaction.meta.blockID, + blockNumber: transaction.meta.blockNumber, + blockTime: formattedTime, + blockConfirmations, + transactionDirection : addressFrom === address.toLowerCase() ? 'outcome' : 'income' , + addressFrom: addressFrom === address.toLowerCase() ? '' : addressFrom, + addressTo: addressTo === address.toLowerCase() ? '' : addressTo, + addressAmount: amount, + transactionStatus: blockConfirmations > 20 ? 'success' : 'new', + transactionFee: '0' + } + return tx + } +} diff --git a/crypto/blockchains/vet/VetTransferProcessor.ts b/crypto/blockchains/vet/VetTransferProcessor.ts new file mode 100644 index 000000000..3e433aa60 --- /dev/null +++ b/crypto/blockchains/vet/VetTransferProcessor.ts @@ -0,0 +1,211 @@ +/** + * @version 0.20 + */ +import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog' +import BlocksoftUtils from '../../common/BlocksoftUtils' + +import { BlocksoftBlockchainTypes } from '../BlocksoftBlockchainTypes' +import { thorify } from 'thorify' +import BlocksoftAxios from '@crypto/common/BlocksoftAxios' +import MarketingEvent from '@app/services/Marketing/MarketingEvent' + +const Web3 = require('web3') +const abi = [ + { + 'constant': false, + 'inputs': [ + { + 'name': 'to', + 'type': 'address' + }, + { + 'name': 'value', + 'type': 'uint256' + } + ], + 'name': 'transfer', + 'outputs': [ + { + 'name': 'ok', + 'type': 'bool' + } + ], + 'payable': false, + 'stateMutability': 'nonpayable', + 'type': 'function' + } +] +const tokenAddress = '0x0000000000000000000000000000456e65726779' + +const API_PATH = 'https://sync-mainnet.vechain.org' + +export default class VetTransferProcessor implements BlocksoftBlockchainTypes.TransferProcessor { + private _settings: { network: string; currencyCode: string } + private _web3: any + private _token: any + + constructor(settings: { network: string; currencyCode: string }) { + this._settings = settings + this._web3 = thorify(new Web3(), API_PATH) + this._token = new this._web3.eth.Contract(abi, tokenAddress) + } + + needPrivateForFee(): boolean { + return false + } + + checkSendAllModal(data: { currencyCode: any }): boolean { + return false + } + + async getFeeRate(data: BlocksoftBlockchainTypes.TransferData, privateData: BlocksoftBlockchainTypes.TransferPrivateData, additionalData: {} = {}): Promise { + const result: BlocksoftBlockchainTypes.FeeRateResult = { + selectedFeeIndex: -3, + shouldShowFees: false + } as BlocksoftBlockchainTypes.FeeRateResult + + if (this._settings.currencyCode === 'VET') { + // @todo + /* + result.fees = [ + { + langMsg: 'xrp_speed_one', + feeForTx: '536300000000000000', + amountForTx: data.amount + } + ] + result.selectedFeeIndex = 0 + */ + } else { + result.fees = [ + { + langMsg: 'xrp_speed_one', + feeForTx: '536300000000000000', + amountForTx: data.amount + } + ] + result.selectedFeeIndex = 0 + } + + + return result + } + + async getTransferAllBalance(data: BlocksoftBlockchainTypes.TransferData, privateData: BlocksoftBlockchainTypes.TransferPrivateData, additionalData: BlocksoftBlockchainTypes.TransferAdditionalData = {}): Promise { + const balance = data.amount + // @ts-ignore + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' VetTransferProcessor.getTransferAllBalance ', data.addressFrom + ' => ' + balance) + + const fees = await this.getFeeRate(data, privateData, additionalData) + + let amount + if (this._settings.currencyCode === 'VET') { + amount = balance + } else { + amount = BlocksoftUtils.diff(balance, '536300000000000000').toString() + } + + return { + ...fees, + shouldShowFees: false, + selectedTransferAllBalance: amount + } + } + + /** + * https://thorify.vecha.in/#/?id=how-do-i-send-vtho-token + * https://thorify.vecha.in/#/?id=send-transaction-1 + * https://github.com/vechain/thorify/blob/bb7a97e5e62b46af0d45fa119e99539d57e2302a/test/web3/eth.test.ts + * @param data + * @param privateData + * @param uiData + */ + async sendTx(data: BlocksoftBlockchainTypes.TransferData, privateData: BlocksoftBlockchainTypes.TransferPrivateData, uiData: BlocksoftBlockchainTypes.TransferUiData): Promise { + + if (typeof privateData.privateKey === 'undefined') { + throw new Error('VET transaction required privateKey') + } + if (typeof data.addressTo === 'undefined') { + throw new Error('VET transaction required addressTo') + } + + let signedData = false + try { + this._web3.eth.accounts.wallet.add(privateData.privateKey) + + if (this._settings.currencyCode === 'VET') { + signedData = await this._web3.eth.accounts.signTransaction({ + from: data.addressFrom, + to: data.addressTo, + value: data.amount, + }, privateData.privateKey) + } else { + const basicAddressTo = data.addressTo.toLowerCase() + const encoded = this._token.methods.transfer(basicAddressTo, data.amount).encodeABI() + signedData = await this._web3.eth.accounts.signTransaction({ + from: data.addressFrom, + to: tokenAddress, + data : encoded, + value: 0, + }, privateData.privateKey) + } + + } catch (e) { + BlocksoftCryptoLog.log(this._settings.currencyCode + ' VetTransferProcessor.sendTx ' + data.addressFrom + ' => ' + data.addressTo + ' ' + data.amount + ' error ' + e.message) + throw new Error(e.message) + } + BlocksoftCryptoLog.log(this._settings.currencyCode + ' VetTransferProcessor.sendTx ' + data.addressFrom + ' => ' + data.addressTo + ' ' + data.amount, signedData) + if (!signedData || typeof signedData.rawTransaction === 'undefined' || !signedData.rawTransaction) { + throw new Error('SYSTEM_ERROR') + } + if (typeof uiData !== 'undefined' && typeof uiData.selectedFee !== 'undefined' && typeof uiData.selectedFee.rawOnly !== 'undefined' && uiData.selectedFee.rawOnly) { + return { rawOnly: uiData.selectedFee.rawOnly, raw : signedData.rawTransaction} + } + + let result = {} as BlocksoftBlockchainTypes.SendTxResult + try { + const send = await BlocksoftAxios.post(API_PATH + '/transactions', { raw: signedData.rawTransaction }, false) + BlocksoftCryptoLog.log(this._settings.currencyCode + ' VetTransferProcessor.sendTx ' + data.addressFrom + ' => ' + data.addressTo + ' ' + data.amount, send.data) + if (typeof send.data === 'undefined' || !send.data || typeof send.data.id === 'undefined') { + throw new Error('SYSTEM_ERROR') + } + result.transactionHash = send.data.id + } catch (e) { + BlocksoftCryptoLog.log(this._settings.currencyCode + ' VetTransferProcessor.sendTx ' + data.addressFrom + ' => ' + data.addressTo + ' ' + data.amount + ' send error ' + e.message) + this.checkError(e, data, false) + } + return result + } + + checkError(e, data, txRBF = false) { + + if (e.message.indexOf('nonce too low') !== -1) { + BlocksoftCryptoLog.log('VeChain checkError0.1 ' + e.message + ' for ' + data.addressFrom) + let e2 + if (txRBF) { + e2 = new Error('SERVER_RESPONSE_TRANSACTION_ALREADY_MINED') + } else { + e2 = new Error('SERVER_RESPONSE_NONCE_ALREADY_MINED') + } + throw e2 + } else if (e.message.indexOf('insufficient funds') !== -1) { + BlocksoftCryptoLog.log('VeChain checkError0.3 ' + e.message + ' for ' + data.addressFrom) + if ((this._settings.currencyCode === 'ETH' || this._settings.currencyCode === 'BNB_SMART') && data.amount * 1 > 0) { + throw new Error('SERVER_RESPONSE_NOTHING_LEFT_FOR_FEE') + } else { + throw new Error('SERVER_RESPONSE_NOT_ENOUGH_FEE') + } + } else if (e.message.indexOf('underpriced') !== -1) { + BlocksoftCryptoLog.log('VeChain checkError0.4 ' + e.message + ' for ' + data.addressFrom) + throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_FEE') + } else if (e.message.indexOf('already known') !== -1) { + BlocksoftCryptoLog.log('VeChain checkError0.5 ' + e.message + ' for ' + data.addressFrom) + throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_FEE') + } else if (e.message.indexOf('insufficient energy') !== -1) { + BlocksoftCryptoLog.log('VeChain checkError0.6 ' + e.message + ' for ' + data.addressFrom) + throw new Error('SERVER_RESPONSE_ENERGY_ERROR_VET') + } else { + throw e + } + } +} diff --git a/crypto/blockchains/waves/WavesAddressProcessor.js b/crypto/blockchains/waves/WavesAddressProcessor.js new file mode 100644 index 000000000..5bbb26ed3 --- /dev/null +++ b/crypto/blockchains/waves/WavesAddressProcessor.js @@ -0,0 +1,27 @@ +/** + * @version 0.5 + */ +import { crypto, base58Encode } from '@waves/ts-lib-crypto' + +export default class WavesAddressProcessor { + + async setBasicRoot(root) { + + } + + /** + * @param {string|Buffer} _privateKey + * @param {*} data + * @returns {Promise<{privateKey: string, address: string, addedData: *}>} + */ + async getAddress(_privateKey, data = {}, superPrivateData = {}) { + + const all = crypto({seed: superPrivateData.mnemonic}) + const buff1 = all.address() + const address = base58Encode(buff1) + const key2 = all.keyPair() + const pubKey = base58Encode(key2.publicKey) + const privKey = base58Encode(key2.privateKey) + return { address, privateKey: privKey, addedData: { pubKey} } + } +} diff --git a/crypto/blockchains/waves/WavesScannerProcessor.js b/crypto/blockchains/waves/WavesScannerProcessor.js new file mode 100644 index 000000000..f7cf50201 --- /dev/null +++ b/crypto/blockchains/waves/WavesScannerProcessor.js @@ -0,0 +1,162 @@ +/** + * @version 0.5 + */ +import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog' +import BlocksoftAxios from '@crypto/common/BlocksoftAxios' +import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings' +import TransactionFilterTypeDict from '@appV2/dicts/transactionFilterTypeDict' +import WavesTransactionsProvider from '@crypto/blockchains/waves/providers/WavesTransactionsProvider' + +export default class WavesScannerProcessor { + + constructor(settings) { + this._settings = settings + this._provider = new WavesTransactionsProvider() + this._mainCurrencyCode = 'WAVES' + if (this._settings.currencyCode === 'ASH' || this._settings.currencyCode.indexOf('ASH_') === 0) { + this._mainCurrencyCode = 'ASH' + } + } + + /** + * https://nodes.wavesnodes.com/addresses/balance/details/3P274YB5qseSE9DTTL3bpSjosZrYBPDpJ8k + * https://nodes.wavesnodes.com/api-docs/index.html#/addresses/getWavesBalances + * https://docs.waves.tech/en/blockchain/account/account-balance#account-balance-in-waves + * @return {Promise<{balance, provider}>} + */ + async getBalanceBlockchain(address) { + if (this._mainCurrencyCode == 'ASH') { + this._apiPath = await BlocksoftExternalSettings.get('ASH_SERVER') + } else { + this._apiPath = await BlocksoftExternalSettings.get('WAVES_SERVER') + } + address = address.trim() + BlocksoftCryptoLog.log(this._settings.currencyCode + ' WavesScannerProcessor getBalanceBlockchain address ' + address) + + const link = this._apiPath + '/addresses/balance/details/' + address + const res = await BlocksoftAxios.get(link) + if (!res) { + return false + } + try { + return { balance: res.data.available, unconfirmed: 0, provider: 'wavesnodes.com' } + } catch (e) { + BlocksoftCryptoLog.log(this._settings.currencyCode + ' WavesProcessor getBalanceBlockchain address ' + address + ' balance ' + JSON.stringify(res) + ' to hex error ' + e.message) + } + return false + } + + /** + * https://nodes.wavesnodes.com/transactions/address/3P274YB5qseSE9DTTL3bpSjosZrYBPDpJ8k/limit/100 + * @param {string} scanData.account.address + * @return {Promise<[UnifiedTransaction]>} + */ + async getTransactionsBlockchain(scanData, source) { + const address = scanData.account.address.trim() + const data = await this._provider.get(address, this._mainCurrencyCode) + const transactions = [] + for (const tx of data) { + const transaction = await this._unifyTransaction(address, tx) + if (transaction) { + transactions.push(transaction) + } + } + return transactions + } + + /** + * @param address + * @param transaction.amount 100000000 + * @param transaction.applicationStatus succeeded + * @param transaction.assetId null + * @param transaction.attachment + * @param transaction.fee 100000 + * @param transaction.feeAsset + * @param transaction.feeAssetId + * @param transaction.height 2715839 + * @param transaction.id GxnhfderDpMwdrSfWbTN53NGB1Q2NRQXcGvYdXbzQBXo + * @param transaction.recipient 3PQQUuGM1Fo8zz72i62dNYkB5kRxqmJkoSu + * @param transaction.sender 3PLPGmXoDNKeWxSgJRU5vDNogbPj7hJiWQx + * @param transaction.senderPublicKey GP9hPWAiGDfNYyCTNw6ZWoLCzUqWiYj7MybtPcu8mpkg + * @param transaction.signature 4FewzMCYLvfQridUZtSFrDXRbDvmawUWtBxWdiEE5CeruG1qfbKbfTkudGyW6Eqs3kW4hTpABQxrhBSBuKV7uHFa + * @param transaction.timestamp 1628523063656 + * @param transaction.type 4 + * @param transaction.version 1 + * + * @returns {Promise} + * @private + */ + async _unifyTransaction(address, transaction) { + let transactionStatus = 'confirming' + if (transaction.applicationStatus === 'succeeded') { + transactionStatus = 'success' + } else if (transaction.applicationStatus === 'script_execution_failed') { + transactionStatus = 'fail' + } + let formattedTime = transaction.timestamp + const blockConfirmations = Math.round((new Date().getTime() - transaction.timestamp) / 6000) + try { + formattedTime = new Date(transaction.timestamp).toISOString() + } catch (e) { + e.message += ' timestamp error transaction2 data ' + JSON.stringify(transaction) + throw e + } + const addressFrom = transaction.sender + const addressTo = transaction.recipient || address + + let transactionFilterType = TransactionFilterTypeDict.USUAL + let transactionDirection = 'self' + if (addressFrom === address) { + if (addressTo !== address) { + transactionDirection = 'outcome' + } + } else if (addressTo === address) { + transactionDirection = 'income' + } + + let addressAmount = 0 + if (typeof transaction.transfers !== 'undefined') { + for (const transfer of transaction.transfers) { + if (transfer.recipient === address && transfer.amount*1>0) { + addressAmount = addressAmount + transfer.amount*1 + transactionDirection = 'income' + } + } + } + if (addressAmount === 0 && typeof transaction.amount !== 'undefined'){ + addressAmount = transaction.amount + } + + const transactionFee = transaction.feeAsset && transaction.feeAssetId ? 0 : transaction.fee + if (transaction.assetId) { + return false + } + + if (typeof transaction.order1 !== 'undefined') { + if (transaction.order2.amount === addressAmount) { + transactionDirection = 'swap_outcome' + } else { + transactionDirection = 'swap_income' + } + transactionFilterType = TransactionFilterTypeDict.SWAP + } + + const tmp = { + transactionHash: transaction.id, + blockHash: transaction.height, + blockNumber: transaction.height, + blockTime: formattedTime, + blockConfirmations, + transactionDirection, + transactionFilterType, + addressFrom: addressFrom === address ? '' : addressFrom, + addressFromBasic: addressFrom, + addressTo: addressTo === address ? '' : addressTo, + addressToBasic: addressTo, + addressAmount, + transactionStatus, + transactionFee + } + return tmp + } +} diff --git a/crypto/blockchains/waves/WavesScannerProcessorErc20.js b/crypto/blockchains/waves/WavesScannerProcessorErc20.js new file mode 100644 index 000000000..d3664d000 --- /dev/null +++ b/crypto/blockchains/waves/WavesScannerProcessorErc20.js @@ -0,0 +1,113 @@ +/** + * @version 0.5 + */ +import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog' +import BlocksoftAxios from '@crypto/common/BlocksoftAxios' +import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings' +import WavesScannerProcessor from '@crypto/blockchains/waves/WavesScannerProcessor' +import TransactionFilterTypeDict from '@appV2/dicts/transactionFilterTypeDict' +import BlocksoftUtils from '@crypto/common/BlocksoftUtils' + +export default class WavesScannerProcessorErc20 extends WavesScannerProcessor { + + constructor(settings) { + super(settings) + this._tokenAddress = settings.tokenAddress + } + + /** + * https://nodes.wavesnodes.com/api-docs/index.html#/assets/getAssetBalanceByAddress + * https://nodes.wavesnodes.com/assets/balance/3PGixfWP1pcuWwND2rDLf2n94J7egSf4uz4/DG2xFkPdDwKUoBkzGAhQtLpSGzfXLiCYPEzeKH2Ad24p + * @return {Promise<{balance, provider}>} + */ + async getBalanceBlockchain(address) { + + if (this._mainCurrencyCode === 'ASH') { + this._apiPath = await BlocksoftExternalSettings.get('ASH_SERVER') + } else { + this._apiPath = await BlocksoftExternalSettings.get('WAVES_SERVER') + } + address = address.trim() + BlocksoftCryptoLog.log(this._settings.currencyCode + ' WavesScannerProcessorErc20 getBalanceBlockchain address ' + address) + + const link = this._apiPath + '/assets/balance/' + address + '/' + this._tokenAddress + const res = await BlocksoftAxios.get(link) + if (!res) { + return false + } + try { + return { balance: res.data.balance, unconfirmed: 0, provider: 'wavesnodes.com' } + } catch (e) { + BlocksoftCryptoLog.log(this._settings.currencyCode + ' WavesProcessorErc20 getBalanceBlockchain address ' + address + ' balance ' + JSON.stringify(res) + ' to hex error ' + e.message) + } + return false + } + + async _unifyTransaction(address, transaction) { + let transactionStatus = 'confirming' + if (transaction.applicationStatus === 'succeeded') { + transactionStatus = 'success' + } else if (transaction.applicationStatus === 'script_execution_failed') { + transactionStatus = 'fail' + } + let formattedTime = transaction.timestamp + const blockConfirmations = Math.round((new Date().getTime() - transaction.timestamp) / 6000) + try { + formattedTime = new Date(transaction.timestamp).toISOString() + } catch (e) { + e.message += ' timestamp error transaction2 data ' + JSON.stringify(transaction) + throw e + } + const addressFrom = transaction.sender + const addressTo = transaction.recipient || address + let addressAmount = transaction.amount + const transactionFee = transaction.feeAsset && transaction.feeAssetId ? 0 : transaction.fee + + let transactionDirection = false + let transactionFilterType = TransactionFilterTypeDict.USUAL + + if (typeof transaction.order1 !== 'undefined') { + if (transaction.order1.assetPair.priceAsset !== this._tokenAddress) { + return false + } + if (transaction.order2.amount === addressAmount) { + transactionDirection = 'swap_income' + addressAmount = transaction.order2.amount * transaction.order1.price + } else { + transactionDirection = 'swap_outcome' + addressAmount = transaction.order1.amount * transaction.order2.price + } + transactionFilterType = TransactionFilterTypeDict.SWAP + addressAmount = BlocksoftUtils.fromUnified(BlocksoftUtils.toUnified(addressAmount, 14), this._settings.decimals) + } else if (transaction.assetId === this._tokenAddress) { + transactionDirection = 'self' + if (addressFrom === address) { + if (addressTo !== address) { + transactionDirection = 'outcome' + } + } else if (addressTo === address) { + transactionDirection = 'income' + } + } else { + return false + } + + const tmp = { + transactionHash: transaction.id, + blockHash: transaction.height, + blockNumber: transaction.height, + blockTime: formattedTime, + blockConfirmations, + transactionDirection, + transactionFilterType, + addressFrom: addressFrom === address ? '' : addressFrom, + addressFromBasic: addressFrom, + addressTo: addressTo === address ? '' : addressTo, + addressToBasic: addressTo, + addressAmount, + transactionStatus, + transactionFee + } + return tmp + } +} diff --git a/crypto/blockchains/waves/WavesTransferProcessor.ts b/crypto/blockchains/waves/WavesTransferProcessor.ts new file mode 100644 index 000000000..fbfcca1f4 --- /dev/null +++ b/crypto/blockchains/waves/WavesTransferProcessor.ts @@ -0,0 +1,159 @@ +/** + * @version 0.5 + */ +import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog' +import BlocksoftUtils from '@crypto/common/BlocksoftUtils' + +import { BlocksoftBlockchainTypes } from '@crypto/blockchains/BlocksoftBlockchainTypes' + +import { transfer, broadcast } from '@waves/waves-transactions/src/index' +import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings' +import config from '@app/config/config' +import MarketingEvent from '@app/services/Marketing/MarketingEvent' + +export default class WavesTransferProcessor implements BlocksoftBlockchainTypes.TransferProcessor { + private _settings: { network: string; currencyCode: string } + + private _tokenAddress: string + + private _mainCurrencyCode: string + + constructor(settings: { network: string; currencyCode: string }) { + this._settings = settings + this._tokenAddress = typeof settings.tokenAddress !== 'undefined' ? settings.tokenAddress : false + + this._mainCurrencyCode = 'WAVES' + if (this._settings.currencyCode === 'ASH' || this._settings.currencyCode.indexOf('ASH_') === 0) { + this._mainCurrencyCode = 'ASH' + } + } + + needPrivateForFee(): boolean { + return false + } + + checkSendAllModal(data: { currencyCode: any }): boolean { + return false + } + + async getFeeRate(data: BlocksoftBlockchainTypes.TransferData, privateData: BlocksoftBlockchainTypes.TransferPrivateData, additionalData: {} = {}): Promise { + const result: BlocksoftBlockchainTypes.FeeRateResult = { + selectedFeeIndex: -3, + shouldShowFees: false + } as BlocksoftBlockchainTypes.FeeRateResult + + result.fees = [ + { + langMsg: 'xrp_speed_one', + feeForTx: '100000', + amountForTx: data.amount + } + ] + result.selectedFeeIndex = 0 + + + return result + } + + async getTransferAllBalance(data: BlocksoftBlockchainTypes.TransferData, privateData: BlocksoftBlockchainTypes.TransferPrivateData, additionalData: BlocksoftBlockchainTypes.TransferAdditionalData = {}): Promise { + const balance = data.amount + // @ts-ignore + BlocksoftCryptoLog.log(this._settings.currencyCode + ' WavesTransferProcessor.getTransferAllBalance ', data.addressFrom + ' => ' + balance) + + const res = await this.getFeeRate(data, privateData, additionalData) + let amount + if (this._tokenAddress) { + amount = balance + } else { + amount = BlocksoftUtils.diff(balance, '100000').toString() + res.fees[0].amountForTx = amount + } + + return { + ...res, + shouldShowFees: false, + selectedTransferAllBalance: amount, + } + } + + /** + * https://docs.waves.tech/en/building-apps/how-to/basic/transaction#sign-transaction-using-your-own-seed + * @param data + * @param privateData + * @param uiData + */ + async sendTx(data: BlocksoftBlockchainTypes.TransferData, privateData: BlocksoftBlockchainTypes.TransferPrivateData, uiData: BlocksoftBlockchainTypes.TransferUiData): Promise { + if (typeof privateData.privateKey === 'undefined') { + throw new Error('WAVES transaction required privateKey') + } + if (typeof data.addressTo === 'undefined') { + throw new Error('WAVES transaction required addressTo') + } + + let addressTo = data.addressTo + let apiPath + if (this._mainCurrencyCode === 'ASH') { + apiPath = await BlocksoftExternalSettings.get('ASH_SERVER') + addressTo = addressTo.replace('Æx', '') + } else { + apiPath = await BlocksoftExternalSettings.get('WAVES_SERVER') + } + + BlocksoftCryptoLog.log(this._settings.currencyCode + ' WavesTransferProcessor.sendTx started ' + data.addressFrom + ' => ' + data.addressTo + ' ' + data.amount) + + let signedData = false + try { + const money = { + recipient: addressTo, + amount: data.amount + } + if (this._tokenAddress) { + money.assetId = this._tokenAddress + } + signedData = transfer(money, { privateKey: privateData.privateKey }) + + } catch (e) { + BlocksoftCryptoLog.log(this._settings.currencyCode + ' WavesTransferProcessor.sendTx ' + data.addressFrom + ' => ' + data.addressTo + ' ' + data.amount + ' signedData error ' + e.message) + throw new Error(e.message) + } + BlocksoftCryptoLog.log(this._settings.currencyCode + ' WavesTransferProcessor.sendTx ' + data.addressFrom + ' => ' + data.addressTo + ' ' + data.amount + ' signedData', signedData) + if (!signedData || typeof signedData.id === 'undefined' || !signedData.id) { + throw new Error('SYSTEM_ERROR') + } + + if (typeof uiData !== 'undefined' && typeof uiData.selectedFee !== 'undefined' && typeof uiData.selectedFee.rawOnly !== 'undefined' && uiData.selectedFee.rawOnly) { + return { rawOnly: uiData.selectedFee.rawOnly, raw: JSON.stringify(signedData) } + } + + let result = {} as BlocksoftBlockchainTypes.SendTxResult + try { + const resp = await new Promise((resolve, reject) => { + BlocksoftCryptoLog.log(this._settings.currencyCode + ' WavesTransferProcessor.sendTx ' + data.addressFrom + ' => ' + data.addressTo + ' ' + data.amount + ' will broadCast ' + JSON.stringify(apiPath)) + broadcast(signedData, apiPath).then(resp => { + resolve(resp) + }).catch(e => { + reject(e) + }) + + }) + BlocksoftCryptoLog.log(this._settings.currencyCode + ' WavesTransferProcessor.sendTx ' + data.addressFrom + ' => ' + data.addressTo + ' ' + data.amount + ' send res ' + resp) + result.transactionHash = signedData.id + } catch (e) { + if (config.debug.cryptoErrors) { + console.log(this._settings.currencyCode + ' WavesTransferProcessor.sendTx ' + data.addressFrom + ' => ' + data.addressTo + ' ' + data.amount + ' send error ' + e.message) + } + BlocksoftCryptoLog.log(this._settings.currencyCode + ' WavesTransferProcessor.sendTx ' + data.addressFrom + ' => ' + data.addressTo + ' ' + data.amount + ' send error ' + e.message) + this.checkError(e, data, false) + } + return result + } + + checkError(e, data, txRBF = false) { + if (e.message.indexOf('waves balance to (at least) temporary negative state') !== -1) { + throw new Error('SERVER_RESPONSE_NOTHING_LEFT_FOR_FEE') + } else { + MarketingEvent.logOnlyRealTime('v20_' + this._settings.currencyCode + '_tx_error ' + this._settings.currencyCode + ' ' + data.addressFrom + ' => ' + data.addressTo + ' ' + e.message, false) + throw e + } + } +} diff --git a/crypto/blockchains/waves/providers/WavesTransactionsProvider.js b/crypto/blockchains/waves/providers/WavesTransactionsProvider.js new file mode 100644 index 000000000..f64cf921b --- /dev/null +++ b/crypto/blockchains/waves/providers/WavesTransactionsProvider.js @@ -0,0 +1,42 @@ +/** + * @version 0.50 + */ +import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings' +import BlocksoftAxios from '@crypto/common/BlocksoftAxios' +import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog' + +const CACHE_OF_TRANSACTIONS = { + ASH : {}, + WAVES : {} +} +const CACHE_VALID_TIME = 30000 // 30 seconds + +export default class WavesTransactionsProvider { + + async get(address, mainCurrencyCode) { + let _apiPath + if (mainCurrencyCode === 'ASH') { + _apiPath = await BlocksoftExternalSettings.get('ASH_SERVER') + } else { + _apiPath = await BlocksoftExternalSettings.get('WAVES_SERVER') + } + const now = new Date().getTime() + if (typeof CACHE_OF_TRANSACTIONS[mainCurrencyCode][address] !== 'undefined' && (now - CACHE_OF_TRANSACTIONS[mainCurrencyCode][address].time) < CACHE_VALID_TIME) { + if (typeof CACHE_OF_TRANSACTIONS[mainCurrencyCode][address] !== 'undefined') { + BlocksoftCryptoLog.log(' WavesTransactionsProvider.get from cache', address + ' => ' + mainCurrencyCode) + return CACHE_OF_TRANSACTIONS[mainCurrencyCode][address].data + } + } + const link = _apiPath + '/transactions/address/' + address + '/limit/100' + const res = await BlocksoftAxios.get(link) + if (!res || !res.data || typeof res.data[0] === 'undefined') { + return false + } + + CACHE_OF_TRANSACTIONS[mainCurrencyCode][address] = { + data : res.data[0], + time : now + } + return res.data[0] + } +} diff --git a/crypto/blockchains/xlm/XlmAddressProcessor.js b/crypto/blockchains/xlm/XlmAddressProcessor.js new file mode 100644 index 000000000..2057fb088 --- /dev/null +++ b/crypto/blockchains/xlm/XlmAddressProcessor.js @@ -0,0 +1,31 @@ +/** + * @version 0.20 + * https://github.com/chatch/stellar-hd-wallet/blob/master/src/stellar-hd-wallet.js + */ +import BlocksoftKeys from '../../actions/BlocksoftKeys/BlocksoftKeys' +import XlmDerivePath from '@crypto/blockchains/xlm/ext/XlmDerivePath' + +const StellarSdk = require('stellar-sdk') + +export default class XlmAddressProcessor { + + async setBasicRoot(root) { + + } + + /** + * @param {string|Buffer} privateKey + * @param {*} data + * @returns {Promise<{privateKey: string, address: string}>} + */ + async getAddress(privateKey, data = {}, superPrivateData = {}) { + const seed = await BlocksoftKeys.getSeedCached(superPrivateData.mnemonic) + const seedHex = seed.toString('hex') + if (seedHex.length < 128) { + throw new Error('bad seedHex') + } + const res = XlmDerivePath(seedHex, `m/44'/148'/0'`) + const keypair = StellarSdk.Keypair.fromRawEd25519Seed(res.key) + return { address: keypair.publicKey(), privateKey: keypair.secret() } + } +} diff --git a/crypto/blockchains/xlm/XlmScannerProcessor.js b/crypto/blockchains/xlm/XlmScannerProcessor.js new file mode 100644 index 000000000..1d5de6cb6 --- /dev/null +++ b/crypto/blockchains/xlm/XlmScannerProcessor.js @@ -0,0 +1,212 @@ +/** + * @version 0.20 + */ +import BlocksoftAxios from '../../common/BlocksoftAxios' +import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog' +import BlocksoftUtils from '../../common/BlocksoftUtils' +import config from '../../../app/config/config' + +const API_PATH = 'https://horizon.stellar.org' +const FEE_DECIMALS = 7 + +export default class XlmScannerProcessor { + + + /** + * https://horizon.stellar.org/accounts/GBH4TZYZ4IRCPO44CBOLFUHULU2WGALXTAVESQA6432MBJMABBB4GIYI + * @param {string} address + * @return {Promise<{balance, unconfirmed, provider}>} + */ + async getBalanceBlockchain(address) { + const link = `${API_PATH}/accounts/${address}` + let res = false + let balance = 0 + try { + res = await BlocksoftAxios.getWithoutBraking(link) + if (res && typeof res.data !== 'undefined' && res.data && typeof res.data.balances !== 'undefined') { + let row + for (row of res.data.balances) { + if (row.asset_type === 'native') { + balance = row.balance + break + } + } + } else { + return false + } + } catch (e) { + if (e.message.indexOf('timed out') === -1 && e.message.indexOf('account not found') === -1 && e.message.indexOf('the resource at the url requested was not found') === -1 ) { + throw e + } else { + return false + } + } + return { balance: balance, unconfirmed: 0, provider: 'horizon.stellar.org' } + } + + + /** + * https://horizon.stellar.org/accounts/GBH4TZYZ4IRCPO44CBOLFUHULU2WGALXTAVESQA6432MBJMABBB4GIYI/payments + * @param {string} scanData.account.address + * @return {Promise} + */ + async getTransactionsBlockchain(scanData, source = '') { + const address = scanData.account.address.trim() + BlocksoftCryptoLog.log('XlmScannerProcessor.getTransactions started ' + address) + const linkTxs = `${API_PATH}/accounts/${address}/transactions?order=desc&limit=50` + let res = false + try { + res = await BlocksoftAxios.getWithoutBraking(linkTxs) + } catch (e) { + if (e.message.indexOf('account not found') === -1 + && e.message.indexOf('to retrieve payments') === -1 + && e.message.indexOf('limit exceeded') === -1 + && e.message.indexOf('timed out') === -1 + && e.message.indexOf('resource missing') === -1 + ) { + throw e + } else { + return false + } + } + + if (!res || typeof res.data === 'undefined' || !res.data) { + return false + } + if (typeof res.data._embedded === 'undefined' || typeof typeof res.data._embedded.records === 'undefined') { + throw new Error('Undefined basic txs ' + linkTxs + ' ' + JSON.stringify(res.data)) + } + if (typeof res.data._embedded.records === 'string') { + throw new Error('Undefined basic txs ' + linkTxs + ' ' + res.data._embedded.records) + } + const basicTxs = {} + for (const row of res.data._embedded.records) { + basicTxs[row.hash] = row + } + + + const link = `${API_PATH}/accounts/${address}/payments?order=desc&limit=50` + res = false + try { + res = await BlocksoftAxios.getWithoutBraking(link) + } catch (e) { + if (e.message.indexOf('account not found') === -1 + && e.message.indexOf('to retrieve payments') === -1 + && e.message.indexOf('limit exceeded') === -1 + && e.message.indexOf('timed out') === -1 + ) { + throw e + } else { + return false + } + } + + if (!res || typeof res.data === 'undefined' || !res.data) { + return false + } + if (typeof res.data._embedded === 'undefined' || typeof typeof res.data._embedded.records === 'undefined') { + throw new Error('Undefined txs ' + link + ' ' + JSON.stringify(res.data)) + } + if (typeof res.data._embedded.records === 'string') { + throw new Error('Undefined txs ' + link + ' ' + res.data._embedded.records) + } + + const transactions = await this._unifyTransactions(address, res.data._embedded.records, basicTxs) + BlocksoftCryptoLog.log('XlmScannerProcessor.getTransactions finished ' + address) + return transactions + } + + async _unifyTransactions(address, result, basicTxs) { + const transactions = [] + let tx + for (tx of result) { + const transaction = await this._unifyPayment(address, tx, typeof basicTxs[tx.transaction_hash] ? basicTxs[tx.transaction_hash] : false) + if (transaction) { + transactions.push(transaction) + } + } + return transactions + } + + /** + * @param {string} address + * @param {Object} transaction + * @param {string} transaction.amount "1.6387292" + * @param {string} transaction.asset_type "native" + * @param {string} transaction.created_at "2021-01-30T21:15:04Z" + * @param {string} transaction.from "GDK45DNCNF66HZ634ZGYHVB3KGF3MUFJJ3CKWCI2QTKHRPQW22PBP5OE" + * @param {string} transaction.id "145082573625237505" + * @param {string} transaction.paging_token "145082573625237505" + * @param {string} transaction.source_account "GDK45DNCNF66HZ634ZGYHVB3KGF3MUFJJ3CKWCI2QTKHRPQW22PBP5OE" + * @param {string} transaction.to "GBH4TZYZ4IRCPO44CBOLFUHULU2WGALXTAVESQA6432MBJMABBB4GIYI" + * @param {string} transaction.transaction_hash "d01d19b75638405a510db0ac0e937849548ee21ff411ebe376a30d25dc78b750" + * @param {string} transaction.transaction_successful true + * @param {string} transaction.type "payment" + * @param {string} transaction.type_i 1 + * @return {UnifiedTransaction} + * @private + **/ + async _unifyPayment(address, transaction, basicTransaction) { + try { + if (typeof transaction.asset_type !== 'undefined') { + if (transaction.asset_type !== 'native') { + return false + } + } else if (typeof transaction.type !== 'undefined') { + if (transaction.type === 'create_account') { + if (typeof transaction.amount === 'undefined' && typeof transaction.starting_balance !== 'undefined') { + transaction.amount = transaction.starting_balance + } + if (typeof transaction.source_account === 'undefined' && typeof transaction.funder !== 'undefined') { + transaction.source_account = transaction.funder + } + } else { + return false + } + } else { + return false + } + const tx = { + transactionHash: transaction.transaction_hash, + blockHash: '', + blockNumber: '', + blockTime: transaction.created_at, + blockConfirmations: transaction.transaction_successful === true ? 100 : 0, + transactionDirection: '?', + addressFrom: transaction.source_account === address ? '' : transaction.source_account, + addressTo: transaction.account === address ? '' : transaction.account, + addressAmount: transaction.amount, + transactionStatus: transaction.transaction_successful === true ? 'success' : 'new', + transactionFee: '0' + } + if (tx.addressTo === '' || !tx.addressTo) { + if (tx.addressFrom === '') { + tx.transactionDirection = 'self' + } else { + tx.transactionDirection = 'income' + } + } else { + tx.transactionDirection = 'outcome' + } + + if (basicTransaction) { + if (typeof basicTransaction.fee_charged !== 'undefined') { + tx.transactionFee = BlocksoftUtils.toUnified(basicTransaction.fee_charged, FEE_DECIMALS) + } + if (typeof basicTransaction.ledger !== 'undefined') { + tx.blockHash = basicTransaction.ledger + tx.blockNumber = basicTransaction.ledger + } + if (typeof basicTransaction.memo !== 'undefined') { + tx.transactionJson = { memo: basicTransaction.memo } + } + } + return tx + } catch(e) { + if (config.debug.cryptoErrors) { + console.log('XLMScannerProcessor _unifyPayment error ' + e.message) + } + throw e + } + } +} diff --git a/crypto/blockchains/xlm/XlmTransferProcessor.ts b/crypto/blockchains/xlm/XlmTransferProcessor.ts new file mode 100644 index 000000000..d5c1a1143 --- /dev/null +++ b/crypto/blockchains/xlm/XlmTransferProcessor.ts @@ -0,0 +1,222 @@ +/** + * @version 0.20 + * + * + * https://developers.stellar.org/docs/tutorials/send-and-receive-payments/ + * https://www.stellar.org/developers/js-stellar-sdk/reference/examples.html + * https://www.stellar.org/developers/js-stellar-sdk/reference/ + * +wsl curl -X POST -F "tx=AAAAAgAAAACq+ux8eDBQfPoRzFjOTwHZKnFQjwRw0DSPL62mg02PjAAAAGQCA52cAAAAAQAAAAEAAAAAAAAAAAAAAABgF7+bAAAAAAAAAAEAAAAAAAAAAQAAAABUMjNVlZnxhC4rIKoE2qO/3QIFfy3nqF5/ObsdmmRWaAAAAAAAAAAABfXhAAAAAAAAAAABg02PjAAAAED6lRqsmOkqB8nRkI0tQTUSRAaHs/0mLuy6G58PvXzVQtlQiE2RPm9KC7Dv6c/a/0HS7F5mPXBFVshwtZS5WcgB" "https://horizon.stellar.org/transactions" + { + "type": "https://stellar.org/horizon-errors/transaction_failed", + "title": "Transaction Failed", + "status": 400, + "detail": "The transaction failed when submitted to the stellar network. The `extras.result_codes` field on this response contains further details. Descriptions of each code can be found at: https://www.stellar.org/developers/guides/concepts/list-of-operations.html", + "extras": { + "envelope_xdr": "AAAAAgAAAACq+ux8eDBQfPoRzFjOTwHZKnFQjwRw0DSPL62mg02PjAAAAGQCA52cAAAAAQAAAAEAAAAAAAAAAAAAAABgF7+bAAAAAAAAAAEAAAAAAAAAAQAAAABUMjNVlZnxhC4rIKoE2qO/3QIFfy3nqF5/ObsdmmRWaAAAAAAAAAAABfXhAAAAAAAAAAABg02PjAAAAED6lRqsmOkqB8nRkI0tQTUSRAaHs/0mLuy6G58PvXzVQtlQiE2RPm9KC7Dv6c/a/0HS7F5mPXBFVshwtZS5WcgB", + "result_codes": { + "transaction": "tx_too_late" + }, + "result_xdr": "AAAAAAAAAGT////9AAAAAA==" + } +} + */ +import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog' +import BlocksoftUtils from '../../common/BlocksoftUtils' +import BlocksoftDispatcher from '../BlocksoftDispatcher' +import MarketingEvent from '../../../app/services/Marketing/MarketingEvent' + +import { BlocksoftBlockchainTypes } from '../BlocksoftBlockchainTypes' +import { XlmTxSendProvider } from './basic/XlmTxSendProvider' + + +const FEE_DECIMALS = 7 + +export default class XlmTransferProcessor implements BlocksoftBlockchainTypes.TransferProcessor { + private _settings: { network: string; currencyCode: string } + private _provider: XlmTxSendProvider + + constructor(settings: { network: string; currencyCode: string }) { + this._settings = settings + this._provider = new XlmTxSendProvider() + } + + needPrivateForFee(): boolean { + return false + } + + checkSendAllModal(data: { currencyCode: any }): boolean { + return false + } + + async checkTransferHasError(data: BlocksoftBlockchainTypes.CheckTransferHasErrorData): Promise { + // @ts-ignore + if (data.amount && data.amount * 1 > 20) { + return { isOk: true } + } + /** + * @type {XlmScannerProcessor} + */ + const balanceProvider = BlocksoftDispatcher.getScannerProcessor(this._settings.currencyCode) + const balanceRaw = await balanceProvider.getBalanceBlockchain(data.addressTo) + if (balanceRaw && typeof balanceRaw.balance !== 'undefined' && balanceRaw.balance > 1) { + return { isOk: true } + } else { + return { isOk: false, code: 'XLM', address: data.addressTo } + } + } + + async getFeeRate(data: BlocksoftBlockchainTypes.TransferData, privateData: BlocksoftBlockchainTypes.TransferPrivateData, additionalData: {} = {}): Promise { + const result: BlocksoftBlockchainTypes.FeeRateResult = { + selectedFeeIndex: -1, + shouldShowFees : false + } as BlocksoftBlockchainTypes.FeeRateResult + + // @ts-ignore + if (data.amount * 1 <= 0) { + BlocksoftCryptoLog.log(this._settings.currencyCode + ' XlmTransferProcessor.getFeeRate ' + data.addressFrom + ' => ' + data.addressTo + ' skipped as zero amount') + return result + } + + BlocksoftCryptoLog.log(this._settings.currencyCode + ' XlmTransferProcessor.getFeeRate ' + data.addressFrom + ' => ' + data.addressTo + ' started amount: ' + data.amount) + + const getFee = await this._provider.getFee() + + if (!getFee) { + throw new Error('SERVER_RESPONSE_BAD_INTERNET') + } + // @ts-ignore + const fee = BlocksoftUtils.toUnified(getFee, FEE_DECIMALS) + + BlocksoftCryptoLog.log(this._settings.currencyCode + ' XlmTransferProcessor.getFeeRate ' + data.addressFrom + ' => ' + data.addressTo + ' finished amount: ' + data.amount + ' fee: ' + fee) + result.fees = [ + { + langMsg: 'xrp_speed_one', + feeForTx: fee, + amountForTx: data.amount, + blockchainData: getFee + } + ] + result.selectedFeeIndex = 0 + return result + } + + async getTransferAllBalance(data: BlocksoftBlockchainTypes.TransferData, privateData: BlocksoftBlockchainTypes.TransferPrivateData, additionalData: BlocksoftBlockchainTypes.TransferAdditionalData = {}): Promise { + + const balance = data.amount + // @ts-ignore + BlocksoftCryptoLog.log(this._settings.currencyCode + ' XlmTransferProcessor.getTransferAllBalance ', data.addressFrom + ' => ' + balance) + // noinspection EqualityComparisonWithCoercionJS + if (BlocksoftUtils.diff(balance, 1) <= 0) { + return { + selectedTransferAllBalance: '0', + selectedFeeIndex: -1, + fees: [], + shouldShowFees : false, + countedForBasicBalance: '0' + } + } + + + const result = await this.getFeeRate(data, privateData, additionalData) + // @ts-ignore + if (!result || result.selectedFeeIndex < 0) { + return { + selectedTransferAllBalance: '0', + selectedFeeIndex: -2, + fees: [], + shouldShowFees : false, + countedForBasicBalance: balance + } + } + // @ts-ignore + let newAmount = BlocksoftUtils.diff(result.fees[result.selectedFeeIndex].amountForTx, result.fees[result.selectedFeeIndex].feeForTx).toString() + newAmount = BlocksoftUtils.diff(newAmount, 1).toString() + /* + console.log(' ' + result.fees[result.selectedFeeIndex].amountForTx) + console.log('--' + result.fees[result.selectedFeeIndex].feeForTx) + console.log('=' + newAmount) + */ + result.fees[result.selectedFeeIndex].amountForTx = newAmount + const tmp = { + ...result, + shouldShowFees : false, + selectedTransferAllBalance: result.fees[result.selectedFeeIndex].amountForTx + } + // console.log('tmp', JSON.stringify(tmp)) + return tmp + } + + async sendTx(data: BlocksoftBlockchainTypes.TransferData, privateData: BlocksoftBlockchainTypes.TransferPrivateData, uiData: BlocksoftBlockchainTypes.TransferUiData): Promise { + + if (typeof privateData.privateKey === 'undefined') { + throw new Error('XLM transaction required privateKey') + } + if (typeof data.addressTo === 'undefined') { + throw new Error('XLM transaction required addressTo') + } + + if (typeof uiData.selectedFee === 'undefined' || typeof uiData.selectedFee.blockchainData === 'undefined') { + const getFee = await this._provider.getFee() + + if (!getFee) { + throw new Error('SERVER_RESPONSE_BAD_INTERNET') + } + if (typeof uiData.selectedFee === 'undefined') { + // @ts-ignore + uiData.selectedFee = {} + } + uiData.selectedFee.blockchainData = getFee + } + + let transaction = false + try { + transaction = await this._provider.getPrepared(data, privateData, uiData) + } catch (e) { + if (e.message.indexOf('destination is invalid') !== -1) { + throw new Error('SERVER_RESPONSE_BAD_DESTINATION') + } + throw e + } + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' XlmTransferProcessor.sendTx prepared') + let raw = transaction.toEnvelope().toXDR('base64') + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' XlmTransferProcessor.sendTx base64', raw) + if (typeof uiData !== 'undefined' && typeof uiData.selectedFee !== 'undefined'&& typeof uiData.selectedFee.rawOnly !== 'undefined' && uiData.selectedFee.rawOnly) { + return { rawOnly: uiData.selectedFee.rawOnly, raw } + } + + let result = false + try { + result = await this._provider.sendRaw(raw) + } catch (e) { + if (e.message.indexOf('op_no_destination') !== -1) { + transaction = await this._provider.getPrepared(data, privateData, uiData, 'create_account') + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' XlmTransferProcessor.sendTx prepared create account') + raw = transaction.toEnvelope().toXDR('base64') + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' XlmTransferProcessor.sendTx base64 create account', raw) + result = await this._provider.sendRaw(raw) + } else { + MarketingEvent.logOnlyRealTime('v20_stellar_error ' + data.addressFrom + ' => ' + data.addressTo + ' ' + e.message, { + raw + }) + if (e.message === 'op_underfunded') { + throw new Error('SERVER_RESPONSE_NOTHING_TO_TRANSFER') + } else { + throw e + } + } + } + if (!result || typeof result.hash === 'undefined') { + MarketingEvent.logOnlyRealTime('v20_stellar_no_result ' + data.addressFrom + ' => ' + data.addressTo, { + raw + }) + throw new Error('SERVER_RESPONSE_NO_RESPONSE') + } + + MarketingEvent.logOnlyRealTime('v20_stellar_success_result ' + data.addressFrom + ' => ' + data.addressTo + ' ' + result.hash, { + result + }) + + return {transactionHash : result.hash} + } +} diff --git a/crypto/blockchains/xlm/basic/XlmTxSendProvider.ts b/crypto/blockchains/xlm/basic/XlmTxSendProvider.ts new file mode 100644 index 000000000..07ca78c31 --- /dev/null +++ b/crypto/blockchains/xlm/basic/XlmTxSendProvider.ts @@ -0,0 +1,159 @@ +/** + * @version 0.20 + * https://developers.stellar.org/docs/tutorials/send-and-receive-payments/ + * https://www.stellar.org/developers/horizon/reference/endpoints/transactions-create.html + */ +import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog' +import BlocksoftExternalSettings from '../../../common/BlocksoftExternalSettings' +import { BlocksoftBlockchainTypes } from '../../BlocksoftBlockchainTypes' + +import { XrpTxUtils } from '../../xrp/basic/XrpTxUtils' + +import config from '../../../../app/config/config' + +const StellarSdk = require('stellar-sdk') + + +const CACHE_VALID_TIME = 600000 // 10 minute +let CACHE_FEES_TIME = 0 +let CACHE_FEES_VALUE = 0 + +const TX_TIMEOUT = 30 + +export class XlmTxSendProvider { + + private readonly _api: any + private readonly _server: any + + constructor() { + this._server = BlocksoftExternalSettings.getStatic('XLM_SERVER') + this._api = new StellarSdk.Server(this._server) + CACHE_FEES_VALUE = BlocksoftExternalSettings.getStatic('XLM_SERVER_PRICE') + } + + async getFee() { + const force = BlocksoftExternalSettings.getStatic('XLM_SERVER_PRICE_FORCE') + if (force * 1 > 1) { + return force + } + + const now = new Date().getTime() + if (now - CACHE_FEES_TIME <= CACHE_VALID_TIME) { + return CACHE_FEES_VALUE + } + + BlocksoftCryptoLog.log('XlmSendProvider.getFee link ' + this._server) + let res = CACHE_FEES_VALUE + try { + res = await this._api.fetchBaseFee() + if (res * 1 > 0) { + CACHE_FEES_VALUE = res * 1 + CACHE_FEES_TIME = now + } + } catch (e) { + BlocksoftCryptoLog.log('XlmSendProvider.getFee error ' + e.message + ' link ' + this._server) + res = CACHE_FEES_VALUE + } + return res + } + + async getPrepared(data: BlocksoftBlockchainTypes.TransferData, privateData: BlocksoftBlockchainTypes.TransferPrivateData, uiData: BlocksoftBlockchainTypes.TransferUiData, type = 'usual') { + const account = await this._api.loadAccount(data.addressFrom) + + let transaction + try { + let operation + if (type === 'create_account') { + // https://stellar.stackexchange.com/questions/2144/create-multiple-trustlines-upon-account-creation + operation = StellarSdk.Operation.createAccount({ + destination: data.addressTo, + startingBalance: XrpTxUtils.amountPrep(data.amount) + }) + } else { + operation = StellarSdk.Operation.payment({ + destination: data.addressTo, + asset: StellarSdk.Asset.native(), + amount: XrpTxUtils.amountPrep(data.amount) + }) + } + + if (typeof data.memo !== 'undefined' && data.memo && data.memo.toString().trim().length > 0) { + transaction = new StellarSdk.TransactionBuilder(account, { + fee: uiData.selectedFee.blockchainData, + networkPassphrase: StellarSdk.Networks.PUBLIC + }).addOperation(operation).addMemo(StellarSdk.Memo.text(data.memo)).setTimeout(TX_TIMEOUT).build() + } else { + transaction = new StellarSdk.TransactionBuilder(account, { + fee: uiData.selectedFee.blockchainData, + networkPassphrase: StellarSdk.Networks.PUBLIC + }).addOperation(operation).setTimeout(TX_TIMEOUT).build() + } + } catch (e) { + await BlocksoftCryptoLog.log('XlmTxSendProvider builder create error ' + e.message) + throw e + } + + try { + transaction.sign(StellarSdk.Keypair.fromSecret(privateData.privateKey)) + } catch (e) { + await BlocksoftCryptoLog.log('XlmTxSendProvider sign error ' + e.message) + throw e + } + return transaction + } + + async sendRaw(raw: string) { + let result = false + const link = BlocksoftExternalSettings.getStatic('XLM_SEND_LINK') + BlocksoftCryptoLog.log('XlmSendProvider.sendRaw ' + link + ' raw ' + raw) + try { + // console.log(`curl -X POST -F "tx=${raw}" "https://horizon.stellar.org/transactions"`) + + const formData = new FormData() + formData.append('tx', raw) + + const response = await fetch(link, { + method: 'POST', + credentials: 'same-origin', + mode: 'same-origin', + headers: { + 'Content-Type': 'multipart/form-data' + }, + body: formData + }) + result = await response.json() + if (result && typeof result.extras !== 'undefined' && typeof result.extras.result_codes !== 'undefined') { + if (config.debug.cryptoErrors) { + console.log('XlmTransferProcessor.sendTx result.extras.result_codes ' + JSON.stringify(result.extras.result_codes)) + } + await BlocksoftCryptoLog.log('XlmTransferProcessor.sendTx result.extras.result_codes ' + JSON.stringify(result.extras.result_codes)) + + if (typeof result.extras.result_codes.operations !== 'undefined') { + throw new Error(result.extras.result_codes.operations[0] + ' ' + raw) + } + if (typeof result.extras.result_codes.transaction !== 'undefined') { + throw new Error(result.extras.result_codes.transaction + ' ' + raw) + } + } + if (typeof result.status !== 'undefined') { + if (result.status === 406 || result.status === 400 || result.status === 504) { + throw new Error(result.title) + } + } + } catch (e) { + if (config.debug.cryptoErrors) { + console.log('XlmTransferProcessor.sendTx error ' + e.message + ' link ' + link) + } + await BlocksoftCryptoLog.log('XlmTransferProcessor.sendTx error ' + e.message + ' link ' + link) + if (e.message.indexOf('status code 406') !== -1 || e.message.indexOf('status code 400') !== -1 || e.message.indexOf('status code 504') !== -1) { + throw new Error('SERVER_RESPONSE_NOT_CONNECTED') + } else if (e.message.indexOf('tx_insufficient_fee') !== -1) { + throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_FEE') + } else { + throw e + } + } + await BlocksoftCryptoLog.log('XlmTransferProcessor.sendTx result ', result) + return result + } +} diff --git a/crypto/blockchains/xlm/ext/XlmDerivePath.js b/crypto/blockchains/xlm/ext/XlmDerivePath.js new file mode 100644 index 000000000..e034a5a05 --- /dev/null +++ b/crypto/blockchains/xlm/ext/XlmDerivePath.js @@ -0,0 +1,49 @@ +/** + * @version 0.20 + * actual derivePath from 'ed25519-hd-key' + */ +const createHmac = require('create-hmac') +const ED25519_CURVE = 'ed25519 seed' +const HARDENED_OFFSET = 0x80000000 + +function getMasterKeyFromSeed(seed) { + const hmac = createHmac('sha512', ED25519_CURVE) + const I = hmac.update(Buffer.from(seed, 'hex')).digest() + const IL = I.slice(0, 32) + const IR = I.slice(32) + return { + key: IL, + chainCode: IR + } +} + +function CKDPriv(_ref, index) { + const key = _ref.key + const chainCode = _ref.chainCode + const indexBuffer = Buffer.allocUnsafe(4) + indexBuffer.writeUInt32BE(index, 0) + const data = Buffer.concat([Buffer.alloc(1, 0), key, indexBuffer]) + const I = createHmac('sha512', chainCode).update(data).digest() + const IL = I.slice(0, 32) + const IR = I.slice(32) + return { + key: IL, + chainCode: IR + } +} + +export default (seed, derivationPath) => { + const getMaster = getMasterKeyFromSeed(seed) + const key = getMaster.key + const chainCode = getMaster.chainCode + const segments = derivationPath + .split('/') + .slice(1) + .map(el => el.replace("'", '')) + .map(el => parseInt(el, 10)) + + const res = segments.reduce(function(parentKeys, segment) { + return CKDPriv(parentKeys, segment + HARDENED_OFFSET) + }, { key: key, chainCode: chainCode }) + return res +} \ No newline at end of file diff --git a/crypto/blockchains/xmr/XmrAddressProcessor.js b/crypto/blockchains/xmr/XmrAddressProcessor.js new file mode 100644 index 000000000..b2ba8be75 --- /dev/null +++ b/crypto/blockchains/xmr/XmrAddressProcessor.js @@ -0,0 +1,137 @@ +/** + * @version 0.11 + * https://coinomi.github.io/bip39-monero/ + * + * + * let mnemonic = '' + * let results = await BlocksoftKeys.discoverAddresses({ mnemonic, fullTree: false, fromIndex: 0, toIndex: 1, currencyCode: ['XMR'] }) + * console.log('r', results['XMR'][0]) + */ +import MoneroUtils from './ext/MoneroUtils' +import MoneroMnemonic from './ext/MoneroMnemonic' +import { soliditySha3 } from 'web3-utils' +import BlocksoftAxios from '../../common/BlocksoftAxios' +import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog' +import BlocksoftSecrets from '@crypto/actions/BlocksoftSecrets/BlocksoftSecrets' +import config from '@app/config/config' + +const bitcoin = require('bitcoinjs-lib') +const networksConstants = require('../../common/ext/networks-constants') + +const BTC = networksConstants['mainnet'].network + + +export default class XmrAddressProcessor { + + _root = false + + async setBasicRoot(root) { + this._root = root + } + + + /** + * @param {string|Buffer} privateKey + * @param {*} data.publicKey + * @param {*} data.walletHash + * @param {*} data.derivationPath + * @param {*} data.derivationIndex + * @param {*} data.derivationType + * @returns {Promise<{privateKey: string, address: string, addedData: *}>} + */ + async getAddress(privateKey, data = {}, superPrivateData = {}) { + let walletMnemonic = false + try { + walletMnemonic = await BlocksoftSecrets.getWords({currencyCode : 'XMR', mnemonic: superPrivateData.mnemonic}) + } catch (e) { + if (config.debug.cryptoErrors) { + console.log('XmrAddressProcessor.getAddress recheck mnemonic error ' + e.message) + } + } + if (!walletMnemonic) { + return { + address: 'invalidRecheck1', + privateKey: '' + } + } + + if (typeof data.derivationType !== 'undefined' && data.derivationType && data.derivationType !== 'main') { + return false + } + if (typeof data.derivationIndex === 'undefined' || !data.derivationIndex || data.derivationIndex === 0) { + const child = this._root.derivePath('m/44\'/128\'/0\'/0/0') + privateKey = child.privateKey + } else { + privateKey = Buffer.from(privateKey) + } + const keyPair = bitcoin.ECPair.fromPrivateKey(privateKey, { network: BTC }) + const rawPrivateKey = keyPair.privateKey + const rawSecretSpendKey = soliditySha3(rawPrivateKey) + const rawSecretSpendKeyBuffer = Buffer.from(rawSecretSpendKey.substr(2), 'hex') + + const secretSpendKey = MoneroUtils.sc_reduce32(rawSecretSpendKeyBuffer) + + const secretViewKey = MoneroUtils.hash_to_scalar(secretSpendKey) + + const words = MoneroMnemonic.secret_spend_key_to_words(MoneroUtils.normString(secretSpendKey), typeof data.walletHash !== 'undefined' ? data.walletHash : 'none') + if (words !== walletMnemonic) { + return { + address: 'invalidRecheck2', + privateKey: '' + } + } + + const publicSpendKey = MoneroUtils.secret_key_to_public_key(secretSpendKey).toString('hex') + + const publicViewKey = MoneroUtils.secret_key_to_public_key(secretViewKey).toString('hex') + + const address = MoneroUtils.pub_keys_to_address(0, publicSpendKey, publicViewKey) + + + let mymoneroError = 0 + let linkParamsLogin = {} + try { + linkParamsLogin = { + address: address, + view_key: MoneroUtils.normString(secretViewKey.toString('hex')), + create_account: true, + generated_locally: true + } + const resLogin = await BlocksoftAxios.post('https://api.mymonero.com:8443/login', linkParamsLogin) + if (typeof resLogin.data === 'undefined' || !resLogin.data || typeof resLogin.data.new_address === 'undefined') { + throw new Error('no data') + } + } catch (e) { + BlocksoftCryptoLog.err('XmrAddressProcessor !!!mymonero error!!! ' + e.message, { + linkParamsLogin, + publicSpendKeyL : publicSpendKey.length, + publicViewKeyL : publicViewKey.length}) + mymoneroError = 1 + } + + /* + console.log({ + derivationPath : data.derivationPath, + secretSpendKey, + ss : Buffer.from(secretSpendKey, 'hex'), + secretViewKey, + sv : Buffer.from(secretViewKey, 'hex'), + words, + publicViewKey: publicViewKey.toString('hex'), + publicSpendKey: publicSpendKey.toString('hex'), + address + }) + */ + + return { + address: address, + privateKey: MoneroUtils.normString(secretSpendKey.toString('hex')) + '_' + MoneroUtils.normString(secretViewKey.toString('hex')), + addedData: { + publicViewKey: MoneroUtils.normString(publicViewKey), + publicSpendKey: MoneroUtils.normString(publicSpendKey), + derivationIndex: 0, + mymoneroError + } + } + } +} diff --git a/crypto/blockchains/xmr/XmrScannerProcessor.js b/crypto/blockchains/xmr/XmrScannerProcessor.js new file mode 100644 index 000000000..e70ad540f --- /dev/null +++ b/crypto/blockchains/xmr/XmrScannerProcessor.js @@ -0,0 +1,329 @@ +/** + * @version 0.11 + * https://api.mymonero.com:8443/get_address_info + */ + +import BlocksoftUtils from '@crypto/common/BlocksoftUtils' +import BlocksoftAxios from '@crypto/common/BlocksoftAxios' +import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog' +import BlocksoftPrivateKeysUtils from '@crypto/common/BlocksoftPrivateKeysUtils' + +import MoneroUtilsParser from './ext/MoneroUtilsParser' + +import { showModal } from '@app/appstores/Stores/Modal/ModalActions' +import { strings } from '@app/services/i18n' +import config from '@app/config/config' + +const CACHE_VALID_TIME = 30000 // 30 seconds +const CACHE = {} +const NEVER_LOGIN = {} +let CACHE_SHOWN_ERROR = 0 + +export default class XmrScannerProcessor { + + /** + * @private + */ + _serverUrl = false + + _blocksToConfirm = 30 + + _maxBlockNumber = 500000000 + + constructor(settings) { + this._settings = settings + } + + async _getCache(address, additionalData, walletHash) { + if (typeof CACHE[address] !== 'undefined') { + CACHE[address].provider = 'mymonero-cache-all' + return CACHE[address] + } else { + return false + } + + } + + /** + * @param address + * @param additionalData + * @param walletHash + * @returns {Promise} + * @private + */ + async _get(address, additionalData, walletHash) { + BlocksoftCryptoLog.log('XMR XmrScannerProcessor._get ' + walletHash + ' ' + address) + const now = new Date().getTime() + if (typeof CACHE[address] !== 'undefined' && (now - CACHE[address].time < CACHE_VALID_TIME)) { + CACHE[address].provider = 'mymonero-cache' + return CACHE[address] + } + + //@todo nodes support + //this._serverUrl = await settingsActions.getSetting('xmrServer') + //if (!this._serverUrl || this._serverUrl === 'false') { + this._serverUrl = 'api.mymonero.com:8443' + //} + + let link = this._serverUrl.trim() + if (link.substr(0, 4).toLowerCase() !== 'http') { + link = 'https://' + this._serverUrl + } + if (link[link.length - 1] !== '/') { + link = link + '/' + } + + const discoverFor = { + addressToCheck: address, + walletHash: walletHash, + currencyCode: 'XMR', + derivationPath: 'm/44\'/0\'/0\'/0/0', + derivationIndex: typeof additionalData.derivationIndex !== 'undefined' ? additionalData.derivationIndex : 0 + } + + const result = await BlocksoftPrivateKeysUtils.getPrivateKey(discoverFor, 'XmrScannerProcessor') // privateSpend_privateView + const keys = result.privateKey.split('_') + const spendKey = keys[0] // private spend and view keys + let viewKey = keys[1] + while (viewKey.length < 64) { + viewKey += '0' + } + const linkParams = { address: address, view_key: viewKey } + + + let res = false + try { + BlocksoftCryptoLog.log(this._settings.currencyCode + ' XmrScannerProcessor._get start ' + link + 'get_address_info', JSON.stringify(linkParams)) + res = await BlocksoftAxios.post(link + 'get_address_info', linkParams) + } catch (e) { + BlocksoftCryptoLog.log(this._settings.currencyCode + ' XmrScannerProcessor._get error ' + e.message, JSON.stringify(linkParams)) + if (CACHE_SHOWN_ERROR === 0 && e.message.indexOf('invalid address and/or view key') !== -1) { + showModal({ + type: 'INFO_MODAL', + icon: false, + title: strings('modal.walletLog.sorry'), + description: strings('settings.walletList.needReinstallXMR') + }) + CACHE_SHOWN_ERROR++ + if (CACHE_SHOWN_ERROR > 100) { + CACHE_SHOWN_ERROR = 0 + } + } + } + if (!res || !res.data) { + if (typeof NEVER_LOGIN[address] === 'undefined') { + const linkParamsLogin = { + address: address, + view_key: viewKey, + create_account: true, + generated_locally: true + } + try { + await BlocksoftAxios.post('https://api.mymonero.com:8443/login', linkParamsLogin) // login needed + } catch (e) { + BlocksoftCryptoLog.log(this._settings.currencyCode + ' XmrScannerProcessor._get login error ' + e.message, linkParamsLogin) + if (CACHE_SHOWN_ERROR === 0 && e.message.indexOf('invalid address and/or view key') !== -1) { + showModal({ + type: 'INFO_MODAL', + icon: false, + title: strings('modal.walletLog.sorry'), + description: strings('settings.walletList.needReinstallXMR') + }) + CACHE_SHOWN_ERROR++ + if (CACHE_SHOWN_ERROR > 100) { + CACHE_SHOWN_ERROR = 0 + } + } + } + } + return false + } + if (typeof res.data.spent_outputs === 'undefined') { + throw new Error('XMR XmrScannerProcessor._get nothing loaded for address ' + link) + } + + let parsed = false + try { + parsed = await MoneroUtilsParser.parseAddressInfo(address, res.data, viewKey, additionalData.publicSpendKey, spendKey) + } catch (e) { + if (config.debug.cryptoErrors) { + console.log('XMR XmrScannerProcessor._get MoneroUtilsParser.parseAddressInfo error ' + e.message) + } + await BlocksoftCryptoLog.log('XMR XmrScannerProcessor._get MoneroUtilsParser.parseAddressInfo error ' + e.message) + } + + const res2 = await BlocksoftAxios.postWithoutBraking(link + 'get_address_txs', linkParams) + if (!res2 || !res2.data) { + return false + } + + let parsed2 = false + try { + parsed2 = await MoneroUtilsParser.parseAddressTransactions(address, res2.data, viewKey, additionalData.publicSpendKey, spendKey) + } catch (e) { + if (config.debug.cryptoErrors) { + console.log('XMR XmrScannerProcessor._get MoneroUtilsParser.parseAddressTransactions error ' + e.message) + } + await BlocksoftCryptoLog.log('XMR XmrScannerProcessor._get MoneroUtilsParser.parseAddressTransactions error ' + e.message) + } + + if (parsed && parsed2) { + CACHE[address] = { + outputs: parsed?.spent_outputs, + transactions: typeof parsed2.serialized_transactions !== 'undefined' ? parsed2.serialized_transactions : parsed2.transactions, + balance: typeof parsed.total_received_String !== 'undefined' ? BlocksoftUtils.diff(parsed.total_received_String, parsed.total_sent_String) : BlocksoftUtils.diff(parsed.total_received, parsed.total_sent), + account_scan_start_height: parsed2.account_scan_start_height, + scanned_block_height: parsed2.account_scanned_block_height, + account_scanned_height: parsed2.account_scanned_height, + blockchain_height: parsed2.blockchain_height, + transaction_height: parsed2.transaction_height, + time: now, + provider: 'mymonero' + } + return CACHE[address] + } + return false + } + + /** + * @param {string} address + * @param {*} additionalData + * @param {string} walletHash + * @return {Promise<{balance:*, unconfirmed:*, provider:string}>} + */ + async getBalanceBlockchain(address, additionalData, walletHash) { + if (address === 'invalidRecheck1') { + return { balance: 0, unconfirmed: 0, provider: 'error'} + } + const res = await this._get(address, additionalData, walletHash) + if (!res) { + return false + } + return { balance: res.balance, unconfirmed: 0, provider: res.provider, time: res.time } + } + + /** + * @param {string} scanData.account.address + * @param {*} scanData.additional + * @param {string} scanData.account.walletHash + * @return {Promise} + */ + async getTransactionsBlockchain(scanData, source = '') { + const address = scanData.account.address.trim() + if (address === 'invalidRecheck1') { + return [] + } + const additionalData = scanData.additional + const walletHash = scanData.account.walletHash + const res = await this._get(address, additionalData, walletHash) + if (!res || typeof res === 'undefined') return [] + + if (typeof res.transactions === 'undefined' || !res.transactions) return [] + let tx + const transactions = [] + + for (tx of res.transactions) { + const transaction = await this._unifyTransaction(address, res.scanned_block_height, tx) + if (transaction) { + transactions.push(transaction) + } + } + return transactions + } + + /** + * + * @param {string} address + * @param {string} lastBlock + * @param {Object} transaction + * @param {BigInteger} transaction.amount BigInteger {_d: Array(2), _s: -1} + * @param {string} transaction.approx_float_amount -0.00002724 + * @param {string} transaction.coinbase false + * @param {string} transaction.fee "27240000" + * @param {string} transaction.hash "ac319a3240f15dab342102fe248d3b95636f8a0bbfa962a5645521fac8fb86d3" + * @param {string} transaction.height 2152183 + * @param {string} transaction.id 10506991 + * @param {string} transaction.mempool: false + * @param {string} transaction.mixin 10 + * @param {string} transaction.payment_id "" + * @param {string} transaction.spent_outputs [{…}] + * @param {string} transaction.timestamp Tue Jul 28 2020 18:10:26 GMT+0300 (Восточная Европа, летнее время) {} + * @param {string} transaction.total_received "12354721582" + * @param {BigInteger} transaction.total_sent BigInteger {_d: Array(2), _s: 1} + * @param {string} transaction.unlock_time + * @return {Promise} + * @private + */ + async _unifyTransaction(address, lastBlock, transaction) { + + let transactionStatus = 'new' + transaction.confirmations = lastBlock * 1 - transaction.height * 1 + //if (transaction.mempool === false) { + if (transaction.confirmations >= this._blocksToConfirm) { + transactionStatus = 'success' + } else if (transaction.confirmations > 0) { + transactionStatus = 'confirming' + } + // } + + if (typeof transaction.unlock_time !== 'undefined') { + const unlockTime = transaction.unlock_time * 1 + if (unlockTime > 0) { + if (unlockTime < this._maxBlockNumber) { + // then unlock time is block height + if (unlockTime > lastBlock) { + transactionStatus = 'locked' + } + } else { + // then unlock time is s timestamp as TimeInterval + const now = new Date().getTime() + if (unlockTime > now) { + transactionStatus = 'locked' + } + } + } + } + + let direction = 'self' + let amount + if (transaction.total_received !== '0') { + if (transaction.total_sent !== '0') { + const diff = BlocksoftUtils.diff(transaction.total_sent, transaction.total_received) + if (diff > 0) { + direction = 'outcome' + amount = diff + } else { + direction = 'income' + amount = -1 * diff + } + } else { + direction = 'income' + amount = transaction.total_received + } + } else if (transaction.total_sent !== '0') { + direction = 'outcome' + amount = transaction.total_sent + } + let formattedTime + try { + formattedTime = BlocksoftUtils.toDate(transaction.timestamp) + } catch (e) { + e.message += ' timestamp error transaction data ' + JSON.stringify(transaction) + throw e + } + + return { + transactionHash: transaction.hash, + blockHash: transaction.id, + blockNumber: +transaction.height, + blockTime: formattedTime, + blockConfirmations: transaction.confirmations, + transactionDirection: direction, + addressFrom: '', + addressTo: '', + addressAmount: amount, + transactionStatus: transactionStatus, + transactionFee: transaction.fee + } + } +} diff --git a/crypto/blockchains/xmr/XmrSecretsProcessor.js b/crypto/blockchains/xmr/XmrSecretsProcessor.js new file mode 100644 index 000000000..a61eb862d --- /dev/null +++ b/crypto/blockchains/xmr/XmrSecretsProcessor.js @@ -0,0 +1,66 @@ +/** + * @version 0.11 + * https://github.com/Coinomi/bip39-coinomi/releases + */ +import BlocksoftKeys from '../../actions/BlocksoftKeys/BlocksoftKeys' + +import { soliditySha3 } from 'web3-utils' +import MoneroUtils from './ext/MoneroUtils' +import MoneroMnemonic from './ext/MoneroMnemonic' + +const bip32 = require('bip32') +const bitcoin = require('bitcoinjs-lib') +const networksConstants = require('../../common/ext/networks-constants') + +const BTC = networksConstants['mainnet'].network + +export default class XmrSecretsProcessor { + + /** + * @param {string} data.mnemonic + */ + async getWords(data) { + const seed = await BlocksoftKeys.getSeedCached(data.mnemonic) + const seedHex = seed.toString('hex') + if (seedHex.length < 128) { + throw new Error('bad seedHex') + } + const root = bip32.fromSeed(seed) + const child = root.derivePath('m/44\'/128\'/0\'/0/0') + const keyPair = bitcoin.ECPair.fromPrivateKey(child.privateKey, { network: BTC }) + + const rawPrivateKey = keyPair.privateKey + const rawSecretSpendKey = soliditySha3(rawPrivateKey) + const rawSecretSpendKeyBuffer = Buffer.from(rawSecretSpendKey.substr(2), 'hex') + + let secretSpendKey = MoneroUtils.sc_reduce32(rawSecretSpendKeyBuffer) + const secretSpendLength = secretSpendKey.length + if (secretSpendLength < 64) { + for (let i = secretSpendLength; i<64; i++) { + secretSpendKey = secretSpendKey + '0' + } + } + + const secretViewKey = MoneroUtils.hash_to_scalar(secretSpendKey) + + const words = MoneroMnemonic.secret_spend_key_to_words(secretSpendKey) + + const publicSpendKey = MoneroUtils.secret_key_to_public_key(secretSpendKey) + + const publicViewKey = MoneroUtils.secret_key_to_public_key(secretViewKey) + + const address = MoneroUtils.pub_keys_to_address(0, publicSpendKey, publicViewKey) + + + /*console.log({ + secretSpendKey, + secretViewKey, + words, + publicViewKey: publicViewKey.toString('hex'), + publicSpendKey: publicSpendKey.toString('hex'), + address + })*/ + + return words + } +} diff --git a/crypto/blockchains/xmr/XmrTransferProcessor.ts b/crypto/blockchains/xmr/XmrTransferProcessor.ts new file mode 100644 index 000000000..9dd8f7908 --- /dev/null +++ b/crypto/blockchains/xmr/XmrTransferProcessor.ts @@ -0,0 +1,280 @@ +/** + * @version 0.20 + */ +import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog' + +import MoneroUtilsParser from './ext/MoneroUtilsParser' +import XmrSendProvider from './providers/XmrSendProvider' +import XmrUnspentsProvider from './providers/XmrUnspentsProvider' + +import { BlocksoftBlockchainTypes } from '../BlocksoftBlockchainTypes' +import config from '../../../app/config/config' +import BlocksoftPrettyNumbers from '@crypto/common/BlocksoftPrettyNumbers' + +export default class XmrTransferProcessor implements BlocksoftBlockchainTypes.TransferProcessor { + private sendProvider: XmrSendProvider + private unspentsProvider: XmrUnspentsProvider + private _settings: any + + constructor(settings: any) { + this._settings = settings + this.sendProvider = new XmrSendProvider(settings) + this.unspentsProvider = new XmrUnspentsProvider(settings) + } + + needPrivateForFee(): boolean { + return true + } + + checkSendAllModal(data: { currencyCode: any }): boolean { + return true + } + + async getFeeRate(data: BlocksoftBlockchainTypes.TransferData, privateData: BlocksoftBlockchainTypes.TransferPrivateData, additionalData: {} = {}): Promise { + const result: BlocksoftBlockchainTypes.FeeRateResult = { + selectedFeeIndex: -1 + } as BlocksoftBlockchainTypes.FeeRateResult + + // @ts-ignore + if (data.amount * 1 <= 0) { + BlocksoftCryptoLog.log(this._settings.currencyCode + ' XmrTransferProcessor.getFeeRate ' + data.addressFrom + ' => ' + data.addressTo + ' skipped as zero amount') + return result + } + + if (typeof data.accountJson === 'undefined' || !data.accountJson || typeof data.accountJson.publicSpendKey === 'undefined') { + throw new Error('XmrTransferProcessor public spend key is required') + } + const keys = privateData.privateKey.split('_') + const privSpendKey = keys[0] + const privViewKey = keys[1] + const pubSpendKey = data.accountJson.publicSpendKey + + BlocksoftCryptoLog.log(this._settings.currencyCode + ' XmrTransferProcessor.getFeeRate newSender ' + data.addressFrom + ' => ' + data.addressTo + ' started amount: ' + data.amount) + + const apiClient = this.unspentsProvider + + let core = await MoneroUtilsParser.getCore() + if (!core || typeof core === 'undefined') { + core = await MoneroUtilsParser.getCore() + } + + result.fees = [] + + const logFees = [] + let noBalanceError = false + apiClient.init() + + + const unspentOuts = await apiClient._getUnspents({ + address: data.addressFrom, + view_key: privViewKey, + amount: '0', + app_name: 'MyMonero', + app_version: '1.3.2', + dust_threshold: '2000000000', + mixin: 15, + use_dust: true + }, false) + + for (let i = 1; i <= 4; i++) { + try { + await BlocksoftCryptoLog.log(this._settings.currencyCode + ' XmrTransferProcessor.getFeeRate ' + data.addressFrom + ' => ' + data.addressTo + ' start amount: ' + data.amount + ' fee ' + i) + + // @ts-ignore + const fee = await core.createTransaction({ + destinations: [{ to_address : data.addressTo, send_amount: data.isTransferAll ? 0 : BlocksoftPrettyNumbers.setCurrencyCode('XMR').makePretty(data.amount)}], + shouldSweep: data.isTransferAll ? true: false, + address: data.addressFrom, + privateViewKey: privViewKey, + privateSpendKey: privSpendKey, + publicSpendKey: pubSpendKey, + priority: '' + i, + nettype: 'MAINNET', + unspentOuts: unspentOuts, + randomOutsCb: (numberOfOuts) => { + const amounts = [] + for (let i = 0; i < numberOfOuts; i++) { + amounts.push('0') + } + return apiClient._getRandomOutputs({ + amounts, + app_name: 'MyMonero', + app_version: '1.3.2', + count: 16 + }) + } + }) + + if (typeof fee !== 'undefined' && fee && typeof fee.used_fee) { + const tmp = { + langMsg: 'xmr_speed_' + i, + feeForTx: fee.used_fee, + blockchainData: { + secretTxKey: fee.tx_key, + rawTxHex: fee.serialized_signed_tx, + rawTxHash: fee.tx_hash, + usingOuts: fee.using_outs, + simplePriority: i + } + } as BlocksoftBlockchainTypes.Fee + + const logTmp = { + langMsg: 'xmr_speed_' + i, + feeForTx: fee.used_fee, + blockchainData: { + secretTxKey: fee.tx_key, + rawTxHash: fee.tx_hash, + usingOuts: fee.using_outs, + simplePriority: i + }, + amountForTx: '?' + } + if (typeof fee.using_amount !== 'undefined') { + tmp.amountForTx = fee.using_amount + logTmp.amountForTx = fee.using_amount + } else { + tmp.amountForTx = data.amount + logTmp.amountForTx = data.amount + } + tmp.addressToTx = data.addressTo + result.fees.push(tmp) + logFees.push(logTmp) + } + } catch (e) { + if (e.message.indexOf('pendable balance too low') !== -1) { + // do nothing + noBalanceError = true + break + } else { + if (config.debug.cryptoErrors) { + console.log('XmrTransferProcessor error ', e) + } + if (e.message.indexOf('An error occurred while getting decoy outputs') !== -1) { + BlocksoftCryptoLog.log(this._settings.currencyCode + ' XmrTransferProcessor error will go out bad decoy') + throw new Error('SERVER_RESPONSE_BAD_CODE') + } else if (e.message.indexOf('decode address') !== -1) { + BlocksoftCryptoLog.log(this._settings.currencyCode + ' XmrTransferProcessor error will go out') + throw new Error('SERVER_RESPONSE_BAD_DESTINATION') + } else if (e.message.indexOf('Not enough spendables') !== -1) { + BlocksoftCryptoLog.log(this._settings.currencyCode + ' XmrTransferProcessor error not enough') + throw new Error('SERVER_RESPONSE_NO_RESPONSE_XMR') + } else { + BlocksoftCryptoLog.err(this._settings.currencyCode + ' XmrTransferProcessor.getFeeRate ' + data.addressFrom + ' => ' + data.addressTo + ' finished amount: ' + data.amount + ' error fee ' + i + ': ' + e.message) + throw e + } + } + } + } + + if (result.fees.length === 0 && noBalanceError) { + throw new Error('SERVER_RESPONSE_NO_RESPONSE_XMR') + } + + // @ts-ignore + BlocksoftCryptoLog.log(this._settings.currencyCode + ' XmrTransferProcessor.getFeeRate ' + data.addressFrom + ' => ' + data.addressTo + ' finished amount: ' + data.amount + ' fee: ', logFees) + + if (result.fees.length < 3) { + result.selectedFeeIndex = result.fees.length - 1 + } else { + result.selectedFeeIndex = 2 + } + return result + } + + + async getTransferAllBalance(data: BlocksoftBlockchainTypes.TransferData, privateData: BlocksoftBlockchainTypes.TransferPrivateData, additionalData: BlocksoftBlockchainTypes.TransferAdditionalData = {}): Promise { + const balance = data.amount + + // @ts-ignore + BlocksoftCryptoLog.log(this._settings.currencyCode + ' XmrTransferProcessor.getTransferAllBalance ', data.addressFrom + ' => ' + balance) + + data.isTransferAll = true + const result = await this.getFeeRate(data, privateData, additionalData) + // @ts-ignore + if (!result || result.selectedFeeIndex < 0) { + return { + selectedTransferAllBalance: '0', + selectedFeeIndex: -2, + fees: [], + shouldShowFees : false, + countedForBasicBalance: balance + } + } + // @ts-ignore + return { + ...result, + shouldShowFees : false, + selectedTransferAllBalance: result.fees[result.selectedFeeIndex].amountForTx, + countedForBasicBalance: balance + } + } + + async sendTx(data: BlocksoftBlockchainTypes.TransferData, privateData: BlocksoftBlockchainTypes.TransferPrivateData, uiData: BlocksoftBlockchainTypes.TransferUiData): Promise { + + if (typeof privateData.privateKey === 'undefined') { + throw new Error('XMR transaction required privateKey') + } + if (typeof data.addressTo === 'undefined') { + throw new Error('XMR transaction required addressTo') + } + + if (typeof uiData.selectedFee === 'undefined') { + throw new Error('XMR transaction required selectedFee') + } + + BlocksoftCryptoLog.log(this._settings.currencyCode + ' XmrTransferProcessor.sendTx started ' + data.addressFrom + '=>' + data.addressTo + ' fee', uiData.selectedFee) + let foundFee = uiData?.selectedFee + if (data.addressTo !== uiData.selectedFee.addressToTx) { + try { + BlocksoftCryptoLog.log(this._settings.currencyCode + ' XmrTransferProcessor.sendTx rechecked ' + data.addressFrom + '=>' + data.addressTo + ' fee rebuild start as got tx to ' + uiData.selectedFee.addressToTx) + const newSelectedFee = await this.getFeeRate(data, privateData) + if (typeof newSelectedFee.fees === 'undefined' || !newSelectedFee.fees) { + throw new Error('no fees') + } + foundFee = newSelectedFee.fees[newSelectedFee.selectedFeeIndex] + for (const fee of newSelectedFee.fees) { + if (fee.langMsg === uiData.selectedFee.langMsg) { + foundFee = fee + } + } + BlocksoftCryptoLog.log(this._settings.currencyCode + ' XmrTransferProcessor.sendTx rechecked ' + data.addressFrom + '=>' + data.addressTo + ' found fee', foundFee) + } catch (e) { + BlocksoftCryptoLog.log(this._settings.currencyCode + ' XmrTransferProcessor.sendTx rechecked ' + data.addressFrom + '=>' + data.addressTo + ' fee rebuild error ' + e.message) + throw new Error('XMR transaction invalid output - please try again') + } + } + const rawTxHex = foundFee?.blockchainData?.rawTxHex + const rawTxHash = foundFee?.blockchainData?.rawTxHash + const secretTxKey = foundFee?.blockchainData?.secretTxKey + const usingOuts = foundFee?.blockchainData?.usingOuts + + const keys = privateData.privateKey.split('_') + const privViewKey = keys[1] + + if (typeof rawTxHex === 'undefined') { + throw new Error('SERVER_RESPONSE_NO_RESPONSE_XMR') + } + + if (typeof uiData !== 'undefined' && typeof uiData.selectedFee !== 'undefined'&& typeof uiData.selectedFee.rawOnly !== 'undefined' && uiData.selectedFee.rawOnly) { + return { rawOnly: uiData.selectedFee.rawOnly, raw : rawTxHex} + } + + const send = await this.sendProvider.send({ + address: data.addressFrom, + tx: rawTxHex, + privViewKey, + secretTxKey, + usingOuts, + unspentsProvider: this.unspentsProvider + }) + + // @ts-ignore + BlocksoftCryptoLog.log(this._settings.currencyCode + ' XmrTransferProcessor.sendTx result', send) + + if (send.status === 'OK') { + return { transactionHash: rawTxHash, transactionJson: { secretTxKey }} + } else { + throw new Error(this._settings.currencyCode + ' XmrTransferProcessor.sendTx status error ' + JSON.stringify(send)) + } + } +} diff --git a/crypto/blockchains/xmr/ext/MoneroDict.js b/crypto/blockchains/xmr/ext/MoneroDict.js new file mode 100644 index 000000000..ce521ca03 --- /dev/null +++ b/crypto/blockchains/xmr/ext/MoneroDict.js @@ -0,0 +1,1634 @@ +const monero_words_english_prefix_len = 3; +const monero_words_english = [ + "abbey", + "abducts", + "ability", + "ablaze", + "abnormal", + "abort", + "abrasive", + "absorb", + "abyss", + "academy", + "aces", + "aching", + "acidic", + "acoustic", + "acquire", + "across", + "actress", + "acumen", + "adapt", + "addicted", + "adept", + "adhesive", + "adjust", + "adopt", + "adrenalin", + "adult", + "adventure", + "aerial", + "afar", + "affair", + "afield", + "afloat", + "afoot", + "afraid", + "after", + "against", + "agenda", + "aggravate", + "agile", + "aglow", + "agnostic", + "agony", + "agreed", + "ahead", + "aided", + "ailments", + "aimless", + "airport", + "aisle", + "ajar", + "akin", + "alarms", + "album", + "alchemy", + "alerts", + "algebra", + "alkaline", + "alley", + "almost", + "aloof", + "alpine", + "already", + "also", + "altitude", + "alumni", + "always", + "amaze", + "ambush", + "amended", + "amidst", + "ammo", + "amnesty", + "among", + "amply", + "amused", + "anchor", + "android", + "anecdote", + "angled", + "ankle", + "annoyed", + "answers", + "antics", + "anvil", + "anxiety", + "anybody", + "apart", + "apex", + "aphid", + "aplomb", + "apology", + "apply", + "apricot", + "aptitude", + "aquarium", + "arbitrary", + "archer", + "ardent", + "arena", + "argue", + "arises", + "army", + "around", + "arrow", + "arsenic", + "artistic", + "ascend", + "ashtray", + "aside", + "asked", + "asleep", + "aspire", + "assorted", + "asylum", + "athlete", + "atlas", + "atom", + "atrium", + "attire", + "auburn", + "auctions", + "audio", + "august", + "aunt", + "austere", + "autumn", + "avatar", + "avidly", + "avoid", + "awakened", + "awesome", + "awful", + "awkward", + "awning", + "awoken", + "axes", + "axis", + "axle", + "aztec", + "azure", + "baby", + "bacon", + "badge", + "baffles", + "bagpipe", + "bailed", + "bakery", + "balding", + "bamboo", + "banjo", + "baptism", + "basin", + "batch", + "bawled", + "bays", + "because", + "beer", + "befit", + "begun", + "behind", + "being", + "below", + "bemused", + "benches", + "berries", + "bested", + "betting", + "bevel", + "beware", + "beyond", + "bias", + "bicycle", + "bids", + "bifocals", + "biggest", + "bikini", + "bimonthly", + "binocular", + "biology", + "biplane", + "birth", + "biscuit", + "bite", + "biweekly", + "blender", + "blip", + "bluntly", + "boat", + "bobsled", + "bodies", + "bogeys", + "boil", + "boldly", + "bomb", + "border", + "boss", + "both", + "bounced", + "bovine", + "bowling", + "boxes", + "boyfriend", + "broken", + "brunt", + "bubble", + "buckets", + "budget", + "buffet", + "bugs", + "building", + "bulb", + "bumper", + "bunch", + "business", + "butter", + "buying", + "buzzer", + "bygones", + "byline", + "bypass", + "cabin", + "cactus", + "cadets", + "cafe", + "cage", + "cajun", + "cake", + "calamity", + "camp", + "candy", + "casket", + "catch", + "cause", + "cavernous", + "cease", + "cedar", + "ceiling", + "cell", + "cement", + "cent", + "certain", + "chlorine", + "chrome", + "cider", + "cigar", + "cinema", + "circle", + "cistern", + "citadel", + "civilian", + "claim", + "click", + "clue", + "coal", + "cobra", + "cocoa", + "code", + "coexist", + "coffee", + "cogs", + "cohesive", + "coils", + "colony", + "comb", + "cool", + "copy", + "corrode", + "costume", + "cottage", + "cousin", + "cowl", + "criminal", + "cube", + "cucumber", + "cuddled", + "cuffs", + "cuisine", + "cunning", + "cupcake", + "custom", + "cycling", + "cylinder", + "cynical", + "dabbing", + "dads", + "daft", + "dagger", + "daily", + "damp", + "dangerous", + "dapper", + "darted", + "dash", + "dating", + "dauntless", + "dawn", + "daytime", + "dazed", + "debut", + "decay", + "dedicated", + "deepest", + "deftly", + "degrees", + "dehydrate", + "deity", + "dejected", + "delayed", + "demonstrate", + "dented", + "deodorant", + "depth", + "desk", + "devoid", + "dewdrop", + "dexterity", + "dialect", + "dice", + "diet", + "different", + "digit", + "dilute", + "dime", + "dinner", + "diode", + "diplomat", + "directed", + "distance", + "ditch", + "divers", + "dizzy", + "doctor", + "dodge", + "does", + "dogs", + "doing", + "dolphin", + "domestic", + "donuts", + "doorway", + "dormant", + "dosage", + "dotted", + "double", + "dove", + "down", + "dozen", + "dreams", + "drinks", + "drowning", + "drunk", + "drying", + "dual", + "dubbed", + "duckling", + "dude", + "duets", + "duke", + "dullness", + "dummy", + "dunes", + "duplex", + "duration", + "dusted", + "duties", + "dwarf", + "dwelt", + "dwindling", + "dying", + "dynamite", + "dyslexic", + "each", + "eagle", + "earth", + "easy", + "eating", + "eavesdrop", + "eccentric", + "echo", + "eclipse", + "economics", + "ecstatic", + "eden", + "edgy", + "edited", + "educated", + "eels", + "efficient", + "eggs", + "egotistic", + "eight", + "either", + "eject", + "elapse", + "elbow", + "eldest", + "eleven", + "elite", + "elope", + "else", + "eluded", + "emails", + "ember", + "emerge", + "emit", + "emotion", + "empty", + "emulate", + "energy", + "enforce", + "enhanced", + "enigma", + "enjoy", + "enlist", + "enmity", + "enough", + "enraged", + "ensign", + "entrance", + "envy", + "epoxy", + "equip", + "erase", + "erected", + "erosion", + "error", + "eskimos", + "espionage", + "essential", + "estate", + "etched", + "eternal", + "ethics", + "etiquette", + "evaluate", + "evenings", + "evicted", + "evolved", + "examine", + "excess", + "exhale", + "exit", + "exotic", + "exquisite", + "extra", + "exult", + "fabrics", + "factual", + "fading", + "fainted", + "faked", + "fall", + "family", + "fancy", + "farming", + "fatal", + "faulty", + "fawns", + "faxed", + "fazed", + "feast", + "february", + "federal", + "feel", + "feline", + "females", + "fences", + "ferry", + "festival", + "fetches", + "fever", + "fewest", + "fiat", + "fibula", + "fictional", + "fidget", + "fierce", + "fifteen", + "fight", + "films", + "firm", + "fishing", + "fitting", + "five", + "fixate", + "fizzle", + "fleet", + "flippant", + "flying", + "foamy", + "focus", + "foes", + "foggy", + "foiled", + "folding", + "fonts", + "foolish", + "fossil", + "fountain", + "fowls", + "foxes", + "foyer", + "framed", + "friendly", + "frown", + "fruit", + "frying", + "fudge", + "fuel", + "fugitive", + "fully", + "fuming", + "fungal", + "furnished", + "fuselage", + "future", + "fuzzy", + "gables", + "gadget", + "gags", + "gained", + "galaxy", + "gambit", + "gang", + "gasp", + "gather", + "gauze", + "gave", + "gawk", + "gaze", + "gearbox", + "gecko", + "geek", + "gels", + "gemstone", + "general", + "geometry", + "germs", + "gesture", + "getting", + "geyser", + "ghetto", + "ghost", + "giant", + "giddy", + "gifts", + "gigantic", + "gills", + "gimmick", + "ginger", + "girth", + "giving", + "glass", + "gleeful", + "glide", + "gnaw", + "gnome", + "goat", + "goblet", + "godfather", + "goes", + "goggles", + "going", + "goldfish", + "gone", + "goodbye", + "gopher", + "gorilla", + "gossip", + "gotten", + "gourmet", + "governing", + "gown", + "greater", + "grunt", + "guarded", + "guest", + "guide", + "gulp", + "gumball", + "guru", + "gusts", + "gutter", + "guys", + "gymnast", + "gypsy", + "gyrate", + "habitat", + "hacksaw", + "haggled", + "hairy", + "hamburger", + "happens", + "hashing", + "hatchet", + "haunted", + "having", + "hawk", + "haystack", + "hazard", + "hectare", + "hedgehog", + "heels", + "hefty", + "height", + "hemlock", + "hence", + "heron", + "hesitate", + "hexagon", + "hickory", + "hiding", + "highway", + "hijack", + "hiker", + "hills", + "himself", + "hinder", + "hippo", + "hire", + "history", + "hitched", + "hive", + "hoax", + "hobby", + "hockey", + "hoisting", + "hold", + "honked", + "hookup", + "hope", + "hornet", + "hospital", + "hotel", + "hounded", + "hover", + "howls", + "hubcaps", + "huddle", + "huge", + "hull", + "humid", + "hunter", + "hurried", + "husband", + "huts", + "hybrid", + "hydrogen", + "hyper", + "iceberg", + "icing", + "icon", + "identity", + "idiom", + "idled", + "idols", + "igloo", + "ignore", + "iguana", + "illness", + "imagine", + "imbalance", + "imitate", + "impel", + "inactive", + "inbound", + "incur", + "industrial", + "inexact", + "inflamed", + "ingested", + "initiate", + "injury", + "inkling", + "inline", + "inmate", + "innocent", + "inorganic", + "input", + "inquest", + "inroads", + "insult", + "intended", + "inundate", + "invoke", + "inwardly", + "ionic", + "irate", + "iris", + "irony", + "irritate", + "island", + "isolated", + "issued", + "italics", + "itches", + "items", + "itinerary", + "itself", + "ivory", + "jabbed", + "jackets", + "jaded", + "jagged", + "jailed", + "jamming", + "january", + "jargon", + "jaunt", + "javelin", + "jaws", + "jazz", + "jeans", + "jeers", + "jellyfish", + "jeopardy", + "jerseys", + "jester", + "jetting", + "jewels", + "jigsaw", + "jingle", + "jittery", + "jive", + "jobs", + "jockey", + "jogger", + "joining", + "joking", + "jolted", + "jostle", + "journal", + "joyous", + "jubilee", + "judge", + "juggled", + "juicy", + "jukebox", + "july", + "jump", + "junk", + "jury", + "justice", + "juvenile", + "kangaroo", + "karate", + "keep", + "kennel", + "kept", + "kernels", + "kettle", + "keyboard", + "kickoff", + "kidneys", + "king", + "kiosk", + "kisses", + "kitchens", + "kiwi", + "knapsack", + "knee", + "knife", + "knowledge", + "knuckle", + "koala", + "laboratory", + "ladder", + "lagoon", + "lair", + "lakes", + "lamb", + "language", + "laptop", + "large", + "last", + "later", + "launching", + "lava", + "lawsuit", + "layout", + "lazy", + "lectures", + "ledge", + "leech", + "left", + "legion", + "leisure", + "lemon", + "lending", + "leopard", + "lesson", + "lettuce", + "lexicon", + "liar", + "library", + "licks", + "lids", + "lied", + "lifestyle", + "light", + "likewise", + "lilac", + "limits", + "linen", + "lion", + "lipstick", + "liquid", + "listen", + "lively", + "loaded", + "lobster", + "locker", + "lodge", + "lofty", + "logic", + "loincloth", + "long", + "looking", + "lopped", + "lordship", + "losing", + "lottery", + "loudly", + "love", + "lower", + "loyal", + "lucky", + "luggage", + "lukewarm", + "lullaby", + "lumber", + "lunar", + "lurk", + "lush", + "luxury", + "lymph", + "lynx", + "lyrics", + "macro", + "madness", + "magically", + "mailed", + "major", + "makeup", + "malady", + "mammal", + "maps", + "masterful", + "match", + "maul", + "maverick", + "maximum", + "mayor", + "maze", + "meant", + "mechanic", + "medicate", + "meeting", + "megabyte", + "melting", + "memoir", + "menu", + "merger", + "mesh", + "metro", + "mews", + "mice", + "midst", + "mighty", + "mime", + "mirror", + "misery", + "mittens", + "mixture", + "moat", + "mobile", + "mocked", + "mohawk", + "moisture", + "molten", + "moment", + "money", + "moon", + "mops", + "morsel", + "mostly", + "motherly", + "mouth", + "movement", + "mowing", + "much", + "muddy", + "muffin", + "mugged", + "mullet", + "mumble", + "mundane", + "muppet", + "mural", + "musical", + "muzzle", + "myriad", + "mystery", + "myth", + "nabbing", + "nagged", + "nail", + "names", + "nanny", + "napkin", + "narrate", + "nasty", + "natural", + "nautical", + "navy", + "nearby", + "necklace", + "needed", + "negative", + "neither", + "neon", + "nephew", + "nerves", + "nestle", + "network", + "neutral", + "never", + "newt", + "nexus", + "nibs", + "niche", + "niece", + "nifty", + "nightly", + "nimbly", + "nineteen", + "nirvana", + "nitrogen", + "nobody", + "nocturnal", + "nodes", + "noises", + "nomad", + "noodles", + "northern", + "nostril", + "noted", + "nouns", + "novelty", + "nowhere", + "nozzle", + "nuance", + "nucleus", + "nudged", + "nugget", + "nuisance", + "null", + "number", + "nuns", + "nurse", + "nutshell", + "nylon", + "oaks", + "oars", + "oasis", + "oatmeal", + "obedient", + "object", + "obliged", + "obnoxious", + "observant", + "obtains", + "obvious", + "occur", + "ocean", + "october", + "odds", + "odometer", + "offend", + "often", + "oilfield", + "ointment", + "okay", + "older", + "olive", + "olympics", + "omega", + "omission", + "omnibus", + "onboard", + "oncoming", + "oneself", + "ongoing", + "onion", + "online", + "onslaught", + "onto", + "onward", + "oozed", + "opacity", + "opened", + "opposite", + "optical", + "opus", + "orange", + "orbit", + "orchid", + "orders", + "organs", + "origin", + "ornament", + "orphans", + "oscar", + "ostrich", + "otherwise", + "otter", + "ouch", + "ought", + "ounce", + "ourselves", + "oust", + "outbreak", + "oval", + "oven", + "owed", + "owls", + "owner", + "oxidant", + "oxygen", + "oyster", + "ozone", + "pact", + "paddles", + "pager", + "pairing", + "palace", + "pamphlet", + "pancakes", + "paper", + "paradise", + "pastry", + "patio", + "pause", + "pavements", + "pawnshop", + "payment", + "peaches", + "pebbles", + "peculiar", + "pedantic", + "peeled", + "pegs", + "pelican", + "pencil", + "people", + "pepper", + "perfect", + "pests", + "petals", + "phase", + "pheasants", + "phone", + "phrases", + "physics", + "piano", + "picked", + "pierce", + "pigment", + "piloted", + "pimple", + "pinched", + "pioneer", + "pipeline", + "pirate", + "pistons", + "pitched", + "pivot", + "pixels", + "pizza", + "playful", + "pledge", + "pliers", + "plotting", + "plus", + "plywood", + "poaching", + "pockets", + "podcast", + "poetry", + "point", + "poker", + "polar", + "ponies", + "pool", + "popular", + "portents", + "possible", + "potato", + "pouch", + "poverty", + "powder", + "pram", + "present", + "pride", + "problems", + "pruned", + "prying", + "psychic", + "public", + "puck", + "puddle", + "puffin", + "pulp", + "pumpkins", + "punch", + "puppy", + "purged", + "push", + "putty", + "puzzled", + "pylons", + "pyramid", + "python", + "queen", + "quick", + "quote", + "rabbits", + "racetrack", + "radar", + "rafts", + "rage", + "railway", + "raking", + "rally", + "ramped", + "randomly", + "rapid", + "rarest", + "rash", + "rated", + "ravine", + "rays", + "razor", + "react", + "rebel", + "recipe", + "reduce", + "reef", + "refer", + "regular", + "reheat", + "reinvest", + "rejoices", + "rekindle", + "relic", + "remedy", + "renting", + "reorder", + "repent", + "request", + "reruns", + "rest", + "return", + "reunion", + "revamp", + "rewind", + "rhino", + "rhythm", + "ribbon", + "richly", + "ridges", + "rift", + "rigid", + "rims", + "ringing", + "riots", + "ripped", + "rising", + "ritual", + "river", + "roared", + "robot", + "rockets", + "rodent", + "rogue", + "roles", + "romance", + "roomy", + "roped", + "roster", + "rotate", + "rounded", + "rover", + "rowboat", + "royal", + "ruby", + "rudely", + "ruffled", + "rugged", + "ruined", + "ruling", + "rumble", + "runway", + "rural", + "rustled", + "ruthless", + "sabotage", + "sack", + "sadness", + "safety", + "saga", + "sailor", + "sake", + "salads", + "sample", + "sanity", + "sapling", + "sarcasm", + "sash", + "satin", + "saucepan", + "saved", + "sawmill", + "saxophone", + "sayings", + "scamper", + "scenic", + "school", + "science", + "scoop", + "scrub", + "scuba", + "seasons", + "second", + "sedan", + "seeded", + "segments", + "seismic", + "selfish", + "semifinal", + "sensible", + "september", + "sequence", + "serving", + "session", + "setup", + "seventh", + "sewage", + "shackles", + "shelter", + "shipped", + "shocking", + "shrugged", + "shuffled", + "shyness", + "siblings", + "sickness", + "sidekick", + "sieve", + "sifting", + "sighting", + "silk", + "simplest", + "sincerely", + "sipped", + "siren", + "situated", + "sixteen", + "sizes", + "skater", + "skew", + "skirting", + "skulls", + "skydive", + "slackens", + "sleepless", + "slid", + "slower", + "slug", + "smash", + "smelting", + "smidgen", + "smog", + "smuggled", + "snake", + "sneeze", + "sniff", + "snout", + "snug", + "soapy", + "sober", + "soccer", + "soda", + "software", + "soggy", + "soil", + "solved", + "somewhere", + "sonic", + "soothe", + "soprano", + "sorry", + "southern", + "sovereign", + "sowed", + "soya", + "space", + "speedy", + "sphere", + "spiders", + "splendid", + "spout", + "sprig", + "spud", + "spying", + "square", + "stacking", + "stellar", + "stick", + "stockpile", + "strained", + "stunning", + "stylishly", + "subtly", + "succeed", + "suddenly", + "suede", + "suffice", + "sugar", + "suitcase", + "sulking", + "summon", + "sunken", + "superior", + "surfer", + "sushi", + "suture", + "swagger", + "swept", + "swiftly", + "sword", + "swung", + "syllabus", + "symptoms", + "syndrome", + "syringe", + "system", + "taboo", + "tacit", + "tadpoles", + "tagged", + "tail", + "taken", + "talent", + "tamper", + "tanks", + "tapestry", + "tarnished", + "tasked", + "tattoo", + "taunts", + "tavern", + "tawny", + "taxi", + "teardrop", + "technical", + "tedious", + "teeming", + "tell", + "template", + "tender", + "tepid", + "tequila", + "terminal", + "testing", + "tether", + "textbook", + "thaw", + "theatrics", + "thirsty", + "thorn", + "threaten", + "thumbs", + "thwart", + "ticket", + "tidy", + "tiers", + "tiger", + "tilt", + "timber", + "tinted", + "tipsy", + "tirade", + "tissue", + "titans", + "toaster", + "tobacco", + "today", + "toenail", + "toffee", + "together", + "toilet", + "token", + "tolerant", + "tomorrow", + "tonic", + "toolbox", + "topic", + "torch", + "tossed", + "total", + "touchy", + "towel", + "toxic", + "toyed", + "trash", + "trendy", + "tribal", + "trolling", + "truth", + "trying", + "tsunami", + "tubes", + "tucks", + "tudor", + "tuesday", + "tufts", + "tugs", + "tuition", + "tulips", + "tumbling", + "tunnel", + "turnip", + "tusks", + "tutor", + "tuxedo", + "twang", + "tweezers", + "twice", + "twofold", + "tycoon", + "typist", + "tyrant", + "ugly", + "ulcers", + "ultimate", + "umbrella", + "umpire", + "unafraid", + "unbending", + "uncle", + "under", + "uneven", + "unfit", + "ungainly", + "unhappy", + "union", + "unjustly", + "unknown", + "unlikely", + "unmask", + "unnoticed", + "unopened", + "unplugs", + "unquoted", + "unrest", + "unsafe", + "until", + "unusual", + "unveil", + "unwind", + "unzip", + "upbeat", + "upcoming", + "update", + "upgrade", + "uphill", + "upkeep", + "upload", + "upon", + "upper", + "upright", + "upstairs", + "uptight", + "upwards", + "urban", + "urchins", + "urgent", + "usage", + "useful", + "usher", + "using", + "usual", + "utensils", + "utility", + "utmost", + "utopia", + "uttered", + "vacation", + "vague", + "vain", + "value", + "vampire", + "vane", + "vapidly", + "vary", + "vastness", + "vats", + "vaults", + "vector", + "veered", + "vegan", + "vehicle", + "vein", + "velvet", + "venomous", + "verification", + "vessel", + "veteran", + "vexed", + "vials", + "vibrate", + "victim", + "video", + "viewpoint", + "vigilant", + "viking", + "village", + "vinegar", + "violin", + "vipers", + "virtual", + "visited", + "vitals", + "vivid", + "vixen", + "vocal", + "vogue", + "voice", + "volcano", + "vortex", + "voted", + "voucher", + "vowels", + "voyage", + "vulture", + "wade", + "waffle", + "wagtail", + "waist", + "waking", + "wallets", + "wanted", + "warped", + "washing", + "water", + "waveform", + "waxing", + "wayside", + "weavers", + "website", + "wedge", + "weekday", + "weird", + "welders", + "went", + "wept", + "were", + "western", + "wetsuit", + "whale", + "when", + "whipped", + "whole", + "wickets", + "width", + "wield", + "wife", + "wiggle", + "wildly", + "winter", + "wipeout", + "wiring", + "wise", + "withdrawn", + "wives", + "wizard", + "wobbly", + "woes", + "woken", + "wolf", + "womanly", + "wonders", + "woozy", + "worry", + "wounded", + "woven", + "wrap", + "wrist", + "wrong", + "yacht", + "yahoo", + "yanks", + "yard", + "yawning", + "yearbook", + "yellow", + "yesterday", + "yeti", + "yields", + "yodel", + "yoga", + "younger", + "yoyo", + "zapped", + "zeal", + "zebra", + "zero", + "zesty", + "zigzags", + "zinger", + "zippers", + "zodiac", + "zombie", + "zones", + "zoom", +]; + +export default { + monero_words_english, + monero_words_english_prefix_len +} diff --git a/crypto/blockchains/xmr/ext/MoneroMnemonic.js b/crypto/blockchains/xmr/ext/MoneroMnemonic.js new file mode 100644 index 000000000..fe6747f6a --- /dev/null +++ b/crypto/blockchains/xmr/ext/MoneroMnemonic.js @@ -0,0 +1,43 @@ +/** + * @author Ksu + * @version 0.11 + * + * based on + * https://github.com/Coinomi/bip39-coinomi + */ +import MoneroDict from './MoneroDict' +import BlocksoftUtils from '../../../common/BlocksoftUtils' + +const crc32 = require('buffer-crc32') + +export default { + secret_spend_key_to_words(secretSpendKeyBufferOrHex, walletHash) { + const buff = typeof secretSpendKeyBufferOrHex === 'string' ? Buffer.from(secretSpendKeyBufferOrHex, 'hex') : secretSpendKeyBufferOrHex + var seed = [] + var forChecksum = '' + for (var i = 0; i < 32; i += 4) { + var w0 = 0 + for (var j = 3; j >= 0; j--) { + w0 = w0 * 256 + buff[i + j] + if (typeof buff[i + j] === 'undefined') { + throw new Error('XMR word for wallet ' + walletHash + ' need to be rechecked as buff is too low') + } + } + + var w1 = w0 % MoneroDict.monero_words_english.length + var w2 = ((w0 / MoneroDict.monero_words_english.length | 0) + w1) % MoneroDict.monero_words_english.length + var w3 = (((w0 / MoneroDict.monero_words_english.length | 0) / MoneroDict.monero_words_english.length | 0) + w2) % MoneroDict.monero_words_english.length + + seed.push(MoneroDict.monero_words_english[w1]) + seed.push(MoneroDict.monero_words_english[w2]) + seed.push(MoneroDict.monero_words_english[w3]) + forChecksum += MoneroDict.monero_words_english[w1].substring(0, MoneroDict.monero_words_english_prefix_len) + forChecksum += MoneroDict.monero_words_english[w2].substring(0, MoneroDict.monero_words_english_prefix_len) + forChecksum += MoneroDict.monero_words_english[w3].substring(0, MoneroDict.monero_words_english_prefix_len) + } + const crc32Res = crc32(forChecksum) + const crc32Decimal = BlocksoftUtils.hexToDecimal('0x' + crc32Res.toString('hex')) + seed.push(seed[crc32Decimal % 24]) + return seed.join(' ') + } +} diff --git a/crypto/blockchains/xmr/ext/MoneroUtils.js b/crypto/blockchains/xmr/ext/MoneroUtils.js new file mode 100644 index 000000000..717e7ed6b --- /dev/null +++ b/crypto/blockchains/xmr/ext/MoneroUtils.js @@ -0,0 +1,168 @@ +/** + * @author Ksu + * @version 0.11 + * + * based on + * https://github.com/mymonero/mymonero-core-js/blob/c04651731e238d23baec9682753acd967679a36e/src/index.cpp + */ +import { soliditySha3 } from 'web3-utils' + +const elliptic = require('elliptic') +const Ed25519 = elliptic.eddsa('ed25519') + + +/** Monero base58 is not like Bitcoin base58, bytes are converted in 8-byte blocks. + * https://docs.rs/base58-monero/0.2.0/base58_monero/ + */ +function base58_encode(data) { + var ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' + var BYTES_TO_LENGTHS = [0, 2, 3, 5, 6, 7, 9, 10, 11] + var ALPHABET_MAP = {} + var BASE = ALPHABET.length + + // pre-compute lookup table + for (var z = 0; z < ALPHABET.length; z++) { + var x = ALPHABET.charAt(z) + if (ALPHABET_MAP[x] !== undefined) throw new TypeError(x + ' is ambiguous') + ALPHABET_MAP[x] = z + } + + function encode_partial(data, pos) { + var len = 8 + if (pos + len > data.length) len = data.length - pos + var digits = [0] + for (var i = 0; i < len; ++i) { + for (var j = 0, carry = data[pos + i]; j < digits.length; ++j) { + carry += digits[j] << 8 + digits[j] = carry % BASE + carry = (carry / BASE) | 0 + } + + while (carry > 0) { + digits.push(carry % BASE) + carry = (carry / BASE) | 0 + } + } + + var res = '' + // deal with leading zeros + for (var k = digits.length; k < BYTES_TO_LENGTHS[len]; ++k) res += ALPHABET[0] + // convert digits to a string + for (var q = digits.length - 1; q >= 0; --q) res += ALPHABET[digits[q]] + return res + } + + var res = '' + for (var i = 0; i < data.length; i += 8) { + res += encode_partial(data, i) + } + return res +} + +export default { + /** + Takes a 32-byte integer and outputs the integer modulo q + Input: + s[0]+256*s[1]+...+256^63*s[63] = s + + Output: + s[0]+256*s[1]+...+256^31*s[31] = s mod l + where l = 2^252 + 27742317777372353535851937790883648493. + + Actually should be valid ed25519 scalar so make with umode and checked + */ + sc_reduce32(dataBufferOrHex) { + const buff = typeof dataBufferOrHex === 'string' ? Buffer.from(dataBufferOrHex,'hex') : dataBufferOrHex + const hex = elliptic.utils.intFromLE(buff).umod(Ed25519.curve.n).toString('hex') + return this.reverse(hex) + }, + + /** + Scalar add - is taking two big numbers, which are stored as binary in byte (char) arrays and returning the result of adding them together in another byte array, mod the order of the base point. + */ + sc_add(dataBufferOrHex, dataBufferOrHex2) { + const buff = typeof dataBufferOrHex === 'string' ? Buffer.from(dataBufferOrHex,'hex') : dataBufferOrHex + const buff2 = typeof dataBufferOrHex2 === 'string' ? Buffer.from(dataBufferOrHex2,'hex') : dataBufferOrHex2 + const hex = elliptic.utils.intFromLE(buff).add(elliptic.utils.intFromLE(buff2)).toString('hex') + return this.reverse(hex) + }, + + reverse(hex) { + let result = '' + const ic = hex.length + for (let i = ic - 1; i >= 0; i = i - 2) { + const tmp = i > 0 ? (hex[i - 1] + hex[i]) : ('0' + hex[i]) + result += tmp + } + return result + }, + + /** + Inputs data (for example, a point P on ed25519) and outputs Hs (P), which is + the Keccak1600 hash of the data. The function then converts the hashed data to a + 32-byte integer modulo q + + void hash_to_scalar(const void *data, size_t length, ec_scalar &res) { + cn_fast_hash(data, length, reinterpret_cast(res)); + sc_reduce32(&res); + } + */ + hash_to_scalar(dataBufferOrHex) { + return this.sc_reduce32(this.cn_fast_hash(dataBufferOrHex)) + }, + + /** + cn_fast_hash = keccak1600 = sha3 !!!! + parameters b = 1600 and c = 512 + Function keccak1600 is defined as a special case of function keccak with parameter mdlen equal to the size in bits of the type state_t. This type is an array of 25 uint64_t so the total size is 25*64=1600 + */ + cn_fast_hash(dataBufferOrHex) { + const str = typeof dataBufferOrHex === 'string' ? dataBufferOrHex : dataBufferOrHex.toString('hex') + const hash = soliditySha3('0x' + str) + return hash.substr(2) + }, + + /** + https://monerodocs.org/cryptography/asymmetric/public-key/ + https://github.com/indutny/elliptic + Actually this function multiplies base G by its input + + Inputs a secret key, checks it for some uniformity conditions, and outputs the corresponding public key, which is essentially just 8 times the base point times the + point. + */ + secret_key_to_public_key(secretSpendKeyBufferOrHex) { + const buff = typeof secretSpendKeyBufferOrHex === 'string' ? Buffer.from(secretSpendKeyBufferOrHex,'hex') : secretSpendKeyBufferOrHex + const publicSpendKey = Ed25519.curve.g.mul(Ed25519.decodeInt(buff.toString('hex'))) + return Buffer.from(Ed25519.encodePoint(publicSpendKey)) + /* + !!! NOPE + constkey = Ed25519.keyFromSecret(secretSpendKeyBuffer) + const publicSpendKey2 = key.getPublic() + console.log(' publicSpendKey2 ', publicSpendKey2, publicSpendKey2.toString('hex'), Buffer.from(publicSpendKey2), Buffer.from(publicSpendKey2).toString('hex')) + */ + }, + + /** + checked + */ + pub_keys_to_address(index, publicSpendKeyBufferOrHex, publicViewKeyHexBufferOrHex) { + const str = typeof publicSpendKeyBufferOrHex === 'string' ? publicSpendKeyBufferOrHex : publicSpendKeyBufferOrHex.toString('hex') + const str2 = typeof publicViewKeyHexBufferOrHex === 'string' ? publicViewKeyHexBufferOrHex : publicViewKeyHexBufferOrHex.toString('hex') + const prefix = (index > 0) ? '2A' : '12' + + let hex = prefix + str + str2 //this.normString(str) + this.normString(str2) + const hash = this.cn_fast_hash(hex) + hex += hash.substring(0, 8) + + hex = Buffer.from(hex, 'hex') + + return base58_encode(hex) + }, + + normString(str) { + while (str.length < 64) { + str += '0' + } + return str + } +} diff --git a/crypto/blockchains/xmr/ext/MoneroUtilsParser.js b/crypto/blockchains/xmr/ext/MoneroUtilsParser.js new file mode 100644 index 000000000..8cb93c1ad --- /dev/null +++ b/crypto/blockchains/xmr/ext/MoneroUtilsParser.js @@ -0,0 +1,200 @@ +/** + * @author Ksu + * @version 0.2 + * https://github.com/mymonero/mymonero-utils/blob/8ea7ff51f931d3c5e27e2ffd2eb8945cdec8e050/packages/mymonero-response-parser-utils/index.js + */ +import config from '../../../../app/config/config' + +import * as payment from '@mymonero/mymonero-paymentid-utils' +// import * as parser from '@mymonero/mymonero-response-parser-utils/ResponseParser' +import * as parser from './vendor/ResponseParser' +import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog' + +const MyMoneroCoreBridgeRN = require('react-native-mymonero-core/src/index') +const MY_MONERO = { core: false } + +export default { + + checkDestination(value) { + return payment.IsValidPaymentIDOrNoPaymentID(value) + }, + + async getCore() { + + if (MY_MONERO.core) { + return MY_MONERO.core + } + MY_MONERO.core = MyMoneroCoreBridgeRN + MY_MONERO.core.generate_key_image = async (txPublicKey, privateViewKey, publicSpendKey, privateSpendKey, outputIndex) => { + if (!txPublicKey || !privateViewKey || !publicSpendKey || !privateSpendKey) { + throw new Error('no keys 1') + } + if (typeof txPublicKey === 'undefined' || typeof privateViewKey === 'undefined' || typeof publicSpendKey === 'undefined' || typeof privateSpendKey === 'undefined') { + throw new Error('no keys 2') + } + if (txPublicKey === 'undefined' || privateViewKey === 'undefined' || publicSpendKey === 'undefined' || privateSpendKey === 'undefined') { + throw new Error('no keys 3') + } + if (typeof outputIndex === 'undefined') { + outputIndex = '' + } + + try { + let res = await MY_MONERO.core.Module.generateKeyImage(txPublicKey, privateViewKey, publicSpendKey, privateSpendKey, outputIndex + '') + if (typeof res !== 'undefined' && res) { + if (typeof res === 'string') { + try { + const newRes = JSON.parse(res) + res = newRes + } catch (e) { + + } + } + if (typeof res.retVal !== 'undefined') { + return res.retVal + } + } + return res + } catch (e) { + BlocksoftCryptoLog.log('MoneroUtilsParser.generate_key_image ' + e.message) + throw new Error('MoneroUtilsParser.generate_key_image ' + e.message) + } + } + MY_MONERO.core.createTransaction = async (options) => { + if (options.privateViewKey.length !== 64) { + throw Error('Invalid privateViewKey length') + } + if (options.publicSpendKey.length !== 64) { + throw Error('Invalid publicSpendKey length') + } + if (options.privateSpendKey.length !== 64) { + throw Error('Invalid privateSpendKey length') + } + if (typeof options.randomOutsCb !== 'function') { + throw Error('Invalid randomsOutCB not a function') + } + if (!Array.isArray(options.destinations)) { + throw Error('Invalid destinations') + } + options.destinations.forEach(function (destination) { + if (!destination.hasOwnProperty('to_address') || !destination.hasOwnProperty('send_amount')) { + throw Error('Invalid destinations missing values') + } + }) + if (options.shouldSweep) { + if (options.destinations.length !== 1) { + throw Error('Invalid number of destinations must be 1') + } + if (options.destinations[0].send_amount !== 0) { + throw Error('Invalid amount when sweeping amount must be 0') + } + } + + // check if destinations is set correctly + const args = + { + destinations: options.destinations, + is_sweeping: options.shouldSweep, + from_address_string: options.address, + sec_viewKey_string: options.privateViewKey, + sec_spendKey_string: options.privateSpendKey, + pub_spendKey_string: options.publicSpendKey, + priority: '' + options.priority, + nettype_string: options.nettype, + unspentOuts: options.unspentOuts + } + + let retString + try { + retString = await MY_MONERO.core.Module.prepareTx(JSON.stringify(args, null, '')) + } catch (e) { + throw Error(' MY_MONERO.core.Module.prepareTx error ' + e.message) + } + + const ret = JSON.parse(retString) + // check for any errors passed back from WebAssembly + if (ret.err_msg) { + BlocksoftCryptoLog.log('MoneroUtilsParser ret.err_msg error ' + ret.err_msg) + return false + } + + const _getRandomOuts = async (numberOfOuts, randomOutsCb) => { + const randomOuts = await randomOutsCb(numberOfOuts) + if (typeof randomOuts.amount_outs === 'undefined' || !Array.isArray(randomOuts.amount_outs)) { + throw Error('Invalid amount_outs in randomOutsCb response') + } + return randomOuts + } + + BlocksoftCryptoLog.log('MoneroUtilsParser ret?.amounts?.length ' + ret?.amounts?.length) + + // fetch random decoys + const randomOuts = await _getRandomOuts(ret?.amounts?.length || 0, options.randomOutsCb) + // send random decoys on and complete the tx creation + const retString2 = await MY_MONERO.core.Module.createAndSignTx(JSON.stringify(randomOuts)) + const rawTx = JSON.parse(retString2) + // check for any errors passed back from WebAssembly + if (rawTx.err_msg) { + throw Error(rawTx.err_msg) + } + // parse variables ruturned as strings + rawTx.mixin = parseInt(rawTx.mixin) + rawTx.isXMRAddressIntegrated = rawTx.isXMRAddressIntegrated === 'true' + + return rawTx + } + return MY_MONERO.core + }, + + async parseAddressInfo(address, data, privViewKey, pubSpendKey, privSpendKey) { + try { + await this.getCore() + let resData = false + await parser.Parsed_AddressInfo__keyImageManaged( + data, + address, + privViewKey, + pubSpendKey, + privSpendKey, + MY_MONERO.core, + (e, returnValuesByKey) => { + if (e) { + console.log('MoneroUtilsParser.parseAddressInfo error2', e) + } + resData = returnValuesByKey + } + ) + return resData + } catch (e) { + if (config.debug.cryptoErrors) { + console.log('MoneroUtilsParser.parseAddressInfo error', e) + } + } + }, + + async parseAddressTransactions(address, data, privViewKey, pubSpendKey, privSpendKey) { + try { + await this.getCore() + let resData = false + await parser.Parsed_AddressTransactions__keyImageManaged( + data, + address, + privViewKey, + pubSpendKey, + privSpendKey, + MY_MONERO.core, + (e, returnValuesByKey) => { + if (e) { + console.log('MoneroUtilsParser.parseAddressTransactions error', e) + } + resData = returnValuesByKey + } + ) + return resData + } catch (e) { + if (config.debug.cryptoErrors) { + console.log('MoneroUtilsParser.parseAddressTransactions error', e) + } + } + } +} diff --git a/crypto/blockchains/xmr/ext/MoneroUtilsParser.oldAndroid.js b/crypto/blockchains/xmr/ext/MoneroUtilsParser.oldAndroid.js new file mode 100644 index 000000000..a21bc1f78 --- /dev/null +++ b/crypto/blockchains/xmr/ext/MoneroUtilsParser.oldAndroid.js @@ -0,0 +1,28 @@ +/** + * @author Ksu + * @version 0.11 + * for old androids + */ + +export default { + + checkDestination(value) { + return false + }, + + async getCore() { + return false + }, + + async getCoreWasm() { + return false + }, + + async parseAddressInfo(address, data, privViewKey, pubSpendKey, privSpendKey) { + return false + }, + + async parseAddressTransactions(address, data, privViewKey, pubSpendKey, privSpendKey) { + return false + } +} diff --git a/crypto/blockchains/xmr/ext/vendor/KeyimageCache.js b/crypto/blockchains/xmr/ext/vendor/KeyimageCache.js new file mode 100644 index 000000000..76dcc76c8 --- /dev/null +++ b/crypto/blockchains/xmr/ext/vendor/KeyimageCache.js @@ -0,0 +1,73 @@ +'use strict' + +const Lazy_KeyImage = async function ( + mutable_keyImagesByCacheKey, // pass a mutable JS dictionary + tx_pub_key, + out_index, + public_address, + view_key__private, + spend_key__public, + spend_key__private, + coreBridge_instance // must pass this so this fn can remain synchronous +) { + var cache_index = tx_pub_key + ':' + public_address + ':' + out_index + const cached__key_image = mutable_keyImagesByCacheKey[cache_index] + if ( + typeof cached__key_image !== 'undefined' && + cached__key_image !== null + ) { + return cached__key_image + } + var key_image = await coreBridge_instance.generate_key_image( + tx_pub_key, + view_key__private, + spend_key__public, + spend_key__private, + out_index + ) + // cache: + mutable_keyImagesByCacheKey[cache_index] = key_image + // + return key_image +} +exports.Lazy_KeyImage = Lazy_KeyImage +// +// +// Managed caches - Can be used by apps which can't send a mutable_keyImagesByCacheKey +const __global_managed_keyImageCaches_by_walletId = {} +function _managedKeyImageCacheWalletIdForWalletWith (public_address) { + // NOTE: making the assumption that public_address is unique enough to identify a wallet for caching.... + // FIXME: with subaddresses, is that still the case? would we need to split them up by subaddr anyway? + if ( + public_address == '' || + !public_address || + typeof public_address === 'undefined' + ) { + throw 'managedKeyImageCacheIdentifierForWalletWith: Illegal public_address' + } + return '' + public_address +} + +const Lazy_KeyImageCacheForWalletWith = function (public_address) { + var cacheId = _managedKeyImageCacheWalletIdForWalletWith(public_address) + var cache = __global_managed_keyImageCaches_by_walletId[cacheId] + if (typeof cache === 'undefined' || !cache) { + cache = {} + __global_managed_keyImageCaches_by_walletId[cacheId] = cache + } + return cache +} +exports.Lazy_KeyImageCacheForWalletWith = Lazy_KeyImageCacheForWalletWith + +const DeleteManagedKeyImagesForWalletWith = function (public_address) { + // IMPORTANT: Ensure you call this method when you want to clear your wallet from + // memory or delete it, or else you could leak key images and public addresses. + const cacheId = _managedKeyImageCacheWalletIdForWalletWith(public_address) + delete __global_managed_keyImageCaches_by_walletId[cacheId] + // + const cache = __global_managed_keyImageCaches_by_walletId[cacheId] + if (typeof cache !== 'undefined') { + throw 'Key image cache still exists after deletion' + } +} +exports.DeleteManagedKeyImagesForWalletWith = DeleteManagedKeyImagesForWalletWith diff --git a/crypto/blockchains/xmr/ext/vendor/ResponseParser.js b/crypto/blockchains/xmr/ext/vendor/ResponseParser.js new file mode 100644 index 000000000..c083f280f --- /dev/null +++ b/crypto/blockchains/xmr/ext/vendor/ResponseParser.js @@ -0,0 +1,333 @@ +// Copyright (c) 2014-2019, MyMonero.com +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +'use strict' +// +const JSBigInt = require('@mymonero/mymonero-bigint').BigInteger +const monero_amount_format_utils = require('@mymonero/mymonero-money-format') +const monero_keyImage_cache_utils = require('./KeyimageCache') // require('@mymonero/mymonero-keyimage-cache') +// +async function Parsed_AddressInfo__sync ( + keyImage_cache, + data, + address, + view_key__private, + spend_key__public, + spend_key__private, + coreBridge_instance +) { + // -> returnValuesByKey + const total_received = new JSBigInt(data.total_received || 0) + const locked_balance = new JSBigInt(data.locked_funds || 0) + var total_sent = new JSBigInt(data.total_sent || 0) // will be modified in place + // + const account_scanned_tx_height = data.scanned_height || 0 + const account_scanned_block_height = data.scanned_block_height || 0 + const account_scan_start_height = data.start_height || 0 + const transaction_height = data.transaction_height || 0 + const blockchain_height = data.blockchain_height || 0 + const spent_outputs = data.spent_outputs || [] + // + for (let spent_output of spent_outputs) { + var key_image = await monero_keyImage_cache_utils.Lazy_KeyImage( + keyImage_cache, + spent_output.tx_pub_key, + spent_output.out_index, + address, + view_key__private, + spend_key__public, + spend_key__private, + coreBridge_instance + ) + if (spent_output.key_image !== key_image) { + // console.log('💬 Output used as mixin (' + spent_output.key_image + '/' + key_image + ')') + total_sent = new JSBigInt(total_sent).subtract(spent_output.amount) + } + } + // + const ratesBySymbol = data.rates || {} // jic it's not there + // + const returnValuesByKey = { + total_received_String: total_received + ? total_received.toString() + : null, + locked_balance_String: locked_balance + ? locked_balance.toString() + : null, + total_sent_String: total_sent ? total_sent.toString() : null, + // ^serialized JSBigInt + spent_outputs: spent_outputs, + account_scanned_tx_height: account_scanned_tx_height, + account_scanned_block_height: account_scanned_block_height, + account_scan_start_height: account_scan_start_height, + transaction_height: transaction_height, + blockchain_height: blockchain_height, + // + ratesBySymbol: ratesBySymbol + } + return returnValuesByKey +} +async function Parsed_AddressInfo__sync__keyImageManaged ( + data, + address, + view_key__private, + spend_key__public, + spend_key__private, + coreBridge_instance +) { + // -> returnValuesByKey + const keyImageCache = monero_keyImage_cache_utils.Lazy_KeyImageCacheForWalletWith( + address + ) + return Parsed_AddressInfo__sync( + keyImageCache, + data, + address, + view_key__private, + spend_key__public, + spend_key__private, + coreBridge_instance + ) +} +async function Parsed_AddressInfo ( + keyImage_cache, + data, + address, + view_key__private, + spend_key__public, + spend_key__private, + coreBridge_instance, + fn // (err?, returnValuesByKey) -> Void +) { + const returnValuesByKey = await Parsed_AddressInfo__sync( + keyImage_cache, + data, + address, + view_key__private, + spend_key__public, + spend_key__private, + coreBridge_instance + ) + fn(null, returnValuesByKey) +} +async function Parsed_AddressInfo__keyImageManaged ( + data, + address, + view_key__private, + spend_key__public, + spend_key__private, + coreBridge_instance, + fn +) { + // -> returnValuesByKey + await Parsed_AddressInfo( + monero_keyImage_cache_utils.Lazy_KeyImageCacheForWalletWith(address), + data, + address, + view_key__private, + spend_key__public, + spend_key__private, + coreBridge_instance, + fn + ) +} +exports.Parsed_AddressInfo = Parsed_AddressInfo +exports.Parsed_AddressInfo__keyImageManaged = Parsed_AddressInfo__keyImageManaged // in case you can't send a mutable key image cache dictionary +exports.Parsed_AddressInfo__sync__keyImageManaged = Parsed_AddressInfo__sync__keyImageManaged // in case you can't send a mutable key image cache dictionary +exports.Parsed_AddressInfo__sync = Parsed_AddressInfo__sync +// +async function Parsed_AddressTransactions ( + keyImage_cache, + data, + address, + view_key__private, + spend_key__public, + spend_key__private, + coreBridge_instance, + fn // (err?, returnValuesByKey) -> Void +) { + const returnValuesByKey = await Parsed_AddressTransactions__sync( + keyImage_cache, + data, + address, + view_key__private, + spend_key__public, + spend_key__private, + coreBridge_instance + ) + fn(null, returnValuesByKey) +} +async function Parsed_AddressTransactions__sync ( + keyImage_cache, + data, + address, + view_key__private, + spend_key__public, + spend_key__private, + coreBridge_instance +) { + const account_scanned_height = data.scanned_height || 0 + const account_scanned_block_height = data.scanned_block_height || 0 + const account_scan_start_height = data.start_height || 0 + const transaction_height = data.transaction_height || 0 + const blockchain_height = data.blockchain_height || 0 + // + const transactions = data.transactions || [] + // + // TODO: rewrite this with more clarity if possible + for (let i = 0; i < transactions.length; ++i) { + if ((transactions[i].spent_outputs || []).length > 0) { + for (var j = 0; j < transactions[i].spent_outputs.length; ++j) { + var key_image = await monero_keyImage_cache_utils.Lazy_KeyImage( + keyImage_cache, + transactions[i].spent_outputs[j].tx_pub_key, + transactions[i].spent_outputs[j].out_index, + address, + view_key__private, + spend_key__public, + spend_key__private, + coreBridge_instance + ) + if (transactions[i].spent_outputs[j].key_image !== key_image) { + // console.log('Output used as mixin, ignoring (' + transactions[i].spent_outputs[j].key_image + '/' + key_image + ')') + transactions[i].total_sent = new JSBigInt( + transactions[i].total_sent + ) + .subtract(transactions[i].spent_outputs[j].amount) + .toString() + transactions[i].spent_outputs.splice(j, 1) + j-- + } + } + } + if ( + new JSBigInt(transactions[i].total_received || 0) + .add(transactions[i].total_sent || 0) + .compare(0) <= 0 + ) { + transactions.splice(i, 1) + i-- + continue + } + transactions[i].amount = new JSBigInt( + transactions[i].total_received || 0 + ) + .subtract(transactions[i].total_sent || 0) + .toString() + transactions[i].approx_float_amount = parseFloat( + monero_amount_format_utils.formatMoney(transactions[i].amount) + ) + transactions[i].timestamp = transactions[i].timestamp + const record__payment_id = transactions[i].payment_id + if (typeof record__payment_id !== 'undefined' && record__payment_id) { + if (record__payment_id.length == 16) { + // short (encrypted) pid + if (transactions[i].approx_float_amount < 0) { + // outgoing + delete transactions[i]['payment_id'] // need to filter these out .. because the server can't filter out short (encrypted) pids on outgoing txs + } + } + } + } + transactions.sort(function (a, b) { + if (a.mempool == true) { + if (b.mempool != true) { + return -1 // a first + } + // both mempool - fall back to .id compare + } else if (b.mempool == true) { + return 1 // b first + } + return b.id - a.id + }) + // prepare transactions to be serialized + for (let transaction of transactions) { + transaction.amount = transaction.amount.toString() // JSBigInt -> String + if ( + typeof transaction.total_sent !== 'undefined' && + transaction.total_sent !== null + ) { + transaction.total_sent = transaction.total_sent.toString() + } + } + // on the other side, we convert transactions timestamp to Date obj + const returnValuesByKey = { + account_scanned_height: account_scanned_height, + account_scanned_block_height: account_scanned_block_height, + account_scan_start_height: account_scan_start_height, + transaction_height: transaction_height, + blockchain_height: blockchain_height, + serialized_transactions: transactions + } + return returnValuesByKey +} +async function Parsed_AddressTransactions__sync__keyImageManaged ( + data, + address, + view_key__private, + spend_key__public, + spend_key__private, + coreBridge_instance +) { + const keyImageCache = monero_keyImage_cache_utils.Lazy_KeyImageCacheForWalletWith( + address + ) + return Parsed_AddressTransactions__sync( + keyImageCache, + data, + address, + view_key__private, + spend_key__public, + spend_key__private, + coreBridge_instance + ) +} +async function Parsed_AddressTransactions__keyImageManaged ( + data, + address, + view_key__private, + spend_key__public, + spend_key__private, + coreBridge_instance, + fn +) { + await Parsed_AddressTransactions( + monero_keyImage_cache_utils.Lazy_KeyImageCacheForWalletWith(address), + data, + address, + view_key__private, + spend_key__public, + spend_key__private, + coreBridge_instance, + fn + ) +} +exports.Parsed_AddressTransactions = Parsed_AddressTransactions +exports.Parsed_AddressTransactions__keyImageManaged = Parsed_AddressTransactions__keyImageManaged +exports.Parsed_AddressTransactions__sync = Parsed_AddressTransactions__sync +exports.Parsed_AddressTransactions__sync__keyImageManaged = Parsed_AddressTransactions__sync__keyImageManaged diff --git a/crypto/blockchains/xmr/ext/vendor/biginteger.js b/crypto/blockchains/xmr/ext/vendor/biginteger.js new file mode 100644 index 000000000..f7d46d729 --- /dev/null +++ b/crypto/blockchains/xmr/ext/vendor/biginteger.js @@ -0,0 +1,1667 @@ +/* + JavaScript BigInteger library version 0.9.1 + http://silentmatt.com/biginteger/ + + Copyright (c) 2009 Matthew Crumley + Copyright (c) 2010,2011 by John Tobey + Licensed under the MIT license. + + Support for arbitrary internal representation base was added by + Vitaly Magerya. +*/ +/* + +This file has been modified by Paul Shapiro to bring in the function lowVal which was written by Lucas Jones + +*/ +/* + File: biginteger.js + + Exports: + + +*/ +(function(exports) { + "use strict"; + /* + Class: BigInteger + An arbitrarily-large integer. + + objects should be considered immutable. None of the "built-in" + methods modify *this* or their arguments. All properties should be + considered private. + + All the methods of instances can be called "statically". The + static versions are convenient if you don't already have a + object. + + As an example, these calls are equivalent. + + > BigInteger(4).multiply(5); // returns BigInteger(20); + > BigInteger.multiply(4, 5); // returns BigInteger(20); + + > var a = 42; + > var a = BigInteger.toJSValue("0b101010"); // Not completely useless... +*/ + + var CONSTRUCT = {}; // Unique token to call "private" version of constructor + + /* + Constructor: BigInteger() + Convert a value to a . + + Although is the constructor for objects, it is + best not to call it as a constructor. If *n* is a object, it is + simply returned as-is. Otherwise, is equivalent to + without a radix argument. + + > var n0 = BigInteger(); // Same as + > var n1 = BigInteger("123"); // Create a new with value 123 + > var n2 = BigInteger(123); // Create a new with value 123 + > var n3 = BigInteger(n2); // Return n2, unchanged + + The constructor form only takes an array and a sign. *n* must be an + array of numbers in little-endian order, where each digit is between 0 + and BigInteger.base. The second parameter sets the sign: -1 for + negative, +1 for positive, or 0 for zero. The array is *not copied and + may be modified*. If the array contains only zeros, the sign parameter + is ignored and is forced to zero. + + > new BigInteger([5], -1): create a new BigInteger with value -5 + + Parameters: + + n - Value to convert to a . + + Returns: + + A value. + + See Also: + + , +*/ + function BigInteger(n, s, token) { + if (token !== CONSTRUCT) { + if (n instanceof BigInteger) { + return n; + } else if (typeof n === "undefined") { + return ZERO; + } + return BigInteger.parse(n); + } + + n = n || []; // Provide the nullary constructor for subclasses. + while (n.length && !n[n.length - 1]) { + --n.length; + } + this._d = n; + this._s = n.length ? s || 1 : 0; + } + + BigInteger._construct = function(n, s) { + return new BigInteger(n, s, CONSTRUCT); + }; + + // Base-10 speedup hacks in parse, toString, exp10 and log functions + // require base to be a power of 10. 10^7 is the largest such power + // that won't cause a precision loss when digits are multiplied. + var BigInteger_base = 10000000; + var BigInteger_base_log10 = 7; + + BigInteger.base = BigInteger_base; + BigInteger.base_log10 = BigInteger_base_log10; + + var ZERO = new BigInteger([], 0, CONSTRUCT); + // Constant: ZERO + // 0. + BigInteger.ZERO = ZERO; + + var ONE = new BigInteger([1], 1, CONSTRUCT); + // Constant: ONE + // 1. + BigInteger.ONE = ONE; + + var M_ONE = new BigInteger(ONE._d, -1, CONSTRUCT); + // Constant: M_ONE + // -1. + BigInteger.M_ONE = M_ONE; + + // Constant: _0 + // Shortcut for . + BigInteger._0 = ZERO; + + // Constant: _1 + // Shortcut for . + BigInteger._1 = ONE; + + /* + Constant: small + Array of from 0 to 36. + + These are used internally for parsing, but useful when you need a "small" + . + + See Also: + + , , <_0>, <_1> +*/ + BigInteger.small = [ + ZERO, + ONE, + /* Assuming BigInteger_base > 36 */ + new BigInteger([2], 1, CONSTRUCT), + new BigInteger([3], 1, CONSTRUCT), + new BigInteger([4], 1, CONSTRUCT), + new BigInteger([5], 1, CONSTRUCT), + new BigInteger([6], 1, CONSTRUCT), + new BigInteger([7], 1, CONSTRUCT), + new BigInteger([8], 1, CONSTRUCT), + new BigInteger([9], 1, CONSTRUCT), + new BigInteger([10], 1, CONSTRUCT), + new BigInteger([11], 1, CONSTRUCT), + new BigInteger([12], 1, CONSTRUCT), + new BigInteger([13], 1, CONSTRUCT), + new BigInteger([14], 1, CONSTRUCT), + new BigInteger([15], 1, CONSTRUCT), + new BigInteger([16], 1, CONSTRUCT), + new BigInteger([17], 1, CONSTRUCT), + new BigInteger([18], 1, CONSTRUCT), + new BigInteger([19], 1, CONSTRUCT), + new BigInteger([20], 1, CONSTRUCT), + new BigInteger([21], 1, CONSTRUCT), + new BigInteger([22], 1, CONSTRUCT), + new BigInteger([23], 1, CONSTRUCT), + new BigInteger([24], 1, CONSTRUCT), + new BigInteger([25], 1, CONSTRUCT), + new BigInteger([26], 1, CONSTRUCT), + new BigInteger([27], 1, CONSTRUCT), + new BigInteger([28], 1, CONSTRUCT), + new BigInteger([29], 1, CONSTRUCT), + new BigInteger([30], 1, CONSTRUCT), + new BigInteger([31], 1, CONSTRUCT), + new BigInteger([32], 1, CONSTRUCT), + new BigInteger([33], 1, CONSTRUCT), + new BigInteger([34], 1, CONSTRUCT), + new BigInteger([35], 1, CONSTRUCT), + new BigInteger([36], 1, CONSTRUCT), + ]; + + // Used for parsing/radix conversion + BigInteger.digits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ".split(""); + + /* + Method: toString + Convert a to a string. + + When *base* is greater than 10, letters are upper case. + + Parameters: + + base - Optional base to represent the number in (default is base 10). + Must be between 2 and 36 inclusive, or an Error will be thrown. + + Returns: + + The string representation of the . +*/ + BigInteger.prototype.toString = function(base) { + base = +base || 10; + if (base < 2 || base > 36) { + throw new Error("illegal radix " + base + "."); + } + if (this._s === 0) { + return "0"; + } + if (base === 10) { + var str = this._s < 0 ? "-" : ""; + str += this._d[this._d.length - 1].toString(); + for (var i = this._d.length - 2; i >= 0; i--) { + var group = this._d[i].toString(); + while (group.length < BigInteger_base_log10) + group = "0" + group; + str += group; + } + return str; + } else { + var numerals = BigInteger.digits; + base = BigInteger.small[base]; + var sign = this._s; + + var n = this.abs(); + var digits = []; + var digit; + + while (n._s !== 0) { + var divmod = n.divRem(base); + n = divmod[0]; + digit = divmod[1]; + // TODO: This could be changed to unshift instead of reversing at the end. + // Benchmark both to compare speeds. + digits.push(numerals[digit.valueOf()]); + } + return (sign < 0 ? "-" : "") + digits.reverse().join(""); + } + }; + + // Verify strings for parsing + BigInteger.radixRegex = [ + /^$/, + /^$/, + /^[01]*$/, + /^[012]*$/, + /^[0-3]*$/, + /^[0-4]*$/, + /^[0-5]*$/, + /^[0-6]*$/, + /^[0-7]*$/, + /^[0-8]*$/, + /^[0-9]*$/, + /^[0-9aA]*$/, + /^[0-9abAB]*$/, + /^[0-9abcABC]*$/, + /^[0-9a-dA-D]*$/, + /^[0-9a-eA-E]*$/, + /^[0-9a-fA-F]*$/, + /^[0-9a-gA-G]*$/, + /^[0-9a-hA-H]*$/, + /^[0-9a-iA-I]*$/, + /^[0-9a-jA-J]*$/, + /^[0-9a-kA-K]*$/, + /^[0-9a-lA-L]*$/, + /^[0-9a-mA-M]*$/, + /^[0-9a-nA-N]*$/, + /^[0-9a-oA-O]*$/, + /^[0-9a-pA-P]*$/, + /^[0-9a-qA-Q]*$/, + /^[0-9a-rA-R]*$/, + /^[0-9a-sA-S]*$/, + /^[0-9a-tA-T]*$/, + /^[0-9a-uA-U]*$/, + /^[0-9a-vA-V]*$/, + /^[0-9a-wA-W]*$/, + /^[0-9a-xA-X]*$/, + /^[0-9a-yA-Y]*$/, + /^[0-9a-zA-Z]*$/, + ]; + + /* + Function: parse + Parse a string into a . + + *base* is optional but, if provided, must be from 2 to 36 inclusive. If + *base* is not provided, it will be guessed based on the leading characters + of *s* as follows: + + - "0x" or "0X": *base* = 16 + - "0c" or "0C": *base* = 8 + - "0b" or "0B": *base* = 2 + - else: *base* = 10 + + If no base is provided, or *base* is 10, the number can be in exponential + form. For example, these are all valid: + + > BigInteger.parse("1e9"); // Same as "1000000000" + > BigInteger.parse("1.234*10^3"); // Same as 1234 + > BigInteger.parse("56789 * 10 ** -2"); // Same as 567 + + If any characters fall outside the range defined by the radix, an exception + will be thrown. + + Parameters: + + s - The string to parse. + base - Optional radix (default is to guess based on *s*). + + Returns: + + a instance. +*/ + BigInteger.parse = function(s, base) { + // Expands a number in exponential form to decimal form. + // expandExponential("-13.441*10^5") === "1344100"; + // expandExponential("1.12300e-1") === "0.112300"; + // expandExponential(1000000000000000000000000000000) === "1000000000000000000000000000000"; + function expandExponential(str) { + str = str.replace(/\s*[*xX]\s*10\s*(\^|\*\*)\s*/, "e"); + + return str.replace( + /^([+\-])?(\d+)\.?(\d*)[eE]([+\-]?\d+)$/, + function(x, s, n, f, c) { + c = +c; + var l = c < 0; + var i = n.length + c; + x = (l ? n : f).length; + c = (c = Math.abs(c)) >= x ? c - x + l : 0; + var z = new Array(c + 1).join("0"); + var r = n + f; + return ( + (s || "") + + (l ? (r = z + r) : (r += z)).substr( + 0, + (i += l ? z.length : 0), + ) + + (i < r.length ? "." + r.substr(i) : "") + ); + }, + ); + } + + s = s.toString(); + if (typeof base === "undefined" || +base === 10) { + s = expandExponential(s); + } + + var prefixRE; + if (typeof base === "undefined") { + prefixRE = "0[xcb]"; + } else if (base == 16) { + prefixRE = "0x"; + } else if (base == 8) { + prefixRE = "0c"; + } else if (base == 2) { + prefixRE = "0b"; + } else { + prefixRE = ""; + } + var parts = new RegExp( + "^([+\\-]?)(" + prefixRE + ")?([0-9a-z]*)(?:\\.\\d*)?$", + "i", + ).exec(s); + if (parts) { + var sign = parts[1] || "+"; + var baseSection = parts[2] || ""; + var digits = parts[3] || ""; + + if (typeof base === "undefined") { + // Guess base + if (baseSection === "0x" || baseSection === "0X") { + // Hex + base = 16; + } else if (baseSection === "0c" || baseSection === "0C") { + // Octal + base = 8; + } else if (baseSection === "0b" || baseSection === "0B") { + // Binary + base = 2; + } else { + base = 10; + } + } else if (base < 2 || base > 36) { + throw new Error("Illegal radix " + base + "."); + } + + base = +base; + + // Check for digits outside the range + if (!BigInteger.radixRegex[base].test(digits)) { + throw new Error("Bad digit for radix " + base); + } + + // Strip leading zeros, and convert to array + digits = digits.replace(/^0+/, "").split(""); + if (digits.length === 0) { + return ZERO; + } + + // Get the sign (we know it's not zero) + sign = sign === "-" ? -1 : 1; + + // Optimize 10 + if (base == 10) { + var d = []; + while (digits.length >= BigInteger_base_log10) { + d.push( + parseInt( + digits + .splice( + digits.length - BigInteger.base_log10, + BigInteger.base_log10, + ) + .join(""), + 10, + ), + ); + } + d.push(parseInt(digits.join(""), 10)); + return new BigInteger(d, sign, CONSTRUCT); + } + + // Do the conversion + var d = ZERO; + base = BigInteger.small[base]; + var small = BigInteger.small; + for (var i = 0; i < digits.length; i++) { + d = d.multiply(base).add(small[parseInt(digits[i], 36)]); + } + return new BigInteger(d._d, sign, CONSTRUCT); + } else { + throw new Error("Invalid BigInteger format: " + s); + } + }; + + /* + Function: add + Add two . + + Parameters: + + n - The number to add to *this*. Will be converted to a . + + Returns: + + The numbers added together. + + See Also: + + , , , +*/ + BigInteger.prototype.add = function(n) { + if (this._s === 0) { + return BigInteger(n); + } + + n = BigInteger(n); + if (n._s === 0) { + return this; + } + if (this._s !== n._s) { + n = n.negate(); + return this.subtract(n); + } + + var a = this._d; + var b = n._d; + var al = a.length; + var bl = b.length; + var sum = new Array(Math.max(al, bl) + 1); + var size = Math.min(al, bl); + var carry = 0; + var digit; + + for (var i = 0; i < size; i++) { + digit = a[i] + b[i] + carry; + sum[i] = digit % BigInteger_base; + carry = (digit / BigInteger_base) | 0; + } + if (bl > al) { + a = b; + al = bl; + } + for (i = size; carry && i < al; i++) { + digit = a[i] + carry; + sum[i] = digit % BigInteger_base; + carry = (digit / BigInteger_base) | 0; + } + if (carry) { + sum[i] = carry; + } + + for (; i < al; i++) { + sum[i] = a[i]; + } + + return new BigInteger(sum, this._s, CONSTRUCT); + }; + + /* + Function: negate + Get the additive inverse of a . + + Returns: + + A with the same magnatude, but with the opposite sign. + + See Also: + + +*/ + BigInteger.prototype.negate = function() { + return new BigInteger(this._d, -this._s | 0, CONSTRUCT); + }; + + /* + Function: abs + Get the absolute value of a . + + Returns: + + A with the same magnatude, but always positive (or zero). + + See Also: + + +*/ + BigInteger.prototype.abs = function() { + return this._s < 0 ? this.negate() : this; + }; + + /* + Function: subtract + Subtract two . + + Parameters: + + n - The number to subtract from *this*. Will be converted to a . + + Returns: + + The *n* subtracted from *this*. + + See Also: + + , , , +*/ + BigInteger.prototype.subtract = function(n) { + if (this._s === 0) { + return BigInteger(n).negate(); + } + + n = BigInteger(n); + if (n._s === 0) { + return this; + } + if (this._s !== n._s) { + n = n.negate(); + return this.add(n); + } + + var m = this; + // negative - negative => -|a| - -|b| => -|a| + |b| => |b| - |a| + if (this._s < 0) { + m = new BigInteger(n._d, 1, CONSTRUCT); + n = new BigInteger(this._d, 1, CONSTRUCT); + } + + // Both are positive => a - b + var sign = m.compareAbs(n); + if (sign === 0) { + return ZERO; + } else if (sign < 0) { + // swap m and n + var t = n; + n = m; + m = t; + } + + // a > b + var a = m._d; + var b = n._d; + var al = a.length; + var bl = b.length; + var diff = new Array(al); // al >= bl since a > b + var borrow = 0; + var i; + var digit; + + for (i = 0; i < bl; i++) { + digit = a[i] - borrow - b[i]; + if (digit < 0) { + digit += BigInteger_base; + borrow = 1; + } else { + borrow = 0; + } + diff[i] = digit; + } + for (i = bl; i < al; i++) { + digit = a[i] - borrow; + if (digit < 0) { + digit += BigInteger_base; + } else { + diff[i++] = digit; + break; + } + diff[i] = digit; + } + for (; i < al; i++) { + diff[i] = a[i]; + } + + return new BigInteger(diff, sign, CONSTRUCT); + }; + + (function() { + function addOne(n, sign) { + var a = n._d; + var sum = a.slice(); + var carry = true; + var i = 0; + + while (true) { + var digit = (a[i] || 0) + 1; + sum[i] = digit % BigInteger_base; + if (digit <= BigInteger_base - 1) { + break; + } + ++i; + } + + return new BigInteger(sum, sign, CONSTRUCT); + } + + function subtractOne(n, sign) { + var a = n._d; + var sum = a.slice(); + var borrow = true; + var i = 0; + + while (true) { + var digit = (a[i] || 0) - 1; + if (digit < 0) { + sum[i] = digit + BigInteger_base; + } else { + sum[i] = digit; + break; + } + ++i; + } + + return new BigInteger(sum, sign, CONSTRUCT); + } + + /* + Function: next + Get the next (add one). + + Returns: + + *this* + 1. + + See Also: + + , + */ + BigInteger.prototype.next = function() { + switch (this._s) { + case 0: + return ONE; + case -1: + return subtractOne(this, -1); + // case 1: + default: + return addOne(this, 1); + } + }; + + /* + Function: prev + Get the previous (subtract one). + + Returns: + + *this* - 1. + + See Also: + + , + */ + BigInteger.prototype.prev = function() { + switch (this._s) { + case 0: + return M_ONE; + case -1: + return addOne(this, -1); + // case 1: + default: + return subtractOne(this, 1); + } + }; + })(); + + /* + Function: compareAbs + Compare the absolute value of two . + + Calling is faster than calling twice, then . + + Parameters: + + n - The number to compare to *this*. Will be converted to a . + + Returns: + + -1, 0, or +1 if *|this|* is less than, equal to, or greater than *|n|*. + + See Also: + + , +*/ + BigInteger.prototype.compareAbs = function(n) { + if (this === n) { + return 0; + } + + if (!(n instanceof BigInteger)) { + if (!isFinite(n)) { + return isNaN(n) ? n : -1; + } + n = BigInteger(n); + } + + if (this._s === 0) { + return n._s !== 0 ? -1 : 0; + } + if (n._s === 0) { + return 1; + } + + var l = this._d.length; + var nl = n._d.length; + if (l < nl) { + return -1; + } else if (l > nl) { + return 1; + } + + var a = this._d; + var b = n._d; + for (var i = l - 1; i >= 0; i--) { + if (a[i] !== b[i]) { + return a[i] < b[i] ? -1 : 1; + } + } + + return 0; + }; + + /* + Function: compare + Compare two . + + Parameters: + + n - The number to compare to *this*. Will be converted to a . + + Returns: + + -1, 0, or +1 if *this* is less than, equal to, or greater than *n*. + + See Also: + + , , , +*/ + BigInteger.prototype.compare = function(n) { + if (this === n) { + return 0; + } + + n = BigInteger(n); + + if (this._s === 0) { + return -n._s; + } + + if (this._s === n._s) { + // both positive or both negative + var cmp = this.compareAbs(n); + return cmp * this._s; + } else { + return this._s; + } + }; + + /* + Function: isUnit + Return true iff *this* is either 1 or -1. + + Returns: + + true if *this* compares equal to or . + + See Also: + + , , , , , + , +*/ + BigInteger.prototype.isUnit = function() { + return ( + this === ONE || + this === M_ONE || + (this._d.length === 1 && this._d[0] === 1) + ); + }; + + /* + Function: multiply + Multiply two . + + Parameters: + + n - The number to multiply *this* by. Will be converted to a + . + + Returns: + + The numbers multiplied together. + + See Also: + + , , , +*/ + BigInteger.prototype.multiply = function(n) { + // TODO: Consider adding Karatsuba multiplication for large numbers + if (this._s === 0) { + return ZERO; + } + + n = BigInteger(n); + if (n._s === 0) { + return ZERO; + } + if (this.isUnit()) { + if (this._s < 0) { + return n.negate(); + } + return n; + } + if (n.isUnit()) { + if (n._s < 0) { + return this.negate(); + } + return this; + } + if (this === n) { + return this.square(); + } + + var r = this._d.length >= n._d.length; + var a = (r ? this : n)._d; // a will be longer than b + var b = (r ? n : this)._d; + var al = a.length; + var bl = b.length; + + var pl = al + bl; + var partial = new Array(pl); + var i; + for (i = 0; i < pl; i++) { + partial[i] = 0; + } + + for (i = 0; i < bl; i++) { + var carry = 0; + var bi = b[i]; + var jlimit = al + i; + var digit; + for (var j = i; j < jlimit; j++) { + digit = partial[j] + bi * a[j - i] + carry; + carry = (digit / BigInteger_base) | 0; + partial[j] = digit % BigInteger_base | 0; + } + if (carry) { + digit = partial[j] + carry; + carry = (digit / BigInteger_base) | 0; + partial[j] = digit % BigInteger_base; + } + } + return new BigInteger(partial, this._s * n._s, CONSTRUCT); + }; + + // Multiply a BigInteger by a single-digit native number + // Assumes that this and n are >= 0 + // This is not really intended to be used outside the library itself + BigInteger.prototype.multiplySingleDigit = function(n) { + if (n === 0 || this._s === 0) { + return ZERO; + } + if (n === 1) { + return this; + } + + var digit; + if (this._d.length === 1) { + digit = this._d[0] * n; + if (digit >= BigInteger_base) { + return new BigInteger( + [ + digit % BigInteger_base | 0, + (digit / BigInteger_base) | 0, + ], + 1, + CONSTRUCT, + ); + } + return new BigInteger([digit], 1, CONSTRUCT); + } + + if (n === 2) { + return this.add(this); + } + if (this.isUnit()) { + return new BigInteger([n], 1, CONSTRUCT); + } + + var a = this._d; + var al = a.length; + + var pl = al + 1; + var partial = new Array(pl); + for (var i = 0; i < pl; i++) { + partial[i] = 0; + } + + var carry = 0; + for (var j = 0; j < al; j++) { + digit = n * a[j] + carry; + carry = (digit / BigInteger_base) | 0; + partial[j] = digit % BigInteger_base | 0; + } + if (carry) { + partial[j] = carry; + } + + return new BigInteger(partial, 1, CONSTRUCT); + }; + + /* + Function: square + Multiply a by itself. + + This is slightly faster than regular multiplication, since it removes the + duplicated multiplcations. + + Returns: + + > this.multiply(this) + + See Also: + +*/ + BigInteger.prototype.square = function() { + // Normally, squaring a 10-digit number would take 100 multiplications. + // Of these 10 are unique diagonals, of the remaining 90 (100-10), 45 are repeated. + // This procedure saves (N*(N-1))/2 multiplications, (e.g., 45 of 100 multiplies). + // Based on code by Gary Darby, Intellitech Systems Inc., www.DelphiForFun.org + + if (this._s === 0) { + return ZERO; + } + if (this.isUnit()) { + return ONE; + } + + var digits = this._d; + var length = digits.length; + var imult1 = new Array(length + length + 1); + var product, carry, k; + var i; + + // Calculate diagonal + for (i = 0; i < length; i++) { + k = i * 2; + product = digits[i] * digits[i]; + carry = (product / BigInteger_base) | 0; + imult1[k] = product % BigInteger_base; + imult1[k + 1] = carry; + } + + // Calculate repeating part + for (i = 0; i < length; i++) { + carry = 0; + k = i * 2 + 1; + for (var j = i + 1; j < length; j++, k++) { + product = digits[j] * digits[i] * 2 + imult1[k] + carry; + carry = (product / BigInteger_base) | 0; + imult1[k] = product % BigInteger_base; + } + k = length + i; + var digit = carry + imult1[k]; + carry = (digit / BigInteger_base) | 0; + imult1[k] = digit % BigInteger_base; + imult1[k + 1] += carry; + } + + return new BigInteger(imult1, 1, CONSTRUCT); + }; + + /* + Function: quotient + Divide two and truncate towards zero. + + throws an exception if *n* is zero. + + Parameters: + + n - The number to divide *this* by. Will be converted to a . + + Returns: + + The *this* / *n*, truncated to an integer. + + See Also: + + , , , , +*/ + BigInteger.prototype.quotient = function(n) { + return this.divRem(n)[0]; + }; + + /* + Function: divide + Deprecated synonym for . +*/ + BigInteger.prototype.divide = BigInteger.prototype.quotient; + + /* + Function: remainder + Calculate the remainder of two . + + throws an exception if *n* is zero. + + Parameters: + + n - The remainder after *this* is divided *this* by *n*. Will be + converted to a . + + Returns: + + *this* % *n*. + + See Also: + + , +*/ + BigInteger.prototype.remainder = function(n) { + return this.divRem(n)[1]; + }; + + /* + Function: divRem + Calculate the integer quotient and remainder of two . + + throws an exception if *n* is zero. + + Parameters: + + n - The number to divide *this* by. Will be converted to a . + + Returns: + + A two-element array containing the quotient and the remainder. + + > a.divRem(b) + + is exactly equivalent to + + > [a.quotient(b), a.remainder(b)] + + except it is faster, because they are calculated at the same time. + + See Also: + + , +*/ + BigInteger.prototype.divRem = function(n) { + n = BigInteger(n); + if (n._s === 0) { + throw new Error("Divide by zero"); + } + if (this._s === 0) { + return [ZERO, ZERO]; + } + if (n._d.length === 1) { + return this.divRemSmall(n._s * n._d[0]); + } + + // Test for easy cases -- |n1| <= |n2| + switch (this.compareAbs(n)) { + case 0: // n1 == n2 + return [this._s === n._s ? ONE : M_ONE, ZERO]; + case -1: // |n1| < |n2| + return [ZERO, this]; + } + + var sign = this._s * n._s; + var a = n.abs(); + var b_digits = this._d; + var b_index = b_digits.length; + var digits = n._d.length; + var quot = []; + var guess; + + var part = new BigInteger([], 0, CONSTRUCT); + + while (b_index) { + part._d.unshift(b_digits[--b_index]); + part = new BigInteger(part._d, 1, CONSTRUCT); + + if (part.compareAbs(n) < 0) { + quot.push(0); + continue; + } + if (part._s === 0) { + guess = 0; + } else { + var xlen = part._d.length, + ylen = a._d.length; + var highx = + part._d[xlen - 1] * BigInteger_base + part._d[xlen - 2]; + var highy = a._d[ylen - 1] * BigInteger_base + a._d[ylen - 2]; + if (part._d.length > a._d.length) { + // The length of part._d can either match a._d length, + // or exceed it by one. + highx = (highx + 1) * BigInteger_base; + } + guess = Math.ceil(highx / highy); + } + do { + var check = a.multiplySingleDigit(guess); + if (check.compareAbs(part) <= 0) { + break; + } + guess--; + } while (guess); + + quot.push(guess); + if (!guess) { + continue; + } + var diff = part.subtract(check); + part._d = diff._d.slice(); + } + + return [ + new BigInteger(quot.reverse(), sign, CONSTRUCT), + new BigInteger(part._d, this._s, CONSTRUCT), + ]; + }; + + // Throws an exception if n is outside of (-BigInteger.base, -1] or + // [1, BigInteger.base). It's not necessary to call this, since the + // other division functions will call it if they are able to. + BigInteger.prototype.divRemSmall = function(n) { + var r; + n = +n; + if (n === 0) { + throw new Error("Divide by zero"); + } + + var n_s = n < 0 ? -1 : 1; + var sign = this._s * n_s; + n = Math.abs(n); + + if (n < 1 || n >= BigInteger_base) { + throw new Error("Argument out of range"); + } + + if (this._s === 0) { + return [ZERO, ZERO]; + } + + if (n === 1 || n === -1) { + return [ + sign === 1 + ? this.abs() + : new BigInteger(this._d, sign, CONSTRUCT), + ZERO, + ]; + } + + // 2 <= n < BigInteger_base + + // divide a single digit by a single digit + if (this._d.length === 1) { + var q = new BigInteger([(this._d[0] / n) | 0], 1, CONSTRUCT); + r = new BigInteger([this._d[0] % n | 0], 1, CONSTRUCT); + if (sign < 0) { + q = q.negate(); + } + if (this._s < 0) { + r = r.negate(); + } + return [q, r]; + } + + var digits = this._d.slice(); + var quot = new Array(digits.length); + var part = 0; + var diff = 0; + var i = 0; + var guess; + + while (digits.length) { + part = part * BigInteger_base + digits[digits.length - 1]; + if (part < n) { + quot[i++] = 0; + digits.pop(); + diff = BigInteger_base * diff + part; + continue; + } + if (part === 0) { + guess = 0; + } else { + guess = (part / n) | 0; + } + + var check = n * guess; + diff = part - check; + quot[i++] = guess; + if (!guess) { + digits.pop(); + continue; + } + + digits.pop(); + part = diff; + } + + r = new BigInteger([diff], 1, CONSTRUCT); + if (this._s < 0) { + r = r.negate(); + } + return [new BigInteger(quot.reverse(), sign, CONSTRUCT), r]; + }; + + /* + Function: isEven + Return true iff *this* is divisible by two. + + Note that is even. + + Returns: + + true if *this* is even, false otherwise. + + See Also: + + +*/ + BigInteger.prototype.isEven = function() { + var digits = this._d; + return this._s === 0 || digits.length === 0 || digits[0] % 2 === 0; + }; + + /* + Function: isOdd + Return true iff *this* is not divisible by two. + + Returns: + + true if *this* is odd, false otherwise. + + See Also: + + +*/ + BigInteger.prototype.isOdd = function() { + return !this.isEven(); + }; + + /* + Function: sign + Get the sign of a . + + Returns: + + * -1 if *this* < 0 + * 0 if *this* == 0 + * +1 if *this* > 0 + + See Also: + + , , , , +*/ + BigInteger.prototype.sign = function() { + return this._s; + }; + + /* + Function: isPositive + Return true iff *this* > 0. + + Returns: + + true if *this*.compare() == 1. + + See Also: + + , , , , , +*/ + BigInteger.prototype.isPositive = function() { + return this._s > 0; + }; + + /* + Function: isNegative + Return true iff *this* < 0. + + Returns: + + true if *this*.compare() == -1. + + See Also: + + , , , , , +*/ + BigInteger.prototype.isNegative = function() { + return this._s < 0; + }; + + /* + Function: isZero + Return true iff *this* == 0. + + Returns: + + true if *this*.compare() == 0. + + See Also: + + , , , , +*/ + BigInteger.prototype.isZero = function() { + return this._s === 0; + }; + + /* + Function: exp10 + Multiply a by a power of 10. + + This is equivalent to, but faster than + + > if (n >= 0) { + > return this.multiply(BigInteger("1e" + n)); + > } + > else { // n <= 0 + > return this.quotient(BigInteger("1e" + -n)); + > } + + Parameters: + + n - The power of 10 to multiply *this* by. *n* is converted to a + javascipt number and must be no greater than + (0x7FFFFFFF), or an exception will be thrown. + + Returns: + + *this* * (10 ** *n*), truncated to an integer if necessary. + + See Also: + + , +*/ + BigInteger.prototype.exp10 = function(n) { + n = +n; + if (n === 0) { + return this; + } + if (Math.abs(n) > Number(MAX_EXP)) { + throw new Error("exponent too large in BigInteger.exp10"); + } + // Optimization for this == 0. This also keeps us from having to trim zeros in the positive n case + if (this._s === 0) { + return ZERO; + } + if (n > 0) { + var k = new BigInteger(this._d.slice(), this._s, CONSTRUCT); + + for (; n >= BigInteger_base_log10; n -= BigInteger_base_log10) { + k._d.unshift(0); + } + if (n == 0) return k; + k._s = 1; + k = k.multiplySingleDigit(Math.pow(10, n)); + return this._s < 0 ? k.negate() : k; + } else if (-n >= this._d.length * BigInteger_base_log10) { + return ZERO; + } else { + var k = new BigInteger(this._d.slice(), this._s, CONSTRUCT); + + for ( + n = -n; + n >= BigInteger_base_log10; + n -= BigInteger_base_log10 + ) { + k._d.shift(); + } + return n == 0 ? k : k.divRemSmall(Math.pow(10, n))[0]; + } + }; + + /* + Function: pow + Raise a to a power. + + In this implementation, 0**0 is 1. + + Parameters: + + n - The exponent to raise *this* by. *n* must be no greater than + (0x7FFFFFFF), or an exception will be thrown. + + Returns: + + *this* raised to the *nth* power. + + See Also: + + +*/ + BigInteger.prototype.pow = function(n) { + if (this.isUnit()) { + if (this._s > 0) { + return this; + } else { + return BigInteger(n).isOdd() ? this : this.negate(); + } + } + + n = BigInteger(n); + if (n._s === 0) { + return ONE; + } else if (n._s < 0) { + if (this._s === 0) { + throw new Error("Divide by zero"); + } else { + return ZERO; + } + } + if (this._s === 0) { + return ZERO; + } + if (n.isUnit()) { + return this; + } + + if (n.compareAbs(MAX_EXP) > 0) { + throw new Error("exponent too large in BigInteger.pow"); + } + var x = this; + var aux = ONE; + var two = BigInteger.small[2]; + + while (n.isPositive()) { + if (n.isOdd()) { + aux = aux.multiply(x); + if (n.isUnit()) { + return aux; + } + } + x = x.square(); + n = n.quotient(two); + } + + return aux; + }; + + /* + Function: modPow + Raise a to a power (mod m). + + Because it is reduced by a modulus, is not limited by + like . + + Parameters: + + exponent - The exponent to raise *this* by. Must be positive. + modulus - The modulus. + + Returns: + + *this* ^ *exponent* (mod *modulus*). + + See Also: + + , +*/ + BigInteger.prototype.modPow = function(exponent, modulus) { + var result = ONE; + var base = this; + + while (exponent.isPositive()) { + if (exponent.isOdd()) { + result = result.multiply(base).remainder(modulus); + } + + exponent = exponent.quotient(BigInteger.small[2]); + if (exponent.isPositive()) { + base = base.square().remainder(modulus); + } + } + + return result; + }; + + /* + Function: log + Get the natural logarithm of a as a native JavaScript number. + + This is equivalent to + + > Math.log(this.toJSValue()) + + but handles values outside of the native number range. + + Returns: + + log( *this* ) + + See Also: + + +*/ + BigInteger.prototype.log = function() { + switch (this._s) { + case 0: + return -Infinity; + case -1: + return NaN; + default: // Fall through. + } + + var l = this._d.length; + + if (l * BigInteger_base_log10 < 30) { + return Math.log(this.valueOf()); + } + + var N = Math.ceil(30 / BigInteger_base_log10); + var firstNdigits = this._d.slice(l - N); + return ( + Math.log(new BigInteger(firstNdigits, 1, CONSTRUCT).valueOf()) + + (l - N) * Math.log(BigInteger_base) + ); + }; + + /* + Function: valueOf + Convert a to a native JavaScript integer. + + This is called automatically by JavaScipt to convert a to a + native value. + + Returns: + + > parseInt(this.toString(), 10) + + See Also: + + , +*/ + BigInteger.prototype.valueOf = function() { + return parseInt(this.toString(), 10); + }; + + /* + Function: toJSValue + Convert a to a native JavaScript integer. + + This is the same as valueOf, but more explicitly named. + + Returns: + + > parseInt(this.toString(), 10) + + See Also: + + , +*/ + BigInteger.prototype.toJSValue = function() { + return parseInt(this.toString(), 10); + }; + + /* + Function: lowVal + Author: Lucas Jones +*/ + BigInteger.prototype.lowVal = function() { + return this._d[0] || 0; + }; + + var MAX_EXP = BigInteger(0x7fffffff); + // Constant: MAX_EXP + // The largest exponent allowed in and (0x7FFFFFFF or 2147483647). + BigInteger.MAX_EXP = MAX_EXP; + + (function() { + function makeUnary(fn) { + return function(a) { + return fn.call(BigInteger(a)); + }; + } + + function makeBinary(fn) { + return function(a, b) { + return fn.call(BigInteger(a), BigInteger(b)); + }; + } + + function makeTrinary(fn) { + return function(a, b, c) { + return fn.call(BigInteger(a), BigInteger(b), BigInteger(c)); + }; + } + + (function() { + var i, fn; + var unary = "toJSValue,isEven,isOdd,sign,isZero,isNegative,abs,isUnit,square,negate,isPositive,toString,next,prev,log".split( + ",", + ); + var binary = "compare,remainder,divRem,subtract,add,quotient,divide,multiply,pow,compareAbs".split( + ",", + ); + var trinary = ["modPow"]; + + for (i = 0; i < unary.length; i++) { + fn = unary[i]; + BigInteger[fn] = makeUnary(BigInteger.prototype[fn]); + } + + for (i = 0; i < binary.length; i++) { + fn = binary[i]; + BigInteger[fn] = makeBinary(BigInteger.prototype[fn]); + } + + for (i = 0; i < trinary.length; i++) { + fn = trinary[i]; + BigInteger[fn] = makeTrinary(BigInteger.prototype[fn]); + } + + BigInteger.exp10 = function(x, n) { + return BigInteger(x).exp10(n); + }; + })(); + })(); + + exports.BigInteger = BigInteger; +})(typeof exports !== "undefined" ? exports : this); diff --git a/crypto/blockchains/xmr/providers/XmrSendProvider.js b/crypto/blockchains/xmr/providers/XmrSendProvider.js new file mode 100644 index 000000000..dad5f484a --- /dev/null +++ b/crypto/blockchains/xmr/providers/XmrSendProvider.js @@ -0,0 +1,86 @@ +/** + * @version 0.11 + */ +import settingsActions from '../../../../app/appstores/Stores/Settings/SettingsActions' +import BlocksoftAxios from '../../../common/BlocksoftAxios' +import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog' + +export default class XmrSendProvider { + + constructor(settings) { + this._settings = settings + this._link = false + + } + + async _init() { + if (this._link) return false + this._serverUrl = await settingsActions.getSetting('xmrServerSend') + if (!this._serverUrl || this._serverUrl === 'false') { + this._serverUrl = 'api.mymonero.com:8443' + } + + let link = this._serverUrl.trim() + if (link.substr(0, 4).toLowerCase() !== 'http') { + if (link.indexOf('mymonero.com') !== -1) { + link = 'https://' + this._serverUrl + } else { + link = 'http://' + this._serverUrl + } + } + if (link[link.length - 1] !== '/') { + link = link + '/' + } + + this._link = link + } + + async send(params) { + await this._init() + // curl http://127.0.0.1:18081/send_raw_transaction -d '{"tx_as_hex":"de6a3...", "do_not_relay":false}' -H 'Content-Type: application/json' + // https://web.getmonero.org/resources/developer-guides/daemon-rpc.html#send_raw_transaction + // const resNode = await BlocksoftAxios.post('http://node.moneroworld.com:18089/send_raw_transaction', {tx_as_hex : params.tx, do_not_relay : false}) + // return resNode.data + + if (this._link.indexOf('mymonero.com') !== -1) { + try { + const res = await BlocksoftAxios.post(this._link + 'submit_raw_tx', { + address: params.address, + view_key: params.privViewKey, + tx: params.tx + }) + BlocksoftCryptoLog.log('XmrSendProvider mymonero.com node ' + this._link, res.data) + return res.data + } catch (e) { + if (e.message.indexOf('double') !== -1) { + throw new Error('SERVER_RESPONSE_DOUBLE_SPEND') + } else { + throw e + } + } + } else { + const resNode = await BlocksoftAxios.post(this._link + 'send_raw_transaction', { + tx_as_hex : params.tx, + do_not_relay : false + }) + BlocksoftCryptoLog.log('XmrSendProvider custom node ' + this._link, resNode.data) + if (typeof resNode.data.double_spend === 'undefined') { + throw new Error('SERVER_RESPONSE_BAD_SEND_NODE') + } + if (resNode.data.double_spend) { + throw new Error('SERVER_RESPONSE_DOUBLE_SPEND') + } + if (resNode.data.fee_too_low) { + throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_FEE') + } + if (resNode.data.overspend) { + throw new Error('SERVER_RESPONSE_TOO_BIG_FEE_FOR_TRANSACTION') + } + if (resNode.data.status === 'Failed') { + throw new Error(JSON.stringify(resNode.data)) + } + return resNode.data + } + } + +} diff --git a/crypto/blockchains/xmr/providers/XmrUnspentsProvider.js b/crypto/blockchains/xmr/providers/XmrUnspentsProvider.js new file mode 100644 index 000000000..fd09c7820 --- /dev/null +++ b/crypto/blockchains/xmr/providers/XmrUnspentsProvider.js @@ -0,0 +1,109 @@ +/** + * @version 0.11 + */ +import settingsActions from '../../../../app/appstores/Stores/Settings/SettingsActions' +import BlocksoftAxios from '../../../common/BlocksoftAxios' +import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog' + +export default class XmrUnspentsProvider { + + constructor(settings) { + this._settings = settings + this._link = false + this._cache = {} + } + + init() { + if (this._link) return false + this._serverUrl = settingsActions.getSettingStatic('xmrServer') + if (!this._serverUrl || this._serverUrl === 'false') { + this._serverUrl = 'api.mymonero.com:8443' + } + + let link = this._serverUrl.trim() + if (link.substr(0, 4).toLowerCase() !== 'http') { + link = 'https://' + this._serverUrl + } + if (link[link.length - 1] !== '/') { + link = link + '/' + } + + this._link = link + this._cache = {} + } + + async _getUnspents(params, fn) { + try { + const key = JSON.stringify(params) + let res = {} + if (typeof this._cache[key] === 'undefined') { + BlocksoftCryptoLog.log('XmrUnspentsProvider Xmr._getUnspents', key) + /* + const linkParams = { + address: params.address, + view_key: params.privViewKey, + amount: params.amount.toString(), + mixin: '10', + use_dust: true, + dust_threshold: '2000000000' + } + */ + res = await BlocksoftAxios.post(this._link + 'get_unspent_outs', params) + BlocksoftCryptoLog.log('XmrUnspentsProvider Xmr._getUnspents res ' + JSON.stringify(res.data).substr(0, 200)) + this._cache[key] = res.data + } else { + res = {data : this._cache[key]} + } + if (typeof fn === 'undefined' || !fn) { + return res.data + } else { + fn(null, res.data) + } + } catch (e) { + e.message += ' while Xmr._getUnspents' + fn(e, null) + if (typeof fn === 'undefined' || !fn) { + throw e + } else { + fn(e, null) + } + } + } + + async _getRandomOutputs(params, fn) { + try { + BlocksoftCryptoLog.log('XmrUnspentsProvider Xmr._getRandomOutputs', params) + + /* + const amounts = usingOuts.map(o => (o.rct ? '0' : o.amount.toString())) + const linkParams = { + amounts, + count: (mixin * 1 + 1) + } + */ + + const res = await BlocksoftAxios.post(this._link + 'get_random_outs', params) + await BlocksoftCryptoLog.log('XmrUnspentsProvider Xmr._getRandomOutputs res ' + JSON.stringify(res.data).substr(0, 200)) + + if (typeof res.data === 'undefined' || !typeof res.data || typeof res.data.amount_outs === 'undefined' || !res.data.amount_outs || res.data.amount_outs.length === 0) { + throw new Error('SERVER_RESPONSE_NO_RESPONSE_XMR') + } + + if (typeof fn === 'undefined' || !fn) { + return res.data + } else { + fn(null, res.data) + } + } catch (e) { + if (e.message.indexOf('SERVER_RESPONSE') === -1) { + e.message += ' while Xmr._getRandomOutputs' + } + if (typeof fn === 'undefined' || !fn) { + throw e + } else { + fn(e, null) + } + } + } + +} diff --git a/crypto/blockchains/xrp/XrpAddressProcessor.js b/crypto/blockchains/xrp/XrpAddressProcessor.js new file mode 100644 index 000000000..cdc05f9b5 --- /dev/null +++ b/crypto/blockchains/xrp/XrpAddressProcessor.js @@ -0,0 +1,34 @@ +/** + * @version 0.5 + * https://github.com/iancoleman/bip39/blob/aa793f572f26ad20740f28040de13431c973dfb8/src/js/ripple-util.js + */ +const bitcoin = require('bitcoinjs-lib') +const basex = require('base-x') + +export default class XrpAddressProcessor { + + async setBasicRoot(root) { + + } + + /** + * @param {string|Buffer} privateKey + * @param {*} data + * @returns {Promise<{privateKey: string, address: string, addedData: *}>} + */ + async getAddress(privateKey, data = {}) { + privateKey = Buffer.from(privateKey) + const keyPair = bitcoin.ECPair.fromPrivateKey(privateKey) + const btcPrivateKey = keyPair.toWIF() + const ripplePrivateKey = basex('123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz').decode(btcPrivateKey).toString("hex").slice(2,66) + const btcAddress = bitcoin.payments.p2pkh({pubkey: keyPair.publicKey, network: bitcoin.networks.bitcoin}).address + const rippleAddress = basex('rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz').encode( + basex('123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz').decode(btcAddress) + ) + const addedData = {} + if (typeof data !== 'undefined' && typeof data.publicKey !== 'undefined') { + addedData.publicKey = data.publicKey.toString('hex') + } + return {address: rippleAddress, privateKey: ripplePrivateKey, addedData} + } +} diff --git a/crypto/blockchains/xrp/XrpScannerProcessor.js b/crypto/blockchains/xrp/XrpScannerProcessor.js new file mode 100644 index 000000000..b2d47f7f0 --- /dev/null +++ b/crypto/blockchains/xrp/XrpScannerProcessor.js @@ -0,0 +1,89 @@ +/** + * @version 0.5 + */ +import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog' +import XrpTmpDS from './stores/XrpTmpDS' +import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings' +import XrpDataRippleProvider from '@crypto/blockchains/xrp/basic/XrpDataRippleProvider' +import XrpDataScanProvider from '@crypto/blockchains/xrp/basic/XrpDataScanProvider' + + +let CACHE_BLOCK_DATA = {} + +export default class XrpScannerProcessor { + + _inited = false + + async init() { + if (this._inited) { + return false + } + CACHE_BLOCK_DATA = await XrpTmpDS.getCache() + const serverType = BlocksoftExternalSettings.getStatic('XRP_SCANNER_TYPE') + if (serverType === 'dataripple') { + this.provider = new XrpDataRippleProvider() + } else { + this.provider = new XrpDataScanProvider() + } + this.provider.setCache(CACHE_BLOCK_DATA) + this._inited = true + } + + /** + * https://data.ripple.com/v2/accounts/rL2SpzwrCZ4N2BaPm88pNGGHkPLzejZgB8/balances + * @param {string} address + * @return {Promise<{balance, unconfirmed, provider}>} + */ + async getBalanceBlockchain(address) { + await this.init() + + let res = false + let balance = 0 + let provider = 'none' + try { + res = await this.provider.getBalanceBlockchain(address) + if (res && typeof res.balance !== 'undefined') { + balance = res.balance + provider = res.provider + } + } catch (e) { + if (e.message.indexOf('timed out') === -1 && e.message.indexOf('account not found') === -1) { + throw e + } else { + return false + } + } + return { balance, unconfirmed: 0, provider } + } + + + /** + * @param {string} scanData.account.address + * @param {*} scanData.additional + * @param {string} scanData.account.walletHash + * @return {Promise} + */ + async getTransactionsBlockchain(scanData, source = '') { + await this.init() + const address = scanData.account.address.trim() + await BlocksoftCryptoLog.log('XrpScannerProcessor.getTransactions started ' + address) + + let transactions = [] + try { + transactions = await this.provider.getTransactionsBlockchain(scanData) + } catch (e) { + if (e.message.indexOf('account not found') === -1 + && e.message.indexOf('to retrieve payments') === -1 + && e.message.indexOf('limit exceeded') === -1 + && e.message.indexOf('timed out') === -1 + ) { + throw e + } else { + return false + } + } + + await BlocksoftCryptoLog.log('XrpScannerProcessor.getTransactions finished ' + address) + return transactions + } +} diff --git a/crypto/blockchains/xrp/XrpTransferProcessor.ts b/crypto/blockchains/xrp/XrpTransferProcessor.ts new file mode 100644 index 000000000..7a8722e96 --- /dev/null +++ b/crypto/blockchains/xrp/XrpTransferProcessor.ts @@ -0,0 +1,229 @@ +/** + * @version 0.20 + * https://gist.github.com/WietseWind/19df307c3c68748543971242284ade4d + * + * https://xrpl.org/rippleapi-reference.html#preparepayment + * https://xrpl.org/rippleapi-reference.html#sign + * https://xrpl.org/rippleapi-reference.html#submit + */ +import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog' +import BlocksoftUtils from '../../common/BlocksoftUtils' +import BlocksoftDispatcher from '../BlocksoftDispatcher' +import MarketingEvent from '../../../app/services/Marketing/MarketingEvent' + +import { BlocksoftBlockchainTypes } from '../BlocksoftBlockchainTypes' +import { XrpTxSendProvider } from './basic/XrpTxSendProvider' +import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings' + + +const FEE_DECIMALS = 6 + +export default class XrpTransferProcessor implements BlocksoftBlockchainTypes.TransferProcessor { + private _settings: { network: string; currencyCode: string } + private _provider: XrpTxSendProvider + private _inited : boolean = false + + constructor(settings: { network: string; currencyCode: string }) { + this._settings = settings + } + + needPrivateForFee(): boolean { + return false + } + + checkSendAllModal(data: { currencyCode: any }): boolean { + return false + } + + async checkTransferHasError(data: BlocksoftBlockchainTypes.CheckTransferHasErrorData): Promise { + // @ts-ignore + if (data.amount && data.amount * 1 > BlocksoftExternalSettings.getStatic('XRP_MIN')) { + return { isOk: true } + } + /** + * @type {XrpScannerProcessor} + */ + const balanceProvider = BlocksoftDispatcher.getScannerProcessor(this._settings.currencyCode) + const balanceRaw = await balanceProvider.getBalanceBlockchain(data.addressTo) + if (balanceRaw && typeof balanceRaw.balance !== 'undefined' && balanceRaw.balance > BlocksoftExternalSettings.getStatic('XRP_MIN')) { + return { isOk: true } + } else { + return { isOk: false, code: 'XRP', address: data.addressTo } + } + } + + async getFeeRate(data: BlocksoftBlockchainTypes.TransferData, privateData: BlocksoftBlockchainTypes.TransferPrivateData, additionalData: {} = {}): Promise { + const result: BlocksoftBlockchainTypes.FeeRateResult = { + selectedFeeIndex: -1, + shouldShowFees : false + } as BlocksoftBlockchainTypes.FeeRateResult + + // @ts-ignore + if (data.amount * 1 <= 0) { + BlocksoftCryptoLog.log(this._settings.currencyCode + ' XrpTransferProcessor.getFeeRate ' + data.addressFrom + ' => ' + data.addressTo + ' skipped as zero amount') + return result + } + + BlocksoftCryptoLog.log(this._settings.currencyCode + ' XrpTransferProcessor.getFeeRate ' + data.addressFrom + ' => ' + data.addressTo + ' started amount: ' + data.amount) + + + let txJson = false + try { + if (!this._inited) { + this._provider = new XrpTxSendProvider() + this._inited = true + } + txJson = await this._provider.getPrepared(data) + } catch (e) { + if (e.message.indexOf('connect() timed out after') !== -1) { + return false + } + if (e.message.indexOf('Account not found') !== -1) { + return false + } + if (e.message.indexOf('Destination does not exist. Too little XRP sent to create it') !== -1) { + throw new Error('SERVER_RESPONSE_NOT_ENOUGH_BALANCE_DEST_XRP') + } + throw e + } + if (!txJson) { + throw new Error('SERVER_RESPONSE_BAD_INTERNET') + } + // @ts-ignore + const fee = BlocksoftUtils.toUnified(txJson.Fee, FEE_DECIMALS) + + BlocksoftCryptoLog.log(this._settings.currencyCode + ' XrpTransferProcessor.getFeeRate ' + data.addressFrom + ' => ' + data.addressTo + ' finished amount: ' + data.amount + ' fee: ' + fee) + result.fees = [ + { + langMsg: 'xrp_speed_one', + feeForTx: fee, + amountForTx: data.amount, + blockchainData: txJson + } + ] + result.selectedFeeIndex = 0 + return result + } + + async getTransferAllBalance(data: BlocksoftBlockchainTypes.TransferData, privateData: BlocksoftBlockchainTypes.TransferPrivateData, additionalData: BlocksoftBlockchainTypes.TransferAdditionalData = {}): Promise { + const balance = data.amount + + // @ts-ignore + BlocksoftCryptoLog.log(this._settings.currencyCode + ' XrpTransferProcessor.getTransferAllBalance ', data.addressFrom + ' => ' + balance) + // noinspection EqualityComparisonWithCoercionJS + if (BlocksoftUtils.diff(balance, BlocksoftExternalSettings.getStatic('XRP_MIN')) <= 0) { + return { + selectedTransferAllBalance: '0', + selectedFeeIndex: -1, + fees: [], + shouldShowFees : false, + countedForBasicBalance: '0' + } + } + + + const result = await this.getFeeRate(data, privateData, additionalData) + // @ts-ignore + if (!result || result.selectedFeeIndex < 0) { + return { + selectedTransferAllBalance: '0', + selectedFeeIndex: -2, + fees: [], + shouldShowFees : false, + countedForBasicBalance: balance + } + } + // @ts-ignore + result.fees[result.selectedFeeIndex].amountForTx = BlocksoftUtils.diff(result.fees[result.selectedFeeIndex].amountForTx, BlocksoftExternalSettings.getStatic('XRP_MIN')).toString() + return { + ...result, + shouldShowFees : false, + selectedTransferAllBalance: result.fees[result.selectedFeeIndex].amountForTx + } + } + + async sendTx(data: BlocksoftBlockchainTypes.TransferData, privateData: BlocksoftBlockchainTypes.TransferPrivateData, uiData: BlocksoftBlockchainTypes.TransferUiData): Promise { + + if (typeof privateData.privateKey === 'undefined') { + throw new Error('XRP transaction required privateKey') + } + if (typeof data.addressTo === 'undefined') { + throw new Error('XRP transaction required addressTo') + } + + let txJson = false + try { + if (!this._inited) { + this._provider = new XrpTxSendProvider() + this._inited = true + } + txJson = await this._provider.getPrepared(data, false) + } catch (e) { + if (e.message.indexOf('connect() timed out after') !== -1) { + throw new Error('SERVER_RESPONSE_BAD_INTERNET') + } + if (e.message.indexOf('Account not found') !== -1) { + throw new Error('SERVER_RESPONSE_BAD_INTERNET') + } + if (e.message.indexOf('Destination does not exist. Too little XRP sent to create it') !== -1) { + throw new Error('SERVER_RESPONSE_NOT_ENOUGH_BALANCE_DEST_XRP') + } + throw e + } + + + // https://xrpl.org/rippleapi-reference.html#preparepayment + // @ts-ignore + BlocksoftCryptoLog.log(this._settings.currencyCode + ' XrpTransferProcessor.sendTx prepared', txJson) + + // https://xrpl.org/rippleapi-reference.html#sign + if (typeof data.accountJson !== 'object') { + try { + const tmp = JSON.parse(data.accountJson) + data.accountJson = tmp + } catch (e) { + BlocksoftCryptoLog.err(this._settings.currencyCode + ' XrpTransferProcessor.sendTx no accountJson ' + JSON.stringify(data.accountJson)) + } + } + if (typeof data.accountJson.publicKey === 'undefined') { + BlocksoftCryptoLog.err(this._settings.currencyCode + ' XrpTransferProcessor.sendTx no publicKey ' + JSON.stringify(data.accountJson)) + throw new Error('SERVER_RESPONSE_BAD_CODE') + } + + if (typeof uiData !== 'undefined' && typeof uiData.selectedFee !== 'undefined'&& typeof uiData.selectedFee.rawOnly !== 'undefined' && uiData.selectedFee.rawOnly) { + return { rawOnly: uiData.selectedFee.rawOnly, raw : this._provider.signTx(data, privateData, txJson)} + } + + const result = await this._provider.sendTx(data, privateData, txJson) + + // noinspection ES6MissingAwait + MarketingEvent.logOnlyRealTime('v20_rippled_any_result ' + data.addressFrom + ' => ' + data.addressTo, { + txJson, + result + }) + // @ts-ignore + BlocksoftCryptoLog.log(this._settings.currencyCode + ' XrpTransferProcessor.sendTx result', result) + + if (result.resultCode === 'tecNO_DST_INSUF_XRP') { + throw new Error(result.resultMessage) // not enough - could be replaced by translated + } else if (result.resultCode === 'tecUNFUNDED_PAYMENT') { + throw new Error('SERVER_RESPONSE_NOT_ENOUGH_BALANCE_XRP') // not enough to pay + } else if (result.resultCode === 'tecNO_DST_INSUF_XRP') { + throw new Error('SERVER_RESPONSE_NOT_ENOUGH_BALANCE_DEST_XRP') // not enough to create account + } else if (result.resultCode === 'tefBAD_AUTH') { + throw new Error(result.resultMessage) // not valid key + } else if (result.resultCode === 'tecDST_TAG_NEEDED') { + throw new Error('SERVER_RESPONSE_TAG_NEEDED_XRP') + } + + if (typeof result.tx_json === 'undefined' || typeof result.tx_json.hash === 'undefined') { + throw new Error(result.resultMessage) // not enough + } + + if (result.resultCode !== 'tesSUCCESS') { + return { transactionHash: result.tx_json.hash, successMessage: result.resultMessage } // Held until escalated fee drops + } + + return { transactionHash: result.tx_json.hash } + } +} diff --git a/crypto/blockchains/xrp/basic/XrpDataRippleProvider.js b/crypto/blockchains/xrp/basic/XrpDataRippleProvider.js new file mode 100644 index 000000000..aa56f18e0 --- /dev/null +++ b/crypto/blockchains/xrp/basic/XrpDataRippleProvider.js @@ -0,0 +1,216 @@ +import BlocksoftAxios from '@crypto/common/BlocksoftAxios' +import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog' +import XrpTmpDS from '@crypto/blockchains/xrp/stores/XrpTmpDS' + +const CACHE_VALID_TIME = 60000 // 1 minute +let CACHE_BLOCK_DATA = {} + +const API_PATH = 'https://data.ripple.com/v2' +export default class XrpDataRippleProvider { + + setCache(tmp) { + CACHE_BLOCK_DATA = tmp + } + + async getBalanceBlockchain(address) { + const link = `${API_PATH}/accounts/${address}/balances` + let res = false + let balance = 0 + + try { + res = await BlocksoftAxios.getWithoutBraking(link) + if (res && typeof res.data !== 'undefined' && res.data && typeof res.data.balances !== 'undefined') { + let row + for (row of res.data.balances) { + if (row.currency === 'XRP') { + balance = row.value + break + } + } + } else { + return false + } + } catch (e) { + if (e.message.indexOf('timed out') === -1 && e.message.indexOf('account not found') === -1) { + throw e + } else { + return false + } + } + return { balance: balance, unconfirmed: 0, provider: 'ripple.com' } + } + + /** + * @param {string} scanData.account.address + * @param {*} scanData.additional + * @param {string} scanData.account.walletHash + * @return {Promise} + */ + async getTransactionsBlockchain(scanData, source = '') { + const address = scanData.account.address.trim() + const action = 'payments' + await BlocksoftCryptoLog.log('XrpScannerProcessor.DataRipple.getTransactions ' + action + ' started ' + address) + const link = `${API_PATH}/accounts/${address}/payments` + let res = false + try { + res = await BlocksoftAxios.getWithoutBraking(link) + } catch (e) { + if (e.message.indexOf('account not found') === -1 + && e.message.indexOf('to retrieve payments') === -1 + && e.message.indexOf('limit exceeded') === -1 + && e.message.indexOf('timed out') === -1 + ) { + throw e + } else { + return false + } + } + + if (!res || typeof res.data === 'undefined' || !res.data) { + return false + } + if (typeof res.data[action] === 'undefined') { + throw new Error('Undefined txs ' + link + ' ' + JSON.stringify(res.data)) + } + if (typeof res.data[action] === 'string') { + throw new Error('Undefined txs ' + link + ' ' + res.data[action]) + } + + const transactions = await this._unifyTransactions(address, res.data[action], action) + await BlocksoftCryptoLog.log('XrpScannerProcessor.DataRipple.getTransactions ' + action + ' finished ' + address) + return transactions + } + + async _unifyTransactions(address, result) { + const transactions = [] + let tx + for (tx of result) { + const transaction = await this._unifyPayment(address, tx) + if (transaction) { + transactions.push(transaction) + } + } + return transactions + } + + /** + * @param {string} address + * @param {Object} transaction + * @param {string} transaction.amount '20.001' + * @param {string} transaction.delivered_amount '20.001', + * @param {Object} transaction.destination_balance_changes: [ { counterparty: '', currency: 'XRP', value: '20.001' } ], + * @param {Object} transaction.source_balance_changes: [ { counterparty: '', currency: 'XRP', value: '-20.001' } ], + * @param {string} transaction.tx_index 8 + * @param {string} transaction.currency 'XRP' + * @param {string} transaction.destination 'rL2SpzwrCZ4N2BaPm88pNGGHkPLzejZgB8' + * @param {string} transaction.destination_tag '1' + * @param {string} transaction.executed_time '2019-10-20T22:45:31Z' + * @param {string} transaction.ledger_index 50845930 + * @param {string} transaction.source 'rpJZ5WyotdphojwMLxCr2prhULvG3Voe3X' + * @param {string} transaction.source_currency 'XRP' + * @param {string} transaction.tx_hash '673F28303546CB8A0F45A0D80E6391B7A4A125DB8B72AD0DA635D625C3AD27F1' + * @param {string} transaction.transaction_cost '0.000012' + * @return {UnifiedTransaction} + * @private + **/ + async _unifyPayment(address, transaction) { + let direction, amount + + if (transaction.currency === 'XRP') { + if (transaction.source_currency === 'XRP') { + direction = (address === transaction.source) ? 'outcome' : 'income' + } else if (transaction.destination === address) { + direction = 'income' // USDT any => XRP my + } else { + // USDT my => XRP not my + return false // do nothing + } + } else if (transaction.source_currency === 'XRP') { + if (transaction.source === address) { + direction = 'outcome' // XRP my => USDT any + } else { + // XRP not my => USDT my + return false // do nothing + } + } else { + return false // USDT => USDT + } + + if (direction === 'income') { + amount = transaction.delivered_amount + } else { + amount = transaction.amount + } + + let transactionStatus = 'new' + let ledger = false + if (typeof transaction.ledger_index !== 'undefined' && transaction.ledger_index > 0) { + ledger = await this._getLedger(transaction.ledger_index) + if (ledger && ledger.transactionConfirmations > 5) { + transactionStatus = 'success' + } + } + + if (typeof transaction.executed_time === 'undefined') { + transaction.executed_time = '' + } + const tx = { + transactionHash: transaction.tx_hash, + blockHash: ledger ? ledger.ledger_hash : '', + blockNumber: transaction.ledger_index, + blockTime: transaction.executed_time, + blockConfirmations: ledger ? ledger.transactionConfirmations : 0, + transactionDirection: direction, + addressFrom: transaction.source === address ? '' : transaction.source, + addressTo: transaction.destination === address ? '' : transaction.destination, + addressAmount: amount, + transactionStatus: transactionStatus, + transactionFee: transaction.transaction_cost + } + if (typeof transaction.destination_tag !== 'undefined') { + tx.transactionJson = { memo: transaction.destination_tag } + } + return tx + } + + async _getLedger(index) { + const now = new Date().getTime() + await BlocksoftCryptoLog.log('XrpScannerProcessor.DataRipple._getLedger started ' + index) + const link = `${API_PATH}/ledgers/${index}` + let res = false + if (typeof CACHE_BLOCK_DATA[index] === 'undefined' || + ( + now - CACHE_BLOCK_DATA[index].time > CACHE_VALID_TIME + && + CACHE_BLOCK_DATA[index].data.transactionConfirmations < 100 + ) + ) { + try { + res = await BlocksoftAxios.getWithoutBraking(link) + if (res.data && typeof res.data !== 'undefined' && typeof res.data.ledger !== 'undefined') { + await BlocksoftCryptoLog.log('XrpScannerProcessor.DataRipple._getLedger updated for index ' + index + ' ' + JSON.stringify(res.data.ledger)) + const ledger = { + close_time: res.data.ledger.close_time, + ledger_hash: res.data.ledger.ledger_hash, + transactionConfirmations : Math.round((now - res.data.ledger.close_time * 1000) / (60 * 1000)) // minutes + } + CACHE_BLOCK_DATA[index] = { + data: ledger, + time: now + } + } + await XrpTmpDS.saveCache(CACHE_BLOCK_DATA) + } catch (e) { + if (e.message.indexOf('timed out') === -1 && e.message.indexOf('account not found') === -1) { + throw e + } else { + res = false + } + } + } + if (typeof CACHE_BLOCK_DATA[index] === 'undefined') { + return false + } + return CACHE_BLOCK_DATA[index].data + } +} diff --git a/crypto/blockchains/xrp/basic/XrpDataScanProvider.js b/crypto/blockchains/xrp/basic/XrpDataScanProvider.js new file mode 100644 index 000000000..334201405 --- /dev/null +++ b/crypto/blockchains/xrp/basic/XrpDataScanProvider.js @@ -0,0 +1,263 @@ +/** + * @version 0.5 + * https://xrpl.org/request-formatting.html + */ +import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings' +import BlocksoftAxios from '@crypto/common/BlocksoftAxios' +import BlocksoftUtils from '@crypto/common/BlocksoftUtils' +import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog' +import XrpTmpDS from '@crypto/blockchains/xrp/stores/XrpTmpDS' + +const CACHE_VALID_TIME = 60000 // 1 minute +let CACHE_BLOCK_DATA = {} + +export default class XrpDataScanProvider { + + setCache(tmp) { + CACHE_BLOCK_DATA = tmp + } + + async getBalanceBlockchain(address) { + const link = BlocksoftExternalSettings.getStatic('XRP_SCANNER_SERVER') + let res = false + let balance = 0 + try { + /** + curl http://s1.ripple.com:51234/ -X POST -H "Content-Type: application/json" -d '{'method":"account_info","params":[{"account":"rEAgA9B8U8RCkwn6MprHqE1ZfXoeGQxz4P","strict":true,"ledger_index":"validated","api_version':1}]}' + curl https://xrplcluster.com/ -X POST -H "Content-Type: application/json" -d '{'method":"account_info","params":[{"account":"rEAgA9B8U8RCkwn6MprHqE1ZfXoeGQxz4P","strict":true,"ledger_index":"validated","api_version':1}]}' + */ + const data = { + method: 'account_info', + params: [ + { + account: address, + strict: true, + ledger_index: 'validated', + api_version: 1 + } + ] + } + res = await BlocksoftAxios.postWithoutBraking(link, data) + + if (res && typeof res.data !== 'undefined' && res.data && typeof res.data.result !== 'undefined' && res.data.result) { + if (typeof res.data.result.account !== 'undefined' && typeof res.data.result.error_code !== 'undefined' && res.data.result.error_code === 19 ) { + balance = 0 + } else if (typeof res.data.result.account_data !== 'undefined' && typeof res.data.result.account_data.Balance !== 'undefined') { + balance = BlocksoftUtils.toUnified(res.data.result.account_data.Balance, 6) + } + } else { + return false + } + } catch (e) { + if (e.message.indexOf('timed out') === -1 && e.message.indexOf('account not found') === -1) { + if (typeof res.data !== 'undefined' && res.data) { + e.message += ' in ' + JSON.stringify(res.data) + } else { + e.message += ' empty data' + } + throw e + } else { + return false + } + } + return { balance: balance, unconfirmed: 0, provider: link } + } + + async getTransactionsBlockchain(scanData, source = '') { + const address = scanData.account.address.trim() + const link = BlocksoftExternalSettings.getStatic('XRP_SCANNER_SERVER') + let transactions = [] + let res = false + try { + // https://xrpl.org/account_tx.html + const data = { + method: 'account_tx', + params: [ + { + account: address, + binary: false, + forward: false, + ledger_index_max: -1, + ledger_index_min: -1, + limit: 100 + } + ] + } + res = await BlocksoftAxios.postWithoutBraking(link, data) + + if (res && typeof res.data !== 'undefined' && res.data + && typeof res.data.result !== 'undefined' && res.data.result + && typeof res.data.result.transactions !== 'undefined' && res.data.result.transactions + ) { + transactions = await this._unifyTransactions(address, res.data.result.transactions, res.data.result.ledger_index_max) + } else { + return false + } + } catch (e) { + if (e.message.indexOf('timed out') === -1 && e.message.indexOf('account not found') === -1) { + throw e + } else { + return false + } + } + return transactions + } + + async _unifyTransactions(address, result, lastBlock) { + const transactions = [] + let tx + for (tx of result) { + const transaction = await this._unifyPayment(address, tx, lastBlock) + if (transaction) { + transactions.push(transaction) + } + } + return transactions + } + + /** + * @param {string} address + * @param {Object} transaction + * @param {bool} transaction.validated + * @param {string} transaction.tx.Account 'rEAgA9B8U8RCkwn6MprHqE1ZfXoeGQxz4P' + * @param {string} transaction.tx.Amount '2000000' + * @param {string} transaction.tx.Destination 'rDh2XemJY5WSNCPgXjhqnJt1PLGsTKbnix' + * @param {string} transaction.tx.DestinationTag + * @param {string} transaction.tx.Fee 127091 + * @param {string} transaction.tx.LastLedgerSequence 68101269 + * @param {string} transaction.tx.TransactionType 'Payment' + * @param {string} transaction.tx.date 691857661 + * @param {string} transaction.tx.hash '4D08316F83148C7C0EC955301E770A196B708EAF874BA2339260317BFDCE89E6' + * @param {string} transaction.tx.inLedger 68101268 + * @param {string} transaction.tx.ledger_index 68101268 + * @param {string} transaction.meta.delivered_amount '2000000' + * @param {string} transaction.meta.TransactionResult 'tesSUCCESS' + * @return {UnifiedTransaction} + * @private + **/ + async _unifyPayment(address, transaction, lastBlock = 0) { + if (transaction.tx.TransactionType !== 'Payment') { + return false + } + let direction, amount + if (transaction.tx.Account === address) { + direction = 'outcome' + } else { + direction = 'income' + } + + amount = transaction.tx.Amount + if (direction === 'income' && typeof transaction.meta.delivered_amount !== 'undefined') { + amount = transaction.meta.delivered_amount + } + + let blockConfirmations = lastBlock - transaction.tx.ledger_index + if (blockConfirmations <= 0) blockConfirmations = 0 + let transactionStatus = 'new' + if (transaction.validated === true || transaction.meta.TransactionResult === 'tesSUCCESS') { + if (blockConfirmations > 5) { + transactionStatus = 'success' + } + } + const ledger = await this._getLedger(transaction.tx.ledger_index) + const blockTime = ledger && typeof ledger.close_time !== 'undefined' && ledger.close_time ? ledger.close_time : transaction.tx.date + const blockHash = ledger && typeof ledger.ledger_hash !== 'undefined' && ledger.ledger_hash ? ledger.ledger_hash : transaction.tx.ledger_index + const tx = { + transactionHash: transaction.tx.hash, + blockHash, + blockNumber: transaction.tx.ledger_index, + blockTime, + blockConfirmations, + transactionDirection: direction, + addressFrom: transaction.tx.Account === address ? '' : transaction.tx.Account, + addressTo: transaction.tx.Destination === address ? '' : transaction.tx.Destination, + addressAmount: BlocksoftUtils.toUnified(amount, 6), + transactionStatus: transactionStatus, + transactionFee: BlocksoftUtils.toUnified(transaction.tx.Fee, 6) + } + // https://blockchair.com/ripple/transaction/F56C6B0CA7BB6CD9AC74843E6C7BA605C7FFBB1F409E356CA235423F30F55F51?from=trustee + if (typeof transaction.tx.DestinationTag !== 'undefined') { + tx.transactionJson = { memo: transaction.tx.DestinationTag } + } + return tx + } + + async _getLedger(index) { + const now = new Date().getTime() + await BlocksoftCryptoLog.log('XrpScannerProcessor.DataScan._getLedger started ' + index) + const link = BlocksoftExternalSettings.getStatic('XRP_SCANNER_SERVER') + let res = false + if (typeof CACHE_BLOCK_DATA[index] === 'undefined' || + ( + now - CACHE_BLOCK_DATA[index].time > CACHE_VALID_TIME + && + CACHE_BLOCK_DATA[index].data.transactionConfirmations < 100 + ) + ) { + try { + const data = { + method: 'ledger_data', + params: [ + { + binary: false, + ledger_index: index + } + ] + } + res = await BlocksoftAxios.postWithoutBraking(link, data) + if (res.data && typeof res.data !== 'undefined' && typeof res.data.result !== 'undefined' && typeof res.data.result.ledger !== 'undefined') { + await BlocksoftCryptoLog.log('XrpScannerProcessor.DataScan._getLedger updated for index ' + index + ' ' + JSON.stringify(res.data.result.ledger)) + const date = this._getDate(res.data.result.ledger.close_time_human) + const ledger = { + close_time: date, + ledger_hash: res.data.result.ledger.ledger_hash, + transactionConfirmations: Math.round((now - date * 1000) / (60 * 1000)) // minutes + } + CACHE_BLOCK_DATA[index] = { + data: ledger, + time: now + } + } + await XrpTmpDS.saveCache(CACHE_BLOCK_DATA) + } catch (e) { + if (e.message.indexOf('timed out') === -1 && e.message.indexOf('account not found') === -1) { + throw e + } else { + res = false + } + } + } + if (typeof CACHE_BLOCK_DATA[index] === 'undefined') { + return false + } + return CACHE_BLOCK_DATA[index].data + + } + + + // 2021-Dec-03 14:41:01.00 + // const tmp = new Date(time) not working in emulator + _getDate(time) { + time = time.split('.')[0] + const months = { + 'Jan': 0, + 'Feb': 1, + 'Mar': 2, + 'Apr': 3, + 'May': 4, + 'Jun': 5, + 'Jul': 6, + 'Aug': 7, + 'Sep': 8, + 'Oct': 9, + 'Nov': 10, + 'Dec': 11 + } + const tmp0 = time.split(' ') + const tmp1 = tmp0[0].split('-') + const tmp2 = tmp0[1].split(':') + const tmp = new Date(tmp1[0], months[tmp1[1]], tmp1[2], tmp2[0], tmp2[1], tmp2[2]) + return tmp.getTime() + + } +} diff --git a/crypto/blockchains/xrp/basic/XrpTxSendProvider.ts b/crypto/blockchains/xrp/basic/XrpTxSendProvider.ts new file mode 100644 index 000000000..23127d7c8 --- /dev/null +++ b/crypto/blockchains/xrp/basic/XrpTxSendProvider.ts @@ -0,0 +1,183 @@ +/** + * @version 0.20 + * https://gist.github.com/WietseWind/19df307c3c68748543971242284ade4d + * + * https://xrpl.org/rippleapi-reference.html#preparepayment + * https://xrpl.org/rippleapi-reference.html#sign + * https://xrpl.org/rippleapi-reference.html#submit + */ +import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog' +import BlocksoftExternalSettings from '../../../common/BlocksoftExternalSettings' +import { BlocksoftBlockchainTypes } from '../../BlocksoftBlockchainTypes' + +import { XrpTxUtils } from './XrpTxUtils' + +import MarketingEvent from '../../../../app/services/Marketing/MarketingEvent' +import config from '../../../../app/config/config' + +const RippleAPI = require('ripple-lib').RippleAPI + +export class XrpTxSendProvider { + + private readonly _api: typeof RippleAPI + + constructor() { + this._api = new RippleAPI({ server: BlocksoftExternalSettings.getStatic('XRP_SERVER') }) // Public rippled server + this._api.on('error', (errorCode: string, errorMessage: string) => { + BlocksoftCryptoLog.log('XrpTransferProcessor constructor' + errorCode + ': ' + errorMessage) + }) + this._api.on('connected', () => { + }) + this._api.on('disconnected', () => { + + }) + } + + async getPrepared(data: BlocksoftBlockchainTypes.TransferData, toObject = true) { + + const payment = { + 'source': { + 'address': data.addressFrom, + 'maxAmount': { + 'value': XrpTxUtils.amountPrep(data.amount), + 'currency': 'XRP' + } + }, + 'destination': { + 'address': data.addressTo, + 'amount': { + 'value': XrpTxUtils.amountPrep(data.amount), + 'currency': 'XRP' + } + } + } + + if (data.addressFrom === data.addressTo) { + throw new Error('SERVER_RESPONSE_SELF_TX_FORBIDDEN') + } + + // https://xrpl.org/rippleapi-reference.html#payment + try { + if (typeof data.memo !== 'undefined' && data.memo && data.memo.toString().trim().length > 0) { + // @ts-ignore + const int = data.memo.toString().trim() * 1 + if (int.toString() !== data.memo) { + throw new Error('Destination tag type validation error') + } + if (int > 4294967295) { + throw new Error('Destination tag couldnt be more then 4294967295') + } + // @ts-ignore + payment.destination.tag = int + } + } catch (e) { + // @ts-ignore + BlocksoftCryptoLog.log('XrpTransferProcessor._getPrepared memo error ' + e.message, data) + } + // @ts-ignore + BlocksoftCryptoLog.log('XrpTransferProcessor._getPrepared payment', payment) + + const api = this._api + + return new Promise((resolve, reject) => { + api.connect().then(() => { + api.preparePayment(data.addressFrom, payment).then((prepared: { txJSON: any }) => { + // https://xrpl.org/rippleapi-reference.html#preparepayment + if (typeof prepared.txJSON === 'undefined') { + reject(new Error('No txJSON inside ripple response ' + JSON.stringify(prepared))) + } + const txJson = prepared.txJSON + BlocksoftCryptoLog.log('XrpTxSendProvider._getPrepared prepared', txJson) + resolve(toObject ? JSON.parse(txJson) : txJson) + }).catch((error: { toString: () => string }) => { + MarketingEvent.logOnlyRealTime('v20_rippled_prepare_error ' + data.addressFrom + ' => ' + data.addressTo, { + payment, + msg: error.toString() + }) + BlocksoftCryptoLog.log('XrpTxSendProvider._getPrepared error ' + error.toString()) + reject(error) + }) + }).catch((error: { toString: () => string }) => { + MarketingEvent.logOnlyRealTime('v20_rippled_prepare_no_connection ' + data.addressFrom + ' => ' + data.addressTo, { + payment, + msg: error.toString() + }) + BlocksoftCryptoLog.log('XrpTxSendProvider._getPrepared connect error ' + error.toString()) + reject(error) + }) + }) + } + + signTx(data: BlocksoftBlockchainTypes.TransferData, privateData: BlocksoftBlockchainTypes.TransferPrivateData, txJson: any): Promise { + const api = this._api + const keypair = { + privateKey: privateData.privateKey, + publicKey: data.accountJson.publicKey.toUpperCase() + } + const signed = api.sign(txJson, keypair) + return signed.signedTransaction + } + + async sendTx(data: BlocksoftBlockchainTypes.TransferData, privateData: BlocksoftBlockchainTypes.TransferPrivateData, txJson: any): Promise<{ + resultCode: string, + resultMessage: string, + // eslint-disable-next-line camelcase + tx_json?: { + hash: string + } + }> { + const api = this._api + let result + try { + const signed = this.signTx(data, privateData, txJson) + BlocksoftCryptoLog.log('XrpTransferProcessor.sendTx signed', signed) + result = await new Promise((resolve, reject) => { + api.connect().then(() => { + // https://xrpl.org/rippleapi-reference.html#submit + api.submit(signed).then((result: { + resultCode: '', + resultMessage: '' + }) => { + MarketingEvent.logOnlyRealTime('v20_rippled_success ' + data.addressFrom + ' => ' + data.addressTo, { + txJson, + result + }) + resolve(result) + }).catch((error: { toString: () => string }) => { + MarketingEvent.logOnlyRealTime('v20_rippled_send_error ' + data.addressFrom + ' => ' + data.addressTo, { + txJson, + msg: error.toString() + }) + BlocksoftCryptoLog.log('XrpTransferProcessor.submit error ' + error.toString()) + reject(error) + }) + }).catch((error: { toString: () => string }) => { + MarketingEvent.logOnlyRealTime('v20_rippled_send_no_connection ' + data.addressFrom + ' => ' + data.addressTo, { + txJson, + msg: error.toString() + }) + BlocksoftCryptoLog.log('XrpTransferProcessor.sendTx connect error ' + error.toString()) + reject(error) + }) + }) + } catch (e) { + if (config.debug.cryptoErrors) { + console.log('XrpTransferProcessor.sendTx error ', e) + } + MarketingEvent.logOnlyRealTime('v20_rippled_send2_error ' + data.addressFrom + ' => ' + data.addressTo, { + txJson, + msg: e.toString() + }) + BlocksoftCryptoLog.log('XrpTransferProcessor.send2 error ' + e.toString()) + if (typeof e.resultMessage !== 'undefined') { + throw new Error(e.resultMessage.toString()) + } else if (typeof e.message !== 'undefined') { + throw new Error(e.message.toString()) + } else { + throw new Error(e.toString()) + } + } + // @ts-ignore + return result + } +} diff --git a/crypto/blockchains/xrp/basic/XrpTxUtils.ts b/crypto/blockchains/xrp/basic/XrpTxUtils.ts new file mode 100644 index 000000000..4005c72fd --- /dev/null +++ b/crypto/blockchains/xrp/basic/XrpTxUtils.ts @@ -0,0 +1,9 @@ +export namespace XrpTxUtils { + export const amountPrep = function( current: string) : string { + const tmp = current.toString().split('.') + if (typeof tmp[1] !== 'undefined' && tmp[1].length > 6) { + current = tmp[0] + '.' + tmp[1].substr(0, 6) + } + return current.toString() + } +} diff --git a/crypto/blockchains/xrp/stores/XrpTmpDS.js b/crypto/blockchains/xrp/stores/XrpTmpDS.js new file mode 100644 index 000000000..dd0bea3c0 --- /dev/null +++ b/crypto/blockchains/xrp/stores/XrpTmpDS.js @@ -0,0 +1,67 @@ + +import Database from '@app/appstores/DataSource/Database'; + +const tableName = 'transactions_scanners_tmp' + +class XrpTmpDS { + /** + * @type {string} + * @private + */ + _currencyCode = 'XRP' + + _valKey = 'ledg' + + _isSaved = false + + async getCache() { + const res = await Database.query(` + SELECT id, tmp_key, tmp_val + FROM ${tableName} + WHERE currency_code='${this._currencyCode}' + AND tmp_key='${this._valKey}' + `) + let tmp = {} + const idsToRemove = [] + if (res.array) { + let row + let found = false + for (row of res.array) { + if (found) { + idsToRemove.push(row.id) + } else { + try { + tmp = JSON.parse(Database.unEscapeString(row.tmp_val)) + this._isSaved = true + found = true + } catch (e) { + idsToRemove.push(row.id) + } + } + } + if (idsToRemove.length > 0) { + await Database.query(`DELETE FROM ${tableName} WHERE id IN (${idsToRemove.join(',')})`) + } + } + return tmp + } + + async saveCache(value) { + const tmp = Database.escapeString(JSON.stringify(value)) + const now = new Date().toISOString() + if (this._isSaved) { + await Database.query(`UPDATE ${tableName} SET tmp_val='${tmp}' WHERE tmp_key='${this._valKey}' AND currency_code='${this._currencyCode}'`) + } else { + const prepared = [{ + currency_code: this._currencyCode, + tmp_key: this._valKey, + tmp_val: tmp, + created_at: now + }] + await Database.setTableName(tableName).setInsertData({ insertObjs: prepared }).insert() + this._isSaved = true + } + } +} + +export default new XrpTmpDS() diff --git a/crypto/blockchains/xvg/XvgScannerProcessor.js b/crypto/blockchains/xvg/XvgScannerProcessor.js new file mode 100644 index 000000000..50f8457b4 --- /dev/null +++ b/crypto/blockchains/xvg/XvgScannerProcessor.js @@ -0,0 +1,241 @@ +/** + * @version 0.5 + * https://github.com/bitpay/bitcore/blob/master/packages/bitcore-node/docs/api-documentation.md + * https://api.vergecurrency.network/node/api/XVG/mainnet/address/DL5LtSf7wztH45VuYunL8oaQHtJbKLCHyw/balance + */ +import BlocksoftAxios from '../../common/BlocksoftAxios' +import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog' + +import XvgTmpDS from './stores/XvgTmpDS' +import XvgFindAddressFunction from './basic/XvgFindAddressFunction' + +const API_PATH = 'https://api.vergecurrency.network/node/api/XVG/mainnet' +const CACHE_VALID_TIME = 30000 // 30 seconds +const CACHE = {} +let CACHE_FROM_DB = {} + +export default class XvgScannerProcessor { + /** + * @type {number} + * @private + */ + _blocksToConfirm = 20 + + /** + * @param {string} address + * @return {Promise<{balance:*, unconfirmed:*, provider:string}>} + */ + async getBalanceBlockchain(address) { + const link = `${API_PATH}/address/${address}/balance` + const res = await BlocksoftAxios.getWithoutBraking(link) + if (!res || !res.data) { + return false + } + if (typeof res.data.confirmed === 'undefined') { + throw new Error('XvgScannerProcessor.getBalance nothing loaded for address ' + link) + } + const balance = res.data.confirmed + return { balance, unconfirmed: 0, provider: 'api.vergecurrency' } + } + + /** + * @param {string} scanData.account.address + * @param {*} scanData.additional + * @param {string} scanData.account.walletHash + * @return {Promise} + */ + async getTransactionsBlockchain(scanData, source = '') { + const address = scanData.account.address.trim() + BlocksoftCryptoLog.log('XvgScannerProcessor.getTransactions started ' + address) + const link = `${API_PATH}/address/${address}/txs` + BlocksoftCryptoLog.log('XvgScannerProcessor.getTransactions call ' + link) + let tmp = await BlocksoftAxios.get(link) + if (tmp.status < 200 || tmp.status >= 300) { + throw new Error('not valid server response status ' + link) + } + + if (typeof tmp.data === 'undefined' || !tmp.data) { + throw new Error('Undefined txs ' + link + ' ' + JSON.stringify(tmp.data)) + } + + tmp = tmp.data + if (tmp.data) { + tmp = tmp.data // wtf but ok to support old wallets + } + + const transactions = [] + const already = {} + CACHE_FROM_DB = await XvgTmpDS.getCache(address) + + let tx + for (tx of tmp) { // ASC order is important + const tmp2 = await this._unifyTransactionStep1(address, tx, already) + if (tmp2) { + if (tmp2.outcoming) { + if (typeof CACHE_FROM_DB[tmp2.outcoming.transactionHash + '_data'] === 'undefined') { + tmp2.outcoming = await this._unifyTransactionStep2(address, tmp2.outcoming) + if (tmp2.outcoming) { + already[tmp2.outcoming.transactionHash] = 1 + if (tmp2.outcoming.addressTo === '?') { + tmp2.outcoming.addressTo = 'self' + BlocksoftCryptoLog.log('XvgScannerProcessor.getTransactions consider as self ' + tmp2.outcoming.transactionHash) + } + transactions.push(tmp2.outcoming) + } + } else { + already[tmp2.outcoming.transactionHash] = 1 + } + } + if (tmp2.incoming) { + if (typeof CACHE_FROM_DB[tmp2.incoming.transactionHash + '_data'] === 'undefined') { + tmp2.incoming = await this._unifyTransactionStep2(address, tmp2.incoming) + if (tmp2.incoming) { + already[tmp2.incoming.transactionHash] = 1 + transactions.push(tmp2.incoming) + } + } else { + already[tmp2.incoming.transactionHash] = 1 + } + } + } + } + BlocksoftCryptoLog.log('XvgScannerProcessor.getTransactions finished ' + address + ' total: ' + transactions.length) + return transactions + + } + + /** + * https://api.vergecurrency.network/node/api/XVG/mainnet/tx/abcda88bdb3968c5e444694ce3914cdec34f3afab73627bf201d34493d5e3aae/coins + * @param address + * @param transaction + * @returns {Promise} + * @private + */ + async _unifyTransactionStep2(address, transaction) { + if (!transaction) return false + + if (typeof CACHE[transaction.transactionHash] !== 'undefined') { + if (CACHE[transaction.transactionHash].data.blockConfirmations > 100) { + return CACHE[transaction.transactionHash].data + } + const now = new Date().getTime() + if (now - CACHE[transaction.transactionHash].time < CACHE_VALID_TIME) { + return CACHE[transaction.transactionHash].data + } + } + + let tmp + + const link = `${API_PATH}/tx/${transaction.transactionHash}/coins` + BlocksoftCryptoLog.log('XvgScannerProcessor._unifyTransactionStep2 call for outputs should be ' + link) + + if (typeof CACHE_FROM_DB[transaction.transactionHash + '_coins'] !== 'undefined') { + tmp = CACHE_FROM_DB[transaction.transactionHash + '_coins'] + } else { + BlocksoftCryptoLog.log('XvgScannerProcessor._unifyTransactionStep2 called ' + link) + tmp = await BlocksoftAxios.get(link) + tmp = tmp.data + // noinspection ES6MissingAwait + XvgTmpDS.saveCache(address, transaction.transactionHash, 'coins', tmp) + CACHE_FROM_DB[transaction.transactionHash + '_coins'] = tmp + } + + + let output + try { + output = await XvgFindAddressFunction(address, tmp) + } catch (e) { + e.message += ' while XvgFindAddressFunction' + throw e + } + + transaction.transactionDirection = output.direction + transaction.addressFrom = output.from + transaction.addressTo = output.to + transaction.addressAmount = output.value + + + const link2 = `${API_PATH}/tx/${transaction.transactionHash}` + BlocksoftCryptoLog.log('XvgScannerProcessor._unifyTransactionStep2 call for details ' + link2) + let tmp2 = await BlocksoftAxios.get(link2) + tmp2 = tmp2.data + transaction.blockHash = tmp2.blockHash + transaction.blockTime = tmp2.blockTimeNormalized + transaction.blockConfirmations = tmp2.confirmations * 1 + if (transaction.blockConfirmations < 0) transaction.blockConfirmations = transaction.blockConfirmations * -1 + + transaction.transaction_fee = tmp2.fee + transaction.transactionStatus = 'new' + if (transaction.blockConfirmations > this._blocksToConfirm) { + transaction.transactionStatus = 'success' + } else if (transaction.blockConfirmations > 0) { + transaction.transactionStatus = 'confirming' + } + if (transaction.transactionStatus === 'success') { + // noinspection ES6MissingAwait + XvgTmpDS.saveCache(address, transaction.transactionHash, 'data', tmp2) + CACHE_FROM_DB[transaction.transactionHash + '_data'] = 1 // no need all - just mark + } + BlocksoftCryptoLog.log('XvgScannerProcessor._unifyTransactionStep2 call for details result ', transaction) + CACHE[transaction.transactionHash] = {} + CACHE[transaction.transactionHash].time = new Date().getTime() + CACHE[transaction.transactionHash].data = transaction + return transaction + } + + /** + * + * @param {string} address + * @param {Object} transaction + * @param {string} transaction._id 5dcedb83746f4c73710ff5ce + * @param {string} transaction.chain XVG + * @param {string} transaction.network mainnet + * @param {string} transaction.coinbase false + * @param {string} transaction.mintIndex 0 + * @param {string} transaction.spentTxid + * @param {string} transaction.mintTxid abcda88bdb3968c5e444694ce3914cdec34f3afab73627bf201d34493d5e3aae + * @param {string} transaction.mintHeight 3600363 + * @param {string} transaction.spentHeight + * @param {string} transaction.address DL5LtSf7wztH45VuYunL8oaQHtJbKLCHyw + * @param {string} transaction.script 76a914a3d43334ff9ea4c257a1796b63e4fa8330747d2e88ac + * @param {string} transaction.value 95000000 + * @param {string} transaction.confirmations + * @param {*} already + * @return {UnifiedTransaction} + * @private + */ + async _unifyTransactionStep1(address, transaction, already) { + if (transaction.chain !== 'XVG' || transaction.network !== 'mainnet') return false + const res = { incoming: false, outcoming: false } + if (transaction.spentTxid && typeof already[transaction.spentTxid] === 'undefined') { + res.outcoming = { + transactionHash: transaction.spentTxid, + blockHash: '?', + blockNumber: +transaction.spentHeight, + blockTime: '?', + blockConfirmations: '?', + transactionDirection: 'outcome', + addressFrom: transaction.address, + addressTo: '?', + addressAmount: '0', + transactionStatus: '?' + } + } + if (transaction.mintTxid && transaction.mintTxid !== transaction.spentTxid && typeof already[transaction.mintTxid] === 'undefined') { + res.incoming = { + transactionHash: transaction.mintTxid, + blockHash: '?', + blockNumber: +transaction.mintHeight, + blockTime: '?', + blockConfirmations: '?', + transactionDirection: 'income', + addressFrom: '?', + addressTo: transaction.address, + addressAmount: '0', // transaction.value + transactionStatus: '?' + } + } + return res + + } +} diff --git a/crypto/blockchains/xvg/XvgTransferProcessor.ts b/crypto/blockchains/xvg/XvgTransferProcessor.ts new file mode 100644 index 000000000..88d50ddce --- /dev/null +++ b/crypto/blockchains/xvg/XvgTransferProcessor.ts @@ -0,0 +1,41 @@ +/** + * @version 0.20 + */ +import { BlocksoftBlockchainTypes } from '../BlocksoftBlockchainTypes' + +import DogeTransferProcessor from '../doge/DogeTransferProcessor' +import XvgUnspentsProvider from './providers/XvgUnspentsProvider' +import XvgSendProvider from './providers/XvgSendProvider' +import DogeTxInputsOutputs from '../doge/tx/DogeTxInputsOutputs' +import DogeTxBuilder from '../doge/tx/DogeTxBuilder' + +export default class XvgTransferProcessor extends DogeTransferProcessor implements BlocksoftBlockchainTypes.TransferProcessor { + + + _trezorServerCode = 'NONE' + + _builderSettings: BlocksoftBlockchainTypes.BuilderSettings = { + minOutputDustReadable: 0.000005, + minChangeDustReadable: 0.00001, + feeMaxForByteSatoshi: 100000000, // for tx builder + feeMaxAutoReadable2: 0.2, // for fee calc, + feeMaxAutoReadable6: 0.1, // for fee calc + feeMaxAutoReadable12: 0.05, // for fee calc + changeTogether: true, + minRbfStepSatoshi: 10, + minSpeedUpMulti : 1.5 + } + + canRBF(data: BlocksoftBlockchainTypes.DbAccount, transaction: BlocksoftBlockchainTypes.DbTransaction): boolean { + return false + } + + _initProviders() { + if (this._initedProviders) return false + this.unspentsProvider = new XvgUnspentsProvider(this._settings, this._trezorServerCode) + this.sendProvider = new XvgSendProvider(this._settings, this._trezorServerCode) + this.txPrepareInputsOutputs = new DogeTxInputsOutputs(this._settings, this._builderSettings) + this.txBuilder = new DogeTxBuilder(this._settings, this._builderSettings) + this._initedProviders = true + } +} diff --git a/crypto/blockchains/xvg/basic/XvgFindAddressFunction.js b/crypto/blockchains/xvg/basic/XvgFindAddressFunction.js new file mode 100644 index 000000000..adfd31015 --- /dev/null +++ b/crypto/blockchains/xvg/basic/XvgFindAddressFunction.js @@ -0,0 +1,95 @@ +/** + * @version 0.5 + * https://api.vergecurrency.network/node/api/XVG/mainnet/tx/abcda88bdb3968c5e444694ce3914cdec34f3afab73627bf201d34493d5e3aae/coins + * @param {string} address + * @param {string} tmp.inputs[].address + * @param {string} tmp.inputs[].value + * @param {string} tmp.outputs[].address + * @param {string} tmp.outputs[].value + * @returns {Promise<{from: string, to: string, value: number, direction: string}>} + * @constructor + */ +import BlocksoftUtils from '../../../common/BlocksoftUtils' +import BlocksoftBN from '../../../common/BlocksoftBN' + +export default async function XvgFindAddressFunction(address, tmp) { + + const inputMyBN = new BlocksoftBN(0) + const inputOthersBN = new BlocksoftBN(0) + const inputOthersAddresses = [] + const uniqueTmp = {} + + let input + for (input of tmp.inputs) { + if (input.address) { + const vinAddress = input.address + if (vinAddress === address) { + inputMyBN.add(input.value) + } else { + if (typeof uniqueTmp[vinAddress] === 'undefined') { + uniqueTmp[vinAddress] = 1 + inputOthersAddresses.push(vinAddress) + } + inputOthersBN.add(input.va) + } + } + } + + const outputMyBN = new BlocksoftBN(0) + const outputOthersBN = new BlocksoftBN(0) + const outputOthersAddresses = [] + const uniqueTmp2 = {} + + let output + for (output of tmp.outputs) { + if (output.address) { + const voutAddress = output.address + if (output.address === address) { + outputMyBN.add(output.value) + } else { + if (typeof uniqueTmp2[voutAddress] === 'undefined') { + uniqueTmp2[voutAddress] = 1 + outputOthersAddresses.push(voutAddress) + } + outputOthersBN.add(output.value) + } + } + } + + if (inputMyBN.get() === '0') { // my only in output + output = { + direction: 'income', + from: inputOthersAddresses.length > 0 ? inputOthersAddresses.join(',') : '', + to: '', // address, + value: outputMyBN.get() + } + } else if (outputMyBN.get() === '0') { // my only in input + output = { + direction: 'outcome', + from: '', // address, + to: outputOthersAddresses.length > 0 ? outputOthersAddresses.join(',') : '', + value: (inputOthersBN.get() === '0') ? outputOthersBN.get() : inputMyBN.get() + } + } else { // both input and output + if (outputOthersAddresses.length > 0) {// there are other address + output = { + direction: 'outcome', + from: '', // address, + to: outputOthersAddresses.join(','), + value: outputOthersBN.get() + } + } else { + output = { + direction: 'self', + from: '', // address, + to: '', // address, + value: inputMyBN.diff(outputMyBN).get() + } + } + } + output.from = output.from.substr(0, 255) + output.to = output.to.substr(0, 255) + + + return output +} diff --git a/crypto/blockchains/xvg/providers/XvgSendProvider.ts b/crypto/blockchains/xvg/providers/XvgSendProvider.ts new file mode 100644 index 000000000..58ffb0637 --- /dev/null +++ b/crypto/blockchains/xvg/providers/XvgSendProvider.ts @@ -0,0 +1,51 @@ +/** + * @version 0.20 + */ +import { BlocksoftBlockchainTypes } from '../../BlocksoftBlockchainTypes' +import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog' +import BlocksoftAxios from '../../../common/BlocksoftAxios' +import BlocksoftExternalSettings from '../../../common/BlocksoftExternalSettings' + +export default class XvgSendProvider implements BlocksoftBlockchainTypes.SendProvider { + + protected _settings: BlocksoftBlockchainTypes.CurrencySettings + + constructor(settings: BlocksoftBlockchainTypes.CurrencySettings, serverCode: string) { + this._settings = settings + } + + async sendTx(hex: string, subtitle: string): Promise<{ transactionHash: string, transactionJson: any }> { + const link = BlocksoftExternalSettings.getStatic('XVG_SEND_LINK') + BlocksoftCryptoLog.log(this._settings.currencyCode + ' XvgSendProvider.sendTx ' + subtitle + ' started ' + subtitle + ' ' + link) + let res + try { + res = await BlocksoftAxios.post(link, { rawTx: hex }) + BlocksoftCryptoLog.log(this._settings.currencyCode + ' XvgSendProvider.sendTx ' + subtitle + ' error ' + subtitle + ' ok ' + hex) + } catch (e) { + BlocksoftCryptoLog.log(this._settings.currencyCode + ' XvgSendProvider.sendTx ' + subtitle + ' error ' + subtitle + ' ' + e.message + ' ' + hex) + if (e.message.indexOf('mandatory-script-verify-flag-failed') !== -1) { + throw new Error('SERVER_RESPONSE_PLEASE_CHECK_SYSTEM_TIME') + } else if (e.message.indexOf('dust') !== -1) { + throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_DUST') + } else if (e.message.indexOf('missing inputs') !== -1) { + throw new Error('SERVER_RESPONSE_NO_RESPONSE') + } else if (e.message.indexOf('bad-txns-inputs-spent') !== -1) { + throw new Error('SERVER_RESPONSE_NO_RESPONSE') + } else if (e.message.indexOf('txn-mempool-conflict') !== -1) { + throw new Error('SERVER_RESPONSE_NO_RESPONSE') + } else if (e.message.indexOf('fee for relay') !== -1 || e.message.indexOf('insufficient priority') !== -1) { + throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_FEE') + } else if (e.message.indexOf('rejecting replacement') !== -1) { + throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_FEE_FOR_REPLACEMENT') + } else { + throw e + } + + } + if (typeof res.data.txid === 'undefined' || !res.data.txid) { + throw new Error('SERVER_RESPONSE_NOT_CONNECTED') + } + return { transactionHash: res.data.txid, transactionJson: {} } + } +} + diff --git a/crypto/blockchains/xvg/providers/XvgUnspentsProvider.ts b/crypto/blockchains/xvg/providers/XvgUnspentsProvider.ts new file mode 100644 index 000000000..695f8d101 --- /dev/null +++ b/crypto/blockchains/xvg/providers/XvgUnspentsProvider.ts @@ -0,0 +1,68 @@ +/** + * @version 0.20 + * https://api.vergecurrency.network/node/api/XVG/mainnet/address/DL5LtSf7wztH45VuYunL8oaQHtJbKLCHyw/txs/?unspent=true + */ +import { BlocksoftBlockchainTypes } from '../../BlocksoftBlockchainTypes' +import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog' +import BlocksoftAxios from '../../../common/BlocksoftAxios' + +export default class XvgUnspentsProvider implements BlocksoftBlockchainTypes.UnspentsProvider { + + _apiPath = 'https://api.vergecurrency.network/node/api/XVG/mainnet/address/' + + protected _settings: BlocksoftBlockchainTypes.CurrencySettings + + constructor(settings: BlocksoftBlockchainTypes.CurrencySettings, serverCode: string) { + this._settings = settings + } + + async getUnspents(address: string): Promise { + // @ts-ignore + BlocksoftCryptoLog.log(this._settings.currencyCode + ' XvgUnspentsProvider.getUnspents started', address) + + const link = this._apiPath + address + '/txs/?unspent=true' + const res = await BlocksoftAxios.getWithoutBraking(link) + BlocksoftCryptoLog.log(this._settings.currencyCode + ' XvgUnspentsProvider.getUnspents link', link) + if (!res || typeof res.data === 'undefined') { + BlocksoftCryptoLog.log(this._settings.currencyCode + ' XvgUnspentsProvider.getUnspents nothing loaded for address ' + address + ' link ' + link) + throw new Error('SERVER_RESPONSE_NOT_CONNECTED') + } + if (!res.data || typeof res.data[0] === 'undefined') { + return [] + } + const sortedUnspents = [] + /** + * https://api.vergecurrency.network/node/api/XVG/mainnet/address/DL5LtSf7wztH45VuYunL8oaQHtJbKLCHyw/txs/?unspent=true + * @param {*} res.data[] + * @param {string} res.data[]._id "5e0b42fb746f4c73717c1d1d" + * @param {string} res.data[].chain "XVG" + * @param {string} res.data[].network "mainnet" + * @param {string} res.data[].coinbase false + * @param {string} res.data[].mintIndex 1 + * @param {string} res.data[].spentTxid + * @param {string} res.data[].mintTxid "50aae03bec6662a277c6e03ff2c58a200912e1bb78519d8403354c66c4d51892" + * @param {string} res.data[].mintHeight 3715825 + * @param {string} res.data[].spentHeight + * @param {string} res.data[].address "DL5LtSf7wztH45VuYunL8oaQHtJbKLCHyw" + * @param {string} res.data[].script "76a914a3d43334ff9ea4c257a1796b63e4fa8330747d2e88ac" + * @param {string} res.data[].value 91523000 + * @param {string} res.data[].confirmations -1 + */ + const already = {} + let unspent + for (unspent of res.data) { + if (typeof already[unspent.mintTxid] === 'undefined' || already[unspent.mintTxid] > unspent.value) { + sortedUnspents.push({ + txid: unspent.mintTxid, + vout: unspent.mintIndex, + value: unspent.value.toString(), + height: unspent.mintHeight, + confirmations: 1, + isRequired: false + }) + already[unspent.mintTxid] = unspent.value + } + } + return sortedUnspents + } +} diff --git a/crypto/blockchains/xvg/stores/XvgTmpDS.js b/crypto/blockchains/xvg/stores/XvgTmpDS.js new file mode 100644 index 000000000..fa3465527 --- /dev/null +++ b/crypto/blockchains/xvg/stores/XvgTmpDS.js @@ -0,0 +1,49 @@ + +import Database from '@app/appstores/DataSource/Database'; + +const tableName = ' transactions_scanners_tmp' + +class XvgTmpDS { + /** + * @type {string} + * @private + */ + _currencyCode = 'XVG' + + async getCache(address) { + const res = await Database.query(` + SELECT tmp_key, tmp_sub_key, tmp_val + FROM ${tableName} + WHERE currency_code='${this._currencyCode}' + AND address='${address}' + AND (tmp_sub_key='coins' OR tmp_sub_key='data') + `) + const tmp = {} + if (res.array) { + let row + for (row of res.array) { + let val = 1 + if (row.tmp_sub_key !== 'data') { + val = JSON.parse(Database.unEscapeString(row.tmp_val)) + } + tmp[row.tmp_key + '_' + row.tmp_sub_key] = val + } + } + return tmp + } + + async saveCache(address, key, subKey, value) { + const now = new Date().toISOString() + const prepared = [{ + currency_code : this._currencyCode, + address : address, + tmp_key : key, + tmp_sub_key : subKey, + tmp_val : Database.escapeString(JSON.stringify(value)), + created_at : now + }] + await Database.setTableName(tableName).setInsertData({insertObjs : prepared}).insert() + } +} + +export default new XvgTmpDS() diff --git a/crypto/common/BlocksoftAxios.js b/crypto/common/BlocksoftAxios.js new file mode 100644 index 000000000..e453a4feb --- /dev/null +++ b/crypto/common/BlocksoftAxios.js @@ -0,0 +1,466 @@ +/** + * @version 0.43 + */ +import BlocksoftCryptoLog from './BlocksoftCryptoLog' + +import axios from 'axios' +import config from '@app/config/config' +import { showModal } from '@app/appstores/Stores/Modal/ModalActions' +import { strings } from '@app/services/i18n' + +import { Platform } from 'react-native' +import CookieManager from '@react-native-cookies/cookies' + +const CancelToken = axios && typeof axios.CancelToken !== 'undefined' ? axios.CancelToken : function() {} + +const CACHE_ERRORS_VALID_TIME = 60000 // 1 minute +const CACHE_ERRORS_BY_LINKS = {} + +const CACHE_STARTED = {} +const CACHE_STARTED_CANCEL = {} + +let CACHE_TIMEOUT_ERRORS = 0 +let CACHE_TIMEOUT_ERROR_SHOWN = 0 + +const TIMEOUT = 20000 +const TIMEOUT_TRUSTEE = 7000 +const TIMEOUT_TRIES_2 = 10000 +const TIMEOUT_TRIES_10 = 15000 +const TIMEOUT_TRIES_INTERNET = 5000 +const TIMEOUT_TRIES_RATES = 20000 + +class BlocksoftAxios { + + /** + * @param link + * @param maxTry + * @returns {Promise} + */ + async getWithoutBraking(link, maxTry = 5, timeOut = false) { + let tmp = false + try { + tmp = await this.get(link, false, false, timeOut) + CACHE_ERRORS_BY_LINKS[link] = { time: 0, tries: 0 } + } catch (e) { + const now = new Date().getTime() + if (typeof CACHE_ERRORS_BY_LINKS[link] === 'undefined') { + // first time + CACHE_ERRORS_BY_LINKS[link] = { time: now, tries: 1 } + } else if ( + now - CACHE_ERRORS_BY_LINKS[link].time < CACHE_ERRORS_VALID_TIME + ) { + // no plus as too fast + } else if ( + CACHE_ERRORS_BY_LINKS[link].tries < maxTry + ) { + // plus as time passed + CACHE_ERRORS_BY_LINKS[link].tries++ + CACHE_ERRORS_BY_LINKS[link].time = now + } else { + // only here will error actual + e.code = 'ERROR_PROVIDER' + CACHE_ERRORS_BY_LINKS[link].time = now + throw e + } + BlocksoftCryptoLog.log('BlocksoftAxios.getWithoutBraking try ' + JSON.stringify(CACHE_ERRORS_BY_LINKS[link]) + ' error ' + e.message.substr(0, 300)) + } + + return tmp + } + + async postWithoutBraking(link, data, maxTry = 5) { + let tmp = false + try { + tmp = await this.post(link, data, false) + CACHE_ERRORS_BY_LINKS[link] = { time: 0, tries: 0 } + } catch (e) { + const now = new Date().getTime() + if (typeof CACHE_ERRORS_BY_LINKS[link] === 'undefined') { + // first time + CACHE_ERRORS_BY_LINKS[link] = { time: now, tries: 1 } + } else if ( + now - CACHE_ERRORS_BY_LINKS[link].time < CACHE_ERRORS_VALID_TIME + ) { + // no plus as too fast + } else if ( + CACHE_ERRORS_BY_LINKS[link].tries < maxTry + ) { + // plus as time passed + CACHE_ERRORS_BY_LINKS[link].tries++ + CACHE_ERRORS_BY_LINKS[link].time = now + } else { + // only here will error actual + e.code = 'ERROR_PROVIDER' + CACHE_ERRORS_BY_LINKS[link].time = now + throw e + } + BlocksoftCryptoLog.log('BlocksoftAxios.postWithoutBraking try ' + JSON.stringify(CACHE_ERRORS_BY_LINKS[link]) + ' error ' + e.message.substr(0, 200)) + } + return tmp + } + + async postWithHeaders(link, data, addHeaders, errSend = true, timeOut = false) { + let tmp = false + try { + const headers = { + 'upgrade-insecure-requests': 1, + 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.128 Safari/537.36' + } + const dataPrep = JSON.stringify(data) + headers['Content-Type'] = 'application/json' + headers['Accept'] = 'application/json' + for (let key in addHeaders) { + headers[key] = addHeaders[key] + } + + const tmpInner = await fetch(link, { + method: 'POST', + credentials: 'same-origin', + mode: 'same-origin', + redirect: 'follow', + headers, + body: dataPrep + }) + if (tmpInner.status !== 200 && tmpInner.status !== 201 && tmpInner.status !== 202) { + BlocksoftCryptoLog.log('BlocksoftAxios.post fetch result ' + JSON.stringify(tmpInner)) + } else { + tmp = { data: await tmpInner.json() } + } + } catch (e) { + BlocksoftCryptoLog.log('BlocksoftAxios.postWithHeaders fetch result error ' + e.message) + } + return tmp + } + + async getWithHeaders(link, addHeaders, errSend = true, timeOut = false) { + let tmp = false + try { + const headers = { + 'upgrade-insecure-requests': 1, + 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.128 Safari/537.36' + } + headers['Content-Type'] = 'application/json' + headers['Accept'] = 'application/json' + for (let key in addHeaders) { + headers[key] = addHeaders[key] + } + + const tmpInner = await fetch(link, { + method: 'GET', + credentials: 'same-origin', + mode: 'same-origin', + redirect: 'follow', + headers + }) + if (tmpInner.status !== 200 && tmpInner.status !== 201 && tmpInner.status !== 202) { + BlocksoftCryptoLog.log('BlocksoftAxios.get fetch result ' + JSON.stringify(tmpInner)) + } else { + tmp = { data: await tmpInner.json() } + } + } catch (e) { + console.log('BlocksoftAxios.getWithHeaders fetch result error ' + e.message) + } + return tmp + } + + async post(link, data, errSend = true, timeOut = false) { + let tmp = false + let doOld = this._isTrustee(link) + if (!doOld) { + try { + await this._cookie(link, 'POST') + let dataPrep + const headers = { + 'upgrade-insecure-requests': 1, + 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.128 Safari/537.36' + } + if (typeof data === 'object') { + dataPrep = JSON.stringify(data) + headers['Content-Type'] = 'application/json' + headers['Accept'] = 'application/json' + } else { + dataPrep = data + headers['Content-Type'] = 'multipart/form-data' + headers['Accept'] = 'multipart/form-data' + } + + const tmpInner = await fetch(link, { + method: 'POST', + credentials: 'same-origin', + mode: 'same-origin', + redirect: 'follow', + headers, + body: dataPrep + }) + if (tmpInner.status !== 200 && tmpInner.status !== 201 && tmpInner.status !== 202) { + BlocksoftCryptoLog.log('BlocksoftAxios.post fetch result ' + JSON.stringify(tmpInner)) + doOld = true + } else { + tmp = { data: await tmpInner.json() } + } + } catch (e) { + BlocksoftCryptoLog.log('BlocksoftAxios.post fetch result error ' + e.message) + doOld = true + } + } + if (doOld) { + tmp = this._request(link, 'post', data, false, errSend, timeOut) + } + return tmp + } + + async get(link, emptyIsBad = false, errSend = true, timeOut = false) { + let tmp = false + let doOld = this._isTrustee(link) + if (!doOld) { + try { + await this._cookie(link, 'GET') + let tryOneMore = false + let antiCycle = 0 + do { + const tmpInner = await fetch(link, { + method: 'GET', + credentials: 'same-origin', + mode: 'same-origin', + redirect: 'follow', + headers: { + 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.128 Safari/537.36' + } + }) + if (tmpInner.status !== 200) { + doOld = true + if (typeof tmpInner.headers.map['set-cookie'] !== 'undefined') { + tryOneMore = true + } + } else { + tmp = { data: await tmpInner.json() } + doOld = false + tryOneMore = false + } + antiCycle++ + } while (tryOneMore && antiCycle < 3) + } catch (e) { + BlocksoftCryptoLog.log('BlocksoftAxios.get fetch result error ' + e.message) + doOld = true + } + } + if (doOld) { + tmp = this._request(link, 'get', {}, emptyIsBad, errSend, timeOut) + } + return tmp + } + + _isTrustee(link) { + const tmp = link.split('/') + const domain = tmp[0] + '/' + tmp[1] + '/' + tmp[2] + return !(domain.indexOf('trustee') === -1) + } + + async _cookie(link, method) { + const tmp = link.split('/') + const domain = tmp[0] + '/' + tmp[1] + '/' + tmp[2] + const domainShort = tmp[2] + if (domain.indexOf('trustee') !== -1) { + return false + } + const oldCookieObj = await CookieManager.get(link) + const oldCookie = JSON.stringify(oldCookieObj) + if (oldCookie === '{}') return false + + // await CookieManager.clearAll() + if (Platform.OS === 'ios') { + await CookieManager.clearByName(domain) + await CookieManager.clearByName(link) + } else { + for (const key in oldCookieObj) { + const newData = { + name: key, + value: 'none', + domain: '.' + domainShort, + path: '/', + version: '1', + expires: new Date(new Date().getTime() + 24 * 360000).toISOString() + } + await CookieManager.set(link, newData) + } + } + } + + + async _request(link, method = 'get', data = {}, emptyIsBad = false, errSend = true, timeOut = false) { + let tmp + let cacheMD = link + if (typeof data !== 'undefined') { + cacheMD += ' ' + JSON.stringify(data) + } + try { + // noinspection JSUnresolvedFunction + const instance = axios.create() + + const cancelSource = CancelToken.source() + + if (!timeOut || typeof timeOut === 'undefined') { + timeOut = TIMEOUT + if (this._isTrustee(link)) { + timeOut = TIMEOUT_TRUSTEE + } + if (typeof CACHE_ERRORS_BY_LINKS[link] !== 'undefined') { + if (CACHE_ERRORS_BY_LINKS[link].tries > 2) { + timeOut = Math.round(TIMEOUT_TRIES_10) + } else { + timeOut = Math.round(TIMEOUT_TRIES_2) + } + } + if (link.indexOf('/fees') !== -1 || link.indexOf('/rates') !== -1) { + timeOut = Math.round(TIMEOUT_TRIES_RATES) + } else if (link.indexOf('/internet') !== -1) { + timeOut = Math.round(TIMEOUT_TRIES_INTERNET) + } + if (link.indexOf('solana.trusteeglobal.com') !== -1) { + timeOut = timeOut * 10 + } + } + + if (typeof CACHE_STARTED[cacheMD] !== 'undefined') { + const now = new Date().getTime() + const timeMsg = ' timeout ' + CACHE_STARTED[cacheMD].timeOut + ' started ' + CACHE_STARTED[cacheMD].time + ' diff ' + (now - CACHE_STARTED[cacheMD].time) + BlocksoftCryptoLog.log('PREV CALL WILL BE CANCELED ' + timeMsg) + await CACHE_STARTED_CANCEL[cacheMD].cancel('PREV CALL CANCELED ' + timeMsg) + } + instance.defaults.timeout = timeOut + instance.defaults.cancelToken = cancelSource.token + CACHE_STARTED[cacheMD] = { time: new Date().getTime(), timeOut } + CACHE_STARTED_CANCEL[cacheMD] = cancelSource + + const tmpTimer = setTimeout(() => { + cancelSource.cancel('TIMEOUT CANCELED ' + timeOut + ' ' + link + ' ' + JSON.stringify(data)) + }, timeOut) + if (method === 'get') { + tmp = await instance.get(link) + } else { + tmp = await instance.post(link, data) + } + clearTimeout(tmpTimer) + + if (emptyIsBad && (tmp.status !== 200 || !tmp.data)) { + // noinspection ExceptionCaughtLocallyJS + throw new Error('BlocksoftAxios.' + method + ' ' + link + ' status: ' + tmp.status + ' data: ' + tmp.data) + } + + if (typeof CACHE_STARTED[cacheMD] !== 'undefined') { + delete CACHE_STARTED[cacheMD] + delete CACHE_STARTED_CANCEL[cacheMD] + } + /* let txt = tmp.data + if (typeof txt === 'string') { + const newTxt = txt.split(' 1) { + txt = newTxt[1].substr(0, 600) + } + } else { + txt = JSON.stringify(tmp.data).substr(0, 300) + } + if (txt.length > 100) { + BlocksoftCryptoLog.log('BlocksoftAxios.' + method + ' finish ' + link, txt) // separate line for txt + } else { + BlocksoftCryptoLog.log('BlocksoftAxios.' + method + ' finish ' + link + ' ' + JSON.stringify(txt)) + } + */ + + CACHE_TIMEOUT_ERRORS = 0 + } catch (e) { + + if (typeof CACHE_STARTED[cacheMD] !== 'undefined') { + delete CACHE_STARTED[cacheMD] + } + let subdata = {} + if (typeof e.response === 'undefined' || typeof e.response.data === 'undefined') { + // do nothing + } else if (e.response.data) { + e.message = JSON.stringify(e.response.data) + ' ' + e.message + subdata = e.response.data + } + + const customError = new Error(link + ' ' + e.message.toLowerCase()) + customError.subdata = subdata + + if (config.debug.appErrors) { + // console.log('BlocksoftAxios._request ' + link + ' data ' + JSON.stringify(data) , e) + } + + if (e.message.indexOf('Network Error') !== -1 + || e.message.indexOf('network error') !== -1 + || e.message.indexOf('timeout') !== -1 + || e.message.indexOf('access is denied') !== -1 + || e.message.indexOf('500 internal') !== -1 + || e.message.indexOf('502') !== -1 + || e.message.indexOf('bad gateway') !== -1 + || e.message.indexOf('503 backend') !== -1 + || e.message.indexOf('error 503') !== -1 + || e.message.indexOf('504 gateway') !== -1 + || e.message.indexOf('server error') !== -1 + || e.message.indexOf('forbidden') !== -1 + || e.message.indexOf('unavailable') !== -1 + || e.message.indexOf('rate limit') !== -1 + || e.message.indexOf('offline') !== -1 + || e.message.indexOf('status code 500') !== -1 + ) { + if (link.indexOf('trustee.deals') !== -1) { + // noinspection ES6MissingAwait + BlocksoftCryptoLog.log('BlocksoftAxios.' + method + ' ' + link + ' NOTICE INNER CONNECTION ' + e.message) + } else { + if (e.message.indexOf('timeout') !== -1) { + CACHE_TIMEOUT_ERRORS++ + let now = new Date().getTime() + if (CACHE_TIMEOUT_ERRORS > 10 && (now - CACHE_TIMEOUT_ERROR_SHOWN) > 60000) { + CACHE_TIMEOUT_ERROR_SHOWN = now + showModal({ + type: 'INFO_MODAL', + icon: null, + title: strings('modal.exchange.sorry'), + description: strings('toast.badInternet') + }) + } + } + // noinspection ES6MissingAwait + BlocksoftCryptoLog.log('BlocksoftAxios.' + method + ' ' + link + ' NOTICE OUTER CONNECTION ' + e.message) + } + customError.code = 'ERROR_NOTICE' + } else if (e.message.indexOf('request failed with status code 525') !== -1 + || e.message.indexOf('api calls limits have been reached') !== -1 + || e.message.indexOf('loudflare') !== -1) { + // noinspection ES6MissingAwait + BlocksoftCryptoLog.log('BlocksoftAxios.' + method + ' ' + link + ' NOTICE TOO MUCH ' + e.message) + customError.code = 'ERROR_NOTICE' + } else if (link.indexOf('/api/v2/sendtx/') !== -1) { + // noinspection ES6MissingAwait + BlocksoftCryptoLog.log('BlocksoftAxios.' + method + ' ' + link + ' ' + e.message + ' GET EXTERNAL LINK ERROR1 ') + customError.code = 'ERROR_NOTICE' + } else if (e.message.indexOf('account not found') !== -1) { + // noinspection ES6MissingAwait + BlocksoftCryptoLog.log('BlocksoftAxios.' + method + ' ' + link + ' ' + e.message) // just nothing found + return false + } else if (errSend) { + // noinspection ES6MissingAwait + if (e.message.indexOf('PREV CALL CANCELED') === -1) { + if (link.indexOf('trustee.deals') !== -1 || link.indexOf('https://api.mainnet-beta.solana.com') !== -1) { + BlocksoftCryptoLog.log('BlocksoftAxios.' + method + ' ' + link + ' ' + e.message + ' GET EXTERNAL LINK ERROR3 ' + JSON.stringify(data)) + } else { + BlocksoftCryptoLog.err('BlocksoftAxios.' + method + ' ' + link + ' ' + e.message + ' GET EXTERNAL LINK ERROR2') + } + } else { + BlocksoftCryptoLog.log('BlocksoftAxios.' + method + ' ' + link + ' ' + e.message + ' GET EXTERNAL LINK ERROR4') + } + customError.code = 'ERROR_SYSTEM' + } else { + BlocksoftCryptoLog.log('BlocksoftAxios.' + method + ' ' + link + ' ' + e.message + ' GET EXTERNAL LINK NOTICE') + customError.code = 'ERROR_SYSTEM' + } + + throw customError + } + return tmp + } +} + +export default new BlocksoftAxios() diff --git a/crypto/common/BlocksoftBN.js b/crypto/common/BlocksoftBN.js new file mode 100644 index 000000000..6d5a84029 --- /dev/null +++ b/crypto/common/BlocksoftBN.js @@ -0,0 +1,98 @@ +import {BigNumber} from 'bignumber.js' +import BlocksoftUtils from './BlocksoftUtils' + +class BlocksoftBN { + + innerBN = false + + constructor(val) { + // console.log('BlocksoftBN construct', JSON.stringify(val)) + if (typeof val.innerBN !== 'undefined') { + try { + // noinspection JSCheckFunctionSignatures,JSUnresolvedVariable + this.innerBN = new BigNumber(val.innerBN.toString()) + } catch (e) { + throw new Error(e.message + ' while BlocksoftBN.constructor ' + val) + } + } else { + try { + // noinspection JSCheckFunctionSignatures,JSUnresolvedVariable + this.innerBN = new BigNumber(val) + } catch (e) { + throw new Error(e.message + ' while BlocksoftBN.constructor ' + val) + } + } + } + + + get() { + return this.innerBN.toString() + } + + toString() { + return this.innerBN.toString() + } + + lessThanZero() { + return this.innerBN.toString().indexOf('-') === 0 + } + + add(val) { + // console.log('BlocksoftBN add ', JSON.stringify(val)) + if (typeof val === 'undefined' || !val || val.toString() === '0' || val === 'null' || val === 'false') { + return this + } + let val2 + if (typeof val !== 'string' && typeof val !== 'number') { + if (typeof val.innerBN !== 'undefined') { + val2 = val.innerBN + } else { + throw new Error('BlocksoftBN.add unsupported type ' + (typeof val) + ' ' + JSON.stringify(val)) + } + } else { + try { + val = BlocksoftUtils.fromENumber(val) + val2 = BigNumber(val) + } catch (e) { + throw new Error(e.message + ' while BlocksoftBN.add transform ' + val) + } + } + try { + this.innerBN = this.innerBN.plus(val2) + } catch (e) { + throw new Error(e.message + ' while BlocksoftBN.add ' + val) + } + return this + } + + diff(val) { + // console.log('BlocksoftBN diff ', JSON.stringify(val)) + if (typeof val === 'undefined' || !val || val.toString() === '0') { + return this + } + let val2 + if (typeof val !== 'string' && typeof val !== 'number') { + if (typeof val.innerBN !== 'undefined') { + val2 = val.innerBN + } else { + throw new Error('BlocksoftBN.diff unsupported type ' + (typeof val) + ' ' + JSON.stringify(val)) + } + } else { + try { + val = BlocksoftUtils.fromENumber(val) + val2 = BigNumber(val) + } catch (e) { + throw new Error(e.message + ' while BlocksoftBN.diff transform ' + val) + } + } + try { + this.innerBN = this.innerBN.minus(val2) + } catch (e) { + throw new Error(e.message + ' while BlocksoftBN.minus ' + val) + } + return this + } + +} + +export default BlocksoftBN diff --git a/crypto/common/BlocksoftCryptoLog.js b/crypto/common/BlocksoftCryptoLog.js new file mode 100644 index 000000000..fb657acd7 --- /dev/null +++ b/crypto/common/BlocksoftCryptoLog.js @@ -0,0 +1,160 @@ +/** + * Separated log class for crypto module - could be encoded here later + * @version 0.9 + */ +import crashlytics from '@react-native-firebase/crashlytics' + +import BlocksoftExternalSettings from './BlocksoftExternalSettings' + +import config from '@app/config/config' +import { FileSystem } from '@app/services/FileSystem/FileSystem' +import MarketingEvent from '@app/services/Marketing/MarketingEvent' +import settingsActions from '@app/appstores/Stores/Settings/SettingsActions' + +const DEBUG = config.debug.cryptoLogs // set true to see usual logs in console + +const MAX_MESSAGE = 2000 +const FULL_MAX_MESSAGE = 20000 + +let LOGS_TXT = '' +let FULL_LOGS_TXT = '' + +class BlocksoftCryptoLog { + + constructor() { + this.FS = new FileSystem({ fileEncoding: 'utf8', fileName: 'CryptoLog', fileExtension: 'txt' }) + + this.DATA = {} + this.DATA.LOG_VERSION = false + } + + async _reinitTgMessage(testerMode, obj, msg) { + + for (const key in obj) { + this.DATA[key] = obj[key] + } + + // noinspection JSIgnoredPromiseFromCall + await this.FS.checkOverflow() + } + + async log(txtOrObj, txtOrObj2 = false, txtOrObj3 = false) { + if (settingsActions.getSettingStatic('loggingCode') === 'none') { + return + } + let line = new Date().toISOString().replace(/T/, ' ').replace(/\..+/, '') + let line2 = '' + if (txtOrObj && typeof txtOrObj !== 'undefined') { + if (typeof txtOrObj === 'string') { + line += ' ' + txtOrObj + } else { + line += ' ' + JSON.stringify(txtOrObj, null, '\t') + } + } + if (txtOrObj2 && typeof txtOrObj2 !== 'undefined') { + if (typeof txtOrObj2 === 'string') { + line += '\n\t\t\t\t\t' + txtOrObj2 + } else if (txtOrObj2 === {}) { + line += ' {} ' + } else if (typeof txtOrObj2.sourceURL === 'undefined') { + line += '\n\t\t\t\t\t' + JSON.stringify(txtOrObj2, null, '\t\t\t\t\t') + } + } + + if (DEBUG) { + console.log('CRYPTO ' + line) + } + + if (!config.debug.cryptoErrors && config.debug.firebaseLogs) { + crashlytics().log(line) + } + await this.FS.writeLine(line) + + if (txtOrObj3 && typeof txtOrObj3 !== 'undefined') { + if (typeof txtOrObj3 === 'string') { + line2 += '\t\t\t\t\t' + txtOrObj3 + } else { + line2 += '\t\t\t\t\t' + JSON.stringify(txtOrObj3, null, '\t\t\t\t\t') + } + if (!config.debug.cryptoErrors && config.debug.firebaseLogs) { + crashlytics().log('\n', line2) + } + await this.FS.writeLine(line2) + } + + LOGS_TXT = line + line2 + '\n' + LOGS_TXT + if (LOGS_TXT.length > MAX_MESSAGE) { + LOGS_TXT = LOGS_TXT.substr(0, MAX_MESSAGE) + '...' + } + + FULL_LOGS_TXT = line + line2 + '\n' + FULL_LOGS_TXT + if (FULL_LOGS_TXT.length > FULL_MAX_MESSAGE) { + FULL_LOGS_TXT = LOGS_TXT.substr(0, FULL_MAX_MESSAGE) + '...' + } + + return true + } + + async err(errorObjectOrText, errorObject2 = '', errorTitle = 'ERROR') { + const now = new Date() + const date = now.toISOString().replace(/T/, ' ').replace(/\..+/, '') + let line = '' + if (errorObjectOrText && typeof errorObjectOrText !== 'undefined') { + if (typeof errorObjectOrText === 'string') { + line += ' ' + errorObjectOrText + } else if (typeof errorObjectOrText.code !== 'undefined') { + line += ' ' + errorObjectOrText.code + ' ' + errorObjectOrText.message + } else { + line += ' ' + errorObjectOrText.message + } + } + + if (errorObject2 && typeof errorObject2 !== 'undefined' && errorObject2 !== '' && typeof errorObject2.message !== 'undefined') { + line += ' ' + errorObject2.message + } + + if (config.debug.cryptoErrors || DEBUG) { + console.log('==========CRPT ' + errorTitle + '==========') + console.log(date + line) + if (errorObject2) { + console.log('error', errorObject2) + } + return false + } + + await this.log(errorObjectOrText, errorObject2) + + LOGS_TXT = '\n\n\n\n==========' + errorTitle + '==========\n\n\n\n' + LOGS_TXT + // noinspection JSUnresolvedFunction + if (!config.debug.cryptoErrors) { + crashlytics().log('==========' + errorTitle + '==========') + } + // noinspection ES6MissingAwait + await this.FS.writeLine('==========' + errorTitle + '==========') + + + if (errorObject2 && typeof errorObject2.code !== 'undefined' && errorObject2.code === 'ERROR_USER') { + return true + } + + try { + await this.FS.writeLine('CRPT_2021_02 ' + line) + if (!config.debug.cryptoErrors) { + const e = new Error('CRPT_2021_02 ' + line) + if (typeof crashlytics().recordError !== 'undefined') { + crashlytics().recordError(e) + } else { + crashlytics().crash() + } + MarketingEvent.reinitCrashlytics() + } + } catch (firebaseError) { + + } + + return true + } + +} + +export default new BlocksoftCryptoLog() diff --git a/crypto/common/BlocksoftCryptoUtils.js b/crypto/common/BlocksoftCryptoUtils.js new file mode 100644 index 000000000..9a7a808cc --- /dev/null +++ b/crypto/common/BlocksoftCryptoUtils.js @@ -0,0 +1,11 @@ +const createHash = require('create-hash') + +class BlocksoftCryptoUtils { + + static sha256(stringHex) { + return createHash('sha256').update(stringHex, 'hex').digest('hex') + } + +} + +export default BlocksoftCryptoUtils diff --git a/crypto/common/BlocksoftCustomLinks.js b/crypto/common/BlocksoftCustomLinks.js new file mode 100644 index 000000000..4e8f2655d --- /dev/null +++ b/crypto/common/BlocksoftCustomLinks.js @@ -0,0 +1,29 @@ +/** + * @version 0.54 + * @author Vadym + */ +import { ThemeContext } from '@app/theme/ThemeProvider' +import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings' +import { sublocale } from '@app/services/i18n' + + +class BlocksoftCustomLinks { + getLink(link, isLight) { + const themeColor = isLight ? 'light' : 'dark' + const siteLang = sublocale() + const paramsLink = `themeColor=${themeColor}&siteLang=${siteLang}` + const subLink = BlocksoftExternalSettings.getStatic(link) + + if (subLink.includes('?')) { + return `${subLink}&${paramsLink}` + } else { + return `${subLink}?${paramsLink}` + } + } +} + +export default new BlocksoftCustomLinks() + +BlocksoftCustomLinks.contextType = ThemeContext + + diff --git a/crypto/common/BlocksoftDict.js b/crypto/common/BlocksoftDict.js new file mode 100644 index 000000000..f1a667fa5 --- /dev/null +++ b/crypto/common/BlocksoftDict.js @@ -0,0 +1,215 @@ +import { NativeModules } from 'react-native' + +import Database from '@app/appstores/DataSource/Database' + +import CoinBlocksoftDict from '@crypto/assets/coinBlocksoftDict.json' + +const { RNFastCrypto } = NativeModules + +const VisibleCodes = [ + 'CASHBACK', 'NFT', 'BTC', 'ETH', 'TRX', 'TRX_USDT' // add code here to show on start screen +] +const Codes = [ + 'CASHBACK', 'NFT', 'BTC', 'ETH', 'USDT', 'LTC', 'ETH_USDT', 'TRX', 'TRX_USDT', 'BNB', 'BNB_SMART', 'MATIC', 'ETH_TRUE_USD', 'ETH_BNB', 'ETH_USDC', 'ETH_PAX', 'ETH_DAI', 'FIO' // add code here for autocreation the wallet address with the currency +] +const Currencies = CoinBlocksoftDict + +const CurrenciesForTests = { + 'BTC_SEGWIT': { + currencyName: 'Bitcoin Segwit', + currencyCode: 'BTC_SEGWIT', + currencySymbol: 'BTC', + addressProcessor: 'BTC_SEGWIT', + extendsProcessor: 'BTC', + ratesCurrencyCode: 'BTC', + addressPrefix: 'bc1', + defaultPath: `m/84'/0'/0'/0/0` + }, + 'BTC_SEGWIT_COMPATIBLE': { + currencyName: 'Bitcoin Compatible Segwit', + currencyCode: 'BTC_SEGWIT_COMPATIBLE', + currencySymbol: 'BTC', + addressProcessor: 'BTC_SEGWIT_COMPATIBLE', + extendsProcessor: 'BTC', + ratesCurrencyCode: 'BTC', + addressPrefix: '3', + defaultPath: `m/49'/0'/0'/0/1` + }, + 'LTC_SEGWIT': { + currencyName: 'Bitcoin Segwit', + currencyCode: 'LTC_SEGWIT', + currencySymbol: 'LTC', + addressProcessor: 'LTC_SEGWIT', + extendsProcessor: 'LTC', + ratesCurrencyCode: 'LTC', + addressPrefix: 'ltc', + defaultPath: `m/84'/2'/0'/0/0` + }, + 'ETH_ROPSTEN_KSU_TOKEN': { + currencyName: 'Some ERC-20 Ropsten', + currencyCode: 'ETH_ROPSTEN_KSU_TOKEN', + currencySymbol: 'Some ERC-20 Ropsten', + extendsProcessor: 'ETH_TRUE_USD', + addressCurrencyCode: 'ETH_ROPSTEN', + feesCurrencyCode: 'ETH_ROPSTEN', + network: 'ropsten', + decimals: 6, + tokenAddress: '0xdb30610f156e1d4aefaa9b4423909297ceff64c2', + currencyExplorerLink: 'https:ropsten.etherscan.io/address/', + currencyExplorerTxLink: 'https:ropsten.etherscan.io/tx/' + }, + 'TRX_PLANET': { + currencyName: 'PLANET (FREE TRX TOKEN)', + currencyCode: 'TRX_PLANET', + currencySymbol: 'PLANET TRX', + extendsProcessor: 'TRX_USDT', + feesCurrencyCode: 'TRX', // pay for tx in other currency, if no - used currencyCode + network: 'trx', // network also used as mark of rate scanning + decimals: 6, + tokenName: '1002742', + currencyExplorerLink: 'https://tronscan.org/#/address/', + currencyExplorerTxLink: 'https://tronscan.org/#/transaction/' + } +} + +if (typeof RNFastCrypto === 'undefined') { + delete Currencies['XMR'] +} + +/** + * @param {int} currencyObject.id + * @param {int} currencyObject.isHidden + * @param {string} currencyObject.tokenJson + * @param {string} currencyObject.tokenDecimals 18 + * @param {string} currencyObject.tokenAddress '0xd26114cd6EE289AccF82350c8d8487fedB8A0C07' + * @param {string} currencyObject.tokenType 'ETH_ERC_20' + * @param {string} currencyObject.currencyName 'OMG Token' + * @param {string} currencyObject.currencySymbol 'OMG' + * @param {string} currencyObject.currencyCode 'OMG' + */ +function addAndUnifyCustomCurrency(currencyObject) { + const tmp = { + currencyName: currencyObject.currencyName, + currencyCode: 'CUSTOM_' + currencyObject.currencyCode, + currencySymbol: currencyObject.currencySymbol, + ratesCurrencyCode: currencyObject.currencyCode, + decimals: currencyObject.tokenDecimals + + } + tmp.currencyType = 'custom' + if (currencyObject.tokenType === 'BNB_SMART_20') { + tmp.currencyCode = 'CUSTOM_BNB_SMART_20_' + currencyObject.currencyCode + if (tmp.ratesCurrencyCode.substr(0, 1) === 'B') { + const subRate = tmp.ratesCurrencyCode.substr(1) + if (typeof Currencies[subRate] !== 'undefined') { + tmp.ratesCurrencyCode = subRate + } + } + tmp.extendsProcessor = 'BNB_SMART_CAKE' + tmp.addressUiChecker = 'ETH' + tmp.tokenAddress = currencyObject.tokenAddress + tmp.tokenBlockchain = 'BNB' + tmp.currencyExplorerLink = 'https://bscscan.com/token/' + currencyObject.tokenAddress + '?a=' + } else if (currencyObject.tokenType === 'MATIC_ERC_20') { + tmp.currencyCode = 'CUSTOM_MATIC_ERC_20_' + currencyObject.currencyCode + tmp.extendsProcessor = 'MATIC_USDT' + tmp.addressUiChecker = 'ETH' + tmp.tokenAddress = currencyObject.tokenAddress + tmp.tokenBlockchain = 'MATIC' + tmp.currencyExplorerLink = 'https://polygonscan.com/token/' + currencyObject.tokenAddress + '?a=' + } else if (currencyObject.tokenType === 'FTM_ERC_20') { + tmp.currencyCode = 'CUSTOM_FTM_ERC_20_' + currencyObject.currencyCode + tmp.extendsProcessor = 'FTM_USDC' + tmp.addressUiChecker = 'ETH' + tmp.tokenAddress = currencyObject.tokenAddress + tmp.tokenBlockchain = 'FTM' + tmp.currencyExplorerLink = 'https://ftmscan.com/token/' + currencyObject.tokenAddress + '?a=' + } else if (currencyObject.tokenType === 'VLX_ERC_20') { + tmp.currencyCode = 'CUSTOM_VLX_ERC_20_' + currencyObject.currencyCode + tmp.extendsProcessor = 'VLX_USDT' + tmp.addressUiChecker = 'ETH' + tmp.tokenAddress = currencyObject.tokenAddress + tmp.tokenBlockchain = 'VLX' + tmp.currencyExplorerLink = 'https://evmexplorer.velas.com/tokens/' + currencyObject.tokenAddress + } else if (currencyObject.tokenType === 'ONE_ERC_20') { + tmp.currencyCode = 'CUSTOM_ONE_ERC_20_' + currencyObject.currencyCode + tmp.extendsProcessor = 'ONE_USDC' + tmp.addressUiChecker = 'ETH' + tmp.tokenAddress = currencyObject.tokenAddress + tmp.tokenBlockchain = 'ONE' + tmp.currencyExplorerLink = 'https://explorer.harmony.one/address/' + currencyObject.tokenAddress + } else if (currencyObject.tokenType === 'SOL') { + tmp.currencyCode = 'CUSTOM_SOL_' + currencyObject.currencyCode + tmp.extendsProcessor = 'SOL_RAY' + tmp.addressUiChecker = 'SOL' + tmp.tokenAddress = currencyObject.tokenAddress + tmp.tokenBlockchain = 'SOLANA' + } else if (currencyObject.tokenType === 'ETH_ERC_20') { + tmp.extendsProcessor = 'ETH_TRUE_USD' + tmp.addressUiChecker = 'ETH' + tmp.tokenAddress = currencyObject.tokenAddress + tmp.tokenBlockchain = 'ETHEREUM' + tmp.currencyExplorerLink = 'https://etherscan.io/token/' + currencyObject.tokenAddress + '?a=' + } else if (currencyObject.tokenType === 'TRX') { + tmp.currencyCode = 'CUSTOM_TRX_' + currencyObject.currencyCode + tmp.extendsProcessor = 'TRX_USDT' + tmp.addressUiChecker = 'TRX' + tmp.currencyIcon = 'TRX' + tmp.tokenName = currencyObject.tokenAddress + tmp.tokenBlockchain = 'TRON' + tmp.currencyExplorerLink = 'https://tronscan.org/#/address/' + tmp.currencyExplorerTxLink = 'https://tronscan.org/#/transaction/' + if (tmp.tokenName.substr(0, 1) !== 'T') { + this.skipParentBalanceCheck = true + } + } else { + return false + } + + Currencies[tmp.currencyCode] = tmp + return tmp +} + +const ALL_SETTINGS = {} + +function getCurrencyAllSettings(currencyCodeOrObject, source = '') { + let currencyCode = currencyCodeOrObject + if (typeof currencyCode === 'undefined' || !currencyCode) { + return false + } + if (currencyCode === 'ETH_LAND') { + Database.query(`DELETE FROM account WHERE currency_code='ETH_LAND'`) + Database.query(`DELETE FROM account_balance WHERE currency_code='ETH_LAND'`) + Database.query(`DELETE FROM currency WHERE currency_code='ETH_LAND'`) + } + + if (typeof currencyCodeOrObject.currencyCode !== 'undefined') { + currencyCode = currencyCodeOrObject.currencyCode + } + if (typeof ALL_SETTINGS[currencyCode] !== 'undefined') { + return ALL_SETTINGS[currencyCode] + } + + let settings = Currencies[currencyCode] + if (!settings) { + settings = CurrenciesForTests[currencyCode] + } + if (!settings) { + throw new Error('Currency code not found in dict ' + JSON.stringify(currencyCode) + ' from ' + source) + } + if (settings.extendsProcessor && Currencies[settings.extendsProcessor]) { + const settingsParent = Currencies[settings.extendsProcessor] + let newKey + for (newKey of Object.keys(settingsParent)) { + if (typeof settings[newKey] === 'undefined' || settings[newKey] === false) { + settings[newKey] = settingsParent[newKey] + } + } + } + ALL_SETTINGS[currencyCode] = settings + return settings +} + +export default { + VisibleCodes, Codes, Currencies, CurrenciesForTests, getCurrencyAllSettings, addAndUnifyCustomCurrency +} diff --git a/crypto/common/BlocksoftDictNfts.js b/crypto/common/BlocksoftDictNfts.js new file mode 100644 index 000000000..78d9741f9 --- /dev/null +++ b/crypto/common/BlocksoftDictNfts.js @@ -0,0 +1,119 @@ +const Nfts = [ + { + currencyCode: 'NFT_ETH', + currencyName: 'NFT', + currencySymbol: 'NFT', + tokenBlockchain: 'ETHEREUM', + tokenBlockchainCode: 'ETH', + currencyType: 'NFT', + apiType: 'OPENSEA', + explorerLink: 'https://explorerLink.com/token/', + showOnHome: true + }, + { + currencyCode: 'NFT_BNB', + currencyName: 'BNB NFT', + currencySymbol: 'NFT', + tokenBlockchain: 'BNB', + tokenBlockchainShortTitle: 'BNB SC', + tokenBlockchainLongTitle: 'BNB Smart Chain', + tokenBlockchainCode: 'BNB_SMART', + currencyType: 'NFT', + explorerLink: 'https://bscscan.com/token/', + showOnHome: false + }, + + { + currencyCode: 'NFT_MATIC', + currencyName: 'Matic NFT', + currencySymbol: 'NFT', + tokenBlockchain: 'MATIC', + tokenBlockchainCode: 'MATIC', + currencyType: 'NFT', + explorerLink: 'https://polygonscan.com/token/', + showOnHome: false + }, + { + currencyCode: 'NFT_ONE', + currencyName: 'Harmony NFT', + currencySymbol: 'NFT', + tokenBlockchain: 'ONE', + tokenBlockchainCode: 'ONE', + currencyType: 'NFT', + explorerLink: 'https://davinci.gallery/view/', + showOnHome: false + }, + { + currencyCode: 'NFT_ROPSTEN', + currencyName: 'Ropsten NFT', + currencySymbol: 'NFT', + tokenBlockchain: 'ROPSTEN', + tokenBlockchainCode: 'ETH_ROPSTEN', + currencyType: 'NFT', + explorerLink: 'https://ropsten.explorerLink.io/token/', + showOnHome: false + }, + { + currencyCode: 'NFT_RINKEBY', + currencyName: 'Rinkeby NFT', + currencySymbol: 'NFT', + tokenBlockchain: 'RINKEBY', + tokenBlockchainCode: 'ETH_RINKEBY', + currencyType: 'NFT', + apiType: 'OPENSEA', + explorerLink: 'https://rinkeby.explorerLink.io/token/', + showOnHome: false + } + /* + { + currencyCode: 'NFT_TRON', + currencyName: 'Tron NFT', + currencySymbol: 'NFT', + tokenBlockchain: 'TRON', + tokenBlockchainCode: 'TRX', + currencyType: 'NFT' + } + */ +] + +const NftsIndexed = {} +for (const tmp of Nfts) { + NftsIndexed[tmp.tokenBlockchainCode] = tmp + NftsIndexed[tmp.tokenBlockchain] = tmp +} + +const getCurrencyCode = (walletCurrency, tokenBlockchainCode) => { + if (!walletCurrency && !tokenBlockchainCode) { + return '' + } + + if (walletCurrency) { + return walletCurrency + } + + const tmp = NftsIndexed[tokenBlockchainCode.toUpperCase()] + if (typeof tmp === 'undefined') { + return tokenBlockchainCode + } + return tmp.tokenBlockchainCode +} + +const getCurrencyTitle = (walletCurrency, tokenBlockchainCode) => { + if (!walletCurrency && !tokenBlockchainCode) { + return '' + } + + if (walletCurrency) { + return walletCurrency + } + + const tmp = NftsIndexed[tokenBlockchainCode.toUpperCase()] + if (typeof tmp === 'undefined') { + return tokenBlockchainCode + } + return tmp.tokenBlockchainShortTitle || tmp.tokenBlockchain +} + +export default { + Nfts, NftsIndexed, getCurrencyTitle, getCurrencyCode +} diff --git a/crypto/common/BlocksoftDictTypes.ts b/crypto/common/BlocksoftDictTypes.ts new file mode 100644 index 000000000..baef64fde --- /dev/null +++ b/crypto/common/BlocksoftDictTypes.ts @@ -0,0 +1,56 @@ +/** + * @author Ksu + * @version 0.20 + */ +export namespace BlocksoftDictTypes { + + export enum Code { + BTC = 'BTC', + BTC_SEGWIT = 'BTC_SEGWIT', + BTC_SEGWIT_COMPATIBLE = 'BTC_SEGWIT_COMPATIBLE', + ETH_ERC_20 = 'ETH_ERC_20', + USDT = 'USDT', + LTC = 'LTC', + ETH = 'ETH', + ETH_USDT = 'ETH_USDT', + ETH_UAX = 'ETH_UAX', + ETH_TRUE_USD = 'ETH_TRUE_USD', + ETH_BNB = 'ETH_BNB', + ETH_USDC = 'ETH_USDC', + ETH_PAX = 'ETH_PAX', + ETH_DAI = 'ETH_DAI', + ETH_DAIM = 'ETH_DAIM', + TRX = 'TRX', + XMR = 'XMR', + XLM = 'XLM', + BTC_TEST = 'BTC_TEST', + BCH = 'BCH', + BSV = 'BSV', + BTG = 'BTG', + DOGE = 'DOGE', + XVG = 'XVG', + XRP = 'XRP', + ETH_ROPSTEN = 'ETH_ROPSTEN', + ETH_RINKEBY = 'ETH_RINKEBY', + TRX_USDT = 'TRX_USDT', + TRX_BTT = 'TRX_BTT', + ETH_OKB = 'ETH_OKB', + ETH_MKR = 'ETH_MKR', + ETH_KNC = 'ETH_KNC', + ETH_COMP = 'ETH_COMP', + ETH_BAL = 'ETH_BAL', + ETH_LEND = 'ETH_LEND', + ETH_BNT = 'ETH_BNT', + ETH_SOUL = 'ETH_SOUL', + ETH_ONE = 'ETH_ONE', + FIO = 'FIO', + BNB = 'BNB', + BNB_SMART = 'BNB_SMART', + BNB_SMART_20 = 'BNB_SMART_20', + ETC = 'ETC', + VET = 'VET', + SOL = 'SOL', + WAVES = 'WAVES', + ASH = 'ASH' + } +} diff --git a/crypto/common/BlocksoftExplorerDict.js b/crypto/common/BlocksoftExplorerDict.js new file mode 100644 index 000000000..2cd7b301d --- /dev/null +++ b/crypto/common/BlocksoftExplorerDict.js @@ -0,0 +1,39 @@ +const CurrenciesExplorer = { + BTC: + [ + { + currencyCode: 'BTC', + tokenBlockchain: 'BITCOIN', + explorerName: 'Blockchair', + explorerLink: 'https://blockchair.com/bitcoin/address/', + explorerTxLink: 'https://blockchair.com/bitcoin/transaction/' + }, + { + currencyCode: 'BTC', + tokenBlockchain: 'BITCOIN', + explorerName: 'Blockchain.com', + explorerLink: 'https://www.blockchain.com/btc/address/', + explorerTxLink: 'https://www.blockchain.com/btc/tx/' + } + ], + ETH: + [ + { + currencyCode: 'ETH', + tokenBlockchain: 'ETHEREUM', + explorerName: 'Blockchair', + explorerLink: 'https://blockchair.com/ethereum/address/', + explorerTxLink: 'https://blockchair.com/ethereum/transaction/' + }, + { + currencyCode: 'ETH', + tokenBlockchain: 'ETHEREUM', + explorerName: 'Etherscan', + explorerLink: 'https://etherscan.io/address/', + explorerTxLink: 'https://etherscan.io/tx/' + } + ] +} + +export default CurrenciesExplorer + \ No newline at end of file diff --git a/crypto/common/BlocksoftExternalSettings.js b/crypto/common/BlocksoftExternalSettings.js new file mode 100644 index 000000000..9cef73970 --- /dev/null +++ b/crypto/common/BlocksoftExternalSettings.js @@ -0,0 +1,299 @@ +import BlocksoftAxios from './BlocksoftAxios' +import BlocksoftCryptoLog from './BlocksoftCryptoLog' +import ApiProxy from '../../app/services/Api/ApiProxy' +import config from '../../app/config/config' + +const MAX_CACHE_VALID_TIME = 6000000 // 100 minutes +const MIN_CACHE_VALID_TIME = 600000 // 10 minute +let CACHE_VALID_TIME = 600000 // 10 minute +let CACHE_TIME = 0 + +const TREZOR_SERVERS = {} + +const CACHE = { + 'TRX_VOTE_BEST' : 'TV9QitxEJ3pdiAUAfJ2QuPxLKp9qTTR3og', + 'TRX_SEND_LINK' : 'http://trx.trusteeglobal.com:8090', // http://trx.trusteeglobal.com:8090/wallet + 'TRX_SOLIDITY_NODE' : 'http://trx.trusteeglobal.com:8091', // http://trx.trusteeglobal.com:8091/walletsolidity + 'TRX_USE_TRONSCAN' : 0, + 'SOL_VOTE_BEST' : 'CertusDeBmqN8ZawdkxK5kFGMwBXdudvWHYwtNgNhvLu', + 'ETH_LONG_QUERY' : 1, + 'ETH_SMALL_FEE_FORCE_QUIT' : 1, + 'ETH_BLOCKED_BALANCE_FORCE_QUIT' : 1, + 'ETH_LONG_QUERY_FORCE_QUIT' : 1, + 'ETH_GAS_LIMIT' : 120000, + 'ETH_GAS_LIMIT_FORCE_QUIT' : 0, + 'BCH': { '2': 2, '6': 1, '12': 1 }, + 'BSV': { '2': 2, '6': 1, '12': 1 }, + 'BTG': { '2': 10, '6': 5, '12': 2 }, + 'DOGE': { '2': 800000, '6': 600000, '12': 500000 }, + 'DOGE_STATIC' : { 'useStatic' : true, 'speed_blocks_2' : 1, 'feeForAllInputs' : 3}, + 'BTC_TEST': { '2': 8, '6': 6, '12': 5 }, + 'LTC': { '2': 8, '6': 5, '12': 2 }, + 'XVG': { '2': 700, '6': 600, '12': 300 }, + 'XVG_SEND_LINK' : 'https://api.vergecurrency.network/node/api/XVG/mainnet/tx/send', + 'XRP_SERVER' : 'wss://xrplcluster.com', + 'XRP_SCANNER_SERVER' : 'https://xrplcluster.com', // https://xrpl.org/public-servers.html + 'XRP_SCANNER_TYPE' : 'xrpscan', // dataripple + 'XRP_MIN' : 10, + 'XLM_SERVER' : 'https://horizon.stellar.org', + 'XLM_SERVER_PRICE' : 5500, + 'XLM_SERVER_PRICE_FORCE' : 5500, + 'XLM_SEND_LINK' : 'https://horizon.stellar.org/transactions', + 'BNB_SERVER' : 'https://dex.binance.org', + 'BNB_SMART_SERVER' : 'https://bsc-dataseed1.binance.org:443', + 'BNB_SMART_PRICE' : 10000000000, + 'BNB_GAS_LIMIT' : 620000, + 'ETH_MIN_GAS_ERC20' : 73000, + 'ETH_MIN_GAS_LIMIT' : 42000, + 'ETH_TESTNET_PRICE' : 6710000000, + 'ETH_INFURA' : '5e52e85aba6f483398c461c55b639a7b', + 'ETH_INFURA_PROJECT_ID' : 'c8b5c2ced3b041a8b55a1719b508ff08', + 'ETH_TREZOR_SERVER': ['https://eth1.trezor.io', 'https://eth2.trezor.io'], + 'BTC_TREZOR_SERVER': ['https://btc1.trezor.io', 'https://btc2.trezor.io', 'https://btc3.trezor.io', 'https://btc4.trezor.io', 'https://btc5.trezor.io'], + 'LTC_TREZOR_SERVER': ['https://ltc1.trezor.io', 'https://ltc2.trezor.io', 'https://ltc3.trezor.io', 'https://ltc4.trezor.io', 'https://ltc5.trezor.io'], + 'BCH_TREZOR_SERVER': ['https://bch1.trezor.io', 'https://bch2.trezor.io', 'https://bch3.trezor.io', 'https://bch4.trezor.io', 'https://bch5.trezor.io'], + 'DOGE_TREZOR_SERVER': ['https://doge1.trezor.io', 'https://doge2.trezor.io', 'https://doge3.trezor.io', 'https://doge4.trezor.io', 'https://doge5.trezor.io'], + 'BTG_TREZOR_SERVER': ['https://btg1.trezor.io', 'https://btg2.trezor.io', 'https://btg3.trezor.io', 'https://btg4.trezor.io', 'https://btg5.trezor.io'], + 'BSV_TREZOR_SERVER': ['https://bsv.trusteeglobal.com'], + 'ETH_ROPSTEN_TREZOR_SERVER' : ['https://ac-dev0.net:29136'], + 'ETC_TREZOR_SERVER' : ['https://etcblockexplorer.com'], + 'ETC_SERVER' : 'https://www.ethercluster.com/etc', + 'ETC_PRICE' : 6710000000, + 'ETC_GAS_LIMIT' : 620000, + 'ETH_POW_SERVER' : 'https://mainnet.ethereumpow.org', + 'AMB_SERVER' : 'https://network.ambrosus.io', + 'AMB_TREZOR_SERVER' : ['https://blockbook.ambrosus.io'], + 'AMB_PRICE' : 5000000000, + 'AMB_GAS_LIMIT' : 620000, + 'ONE_SERVER' : 'https://api.harmony.one', + 'ONE_GAS_LIMIT' : 920000, + 'ONE_PRICE' : 30000000000, + 'VLX_SERVER' : 'https://evmexplorer.velas.com/rpc', + 'VLX_GAS_LIMIT' : 620000, + 'VLX_PRICE' : 3000000000, + 'METIS_SERVER' : 'https://andromeda.metis.io/?owner=1088', + 'METIS_GAS_LIMIT' : 620000, + 'METIS_PRICE' : 40000000000, + 'BTTC_SERVER' : 'https://rpc.bt.io', + 'OPTIMISM_SERVER' : 'https://mainnet.optimism.io', + 'OPTIMISM_PRICE' : 15000000, + 'OPTIMISM_GAS_LIMIT' : 2320100000, + 'OPTIMISM_MIN_GAS_LIMIT' : 23201000, + 'MATIC_SERVER' : 'https://polygon-rpc.com', + 'MATIC_PRICE' : 1000000000, + 'MATIC_GAS_LIMIT' : 620000, + 'FTM_SERVER' : 'https://rpc.ftm.tools', + 'FTM_PRICE' : 400000000000, + 'FTM_GAS_LIMIT' : 620000, + 'RSK_SERVER' : 'https://public-node.rsk.co', + 'RSK_PRICE' : 5000000000, + 'RSK_GAS_LIMIT' : 620000, + 'SOL_SERVER' : 'https://api.mainnet-beta.solana.com', + 'SOL_SERVER_2' : 'https://solana-mainnet.phantom.app/YBPpkkN4g91xDiAnTE9r0RcMkjg0sKUIWvAfoFVJ', + 'SOL_PRICE' : 5000, + 'SOL_PRICE_NEW_SPL' : 2044280, + 'SOL_TOKENS_LIST' : 'https://raw.githubusercontent.com/solana-labs/token-list/main/src/tokens/solana.tokenlist.json', + 'SOL_VALIDATORS_LIST' : 'https://raw.githubusercontent.com/trustee-wallet/trusteeWalletAssets/main/blockchains/sol/validators.json', + 'WAVES_SERVER' : 'https://nodes.wavesnodes.com', + 'ASH_SERVER' : 'http://51.158.70.89:6555', + 'FIO_BASE_URL' : 'https://fio.eosphere.io/v1/', + 'FIO_HISTORY_URL': 'https://fio.eosphere.io/v1/history/', + 'FIO_REGISTRATION_URL' : 'https://reg.fioprotocol.io/ref/trustee?publicKey=', + 'minCryptoErrorsVersion': 491, + 'minAppErrorsVersion': 491, + 'SUPPORT_BOT' : 'https://t.me/trustee_support_bot?start=app', + 'SUPPORT_BOT_NAME' : '@trustee_support_bot', + 'SUPPORT_EMAIL' : 'contact@trustee.deals', + 'navigationViewV3': 1, + 'SOCIAL_LINK_SITE': 'trusteeglobal.com', + 'SOCIAL_LINK_TELEGRAM': 'https://t.me/trustee_deals', + 'SOCIAL_LINK_TWITTER': 'https://twitter.com/Trustee_Wallet', + 'SOCIAL_LINK_FACEBOOK': 'https://facebook.com/Trustee.Wallet/', + 'SOCIAL_LINK_INSTAGRAM': 'https://instagram.com/trustee_wallet/', + 'SOCIAL_LINK_VK': 'https://vk.com/trustee_wallet', + 'SOCIAL_LINK_GITHUB': 'https://github.com/trustee-wallet/trusteeWallet', + 'SOCIAL_LINK_FAQ': 'https://trusteeglobal.com/faq/', + 'SOCIAL_LINK_YOUTUBE': 'https://www.youtube.com/TrusteeWallet', + 'PRIVACY_POLICY': 'https://trusteeglobal.com/privacy-policy/?header_footer=none', + 'TERMS': 'https://trusteeglobal.com/terms-of-use/?header_footer=none', + 'SEND_CHECK_ALMOST_ALL_PERCENT' : 0.95, + 'SEND_AMOUNT_CHECK' : 1, + 'TRADE_SEND_AMOUNT_CHECK_FORCE_QUIT' : 1, + 'ROCKET_CHAT_USE' : 0, + 'HOW_WORK_CASHBACK_LINK' : 'https://trusteeglobal.com/programma-loyalnosti/', + 'HOW_WORK_CPA_LINK' : 'https://trusteeglobal.com/cpa/', + 'TRX_STAKING_LINK' : 'https://blog.trusteeglobal.com/stejking-trona-i-kak-zarabotat/', + 'TRX_SPAM_LIMIT': 40000, + 'TRX_BASIC_PRICE_WHEN_NO_BAND': 100000, + 'TRX_TRC20_BAND_PER_TX': 350, + 'TRX_TRC20_PRICE_PER_BAND': 140, + 'TRX_TRC20_ENERGY_PER_TX': 29650, // 14650, + 'TRX_TRC20_PRICE_PER_ENERGY': 420, + 'TRX_TRC20_MAX_LIMIT' : 100000000, + 'INVOICE_URL' : 'https://trusteeglobal.com/', + 'STAKING_COINS_PERCENT' : { 'TRX': 5.06, 'SOL': 7.02, 'VET': 1.63, 'ETH': 5.1, 'ETH_MATIC': 6.3 }, + 'DAPPS_IMAGE_LINK': 'https://raw.githubusercontent.com/trustee-wallet/trusteeWalletAssets/main/dapps/' +} + + +class BlocksoftExternalSettings { + + async getAll(source) { + await this._get('getAll ' + source) + return CACHE + } + + async get(param, source = '') { + // BlocksoftCryptoLog.log('BlocksoftExternalSettings.get started ' + param + ' from ' + source) + await this._get('get ' + (typeof source !== 'undefined' && source ? source : param)) + if (typeof CACHE[param] === 'undefined') return false + return CACHE[param] + } + + getStatic(param, source = '') { + if (typeof CACHE[param] === 'undefined') return false + return CACHE[param] + } + + async _get(source) { + const now = new Date().getTime() + if (now - CACHE_TIME < CACHE_VALID_TIME) { + return false + } + try { + // BlocksoftCryptoLog.log('BlocksoftExternalSettings._get started ALL from ' + source) + const tmp = await ApiProxy.getAll({source : 'BlocksoftExternalSettings._get ' + source, onlyFees: true}) + + CACHE_TIME = now + // BlocksoftCryptoLog.log('BlocksoftExternalSettings._get returned ALL from ' + source) + if (tmp && typeof tmp.fees !== 'undefined' && tmp.fees) { + this._setCache(tmp.fees.data) + CACHE_VALID_TIME = MIN_CACHE_VALID_TIME + } else { + CACHE_VALID_TIME = MAX_CACHE_VALID_TIME + } + } catch (e) { + if (config.debug.appErrors) { + console.log('BlocksoftExternalSettings._get started ALL from ' + source + ' error ' + e.message.toString().substr(0, 150)) + } + // BlocksoftCryptoLog.log('BlocksoftExternalSettings._get started ALL from ' + source + ' error ' + e.message) + } + } + + _setCache(json) { + for (const key in json) { + if (key === 'STAKING_COINS_PERCENT') { + for (const key2 in json[key]) { + CACHE[key][key2] = json[key][key2] + } + } else { + CACHE[key] = json[key] + } + } + } + + async setTrezorServerInvalid(key, server) { + // BlocksoftCryptoLog.log('BlocksoftExternalSettings.setTrezorServerInvalid ' + key + ' start invalid ' + server + ' cache ' + JSON.stringify(TREZOR_SERVERS[key])) + if (typeof TREZOR_SERVERS[key] === 'undefined') { + return false + } + const cached = TREZOR_SERVERS[key] + if (cached.currentServerValue !== server) { + return false + } + cached.currentServerIndex++ + if (cached.currentServerIndex >= cached.okServers.length || typeof cached.okServers[cached.currentServerIndex] === 'undefined') { + cached.currentServerIndex = 0 + } + if (typeof cached.okServers[cached.currentServerIndex] !== 'undefined') { + cached.currentServerValue = cached.okServers[cached.currentServerIndex] + } + // BlocksoftCryptoLog.log('BlocksoftExternalSettings.setTrezorServerInvalid ' + key + ' finish invalid ' + server + ' cache ' + JSON.stringify(TREZOR_SERVERS[key])) + if (typeof cached.currentServerValue === 'undefined' || !cached.currentServerValue) { + BlocksoftCryptoLog.err('BlocksoftExternalSettings.getTrezorServer ' + key + ' setInvalid error with currentServer ' + JSON.stringify(TREZOR_SERVERS[key])) + return CACHE[key][0] + } + return cached.currentServerValue + } + + async getTrezorServer(key, source) { + // BlocksoftCryptoLog.log('BlocksoftExternalSettings.getTrezorServer ' + key + ' from ' + source) + const now = new Date().getTime() + if (typeof TREZOR_SERVERS[key] !== 'undefined') { + const cached = TREZOR_SERVERS[key] + if (now - cached.cacheTime < MIN_CACHE_VALID_TIME) { + // BlocksoftCryptoLog.log('BlocksoftExternalSettings.getTrezorServer ' + key + ' from ' + source + ' got from cache ' + JSON.stringify(TREZOR_SERVERS[key])) + if (typeof cached.currentServerValue === 'undefined' || !cached.currentServerValue) { + BlocksoftCryptoLog.err('BlocksoftExternalSettings.getTrezorServer ' + key + ' from ' + source + ' cache error with currentServer ' + JSON.stringify(TREZOR_SERVERS[key])) + return CACHE[key][0] + } + return cached.currentServerValue + } + } + const servers = await this.get(key, 'inner getTrezorServer ' + key) + let okServers = [] + let bestHeight = 0 + let currentServer = false + if (key === 'BTC_TREZOR_SERVER' || servers.length === 1) { + okServers = servers + currentServer = servers[0] + } else { + let server + const allServers = [] + for (server of servers) { + if (!currentServer) { + currentServer = server + } + // BlocksoftCryptoLog.log('BlocksoftExternalSettings.getTrezorServer ' + key + ' from ' + source + ' server ' + server + ' will load ' + server + '/api') + const current = await BlocksoftAxios.getWithoutBraking(server + '/api') + // BlocksoftCryptoLog.log('BlocksoftExternalSettings.getTrezorServer ' + key + ' from ' + source + ' server ' + server + ' is loaded') + if (current && typeof current.data !== 'undefined' && current.data) { + if (current.data.blockbook.bestHeight !== 'undefined') { + const tmp = current.data.blockbook.bestHeight + if (tmp > bestHeight) { + bestHeight = tmp + } + allServers[server] = tmp + // BlocksoftCryptoLog.log('BlocksoftExternalSettings.getTrezorServer ' + key + ' from ' + source + ' server ' + server + ' height ' + tmp) + } else { + BlocksoftCryptoLog.err('BlocksoftExternalSettings.getTrezorServer ' + key + ' from ' + source + ' server ' + server + ' something wrong with blockbook ' + JSON.stringify(current.data)) + } + + } + } + if (typeof TREZOR_SERVERS[key] !== 'undefined' && TREZOR_SERVERS[key].bestHeight > bestHeight && typeof TREZOR_SERVERS[key].currentServerValue !== 'undefined' && TREZOR_SERVERS[key].currentServerValue) { + TREZOR_SERVERS[key].cacheTime = now + return TREZOR_SERVERS[key].currentServerValue + } + for (server of servers) { + if (typeof allServers[server] !== 'undefined' && allServers[server] === bestHeight) { + okServers.push(server) + } + } + } + + + if (okServers && typeof okServers[0] !== 'undefined') { + currentServer = okServers[0] + } else { + okServers = [currentServer] + } + + TREZOR_SERVERS[key] = { + okServers, + bestHeight, + cacheTime: now, + currentServerValue: currentServer, + currentServerIndex: 0 + } + if (typeof currentServer === 'undefined' || !currentServer) { + BlocksoftCryptoLog.err('BlocksoftExternalSettings.getTrezorServer ' + key + ' from ' + source + ' error with currentServer ', JSON.stringify(TREZOR_SERVERS[key])) + } + + BlocksoftCryptoLog.log('BlocksoftExternalSettings.getTrezorServer ' + key + ' from ' + source + ' put to cache ', JSON.stringify(TREZOR_SERVERS[key])) + + return currentServer + } +} + +export default new BlocksoftExternalSettings() diff --git a/crypto/common/BlocksoftFixBalance.js b/crypto/common/BlocksoftFixBalance.js new file mode 100644 index 000000000..a0cc733be --- /dev/null +++ b/crypto/common/BlocksoftFixBalance.js @@ -0,0 +1,28 @@ +export default function fixBalance(obj, pref) { + if (typeof obj === 'undefined') return 0 + + let balance + if (typeof (obj[pref + 'Txt']) !== 'undefined') { + balance = obj[pref + 'Txt'] + } else { + balance = obj[pref + '_txt'] + } + if (balance && balance * 1 > 0) { + return balance + } + + if (typeof (obj[pref + 'Fix']) !== 'undefined') { + balance = obj[pref + 'Fix'] + } else { + balance = obj[pref + '_fix'] + } + if (!balance) { + return '0' + } + if (balance.toString().indexOf('e') === -1) { + return balance + } + + return 0 +} + diff --git a/crypto/common/BlocksoftPrettyDates.js b/crypto/common/BlocksoftPrettyDates.js new file mode 100644 index 000000000..36f5400f9 --- /dev/null +++ b/crypto/common/BlocksoftPrettyDates.js @@ -0,0 +1,20 @@ +import config from '@app/config/config' + +class BlocksoftPrettyDates { + + timestampToPretty(str) { + try { + const datetime = new Date(str) + return datetime.toTimeString().slice(0, 8) + ' ' + + (datetime.getDate().toString().length === 1 ? '0' + datetime.getDate() : datetime.getDate()) + '/' + + ((datetime.getMonth() + 1).toString().length === 1 ? '0' + (datetime.getMonth() + 1) : (datetime.getMonth() + 1)) + '/' + datetime.getFullYear() + } catch (e) { + if (config.debug.appErrors) { + console.log('BlocksoftPrettyDates.timestampToPretty ' + str + ' error ' + e.message) + } + return '' + } + } +} + +export default new BlocksoftPrettyDates() diff --git a/crypto/common/BlocksoftPrettyLocalize.js b/crypto/common/BlocksoftPrettyLocalize.js new file mode 100644 index 000000000..8ee2e2b93 --- /dev/null +++ b/crypto/common/BlocksoftPrettyLocalize.js @@ -0,0 +1,16 @@ +import i18n from '../../app/services/i18n' + +class BlocksoftPrettyLocalize { + + makeLink(link) { + const main = i18n.locale.split('-')[0].toLowerCase() + if ((main === 'uk' || main === 'ru') && link) { + if (link.indexOf('https://blockchair.com/') === 0) { + link = link.replace('https://blockchair.com/', 'https://blockchair.com/ru/') + } + } + return link + } +} + +export default new BlocksoftPrettyLocalize() diff --git a/crypto/common/BlocksoftPrettyNumbers.js b/crypto/common/BlocksoftPrettyNumbers.js new file mode 100644 index 000000000..9d70af655 --- /dev/null +++ b/crypto/common/BlocksoftPrettyNumbers.js @@ -0,0 +1,165 @@ +import BlocksoftDict from './BlocksoftDict' +import BlocksoftUtils from './BlocksoftUtils' + +class BlocksoftPrettyNumbers { + + /** + * @param {string} currencyCode + * @return {BlocksoftPrettyNumbers} + */ + setCurrencyCode(currencyCode) { + const settings = BlocksoftDict.getCurrencyAllSettings(currencyCode) + if (settings.prettyNumberProcessor) { + this._processorCode = settings.prettyNumberProcessor + } else { + throw new Error('BlocksoftDict.getCurrencyAllSettings no settings.prettyNumberProcessor for ' + currencyCode) + } + if (typeof settings.decimals !== 'undefined' && settings.decimals !== false) { + this._decimals = settings.decimals + } else { + throw new Error('BlocksoftDict.getCurrencyAllSettings no settings.decimals for ' + currencyCode) + } + + return this + } + + /** + * @param {string|number} number + * @return {string} + */ + makePretty(number, source = '') { + if (this._processorCode === 'USDT') { + return number + } + const str = number.toString() + if (str.indexOf('.') !== -1 || str.indexOf(',') !== -1) { + number = str.split('.')[0] + } + if (this._processorCode === 'ETH') { + return BlocksoftUtils.toEther(number) + } else if (this._processorCode === 'BTC') { + return BlocksoftUtils.toBtc(number) + } else if (this._processorCode === 'ETH_ERC_20' || this._processorCode === 'UNIFIED') { + // console.log('makePretty ' + JSON.stringify(number) + ' source ' + source) + return BlocksoftUtils.toUnified(number, this._decimals) + } + throw new Error('undefined BlocksoftPrettyNumbers processor to makePretty') + } + + makeCut(tmp, size = 5, source = false, useDict = true) { + if (this._decimals <= 6 && size === 5 && this._decimals > 0 && useDict) { + size = this._decimals + } + let cutted = 0 + let isSatoshi = false + if (typeof tmp === 'undefined' || !tmp) { + return { cutted, isSatoshi, justCutted: cutted, separated: '0' } + } + const splitted = tmp.toString().split('.') + const def = '0.' + '0'.repeat(size) + let firstPart = false + let secondPart = false + if (splitted[0] === '0') { + if (typeof splitted[1] !== 'undefined' && splitted[1]) { + cutted = splitted[0] + '.' + splitted[1].substr(0, size) + if (cutted === def) { + cutted = splitted[0] + '.' + splitted[1].substr(0, size * 2) + const def2 = '0.' + '0'.repeat(size * 2) + if (cutted !== def2) { + secondPart = splitted[1].substr(0, size * 2) + } + isSatoshi = true + } + } else { + cutted = '0' + } + } else if (typeof splitted[1] !== 'undefined' && splitted[1]) { + const second = splitted[1].substr(0, size) + if (second !== '0'.repeat(size)) { + cutted = splitted[0] + '.' + second + secondPart = second + } else { + cutted = splitted[0] + } + firstPart = splitted[0] + '' + } else { + cutted = splitted[0] + firstPart = splitted[0] + '' + } + let justCutted = isSatoshi ? '0' : cutted + if (justCutted === def) { + justCutted = '0' + } + + if (secondPart) { + for (let i = secondPart.length; i--; i >= 0) { + if (typeof secondPart[i] === 'undefined' || secondPart[i] === '0') { + secondPart = secondPart.substr(0, i) + } else { + break + } + } + } + + let separated = justCutted + let separatedForInput = false + if (firstPart) { + const len = firstPart.length + if (len > 3) { + separated = '' + let j = 0 + for (let i = len - 1; i >= 0; i--) { + if (j === 3) { + separated = ' ' + separated + j = 0 + } + j++ + separated = firstPart[i] + separated + } + } else { + separated = firstPart + } + separatedForInput = separated.toString() + if (secondPart) { + separated += '.' + secondPart + } + } else if (secondPart) { + separated = '0.' + secondPart + separatedForInput = '0' + } + if (separatedForInput === false) { + separatedForInput = justCutted + } else if (typeof splitted[1] !== 'undefined') { + separatedForInput += '.' + splitted[1] + } + + return { cutted, isSatoshi, justCutted, separated: separated.toString(), separatedForInput } + } + + /** + * @param {string} value + * @return {string} + */ + makeUnPretty(value) { + const number = value.toString().replace(' ', '') + try { + if (this._processorCode === 'USDT') { + return number + } else if (this._processorCode === 'ETH') { + return BlocksoftUtils.toWei(number) + } else if (this._processorCode === 'BTC') { + return BlocksoftUtils.toSatoshi(number) + } else if (this._processorCode === 'ETH_ERC_20' || this._processorCode === 'UNIFIED') { + return BlocksoftUtils.fromUnified(number, this._decimals) + } + } catch (e) { + e.message += 'in makeUnPretty' + throw e + } + throw new Error('undefined BlocksoftPrettyNumbers processor to makeUnPretty') + } + + +} + +export default new BlocksoftPrettyNumbers() diff --git a/crypto/common/BlocksoftPrettyStrings.js b/crypto/common/BlocksoftPrettyStrings.js new file mode 100644 index 000000000..220c7ce3e --- /dev/null +++ b/crypto/common/BlocksoftPrettyStrings.js @@ -0,0 +1,26 @@ +class BlocksoftPrettyStrings { + + makeCut(str, size = 5, sizeEnd = false) { + if (!str) return str + + const ln = str.length + if (!sizeEnd || sizeEnd == 0) { + sizeEnd = size + } + if (ln < size + sizeEnd + 3) return str + return str.substring(0, size) + '...' + str.substring(ln - sizeEnd) + } + + makeFromTrustee(link) { + let linkUrl = link + if (linkUrl.indexOf('harmony.one') !== -1) { + return linkUrl + } + if (linkUrl.indexOf('?') === -1) { + linkUrl += '?from=trustee' + } + return linkUrl + } +} + +export default new BlocksoftPrettyStrings() diff --git a/crypto/common/BlocksoftPrivateKeysUtils.js b/crypto/common/BlocksoftPrivateKeysUtils.js new file mode 100644 index 000000000..abae117c2 --- /dev/null +++ b/crypto/common/BlocksoftPrivateKeysUtils.js @@ -0,0 +1,100 @@ +/** + * @version 0.52 + */ +import BlocksoftKeysStorage from '@crypto/actions/BlocksoftKeysStorage/BlocksoftKeysStorage' +import BlocksoftKeys from '@crypto/actions/BlocksoftKeys/BlocksoftKeys' +import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog' +import config from '@app/config/config' + +const CACHE = [] + +export default { + /** + * @param discoverFor.mnemonic + * @param discoverFor.derivationPath + * @param discoverFor.derivationIndex + * @param discoverFor.derivationType + * @param discoverFor.path + * @param discoverFor.currencyCode + * @param discoverFor.walletHash + * @param discoverFor.addressToCheck + * @returns {Promise<{privateKey: string}>} + */ + async getPrivateKey(discoverFor, source) { + const path = typeof discoverFor.path !== 'undefined' && discoverFor.path ? discoverFor.path : discoverFor.derivationPath + if (path === 'false' || !path) { + await BlocksoftCryptoLog.log('BlocksoftTransferPrivateKeysDiscover private key not discovered as path = false from ' + source) + } + const discoverForKey = BlocksoftKeysStorage.getAddressCacheKey(discoverFor.walletHash, path.replace(/[']/g, 'quote'), discoverFor.currencyCode) + await BlocksoftCryptoLog.log('BlocksoftTransferPrivateKeysDiscover.getPrivateKey actually inited ', { address: discoverFor.addressToCheck, path, discoverForKey }) + let result = CACHE[discoverForKey] + if (result) { + return result + } + result = await BlocksoftKeysStorage.getAddressCache(discoverForKey) + if (result) { + CACHE[discoverForKey] = result + return result + } + + try { + if (typeof discoverFor.mnemonic === 'undefined' || !discoverFor.mnemonic) { + await BlocksoftCryptoLog.log('BlocksoftTransferPrivateKeysDiscover.getPrivateKey actually redo mnemonic ' + discoverFor.walletHash), + discoverFor.mnemonic = await BlocksoftKeysStorage.getWalletMnemonic(discoverFor.walletHash, 'getPrivateKey') + } + result = await BlocksoftKeys.discoverOne(discoverFor) + if (discoverFor.addressToCheck && discoverFor.addressToCheck !== result.address) { + + await BlocksoftCryptoLog.log('BlocksoftTransferPrivateKeysDiscover private key discovered is not for address you path=' + discoverFor.derivationPath + ' set ' + result.address + '!=' + discoverFor.addressToCheck + ' key=' + discoverForKey + ' from ' + source) + + const tmpPath = [ + `m/84'/0'/0'/0/0`, + `m/84'/0'/0'/0/1`, + `m/44'/0'/0'/0/0`, + `m/44'/0'/0'/0/1`, + `m/49'/0'/0'/0/0`, + `m/49'/0'/0'/0/1`, + `m/44'/0`, + `m/44'/1`, + `m/49'/0`, + `m/49'/1`, + `m/84'/0`, + `m/84'/1`, + `m/0` + ] + let tmpFound = false + for (const path of tmpPath) { + const clone = JSON.parse(JSON.stringify(discoverFor)) + clone.derivationPath = path + const result2 = await BlocksoftKeys.discoverOne(clone) + if (discoverFor.addressToCheck === result2.address) { + await BlocksoftCryptoLog.log('BlocksoftTransferPrivateKeysDiscover private key rediscovered FOUND address path=' + clone.derivationPath + ' set ' + result2.address + '=' + discoverFor.addressToCheck + ' from ' + source) + result = result2 + tmpFound = true + break + } else { + await BlocksoftCryptoLog.log('BlocksoftTransferPrivateKeysDiscover private key rediscovered is not for address path=' + clone.derivationPath + ' set ' + result2.address + '!=' + discoverFor.addressToCheck + ' from ' + source) + } + } + if (!tmpFound) { + throw new Error('invalid address') + } + } + await BlocksoftKeysStorage.setAddressCache(discoverForKey, result) + } catch (e) { + if (config.debug.appErrors) { + console.log('BlocksoftTransferPrivateKeysDiscover private key error ' + e.message + ' from ' + source, e) + } + const clone = JSON.parse(JSON.stringify(discoverFor)) + const msg = e.message.toString().replace(discoverFor.mnemonic, '***') + if (clone.mnemonic === '***') { + clone.mnemonic = '*** already masked ***' + } else { + clone.mnemonic = '***' + } + throw new Error('BlocksoftTransferPrivateKeysDiscover private key error ' + msg + ' from ' + source + ' this._data=' + JSON.stringify(clone)) + } + CACHE[discoverForKey] = result + return result + } +} diff --git a/crypto/common/BlocksoftQrScanDict.js b/crypto/common/BlocksoftQrScanDict.js new file mode 100644 index 000000000..e7cc1d21d --- /dev/null +++ b/crypto/common/BlocksoftQrScanDict.js @@ -0,0 +1,91 @@ +/** + * @version 0.77 + */ +const networkToCurrencyCode = { + 'litecoin': 'LTC', + 'ltc': 'LTC', + 'bitcoingold': 'BTG', + 'btg': 'BTG', + 'bitcoinsv': 'BSV', + 'bsv': 'BSV', + 'tron': 'TRX', + 'trx': 'TRX', + 'ripple': 'XRP', + 'xrp': 'XRP', + 'stellar': 'XLM', + 'xlm': 'XLM', + 'doge': 'DOGE', + 'dogecoin': 'DOGE', + 'ethereum': 'ETH', + 'eth': 'ETH', + 'verge': 'XVG', + 'xvg': 'XVG', + 'ethereumropsten': 'ETH_ROPSTEN', + 'ethereumrinkeby': 'ETH_RINKEBY', + 'monero': 'XMR', + 'matic': 'MATIC', + 'polygon': 'MATIC', + 'velas': 'VLX', + 'vlx': 'VLX', + 'binance': 'BNB', + 'bnb': 'BNB', + 'fio': 'FIO', + 'bnbsmartchain': 'BNB_SMART', + 'ethereumclassic': 'ETC', + 'vechainthor': 'VET', + 'vechainthortoken': 'VTHO', + 'metis': 'METIS', + 'ambrosus': 'AMB', + 'amb': 'AMB', + 'waves': 'WAVES', + 'aeneas': 'ASH', + 'ash': 'ASH', + 'bitcointestnet': 'BTC_TEST', + 'bitcoincash': 'BCH', + 'fantom': 'FTM', + 'optimism': 'OPTIMISM', + 'solana': 'SOL', + 'tether usd (usdttrx) address': 'TRX_USDT', + 'tron trc20 (usdt)': 'TRX_USDT', + 'trc20': 'TRX_USDT', + 'tether-erc-20': 'TRX_USDT', + 'tethererc20': 'ETH_USDT' +} + +const currencyNameToNetwork = { + 'optimisticethereum': 'optimism', + 'fantomnetwork': 'fantom', + 'aeneas': 'aeneas', + 'ethereumclassic': 'ethereumclassic', + 'vechainthor': 'vechainthor', + 'vechainthortoken': 'vechainthortoken', + 'binancecoin': 'bnb', + 'velas': 'velas', + 'metis': 'metis', + 'bnbsmartchain': 'bnbsmartchain', + 'polygon(matic)network': 'polygon', + 'token_of_ethereum' : 'ethereum', + 'token_of_bnb' : 'bnbsmartchain', + 'token_of_solana' : 'solana', + 'token_of_matic' : 'polygon', + 'token_of_ftm' : 'fantom', + 'token_of_metis' : 'metis', + 'token_of_tron' : 'tron', + 'token_of_vlx' : 'velas', + 'token_of_bitcoin' : 'bitcoin', +} + + +function changeCurrencyNameToNetwork(_currencyName, _helperName) { + let helperName = typeof _helperName !== 'undefined' && _helperName ? _helperName.toLowerCase().replace(/ /g, '') : '' + let currencyName = typeof _currencyName !== 'undefined' && _currencyName ? _currencyName.toLowerCase().replace(/ /g, '') : '' + let tmp = currencyNameToNetwork[helperName] || currencyNameToNetwork[currencyName] || currencyName + return tmp.toLowerCase().replace(/ /g, '') +} + +function changeNetworkToCurrencyCode(network) { + return networkToCurrencyCode[network] || 'BTC' +} + + +export { changeNetworkToCurrencyCode, changeCurrencyNameToNetwork } diff --git a/crypto/common/BlocksoftUtils.js b/crypto/common/BlocksoftUtils.js new file mode 100644 index 000000000..108142591 --- /dev/null +++ b/crypto/common/BlocksoftUtils.js @@ -0,0 +1,375 @@ +import { BigNumber } from 'bignumber.js' +import { hexToBn, bnToHex } from '../blockchains/eth/ext/estimateGas/util' +import Log from '../../app/services/Log/Log' + +import BigIntXmr from '@crypto/blockchains/xmr/ext/vendor/biginteger' +const Web3 = require('web3') + +class BlocksoftUtils { + + static cutZeros(val) { + const tmp = val.toString().split('.') + if (typeof tmp[1] === 'undefined' || !tmp[1]) return tmp[0] + + let firstNonZero = -1 + let i = tmp[1].length - 1 + do { + const char = tmp[1][i] + if (char !== '0') { + firstNonZero = i + } + i-- + } while (firstNonZero === -1 && i >= 0) + const last = tmp[1].substr(0, firstNonZero + 1) + if (!last || last === '') return tmp[0] + return tmp[0] + '.' + last + } + + static round(val) { + const tmp = val.toString().split('.') + return tmp[0].replace(' ', '') + } + + // // console.log('added', BlocksoftUtils.add(967282001717650,87696220292905380)) + static add(val1, val2) { + // console.log('BlocksoftUtils add ', JSON.stringify({ val1, val2})) + let res = 0 + if (typeof val1 === 'undefined') { + res = val2 || '' + } else if (typeof val2 === 'undefined' || val2 === 0 || val2 === '0' || !val2) { + res = val1 + } else if (typeof val1.innerBN !== 'undefined') { + if (typeof val2.innerBN !== 'undefined') { + res = val1.innerBN.plus(val2.innerBN).toString() + } else { + res = val1.innerBN.plus(BigNumber(val2)).toString() + } + } else if (!val2 || !(val2 * 1 > 0)) { + res = val1 + } else { + const str = val1.toString() + val2.toString() + if (str.indexOf('.') !== -1 || str.indexOf(',') !== -1) { + res = val1 * 1 + val2 * 1 + } else { + try { + res = BigNumber(val1).plus(BigNumber(val2)).toString() + } catch (e) { + res = val1 * 1 + val2 * 1 + } + } + } + // console.log('BlocksoftUtils added ', JSON.stringify({ val1, val2, res})) + return BlocksoftUtils.fromENumber(res) + } + + static mul(val1, val2) { + // console.log('BlocksoftUtils mul ', JSON.stringify({ val1, val2 })) + if (typeof val1 === 'undefined') { + return '' + } + if (typeof val2 === 'undefined') { + return val1 + } + if (val2 === '1' || val2 === 1) { + return val1 + } + if (typeof val1.innerBN !== 'undefined') { + if (typeof val2.innerBN !== 'undefined') { + return val1.innerBN.times(val2.innerBN).toString() + } else { + return val1.innerBN.times(BigNumber(val2)).toString() + } + } + const str = val1.toString() + val2.toString() + let res = 0 + if (str.indexOf('.') !== -1 || str.indexOf(',') !== -1) { + res = val1 * val2 + } else { + try { + res = BigNumber(val1).times(BigNumber(val2)).toString() + } catch (e) { + res = val1 * val2 + } + } + return BlocksoftUtils.fromENumber(res) + } + + static div(val1, val2) { + // console.log('BlocksoftUtils div ', JSON.stringify({ val1, val2 })) + if (typeof val1 === 'undefined') { + return '' + } + if (typeof val2 === 'undefined') { + return val1 + } + if (val2 === '1' || val2 === 1) { + return val1 + } + if (typeof val1.innerBN !== 'undefined') { + if (typeof val2.innerBN !== 'undefined') { + return val1.innerBN.dividedBy(val2.innerBN).toString() + } else { + return val1.innerBN.dividedBy(BigNumber(val2 + '')).toString() + } + } + const str = val1.toString() + val2.toString() + let res = 0 + if (str.indexOf('.') !== -1 || str.indexOf(',') !== -1) { + res = val1 / val2 + } else { + let addedZeros = false + if (val1.length <= val2.length + 2) { + val1 += '00000000' + addedZeros = true + } + try { + res = BigNumber(val1).dividedBy(BigNumber(val2)).toString() + if (addedZeros) { + res = res / 100000000 + } + } catch (e) { + res = val1 / val2 + } + } + return BlocksoftUtils.fromENumber(res) + } + + static diff(val1, val2) { + // console.log('BlocksoftUtils diff ', JSON.stringify({ val1, val2 })) + if (typeof val1 === 'undefined') { + return val2 || '' + } + if (typeof val2 === 'undefined') { + return val1 + } + if (!val2) { + return val1 + } + if (!val1) { + return -1 * val2 + } + if (typeof val1.innerBN !== 'undefined') { + if (typeof val2.innerBN !== 'undefined') { + return val1.innerBN.minus(val2.innerBN).toString() + } else { + return val1.innerBN.minus(BigNumber(val2 + '')).toString() + } + } + const str = val1.toString() + val2.toString() + let res = 0 + if (str.indexOf('.') !== -1 || str.indexOf(',') !== -1) { + res = val1 - val2 + } else { + try { + res = BigNumber(val1).minus(BigNumber(val2 + '')).toString() + } catch (e) { + res = val1 - val2 + } + } + return BlocksoftUtils.fromENumber(res) + } + + static fromENumber(val) { + // console.log('BlocksoftUtils fromE ', JSON.stringify(val)) + if (val === null || typeof (val) === 'undefined' || !val) { + return 0 + } + val = val.toString().toLowerCase() + if (val.indexOf('0x') === 0) { + return BigIntXmr.BigInteger(val).toString() + } + if (val.indexOf('e') === -1) { + return val + } + const parts = val.split('e') + const number = parts[1].substr(0, 1) + const power = parts[1].substr(1) + const first = parts[0].split('.') + if (number === '+') { + return this.fromUnified(parts[0], power) + } else if (typeof power !== 'undefined' && power*1 > 0) { + return '0.' + ('0'.repeat(power - 1)) + first[0] + (typeof first[1] !== 'undefined' ? first[1] : '') + } else { + return '0.0' + } + } + + static toSatoshi(val) { + return this.fromUnified(val, 8) + } + + static toBtc(val) { + return this.toUnified(val, 8) + } + + static toUnified(val, decimals = 8) { + if (typeof val === 'undefined' || val === 'undefined' || !val) { + return 0 + } + if (typeof val === 'object') { + val = val.toString() + } + if (typeof val === 'number') { + val += '' + } + // noinspection JSUnresolvedVariable,JSCheckFunctionSignatures + let added = '' + const till = 18 - decimals + if (till < 0) { + throw new Error('toUnified till is less then 0, decimals = ' + decimals) + } + for (let i = 0; i < till; i++) { + added += '0' + } + const parts = val.split('.') + // noinspection JSUnresolvedVariable + const tmp = parts[0] + added + const res = Web3.utils.fromWei(tmp, 'ether') + // console.log('BlocksoftUtils toUnified ', JSON.stringify(val), JSON.stringify(res)) + return res + } + + static fromUnified(val, decimals = 8) { + // console.log('BlocksoftUtils fromUnified ', JSON.stringify(val)) + if (typeof val === 'undefined') return 0 + val = val.toString() + const parts = val.split('.') + let number = parts[0] + if (!parts[1] || !parts[1].length) return (number + '0'.repeat(decimals)) + + // fill the letters after point + const letters = parts[1].split('') + let needToFill = decimals + for (let i = 0, ic = letters.length; i < ic; i++) { + needToFill-- + number += letters[i] + if (needToFill === 0) break + } + for (let i = 0; i < needToFill; i++) { + number += '0' + } + + // cut first 0 + let cutted = '' + let started = false + for (let i = 0, ic = number.length; i < ic; i++) { + if (!started && number[i] === '0') continue + cutted += number[i] + started = true + } + + return cutted || 0 + } + + static toWei(val, from = 'ether') { + // console.log('BlocksoftUtils toWei ', JSON.stringify(val)) + if (typeof val === 'undefined') { + throw new Error('toWei val is undefined') + } + if (typeof val === 'number') { + val += '' + } + const parts = val.toString().split('.') + if (typeof parts[1] === 'undefined' || parts[1] === '' || !parts[1]) { + // noinspection JSUnresolvedVariable + return Web3.utils.toWei(val, from) + } + + let decimals = 18 + if (from === 'gwei') { + decimals = 9 + } + const newVal = parts[0] + '.' + parts[1].substring(0, decimals) + return Web3.utils.toWei(newVal, from) + } + + static toGwei(val) { + // console.log('BlocksoftUtils toGwei ', JSON.stringify(val)) + if (typeof val === 'number') { + val += '' + } + + // noinspection JSUnresolvedVariable + let newVal = 0 + try { + // noinspection JSUnresolvedVariable,JSCheckFunctionSignatures + const tmp = val.split('.') + newVal = Web3.utils.fromWei(tmp[0], 'gwei') + } catch (e) { + e.message = JSON.stringify(val) + ' ' + e.message + Log.err('BlocksoftUtils.toGwei error ' + e.message) + } + return newVal + } + + static toEther(val) { + // console.log('BlocksoftUtils toEth ', JSON.stringify(val)) + if (typeof val === 'number') { + val += '' + } + + // noinspection JSUnresolvedVariable + let newVal + try { + // noinspection JSUnresolvedVariable + newVal = Web3.utils.fromWei(val, 'ether') + } catch (e) { + e.message = JSON.stringify(val) + ' ' + e.message + } + return newVal + } + + static toDate(timeStamp, multiply = true) { + if (timeStamp.toString().indexOf('T') !== -1) { + return timeStamp + } else if (timeStamp && timeStamp > 0) { + if (multiply) { + timeStamp = timeStamp * 1000 + } + return (new Date(timeStamp)).toISOString() + } else { + return new Date().toISOString() + } + } + + static hexToUtf(hex) { + return Web3.utils.hexToUtf8(hex) + } + + static utfToHex(str) { + return Web3.utils.utf8ToHex(str) + } + + + static hexToDecimal(hex) { + if (hex.toString().indexOf('0x') === 0) { + return Web3.utils.hexToNumber(hex) + } + return hex + } + + static hexToDecimalWalletConnect(hex) { + return hexToBn(hex).toString() + } + + static decimalToHexWalletConnect(decimal) { + return bnToHex(decimal) + } + + static decimalToHex(decimal, len = 0) { + let str = Web3.utils.toHex(decimal).substr(2) + if (len > 0) { + if (len < str.length) { + throw new Error('hex ' + decimal + ' => ' + str + ' is longer then ' + len + ' and equal ' + str.length ) + } + str = '0'.repeat(len - str.length) + str + } + return str + } + + static hexToDecimalBigger(hex) { + return BigIntXmr.BigInteger(hex).toString() + } +} + +export default BlocksoftUtils diff --git a/crypto/common/ext/bip44-constants.js b/crypto/common/ext/bip44-constants.js new file mode 100644 index 000000000..eedf32a54 --- /dev/null +++ b/crypto/common/ext/bip44-constants.js @@ -0,0 +1,516 @@ +/** + * based on https://github.com/bitcoinjs/bip44-constants/blob/master/index.js + * Format for each row: + * [ constant, coinSymbol, coinName ] + */ +module.exports = [ + + [ `44'/0`, "BTC", "Bitcoin" ], + [ `44'/1`, "BTC_TEST", "Testnet (all coins)" ], + [ `84'/0`, "BTC_SEGWIT", "Bitcoin" ], + [ `49'/0`, "BTC_SEGWIT_COMPATIBLE", "Bitcoin" ], + [ `44'/0`, "USDT", "USDT Omni" ], // actual = 200 + [ `44'/279553`, "BTC_LIGHT", "Bitcoin Light" ], + + [ `44'/145`, "BCH", "Bitcoin Cash" ], + [ `44'/156`, "BTG", "Bitcoin Gold" ], + [ `44'/236`, "BSV", "Bitcoin SV" ], + + [ `44'/60`, "ETH", "Ether" ], + [ `44'/61`, "ETC", "Ether Classic" ], + [ `44'/1`, "ETH_ROPSTEN", "Ropsten Ether" ], + [ `44'/1`, "ETH_RINKEBY", "Rinkeby Ether" ], + + [ `44'/2`, "LTC", "Litecoin" ], + [ `84'/2`, "LTC_SEGWIT", "Litecoin" ], + [ `44'/3`, "DOGE", "Dogecoin" ], + [ `44'/77`, "XVG", "Verge" ], + [ `44'/195`, "TRX", "Tron" ], + + [ `44'/144`, "XRP", "Ripple" ], + [ `44'/148`, "XLM", "Stellar" ], + [ `44'/128`, "XMR", "Monero" ], + + [ `44'/235`, "FIO", "FIO" ], + + [ `44'/714`, "BNB", "BNB" ], + [ `44'/818`, "VET", "VET" ], + + [ `44'/501`, "SOL", "SOL" ], + [ `44'/5741564`, "WAVES", "Waves" ], + [ `44'/5741564`, "ASH", "Ash" ], + + /* + [ 0x80000004, "RDD", "Reddcoin" ], + [ 0x80000005, "DASH", "Dash (ex Darkcoin)" ], + [ 0x80000006, "PPC", "Peercoin" ], + [ 0x80000007, "NMC", "Namecoin" ], + [ 0x80000008, "FTC", "Feathercoin" ], + [ 0x80000009, "XCP", "Counterparty" ], + [ 0x8000000a, "BLK", "Blackcoin" ], + [ 0x8000000b, "NSR", "NuShares" ], + [ 0x8000000c, "NBT", "NuBits" ], + [ 0x8000000d, "MZC", "Mazacoin" ], + [ 0x8000000e, "VIA", "Viacoin" ], + [ 0x8000000f, "XCH", "ClearingHouse" ], + [ 0x80000010, "RBY", "Rubycoin" ], + [ 0x80000011, "GRS", "Groestlcoin" ], + [ 0x80000012, "DGC", "Digitalcoin" ], + [ 0x80000013, "CCN", "Cannacoin" ], + [ 0x80000014, "DGB", "DigiByte" ], + [ 0x80000015, "", "Open Assets" ], + [ 0x80000016, "MONA", "Monacoin" ], + [ 0x80000017, "CLAM", "Clams" ], + [ 0x80000018, "XPM", "Primecoin" ], + [ 0x80000019, "NEOS", "Neoscoin" ], + [ 0x8000001a, "JBS", "Jumbucks" ], + [ 0x8000001b, "ZRC", "ziftrCOIN" ], + [ 0x8000001c, "VTC", "Vertcoin" ], + [ 0x8000001d, "NXT", "NXT" ], + [ 0x8000001e, "BURST", "Burst" ], + [ 0x8000001f, "MUE", "MonetaryUnit" ], + [ 0x80000020, "ZOOM", "Zoom" ], + [ 0x80000021, "VASH", "Virtual Cash Also known as VPNcoin" ], + [ 0x80000022, "CDN", "Canada eCoin" ], + [ 0x80000023, "SDC", "ShadowCash" ], + [ 0x80000024, "PKB", "ParkByte" ], + [ 0x80000025, "PND", "Pandacoin" ], + [ 0x80000026, "START", "StartCOIN" ], + [ 0x80000027, "MOIN", "MOIN" ], + [ 0x80000028, "EXP", "Expanse" ], + [ 0x80000029, "EMC2", "Einsteinium" ], + [ 0x8000002a, "DCR", "Decred" ], + [ 0x8000002b, "XEM", "NEM" ], + [ 0x8000002c, "PART", "Particl" ], + [ 0x8000002d, "ARG", "Argentum" ], + [ 0x8000002e, "", "Libertas" ], + [ 0x8000002f, "", "Posw coin" ], + [ 0x80000030, "SHR", "Shreeji" ], + [ 0x80000031, "GCR", "Global Currency Reserve (GCRcoin)" ], + [ 0x80000032, "NVC", "Novacoin" ], + [ 0x80000033, "AC", "Asiacoin" ], + [ 0x80000034, "BTCD", "Bitcoindark" ], + [ 0x80000035, "DOPE", "Dopecoin" ], + [ 0x80000036, "TPC", "Templecoin" ], + [ 0x80000037, "AIB", "AIB" ], + [ 0x80000038, "EDRC", "EDRCoin" ], + [ 0x80000039, "SYS", "Syscoin" ], + [ 0x8000003a, "SLR", "Solarcoin" ], + [ 0x8000003b, "SMLY", "Smileycoin" ], + + [ 0x8000003d, "ETC", "Ether Classic" ], + [ 0x8000003e, "PSB", "Pesobit" ], + [ 0x8000003f, "LDCN", "Landcoin" ], + [ 0x80000040, "", "Open Chain" ], + [ 0x80000041, "XBC", "Bitcoinplus" ], + [ 0x80000042, "IOP", "Internet of People" ], + [ 0x80000043, "NXS", "Nexus" ], + [ 0x80000044, "INSN", "InsaneCoin" ], + [ 0x80000045, "OK", "OKCash" ], + [ 0x80000046, "BRIT", "BritCoin" ], + [ 0x80000047, "CMP", "Compcoin" ], + [ 0x80000048, "CRW", "Crown" ], + [ 0x80000049, "BELA", "BelaCoin" ], + [ 0x8000004a, "ICX", "ICON" ], + [ 0x8000004b, "FJC", "FujiCoin" ], + [ 0x8000004c, "MIX", "MIX" ], + [ 0x8000004e, "EFL", "Electronic Gulden" ], + [ 0x8000004f, "CLUB", "ClubCoin" ], + [ 0x80000050, "RICHX", "RichCoin" ], + [ 0x80000051, "POT", "Potcoin" ], + [ 0x80000052, "QRK", "Quarkcoin" ], + [ 0x80000053, "TRC", "Terracoin" ], + [ 0x80000054, "GRC", "Gridcoin" ], + [ 0x80000055, "AUR", "Auroracoin" ], + [ 0x80000056, "IXC", "IXCoin" ], + [ 0x80000057, "NLG", "Gulden" ], + [ 0x80000058, "BITB", "BitBean" ], + [ 0x80000059, "BTA", "Bata" ], + [ 0x8000005a, "XMY", "Myriadcoin" ], + [ 0x8000005b, "BSD", "BitSend" ], + [ 0x8000005c, "UNO", "Unobtanium" ], + [ 0x8000005d, "MTR", "MasterTrader" ], + [ 0x8000005e, "GB", "GoldBlocks" ], + [ 0x8000005f, "SHM", "Saham" ], + [ 0x80000060, "CRX", "Chronos" ], + [ 0x80000061, "BIQ", "Ubiquoin" ], + [ 0x80000062, "EVO", "Evotion" ], + [ 0x80000063, "STO", "SaveTheOcean" ], + [ 0x80000064, "BIGUP", "BigUp" ], + [ 0x80000065, "GAME", "GameCredits" ], + [ 0x80000066, "DLC", "Dollarcoins" ], + [ 0x80000067, "ZYD", "Zayedcoin" ], + [ 0x80000068, "DBIC", "Dubaicoin" ], + [ 0x80000069, "STRAT", "Stratis" ], + [ 0x8000006a, "SH", "Shilling" ], + [ 0x8000006b, "MARS", "MarsCoin" ], + [ 0x8000006c, "UBQ", "Ubiq" ], + [ 0x8000006d, "PTC", "Pesetacoin" ], + [ 0x8000006e, "NRO", "Neurocoin" ], + [ 0x8000006f, "ARK", "ARK" ], + [ 0x80000070, "USC", "UltimateSecureCashMain" ], + [ 0x80000071, "THC", "Hempcoin" ], + [ 0x80000072, "LINX", "Linx" ], + [ 0x80000073, "ECN", "Ecoin" ], + [ 0x80000074, "DNR", "Denarius" ], + [ 0x80000075, "PINK", "Pinkcoin" ], + [ 0x80000076, "ATOM", "Atom" ], + [ 0x80000077, "PIVX", "Pivx" ], + [ 0x80000078, "FLASH", "Flashcoin" ], + [ 0x80000079, "ZEN", "Zencash" ], + [ 0x8000007a, "PUT", "Putincoin" ], + [ 0x8000007b, "ZNY", "BitZeny" ], + [ 0x8000007c, "UNIFY", "Unify" ], + [ 0x8000007d, "XST", "StealthCoin" ], + [ 0x8000007e, "BRK", "Breakout Coin" ], + [ 0x8000007f, "VC", "Vcash" ], + [ 0x80000080, "XMR", "Monero" ], + [ 0x80000081, "VOX", "Voxels" ], + [ 0x80000082, "NAV", "NavCoin" ], + [ 0x80000083, "FCT", "Factom Factoids" ], + [ 0x80000084, "EC", "Factom Entry Credits" ], + [ 0x80000085, "ZEC", "Zcash" ], + [ 0x80000086, "LSK", "Lisk" ], + [ 0x80000087, "STEEM", "Steem" ], + [ 0x80000088, "XZC", "ZCoin" ], + [ 0x80000089, "RBTC", "RSK" ], + [ 0x8000008a, "", "Giftblock" ], + [ 0x8000008b, "RPT", "RealPointCoin" ], + [ 0x8000008c, "LBC", "LBRY Credits" ], + [ 0x8000008d, "KMD", "Komodo" ], + [ 0x8000008e, "BSQ", "bisq Token" ], + [ 0x8000008f, "RIC", "Riecoin" ], + [ 0x80000090, "XRP", "Ripple" ], + [ 0x80000091, "BCH", "Bitcoin Cash" ], + [ 0x80000092, "NEBL", "Neblio" ], + [ 0x80000093, "ZCL", "ZClassic" ], + [ 0x80000094, "XLM", "Stellar Lumens" ], + [ 0x80000095, "NLC2", "NoLimitCoin2" ], + [ 0x80000096, "WHL", "WhaleCoin" ], + [ 0x80000097, "ERC", "EuropeCoin" ], + [ 0x80000098, "DMD", "Diamond" ], + [ 0x80000099, "BTM", "Bytom" ], + [ 0x8000009a, "BIO", "Biocoin" ], + [ 0x8000009b, "XWC", "Whitecoin" ], + [ 0x8000009c, "BTG", "Bitcoin Gold" ], + [ 0x8000009d, "BTC2X", "Bitcoin 2x" ], + [ 0x8000009e, "SSN", "SuperSkynet" ], + [ 0x8000009f, "TOA", "TOACoin" ], + [ 0x800000a0, "BTX", "Bitcore" ], + [ 0x800000a1, "ACC", "Adcoin" ], + [ 0x800000a2, "BCO", "Bridgecoin" ], + [ 0x800000a3, "ELLA", "Ellaism" ], + [ 0x800000a4, "PIRL", "Pirl" ], + [ 0x800000a5, "XRB", "RaiBlocks" ], + [ 0x800000a6, "VIVO", "Vivo" ], + [ 0x800000a7, "FRST", "Firstcoin" ], + [ 0x800000a8, "HNC", "Helleniccoin" ], + [ 0x800000a9, "BUZZ", "BUZZ" ], + [ 0x800000aa, "MBRS", "Ember" ], + [ 0x800000ab, "HSR", "Hcash" ], + [ 0x800000ac, "HTML", "HTMLCOIN" ], + [ 0x800000ad, "ODN", "Obsidian" ], + [ 0x800000ae, "ONX", "OnixCoin" ], + [ 0x800000af, "RVN", "Ravencoin" ], + [ 0x800000b0, "GBX", "GoByte" ], + [ 0x800000b1, "BTCZ", "BitcoinZ" ], + [ 0x800000b2, "POA", "Poa" ], + [ 0x800000b3, "NYC", "NewYorkCoin" ], + [ 0x800000b4, "MXT", "MarteXcoin" ], + [ 0x800000b5, "WC", "Wincoin" ], + [ 0x800000b6, "MNX", "Minexcoin" ], + [ 0x800000b7, "BTCP", "Bitcoin Private" ], + [ 0x800000b8, "MUSIC", "Musicoin" ], + [ 0x800000b9, "BCA", "Bitcoin Atom" ], + [ 0x800000ba, "CRAVE", "Crave" ], + [ 0x800000bb, "STAK", "STRAKS" ], + [ 0x800000bc, "WBTC", "World Bitcoin" ], + [ 0x800000bd, "LCH", "LiteCash" ], + [ 0x800000be, "EXCL", "ExclusiveCoin" ], + [ 0x800000bf, "", "Lynx" ], + [ 0x800000c0, "LCC", "LitecoinCash" ], + [ 0x800000c1, "XFE", "Feirm" ], + [ 0x800000c2, "EOS", "EOS" ], + [ 0x800000c3, "TRX", "Tron" ], + [ 0x800000c4, "KOBO", "Kobocoin" ], + [ 0x800000c5, "HUSH", "HUSH" ], + [ 0x800000c6, "BANANO", "Bananos" ], + [ 0x800000c7, "ETF", "ETF" ], + [ 0x800000c8, "OMNI", "Omni" ], + [ 0x800000c9, "BIFI", "BitcoinFile" ], + [ 0x800000ca, "UFO", "Uniform Fiscal Object" ], + [ 0x800000cb, "CNMC", "Cryptonodes" ], + [ 0x800000cc, "BCN", "Bytecoin" ], + [ 0x800000cd, "RIN", "Ringo" ], + [ 0x800000ce, "ATP", "PlatON" ], + [ 0x800000cf, "EVT", "everiToken" ], + [ 0x800000d0, "ATN", "ATN" ], + [ 0x800000d1, "BIS", "Bismuth" ], + [ 0x800000d2, "NEET", "NEETCOIN" ], + [ 0x800000d3, "BOPO", "BopoChain" ], + [ 0x800000d4, "OOT", "Utrum" ], + [ 0x800000d5, "XSPEC", "Spectrecoin" ], + [ 0x800000d5, "MONK", "Monkey Project" ], + [ 0x800000d7, "BOXY", "BoxyCoin" ], + [ 0x800000d8, "FLO", "Flo" ], + [ 0x800000d9, "MEC", "Megacoin" ], + [ 0x800000da, "BTDX", "BitCloud" ], + [ 0x800000db, "XAX", "Artax" ], + [ 0x800000dc, "ANON", "ANON" ], + [ 0x800000dd, "LTZ", "LitecoinZ" ], + [ 0x800000de, "BITG", "Bitcoin Green" ], + [ 0x800000df, "ASK", "AskCoin" ], + [ 0x800000e0, "SMART", "Smartcash" ], + [ 0x800000e1, "XUEZ", "XUEZ" ], + [ 0x800000e2, "HLM", "Helium" ], + [ 0x800000e3, "WEB", "Webchain" ], + [ 0x800000e4, "ACM", "Actinium" ], + [ 0x800000e5, "NOS", "NOS Stable Coins" ], + [ 0x800000e6, "BITC", "BitCash" ], + [ 0x800000e7, "HTH", "Help The Homeless Coin" ], + [ 0x800000e8, "TZC", "Trezarcoin" ], + [ 0x800000e9, "VAR", "Varda" ], + [ 0x800000ea, "IOV", "IOV" ], + [ 0x800000eb, "FIO", "FIO" ], + [ 0x800000ec, "BSV", "BitcoinSV" ], + [ 0x800000ed, "DXN", "DEXON" ], + [ 0x800000ee, "QRL", "Quantum Resistant Ledger" ], + [ 0x800000ef, "PCX", "ChainX" ], + [ 0x800000f0, "LOKI", "Loki" ], + [ 0x800000f1, "", "Imagewallet" ], + [ 0x800000f2, "NIM", "Nimiq" ], + [ 0x800000f3, "SOV", "Sovereign Coin" ], + [ 0x800000f4, "JCT", "Jibital Coin" ], + [ 0x800000f5, "SLP", "Simple Ledger Protocol" ], + [ 0x800000f6, "EWT", "Energy Web" ], + [ 0x800000f7, "UC", "Ulord" ], + [ 0x800000f8, "EXOS", "EXOS" ], + [ 0x800000f9, "ECA", "Electra" ], + [ 0x800000fa, "SOOM", "Soom" ], + [ 0x800000fb, "XRD", "Redstone" ], + [ 0x800000fc, "FREE", "FreeCoin" ], + [ 0x800000fd, "NPW", "NewPowerCoin" ], + [ 0x800000fe, "BST", "BlockStamp" ], + [ 0x800000ff, "", "SmartHoldem" ], + [ 0x80000100, "NANO", "Bitcoin Nano" ], + [ 0x80000101, "BTCC", "Bitcoin Core" ], + [ 0x80000102, "", "Zen Protocol" ], + [ 0x80000103, "ZEST", "Zest" ], + [ 0x80000104, "ABT", "ArcBlock" ], + [ 0x80000105, "PION", "Pion" ], + [ 0x80000106, "DT3", "DreamTeam3" ], + [ 0x80000107, "ZBUX", "Zbux" ], + [ 0x80000108, "KPL", "Kepler" ], + [ 0x80000109, "TPAY", "TokenPay" ], + [ 0x8000010a, "ZILLA", "ChainZilla" ], + [ 0x8000010b, "ANK", "Anker" ], + [ 0x8000010c, "BCC", "BCChain" ], + [ 0x8000010d, "HPB", "HPB" ], + [ 0x8000010e, "ONE", "ONE" ], + [ 0x8000010f, "SBC", "SBC" ], + [ 0x80000110, "IPC", "IPChain" ], + [ 0x80000111, "DMTC", "Dominantchain" ], + [ 0x80000112, "OGC", "Onegram" ], + [ 0x80000113, "SHIT", "Shitcoin" ], + [ 0x80000114, "ANDES", "Andescoin" ], + [ 0x80000115, "AREPA", "Arepacoin" ], + [ 0x80000116, "BOLI", "Bolivarcoin" ], + [ 0x80000117, "RIL", "Rilcoin" ], + [ 0x80000118, "HTR", "Hathor Network" ], + [ 0x80000119, "FCTID", "Factom ID" ], + [ 0x8000011a, "BRAVO", "BRAVO" ], + [ 0x8000011b, "ALGO", "Algorand" ], + [ 0x8000011c, "BZX", "Bitcoinzero" ], + [ 0x8000011d, "GXX", "GravityCoin" ], + [ 0x8000011e, "HEAT", "HEAT" ], + [ 0x8000011f, "XDN", "DigitalNote" ], + [ 0x80000120, "FSN", "FUSION" ], + [ 0x80000121, "CPC", "Capricoin" ], + [ 0x80000122, "BOLD", "Bold" ], + [ 0x80000123, "IOST", "IOST" ], + [ 0x80000124, "TKEY", "Tkeycoin" ], + [ 0x80000125, "USE", "Usechain" ], + [ 0x80000126, "BCZ", "BitcoinCashZero" ], + [ 0x80000127, "IOC", "Iocoin" ], + [ 0x8000012b, "NUKO", "Nekonium" ], + [ 0x8000012c, "GNX", "Genaro Network" ], + [ 0x8000012d, "DIVI", "Divi Project" ], + [ 0x8000012f, "EUNO", "EUNO" ], + [ 0x80000137, "ADS", "Adshares" ], + [ 0x80000138, "ARA", "Aura" ], + [ 0x80000139, "ZIL", "Zilliqa" ], + [ 0x8000013a, "MOAC", "MOAC" ], + [ 0x8000013b, "SWTC", "SWTC" ], + [ 0x8000013c, "VNSC", "vnscoin" ], + [ 0x80000141, "RAP", "Rapture" ], + [ 0x80000142, "GARD", "Hashgard" ], + [ 0x80000148, "BLOCK", "Blocknet" ], + [ 0x8000014d, "MEM", "MemCoin" ], + [ 0x80000171, "XAS", "Asch" ], + [ 0x80000180, "XSN", "Stakenet" ], + [ 0x80000188, "CENNZ", "CENNZnet" ], + [ 0x8000018e, "XPC", "XPChain" ], + [ 0x80000190, "NIX", "NIX" ], + [ 0x80000194, "XBI", "XBI" ], + [ 0x8000019c, "AIN", "AIN" ], + [ 0x800001a0, "SLX", "SLX" ], + [ 0x800001a4, "NODE", "NodeHost" ], + [ 0x800001a9, "AION", "Aion" ], + [ 0x800001bc, "PHR", "Phore" ], + [ 0x800001bf, "DIN", "Dinero" ], + [ 0x800001c9, "AE", "æternity" ], + [ 0x800001d0, "ETI", "EtherInc" ], + [ 0x800001e8, "VEO", "Amoveo" ], + [ 0x800001f4, "THETA", "Theta" ], + [ 0x800001fe, "KOTO", "Koto" ], + [ 0x80000200, "XRD", "Radiant" ], + [ 0x80000204, "VEE", "Virtual Economy Era" ], + [ 0x80000206, "LET", "Linkeye" ], + [ 0x80000208, "BTCV", "BitcoinVIP" ], + [ 0x8000020e, "BU", "BUMO" ], + [ 0x80000210, "YAP", "Yapstone" ], + [ 0x80000215, "PRJ", "ProjectCoin" ], + [ 0x8000022b, "BCS", "Bitcoin Smart" ], + [ 0x8000022d, "LKR", "Lkrcoin" ], + [ 0x80000258, "UTE", "Unit-e" ], + [ 0x8000026a, "SSP", "SmartShare" ], + [ 0x80000271, "EAST", "Eastcoin" ], + [ 0x80000297, "SFRX", "EtherGem Sapphire" ], + [ 0x8000029a, "ACT", "Achain" ], + [ 0x8000029c, "SSC", "SelfSell" ], + [ 0x800002ba, "VEIL", "Veil" ], + [ 0x800002bc, "XDAI", "xDai" ], + [ 0x800002c9, "KTS", "Katallassos" ], + [ 0x800002ca, "BNB", "Binance" ], + [ 0x80000300, "BALLZ", "Ballzcoin" ], + [ 0x80000309, "BTW", "Bitcoin World" ], + [ 0x80000320, "BEET", "Beetle Coin" ], + [ 0x80000321, "DST", "DSTRA" ], + [ 0x80000328, "QVT", "Qvolta" ], + [ 0x80000332, "VET", "VeChain Token" ], + [ 0x80000334, "CLO", "Callisto" ], + [ 0x80000376, "ADF", "AD Token" ], + [ 0x80000378, "NEO", "NEO" ], + [ 0x80000379, "TOMO", "TOMO" ], + [ 0x8000037a, "XSEL", "Seln" ], + [ 0x80000384, "LMO", "Lumeneo" ], + [ 0x80000394, "META", "Metadium" ], + [ 0x800003ca, "TWINS", "TWINS" ], + [ 0x800003e4, "OKP", "OK Points" ], + [ 0x800003e6, "LBTC", "Lightning Bitcoin" ], + [ 0x800003e7, "BCD", "Bitcoin Diamond" ], + [ 0x800003e8, "BTN", "Bitcoin New" ], + [ 0x800003e9, "TT", "ThunderCore" ], + [ 0x800003ea, "BKT", "BanKitt" ], + [ 0x80000400, "ONT", "Ontology" ], + [ 0x80000457, "BBC", "Big Bitcoin" ], + [ 0x80000460, "RISE", "RISE" ], + [ 0x80000462, "CMT", "CyberMiles Token" ], + [ 0x80000468, "ETSC", "Ethereum Social" ], + [ 0x80000479, "CDY", "Bitcoin Candy" ], + [ 0x80000539, "DFC", "Defcoin" ], + [ 0x80000575, "HYC", "Hycon" ], + [ 0x800005f4, "", "Taler" ], + [ 0x80000654, "ATH", "Atheios" ], + [ 0x80000698, "BCX", "BitcoinX" ], + [ 0x800006c1, "XTZ", "Tezos" ], + [ 0x800006f0, "LBTC", "Liquid BTC" ], + [ 0x80000717, "ADA", "Cardano" ], + [ 0x80000743, "TES", "Teslacoin" ], + [ 0x8000076d, "CLC", "Classica" ], + [ 0x8000077f, "VIPS", "VIPSTARCOIN" ], + [ 0x80000786, "CITY", "City Coin" ], + [ 0x800007b9, "XMX", "Xuma" ], + [ 0x800007c0, "TRTL", "TurtleCoin" ], + [ 0x800007c3, "EGEM", "EtherGem" ], + [ 0x800007c5, "HODL", "HOdlcoin" ], + [ 0x800007c6, "PHL", "Placeholders" ], + [ 0x800007cd, "POLIS", "Polis" ], + [ 0x800007ce, "XMCC", "Monoeci" ], + [ 0x800007cf, "COLX", "ColossusXT" ], + [ 0x800007d0, "GIN", "GinCoin" ], + [ 0x800007d1, "MNP", "MNPCoin" ], + [ 0x800007e1, "KIN", "Kin" ], + [ 0x800007e2, "EOSC", "EOSClassic" ], + [ 0x800007e3, "GBT", "GoldBean Token" ], + [ 0x800007e4, "PKC", "PKC" ], + [ 0x80000801, "TRUE", "TrueChain" ], + [ 0x80000840, "IoTE", "IoTE" ], + [ 0x800008fd, "QTUM", "QTUM" ], + [ 0x800008fe, "ETP", "Metaverse" ], + [ 0x800008ff, "GXC", "GXChain" ], + [ 0x80000900, "CRP", "CranePay" ], + [ 0x80000901, "ELA", "Elastos" ], + [ 0x80000922, "SNOW", "Snowblossom" ], + [ 0x80000a0a, "AOA", "Aurora" ], + [ 0x80000b4e, "REOSC", "REOSC Ecosystem" ], + [ 0x80000bbb, "LUX", "LUX" ], + [ 0x80000d35, "DYN", "Dynamic" ], + [ 0x80000d37, "SEQ", "Sequence" ], + [ 0x80000de0, "DEO", "Destocoin" ], + [ 0x80000dec, "DST", "DeStream" ], + [ 0x80000a9e, "NAS", "Nebulas" ], + [ 0x80000b7d, "BND", "Blocknode" ], + [ 0x80000ccc, "CCC", "CodeChain" ], + [ 0x80000d31, "ROI", "ROIcoin" ], + [ 0x8000107a, "IOTA", "IOTA" ], + [ 0x80001092, "AXE", "Axe" ], + [ 0x00001480, "FIC", "FIC" ], + [ 0x000014e9, "HNS", "Handshake" ], + [ 0x8000167d, "", "Stacks" ], + [ 0x80001720, "SLU", "SILUBIUM" ], + [ 0x800017ac, "GO", "GoChain GO" ], + [ 0x80001a0a, "BPA", "Bitcoin Pizza" ], + [ 0x80001a20, "SAFE", "SAFE" ], + [ 0x80001b39, "ROGER", "TheHolyrogerCoin" ], + [ 0x80001e61, "BTV", "Bitvote" ], + [ 0x80002093, "BTQ", "BitcoinQuark" ], + [ 0x800022b8, "SBTC", "Super Bitcoin" ], + [ 0x80002304, "NULS", "NULS" ], + [ 0x80002327, "BTP", "Bitcoin Pay" ], + [ 0x80002645, "NRG", "Energi" ], + [ 0x800026a0, "BTF", "Bitcoin Faith" ], + [ 0x8000270f, "GOD", "Bitcoin God" ], + [ 0x80002710, "FO", "FIBOS" ], + [ 0x80002833, "BTR", "Bitcoin Rhodium" ], + [ 0x80002B67, "ESS", "Essentia One" ], + [ 0x80003333, "BTY", "BitYuan" ], + [ 0x80003334, "YCC", "Yuan Chain Coin" ], + [ 0x80003de5, "SDGO", "SanDeGo" ], + [ 0x80004add, "SAFE", "Safecoin" ], + [ 0x80004adf, "ZEL", "ZelCash" ], + [ 0x80004ae1, "RITO", "Ritocoin" ], + [ 0x80004e44, "NDAU", "ndau" ], + [ 0x800057e8, "PWR", "PWRcoin" ], + [ 0x800062a4, "BELL", "Bellcoin" ], + [ 0x8000797e, "ESN", "EtherSocial Network" ], + [ 0x80007a69, "", "ThePower.io" ], + [ 0x80008456, "BTCS", "Bitcoin Stake" ], + [ 0x80008888, "BTT", "ByteTrade" ], + [ 0x80009468, "FXTC", "FixedTradeCoin" ], + [ 0x80009999, "AMA", "Amabig" ], + [ 0x80010000, "KETH", "Krypton World" ], + [ 0x80015b38, "RYO", "c0ban" ], + [ 0x8001869f, "WICC", "Waykichain" ], + [ 0x80030fb1, "AKA", "Akroma" ], + [ 0x80011000, "GENOM", "GENOM" ], + [ 0x8003C301, "ATS", "ARTIS sigma1" ], + [ 0x80067932, "X42", "x42" ], + [ 0x800a2c2a, "VITE", "Vite" ], + [ 0x8011df89, "ILT", "iOlite" ], + [ 0x8014095a, "ETHO", "Ether-1" ], + [ 0x801a2010, "LAX", "LAPO" ], + [ 0x80501949, "BCO", "BitcoinOre" ], + [ 0x8050194a, "BHD", "BitcoinHD" ], + [ 0x8050544e, "PTN", "PalletOne" ], + [ 0x8057414e, "WAN", "Wanchain" ], + [ 0x80579bfc, "WAVES", "Waves" ], + [ 0x8073656d, "SEM", "Semux" ], + [ 0x80737978, "ION", "ION" ], + [ 0x80776772, "WGR", "WGR" ], + [ 0x80776773, "OBSR", "OBServer" ], + [ 0x83adbc39, "AQUA", "Aquachain" ], + [ 0x857ab1e1, "kUSD", "kUSD" ], + [ 0x85f5e0fe, "FLUID", "Fluid Chains" ], + [ 0x85f5e0ff, "QKC", "QuarkChain" ] */ +] diff --git a/crypto/common/ext/networks-constants.js b/crypto/common/ext/networks-constants.js new file mode 100644 index 000000000..58860f9cf --- /dev/null +++ b/crypto/common/ext/networks-constants.js @@ -0,0 +1,104 @@ +// https://github.com/iancoleman/bip39/blob/0a23f51792722f094328d695242556c4c0195a8b/src/js/bitcoinjs-extensions.js +const bitcoin = require('bitcoinjs-lib') + +module.exports = { + 'mainnet': { + network: bitcoin.networks.bitcoin, + langPrefix: 'btc' + }, + 'testnet': { + network: bitcoin.networks.testnet, + langPrefix: 'btc' + }, + 'litecoin': { + network: { + bech32: 'ltc', + messagePrefix: '\x19Litecoin Signed Message:\n', + pubKeyHash: 0x30, // change to 0x6f + scriptHash: 0x32, + wif: 0xb0, + bip32: { + public: 0x019da462, + private: 0x019d9cfe + } + }, + langPrefix: 'ltc' + }, + 'dogecoin': { + network: { + messagePrefix: '\x19Dogecoin Signed Message:\n', + bip32: { + public: 0x02facafd, + private: 0x02fac398 + }, + pubKeyHash: 0x1e, + scriptHash: 0x16, + wif: 0x9e + }, + langPrefix: 'ltc' + }, + 'verge': { + network: { + messagePrefix: '\x18VERGE Signed Message:\n', + bip32: { + public: 0x0488b21e, + private: 0x0488ade4 + }, + pubKeyHash: 0x1e, + scriptHash: 0x21, + wif: 0x9e + }, + langPrefix: 'ltc' + }, + 'bitcoincash': { + network: { + + /* messagePrefix: 'unused', + bip32: { + public: 0x0488b21e, + private: 0x0488ade4 + }, + pubKeyHash: 0x00, + scriptHash: 0x05, + wif: 0x80 + */ + messagePrefix: '\u0018Bitcoin Signed Message:\n', + bech32: 'bc', + bip32: { public: 76067358, private: 76066276 }, + pubKeyHash: 0, + scriptHash: 5, + wif: 128, + BTCFork : 'BCH' + }, + langPrefix: 'bch' + }, + 'bitcoinsv': { + network: { + messagePrefix: 'unused', + bip32: { + public: 0x0488b21e, + private: 0x0488ade4 + }, + pubKeyHash: 0x00, + scriptHash: 0x05, + wif: 0x80, + BTCFork : 'BCH' + }, + langPrefix: 'bch' + }, + 'bitcoingold': { + network: { + bech32: 'btg', + messagePrefix: '\x1DBitcoin Gold Signed Message:\n', + bip32: { + public: 0x0488b21e, + private: 0x0488ade4 + }, + pubKeyHash: 38, + scriptHash: 23, + wif: 128, + BTCFork : 'BTG' + }, + langPrefix: 'ltc' + } +} diff --git a/crypto/common/ext/scam-seeds.js b/crypto/common/ext/scam-seeds.js new file mode 100644 index 000000000..6c5b9bde0 --- /dev/null +++ b/crypto/common/ext/scam-seeds.js @@ -0,0 +1,4 @@ +module.exports = { + 'blue boost talk hero praise enemy sleep extra toddler escape ankle silk' : 1, + 'pass sugar city plate comfort tube filter merit oak trim frown love' : 1, +} diff --git a/crypto/readme.md b/crypto/readme.md new file mode 100644 index 000000000..dfac66816 --- /dev/null +++ b/crypto/readme.md @@ -0,0 +1,43 @@ +# This is crypto actions and libraries for the Trustee Wallet + +## Please follow general style and testing rules + +- All files should be checked with eslint for autoformatting etc + +- All new functions should be added with tests, that could be performed on code review +at any environment (max some manual works that will be needed is airdrop of btc to test address +or cache update for utxo`s) + +- All changes in existing code should not break existing tests (max some manual...) + + + +## Testing tx send uses + +#### BTC_TEST + +crypto/blockchains/btc/tests/providers/BtcTxUnspentsProvider.test.withTxSend.test.js + +mmtxZNdw5L7ugQcCRCZJZinSicgMGsDEK3 cPqjJ1iASqCaC1YsCcBUixYK6jQrcjF7qshGLGMxpprRiHdDDsRd + +#### BTC_TEST ROUND + +used at crypto/actions/BlocksoftTransaction/tests/BlocksoftTransaction.test.Send.code.BTC.test.js + +mggtxjLhuWM8zWCxY7DXE3UWNXWdEjjs51 cUhyqBWAFhAWmJcq6Tqbj1kKrZFhxXVc8aRDgLZJFpYRRPBpoLuf + +msTNzykR2TaousFS7S6pLSCesnkLDohewm cMfQmbELgNERpBXpf57wU3uxZG47nAkuGhSVZRb3QogevptgAG5b + +#### ETH_ROPSTEN + +0x103f8f95c7539A87968C2F5044c02c5A17066177 0xdce0ecf2b36759f68761e5a21dfa124b06d03aeda52cb5968139689432f9a1aa + +0xcb133e23A3461984aB4d6F48AcB6d3bf2aD61Ad2 0xdb30610f156e1d4aefaa9b4423909297ceff64c2 + +#### ETH_ROPSTEN ROUND + +used at crypto/actions/BlocksoftTransaction/tests/BlocksoftTransaction.test.Send.code.ETH.test.js + +0xA61846D2054ACc63d3869c52ae69180BF649615e 0xfdc00ea004a8d77ec744f0bf35fcb6e7c8fa7402d3e9c8b6d4da2e9b1d979706 + +0x087e3D9C42C39149Ed62fAd0C4698fb8CE59fCf9 0xd49b3b91ae567ed1f1e45e88d696a575066c30fde1cc4f9e2ef27607d89af90f diff --git a/crypto/services/EnsUtils.js b/crypto/services/EnsUtils.js new file mode 100644 index 000000000..8c2b8b713 --- /dev/null +++ b/crypto/services/EnsUtils.js @@ -0,0 +1,25 @@ +/** + * @version 0.41 + */ +import { Web3Injected } from '@crypto/services/Web3Injected' + +/** + * @param address + * @returns {boolean} + */ +export const isEnsAddressValid = (address) => { + if (address) { + try { + return (/^.+\.eth$/.test(address)) + } catch (e) { + + } + } + return false +} + +export const getEnsAddress = async (address) => { + const WEB3 = Web3Injected('mainnet') + const res = await WEB3.eth.ens.getAddress(address) + return res +} diff --git a/crypto/services/UnstoppableUtils.js b/crypto/services/UnstoppableUtils.js new file mode 100644 index 000000000..ac6ad5ee4 --- /dev/null +++ b/crypto/services/UnstoppableUtils.js @@ -0,0 +1,18 @@ +/** + * @version 0.41 + */ + +/** + * @param address + * @returns {boolean} + */ +export const isUnstoppableAddressValid = (address) => { + if (address) { + try { + return (/^.+\.crypto$/.test(address)) + } catch (e) { + + } + } + return false +} diff --git a/crypto/services/Web3Injected.js b/crypto/services/Web3Injected.js new file mode 100644 index 000000000..a35c48765 --- /dev/null +++ b/crypto/services/Web3Injected.js @@ -0,0 +1,87 @@ +/** + * @version 0.41 + */ +import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings' + +const Web3 = require('web3') +const CACHE_WEB3 = {} + +export const Web3Injected = (type) => { + let WEB3_LINK = `https://mainnet.infura.io/v3/${BlocksoftExternalSettings.getStatic('ETH_INFURA')}` + let MAIN_CURRENCY_CODE = 'ETH' + let MAIN_CHAIN_ID = 1 + if (!type || type === 0 || type === 1 || type === 'ethereum' || type === 'ETH' || type === 'mainnet') { + MAIN_CURRENCY_CODE = 'ETH' + MAIN_CHAIN_ID = 1 + } else if (type === 10001 || type === 'eth_pow' || type === 'ETH_POW' || type === 'ETHW' || type === 'ethw') { + MAIN_CURRENCY_CODE = 'ETH_POW' + WEB3_LINK = BlocksoftExternalSettings.getStatic('ETH_POW_SERVER') + MAIN_CHAIN_ID = 10001 + } else if (type === 3 || type === 'ropsten' || type === 'ETH_ROPSTEN') { + MAIN_CURRENCY_CODE = 'ETH_ROPSTEN' + WEB3_LINK = `https://ropsten.infura.io/v3/${BlocksoftExternalSettings.getStatic('ETH_INFURA')}` + MAIN_CHAIN_ID = 3 + } else if (type === 4 || type === 'rinkeby' || type === 'ETH_RINKEBY') { + MAIN_CURRENCY_CODE = 'ETH_RINKEBY' + WEB3_LINK = `https://rinkeby.infura.io/v3/${BlocksoftExternalSettings.getStatic('ETH_INFURA')}` + MAIN_CHAIN_ID = 4 + } else if (type === 56 || type === 'bnb' || type === 'BNB_SMART') { + MAIN_CURRENCY_CODE = 'BNB_SMART' + WEB3_LINK = BlocksoftExternalSettings.getStatic('BNB_SMART_SERVER') + MAIN_CHAIN_ID = 56 + } else if (type === 1088 || type === 'METIS') { + MAIN_CURRENCY_CODE = 'METIS' + WEB3_LINK = BlocksoftExternalSettings.getStatic('METIS_SERVER') + MAIN_CHAIN_ID = 1088 + } else if (type === 199 || type === 'BTTC') { + MAIN_CURRENCY_CODE = 'BTTC' + WEB3_LINK = BlocksoftExternalSettings.getStatic('BTTC_SERVER') + MAIN_CHAIN_ID = 199 + } else if (type === 106 || type === 'VLX') { + MAIN_CURRENCY_CODE = 'VLX' + WEB3_LINK = BlocksoftExternalSettings.getStatic('VLX_SERVER') + MAIN_CHAIN_ID = 106 // https://docs.velas.com/clusters/ + } else if (type === 1666600000 || type === 'ONE') { + MAIN_CURRENCY_CODE = 'ONE' + WEB3_LINK = BlocksoftExternalSettings.getStatic('ONE_SERVER') + MAIN_CHAIN_ID = 1666600000 + } else if (type === 10 || type === 'OPTIMISM') { + MAIN_CURRENCY_CODE = 'OPTIMISM' + WEB3_LINK = BlocksoftExternalSettings.getStatic('OPTIMISM_SERVER') + MAIN_CHAIN_ID = 10 + } else if (type === 137 || type === 'MATIC') { + MAIN_CURRENCY_CODE = 'MATIC' + WEB3_LINK = BlocksoftExternalSettings.getStatic('MATIC_SERVER') + MAIN_CHAIN_ID = 137 + } else if (type === 250 || type === 'FTM') { + MAIN_CURRENCY_CODE = 'FTM' + WEB3_LINK = BlocksoftExternalSettings.getStatic('FTM_SERVER') + MAIN_CHAIN_ID = 250 + } else if (type === 16718 || type === 'AMB') { + MAIN_CURRENCY_CODE = 'AMB' + WEB3_LINK = BlocksoftExternalSettings.getStatic('AMB_SERVER') + MAIN_CHAIN_ID = 16718 + } else if (type === 61 || type === 'ETC') { + MAIN_CURRENCY_CODE = 'ETC' + WEB3_LINK = BlocksoftExternalSettings.getStatic('ETC_SERVER') + MAIN_CHAIN_ID = 61 + } else if (type === 30 || type === 'RSK') { + MAIN_CURRENCY_CODE = 'RSK' + WEB3_LINK = BlocksoftExternalSettings.getStatic('RSK_SERVER') + MAIN_CHAIN_ID = 30 + } else { + throw new Error('PLEASE ADD SUPPORT FOR ETH NETWORK ' + type) + } + + if (typeof CACHE_WEB3[WEB3_LINK] !== 'undefined') { + return CACHE_WEB3[WEB3_LINK] + } + + const WEB3 = new Web3(new Web3.providers.HttpProvider(WEB3_LINK)) + WEB3.MAIN_CURRENCY_CODE = MAIN_CURRENCY_CODE + WEB3.LINK = WEB3_LINK + WEB3.MAIN_CHAIN_ID = MAIN_CHAIN_ID + CACHE_WEB3[WEB3_LINK] = WEB3 + + return WEB3 +} diff --git a/package.json b/package.json index b3e7858bf..53c3ec2df 100644 --- a/package.json +++ b/package.json @@ -38,8 +38,10 @@ "@testing-library/jest-dom": "^5.16.5", "@testing-library/react-native": "^12.1.1", "@types/jest": "^29.5.1", + "@waves/ts-lib-crypto": "^1.4.4-beta.1", "axios": "^1.3.4", "bip39": "^3.1.0", + "bitcoinjs-lib": "git+https://git@github.com/trustee-wallet/bitcoinjs-lib", "buffer": "^6.0.3", "expo": "~48.0.18", "expo-barcode-scanner": "~12.3.2", diff --git a/src/lib/BlocksoftDispatcher.js b/src/lib/BlocksoftDispatcher.js new file mode 100644 index 000000000..ce3c173b2 --- /dev/null +++ b/src/lib/BlocksoftDispatcher.js @@ -0,0 +1,254 @@ +/** + * @author Ksu + * @version 0.5 + */ +import BlocksoftDict from '../common/BlocksoftDict' + +import BchAddressProcessor from './bch/BchAddressProcessor' +import BchScannerProcessor from './bch/BchScannerProcessor' + +import BsvScannerProcessor from './bsv/BsvScannerProcessor' + +import BtcAddressProcessor from './btc/address/BtcAddressProcessor' +import BtcScannerProcessor from './btc/BtcScannerProcessor' + +import BtcSegwitCompatibleAddressProcessor from './btc/address/BtcSegwitCompatibleAddressProcessor' +import BtcSegwitAddressProcessor from './btc/address/BtcSegwitAddressProcessor' + +import BtcTestScannerProcessor from './btc_test/BtcTestScannerProcessor' + +import BtgScannerProcessor from './btg/BtgScannerProcessor' + +import DogeScannerProcessor from './doge/DogeScannerProcessor' + +import EthAddressProcessor from './eth/EthAddressProcessor' +import EthScannerProcessor from './eth/EthScannerProcessor' +import EthScannerProcessorErc20 from './eth/EthScannerProcessorErc20' +import EthScannerProcessorSoul from './eth/forks/EthScannerProcessorSoul' +import EthTokenProcessorErc20 from './eth/EthTokenProcessorErc20' + +import LtcScannerProcessor from './ltc/LtcScannerProcessor' + +import TrxAddressProcessor from './trx/TrxAddressProcessor' +import TrxScannerProcessor from './trx/TrxScannerProcessor' +import TrxTokenProcessor from './trx/TrxTokenProcessor' + +import UsdtScannerProcessor from './usdt/UsdtScannerProcessor' + +import XrpAddressProcessor from './xrp/XrpAddressProcessor' +import XrpScannerProcessor from './xrp/XrpScannerProcessor' + +import XlmAddressProcessor from './xlm/XlmAddressProcessor' +import XlmScannerProcessor from './xlm/XlmScannerProcessor' + +import XvgScannerProcessor from './xvg/XvgScannerProcessor' + +import XmrAddressProcessor from './xmr/XmrAddressProcessor' +import XmrScannerProcessor from './xmr/XmrScannerProcessor' +import XmrSecretsProcessor from './xmr/XmrSecretsProcessor' +import FioAddressProcessor from './fio/FioAddressProcessor' +import FioScannerProcessor from './fio/FioScannerProcessor' + + +import BnbAddressProcessor from './bnb/BnbAddressProcessor' +import BnbScannerProcessor from './bnb/BnbScannerProcessor' +import VetScannerProcessor from '@crypto/blockchains/vet/VetScannerProcessor' + +import SolAddressProcessor from '@crypto/blockchains/sol/SolAddressProcessor' +import SolScannerProcessor from '@crypto/blockchains/sol/SolScannerProcessor' + +import WavesAddressProcessor from '@crypto/blockchains/waves/WavesAddressProcessor' +import WavesScannerProcessor from '@crypto/blockchains/waves/WavesScannerProcessor' + +import SolScannerProcessorSpl from '@crypto/blockchains/sol/SolScannerProcessorSpl' +import SolTokenProcessor from '@crypto/blockchains/sol/SolTokenProcessor' +import EthTokenProcessorNft from '@crypto/blockchains/eth/EthTokenProcessorNft' +import AshAddressProcessor from '@crypto/blockchains/ash/AshAddressProcessor' + +import MetisScannerProcessor from '@crypto/blockchains/metis/MetisScannerProcessor' +import OneScannerProcessor from '@crypto/blockchains/one/OneScannerProcessor' +import OneScannerProcessorErc20 from '@crypto/blockchains/one/OneScannerProcessorErc20' + +import WavesScannerProcessorErc20 from '@crypto/blockchains/waves/WavesScannerProcessorErc20' + +class BlocksoftDispatcher { + + /** + * @param {string} currencyCode + * @return {EthAddressProcessor|BtcAddressProcessor} + */ + getAddressProcessor(currencyCode) { + const currencyDictSettings = BlocksoftDict.getCurrencyAllSettings(currencyCode) + return this.innerGetAddressProcessor(currencyDictSettings) + } + + /** + * @param {Object} currencyDictSettings + * @return {EthAddressProcessor|BtcAddressProcessor|TrxAddressProcessor} + */ + innerGetAddressProcessor(currencyDictSettings) { + switch (currencyDictSettings.addressProcessor) { + case 'BCH': + return new BchAddressProcessor(currencyDictSettings) + case 'BTC': + return new BtcAddressProcessor(currencyDictSettings) + case 'BTC_SEGWIT': case 'LTC_SEGWIT': + return new BtcSegwitAddressProcessor(currencyDictSettings) + case 'BTC_SEGWIT_COMPATIBLE': + return new BtcSegwitCompatibleAddressProcessor(currencyDictSettings) + case 'ETH': + return new EthAddressProcessor(currencyDictSettings) + case 'TRX': + return new TrxAddressProcessor() + case 'XRP': + return new XrpAddressProcessor() + case 'XLM': + return new XlmAddressProcessor() + case 'XMR': + return new XmrAddressProcessor() + case 'FIO': + return new FioAddressProcessor() + case 'BNB': + return new BnbAddressProcessor() + case 'SOL': + return new SolAddressProcessor() + case 'WAVES': + return new WavesAddressProcessor() + case 'ASH' : + return new AshAddressProcessor() + default: + throw new Error('Unknown addressProcessor ' + currencyDictSettings.addressProcessor) + } + } + + /** + * @param {string} currencyCode + * @returns {BsvScannerProcessor|BtcScannerProcessor|UsdtScannerProcessor|EthScannerProcessorErc20|BchScannerProcessor|LtcScannerProcessor|XvgScannerProcessor|BtcTestScannerProcessor|DogeScannerProcessor|EthScannerProcessorSoul|EthScannerProcessor|BtgScannerProcessor|TrxScannerProcessor} + */ + getScannerProcessor(currencyCode) { + const currencyDictSettings = BlocksoftDict.getCurrencyAllSettings(currencyCode) + switch (currencyDictSettings.scannerProcessor) { + case 'BCH': + return new BchScannerProcessor(currencyDictSettings) + case 'BSV': + return new BsvScannerProcessor(currencyDictSettings) + case 'BTC': case 'BTC_SEGWIT': case 'BTC_SEGWIT_COMPATIBLE': + return new BtcScannerProcessor(currencyDictSettings) + case 'BTC_TEST': + return new BtcTestScannerProcessor(currencyDictSettings) + case 'BTG': + return new BtgScannerProcessor(currencyDictSettings) + case 'DOGE': + return new DogeScannerProcessor(currencyDictSettings) + case 'ETH': + return new EthScannerProcessor(currencyDictSettings) + case 'ETH_ERC_20': + return new EthScannerProcessorErc20(currencyDictSettings) + case 'ETH_SOUL': + return new EthScannerProcessorSoul(currencyDictSettings) + case 'LTC': + return new LtcScannerProcessor(currencyDictSettings) + case 'TRX': + return new TrxScannerProcessor(currencyDictSettings) + case 'USDT': + return new UsdtScannerProcessor(currencyDictSettings) + case 'XRP': + return new XrpScannerProcessor(currencyDictSettings) + case 'XLM': + return new XlmScannerProcessor(currencyDictSettings) + case 'XVG': + return new XvgScannerProcessor(currencyDictSettings) + case 'XMR': + return new XmrScannerProcessor(currencyDictSettings) + case 'FIO': + return new FioScannerProcessor(currencyDictSettings) + case 'BNB': + return new BnbScannerProcessor(currencyDictSettings) + case 'VET': + return new VetScannerProcessor(currencyDictSettings) + case 'SOL': + return new SolScannerProcessor(currencyDictSettings) + case 'SOL_SPL': + return new SolScannerProcessorSpl(currencyDictSettings) + case 'WAVES': + return new WavesScannerProcessor(currencyDictSettings) + case 'METIS': + return new MetisScannerProcessor(currencyDictSettings) + case 'ONE': + return new OneScannerProcessor(currencyDictSettings) + case 'ONE_ERC_20': + return new OneScannerProcessorErc20(currencyDictSettings) + case 'WAVES_ERC_20': + return new WavesScannerProcessorErc20(currencyDictSettings) + default: + throw new Error('Unknown scannerProcessor ' + currencyDictSettings.scannerProcessor) + } + } + + /** + * @param tokenType + * @returns {TrxTokenProcessor|EthTokenProcessorErc20} + */ + getTokenProcessor(tokenType) { + switch (tokenType) { + case 'ETH_ERC_20': + return new EthTokenProcessorErc20({ network: 'mainnet', tokenBlockchain: 'ETHEREUM' }) + case 'BNB_SMART_20': + return new EthTokenProcessorErc20({ network: 'mainnet', tokenBlockchain : 'BNB' }) + case 'MATIC_ERC_20': + return new EthTokenProcessorErc20({ network: 'mainnet', tokenBlockchain : 'MATIC' }) + case 'FTM_ERC_20': + return new EthTokenProcessorErc20({ network: 'mainnet', tokenBlockchain : 'FTM' }) + case 'VLX_ERC_20': + return new EthTokenProcessorErc20({ network: 'mainnet', tokenBlockchain : 'VLX' }) + case 'ONE_ERC_20': + return new EthTokenProcessorErc20({ network: 'mainnet', tokenBlockchain : 'ONE' }) + case 'METIS_ERC_20': + return new EthTokenProcessorErc20({ network: 'mainnet', tokenBlockchain : 'METIS' }) + case 'TRX': + return new TrxTokenProcessor() + case 'SOL': + return new SolTokenProcessor() + default: + throw new Error('Unknown tokenProcessor ' + tokenType) + } + } + + /** + * @param tokenBlockchainCode + * @returns {EthTokenProcessorNft} + */ + getTokenNftsProcessor(tokenBlockchainCode) { + switch (tokenBlockchainCode) { + case 'ETH': case 'NFT_ETH': + return new EthTokenProcessorNft({ network: 'mainnet', tokenBlockchain: 'ETHEREUM', tokenBlockchainCode : 'ETH' }) + case 'ETH_RINKEBY': case 'NFT_RINKEBY': + return new EthTokenProcessorNft({ network: 'rinkeby', tokenBlockchain: 'RINKEBY', tokenBlockchainCode : 'ETH_RINKEBY' }) + case 'MATIC': case 'NFT_MATIC': + return new EthTokenProcessorNft({ network: 'mainnet', tokenBlockchain : 'MATIC', tokenBlockchainCode : 'MATIC' }) + case 'BNB': case 'NFT_BNB': + return new EthTokenProcessorNft({ network: 'mainnet', tokenBlockchain : 'BNB', tokenBlockchainCode : 'BNB' }) + case 'ONE': case 'NFT_ONE': + return new EthTokenProcessorNft({ network: 'mainnet', tokenBlockchain : 'ONE', tokenBlockchainCode : 'ONE' }) + case 'ETH_ROPSTEN': case 'NFT_ROPSTEN': + return new EthTokenProcessorNft({ network: 'ropsten', tokenBlockchain : 'ROPSTEN', tokenBlockchainCode : 'ETH_ROPSTEN' }) + default: + throw new Error('Unknown NFT tokenProcessor ' + tokenBlockchainCode) + } + } + + /** + * @param {string} currencyCode + * @return {XmrSecretsProcessor} + */ + getSecretsProcessor(currencyCode) { + const currencyDictSettings = BlocksoftDict.getCurrencyAllSettings(currencyCode) + if (currencyDictSettings.currencyCode !== 'XMR') { + throw new Error('Unknown secretsProcessor ' + currencyDictSettings.currencyCode) + } + return new XmrSecretsProcessor() + } +} + +const singleBlocksoftDispatcher = new BlocksoftDispatcher() +export default singleBlocksoftDispatcher diff --git a/src/lib/BlocksoftKeys.js b/src/lib/BlocksoftKeys.js new file mode 100644 index 000000000..251022c05 --- /dev/null +++ b/src/lib/BlocksoftKeys.js @@ -0,0 +1,419 @@ +/** + * @author Ksu + * @version 0.5 + */ +import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog' +import BlocksoftDict from '@crypto/common/BlocksoftDict' +import BlocksoftKeysUtils from '@crypto/actions/BlocksoftKeys/BlocksoftKeysUtils' + + +import * as BlocksoftRandom from 'react-native-blocksoft-random' +import BlocksoftDispatcher from '../../blockchains/BlocksoftDispatcher' +import BlocksoftKeysScam from '@crypto/actions/BlocksoftKeys/BlocksoftKeysScam' +import { strings } from '@app/services/i18n' + +const bip32 = require('bip32') +const bip39 = require('bip39') +const bip44Constants = require('../../common/ext/bip44-constants') +const networksConstants = require('../../common/ext/networks-constants') + +const bs58check = require('bs58check') + + +const ETH_CACHE = {} +const CACHE = {} +const CACHE_ROOTS = {} + + +class BlocksoftKeys { + + constructor() { + this._bipHex = {} + let currency + for (currency of bip44Constants) { + this._bipHex[currency[1]] = currency[0] + } + this._getRandomBytesFunction = BlocksoftRandom.getRandomBytes + } + + /** + * create new mnemonic object (also gives hash to store in public db) + * @param {int} size + * @return {Promise<{mnemonic: string, hash: string}>} + */ + async newMnemonic(size = 256) { + BlocksoftCryptoLog.log(`BlocksoftKeys newMnemonic called`) + + /* let mnemonic = false + if (USE_TON) { + for (let i = 0; i < 10000; i++) { + let random = await this._getRandomBytesFunction(size / 8) + random = Buffer.from(random, 'base64') + let testMnemonic = bip39.entropyToMnemonic(random) + if (await BlocksoftKeysUtils.tonCheckRevert(testMnemonic)) { + mnemonic = testMnemonic + break + } + BlocksoftCryptoLog.log('step ' + i + ' not valid ton ' + testMnemonic) + } + if (!mnemonic) { + throw new Error('TON Mnemonic is not validating') + } + } else { + let random = await this._getRandomBytesFunction(size / 8) + random = Buffer.from(random, 'base64') + mnemonic = bip39.entropyToMnemonic(random) + } */ + let random = await this._getRandomBytesFunction(size / 8) + random = Buffer.from(random, 'base64') + const mnemonic = bip39.entropyToMnemonic(random) + const hash = BlocksoftKeysUtils.hashMnemonic(mnemonic) + BlocksoftCryptoLog.log(`BlocksoftKeys newMnemonic finished`) + return { mnemonic, hash } + } + + /** + * @param {string} mnemonic + * @return {Promise} + */ + async validateMnemonic(mnemonic) { + BlocksoftCryptoLog.log(`BlocksoftKeys validateMnemonic called`) + if (await BlocksoftKeysScam.isScamMnemonic(mnemonic)) { + throw new Error(strings('settings.walletList.scamImport')) + } + const result = await bip39.validateMnemonic(mnemonic) + if (!result) { + throw new Error('invalid mnemonic for bip39') + } + return result + } + + + /** + * @param {string} data.mnemonic + * @param {string} data.walletHash + * @param {string|string[]} data.currencyCode = all + * @param {int} data.fromIndex = 0 + * @param {int} data.toIndex = 100 + * @param {boolean} data.fullTree = false + * @param {array} data.derivations.BTC + * @param {array} data.derivations.BTC_SEGWIT + * @return {Promise<{currencyCode:[{address, privateKey, path, index, type}]}>} + */ + async discoverAddresses(data, source) { + + const logData = { ...data } + if (typeof logData.mnemonic !== 'undefined') logData.mnemonic = '***' + + BlocksoftCryptoLog.log(`BlocksoftKeys discoverAddresses called from ${source}`, JSON.stringify(logData).substring(0, 200)) + + let toDiscover = BlocksoftDict.Codes + if (data.currencyCode) { + if (typeof data.currencyCode === 'string') { + toDiscover = [data.currencyCode] + } else { + toDiscover = data.currencyCode + } + } + const fromIndex = data.fromIndex ? data.fromIndex : 0 + const toIndex = data.toIndex ? data.toIndex : 10 + const fullTree = data.fullTree ? data.fullTree : false + + + const results = {} + + + const mnemonicCache = data.mnemonic.toLowerCase() + let bitcoinRoot = false + let currencyCode + let settings + const seed = await BlocksoftKeysUtils.bip39MnemonicToSeed(data.mnemonic.toLowerCase()) + for (currencyCode of toDiscover) { + results[currencyCode] = [] + try { + settings = BlocksoftDict.getCurrencyAllSettings(currencyCode, 'BlocksoftKeys') + } catch (e) { + // do nothing for now + continue + } + + let hexes = [] + if (settings.addressCurrencyCode) { + hexes.push(this._bipHex[settings.addressCurrencyCode]) + if (!this._bipHex[settings.addressCurrencyCode]) { + throw new Error('UNKNOWN_CURRENCY_CODE SETTED ' + settings.addressCurrencyCode) + } + } + + if (this._bipHex[currencyCode]) { + hexes.push(this._bipHex[currencyCode]) + } else if (!settings.addressCurrencyCode) { + if (settings.extendsProcessor && this._bipHex[settings.extendsProcessor]) { + hexes.push(this._bipHex[settings.extendsProcessor]) + } else { + throw new Error('UNKNOWN_CURRENCY_CODE ' + currencyCode + ' in bipHex AND NO SETTED addressCurrencyCode') + } + } + + let isAlreadyMain = false + + if (!data.fullTree) { + hexes = [hexes[0]] + } + + const hexesCache = mnemonicCache + '_' + settings.addressProcessor + '_fromINDEX_' + fromIndex + '_' + JSON.stringify(hexes) + let hasDerivations = false + if (typeof data.derivations !== 'undefined' && typeof data.derivations[currencyCode] !== 'undefined' && data.derivations[currencyCode]) { + hasDerivations = true + } + if (typeof CACHE[hexesCache] === 'undefined' || hasDerivations) { + BlocksoftCryptoLog.log(`BlocksoftKeys will discover ${settings.addressProcessor}`) + let root = false + if (typeof networksConstants[currencyCode] !== 'undefined') { + root = await this.getBip32Cached(data.mnemonic, networksConstants[currencyCode], seed) + } else { + if (!bitcoinRoot) { + bitcoinRoot = await this.getBip32Cached(data.mnemonic) + } + root = bitcoinRoot + } + // BIP32 Extended Private Key to check - uncomment + // let childFirst = root.derivePath('m/44\'/2\'/0\'/0') + // BlocksoftCryptoLog.log(childFirst.toBase58()) + + /** + * @type {EthAddressProcessor|BtcAddressProcessor} + */ + const processor = await BlocksoftDispatcher.innerGetAddressProcessor(settings) + + try { + await processor.setBasicRoot(root) + } catch (e) { + e.message += ' while doing ' + JSON.stringify(settings) + throw e + } + let currentFromIndex = fromIndex + let currentToIndex = toIndex + let currentFullTree = fullTree + + BlocksoftCryptoLog.log(`BlocksoftKeys discoverAddresses ${currencyCode} currentFromIndex.1 ${currentFromIndex}`) + if (hasDerivations) { + let derivation = {path : '', alreadyShown : 0, walletPubId : 0} + let maxIndex = 0 + for (derivation of data.derivations[currencyCode]) { + const child = root.derivePath(derivation.path) + const tmp = derivation.path.split('/') + const result = await processor.getAddress(child.privateKey, { + publicKey: child.publicKey, + walletHash: data.walletHash, + derivationPath : derivation.path, + }, data, seed, 'discoverAddresses') + result.basicPrivateKey = child.privateKey.toString('hex') + result.basicPublicKey = child.publicKey.toString('hex') + result.path = derivation.path + result.alreadyShown = derivation.alreadyShown + result.walletPubId = derivation.walletPubId || 0 + result.index = tmp[5] + if (maxIndex < result.index) { + maxIndex = result.index*1 + } + result.type = 'main' + results[currencyCode].push(result) + } + if (maxIndex > 0) { + // noinspection PointlessArithmeticExpressionJS + currentFromIndex = maxIndex*1 + 1 + currentToIndex = currentFromIndex*1+ 10 + currentFullTree = true + BlocksoftCryptoLog.log(`BlocksoftKeys ${currencyCode} discoverAddresses currentFromIndex.2 ${currentFromIndex}`) + } + } + + + let suffixes + if (currencyCode === 'SOL') { + suffixes = [ + { 'type': 'main', 'suffix': false, after : `'/0'` }, + { 'type': 'no_scan', 'suffix': false, after: `'` } + ] + } else if (currentFullTree) { + suffixes = [ + { 'type': 'main', 'suffix': `0'/0` }, + { 'type': 'change', 'suffix': `0'/1` } + // { 'type': 'second', 'suffix': `1'/0` }, + // { 'type': 'secondchange', 'suffix': `1'/1` } + ] + } else { + suffixes = [ + { 'type': 'main', 'suffix': `0'/0` } + ] + if (currencyCode === 'BTC_SEGWIT_COMPATIBLE') { + suffixes = [ + { 'type': 'main', 'suffix': '0/1' } // heh + ] + } + hexes = [hexes[0]] + } + + + let hex + for (hex of hexes) { + if (isAlreadyMain) { + suffixes[0].type = 'second' + } + isAlreadyMain = true + + if (currentFromIndex >= 0 && currentToIndex >= 0) { + for (let index = currentFromIndex; index < currentToIndex; index++) { + let suffix + for (suffix of suffixes) { + const path = suffix.suffix ? `m/${hex}'/${suffix.suffix}/${index}` : `m/${hex}'/${index}${suffix.after}` + let privateKey = false + let publicKey = false + if (currencyCode === 'SOL' || currencyCode === 'XLM' || currencyCode === 'WAVES' || currencyCode === 'ASH') { + // @todo move to coin address processor + } else { + const child = root.derivePath(path) + privateKey = child.privateKey + publicKey = child.publicKey + } + const result = await processor.getAddress(privateKey, { + publicKey, + walletHash: data.walletHash, + derivationPath : path, + derivationIndex : index, + derivationType : suffix.type + }, data, seed, 'discoverAddresses2') + if (result) { + if (privateKey) { + result.basicPrivateKey = privateKey.toString('hex') + result.basicPublicKey = publicKey.toString('hex') + } + result.path = path + result.index = index + result.alreadyShown = 0 + result.type = suffix.type + results[currencyCode].push(result) + } + } + } + } + } + CACHE[hexesCache] = results[currencyCode] + if (currencyCode === 'ETH') { + ETH_CACHE[mnemonicCache] = results[currencyCode][0] + } + } else { + BlocksoftCryptoLog.log(`BlocksoftKeys will be from cache ${settings.addressProcessor}`) + results[currencyCode] = CACHE[hexesCache] + if (currencyCode === 'USDT') { + results[currencyCode] = [results[currencyCode][0]] + } + } + } + BlocksoftCryptoLog.log(`BlocksoftKeys discoverAddresses finished`) + return results + } + + async getSeedCached(mnemonic) { + BlocksoftCryptoLog.log(`BlocksoftKeys bip39MnemonicToSeed started`) + const mnemonicCache = mnemonic.toLowerCase() + if (typeof CACHE[mnemonicCache] === 'undefined') { + CACHE[mnemonicCache] = await BlocksoftKeysUtils.bip39MnemonicToSeed(mnemonic.toLowerCase()) + } + const seed = CACHE[mnemonicCache] // will be rechecked on saving + BlocksoftCryptoLog.log(`BlocksoftKeys bip39MnemonicToSeed ended`) + return seed + } + + async getBip32Cached(mnemonic, network = false, seed = false) { + const mnemonicCache = mnemonic.toLowerCase() + '_' + (network || 'btc') + if (typeof CACHE_ROOTS[mnemonicCache] === 'undefined') { + if (!seed) { + seed = await this.getSeedCached(mnemonic) + } + CACHE_ROOTS[mnemonicCache] = network ? bip32.fromSeed(seed, network) : bip32.fromSeed(seed) + } + return CACHE_ROOTS[mnemonicCache] + } + + getEthCached(mnemonicCache) { + if (typeof ETH_CACHE[mnemonicCache] === 'undefined') return false + return ETH_CACHE[mnemonicCache] + } + + setEthCached(mnemonic, result) { + const mnemonicCache = mnemonic.toLowerCase() + ETH_CACHE[mnemonicCache] = result + } + + /** + * @param {string} data.mnemonic + * @param {string} data.currencyCode + * @param {string} data.derivationPath + * @param {string} data.derivationIndex + * @param {string} data.derivationType + * @return {Promise<{address, privateKey}>} + */ + async discoverOne(data) { + const seed = await BlocksoftKeysUtils.bip39MnemonicToSeed(data.mnemonic.toLowerCase()) + const root = bip32.fromSeed(seed) + const child = root.derivePath(data.derivationPath.replace(/quote/g, '\'')) + /** + * @type {EthAddressProcessor|BtcAddressProcessor} + */ + const processor = await BlocksoftDispatcher.getAddressProcessor(data.currencyCode) + processor.setBasicRoot(root) + return processor.getAddress(child.privateKey, { + derivationPath : data.derivationPath, + derivationIndex: data.derivationIndex, + derivationType: data.derivationType, + publicKey : child.publicKey + }, data, seed, 'discoverOne') + } + + /** + * @param {string} data.mnemonic + * @param {string} data.currencyCode + * @return {Promise<{address, privateKey}>} + */ + async discoverXpub(data) { + const seed = await BlocksoftKeysUtils.bip39MnemonicToSeed(data.mnemonic.toLowerCase()) + const root = bip32.fromSeed(seed) + let path = `m/44'/0'/0'` + let version = 0x0488B21E // xpub + if (data.currencyCode === 'BTC_SEGWIT') { + path = `m/84'/0'/0'` + version = 0x04b24746 // https://github.com/satoshilabs/slips/blob/master/slip-0132.md + } else if (data.currencyCode === 'BTC_SEGWIT_COMPATIBLE') { + path = `m/49'/0'/0'` + version = 0x049d7cb2 + } + BlocksoftCryptoLog.log('BlocksoftKeys.discoverXpub derive started ' + JSON.stringify(path)) + const rootWallet = root.derivePath(path) + BlocksoftCryptoLog.log('BlocksoftKeys.discoverXpub derived, bs58 started ' + JSON.stringify(path)) + const res = bs58check.encode(serialize(rootWallet, version, rootWallet.publicKey)) + BlocksoftCryptoLog.log('BlocksoftKeys.discoverXpub res ' + JSON.stringify(path), res) + return res + } +} + +function serialize(hdkey, version, key, LEN = 78) { + // => version(4) || depth(1) || fingerprint(4) || index(4) || chain(32) || key(33) + const buffer = Buffer.allocUnsafe(LEN) + + buffer.writeUInt32BE(version, 0) + buffer.writeUInt8(hdkey.depth, 4) + + const fingerprint = hdkey.depth ? hdkey.parentFingerprint : 0x00000000 + buffer.writeUInt32BE(fingerprint, 5) + buffer.writeUInt32BE(hdkey.index, 9) + + hdkey.chainCode.copy(buffer, 13) + key.copy(buffer, 45) + + return buffer +} + +const singleBlocksoftKeys = new BlocksoftKeys() +export default singleBlocksoftKeys diff --git a/src/lib/BlocksoftKeysForRef.js b/src/lib/BlocksoftKeysForRef.js new file mode 100644 index 000000000..ddd890190 --- /dev/null +++ b/src/lib/BlocksoftKeysForRef.js @@ -0,0 +1,82 @@ +/** + * @author Ksu + * @version 0.5 + */ +import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog'; +import BlocksoftKeysForRefServerSide from './BlocksoftKeysForRefServerSide'; +import BlocksoftKeys from '../BlocksoftKeys/BlocksoftKeys'; +import BlocksoftDispatcher from '../../blockchains/BlocksoftDispatcher'; + +const CACHE = {}; + +class BlocksoftKeysForRef { + /** + * @param {string} data.mnemonic + * @param {int} data.index + * @return {Promise<{currencyCode:[{address, privateKey, path, index, type}]}>} + */ + async discoverPublicAndPrivate(data) { + const logData = { ...data }; + const mnemonicCache = data.mnemonic.toLowerCase(); + + if (typeof logData.mnemonic !== 'undefined') logData.mnemonic = '***'; + if (typeof CACHE[mnemonicCache] !== 'undefined') + return CACHE[mnemonicCache]; + BlocksoftCryptoLog.log( + `BlocksoftKeysForRef discoverPublicAndPrivate called ` + + JSON.stringify(logData) + ); + + let result = BlocksoftKeys.getEthCached(mnemonicCache); + if (!result) { + BlocksoftCryptoLog.log( + `BlocksoftKeysForRef discoverPublicAndPrivate no cache ` + + JSON.stringify(logData) + ); + let index = 0; + if (typeof data.index !== 'undefined') { + index = data.index; + } + const root = await BlocksoftKeys.getBip32Cached(data.mnemonic); + const path = `m/44'/60'/${index}'/0/0`; + const child = root.derivePath(path); + + const processor = await BlocksoftDispatcher.getAddressProcessor('ETH'); + result = await processor.getAddress(child.privateKey); + result.index = index; + result.path = path; + if (index === 0) { + BlocksoftKeys.setEthCached(data.mnemonic, result); + } + BlocksoftCryptoLog.log( + `BlocksoftKeysForRef discoverPublicAndPrivate finished no cache ` + + JSON.stringify(logData) + ); + } + // noinspection JSPrimitiveTypeWrapperUsage + result.cashbackToken = BlocksoftKeysForRefServerSide.addressToToken( + result.address + ); + BlocksoftCryptoLog.log( + `BlocksoftKeysForRef discoverPublicAndPrivate finished ` + + JSON.stringify(logData) + ); + CACHE[mnemonicCache] = result; + return result; + } + + async signDataForApi(msg, privateKey) { + const processor = await BlocksoftDispatcher.getAddressProcessor('ETH'); + if (privateKey.substr(0, 2) !== '0x') { + privateKey = '0x' + privateKey; + } + const signedData = await processor.signMessage(msg, privateKey); + delete signedData.v; + delete signedData.r; + delete signedData.s; + return signedData; + } +} + +const singleBlocksoftKeysForRef = new BlocksoftKeysForRef(); +export default singleBlocksoftKeysForRef; diff --git a/src/lib/blockchains/BlocksoftBlockchainTypes.ts b/src/lib/blockchains/BlocksoftBlockchainTypes.ts new file mode 100644 index 000000000..b86fa09e9 --- /dev/null +++ b/src/lib/blockchains/BlocksoftBlockchainTypes.ts @@ -0,0 +1,357 @@ +/** + * @author Ksu + * @version 0.20 + */ +import { BlocksoftDictTypes } from '../common/BlocksoftDictTypes'; +import { TransactionBuilder } from 'bitcoinjs-lib'; + +export namespace BlocksoftBlockchainTypes { + export interface TransferProcessor { + needPrivateForFee(): boolean; + + sendTx( + data: TransferData, + privateData: TransferPrivateData, + uiData: TransferUiData + ): Promise; + + checkTransferHasError?( + data: CheckTransferHasErrorData + ): Promise; + + checkSendAllModal?(data: { currencyCode: any }): boolean; + + getTransferAllBalance( + data: TransferData, + privateData?: TransferPrivateData, + additionalData?: TransferAdditionalData + ): Promise; + + getFeeRate( + data: TransferData, + privateData?: TransferPrivateData, + additionalData?: TransferAdditionalData + ): Promise; + + sendRawTx?( + data: DbAccount, + rawTxHex: string, + txRBF: any, + logData: any + ): Promise; + + setMissingTx?( + data: DbAccount, + transaction: DbTransaction + ): Promise; + + canRBF?( + data: BlocksoftBlockchainTypes.DbAccount, + dbTransaction: BlocksoftBlockchainTypes.DbTransaction, + source: string + ): boolean; + } + + export interface UnspentsProvider { + getUnspents(address: string): Promise; + + getTx?( + tx: string, + address: string, + allUnspents: BlocksoftBlockchainTypes.UnspentTx[], + walletHash: string + ): Promise; + + _isMyAddress?( + voutAddress: string, + address: string, + walletHash: string + ): string; + } + + export interface SendProvider { + sendTx( + hex: string, + subtitle: string, + txRBF: any, + logData: any + ): Promise<{ + transactionHash: string; + transactionJson: any; + logData?: any; + }>; + } + + export interface NetworkPrices { + getNetworkPrices(currencyCode: string): Promise<{ + speed_blocks_2: number; + speed_blocks_6: number; + speed_blocks_12: number; + }>; + } + + export interface TxInputsOutputs { + getInputsOutputs( + data: BlocksoftBlockchainTypes.TransferData, + unspents: BlocksoftBlockchainTypes.UnspentTx[], + feeToCount: { + feeForByte?: string; + feeForAll?: string; + autoFeeLimitReadable?: string | number; + }, + additionalData: BlocksoftBlockchainTypes.TransferAdditionalData, + subtitle: string + ): Promise; + } + + export interface TxBuilder { + getRawTx( + data: BlocksoftBlockchainTypes.TransferData, + privateData: BlocksoftBlockchainTypes.TransferPrivateData, + preparedInputsOutputs: BlocksoftBlockchainTypes.PreparedInputsOutputsTx + ): Promise<{ + rawTxHex: string; + nSequence: number; + txAllowReplaceByFee: boolean; + preparedInputsOutputs: BlocksoftBlockchainTypes.PreparedInputsOutputsTx; + }>; + + _getRawTxValidateKeyPair( + privateData: BlocksoftBlockchainTypes.TransferPrivateData, + data: BlocksoftBlockchainTypes.TransferData + ): void; + + _getRawTxAddInput( + txb: TransactionBuilder, + i: number, + input: UnspentTx, + nSequence: number + ): Promise; + + _getRawTxSign( + txb: TransactionBuilder, + i: number, + input: UnspentTx + ): Promise; + + _getRawTxAddOutput(txb: TransactionBuilder, output: OutputTx): void; + } + + export interface Fee { + langMsg: string; + gasPrice?: string; + gasLimit?: string; + needSpeed?: string; + feeForByte?: string; + feeForTx: string; + nonceForTx?: number | string; + amountForTx: string; + addressToTx?: string; + isCustomFee?: boolean; + showNonce?: boolean; + feeForTxBasicAmount?: number; + feeForTxBasicSymbol?: string; + feeForTxCurrencyAmount?: number; + feeForTxDelegated?: number; + blockchainData?: any; + rawOnly?: boolean; + bseOrderId?: string; + } + + export interface CheckTransferHasErrorData { + currencyCode: BlocksoftDictTypes.Code; + walletHash: string; + addressTo: string; + addressFrom: string; + amount: string; + } + + export interface CheckTransferHasErrorResult { + isOk: boolean; + code?: 'TOKEN' | 'XRP' | 'XLM'; + parentBlockchain?: 'Ethereum' | 'Bitcoin' | 'Binance'; + parentCurrency?: 'ETH' | 'BTC' | 'BNB'; + address?: string; + } + + export interface TransferData { + currencyCode: BlocksoftDictTypes.Code; + walletHash: string; + derivationPath: string; + addressFrom: string; + addressTo: string; + amount: string; + useOnlyConfirmed: boolean; + allowReplaceByFee: boolean; + useLegacy: number; + isHd: boolean; + + accountBalanceRaw: string; + isTransferAll: boolean; + + memo?: string; + transactionReplaceByFee?: string; + transactionSpeedUp?: string; + transactionRemoveByFee?: string; + + blockchainData?: string; + dexOrderData?: string; + contractCallData?: string; + + accountJson?: any; + transactionJson?: any; + } + + export interface TransferPrivateData { + privateKey: string; + addedData?: any; + } + + export interface TransferUiData { + uiErrorConfirmed: boolean; + selectedFee: Fee; + } + + export interface TransferAdditionalData { + mnemonic?: string; + prices?: { + speed_blocks_2: string; + speed_blocks_6: string; + speed_blocks_12: string; + }; + feeForByte?: string; + gasLimit?: number; + gasPrice?: number; + gasPriceTitle?: 'speed_blocks_2'; + nonceForTx?: number; + isCustomFee?: boolean; + balance?: string; + unspents?: []; + } + + export interface TransferAllBalanceResult { + additionalData?: { unspents: any[] }; + selectedTransferAllBalance: string; + selectedFeeIndex: number; + countedForBasicBalance: string; + fees: Fee[]; + shouldShowFees?: boolean; + showSmallFeeNotice?: number; + countedTime?: number; + } + + export interface FeeRateResult { + additionalData: { unspents: any[] }; + countedForBasicBalance: string; + selectedFeeIndex: number; + fees: Fee[]; + shouldShowFees?: boolean; + + showBigGasNotice?: number; + showSmallFeeNotice?: number; + + showLongQueryNotice?: number; + showLongQueryNoticeTxs?: any; + + showBlockedBalanceNotice?: number; + showBlockedBalanceFree?: number | any; + countedTime: number; + } + + export interface SendTxResult { + transactionHash: string; + transactionJson?: { + nonce?: string; + gasPrice?: number; + nSequence?: string; + txAllowReplaceByFee?: boolean; + feeForByte?: string; + secretTxKey?: string; + }; + + amountForTx?: string; + addressTo?: string; + blockHash?: string; + + transactionFee?: string; + transactionFeeCurrencyCode?: string; + transactionStatus?: string; + transactionTimestamp?: string; + successMessage?: string; + } + + export interface CurrencySettings { + network: any; + currencyCode: string; + decimals: any; + } + + export interface BuilderSettings { + minOutputDustReadable: number; + minChangeDustReadable: number; + feeMaxForByteSatoshi: number; + feeMaxAutoReadable2: number; + feeMaxAutoReadable6: number; + feeMaxAutoReadable12: number; + changeTogether: boolean; + minRbfStepSatoshi?: number; + minSpeedUpMulti?: number; + feeMinTotalReadable?: number; + } + + export interface UnspentTx { + isRequired: boolean; + derivationPath?: string; + address?: string; + txid: string; + vout: number; + value: string; + height: number; + confirmations: number; + } + + export interface OutputTx { + isChange?: boolean; + isUsdt?: boolean; + tokenAmount?: string; + logType?: string; + to: string; + amount: string; + } + + export interface PreparedInputsOutputsTx { + inputs: BlocksoftBlockchainTypes.UnspentTx[]; + outputs: BlocksoftBlockchainTypes.OutputTx[]; + multiAddress: []; + msg: string; + countedFor?: string; + } + + export interface EthTx { + from: string; + to: string; + gasPrice: number; + gas: number; + value: string; + nonce?: number; + data?: string; + } + + export interface DbAccount { + currencyCode: BlocksoftDictTypes.Code; + walletHash: string; + address: string; + } + + export interface DbTransaction { + currencyCode: BlocksoftDictTypes.Code; + transactionHash: string; + transactionDirection: string; + transactionStatus: string; + addressTo: string; + transactionJson?: { + delegatedNonce?: string; + nonce?: string; + gasPrice?: number; + }; + } diff --git a/src/lib/blockchains/BlocksoftDispatcher.ts b/src/lib/blockchains/BlocksoftDispatcher.ts new file mode 100644 index 000000000..97d02cbe8 --- /dev/null +++ b/src/lib/blockchains/BlocksoftDispatcher.ts @@ -0,0 +1,338 @@ +/** + * @author Ksu + * @version 0.5 + */ + +import BchAddressProcessor from '@crypto/blockchains/bch/BchAddressProcessor'; +import BchScannerProcessor from '@crypto/blockchains/bch/BchScannerProcessor'; + +import BsvScannerProcessor from '@crypto/blockchains/bsv/BsvScannerProcessor'; + +import BtcAddressProcessor from '@crypto/blockchains/btc/address/BtcAddressProcessor'; +import BtcScannerProcessor from '@crypto/blockchains/btc/BtcScannerProcessor'; + +import BtcSegwitCompatibleAddressProcessor from '@crypto/blockchains/btc/address/BtcSegwitCompatibleAddressProcessor'; +import BtcSegwitAddressProcessor from '@crypto/blockchains/btc/address/BtcSegwitAddressProcessor'; + +import BtcTestScannerProcessor from '@crypto/blockchains/btc_test/BtcTestScannerProcessor'; + +import BtgScannerProcessor from '@crypto/blockchains/btg/BtgScannerProcessor'; + +import DogeScannerProcessor from '@crypto/blockchains/doge/DogeScannerProcessor'; + +import EthAddressProcessor from '@crypto/blockchains/eth/EthAddressProcessor'; +import EthScannerProcessor from '@crypto/blockchains/eth/EthScannerProcessor'; +import EthScannerProcessorErc20 from '@crypto/blockchains/eth/EthScannerProcessorErc20'; +import EthScannerProcessorSoul from '@crypto/blockchains/eth/forks/EthScannerProcessorSoul'; +import EthTokenProcessorErc20 from '@crypto/blockchains/eth/EthTokenProcessorErc20'; + +import LtcScannerProcessor from '@crypto/blockchains/ltc/LtcScannerProcessor'; + +import TrxAddressProcessor from '@crypto/blockchains/trx/TrxAddressProcessor'; +import TrxScannerProcessor from '@crypto/blockchains/trx/TrxScannerProcessor'; +import TrxTokenProcessor from '@crypto/blockchains/trx/TrxTokenProcessor'; + +import UsdtScannerProcessor from '@crypto/blockchains/usdt/UsdtScannerProcessor'; + +import XrpAddressProcessor from '@crypto/blockchains/xrp/XrpAddressProcessor'; +import XrpScannerProcessor from '@crypto/blockchains/xrp/XrpScannerProcessor'; + +import XlmAddressProcessor from '@crypto/blockchains/xlm/XlmAddressProcessor'; +import XlmScannerProcessor from '@crypto/blockchains/xlm/XlmScannerProcessor'; + +import XvgScannerProcessor from '@crypto/blockchains/xvg/XvgScannerProcessor'; + +import XmrAddressProcessor from '@crypto/blockchains/xmr/XmrAddressProcessor'; +import XmrScannerProcessor from '@crypto/blockchains/xmr/XmrScannerProcessor'; +import XmrSecretsProcessor from '@crypto/blockchains/xmr/XmrSecretsProcessor'; +import FioAddressProcessor from '@crypto/blockchains/fio/FioAddressProcessor'; +import FioScannerProcessor from '@crypto/blockchains/fio/FioScannerProcessor'; + +import BnbAddressProcessor from '@crypto/blockchains/bnb/BnbAddressProcessor'; +import BnbScannerProcessor from '@crypto/blockchains/bnb/BnbScannerProcessor'; +import { BlockchainUtils } from '@utils/blockchain'; + +import VetScannerProcessor from '@crypto/blockchains/vet/VetScannerProcessor'; + +import SolAddressProcessor from '@crypto/blockchains/sol/SolAddressProcessor'; +import SolScannerProcessor from '@crypto/blockchains/sol/SolScannerProcessor'; + +import WavesAddressProcessor from '@crypto/blockchains/waves/WavesAddressProcessor'; +import WavesScannerProcessor from '@crypto/blockchains/waves/WavesScannerProcessor'; + +import SolScannerProcessorSpl from '@crypto/blockchains/sol/SolScannerProcessorSpl'; +import SolTokenProcessor from '@crypto/blockchains/sol/SolTokenProcessor'; +import EthTokenProcessorNft from '@crypto/blockchains/eth/EthTokenProcessorNft'; +import AshAddressProcessor from '@crypto/blockchains/ash/AshAddressProcessor'; + +import MetisScannerProcessor from '@crypto/blockchains/metis/MetisScannerProcessor'; +import OneScannerProcessor from '@crypto/blockchains/one/OneScannerProcessor'; +import OneScannerProcessorErc20 from '@crypto/blockchains/one/OneScannerProcessorErc20'; + +import WavesScannerProcessorErc20 from '@crypto/blockchains/waves/WavesScannerProcessorErc20'; + +class AirDAODispatcher { + getAddressProcessor( + currencyCode: string + ): + | EthAddressProcessor + | BtcAddressProcessor + | TrxAddressProcessor + | XlmAddressProcessor + | SolAddressProcessor { + const currencyDictSettings = + BlockchainUtils.getCurrencyAllSettings(currencyCode); + return this.innerGetAddressProcessor(currencyDictSettings); + } + + innerGetAddressProcessor( + currencyDictSettings: any // TODO + ): + | EthAddressProcessor + | BtcAddressProcessor + | TrxAddressProcessor + | XlmAddressProcessor + | SolAddressProcessor { + switch (currencyDictSettings.addressProcessor) { + case 'BCH': + return new BchAddressProcessor(currencyDictSettings); + case 'BTC': + return new BtcAddressProcessor(currencyDictSettings); + case 'BTC_SEGWIT': + case 'LTC_SEGWIT': + return new BtcSegwitAddressProcessor(currencyDictSettings); + case 'BTC_SEGWIT_COMPATIBLE': + return new BtcSegwitCompatibleAddressProcessor(currencyDictSettings); + case 'ETH': + return new EthAddressProcessor(currencyDictSettings); + case 'TRX': + return new TrxAddressProcessor(); + case 'XRP': + return new XrpAddressProcessor(); + case 'XLM': + return new XlmAddressProcessor(); + case 'XMR': + return new XmrAddressProcessor(); + case 'FIO': + return new FioAddressProcessor(); + case 'BNB': + return new BnbAddressProcessor(); + case 'SOL': + return new SolAddressProcessor(); + case 'WAVES': + return new WavesAddressProcessor(); + case 'ASH': + return new AshAddressProcessor(); + default: + throw new Error( + 'Unknown addressProcessor ' + currencyDictSettings.addressProcessor + ); + } + } + + getScannerProcessor( + currencyCode: string + ): + | BsvScannerProcessor + | BtcScannerProcessor + | UsdtScannerProcessor + | EthScannerProcessorErc20 + | BchScannerProcessor + | LtcScannerProcessor + | XvgScannerProcessor + | BtcTestScannerProcessor + | DogeScannerProcessor + | EthScannerProcessorSoul + | EthScannerProcessor + | BtgScannerProcessor + | TrxScannerProcessor + | XrpScannerProcessor + | XlmScannerProcessor + | XmrScannerProcessor + | FioScannerProcessor + | BnbScannerProcessor + | VetScannerProcessor + | SolScannerProcessor + | SolScannerProcessorSpl + | WavesScannerProcessor + | MetisScannerProcessor + | OneScannerProcessor + | OneScannerProcessorErc20 + | WavesScannerProcessorErc20 { + const currencyDictSettings = + BlockchainUtils.getCurrencyAllSettings(currencyCode); + switch (currencyDictSettings.scannerProcessor) { + case 'BCH': + return new BchScannerProcessor(currencyDictSettings); + case 'BSV': + return new BsvScannerProcessor(); + case 'BTC': + case 'BTC_SEGWIT': + case 'BTC_SEGWIT_COMPATIBLE': + return new BtcScannerProcessor(currencyDictSettings); + case 'BTC_TEST': + return new BtcTestScannerProcessor(); + case 'BTG': + return new BtgScannerProcessor(currencyDictSettings); + case 'DOGE': + return new DogeScannerProcessor(currencyDictSettings); + case 'ETH': + return new EthScannerProcessor(currencyDictSettings); + case 'ETH_ERC_20': + return new EthScannerProcessorErc20(currencyDictSettings); + case 'ETH_SOUL': + return new EthScannerProcessorSoul(currencyDictSettings); + case 'LTC': + return new LtcScannerProcessor(currencyDictSettings); + case 'TRX': + return new TrxScannerProcessor(currencyDictSettings); + case 'USDT': + return new UsdtScannerProcessor(); + case 'XRP': + return new XrpScannerProcessor(); + case 'XLM': + return new XlmScannerProcessor(); + case 'XVG': + return new XvgScannerProcessor(); + case 'XMR': + return new XmrScannerProcessor(currencyDictSettings); + case 'FIO': + return new FioScannerProcessor(currencyDictSettings); + case 'BNB': + return new BnbScannerProcessor(); + case 'VET': + return new VetScannerProcessor(currencyDictSettings); + case 'SOL': + return new SolScannerProcessor(currencyDictSettings); + case 'SOL_SPL': + return new SolScannerProcessorSpl(currencyDictSettings); + case 'WAVES': + return new WavesScannerProcessor(currencyDictSettings); + case 'METIS': + return new MetisScannerProcessor(currencyDictSettings); + case 'ONE': + return new OneScannerProcessor(currencyDictSettings); + case 'ONE_ERC_20': + return new OneScannerProcessorErc20(currencyDictSettings); + case 'WAVES_ERC_20': + return new WavesScannerProcessorErc20(currencyDictSettings); + default: + throw new Error( + 'Unknown scannerProcessor ' + currencyDictSettings.scannerProcessor + ); + } + } + + // TODO enum tokenType + getTokenProcessor( + tokenType: string + ): TrxTokenProcessor | EthTokenProcessorErc20 | SolTokenProcessor { + switch (tokenType) { + case 'ETH_ERC_20': + return new EthTokenProcessorErc20({ + network: 'mainnet', + tokenBlockchain: 'ETHEREUM' + }); + case 'BNB_SMART_20': + return new EthTokenProcessorErc20({ + network: 'mainnet', + tokenBlockchain: 'BNB' + }); + case 'MATIC_ERC_20': + return new EthTokenProcessorErc20({ + network: 'mainnet', + tokenBlockchain: 'MATIC' + }); + case 'FTM_ERC_20': + return new EthTokenProcessorErc20({ + network: 'mainnet', + tokenBlockchain: 'FTM' + }); + case 'VLX_ERC_20': + return new EthTokenProcessorErc20({ + network: 'mainnet', + tokenBlockchain: 'VLX' + }); + case 'ONE_ERC_20': + return new EthTokenProcessorErc20({ + network: 'mainnet', + tokenBlockchain: 'ONE' + }); + case 'METIS_ERC_20': + return new EthTokenProcessorErc20({ + network: 'mainnet', + tokenBlockchain: 'METIS' + }); + case 'TRX': + return new TrxTokenProcessor(); + case 'SOL': + return new SolTokenProcessor(); + default: + throw new Error('Unknown tokenProcessor ' + tokenType); + } + } + + // TODO enum tokenBlockchainCode + getTokenNftsProcessor(tokenBlockchainCode: string) { + switch (tokenBlockchainCode) { + case 'ETH': + case 'NFT_ETH': + return new EthTokenProcessorNft({ + network: 'mainnet', + tokenBlockchain: 'ETHEREUM', + tokenBlockchainCode: 'ETH' + }); + case 'ETH_RINKEBY': + case 'NFT_RINKEBY': + return new EthTokenProcessorNft({ + network: 'rinkeby', + tokenBlockchain: 'RINKEBY', + tokenBlockchainCode: 'ETH_RINKEBY' + }); + case 'MATIC': + case 'NFT_MATIC': + return new EthTokenProcessorNft({ + network: 'mainnet', + tokenBlockchain: 'MATIC', + tokenBlockchainCode: 'MATIC' + }); + case 'BNB': + case 'NFT_BNB': + return new EthTokenProcessorNft({ + network: 'mainnet', + tokenBlockchain: 'BNB', + tokenBlockchainCode: 'BNB' + }); + case 'ONE': + case 'NFT_ONE': + return new EthTokenProcessorNft({ + network: 'mainnet', + tokenBlockchain: 'ONE', + tokenBlockchainCode: 'ONE' + }); + case 'ETH_ROPSTEN': + case 'NFT_ROPSTEN': + return new EthTokenProcessorNft({ + network: 'ropsten', + tokenBlockchain: 'ROPSTEN', + tokenBlockchainCode: 'ETH_ROPSTEN' + }); + default: + throw new Error('Unknown NFT tokenProcessor ' + tokenBlockchainCode); + } + } + + getSecretsProcessor(currencyCode: string): XmrSecretsProcessor { + const currencyDictSettings = + BlockchainUtils.getCurrencyAllSettings(currencyCode); + if (currencyDictSettings.currencyCode !== 'XMR') { + throw new Error( + 'Unknown secretsProcessor ' + currencyDictSettings.currencyCode + ); + } + return new XmrSecretsProcessor(); + } +} + +const singleBlocksoftDispatcher = new AirDAODispatcher(); +export default singleBlocksoftDispatcher; diff --git a/src/lib/blockchains/BlocksoftTransferDispatcher.ts b/src/lib/blockchains/BlocksoftTransferDispatcher.ts new file mode 100644 index 000000000..24c8730ad --- /dev/null +++ b/src/lib/blockchains/BlocksoftTransferDispatcher.ts @@ -0,0 +1,135 @@ +/** + * @author Ksu + * @version 0.20 + */ +import BlocksoftDict from '../common/BlocksoftDict' +import { BlocksoftDictTypes } from '../common/BlocksoftDictTypes' + +import BchTransferProcessor from './bch/BchTransferProcessor' +import BsvTransferProcessor from './bsv/BsvTransferProcessor' +import BtcTransferProcessor from './btc/BtcTransferProcessor' +import BtcTestTransferProcessor from './btc_test/BtcTestTransferProcessor' +import BtgTransferProcessor from './btg/BtgTransferProcessor' +import DogeTransferProcessor from './doge/DogeTransferProcessor' +import EthTransferProcessor from './eth/EthTransferProcessor' +import EthTransferProcessorErc20 from './eth/EthTransferProcessorErc20' +import LtcTransferProcessor from './ltc/LtcTransferProcessor' +import TrxTransferProcessor from './trx/TrxTransferProcessor' +import UsdtTransferProcessor from './usdt/UsdtTransferProcessor' +import XrpTransferProcessor from './xrp/XrpTransferProcessor' +import XlmTransferProcessor from './xlm/XlmTransferProcessor' +import XvgTransferProcessor from './xvg/XvgTransferProcessor' +import XmrTransferProcessor from './xmr/XmrTransferProcessor' +import FioTransferProcessor from './fio/FioTransferProcessor' +import BnbTransferProcessor from './bnb/BnbTransferProcessor' +import BnbSmartTransferProcessor from './bnb_smart/BnbSmartTransferProcessor' +import BnbSmartTransferProcessorErc20 from './bnb_smart/BnbSmartTransferProcessorErc20' +import EtcTransferProcessor from './etc/EtcTransferProcessor' +import VetTransferProcessor from '@crypto/blockchains/vet/VetTransferProcessor' +import SolTransferProcessor from '@crypto/blockchains/sol/SolTransferProcessor' +import SolTransferProcessorSpl from '@crypto/blockchains/sol/SolTransferProcessorSpl' +import WavesTransferProcessor from '@crypto/blockchains/waves/WavesTransferProcessor' +import MetisTransferProcessor from '@crypto/blockchains/metis/MetisTransferProcessor' + +import { BlocksoftBlockchainTypes } from './BlocksoftBlockchainTypes' + + +export namespace BlocksoftTransferDispatcher { + + type BlocksoftTransferDispatcherDict = { + [key in BlocksoftDictTypes.Code]: BlocksoftBlockchainTypes.TransferProcessor + } + + const CACHE_PROCESSORS: BlocksoftTransferDispatcherDict = {} as BlocksoftTransferDispatcherDict + + export const getTransferProcessor = function(currencyCode: BlocksoftDictTypes.Code): BlocksoftBlockchainTypes.TransferProcessor { + const currencyDictSettings = BlocksoftDict.getCurrencyAllSettings(currencyCode) + if (typeof CACHE_PROCESSORS[currencyCode] !== 'undefined') { + return CACHE_PROCESSORS[currencyCode] + } + let transferProcessor = currencyCode + if (typeof currencyDictSettings.transferProcessor !== 'undefined') { + transferProcessor = currencyDictSettings.transferProcessor + } + switch (transferProcessor) { + case 'BCH': + CACHE_PROCESSORS[currencyCode] = new BchTransferProcessor(currencyDictSettings) + break + case 'BSV': + CACHE_PROCESSORS[currencyCode] = new BsvTransferProcessor(currencyDictSettings) + break + case 'BTC': + CACHE_PROCESSORS[currencyCode] = new BtcTransferProcessor(currencyDictSettings) + break + case 'BTC_TEST': + CACHE_PROCESSORS[currencyCode] = new BtcTestTransferProcessor(currencyDictSettings) + break + case 'BTG': + CACHE_PROCESSORS[currencyCode] = new BtgTransferProcessor(currencyDictSettings) + break + case 'DOGE': + CACHE_PROCESSORS[currencyCode] = new DogeTransferProcessor(currencyDictSettings) + break + case 'ETH': + CACHE_PROCESSORS[currencyCode] = new EthTransferProcessor(currencyDictSettings) + break + case 'ETH_ERC_20': + CACHE_PROCESSORS[currencyCode] = new EthTransferProcessorErc20(currencyDictSettings) + break + case 'ETC': + CACHE_PROCESSORS[currencyCode] = new EtcTransferProcessor(currencyDictSettings) + break + case 'BNB_SMART_20': + CACHE_PROCESSORS[currencyCode] = new BnbSmartTransferProcessorErc20(currencyDictSettings) + break + case 'LTC': + CACHE_PROCESSORS[currencyCode] = new LtcTransferProcessor(currencyDictSettings) + break + case 'TRX': + CACHE_PROCESSORS[currencyCode] = new TrxTransferProcessor(currencyDictSettings) + break + case 'USDT': + CACHE_PROCESSORS[currencyCode] = new UsdtTransferProcessor(currencyDictSettings) + break + case 'XRP': + CACHE_PROCESSORS[currencyCode] = new XrpTransferProcessor(currencyDictSettings) + break + case 'XLM': + CACHE_PROCESSORS[currencyCode] = new XlmTransferProcessor(currencyDictSettings) + break + case 'XVG': + CACHE_PROCESSORS[currencyCode] = new XvgTransferProcessor(currencyDictSettings) + break + case 'XMR': + CACHE_PROCESSORS[currencyCode] = new XmrTransferProcessor(currencyDictSettings) + break + case 'FIO': + CACHE_PROCESSORS[currencyCode] = new FioTransferProcessor(currencyDictSettings) + break + case 'BNB': + CACHE_PROCESSORS[currencyCode] = new BnbTransferProcessor(currencyDictSettings) + break + case 'BNB_SMART': + CACHE_PROCESSORS[currencyCode] = new BnbSmartTransferProcessor(currencyDictSettings) + break + case 'VET': + CACHE_PROCESSORS[currencyCode] = new VetTransferProcessor(currencyDictSettings) + break + case 'SOL': + CACHE_PROCESSORS[currencyCode] = new SolTransferProcessor(currencyDictSettings) + break + case 'SOL_SPL': + CACHE_PROCESSORS[currencyCode] = new SolTransferProcessorSpl(currencyDictSettings) + break + case 'METIS': + CACHE_PROCESSORS[currencyCode] = new MetisTransferProcessor(currencyDictSettings) + break + case 'WAVES': + CACHE_PROCESSORS[currencyCode] = new WavesTransferProcessor(currencyDictSettings) + break + default: + throw new Error('Unknown transferProcessor ' + transferProcessor) + } + return CACHE_PROCESSORS[currencyCode] + } +} diff --git a/src/lib/helpers/AirDAOKeys.ts b/src/lib/helpers/AirDAOKeys.ts new file mode 100644 index 000000000..b50a4898a --- /dev/null +++ b/src/lib/helpers/AirDAOKeys.ts @@ -0,0 +1,497 @@ +/** + * @author Ksu + * @version 0.5 + */ +import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; +import BlocksoftDict from '@crypto/common/BlocksoftDict'; +import BlocksoftKeysUtils from '@crypto/actions/BlocksoftKeys/BlocksoftKeysUtils'; + +import * as BlocksoftRandom from 'react-native-blocksoft-random'; +import BlocksoftDispatcher from '../../blockchains/BlocksoftDispatcher'; +import BlocksoftKeysScam from '@crypto/actions/BlocksoftKeys/BlocksoftKeysScam'; +import { strings } from '@app/services/i18n'; + +const bip32 = require('bip32'); +const bip39 = require('bip39'); +const bip44Constants = require('../../common/ext/bip44-constants'); +const networksConstants = require('../../common/ext/networks-constants'); + +const bs58check = require('bs58check'); + +const ETH_CACHE = {}; +const CACHE = {}; +const CACHE_ROOTS = {}; + +class BlocksoftKeys { + constructor() { + this._bipHex = {}; + let currency; + for (currency of bip44Constants) { + this._bipHex[currency[1]] = currency[0]; + } + this._getRandomBytesFunction = BlocksoftRandom.getRandomBytes; + } + + /** + * create new mnemonic object (also gives hash to store in public db) + * @param {int} size + * @return {Promise<{mnemonic: string, hash: string}>} + */ + async newMnemonic(size = 256) { + BlocksoftCryptoLog.log(`BlocksoftKeys newMnemonic called`); + + /* let mnemonic = false + if (USE_TON) { + for (let i = 0; i < 10000; i++) { + let random = await this._getRandomBytesFunction(size / 8) + random = Buffer.from(random, 'base64') + let testMnemonic = bip39.entropyToMnemonic(random) + if (await BlocksoftKeysUtils.tonCheckRevert(testMnemonic)) { + mnemonic = testMnemonic + break + } + BlocksoftCryptoLog.log('step ' + i + ' not valid ton ' + testMnemonic) + } + if (!mnemonic) { + throw new Error('TON Mnemonic is not validating') + } + } else { + let random = await this._getRandomBytesFunction(size / 8) + random = Buffer.from(random, 'base64') + mnemonic = bip39.entropyToMnemonic(random) + } */ + let random = await this._getRandomBytesFunction(size / 8); + random = Buffer.from(random, 'base64'); + const mnemonic = bip39.entropyToMnemonic(random); + const hash = BlocksoftKeysUtils.hashMnemonic(mnemonic); + BlocksoftCryptoLog.log(`BlocksoftKeys newMnemonic finished`); + return { mnemonic, hash }; + } + + /** + * @param {string} mnemonic + * @return {Promise} + */ + async validateMnemonic(mnemonic) { + BlocksoftCryptoLog.log(`BlocksoftKeys validateMnemonic called`); + if (await BlocksoftKeysScam.isScamMnemonic(mnemonic)) { + throw new Error(strings('settings.walletList.scamImport')); + } + const result = await bip39.validateMnemonic(mnemonic); + if (!result) { + throw new Error('invalid mnemonic for bip39'); + } + return result; + } + + /** + * @param {string} data.mnemonic + * @param {string} data.walletHash + * @param {string|string[]} data.currencyCode = all + * @param {int} data.fromIndex = 0 + * @param {int} data.toIndex = 100 + * @param {boolean} data.fullTree = false + * @param {array} data.derivations.BTC + * @param {array} data.derivations.BTC_SEGWIT + * @return {Promise<{currencyCode:[{address, privateKey, path, index, type}]}>} + */ + async discoverAddresses(data, source) { + const logData = { ...data }; + if (typeof logData.mnemonic !== 'undefined') logData.mnemonic = '***'; + + BlocksoftCryptoLog.log( + `BlocksoftKeys discoverAddresses called from ${source}`, + JSON.stringify(logData).substring(0, 200) + ); + + let toDiscover = BlocksoftDict.Codes; + if (data.currencyCode) { + if (typeof data.currencyCode === 'string') { + toDiscover = [data.currencyCode]; + } else { + toDiscover = data.currencyCode; + } + } + const fromIndex = data.fromIndex ? data.fromIndex : 0; + const toIndex = data.toIndex ? data.toIndex : 10; + const fullTree = data.fullTree ? data.fullTree : false; + + const results = {}; + + const mnemonicCache = data.mnemonic.toLowerCase(); + let bitcoinRoot = false; + let currencyCode; + let settings; + const seed = await BlocksoftKeysUtils.bip39MnemonicToSeed( + data.mnemonic.toLowerCase() + ); + for (currencyCode of toDiscover) { + results[currencyCode] = []; + try { + settings = BlocksoftDict.getCurrencyAllSettings( + currencyCode, + 'BlocksoftKeys' + ); + } catch (e) { + // do nothing for now + continue; + } + + let hexes = []; + if (settings.addressCurrencyCode) { + hexes.push(this._bipHex[settings.addressCurrencyCode]); + if (!this._bipHex[settings.addressCurrencyCode]) { + throw new Error( + 'UNKNOWN_CURRENCY_CODE SETTED ' + settings.addressCurrencyCode + ); + } + } + + if (this._bipHex[currencyCode]) { + hexes.push(this._bipHex[currencyCode]); + } else if (!settings.addressCurrencyCode) { + if ( + settings.extendsProcessor && + this._bipHex[settings.extendsProcessor] + ) { + hexes.push(this._bipHex[settings.extendsProcessor]); + } else { + throw new Error( + 'UNKNOWN_CURRENCY_CODE ' + + currencyCode + + ' in bipHex AND NO SETTED addressCurrencyCode' + ); + } + } + + let isAlreadyMain = false; + + if (!data.fullTree) { + hexes = [hexes[0]]; + } + + const hexesCache = + mnemonicCache + + '_' + + settings.addressProcessor + + '_fromINDEX_' + + fromIndex + + '_' + + JSON.stringify(hexes); + let hasDerivations = false; + if ( + typeof data.derivations !== 'undefined' && + typeof data.derivations[currencyCode] !== 'undefined' && + data.derivations[currencyCode] + ) { + hasDerivations = true; + } + if (typeof CACHE[hexesCache] === 'undefined' || hasDerivations) { + BlocksoftCryptoLog.log( + `BlocksoftKeys will discover ${settings.addressProcessor}` + ); + let root = false; + if (typeof networksConstants[currencyCode] !== 'undefined') { + root = await this.getBip32Cached( + data.mnemonic, + networksConstants[currencyCode], + seed + ); + } else { + if (!bitcoinRoot) { + bitcoinRoot = await this.getBip32Cached(data.mnemonic); + } + root = bitcoinRoot; + } + // BIP32 Extended Private Key to check - uncomment + // let childFirst = root.derivePath('m/44\'/2\'/0\'/0') + // BlocksoftCryptoLog.log(childFirst.toBase58()) + + /** + * @type {EthAddressProcessor|BtcAddressProcessor} + */ + const processor = await BlocksoftDispatcher.innerGetAddressProcessor( + settings + ); + + try { + await processor.setBasicRoot(root); + } catch (e) { + e.message += ' while doing ' + JSON.stringify(settings); + throw e; + } + let currentFromIndex = fromIndex; + let currentToIndex = toIndex; + let currentFullTree = fullTree; + + BlocksoftCryptoLog.log( + `BlocksoftKeys discoverAddresses ${currencyCode} currentFromIndex.1 ${currentFromIndex}` + ); + if (hasDerivations) { + let derivation = { path: '', alreadyShown: 0, walletPubId: 0 }; + let maxIndex = 0; + for (derivation of data.derivations[currencyCode]) { + const child = root.derivePath(derivation.path); + const tmp = derivation.path.split('/'); + const result = await processor.getAddress( + child.privateKey, + { + publicKey: child.publicKey, + walletHash: data.walletHash, + derivationPath: derivation.path + }, + data, + seed, + 'discoverAddresses' + ); + result.basicPrivateKey = child.privateKey.toString('hex'); + result.basicPublicKey = child.publicKey.toString('hex'); + result.path = derivation.path; + result.alreadyShown = derivation.alreadyShown; + result.walletPubId = derivation.walletPubId || 0; + result.index = tmp[5]; + if (maxIndex < result.index) { + maxIndex = result.index * 1; + } + result.type = 'main'; + results[currencyCode].push(result); + } + if (maxIndex > 0) { + // noinspection PointlessArithmeticExpressionJS + currentFromIndex = maxIndex * 1 + 1; + currentToIndex = currentFromIndex * 1 + 10; + currentFullTree = true; + BlocksoftCryptoLog.log( + `BlocksoftKeys ${currencyCode} discoverAddresses currentFromIndex.2 ${currentFromIndex}` + ); + } + } + + let suffixes; + if (currencyCode === 'SOL') { + suffixes = [ + { type: 'main', suffix: false, after: `'/0'` }, + { type: 'no_scan', suffix: false, after: `'` } + ]; + } else if (currentFullTree) { + suffixes = [ + { type: 'main', suffix: `0'/0` }, + { type: 'change', suffix: `0'/1` } + // { 'type': 'second', 'suffix': `1'/0` }, + // { 'type': 'secondchange', 'suffix': `1'/1` } + ]; + } else { + suffixes = [{ type: 'main', suffix: `0'/0` }]; + if (currencyCode === 'BTC_SEGWIT_COMPATIBLE') { + suffixes = [ + { type: 'main', suffix: '0/1' } // heh + ]; + } + hexes = [hexes[0]]; + } + + let hex; + for (hex of hexes) { + if (isAlreadyMain) { + suffixes[0].type = 'second'; + } + isAlreadyMain = true; + + if (currentFromIndex >= 0 && currentToIndex >= 0) { + for ( + let index = currentFromIndex; + index < currentToIndex; + index++ + ) { + let suffix; + for (suffix of suffixes) { + const path = suffix.suffix + ? `m/${hex}'/${suffix.suffix}/${index}` + : `m/${hex}'/${index}${suffix.after}`; + let privateKey = false; + let publicKey = false; + if ( + currencyCode === 'SOL' || + currencyCode === 'XLM' || + currencyCode === 'WAVES' || + currencyCode === 'ASH' + ) { + // @todo move to coin address processor + } else { + const child = root.derivePath(path); + privateKey = child.privateKey; + publicKey = child.publicKey; + } + const result = await processor.getAddress( + privateKey, + { + publicKey, + walletHash: data.walletHash, + derivationPath: path, + derivationIndex: index, + derivationType: suffix.type + }, + data, + seed, + 'discoverAddresses2' + ); + if (result) { + if (privateKey) { + result.basicPrivateKey = privateKey.toString('hex'); + result.basicPublicKey = publicKey.toString('hex'); + } + result.path = path; + result.index = index; + result.alreadyShown = 0; + result.type = suffix.type; + results[currencyCode].push(result); + } + } + } + } + } + CACHE[hexesCache] = results[currencyCode]; + if (currencyCode === 'ETH') { + ETH_CACHE[mnemonicCache] = results[currencyCode][0]; + } + } else { + BlocksoftCryptoLog.log( + `BlocksoftKeys will be from cache ${settings.addressProcessor}` + ); + results[currencyCode] = CACHE[hexesCache]; + if (currencyCode === 'USDT') { + results[currencyCode] = [results[currencyCode][0]]; + } + } + } + BlocksoftCryptoLog.log(`BlocksoftKeys discoverAddresses finished`); + return results; + } + + async getSeedCached(mnemonic) { + BlocksoftCryptoLog.log(`BlocksoftKeys bip39MnemonicToSeed started`); + const mnemonicCache = mnemonic.toLowerCase(); + if (typeof CACHE[mnemonicCache] === 'undefined') { + CACHE[mnemonicCache] = await BlocksoftKeysUtils.bip39MnemonicToSeed( + mnemonic.toLowerCase() + ); + } + const seed = CACHE[mnemonicCache]; // will be rechecked on saving + BlocksoftCryptoLog.log(`BlocksoftKeys bip39MnemonicToSeed ended`); + return seed; + } + + async getBip32Cached(mnemonic, network = false, seed = false) { + const mnemonicCache = mnemonic.toLowerCase() + '_' + (network || 'btc'); + if (typeof CACHE_ROOTS[mnemonicCache] === 'undefined') { + if (!seed) { + seed = await this.getSeedCached(mnemonic); + } + CACHE_ROOTS[mnemonicCache] = network + ? bip32.fromSeed(seed, network) + : bip32.fromSeed(seed); + } + return CACHE_ROOTS[mnemonicCache]; + } + + getEthCached(mnemonicCache) { + if (typeof ETH_CACHE[mnemonicCache] === 'undefined') return false; + return ETH_CACHE[mnemonicCache]; + } + + setEthCached(mnemonic, result) { + const mnemonicCache = mnemonic.toLowerCase(); + ETH_CACHE[mnemonicCache] = result; + } + + /** + * @param {string} data.mnemonic + * @param {string} data.currencyCode + * @param {string} data.derivationPath + * @param {string} data.derivationIndex + * @param {string} data.derivationType + * @return {Promise<{address, privateKey}>} + */ + async discoverOne(data) { + const seed = await BlocksoftKeysUtils.bip39MnemonicToSeed( + data.mnemonic.toLowerCase() + ); + const root = bip32.fromSeed(seed); + const child = root.derivePath(data.derivationPath.replace(/quote/g, "'")); + /** + * @type {EthAddressProcessor|BtcAddressProcessor} + */ + const processor = await BlocksoftDispatcher.getAddressProcessor( + data.currencyCode + ); + processor.setBasicRoot(root); + return processor.getAddress( + child.privateKey, + { + derivationPath: data.derivationPath, + derivationIndex: data.derivationIndex, + derivationType: data.derivationType, + publicKey: child.publicKey + }, + data, + seed, + 'discoverOne' + ); + } + + /** + * @param {string} data.mnemonic + * @param {string} data.currencyCode + * @return {Promise<{address, privateKey}>} + */ + async discoverXpub(data) { + const seed = await BlocksoftKeysUtils.bip39MnemonicToSeed( + data.mnemonic.toLowerCase() + ); + const root = bip32.fromSeed(seed); + let path = `m/44'/0'/0'`; + let version = 0x0488b21e; // xpub + if (data.currencyCode === 'BTC_SEGWIT') { + path = `m/84'/0'/0'`; + version = 0x04b24746; // https://github.com/satoshilabs/slips/blob/master/slip-0132.md + } else if (data.currencyCode === 'BTC_SEGWIT_COMPATIBLE') { + path = `m/49'/0'/0'`; + version = 0x049d7cb2; + } + BlocksoftCryptoLog.log( + 'BlocksoftKeys.discoverXpub derive started ' + JSON.stringify(path) + ); + const rootWallet = root.derivePath(path); + BlocksoftCryptoLog.log( + 'BlocksoftKeys.discoverXpub derived, bs58 started ' + JSON.stringify(path) + ); + const res = bs58check.encode( + serialize(rootWallet, version, rootWallet.publicKey) + ); + BlocksoftCryptoLog.log( + 'BlocksoftKeys.discoverXpub res ' + JSON.stringify(path), + res + ); + return res; + } +} + +function serialize(hdkey, version, key, LEN = 78) { + // => version(4) || depth(1) || fingerprint(4) || index(4) || chain(32) || key(33) + const buffer = Buffer.allocUnsafe(LEN); + + buffer.writeUInt32BE(version, 0); + buffer.writeUInt8(hdkey.depth, 4); + + const fingerprint = hdkey.depth ? hdkey.parentFingerprint : 0x00000000; + buffer.writeUInt32BE(fingerprint, 5); + buffer.writeUInt32BE(hdkey.index, 9); + + hdkey.chainCode.copy(buffer, 13); + key.copy(buffer, 45); + + return buffer; +} + +const singleBlocksoftKeys = new BlocksoftKeys(); +export default singleBlocksoftKeys; diff --git a/src/lib/helpers/AirDAOKeysForRef.ts b/src/lib/helpers/AirDAOKeysForRef.ts new file mode 100644 index 000000000..ffb92b1dc --- /dev/null +++ b/src/lib/helpers/AirDAOKeysForRef.ts @@ -0,0 +1,65 @@ +/** + * @author Javid + * @version 0.5 + */ +import AirDAOKeys from './AirDAOKeys'; +import BlocksoftDispatcher from './AirDAODispatcher'; +import { WalletUtils } from '@utils/wallet'; + +const CACHE: { [key: string]: any } = {}; + +class AirDAOKeysForRef { + async discoverPublicAndPrivate(data: { + mnemonic: string; + index: number; + }): Promise<{ + currencyCode: { + address: string; + privateKey: string; + path: string; + index: number; + type: unknown; // TODO + }[]; + }> { + const mnemonicCache = data.mnemonic.toLowerCase(); + if (typeof CACHE[mnemonicCache] !== 'undefined') + return CACHE[mnemonicCache]; + let result = AirDAOKeys.getEthCached(mnemonicCache); + if (!result) { + let index = 0; + if (typeof data.index !== 'undefined') { + index = data.index; + } + const root = await AirDAOKeys.getBip32Cached(data.mnemonic); + const path = `m/44'/60'/${index}'/0/0`; + const child = root.derivePath(path); + + const processor = await BlocksoftDispatcher.getAddressProcessor('ETH'); + result = await processor.getAddress(child.privateKey); + result.index = index; + result.path = path; + if (index === 0) { + AirDAOKeys.setEthCached(data.mnemonic, result); + } + } + // noinspection JSPrimitiveTypeWrapperUsage + result.cashbackToken = WalletUtils.addressToToken(result.address); + CACHE[mnemonicCache] = result; + return result; + } + + async signDataForApi(msg: string, privateKey: string) { + const processor = await BlocksoftDispatcher.getAddressProcessor('ETH'); + if (privateKey.substring(0, 2) !== '0x') { + privateKey = '0x' + privateKey; + } + const signedData = await processor.signMessage(msg, privateKey); + delete signedData.v; + delete signedData.r; + delete signedData.s; + return signedData; + } +} + +const singleAirDAOKeysForRef = new AirDAOKeysForRef(); +export default singleAirDAOKeysForRef; diff --git a/src/lib/AirDAOStorage.ts b/src/lib/helpers/AirDAOStorage.ts similarity index 100% rename from src/lib/AirDAOStorage.ts rename to src/lib/helpers/AirDAOStorage.ts diff --git a/src/lib/index.ts b/src/lib/index.ts index 1696fbc37..fb6863601 100644 --- a/src/lib/index.ts +++ b/src/lib/index.ts @@ -1,4 +1,4 @@ export * from './device'; export * from './notification'; -export * from './AirDAOStorage'; +export * from './helpers/AirDAOStorage'; export { default as PermissionService } from './permission'; diff --git a/src/utils/blockchain.js b/src/utils/blockchain.js new file mode 100644 index 000000000..f4b4c840d --- /dev/null +++ b/src/utils/blockchain.js @@ -0,0 +1,255 @@ +import { NativeModules } from 'react-native'; + +import CoinBlocksoftDict from '@crypto/assets/coinBlocksoftDict.json'; + +const { RNFastCrypto } = NativeModules; + +const VisibleCodes = [ + 'CASHBACK', + 'NFT', + 'BTC', + 'ETH', + 'TRX', + 'TRX_USDT' // add code here to show on start screen +]; +const Codes = [ + 'CASHBACK', + 'NFT', + 'BTC', + 'ETH', + 'USDT', + 'LTC', + 'ETH_USDT', + 'TRX', + 'TRX_USDT', + 'BNB', + 'BNB_SMART', + 'MATIC', + 'ETH_TRUE_USD', + 'ETH_BNB', + 'ETH_USDC', + 'ETH_PAX', + 'ETH_DAI', + 'FIO' // add code here for autocreation the wallet address with the currency +]; +const Currencies = CoinBlocksoftDict; + +const CurrenciesForTests = { + BTC_SEGWIT: { + currencyName: 'Bitcoin Segwit', + currencyCode: 'BTC_SEGWIT', + currencySymbol: 'BTC', + addressProcessor: 'BTC_SEGWIT', + extendsProcessor: 'BTC', + ratesCurrencyCode: 'BTC', + addressPrefix: 'bc1', + defaultPath: `m/84'/0'/0'/0/0` + }, + BTC_SEGWIT_COMPATIBLE: { + currencyName: 'Bitcoin Compatible Segwit', + currencyCode: 'BTC_SEGWIT_COMPATIBLE', + currencySymbol: 'BTC', + addressProcessor: 'BTC_SEGWIT_COMPATIBLE', + extendsProcessor: 'BTC', + ratesCurrencyCode: 'BTC', + addressPrefix: '3', + defaultPath: `m/49'/0'/0'/0/1` + }, + LTC_SEGWIT: { + currencyName: 'Bitcoin Segwit', + currencyCode: 'LTC_SEGWIT', + currencySymbol: 'LTC', + addressProcessor: 'LTC_SEGWIT', + extendsProcessor: 'LTC', + ratesCurrencyCode: 'LTC', + addressPrefix: 'ltc', + defaultPath: `m/84'/2'/0'/0/0` + }, + ETH_ROPSTEN_KSU_TOKEN: { + currencyName: 'Some ERC-20 Ropsten', + currencyCode: 'ETH_ROPSTEN_KSU_TOKEN', + currencySymbol: 'Some ERC-20 Ropsten', + extendsProcessor: 'ETH_TRUE_USD', + addressCurrencyCode: 'ETH_ROPSTEN', + feesCurrencyCode: 'ETH_ROPSTEN', + network: 'ropsten', + decimals: 6, + tokenAddress: '0xdb30610f156e1d4aefaa9b4423909297ceff64c2', + currencyExplorerLink: 'https:ropsten.etherscan.io/address/', + currencyExplorerTxLink: 'https:ropsten.etherscan.io/tx/' + }, + TRX_PLANET: { + currencyName: 'PLANET (FREE TRX TOKEN)', + currencyCode: 'TRX_PLANET', + currencySymbol: 'PLANET TRX', + extendsProcessor: 'TRX_USDT', + feesCurrencyCode: 'TRX', // pay for tx in other currency, if no - used currencyCode + network: 'trx', // network also used as mark of rate scanning + decimals: 6, + tokenName: '1002742', + currencyExplorerLink: 'https://tronscan.org/#/address/', + currencyExplorerTxLink: 'https://tronscan.org/#/transaction/' + } +}; + +if (typeof RNFastCrypto === 'undefined') { + delete Currencies['XMR']; +} + +/** + * @param {int} currencyObject.id + * @param {int} currencyObject.isHidden + * @param {string} currencyObject.tokenJson + * @param {string} currencyObject.tokenDecimals 18 + * @param {string} currencyObject.tokenAddress '0xd26114cd6EE289AccF82350c8d8487fedB8A0C07' + * @param {string} currencyObject.tokenType 'ETH_ERC_20' + * @param {string} currencyObject.currencyName 'OMG Token' + * @param {string} currencyObject.currencySymbol 'OMG' + * @param {string} currencyObject.currencyCode 'OMG' + */ +function addAndUnifyCustomCurrency(currencyObject) { + const tmp = { + currencyName: currencyObject.currencyName, + currencyCode: 'CUSTOM_' + currencyObject.currencyCode, + currencySymbol: currencyObject.currencySymbol, + ratesCurrencyCode: currencyObject.currencyCode, + decimals: currencyObject.tokenDecimals + }; + tmp.currencyType = 'custom'; + if (currencyObject.tokenType === 'BNB_SMART_20') { + tmp.currencyCode = 'CUSTOM_BNB_SMART_20_' + currencyObject.currencyCode; + if (tmp.ratesCurrencyCode.substr(0, 1) === 'B') { + const subRate = tmp.ratesCurrencyCode.substr(1); + if (typeof Currencies[subRate] !== 'undefined') { + tmp.ratesCurrencyCode = subRate; + } + } + tmp.extendsProcessor = 'BNB_SMART_CAKE'; + tmp.addressUiChecker = 'ETH'; + tmp.tokenAddress = currencyObject.tokenAddress; + tmp.tokenBlockchain = 'BNB'; + tmp.currencyExplorerLink = + 'https://bscscan.com/token/' + currencyObject.tokenAddress + '?a='; + } else if (currencyObject.tokenType === 'MATIC_ERC_20') { + tmp.currencyCode = 'CUSTOM_MATIC_ERC_20_' + currencyObject.currencyCode; + tmp.extendsProcessor = 'MATIC_USDT'; + tmp.addressUiChecker = 'ETH'; + tmp.tokenAddress = currencyObject.tokenAddress; + tmp.tokenBlockchain = 'MATIC'; + tmp.currencyExplorerLink = + 'https://polygonscan.com/token/' + currencyObject.tokenAddress + '?a='; + } else if (currencyObject.tokenType === 'FTM_ERC_20') { + tmp.currencyCode = 'CUSTOM_FTM_ERC_20_' + currencyObject.currencyCode; + tmp.extendsProcessor = 'FTM_USDC'; + tmp.addressUiChecker = 'ETH'; + tmp.tokenAddress = currencyObject.tokenAddress; + tmp.tokenBlockchain = 'FTM'; + tmp.currencyExplorerLink = + 'https://ftmscan.com/token/' + currencyObject.tokenAddress + '?a='; + } else if (currencyObject.tokenType === 'VLX_ERC_20') { + tmp.currencyCode = 'CUSTOM_VLX_ERC_20_' + currencyObject.currencyCode; + tmp.extendsProcessor = 'VLX_USDT'; + tmp.addressUiChecker = 'ETH'; + tmp.tokenAddress = currencyObject.tokenAddress; + tmp.tokenBlockchain = 'VLX'; + tmp.currencyExplorerLink = + 'https://evmexplorer.velas.com/tokens/' + currencyObject.tokenAddress; + } else if (currencyObject.tokenType === 'ONE_ERC_20') { + tmp.currencyCode = 'CUSTOM_ONE_ERC_20_' + currencyObject.currencyCode; + tmp.extendsProcessor = 'ONE_USDC'; + tmp.addressUiChecker = 'ETH'; + tmp.tokenAddress = currencyObject.tokenAddress; + tmp.tokenBlockchain = 'ONE'; + tmp.currencyExplorerLink = + 'https://explorer.harmony.one/address/' + currencyObject.tokenAddress; + } else if (currencyObject.tokenType === 'SOL') { + tmp.currencyCode = 'CUSTOM_SOL_' + currencyObject.currencyCode; + tmp.extendsProcessor = 'SOL_RAY'; + tmp.addressUiChecker = 'SOL'; + tmp.tokenAddress = currencyObject.tokenAddress; + tmp.tokenBlockchain = 'SOLANA'; + } else if (currencyObject.tokenType === 'ETH_ERC_20') { + tmp.extendsProcessor = 'ETH_TRUE_USD'; + tmp.addressUiChecker = 'ETH'; + tmp.tokenAddress = currencyObject.tokenAddress; + tmp.tokenBlockchain = 'ETHEREUM'; + tmp.currencyExplorerLink = + 'https://etherscan.io/token/' + currencyObject.tokenAddress + '?a='; + } else if (currencyObject.tokenType === 'TRX') { + tmp.currencyCode = 'CUSTOM_TRX_' + currencyObject.currencyCode; + tmp.extendsProcessor = 'TRX_USDT'; + tmp.addressUiChecker = 'TRX'; + tmp.currencyIcon = 'TRX'; + tmp.tokenName = currencyObject.tokenAddress; + tmp.tokenBlockchain = 'TRON'; + tmp.currencyExplorerLink = 'https://tronscan.org/#/address/'; + tmp.currencyExplorerTxLink = 'https://tronscan.org/#/transaction/'; + if (tmp.tokenName.substr(0, 1) !== 'T') { + this.skipParentBalanceCheck = true; + } + } else { + return false; + } + + Currencies[tmp.currencyCode] = tmp; + return tmp; +} + +const ALL_SETTINGS = {}; + +function getCurrencyAllSettings(currencyCodeOrObject, source = '') { + let currencyCode = currencyCodeOrObject; + if (typeof currencyCode === 'undefined' || !currencyCode) { + return false; + } + if (currencyCode === 'ETH_LAND') { + Database.query(`DELETE FROM account WHERE currency_code='ETH_LAND'`); + Database.query( + `DELETE FROM account_balance WHERE currency_code='ETH_LAND'` + ); + Database.query(`DELETE FROM currency WHERE currency_code='ETH_LAND'`); + } + + if (typeof currencyCodeOrObject.currencyCode !== 'undefined') { + currencyCode = currencyCodeOrObject.currencyCode; + } + if (typeof ALL_SETTINGS[currencyCode] !== 'undefined') { + return ALL_SETTINGS[currencyCode]; + } + + let settings = Currencies[currencyCode]; + if (!settings) { + settings = CurrenciesForTests[currencyCode]; + } + if (!settings) { + throw new Error( + 'Currency code not found in dict ' + + JSON.stringify(currencyCode) + + ' from ' + + source + ); + } + if (settings.extendsProcessor && Currencies[settings.extendsProcessor]) { + const settingsParent = Currencies[settings.extendsProcessor]; + let newKey; + for (newKey of Object.keys(settingsParent)) { + if ( + typeof settings[newKey] === 'undefined' || + settings[newKey] === false + ) { + settings[newKey] = settingsParent[newKey]; + } + } + } + ALL_SETTINGS[currencyCode] = settings; + return settings; +} + +export const BlockchainUtils = { + VisibleCodes, + Codes, + Currencies, + CurrenciesForTests, + getCurrencyAllSettings, + addAndUnifyCustomCurrency +}; diff --git a/src/utils/cashback.ts b/src/utils/cashback.ts new file mode 100644 index 000000000..0b5e2cad7 --- /dev/null +++ b/src/utils/cashback.ts @@ -0,0 +1 @@ +const getByHash = async (tmpHash: string) => {}; diff --git a/src/utils/wallet.ts b/src/utils/wallet.ts index 44fa5c4b8..7b0744524 100644 --- a/src/utils/wallet.ts +++ b/src/utils/wallet.ts @@ -2,7 +2,7 @@ import { WalletInitSource, WalletMetadata } from '@appTypes'; import { Wallet } from '@models/Wallet'; import { Crypto } from './crypto'; import { MnemonicUtils } from './mnemonics'; -import AirDAOStorage from '@lib/storage'; +import AirDAOStorage from '@lib/helpers/AirDAOStorage'; const _saveWallet = async (wallet: WalletMetadata) => { let storedKey = ''; @@ -24,7 +24,13 @@ const _saveWallet = async (wallet: WalletMetadata) => { return storedKey; }; +const addressToToken = (address: string) => { + // any algo could be used to "hide" actual address + return Buffer.from(address).toString('base64').slice(3, 11); +}; + const _getWalletName = async () => { + // TODO return 'AirDAO Wallet'; }; @@ -32,7 +38,7 @@ const processWallet = async ( data: Pick, source = WalletInitSource.GENERATION ) => { - const hash = await _saveWallet(data); + const hash = await _saveWallet(data); // done let tmpWalletName = data.name; if (!tmpWalletName || tmpWalletName === '') { @@ -47,4 +53,4 @@ const processWallet = async ( } }; -export const WalletUtils = { processWallet }; +export const WalletUtils = { processWallet, addressToToken }; diff --git a/tsconfig.json b/tsconfig.json index ff60d6727..065ef34b4 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -18,6 +18,7 @@ "@components/*": ["./src/components/*"], "@constants/*": ["./src/constants/*"], "@contexts/*": ["./src/contexts/*"], + "@crypto/*": ["./crypto/*"], "@database/*": ["./src/database/*"], "@hooks/*": ["./src/hooks/*"], "@lib/*": ["./src/lib/*"], diff --git a/yarn.lock b/yarn.lock index ffdf08b9b..800bce633 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2748,6 +2748,16 @@ resolved "https://registry.npmjs.org/@types/node/-/node-18.15.11.tgz" integrity sha512-E5Kwq2n4SbMzQOn6wnmBjuK9ouqlURrcZDVfbo9ftDDTFt3nk7ZKK4GMOzoYgnpQJKcxwQw+lGaBvvlMo0qN/Q== +"@types/node@10.12.18": + version "10.12.18" + resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.18.tgz#1d3ca764718915584fcd9f6344621b7672665c67" + integrity sha512-fh+pAqt4xRzPfqA6eh3Z2y6fyZavRIumvjhaCL753+TVkGKGhpPeyrJG2JftD0T9q4GF00KjefsQ+PQNDdWQaQ== + +"@types/node@12.7.5": + version "12.7.5" + resolved "https://registry.yarnpkg.com/@types/node/-/node-12.7.5.tgz#e19436e7f8e9b4601005d73673b6dc4784ffcc2f" + integrity sha512-9fq4jZVhPNW8r+UYKnxF1e2HkDWOWKM5bC2/7c9wPV835I0aOrVbS/Hw/pWPk2uKrNXQqg9Z959Kz+IYDd5p3w== + "@types/pixelmatch@^5.2.4": version "5.2.4" resolved "https://registry.yarnpkg.com/@types/pixelmatch/-/pixelmatch-5.2.4.tgz#ca145cc5ede1388c71c68edf2d1f5190e5ddd0f6" @@ -2965,6 +2975,14 @@ "@urql/core" ">=2.3.1" wonka "^4.0.14" +"@waves/ts-lib-crypto@^1.4.4-beta.1": + version "1.4.4-beta.1" + resolved "https://registry.yarnpkg.com/@waves/ts-lib-crypto/-/ts-lib-crypto-1.4.4-beta.1.tgz#5a494f5ff3128b2f80c1890b1fb13d8ba2cb8c53" + integrity sha512-tlvThkMCoCDicOznW82wDZWQqfAWcm6ulQnuNzR++X9o0EOHM3Cj8LlS2pkgF0YjZrqEYHTp/4e0RXXYVY+dpw== + dependencies: + js-sha3 "^0.8.0" + node-forge "^0.10.0" + "@xmldom/xmldom@~0.7.7": version "0.7.10" resolved "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.7.10.tgz" @@ -3494,6 +3512,13 @@ balanced-match@^1.0.0: resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== +base-x@^3.0.2: + version "3.0.9" + resolved "https://registry.yarnpkg.com/base-x/-/base-x-3.0.9.tgz#6349aaabb58526332de9f60995e548a53fe21320" + integrity sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ== + dependencies: + safe-buffer "^5.0.1" + base64-js@^1.1.2, base64-js@^1.2.3, base64-js@^1.3.0, base64-js@^1.3.1, base64-js@^1.5.1: version "1.5.1" resolved "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz" @@ -3512,6 +3537,11 @@ base@^0.11.1: mixin-deep "^1.2.0" pascalcase "^0.1.1" +bech32@^1.1.2: + version "1.1.4" + resolved "https://registry.yarnpkg.com/bech32/-/bech32-1.1.4.tgz#e38c9f37bf179b8eb16ae3a772b40c356d4832e9" + integrity sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ== + better-opn@~3.0.2: version "3.0.2" resolved "https://registry.npmjs.org/better-opn/-/better-opn-3.0.2.tgz" @@ -3524,6 +3554,31 @@ big-integer@1.6.x: resolved "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz" integrity sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg== +bindings@^1.3.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" + integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ== + dependencies: + file-uri-to-path "1.0.0" + +bip174@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/bip174/-/bip174-1.0.1.tgz#858a587f9529e22ee9b0572fd884e5783696824d" + integrity sha512-Mq2aFs1TdMfxBpYPg7uzjhsiXbAtoVq44TNjEWtvuZBiBgc3m7+n55orYMtTAxdg7jWbL4DtH0MKocJER4xERQ== + +bip32@^2.0.4: + version "2.0.6" + resolved "https://registry.yarnpkg.com/bip32/-/bip32-2.0.6.tgz#6a81d9f98c4cd57d05150c60d8f9e75121635134" + integrity sha512-HpV5OMLLGTjSVblmrtYRfFFKuQB+GArM0+XP8HGWfJ5vxYBqo+DesvJwOdC2WJ3bCkZShGf0QIfoIpeomVzVdA== + dependencies: + "@types/node" "10.12.18" + bs58check "^2.1.1" + create-hash "^1.2.0" + create-hmac "^1.1.7" + tiny-secp256k1 "^1.1.3" + typeforce "^1.11.5" + wif "^2.0.6" + bip39@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/bip39/-/bip39-3.1.0.tgz#c55a418deaf48826a6ceb34ac55b3ee1577e18a3" @@ -3531,6 +3586,39 @@ bip39@^3.1.0: dependencies: "@noble/hashes" "^1.2.0" +bip66@^1.1.0: + version "1.1.5" + resolved "https://registry.yarnpkg.com/bip66/-/bip66-1.1.5.tgz#01fa8748785ca70955d5011217d1b3139969ca22" + integrity sha512-nemMHz95EmS38a26XbbdxIYj5csHd3RMP3H5bwQknX0WYHF01qhpufP42mLOwVICuH2JmhIhXiWs89MfUGL7Xw== + dependencies: + safe-buffer "^5.0.1" + +bitcoin-ops@^1.3.0, bitcoin-ops@^1.4.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/bitcoin-ops/-/bitcoin-ops-1.4.1.tgz#e45de620398e22fd4ca6023de43974ff42240278" + integrity sha512-pef6gxZFztEhaE9RY9HmWVmiIHqCb2OyS4HPKkpc6CIiiOa3Qmuoylxc5P2EkU3w+5eTSifI9SEZC88idAIGow== + +"bitcoinjs-lib@git+https://git@github.com/trustee-wallet/bitcoinjs-lib": + version "5.1.6" + resolved "git+https://git@github.com/trustee-wallet/bitcoinjs-lib#400f78be56f9017fbb9a24dce16f1a3b839a2df8" + dependencies: + "@types/node" "12.7.5" + bech32 "^1.1.2" + bip174 "^1.0.1" + bip32 "^2.0.4" + bip66 "^1.1.0" + bitcoin-ops "^1.4.0" + bs58check "^2.0.0" + create-hash "^1.1.0" + create-hmac "^1.1.3" + merkle-lib "^2.0.10" + pushdata-bitcoin "^1.0.1" + randombytes "^2.0.1" + tiny-secp256k1 "^1.1.1" + typeforce "^1.11.3" + varuint-bitcoin "^1.0.4" + wif "^2.0.1" + bl@^4.1.0: version "4.1.0" resolved "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz" @@ -3545,6 +3633,11 @@ blueimp-md5@^2.10.0: resolved "https://registry.npmjs.org/blueimp-md5/-/blueimp-md5-2.19.0.tgz" integrity sha512-DRQrD6gJyy8FbiE4s+bDoXS9hiW3Vbx5uCdwvcCf3zLHL+Iv7LtGHLpr+GZV8rHG8tK766FGYBwRbu8pELTt+w== +bn.js@^4.11.8, bn.js@^4.11.9: + version "4.12.0" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" + integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA== + body-parser@^1.20.1: version "1.20.2" resolved "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz" @@ -3620,6 +3713,11 @@ braces@^3.0.2: dependencies: fill-range "^7.0.1" +brorand@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" + integrity sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w== + browserslist@^4.21.3, browserslist@^4.21.5: version "4.21.5" resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz" @@ -3630,6 +3728,22 @@ browserslist@^4.21.3, browserslist@^4.21.5: node-releases "^2.0.8" update-browserslist-db "^1.0.10" +bs58@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/bs58/-/bs58-4.0.1.tgz#be161e76c354f6f788ae4071f63f34e8c4f0a42a" + integrity sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw== + dependencies: + base-x "^3.0.2" + +bs58check@<3.0.0, bs58check@^2.0.0, bs58check@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/bs58check/-/bs58check-2.1.2.tgz#53b018291228d82a5aa08e7d796fdafda54aebfc" + integrity sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA== + dependencies: + bs58 "^4.0.0" + create-hash "^1.1.0" + safe-buffer "^5.1.2" + bser@2.1.1: version "2.1.1" resolved "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz" @@ -3854,6 +3968,14 @@ ci-info@^3.2.0, ci-info@^3.3.0, ci-info@^3.7.0: resolved "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz" integrity sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw== +cipher-base@^1.0.1, cipher-base@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de" + integrity sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q== + dependencies: + inherits "^2.0.1" + safe-buffer "^5.0.1" + cjs-module-lexer@^1.0.0: version "1.2.2" resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz#9f84ba3244a512f3a54e5277e8eef4c489864e40" @@ -4173,6 +4295,29 @@ cosmiconfig@^5.0.5, cosmiconfig@^5.1.0: js-yaml "^3.13.1" parse-json "^4.0.0" +create-hash@^1.1.0, create-hash@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196" + integrity sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg== + dependencies: + cipher-base "^1.0.1" + inherits "^2.0.1" + md5.js "^1.3.4" + ripemd160 "^2.0.1" + sha.js "^2.4.0" + +create-hmac@^1.1.3, create-hmac@^1.1.7: + version "1.1.7" + resolved "https://registry.yarnpkg.com/create-hmac/-/create-hmac-1.1.7.tgz#69170c78b3ab957147b2b8b04572e47ead2243ff" + integrity sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg== + dependencies: + cipher-base "^1.0.3" + create-hash "^1.1.0" + inherits "^2.0.1" + ripemd160 "^2.0.0" + safe-buffer "^5.0.1" + sha.js "^2.4.8" + cross-fetch@^3.1.5: version "3.1.5" resolved "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz" @@ -4575,6 +4720,19 @@ electron-to-chromium@^1.4.284: resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.348.tgz" integrity sha512-gM7TdwuG3amns/1rlgxMbeeyNoBFPa+4Uu0c7FeROWh4qWmvSOnvcslKmWy51ggLKZ2n/F/4i2HJ+PVNxH9uCQ== +elliptic@^6.4.0: + version "6.5.4" + resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb" + integrity sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ== + dependencies: + bn.js "^4.11.9" + brorand "^1.1.0" + hash.js "^1.0.0" + hmac-drbg "^1.0.1" + inherits "^2.0.4" + minimalistic-assert "^1.0.1" + minimalistic-crypto-utils "^1.0.1" + emittery@^0.13.1: version "0.13.1" resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.13.1.tgz#c04b8c3457490e0847ae51fced3af52d338e3dad" @@ -5512,6 +5670,11 @@ file-entry-cache@^6.0.1: dependencies: flat-cache "^3.0.4" +file-uri-to-path@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" + integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== + fill-range@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz" @@ -5997,6 +6160,23 @@ has@^1.0.3: dependencies: function-bind "^1.1.1" +hash-base@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.1.0.tgz#55c381d9e06e1d2997a883b4a3fddfe7f0d3af33" + integrity sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA== + dependencies: + inherits "^2.0.4" + readable-stream "^3.6.0" + safe-buffer "^5.2.0" + +hash.js@^1.0.0, hash.js@^1.0.3: + version "1.1.7" + resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42" + integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA== + dependencies: + inherits "^2.0.3" + minimalistic-assert "^1.0.1" + hermes-estree@0.8.0: version "0.8.0" resolved "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.8.0.tgz" @@ -6016,6 +6196,15 @@ hermes-profile-transformer@^0.0.6: dependencies: source-map "^0.7.3" +hmac-drbg@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" + integrity sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg== + dependencies: + hash.js "^1.0.3" + minimalistic-assert "^1.0.0" + minimalistic-crypto-utils "^1.0.1" + hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2: version "3.3.2" resolved "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz" @@ -6161,9 +6350,9 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@2.0.4, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3: +inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3: version "2.0.4" - resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== ini@~1.3.0: @@ -7147,6 +7336,11 @@ js-sdsl@^4.1.4: resolved "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.4.0.tgz" integrity sha512-FfVSdx6pJ41Oa+CF7RDaFmTnCaFhua+SNYQX74riGOpl96x+2jQCqEfQ2bnXu/5DPCqlRuiqyvTJM0Qjz26IVg== +js-sha3@^0.8.0: + version "0.8.0" + resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.8.0.tgz#b9b7a5da73afad7dedd0f8c463954cbde6818840" + integrity sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q== + "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz" @@ -7567,6 +7761,15 @@ md5-file@^3.2.3: dependencies: buffer-alloc "^1.1.0" +md5.js@^1.3.4: + version "1.3.5" + resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f" + integrity sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg== + dependencies: + hash-base "^3.0.0" + inherits "^2.0.1" + safe-buffer "^5.1.2" + md5@^2.2.1: version "2.3.0" resolved "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz" @@ -7620,6 +7823,11 @@ merge2@^1.3.0, merge2@^1.4.1: resolved "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz" integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== +merkle-lib@^2.0.10: + version "2.0.10" + resolved "https://registry.yarnpkg.com/merkle-lib/-/merkle-lib-2.0.10.tgz#82b8dbae75e27a7785388b73f9d7725d0f6f3326" + integrity sha512-XrNQvUbn1DL5hKNe46Ccs+Tu3/PYOlrcZILuGUhb95oKBPjc/nmIC8D462PQkipVDGKRvwhn+QFg2cCdIvmDJA== + metro-babel-transformer@0.73.9: version "0.73.9" resolved "https://registry.npmjs.org/metro-babel-transformer/-/metro-babel-transformer-0.73.9.tgz" @@ -7966,6 +8174,16 @@ min-indent@^1.0.0: resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg== +minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" + integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== + +minimalistic-crypto-utils@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" + integrity sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg== + "minimatch@2 || 3", minimatch@^3.0.2, minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: version "3.1.2" resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz" @@ -8092,6 +8310,11 @@ mz@^2.7.0: object-assign "^4.0.1" thenify-all "^1.0.0" +nan@^2.13.2: + version "2.17.0" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.17.0.tgz#c0150a2368a182f033e9aa5195ec76ea41a199cb" + integrity sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ== + nanoid@^3.1.23: version "3.3.6" resolved "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz" @@ -8183,6 +8406,11 @@ node-fetch@^2.2.0, node-fetch@^2.6.0, node-fetch@^2.6.1, node-fetch@^2.6.7: dependencies: whatwg-url "^5.0.0" +node-forge@^0.10.0: + version "0.10.0" + resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.10.0.tgz#32dea2afb3e9926f02ee5ce8794902691a676bf3" + integrity sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA== + node-forge@^1.2.1, node-forge@^1.3.1: version "1.3.1" resolved "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz" @@ -8889,6 +9117,13 @@ pure-rand@^6.0.0: resolved "https://registry.yarnpkg.com/pure-rand/-/pure-rand-6.0.2.tgz#a9c2ddcae9b68d736a8163036f088a2781c8b306" integrity sha512-6Yg0ekpKICSjPswYOuC5sku/TSWaRYlA0qsXqJgM/d/4pLPHPuTxK7Nbf7jFKzAeedUhR8C7K9Uv63FBsSo8xQ== +pushdata-bitcoin@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/pushdata-bitcoin/-/pushdata-bitcoin-1.0.1.tgz#15931d3cd967ade52206f523aa7331aef7d43af7" + integrity sha512-hw7rcYTJRAl4olM8Owe8x0fBuJJ+WGbMhQuLWOXEMN3PxPCKQHRkhfL+XG0+iXUmSHjkMmb3Ba55Mt21cZc9kQ== + dependencies: + bitcoin-ops "^1.3.0" + qrcode-terminal@0.11.0: version "0.11.0" resolved "https://registry.npmjs.org/qrcode-terminal/-/qrcode-terminal-0.11.0.tgz" @@ -8921,6 +9156,13 @@ queue-microtask@^1.2.2: resolved "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz" integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== +randombytes@^2.0.1: + version "2.1.0" + resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" + integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== + dependencies: + safe-buffer "^5.1.0" + range-parser@~1.2.1: version "1.2.1" resolved "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz" @@ -9183,9 +9425,9 @@ react@18.2.0: dependencies: loose-envify "^1.1.0" -readable-stream@^3.4.0: +readable-stream@^3.4.0, readable-stream@^3.6.0: version "3.6.2" - resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== dependencies: inherits "^2.0.3" @@ -9490,6 +9732,14 @@ rimraf@~2.6.2: dependencies: glob "^7.1.3" +ripemd160@^2.0.0, ripemd160@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c" + integrity sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA== + dependencies: + hash-base "^3.0.0" + inherits "^2.0.1" + run-parallel@^1.1.9: version "1.2.0" resolved "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz" @@ -9509,9 +9759,9 @@ safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== -safe-buffer@~5.2.0: +safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@~5.2.0: version "5.2.1" - resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== safe-json-stringify@~1: @@ -9654,6 +9904,14 @@ setprototypeof@1.2.0: resolved "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz" integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== +sha.js@^2.4.0, sha.js@^2.4.8: + version "2.4.11" + resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7" + integrity sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ== + dependencies: + inherits "^2.0.1" + safe-buffer "^5.0.1" + shallow-clone@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-1.0.0.tgz#4480cd06e882ef68b2ad88a3ea54832e2c48b571" @@ -10352,6 +10610,17 @@ through@2, through@^2.3.8: resolved "https://registry.npmjs.org/through/-/through-2.3.8.tgz" integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== +tiny-secp256k1@^1.1.1, tiny-secp256k1@^1.1.3: + version "1.1.6" + resolved "https://registry.yarnpkg.com/tiny-secp256k1/-/tiny-secp256k1-1.1.6.tgz#7e224d2bee8ab8283f284e40e6b4acb74ffe047c" + integrity sha512-FmqJZGduTyvsr2cF3375fqGHUovSwDi/QytexX1Se4BPuPZpTE5Ftp5fg+EFSuEf3lhZqgCRjEG3ydUQ/aNiwA== + dependencies: + bindings "^1.3.0" + bn.js "^4.11.8" + create-hmac "^1.1.7" + elliptic "^6.4.0" + nan "^2.13.2" + tmp@^0.0.33: version "0.0.33" resolved "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz" @@ -10572,6 +10841,11 @@ typed-array-length@^1.0.4: for-each "^0.3.3" is-typed-array "^1.1.9" +typeforce@^1.11.3, typeforce@^1.11.5: + version "1.18.0" + resolved "https://registry.yarnpkg.com/typeforce/-/typeforce-1.18.0.tgz#d7416a2c5845e085034d70fcc5b6cc4a90edbfdc" + integrity sha512-7uc1O8h1M1g0rArakJdf0uLRSSgFcYexrVoKo+bzJd32gd4gDy2L/Z+8/FjPnU9ydY3pEnVPtr9FyscYY60K1g== + typescript@^4.9.5: version "4.9.5" resolved "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz" @@ -10804,6 +11078,13 @@ validate-npm-package-name@^3.0.0: dependencies: builtins "^1.0.3" +varuint-bitcoin@^1.0.4: + version "1.1.2" + resolved "https://registry.yarnpkg.com/varuint-bitcoin/-/varuint-bitcoin-1.1.2.tgz#e76c138249d06138b480d4c5b40ef53693e24e92" + integrity sha512-4EVb+w4rx+YfVM32HQX42AbbT7/1f5zwAYhIujKXKk8NQK+JfRVl3pqT3hjNn/L+RstigmGGKVwHA/P0wgITZw== + dependencies: + safe-buffer "^5.1.1" + vary@~1.1.2: version "1.1.2" resolved "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz" @@ -10935,6 +11216,13 @@ which@^2.0.1: dependencies: isexe "^2.0.0" +wif@^2.0.1, wif@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/wif/-/wif-2.0.6.tgz#08d3f52056c66679299726fade0d432ae74b4704" + integrity sha512-HIanZn1zmduSF+BQhkE+YXIbEiH0xPr1012QbFEGB0xsKqJii0/SqJjyn8dFv6y36kOznMgMB+LGcbZTJ1xACQ== + dependencies: + bs58check "<3.0.0" + wonka@^4.0.14: version "4.0.15" resolved "https://registry.npmjs.org/wonka/-/wonka-4.0.15.tgz" From f063bc78d2a91a26d0f26d980144b46a3734c892 Mon Sep 17 00:00:00 2001 From: JavidHaji-zada Date: Tue, 25 Jul 2023 12:15:46 +0400 Subject: [PATCH 014/509] converted bch --- App.tsx | 2 +- crypto/actions/BlocksoftKeys/BlocksoftKeys.js | 796 ++++++------ .../BlocksoftKeysStorage.js | 604 ++++----- .../BlocksoftTransfer/BlocksoftTransfer.ts | 694 +++++++---- .../BlocksoftTransferUtils.ts | 121 +- .../blockchains/AirDAOBlockchainTypes.ts | 44 +- .../blockchains/AirDAODispatcher.ts | 0 .../blockchains/AirDAOTransferDispatcher.ts | 188 +++ .../blockchains/bch/BchTransferProcessor.ts | 6 +- crypto/blockchains/bch/ext/BtcCashUtils.js | 344 +++--- .../bch/providers/BchSendProvider.ts | 12 +- .../bch/providers/BchUnspentsProvider.ts | 119 +- crypto/blockchains/bch/tx/BchTxBuilder.ts | 10 +- .../btc/providers/BtcUnspentsProvider.ts | 434 ++++--- crypto/blockchains/btc/tx/BtcTxBuilder.ts | 351 +++--- .../blockchains/btc/tx/BtcTxInputsOutputs.ts | 282 +++-- .../doge/tx/DogeTxInputsOutputs.ts | 1074 ++++++++++------- crypto/common/AirDAODictTypes.ts | 58 + crypto/common/BlocksoftDict.js | 398 +++--- crypto/common/BlocksoftDictTypes.ts | 56 - crypto/common/BlocksoftPrettyNumbers.js | 312 ++--- package.json | 2 + src/lib/BlocksoftKeys.js | 796 ++++++------ .../BlocksoftTransferDispatcher.ts | 135 --- src/prototypes/buffer.ts | 2 - src/prototypes/global.js | 26 + yarn.lock | 48 +- 27 files changed, 3996 insertions(+), 2918 deletions(-) rename src/lib/blockchains/BlocksoftBlockchainTypes.ts => crypto/blockchains/AirDAOBlockchainTypes.ts (86%) rename src/lib/blockchains/BlocksoftDispatcher.ts => crypto/blockchains/AirDAODispatcher.ts (100%) create mode 100644 crypto/blockchains/AirDAOTransferDispatcher.ts create mode 100644 crypto/common/AirDAODictTypes.ts delete mode 100644 crypto/common/BlocksoftDictTypes.ts delete mode 100644 src/lib/blockchains/BlocksoftTransferDispatcher.ts delete mode 100644 src/prototypes/buffer.ts create mode 100644 src/prototypes/global.js diff --git a/App.tsx b/App.tsx index 2f1366d63..1b85bde19 100644 --- a/App.tsx +++ b/App.tsx @@ -1,5 +1,5 @@ import './src/prototypes/array'; -import './src/prototypes/buffer'; +import './src/prototypes/global'; import React from 'react'; import Navigation from '@navigation/NavigationContainer'; import { useAppInit } from '@hooks/useAppInit'; diff --git a/crypto/actions/BlocksoftKeys/BlocksoftKeys.js b/crypto/actions/BlocksoftKeys/BlocksoftKeys.js index 251022c05..b50a4898a 100644 --- a/crypto/actions/BlocksoftKeys/BlocksoftKeys.js +++ b/crypto/actions/BlocksoftKeys/BlocksoftKeys.js @@ -2,49 +2,45 @@ * @author Ksu * @version 0.5 */ -import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog' -import BlocksoftDict from '@crypto/common/BlocksoftDict' -import BlocksoftKeysUtils from '@crypto/actions/BlocksoftKeys/BlocksoftKeysUtils' +import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; +import BlocksoftDict from '@crypto/common/BlocksoftDict'; +import BlocksoftKeysUtils from '@crypto/actions/BlocksoftKeys/BlocksoftKeysUtils'; +import * as BlocksoftRandom from 'react-native-blocksoft-random'; +import BlocksoftDispatcher from '../../blockchains/BlocksoftDispatcher'; +import BlocksoftKeysScam from '@crypto/actions/BlocksoftKeys/BlocksoftKeysScam'; +import { strings } from '@app/services/i18n'; -import * as BlocksoftRandom from 'react-native-blocksoft-random' -import BlocksoftDispatcher from '../../blockchains/BlocksoftDispatcher' -import BlocksoftKeysScam from '@crypto/actions/BlocksoftKeys/BlocksoftKeysScam' -import { strings } from '@app/services/i18n' +const bip32 = require('bip32'); +const bip39 = require('bip39'); +const bip44Constants = require('../../common/ext/bip44-constants'); +const networksConstants = require('../../common/ext/networks-constants'); -const bip32 = require('bip32') -const bip39 = require('bip39') -const bip44Constants = require('../../common/ext/bip44-constants') -const networksConstants = require('../../common/ext/networks-constants') - -const bs58check = require('bs58check') - - -const ETH_CACHE = {} -const CACHE = {} -const CACHE_ROOTS = {} +const bs58check = require('bs58check'); +const ETH_CACHE = {}; +const CACHE = {}; +const CACHE_ROOTS = {}; class BlocksoftKeys { - - constructor() { - this._bipHex = {} - let currency - for (currency of bip44Constants) { - this._bipHex[currency[1]] = currency[0] - } - this._getRandomBytesFunction = BlocksoftRandom.getRandomBytes + constructor() { + this._bipHex = {}; + let currency; + for (currency of bip44Constants) { + this._bipHex[currency[1]] = currency[0]; } - - /** - * create new mnemonic object (also gives hash to store in public db) - * @param {int} size - * @return {Promise<{mnemonic: string, hash: string}>} - */ - async newMnemonic(size = 256) { - BlocksoftCryptoLog.log(`BlocksoftKeys newMnemonic called`) - - /* let mnemonic = false + this._getRandomBytesFunction = BlocksoftRandom.getRandomBytes; + } + + /** + * create new mnemonic object (also gives hash to store in public db) + * @param {int} size + * @return {Promise<{mnemonic: string, hash: string}>} + */ + async newMnemonic(size = 256) { + BlocksoftCryptoLog.log(`BlocksoftKeys newMnemonic called`); + + /* let mnemonic = false if (USE_TON) { for (let i = 0; i < 10000; i++) { let random = await this._getRandomBytesFunction(size / 8) @@ -64,356 +60,438 @@ class BlocksoftKeys { random = Buffer.from(random, 'base64') mnemonic = bip39.entropyToMnemonic(random) } */ - let random = await this._getRandomBytesFunction(size / 8) - random = Buffer.from(random, 'base64') - const mnemonic = bip39.entropyToMnemonic(random) - const hash = BlocksoftKeysUtils.hashMnemonic(mnemonic) - BlocksoftCryptoLog.log(`BlocksoftKeys newMnemonic finished`) - return { mnemonic, hash } + let random = await this._getRandomBytesFunction(size / 8); + random = Buffer.from(random, 'base64'); + const mnemonic = bip39.entropyToMnemonic(random); + const hash = BlocksoftKeysUtils.hashMnemonic(mnemonic); + BlocksoftCryptoLog.log(`BlocksoftKeys newMnemonic finished`); + return { mnemonic, hash }; + } + + /** + * @param {string} mnemonic + * @return {Promise} + */ + async validateMnemonic(mnemonic) { + BlocksoftCryptoLog.log(`BlocksoftKeys validateMnemonic called`); + if (await BlocksoftKeysScam.isScamMnemonic(mnemonic)) { + throw new Error(strings('settings.walletList.scamImport')); } - - /** - * @param {string} mnemonic - * @return {Promise} - */ - async validateMnemonic(mnemonic) { - BlocksoftCryptoLog.log(`BlocksoftKeys validateMnemonic called`) - if (await BlocksoftKeysScam.isScamMnemonic(mnemonic)) { - throw new Error(strings('settings.walletList.scamImport')) + const result = await bip39.validateMnemonic(mnemonic); + if (!result) { + throw new Error('invalid mnemonic for bip39'); + } + return result; + } + + /** + * @param {string} data.mnemonic + * @param {string} data.walletHash + * @param {string|string[]} data.currencyCode = all + * @param {int} data.fromIndex = 0 + * @param {int} data.toIndex = 100 + * @param {boolean} data.fullTree = false + * @param {array} data.derivations.BTC + * @param {array} data.derivations.BTC_SEGWIT + * @return {Promise<{currencyCode:[{address, privateKey, path, index, type}]}>} + */ + async discoverAddresses(data, source) { + const logData = { ...data }; + if (typeof logData.mnemonic !== 'undefined') logData.mnemonic = '***'; + + BlocksoftCryptoLog.log( + `BlocksoftKeys discoverAddresses called from ${source}`, + JSON.stringify(logData).substring(0, 200) + ); + + let toDiscover = BlocksoftDict.Codes; + if (data.currencyCode) { + if (typeof data.currencyCode === 'string') { + toDiscover = [data.currencyCode]; + } else { + toDiscover = data.currencyCode; + } + } + const fromIndex = data.fromIndex ? data.fromIndex : 0; + const toIndex = data.toIndex ? data.toIndex : 10; + const fullTree = data.fullTree ? data.fullTree : false; + + const results = {}; + + const mnemonicCache = data.mnemonic.toLowerCase(); + let bitcoinRoot = false; + let currencyCode; + let settings; + const seed = await BlocksoftKeysUtils.bip39MnemonicToSeed( + data.mnemonic.toLowerCase() + ); + for (currencyCode of toDiscover) { + results[currencyCode] = []; + try { + settings = BlocksoftDict.getCurrencyAllSettings( + currencyCode, + 'BlocksoftKeys' + ); + } catch (e) { + // do nothing for now + continue; + } + + let hexes = []; + if (settings.addressCurrencyCode) { + hexes.push(this._bipHex[settings.addressCurrencyCode]); + if (!this._bipHex[settings.addressCurrencyCode]) { + throw new Error( + 'UNKNOWN_CURRENCY_CODE SETTED ' + settings.addressCurrencyCode + ); } - const result = await bip39.validateMnemonic(mnemonic) - if (!result) { - throw new Error('invalid mnemonic for bip39') + } + + if (this._bipHex[currencyCode]) { + hexes.push(this._bipHex[currencyCode]); + } else if (!settings.addressCurrencyCode) { + if ( + settings.extendsProcessor && + this._bipHex[settings.extendsProcessor] + ) { + hexes.push(this._bipHex[settings.extendsProcessor]); + } else { + throw new Error( + 'UNKNOWN_CURRENCY_CODE ' + + currencyCode + + ' in bipHex AND NO SETTED addressCurrencyCode' + ); } - return result - } - - - /** - * @param {string} data.mnemonic - * @param {string} data.walletHash - * @param {string|string[]} data.currencyCode = all - * @param {int} data.fromIndex = 0 - * @param {int} data.toIndex = 100 - * @param {boolean} data.fullTree = false - * @param {array} data.derivations.BTC - * @param {array} data.derivations.BTC_SEGWIT - * @return {Promise<{currencyCode:[{address, privateKey, path, index, type}]}>} - */ - async discoverAddresses(data, source) { - - const logData = { ...data } - if (typeof logData.mnemonic !== 'undefined') logData.mnemonic = '***' - - BlocksoftCryptoLog.log(`BlocksoftKeys discoverAddresses called from ${source}`, JSON.stringify(logData).substring(0, 200)) - - let toDiscover = BlocksoftDict.Codes - if (data.currencyCode) { - if (typeof data.currencyCode === 'string') { - toDiscover = [data.currencyCode] - } else { - toDiscover = data.currencyCode - } + } + + let isAlreadyMain = false; + + if (!data.fullTree) { + hexes = [hexes[0]]; + } + + const hexesCache = + mnemonicCache + + '_' + + settings.addressProcessor + + '_fromINDEX_' + + fromIndex + + '_' + + JSON.stringify(hexes); + let hasDerivations = false; + if ( + typeof data.derivations !== 'undefined' && + typeof data.derivations[currencyCode] !== 'undefined' && + data.derivations[currencyCode] + ) { + hasDerivations = true; + } + if (typeof CACHE[hexesCache] === 'undefined' || hasDerivations) { + BlocksoftCryptoLog.log( + `BlocksoftKeys will discover ${settings.addressProcessor}` + ); + let root = false; + if (typeof networksConstants[currencyCode] !== 'undefined') { + root = await this.getBip32Cached( + data.mnemonic, + networksConstants[currencyCode], + seed + ); + } else { + if (!bitcoinRoot) { + bitcoinRoot = await this.getBip32Cached(data.mnemonic); + } + root = bitcoinRoot; } - const fromIndex = data.fromIndex ? data.fromIndex : 0 - const toIndex = data.toIndex ? data.toIndex : 10 - const fullTree = data.fullTree ? data.fullTree : false - - - const results = {} - - - const mnemonicCache = data.mnemonic.toLowerCase() - let bitcoinRoot = false - let currencyCode - let settings - const seed = await BlocksoftKeysUtils.bip39MnemonicToSeed(data.mnemonic.toLowerCase()) - for (currencyCode of toDiscover) { - results[currencyCode] = [] - try { - settings = BlocksoftDict.getCurrencyAllSettings(currencyCode, 'BlocksoftKeys') - } catch (e) { - // do nothing for now - continue - } - - let hexes = [] - if (settings.addressCurrencyCode) { - hexes.push(this._bipHex[settings.addressCurrencyCode]) - if (!this._bipHex[settings.addressCurrencyCode]) { - throw new Error('UNKNOWN_CURRENCY_CODE SETTED ' + settings.addressCurrencyCode) - } - } - - if (this._bipHex[currencyCode]) { - hexes.push(this._bipHex[currencyCode]) - } else if (!settings.addressCurrencyCode) { - if (settings.extendsProcessor && this._bipHex[settings.extendsProcessor]) { - hexes.push(this._bipHex[settings.extendsProcessor]) - } else { - throw new Error('UNKNOWN_CURRENCY_CODE ' + currencyCode + ' in bipHex AND NO SETTED addressCurrencyCode') - } - } - - let isAlreadyMain = false + // BIP32 Extended Private Key to check - uncomment + // let childFirst = root.derivePath('m/44\'/2\'/0\'/0') + // BlocksoftCryptoLog.log(childFirst.toBase58()) - if (!data.fullTree) { - hexes = [hexes[0]] - } - - const hexesCache = mnemonicCache + '_' + settings.addressProcessor + '_fromINDEX_' + fromIndex + '_' + JSON.stringify(hexes) - let hasDerivations = false - if (typeof data.derivations !== 'undefined' && typeof data.derivations[currencyCode] !== 'undefined' && data.derivations[currencyCode]) { - hasDerivations = true + /** + * @type {EthAddressProcessor|BtcAddressProcessor} + */ + const processor = await BlocksoftDispatcher.innerGetAddressProcessor( + settings + ); + + try { + await processor.setBasicRoot(root); + } catch (e) { + e.message += ' while doing ' + JSON.stringify(settings); + throw e; + } + let currentFromIndex = fromIndex; + let currentToIndex = toIndex; + let currentFullTree = fullTree; + + BlocksoftCryptoLog.log( + `BlocksoftKeys discoverAddresses ${currencyCode} currentFromIndex.1 ${currentFromIndex}` + ); + if (hasDerivations) { + let derivation = { path: '', alreadyShown: 0, walletPubId: 0 }; + let maxIndex = 0; + for (derivation of data.derivations[currencyCode]) { + const child = root.derivePath(derivation.path); + const tmp = derivation.path.split('/'); + const result = await processor.getAddress( + child.privateKey, + { + publicKey: child.publicKey, + walletHash: data.walletHash, + derivationPath: derivation.path + }, + data, + seed, + 'discoverAddresses' + ); + result.basicPrivateKey = child.privateKey.toString('hex'); + result.basicPublicKey = child.publicKey.toString('hex'); + result.path = derivation.path; + result.alreadyShown = derivation.alreadyShown; + result.walletPubId = derivation.walletPubId || 0; + result.index = tmp[5]; + if (maxIndex < result.index) { + maxIndex = result.index * 1; } - if (typeof CACHE[hexesCache] === 'undefined' || hasDerivations) { - BlocksoftCryptoLog.log(`BlocksoftKeys will discover ${settings.addressProcessor}`) - let root = false - if (typeof networksConstants[currencyCode] !== 'undefined') { - root = await this.getBip32Cached(data.mnemonic, networksConstants[currencyCode], seed) - } else { - if (!bitcoinRoot) { - bitcoinRoot = await this.getBip32Cached(data.mnemonic) - } - root = bitcoinRoot - } - // BIP32 Extended Private Key to check - uncomment - // let childFirst = root.derivePath('m/44\'/2\'/0\'/0') - // BlocksoftCryptoLog.log(childFirst.toBase58()) - - /** - * @type {EthAddressProcessor|BtcAddressProcessor} - */ - const processor = await BlocksoftDispatcher.innerGetAddressProcessor(settings) - - try { - await processor.setBasicRoot(root) - } catch (e) { - e.message += ' while doing ' + JSON.stringify(settings) - throw e - } - let currentFromIndex = fromIndex - let currentToIndex = toIndex - let currentFullTree = fullTree - - BlocksoftCryptoLog.log(`BlocksoftKeys discoverAddresses ${currencyCode} currentFromIndex.1 ${currentFromIndex}`) - if (hasDerivations) { - let derivation = {path : '', alreadyShown : 0, walletPubId : 0} - let maxIndex = 0 - for (derivation of data.derivations[currencyCode]) { - const child = root.derivePath(derivation.path) - const tmp = derivation.path.split('/') - const result = await processor.getAddress(child.privateKey, { - publicKey: child.publicKey, - walletHash: data.walletHash, - derivationPath : derivation.path, - }, data, seed, 'discoverAddresses') - result.basicPrivateKey = child.privateKey.toString('hex') - result.basicPublicKey = child.publicKey.toString('hex') - result.path = derivation.path - result.alreadyShown = derivation.alreadyShown - result.walletPubId = derivation.walletPubId || 0 - result.index = tmp[5] - if (maxIndex < result.index) { - maxIndex = result.index*1 - } - result.type = 'main' - results[currencyCode].push(result) - } - if (maxIndex > 0) { - // noinspection PointlessArithmeticExpressionJS - currentFromIndex = maxIndex*1 + 1 - currentToIndex = currentFromIndex*1+ 10 - currentFullTree = true - BlocksoftCryptoLog.log(`BlocksoftKeys ${currencyCode} discoverAddresses currentFromIndex.2 ${currentFromIndex}`) - } - } + result.type = 'main'; + results[currencyCode].push(result); + } + if (maxIndex > 0) { + // noinspection PointlessArithmeticExpressionJS + currentFromIndex = maxIndex * 1 + 1; + currentToIndex = currentFromIndex * 1 + 10; + currentFullTree = true; + BlocksoftCryptoLog.log( + `BlocksoftKeys ${currencyCode} discoverAddresses currentFromIndex.2 ${currentFromIndex}` + ); + } + } + let suffixes; + if (currencyCode === 'SOL') { + suffixes = [ + { type: 'main', suffix: false, after: `'/0'` }, + { type: 'no_scan', suffix: false, after: `'` } + ]; + } else if (currentFullTree) { + suffixes = [ + { type: 'main', suffix: `0'/0` }, + { type: 'change', suffix: `0'/1` } + // { 'type': 'second', 'suffix': `1'/0` }, + // { 'type': 'secondchange', 'suffix': `1'/1` } + ]; + } else { + suffixes = [{ type: 'main', suffix: `0'/0` }]; + if (currencyCode === 'BTC_SEGWIT_COMPATIBLE') { + suffixes = [ + { type: 'main', suffix: '0/1' } // heh + ]; + } + hexes = [hexes[0]]; + } - let suffixes - if (currencyCode === 'SOL') { - suffixes = [ - { 'type': 'main', 'suffix': false, after : `'/0'` }, - { 'type': 'no_scan', 'suffix': false, after: `'` } - ] - } else if (currentFullTree) { - suffixes = [ - { 'type': 'main', 'suffix': `0'/0` }, - { 'type': 'change', 'suffix': `0'/1` } - // { 'type': 'second', 'suffix': `1'/0` }, - // { 'type': 'secondchange', 'suffix': `1'/1` } - ] + let hex; + for (hex of hexes) { + if (isAlreadyMain) { + suffixes[0].type = 'second'; + } + isAlreadyMain = true; + + if (currentFromIndex >= 0 && currentToIndex >= 0) { + for ( + let index = currentFromIndex; + index < currentToIndex; + index++ + ) { + let suffix; + for (suffix of suffixes) { + const path = suffix.suffix + ? `m/${hex}'/${suffix.suffix}/${index}` + : `m/${hex}'/${index}${suffix.after}`; + let privateKey = false; + let publicKey = false; + if ( + currencyCode === 'SOL' || + currencyCode === 'XLM' || + currencyCode === 'WAVES' || + currencyCode === 'ASH' + ) { + // @todo move to coin address processor } else { - suffixes = [ - { 'type': 'main', 'suffix': `0'/0` } - ] - if (currencyCode === 'BTC_SEGWIT_COMPATIBLE') { - suffixes = [ - { 'type': 'main', 'suffix': '0/1' } // heh - ] - } - hexes = [hexes[0]] - } - - - let hex - for (hex of hexes) { - if (isAlreadyMain) { - suffixes[0].type = 'second' - } - isAlreadyMain = true - - if (currentFromIndex >= 0 && currentToIndex >= 0) { - for (let index = currentFromIndex; index < currentToIndex; index++) { - let suffix - for (suffix of suffixes) { - const path = suffix.suffix ? `m/${hex}'/${suffix.suffix}/${index}` : `m/${hex}'/${index}${suffix.after}` - let privateKey = false - let publicKey = false - if (currencyCode === 'SOL' || currencyCode === 'XLM' || currencyCode === 'WAVES' || currencyCode === 'ASH') { - // @todo move to coin address processor - } else { - const child = root.derivePath(path) - privateKey = child.privateKey - publicKey = child.publicKey - } - const result = await processor.getAddress(privateKey, { - publicKey, - walletHash: data.walletHash, - derivationPath : path, - derivationIndex : index, - derivationType : suffix.type - }, data, seed, 'discoverAddresses2') - if (result) { - if (privateKey) { - result.basicPrivateKey = privateKey.toString('hex') - result.basicPublicKey = publicKey.toString('hex') - } - result.path = path - result.index = index - result.alreadyShown = 0 - result.type = suffix.type - results[currencyCode].push(result) - } - } - } - } - } - CACHE[hexesCache] = results[currencyCode] - if (currencyCode === 'ETH') { - ETH_CACHE[mnemonicCache] = results[currencyCode][0] + const child = root.derivePath(path); + privateKey = child.privateKey; + publicKey = child.publicKey; } - } else { - BlocksoftCryptoLog.log(`BlocksoftKeys will be from cache ${settings.addressProcessor}`) - results[currencyCode] = CACHE[hexesCache] - if (currencyCode === 'USDT') { - results[currencyCode] = [results[currencyCode][0]] + const result = await processor.getAddress( + privateKey, + { + publicKey, + walletHash: data.walletHash, + derivationPath: path, + derivationIndex: index, + derivationType: suffix.type + }, + data, + seed, + 'discoverAddresses2' + ); + if (result) { + if (privateKey) { + result.basicPrivateKey = privateKey.toString('hex'); + result.basicPublicKey = publicKey.toString('hex'); + } + result.path = path; + result.index = index; + result.alreadyShown = 0; + result.type = suffix.type; + results[currencyCode].push(result); } + } } + } } - BlocksoftCryptoLog.log(`BlocksoftKeys discoverAddresses finished`) - return results - } - - async getSeedCached(mnemonic) { - BlocksoftCryptoLog.log(`BlocksoftKeys bip39MnemonicToSeed started`) - const mnemonicCache = mnemonic.toLowerCase() - if (typeof CACHE[mnemonicCache] === 'undefined') { - CACHE[mnemonicCache] = await BlocksoftKeysUtils.bip39MnemonicToSeed(mnemonic.toLowerCase()) + CACHE[hexesCache] = results[currencyCode]; + if (currencyCode === 'ETH') { + ETH_CACHE[mnemonicCache] = results[currencyCode][0]; } - const seed = CACHE[mnemonicCache] // will be rechecked on saving - BlocksoftCryptoLog.log(`BlocksoftKeys bip39MnemonicToSeed ended`) - return seed - } - - async getBip32Cached(mnemonic, network = false, seed = false) { - const mnemonicCache = mnemonic.toLowerCase() + '_' + (network || 'btc') - if (typeof CACHE_ROOTS[mnemonicCache] === 'undefined') { - if (!seed) { - seed = await this.getSeedCached(mnemonic) - } - CACHE_ROOTS[mnemonicCache] = network ? bip32.fromSeed(seed, network) : bip32.fromSeed(seed) + } else { + BlocksoftCryptoLog.log( + `BlocksoftKeys will be from cache ${settings.addressProcessor}` + ); + results[currencyCode] = CACHE[hexesCache]; + if (currencyCode === 'USDT') { + results[currencyCode] = [results[currencyCode][0]]; } - return CACHE_ROOTS[mnemonicCache] - } - - getEthCached(mnemonicCache) { - if (typeof ETH_CACHE[mnemonicCache] === 'undefined') return false - return ETH_CACHE[mnemonicCache] + } } - - setEthCached(mnemonic, result) { - const mnemonicCache = mnemonic.toLowerCase() - ETH_CACHE[mnemonicCache] = result + BlocksoftCryptoLog.log(`BlocksoftKeys discoverAddresses finished`); + return results; + } + + async getSeedCached(mnemonic) { + BlocksoftCryptoLog.log(`BlocksoftKeys bip39MnemonicToSeed started`); + const mnemonicCache = mnemonic.toLowerCase(); + if (typeof CACHE[mnemonicCache] === 'undefined') { + CACHE[mnemonicCache] = await BlocksoftKeysUtils.bip39MnemonicToSeed( + mnemonic.toLowerCase() + ); } - - /** - * @param {string} data.mnemonic - * @param {string} data.currencyCode - * @param {string} data.derivationPath - * @param {string} data.derivationIndex - * @param {string} data.derivationType - * @return {Promise<{address, privateKey}>} - */ - async discoverOne(data) { - const seed = await BlocksoftKeysUtils.bip39MnemonicToSeed(data.mnemonic.toLowerCase()) - const root = bip32.fromSeed(seed) - const child = root.derivePath(data.derivationPath.replace(/quote/g, '\'')) - /** - * @type {EthAddressProcessor|BtcAddressProcessor} - */ - const processor = await BlocksoftDispatcher.getAddressProcessor(data.currencyCode) - processor.setBasicRoot(root) - return processor.getAddress(child.privateKey, { - derivationPath : data.derivationPath, - derivationIndex: data.derivationIndex, - derivationType: data.derivationType, - publicKey : child.publicKey - }, data, seed, 'discoverOne') + const seed = CACHE[mnemonicCache]; // will be rechecked on saving + BlocksoftCryptoLog.log(`BlocksoftKeys bip39MnemonicToSeed ended`); + return seed; + } + + async getBip32Cached(mnemonic, network = false, seed = false) { + const mnemonicCache = mnemonic.toLowerCase() + '_' + (network || 'btc'); + if (typeof CACHE_ROOTS[mnemonicCache] === 'undefined') { + if (!seed) { + seed = await this.getSeedCached(mnemonic); + } + CACHE_ROOTS[mnemonicCache] = network + ? bip32.fromSeed(seed, network) + : bip32.fromSeed(seed); } - + return CACHE_ROOTS[mnemonicCache]; + } + + getEthCached(mnemonicCache) { + if (typeof ETH_CACHE[mnemonicCache] === 'undefined') return false; + return ETH_CACHE[mnemonicCache]; + } + + setEthCached(mnemonic, result) { + const mnemonicCache = mnemonic.toLowerCase(); + ETH_CACHE[mnemonicCache] = result; + } + + /** + * @param {string} data.mnemonic + * @param {string} data.currencyCode + * @param {string} data.derivationPath + * @param {string} data.derivationIndex + * @param {string} data.derivationType + * @return {Promise<{address, privateKey}>} + */ + async discoverOne(data) { + const seed = await BlocksoftKeysUtils.bip39MnemonicToSeed( + data.mnemonic.toLowerCase() + ); + const root = bip32.fromSeed(seed); + const child = root.derivePath(data.derivationPath.replace(/quote/g, "'")); /** - * @param {string} data.mnemonic - * @param {string} data.currencyCode - * @return {Promise<{address, privateKey}>} + * @type {EthAddressProcessor|BtcAddressProcessor} */ - async discoverXpub(data) { - const seed = await BlocksoftKeysUtils.bip39MnemonicToSeed(data.mnemonic.toLowerCase()) - const root = bip32.fromSeed(seed) - let path = `m/44'/0'/0'` - let version = 0x0488B21E // xpub - if (data.currencyCode === 'BTC_SEGWIT') { - path = `m/84'/0'/0'` - version = 0x04b24746 // https://github.com/satoshilabs/slips/blob/master/slip-0132.md - } else if (data.currencyCode === 'BTC_SEGWIT_COMPATIBLE') { - path = `m/49'/0'/0'` - version = 0x049d7cb2 - } - BlocksoftCryptoLog.log('BlocksoftKeys.discoverXpub derive started ' + JSON.stringify(path)) - const rootWallet = root.derivePath(path) - BlocksoftCryptoLog.log('BlocksoftKeys.discoverXpub derived, bs58 started ' + JSON.stringify(path)) - const res = bs58check.encode(serialize(rootWallet, version, rootWallet.publicKey)) - BlocksoftCryptoLog.log('BlocksoftKeys.discoverXpub res ' + JSON.stringify(path), res) - return res + const processor = await BlocksoftDispatcher.getAddressProcessor( + data.currencyCode + ); + processor.setBasicRoot(root); + return processor.getAddress( + child.privateKey, + { + derivationPath: data.derivationPath, + derivationIndex: data.derivationIndex, + derivationType: data.derivationType, + publicKey: child.publicKey + }, + data, + seed, + 'discoverOne' + ); + } + + /** + * @param {string} data.mnemonic + * @param {string} data.currencyCode + * @return {Promise<{address, privateKey}>} + */ + async discoverXpub(data) { + const seed = await BlocksoftKeysUtils.bip39MnemonicToSeed( + data.mnemonic.toLowerCase() + ); + const root = bip32.fromSeed(seed); + let path = `m/44'/0'/0'`; + let version = 0x0488b21e; // xpub + if (data.currencyCode === 'BTC_SEGWIT') { + path = `m/84'/0'/0'`; + version = 0x04b24746; // https://github.com/satoshilabs/slips/blob/master/slip-0132.md + } else if (data.currencyCode === 'BTC_SEGWIT_COMPATIBLE') { + path = `m/49'/0'/0'`; + version = 0x049d7cb2; } + BlocksoftCryptoLog.log( + 'BlocksoftKeys.discoverXpub derive started ' + JSON.stringify(path) + ); + const rootWallet = root.derivePath(path); + BlocksoftCryptoLog.log( + 'BlocksoftKeys.discoverXpub derived, bs58 started ' + JSON.stringify(path) + ); + const res = bs58check.encode( + serialize(rootWallet, version, rootWallet.publicKey) + ); + BlocksoftCryptoLog.log( + 'BlocksoftKeys.discoverXpub res ' + JSON.stringify(path), + res + ); + return res; + } } function serialize(hdkey, version, key, LEN = 78) { - // => version(4) || depth(1) || fingerprint(4) || index(4) || chain(32) || key(33) - const buffer = Buffer.allocUnsafe(LEN) + // => version(4) || depth(1) || fingerprint(4) || index(4) || chain(32) || key(33) + const buffer = Buffer.allocUnsafe(LEN); - buffer.writeUInt32BE(version, 0) - buffer.writeUInt8(hdkey.depth, 4) + buffer.writeUInt32BE(version, 0); + buffer.writeUInt8(hdkey.depth, 4); - const fingerprint = hdkey.depth ? hdkey.parentFingerprint : 0x00000000 - buffer.writeUInt32BE(fingerprint, 5) - buffer.writeUInt32BE(hdkey.index, 9) + const fingerprint = hdkey.depth ? hdkey.parentFingerprint : 0x00000000; + buffer.writeUInt32BE(fingerprint, 5); + buffer.writeUInt32BE(hdkey.index, 9); - hdkey.chainCode.copy(buffer, 13) - key.copy(buffer, 45) + hdkey.chainCode.copy(buffer, 13); + key.copy(buffer, 45); - return buffer + return buffer; } -const singleBlocksoftKeys = new BlocksoftKeys() -export default singleBlocksoftKeys +const singleBlocksoftKeys = new BlocksoftKeys(); +export default singleBlocksoftKeys; diff --git a/crypto/actions/BlocksoftKeysStorage/BlocksoftKeysStorage.js b/crypto/actions/BlocksoftKeysStorage/BlocksoftKeysStorage.js index 60787f675..3ab4e828f 100644 --- a/crypto/actions/BlocksoftKeysStorage/BlocksoftKeysStorage.js +++ b/crypto/actions/BlocksoftKeysStorage/BlocksoftKeysStorage.js @@ -3,255 +3,282 @@ * @version 0.5 * @docs https://www.npmjs.com/package/react-native-keychain */ -import 'react-native' -import * as Keychain from 'react-native-keychain' +import 'react-native'; +import * as Keychain from 'react-native-keychain'; -import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog' -import config from '@app/config/config' -import BlocksoftDict from '@crypto/common/BlocksoftDict' +import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; +import config from '@app/config/config'; +import BlocksoftDict from '@crypto/common/BlocksoftDict'; export class BlocksoftKeysStorage { - - /** - * @type {string} - * @private - */ - _serviceName = '' - - /** - * @type {number} - * @private - */ - _serviceWalletsCounter = 0 - - /** - * @type {{}|array} - * @private - */ - _serviceWallets = {} - - /** - * @type {boolean} - * @private - */ - _serviceWasInited = false - - /** - * @type {array} - */ - publicWallets = [] - - constructor(_serviceName = 'BlocksoftKeysStorage3') { - this._serviceName = _serviceName - } - - /** - * @private - */ - async _getServiceName(name) { - this._serviceName = name - this._serviceWasInited = false - await this._init() + /** + * @type {string} + * @private + */ + _serviceName = ''; + + /** + * @type {number} + * @private + */ + _serviceWalletsCounter = 0; + + /** + * @type {{}|array} + * @private + */ + _serviceWallets = {}; + + /** + * @type {boolean} + * @private + */ + _serviceWasInited = false; + + /** + * @type {array} + */ + publicWallets = []; + + constructor(_serviceName = 'BlocksoftKeysStorage3') { + this._serviceName = _serviceName; + } + + /** + * @private + */ + async _getServiceName(name) { + this._serviceName = name; + this._serviceWasInited = false; + await this._init(); + } + + /** + * @param {string} key + * @return {Promise} + * @private + */ + async _getKeyValue(key) { + const res = await Keychain.getInternetCredentials( + this._serviceName + '_' + key, + { + authenticationPrompt: { + title: 'Fingerprint title', + cancel: 'Cancel' + } + } + ); + if (!res) return false; + return { pub: res.username, priv: res.password }; + } + + /** + * @param {string} key + * @param {string|int} pub + * @param {string|boolean} priv + * @return {Promise<*>} + * @private + */ + async _setKeyValue(key, pub, priv = false) { + pub = pub + ''; + if (!priv) priv = pub; + let res = false; + try { + res = await Keychain.setInternetCredentials( + this._serviceName + '_' + key, + pub, + priv, + { + authenticationPrompt: { + title: 'Fingerprint title', + cancel: 'Cancel' + } + // if will be breaking again try accessControl : 'BiometryAnyOrDevicePasscode' + } + ); + } catch (e) { + if (config.debug.cryptoErrors) { + console.log('BlocksoftKeysStorage _setKeyValue error ', e); + } + if (key.indexOf('wallet') !== -1) { + throw e; + } } - - /** - * @param {string} key - * @return {Promise} - * @private - */ - async _getKeyValue(key) { - const res = await Keychain.getInternetCredentials(this._serviceName + '_' + key, { - authenticationPrompt: { - title: 'Fingerprint title', - cancel: 'Cancel' - } - }) - if (!res) return false - return { 'pub': res.username, 'priv': res.password } + return res; + } + + /** + * @private + */ + async _init() { + if (this._serviceWasInited) { + return true; } - /** - * @param {string} key - * @param {string|int} pub - * @param {string|boolean} priv - * @return {Promise<*>} - * @private - */ - async _setKeyValue(key, pub, priv = false) { - pub = pub + '' - if (!priv) priv = pub - let res = false - try { - res = await Keychain.setInternetCredentials(this._serviceName + '_' + key, pub, priv, { - authenticationPrompt: { - title: 'Fingerprint title', - cancel: 'Cancel' - }, - // if will be breaking again try accessControl : 'BiometryAnyOrDevicePasscode' - }) - } catch (e) { - if (config.debug.cryptoErrors) { - console.log('BlocksoftKeysStorage _setKeyValue error ', e) - } - if (key.indexOf('wallet') !== -1) { - throw e - } + const count = await this._getKeyValue('wallets_counter'); + + this._serviceWalletsCounter = count && count.priv ? count.priv * 1 : 0; + this._serviceWallets = {}; + this.publicWallets = []; + this.publicSelectedWallet = false; + if (this._serviceWalletsCounter > 0) { + for (let i = 1; i <= this._serviceWalletsCounter; i++) { + const wallet = await this._getKeyValue('wallet_' + i); + if ( + !wallet.priv || + wallet.priv === '' || + wallet.priv === + 'new wallet is not generated - please reinstall and restart' || + wallet.priv === wallet.pub + ) { + continue; } - return res + this._serviceWallets[wallet.pub] = wallet.priv; + this._serviceWallets[i - 1] = wallet.priv; + this.publicWallets.push(wallet.pub); + } } - - /** - * @private - */ - async _init() { - if (this._serviceWasInited) { - return true - } - - const count = await this._getKeyValue('wallets_counter') - - this._serviceWalletsCounter = count && count.priv ? count.priv * 1 : 0 - this._serviceWallets = {} - this.publicWallets = [] - this.publicSelectedWallet = false - if (this._serviceWalletsCounter > 0) { - for (let i = 1; i <= this._serviceWalletsCounter; i++) { - const wallet = await this._getKeyValue('wallet_' + i) - if (!wallet.priv || wallet.priv === '' || wallet.priv === 'new wallet is not generated - please reinstall and restart' || wallet.priv === wallet.pub) { - continue - } - this._serviceWallets[wallet.pub] = wallet.priv - this._serviceWallets[i - 1] = wallet.priv - this.publicWallets.push(wallet.pub) - } - } - this._serviceWasInited = true + this._serviceWasInited = true; + } + + async getOldSelectedWallet() { + this._init(); + const tmp = await this._getKeyValue('selected_hash'); + let publicSelectedWallet = false; + if (tmp && tmp.pub) { + publicSelectedWallet = tmp.pub; } - - async getOldSelectedWallet() { - this._init() - const tmp = await this._getKeyValue('selected_hash') - let publicSelectedWallet = false - if (tmp && tmp.pub) { - publicSelectedWallet = tmp.pub - } - if (!this.publicSelectedWallet || !this._serviceWallets[this.publicSelectedWallet]) { - publicSelectedWallet = false - } - return publicSelectedWallet + if ( + !this.publicSelectedWallet || + !this._serviceWallets[this.publicSelectedWallet] + ) { + publicSelectedWallet = false; } - - async reInit() { - this._serviceWasInited = false - return this._init() + return publicSelectedWallet; + } + + async reInit() { + this._serviceWasInited = false; + return this._init(); + } + + async getOneWalletText(walletHash, discoverPath, currencyCode) { + try { + if (typeof discoverPath === 'undefined' || discoverPath === false) { + const res = await this._getKeyValue(walletHash); + if (!res) return false; + return res.priv; + } else { + const key = this.getAddressCacheKey( + walletHash, + discoverPath, + currencyCode + ); + return this.getAddressCache(key); + } + } catch (e) { + // do nothing } - - async getOneWalletText(walletHash, discoverPath, currencyCode) { - try { - if (typeof discoverPath === 'undefined' || discoverPath === false) { - const res = await this._getKeyValue(walletHash) - if (!res) return false - return res.priv - } else { - const key = this.getAddressCacheKey(walletHash, discoverPath, currencyCode) - return this.getAddressCache(key) - } - } catch (e) { - // do nothing + return false; + } + + async getAllWalletsText() { + let res = ''; + for (let i = 1; i <= 3; i++) { + try { + const wallet = await this._getKeyValue('wallet_' + i); + if (typeof wallet.priv !== 'undefined' && wallet.priv !== 'undefined') { + res += ' WALLET#' + i + ' ' + wallet.priv; } - return false + } catch (e) { + // do nothing + } } - - async getAllWalletsText() { - let res = '' - for (let i = 1; i <= 3; i++) { - try { - const wallet = await this._getKeyValue('wallet_' + i) - if (typeof wallet.priv !== 'undefined' && wallet.priv !== 'undefined') { - res += ' WALLET#' + i + ' ' + wallet.priv - } - } catch (e) { - // do nothing - } - } - if (res === '') { - return 'Nothing found by general search' - } - return res + if (res === '') { + return 'Nothing found by general search'; } - - /** - * @return {Promise} - */ - async countMnemonics() { - await this._init() - return this._serviceWalletsCounter + return res; + } + + /** + * @return {Promise} + */ + async countMnemonics() { + await this._init(); + return this._serviceWalletsCounter; + } + + /** + * public list of wallets hashes + * @return {Array} + */ + async getWallets() { + await this._init(); + return this.publicWallets; + } + + /** + * public wallet mnemonic by hash + * @return {string} + */ + async getWalletMnemonic(hashOrId, source = 'default') { + await this._init(); + if (!this._serviceWallets[hashOrId]) { + throw new Error( + 'undefined wallet with hash ' + hashOrId + ' source ' + source + ); } - - - /** - * public list of wallets hashes - * @return {Array} - */ - async getWallets() { - await this._init() - return this.publicWallets + return this._serviceWallets[hashOrId]; + } + + /** + * @param {string} newMnemonic.mnemonic + * @param {string} newMnemonic.hash + * @return {Promise} + */ + async isMnemonicAlreadySaved(newMnemonic) { + await this._init(); + if ( + typeof this._serviceWallets[newMnemonic.hash] === 'undefined' || + !this._serviceWallets[newMnemonic.hash] + ) { + return false; } - - /** - * public wallet mnemonic by hash - * @return {string} - */ - async getWalletMnemonic(hashOrId, source = 'default') { - await this._init() - if (!this._serviceWallets[hashOrId]) { - throw new Error('undefined wallet with hash ' + hashOrId + ' source ' + source) - } - return this._serviceWallets[hashOrId] + if (this._serviceWallets[newMnemonic.hash] !== newMnemonic.mnemonic) { + throw new Error('something wrong with hash algorithm'); } - - /** - * @param {string} newMnemonic.mnemonic - * @param {string} newMnemonic.hash - * @return {Promise} - */ - async isMnemonicAlreadySaved(newMnemonic) { - await this._init() - if (typeof this._serviceWallets[newMnemonic.hash] === 'undefined' || !this._serviceWallets[newMnemonic.hash]) { - return false - } - if (this._serviceWallets[newMnemonic.hash] !== newMnemonic.mnemonic) { - throw new Error('something wrong with hash algorithm') - } - return true + return true; + } + + /** + * @param {string} newMnemonic.mnemonic + * @param {string} newMnemonic.hash + * @return {Promise} + */ + async saveMnemonic(newMnemonic) { + await this._init(); + + const logData = { ...newMnemonic }; + if (typeof logData.mnemonic !== 'undefined') logData.mnemonic = '***'; + BlocksoftCryptoLog.log('BlocksoftKeysStorage saveMnemonic', logData); + + if (!newMnemonic.hash) { + throw new Error('unique hash required ' + JSON.stringify(newMnemonic)); } + if ( + typeof this._serviceWallets[newMnemonic.hash] !== 'undefined' && + this._serviceWallets[newMnemonic.hash] + ) { + if (this._serviceWallets[newMnemonic.hash] !== newMnemonic.mnemonic) { + throw new Error('something wrong with hash algorithm'); + } + return newMnemonic.hash; // its ok + } + this._serviceWalletsCounter++; - /** - * @param {string} newMnemonic.mnemonic - * @param {string} newMnemonic.hash - * @return {Promise} - */ - async saveMnemonic(newMnemonic) { - await this._init() - - const logData = { ...newMnemonic } - if (typeof logData.mnemonic !== 'undefined') logData.mnemonic = '***' - BlocksoftCryptoLog.log('BlocksoftKeysStorage saveMnemonic', logData) - - if (!newMnemonic.hash) { - throw new Error('unique hash required ' + JSON.stringify(newMnemonic)) - } - if (typeof this._serviceWallets[newMnemonic.hash] !== 'undefined' && this._serviceWallets[newMnemonic.hash]) { - if (this._serviceWallets[newMnemonic.hash] !== newMnemonic.mnemonic) { - throw new Error('something wrong with hash algorithm') - } - return newMnemonic.hash // its ok - } - this._serviceWalletsCounter++ - - const unique = newMnemonic.hash + const unique = newMnemonic.hash; - /* + /* hash should give unique values for different mnemonics let i = 0 while (this._serviceWallets[unique]) { @@ -263,63 +290,74 @@ export class BlocksoftKeysStorage { } */ - await this._setKeyValue(unique, unique, newMnemonic.mnemonic) - await this._setKeyValue('wallet_' + this._serviceWalletsCounter, unique, newMnemonic.mnemonic) - await this._setKeyValue('wallets_counter', this._serviceWalletsCounter) - this._serviceWallets[unique] = newMnemonic.mnemonic - this._serviceWallets[this._serviceWalletsCounter - 1] = newMnemonic.mnemonic - - this.publicWallets.push(unique) - this.publicSelectedWallet = unique - - return newMnemonic.hash + await this._setKeyValue(unique, unique, newMnemonic.mnemonic); + await this._setKeyValue( + 'wallet_' + this._serviceWalletsCounter, + unique, + newMnemonic.mnemonic + ); + await this._setKeyValue('wallets_counter', this._serviceWalletsCounter); + this._serviceWallets[unique] = newMnemonic.mnemonic; + this._serviceWallets[this._serviceWalletsCounter - 1] = + newMnemonic.mnemonic; + + this.publicWallets.push(unique); + this.publicSelectedWallet = unique; + + return newMnemonic.hash; + } + + getAddressCacheKey(walletHash, discoverPath, currencyCode) { + const settings = BlocksoftDict.getCurrencyAllSettings(currencyCode); + if ( + typeof settings.addressCurrencyCode !== 'undefined' && + typeof settings.tokenBlockchain !== 'undefined' && + settings.tokenBlockchain !== 'BITCOIN' + ) { + return ( + walletHash + '_' + discoverPath + '_' + settings.addressCurrencyCode + ); } - - getAddressCacheKey(walletHash, discoverPath, currencyCode) { - const settings = BlocksoftDict.getCurrencyAllSettings(currencyCode) - if (typeof settings.addressCurrencyCode !== 'undefined' && typeof settings.tokenBlockchain !== 'undefined' && settings.tokenBlockchain !== 'BITCOIN' ) { - return walletHash + '_' + discoverPath + '_' + settings.addressCurrencyCode - } - return walletHash + '_' + discoverPath + '_' + currencyCode - } - - async getAddressCache(hashOrId) { - try { - const res = await this._getKeyValue('ar4_' + hashOrId) - if (!res || !res.priv || res.pub === res.priv) return false - return { address: res.pub, privateKey: res.priv } - } catch (e) { - return false - } - } - - async setAddressCache(hashOrId, res) { - if (typeof res.privateKey === 'undefined' || !res.privateKey) { - return false - } - return this._setKeyValue('ar4_' + hashOrId, res.address, res.privateKey) - } - - async getLoginCache(hashOrId) { - const res = await this._getKeyValue('login_' + hashOrId) - if (!res) return false - return { login: res.pub, pass: res.priv } - } - - async setLoginCache(hashOrId, res) { - return this._setKeyValue('login_' + hashOrId, res.login, res.pass) - } - - async setSettingValue(hashOrId, value) { - return this._setKeyValue('setting_' + hashOrId, hashOrId, value) + return walletHash + '_' + discoverPath + '_' + currencyCode; + } + + async getAddressCache(hashOrId) { + try { + const res = await this._getKeyValue('ar4_' + hashOrId); + if (!res || !res.priv || res.pub === res.priv) return false; + return { address: res.pub, privateKey: res.priv }; + } catch (e) { + return false; } + } - async getSettingValue(hashOrId) { - const res = await this._getKeyValue('setting_' + hashOrId) - if (!res) return '0' - return res.priv.toString() + async setAddressCache(hashOrId, res) { + if (typeof res.privateKey === 'undefined' || !res.privateKey) { + return false; } + return this._setKeyValue('ar4_' + hashOrId, res.address, res.privateKey); + } + + async getLoginCache(hashOrId) { + const res = await this._getKeyValue('login_' + hashOrId); + if (!res) return false; + return { login: res.pub, pass: res.priv }; + } + + async setLoginCache(hashOrId, res) { + return this._setKeyValue('login_' + hashOrId, res.login, res.pass); + } + + async setSettingValue(hashOrId, value) { + return this._setKeyValue('setting_' + hashOrId, hashOrId, value); + } + + async getSettingValue(hashOrId) { + const res = await this._getKeyValue('setting_' + hashOrId); + if (!res) return '0'; + return res.priv.toString(); + } } -const singleBlocksoftKeysStorage = new BlocksoftKeysStorage() -export default singleBlocksoftKeysStorage +const singleBlocksoftKeysStorage = new BlocksoftKeysStorage(); +export default singleBlocksoftKeysStorage; diff --git a/crypto/actions/BlocksoftTransfer/BlocksoftTransfer.ts b/crypto/actions/BlocksoftTransfer/BlocksoftTransfer.ts index e2c165db8..f9d8b752b 100644 --- a/crypto/actions/BlocksoftTransfer/BlocksoftTransfer.ts +++ b/crypto/actions/BlocksoftTransfer/BlocksoftTransfer.ts @@ -2,296 +2,480 @@ * @author Ksu * @version 0.20 */ -import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog' -import { BlocksoftBlockchainTypes } from '../../blockchains/BlocksoftBlockchainTypes' -import { BlocksoftTransferDispatcher } from '../../blockchains/BlocksoftTransferDispatcher' -import { BlocksoftTransferPrivate } from './BlocksoftTransferPrivate' -import { BlocksoftDictTypes } from '../../common/BlocksoftDictTypes' +import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog'; +import { BlocksoftBlockchainTypes } from '../../blockchains/BlocksoftBlockchainTypes'; +import { BlocksoftTransferDispatcher } from '../../blockchains/BlocksoftTransferDispatcher'; +import { BlocksoftTransferPrivate } from './BlocksoftTransferPrivate'; +import { BlocksoftDictTypes } from '../../common/AirDAODictTypes'; -import CoinBlocksoftDict from '@crypto/assets/coinBlocksoftDict.json' - -import config from '../../../app/config/config' +import CoinBlocksoftDict from '@crypto/assets/coinBlocksoftDict.json'; +import config from '../../../app/config/config'; type DataCache = { - [key in BlocksoftDictTypes.Code]: { - key: string, - memo : string | boolean, - time: number - } -} + [key in BlocksoftDictTypes.Code]: { + key: string; + memo: string | boolean; + time: number; + }; +}; -const CACHE_DOUBLE_TO: DataCache = {} as DataCache -const CACHE_VALID_TIME = 120000 // 2 minute -const CACHE_DOUBLE_BSE = {} +const CACHE_DOUBLE_TO: DataCache = {} as DataCache; +const CACHE_VALID_TIME = 120000; // 2 minute +const CACHE_DOUBLE_BSE = {}; export namespace BlocksoftTransfer { - - export const getTransferAllBalance = async function(data: BlocksoftBlockchainTypes.TransferData, additionalData: BlocksoftBlockchainTypes.TransferAdditionalData = {}): Promise { - if (config.debug.sendLogs) { - console.log(`${data.currencyCode} BlocksoftTransfer.getTransferAllBalance`, JSON.parse(JSON.stringify(data)), JSON.parse(JSON.stringify(additionalData))) - } - if (typeof data.derivationPath !== 'undefined' && data.derivationPath) { - data.derivationPath = data.derivationPath.replace(/quote/g, '\'') - } - data.isTransferAll = true - let transferAllCount - try { - BlocksoftCryptoLog.log(`${data.currencyCode} BlocksoftTransfer.getTransferAllBalance started ${data.addressFrom} `) - const processor = BlocksoftTransferDispatcher.getTransferProcessor(data.currencyCode) - const additionalDataTmp = { ...additionalData } - let privateData = {} as BlocksoftBlockchainTypes.TransferPrivateData - if (processor.needPrivateForFee()) { - privateData = await BlocksoftTransferPrivate.initTransferPrivate(data, additionalData) - } - additionalDataTmp.mnemonic = '***' - transferAllCount = await (BlocksoftTransferDispatcher.getTransferProcessor(data.currencyCode)).getTransferAllBalance(data, privateData, additionalDataTmp) - - BlocksoftCryptoLog.log(`${data.currencyCode} BlocksoftTransfer.getTransferAllBalance got ${data.addressFrom} result is ok`) - if (config.debug.sendLogs) { - console.log(`${data.currencyCode} BlocksoftTransfer.getTransferAllBalance result`, JSON.parse(JSON.stringify(transferAllCount))) - } - } catch (e) { - if (e.message.indexOf('SERVER_RESPONSE_') === -1 && e.message.indexOf('UI_') === -1) { - // noinspection ES6MissingAwait - BlocksoftCryptoLog.err(`${data.currencyCode} BlocksoftTransfer.getTransferAllBalance ` + e.message) - throw new Error('server.not.responding.all.balance.' + data.currencyCode + ' ' + e.message) - } else { - if (config.debug.appErrors) { - console.log(`${data.currencyCode} BlocksoftTransfer.getTransferAllBalance error ` + e.message) - } - throw e - } - } - return transferAllCount + export const getTransferAllBalance = async function ( + data: BlocksoftBlockchainTypes.TransferData, + additionalData: BlocksoftBlockchainTypes.TransferAdditionalData = {} + ): Promise { + if (config.debug.sendLogs) { + console.log( + `${data.currencyCode} BlocksoftTransfer.getTransferAllBalance`, + JSON.parse(JSON.stringify(data)), + JSON.parse(JSON.stringify(additionalData)) + ); + } + if (typeof data.derivationPath !== 'undefined' && data.derivationPath) { + data.derivationPath = data.derivationPath.replace(/quote/g, "'"); } + data.isTransferAll = true; + let transferAllCount; + try { + BlocksoftCryptoLog.log( + `${data.currencyCode} BlocksoftTransfer.getTransferAllBalance started ${data.addressFrom} ` + ); + const processor = BlocksoftTransferDispatcher.getTransferProcessor( + data.currencyCode + ); + const additionalDataTmp = { ...additionalData }; + let privateData = {} as BlocksoftBlockchainTypes.TransferPrivateData; + if (processor.needPrivateForFee()) { + privateData = await BlocksoftTransferPrivate.initTransferPrivate( + data, + additionalData + ); + } + additionalDataTmp.mnemonic = '***'; + transferAllCount = await BlocksoftTransferDispatcher.getTransferProcessor( + data.currencyCode + ).getTransferAllBalance(data, privateData, additionalDataTmp); - export const getFeeRate = async function(data: BlocksoftBlockchainTypes.TransferData, additionalData: BlocksoftBlockchainTypes.TransferAdditionalData = {}): Promise { - const lower = data.addressTo.toLowerCase() - if (!data?.walletConnectData?.data) { - for (const key in CoinBlocksoftDict) { - const tmp = CoinBlocksoftDict[key] - if (typeof tmp.canBeDestination !== 'undefined' && tmp.canBeDestination) { - continue - } - if (tmp?.tokenName && tmp?.tokenName.toLowerCase() === lower) { - throw new Error('SERVER_RESPONSE_CONTRACT_DESTINATION_INVALID') - } - if (tmp?.tokenAddress && tmp?.tokenAddress.toLowerCase() === lower) { - throw new Error('SERVER_RESPONSE_CONTRACT_DESTINATION_INVALID') - } - } + BlocksoftCryptoLog.log( + `${data.currencyCode} BlocksoftTransfer.getTransferAllBalance got ${data.addressFrom} result is ok` + ); + if (config.debug.sendLogs) { + console.log( + `${data.currencyCode} BlocksoftTransfer.getTransferAllBalance result`, + JSON.parse(JSON.stringify(transferAllCount)) + ); + } + } catch (e) { + if ( + e.message.indexOf('SERVER_RESPONSE_') === -1 && + e.message.indexOf('UI_') === -1 + ) { + // noinspection ES6MissingAwait + BlocksoftCryptoLog.err( + `${data.currencyCode} BlocksoftTransfer.getTransferAllBalance ` + + e.message + ); + throw new Error( + 'server.not.responding.all.balance.' + + data.currencyCode + + ' ' + + e.message + ); + } else { + if (config.debug.appErrors) { + console.log( + `${data.currencyCode} BlocksoftTransfer.getTransferAllBalance error ` + + e.message + ); } + throw e; + } + } + return transferAllCount; + }; - if (config.debug.sendLogs) { - console.log('BlocksoftTransfer.getFeeRate', JSON.parse(JSON.stringify(data)), JSON.parse(JSON.stringify(additionalData))) + export const getFeeRate = async function ( + data: BlocksoftBlockchainTypes.TransferData, + additionalData: BlocksoftBlockchainTypes.TransferAdditionalData = {} + ): Promise { + const lower = data.addressTo.toLowerCase(); + if (!data?.walletConnectData?.data) { + for (const key in CoinBlocksoftDict) { + const tmp = CoinBlocksoftDict[key]; + if ( + typeof tmp.canBeDestination !== 'undefined' && + tmp.canBeDestination + ) { + continue; } - if (typeof data.derivationPath === 'undefined' || !data.derivationPath) { - throw new Error('BlocksoftTransfer.getFeeRate requires derivationPath ' + JSON.stringify(data)) + if (tmp?.tokenName && tmp?.tokenName.toLowerCase() === lower) { + throw new Error('SERVER_RESPONSE_CONTRACT_DESTINATION_INVALID'); } - data.derivationPath = data.derivationPath.replace(/quote/g, '\'') - let feesCount - try { - BlocksoftCryptoLog.log(`${data.currencyCode} BlocksoftTransfer.getFeeRate started ${data.addressFrom} `) - const processor = BlocksoftTransferDispatcher.getTransferProcessor(data.currencyCode) - const additionalDataTmp = { ...additionalData } - - let privateData = {} as BlocksoftBlockchainTypes.TransferPrivateData - if (processor.needPrivateForFee()) { - privateData = await BlocksoftTransferPrivate.initTransferPrivate(data, additionalData) - } - additionalDataTmp.mnemonic = '***' - feesCount = await processor.getFeeRate(data, privateData, additionalDataTmp) - feesCount.countedTime = new Date().getTime() - } catch (e) { - if (config.debug.cryptoErrors) { - console.log('BlocksoftTransfer.getFeeRate error ', e) - } - if (typeof e.message === 'undefined' ) { - await BlocksoftCryptoLog.log('BlocksoftTransfer.getFeeRate strange error') - } else if (e.message.indexOf('SERVER_RESPONSE_') === -1 && e.message.indexOf('UI_') === -1) { - // noinspection ES6MissingAwait - await BlocksoftCryptoLog.err(`${data.currencyCode} BlocksoftTransfer.getFeeRate error ` + data.addressFrom + ' => ' + data.addressTo + ' ' + data.amount + ' ' + e.message) - throw new Error('server.not.responding.network.prices.' + data.currencyCode + ' ' + e.message) - } else { - await BlocksoftCryptoLog.log('BlocksoftTransfer.getFeeRate inner error ' + e.message) - throw e - } + if (tmp?.tokenAddress && tmp?.tokenAddress.toLowerCase() === lower) { + throw new Error('SERVER_RESPONSE_CONTRACT_DESTINATION_INVALID'); } - return feesCount + } } - export const sendTx = async function(data: BlocksoftBlockchainTypes.TransferData, uiData: BlocksoftBlockchainTypes.TransferUiData, additionalData: BlocksoftBlockchainTypes.TransferAdditionalData): Promise { - if (config.debug.sendLogs) { - console.log('BlocksoftTransfer.sendTx', data, uiData) - } + if (config.debug.sendLogs) { + console.log( + 'BlocksoftTransfer.getFeeRate', + JSON.parse(JSON.stringify(data)), + JSON.parse(JSON.stringify(additionalData)) + ); + } + if (typeof data.derivationPath === 'undefined' || !data.derivationPath) { + throw new Error( + 'BlocksoftTransfer.getFeeRate requires derivationPath ' + + JSON.stringify(data) + ); + } + data.derivationPath = data.derivationPath.replace(/quote/g, "'"); + let feesCount; + try { + BlocksoftCryptoLog.log( + `${data.currencyCode} BlocksoftTransfer.getFeeRate started ${data.addressFrom} ` + ); + const processor = BlocksoftTransferDispatcher.getTransferProcessor( + data.currencyCode + ); + const additionalDataTmp = { ...additionalData }; - const lower = data.addressTo.toLowerCase() - if (!data?.walletConnectData?.data) { - for (const key in CoinBlocksoftDict) { - const tmp = CoinBlocksoftDict[key] - if (typeof tmp.canBeDestination !== 'undefined' && tmp.canBeDestination) { - continue - } - if (tmp?.tokenName && tmp?.tokenName.toLowerCase() === lower) { - throw new Error('SERVER_RESPONSE_CONTRACT_DESTINATION_INVALID') - } - if (tmp?.tokenAddress && tmp?.tokenAddress.toLowerCase() === lower) { - throw new Error('SERVER_RESPONSE_CONTRACT_DESTINATION_INVALID') - } - } - } + let privateData = {} as BlocksoftBlockchainTypes.TransferPrivateData; + if (processor.needPrivateForFee()) { + privateData = await BlocksoftTransferPrivate.initTransferPrivate( + data, + additionalData + ); + } + additionalDataTmp.mnemonic = '***'; + feesCount = await processor.getFeeRate( + data, + privateData, + additionalDataTmp + ); + feesCount.countedTime = new Date().getTime(); + } catch (e) { + if (config.debug.cryptoErrors) { + console.log('BlocksoftTransfer.getFeeRate error ', e); + } + if (typeof e.message === 'undefined') { + await BlocksoftCryptoLog.log( + 'BlocksoftTransfer.getFeeRate strange error' + ); + } else if ( + e.message.indexOf('SERVER_RESPONSE_') === -1 && + e.message.indexOf('UI_') === -1 + ) { + // noinspection ES6MissingAwait + await BlocksoftCryptoLog.err( + `${data.currencyCode} BlocksoftTransfer.getFeeRate error ` + + data.addressFrom + + ' => ' + + data.addressTo + + ' ' + + data.amount + + ' ' + + e.message + ); + throw new Error( + 'server.not.responding.network.prices.' + + data.currencyCode + + ' ' + + e.message + ); + } else { + await BlocksoftCryptoLog.log( + 'BlocksoftTransfer.getFeeRate inner error ' + e.message + ); + throw e; + } + } + return feesCount; + }; - data.derivationPath = data.derivationPath.replace(/quote/g, '\'') + export const sendTx = async function ( + data: BlocksoftBlockchainTypes.TransferData, + uiData: BlocksoftBlockchainTypes.TransferUiData, + additionalData: BlocksoftBlockchainTypes.TransferAdditionalData + ): Promise { + if (config.debug.sendLogs) { + console.log('BlocksoftTransfer.sendTx', data, uiData); + } - const bseOrderId = typeof uiData !== 'undefined' && uiData && typeof uiData.selectedFee !== 'undefined' && typeof uiData.selectedFee.bseOrderId !== 'undefined' ? uiData.selectedFee.bseOrderId : false - const uiErrorConfirmed = typeof uiData !== 'undefined' && uiData && typeof uiData.uiErrorConfirmed !== 'undefined' && uiData.uiErrorConfirmed - const memo = typeof data !== 'undefined' && data && typeof data.memo !== 'undefined' ? data.memo : false + const lower = data.addressTo.toLowerCase(); + if (!data?.walletConnectData?.data) { + for (const key in CoinBlocksoftDict) { + const tmp = CoinBlocksoftDict[key]; + if ( + typeof tmp.canBeDestination !== 'undefined' && + tmp.canBeDestination + ) { + continue; + } + if (tmp?.tokenName && tmp?.tokenName.toLowerCase() === lower) { + throw new Error('SERVER_RESPONSE_CONTRACT_DESTINATION_INVALID'); + } + if (tmp?.tokenAddress && tmp?.tokenAddress.toLowerCase() === lower) { + throw new Error('SERVER_RESPONSE_CONTRACT_DESTINATION_INVALID'); + } + } + } + data.derivationPath = data.derivationPath.replace(/quote/g, "'"); - try { - if (data.transactionReplaceByFee || data.transactionRemoveByFee || data.transactionSpeedUp) { - // do nothing - } else { - if (bseOrderId) { - // bse order - if (typeof CACHE_DOUBLE_BSE[bseOrderId] !== 'undefined') { - if (!uiErrorConfirmed) { - throw new Error('UI_CONFIRM_DOUBLE_BSE_SEND') - } - } - } - // usual tx - if (typeof CACHE_DOUBLE_TO[data.currencyCode] !== 'undefined') { - if (!uiErrorConfirmed) { - if (data.addressTo && CACHE_DOUBLE_TO[data.currencyCode].key === data.addressTo && CACHE_DOUBLE_TO[data.currencyCode].memo === memo) { - const time = new Date().getTime() - const diff = time - CACHE_DOUBLE_TO[data.currencyCode].time - if (diff < CACHE_VALID_TIME) { - CACHE_DOUBLE_TO[data.currencyCode].time = time - throw new Error('UI_CONFIRM_DOUBLE_SEND') - } - } - } - } + const bseOrderId = + typeof uiData !== 'undefined' && + uiData && + typeof uiData.selectedFee !== 'undefined' && + typeof uiData.selectedFee.bseOrderId !== 'undefined' + ? uiData.selectedFee.bseOrderId + : false; + const uiErrorConfirmed = + typeof uiData !== 'undefined' && + uiData && + typeof uiData.uiErrorConfirmed !== 'undefined' && + uiData.uiErrorConfirmed; + const memo = + typeof data !== 'undefined' && data && typeof data.memo !== 'undefined' + ? data.memo + : false; + try { + if ( + data.transactionReplaceByFee || + data.transactionRemoveByFee || + data.transactionSpeedUp + ) { + // do nothing + } else { + if (bseOrderId) { + // bse order + if (typeof CACHE_DOUBLE_BSE[bseOrderId] !== 'undefined') { + if (!uiErrorConfirmed) { + throw new Error('UI_CONFIRM_DOUBLE_BSE_SEND'); } - } catch (e) { - if (config.debug.cryptoErrors) { - console.log(`${data.currencyCode} BlocksoftTransfer.sendTx check double error ` + e.message, e) - } - if (e.message.indexOf('SERVER_RESPONSE_') === -1 && e.message.indexOf('UI_') === -1) { - BlocksoftCryptoLog.log(`${data.currencyCode} BlocksoftTransfer.sendTx error ` + e.message) - } - throw e + } } - - let txResult - try { - BlocksoftCryptoLog.log(`${data.currencyCode} BlocksoftTransfer.sendTx started ${data.addressFrom} `) - const processor = BlocksoftTransferDispatcher.getTransferProcessor(data.currencyCode) - const privateData = await BlocksoftTransferPrivate.initTransferPrivate(data, additionalData) - txResult = await processor.sendTx(data, privateData, uiData) - BlocksoftCryptoLog.log(`${data.currencyCode} BlocksoftTransfer.sendTx got ${data.addressFrom} result is ok`) - if (typeof uiData === 'undefined' || typeof uiData.selectedFee === 'undefined' || typeof uiData.selectedFee.rawOnly === 'undefined' || !uiData.selectedFee.rawOnly) { - CACHE_DOUBLE_TO[data.currencyCode] = { - key: data.addressTo, - memo, - time: new Date().getTime() - } - } - if (bseOrderId) { - CACHE_DOUBLE_BSE[bseOrderId] = true - } - // if (typeof uiData.selectedFee !== 'undefined') - } catch (e) { - if (config.debug.cryptoErrors) { - console.log('BlocksoftTransfer.sendTx error ' + e.message, e) - } - if (e.message.indexOf('SERVER_RESPONSE_') === -1 && e.message.indexOf('UI_') === -1 && e.message.indexOf('connect() timed') === -1) { - // noinspection ES6MissingAwait - BlocksoftCryptoLog.err(`${data.currencyCode} BlocksoftTransfer.sendTx ` + e.message) - } - - if (e.message.indexOf('imeout') !== -1 || e.message.indexOf('network error') !== -1 || e.message==='SERVER_RESPONSE_BAD_INTERNET') { - throw new Error('SERVER_RESPONSE_NOT_CONNECTED') - } - if (e.message.indexOf('SERVER_RESPONSE_NOT_CONNECTED') !== -1 && (data.currencyCode === 'TRX' || data.currencyCode.indexOf('TRX_') !== -1)) { - BlocksoftCryptoLog.log(`${data.currencyCode} BlocksoftTransfer.sendTx ` + e.message) - } else { - throw e + // usual tx + if (typeof CACHE_DOUBLE_TO[data.currencyCode] !== 'undefined') { + if (!uiErrorConfirmed) { + if ( + data.addressTo && + CACHE_DOUBLE_TO[data.currencyCode].key === data.addressTo && + CACHE_DOUBLE_TO[data.currencyCode].memo === memo + ) { + const time = new Date().getTime(); + const diff = time - CACHE_DOUBLE_TO[data.currencyCode].time; + if (diff < CACHE_VALID_TIME) { + CACHE_DOUBLE_TO[data.currencyCode].time = time; + throw new Error('UI_CONFIRM_DOUBLE_SEND'); + } } + } } - return txResult + } + } catch (e) { + if (config.debug.cryptoErrors) { + console.log( + `${data.currencyCode} BlocksoftTransfer.sendTx check double error ` + + e.message, + e + ); + } + if ( + e.message.indexOf('SERVER_RESPONSE_') === -1 && + e.message.indexOf('UI_') === -1 + ) { + BlocksoftCryptoLog.log( + `${data.currencyCode} BlocksoftTransfer.sendTx error ` + e.message + ); + } + throw e; } + let txResult; + try { + BlocksoftCryptoLog.log( + `${data.currencyCode} BlocksoftTransfer.sendTx started ${data.addressFrom} ` + ); + const processor = BlocksoftTransferDispatcher.getTransferProcessor( + data.currencyCode + ); + const privateData = await BlocksoftTransferPrivate.initTransferPrivate( + data, + additionalData + ); + txResult = await processor.sendTx(data, privateData, uiData); + BlocksoftCryptoLog.log( + `${data.currencyCode} BlocksoftTransfer.sendTx got ${data.addressFrom} result is ok` + ); + if ( + typeof uiData === 'undefined' || + typeof uiData.selectedFee === 'undefined' || + typeof uiData.selectedFee.rawOnly === 'undefined' || + !uiData.selectedFee.rawOnly + ) { + CACHE_DOUBLE_TO[data.currencyCode] = { + key: data.addressTo, + memo, + time: new Date().getTime() + }; + } + if (bseOrderId) { + CACHE_DOUBLE_BSE[bseOrderId] = true; + } + // if (typeof uiData.selectedFee !== 'undefined') + } catch (e) { + if (config.debug.cryptoErrors) { + console.log('BlocksoftTransfer.sendTx error ' + e.message, e); + } + if ( + e.message.indexOf('SERVER_RESPONSE_') === -1 && + e.message.indexOf('UI_') === -1 && + e.message.indexOf('connect() timed') === -1 + ) { + // noinspection ES6MissingAwait + BlocksoftCryptoLog.err( + `${data.currencyCode} BlocksoftTransfer.sendTx ` + e.message + ); + } - export const sendRawTx = async function(data: BlocksoftBlockchainTypes.DbAccount, rawTxHex: string, txRBF: any, logData: any): Promise { - let txResult = '' - try { - BlocksoftCryptoLog.log(`${data.currencyCode} BlocksoftTransfer.sendRawTx started ${data.address} `) - const processor = BlocksoftTransferDispatcher.getTransferProcessor(data.currencyCode) - if (typeof processor.sendRawTx === 'undefined') { - return 'none' - } - txResult = await processor.sendRawTx(data, rawTxHex, txRBF, logData) - BlocksoftCryptoLog.log(`${data.currencyCode} BlocksoftTransfer.sendRawTx got ${data.address} result is ok`) - } catch (e) { - if (config.debug.cryptoErrors) { - console.log(`${data.currencyCode} BlocksoftTransfer.sendRawTx error `, e) - } - BlocksoftCryptoLog.log(`${data.currencyCode} BlocksoftTransfer.sendRawTx error ` + e.message) - throw e - } - return txResult + if ( + e.message.indexOf('imeout') !== -1 || + e.message.indexOf('network error') !== -1 || + e.message === 'SERVER_RESPONSE_BAD_INTERNET' + ) { + throw new Error('SERVER_RESPONSE_NOT_CONNECTED'); + } + if ( + e.message.indexOf('SERVER_RESPONSE_NOT_CONNECTED') !== -1 && + (data.currencyCode === 'TRX' || + data.currencyCode.indexOf('TRX_') !== -1) + ) { + BlocksoftCryptoLog.log( + `${data.currencyCode} BlocksoftTransfer.sendTx ` + e.message + ); + } else { + throw e; + } } + return txResult; + }; + export const sendRawTx = async function ( + data: BlocksoftBlockchainTypes.DbAccount, + rawTxHex: string, + txRBF: any, + logData: any + ): Promise { + let txResult = ''; + try { + BlocksoftCryptoLog.log( + `${data.currencyCode} BlocksoftTransfer.sendRawTx started ${data.address} ` + ); + const processor = BlocksoftTransferDispatcher.getTransferProcessor( + data.currencyCode + ); + if (typeof processor.sendRawTx === 'undefined') { + return 'none'; + } + txResult = await processor.sendRawTx(data, rawTxHex, txRBF, logData); + BlocksoftCryptoLog.log( + `${data.currencyCode} BlocksoftTransfer.sendRawTx got ${data.address} result is ok` + ); + } catch (e) { + if (config.debug.cryptoErrors) { + console.log( + `${data.currencyCode} BlocksoftTransfer.sendRawTx error `, + e + ); + } + BlocksoftCryptoLog.log( + `${data.currencyCode} BlocksoftTransfer.sendRawTx error ` + e.message + ); + throw e; + } + return txResult; + }; - export const setMissingTx = async function(data: BlocksoftBlockchainTypes.DbAccount, dbTransaction: BlocksoftBlockchainTypes.DbTransaction): Promise { - let txResult = false - try { - BlocksoftCryptoLog.log(`${data.currencyCode} BlocksoftTransfer.setMissing started ${data.address} `) - const processor = BlocksoftTransferDispatcher.getTransferProcessor(data.currencyCode) - if (typeof processor.setMissingTx === 'undefined') { - return false - } - txResult = await processor.setMissingTx(data, dbTransaction) - BlocksoftCryptoLog.log(`${data.currencyCode} BlocksoftTransfer.setMissing got ${data.address} result is ok`) - } catch (e) { - BlocksoftCryptoLog.err(`${data.currencyCode} BlocksoftTransfer.setMissing error ` + e.message) - } - return txResult + export const setMissingTx = async function ( + data: BlocksoftBlockchainTypes.DbAccount, + dbTransaction: BlocksoftBlockchainTypes.DbTransaction + ): Promise { + let txResult = false; + try { + BlocksoftCryptoLog.log( + `${data.currencyCode} BlocksoftTransfer.setMissing started ${data.address} ` + ); + const processor = BlocksoftTransferDispatcher.getTransferProcessor( + data.currencyCode + ); + if (typeof processor.setMissingTx === 'undefined') { + return false; + } + txResult = await processor.setMissingTx(data, dbTransaction); + BlocksoftCryptoLog.log( + `${data.currencyCode} BlocksoftTransfer.setMissing got ${data.address} result is ok` + ); + } catch (e) { + BlocksoftCryptoLog.err( + `${data.currencyCode} BlocksoftTransfer.setMissing error ` + e.message + ); } + return txResult; + }; - export const canRBF = function(data: BlocksoftBlockchainTypes.DbAccount, dbTransaction: BlocksoftBlockchainTypes.DbTransaction, source: string): boolean { - let txResult = false - try { - // BlocksoftCryptoLog.log(`BlocksoftTransfer.canRBF ${data.currencyCode} from ${source} started ${data.address} `) - const processor = BlocksoftTransferDispatcher.getTransferProcessor(typeof data.currencyCode !== 'undefined' ? data.currencyCode : dbTransaction.currencyCode) - if (typeof processor.canRBF === 'undefined') { - return false - } - txResult = processor.canRBF(data, dbTransaction, source) - // BlocksoftCryptoLog.log(`BlocksoftTransfer.canRBF ${data.currencyCode} from ${source} got ${data.address} result is ${JSON.stringify(txResult)}`) - } catch (e) { - BlocksoftCryptoLog.err(`${data.currencyCode} BlocksoftTransfer.canRBF error from ${source} ` + e.message) - } - return txResult + export const canRBF = function ( + data: BlocksoftBlockchainTypes.DbAccount, + dbTransaction: BlocksoftBlockchainTypes.DbTransaction, + source: string + ): boolean { + let txResult = false; + try { + // BlocksoftCryptoLog.log(`BlocksoftTransfer.canRBF ${data.currencyCode} from ${source} started ${data.address} `) + const processor = BlocksoftTransferDispatcher.getTransferProcessor( + typeof data.currencyCode !== 'undefined' + ? data.currencyCode + : dbTransaction.currencyCode + ); + if (typeof processor.canRBF === 'undefined') { + return false; + } + txResult = processor.canRBF(data, dbTransaction, source); + // BlocksoftCryptoLog.log(`BlocksoftTransfer.canRBF ${data.currencyCode} from ${source} got ${data.address} result is ${JSON.stringify(txResult)}`) + } catch (e) { + BlocksoftCryptoLog.err( + `${data.currencyCode} BlocksoftTransfer.canRBF error from ${source} ` + + e.message + ); } + return txResult; + }; - export const checkSendAllModal = function(data: { currencyCode: any }) { - let checkSendAllModalResult = false - try { - // BlocksoftCryptoLog.log(`BlocksoftTransfer.checkSendAllModal ${data.currencyCode} started `) - const processor = BlocksoftTransferDispatcher.getTransferProcessor(data.currencyCode) - if (typeof processor.checkSendAllModal === 'undefined') { - return false - } - checkSendAllModalResult = processor.checkSendAllModal(data) - // BlocksoftCryptoLog.log(`BlocksoftTransfer.checkSendAllModal ${data.currencyCode} got result is ok ` + JSON.stringify(checkSendAllModalResult)) - } catch (e) { - BlocksoftCryptoLog.err(`${data.currencyCode} BlocksoftTransfer.checkSendAllModal error ` + e.message) - } - return checkSendAllModalResult + export const checkSendAllModal = function (data: { currencyCode: any }) { + let checkSendAllModalResult = false; + try { + // BlocksoftCryptoLog.log(`BlocksoftTransfer.checkSendAllModal ${data.currencyCode} started `) + const processor = BlocksoftTransferDispatcher.getTransferProcessor( + data.currencyCode + ); + if (typeof processor.checkSendAllModal === 'undefined') { + return false; + } + checkSendAllModalResult = processor.checkSendAllModal(data); + // BlocksoftCryptoLog.log(`BlocksoftTransfer.checkSendAllModal ${data.currencyCode} got result is ok ` + JSON.stringify(checkSendAllModalResult)) + } catch (e) { + BlocksoftCryptoLog.err( + `${data.currencyCode} BlocksoftTransfer.checkSendAllModal error ` + + e.message + ); } + return checkSendAllModalResult; + }; } diff --git a/crypto/actions/BlocksoftTransfer/BlocksoftTransferUtils.ts b/crypto/actions/BlocksoftTransfer/BlocksoftTransferUtils.ts index 00e179404..6e70231ef 100644 --- a/crypto/actions/BlocksoftTransfer/BlocksoftTransferUtils.ts +++ b/crypto/actions/BlocksoftTransfer/BlocksoftTransferUtils.ts @@ -2,66 +2,77 @@ * @author Ksu * @version 0.20 */ -import { BlocksoftDictTypes } from '../../common/BlocksoftDictTypes' -import { BlocksoftTransferDispatcher } from '../../blockchains/BlocksoftTransferDispatcher' -import { BlocksoftBlockchainTypes } from '../../blockchains/BlocksoftBlockchainTypes' -import BlocksoftUtils from '../../common/BlocksoftUtils' +import { BlocksoftDictTypes } from '../../common/AirDAODictTypes'; +import { BlocksoftTransferDispatcher } from '../../blockchains/BlocksoftTransferDispatcher'; +import { BlocksoftBlockchainTypes } from '../../blockchains/BlocksoftBlockchainTypes'; +import BlocksoftUtils from '../../common/BlocksoftUtils'; export namespace BlocksoftTransferUtils { - - export const getAddressToForTransferAll = function(data : {currencyCode: BlocksoftDictTypes.Code, address : string} ) : string { - if (data.currencyCode === BlocksoftDictTypes.Code.BTC_TEST) { - return 'mjojEgUSi68PqNHoAyjhVkwdqQyLv9dTfV' - } - if (data.currencyCode === BlocksoftDictTypes.Code.XRP) { - const tmp1 = 'rEAgA9B8U8RCkwn6MprHqE1ZfXoeGQxz4P' - const tmp2 = 'rnyWPfJ7dk2X15N7jqwmqo3Nspu1oYiRZ3' - return data.address === tmp1 ? tmp2 : tmp1 - } - if (data.currencyCode === BlocksoftDictTypes.Code.XLM) { - const tmp1 = 'GCVPV3D4PAYFA7H2CHGFRTSPAHMSU4KQR4CHBUBUR4X23JUDJWHYZDYZ' - const tmp2 = 'GAQA5FITDUZW36J6VFXAH4YDNTTDEGRNWIXHIOR3FNN4DVJCXCNHUR4E' - return data.address === tmp1 ? tmp2 : tmp1 - - } - return data.address + export const getAddressToForTransferAll = function (data: { + currencyCode: BlocksoftDictTypes.Code; + address: string; + }): string { + if (data.currencyCode === BlocksoftDictTypes.Code.BTC_TEST) { + return 'mjojEgUSi68PqNHoAyjhVkwdqQyLv9dTfV'; + } + if (data.currencyCode === BlocksoftDictTypes.Code.XRP) { + const tmp1 = 'rEAgA9B8U8RCkwn6MprHqE1ZfXoeGQxz4P'; + const tmp2 = 'rnyWPfJ7dk2X15N7jqwmqo3Nspu1oYiRZ3'; + return data.address === tmp1 ? tmp2 : tmp1; } + if (data.currencyCode === BlocksoftDictTypes.Code.XLM) { + const tmp1 = 'GCVPV3D4PAYFA7H2CHGFRTSPAHMSU4KQR4CHBUBUR4X23JUDJWHYZDYZ'; + const tmp2 = 'GAQA5FITDUZW36J6VFXAH4YDNTTDEGRNWIXHIOR3FNN4DVJCXCNHUR4E'; + return data.address === tmp1 ? tmp2 : tmp1; + } + return data.address; + }; - export const checkTransferHasError = async function( data : BlocksoftBlockchainTypes.CheckTransferHasErrorData) : Promise<{isOk : boolean, code ?: string}> { - const processor = BlocksoftTransferDispatcher.getTransferProcessor(data.currencyCode) - if (typeof processor.checkTransferHasError === 'undefined') { - return {isOk : true} - } - return processor.checkTransferHasError(data) + export const checkTransferHasError = async function ( + data: BlocksoftBlockchainTypes.CheckTransferHasErrorData + ): Promise<{ isOk: boolean; code?: string }> { + const processor = BlocksoftTransferDispatcher.getTransferProcessor( + data.currencyCode + ); + if (typeof processor.checkTransferHasError === 'undefined') { + return { isOk: true }; } + return processor.checkTransferHasError(data); + }; - export const getBalanceForTransfer = function(data : { - balance : string, - unconfirmed : string | boolean, - balanceStaked : string, - currencyCode: BlocksoftDictTypes.Code - }) : string { - if (data.currencyCode === BlocksoftDictTypes.Code.TRX) { - if (data.balanceStaked && data.balanceStaked !== '') { - return BlocksoftUtils.diff(data.balance, data.balanceStaked) - } - } - if (data.unconfirmed === false) { - return data.balance - } - // @ts-ignore - if (data.unconfirmed * 1 < 0) { - return data.balance - } - if (data.currencyCode === BlocksoftDictTypes.Code.XRP) { - return data.balance - } - if (data.currencyCode === BlocksoftDictTypes.Code.ETH || data.currencyCode.indexOf('ETH_') === 0) { - return data.balance - } - if (data.currencyCode === BlocksoftDictTypes.Code.TRX || data.currencyCode.indexOf('TRX_') === 0) { - return data.balance - } - return BlocksoftUtils.add(data.balance, data.unconfirmed).toString() + export const getBalanceForTransfer = function (data: { + balance: string; + unconfirmed: string | boolean; + balanceStaked: string; + currencyCode: BlocksoftDictTypes.Code; + }): string { + if (data.currencyCode === BlocksoftDictTypes.Code.TRX) { + if (data.balanceStaked && data.balanceStaked !== '') { + return BlocksoftUtils.diff(data.balance, data.balanceStaked); + } + } + if (data.unconfirmed === false) { + return data.balance; + } + // @ts-ignore + if (data.unconfirmed * 1 < 0) { + return data.balance; + } + if (data.currencyCode === BlocksoftDictTypes.Code.XRP) { + return data.balance; + } + if ( + data.currencyCode === BlocksoftDictTypes.Code.ETH || + data.currencyCode.indexOf('ETH_') === 0 + ) { + return data.balance; + } + if ( + data.currencyCode === BlocksoftDictTypes.Code.TRX || + data.currencyCode.indexOf('TRX_') === 0 + ) { + return data.balance; } + return BlocksoftUtils.add(data.balance, data.unconfirmed).toString(); + }; } diff --git a/src/lib/blockchains/BlocksoftBlockchainTypes.ts b/crypto/blockchains/AirDAOBlockchainTypes.ts similarity index 86% rename from src/lib/blockchains/BlocksoftBlockchainTypes.ts rename to crypto/blockchains/AirDAOBlockchainTypes.ts index b86fa09e9..13720a77e 100644 --- a/src/lib/blockchains/BlocksoftBlockchainTypes.ts +++ b/crypto/blockchains/AirDAOBlockchainTypes.ts @@ -1,11 +1,12 @@ +/* eslint-disable @typescript-eslint/no-namespace */ /** * @author Ksu * @version 0.20 */ -import { BlocksoftDictTypes } from '../common/BlocksoftDictTypes'; +import { AirDAODictTypes } from '../common/AirDAODictTypes'; import { TransactionBuilder } from 'bitcoinjs-lib'; -export namespace BlocksoftBlockchainTypes { +export namespace AirDAOBlockchainTypes { export interface TransferProcessor { needPrivateForFee(): boolean; @@ -46,8 +47,8 @@ export namespace BlocksoftBlockchainTypes { ): Promise; canRBF?( - data: BlocksoftBlockchainTypes.DbAccount, - dbTransaction: BlocksoftBlockchainTypes.DbTransaction, + data: AirDAOBlockchainTypes.DbAccount, + dbTransaction: AirDAOBlockchainTypes.DbTransaction, source: string ): boolean; } @@ -58,7 +59,7 @@ export namespace BlocksoftBlockchainTypes { getTx?( tx: string, address: string, - allUnspents: BlocksoftBlockchainTypes.UnspentTx[], + allUnspents: AirDAOBlockchainTypes.UnspentTx[], walletHash: string ): Promise; @@ -92,33 +93,33 @@ export namespace BlocksoftBlockchainTypes { export interface TxInputsOutputs { getInputsOutputs( - data: BlocksoftBlockchainTypes.TransferData, - unspents: BlocksoftBlockchainTypes.UnspentTx[], + data: AirDAOBlockchainTypes.TransferData, + unspents: AirDAOBlockchainTypes.UnspentTx[], feeToCount: { feeForByte?: string; feeForAll?: string; autoFeeLimitReadable?: string | number; }, - additionalData: BlocksoftBlockchainTypes.TransferAdditionalData, + additionalData: AirDAOBlockchainTypes.TransferAdditionalData, subtitle: string - ): Promise; + ): Promise; } export interface TxBuilder { getRawTx( - data: BlocksoftBlockchainTypes.TransferData, - privateData: BlocksoftBlockchainTypes.TransferPrivateData, - preparedInputsOutputs: BlocksoftBlockchainTypes.PreparedInputsOutputsTx + data: AirDAOBlockchainTypes.TransferData, + privateData: AirDAOBlockchainTypes.TransferPrivateData, + preparedInputsOutputs: AirDAOBlockchainTypes.PreparedInputsOutputsTx ): Promise<{ rawTxHex: string; nSequence: number; txAllowReplaceByFee: boolean; - preparedInputsOutputs: BlocksoftBlockchainTypes.PreparedInputsOutputsTx; + preparedInputsOutputs: AirDAOBlockchainTypes.PreparedInputsOutputsTx; }>; _getRawTxValidateKeyPair( - privateData: BlocksoftBlockchainTypes.TransferPrivateData, - data: BlocksoftBlockchainTypes.TransferData + privateData: AirDAOBlockchainTypes.TransferPrivateData, + data: AirDAOBlockchainTypes.TransferData ): void; _getRawTxAddInput( @@ -159,7 +160,7 @@ export namespace BlocksoftBlockchainTypes { } export interface CheckTransferHasErrorData { - currencyCode: BlocksoftDictTypes.Code; + currencyCode: AirDAODictTypes.Code; walletHash: string; addressTo: string; addressFrom: string; @@ -175,7 +176,7 @@ export namespace BlocksoftBlockchainTypes { } export interface TransferData { - currencyCode: BlocksoftDictTypes.Code; + currencyCode: AirDAODictTypes.Code; walletHash: string; derivationPath: string; addressFrom: string; @@ -320,8 +321,8 @@ export namespace BlocksoftBlockchainTypes { } export interface PreparedInputsOutputsTx { - inputs: BlocksoftBlockchainTypes.UnspentTx[]; - outputs: BlocksoftBlockchainTypes.OutputTx[]; + inputs: AirDAOBlockchainTypes.UnspentTx[]; + outputs: AirDAOBlockchainTypes.OutputTx[]; multiAddress: []; msg: string; countedFor?: string; @@ -338,13 +339,13 @@ export namespace BlocksoftBlockchainTypes { } export interface DbAccount { - currencyCode: BlocksoftDictTypes.Code; + currencyCode: AirDAODictTypes.Code; walletHash: string; address: string; } export interface DbTransaction { - currencyCode: BlocksoftDictTypes.Code; + currencyCode: AirDAODictTypes.Code; transactionHash: string; transactionDirection: string; transactionStatus: string; @@ -355,3 +356,4 @@ export namespace BlocksoftBlockchainTypes { gasPrice?: number; }; } +} diff --git a/src/lib/blockchains/BlocksoftDispatcher.ts b/crypto/blockchains/AirDAODispatcher.ts similarity index 100% rename from src/lib/blockchains/BlocksoftDispatcher.ts rename to crypto/blockchains/AirDAODispatcher.ts diff --git a/crypto/blockchains/AirDAOTransferDispatcher.ts b/crypto/blockchains/AirDAOTransferDispatcher.ts new file mode 100644 index 000000000..192733aa9 --- /dev/null +++ b/crypto/blockchains/AirDAOTransferDispatcher.ts @@ -0,0 +1,188 @@ +/* eslint-disable @typescript-eslint/no-namespace */ +/** + * @author Ksu + * @version 0.20 + */ +import AirDAODict from '../common/BlocksoftDict'; +import { AirDAODictTypes } from '../common/AirDAODictTypes'; + +import BchTransferProcessor from './bch/BchTransferProcessor'; +import BsvTransferProcessor from './bsv/BsvTransferProcessor'; +import BtcTransferProcessor from './btc/BtcTransferProcessor'; +import BtcTestTransferProcessor from './btc_test/BtcTestTransferProcessor'; +import BtgTransferProcessor from './btg/BtgTransferProcessor'; +import DogeTransferProcessor from './doge/DogeTransferProcessor'; +import EthTransferProcessor from './eth/EthTransferProcessor'; +import EthTransferProcessorErc20 from './eth/EthTransferProcessorErc20'; +import LtcTransferProcessor from './ltc/LtcTransferProcessor'; +import TrxTransferProcessor from './trx/TrxTransferProcessor'; +import UsdtTransferProcessor from './usdt/UsdtTransferProcessor'; +import XrpTransferProcessor from './xrp/XrpTransferProcessor'; +import XlmTransferProcessor from './xlm/XlmTransferProcessor'; +import XvgTransferProcessor from './xvg/XvgTransferProcessor'; +import XmrTransferProcessor from './xmr/XmrTransferProcessor'; +import FioTransferProcessor from './fio/FioTransferProcessor'; +import BnbTransferProcessor from './bnb/BnbTransferProcessor'; +import BnbSmartTransferProcessor from './bnb_smart/BnbSmartTransferProcessor'; +import BnbSmartTransferProcessorErc20 from './bnb_smart/BnbSmartTransferProcessorErc20'; +import EtcTransferProcessor from './etc/EtcTransferProcessor'; +import VetTransferProcessor from '@crypto/blockchains/vet/VetTransferProcessor'; +import SolTransferProcessor from '@crypto/blockchains/sol/SolTransferProcessor'; +import SolTransferProcessorSpl from '@crypto/blockchains/sol/SolTransferProcessorSpl'; +import WavesTransferProcessor from '@crypto/blockchains/waves/WavesTransferProcessor'; +import MetisTransferProcessor from '@crypto/blockchains/metis/MetisTransferProcessor'; + +import { AirDAOBlockchainTypes } from './AirDAOBlockchainTypes'; + +export namespace AirDAOTransferDispatcher { + type AirDAOTransferDispatcherDict = { + [key in AirDAODictTypes.Code]: AirDAOBlockchainTypes.TransferProcessor; + }; + + const CACHE_PROCESSORS: AirDAOTransferDispatcherDict = + {} as AirDAOTransferDispatcherDict; + + export const getTransferProcessor = function ( + currencyCode: AirDAODictTypes.Code + ): AirDAOBlockchainTypes.TransferProcessor { + const currencyDictSettings = + AirDAODict.getCurrencyAllSettings(currencyCode); + if (typeof CACHE_PROCESSORS[currencyCode] !== 'undefined') { + return CACHE_PROCESSORS[currencyCode]; + } + let transferProcessor = currencyCode; + if (typeof currencyDictSettings.transferProcessor !== 'undefined') { + transferProcessor = currencyDictSettings.transferProcessor; + } + switch (transferProcessor) { + case 'BCH': + CACHE_PROCESSORS[currencyCode] = new BchTransferProcessor( + currencyDictSettings + ); + break; + case 'BSV': + CACHE_PROCESSORS[currencyCode] = new BsvTransferProcessor( + currencyDictSettings + ); + break; + case 'BTC': + CACHE_PROCESSORS[currencyCode] = new BtcTransferProcessor( + currencyDictSettings + ); + break; + case 'BTC_TEST': + CACHE_PROCESSORS[currencyCode] = new BtcTestTransferProcessor( + currencyDictSettings + ); + break; + case 'BTG': + CACHE_PROCESSORS[currencyCode] = new BtgTransferProcessor( + currencyDictSettings + ); + break; + case 'DOGE': + CACHE_PROCESSORS[currencyCode] = new DogeTransferProcessor( + currencyDictSettings + ); + break; + case 'ETH': + CACHE_PROCESSORS[currencyCode] = new EthTransferProcessor( + currencyDictSettings + ); + break; + case 'ETH_ERC_20': + CACHE_PROCESSORS[currencyCode] = new EthTransferProcessorErc20( + currencyDictSettings + ); + break; + case 'ETC': + CACHE_PROCESSORS[currencyCode] = new EtcTransferProcessor( + currencyDictSettings + ); + break; + case 'BNB_SMART_20': + CACHE_PROCESSORS[currencyCode] = new BnbSmartTransferProcessorErc20( + currencyDictSettings + ); + break; + case 'LTC': + CACHE_PROCESSORS[currencyCode] = new LtcTransferProcessor( + currencyDictSettings + ); + break; + case 'TRX': + CACHE_PROCESSORS[currencyCode] = new TrxTransferProcessor( + currencyDictSettings + ); + break; + case 'USDT': + CACHE_PROCESSORS[currencyCode] = new UsdtTransferProcessor( + currencyDictSettings + ); + break; + case 'XRP': + CACHE_PROCESSORS[currencyCode] = new XrpTransferProcessor( + currencyDictSettings + ); + break; + case 'XLM': + CACHE_PROCESSORS[currencyCode] = new XlmTransferProcessor( + currencyDictSettings + ); + break; + case 'XVG': + CACHE_PROCESSORS[currencyCode] = new XvgTransferProcessor( + currencyDictSettings + ); + break; + case 'XMR': + CACHE_PROCESSORS[currencyCode] = new XmrTransferProcessor( + currencyDictSettings + ); + break; + case 'FIO': + CACHE_PROCESSORS[currencyCode] = new FioTransferProcessor( + currencyDictSettings + ); + break; + case 'BNB': + CACHE_PROCESSORS[currencyCode] = new BnbTransferProcessor( + currencyDictSettings + ); + break; + case 'BNB_SMART': + CACHE_PROCESSORS[currencyCode] = new BnbSmartTransferProcessor( + currencyDictSettings + ); + break; + case 'VET': + CACHE_PROCESSORS[currencyCode] = new VetTransferProcessor( + currencyDictSettings + ); + break; + case 'SOL': + CACHE_PROCESSORS[currencyCode] = new SolTransferProcessor( + currencyDictSettings + ); + break; + case 'SOL_SPL': + CACHE_PROCESSORS[currencyCode] = new SolTransferProcessorSpl( + currencyDictSettings + ); + break; + case 'METIS': + CACHE_PROCESSORS[currencyCode] = new MetisTransferProcessor( + currencyDictSettings + ); + break; + case 'WAVES': + CACHE_PROCESSORS[currencyCode] = new WavesTransferProcessor( + currencyDictSettings + ); + break; + default: + throw new Error('Unknown transferProcessor ' + transferProcessor); + } + return CACHE_PROCESSORS[currencyCode]; + }; +} diff --git a/crypto/blockchains/bch/BchTransferProcessor.ts b/crypto/blockchains/bch/BchTransferProcessor.ts index 2dd70c4c1..1444060e0 100644 --- a/crypto/blockchains/bch/BchTransferProcessor.ts +++ b/crypto/blockchains/bch/BchTransferProcessor.ts @@ -1,7 +1,7 @@ /** * @version 0.20 */ -import { BlocksoftBlockchainTypes } from '../BlocksoftBlockchainTypes'; +import { AirDAOBlockchainTypes } from '../AirDAOBlockchainTypes'; import DogeTransferProcessor from '../doge/DogeTransferProcessor'; import BchUnspentsProvider from './providers/BchUnspentsProvider'; import BchSendProvider from './providers/BchSendProvider'; @@ -10,11 +10,11 @@ import BchTxBuilder from './tx/BchTxBuilder'; export default class BchTransferProcessor extends DogeTransferProcessor - implements BlocksoftBlockchainTypes.TransferProcessor + implements AirDAOBlockchainTypes.TransferProcessor { _trezorServerCode = 'BCH_TREZOR_SERVER'; - _builderSettings: BlocksoftBlockchainTypes.BuilderSettings = { + _builderSettings: AirDAOBlockchainTypes.BuilderSettings = { minOutputDustReadable: 0.000005, minChangeDustReadable: 0.00001, feeMaxForByteSatoshi: 10000, // for tx builder diff --git a/crypto/blockchains/bch/ext/BtcCashUtils.js b/crypto/blockchains/bch/ext/BtcCashUtils.js index e18d08ced..9d5b4d5b4 100644 --- a/crypto/blockchains/bch/ext/BtcCashUtils.js +++ b/crypto/blockchains/bch/ext/BtcCashUtils.js @@ -1,205 +1,233 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ /** * @author Ksu * @version 0.5 */ -const bs58check = require('bs58check') +const bs58check = require('bs58check'); +const createHash = require('create-hash'); const VERSION_BYTE = { - 'P2PKH': 0, - 'P2SH': 5 -} + P2PKH: 0, + P2SH: 5 +}; // for legacy const CHARSET_INVERSE_INDEX = { - 'q': 0, 'p': 1, 'z': 2, 'r': 3, 'y': 4, '9': 5, 'x': 6, '8': 7, - 'g': 8, 'f': 9, '2': 10, 't': 11, 'v': 12, 'd': 13, 'w': 14, '0': 15, - 's': 16, '3': 17, 'j': 18, 'n': 19, '5': 20, '4': 21, 'k': 22, 'h': 23, - 'c': 24, 'e': 25, '6': 26, 'm': 27, 'u': 28, 'a': 29, '7': 30, 'l': 31, -} + q: 0, + p: 1, + z: 2, + r: 3, + y: 4, + 9: 5, + x: 6, + 8: 7, + g: 8, + f: 9, + 2: 10, + t: 11, + v: 12, + d: 13, + w: 14, + 0: 15, + s: 16, + 3: 17, + j: 18, + n: 19, + 5: 20, + 4: 21, + k: 22, + h: 23, + c: 24, + e: 25, + 6: 26, + m: 27, + u: 28, + a: 29, + 7: 30, + l: 31 +}; function fromUint5Array(data) { - return convertBits(data, 5, 8, true); + return convertBits(data, 5, 8, true); } function getType(versionByte) { - switch (versionByte & 120) { - case 0: - return 'P2PKH'; - case 8: - return 'P2SH'; - default: - throw new Error('Invalid address type in version byte: ' + versionByte + '.'); - } + switch (versionByte & 120) { + case 0: + return 'P2PKH'; + case 8: + return 'P2SH'; + default: + throw new Error( + 'Invalid address type in version byte: ' + versionByte + '.' + ); + } } function base32decode(string) { - const data = new Uint8Array(string.length) - for (let i = 0; i < string.length; ++i) { - const value = string[i] - data[i] = CHARSET_INVERSE_INDEX[value] - } - return data + const data = new Uint8Array(string.length); + for (let i = 0; i < string.length; ++i) { + const value = string[i]; + data[i] = CHARSET_INVERSE_INDEX[value]; + } + return data; } - // for bitcoincash -const createHash = require('create-hash') - -const GENERATOR1 = [0x98, 0x79, 0xf3, 0xae, 0x1e] -const GENERATOR2 = [0xf2bc8e61, 0xb76d99e2, 0x3e5fb3c4, 0x2eabe2a8, 0x4f43e470] +const GENERATOR1 = [0x98, 0x79, 0xf3, 0xae, 0x1e]; +const GENERATOR2 = [0xf2bc8e61, 0xb76d99e2, 0x3e5fb3c4, 0x2eabe2a8, 0x4f43e470]; -const CHARSET = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l' +const CHARSET = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l'; function base32encode(data) { - let base32 = '' - for (let i = 0; i < data.length; i++) { - const value = data[i] - base32 += CHARSET[value] - } - return base32 + let base32 = ''; + for (let i = 0; i < data.length; i++) { + const value = data[i]; + base32 += CHARSET[value]; + } + return base32; } - function polymod(data) { - // Treat c as 8 bits + 32 bits - let c0 = 0 - let c1 = 1 - let C = 0 - for (let j = 0; j < data.length; j++) { - // Set C to c shifted by 35 - C = c0 >>> 3 - // 0x[07]ffffffff - c0 &= 0x07 - // Shift as a whole number - c0 <<= 5 - c0 |= c1 >>> 27 - // 0xffffffff >>> 5 - c1 &= 0x07ffffff - c1 <<= 5 - // xor the last 5 bits - c1 ^= data[j] - for (let i = 0; i < GENERATOR1.length; ++i) { - if (C & (1 << i)) { - c0 ^= GENERATOR1[i] - c1 ^= GENERATOR2[i] - } - } + // Treat c as 8 bits + 32 bits + let c0 = 0; + let c1 = 1; + let C = 0; + for (let j = 0; j < data.length; j++) { + // Set C to c shifted by 35 + C = c0 >>> 3; + // 0x[07]ffffffff + c0 &= 0x07; + // Shift as a whole number + c0 <<= 5; + c0 |= c1 >>> 27; + // 0xffffffff >>> 5 + c1 &= 0x07ffffff; + c1 <<= 5; + // xor the last 5 bits + c1 ^= data[j]; + for (let i = 0; i < GENERATOR1.length; ++i) { + if (C & (1 << i)) { + c0 ^= GENERATOR1[i]; + c1 ^= GENERATOR2[i]; + } } - c1 ^= 1 - // Negative numbers -> large positive numbers - if (c1 < 0) { - c1 ^= 1 << 31 - c1 += (1 << 30) * 2 - } - // Unless bitwise operations are used, - // numbers are consisting of 52 bits, except - // the sign bit. The result is max 40 bits, - // so it fits perfectly in one number! - return c0 * (1 << 30) * 4 + c1 + } + c1 ^= 1; + // Negative numbers -> large positive numbers + if (c1 < 0) { + c1 ^= 1 << 31; + c1 += (1 << 30) * 2; + } + // Unless bitwise operations are used, + // numbers are consisting of 52 bits, except + // the sign bit. The result is max 40 bits, + // so it fits perfectly in one number! + return c0 * (1 << 30) * 4 + c1; } function checksumToArray(checksum) { - const result = [] - for (let i = 0; i < 8; ++i) { - result.push(checksum & 31) - checksum /= 32 - } - return result.reverse() + const result = []; + for (let i = 0; i < 8; ++i) { + result.push(checksum & 31); + checksum /= 32; + } + return result.reverse(); } function getHashSizeBits(hash) { - switch (hash.length * 8) { - case 160: - return 0 - case 192: - return 1 - case 224: - return 2 - case 256: - return 3 - case 320: - return 4 - case 384: - return 5 - case 448: - return 6 - case 512: - return 7 - default: - throw new Error('Invalid hash size:' + hash.length) - } + switch (hash.length * 8) { + case 160: + return 0; + case 192: + return 1; + case 224: + return 2; + case 256: + return 3; + case 320: + return 4; + case 384: + return 5; + case 448: + return 6; + case 512: + return 7; + default: + throw new Error('Invalid hash size:' + hash.length); + } } function convertBits(data, from, to, strict) { - strict = strict || false - let accumulator = 0 - let bits = 0 - const result = [] - const mask = (1 << to) - 1 - for (let i = 0; i < data.length; i++) { - const value = data[i] - accumulator = (accumulator << from) | value - bits += from - while (bits >= to) { - bits -= to - result.push((accumulator >> bits) & mask) - } + strict = strict || false; + let accumulator = 0; + let bits = 0; + const result = []; + const mask = (1 << to) - 1; + for (let i = 0; i < data.length; i++) { + const value = data[i]; + accumulator = (accumulator << from) | value; + bits += from; + while (bits >= to) { + bits -= to; + result.push((accumulator >> bits) & mask); } - if (!strict) { - if (bits > 0) { - result.push((accumulator << (to - bits)) & mask) - } + } + if (!strict) { + if (bits > 0) { + result.push((accumulator << (to - bits)) & mask); } - return result + } + return result; } function fromHashToAddress(hash) { - const eight0 = [0, 0, 0, 0, 0, 0, 0, 0] - // noinspection PointlessArithmeticExpressionJS - const versionByte = 0 + getHashSizeBits(hash) // getTypeBits(this.type) - const arr = Array.prototype.slice.call(hash, 0) - const payloadData = convertBits([versionByte].concat(arr), 8, 5) - - const prefixData = [2, 9, 20, 3, 15, 9, 14, 3, 1, 19, 8, 0] - const checksumData = prefixData.concat(payloadData).concat(eight0) - const payload = payloadData.concat(checksumToArray(polymod(checksumData))) - return base32encode(payload) + const eight0 = [0, 0, 0, 0, 0, 0, 0, 0]; + // noinspection PointlessArithmeticExpressionJS + const versionByte = 0 + getHashSizeBits(hash); // getTypeBits(this.type) + const arr = Array.prototype.slice.call(hash, 0); + const payloadData = convertBits([versionByte].concat(arr), 8, 5); + + const prefixData = [2, 9, 20, 3, 15, 9, 14, 3, 1, 19, 8, 0]; + const checksumData = prefixData.concat(payloadData).concat(eight0); + const payload = payloadData.concat(checksumToArray(polymod(checksumData))); + return base32encode(payload); } export default { - fromPublicKeyToAddress(publicKey) { - const one = createHash('sha256').update(publicKey, 'hex').digest() - const hash = createHash('ripemd160').update(one).digest() - return fromHashToAddress(hash) - }, - toLegacyAddress(address) { - if (address.indexOf('bitcoincash:') === 0) { - address = address.substr(12) - } - if (address.substr(0, 1) !== 'q' && address.substr(0,1) !== 'p') { - return address - } - const payloadBack = base32decode(address) - const payloadDataBack = fromUint5Array(payloadBack.subarray(0, -8)); - const versionByteBack = payloadDataBack[0]; - const hashBack = payloadDataBack.slice(1); - const typeBack = getType(versionByteBack); - const buffer = Buffer.alloc(1 + hashBack.length) - buffer[0] = VERSION_BYTE[typeBack] - buffer.set(hashBack, 1) - return bs58check.encode(buffer) - }, - fromLegacyAddress(address) { - if (!address || address === '') { - return '' - } - if (address.substr(0, 1) === 'q') { - return address - } - let hash = bs58check.decode(address) - hash = hash.subarray(1) - return fromHashToAddress(hash) + fromPublicKeyToAddress(publicKey) { + const one = createHash('sha256').update(publicKey, 'hex').digest(); + const hash = createHash('ripemd160').update(one).digest(); + return fromHashToAddress(hash); + }, + toLegacyAddress(address) { + if (address.indexOf('bitcoincash:') === 0) { + address = address.substr(12); } -} + if (address.substr(0, 1) !== 'q' && address.substr(0, 1) !== 'p') { + return address; + } + const payloadBack = base32decode(address); + const payloadDataBack = fromUint5Array(payloadBack.subarray(0, -8)); + const versionByteBack = payloadDataBack[0]; + const hashBack = payloadDataBack.slice(1); + const typeBack = getType(versionByteBack); + const buffer = Buffer.alloc(1 + hashBack.length); + buffer[0] = VERSION_BYTE[typeBack]; + buffer.set(hashBack, 1); + return bs58check.encode(buffer); + }, + fromLegacyAddress(address) { + if (!address || address === '') { + return ''; + } + if (address.substr(0, 1) === 'q') { + return address; + } + let hash = bs58check.decode(address); + hash = hash.subarray(1); + return fromHashToAddress(hash); + } +}; diff --git a/crypto/blockchains/bch/providers/BchSendProvider.ts b/crypto/blockchains/bch/providers/BchSendProvider.ts index fa98f91e9..a5d2c30ac 100644 --- a/crypto/blockchains/bch/providers/BchSendProvider.ts +++ b/crypto/blockchains/bch/providers/BchSendProvider.ts @@ -1,14 +1,14 @@ /** * @version 0.20 */ -import { BlocksoftBlockchainTypes } from '../../BlocksoftBlockchainTypes'; +import { AirDAOBlockchainTypes } from '../../AirDAOBlockchainTypes'; import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; import BlocksoftAxios from '../../../common/BlocksoftAxios'; import DogeSendProvider from '../../doge/providers/DogeSendProvider'; export default class BchSendProvider extends DogeSendProvider - implements BlocksoftBlockchainTypes.SendProvider + implements AirDAOBlockchainTypes.SendProvider { _apiPath = 'https://rest.bitcoin.com/v2/rawtransactions/sendRawTransaction/'; @@ -31,7 +31,8 @@ export default class BchSendProvider if (trezor) { return trezor; } - } catch (e) { + } catch (error) { + const e = error as unknown as any; if (e.message.indexOf('SERVER_RESPONSE_') !== -1) { throw e; } else { @@ -41,8 +42,9 @@ export default class BchSendProvider let res; try { - res = await BlocksoftAxios.get(this._apiPath + hex); - } catch (e) { + res = (await BlocksoftAxios.get(this._apiPath + hex)) as any; + } catch (error) { + const e = error as unknown as any; if (e.message.indexOf('dust') !== -1) { throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_DUST'); } else if ( diff --git a/crypto/blockchains/bch/providers/BchUnspentsProvider.ts b/crypto/blockchains/bch/providers/BchUnspentsProvider.ts index 39504ebe7..1fe38b96f 100644 --- a/crypto/blockchains/bch/providers/BchUnspentsProvider.ts +++ b/crypto/blockchains/bch/providers/BchUnspentsProvider.ts @@ -4,60 +4,73 @@ * https://developer.bitcoin.com/rest/docs/address * https://rest.bitcoin.com/v2/address/utxo/bitcoincash:qz6qh4304stgwpqxp6gwsucma30fewp7z5cs4yuvdf */ -import { BlocksoftBlockchainTypes } from '../../BlocksoftBlockchainTypes' -import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog' -import BlocksoftAxios from '../../../common/BlocksoftAxios' -import BtcCashUtils from '../ext/BtcCashUtils' -import DogeUnspentsProvider from '../../doge/providers/DogeUnspentsProvider' +import { AirDAOBlockchainTypes } from '../../AirDAOBlockchainTypes'; +import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; +import BlocksoftAxios from '../../../common/BlocksoftAxios'; +import BtcCashUtils from '../ext/BtcCashUtils'; +import DogeUnspentsProvider from '../../doge/providers/DogeUnspentsProvider'; -export default class BchUnspentsProvider extends DogeUnspentsProvider implements BlocksoftBlockchainTypes.UnspentsProvider { +export default class BchUnspentsProvider + extends DogeUnspentsProvider + implements AirDAOBlockchainTypes.UnspentsProvider +{ + _apiPath = 'https://rest.bitcoin.com/v2/address/utxo/bitcoincash:'; - _apiPath = 'https://rest.bitcoin.com/v2/address/utxo/bitcoincash:' - - async getUnspents(address: string): Promise { - try { - const trezor = await super.getUnspents('bitcoincash:' + address) - if (trezor && trezor.length > 0) { - return trezor - } - } catch (e) { - // do nothing - } - // @ts-ignore - BlocksoftCryptoLog.log(this._settings.currencyCode + ' BchUnspentsProvider.getUnspents started', address) - address = BtcCashUtils.fromLegacyAddress(address) - const link = this._apiPath + address - const res = await BlocksoftAxios.getWithoutBraking(link) - if (!res || !res.data || typeof res.data === 'undefined') { - BlocksoftCryptoLog.log(this._settings.currencyCode + ' BchUnspentsProvider.getUnspents nothing loaded for address ' + address + ' link ' + link) - throw new Error('SERVER_RESPONSE_NOT_CONNECTED') - } - if (!res.data || typeof res.data.utxos === 'undefined' || !res.data.utxos) { - return [] - } - const sortedUnspents = [] - /** - * https://rest.bitcoin.com/v2/address/utxo/bitcoincash:qz6qh4304stgwpqxp6gwsucma30fewp7z5cs4yuvdf - * @param {*} res.data.utxos[] - * @param {string} res.data.utxos[].txid 5be83026d82b56e8df7fa309e0b50132cb5cac228f83103532b20e0c991a3f9b - * @param {string} res.data.utxos[].vout 1 - * @param {string} res.data.utxos[].amount 0.04373313 - * @param {string} res.data.utxos[].satoshis 4373313 - * @param {string} res.data.utxos[].height 615754 - * @param {string} res.data.utxos[].confirmations - */ - let unspent - for (unspent of res.data.utxos) { - sortedUnspents.push({ - txid: unspent.txid, - vout: unspent.vout, - value: unspent.satoshis.toString(), - height: unspent.height, - confirmations : unspent.confirmations, - isRequired : false - }) - } - - return sortedUnspents + async getUnspents( + address: string + ): Promise { + try { + const trezor = await super.getUnspents('bitcoincash:' + address); + if (trezor && trezor.length > 0) { + return trezor; + } + } catch (e) { + // do nothing + } + // TODO check if log is necessary @ts-ignore + // BlocksoftCryptoLog.log( + // this._settings.currencyCode + ' BchUnspentsProvider.getUnspents started', + // address + // ); + address = BtcCashUtils.fromLegacyAddress(address); + const link = this._apiPath + address; + const res = (await BlocksoftAxios.getWithoutBraking(link)) as any; + if (!res || !res.data || typeof res.data === 'undefined') { + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' BchUnspentsProvider.getUnspents nothing loaded for address ' + + address + + ' link ' + + link + ); + throw new Error('SERVER_RESPONSE_NOT_CONNECTED'); } + if (!res.data || typeof res.data.utxos === 'undefined' || !res.data.utxos) { + return []; + } + const sortedUnspents = []; + /** + * https://rest.bitcoin.com/v2/address/utxo/bitcoincash:qz6qh4304stgwpqxp6gwsucma30fewp7z5cs4yuvdf + * @param {*} res.data.utxos[] + * @param {string} res.data.utxos[].txid 5be83026d82b56e8df7fa309e0b50132cb5cac228f83103532b20e0c991a3f9b + * @param {string} res.data.utxos[].vout 1 + * @param {string} res.data.utxos[].amount 0.04373313 + * @param {string} res.data.utxos[].satoshis 4373313 + * @param {string} res.data.utxos[].height 615754 + * @param {string} res.data.utxos[].confirmations + */ + let unspent; + for (unspent of res.data.utxos) { + sortedUnspents.push({ + txid: unspent.txid, + vout: unspent.vout, + value: unspent.satoshis.toString(), + height: unspent.height, + confirmations: unspent.confirmations, + isRequired: false + }); + } + + return sortedUnspents; + } } diff --git a/crypto/blockchains/bch/tx/BchTxBuilder.ts b/crypto/blockchains/bch/tx/BchTxBuilder.ts index e7e64392a..1bd9f5fa0 100644 --- a/crypto/blockchains/bch/tx/BchTxBuilder.ts +++ b/crypto/blockchains/bch/tx/BchTxBuilder.ts @@ -1,18 +1,18 @@ /** * @version 0.20 */ -import { BlocksoftBlockchainTypes } from '../../BlocksoftBlockchainTypes'; +import { AirDAOBlockchainTypes } from '../../AirDAOBlockchainTypes'; import DogeTxBuilder from '../../doge/tx/DogeTxBuilder'; import BtcCashUtils from '../ext/BtcCashUtils'; import bitcoin, { TransactionBuilder } from 'bitcoinjs-lib'; export default class BchTxBuilder extends DogeTxBuilder - implements BlocksoftBlockchainTypes.TxBuilder + implements AirDAOBlockchainTypes.TxBuilder { _getRawTxValidateKeyPair( - privateData: BlocksoftBlockchainTypes.TransferPrivateData, - data: BlocksoftBlockchainTypes.TransferData + privateData: AirDAOBlockchainTypes.TransferPrivateData, + data: AirDAOBlockchainTypes.TransferData ): void { this.keyPair = false; try { @@ -45,7 +45,7 @@ export default class BchTxBuilder _getRawTxAddOutput( txb: TransactionBuilder, - output: BlocksoftBlockchainTypes.OutputTx + output: AirDAOBlockchainTypes.OutputTx ): void { const to = BtcCashUtils.toLegacyAddress(output.to); txb.addOutput(to, Number(output.amount)); diff --git a/crypto/blockchains/btc/providers/BtcUnspentsProvider.ts b/crypto/blockchains/btc/providers/BtcUnspentsProvider.ts index dc20dc761..ae3fbd976 100644 --- a/crypto/blockchains/btc/providers/BtcUnspentsProvider.ts +++ b/crypto/blockchains/btc/providers/BtcUnspentsProvider.ts @@ -3,205 +3,327 @@ * https://github.com/trezor/blockbook/blob/master/docs/api.md * https://doge1.trezor.io/api/v2/utxo/D5oKvWEibVe74CXLASmhpkRpLoyjgZhm71 */ -import { BlocksoftBlockchainTypes } from '../../BlocksoftBlockchainTypes' -import DogeUnspentsProvider from '../../doge/providers/DogeUnspentsProvider' +import { BlocksoftBlockchainTypes } from '../../BlocksoftBlockchainTypes'; +import DogeUnspentsProvider from '../../doge/providers/DogeUnspentsProvider'; import Database from '@app/appstores/DataSource/Database'; -import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog' -import BlocksoftDict from '@crypto/common/BlocksoftDict' -import main from '@app/appstores/DataSource/Database' +import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; +import BlocksoftDict from '@crypto/common/BlocksoftDict'; +import main from '@app/appstores/DataSource/Database'; -const CACHE_FOR_CHANGE = {} +const CACHE_FOR_CHANGE = {}; -export default class BtcUnspentsProvider extends DogeUnspentsProvider implements BlocksoftBlockchainTypes.UnspentsProvider { - - static async getCache(walletHash : string, currencyCode = 'BTC') { - if (typeof CACHE_FOR_CHANGE[walletHash] !== 'undefined') { - return CACHE_FOR_CHANGE[walletHash] - } - const mainCurrencyCode = currencyCode === 'LTC' ? 'LTC' : 'BTC' - const segwitPrefix = BlocksoftDict.CurrenciesForTests[mainCurrencyCode + '_SEGWIT'].addressPrefix +export default class BtcUnspentsProvider + extends DogeUnspentsProvider + implements BlocksoftBlockchainTypes.UnspentsProvider +{ + static async getCache(walletHash: string, currencyCode = 'BTC') { + if (typeof CACHE_FOR_CHANGE[walletHash] !== 'undefined') { + return CACHE_FOR_CHANGE[walletHash]; + } + const mainCurrencyCode = currencyCode === 'LTC' ? 'LTC' : 'BTC'; + const segwitPrefix = + BlocksoftDict.CurrenciesForTests[mainCurrencyCode + '_SEGWIT'] + .addressPrefix; - BlocksoftCryptoLog.log(currencyCode + ' ' + mainCurrencyCode + ' BtcUnspentsProvider.getCache ' + walletHash + ' started as ' + JSON.stringify(CACHE_FOR_CHANGE[walletHash])) + BlocksoftCryptoLog.log( + currencyCode + + ' ' + + mainCurrencyCode + + ' BtcUnspentsProvider.getCache ' + + walletHash + + ' started as ' + + JSON.stringify(CACHE_FOR_CHANGE[walletHash]) + ); - const sqlPub = `SELECT wallet_pub_value as walletPub + const sqlPub = `SELECT wallet_pub_value as walletPub FROM wallet_pub WHERE wallet_hash = '${walletHash} AND currency_code='${mainCurrencyCode}' - ` - const resPub = await Database.query(sqlPub) - if (resPub && resPub.array && resPub.array.length > 0) { - - const sql = `SELECT account.address + `; + const resPub = await Database.query(sqlPub); + if (resPub && resPub.array && resPub.array.length > 0) { + const sql = `SELECT account.address FROM account WHERE account.wallet_hash = '${walletHash} AND currency_code='${mainCurrencyCode}' AND (already_shown IS NULL OR already_shown=0) AND derivation_type!='main' ORDER BY derivation_index ASC - ` - const res = await Database.query(sql) - for (const row of res.array) { - const prefix = row.address.indexOf(segwitPrefix) === 0 ? segwitPrefix : row.address.substr(0, 1) - await BlocksoftCryptoLog.log(currencyCode + ' ' + mainCurrencyCode + ' BtcUnspentsProvider.getCache started HD CACHE_FOR_CHANGE ' + walletHash) - // @ts-ignore - if (typeof CACHE_FOR_CHANGE[walletHash] === 'undefined') { - // @ts-ignore - CACHE_FOR_CHANGE[walletHash] = {} - } - // @ts-ignore - if (typeof CACHE_FOR_CHANGE[walletHash][prefix] === 'undefined' || CACHE_FOR_CHANGE[walletHash][prefix] === '') { - // @ts-ignore - CACHE_FOR_CHANGE[walletHash][prefix] = row.address - // @ts-ignore - await BlocksoftCryptoLog.log(currencyCode + ' ' + mainCurrencyCode + ' BtcUnspentsProvider.getCache started HD CACHE_FOR_CHANGE ' - + walletHash + ' ' + prefix + ' changed ' + JSON.stringify(CACHE_FOR_CHANGE[walletHash])) - } - } - - } else { - - const sql = `SELECT account.address + `; + const res = await Database.query(sql); + for (const row of res.array) { + const prefix = + row.address.indexOf(segwitPrefix) === 0 + ? segwitPrefix + : row.address.substr(0, 1); + await BlocksoftCryptoLog.log( + currencyCode + + ' ' + + mainCurrencyCode + + ' BtcUnspentsProvider.getCache started HD CACHE_FOR_CHANGE ' + + walletHash + ); + // @ts-ignore + if (typeof CACHE_FOR_CHANGE[walletHash] === 'undefined') { + // @ts-ignore + CACHE_FOR_CHANGE[walletHash] = {}; + } + // @ts-ignore + if ( + typeof CACHE_FOR_CHANGE[walletHash][prefix] === 'undefined' || + CACHE_FOR_CHANGE[walletHash][prefix] === '' + ) { + // @ts-ignore + CACHE_FOR_CHANGE[walletHash][prefix] = row.address; + // @ts-ignore + await BlocksoftCryptoLog.log( + currencyCode + + ' ' + + mainCurrencyCode + + ' BtcUnspentsProvider.getCache started HD CACHE_FOR_CHANGE ' + + walletHash + + ' ' + + prefix + + ' changed ' + + JSON.stringify(CACHE_FOR_CHANGE[walletHash]) + ); + } + } + } else { + const sql = `SELECT account.address FROM account WHERE account.wallet_hash = '${walletHash}' AND currency_code='${mainCurrencyCode}' - ` - const res = await Database.query(sql) - for (const row of res.array) { - // @ts-ignore - await BlocksoftCryptoLog.log(currencyCode + '/' + mainCurrencyCode + ' BtcUnspentsProvider.getUnspents started CACHE_FOR_CHANGE ' + walletHash) - if (typeof CACHE_FOR_CHANGE[walletHash] === 'undefined') { - // @ts-ignore - CACHE_FOR_CHANGE[walletHash] = {} - } - const prefix = row.address.indexOf(segwitPrefix) === 0 ? segwitPrefix : row.address.substr(0, 1) - // @ts-ignore - CACHE_FOR_CHANGE[walletHash][prefix] = row.address - } - } + `; + const res = await Database.query(sql); + for (const row of res.array) { + // @ts-ignore + await BlocksoftCryptoLog.log( + currencyCode + + '/' + + mainCurrencyCode + + ' BtcUnspentsProvider.getUnspents started CACHE_FOR_CHANGE ' + + walletHash + ); if (typeof CACHE_FOR_CHANGE[walletHash] === 'undefined') { - throw new Error(currencyCode + '/' + mainCurrencyCode + ' BtcUnspentsProvider no CACHE_FOR_CHANGE retry for ' + walletHash) + // @ts-ignore + CACHE_FOR_CHANGE[walletHash] = {}; } - return CACHE_FOR_CHANGE[walletHash] + const prefix = + row.address.indexOf(segwitPrefix) === 0 + ? segwitPrefix + : row.address.substr(0, 1); + // @ts-ignore + CACHE_FOR_CHANGE[walletHash][prefix] = row.address; + } + } + if (typeof CACHE_FOR_CHANGE[walletHash] === 'undefined') { + throw new Error( + currencyCode + + '/' + + mainCurrencyCode + + ' BtcUnspentsProvider no CACHE_FOR_CHANGE retry for ' + + walletHash + ); } + return CACHE_FOR_CHANGE[walletHash]; + } - _isMyAddress(voutAddress: string, address: string, walletHash: string): string { - // @ts-ignore - if (typeof CACHE_FOR_CHANGE[walletHash] === 'undefined' || !CACHE_FOR_CHANGE[walletHash]) { - return '' - } - // @ts-ignore - let found = '' - for (const key in CACHE_FOR_CHANGE[walletHash]) { - BlocksoftCryptoLog.log('CACHE_FOR_CHANGE[walletHash][key]', key + '_' + CACHE_FOR_CHANGE[walletHash][key]) - if (voutAddress === CACHE_FOR_CHANGE[walletHash][key]) { - found = voutAddress - } - } - return found + _isMyAddress( + voutAddress: string, + address: string, + walletHash: string + ): string { + // @ts-ignore + if ( + typeof CACHE_FOR_CHANGE[walletHash] === 'undefined' || + !CACHE_FOR_CHANGE[walletHash] + ) { + return ''; } + // @ts-ignore + let found = ''; + for (const key in CACHE_FOR_CHANGE[walletHash]) { + BlocksoftCryptoLog.log( + 'CACHE_FOR_CHANGE[walletHash][key]', + key + '_' + CACHE_FOR_CHANGE[walletHash][key] + ); + if (voutAddress === CACHE_FOR_CHANGE[walletHash][key]) { + found = voutAddress; + } + } + return found; + } - async getUnspents(address: string): Promise { - const mainCurrencyCode = this._settings.currencyCode === 'LTC' ? 'LTC' : 'BTC' - const segwitPrefix = BlocksoftDict.CurrenciesForTests[mainCurrencyCode + '_SEGWIT'].addressPrefix + async getUnspents( + address: string + ): Promise { + const mainCurrencyCode = + this._settings.currencyCode === 'LTC' ? 'LTC' : 'BTC'; + const segwitPrefix = + BlocksoftDict.CurrenciesForTests[mainCurrencyCode + '_SEGWIT'] + .addressPrefix; - const sqlPub = `SELECT wallet_pub_value as walletPub + const sqlPub = `SELECT wallet_pub_value as walletPub FROM wallet_pub WHERE wallet_hash = (SELECT wallet_hash FROM account WHERE address='${address}') AND currency_code='${mainCurrencyCode}' - ` - const totalUnspents = [] - const resPub = await Database.query(sqlPub) - if (resPub && resPub.array && resPub.array.length > 0) { - for (const row of resPub.array) { - const unspents = await super.getUnspents(row.walletPub) - if (unspents) { - for (const unspent of unspents) { - totalUnspents.push(unspent) - } - } - } - const sqlAdditional = `SELECT account.address, account.derivation_path as derivationPath, wallet_hash AS walletHash + `; + const totalUnspents = []; + const resPub = await Database.query(sqlPub); + if (resPub && resPub.array && resPub.array.length > 0) { + for (const row of resPub.array) { + const unspents = await super.getUnspents(row.walletPub); + if (unspents) { + for (const unspent of unspents) { + totalUnspents.push(unspent); + } + } + } + const sqlAdditional = `SELECT account.address, account.derivation_path as derivationPath, wallet_hash AS walletHash FROM account WHERE account.wallet_hash = (SELECT wallet_hash FROM account WHERE address='${address}') AND account.derivation_path = 'm/49quote/0quote/0/1/0' AND currency_code='${mainCurrencyCode}' - ` - const resAdditional = await Database.query(sqlAdditional) - if (resAdditional && resAdditional.array && resAdditional.array.length > 0) { - for (const row of resAdditional.array) { - const unspents = await super.getUnspents(row.address) - if (unspents) { - for (const unspent of unspents) { - unspent.address = row.address - unspent.derivationPath = Database.unEscapeString(row.derivationPath) - totalUnspents.push(unspent) - } - } - } + `; + const resAdditional = await Database.query(sqlAdditional); + if ( + resAdditional && + resAdditional.array && + resAdditional.array.length > 0 + ) { + for (const row of resAdditional.array) { + const unspents = await super.getUnspents(row.address); + if (unspents) { + for (const unspent of unspents) { + unspent.address = row.address; + unspent.derivationPath = Database.unEscapeString( + row.derivationPath + ); + totalUnspents.push(unspent); } + } + } + } - const sql = `SELECT account.address, account.derivation_path as derivationPath, wallet_hash AS walletHash + const sql = `SELECT account.address, account.derivation_path as derivationPath, wallet_hash AS walletHash FROM account WHERE account.wallet_hash = (SELECT wallet_hash FROM account WHERE address='${address}') AND currency_code='${mainCurrencyCode}' AND (already_shown IS NULL OR already_shown=0) AND derivation_type!='main' ORDER BY derivation_index ASC - ` - const res = await Database.query(sql) - for (const row of res.array) { - const walletHash = row.walletHash - const prefix = row.address.indexOf(segwitPrefix) === 0 ? segwitPrefix : row.address.substr(0, 1) - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' ' + mainCurrencyCode + ' BtcUnspentsProvider.getUnspents started HD CACHE_FOR_CHANGE ' + address + ' walletHash ' + walletHash) - // @ts-ignore - if (typeof CACHE_FOR_CHANGE[walletHash] === 'undefined') { - // @ts-ignore - CACHE_FOR_CHANGE[walletHash] = {} - } - // @ts-ignore - if (typeof CACHE_FOR_CHANGE[walletHash][prefix] === 'undefined' || CACHE_FOR_CHANGE[walletHash][prefix] === '') { - // @ts-ignore - CACHE_FOR_CHANGE[walletHash][prefix] = row.address - // @ts-ignore - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' ' + mainCurrencyCode + ' BtcUnspentsProvider.getUnspents started HD CACHE_FOR_CHANGE ' - + address + ' walletHash ' + walletHash + ' ' + prefix + ' changed ' + JSON.stringify(CACHE_FOR_CHANGE[walletHash])) - } - } - - } else { - - const sql = `SELECT account.address, account.derivation_path as derivationPath, wallet_hash AS walletHash + `; + const res = await Database.query(sql); + for (const row of res.array) { + const walletHash = row.walletHash; + const prefix = + row.address.indexOf(segwitPrefix) === 0 + ? segwitPrefix + : row.address.substr(0, 1); + await BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' ' + + mainCurrencyCode + + ' BtcUnspentsProvider.getUnspents started HD CACHE_FOR_CHANGE ' + + address + + ' walletHash ' + + walletHash + ); + // @ts-ignore + if (typeof CACHE_FOR_CHANGE[walletHash] === 'undefined') { + // @ts-ignore + CACHE_FOR_CHANGE[walletHash] = {}; + } + // @ts-ignore + if ( + typeof CACHE_FOR_CHANGE[walletHash][prefix] === 'undefined' || + CACHE_FOR_CHANGE[walletHash][prefix] === '' + ) { + // @ts-ignore + CACHE_FOR_CHANGE[walletHash][prefix] = row.address; + // @ts-ignore + await BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' ' + + mainCurrencyCode + + ' BtcUnspentsProvider.getUnspents started HD CACHE_FOR_CHANGE ' + + address + + ' walletHash ' + + walletHash + + ' ' + + prefix + + ' changed ' + + JSON.stringify(CACHE_FOR_CHANGE[walletHash]) + ); + } + } + } else { + const sql = `SELECT account.address, account.derivation_path as derivationPath, wallet_hash AS walletHash FROM account WHERE account.wallet_hash = (SELECT wallet_hash FROM account WHERE address='${address}') AND currency_code='${mainCurrencyCode}' - ` - const res = await Database.query(sql) - for (const row of res.array) { - const walletHash = row.walletHash - const unspents = await super.getUnspents(row.address) - // @ts-ignore - await BlocksoftCryptoLog.log(this._settings.currencyCode + '/' + mainCurrencyCode + ' BtcUnspentsProvider.getUnspents started CACHE_FOR_CHANGE ' + address + ' ' + row.address + ' walletHash ' + walletHash) - if (typeof CACHE_FOR_CHANGE[walletHash] === 'undefined') { - // @ts-ignore - CACHE_FOR_CHANGE[walletHash] = {} - } - const prefix = row.address.indexOf(segwitPrefix) === 0 ? segwitPrefix : row.address.substr(0, 1) - // @ts-ignore - CACHE_FOR_CHANGE[walletHash][prefix] = row.address - if (unspents) { - for (const unspent of unspents) { - unspent.address = row.address - unspent.derivationPath = Database.unEscapeString(row.derivationPath) - totalUnspents.push(unspent) - } - } - } + `; + const res = await Database.query(sql); + for (const row of res.array) { + const walletHash = row.walletHash; + const unspents = await super.getUnspents(row.address); + // @ts-ignore + await BlocksoftCryptoLog.log( + this._settings.currencyCode + + '/' + + mainCurrencyCode + + ' BtcUnspentsProvider.getUnspents started CACHE_FOR_CHANGE ' + + address + + ' ' + + row.address + + ' walletHash ' + + walletHash + ); + if (typeof CACHE_FOR_CHANGE[walletHash] === 'undefined') { + // @ts-ignore + CACHE_FOR_CHANGE[walletHash] = {}; } + const prefix = + row.address.indexOf(segwitPrefix) === 0 + ? segwitPrefix + : row.address.substr(0, 1); // @ts-ignore - if (totalUnspents.length > 10) { - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' ' + mainCurrencyCode + ' BtcUnspentsProvider.getUnspents finished ' + address + ' total ' + totalUnspents.length, totalUnspents.slice(0, 10)) - } else { - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' ' + mainCurrencyCode + ' BtcUnspentsProvider.getUnspents finished ' + address + ' total ' + totalUnspents.length, totalUnspents) + CACHE_FOR_CHANGE[walletHash][prefix] = row.address; + if (unspents) { + for (const unspent of unspents) { + unspent.address = row.address; + unspent.derivationPath = Database.unEscapeString( + row.derivationPath + ); + totalUnspents.push(unspent); + } } - return totalUnspents + } + } + // @ts-ignore + if (totalUnspents.length > 10) { + await BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' ' + + mainCurrencyCode + + ' BtcUnspentsProvider.getUnspents finished ' + + address + + ' total ' + + totalUnspents.length, + totalUnspents.slice(0, 10) + ); + } else { + await BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' ' + + mainCurrencyCode + + ' BtcUnspentsProvider.getUnspents finished ' + + address + + ' total ' + + totalUnspents.length, + totalUnspents + ); } + return totalUnspents; + } } diff --git a/crypto/blockchains/btc/tx/BtcTxBuilder.ts b/crypto/blockchains/btc/tx/BtcTxBuilder.ts index 8eae14e40..69142cb8b 100644 --- a/crypto/blockchains/btc/tx/BtcTxBuilder.ts +++ b/crypto/blockchains/btc/tx/BtcTxBuilder.ts @@ -1,161 +1,234 @@ /** * @version 0.20 */ -import { BlocksoftBlockchainTypes } from '../../BlocksoftBlockchainTypes' -import DogeTxBuilder from '../../doge/tx/DogeTxBuilder' -import BlocksoftPrivateKeysUtils from '../../../common/BlocksoftPrivateKeysUtils' -import { ECPair, payments, TransactionBuilder } from 'bitcoinjs-lib' -import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog' -import BlocksoftDict from '@crypto/common/BlocksoftDict' -import main from '@app/appstores/DataSource/Database' +import { BlocksoftBlockchainTypes } from '../../BlocksoftBlockchainTypes'; +import DogeTxBuilder from '../../doge/tx/DogeTxBuilder'; +import BlocksoftPrivateKeysUtils from '../../../common/BlocksoftPrivateKeysUtils'; +import { ECPair, payments, TransactionBuilder } from 'bitcoinjs-lib'; +import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; +import BlocksoftDict from '@crypto/common/BlocksoftDict'; +import main from '@app/appstores/DataSource/Database'; -export default class BtcTxBuilder extends DogeTxBuilder implements BlocksoftBlockchainTypes.TxBuilder { - private mnemonic: string = '' - private walletHash : string = '' - private keyPairBTC : any = {} - private p2wpkhBTC: any = {} - private p2shBTC: any ={} +export default class BtcTxBuilder + extends DogeTxBuilder + implements BlocksoftBlockchainTypes.TxBuilder +{ + private mnemonic: string = ''; + private walletHash: string = ''; + private keyPairBTC: any = {}; + private p2wpkhBTC: any = {}; + private p2shBTC: any = {}; + _getRawTxValidateKeyPair( + privateData: BlocksoftBlockchainTypes.TransferPrivateData, + data: BlocksoftBlockchainTypes.TransferData + ): void { + if (this.mnemonic === privateData.privateKey) return; - _getRawTxValidateKeyPair(privateData: BlocksoftBlockchainTypes.TransferPrivateData, data: BlocksoftBlockchainTypes.TransferData): void { - if (this.mnemonic === privateData.privateKey) return + this.mnemonic = privateData.privateKey; + this.walletHash = data.walletHash; + this.keyPairBTC = {}; + this.p2wpkhBTC = {}; + this.p2shBTC = {}; + } - this.mnemonic = privateData.privateKey - this.walletHash = data.walletHash - this.keyPairBTC = {} - this.p2wpkhBTC = {} - this.p2shBTC = {} + async _getRawTxAddInput( + txb: TransactionBuilder, + i: number, + input: BlocksoftBlockchainTypes.UnspentTx, + nSequence: number + ): Promise { + if (typeof input.address === 'undefined') { + throw new Error('no address in input ' + JSON.stringify(input)); } - async _getRawTxAddInput(txb: TransactionBuilder, i: number, input: BlocksoftBlockchainTypes.UnspentTx, nSequence: number): Promise { - if (typeof input.address === 'undefined') { - throw new Error('no address in input ' + JSON.stringify(input)) - } - - if (!input.derivationPath || input.derivationPath === "false") { - // @ts-ignore - if (typeof input.path !== 'undefined') { - // @ts-ignore - input.derivationPath = input.path - } - } - + if (!input.derivationPath || input.derivationPath === 'false') { + // @ts-ignore + if (typeof input.path !== 'undefined') { // @ts-ignore - const mainCurrencyCode = this._settings.currencyCode === 'LTC' ? 'LTC' : 'BTC' + input.derivationPath = input.path; + } + } - const segwitPrefix = BlocksoftDict.CurrenciesForTests[mainCurrencyCode + '_SEGWIT'].addressPrefix - const segwitCompatiblePrefix = typeof (BlocksoftDict.CurrenciesForTests[mainCurrencyCode + '_SEGWIT_COMPATIBLE']) !== 'undefined' ? - BlocksoftDict.CurrenciesForTests[mainCurrencyCode + '_SEGWIT_COMPATIBLE'].addressPrefix : false + // @ts-ignore + const mainCurrencyCode = + this._settings.currencyCode === 'LTC' ? 'LTC' : 'BTC'; - if (typeof this.keyPairBTC[input.address] === 'undefined') { - let currencyCode = mainCurrencyCode - if (input.address.indexOf(segwitPrefix) === 0) { - currencyCode += '_SEGWIT' - if (!input.derivationPath || input.derivationPath === "false") { - input.derivationPath = BlocksoftDict.CurrenciesForTests[currencyCode].defaultPath - } - } else if (segwitCompatiblePrefix && input.address.indexOf(segwitCompatiblePrefix) === 0) { - currencyCode += '_SEGWIT_COMPATIBLE' - if (!input.derivationPath || input.derivationPath === "false") { - input.derivationPath = BlocksoftDict.CurrenciesForTests[currencyCode].defaultPath - } - } else if (!input.derivationPath || input.derivationPath === "false") { - input.derivationPath = BlocksoftDict.CurrenciesForTests[mainCurrencyCode].defaultPath - } - const discoverFor = { - mnemonic: this.mnemonic, - addressToCheck: input.address, - walletHash: this.walletHash, - derivationPath: input.derivationPath, - currencyCode - } - const result = await BlocksoftPrivateKeysUtils.getPrivateKey(discoverFor, 'BtcTxBuilder') + const segwitPrefix = + BlocksoftDict.CurrenciesForTests[mainCurrencyCode + '_SEGWIT'] + .addressPrefix; + const segwitCompatiblePrefix = + typeof BlocksoftDict.CurrenciesForTests[ + mainCurrencyCode + '_SEGWIT_COMPATIBLE' + ] !== 'undefined' + ? BlocksoftDict.CurrenciesForTests[ + mainCurrencyCode + '_SEGWIT_COMPATIBLE' + ].addressPrefix + : false; - try { - this.keyPairBTC[input.address] = ECPair.fromWIF(result.privateKey, this._bitcoinNetwork) - let address - if (currencyCode === mainCurrencyCode + '_SEGWIT') { - try { - this.p2wpkhBTC[input.address] = payments.p2wpkh({ - pubkey: this.keyPairBTC[input.address].publicKey, - network: this._bitcoinNetwork - }) - } catch (e) { - e.message += ' in privateKey ' + currencyCode + ' Segwit signature create' - // noinspection ExceptionCaughtLocallyJS - throw e - } + if (typeof this.keyPairBTC[input.address] === 'undefined') { + let currencyCode = mainCurrencyCode; + if (input.address.indexOf(segwitPrefix) === 0) { + currencyCode += '_SEGWIT'; + if (!input.derivationPath || input.derivationPath === 'false') { + input.derivationPath = + BlocksoftDict.CurrenciesForTests[currencyCode].defaultPath; + } + } else if ( + segwitCompatiblePrefix && + input.address.indexOf(segwitCompatiblePrefix) === 0 + ) { + currencyCode += '_SEGWIT_COMPATIBLE'; + if (!input.derivationPath || input.derivationPath === 'false') { + input.derivationPath = + BlocksoftDict.CurrenciesForTests[currencyCode].defaultPath; + } + } else if (!input.derivationPath || input.derivationPath === 'false') { + input.derivationPath = + BlocksoftDict.CurrenciesForTests[mainCurrencyCode].defaultPath; + } + const discoverFor = { + mnemonic: this.mnemonic, + addressToCheck: input.address, + walletHash: this.walletHash, + derivationPath: input.derivationPath, + currencyCode + }; + const result = await BlocksoftPrivateKeysUtils.getPrivateKey( + discoverFor, + 'BtcTxBuilder' + ); - if (typeof this.p2wpkhBTC[input.address].address === 'undefined') { - // noinspection ExceptionCaughtLocallyJS - throw new Error('not valid ' + currencyCode + ' segwit p2sh') - } - address = this.p2wpkhBTC[input.address].address - } else if (currencyCode === mainCurrencyCode + '_SEGWIT_COMPATIBLE') { - try { - this.p2wpkhBTC[input.address] = payments.p2wpkh({ - pubkey: this.keyPairBTC[input.address].publicKey, - network: this._bitcoinNetwork - }) - this.p2shBTC[input.address] = payments.p2sh({ - redeem: this.p2wpkhBTC[input.address], - network: this._bitcoinNetwork - }) - } catch (e) { - e.message += ' in privateKey ' + currencyCode + ' SegwitCompatible signature create' - // noinspection ExceptionCaughtLocallyJS - throw e - } - if (typeof this.p2shBTC[input.address].address === 'undefined') { - // noinspection ExceptionCaughtLocallyJS - throw new Error('not valid ' + currencyCode + ' segwit compatible p2sh') - } - address = this.p2shBTC[input.address].address - } else { - address = payments.p2pkh({ - pubkey: this.keyPairBTC[input.address].publicKey, - network: this._bitcoinNetwork - }).address - } + try { + this.keyPairBTC[input.address] = ECPair.fromWIF( + result.privateKey, + this._bitcoinNetwork + ); + let address; + if (currencyCode === mainCurrencyCode + '_SEGWIT') { + try { + this.p2wpkhBTC[input.address] = payments.p2wpkh({ + pubkey: this.keyPairBTC[input.address].publicKey, + network: this._bitcoinNetwork + }); + } catch (e) { + e.message += + ' in privateKey ' + currencyCode + ' Segwit signature create'; + // noinspection ExceptionCaughtLocallyJS + throw e; + } - if (address !== input.address) { - // noinspection ExceptionCaughtLocallyJS - throw new Error('not valid ' + currencyCode + ' signing path ' + input.derivationPath + ' address ' + input.address + ' != ' + address + ' segwit type = ' + currencyCode) - } - } catch (e) { - e.message += ' in privateKey ' + currencyCode + ' signature check ' - throw e - } + if (typeof this.p2wpkhBTC[input.address].address === 'undefined') { + // noinspection ExceptionCaughtLocallyJS + throw new Error('not valid ' + currencyCode + ' segwit p2sh'); + } + address = this.p2wpkhBTC[input.address].address; + } else if (currencyCode === mainCurrencyCode + '_SEGWIT_COMPATIBLE') { + try { + this.p2wpkhBTC[input.address] = payments.p2wpkh({ + pubkey: this.keyPairBTC[input.address].publicKey, + network: this._bitcoinNetwork + }); + this.p2shBTC[input.address] = payments.p2sh({ + redeem: this.p2wpkhBTC[input.address], + network: this._bitcoinNetwork + }); + } catch (e) { + e.message += + ' in privateKey ' + + currencyCode + + ' SegwitCompatible signature create'; + // noinspection ExceptionCaughtLocallyJS + throw e; + } + if (typeof this.p2shBTC[input.address].address === 'undefined') { + // noinspection ExceptionCaughtLocallyJS + throw new Error( + 'not valid ' + currencyCode + ' segwit compatible p2sh' + ); + } + address = this.p2shBTC[input.address].address; + } else { + address = payments.p2pkh({ + pubkey: this.keyPairBTC[input.address].publicKey, + network: this._bitcoinNetwork + }).address; } - if (typeof this.p2wpkhBTC[input.address] === 'undefined') { - txb.addInput(input.txid, input.vout, nSequence) - } else if (typeof this.p2shBTC[input.address] === 'undefined') { - txb.addInput(input.txid, input.vout, nSequence, this.p2wpkhBTC[input.address].output) - } else { - txb.addInput(input.txid, input.vout, nSequence) + if (address !== input.address) { + // noinspection ExceptionCaughtLocallyJS + throw new Error( + 'not valid ' + + currencyCode + + ' signing path ' + + input.derivationPath + + ' address ' + + input.address + + ' != ' + + address + + ' segwit type = ' + + currencyCode + ); } + } catch (e) { + e.message += ' in privateKey ' + currencyCode + ' signature check '; + throw e; + } } - async _getRawTxSign(txb: TransactionBuilder, i: number, input: BlocksoftBlockchainTypes.UnspentTx): Promise { - if (typeof input.address === 'undefined') { - throw new Error('no address in input ' + JSON.stringify(input)) - } - if (typeof this.p2wpkhBTC[input.address] === 'undefined') { - // @ts-ignore - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' BtcTxBuilder.getRawTx sign usual', input) - // @ts-ignore - txb.sign(i, this.keyPairBTC[input.address], null, null, input.value * 1) - } else if (typeof this.p2shBTC[input.address] === 'undefined') { - // @ts-ignore - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' BtcTxBuilder.getRawTx sign segwit', input) - // @ts-ignore - txb.sign(i, this.keyPairBTC[input.address], null, null, input.value * 1) - } else { - // @ts-ignore - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' BtcTxBuilder.getRawTx sign segwit compatible', input) - // @ts-ignore - txb.sign(i, this.keyPairBTC[input.address], this.p2shBTC[input.address].redeem.output, null, input.value * 1) - } + if (typeof this.p2wpkhBTC[input.address] === 'undefined') { + txb.addInput(input.txid, input.vout, nSequence); + } else if (typeof this.p2shBTC[input.address] === 'undefined') { + txb.addInput( + input.txid, + input.vout, + nSequence, + this.p2wpkhBTC[input.address].output + ); + } else { + txb.addInput(input.txid, input.vout, nSequence); + } + } + + async _getRawTxSign( + txb: TransactionBuilder, + i: number, + input: BlocksoftBlockchainTypes.UnspentTx + ): Promise { + if (typeof input.address === 'undefined') { + throw new Error('no address in input ' + JSON.stringify(input)); + } + if (typeof this.p2wpkhBTC[input.address] === 'undefined') { + // @ts-ignore + await BlocksoftCryptoLog.log( + this._settings.currencyCode + ' BtcTxBuilder.getRawTx sign usual', + input + ); + // @ts-ignore + txb.sign(i, this.keyPairBTC[input.address], null, null, input.value * 1); + } else if (typeof this.p2shBTC[input.address] === 'undefined') { + // @ts-ignore + await BlocksoftCryptoLog.log( + this._settings.currencyCode + ' BtcTxBuilder.getRawTx sign segwit', + input + ); + // @ts-ignore + txb.sign(i, this.keyPairBTC[input.address], null, null, input.value * 1); + } else { + // @ts-ignore + await BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' BtcTxBuilder.getRawTx sign segwit compatible', + input + ); + // @ts-ignore + txb.sign( + i, + this.keyPairBTC[input.address], + this.p2shBTC[input.address].redeem.output, + null, + input.value * 1 + ); } + } } diff --git a/crypto/blockchains/btc/tx/BtcTxInputsOutputs.ts b/crypto/blockchains/btc/tx/BtcTxInputsOutputs.ts index b7a3c2cd6..b9b127888 100644 --- a/crypto/blockchains/btc/tx/BtcTxInputsOutputs.ts +++ b/crypto/blockchains/btc/tx/BtcTxInputsOutputs.ts @@ -1,130 +1,188 @@ /** * @version 0.20 */ -import { BlocksoftBlockchainTypes } from '../../BlocksoftBlockchainTypes' -import BtcUnspentsProvider from '../providers/BtcUnspentsProvider' -import DogeTxInputsOutputs from '../../doge/tx/DogeTxInputsOutputs' -import settingsActions from '../../../../app/appstores/Stores/Settings/SettingsActions' -import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog' -import DaemonCache from '../../../../app/daemons/DaemonCache' -import BlocksoftDict from '@crypto/common/BlocksoftDict' - -export default class BtcTxInputsOutputs extends DogeTxInputsOutputs implements BlocksoftBlockchainTypes.TxInputsOutputs { - - async _addressForChange(data: BlocksoftBlockchainTypes.TransferData): string { - const btcShowTwoAddress = await settingsActions.getSetting('btcShowTwoAddress') - const btcLegacyOrSegwit = await settingsActions.getSetting('btc_legacy_or_segwit') - - const mainCurrencyCode = this._settings.currencyCode === 'LTC' ? 'LTC' : 'BTC' - const legacyPrefix = BlocksoftDict.Currencies[mainCurrencyCode].addressPrefix - const segwitPrefix = BlocksoftDict.CurrenciesForTests[mainCurrencyCode + '_SEGWIT'].addressPrefix - - let needFindSegwit = true - if (btcShowTwoAddress === '1' || data.useLegacy === 1) { - // @todo as btcShowTwoAddress this will be deprecated simplify the code - // its only for wallets with old setting of two addresses where there was useLegacy on - // console.log('will legacy') - needFindSegwit = false - } else if (btcShowTwoAddress === '1' || btcLegacyOrSegwit === 'segwit') { - needFindSegwit = true - } else if (btcLegacyOrSegwit === 'legacy') { - needFindSegwit = false - // console.log('will legacy 2') - } - - BlocksoftCryptoLog.log('BtcTxInputsOutputs needFindSegwit ' + JSON.stringify(needFindSegwit)) - try { - const CACHE_FOR_CHANGE = await BtcUnspentsProvider.getCache(data.walletHash, this._settings.currencyCode) - BlocksoftCryptoLog.log('BtcTxInputsOutputs CACHE_FOR_CHANGE ' + data.walletHash, CACHE_FOR_CHANGE) +import { BlocksoftBlockchainTypes } from '../../BlocksoftBlockchainTypes'; +import BtcUnspentsProvider from '../providers/BtcUnspentsProvider'; +import DogeTxInputsOutputs from '../../doge/tx/DogeTxInputsOutputs'; +import settingsActions from '../../../../app/appstores/Stores/Settings/SettingsActions'; +import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; +import DaemonCache from '../../../../app/daemons/DaemonCache'; +import BlocksoftDict from '@crypto/common/BlocksoftDict'; + +export default class BtcTxInputsOutputs + extends DogeTxInputsOutputs + implements BlocksoftBlockchainTypes.TxInputsOutputs +{ + async _addressForChange(data: BlocksoftBlockchainTypes.TransferData): string { + const btcShowTwoAddress = await settingsActions.getSetting( + 'btcShowTwoAddress' + ); + const btcLegacyOrSegwit = await settingsActions.getSetting( + 'btc_legacy_or_segwit' + ); + + const mainCurrencyCode = + this._settings.currencyCode === 'LTC' ? 'LTC' : 'BTC'; + const legacyPrefix = + BlocksoftDict.Currencies[mainCurrencyCode].addressPrefix; + const segwitPrefix = + BlocksoftDict.CurrenciesForTests[mainCurrencyCode + '_SEGWIT'] + .addressPrefix; + + let needFindSegwit = true; + if (btcShowTwoAddress === '1' || data.useLegacy === 1) { + // @todo as btcShowTwoAddress this will be deprecated simplify the code + // its only for wallets with old setting of two addresses where there was useLegacy on + // console.log('will legacy') + needFindSegwit = false; + } else if (btcShowTwoAddress === '1' || btcLegacyOrSegwit === 'segwit') { + needFindSegwit = true; + } else if (btcLegacyOrSegwit === 'legacy') { + needFindSegwit = false; + // console.log('will legacy 2') + } - let addressForChange = false - if (needFindSegwit) { - addressForChange = CACHE_FOR_CHANGE[segwitPrefix] - } else { - addressForChange = CACHE_FOR_CHANGE[legacyPrefix] - } - // @ts-ignore - BlocksoftCryptoLog.log(this._settings.currencyCode + ' ' + mainCurrencyCode + ' BtcTxInputsOutputs _addressForChange addressForChange logic ', { - needFindSegwit, - addressForChange, - CACHE: CACHE_FOR_CHANGE - }) - if (addressForChange && addressForChange !== '') { - return addressForChange - } - } catch (e) { - BlocksoftCryptoLog.err(this._settings.currencyCode + ' ' + mainCurrencyCode + ' BtcTxInputsOutputs _addressForChange error ' + e.message) + BlocksoftCryptoLog.log( + 'BtcTxInputsOutputs needFindSegwit ' + JSON.stringify(needFindSegwit) + ); + try { + const CACHE_FOR_CHANGE = await BtcUnspentsProvider.getCache( + data.walletHash, + this._settings.currencyCode + ); + BlocksoftCryptoLog.log( + 'BtcTxInputsOutputs CACHE_FOR_CHANGE ' + data.walletHash, + CACHE_FOR_CHANGE + ); + + let addressForChange = false; + if (needFindSegwit) { + addressForChange = CACHE_FOR_CHANGE[segwitPrefix]; + } else { + addressForChange = CACHE_FOR_CHANGE[legacyPrefix]; + } + // @ts-ignore + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' ' + + mainCurrencyCode + + ' BtcTxInputsOutputs _addressForChange addressForChange logic ', + { + needFindSegwit, + addressForChange, + CACHE: CACHE_FOR_CHANGE } - - return data.addressFrom + ); + if (addressForChange && addressForChange !== '') { + return addressForChange; + } + } catch (e) { + BlocksoftCryptoLog.err( + this._settings.currencyCode + + ' ' + + mainCurrencyCode + + ' BtcTxInputsOutputs _addressForChange error ' + + e.message + ); } - async getInputsOutputs(data: BlocksoftBlockchainTypes.TransferData, unspents: BlocksoftBlockchainTypes.UnspentTx[], - feeToCount: { feeForByte?: string, feeForAll?: string, autoFeeLimitReadable?: string | number }, - additionalData : BlocksoftBlockchainTypes.TransferAdditionalData, - subtitle: string = 'default') - : Promise - { - const res = await super._getInputsOutputs(data, unspents, feeToCount, additionalData, subtitle + ' btced') + return data.addressFrom; + } + + async getInputsOutputs( + data: BlocksoftBlockchainTypes.TransferData, + unspents: BlocksoftBlockchainTypes.UnspentTx[], + feeToCount: { + feeForByte?: string; + feeForAll?: string; + autoFeeLimitReadable?: string | number; + }, + additionalData: BlocksoftBlockchainTypes.TransferAdditionalData, + subtitle: string = 'default' + ): Promise { + const res = await super._getInputsOutputs( + data, + unspents, + feeToCount, + additionalData, + subtitle + ' btced' + ); + + if (this._settings.currencyCode !== 'BTC') { + return res; + } - if (this._settings.currencyCode !== 'BTC') { - return res - } + const tmp = DaemonCache.getCacheAccountStatic(data.walletHash, 'USDT'); + if (tmp.balance === '0' || tmp.balance === 0) { + return res; + } - const tmp = DaemonCache.getCacheAccountStatic(data.walletHash, 'USDT') - if (tmp.balance === '0' || tmp.balance === 0) { - return res - } + let usdtCount = 0; + for (const unspent of unspents) { + if (unspent.address === tmp.address) { + usdtCount++; + } + } + if (usdtCount === 0) { + res.outputs.push({ + to: tmp.address, + amount: '546', + isChange: true, + logType: 'FOR_LEGACY_USDT_KEEP_FROM_BTC' + }); + return res; + } - let usdtCount = 0 - for (const unspent of unspents) { - if (unspent.address === tmp.address) { - usdtCount++ - } - } - if (usdtCount === 0) { - res.outputs.push({ to: tmp.address, amount: '546', isChange: true, logType : 'FOR_LEGACY_USDT_KEEP_FROM_BTC' }) - return res + let usdtUsed = 0; + for (const input of res.inputs) { + if (input.address === tmp.address) { + usdtUsed++; + } + } + BlocksoftCryptoLog.log( + 'BtxTxInputsOutputs for ' + + tmp.address + + ' usdtUsed ' + + usdtUsed + + ' usdtCount ' + + usdtCount + ); + + if (usdtUsed >= usdtCount) { + let found = false; + for (const input of res.inputs) { + if (input.address === tmp.address && !found && input.value === '546') { + input.value = '0'; + found = true; } - - let usdtUsed = 0 + } + if (!found) { for (const input of res.inputs) { - if (input.address === tmp.address) { - usdtUsed++ - } + if (input.address === tmp.address) { + res.outputs.push({ + to: tmp.address, + amount: '546', + isChange: true, + logType: 'FOR_LEGACY_USDT_KEEP_FROM_BTC' + }); + break; + } } - BlocksoftCryptoLog.log('BtxTxInputsOutputs for ' + tmp.address + ' usdtUsed ' + usdtUsed + ' usdtCount ' + usdtCount) - - if (usdtUsed >= usdtCount) { - let found = false - for (const input of res.inputs) { - if (input.address === tmp.address && !found && input.value === '546') { - input.value = '0' - found = true - } - } - if (!found) { - for (const input of res.inputs) { - if (input.address === tmp.address) { - res.outputs.push({ to: tmp.address, amount: '546', isChange: true, logType : 'FOR_LEGACY_USDT_KEEP_FROM_BTC' }) - break - } - } - } + } - if (found) { - const inputs = [] - for (const input of res.inputs) { - if (input.value !== '0') { - inputs.push(input) - } - } - res.inputs = inputs - } + if (found) { + const inputs = []; + for (const input of res.inputs) { + if (input.value !== '0') { + inputs.push(input); + } } + res.inputs = inputs; + } + } - res.countedFor = 'BTC' + res.countedFor = 'BTC'; - return res - } + return res; + } } diff --git a/crypto/blockchains/doge/tx/DogeTxInputsOutputs.ts b/crypto/blockchains/doge/tx/DogeTxInputsOutputs.ts index 5871154db..49927cf22 100644 --- a/crypto/blockchains/doge/tx/DogeTxInputsOutputs.ts +++ b/crypto/blockchains/doge/tx/DogeTxInputsOutputs.ts @@ -1,146 +1,196 @@ /** * @version 0.20 */ -import { BlocksoftBlockchainTypes } from '../../BlocksoftBlockchainTypes' -import BlocksoftBN from '../../../common/BlocksoftBN' -import BlocksoftUtils from '../../../common/BlocksoftUtils' -import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog' -import BlocksoftDict from '@crypto/common/BlocksoftDict' - -const coinSelect = require('coinselect') -const coinSplit = require('coinselect/split') - -export default class DogeTxInputsOutputs implements BlocksoftBlockchainTypes.TxInputsOutputs { - private _builderSettings: BlocksoftBlockchainTypes.BuilderSettings - protected _settings: BlocksoftBlockchainTypes.CurrencySettings - private _minOutputDust: any - private _minChangeDust: any - - // in*148 + out*34 + 10 plus or minus 'in' - SIZE_FOR_BASIC = 34 - SIZE_FOR_INPUT = 148 // TX_INPUT_PUBKEYHASH = 107 - SIZE_FOR_BC = 75 - - constructor(settings: BlocksoftBlockchainTypes.CurrencySettings, builderSettings: BlocksoftBlockchainTypes.BuilderSettings) { - this._settings = settings - this._builderSettings = builderSettings - this._minOutputDust = BlocksoftUtils.fromUnified(this._builderSettings.minOutputDustReadable, settings.decimals) // output amount that will be considered as "dust" so we dont need it - this._minChangeDust = BlocksoftUtils.fromUnified(this._builderSettings.minChangeDustReadable, settings.decimals) // change amount that will be considered as "dust" so we dont need it +import { BlocksoftBlockchainTypes } from '../../BlocksoftBlockchainTypes'; +import BlocksoftBN from '../../../common/BlocksoftBN'; +import BlocksoftUtils from '../../../common/BlocksoftUtils'; +import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; +import BlocksoftDict from '@crypto/common/BlocksoftDict'; + +const coinSelect = require('coinselect'); +const coinSplit = require('coinselect/split'); + +export default class DogeTxInputsOutputs + implements BlocksoftBlockchainTypes.TxInputsOutputs +{ + private _builderSettings: BlocksoftBlockchainTypes.BuilderSettings; + protected _settings: BlocksoftBlockchainTypes.CurrencySettings; + private _minOutputDust: any; + private _minChangeDust: any; + + // in*148 + out*34 + 10 plus or minus 'in' + SIZE_FOR_BASIC = 34; + SIZE_FOR_INPUT = 148; // TX_INPUT_PUBKEYHASH = 107 + SIZE_FOR_BC = 75; + + constructor( + settings: BlocksoftBlockchainTypes.CurrencySettings, + builderSettings: BlocksoftBlockchainTypes.BuilderSettings + ) { + this._settings = settings; + this._builderSettings = builderSettings; + this._minOutputDust = BlocksoftUtils.fromUnified( + this._builderSettings.minOutputDustReadable, + settings.decimals + ); // output amount that will be considered as "dust" so we dont need it + this._minChangeDust = BlocksoftUtils.fromUnified( + this._builderSettings.minChangeDustReadable, + settings.decimals + ); // change amount that will be considered as "dust" so we dont need it + } + + _coinSelectTargets( + data: BlocksoftBlockchainTypes.TransferData, + unspents: BlocksoftBlockchainTypes.UnspentTx[], + feeForByte: string, + multiAddress: string[], + subtitle: string + ) { + let targets; + if (data.isTransferAll) { + targets = [ + { + address: data.addressTo + } + ]; + } else if (multiAddress.length === 0) { + targets = [ + { + address: data.addressTo, + // @ts-ignore + value: data.amount * 1 + } + ]; + } else { + targets = []; + for (const address of multiAddress) { + targets.push({ + address: address, + // @ts-ignore + value: data.amount * 1 + }); + } } - - _coinSelectTargets(data: BlocksoftBlockchainTypes.TransferData, unspents: BlocksoftBlockchainTypes.UnspentTx[], feeForByte: string, multiAddress: string[], subtitle: string) { - let targets - if (data.isTransferAll) { - targets = [{ - address: data.addressTo - }] - } else if (multiAddress.length === 0) { - targets = [{ - address: data.addressTo, - // @ts-ignore - value: data.amount * 1 - }] - } else { - targets = [] - for (const address of multiAddress) { - targets.push({ - address: address, - // @ts-ignore - value: data.amount * 1 - }) - } + // @ts-ignore + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' DogeTxInputsOutputs.getInputsOutputs _coinSelectTargets', + { + amount: data.amount, + isTransferAll: data.isTransferAll, + multiAddress, + address: data.addressTo + }, + targets + ); + return targets; + } + + _addressForChange(data: BlocksoftBlockchainTypes.TransferData): string { + return data.addressFrom; + } + + _usualTargets( + data: BlocksoftBlockchainTypes.TransferData, + unspents: BlocksoftBlockchainTypes.UnspentTx[] + ) { + const multiAddress = []; + const basicWishedAmountBN = new BlocksoftBN(data.amount); + const wishedAmountBN = new BlocksoftBN(basicWishedAmountBN); + + const outputs = []; + if (data.addressTo.indexOf(';') === -1) { + outputs.push({ + to: data.addressTo, + amount: data.amount.toString() + }); + } else { + const addresses = data.addressTo.replace(/\s+/g, ';').split(';'); + let total = 0; + for (let i = 0, ic = addresses.length; i < ic; i++) { + const address = addresses[i].trim(); + if (!address) continue; + outputs.push({ + to: address, + amount: data.amount.toString() + }); + multiAddress.push(address); + if (total > 0) { + wishedAmountBN.add(basicWishedAmountBN); } + total++; + } + } + return { + multiAddress, + basicWishedAmountBN, + wishedAmountBN, + outputs + }; + } + + async _coinSelect( + data: BlocksoftBlockchainTypes.TransferData, + unspents: BlocksoftBlockchainTypes.UnspentTx[], + feeForByte: string, + multiAddress: string[], + subtitle: string + ): Promise { + const utxos = []; + const isRequired: any = {}; + let isAllRequired: boolean = true; + const segwitPrefix = + typeof BlocksoftDict.CurrenciesForTests[ + this._settings.currencyCode + '_SEGWIT' + ] !== 'undefined' + ? BlocksoftDict.CurrenciesForTests[ + this._settings.currencyCode + '_SEGWIT' + ].addressPrefix + : false; + for (const unspent of unspents) { + const input = { + txId: unspent.txid, + vout: unspent.vout, // @ts-ignore - BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeTxInputsOutputs.getInputsOutputs _coinSelectTargets', { - amount: data.amount, - isTransferAll: data.isTransferAll, - multiAddress, - address : data.addressTo - }, targets) - return targets + value: unspent.value * 1, + my: unspent + }; // script + if ( + typeof unspent.address !== 'undefined' && + unspent.address.indexOf(segwitPrefix) === 0 + ) { + input.isSegwit = true; + // https://github.com/bitcoinjs/coinselect/pull/63 wait for it to be merged + } + utxos.push(input); + if (unspent.isRequired) { + if (typeof isRequired[unspent.txid] === 'undefined') { + isRequired[unspent.txid] = unspent; + } + } else { + isAllRequired = false; + } } - - _addressForChange(data: BlocksoftBlockchainTypes.TransferData): string { - return data.addressFrom + if (isAllRequired) { + if (data.addressFrom === data.addressTo) { + data.isTransferAll = true; + } } - - _usualTargets(data: BlocksoftBlockchainTypes.TransferData, unspents: BlocksoftBlockchainTypes.UnspentTx[]) { - const multiAddress = [] - const basicWishedAmountBN = new BlocksoftBN(data.amount) - const wishedAmountBN = new BlocksoftBN(basicWishedAmountBN) - - const outputs = [] - if (data.addressTo.indexOf(';') === -1) { - outputs.push({ - 'to': data.addressTo, - 'amount': data.amount.toString() - }) - } else { - const addresses = data.addressTo.replace(/\s+/g, ';').split(';') - let total = 0 - for (let i = 0, ic = addresses.length; i < ic; i++) { - const address = addresses[i].trim() - if (!address) continue - outputs.push({ - 'to': address, - 'amount': data.amount.toString() - }) - multiAddress.push(address) - if (total > 0) { - wishedAmountBN.add(basicWishedAmountBN) - } - total++ - } - } - return { - multiAddress, - basicWishedAmountBN, - wishedAmountBN, - outputs - } + const targets = this._coinSelectTargets( + data, + unspents, + feeForByte, + multiAddress, + subtitle + ); + let res; + if (data.isTransferAll) { + res = coinSplit(utxos, targets, feeForByte); + } else { + res = coinSelect(utxos, targets, feeForByte); } - - async _coinSelect(data: BlocksoftBlockchainTypes.TransferData, unspents: BlocksoftBlockchainTypes.UnspentTx[], feeForByte: string, multiAddress: string[], subtitle: string) - : Promise { - const utxos = [] - const isRequired: any = {} - let isAllRequired: boolean = true - const segwitPrefix = typeof BlocksoftDict.CurrenciesForTests[this._settings.currencyCode + '_SEGWIT'] !== 'undefined' ? BlocksoftDict.CurrenciesForTests[this._settings.currencyCode + '_SEGWIT'].addressPrefix : false - for (const unspent of unspents) { - const input = { - txId: unspent.txid, - vout: unspent.vout, - // @ts-ignore - value: unspent.value * 1, - my: unspent - }// script - if (typeof unspent.address !== 'undefined' && unspent.address.indexOf(segwitPrefix) === 0) { - input.isSegwit = true - // https://github.com/bitcoinjs/coinselect/pull/63 wait for it to be merged - } - utxos.push(input) - if (unspent.isRequired) { - if (typeof isRequired[unspent.txid] === 'undefined') { - isRequired[unspent.txid] = unspent - } - } else { - isAllRequired = false - } - } - if (isAllRequired) { - if (data.addressFrom === data.addressTo) { - data.isTransferAll = true - } - } - const targets = this._coinSelectTargets(data, unspents, feeForByte, multiAddress, subtitle) - let res - if (data.isTransferAll) { - res = coinSplit(utxos, targets, feeForByte) - } else { - res = coinSelect(utxos, targets, feeForByte) - } - const { inputs, outputs, fee } = res - /* + const { inputs, outputs, fee } = res; + /* console.log('CS feeForByte ' + feeForByte) console.log('CS isAllRequired ', JSON.stringify(isAllRequired)) console.log('CS targets ' + feeForByte, JSON.parse(JSON.stringify(targets))) @@ -163,331 +213,489 @@ export default class DogeTxInputsOutputs implements BlocksoftBlockchainTypes.TxI console.log('') */ - const formatted = { - inputs: [], - outputs: [], - multiAddress, - msg: ' coinselect for ' + feeForByte + ' fee ' + fee + ' ' + subtitle + ' all data ' + JSON.stringify(inputs) + ' ' + JSON.stringify(outputs), - countedFor: 'DOGE' - } - if (!inputs || typeof inputs === 'undefined') { - // @ts-ignore - return formatted - } - - let input, output - for (input of inputs) { - // @ts-ignore - formatted.inputs.push(input.my) - if (typeof isRequired[input.my.txid] !== 'undefined') { - delete isRequired[input.my.txid] - } - } - const changeBN = new BlocksoftBN(0) - let changeIsNeeded = false - for (const txid in isRequired) { - formatted.msg += ' txidAdded ' + txid - // @ts-ignore - formatted.inputs.push(isRequired[txid]) - changeBN.add(isRequired[txid].value * 1) - changeIsNeeded = true - } - + const formatted = { + inputs: [], + outputs: [], + multiAddress, + msg: + ' coinselect for ' + + feeForByte + + ' fee ' + + fee + + ' ' + + subtitle + + ' all data ' + + JSON.stringify(inputs) + + ' ' + + JSON.stringify(outputs), + countedFor: 'DOGE' + }; + if (!inputs || typeof inputs === 'undefined') { + // @ts-ignore + return formatted; + } - const addressForChange = await this._addressForChange(data) - for (output of outputs) { - if (output.address) { - formatted.outputs.push({ - // @ts-ignore - to: output.address, - // @ts-ignore - amount: output.value.toString() - }) - } else if (addressForChange === data.addressTo) { - changeIsNeeded = true - changeBN.add(output.value) - } else if (changeIsNeeded) { - changeIsNeeded = false - changeBN.add(output.value) - formatted.outputs.push({ - // @ts-ignore - to: addressForChange, - // @ts-ignore - amount: changeBN.toString(), - // @ts-ignore - isChange: true - }) - } else { - formatted.outputs.push({ - // @ts-ignore - to: addressForChange, - // @ts-ignore - amount: output.value.toString(), - // @ts-ignore - isChange: true - }) - } - } + let input, output; + for (input of inputs) { + // @ts-ignore + formatted.inputs.push(input.my); + if (typeof isRequired[input.my.txid] !== 'undefined') { + delete isRequired[input.my.txid]; + } + } + const changeBN = new BlocksoftBN(0); + let changeIsNeeded = false; + for (const txid in isRequired) { + formatted.msg += ' txidAdded ' + txid; + // @ts-ignore + formatted.inputs.push(isRequired[txid]); + changeBN.add(isRequired[txid].value * 1); + changeIsNeeded = true; + } - if (changeIsNeeded) { - // @ts-ignore - if (this._builderSettings.changeTogether && typeof formatted.outputs[0] !== 'undefined' && addressForChange === data.addressTo && addressForChange === formatted.outputs[0].to) { - // @ts-ignore - changeBN.add(formatted.outputs[0].amount) - // @ts-ignore - formatted.outputs[0].amount = changeBN.toString() - } else { - formatted.outputs.push({ - // @ts-ignore - to: addressForChange, - // @ts-ignore - amount: changeBN.toString(), - // @ts-ignore - isChange: true - }) - } - } + const addressForChange = await this._addressForChange(data); + for (output of outputs) { + if (output.address) { + formatted.outputs.push({ + // @ts-ignore + to: output.address, + // @ts-ignore + amount: output.value.toString() + }); + } else if (addressForChange === data.addressTo) { + changeIsNeeded = true; + changeBN.add(output.value); + } else if (changeIsNeeded) { + changeIsNeeded = false; + changeBN.add(output.value); + formatted.outputs.push({ + // @ts-ignore + to: addressForChange, + // @ts-ignore + amount: changeBN.toString(), + // @ts-ignore + isChange: true + }); + } else { + formatted.outputs.push({ + // @ts-ignore + to: addressForChange, + // @ts-ignore + amount: output.value.toString(), + // @ts-ignore + isChange: true + }); + } + } + if (changeIsNeeded) { + // @ts-ignore + if ( + this._builderSettings.changeTogether && + typeof formatted.outputs[0] !== 'undefined' && + addressForChange === data.addressTo && + addressForChange === formatted.outputs[0].to + ) { + // @ts-ignore + changeBN.add(formatted.outputs[0].amount); // @ts-ignore - return formatted + formatted.outputs[0].amount = changeBN.toString(); + } else { + formatted.outputs.push({ + // @ts-ignore + to: addressForChange, + // @ts-ignore + amount: changeBN.toString(), + // @ts-ignore + isChange: true + }); + } } - async getInputsOutputs(data: BlocksoftBlockchainTypes.TransferData, - unspents: BlocksoftBlockchainTypes.UnspentTx[], - feeToCount: { feeForByte?: string, feeForAll?: string, autoFeeLimitReadable?: string | number }, - additionalData: BlocksoftBlockchainTypes.TransferAdditionalData, - subtitle: string = 'default') - : Promise { - return this._getInputsOutputs(data, unspents, feeToCount, additionalData, subtitle) + // @ts-ignore + return formatted; + } + + async getInputsOutputs( + data: BlocksoftBlockchainTypes.TransferData, + unspents: BlocksoftBlockchainTypes.UnspentTx[], + feeToCount: { + feeForByte?: string; + feeForAll?: string; + autoFeeLimitReadable?: string | number; + }, + additionalData: BlocksoftBlockchainTypes.TransferAdditionalData, + subtitle: string = 'default' + ): Promise { + return this._getInputsOutputs( + data, + unspents, + feeToCount, + additionalData, + subtitle + ); + } + + async _getInputsOutputs( + data: BlocksoftBlockchainTypes.TransferData, + unspents: BlocksoftBlockchainTypes.UnspentTx[], + feeToCount: { + feeForByte?: string; + feeForAll?: string; + autoFeeLimitReadable?: string | number; + }, + additionalData: BlocksoftBlockchainTypes.TransferAdditionalData, + subtitle: string = 'default' + ): Promise { + if (typeof data.addressFrom === 'undefined') { + throw new Error( + 'DogeTxInputsOutputs.getInputsOutputs requires addressFrom' + ); + } + if (typeof data.addressTo === 'undefined') { + throw new Error( + 'DogeTxInputsOutputs.getInputsOutputs requires addressTo' + ); + } + if (typeof data.amount === 'undefined') { + throw new Error('DogeTxInputsOutputs.getInputsOutputs requires amount'); } - async _getInputsOutputs(data: BlocksoftBlockchainTypes.TransferData, - unspents: BlocksoftBlockchainTypes.UnspentTx[], - feeToCount: { feeForByte?: string, feeForAll?: string, autoFeeLimitReadable?: string | number }, - additionalData: BlocksoftBlockchainTypes.TransferAdditionalData, - subtitle: string = 'default') - : Promise { - if (typeof data.addressFrom === 'undefined') { - throw new Error('DogeTxInputsOutputs.getInputsOutputs requires addressFrom') - } - if (typeof data.addressTo === 'undefined') { - throw new Error('DogeTxInputsOutputs.getInputsOutputs requires addressTo') - } - if (typeof data.amount === 'undefined') { - throw new Error('DogeTxInputsOutputs.getInputsOutputs requires amount') + const filteredUnspents = []; + const unconfirmedBN = new BlocksoftBN(0); + + const isRequired: any = {}; + let isFoundSpeedUp = false; + const filteredBN = new BlocksoftBN(0); + for (const unspent of unspents) { + if ( + typeof data.transactionSpeedUp !== 'undefined' && + unspent.txid === data.transactionSpeedUp + ) { + unspent.isRequired = true; + isFoundSpeedUp = true; + } + if (unspent.isRequired) { + filteredUnspents.push(unspent); + filteredBN.add(unspent.value); + if ( + unspent.isRequired && + typeof isRequired[unspent.txid] === 'undefined' + ) { + isRequired[unspent.txid] = unspent; } - - const filteredUnspents = [] - const unconfirmedBN = new BlocksoftBN(0) - - const isRequired: any = {} - let isFoundSpeedUp = false - const filteredBN = new BlocksoftBN(0) - for (const unspent of unspents) { - if (typeof data.transactionSpeedUp !== 'undefined' && unspent.txid === data.transactionSpeedUp) { - unspent.isRequired = true - isFoundSpeedUp = true - } - if (unspent.isRequired) { - filteredUnspents.push(unspent) - filteredBN.add(unspent.value) - if (unspent.isRequired && typeof isRequired[unspent.txid] === 'undefined') { - isRequired[unspent.txid] = unspent - } - } else { - const diff = BlocksoftUtils.diff(unspent.value, this._minOutputDust) - if (diff * 1 < 0) { - // skip as dust - // @ts-ignore - BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeTxInputsOutputs unspent skipped as dust ' + this._minOutputDust + ' diff ' + diff, unspent) - } else if (!data.useOnlyConfirmed || unspent.confirmations > 0) { - filteredUnspents.push(unspent) - filteredBN.add(unspent.value) - } else { - unconfirmedBN.add(unspent.value) - } - } + } else { + const diff = BlocksoftUtils.diff(unspent.value, this._minOutputDust); + if (diff * 1 < 0) { + // skip as dust + // @ts-ignore + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' DogeTxInputsOutputs unspent skipped as dust ' + + this._minOutputDust + + ' diff ' + + diff, + unspent + ); + } else if (!data.useOnlyConfirmed || unspent.confirmations > 0) { + filteredUnspents.push(unspent); + filteredBN.add(unspent.value); + } else { + unconfirmedBN.add(unspent.value); } + } + } - if (typeof data.transactionSpeedUp !== 'undefined' && !isFoundSpeedUp) { - throw new Error('SERVER_RESPONSE_NO_TX_TO_SPEEDUP') - } + if (typeof data.transactionSpeedUp !== 'undefined' && !isFoundSpeedUp) { + throw new Error('SERVER_RESPONSE_NO_TX_TO_SPEEDUP'); + } - if (filteredUnspents.length === 0 && unspents.length !== 0) { - throw new Error('SERVER_RESPONSE_WAIT_FOR_CONFIRM') - } + if (filteredUnspents.length === 0 && unspents.length !== 0) { + throw new Error('SERVER_RESPONSE_WAIT_FOR_CONFIRM'); + } - const totalBalanceBN = new BlocksoftBN(0) - for (const unspent of filteredUnspents) { - totalBalanceBN.add(unspent.value) - } + const totalBalanceBN = new BlocksoftBN(0); + for (const unspent of filteredUnspents) { + totalBalanceBN.add(unspent.value); + } - let { - multiAddress, - wishedAmountBN, - outputs - } = this._usualTargets(data, unspents) - - if (typeof feeToCount.feeForByte !== 'undefined' && feeToCount.feeForByte !== 'none') { - const result = await this._coinSelect(data, filteredUnspents, feeToCount.feeForByte, multiAddress, subtitle) - if (result.inputs.length > 0) { - return result - } - } + let { multiAddress, wishedAmountBN, outputs } = this._usualTargets( + data, + unspents + ); + + if ( + typeof feeToCount.feeForByte !== 'undefined' && + feeToCount.feeForByte !== 'none' + ) { + const result = await this._coinSelect( + data, + filteredUnspents, + feeToCount.feeForByte, + multiAddress, + subtitle + ); + if (result.inputs.length > 0) { + return result; + } + } + const ic = filteredUnspents.length; + let msg = + 'v20 ' + + subtitle + + ' totalInputs ' + + ic + + ' totalBalance ' + + totalBalanceBN.get() + + ' = ' + + BlocksoftUtils.toUnified(totalBalanceBN.get(), this._settings.decimals) + + ' for wishedAmount ' + + wishedAmountBN.get() + + ' = ' + + BlocksoftUtils.toUnified(wishedAmountBN.get(), this._settings.decimals); + let autocalculateFee = false; + if (typeof feeToCount.feeForAll === 'undefined') { + autocalculateFee = true; + msg += ' and autocalculate feeForByte ' + feeToCount.feeForByte; + } else { + if ( + data.isTransferAll && + typeof feeToCount.feeForAllInputs !== 'undefined' && + feeToCount.feeForAllInputs * 1 > 0 + ) { + feeToCount.feeForAll = BlocksoftUtils.mul( + feeToCount.feeForAll, + Math.ceil(filteredUnspents.length / feeToCount.feeForAllInputs) + ); + wishedAmountBN = new BlocksoftBN( + BlocksoftUtils.diff(totalBalanceBN, feeToCount.feeForAll) + ); + outputs[0].amount = wishedAmountBN.toString(); + msg += ' and isTransferAll inputs counted '; + } + msg += + ' and prefee ' + + feeToCount.feeForAll + + ' = ' + + BlocksoftUtils.toUnified( + feeToCount.feeForAll.toString(), + this._settings.decimals + ); + } + const inputs = []; + const inputsBalanceBN = new BlocksoftBN(0); + + const wishedAmountWithFeeBN = new BlocksoftBN(wishedAmountBN); + const autoFeeBN = new BlocksoftBN(0); + if (autocalculateFee) { + const tmp = BlocksoftUtils.mul( + this.SIZE_FOR_BASIC, + feeToCount.feeForByte + ); + wishedAmountWithFeeBN.add(tmp); + autoFeeBN.add(tmp); + msg += + ' auto => ' + + BlocksoftUtils.toUnified(autoFeeBN.get(), this._settings.decimals); + } else { + wishedAmountWithFeeBN.add(feeToCount.feeForAll); + } - const ic = filteredUnspents.length - let msg = 'v20 ' + subtitle + ' totalInputs ' + ic - + ' totalBalance ' + totalBalanceBN.get() + ' = ' + BlocksoftUtils.toUnified(totalBalanceBN.get(), this._settings.decimals) - + ' for wishedAmount ' + wishedAmountBN.get() + ' = ' + BlocksoftUtils.toUnified(wishedAmountBN.get(), this._settings.decimals) - let autocalculateFee = false - if (typeof feeToCount.feeForAll === 'undefined') { - autocalculateFee = true - msg += ' and autocalculate feeForByte ' + feeToCount.feeForByte - } else { - if (data.isTransferAll && typeof feeToCount.feeForAllInputs !== 'undefined' && feeToCount.feeForAllInputs * 1 > 0 ) { - feeToCount.feeForAll = BlocksoftUtils.mul(feeToCount.feeForAll, Math.ceil(filteredUnspents.length / feeToCount.feeForAllInputs)) - wishedAmountBN = new BlocksoftBN(BlocksoftUtils.diff(totalBalanceBN, feeToCount.feeForAll)) - outputs[0].amount = wishedAmountBN.toString() - msg += ' and isTransferAll inputs counted ' - } - msg += ' and prefee ' + feeToCount.feeForAll + ' = ' + BlocksoftUtils.toUnified(feeToCount.feeForAll.toString(), this._settings.decimals) + for (let i = 0; i < ic; i++) { + if (!data.isTransferAll) { + const tmp = new BlocksoftBN(wishedAmountWithFeeBN).diff( + inputsBalanceBN + ); + if (tmp.lessThanZero()) { + msg += + ' finished by collectedAmount ' + + inputsBalanceBN.get() + + ' = ' + + BlocksoftUtils.toUnified( + inputsBalanceBN.get(), + this._settings.decimals + ); + msg += + ' on wishedAmountWithFee ' + + wishedAmountWithFeeBN.get() + + ' = ' + + BlocksoftUtils.toUnified( + wishedAmountWithFeeBN.get(), + this._settings.decimals + ); + break; } - const inputs = [] - const inputsBalanceBN = new BlocksoftBN(0) - - const wishedAmountWithFeeBN = new BlocksoftBN(wishedAmountBN) - const autoFeeBN = new BlocksoftBN(0) - if (autocalculateFee) { - const tmp = BlocksoftUtils.mul(this.SIZE_FOR_BASIC, feeToCount.feeForByte) - wishedAmountWithFeeBN.add(tmp) - autoFeeBN.add(tmp) - msg += ' auto => ' + BlocksoftUtils.toUnified(autoFeeBN.get(), this._settings.decimals) - } else { - wishedAmountWithFeeBN.add(feeToCount.feeForAll) - } - - for (let i = 0; i < ic; i++) { - if (!data.isTransferAll) { - const tmp = new BlocksoftBN(wishedAmountWithFeeBN).diff(inputsBalanceBN) - if (tmp.lessThanZero()) { - msg += ' finished by collectedAmount ' + inputsBalanceBN.get() + ' = ' + BlocksoftUtils.toUnified(inputsBalanceBN.get(), this._settings.decimals) - msg += ' on wishedAmountWithFee ' + wishedAmountWithFeeBN.get() + ' = ' + BlocksoftUtils.toUnified(wishedAmountWithFeeBN.get(), this._settings.decimals) - break - } - } - const unspent = filteredUnspents[i] - inputs.push(unspent) - inputsBalanceBN.add(unspent.value) - if (typeof isRequired[unspent.txid] !== 'undefined') { - delete isRequired[unspent.txid] - } - if (autocalculateFee) { - let size = this.SIZE_FOR_INPUT - if (typeof unspent.address !== 'undefined' && unspent.address && unspent.address.toString().indexOf('bc1') === 0) { - size = this.SIZE_FOR_BC - } - const tmp2 = BlocksoftUtils.mul(size, feeToCount.feeForByte) - autoFeeBN.add(tmp2) - wishedAmountWithFeeBN.add(tmp2) - msg += ' auto => ' + BlocksoftUtils.toUnified(autoFeeBN.get(), this._settings.decimals) - } + } + const unspent = filteredUnspents[i]; + inputs.push(unspent); + inputsBalanceBN.add(unspent.value); + if (typeof isRequired[unspent.txid] !== 'undefined') { + delete isRequired[unspent.txid]; + } + if (autocalculateFee) { + let size = this.SIZE_FOR_INPUT; + if ( + typeof unspent.address !== 'undefined' && + unspent.address && + unspent.address.toString().indexOf('bc1') === 0 + ) { + size = this.SIZE_FOR_BC; } + const tmp2 = BlocksoftUtils.mul(size, feeToCount.feeForByte); + autoFeeBN.add(tmp2); + wishedAmountWithFeeBN.add(tmp2); + msg += + ' auto => ' + + BlocksoftUtils.toUnified(autoFeeBN.get(), this._settings.decimals); + } + } - for (const txid in isRequired) { - msg += ' txidAdded ' + txid - inputs.push(isRequired[txid]) - inputsBalanceBN.add(isRequired[txid].value) - } + for (const txid in isRequired) { + msg += ' txidAdded ' + txid; + inputs.push(isRequired[txid]); + inputsBalanceBN.add(isRequired[txid].value); + } - const leftForChangeDiff = new BlocksoftBN(inputsBalanceBN).diff(wishedAmountWithFeeBN) - - if (leftForChangeDiff.lessThanZero()) { - if (autocalculateFee) { - const newData = JSON.parse(JSON.stringify(data)) - const autoFeeLimit = BlocksoftUtils.fromUnified(feeToCount.autoFeeLimitReadable, this._settings.decimals) - const autoDiff = new BlocksoftBN(autoFeeLimit).diff(autoFeeBN) - - let recountWithFee = autoFeeBN.get() - if (autoDiff.lessThanZero()) { - recountWithFee = autoFeeLimit.toString() - } - const res = await this._getInputsOutputs(newData, unspents, { feeForAll: recountWithFee }, additionalData, subtitle + ' notEnough1 leftForChangeDiff ' + leftForChangeDiff.toString() + ' //// ') - if (res.msg.indexOf('RECHECK') === -1) { - return res - } - } else if (subtitle.indexOf('notEnough1') !== -1) { - const newData = JSON.parse(JSON.stringify(data)) - const tmp = leftForChangeDiff.get().replace('-', '') - const tmp2 = new BlocksoftBN(data.amount).diff(tmp) - if (!tmp2.lessThanZero()) { - if (this._settings.currencyCode === 'USDT') { - console.log('adsfgadfgadfg') - console.log('tmp2', tmp2) - } else { - newData.amount = tmp2.get() - return this._getInputsOutputs(newData, unspents, feeToCount, additionalData, subtitle + ' notEnough3 ' + data.amount + ' => ' + newData.amount + ' leftForChangeDiff ' + leftForChangeDiff.toString() + ' //// ') - } - } else { - // @ts-ignore - return { - inputs: [], - outputs: [], - msg: subtitle + ' notEnough3Stop' + data.amount + ' => ' + newData.amount + ' leftForChangeDiff ' + leftForChangeDiff.toString() + ' ' + msg, - // @ts-ignore - multiAddress, - countedFor: 'DOGE' - } - } - } - // no change - msg += ' will transfer all but later will RECHECK as change ' + leftForChangeDiff.toString() - return { - inputs, - outputs, - msg, - // @ts-ignore - multiAddress, - countedFor: 'DOGE' - } + const leftForChangeDiff = new BlocksoftBN(inputsBalanceBN).diff( + wishedAmountWithFeeBN + ); + + if (leftForChangeDiff.lessThanZero()) { + if (autocalculateFee) { + const newData = JSON.parse(JSON.stringify(data)); + const autoFeeLimit = BlocksoftUtils.fromUnified( + feeToCount.autoFeeLimitReadable, + this._settings.decimals + ); + const autoDiff = new BlocksoftBN(autoFeeLimit).diff(autoFeeBN); + + let recountWithFee = autoFeeBN.get(); + if (autoDiff.lessThanZero()) { + recountWithFee = autoFeeLimit.toString(); } - - const changeDiff = new BlocksoftBN(leftForChangeDiff).diff(this._minChangeDust) - if (changeDiff.lessThanZero()) { - // no change - msg += ' will transfer all as change ' + leftForChangeDiff.toString() + ' - dust = ' + changeDiff.toString() - return { - inputs, - outputs, - msg, - // @ts-ignore - multiAddress, - countedFor: 'DOGE' - } + const res = await this._getInputsOutputs( + newData, + unspents, + { feeForAll: recountWithFee }, + additionalData, + subtitle + + ' notEnough1 leftForChangeDiff ' + + leftForChangeDiff.toString() + + ' //// ' + ); + if (res.msg.indexOf('RECHECK') === -1) { + return res; } - - - msg += ' will have change as change ' + leftForChangeDiff.toString() + ' = ' + BlocksoftUtils.toUnified(leftForChangeDiff.toString(), this._settings.decimals) - const addressForChange = await this._addressForChange(data) - if (this._builderSettings.changeTogether && addressForChange === data.addressTo) { - leftForChangeDiff.add(outputs[0].amount) - outputs[0].amount = leftForChangeDiff.toString() + } else if (subtitle.indexOf('notEnough1') !== -1) { + const newData = JSON.parse(JSON.stringify(data)); + const tmp = leftForChangeDiff.get().replace('-', ''); + const tmp2 = new BlocksoftBN(data.amount).diff(tmp); + if (!tmp2.lessThanZero()) { + if (this._settings.currencyCode === 'USDT') { + console.log('adsfgadfgadfg'); + console.log('tmp2', tmp2); + } else { + newData.amount = tmp2.get(); + return this._getInputsOutputs( + newData, + unspents, + feeToCount, + additionalData, + subtitle + + ' notEnough3 ' + + data.amount + + ' => ' + + newData.amount + + ' leftForChangeDiff ' + + leftForChangeDiff.toString() + + ' //// ' + ); + } } else { - outputs.push( - { - to: addressForChange, - amount: leftForChangeDiff.toString(), - isChange: true - } as BlocksoftBlockchainTypes.OutputTx - ) - } - return { - inputs, - outputs, - msg, + // @ts-ignore + return { + inputs: [], + outputs: [], + msg: + subtitle + + ' notEnough3Stop' + + data.amount + + ' => ' + + newData.amount + + ' leftForChangeDiff ' + + leftForChangeDiff.toString() + + ' ' + + msg, // @ts-ignore multiAddress, countedFor: 'DOGE' + }; } + } + // no change + msg += + ' will transfer all but later will RECHECK as change ' + + leftForChangeDiff.toString(); + return { + inputs, + outputs, + msg, + // @ts-ignore + multiAddress, + countedFor: 'DOGE' + }; + } + + const changeDiff = new BlocksoftBN(leftForChangeDiff).diff( + this._minChangeDust + ); + if (changeDiff.lessThanZero()) { + // no change + msg += + ' will transfer all as change ' + + leftForChangeDiff.toString() + + ' - dust = ' + + changeDiff.toString(); + return { + inputs, + outputs, + msg, + // @ts-ignore + multiAddress, + countedFor: 'DOGE' + }; + } + + msg += + ' will have change as change ' + + leftForChangeDiff.toString() + + ' = ' + + BlocksoftUtils.toUnified( + leftForChangeDiff.toString(), + this._settings.decimals + ); + const addressForChange = await this._addressForChange(data); + if ( + this._builderSettings.changeTogether && + addressForChange === data.addressTo + ) { + leftForChangeDiff.add(outputs[0].amount); + outputs[0].amount = leftForChangeDiff.toString(); + } else { + outputs.push({ + to: addressForChange, + amount: leftForChangeDiff.toString(), + isChange: true + } as BlocksoftBlockchainTypes.OutputTx); } + return { + inputs, + outputs, + msg, + // @ts-ignore + multiAddress, + countedFor: 'DOGE' + }; + } } diff --git a/crypto/common/AirDAODictTypes.ts b/crypto/common/AirDAODictTypes.ts new file mode 100644 index 000000000..9c8d20145 --- /dev/null +++ b/crypto/common/AirDAODictTypes.ts @@ -0,0 +1,58 @@ +/* eslint-disable @typescript-eslint/no-namespace */ +/** + * @author Ksu + * @version 0.20 + */ +export namespace AirDAODictTypes { + export enum Code { + BTC = 'BTC', + BTC_SEGWIT = 'BTC_SEGWIT', + BTC_SEGWIT_COMPATIBLE = 'BTC_SEGWIT_COMPATIBLE', + ETH_ERC_20 = 'ETH_ERC_20', + USDT = 'USDT', + LTC = 'LTC', + ETH = 'ETH', + ETH_USDT = 'ETH_USDT', + ETH_UAX = 'ETH_UAX', + ETH_TRUE_USD = 'ETH_TRUE_USD', + ETH_BNB = 'ETH_BNB', + ETH_USDC = 'ETH_USDC', + ETH_PAX = 'ETH_PAX', + ETH_DAI = 'ETH_DAI', + ETH_DAIM = 'ETH_DAIM', + TRX = 'TRX', + XMR = 'XMR', + XLM = 'XLM', + BTC_TEST = 'BTC_TEST', + BCH = 'BCH', + BSV = 'BSV', + BTG = 'BTG', + DOGE = 'DOGE', + XVG = 'XVG', + XRP = 'XRP', + ETH_ROPSTEN = 'ETH_ROPSTEN', + ETH_RINKEBY = 'ETH_RINKEBY', + TRX_USDT = 'TRX_USDT', + TRX_BTT = 'TRX_BTT', + ETH_OKB = 'ETH_OKB', + ETH_MKR = 'ETH_MKR', + ETH_KNC = 'ETH_KNC', + ETH_COMP = 'ETH_COMP', + ETH_BAL = 'ETH_BAL', + ETH_LEND = 'ETH_LEND', + ETH_BNT = 'ETH_BNT', + ETH_SOUL = 'ETH_SOUL', + ETH_ONE = 'ETH_ONE', + FIO = 'FIO', + BNB = 'BNB', + BNB_SMART = 'BNB_SMART', + BNB_SMART_20 = 'BNB_SMART_20', + ETC = 'ETC', + VET = 'VET', + SOL = 'SOL', + WAVES = 'WAVES', + ASH = 'ASH', + METIS = 'METIS', + SOL_SPL = 'SOL_SPL' + } +} diff --git a/crypto/common/BlocksoftDict.js b/crypto/common/BlocksoftDict.js index f1a667fa5..cd1f4d52e 100644 --- a/crypto/common/BlocksoftDict.js +++ b/crypto/common/BlocksoftDict.js @@ -1,79 +1,101 @@ -import { NativeModules } from 'react-native' +import { NativeModules } from 'react-native'; -import Database from '@app/appstores/DataSource/Database' +import Database from '@app/appstores/DataSource/Database'; -import CoinBlocksoftDict from '@crypto/assets/coinBlocksoftDict.json' +import CoinBlocksoftDict from '@crypto/assets/coinBlocksoftDict.json'; -const { RNFastCrypto } = NativeModules +const { RNFastCrypto } = NativeModules; const VisibleCodes = [ - 'CASHBACK', 'NFT', 'BTC', 'ETH', 'TRX', 'TRX_USDT' // add code here to show on start screen -] + 'CASHBACK', + 'NFT', + 'BTC', + 'ETH', + 'TRX', + 'TRX_USDT' // add code here to show on start screen +]; const Codes = [ - 'CASHBACK', 'NFT', 'BTC', 'ETH', 'USDT', 'LTC', 'ETH_USDT', 'TRX', 'TRX_USDT', 'BNB', 'BNB_SMART', 'MATIC', 'ETH_TRUE_USD', 'ETH_BNB', 'ETH_USDC', 'ETH_PAX', 'ETH_DAI', 'FIO' // add code here for autocreation the wallet address with the currency -] -const Currencies = CoinBlocksoftDict + 'CASHBACK', + 'NFT', + 'BTC', + 'ETH', + 'USDT', + 'LTC', + 'ETH_USDT', + 'TRX', + 'TRX_USDT', + 'BNB', + 'BNB_SMART', + 'MATIC', + 'ETH_TRUE_USD', + 'ETH_BNB', + 'ETH_USDC', + 'ETH_PAX', + 'ETH_DAI', + 'FIO' // add code here for autocreation the wallet address with the currency +]; +const Currencies = CoinBlocksoftDict; const CurrenciesForTests = { - 'BTC_SEGWIT': { - currencyName: 'Bitcoin Segwit', - currencyCode: 'BTC_SEGWIT', - currencySymbol: 'BTC', - addressProcessor: 'BTC_SEGWIT', - extendsProcessor: 'BTC', - ratesCurrencyCode: 'BTC', - addressPrefix: 'bc1', - defaultPath: `m/84'/0'/0'/0/0` - }, - 'BTC_SEGWIT_COMPATIBLE': { - currencyName: 'Bitcoin Compatible Segwit', - currencyCode: 'BTC_SEGWIT_COMPATIBLE', - currencySymbol: 'BTC', - addressProcessor: 'BTC_SEGWIT_COMPATIBLE', - extendsProcessor: 'BTC', - ratesCurrencyCode: 'BTC', - addressPrefix: '3', - defaultPath: `m/49'/0'/0'/0/1` - }, - 'LTC_SEGWIT': { - currencyName: 'Bitcoin Segwit', - currencyCode: 'LTC_SEGWIT', - currencySymbol: 'LTC', - addressProcessor: 'LTC_SEGWIT', - extendsProcessor: 'LTC', - ratesCurrencyCode: 'LTC', - addressPrefix: 'ltc', - defaultPath: `m/84'/2'/0'/0/0` - }, - 'ETH_ROPSTEN_KSU_TOKEN': { - currencyName: 'Some ERC-20 Ropsten', - currencyCode: 'ETH_ROPSTEN_KSU_TOKEN', - currencySymbol: 'Some ERC-20 Ropsten', - extendsProcessor: 'ETH_TRUE_USD', - addressCurrencyCode: 'ETH_ROPSTEN', - feesCurrencyCode: 'ETH_ROPSTEN', - network: 'ropsten', - decimals: 6, - tokenAddress: '0xdb30610f156e1d4aefaa9b4423909297ceff64c2', - currencyExplorerLink: 'https:ropsten.etherscan.io/address/', - currencyExplorerTxLink: 'https:ropsten.etherscan.io/tx/' - }, - 'TRX_PLANET': { - currencyName: 'PLANET (FREE TRX TOKEN)', - currencyCode: 'TRX_PLANET', - currencySymbol: 'PLANET TRX', - extendsProcessor: 'TRX_USDT', - feesCurrencyCode: 'TRX', // pay for tx in other currency, if no - used currencyCode - network: 'trx', // network also used as mark of rate scanning - decimals: 6, - tokenName: '1002742', - currencyExplorerLink: 'https://tronscan.org/#/address/', - currencyExplorerTxLink: 'https://tronscan.org/#/transaction/' - } -} + BTC_SEGWIT: { + currencyName: 'Bitcoin Segwit', + currencyCode: 'BTC_SEGWIT', + currencySymbol: 'BTC', + addressProcessor: 'BTC_SEGWIT', + extendsProcessor: 'BTC', + ratesCurrencyCode: 'BTC', + addressPrefix: 'bc1', + defaultPath: `m/84'/0'/0'/0/0` + }, + BTC_SEGWIT_COMPATIBLE: { + currencyName: 'Bitcoin Compatible Segwit', + currencyCode: 'BTC_SEGWIT_COMPATIBLE', + currencySymbol: 'BTC', + addressProcessor: 'BTC_SEGWIT_COMPATIBLE', + extendsProcessor: 'BTC', + ratesCurrencyCode: 'BTC', + addressPrefix: '3', + defaultPath: `m/49'/0'/0'/0/1` + }, + LTC_SEGWIT: { + currencyName: 'Bitcoin Segwit', + currencyCode: 'LTC_SEGWIT', + currencySymbol: 'LTC', + addressProcessor: 'LTC_SEGWIT', + extendsProcessor: 'LTC', + ratesCurrencyCode: 'LTC', + addressPrefix: 'ltc', + defaultPath: `m/84'/2'/0'/0/0` + }, + ETH_ROPSTEN_KSU_TOKEN: { + currencyName: 'Some ERC-20 Ropsten', + currencyCode: 'ETH_ROPSTEN_KSU_TOKEN', + currencySymbol: 'Some ERC-20 Ropsten', + extendsProcessor: 'ETH_TRUE_USD', + addressCurrencyCode: 'ETH_ROPSTEN', + feesCurrencyCode: 'ETH_ROPSTEN', + network: 'ropsten', + decimals: 6, + tokenAddress: '0xdb30610f156e1d4aefaa9b4423909297ceff64c2', + currencyExplorerLink: 'https:ropsten.etherscan.io/address/', + currencyExplorerTxLink: 'https:ropsten.etherscan.io/tx/' + }, + TRX_PLANET: { + currencyName: 'PLANET (FREE TRX TOKEN)', + currencyCode: 'TRX_PLANET', + currencySymbol: 'PLANET TRX', + extendsProcessor: 'TRX_USDT', + feesCurrencyCode: 'TRX', // pay for tx in other currency, if no - used currencyCode + network: 'trx', // network also used as mark of rate scanning + decimals: 6, + tokenName: '1002742', + currencyExplorerLink: 'https://tronscan.org/#/address/', + currencyExplorerTxLink: 'https://tronscan.org/#/transaction/' + } +}; if (typeof RNFastCrypto === 'undefined') { - delete Currencies['XMR'] + delete Currencies['XMR']; } /** @@ -88,128 +110,148 @@ if (typeof RNFastCrypto === 'undefined') { * @param {string} currencyObject.currencyCode 'OMG' */ function addAndUnifyCustomCurrency(currencyObject) { - const tmp = { - currencyName: currencyObject.currencyName, - currencyCode: 'CUSTOM_' + currencyObject.currencyCode, - currencySymbol: currencyObject.currencySymbol, - ratesCurrencyCode: currencyObject.currencyCode, - decimals: currencyObject.tokenDecimals - + const tmp = { + currencyName: currencyObject.currencyName, + currencyCode: 'CUSTOM_' + currencyObject.currencyCode, + currencySymbol: currencyObject.currencySymbol, + ratesCurrencyCode: currencyObject.currencyCode, + decimals: currencyObject.tokenDecimals + }; + tmp.currencyType = 'custom'; + if (currencyObject.tokenType === 'BNB_SMART_20') { + tmp.currencyCode = 'CUSTOM_BNB_SMART_20_' + currencyObject.currencyCode; + if (tmp.ratesCurrencyCode.substr(0, 1) === 'B') { + const subRate = tmp.ratesCurrencyCode.substr(1); + if (typeof Currencies[subRate] !== 'undefined') { + tmp.ratesCurrencyCode = subRate; + } } - tmp.currencyType = 'custom' - if (currencyObject.tokenType === 'BNB_SMART_20') { - tmp.currencyCode = 'CUSTOM_BNB_SMART_20_' + currencyObject.currencyCode - if (tmp.ratesCurrencyCode.substr(0, 1) === 'B') { - const subRate = tmp.ratesCurrencyCode.substr(1) - if (typeof Currencies[subRate] !== 'undefined') { - tmp.ratesCurrencyCode = subRate - } - } - tmp.extendsProcessor = 'BNB_SMART_CAKE' - tmp.addressUiChecker = 'ETH' - tmp.tokenAddress = currencyObject.tokenAddress - tmp.tokenBlockchain = 'BNB' - tmp.currencyExplorerLink = 'https://bscscan.com/token/' + currencyObject.tokenAddress + '?a=' - } else if (currencyObject.tokenType === 'MATIC_ERC_20') { - tmp.currencyCode = 'CUSTOM_MATIC_ERC_20_' + currencyObject.currencyCode - tmp.extendsProcessor = 'MATIC_USDT' - tmp.addressUiChecker = 'ETH' - tmp.tokenAddress = currencyObject.tokenAddress - tmp.tokenBlockchain = 'MATIC' - tmp.currencyExplorerLink = 'https://polygonscan.com/token/' + currencyObject.tokenAddress + '?a=' - } else if (currencyObject.tokenType === 'FTM_ERC_20') { - tmp.currencyCode = 'CUSTOM_FTM_ERC_20_' + currencyObject.currencyCode - tmp.extendsProcessor = 'FTM_USDC' - tmp.addressUiChecker = 'ETH' - tmp.tokenAddress = currencyObject.tokenAddress - tmp.tokenBlockchain = 'FTM' - tmp.currencyExplorerLink = 'https://ftmscan.com/token/' + currencyObject.tokenAddress + '?a=' - } else if (currencyObject.tokenType === 'VLX_ERC_20') { - tmp.currencyCode = 'CUSTOM_VLX_ERC_20_' + currencyObject.currencyCode - tmp.extendsProcessor = 'VLX_USDT' - tmp.addressUiChecker = 'ETH' - tmp.tokenAddress = currencyObject.tokenAddress - tmp.tokenBlockchain = 'VLX' - tmp.currencyExplorerLink = 'https://evmexplorer.velas.com/tokens/' + currencyObject.tokenAddress - } else if (currencyObject.tokenType === 'ONE_ERC_20') { - tmp.currencyCode = 'CUSTOM_ONE_ERC_20_' + currencyObject.currencyCode - tmp.extendsProcessor = 'ONE_USDC' - tmp.addressUiChecker = 'ETH' - tmp.tokenAddress = currencyObject.tokenAddress - tmp.tokenBlockchain = 'ONE' - tmp.currencyExplorerLink = 'https://explorer.harmony.one/address/' + currencyObject.tokenAddress - } else if (currencyObject.tokenType === 'SOL') { - tmp.currencyCode = 'CUSTOM_SOL_' + currencyObject.currencyCode - tmp.extendsProcessor = 'SOL_RAY' - tmp.addressUiChecker = 'SOL' - tmp.tokenAddress = currencyObject.tokenAddress - tmp.tokenBlockchain = 'SOLANA' - } else if (currencyObject.tokenType === 'ETH_ERC_20') { - tmp.extendsProcessor = 'ETH_TRUE_USD' - tmp.addressUiChecker = 'ETH' - tmp.tokenAddress = currencyObject.tokenAddress - tmp.tokenBlockchain = 'ETHEREUM' - tmp.currencyExplorerLink = 'https://etherscan.io/token/' + currencyObject.tokenAddress + '?a=' - } else if (currencyObject.tokenType === 'TRX') { - tmp.currencyCode = 'CUSTOM_TRX_' + currencyObject.currencyCode - tmp.extendsProcessor = 'TRX_USDT' - tmp.addressUiChecker = 'TRX' - tmp.currencyIcon = 'TRX' - tmp.tokenName = currencyObject.tokenAddress - tmp.tokenBlockchain = 'TRON' - tmp.currencyExplorerLink = 'https://tronscan.org/#/address/' - tmp.currencyExplorerTxLink = 'https://tronscan.org/#/transaction/' - if (tmp.tokenName.substr(0, 1) !== 'T') { - this.skipParentBalanceCheck = true - } - } else { - return false + tmp.extendsProcessor = 'BNB_SMART_CAKE'; + tmp.addressUiChecker = 'ETH'; + tmp.tokenAddress = currencyObject.tokenAddress; + tmp.tokenBlockchain = 'BNB'; + tmp.currencyExplorerLink = + 'https://bscscan.com/token/' + currencyObject.tokenAddress + '?a='; + } else if (currencyObject.tokenType === 'MATIC_ERC_20') { + tmp.currencyCode = 'CUSTOM_MATIC_ERC_20_' + currencyObject.currencyCode; + tmp.extendsProcessor = 'MATIC_USDT'; + tmp.addressUiChecker = 'ETH'; + tmp.tokenAddress = currencyObject.tokenAddress; + tmp.tokenBlockchain = 'MATIC'; + tmp.currencyExplorerLink = + 'https://polygonscan.com/token/' + currencyObject.tokenAddress + '?a='; + } else if (currencyObject.tokenType === 'FTM_ERC_20') { + tmp.currencyCode = 'CUSTOM_FTM_ERC_20_' + currencyObject.currencyCode; + tmp.extendsProcessor = 'FTM_USDC'; + tmp.addressUiChecker = 'ETH'; + tmp.tokenAddress = currencyObject.tokenAddress; + tmp.tokenBlockchain = 'FTM'; + tmp.currencyExplorerLink = + 'https://ftmscan.com/token/' + currencyObject.tokenAddress + '?a='; + } else if (currencyObject.tokenType === 'VLX_ERC_20') { + tmp.currencyCode = 'CUSTOM_VLX_ERC_20_' + currencyObject.currencyCode; + tmp.extendsProcessor = 'VLX_USDT'; + tmp.addressUiChecker = 'ETH'; + tmp.tokenAddress = currencyObject.tokenAddress; + tmp.tokenBlockchain = 'VLX'; + tmp.currencyExplorerLink = + 'https://evmexplorer.velas.com/tokens/' + currencyObject.tokenAddress; + } else if (currencyObject.tokenType === 'ONE_ERC_20') { + tmp.currencyCode = 'CUSTOM_ONE_ERC_20_' + currencyObject.currencyCode; + tmp.extendsProcessor = 'ONE_USDC'; + tmp.addressUiChecker = 'ETH'; + tmp.tokenAddress = currencyObject.tokenAddress; + tmp.tokenBlockchain = 'ONE'; + tmp.currencyExplorerLink = + 'https://explorer.harmony.one/address/' + currencyObject.tokenAddress; + } else if (currencyObject.tokenType === 'SOL') { + tmp.currencyCode = 'CUSTOM_SOL_' + currencyObject.currencyCode; + tmp.extendsProcessor = 'SOL_RAY'; + tmp.addressUiChecker = 'SOL'; + tmp.tokenAddress = currencyObject.tokenAddress; + tmp.tokenBlockchain = 'SOLANA'; + } else if (currencyObject.tokenType === 'ETH_ERC_20') { + tmp.extendsProcessor = 'ETH_TRUE_USD'; + tmp.addressUiChecker = 'ETH'; + tmp.tokenAddress = currencyObject.tokenAddress; + tmp.tokenBlockchain = 'ETHEREUM'; + tmp.currencyExplorerLink = + 'https://etherscan.io/token/' + currencyObject.tokenAddress + '?a='; + } else if (currencyObject.tokenType === 'TRX') { + tmp.currencyCode = 'CUSTOM_TRX_' + currencyObject.currencyCode; + tmp.extendsProcessor = 'TRX_USDT'; + tmp.addressUiChecker = 'TRX'; + tmp.currencyIcon = 'TRX'; + tmp.tokenName = currencyObject.tokenAddress; + tmp.tokenBlockchain = 'TRON'; + tmp.currencyExplorerLink = 'https://tronscan.org/#/address/'; + tmp.currencyExplorerTxLink = 'https://tronscan.org/#/transaction/'; + if (tmp.tokenName.substr(0, 1) !== 'T') { + this.skipParentBalanceCheck = true; } + } else { + return false; + } - Currencies[tmp.currencyCode] = tmp - return tmp + Currencies[tmp.currencyCode] = tmp; + return tmp; } -const ALL_SETTINGS = {} +const ALL_SETTINGS = {}; function getCurrencyAllSettings(currencyCodeOrObject, source = '') { - let currencyCode = currencyCodeOrObject - if (typeof currencyCode === 'undefined' || !currencyCode) { - return false - } - if (currencyCode === 'ETH_LAND') { - Database.query(`DELETE FROM account WHERE currency_code='ETH_LAND'`) - Database.query(`DELETE FROM account_balance WHERE currency_code='ETH_LAND'`) - Database.query(`DELETE FROM currency WHERE currency_code='ETH_LAND'`) - } + let currencyCode = currencyCodeOrObject; + if (typeof currencyCode === 'undefined' || !currencyCode) { + return false; + } + if (currencyCode === 'ETH_LAND') { + Database.query(`DELETE FROM account WHERE currency_code='ETH_LAND'`); + Database.query( + `DELETE FROM account_balance WHERE currency_code='ETH_LAND'` + ); + Database.query(`DELETE FROM currency WHERE currency_code='ETH_LAND'`); + } - if (typeof currencyCodeOrObject.currencyCode !== 'undefined') { - currencyCode = currencyCodeOrObject.currencyCode - } - if (typeof ALL_SETTINGS[currencyCode] !== 'undefined') { - return ALL_SETTINGS[currencyCode] - } + if (typeof currencyCodeOrObject.currencyCode !== 'undefined') { + currencyCode = currencyCodeOrObject.currencyCode; + } + if (typeof ALL_SETTINGS[currencyCode] !== 'undefined') { + return ALL_SETTINGS[currencyCode]; + } - let settings = Currencies[currencyCode] - if (!settings) { - settings = CurrenciesForTests[currencyCode] - } - if (!settings) { - throw new Error('Currency code not found in dict ' + JSON.stringify(currencyCode) + ' from ' + source) + let settings = Currencies[currencyCode]; + if (!settings) { + settings = CurrenciesForTests[currencyCode]; + } + if (!settings) { + throw new Error( + 'Currency code not found in dict ' + + JSON.stringify(currencyCode) + + ' from ' + + source + ); + } + if (settings.extendsProcessor && Currencies[settings.extendsProcessor]) { + const settingsParent = Currencies[settings.extendsProcessor]; + let newKey; + for (newKey of Object.keys(settingsParent)) { + if ( + typeof settings[newKey] === 'undefined' || + settings[newKey] === false + ) { + settings[newKey] = settingsParent[newKey]; + } } - if (settings.extendsProcessor && Currencies[settings.extendsProcessor]) { - const settingsParent = Currencies[settings.extendsProcessor] - let newKey - for (newKey of Object.keys(settingsParent)) { - if (typeof settings[newKey] === 'undefined' || settings[newKey] === false) { - settings[newKey] = settingsParent[newKey] - } - } - } - ALL_SETTINGS[currencyCode] = settings - return settings + } + ALL_SETTINGS[currencyCode] = settings; + return settings; } export default { - VisibleCodes, Codes, Currencies, CurrenciesForTests, getCurrencyAllSettings, addAndUnifyCustomCurrency -} + VisibleCodes, + Codes, + Currencies, + CurrenciesForTests, + getCurrencyAllSettings, + addAndUnifyCustomCurrency +}; diff --git a/crypto/common/BlocksoftDictTypes.ts b/crypto/common/BlocksoftDictTypes.ts deleted file mode 100644 index baef64fde..000000000 --- a/crypto/common/BlocksoftDictTypes.ts +++ /dev/null @@ -1,56 +0,0 @@ -/** - * @author Ksu - * @version 0.20 - */ -export namespace BlocksoftDictTypes { - - export enum Code { - BTC = 'BTC', - BTC_SEGWIT = 'BTC_SEGWIT', - BTC_SEGWIT_COMPATIBLE = 'BTC_SEGWIT_COMPATIBLE', - ETH_ERC_20 = 'ETH_ERC_20', - USDT = 'USDT', - LTC = 'LTC', - ETH = 'ETH', - ETH_USDT = 'ETH_USDT', - ETH_UAX = 'ETH_UAX', - ETH_TRUE_USD = 'ETH_TRUE_USD', - ETH_BNB = 'ETH_BNB', - ETH_USDC = 'ETH_USDC', - ETH_PAX = 'ETH_PAX', - ETH_DAI = 'ETH_DAI', - ETH_DAIM = 'ETH_DAIM', - TRX = 'TRX', - XMR = 'XMR', - XLM = 'XLM', - BTC_TEST = 'BTC_TEST', - BCH = 'BCH', - BSV = 'BSV', - BTG = 'BTG', - DOGE = 'DOGE', - XVG = 'XVG', - XRP = 'XRP', - ETH_ROPSTEN = 'ETH_ROPSTEN', - ETH_RINKEBY = 'ETH_RINKEBY', - TRX_USDT = 'TRX_USDT', - TRX_BTT = 'TRX_BTT', - ETH_OKB = 'ETH_OKB', - ETH_MKR = 'ETH_MKR', - ETH_KNC = 'ETH_KNC', - ETH_COMP = 'ETH_COMP', - ETH_BAL = 'ETH_BAL', - ETH_LEND = 'ETH_LEND', - ETH_BNT = 'ETH_BNT', - ETH_SOUL = 'ETH_SOUL', - ETH_ONE = 'ETH_ONE', - FIO = 'FIO', - BNB = 'BNB', - BNB_SMART = 'BNB_SMART', - BNB_SMART_20 = 'BNB_SMART_20', - ETC = 'ETC', - VET = 'VET', - SOL = 'SOL', - WAVES = 'WAVES', - ASH = 'ASH' - } -} diff --git a/crypto/common/BlocksoftPrettyNumbers.js b/crypto/common/BlocksoftPrettyNumbers.js index 9d70af655..6c80bc9bf 100644 --- a/crypto/common/BlocksoftPrettyNumbers.js +++ b/crypto/common/BlocksoftPrettyNumbers.js @@ -1,165 +1,185 @@ -import BlocksoftDict from './BlocksoftDict' -import BlocksoftUtils from './BlocksoftUtils' +import BlocksoftDict from './BlocksoftDict'; +import BlocksoftUtils from './BlocksoftUtils'; class BlocksoftPrettyNumbers { + /** + * @param {string} currencyCode + * @return {BlocksoftPrettyNumbers} + */ + setCurrencyCode(currencyCode) { + const settings = BlocksoftDict.getCurrencyAllSettings(currencyCode); + if (settings.prettyNumberProcessor) { + this._processorCode = settings.prettyNumberProcessor; + } else { + throw new Error( + 'BlocksoftDict.getCurrencyAllSettings no settings.prettyNumberProcessor for ' + + currencyCode + ); + } + if ( + typeof settings.decimals !== 'undefined' && + settings.decimals !== false + ) { + this._decimals = settings.decimals; + } else { + throw new Error( + 'BlocksoftDict.getCurrencyAllSettings no settings.decimals for ' + + currencyCode + ); + } - /** - * @param {string} currencyCode - * @return {BlocksoftPrettyNumbers} - */ - setCurrencyCode(currencyCode) { - const settings = BlocksoftDict.getCurrencyAllSettings(currencyCode) - if (settings.prettyNumberProcessor) { - this._processorCode = settings.prettyNumberProcessor - } else { - throw new Error('BlocksoftDict.getCurrencyAllSettings no settings.prettyNumberProcessor for ' + currencyCode) - } - if (typeof settings.decimals !== 'undefined' && settings.decimals !== false) { - this._decimals = settings.decimals - } else { - throw new Error('BlocksoftDict.getCurrencyAllSettings no settings.decimals for ' + currencyCode) - } + return this; + } - return this + /** + * @param {string|number} number + * @return {string} + */ + makePretty(number, source = '') { + if (this._processorCode === 'USDT') { + return number; } + const str = number.toString(); + if (str.indexOf('.') !== -1 || str.indexOf(',') !== -1) { + number = str.split('.')[0]; + } + if (this._processorCode === 'ETH') { + return BlocksoftUtils.toEther(number); + } else if (this._processorCode === 'BTC') { + return BlocksoftUtils.toBtc(number); + } else if ( + this._processorCode === 'ETH_ERC_20' || + this._processorCode === 'UNIFIED' + ) { + // console.log('makePretty ' + JSON.stringify(number) + ' source ' + source) + return BlocksoftUtils.toUnified(number, this._decimals); + } + throw new Error('undefined BlocksoftPrettyNumbers processor to makePretty'); + } - /** - * @param {string|number} number - * @return {string} - */ - makePretty(number, source = '') { - if (this._processorCode === 'USDT') { - return number - } - const str = number.toString() - if (str.indexOf('.') !== -1 || str.indexOf(',') !== -1) { - number = str.split('.')[0] - } - if (this._processorCode === 'ETH') { - return BlocksoftUtils.toEther(number) - } else if (this._processorCode === 'BTC') { - return BlocksoftUtils.toBtc(number) - } else if (this._processorCode === 'ETH_ERC_20' || this._processorCode === 'UNIFIED') { - // console.log('makePretty ' + JSON.stringify(number) + ' source ' + source) - return BlocksoftUtils.toUnified(number, this._decimals) + makeCut(tmp, size = 5, source = false, useDict = true) { + if (this._decimals <= 6 && size === 5 && this._decimals > 0 && useDict) { + size = this._decimals; + } + let cutted = 0; + let isSatoshi = false; + if (typeof tmp === 'undefined' || !tmp) { + return { cutted, isSatoshi, justCutted: cutted, separated: '0' }; + } + const splitted = tmp.toString().split('.'); + const def = '0.' + '0'.repeat(size); + let firstPart = false; + let secondPart = false; + if (splitted[0] === '0') { + if (typeof splitted[1] !== 'undefined' && splitted[1]) { + cutted = splitted[0] + '.' + splitted[1].substr(0, size); + if (cutted === def) { + cutted = splitted[0] + '.' + splitted[1].substr(0, size * 2); + const def2 = '0.' + '0'.repeat(size * 2); + if (cutted !== def2) { + secondPart = splitted[1].substr(0, size * 2); + } + isSatoshi = true; } - throw new Error('undefined BlocksoftPrettyNumbers processor to makePretty') + } else { + cutted = '0'; + } + } else if (typeof splitted[1] !== 'undefined' && splitted[1]) { + const second = splitted[1].substr(0, size); + if (second !== '0'.repeat(size)) { + cutted = splitted[0] + '.' + second; + secondPart = second; + } else { + cutted = splitted[0]; + } + firstPart = splitted[0] + ''; + } else { + cutted = splitted[0]; + firstPart = splitted[0] + ''; + } + let justCutted = isSatoshi ? '0' : cutted; + if (justCutted === def) { + justCutted = '0'; } - makeCut(tmp, size = 5, source = false, useDict = true) { - if (this._decimals <= 6 && size === 5 && this._decimals > 0 && useDict) { - size = this._decimals - } - let cutted = 0 - let isSatoshi = false - if (typeof tmp === 'undefined' || !tmp) { - return { cutted, isSatoshi, justCutted: cutted, separated: '0' } - } - const splitted = tmp.toString().split('.') - const def = '0.' + '0'.repeat(size) - let firstPart = false - let secondPart = false - if (splitted[0] === '0') { - if (typeof splitted[1] !== 'undefined' && splitted[1]) { - cutted = splitted[0] + '.' + splitted[1].substr(0, size) - if (cutted === def) { - cutted = splitted[0] + '.' + splitted[1].substr(0, size * 2) - const def2 = '0.' + '0'.repeat(size * 2) - if (cutted !== def2) { - secondPart = splitted[1].substr(0, size * 2) - } - isSatoshi = true - } - } else { - cutted = '0' - } - } else if (typeof splitted[1] !== 'undefined' && splitted[1]) { - const second = splitted[1].substr(0, size) - if (second !== '0'.repeat(size)) { - cutted = splitted[0] + '.' + second - secondPart = second - } else { - cutted = splitted[0] - } - firstPart = splitted[0] + '' + if (secondPart) { + for (let i = secondPart.length; i--; i >= 0) { + if (typeof secondPart[i] === 'undefined' || secondPart[i] === '0') { + secondPart = secondPart.substr(0, i); } else { - cutted = splitted[0] - firstPart = splitted[0] + '' - } - let justCutted = isSatoshi ? '0' : cutted - if (justCutted === def) { - justCutted = '0' - } - - if (secondPart) { - for (let i = secondPart.length; i--; i >= 0) { - if (typeof secondPart[i] === 'undefined' || secondPart[i] === '0') { - secondPart = secondPart.substr(0, i) - } else { - break - } - } + break; } - - let separated = justCutted - let separatedForInput = false - if (firstPart) { - const len = firstPart.length - if (len > 3) { - separated = '' - let j = 0 - for (let i = len - 1; i >= 0; i--) { - if (j === 3) { - separated = ' ' + separated - j = 0 - } - j++ - separated = firstPart[i] + separated - } - } else { - separated = firstPart - } - separatedForInput = separated.toString() - if (secondPart) { - separated += '.' + secondPart - } - } else if (secondPart) { - separated = '0.' + secondPart - separatedForInput = '0' - } - if (separatedForInput === false) { - separatedForInput = justCutted - } else if (typeof splitted[1] !== 'undefined') { - separatedForInput += '.' + splitted[1] - } - - return { cutted, isSatoshi, justCutted, separated: separated.toString(), separatedForInput } + } } - /** - * @param {string} value - * @return {string} - */ - makeUnPretty(value) { - const number = value.toString().replace(' ', '') - try { - if (this._processorCode === 'USDT') { - return number - } else if (this._processorCode === 'ETH') { - return BlocksoftUtils.toWei(number) - } else if (this._processorCode === 'BTC') { - return BlocksoftUtils.toSatoshi(number) - } else if (this._processorCode === 'ETH_ERC_20' || this._processorCode === 'UNIFIED') { - return BlocksoftUtils.fromUnified(number, this._decimals) - } - } catch (e) { - e.message += 'in makeUnPretty' - throw e + let separated = justCutted; + let separatedForInput = false; + if (firstPart) { + const len = firstPart.length; + if (len > 3) { + separated = ''; + let j = 0; + for (let i = len - 1; i >= 0; i--) { + if (j === 3) { + separated = ' ' + separated; + j = 0; + } + j++; + separated = firstPart[i] + separated; } - throw new Error('undefined BlocksoftPrettyNumbers processor to makeUnPretty') + } else { + separated = firstPart; + } + separatedForInput = separated.toString(); + if (secondPart) { + separated += '.' + secondPart; + } + } else if (secondPart) { + separated = '0.' + secondPart; + separatedForInput = '0'; + } + if (separatedForInput === false) { + separatedForInput = justCutted; + } else if (typeof splitted[1] !== 'undefined') { + separatedForInput += '.' + splitted[1]; } + return { + cutted, + isSatoshi, + justCutted, + separated: separated.toString(), + separatedForInput + }; + } + /** + * @param {string} value + * @return {string} + */ + makeUnPretty(value) { + const number = value.toString().replace(' ', ''); + try { + if (this._processorCode === 'USDT') { + return number; + } else if (this._processorCode === 'ETH') { + return BlocksoftUtils.toWei(number); + } else if (this._processorCode === 'BTC') { + return BlocksoftUtils.toSatoshi(number); + } else if ( + this._processorCode === 'ETH_ERC_20' || + this._processorCode === 'UNIFIED' + ) { + return BlocksoftUtils.fromUnified(number, this._decimals); + } + } catch (e) { + e.message += 'in makeUnPretty'; + throw e; + } + throw new Error( + 'undefined BlocksoftPrettyNumbers processor to makeUnPretty' + ); + } } -export default new BlocksoftPrettyNumbers() +export default new BlocksoftPrettyNumbers(); diff --git a/package.json b/package.json index cb4f65883..5202205eb 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ "bitcoinjs-lib": "git+https://git@github.com/trustee-wallet/bitcoinjs-lib", "bs58check": "^3.0.1", "buffer": "^6.0.3", + "create-hash": "^1.2.0", "expo": "~48.0.18", "expo-barcode-scanner": "~12.3.2", "expo-build-properties": "~0.6.0", @@ -67,6 +68,7 @@ "moment": "^2.29.4", "patch-package": "^7.0.0", "postinstall-postinstall": "^2.1.0", + "process": "^0.11.10", "react": "18.2.0", "react-native": "0.71.8", "react-native-calendar-picker": "^7.1.4", diff --git a/src/lib/BlocksoftKeys.js b/src/lib/BlocksoftKeys.js index 251022c05..b50a4898a 100644 --- a/src/lib/BlocksoftKeys.js +++ b/src/lib/BlocksoftKeys.js @@ -2,49 +2,45 @@ * @author Ksu * @version 0.5 */ -import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog' -import BlocksoftDict from '@crypto/common/BlocksoftDict' -import BlocksoftKeysUtils from '@crypto/actions/BlocksoftKeys/BlocksoftKeysUtils' +import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; +import BlocksoftDict from '@crypto/common/BlocksoftDict'; +import BlocksoftKeysUtils from '@crypto/actions/BlocksoftKeys/BlocksoftKeysUtils'; +import * as BlocksoftRandom from 'react-native-blocksoft-random'; +import BlocksoftDispatcher from '../../blockchains/BlocksoftDispatcher'; +import BlocksoftKeysScam from '@crypto/actions/BlocksoftKeys/BlocksoftKeysScam'; +import { strings } from '@app/services/i18n'; -import * as BlocksoftRandom from 'react-native-blocksoft-random' -import BlocksoftDispatcher from '../../blockchains/BlocksoftDispatcher' -import BlocksoftKeysScam from '@crypto/actions/BlocksoftKeys/BlocksoftKeysScam' -import { strings } from '@app/services/i18n' +const bip32 = require('bip32'); +const bip39 = require('bip39'); +const bip44Constants = require('../../common/ext/bip44-constants'); +const networksConstants = require('../../common/ext/networks-constants'); -const bip32 = require('bip32') -const bip39 = require('bip39') -const bip44Constants = require('../../common/ext/bip44-constants') -const networksConstants = require('../../common/ext/networks-constants') - -const bs58check = require('bs58check') - - -const ETH_CACHE = {} -const CACHE = {} -const CACHE_ROOTS = {} +const bs58check = require('bs58check'); +const ETH_CACHE = {}; +const CACHE = {}; +const CACHE_ROOTS = {}; class BlocksoftKeys { - - constructor() { - this._bipHex = {} - let currency - for (currency of bip44Constants) { - this._bipHex[currency[1]] = currency[0] - } - this._getRandomBytesFunction = BlocksoftRandom.getRandomBytes + constructor() { + this._bipHex = {}; + let currency; + for (currency of bip44Constants) { + this._bipHex[currency[1]] = currency[0]; } - - /** - * create new mnemonic object (also gives hash to store in public db) - * @param {int} size - * @return {Promise<{mnemonic: string, hash: string}>} - */ - async newMnemonic(size = 256) { - BlocksoftCryptoLog.log(`BlocksoftKeys newMnemonic called`) - - /* let mnemonic = false + this._getRandomBytesFunction = BlocksoftRandom.getRandomBytes; + } + + /** + * create new mnemonic object (also gives hash to store in public db) + * @param {int} size + * @return {Promise<{mnemonic: string, hash: string}>} + */ + async newMnemonic(size = 256) { + BlocksoftCryptoLog.log(`BlocksoftKeys newMnemonic called`); + + /* let mnemonic = false if (USE_TON) { for (let i = 0; i < 10000; i++) { let random = await this._getRandomBytesFunction(size / 8) @@ -64,356 +60,438 @@ class BlocksoftKeys { random = Buffer.from(random, 'base64') mnemonic = bip39.entropyToMnemonic(random) } */ - let random = await this._getRandomBytesFunction(size / 8) - random = Buffer.from(random, 'base64') - const mnemonic = bip39.entropyToMnemonic(random) - const hash = BlocksoftKeysUtils.hashMnemonic(mnemonic) - BlocksoftCryptoLog.log(`BlocksoftKeys newMnemonic finished`) - return { mnemonic, hash } + let random = await this._getRandomBytesFunction(size / 8); + random = Buffer.from(random, 'base64'); + const mnemonic = bip39.entropyToMnemonic(random); + const hash = BlocksoftKeysUtils.hashMnemonic(mnemonic); + BlocksoftCryptoLog.log(`BlocksoftKeys newMnemonic finished`); + return { mnemonic, hash }; + } + + /** + * @param {string} mnemonic + * @return {Promise} + */ + async validateMnemonic(mnemonic) { + BlocksoftCryptoLog.log(`BlocksoftKeys validateMnemonic called`); + if (await BlocksoftKeysScam.isScamMnemonic(mnemonic)) { + throw new Error(strings('settings.walletList.scamImport')); } - - /** - * @param {string} mnemonic - * @return {Promise} - */ - async validateMnemonic(mnemonic) { - BlocksoftCryptoLog.log(`BlocksoftKeys validateMnemonic called`) - if (await BlocksoftKeysScam.isScamMnemonic(mnemonic)) { - throw new Error(strings('settings.walletList.scamImport')) + const result = await bip39.validateMnemonic(mnemonic); + if (!result) { + throw new Error('invalid mnemonic for bip39'); + } + return result; + } + + /** + * @param {string} data.mnemonic + * @param {string} data.walletHash + * @param {string|string[]} data.currencyCode = all + * @param {int} data.fromIndex = 0 + * @param {int} data.toIndex = 100 + * @param {boolean} data.fullTree = false + * @param {array} data.derivations.BTC + * @param {array} data.derivations.BTC_SEGWIT + * @return {Promise<{currencyCode:[{address, privateKey, path, index, type}]}>} + */ + async discoverAddresses(data, source) { + const logData = { ...data }; + if (typeof logData.mnemonic !== 'undefined') logData.mnemonic = '***'; + + BlocksoftCryptoLog.log( + `BlocksoftKeys discoverAddresses called from ${source}`, + JSON.stringify(logData).substring(0, 200) + ); + + let toDiscover = BlocksoftDict.Codes; + if (data.currencyCode) { + if (typeof data.currencyCode === 'string') { + toDiscover = [data.currencyCode]; + } else { + toDiscover = data.currencyCode; + } + } + const fromIndex = data.fromIndex ? data.fromIndex : 0; + const toIndex = data.toIndex ? data.toIndex : 10; + const fullTree = data.fullTree ? data.fullTree : false; + + const results = {}; + + const mnemonicCache = data.mnemonic.toLowerCase(); + let bitcoinRoot = false; + let currencyCode; + let settings; + const seed = await BlocksoftKeysUtils.bip39MnemonicToSeed( + data.mnemonic.toLowerCase() + ); + for (currencyCode of toDiscover) { + results[currencyCode] = []; + try { + settings = BlocksoftDict.getCurrencyAllSettings( + currencyCode, + 'BlocksoftKeys' + ); + } catch (e) { + // do nothing for now + continue; + } + + let hexes = []; + if (settings.addressCurrencyCode) { + hexes.push(this._bipHex[settings.addressCurrencyCode]); + if (!this._bipHex[settings.addressCurrencyCode]) { + throw new Error( + 'UNKNOWN_CURRENCY_CODE SETTED ' + settings.addressCurrencyCode + ); } - const result = await bip39.validateMnemonic(mnemonic) - if (!result) { - throw new Error('invalid mnemonic for bip39') + } + + if (this._bipHex[currencyCode]) { + hexes.push(this._bipHex[currencyCode]); + } else if (!settings.addressCurrencyCode) { + if ( + settings.extendsProcessor && + this._bipHex[settings.extendsProcessor] + ) { + hexes.push(this._bipHex[settings.extendsProcessor]); + } else { + throw new Error( + 'UNKNOWN_CURRENCY_CODE ' + + currencyCode + + ' in bipHex AND NO SETTED addressCurrencyCode' + ); } - return result - } - - - /** - * @param {string} data.mnemonic - * @param {string} data.walletHash - * @param {string|string[]} data.currencyCode = all - * @param {int} data.fromIndex = 0 - * @param {int} data.toIndex = 100 - * @param {boolean} data.fullTree = false - * @param {array} data.derivations.BTC - * @param {array} data.derivations.BTC_SEGWIT - * @return {Promise<{currencyCode:[{address, privateKey, path, index, type}]}>} - */ - async discoverAddresses(data, source) { - - const logData = { ...data } - if (typeof logData.mnemonic !== 'undefined') logData.mnemonic = '***' - - BlocksoftCryptoLog.log(`BlocksoftKeys discoverAddresses called from ${source}`, JSON.stringify(logData).substring(0, 200)) - - let toDiscover = BlocksoftDict.Codes - if (data.currencyCode) { - if (typeof data.currencyCode === 'string') { - toDiscover = [data.currencyCode] - } else { - toDiscover = data.currencyCode - } + } + + let isAlreadyMain = false; + + if (!data.fullTree) { + hexes = [hexes[0]]; + } + + const hexesCache = + mnemonicCache + + '_' + + settings.addressProcessor + + '_fromINDEX_' + + fromIndex + + '_' + + JSON.stringify(hexes); + let hasDerivations = false; + if ( + typeof data.derivations !== 'undefined' && + typeof data.derivations[currencyCode] !== 'undefined' && + data.derivations[currencyCode] + ) { + hasDerivations = true; + } + if (typeof CACHE[hexesCache] === 'undefined' || hasDerivations) { + BlocksoftCryptoLog.log( + `BlocksoftKeys will discover ${settings.addressProcessor}` + ); + let root = false; + if (typeof networksConstants[currencyCode] !== 'undefined') { + root = await this.getBip32Cached( + data.mnemonic, + networksConstants[currencyCode], + seed + ); + } else { + if (!bitcoinRoot) { + bitcoinRoot = await this.getBip32Cached(data.mnemonic); + } + root = bitcoinRoot; } - const fromIndex = data.fromIndex ? data.fromIndex : 0 - const toIndex = data.toIndex ? data.toIndex : 10 - const fullTree = data.fullTree ? data.fullTree : false - - - const results = {} - - - const mnemonicCache = data.mnemonic.toLowerCase() - let bitcoinRoot = false - let currencyCode - let settings - const seed = await BlocksoftKeysUtils.bip39MnemonicToSeed(data.mnemonic.toLowerCase()) - for (currencyCode of toDiscover) { - results[currencyCode] = [] - try { - settings = BlocksoftDict.getCurrencyAllSettings(currencyCode, 'BlocksoftKeys') - } catch (e) { - // do nothing for now - continue - } - - let hexes = [] - if (settings.addressCurrencyCode) { - hexes.push(this._bipHex[settings.addressCurrencyCode]) - if (!this._bipHex[settings.addressCurrencyCode]) { - throw new Error('UNKNOWN_CURRENCY_CODE SETTED ' + settings.addressCurrencyCode) - } - } - - if (this._bipHex[currencyCode]) { - hexes.push(this._bipHex[currencyCode]) - } else if (!settings.addressCurrencyCode) { - if (settings.extendsProcessor && this._bipHex[settings.extendsProcessor]) { - hexes.push(this._bipHex[settings.extendsProcessor]) - } else { - throw new Error('UNKNOWN_CURRENCY_CODE ' + currencyCode + ' in bipHex AND NO SETTED addressCurrencyCode') - } - } - - let isAlreadyMain = false + // BIP32 Extended Private Key to check - uncomment + // let childFirst = root.derivePath('m/44\'/2\'/0\'/0') + // BlocksoftCryptoLog.log(childFirst.toBase58()) - if (!data.fullTree) { - hexes = [hexes[0]] - } - - const hexesCache = mnemonicCache + '_' + settings.addressProcessor + '_fromINDEX_' + fromIndex + '_' + JSON.stringify(hexes) - let hasDerivations = false - if (typeof data.derivations !== 'undefined' && typeof data.derivations[currencyCode] !== 'undefined' && data.derivations[currencyCode]) { - hasDerivations = true + /** + * @type {EthAddressProcessor|BtcAddressProcessor} + */ + const processor = await BlocksoftDispatcher.innerGetAddressProcessor( + settings + ); + + try { + await processor.setBasicRoot(root); + } catch (e) { + e.message += ' while doing ' + JSON.stringify(settings); + throw e; + } + let currentFromIndex = fromIndex; + let currentToIndex = toIndex; + let currentFullTree = fullTree; + + BlocksoftCryptoLog.log( + `BlocksoftKeys discoverAddresses ${currencyCode} currentFromIndex.1 ${currentFromIndex}` + ); + if (hasDerivations) { + let derivation = { path: '', alreadyShown: 0, walletPubId: 0 }; + let maxIndex = 0; + for (derivation of data.derivations[currencyCode]) { + const child = root.derivePath(derivation.path); + const tmp = derivation.path.split('/'); + const result = await processor.getAddress( + child.privateKey, + { + publicKey: child.publicKey, + walletHash: data.walletHash, + derivationPath: derivation.path + }, + data, + seed, + 'discoverAddresses' + ); + result.basicPrivateKey = child.privateKey.toString('hex'); + result.basicPublicKey = child.publicKey.toString('hex'); + result.path = derivation.path; + result.alreadyShown = derivation.alreadyShown; + result.walletPubId = derivation.walletPubId || 0; + result.index = tmp[5]; + if (maxIndex < result.index) { + maxIndex = result.index * 1; } - if (typeof CACHE[hexesCache] === 'undefined' || hasDerivations) { - BlocksoftCryptoLog.log(`BlocksoftKeys will discover ${settings.addressProcessor}`) - let root = false - if (typeof networksConstants[currencyCode] !== 'undefined') { - root = await this.getBip32Cached(data.mnemonic, networksConstants[currencyCode], seed) - } else { - if (!bitcoinRoot) { - bitcoinRoot = await this.getBip32Cached(data.mnemonic) - } - root = bitcoinRoot - } - // BIP32 Extended Private Key to check - uncomment - // let childFirst = root.derivePath('m/44\'/2\'/0\'/0') - // BlocksoftCryptoLog.log(childFirst.toBase58()) - - /** - * @type {EthAddressProcessor|BtcAddressProcessor} - */ - const processor = await BlocksoftDispatcher.innerGetAddressProcessor(settings) - - try { - await processor.setBasicRoot(root) - } catch (e) { - e.message += ' while doing ' + JSON.stringify(settings) - throw e - } - let currentFromIndex = fromIndex - let currentToIndex = toIndex - let currentFullTree = fullTree - - BlocksoftCryptoLog.log(`BlocksoftKeys discoverAddresses ${currencyCode} currentFromIndex.1 ${currentFromIndex}`) - if (hasDerivations) { - let derivation = {path : '', alreadyShown : 0, walletPubId : 0} - let maxIndex = 0 - for (derivation of data.derivations[currencyCode]) { - const child = root.derivePath(derivation.path) - const tmp = derivation.path.split('/') - const result = await processor.getAddress(child.privateKey, { - publicKey: child.publicKey, - walletHash: data.walletHash, - derivationPath : derivation.path, - }, data, seed, 'discoverAddresses') - result.basicPrivateKey = child.privateKey.toString('hex') - result.basicPublicKey = child.publicKey.toString('hex') - result.path = derivation.path - result.alreadyShown = derivation.alreadyShown - result.walletPubId = derivation.walletPubId || 0 - result.index = tmp[5] - if (maxIndex < result.index) { - maxIndex = result.index*1 - } - result.type = 'main' - results[currencyCode].push(result) - } - if (maxIndex > 0) { - // noinspection PointlessArithmeticExpressionJS - currentFromIndex = maxIndex*1 + 1 - currentToIndex = currentFromIndex*1+ 10 - currentFullTree = true - BlocksoftCryptoLog.log(`BlocksoftKeys ${currencyCode} discoverAddresses currentFromIndex.2 ${currentFromIndex}`) - } - } + result.type = 'main'; + results[currencyCode].push(result); + } + if (maxIndex > 0) { + // noinspection PointlessArithmeticExpressionJS + currentFromIndex = maxIndex * 1 + 1; + currentToIndex = currentFromIndex * 1 + 10; + currentFullTree = true; + BlocksoftCryptoLog.log( + `BlocksoftKeys ${currencyCode} discoverAddresses currentFromIndex.2 ${currentFromIndex}` + ); + } + } + let suffixes; + if (currencyCode === 'SOL') { + suffixes = [ + { type: 'main', suffix: false, after: `'/0'` }, + { type: 'no_scan', suffix: false, after: `'` } + ]; + } else if (currentFullTree) { + suffixes = [ + { type: 'main', suffix: `0'/0` }, + { type: 'change', suffix: `0'/1` } + // { 'type': 'second', 'suffix': `1'/0` }, + // { 'type': 'secondchange', 'suffix': `1'/1` } + ]; + } else { + suffixes = [{ type: 'main', suffix: `0'/0` }]; + if (currencyCode === 'BTC_SEGWIT_COMPATIBLE') { + suffixes = [ + { type: 'main', suffix: '0/1' } // heh + ]; + } + hexes = [hexes[0]]; + } - let suffixes - if (currencyCode === 'SOL') { - suffixes = [ - { 'type': 'main', 'suffix': false, after : `'/0'` }, - { 'type': 'no_scan', 'suffix': false, after: `'` } - ] - } else if (currentFullTree) { - suffixes = [ - { 'type': 'main', 'suffix': `0'/0` }, - { 'type': 'change', 'suffix': `0'/1` } - // { 'type': 'second', 'suffix': `1'/0` }, - // { 'type': 'secondchange', 'suffix': `1'/1` } - ] + let hex; + for (hex of hexes) { + if (isAlreadyMain) { + suffixes[0].type = 'second'; + } + isAlreadyMain = true; + + if (currentFromIndex >= 0 && currentToIndex >= 0) { + for ( + let index = currentFromIndex; + index < currentToIndex; + index++ + ) { + let suffix; + for (suffix of suffixes) { + const path = suffix.suffix + ? `m/${hex}'/${suffix.suffix}/${index}` + : `m/${hex}'/${index}${suffix.after}`; + let privateKey = false; + let publicKey = false; + if ( + currencyCode === 'SOL' || + currencyCode === 'XLM' || + currencyCode === 'WAVES' || + currencyCode === 'ASH' + ) { + // @todo move to coin address processor } else { - suffixes = [ - { 'type': 'main', 'suffix': `0'/0` } - ] - if (currencyCode === 'BTC_SEGWIT_COMPATIBLE') { - suffixes = [ - { 'type': 'main', 'suffix': '0/1' } // heh - ] - } - hexes = [hexes[0]] - } - - - let hex - for (hex of hexes) { - if (isAlreadyMain) { - suffixes[0].type = 'second' - } - isAlreadyMain = true - - if (currentFromIndex >= 0 && currentToIndex >= 0) { - for (let index = currentFromIndex; index < currentToIndex; index++) { - let suffix - for (suffix of suffixes) { - const path = suffix.suffix ? `m/${hex}'/${suffix.suffix}/${index}` : `m/${hex}'/${index}${suffix.after}` - let privateKey = false - let publicKey = false - if (currencyCode === 'SOL' || currencyCode === 'XLM' || currencyCode === 'WAVES' || currencyCode === 'ASH') { - // @todo move to coin address processor - } else { - const child = root.derivePath(path) - privateKey = child.privateKey - publicKey = child.publicKey - } - const result = await processor.getAddress(privateKey, { - publicKey, - walletHash: data.walletHash, - derivationPath : path, - derivationIndex : index, - derivationType : suffix.type - }, data, seed, 'discoverAddresses2') - if (result) { - if (privateKey) { - result.basicPrivateKey = privateKey.toString('hex') - result.basicPublicKey = publicKey.toString('hex') - } - result.path = path - result.index = index - result.alreadyShown = 0 - result.type = suffix.type - results[currencyCode].push(result) - } - } - } - } - } - CACHE[hexesCache] = results[currencyCode] - if (currencyCode === 'ETH') { - ETH_CACHE[mnemonicCache] = results[currencyCode][0] + const child = root.derivePath(path); + privateKey = child.privateKey; + publicKey = child.publicKey; } - } else { - BlocksoftCryptoLog.log(`BlocksoftKeys will be from cache ${settings.addressProcessor}`) - results[currencyCode] = CACHE[hexesCache] - if (currencyCode === 'USDT') { - results[currencyCode] = [results[currencyCode][0]] + const result = await processor.getAddress( + privateKey, + { + publicKey, + walletHash: data.walletHash, + derivationPath: path, + derivationIndex: index, + derivationType: suffix.type + }, + data, + seed, + 'discoverAddresses2' + ); + if (result) { + if (privateKey) { + result.basicPrivateKey = privateKey.toString('hex'); + result.basicPublicKey = publicKey.toString('hex'); + } + result.path = path; + result.index = index; + result.alreadyShown = 0; + result.type = suffix.type; + results[currencyCode].push(result); } + } } + } } - BlocksoftCryptoLog.log(`BlocksoftKeys discoverAddresses finished`) - return results - } - - async getSeedCached(mnemonic) { - BlocksoftCryptoLog.log(`BlocksoftKeys bip39MnemonicToSeed started`) - const mnemonicCache = mnemonic.toLowerCase() - if (typeof CACHE[mnemonicCache] === 'undefined') { - CACHE[mnemonicCache] = await BlocksoftKeysUtils.bip39MnemonicToSeed(mnemonic.toLowerCase()) + CACHE[hexesCache] = results[currencyCode]; + if (currencyCode === 'ETH') { + ETH_CACHE[mnemonicCache] = results[currencyCode][0]; } - const seed = CACHE[mnemonicCache] // will be rechecked on saving - BlocksoftCryptoLog.log(`BlocksoftKeys bip39MnemonicToSeed ended`) - return seed - } - - async getBip32Cached(mnemonic, network = false, seed = false) { - const mnemonicCache = mnemonic.toLowerCase() + '_' + (network || 'btc') - if (typeof CACHE_ROOTS[mnemonicCache] === 'undefined') { - if (!seed) { - seed = await this.getSeedCached(mnemonic) - } - CACHE_ROOTS[mnemonicCache] = network ? bip32.fromSeed(seed, network) : bip32.fromSeed(seed) + } else { + BlocksoftCryptoLog.log( + `BlocksoftKeys will be from cache ${settings.addressProcessor}` + ); + results[currencyCode] = CACHE[hexesCache]; + if (currencyCode === 'USDT') { + results[currencyCode] = [results[currencyCode][0]]; } - return CACHE_ROOTS[mnemonicCache] - } - - getEthCached(mnemonicCache) { - if (typeof ETH_CACHE[mnemonicCache] === 'undefined') return false - return ETH_CACHE[mnemonicCache] + } } - - setEthCached(mnemonic, result) { - const mnemonicCache = mnemonic.toLowerCase() - ETH_CACHE[mnemonicCache] = result + BlocksoftCryptoLog.log(`BlocksoftKeys discoverAddresses finished`); + return results; + } + + async getSeedCached(mnemonic) { + BlocksoftCryptoLog.log(`BlocksoftKeys bip39MnemonicToSeed started`); + const mnemonicCache = mnemonic.toLowerCase(); + if (typeof CACHE[mnemonicCache] === 'undefined') { + CACHE[mnemonicCache] = await BlocksoftKeysUtils.bip39MnemonicToSeed( + mnemonic.toLowerCase() + ); } - - /** - * @param {string} data.mnemonic - * @param {string} data.currencyCode - * @param {string} data.derivationPath - * @param {string} data.derivationIndex - * @param {string} data.derivationType - * @return {Promise<{address, privateKey}>} - */ - async discoverOne(data) { - const seed = await BlocksoftKeysUtils.bip39MnemonicToSeed(data.mnemonic.toLowerCase()) - const root = bip32.fromSeed(seed) - const child = root.derivePath(data.derivationPath.replace(/quote/g, '\'')) - /** - * @type {EthAddressProcessor|BtcAddressProcessor} - */ - const processor = await BlocksoftDispatcher.getAddressProcessor(data.currencyCode) - processor.setBasicRoot(root) - return processor.getAddress(child.privateKey, { - derivationPath : data.derivationPath, - derivationIndex: data.derivationIndex, - derivationType: data.derivationType, - publicKey : child.publicKey - }, data, seed, 'discoverOne') + const seed = CACHE[mnemonicCache]; // will be rechecked on saving + BlocksoftCryptoLog.log(`BlocksoftKeys bip39MnemonicToSeed ended`); + return seed; + } + + async getBip32Cached(mnemonic, network = false, seed = false) { + const mnemonicCache = mnemonic.toLowerCase() + '_' + (network || 'btc'); + if (typeof CACHE_ROOTS[mnemonicCache] === 'undefined') { + if (!seed) { + seed = await this.getSeedCached(mnemonic); + } + CACHE_ROOTS[mnemonicCache] = network + ? bip32.fromSeed(seed, network) + : bip32.fromSeed(seed); } - + return CACHE_ROOTS[mnemonicCache]; + } + + getEthCached(mnemonicCache) { + if (typeof ETH_CACHE[mnemonicCache] === 'undefined') return false; + return ETH_CACHE[mnemonicCache]; + } + + setEthCached(mnemonic, result) { + const mnemonicCache = mnemonic.toLowerCase(); + ETH_CACHE[mnemonicCache] = result; + } + + /** + * @param {string} data.mnemonic + * @param {string} data.currencyCode + * @param {string} data.derivationPath + * @param {string} data.derivationIndex + * @param {string} data.derivationType + * @return {Promise<{address, privateKey}>} + */ + async discoverOne(data) { + const seed = await BlocksoftKeysUtils.bip39MnemonicToSeed( + data.mnemonic.toLowerCase() + ); + const root = bip32.fromSeed(seed); + const child = root.derivePath(data.derivationPath.replace(/quote/g, "'")); /** - * @param {string} data.mnemonic - * @param {string} data.currencyCode - * @return {Promise<{address, privateKey}>} + * @type {EthAddressProcessor|BtcAddressProcessor} */ - async discoverXpub(data) { - const seed = await BlocksoftKeysUtils.bip39MnemonicToSeed(data.mnemonic.toLowerCase()) - const root = bip32.fromSeed(seed) - let path = `m/44'/0'/0'` - let version = 0x0488B21E // xpub - if (data.currencyCode === 'BTC_SEGWIT') { - path = `m/84'/0'/0'` - version = 0x04b24746 // https://github.com/satoshilabs/slips/blob/master/slip-0132.md - } else if (data.currencyCode === 'BTC_SEGWIT_COMPATIBLE') { - path = `m/49'/0'/0'` - version = 0x049d7cb2 - } - BlocksoftCryptoLog.log('BlocksoftKeys.discoverXpub derive started ' + JSON.stringify(path)) - const rootWallet = root.derivePath(path) - BlocksoftCryptoLog.log('BlocksoftKeys.discoverXpub derived, bs58 started ' + JSON.stringify(path)) - const res = bs58check.encode(serialize(rootWallet, version, rootWallet.publicKey)) - BlocksoftCryptoLog.log('BlocksoftKeys.discoverXpub res ' + JSON.stringify(path), res) - return res + const processor = await BlocksoftDispatcher.getAddressProcessor( + data.currencyCode + ); + processor.setBasicRoot(root); + return processor.getAddress( + child.privateKey, + { + derivationPath: data.derivationPath, + derivationIndex: data.derivationIndex, + derivationType: data.derivationType, + publicKey: child.publicKey + }, + data, + seed, + 'discoverOne' + ); + } + + /** + * @param {string} data.mnemonic + * @param {string} data.currencyCode + * @return {Promise<{address, privateKey}>} + */ + async discoverXpub(data) { + const seed = await BlocksoftKeysUtils.bip39MnemonicToSeed( + data.mnemonic.toLowerCase() + ); + const root = bip32.fromSeed(seed); + let path = `m/44'/0'/0'`; + let version = 0x0488b21e; // xpub + if (data.currencyCode === 'BTC_SEGWIT') { + path = `m/84'/0'/0'`; + version = 0x04b24746; // https://github.com/satoshilabs/slips/blob/master/slip-0132.md + } else if (data.currencyCode === 'BTC_SEGWIT_COMPATIBLE') { + path = `m/49'/0'/0'`; + version = 0x049d7cb2; } + BlocksoftCryptoLog.log( + 'BlocksoftKeys.discoverXpub derive started ' + JSON.stringify(path) + ); + const rootWallet = root.derivePath(path); + BlocksoftCryptoLog.log( + 'BlocksoftKeys.discoverXpub derived, bs58 started ' + JSON.stringify(path) + ); + const res = bs58check.encode( + serialize(rootWallet, version, rootWallet.publicKey) + ); + BlocksoftCryptoLog.log( + 'BlocksoftKeys.discoverXpub res ' + JSON.stringify(path), + res + ); + return res; + } } function serialize(hdkey, version, key, LEN = 78) { - // => version(4) || depth(1) || fingerprint(4) || index(4) || chain(32) || key(33) - const buffer = Buffer.allocUnsafe(LEN) + // => version(4) || depth(1) || fingerprint(4) || index(4) || chain(32) || key(33) + const buffer = Buffer.allocUnsafe(LEN); - buffer.writeUInt32BE(version, 0) - buffer.writeUInt8(hdkey.depth, 4) + buffer.writeUInt32BE(version, 0); + buffer.writeUInt8(hdkey.depth, 4); - const fingerprint = hdkey.depth ? hdkey.parentFingerprint : 0x00000000 - buffer.writeUInt32BE(fingerprint, 5) - buffer.writeUInt32BE(hdkey.index, 9) + const fingerprint = hdkey.depth ? hdkey.parentFingerprint : 0x00000000; + buffer.writeUInt32BE(fingerprint, 5); + buffer.writeUInt32BE(hdkey.index, 9); - hdkey.chainCode.copy(buffer, 13) - key.copy(buffer, 45) + hdkey.chainCode.copy(buffer, 13); + key.copy(buffer, 45); - return buffer + return buffer; } -const singleBlocksoftKeys = new BlocksoftKeys() -export default singleBlocksoftKeys +const singleBlocksoftKeys = new BlocksoftKeys(); +export default singleBlocksoftKeys; diff --git a/src/lib/blockchains/BlocksoftTransferDispatcher.ts b/src/lib/blockchains/BlocksoftTransferDispatcher.ts deleted file mode 100644 index 24c8730ad..000000000 --- a/src/lib/blockchains/BlocksoftTransferDispatcher.ts +++ /dev/null @@ -1,135 +0,0 @@ -/** - * @author Ksu - * @version 0.20 - */ -import BlocksoftDict from '../common/BlocksoftDict' -import { BlocksoftDictTypes } from '../common/BlocksoftDictTypes' - -import BchTransferProcessor from './bch/BchTransferProcessor' -import BsvTransferProcessor from './bsv/BsvTransferProcessor' -import BtcTransferProcessor from './btc/BtcTransferProcessor' -import BtcTestTransferProcessor from './btc_test/BtcTestTransferProcessor' -import BtgTransferProcessor from './btg/BtgTransferProcessor' -import DogeTransferProcessor from './doge/DogeTransferProcessor' -import EthTransferProcessor from './eth/EthTransferProcessor' -import EthTransferProcessorErc20 from './eth/EthTransferProcessorErc20' -import LtcTransferProcessor from './ltc/LtcTransferProcessor' -import TrxTransferProcessor from './trx/TrxTransferProcessor' -import UsdtTransferProcessor from './usdt/UsdtTransferProcessor' -import XrpTransferProcessor from './xrp/XrpTransferProcessor' -import XlmTransferProcessor from './xlm/XlmTransferProcessor' -import XvgTransferProcessor from './xvg/XvgTransferProcessor' -import XmrTransferProcessor from './xmr/XmrTransferProcessor' -import FioTransferProcessor from './fio/FioTransferProcessor' -import BnbTransferProcessor from './bnb/BnbTransferProcessor' -import BnbSmartTransferProcessor from './bnb_smart/BnbSmartTransferProcessor' -import BnbSmartTransferProcessorErc20 from './bnb_smart/BnbSmartTransferProcessorErc20' -import EtcTransferProcessor from './etc/EtcTransferProcessor' -import VetTransferProcessor from '@crypto/blockchains/vet/VetTransferProcessor' -import SolTransferProcessor from '@crypto/blockchains/sol/SolTransferProcessor' -import SolTransferProcessorSpl from '@crypto/blockchains/sol/SolTransferProcessorSpl' -import WavesTransferProcessor from '@crypto/blockchains/waves/WavesTransferProcessor' -import MetisTransferProcessor from '@crypto/blockchains/metis/MetisTransferProcessor' - -import { BlocksoftBlockchainTypes } from './BlocksoftBlockchainTypes' - - -export namespace BlocksoftTransferDispatcher { - - type BlocksoftTransferDispatcherDict = { - [key in BlocksoftDictTypes.Code]: BlocksoftBlockchainTypes.TransferProcessor - } - - const CACHE_PROCESSORS: BlocksoftTransferDispatcherDict = {} as BlocksoftTransferDispatcherDict - - export const getTransferProcessor = function(currencyCode: BlocksoftDictTypes.Code): BlocksoftBlockchainTypes.TransferProcessor { - const currencyDictSettings = BlocksoftDict.getCurrencyAllSettings(currencyCode) - if (typeof CACHE_PROCESSORS[currencyCode] !== 'undefined') { - return CACHE_PROCESSORS[currencyCode] - } - let transferProcessor = currencyCode - if (typeof currencyDictSettings.transferProcessor !== 'undefined') { - transferProcessor = currencyDictSettings.transferProcessor - } - switch (transferProcessor) { - case 'BCH': - CACHE_PROCESSORS[currencyCode] = new BchTransferProcessor(currencyDictSettings) - break - case 'BSV': - CACHE_PROCESSORS[currencyCode] = new BsvTransferProcessor(currencyDictSettings) - break - case 'BTC': - CACHE_PROCESSORS[currencyCode] = new BtcTransferProcessor(currencyDictSettings) - break - case 'BTC_TEST': - CACHE_PROCESSORS[currencyCode] = new BtcTestTransferProcessor(currencyDictSettings) - break - case 'BTG': - CACHE_PROCESSORS[currencyCode] = new BtgTransferProcessor(currencyDictSettings) - break - case 'DOGE': - CACHE_PROCESSORS[currencyCode] = new DogeTransferProcessor(currencyDictSettings) - break - case 'ETH': - CACHE_PROCESSORS[currencyCode] = new EthTransferProcessor(currencyDictSettings) - break - case 'ETH_ERC_20': - CACHE_PROCESSORS[currencyCode] = new EthTransferProcessorErc20(currencyDictSettings) - break - case 'ETC': - CACHE_PROCESSORS[currencyCode] = new EtcTransferProcessor(currencyDictSettings) - break - case 'BNB_SMART_20': - CACHE_PROCESSORS[currencyCode] = new BnbSmartTransferProcessorErc20(currencyDictSettings) - break - case 'LTC': - CACHE_PROCESSORS[currencyCode] = new LtcTransferProcessor(currencyDictSettings) - break - case 'TRX': - CACHE_PROCESSORS[currencyCode] = new TrxTransferProcessor(currencyDictSettings) - break - case 'USDT': - CACHE_PROCESSORS[currencyCode] = new UsdtTransferProcessor(currencyDictSettings) - break - case 'XRP': - CACHE_PROCESSORS[currencyCode] = new XrpTransferProcessor(currencyDictSettings) - break - case 'XLM': - CACHE_PROCESSORS[currencyCode] = new XlmTransferProcessor(currencyDictSettings) - break - case 'XVG': - CACHE_PROCESSORS[currencyCode] = new XvgTransferProcessor(currencyDictSettings) - break - case 'XMR': - CACHE_PROCESSORS[currencyCode] = new XmrTransferProcessor(currencyDictSettings) - break - case 'FIO': - CACHE_PROCESSORS[currencyCode] = new FioTransferProcessor(currencyDictSettings) - break - case 'BNB': - CACHE_PROCESSORS[currencyCode] = new BnbTransferProcessor(currencyDictSettings) - break - case 'BNB_SMART': - CACHE_PROCESSORS[currencyCode] = new BnbSmartTransferProcessor(currencyDictSettings) - break - case 'VET': - CACHE_PROCESSORS[currencyCode] = new VetTransferProcessor(currencyDictSettings) - break - case 'SOL': - CACHE_PROCESSORS[currencyCode] = new SolTransferProcessor(currencyDictSettings) - break - case 'SOL_SPL': - CACHE_PROCESSORS[currencyCode] = new SolTransferProcessorSpl(currencyDictSettings) - break - case 'METIS': - CACHE_PROCESSORS[currencyCode] = new MetisTransferProcessor(currencyDictSettings) - break - case 'WAVES': - CACHE_PROCESSORS[currencyCode] = new WavesTransferProcessor(currencyDictSettings) - break - default: - throw new Error('Unknown transferProcessor ' + transferProcessor) - } - return CACHE_PROCESSORS[currencyCode] - } -} diff --git a/src/prototypes/buffer.ts b/src/prototypes/buffer.ts deleted file mode 100644 index 95f83ee88..000000000 --- a/src/prototypes/buffer.ts +++ /dev/null @@ -1,2 +0,0 @@ -// eslint-disable-next-line @typescript-eslint/no-var-requires -global.Buffer = require('buffer').Buffer; diff --git a/src/prototypes/global.js b/src/prototypes/global.js new file mode 100644 index 000000000..d4755f492 --- /dev/null +++ b/src/prototypes/global.js @@ -0,0 +1,26 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ +// Inject node globals into React Native global scope. +import * as ExpoCrypto from 'expo-crypto'; +global.Buffer = require('buffer').Buffer; +global.process = require('process'); + +if (typeof btoa === 'undefined') { + global.btoa = function (str) { + return new Buffer(str, 'binary').toString('base64'); + }; +} + +if (typeof atob === 'undefined') { + global.atob = function (b64Encoded) { + return new Buffer(b64Encoded, 'base64').toString('binary'); + }; +} + +if (typeof global.crypto !== 'object') { + global.crypto = {}; +} + +if (typeof global.crypto.getRandomValues !== 'function') { + global.crypto.getRandomValues = (array) => + ExpoCrypto.getRandomBytes(array.byteLength); +} diff --git a/yarn.lock b/yarn.lock index 800bce633..1f49806eb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2554,6 +2554,11 @@ dependencies: nanoid "^3.1.23" +"@scure/base@^1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.1.tgz#ebb651ee52ff84f420097055f4bf46cfba403938" + integrity sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA== + "@segment/loosely-validate-event@^2.0.0": version "2.0.0" resolved "https://registry.npmjs.org/@segment/loosely-validate-event/-/loosely-validate-event-2.0.0.tgz" @@ -3519,6 +3524,11 @@ base-x@^3.0.2: dependencies: safe-buffer "^5.0.1" +base-x@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/base-x/-/base-x-4.0.0.tgz#d0e3b7753450c73f8ad2389b5c018a4af7b2224a" + integrity sha512-FuwxlW4H5kh37X/oW59pwTzzTKRzfrrQwhmyspRM7swOEZcHtDZSCt45U6oKgtuFE+WYPblePMVIPR4RZrh/hw== + base64-js@^1.1.2, base64-js@^1.2.3, base64-js@^1.3.0, base64-js@^1.3.1, base64-js@^1.5.1: version "1.5.1" resolved "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz" @@ -3579,6 +3589,16 @@ bip32@^2.0.4: typeforce "^1.11.5" wif "^2.0.6" +bip32@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/bip32/-/bip32-4.0.0.tgz#7fac3c05072188d2d355a4d6596b37188f06aa2f" + integrity sha512-aOGy88DDlVUhspIXJN+dVEtclhIsfAUppD43V0j40cPTld3pv/0X/MlrZSZ6jowIaQQzFwP8M6rFU2z2mVYjDQ== + dependencies: + "@noble/hashes" "^1.2.0" + "@scure/base" "^1.1.1" + typeforce "^1.11.5" + wif "^2.0.6" + bip39@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/bip39/-/bip39-3.1.0.tgz#c55a418deaf48826a6ceb34ac55b3ee1577e18a3" @@ -3735,6 +3755,13 @@ bs58@^4.0.0: dependencies: base-x "^3.0.2" +bs58@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/bs58/-/bs58-5.0.0.tgz#865575b4d13c09ea2a84622df6c8cbeb54ffc279" + integrity sha512-r+ihvQJvahgYT50JD05dyJNKlmmSlMoOGwn1lCcEzanPglg7TxYjioQUYehQ9mAR/+hOSd2jRc/Z2y5UxBymvQ== + dependencies: + base-x "^4.0.0" + bs58check@<3.0.0, bs58check@^2.0.0, bs58check@^2.1.1: version "2.1.2" resolved "https://registry.yarnpkg.com/bs58check/-/bs58check-2.1.2.tgz#53b018291228d82a5aa08e7d796fdafda54aebfc" @@ -3744,6 +3771,14 @@ bs58check@<3.0.0, bs58check@^2.0.0, bs58check@^2.1.1: 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" @@ -5326,10 +5361,10 @@ expo-constants@~14.2.0, expo-constants@~14.2.1: "@expo/config" "~8.0.0" uuid "^3.3.2" -expo-crypto@~12.2.1: - version "12.2.1" - resolved "https://registry.npmjs.org/expo-crypto/-/expo-crypto-12.2.1.tgz" - integrity sha512-NfKsREzj55xCm0v9OKuNw3DU1r6i6VNTYrQcYSZfGOp7o+tvZsJRyr+NyrUg5YVH93TUkothCtsPahdYA4r1Wg== +expo-crypto@^12.4.1: + version "12.4.1" + resolved "https://registry.yarnpkg.com/expo-crypto/-/expo-crypto-12.4.1.tgz#f3ee7156fd8167c3c3304b6e21dcfb0dc8a44bba" + integrity sha512-/en03oPNAX6gP0bKpwA1EyLBnGG9uv0+Q7uvGYyOXaQQEvj31a+8cEvNPkv75x6GuK1hcaBfO25RtX9AGOMwVA== dependencies: base64-js "^1.3.0" @@ -9041,6 +9076,11 @@ process-nextick-args@~2.0.0: resolved "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz" integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== +process@^0.11.10: + version "0.11.10" + resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" + integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A== + progress@2.0.3: version "2.0.3" resolved "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz" From fb9a89fc4cbc7128edb4e7968a2520717c28b414 Mon Sep 17 00:00:00 2001 From: JavidHaji-zada Date: Wed, 26 Jul 2023 12:07:56 +0400 Subject: [PATCH 015/509] converted bnb --- .../BlocksoftInvoice/BlocksoftInvoice.js | 205 +- .../BlocksoftKeysForRef.js | 121 +- .../BlocksoftSecrets/BlocksoftSecrets.js | 85 +- .../BlocksoftTransferPrivate.ts | 97 +- crypto/blockchains/bnb/BnbAddressProcessor.js | 52 - crypto/blockchains/bnb/BnbAddressProcessor.ts | 61 + crypto/blockchains/bnb/BnbScannerProcessor.js | 169 -- crypto/blockchains/bnb/BnbScannerProcessor.ts | 202 ++ .../blockchains/bnb/BnbTransferProcessor.ts | 312 ++- .../blockchains/bnb/basic/BnbNetworkPrices.js | 91 +- .../bnb/basic/BnbTxSendProvider.ts | 594 +++-- crypto/blockchains/bnb/utils/Encode.js | 523 ++-- crypto/blockchains/bnb/utils/EncoderHelper.js | 44 - crypto/blockchains/bnb/utils/EncoderHelper.ts | 46 + crypto/blockchains/bnb/utils/IsJs.js | 1922 ++++++------- crypto/blockchains/bnb/utils/UVarInt.js | 81 - crypto/blockchains/bnb/utils/UVarInt.ts | 81 + .../bnb_smart/BnbSmartTransferProcessor.ts | 72 +- .../BnbSmartTransferProcessorErc20.ts | 106 +- .../bnb_smart/basic/BnbSmartNetworkPrices.js | 113 +- crypto/blockchains/bsv/BsvScannerProcessor.js | 620 +++-- .../bsv/providers/BsvSendProvider.ts | 174 +- .../bsv/providers/BsvUnspentsProvider.ts | 89 +- crypto/blockchains/btc/BtcScannerProcessor.js | 853 +++--- .../blockchains/btc/basic/BtcNetworkPrices.ts | 505 ++-- .../btc_test/BtcTestScannerProcessor.js | 218 +- .../btc_test/providers/BtcTestSendProvider.ts | 169 +- .../providers/BtcTestUnspentsProvider.ts | 122 +- .../blockchains/doge/DogeScannerProcessor.js | 484 ++-- .../blockchains/doge/DogeTransferProcessor.ts | 1564 +++++++---- .../doge/basic/DogeFindAddressFunction.js | 210 +- crypto/blockchains/doge/basic/DogeLogs.ts | 178 +- .../doge/basic/DogeNetworkPrices.ts | 60 +- .../doge/providers/DogeSendProvider.ts | 587 ++-- .../doge/providers/DogeUnspentsProvider.ts | 411 +-- crypto/blockchains/doge/stores/DogeRawDS.js | 376 +-- crypto/blockchains/doge/tx/DogeTxBuilder.ts | 467 ++-- crypto/blockchains/eth/EthScannerProcessor.js | 1772 +++++++----- .../eth/EthScannerProcessorErc20.js | 166 +- .../blockchains/eth/EthTransferProcessor.ts | 2375 +++++++++++------ .../eth/EthTransferProcessorErc20.ts | 495 ++-- crypto/blockchains/eth/apis/EthNftMatic.js | 173 +- crypto/blockchains/eth/apis/EthNftOpensea.js | 269 +- crypto/blockchains/eth/basic/EthBasic.js | 719 ++--- .../blockchains/eth/basic/EthNetworkPrices.js | 620 +++-- .../eth/basic/EthTxSendProvider.ts | 768 ++++-- .../eth/forks/EthScannerProcessorSoul.js | 107 +- crypto/blockchains/eth/stores/EthRawDS.js | 524 ++-- crypto/blockchains/fio/FioScannerProcessor.js | 250 +- crypto/blockchains/fio/FioSdkWrapper.js | 94 +- .../blockchains/fio/FioTransferProcessor.ts | 158 +- crypto/blockchains/fio/FioUtils.js | 631 +++-- .../metis/MetisScannerProcessor.js | 60 +- .../metis/MetisTransferProcessor.ts | 113 +- crypto/blockchains/one/OneScannerProcessor.js | 375 +-- .../one/OneScannerProcessorErc20.js | 359 +-- crypto/blockchains/sol/SolScannerProcessor.js | 689 ++--- .../blockchains/sol/SolScannerProcessorSpl.js | 135 +- crypto/blockchains/sol/SolTokenProcessor.js | 171 +- .../blockchains/sol/SolTransferProcessor.ts | 696 +++-- .../sol/SolTransferProcessorSpl.ts | 485 ++-- crypto/blockchains/sol/ext/SolStakeUtils.js | 601 +++-- crypto/blockchains/sol/ext/SolUtils.js | 427 +-- crypto/blockchains/trx/TrxScannerProcessor.js | 677 +++-- .../blockchains/trx/TrxTransferProcessor.ts | 1525 +++++++---- .../trx/basic/TrxNodeInfoProvider.js | 64 +- .../trx/basic/TrxTransactionsProvider.js | 436 +-- .../trx/basic/TrxTransactionsTrc20Provider.js | 317 ++- .../trx/basic/TrxTrongridProvider.js | 425 +-- .../trx/basic/TrxTronscanProvider.js | 186 +- crypto/blockchains/trx/ext/TronStakeUtils.js | 262 +- .../trx/providers/TrxSendProvider.ts | 331 ++- .../blockchains/usdt/UsdtScannerProcessor.js | 462 ++-- .../blockchains/usdt/UsdtTransferProcessor.ts | 207 +- crypto/blockchains/usdt/tx/UsdtTxBuilder.ts | 70 +- .../usdt/tx/UsdtTxInputsOutputs.ts | 590 ++-- crypto/blockchains/vet/VetScannerProcessor.js | 524 ++-- .../blockchains/vet/VetTransferProcessor.ts | 442 +-- .../waves/WavesScannerProcessor.js | 308 ++- .../waves/WavesScannerProcessorErc20.js | 218 +- .../waves/WavesTransferProcessor.ts | 381 ++- .../providers/WavesTransactionsProvider.js | 74 +- crypto/blockchains/xlm/XlmScannerProcessor.js | 432 +-- .../blockchains/xlm/XlmTransferProcessor.ts | 431 +-- .../xlm/basic/XlmTxSendProvider.ts | 312 ++- crypto/blockchains/xmr/XmrAddressProcessor.js | 245 +- crypto/blockchains/xmr/XmrScannerProcessor.js | 651 +++-- .../blockchains/xmr/XmrTransferProcessor.ts | 643 +++-- crypto/blockchains/xmr/ext/MoneroMnemonic.js | 85 +- .../blockchains/xmr/ext/MoneroUtilsParser.js | 419 +-- .../xmr/providers/XmrSendProvider.js | 148 +- .../xmr/providers/XmrUnspentsProvider.js | 171 +- crypto/blockchains/xrp/XrpScannerProcessor.js | 153 +- .../blockchains/xrp/XrpTransferProcessor.ts | 537 ++-- .../xrp/basic/XrpDataRippleProvider.js | 424 +-- .../xrp/basic/XrpDataScanProvider.js | 531 ++-- .../xrp/basic/XrpTxSendProvider.ts | 384 +-- crypto/blockchains/xvg/XvgScannerProcessor.js | 467 ++-- .../xvg/basic/XvgFindAddressFunction.js | 147 +- .../xvg/providers/XvgSendProvider.ts | 120 +- .../xvg/providers/XvgUnspentsProvider.ts | 135 +- crypto/common/BlocksoftAxios.js | 985 ++++--- crypto/common/BlocksoftBN.js | 181 +- crypto/common/BlocksoftCustomLinks.js | 33 +- crypto/common/BlocksoftPrivateKeysUtils.js | 246 +- crypto/services/Web3Injected.js | 175 +- package.json | 10 +- yarn.lock | 780 +++++- 108 files changed, 24689 insertions(+), 16951 deletions(-) delete mode 100644 crypto/blockchains/bnb/BnbAddressProcessor.js create mode 100644 crypto/blockchains/bnb/BnbAddressProcessor.ts delete mode 100644 crypto/blockchains/bnb/BnbScannerProcessor.js create mode 100644 crypto/blockchains/bnb/BnbScannerProcessor.ts delete mode 100644 crypto/blockchains/bnb/utils/EncoderHelper.js create mode 100644 crypto/blockchains/bnb/utils/EncoderHelper.ts delete mode 100644 crypto/blockchains/bnb/utils/UVarInt.js create mode 100644 crypto/blockchains/bnb/utils/UVarInt.ts diff --git a/crypto/actions/BlocksoftInvoice/BlocksoftInvoice.js b/crypto/actions/BlocksoftInvoice/BlocksoftInvoice.js index 16fc55945..c7f910643 100644 --- a/crypto/actions/BlocksoftInvoice/BlocksoftInvoice.js +++ b/crypto/actions/BlocksoftInvoice/BlocksoftInvoice.js @@ -2,115 +2,130 @@ * @author Ksu * @version 0.5 */ -import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog' -import BlocksoftDispatcher from '../../blockchains/BlocksoftDispatcher' +import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog'; +import BlocksoftDispatcher from '../../blockchains/BlocksoftDispatcher'; class BlocksoftInvoice { + /** + * @type {{}} + * @private + */ + _processor = {}; + /** + * @type {{privateKey, address, amount, feeForTx, currencyCode, addressForChange, nSequence, jsonData, memo}} + * @private + */ + _data = {}; - /** - * @type {{}} - * @private - */ - _processor = {} - /** - * @type {{privateKey, address, amount, feeForTx, currencyCode, addressForChange, nSequence, jsonData, memo}} - * @private - */ - _data = {} + /** + * @param address + * @return {BlocksoftInvoice} + */ + setAddress(address) { + this._data.address = address; + return this; + } - /** - * @param address - * @return {BlocksoftInvoice} - */ - setAddress(address) { - this._data.address = address - return this - } + /** + * @param jsonData + * @return {BlocksoftInvoice} + */ + setAdditional(jsonData) { + this._data.jsonData = jsonData; + return this; + } - /** - * @param jsonData - * @return {BlocksoftInvoice} - */ - setAdditional(jsonData) { - this._data.jsonData = jsonData - return this - } + /** + * @param amount + * @return {BlocksoftInvoice} + */ + setAmount(amount) { + this._data.amount = amount; + return this; + } - /** - * @param amount - * @return {BlocksoftInvoice} - */ - setAmount(amount) { - this._data.amount = amount - return this - } + /** + * @param memo + * @return {BlocksoftInvoice} + */ + setMemo(memo) { + this._data.memo = memo; + return this; + } - /** - * @param memo - * @return {BlocksoftInvoice} - */ - setMemo(memo) { - this._data.memo = memo - return this + /** + * @param currencyCode + * @return {BlocksoftInvoice} + */ + setCurrencyCode(currencyCode) { + this._data.currencyCode = currencyCode; + if (!this._processor[currencyCode]) { } + return this; + } - /** - * @param currencyCode - * @return {BlocksoftInvoice} - */ - setCurrencyCode(currencyCode) { - this._data.currencyCode = currencyCode - if (!this._processor[currencyCode]) { - - } - return this + /** + * @return {Promise<{hash}>} + */ + async createInvoice() { + const currencyCode = this._data.currencyCode; + if (!currencyCode) { + throw new Error('plz set currencyCode before calling'); } - - - /** - * @return {Promise<{hash}>} - */ - async createInvoice() { - const currencyCode = this._data.currencyCode - if (!currencyCode) { - throw new Error('plz set currencyCode before calling') - } - let res = '' - try { - BlocksoftCryptoLog.log(`BlocksoftInvoice.createInvoice ${currencyCode} started`, this._data) - res = await this._processor[currencyCode].createInvoice(this._data) - BlocksoftCryptoLog.log(`BlocksoftInvoice.createInvoice ${currencyCode} finished`, res) - } catch (e) { - // noinspection ES6MissingAwait - BlocksoftCryptoLog.err(`BlocksoftInvoice.createInvoice ${currencyCode} error ` + e.message, e.data ? e.data : e) - throw e - } - - return res + let res = ''; + try { + BlocksoftCryptoLog.log( + `BlocksoftInvoice.createInvoice ${currencyCode} started`, + this._data + ); + res = await this._processor[currencyCode].createInvoice(this._data); + BlocksoftCryptoLog.log( + `BlocksoftInvoice.createInvoice ${currencyCode} finished`, + res + ); + } catch (e) { + // noinspection ES6MissingAwait + BlocksoftCryptoLog.err( + `BlocksoftInvoice.createInvoice ${currencyCode} error ` + e.message, + e.data ? e.data : e + ); + throw e; } - async checkInvoice(hash) { - const currencyCode = this._data.currencyCode - if (!currencyCode) { - throw new Error('plz set currencyCode before calling') - } - let res = '' - try { - BlocksoftCryptoLog.log(`BlocksoftInvoice.checkInvoice ${currencyCode} started`, this._data) - res = await this._processor[currencyCode].checkInvoice(hash, this._data) - BlocksoftCryptoLog.log(`BlocksoftInvoice.checkInvoice ${currencyCode} finished`, res) - } catch (e) { - if (e.message.indexOf('not a valid invoice') === -1) { - // noinspection ES6MissingAwait - BlocksoftCryptoLog.err(`BlocksoftInvoice.checkInvoice ${currencyCode} error ` + e.message, e.data ? e.data : e) - } - throw e - } + return res; + } - return res + async checkInvoice(hash) { + const currencyCode = this._data.currencyCode; + if (!currencyCode) { + throw new Error('plz set currencyCode before calling'); } + let res = ''; + try { + BlocksoftCryptoLog.log( + `BlocksoftInvoice.checkInvoice ${currencyCode} started`, + this._data + ); + res = await this._processor[currencyCode].checkInvoice(hash, this._data); + BlocksoftCryptoLog.log( + `BlocksoftInvoice.checkInvoice ${currencyCode} finished`, + res + ); + } catch (e) { + if (e.message.indexOf('not a valid invoice') === -1) { + // noinspection ES6MissingAwait + BlocksoftCryptoLog.err( + `BlocksoftInvoice.checkInvoice ${currencyCode} error ` + e.message, + e.data ? e.data : e + ); + } + throw e; + } + + return res; + } } -const singleBlocksoftInvoice = new BlocksoftInvoice() +const singleBlocksoftInvoice = new BlocksoftInvoice(); -export default singleBlocksoftInvoice +export default singleBlocksoftInvoice; diff --git a/crypto/actions/BlocksoftKeysForRef/BlocksoftKeysForRef.js b/crypto/actions/BlocksoftKeysForRef/BlocksoftKeysForRef.js index d512bc99f..ddd890190 100644 --- a/crypto/actions/BlocksoftKeysForRef/BlocksoftKeysForRef.js +++ b/crypto/actions/BlocksoftKeysForRef/BlocksoftKeysForRef.js @@ -2,68 +2,81 @@ * @author Ksu * @version 0.5 */ -import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog' -import BlocksoftKeysForRefServerSide from './BlocksoftKeysForRefServerSide' -import BlocksoftKeys from '../BlocksoftKeys/BlocksoftKeys' -import BlocksoftDispatcher from '../../blockchains/BlocksoftDispatcher' +import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog'; +import BlocksoftKeysForRefServerSide from './BlocksoftKeysForRefServerSide'; +import BlocksoftKeys from '../BlocksoftKeys/BlocksoftKeys'; +import BlocksoftDispatcher from '../../blockchains/BlocksoftDispatcher'; - -const CACHE = {} +const CACHE = {}; class BlocksoftKeysForRef { + /** + * @param {string} data.mnemonic + * @param {int} data.index + * @return {Promise<{currencyCode:[{address, privateKey, path, index, type}]}>} + */ + async discoverPublicAndPrivate(data) { + const logData = { ...data }; + const mnemonicCache = data.mnemonic.toLowerCase(); - /** - * @param {string} data.mnemonic - * @param {int} data.index - * @return {Promise<{currencyCode:[{address, privateKey, path, index, type}]}>} - */ - async discoverPublicAndPrivate(data) { - const logData = { ...data } - const mnemonicCache = data.mnemonic.toLowerCase() - - if (typeof logData.mnemonic !== 'undefined') logData.mnemonic = '***' - if (typeof CACHE[mnemonicCache] !== 'undefined') return CACHE[mnemonicCache] - BlocksoftCryptoLog.log(`BlocksoftKeysForRef discoverPublicAndPrivate called ` + JSON.stringify(logData)) + if (typeof logData.mnemonic !== 'undefined') logData.mnemonic = '***'; + if (typeof CACHE[mnemonicCache] !== 'undefined') + return CACHE[mnemonicCache]; + BlocksoftCryptoLog.log( + `BlocksoftKeysForRef discoverPublicAndPrivate called ` + + JSON.stringify(logData) + ); - let result = BlocksoftKeys.getEthCached(mnemonicCache) - if (!result) { - BlocksoftCryptoLog.log(`BlocksoftKeysForRef discoverPublicAndPrivate no cache ` + JSON.stringify(logData)) - let index = 0 - if (typeof data.index !== 'undefined') { - index = data.index - } - const root = await BlocksoftKeys.getBip32Cached(data.mnemonic) - const path = `m/44'/60'/${index}'/0/0` - const child = root.derivePath(path) + let result = BlocksoftKeys.getEthCached(mnemonicCache); + if (!result) { + BlocksoftCryptoLog.log( + `BlocksoftKeysForRef discoverPublicAndPrivate no cache ` + + JSON.stringify(logData) + ); + let index = 0; + if (typeof data.index !== 'undefined') { + index = data.index; + } + const root = await BlocksoftKeys.getBip32Cached(data.mnemonic); + const path = `m/44'/60'/${index}'/0/0`; + const child = root.derivePath(path); - const processor = await BlocksoftDispatcher.getAddressProcessor('ETH') - result = await processor.getAddress(child.privateKey) - result.index = index - result.path = path - if (index === 0) { - BlocksoftKeys.setEthCached(data.mnemonic, result) - } - BlocksoftCryptoLog.log(`BlocksoftKeysForRef discoverPublicAndPrivate finished no cache ` + JSON.stringify(logData)) - } - // noinspection JSPrimitiveTypeWrapperUsage - result.cashbackToken = BlocksoftKeysForRefServerSide.addressToToken(result.address) - BlocksoftCryptoLog.log(`BlocksoftKeysForRef discoverPublicAndPrivate finished ` + JSON.stringify(logData)) - CACHE[mnemonicCache] = result - return result + const processor = await BlocksoftDispatcher.getAddressProcessor('ETH'); + result = await processor.getAddress(child.privateKey); + result.index = index; + result.path = path; + if (index === 0) { + BlocksoftKeys.setEthCached(data.mnemonic, result); + } + BlocksoftCryptoLog.log( + `BlocksoftKeysForRef discoverPublicAndPrivate finished no cache ` + + JSON.stringify(logData) + ); } + // noinspection JSPrimitiveTypeWrapperUsage + result.cashbackToken = BlocksoftKeysForRefServerSide.addressToToken( + result.address + ); + BlocksoftCryptoLog.log( + `BlocksoftKeysForRef discoverPublicAndPrivate finished ` + + JSON.stringify(logData) + ); + CACHE[mnemonicCache] = result; + return result; + } - async signDataForApi(msg, privateKey) { - const processor = await BlocksoftDispatcher.getAddressProcessor('ETH') - if (privateKey.substr(0, 2) !== '0x') { - privateKey = '0x' + privateKey - } - const signedData = await processor.signMessage(msg, privateKey) - delete signedData.v - delete signedData.r - delete signedData.s - return signedData + async signDataForApi(msg, privateKey) { + const processor = await BlocksoftDispatcher.getAddressProcessor('ETH'); + if (privateKey.substr(0, 2) !== '0x') { + privateKey = '0x' + privateKey; } + const signedData = await processor.signMessage(msg, privateKey); + delete signedData.v; + delete signedData.r; + delete signedData.s; + return signedData; + } } -const singleBlocksoftKeysForRef = new BlocksoftKeysForRef() -export default singleBlocksoftKeysForRef +const singleBlocksoftKeysForRef = new BlocksoftKeysForRef(); +export default singleBlocksoftKeysForRef; diff --git a/crypto/actions/BlocksoftSecrets/BlocksoftSecrets.js b/crypto/actions/BlocksoftSecrets/BlocksoftSecrets.js index 4df9b8f67..1240e6e33 100644 --- a/crypto/actions/BlocksoftSecrets/BlocksoftSecrets.js +++ b/crypto/actions/BlocksoftSecrets/BlocksoftSecrets.js @@ -2,50 +2,57 @@ * @author Ksu * @version 0.11 */ -import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog' -import BlocksoftDispatcher from '../../blockchains/BlocksoftDispatcher' +import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog'; +import BlocksoftDispatcher from '../../blockchains/BlocksoftDispatcher'; class BlocksoftSecrets { + /** + * @type {{}} + * @private + */ + _processor = {}; + + /** + * @param {string} params.currencyCode + * @param {string} params.mnemonic + * @return {Promise<{hash}>} + */ + async getWords(params) { + const currencyCode = params.currencyCode; + if (!currencyCode) { + throw new Error('plz set currencyCode before calling'); + } - /** - * @type {{}} - * @private - */ - _processor = {} - - /** - * @param {string} params.currencyCode - * @param {string} params.mnemonic - * @return {Promise<{hash}>} - */ - async getWords(params) { - const currencyCode = params.currencyCode - if (!currencyCode) { - throw new Error('plz set currencyCode before calling') - } - - if (!this._processor[currencyCode]) { - /** - * @type {XmrSecretsProcessor} - */ - this._processor[currencyCode] = BlocksoftDispatcher.getSecretsProcessor(currencyCode) - } - - let res = '' - try { - BlocksoftCryptoLog.log(`BlocksoftSecrets.getWords ${currencyCode} started`) - res = await this._processor[currencyCode].getWords(params) - BlocksoftCryptoLog.log(`BlocksoftSecrets.getWords ${currencyCode} finished`) - } catch (e) { - // noinspection ES6MissingAwait - BlocksoftCryptoLog.err(`BlocksoftSecrets.getWords ${currencyCode} error ` + e.message, e.data ? e.data : e) - throw e - } + if (!this._processor[currencyCode]) { + /** + * @type {XmrSecretsProcessor} + */ + this._processor[currencyCode] = + BlocksoftDispatcher.getSecretsProcessor(currencyCode); + } - return res + let res = ''; + try { + BlocksoftCryptoLog.log( + `BlocksoftSecrets.getWords ${currencyCode} started` + ); + res = await this._processor[currencyCode].getWords(params); + BlocksoftCryptoLog.log( + `BlocksoftSecrets.getWords ${currencyCode} finished` + ); + } catch (e) { + // noinspection ES6MissingAwait + BlocksoftCryptoLog.err( + `BlocksoftSecrets.getWords ${currencyCode} error ` + e.message, + e.data ? e.data : e + ); + throw e; } + + return res; + } } -const singleBlocksoftSecrets = new BlocksoftSecrets() +const singleBlocksoftSecrets = new BlocksoftSecrets(); -export default singleBlocksoftSecrets +export default singleBlocksoftSecrets; diff --git a/crypto/actions/BlocksoftTransfer/BlocksoftTransferPrivate.ts b/crypto/actions/BlocksoftTransfer/BlocksoftTransferPrivate.ts index 804d54f83..dea44ecd1 100644 --- a/crypto/actions/BlocksoftTransfer/BlocksoftTransferPrivate.ts +++ b/crypto/actions/BlocksoftTransfer/BlocksoftTransferPrivate.ts @@ -2,47 +2,68 @@ * @author Ksu * @version 0.20 */ -import { BlocksoftBlockchainTypes } from '../../blockchains/BlocksoftBlockchainTypes' -import BlocksoftPrivateKeysUtils from '../../common/BlocksoftPrivateKeysUtils' -import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog' -import BlocksoftKeysStorage from '../BlocksoftKeysStorage/BlocksoftKeysStorage' +import { BlocksoftBlockchainTypes } from '../../blockchains/BlocksoftBlockchainTypes'; +import BlocksoftPrivateKeysUtils from '../../common/BlocksoftPrivateKeysUtils'; +import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog'; +import BlocksoftKeysStorage from '../BlocksoftKeysStorage/BlocksoftKeysStorage'; export namespace BlocksoftTransferPrivate { + const CACHE_PRIVATE: any = {}; - const CACHE_PRIVATE: any = {} - - const initTransferPrivateBTC = async function(data: BlocksoftBlockchainTypes.TransferData, mnemonic : string): Promise { - const privateData = { - privateKey : mnemonic - } as BlocksoftBlockchainTypes.TransferPrivateData - return privateData + const initTransferPrivateBTC = async function ( + data: BlocksoftBlockchainTypes.TransferData, + mnemonic: string + ): Promise { + const privateData = { + privateKey: mnemonic + } as BlocksoftBlockchainTypes.TransferPrivateData; + return privateData; + }; + export const initTransferPrivate = async function ( + data: BlocksoftBlockchainTypes.TransferData, + additionalData: BlocksoftBlockchainTypes.TransferAdditionalData + ): Promise { + const privateData = {} as BlocksoftBlockchainTypes.TransferPrivateData; + let mnemonic = + typeof additionalData !== 'undefined' && + typeof additionalData.mnemonic !== 'undefined' + ? additionalData.mnemonic + : CACHE_PRIVATE[data.walletHash]; + if (!mnemonic) { + mnemonic = await BlocksoftKeysStorage.getWalletMnemonic( + data.walletHash, + 'initTransferPrivate' + ); + CACHE_PRIVATE[data.walletHash] = mnemonic; + } + if (!mnemonic) { + throw new Error('no mnemonic for hash ' + data.walletHash); } - export const initTransferPrivate = async function(data: BlocksoftBlockchainTypes.TransferData, additionalData: BlocksoftBlockchainTypes.TransferAdditionalData): Promise { - const privateData = {} as BlocksoftBlockchainTypes.TransferPrivateData - let mnemonic = (typeof additionalData !== 'undefined' && typeof additionalData.mnemonic !== 'undefined') ? additionalData.mnemonic : CACHE_PRIVATE[data.walletHash] - if (!mnemonic) { - mnemonic = await BlocksoftKeysStorage.getWalletMnemonic(data.walletHash, 'initTransferPrivate') - CACHE_PRIVATE[data.walletHash] = mnemonic - } - if (!mnemonic) { - throw new Error('no mnemonic for hash ' + data.walletHash) - } - if (data.currencyCode === 'BTC' || data.currencyCode === 'LTC' || data.currencyCode === 'USDT') { - return initTransferPrivateBTC(data, mnemonic) - } - const discoverFor = { - mnemonic, - addressToCheck: data.addressFrom, - walletHash: data.walletHash, - derivationPath: data.derivationPath, - currencyCode: data.currencyCode - } - const result = await BlocksoftPrivateKeysUtils.getPrivateKey(discoverFor, 'initTransferPrivate') - // @ts-ignore - privateData.privateKey = result.privateKey - // @ts-ignore - privateData.addedData = result.addedData - BlocksoftCryptoLog.log(`${data.currencyCode} BlocksoftTransferPrivate.initTransferPrivate finished for ${data.addressFrom}`) - return privateData + if ( + data.currencyCode === 'BTC' || + data.currencyCode === 'LTC' || + data.currencyCode === 'USDT' + ) { + return initTransferPrivateBTC(data, mnemonic); } + const discoverFor = { + mnemonic, + addressToCheck: data.addressFrom, + walletHash: data.walletHash, + derivationPath: data.derivationPath, + currencyCode: data.currencyCode + }; + const result = await BlocksoftPrivateKeysUtils.getPrivateKey( + discoverFor, + 'initTransferPrivate' + ); + // @ts-ignore + privateData.privateKey = result.privateKey; + // @ts-ignore + privateData.addedData = result.addedData; + BlocksoftCryptoLog.log( + `${data.currencyCode} BlocksoftTransferPrivate.initTransferPrivate finished for ${data.addressFrom}` + ); + return privateData; + }; } diff --git a/crypto/blockchains/bnb/BnbAddressProcessor.js b/crypto/blockchains/bnb/BnbAddressProcessor.js deleted file mode 100644 index b16a66dbc..000000000 --- a/crypto/blockchains/bnb/BnbAddressProcessor.js +++ /dev/null @@ -1,52 +0,0 @@ -/** - * @version 0.20 - * https://github.com/binance-chain/javascript-sdk/blob/aa1947b696f984aa931f5f029e4a439c45d5e853/src/client/index.ts#L208 - */ -const elliptic = require('elliptic') -const ec = new elliptic.ec('secp256k1') -const createHash = require('create-hash') -const bech = require('bech32') - -const Web3 = require('web3') - -function ab2hexstring(arr) { - let result = '' - for (let i = 0; i < arr.length; i++) { - let str = arr[i].toString(16) - str = str.length === 0 ? '00' : str.length === 1 ? '0' + str : str - result += str - } - return result -} - -export default class BnbAddressProcessor { - - constructor() { - this._web3Link = 'https://bsc-dataseed1.binance.org:443' - this._web3 = new Web3(new Web3.providers.HttpProvider(this._web3Link)) - } - - async setBasicRoot(root) { - - } - - /** - * @param {string|Buffer} privateKey - * @param {*} data - * @returns {Promise<{privateKey: string, address: string}>} - */ - async getAddress(privateKey, data = {}, superPrivateData = {}) { - const keypair = ec.keyFromPrivate(privateKey.toString('hex'), 'hex') - const pubPoint = keypair.getPublic() - const compressed = pubPoint.encodeCompressed() - const hexed = ab2hexstring(compressed) - const one = createHash('sha256').update(hexed, 'hex').digest() - const hash = createHash('ripemd160').update(one).digest() - const words = bech.toWords(hash) - const address = bech.encode('bnb', words) - - const account = this._web3.eth.accounts.privateKeyToAccount( '0x' + privateKey.toString('hex')) - - return { address, privateKey : privateKey.toString('hex'), addedData : { ethAddress : account.address} } - } -} diff --git a/crypto/blockchains/bnb/BnbAddressProcessor.ts b/crypto/blockchains/bnb/BnbAddressProcessor.ts new file mode 100644 index 000000000..531d1a7d0 --- /dev/null +++ b/crypto/blockchains/bnb/BnbAddressProcessor.ts @@ -0,0 +1,61 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ +/** + * @version 0.20 + * https://github.com/binance-chain/javascript-sdk/blob/aa1947b696f984aa931f5f029e4a439c45d5e853/src/client/index.ts#L208 + */ +const elliptic = require('elliptic'); +const ec = new elliptic.ec('secp256k1'); +const createHash = require('create-hash'); +const bech = require('bech32'); +const Web3 = require('web3'); + +function ab2hexstring(arr) { + let result = ''; + for (let i = 0; i < arr.length; i++) { + let str = arr[i].toString(16); + str = str.length === 0 ? '00' : str.length === 1 ? '0' + str : str; + result += str; + } + return result; +} + +export default class BnbAddressProcessor { + _web3Link: string; + _web3: typeof Web3; + constructor() { + this._web3Link = 'https://bsc-dataseed1.binance.org:443'; + this._web3 = new Web3(new Web3.providers.HttpProvider(this._web3Link)); + } + + async setBasicRoot(root) {} + + /** + * @param {string|Buffer} privateKey + * @param {*} data + * @returns {Promise<{privateKey: string, address: string}>} + */ + async getAddress( + privateKey: string | Buffer, + data = {}, + superPrivateData = {} + ): Promise<{ privateKey: string; address: string }> { + const keypair = ec.keyFromPrivate(privateKey.toString('hex'), 'hex'); + const pubPoint = keypair.getPublic(); + const compressed = pubPoint.encodeCompressed(); + const hexed = ab2hexstring(compressed); + const one = createHash('sha256').update(hexed, 'hex').digest(); + const hash = createHash('ripemd160').update(one).digest(); + const words = bech.toWords(hash); + const address = bech.encode('bnb', words); + + const account = this._web3.eth.accounts.privateKeyToAccount( + '0x' + privateKey.toString('hex') + ); + + return { + address, + privateKey: privateKey.toString('hex'), + addedData: { ethAddress: account.address } + }; + } +} diff --git a/crypto/blockchains/bnb/BnbScannerProcessor.js b/crypto/blockchains/bnb/BnbScannerProcessor.js deleted file mode 100644 index 30b080ec8..000000000 --- a/crypto/blockchains/bnb/BnbScannerProcessor.js +++ /dev/null @@ -1,169 +0,0 @@ -/** - * @version 0.20 - * https://docs.binance.org/api-reference/dex-api/paths.html#apiv1transactions - */ -import BlocksoftAxios from '../../common/BlocksoftAxios' -import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog' -import BlocksoftUtils from '../../common/BlocksoftUtils' -import config from '../../../app/config/config' -import BlocksoftExternalSettings from '../../common/BlocksoftExternalSettings' - -export default class BnbScannerProcessor { - - - /** - * https://dex.binance.org/api/v1/account/bnb146lec0enyzz2x5kpna8kwelky8kumfhj05aspz - * @param {string} address - * @return {Promise<{balance, unconfirmed, provider}>} - */ - async getBalanceBlockchain(address) { - const apiServer = await BlocksoftExternalSettings.getStatic('BNB_SERVER') - const link = `${apiServer}/api/v1/account/${address}` - let balance = 0 - let frozen = 0 - const res = await BlocksoftAxios.getWithoutBraking(link) - // "balances":[{"free":"0.00100000","frozen":"0.00000000","locked":"0.00000000","symbol":"BNB"}] - if (res && typeof res.data !== 'undefined' && res.data && typeof res.data.balances !== 'undefined') { - let row - for (row of res.data.balances) { - if (row.symbol === 'BNB') { - balance = row.free - frozen = row.frozen - break - } - } - } else { - await BlocksoftCryptoLog.log('BnbScannerProcessor.getBalanceBlockchain ' + address + ' no actual balance ' + link, res) - return false - } - - - return { balance, unconfirmed: 0, frozen, provider: 'dex.binance' } - } - - - /** - * https://dex.binance.org/api/v1/transactions/?address=bnb146lec0enyzz2x5kpna8kwelky8kumfhj05aspz - * https://dex.binance.org/api/v1/transactions/?address=bnb146lec0enyzz2x5kpna8kwelky8kumfhj05aspz&startTime=1609452000000&limit=100 - * https://docs.binance.org/api-reference/dex-api/paths.html#apiv1transactions - * https://github.com/trustwallet/blockatlas/blob/b4f6dc360bed412ff555aa981d83e4421380f104/platform/binance/client.go#L43 - * @param {string} scanData.account.address - * @return {Promise<[UnifiedTransaction]>} - */ - async getTransactionsBlockchain(scanData) { - const address = scanData.account.address.trim() - BlocksoftCryptoLog.log('BnbScannerProcessor.getTransactions started', address) - - const apiServer = await BlocksoftExternalSettings.getStatic('BNB_SERVER') - - let linkTxs = `${apiServer}/api/v1/transactions/?address=${address}&txAsset=BNB` // 2021-01-01 - if (scanData.account.balanceScanTime && scanData.account.balanceScanTime * 1 > 0) { - linkTxs += '&startTime=' + (scanData.account.balanceScanTime - 86400) * 1000 // 1 day - } - - const res = await BlocksoftAxios.getWithoutBraking(linkTxs) - if (!res || typeof res.data === 'undefined' || !res.data) { - return false - } - - const transactions = await this._unifyTransactions(address, res.data.tx) - BlocksoftCryptoLog.log('BnbScannerProcessor.getTransactions finished', address) - return transactions - } - - async _unifyTransactions(address, result) { - const transactions = [] - let tx - for (tx of result) { - const transaction = await this._unifyTransaction(address, tx) - if (transaction) { - transactions.push(transaction) - } - } - return transactions - } - - /** - * @param {string} address - * @param {Object} transaction - * @param {string} transaction.blockHeight 144626977 - * @param {string} transaction.code 0 - * @param {string} transaction.confirmBlocks - * @param {string} transaction.data - * @param {string} transaction.fromAddr bnb1jxfh2g85q3v0tdq56fnevx6xcxtcnhtsmcu64m - * @param {string} transaction.memo - * @param {string} transaction.orderId - * @param {string} transaction.proposalId - * @param {string} transaction.sequence 1950767 - * @param {string} transaction.source 0 - * @param {string} transaction.timeStamp 2021-02-15T21:40:00.232Z - * @param {string} transaction.toAddr bnb146lec0enyzz2x5kpna8kwelky8kumfhj05aspz - * @param {string} transaction.txAge 428893 - * @param {string} transaction.txAsset "BNB" - * @param {string} transaction.txFee "0.00037500" - * @param {string} transaction.txHash "D160D16A01998B023EC3ABBCD1D1064F23AC1D17715ECAE1E895DC0AA9D12B5A" - * @param {string} transaction.txType: "TRANSFER" - * @param {string} transaction.value: "0.00100000" - * @return {UnifiedTransaction} - * @private - **/ - async _unifyTransaction(address, transaction) { - try { - let tx - if (transaction.txType === 'TRANSFER') { - tx = { - transactionHash: transaction.txHash, - blockHash: transaction.blockHeight, - blockNumber: transaction.blockHeight, - blockTime: transaction.timeStamp, - blockConfirmations: transaction.txAge, - transactionDirection: '?', - addressFrom: transaction.fromAddr === address ? '' : transaction.fromAddr, - addressTo: transaction.toAddr === address ? '' : transaction.toAddr, - addressAmount: transaction.value, - transactionStatus: transaction.code === 0 ? 'success' : (transaction.txAge === 0 ? 'new' : 'fail'), - transactionFee: transaction.txFee - } - } else if (transaction.txType === 'CROSS_TRANSFER_OUT' && typeof transaction.data !== 'undefined') { - const tmp = JSON.parse(transaction.data) - if (typeof tmp.amount.denom === 'undefined' || tmp.amount.denom !== 'BNB') return false - tx = { - transactionHash: transaction.txHash, - blockHash: transaction.blockHeight, - blockNumber: transaction.blockHeight, - blockTime: transaction.timeStamp, - blockConfirmations: transaction.txAge, - transactionDirection: '?', - addressFrom: tmp.from === address ? '' : tmp.from, - addressTo: tmp.to === address ? '' : tmp.to, - addressAmount: BlocksoftUtils.toUnified(tmp.amount.amount, 8), - transactionStatus: transaction.code === 0 ? 'success' : (transaction.txAge === 0 ? 'new' : 'fail'), - transactionFee: transaction.txFee - } - } else { - return false - } - - if (tx.addressTo === '' || !tx.addressTo) { - if (tx.addressFrom === '') { - tx.transactionDirection = 'self' - } else { - tx.transactionDirection = 'income' - } - } else { - tx.transactionDirection = 'outcome' - } - - if (typeof transaction.memo !== 'undefined' && transaction.memo !== '') { - tx.transactionJson = { memo: transaction.memo } - } - - return tx - } catch (e) { - if (config.debug.cryptoErrors) { - console.log('BnbScannerProcessor _unifyTransaction error ' + e.message) - } - throw e - } - } -} diff --git a/crypto/blockchains/bnb/BnbScannerProcessor.ts b/crypto/blockchains/bnb/BnbScannerProcessor.ts new file mode 100644 index 000000000..8b7731b00 --- /dev/null +++ b/crypto/blockchains/bnb/BnbScannerProcessor.ts @@ -0,0 +1,202 @@ +/** + * @version 0.20 + * https://docs.binance.org/api-reference/dex-api/paths.html#apiv1transactions + */ +import BlocksoftAxios from '../../common/BlocksoftAxios'; +import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog'; +import BlocksoftUtils from '../../common/BlocksoftUtils'; +import BlocksoftExternalSettings from '../../common/BlocksoftExternalSettings'; + +export default class BnbScannerProcessor { + /** + * https://dex.binance.org/api/v1/account/bnb146lec0enyzz2x5kpna8kwelky8kumfhj05aspz + * @param {string} address + * @return {Promise<{balance, unconfirmed, provider}>} + */ + async getBalanceBlockchain( + address: string + ): Promise<{ balance: string; unconfirmed: any; provider: any }> { + const apiServer = await BlocksoftExternalSettings.getStatic('BNB_SERVER'); + const link = `${apiServer}/api/v1/account/${address}`; + let balance = 0; + let frozen = 0; + const res = await BlocksoftAxios.getWithoutBraking(link); + // "balances":[{"free":"0.00100000","frozen":"0.00000000","locked":"0.00000000","symbol":"BNB"}] + if ( + res && + typeof res.data !== 'undefined' && + res.data && + typeof res.data.balances !== 'undefined' + ) { + let row; + for (row of res.data.balances) { + if (row.symbol === 'BNB') { + balance = row.free; + frozen = row.frozen; + break; + } + } + } else { + await BlocksoftCryptoLog.log( + 'BnbScannerProcessor.getBalanceBlockchain ' + + address + + ' no actual balance ' + + link, + res + ); + return false; + } + + return { balance, unconfirmed: 0, frozen, provider: 'dex.binance' }; + } + + /** + * https://dex.binance.org/api/v1/transactions/?address=bnb146lec0enyzz2x5kpna8kwelky8kumfhj05aspz + * https://dex.binance.org/api/v1/transactions/?address=bnb146lec0enyzz2x5kpna8kwelky8kumfhj05aspz&startTime=1609452000000&limit=100 + * https://docs.binance.org/api-reference/dex-api/paths.html#apiv1transactions + * https://github.com/trustwallet/blockatlas/blob/b4f6dc360bed412ff555aa981d83e4421380f104/platform/binance/client.go#L43 + * @param {string} scanData.account.address + * @return {Promise<[UnifiedTransaction]>} + */ + async getTransactionsBlockchain(scanData) { + const address = scanData.account.address.trim(); + BlocksoftCryptoLog.log( + 'BnbScannerProcessor.getTransactions started', + address + ); + + const apiServer = await BlocksoftExternalSettings.getStatic('BNB_SERVER'); + + let linkTxs = `${apiServer}/api/v1/transactions/?address=${address}&txAsset=BNB`; // 2021-01-01 + if ( + scanData.account.balanceScanTime && + scanData.account.balanceScanTime * 1 > 0 + ) { + linkTxs += + '&startTime=' + (scanData.account.balanceScanTime - 86400) * 1000; // 1 day + } + + const res = await BlocksoftAxios.getWithoutBraking(linkTxs); + if (!res || typeof res.data === 'undefined' || !res.data) { + return false; + } + + const transactions = await this._unifyTransactions(address, res.data.tx); + BlocksoftCryptoLog.log( + 'BnbScannerProcessor.getTransactions finished', + address + ); + return transactions; + } + + async _unifyTransactions(address, result) { + const transactions = []; + let tx; + for (tx of result) { + const transaction = await this._unifyTransaction(address, tx); + if (transaction) { + transactions.push(transaction); + } + } + return transactions; + } + + /** + * @param {string} address + * @param {Object} transaction + * @param {string} transaction.blockHeight 144626977 + * @param {string} transaction.code 0 + * @param {string} transaction.confirmBlocks + * @param {string} transaction.data + * @param {string} transaction.fromAddr bnb1jxfh2g85q3v0tdq56fnevx6xcxtcnhtsmcu64m + * @param {string} transaction.memo + * @param {string} transaction.orderId + * @param {string} transaction.proposalId + * @param {string} transaction.sequence 1950767 + * @param {string} transaction.source 0 + * @param {string} transaction.timeStamp 2021-02-15T21:40:00.232Z + * @param {string} transaction.toAddr bnb146lec0enyzz2x5kpna8kwelky8kumfhj05aspz + * @param {string} transaction.txAge 428893 + * @param {string} transaction.txAsset "BNB" + * @param {string} transaction.txFee "0.00037500" + * @param {string} transaction.txHash "D160D16A01998B023EC3ABBCD1D1064F23AC1D17715ECAE1E895DC0AA9D12B5A" + * @param {string} transaction.txType: "TRANSFER" + * @param {string} transaction.value: "0.00100000" + * @return {UnifiedTransaction} + * @private + **/ + async _unifyTransaction(address, transaction) { + try { + let tx; + if (transaction.txType === 'TRANSFER') { + tx = { + transactionHash: transaction.txHash, + blockHash: transaction.blockHeight, + blockNumber: transaction.blockHeight, + blockTime: transaction.timeStamp, + blockConfirmations: transaction.txAge, + transactionDirection: '?', + addressFrom: + transaction.fromAddr === address ? '' : transaction.fromAddr, + addressTo: transaction.toAddr === address ? '' : transaction.toAddr, + addressAmount: transaction.value, + transactionStatus: + transaction.code === 0 + ? 'success' + : transaction.txAge === 0 + ? 'new' + : 'fail', + transactionFee: transaction.txFee + }; + } else if ( + transaction.txType === 'CROSS_TRANSFER_OUT' && + typeof transaction.data !== 'undefined' + ) { + const tmp = JSON.parse(transaction.data); + if ( + typeof tmp.amount.denom === 'undefined' || + tmp.amount.denom !== 'BNB' + ) + return false; + tx = { + transactionHash: transaction.txHash, + blockHash: transaction.blockHeight, + blockNumber: transaction.blockHeight, + blockTime: transaction.timeStamp, + blockConfirmations: transaction.txAge, + transactionDirection: '?', + addressFrom: tmp.from === address ? '' : tmp.from, + addressTo: tmp.to === address ? '' : tmp.to, + addressAmount: BlocksoftUtils.toUnified(tmp.amount.amount, 8), + transactionStatus: + transaction.code === 0 + ? 'success' + : transaction.txAge === 0 + ? 'new' + : 'fail', + transactionFee: transaction.txFee + }; + } else { + return false; + } + + if (tx.addressTo === '' || !tx.addressTo) { + if (tx.addressFrom === '') { + tx.transactionDirection = 'self'; + } else { + tx.transactionDirection = 'income'; + } + } else { + tx.transactionDirection = 'outcome'; + } + + if (typeof transaction.memo !== 'undefined' && transaction.memo !== '') { + tx.transactionJson = { memo: transaction.memo }; + } + + return tx; + } catch (e) { + throw e; + } + } +} diff --git a/crypto/blockchains/bnb/BnbTransferProcessor.ts b/crypto/blockchains/bnb/BnbTransferProcessor.ts index 6dfe3d3b0..252167466 100644 --- a/crypto/blockchains/bnb/BnbTransferProcessor.ts +++ b/crypto/blockchains/bnb/BnbTransferProcessor.ts @@ -3,151 +3,189 @@ * https://docs.binance.org/smart-chain/developer/create-wallet.html * https://docs.binance.org/guides/concepts/encoding/amino-example.html#transfer */ -import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog' -import MarketingEvent from '../../../app/services/Marketing/MarketingEvent' - -import { BlocksoftBlockchainTypes } from '../BlocksoftBlockchainTypes' -import { BnbTxSendProvider } from './basic/BnbTxSendProvider' -import BlocksoftUtils from '../../common/BlocksoftUtils' -import BnbNetworkPrices from './basic/BnbNetworkPrices' - -export default class BnbTransferProcessor implements BlocksoftBlockchainTypes.TransferProcessor { - private _settings: { network: string; currencyCode: string } - private _provider: BnbTxSendProvider - - constructor(settings: { network: string; currencyCode: string }) { - this._settings = settings - this._provider = new BnbTxSendProvider() +import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog'; + +import { AirDAOBlockchainTypes } from '../AirDAOBlockchainTypes'; +import { BnbTxSendProvider } from './basic/BnbTxSendProvider'; +import BlocksoftUtils from '../../common/BlocksoftUtils'; +import BnbNetworkPrices from './basic/BnbNetworkPrices'; + +export default class BnbTransferProcessor + implements AirDAOBlockchainTypes.TransferProcessor +{ + private _settings: { network: string; currencyCode: string }; + private _provider: BnbTxSendProvider; + + constructor(settings: { network: string; currencyCode: string }) { + this._settings = settings; + this._provider = new BnbTxSendProvider(); + } + + needPrivateForFee(): boolean { + return false; + } + + checkSendAllModal(data: { currencyCode: any }): boolean { + return false; + } + + async getFeeRate( + data: AirDAOBlockchainTypes.TransferData, + privateData: AirDAOBlockchainTypes.TransferPrivateData, + additionalData: {} = {} + ): Promise { + const fees = await BnbNetworkPrices.getFees(); + const feeForTx = BlocksoftUtils.toUnified(fees.send.fee, 8); + const result: AirDAOBlockchainTypes.FeeRateResult = { + selectedFeeIndex: 0, + shouldShowFees: false, + fees: [ + { + langMsg: 'xrp_speed_one', + feeForTx, + amountForTx: data.amount + } + ] + } as AirDAOBlockchainTypes.FeeRateResult; + return result; + } + + async getTransferAllBalance( + data: AirDAOBlockchainTypes.TransferData, + privateData: AirDAOBlockchainTypes.TransferPrivateData, + additionalData: AirDAOBlockchainTypes.TransferAdditionalData = {} + ): Promise { + const balance = data.amount; + // @ts-ignore + await BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' BnbTransferProcessor.getTransferAllBalance ', + data.addressFrom + ' => ' + balance + ); + + const result = await this.getFeeRate(data, privateData, additionalData); + // @ts-ignore + if (!result || result.selectedFeeIndex < 0) { + return { + selectedTransferAllBalance: '0', + selectedFeeIndex: -2, + fees: [], + countedForBasicBalance: balance + }; } - - needPrivateForFee(): boolean { - return false + // @ts-ignore + const newAmount = BlocksoftUtils.diff( + result.fees[result.selectedFeeIndex].amountForTx, + result.fees[result.selectedFeeIndex].feeForTx + ).toString(); + if (newAmount * 1 < 0) { + throw new Error('SERVER_RESPONSE_NOTHING_TO_TRANSFER'); } - - checkSendAllModal(data: { currencyCode: any }): boolean { - return false + result.fees[result.selectedFeeIndex].amountForTx = newAmount; + const tmp = { + ...result, + shouldShowFees: false, + selectedTransferAllBalance: + result.fees[result.selectedFeeIndex].amountForTx + }; + // console.log('tmp', JSON.stringify(tmp)) + return tmp; + } + + async sendTx( + data: AirDAOBlockchainTypes.TransferData, + privateData: AirDAOBlockchainTypes.TransferPrivateData, + uiData: AirDAOBlockchainTypes.TransferUiData + ): Promise { + if (typeof privateData.privateKey === 'undefined') { + throw new Error('BNB transaction required privateKey'); } - - async getFeeRate(data: BlocksoftBlockchainTypes.TransferData, privateData: BlocksoftBlockchainTypes.TransferPrivateData, additionalData: {} = {}): Promise { - - const fees = await BnbNetworkPrices.getFees() - const feeForTx = BlocksoftUtils.toUnified(fees.send.fee, 8) - const result: BlocksoftBlockchainTypes.FeeRateResult = { - selectedFeeIndex: 0, - shouldShowFees : false, - fees : [ - { - langMsg: 'xrp_speed_one', - feeForTx, - amountForTx: data.amount - } - ] - } as BlocksoftBlockchainTypes.FeeRateResult - return result + if (typeof data.addressTo === 'undefined') { + throw new Error('BNB transaction required addressTo'); } - async getTransferAllBalance(data: BlocksoftBlockchainTypes.TransferData, privateData: BlocksoftBlockchainTypes.TransferPrivateData, additionalData: BlocksoftBlockchainTypes.TransferAdditionalData = {}): Promise { + await BlocksoftCryptoLog.log( + this._settings.currencyCode + ' BnbTransferProcessor.sendTx start' + ); - const balance = data.amount - // @ts-ignore - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' BnbTransferProcessor.getTransferAllBalance ', data.addressFrom + ' => ' + balance) - - const result = await this.getFeeRate(data, privateData, additionalData) - // @ts-ignore - if (!result || result.selectedFeeIndex < 0) { - return { - selectedTransferAllBalance: '0', - selectedFeeIndex: -2, - fees: [], - countedForBasicBalance: balance - } - } - // @ts-ignore - const newAmount = BlocksoftUtils.diff(result.fees[result.selectedFeeIndex].amountForTx, result.fees[result.selectedFeeIndex].feeForTx).toString() - if (newAmount*1 < 0) { - throw new Error('SERVER_RESPONSE_NOTHING_TO_TRANSFER') - } - result.fees[result.selectedFeeIndex].amountForTx = newAmount - const tmp = { - ...result, - shouldShowFees : false, - selectedTransferAllBalance: result.fees[result.selectedFeeIndex].amountForTx - } - // console.log('tmp', JSON.stringify(tmp)) - return tmp + let transaction; + try { + transaction = await this._provider.getPrepared(data, privateData, uiData); + } catch (e) { + throw new Error(e.message + ' in BNB getPrepared'); + } + // @ts-ignore + await BlocksoftCryptoLog.log( + this._settings.currencyCode + ' BnbTransferProcessor.sendTx tx', + transaction + ); + + let raw; + try { + raw = this._provider.serializeTx(transaction); + } catch (e) { + throw new Error(e.message + ' in BNB serializeTx'); + } + await BlocksoftCryptoLog.log( + this._settings.currencyCode + ' BnbTransferProcessor.sendTx raw', + raw + ); + if ( + typeof uiData !== 'undefined' && + typeof uiData.selectedFee !== 'undefined' && + typeof uiData.selectedFee.rawOnly !== 'undefined' && + uiData.selectedFee.rawOnly + ) { + return { rawOnly: uiData.selectedFee.rawOnly, raw }; } - async sendTx(data: BlocksoftBlockchainTypes.TransferData, privateData: BlocksoftBlockchainTypes.TransferPrivateData, uiData: BlocksoftBlockchainTypes.TransferUiData): Promise { - - if (typeof privateData.privateKey === 'undefined') { - throw new Error('BNB transaction required privateKey') - } - if (typeof data.addressTo === 'undefined') { - throw new Error('BNB transaction required addressTo') - } - - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' BnbTransferProcessor.sendTx start') - - let transaction - try { - transaction = await this._provider.getPrepared(data, privateData, uiData) - } catch (e) { - throw new Error(e.message + ' in BNB getPrepared') - } - // @ts-ignore - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' BnbTransferProcessor.sendTx tx', transaction) - - - let raw - try { - raw = this._provider.serializeTx(transaction) - } catch (e) { - throw new Error(e.message + ' in BNB serializeTx') - } - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' BnbTransferProcessor.sendTx raw', raw) - if (typeof uiData !== 'undefined' && typeof uiData.selectedFee !== 'undefined' && typeof uiData.selectedFee.rawOnly !== 'undefined' && uiData.selectedFee.rawOnly) { - return { rawOnly: uiData.selectedFee.rawOnly, raw } - } - - let result - try { - result = await this._provider.sendRaw(raw) - } catch (e) { - if (e.message.indexOf('SERVER_RESPONSE_') === -1) { - throw new Error(e.message + ' in BNB sendRaw1') - } else { - throw e - } - } - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' BnbTransferProcessor.sendTx result', result) - - try { - if (typeof result.message !== 'undefined') { - if (result.message.indexOf('insufficient fund') !== -1 || result.message.indexOf('BNB <') !== -1) { - throw new Error('SERVER_RESPONSE_NOTHING_TO_TRANSFER') - } else { - throw new Error(result.message) - } - } - if (typeof result[0] === 'undefined' || typeof result[0].hash === 'undefined' || typeof result[0].ok === 'undefined' || !result[0].ok || !result[0].hash) { - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' BnbTransferProcessor.sendTx result', result) - MarketingEvent.logOnlyRealTime('v20_bnb_no_result ' + data.addressFrom + ' => ' + data.addressTo, { - result, - raw - }) - throw new Error('SERVER_RESPONSE_NO_RESPONSE') - } - - MarketingEvent.logOnlyRealTime('v20_bnb_success_result ' + data.addressFrom + ' => ' + data.addressTo + ' ' + result[0].hash, { - result - }) - return { transactionHash: result[0].hash } - } catch (e) { - if (e.message.indexOf('SERVER_RESPONSE_') === -1) { - throw new Error(e.message + ' in BNB sendRaw1 parse result') - } else { - throw e - } + let result; + try { + result = await this._provider.sendRaw(raw); + } catch (e) { + if (e.message.indexOf('SERVER_RESPONSE_') === -1) { + throw new Error(e.message + ' in BNB sendRaw1'); + } else { + throw e; + } + } + await BlocksoftCryptoLog.log( + this._settings.currencyCode + ' BnbTransferProcessor.sendTx result', + result + ); + + try { + if (typeof result.message !== 'undefined') { + if ( + result.message.indexOf('insufficient fund') !== -1 || + result.message.indexOf('BNB <') !== -1 + ) { + throw new Error('SERVER_RESPONSE_NOTHING_TO_TRANSFER'); + } else { + throw new Error(result.message); } + } + if ( + typeof result[0] === 'undefined' || + typeof result[0].hash === 'undefined' || + typeof result[0].ok === 'undefined' || + !result[0].ok || + !result[0].hash + ) { + await BlocksoftCryptoLog.log( + this._settings.currencyCode + ' BnbTransferProcessor.sendTx result', + result + ); + throw new Error('SERVER_RESPONSE_NO_RESPONSE'); + } + + return { transactionHash: result[0].hash }; + } catch (e) { + if (e.message.indexOf('SERVER_RESPONSE_') === -1) { + throw new Error(e.message + ' in BNB sendRaw1 parse result'); + } else { + throw e; + } } + } } diff --git a/crypto/blockchains/bnb/basic/BnbNetworkPrices.js b/crypto/blockchains/bnb/basic/BnbNetworkPrices.js index e6d4043a3..d8cd29042 100644 --- a/crypto/blockchains/bnb/basic/BnbNetworkPrices.js +++ b/crypto/blockchains/bnb/basic/BnbNetworkPrices.js @@ -5,60 +5,61 @@ * https://dex.binance.org/api/v1/fees * https://docs.binance.org/api-reference/dex-api/paths.html#apiv1fees */ -import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog' -import BlocksoftAxios from '../../../common/BlocksoftAxios' -import BlocksoftExternalSettings from '../../../common/BlocksoftExternalSettings' +import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; +import BlocksoftAxios from '../../../common/BlocksoftAxios'; +import BlocksoftExternalSettings from '../../../common/BlocksoftExternalSettings'; -const CACHE_VALID_TIME = 60000 // 1 minute +const CACHE_VALID_TIME = 60000; // 1 minute let CACHE_FEES = { - send : { - fee: 37500 - } -} -let CACHE_FEES_TIME = 0 + send: { + fee: 37500 + } +}; +let CACHE_FEES_TIME = 0; class BnbNetworkPrices { + async getFees() { + const now = new Date().getTime(); + if (CACHE_FEES && now - CACHE_FEES_TIME < CACHE_VALID_TIME) { + return CACHE_FEES; + } + BlocksoftCryptoLog.log('BnbNetworkPricesProvider.getFees no cache load'); - async getFees() { - const now = new Date().getTime() - if (CACHE_FEES && now - CACHE_FEES_TIME < CACHE_VALID_TIME) { - return CACHE_FEES - } - - BlocksoftCryptoLog.log('BnbNetworkPricesProvider.getFees no cache load') - - const apiServer = await BlocksoftExternalSettings.getStatic('BNB_SERVER') - const link = `${apiServer}/api/v1/fees` + const apiServer = await BlocksoftExternalSettings.getStatic('BNB_SERVER'); + const link = `${apiServer}/api/v1/fees`; - try { - const tmp = await BlocksoftAxios.getWithoutBraking(link, 2) - if (tmp.data) { - // {"fee": 1000000, "fee_for": 1, "msg_type": "transferOwnership"}] - const result = {} - for (const row of tmp.data) { - if (typeof row.fixed_fee_params !== 'undefined') { - result[row.fixed_fee_params.msg_type] = { fee: row.fixed_fee_params.fee, for: row.fixed_fee_params.fee_for } - } else { - result[row.msg_type] = { fee: row.fee, for: row.fee_for } - } - } - if (typeof result['send'] !== 'undefined') { - CACHE_FEES = result - CACHE_FEES_TIME = now - } - } - } catch (e) { - BlocksoftCryptoLog.log('BnbNetworkPricesProvider.getOnlyFees loaded prev fee as error' + e.message) - // do nothing + try { + const tmp = await BlocksoftAxios.getWithoutBraking(link, 2); + if (tmp.data) { + // {"fee": 1000000, "fee_for": 1, "msg_type": "transferOwnership"}] + const result = {}; + for (const row of tmp.data) { + if (typeof row.fixed_fee_params !== 'undefined') { + result[row.fixed_fee_params.msg_type] = { + fee: row.fixed_fee_params.fee, + for: row.fixed_fee_params.fee_for + }; + } else { + result[row.msg_type] = { fee: row.fee, for: row.fee_for }; + } } - - return CACHE_FEES + if (typeof result['send'] !== 'undefined') { + CACHE_FEES = result; + CACHE_FEES_TIME = now; + } + } + } catch (e) { + BlocksoftCryptoLog.log( + 'BnbNetworkPricesProvider.getOnlyFees loaded prev fee as error' + + e.message + ); + // do nothing } - + return CACHE_FEES; + } } -const singleton = new BnbNetworkPrices() -export default singleton - +const singleton = new BnbNetworkPrices(); +export default singleton; diff --git a/crypto/blockchains/bnb/basic/BnbTxSendProvider.ts b/crypto/blockchains/bnb/basic/BnbTxSendProvider.ts index c31a3182b..916975812 100644 --- a/crypto/blockchains/bnb/basic/BnbTxSendProvider.ts +++ b/crypto/blockchains/bnb/basic/BnbTxSendProvider.ts @@ -1,272 +1,322 @@ -import BlocksoftAxios from '../../../common/BlocksoftAxios' -import { BlocksoftBlockchainTypes } from '../../BlocksoftBlockchainTypes' -import config from '../../../../app/config/config' -import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog' -import BlocksoftUtils from '../../../common/BlocksoftUtils' -import BlocksoftExternalSettings from '../../../common/BlocksoftExternalSettings' - -const elliptic = require('elliptic') -const ec = new elliptic.ec('secp256k1') -const createHash = require('create-hash') -const bech = require('bech32') - - -const UVarInt = require('../utils/UVarInt').UVarInt -const Encode = require('../utils/Encode') -const _tinySecp256k = require('tiny-secp256k1') - -function decodeAddress(value: any) { - try { - const decodeAddress = bech.decode(value) - return Buffer.from(bech.fromWords(decodeAddress.words)) - } catch (e) { - throw new Error(e.message + ' while decodeAddress ' + value) - } -} - -function convertObjectToSignBytes(obj: any) { - try { - return Buffer.from(JSON.stringify(Encode.sortObject(obj))) - } catch (e) { - throw new Error(e.message + ' in convertObjectToSignBytes') - } -} - -function encodeBinaryByteArray(bytes: any) { - try { - const lenPrefix = bytes.length - return Buffer.concat([UVarInt.encode(lenPrefix), bytes]) - } catch (e) { - throw new Error(e.message + ' in encodeBinaryByteArray') - } -} - -function serializePubKey(unencodedPubKey: any) { - try { - let format = 0x2 - const y = unencodedPubKey.getY() - const x = unencodedPubKey.getX() - - if (y && y.isOdd()) { - format |= 0x1 - } - - let pubBz = Buffer.concat([UVarInt.encode(format), x.toArrayLike(Buffer, 'be', 32)]) // prefixed with length - - pubBz = encodeBinaryByteArray(pubBz) // add the amino prefix - - pubBz = Buffer.concat([Buffer.from('EB5AE987', 'hex'), pubBz]) - return pubBz - } catch (e) { - throw new Error(e.message + ' in serializePubKey') - } -} - -function marshalBinary(obj: any) { - try { - return Encode.encodeBinary(obj, -1, true).toString('hex') - } catch (e) { - throw new Error(e.message + ' in marshalBinary') - } -} - - -export class BnbTxSendProvider { - - async getPrepared(data: BlocksoftBlockchainTypes.TransferData, privateData: BlocksoftBlockchainTypes.TransferPrivateData, uiData: BlocksoftBlockchainTypes.TransferUiData, type = 'usual') { - const apiServer = await BlocksoftExternalSettings.getStatic('BNB_SERVER') - const res = await BlocksoftAxios.getWithoutBraking(apiServer + '/api/v1/account/' + data.addressFrom) - if (!res.data) { - throw new Error('no data') - } - const account = res.data - - const unified = BlocksoftUtils.fromUnified(data.amount, 8) * 1 - let msg - let txMsg - - let decodedFrom - let decodedTo - try { - decodedFrom = decodeAddress(data.addressFrom) - } catch (e) { - throw new Error(e.message + ' in BNB decodeAddress ' + JSON.stringify(data.addressFrom)) - } - try { - if (data.addressTo.indexOf('0x') === -1) { - decodedTo = decodeAddress(data.addressTo) - } else { - decodedTo = data.addressTo - } - } catch (e) { - throw new Error(e.message + ' in BNB decodeAddress ' + JSON.stringify(data.addressTo)) - } - - if (typeof data.blockchainData !== 'undefined' && typeof data.blockchainData.action !== 'undefined' && data.blockchainData.action === 'BnbToSmart') { - let addressTo = data.addressTo - if (addressTo.substr(0,2) === '0x') { - addressTo = addressTo.substr(2) - } - msg = { - from: data.addressFrom, - to: data.addressTo, - amount: { 'denom': 'BNB', 'amount': unified }, - expire_time: data.blockchainData.expire_time - } - txMsg = { - from: decodedFrom, - to: Buffer.from(addressTo, 'hex'), - amount: { 'denom': 'BNB', 'amount': unified }, - expire_time: data.blockchainData.expire_time, - aminoPrefix: '800819C0' - } - } else { - msg = { - 'inputs': [{ - 'address': data.addressFrom, 'coins': [{ - 'amount': unified, - 'denom': 'BNB' - }] - }], - 'outputs': [{ - 'address': data.addressTo, 'coins': [{ - 'amount': unified, - 'denom': 'BNB' - }] - }] - } - txMsg = { - 'inputs': [{ - 'address': decodedFrom, 'coins': [{ - 'denom': 'BNB', - 'amount': unified - }] - }], - 'outputs': [{ - 'address': decodedTo, 'coins': [{ - 'denom': 'BNB', - 'amount': unified - }] - }], - aminoPrefix: '2A2C87FA' - } - } - const memo = (typeof data.memo === 'undefined' || !data.memo) ? '' : data.memo - const signMsg = { - account_number: account.account_number + '', - chain_id: 'Binance-Chain-Tigris', - data: null, - memo, - msgs: [msg], - sequence: account.sequence + '', - source: '0' - } - let buff - try { - buff = convertObjectToSignBytes(signMsg) - } catch (e) { - throw new Error(e.message + ' in BNB convertObjectToSignBytes ' + JSON.stringify(signMsg)) - } - const signBytesHex = buff.toString('hex') - let msgHash - try { - msgHash = createHash('sha256').update(signBytesHex, 'hex').digest() - } catch (e) { - throw new Error(e.message + ' in BNB createHash') - } - let keypair - try { - keypair = ec.keyFromPrivate(privateData.privateKey, 'hex') - } catch (e) { - throw new Error(e.message + ' in BNB ec.keyFromPrivate') - } - let signature - try { - signature = _tinySecp256k.sign(msgHash, Buffer.from(privateData.privateKey, 'hex')) - } catch (e) { - throw new Error(e.message + ' in BNB _tinySecp256k.sign') - } - const signatureHex = signature.toString('hex') - let pubKey - try { - pubKey = keypair.getPublic() - } catch (e) { - throw new Error(e.message + ' in BNB keypair.getPublic()') - } - let pubSerialize - try { - pubSerialize = serializePubKey(pubKey) - } catch (e) { - throw new Error(e.message + ' in BNB serializePubKey') - } - - const signatures = [{ - pub_key: pubSerialize, - signature: Buffer.from(signatureHex, 'hex'), - account_number: account.account_number, - sequence: account.sequence - }] - const transaction = { - sequence: account.sequence + '', - accountNumber: account.account_number + '', - chainId: 'Binance-Chain-Tigris', - msg: txMsg, - baseMsg: undefined, - memo: memo, - source: 0, - signatures - } - return transaction - } - - serializeTx(tx: any) { - if (!tx.signatures) { - throw new Error('need signature') - } - - const msg = tx.msg || tx.baseMsg && tx.baseMsg.getMsg() - const stdTx = { - msg: [msg], - signatures: tx.signatures, - memo: tx.memo, - source: tx.source, - // sdk value is 0, web wallet value is 1 - data: '', - aminoPrefix: 'F0625DEE' - } - const bytes = marshalBinary(stdTx) - return bytes.toString('hex') - } - - async sendRaw(raw: string) { - let result = false - try { - // console.log(`curl -X POST -F "tx=${raw}" "https://dex.binance.org/api/v1/broadcast"`) - const apiServer = await BlocksoftExternalSettings.getStatic('BNB_SERVER') - const response = await fetch(apiServer + '/api/v1/broadcast?sync=true', { - method: 'POST', - credentials: 'same-origin', - mode: 'same-origin', - headers: { - 'Content-Type': 'text/plain' - }, - body: raw - }) - result = await response.json() - - if (typeof result.status !== 'undefined') { - if (result.status === 406 || result.status === 400 || result.status === 504) { - throw new Error(result.title) - } - } - } catch (e) { - if (config.debug.cryptoErrors) { - console.log('BnbTransferProcessor.sendTx error ', e) - } - await BlocksoftCryptoLog.log('BnbTransferProcessor.sendTx error ' + e.message) - throw e - } - await BlocksoftCryptoLog.log('BnbTransferProcessor.sendTx result ', result) - return result - } -} +/* eslint-disable camelcase */ +/* eslint-disable @typescript-eslint/no-var-requires */ +import BlocksoftAxios from '../../../common/BlocksoftAxios'; +import { AirDAOBlockchainTypes } from '../../AirDAOBlockchainTypes'; +import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; +import BlocksoftUtils from '../../../common/BlocksoftUtils'; +import BlocksoftExternalSettings from '../../../common/BlocksoftExternalSettings'; + +const elliptic = require('elliptic'); +const ec = new elliptic.ec('secp256k1'); +const createHash = require('create-hash'); +const bech = require('bech32'); +const UVarInt = require('../utils/UVarInt').UVarInt; +const Encode = require('../utils/Encode'); +const _tinySecp256k = require('tiny-secp256k1'); + +function decodeAddress(value: any) { + try { + const decodeAddress = bech.decode(value); + return Buffer.from(bech.fromWords(decodeAddress.words)); + } catch (err: unknown) { + const e = err as any; + throw new Error(e.message + ' while decodeAddress ' + value); + } +} + +function convertObjectToSignBytes(obj: any) { + try { + return Buffer.from(JSON.stringify(Encode.sortObject(obj))); + } catch (e) { + throw new Error(e.message + ' in convertObjectToSignBytes'); + } +} + +function encodeBinaryByteArray(bytes: any) { + try { + const lenPrefix = bytes.length; + return Buffer.concat([UVarInt.encode(lenPrefix), bytes]); + } catch (e) { + throw new Error(e.message + ' in encodeBinaryByteArray'); + } +} + +function serializePubKey(unencodedPubKey: any) { + try { + let format = 0x2; + const y = unencodedPubKey.getY(); + const x = unencodedPubKey.getX(); + + if (y && y.isOdd()) { + format |= 0x1; + } + + let pubBz = Buffer.concat([ + UVarInt.encode(format), + x.toArrayLike(Buffer, 'be', 32) + ]); // prefixed with length + + pubBz = encodeBinaryByteArray(pubBz); // add the amino prefix + + pubBz = Buffer.concat([Buffer.from('EB5AE987', 'hex'), pubBz]); + return pubBz; + } catch (e) { + throw new Error(e.message + ' in serializePubKey'); + } +} + +function marshalBinary(obj: any) { + try { + return Encode.encodeBinary(obj, -1, true).toString('hex'); + } catch (e) { + throw new Error(e.message + ' in marshalBinary'); + } +} + +export class BnbTxSendProvider { + async getPrepared( + data: AirDAOBlockchainTypes.TransferData, + privateData: AirDAOBlockchainTypes.TransferPrivateData, + uiData: AirDAOBlockchainTypes.TransferUiData, + type = 'usual' + ) { + const apiServer = await BlocksoftExternalSettings.getStatic('BNB_SERVER'); + const res = await BlocksoftAxios.getWithoutBraking( + apiServer + '/api/v1/account/' + data.addressFrom + ); + if (!res.data) { + throw new Error('no data'); + } + const account = res.data; + + const unified = BlocksoftUtils.fromUnified(data.amount, 8) * 1; + let msg; + let txMsg; + + let decodedFrom; + let decodedTo; + try { + decodedFrom = decodeAddress(data.addressFrom); + } catch (e) { + throw new Error( + e.message + ' in BNB decodeAddress ' + JSON.stringify(data.addressFrom) + ); + } + try { + if (data.addressTo.indexOf('0x') === -1) { + decodedTo = decodeAddress(data.addressTo); + } else { + decodedTo = data.addressTo; + } + } catch (e) { + throw new Error( + e.message + ' in BNB decodeAddress ' + JSON.stringify(data.addressTo) + ); + } + + if ( + typeof data.blockchainData !== 'undefined' && + typeof data.blockchainData.action !== 'undefined' && + data.blockchainData.action === 'BnbToSmart' + ) { + let addressTo = data.addressTo; + if (addressTo.substr(0, 2) === '0x') { + addressTo = addressTo.substr(2); + } + msg = { + from: data.addressFrom, + to: data.addressTo, + amount: { denom: 'BNB', amount: unified }, + expire_time: data.blockchainData.expire_time + }; + txMsg = { + from: decodedFrom, + to: Buffer.from(addressTo, 'hex'), + amount: { denom: 'BNB', amount: unified }, + expire_time: data.blockchainData.expire_time, + aminoPrefix: '800819C0' + }; + } else { + msg = { + inputs: [ + { + address: data.addressFrom, + coins: [ + { + amount: unified, + denom: 'BNB' + } + ] + } + ], + outputs: [ + { + address: data.addressTo, + coins: [ + { + amount: unified, + denom: 'BNB' + } + ] + } + ] + }; + txMsg = { + inputs: [ + { + address: decodedFrom, + coins: [ + { + denom: 'BNB', + amount: unified + } + ] + } + ], + outputs: [ + { + address: decodedTo, + coins: [ + { + denom: 'BNB', + amount: unified + } + ] + } + ], + aminoPrefix: '2A2C87FA' + }; + } + const memo = + typeof data.memo === 'undefined' || !data.memo ? '' : data.memo; + const signMsg = { + account_number: account.account_number + '', + chain_id: 'Binance-Chain-Tigris', + data: null, + memo, + msgs: [msg], + sequence: account.sequence + '', + source: '0' + }; + let buff; + try { + buff = convertObjectToSignBytes(signMsg); + } catch (e) { + throw new Error( + e.message + + ' in BNB convertObjectToSignBytes ' + + JSON.stringify(signMsg) + ); + } + const signBytesHex = buff.toString('hex'); + let msgHash; + try { + msgHash = createHash('sha256').update(signBytesHex, 'hex').digest(); + } catch (e) { + throw new Error(e.message + ' in BNB createHash'); + } + let keypair; + try { + keypair = ec.keyFromPrivate(privateData.privateKey, 'hex'); + } catch (e) { + throw new Error(e.message + ' in BNB ec.keyFromPrivate'); + } + let signature; + try { + signature = _tinySecp256k.sign( + msgHash, + Buffer.from(privateData.privateKey, 'hex') + ); + } catch (e) { + throw new Error(e.message + ' in BNB _tinySecp256k.sign'); + } + const signatureHex = signature.toString('hex'); + let pubKey; + try { + pubKey = keypair.getPublic(); + } catch (e) { + throw new Error(e.message + ' in BNB keypair.getPublic()'); + } + let pubSerialize; + try { + pubSerialize = serializePubKey(pubKey); + } catch (err: unknown) { + const e = err as any; + throw new Error(e.message + ' in BNB serializePubKey'); + } + + const signatures = [ + { + pub_key: pubSerialize, + signature: Buffer.from(signatureHex, 'hex'), + account_number: account.account_number, + sequence: account.sequence + } + ]; + const transaction = { + sequence: account.sequence + '', + accountNumber: account.account_number + '', + chainId: 'Binance-Chain-Tigris', + msg: txMsg, + baseMsg: undefined, + memo: memo, + source: 0, + signatures + }; + return transaction; + } + + serializeTx(tx: any) { + if (!tx.signatures) { + throw new Error('need signature'); + } + + const msg = tx.msg || (tx.baseMsg && tx.baseMsg.getMsg()); + const stdTx = { + msg: [msg], + signatures: tx.signatures, + memo: tx.memo, + source: tx.source, + // sdk value is 0, web wallet value is 1 + data: '', + aminoPrefix: 'F0625DEE' + }; + const bytes = marshalBinary(stdTx); + return bytes.toString('hex'); + } + + async sendRaw(raw: string) { + let result = false; + try { + // console.log(`curl -X POST -F "tx=${raw}" "https://dex.binance.org/api/v1/broadcast"`) + const apiServer = await BlocksoftExternalSettings.getStatic('BNB_SERVER'); + const response = await fetch(apiServer + '/api/v1/broadcast?sync=true', { + method: 'POST', + credentials: 'same-origin', + mode: 'same-origin', + headers: { + 'Content-Type': 'text/plain' + }, + body: raw + }); + result = await response.json(); + + if (typeof result.status !== 'undefined') { + if ( + result.status === 406 || + result.status === 400 || + result.status === 504 + ) { + throw new Error(result.title); + } + } + } catch (e) { + await BlocksoftCryptoLog.log( + 'BnbTransferProcessor.sendTx error ' + e.message + ); + throw e; + } + await BlocksoftCryptoLog.log('BnbTransferProcessor.sendTx result ', result); + return result; + } +} diff --git a/crypto/blockchains/bnb/utils/Encode.js b/crypto/blockchains/bnb/utils/Encode.js index 5b1346e3f..62f520256 100644 --- a/crypto/blockchains/bnb/utils/Encode.js +++ b/crypto/blockchains/bnb/utils/Encode.js @@ -1,262 +1,261 @@ -const protocolBuffersEncodings = require("protocol-buffers-encodings") - -const IsJs = require('./IsJs.js') -const UVarInt = require('./UVarInt').UVarInt -const EncoderHelper = require('./EncoderHelper') - - -const sortObject = function sortObject(obj) { - if (obj === null) return null - if (Array.isArray(obj)) return obj.map(sortObject) - if (typeof (obj) !== 'object') return obj - - var sortedKeys = Object.keys(obj).sort() - var result = {} - sortedKeys.forEach(function(key) { - // @ts-ignore - result[key] = sortObject(obj[key]) - }) - return result -} -exports.sortObject = sortObject - -/** - * encode number - * @category amino - * @param num - */ - -var encodeNumber = function encodeNumber(num) { - return UVarInt.encode(num) -} -/** - * encode bool - * @category amino - * @param b - */ - -exports.encodeNumber = encodeNumber - -var encodeBool = function encodeBool(b) { - return b ? UVarInt.encode(1) : UVarInt.encode(0) -} -/** - * encode string - * @category amino - * @param str - */ - - -exports.encodeBool = encodeBool - -var encodeString = function encodeString(str) { - try { - var buf = Buffer.alloc(protocolBuffersEncodings.string.encodingLength(str)) - return protocolBuffersEncodings.string.encode(str, buf, 0) - } catch (e) { - throw new Error(e.message + ' in protocolBuffersEncodings.string.encode') - } -} -/** - * encode time - * @category amino - * @param value - */ - - -exports.encodeString = encodeString - -var encodeTime = function encodeTime(value) { - var millis = new Date(value).getTime() - var seconds = Math.floor(millis / 1000) - var nanos = Number(seconds.toString().padEnd(9, '0')) - var buffer = Buffer.alloc(14) // buffer[0] = (1 << 3) | 1 // field 1, typ3 1 - - buffer.writeInt32LE(1 << 3 | 1, 0) - buffer.writeUInt32LE(seconds, 1) // buffer[9] = (2 << 3) | 5 // field 2, typ3 5 - - buffer.writeInt32LE(2 << 3 | 5, 9) - buffer.writeUInt32LE(nanos, 10) - return buffer -} -/** - * @category amino - * @param obj -- {object} - * @return bytes {Buffer} - */ - - -exports.encodeTime = encodeTime - -var convertObjectToSignBytes = function convertObjectToSignBytes(obj) { - return Buffer.from(JSON.stringify(sortObject(obj))) -} -/** - * js amino MarshalBinary - * @category amino - * @param {Object} obj - * */ - - -exports.convertObjectToSignBytes = convertObjectToSignBytes - -var marshalBinary = function marshalBinary(obj) { - if (!IsJs.object(obj)) throw new TypeError('data must be an object') - return encodeBinary(obj, -1, true).toString('hex') -} -/** - * js amino MarshalBinaryBare - * @category amino - * @param {Object} obj - * */ - - -exports.marshalBinary = marshalBinary - -var marshalBinaryBare = function marshalBinaryBare(obj) { - if (!IsJs.object(obj)) throw new TypeError('data must be an object') - return encodeBinary(obj).toString('hex') -} -/** - * This is the main entrypoint for encoding all types in binary form. - * @category amino - * @param {*} js data type (not null, not undefined) - * @param {Number} field index of object - * @param {Boolean} isByteLenPrefix - * @return {Buffer} binary of object. - */ - - -exports.marshalBinaryBare = marshalBinaryBare - -var encodeBinary = function encodeBinary(val, fieldNum, isByteLenPrefix) { - if (val === null || val === undefined) throw new TypeError('unsupported type') - - if (Buffer.isBuffer(val)) { - if (isByteLenPrefix) { - return Buffer.concat([UVarInt.encode(val.length), val]) - } - - return val - } - - if (IsJs.array(val)) { - return encodeArrayBinary(fieldNum, val, isByteLenPrefix) - } - - if (IsJs.number(val)) { - return encodeNumber(val) - } - - if (IsJs['boolean'](val)) { - return encodeBool(val) - } - - if (IsJs.string(val)) { - return encodeString(val) - } - - if (IsJs.object(val)) { - return encodeObjectBinary(val, isByteLenPrefix) - } - - return -} -/** - * prefixed with bytes length - * @category amino - * @param {Buffer} bytes - * @return {Buffer} with bytes length prefixed - */ - - -exports.encodeBinary = encodeBinary - -var encodeBinaryByteArray = function encodeBinaryByteArray(bytes) { - var lenPrefix = bytes.length - return Buffer.concat([UVarInt.encode(lenPrefix), bytes]) -} -/** - * @category amino - * @param {Object} obj - * @return {Buffer} with bytes length prefixed - */ - - -exports.encodeBinaryByteArray = encodeBinaryByteArray - -var encodeObjectBinary = function encodeObjectBinary(obj, isByteLenPrefix) { - var bufferArr = [] - Object.keys(obj).forEach(function(key, index) { - if (key === 'aminoPrefix' || key === 'version') return - if (isDefaultValue(obj[key])) return - - if (IsJs.array(obj[key]) && obj[key].length > 0) { - bufferArr.push(encodeArrayBinary(index, obj[key])) - } else { - bufferArr.push(encodeTypeAndField(index, obj[key])) - bufferArr.push(encodeBinary(obj[key], index, true)) - } - }) - var bytes = Buffer.concat(bufferArr) // add prefix - - if (obj.aminoPrefix) { - var prefix = Buffer.from(obj.aminoPrefix, 'hex') - bytes = Buffer.concat([prefix, bytes]) - } // Write byte-length prefixed. - - - if (isByteLenPrefix) { - var lenBytes = UVarInt.encode(bytes.length) - - bytes = Buffer.concat([lenBytes, bytes]) - } - return bytes -} -/** - * @category amino - * @param {Number} fieldNum object field index - * @param {Array} arr - * @param {Boolean} isByteLenPrefix - * @return {Buffer} bytes of array - */ - - -exports.encodeObjectBinary = encodeObjectBinary - -var encodeArrayBinary = function encodeArrayBinary(fieldNum, arr, isByteLenPrefix) { - var result = [] - arr.forEach(function(item) { - result.push(encodeTypeAndField(fieldNum, item)) - - if (isDefaultValue(item)) { - result.push(Buffer.from('00', 'hex')) - return - } - - result.push(encodeBinary(item, fieldNum, true)) - }) //encode length - - if (isByteLenPrefix) { - var length = result.reduce(function(prev, item) { - return prev + item.length - }, 0) - result.unshift(UVarInt.encode(length)) - } - - return Buffer.concat(result) -} // Write field key. - - -exports.encodeArrayBinary = encodeArrayBinary - -var encodeTypeAndField = function encodeTypeAndField(index, field) { - index = Number(index) - var value = index + 1 << 3 | EncoderHelper._default(field) - return UVarInt.encode(value) -} - -var isDefaultValue = function isDefaultValue(obj) { - if (obj === null) return false; - return IsJs.number(obj) && obj === 0 || IsJs.string(obj) && obj === "" || IsJs.array(obj) && obj.length === 0 || IsJs["boolean"](obj) && !obj; -}; +/* eslint-disable @typescript-eslint/no-var-requires */ +const protocolBuffersEncodings = require('protocol-buffers-encodings'); + +const IsJs = require('./IsJs.js'); +const UVarInt = require('./UVarInt.js').UVarInt; +const EncoderHelper = require('./EncoderHelper.js'); + +const sortObject = function sortObject(obj) { + if (obj === null) return null; + if (Array.isArray(obj)) return obj.map(sortObject); + if (typeof obj !== 'object') return obj; + + var sortedKeys = Object.keys(obj).sort(); + var result = {}; + sortedKeys.forEach((key) => { + // @ts-ignore + result[key] = sortObject(obj[key]); + }); + return result; +}; +exports.sortObject = sortObject; + +/** + * encode number + * @category amino + * @param num + */ + +var encodeNumber = function encodeNumber(num) { + return UVarInt.encode(num); +}; +/** + * encode bool + * @category amino + * @param b + */ + +exports.encodeNumber = encodeNumber; + +var encodeBool = function encodeBool(b) { + return b ? UVarInt.encode(1) : UVarInt.encode(0); +}; +/** + * encode string + * @category amino + * @param str + */ + +exports.encodeBool = encodeBool; + +var encodeString = function encodeString(str) { + try { + var buf = Buffer.alloc(protocolBuffersEncodings.string.encodingLength(str)); + return protocolBuffersEncodings.string.encode(str, buf, 0); + } catch (e) { + throw new Error(e.message + ' in protocolBuffersEncodings.string.encode'); + } +}; +/** + * encode time + * @category amino + * @param value + */ + +exports.encodeString = encodeString; + +var encodeTime = function encodeTime(value) { + var millis = new Date(value).getTime(); + var seconds = Math.floor(millis / 1000); + var nanos = Number(seconds.toString().padEnd(9, '0')); + var buffer = Buffer.alloc(14); // buffer[0] = (1 << 3) | 1 // field 1, typ3 1 + + buffer.writeInt32LE((1 << 3) | 1, 0); + buffer.writeUInt32LE(seconds, 1); // buffer[9] = (2 << 3) | 5 // field 2, typ3 5 + + buffer.writeInt32LE((2 << 3) | 5, 9); + buffer.writeUInt32LE(nanos, 10); + return buffer; +}; +/** + * @category amino + * @param obj -- {object} + * @return bytes {Buffer} + */ + +exports.encodeTime = encodeTime; + +var convertObjectToSignBytes = function convertObjectToSignBytes(obj) { + return Buffer.from(JSON.stringify(sortObject(obj))); +}; +/** + * js amino MarshalBinary + * @category amino + * @param {Object} obj + * */ + +exports.convertObjectToSignBytes = convertObjectToSignBytes; + +var marshalBinary = function marshalBinary(obj) { + if (!IsJs.object(obj)) throw new TypeError('data must be an object'); + return encodeBinary(obj, -1, true).toString('hex'); +}; +/** + * js amino MarshalBinaryBare + * @category amino + * @param {Object} obj + * */ + +exports.marshalBinary = marshalBinary; + +var marshalBinaryBare = function marshalBinaryBare(obj) { + if (!IsJs.object(obj)) throw new TypeError('data must be an object'); + return encodeBinary(obj).toString('hex'); +}; +/** + * This is the main entrypoint for encoding all types in binary form. + * @category amino + * @param {*} js data type (not null, not undefined) + * @param {Number} field index of object + * @param {Boolean} isByteLenPrefix + * @return {Buffer} binary of object. + */ + +exports.marshalBinaryBare = marshalBinaryBare; + +var encodeBinary = function encodeBinary(val, fieldNum, isByteLenPrefix) { + if (val === null || val === undefined) + throw new TypeError('unsupported type'); + + if (Buffer.isBuffer(val)) { + if (isByteLenPrefix) { + return Buffer.concat([UVarInt.encode(val.length), val]); + } + + return val; + } + + if (IsJs.array(val)) { + return encodeArrayBinary(fieldNum, val, isByteLenPrefix); + } + + if (IsJs.number(val)) { + return encodeNumber(val); + } + + if (IsJs['boolean'](val)) { + return encodeBool(val); + } + + if (IsJs.string(val)) { + return encodeString(val); + } + + if (IsJs.object(val)) { + return encodeObjectBinary(val, isByteLenPrefix); + } + + return; +}; +/** + * prefixed with bytes length + * @category amino + * @param {Buffer} bytes + * @return {Buffer} with bytes length prefixed + */ + +exports.encodeBinary = encodeBinary; + +var encodeBinaryByteArray = function encodeBinaryByteArray(bytes) { + var lenPrefix = bytes.length; + return Buffer.concat([UVarInt.encode(lenPrefix), bytes]); +}; +/** + * @category amino + * @param {Object} obj + * @return {Buffer} with bytes length prefixed + */ + +exports.encodeBinaryByteArray = encodeBinaryByteArray; + +var encodeObjectBinary = function encodeObjectBinary(obj, isByteLenPrefix) { + var bufferArr = []; + Object.keys(obj).forEach((key, index) => { + if (key === 'aminoPrefix' || key === 'version') return; + if (isDefaultValue(obj[key])) return; + + if (IsJs.array(obj[key]) && obj[key].length > 0) { + bufferArr.push(encodeArrayBinary(index, obj[key])); + } else { + bufferArr.push(encodeTypeAndField(index, obj[key])); + bufferArr.push(encodeBinary(obj[key], index, true)); + } + }); + var bytes = Buffer.concat(bufferArr); // add prefix + + if (obj.aminoPrefix) { + var prefix = Buffer.from(obj.aminoPrefix, 'hex'); + bytes = Buffer.concat([prefix, bytes]); + } // Write byte-length prefixed. + + if (isByteLenPrefix) { + var lenBytes = UVarInt.encode(bytes.length); + + bytes = Buffer.concat([lenBytes, bytes]); + } + return bytes; +}; +/** + * @category amino + * @param {Number} fieldNum object field index + * @param {Array} arr + * @param {Boolean} isByteLenPrefix + * @return {Buffer} bytes of array + */ + +exports.encodeObjectBinary = encodeObjectBinary; + +var encodeArrayBinary = function encodeArrayBinary( + fieldNum, + arr, + isByteLenPrefix +) { + var result = []; + arr.forEach((item) => { + result.push(encodeTypeAndField(fieldNum, item)); + + if (isDefaultValue(item)) { + result.push(Buffer.from('00', 'hex')); + return; + } + + result.push(encodeBinary(item, fieldNum, true)); + }); //encode length + + if (isByteLenPrefix) { + var length = result.reduce((prev, item) => { + return prev + item.length; + }, 0); + result.unshift(UVarInt.encode(length)); + } + + return Buffer.concat(result); +}; // Write field key. + +exports.encodeArrayBinary = encodeArrayBinary; + +var encodeTypeAndField = function encodeTypeAndField(index, field) { + index = Number(index); + var value = ((index + 1) << 3) | EncoderHelper._default(field); + return UVarInt.encode(value); +}; + +var isDefaultValue = function isDefaultValue(obj) { + if (obj === null) return false; + return ( + (IsJs.number(obj) && obj === 0) || + (IsJs.string(obj) && obj === '') || + (IsJs.array(obj) && obj.length === 0) || + (IsJs['boolean'](obj) && !obj) + ); +}; diff --git a/crypto/blockchains/bnb/utils/EncoderHelper.js b/crypto/blockchains/bnb/utils/EncoderHelper.js deleted file mode 100644 index 7cbf25884..000000000 --- a/crypto/blockchains/bnb/utils/EncoderHelper.js +++ /dev/null @@ -1,44 +0,0 @@ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.size = exports["default"] = void 0; - -const IsJs = require('./IsJs.js') - -// typeToTyp3 -//amino type convert -var _default = function _default(type) { - if (IsJs["boolean"](type)) { - return 0; - } - - if (IsJs.number(type)) { - if (IsJs.integer(type)) { - return 0; - } else { - return 1; - } - } - - if (IsJs.string(type) || IsJs.array(type) || IsJs.object(type)) { - return 2; - } - - throw new Error("Invalid type \"".concat(type, "\"")); // Is this what's expected? -}; - -exports._default = _default - -var size = function size(items, iter, acc) { - if (acc === undefined) acc = 0; - - for (var i = 0; i < items.length; ++i) { - acc += iter(items[i], i, acc); - } - - return acc; -}; - -exports.size = size; diff --git a/crypto/blockchains/bnb/utils/EncoderHelper.ts b/crypto/blockchains/bnb/utils/EncoderHelper.ts new file mode 100644 index 000000000..f0da9adbe --- /dev/null +++ b/crypto/blockchains/bnb/utils/EncoderHelper.ts @@ -0,0 +1,46 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ +'use strict'; + +Object.defineProperty(exports, '__esModule', { + value: true +}); +exports.size = exports['default'] = void 0; + +const IsJs = require('./IsJs.js'); + +// typeToTyp3 +//amino type convert +const _default = function _default(type) { + if (IsJs['boolean'](type)) { + return 0; + } + + if (IsJs.number(type)) { + if (IsJs.integer(type)) { + return 0; + } else { + return 1; + } + } + + if (IsJs.string(type) || IsJs.array(type) || IsJs.object(type)) { + return 2; + } + + throw new Error('Invalid type "'.concat(type, '"')); // Is this what's expected? +}; + +exports._default = _default; + +// @ts-ignore TODO +const size = function size(items, iter, acc) { + if (acc === undefined) acc = 0; + + for (let i = 0; i < items.length; ++i) { + acc += iter(items[i], i, acc); + } + + return acc; +}; + +exports.size = size; diff --git a/crypto/blockchains/bnb/utils/IsJs.js b/crypto/blockchains/bnb/utils/IsJs.js index bf131de84..048a6b19a 100644 --- a/crypto/blockchains/bnb/utils/IsJs.js +++ b/crypto/blockchains/bnb/utils/IsJs.js @@ -1,906 +1,1016 @@ -/*! - * is.js 0.8.0 - * Author: Aras Atasaygin - */ - -// AMD with global, Node, or global -;(function(root, factory) { // eslint-disable-line no-extra-semi - if (typeof define === 'function' && define.amd) { - // AMD. Register as an anonymous module. - define(function() { - // Also create a global in case some scripts - // that are loaded still are looking for - // a global even when an AMD loader is in use. - return (root.is = factory()); - }); - } else if (typeof exports === 'object') { - // Node. Does not work with strict CommonJS, but - // only CommonJS-like enviroments that support module.exports, - // like Node. - module.exports = factory(); - } else { - // Browser globals (root is self) - root.is = factory(); - } -}(this, function() { - - // Baseline - /* -------------------------------------------------------------------------- */ - - // define 'is' object and current version - var is = {}; - is.VERSION = '0.8.0'; - - // define interfaces - is.not = {}; - is.all = {}; - is.any = {}; - - // cache some methods to call later on - var toString = Object.prototype.toString; - var slice = Array.prototype.slice; - var hasOwnProperty = Object.prototype.hasOwnProperty; - - // helper function which reverses the sense of predicate result - function not(func) { - return function() { - return !func.apply(null, slice.call(arguments)); - }; - } - - // helper function which call predicate function per parameter and return true if all pass - function all(func) { - return function() { - var params = getParams(arguments); - var length = params.length; - for (var i = 0; i < length; i++) { - if (!func.call(null, params[i])) { - return false; - } - } - return true; - }; - } - - // helper function which call predicate function per parameter and return true if any pass - function any(func) { - return function() { - var params = getParams(arguments); - var length = params.length; - for (var i = 0; i < length; i++) { - if (func.call(null, params[i])) { - return true; - } - } - return false; - }; - } - - // build a 'comparator' object for various comparison checks - var comparator = { - '<': function(a, b) { return a < b; }, - '<=': function(a, b) { return a <= b; }, - '>': function(a, b) { return a > b; }, - '>=': function(a, b) { return a >= b; } - } - - // helper function which compares a version to a range - function compareVersion(version, range) { - var string = (range + ''); - var n = +(string.match(/\d+/) || NaN); - var op = string.match(/^[<>]=?|/)[0]; - return comparator[op] ? comparator[op](version, n) : (version == n || n !== n); - } - - // helper function which extracts params from arguments - function getParams(args) { - var params = slice.call(args); - var length = params.length; - if (length === 1 && is.array(params[0])) { // support array - params = params[0]; - } - return params; - } - - // Type checks - /* -------------------------------------------------------------------------- */ - - // is a given value Arguments? - is.arguments = function(value) { // fallback check is for IE - return toString.call(value) === '[object Arguments]' || - (value != null && typeof value === 'object' && 'callee' in value); - }; - - // is a given value Array? - is.array = Array.isArray || function(value) { // check native isArray first - return toString.call(value) === '[object Array]'; - }; - - // is a given value Boolean? - is.boolean = function(value) { - return value === true || value === false || toString.call(value) === '[object Boolean]'; - }; - - // is a given value Char? - is.char = function(value) { - return is.string(value) && value.length === 1; - }; - - // is a given value Date Object? - is.date = function(value) { - return toString.call(value) === '[object Date]'; - }; - - // is a given object a DOM node? - is.domNode = function(object) { - return is.object(object) && object.nodeType > 0; - }; - - // is a given value Error object? - is.error = function(value) { - return toString.call(value) === '[object Error]'; - }; - - // is a given value function? - is['function'] = function(value) { // fallback check is for IE - return toString.call(value) === '[object Function]' || typeof value === 'function'; - }; - - // is given value a pure JSON object? - is.json = function(value) { - return toString.call(value) === '[object Object]'; - }; - - // is a given value NaN? - is.nan = function(value) { // NaN is number :) Also it is the only value which does not equal itself - return value !== value; - }; - - // is a given value null? - is['null'] = function(value) { - return value === null; - }; - - // is a given value number? - is.number = function(value) { - return is.not.nan(value) && toString.call(value) === '[object Number]'; - }; - - // is a given value object? - is.object = function(value) { - return Object(value) === value; - }; - - // is a given value RegExp? - is.regexp = function(value) { - return toString.call(value) === '[object RegExp]'; - }; - - // are given values same type? - // prevent NaN, Number same type check - is.sameType = function(value, other) { - var tag = toString.call(value); - if (tag !== toString.call(other)) { - return false; - } - if (tag === '[object Number]') { - return !is.any.nan(value, other) || is.all.nan(value, other); - } - return true; - }; - // sameType method does not support 'all' and 'any' interfaces - is.sameType.api = ['not']; - - // is a given value String? - is.string = function(value) { - return toString.call(value) === '[object String]'; - }; - - // is a given value undefined? - is.undefined = function(value) { - return value === void 0; - }; - - // is a given value window? - // setInterval method is only available for window object - is.windowObject = function(value) { - return value != null && typeof value === 'object' && 'setInterval' in value; - }; - - // Presence checks - /* -------------------------------------------------------------------------- */ - - //is a given value empty? Objects, arrays, strings - is.empty = function(value) { - if (is.object(value)) { - var length = Object.getOwnPropertyNames(value).length; - if (length === 0 || (length === 1 && is.array(value)) || - (length === 2 && is.arguments(value))) { - return true; - } - return false; - } - return value === ''; - }; - - // is a given value existy? - is.existy = function(value) { - return value != null; - }; - - // is a given value falsy? - is.falsy = function(value) { - return !value; - }; - - // is a given value truthy? - is.truthy = not(is.falsy); - - // Arithmetic checks - /* -------------------------------------------------------------------------- */ - - // is a given number above minimum parameter? - is.above = function(n, min) { - return is.all.number(n, min) && n > min; - }; - // above method does not support 'all' and 'any' interfaces - is.above.api = ['not']; - - // is a given number decimal? - is.decimal = function(n) { - return is.number(n) && n % 1 !== 0; - }; - - // are given values equal? supports numbers, strings, regexes, booleans - // TODO: Add object and array support - is.equal = function(value, other) { - // check 0 and -0 equity with Infinity and -Infinity - if (is.all.number(value, other)) { - return value === other && 1 / value === 1 / other; - } - // check regexes as strings too - if (is.all.string(value, other) || is.all.regexp(value, other)) { - return '' + value === '' + other; - } - if (is.all.boolean(value, other)) { - return value === other; - } - return false; - }; - // equal method does not support 'all' and 'any' interfaces - is.equal.api = ['not']; - - // is a given number even? - is.even = function(n) { - return is.number(n) && n % 2 === 0; - }; - - // is a given number finite? - is.finite = isFinite || function(n) { - return is.not.infinite(n) && is.not.nan(n); - }; - - // is a given number infinite? - is.infinite = function(n) { - return n === Infinity || n === -Infinity; - }; - - // is a given number integer? - is.integer = function(n) { - return is.number(n) && n % 1 === 0; - }; - - // is a given number negative? - is.negative = function(n) { - return is.number(n) && n < 0; - }; - - // is a given number odd? - is.odd = function(n) { - return is.number(n) && n % 2 === 1; - }; - - // is a given number positive? - is.positive = function(n) { - return is.number(n) && n > 0; - }; - - // is a given number above maximum parameter? - is.under = function(n, max) { - return is.all.number(n, max) && n < max; - }; - // least method does not support 'all' and 'any' interfaces - is.under.api = ['not']; - - // is a given number within minimum and maximum parameters? - is.within = function(n, min, max) { - return is.all.number(n, min, max) && n > min && n < max; - }; - // within method does not support 'all' and 'any' interfaces - is.within.api = ['not']; - - // Regexp checks - /* -------------------------------------------------------------------------- */ - // Steven Levithan, Jan Goyvaerts: Regular Expressions Cookbook - // Scott Gonzalez: Email address validation - - // dateString match m/d/yy and mm/dd/yyyy, allowing any combination of one or two digits for the day and month, and two or four digits for the year - // eppPhone match extensible provisioning protocol format - // nanpPhone match north american number plan format - // time match hours, minutes, and seconds, 24-hour clock - var regexes = { - affirmative: /^(?:1|t(?:rue)?|y(?:es)?|ok(?:ay)?)$/, - alphaNumeric: /^[A-Za-z0-9]+$/, - caPostalCode: /^(?!.*[DFIOQU])[A-VXY][0-9][A-Z]\s?[0-9][A-Z][0-9]$/, - creditCard: /^(?:(4[0-9]{12}(?:[0-9]{3})?)|(5[1-5][0-9]{14})|(6(?:011|5[0-9]{2})[0-9]{12})|(3[47][0-9]{13})|(3(?:0[0-5]|[68][0-9])[0-9]{11})|((?:2131|1800|35[0-9]{3})[0-9]{11}))$/, - dateString: /^(1[0-2]|0?[1-9])([\/-])(3[01]|[12][0-9]|0?[1-9])(?:\2)(?:[0-9]{2})?[0-9]{2}$/, - email: /^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$/i, // eslint-disable-line no-control-regex - eppPhone: /^\+[0-9]{1,3}\.[0-9]{4,14}(?:x.+)?$/, - hexadecimal: /^(?:0x)?[0-9a-fA-F]+$/, - hexColor: /^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/, - ipv4: /^(?:(?:\d|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5])\.){3}(?:\d|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5])$/, - ipv6: /^((?=.*::)(?!.*::.+::)(::)?([\dA-F]{1,4}:(:|\b)|){5}|([\dA-F]{1,4}:){6})((([\dA-F]{1,4}((?!\3)::|:\b|$))|(?!\2\3)){2}|(((2[0-4]|1\d|[1-9])?\d|25[0-5])\.?\b){4})$/i, - nanpPhone: /^\(?([0-9]{3})\)?[-. ]?([0-9]{3})[-. ]?([0-9]{4})$/, - socialSecurityNumber: /^(?!000|666)[0-8][0-9]{2}-?(?!00)[0-9]{2}-?(?!0000)[0-9]{4}$/, - timeString: /^(2[0-3]|[01]?[0-9]):([0-5]?[0-9]):([0-5]?[0-9])$/, - ukPostCode: /^[A-Z]{1,2}[0-9RCHNQ][0-9A-Z]?\s?[0-9][ABD-HJLNP-UW-Z]{2}$|^[A-Z]{2}-?[0-9]{4}$/, - url: /^(?:(?:https?|ftp):\/\/)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})))(?::\d{2,5})?(?:\/\S*)?$/i, - usZipCode: /^[0-9]{5}(?:-[0-9]{4})?$/ - }; - - function regexpCheck(regexp, regexes) { - is[regexp] = function(value) { - return regexes[regexp].test(value); - }; - } - - // create regexp checks methods from 'regexes' object - for (var regexp in regexes) { - if (regexes.hasOwnProperty(regexp)) { - regexpCheck(regexp, regexes); - } - } - - // simplify IP checks by calling the regex helpers for IPv4 and IPv6 - is.ip = function(value) { - return is.ipv4(value) || is.ipv6(value); - }; - - // String checks - /* -------------------------------------------------------------------------- */ - - // is a given string or sentence capitalized? - is.capitalized = function(string) { - if (is.not.string(string)) { - return false; - } - var words = string.split(' '); - for (var i = 0; i < words.length; i++) { - var word = words[i]; - if (word.length) { - var chr = word.charAt(0); - if (chr !== chr.toUpperCase()) { - return false; - } - } - } - return true; - }; - - // is string end with a given target parameter? - is.endWith = function(string, target) { - if (is.not.string(string)) { - return false; - } - target += ''; - var position = string.length - target.length; - return position >= 0 && string.indexOf(target, position) === position; - }; - // endWith method does not support 'all' and 'any' interfaces - is.endWith.api = ['not']; - - // is a given string include parameter target? - is.include = function(string, target) { - return string.indexOf(target) > -1; - }; - // include method does not support 'all' and 'any' interfaces - is.include.api = ['not']; - - // is a given string all lowercase? - is.lowerCase = function(string) { - return is.string(string) && string === string.toLowerCase(); - }; - - // is a given string palindrome? - is.palindrome = function(string) { - if (is.not.string(string)) { - return false; - } - string = string.replace(/[^a-zA-Z0-9]+/g, '').toLowerCase(); - var length = string.length - 1; - for (var i = 0, half = Math.floor(length / 2); i <= half; i++) { - if (string.charAt(i) !== string.charAt(length - i)) { - return false; - } - } - return true; - }; - - // is a given value space? - // horizantal tab: 9, line feed: 10, vertical tab: 11, form feed: 12, carriage return: 13, space: 32 - is.space = function(value) { - if (is.not.char(value)) { - return false; - } - var charCode = value.charCodeAt(0); - return (charCode > 8 && charCode < 14) || charCode === 32; - }; - - // is string start with a given target parameter? - is.startWith = function(string, target) { - return is.string(string) && string.indexOf(target) === 0; - }; - // startWith method does not support 'all' and 'any' interfaces - is.startWith.api = ['not']; - - // is a given string all uppercase? - is.upperCase = function(string) { - return is.string(string) && string === string.toUpperCase(); - }; - - // Time checks - /* -------------------------------------------------------------------------- */ - - var days = ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday']; - var months = ['january', 'february', 'march', 'april', 'may', 'june', 'july', 'august', 'september', 'october', 'november', 'december']; - - // is a given dates day equal given day parameter? - is.day = function(date, day) { - return is.date(date) && day.toLowerCase() === days[date.getDay()]; - }; - // day method does not support 'all' and 'any' interfaces - is.day.api = ['not']; - - // is a given date in daylight saving time? - is.dayLightSavingTime = function(date) { - var january = new Date(date.getFullYear(), 0, 1); - var july = new Date(date.getFullYear(), 6, 1); - var stdTimezoneOffset = Math.max(january.getTimezoneOffset(), july.getTimezoneOffset()); - return date.getTimezoneOffset() < stdTimezoneOffset; - }; - - // is a given date future? - is.future = function(date) { - var now = new Date(); - return is.date(date) && date.getTime() > now.getTime(); - }; - - // is date within given range? - is.inDateRange = function(date, start, end) { - if (is.not.date(date) || is.not.date(start) || is.not.date(end)) { - return false; - } - var stamp = date.getTime(); - return stamp > start.getTime() && stamp < end.getTime(); - }; - // inDateRange method does not support 'all' and 'any' interfaces - is.inDateRange.api = ['not']; - - // is a given date in last month range? - is.inLastMonth = function(date) { - return is.inDateRange(date, new Date(new Date().setMonth(new Date().getMonth() - 1)), new Date()); - }; - - // is a given date in last week range? - is.inLastWeek = function(date) { - return is.inDateRange(date, new Date(new Date().setDate(new Date().getDate() - 7)), new Date()); - }; - - // is a given date in last year range? - is.inLastYear = function(date) { - return is.inDateRange(date, new Date(new Date().setFullYear(new Date().getFullYear() - 1)), new Date()); - }; - - // is a given date in next month range? - is.inNextMonth = function(date) { - return is.inDateRange(date, new Date(), new Date(new Date().setMonth(new Date().getMonth() + 1))); - }; - - // is a given date in next week range? - is.inNextWeek = function(date) { - return is.inDateRange(date, new Date(), new Date(new Date().setDate(new Date().getDate() + 7))); - }; - - // is a given date in next year range? - is.inNextYear = function(date) { - return is.inDateRange(date, new Date(), new Date(new Date().setFullYear(new Date().getFullYear() + 1))); - }; - - // is the given year a leap year? - is.leapYear = function(year) { - return is.number(year) && ((year % 4 === 0 && year % 100 !== 0) || year % 400 === 0); - }; - - // is a given dates month equal given month parameter? - is.month = function(date, month) { - return is.date(date) && month.toLowerCase() === months[date.getMonth()]; - }; - // month method does not support 'all' and 'any' interfaces - is.month.api = ['not']; - - // is a given date past? - is.past = function(date) { - var now = new Date(); - return is.date(date) && date.getTime() < now.getTime(); - }; - - // is a given date in the parameter quarter? - is.quarterOfYear = function(date, quarter) { - return is.date(date) && is.number(quarter) && quarter === Math.floor((date.getMonth() + 3) / 3); - }; - // quarterOfYear method does not support 'all' and 'any' interfaces - is.quarterOfYear.api = ['not']; - - // is a given date indicate today? - is.today = function(date) { - var now = new Date(); - var todayString = now.toDateString(); - return is.date(date) && date.toDateString() === todayString; - }; - - // is a given date indicate tomorrow? - is.tomorrow = function(date) { - var now = new Date(); - var tomorrowString = new Date(now.setDate(now.getDate() + 1)).toDateString(); - return is.date(date) && date.toDateString() === tomorrowString; - }; - - // is a given date weekend? - // 6: Saturday, 0: Sunday - is.weekend = function(date) { - return is.date(date) && (date.getDay() === 6 || date.getDay() === 0); - }; - - // is a given date weekday? - is.weekday = not(is.weekend); - - // is a given dates year equal given year parameter? - is.year = function(date, year) { - return is.date(date) && is.number(year) && year === date.getFullYear(); - }; - // year method does not support 'all' and 'any' interfaces - is.year.api = ['not']; - - // is a given date indicate yesterday? - is.yesterday = function(date) { - var now = new Date(); - var yesterdayString = new Date(now.setDate(now.getDate() - 1)).toDateString(); - return is.date(date) && date.toDateString() === yesterdayString; - }; - - // Environment checks - /* -------------------------------------------------------------------------- */ - - var freeGlobal = is.windowObject(typeof global == 'object' && global) && global; - var freeSelf = is.windowObject(typeof self == 'object' && self) && self; - var thisGlobal = is.windowObject(typeof this == 'object' && this) && this; - var root = freeGlobal || freeSelf || thisGlobal || Function('return this')(); - - var document = freeSelf && freeSelf.document; - var previousIs = root.is; - - // store navigator properties to use later - var navigator = freeSelf && freeSelf.navigator; - var appVersion = (navigator && navigator.appVersion || '').toLowerCase(); - var userAgent = (navigator && navigator.userAgent || '').toLowerCase(); - var vendor = (navigator && navigator.vendor || '').toLowerCase(); - - // is current device android? - is.android = function() { - return /android/.test(userAgent); - }; - // android method does not support 'all' and 'any' interfaces - is.android.api = ['not']; - - // is current device android phone? - is.androidPhone = function() { - return /android/.test(userAgent) && /mobile/.test(userAgent); - }; - // androidPhone method does not support 'all' and 'any' interfaces - is.androidPhone.api = ['not']; - - // is current device android tablet? - is.androidTablet = function() { - return /android/.test(userAgent) && !/mobile/.test(userAgent); - }; - // androidTablet method does not support 'all' and 'any' interfaces - is.androidTablet.api = ['not']; - - // is current device blackberry? - is.blackberry = function() { - return /blackberry/.test(userAgent) || /bb10/.test(userAgent); - }; - // blackberry method does not support 'all' and 'any' interfaces - is.blackberry.api = ['not']; - - // is current browser chrome? - // parameter is optional - is.chrome = function(range) { - var match = /google inc/.test(vendor) ? userAgent.match(/(?:chrome|crios)\/(\d+)/) : null; - return match !== null && compareVersion(match[1], range); - }; - // chrome method does not support 'all' and 'any' interfaces - is.chrome.api = ['not']; - - // is current device desktop? - is.desktop = function() { - return is.not.mobile() && is.not.tablet(); - }; - // desktop method does not support 'all' and 'any' interfaces - is.desktop.api = ['not']; - - // is current browser edge? - // parameter is optional - is.edge = function(range) { - var match = userAgent.match(/edge\/(\d+)/); - return match !== null && compareVersion(match[1], range); - }; - // edge method does not support 'all' and 'any' interfaces - is.edge.api = ['not']; - - // is current browser firefox? - // parameter is optional - is.firefox = function(range) { - var match = userAgent.match(/(?:firefox|fxios)\/(\d+)/); - return match !== null && compareVersion(match[1], range); - }; - // firefox method does not support 'all' and 'any' interfaces - is.firefox.api = ['not']; - - // is current browser internet explorer? - // parameter is optional - is.ie = function(range) { - var match = userAgent.match(/(?:msie |trident.+?; rv:)(\d+)/); - return match !== null && compareVersion(match[1], range); - }; - // ie method does not support 'all' and 'any' interfaces - is.ie.api = ['not']; - - // is current device ios? - is.ios = function() { - return is.iphone() || is.ipad() || is.ipod(); - }; - // ios method does not support 'all' and 'any' interfaces - is.ios.api = ['not']; - - // is current device ipad? - // parameter is optional - is.ipad = function(range) { - var match = userAgent.match(/ipad.+?os (\d+)/); - return match !== null && compareVersion(match[1], range); - }; - // ipad method does not support 'all' and 'any' interfaces - is.ipad.api = ['not']; - - // is current device iphone? - // parameter is optional - is.iphone = function(range) { - // original iPhone doesn't have the os portion of the UA - var match = userAgent.match(/iphone(?:.+?os (\d+))?/); - return match !== null && compareVersion(match[1] || 1, range); - }; - // iphone method does not support 'all' and 'any' interfaces - is.iphone.api = ['not']; - - // is current device ipod? - // parameter is optional - is.ipod = function(range) { - var match = userAgent.match(/ipod.+?os (\d+)/); - return match !== null && compareVersion(match[1], range); - }; - // ipod method does not support 'all' and 'any' interfaces - is.ipod.api = ['not']; - - // is current operating system linux? - is.linux = function() { - return /linux/.test(appVersion); - }; - // linux method does not support 'all' and 'any' interfaces - is.linux.api = ['not']; - - // is current operating system mac? - is.mac = function() { - return /mac/.test(appVersion); - }; - // mac method does not support 'all' and 'any' interfaces - is.mac.api = ['not']; - - // is current device mobile? - is.mobile = function() { - return is.iphone() || is.ipod() || is.androidPhone() || is.blackberry() || is.windowsPhone(); - }; - // mobile method does not support 'all' and 'any' interfaces - is.mobile.api = ['not']; - - // is current state offline? - is.offline = not(is.online); - // offline method does not support 'all' and 'any' interfaces - is.offline.api = ['not']; - - // is current state online? - is.online = function() { - return !navigator || navigator.onLine === true; - }; - // online method does not support 'all' and 'any' interfaces - is.online.api = ['not']; - - // is current browser opera? - // parameter is optional - is.opera = function(range) { - var match = userAgent.match(/(?:^opera.+?version|opr)\/(\d+)/); - return match !== null && compareVersion(match[1], range); - }; - // opera method does not support 'all' and 'any' interfaces - is.opera.api = ['not']; - - // is current browser phantomjs? - // parameter is optional - is.phantom = function(range) { - var match = userAgent.match(/phantomjs\/(\d+)/); - return match !== null && compareVersion(match[1], range); - }; - // phantom method does not support 'all' and 'any' interfaces - is.phantom.api = ['not']; - - // is current browser safari? - // parameter is optional - is.safari = function(range) { - var match = userAgent.match(/version\/(\d+).+?safari/); - return match !== null && compareVersion(match[1], range); - }; - // safari method does not support 'all' and 'any' interfaces - is.safari.api = ['not']; - - // is current device tablet? - is.tablet = function() { - return is.ipad() || is.androidTablet() || is.windowsTablet(); - }; - // tablet method does not support 'all' and 'any' interfaces - is.tablet.api = ['not']; - - // is current device supports touch? - is.touchDevice = function() { - return !!document && ('ontouchstart' in freeSelf || - ('DocumentTouch' in freeSelf && document instanceof DocumentTouch)); - }; - // touchDevice method does not support 'all' and 'any' interfaces - is.touchDevice.api = ['not']; - - // is current operating system windows? - is.windows = function() { - return /win/.test(appVersion); - }; - // windows method does not support 'all' and 'any' interfaces - is.windows.api = ['not']; - - // is current device windows phone? - is.windowsPhone = function() { - return is.windows() && /phone/.test(userAgent); - }; - // windowsPhone method does not support 'all' and 'any' interfaces - is.windowsPhone.api = ['not']; - - // is current device windows tablet? - is.windowsTablet = function() { - return is.windows() && is.not.windowsPhone() && /touch/.test(userAgent); - }; - // windowsTablet method does not support 'all' and 'any' interfaces - is.windowsTablet.api = ['not']; - - // Object checks - /* -------------------------------------------------------------------------- */ - - // has a given object got parameterized count property? - is.propertyCount = function(object, count) { - if (is.not.object(object) || is.not.number(count)) { - return false; - } - var n = 0; - for (var property in object) { - if (hasOwnProperty.call(object, property) && ++n > count) { - return false; - } - } - return n === count; - }; - // propertyCount method does not support 'all' and 'any' interfaces - is.propertyCount.api = ['not']; - - // is given object has parameterized property? - is.propertyDefined = function(object, property) { - return is.object(object) && is.string(property) && property in object; - }; - // propertyDefined method does not support 'all' and 'any' interfaces - is.propertyDefined.api = ['not']; - - // Array checks - /* -------------------------------------------------------------------------- */ - - // is a given item in an array? - is.inArray = function(value, array) { - if (is.not.array(array)) { - return false; - } - for (var i = 0; i < array.length; i++) { - if (array[i] === value) { - return true; - } - } - return false; - }; - // inArray method does not support 'all' and 'any' interfaces - is.inArray.api = ['not']; - - // is a given array sorted? - is.sorted = function(array, sign) { - if (is.not.array(array)) { - return false; - } - var predicate = comparator[sign] || comparator['>=']; - for (var i = 1; i < array.length; i++) { - if (!predicate(array[i], array[i - 1])) { - return false; - } - } - return true; - }; - - // API - // Set 'not', 'all' and 'any' interfaces to methods based on their api property - /* -------------------------------------------------------------------------- */ - - function setInterfaces() { - var options = is; - for (var option in options) { - if (hasOwnProperty.call(options, option) && is['function'](options[option])) { - var interfaces = options[option].api || ['not', 'all', 'any']; - for (var i = 0; i < interfaces.length; i++) { - if (interfaces[i] === 'not') { - is.not[option] = not(is[option]); - } - if (interfaces[i] === 'all') { - is.all[option] = all(is[option]); - } - if (interfaces[i] === 'any') { - is.any[option] = any(is[option]); - } - } - } - } - } - setInterfaces(); - - // Configuration methods - // Intentionally added after setInterfaces function - /* -------------------------------------------------------------------------- */ - - // change namespace of library to prevent name collisions - // var preferredName = is.setNamespace(); - // preferredName.odd(3); - // => true - is.setNamespace = function() { - root.is = previousIs; - return this; - }; - - // set optional regexes to methods - is.setRegexp = function(regexp, name) { - for (var r in regexes) { - if (hasOwnProperty.call(regexes, r) && (name === r)) { - regexes[r] = regexp; - } - } - }; - - return is; -})); +/*! + * is.js 0.8.0 + * Author: Aras Atasaygin + */ + +// AMD with global, Node, or global +(function (root, factory) { + // eslint-disable-line no-extra-semi + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define(function () { + // Also create a global in case some scripts + // that are loaded still are looking for + // a global even when an AMD loader is in use. + return (root.is = factory()); + }); + } else if (typeof exports === 'object') { + // Node. Does not work with strict CommonJS, but + // only CommonJS-like enviroments that support module.exports, + // like Node. + module.exports = factory(); + } else { + // Browser globals (root is self) + root.is = factory(); + } +})(this, function () { + // Baseline + /* -------------------------------------------------------------------------- */ + + // define 'is' object and current version + var is = {}; + is.VERSION = '0.8.0'; + + // define interfaces + is.not = {}; + is.all = {}; + is.any = {}; + + // cache some methods to call later on + var toString = Object.prototype.toString; + var slice = Array.prototype.slice; + var hasOwnProperty = Object.prototype.hasOwnProperty; + + // helper function which reverses the sense of predicate result + function not(func) { + return function () { + return !func.apply(null, slice.call(arguments)); + }; + } + + // helper function which call predicate function per parameter and return true if all pass + function all(func) { + return function () { + var params = getParams(arguments); + var length = params.length; + for (var i = 0; i < length; i++) { + if (!func.call(null, params[i])) { + return false; + } + } + return true; + }; + } + + // helper function which call predicate function per parameter and return true if any pass + function any(func) { + return function () { + var params = getParams(arguments); + var length = params.length; + for (var i = 0; i < length; i++) { + if (func.call(null, params[i])) { + return true; + } + } + return false; + }; + } + + // build a 'comparator' object for various comparison checks + var comparator = { + '<': function (a, b) { + return a < b; + }, + '<=': function (a, b) { + return a <= b; + }, + '>': function (a, b) { + return a > b; + }, + '>=': function (a, b) { + return a >= b; + } + }; + + // helper function which compares a version to a range + function compareVersion(version, range) { + var string = range + ''; + var n = +(string.match(/\d+/) || NaN); + var op = string.match(/^[<>]=?|/)[0]; + return comparator[op] + ? comparator[op](version, n) + : version == n || n !== n; + } + + // helper function which extracts params from arguments + function getParams(args) { + var params = slice.call(args); + var length = params.length; + if (length === 1 && is.array(params[0])) { + // support array + params = params[0]; + } + return params; + } + + // Type checks + /* -------------------------------------------------------------------------- */ + + // is a given value Arguments? + is.arguments = function (value) { + // fallback check is for IE + return ( + toString.call(value) === '[object Arguments]' || + (value != null && typeof value === 'object' && 'callee' in value) + ); + }; + + // is a given value Array? + is.array = + Array.isArray || + function (value) { + // check native isArray first + return toString.call(value) === '[object Array]'; + }; + + // is a given value Boolean? + is.boolean = function (value) { + return ( + value === true || + value === false || + toString.call(value) === '[object Boolean]' + ); + }; + + // is a given value Char? + is.char = function (value) { + return is.string(value) && value.length === 1; + }; + + // is a given value Date Object? + is.date = function (value) { + return toString.call(value) === '[object Date]'; + }; + + // is a given object a DOM node? + is.domNode = function (object) { + return is.object(object) && object.nodeType > 0; + }; + + // is a given value Error object? + is.error = function (value) { + return toString.call(value) === '[object Error]'; + }; + + // is a given value function? + is['function'] = function (value) { + // fallback check is for IE + return ( + toString.call(value) === '[object Function]' || + typeof value === 'function' + ); + }; + + // is given value a pure JSON object? + is.json = function (value) { + return toString.call(value) === '[object Object]'; + }; + + // is a given value NaN? + is.nan = function (value) { + // NaN is number :) Also it is the only value which does not equal itself + return value !== value; + }; + + // is a given value null? + is['null'] = function (value) { + return value === null; + }; + + // is a given value number? + is.number = function (value) { + return is.not.nan(value) && toString.call(value) === '[object Number]'; + }; + + // is a given value object? + is.object = function (value) { + return Object(value) === value; + }; + + // is a given value RegExp? + is.regexp = function (value) { + return toString.call(value) === '[object RegExp]'; + }; + + // are given values same type? + // prevent NaN, Number same type check + is.sameType = function (value, other) { + var tag = toString.call(value); + if (tag !== toString.call(other)) { + return false; + } + if (tag === '[object Number]') { + return !is.any.nan(value, other) || is.all.nan(value, other); + } + return true; + }; + // sameType method does not support 'all' and 'any' interfaces + is.sameType.api = ['not']; + + // is a given value String? + is.string = function (value) { + return toString.call(value) === '[object String]'; + }; + + // is a given value undefined? + is.undefined = function (value) { + return value === void 0; + }; + + // is a given value window? + // setInterval method is only available for window object + is.windowObject = function (value) { + return value != null && typeof value === 'object' && 'setInterval' in value; + }; + + // Presence checks + /* -------------------------------------------------------------------------- */ + + //is a given value empty? Objects, arrays, strings + is.empty = function (value) { + if (is.object(value)) { + var length = Object.getOwnPropertyNames(value).length; + if ( + length === 0 || + (length === 1 && is.array(value)) || + (length === 2 && is.arguments(value)) + ) { + return true; + } + return false; + } + return value === ''; + }; + + // is a given value existy? + is.existy = function (value) { + return value != null; + }; + + // is a given value falsy? + is.falsy = function (value) { + return !value; + }; + + // is a given value truthy? + is.truthy = not(is.falsy); + + // Arithmetic checks + /* -------------------------------------------------------------------------- */ + + // is a given number above minimum parameter? + is.above = function (n, min) { + return is.all.number(n, min) && n > min; + }; + // above method does not support 'all' and 'any' interfaces + is.above.api = ['not']; + + // is a given number decimal? + is.decimal = function (n) { + return is.number(n) && n % 1 !== 0; + }; + + // are given values equal? supports numbers, strings, regexes, booleans + // TODO: Add object and array support + is.equal = function (value, other) { + // check 0 and -0 equity with Infinity and -Infinity + if (is.all.number(value, other)) { + return value === other && 1 / value === 1 / other; + } + // check regexes as strings too + if (is.all.string(value, other) || is.all.regexp(value, other)) { + return '' + value === '' + other; + } + if (is.all.boolean(value, other)) { + return value === other; + } + return false; + }; + // equal method does not support 'all' and 'any' interfaces + is.equal.api = ['not']; + + // is a given number even? + is.even = function (n) { + return is.number(n) && n % 2 === 0; + }; + + // is a given number finite? + is.finite = + isFinite || + function (n) { + return is.not.infinite(n) && is.not.nan(n); + }; + + // is a given number infinite? + is.infinite = function (n) { + return n === Infinity || n === -Infinity; + }; + + // is a given number integer? + is.integer = function (n) { + return is.number(n) && n % 1 === 0; + }; + + // is a given number negative? + is.negative = function (n) { + return is.number(n) && n < 0; + }; + + // is a given number odd? + is.odd = function (n) { + return is.number(n) && n % 2 === 1; + }; + + // is a given number positive? + is.positive = function (n) { + return is.number(n) && n > 0; + }; + + // is a given number above maximum parameter? + is.under = function (n, max) { + return is.all.number(n, max) && n < max; + }; + // least method does not support 'all' and 'any' interfaces + is.under.api = ['not']; + + // is a given number within minimum and maximum parameters? + is.within = function (n, min, max) { + return is.all.number(n, min, max) && n > min && n < max; + }; + // within method does not support 'all' and 'any' interfaces + is.within.api = ['not']; + + // Regexp checks + /* -------------------------------------------------------------------------- */ + // Steven Levithan, Jan Goyvaerts: Regular Expressions Cookbook + // Scott Gonzalez: Email address validation + + // dateString match m/d/yy and mm/dd/yyyy, allowing any combination of one or two digits for the day and month, and two or four digits for the year + // eppPhone match extensible provisioning protocol format + // nanpPhone match north american number plan format + // time match hours, minutes, and seconds, 24-hour clock + var regexes = { + affirmative: /^(?:1|t(?:rue)?|y(?:es)?|ok(?:ay)?)$/, + alphaNumeric: /^[A-Za-z0-9]+$/, + caPostalCode: /^(?!.*[DFIOQU])[A-VXY][0-9][A-Z]\s?[0-9][A-Z][0-9]$/, + creditCard: + /^(?:(4[0-9]{12}(?:[0-9]{3})?)|(5[1-5][0-9]{14})|(6(?:011|5[0-9]{2})[0-9]{12})|(3[47][0-9]{13})|(3(?:0[0-5]|[68][0-9])[0-9]{11})|((?:2131|1800|35[0-9]{3})[0-9]{11}))$/, + dateString: + /^(1[0-2]|0?[1-9])([\/-])(3[01]|[12][0-9]|0?[1-9])(?:\2)(?:[0-9]{2})?[0-9]{2}$/, + email: + /^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$/i, // eslint-disable-line no-control-regex + eppPhone: /^\+[0-9]{1,3}\.[0-9]{4,14}(?:x.+)?$/, + hexadecimal: /^(?:0x)?[0-9a-fA-F]+$/, + hexColor: /^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/, + ipv4: /^(?:(?:\d|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5])\.){3}(?:\d|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5])$/, + ipv6: /^((?=.*::)(?!.*::.+::)(::)?([\dA-F]{1,4}:(:|\b)|){5}|([\dA-F]{1,4}:){6})((([\dA-F]{1,4}((?!\3)::|:\b|$))|(?!\2\3)){2}|(((2[0-4]|1\d|[1-9])?\d|25[0-5])\.?\b){4})$/i, + nanpPhone: /^\(?([0-9]{3})\)?[-. ]?([0-9]{3})[-. ]?([0-9]{4})$/, + socialSecurityNumber: + /^(?!000|666)[0-8][0-9]{2}-?(?!00)[0-9]{2}-?(?!0000)[0-9]{4}$/, + timeString: /^(2[0-3]|[01]?[0-9]):([0-5]?[0-9]):([0-5]?[0-9])$/, + ukPostCode: + /^[A-Z]{1,2}[0-9RCHNQ][0-9A-Z]?\s?[0-9][ABD-HJLNP-UW-Z]{2}$|^[A-Z]{2}-?[0-9]{4}$/, + url: /^(?:(?:https?|ftp):\/\/)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})))(?::\d{2,5})?(?:\/\S*)?$/i, + usZipCode: /^[0-9]{5}(?:-[0-9]{4})?$/ + }; + + function regexpCheck(regexp, regexes) { + is[regexp] = function (value) { + return regexes[regexp].test(value); + }; + } + + // create regexp checks methods from 'regexes' object + for (var regexp in regexes) { + if (regexes.hasOwnProperty(regexp)) { + regexpCheck(regexp, regexes); + } + } + + // simplify IP checks by calling the regex helpers for IPv4 and IPv6 + is.ip = function (value) { + return is.ipv4(value) || is.ipv6(value); + }; + + // String checks + /* -------------------------------------------------------------------------- */ + + // is a given string or sentence capitalized? + is.capitalized = function (string) { + if (is.not.string(string)) { + return false; + } + var words = string.split(' '); + for (var i = 0; i < words.length; i++) { + var word = words[i]; + if (word.length) { + var chr = word.charAt(0); + if (chr !== chr.toUpperCase()) { + return false; + } + } + } + return true; + }; + + // is string end with a given target parameter? + is.endWith = function (string, target) { + if (is.not.string(string)) { + return false; + } + target += ''; + var position = string.length - target.length; + return position >= 0 && string.indexOf(target, position) === position; + }; + // endWith method does not support 'all' and 'any' interfaces + is.endWith.api = ['not']; + + // is a given string include parameter target? + is.include = function (string, target) { + return string.indexOf(target) > -1; + }; + // include method does not support 'all' and 'any' interfaces + is.include.api = ['not']; + + // is a given string all lowercase? + is.lowerCase = function (string) { + return is.string(string) && string === string.toLowerCase(); + }; + + // is a given string palindrome? + is.palindrome = function (string) { + if (is.not.string(string)) { + return false; + } + string = string.replace(/[^a-zA-Z0-9]+/g, '').toLowerCase(); + var length = string.length - 1; + for (var i = 0, half = Math.floor(length / 2); i <= half; i++) { + if (string.charAt(i) !== string.charAt(length - i)) { + return false; + } + } + return true; + }; + + // is a given value space? + // horizantal tab: 9, line feed: 10, vertical tab: 11, form feed: 12, carriage return: 13, space: 32 + is.space = function (value) { + if (is.not.char(value)) { + return false; + } + var charCode = value.charCodeAt(0); + return (charCode > 8 && charCode < 14) || charCode === 32; + }; + + // is string start with a given target parameter? + is.startWith = function (string, target) { + return is.string(string) && string.indexOf(target) === 0; + }; + // startWith method does not support 'all' and 'any' interfaces + is.startWith.api = ['not']; + + // is a given string all uppercase? + is.upperCase = function (string) { + return is.string(string) && string === string.toUpperCase(); + }; + + // Time checks + /* -------------------------------------------------------------------------- */ + + var days = [ + 'sunday', + 'monday', + 'tuesday', + 'wednesday', + 'thursday', + 'friday', + 'saturday' + ]; + var months = [ + 'january', + 'february', + 'march', + 'april', + 'may', + 'june', + 'july', + 'august', + 'september', + 'october', + 'november', + 'december' + ]; + + // is a given dates day equal given day parameter? + is.day = function (date, day) { + return is.date(date) && day.toLowerCase() === days[date.getDay()]; + }; + // day method does not support 'all' and 'any' interfaces + is.day.api = ['not']; + + // is a given date in daylight saving time? + is.dayLightSavingTime = function (date) { + var january = new Date(date.getFullYear(), 0, 1); + var july = new Date(date.getFullYear(), 6, 1); + var stdTimezoneOffset = Math.max( + january.getTimezoneOffset(), + july.getTimezoneOffset() + ); + return date.getTimezoneOffset() < stdTimezoneOffset; + }; + + // is a given date future? + is.future = function (date) { + var now = new Date(); + return is.date(date) && date.getTime() > now.getTime(); + }; + + // is date within given range? + is.inDateRange = function (date, start, end) { + if (is.not.date(date) || is.not.date(start) || is.not.date(end)) { + return false; + } + var stamp = date.getTime(); + return stamp > start.getTime() && stamp < end.getTime(); + }; + // inDateRange method does not support 'all' and 'any' interfaces + is.inDateRange.api = ['not']; + + // is a given date in last month range? + is.inLastMonth = function (date) { + return is.inDateRange( + date, + new Date(new Date().setMonth(new Date().getMonth() - 1)), + new Date() + ); + }; + + // is a given date in last week range? + is.inLastWeek = function (date) { + return is.inDateRange( + date, + new Date(new Date().setDate(new Date().getDate() - 7)), + new Date() + ); + }; + + // is a given date in last year range? + is.inLastYear = function (date) { + return is.inDateRange( + date, + new Date(new Date().setFullYear(new Date().getFullYear() - 1)), + new Date() + ); + }; + + // is a given date in next month range? + is.inNextMonth = function (date) { + return is.inDateRange( + date, + new Date(), + new Date(new Date().setMonth(new Date().getMonth() + 1)) + ); + }; + + // is a given date in next week range? + is.inNextWeek = function (date) { + return is.inDateRange( + date, + new Date(), + new Date(new Date().setDate(new Date().getDate() + 7)) + ); + }; + + // is a given date in next year range? + is.inNextYear = function (date) { + return is.inDateRange( + date, + new Date(), + new Date(new Date().setFullYear(new Date().getFullYear() + 1)) + ); + }; + + // is the given year a leap year? + is.leapYear = function (year) { + return ( + is.number(year) && + ((year % 4 === 0 && year % 100 !== 0) || year % 400 === 0) + ); + }; + + // is a given dates month equal given month parameter? + is.month = function (date, month) { + return is.date(date) && month.toLowerCase() === months[date.getMonth()]; + }; + // month method does not support 'all' and 'any' interfaces + is.month.api = ['not']; + + // is a given date past? + is.past = function (date) { + var now = new Date(); + return is.date(date) && date.getTime() < now.getTime(); + }; + + // is a given date in the parameter quarter? + is.quarterOfYear = function (date, quarter) { + return ( + is.date(date) && + is.number(quarter) && + quarter === Math.floor((date.getMonth() + 3) / 3) + ); + }; + // quarterOfYear method does not support 'all' and 'any' interfaces + is.quarterOfYear.api = ['not']; + + // is a given date indicate today? + is.today = function (date) { + var now = new Date(); + var todayString = now.toDateString(); + return is.date(date) && date.toDateString() === todayString; + }; + + // is a given date indicate tomorrow? + is.tomorrow = function (date) { + var now = new Date(); + var tomorrowString = new Date( + now.setDate(now.getDate() + 1) + ).toDateString(); + return is.date(date) && date.toDateString() === tomorrowString; + }; + + // is a given date weekend? + // 6: Saturday, 0: Sunday + is.weekend = function (date) { + return is.date(date) && (date.getDay() === 6 || date.getDay() === 0); + }; + + // is a given date weekday? + is.weekday = not(is.weekend); + + // is a given dates year equal given year parameter? + is.year = function (date, year) { + return is.date(date) && is.number(year) && year === date.getFullYear(); + }; + // year method does not support 'all' and 'any' interfaces + is.year.api = ['not']; + + // is a given date indicate yesterday? + is.yesterday = function (date) { + var now = new Date(); + var yesterdayString = new Date( + now.setDate(now.getDate() - 1) + ).toDateString(); + return is.date(date) && date.toDateString() === yesterdayString; + }; + + // Environment checks + /* -------------------------------------------------------------------------- */ + + var freeGlobal = + is.windowObject(typeof global == 'object' && global) && global; + var freeSelf = is.windowObject(typeof self == 'object' && self) && self; + var thisGlobal = is.windowObject(typeof this == 'object' && this) && this; + var root = freeGlobal || freeSelf || thisGlobal || Function('return this')(); + + var document = freeSelf && freeSelf.document; + var previousIs = root.is; + + // store navigator properties to use later + var navigator = freeSelf && freeSelf.navigator; + var appVersion = ((navigator && navigator.appVersion) || '').toLowerCase(); + var userAgent = ((navigator && navigator.userAgent) || '').toLowerCase(); + var vendor = ((navigator && navigator.vendor) || '').toLowerCase(); + + // is current device android? + is.android = function () { + return /android/.test(userAgent); + }; + // android method does not support 'all' and 'any' interfaces + is.android.api = ['not']; + + // is current device android phone? + is.androidPhone = function () { + return /android/.test(userAgent) && /mobile/.test(userAgent); + }; + // androidPhone method does not support 'all' and 'any' interfaces + is.androidPhone.api = ['not']; + + // is current device android tablet? + is.androidTablet = function () { + return /android/.test(userAgent) && !/mobile/.test(userAgent); + }; + // androidTablet method does not support 'all' and 'any' interfaces + is.androidTablet.api = ['not']; + + // is current device blackberry? + is.blackberry = function () { + return /blackberry/.test(userAgent) || /bb10/.test(userAgent); + }; + // blackberry method does not support 'all' and 'any' interfaces + is.blackberry.api = ['not']; + + // is current browser chrome? + // parameter is optional + is.chrome = function (range) { + var match = /google inc/.test(vendor) + ? userAgent.match(/(?:chrome|crios)\/(\d+)/) + : null; + return match !== null && compareVersion(match[1], range); + }; + // chrome method does not support 'all' and 'any' interfaces + is.chrome.api = ['not']; + + // is current device desktop? + is.desktop = function () { + return is.not.mobile() && is.not.tablet(); + }; + // desktop method does not support 'all' and 'any' interfaces + is.desktop.api = ['not']; + + // is current browser edge? + // parameter is optional + is.edge = function (range) { + var match = userAgent.match(/edge\/(\d+)/); + return match !== null && compareVersion(match[1], range); + }; + // edge method does not support 'all' and 'any' interfaces + is.edge.api = ['not']; + + // is current browser firefox? + // parameter is optional + is.firefox = function (range) { + var match = userAgent.match(/(?:firefox|fxios)\/(\d+)/); + return match !== null && compareVersion(match[1], range); + }; + // firefox method does not support 'all' and 'any' interfaces + is.firefox.api = ['not']; + + // is current browser internet explorer? + // parameter is optional + is.ie = function (range) { + var match = userAgent.match(/(?:msie |trident.+?; rv:)(\d+)/); + return match !== null && compareVersion(match[1], range); + }; + // ie method does not support 'all' and 'any' interfaces + is.ie.api = ['not']; + + // is current device ios? + is.ios = function () { + return is.iphone() || is.ipad() || is.ipod(); + }; + // ios method does not support 'all' and 'any' interfaces + is.ios.api = ['not']; + + // is current device ipad? + // parameter is optional + is.ipad = function (range) { + var match = userAgent.match(/ipad.+?os (\d+)/); + return match !== null && compareVersion(match[1], range); + }; + // ipad method does not support 'all' and 'any' interfaces + is.ipad.api = ['not']; + + // is current device iphone? + // parameter is optional + is.iphone = function (range) { + // original iPhone doesn't have the os portion of the UA + var match = userAgent.match(/iphone(?:.+?os (\d+))?/); + return match !== null && compareVersion(match[1] || 1, range); + }; + // iphone method does not support 'all' and 'any' interfaces + is.iphone.api = ['not']; + + // is current device ipod? + // parameter is optional + is.ipod = function (range) { + var match = userAgent.match(/ipod.+?os (\d+)/); + return match !== null && compareVersion(match[1], range); + }; + // ipod method does not support 'all' and 'any' interfaces + is.ipod.api = ['not']; + + // is current operating system linux? + is.linux = function () { + return /linux/.test(appVersion); + }; + // linux method does not support 'all' and 'any' interfaces + is.linux.api = ['not']; + + // is current operating system mac? + is.mac = function () { + return /mac/.test(appVersion); + }; + // mac method does not support 'all' and 'any' interfaces + is.mac.api = ['not']; + + // is current device mobile? + is.mobile = function () { + return ( + is.iphone() || + is.ipod() || + is.androidPhone() || + is.blackberry() || + is.windowsPhone() + ); + }; + // mobile method does not support 'all' and 'any' interfaces + is.mobile.api = ['not']; + + // is current state offline? + is.offline = not(is.online); + // offline method does not support 'all' and 'any' interfaces + is.offline.api = ['not']; + + // is current state online? + is.online = function () { + return !navigator || navigator.onLine === true; + }; + // online method does not support 'all' and 'any' interfaces + is.online.api = ['not']; + + // is current browser opera? + // parameter is optional + is.opera = function (range) { + var match = userAgent.match(/(?:^opera.+?version|opr)\/(\d+)/); + return match !== null && compareVersion(match[1], range); + }; + // opera method does not support 'all' and 'any' interfaces + is.opera.api = ['not']; + + // is current browser phantomjs? + // parameter is optional + is.phantom = function (range) { + var match = userAgent.match(/phantomjs\/(\d+)/); + return match !== null && compareVersion(match[1], range); + }; + // phantom method does not support 'all' and 'any' interfaces + is.phantom.api = ['not']; + + // is current browser safari? + // parameter is optional + is.safari = function (range) { + var match = userAgent.match(/version\/(\d+).+?safari/); + return match !== null && compareVersion(match[1], range); + }; + // safari method does not support 'all' and 'any' interfaces + is.safari.api = ['not']; + + // is current device tablet? + is.tablet = function () { + return is.ipad() || is.androidTablet() || is.windowsTablet(); + }; + // tablet method does not support 'all' and 'any' interfaces + is.tablet.api = ['not']; + + // is current device supports touch? + is.touchDevice = function () { + return ( + !!document && + ('ontouchstart' in freeSelf || + ('DocumentTouch' in freeSelf && document instanceof DocumentTouch)) + ); + }; + // touchDevice method does not support 'all' and 'any' interfaces + is.touchDevice.api = ['not']; + + // is current operating system windows? + is.windows = function () { + return /win/.test(appVersion); + }; + // windows method does not support 'all' and 'any' interfaces + is.windows.api = ['not']; + + // is current device windows phone? + is.windowsPhone = function () { + return is.windows() && /phone/.test(userAgent); + }; + // windowsPhone method does not support 'all' and 'any' interfaces + is.windowsPhone.api = ['not']; + + // is current device windows tablet? + is.windowsTablet = function () { + return is.windows() && is.not.windowsPhone() && /touch/.test(userAgent); + }; + // windowsTablet method does not support 'all' and 'any' interfaces + is.windowsTablet.api = ['not']; + + // Object checks + /* -------------------------------------------------------------------------- */ + + // has a given object got parameterized count property? + is.propertyCount = function (object, count) { + if (is.not.object(object) || is.not.number(count)) { + return false; + } + var n = 0; + for (var property in object) { + if (hasOwnProperty.call(object, property) && ++n > count) { + return false; + } + } + return n === count; + }; + // propertyCount method does not support 'all' and 'any' interfaces + is.propertyCount.api = ['not']; + + // is given object has parameterized property? + is.propertyDefined = function (object, property) { + return is.object(object) && is.string(property) && property in object; + }; + // propertyDefined method does not support 'all' and 'any' interfaces + is.propertyDefined.api = ['not']; + + // Array checks + /* -------------------------------------------------------------------------- */ + + // is a given item in an array? + is.inArray = function (value, array) { + if (is.not.array(array)) { + return false; + } + for (var i = 0; i < array.length; i++) { + if (array[i] === value) { + return true; + } + } + return false; + }; + // inArray method does not support 'all' and 'any' interfaces + is.inArray.api = ['not']; + + // is a given array sorted? + is.sorted = function (array, sign) { + if (is.not.array(array)) { + return false; + } + var predicate = comparator[sign] || comparator['>=']; + for (var i = 1; i < array.length; i++) { + if (!predicate(array[i], array[i - 1])) { + return false; + } + } + return true; + }; + + // API + // Set 'not', 'all' and 'any' interfaces to methods based on their api property + /* -------------------------------------------------------------------------- */ + + function setInterfaces() { + var options = is; + for (var option in options) { + if ( + hasOwnProperty.call(options, option) && + is['function'](options[option]) + ) { + var interfaces = options[option].api || ['not', 'all', 'any']; + for (var i = 0; i < interfaces.length; i++) { + if (interfaces[i] === 'not') { + is.not[option] = not(is[option]); + } + if (interfaces[i] === 'all') { + is.all[option] = all(is[option]); + } + if (interfaces[i] === 'any') { + is.any[option] = any(is[option]); + } + } + } + } + } + setInterfaces(); + + // Configuration methods + // Intentionally added after setInterfaces function + /* -------------------------------------------------------------------------- */ + + // change namespace of library to prevent name collisions + // var preferredName = is.setNamespace(); + // preferredName.odd(3); + // => true + is.setNamespace = function () { + root.is = previousIs; + return this; + }; + + // set optional regexes to methods + is.setRegexp = function (regexp, name) { + for (var r in regexes) { + if (hasOwnProperty.call(regexes, r) && name === r) { + regexes[r] = regexp; + } + } + }; + + return is; +}); diff --git a/crypto/blockchains/bnb/utils/UVarInt.js b/crypto/blockchains/bnb/utils/UVarInt.js deleted file mode 100644 index 715f2821d..000000000 --- a/crypto/blockchains/bnb/utils/UVarInt.js +++ /dev/null @@ -1,81 +0,0 @@ -const BN = require('bn.js') - -function VarIntFunc(signed) { - var encodingLength = function encodingLength(n) { - if (signed) n *= 2 - - if (n < 0) { - throw Error('varint value is out of bounds') - } - - var bits = Math.log2(n + 1) - return Math.ceil(bits / 7) || 1 - } - - var encode = function encode(n, buffer, offset) { - if (n < 0) { - throw Error('varint value is out of bounds') - } - - buffer = buffer || Buffer.alloc(encodingLength(n)) - offset = offset || 0 - var nStr = n.toString() - var bn = new BN(nStr, 10) - var num255 = new BN(0xff) - var num128 = new BN(0x80) // amino signed varint is multiplied by 2 - - if (signed) { - bn = bn.muln(2) - } - - var i = 0 - - while (bn.gten(0x80)) { - buffer[offset + i] = bn.and(num255).or(num128).toNumber() - bn = bn.shrn(7) - i++ - } - - buffer[offset + i] = bn.andln(0xff) // TODO - // encode.bytes = i + 1 - - return buffer - } - /** - * https://github.com/golang/go/blob/master/src/encoding/binary/varint.go#L60 - */ - - - var decode = function decode(bytes) { - var x = 0 - var s = 0 - - for (var i = 0, len = bytes.length; i < len; i++) { - var b = bytes[i] - - if (b < 0x80) { - if (i > 9 || i === 9 && b > 1) { - return 0 - } - - return x | b << s - } - - x |= (b & 0x7f) << s - s += 7 - } - - return 0 - } - - return { - encode: encode, - decode: decode, - encodingLength: encodingLength - } -} - -var UVarInt = VarIntFunc(false) -exports.UVarInt = UVarInt -var VarInt = VarIntFunc(true) -exports.VarInt = VarInt diff --git a/crypto/blockchains/bnb/utils/UVarInt.ts b/crypto/blockchains/bnb/utils/UVarInt.ts new file mode 100644 index 000000000..95278358c --- /dev/null +++ b/crypto/blockchains/bnb/utils/UVarInt.ts @@ -0,0 +1,81 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ +const BN = require('bn.js'); + +function VarIntFunc(signed: boolean) { + const encodingLength = function encodingLength(n: number) { + if (signed) n *= 2; + + if (n < 0) { + throw Error('varint value is out of bounds'); + } + + const bits = Math.log2(n + 1); + return Math.ceil(bits / 7) || 1; + }; + + const encode = function encode(n: number, buffer?: Buffer, offset?: number) { + if (n < 0) { + throw Error('varint value is out of bounds'); + } + + buffer = buffer || Buffer.alloc(encodingLength(n)); + offset = offset || 0; + const nStr = n.toString(); + let bn = new BN(nStr, 10); + const num255 = new BN(0xff); + const num128 = new BN(0x80); // amino signed varint is multiplied by 2 + + if (signed) { + bn = bn.muln(2); + } + + let i = 0; + + while (bn.gten(0x80)) { + buffer[offset + i] = bn.and(num255).or(num128).toNumber(); + bn = bn.shrn(7); + i++; + } + + buffer[offset + i] = bn.andln(0xff); // TODO + // encode.bytes = i + 1 + + return buffer; + }; + /** + * https://github.com/golang/go/blob/master/src/encoding/binary/varint.go#L60 + */ + + const decode = function decode(bytes: number[]) { + let x = 0; + let s = 0; + + for (let i = 0, len = bytes.length; i < len; i++) { + const b = bytes[i]; + + if (b < 0x80) { + if (i > 9 || (i === 9 && b > 1)) { + return 0; + } + + return x | (b << s); + } + + x |= (b & 0x7f) << s; + s += 7; + } + + return 0; + }; + + return { + encode: encode, + decode: decode, + encodingLength: encodingLength + }; +} + +const UVarInt = VarIntFunc(false); +exports.UVarInt = UVarInt; +const VarInt = VarIntFunc(true); +exports.VarInt = VarInt; diff --git a/crypto/blockchains/bnb_smart/BnbSmartTransferProcessor.ts b/crypto/blockchains/bnb_smart/BnbSmartTransferProcessor.ts index 95659fa5d..8b5fe97df 100644 --- a/crypto/blockchains/bnb_smart/BnbSmartTransferProcessor.ts +++ b/crypto/blockchains/bnb_smart/BnbSmartTransferProcessor.ts @@ -3,35 +3,53 @@ * @version 0.20 * https://api.etherscan.io/api?module=gastracker&action=gasoracle&apikey=YourApiKeyToken */ -import { BlocksoftBlockchainTypes } from '../BlocksoftBlockchainTypes' -import EthTransferProcessor from '../eth/EthTransferProcessor' +import { BlocksoftBlockchainTypes } from '../BlocksoftBlockchainTypes'; +import EthTransferProcessor from '../eth/EthTransferProcessor'; -import BnbSmartNetworkPrices from './basic/BnbSmartNetworkPrices' -import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings' +import BnbSmartNetworkPrices from './basic/BnbSmartNetworkPrices'; +import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings'; -export default class BnbSmartTransferProcessor extends EthTransferProcessor implements BlocksoftBlockchainTypes.TransferProcessor { - - async getFeeRate(data: BlocksoftBlockchainTypes.TransferData, privateData: BlocksoftBlockchainTypes.TransferPrivateData, additionalData: {} = {}): Promise { - if (typeof additionalData.gasPrice === 'undefined' || !additionalData.gasPrice) { - let minFee = BlocksoftExternalSettings.getStatic(this._mainCurrencyCode + '_FORCE_PRICE') - if (typeof minFee !== 'undefined' && minFee > 1) { - additionalData.gasPrice = minFee - additionalData.gasPriceTitle = 'speed_blocks_2' - } else { - let defaultFee = BlocksoftExternalSettings.getStatic(this._mainCurrencyCode + '_PRICE') - if (typeof defaultFee === 'undefined' || !defaultFee) { - defaultFee = 5000000000 - } - if (this._etherscanApiPathForFee) { - const tmpPrice = await BnbSmartNetworkPrices.getFees(this._mainCurrencyCode, this._etherscanApiPathForFee, defaultFee, 'BnbSmartTransferProcessor.getFeeRate') - if (tmpPrice * 1 > defaultFee * 1) { - defaultFee = tmpPrice * 1 - } - } - additionalData.gasPrice = defaultFee - additionalData.gasPriceTitle = 'speed_blocks_2' - } +export default class BnbSmartTransferProcessor + extends EthTransferProcessor + implements BlocksoftBlockchainTypes.TransferProcessor +{ + async getFeeRate( + data: BlocksoftBlockchainTypes.TransferData, + privateData: BlocksoftBlockchainTypes.TransferPrivateData, + additionalData: {} = {} + ): Promise { + if ( + typeof additionalData.gasPrice === 'undefined' || + !additionalData.gasPrice + ) { + let minFee = BlocksoftExternalSettings.getStatic( + this._mainCurrencyCode + '_FORCE_PRICE' + ); + if (typeof minFee !== 'undefined' && minFee > 1) { + additionalData.gasPrice = minFee; + additionalData.gasPriceTitle = 'speed_blocks_2'; + } else { + let defaultFee = BlocksoftExternalSettings.getStatic( + this._mainCurrencyCode + '_PRICE' + ); + if (typeof defaultFee === 'undefined' || !defaultFee) { + defaultFee = 5000000000; + } + if (this._etherscanApiPathForFee) { + const tmpPrice = await BnbSmartNetworkPrices.getFees( + this._mainCurrencyCode, + this._etherscanApiPathForFee, + defaultFee, + 'BnbSmartTransferProcessor.getFeeRate' + ); + if (tmpPrice * 1 > defaultFee * 1) { + defaultFee = tmpPrice * 1; + } } - return super.getFeeRate(data, privateData, additionalData) + additionalData.gasPrice = defaultFee; + additionalData.gasPriceTitle = 'speed_blocks_2'; + } } + return super.getFeeRate(data, privateData, additionalData); + } } diff --git a/crypto/blockchains/bnb_smart/BnbSmartTransferProcessorErc20.ts b/crypto/blockchains/bnb_smart/BnbSmartTransferProcessorErc20.ts index 07296929c..2aac7d29a 100644 --- a/crypto/blockchains/bnb_smart/BnbSmartTransferProcessorErc20.ts +++ b/crypto/blockchains/bnb_smart/BnbSmartTransferProcessorErc20.ts @@ -1,48 +1,78 @@ /** * @version 0.20 */ -import { BlocksoftBlockchainTypes } from '../BlocksoftBlockchainTypes' -import EthTransferProcessorErc20 from '../eth/EthTransferProcessorErc20' -import BnbSmartNetworkPrices from './basic/BnbSmartNetworkPrices' -import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings' +import { BlocksoftBlockchainTypes } from '../BlocksoftBlockchainTypes'; +import EthTransferProcessorErc20 from '../eth/EthTransferProcessorErc20'; +import BnbSmartNetworkPrices from './basic/BnbSmartNetworkPrices'; +import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings'; -export default class BnbSmartTransferProcessorErc20 extends EthTransferProcessorErc20 implements BlocksoftBlockchainTypes.TransferProcessor { - - async getFeeRate(data: BlocksoftBlockchainTypes.TransferData, privateData: BlocksoftBlockchainTypes.TransferPrivateData, additionalData: {} = {}): Promise { - if (typeof additionalData.gasPrice === 'undefined' || !additionalData.gasPrice) { - let minFee = BlocksoftExternalSettings.getStatic(this._mainCurrencyCode + '_FORCE_PRICE_ERC20') - if (typeof minFee !== 'undefined' && minFee > 1) { - additionalData.gasPrice = minFee - additionalData.gasPriceTitle = 'speed_blocks_2' - } else { - let defaultFee = BlocksoftExternalSettings.getStatic(this._mainCurrencyCode + '_PRICE') - if (typeof defaultFee === 'undefined' || !defaultFee) { - defaultFee = 5000000000 - } - if (!this._etherscanApiPathForFee) { - additionalData.gasPrice = defaultFee - additionalData.gasPriceTitle = 'speed_blocks_2' - } else { - additionalData.gasPrice = await BnbSmartNetworkPrices.getFees(this._mainCurrencyCode, this._etherscanApiPathForFee, defaultFee, 'BnbSmartTransferProcessorErc20.getFeeRate') - additionalData.gasPriceTitle = 'speed_blocks_2' - } - } +export default class BnbSmartTransferProcessorErc20 + extends EthTransferProcessorErc20 + implements BlocksoftBlockchainTypes.TransferProcessor +{ + async getFeeRate( + data: BlocksoftBlockchainTypes.TransferData, + privateData: BlocksoftBlockchainTypes.TransferPrivateData, + additionalData: {} = {} + ): Promise { + if ( + typeof additionalData.gasPrice === 'undefined' || + !additionalData.gasPrice + ) { + let minFee = BlocksoftExternalSettings.getStatic( + this._mainCurrencyCode + '_FORCE_PRICE_ERC20' + ); + if (typeof minFee !== 'undefined' && minFee > 1) { + additionalData.gasPrice = minFee; + additionalData.gasPriceTitle = 'speed_blocks_2'; + } else { + let defaultFee = BlocksoftExternalSettings.getStatic( + this._mainCurrencyCode + '_PRICE' + ); + if (typeof defaultFee === 'undefined' || !defaultFee) { + defaultFee = 5000000000; } - const result = await super.getFeeRate(data, privateData, additionalData) - result.shouldShowFees = true - return result - } - - async checkTransferHasError(data: BlocksoftBlockchainTypes.CheckTransferHasErrorData): Promise { - // @ts-ignore - const balance = data.addressFrom && data.addressFrom !== '' ? await this._web3.eth.getBalance(data.addressFrom) : 0 - if (balance > 0) { - return { isOk: true } + if (!this._etherscanApiPathForFee) { + additionalData.gasPrice = defaultFee; + additionalData.gasPriceTitle = 'speed_blocks_2'; } else { - const title = this._mainCurrencyCode === 'BNB' ? 'BNB Smart Chain' : this._mainCurrencyCode - // @ts-ignore - return { isOk: false, code: 'TOKEN', parentBlockchain: title, parentCurrency: title } + additionalData.gasPrice = await BnbSmartNetworkPrices.getFees( + this._mainCurrencyCode, + this._etherscanApiPathForFee, + defaultFee, + 'BnbSmartTransferProcessorErc20.getFeeRate' + ); + additionalData.gasPriceTitle = 'speed_blocks_2'; } + } } + const result = await super.getFeeRate(data, privateData, additionalData); + result.shouldShowFees = true; + return result; + } + async checkTransferHasError( + data: BlocksoftBlockchainTypes.CheckTransferHasErrorData + ): Promise { + // @ts-ignore + const balance = + data.addressFrom && data.addressFrom !== '' + ? await this._web3.eth.getBalance(data.addressFrom) + : 0; + if (balance > 0) { + return { isOk: true }; + } else { + const title = + this._mainCurrencyCode === 'BNB' + ? 'BNB Smart Chain' + : this._mainCurrencyCode; + // @ts-ignore + return { + isOk: false, + code: 'TOKEN', + parentBlockchain: title, + parentCurrency: title + }; + } + } } diff --git a/crypto/blockchains/bnb_smart/basic/BnbSmartNetworkPrices.js b/crypto/blockchains/bnb_smart/basic/BnbSmartNetworkPrices.js index b307c99a8..a0363d87d 100644 --- a/crypto/blockchains/bnb_smart/basic/BnbSmartNetworkPrices.js +++ b/crypto/blockchains/bnb_smart/basic/BnbSmartNetworkPrices.js @@ -1,54 +1,81 @@ /** * @version 0.20 */ -import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog' -import BlocksoftAxios from '../../../common/BlocksoftAxios' -import BlocksoftUtils from '../../../common/BlocksoftUtils' -import BlocksoftExternalSettings from '../../../common/BlocksoftExternalSettings' -import config from '@app/config/config' +import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; +import BlocksoftAxios from '../../../common/BlocksoftAxios'; +import BlocksoftUtils from '../../../common/BlocksoftUtils'; +import BlocksoftExternalSettings from '../../../common/BlocksoftExternalSettings'; +import config from '@app/config/config'; -const CACHE_VALID_TIME = 120000 // 2 minute +const CACHE_VALID_TIME = 120000; // 2 minute const CACHE_FEES = { - 'BNB' : { - fee : 5000000000, - ts : 0 - } -} + BNB: { + fee: 5000000000, + ts: 0 + } +}; class BnbSmartNetworkPrices { - async getFees(mainCurrencyCode, etherscanApiPath, defaultFee = 5000000000, source = '') { - const now = new Date().getTime() - if (typeof CACHE_FEES[mainCurrencyCode] !== 'undefined' && now - CACHE_FEES[mainCurrencyCode].ts < CACHE_VALID_TIME) { - return CACHE_FEES[mainCurrencyCode].fee - } - const tmp = etherscanApiPath.split('/') - const feesApiPath = `https://${tmp[2]}/api?module=proxy&action=eth_gasPrice&apikey=YourApiKeyToken` - BlocksoftCryptoLog.log(mainCurrencyCode + ' BnbSmartNetworkPricesProvider.getFees no cache load') - try { - const res = await BlocksoftAxios.getWithoutBraking(feesApiPath) - if (res && typeof res.data !== 'undefined' && typeof res.data.result !== 'undefined') { - const tmp = BlocksoftUtils.hexToDecimal(res.data.result) - if (tmp * 1 > 0) { - CACHE_FEES[mainCurrencyCode] = { - fee : (tmp * 1).toString().substr(0, 11), - time : now - } - } else if (typeof CACHE_FEES[mainCurrencyCode] === 'undefined' || !CACHE_FEES[mainCurrencyCode].fee) { - CACHE_FEES[mainCurrencyCode].fee = await BlocksoftExternalSettings.getStatic('BNB_SMART_PRICE') - } - } - } catch (e) { - if (config.debug.cryptoErrors) { - console.log(mainCurrencyCode + ' BnbSmartNetworkPricesProvider.getOnlyFees loaded prev fee as error' + e.message) - } - BlocksoftCryptoLog.log(mainCurrencyCode + ' BnbSmartNetworkPricesProvider.getOnlyFees loaded prev fee as error' + e.message) - // do nothing + async getFees( + mainCurrencyCode, + etherscanApiPath, + defaultFee = 5000000000, + source = '' + ) { + const now = new Date().getTime(); + if ( + typeof CACHE_FEES[mainCurrencyCode] !== 'undefined' && + now - CACHE_FEES[mainCurrencyCode].ts < CACHE_VALID_TIME + ) { + return CACHE_FEES[mainCurrencyCode].fee; + } + const tmp = etherscanApiPath.split('/'); + const feesApiPath = `https://${tmp[2]}/api?module=proxy&action=eth_gasPrice&apikey=YourApiKeyToken`; + BlocksoftCryptoLog.log( + mainCurrencyCode + ' BnbSmartNetworkPricesProvider.getFees no cache load' + ); + try { + const res = await BlocksoftAxios.getWithoutBraking(feesApiPath); + if ( + res && + typeof res.data !== 'undefined' && + typeof res.data.result !== 'undefined' + ) { + const tmp = BlocksoftUtils.hexToDecimal(res.data.result); + if (tmp * 1 > 0) { + CACHE_FEES[mainCurrencyCode] = { + fee: (tmp * 1).toString().substr(0, 11), + time: now + }; + } else if ( + typeof CACHE_FEES[mainCurrencyCode] === 'undefined' || + !CACHE_FEES[mainCurrencyCode].fee + ) { + CACHE_FEES[mainCurrencyCode].fee = + await BlocksoftExternalSettings.getStatic('BNB_SMART_PRICE'); } - - return typeof CACHE_FEES[mainCurrencyCode] !== 'undefined' ? CACHE_FEES[mainCurrencyCode].fee : defaultFee + } + } catch (e) { + if (config.debug.cryptoErrors) { + console.log( + mainCurrencyCode + + ' BnbSmartNetworkPricesProvider.getOnlyFees loaded prev fee as error' + + e.message + ); + } + BlocksoftCryptoLog.log( + mainCurrencyCode + + ' BnbSmartNetworkPricesProvider.getOnlyFees loaded prev fee as error' + + e.message + ); + // do nothing } -} + return typeof CACHE_FEES[mainCurrencyCode] !== 'undefined' + ? CACHE_FEES[mainCurrencyCode].fee + : defaultFee; + } +} -const singleton = new BnbSmartNetworkPrices() -export default singleton +const singleton = new BnbSmartNetworkPrices(); +export default singleton; diff --git a/crypto/blockchains/bsv/BsvScannerProcessor.js b/crypto/blockchains/bsv/BsvScannerProcessor.js index 2ad6d81a6..63bb29f28 100644 --- a/crypto/blockchains/bsv/BsvScannerProcessor.js +++ b/crypto/blockchains/bsv/BsvScannerProcessor.js @@ -2,317 +2,375 @@ * @version 0.5 */ -import config from "@app/config/config" -import BlocksoftAxios from "@crypto/common/BlocksoftAxios" -import BlocksoftCryptoLog from "@crypto/common/BlocksoftCryptoLog" -import BlocksoftUtils from "@crypto/common/BlocksoftUtils"; -import BlocksoftPrettyNumbers from "@crypto/common/BlocksoftPrettyNumbers"; -import BsvTmpDS from "@crypto/blockchains/bsv/stores/BsvTmpDS"; +import config from '@app/config/config'; +import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; +import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; +import BlocksoftUtils from '@crypto/common/BlocksoftUtils'; +import BlocksoftPrettyNumbers from '@crypto/common/BlocksoftPrettyNumbers'; +import BsvTmpDS from '@crypto/blockchains/bsv/stores/BsvTmpDS'; -const API_PATH = 'https://api.whatsonchain.com/v1/bsv/main' -const CACHE_TXS = {} -const CACHE_ASKED = {} +const API_PATH = 'https://api.whatsonchain.com/v1/bsv/main'; +const CACHE_TXS = {}; +const CACHE_ASKED = {}; export default class BsvScannerProcessor { + /** + * @type {number} + * @private + */ + _blocksToConfirm = 10; - /** - * @type {number} - * @private - */ - _blocksToConfirm = 10 - - /** - * https://api.whatsonchain.com/v1/bsv/main/address/1BcHq66j64juMffHc4Sc5XQ59wWrcSygoZ/balance - * @param {string} address - * @return {Promise<{balance, unconfirmed, provider}>} - */ - async getBalanceBlockchain(address) { - const link = `${API_PATH}/address/${address}/balance` - let res = false - let balance = 0 - try { - res = await BlocksoftAxios.getWithoutBraking(link) - if (res && typeof res.data !== 'undefined' && res.data && typeof res.data.confirmed !== 'undefined') { - balance = res.data.confirmed - } else { - return false - } - } catch (e) { - // ? - throw e - } - return {balance: balance, unconfirmed: 0, provider: 'api.whatsonchain.com'} + /** + * https://api.whatsonchain.com/v1/bsv/main/address/1BcHq66j64juMffHc4Sc5XQ59wWrcSygoZ/balance + * @param {string} address + * @return {Promise<{balance, unconfirmed, provider}>} + */ + async getBalanceBlockchain(address) { + const link = `${API_PATH}/address/${address}/balance`; + let res = false; + let balance = 0; + try { + res = await BlocksoftAxios.getWithoutBraking(link); + if ( + res && + typeof res.data !== 'undefined' && + res.data && + typeof res.data.confirmed !== 'undefined' + ) { + balance = res.data.confirmed; + } else { + return false; + } + } catch (e) { + // ? + throw e; } + return { + balance: balance, + unconfirmed: 0, + provider: 'api.whatsonchain.com' + }; + } - async _saveTxToCache(tx) { - if (typeof CACHE_TXS[tx.txid] === 'undefined' && tx.confirmations > 100) { - await BsvTmpDS.saveCache(tx.txid, tx) - } - - CACHE_TXS[tx.txid] = tx + async _saveTxToCache(tx) { + if (typeof CACHE_TXS[tx.txid] === 'undefined' && tx.confirmations > 100) { + await BsvTmpDS.saveCache(tx.txid, tx); } - /** - * https://api.whatsonchain.com/v1/bsv/main/address/1BcHq66j64juMffHc4Sc5XQ59wWrcSygoZ/history - * @param scanData - * @param source - * @returns {Promise<*[]>} - */ - async getTransactionsBlockchain(scanData, source = '') { - const address = scanData.account.address.trim() - if (!CACHE_ASKED[address]) { - try { - const asked = await BsvTmpDS.getCache(address) - if (asked) { - for (let txid in asked) { - CACHE_TXS[txid] = asked[txid] - } - } - CACHE_ASKED[address] = true - } catch (e) { - throw new Error(e.message + ' in BsvTmpDS.getCache') - } - } - BlocksoftCryptoLog.log('BsvScannerProcessor.getTransactions started ' + address) - const linkTxs = `${API_PATH}/address/${address}/history` - let res = false - try { - res = await BlocksoftAxios.getWithoutBraking(linkTxs) - } catch (e) { - throw e - } - - if (!res || typeof res.data === 'undefined' || !res.data) { - return false - } - const basicTxs = [] - for (const row of res.data) { - if (row.height * 1 > 0) { - basicTxs.push(row) - } else { - BlocksoftCryptoLog.log('BsvScannerProcessor.getTransactions strange one ' + JSON.stringify(row)) - } - } + CACHE_TXS[tx.txid] = tx; + } - let index = basicTxs.length - let bulkTxs = [] - let otherTxs = [] - let transactions = [] - for (let i = 0; i < 20; i++) { - index-- - if (index < 0) { - break - } - let txid = basicTxs[index].tx_hash - if (typeof CACHE_TXS[txid] === 'undefined') { - bulkTxs.push(txid) - } else { - otherTxs.push(CACHE_TXS[txid]) - } + /** + * https://api.whatsonchain.com/v1/bsv/main/address/1BcHq66j64juMffHc4Sc5XQ59wWrcSygoZ/history + * @param scanData + * @param source + * @returns {Promise<*[]>} + */ + async getTransactionsBlockchain(scanData, source = '') { + const address = scanData.account.address.trim(); + if (!CACHE_ASKED[address]) { + try { + const asked = await BsvTmpDS.getCache(address); + if (asked) { + for (let txid in asked) { + CACHE_TXS[txid] = asked[txid]; + } } + CACHE_ASKED[address] = true; + } catch (e) { + throw new Error(e.message + ' in BsvTmpDS.getCache'); + } + } + BlocksoftCryptoLog.log( + 'BsvScannerProcessor.getTransactions started ' + address + ); + const linkTxs = `${API_PATH}/address/${address}/history`; + let res = false; + try { + res = await BlocksoftAxios.getWithoutBraking(linkTxs); + } catch (e) { + throw e; + } - if (bulkTxs.length > 0) { - BlocksoftCryptoLog.log('BsvScannerProcessor.getTransactions will ask ' + JSON.stringify(bulkTxs)) - res = false - try { - res = await BlocksoftAxios.post(API_PATH + '/txs', {txids: bulkTxs}) - } catch (e) { - throw e - } - if (typeof res !== 'undefined' && res && typeof res.data !== 'undefined' && res.data) { - transactions = await this._unifyTransactions(address, res.data, otherTxs) - } else { - transactions = await this._unifyTransactions(address, [], otherTxs) - } - } else { - transactions = await this._unifyTransactions(address, [], otherTxs) - } + if (!res || typeof res.data === 'undefined' || !res.data) { + return false; + } + const basicTxs = []; + for (const row of res.data) { + if (row.height * 1 > 0) { + basicTxs.push(row); + } else { + BlocksoftCryptoLog.log( + 'BsvScannerProcessor.getTransactions strange one ' + + JSON.stringify(row) + ); + } + } + let index = basicTxs.length; + let bulkTxs = []; + let otherTxs = []; + let transactions = []; + for (let i = 0; i < 20; i++) { + index--; + if (index < 0) { + break; + } + let txid = basicTxs[index].tx_hash; + if (typeof CACHE_TXS[txid] === 'undefined') { + bulkTxs.push(txid); + } else { + otherTxs.push(CACHE_TXS[txid]); + } + } - BlocksoftCryptoLog.log('BsvScannerProcessor.getTransactions finished ' + address) - return transactions + if (bulkTxs.length > 0) { + BlocksoftCryptoLog.log( + 'BsvScannerProcessor.getTransactions will ask ' + + JSON.stringify(bulkTxs) + ); + res = false; + try { + res = await BlocksoftAxios.post(API_PATH + '/txs', { txids: bulkTxs }); + } catch (e) { + throw e; + } + if ( + typeof res !== 'undefined' && + res && + typeof res.data !== 'undefined' && + res.data + ) { + transactions = await this._unifyTransactions( + address, + res.data, + otherTxs + ); + } else { + transactions = await this._unifyTransactions(address, [], otherTxs); + } + } else { + transactions = await this._unifyTransactions(address, [], otherTxs); } - async _precheckVins(result) { - let vins = [] - for (let tx of result) { - for (let vin of tx.vin) { - if (typeof CACHE_TXS[vin.txid] === 'undefined') { - vins.push(vin.txid) - if (vins.length > 19) { - BlocksoftCryptoLog.log('BsvScannerProcessor.getTransactions will ask vins ' + JSON.stringify(vins)) - const res = await BlocksoftAxios.post(API_PATH + '/txs', {txids: vins}) - if (res && typeof res.data !== 'undefined' && res.data) { - for (const tx of res.data) { - await this._saveTxToCache(tx) - } - } - vins = [] - } - } - } - } - if (vins && vins.length > 0) { - BlocksoftCryptoLog.log('BsvScannerProcessor.getTransactions will ask vins1 ' + JSON.stringify(vins)) - const res = await BlocksoftAxios.post(API_PATH + '/txs', {txids: vins}) + BlocksoftCryptoLog.log( + 'BsvScannerProcessor.getTransactions finished ' + address + ); + return transactions; + } + + async _precheckVins(result) { + let vins = []; + for (let tx of result) { + for (let vin of tx.vin) { + if (typeof CACHE_TXS[vin.txid] === 'undefined') { + vins.push(vin.txid); + if (vins.length > 19) { + BlocksoftCryptoLog.log( + 'BsvScannerProcessor.getTransactions will ask vins ' + + JSON.stringify(vins) + ); + const res = await BlocksoftAxios.post(API_PATH + '/txs', { + txids: vins + }); if (res && typeof res.data !== 'undefined' && res.data) { - for (const tx of res.data) { - await this._saveTxToCache(tx) - } + for (const tx of res.data) { + await this._saveTxToCache(tx); + } } + vins = []; + } } + } } - - async _unifyTransactions(address, result, otherTxs) { - const transactions = [] - if (result && result.length > 0) { - for (let tx of result) { - await this._saveTxToCache(tx) - } - await this._precheckVins(result) - for (let tx of result) { - const transaction = await this._unifyTransaction(address, tx) - if (transaction) { - transactions.push(transaction) - } - } - } - if (otherTxs && otherTxs.length > 0) { - for (let tx of otherTxs) { - await this._precheckVins(otherTxs) - try { - const transaction = await this._unifyTransaction(address, tx) - if (transaction) { - transactions.push(transaction) - } - } catch (e) { - throw new Error(e.message + ' in otherTxs _unifyTransaction ') - } - } + if (vins && vins.length > 0) { + BlocksoftCryptoLog.log( + 'BsvScannerProcessor.getTransactions will ask vins1 ' + + JSON.stringify(vins) + ); + const res = await BlocksoftAxios.post(API_PATH + '/txs', { txids: vins }); + if (res && typeof res.data !== 'undefined' && res.data) { + for (const tx of res.data) { + await this._saveTxToCache(tx); } - return transactions + } } + } - - /** - * @param {string} address - * @param {Object} transaction - * @param {string} transaction.txid "d1eecd2a07b9712644bcec3d7285fdc444691da6701d0032ebf6b0f823ab1727" - * @param {string} transaction.hash "d1eecd2a07b9712644bcec3d7285fdc444691da6701d0032ebf6b0f823ab1727" - * @param {string} transaction.version 2 - * @param {string} transaction.size 521 - * @param {string} transaction.locktime 0 - * @param {string} transaction.blockhash "000000000000000006d66f40fbe37ccc1854c3d13167e41485716b217ead8d6a" - * @param {string} transaction.confirmations 151964 - * @param {string} transaction.time 1576793232, - * @param {string} transaction.blocktime 1576793232 - * @param {string} transaction.blockheight 613832 - * @param {string} transaction.vin[].vout - * @param {string} transaction.vin[].txid - * @param {string} transaction.vin[].scriptSig.hex - * @param {string} transaction.vout[].value - * @param {string} transaction.vout[].n - * @param {string} transaction.vout[].scriptPubKey.addresses[] - * @return {UnifiedTransaction} - * @private - **/ - async _unifyTransaction(address, transaction) { + async _unifyTransactions(address, result, otherTxs) { + const transactions = []; + if (result && result.length > 0) { + for (let tx of result) { + await this._saveTxToCache(tx); + } + await this._precheckVins(result); + for (let tx of result) { + const transaction = await this._unifyTransaction(address, tx); + if (transaction) { + transactions.push(transaction); + } + } + } + if (otherTxs && otherTxs.length > 0) { + for (let tx of otherTxs) { + await this._precheckVins(otherTxs); try { - const tx = { - transactionHash: transaction.txid, - blockHash: transaction.blockhash, - blockNumber: transaction.blockheight, - blockTime: new Date(transaction.blocktime * 1000).toISOString(), - blockConfirmations: transaction.confirmations, - transactionDirection: '?', - addressFrom: '', - addressTo: '', - addressAmount: 0, - transactionStatus: transaction.confirmations > this._blocksToConfirm ? 'success' : 'new', - transactionFee: '0' - } + const transaction = await this._unifyTransaction(address, tx); + if (transaction) { + transactions.push(transaction); + } + } catch (e) { + throw new Error(e.message + ' in otherTxs _unifyTransaction '); + } + } + } + return transactions; + } - const vins = [] - for (let vin of transaction.vin) { - if (typeof CACHE_TXS[vin.txid] === 'undefined') { - vins.push(vin.txid) - } - } - if (vins && vins.length > 0) { - BlocksoftCryptoLog.log('BsvScannerProcessor.getTransactions will ask vins2 ' + JSON.stringify(vins)) - const res = await BlocksoftAxios.post(API_PATH + '/txs', {txids: vins}) - if (res && typeof res.data !== 'undefined' && res.data) { - for (const tx of res.data) { - await this._saveTxToCache(tx) - } - } - } - let othersSumIn = 0 - let mySumIn = 0 - let othersSumOut = 0 - let mySumOut = 0 - let othersAddressIn = false - let othersAddressOut = false - for (let vin of transaction.vin) { - if (typeof CACHE_TXS[vin.txid] === 'undefined') { - BlocksoftCryptoLog.log('BsvScannerProcessor _unifyTransaction error cant find vin ' + vin.txid + ' for tx ' + transaction.txid) - } else { - let found = false - for (let vinToCheck of CACHE_TXS[vin.txid].vout) { - if (vinToCheck.n === vin.vout) { - if (typeof vinToCheck.scriptPubKey.addresses !== 'undefined') { - const addressToCheck = vinToCheck.scriptPubKey.addresses[0] - const valueToCheck = vinToCheck.value - if (addressToCheck.toLowerCase() === address.toLowerCase()) { - mySumIn += valueToCheck * 1 - } else { - othersSumIn += valueToCheck * 1 - othersAddressIn = addressToCheck - } - } - found = true - } - } - if (!found) { - BlocksoftCryptoLog.log('BsvScannerProcessor _unifyTransaction error cant find vin ' + vin.txid + ' n ' + vin.vout + ' for tx ' + transaction.txid) - } - } - } + /** + * @param {string} address + * @param {Object} transaction + * @param {string} transaction.txid "d1eecd2a07b9712644bcec3d7285fdc444691da6701d0032ebf6b0f823ab1727" + * @param {string} transaction.hash "d1eecd2a07b9712644bcec3d7285fdc444691da6701d0032ebf6b0f823ab1727" + * @param {string} transaction.version 2 + * @param {string} transaction.size 521 + * @param {string} transaction.locktime 0 + * @param {string} transaction.blockhash "000000000000000006d66f40fbe37ccc1854c3d13167e41485716b217ead8d6a" + * @param {string} transaction.confirmations 151964 + * @param {string} transaction.time 1576793232, + * @param {string} transaction.blocktime 1576793232 + * @param {string} transaction.blockheight 613832 + * @param {string} transaction.vin[].vout + * @param {string} transaction.vin[].txid + * @param {string} transaction.vin[].scriptSig.hex + * @param {string} transaction.vout[].value + * @param {string} transaction.vout[].n + * @param {string} transaction.vout[].scriptPubKey.addresses[] + * @return {UnifiedTransaction} + * @private + **/ + async _unifyTransaction(address, transaction) { + try { + const tx = { + transactionHash: transaction.txid, + blockHash: transaction.blockhash, + blockNumber: transaction.blockheight, + blockTime: new Date(transaction.blocktime * 1000).toISOString(), + blockConfirmations: transaction.confirmations, + transactionDirection: '?', + addressFrom: '', + addressTo: '', + addressAmount: 0, + transactionStatus: + transaction.confirmations > this._blocksToConfirm ? 'success' : 'new', + transactionFee: '0' + }; - for (let vout of transaction.vout) { - const addressToCheck = vout.scriptPubKey.addresses[0] - const valueToCheck = vout.value + const vins = []; + for (let vin of transaction.vin) { + if (typeof CACHE_TXS[vin.txid] === 'undefined') { + vins.push(vin.txid); + } + } + if (vins && vins.length > 0) { + BlocksoftCryptoLog.log( + 'BsvScannerProcessor.getTransactions will ask vins2 ' + + JSON.stringify(vins) + ); + const res = await BlocksoftAxios.post(API_PATH + '/txs', { + txids: vins + }); + if (res && typeof res.data !== 'undefined' && res.data) { + for (const tx of res.data) { + await this._saveTxToCache(tx); + } + } + } + let othersSumIn = 0; + let mySumIn = 0; + let othersSumOut = 0; + let mySumOut = 0; + let othersAddressIn = false; + let othersAddressOut = false; + for (let vin of transaction.vin) { + if (typeof CACHE_TXS[vin.txid] === 'undefined') { + BlocksoftCryptoLog.log( + 'BsvScannerProcessor _unifyTransaction error cant find vin ' + + vin.txid + + ' for tx ' + + transaction.txid + ); + } else { + let found = false; + for (let vinToCheck of CACHE_TXS[vin.txid].vout) { + if (vinToCheck.n === vin.vout) { + if (typeof vinToCheck.scriptPubKey.addresses !== 'undefined') { + const addressToCheck = vinToCheck.scriptPubKey.addresses[0]; + const valueToCheck = vinToCheck.value; if (addressToCheck.toLowerCase() === address.toLowerCase()) { - mySumOut += valueToCheck * 1 + mySumIn += valueToCheck * 1; } else { - othersSumOut += valueToCheck * 1 - othersAddressOut = addressToCheck + othersSumIn += valueToCheck * 1; + othersAddressIn = addressToCheck; } + } + found = true; } + } + if (!found) { + BlocksoftCryptoLog.log( + 'BsvScannerProcessor _unifyTransaction error cant find vin ' + + vin.txid + + ' n ' + + vin.vout + + ' for tx ' + + transaction.txid + ); + } + } + } - if (!othersSumIn && !othersSumOut) { - tx.transactionDirection = 'self' - tx.transactionFee = BlocksoftPrettyNumbers.setCurrencyCode('BSV').makeUnPretty(BlocksoftUtils.diff(mySumOut, mySumIn)) - } else if (!othersSumIn) { - tx.transactionDirection = 'outcome' - tx.addressTo = othersAddressOut - const amount = BlocksoftUtils.diff(othersSumOut, othersSumIn) - const fee = BlocksoftUtils.diff(mySumOut + othersSumOut, mySumIn + othersSumIn) - tx.addressAmount = BlocksoftPrettyNumbers.setCurrencyCode('BSV').makeUnPretty(amount) - tx.transactionFee = BlocksoftPrettyNumbers.setCurrencyCode('BSV').makeUnPretty(fee) - } else { - tx.transactionDirection = 'income' - tx.addressFrom = othersAddressIn - const amount = BlocksoftUtils.diff(mySumOut, mySumIn) - tx.addressAmount = BlocksoftPrettyNumbers.setCurrencyCode('BSV').makeUnPretty(amount) - } - return tx - } catch (e) { - if (config.debug.cryptoErrors) { - console.log('BsvScannerProcessor _unifyTransaction error ' + e.message) - } - throw e + for (let vout of transaction.vout) { + const addressToCheck = vout.scriptPubKey.addresses[0]; + const valueToCheck = vout.value; + if (addressToCheck.toLowerCase() === address.toLowerCase()) { + mySumOut += valueToCheck * 1; + } else { + othersSumOut += valueToCheck * 1; + othersAddressOut = addressToCheck; } + } + + if (!othersSumIn && !othersSumOut) { + tx.transactionDirection = 'self'; + tx.transactionFee = BlocksoftPrettyNumbers.setCurrencyCode( + 'BSV' + ).makeUnPretty(BlocksoftUtils.diff(mySumOut, mySumIn)); + } else if (!othersSumIn) { + tx.transactionDirection = 'outcome'; + tx.addressTo = othersAddressOut; + const amount = BlocksoftUtils.diff(othersSumOut, othersSumIn); + const fee = BlocksoftUtils.diff( + mySumOut + othersSumOut, + mySumIn + othersSumIn + ); + tx.addressAmount = + BlocksoftPrettyNumbers.setCurrencyCode('BSV').makeUnPretty(amount); + tx.transactionFee = + BlocksoftPrettyNumbers.setCurrencyCode('BSV').makeUnPretty(fee); + } else { + tx.transactionDirection = 'income'; + tx.addressFrom = othersAddressIn; + const amount = BlocksoftUtils.diff(mySumOut, mySumIn); + tx.addressAmount = + BlocksoftPrettyNumbers.setCurrencyCode('BSV').makeUnPretty(amount); + } + return tx; + } catch (e) { + if (config.debug.cryptoErrors) { + console.log('BsvScannerProcessor _unifyTransaction error ' + e.message); + } + throw e; } + } } - diff --git a/crypto/blockchains/bsv/providers/BsvSendProvider.ts b/crypto/blockchains/bsv/providers/BsvSendProvider.ts index fe50558a9..e223d09fd 100644 --- a/crypto/blockchains/bsv/providers/BsvSendProvider.ts +++ b/crypto/blockchains/bsv/providers/BsvSendProvider.ts @@ -1,75 +1,121 @@ /** * @version 0.5 */ -import {BlocksoftBlockchainTypes} from "@crypto/blockchains/BlocksoftBlockchainTypes" -import DogeSendProvider from '@crypto/blockchains/doge/providers/DogeSendProvider' -import BlocksoftCryptoLog from "@crypto/common/BlocksoftCryptoLog" -import BlocksoftAxios from "@crypto/common/BlocksoftAxios" -import config from "@app/config/config" +import { BlocksoftBlockchainTypes } from '@crypto/blockchains/BlocksoftBlockchainTypes'; +import DogeSendProvider from '@crypto/blockchains/doge/providers/DogeSendProvider'; +import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; +import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; +import config from '@app/config/config'; -export default class BsvSendProvider extends DogeSendProvider implements BlocksoftBlockchainTypes.SendProvider { +export default class BsvSendProvider + extends DogeSendProvider + implements BlocksoftBlockchainTypes.SendProvider +{ + async sendTx( + hex: string, + subtitle: string, + txRBF: any, + logData: any + ): Promise<{ transactionHash: string; transactionJson: any }> { + await BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' BsvSendProvider.sendTx ' + + subtitle + + ' started ', + logData + ); - async sendTx(hex: string, subtitle: string, txRBF: any, logData: any): Promise<{ transactionHash: string, transactionJson: any }> { - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' BsvSendProvider.sendTx ' + subtitle + ' started ', logData) + let link = 'https://api.whatsonchain.com/v1/bsv/main/tx/raw'; - let link = 'https://api.whatsonchain.com/v1/bsv/main/tx/raw' + //logData = await this._check(hex, subtitle, txRBF, logData) - //logData = await this._check(hex, subtitle, txRBF, logData) - - let res - try { - res = await BlocksoftAxios.post(link, {txhex: hex}) - } catch (e) { - if (config.debug.cryptoErrors) { - console.log(this._settings.currencyCode + ' BsvSendProvider.sendTx error ', e) - } - if (subtitle.indexOf('rawSend') !== -1) { - throw e - } - try { - logData.error = e.message - await this._checkError(hex, subtitle, txRBF, logData) - } catch (e2) { - if (config.debug.cryptoErrors) { - console.log(this._settings.currencyCode + ' DogeSendProvider.send proxy error errorTx ' + e.message) - } - BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeSendProvider.send proxy error errorTx ' + e2.message) - } - if (this._settings.currencyCode === 'USDT' && e.message.indexOf('bad-txns-in-belowout') !== -1) { - throw new Error('SERVER_RESPONSE_NOT_ENOUGH_FEE') - } else if (e.message.indexOf('transaction already in block') !== -1) { - throw new Error('SERVER_RESPONSE_TRANSACTION_ALREADY_MINED') - } else if (e.message.indexOf('inputs-missingorspent') !== -1) { - throw new Error('SERVER_RESPONSE_TRANSACTION_ALREADY_MINED') - } else if (e.message.indexOf('insufficient priority') !== -1) { - throw new Error('SERVER_RESPONSE_NO_RESPONSE_OR_MORE_FEE') - } else if (e.message.indexOf('dust') !== -1) { - throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_DUST') - } else if (e.message.indexOf('bad-txns-inputs-spent') !== -1 || e.message.indexOf('txn-mempool-conflict') !== -1) { - throw new Error('SERVER_RESPONSE_NO_RESPONSE') - } else if (e.message.indexOf('min relay fee not met') !== -1 || e.message.indexOf('fee for relay') !== -1) { - throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_FEE') - } else if (e.message.indexOf('insufficient fee, rejecting replacement') !== -1) { - throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_FEE_FOR_REPLACEMENT') - } else if (e.message.indexOf('insufficient fee') !== -1) { - throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_FEE') - } else if (e.message.indexOf('too-long-mempool-chain') !== -1) { - throw new Error('SERVER_RESPONSE_NO_RESPONSE') - } else { - e.message += ' link: ' + link - throw e - } - } - if (typeof res.data === 'undefined' || !res.data) { - if (config.debug.cryptoErrors) { - console.log(this._settings.currencyCode + 'BsvSendProvider.send no txid', res.data) - } - throw new Error('SERVER_RESPONSE_NOT_CONNECTED') + let res; + try { + res = await BlocksoftAxios.post(link, { txhex: hex }); + } catch (e) { + if (config.debug.cryptoErrors) { + console.log( + this._settings.currencyCode + ' BsvSendProvider.sendTx error ', + e + ); + } + if (subtitle.indexOf('rawSend') !== -1) { + throw e; + } + try { + logData.error = e.message; + await this._checkError(hex, subtitle, txRBF, logData); + } catch (e2) { + if (config.debug.cryptoErrors) { + console.log( + this._settings.currencyCode + + ' DogeSendProvider.send proxy error errorTx ' + + e.message + ); } + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' DogeSendProvider.send proxy error errorTx ' + + e2.message + ); + } + if ( + this._settings.currencyCode === 'USDT' && + e.message.indexOf('bad-txns-in-belowout') !== -1 + ) { + throw new Error('SERVER_RESPONSE_NOT_ENOUGH_FEE'); + } else if (e.message.indexOf('transaction already in block') !== -1) { + throw new Error('SERVER_RESPONSE_TRANSACTION_ALREADY_MINED'); + } else if (e.message.indexOf('inputs-missingorspent') !== -1) { + throw new Error('SERVER_RESPONSE_TRANSACTION_ALREADY_MINED'); + } else if (e.message.indexOf('insufficient priority') !== -1) { + throw new Error('SERVER_RESPONSE_NO_RESPONSE_OR_MORE_FEE'); + } else if (e.message.indexOf('dust') !== -1) { + throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_DUST'); + } else if ( + e.message.indexOf('bad-txns-inputs-spent') !== -1 || + e.message.indexOf('txn-mempool-conflict') !== -1 + ) { + throw new Error('SERVER_RESPONSE_NO_RESPONSE'); + } else if ( + e.message.indexOf('min relay fee not met') !== -1 || + e.message.indexOf('fee for relay') !== -1 + ) { + throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_FEE'); + } else if ( + e.message.indexOf('insufficient fee, rejecting replacement') !== -1 + ) { + throw new Error( + 'SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_FEE_FOR_REPLACEMENT' + ); + } else if (e.message.indexOf('insufficient fee') !== -1) { + throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_FEE'); + } else if (e.message.indexOf('too-long-mempool-chain') !== -1) { + throw new Error('SERVER_RESPONSE_NO_RESPONSE'); + } else { + e.message += ' link: ' + link; + throw e; + } + } + if (typeof res.data === 'undefined' || !res.data) { + if (config.debug.cryptoErrors) { + console.log( + this._settings.currencyCode + 'BsvSendProvider.send no txid', + res.data + ); + } + throw new Error('SERVER_RESPONSE_NOT_CONNECTED'); + } - const transactionHash = res.data - logData = await this._checkSuccess(transactionHash, hex, subtitle, txRBF, logData) + const transactionHash = res.data; + logData = await this._checkSuccess( + transactionHash, + hex, + subtitle, + txRBF, + logData + ); - return { transactionHash, transactionJson: {}, logData } - } + return { transactionHash, transactionJson: {}, logData }; + } } diff --git a/crypto/blockchains/bsv/providers/BsvUnspentsProvider.ts b/crypto/blockchains/bsv/providers/BsvUnspentsProvider.ts index 4faef4273..39914f0bd 100644 --- a/crypto/blockchains/bsv/providers/BsvUnspentsProvider.ts +++ b/crypto/blockchains/bsv/providers/BsvUnspentsProvider.ts @@ -1,42 +1,61 @@ /** * @version 0.5 */ -import { BlocksoftBlockchainTypes } from '@crypto/blockchains/BlocksoftBlockchainTypes' -import DogeUnspentsProvider from '@crypto/blockchains/doge/providers/DogeUnspentsProvider' -import BtcCashUtils from '@crypto/blockchains/bch/ext/BtcCashUtils' -import BlocksoftCryptoLog from "@crypto/common/BlocksoftCryptoLog" -import BlocksoftExternalSettings from "@crypto/common/BlocksoftExternalSettings" -import BlocksoftAxios from "@crypto/common/BlocksoftAxios" -export default class BsvUnspentsProvider extends DogeUnspentsProvider implements BlocksoftBlockchainTypes.UnspentsProvider { +import { BlocksoftBlockchainTypes } from '@crypto/blockchains/BlocksoftBlockchainTypes'; +import DogeUnspentsProvider from '@crypto/blockchains/doge/providers/DogeUnspentsProvider'; +import BtcCashUtils from '@crypto/blockchains/bch/ext/BtcCashUtils'; +import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; +import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings'; +import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; +export default class BsvUnspentsProvider + extends DogeUnspentsProvider + implements BlocksoftBlockchainTypes.UnspentsProvider +{ + _isMyAddress( + voutAddress: string, + address: string, + walletHash: string + ): string { + const address2 = BtcCashUtils.fromLegacyAddress(address); + const address3 = 'bitcoincash:' + address2; + return voutAddress === address || + voutAddress === address2 || + voutAddress === address3 + ? address + : ''; + } - _isMyAddress(voutAddress: string, address: string, walletHash: string): string { - const address2 = BtcCashUtils.fromLegacyAddress(address) - const address3 = 'bitcoincash:' + address2 - return (voutAddress === address || voutAddress === address2 || voutAddress === address3) ? address : '' - } - - async getUnspents(address: string): Promise { - // @ts-ignore - BlocksoftCryptoLog.log(this._settings.currencyCode + ' BsvUnspentsProvider.getUnspents started ' + address) - let link = 'https://api.whatsonchain.com/v1/bsv/main/address/' + address + '/unspent' + async getUnspents( + address: string + ): Promise { + // @ts-ignore + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' BsvUnspentsProvider.getUnspents started ' + + address + ); + let link = + 'https://api.whatsonchain.com/v1/bsv/main/address/' + + address + + '/unspent'; - const res = await BlocksoftAxios.getWithoutBraking(link) - if (!res.data || typeof res.data[0] === 'undefined') { - return [] - } - const sortedUnspents = [] - for (let unspent of res.data) { - let unspentFormatted = { - confirmations: unspent.height, - height: unspent.height, - derivationPath: false, - vout: unspent?.tx_pos, - isRequired : false, - txid: unspent.tx_hash, - value: unspent.value - } - sortedUnspents.push(unspentFormatted) - } - return sortedUnspents + const res = await BlocksoftAxios.getWithoutBraking(link); + if (!res.data || typeof res.data[0] === 'undefined') { + return []; + } + const sortedUnspents = []; + for (let unspent of res.data) { + let unspentFormatted = { + confirmations: unspent.height, + height: unspent.height, + derivationPath: false, + vout: unspent?.tx_pos, + isRequired: false, + txid: unspent.tx_hash, + value: unspent.value + }; + sortedUnspents.push(unspentFormatted); } + return sortedUnspents; + } } diff --git a/crypto/blockchains/btc/BtcScannerProcessor.js b/crypto/blockchains/btc/BtcScannerProcessor.js index bfa8dcc4b..de136e41a 100644 --- a/crypto/blockchains/btc/BtcScannerProcessor.js +++ b/crypto/blockchains/btc/BtcScannerProcessor.js @@ -2,410 +2,537 @@ * @version 0.9 */ -import BlocksoftUtils from '@crypto/common/BlocksoftUtils' -import BlocksoftAxios from '@crypto/common/BlocksoftAxios' -import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog' -import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings' +import BlocksoftUtils from '@crypto/common/BlocksoftUtils'; +import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; +import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; +import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings'; -import BtcFindAddressFunction from './basic/BtcFindAddressFunction' -import config from '@app/config/config' -import Database from '@app/appstores/DataSource/Database' -import TransactionFilterTypeDict from '@appV2/dicts/transactionFilterTypeDict' +import BtcFindAddressFunction from './basic/BtcFindAddressFunction'; +import config from '@app/config/config'; +import Database from '@app/appstores/DataSource/Database'; +import TransactionFilterTypeDict from '@appV2/dicts/transactionFilterTypeDict'; -const CACHE_VALID_TIME = 60000 // 60 seconds -const CACHE = {} -const CACHE_WALLET_PUBS = {} +const CACHE_VALID_TIME = 60000; // 60 seconds +const CACHE = {}; +const CACHE_WALLET_PUBS = {}; -const TIMEOUT_BTC = 60000 -const PROXY_TXS = 'https://proxy.trustee.deals/btc/getTxs' +const TIMEOUT_BTC = 60000; +const PROXY_TXS = 'https://proxy.trustee.deals/btc/getTxs'; export default class BtcScannerProcessor { + /** + * @type {number} + * @private + */ + _blocksToConfirm = 1; - /** - * @type {number} - * @private - */ - _blocksToConfirm = 1 + /** + * @type {string} + * @private + */ + _trezorServerCode = 'BTC_TREZOR_SERVER'; - /** - * @type {string} - * @private - */ - _trezorServerCode = 'BTC_TREZOR_SERVER' + /** + * @private + */ + _trezorServer = false; - /** - * @private - */ - _trezorServer = false + constructor(settings) { + this._settings = settings; + } - constructor(settings) { - this._settings = settings + /** + * @param address + * @param additionalData + * @returns {Promise} + * @private + */ + async _get(address, additionalData, source = '') { + const now = new Date().getTime(); + if ( + typeof CACHE[address] !== 'undefined' && + now - CACHE[address].time < CACHE_VALID_TIME + ) { + CACHE[address].provider = 'trezor-cache'; + return CACHE[address]; } + BlocksoftCryptoLog.log( + 'BtcScannerProcessor._get ' + address + ' from ' + source + ' started' + ); - /** - * @param address - * @param additionalData - * @returns {Promise} - * @private - */ - async _get(address, additionalData, source = '') { - const now = new Date().getTime() - if (typeof CACHE[address] !== 'undefined' && (now - CACHE[address].time < CACHE_VALID_TIME)) { - CACHE[address].provider = 'trezor-cache' - return CACHE[address] - } - BlocksoftCryptoLog.log('BtcScannerProcessor._get ' + address + ' from ' + source + ' started') - - this._trezorServer = await BlocksoftExternalSettings.getTrezorServer(this._trezorServerCode, 'BTC.Scanner._get') + this._trezorServer = await BlocksoftExternalSettings.getTrezorServer( + this._trezorServerCode, + 'BTC.Scanner._get' + ); - const prefix = address.substr(0, 4) + const prefix = address.substr(0, 4); - let link = '' - let res = false - if (prefix === 'xpub' || prefix === 'zpub' || prefix === 'ypub') { - link = PROXY_TXS + '?address=' + address + '&type=xpub¤cyCode=' + this._settings['currencyCode'] - res = await BlocksoftAxios.getWithoutBraking(link, 5, TIMEOUT_BTC) - if (res && typeof res.data !== 'undefined' && res.data && typeof res.data.data !== 'undefined') { - res.data = res.data.data - } else { - link = this._trezorServer + '/api/v2/xpub/' + address + '?details=txs&gap=9999&tokens=used&pageSize=40' - try { - res = await BlocksoftAxios._request(link, 'get', false, false, true, TIMEOUT_BTC) - } catch (e) { - if (e.message.indexOf('"error":"internal server error"') !== -1) { - CACHE[address] = { - data: { - balance: 0, - unconfirmedBalance: 0, - addresses: [], - specialMark: 'badServer' - }, - time: now, - provider: 'trezor-badserver' - } - return CACHE[address] - } - } - } - } else { - link = PROXY_TXS + '?address=' + address + '¤cyCode=' + this._settings['currencyCode'] - res = await BlocksoftAxios.getWithoutBraking(link, 5, TIMEOUT_BTC) - if (res && typeof res.data !== 'undefined' && res.data && typeof res.data.data !== 'undefined') { - res.data = res.data.data - } else { - link = this._trezorServer + '/api/v2/address/' + address + '?details=txs&gap=9999&pageSize=80' - res = await BlocksoftAxios.getWithoutBraking(link, 5, TIMEOUT_BTC) - } - } - - if (!res || !res.data) { - await BlocksoftExternalSettings.setTrezorServerInvalid(this._trezorServerCode, this._trezorServer) + let link = ''; + let res = false; + if (prefix === 'xpub' || prefix === 'zpub' || prefix === 'ypub') { + link = + PROXY_TXS + + '?address=' + + address + + '&type=xpub¤cyCode=' + + this._settings['currencyCode']; + res = await BlocksoftAxios.getWithoutBraking(link, 5, TIMEOUT_BTC); + if ( + res && + typeof res.data !== 'undefined' && + res.data && + typeof res.data.data !== 'undefined' + ) { + res.data = res.data.data; + } else { + link = + this._trezorServer + + '/api/v2/xpub/' + + address + + '?details=txs&gap=9999&tokens=used&pageSize=40'; + try { + res = await BlocksoftAxios._request( + link, + 'get', + false, + false, + true, + TIMEOUT_BTC + ); + } catch (e) { + if (e.message.indexOf('"error":"internal server error"') !== -1) { CACHE[address] = { - data: false, - time: now, - provider: 'trezor-empty' - } - return false - } - if (typeof res.data.balance === 'undefined') { - throw new Error(this._settings.currencyCode + ' BtcScannerProcessor._get nothing loaded for address ' + link) + data: { + balance: 0, + unconfirmedBalance: 0, + addresses: [], + specialMark: 'badServer' + }, + time: now, + provider: 'trezor-badserver' + }; + return CACHE[address]; + } } + } + } else { + link = + PROXY_TXS + + '?address=' + + address + + '¤cyCode=' + + this._settings['currencyCode']; + res = await BlocksoftAxios.getWithoutBraking(link, 5, TIMEOUT_BTC); + if ( + res && + typeof res.data !== 'undefined' && + res.data && + typeof res.data.data !== 'undefined' + ) { + res.data = res.data.data; + } else { + link = + this._trezorServer + + '/api/v2/address/' + + address + + '?details=txs&gap=9999&pageSize=80'; + res = await BlocksoftAxios.getWithoutBraking(link, 5, TIMEOUT_BTC); + } + } - const addresses = {} - let plainAddresses = {} - if (additionalData && additionalData.addresses) { - plainAddresses = additionalData.addresses - } - if (typeof res.data.tokens !== 'undefined') { - let token - for (token of res.data.tokens) { - addresses[token.name] = { - balance: token.balance, - transactions: token.transfers, - path : token.path - } - plainAddresses[token.name] = token.path - } - } else { - plainAddresses[address] = 1 - } - res.data.addresses = addresses - res.data.plainAddresses = plainAddresses - CACHE[address] = { - data: res.data, - time: now, - provider: 'trezor' - } - return CACHE[address] + if (!res || !res.data) { + await BlocksoftExternalSettings.setTrezorServerInvalid( + this._trezorServerCode, + this._trezorServer + ); + CACHE[address] = { + data: false, + time: now, + provider: 'trezor-empty' + }; + return false; + } + if (typeof res.data.balance === 'undefined') { + throw new Error( + this._settings.currencyCode + + ' BtcScannerProcessor._get nothing loaded for address ' + + link + ); } - async _getPubs(walletHash) { - if (typeof CACHE_WALLET_PUBS[walletHash] !== 'undefined') { - return CACHE_WALLET_PUBS[walletHash] - } - const sqlPub = `SELECT wallet_pub_value as walletPub + const addresses = {}; + let plainAddresses = {}; + if (additionalData && additionalData.addresses) { + plainAddresses = additionalData.addresses; + } + if (typeof res.data.tokens !== 'undefined') { + let token; + for (token of res.data.tokens) { + addresses[token.name] = { + balance: token.balance, + transactions: token.transfers, + path: token.path + }; + plainAddresses[token.name] = token.path; + } + } else { + plainAddresses[address] = 1; + } + res.data.addresses = addresses; + res.data.plainAddresses = plainAddresses; + CACHE[address] = { + data: res.data, + time: now, + provider: 'trezor' + }; + return CACHE[address]; + } + + async _getPubs(walletHash) { + if (typeof CACHE_WALLET_PUBS[walletHash] !== 'undefined') { + return CACHE_WALLET_PUBS[walletHash]; + } + const sqlPub = `SELECT wallet_pub_value as walletPub FROM wallet_pub WHERE wallet_hash = '${walletHash}' - AND currency_code='BTC'` - const resPub = await Database.query(sqlPub) - CACHE_WALLET_PUBS[walletHash] = {} - if (resPub && resPub.array && resPub.array.length > 0) { - for (const row of resPub.array) { - const scanAddress = row.walletPub - CACHE_WALLET_PUBS[walletHash][scanAddress] = 1 - } - } - return CACHE_WALLET_PUBS[walletHash] + AND currency_code='BTC'`; + const resPub = await Database.query(sqlPub); + CACHE_WALLET_PUBS[walletHash] = {}; + if (resPub && resPub.array && resPub.array.length > 0) { + for (const row of resPub.array) { + const scanAddress = row.walletPub; + CACHE_WALLET_PUBS[walletHash][scanAddress] = 1; + } } + return CACHE_WALLET_PUBS[walletHash]; + } - /** - * @param {string} address - * @return {Promise<{balance:*, unconfirmed:*, provider:string}>} - */ - async getBalanceBlockchain(address, data, walletHash, source = '') { - BlocksoftCryptoLog.log(this._settings.currencyCode + ' BtcScannerProcessor.getBalance started ' + address) - const res = await this._get(address, data, source) - if (!res) { - return false - } - return { - address : address, - balance: res.data.balance, - unconfirmed: res.data.unconfirmedBalance, - provider: res.provider, - time: res.time, - addresses: res.data.addresses, - specialMark : typeof res.data.specialMark !== 'undefined' ? res.data.specialMark : false - } + /** + * @param {string} address + * @return {Promise<{balance:*, unconfirmed:*, provider:string}>} + */ + async getBalanceBlockchain(address, data, walletHash, source = '') { + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' BtcScannerProcessor.getBalance started ' + + address + ); + const res = await this._get(address, data, source); + if (!res) { + return false; } + return { + address: address, + balance: res.data.balance, + unconfirmed: res.data.unconfirmedBalance, + provider: res.provider, + time: res.time, + addresses: res.data.addresses, + specialMark: + typeof res.data.specialMark !== 'undefined' + ? res.data.specialMark + : false + }; + } - async getAddressesBlockchain(scanData, source = '') { - const address = scanData.account.address.trim() - const data = scanData.additional - const withBalances = typeof scanData.withBalances !== 'undefined' && scanData.withBalances - if (!withBalances) { - if (config.debug.cryptoErrors) { - console.log(this._settings.currencyCode + ' BtcScannerProcessor.getAddresses started withoutBalances (KSU!)', address) - } - BlocksoftCryptoLog.log(this._settings.currencyCode + ' BtcScannerProcessor.getAddresses started withoutBalances (KSU!)', address) - } else { - BlocksoftCryptoLog.log(this._settings.currencyCode + ' BtcScannerProcessor.getAddresses started withBalances', address) - } - let res = await this._get(address, data, source) - if (typeof res.data !== 'undefined') { - res = JSON.parse(JSON.stringify(res.data)) - } else { - res = false - } - try { - if (typeof data.walletPub !== 'undefined') { - const resPub = await this._getPubs(data.walletPub.walletHash) - for (const scanAddress in resPub) { - if (scanAddress === address) continue - const tmp = await this._get(scanAddress, data, source + ' _getPubs1') - if (typeof tmp.data === 'undefined' || typeof tmp.data.plainAddresses === 'undefined') continue - if (res === false || typeof res.plainAddresses === 'undefined') { - res = JSON.parse(JSON.stringify(tmp.data)) - } else { - if (withBalances) { - for (const row in tmp.data.addresses) { - res.addresses[row] = tmp.data.addresses[row] - } - } else { - for (const row in tmp.data.plainAddresses) { - res.plainAddresses[row] = tmp.data.plainAddresses[row] - } - } - } - - } - } - } catch (e) { - if (config.debug.cryptoErrors) { - console.log(this._settings.currencyCode + ' BtcScannerProcessor.getAddresses load from all addresses error ' + e.message, e) - } - BlocksoftCryptoLog.log(this._settings.currencyCode + ' BtcScannerProcessor.getAddresses load from all addresses error ' + e.message) - } - return withBalances ? res.addresses : res.plainAddresses + async getAddressesBlockchain(scanData, source = '') { + const address = scanData.account.address.trim(); + const data = scanData.additional; + const withBalances = + typeof scanData.withBalances !== 'undefined' && scanData.withBalances; + if (!withBalances) { + if (config.debug.cryptoErrors) { + console.log( + this._settings.currencyCode + + ' BtcScannerProcessor.getAddresses started withoutBalances (KSU!)', + address + ); + } + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' BtcScannerProcessor.getAddresses started withoutBalances (KSU!)', + address + ); + } else { + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' BtcScannerProcessor.getAddresses started withBalances', + address + ); } - - /** - * @param {string} scanData.account.address - * @param {*} scanData.additional - * @param {string} scanData.account.walletHash - * @return {Promise} - */ - async getTransactionsBlockchain(scanData, source = '') { - const address = scanData.account.address.trim() - const data = scanData.additional - - BlocksoftCryptoLog.log(this._settings.currencyCode + ' BtcScannerProcessor.getTransactions started ' + address) - let res = await this._get(address, data, source) - if (typeof res.data !== 'undefined') { - res = JSON.parse(JSON.stringify(res.data)) - } else { - res = false - } - try { - if (typeof data.walletPub !== 'undefined') { - const resPub = await this._getPubs(data.walletPub.walletHash) - for (const scanAddress in resPub) { - if (scanAddress === address) continue - const tmp = await this._get(scanAddress, data, source + ' _getPubs2') - if (typeof tmp.data === 'undefined' || typeof tmp.data.transactions === 'undefined') continue - if (res === false || typeof res.transactions === 'undefined') { - res = JSON.parse(JSON.stringify(tmp.data)) - } else { - for (const row of tmp.data.transactions) { - res.transactions.push(row) - } - } - } + let res = await this._get(address, data, source); + if (typeof res.data !== 'undefined') { + res = JSON.parse(JSON.stringify(res.data)); + } else { + res = false; + } + try { + if (typeof data.walletPub !== 'undefined') { + const resPub = await this._getPubs(data.walletPub.walletHash); + for (const scanAddress in resPub) { + if (scanAddress === address) continue; + const tmp = await this._get(scanAddress, data, source + ' _getPubs1'); + if ( + typeof tmp.data === 'undefined' || + typeof tmp.data.plainAddresses === 'undefined' + ) + continue; + if (res === false || typeof res.plainAddresses === 'undefined') { + res = JSON.parse(JSON.stringify(tmp.data)); + } else { + if (withBalances) { + for (const row in tmp.data.addresses) { + res.addresses[row] = tmp.data.addresses[row]; + } } else { - for (const scanAddress in data.addresses) { - if (scanAddress === address) continue - const tmp = await this._get(scanAddress, data, source + ' _getOnes2') - if (typeof tmp.data === 'undefined' || typeof tmp.data.transactions === 'undefined') continue - if (res === false || typeof res.transactions === 'undefined') { - res = tmp.data - } else { - for (const row of tmp.data.transactions) { - res.transactions.push(row) - } - } - } - } - } catch (e) { - if (config.debug.cryptoErrors) { - console.log(this._settings.currencyCode + ' BtcScannerProcessor.getTransactions load from all addresses error ' + e.message, e) + for (const row in tmp.data.plainAddresses) { + res.plainAddresses[row] = tmp.data.plainAddresses[row]; + } } - BlocksoftCryptoLog.log(this._settings.currencyCode + ' BtcScannerProcessor.getTransactions load from all addresses error ' + e.message) + } } - BlocksoftCryptoLog.log(this._settings.currencyCode + ' BtcScannerProcessor.getTransactions loaded from ' + res.provider + ' ' + res.time) + } + } catch (e) { + if (config.debug.cryptoErrors) { + console.log( + this._settings.currencyCode + + ' BtcScannerProcessor.getAddresses load from all addresses error ' + + e.message, + e + ); + } + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' BtcScannerProcessor.getAddresses load from all addresses error ' + + e.message + ); + } + return withBalances ? res.addresses : res.plainAddresses; + } + + /** + * @param {string} scanData.account.address + * @param {*} scanData.additional + * @param {string} scanData.account.walletHash + * @return {Promise} + */ + async getTransactionsBlockchain(scanData, source = '') { + const address = scanData.account.address.trim(); + const data = scanData.additional; - if (typeof res.transactions === 'undefined' || !res.transactions) return [] - const transactions = [] - const addresses = res.plainAddresses - if (typeof data !== 'undefined' && data && typeof data.addresses !== 'undefined') { - for (const tmp in data.addresses) { - addresses[tmp] = data.addresses[tmp] + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' BtcScannerProcessor.getTransactions started ' + + address + ); + let res = await this._get(address, data, source); + if (typeof res.data !== 'undefined') { + res = JSON.parse(JSON.stringify(res.data)); + } else { + res = false; + } + try { + if (typeof data.walletPub !== 'undefined') { + const resPub = await this._getPubs(data.walletPub.walletHash); + for (const scanAddress in resPub) { + if (scanAddress === address) continue; + const tmp = await this._get(scanAddress, data, source + ' _getPubs2'); + if ( + typeof tmp.data === 'undefined' || + typeof tmp.data.transactions === 'undefined' + ) + continue; + if (res === false || typeof res.transactions === 'undefined') { + res = JSON.parse(JSON.stringify(tmp.data)); + } else { + for (const row of tmp.data.transactions) { + res.transactions.push(row); } + } } - if (typeof scanData.additional.addresses !== 'undefined') { - for (const tmp in scanData.additional.addresses) { - address[tmp] = tmp + } else { + for (const scanAddress in data.addresses) { + if (scanAddress === address) continue; + const tmp = await this._get(scanAddress, data, source + ' _getOnes2'); + if ( + typeof tmp.data === 'undefined' || + typeof tmp.data.transactions === 'undefined' + ) + continue; + if (res === false || typeof res.transactions === 'undefined') { + res = tmp.data; + } else { + for (const row of tmp.data.transactions) { + res.transactions.push(row); } + } } + } + } catch (e) { + if (config.debug.cryptoErrors) { + console.log( + this._settings.currencyCode + + ' BtcScannerProcessor.getTransactions load from all addresses error ' + + e.message, + e + ); + } + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' BtcScannerProcessor.getTransactions load from all addresses error ' + + e.message + ); + } + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' BtcScannerProcessor.getTransactions loaded from ' + + res.provider + + ' ' + + res.time + ); - const vinsOrder = {} - for (const tx of res.transactions) { - vinsOrder[tx.txid] = tx.blockTime - } - - let plussed = false - let i = 0 - do { - for (const tx of res.transactions) { - if (typeof tx.vin === 'undefined' || tx.vin.length === 0) continue - for (const vin of tx.vin) { - if (typeof vinsOrder[vin.txid] === 'undefined') { - continue - } - const newTime = vinsOrder[vin.txid] + 1 - if (tx.blockTime < newTime) { - tx.blockTime = newTime - plussed = true - } - vinsOrder[tx.txid] = tx.blockTime - } - } - i++ - } while (plussed && i < 100) + if (typeof res.transactions === 'undefined' || !res.transactions) return []; + const transactions = []; + const addresses = res.plainAddresses; + if ( + typeof data !== 'undefined' && + data && + typeof data.addresses !== 'undefined' + ) { + for (const tmp in data.addresses) { + addresses[tmp] = data.addresses[tmp]; + } + } + if (typeof scanData.additional.addresses !== 'undefined') { + for (const tmp in scanData.additional.addresses) { + address[tmp] = tmp; + } + } - const uniqueTxs = {} - for (const tx of res.transactions) { - const transaction = await this._unifyTransaction(address, addresses, tx) - if (transaction) { - if (typeof uniqueTxs[transaction.transactionHash] !== 'undefined') continue - uniqueTxs[transaction.transactionHash] = 1 - transactions.push(transaction) - } - } - BlocksoftCryptoLog.log(this._settings.currencyCode + ' BtcScannerProcessor.getTransactions finished ' + address + ' total: ' + transactions.length) - return transactions + const vinsOrder = {}; + for (const tx of res.transactions) { + vinsOrder[tx.txid] = tx.blockTime; } - /** - * - * @param {string} address - * @param {string} addresses - * @param {Object} transaction - * @param {string} transaction.txid c6b4c3879196857bed7fd5b553dd0049486c032d6a1be72b98fda967ca54b2da - * @param {string} transaction.version 1 - * @param {string} transaction.vin[].txid aa31777a9db759f57fd243ef47419939f233d16bc3e535e9a1c5af3ace87cb54 - * @param {string} transaction.vin[].sequence 4294967294 - * @param {string} transaction.vin[].n 0 - * @param {string} transaction.vin[].addresses [ 'DFDn5QyHH9DiFBNFGMcyJT5uUpDvmBRDqH' ] - * @param {string} transaction.vin[].value 44400000000 - * @param {string} transaction.vin[].hex 47304402200826f97d3432452abedd4346553de0b0c2d401ad7056b155e6462484afd98aa902202b5fb3166b96ded33249aecad7c667c0870c1 - * @param {string} transaction.vout[].value 59999824800 - * @param {string} transaction.vout[].n 0 - * @param {string} transaction.vout[].spent true - * @param {string} transaction.vout[].hex 76a91456d49605503d4770cf1f32fbfb69676d9a72554f88ac - * @param {string} transaction.vout[].addresses [ 'DD4DKVTEkRUGs7qzN8b7q5LKmoE9mXsJk4' ] - * @param {string} transaction.blockHash fc590834c04812e1c7818024a94021e12c4d8ab905724b4a4fdb4d4732878f69 - * @param {string} transaction.blockHeight 3036225 - * @param {string} transaction.confirmations 8568 - * @param {string} transaction.blockTime 1577362993 - * @param {string} transaction.value 59999917700 - * @param {string} transaction.valueIn 59999917700 - * @param {string} transaction.fees 0 - * @param {string} transaction.hex 010000000654cb87ce3aafc5a1e935e5c36bd133f239 - * @return {Promise} - * @private - */ - async _unifyTransaction(address, addresses, transaction) { - let showAddresses = false - try { - showAddresses = await BtcFindAddressFunction(addresses, transaction) - } catch (e) { - e.message += ' transaction hash ' + JSON.stringify(transaction) + ' address ' + address - throw e + let plussed = false; + let i = 0; + do { + for (const tx of res.transactions) { + if (typeof tx.vin === 'undefined' || tx.vin.length === 0) continue; + for (const vin of tx.vin) { + if (typeof vinsOrder[vin.txid] === 'undefined') { + continue; + } + const newTime = vinsOrder[vin.txid] + 1; + if (tx.blockTime < newTime) { + tx.blockTime = newTime; + plussed = true; + } + vinsOrder[tx.txid] = tx.blockTime; } + } + i++; + } while (plussed && i < 100); - let transactionStatus = 'new' - if (transaction.confirmations >= this._blocksToConfirm) { - transactionStatus = 'success' - } else if (transaction.confirmations > 0) { - transactionStatus = 'confirming' - } + const uniqueTxs = {}; + for (const tx of res.transactions) { + const transaction = await this._unifyTransaction(address, addresses, tx); + if (transaction) { + if (typeof uniqueTxs[transaction.transactionHash] !== 'undefined') + continue; + uniqueTxs[transaction.transactionHash] = 1; + transactions.push(transaction); + } + } + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' BtcScannerProcessor.getTransactions finished ' + + address + + ' total: ' + + transactions.length + ); + return transactions; + } - let transactionFilterType = TransactionFilterTypeDict.USUAL - if (typeof showAddresses.to !== 'undefined' && showAddresses.to.toLowerCase().indexOf('simple send') !== -1) { - transactionFilterType = TransactionFilterTypeDict.FEE - } + /** + * + * @param {string} address + * @param {string} addresses + * @param {Object} transaction + * @param {string} transaction.txid c6b4c3879196857bed7fd5b553dd0049486c032d6a1be72b98fda967ca54b2da + * @param {string} transaction.version 1 + * @param {string} transaction.vin[].txid aa31777a9db759f57fd243ef47419939f233d16bc3e535e9a1c5af3ace87cb54 + * @param {string} transaction.vin[].sequence 4294967294 + * @param {string} transaction.vin[].n 0 + * @param {string} transaction.vin[].addresses [ 'DFDn5QyHH9DiFBNFGMcyJT5uUpDvmBRDqH' ] + * @param {string} transaction.vin[].value 44400000000 + * @param {string} transaction.vin[].hex 47304402200826f97d3432452abedd4346553de0b0c2d401ad7056b155e6462484afd98aa902202b5fb3166b96ded33249aecad7c667c0870c1 + * @param {string} transaction.vout[].value 59999824800 + * @param {string} transaction.vout[].n 0 + * @param {string} transaction.vout[].spent true + * @param {string} transaction.vout[].hex 76a91456d49605503d4770cf1f32fbfb69676d9a72554f88ac + * @param {string} transaction.vout[].addresses [ 'DD4DKVTEkRUGs7qzN8b7q5LKmoE9mXsJk4' ] + * @param {string} transaction.blockHash fc590834c04812e1c7818024a94021e12c4d8ab905724b4a4fdb4d4732878f69 + * @param {string} transaction.blockHeight 3036225 + * @param {string} transaction.confirmations 8568 + * @param {string} transaction.blockTime 1577362993 + * @param {string} transaction.value 59999917700 + * @param {string} transaction.valueIn 59999917700 + * @param {string} transaction.fees 0 + * @param {string} transaction.hex 010000000654cb87ce3aafc5a1e935e5c36bd133f239 + * @return {Promise} + * @private + */ + async _unifyTransaction(address, addresses, transaction) { + let showAddresses = false; + try { + showAddresses = await BtcFindAddressFunction(addresses, transaction); + } catch (e) { + e.message += + ' transaction hash ' + + JSON.stringify(transaction) + + ' address ' + + address; + throw e; + } - let formattedTime - try { - formattedTime = BlocksoftUtils.toDate(transaction.blockTime) - } catch (e) { - e.message += ' timestamp error transaction data ' + JSON.stringify(transaction) - throw e - } + let transactionStatus = 'new'; + if (transaction.confirmations >= this._blocksToConfirm) { + transactionStatus = 'success'; + } else if (transaction.confirmations > 0) { + transactionStatus = 'confirming'; + } - return { - transactionHash: transaction.txid, - blockHash: transaction.blockHash, - blockNumber: +transaction.blockHeight, - blockTime: formattedTime, - blockConfirmations: transaction.confirmations, - transactionDirection: showAddresses.direction, - addressFrom: showAddresses.from, - addressTo: showAddresses.to, - addressAmount: showAddresses.value, - transactionStatus: transactionStatus, - transactionFee: transaction.fees, - transactionFilterType - } + let transactionFilterType = TransactionFilterTypeDict.USUAL; + if ( + typeof showAddresses.to !== 'undefined' && + showAddresses.to.toLowerCase().indexOf('simple send') !== -1 + ) { + transactionFilterType = TransactionFilterTypeDict.FEE; + } + + let formattedTime; + try { + formattedTime = BlocksoftUtils.toDate(transaction.blockTime); + } catch (e) { + e.message += + ' timestamp error transaction data ' + JSON.stringify(transaction); + throw e; } + + return { + transactionHash: transaction.txid, + blockHash: transaction.blockHash, + blockNumber: +transaction.blockHeight, + blockTime: formattedTime, + blockConfirmations: transaction.confirmations, + transactionDirection: showAddresses.direction, + addressFrom: showAddresses.from, + addressTo: showAddresses.to, + addressAmount: showAddresses.value, + transactionStatus: transactionStatus, + transactionFee: transaction.fees, + transactionFilterType + }; + } } diff --git a/crypto/blockchains/btc/basic/BtcNetworkPrices.ts b/crypto/blockchains/btc/basic/BtcNetworkPrices.ts index 0ea27bd46..120168ef6 100644 --- a/crypto/blockchains/btc/basic/BtcNetworkPrices.ts +++ b/crypto/blockchains/btc/basic/BtcNetworkPrices.ts @@ -1,245 +1,336 @@ /** * @version 0.20 **/ -import { BlocksoftBlockchainTypes } from '../../BlocksoftBlockchainTypes' -import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog' -import BlocksoftAxios from '../../../common/BlocksoftAxios' -import BlocksoftExternalSettings from '../../../common/BlocksoftExternalSettings' +import { BlocksoftBlockchainTypes } from '../../BlocksoftBlockchainTypes'; +import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; +import BlocksoftAxios from '../../../common/BlocksoftAxios'; +import BlocksoftExternalSettings from '../../../common/BlocksoftExternalSettings'; -import MarketingEvent from '../../../../app/services/Marketing/MarketingEvent' -import BlocksoftUtils from '../../../common/BlocksoftUtils' +import MarketingEvent from '../../../../app/services/Marketing/MarketingEvent'; +import BlocksoftUtils from '../../../common/BlocksoftUtils'; -const ESTIMATE_PATH = 'https://mempool.space/api/v1/fees/recommended' +const ESTIMATE_PATH = 'https://mempool.space/api/v1/fees/recommended'; -const CACHE_VALID_TIME = 60000 // 1 minute +const CACHE_VALID_TIME = 60000; // 1 minute -let CACHE_FEES_BTC_TIME = 0 +let CACHE_FEES_BTC_TIME = 0; let CACHE_FEES_BTC = { - 'speed_blocks_2': 0, - 'speed_blocks_6': 0, - 'speed_blocks_12': 0 -} + speed_blocks_2: 0, + speed_blocks_6: 0, + speed_blocks_12: 0 +}; let CACHE_PREV_DATA = { - fastestFee: 19, - halfHourFee: 3, - hourFee: 2, - lastBlock: 0, - timeFromBlock: 0, - timeFromBlockDiff: 0, - mempoolSize: 0 -} + fastestFee: 19, + halfHourFee: 3, + hourFee: 2, + lastBlock: 0, + timeFromBlock: 0, + timeFromBlockDiff: 0, + mempoolSize: 0 +}; let CACHE_PREV_PREV_DATA = { - fastestFee: 19, - halfHourFee: 3, - hourFee: 2, - lastBlock: 0, - timeFromBlock: 0, - timeFromBlockDiff: 0, - mempoolSize: 0 -} + fastestFee: 19, + halfHourFee: 3, + hourFee: 2, + lastBlock: 0, + timeFromBlock: 0, + timeFromBlockDiff: 0, + mempoolSize: 0 +}; + +export default class BtcNetworkPrices + implements BlocksoftBlockchainTypes.NetworkPrices +{ + private _trezorServerCode = 'BTC_TREZOR_SERVER'; + private _trezorServer: any; + async getNetworkPrices(currencyCode: string): Promise<{ + speed_blocks_2: number; + speed_blocks_6: number; + speed_blocks_12: number; + }> { + BlocksoftCryptoLog.log('BtcNetworkPricesProvider ' + currencyCode); + const logData = { + currencyCode, + source: 'fromCache', + cacheTime: CACHE_FEES_BTC_TIME + '', + fee: JSON.stringify(CACHE_FEES_BTC) + }; + const now = new Date().getTime(); + if (CACHE_FEES_BTC && now - CACHE_FEES_BTC_TIME < CACHE_VALID_TIME) { + // noinspection ES6MissingAwait + MarketingEvent.logEvent('estimate_fee_btc_result', logData); + // @ts-ignore + BlocksoftCryptoLog.log( + 'BtcNetworkPricesProvider ' + + currencyCode + + ' used cache ' + + JSON.stringify(CACHE_FEES_BTC) + ); + return CACHE_FEES_BTC; + } -export default class BtcNetworkPrices implements BlocksoftBlockchainTypes.NetworkPrices { + BlocksoftCryptoLog.log( + 'BtcNetworkPricesProvider ' + currencyCode + ' no cache load' + ); - private _trezorServerCode = 'BTC_TREZOR_SERVER' - private _trezorServer: any + let link = `${ESTIMATE_PATH}`; + let timeFromBlock = false; + let timeFromBlockDiff = false; + let lastBlock = 0; + let mempoolSize = 0; + if (currencyCode !== 'BTC_TEST') { + this._trezorServer = await BlocksoftExternalSettings.getTrezorServer( + this._trezorServerCode, + 'BTC.NetworkPrices' + ); + const linkTrezor = this._trezorServer + '/api/'; - async getNetworkPrices(currencyCode: string): Promise<{ 'speed_blocks_2': number, 'speed_blocks_6': number, 'speed_blocks_12': number }> { - BlocksoftCryptoLog.log('BtcNetworkPricesProvider ' + currencyCode) - const logData = { - currencyCode, - source: 'fromCache', - cacheTime: CACHE_FEES_BTC_TIME + '', - fee: JSON.stringify(CACHE_FEES_BTC) - } - const now = new Date().getTime() - if (CACHE_FEES_BTC && now - CACHE_FEES_BTC_TIME < CACHE_VALID_TIME) { - // noinspection ES6MissingAwait - MarketingEvent.logEvent('estimate_fee_btc_result', logData) - // @ts-ignore - BlocksoftCryptoLog.log('BtcNetworkPricesProvider ' + currencyCode + ' used cache ' + JSON.stringify(CACHE_FEES_BTC)) - return CACHE_FEES_BTC + let tmp = false; + try { + tmp = await BlocksoftAxios.getWithoutBraking(linkTrezor); + if (tmp && tmp.data) { + lastBlock = tmp.data.blockbook.bestHeight; + mempoolSize = tmp.data.blockbook.mempoolSize; + timeFromBlock = tmp.data.blockbook.lastBlockTime; + timeFromBlockDiff = now - new Date(timeFromBlock).getTime(); } + } catch (e) {} - BlocksoftCryptoLog.log('BtcNetworkPricesProvider ' + currencyCode + ' no cache load') - - let link = `${ESTIMATE_PATH}` - let timeFromBlock = false - let timeFromBlockDiff = false - let lastBlock = 0 - let mempoolSize = 0 - if (currencyCode !== 'BTC_TEST') { - - this._trezorServer = await BlocksoftExternalSettings.getTrezorServer(this._trezorServerCode, 'BTC.NetworkPrices') - const linkTrezor = this._trezorServer + '/api/' - - let tmp = false - try { - tmp = await BlocksoftAxios.getWithoutBraking(linkTrezor) - if (tmp && tmp.data) { - lastBlock = tmp.data.blockbook.bestHeight - mempoolSize = tmp.data.blockbook.mempoolSize - timeFromBlock = tmp.data.blockbook.lastBlockTime - timeFromBlockDiff = now - new Date(timeFromBlock).getTime() - } - } catch (e) { - - } - - BlocksoftCryptoLog.log('BtcNetworkPricesProvider lastBlock ' + lastBlock + ' mempool ' + mempoolSize + ' timeFromBlock ' + timeFromBlock + ' diff ' + timeFromBlockDiff) - - tmp = false - try { - tmp = await BlocksoftAxios.getWithoutBraking(link) - if (tmp && tmp.data) { - logData.source = 'reloaded' - if (lastBlock > CACHE_PREV_DATA.lastBlock) { - CACHE_PREV_PREV_DATA = CACHE_PREV_DATA - } - CACHE_PREV_DATA = tmp.data - CACHE_PREV_DATA.lastBlock = lastBlock - CACHE_PREV_DATA.mempoolSize = mempoolSize - CACHE_PREV_DATA.timeFromBlock = timeFromBlock - CACHE_PREV_DATA.timeFromBlockDiff = timeFromBlockDiff - } else { - logData.source = 'fromLoadCache' - link = 'prev' - } - } catch (e) { - // noinspection ES6MissingAwait - MarketingEvent.logEvent('estimate_fee_btc_load_error', { currencyCode, link, data: e.toString() }) - // do nothing - } - } - // @ts-ignore - BlocksoftCryptoLog.log('BtcNetworkPricesProvider CACHE_FEES', { - CACHE_PREV_DATA, - CACHE_PREV_PREV_DATA, ...logData - }) - - try { - const cachedWithTime = CACHE_PREV_DATA - if (timeFromBlock) { - if (timeFromBlockDiff < 1000 && cachedWithTime.fastestFee === '4') { // 1 minute from block - if (cachedWithTime.fastestFee < CACHE_PREV_PREV_DATA.fastestFee) { - cachedWithTime.fastestFee = CACHE_PREV_PREV_DATA.fastestFee - // @ts-ignore - BlocksoftCryptoLog.log('BtcNetworkPricesProvider change as block 1 minute ago and fastest no ok - used prev ', CACHE_PREV_PREV_DATA) - } else if (CACHE_PREV_DATA.mempoolSize < 10000) { - cachedWithTime.fastestFee = cachedWithTime.fastestFee * 1.5 - BlocksoftCryptoLog.log('BtcNetworkPricesProvider change as block 1 minute ago and fastest no ok - mempool is small') - } else { - cachedWithTime.fastestFee = cachedWithTime.fastestFee * 3 - BlocksoftCryptoLog.log('BtcNetworkPricesProvider change as block 1 minute ago and fastest no ok - mempool is ok') - } - } else if (timeFromBlockDiff < 5000) { // 2 minute from block - if (cachedWithTime.fastestFee < CACHE_PREV_PREV_DATA.fastestFee) { - cachedWithTime.fastestFee = CACHE_PREV_PREV_DATA.fastestFee - // @ts-ignore - BlocksoftCryptoLog.log('BtcNetworkPricesProvider change as block 5 minute ago and fastest no ok - used prev ', CACHE_PREV_PREV_DATA) - } - } - } - await this._parseLoaded(currencyCode, cachedWithTime, link) - } catch (e) { - // noinspection ES6MissingAwait - MarketingEvent.logEvent('estimate_fee_btc_parse_error', { currencyCode, link, data: e.toString() }) - // do nothing + BlocksoftCryptoLog.log( + 'BtcNetworkPricesProvider lastBlock ' + + lastBlock + + ' mempool ' + + mempoolSize + + ' timeFromBlock ' + + timeFromBlock + + ' diff ' + + timeFromBlockDiff + ); + + tmp = false; + try { + tmp = await BlocksoftAxios.getWithoutBraking(link); + if (tmp && tmp.data) { + logData.source = 'reloaded'; + if (lastBlock > CACHE_PREV_DATA.lastBlock) { + CACHE_PREV_PREV_DATA = CACHE_PREV_DATA; + } + CACHE_PREV_DATA = tmp.data; + CACHE_PREV_DATA.lastBlock = lastBlock; + CACHE_PREV_DATA.mempoolSize = mempoolSize; + CACHE_PREV_DATA.timeFromBlock = timeFromBlock; + CACHE_PREV_DATA.timeFromBlockDiff = timeFromBlockDiff; + } else { + logData.source = 'fromLoadCache'; + link = 'prev'; } + } catch (e) { // noinspection ES6MissingAwait - MarketingEvent.logEvent('estimate_fee_btc_result', logData) - return CACHE_FEES_BTC + MarketingEvent.logEvent('estimate_fee_btc_load_error', { + currencyCode, + link, + data: e.toString() + }); + // do nothing + } } + // @ts-ignore + BlocksoftCryptoLog.log('BtcNetworkPricesProvider CACHE_FEES', { + CACHE_PREV_DATA, + CACHE_PREV_PREV_DATA, + ...logData + }); - async _parseLoaded(currencyCode: string, json: { fastestFee: any; halfHourFee: any; hourFee: any; lastBlock?: number; timeFromBlock?: number; timeFromBlockDiff?: number; mempoolSize?: number }, link: string) { - - CACHE_FEES_BTC = { - 'speed_blocks_2': 0, - 'speed_blocks_6': 0, - 'speed_blocks_12': 0 + try { + const cachedWithTime = CACHE_PREV_DATA; + if (timeFromBlock) { + if (timeFromBlockDiff < 1000 && cachedWithTime.fastestFee === '4') { + // 1 minute from block + if (cachedWithTime.fastestFee < CACHE_PREV_PREV_DATA.fastestFee) { + cachedWithTime.fastestFee = CACHE_PREV_PREV_DATA.fastestFee; + // @ts-ignore + BlocksoftCryptoLog.log( + 'BtcNetworkPricesProvider change as block 1 minute ago and fastest no ok - used prev ', + CACHE_PREV_PREV_DATA + ); + } else if (CACHE_PREV_DATA.mempoolSize < 10000) { + cachedWithTime.fastestFee = cachedWithTime.fastestFee * 1.5; + BlocksoftCryptoLog.log( + 'BtcNetworkPricesProvider change as block 1 minute ago and fastest no ok - mempool is small' + ); + } else { + cachedWithTime.fastestFee = cachedWithTime.fastestFee * 3; + BlocksoftCryptoLog.log( + 'BtcNetworkPricesProvider change as block 1 minute ago and fastest no ok - mempool is ok' + ); + } + } else if (timeFromBlockDiff < 5000) { + // 2 minute from block + if (cachedWithTime.fastestFee < CACHE_PREV_PREV_DATA.fastestFee) { + cachedWithTime.fastestFee = CACHE_PREV_PREV_DATA.fastestFee; + // @ts-ignore + BlocksoftCryptoLog.log( + 'BtcNetworkPricesProvider change as block 5 minute ago and fastest no ok - used prev ', + CACHE_PREV_PREV_DATA + ); + } } + } + await this._parseLoaded(currencyCode, cachedWithTime, link); + } catch (e) { + // noinspection ES6MissingAwait + MarketingEvent.logEvent('estimate_fee_btc_parse_error', { + currencyCode, + link, + data: e.toString() + }); + // do nothing + } + // noinspection ES6MissingAwait + MarketingEvent.logEvent('estimate_fee_btc_result', logData); + return CACHE_FEES_BTC; + } - const externalSettings = await BlocksoftExternalSettings.getAll('BTC.getNetworkPrices') - addMultiply(2, json.fastestFee * 1, externalSettings) - addMultiply(6, json.halfHourFee * 1, externalSettings) - addMultiply(12, json.hourFee * 1, externalSettings) + async _parseLoaded( + currencyCode: string, + json: { + fastestFee: any; + halfHourFee: any; + hourFee: any; + lastBlock?: number; + timeFromBlock?: number; + timeFromBlockDiff?: number; + mempoolSize?: number; + }, + link: string + ) { + CACHE_FEES_BTC = { + speed_blocks_2: 0, + speed_blocks_6: 0, + speed_blocks_12: 0 + }; - if (CACHE_FEES_BTC.speed_blocks_2 === 1) { - CACHE_FEES_BTC.speed_blocks_2 = 4 - } - if (CACHE_FEES_BTC.speed_blocks_6 === CACHE_FEES_BTC.speed_blocks_2) { - if (CACHE_FEES_BTC.speed_blocks_12 === CACHE_FEES_BTC.speed_blocks_6) { - CACHE_FEES_BTC.speed_blocks_6 = Math.round(CACHE_FEES_BTC.speed_blocks_6 / 2) - CACHE_FEES_BTC.speed_blocks_12 = Math.round(CACHE_FEES_BTC.speed_blocks_6 / 2) - } else { - CACHE_FEES_BTC.speed_blocks_6 = Math.round(CACHE_FEES_BTC.speed_blocks_2 / 2) - } - } else if (CACHE_FEES_BTC.speed_blocks_12 === CACHE_FEES_BTC.speed_blocks_6) { - CACHE_FEES_BTC.speed_blocks_12 = Math.round(CACHE_FEES_BTC.speed_blocks_6 / 2) - } + const externalSettings = await BlocksoftExternalSettings.getAll( + 'BTC.getNetworkPrices' + ); + addMultiply(2, json.fastestFee * 1, externalSettings); + addMultiply(6, json.halfHourFee * 1, externalSettings); + addMultiply(12, json.hourFee * 1, externalSettings); + if (CACHE_FEES_BTC.speed_blocks_2 === 1) { + CACHE_FEES_BTC.speed_blocks_2 = 4; + } + if (CACHE_FEES_BTC.speed_blocks_6 === CACHE_FEES_BTC.speed_blocks_2) { + if (CACHE_FEES_BTC.speed_blocks_12 === CACHE_FEES_BTC.speed_blocks_6) { + CACHE_FEES_BTC.speed_blocks_6 = Math.round( + CACHE_FEES_BTC.speed_blocks_6 / 2 + ); + CACHE_FEES_BTC.speed_blocks_12 = Math.round( + CACHE_FEES_BTC.speed_blocks_6 / 2 + ); + } else { + CACHE_FEES_BTC.speed_blocks_6 = Math.round( + CACHE_FEES_BTC.speed_blocks_2 / 2 + ); + } + } else if ( + CACHE_FEES_BTC.speed_blocks_12 === CACHE_FEES_BTC.speed_blocks_6 + ) { + CACHE_FEES_BTC.speed_blocks_12 = Math.round( + CACHE_FEES_BTC.speed_blocks_6 / 2 + ); + } - if (CACHE_FEES_BTC.speed_blocks_12 === 0 || CACHE_FEES_BTC.speed_blocks_6 === 1) { - CACHE_FEES_BTC.speed_blocks_12 = 1 - if (CACHE_FEES_BTC.speed_blocks_6 === 1) { - CACHE_FEES_BTC.speed_blocks_6 = 2 - } - } + if ( + CACHE_FEES_BTC.speed_blocks_12 === 0 || + CACHE_FEES_BTC.speed_blocks_6 === 1 + ) { + CACHE_FEES_BTC.speed_blocks_12 = 1; + if (CACHE_FEES_BTC.speed_blocks_6 === 1) { + CACHE_FEES_BTC.speed_blocks_6 = 2; + } + } - if (CACHE_FEES_BTC.speed_blocks_6 < CACHE_FEES_BTC.speed_blocks_12) { - const t = CACHE_FEES_BTC.speed_blocks_6 - CACHE_FEES_BTC.speed_blocks_6 = CACHE_FEES_BTC.speed_blocks_12 - CACHE_FEES_BTC.speed_blocks_12 = t - } + if (CACHE_FEES_BTC.speed_blocks_6 < CACHE_FEES_BTC.speed_blocks_12) { + const t = CACHE_FEES_BTC.speed_blocks_6; + CACHE_FEES_BTC.speed_blocks_6 = CACHE_FEES_BTC.speed_blocks_12; + CACHE_FEES_BTC.speed_blocks_12 = t; + } - if (CACHE_FEES_BTC.speed_blocks_2 < CACHE_FEES_BTC.speed_blocks_6) { - const t = CACHE_FEES_BTC.speed_blocks_6 - CACHE_FEES_BTC.speed_blocks_6 = CACHE_FEES_BTC.speed_blocks_2 - CACHE_FEES_BTC.speed_blocks_2 = t - } + if (CACHE_FEES_BTC.speed_blocks_2 < CACHE_FEES_BTC.speed_blocks_6) { + const t = CACHE_FEES_BTC.speed_blocks_6; + CACHE_FEES_BTC.speed_blocks_6 = CACHE_FEES_BTC.speed_blocks_2; + CACHE_FEES_BTC.speed_blocks_2 = t; + } - if (CACHE_FEES_BTC.speed_blocks_6 < CACHE_FEES_BTC.speed_blocks_12) { - const t = CACHE_FEES_BTC.speed_blocks_6 - CACHE_FEES_BTC.speed_blocks_6 = CACHE_FEES_BTC.speed_blocks_12 - CACHE_FEES_BTC.speed_blocks_12 = t - } + if (CACHE_FEES_BTC.speed_blocks_6 < CACHE_FEES_BTC.speed_blocks_12) { + const t = CACHE_FEES_BTC.speed_blocks_6; + CACHE_FEES_BTC.speed_blocks_6 = CACHE_FEES_BTC.speed_blocks_12; + CACHE_FEES_BTC.speed_blocks_12 = t; + } - CACHE_FEES_BTC_TIME = new Date().getTime() - if (CACHE_FEES_BTC.speed_blocks_2 > 0) { - BlocksoftCryptoLog.log('BtcNetworkPricesProvider ' + currencyCode + ' new cache fees', CACHE_FEES_BTC) - } else { - // noinspection ES6MissingAwait - MarketingEvent.logEvent('estimate_fee_btc_error', { currencyCode, link, json, externalSettings }) - } + CACHE_FEES_BTC_TIME = new Date().getTime(); + if (CACHE_FEES_BTC.speed_blocks_2 > 0) { + BlocksoftCryptoLog.log( + 'BtcNetworkPricesProvider ' + currencyCode + ' new cache fees', + CACHE_FEES_BTC + ); + } else { + // noinspection ES6MissingAwait + MarketingEvent.logEvent('estimate_fee_btc_error', { + currencyCode, + link, + json, + externalSettings + }); } + } } function addMultiply(blocks, fee, externalSettings) { - const key = 'speed_blocks_' + blocks - if (typeof externalSettings['BTC_CURRENT_PRICE_' + blocks] !== 'undefined' && externalSettings['BTC_CURRENT_PRICE_' + blocks] > 0) { - CACHE_FEES_BTC[key] = externalSettings['BTC_CURRENT_PRICE_' + blocks] - } else if (typeof externalSettings['BTC_MULTI_V3_' + blocks] !== 'undefined' && externalSettings['BTC_MULTI_V3_' + blocks] > 0) { - CACHE_FEES_BTC[key] = BlocksoftUtils.mul(fee, externalSettings['BTC_MULTI_V3_' + blocks]) * 1 - } else if (typeof externalSettings.BTC_MULTI_V3 !== 'undefined' && externalSettings.BTC_MULTI_V3 > 0) { - CACHE_FEES_BTC[key] = BlocksoftUtils.mul(fee, externalSettings.BTC_MULTI_V3) * 1 - BlocksoftCryptoLog.log('BtcNetworkPricesProvider addMultiply result', { - blocks, - fee, - mul: externalSettings.BTC_MULTI_V3, - res: CACHE_FEES_BTC[key] - }) - } else { - CACHE_FEES_BTC[key] = fee + const key = 'speed_blocks_' + blocks; + if ( + typeof externalSettings['BTC_CURRENT_PRICE_' + blocks] !== 'undefined' && + externalSettings['BTC_CURRENT_PRICE_' + blocks] > 0 + ) { + CACHE_FEES_BTC[key] = externalSettings['BTC_CURRENT_PRICE_' + blocks]; + } else if ( + typeof externalSettings['BTC_MULTI_V3_' + blocks] !== 'undefined' && + externalSettings['BTC_MULTI_V3_' + blocks] > 0 + ) { + CACHE_FEES_BTC[key] = + BlocksoftUtils.mul(fee, externalSettings['BTC_MULTI_V3_' + blocks]) * 1; + } else if ( + typeof externalSettings.BTC_MULTI_V3 !== 'undefined' && + externalSettings.BTC_MULTI_V3 > 0 + ) { + CACHE_FEES_BTC[key] = + BlocksoftUtils.mul(fee, externalSettings.BTC_MULTI_V3) * 1; + BlocksoftCryptoLog.log('BtcNetworkPricesProvider addMultiply result', { + blocks, + fee, + mul: externalSettings.BTC_MULTI_V3, + res: CACHE_FEES_BTC[key] + }); + } else { + CACHE_FEES_BTC[key] = fee; + } + if ( + typeof externalSettings['BTC_MIN_' + blocks] !== 'undefined' && + externalSettings['BTC_MIN_' + blocks] > 0 + ) { + if (externalSettings['BTC_MIN_' + blocks] > CACHE_FEES_BTC[key]) { + CACHE_FEES_BTC[key] = externalSettings['BTC_MIN_' + blocks]; } - if (typeof externalSettings['BTC_MIN_' + blocks] !== 'undefined' && externalSettings['BTC_MIN_' + blocks] > 0) { - if (externalSettings['BTC_MIN_' + blocks] > CACHE_FEES_BTC[key]) { - CACHE_FEES_BTC[key] = externalSettings['BTC_MIN_' + blocks] - } - } else if (typeof externalSettings.BTC_MIN !== 'undefined' && externalSettings.BTC_MIN > 0) { - if (externalSettings.BTC_MIN > CACHE_FEES_BTC[key]) { - CACHE_FEES_BTC[key] = externalSettings.BTC_MIN - } + } else if ( + typeof externalSettings.BTC_MIN !== 'undefined' && + externalSettings.BTC_MIN > 0 + ) { + if (externalSettings.BTC_MIN > CACHE_FEES_BTC[key]) { + CACHE_FEES_BTC[key] = externalSettings.BTC_MIN; } + } } diff --git a/crypto/blockchains/btc_test/BtcTestScannerProcessor.js b/crypto/blockchains/btc_test/BtcTestScannerProcessor.js index cabd65cb9..ac9451c98 100644 --- a/crypto/blockchains/btc_test/BtcTestScannerProcessor.js +++ b/crypto/blockchains/btc_test/BtcTestScannerProcessor.js @@ -2,113 +2,135 @@ * @version 0.52 * https://github.com/Blockstream/esplora/blob/master/API.md */ -import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog' -import BlocksoftAxios from '@crypto/common/BlocksoftAxios' -import BlocksoftUtils from '@crypto/common/BlocksoftUtils' -import DogeFindAddressFunction from '@crypto/blockchains/doge/basic/DogeFindAddressFunction' +import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; +import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; +import BlocksoftUtils from '@crypto/common/BlocksoftUtils'; +import DogeFindAddressFunction from '@crypto/blockchains/doge/basic/DogeFindAddressFunction'; -const API_PATH = 'https://blockstream.info/testnet/api/' +const API_PATH = 'https://blockstream.info/testnet/api/'; export default class BtcTestScannerProcessor { - - - /* - * https://blockstream.info/testnet/api/address/mtU4mYXfBRiTx1iUBWcCvUTr4CgRnRALaL - * @param {string} address - * @return {Promise<{int:balance, int:provider}>} - */ - async getBalanceBlockchain(address) { - BlocksoftCryptoLog.log('BtcTestScannerProcessor.getBalance started ' + address) - const res = await BlocksoftAxios.getWithoutBraking(API_PATH + 'address/' + address) - // console.log('res', res.data.chain_stats.funded_txo_sum) // spent_txo_sum - if (!res || typeof res.data === 'undefined' || typeof res.data.chain_stats === 'undefined' || typeof res.data.chain_stats.funded_txo_sum === 'undefined') { - return false - } - return { balance: res.data.chain_stats.funded_txo_sum, unconfirmed: 0, provider: 'blockstream.info' } + /* + * https://blockstream.info/testnet/api/address/mtU4mYXfBRiTx1iUBWcCvUTr4CgRnRALaL + * @param {string} address + * @return {Promise<{int:balance, int:provider}>} + */ + async getBalanceBlockchain(address) { + BlocksoftCryptoLog.log( + 'BtcTestScannerProcessor.getBalance started ' + address + ); + const res = await BlocksoftAxios.getWithoutBraking( + API_PATH + 'address/' + address + ); + // console.log('res', res.data.chain_stats.funded_txo_sum) // spent_txo_sum + if ( + !res || + typeof res.data === 'undefined' || + typeof res.data.chain_stats === 'undefined' || + typeof res.data.chain_stats.funded_txo_sum === 'undefined' + ) { + return false; } + return { + balance: res.data.chain_stats.funded_txo_sum, + unconfirmed: 0, + provider: 'blockstream.info' + }; + } - /** - * https://blockstream.info/testnet/api/address/mtU4mYXfBRiTx1iUBWcCvUTr4CgRnRALaL/txs - * @param {string} scanData.account.address - * @return {Promise} - */ - async getTransactionsBlockchain(scanData) { - const address = scanData.account.address.trim() - BlocksoftCryptoLog.log('BtcTestScannerProcessor.getTransactions started ' + address) - const res = await BlocksoftAxios.getWithoutBraking(API_PATH + 'address/' + address + '/txs') - if (!res || typeof res.data === 'undefined' || !res.data) { - return [] - } - const transactions = [] - let tx - for (tx of res.data) { - const transaction = await this._unifyTransaction(address, tx) - transactions.push(transaction) - } - BlocksoftCryptoLog.log('BtcTestScannerProcessor.getTransactions finished ' + address + ' total: ' + transactions.length) - return transactions + /** + * https://blockstream.info/testnet/api/address/mtU4mYXfBRiTx1iUBWcCvUTr4CgRnRALaL/txs + * @param {string} scanData.account.address + * @return {Promise} + */ + async getTransactionsBlockchain(scanData) { + const address = scanData.account.address.trim(); + BlocksoftCryptoLog.log( + 'BtcTestScannerProcessor.getTransactions started ' + address + ); + const res = await BlocksoftAxios.getWithoutBraking( + API_PATH + 'address/' + address + '/txs' + ); + if (!res || typeof res.data === 'undefined' || !res.data) { + return []; } + const transactions = []; + let tx; + for (tx of res.data) { + const transaction = await this._unifyTransaction(address, tx); + transactions.push(transaction); + } + BlocksoftCryptoLog.log( + 'BtcTestScannerProcessor.getTransactions finished ' + + address + + ' total: ' + + transactions.length + ); + return transactions; + } - /** - * - * @param {string} address - * @param {Object} transaction - * @return {Promise} - * @private - */ - async _unifyTransaction(address, transaction) { - - let showAddresses = false + /** + * + * @param {string} address + * @param {Object} transaction + * @return {Promise} + * @private + */ + async _unifyTransaction(address, transaction) { + let showAddresses = false; + try { + showAddresses = await DogeFindAddressFunction([address], transaction); + } catch (e) { + e.message += + ' transaction hash ' + + JSON.stringify(transaction) + + ' address ' + + address; + throw e; + } + let transactionStatus = 'new'; + let blockConfirmations = 0; + let blockHash = ''; + let blockNumber = ''; + let formattedTime = 0; + if (typeof transaction.status !== 'undefined') { + if (typeof transaction.status.block_hash !== 'undefined') { + blockHash = transaction.status.block_hash; + } + if (typeof transaction.status.block_height !== 'undefined') { + blockNumber = transaction.status.block_height; + } + if (typeof transaction.status.confirmed !== 'undefined') { + if (transaction.status.confirmed) { + transactionStatus = 'success'; + blockConfirmations = 100; + } else { + transactionStatus = 'confirming'; + } + } + if (typeof transaction.status.block_time !== 'undefined') { try { - showAddresses = await DogeFindAddressFunction([address], transaction) + formattedTime = BlocksoftUtils.toDate(transaction.status.block_time); } catch (e) { - e.message += ' transaction hash ' + JSON.stringify(transaction) + ' address ' + address - throw e - } - let transactionStatus = 'new' - let blockConfirmations = 0 - let blockHash = '' - let blockNumber = '' - let formattedTime = 0 - if (typeof transaction.status !== 'undefined') { - if (typeof transaction.status.block_hash !== 'undefined') { - blockHash = transaction.status.block_hash - } - if (typeof transaction.status.block_height !== 'undefined') { - blockNumber = transaction.status.block_height - } - if (typeof transaction.status.confirmed !== 'undefined') { - if (transaction.status.confirmed) { - transactionStatus = 'success' - blockConfirmations = 100 - } else { - transactionStatus = 'confirming' - } - } - if (typeof transaction.status.block_time !== 'undefined') { - try { - formattedTime = BlocksoftUtils.toDate(transaction.status.block_time) - } catch (e) { - e.message += ' timestamp error transaction data ' + JSON.stringify(transaction) - throw e - } - } - } - - - - return { - transactionHash: transaction.txid, - blockHash, - blockNumber, - blockTime: formattedTime, - blockConfirmations, - transactionDirection: showAddresses.direction, - addressFrom: showAddresses.from, - addressTo: showAddresses.to, - addressAmount: showAddresses.value, - transactionStatus: transactionStatus, - transactionFee: transaction.fee + e.message += + ' timestamp error transaction data ' + JSON.stringify(transaction); + throw e; } + } } + + return { + transactionHash: transaction.txid, + blockHash, + blockNumber, + blockTime: formattedTime, + blockConfirmations, + transactionDirection: showAddresses.direction, + addressFrom: showAddresses.from, + addressTo: showAddresses.to, + addressAmount: showAddresses.value, + transactionStatus: transactionStatus, + transactionFee: transaction.fee + }; + } } diff --git a/crypto/blockchains/btc_test/providers/BtcTestSendProvider.ts b/crypto/blockchains/btc_test/providers/BtcTestSendProvider.ts index 1dffcdcf3..b6d140b1c 100644 --- a/crypto/blockchains/btc_test/providers/BtcTestSendProvider.ts +++ b/crypto/blockchains/btc_test/providers/BtcTestSendProvider.ts @@ -1,80 +1,117 @@ /** * @version 0.52 */ -import { BlocksoftBlockchainTypes } from '@crypto/blockchains/BlocksoftBlockchainTypes' +import { BlocksoftBlockchainTypes } from '@crypto/blockchains/BlocksoftBlockchainTypes'; -import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog' -import BlocksoftAxios from '@crypto/common/BlocksoftAxios' -import DogeSendProvider from '@crypto/blockchains/doge/providers/DogeSendProvider' -import config from '@app/config/config' +import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; +import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; +import DogeSendProvider from '@crypto/blockchains/doge/providers/DogeSendProvider'; +import config from '@app/config/config'; -const API_URL = 'https://api.blockchair.com/bitcoin/testnet/push/transaction' +const API_URL = 'https://api.blockchair.com/bitcoin/testnet/push/transaction'; -export default class BtcTestSendProvider extends DogeSendProvider implements BlocksoftBlockchainTypes.SendProvider { +export default class BtcTestSendProvider + extends DogeSendProvider + implements BlocksoftBlockchainTypes.SendProvider +{ + async sendTx( + hex: string, + subtitle: string, + txRBF: any, + logData: any + ): Promise<{ transactionHash: string; transactionJson: any; logData: any }> { + await BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' BtcTestSendProvider.sendTx ' + + subtitle + + ' started ', + logData + ); - async sendTx(hex: string, subtitle: string, txRBF : any, logData : any) : Promise<{transactionHash: string, transactionJson:any, logData: any}> { - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' BtcTestSendProvider.sendTx ' + subtitle + ' started ', logData) + // logData = await this._check(hex, subtitle, txRBF, logData) - // logData = await this._check(hex, subtitle, txRBF, logData) - - let res - try { - res = await BlocksoftAxios.post(API_URL, {data : hex}) - } catch (e) { - if (config.debug.cryptoErrors) { - console.log(this._settings.currencyCode + ' BtcTestSendProvider.sendTx error ', e) - } - try { - logData.error = e.message - // await this._checkError(hex, subtitle, txRBF, logData) - } catch (e2) { - if (config.debug.cryptoErrors) { - console.log(this._settings.currencyCode + ' BtcTestSendProvider.send proxy error errorTx ' + e.message) - } - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' BtcTestSendProvider.send proxy error errorTx ' + e2.message) - } - if (e.message.indexOf('transaction already in the mempool') !== -1 || e.message.indexOf('TXN-MEMPOOL-CONFLICT')) { - throw new Error('SERVER_RESPONSE_NO_RESPONSE') - } else if (e.message.indexOf('dust') !== -1) { - throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_DUST') - } else if (e.message.indexOf('bad-txns-inputs-spent') !== -1 || e.message.indexOf('txn-mempool-conflict') !== -1) { - throw new Error('SERVER_RESPONSE_NO_RESPONSE') - } else if (e.message.indexOf('fee for relay') !== -1 || e.message.indexOf('insufficient priority') !== -1) { - throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_FEE') - } else { - throw e - } - } - let txid = '' - // @ts-ignore - if (typeof res.data === 'undefined' || !res.data) { - throw new Error('SERVER_RESPONSE_NOT_CONNECTED') + let res; + try { + res = await BlocksoftAxios.post(API_URL, { data: hex }); + } catch (e) { + if (config.debug.cryptoErrors) { + console.log( + this._settings.currencyCode + ' BtcTestSendProvider.sendTx error ', + e + ); + } + try { + logData.error = e.message; + // await this._checkError(hex, subtitle, txRBF, logData) + } catch (e2) { + if (config.debug.cryptoErrors) { + console.log( + this._settings.currencyCode + + ' BtcTestSendProvider.send proxy error errorTx ' + + e.message + ); } + await BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' BtcTestSendProvider.send proxy error errorTx ' + + e2.message + ); + } + if ( + e.message.indexOf('transaction already in the mempool') !== -1 || + e.message.indexOf('TXN-MEMPOOL-CONFLICT') + ) { + throw new Error('SERVER_RESPONSE_NO_RESPONSE'); + } else if (e.message.indexOf('dust') !== -1) { + throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_DUST'); + } else if ( + e.message.indexOf('bad-txns-inputs-spent') !== -1 || + e.message.indexOf('txn-mempool-conflict') !== -1 + ) { + throw new Error('SERVER_RESPONSE_NO_RESPONSE'); + } else if ( + e.message.indexOf('fee for relay') !== -1 || + e.message.indexOf('insufficient priority') !== -1 + ) { + throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_FEE'); + } else { + throw e; + } + } + let txid = ''; + // @ts-ignore + if (typeof res.data === 'undefined' || !res.data) { + throw new Error('SERVER_RESPONSE_NOT_CONNECTED'); + } + // @ts-ignore + if ( + typeof res.data !== 'undefined' && + typeof res.data.txid !== 'undefined' + ) { + // @ts-ignore + txid = res.data.txid; + } + // @ts-ignore + if (typeof res.data.data !== 'undefined') { + // @ts-ignore + if (typeof res.data.data.transaction_hash !== 'undefined') { // @ts-ignore - if (typeof res.data !== 'undefined' && typeof res.data.txid !== 'undefined') { - // @ts-ignore - txid = res.data.txid - } + txid = res.data.data.transaction_hash; + } + } + if (txid === '') { + if (config.debug.cryptoErrors) { // @ts-ignore - if (typeof res.data.data !== 'undefined') { - // @ts-ignore - if (typeof res.data.data.transaction_hash !== 'undefined') { - // @ts-ignore - txid = res.data.data.transaction_hash - } - } - if (txid === '') { - if (config.debug.cryptoErrors) { - // @ts-ignore - console.log(this._settings.currencyCode + ' BtcTestSendProvider.send no txid', res.data) - } - throw new Error('SERVER_RESPONSE_NOT_CONNECTED') - } - - // logData = await this._checkSuccess(txid, hex, subtitle, txRBF, logData) - - return {transactionHash : txid, transactionJson: {}, logData } + console.log( + this._settings.currencyCode + ' BtcTestSendProvider.send no txid', + res.data + ); + } + throw new Error('SERVER_RESPONSE_NOT_CONNECTED'); } -} + // logData = await this._checkSuccess(txid, hex, subtitle, txRBF, logData) + return { transactionHash: txid, transactionJson: {}, logData }; + } +} diff --git a/crypto/blockchains/btc_test/providers/BtcTestUnspentsProvider.ts b/crypto/blockchains/btc_test/providers/BtcTestUnspentsProvider.ts index fd42175fa..8a9ae928f 100644 --- a/crypto/blockchains/btc_test/providers/BtcTestUnspentsProvider.ts +++ b/crypto/blockchains/btc_test/providers/BtcTestUnspentsProvider.ts @@ -1,61 +1,87 @@ /** * @version 0.52 */ -import { BlocksoftBlockchainTypes } from '@crypto/blockchains/BlocksoftBlockchainTypes' +import { BlocksoftBlockchainTypes } from '@crypto/blockchains/BlocksoftBlockchainTypes'; -import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog' -import BlocksoftAxios from '@crypto/common/BlocksoftAxios' +import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; +import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; -const API_PATH = 'https://blockstream.info/testnet/api/' +const API_PATH = 'https://blockstream.info/testnet/api/'; -export default class BtcTestUnspentsProvider implements BlocksoftBlockchainTypes.UnspentsProvider { +export default class BtcTestUnspentsProvider + implements BlocksoftBlockchainTypes.UnspentsProvider +{ + protected _settings: BlocksoftBlockchainTypes.CurrencySettings; - protected _settings: BlocksoftBlockchainTypes.CurrencySettings + constructor( + settings: BlocksoftBlockchainTypes.CurrencySettings, + serverCode: string + ) { + this._settings = settings; + } - constructor(settings: BlocksoftBlockchainTypes.CurrencySettings, serverCode: string) { - this._settings = settings - } + /** + * https://blockstream.info/testnet/api/address/mtU4mYXfBRiTx1iUBWcCvUTr4CgRnRALaL/utxo + * @param address + */ + async getUnspents( + address: string + ): Promise { + // @ts-ignore + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' BtcTestUnspentsProvider.getUnspents started', + address + ); + + const link = API_PATH + `address/${address}/utxo`; + const res = await BlocksoftAxios.getWithoutBraking(link); + if (!res || typeof res.data === 'undefined') { + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' BtcTestUnspentsProvider.getUnspents nothing loaded for address ' + + address + + ' link ' + + link + ); + throw new Error('SERVER_RESPONSE_NOT_CONNECTED'); + } + if ( + !res.data || + typeof res.data.length === 'undefined' || + !res.data || + !res.data.length + ) { + return []; + } + const sortedUnspents = []; /** - * https://blockstream.info/testnet/api/address/mtU4mYXfBRiTx1iUBWcCvUTr4CgRnRALaL/utxo - * @param address + * @param {*} res.data[] + * @param {string} res.data[].txid + * @param {string} res.data[].vout + * @param {string} res.data[].status + * @param {string} res.data[].status.confirmed + * @param {string} res.data[].status.block_height + * @param {string} res.data[].status.block_hash + * @param {string} res.data[].status.block_time + * @param {string} res.data[].value */ - async getUnspents(address: string): Promise { - // @ts-ignore - BlocksoftCryptoLog.log(this._settings.currencyCode + ' BtcTestUnspentsProvider.getUnspents started', address) - - const link = API_PATH + `address/${address}/utxo` - const res = await BlocksoftAxios.getWithoutBraking(link) - - if (!res || typeof res.data === 'undefined') { - BlocksoftCryptoLog.log(this._settings.currencyCode + ' BtcTestUnspentsProvider.getUnspents nothing loaded for address ' + address + ' link ' + link) - throw new Error('SERVER_RESPONSE_NOT_CONNECTED') - } - if (!res.data || typeof res.data.length === 'undefined' || !res.data || !res.data.length) { - return [] - } - const sortedUnspents = [] - /** - * @param {*} res.data[] - * @param {string} res.data[].txid - * @param {string} res.data[].vout - * @param {string} res.data[].status - * @param {string} res.data[].status.confirmed - * @param {string} res.data[].status.block_height - * @param {string} res.data[].status.block_hash - * @param {string} res.data[].status.block_time - * @param {string} res.data[].value - */ - for (const unspent of res.data) { - sortedUnspents.push({ - txid: unspent.txid, - vout: typeof unspent.vout === 'undefined' ? 0 : unspent.vout, - value: unspent.value.toString(), - height: 0, - confirmations : typeof unspent.status !== 'undefined' && typeof unspent.status.confirmed !== 'undefined' && unspent.status.confirmed ? 100 : 0, - isRequired : false - }) - } - return sortedUnspents + for (const unspent of res.data) { + sortedUnspents.push({ + txid: unspent.txid, + vout: typeof unspent.vout === 'undefined' ? 0 : unspent.vout, + value: unspent.value.toString(), + height: 0, + confirmations: + typeof unspent.status !== 'undefined' && + typeof unspent.status.confirmed !== 'undefined' && + unspent.status.confirmed + ? 100 + : 0, + isRequired: false + }); } + return sortedUnspents; + } } diff --git a/crypto/blockchains/doge/DogeScannerProcessor.js b/crypto/blockchains/doge/DogeScannerProcessor.js index b6a629f1e..cd8ddde2c 100644 --- a/crypto/blockchains/doge/DogeScannerProcessor.js +++ b/crypto/blockchains/doge/DogeScannerProcessor.js @@ -20,234 +20,304 @@ * @property {*} inputValue * @property {*} transactionJson */ -import BlocksoftUtils from '../../common/BlocksoftUtils' -import BlocksoftAxios from '../../common/BlocksoftAxios' -import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog' +import BlocksoftUtils from '../../common/BlocksoftUtils'; +import BlocksoftAxios from '../../common/BlocksoftAxios'; +import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog'; -import DogeFindAddressFunction from './basic/DogeFindAddressFunction' -import BlocksoftExternalSettings from '../../common/BlocksoftExternalSettings' -import DogeRawDS from './stores/DogeRawDS' -import EthRawDS from '../eth/stores/EthRawDS' +import DogeFindAddressFunction from './basic/DogeFindAddressFunction'; +import BlocksoftExternalSettings from '../../common/BlocksoftExternalSettings'; +import DogeRawDS from './stores/DogeRawDS'; +import EthRawDS from '../eth/stores/EthRawDS'; -const CACHE_VALID_TIME = 30000 // 30 seconds -const CACHE = {} +const CACHE_VALID_TIME = 30000; // 30 seconds +const CACHE = {}; -const TIMEOUT_DOGE = 60000 -const PROXY_TXS = 'https://proxy.trustee.deals/btc/getTxs' +const TIMEOUT_DOGE = 60000; +const PROXY_TXS = 'https://proxy.trustee.deals/btc/getTxs'; export default class DogeScannerProcessor { + /** + * @type {number} + * @private + */ + _blocksToConfirm = 5; - /** - * @type {number} - * @private - */ - _blocksToConfirm = 5 - - /** - * @type {string} - * @private - */ - _trezorServerCode = 'DOGE_TREZOR_SERVER' - - /** - * @private - */ - _trezorServer = false - - constructor(settings) { - this._settings = settings - } + /** + * @type {string} + * @private + */ + _trezorServerCode = 'DOGE_TREZOR_SERVER'; - _addressesForFind(address, jsonData = {}) { - return [address] - } + /** + * @private + */ + _trezorServer = false; - /** - * @param address - * @returns {Promise} - * @private - */ - async _get(address, jsonData) { - const now = new Date().getTime() - if (typeof CACHE[address] !== 'undefined' && (now - CACHE[address].time < CACHE_VALID_TIME)) { - CACHE[address].provider = 'trezor-cache' - return CACHE[address] - } + constructor(settings) { + this._settings = settings; + } - let link - let res = false - // remove later after tests - if (this._settings['currencyCode'] === 'DOGE' || this._settings['currencyCode'] === 'LTC' || this._settings['currencyCode'] === 'BSV' || this._settings['currencyCode'] === 'BCH') { - link = PROXY_TXS + '?address=' + address + '¤cyCode=' + this._settings['currencyCode'] - res = await BlocksoftAxios.getWithoutBraking(link, 5, TIMEOUT_DOGE) - } - if (res && typeof res.data !== 'undefined' && res.data && typeof res.data.data !== 'undefined') { - res.data = res.data.data - } else { - this._trezorServer = await BlocksoftExternalSettings.getTrezorServer(this._trezorServerCode, 'DOGE.Scanner._get') - link = this._trezorServer + '/api/v2/address/' + address + '?details=txs&pageSize=40' - res = await BlocksoftAxios.getWithoutBraking(link, 5, TIMEOUT_DOGE) - } + _addressesForFind(address, jsonData = {}) { + return [address]; + } - if (!res || !res.data) { - await BlocksoftExternalSettings.setTrezorServerInvalid(this._trezorServerCode, this._trezorServer) - return false - } - if (typeof res.data.balance === 'undefined') { - throw new Error(this._settings.currencyCode + ' DogeScannerProcessor._get nothing loaded for address ' + link) - } - CACHE[address] = { - data: res.data, - time: now, - provider : 'trezor' - } - return CACHE[address] + /** + * @param address + * @returns {Promise} + * @private + */ + async _get(address, jsonData) { + const now = new Date().getTime(); + if ( + typeof CACHE[address] !== 'undefined' && + now - CACHE[address].time < CACHE_VALID_TIME + ) { + CACHE[address].provider = 'trezor-cache'; + return CACHE[address]; } - /** - * @param {string} address - * @return {Promise<{balance:*, unconfirmed:*, provider:string}>} - */ - async getBalanceBlockchain(address, jsonData = {}) { - BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeScannerProcessor.getBalance started ' + address) - const res = await this._get(address, jsonData) - if (!res) { - return false - } - return { balance: res.data.balance, unconfirmed: res.data.unconfirmedBalance, provider: res.provider, time : res.time } + let link; + let res = false; + // remove later after tests + if ( + this._settings['currencyCode'] === 'DOGE' || + this._settings['currencyCode'] === 'LTC' || + this._settings['currencyCode'] === 'BSV' || + this._settings['currencyCode'] === 'BCH' + ) { + link = + PROXY_TXS + + '?address=' + + address + + '¤cyCode=' + + this._settings['currencyCode']; + res = await BlocksoftAxios.getWithoutBraking(link, 5, TIMEOUT_DOGE); + } + if ( + res && + typeof res.data !== 'undefined' && + res.data && + typeof res.data.data !== 'undefined' + ) { + res.data = res.data.data; + } else { + this._trezorServer = await BlocksoftExternalSettings.getTrezorServer( + this._trezorServerCode, + 'DOGE.Scanner._get' + ); + link = + this._trezorServer + + '/api/v2/address/' + + address + + '?details=txs&pageSize=40'; + res = await BlocksoftAxios.getWithoutBraking(link, 5, TIMEOUT_DOGE); } - /** - * @param {string} scanData.account.address - * @param {*} scanData.additional - * @return {Promise} - */ - async getTransactionsBlockchain(scanData) { - const address = scanData.account.address.trim() - const jsonData = scanData.additional - BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeScannerProcessor.getTransactions started ' + address) - - const notBroadcasted = await DogeRawDS.getForAddress({ address, currencyCode: this._settings.currencyCode }) - - let res = await this._get(address, jsonData) - if (!res || typeof res.data === 'undefined') return [] - BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeScannerProcessor.getTransactions loaded from ' + res.provider + ' ' + res.time) - res = res.data - if (typeof res.transactions === 'undefined' || !res.transactions) return [] - - const vinsOrder = {} - for (const tx of res.transactions) { - vinsOrder[tx.txid] = tx.blockTime - } + if (!res || !res.data) { + await BlocksoftExternalSettings.setTrezorServerInvalid( + this._trezorServerCode, + this._trezorServer + ); + return false; + } + if (typeof res.data.balance === 'undefined') { + throw new Error( + this._settings.currencyCode + + ' DogeScannerProcessor._get nothing loaded for address ' + + link + ); + } + CACHE[address] = { + data: res.data, + time: now, + provider: 'trezor' + }; + return CACHE[address]; + } - let plussed = false - let i = 0 - do { - for (const tx of res.transactions) { - if (typeof tx.vin === 'undefined' || tx.vin.length === 0) continue - for (const vin of tx.vin) { - if (typeof vinsOrder[vin.txid] === 'undefined') { - continue - } - const newTime = vinsOrder[vin.txid] + 1 - if (tx.blockTime < newTime) { - tx.blockTime = newTime - plussed = true - } - vinsOrder[tx.txid] = tx.blockTime - } - } - i++ - } while (plussed && i < 100) - - const transactions = [] - for (const tx of res.transactions) { - const transaction = await this._unifyTransaction(address, tx, jsonData) - if (transaction) { - transactions.push(transaction) - if ( - transaction.transactionDirection === 'outcome' || transaction.transactionDirection === 'self' - ) { - const uniqueFrom = address.toLowerCase() + '_' + transaction.transactionHash - if (notBroadcasted && typeof notBroadcasted[uniqueFrom] !== 'undefined' && transaction.transactionStatus !== 'new') { - DogeRawDS.cleanRaw({ - address, - transactionUnique: uniqueFrom, - currencyCode: this._settings.currencyCode - }) - } - } - } - } - BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeScannerProcessor.getTransactions finished ' + address + ' total: ' + transactions.length) - return transactions + /** + * @param {string} address + * @return {Promise<{balance:*, unconfirmed:*, provider:string}>} + */ + async getBalanceBlockchain(address, jsonData = {}) { + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' DogeScannerProcessor.getBalance started ' + + address + ); + const res = await this._get(address, jsonData); + if (!res) { + return false; } + return { + balance: res.data.balance, + unconfirmed: res.data.unconfirmedBalance, + provider: res.provider, + time: res.time + }; + } - /** - * - * @param {string} address - * @param {Object} transaction - * @param {string} transaction.txid c6b4c3879196857bed7fd5b553dd0049486c032d6a1be72b98fda967ca54b2da - * @param {string} transaction.version 1 - * @param {string} transaction.vin[].txid aa31777a9db759f57fd243ef47419939f233d16bc3e535e9a1c5af3ace87cb54 - * @param {string} transaction.vin[].sequence 4294967294 - * @param {string} transaction.vin[].n 0 - * @param {string} transaction.vin[].addresses [ 'DFDn5QyHH9DiFBNFGMcyJT5uUpDvmBRDqH' ] - * @param {string} transaction.vin[].value 44400000000 - * @param {string} transaction.vin[].hex 47304402200826f97d3432452abedd4346553de0b0c2d401ad7056b155e6462484afd98aa902202b5fb3166b96ded33249aecad7c667c0870c1 - * @param {string} transaction.vout[].value 59999824800 - * @param {string} transaction.vout[].n 0 - * @param {string} transaction.vout[].spent true - * @param {string} transaction.vout[].hex 76a91456d49605503d4770cf1f32fbfb69676d9a72554f88ac - * @param {string} transaction.vout[].addresses [ 'DD4DKVTEkRUGs7qzN8b7q5LKmoE9mXsJk4' ] - * @param {string} transaction.blockHash fc590834c04812e1c7818024a94021e12c4d8ab905724b4a4fdb4d4732878f69 - * @param {string} transaction.blockHeight 3036225 - * @param {string} transaction.confirmations 8568 - * @param {string} transaction.blockTime 1577362993 - * @param {string} transaction.value 59999917700 - * @param {string} transaction.valueIn 59999917700 - * @param {string} transaction.fees 0 - * @param {string} transaction.hex 010000000654cb87ce3aafc5a1e935e5c36bd133f239 - * @return {Promise} - * @private - */ - async _unifyTransaction(address, transaction, jsonData = {}) { - let showAddresses = false - try { - const tmp = this._addressesForFind(address, jsonData) - showAddresses = await DogeFindAddressFunction(tmp, transaction) - } catch (e) { - e.message += ' transaction hash ' + JSON.stringify(transaction) + ' address ' + address - throw e - } + /** + * @param {string} scanData.account.address + * @param {*} scanData.additional + * @return {Promise} + */ + async getTransactionsBlockchain(scanData) { + const address = scanData.account.address.trim(); + const jsonData = scanData.additional; + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' DogeScannerProcessor.getTransactions started ' + + address + ); - let transactionStatus = 'new' - if (transaction.confirmations > this._blocksToConfirm) { - transactionStatus = 'success' - } else if (transaction.confirmations > 0) { - transactionStatus = 'confirming' - } + const notBroadcasted = await DogeRawDS.getForAddress({ + address, + currencyCode: this._settings.currencyCode + }); + + let res = await this._get(address, jsonData); + if (!res || typeof res.data === 'undefined') return []; + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' DogeScannerProcessor.getTransactions loaded from ' + + res.provider + + ' ' + + res.time + ); + res = res.data; + if (typeof res.transactions === 'undefined' || !res.transactions) return []; - let formattedTime - try { - formattedTime = BlocksoftUtils.toDate(transaction.blockTime) - } catch (e) { - e.message += ' timestamp error transaction data ' + JSON.stringify(transaction) - throw e + const vinsOrder = {}; + for (const tx of res.transactions) { + vinsOrder[tx.txid] = tx.blockTime; + } + + let plussed = false; + let i = 0; + do { + for (const tx of res.transactions) { + if (typeof tx.vin === 'undefined' || tx.vin.length === 0) continue; + for (const vin of tx.vin) { + if (typeof vinsOrder[vin.txid] === 'undefined') { + continue; + } + const newTime = vinsOrder[vin.txid] + 1; + if (tx.blockTime < newTime) { + tx.blockTime = newTime; + plussed = true; + } + vinsOrder[tx.txid] = tx.blockTime; } + } + i++; + } while (plussed && i < 100); - return { - transactionHash: transaction.txid, - blockHash: transaction.blockHash, - blockNumber: +transaction.blockHeight, - blockTime: formattedTime, - blockConfirmations: transaction.confirmations, - transactionDirection: showAddresses.direction, - addressFrom: showAddresses.from, - addressTo: showAddresses.to, - addressAmount: showAddresses.value, - transactionStatus: transactionStatus, - transactionFee: transaction.fees + const transactions = []; + for (const tx of res.transactions) { + const transaction = await this._unifyTransaction(address, tx, jsonData); + if (transaction) { + transactions.push(transaction); + if ( + transaction.transactionDirection === 'outcome' || + transaction.transactionDirection === 'self' + ) { + const uniqueFrom = + address.toLowerCase() + '_' + transaction.transactionHash; + if ( + notBroadcasted && + typeof notBroadcasted[uniqueFrom] !== 'undefined' && + transaction.transactionStatus !== 'new' + ) { + DogeRawDS.cleanRaw({ + address, + transactionUnique: uniqueFrom, + currencyCode: this._settings.currencyCode + }); + } } + } } + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' DogeScannerProcessor.getTransactions finished ' + + address + + ' total: ' + + transactions.length + ); + return transactions; + } + + /** + * + * @param {string} address + * @param {Object} transaction + * @param {string} transaction.txid c6b4c3879196857bed7fd5b553dd0049486c032d6a1be72b98fda967ca54b2da + * @param {string} transaction.version 1 + * @param {string} transaction.vin[].txid aa31777a9db759f57fd243ef47419939f233d16bc3e535e9a1c5af3ace87cb54 + * @param {string} transaction.vin[].sequence 4294967294 + * @param {string} transaction.vin[].n 0 + * @param {string} transaction.vin[].addresses [ 'DFDn5QyHH9DiFBNFGMcyJT5uUpDvmBRDqH' ] + * @param {string} transaction.vin[].value 44400000000 + * @param {string} transaction.vin[].hex 47304402200826f97d3432452abedd4346553de0b0c2d401ad7056b155e6462484afd98aa902202b5fb3166b96ded33249aecad7c667c0870c1 + * @param {string} transaction.vout[].value 59999824800 + * @param {string} transaction.vout[].n 0 + * @param {string} transaction.vout[].spent true + * @param {string} transaction.vout[].hex 76a91456d49605503d4770cf1f32fbfb69676d9a72554f88ac + * @param {string} transaction.vout[].addresses [ 'DD4DKVTEkRUGs7qzN8b7q5LKmoE9mXsJk4' ] + * @param {string} transaction.blockHash fc590834c04812e1c7818024a94021e12c4d8ab905724b4a4fdb4d4732878f69 + * @param {string} transaction.blockHeight 3036225 + * @param {string} transaction.confirmations 8568 + * @param {string} transaction.blockTime 1577362993 + * @param {string} transaction.value 59999917700 + * @param {string} transaction.valueIn 59999917700 + * @param {string} transaction.fees 0 + * @param {string} transaction.hex 010000000654cb87ce3aafc5a1e935e5c36bd133f239 + * @return {Promise} + * @private + */ + async _unifyTransaction(address, transaction, jsonData = {}) { + let showAddresses = false; + try { + const tmp = this._addressesForFind(address, jsonData); + showAddresses = await DogeFindAddressFunction(tmp, transaction); + } catch (e) { + e.message += + ' transaction hash ' + + JSON.stringify(transaction) + + ' address ' + + address; + throw e; + } + + let transactionStatus = 'new'; + if (transaction.confirmations > this._blocksToConfirm) { + transactionStatus = 'success'; + } else if (transaction.confirmations > 0) { + transactionStatus = 'confirming'; + } + + let formattedTime; + try { + formattedTime = BlocksoftUtils.toDate(transaction.blockTime); + } catch (e) { + e.message += + ' timestamp error transaction data ' + JSON.stringify(transaction); + throw e; + } + + return { + transactionHash: transaction.txid, + blockHash: transaction.blockHash, + blockNumber: +transaction.blockHeight, + blockTime: formattedTime, + blockConfirmations: transaction.confirmations, + transactionDirection: showAddresses.direction, + addressFrom: showAddresses.from, + addressTo: showAddresses.to, + addressAmount: showAddresses.value, + transactionStatus: transactionStatus, + transactionFee: transaction.fees + }; + } } diff --git a/crypto/blockchains/doge/DogeTransferProcessor.ts b/crypto/blockchains/doge/DogeTransferProcessor.ts index 18223a5cd..43997f2af 100644 --- a/crypto/blockchains/doge/DogeTransferProcessor.ts +++ b/crypto/blockchains/doge/DogeTransferProcessor.ts @@ -1,410 +1,662 @@ /** * @version 0.20 */ -import { BlocksoftBlockchainTypes } from '../BlocksoftBlockchainTypes' -import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog' -import BlocksoftUtils from '../../common/BlocksoftUtils' - -import DogeNetworkPrices from './basic/DogeNetworkPrices' -import DogeUnspentsProvider from './providers/DogeUnspentsProvider' -import DogeTxInputsOutputs from './tx/DogeTxInputsOutputs' -import DogeTxBuilder from './tx/DogeTxBuilder' -import DogeSendProvider from './providers/DogeSendProvider' -import DogeRawDS from './stores/DogeRawDS' -import { DogeLogs } from './basic/DogeLogs' - -import MarketingEvent from '../../../app/services/Marketing/MarketingEvent' -import config from '../../../app/config/config' -import { err } from 'react-native-svg/lib/typescript/xml' -import { sublocale } from '../../../app/services/i18n' -import settingsActions from '../../../app/appstores/Stores/Settings/SettingsActions' -import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings' - - -const networksConstants = require('../../common/ext/networks-constants') -const MAX_UNSPENTS = 100 - -export default class DogeTransferProcessor implements BlocksoftBlockchainTypes.TransferProcessor { - - _trezorServerCode = 'DOGE_TREZOR_SERVER' - - _builderSettings: BlocksoftBlockchainTypes.BuilderSettings = { - minOutputDustReadable: 0.001, - minChangeDustReadable: 0.5, - feeMaxForByteSatoshi: 100000000, // for tx builder - feeMaxAutoReadable2: 300, // for fee calc, - feeMaxAutoReadable6: 150, // for fee calc - feeMaxAutoReadable12: 100, // for fee calc - - changeTogether: true, - minRbfStepSatoshi: 50, - minSpeedUpMulti: 1.5, - feeMinTotalReadable : 1 - } +import { BlocksoftBlockchainTypes } from '../BlocksoftBlockchainTypes'; +import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog'; +import BlocksoftUtils from '../../common/BlocksoftUtils'; - _initedProviders: boolean = false +import DogeNetworkPrices from './basic/DogeNetworkPrices'; +import DogeUnspentsProvider from './providers/DogeUnspentsProvider'; +import DogeTxInputsOutputs from './tx/DogeTxInputsOutputs'; +import DogeTxBuilder from './tx/DogeTxBuilder'; +import DogeSendProvider from './providers/DogeSendProvider'; +import DogeRawDS from './stores/DogeRawDS'; +import { DogeLogs } from './basic/DogeLogs'; - _settings: BlocksoftBlockchainTypes.CurrencySettings +import MarketingEvent from '../../../app/services/Marketing/MarketingEvent'; +import config from '../../../app/config/config'; +import { err } from 'react-native-svg/lib/typescript/xml'; +import { sublocale } from '../../../app/services/i18n'; +import settingsActions from '../../../app/appstores/Stores/Settings/SettingsActions'; +import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings'; - _langPrefix: string +const networksConstants = require('../../common/ext/networks-constants'); +const MAX_UNSPENTS = 100; - // @ts-ignore - networkPrices: BlocksoftBlockchainTypes.NetworkPrices +export default class DogeTransferProcessor + implements BlocksoftBlockchainTypes.TransferProcessor +{ + _trezorServerCode = 'DOGE_TREZOR_SERVER'; - // @ts-ignore - unspentsProvider: BlocksoftBlockchainTypes.UnspentsProvider + _builderSettings: BlocksoftBlockchainTypes.BuilderSettings = { + minOutputDustReadable: 0.001, + minChangeDustReadable: 0.5, + feeMaxForByteSatoshi: 100000000, // for tx builder + feeMaxAutoReadable2: 300, // for fee calc, + feeMaxAutoReadable6: 150, // for fee calc + feeMaxAutoReadable12: 100, // for fee calc - // @ts-ignore - sendProvider: BlocksoftBlockchainTypes.SendProvider + changeTogether: true, + minRbfStepSatoshi: 50, + minSpeedUpMulti: 1.5, + feeMinTotalReadable: 1 + }; - // @ts-ignore - txPrepareInputsOutputs: BlocksoftBlockchainTypes.TxInputsOutputs + _initedProviders: boolean = false; - // @ts-ignore - txBuilder: BlocksoftBlockchainTypes.TxBuilder + _settings: BlocksoftBlockchainTypes.CurrencySettings; + + _langPrefix: string; + + // @ts-ignore + networkPrices: BlocksoftBlockchainTypes.NetworkPrices; + + // @ts-ignore + unspentsProvider: BlocksoftBlockchainTypes.UnspentsProvider; + + // @ts-ignore + sendProvider: BlocksoftBlockchainTypes.SendProvider; + + // @ts-ignore + txPrepareInputsOutputs: BlocksoftBlockchainTypes.TxInputsOutputs; + + // @ts-ignore + txBuilder: BlocksoftBlockchainTypes.TxBuilder; + + constructor(settings: BlocksoftBlockchainTypes.CurrencySettings) { + this._settings = settings; + this._langPrefix = networksConstants[settings.network].langPrefix; + this.networkPrices = new DogeNetworkPrices(); + } + + _initProviders() { + if (this._initedProviders) return false; + this.unspentsProvider = new DogeUnspentsProvider( + this._settings, + this._trezorServerCode + ); + this.sendProvider = new DogeSendProvider( + this._settings, + this._trezorServerCode + ); + this.txPrepareInputsOutputs = new DogeTxInputsOutputs( + this._settings, + this._builderSettings + ); + this.txBuilder = new DogeTxBuilder(this._settings, this._builderSettings); + this._initedProviders = true; + } + + needPrivateForFee(): boolean { + return true; + } + + checkSendAllModal(data: { currencyCode: any }): boolean { + return true; + } - constructor(settings: BlocksoftBlockchainTypes.CurrencySettings) { - this._settings = settings - this._langPrefix = networksConstants[settings.network].langPrefix - this.networkPrices = new DogeNetworkPrices() + async getFeeRate( + data: BlocksoftBlockchainTypes.TransferData, + privateData: BlocksoftBlockchainTypes.TransferPrivateData, + additionalData: BlocksoftBlockchainTypes.TransferAdditionalData = {} + ): Promise { + this._initProviders(); + + let isStaticFee = + this._settings.currencyCode === 'DOGE' && + (typeof additionalData.isCustomFee === 'undefined' || + !additionalData.isCustomFee); + let feeStaticReadable; + if (isStaticFee) { + feeStaticReadable = BlocksoftExternalSettings.getStatic('DOGE_STATIC'); + if (!feeStaticReadable['useStatic']) { + isStaticFee = false; + } + } + let txRBF = false; + let transactionSpeedUp = false; + let transactionReplaceByFee = false; + let transactionRemoveByFee = false; + if ( + typeof data.transactionRemoveByFee !== 'undefined' && + data.transactionRemoveByFee + ) { + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' DogeTransferProcessor.getFeeRate remove started ' + + data.addressFrom + + ' => ' + + data.amount + ); + transactionRemoveByFee = data.transactionRemoveByFee; + txRBF = transactionRemoveByFee; + isStaticFee = false; + } else if ( + typeof data.transactionReplaceByFee !== 'undefined' && + data.transactionReplaceByFee + ) { + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' DogeTransferProcessor.getFeeRate resend started ' + + data.addressFrom + + ' => ' + + data.amount + ); + transactionReplaceByFee = data.transactionReplaceByFee; + txRBF = transactionReplaceByFee; + isStaticFee = false; + } else if ( + typeof data.transactionSpeedUp !== 'undefined' && + data.transactionSpeedUp + ) { + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' DogeTransferProcessor.getFeeRate speedup started ' + + data.addressFrom + + ' => ' + + data.amount + ); + transactionSpeedUp = data.transactionSpeedUp; + txRBF = transactionSpeedUp; + } else { + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' DogeTransferProcessor.getFeeRate started ' + + data.addressFrom + + ' => ' + + data.amount + ); } - _initProviders() { - if (this._initedProviders) return false - this.unspentsProvider = new DogeUnspentsProvider(this._settings, this._trezorServerCode) - this.sendProvider = new DogeSendProvider(this._settings, this._trezorServerCode) - this.txPrepareInputsOutputs = new DogeTxInputsOutputs(this._settings, this._builderSettings) - this.txBuilder = new DogeTxBuilder(this._settings, this._builderSettings) - this._initedProviders = true + if (txRBF) { + const savedData = await DogeRawDS.getJson({ + address: data.addressFrom, + currencyCode: this._settings.currencyCode, + transactionHash: txRBF + }); // sometimes replaced in db or got from server + if (savedData) { + if (typeof data.transactionJson === 'undefined') { + data.transactionJson = {}; + } + for (const key in savedData) { + // @ts-ignore + data.transactionJson[key] = savedData[key]; + } + } } + let prices = {}; + let autocorrectFee = false; + if (typeof additionalData.feeForByte === 'undefined') { + prices = + typeof additionalData.prices !== 'undefined' + ? additionalData.prices + : await this.networkPrices.getNetworkPrices( + this._settings.currencyCode + ); + } else { + // @ts-ignore + prices.speed_blocks_12 = additionalData.feeForByte; + autocorrectFee = true; + } + + let unspents = []; + let totalUnspents = 0; + if (transactionRemoveByFee) { + if (typeof this.unspentsProvider.getTx === 'undefined') { + throw new Error( + 'No DogeTransferProcessor unspentsProvider.getTx for transactionRemoveByFee' + ); + } + unspents = await this.unspentsProvider.getTx( + data.transactionRemoveByFee, + data.addressFrom, + [], + data.walletHash + ); + data.isTransferAll = true; + } else { + unspents = + typeof additionalData.unspents !== 'undefined' + ? additionalData.unspents + : await this.unspentsProvider.getUnspents(data.addressFrom); + if (transactionReplaceByFee) { + if (typeof this.unspentsProvider.getTx === 'undefined') { + throw new Error( + 'No DogeTransferProcessor unspentsProvider.getTx for transactionReplaceByFee' + ); + } + unspents = await this.unspentsProvider.getTx( + data.transactionReplaceByFee, + data.addressFrom, + unspents, + data.walletHash + ); + } - needPrivateForFee(): boolean { - return true + totalUnspents = unspents.length; + if (totalUnspents > MAX_UNSPENTS) { + unspents = unspents.slice(0, MAX_UNSPENTS); + } } - checkSendAllModal(data: { currencyCode: any }): boolean { - return true + if (unspents.length > 1) { + unspents.sort((a, b) => { + return BlocksoftUtils.diff(b.value, a.value) * 1; + }); + // @ts-ignore + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' DogeTransferProcessor.getFeeRate unspents sorted', + unspents + ); + } else { + // @ts-ignore + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' DogeTransferProcessor.getFeeRate unspents no need to sort', + unspents + ); } - async getFeeRate(data: BlocksoftBlockchainTypes.TransferData, privateData: BlocksoftBlockchainTypes.TransferPrivateData, additionalData: BlocksoftBlockchainTypes.TransferAdditionalData = {}) - : Promise { - this._initProviders() + const result: BlocksoftBlockchainTypes.FeeRateResult = { + selectedFeeIndex: -1, + fees: [] as BlocksoftBlockchainTypes.Fee[] + } as BlocksoftBlockchainTypes.FeeRateResult; - let isStaticFee = this._settings.currencyCode === 'DOGE' && (typeof additionalData.isCustomFee === 'undefined' || !additionalData.isCustomFee) - let feeStaticReadable - if (isStaticFee) { - feeStaticReadable = BlocksoftExternalSettings.getStatic('DOGE_STATIC') - if (!feeStaticReadable['useStatic']) { - isStaticFee = false - } - } - let txRBF = false - let transactionSpeedUp = false - let transactionReplaceByFee = false - let transactionRemoveByFee = false - if (typeof data.transactionRemoveByFee !== 'undefined' && data.transactionRemoveByFee) { - BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeTransferProcessor.getFeeRate remove started ' + data.addressFrom + ' => ' + data.amount) - transactionRemoveByFee = data.transactionRemoveByFee - txRBF = transactionRemoveByFee - isStaticFee = false - } else if (typeof data.transactionReplaceByFee !== 'undefined' && data.transactionReplaceByFee) { - BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeTransferProcessor.getFeeRate resend started ' + data.addressFrom + ' => ' + data.amount) - transactionReplaceByFee = data.transactionReplaceByFee - txRBF = transactionReplaceByFee - isStaticFee = false - } else if (typeof data.transactionSpeedUp !== 'undefined' && data.transactionSpeedUp) { - BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeTransferProcessor.getFeeRate speedup started ' + data.addressFrom + ' => ' + data.amount) - transactionSpeedUp = data.transactionSpeedUp - txRBF = transactionSpeedUp - } else { - BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeTransferProcessor.getFeeRate started ' + data.addressFrom + ' => ' + data.amount) - } + const keys = ['speed_blocks_12', 'speed_blocks_6', 'speed_blocks_2']; + const checkedPrices = {}; + let prevFeeForByte = 0; - if (txRBF) { - const savedData = await DogeRawDS.getJson({ - address: data.addressFrom, - currencyCode: this._settings.currencyCode, - transactionHash: txRBF - }) // sometimes replaced in db or got from server - if (savedData) { - if (typeof data.transactionJson === 'undefined') { - data.transactionJson = {} - } - for (const key in savedData) { - // @ts-ignore - data.transactionJson[key] = savedData[key] - } - } - } - let prices = {} - let autocorrectFee = false - if (typeof additionalData.feeForByte === 'undefined') { - prices = typeof additionalData.prices !== 'undefined' ? additionalData.prices : await this.networkPrices.getNetworkPrices(this._settings.currencyCode) - } else { - // @ts-ignore - prices.speed_blocks_12 = additionalData.feeForByte - autocorrectFee = true - } + if (transactionSpeedUp) { + autocorrectFee = true; + } - let unspents = [] - let totalUnspents = 0 - if (transactionRemoveByFee) { - if (typeof this.unspentsProvider.getTx === 'undefined') { - throw new Error('No DogeTransferProcessor unspentsProvider.getTx for transactionRemoveByFee') - } - unspents = await this.unspentsProvider.getTx(data.transactionRemoveByFee, data.addressFrom, [], data.walletHash) - data.isTransferAll = true - } else { - unspents = typeof additionalData.unspents !== 'undefined' ? additionalData.unspents : await this.unspentsProvider.getUnspents(data.addressFrom) - if (transactionReplaceByFee) { - if (typeof this.unspentsProvider.getTx === 'undefined') { - throw new Error('No DogeTransferProcessor unspentsProvider.getTx for transactionReplaceByFee') - } - unspents = await this.unspentsProvider.getTx(data.transactionReplaceByFee, data.addressFrom, unspents, data.walletHash) - } + if (isStaticFee && feeStaticReadable) { + const newPrices = {}; + for (const key of keys) { + if (typeof feeStaticReadable[key] === 'undefined') continue; + newPrices[key] = prices[key]; + } + prices = newPrices; + } - totalUnspents = unspents.length - if (totalUnspents > MAX_UNSPENTS) { - unspents = unspents.slice(0, MAX_UNSPENTS) + const stepSatoshi = transactionRemoveByFee + ? this._builderSettings.minRbfStepSatoshi * 2 + : this._builderSettings.minRbfStepSatoshi; + let pricesTotal = 0; + for (const key of keys) { + // @ts-ignore + if (typeof prices[key] === 'undefined' || !prices[key]) continue; + pricesTotal++; + // @ts-ignore + let feeForByte = prices[key]; + if (typeof additionalData.feeForByte === 'undefined') { + if (transactionReplaceByFee || transactionRemoveByFee) { + if ( + typeof data.transactionJson !== 'undefined' && + data.transactionJson !== null && + typeof data.transactionJson.feeForByte !== 'undefined' + ) { + if (feeForByte * 1 < data.transactionJson.feeForByte * 1) { + feeForByte = Math.ceil( + data.transactionJson.feeForByte * 1 + stepSatoshi + ); } + } else { + feeForByte = Math.ceil(feeForByte * 1 + stepSatoshi); + } + if (feeForByte * 1 <= prevFeeForByte * 1) { + feeForByte = Math.ceil(prevFeeForByte * 1 + stepSatoshi); + } + } else if (transactionSpeedUp) { + feeForByte = Math.ceil( + feeForByte * this._builderSettings.minSpeedUpMulti + ); + if (feeForByte * 1 <= prevFeeForByte * 1) { + feeForByte = Math.ceil(prevFeeForByte * 1.2); + } } + } + // @ts-ignore + checkedPrices[key] = feeForByte; + prevFeeForByte = feeForByte; + } + + let uniqueFees = {}; + let allFees = {}; + let isError = false; + for (const key of keys) { + // @ts-ignore + if (typeof checkedPrices[key] === 'undefined' || !checkedPrices[key]) + continue; + // @ts-ignore + const feeForByte = checkedPrices[key]; + let preparedInputsOutputs; + const subtitle = 'getFeeRate_' + key + ' ' + feeForByte; + let blocks = '2'; + let autoFeeLimitReadable = this._builderSettings.feeMaxAutoReadable2; + if (key === 'speed_blocks_6') { + blocks = '6'; + autoFeeLimitReadable = this._builderSettings.feeMaxAutoReadable6; + } else if (key === 'speed_blocks_12') { + blocks = '12'; + autoFeeLimitReadable = this._builderSettings.feeMaxAutoReadable12; + } - if (unspents.length > 1) { - unspents.sort((a, b) => { - return BlocksoftUtils.diff(b.value, a.value) * 1 - }) - // @ts-ignore - BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeTransferProcessor.getFeeRate unspents sorted', unspents) + let logInputsOutputs, + blockchainData, + txSize, + actualFeeForByte, + actualFeeForByteNotRounded; + try { + if (isStaticFee) { + preparedInputsOutputs = + await this.txPrepareInputsOutputs.getInputsOutputs( + data, + unspents, + { + feeForByte: 'none', + feeForAll: BlocksoftUtils.fromUnified( + feeStaticReadable['speed_blocks_' + blocks], + this._settings.decimals + ), + feeForAllInputs: feeStaticReadable.feeForAllInputs, + autoFeeLimitReadable + }, + additionalData, + subtitle + ); + let newStatic = 0; + if ( + !data.isTransferAll && + preparedInputsOutputs.inputs && + preparedInputsOutputs.inputs.length > + feeStaticReadable.feeForAllInputs * 1 + ) { + newStatic = BlocksoftUtils.mul( + feeStaticReadable['speed_blocks_' + blocks], + Math.ceil( + preparedInputsOutputs.inputs.length / + feeStaticReadable.feeForAllInputs + ) + ); + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' DogeTransferProcessor.getFeeRate_' + + key + + ' inputs ' + + preparedInputsOutputs.inputs.length + + ' newStatic ' + + newStatic + ); + preparedInputsOutputs = + await this.txPrepareInputsOutputs.getInputsOutputs( + data, + unspents, + { + feeForByte: 'none', + feeForAll: BlocksoftUtils.fromUnified( + newStatic, + this._settings.decimals + ), + feeForAllInputs: feeStaticReadable.feeForAllInputs, + autoFeeLimitReadable + }, + additionalData, + subtitle + ); + } } else { - // @ts-ignore - BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeTransferProcessor.getFeeRate unspents no need to sort', unspents) + preparedInputsOutputs = + await this.txPrepareInputsOutputs.getInputsOutputs( + data, + unspents, + { + feeForByte, + autoFeeLimitReadable + }, + additionalData, + subtitle + ); } - const result: BlocksoftBlockchainTypes.FeeRateResult = { - selectedFeeIndex: -1, - fees: [] as BlocksoftBlockchainTypes.Fee[] - } as BlocksoftBlockchainTypes.FeeRateResult - - const keys = ['speed_blocks_12', 'speed_blocks_6', 'speed_blocks_2'] - const checkedPrices = {} - let prevFeeForByte = 0 - - if (transactionSpeedUp) { - autocorrectFee = true + if ( + typeof additionalData.feeForByte === 'undefined' && + typeof this._builderSettings.feeMinTotalReadable !== 'undefined' + ) { + logInputsOutputs = DogeLogs.logInputsOutputs( + data, + unspents, + preparedInputsOutputs, + this._settings, + subtitle + ); + if ( + logInputsOutputs.diffInOutReadable * 1 < + this._builderSettings.feeMinTotalReadable + ) { + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' DogeTransferProcessor.getFeeRate_' + + key + + ' ' + + feeForByte + + ' less minTotalReadable ' + + logInputsOutputs.diffInOutReadable + ); + preparedInputsOutputs = + await this.txPrepareInputsOutputs.getInputsOutputs( + data, + unspents, + { + feeForAll: BlocksoftUtils.fromUnified( + this._builderSettings.feeMinTotalReadable, + this._settings.decimals + ), + autoFeeLimitReadable + }, + additionalData, + subtitle + ); + autocorrectFee = false; + } } - - if (isStaticFee && feeStaticReadable) { - const newPrices = {} - for (const key of keys) { - if (typeof feeStaticReadable[key] === 'undefined') continue - newPrices[key] = prices[key] - } - prices = newPrices + // @ts-ignore + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' DogeTransferProcessor.getFeeRate_' + + key + + ' ' + + feeForByte + + ' preparedInputsOutputs addressTo' + + data.addressTo, + preparedInputsOutputs + ); + if (preparedInputsOutputs.inputs.length === 0) { + // do noting + continue; } - - const stepSatoshi = transactionRemoveByFee ? this._builderSettings.minRbfStepSatoshi * 2 : this._builderSettings.minRbfStepSatoshi - let pricesTotal = 0 - for (const key of keys) { - // @ts-ignore - if (typeof prices[key] === 'undefined' || !prices[key]) continue - pricesTotal++ - // @ts-ignore - let feeForByte = prices[key] - if (typeof additionalData.feeForByte === 'undefined') { - if (transactionReplaceByFee || transactionRemoveByFee) { - if (typeof data.transactionJson !== 'undefined' && data.transactionJson !== null && typeof data.transactionJson.feeForByte !== 'undefined') { - if (feeForByte * 1 < data.transactionJson.feeForByte * 1) { - feeForByte = Math.ceil(data.transactionJson.feeForByte * 1 + stepSatoshi) - } - } else { - feeForByte = Math.ceil(feeForByte * 1 + stepSatoshi) - } - if (feeForByte * 1 <= prevFeeForByte * 1) { - feeForByte = Math.ceil(prevFeeForByte * 1 + stepSatoshi) - } - } else if (transactionSpeedUp) { - feeForByte = Math.ceil(feeForByte * this._builderSettings.minSpeedUpMulti) - if (feeForByte * 1 <= prevFeeForByte * 1) { - feeForByte = Math.ceil(prevFeeForByte * 1.2) - } - } - } - // @ts-ignore - checkedPrices[key] = feeForByte - prevFeeForByte = feeForByte + } catch (e) { + if (config.debug.cryptoErrors) { + console.log( + this._settings.currencyCode + + ' DogeTransferProcessor.getFeeRate_' + + key + + ' ' + + feeForByte + + ' getInputsOutputs error', + e + ); } + // noinspection ES6MissingAwait + MarketingEvent.logOnlyRealTime( + 'v20_doge_error_getfeerate_' + + key + + ' ' + + feeForByte + + ' ' + + this._settings.currencyCode + + ' ' + + data.addressFrom + + ' => ' + + data.addressTo + + ' ' + + e.message, + unspents + ); + throw e; + } - let uniqueFees = {} - let allFees = {} - let isError = false - for (const key of keys) { - // @ts-ignore - if (typeof checkedPrices[key] === 'undefined' || !checkedPrices[key]) continue - // @ts-ignore - const feeForByte = checkedPrices[key] - let preparedInputsOutputs - const subtitle = 'getFeeRate_' + key + ' ' + feeForByte - let blocks = '2' - let autoFeeLimitReadable = this._builderSettings.feeMaxAutoReadable2 - if (key === 'speed_blocks_6') { - blocks = '6' - autoFeeLimitReadable = this._builderSettings.feeMaxAutoReadable6 - } else if (key === 'speed_blocks_12') { - blocks = '12' - autoFeeLimitReadable = this._builderSettings.feeMaxAutoReadable12 + try { + let doBuild = false; + let actualFeeRebuild = false; + do { + doBuild = false; + logInputsOutputs = DogeLogs.logInputsOutputs( + data, + unspents, + preparedInputsOutputs, + this._settings, + subtitle + ); + blockchainData = await this.txBuilder.getRawTx( + data, + privateData, + preparedInputsOutputs + ); + txSize = Math.ceil(blockchainData.rawTxHex.length / 2); + actualFeeForByteNotRounded = BlocksoftUtils.div( + logInputsOutputs.diffInOut, + txSize + ); + actualFeeForByte = Math.floor(actualFeeForByteNotRounded); + let needAutoCorrect = false; + if (autocorrectFee && !isStaticFee) { + needAutoCorrect = + actualFeeForByte.toString() !== feeForByte.toString(); + } + if (!actualFeeRebuild && needAutoCorrect) { + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' DogeTransferProcessor.getFeeRate will correct as ' + + actualFeeForByte.toString() + + ' != ' + + feeForByte.toString() + ); + let outputForCorrecting = -1; + for ( + let i = 0, ic = preparedInputsOutputs.outputs.length; + i < ic; + i++ + ) { + const output = preparedInputsOutputs.outputs[i]; + if (typeof output.isUsdt !== 'undefined') continue; + if (typeof output.isChange !== 'undefined' && output.isChange) { + outputForCorrecting = i; + } } + if (outputForCorrecting >= 0) { + const diff = BlocksoftUtils.diff( + actualFeeForByteNotRounded.toString(), + feeForByte.toString() + ); - let logInputsOutputs, blockchainData, txSize, actualFeeForByte, actualFeeForByteNotRounded - try { - if (isStaticFee) { - preparedInputsOutputs = await this.txPrepareInputsOutputs.getInputsOutputs(data, unspents, { - feeForByte : 'none', - feeForAll : BlocksoftUtils.fromUnified(feeStaticReadable['speed_blocks_' + blocks], this._settings.decimals), - feeForAllInputs : feeStaticReadable.feeForAllInputs, - autoFeeLimitReadable - }, - additionalData, - subtitle) - let newStatic = 0 - if (!data.isTransferAll && preparedInputsOutputs.inputs && preparedInputsOutputs.inputs.length > feeStaticReadable.feeForAllInputs * 1) { - newStatic = BlocksoftUtils.mul(feeStaticReadable['speed_blocks_' + blocks], Math.ceil(preparedInputsOutputs.inputs.length / feeStaticReadable.feeForAllInputs)) - BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeTransferProcessor.getFeeRate_' + key + ' inputs ' + preparedInputsOutputs.inputs.length + ' newStatic ' + newStatic) - preparedInputsOutputs = await this.txPrepareInputsOutputs.getInputsOutputs(data, unspents, { - feeForByte : 'none', - feeForAll : BlocksoftUtils.fromUnified(newStatic, this._settings.decimals), - feeForAllInputs : feeStaticReadable.feeForAllInputs, - autoFeeLimitReadable - }, - additionalData, - subtitle) - } - } else { - preparedInputsOutputs = await this.txPrepareInputsOutputs.getInputsOutputs(data, unspents, { - feeForByte, - autoFeeLimitReadable - }, - additionalData, - subtitle) - } + const part = BlocksoftUtils.mul( + txSize.toString(), + diff.toString() + ).toString(); + let newAmount; + if (part.indexOf('-') === 0) { + newAmount = BlocksoftUtils.diff( + preparedInputsOutputs.outputs[outputForCorrecting].amount, + part.replace('-', '') + ); + } else { + newAmount = BlocksoftUtils.add( + preparedInputsOutputs.outputs[outputForCorrecting].amount, + part + ); + } - if (typeof additionalData.feeForByte === 'undefined' && typeof this._builderSettings.feeMinTotalReadable !== 'undefined') { - logInputsOutputs = DogeLogs.logInputsOutputs(data, unspents, preparedInputsOutputs, this._settings, subtitle) - if (logInputsOutputs.diffInOutReadable * 1 < this._builderSettings.feeMinTotalReadable) { - BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeTransferProcessor.getFeeRate_' + key + ' ' + feeForByte + ' less minTotalReadable ' + logInputsOutputs.diffInOutReadable ) - preparedInputsOutputs = await this.txPrepareInputsOutputs.getInputsOutputs(data, unspents, { - feeForAll : BlocksoftUtils.fromUnified(this._builderSettings.feeMinTotalReadable, this._settings.decimals), - autoFeeLimitReadable - }, - additionalData, - subtitle) - autocorrectFee = false - } + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' DogeTransferProcessor.getFeeRate diff ' + + diff + + ' part ' + + part + + ' amount ' + + preparedInputsOutputs.outputs[outputForCorrecting].amount + + ' => ' + + newAmount + ); + + // @ts-ignore + if (newAmount * 1 < 0) { + preparedInputsOutputs.outputs[outputForCorrecting].amount = + 'removed'; + outputForCorrecting = -1; + } else { + preparedInputsOutputs.outputs[outputForCorrecting].amount = + newAmount; + } + } + doBuild = true; + actualFeeRebuild = true; + if (outputForCorrecting === -1) { + let foundToMore = false; + for (let i = 0, ic = unspents.length; i < ic; i++) { + const unspent = unspents[i]; + if (unspent.confirmations > 0 && !unspent.isRequired) { + unspents[i].isRequired = true; + foundToMore = true; } - // @ts-ignore - BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeTransferProcessor.getFeeRate_' + key + ' ' + feeForByte - + ' preparedInputsOutputs addressTo' + data.addressTo, preparedInputsOutputs) - if (preparedInputsOutputs.inputs.length === 0) { - // do noting - continue + } + if (!foundToMore) { + for (let i = 0, ic = unspents.length; i < ic; i++) { + const unspent = unspents[i]; + if (!unspent.isRequired) { + unspents[i].isRequired = true; + foundToMore = true; + } } - } catch (e) { - if (config.debug.cryptoErrors) { - console.log(this._settings.currencyCode + ' DogeTransferProcessor.getFeeRate_' + key + ' ' + feeForByte + ' getInputsOutputs error', e) + } + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' DogeTransferProcessor.getFeeRate foundToMore ' + + JSON.stringify(foundToMore) + ); + if (foundToMore) { + try { + const preparedInputsOutputs2 = + await this.txPrepareInputsOutputs.getInputsOutputs( + data, + unspents, + { + feeForByte, + autoFeeLimitReadable + }, + additionalData, + subtitle + ' foundToMore' + ); + actualFeeRebuild = false; + if (preparedInputsOutputs2.inputs.length > 0) { + preparedInputsOutputs = preparedInputsOutputs2; + } + } catch (e) { + // do nothing } - // noinspection ES6MissingAwait - MarketingEvent.logOnlyRealTime('v20_doge_error_getfeerate_' + key + ' ' + feeForByte + ' ' + this._settings.currencyCode + ' ' + data.addressFrom + ' => ' + data.addressTo + ' ' + e.message, unspents) - throw e + } } - - try { - let doBuild = false - let actualFeeRebuild = false - do { - doBuild = false - logInputsOutputs = DogeLogs.logInputsOutputs(data, unspents, preparedInputsOutputs, this._settings, subtitle) - blockchainData = await this.txBuilder.getRawTx(data, privateData, preparedInputsOutputs) - txSize = Math.ceil(blockchainData.rawTxHex.length / 2) - actualFeeForByteNotRounded = BlocksoftUtils.div(logInputsOutputs.diffInOut, txSize) - actualFeeForByte = Math.floor(actualFeeForByteNotRounded) - let needAutoCorrect = false - if (autocorrectFee && !isStaticFee) { - needAutoCorrect = actualFeeForByte.toString() !== feeForByte.toString() - } - if (!actualFeeRebuild && needAutoCorrect) { - BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeTransferProcessor.getFeeRate will correct as ' + actualFeeForByte.toString() + ' != ' + feeForByte.toString()) - let outputForCorrecting = -1 - for (let i = 0, ic = preparedInputsOutputs.outputs.length; i < ic; i++) { - const output = preparedInputsOutputs.outputs[i] - if (typeof output.isUsdt !== 'undefined') continue - if (typeof output.isChange !== 'undefined' && output.isChange) { - outputForCorrecting = i - } - } - if (outputForCorrecting >= 0) { - const diff = BlocksoftUtils.diff(actualFeeForByteNotRounded.toString(), feeForByte.toString()) - - const part = BlocksoftUtils.mul(txSize.toString(), diff.toString()).toString() - let newAmount - if (part.indexOf('-') === 0) { - newAmount = BlocksoftUtils.diff(preparedInputsOutputs.outputs[outputForCorrecting].amount, part.replace('-', '')) - } else { - newAmount = BlocksoftUtils.add(preparedInputsOutputs.outputs[outputForCorrecting].amount, part) - } - - - BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeTransferProcessor.getFeeRate diff ' + diff + ' part ' + part + ' amount ' + preparedInputsOutputs.outputs[outputForCorrecting].amount + ' => ' + newAmount) - - // @ts-ignore - if (newAmount * 1 < 0) { - preparedInputsOutputs.outputs[outputForCorrecting].amount = 'removed' - outputForCorrecting = -1 - } else { - preparedInputsOutputs.outputs[outputForCorrecting].amount = newAmount - } - } - doBuild = true - actualFeeRebuild = true - if (outputForCorrecting === -1) { - let foundToMore = false - for (let i = 0, ic = unspents.length; i < ic; i++) { - const unspent = unspents[i] - if (unspent.confirmations > 0 && !unspent.isRequired) { - unspents[i].isRequired = true - foundToMore = true - } - } - if (!foundToMore) { - for (let i = 0, ic = unspents.length; i < ic; i++) { - const unspent = unspents[i] - if (!unspent.isRequired) { - unspents[i].isRequired = true - foundToMore = true - } - } - } - BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeTransferProcessor.getFeeRate foundToMore ' + JSON.stringify(foundToMore)) - if (foundToMore) { - try { - const preparedInputsOutputs2 = await this.txPrepareInputsOutputs.getInputsOutputs(data, unspents, { - feeForByte, - autoFeeLimitReadable - }, - additionalData, subtitle + ' foundToMore') - actualFeeRebuild = false - if (preparedInputsOutputs2.inputs.length > 0) { - preparedInputsOutputs = preparedInputsOutputs2 - } - } catch (e) { - // do nothing - } - } - } - } - } while (doBuild) - } catch (e) { - if (config.debug.cryptoErrors) { - console.log(this._settings.currencyCode + ' DogeTransferProcessor.getRawTx error ' + e.message) - /* + } + } while (doBuild); + } catch (e) { + if (config.debug.cryptoErrors) { + console.log( + this._settings.currencyCode + + ' DogeTransferProcessor.getRawTx error ' + + e.message + ); + /* console.log('') console.log('') if (preparedInputsOutputs.inputs) { @@ -427,240 +679,394 @@ export default class DogeTransferProcessor implements BlocksoftBlockchainTypes.T console.log('---------------------') console.log('') */ - } - BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeTransferProcessor.getRawTx error '+ e.message) - MarketingEvent.logOnlyRealTime('v20_doge_error_tx_builder_fees ' + this._settings.currencyCode + ' ' + data.addressFrom + ' => ' + data.addressTo + ' ' + e.message.toString(), logInputsOutputs) - - if (e.message.indexOf('Transaction has absurd fees') !== -1) { - isError = 'SERVER_RESPONSE_TOO_BIG_FEE_PER_BYTE_FOR_TRANSACTION' - continue - } else { - throw e - } - } - - isError = false - // @ts-ignore - blockchainData.isTransferAll = data.isTransferAll - blockchainData.isRBFed = { transactionRemoveByFee, transactionReplaceByFee, transactionSpeedUp } - - - allFees[this._langPrefix + '_' + key] = logInputsOutputs.diffInOut - if (typeof uniqueFees[logInputsOutputs.diffInOut] !== 'undefined') { - continue - } - result.fees.push( - { - langMsg: this._langPrefix + '_' + key, - feeForByte: actualFeeForByte.toString(), - needSpeed: feeForByte.toString(), - feeForTx: logInputsOutputs.diffInOut, - amountForTx: logInputsOutputs.sendBalance, - addressToTx: data.addressTo, - blockchainData - } - ) - uniqueFees[logInputsOutputs.diffInOut] = true } - if (isError) { - throw new Error(isError) - } - result.selectedFeeIndex = result.fees.length - 1 + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' DogeTransferProcessor.getRawTx error ' + + e.message + ); + MarketingEvent.logOnlyRealTime( + 'v20_doge_error_tx_builder_fees ' + + this._settings.currencyCode + + ' ' + + data.addressFrom + + ' => ' + + data.addressTo + + ' ' + + e.message.toString(), + logInputsOutputs + ); - if (!transactionReplaceByFee && !transactionRemoveByFee && !isStaticFee) { + if (e.message.indexOf('Transaction has absurd fees') !== -1) { + isError = 'SERVER_RESPONSE_TOO_BIG_FEE_PER_BYTE_FOR_TRANSACTION'; + continue; + } else { + throw e; + } + } - if (typeof allFees[this._langPrefix + '_speed_blocks_2'] === 'undefined') { + isError = false; + // @ts-ignore + blockchainData.isTransferAll = data.isTransferAll; + blockchainData.isRBFed = { + transactionRemoveByFee, + transactionReplaceByFee, + transactionSpeedUp + }; - result.showSmallFeeNotice = new Date().getTime() - } - } - if (result.fees.length === 0) { - result.amountForTx = 0 - } - result.additionalData = { unspents } - const logResult = { - selectedFeeIndex: result.selectedFeeIndex ? result.selectedFeeIndex : 'none', - showSmallFeeNotice: result.showSmallFeeNotice ? result.showSmallFeeNotice : 'none', - allFees: allFees, - fees: [] - } - if (result.fees) { - for (const fee of result.fees) { - if (totalUnspents && totalUnspents > unspents.length) { - fee.blockchainData.countedForLessOutputs = totalUnspents - } - const logFee = { ...fee } - delete logFee.blockchainData - logResult.fees.push(logFee) - } - } - BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeTransferProcessor.getFees ' + JSON.stringify(logResult)) - return result + allFees[this._langPrefix + '_' + key] = logInputsOutputs.diffInOut; + if (typeof uniqueFees[logInputsOutputs.diffInOut] !== 'undefined') { + continue; + } + result.fees.push({ + langMsg: this._langPrefix + '_' + key, + feeForByte: actualFeeForByte.toString(), + needSpeed: feeForByte.toString(), + feeForTx: logInputsOutputs.diffInOut, + amountForTx: logInputsOutputs.sendBalance, + addressToTx: data.addressTo, + blockchainData + }); + uniqueFees[logInputsOutputs.diffInOut] = true; } - - async getTransferAllBalance(data: BlocksoftBlockchainTypes.TransferData, privateData: BlocksoftBlockchainTypes.TransferPrivateData, additionalData: BlocksoftBlockchainTypes.TransferAdditionalData = {}): Promise { - data.isTransferAll = true - const result = await this.getFeeRate(data, privateData, additionalData) - // @ts-ignore - if (!result || result.selectedFeeIndex < 0) { - return { - selectedTransferAllBalance: '0', - selectedFeeIndex: -2, - fees: [], - countedForBasicBalance: data.amount - } - } - // @ts-ignore - return { - ...result, - selectedTransferAllBalance: result.fees[result.selectedFeeIndex].amountForTx, - countedForBasicBalance: data.amount - } + if (isError) { + throw new Error(isError); } + result.selectedFeeIndex = result.fees.length - 1; - async sendTx(data: BlocksoftBlockchainTypes.TransferData, privateData: BlocksoftBlockchainTypes.TransferPrivateData, uiData: BlocksoftBlockchainTypes.TransferUiData): Promise { - if (typeof uiData.selectedFee.blockchainData === 'undefined' && typeof uiData.selectedFee.feeForTx === 'undefined') { - throw new Error('SERVER_RESPONSE_PLEASE_SELECT_FEE') + if (!transactionReplaceByFee && !transactionRemoveByFee && !isStaticFee) { + if ( + typeof allFees[this._langPrefix + '_speed_blocks_2'] === 'undefined' + ) { + result.showSmallFeeNotice = new Date().getTime(); + } + } + if (result.fees.length === 0) { + result.amountForTx = 0; + } + result.additionalData = { unspents }; + const logResult = { + selectedFeeIndex: result.selectedFeeIndex + ? result.selectedFeeIndex + : 'none', + showSmallFeeNotice: result.showSmallFeeNotice + ? result.showSmallFeeNotice + : 'none', + allFees: allFees, + fees: [] + }; + if (result.fees) { + for (const fee of result.fees) { + if (totalUnspents && totalUnspents > unspents.length) { + fee.blockchainData.countedForLessOutputs = totalUnspents; } + const logFee = { ...fee }; + delete logFee.blockchainData; + logResult.fees.push(logFee); + } + } + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' DogeTransferProcessor.getFees ' + + JSON.stringify(logResult) + ); + return result; + } - if (typeof privateData.privateKey === 'undefined') { - throw new Error('DOGE transaction required privateKey') - } - if (typeof data.addressTo === 'undefined') { - throw new Error('DOGE transaction required addressTo') - } + async getTransferAllBalance( + data: BlocksoftBlockchainTypes.TransferData, + privateData: BlocksoftBlockchainTypes.TransferPrivateData, + additionalData: BlocksoftBlockchainTypes.TransferAdditionalData = {} + ): Promise { + data.isTransferAll = true; + const result = await this.getFeeRate(data, privateData, additionalData); + // @ts-ignore + if (!result || result.selectedFeeIndex < 0) { + return { + selectedTransferAllBalance: '0', + selectedFeeIndex: -2, + fees: [], + countedForBasicBalance: data.amount + }; + } + // @ts-ignore + return { + ...result, + selectedTransferAllBalance: + result.fees[result.selectedFeeIndex].amountForTx, + countedForBasicBalance: data.amount + }; + } - let txRBFed = '' - let txRBF = false - if (typeof data.transactionRemoveByFee !== 'undefined' && data.transactionRemoveByFee) { - BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeTransferProcessor.sendTx remove started ' + data.transactionRemoveByFee) - txRBF = data.transactionRemoveByFee - txRBFed = 'RBFremoved' - } else if (typeof data.transactionReplaceByFee !== 'undefined' && data.transactionReplaceByFee) { - BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeTransferProcessor.sendTx resend started ' + data.transactionReplaceByFee) - txRBF = data.transactionReplaceByFee - txRBFed = 'RBFed' - } else { - BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeTransferProcessor.sendTx started') - txRBFed = 'usualSend' - } + async sendTx( + data: BlocksoftBlockchainTypes.TransferData, + privateData: BlocksoftBlockchainTypes.TransferPrivateData, + uiData: BlocksoftBlockchainTypes.TransferUiData + ): Promise { + if ( + typeof uiData.selectedFee.blockchainData === 'undefined' && + typeof uiData.selectedFee.feeForTx === 'undefined' + ) { + throw new Error('SERVER_RESPONSE_PLEASE_SELECT_FEE'); + } + if (typeof privateData.privateKey === 'undefined') { + throw new Error('DOGE transaction required privateKey'); + } + if (typeof data.addressTo === 'undefined') { + throw new Error('DOGE transaction required addressTo'); + } - const logData = {} - logData.currencyCode = this._settings.currencyCode - logData.selectedFee = uiData.selectedFee - logData.from = data.addressFrom - logData.basicAddressTo = data.addressTo - logData.basicAmount = data.amount - logData.pushLocale = sublocale() - logData.pushSetting = await settingsActions.getSetting('transactionsNotifs') + let txRBFed = ''; + let txRBF = false; + if ( + typeof data.transactionRemoveByFee !== 'undefined' && + data.transactionRemoveByFee + ) { + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' DogeTransferProcessor.sendTx remove started ' + + data.transactionRemoveByFee + ); + txRBF = data.transactionRemoveByFee; + txRBFed = 'RBFremoved'; + } else if ( + typeof data.transactionReplaceByFee !== 'undefined' && + data.transactionReplaceByFee + ) { + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' DogeTransferProcessor.sendTx resend started ' + + data.transactionReplaceByFee + ); + txRBF = data.transactionReplaceByFee; + txRBFed = 'RBFed'; + } else { + BlocksoftCryptoLog.log( + this._settings.currencyCode + ' DogeTransferProcessor.sendTx started' + ); + txRBFed = 'usualSend'; + } - if (typeof uiData !== 'undefined' && typeof uiData.selectedFee !== 'undefined' && typeof uiData.selectedFee.rawOnly !== 'undefined' && uiData.selectedFee.rawOnly) { - return { rawOnly: uiData.selectedFee.rawOnly, raw : uiData.selectedFee.blockchainData.rawTxHex } - } + const logData = {}; + logData.currencyCode = this._settings.currencyCode; + logData.selectedFee = uiData.selectedFee; + logData.from = data.addressFrom; + logData.basicAddressTo = data.addressTo; + logData.basicAmount = data.amount; + logData.pushLocale = sublocale(); + logData.pushSetting = await settingsActions.getSetting( + 'transactionsNotifs' + ); + if ( + typeof uiData !== 'undefined' && + typeof uiData.selectedFee !== 'undefined' && + typeof uiData.selectedFee.rawOnly !== 'undefined' && + uiData.selectedFee.rawOnly + ) { + return { + rawOnly: uiData.selectedFee.rawOnly, + raw: uiData.selectedFee.blockchainData.rawTxHex + }; + } - let result = {} as BlocksoftBlockchainTypes.SendTxResult - try { - result = await this.sendProvider.sendTx(uiData.selectedFee.blockchainData.rawTxHex, txRBFed, txRBF, logData) - } catch (e) { - if (config.debug.cryptoErrors) { - console.log(this._settings.currencyCode + ' DogeTransferProcessor.sent error', e) - } - BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeTransferProcessor.sent error '+ e.message) - // noinspection ES6MissingAwait - MarketingEvent.logOnlyRealTime('v20_doge_tx_error ' + this._settings.currencyCode + ' ' + data.addressFrom + ' => ' + data.addressTo + ' ' + e.message, logData) - throw e - } + let result = {} as BlocksoftBlockchainTypes.SendTxResult; + try { + result = await this.sendProvider.sendTx( + uiData.selectedFee.blockchainData.rawTxHex, + txRBFed, + txRBF, + logData + ); + } catch (e) { + if (config.debug.cryptoErrors) { + console.log( + this._settings.currencyCode + ' DogeTransferProcessor.sent error', + e + ); + } + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' DogeTransferProcessor.sent error ' + + e.message + ); + // noinspection ES6MissingAwait + MarketingEvent.logOnlyRealTime( + 'v20_doge_tx_error ' + + this._settings.currencyCode + + ' ' + + data.addressFrom + + ' => ' + + data.addressTo + + ' ' + + e.message, + logData + ); + throw e; + } - try { - result.transactionFee = uiData.selectedFee.feeForTx - result.transactionFeeCurrencyCode = this._settings.currencyCode - result.transactionJson = { - nSequence: uiData.selectedFee.blockchainData.nSequence, - txAllowReplaceByFee: uiData.selectedFee.blockchainData.txAllowReplaceByFee, - feeForByte: uiData.selectedFee.feeForByte - } - if (typeof uiData.selectedFee.amountForTx !== 'undefined' && uiData.selectedFee.amountForTx) { - result.amountForTx = uiData.selectedFee.amountForTx - } - if (txRBF) { - await DogeRawDS.cleanRaw({ - address: data.addressFrom, - currencyCode: this._settings.currencyCode, - transactionHash: txRBF - }) - } - const transactionLog = typeof result.logData !== 'undefined' ? result.logData : logData - const inputsLog = JSON.stringify(uiData.selectedFee.blockchainData.preparedInputsOutputs.inputs) - const transactionRaw = uiData.selectedFee.blockchainData.rawTxHex + '' - //if (typeof transactionLog.selectedFee !== 'undefined' && typeof transactionLog.selectedFee.blockchainData !== 'undefined') { - // transactionLog.selectedFee.blockchainData = '*' - //} - await DogeRawDS.saveRaw({ - address: data.addressFrom, - currencyCode: this._settings.currencyCode, - transactionHash: result.transactionHash, - transactionRaw, - transactionLog - }) - BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeTransferProcessor.sendTx hex ', uiData.selectedFee.blockchainData.rawTxHex) - // @ts-ignore - BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeTransferProcessor.sendTx result ', result) - await DogeRawDS.saveInputs({ - address: data.addressFrom, - currencyCode: this._settings.currencyCode, - transactionHash: result.transactionHash, - transactionRaw: inputsLog - }) - await DogeRawDS.saveJson({ - address: data.addressFrom, - currencyCode: this._settings.currencyCode, - transactionHash: result.transactionHash, - transactionRaw: JSON.stringify(result.transactionJson) - }) - - BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeTransferProcessor.sent ' + data.addressFrom + ' done ' + JSON.stringify(result.transactionJson)) - } catch (e) { - if (config.debug.cryptoErrors) { - console.log(this._settings.currencyCode + ' DogeTransferProcessor.sent error additional', e, uiData) - } - BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeTransferProcessor.sent error additional'+ e.message) - // noinspection ES6MissingAwait - MarketingEvent.logOnlyRealTime('v20_doge_tx_error2 ' + this._settings.currencyCode + ' ' + data.addressFrom + ' => ' + data.addressTo + ' ' + e.message, logData) - } - // noinspection ES6MissingAwait - MarketingEvent.logOnlyRealTime('v20_doge_tx_success ' + this._settings.currencyCode + ' ' + data.addressFrom + ' => ' + data.addressTo, logData) + try { + result.transactionFee = uiData.selectedFee.feeForTx; + result.transactionFeeCurrencyCode = this._settings.currencyCode; + result.transactionJson = { + nSequence: uiData.selectedFee.blockchainData.nSequence, + txAllowReplaceByFee: + uiData.selectedFee.blockchainData.txAllowReplaceByFee, + feeForByte: uiData.selectedFee.feeForByte + }; + if ( + typeof uiData.selectedFee.amountForTx !== 'undefined' && + uiData.selectedFee.amountForTx + ) { + result.amountForTx = uiData.selectedFee.amountForTx; + } + if (txRBF) { + await DogeRawDS.cleanRaw({ + address: data.addressFrom, + currencyCode: this._settings.currencyCode, + transactionHash: txRBF + }); + } + const transactionLog = + typeof result.logData !== 'undefined' ? result.logData : logData; + const inputsLog = JSON.stringify( + uiData.selectedFee.blockchainData.preparedInputsOutputs.inputs + ); + const transactionRaw = uiData.selectedFee.blockchainData.rawTxHex + ''; + //if (typeof transactionLog.selectedFee !== 'undefined' && typeof transactionLog.selectedFee.blockchainData !== 'undefined') { + // transactionLog.selectedFee.blockchainData = '*' + //} + await DogeRawDS.saveRaw({ + address: data.addressFrom, + currencyCode: this._settings.currencyCode, + transactionHash: result.transactionHash, + transactionRaw, + transactionLog + }); + BlocksoftCryptoLog.log( + this._settings.currencyCode + ' DogeTransferProcessor.sendTx hex ', + uiData.selectedFee.blockchainData.rawTxHex + ); + // @ts-ignore + BlocksoftCryptoLog.log( + this._settings.currencyCode + ' DogeTransferProcessor.sendTx result ', + result + ); + await DogeRawDS.saveInputs({ + address: data.addressFrom, + currencyCode: this._settings.currencyCode, + transactionHash: result.transactionHash, + transactionRaw: inputsLog + }); + await DogeRawDS.saveJson({ + address: data.addressFrom, + currencyCode: this._settings.currencyCode, + transactionHash: result.transactionHash, + transactionRaw: JSON.stringify(result.transactionJson) + }); - if (config.debug.cryptoErrors) { - console.log(this._settings.currencyCode + ' DogeTransferProcessor.sendTx result', JSON.parse(JSON.stringify(result))) - } - return result + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' DogeTransferProcessor.sent ' + + data.addressFrom + + ' done ' + + JSON.stringify(result.transactionJson) + ); + } catch (e) { + if (config.debug.cryptoErrors) { + console.log( + this._settings.currencyCode + + ' DogeTransferProcessor.sent error additional', + e, + uiData + ); + } + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' DogeTransferProcessor.sent error additional' + + e.message + ); + // noinspection ES6MissingAwait + MarketingEvent.logOnlyRealTime( + 'v20_doge_tx_error2 ' + + this._settings.currencyCode + + ' ' + + data.addressFrom + + ' => ' + + data.addressTo + + ' ' + + e.message, + logData + ); } + // noinspection ES6MissingAwait + MarketingEvent.logOnlyRealTime( + 'v20_doge_tx_success ' + + this._settings.currencyCode + + ' ' + + data.addressFrom + + ' => ' + + data.addressTo, + logData + ); - async sendRawTx(data: BlocksoftBlockchainTypes.DbAccount, rawTxHex: string, txRBF : any, logData : any): Promise { - this._initProviders() - const result = await this.sendProvider.sendTx(rawTxHex, 'rawSend', txRBF, logData) - return result.transactionHash + if (config.debug.cryptoErrors) { + console.log( + this._settings.currencyCode + ' DogeTransferProcessor.sendTx result', + JSON.parse(JSON.stringify(result)) + ); } + return result; + } - async setMissingTx(data: BlocksoftBlockchainTypes.DbAccount, transaction: BlocksoftBlockchainTypes.DbTransaction): Promise { - DogeRawDS.cleanRaw({ - address: data.address, - transactionHash: transaction.transactionHash, - currencyCode: this._settings.currencyCode - }) - MarketingEvent.logOnlyRealTime('v20_doge_tx_set_missing ' + this._settings.currencyCode + ' ' + data.address + ' => ' + transaction.addressTo, transaction) - return true - } + async sendRawTx( + data: BlocksoftBlockchainTypes.DbAccount, + rawTxHex: string, + txRBF: any, + logData: any + ): Promise { + this._initProviders(); + const result = await this.sendProvider.sendTx( + rawTxHex, + 'rawSend', + txRBF, + logData + ); + return result.transactionHash; + } - canRBF(data: BlocksoftBlockchainTypes.DbAccount, transaction: BlocksoftBlockchainTypes.DbTransaction): boolean { - if (transaction.transactionDirection === 'income') { - return true - } - if (typeof transaction.transactionJson !== 'undefined') { - // console.log('transaction.transactionJson', JSON.stringify(transaction.transactionJson)) - } - return true + async setMissingTx( + data: BlocksoftBlockchainTypes.DbAccount, + transaction: BlocksoftBlockchainTypes.DbTransaction + ): Promise { + DogeRawDS.cleanRaw({ + address: data.address, + transactionHash: transaction.transactionHash, + currencyCode: this._settings.currencyCode + }); + MarketingEvent.logOnlyRealTime( + 'v20_doge_tx_set_missing ' + + this._settings.currencyCode + + ' ' + + data.address + + ' => ' + + transaction.addressTo, + transaction + ); + return true; + } + + canRBF( + data: BlocksoftBlockchainTypes.DbAccount, + transaction: BlocksoftBlockchainTypes.DbTransaction + ): boolean { + if (transaction.transactionDirection === 'income') { + return true; + } + if (typeof transaction.transactionJson !== 'undefined') { + // console.log('transaction.transactionJson', JSON.stringify(transaction.transactionJson)) } + return true; + } } diff --git a/crypto/blockchains/doge/basic/DogeFindAddressFunction.js b/crypto/blockchains/doge/basic/DogeFindAddressFunction.js index 1d94ff168..b9aa310a3 100644 --- a/crypto/blockchains/doge/basic/DogeFindAddressFunction.js +++ b/crypto/blockchains/doge/basic/DogeFindAddressFunction.js @@ -19,111 +19,131 @@ * @returns {Promise<{from: string, to: string, value: number, direction: string}>} * @constructor */ -import BlocksoftUtils from '../../../common/BlocksoftUtils' -import BlocksoftBN from '../../../common/BlocksoftBN' +import BlocksoftUtils from '../../../common/BlocksoftUtils'; +import BlocksoftBN from '../../../common/BlocksoftBN'; export default async function DogeFindAddressFunction(addresses, transaction) { + const inputMyBN = new BlocksoftBN(0); + const inputOthersBN = new BlocksoftBN(0); + const inputOthersAddresses = []; + const uniqueTmp = {}; - const inputMyBN = new BlocksoftBN(0) - const inputOthersBN = new BlocksoftBN(0) - const inputOthersAddresses = [] - const uniqueTmp = {} - - const address1 = addresses[0] - const address2 = typeof addresses[1] !== 'undefined' ? addresses[1] : addresses[0] - const address3 = addresses[addresses.length - 1] // three is max now - if (transaction.vin) { - for (let i = 0, ic = transaction.vin.length; i < ic; i++) { - let vinAddress - let vinValue = transaction.vin[i].value - if (typeof transaction.vin[i].addresses !== 'undefined') { - vinAddress = transaction.vin[i].addresses[0] - } else if (typeof transaction.vin[i].addr !== 'undefined') { - vinAddress = transaction.vin[i].addr - } else if ( - typeof transaction.vin[i].prevout !== 'undefined' - ) { - if (typeof transaction.vin[i].prevout.scriptpubkey_address !== 'undefined') { - vinAddress = transaction.vin[i].prevout.scriptpubkey_address - } - if (typeof transaction.vin[i].prevout.value !== 'undefined') { - vinValue = transaction.vin[i].prevout.value - } - } - if (vinAddress === address1 || vinAddress === address2 || vinAddress === address3) { - inputMyBN.add(vinValue) - } else { - if (typeof uniqueTmp[vinAddress] === 'undefined') { - uniqueTmp[vinAddress] = 1 - inputOthersAddresses.push(vinAddress) - } - inputOthersBN.add(vinValue) - } + const address1 = addresses[0]; + const address2 = + typeof addresses[1] !== 'undefined' ? addresses[1] : addresses[0]; + const address3 = addresses[addresses.length - 1]; // three is max now + if (transaction.vin) { + for (let i = 0, ic = transaction.vin.length; i < ic; i++) { + let vinAddress; + let vinValue = transaction.vin[i].value; + if (typeof transaction.vin[i].addresses !== 'undefined') { + vinAddress = transaction.vin[i].addresses[0]; + } else if (typeof transaction.vin[i].addr !== 'undefined') { + vinAddress = transaction.vin[i].addr; + } else if (typeof transaction.vin[i].prevout !== 'undefined') { + if ( + typeof transaction.vin[i].prevout.scriptpubkey_address !== 'undefined' + ) { + vinAddress = transaction.vin[i].prevout.scriptpubkey_address; + } + if (typeof transaction.vin[i].prevout.value !== 'undefined') { + vinValue = transaction.vin[i].prevout.value; + } + } + if ( + vinAddress === address1 || + vinAddress === address2 || + vinAddress === address3 + ) { + inputMyBN.add(vinValue); + } else { + if (typeof uniqueTmp[vinAddress] === 'undefined') { + uniqueTmp[vinAddress] = 1; + inputOthersAddresses.push(vinAddress); } + inputOthersBN.add(vinValue); + } } + } - const outputMyBN = new BlocksoftBN(0) - const outputOthersBN = new BlocksoftBN(0) - const outputOthersAddresses = [] - const uniqueTmp2 = {} + const outputMyBN = new BlocksoftBN(0); + const outputOthersBN = new BlocksoftBN(0); + const outputOthersAddresses = []; + const uniqueTmp2 = {}; - if (transaction.vout) { - for (let j = 0, jc = transaction.vout.length; j < jc; j++) { - let voutAddress - const voutValue = transaction.vout[j].value - if (typeof transaction.vout[j].addresses !== 'undefined') { - voutAddress = transaction.vout[j].addresses[0] - } else if (typeof transaction.vout[j].scriptPubKey !== 'undefined' && typeof transaction.vout[j].scriptPubKey.addresses !== 'undefined') { - voutAddress = transaction.vout[j].scriptPubKey.addresses[0] - } else if (typeof transaction.vout[j].scriptpubkey_address !== 'undefined') { - voutAddress = transaction.vout[j].scriptpubkey_address - } - if (voutAddress === address1 || voutAddress === address2 || voutAddress === address3) { - outputMyBN.add(voutValue) - } else { - if (typeof uniqueTmp2[voutAddress] === 'undefined') { - uniqueTmp2[voutAddress] = 1 - outputOthersAddresses.push(voutAddress) - } - outputOthersBN.add(voutValue) - } + if (transaction.vout) { + for (let j = 0, jc = transaction.vout.length; j < jc; j++) { + let voutAddress; + const voutValue = transaction.vout[j].value; + if (typeof transaction.vout[j].addresses !== 'undefined') { + voutAddress = transaction.vout[j].addresses[0]; + } else if ( + typeof transaction.vout[j].scriptPubKey !== 'undefined' && + typeof transaction.vout[j].scriptPubKey.addresses !== 'undefined' + ) { + voutAddress = transaction.vout[j].scriptPubKey.addresses[0]; + } else if ( + typeof transaction.vout[j].scriptpubkey_address !== 'undefined' + ) { + voutAddress = transaction.vout[j].scriptpubkey_address; + } + if ( + voutAddress === address1 || + voutAddress === address2 || + voutAddress === address3 + ) { + outputMyBN.add(voutValue); + } else { + if (typeof uniqueTmp2[voutAddress] === 'undefined') { + uniqueTmp2[voutAddress] = 1; + outputOthersAddresses.push(voutAddress); } + outputOthersBN.add(voutValue); + } } + } - let output - if (inputMyBN.get() === '0') { // my only in output - output = { - direction: 'income', - from: inputOthersAddresses.length > 0 ? inputOthersAddresses.join(',') : '', - to: '', // address1, - value: outputMyBN.get() - } - } else if (outputMyBN.get() === '0') { // my only in input - output = { - direction: 'outcome', - from: '', // address1, - to: outputOthersAddresses.length > 0 ? outputOthersAddresses.join(',') : '', - value: (inputOthersBN.get() === '0') ? outputOthersBN.get() : inputMyBN.get() - } - } else { // both input and output - if (outputOthersAddresses.length > 0) {// there are other address - output = { - direction: 'outcome', - from: '', // address1, - to: outputOthersAddresses.join(','), - value: outputOthersBN.get() - } - } else { - output = { - direction: 'self', - from: '', // address1, - to: '', // address1, - value: inputMyBN.diff(outputMyBN).get() - } - } + let output; + if (inputMyBN.get() === '0') { + // my only in output + output = { + direction: 'income', + from: + inputOthersAddresses.length > 0 ? inputOthersAddresses.join(',') : '', + to: '', // address1, + value: outputMyBN.get() + }; + } else if (outputMyBN.get() === '0') { + // my only in input + output = { + direction: 'outcome', + from: '', // address1, + to: + outputOthersAddresses.length > 0 ? outputOthersAddresses.join(',') : '', + value: + inputOthersBN.get() === '0' ? outputOthersBN.get() : inputMyBN.get() + }; + } else { + // both input and output + if (outputOthersAddresses.length > 0) { + // there are other address + output = { + direction: 'outcome', + from: '', // address1, + to: outputOthersAddresses.join(','), + value: outputOthersBN.get() + }; + } else { + output = { + direction: 'self', + from: '', // address1, + to: '', // address1, + value: inputMyBN.diff(outputMyBN).get() + }; } - output.from = output.from.substr(0, 255) - output.to = output.to.substr(0, 255) + } + output.from = output.from.substr(0, 255); + output.to = output.to.substr(0, 255); - return output + return output; } diff --git a/crypto/blockchains/doge/basic/DogeLogs.ts b/crypto/blockchains/doge/basic/DogeLogs.ts index 4a5025baf..8cccf7e3f 100644 --- a/crypto/blockchains/doge/basic/DogeLogs.ts +++ b/crypto/blockchains/doge/basic/DogeLogs.ts @@ -1,86 +1,114 @@ /** * @version 0.20 */ -import { BlocksoftBlockchainTypes } from '../../BlocksoftBlockchainTypes' -import BlocksoftBN from '../../../common/BlocksoftBN' -import BlocksoftUtils from '../../../common/BlocksoftUtils' -import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog' -import MarketingEvent from '../../../../app/services/Marketing/MarketingEvent' +import { BlocksoftBlockchainTypes } from '../../BlocksoftBlockchainTypes'; +import BlocksoftBN from '../../../common/BlocksoftBN'; +import BlocksoftUtils from '../../../common/BlocksoftUtils'; +import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; +import MarketingEvent from '../../../../app/services/Marketing/MarketingEvent'; export namespace DogeLogs { - export const logInputsOutputs = function (data: BlocksoftBlockchainTypes.TransferData, - unspents: BlocksoftBlockchainTypes.UnspentTx[], - preparedInputsOutputs: { - inputs: BlocksoftBlockchainTypes.UnspentTx[], - outputs: BlocksoftBlockchainTypes.OutputTx[], - multiAddress: [], - msg: string, - }, settings: any, title: string): any { - const logInputsOutputs = { - inputs: [], - outputs: [], - totalIn: 0, - totalOut: 0, - diffInOut: 0, - msg: preparedInputsOutputs.msg || 'none' - } - const totalInBN = new BlocksoftBN(0) - const totalOutBN = new BlocksoftBN(0) - const totalBalanceBN = new BlocksoftBN(0) - if (typeof unspents !== 'undefined' && unspents && unspents.length > 0) { - for (const unspent of unspents) { - totalBalanceBN.add(unspent.value) - } - } + export const logInputsOutputs = function ( + data: BlocksoftBlockchainTypes.TransferData, + unspents: BlocksoftBlockchainTypes.UnspentTx[], + preparedInputsOutputs: { + inputs: BlocksoftBlockchainTypes.UnspentTx[]; + outputs: BlocksoftBlockchainTypes.OutputTx[]; + multiAddress: []; + msg: string; + }, + settings: any, + title: string + ): any { + const logInputsOutputs = { + inputs: [], + outputs: [], + totalIn: 0, + totalOut: 0, + diffInOut: 0, + msg: preparedInputsOutputs.msg || 'none' + }; + const totalInBN = new BlocksoftBN(0); + const totalOutBN = new BlocksoftBN(0); + const totalBalanceBN = new BlocksoftBN(0); + if (typeof unspents !== 'undefined' && unspents && unspents.length > 0) { + for (const unspent of unspents) { + totalBalanceBN.add(unspent.value); + } + } - const leftBalanceBN = new BlocksoftBN(totalBalanceBN) - const sendBalanceBN = new BlocksoftBN(0) - let input, output - if (preparedInputsOutputs) { - for (input of preparedInputsOutputs.inputs) { - logInputsOutputs.inputs.push({ - txid: input.txid, - vout: input.vout, - value: input.value, - confirmations: input.confirmations, - address: input.address || 'none' - }) - totalInBN.add(input.value) - leftBalanceBN.diff(input.value) - } - for (output of preparedInputsOutputs.outputs) { - if (output.amount === 'removed') continue - logInputsOutputs.outputs.push(output) - totalOutBN.add(output.amount) - if (typeof output.isChange === 'undefined' || !output.isChange) { - sendBalanceBN.add(output.amount) - } - } + const leftBalanceBN = new BlocksoftBN(totalBalanceBN); + const sendBalanceBN = new BlocksoftBN(0); + let input, output; + if (preparedInputsOutputs) { + for (input of preparedInputsOutputs.inputs) { + logInputsOutputs.inputs.push({ + txid: input.txid, + vout: input.vout, + value: input.value, + confirmations: input.confirmations, + address: input.address || 'none' + }); + totalInBN.add(input.value); + leftBalanceBN.diff(input.value); + } + for (output of preparedInputsOutputs.outputs) { + if (output.amount === 'removed') continue; + logInputsOutputs.outputs.push(output); + totalOutBN.add(output.amount); + if (typeof output.isChange === 'undefined' || !output.isChange) { + sendBalanceBN.add(output.amount); } - logInputsOutputs.totalIn = totalInBN.get() - logInputsOutputs.totalOut = totalOutBN.get() - logInputsOutputs.diffInOut = totalInBN.diff(totalOutBN).get() - logInputsOutputs.diffInOutReadable = BlocksoftUtils.toUnified(logInputsOutputs.diffInOut, settings.decimals) + } + } + logInputsOutputs.totalIn = totalInBN.get(); + logInputsOutputs.totalOut = totalOutBN.get(); + logInputsOutputs.diffInOut = totalInBN.diff(totalOutBN).get(); + logInputsOutputs.diffInOutReadable = BlocksoftUtils.toUnified( + logInputsOutputs.diffInOut, + settings.decimals + ); - const tmpBN = new BlocksoftBN(totalOutBN).diff(data.amount) - if (logInputsOutputs.diffInOut > 0) { - tmpBN.add(logInputsOutputs.diffInOut) - } - logInputsOutputs.totalOutMinusAmount = tmpBN.get() - logInputsOutputs.totalBalance = totalBalanceBN.get() - logInputsOutputs.leftBalance = leftBalanceBN.get() - logInputsOutputs.leftBalanceAndChange = BlocksoftUtils.add(leftBalanceBN, tmpBN) - logInputsOutputs.sendBalance = sendBalanceBN.get() + const tmpBN = new BlocksoftBN(totalOutBN).diff(data.amount); + if (logInputsOutputs.diffInOut > 0) { + tmpBN.add(logInputsOutputs.diffInOut); + } + logInputsOutputs.totalOutMinusAmount = tmpBN.get(); + logInputsOutputs.totalBalance = totalBalanceBN.get(); + logInputsOutputs.leftBalance = leftBalanceBN.get(); + logInputsOutputs.leftBalanceAndChange = BlocksoftUtils.add( + leftBalanceBN, + tmpBN + ); + logInputsOutputs.sendBalance = sendBalanceBN.get(); - logInputsOutputs.data = JSON.parse(JSON.stringify(data)) - if (typeof data.feeForTx === 'undefined' || typeof data.feeForTx.feeForByte === 'undefined' || data.feeForTx.feeForByte < 0) { - BlocksoftCryptoLog.log(title + ' preparedInputsOutputs with autofee ', logInputsOutputs) - } else { - BlocksoftCryptoLog.log(title + ' preparedInputsOutputs with fee ' + data.feeForTx.feeForTx, logInputsOutputs) - } - // console.log('btc_info ' + this._settings.currencyCode + ' ' + data.addressFrom + ' => ' + data.addressTo, logInputsOutputs) - // noinspection JSIgnoredPromiseFromCall - MarketingEvent.logOnlyRealTime('v20_doge_info ' + settings.currencyCode + ' ' + data.addressFrom + ' => ' + data.addressTo, logInputsOutputs) - return logInputsOutputs + logInputsOutputs.data = JSON.parse(JSON.stringify(data)); + if ( + typeof data.feeForTx === 'undefined' || + typeof data.feeForTx.feeForByte === 'undefined' || + data.feeForTx.feeForByte < 0 + ) { + BlocksoftCryptoLog.log( + title + ' preparedInputsOutputs with autofee ', + logInputsOutputs + ); + } else { + BlocksoftCryptoLog.log( + title + ' preparedInputsOutputs with fee ' + data.feeForTx.feeForTx, + logInputsOutputs + ); } + // console.log('btc_info ' + this._settings.currencyCode + ' ' + data.addressFrom + ' => ' + data.addressTo, logInputsOutputs) + // noinspection JSIgnoredPromiseFromCall + MarketingEvent.logOnlyRealTime( + 'v20_doge_info ' + + settings.currencyCode + + ' ' + + data.addressFrom + + ' => ' + + data.addressTo, + logInputsOutputs + ); + return logInputsOutputs; + }; } diff --git a/crypto/blockchains/doge/basic/DogeNetworkPrices.ts b/crypto/blockchains/doge/basic/DogeNetworkPrices.ts index 0931eb05c..56689fc15 100644 --- a/crypto/blockchains/doge/basic/DogeNetworkPrices.ts +++ b/crypto/blockchains/doge/basic/DogeNetworkPrices.ts @@ -1,31 +1,45 @@ /** * @version 0.20 **/ -import { BlocksoftBlockchainTypes } from '../../BlocksoftBlockchainTypes' -import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog' -import BlocksoftExternalSettings from '../../../common/BlocksoftExternalSettings' +import { BlocksoftBlockchainTypes } from '../../BlocksoftBlockchainTypes'; +import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; +import BlocksoftExternalSettings from '../../../common/BlocksoftExternalSettings'; -export default class DogeNetworkPrices implements BlocksoftBlockchainTypes.NetworkPrices { +export default class DogeNetworkPrices + implements BlocksoftBlockchainTypes.NetworkPrices +{ + async getNetworkPrices(currencyCode: string): Promise<{ + speed_blocks_2: number; + speed_blocks_6: number; + speed_blocks_12: number; + }> { + BlocksoftCryptoLog.log(currencyCode + ' DogeNetworkPricesProvider '); - async getNetworkPrices(currencyCode: string) : Promise<{ 'speed_blocks_2': number, 'speed_blocks_6': number, 'speed_blocks_12': number }>{ + const externalSettings = await BlocksoftExternalSettings.getAll( + 'DOGE.getNetworkPrices' + ); - BlocksoftCryptoLog.log(currencyCode + ' DogeNetworkPricesProvider ') - - const externalSettings = await BlocksoftExternalSettings.getAll('DOGE.getNetworkPrices') - - // @ts-ignore - if (!externalSettings || typeof externalSettings[currencyCode] === 'undefined') { - throw new Error(currencyCode + ' DogeNetworkPricesProvider ' + currencyCode + ' not defined') - } - - const prices = { - // @ts-ignore - 'speed_blocks_2': externalSettings[currencyCode]['2'] || 0, - // @ts-ignore - 'speed_blocks_6': externalSettings[currencyCode]['6'] || 0, - // @ts-ignore - 'speed_blocks_12': externalSettings[currencyCode]['12'] || 0 - } - return prices + // @ts-ignore + if ( + !externalSettings || + typeof externalSettings[currencyCode] === 'undefined' + ) { + throw new Error( + currencyCode + + ' DogeNetworkPricesProvider ' + + currencyCode + + ' not defined' + ); } + + const prices = { + // @ts-ignore + speed_blocks_2: externalSettings[currencyCode]['2'] || 0, + // @ts-ignore + speed_blocks_6: externalSettings[currencyCode]['6'] || 0, + // @ts-ignore + speed_blocks_12: externalSettings[currencyCode]['12'] || 0 + }; + return prices; + } } diff --git a/crypto/blockchains/doge/providers/DogeSendProvider.ts b/crypto/blockchains/doge/providers/DogeSendProvider.ts index 1421a8150..576caa97d 100644 --- a/crypto/blockchains/doge/providers/DogeSendProvider.ts +++ b/crypto/blockchains/doge/providers/DogeSendProvider.ts @@ -2,225 +2,424 @@ * @version 0.20 * https://github.com/trezor/blockbook/blob/master/docs/api.md */ -import { BlocksoftBlockchainTypes } from '../../BlocksoftBlockchainTypes' -import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog' -import BlocksoftAxios from '../../../common/BlocksoftAxios' -import BlocksoftExternalSettings from '../../../common/BlocksoftExternalSettings' -import config from '../../../../app/config/config' -import MarketingEvent from '../../../../app/services/Marketing/MarketingEvent' +import { BlocksoftBlockchainTypes } from '../../BlocksoftBlockchainTypes'; +import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; +import BlocksoftAxios from '../../../common/BlocksoftAxios'; +import BlocksoftExternalSettings from '../../../common/BlocksoftExternalSettings'; +import config from '../../../../app/config/config'; +import MarketingEvent from '../../../../app/services/Marketing/MarketingEvent'; -export default class DogeSendProvider implements BlocksoftBlockchainTypes.SendProvider { +export default class DogeSendProvider + implements BlocksoftBlockchainTypes.SendProvider +{ + protected _trezorServerCode: string = ''; - protected _trezorServerCode: string = '' + private _trezorServer: string = ''; - private _trezorServer: string = '' + protected _settings: BlocksoftBlockchainTypes.CurrencySettings; - protected _settings: BlocksoftBlockchainTypes.CurrencySettings + private _proxy: string; - private _proxy: string + private _errorProxy: string; - private _errorProxy: string + private _successProxy: string; - private _successProxy: string + constructor( + settings: BlocksoftBlockchainTypes.CurrencySettings, + serverCode: string + ) { + this._settings = settings; + this._trezorServerCode = serverCode; - constructor(settings: BlocksoftBlockchainTypes.CurrencySettings, serverCode: string) { - this._settings = settings - this._trezorServerCode = serverCode + const { apiEndpoints } = config.proxy; + const baseURL = MarketingEvent.DATA.LOG_TESTER + ? apiEndpoints.baseURLTest + : apiEndpoints.baseURL; + this._proxy = baseURL + '/send/checktx'; + this._errorProxy = baseURL + '/send/errortx'; + this._successProxy = baseURL + '/send/sendtx'; + } - const { apiEndpoints } = config.proxy - const baseURL = MarketingEvent.DATA.LOG_TESTER ? apiEndpoints.baseURLTest : apiEndpoints.baseURL - this._proxy = baseURL + '/send/checktx' - this._errorProxy = baseURL + '/send/errortx' - this._successProxy = baseURL + '/send/sendtx' + async _check(hex: string, subtitle: string, txRBF: any, logData: any) { + let checkResult = false; + try { + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' DogeSendProvider.sendTx ' + + subtitle + + ' proxy checkResult start ' + + this._proxy, + logData + ); + if (config.debug.cryptoErrors) { + console.log( + new Date().toISOString() + + ' ' + + this._settings.currencyCode + + ' DogeSendProvider.sendTx ' + + subtitle + + ' proxy checkResult start ' + + this._proxy + ); + } + checkResult = await BlocksoftAxios.post(this._proxy, { + raw: hex, + txRBF, + logData, + marketingData: MarketingEvent.DATA + }); + if (config.debug.cryptoErrors) { + console.log( + new Date().toISOString() + + ' ' + + this._settings.currencyCode + + ' DogeSendProvider.sendTx ' + + subtitle + + ' proxy checkResult end ' + + this._proxy + ); + } + } catch (e) { + if (config.debug.cryptoErrors) { + console.log( + this._settings.currencyCode + + ' DogeSendProvider.send proxy error checkResult ' + + e.message + ); + } + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' DogeSendProvider.send proxy error checkResult ' + + e.message + ); } - async _check(hex: string, subtitle: string, txRBF: any, logData: any) { - let checkResult = false - try { - BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeSendProvider.sendTx ' + subtitle + ' proxy checkResult start ' + this._proxy, logData) - if (config.debug.cryptoErrors) { - console.log(new Date().toISOString() + ' ' + this._settings.currencyCode + ' DogeSendProvider.sendTx ' + subtitle + ' proxy checkResult start ' + this._proxy) - } - checkResult = await BlocksoftAxios.post(this._proxy, { - raw: hex, - txRBF, - logData, - marketingData: MarketingEvent.DATA - }) - if (config.debug.cryptoErrors) { - console.log(new Date().toISOString() + ' ' + this._settings.currencyCode + ' DogeSendProvider.sendTx ' + subtitle + ' proxy checkResult end ' + this._proxy) - } - } catch (e) { - if (config.debug.cryptoErrors) { - console.log(this._settings.currencyCode + ' DogeSendProvider.send proxy error checkResult ' + e.message) - } - BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeSendProvider.send proxy error checkResult ' + e.message) + if (checkResult !== false) { + if (typeof checkResult.data !== 'undefined') { + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' DogeSendProvider.send proxy checkResult1 ', + checkResult.data + ); + if ( + typeof checkResult.data.status === 'undefined' || + checkResult.data.status === 'error' + ) { + if (config.debug.cryptoErrors) { + console.log( + this._settings.currencyCode + + ' DogeSendProvider.send proxy error checkResult1 ', + checkResult + ); + } + checkResult = false; + } else if (checkResult.data.status === 'notice') { + throw new Error(checkResult.data.msg); } - - if (checkResult !== false) { - if (typeof checkResult.data !== 'undefined') { - BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeSendProvider.send proxy checkResult1 ', checkResult.data) - if (typeof checkResult.data.status === 'undefined' || checkResult.data.status === 'error') { - if (config.debug.cryptoErrors) { - console.log(this._settings.currencyCode + ' DogeSendProvider.send proxy error checkResult1 ', checkResult) - } - checkResult = false - } else if (checkResult.data.status === 'notice') { - throw new Error(checkResult.data.msg) - } - } else { - BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeSendProvider.send proxy checkResult2 ', checkResult) - if (config.debug.cryptoErrors) { - console.log(this._settings.currencyCode + ' DogeSendProvider.send proxy error checkResult2 ', checkResult) - } - } - } else { - if (config.debug.cryptoErrors) { - console.log(this._settings.currencyCode + ' DogeSendProvider.send proxy error checkResultEmpty ', checkResult) - } - } - if (typeof logData === 'undefined' || !logData) { - logData = {} + } else { + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' DogeSendProvider.send proxy checkResult2 ', + checkResult + ); + if (config.debug.cryptoErrors) { + console.log( + this._settings.currencyCode + + ' DogeSendProvider.send proxy error checkResult2 ', + checkResult + ); } - logData.checkResult = checkResult && typeof checkResult.data !== 'undefined' && checkResult.data ? JSON.parse(JSON.stringify(checkResult.data)) : false - return logData + } + } else { + if (config.debug.cryptoErrors) { + console.log( + this._settings.currencyCode + + ' DogeSendProvider.send proxy error checkResultEmpty ', + checkResult + ); + } } + if (typeof logData === 'undefined' || !logData) { + logData = {}; + } + logData.checkResult = + checkResult && typeof checkResult.data !== 'undefined' && checkResult.data + ? JSON.parse(JSON.stringify(checkResult.data)) + : false; + return logData; + } - async _checkError(hex: string, subtitle: string, txRBF: any, logData: any) { - BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeSendProvider.send proxy errorTx start ' + this._errorProxy, logData) - if (config.debug.cryptoErrors) { - console.log(new Date().toISOString() + ' ' + this._settings.currencyCode + ' DogeSendProvider.sendTx ' + subtitle + ' proxy errorTx start ' + this._errorProxy) + async _checkError(hex: string, subtitle: string, txRBF: any, logData: any) { + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' DogeSendProvider.send proxy errorTx start ' + + this._errorProxy, + logData + ); + if (config.debug.cryptoErrors) { + console.log( + new Date().toISOString() + + ' ' + + this._settings.currencyCode + + ' DogeSendProvider.sendTx ' + + subtitle + + ' proxy errorTx start ' + + this._errorProxy + ); + } + const res2 = await BlocksoftAxios.post(this._errorProxy, { + raw: hex, + txRBF, + logData, + marketingData: MarketingEvent.DATA + }); + if (config.debug.cryptoErrors) { + console.log( + new Date().toISOString() + + ' ' + + this._settings.currencyCode + + ' DogeSendProvider.sendTx ' + + subtitle + + ' proxy errorTx result ', + JSON.parse(JSON.stringify(res2.data)) + ); + } + BlocksoftCryptoLog.log( + this._settings.currencyCode + ' DogeSendProvider.send proxy errorTx', + typeof res2.data !== 'undefined' ? res2.data : res2 + ); + } + + async _checkSuccess( + transactionHash: string, + hex: string, + subtitle: string, + txRBF: any, + logData: any + ) { + let checkResult = false; + try { + logData.txHash = transactionHash; + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' DogeSendProvider.send proxy successTx start ' + + this._successProxy, + logData + ); + if (config.debug.cryptoErrors) { + console.log( + new Date().toISOString() + + ' ' + + this._settings.currencyCode + + ' DogeSendProvider.sendTx ' + + subtitle + + ' proxy successTx start ' + + this._successProxy + ); + } + checkResult = await BlocksoftAxios.post(this._successProxy, { + raw: hex, + txRBF, + logData, + marketingData: MarketingEvent.DATA + }); + if (config.debug.cryptoErrors) { + console.log( + new Date().toISOString() + + ' ' + + this._settings.currencyCode + + ' DogeSendProvider.sendTx ' + + subtitle + + ' proxy successTx result ', + JSON.parse(JSON.stringify(checkResult.data)) + ); + } + } catch (e3) { + if (config.debug.cryptoErrors) { + console.log( + this._settings.currencyCode + + ' DogeSendProvider.send proxy error successTx ' + + e3.message + ); + } + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' DogeSendProvider.send proxy error successTx ' + + e3.message + ); + } + + if (checkResult !== false) { + if (typeof checkResult.data !== 'undefined') { + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' DogeSendProvider.send proxy successResult1 ', + checkResult.data + ); + if ( + typeof checkResult.data.status === 'undefined' || + checkResult.data.status === 'error' + ) { + if (config.debug.cryptoErrors) { + console.log( + this._settings.currencyCode + + ' DogeSendProvider.send proxy error successResult1 ', + checkResult + ); + } + checkResult = false; + } else if (checkResult.data.status === 'notice') { + throw new Error(checkResult.data.msg); } - const res2 = await BlocksoftAxios.post(this._errorProxy, { - raw: hex, - txRBF, - logData, - marketingData: MarketingEvent.DATA - }) + } else { + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' DogeSendProvider.send proxy successResult2 ', + checkResult + ); if (config.debug.cryptoErrors) { - console.log(new Date().toISOString() + ' ' + this._settings.currencyCode + ' DogeSendProvider.sendTx ' + subtitle + ' proxy errorTx result ', JSON.parse(JSON.stringify(res2.data))) + console.log( + this._settings.currencyCode + + ' DogeSendProvider.send proxy error successResult2 ', + checkResult + ); } - BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeSendProvider.send proxy errorTx', typeof res2.data !== 'undefined' ? res2.data : res2) + } + } else { + if (config.debug.cryptoErrors) { + console.log( + this._settings.currencyCode + + ' DogeSendProvider.send proxy error successResultEmpty ', + checkResult + ); + } } + logData.successResult = + checkResult && typeof checkResult.data !== 'undefined' && checkResult.data + ? JSON.parse(JSON.stringify(checkResult.data)) + : false; + logData.txRBF = txRBF; + return logData; + } - async _checkSuccess(transactionHash: string, hex: string, subtitle: string, txRBF: any, logData: any) { - let checkResult = false - try { - logData.txHash = transactionHash - BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeSendProvider.send proxy successTx start ' + this._successProxy, logData) - if (config.debug.cryptoErrors) { - console.log(new Date().toISOString() + ' ' + this._settings.currencyCode + ' DogeSendProvider.sendTx ' + subtitle + ' proxy successTx start ' + this._successProxy) - } - checkResult = await BlocksoftAxios.post(this._successProxy, { - raw: hex, - txRBF, - logData, - marketingData: MarketingEvent.DATA - }) - if (config.debug.cryptoErrors) { - console.log(new Date().toISOString() + ' ' + this._settings.currencyCode + ' DogeSendProvider.sendTx ' + subtitle + ' proxy successTx result ', JSON.parse(JSON.stringify(checkResult.data))) - } - } catch (e3) { - if (config.debug.cryptoErrors) { - console.log(this._settings.currencyCode + ' DogeSendProvider.send proxy error successTx ' + e3.message) - } - BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeSendProvider.send proxy error successTx ' + e3.message) - } + async sendTx( + hex: string, + subtitle: string, + txRBF: any, + logData: any + ): Promise<{ transactionHash: string; transactionJson: any }> { + await BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' DogeSendProvider.sendTx ' + + subtitle + + ' started ', + logData + ); - if (checkResult !== false) { - if (typeof checkResult.data !== 'undefined') { - BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeSendProvider.send proxy successResult1 ', checkResult.data) - if (typeof checkResult.data.status === 'undefined' || checkResult.data.status === 'error') { - if (config.debug.cryptoErrors) { - console.log(this._settings.currencyCode + ' DogeSendProvider.send proxy error successResult1 ', checkResult) - } - checkResult = false - } else if (checkResult.data.status === 'notice') { - throw new Error(checkResult.data.msg) - } - } else { - BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeSendProvider.send proxy successResult2 ', checkResult) - if (config.debug.cryptoErrors) { - console.log(this._settings.currencyCode + ' DogeSendProvider.send proxy error successResult2 ', checkResult) - } - } - } else { - if (config.debug.cryptoErrors) { - console.log(this._settings.currencyCode + ' DogeSendProvider.send proxy error successResultEmpty ', checkResult) - } - } - logData.successResult = checkResult && typeof checkResult.data !== 'undefined' && checkResult.data ? JSON.parse(JSON.stringify(checkResult.data)) : false - logData.txRBF = txRBF - return logData + let link = BlocksoftExternalSettings.getStatic( + this._trezorServerCode + '_SEND_LINK' + ); + if (!link || link === '') { + this._trezorServer = await BlocksoftExternalSettings.getTrezorServer( + this._trezorServerCode, + 'DOGE.Send.sendTx' + ); + link = this._trezorServer + '/api/v2/sendtx/'; } - async sendTx(hex: string, subtitle: string, txRBF: any, logData: any): Promise<{ transactionHash: string, transactionJson: any }> { - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeSendProvider.sendTx ' + subtitle + ' started ', logData) + logData = await this._check(hex, subtitle, txRBF, logData); - let link = BlocksoftExternalSettings.getStatic(this._trezorServerCode + '_SEND_LINK') - if (!link || link === '') { - this._trezorServer = await BlocksoftExternalSettings.getTrezorServer(this._trezorServerCode, 'DOGE.Send.sendTx') - link = this._trezorServer + '/api/v2/sendtx/' - } - - logData = await this._check(hex, subtitle, txRBF, logData) - - let res - try { - res = await BlocksoftAxios.post(link, hex) - } catch (e) { - if (config.debug.cryptoErrors) { - console.log(this._settings.currencyCode + ' DogeSendProvider.sendTx error ', e) - } - if (subtitle.indexOf('rawSend') !== -1) { - throw e - } - try { - logData.error = e.message - await this._checkError(hex, subtitle, txRBF, logData) - } catch (e2) { - if (config.debug.cryptoErrors) { - console.log(this._settings.currencyCode + ' DogeSendProvider.send proxy error errorTx ' + e.message) - } - BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeSendProvider.send proxy error errorTx ' + e2.message) - } - if (this._settings.currencyCode === 'USDT' && e.message.indexOf('bad-txns-in-belowout') !== -1) { - throw new Error('SERVER_RESPONSE_NOT_ENOUGH_FEE') - } else if (e.message.indexOf('transaction already in block') !== -1) { - throw new Error('SERVER_RESPONSE_TRANSACTION_ALREADY_MINED') - } else if (e.message.indexOf('inputs-missingorspent') !== -1) { - throw new Error('SERVER_RESPONSE_TRANSACTION_ALREADY_MINED') - } else if (e.message.indexOf('insufficient priority') !== -1) { - throw new Error('SERVER_RESPONSE_NO_RESPONSE_OR_MORE_FEE') - } else if (e.message.indexOf('dust') !== -1) { - throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_DUST') - } else if (e.message.indexOf('bad-txns-inputs-spent') !== -1 || e.message.indexOf('txn-mempool-conflict') !== -1) { - throw new Error('SERVER_RESPONSE_NO_RESPONSE') - } else if (e.message.indexOf('min relay fee not met') !== -1 || e.message.indexOf('fee for relay') !== -1) { - throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_FEE') - } else if (e.message.indexOf('insufficient fee, rejecting replacement') !== -1) { - throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_FEE_FOR_REPLACEMENT') - } else if (e.message.indexOf('insufficient fee') !== -1) { - throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_FEE') - } else if (e.message.indexOf('too-long-mempool-chain') !== -1) { - throw new Error('SERVER_RESPONSE_NO_RESPONSE') - } else { - await BlocksoftExternalSettings.setTrezorServerInvalid(this._trezorServerCode, this._trezorServer) - e.message += ' link: ' + link - throw e - } - } - if (typeof res.data.result === 'undefined' || !res.data.result) { - if (config.debug.cryptoErrors) { - console.log(this._settings.currencyCode + 'DogeSendProvider.send no txid', res.data) - } - throw new Error('SERVER_RESPONSE_NOT_CONNECTED') + let res; + try { + res = await BlocksoftAxios.post(link, hex); + } catch (e) { + if (config.debug.cryptoErrors) { + console.log( + this._settings.currencyCode + ' DogeSendProvider.sendTx error ', + e + ); + } + if (subtitle.indexOf('rawSend') !== -1) { + throw e; + } + try { + logData.error = e.message; + await this._checkError(hex, subtitle, txRBF, logData); + } catch (e2) { + if (config.debug.cryptoErrors) { + console.log( + this._settings.currencyCode + + ' DogeSendProvider.send proxy error errorTx ' + + e.message + ); } + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' DogeSendProvider.send proxy error errorTx ' + + e2.message + ); + } + if ( + this._settings.currencyCode === 'USDT' && + e.message.indexOf('bad-txns-in-belowout') !== -1 + ) { + throw new Error('SERVER_RESPONSE_NOT_ENOUGH_FEE'); + } else if (e.message.indexOf('transaction already in block') !== -1) { + throw new Error('SERVER_RESPONSE_TRANSACTION_ALREADY_MINED'); + } else if (e.message.indexOf('inputs-missingorspent') !== -1) { + throw new Error('SERVER_RESPONSE_TRANSACTION_ALREADY_MINED'); + } else if (e.message.indexOf('insufficient priority') !== -1) { + throw new Error('SERVER_RESPONSE_NO_RESPONSE_OR_MORE_FEE'); + } else if (e.message.indexOf('dust') !== -1) { + throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_DUST'); + } else if ( + e.message.indexOf('bad-txns-inputs-spent') !== -1 || + e.message.indexOf('txn-mempool-conflict') !== -1 + ) { + throw new Error('SERVER_RESPONSE_NO_RESPONSE'); + } else if ( + e.message.indexOf('min relay fee not met') !== -1 || + e.message.indexOf('fee for relay') !== -1 + ) { + throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_FEE'); + } else if ( + e.message.indexOf('insufficient fee, rejecting replacement') !== -1 + ) { + throw new Error( + 'SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_FEE_FOR_REPLACEMENT' + ); + } else if (e.message.indexOf('insufficient fee') !== -1) { + throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_FEE'); + } else if (e.message.indexOf('too-long-mempool-chain') !== -1) { + throw new Error('SERVER_RESPONSE_NO_RESPONSE'); + } else { + await BlocksoftExternalSettings.setTrezorServerInvalid( + this._trezorServerCode, + this._trezorServer + ); + e.message += ' link: ' + link; + throw e; + } + } + if (typeof res.data.result === 'undefined' || !res.data.result) { + if (config.debug.cryptoErrors) { + console.log( + this._settings.currencyCode + 'DogeSendProvider.send no txid', + res.data + ); + } + throw new Error('SERVER_RESPONSE_NOT_CONNECTED'); + } - const transactionHash = res.data.result - logData = await this._checkSuccess(transactionHash, hex, subtitle, txRBF, logData) + const transactionHash = res.data.result; + logData = await this._checkSuccess( + transactionHash, + hex, + subtitle, + txRBF, + logData + ); - return { transactionHash, transactionJson: {}, logData } - } + return { transactionHash, transactionJson: {}, logData }; + } } - diff --git a/crypto/blockchains/doge/providers/DogeUnspentsProvider.ts b/crypto/blockchains/doge/providers/DogeUnspentsProvider.ts index 20a2a6f4a..bb84d07ce 100644 --- a/crypto/blockchains/doge/providers/DogeUnspentsProvider.ts +++ b/crypto/blockchains/doge/providers/DogeUnspentsProvider.ts @@ -3,199 +3,272 @@ * https://github.com/trezor/blockbook/blob/master/docs/api.md * https://doge1.trezor.io/api/v2/utxo/D5oKvWEibVe74CXLASmhpkRpLoyjgZhm71 */ -import { BlocksoftBlockchainTypes } from '../../BlocksoftBlockchainTypes' -import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog' -import BlocksoftAxios from '../../../common/BlocksoftAxios' -import BlocksoftExternalSettings from '../../../common/BlocksoftExternalSettings' -import DogeRawDS from '../stores/DogeRawDS' +import { BlocksoftBlockchainTypes } from '../../BlocksoftBlockchainTypes'; +import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; +import BlocksoftAxios from '../../../common/BlocksoftAxios'; +import BlocksoftExternalSettings from '../../../common/BlocksoftExternalSettings'; +import DogeRawDS from '../stores/DogeRawDS'; -export default class DogeUnspentsProvider implements BlocksoftBlockchainTypes.UnspentsProvider { +export default class DogeUnspentsProvider + implements BlocksoftBlockchainTypes.UnspentsProvider +{ + private _trezorServerCode: string = ''; - private _trezorServerCode: string = '' + private _trezorServer: string = ''; - private _trezorServer: string = '' + protected _settings: BlocksoftBlockchainTypes.CurrencySettings; - protected _settings: BlocksoftBlockchainTypes.CurrencySettings + constructor( + settings: BlocksoftBlockchainTypes.CurrencySettings, + serverCode: string + ) { + this._settings = settings; + this._trezorServerCode = serverCode; + } - constructor(settings: BlocksoftBlockchainTypes.CurrencySettings, serverCode: string) { - this._settings = settings - this._trezorServerCode = serverCode + async getUnspents( + address: string + ): Promise { + // @ts-ignore + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' DogeUnspentsProvider.getUnspents started ' + + address + ); + this._trezorServer = await BlocksoftExternalSettings.getTrezorServer( + this._trezorServerCode, + 'DOGE.Unspents.getUnspents' + ); + let link = BlocksoftExternalSettings.getStatic( + this._trezorServerCode + '_UNSPENDS_LINK' + ); + if (!link || link === '') { + link = this._trezorServer + '/api/v2/utxo/' + address + '?gap=9999'; } - async getUnspents(address: string): Promise { - // @ts-ignore - BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeUnspentsProvider.getUnspents started ' + address) - this._trezorServer = await BlocksoftExternalSettings.getTrezorServer(this._trezorServerCode, 'DOGE.Unspents.getUnspents') - let link = BlocksoftExternalSettings.getStatic(this._trezorServerCode + '_UNSPENDS_LINK') - if (!link || link === '') { - link = this._trezorServer + '/api/v2/utxo/' + address + '?gap=9999' - } - - const res = await BlocksoftAxios.getWithoutBraking(link) - // @ts-ignore - if (!res || typeof res.data === 'undefined') { - await BlocksoftExternalSettings.setTrezorServerInvalid(this._trezorServerCode, this._trezorServer) - BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeUnspentsProvider.getUnspents nothing loaded for address ' + address + ' link ' + link) - throw new Error('SERVER_RESPONSE_NOT_CONNECTED') - } - // @ts-ignore - if (!res.data || typeof res.data[0] === 'undefined') { - return [] - } - const sortedUnspents = [] - let unspent - // @ts-ignore - for (unspent of res.data) { - if (typeof unspent.path !== 'undefined') { - unspent.derivationPath = unspent.path - } - sortedUnspents.push(unspent) - } - return sortedUnspents + const res = await BlocksoftAxios.getWithoutBraking(link); + // @ts-ignore + if (!res || typeof res.data === 'undefined') { + await BlocksoftExternalSettings.setTrezorServerInvalid( + this._trezorServerCode, + this._trezorServer + ); + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' DogeUnspentsProvider.getUnspents nothing loaded for address ' + + address + + ' link ' + + link + ); + throw new Error('SERVER_RESPONSE_NOT_CONNECTED'); } - - _isMyAddress(voutAddress: string, address: string, walletHash: string): string { - return (voutAddress === address) ? address : '' + // @ts-ignore + if (!res.data || typeof res.data[0] === 'undefined') { + return []; + } + const sortedUnspents = []; + let unspent; + // @ts-ignore + for (unspent of res.data) { + if (typeof unspent.path !== 'undefined') { + unspent.derivationPath = unspent.path; + } + sortedUnspents.push(unspent); } + return sortedUnspents; + } - async getTx(tx: string, address: string, allUnspents: BlocksoftBlockchainTypes.UnspentTx[], walletHash: string): Promise { - BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeUnspentsProvider.getTx started ' + tx) + _isMyAddress( + voutAddress: string, + address: string, + walletHash: string + ): string { + return voutAddress === address ? address : ''; + } - this._trezorServer = await BlocksoftExternalSettings.getTrezorServer(this._trezorServerCode, 'Doge.Unspents.getTx') + async getTx( + tx: string, + address: string, + allUnspents: BlocksoftBlockchainTypes.UnspentTx[], + walletHash: string + ): Promise { + BlocksoftCryptoLog.log( + this._settings.currencyCode + ' DogeUnspentsProvider.getTx started ' + tx + ); - let saved = await DogeRawDS.getInputs({ - currencyCode: this._settings.currencyCode, - transactionHash: tx - }) - BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeUnspentsProvider.getTx inputs ' + tx, saved) + this._trezorServer = await BlocksoftExternalSettings.getTrezorServer( + this._trezorServerCode, + 'Doge.Unspents.getTx' + ); - let recheckInputs = false - if (saved) { - if (typeof saved.inputs !== 'undefined') { - saved = saved.inputs - } - } else { - const link = this._trezorServer + '/api/v2/tx/' + tx - const res = await BlocksoftAxios.getWithoutBraking(link) - // @ts-ignore - if (!res || typeof res.data === 'undefined' || !res.data) { - BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeUnspentsProvider.getTx no tx ' + tx) - throw new Error('SERVER_RESPONSE_BAD_TX_TO_REPLACE') - } - // @ts-ignore - saved = res.data.vin - recheckInputs = true - } - - const sortedUnspents = [] - const unique = {} - if (allUnspents) { - for (const unspent of allUnspents) { - if (unspent.txid === tx) continue - if (typeof unspent.vout === 'undefined') { - // @ts-ignore - unspent.vout = unspent.n - } - const key = unspent.txid + '_' + unspent.vout - // @ts-ignore - if (typeof unique[key] !== 'undefined') continue - // @ts-ignore - unique[key] = sortedUnspents.length - sortedUnspents.push(unspent) - } - } - let txIn = false - for (const unspent of saved) { - if (unspent.txid === tx) continue - - try { - const link2 = this._trezorServer + '/api/v2/tx/' + unspent.txid - const res2 = await BlocksoftAxios.getWithoutBraking(link2) - // @ts-ignore - if (res2 && typeof res2.data !== 'undefined' && res2.data) { - // @ts-ignore - if (typeof res2.data.confirmations !== 'undefined' && res2.data.confirmations * 1 > 0) { - // @ts-ignore - unspent.confirmations = res2.data.confirmations * 1 - } else { - unspent.confirmations = 0 - } - // @ts-ignore - if (recheckInputs && typeof res2.data.vout !== 'undefined' && res2.data.vout) { - // @ts-ignore - BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeUnspentsProvider.getTx loading output data ' + JSON.stringify(unspent) + ' success', res2.data.vout) - let tmp - // @ts-ignore - if (res2.data.vout.length > 0) { - // @ts-ignore - unspent.vout = false - // @ts-ignore - for (tmp of res2.data.vout) { - if (typeof tmp.addresses !== 'undefined' && tmp.addresses) { - if (typeof tmp.addresses[0] !== 'undefined') { - const found = this._isMyAddress(tmp.addresses[0], address, walletHash) - if (found !== '') { - unspent.vout = tmp.n - unspent.address = found - break // 1 is enough - } - } - } - } - } - } - } else { - BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeUnspentsProvider.getTx loading output data ' + JSON.stringify(unspent) + ' no res') - } - } catch (e) { - BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeUnspentsProvider.getTx while loading output data ' + JSON.stringify(unspent)) - } + let saved = await DogeRawDS.getInputs({ + currencyCode: this._settings.currencyCode, + transactionHash: tx + }); + BlocksoftCryptoLog.log( + this._settings.currencyCode + ' DogeUnspentsProvider.getTx inputs ' + tx, + saved + ); + let recheckInputs = false; + if (saved) { + if (typeof saved.inputs !== 'undefined') { + saved = saved.inputs; + } + } else { + const link = this._trezorServer + '/api/v2/tx/' + tx; + const res = await BlocksoftAxios.getWithoutBraking(link); + // @ts-ignore + if (!res || typeof res.data === 'undefined' || !res.data) { + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' DogeUnspentsProvider.getTx no tx ' + + tx + ); + throw new Error('SERVER_RESPONSE_BAD_TX_TO_REPLACE'); + } + // @ts-ignore + saved = res.data.vin; + recheckInputs = true; + } - if (typeof unspent.vout === 'undefined' || unspent.vout === false) { - continue - } + const sortedUnspents = []; + const unique = {}; + if (allUnspents) { + for (const unspent of allUnspents) { + if (unspent.txid === tx) continue; + if (typeof unspent.vout === 'undefined') { + // @ts-ignore + unspent.vout = unspent.n; + } + const key = unspent.txid + '_' + unspent.vout; + // @ts-ignore + if (typeof unique[key] !== 'undefined') continue; + // @ts-ignore + unique[key] = sortedUnspents.length; + sortedUnspents.push(unspent); + } + } + let txIn = false; + for (const unspent of saved) { + if (unspent.txid === tx) continue; - const key = unspent.txid + '_' + unspent.vout + try { + const link2 = this._trezorServer + '/api/v2/tx/' + unspent.txid; + const res2 = await BlocksoftAxios.getWithoutBraking(link2); + // @ts-ignore + if (res2 && typeof res2.data !== 'undefined' && res2.data) { + // @ts-ignore + if ( + typeof res2.data.confirmations !== 'undefined' && + res2.data.confirmations * 1 > 0 + ) { + // @ts-ignore + unspent.confirmations = res2.data.confirmations * 1; + } else { + unspent.confirmations = 0; + } + // @ts-ignore + if ( + recheckInputs && + typeof res2.data.vout !== 'undefined' && + res2.data.vout + ) { // @ts-ignore - if (typeof unique[key] !== 'undefined') { - // @ts-ignore - const index = unique[key] - sortedUnspents[index].isRequired = true - } else { - // @ts-ignore - unique[key] = sortedUnspents.length - unspent.isRequired = true - if (typeof unspent.confirmations === 'undefined') { - unspent.confirmations = 1 + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' DogeUnspentsProvider.getTx loading output data ' + + JSON.stringify(unspent) + + ' success', + res2.data.vout + ); + let tmp; + // @ts-ignore + if (res2.data.vout.length > 0) { + // @ts-ignore + unspent.vout = false; + // @ts-ignore + for (tmp of res2.data.vout) { + if (typeof tmp.addresses !== 'undefined' && tmp.addresses) { + if (typeof tmp.addresses[0] !== 'undefined') { + const found = this._isMyAddress( + tmp.addresses[0], + address, + walletHash + ); + if (found !== '') { + unspent.vout = tmp.n; + unspent.address = found; + break; // 1 is enough + } + } } - sortedUnspents.push(unspent) + } } - txIn = true + } + } else { + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' DogeUnspentsProvider.getTx loading output data ' + + JSON.stringify(unspent) + + ' no res' + ); } + } catch (e) { + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' DogeUnspentsProvider.getTx while loading output data ' + + JSON.stringify(unspent) + ); + } - let foundRequired = false - for (const unspent of sortedUnspents) { - if (unspent.isRequired && unspent.confirmations > 0) { - foundRequired = true - break - } - } - if (!foundRequired) { - for (const unspent of sortedUnspents) { - if (unspent.isRequired) { - unspent.confirmations = 1 - break - } - } + if (typeof unspent.vout === 'undefined' || unspent.vout === false) { + continue; + } + + const key = unspent.txid + '_' + unspent.vout; + // @ts-ignore + if (typeof unique[key] !== 'undefined') { + // @ts-ignore + const index = unique[key]; + sortedUnspents[index].isRequired = true; + } else { + // @ts-ignore + unique[key] = sortedUnspents.length; + unspent.isRequired = true; + if (typeof unspent.confirmations === 'undefined') { + unspent.confirmations = 1; } + sortedUnspents.push(unspent); + } + txIn = true; + } - if (!txIn) { - throw new Error('SERVER_RESPONSE_BAD_TX_TO_REPLACE') + let foundRequired = false; + for (const unspent of sortedUnspents) { + if (unspent.isRequired && unspent.confirmations > 0) { + foundRequired = true; + break; + } + } + if (!foundRequired) { + for (const unspent of sortedUnspents) { + if (unspent.isRequired) { + unspent.confirmations = 1; + break; } + } + } - // @ts-ignore - BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeUnspentsProvider.getTx found ' + tx, sortedUnspents) - return sortedUnspents + if (!txIn) { + throw new Error('SERVER_RESPONSE_BAD_TX_TO_REPLACE'); } + + // @ts-ignore + BlocksoftCryptoLog.log( + this._settings.currencyCode + ' DogeUnspentsProvider.getTx found ' + tx, + sortedUnspents + ); + return sortedUnspents; + } } diff --git a/crypto/blockchains/doge/stores/DogeRawDS.js b/crypto/blockchains/doge/stores/DogeRawDS.js index 7b6a14a05..d113097bd 100644 --- a/crypto/blockchains/doge/stores/DogeRawDS.js +++ b/crypto/blockchains/doge/stores/DogeRawDS.js @@ -1,24 +1,22 @@ - import Database from '@app/appstores/DataSource/Database'; -import BlocksoftExternalSettings from '../../../common/BlocksoftExternalSettings' -import BlocksoftAxios from '../../../common/BlocksoftAxios' -import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog' -import { BlocksoftTransfer } from '../../../actions/BlocksoftTransfer/BlocksoftTransfer' -import config from '../../../../app/config/config' +import BlocksoftExternalSettings from '../../../common/BlocksoftExternalSettings'; +import BlocksoftAxios from '../../../common/BlocksoftAxios'; +import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; +import { BlocksoftTransfer } from '../../../actions/BlocksoftTransfer/BlocksoftTransfer'; +import config from '../../../../app/config/config'; -const tableName = 'transactions_raw' +const tableName = 'transactions_raw'; class DogeRawDS { + _trezorServer = 'none'; - _trezorServer = 'none' - - _canUpdate = true - async getForAddress(data) { - return false - // @todo refactor from server side - if (!this._canUpdate) return false - try { - const sql = ` + _canUpdate = true; + async getForAddress(data) { + return false; + // @todo refactor from server side + if (!this._canUpdate) return false; + try { + const sql = ` SELECT id, transaction_unique_key AS transactionUnique, transaction_hash AS transactionHash, @@ -34,183 +32,237 @@ class DogeRawDS { AND transaction_unique_key NOT LIKE 'inputs_%' AND transaction_unique_key NOT LIKE 'json_%' AND (is_removed=0 OR is_removed IS NULL) - ` - const result = await Database.query(sql) - if (!result || !result.array || result.array.length === 0) { - return {} + `; + const result = await Database.query(sql); + if (!result || !result.array || result.array.length === 0) { + return {}; + } + const ret = {}; + + const now = new Date().toISOString(); + + for (const row of result.array) { + try { + if (typeof ret[row.transactionUnique] !== 'undefined') { + continue; + } + ret[row.transactionUnique] = row; + let transactionLog; + try { + transactionLog = row.transactionLog + ? JSON.parse(Database.unEscapeString(row.transactionLog)) + : row.transactionLog; + } catch (e) { + // do nothing + } + + let broadcastLog = ''; + const updateObj = { broadcastUpdated: now }; + let broad; + try { + broad = await BlocksoftTransfer.sendRawTx( + data, + row.transactionRaw, + typeof transactionLog !== 'undefined' && + transactionLog && + typeof transactionLog.txRBF !== 'undefined' + ? transactionLog.txRBF + : false, + transactionLog + ); + if (broad === '') { + throw new Error('not broadcasted'); } - const ret = {} - - const now = new Date().toISOString() - - for (const row of result.array) { - try { - if (typeof ret[row.transactionUnique] !== 'undefined') { - continue - } - ret[row.transactionUnique] = row - let transactionLog - try { - transactionLog = row.transactionLog ? JSON.parse(Database.unEscapeString(row.transactionLog)) : row.transactionLog - } catch (e) { - // do nothing - } - - let broadcastLog = '' - const updateObj = { broadcastUpdated: now } - let broad - try { - broad = await BlocksoftTransfer.sendRawTx(data, row.transactionRaw, - typeof transactionLog !== 'undefined' && transactionLog && typeof transactionLog.txRBF !== 'undefined' ? transactionLog.txRBF : false, - transactionLog - ) - if (broad === '') { - throw new Error('not broadcasted') - } - broadcastLog = ' broadcasted ok ' + JSON.stringify(broad) - updateObj.is_removed = 1 - updateObj.removed_at = now - } catch (e) { - if (config.debug.cryptoErrors) { - const dbTx = await Database.query(`SELECT * FROM transactions WHERE transaction_hash='${row.transactionHash}'`) - if (config.debug.cryptoErrors) { - console.log('DogeRawDS.getForAddress send error ' + e.message, JSON.parse(JSON.stringify(row)), dbTx, e) - } - - } - if (e.message.indexOf('bad-txns-inputs-spent') !== -1 || e.message.indexOf('missing-inputs') !== -1 || e.message.indexOf('insufficient fee') !== -1) { - broadcastLog = ' sub-spent ' + e.message - updateObj.is_removed = 3 - const sql = `UPDATE transactions + broadcastLog = ' broadcasted ok ' + JSON.stringify(broad); + updateObj.is_removed = 1; + updateObj.removed_at = now; + } catch (e) { + if (config.debug.cryptoErrors) { + const dbTx = await Database.query( + `SELECT * FROM transactions WHERE transaction_hash='${row.transactionHash}'` + ); + if (config.debug.cryptoErrors) { + console.log( + 'DogeRawDS.getForAddress send error ' + e.message, + JSON.parse(JSON.stringify(row)), + dbTx, + e + ); + } + } + if ( + e.message.indexOf('bad-txns-inputs-spent') !== -1 || + e.message.indexOf('missing-inputs') !== -1 || + e.message.indexOf('insufficient fee') !== -1 + ) { + broadcastLog = ' sub-spent ' + e.message; + updateObj.is_removed = 3; + const sql = `UPDATE transactions SET transaction_status='replaced', hidden_at='${now}' WHERE transaction_hash='${row.transactionHash}' AND (transaction_status='missing' OR transaction_status='new') - ` - await Database.query(sql) - } else if (e.message.indexOf('already known') !== -1) { - broadcastLog = ' already known' - } else { - broadcastLog = e.message - } - } - broadcastLog = new Date().toISOString() + ' ' + broadcastLog + ' ' + (row.broadcastLog ? row.broadcastLog.substr(0, 1000) : '') - updateObj.broadcastLog = broadcastLog - await Database.setTableName('transactions_raw').setUpdateData({ - updateObj, - key: { id: row.id } - }).update() - - } catch (e) { - if (config.debug.cryptoErrors) { - console.log('DogeRawDS.getForAddress error ' + e.message + ' in ' + row.transactionHash, e) - } - throw new Error('DogeRawDS.getForAddress error ' + e.message + ' in ' + row.transactionHash) - } + `; + await Database.query(sql); + } else if (e.message.indexOf('already known') !== -1) { + broadcastLog = ' already known'; + } else { + broadcastLog = e.message; } - this._canUpdate = true - return ret + } + broadcastLog = + new Date().toISOString() + + ' ' + + broadcastLog + + ' ' + + (row.broadcastLog ? row.broadcastLog.substr(0, 1000) : ''); + updateObj.broadcastLog = broadcastLog; + await Database.setTableName('transactions_raw') + .setUpdateData({ + updateObj, + key: { id: row.id } + }) + .update(); } catch (e) { - this._canUpdate = true - throw new Error(e.message + ' on DogeRawDS.getAddress') + if (config.debug.cryptoErrors) { + console.log( + 'DogeRawDS.getForAddress error ' + + e.message + + ' in ' + + row.transactionHash, + e + ); + } + throw new Error( + 'DogeRawDS.getForAddress error ' + + e.message + + ' in ' + + row.transactionHash + ); } + } + this._canUpdate = true; + return ret; + } catch (e) { + this._canUpdate = true; + throw new Error(e.message + ' on DogeRawDS.getAddress'); } + } - async cleanRaw(data) { - if (typeof data.transactionUnique === 'undefined') { - data.transactionUnique = data.address.toLowerCase() + '_' + data.transactionHash - } - BlocksoftCryptoLog.log('DogeRawDS cleanRaw ', data) - const now = new Date().toISOString() - const sql = `UPDATE transactions_raw + async cleanRaw(data) { + if (typeof data.transactionUnique === 'undefined') { + data.transactionUnique = + data.address.toLowerCase() + '_' + data.transactionHash; + } + BlocksoftCryptoLog.log('DogeRawDS cleanRaw ', data); + const now = new Date().toISOString(); + const sql = `UPDATE transactions_raw SET is_removed=1, removed_at = '${now}' WHERE (is_removed=0 OR is_removed IS NULL) AND currency_code='${data.currencyCode}' AND address='${data.address.toLowerCase()}' - AND transaction_unique_key='${data.transactionUnique}'` - await Database.query(sql) - } + AND transaction_unique_key='${data.transactionUnique}'`; + await Database.query(sql); + } - async saveRaw(data) { - if (typeof data.transactionUnique === 'undefined') { - data.transactionUnique = data.address.toLowerCase() + '_' + data.transactionHash - } - const now = new Date().toISOString() + async saveRaw(data) { + if (typeof data.transactionUnique === 'undefined') { + data.transactionUnique = + data.address.toLowerCase() + '_' + data.transactionHash; + } + const now = new Date().toISOString(); - const sql = `UPDATE transactions_raw + const sql = `UPDATE transactions_raw SET is_removed=1, removed_at = '${now}' WHERE (is_removed=0 OR is_removed IS NULL) AND currency_code='${data.currencyCode}' AND address='${data.address.toLowerCase()}' - AND transaction_unique_key='${data.transactionUnique}'` - await Database.query(sql) - - const prepared = [{ - currency_code: data.currencyCode, - address: data.address.toLowerCase(), - transaction_unique_key: data.transactionUnique, - transaction_hash: data.transactionHash, - transaction_raw: data.transactionRaw, - created_at: now - }] - if (typeof data.transactionLog !== 'undefined' && data.transactionLog) { - prepared[0].transaction_log = Database.escapeString(JSON.stringify(data.transactionLog)) - } - await Database.setTableName(tableName).setInsertData({ insertObjs: prepared }).insert() + AND transaction_unique_key='${data.transactionUnique}'`; + await Database.query(sql); + + const prepared = [ + { + currency_code: data.currencyCode, + address: data.address.toLowerCase(), + transaction_unique_key: data.transactionUnique, + transaction_hash: data.transactionHash, + transaction_raw: data.transactionRaw, + created_at: now + } + ]; + if (typeof data.transactionLog !== 'undefined' && data.transactionLog) { + prepared[0].transaction_log = Database.escapeString( + JSON.stringify(data.transactionLog) + ); } + await Database.setTableName(tableName) + .setInsertData({ insertObjs: prepared }) + .insert(); + } - async savePrefixed(data, prefix) { - const now = new Date().toISOString() - - const prepared = [{ - currency_code: data.currencyCode, - address: data.address.toLowerCase(), - transaction_unique_key: prefix + '_' + data.transactionHash, - transaction_hash: data.transactionHash, - transaction_raw: Database.escapeString(data.transactionRaw), - is_removed: 2, - created_at: now - }] - if (typeof data.transactionLog !== 'undefined' && data.transactionLog) { - prepared[0].transaction_log = Database.escapeString(JSON.stringify(data.transactionLog)) - } - await Database.setTableName(tableName).setInsertData({ insertObjs: prepared }).insert() + async savePrefixed(data, prefix) { + const now = new Date().toISOString(); + + const prepared = [ + { + currency_code: data.currencyCode, + address: data.address.toLowerCase(), + transaction_unique_key: prefix + '_' + data.transactionHash, + transaction_hash: data.transactionHash, + transaction_raw: Database.escapeString(data.transactionRaw), + is_removed: 2, + created_at: now + } + ]; + if (typeof data.transactionLog !== 'undefined' && data.transactionLog) { + prepared[0].transaction_log = Database.escapeString( + JSON.stringify(data.transactionLog) + ); } + await Database.setTableName(tableName) + .setInsertData({ insertObjs: prepared }) + .insert(); + } - async getPrefixed(data, prefix) { - const sql = `SELECT transaction_raw AS transactionRaw + async getPrefixed(data, prefix) { + const sql = `SELECT transaction_raw AS transactionRaw FROM ${tableName} WHERE currency_code='${data.currencyCode}' - AND transaction_unique_key='${prefix}_${data.transactionHash}' LIMIT 1` - const res = await Database.query(sql) - if (!res || !res.array || typeof res.array[0] === 'undefined' || typeof res.array[0].transactionRaw === 'undefined') { - return false - } - try { - const str = Database.unEscapeString(res.array[0].transactionRaw) - return JSON.parse(str) - } catch (e) { - BlocksoftCryptoLog.err('DogeRawDS getInputs error ' + e.message) - return false - } + AND transaction_unique_key='${prefix}_${data.transactionHash}' LIMIT 1`; + const res = await Database.query(sql); + if ( + !res || + !res.array || + typeof res.array[0] === 'undefined' || + typeof res.array[0].transactionRaw === 'undefined' + ) { + return false; } - - async saveInputs(data) { - return this.savePrefixed(data, 'inputs') + try { + const str = Database.unEscapeString(res.array[0].transactionRaw); + return JSON.parse(str); + } catch (e) { + BlocksoftCryptoLog.err('DogeRawDS getInputs error ' + e.message); + return false; } + } - async getInputs(data) { - return this.getPrefixed(data, 'inputs') - } + async saveInputs(data) { + return this.savePrefixed(data, 'inputs'); + } - async saveJson(data) { - return this.savePrefixed(data, 'json') - } + async getInputs(data) { + return this.getPrefixed(data, 'inputs'); + } - async getJson(data) { - return this.getPrefixed(data, 'json') - } + async saveJson(data) { + return this.savePrefixed(data, 'json'); + } + + async getJson(data) { + return this.getPrefixed(data, 'json'); + } } -export default new DogeRawDS() +export default new DogeRawDS(); diff --git a/crypto/blockchains/doge/tx/DogeTxBuilder.ts b/crypto/blockchains/doge/tx/DogeTxBuilder.ts index 48af913c3..c2b7faf98 100644 --- a/crypto/blockchains/doge/tx/DogeTxBuilder.ts +++ b/crypto/blockchains/doge/tx/DogeTxBuilder.ts @@ -1,195 +1,328 @@ /** * @version 0.20 */ -import { BlocksoftBlockchainTypes } from '../../BlocksoftBlockchainTypes' -import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog' -import BlocksoftUtils from '../../../common/BlocksoftUtils' -import { TransactionBuilder, ECPair, payments } from 'bitcoinjs-lib' -import BlocksoftExternalSettings from '../../../common/BlocksoftExternalSettings' -import config from '../../../../app/config/config' +import { BlocksoftBlockchainTypes } from '../../BlocksoftBlockchainTypes'; +import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; +import BlocksoftUtils from '../../../common/BlocksoftUtils'; +import { TransactionBuilder, ECPair, payments } from 'bitcoinjs-lib'; +import BlocksoftExternalSettings from '../../../common/BlocksoftExternalSettings'; +import config from '../../../../app/config/config'; -const networksConstants = require('../../../common/ext/networks-constants') +const networksConstants = require('../../../common/ext/networks-constants'); -const MAX_SEQ = 4294967294 // 0xfffffffe // no replace by fee -const MIN_SEQ = 4294960000 // for RBF +const MAX_SEQ = 4294967294; // 0xfffffffe // no replace by fee +const MIN_SEQ = 4294960000; // for RBF -export default class DogeTxBuilder implements BlocksoftBlockchainTypes.TxBuilder { +export default class DogeTxBuilder + implements BlocksoftBlockchainTypes.TxBuilder +{ + protected _settings: BlocksoftBlockchainTypes.CurrencySettings; + private _builderSettings: BlocksoftBlockchainTypes.BuilderSettings; + protected _bitcoinNetwork: any; + private _feeMaxForByteSatoshi: number | any; - protected _settings: BlocksoftBlockchainTypes.CurrencySettings - private _builderSettings: BlocksoftBlockchainTypes.BuilderSettings - protected _bitcoinNetwork: any - private _feeMaxForByteSatoshi: number | any + protected keyPair: any; - protected keyPair: any + constructor( + settings: BlocksoftBlockchainTypes.CurrencySettings, + builderSettings: BlocksoftBlockchainTypes.BuilderSettings + ) { + this._settings = settings; + this._builderSettings = builderSettings; + this._bitcoinNetwork = networksConstants[settings.network].network; + } - constructor(settings: BlocksoftBlockchainTypes.CurrencySettings, builderSettings: BlocksoftBlockchainTypes.BuilderSettings) { - this._settings = settings - this._builderSettings = builderSettings - this._bitcoinNetwork = networksConstants[settings.network].network - } + async _reInit() { + const fromExt = await BlocksoftExternalSettings.get( + this._settings.currencyCode + '_MAX_FOR_BYTE_TX_BUILDER', + 'DogeTxBuilder._reInit' + ); + this._feeMaxForByteSatoshi = + fromExt && fromExt * 1 > 0 + ? fromExt * 1 + : this._builderSettings.feeMaxForByteSatoshi; + await BlocksoftCryptoLog.log( + 'DogeTxBuilder.getRawTx ' + + this._settings.currencyCode + + ' _feeMaxForByteSatoshi ' + + this._feeMaxForByteSatoshi + + ' fromExt ' + + fromExt + ); + } - async _reInit() { - const fromExt = await BlocksoftExternalSettings.get(this._settings.currencyCode + '_MAX_FOR_BYTE_TX_BUILDER', 'DogeTxBuilder._reInit') - this._feeMaxForByteSatoshi = fromExt && fromExt * 1 > 0 ? fromExt * 1 : this._builderSettings.feeMaxForByteSatoshi - await BlocksoftCryptoLog.log('DogeTxBuilder.getRawTx ' + this._settings.currencyCode + ' _feeMaxForByteSatoshi ' + this._feeMaxForByteSatoshi + ' fromExt ' + fromExt ) + _getRawTxValidateKeyPair( + privateData: BlocksoftBlockchainTypes.TransferPrivateData, + data: BlocksoftBlockchainTypes.TransferData + ): void { + if (typeof privateData.privateKey === 'undefined') { + throw new Error('DogeTxBuilder.getRawTx requires privateKey'); } - - _getRawTxValidateKeyPair(privateData: BlocksoftBlockchainTypes.TransferPrivateData, data: BlocksoftBlockchainTypes.TransferData): void { - if (typeof privateData.privateKey === 'undefined') { - throw new Error('DogeTxBuilder.getRawTx requires privateKey') - } - try { - this.keyPair = ECPair.fromWIF(privateData.privateKey, this._bitcoinNetwork) - const address = payments.p2pkh({ - pubkey: this.keyPair.publicKey, - network: this._bitcoinNetwork - }).address - if (address !== data.addressFrom) { - // noinspection ExceptionCaughtLocallyJS - throw new Error('not valid signing address ' + data.addressFrom + ' != ' + address) - } - } catch (e) { - e.message += ' in privateKey ' + this._settings.currencyCode + ' DogeTxBuilder signature check ' - throw e - } + try { + this.keyPair = ECPair.fromWIF( + privateData.privateKey, + this._bitcoinNetwork + ); + const address = payments.p2pkh({ + pubkey: this.keyPair.publicKey, + network: this._bitcoinNetwork + }).address; + if (address !== data.addressFrom) { + // noinspection ExceptionCaughtLocallyJS + throw new Error( + 'not valid signing address ' + data.addressFrom + ' != ' + address + ); + } + } catch (e) { + e.message += + ' in privateKey ' + + this._settings.currencyCode + + ' DogeTxBuilder signature check '; + throw e; } + } - async _getRawTxAddInput(txb: TransactionBuilder, i: number, input: BlocksoftBlockchainTypes.UnspentTx, nSequence: number): Promise { - if (typeof input.vout === 'undefined') { - throw new Error('no input.vout') - } - if (typeof nSequence === 'undefined') { - throw new Error('no nSequence') - } - txb.addInput(input.txid, input.vout, nSequence) + async _getRawTxAddInput( + txb: TransactionBuilder, + i: number, + input: BlocksoftBlockchainTypes.UnspentTx, + nSequence: number + ): Promise { + if (typeof input.vout === 'undefined') { + throw new Error('no input.vout'); + } + if (typeof nSequence === 'undefined') { + throw new Error('no nSequence'); } + txb.addInput(input.txid, input.vout, nSequence); + } - async _getRawTxSign(txb: TransactionBuilder, i: number, input: BlocksoftBlockchainTypes.UnspentTx): Promise { - await BlocksoftCryptoLog.log('DogeTxBuilder.getRawTx sign', input) - // @ts-ignore - txb.sign(i, this.keyPair, null, null, input.value * 1) + async _getRawTxSign( + txb: TransactionBuilder, + i: number, + input: BlocksoftBlockchainTypes.UnspentTx + ): Promise { + await BlocksoftCryptoLog.log('DogeTxBuilder.getRawTx sign', input); + // @ts-ignore + txb.sign(i, this.keyPair, null, null, input.value * 1); + } + + _getRawTxAddOutput( + txb: TransactionBuilder, + output: BlocksoftBlockchainTypes.OutputTx + ): void { + // @ts-ignore + const amount = Math.round(output.amount * 1); + if (amount === 0) { + // do nothing or txb.addOutput(output.to, 546) + } else { + txb.addOutput(output.to, amount); } + } - _getRawTxAddOutput(txb: TransactionBuilder, output: BlocksoftBlockchainTypes.OutputTx): void { - // @ts-ignore - const amount = Math.round(output.amount * 1) - if (amount === 0) { - // do nothing or txb.addOutput(output.to, 546) - } else { - txb.addOutput(output.to, amount) - } + async getRawTx( + data: BlocksoftBlockchainTypes.TransferData, + privateData: BlocksoftBlockchainTypes.TransferPrivateData, + preparedInputsOutputs: BlocksoftBlockchainTypes.PreparedInputsOutputsTx + ): Promise<{ + rawTxHex: string; + nSequence: number; + txAllowReplaceByFee: boolean; + preparedInputsOutputs: BlocksoftBlockchainTypes.PreparedInputsOutputsTx; + }> { + await this._reInit(); + + this._getRawTxValidateKeyPair(privateData, data); + await BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' DogeTxBuilder.getRawTx validated address private key' + ); + + let nSequence = 0; + let txAllowReplaceByFee = false; + if ( + typeof data.transactionJson === 'undefined' || + !data.transactionJson || + typeof data.transactionJson.nSequence === 'undefined' + ) { + if (data.allowReplaceByFee) { + nSequence = MIN_SEQ; + txAllowReplaceByFee = true; + await BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' DogeTxBuilder.getRawTx allow RBF ' + + nSequence + ); + } else { + nSequence = MAX_SEQ; + txAllowReplaceByFee = false; + await BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' DogeTxBuilder.getRawTx no RBF ' + + nSequence + ); + } + } else { + nSequence = data.transactionJson.nSequence * 1 + 1; + txAllowReplaceByFee = true; + + if (nSequence >= MAX_SEQ) { + nSequence = MAX_SEQ; + txAllowReplaceByFee = false; + await BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' DogeTxBuilder.getRawTx no RBF by old nSeq ' + + data.transactionJson.nSequence + + ' +1 => ' + + nSequence + ); + } else { + await BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' DogeTxBuilder.getRawTx allow RBF by old nSeq ' + + data.transactionJson.nSequence + + ' +1 => ' + + nSequence + ); + } } - async getRawTx(data: BlocksoftBlockchainTypes.TransferData, privateData: BlocksoftBlockchainTypes.TransferPrivateData, preparedInputsOutputs: BlocksoftBlockchainTypes.PreparedInputsOutputsTx): - Promise<{ - rawTxHex: string, - nSequence: number, - txAllowReplaceByFee: boolean, - preparedInputsOutputs: BlocksoftBlockchainTypes.PreparedInputsOutputsTx - }> { - - await this._reInit() - - this._getRawTxValidateKeyPair(privateData, data) - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeTxBuilder.getRawTx validated address private key') - - let nSequence = 0 - let txAllowReplaceByFee = false - if (typeof data.transactionJson === 'undefined' || !data.transactionJson || typeof data.transactionJson.nSequence === 'undefined') { - if (data.allowReplaceByFee) { - nSequence = MIN_SEQ - txAllowReplaceByFee = true - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeTxBuilder.getRawTx allow RBF ' + nSequence) - } else { - nSequence = MAX_SEQ - txAllowReplaceByFee = false - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeTxBuilder.getRawTx no RBF ' + nSequence) - } - } else { - nSequence = data.transactionJson.nSequence * 1 + 1 - txAllowReplaceByFee = true - - if (nSequence >= MAX_SEQ) { - nSequence = MAX_SEQ - txAllowReplaceByFee = false - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeTxBuilder.getRawTx no RBF by old nSeq ' + data.transactionJson.nSequence + ' +1 => ' + nSequence) - } else { - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeTxBuilder.getRawTx allow RBF by old nSeq ' + data.transactionJson.nSequence + ' +1 => ' + nSequence) - } - } + const txb = new TransactionBuilder( + this._bitcoinNetwork, + this._feeMaxForByteSatoshi + ); + await BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' DogeTxBuilder.getRawTx started max4Bytes ' + + this._feeMaxForByteSatoshi + ); - const txb = new TransactionBuilder(this._bitcoinNetwork, this._feeMaxForByteSatoshi) - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeTxBuilder.getRawTx started max4Bytes ' + this._feeMaxForByteSatoshi) - - txb.setVersion(1) - - const log = { inputs: [], outputs: [] } - for (let i = 0, ic = preparedInputsOutputs.inputs.length; i < ic; i++) { - const input = preparedInputsOutputs.inputs[i] - try { - await this._getRawTxAddInput(txb, i, input, nSequence) - // @ts-ignore - log.inputs.push({ txid: input.txid, vout: input.vout, nSequence }) - // @ts-ignore - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeTxBuilder.getRawTx input added', input) - } catch (e) { - if (config.debug.cryptoErrors) { - console.log(this._settings.currencyCode + ' DogeTxBuilder.getRawTx input add error ', e, JSON.parse(JSON.stringify(input))) - } - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeTxBuilder.getRawTx input add error ', input) - throw e - } - } + txb.setVersion(1); - let output - for (output of preparedInputsOutputs.outputs) { - try { - if (output.amount !== 'removed') { - this._getRawTxAddOutput(txb, output) - } - // @ts-ignore - log.outputs.push({ addressTo: output.to, amount: output.amount }) - // @ts-ignore - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeTxBuilder.getRawTx output added ', output) - } catch (e) { - if (config.debug.cryptoErrors) { - console.log(this._settings.currencyCode + ' DogeTxBuilder.getRawTx output add error ', e, JSON.parse(JSON.stringify(output))) - } - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeTxBuilder.getRawTx output add error ', output) - throw e - } + const log = { inputs: [], outputs: [] }; + for (let i = 0, ic = preparedInputsOutputs.inputs.length; i < ic; i++) { + const input = preparedInputsOutputs.inputs[i]; + try { + await this._getRawTxAddInput(txb, i, input, nSequence); + // @ts-ignore + log.inputs.push({ txid: input.txid, vout: input.vout, nSequence }); + // @ts-ignore + await BlocksoftCryptoLog.log( + this._settings.currencyCode + ' DogeTxBuilder.getRawTx input added', + input + ); + } catch (e) { + if (config.debug.cryptoErrors) { + console.log( + this._settings.currencyCode + + ' DogeTxBuilder.getRawTx input add error ', + e, + JSON.parse(JSON.stringify(input)) + ); } + await BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' DogeTxBuilder.getRawTx input add error ', + input + ); + throw e; + } + } - for (let i = 0, ic = preparedInputsOutputs.inputs.length; i < ic; i++) { - const input = preparedInputsOutputs.inputs[i] - try { - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeTxBuilder.getRawTx sign adding') - await this._getRawTxSign(txb, i, input) - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeTxBuilder.getRawTx sign added') - } catch (e) { - if (config.debug.cryptoErrors) { - if (e.message.indexOf('Transaction needs outputs') !== -1) { - console.log(this._settings.currencyCode + ' DogeTxBuilder.getRawTx input sign error ' + e.message, JSON.parse(JSON.stringify(preparedInputsOutputs))) - } else { - console.log(this._settings.currencyCode + ' DogeTxBuilder.getRawTx input sign error ', e, JSON.parse(JSON.stringify(input))) - } - } - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeTxBuilder.getRawTx input sign error ', input) - e.message = ' transaction ' + this._settings.currencyCode + ' DogeTxBuilder sign error: ' + e.message - throw e - } + let output; + for (output of preparedInputsOutputs.outputs) { + try { + if (output.amount !== 'removed') { + this._getRawTxAddOutput(txb, output); } - let rawTxHex - try { - rawTxHex = txb.build().toHex() - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeTxBuilder.getRawTx size ' + rawTxHex.length) - // @ts-ignore - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' DogeTxBuilder.getRawTx hex', rawTxHex) - } catch (e) { - e.message = ' transaction ' + this._settings.currencyCode + ' DogeTxBuilder build error: ' + e.message - throw e + // @ts-ignore + log.outputs.push({ addressTo: output.to, amount: output.amount }); + // @ts-ignore + await BlocksoftCryptoLog.log( + this._settings.currencyCode + ' DogeTxBuilder.getRawTx output added ', + output + ); + } catch (e) { + if (config.debug.cryptoErrors) { + console.log( + this._settings.currencyCode + + ' DogeTxBuilder.getRawTx output add error ', + e, + JSON.parse(JSON.stringify(output)) + ); } + await BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' DogeTxBuilder.getRawTx output add error ', + output + ); + throw e; + } + } - return { rawTxHex, nSequence, txAllowReplaceByFee, preparedInputsOutputs } + for (let i = 0, ic = preparedInputsOutputs.inputs.length; i < ic; i++) { + const input = preparedInputsOutputs.inputs[i]; + try { + await BlocksoftCryptoLog.log( + this._settings.currencyCode + ' DogeTxBuilder.getRawTx sign adding' + ); + await this._getRawTxSign(txb, i, input); + await BlocksoftCryptoLog.log( + this._settings.currencyCode + ' DogeTxBuilder.getRawTx sign added' + ); + } catch (e) { + if (config.debug.cryptoErrors) { + if (e.message.indexOf('Transaction needs outputs') !== -1) { + console.log( + this._settings.currencyCode + + ' DogeTxBuilder.getRawTx input sign error ' + + e.message, + JSON.parse(JSON.stringify(preparedInputsOutputs)) + ); + } else { + console.log( + this._settings.currencyCode + + ' DogeTxBuilder.getRawTx input sign error ', + e, + JSON.parse(JSON.stringify(input)) + ); + } + } + await BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' DogeTxBuilder.getRawTx input sign error ', + input + ); + e.message = + ' transaction ' + + this._settings.currencyCode + + ' DogeTxBuilder sign error: ' + + e.message; + throw e; + } + } + let rawTxHex; + try { + rawTxHex = txb.build().toHex(); + await BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' DogeTxBuilder.getRawTx size ' + + rawTxHex.length + ); + // @ts-ignore + await BlocksoftCryptoLog.log( + this._settings.currencyCode + ' DogeTxBuilder.getRawTx hex', + rawTxHex + ); + } catch (e) { + e.message = + ' transaction ' + + this._settings.currencyCode + + ' DogeTxBuilder build error: ' + + e.message; + throw e; } + + return { rawTxHex, nSequence, txAllowReplaceByFee, preparedInputsOutputs }; + } } diff --git a/crypto/blockchains/eth/EthScannerProcessor.js b/crypto/blockchains/eth/EthScannerProcessor.js index 799c331fd..c16dfd709 100644 --- a/crypto/blockchains/eth/EthScannerProcessor.js +++ b/crypto/blockchains/eth/EthScannerProcessor.js @@ -1,785 +1,1119 @@ /** * @version 0.5 */ -import BlocksoftUtils from '../../common/BlocksoftUtils' -import BlocksoftAxios from '../../common/BlocksoftAxios' -import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog' -import EthBasic from './basic/EthBasic' -import BlocksoftExternalSettings from '../../common/BlocksoftExternalSettings' -import BlocksoftBN from '../../common/BlocksoftBN' - -import EthTmpDS from './stores/EthTmpDS' -import EthRawDS from './stores/EthRawDS' -import config from '@app/config/config' -import BlocksoftPrettyNumbers from '@crypto/common/BlocksoftPrettyNumbers' +import BlocksoftUtils from '../../common/BlocksoftUtils'; +import BlocksoftAxios from '../../common/BlocksoftAxios'; +import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog'; +import EthBasic from './basic/EthBasic'; +import BlocksoftExternalSettings from '../../common/BlocksoftExternalSettings'; +import BlocksoftBN from '../../common/BlocksoftBN'; + +import EthTmpDS from './stores/EthTmpDS'; +import EthRawDS from './stores/EthRawDS'; +import config from '@app/config/config'; +import BlocksoftPrettyNumbers from '@crypto/common/BlocksoftPrettyNumbers'; const CACHE_GET_MAX_BLOCK = { - ETH: { max_block_number: 0, confirmations: 0 }, - BNB: { max_block_number: 0, confirmations: 0 }, - ETC: { max_block_number: 0, confirmations: 0 }, - AMB: { max_block_number: 0, confirmations: 0 }, - MATIC : { max_block_number: 0, confirmations: 0 }, - FTM : { max_block_number: 0, confirmations: 0 }, - RSK : { max_block_number: 0, confirmations: 0 }, - OPTIMISM : { max_block_number: 0, confirmations: 0 }, - METIS : { max_block_number: 0, confirmations: 0 }, - VLX : { max_block_number: 0, confirmations: 0 }, -} + ETH: { max_block_number: 0, confirmations: 0 }, + BNB: { max_block_number: 0, confirmations: 0 }, + ETC: { max_block_number: 0, confirmations: 0 }, + AMB: { max_block_number: 0, confirmations: 0 }, + MATIC: { max_block_number: 0, confirmations: 0 }, + FTM: { max_block_number: 0, confirmations: 0 }, + RSK: { max_block_number: 0, confirmations: 0 }, + OPTIMISM: { max_block_number: 0, confirmations: 0 }, + METIS: { max_block_number: 0, confirmations: 0 }, + VLX: { max_block_number: 0, confirmations: 0 } +}; const CACHE_BLOCK_NUMBER_TO_HASH = { - ETH: {}, - BNB: {}, - ETC : {}, - AMB : {}, - MATIC : {}, - FTM : {}, - RSK : {}, - OPTIMISM : {}, - METIS : {}, - VLX : {} -} + ETH: {}, + BNB: {}, + ETC: {}, + AMB: {}, + MATIC: {}, + FTM: {}, + RSK: {}, + OPTIMISM: {}, + METIS: {}, + VLX: {} +}; -const CACHE_VALID_TIME = 30000 // 30 seconds +const CACHE_VALID_TIME = 30000; // 30 seconds const CACHE = { - ETH: {}, - BNB: {}, - ETC : {}, - AMB : {}, - MATIC : {}, - FTM : {}, - RSK : {}, - OPTIMISM : {}, - METIS : {}, - VLX : {} -} + ETH: {}, + BNB: {}, + ETC: {}, + AMB: {}, + MATIC: {}, + FTM: {}, + RSK: {}, + OPTIMISM: {}, + METIS: {}, + VLX: {} +}; export default class EthScannerProcessor extends EthBasic { - /** - * @type {number} - * @private - */ - _blocksToConfirm = 10 - - /** - * @type {boolean} - * @private - */ - _useInternal = true - - /** - * https://eth1.trezor.io/api/v2/address/0x8b661361Be29E688Dda65b323526aD536c8B3997?details=txs - * @param address - * @returns {Promise} - * @private - */ - async _get(_address) { - const address = _address.toLowerCase() - - try { - this._trezorServer = await BlocksoftExternalSettings.getTrezorServer(this._trezorServerCode, 'ETH.Scanner._get') - } catch (e) { - throw new Error(e.message + ' while getTrezorServer ' + this._trezorServerCode) - } + /** + * @type {number} + * @private + */ + _blocksToConfirm = 10; - if (typeof this._trezorServer === 'undefined') { - BlocksoftCryptoLog.err(this._settings.currencyCode + ' EthScannerProcessor._get empty trezorServer') - throw new Error(this._settings.currencyCode + ' EthScannerProcessor._get empty trezorServer') - } + /** + * @type {boolean} + * @private + */ + _useInternal = true; - if (!this._trezorServer) { - return false - } + /** + * https://eth1.trezor.io/api/v2/address/0x8b661361Be29E688Dda65b323526aD536c8B3997?details=txs + * @param address + * @returns {Promise} + * @private + */ + async _get(_address) { + const address = _address.toLowerCase(); - const now = new Date().getTime() - if (typeof CACHE[this._mainCurrencyCode] !== 'undefined' && typeof CACHE[this._mainCurrencyCode][address] !== 'undefined' && (now - CACHE[this._mainCurrencyCode][address].time < CACHE_VALID_TIME)) { - CACHE[this._mainCurrencyCode][address].provider = 'trezor-cache' - return CACHE[this._mainCurrencyCode][address] - } + try { + this._trezorServer = await BlocksoftExternalSettings.getTrezorServer( + this._trezorServerCode, + 'ETH.Scanner._get' + ); + } catch (e) { + throw new Error( + e.message + ' while getTrezorServer ' + this._trezorServerCode + ); + } - let link = this._trezorServer + '/api/v2/address/' + address + '?details=txs' - let res = await BlocksoftAxios.getWithoutBraking(link) + if (typeof this._trezorServer === 'undefined') { + BlocksoftCryptoLog.err( + this._settings.currencyCode + + ' EthScannerProcessor._get empty trezorServer' + ); + throw new Error( + this._settings.currencyCode + + ' EthScannerProcessor._get empty trezorServer' + ); + } - if (!res || !res.data) { - BlocksoftExternalSettings.setTrezorServerInvalid(this._trezorServerCode, this._trezorServer) - this._trezorServer = await BlocksoftExternalSettings.getTrezorServer(this._trezorServerCode, this._settings.currencyCode + ' ETH.Scanner._get') - if (typeof this._trezorServer === 'undefined') { - BlocksoftCryptoLog.err(this._settings.currencyCode + ' EthScannerProcessor._get empty trezorServer2') - throw new Error(this._settings.currencyCode + ' EthScannerProcessor._get empty trezorServer2') - } - link = this._trezorServer + '/api/v2/address/' + address + '?details=txs' - res = await BlocksoftAxios.getWithoutBraking(link) - if (!res || !res.data) { - BlocksoftExternalSettings.setTrezorServerInvalid(this._trezorServerCode, this._trezorServer) - return false - } - } + if (!this._trezorServer) { + return false; + } - if (typeof res.data.balance === 'undefined') { - throw new Error(this._settings.currencyCode + ' EthScannerProcessor._get nothing loaded for address ' + link) - } - const data = res.data - data.totalTokens = 0 - data.formattedTokens = {} - //await BlocksoftCryptoLog.log('EthScannerProcessor._get ERC20 tokens ' + JSON.stringify(data.tokens)) - if (typeof data.tokens !== 'undefined') { - let token - for (token of data.tokens) { - data.formattedTokens[token.contract.toLowerCase()] = token - } - } - if (typeof CACHE[this._mainCurrencyCode][address] !== 'undefined') { - if (CACHE[this._mainCurrencyCode][address].data.nonce > res.data.nonce) { - return false - } - } - CACHE[this._mainCurrencyCode][address] = { - data, - provider: 'trezor', - time: now - } - return CACHE[this._mainCurrencyCode][address] - } - - /** - * @param {string} address - * @return {Promise<{balance, unconfirmed, provider}>} - */ - async getBalanceBlockchain(address) { - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthScannerProcessor.getBalance started ' + address) - - this.checkWeb3CurrentServerUpdated() - // noinspection JSUnresolvedVariable - let balance = 0 - let provider = '' - let time = 0 - try { - if (this._trezorServerCode && this._trezorServerCode.indexOf('http') === -1) { - const res = await this._get(address) - - if (res && typeof res.data !== 'undefined' && res.data && typeof res.data.balance !== 'undefined') { - balance = res.data.balance - provider = res.provider - time = res.time - return { balance, unconfirmed: 0, provider, time, balanceScanBlock: res.data.nonce } - } - } - } catch (e) { - if (config.debug.cryptoErrors) { - console.log(this._settings.currencyCode + ' EthScannerProcessor.getBalance ' + address + ' error ' + e.message) - } - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthScannerProcessor.getBalance ' + address + ' trezor error ' + e.message) - return false - } + const now = new Date().getTime(); + if ( + typeof CACHE[this._mainCurrencyCode] !== 'undefined' && + typeof CACHE[this._mainCurrencyCode][address] !== 'undefined' && + now - CACHE[this._mainCurrencyCode][address].time < CACHE_VALID_TIME + ) { + CACHE[this._mainCurrencyCode][address].provider = 'trezor-cache'; + return CACHE[this._mainCurrencyCode][address]; + } - try { - balance = await this._web3.eth.getBalance(address) - BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthScannerProcessor.getBalance ' + address + ' result ' + JSON.stringify(balance)) - provider = 'web3' - time = 'now()' - return { balance, unconfirmed: 0, provider, time } - } catch (e) { - if (config.debug.cryptoErrors) { - console.log(this._settings.currencyCode + ' EthScannerProcessor.getBalance ' + address + ' ' + this._web3.LINK + ' rpc error ' + e.message) - } - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthScannerProcessor.getBalance ' + address + ' ' + this._web3.LINK + ' rpc error ' + e.message) - return false - } + let link = + this._trezorServer + '/api/v2/address/' + address + '?details=txs'; + let res = await BlocksoftAxios.getWithoutBraking(link); + + if (!res || !res.data) { + BlocksoftExternalSettings.setTrezorServerInvalid( + this._trezorServerCode, + this._trezorServer + ); + this._trezorServer = await BlocksoftExternalSettings.getTrezorServer( + this._trezorServerCode, + this._settings.currencyCode + ' ETH.Scanner._get' + ); + if (typeof this._trezorServer === 'undefined') { + BlocksoftCryptoLog.err( + this._settings.currencyCode + + ' EthScannerProcessor._get empty trezorServer2' + ); + throw new Error( + this._settings.currencyCode + + ' EthScannerProcessor._get empty trezorServer2' + ); + } + link = this._trezorServer + '/api/v2/address/' + address + '?details=txs'; + res = await BlocksoftAxios.getWithoutBraking(link); + if (!res || !res.data) { + BlocksoftExternalSettings.setTrezorServerInvalid( + this._trezorServerCode, + this._trezorServer + ); + return false; + } } - /** - * @param {string} scanData.account.address - * @return {Promise<[UnifiedTransaction]>} - */ - async getTransactionsBlockchain(scanData) { - const address = scanData.account.address - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthScannerProcessor.getTransactions started ' + address) - let res = false - if (this._settings.currencyCode !== 'ETH_ROPSTEN' && this._settings.currencyCode !== 'ETH_RINKEBY' && this._trezorServerCode) { - try { - res = await this._get(address) - } catch (e) { - throw new Error(e.message + ' in EthScannerProcessor._get') - } - } + if (typeof res.data.balance === 'undefined') { + throw new Error( + this._settings.currencyCode + + ' EthScannerProcessor._get nothing loaded for address ' + + link + ); + } + const data = res.data; + data.totalTokens = 0; + data.formattedTokens = {}; + //await BlocksoftCryptoLog.log('EthScannerProcessor._get ERC20 tokens ' + JSON.stringify(data.tokens)) + if (typeof data.tokens !== 'undefined') { + let token; + for (token of data.tokens) { + data.formattedTokens[token.contract.toLowerCase()] = token; + } + } + if (typeof CACHE[this._mainCurrencyCode][address] !== 'undefined') { + if (CACHE[this._mainCurrencyCode][address].data.nonce > res.data.nonce) { + return false; + } + } + CACHE[this._mainCurrencyCode][address] = { + data, + provider: 'trezor', + time: now + }; + return CACHE[this._mainCurrencyCode][address]; + } - let transactions - if (res && typeof res.data !== 'undefined' && res.data) { - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthScannerProcessor.getBalance loaded from ' + res.provider + ' ' + res.time) - if (this._tokenAddress && typeof res.data.formattedTokens[this._tokenAddress] === 'undefined') { - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthScannerProcessor.getTransactions skipped token ' + this._tokenAddress + ' ' + address) - return false - } - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthScannerProcessor.getTransactions trezor unify started ' + address) - transactions = await this._unifyTransactions(address, res.data.transactions, false, true, {}) - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthScannerProcessor.getTransactions trezor finished ' + address) - } else if (this._oklinkAPI) { - let logTitle = this._settings.currencyCode + ' EthScannerProcessor.getTransactions oklink ' - transactions = await this._getFromOklink(address, this._oklinkAPI, logTitle, {}) - } else { - if (!this._etherscanApiPath) { - BlocksoftCryptoLog.err(this._settings.currencyCode + ' EthScannerProcessor.getTransactions no _etherscanApiPath') - } - let link = this._etherscanApiPath + '&address=' + address - let logTitle = this._settings.currencyCode + ' EthScannerProcessor.getTransactions etherscan ' - transactions = await this._getFromEtherscan(address, link, logTitle, false, {}) - if (this._etherscanApiPathDeposits) { - link = this._etherscanApiPathDeposits + '&address=' + address - logTitle = this._settings.currencyCode + ' EthScannerProcessor.getTransactions etherscan deposits' - transactions = await this._getFromEtherscan(address, link, logTitle, false, transactions) - } + /** + * @param {string} address + * @return {Promise<{balance, unconfirmed, provider}>} + */ + async getBalanceBlockchain(address) { + await BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' EthScannerProcessor.getBalance started ' + + address + ); - if (this._useInternal && this._etherscanApiPathInternal) { - link = this._etherscanApiPathInternal + '&address=' + address - logTitle = this._settings.currencyCode + ' EthScannerProcessor.getTransactions etherscan forInternal' - transactions = await this._getFromEtherscan(address, link, logTitle, true, transactions) - } - } - if (!transactions) { - return [] - } - const reformatted = [] - for (const key in transactions) { - reformatted.push(transactions[key]) + this.checkWeb3CurrentServerUpdated(); + // noinspection JSUnresolvedVariable + let balance = 0; + let provider = ''; + let time = 0; + try { + if ( + this._trezorServerCode && + this._trezorServerCode.indexOf('http') === -1 + ) { + const res = await this._get(address); + + if ( + res && + typeof res.data !== 'undefined' && + res.data && + typeof res.data.balance !== 'undefined' + ) { + balance = res.data.balance; + provider = res.provider; + time = res.time; + return { + balance, + unconfirmed: 0, + provider, + time, + balanceScanBlock: res.data.nonce + }; } - return reformatted + } + } catch (e) { + if (config.debug.cryptoErrors) { + console.log( + this._settings.currencyCode + + ' EthScannerProcessor.getBalance ' + + address + + ' error ' + + e.message + ); + } + await BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' EthScannerProcessor.getBalance ' + + address + + ' trezor error ' + + e.message + ); + return false; } - async _getFromOklink(address, key, logTitle, transactions = {}) { - const link = 'https://www.oklink.com/api/v5/explorer/address/transaction-list?chainShortName=ethw&address=' + address - const tmp = await BlocksoftAxios.getWithHeaders(link, {'Ok-Access-Key': key}) - if (typeof tmp?.data?.data === 'undefined' || typeof tmp?.data?.data[0] === 'undefined' || typeof tmp?.data?.data[0]?.transactionLists === 'undefined') { - return [] - } - await BlocksoftCryptoLog.log(logTitle + ' started ', link) - const list = tmp.data.data[0].transactionLists - for (const tx of list) { - const transaction = await this._unifyTransactionOklink(address, tx) - if (transaction) { - transactions[transaction.transactionHash] = transaction - } - } - await BlocksoftCryptoLog.log(logTitle + ' finished ' + address) - return transactions + try { + balance = await this._web3.eth.getBalance(address); + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' EthScannerProcessor.getBalance ' + + address + + ' result ' + + JSON.stringify(balance) + ); + provider = 'web3'; + time = 'now()'; + return { balance, unconfirmed: 0, provider, time }; + } catch (e) { + if (config.debug.cryptoErrors) { + console.log( + this._settings.currencyCode + + ' EthScannerProcessor.getBalance ' + + address + + ' ' + + this._web3.LINK + + ' rpc error ' + + e.message + ); + } + await BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' EthScannerProcessor.getBalance ' + + address + + ' ' + + this._web3.LINK + + ' rpc error ' + + e.message + ); + return false; } + } + /** + * @param {string} scanData.account.address + * @return {Promise<[UnifiedTransaction]>} + */ + async getTransactionsBlockchain(scanData) { + const address = scanData.account.address; + await BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' EthScannerProcessor.getTransactions started ' + + address + ); + let res = false; + if ( + this._settings.currencyCode !== 'ETH_ROPSTEN' && + this._settings.currencyCode !== 'ETH_RINKEBY' && + this._trezorServerCode + ) { + try { + res = await this._get(address); + } catch (e) { + throw new Error(e.message + ' in EthScannerProcessor._get'); + } + } - async _getFromEtherscan(address, link, logTitle, isInternal, transactions = {}) { + let transactions; + if (res && typeof res.data !== 'undefined' && res.data) { + await BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' EthScannerProcessor.getBalance loaded from ' + + res.provider + + ' ' + + res.time + ); + if ( + this._tokenAddress && + typeof res.data.formattedTokens[this._tokenAddress] === 'undefined' + ) { + await BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' EthScannerProcessor.getTransactions skipped token ' + + this._tokenAddress + + ' ' + + address + ); + return false; + } + await BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' EthScannerProcessor.getTransactions trezor unify started ' + + address + ); + transactions = await this._unifyTransactions( + address, + res.data.transactions, + false, + true, + {} + ); + await BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' EthScannerProcessor.getTransactions trezor finished ' + + address + ); + } else if (this._oklinkAPI) { + let logTitle = + this._settings.currencyCode + + ' EthScannerProcessor.getTransactions oklink '; + transactions = await this._getFromOklink( + address, + this._oklinkAPI, + logTitle, + {} + ); + } else { + if (!this._etherscanApiPath) { + BlocksoftCryptoLog.err( + this._settings.currencyCode + + ' EthScannerProcessor.getTransactions no _etherscanApiPath' + ); + } + let link = this._etherscanApiPath + '&address=' + address; + let logTitle = + this._settings.currencyCode + + ' EthScannerProcessor.getTransactions etherscan '; + transactions = await this._getFromEtherscan( + address, + link, + logTitle, + false, + {} + ); + if (this._etherscanApiPathDeposits) { + link = this._etherscanApiPathDeposits + '&address=' + address; + logTitle = + this._settings.currencyCode + + ' EthScannerProcessor.getTransactions etherscan deposits'; + transactions = await this._getFromEtherscan( + address, + link, + logTitle, + false, + transactions + ); + } - await BlocksoftCryptoLog.log(logTitle + ' started ' + JSON.stringify(isInternal), link) - const tmp = await BlocksoftAxios.getWithoutBraking(link) - if (!tmp || typeof tmp.data === 'undefined' || !tmp.data || typeof tmp.data.result === 'undefined') { - return transactions - } - if (typeof tmp.data.result === 'string') { - if (tmp.data.result.indexOf('API Key') === -1) { - throw new Error('Undefined txs etherscan ' + link + ' ' + tmp.data.result) - } else { - return transactions - } - } + if (this._useInternal && this._etherscanApiPathInternal) { + link = this._etherscanApiPathInternal + '&address=' + address; + logTitle = + this._settings.currencyCode + + ' EthScannerProcessor.getTransactions etherscan forInternal'; + transactions = await this._getFromEtherscan( + address, + link, + logTitle, + true, + transactions + ); + } + } + if (!transactions) { + return []; + } + const reformatted = []; + for (const key in transactions) { + reformatted.push(transactions[key]); + } + return reformatted; + } - transactions = await this._unifyTransactions(address, tmp.data.result, isInternal, false, transactions) - await BlocksoftCryptoLog.log(logTitle + ' finished ' + address) - return transactions + async _getFromOklink(address, key, logTitle, transactions = {}) { + const link = + 'https://www.oklink.com/api/v5/explorer/address/transaction-list?chainShortName=ethw&address=' + + address; + const tmp = await BlocksoftAxios.getWithHeaders(link, { + 'Ok-Access-Key': key + }); + if ( + typeof tmp?.data?.data === 'undefined' || + typeof tmp?.data?.data[0] === 'undefined' || + typeof tmp?.data?.data[0]?.transactionLists === 'undefined' + ) { + return []; } + await BlocksoftCryptoLog.log(logTitle + ' started ', link); + const list = tmp.data.data[0].transactionLists; + for (const tx of list) { + const transaction = await this._unifyTransactionOklink(address, tx); + if (transaction) { + transactions[transaction.transactionHash] = transaction; + } + } + await BlocksoftCryptoLog.log(logTitle + ' finished ' + address); + return transactions; + } + async _getFromEtherscan( + address, + link, + logTitle, + isInternal, + transactions = {} + ) { + await BlocksoftCryptoLog.log( + logTitle + ' started ' + JSON.stringify(isInternal), + link + ); + const tmp = await BlocksoftAxios.getWithoutBraking(link); + if ( + !tmp || + typeof tmp.data === 'undefined' || + !tmp.data || + typeof tmp.data.result === 'undefined' + ) { + return transactions; + } + if (typeof tmp.data.result === 'string') { + if (tmp.data.result.indexOf('API Key') === -1) { + throw new Error( + 'Undefined txs etherscan ' + link + ' ' + tmp.data.result + ); + } else { + return transactions; + } + } - /** - * @param {string} txHash - * @return {Promise<[UnifiedTransaction]>} - */ - async getTransactionBlockchain(txHash) { - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthScannerProcessor.getTransaction started ' + txHash) + transactions = await this._unifyTransactions( + address, + tmp.data.result, + isInternal, + false, + transactions + ); + await BlocksoftCryptoLog.log(logTitle + ' finished ' + address); + return transactions; + } + /** + * @param {string} txHash + * @return {Promise<[UnifiedTransaction]>} + */ + async getTransactionBlockchain(txHash) { + await BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' EthScannerProcessor.getTransaction started ' + + txHash + ); - this._trezorServer = await BlocksoftExternalSettings.getTrezorServer(this._trezorServerCode, this._settings.currencyCode + ' ETH.Scanner.getTransaction') + this._trezorServer = await BlocksoftExternalSettings.getTrezorServer( + this._trezorServerCode, + this._settings.currencyCode + ' ETH.Scanner.getTransaction' + ); - if (typeof this._trezorServer === 'undefined') { - BlocksoftCryptoLog.err(this._settings.currencyCode + ' EthScannerProcessor.getTransaction empty trezorServer') - throw new Error(this._settings.currencyCode + ' EthScannerProcessor.getTransaction empty trezorServer') - } + if (typeof this._trezorServer === 'undefined') { + BlocksoftCryptoLog.err( + this._settings.currencyCode + + ' EthScannerProcessor.getTransaction empty trezorServer' + ); + throw new Error( + this._settings.currencyCode + + ' EthScannerProcessor.getTransaction empty trezorServer' + ); + } - if (!this._trezorServer) { - return false - } + if (!this._trezorServer) { + return false; + } - let link = this._trezorServer + '/api/v2/tx-specific/' + txHash - let res = await BlocksoftAxios.getWithoutBraking(link) + let link = this._trezorServer + '/api/v2/tx-specific/' + txHash; + let res = await BlocksoftAxios.getWithoutBraking(link); - if (!res || !res.data) { - BlocksoftExternalSettings.setTrezorServerInvalid(this._trezorServerCode, this._trezorServer) - this._trezorServer = await BlocksoftExternalSettings.getTrezorServer(this._trezorServerCode, this._settings.currencyCode + ' ETH.Scanner._get') - if (typeof this._trezorServer === 'undefined') { - BlocksoftCryptoLog.err(this._settings.currencyCode + ' EthScannerProcessor._get empty trezorServer2') - throw new Error(this._settings.currencyCode + ' EthScannerProcessor._get empty trezorServer2') - } - link = this._trezorServer + '/api/v2/tx-specific/' + txHash - res = await BlocksoftAxios.getWithoutBraking(link) - if (!res || !res.data) { - BlocksoftExternalSettings.setTrezorServerInvalid(this._trezorServerCode, this._trezorServer) - return false - } - } + if (!res || !res.data) { + BlocksoftExternalSettings.setTrezorServerInvalid( + this._trezorServerCode, + this._trezorServer + ); + this._trezorServer = await BlocksoftExternalSettings.getTrezorServer( + this._trezorServerCode, + this._settings.currencyCode + ' ETH.Scanner._get' + ); + if (typeof this._trezorServer === 'undefined') { + BlocksoftCryptoLog.err( + this._settings.currencyCode + + ' EthScannerProcessor._get empty trezorServer2' + ); + throw new Error( + this._settings.currencyCode + + ' EthScannerProcessor._get empty trezorServer2' + ); + } + link = this._trezorServer + '/api/v2/tx-specific/' + txHash; + res = await BlocksoftAxios.getWithoutBraking(link); + if (!res || !res.data) { + BlocksoftExternalSettings.setTrezorServerInvalid( + this._trezorServerCode, + this._trezorServer + ); + return false; + } + } - if (typeof res.data.tx === 'undefined') { - return false - } + if (typeof res.data.tx === 'undefined') { + return false; + } - let tx - if (typeof res.data.receipt === 'undefined') { - tx = { ...{ status: 0x0 }, ...res.data.tx } - } else { - tx = { ...res.data.receipt, ...res.data.tx } - } + let tx; + if (typeof res.data.receipt === 'undefined') { + tx = { ...{ status: 0x0 }, ...res.data.tx }; + } else { + tx = { ...res.data.receipt, ...res.data.tx }; + } + + tx.nonce = BlocksoftUtils.hexToDecimal(tx.nonce); + if (tx.nonce * 1 === 0) { + tx.nonce = 0; + } + tx.status = BlocksoftUtils.hexToDecimal(tx.status); + tx.gas = BlocksoftUtils.hexToDecimal(tx.gas); + tx.gasPrice = BlocksoftUtils.hexToDecimal(tx.gasPrice); + tx.gasUsed = BlocksoftUtils.hexToDecimal(tx.gasUsed); + return tx; + } + + /** + * @param {string} address + * @param {*} result[] + * @param {boolean} isInternal + * @returns {Promise<[{UnifiedTransaction}]>} + * @private + */ + async _unifyTransactions( + _address, + result, + isInternal, + isTrezor = false, + transactions = {} + ) { + if (!result) { + return transactions; + } + const address = _address.toLowerCase(); + let tx; + let maxNonce = -1; + let maxSuccessNonce = -1; - tx.nonce = BlocksoftUtils.hexToDecimal(tx.nonce) - if (tx.nonce * 1 === 0) { - tx.nonce = 0 + const notBroadcasted = await EthRawDS.getForAddress({ + address, + currencyCode: this._settings.currencyCode + }); + for (tx of result) { + try { + let transaction; + const key = typeof tx.hash !== 'undefined' ? tx.hash : tx.txid; + if (typeof transactions[key] !== 'undefined') { + continue; } - tx.status = BlocksoftUtils.hexToDecimal(tx.status) - tx.gas = BlocksoftUtils.hexToDecimal(tx.gas) - tx.gasPrice = BlocksoftUtils.hexToDecimal(tx.gasPrice) - tx.gasUsed = BlocksoftUtils.hexToDecimal(tx.gasUsed) - return tx - } - - - /** - * @param {string} address - * @param {*} result[] - * @param {boolean} isInternal - * @returns {Promise<[{UnifiedTransaction}]>} - * @private - */ - async _unifyTransactions(_address, result, isInternal, isTrezor = false, transactions = {}) { - if (!result) { - return transactions + if (isTrezor) { + transaction = await this._unifyTransactionTrezor( + address, + tx, + isInternal + ); + } else { + transaction = await this._unifyTransaction(address, tx, isInternal); } - const address = _address.toLowerCase() - let tx - let maxNonce = -1 - let maxSuccessNonce = -1 - - const notBroadcasted = await EthRawDS.getForAddress({ address, currencyCode: this._settings.currencyCode }) - for (tx of result) { - try { - - let transaction - const key = typeof tx.hash !== 'undefined' ? tx.hash : tx.txid - if (typeof transactions[key] !== 'undefined') { - continue - } - if (isTrezor) { - transaction = await this._unifyTransactionTrezor(address, tx, isInternal) - } else { - transaction = await this._unifyTransaction(address, tx, isInternal) - } - if (transaction) { - transactions[key] = transaction - if ( - typeof transaction.transactionJson !== 'undefined' - && typeof transaction.transactionJson.feeType === 'undefined' - && (transaction.transactionDirection === 'outcome' || transaction.transactionDirection === 'self') - && typeof transaction.transactionJson.nonce !== 'undefined') { - - const uniqueFrom = address.toLowerCase() + '_' + transaction.transactionJson.nonce - if (notBroadcasted && typeof notBroadcasted[uniqueFrom] !== 'undefined' && transaction.transactionStatus !== 'new') { - EthRawDS.cleanRaw({ - address, - transactionUnique: uniqueFrom, - currencyCode: this._settings.currencyCode - }) - } - if (transaction.transactionJson.nonce * 1 > maxNonce) { - maxNonce = transaction.transactionJson.nonce * 1 - } - if ((transaction.transactionStatus === 'success' || transaction.transactionStatus === 'confirming')) { - if (transaction.transactionJson.nonce * 1 > maxSuccessNonce) { - maxSuccessNonce = transaction.transactionJson.nonce * 1 - } - } - } - } - } catch (e) { - BlocksoftCryptoLog.err(this._settings.currencyCode + ' EthScannerProcessor._unifyTransaction error ' + e.message + ' on ' + (isTrezor ? 'Trezor' : 'usual') + ' tx ' + JSON.stringify(tx)) + if (transaction) { + transactions[key] = transaction; + if ( + typeof transaction.transactionJson !== 'undefined' && + typeof transaction.transactionJson.feeType === 'undefined' && + (transaction.transactionDirection === 'outcome' || + transaction.transactionDirection === 'self') && + typeof transaction.transactionJson.nonce !== 'undefined' + ) { + const uniqueFrom = + address.toLowerCase() + '_' + transaction.transactionJson.nonce; + if ( + notBroadcasted && + typeof notBroadcasted[uniqueFrom] !== 'undefined' && + transaction.transactionStatus !== 'new' + ) { + EthRawDS.cleanRaw({ + address, + transactionUnique: uniqueFrom, + currencyCode: this._settings.currencyCode + }); } + if (transaction.transactionJson.nonce * 1 > maxNonce) { + maxNonce = transaction.transactionJson.nonce * 1; + } + if ( + transaction.transactionStatus === 'success' || + transaction.transactionStatus === 'confirming' + ) { + if (transaction.transactionJson.nonce * 1 > maxSuccessNonce) { + maxSuccessNonce = transaction.transactionJson.nonce * 1; + } + } + } } + } catch (e) { + BlocksoftCryptoLog.err( + this._settings.currencyCode + + ' EthScannerProcessor._unifyTransaction error ' + + e.message + + ' on ' + + (isTrezor ? 'Trezor' : 'usual') + + ' tx ' + + JSON.stringify(tx) + ); + } + } - if (maxNonce > -1) { - await EthTmpDS.saveNonce(this._mainCurrencyCode, address, 'maxScanned', maxNonce) - } + if (maxNonce > -1) { + await EthTmpDS.saveNonce( + this._mainCurrencyCode, + address, + 'maxScanned', + maxNonce + ); + } - if (maxSuccessNonce > -1) { - await EthTmpDS.saveNonce(this._mainCurrencyCode, address, 'maxSuccess', maxSuccessNonce) - } + if (maxSuccessNonce > -1) { + await EthTmpDS.saveNonce( + this._mainCurrencyCode, + address, + 'maxSuccess', + maxSuccessNonce + ); + } - return transactions - } - - /** - * @param {string} address - * @param {Object} transaction - * @param {string} transaction.txid: "0xdbbce8ace9ecfa2bcd2a5ff54590a9f3b9c445c1111f1b9404ec33ef2314a864" - * @param {string} transaction.vin[].addresses[] - * @param {string} transaction.vin[].isAddress - * @param {string} transaction.vout[].addresses[] - * @param {string} transaction.vout[].isAddress - * @param {string} transaction.blockHash: "0xf31e629dea39a96da1ff977fc991552c5f131c4b544a87fbea0533e985ce7e69" - * @param {string} transaction.blockHeight: 9409918 - * @param {string} transaction.confirmations: 78610 - * @param {string} transaction.blockTime: 1580737403 - * @param {string} transaction.value: "12559200000000000" - * @param {string} transaction.fees: "69854400000000" - * @param {string} transaction.ethereumSpecific.status 1 - * @param {string} transaction.ethereumSpecific.nonce 42458 - * @param {string} transaction.ethereumSpecific.gasLimit 150000 - * @param {string} transaction.ethereumSpecific.gasUsed 21000 - * @param {string} transaction.ethereumSpecific.gasPrice "3326400000" - * @param {array} transaction.tokenTransfers - * @param {string} transaction.tokenTransfers[].type "ERC20" - * @param {string} transaction.tokenTransfers[].from "0x8b661361be29e688dda65b323526ad536c8b3997" - * @param {string} transaction.tokenTransfers[].to "0xa00ed7686c380740fe2adb141136c217b90c5ca5" - * @param {string} transaction.tokenTransfers[].token "0xdac17f958d2ee523a2206206994597c13d831ec7" - * @param {string} transaction.tokenTransfers[].name "Tether USD" - * @param {string} transaction.tokenTransfers[].symbol "USDT" - * @param {string} transaction.tokenTransfers[].decimals 6 - * @param {string} transaction.tokenTransfers[].value "1000000" - * @private - */ - async _unifyTransactionTrezor(_address, transaction, isInternal = false) { - let fromAddress = '' - const address = _address.toLowerCase() - if (typeof transaction.vin[0] !== 'undefined' && transaction.vin[0].addresses && typeof transaction.vin[0].addresses[0] !== 'undefined') { - fromAddress = transaction.vin[0].addresses[0].toLowerCase() - } - let toAddress = '' - if (typeof transaction.vout[0] !== 'undefined' && transaction.vout[0].addresses && typeof transaction.vout[0].addresses[0] !== 'undefined') { - toAddress = transaction.vout[0].addresses[0].toLowerCase() - } - let amount = transaction.value + return transactions; + } - let nonce = transaction.ethereumSpecific.nonce - if (nonce * 1 === 0) { - nonce = 0 - } - const additional = { - nonce, - gas: transaction.ethereumSpecific.gasLimit || '', - gasPrice: transaction.ethereumSpecific.gasPrice || '', - gasUsed: transaction.ethereumSpecific.gasUsed || '' + /** + * @param {string} address + * @param {Object} transaction + * @param {string} transaction.txid: "0xdbbce8ace9ecfa2bcd2a5ff54590a9f3b9c445c1111f1b9404ec33ef2314a864" + * @param {string} transaction.vin[].addresses[] + * @param {string} transaction.vin[].isAddress + * @param {string} transaction.vout[].addresses[] + * @param {string} transaction.vout[].isAddress + * @param {string} transaction.blockHash: "0xf31e629dea39a96da1ff977fc991552c5f131c4b544a87fbea0533e985ce7e69" + * @param {string} transaction.blockHeight: 9409918 + * @param {string} transaction.confirmations: 78610 + * @param {string} transaction.blockTime: 1580737403 + * @param {string} transaction.value: "12559200000000000" + * @param {string} transaction.fees: "69854400000000" + * @param {string} transaction.ethereumSpecific.status 1 + * @param {string} transaction.ethereumSpecific.nonce 42458 + * @param {string} transaction.ethereumSpecific.gasLimit 150000 + * @param {string} transaction.ethereumSpecific.gasUsed 21000 + * @param {string} transaction.ethereumSpecific.gasPrice "3326400000" + * @param {array} transaction.tokenTransfers + * @param {string} transaction.tokenTransfers[].type "ERC20" + * @param {string} transaction.tokenTransfers[].from "0x8b661361be29e688dda65b323526ad536c8b3997" + * @param {string} transaction.tokenTransfers[].to "0xa00ed7686c380740fe2adb141136c217b90c5ca5" + * @param {string} transaction.tokenTransfers[].token "0xdac17f958d2ee523a2206206994597c13d831ec7" + * @param {string} transaction.tokenTransfers[].name "Tether USD" + * @param {string} transaction.tokenTransfers[].symbol "USDT" + * @param {string} transaction.tokenTransfers[].decimals 6 + * @param {string} transaction.tokenTransfers[].value "1000000" + * @private + */ + async _unifyTransactionTrezor(_address, transaction, isInternal = false) { + let fromAddress = ''; + const address = _address.toLowerCase(); + if ( + typeof transaction.vin[0] !== 'undefined' && + transaction.vin[0].addresses && + typeof transaction.vin[0].addresses[0] !== 'undefined' + ) { + fromAddress = transaction.vin[0].addresses[0].toLowerCase(); + } + let toAddress = ''; + if ( + typeof transaction.vout[0] !== 'undefined' && + transaction.vout[0].addresses && + typeof transaction.vout[0].addresses[0] !== 'undefined' + ) { + toAddress = transaction.vout[0].addresses[0].toLowerCase(); + } + let amount = transaction.value; + + let nonce = transaction.ethereumSpecific.nonce; + if (nonce * 1 === 0) { + nonce = 0; + } + const additional = { + nonce, + gas: transaction.ethereumSpecific.gasLimit || '', + gasPrice: transaction.ethereumSpecific.gasPrice || '', + gasUsed: transaction.ethereumSpecific.gasUsed || '' + }; + let fee = transaction.fees || 0; + let feeCurrencyCode = this._mainCurrencyCode; + + if (this._tokenAddress) { + let failToken = false; + if (typeof transaction.tokenTransfers === 'undefined') { + if (this._tokenAddress === toAddress) { + failToken = true; + } else { + return false; } - let fee = transaction.fees || 0 - let feeCurrencyCode = this._mainCurrencyCode - - if (this._tokenAddress) { - let failToken = false - if (typeof transaction.tokenTransfers === 'undefined') { - if (this._tokenAddress === toAddress) { - failToken = true - } else { - return false - } + } + if (!failToken) { + let tmp; + let found = false; + amount = new BlocksoftBN(0); + for (tmp of transaction.tokenTransfers) { + if (tmp.token.toLowerCase() === this._tokenAddress.toLowerCase()) { + tmp.from = tmp.from.toLowerCase(); + tmp.to = tmp.to.toLowerCase(); + if (tmp.to !== address && tmp.from !== address) { + continue; } - if (!failToken) { - let tmp - let found = false - amount = new BlocksoftBN(0) - for (tmp of transaction.tokenTransfers) { - if (tmp.token.toLowerCase() === this._tokenAddress.toLowerCase()) { - tmp.from = tmp.from.toLowerCase() - tmp.to = tmp.to.toLowerCase() - if (tmp.to !== address && tmp.from !== address) { - continue - } - if (tmp.to === address) { - fromAddress = tmp.from - amount.add(tmp.value) - } else if (tmp.from === address) { - if (this._delegateAddress && tmp.to.toLowerCase() === this._delegateAddress.toLowerCase()) { - fee = tmp.value - additional.feeType = 'DELEGATE' - feeCurrencyCode = this._settings.currencyCode || 'DELEGATE' - } else { - toAddress = tmp.to - amount.diff(tmp.value) - } - } - found = true - } - } - amount = amount.get() - if (amount < 0) { - amount = -1 * amount - fromAddress = address - } else { - toAddress = address - } - if (!found) { - return false - } + if (tmp.to === address) { + fromAddress = tmp.from; + amount.add(tmp.value); + } else if (tmp.from === address) { + if ( + this._delegateAddress && + tmp.to.toLowerCase() === this._delegateAddress.toLowerCase() + ) { + fee = tmp.value; + additional.feeType = 'DELEGATE'; + feeCurrencyCode = this._settings.currencyCode || 'DELEGATE'; + } else { + toAddress = tmp.to; + amount.diff(tmp.value); + } } + found = true; + } } - - if (typeof transaction.blockTime === 'undefined') { - throw new Error(' no transaction.blockTime error transaction data ' + JSON.stringify(transaction)) + amount = amount.get(); + if (amount < 0) { + amount = -1 * amount; + fromAddress = address; + } else { + toAddress = address; } - let formattedTime = transaction.blockTime - try { - formattedTime = BlocksoftUtils.toDate(transaction.blockTime) - } catch (e) { - e.message += ' timestamp error transaction data ' + JSON.stringify(transaction) - throw e + if (!found) { + return false; } + } + } - let blockHash = false - const confirmations = transaction.confirmations - try { - CACHE_BLOCK_NUMBER_TO_HASH[this._mainCurrencyCode][transaction.blockHeight] = transaction.blockHash - if (typeof transaction.blockHash !== 'undefined') { - blockHash = transaction.blockHash - } - if (confirmations > 0 && transaction.blockHeight > CACHE_GET_MAX_BLOCK[this._mainCurrencyCode].max_block_number) { - CACHE_GET_MAX_BLOCK[this._mainCurrencyCode].max_block_number = transaction.blockHeight - CACHE_GET_MAX_BLOCK[this._mainCurrencyCode].confirmations = confirmations - } - } catch (e) { - throw new Error(e.message + ' in CACHE_GET_MAX_BLOCK ' + this._mainCurrencyCode ) - } - let transactionStatus = 'new' - - if (blockHash) { - if (transaction.ethereumSpecific.status === 1) { - if (confirmations > this._blocksToConfirm) { - transactionStatus = 'success' - } else if (confirmations > 0) { - transactionStatus = 'confirming' - } - } else { - transactionStatus = 'fail' - } - } + if (typeof transaction.blockTime === 'undefined') { + throw new Error( + ' no transaction.blockTime error transaction data ' + + JSON.stringify(transaction) + ); + } + let formattedTime = transaction.blockTime; + try { + formattedTime = BlocksoftUtils.toDate(transaction.blockTime); + } catch (e) { + e.message += + ' timestamp error transaction data ' + JSON.stringify(transaction); + throw e; + } - const tx = { - transactionHash: transaction.txid.toLowerCase(), - blockHash: blockHash || '', - blockNumber: +transaction.blockHeight, - blockTime: formattedTime, - blockConfirmations: confirmations, - transactionDirection: (address.toLowerCase() === fromAddress.toLowerCase()) ? 'outcome' : 'income', - addressFrom: address.toLowerCase() === fromAddress.toLowerCase() ? '' : fromAddress, - addressTo: address.toLowerCase() === toAddress.toLowerCase() ? '' : toAddress, - addressFromBasic: fromAddress.toLowerCase(), - addressAmount: amount, - transactionStatus: transactionStatus, - transactionFee: fee, - transactionFeeCurrencyCode: feeCurrencyCode, - contractAddress: '', - inputValue: '' - } - if (tx.addressFrom === '' && tx.addressTo === '') { - tx.transactionDirection = 'self' - // self zero will not shown if uncomment! tx.addressAmount = 0 - } - if (additional) { - tx.transactionJson = additional - } - return tx - } - - /** - * @param {string} address - * @param {Object} transaction - * @param {string} transaction.amount 0.001852572296633876 - * @param {string} transaction.blockHash 0x3661deb1c783baed6b1917a1f58ad460ad9cbaac5977919cfd75019e6a12363a - * @param {string} transaction.challengeStatus - * @param {string} transaction.from 0xf1cff704c6e6ce459e3e1544a9533cccbdad7b99 - * @param {string} transaction.height 5495990 - * @param {string} transaction.isFromContract false - * @param {string} transaction.isToContract false - * @param {string} transaction.l1OriginHash '' - * @param {string} transaction.methodId '' - * @param {string} transaction.state success - * @param {string} transaction.to 0xf1cff704c6e6ce459e3e1544a9533cccbdad7b99 - * @param {string} transaction.tokenContractAddress '' - * @param {string} transaction.tokenId '' - * @param {string} transaction.transactionSymbol ETHW - * @param {string} transaction.transactionTime 1662632042000 - * @param {string} transaction.txFee 0.000231 - * @param {string} transaction.txId 0x3cc404044f96f07bee0af9717d49ce72dba645c5bb5af4846be84dafa68127b1 - * @return {UnifiedTransaction} - * @protected - */ - async _unifyTransactionOklink(_address, transaction) { - if (typeof transaction.transactionTime === 'undefined') { - throw new Error(' no transaction.timeStamp error transaction data ' + JSON.stringify(transaction)) - } + let blockHash = false; + const confirmations = transaction.confirmations; + try { + CACHE_BLOCK_NUMBER_TO_HASH[this._mainCurrencyCode][ + transaction.blockHeight + ] = transaction.blockHash; + if (typeof transaction.blockHash !== 'undefined') { + blockHash = transaction.blockHash; + } + if ( + confirmations > 0 && + transaction.blockHeight > + CACHE_GET_MAX_BLOCK[this._mainCurrencyCode].max_block_number + ) { + CACHE_GET_MAX_BLOCK[this._mainCurrencyCode].max_block_number = + transaction.blockHeight; + CACHE_GET_MAX_BLOCK[this._mainCurrencyCode].confirmations = + confirmations; + } + } catch (e) { + throw new Error( + e.message + ' in CACHE_GET_MAX_BLOCK ' + this._mainCurrencyCode + ); + } + let transactionStatus = 'new'; - const address = _address.toLowerCase() - const formattedTime = transaction.transactionTime - - let transactionStatus = 'new' - let confirmations = 0 - if (transaction.state === 'success') { - const diff = new Date().getTime() - transaction.transactionTime - confirmations = Math.round(diff / 60000) - if (confirmations > 120) { - transactionStatus = 'success' - } else { - transactionStatus = 'confirming' - } - } else if (transaction.state === 'fail') { - transactionStatus = 'fail' - } else if (transaction.state === 'pending') { - transactionStatus = 'confirming' + if (blockHash) { + if (transaction.ethereumSpecific.status === 1) { + if (confirmations > this._blocksToConfirm) { + transactionStatus = 'success'; + } else if (confirmations > 0) { + transactionStatus = 'confirming'; } + } else { + transactionStatus = 'fail'; + } + } - const tx = { - transactionHash: transaction.txId.toLowerCase() + '_1', - blockHash: transaction.blockHash, - blockNumber: +transaction.height, - blockTime: formattedTime, - blockConfirmations: confirmations, - transactionDirection: (address.toLowerCase() === transaction.from.toLowerCase()) ? 'outcome' : 'income', - addressFrom: (address.toLowerCase() === transaction.from.toLowerCase()) ? '' : transaction.from, - addressFromBasic: transaction.from.toLowerCase(), - addressTo: (address.toLowerCase() === transaction.to.toLowerCase()) ? '' : transaction.to, - addressAmount : BlocksoftPrettyNumbers.setCurrencyCode('ETH').makeUnPretty(transaction.amount), - transactionStatus: transactionStatus, - contractAddress : '', - inputValue: '', - transactionFee: BlocksoftPrettyNumbers.setCurrencyCode('ETH').makeUnPretty(transaction.txFee) - } - if (tx.addressFrom === '' && tx.addressTo === '') { - tx.transactionDirection = 'self' - tx.addressAmount = 0 - } - return tx - } - - /** - * @param {string} address - * @param {Object} transaction - * @param {string} transaction.blockNumber 4673230 - * @param {string} transaction.timeStamp 1512376529 - * @param {string} transaction.hash - * @param {string} transaction.nonce - * @param {string} transaction.blockHash - * @param {string} transaction.transactionIndex - * @param {string} transaction.from - * @param {string} transaction.to - * @param {string} transaction.value - * @param {string} transaction.gas - * @param {string} transaction.gasPrice - * @param {string} transaction.isError - * @param {string} transaction.txreceipt_status - * @param {string} transaction.input - * @param {string} transaction.type - * @param {string} transaction.contractAddress - * @param {string} transaction.cumulativeGasUsed - * @param {string} transaction.gasUsed - * @param {string} transaction.confirmations - * @param {boolean} isInternal - * @return {UnifiedTransaction} - * @protected - */ - async _unifyTransaction(_address, transaction, isInternal = false) { - if (typeof transaction.timeStamp === 'undefined') { - throw new Error(' no transaction.timeStamp error transaction data ' + JSON.stringify(transaction)) - } + const tx = { + transactionHash: transaction.txid.toLowerCase(), + blockHash: blockHash || '', + blockNumber: +transaction.blockHeight, + blockTime: formattedTime, + blockConfirmations: confirmations, + transactionDirection: + address.toLowerCase() === fromAddress.toLowerCase() + ? 'outcome' + : 'income', + addressFrom: + address.toLowerCase() === fromAddress.toLowerCase() ? '' : fromAddress, + addressTo: + address.toLowerCase() === toAddress.toLowerCase() ? '' : toAddress, + addressFromBasic: fromAddress.toLowerCase(), + addressAmount: amount, + transactionStatus: transactionStatus, + transactionFee: fee, + transactionFeeCurrencyCode: feeCurrencyCode, + contractAddress: '', + inputValue: '' + }; + if (tx.addressFrom === '' && tx.addressTo === '') { + tx.transactionDirection = 'self'; + // self zero will not shown if uncomment! tx.addressAmount = 0 + } + if (additional) { + tx.transactionJson = additional; + } + return tx; + } - const address = _address.toLowerCase() - let formattedTime = transaction.timeStamp - try { - formattedTime = BlocksoftUtils.toDate(transaction.timeStamp) - } catch (e) { - console.log('no timestamp2') - e.message += ' timestamp error transaction data ' + JSON.stringify(transaction) - throw e - } + /** + * @param {string} address + * @param {Object} transaction + * @param {string} transaction.amount 0.001852572296633876 + * @param {string} transaction.blockHash 0x3661deb1c783baed6b1917a1f58ad460ad9cbaac5977919cfd75019e6a12363a + * @param {string} transaction.challengeStatus + * @param {string} transaction.from 0xf1cff704c6e6ce459e3e1544a9533cccbdad7b99 + * @param {string} transaction.height 5495990 + * @param {string} transaction.isFromContract false + * @param {string} transaction.isToContract false + * @param {string} transaction.l1OriginHash '' + * @param {string} transaction.methodId '' + * @param {string} transaction.state success + * @param {string} transaction.to 0xf1cff704c6e6ce459e3e1544a9533cccbdad7b99 + * @param {string} transaction.tokenContractAddress '' + * @param {string} transaction.tokenId '' + * @param {string} transaction.transactionSymbol ETHW + * @param {string} transaction.transactionTime 1662632042000 + * @param {string} transaction.txFee 0.000231 + * @param {string} transaction.txId 0x3cc404044f96f07bee0af9717d49ce72dba645c5bb5af4846be84dafa68127b1 + * @return {UnifiedTransaction} + * @protected + */ + async _unifyTransactionOklink(_address, transaction) { + if (typeof transaction.transactionTime === 'undefined') { + throw new Error( + ' no transaction.timeStamp error transaction data ' + + JSON.stringify(transaction) + ); + } - let addressAmount = transaction.value - if (typeof transaction.L1TxOrigin !== 'undefined') { - if (transaction.from === '0x0000000000000000000000000000000000000000') { - transaction.from = 'ETH: ' + transaction.L1TxOrigin - } - CACHE_BLOCK_NUMBER_TO_HASH[transaction.blockNumber] = transaction.blockHash - addressAmount = transaction.tokenValue - transaction.confirmations = 100 - } else if (isInternal) { - if (transaction.contractAddress !== '') { - return false - } - if (transaction.type !== 'call') { - return false - } + const address = _address.toLowerCase(); + const formattedTime = transaction.transactionTime; - if (typeof CACHE_BLOCK_NUMBER_TO_HASH[this._mainCurrencyCode][transaction.blockNumber] === 'undefined') { - const data = await this._web3.eth.getTransaction(transaction.hash) - CACHE_BLOCK_NUMBER_TO_HASH[this._mainCurrencyCode][transaction.blockNumber] = data?.blockHash - } - transaction.blockHash = CACHE_BLOCK_NUMBER_TO_HASH[this._mainCurrencyCode][transaction.blockNumber] || transaction.blockNumber - // noinspection PointlessArithmeticExpressionJS - transaction.confirmations = CACHE_GET_MAX_BLOCK[this._mainCurrencyCode].max_block_number - transaction.blockNumber + 1 * CACHE_GET_MAX_BLOCK[this._mainCurrencyCode].confirmations - } else { - CACHE_BLOCK_NUMBER_TO_HASH[transaction.blockNumber] = transaction.blockHash - } + let transactionStatus = 'new'; + let confirmations = 0; + if (transaction.state === 'success') { + const diff = new Date().getTime() - transaction.transactionTime; + confirmations = Math.round(diff / 60000); + if (confirmations > 120) { + transactionStatus = 'success'; + } else { + transactionStatus = 'confirming'; + } + } else if (transaction.state === 'fail') { + transactionStatus = 'fail'; + } else if (transaction.state === 'pending') { + transactionStatus = 'confirming'; + } - const confirmations = transaction.confirmations - if (confirmations > 0 && transaction.blockNumber > CACHE_GET_MAX_BLOCK[this._mainCurrencyCode].max_block_number) { - CACHE_GET_MAX_BLOCK[this._mainCurrencyCode].max_block_number = transaction.blockNumber - CACHE_GET_MAX_BLOCK[this._mainCurrencyCode].confirmations = confirmations - } - let transactionStatus = 'new' - if (typeof transaction.txreceipt_status === 'undefined' || transaction.txreceipt_status === '1') { - if (confirmations > this._blocksToConfirm) { - transactionStatus = 'success' - } else if (confirmations > 0) { - transactionStatus = 'confirming' - } - } else if (transaction.isError !== '0') { - transactionStatus = 'fail' - } - // if (isInternal) { - // transactionStatus = 'internal_' + transactionStatus - // } - let contractAddress = false - if (typeof transaction.contractAddress !== 'undefined') { - contractAddress = transaction.contractAddress.toLowerCase() - } - const tx = { - transactionHash: transaction.hash.toLowerCase(), - blockHash: transaction.blockHash, - blockNumber: +transaction.blockNumber, - blockTime: formattedTime, - blockConfirmations: confirmations, - transactionDirection: (address.toLowerCase() === transaction.from.toLowerCase()) ? 'outcome' : 'income', - addressFrom: (address.toLowerCase() === transaction.from.toLowerCase()) ? '' : transaction.from, - addressFromBasic: transaction.from.toLowerCase(), - addressTo: (address.toLowerCase() === transaction.to.toLowerCase()) ? '' : transaction.to, - addressAmount, - transactionStatus: transactionStatus, - contractAddress, - inputValue: transaction.input - } - let nonce = transaction.nonce - if (nonce * 1 === 0) { - nonce = 0 - } - if (!isInternal) { - const additional = { - nonce, - gas: transaction.gas, - gasPrice: transaction.gasPrice, - cumulativeGasUsed: transaction.cumulativeGasUsed, - gasUsed: transaction.gasUsed, - transactionIndex: transaction.transactionIndex - } - tx.transactionJson = additional - tx.transactionFee = BlocksoftUtils.mul(transaction.gasUsed, transaction.gasPrice).toString() - } - if (tx.addressFrom === '' && tx.addressTo === '') { - tx.transactionDirection = 'self' - tx.addressAmount = 0 - } - return tx + const tx = { + transactionHash: transaction.txId.toLowerCase() + '_1', + blockHash: transaction.blockHash, + blockNumber: +transaction.height, + blockTime: formattedTime, + blockConfirmations: confirmations, + transactionDirection: + address.toLowerCase() === transaction.from.toLowerCase() + ? 'outcome' + : 'income', + addressFrom: + address.toLowerCase() === transaction.from.toLowerCase() + ? '' + : transaction.from, + addressFromBasic: transaction.from.toLowerCase(), + addressTo: + address.toLowerCase() === transaction.to.toLowerCase() + ? '' + : transaction.to, + addressAmount: BlocksoftPrettyNumbers.setCurrencyCode('ETH').makeUnPretty( + transaction.amount + ), + transactionStatus: transactionStatus, + contractAddress: '', + inputValue: '', + transactionFee: BlocksoftPrettyNumbers.setCurrencyCode( + 'ETH' + ).makeUnPretty(transaction.txFee) + }; + if (tx.addressFrom === '' && tx.addressTo === '') { + tx.transactionDirection = 'self'; + tx.addressAmount = 0; + } + return tx; + } + + /** + * @param {string} address + * @param {Object} transaction + * @param {string} transaction.blockNumber 4673230 + * @param {string} transaction.timeStamp 1512376529 + * @param {string} transaction.hash + * @param {string} transaction.nonce + * @param {string} transaction.blockHash + * @param {string} transaction.transactionIndex + * @param {string} transaction.from + * @param {string} transaction.to + * @param {string} transaction.value + * @param {string} transaction.gas + * @param {string} transaction.gasPrice + * @param {string} transaction.isError + * @param {string} transaction.txreceipt_status + * @param {string} transaction.input + * @param {string} transaction.type + * @param {string} transaction.contractAddress + * @param {string} transaction.cumulativeGasUsed + * @param {string} transaction.gasUsed + * @param {string} transaction.confirmations + * @param {boolean} isInternal + * @return {UnifiedTransaction} + * @protected + */ + async _unifyTransaction(_address, transaction, isInternal = false) { + if (typeof transaction.timeStamp === 'undefined') { + throw new Error( + ' no transaction.timeStamp error transaction data ' + + JSON.stringify(transaction) + ); + } + + const address = _address.toLowerCase(); + let formattedTime = transaction.timeStamp; + try { + formattedTime = BlocksoftUtils.toDate(transaction.timeStamp); + } catch (e) { + console.log('no timestamp2'); + e.message += + ' timestamp error transaction data ' + JSON.stringify(transaction); + throw e; + } + + let addressAmount = transaction.value; + if (typeof transaction.L1TxOrigin !== 'undefined') { + if (transaction.from === '0x0000000000000000000000000000000000000000') { + transaction.from = 'ETH: ' + transaction.L1TxOrigin; + } + CACHE_BLOCK_NUMBER_TO_HASH[transaction.blockNumber] = + transaction.blockHash; + addressAmount = transaction.tokenValue; + transaction.confirmations = 100; + } else if (isInternal) { + if (transaction.contractAddress !== '') { + return false; + } + if (transaction.type !== 'call') { + return false; + } + + if ( + typeof CACHE_BLOCK_NUMBER_TO_HASH[this._mainCurrencyCode][ + transaction.blockNumber + ] === 'undefined' + ) { + const data = await this._web3.eth.getTransaction(transaction.hash); + CACHE_BLOCK_NUMBER_TO_HASH[this._mainCurrencyCode][ + transaction.blockNumber + ] = data?.blockHash; + } + transaction.blockHash = + CACHE_BLOCK_NUMBER_TO_HASH[this._mainCurrencyCode][ + transaction.blockNumber + ] || transaction.blockNumber; + // noinspection PointlessArithmeticExpressionJS + transaction.confirmations = + CACHE_GET_MAX_BLOCK[this._mainCurrencyCode].max_block_number - + transaction.blockNumber + + 1 * CACHE_GET_MAX_BLOCK[this._mainCurrencyCode].confirmations; + } else { + CACHE_BLOCK_NUMBER_TO_HASH[transaction.blockNumber] = + transaction.blockHash; + } + + const confirmations = transaction.confirmations; + if ( + confirmations > 0 && + transaction.blockNumber > + CACHE_GET_MAX_BLOCK[this._mainCurrencyCode].max_block_number + ) { + CACHE_GET_MAX_BLOCK[this._mainCurrencyCode].max_block_number = + transaction.blockNumber; + CACHE_GET_MAX_BLOCK[this._mainCurrencyCode].confirmations = confirmations; + } + let transactionStatus = 'new'; + if ( + typeof transaction.txreceipt_status === 'undefined' || + transaction.txreceipt_status === '1' + ) { + if (confirmations > this._blocksToConfirm) { + transactionStatus = 'success'; + } else if (confirmations > 0) { + transactionStatus = 'confirming'; + } + } else if (transaction.isError !== '0') { + transactionStatus = 'fail'; + } + // if (isInternal) { + // transactionStatus = 'internal_' + transactionStatus + // } + let contractAddress = false; + if (typeof transaction.contractAddress !== 'undefined') { + contractAddress = transaction.contractAddress.toLowerCase(); + } + const tx = { + transactionHash: transaction.hash.toLowerCase(), + blockHash: transaction.blockHash, + blockNumber: +transaction.blockNumber, + blockTime: formattedTime, + blockConfirmations: confirmations, + transactionDirection: + address.toLowerCase() === transaction.from.toLowerCase() + ? 'outcome' + : 'income', + addressFrom: + address.toLowerCase() === transaction.from.toLowerCase() + ? '' + : transaction.from, + addressFromBasic: transaction.from.toLowerCase(), + addressTo: + address.toLowerCase() === transaction.to.toLowerCase() + ? '' + : transaction.to, + addressAmount, + transactionStatus: transactionStatus, + contractAddress, + inputValue: transaction.input + }; + let nonce = transaction.nonce; + if (nonce * 1 === 0) { + nonce = 0; + } + if (!isInternal) { + const additional = { + nonce, + gas: transaction.gas, + gasPrice: transaction.gasPrice, + cumulativeGasUsed: transaction.cumulativeGasUsed, + gasUsed: transaction.gasUsed, + transactionIndex: transaction.transactionIndex + }; + tx.transactionJson = additional; + tx.transactionFee = BlocksoftUtils.mul( + transaction.gasUsed, + transaction.gasPrice + ).toString(); + } + if (tx.addressFrom === '' && tx.addressTo === '') { + tx.transactionDirection = 'self'; + tx.addressAmount = 0; } + return tx; + } } diff --git a/crypto/blockchains/eth/EthScannerProcessorErc20.js b/crypto/blockchains/eth/EthScannerProcessorErc20.js index a56f6f8ba..fa4e61e89 100644 --- a/crypto/blockchains/eth/EthScannerProcessorErc20.js +++ b/crypto/blockchains/eth/EthScannerProcessorErc20.js @@ -1,79 +1,121 @@ /** * @version 0.5 */ -import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog' -import EthScannerProcessor from './EthScannerProcessor' -import config from '@app/config/config' +import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog'; +import EthScannerProcessor from './EthScannerProcessor'; +import config from '@app/config/config'; -const abi = require('./ext/erc20') +const abi = require('./ext/erc20'); export default class EthScannerProcessorErc20 extends EthScannerProcessor { + /** + * @type {boolean} + * @private + */ + _useInternal = false; - /** - * @type {boolean} - * @private - */ - _useInternal = false + constructor(settings) { + super(settings); + // noinspection JSUnresolvedVariable + this._token = new this._web3.eth.Contract(abi.ERC20, settings.tokenAddress); + this._tokenAddress = settings.tokenAddress.toLowerCase(); + this._delegateAddress = (settings.delegateAddress || '').toLowerCase(); - constructor(settings) { - super(settings) - // noinspection JSUnresolvedVariable - this._token = new this._web3.eth.Contract(abi.ERC20, settings.tokenAddress) - this._tokenAddress = settings.tokenAddress.toLowerCase() - this._delegateAddress = (settings.delegateAddress || '').toLowerCase() - - if (this._etherscanApiPath && typeof this._etherscanApiPath !== 'undefined') { - const tmp = this._etherscanApiPath.split('/') - this._etherscanApiPath = `https://${tmp[2]}/api?module=account&action=tokentx&sort=desc&contractaddress=${settings.tokenAddress}&apikey=YourApiKeyToken` - } + if ( + this._etherscanApiPath && + typeof this._etherscanApiPath !== 'undefined' + ) { + const tmp = this._etherscanApiPath.split('/'); + this._etherscanApiPath = `https://${tmp[2]}/api?module=account&action=tokentx&sort=desc&contractaddress=${settings.tokenAddress}&apikey=YourApiKeyToken`; } + } - /** - * @param {string} address - * @return {Promise<{balance, unconfirmed, provider}>} - */ - async getBalanceBlockchain(address) { - BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthScannerProcessorErc20.getBalance started ' + address) - if (this.checkWeb3CurrentServerUpdated()) { - this._token = new this._web3.eth.Contract(abi.ERC20, this._settings.tokenAddress) - } - - // noinspection JSUnresolvedVariable - try { - let balance = 0 - let provider = '' - let time = 0 - - if (this._trezorServerCode) { - const res = await this._get(address) - if (!res || typeof res.data === 'undefined') return false - BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthScannerProcessorErc20.getBalance loaded from ' + res.provider + ' ' + res.time) - const data = res.data + /** + * @param {string} address + * @return {Promise<{balance, unconfirmed, provider}>} + */ + async getBalanceBlockchain(address) { + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' EthScannerProcessorErc20.getBalance started ' + + address + ); + if (this.checkWeb3CurrentServerUpdated()) { + this._token = new this._web3.eth.Contract( + abi.ERC20, + this._settings.tokenAddress + ); + } - if (data && this._tokenAddress && typeof data.formattedTokens[this._tokenAddress] !== 'undefined' && typeof typeof data.formattedTokens[this._tokenAddress].balance !== 'undefined') { - balance = data.formattedTokens[this._tokenAddress].balance - if (balance === []) return false - provider = res.provider - time = res.time - return { balance, unconfirmed: 0, provider, time, balanceScanBlock: res.data.nonce } - } - } - balance = await this._token.methods.balanceOf(address).call() - BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthScannerProcessorErc20.getBalance ' + address + ' result ' + JSON.stringify(balance)) - if (balance === []) return false + // noinspection JSUnresolvedVariable + try { + let balance = 0; + let provider = ''; + let time = 0; - provider = 'web3' - time = 'now()' - return { balance, unconfirmed: 0, provider, time } + if (this._trezorServerCode) { + const res = await this._get(address); + if (!res || typeof res.data === 'undefined') return false; + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' EthScannerProcessorErc20.getBalance loaded from ' + + res.provider + + ' ' + + res.time + ); + const data = res.data; - } catch (e) { - if (config.debug.appErrors) { - console.log( this._settings.currencyCode + ' EthScannerProcessorErc20.getBalance ' + address + ' error ' + e.message) - } - BlocksoftCryptoLog.log( this._settings.currencyCode + ' EthScannerProcessorErc20.getBalance ' + address + ' error ' + e.message) - return false + if ( + data && + this._tokenAddress && + typeof data.formattedTokens[this._tokenAddress] !== 'undefined' && + typeof typeof data.formattedTokens[this._tokenAddress].balance !== + 'undefined' + ) { + balance = data.formattedTokens[this._tokenAddress].balance; + if (balance === []) return false; + provider = res.provider; + time = res.time; + return { + balance, + unconfirmed: 0, + provider, + time, + balanceScanBlock: res.data.nonce + }; } + } + balance = await this._token.methods.balanceOf(address).call(); + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' EthScannerProcessorErc20.getBalance ' + + address + + ' result ' + + JSON.stringify(balance) + ); + if (balance === []) return false; - + provider = 'web3'; + time = 'now()'; + return { balance, unconfirmed: 0, provider, time }; + } catch (e) { + if (config.debug.appErrors) { + console.log( + this._settings.currencyCode + + ' EthScannerProcessorErc20.getBalance ' + + address + + ' error ' + + e.message + ); + } + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' EthScannerProcessorErc20.getBalance ' + + address + + ' error ' + + e.message + ); + return false; } + } } diff --git a/crypto/blockchains/eth/EthTransferProcessor.ts b/crypto/blockchains/eth/EthTransferProcessor.ts index 48a9ef80c..7c8b6a6c5 100644 --- a/crypto/blockchains/eth/EthTransferProcessor.ts +++ b/crypto/blockchains/eth/EthTransferProcessor.ts @@ -2,961 +2,1556 @@ * @author Ksu * @version 0.20 */ -import BlocksoftUtils from '../../common/BlocksoftUtils' -import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog' - -import EthTmpDS from './stores/EthTmpDS' - -import EthEstimateGas from './ext/EthEstimateGas' -import EthBasic from './basic/EthBasic' -import EthNetworkPrices from './basic/EthNetworkPrices' -import EthTxSendProvider from './basic/EthTxSendProvider' - -import MarketingEvent from '../../../app/services/Marketing/MarketingEvent' -import BlocksoftDispatcher from '../BlocksoftDispatcher' -import { BlocksoftBlockchainTypes } from '../BlocksoftBlockchainTypes' - -import config from '../../../app/config/config' -import settingsActions from '../../../app/appstores/Stores/Settings/SettingsActions' -import BlocksoftExternalSettings from '../../common/BlocksoftExternalSettings' -import { sublocale } from '../../../app/services/i18n' -import abi721 from './ext/erc721.js' -import abi1155 from './ext/erc1155' -import BlocksoftAxios from '@crypto/common/BlocksoftAxios' -import OneUtils from '@crypto/blockchains/one/ext/OneUtils' - -export default class EthTransferProcessor extends EthBasic implements BlocksoftBlockchainTypes.TransferProcessor { - - - _useThisBalance: boolean = true - - needPrivateForFee(): boolean { - return false +import BlocksoftUtils from '../../common/BlocksoftUtils'; +import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog'; + +import EthTmpDS from './stores/EthTmpDS'; + +import EthEstimateGas from './ext/EthEstimateGas'; +import EthBasic from './basic/EthBasic'; +import EthNetworkPrices from './basic/EthNetworkPrices'; +import EthTxSendProvider from './basic/EthTxSendProvider'; + +import MarketingEvent from '../../../app/services/Marketing/MarketingEvent'; +import BlocksoftDispatcher from '../BlocksoftDispatcher'; +import { BlocksoftBlockchainTypes } from '../BlocksoftBlockchainTypes'; + +import config from '../../../app/config/config'; +import settingsActions from '../../../app/appstores/Stores/Settings/SettingsActions'; +import BlocksoftExternalSettings from '../../common/BlocksoftExternalSettings'; +import { sublocale } from '../../../app/services/i18n'; +import abi721 from './ext/erc721.js'; +import abi1155 from './ext/erc1155'; +import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; +import OneUtils from '@crypto/blockchains/one/ext/OneUtils'; + +export default class EthTransferProcessor + extends EthBasic + implements BlocksoftBlockchainTypes.TransferProcessor +{ + _useThisBalance: boolean = true; + + needPrivateForFee(): boolean { + return false; + } + + checkSendAllModal(data: { currencyCode: any }): boolean { + return true; + } + + async getFeeRate( + data: BlocksoftBlockchainTypes.TransferData, + privateData?: BlocksoftBlockchainTypes.TransferPrivateData, + additionalData: BlocksoftBlockchainTypes.TransferAdditionalData = {} + ): Promise { + let txRBFed = ''; + let txRBF = false; + + this.checkWeb3CurrentServerUpdated(); + + let realAddressTo = data.addressTo; + if (realAddressTo !== '' && OneUtils.isOneAddress(realAddressTo)) { + realAddressTo = OneUtils.fromOneAddress(realAddressTo); } - - checkSendAllModal(data: { currencyCode: any }): boolean { - return true + if ( + typeof data.transactionRemoveByFee !== 'undefined' && + data.transactionRemoveByFee + ) { + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' EthTransferProcessor.getFeeRate remove started ' + + data.transactionRemoveByFee + ); + txRBF = data.transactionRemoveByFee; + txRBFed = 'RBFremoved'; + } else if ( + typeof data.transactionReplaceByFee !== 'undefined' && + data.transactionReplaceByFee + ) { + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' EthTransferProcessor.getFeeRate resend started ' + + data.transactionReplaceByFee + ); + txRBF = data.transactionReplaceByFee; + txRBFed = 'RBFed'; + } else if (typeof data.dexOrderData !== 'undefined' && data.dexOrderData) { + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' EthTransferProcessor.getFeeRate dex ' + + data.addressFrom + + ' started' + ); + } else { + const realAddressToLower = realAddressTo.toLowerCase(); + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' EthTransferProcessor.getFeeRate ' + + data.addressFrom + + ' started' + ); + txRBFed = 'usualSend'; + if ( + realAddressTo !== '' && + (realAddressToLower.indexOf('0x') === -1 || + realAddressToLower.indexOf('0x') !== 0) + ) { + throw new Error('SERVER_RESPONSE_BAD_DESTINATION'); + } } - async getFeeRate(data: BlocksoftBlockchainTypes.TransferData, privateData?: BlocksoftBlockchainTypes.TransferPrivateData, additionalData: BlocksoftBlockchainTypes.TransferAdditionalData = {}): Promise { - let txRBFed = '' - let txRBF = false - - this.checkWeb3CurrentServerUpdated() - - let realAddressTo = data.addressTo - if (realAddressTo !== '' && OneUtils.isOneAddress(realAddressTo)) { - realAddressTo = OneUtils.fromOneAddress(realAddressTo) - } - if (typeof data.transactionRemoveByFee !== 'undefined' && data.transactionRemoveByFee) { - BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTransferProcessor.getFeeRate remove started ' + data.transactionRemoveByFee) - txRBF = data.transactionRemoveByFee - txRBFed = 'RBFremoved' - } else if (typeof data.transactionReplaceByFee !== 'undefined' && data.transactionReplaceByFee) { - BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTransferProcessor.getFeeRate resend started ' + data.transactionReplaceByFee) - txRBF = data.transactionReplaceByFee - txRBFed = 'RBFed' - } else if (typeof data.dexOrderData !== 'undefined' && data.dexOrderData) { - BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTransferProcessor.getFeeRate dex ' + data.addressFrom + ' started') - } else { - const realAddressToLower = realAddressTo.toLowerCase() - BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTransferProcessor.getFeeRate ' + data.addressFrom + ' started') - txRBFed = 'usualSend' - if (realAddressTo !== '' && (realAddressToLower.indexOf('0x') === -1 || realAddressToLower.indexOf('0x') !== 0)) { - throw new Error('SERVER_RESPONSE_BAD_DESTINATION') - } + let oldGasPrice = -1; + let oldNonce = -1; + let nonceLog = ''; + if (txRBF) { + oldGasPrice = + typeof data.transactionJson !== 'undefined' && + typeof data.transactionJson.gasPrice !== 'undefined' + ? data.transactionJson.gasPrice + : false; + oldNonce = + typeof data.transactionJson !== 'undefined' && + typeof data.transactionJson.nonce !== 'undefined' + ? data.transactionJson.nonce + : false; + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' EthTransferProcessor.getFeeRate ' + + data.addressFrom + + ' rbf preset nonceForTx ' + + oldNonce + ); + if (oldGasPrice === false) { + try { + const ethProvider = BlocksoftDispatcher.getScannerProcessor( + data.currencyCode + ); + const scannedTx = await ethProvider.getTransactionBlockchain(txRBF); + if (scannedTx) { + oldGasPrice = scannedTx.gasPrice; + oldNonce = scannedTx.nonce; + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' EthTransferProcessor.getFeeRate ' + + data.addressFrom + + ' rbf reloaded nonceForTx ' + + oldNonce + ); + } + if (!oldGasPrice) { + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' EthTransferProcessor.getFeeRate ' + + txRBFed + + ' no gasPrice for ' + + txRBF + ); + } + } catch (e) { + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' EthTransferProcessor.getFeeRate ' + + txRBFed + + 'not loaded gasPrice for ' + + txRBF + + ' ' + + e.message + ); } + } + nonceLog += ' txRBFNonce ' + oldNonce; + } else if ( + typeof additionalData.nonceForTx !== 'undefined' && + additionalData.nonceForTx !== -1 + ) { + oldNonce = additionalData.nonceForTx; + nonceLog += ' customFeeNonce ' + oldNonce; + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' EthTransferProcessor.getFeeRate ' + + data.addressFrom + + ' custom nonceForTx ' + + additionalData.nonceForTx + ); + } else { + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' EthTransferProcessor.getFeeRate ' + + data.addressFrom + + ' no nonceForTx' + ); + } - let oldGasPrice = -1 - let oldNonce = -1 - let nonceLog = '' - if (txRBF) { - oldGasPrice = typeof data.transactionJson !== 'undefined' && typeof data.transactionJson.gasPrice !== 'undefined' ? data.transactionJson.gasPrice : false - oldNonce = typeof data.transactionJson !== 'undefined' && typeof data.transactionJson.nonce !== 'undefined' ? data.transactionJson.nonce : false - BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTransferProcessor.getFeeRate ' + data.addressFrom + ' rbf preset nonceForTx ' + oldNonce) - if (oldGasPrice === false) { - try { - const ethProvider = BlocksoftDispatcher.getScannerProcessor(data.currencyCode) - const scannedTx = await ethProvider.getTransactionBlockchain(txRBF) - if (scannedTx) { - oldGasPrice = scannedTx.gasPrice - oldNonce = scannedTx.nonce - BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTransferProcessor.getFeeRate ' + data.addressFrom + ' rbf reloaded nonceForTx ' + oldNonce) - } - if (!oldGasPrice) { - BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTransferProcessor.getFeeRate ' + txRBFed + ' no gasPrice for ' + txRBF) - } - } catch (e) { - BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTransferProcessor.getFeeRate ' + txRBFed + 'not loaded gasPrice for ' + txRBF + ' ' + e.message) - } - } - nonceLog += ' txRBFNonce ' + oldNonce - } else if (typeof additionalData.nonceForTx !== 'undefined' && additionalData.nonceForTx !== -1) { - oldNonce = additionalData.nonceForTx - nonceLog += ' customFeeNonce ' + oldNonce - BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTransferProcessor.getFeeRate ' + data.addressFrom + ' custom nonceForTx ' + additionalData.nonceForTx) - } else { - BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTransferProcessor.getFeeRate ' + data.addressFrom + ' no nonceForTx') + let gasPrice = {}; + + let maxNonceLocal = await EthTmpDS.getMaxNonce( + this._mainCurrencyCode, + data.addressFrom + ); + const ethAllowBlockedBalance = await settingsActions.getSetting( + 'ethAllowBlockedBalance' + ); + const ethAllowLongQuery = await settingsActions.getSetting( + 'ethAllowLongQuery' + ); + + const proxyPriceCheck = await EthNetworkPrices.getWithProxy( + this._mainCurrencyCode, + this._isTestnet, + typeof data.addressFrom !== 'undefined' ? data.addressFrom : 'none', + { + data, + additionalData, + feesSource: 'EthTransferProcessor', + feesOldNonce: oldNonce, + ethAllowBlockedBalance, + ethAllowLongQuery, + maxNonceLocal + } + ); + + if ( + typeof additionalData.gasPrice !== 'undefined' && + additionalData.gasPrice + ) { + if (typeof additionalData.gasPriceTitle !== 'undefined') { + // @ts-ignore + gasPrice[additionalData.gasPriceTitle] = additionalData.gasPrice; + } else { + gasPrice = { speed_blocks_12: additionalData.gasPrice }; + } + } else if ( + typeof additionalData.prices !== 'undefined' && + additionalData.prices + ) { + gasPrice = additionalData.prices; + } else if (proxyPriceCheck) { + let tmp = 0; + if (typeof data.dexOrderData !== 'undefined' && data.dexOrderData) { + tmp = + typeof data.dexOrderData[0].params.gasPrice !== 'undefined' + ? data.dexOrderData[0].params.gasPrice + : 0; + if (tmp > 0) { + gasPrice = { speed_blocks_2: tmp }; } + } else if ( + typeof data.walletConnectData !== 'undefined' && + typeof data.walletConnectData.gasPrice !== 'undefined' && + data.walletConnectData.gasPrice + ) { + tmp = BlocksoftUtils.hexToDecimalWalletConnect( + data.walletConnectData.gasPrice + ); + if (tmp > 0) { + gasPrice = { speed_blocks_2: tmp }; + } + } + if (!tmp) { + gasPrice = + typeof proxyPriceCheck.gasPrice !== 'undefined' && + proxyPriceCheck.gasPrice + ? proxyPriceCheck.gasPrice + : { speed_blocks_12: '10' }; + } + if ( + typeof proxyPriceCheck.maxNonceLocal !== 'undefined' && + proxyPriceCheck.maxNonceLocal + ) { + maxNonceLocal = proxyPriceCheck.maxNonceLocal; + } + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' EthTransferProcessor.getFeeRate ' + + data.addressFrom + + ' proxyPriceCheck', + proxyPriceCheck + ); + } - let gasPrice = {} - - let maxNonceLocal = await EthTmpDS.getMaxNonce(this._mainCurrencyCode, data.addressFrom) - const ethAllowBlockedBalance = await settingsActions.getSetting('ethAllowBlockedBalance') - const ethAllowLongQuery = await settingsActions.getSetting('ethAllowLongQuery') - - const proxyPriceCheck = await EthNetworkPrices.getWithProxy(this._mainCurrencyCode, this._isTestnet, typeof data.addressFrom !== 'undefined' ? data.addressFrom : 'none', { - data, - additionalData, - feesSource: 'EthTransferProcessor', - feesOldNonce: oldNonce, - ethAllowBlockedBalance, - ethAllowLongQuery, - maxNonceLocal - }) + if (typeof this._web3.LINK === 'undefined') { + throw new Error('EthTransferProcessor need this._web3.LINK'); + } - if (typeof additionalData.gasPrice !== 'undefined' && additionalData.gasPrice) { - if (typeof additionalData.gasPriceTitle !== 'undefined') { - // @ts-ignore - gasPrice[additionalData.gasPriceTitle] = additionalData.gasPrice - } else { - gasPrice = { 'speed_blocks_12': additionalData.gasPrice } + let gasLimit = 0; + try { + if ( + typeof additionalData === 'undefined' || + typeof additionalData.gasLimit === 'undefined' || + !additionalData.gasLimit + ) { + if (typeof data.dexOrderData !== 'undefined' && data.dexOrderData) { + gasLimit = + typeof data.dexOrderData[0].params.gas !== 'undefined' + ? data.dexOrderData[0].params.gas + : 0; + } else if ( + typeof data.walletConnectData !== 'undefined' && + typeof data.walletConnectData.gas !== 'undefined' && + data.walletConnectData.gas && + data.walletConnectData.gas !== '0x0' + ) { + gasLimit = BlocksoftUtils.hexToDecimalWalletConnect( + data.walletConnectData.gas + ); + } else if (typeof data.walletConnectData !== 'undefined') { + const params = { + jsonrpc: '2.0', + method: 'eth_estimateGas', + params: [ + { + from: + typeof data.walletConnectData.from && + data.walletConnectData.from + ? data.walletConnectData.from + : data.addressFrom, + to: + typeof data.walletConnectData.to && data.walletConnectData.to + ? data.walletConnectData.to + : realAddressTo, + value: + typeof data.walletConnectData.value !== 'undefined' && + data.walletConnectData.value + ? data.walletConnectData.value + : data.amount.indexOf('0x') === 0 + ? data.amount + : '0x' + BlocksoftUtils.decimalToHex(data.amount), + data: + typeof data.walletConnectData.data !== 'undefined' && + data.walletConnectData.data + ? data.walletConnectData.data + : '0x' + } + ], + id: 1 + }; + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' EthTransferProcessor.getFeeRate estimatedGas for WalletConnect start' + ); + const tmp = await BlocksoftAxios.postWithoutBraking( + this._web3.LINK, + params + ); + if (typeof tmp !== 'undefined' && typeof tmp.data !== 'undefined') { + if (typeof tmp.data.result !== 'undefined') { + gasLimit = + BlocksoftUtils.hexToDecimalWalletConnect(tmp.data.result) * 1 + + 150000; + } else if (typeof tmp.data.error !== 'undefined') { + throw new Error(tmp.data.error.message); } - } else if (typeof additionalData.prices !== 'undefined' && additionalData.prices) { - gasPrice = additionalData.prices - } else if (proxyPriceCheck) { - let tmp = 0 - if (typeof data.dexOrderData !== 'undefined' && data.dexOrderData) { - tmp = typeof data.dexOrderData[0].params.gasPrice !== 'undefined' ? data.dexOrderData[0].params.gasPrice : 0 - if (tmp > 0) { - gasPrice = { 'speed_blocks_2': tmp } - } - } else if (typeof data.walletConnectData !== 'undefined' && typeof data.walletConnectData.gasPrice !== 'undefined' && data.walletConnectData.gasPrice) { - tmp = BlocksoftUtils.hexToDecimalWalletConnect(data.walletConnectData.gasPrice) - if (tmp > 0) { - gasPrice = { 'speed_blocks_2': tmp } - } + } else { + gasLimit = 500000; + } + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' EthTransferProcessor.getFeeRate estimatedGas for WalletConnect result ' + + gasLimit + ); + } else if ( + typeof data.contractCallData !== 'undefined' && + typeof data.contractCallData.contractAddress !== 'undefined' + ) { + const schema = data.contractCallData.contractSchema; + let abiCode; + if (schema === 'ERC721') { + abiCode = abi721.ERC721; + } else if (schema === 'ERC1155') { + abiCode = abi1155.ERC1155; + } else { + throw new Error('Contract abi not found ' + schema); + } + const token = new this._web3.eth.Contract( + abiCode, + data.contractCallData.contractAddress + ); + + gasLimit = 150000; + try { + const tmpParams = data.contractCallData.contractActionParams; + for (let i = 0, ic = tmpParams.length; i < ic; i++) { + if (tmpParams[i] === 'addressTo') { + tmpParams[i] = realAddressTo; + } } - if (!tmp) { - gasPrice = typeof proxyPriceCheck.gasPrice !== 'undefined' && proxyPriceCheck.gasPrice ? proxyPriceCheck.gasPrice : { 'speed_blocks_12': '10' } + gasLimit = await token.methods[ + data.contractCallData.contractAction + ](...tmpParams).estimateGas({ from: data.addressFrom }); + if (gasLimit) { + gasLimit = BlocksoftUtils.mul(gasLimit, 1.5); } - if (typeof proxyPriceCheck.maxNonceLocal !== 'undefined' && proxyPriceCheck.maxNonceLocal) { - maxNonceLocal = proxyPriceCheck.maxNonceLocal + } catch (e) { + if (config.debug.cryptoErrors) { + BlocksoftCryptoLog.log( + 'EthTransferProcessor data.contractCallData error ' + e.message + ); } - BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTransferProcessor.getFeeRate ' + data.addressFrom + ' proxyPriceCheck', proxyPriceCheck) - } - - if (typeof this._web3.LINK === 'undefined') { - throw new Error('EthTransferProcessor need this._web3.LINK') - } - - let gasLimit = 0 - try { - - if (typeof additionalData === 'undefined' || typeof additionalData.gasLimit === 'undefined' || !additionalData.gasLimit) { - if (typeof data.dexOrderData !== 'undefined' && data.dexOrderData) { - gasLimit = typeof data.dexOrderData[0].params.gas !== 'undefined' ? data.dexOrderData[0].params.gas : 0 - } else if (typeof data.walletConnectData !== 'undefined' && typeof data.walletConnectData.gas !== 'undefined' && data.walletConnectData.gas && data.walletConnectData.gas !== '0x0') { - gasLimit = BlocksoftUtils.hexToDecimalWalletConnect(data.walletConnectData.gas) - } else if (typeof data.walletConnectData !== 'undefined') { - const params = { - 'jsonrpc': '2.0', - 'method': 'eth_estimateGas', - 'params': [ - { - 'from': typeof data.walletConnectData.from && data.walletConnectData.from ? data.walletConnectData.from : data.addressFrom, - 'to': typeof data.walletConnectData.to && data.walletConnectData.to ? data.walletConnectData.to : realAddressTo, - 'value': typeof data.walletConnectData.value !== 'undefined' && data.walletConnectData.value - ? data.walletConnectData.value - : data.amount.indexOf('0x') === 0 ? data.amount : ('0x' + BlocksoftUtils.decimalToHex(data.amount)), - 'data': typeof data.walletConnectData.data !== 'undefined' && data.walletConnectData.data ? data.walletConnectData.data : '0x' - } - ], - 'id': 1 - } - BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTransferProcessor.getFeeRate estimatedGas for WalletConnect start') - const tmp = await BlocksoftAxios.postWithoutBraking(this._web3.LINK, params) - if (typeof tmp !== 'undefined' && typeof tmp.data !== 'undefined') { - if (typeof tmp.data.result !== 'undefined') { - gasLimit = BlocksoftUtils.hexToDecimalWalletConnect(tmp.data.result) * 1 + 150000 - } else if (typeof tmp.data.error !== 'undefined') { - throw new Error(tmp.data.error.message) - } - } else { - gasLimit = 500000 - } - BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTransferProcessor.getFeeRate estimatedGas for WalletConnect result ' + gasLimit) - } else if (typeof data.contractCallData !== 'undefined' && typeof data.contractCallData.contractAddress !== 'undefined') { - const schema = data.contractCallData.contractSchema - let abiCode - if (schema === 'ERC721') { - abiCode = abi721.ERC721 - } else if (schema === 'ERC1155') { - abiCode = abi1155.ERC1155 - } else { - throw new Error('Contract abi not found ' + schema) - } - const token = new this._web3.eth.Contract(abiCode, data.contractCallData.contractAddress) - - gasLimit = 150000 - try { - const tmpParams = data.contractCallData.contractActionParams - for (let i = 0, ic = tmpParams.length; i 3) { - throw e1 - } - } - } while (!ok && i <= 5) - if (gasLimitNew && typeof gasLimitNew !== 'undefined' && gasLimitNew * 1 > 0) { - gasLimit = gasLimitNew * 1 - } - } catch (e) { - if (e.message.indexOf('resolve host') !== -1) { - throw new Error('SERVER_RESPONSE_NOT_CONNECTED') - } else { - gasLimit = BlocksoftExternalSettings.getStatic('ETH_MIN_GAS_LIMIT') - // e.message += ' in EthEstimateGas in getFeeRate' - // throw e - } - } - if (!gasLimit || typeof gasLimit !== 'undefined') { - gasLimit = BlocksoftExternalSettings.getStatic('ETH_MIN_GAS_LIMIT') - } - - const minGasLimit = BlocksoftExternalSettings.getStatic(this._mainCurrencyCode + '_MIN_GAS_LIMIT') * 1 - if (minGasLimit > 0 && gasLimit < minGasLimit) { - gasLimit = minGasLimit - } - + BlocksoftCryptoLog.log( + 'EthTransferProcessor data.contractCallData error ' + e.message + ); + // do nothing + } + + if (gasLimit <= 150000) { + gasLimit = 150000; + } + } else { + try { + let ok = false; + let i = 0; + let gasLimitNew = false; + do { + try { + i++; + gasLimitNew = await EthEstimateGas( + this._web3.LINK, + gasPrice.speed_blocks_2 || gasPrice.speed_blocks_12, + data.addressFrom, + realAddressTo, + data.amount + ); // it doesn't matter what the price of gas is, just a required parameter + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' EthTransferProcessor.getFeeRate estimatedGas ' + + gasLimit + ); + } catch (e1) { + ok = false; + if (i > 3) { + throw e1; } + } + } while (!ok && i <= 5); + if ( + gasLimitNew && + typeof gasLimitNew !== 'undefined' && + gasLimitNew * 1 > 0 + ) { + gasLimit = gasLimitNew * 1; + } + } catch (e) { + if (e.message.indexOf('resolve host') !== -1) { + throw new Error('SERVER_RESPONSE_NOT_CONNECTED'); } else { - gasLimit = additionalData.gasLimit - BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTransferProcessor.getFeeRate preestimatedGas ' + gasLimit) + gasLimit = + BlocksoftExternalSettings.getStatic('ETH_MIN_GAS_LIMIT'); + // e.message += ' in EthEstimateGas in getFeeRate' + // throw e } - } catch (e) { - throw new Error(e.message + ' in get gasLimit') + } + if (!gasLimit || typeof gasLimit !== 'undefined') { + gasLimit = BlocksoftExternalSettings.getStatic('ETH_MIN_GAS_LIMIT'); + } + + const minGasLimit = + BlocksoftExternalSettings.getStatic( + this._mainCurrencyCode + '_MIN_GAS_LIMIT' + ) * 1; + if (minGasLimit > 0 && gasLimit < minGasLimit) { + gasLimit = minGasLimit; + } } + } else { + gasLimit = additionalData.gasLimit; + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' EthTransferProcessor.getFeeRate preestimatedGas ' + + gasLimit + ); + } + } catch (e) { + throw new Error(e.message + ' in get gasLimit'); + } - let showBigGasNotice = false - if (typeof additionalData === 'undefined' || typeof additionalData.isCustomFee === 'undefined' || !additionalData.isCustomFee) { - try { - const limit = BlocksoftExternalSettings.getStatic(this._mainCurrencyCode + '_GAS_LIMIT') - if (gasLimit * 1 > limit * 1) { - showBigGasNotice = true - } - } catch (e) { - throw new Error(e.message + ' in get showBigGasNotice') - } + let showBigGasNotice = false; + if ( + typeof additionalData === 'undefined' || + typeof additionalData.isCustomFee === 'undefined' || + !additionalData.isCustomFee + ) { + try { + const limit = BlocksoftExternalSettings.getStatic( + this._mainCurrencyCode + '_GAS_LIMIT' + ); + if (gasLimit * 1 > limit * 1) { + showBigGasNotice = true; } + } catch (e) { + throw new Error(e.message + ' in get showBigGasNotice'); + } + } - if (!gasLimit) { - throw new Error('invalid transaction (no gas limit.2)') - } + if (!gasLimit) { + throw new Error('invalid transaction (no gas limit.2)'); + } - // @ts-ignore - BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTransferProcessor.getFeeRate prefinished', { - gasPrice, - gasLimit - }) - - const result: BlocksoftBlockchainTypes.FeeRateResult = {} as BlocksoftBlockchainTypes.FeeRateResult - result.fees = [] - - let balance = '0' - let actualCheckBalance - let nonceForTx = -1 - let isNewNonce = true - if (typeof data.transactionJson !== 'undefined' && typeof data.transactionJson.nonce !== 'undefined' && data.transactionJson.nonce) { - nonceForTx = data.transactionJson.nonce - nonceLog += ' from transactionJSON' + // @ts-ignore + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' EthTransferProcessor.getFeeRate prefinished', + { + gasPrice, + gasLimit + } + ); + + const result: BlocksoftBlockchainTypes.FeeRateResult = + {} as BlocksoftBlockchainTypes.FeeRateResult; + result.fees = []; + + let balance = '0'; + let actualCheckBalance; + let nonceForTx = -1; + let isNewNonce = true; + if ( + typeof data.transactionJson !== 'undefined' && + typeof data.transactionJson.nonce !== 'undefined' && + data.transactionJson.nonce + ) { + nonceForTx = data.transactionJson.nonce; + nonceLog += ' from transactionJSON'; + } else { + let nonceForTxBasic = + maxNonceLocal.maxValue * 1 > maxNonceLocal.maxScanned * 1 + ? maxNonceLocal.maxValue + : maxNonceLocal.maxScanned; + if (proxyPriceCheck && typeof proxyPriceCheck.newNonce !== 'undefined') { + nonceForTxBasic = proxyPriceCheck.newNonce; + if (typeof proxyPriceCheck.logNonce !== 'undefined') { + nonceLog += ' from serverCheck ' + proxyPriceCheck.logNonce; } else { - let nonceForTxBasic = maxNonceLocal.maxValue * 1 > maxNonceLocal.maxScanned * 1 ? maxNonceLocal.maxValue : maxNonceLocal.maxScanned - if (proxyPriceCheck && typeof proxyPriceCheck.newNonce !== 'undefined') { - nonceForTxBasic = proxyPriceCheck.newNonce - if (typeof proxyPriceCheck.logNonce !== 'undefined') { - nonceLog += ' from serverCheck ' + proxyPriceCheck.logNonce - } else { - nonceLog += ' from serverCheckNoLog ' - } - if (MarketingEvent.DATA.LOG_TESTER && typeof proxyPriceCheck.newNonceDEBUG !== 'undefined') { - nonceForTxBasic = proxyPriceCheck.newNonceDEBUG - nonceLog += ' used newNonceDEBUG' - } else { - nonceLog += ' used newNonce ' - } - - if (nonceForTxBasic === 'maxValue+1') { - if (maxNonceLocal.maxValue * 1 > -1) { - nonceForTxBasic = maxNonceLocal.maxValue + 1 - nonceLog += ' usedMax ' + JSON.stringify(maxNonceLocal) - } else { - nonceForTxBasic = -1 - nonceLog += ' noMax => -1 ' - } - } - nonceLog += ' nonceForTxBasic ' + nonceForTxBasic - - } else if (nonceForTxBasic * 1 >= 0) { - nonceLog += ' used localNonce ' + JSON.stringify(maxNonceLocal) - nonceLog += ' nonceForTxBasic ' + nonceForTxBasic - nonceForTxBasic = nonceForTxBasic * 1 + 1 - } else { - nonceLog += ' used noLocalNoServer ' + JSON.stringify(nonceForTxBasic) - } - - if (oldNonce !== false && oldNonce * 1 > -1 && oldNonce !== nonceForTxBasic) { - nonceForTx = oldNonce - isNewNonce = false - nonceLog = 'recheck oldNonce ' + oldNonce + ' with basic ' + nonceForTxBasic + nonceLog - BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTransferProcessor.getFeeRate ' + data.addressFrom + ' ' + nonceLog) - } else { - nonceForTx = nonceForTxBasic - isNewNonce = true - nonceLog = 'recheck nonce ' + oldNonce + ' replaced by basic ' + nonceForTxBasic + nonceLog - BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTransferProcessor.getFeeRate ' + data.addressFrom + ' ' + nonceLog) - } + nonceLog += ' from serverCheckNoLog '; } - - if (data.isTransferAll && this._useThisBalance) { - balance = data.amount - actualCheckBalance = true + if ( + MarketingEvent.DATA.LOG_TESTER && + typeof proxyPriceCheck.newNonceDEBUG !== 'undefined' + ) { + nonceForTxBasic = proxyPriceCheck.newNonceDEBUG; + nonceLog += ' used newNonceDEBUG'; } else { - try { - // @ts-ignore - balance = additionalData.balance || await this._web3.eth.getBalance(data.addressFrom) - // @ts-ignore - if (!balance || balance * 1 === 0) { - BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTxProcessor.getFeeRate balanceFromWeb3 is empty ' + balance) - actualCheckBalance = false - } else { - actualCheckBalance = true - } - } catch (e) { - actualCheckBalance = false - } + nonceLog += ' used newNonce '; } - const titles = ['eth_speed_slow', 'eth_speed_medium', 'eth_speed_fast'] - const titlesChanged = ['eth_speed_slowest', 'eth_speed_medium_to_slow', 'eth_speed_fast_to_medium'] - const keys = ['speed_blocks_12', 'speed_blocks_6', 'speed_blocks_2'] - let skippedByOld = false - let prevGasPrice = 0 - const feesOK = {} - for (let index = 0; index <= 2; index++) { - const key = keys[index] - if (typeof gasPrice[key] === 'undefined') continue - if (gasPrice[key] <= oldGasPrice) { - skippedByOld = true - continue - } - let fee = BlocksoftUtils.mul(gasPrice[key], gasLimit) - let amount = data.amount - let needSpeed = gasPrice[key].toString() - let newGasPrice = needSpeed - let changedFeeByBalance = false - if (actualCheckBalance) { - const tmp = BlocksoftUtils.diff(balance, fee).toString() - if (this._useThisBalance) { - if (tmp * 1 < 0) { - continue - } - if (data.isTransferAll) { - amount = tmp - } else { - const tmp2 = BlocksoftUtils.diff(tmp, amount).toString() - if (tmp2 * 1 < 0) { - continue - } - } - } else { - if (tmp * 1 < 0) { - const tmpGasPrice = BlocksoftUtils.div(balance, gasLimit).toString() - if (tmpGasPrice && BlocksoftUtils.diff(tmpGasPrice, prevGasPrice).toString() * 1 > 0) { - fee = balance - newGasPrice = tmpGasPrice.split('.')[0] - changedFeeByBalance = true - } else { - continue - } - } - } - } - if (typeof newGasPrice === 'undefined' || newGasPrice === 'undefined') { - newGasPrice = '0' - } else { - newGasPrice = newGasPrice.toString() - } - - let langMsg = titles[index] - if (changedFeeByBalance) { - if (index > 0) { - langMsg = titlesChanged[index - 1] - } - } - let gweiFee = 0 - try { - gweiFee = newGasPrice !== '0' ? BlocksoftUtils.toGwei(newGasPrice).toString() : newGasPrice - } catch (e) { - BlocksoftCryptoLog.err('EthTxProcessor.getFeeRate newGasPrice to gwei error ' + e.message) - } - - const tmp = { - langMsg, - gasPrice: newGasPrice, - gasPriceGwei: gweiFee, - gasLimit: gasLimit.toString(), - feeForTx: fee.toString(), - nonceForTx, - nonceLog, - needSpeed, - ethAllowBlockedBalance, - ethAllowLongQuery, - isTransferAll: data.isTransferAll, - amountForTx: amount - } + if (nonceForTxBasic === 'maxValue+1') { + if (maxNonceLocal.maxValue * 1 > -1) { + nonceForTxBasic = maxNonceLocal.maxValue + 1; + nonceLog += ' usedMax ' + JSON.stringify(maxNonceLocal); + } else { + nonceForTxBasic = -1; + nonceLog += ' noMax => -1 '; + } + } + nonceLog += ' nonceForTxBasic ' + nonceForTxBasic; + } else if (nonceForTxBasic * 1 >= 0) { + nonceLog += ' used localNonce ' + JSON.stringify(maxNonceLocal); + nonceLog += ' nonceForTxBasic ' + nonceForTxBasic; + nonceForTxBasic = nonceForTxBasic * 1 + 1; + } else { + nonceLog += ' used noLocalNoServer ' + JSON.stringify(nonceForTxBasic); + } + + if ( + oldNonce !== false && + oldNonce * 1 > -1 && + oldNonce !== nonceForTxBasic + ) { + nonceForTx = oldNonce; + isNewNonce = false; + nonceLog = + 'recheck oldNonce ' + + oldNonce + + ' with basic ' + + nonceForTxBasic + + nonceLog; + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' EthTransferProcessor.getFeeRate ' + + data.addressFrom + + ' ' + + nonceLog + ); + } else { + nonceForTx = nonceForTxBasic; + isNewNonce = true; + nonceLog = + 'recheck nonce ' + + oldNonce + + ' replaced by basic ' + + nonceForTxBasic + + nonceLog; + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' EthTransferProcessor.getFeeRate ' + + data.addressFrom + + ' ' + + nonceLog + ); + } + } - const diff = needSpeed - newGasPrice - if (!changedFeeByBalance || diff * 1 < 1000) { - feesOK[titles[index]] = tmp.gasPrice - } else { - BlocksoftCryptoLog.log('EthTxProcessor.getFeeRate skipped feesOk ' + titles[index] + ' as diff ' + diff + ' gasPrice ' + tmp.gasPrice + ' / gasLimit ' + tmp.gasLimit) + if (data.isTransferAll && this._useThisBalance) { + balance = data.amount; + actualCheckBalance = true; + } else { + try { + // @ts-ignore + balance = + additionalData.balance || + (await this._web3.eth.getBalance(data.addressFrom)); + // @ts-ignore + if (!balance || balance * 1 === 0) { + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' EthTxProcessor.getFeeRate balanceFromWeb3 is empty ' + + balance + ); + actualCheckBalance = false; + } else { + actualCheckBalance = true; + } + } catch (e) { + actualCheckBalance = false; + } + } + const titles = ['eth_speed_slow', 'eth_speed_medium', 'eth_speed_fast']; + const titlesChanged = [ + 'eth_speed_slowest', + 'eth_speed_medium_to_slow', + 'eth_speed_fast_to_medium' + ]; + const keys = ['speed_blocks_12', 'speed_blocks_6', 'speed_blocks_2']; + let skippedByOld = false; + let prevGasPrice = 0; + const feesOK = {}; + for (let index = 0; index <= 2; index++) { + const key = keys[index]; + if (typeof gasPrice[key] === 'undefined') continue; + if (gasPrice[key] <= oldGasPrice) { + skippedByOld = true; + continue; + } + let fee = BlocksoftUtils.mul(gasPrice[key], gasLimit); + let amount = data.amount; + let needSpeed = gasPrice[key].toString(); + let newGasPrice = needSpeed; + let changedFeeByBalance = false; + if (actualCheckBalance) { + const tmp = BlocksoftUtils.diff(balance, fee).toString(); + if (this._useThisBalance) { + if (tmp * 1 < 0) { + continue; + } + if (data.isTransferAll) { + amount = tmp; + } else { + const tmp2 = BlocksoftUtils.diff(tmp, amount).toString(); + if (tmp2 * 1 < 0) { + continue; } - if (BlocksoftUtils.diff(newGasPrice, prevGasPrice).indexOf('-') === -1 && newGasPrice !== prevGasPrice) { - prevGasPrice = tmp.gasPrice - BlocksoftCryptoLog.log('EthTxProcessor.getFeeRate added feeForTx ' + titles[index] + ' ' + tmp.feeForTx + ' with gasPrice ' + tmp.gasPrice + ' / gasLimit ' + tmp.gasLimit) - result.fees.push(tmp) + } + } else { + if (tmp * 1 < 0) { + const tmpGasPrice = BlocksoftUtils.div( + balance, + gasLimit + ).toString(); + if ( + tmpGasPrice && + BlocksoftUtils.diff(tmpGasPrice, prevGasPrice).toString() * 1 > 0 + ) { + fee = balance; + newGasPrice = tmpGasPrice.split('.')[0]; + changedFeeByBalance = true; } else { - BlocksoftCryptoLog.log('EthTxProcessor.getFeeRate skipped feeForTx ' + titles[index] + ' ' + tmp.feeForTx + ' with gasPrice ' + tmp.gasPrice + ' / gasLimit ' + tmp.gasLimit) + continue; } + } } - - prevGasPrice = 0 - if (txRBF) { - let recheck = result.fees.length < 2 - if (typeof additionalData.isCustomFee !== 'undefined' && additionalData.isCustomFee) { - recheck = result.fees.length === 0 - } - if (recheck) { - for (let index = 0; index <= 2; index++) { - if (typeof result.fees[index] !== 'undefined') { - result.fees[index].langMsg = titles[index] - continue - } - let newGasPrice = Math.round(oldGasPrice * (10 + index + 1) / 10) - const title = titles[index] - const key = keys[index] - const needSpeed = gasPrice[key] - if (newGasPrice < gasPrice[key]) { - newGasPrice = gasPrice[key] - } - let fee = BlocksoftUtils.mul(newGasPrice, gasLimit) - let amount = data.amount - if (actualCheckBalance) { - const tmp = BlocksoftUtils.diff(balance, fee).toString() - if (this._useThisBalance) { - if (tmp * 1 < 0) { - continue - } - if (data.isTransferAll) { - amount = tmp - } else { - const tmp2 = BlocksoftUtils.diff(tmp, amount).toString() - if (tmp2 * 1 < 0) { - amount = tmp - } - } - } else { - if (tmp * 1 < 0) { - const tmpGasPrice = BlocksoftUtils.div(balance, gasLimit).toString() - if (BlocksoftUtils.diff(tmpGasPrice, prevGasPrice).toString() * 1 > 0) { - fee = balance - newGasPrice = tmpGasPrice - } else { - continue - } - } - } - } - - if (typeof newGasPrice === 'undefined') { - newGasPrice = '0' - } else { - newGasPrice = newGasPrice.toString() - newGasPrice = BlocksoftUtils.round(newGasPrice) - } - - let gweiFee = 0 - try { - gweiFee = newGasPrice !== '0' ? BlocksoftUtils.toGwei(newGasPrice).toString() : newGasPrice - } catch (e) { - BlocksoftCryptoLog.err('EthTxProcessor.getFeeRate newGasPrice2 to gwei error ' + e.message) - } - - const tmp = { - langMsg: title, - gasPrice: newGasPrice, - gasPriceGwei: gweiFee, - gasLimit: gasLimit.toString(), - feeForTx: fee.toString(), - amountForTx: amount, - nonceForTx, - nonceLog, - needSpeed, - ethAllowBlockedBalance, - ethAllowLongQuery, - isTransferAll: data.isTransferAll - } - // @ts-ignore - prevGasPrice = tmp.gasPrice - BlocksoftCryptoLog.log('EthTxProcessor.getFeeRate feeForTx rbfFaster ' + tmp.feeForTx + ' with gasPrice ' + tmp.gasPrice + ' / gasLimit ' + tmp.gasLimit) - result.fees.push(tmp) - } - } + } + if (typeof newGasPrice === 'undefined' || newGasPrice === 'undefined') { + newGasPrice = '0'; + } else { + newGasPrice = newGasPrice.toString(); + } + + let langMsg = titles[index]; + if (changedFeeByBalance) { + if (index > 0) { + langMsg = titlesChanged[index - 1]; } + } + + let gweiFee = 0; + try { + gweiFee = + newGasPrice !== '0' + ? BlocksoftUtils.toGwei(newGasPrice).toString() + : newGasPrice; + } catch (e) { + BlocksoftCryptoLog.err( + 'EthTxProcessor.getFeeRate newGasPrice to gwei error ' + e.message + ); + } + + const tmp = { + langMsg, + gasPrice: newGasPrice, + gasPriceGwei: gweiFee, + gasLimit: gasLimit.toString(), + feeForTx: fee.toString(), + nonceForTx, + nonceLog, + needSpeed, + ethAllowBlockedBalance, + ethAllowLongQuery, + isTransferAll: data.isTransferAll, + amountForTx: amount + }; + + const diff = needSpeed - newGasPrice; + if (!changedFeeByBalance || diff * 1 < 1000) { + feesOK[titles[index]] = tmp.gasPrice; + } else { + BlocksoftCryptoLog.log( + 'EthTxProcessor.getFeeRate skipped feesOk ' + + titles[index] + + ' as diff ' + + diff + + ' gasPrice ' + + tmp.gasPrice + + ' / gasLimit ' + + tmp.gasLimit + ); + } + if ( + BlocksoftUtils.diff(newGasPrice, prevGasPrice).indexOf('-') === -1 && + newGasPrice !== prevGasPrice + ) { + prevGasPrice = tmp.gasPrice; + BlocksoftCryptoLog.log( + 'EthTxProcessor.getFeeRate added feeForTx ' + + titles[index] + + ' ' + + tmp.feeForTx + + ' with gasPrice ' + + tmp.gasPrice + + ' / gasLimit ' + + tmp.gasLimit + ); + result.fees.push(tmp); + } else { + BlocksoftCryptoLog.log( + 'EthTxProcessor.getFeeRate skipped feeForTx ' + + titles[index] + + ' ' + + tmp.feeForTx + + ' with gasPrice ' + + tmp.gasPrice + + ' / gasLimit ' + + tmp.gasLimit + ); + } + } - if (balance !== '0' && result.fees.length === 0) { - const index = 0 - let feeForTx - let amountForTx = data.amount - let leftForFee = balance - if (this._useThisBalance && !data.isTransferAll) { - if (txRBF) { - leftForFee = BlocksoftUtils.diff(balance, BlocksoftUtils.div(data.amount, 2)) // if eth is transferred and paid in eth - so amount is not changed except send all - } else { - leftForFee = BlocksoftUtils.diff(balance, data.amount) // if eth is transferred and paid in eth - so amount is not changed except send all + prevGasPrice = 0; + if (txRBF) { + let recheck = result.fees.length < 2; + if ( + typeof additionalData.isCustomFee !== 'undefined' && + additionalData.isCustomFee + ) { + recheck = result.fees.length === 0; + } + if (recheck) { + for (let index = 0; index <= 2; index++) { + if (typeof result.fees[index] !== 'undefined') { + result.fees[index].langMsg = titles[index]; + continue; + } + let newGasPrice = Math.round((oldGasPrice * (10 + index + 1)) / 10); + const title = titles[index]; + const key = keys[index]; + const needSpeed = gasPrice[key]; + if (newGasPrice < gasPrice[key]) { + newGasPrice = gasPrice[key]; + } + let fee = BlocksoftUtils.mul(newGasPrice, gasLimit); + let amount = data.amount; + if (actualCheckBalance) { + const tmp = BlocksoftUtils.diff(balance, fee).toString(); + if (this._useThisBalance) { + if (tmp * 1 < 0) { + continue; + } + if (data.isTransferAll) { + amount = tmp; + } else { + const tmp2 = BlocksoftUtils.diff(tmp, amount).toString(); + if (tmp2 * 1 < 0) { + amount = tmp; } - } - - let fee = BlocksoftUtils.div(leftForFee, gasLimit) - if (fee) { - const tmp = fee.split('.') - if (tmp) { - fee = tmp[0] - feeForTx = BlocksoftUtils.mul(fee, gasLimit) - if (this._useThisBalance && (data.isTransferAll || txRBF)) { - amountForTx = BlocksoftUtils.diff(balance, feeForTx) // change amount for send all calculations - } + } + } else { + if (tmp * 1 < 0) { + const tmpGasPrice = BlocksoftUtils.div( + balance, + gasLimit + ).toString(); + if ( + BlocksoftUtils.diff(tmpGasPrice, prevGasPrice).toString() * + 1 > + 0 + ) { + fee = balance; + newGasPrice = tmpGasPrice; } else { - feeForTx = 0 + continue; } - } else { - feeForTx = 0 - } - if (!feeForTx) { - throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_FOR_ANY_FEE') - } - if (typeof fee === 'undefined') { - fee = '0' - } else { - fee = fee.toString() - } - const needSpeed = typeof keys[index] !== 'undefined' && typeof gasPrice[keys[index]] !== 'undefined' ? gasPrice[keys[index]].toString() : '?' - let gweiFee = 0 - try { - gweiFee = fee !== '0' ? BlocksoftUtils.toGwei(fee).toString() : fee - } catch (e) { - BlocksoftCryptoLog.err('EthTxProcessor.getFeeRate fee to gwei error ' + e.message) - } - const tmp = { - langMsg: 'eth_speed_slowest', - gasPrice: fee, - gasPriceGwei: gweiFee, - gasLimit: gasLimit.toString(), - feeForTx, - amountForTx, - nonceForTx, - nonceLog, - needSpeed, - ethAllowBlockedBalance, - ethAllowLongQuery, - isTransferAll: data.isTransferAll - } - - BlocksoftCryptoLog.log('EthTxProcessor.getFeeRate feeForTx ' + titles[index] + ' ' + tmp.feeForTx + ' corrected for balance ' + balance + ' with gasPrice ' + tmp.gasPrice + ' / gasLimit ' + tmp.gasLimit) - if (tmp.gasPrice > 0) { - result.fees.push(tmp) - } else { - throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_FOR_ANY_FEE') + } } + } + + if (typeof newGasPrice === 'undefined') { + newGasPrice = '0'; + } else { + newGasPrice = newGasPrice.toString(); + newGasPrice = BlocksoftUtils.round(newGasPrice); + } + + let gweiFee = 0; + try { + gweiFee = + newGasPrice !== '0' + ? BlocksoftUtils.toGwei(newGasPrice).toString() + : newGasPrice; + } catch (e) { + BlocksoftCryptoLog.err( + 'EthTxProcessor.getFeeRate newGasPrice2 to gwei error ' + + e.message + ); + } + + const tmp = { + langMsg: title, + gasPrice: newGasPrice, + gasPriceGwei: gweiFee, + gasLimit: gasLimit.toString(), + feeForTx: fee.toString(), + amountForTx: amount, + nonceForTx, + nonceLog, + needSpeed, + ethAllowBlockedBalance, + ethAllowLongQuery, + isTransferAll: data.isTransferAll + }; + // @ts-ignore + prevGasPrice = tmp.gasPrice; + BlocksoftCryptoLog.log( + 'EthTxProcessor.getFeeRate feeForTx rbfFaster ' + + tmp.feeForTx + + ' with gasPrice ' + + tmp.gasPrice + + ' / gasLimit ' + + tmp.gasLimit + ); + result.fees.push(tmp); } + } + } - - - if (!skippedByOld) { - if (typeof feesOK['eth_speed_fast'] === 'undefined') { - BlocksoftCryptoLog.log('EthTxProcessor.getFeeRate showSmallFeeNotice reason ' + JSON.stringify(feesOK)) - result.showSmallFeeNotice = new Date().getTime() - } + if (balance !== '0' && result.fees.length === 0) { + const index = 0; + let feeForTx; + let amountForTx = data.amount; + let leftForFee = balance; + if (this._useThisBalance && !data.isTransferAll) { + if (txRBF) { + leftForFee = BlocksoftUtils.diff( + balance, + BlocksoftUtils.div(data.amount, 2) + ); // if eth is transferred and paid in eth - so amount is not changed except send all + } else { + leftForFee = BlocksoftUtils.diff(balance, data.amount); // if eth is transferred and paid in eth - so amount is not changed except send all } - - result.selectedFeeIndex = result.fees.length - 1 - result.countedForBasicBalance = actualCheckBalance ? balance : '0' - if (!txRBF) { - let check = ethAllowBlockedBalance !== '1' && isNewNonce && maxNonceLocal.amountBlocked && typeof maxNonceLocal.amountBlocked[this._settings.currencyCode] !== 'undefined' - BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTransferProcessor.getFees ethAllowBlockedBalance ' - + ethAllowBlockedBalance + ' isNewNonce ' + isNewNonce + ' oldNonce ' + oldNonce - + ' amountBlocked ' + JSON.stringify(maxNonceLocal.amountBlocked) + ' => ' - + (check ? 'true' : 'false')) - if (check) { - try { - const diff = BlocksoftUtils.diff(result.countedForBasicBalance, maxNonceLocal.amountBlocked[this._settings.currencyCode]).toString() - const diffAmount = BlocksoftUtils.diff(diff, data.amount).toString() - BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTransferProcessor.getFees balance ' - + result.countedForBasicBalance + ' - blocked ' + maxNonceLocal.amountBlocked[this._settings.currencyCode] + ' = left Balance ' + diff + ' => left Amount ' + diffAmount) - if (diff.indexOf('-') !== -1) { - result.showBlockedBalanceNotice = new Date().getTime() - result.showBlockedBalanceFree = '0 ' + this._settings.currencySymbol - } else { - if (diffAmount.indexOf('-') !== -1) { - result.showBlockedBalanceNotice = new Date().getTime() - result.showBlockedBalanceFree = BlocksoftUtils.toUnified(diff, this._settings.decimals) + ' ' + this._settings.currencySymbol - } - } - } catch (e) { - if (config.debug.cryptoErrors) { - BlocksoftCryptoLog.log(' EthTransferProcessor.getFees ethAllowBlockedBalance inner error ' + e.message) - } - BlocksoftCryptoLog.log(' EthTransferProcessor.getFees ethAllowBlockedBalance inner error ' + e.message) - } - } - const LONG_QUERY = await BlocksoftExternalSettings.getStatic('ETH_LONG_QUERY') - check = maxNonceLocal.queryLength * 1 >= LONG_QUERY * 1 - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTransferProcessor.getFees ethAllowLongQuery ' - + ethAllowLongQuery + ' Query scanned ' + maxNonceLocal.maxScanned + ' success ' + maxNonceLocal.maxSuccess + ' length ' + maxNonceLocal.queryLength - + ' txs ' + JSON.stringify(maxNonceLocal.queryTxs) + ' => ' - + (check ? 'true' : 'false') - ) - if (check) { - result.showLongQueryNotice = new Date().getTime() - result.showLongQueryNoticeTxs = maxNonceLocal.queryTxs - } - + } + + let fee = BlocksoftUtils.div(leftForFee, gasLimit); + if (fee) { + const tmp = fee.split('.'); + if (tmp) { + fee = tmp[0]; + feeForTx = BlocksoftUtils.mul(fee, gasLimit); + if (this._useThisBalance && (data.isTransferAll || txRBF)) { + amountForTx = BlocksoftUtils.diff(balance, feeForTx); // change amount for send all calculations + } } else { - for (const fee of result.fees) { - fee.showNonce = true - } + feeForTx = 0; } - result.showBigGasNotice = showBigGasNotice ? new Date().getTime() : 0 - return result + } else { + feeForTx = 0; + } + if (!feeForTx) { + throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_FOR_ANY_FEE'); + } + if (typeof fee === 'undefined') { + fee = '0'; + } else { + fee = fee.toString(); + } + const needSpeed = + typeof keys[index] !== 'undefined' && + typeof gasPrice[keys[index]] !== 'undefined' + ? gasPrice[keys[index]].toString() + : '?'; + let gweiFee = 0; + try { + gweiFee = fee !== '0' ? BlocksoftUtils.toGwei(fee).toString() : fee; + } catch (e) { + BlocksoftCryptoLog.err( + 'EthTxProcessor.getFeeRate fee to gwei error ' + e.message + ); + } + const tmp = { + langMsg: 'eth_speed_slowest', + gasPrice: fee, + gasPriceGwei: gweiFee, + gasLimit: gasLimit.toString(), + feeForTx, + amountForTx, + nonceForTx, + nonceLog, + needSpeed, + ethAllowBlockedBalance, + ethAllowLongQuery, + isTransferAll: data.isTransferAll + }; + + BlocksoftCryptoLog.log( + 'EthTxProcessor.getFeeRate feeForTx ' + + titles[index] + + ' ' + + tmp.feeForTx + + ' corrected for balance ' + + balance + + ' with gasPrice ' + + tmp.gasPrice + + ' / gasLimit ' + + tmp.gasLimit + ); + if (tmp.gasPrice > 0) { + result.fees.push(tmp); + } else { + throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_FOR_ANY_FEE'); + } } + if (!skippedByOld) { + if (typeof feesOK['eth_speed_fast'] === 'undefined') { + BlocksoftCryptoLog.log( + 'EthTxProcessor.getFeeRate showSmallFeeNotice reason ' + + JSON.stringify(feesOK) + ); + result.showSmallFeeNotice = new Date().getTime(); + } + } - async getTransferAllBalance(data: BlocksoftBlockchainTypes.TransferData, privateData?: BlocksoftBlockchainTypes.TransferPrivateData, additionalData: BlocksoftBlockchainTypes.TransferAdditionalData = {}): Promise { - if (!data.amount || data.amount === '0') { - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTransferProcessor.getTransferAllBalance ' + data.addressFrom + ' started with load balance needed') - try { - // @ts-ignore - data.amount = await this._web3.eth.getBalance(data.addressFrom).toString() - } catch (e) { - this.checkError(e, data) - } - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTransferProcessor.getTransferAllBalance ' + data.addressFrom + ' started with loaded balance ' + data.amount) - } else { - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTransferProcessor.getTransferAllBalance ' + data.addressFrom + ' started with preset balance ' + data.amount) - } - - // noinspection EqualityComparisonWithCoercionJS - if (data.amount === '0' || data.amount === '') { - return { - selectedTransferAllBalance: '0', - selectedFeeIndex: -1, - fees: [], - countedForBasicBalance: '0' + result.selectedFeeIndex = result.fees.length - 1; + result.countedForBasicBalance = actualCheckBalance ? balance : '0'; + if (!txRBF) { + let check = + ethAllowBlockedBalance !== '1' && + isNewNonce && + maxNonceLocal.amountBlocked && + typeof maxNonceLocal.amountBlocked[this._settings.currencyCode] !== + 'undefined'; + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' EthTransferProcessor.getFees ethAllowBlockedBalance ' + + ethAllowBlockedBalance + + ' isNewNonce ' + + isNewNonce + + ' oldNonce ' + + oldNonce + + ' amountBlocked ' + + JSON.stringify(maxNonceLocal.amountBlocked) + + ' => ' + + (check ? 'true' : 'false') + ); + if (check) { + try { + const diff = BlocksoftUtils.diff( + result.countedForBasicBalance, + maxNonceLocal.amountBlocked[this._settings.currencyCode] + ).toString(); + const diffAmount = BlocksoftUtils.diff(diff, data.amount).toString(); + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' EthTransferProcessor.getFees balance ' + + result.countedForBasicBalance + + ' - blocked ' + + maxNonceLocal.amountBlocked[this._settings.currencyCode] + + ' = left Balance ' + + diff + + ' => left Amount ' + + diffAmount + ); + if (diff.indexOf('-') !== -1) { + result.showBlockedBalanceNotice = new Date().getTime(); + result.showBlockedBalanceFree = + '0 ' + this._settings.currencySymbol; + } else { + if (diffAmount.indexOf('-') !== -1) { + result.showBlockedBalanceNotice = new Date().getTime(); + result.showBlockedBalanceFree = + BlocksoftUtils.toUnified(diff, this._settings.decimals) + + ' ' + + this._settings.currencySymbol; } + } + } catch (e) { + if (config.debug.cryptoErrors) { + BlocksoftCryptoLog.log( + ' EthTransferProcessor.getFees ethAllowBlockedBalance inner error ' + + e.message + ); + } + BlocksoftCryptoLog.log( + ' EthTransferProcessor.getFees ethAllowBlockedBalance inner error ' + + e.message + ); } + } + const LONG_QUERY = await BlocksoftExternalSettings.getStatic( + 'ETH_LONG_QUERY' + ); + check = maxNonceLocal.queryLength * 1 >= LONG_QUERY * 1; + await BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' EthTransferProcessor.getFees ethAllowLongQuery ' + + ethAllowLongQuery + + ' Query scanned ' + + maxNonceLocal.maxScanned + + ' success ' + + maxNonceLocal.maxSuccess + + ' length ' + + maxNonceLocal.queryLength + + ' txs ' + + JSON.stringify(maxNonceLocal.queryTxs) + + ' => ' + + (check ? 'true' : 'false') + ); + if (check) { + result.showLongQueryNotice = new Date().getTime(); + result.showLongQueryNoticeTxs = maxNonceLocal.queryTxs; + } + } else { + for (const fee of result.fees) { + fee.showNonce = true; + } + } + result.showBigGasNotice = showBigGasNotice ? new Date().getTime() : 0; + return result; + } + + async getTransferAllBalance( + data: BlocksoftBlockchainTypes.TransferData, + privateData?: BlocksoftBlockchainTypes.TransferPrivateData, + additionalData: BlocksoftBlockchainTypes.TransferAdditionalData = {} + ): Promise { + if (!data.amount || data.amount === '0') { + await BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' EthTransferProcessor.getTransferAllBalance ' + + data.addressFrom + + ' started with load balance needed' + ); + try { + // @ts-ignore + data.amount = await this._web3.eth + .getBalance(data.addressFrom) + .toString(); + } catch (e) { + this.checkError(e, data); + } + await BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' EthTransferProcessor.getTransferAllBalance ' + + data.addressFrom + + ' started with loaded balance ' + + data.amount + ); + } else { + await BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' EthTransferProcessor.getTransferAllBalance ' + + data.addressFrom + + ' started with preset balance ' + + data.amount + ); + } + // noinspection EqualityComparisonWithCoercionJS + if (data.amount === '0' || data.amount === '') { + return { + selectedTransferAllBalance: '0', + selectedFeeIndex: -1, + fees: [], + countedForBasicBalance: '0' + }; + } - const fees = await this.getFeeRate(data, privateData, additionalData) + const fees = await this.getFeeRate(data, privateData, additionalData); - if (!fees || fees.selectedFeeIndex < 0) { - return { - selectedTransferAllBalance: '0', - selectedFeeIndex: -2, - fees: [], - countedForBasicBalance: '0' - } - } - if (this._useThisBalance) { - try { - for (const fee of fees.fees) { - fee.totalFeePlusAmountETH = BlocksoftUtils.toEther(BlocksoftUtils.add(fee.amountForTx, fee.feeForTx)) - } - fees.selectedTransferAllBalanceETH = BlocksoftUtils.toEther(fees.fees[fees.selectedFeeIndex].amountForTx) - } catch (e) { - BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTransferProcessor.getTransferAllBalance ' + data.addressFrom + ' => ' + data.addressTo + ' error on logging ' + e.message) - } - } - return { - ...fees, - selectedTransferAllBalance: fees.fees[fees.selectedFeeIndex].amountForTx + if (!fees || fees.selectedFeeIndex < 0) { + return { + selectedTransferAllBalance: '0', + selectedFeeIndex: -2, + fees: [], + countedForBasicBalance: '0' + }; + } + if (this._useThisBalance) { + try { + for (const fee of fees.fees) { + fee.totalFeePlusAmountETH = BlocksoftUtils.toEther( + BlocksoftUtils.add(fee.amountForTx, fee.feeForTx) + ); } + fees.selectedTransferAllBalanceETH = BlocksoftUtils.toEther( + fees.fees[fees.selectedFeeIndex].amountForTx + ); + } catch (e) { + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' EthTransferProcessor.getTransferAllBalance ' + + data.addressFrom + + ' => ' + + data.addressTo + + ' error on logging ' + + e.message + ); + } + } + return { + ...fees, + selectedTransferAllBalance: fees.fees[fees.selectedFeeIndex].amountForTx + }; + } + + async sendTx( + data: BlocksoftBlockchainTypes.TransferData, + privateData: BlocksoftBlockchainTypes.TransferPrivateData, + uiData: BlocksoftBlockchainTypes.TransferUiData + ): Promise { + if (typeof privateData.privateKey === 'undefined') { + throw new Error('ETH transaction required privateKey'); + } + if (typeof data.addressTo === 'undefined') { + throw new Error('ETH transaction required addressTo'); } - async sendTx(data: BlocksoftBlockchainTypes.TransferData, privateData: BlocksoftBlockchainTypes.TransferPrivateData, uiData: BlocksoftBlockchainTypes.TransferUiData): Promise { - if (typeof privateData.privateKey === 'undefined') { - throw new Error('ETH transaction required privateKey') - } - if (typeof data.addressTo === 'undefined') { - throw new Error('ETH transaction required addressTo') - } + await BlocksoftCryptoLog.log( + this._settings.currencyCode + ' EthTransferProcessor sendTx started', + JSON.parse(JSON.stringify(data)) + ); - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTransferProcessor sendTx started', JSON.parse(JSON.stringify(data))) + let txRBFed = ''; + let txRBF = false; + let realAddressTo = data.addressTo; + if (realAddressTo !== '' && OneUtils.isOneAddress(realAddressTo)) { + realAddressTo = OneUtils.fromOneAddress(realAddressTo); + } + const realAddressToLower = realAddressTo.toLowerCase(); + if ( + typeof data.transactionRemoveByFee !== 'undefined' && + data.transactionRemoveByFee + ) { + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' EthTransferProcessor.sendTx started ' + + data.transactionRemoveByFee + ); + txRBF = data.transactionRemoveByFee; + txRBFed = 'RBFremoved'; + } else if ( + typeof data.transactionReplaceByFee !== 'undefined' && + data.transactionReplaceByFee + ) { + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' EthTransferProcessor.sendTx resend started ' + + data.transactionReplaceByFee + ); + txRBF = data.transactionReplaceByFee; + txRBFed = 'RBFed'; + } else if (typeof data.dexOrderData !== 'undefined' && data.dexOrderData) { + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' EthTransferProcessor.sendTx dex ' + + data.addressFrom + + ' started' + ); + txRBFed = 'dexSend'; + } else { + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' EthTransferProcessor.sendTx ' + + data.addressFrom + + ' started' + ); + txRBFed = 'usualSend'; + if ( + realAddressTo !== '' && + (realAddressToLower.indexOf('0x') === -1 || + realAddressToLower.indexOf('0x') !== 0) + ) { + throw new Error('SERVER_RESPONSE_BAD_DESTINATION'); + } + } - let txRBFed = '' - let txRBF = false - let realAddressTo = data.addressTo - if (realAddressTo !== '' && OneUtils.isOneAddress(realAddressTo)) { - realAddressTo = OneUtils.fromOneAddress(realAddressTo) - } - const realAddressToLower = realAddressTo.toLowerCase() - if (typeof data.transactionRemoveByFee !== 'undefined' && data.transactionRemoveByFee) { - BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTransferProcessor.sendTx started ' + data.transactionRemoveByFee) - txRBF = data.transactionRemoveByFee - txRBFed = 'RBFremoved' - } else if (typeof data.transactionReplaceByFee !== 'undefined' && data.transactionReplaceByFee) { - BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTransferProcessor.sendTx resend started ' + data.transactionReplaceByFee) - txRBF = data.transactionReplaceByFee - txRBFed = 'RBFed' - } else if (typeof data.dexOrderData !== 'undefined' && data.dexOrderData) { - BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTransferProcessor.sendTx dex ' + data.addressFrom + ' started') - txRBFed = 'dexSend' - } else { - BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTransferProcessor.sendTx ' + data.addressFrom + ' started') - txRBFed = 'usualSend' - if (realAddressTo !== '' && (realAddressToLower.indexOf('0x') === -1 || realAddressToLower.indexOf('0x') !== 0)) { - throw new Error('SERVER_RESPONSE_BAD_DESTINATION') - } - } + let finalGasPrice = 0; + let finalGasLimit = 0; + + let selectedFee; + if ( + typeof uiData.selectedFee !== 'undefined' && + typeof uiData.selectedFee.gasPrice !== 'undefined' + ) { + selectedFee = uiData.selectedFee; + // @ts-ignore + finalGasPrice = uiData.selectedFee.gasPrice * 1; + // @ts-ignore + finalGasLimit = Math.ceil(uiData.selectedFee.gasLimit * 1); + } else { + const fees = await this.getFeeRate(data, privateData); + if (fees.selectedFeeIndex < 0) { + throw new Error('SERVER_RESPONSE_NOTHING_LEFT_FOR_FEE'); + } + selectedFee = fees.fees[fees.selectedFeeIndex]; + // @ts-ignore + finalGasPrice = selectedFee.gasPrice * 1; + // @ts-ignoreф + finalGasLimit = Math.ceil(selectedFee.gasLimit * 1); + } - let finalGasPrice = 0 - let finalGasLimit = 0 + // @ts-ignore + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' EthTransferProcessor.sendTx ' + + txRBFed + + ' feeForTx', + { + uiData, + finalGasPrice, + finalGasLimit + } + ); + if (finalGasLimit === 0 || !finalGasLimit) { + throw new Error('SERVER_PLEASE_SELECT_FEE'); + } + if (finalGasPrice === 0 || !finalGasPrice) { + throw new Error('SERVER_PLEASE_SELECT_FEE'); + } - let selectedFee - if (typeof uiData.selectedFee !== 'undefined' && typeof uiData.selectedFee.gasPrice !== 'undefined') { - selectedFee = uiData.selectedFee - // @ts-ignore - finalGasPrice = uiData.selectedFee.gasPrice * 1 - // @ts-ignore - finalGasLimit = Math.ceil(uiData.selectedFee.gasLimit * 1) + const tx: BlocksoftBlockchainTypes.EthTx = { + from: data.addressFrom, + to: realAddressToLower, + gasPrice: finalGasPrice, + gas: finalGasLimit, + value: data.amount + }; + + if (typeof data.dexOrderData !== 'undefined' && data.dexOrderData) { + if (typeof data.dexOrderData[0].params.data !== 'undefined') { + tx.data = data.dexOrderData[0].params.data; + if (typeof data.dexOrderData[0].params.value !== 'undefined') { + tx.value = data.dexOrderData[0].params.value; } else { - const fees = await this.getFeeRate(data, privateData) - if (fees.selectedFeeIndex < 0) { - throw new Error('SERVER_RESPONSE_NOTHING_LEFT_FOR_FEE') - } - selectedFee = fees.fees[fees.selectedFeeIndex] - // @ts-ignore - finalGasPrice = selectedFee.gasPrice * 1 - // @ts-ignoreф - finalGasLimit = Math.ceil(selectedFee.gasLimit * 1) - } - - // @ts-ignore - BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTransferProcessor.sendTx ' + txRBFed + ' feeForTx', { - uiData, - finalGasPrice, - finalGasLimit - }) - if (finalGasLimit === 0 || !finalGasLimit) { - throw new Error('SERVER_PLEASE_SELECT_FEE') + tx.value = 0; } - if (finalGasPrice === 0 || !finalGasPrice) { - throw new Error('SERVER_PLEASE_SELECT_FEE') + } + if (typeof data.dexOrderData[0].params.to !== 'undefined') { + tx.to = data.dexOrderData[0].params.to; + } + } else if ( + typeof data.contractCallData !== 'undefined' && + typeof data.contractCallData.contractAddress !== 'undefined' + ) { + const schema = data.contractCallData.contractSchema; + try { + let abiCode; + if (schema === 'ERC721') { + abiCode = abi721.ERC721; + } else if (schema === 'ERC1155') { + abiCode = abi1155.ERC1155; + } else { + throw new Error('Contract abi not found ' + schema); } - - const tx: BlocksoftBlockchainTypes.EthTx = { - from: data.addressFrom, - to: realAddressToLower, - gasPrice: finalGasPrice, - gas: finalGasLimit, - value: data.amount + const token = new this._web3.eth.Contract( + abiCode, + data.contractCallData.contractAddress + ); + + const tmpParams = data.contractCallData.contractActionParams; + for (let i = 0, ic = tmpParams.length; i < ic; i++) { + if (tmpParams[i] === 'addressTo') { + tmpParams[i] = realAddressTo; + } } + tx.to = data.contractCallData.contractAddress; + tx.data = token.methods[data.contractCallData.contractAction]( + ...tmpParams + ).encodeABI(); + } catch (e) { + throw new Error(e.message + ' while encodeABI for ' + schema); + } + } else if ( + typeof data.walletConnectData !== 'undefined' && + typeof data.walletConnectData.data !== 'undefined' + ) { + tx.data = data.walletConnectData.data; + } else if (typeof data.blockchainData !== 'undefined') { + tx.data = data.blockchainData; // actual value for erc20 etc + } else if (typeof data?.transactionJson?.txData !== 'undefined') { + tx.data = data?.transactionJson?.txData; + } - if (typeof data.dexOrderData !== 'undefined' && data.dexOrderData) { - if (typeof data.dexOrderData[0].params.data !== 'undefined') { - tx.data = data.dexOrderData[0].params.data - if (typeof data.dexOrderData[0].params.value !== 'undefined') { - tx.value = data.dexOrderData[0].params.value - } else { - tx.value = 0 - } - } - if (typeof data.dexOrderData[0].params.to !== 'undefined') { - tx.to = data.dexOrderData[0].params.to - } - } else if (typeof data.contractCallData !== 'undefined' && typeof data.contractCallData.contractAddress !== 'undefined') { - const schema = data.contractCallData.contractSchema - try { - let abiCode - if (schema === 'ERC721') { - abiCode = abi721.ERC721 - } else if (schema === 'ERC1155') { - abiCode = abi1155.ERC1155 - } else { - throw new Error('Contract abi not found ' + schema) - } - const token = new this._web3.eth.Contract(abiCode, data.contractCallData.contractAddress) - - const tmpParams = data.contractCallData.contractActionParams - for (let i = 0, ic = tmpParams.length; i < ic; i++) { - if (tmpParams[i] === 'addressTo') { - tmpParams[i] = realAddressTo - } - } - tx.to = data.contractCallData.contractAddress - tx.data = token.methods[data.contractCallData.contractAction](...tmpParams).encodeABI() - } catch (e) { - throw new Error(e.message + ' while encodeABI for ' + schema) - } - } else if (typeof data.walletConnectData !== 'undefined' && typeof data.walletConnectData.data !== 'undefined') { - tx.data = data.walletConnectData.data - } else if (typeof data.blockchainData !== 'undefined') { - tx.data = data.blockchainData // actual value for erc20 etc - } else if (typeof data?.transactionJson?.txData !== 'undefined') { - tx.data = data?.transactionJson?.txData + const sender = new EthTxSendProvider( + this._web3, + this._trezorServerCode, + this._mainCurrencyCode, + this._mainChainId, + this._settings + ); + const logData = JSON.parse(JSON.stringify(tx)); + logData.currencyCode = this._settings.currencyCode; + logData.selectedFee = selectedFee; + logData.basicAddressTo = + typeof data.basicAddressTo !== 'undefined' + ? data.basicAddressTo.toLowerCase() + : realAddressToLower; + logData.basicAmount = + typeof data.basicAmount !== 'undefined' ? data.basicAmount : data.amount; + logData.basicToken = + typeof data.basicToken !== 'undefined' ? data.basicToken : ''; + logData.pushLocale = sublocale(); + logData.pushSetting = await settingsActions.getSetting( + 'transactionsNotifs' + ); + + let result = {} as BlocksoftBlockchainTypes.SendTxResult; + try { + if (txRBF) { + let oldNonce = + typeof uiData.selectedFee.nonceForTx !== 'undefined' + ? uiData.selectedFee.nonceForTx + : false; + if (oldNonce === false || oldNonce === -1) { + // actually could remove as not used without receipt fee + oldNonce = + typeof data.transactionJson !== 'undefined' && + typeof data.transactionJson.nonce !== 'undefined' + ? data.transactionJson.nonce + : false; } - - const sender = new EthTxSendProvider(this._web3, this._trezorServerCode, this._mainCurrencyCode, this._mainChainId, this._settings) - const logData = JSON.parse(JSON.stringify(tx)) - logData.currencyCode = this._settings.currencyCode - logData.selectedFee = selectedFee - logData.basicAddressTo = typeof data.basicAddressTo !== 'undefined' ? data.basicAddressTo.toLowerCase() : realAddressToLower - logData.basicAmount = typeof data.basicAmount !== 'undefined' ? data.basicAmount : data.amount - logData.basicToken = typeof data.basicToken !== 'undefined' ? data.basicToken : '' - logData.pushLocale = sublocale() - logData.pushSetting = await settingsActions.getSetting('transactionsNotifs') - - let result = {} as BlocksoftBlockchainTypes.SendTxResult - try { - if (txRBF) { - let oldNonce = typeof uiData.selectedFee.nonceForTx !== 'undefined' ? uiData.selectedFee.nonceForTx : false - if (oldNonce === false || oldNonce === -1) { - // actually could remove as not used without receipt fee - oldNonce = typeof data.transactionJson !== 'undefined' && typeof data.transactionJson.nonce !== 'undefined' ? data.transactionJson.nonce : false - } - if (oldNonce === false || oldNonce === -1) { - try { - const ethProvider = BlocksoftDispatcher.getScannerProcessor(data.currencyCode) - const scannedTx = await ethProvider.getTransactionBlockchain(txRBF) - if (scannedTx) { - oldNonce = scannedTx.nonce - } - } catch (e) { - BlocksoftCryptoLog.err(this._settings.currencyCode + ' EthTransferProcessor.sent rbf not loaded nonce for ' + txRBF + ' ' + e.message) - throw new Error('System error: not loaded nonce for ' + txRBF) - } - if (oldNonce === false || oldNonce === -1) { - BlocksoftCryptoLog.err(this._settings.currencyCode + ' EthTransferProcessor.sent rbf no nonce for ' + txRBF) - throw new Error('System error: no nonce for ' + txRBF) - } - } - logData.setNonce = oldNonce - tx.nonce = oldNonce - } else { - // @ts-ignore - if (typeof uiData.selectedFee.nonceForTx !== 'undefined' - && (uiData.selectedFee.nonceForTx.toString() === '0' || uiData.selectedFee.nonceForTx) - && uiData.selectedFee.nonceForTx !== '' - && uiData.selectedFee.nonceForTx * 1 >= 0 - ) { - // @ts-ignore - tx.nonce = uiData.selectedFee.nonceForTx * 1 - logData.setNonce = tx.nonce - logData.selectedFee.nonceLog = 'replacedByUi ' + uiData.selectedFee.nonceForTx + ' ' + (typeof logData.selectedFee.nonceLog !== 'undefined' ? logData.selectedFee.nonceLog : '') - } - BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTransferProcessor.sent ' + data.addressFrom + ' nonceLog ' + logData.selectedFee.nonceLog) - } - - if (typeof uiData !== 'undefined' && typeof uiData.selectedFee !== 'undefined' && typeof uiData.selectedFee.rawOnly !== 'undefined' && uiData.selectedFee.rawOnly) { - return { rawOnly: uiData.selectedFee.rawOnly, raw : await sender.sign(tx, privateData, txRBF, logData)} - } - - try { - result = await sender.send(tx, privateData, txRBF, logData) - } catch (e) { - if (config.debug.cryptoErrors) { - BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTransferProcessor.sent while ' + (txRBF ? 'txRbf':'usual') + ' sender.send error ' + e.message) - } - throw e - } - - result.transactionFee = BlocksoftUtils.mul(finalGasPrice, finalGasLimit) - result.transactionFeeCurrencyCode = this._mainCurrencyCode === 'BNB' ? 'BNB_SMART' : this._mainCurrencyCode - if (txRBF) { - if (typeof data.blockchainData === 'undefined' || !data.blockchainData) { - result.amountForTx = data.amount - } - result.addressTo = data.addressTo === data.addressFrom || realAddressTo === data.addressFrom ? '' : realAddressTo - } else { - result.transactionJson.txData = tx.data - await EthTmpDS.getCache(this._mainCurrencyCode, data.addressFrom) - } - BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTransferProcessor.sent ' + data.addressFrom + ' done ' + JSON.stringify(result.transactionJson)) - } catch (e) { - if (config.debug.cryptoErrors) { - BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTransferProcessor.sent error ' + e.message, tx) + if (oldNonce === false || oldNonce === -1) { + try { + const ethProvider = BlocksoftDispatcher.getScannerProcessor( + data.currencyCode + ); + const scannedTx = await ethProvider.getTransactionBlockchain(txRBF); + if (scannedTx) { + oldNonce = scannedTx.nonce; } - this.checkError(e, data, txRBF, logData) + } catch (e) { + BlocksoftCryptoLog.err( + this._settings.currencyCode + + ' EthTransferProcessor.sent rbf not loaded nonce for ' + + txRBF + + ' ' + + e.message + ); + throw new Error('System error: not loaded nonce for ' + txRBF); + } + if (oldNonce === false || oldNonce === -1) { + BlocksoftCryptoLog.err( + this._settings.currencyCode + + ' EthTransferProcessor.sent rbf no nonce for ' + + txRBF + ); + throw new Error('System error: no nonce for ' + txRBF); + } } + logData.setNonce = oldNonce; + tx.nonce = oldNonce; + } else { // @ts-ignore - logData.result = result - // noinspection ES6MissingAwait - MarketingEvent.logOnlyRealTime('v20_eth_tx_success ' + this._settings.currencyCode + ' ' + data.addressFrom + ' => ' + realAddressTo, logData) - - return result - } - - async setMissingTx(data: BlocksoftBlockchainTypes.DbAccount, transaction: BlocksoftBlockchainTypes.DbTransaction): Promise { - if (typeof transaction.transactionJson !== 'undefined' && transaction.transactionJson && typeof transaction.transactionJson.nonce !== 'undefined') { - BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTransferPRocessor.setMissingTx remove nonce ' + transaction.transactionJson.nonce + ' ' + transaction.transactionHash) - await EthTmpDS.removeNonce(this._mainCurrencyCode, data.address, 'send_' + transaction.transactionHash) + if ( + typeof uiData.selectedFee.nonceForTx !== 'undefined' && + (uiData.selectedFee.nonceForTx.toString() === '0' || + uiData.selectedFee.nonceForTx) && + uiData.selectedFee.nonceForTx !== '' && + uiData.selectedFee.nonceForTx * 1 >= 0 + ) { + // @ts-ignore + tx.nonce = uiData.selectedFee.nonceForTx * 1; + logData.setNonce = tx.nonce; + logData.selectedFee.nonceLog = + 'replacedByUi ' + + uiData.selectedFee.nonceForTx + + ' ' + + (typeof logData.selectedFee.nonceLog !== 'undefined' + ? logData.selectedFee.nonceLog + : ''); } - MarketingEvent.logOnlyRealTime('v20_eth_tx_set_missing ' + this._settings.currencyCode + ' ' + data.address + ' => ' + transaction.addressTo, transaction) - return true - } - - canRBF(data: BlocksoftBlockchainTypes.DbAccount, transaction: BlocksoftBlockchainTypes.DbTransaction, source: string): boolean { - if (transaction.transactionDirection === 'income') { - BlocksoftCryptoLog.log('EthTransferProcessor.canRBF ' + transaction.transactionHash + ' false by income') - return false + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' EthTransferProcessor.sent ' + + data.addressFrom + + ' nonceLog ' + + logData.selectedFee.nonceLog + ); + } + + if ( + typeof uiData !== 'undefined' && + typeof uiData.selectedFee !== 'undefined' && + typeof uiData.selectedFee.rawOnly !== 'undefined' && + uiData.selectedFee.rawOnly + ) { + return { + rawOnly: uiData.selectedFee.rawOnly, + raw: await sender.sign(tx, privateData, txRBF, logData) + }; + } + + try { + result = await sender.send(tx, privateData, txRBF, logData); + } catch (e) { + if (config.debug.cryptoErrors) { + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' EthTransferProcessor.sent while ' + + (txRBF ? 'txRbf' : 'usual') + + ' sender.send error ' + + e.message + ); } - if (typeof transaction.transactionJson !== 'undefined') { - if (typeof transaction.transactionJson.delegatedNonce !== 'undefined') { - BlocksoftCryptoLog.log('EthTransferProcessor.canRBF ' + transaction.transactionHash + ' false by delegated') - return false - } - /*if (typeof transaction.transactionJson.nonce !== 'undefined') { + throw e; + } + + result.transactionFee = BlocksoftUtils.mul(finalGasPrice, finalGasLimit); + result.transactionFeeCurrencyCode = + this._mainCurrencyCode === 'BNB' ? 'BNB_SMART' : this._mainCurrencyCode; + if (txRBF) { + if ( + typeof data.blockchainData === 'undefined' || + !data.blockchainData + ) { + result.amountForTx = data.amount; + } + result.addressTo = + data.addressTo === data.addressFrom || + realAddressTo === data.addressFrom + ? '' + : realAddressTo; + } else { + result.transactionJson.txData = tx.data; + await EthTmpDS.getCache(this._mainCurrencyCode, data.addressFrom); + } + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' EthTransferProcessor.sent ' + + data.addressFrom + + ' done ' + + JSON.stringify(result.transactionJson) + ); + } catch (e) { + if (config.debug.cryptoErrors) { + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' EthTransferProcessor.sent error ' + + e.message, + tx + ); + } + this.checkError(e, data, txRBF, logData); + } + // @ts-ignore + logData.result = result; + // noinspection ES6MissingAwait + MarketingEvent.logOnlyRealTime( + 'v20_eth_tx_success ' + + this._settings.currencyCode + + ' ' + + data.addressFrom + + ' => ' + + realAddressTo, + logData + ); + + return result; + } + + async setMissingTx( + data: BlocksoftBlockchainTypes.DbAccount, + transaction: BlocksoftBlockchainTypes.DbTransaction + ): Promise { + if ( + typeof transaction.transactionJson !== 'undefined' && + transaction.transactionJson && + typeof transaction.transactionJson.nonce !== 'undefined' + ) { + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' EthTransferPRocessor.setMissingTx remove nonce ' + + transaction.transactionJson.nonce + + ' ' + + transaction.transactionHash + ); + await EthTmpDS.removeNonce( + this._mainCurrencyCode, + data.address, + 'send_' + transaction.transactionHash + ); + } + MarketingEvent.logOnlyRealTime( + 'v20_eth_tx_set_missing ' + + this._settings.currencyCode + + ' ' + + data.address + + ' => ' + + transaction.addressTo, + transaction + ); + return true; + } + + canRBF( + data: BlocksoftBlockchainTypes.DbAccount, + transaction: BlocksoftBlockchainTypes.DbTransaction, + source: string + ): boolean { + if (transaction.transactionDirection === 'income') { + BlocksoftCryptoLog.log( + 'EthTransferProcessor.canRBF ' + + transaction.transactionHash + + ' false by income' + ); + return false; + } + if (typeof transaction.transactionJson !== 'undefined') { + if (typeof transaction.transactionJson.delegatedNonce !== 'undefined') { + BlocksoftCryptoLog.log( + 'EthTransferProcessor.canRBF ' + + transaction.transactionHash + + ' false by delegated' + ); + return false; + } + /*if (typeof transaction.transactionJson.nonce !== 'undefined') { const max = EthTmpDS.getMaxStatic(data.address) if (max.success > -1) { // @ts-ignore @@ -966,7 +1561,7 @@ export default class EthTransferProcessor extends EthBasic implements BlocksoftB return false } }*/ - } - return true } + return true; + } } diff --git a/crypto/blockchains/eth/EthTransferProcessorErc20.ts b/crypto/blockchains/eth/EthTransferProcessorErc20.ts index 5fd891c0d..079146f56 100644 --- a/crypto/blockchains/eth/EthTransferProcessorErc20.ts +++ b/crypto/blockchains/eth/EthTransferProcessorErc20.ts @@ -2,200 +2,359 @@ * @author Ksu * @version 0.20 */ -import EthTransferProcessor from './EthTransferProcessor' -import config from '@app/config/config' -import MarketingEvent from '@app/services/Marketing/MarketingEvent' +import EthTransferProcessor from './EthTransferProcessor'; +import config from '@app/config/config'; +import MarketingEvent from '@app/services/Marketing/MarketingEvent'; -import { BlocksoftBlockchainTypes } from '../BlocksoftBlockchainTypes' +import { BlocksoftBlockchainTypes } from '../BlocksoftBlockchainTypes'; -import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog' -import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings' +import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; +import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings'; +const abi = require('./ext/erc20.js'); -const abi = require('./ext/erc20.js') +export default class EthTransferProcessorErc20 + extends EthTransferProcessor + implements BlocksoftBlockchainTypes.TransferProcessor +{ + constructor(settings: { network?: string; tokenAddress: any }) { + super(settings); + // @ts-ignore + this._token = new this._web3.eth.Contract(abi.ERC20, settings.tokenAddress); + this._tokenAddress = settings.tokenAddress.toLowerCase(); + this._useThisBalance = false; + } -export default class EthTransferProcessorErc20 extends EthTransferProcessor implements BlocksoftBlockchainTypes.TransferProcessor { + _token: any = false; + _tokenAddress: string = ''; + _useThisBalance: boolean = false; - - constructor(settings: { network?: string; tokenAddress: any }) { - super(settings) - // @ts-ignore - this._token = new this._web3.eth.Contract(abi.ERC20, settings.tokenAddress) - this._tokenAddress = settings.tokenAddress.toLowerCase() - this._useThisBalance = false + async checkTransferHasError( + data: BlocksoftBlockchainTypes.CheckTransferHasErrorData + ): Promise { + // @ts-ignore + const balance = + data.addressFrom && data.addressFrom !== '' + ? await this._web3.eth.getBalance(data.addressFrom) + : 0; + if (balance > 0) { + return { isOk: true }; + } else { + // @ts-ignore + return { + isOk: false, + code: 'TOKEN', + parentBlockchain: this._mainTokenBlockchain, + parentCurrency: this._mainCurrencyCode + }; } + } - _token: any = false - _tokenAddress: string = '' - _useThisBalance: boolean = false + checkSendAllModal(data: { currencyCode: any }): boolean { + return false; + } - async checkTransferHasError(data: BlocksoftBlockchainTypes.CheckTransferHasErrorData): Promise { - // @ts-ignore - const balance = data.addressFrom && data.addressFrom !== '' ? await this._web3.eth.getBalance(data.addressFrom) : 0 - if (balance > 0) { - return { isOk: true } - } else { - // @ts-ignore - return { isOk: false, code: 'TOKEN', parentBlockchain: this._mainTokenBlockchain, parentCurrency: this._mainCurrencyCode } - } + async getFeeRate( + data: BlocksoftBlockchainTypes.TransferData, + privateData?: BlocksoftBlockchainTypes.TransferPrivateData, + additionalData: BlocksoftBlockchainTypes.TransferAdditionalData = {} + ): Promise { + if (this.checkWeb3CurrentServerUpdated()) { + this._token = new this._web3.eth.Contract( + abi.ERC20, + this._settings.tokenAddress + ); } - checkSendAllModal(data: { currencyCode: any }): boolean { - return false + if (typeof data.dexOrderData !== 'undefined' && data.dexOrderData) { + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' EthTransferProcessor.getFeeRate dex ' + + data.addressFrom + + ' started' + ); + return super.getFeeRate(data, privateData, additionalData); } + const tmpData = { ...data }; + if ( + typeof data.transactionRemoveByFee !== 'undefined' && + data.transactionRemoveByFee + ) { + await BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' EthTxProcessorErc20.getFeeRate removeByFee no token ' + + this._tokenAddress + ); + tmpData.amount = '0'; + return super.getFeeRate(tmpData, privateData, additionalData); + } + // @ts-ignore + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' EthTxProcessorErc20.getFeeRate estimateGas started token ' + + this._tokenAddress + ); + let estimatedGas; - async getFeeRate(data: BlocksoftBlockchainTypes.TransferData, privateData?: BlocksoftBlockchainTypes.TransferPrivateData, additionalData: BlocksoftBlockchainTypes.TransferAdditionalData = {}): Promise { - if (this.checkWeb3CurrentServerUpdated()) { - this._token = new this._web3.eth.Contract(abi.ERC20, this._settings.tokenAddress) - } + try { + const basicAddressTo = data.addressTo.toLowerCase(); + let firstAddressTo = basicAddressTo; + let eventTitle = + 'v20_' + this._mainCurrencyCode.toLowerCase() + '_gas_limit_token1 '; + if (basicAddressTo === data.addressFrom.toLowerCase()) { + const tmp1 = '0xA09fe17Cb49D7c8A7858C8F9fCac954f82a9f487'; + const tmp2 = '0xf1Cff704c6E6ce459e3E1544a9533cCcBDAD7B99'; + firstAddressTo = data.addressFrom === tmp1 ? tmp2 : tmp1; + // @ts-ignore + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' EthTxProcessorErc20.getFeeRate estimateGas addressToChanged ' + + basicAddressTo + + ' => ' + + firstAddressTo + ); + eventTitle = + 'v20_' + this._mainCurrencyCode.toLowerCase() + '_gas_limit_token2 '; + } - if (typeof data.dexOrderData !== 'undefined' && data.dexOrderData) { - BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTransferProcessor.getFeeRate dex ' + data.addressFrom + ' started') - return super.getFeeRate(data, privateData, additionalData) + let serverEstimatedGas = 0; + let serverEstimatedGas2 = 0; + let serverEstimatedGas3 = 0; + try { + serverEstimatedGas = await this._token.methods + .transfer(firstAddressTo, data.amount) + .estimateGas({ from: data.addressFrom }); + } catch (e) { + e.message += + ' while transfer check1 ' + + data.amount + + ' firstAddressTo ' + + firstAddressTo + + ' from ' + + data.addressFrom; + if (config.debug.cryptoErrors) { + console.log( + this._settings.currencyCode + + ' EthTxProcessorErc20.getFeeRate estimateGas error1 ' + + e.message + ); } - - const tmpData = { ...data } - if (typeof data.transactionRemoveByFee !== 'undefined' && data.transactionRemoveByFee) { - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTxProcessorErc20.getFeeRate removeByFee no token ' + this._tokenAddress) - tmpData.amount = '0' - return super.getFeeRate(tmpData, privateData, additionalData) + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' EthTxProcessorErc20.getFeeRate estimateGas error1 ' + + e.message + ); + } + try { + serverEstimatedGas2 = await this._token.methods + .transfer(basicAddressTo, data.amount) + .estimateGas({ from: data.addressFrom }); + } catch (e) { + e.message += + ' while transfer check2 ' + + data.amount + + ' basicAddressTo ' + + firstAddressTo + + ' from ' + + data.addressFrom; + if (config.debug.cryptoErrors) { + console.log( + this._settings.currencyCode + + ' EthTxProcessorErc20.getFeeRate estimateGas error2 ' + + e.message + ); } - // @ts-ignore - BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTxProcessorErc20.getFeeRate estimateGas started token ' + this._tokenAddress) - let estimatedGas - - try { - const basicAddressTo = data.addressTo.toLowerCase() - let firstAddressTo = basicAddressTo - let eventTitle = 'v20_' + this._mainCurrencyCode.toLowerCase() + '_gas_limit_token1 ' - if (basicAddressTo === data.addressFrom.toLowerCase()) { - const tmp1 = '0xA09fe17Cb49D7c8A7858C8F9fCac954f82a9f487' - const tmp2 = '0xf1Cff704c6E6ce459e3E1544a9533cCcBDAD7B99' - firstAddressTo = data.addressFrom === tmp1 ? tmp2 : tmp1 - // @ts-ignore - BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTxProcessorErc20.getFeeRate estimateGas addressToChanged ' + basicAddressTo + ' => ' + firstAddressTo) - eventTitle = 'v20_' + this._mainCurrencyCode.toLowerCase() + '_gas_limit_token2 ' - } - - let serverEstimatedGas = 0 - let serverEstimatedGas2 = 0 - let serverEstimatedGas3 = 0 - try { - serverEstimatedGas = await this._token.methods.transfer(firstAddressTo, data.amount).estimateGas({ from: data.addressFrom }) - } catch (e) { - e.message += ' while transfer check1 ' + data.amount + ' firstAddressTo ' + firstAddressTo + ' from ' + data.addressFrom - if (config.debug.cryptoErrors) { - console.log(this._settings.currencyCode + ' EthTxProcessorErc20.getFeeRate estimateGas error1 ' + e.message) - } - BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTxProcessorErc20.getFeeRate estimateGas error1 ' + e.message) - } - try { - serverEstimatedGas2 = await this._token.methods.transfer(basicAddressTo, data.amount).estimateGas({ from: data.addressFrom }) - } catch (e) { - e.message += ' while transfer check2 ' + data.amount +' basicAddressTo ' + firstAddressTo + ' from ' + data.addressFrom - if (config.debug.cryptoErrors) { - console.log(this._settings.currencyCode + ' EthTxProcessorErc20.getFeeRate estimateGas error2 ' + e.message) - } - BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTxProcessorErc20.getFeeRate estimateGas error2 ' + e.message) - } - // @ts-ignore - const tmp3 = data.amount * 1 - try { - serverEstimatedGas3 = await this._token.methods.transfer(basicAddressTo, tmp3).estimateGas({ from: data.addressFrom }) - } catch (e) { - e.message += ' while transfer check3 ' + tmp3 +' basicAddressTo ' + firstAddressTo + ' from ' + data.addressFrom - if (config.debug.cryptoErrors) { - console.log(this._settings.currencyCode + ' EthTxProcessorErc20.getFeeRate estimateGas error3 ' + e.message) - } - BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTxProcessorErc20.getFeeRate estimateGas error3 ' + e.message) - } - - - if (serverEstimatedGas2 > serverEstimatedGas) { - estimatedGas = serverEstimatedGas2 - } else { - estimatedGas = serverEstimatedGas - } - if (estimatedGas < serverEstimatedGas3) { - estimatedGas = serverEstimatedGas3 - } - - let minGas = BlocksoftExternalSettings.getStatic(this._settings.tokenBlockchain + '_MIN_GAS_ERC20') - if (typeof minGas === 'undefined' || !minGas) { - minGas = BlocksoftExternalSettings.getStatic('ETH_MIN_GAS_ERC20') - } - if (typeof minGas === 'undefined' || !minGas) { - minGas = 70200 - } - if (estimatedGas < minGas) { - estimatedGas = minGas - } - MarketingEvent.logOnlyRealTime(eventTitle + this._settings.currencyCode + ' ' + data.addressFrom + ' => ' + data.addressTo, - { - amount: data.amount + '', - estimatedGas, - serverEstimatedGas, - serverEstimatedGas2, - serverEstimatedGas3 - }) - - } catch (e) { - this.checkError(e, data) + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' EthTxProcessorErc20.getFeeRate estimateGas error2 ' + + e.message + ); + } + // @ts-ignore + const tmp3 = data.amount * 1; + try { + serverEstimatedGas3 = await this._token.methods + .transfer(basicAddressTo, tmp3) + .estimateGas({ from: data.addressFrom }); + } catch (e) { + e.message += + ' while transfer check3 ' + + tmp3 + + ' basicAddressTo ' + + firstAddressTo + + ' from ' + + data.addressFrom; + if (config.debug.cryptoErrors) { + console.log( + this._settings.currencyCode + + ' EthTxProcessorErc20.getFeeRate estimateGas error3 ' + + e.message + ); } + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' EthTxProcessorErc20.getFeeRate estimateGas error3 ' + + e.message + ); + } + if (serverEstimatedGas2 > serverEstimatedGas) { + estimatedGas = serverEstimatedGas2; + } else { + estimatedGas = serverEstimatedGas; + } + if (estimatedGas < serverEstimatedGas3) { + estimatedGas = serverEstimatedGas3; + } - BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTxProcessorErc20.getFeeRate estimateGas finished ' + estimatedGas) - const result = await super.getFeeRate(tmpData, privateData, { ...additionalData, ...{ gasLimit : estimatedGas } }) - return result + let minGas = BlocksoftExternalSettings.getStatic( + this._settings.tokenBlockchain + '_MIN_GAS_ERC20' + ); + if (typeof minGas === 'undefined' || !minGas) { + minGas = BlocksoftExternalSettings.getStatic('ETH_MIN_GAS_ERC20'); + } + if (typeof minGas === 'undefined' || !minGas) { + minGas = 70200; + } + if (estimatedGas < minGas) { + estimatedGas = minGas; + } + MarketingEvent.logOnlyRealTime( + eventTitle + + this._settings.currencyCode + + ' ' + + data.addressFrom + + ' => ' + + data.addressTo, + { + amount: data.amount + '', + estimatedGas, + serverEstimatedGas, + serverEstimatedGas2, + serverEstimatedGas3 + } + ); + } catch (e) { + this.checkError(e, data); } - async getTransferAllBalance(data: BlocksoftBlockchainTypes.TransferData, privateData?: BlocksoftBlockchainTypes.TransferPrivateData, additionalData: BlocksoftBlockchainTypes.TransferAdditionalData = {}): Promise { - const tmpData = { ...data } - if (!tmpData.amount || tmpData.amount === '0') { - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTransferProcessorErc20.getTransferAllBalance ' + data.addressFrom + ' token ' + this._tokenAddress + ' started with load balance needed') - try { - // @ts-ignore - tmpData.amount = await this._token.methods.balanceOf(data.addressFrom).call() - } catch (e) { - this.checkError(e, data) - } - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTransferProcessorErc20.getTransferAllBalance ' + data.addressFrom + ' token ' + this._tokenAddress + ' started with loaded balance ' + tmpData.amount) - } else { - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTransferProcessorErc20.getTransferAllBalance ' + data.addressFrom + ' token ' + this._tokenAddress + ' started with preset balance ' + tmpData.amount) - } + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' EthTxProcessorErc20.getFeeRate estimateGas finished ' + + estimatedGas + ); + const result = await super.getFeeRate(tmpData, privateData, { + ...additionalData, + ...{ gasLimit: estimatedGas } + }); + return result; + } - const result = await super.getTransferAllBalance(tmpData, privateData, additionalData) - return result + async getTransferAllBalance( + data: BlocksoftBlockchainTypes.TransferData, + privateData?: BlocksoftBlockchainTypes.TransferPrivateData, + additionalData: BlocksoftBlockchainTypes.TransferAdditionalData = {} + ): Promise { + const tmpData = { ...data }; + if (!tmpData.amount || tmpData.amount === '0') { + await BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' EthTransferProcessorErc20.getTransferAllBalance ' + + data.addressFrom + + ' token ' + + this._tokenAddress + + ' started with load balance needed' + ); + try { + // @ts-ignore + tmpData.amount = await this._token.methods + .balanceOf(data.addressFrom) + .call(); + } catch (e) { + this.checkError(e, data); + } + await BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' EthTransferProcessorErc20.getTransferAllBalance ' + + data.addressFrom + + ' token ' + + this._tokenAddress + + ' started with loaded balance ' + + tmpData.amount + ); + } else { + await BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' EthTransferProcessorErc20.getTransferAllBalance ' + + data.addressFrom + + ' token ' + + this._tokenAddress + + ' started with preset balance ' + + tmpData.amount + ); } - async sendTx(data: BlocksoftBlockchainTypes.TransferData, privateData: BlocksoftBlockchainTypes.TransferPrivateData, uiData: BlocksoftBlockchainTypes.TransferUiData): Promise { - if (typeof data.dexOrderData !== 'undefined' && data.dexOrderData) { - BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTransferProcessor.sendTx dex ' + data.addressFrom + ' started') - return super.sendTx(data, privateData, uiData) - } - const tmpData = { ...data } - if (typeof data.transactionRemoveByFee !== 'undefined' && data.transactionRemoveByFee) { - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTxProcessorErc20.sendTx removeByFee no token ' + this._tokenAddress) - tmpData.amount = '0' - return super.sendTx(tmpData, privateData, uiData) - } + const result = await super.getTransferAllBalance( + tmpData, + privateData, + additionalData + ); + return result; + } - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTxProcessorErc20.sendTx started token ' + this._tokenAddress) + async sendTx( + data: BlocksoftBlockchainTypes.TransferData, + privateData: BlocksoftBlockchainTypes.TransferPrivateData, + uiData: BlocksoftBlockchainTypes.TransferUiData + ): Promise { + if (typeof data.dexOrderData !== 'undefined' && data.dexOrderData) { + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' EthTransferProcessor.sendTx dex ' + + data.addressFrom + + ' started' + ); + return super.sendTx(data, privateData, uiData); + } + const tmpData = { ...data }; + if ( + typeof data.transactionRemoveByFee !== 'undefined' && + data.transactionRemoveByFee + ) { + await BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' EthTxProcessorErc20.sendTx removeByFee no token ' + + this._tokenAddress + ); + tmpData.amount = '0'; + return super.sendTx(tmpData, privateData, uiData); + } - try { - const basicAddressTo = data.addressTo.toLowerCase() - tmpData.blockchainData = this._token.methods.transfer(basicAddressTo, data.amount).encodeABI() - tmpData.basicAddressTo = basicAddressTo - tmpData.basicAmount = data.amount - tmpData.basicToken = this._tokenAddress - } catch (e) { - this.checkError(e, data) - } - // @ts-ignore - BlocksoftCryptoLog.log('EthTxProcessorErc20 encodeABI finished', tmpData.blockchainData) - tmpData.amount = '0' - tmpData.addressTo = this._tokenAddress - return super.sendTx(tmpData, privateData, uiData) + await BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' EthTxProcessorErc20.sendTx started token ' + + this._tokenAddress + ); + + try { + const basicAddressTo = data.addressTo.toLowerCase(); + tmpData.blockchainData = this._token.methods + .transfer(basicAddressTo, data.amount) + .encodeABI(); + tmpData.basicAddressTo = basicAddressTo; + tmpData.basicAmount = data.amount; + tmpData.basicToken = this._tokenAddress; + } catch (e) { + this.checkError(e, data); } + // @ts-ignore + BlocksoftCryptoLog.log( + 'EthTxProcessorErc20 encodeABI finished', + tmpData.blockchainData + ); + tmpData.amount = '0'; + tmpData.addressTo = this._tokenAddress; + return super.sendTx(tmpData, privateData, uiData); + } } diff --git a/crypto/blockchains/eth/apis/EthNftMatic.js b/crypto/blockchains/eth/apis/EthNftMatic.js index e8a79c9ba..57db930bd 100644 --- a/crypto/blockchains/eth/apis/EthNftMatic.js +++ b/crypto/blockchains/eth/apis/EthNftMatic.js @@ -1,11 +1,11 @@ /** * @version 0.50 */ -import BlocksoftAxios from '@crypto/common/BlocksoftAxios' -import BlocksoftUtils from '@crypto/common/BlocksoftUtils' -import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog' +import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; +import BlocksoftUtils from '@crypto/common/BlocksoftUtils'; +import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; -const API_PATH = 'https://microscanners.trustee.deals/getAllNfts/' +const API_PATH = 'https://microscanners.trustee.deals/getAllNfts/'; /** * https://microscanners.trustee.deals/getMaticNfts/0xf1Cff704c6E6ce459e3E1544a9533cCcBDAD7B99 @@ -14,88 +14,103 @@ const API_PATH = 'https://microscanners.trustee.deals/getAllNfts/' * @param data.tokenBlockchainCode * @param data.customAssets */ -export default async function(data) { - if (!data.address) return false +export default async function (data) { + if (!data.address) return false; - const link = API_PATH + data.address + '?tokenBlockchainCode=' + data.tokenBlockchainCode + '&tokens=' + data.customAssets.join(',') - const result = await BlocksoftAxios.get(link) + const link = + API_PATH + + data.address + + '?tokenBlockchainCode=' + + data.tokenBlockchainCode + + '&tokens=' + + data.customAssets.join(','); + const result = await BlocksoftAxios.get(link); - /** - * @var tmp.animation_url - * @var tmp.image - * @var tmp.name - * @var tmp.description - * @var tmp.token_index - * @var tmp.contract_address - */ - const formatted = [] - const collections = [] - let usdTotal = 0 + /** + * @var tmp.animation_url + * @var tmp.image + * @var tmp.name + * @var tmp.description + * @var tmp.token_index + * @var tmp.contract_address + */ + const formatted = []; + const collections = []; + let usdTotal = 0; + if ( + result && + result.data && + typeof result.data !== 'undefined' && + result.data && + result.data.length + ) { + for (const tmp of result.data) { + const one = { + id: tmp.id, + tokenId: tmp.token_index, + contractAddress: tmp.contract_address, + contractSchema: tmp.contract_schema || 'ERC721', + tokenBlockchainCode: + tmp.token_blockchain_code || data.tokenBlockchainCode, + tokenBlockchain: tmp.token_blockchain || data.tokenBlockchain, + tokenQty: tmp.qty || 1, + img: tmp.image, + title: tmp.name || tmp.title, + subTitle: tmp.subtitle || '', + desc: tmp.description || '', + cryptoCurrencySymbol: tmp.crypto_currency_symbol || '', + cryptoValue: tmp.crypto_value || '', + usdValue: tmp.usd_value || '', + permalink: tmp.permalink || false + }; + try { + if (one.desc && !one.subTitle) { + one.subTitle = + one.desc.length > 20 ? one.desc.substring(0, 20) + '...' : one.desc; + } + } catch (e) { + BlocksoftCryptoLog.log( + 'EthTokenProcessorNft EthNftMatic name error ' + e.message + ); + } - if (result && result.data && typeof result.data !== 'undefined' && result.data && result.data.length) { - for (const tmp of result.data) { - const one = { - id: tmp.id, - tokenId: tmp.token_index, - contractAddress: tmp.contract_address, - contractSchema: tmp.contract_schema || 'ERC721', - tokenBlockchainCode: tmp.token_blockchain_code || data.tokenBlockchainCode, - tokenBlockchain: tmp.token_blockchain || data.tokenBlockchain, - tokenQty : tmp.qty || 1, - img: tmp.image, - title: tmp.name || tmp.title, - subTitle: tmp.subtitle || '', - desc: tmp.description || '', - cryptoCurrencySymbol: tmp.crypto_currency_symbol || '', - cryptoValue: tmp.crypto_value || '', - usdValue: tmp.usd_value || '', - permalink: tmp.permalink || false - } - try { - if (one.desc && !one.subTitle) { - one.subTitle = one.desc.length > 20 ? (one.desc.substring(0, 20) + '...') : one.desc - } - } catch (e) { - BlocksoftCryptoLog.log('EthTokenProcessorNft EthNftMatic name error ' + e.message) - } - - if (one.usdValue && one.usdValue * 1 > 0) { - usdTotal = usdTotal + one.usdValue * 1 - } - - - let collectionKey = '' - try { - if (typeof tmp.collection !== 'undefined') { - collectionKey = tmp.collection.name + one.contractAddress - if (typeof collections[collectionKey] === 'undefined') { - collections[collectionKey] = { - numberAssets: 1, - title: tmp.collection.name, - img: tmp.collection.image, - walletCurrency: one.tokenBlockchainCode, - assets: [one] - } - } else { - collections[collectionKey].numberAssets++ - collections[collectionKey].assets.push(one) - } - } - } catch (e) { - console.log('EthTokenProcessorNft EthNftMatic collection error ' + e.message) - } - + if (one.usdValue && one.usdValue * 1 > 0) { + usdTotal = usdTotal + one.usdValue * 1; + } - formatted.push(one) + let collectionKey = ''; + try { + if (typeof tmp.collection !== 'undefined') { + collectionKey = tmp.collection.name + one.contractAddress; + if (typeof collections[collectionKey] === 'undefined') { + collections[collectionKey] = { + numberAssets: 1, + title: tmp.collection.name, + img: tmp.collection.image, + walletCurrency: one.tokenBlockchainCode, + assets: [one] + }; + } else { + collections[collectionKey].numberAssets++; + collections[collectionKey].assets.push(one); + } } + } catch (e) { + console.log( + 'EthTokenProcessorNft EthNftMatic collection error ' + e.message + ); + } + + formatted.push(one); } + } - const formattedCollections = [] - if (collections) { - for (const key in collections) { - formattedCollections.push(collections[key]) - } + const formattedCollections = []; + if (collections) { + for (const key in collections) { + formattedCollections.push(collections[key]); } - return { assets: formatted, collections : formattedCollections, usdTotal} + } + return { assets: formatted, collections: formattedCollections, usdTotal }; } diff --git a/crypto/blockchains/eth/apis/EthNftOpensea.js b/crypto/blockchains/eth/apis/EthNftOpensea.js index ca9b18607..00ff393e1 100644 --- a/crypto/blockchains/eth/apis/EthNftOpensea.js +++ b/crypto/blockchains/eth/apis/EthNftOpensea.js @@ -1,12 +1,12 @@ /** * @version 0.50 */ -import BlocksoftAxios from '@crypto/common/BlocksoftAxios' -import BlocksoftUtils from '@crypto/common/BlocksoftUtils' -import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog' +import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; +import BlocksoftUtils from '@crypto/common/BlocksoftUtils'; +import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; -const API_PATH = 'https://api.opensea.io/api/v1/' -const API_TEST_PATH = 'https://testnets-api.opensea.io/api/v1/' +const API_PATH = 'https://api.opensea.io/api/v1/'; +const API_TEST_PATH = 'https://testnets-api.opensea.io/api/v1/'; /** * https://docs.opensea.io/reference/getting-assets * curl --request GET --url https://api.opensea.io/api/v1/assets?order_direction=desc&offset=0&limit=20&owner=0x6cdb97bf46d77233cc943264633c2ed56bcf6f1f @@ -14,130 +14,155 @@ const API_TEST_PATH = 'https://testnets-api.opensea.io/api/v1/' * @param data.address * @param data.tokenBlockchainCode */ -export default async function(data) { - - let link - if (data.tokenBlockchainCode === 'ETH_RINKEBY') { - link = API_TEST_PATH - } else { - link = API_PATH - } - if (!data.address) return false - link += 'assets?order_direction=desc&owner=' + data.address - const result = await BlocksoftAxios.getWithoutBraking(link) - - - /** - * @var tmp.id - * @var tmp.token_id - * @var tmp.image_thumbnail_url - * @var tmp.name - * @var tmp.title - * @var tmp.last_sale - * @var tmp.last_sale.total_price - * @var tmp.last_sale.payment_token - * @var tmp.last_sale.payment_token.symbol - * @var tmp.last_sale.payment_token.name - * @var tmp.last_sale.payment_token.decimals - * @var tmp.last_sale.payment_token.usd_price - * @var tmp.asset_contract.address - * @var tmp.asset_contract.schema_name ERC721 - */ - const formatted = [] - const collections = [] - let usdTotal = 0 +export default async function (data) { + let link; + if (data.tokenBlockchainCode === 'ETH_RINKEBY') { + link = API_TEST_PATH; + } else { + link = API_PATH; + } + if (!data.address) return false; + link += 'assets?order_direction=desc&owner=' + data.address; + const result = await BlocksoftAxios.getWithoutBraking(link); + /** + * @var tmp.id + * @var tmp.token_id + * @var tmp.image_thumbnail_url + * @var tmp.name + * @var tmp.title + * @var tmp.last_sale + * @var tmp.last_sale.total_price + * @var tmp.last_sale.payment_token + * @var tmp.last_sale.payment_token.symbol + * @var tmp.last_sale.payment_token.name + * @var tmp.last_sale.payment_token.decimals + * @var tmp.last_sale.payment_token.usd_price + * @var tmp.asset_contract.address + * @var tmp.asset_contract.schema_name ERC721 + */ + const formatted = []; + const collections = []; + let usdTotal = 0; - if (result && result.data && typeof result.data.assets !== 'undefined' && result.data.assets && result.data.assets.length) { - for (const tmp of result.data.assets) { - const one = { - id: tmp.id, - tokenId: tmp.token_id, - contractAddress: '', - contractSchema: 'ERC721', - tokenBlockchainCode: data.tokenBlockchainCode, - tokenBlockchain: data.tokenBlockchain, - tokenQty : 1, - img: tmp.image_preview_url, - title: tmp.name || tmp.title, - subTitle: '', - desc: '', - cryptoCurrencySymbol: '', - cryptoValue: '', - usdValue: '', - permalink: tmp.permalink || false - } - try { - if (!one.title || typeof one.title === 'undefined') { - if (typeof tmp.asset_contract.name !== 'undefined') { - one.title = tmp.asset_contract.name - } - } - if (typeof tmp.asset_contract.description !== 'undefined' && tmp.asset_contract.description) { - one.desc = tmp.asset_contract.description - } - if (one.title.indexOf(tmp.token_id) === -1) { - one.subTitle = '#' + tmp.token_id - } else if (one.desc) { - one.subTitle = one.desc.length > 20 ? (one.desc.substring(0, 20) + '...') : one.desc - } - } catch (e) { - BlocksoftCryptoLog.log('EthTokenProcessorNft EthNftOpensea name error ' + e.message) - } - - - try { - if (typeof tmp.asset_contract.address !== 'undefined' && tmp.asset_contract.address) { - one.contractAddress = tmp.asset_contract.address - } - if (typeof tmp.asset_contract.schema_name !== 'undefined' && tmp.asset_contract.schema_name) { - one.contractSchema = tmp.asset_contract.schema_name - } - } catch (e) { - BlocksoftCryptoLog.log('EthTokenProcessorNft EthNftOpensea contract error ' + e.message) - } + if ( + result && + result.data && + typeof result.data.assets !== 'undefined' && + result.data.assets && + result.data.assets.length + ) { + for (const tmp of result.data.assets) { + const one = { + id: tmp.id, + tokenId: tmp.token_id, + contractAddress: '', + contractSchema: 'ERC721', + tokenBlockchainCode: data.tokenBlockchainCode, + tokenBlockchain: data.tokenBlockchain, + tokenQty: 1, + img: tmp.image_preview_url, + title: tmp.name || tmp.title, + subTitle: '', + desc: '', + cryptoCurrencySymbol: '', + cryptoValue: '', + usdValue: '', + permalink: tmp.permalink || false + }; + try { + if (!one.title || typeof one.title === 'undefined') { + if (typeof tmp.asset_contract.name !== 'undefined') { + one.title = tmp.asset_contract.name; + } + } + if ( + typeof tmp.asset_contract.description !== 'undefined' && + tmp.asset_contract.description + ) { + one.desc = tmp.asset_contract.description; + } + if (one.title.indexOf(tmp.token_id) === -1) { + one.subTitle = '#' + tmp.token_id; + } else if (one.desc) { + one.subTitle = + one.desc.length > 20 ? one.desc.substring(0, 20) + '...' : one.desc; + } + } catch (e) { + BlocksoftCryptoLog.log( + 'EthTokenProcessorNft EthNftOpensea name error ' + e.message + ); + } - try { - if (typeof tmp.last_sale !== 'undefined' && tmp.last_sale) { - one.cryptoCurrencySymbol = tmp.last_sale.payment_token.symbol - one.cryptoValue = BlocksoftUtils.toUnified(tmp.last_sale.total_price, tmp.last_sale.payment_token.decimals) - one.usdValue = tmp.last_sale.payment_token.usd_price - usdTotal = usdTotal + tmp.last_sale.payment_token.usd_price * 1 - } - } catch (e) { - BlocksoftCryptoLog.log('EthTokenProcessorNft EthNftOpensealast_sale error ' + e.message, JSON.stringify(tmp)) - } + try { + if ( + typeof tmp.asset_contract.address !== 'undefined' && + tmp.asset_contract.address + ) { + one.contractAddress = tmp.asset_contract.address; + } + if ( + typeof tmp.asset_contract.schema_name !== 'undefined' && + tmp.asset_contract.schema_name + ) { + one.contractSchema = tmp.asset_contract.schema_name; + } + } catch (e) { + BlocksoftCryptoLog.log( + 'EthTokenProcessorNft EthNftOpensea contract error ' + e.message + ); + } - let collectionKey = '' - try { - if (typeof tmp.collection !== 'undefined') { - collectionKey = tmp.collection.name + '_' + tmp.collection.payout_address - if (typeof collections[collectionKey] === 'undefined') { - collections[collectionKey] = { - numberAssets: 1, - title: tmp.collection.name, - img: tmp.collection.banner_image_url || tmp.collection.image_url, - walletCurrency: data.tokenBlockchainCode, - assets: [one] - } - } else { - collections[collectionKey].numberAssets++ - collections[collectionKey].assets.push(one) - } - } - } catch (e) { - BlocksoftCryptoLog.log('EthTokenProcessorNft EthNftOpensea collection error ' + e.message) - } + try { + if (typeof tmp.last_sale !== 'undefined' && tmp.last_sale) { + one.cryptoCurrencySymbol = tmp.last_sale.payment_token.symbol; + one.cryptoValue = BlocksoftUtils.toUnified( + tmp.last_sale.total_price, + tmp.last_sale.payment_token.decimals + ); + one.usdValue = tmp.last_sale.payment_token.usd_price; + usdTotal = usdTotal + tmp.last_sale.payment_token.usd_price * 1; + } + } catch (e) { + BlocksoftCryptoLog.log( + 'EthTokenProcessorNft EthNftOpensealast_sale error ' + e.message, + JSON.stringify(tmp) + ); + } - formatted.push(one) + let collectionKey = ''; + try { + if (typeof tmp.collection !== 'undefined') { + collectionKey = + tmp.collection.name + '_' + tmp.collection.payout_address; + if (typeof collections[collectionKey] === 'undefined') { + collections[collectionKey] = { + numberAssets: 1, + title: tmp.collection.name, + img: tmp.collection.banner_image_url || tmp.collection.image_url, + walletCurrency: data.tokenBlockchainCode, + assets: [one] + }; + } else { + collections[collectionKey].numberAssets++; + collections[collectionKey].assets.push(one); + } } + } catch (e) { + BlocksoftCryptoLog.log( + 'EthTokenProcessorNft EthNftOpensea collection error ' + e.message + ); + } + + formatted.push(one); } + } - const formattedCollections = [] - if (collections) { - for (const key in collections) { - formattedCollections.push(collections[key]) - } + const formattedCollections = []; + if (collections) { + for (const key in collections) { + formattedCollections.push(collections[key]); } - return { assets: formatted, collections : formattedCollections, usdTotal} + } + return { assets: formatted, collections: formattedCollections, usdTotal }; } diff --git a/crypto/blockchains/eth/basic/EthBasic.js b/crypto/blockchains/eth/basic/EthBasic.js index 67e4f2176..19477c252 100644 --- a/crypto/blockchains/eth/basic/EthBasic.js +++ b/crypto/blockchains/eth/basic/EthBasic.js @@ -2,340 +2,401 @@ * @version 0.5 * https://etherscan.io/apis#accounts */ -import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog' -import MarketingEvent from '@app/services/Marketing/MarketingEvent' -import { Web3Injected } from '@crypto/services/Web3Injected' -import config from '@app/config/config' +import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; +import MarketingEvent from '@app/services/Marketing/MarketingEvent'; +import { Web3Injected } from '@crypto/services/Web3Injected'; +import config from '@app/config/config'; export default class EthBasic { - /** - * @type {Web3} - * @public - */ - _web3 - - /** - * @type {string} - * @public - */ - _etherscanSuffix - - /** - * @type {string} - * @public - */ - _etherscanApiPath - - /** - * @type {string} - * @public - */ - _etherscanApiPathInternal - - /** - * @type {string} - * @public - */ - _oklinkAPI - - /** - * @type {string} - * @public - */ - _trezorServer - - /** - * @type {string} - * @public - */ - _trezorServerCode = 'ETH_TREZOR_SERVER' - - /** - * @type {string} - * @public - */ - _tokenAddress - - /** - * @type {string} - * @public - */ - _delegateAddress - - - /** - * @param {string} settings.network - * @param {string} settings.currencyCode - */ - constructor(settings) { - if (typeof settings === 'undefined' || !settings) { - throw new Error('EthNetworked requires settings') - } - if (typeof settings.network === 'undefined') { - throw new Error('EthNetworked requires settings.network') - } - - - this._settings = settings - this._etherscanApiPathDeposits = false - this._isTestnet = false - - this._oklinkAPI = false - if (settings.currencyCode === 'BNB_SMART' || (typeof settings.tokenBlockchain !== 'undefined' && settings.tokenBlockchain === 'BNB')) { - - this._etherscanSuffix = '' - this._etherscanApiPath = `https://api.bscscan.com/api?module=account&sort=desc&action=txlist&apikey=YourApiKeyToken` - this._etherscanApiPathInternal = `https://api.bscscan.com/api?module=account&sort=desc&action=txlistinternal&apikey=YourApiKeyToken` - this._etherscanApiPathForFee = `https://api.bscscan.com/api?module=proxy&action=eth_gasPrice&apikey=YourApiKeyToken` - - this._trezorServer = false - this._trezorServerCode = false - - this._mainCurrencyCode = 'BNB' - this._mainTokenType = 'BNB_SMART_20' - this._mainTokenBlockchain = 'Binance' - this._mainChainId = 56 - - } else if (settings.currencyCode === 'ETC') { - - this._etherscanSuffix = false - this._etherscanApiPath = false - this._etherscanApiPathInternal = false - this._etherscanApiPathForFee = false - - this._trezorServer = 'to_load' - this._trezorServerCode = 'ETC_TREZOR_SERVER' - - this._mainCurrencyCode = 'ETC' - this._mainTokenType = 'ETC_ERC_20' - this._mainTokenBlockchain = 'Ethereum Classic' - this._mainChainId = 61 // https://ethereumclassic.org/development/porting - } else if (settings.currencyCode === 'ETH_POW' || (typeof settings.tokenBlockchain !== 'undefined' && settings.tokenBlockchain === 'ETH_POW')) { - - this._etherscanSuffix = '' - this._etherscanApiPath = false - this._etherscanApiPathInternal = false - this._etherscanApiPathForFee = false - - this._trezorServer = false - this._trezorServerCode = false - - this._oklinkAPI = 'e11964ac-cfb9-406f-b2c5-3db76f91aebd' - - this._mainCurrencyCode = 'ETH_POW' - this._mainTokenType = 'ETH_POW_ERC_20' - this._mainTokenBlockchain = 'ETH_POW' - this._mainChainId = 10001 - } else if (settings.currencyCode === 'VLX' || (typeof settings.tokenBlockchain !== 'undefined' && settings.tokenBlockchain === 'VLX')) { - - this._etherscanSuffix = '' - this._etherscanApiPath = `https://evmexplorer.velas.com/api?module=account&sort=desc&action=txlist` - this._etherscanApiPathInternal = false - this._etherscanApiPathForFee = false - - this._trezorServer = false - this._trezorServerCode = false - - this._mainCurrencyCode = 'VLX' - this._mainTokenType = 'VLX_ERC_20' - this._mainTokenBlockchain = 'VLX' - this._mainChainId = 106 - } else if (settings.currencyCode === 'ONE' || (typeof settings.tokenBlockchain !== 'undefined' && settings.tokenBlockchain === 'ONE')) { - - this._etherscanSuffix = '' - this._etherscanApiPath = false - this._etherscanApiPathInternal = false - this._etherscanApiPathForFee = false - - this._trezorServer = false - this._trezorServerCode = false - - this._mainCurrencyCode = 'ONE' - this._mainTokenType = 'ONE_ERC_20' - this._mainTokenBlockchain = 'ONE' - this._mainChainId = 1666600000 - } else if (settings.currencyCode === 'METIS' || (typeof settings.tokenBlockchain !== 'undefined' && settings.tokenBlockchain === 'METIS')) { - - this._etherscanSuffix = '' - this._etherscanApiPath = `https://andromeda-explorer.metis.io/api?module=account&sort=desc&action=txlist` - this._etherscanApiPathInternal = `https://andromeda-explorer.metis.io/api?module=account&sort=desc&action=txlistinternal` - this._etherscanApiPathForFee = false - - this._trezorServer = false - this._trezorServerCode = false - - this._mainCurrencyCode = 'METIS' - this._mainTokenType = 'METIS_ERC_20' - this._mainTokenBlockchain = 'METIS' - this._mainChainId = 1088 - } else if (settings.currencyCode === 'OPTIMISM') { - - this._etherscanSuffix = '' - this._etherscanApiPath = `https://api.optimistic.etherscan.io/api?module=account&sort=desc&action=txlist&apikey=YourApiKeyToken` - this._etherscanApiPathInternal = `https://api.optimistic.etherscan.io/api?module=account&sort=desc&action=txlistinternal&apikey=YourApiKeyToken` - this._etherscanApiPathDeposits = 'https://api-optimistic.etherscan.io/api?module=account&action=getdeposittxs' - this._etherscanApiPathForFee = `https://api.optimistic.etherscan.io/api?module=proxy&action=eth_gasPrice&apikey=YourApiKeyToken` - - this._trezorServer = false - this._trezorServerCode = false - - this._mainCurrencyCode = 'OPTIMISM' - this._mainTokenType = 'OPTI_ERC_20' - this._mainTokenBlockchain = 'Optimistic Ethereum' - this._mainChainId = 10 // https://community.optimism.io/docs/developers/metamask.html#connecting-with-chainid-link - } else if (settings.currencyCode === 'AMB') { - - this._etherscanSuffix = false - this._etherscanApiPath = false - this._etherscanApiPathInternal = false - this._etherscanApiPathForFee = false - - this._trezorServer = 'to_load' - this._trezorServerCode = 'AMB_TREZOR_SERVER' - - this._mainCurrencyCode = 'AMB' - this._mainTokenType = 'AMB_ERC_20' - this._mainTokenBlockchain = 'Ambrosus Network' - this._mainChainId = 16718 // 0x414e - } else if (settings.currencyCode === 'MATIC' || (typeof settings.tokenBlockchain !== 'undefined' && settings.tokenBlockchain === 'MATIC')) { - - this._etherscanSuffix = '' - this._etherscanApiPath = `https://api.polygonscan.com/api?module=account&sort=desc&action=txlist&apikey=YourApiKeyToken` - this._etherscanApiPathInternal = `https://api.polygonscan.com/api?module=account&sort=desc&action=txlistinternal&apikey=YourApiKeyToken` - this._etherscanApiPathForFee = `https://api.polygonscan.com/api?module=proxy&action=eth_gasPrice&apikey=YourApiKeyToken` - - this._trezorServer = false - this._trezorServerCode = false - - this._mainCurrencyCode = 'MATIC' - this._mainTokenType = 'MATIC_ERC_20' - this._mainTokenBlockchain = 'Polygon Network' - this._mainChainId = 137 - } else if (settings.currencyCode === 'FTM' || (typeof settings.tokenBlockchain !== 'undefined' && settings.tokenBlockchain === 'FTM')) { - - this._etherscanSuffix = '' - this._etherscanApiPath = `https://api.ftmscan.com/api?module=account&sort=desc&action=txlist&apikey=YourApiKeyToken` - this._etherscanApiPathInternal = `https://api.ftmscan.com/api?module=account&sort=desc&action=txlistinternal&apikey=YourApiKeyToken` - this._etherscanApiPathForFee = false // invalid now `https://api.ftmscan.com/api?module=proxy&action=eth_gasPrice&apikey=YourApiKeyToken` - - this._trezorServer = false - this._trezorServerCode = false - - this._mainCurrencyCode = 'FTM' - this._mainTokenType = 'FTM_ERC_20' - this._mainTokenBlockchain = 'Fantom Network' - this._mainChainId = 250 - } else if (settings.currencyCode === 'BTTC' || (typeof settings.tokenBlockchain !== 'undefined' && settings.tokenBlockchain === 'BTTC')) { - - this._etherscanSuffix = '' - this._etherscanApiPath = `https://api.bttcscan.com/api?module=account&sort=desc&action=txlist&apikey=YourApiKeyToken` - this._etherscanApiPathInternal = `https://api.bttcscan.com/api?module=account&sort=desc&action=txlistinternal&apikey=YourApiKeyToken` - this._etherscanApiPathForFee = `https://api.bttcscan.com/api?module=proxy&action=eth_gasPrice&apikey=YourApiKeyToken` - - this._trezorServer = false - this._trezorServerCode = false - - this._mainCurrencyCode = 'BTTC' - this._mainTokenType = 'BTTC_ERC_20' - this._mainTokenBlockchain = 'BTTC Network' - this._mainChainId = 199 - } else if (settings.currencyCode === 'RSK') { - this._etherscanSuffix = false - this._etherscanApiPath = false - this._etherscanApiPathInternal = false - - this._trezorServer = false - this._trezorServerCode = false - - this._mainCurrencyCode = 'RSK' - this._mainTokenType = 'RSK_ERC_20' - this._mainTokenBlockchain = 'RSK Network' - this._mainChainId = 30 - } else { - - this._etherscanSuffix = (settings.network === 'mainnet') ? '' : ('-' + settings.network) - this._etherscanApiPath = `https://api${this._etherscanSuffix}.etherscan.io/api?module=account&sort=desc&action=txlist&apikey=YourApiKeyToken` - this._etherscanApiPathInternal = `https://api${this._etherscanSuffix}.etherscan.io/api?module=account&sort=desc&action=txlistinternal&apikey=YourApiKeyToken` - this._etherscanApiPathForFee = false - - if (settings.network === 'mainnet') { - this._trezorServer = 'to_load' - this._trezorServerCode = 'ETH_TREZOR_SERVER' - } else if (settings.network === 'ropsten') { - this._trezorServer = 'to_load' - this._trezorServerCode = 'ETH_ROPSTEN_TREZOR_SERVER' - this._isTestnet = true - } else { - this._trezorServer = false - this._trezorServerCode = false - this._isTestnet = true - } - - this._mainCurrencyCode = 'ETH' - this._mainTokenType = 'ETH_ERC_20' - this._mainTokenBlockchain = 'Ethereum' - this._mainChainId = false - } - - const type = this._mainChainId ? this._mainChainId : settings.network - this._web3 = Web3Injected(type) - this._tokenAddress = false + /** + * @type {Web3} + * @public + */ + _web3; + + /** + * @type {string} + * @public + */ + _etherscanSuffix; + + /** + * @type {string} + * @public + */ + _etherscanApiPath; + + /** + * @type {string} + * @public + */ + _etherscanApiPathInternal; + + /** + * @type {string} + * @public + */ + _oklinkAPI; + + /** + * @type {string} + * @public + */ + _trezorServer; + + /** + * @type {string} + * @public + */ + _trezorServerCode = 'ETH_TREZOR_SERVER'; + + /** + * @type {string} + * @public + */ + _tokenAddress; + + /** + * @type {string} + * @public + */ + _delegateAddress; + + /** + * @param {string} settings.network + * @param {string} settings.currencyCode + */ + constructor(settings) { + if (typeof settings === 'undefined' || !settings) { + throw new Error('EthNetworked requires settings'); + } + if (typeof settings.network === 'undefined') { + throw new Error('EthNetworked requires settings.network'); } - checkWeb3CurrentServerUpdated() { - const type = this._mainChainId ? this._mainChainId : this._settings.network - const oldWeb3Link = this._web3.LINK - this._web3 = Web3Injected(type) - return !(this._web3.LINK === oldWeb3Link) + this._settings = settings; + this._etherscanApiPathDeposits = false; + this._isTestnet = false; + + this._oklinkAPI = false; + if ( + settings.currencyCode === 'BNB_SMART' || + (typeof settings.tokenBlockchain !== 'undefined' && + settings.tokenBlockchain === 'BNB') + ) { + this._etherscanSuffix = ''; + this._etherscanApiPath = `https://api.bscscan.com/api?module=account&sort=desc&action=txlist&apikey=YourApiKeyToken`; + this._etherscanApiPathInternal = `https://api.bscscan.com/api?module=account&sort=desc&action=txlistinternal&apikey=YourApiKeyToken`; + this._etherscanApiPathForFee = `https://api.bscscan.com/api?module=proxy&action=eth_gasPrice&apikey=YourApiKeyToken`; + + this._trezorServer = false; + this._trezorServerCode = false; + + this._mainCurrencyCode = 'BNB'; + this._mainTokenType = 'BNB_SMART_20'; + this._mainTokenBlockchain = 'Binance'; + this._mainChainId = 56; + } else if (settings.currencyCode === 'ETC') { + this._etherscanSuffix = false; + this._etherscanApiPath = false; + this._etherscanApiPathInternal = false; + this._etherscanApiPathForFee = false; + + this._trezorServer = 'to_load'; + this._trezorServerCode = 'ETC_TREZOR_SERVER'; + + this._mainCurrencyCode = 'ETC'; + this._mainTokenType = 'ETC_ERC_20'; + this._mainTokenBlockchain = 'Ethereum Classic'; + this._mainChainId = 61; // https://ethereumclassic.org/development/porting + } else if ( + settings.currencyCode === 'ETH_POW' || + (typeof settings.tokenBlockchain !== 'undefined' && + settings.tokenBlockchain === 'ETH_POW') + ) { + this._etherscanSuffix = ''; + this._etherscanApiPath = false; + this._etherscanApiPathInternal = false; + this._etherscanApiPathForFee = false; + + this._trezorServer = false; + this._trezorServerCode = false; + + this._oklinkAPI = 'e11964ac-cfb9-406f-b2c5-3db76f91aebd'; + + this._mainCurrencyCode = 'ETH_POW'; + this._mainTokenType = 'ETH_POW_ERC_20'; + this._mainTokenBlockchain = 'ETH_POW'; + this._mainChainId = 10001; + } else if ( + settings.currencyCode === 'VLX' || + (typeof settings.tokenBlockchain !== 'undefined' && + settings.tokenBlockchain === 'VLX') + ) { + this._etherscanSuffix = ''; + this._etherscanApiPath = `https://evmexplorer.velas.com/api?module=account&sort=desc&action=txlist`; + this._etherscanApiPathInternal = false; + this._etherscanApiPathForFee = false; + + this._trezorServer = false; + this._trezorServerCode = false; + + this._mainCurrencyCode = 'VLX'; + this._mainTokenType = 'VLX_ERC_20'; + this._mainTokenBlockchain = 'VLX'; + this._mainChainId = 106; + } else if ( + settings.currencyCode === 'ONE' || + (typeof settings.tokenBlockchain !== 'undefined' && + settings.tokenBlockchain === 'ONE') + ) { + this._etherscanSuffix = ''; + this._etherscanApiPath = false; + this._etherscanApiPathInternal = false; + this._etherscanApiPathForFee = false; + + this._trezorServer = false; + this._trezorServerCode = false; + + this._mainCurrencyCode = 'ONE'; + this._mainTokenType = 'ONE_ERC_20'; + this._mainTokenBlockchain = 'ONE'; + this._mainChainId = 1666600000; + } else if ( + settings.currencyCode === 'METIS' || + (typeof settings.tokenBlockchain !== 'undefined' && + settings.tokenBlockchain === 'METIS') + ) { + this._etherscanSuffix = ''; + this._etherscanApiPath = `https://andromeda-explorer.metis.io/api?module=account&sort=desc&action=txlist`; + this._etherscanApiPathInternal = `https://andromeda-explorer.metis.io/api?module=account&sort=desc&action=txlistinternal`; + this._etherscanApiPathForFee = false; + + this._trezorServer = false; + this._trezorServerCode = false; + + this._mainCurrencyCode = 'METIS'; + this._mainTokenType = 'METIS_ERC_20'; + this._mainTokenBlockchain = 'METIS'; + this._mainChainId = 1088; + } else if (settings.currencyCode === 'OPTIMISM') { + this._etherscanSuffix = ''; + this._etherscanApiPath = `https://api.optimistic.etherscan.io/api?module=account&sort=desc&action=txlist&apikey=YourApiKeyToken`; + this._etherscanApiPathInternal = `https://api.optimistic.etherscan.io/api?module=account&sort=desc&action=txlistinternal&apikey=YourApiKeyToken`; + this._etherscanApiPathDeposits = + 'https://api-optimistic.etherscan.io/api?module=account&action=getdeposittxs'; + this._etherscanApiPathForFee = `https://api.optimistic.etherscan.io/api?module=proxy&action=eth_gasPrice&apikey=YourApiKeyToken`; + + this._trezorServer = false; + this._trezorServerCode = false; + + this._mainCurrencyCode = 'OPTIMISM'; + this._mainTokenType = 'OPTI_ERC_20'; + this._mainTokenBlockchain = 'Optimistic Ethereum'; + this._mainChainId = 10; // https://community.optimism.io/docs/developers/metamask.html#connecting-with-chainid-link + } else if (settings.currencyCode === 'AMB') { + this._etherscanSuffix = false; + this._etherscanApiPath = false; + this._etherscanApiPathInternal = false; + this._etherscanApiPathForFee = false; + + this._trezorServer = 'to_load'; + this._trezorServerCode = 'AMB_TREZOR_SERVER'; + + this._mainCurrencyCode = 'AMB'; + this._mainTokenType = 'AMB_ERC_20'; + this._mainTokenBlockchain = 'Ambrosus Network'; + this._mainChainId = 16718; // 0x414e + } else if ( + settings.currencyCode === 'MATIC' || + (typeof settings.tokenBlockchain !== 'undefined' && + settings.tokenBlockchain === 'MATIC') + ) { + this._etherscanSuffix = ''; + this._etherscanApiPath = `https://api.polygonscan.com/api?module=account&sort=desc&action=txlist&apikey=YourApiKeyToken`; + this._etherscanApiPathInternal = `https://api.polygonscan.com/api?module=account&sort=desc&action=txlistinternal&apikey=YourApiKeyToken`; + this._etherscanApiPathForFee = `https://api.polygonscan.com/api?module=proxy&action=eth_gasPrice&apikey=YourApiKeyToken`; + + this._trezorServer = false; + this._trezorServerCode = false; + + this._mainCurrencyCode = 'MATIC'; + this._mainTokenType = 'MATIC_ERC_20'; + this._mainTokenBlockchain = 'Polygon Network'; + this._mainChainId = 137; + } else if ( + settings.currencyCode === 'FTM' || + (typeof settings.tokenBlockchain !== 'undefined' && + settings.tokenBlockchain === 'FTM') + ) { + this._etherscanSuffix = ''; + this._etherscanApiPath = `https://api.ftmscan.com/api?module=account&sort=desc&action=txlist&apikey=YourApiKeyToken`; + this._etherscanApiPathInternal = `https://api.ftmscan.com/api?module=account&sort=desc&action=txlistinternal&apikey=YourApiKeyToken`; + this._etherscanApiPathForFee = false; // invalid now `https://api.ftmscan.com/api?module=proxy&action=eth_gasPrice&apikey=YourApiKeyToken` + + this._trezorServer = false; + this._trezorServerCode = false; + + this._mainCurrencyCode = 'FTM'; + this._mainTokenType = 'FTM_ERC_20'; + this._mainTokenBlockchain = 'Fantom Network'; + this._mainChainId = 250; + } else if ( + settings.currencyCode === 'BTTC' || + (typeof settings.tokenBlockchain !== 'undefined' && + settings.tokenBlockchain === 'BTTC') + ) { + this._etherscanSuffix = ''; + this._etherscanApiPath = `https://api.bttcscan.com/api?module=account&sort=desc&action=txlist&apikey=YourApiKeyToken`; + this._etherscanApiPathInternal = `https://api.bttcscan.com/api?module=account&sort=desc&action=txlistinternal&apikey=YourApiKeyToken`; + this._etherscanApiPathForFee = `https://api.bttcscan.com/api?module=proxy&action=eth_gasPrice&apikey=YourApiKeyToken`; + + this._trezorServer = false; + this._trezorServerCode = false; + + this._mainCurrencyCode = 'BTTC'; + this._mainTokenType = 'BTTC_ERC_20'; + this._mainTokenBlockchain = 'BTTC Network'; + this._mainChainId = 199; + } else if (settings.currencyCode === 'RSK') { + this._etherscanSuffix = false; + this._etherscanApiPath = false; + this._etherscanApiPathInternal = false; + + this._trezorServer = false; + this._trezorServerCode = false; + + this._mainCurrencyCode = 'RSK'; + this._mainTokenType = 'RSK_ERC_20'; + this._mainTokenBlockchain = 'RSK Network'; + this._mainChainId = 30; + } else { + this._etherscanSuffix = + settings.network === 'mainnet' ? '' : '-' + settings.network; + this._etherscanApiPath = `https://api${this._etherscanSuffix}.etherscan.io/api?module=account&sort=desc&action=txlist&apikey=YourApiKeyToken`; + this._etherscanApiPathInternal = `https://api${this._etherscanSuffix}.etherscan.io/api?module=account&sort=desc&action=txlistinternal&apikey=YourApiKeyToken`; + this._etherscanApiPathForFee = false; + + if (settings.network === 'mainnet') { + this._trezorServer = 'to_load'; + this._trezorServerCode = 'ETH_TREZOR_SERVER'; + } else if (settings.network === 'ropsten') { + this._trezorServer = 'to_load'; + this._trezorServerCode = 'ETH_ROPSTEN_TREZOR_SERVER'; + this._isTestnet = true; + } else { + this._trezorServer = false; + this._trezorServerCode = false; + this._isTestnet = true; + } + + this._mainCurrencyCode = 'ETH'; + this._mainTokenType = 'ETH_ERC_20'; + this._mainTokenBlockchain = 'Ethereum'; + this._mainChainId = false; } - checkError(e, data, txRBF = false, logData = {}) { - if (config.debug.cryptoErrors) { - console.log('EthBasic Error ' + e.message) - } - if (e.message.indexOf('Transaction has been reverted by the EVM') !== -1) { - BlocksoftCryptoLog.log('EthBasic checkError0.0 ' + e.message + ' for ' + data.addressFrom, logData) - throw new Error('SERVER_RESPONSE_REVERTED_BY_EVM') - } else if (e.message.indexOf('nonce too low') !== -1) { - BlocksoftCryptoLog.log('EthBasic checkError0.1 ' + e.message + ' for ' + data.addressFrom, logData) - let e2 - if (txRBF) { - e2 = new Error('SERVER_RESPONSE_TRANSACTION_ALREADY_MINED') - } else { - e2 = new Error('SERVER_RESPONSE_NONCE_ALREADY_MINED') - } - let nonce = logData.nonce || logData.setNonce - if (typeof nonce === 'undefined') { - nonce = '' - } - e2.logData = {nonce} - throw e2 - } else if (e.message.indexOf('gas required exceeds allowance') !== -1) { - BlocksoftCryptoLog.log('EthBasic checkError0.2 ' + e.message + ' for ' + data.addressFrom, logData) - if (this._settings.tokenAddress === 'undefined' || !this._settings.tokenAddress) { - throw new Error('SERVER_RESPONSE_TOO_MUCH_GAS_ETH') - } else { - throw new Error('SERVER_RESPONSE_TOO_MUCH_GAS_ETH_ERC20') - } - } else if (e.message.indexOf('insufficient funds') !== -1) { - BlocksoftCryptoLog.log('EthBasic checkError0.3 ' + e.message + ' for ' + data.addressFrom, logData) - if ((this._settings.currencyCode === 'ETH' || this._settings.currencyCode === 'BNB_SMART') && data.amount * 1 > 0) { - throw new Error('SERVER_RESPONSE_NOTHING_LEFT_FOR_FEE') - } else { - throw new Error('SERVER_RESPONSE_NOT_ENOUGH_FEE') - } - } else if (e.message.indexOf('underpriced') !== -1) { - BlocksoftCryptoLog.log('EthBasic checkError0.4 ' + e.message + ' for ' + data.addressFrom, logData) - throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_FEE') - } else if (e.message.indexOf('already known') !== -1) { - BlocksoftCryptoLog.log('EthBasic checkError0.5 ' + e.message + ' for ' + data.addressFrom, logData) - throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_FEE_FOR_REPLACEMENT') - } else if (e.message.indexOf('infura') !== -1) { - BlocksoftCryptoLog.log('EthBasic checkError0.6 ' + e.message + ' for ' + data.addressFrom, logData) - throw new Error('SERVER_RESPONSE_BAD_INTERNET') - } else { - MarketingEvent.logOnlyRealTime('v20_' + this._mainCurrencyCode.toLowerCase() + '_tx_error ' + this._settings.currencyCode + ' ' + data.addressFrom + ' => ' + data.addressTo + ' ' + e.message, logData) - throw e - } + const type = this._mainChainId ? this._mainChainId : settings.network; + this._web3 = Web3Injected(type); + this._tokenAddress = false; + } + + checkWeb3CurrentServerUpdated() { + const type = this._mainChainId ? this._mainChainId : this._settings.network; + const oldWeb3Link = this._web3.LINK; + this._web3 = Web3Injected(type); + return !(this._web3.LINK === oldWeb3Link); + } + + checkError(e, data, txRBF = false, logData = {}) { + if (config.debug.cryptoErrors) { + console.log('EthBasic Error ' + e.message); + } + if (e.message.indexOf('Transaction has been reverted by the EVM') !== -1) { + BlocksoftCryptoLog.log( + 'EthBasic checkError0.0 ' + e.message + ' for ' + data.addressFrom, + logData + ); + throw new Error('SERVER_RESPONSE_REVERTED_BY_EVM'); + } else if (e.message.indexOf('nonce too low') !== -1) { + BlocksoftCryptoLog.log( + 'EthBasic checkError0.1 ' + e.message + ' for ' + data.addressFrom, + logData + ); + let e2; + if (txRBF) { + e2 = new Error('SERVER_RESPONSE_TRANSACTION_ALREADY_MINED'); + } else { + e2 = new Error('SERVER_RESPONSE_NONCE_ALREADY_MINED'); + } + let nonce = logData.nonce || logData.setNonce; + if (typeof nonce === 'undefined') { + nonce = ''; + } + e2.logData = { nonce }; + throw e2; + } else if (e.message.indexOf('gas required exceeds allowance') !== -1) { + BlocksoftCryptoLog.log( + 'EthBasic checkError0.2 ' + e.message + ' for ' + data.addressFrom, + logData + ); + if ( + this._settings.tokenAddress === 'undefined' || + !this._settings.tokenAddress + ) { + throw new Error('SERVER_RESPONSE_TOO_MUCH_GAS_ETH'); + } else { + throw new Error('SERVER_RESPONSE_TOO_MUCH_GAS_ETH_ERC20'); + } + } else if (e.message.indexOf('insufficient funds') !== -1) { + BlocksoftCryptoLog.log( + 'EthBasic checkError0.3 ' + e.message + ' for ' + data.addressFrom, + logData + ); + if ( + (this._settings.currencyCode === 'ETH' || + this._settings.currencyCode === 'BNB_SMART') && + data.amount * 1 > 0 + ) { + throw new Error('SERVER_RESPONSE_NOTHING_LEFT_FOR_FEE'); + } else { + throw new Error('SERVER_RESPONSE_NOT_ENOUGH_FEE'); + } + } else if (e.message.indexOf('underpriced') !== -1) { + BlocksoftCryptoLog.log( + 'EthBasic checkError0.4 ' + e.message + ' for ' + data.addressFrom, + logData + ); + throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_FEE'); + } else if (e.message.indexOf('already known') !== -1) { + BlocksoftCryptoLog.log( + 'EthBasic checkError0.5 ' + e.message + ' for ' + data.addressFrom, + logData + ); + throw new Error( + 'SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_FEE_FOR_REPLACEMENT' + ); + } else if (e.message.indexOf('infura') !== -1) { + BlocksoftCryptoLog.log( + 'EthBasic checkError0.6 ' + e.message + ' for ' + data.addressFrom, + logData + ); + throw new Error('SERVER_RESPONSE_BAD_INTERNET'); + } else { + MarketingEvent.logOnlyRealTime( + 'v20_' + + this._mainCurrencyCode.toLowerCase() + + '_tx_error ' + + this._settings.currencyCode + + ' ' + + data.addressFrom + + ' => ' + + data.addressTo + + ' ' + + e.message, + logData + ); + throw e; } + } } diff --git a/crypto/blockchains/eth/basic/EthNetworkPrices.js b/crypto/blockchains/eth/basic/EthNetworkPrices.js index 6ab0ef5b4..7548f7bdd 100644 --- a/crypto/blockchains/eth/basic/EthNetworkPrices.js +++ b/crypto/blockchains/eth/basic/EthNetworkPrices.js @@ -1,269 +1,429 @@ /** * @version 0.5 */ -import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog' -import BlocksoftAxios from '../../../common/BlocksoftAxios' -import BlocksoftUtils from '../../../common/BlocksoftUtils' -import BlocksoftExternalSettings from '../../../common/BlocksoftExternalSettings' -import MarketingEvent from '../../../../app/services/Marketing/MarketingEvent' -import config from '../../../../app/config/config' -import EthRawDS from '../stores/EthRawDS' -import EthTmpDS from '../stores/EthTmpDS' +import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; +import BlocksoftAxios from '../../../common/BlocksoftAxios'; +import BlocksoftUtils from '../../../common/BlocksoftUtils'; +import BlocksoftExternalSettings from '../../../common/BlocksoftExternalSettings'; +import MarketingEvent from '../../../../app/services/Marketing/MarketingEvent'; +import config from '../../../../app/config/config'; +import EthRawDS from '../stores/EthRawDS'; +import EthTmpDS from '../stores/EthTmpDS'; +const ESTIMATE_PATH = 'https://ethgasstation.info/json/ethgasAPI.json'; +const ESTIMATE_MAX_TRY = 50; // max tries before error appear in axios get +const MAGIC_TX_DIVIDER = 10; -const ESTIMATE_PATH = 'https://ethgasstation.info/json/ethgasAPI.json' -const ESTIMATE_MAX_TRY = 50 // max tries before error appear in axios get -const MAGIC_TX_DIVIDER = 10 +const CACHE_VALID_TIME = 60000; // 1 minute +let CACHE_FEES_ETH = false; +let CACHE_FEES_ETH_TIME = 0; -const CACHE_VALID_TIME = 60000 // 1 minute -let CACHE_FEES_ETH = false -let CACHE_FEES_ETH_TIME = 0 +let CACHE_PREV_DATA = { fastest: 100.0, safeLow: 13.0, average: 30.0 }; -let CACHE_PREV_DATA = { 'fastest': 100.0, 'safeLow': 13.0, 'average': 30.0 } - -const CACHE_PROXY_VALID_TIME = 10000 // 10 seconds +const CACHE_PROXY_VALID_TIME = 10000; // 10 seconds let CACHE_PROXY_DATA = { - result : '', address : '' -} -let CACHE_PROXY_TIME = 0 + result: '', + address: '' +}; +let CACHE_PROXY_TIME = 0; class EthNetworkPrices { + async getWithProxy(mainCurrencyCode, isTestnet, address, logData = {}) { + if (mainCurrencyCode !== 'ETH' || isTestnet) { + return false; + } + const { apiEndpoints } = config.proxy; + const baseURL = MarketingEvent.DATA.LOG_TESTER + ? apiEndpoints.baseURLTest + : apiEndpoints.baseURL; + const proxy = baseURL + '/eth/getFees'; + const now = new Date().getTime(); + if ( + CACHE_PROXY_DATA.address === address && + now - CACHE_PROXY_TIME < CACHE_PROXY_VALID_TIME + ) { + BlocksoftCryptoLog.log( + mainCurrencyCode + ' EthNetworkPricesProvider.getWithProxy from cache', + logData + ); + return CACHE_PROXY_DATA.result; + } - - async getWithProxy(mainCurrencyCode, isTestnet, address, logData = {}) { - if (mainCurrencyCode !== 'ETH' || isTestnet) { - return false - } - const { apiEndpoints } = config.proxy - const baseURL = MarketingEvent.DATA.LOG_TESTER ? apiEndpoints.baseURLTest : apiEndpoints.baseURL - const proxy = baseURL + '/eth/getFees' - const now = new Date().getTime() - if (CACHE_PROXY_DATA.address === address && now - CACHE_PROXY_TIME < CACHE_PROXY_VALID_TIME) { - BlocksoftCryptoLog.log(mainCurrencyCode + ' EthNetworkPricesProvider.getWithProxy from cache', logData) - return CACHE_PROXY_DATA.result + BlocksoftCryptoLog.log( + mainCurrencyCode + ' EthNetworkPricesProvider.getWithProxy started', + logData + ); + let checkResult = false; + let index = 0; + do { + try { + checkResult = await BlocksoftAxios.post( + proxy, + { + address, + logData, + marketingData: MarketingEvent.DATA + }, + 20000 + ); + } catch (e) { + if (config.debug.cryptoErrors) { + console.log( + 'EthNetworkPricesProvider.getWithProxy proxy error checkError ' + + e.message + ); } + } + index++; + } while (index < 3 && !checkResult); - BlocksoftCryptoLog.log(mainCurrencyCode + ' EthNetworkPricesProvider.getWithProxy started', logData) - let checkResult = false - let index = 0 - do { - try { - checkResult = await BlocksoftAxios.post(proxy, { - address, - logData, - marketingData: MarketingEvent.DATA - }, 20000) - } catch (e) { - if (config.debug.cryptoErrors) { - console.log('EthNetworkPricesProvider.getWithProxy proxy error checkError ' + e.message) - } - } - index++ - } while(index < 3 && !checkResult) - - if (checkResult !== false) { - if (typeof checkResult.data !== 'undefined') { - await BlocksoftCryptoLog.log(mainCurrencyCode + ' EthNetworkPricesProvider.getWithProxy proxy checkResult1 ', checkResult.data) - if (typeof checkResult.data.status === 'undefined' || checkResult.data.status === 'error') { - if (config.debug.cryptoErrors) { - console.log('EthNetworkPricesProvider.getWithProxy proxy error checkResult1 ', checkResult) - } - checkResult = false - } else if (checkResult.data.status === 'notice') { - throw new Error(checkResult.data.msg) - } - } else { - await BlocksoftCryptoLog.log(mainCurrencyCode + ' EthNetworkPricesProvider.getWithProxy proxy checkResult2 ', checkResult) - if (config.debug.cryptoErrors) { - console.log('EthNetworkPricesProvider.getWithProxy proxy error checkResult2 ', checkResult) - } - } - } else { - if (config.debug.cryptoErrors) { - console.log('EthNetworkPricesProvider.getWithProxy proxy error checkResultEmpty ', checkResult) - } + if (checkResult !== false) { + if (typeof checkResult.data !== 'undefined') { + await BlocksoftCryptoLog.log( + mainCurrencyCode + + ' EthNetworkPricesProvider.getWithProxy proxy checkResult1 ', + checkResult.data + ); + if ( + typeof checkResult.data.status === 'undefined' || + checkResult.data.status === 'error' + ) { + if (config.debug.cryptoErrors) { + console.log( + 'EthNetworkPricesProvider.getWithProxy proxy error checkResult1 ', + checkResult + ); + } + checkResult = false; + } else if (checkResult.data.status === 'notice') { + throw new Error(checkResult.data.msg); } - - if (checkResult === false) { - return { - gasPrice: await this.getOnlyFees(mainCurrencyCode, isTestnet, address, logData) - } + } else { + await BlocksoftCryptoLog.log( + mainCurrencyCode + + ' EthNetworkPricesProvider.getWithProxy proxy checkResult2 ', + checkResult + ); + if (config.debug.cryptoErrors) { + console.log( + 'EthNetworkPricesProvider.getWithProxy proxy error checkResult2 ', + checkResult + ); } + } + } else { + if (config.debug.cryptoErrors) { + console.log( + 'EthNetworkPricesProvider.getWithProxy proxy error checkResultEmpty ', + checkResult + ); + } + } - const result = checkResult.data - if (typeof result.gasPrice !== 'undefined') { - for (const key in result.gasPrice) { - result.gasPrice[key] = BlocksoftUtils.div(BlocksoftUtils.toWei(result.gasPrice[key], 'gwei'), MAGIC_TX_DIVIDER) // in gwei to wei + magic - } - } - const indexed = {} - let updatedCache = false - if (typeof checkResult.data.maxScanned !== 'undefined') { - await EthTmpDS.saveNonce(this._mainCurrencyCode, address, 'maxScanned', checkResult.data.maxScanned) - updatedCache = true - } - if (typeof checkResult.data.maxSuccess !== 'undefined') { - await EthTmpDS.saveNonce(this._mainCurrencyCode, address, 'maxSuccess', checkResult.data.maxSuccess) - updatedCache = true - } - if (typeof checkResult.data.txsToRemove !== 'undefined' && checkResult.data.txsToRemove) { - for (const transactionHash of checkResult.data.txsToRemove) { - await EthRawDS.cleanRawHash({ transactionHash }) - indexed[transactionHash] = 1 - } - updatedCache = true - } - if (typeof checkResult.data.txsToSuccess !== 'undefined' && checkResult.data.txsToSuccess) { - for (const transactionHash of checkResult.data.txsToSuccess) { - await EthTmpDS.setSuccess(transactionHash) - } - updatedCache = true - } - if (typeof checkResult.data.queryTxs !== 'undefined' && typeof checkResult.data.queryLength !== 'undefined') { - updatedCache = true - } - if (updatedCache) { - result.maxNonceLocal = await EthTmpDS.getCache(mainCurrencyCode, address, indexed) - } - if (typeof checkResult.data.queryTxs !== 'undefined') { - result.maxNonceLocal.queryTxs = checkResult.data.queryTxs - } - if (typeof checkResult.data.queryLength !== 'undefined') { - result.maxNonceLocal.queryLength = checkResult.data.queryLength - } - CACHE_PROXY_DATA = { result, address } - CACHE_PROXY_TIME = now + if (checkResult === false) { + return { + gasPrice: await this.getOnlyFees( + mainCurrencyCode, + isTestnet, + address, + logData + ) + }; + } - return result + const result = checkResult.data; + if (typeof result.gasPrice !== 'undefined') { + for (const key in result.gasPrice) { + result.gasPrice[key] = BlocksoftUtils.div( + BlocksoftUtils.toWei(result.gasPrice[key], 'gwei'), + MAGIC_TX_DIVIDER + ); // in gwei to wei + magic + } + } + const indexed = {}; + let updatedCache = false; + if (typeof checkResult.data.maxScanned !== 'undefined') { + await EthTmpDS.saveNonce( + this._mainCurrencyCode, + address, + 'maxScanned', + checkResult.data.maxScanned + ); + updatedCache = true; + } + if (typeof checkResult.data.maxSuccess !== 'undefined') { + await EthTmpDS.saveNonce( + this._mainCurrencyCode, + address, + 'maxSuccess', + checkResult.data.maxSuccess + ); + updatedCache = true; } + if ( + typeof checkResult.data.txsToRemove !== 'undefined' && + checkResult.data.txsToRemove + ) { + for (const transactionHash of checkResult.data.txsToRemove) { + await EthRawDS.cleanRawHash({ transactionHash }); + indexed[transactionHash] = 1; + } + updatedCache = true; + } + if ( + typeof checkResult.data.txsToSuccess !== 'undefined' && + checkResult.data.txsToSuccess + ) { + for (const transactionHash of checkResult.data.txsToSuccess) { + await EthTmpDS.setSuccess(transactionHash); + } + updatedCache = true; + } + if ( + typeof checkResult.data.queryTxs !== 'undefined' && + typeof checkResult.data.queryLength !== 'undefined' + ) { + updatedCache = true; + } + if (updatedCache) { + result.maxNonceLocal = await EthTmpDS.getCache( + mainCurrencyCode, + address, + indexed + ); + } + if (typeof checkResult.data.queryTxs !== 'undefined') { + result.maxNonceLocal.queryTxs = checkResult.data.queryTxs; + } + if (typeof checkResult.data.queryLength !== 'undefined') { + result.maxNonceLocal.queryLength = checkResult.data.queryLength; + } + CACHE_PROXY_DATA = { result, address }; + CACHE_PROXY_TIME = now; - async getOnlyFees(mainCurrencyCode, isTestnet, address, logData = {}) { - logData.resultFeeSource = 'fromCache' - const now = new Date().getTime() - if (CACHE_FEES_ETH && (now - CACHE_FEES_ETH_TIME) < CACHE_VALID_TIME) { - logData.resultFeeCacheTime = CACHE_FEES_ETH_TIME - logData.resultFee = JSON.stringify(CACHE_FEES_ETH) - // noinspection ES6MissingAwait - MarketingEvent.logEvent('v20_estimate_fee_' + mainCurrencyCode.toLowerCase() + '_result', logData) - BlocksoftCryptoLog.log(mainCurrencyCode + ' EthNetworkPricesProvider.getOnlyFees used cache => ' + JSON.stringify(CACHE_FEES_ETH)) - return this._format() - } + return result; + } - BlocksoftCryptoLog.log(mainCurrencyCode + ' EthNetworkPricesProvider.getOnlyFees no cache load') + async getOnlyFees(mainCurrencyCode, isTestnet, address, logData = {}) { + logData.resultFeeSource = 'fromCache'; + const now = new Date().getTime(); + if (CACHE_FEES_ETH && now - CACHE_FEES_ETH_TIME < CACHE_VALID_TIME) { + logData.resultFeeCacheTime = CACHE_FEES_ETH_TIME; + logData.resultFee = JSON.stringify(CACHE_FEES_ETH); + // noinspection ES6MissingAwait + MarketingEvent.logEvent( + 'v20_estimate_fee_' + mainCurrencyCode.toLowerCase() + '_result', + logData + ); + BlocksoftCryptoLog.log( + mainCurrencyCode + + ' EthNetworkPricesProvider.getOnlyFees used cache => ' + + JSON.stringify(CACHE_FEES_ETH) + ); + return this._format(); + } - let link = `${ESTIMATE_PATH}` - let tmp = false - try { - tmp = await BlocksoftAxios.getWithoutBraking(link, ESTIMATE_MAX_TRY) - if (tmp.data && tmp.data.fastest) { - if (typeof tmp.data.gasPriceRange !== 'undefined') { - delete tmp.data.gasPriceRange - } - logData.resultFeeSource = 'reloaded' - CACHE_PREV_DATA = tmp.data - BlocksoftCryptoLog.log(mainCurrencyCode + ' EthNetworkPricesProvider.getOnlyFees loaded new fee', CACHE_PREV_DATA) - } else { - logData.resultFeeSource = 'fromLoadCache' - link = 'prev' - BlocksoftCryptoLog.log(mainCurrencyCode + ' EthNetworkPricesProvider.getOnlyFees loaded prev fee as no fastest', CACHE_PREV_DATA) - } - } catch (e) { - // noinspection ES6MissingAwait - MarketingEvent.logEvent('estimate_fee_eth_load_error', { link, data: e.message }) - BlocksoftCryptoLog.log(mainCurrencyCode + ' EthNetworkPricesProvider.getOnlyFees loaded prev fee as error', CACHE_PREV_DATA) - // do nothing - } + BlocksoftCryptoLog.log( + mainCurrencyCode + ' EthNetworkPricesProvider.getOnlyFees no cache load' + ); - try { - await this._parseLoaded(mainCurrencyCode, CACHE_PREV_DATA, link) - } catch (e) { - if (config.debug.cryptoErrors) { - console.log(mainCurrencyCode + ' EthNetworkPricesProvider.getOnlyFees _parseLoaded error ' + e.message) - } - // noinspection ES6MissingAwait - MarketingEvent.logEvent('estimate_fee_eth_parse_error', { link, data: e.message }) - BlocksoftCryptoLog.log(mainCurrencyCode + ' EthNetworkPricesProvider.getOnlyFees _parseLoaded error ' + e.message) - // do nothing + let link = `${ESTIMATE_PATH}`; + let tmp = false; + try { + tmp = await BlocksoftAxios.getWithoutBraking(link, ESTIMATE_MAX_TRY); + if (tmp.data && tmp.data.fastest) { + if (typeof tmp.data.gasPriceRange !== 'undefined') { + delete tmp.data.gasPriceRange; } - logData.resultFeeCacheTime = CACHE_FEES_ETH_TIME - logData.resultFee = JSON.stringify(CACHE_FEES_ETH) - MarketingEvent.logEvent('estimate_fee_eth_result', logData) - - return this._format() + logData.resultFeeSource = 'reloaded'; + CACHE_PREV_DATA = tmp.data; + BlocksoftCryptoLog.log( + mainCurrencyCode + + ' EthNetworkPricesProvider.getOnlyFees loaded new fee', + CACHE_PREV_DATA + ); + } else { + logData.resultFeeSource = 'fromLoadCache'; + link = 'prev'; + BlocksoftCryptoLog.log( + mainCurrencyCode + + ' EthNetworkPricesProvider.getOnlyFees loaded prev fee as no fastest', + CACHE_PREV_DATA + ); + } + } catch (e) { + // noinspection ES6MissingAwait + MarketingEvent.logEvent('estimate_fee_eth_load_error', { + link, + data: e.message + }); + BlocksoftCryptoLog.log( + mainCurrencyCode + + ' EthNetworkPricesProvider.getOnlyFees loaded prev fee as error', + CACHE_PREV_DATA + ); + // do nothing } - _format() { - return { 'speed_blocks_2': CACHE_FEES_ETH[2], 'speed_blocks_6': CACHE_FEES_ETH[6], 'speed_blocks_12': CACHE_FEES_ETH[12] } + try { + await this._parseLoaded(mainCurrencyCode, CACHE_PREV_DATA, link); + } catch (e) { + if (config.debug.cryptoErrors) { + console.log( + mainCurrencyCode + + ' EthNetworkPricesProvider.getOnlyFees _parseLoaded error ' + + e.message + ); + } + // noinspection ES6MissingAwait + MarketingEvent.logEvent('estimate_fee_eth_parse_error', { + link, + data: e.message + }); + BlocksoftCryptoLog.log( + mainCurrencyCode + + ' EthNetworkPricesProvider.getOnlyFees _parseLoaded error ' + + e.message + ); + // do nothing } + logData.resultFeeCacheTime = CACHE_FEES_ETH_TIME; + logData.resultFee = JSON.stringify(CACHE_FEES_ETH); + MarketingEvent.logEvent('estimate_fee_eth_result', logData); + return this._format(); + } - /** - * @param {int} json.safeLow - * @param {int} json.average - * @param {int} json.fastest - * @private - */ - async _parseLoaded(mainCurrencyCode, json) { - CACHE_FEES_ETH = {} + _format() { + return { + speed_blocks_2: CACHE_FEES_ETH[2], + speed_blocks_6: CACHE_FEES_ETH[6], + speed_blocks_12: CACHE_FEES_ETH[12] + }; + } - const externalSettings = await BlocksoftExternalSettings.getAll('ETH.getNetworkPrices') - addMultiply(mainCurrencyCode,2, json.fastest * 1, externalSettings) - addMultiply(mainCurrencyCode,6, json.average * 1, externalSettings) - addMultiply(mainCurrencyCode,12, json.safeLow * 1, externalSettings) + /** + * @param {int} json.safeLow + * @param {int} json.average + * @param {int} json.fastest + * @private + */ + async _parseLoaded(mainCurrencyCode, json) { + CACHE_FEES_ETH = {}; - if (CACHE_FEES_ETH[12] === CACHE_FEES_ETH[6]) { - if (CACHE_FEES_ETH[6] === CACHE_FEES_ETH[2]) { - CACHE_FEES_ETH[6] = Math.round(CACHE_FEES_ETH[12] * 1.1) - CACHE_FEES_ETH[2] = Math.round(CACHE_FEES_ETH[6] * 1.1) - } else { - CACHE_FEES_ETH[6] = Math.round(CACHE_FEES_ETH[12] * 1.1) - } - } else if (CACHE_FEES_ETH[6] === CACHE_FEES_ETH[2]) { - CACHE_FEES_ETH[2] = Math.round(CACHE_FEES_ETH[6] * 1.1) - } - if (CACHE_FEES_ETH[6] > CACHE_FEES_ETH[2]) { - const tmp = CACHE_FEES_ETH[6] - CACHE_FEES_ETH[6] = CACHE_FEES_ETH[2] - CACHE_FEES_ETH[2] = tmp - } + const externalSettings = await BlocksoftExternalSettings.getAll( + 'ETH.getNetworkPrices' + ); + addMultiply(mainCurrencyCode, 2, json.fastest * 1, externalSettings); + addMultiply(mainCurrencyCode, 6, json.average * 1, externalSettings); + addMultiply(mainCurrencyCode, 12, json.safeLow * 1, externalSettings); - try { - CACHE_FEES_ETH[12] = BlocksoftUtils.div(BlocksoftUtils.toWei(CACHE_FEES_ETH[12], 'gwei'), MAGIC_TX_DIVIDER) // in gwei to wei + magic - CACHE_FEES_ETH[6] = BlocksoftUtils.div(BlocksoftUtils.toWei(CACHE_FEES_ETH[6], 'gwei'), MAGIC_TX_DIVIDER) // in gwei to wei + magic - CACHE_FEES_ETH[2] = BlocksoftUtils.div(BlocksoftUtils.toWei(CACHE_FEES_ETH[2], 'gwei'), MAGIC_TX_DIVIDER) // in gwei to wei + magic - } catch (e) { - e.message += ' in EthPrice Magic divider' - throw e - } + if (CACHE_FEES_ETH[12] === CACHE_FEES_ETH[6]) { + if (CACHE_FEES_ETH[6] === CACHE_FEES_ETH[2]) { + CACHE_FEES_ETH[6] = Math.round(CACHE_FEES_ETH[12] * 1.1); + CACHE_FEES_ETH[2] = Math.round(CACHE_FEES_ETH[6] * 1.1); + } else { + CACHE_FEES_ETH[6] = Math.round(CACHE_FEES_ETH[12] * 1.1); + } + } else if (CACHE_FEES_ETH[6] === CACHE_FEES_ETH[2]) { + CACHE_FEES_ETH[2] = Math.round(CACHE_FEES_ETH[6] * 1.1); + } + if (CACHE_FEES_ETH[6] > CACHE_FEES_ETH[2]) { + const tmp = CACHE_FEES_ETH[6]; + CACHE_FEES_ETH[6] = CACHE_FEES_ETH[2]; + CACHE_FEES_ETH[2] = tmp; + } - CACHE_FEES_ETH_TIME = new Date().getTime() + try { + CACHE_FEES_ETH[12] = BlocksoftUtils.div( + BlocksoftUtils.toWei(CACHE_FEES_ETH[12], 'gwei'), + MAGIC_TX_DIVIDER + ); // in gwei to wei + magic + CACHE_FEES_ETH[6] = BlocksoftUtils.div( + BlocksoftUtils.toWei(CACHE_FEES_ETH[6], 'gwei'), + MAGIC_TX_DIVIDER + ); // in gwei to wei + magic + CACHE_FEES_ETH[2] = BlocksoftUtils.div( + BlocksoftUtils.toWei(CACHE_FEES_ETH[2], 'gwei'), + MAGIC_TX_DIVIDER + ); // in gwei to wei + magic + } catch (e) { + e.message += ' in EthPrice Magic divider'; + throw e; } + + CACHE_FEES_ETH_TIME = new Date().getTime(); + } } function addMultiply(mainCurrencyCode, blocks, fee, externalSettings) { - if (typeof externalSettings['ETH_CURRENT_PRICE_' + blocks] !== 'undefined' && externalSettings['ETH_CURRENT_PRICE_' + blocks] > 0) { - CACHE_FEES_ETH[blocks] = externalSettings['ETH_CURRENT_PRICE_' + blocks] - BlocksoftCryptoLog.log(mainCurrencyCode + ' EthNetworkPricesProvider current price result', { blocks, fee, current: externalSettings['ETH_CURRENT_PRICE_' + blocks], res: CACHE_FEES_ETH[blocks] }) - } else if (typeof externalSettings['ETH_MULTI_' + blocks] !== 'undefined' && externalSettings['ETH_MULTI_' + blocks] > 0) { - CACHE_FEES_ETH[blocks] = BlocksoftUtils.mul(fee, externalSettings['ETH_MULTI_' + blocks]) - BlocksoftCryptoLog.log(mainCurrencyCode + ' EthNetworkPricesProvider addMultiply' + blocks + ' result', { blocks, fee, mul: externalSettings['ETH_MULTI_' + blocks], res: CACHE_FEES_ETH[blocks] }) - } else if (typeof externalSettings.ETH_MULTI !== 'undefined' && externalSettings.ETH_MULTI > 0) { - CACHE_FEES_ETH[blocks] = BlocksoftUtils.mul(fee, externalSettings.ETH_MULTI) * 1 - BlocksoftCryptoLog.log(mainCurrencyCode + ' EthNetworkPricesProvider addMultiply result', { blocks, fee, mul: externalSettings.ETH_MULTI, res: CACHE_FEES_ETH[blocks] }) - } else { - CACHE_FEES_ETH[blocks] = fee + if ( + typeof externalSettings['ETH_CURRENT_PRICE_' + blocks] !== 'undefined' && + externalSettings['ETH_CURRENT_PRICE_' + blocks] > 0 + ) { + CACHE_FEES_ETH[blocks] = externalSettings['ETH_CURRENT_PRICE_' + blocks]; + BlocksoftCryptoLog.log( + mainCurrencyCode + ' EthNetworkPricesProvider current price result', + { + blocks, + fee, + current: externalSettings['ETH_CURRENT_PRICE_' + blocks], + res: CACHE_FEES_ETH[blocks] + } + ); + } else if ( + typeof externalSettings['ETH_MULTI_' + blocks] !== 'undefined' && + externalSettings['ETH_MULTI_' + blocks] > 0 + ) { + CACHE_FEES_ETH[blocks] = BlocksoftUtils.mul( + fee, + externalSettings['ETH_MULTI_' + blocks] + ); + BlocksoftCryptoLog.log( + mainCurrencyCode + + ' EthNetworkPricesProvider addMultiply' + + blocks + + ' result', + { + blocks, + fee, + mul: externalSettings['ETH_MULTI_' + blocks], + res: CACHE_FEES_ETH[blocks] + } + ); + } else if ( + typeof externalSettings.ETH_MULTI !== 'undefined' && + externalSettings.ETH_MULTI > 0 + ) { + CACHE_FEES_ETH[blocks] = + BlocksoftUtils.mul(fee, externalSettings.ETH_MULTI) * 1; + BlocksoftCryptoLog.log( + mainCurrencyCode + ' EthNetworkPricesProvider addMultiply result', + { + blocks, + fee, + mul: externalSettings.ETH_MULTI, + res: CACHE_FEES_ETH[blocks] + } + ); + } else { + CACHE_FEES_ETH[blocks] = fee; + } + if ( + typeof externalSettings['ETH_MIN_' + blocks] !== 'undefined' && + externalSettings['ETH_MIN_' + blocks] > 0 + ) { + if (externalSettings['ETH_MIN_' + blocks] > CACHE_FEES_ETH[blocks]) { + CACHE_FEES_ETH[blocks] = externalSettings['ETH_MIN_' + blocks]; } - if (typeof externalSettings['ETH_MIN_' + blocks] !== 'undefined' && externalSettings['ETH_MIN_' + blocks] > 0) { - if (externalSettings['ETH_MIN_' + blocks] > CACHE_FEES_ETH[blocks]) { - CACHE_FEES_ETH[blocks] = externalSettings['ETH_MIN_' + blocks] - } - } else if (typeof externalSettings.ETH_MIN !== 'undefined' && externalSettings.ETH_MIN > 0) { - if (externalSettings.ETH_MIN > CACHE_FEES_ETH[blocks]) { - CACHE_FEES_ETH[blocks] = externalSettings.ETH_MIN - } + } else if ( + typeof externalSettings.ETH_MIN !== 'undefined' && + externalSettings.ETH_MIN > 0 + ) { + if (externalSettings.ETH_MIN > CACHE_FEES_ETH[blocks]) { + CACHE_FEES_ETH[blocks] = externalSettings.ETH_MIN; } + } } - -const singleton = new EthNetworkPrices() -export default singleton - +const singleton = new EthNetworkPrices(); +export default singleton; diff --git a/crypto/blockchains/eth/basic/EthTxSendProvider.ts b/crypto/blockchains/eth/basic/EthTxSendProvider.ts index 443b59e80..3a98140cd 100644 --- a/crypto/blockchains/eth/basic/EthTxSendProvider.ts +++ b/crypto/blockchains/eth/basic/EthTxSendProvider.ts @@ -2,304 +2,528 @@ * @author Ksu * @version 0.32 */ -import { BlocksoftBlockchainTypes } from '../../BlocksoftBlockchainTypes' -import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog' -import BlocksoftUtils from '../../../common/BlocksoftUtils' -import EthTmpDS from '../stores/EthTmpDS' -import EthRawDS from '../stores/EthRawDS' -import BlocksoftExternalSettings from '../../../common/BlocksoftExternalSettings' -import BlocksoftAxios from '../../../common/BlocksoftAxios' -import config from '../../../../app/config/config' -import MarketingEvent from '../../../../app/services/Marketing/MarketingEvent' - +import { BlocksoftBlockchainTypes } from '../../BlocksoftBlockchainTypes'; +import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; +import BlocksoftUtils from '../../../common/BlocksoftUtils'; +import EthTmpDS from '../stores/EthTmpDS'; +import EthRawDS from '../stores/EthRawDS'; +import BlocksoftExternalSettings from '../../../common/BlocksoftExternalSettings'; +import BlocksoftAxios from '../../../common/BlocksoftAxios'; +import config from '../../../../app/config/config'; +import MarketingEvent from '../../../../app/services/Marketing/MarketingEvent'; export default class EthTxSendProvider { + private _web3: any; + private _trezorServerCode: any; + private _trezorServer: any; + private _settings: any; + private _mainCurrencyCode: string; + private _mainChainId: any; - private _web3: any - private _trezorServerCode: any - private _trezorServer: any - private _settings: any - private _mainCurrencyCode: string - private _mainChainId: any + constructor( + web3: any, + trezorServerCode: any, + mainCurrencyCode: string, + mainChainId: any, + settings: any + ) { + this._web3 = web3; + this._trezorServerCode = trezorServerCode; + this._trezorServer = 'to_load'; + this._settings = settings; - constructor(web3: any, trezorServerCode: any, mainCurrencyCode : string, mainChainId : any, settings: any) { - this._web3 = web3 - this._trezorServerCode = trezorServerCode - this._trezorServer = 'to_load' - this._settings = settings + this._mainCurrencyCode = mainCurrencyCode; + this._mainChainId = mainChainId; + } - this._mainCurrencyCode = mainCurrencyCode - this._mainChainId = mainChainId + async sign( + tx: BlocksoftBlockchainTypes.EthTx, + privateData: BlocksoftBlockchainTypes.TransferPrivateData, + txRBF: any, + logData: any + ): Promise<{ transactionHash: string; transactionJson: any }> { + // @ts-ignore + await BlocksoftCryptoLog.log( + this._settings.currencyCode + ' EthTxSendProvider._innerSign started', + logData + ); + // noinspection JSUnresolvedVariable + if (privateData.privateKey.substr(0, 2) !== '0x') { + privateData.privateKey = '0x' + privateData.privateKey; } - - - async sign(tx: BlocksoftBlockchainTypes.EthTx, privateData: BlocksoftBlockchainTypes.TransferPrivateData, txRBF: any, logData: any): Promise<{ transactionHash: string, transactionJson: any }> { - // @ts-ignore - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTxSendProvider._innerSign started', logData) - // noinspection JSUnresolvedVariable - if (privateData.privateKey.substr(0, 2) !== '0x') { - privateData.privateKey = '0x' + privateData.privateKey - } - if (tx.value.toString().substr(0, 1) === '-') { - throw new Error('SERVER_RESPONSE_NOTHING_LEFT_FOR_FEE') - } - // noinspection JSUnresolvedVariable - if (this._mainChainId) { - tx.chainId = this._mainChainId - } - let signData = false - try { - signData = await this._web3.eth.accounts.signTransaction(tx, privateData.privateKey) - } catch (e) { - throw new Error(this._settings.currencyCode + ' EthTxSendProvider._innerSign signTransaction error ' + e.message) - } - return signData.rawTransaction + if (tx.value.toString().substr(0, 1) === '-') { + throw new Error('SERVER_RESPONSE_NOTHING_LEFT_FOR_FEE'); + } + // noinspection JSUnresolvedVariable + if (this._mainChainId) { + tx.chainId = this._mainChainId; } + let signData = false; + try { + signData = await this._web3.eth.accounts.signTransaction( + tx, + privateData.privateKey + ); + } catch (e) { + throw new Error( + this._settings.currencyCode + + ' EthTxSendProvider._innerSign signTransaction error ' + + e.message + ); + } + return signData.rawTransaction; + } - async send(tx: BlocksoftBlockchainTypes.EthTx, privateData: BlocksoftBlockchainTypes.TransferPrivateData, txRBF: any, logData: any): Promise<{ transactionHash: string, transactionJson: any }> { - // @ts-ignore - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTxSendProvider._innerSendTx started', logData) + async send( + tx: BlocksoftBlockchainTypes.EthTx, + privateData: BlocksoftBlockchainTypes.TransferPrivateData, + txRBF: any, + logData: any + ): Promise<{ transactionHash: string; transactionJson: any }> { + // @ts-ignore + await BlocksoftCryptoLog.log( + this._settings.currencyCode + ' EthTxSendProvider._innerSendTx started', + logData + ); - const rawTransaction = await this.sign(tx, privateData, txRBF, logData) + const rawTransaction = await this.sign(tx, privateData, txRBF, logData); - // @ts-ignore - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTxSendProvider._innerSendTx signed', tx) - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTxSendProvider._innerSendTx hex', rawTransaction) + // @ts-ignore + await BlocksoftCryptoLog.log( + this._settings.currencyCode + ' EthTxSendProvider._innerSendTx signed', + tx + ); + await BlocksoftCryptoLog.log( + this._settings.currencyCode + ' EthTxSendProvider._innerSendTx hex', + rawTransaction + ); - let link = BlocksoftExternalSettings.getStatic(this._trezorServerCode + '_SEND_LINK') - if (!link || link === '') { - if (this._trezorServerCode) { - if (this._trezorServerCode === 'TRX') { - link = this._trezorServerCode - } else if (this._trezorServerCode.indexOf('http') === -1) { - this._trezorServer = await BlocksoftExternalSettings.getTrezorServer(this._trezorServerCode, 'ETH.Send.sendTx') - link = this._trezorServer + '/api/v2/sendtx/' - } - } else { - link = this._trezorServerCode // actually is direct url like link = 'https://dex.binance.org/api/v1/broadcast' - } + let link = BlocksoftExternalSettings.getStatic( + this._trezorServerCode + '_SEND_LINK' + ); + if (!link || link === '') { + if (this._trezorServerCode) { + if (this._trezorServerCode === 'TRX') { + link = this._trezorServerCode; + } else if (this._trezorServerCode.indexOf('http') === -1) { + this._trezorServer = await BlocksoftExternalSettings.getTrezorServer( + this._trezorServerCode, + 'ETH.Send.sendTx' + ); + link = this._trezorServer + '/api/v2/sendtx/'; } + } else { + link = this._trezorServerCode; // actually is direct url like link = 'https://dex.binance.org/api/v1/broadcast' + } + } - const { apiEndpoints } = config.proxy - const baseURL = MarketingEvent.DATA.LOG_TESTER ? apiEndpoints.baseURLTest : apiEndpoints.baseURL - const proxy = baseURL + '/send/checktx' - const errorProxy = baseURL + '/send/errortx' - const successProxy = baseURL + '/send/sendtx' - let checkResult = false - try { - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTxSendProvider.send proxy checkResult start ' + proxy, logData) - checkResult = await BlocksoftAxios.post(proxy, { - raw: rawTransaction, - txRBF, - logData, - marketingData: MarketingEvent.DATA - }) - } catch (e) { - if (config.debug.cryptoErrors) { - console.log(this._settings.currencyCode + ' EthTxSendProvider.send proxy error checkResult ' + e.message) - } - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTxSendProvider.send proxy error checkResult ' + e.message) - } + const { apiEndpoints } = config.proxy; + const baseURL = MarketingEvent.DATA.LOG_TESTER + ? apiEndpoints.baseURLTest + : apiEndpoints.baseURL; + const proxy = baseURL + '/send/checktx'; + const errorProxy = baseURL + '/send/errortx'; + const successProxy = baseURL + '/send/sendtx'; + let checkResult = false; + try { + await BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' EthTxSendProvider.send proxy checkResult start ' + + proxy, + logData + ); + checkResult = await BlocksoftAxios.post(proxy, { + raw: rawTransaction, + txRBF, + logData, + marketingData: MarketingEvent.DATA + }); + } catch (e) { + if (config.debug.cryptoErrors) { + console.log( + this._settings.currencyCode + + ' EthTxSendProvider.send proxy error checkResult ' + + e.message + ); + } + await BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' EthTxSendProvider.send proxy error checkResult ' + + e.message + ); + } - if (checkResult !== false) { - if (typeof checkResult.data !== 'undefined') { - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTxSendProvider.send proxy checkResult1 ', checkResult.data) - if (typeof checkResult.data.status === 'undefined' || checkResult.data.status === 'error') { - if (config.debug.cryptoErrors) { - console.log(this._settings.currencyCode + ' EthTxSendProvider.send proxy error checkResult1 ', JSON.parse(JSON.stringify(checkResult.data))) - } - checkResult = false - } else if (checkResult.data.status === 'notice') { - throw new Error(checkResult.data.msg) - } - } else { - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTxSendProvider.send proxy checkResult2 ', checkResult) - if (config.debug.cryptoErrors) { - console.log(this._settings.currencyCode + ' EthTxSendProvider.send proxy error checkResult2 ', JSON.parse(JSON.stringify(checkResult.data))) - } - } - } else { - if (config.debug.cryptoErrors) { - console.log(this._settings.currencyCode + ' EthTxSendProvider.send proxy error checkResultEmpty ', JSON.stringify(checkResult.data)) - } + if (checkResult !== false) { + if (typeof checkResult.data !== 'undefined') { + await BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' EthTxSendProvider.send proxy checkResult1 ', + checkResult.data + ); + if ( + typeof checkResult.data.status === 'undefined' || + checkResult.data.status === 'error' + ) { + if (config.debug.cryptoErrors) { + console.log( + this._settings.currencyCode + + ' EthTxSendProvider.send proxy error checkResult1 ', + JSON.parse(JSON.stringify(checkResult.data)) + ); + } + checkResult = false; + } else if (checkResult.data.status === 'notice') { + throw new Error(checkResult.data.msg); } - logData.checkResult = checkResult && typeof checkResult.data !== 'undefined' && checkResult.data ? JSON.parse(JSON.stringify(checkResult.data)) : false - - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTxSendProvider.send will send') - let result - let sendLink - try { - if (this._mainCurrencyCode === 'MATIC' || this._mainCurrencyCode === 'FTM' || !link) { - /** - * curl http://matic.trusteeglobal.com:8545 -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","method":"eth_sendRawTransaction","params":["0x..."],"id":83}' - */ - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTxSendProvider.send sendSignedTransaction to ' + this._web3.LINK, rawTransaction) - sendLink = this._web3.LINK - const tmp = await BlocksoftAxios.postWithoutBraking(sendLink, { - jsonrpc: '2.0', - method: 'eth_sendRawTransaction', - params: [rawTransaction], - id: 1 - }) - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTxSendProvider.send sendSignedTransaction to ' + this._web3.LINK + ' result ', tmp) - if (!tmp || typeof tmp.data === 'undefined') { - throw new Error('SERVER_RESPONSE_NOT_CONNECTED') - } - if (typeof tmp.data.error !== 'undefined' && tmp.data.error) { - throw new Error(typeof tmp.data.error.message !== 'undefined' ? tmp.data.error.message : tmp.data.error) - } - result = { - data: { - result: typeof tmp.data.result !== 'undefined' ? tmp.data.result : false - } - } - } else if (this._mainCurrencyCode === 'BNB') { - /** - * {"blockHash": "0x01d48fd5de1ebb62275096f749acb6849bd97f3c050acb07358222cea0a527bc", - * "blockNumber": 5223318, "contractAddress": null, - * "cumulativeGasUsed": 14465279, "from": "0xf1cff704c6e6ce459e3e1544a9533cccbdad7b99", - * "gasUsed": 21000, "logs": [], - * "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - * "status": true, "to": "0xf1cff704c6e6ce459e3e1544a9533cccbdad7b99", "transactionHash": "0x1fa5646517b625d422863e6c27082104e1697543a6f912421527bb171c6173f2", "transactionIndex": 95} - */ - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTxSendProvider.send sendSignedTransaction ', rawTransaction) - sendLink = this._web3.LINK - const tmp = await this._web3.eth.sendSignedTransaction(rawTransaction) - result = { - data: { - result: typeof tmp.transactionHash !== 'undefined' ? tmp.transactionHash : false - } - } - - } else { - sendLink = link - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTxSendProvider.send post ', rawTransaction) - result = await BlocksoftAxios.post(sendLink, rawTransaction) - } - // @ts-ignore - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTxSendProvider.send result ', typeof result !== 'undefined' && result ? result.data : 'NO RESULT') - } catch (e) { - if (config.debug.cryptoErrors) { - console.log(this._settings.currencyCode + ' ' + this._mainCurrencyCode + ' EthTxSendProvider.send trezor ' + sendLink + ' error ' + e.message, JSON.parse(JSON.stringify(logData))) - } - try { - logData.error = e.message - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTxSendProvider.send proxy errorTx start ' + errorProxy, logData) - const res2 = await BlocksoftAxios.post(errorProxy, { - raw: rawTransaction, - txRBF, - logData, - marketingData: MarketingEvent.DATA - }) - if (config.debug.cryptoErrors) { - console.log(this._settings.currencyCode + ' EthTxSendProvider.send proxy errorTx result', JSON.parse(JSON.stringify(res2.data))) - } - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTxSendProvider.send proxy errorTx', typeof res2.data !== 'undefined' ? res2.data : res2) - throw new Error('res2.data : ' + res2.data) - } catch (e2) { - if (config.debug.cryptoErrors) { - console.log(this._settings.currencyCode + ' EthTxSendProvider.send proxy error errorTx ' + e.message) - } - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTxSendProvider.send proxy error errorTx ' + e2.message) - } - if (this._settings.currencyCode !== 'ETH' && this._settings.currencyCode !== 'ETH_ROPSTEN' && e.message.indexOf('bad-txns-in-belowout') !== -1) { - throw new Error('SERVER_RESPONSE_NOT_ENOUGH_FEE') - } else if (e.message.indexOf('dust') !== -1) { - throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_DUST') - } else if (e.message.indexOf('bad-txns-inputs-spent') !== -1 || e.message.indexOf('txn-mempool-conflict') !== -1) { - throw new Error('SERVER_RESPONSE_NO_RESPONSE') - } else if (e.message.indexOf('min relay fee not met') !== -1 || e.message.indexOf('fee for relay') !== -1 || e.message.indexOf('insufficient priority') !== -1) { - throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_FEE') - } else if (e.message.indexOf('insufficient fee, rejecting replacement') !== -1) { - if (this._settings.currencyCode !== 'ETH' && this._settings.currencyCode !== 'ETH_ROPSTEN') { - throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_FEE_FOR_REPLACEMENT') - } else { - throw new Error('UI_CONFIRM_CHANGE_AMOUNT_FOR_REPLACEMENT') - } - } else if (e.message.indexOf('too-long-mempool-chain') !== -1) { - throw new Error('SERVER_RESPONSE_NO_RESPONSE') - } else { - await BlocksoftExternalSettings.setTrezorServerInvalid(this._trezorServerCode, this._trezorServer) - e.message += ' link: ' + link - throw e - } + } else { + await BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' EthTxSendProvider.send proxy checkResult2 ', + checkResult + ); + if (config.debug.cryptoErrors) { + console.log( + this._settings.currencyCode + + ' EthTxSendProvider.send proxy error checkResult2 ', + JSON.parse(JSON.stringify(checkResult.data)) + ); } + } + } else { + if (config.debug.cryptoErrors) { + console.log( + this._settings.currencyCode + + ' EthTxSendProvider.send proxy error checkResultEmpty ', + JSON.stringify(checkResult.data) + ); + } + } + logData.checkResult = + checkResult && typeof checkResult.data !== 'undefined' && checkResult.data + ? JSON.parse(JSON.stringify(checkResult.data)) + : false; - // @ts-ignore - if (typeof result.data.result === 'undefined' || !result.data.result) { - throw new Error('SERVER_RESPONSE_NOT_CONNECTED') + await BlocksoftCryptoLog.log( + this._settings.currencyCode + ' EthTxSendProvider.send will send' + ); + let result; + let sendLink; + try { + if ( + this._mainCurrencyCode === 'MATIC' || + this._mainCurrencyCode === 'FTM' || + !link + ) { + /** + * curl http://matic.trusteeglobal.com:8545 -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","method":"eth_sendRawTransaction","params":["0x..."],"id":83}' + */ + await BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' EthTxSendProvider.send sendSignedTransaction to ' + + this._web3.LINK, + rawTransaction + ); + sendLink = this._web3.LINK; + const tmp = await BlocksoftAxios.postWithoutBraking(sendLink, { + jsonrpc: '2.0', + method: 'eth_sendRawTransaction', + params: [rawTransaction], + id: 1 + }); + await BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' EthTxSendProvider.send sendSignedTransaction to ' + + this._web3.LINK + + ' result ', + tmp + ); + if (!tmp || typeof tmp.data === 'undefined') { + throw new Error('SERVER_RESPONSE_NOT_CONNECTED'); } - - // @ts-ignore - const transactionHash = result.data.result - if (transactionHash === '') { - throw new Error('SERVER_RESPONSE_BAD_CODE') + if (typeof tmp.data.error !== 'undefined' && tmp.data.error) { + throw new Error( + typeof tmp.data.error.message !== 'undefined' + ? tmp.data.error.message + : tmp.data.error + ); } - - checkResult = false - try { - logData.txHash = transactionHash - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTxSendProvider.send proxy successTx start ' + successProxy, logData) - checkResult = await BlocksoftAxios.post(successProxy, { - raw: rawTransaction, - txRBF, - logData, - marketingData: MarketingEvent.DATA - }) - if (config.debug.cryptoErrors) { - console.log(this._settings.currencyCode + ' EthTxSendProvider.send proxy successTx result ', JSON.parse(JSON.stringify(checkResult.data))) - } - } catch (e3) { - if (config.debug.cryptoErrors) { - console.log(this._settings.currencyCode + ' EthTxSendProvider.send proxy error successTx ' + e3.message) - } - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTxSendProvider.send proxy error successTx ' + e3.message) + result = { + data: { + result: + typeof tmp.data.result !== 'undefined' ? tmp.data.result : false + } + }; + } else if (this._mainCurrencyCode === 'BNB') { + /** + * {"blockHash": "0x01d48fd5de1ebb62275096f749acb6849bd97f3c050acb07358222cea0a527bc", + * "blockNumber": 5223318, "contractAddress": null, + * "cumulativeGasUsed": 14465279, "from": "0xf1cff704c6e6ce459e3e1544a9533cccbdad7b99", + * "gasUsed": 21000, "logs": [], + * "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + * "status": true, "to": "0xf1cff704c6e6ce459e3e1544a9533cccbdad7b99", "transactionHash": "0x1fa5646517b625d422863e6c27082104e1697543a6f912421527bb171c6173f2", "transactionIndex": 95} + */ + await BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' EthTxSendProvider.send sendSignedTransaction ', + rawTransaction + ); + sendLink = this._web3.LINK; + const tmp = await this._web3.eth.sendSignedTransaction(rawTransaction); + result = { + data: { + result: + typeof tmp.transactionHash !== 'undefined' + ? tmp.transactionHash + : false + } + }; + } else { + sendLink = link; + await BlocksoftCryptoLog.log( + this._settings.currencyCode + ' EthTxSendProvider.send post ', + rawTransaction + ); + result = await BlocksoftAxios.post(sendLink, rawTransaction); + } + // @ts-ignore + await BlocksoftCryptoLog.log( + this._settings.currencyCode + ' EthTxSendProvider.send result ', + typeof result !== 'undefined' && result ? result.data : 'NO RESULT' + ); + } catch (e) { + if (config.debug.cryptoErrors) { + console.log( + this._settings.currencyCode + + ' ' + + this._mainCurrencyCode + + ' EthTxSendProvider.send trezor ' + + sendLink + + ' error ' + + e.message, + JSON.parse(JSON.stringify(logData)) + ); + } + try { + logData.error = e.message; + await BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' EthTxSendProvider.send proxy errorTx start ' + + errorProxy, + logData + ); + const res2 = await BlocksoftAxios.post(errorProxy, { + raw: rawTransaction, + txRBF, + logData, + marketingData: MarketingEvent.DATA + }); + if (config.debug.cryptoErrors) { + console.log( + this._settings.currencyCode + + ' EthTxSendProvider.send proxy errorTx result', + JSON.parse(JSON.stringify(res2.data)) + ); } - - if (checkResult !== false) { - if (typeof checkResult.data !== 'undefined') { - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTxSendProvider.send proxy successResult1 ', checkResult.data) - if (typeof checkResult.data.status === 'undefined' || checkResult.data.status === 'error') { - if (config.debug.cryptoErrors) { - console.log(this._settings.currencyCode + ' EthTxSendProvider.send proxy error successResult1 ', checkResult) - } - checkResult = false - } else if (checkResult.data.status === 'notice') { - throw new Error(checkResult.data.msg) - } - } else { - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTxSendProvider.send proxy successResult2 ', checkResult) - if (config.debug.cryptoErrors) { - console.log(this._settings.currencyCode + ' EthTxSendProvider.send proxy error successResult2 ', checkResult) - } - } + await BlocksoftCryptoLog.log( + this._settings.currencyCode + ' EthTxSendProvider.send proxy errorTx', + typeof res2.data !== 'undefined' ? res2.data : res2 + ); + throw new Error('res2.data : ' + res2.data); + } catch (e2) { + if (config.debug.cryptoErrors) { + console.log( + this._settings.currencyCode + + ' EthTxSendProvider.send proxy error errorTx ' + + e.message + ); + } + await BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' EthTxSendProvider.send proxy error errorTx ' + + e2.message + ); + } + if ( + this._settings.currencyCode !== 'ETH' && + this._settings.currencyCode !== 'ETH_ROPSTEN' && + e.message.indexOf('bad-txns-in-belowout') !== -1 + ) { + throw new Error('SERVER_RESPONSE_NOT_ENOUGH_FEE'); + } else if (e.message.indexOf('dust') !== -1) { + throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_DUST'); + } else if ( + e.message.indexOf('bad-txns-inputs-spent') !== -1 || + e.message.indexOf('txn-mempool-conflict') !== -1 + ) { + throw new Error('SERVER_RESPONSE_NO_RESPONSE'); + } else if ( + e.message.indexOf('min relay fee not met') !== -1 || + e.message.indexOf('fee for relay') !== -1 || + e.message.indexOf('insufficient priority') !== -1 + ) { + throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_FEE'); + } else if ( + e.message.indexOf('insufficient fee, rejecting replacement') !== -1 + ) { + if ( + this._settings.currencyCode !== 'ETH' && + this._settings.currencyCode !== 'ETH_ROPSTEN' + ) { + throw new Error( + 'SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_FEE_FOR_REPLACEMENT' + ); } else { - if (config.debug.cryptoErrors) { - console.log(this._settings.currencyCode + ' EthTxSendProvider.send proxy error successResultEmpty ', checkResult) - } + throw new Error('UI_CONFIRM_CHANGE_AMOUNT_FOR_REPLACEMENT'); } - logData.successResult = checkResult && typeof checkResult.data !== 'undefined' && checkResult.data ? JSON.parse(JSON.stringify(checkResult.data)) : false - logData.txRBF = txRBF + } else if (e.message.indexOf('too-long-mempool-chain') !== -1) { + throw new Error('SERVER_RESPONSE_NO_RESPONSE'); + } else { + await BlocksoftExternalSettings.setTrezorServerInvalid( + this._trezorServerCode, + this._trezorServer + ); + e.message += ' link: ' + link; + throw e; + } + } + + // @ts-ignore + if (typeof result.data.result === 'undefined' || !result.data.result) { + throw new Error('SERVER_RESPONSE_NOT_CONNECTED'); + } - const nonce = typeof logData.setNonce !== 'undefined' ? logData.setNonce : BlocksoftUtils.hexToDecimal(tx.nonce) + // @ts-ignore + const transactionHash = result.data.result; + if (transactionHash === '') { + throw new Error('SERVER_RESPONSE_BAD_CODE'); + } - const transactionJson = { - nonce, - gasPrice: typeof logData.gasPrice !== 'undefined' ? logData.gasPrice : BlocksoftUtils.hexToDecimal(tx.gasPrice) + checkResult = false; + try { + logData.txHash = transactionHash; + await BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' EthTxSendProvider.send proxy successTx start ' + + successProxy, + logData + ); + checkResult = await BlocksoftAxios.post(successProxy, { + raw: rawTransaction, + txRBF, + logData, + marketingData: MarketingEvent.DATA + }); + if (config.debug.cryptoErrors) { + console.log( + this._settings.currencyCode + + ' EthTxSendProvider.send proxy successTx result ', + JSON.parse(JSON.stringify(checkResult.data)) + ); + } + } catch (e3) { + if (config.debug.cryptoErrors) { + console.log( + this._settings.currencyCode + + ' EthTxSendProvider.send proxy error successTx ' + + e3.message + ); + } + await BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' EthTxSendProvider.send proxy error successTx ' + + e3.message + ); + } + + if (checkResult !== false) { + if (typeof checkResult.data !== 'undefined') { + await BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' EthTxSendProvider.send proxy successResult1 ', + checkResult.data + ); + if ( + typeof checkResult.data.status === 'undefined' || + checkResult.data.status === 'error' + ) { + if (config.debug.cryptoErrors) { + console.log( + this._settings.currencyCode + + ' EthTxSendProvider.send proxy error successResult1 ', + checkResult + ); + } + checkResult = false; + } else if (checkResult.data.status === 'notice') { + throw new Error(checkResult.data.msg); } + } else { + await BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' EthTxSendProvider.send proxy successResult2 ', + checkResult + ); + if (config.debug.cryptoErrors) { + console.log( + this._settings.currencyCode + + ' EthTxSendProvider.send proxy error successResult2 ', + checkResult + ); + } + } + } else { + if (config.debug.cryptoErrors) { + console.log( + this._settings.currencyCode + + ' EthTxSendProvider.send proxy error successResultEmpty ', + checkResult + ); + } + } + logData.successResult = + checkResult && typeof checkResult.data !== 'undefined' && checkResult.data + ? JSON.parse(JSON.stringify(checkResult.data)) + : false; + logData.txRBF = txRBF; - await EthRawDS.saveRaw({ - address: tx.from, - currencyCode: this._settings.currencyCode, - transactionUnique: tx.from + '_' + nonce, - transactionHash, - transactionRaw: rawTransaction, - transactionLog: logData - }) + const nonce = + typeof logData.setNonce !== 'undefined' + ? logData.setNonce + : BlocksoftUtils.hexToDecimal(tx.nonce); - BlocksoftCryptoLog.log(this._settings.currencyCode + ' EthTxSendProvider.send save nonce ' + nonce + ' from ' + tx.from + ' ' + transactionHash) - await EthTmpDS.saveNonce(this._mainCurrencyCode, tx.from, 'send_' + transactionHash, nonce) + const transactionJson = { + nonce, + gasPrice: + typeof logData.gasPrice !== 'undefined' + ? logData.gasPrice + : BlocksoftUtils.hexToDecimal(tx.gasPrice) + }; - return { transactionHash, transactionJson } - } + await EthRawDS.saveRaw({ + address: tx.from, + currencyCode: this._settings.currencyCode, + transactionUnique: tx.from + '_' + nonce, + transactionHash, + transactionRaw: rawTransaction, + transactionLog: logData + }); + + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' EthTxSendProvider.send save nonce ' + + nonce + + ' from ' + + tx.from + + ' ' + + transactionHash + ); + await EthTmpDS.saveNonce( + this._mainCurrencyCode, + tx.from, + 'send_' + transactionHash, + nonce + ); + + return { transactionHash, transactionJson }; + } } diff --git a/crypto/blockchains/eth/forks/EthScannerProcessorSoul.js b/crypto/blockchains/eth/forks/EthScannerProcessorSoul.js index 5decb8ab9..a769a59c5 100644 --- a/crypto/blockchains/eth/forks/EthScannerProcessorSoul.js +++ b/crypto/blockchains/eth/forks/EthScannerProcessorSoul.js @@ -1,55 +1,68 @@ /** * @version 0.5 */ -import BlocksoftUtils from '../../../common/BlocksoftUtils' -import BlocksoftBN from '../../../common/BlocksoftBN' -import EthScannerProcessorErc20 from '../EthScannerProcessorErc20' -import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog' +import BlocksoftUtils from '../../../common/BlocksoftUtils'; +import BlocksoftBN from '../../../common/BlocksoftBN'; +import EthScannerProcessorErc20 from '../EthScannerProcessorErc20'; +import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; export default class EthScannerProcessorSoul extends EthScannerProcessorErc20 { - - /** - * some fix for soul double tx - * @param {string} address - * @param {*} result - * @param {boolean} isInternal - * @param {boolean} isTrezor - * @returns {Promise<[]>} - * @private - */ - async _unifyTransactions(address, result, isInternal, isTrezor = true) { - const transactions = [] - const alreadyTransactions = {} - let count = 0 - let tx - for (tx of result) { - - let transaction - try { - if (isTrezor) { - transaction = await this._unifyTransactionTrezor(address, tx, isInternal) - } else { - transaction = await this._unifyTransaction(address, tx, isInternal) - } - } catch (e) { - BlocksoftCryptoLog.error('EthScannerProcessorSoul._unifyTransaction error ' + e.message + ' on ' + (isTrezor ? 'Trezor' : 'usual') + ' tx ' + JSON.stringify(tx)) - } - if (!transaction) { - continue - } - transaction.addressAmount = new BlocksoftBN(BlocksoftUtils.fromENumber(transaction.addressAmount)) - if (typeof (alreadyTransactions[transaction.transactionHash]) !== 'undefined') { - const already = alreadyTransactions[transaction.transactionHash] - transactions[already].addressAmount.add(transaction.addressAmount) - } else { - alreadyTransactions[transaction.transactionHash] = count - count++ - transactions.push(transaction) - } + /** + * some fix for soul double tx + * @param {string} address + * @param {*} result + * @param {boolean} isInternal + * @param {boolean} isTrezor + * @returns {Promise<[]>} + * @private + */ + async _unifyTransactions(address, result, isInternal, isTrezor = true) { + const transactions = []; + const alreadyTransactions = {}; + let count = 0; + let tx; + for (tx of result) { + let transaction; + try { + if (isTrezor) { + transaction = await this._unifyTransactionTrezor( + address, + tx, + isInternal + ); + } else { + transaction = await this._unifyTransaction(address, tx, isInternal); } - for (let i = 0, ic = transactions.length; i < ic; i++) { - transactions[i].addressAmount = transactions[i].addressAmount.get() - } - return transactions + } catch (e) { + BlocksoftCryptoLog.error( + 'EthScannerProcessorSoul._unifyTransaction error ' + + e.message + + ' on ' + + (isTrezor ? 'Trezor' : 'usual') + + ' tx ' + + JSON.stringify(tx) + ); + } + if (!transaction) { + continue; + } + transaction.addressAmount = new BlocksoftBN( + BlocksoftUtils.fromENumber(transaction.addressAmount) + ); + if ( + typeof alreadyTransactions[transaction.transactionHash] !== 'undefined' + ) { + const already = alreadyTransactions[transaction.transactionHash]; + transactions[already].addressAmount.add(transaction.addressAmount); + } else { + alreadyTransactions[transaction.transactionHash] = count; + count++; + transactions.push(transaction); + } + } + for (let i = 0, ic = transactions.length; i < ic; i++) { + transactions[i].addressAmount = transactions[i].addressAmount.get(); } + return transactions; + } } diff --git a/crypto/blockchains/eth/stores/EthRawDS.js b/crypto/blockchains/eth/stores/EthRawDS.js index e0ccc7fb1..360fb522b 100644 --- a/crypto/blockchains/eth/stores/EthRawDS.js +++ b/crypto/blockchains/eth/stores/EthRawDS.js @@ -3,30 +3,31 @@ * @version 0.32 */ import Database from '@app/appstores/DataSource/Database'; -import EthTxSendProvider from '../basic/EthTxSendProvider' -import BlocksoftExternalSettings from '../../../common/BlocksoftExternalSettings' -import BlocksoftAxios from '../../../common/BlocksoftAxios' -import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog' -import MarketingEvent from '../../../../app/services/Marketing/MarketingEvent' -import config from '../../../../app/config/config' +import EthTxSendProvider from '../basic/EthTxSendProvider'; +import BlocksoftExternalSettings from '../../../common/BlocksoftExternalSettings'; +import BlocksoftAxios from '../../../common/BlocksoftAxios'; +import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; +import MarketingEvent from '../../../../app/services/Marketing/MarketingEvent'; +import config from '../../../../app/config/config'; -const tableName = 'transactions_raw' +const tableName = 'transactions_raw'; class EthRawDS { - /** - * @type {string} - * @private - */ - _currencyCode = 'ETH' + /** + * @type {string} + * @private + */ + _currencyCode = 'ETH'; - _trezorServer = 'none' + _trezorServer = 'none'; - async getForAddress(data) { - try { - if (typeof data.currencyCode !== 'undefined') { - this._currencyCode = data.currencyCode === 'ETH_ROPSTEN' ? 'ETH_ROPSTEN' : 'ETH' - } - const sql = ` + async getForAddress(data) { + try { + if (typeof data.currencyCode !== 'undefined') { + this._currencyCode = + data.currencyCode === 'ETH_ROPSTEN' ? 'ETH_ROPSTEN' : 'ETH'; + } + const sql = ` SELECT id, transaction_unique_key AS transactionUnique, transaction_hash AS transactionHash, @@ -40,244 +41,329 @@ class EthRawDS { WHERE (is_removed=0 OR is_removed IS NULL) AND currency_code='${this._currencyCode}' - AND address='${data.address.toLowerCase()}'` - const result = await Database.query(sql) - if (!result || !result.array || result.array.length === 0) { - return {} - } - - const ret = {} - - if (this._trezorServer === 'none') { - if (this._currencyCode === 'ETH') { - try { - this._trezorServer = await BlocksoftExternalSettings.getTrezorServer('ETH_TREZOR_SERVER', 'ETH.Broadcast') - this._infuraProjectId = BlocksoftExternalSettings.getStatic('ETH_INFURA_PROJECT_ID', 'ETH.Broadcast') - } catch (e) { - throw new Error(e.message + ' inside trezorServer') - } - } else { - this._trezorServer = await BlocksoftExternalSettings.getTrezorServer('ETH_ROPSTEN_TREZOR_SERVER', 'ETH.Broadcast') - } - } + AND address='${data.address.toLowerCase()}'`; + const result = await Database.query(sql); + if (!result || !result.array || result.array.length === 0) { + return {}; + } - const now = new Date().toISOString() + const ret = {}; - for (const row of result.array) { - try { - ret[row.transactionUnique] = row - let transactionLog - try { - transactionLog = row.transactionLog ? JSON.parse(Database.unEscapeString(row.transactionLog)) : row.transactionLog - } catch (e) { - // do nothing - } - - if (transactionLog && typeof transactionLog.currencyCode !== 'undefined' && (typeof transactionLog.successResult === 'undefined' || !transactionLog.successResult)) { - const { apiEndpoints } = config.proxy - const baseURL = MarketingEvent.DATA.LOG_TESTER ? apiEndpoints.baseURLTest : apiEndpoints.baseURL - const successProxy = baseURL + '/send/sendtx' - let checkResult = false - try { - transactionLog.selectedFee.isRebroadcast = true - checkResult = await BlocksoftAxios.post(successProxy, { - raw: row.transactionRaw, - txRBF: typeof transactionLog.txRBF !== 'undefined' ? transactionLog.txRBF : false, - logData: transactionLog, - marketingData: MarketingEvent.DATA - }) - await BlocksoftCryptoLog.log(this._currencyCode + ' EthRawDS.send proxy success result', JSON.parse(JSON.stringify(checkResult))) - } catch (e3) { - if (config.debug.cryptoErrors) { - console.log(this._currencyCode + ' EthRawDS.send proxy success error ' + e3.message) - } - await BlocksoftCryptoLog.log(this._currencyCode + ' EthRawDS.send proxy success error ' + e3.message) - } - if (checkResult && typeof checkResult.data !== 'undefined') { - transactionLog.successResult = checkResult.data - } + if (this._trezorServer === 'none') { + if (this._currencyCode === 'ETH') { + try { + this._trezorServer = + await BlocksoftExternalSettings.getTrezorServer( + 'ETH_TREZOR_SERVER', + 'ETH.Broadcast' + ); + this._infuraProjectId = BlocksoftExternalSettings.getStatic( + 'ETH_INFURA_PROJECT_ID', + 'ETH.Broadcast' + ); + } catch (e) { + throw new Error(e.message + ' inside trezorServer'); + } + } else { + this._trezorServer = await BlocksoftExternalSettings.getTrezorServer( + 'ETH_ROPSTEN_TREZOR_SERVER', + 'ETH.Broadcast' + ); + } + } - await Database.setTableName('transactions_raw').setUpdateData({ - updateObj: { transactionLog: Database.escapeString(JSON.stringify(transactionLog)) }, - key: { id: row.id } - }).update() - } + const now = new Date().toISOString(); - let broadcastLog = '' - let link = '' - let broad - const updateObj = { - broadcastUpdated: now, - is_removed: '0' - } - if (this._currencyCode === 'ETH' || this._currencyCode === 'ETH_ROPSTEN') { - link = this._trezorServer + '/api/v2/sendtx/' - try { - broad = await BlocksoftAxios.post(link, row.transactionRaw) - broadcastLog = ' broadcasted ok ' + JSON.stringify(broad.data) - updateObj.is_removed = '1' - updateObj.removed_at = now - } catch (e) { - if (e.message.indexOf('transaction underpriced') !== -1 || e.message.indexOf('already known') !== -1) { - updateObj.is_removed = '1' - broadcastLog += ' already known' - } else { - updateObj.is_removed = '0' - broadcastLog += e.message - } - } - broadcastLog += ' ' + link + '; ' - MarketingEvent.logOnlyRealTime('v20_eth_resend_0 ' + row.transactionHash, { broadcastLog, ...updateObj }) - } + for (const row of result.array) { + try { + ret[row.transactionUnique] = row; + let transactionLog; + try { + transactionLog = row.transactionLog + ? JSON.parse(Database.unEscapeString(row.transactionLog)) + : row.transactionLog; + } catch (e) { + // do nothing + } - if (this._currencyCode === 'ETH') { - link = 'https://api.etherscan.io/api?module=proxy&action=eth_sendRawTransaction&apikey=YourApiKeyToken&hex=' - let broadcastLog1 = '' - try { - broad = await BlocksoftAxios.get(link + row.transactionRaw) - if (typeof broad.data.error !== 'undefined') { - throw new Error(JSON.stringify(broad.data.error)) - } - broadcastLog1 = ' broadcasted ok ' + JSON.stringify(broad.data) - updateObj.is_removed += '1' - updateObj.removed_at = now - } catch (e) { - if (e.message.indexOf('transaction underpriced') !== -1 || e.message.indexOf('already known') !== -1) { - updateObj.is_removed += '1' - broadcastLog1 += ' already known' - } else { - updateObj.is_removed += '0' - broadcastLog1 += e.message - } - } - broadcastLog1 += ' ' + link + '; ' - MarketingEvent.logOnlyRealTime('v20_eth_resend_1 ' + row.transactionHash, { broadcastLog1, ...updateObj }) + if ( + transactionLog && + typeof transactionLog.currencyCode !== 'undefined' && + (typeof transactionLog.successResult === 'undefined' || + !transactionLog.successResult) + ) { + const { apiEndpoints } = config.proxy; + const baseURL = MarketingEvent.DATA.LOG_TESTER + ? apiEndpoints.baseURLTest + : apiEndpoints.baseURL; + const successProxy = baseURL + '/send/sendtx'; + let checkResult = false; + try { + transactionLog.selectedFee.isRebroadcast = true; + checkResult = await BlocksoftAxios.post(successProxy, { + raw: row.transactionRaw, + txRBF: + typeof transactionLog.txRBF !== 'undefined' + ? transactionLog.txRBF + : false, + logData: transactionLog, + marketingData: MarketingEvent.DATA + }); + await BlocksoftCryptoLog.log( + this._currencyCode + ' EthRawDS.send proxy success result', + JSON.parse(JSON.stringify(checkResult)) + ); + } catch (e3) { + if (config.debug.cryptoErrors) { + console.log( + this._currencyCode + + ' EthRawDS.send proxy success error ' + + e3.message + ); + } + await BlocksoftCryptoLog.log( + this._currencyCode + + ' EthRawDS.send proxy success error ' + + e3.message + ); + } + if (checkResult && typeof checkResult.data !== 'undefined') { + transactionLog.successResult = checkResult.data; + } + await Database.setTableName('transactions_raw') + .setUpdateData({ + updateObj: { + transactionLog: Database.escapeString( + JSON.stringify(transactionLog) + ) + }, + key: { id: row.id } + }) + .update(); + } - link = 'https://mainnet.infura.io/v3/' + this._infuraProjectId - let broadcastLog2 = '' - try { - broad = await BlocksoftAxios.post(link, - { - 'jsonrpc': '2.0', - 'method': 'eth_sendRawTransaction', - 'params': [row.transactionRaw], - 'id': 1 - } - ) - if (typeof broad.data.error !== 'undefined') { - throw new Error(JSON.stringify(broad.data.error)) - } - broadcastLog2 = ' broadcasted ok ' + JSON.stringify(broad.data) - updateObj.is_removed += '1' - updateObj.removed_at = now - } catch (e) { - if (e.message.indexOf('transaction underpriced') !== -1 || e.message.indexOf('already known') !== -1) { - updateObj.is_removed += '1' - broadcastLog2 += ' already known' - } else { - updateObj.is_removed += '0' - broadcastLog2 += e.message - } + let broadcastLog = ''; + let link = ''; + let broad; + const updateObj = { + broadcastUpdated: now, + is_removed: '0' + }; + if ( + this._currencyCode === 'ETH' || + this._currencyCode === 'ETH_ROPSTEN' + ) { + link = this._trezorServer + '/api/v2/sendtx/'; + try { + broad = await BlocksoftAxios.post(link, row.transactionRaw); + broadcastLog = ' broadcasted ok ' + JSON.stringify(broad.data); + updateObj.is_removed = '1'; + updateObj.removed_at = now; + } catch (e) { + if ( + e.message.indexOf('transaction underpriced') !== -1 || + e.message.indexOf('already known') !== -1 + ) { + updateObj.is_removed = '1'; + broadcastLog += ' already known'; + } else { + updateObj.is_removed = '0'; + broadcastLog += e.message; + } + } + broadcastLog += ' ' + link + '; '; + MarketingEvent.logOnlyRealTime( + 'v20_eth_resend_0 ' + row.transactionHash, + { broadcastLog, ...updateObj } + ); + } - } - broadcastLog2 += ' ' + link + '; ' - MarketingEvent.logOnlyRealTime('v20_eth_resend_2 ' + row.transactionHash, { broadcastLog2, ...updateObj }) + if (this._currencyCode === 'ETH') { + link = + 'https://api.etherscan.io/api?module=proxy&action=eth_sendRawTransaction&apikey=YourApiKeyToken&hex='; + let broadcastLog1 = ''; + try { + broad = await BlocksoftAxios.get(link + row.transactionRaw); + if (typeof broad.data.error !== 'undefined') { + throw new Error(JSON.stringify(broad.data.error)); + } + broadcastLog1 = ' broadcasted ok ' + JSON.stringify(broad.data); + updateObj.is_removed += '1'; + updateObj.removed_at = now; + } catch (e) { + if ( + e.message.indexOf('transaction underpriced') !== -1 || + e.message.indexOf('already known') !== -1 + ) { + updateObj.is_removed += '1'; + broadcastLog1 += ' already known'; + } else { + updateObj.is_removed += '0'; + broadcastLog1 += e.message; + } + } + broadcastLog1 += ' ' + link + '; '; + MarketingEvent.logOnlyRealTime( + 'v20_eth_resend_1 ' + row.transactionHash, + { broadcastLog1, ...updateObj } + ); - if (updateObj.is_removed === '111') { // do ALL! - updateObj.is_removed = 1 - } else { - updateObj.is_removed = 0 - } + link = 'https://mainnet.infura.io/v3/' + this._infuraProjectId; + let broadcastLog2 = ''; + try { + broad = await BlocksoftAxios.post(link, { + jsonrpc: '2.0', + method: 'eth_sendRawTransaction', + params: [row.transactionRaw], + id: 1 + }); + if (typeof broad.data.error !== 'undefined') { + throw new Error(JSON.stringify(broad.data.error)); + } + broadcastLog2 = ' broadcasted ok ' + JSON.stringify(broad.data); + updateObj.is_removed += '1'; + updateObj.removed_at = now; + } catch (e) { + if ( + e.message.indexOf('transaction underpriced') !== -1 || + e.message.indexOf('already known') !== -1 + ) { + updateObj.is_removed += '1'; + broadcastLog2 += ' already known'; + } else { + updateObj.is_removed += '0'; + broadcastLog2 += e.message; + } + } + broadcastLog2 += ' ' + link + '; '; + MarketingEvent.logOnlyRealTime( + 'v20_eth_resend_2 ' + row.transactionHash, + { broadcastLog2, ...updateObj } + ); - broadcastLog = new Date().toISOString() + ' ' + broadcastLog + ' ' + broadcastLog1 + ' ' + broadcastLog2 + ' ' + (row.broadcastLog ? row.broadcastLog.substr(0, 1000) : '') - } else if (this._currencyCode === 'ETH_ROPSTEN') { - if (updateObj.is_removed === '1') { // do ALL! - updateObj.is_removed = 1 - } else { - updateObj.is_removed = 0 - } + if (updateObj.is_removed === '111') { + // do ALL! + updateObj.is_removed = 1; + } else { + updateObj.is_removed = 0; + } - broadcastLog = new Date().toISOString() + ' ' + broadcastLog + ' ' + (row.broadcastLog ? row.broadcastLog.substr(0, 1000) : '') - } + broadcastLog = + new Date().toISOString() + + ' ' + + broadcastLog + + ' ' + + broadcastLog1 + + ' ' + + broadcastLog2 + + ' ' + + (row.broadcastLog ? row.broadcastLog.substr(0, 1000) : ''); + } else if (this._currencyCode === 'ETH_ROPSTEN') { + if (updateObj.is_removed === '1') { + // do ALL! + updateObj.is_removed = 1; + } else { + updateObj.is_removed = 0; + } - if (this._currencyCode === 'ETH' || this._currencyCode === 'ETH_ROPSTEN') { - updateObj.broadcastLog = broadcastLog + broadcastLog = + new Date().toISOString() + + ' ' + + broadcastLog + + ' ' + + (row.broadcastLog ? row.broadcastLog.substr(0, 1000) : ''); + } - await Database.setTableName('transactions_raw').setUpdateData({ - updateObj, - key: { id: row.id } - }).update() - } + if ( + this._currencyCode === 'ETH' || + this._currencyCode === 'ETH_ROPSTEN' + ) { + updateObj.broadcastLog = broadcastLog; - } catch (e) { - throw new Error(e.message + ' inside row ' + row.transactionHash) - } - } - return ret + await Database.setTableName('transactions_raw') + .setUpdateData({ + updateObj, + key: { id: row.id } + }) + .update(); + } } catch (e) { - throw new Error(e.message + ' on EthRawDS.getAddress') + throw new Error(e.message + ' inside row ' + row.transactionHash); } + } + return ret; + } catch (e) { + throw new Error(e.message + ' on EthRawDS.getAddress'); } + } - async cleanRawHash(data) { - BlocksoftCryptoLog.log('EthRawDS cleanRawHash ', data) + async cleanRawHash(data) { + BlocksoftCryptoLog.log('EthRawDS cleanRawHash ', data); - const now = new Date().toISOString() - const sql = `UPDATE transactions_raw + const now = new Date().toISOString(); + const sql = `UPDATE transactions_raw SET is_removed=1, removed_at = '${now}' WHERE (is_removed=0 OR is_removed IS NULL) AND (currency_code='ETH' OR currency_code='ETH_ROPSTEN') - AND transaction_hash='${data.transactionHash}'` - await Database.query(sql) - } + AND transaction_hash='${data.transactionHash}'`; + await Database.query(sql); + } - async cleanRaw(data) { - BlocksoftCryptoLog.log('EthRawDS cleanRaw ', data) + async cleanRaw(data) { + BlocksoftCryptoLog.log('EthRawDS cleanRaw ', data); - if (typeof data.currencyCode !== 'undefined') { - this._currencyCode = data.currencyCode === 'ETH_ROPSTEN' ? 'ETH_ROPSTEN' : 'ETH' - } + if (typeof data.currencyCode !== 'undefined') { + this._currencyCode = + data.currencyCode === 'ETH_ROPSTEN' ? 'ETH_ROPSTEN' : 'ETH'; + } - const now = new Date().toISOString() - const sql = `UPDATE transactions_raw + const now = new Date().toISOString(); + const sql = `UPDATE transactions_raw SET is_removed=1, removed_at = '${now}' WHERE (is_removed=0 OR is_removed IS NULL) AND currency_code='${this._currencyCode}' AND address='${data.address.toLowerCase()}' - AND transaction_unique_key='${data.transactionUnique}'` - await Database.query(sql) - } + AND transaction_unique_key='${data.transactionUnique}'`; + await Database.query(sql); + } - async saveRaw(data) { - if (typeof data.currencyCode !== 'undefined') { - this._currencyCode = data.currencyCode === 'ETH_ROPSTEN' ? 'ETH_ROPSTEN' : 'ETH' - } - const now = new Date().toISOString() + async saveRaw(data) { + if (typeof data.currencyCode !== 'undefined') { + this._currencyCode = + data.currencyCode === 'ETH_ROPSTEN' ? 'ETH_ROPSTEN' : 'ETH'; + } + const now = new Date().toISOString(); - const sql = `UPDATE transactions_raw + const sql = `UPDATE transactions_raw SET is_removed=1, removed_at = '${now}' WHERE (is_removed=0 OR is_removed IS NULL) AND currency_code='${this._currencyCode}' AND address='${data.address.toLowerCase()}' - AND transaction_unique_key='${data.transactionUnique.toLowerCase()}'` - await Database.query(sql) + AND transaction_unique_key='${data.transactionUnique.toLowerCase()}'`; + await Database.query(sql); - const prepared = [{ - currency_code: this._currencyCode, - address: data.address.toLowerCase(), - transaction_unique_key: data.transactionUnique.toLowerCase(), - transaction_hash: data.transactionHash, - transaction_raw: data.transactionRaw, - transaction_log: Database.escapeString(JSON.stringify(data.transactionLog)), - created_at: now, - is_removed: 0 - }] - await Database.setTableName(tableName).setInsertData({ insertObjs: prepared }).insert() - } + const prepared = [ + { + currency_code: this._currencyCode, + address: data.address.toLowerCase(), + transaction_unique_key: data.transactionUnique.toLowerCase(), + transaction_hash: data.transactionHash, + transaction_raw: data.transactionRaw, + transaction_log: Database.escapeString( + JSON.stringify(data.transactionLog) + ), + created_at: now, + is_removed: 0 + } + ]; + await Database.setTableName(tableName) + .setInsertData({ insertObjs: prepared }) + .insert(); + } } -export default new EthRawDS() +export default new EthRawDS(); diff --git a/crypto/blockchains/fio/FioScannerProcessor.js b/crypto/blockchains/fio/FioScannerProcessor.js index acb8fe657..777823d84 100644 --- a/crypto/blockchains/fio/FioScannerProcessor.js +++ b/crypto/blockchains/fio/FioScannerProcessor.js @@ -1,130 +1,152 @@ -import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog' -import { getFioBalance, getTransactions } from './FioUtils' +import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog'; +import { getFioBalance, getTransactions } from './FioUtils'; export default class FioScannerProcessor { + /** + * @private + */ + _serverUrl = false; - /** - * @private - */ - _serverUrl = false + _blocksToConfirm = 10; - _blocksToConfirm = 10 + _maxBlockNumber = 500000000; - _maxBlockNumber = 500000000 + constructor(settings) { + this._settings = settings; + } - constructor(settings) { - this._settings = settings - } - - async _getCache(address, additionalData, walletHash) { - return false - } + async _getCache(address, additionalData, walletHash) { + return false; + } - /** - * @param address - * @param additionalData - * @param walletHash - * @returns {Promise} - * @private - */ - async _get(address, additionalData, walletHash) { - return false - } + /** + * @param address + * @param additionalData + * @param walletHash + * @returns {Promise} + * @private + */ + async _get(address, additionalData, walletHash) { + return false; + } - /** - * @param {string} address - * @param {*} additionalData - * @param {string} walletHash - * @return {Promise<{balance:*, unconfirmed:*, provider:string}>} - */ - async getBalanceBlockchainCache(address, additionalData, walletHash) { - BlocksoftCryptoLog.log(this._settings.currencyCode + ' FioScannerProcessor.getBalance (cache) started ' + address + ' of ' + walletHash) - return false - } + /** + * @param {string} address + * @param {*} additionalData + * @param {string} walletHash + * @return {Promise<{balance:*, unconfirmed:*, provider:string}>} + */ + async getBalanceBlockchainCache(address, additionalData, walletHash) { + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' FioScannerProcessor.getBalance (cache) started ' + + address + + ' of ' + + walletHash + ); + return false; + } - /** - * @param {string} address - * @param {*} additionalData - * @param {string} walletHash - * @return {Promise<{balance:*, unconfirmed:*, provider:string}>} - */ - async getBalanceBlockchain(address, additionalData, walletHash) { - BlocksoftCryptoLog.log(this._settings.currencyCode + ' FioScannerProcessor.getBalance started ' + address + ' of ' + walletHash) - const balance = await getFioBalance(address) - return { - balance, - unconfirmed: 0, - } - } + /** + * @param {string} address + * @param {*} additionalData + * @param {string} walletHash + * @return {Promise<{balance:*, unconfirmed:*, provider:string}>} + */ + async getBalanceBlockchain(address, additionalData, walletHash) { + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' FioScannerProcessor.getBalance started ' + + address + + ' of ' + + walletHash + ); + const balance = await getFioBalance(address); + return { + balance, + unconfirmed: 0 + }; + } - /** - * @param {string} scanData.account.address - * @param {string} scanData.account.walletHash - * @return {Promise<[UnifiedTransaction]>} - */ - async getTransactionsBlockchain(scanData) { - const address = scanData.account.address.trim() - const walletHash = scanData.account.walletHash - BlocksoftCryptoLog.log(this._settings.currencyCode + ' FioScannerProcessor.getTransactionsBlockchain started ' + address + ' of ' + walletHash) - const response = await getTransactions(address) - const actions = response['actions'] || [] - const lastBlock = response['last_irreversible_block'] + /** + * @param {string} scanData.account.address + * @param {string} scanData.account.walletHash + * @return {Promise<[UnifiedTransaction]>} + */ + async getTransactionsBlockchain(scanData) { + const address = scanData.account.address.trim(); + const walletHash = scanData.account.walletHash; + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' FioScannerProcessor.getTransactionsBlockchain started ' + + address + + ' of ' + + walletHash + ); + const response = await getTransactions(address); + const actions = response['actions'] || []; + const lastBlock = response['last_irreversible_block']; - const transactions = [] - let tx - for (tx of actions) { - const transaction = await this._unifyTransaction(address, lastBlock, tx) - if (transaction) { - transactions.push(transaction) - } - } - return transactions + const transactions = []; + let tx; + for (tx of actions) { + const transaction = await this._unifyTransaction(address, lastBlock, tx); + if (transaction) { + transactions.push(transaction); + } } + return transactions; + } - /** - * - * @param {string} address - * @param {string} lastBlock - * @param {Object} transaction - * @param {BigInteger} transaction.amount BigInteger {_d: Array(2), _s: -1} - * @param {string} transaction.approx_float_amount -0.00002724 - * @param {string} transaction.coinbase false - * @param {string} transaction.fee "27240000" - * @param {string} transaction.hash "ac319a3240f15dab342102fe248d3b95636f8a0bbfa962a5645521fac8fb86d3" - * @param {string} transaction.height 2152183 - * @param {string} transaction.id 10506991 - * @param {string} transaction.mempool: false - * @param {string} transaction.mixin 10 - * @param {string} transaction.payment_id "" - * @param {string} transaction.spent_outputs [{…}] - * @param {string} transaction.timestamp Tue Jul 28 2020 18:10:26 GMT+0300 (Восточная Европа, летнее время) {} - * @param {string} transaction.total_received "12354721582" - * @param {BigInteger} transaction.total_sent BigInteger {_d: Array(2), _s: 1} - * @param {string} transaction.unlock_time - * @return {Promise} - * @private - */ - async _unifyTransaction(address, lastBlock, transaction) { - const txData = transaction.action_trace?.act?.data - if (!txData?.payee_public_key || transaction.action_trace.receiver !== 'fio.token') { - return false - } + /** + * + * @param {string} address + * @param {string} lastBlock + * @param {Object} transaction + * @param {BigInteger} transaction.amount BigInteger {_d: Array(2), _s: -1} + * @param {string} transaction.approx_float_amount -0.00002724 + * @param {string} transaction.coinbase false + * @param {string} transaction.fee "27240000" + * @param {string} transaction.hash "ac319a3240f15dab342102fe248d3b95636f8a0bbfa962a5645521fac8fb86d3" + * @param {string} transaction.height 2152183 + * @param {string} transaction.id 10506991 + * @param {string} transaction.mempool: false + * @param {string} transaction.mixin 10 + * @param {string} transaction.payment_id "" + * @param {string} transaction.spent_outputs [{…}] + * @param {string} transaction.timestamp Tue Jul 28 2020 18:10:26 GMT+0300 (Восточная Европа, летнее время) {} + * @param {string} transaction.total_received "12354721582" + * @param {BigInteger} transaction.total_sent BigInteger {_d: Array(2), _s: 1} + * @param {string} transaction.unlock_time + * @return {Promise} + * @private + */ + async _unifyTransaction(address, lastBlock, transaction) { + const txData = transaction.action_trace?.act?.data; + if ( + !txData?.payee_public_key || + transaction.action_trace.receiver !== 'fio.token' + ) { + return false; + } - const transactionStatus = lastBlock - transaction['block_num'] > 5 ? 'success' : 'new' - const direction = (address === txData?.payee_public_key) ? 'income' : 'outcome' + const transactionStatus = + lastBlock - transaction['block_num'] > 5 ? 'success' : 'new'; + const direction = + address === txData?.payee_public_key ? 'income' : 'outcome'; - return { - transactionHash: transaction['action_trace']['trx_id'], - blockHash: '', - blockNumber: transaction['block_num'], - blockTime: transaction['block_time'], - blockConfirmations: lastBlock - transaction['block_num'], - transactionDirection: direction, - addressFrom: direction === 'income' ? '-' : txData?.payee_public_key, - addressTo: direction === 'income' ? txData?.payee_public_key : address, - addressAmount: txData?.amount, - transactionStatus: transactionStatus, - transactionFee: txData?.max_fee || 0 - } - } + return { + transactionHash: transaction['action_trace']['trx_id'], + blockHash: '', + blockNumber: transaction['block_num'], + blockTime: transaction['block_time'], + blockConfirmations: lastBlock - transaction['block_num'], + transactionDirection: direction, + addressFrom: direction === 'income' ? '-' : txData?.payee_public_key, + addressTo: direction === 'income' ? txData?.payee_public_key : address, + addressAmount: txData?.amount, + transactionStatus: transactionStatus, + transactionFee: txData?.max_fee || 0 + }; + } } diff --git a/crypto/blockchains/fio/FioSdkWrapper.js b/crypto/blockchains/fio/FioSdkWrapper.js index a35d72456..e49d78d80 100644 --- a/crypto/blockchains/fio/FioSdkWrapper.js +++ b/crypto/blockchains/fio/FioSdkWrapper.js @@ -1,57 +1,65 @@ /** * @version 0.77 */ -import { FIOSDK } from '@fioprotocol/fiosdk' +import { FIOSDK } from '@fioprotocol/fiosdk'; -import BlocksoftKeysStorage from '@crypto/actions/BlocksoftKeysStorage/BlocksoftKeysStorage' -import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings' -import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog' +import BlocksoftKeysStorage from '@crypto/actions/BlocksoftKeysStorage/BlocksoftKeysStorage'; +import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings'; +import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; -import config from '@app/config/config' +import config from '@app/config/config'; const fetchJson = async (uri, opts = {}) => { - // eslint-disable-next-line no-undef - return fetch(uri, opts) -} + // eslint-disable-next-line no-undef + return fetch(uri, opts); +}; export class FioSdkWrapper { - sdk - walletHash = false - async init(walletHash, source) { - if (this.walletHash === walletHash) return false - try { - const res = await BlocksoftKeysStorage.getAddressCache(walletHash + 'SpecialFio') - let publicKey, fioKey - if (res) { - publicKey = res.address - fioKey = res.privateKey - } else { - const mnemonic = await BlocksoftKeysStorage.getWalletMnemonic(walletHash, source + ' setSelectedWallet init for Fio') - let tmp = await FIOSDK.createPrivateKeyMnemonic(mnemonic) - fioKey = tmp.fioKey - tmp = FIOSDK.derivedPublicKey(fioKey) - publicKey = tmp.publicKey - await BlocksoftKeysStorage.setAddressCache(walletHash + 'SpecialFio', {address : publicKey, privateKey : fioKey}) - } - const link = BlocksoftExternalSettings.getStatic('FIO_BASE_URL') - this.sdk = new FIOSDK(fioKey, publicKey, link, fetchJson) - this.walletHash = walletHash - BlocksoftCryptoLog.log(`FioSdkWrapper.inited for ${walletHash}`) - } catch (e) { - if (config.debug.fioErrors) { - console.log('FioSdkWrapper.init error') - } - } - return true + sdk; + walletHash = false; + async init(walletHash, source) { + if (this.walletHash === walletHash) return false; + try { + const res = await BlocksoftKeysStorage.getAddressCache( + walletHash + 'SpecialFio' + ); + let publicKey, fioKey; + if (res) { + publicKey = res.address; + fioKey = res.privateKey; + } else { + const mnemonic = await BlocksoftKeysStorage.getWalletMnemonic( + walletHash, + source + ' setSelectedWallet init for Fio' + ); + let tmp = await FIOSDK.createPrivateKeyMnemonic(mnemonic); + fioKey = tmp.fioKey; + tmp = FIOSDK.derivedPublicKey(fioKey); + publicKey = tmp.publicKey; + await BlocksoftKeysStorage.setAddressCache(walletHash + 'SpecialFio', { + address: publicKey, + privateKey: fioKey + }); + } + const link = BlocksoftExternalSettings.getStatic('FIO_BASE_URL'); + this.sdk = new FIOSDK(fioKey, publicKey, link, fetchJson); + this.walletHash = walletHash; + BlocksoftCryptoLog.log(`FioSdkWrapper.inited for ${walletHash}`); + } catch (e) { + if (config.debug.fioErrors) { + console.log('FioSdkWrapper.init error'); + } } + return true; + } } -export const fioSdkWrapper = new FioSdkWrapper() +export const fioSdkWrapper = new FioSdkWrapper(); export const getFioSdk = () => { - if (typeof fioSdkWrapper?.sdk !== 'undefined' && fioSdkWrapper?.sdk) { - return fioSdkWrapper?.sdk - } - const link = BlocksoftExternalSettings.getStatic('FIO_BASE_URL') - return new FIOSDK(null, null, link, fetchJson) -} + if (typeof fioSdkWrapper?.sdk !== 'undefined' && fioSdkWrapper?.sdk) { + return fioSdkWrapper?.sdk; + } + const link = BlocksoftExternalSettings.getStatic('FIO_BASE_URL'); + return new FIOSDK(null, null, link, fetchJson); +}; diff --git a/crypto/blockchains/fio/FioTransferProcessor.ts b/crypto/blockchains/fio/FioTransferProcessor.ts index 0a23a11eb..1d7c0099a 100644 --- a/crypto/blockchains/fio/FioTransferProcessor.ts +++ b/crypto/blockchains/fio/FioTransferProcessor.ts @@ -1,84 +1,106 @@ /** * @version 0.20 */ -import { getFioSdk } from './FioSdkWrapper' -import { getFioBalance, transferTokens } from './FioUtils' -import { BlocksoftBlockchainTypes } from '../BlocksoftBlockchainTypes' -import BlocksoftUtils from '../../common/BlocksoftUtils' +import { getFioSdk } from './FioSdkWrapper'; +import { getFioBalance, transferTokens } from './FioUtils'; +import { BlocksoftBlockchainTypes } from '../BlocksoftBlockchainTypes'; +import BlocksoftUtils from '../../common/BlocksoftUtils'; -export default class FioTransferProcessor implements BlocksoftBlockchainTypes.TransferProcessor { - private _settings: any +export default class FioTransferProcessor + implements BlocksoftBlockchainTypes.TransferProcessor +{ + private _settings: any; - constructor(settings: any) { - this._settings = settings - } - - needPrivateForFee(): boolean { - return false - } + constructor(settings: any) { + this._settings = settings; + } - checkSendAllModal(data: { currencyCode: any }): boolean { - return false - } + needPrivateForFee(): boolean { + return false; + } - async getFeeRate(data: BlocksoftBlockchainTypes.TransferData, privateData: BlocksoftBlockchainTypes.TransferPrivateData, additionalData: {} = {}): Promise { - const { fee = 0 } = await getFioSdk().getFee('transfer_tokens_pub_key') - const result: BlocksoftBlockchainTypes.FeeRateResult = { - selectedFeeIndex: 0, - shouldShowFees : false, - fees : [ - { - langMsg: 'xrp_speed_one', - feeForTx: fee, - amountForTx: data.amount - } - ] - } as BlocksoftBlockchainTypes.FeeRateResult - return result - } + checkSendAllModal(data: { currencyCode: any }): boolean { + return false; + } - async getTransferAllBalance(data: BlocksoftBlockchainTypes.TransferData, privateData: BlocksoftBlockchainTypes.TransferPrivateData, additionalData: any = {}): Promise { - const { fee = 0 } = await getFioSdk().getFee('transfer_tokens_pub_key') - const balance = await getFioBalance(data.addressFrom) - if (balance === 0) { - return { - selectedTransferAllBalance: '0', - selectedFeeIndex: -1, - fees: [], - countedForBasicBalance: '0' - } + async getFeeRate( + data: BlocksoftBlockchainTypes.TransferData, + privateData: BlocksoftBlockchainTypes.TransferPrivateData, + additionalData: {} = {} + ): Promise { + const { fee = 0 } = await getFioSdk().getFee('transfer_tokens_pub_key'); + const result: BlocksoftBlockchainTypes.FeeRateResult = { + selectedFeeIndex: 0, + shouldShowFees: false, + fees: [ + { + langMsg: 'xrp_speed_one', + feeForTx: fee, + amountForTx: data.amount } + ] + } as BlocksoftBlockchainTypes.FeeRateResult; + return result; + } - const diff = BlocksoftUtils.diff(balance, fee) - if (diff*1 < 0) { - return { - selectedTransferAllBalance: '0', - selectedFeeIndex: -2, - fees: [], - countedForBasicBalance: '0' - } - } + async getTransferAllBalance( + data: BlocksoftBlockchainTypes.TransferData, + privateData: BlocksoftBlockchainTypes.TransferPrivateData, + additionalData: any = {} + ): Promise { + const { fee = 0 } = await getFioSdk().getFee('transfer_tokens_pub_key'); + const balance = await getFioBalance(data.addressFrom); + if (balance === 0) { + return { + selectedTransferAllBalance: '0', + selectedFeeIndex: -1, + fees: [], + countedForBasicBalance: '0' + }; + } - const result: BlocksoftBlockchainTypes.TransferAllBalanceResult = { - selectedFeeIndex: 0, - fees : [ - { - langMsg: 'xrp_speed_one', - feeForTx: fee, - amountForTx: diff - } - ], - selectedTransferAllBalance: diff - } as BlocksoftBlockchainTypes.TransferAllBalanceResult - return result + const diff = BlocksoftUtils.diff(balance, fee); + if (diff * 1 < 0) { + return { + selectedTransferAllBalance: '0', + selectedFeeIndex: -2, + fees: [], + countedForBasicBalance: '0' + }; } - async sendTx(data: BlocksoftBlockchainTypes.TransferData, privateData: BlocksoftBlockchainTypes.TransferPrivateData, uiData: BlocksoftBlockchainTypes.TransferUiData): Promise { - if (typeof uiData !== 'undefined' && typeof uiData.selectedFee !== 'undefined' && typeof uiData.selectedFee.rawOnly !== 'undefined' && uiData.selectedFee.rawOnly) { - // @todo ksu - return { rawOnly: uiData.selectedFee.rawOnly, raw : 'feature in development'} + const result: BlocksoftBlockchainTypes.TransferAllBalanceResult = { + selectedFeeIndex: 0, + fees: [ + { + langMsg: 'xrp_speed_one', + feeForTx: fee, + amountForTx: diff } - const txId = await transferTokens(data.addressTo, data.amount) - return { transactionHash: txId, transactionJson : {} } + ], + selectedTransferAllBalance: diff + } as BlocksoftBlockchainTypes.TransferAllBalanceResult; + return result; + } + + async sendTx( + data: BlocksoftBlockchainTypes.TransferData, + privateData: BlocksoftBlockchainTypes.TransferPrivateData, + uiData: BlocksoftBlockchainTypes.TransferUiData + ): Promise { + if ( + typeof uiData !== 'undefined' && + typeof uiData.selectedFee !== 'undefined' && + typeof uiData.selectedFee.rawOnly !== 'undefined' && + uiData.selectedFee.rawOnly + ) { + // @todo ksu + return { + rawOnly: uiData.selectedFee.rawOnly, + raw: 'feature in development' + }; } + const txId = await transferTokens(data.addressTo, data.amount); + return { transactionHash: txId, transactionJson: {} }; + } } diff --git a/crypto/blockchains/fio/FioUtils.js b/crypto/blockchains/fio/FioUtils.js index b003d7d7d..6fd53f912 100644 --- a/crypto/blockchains/fio/FioUtils.js +++ b/crypto/blockchains/fio/FioUtils.js @@ -1,96 +1,99 @@ -import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog' -import { getFioSdk } from './FioSdkWrapper' -import config from '../../../app/config/config' -import BlocksoftAxios from '../../common/BlocksoftAxios' -import { Fio } from '@fioprotocol/fiojs' -import { FIOSDK } from '@fioprotocol/fiosdk/src/FIOSDK' -import chunk from 'lodash/chunk' -import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings' +import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog'; +import { getFioSdk } from './FioSdkWrapper'; +import config from '../../../app/config/config'; +import BlocksoftAxios from '../../common/BlocksoftAxios'; +import { Fio } from '@fioprotocol/fiojs'; +import { FIOSDK } from '@fioprotocol/fiosdk/src/FIOSDK'; +import chunk from 'lodash/chunk'; +import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings'; export const resolveChainCode = (currencyCode, currencySymbol) => { - let chainCode = currencyCode - if (typeof currencyCode !== 'undefined' && currencyCode !== currencySymbol) { - const tmp = currencyCode.split('_') - if (typeof tmp[0] !== 'undefined' && tmp[0]) { - chainCode = tmp[0] - } + let chainCode = currencyCode; + if (typeof currencyCode !== 'undefined' && currencyCode !== currencySymbol) { + const tmp = currencyCode.split('_'); + if (typeof tmp[0] !== 'undefined' && tmp[0]) { + chainCode = tmp[0]; } - return chainCode -} + } + return chainCode; +}; export const resolveChainToken = (currencyCode, extend) => { - if (extend.currencyCode === 'BNB_SMART') { - return 'SMART' + if (extend.currencyCode === 'BNB_SMART') { + return 'SMART'; + } + if (typeof extend.tokenBlockchain !== 'undefined') { + if (extend.tokenBlockchain === 'BNB') { + return 'SMART'; } - if (typeof extend.tokenBlockchain !== 'undefined') { - if (extend.tokenBlockchain === 'BNB') { - return 'SMART' - } - } - return extend.currencySymbol -} + } + return extend.currencySymbol; +}; export const resolveCryptoCodes = (currencyCode) => { - let chainCode = currencyCode - let currencySymbol = currencyCode - const tmp = currencyCode.split('_') - if (typeof tmp[0] !== 'undefined' && tmp[0] && tmp[1]) { - chainCode = tmp[0] - currencySymbol = tmp[1] - } - return { - chain_code: chainCode, - token_code: currencySymbol - } -} + let chainCode = currencyCode; + let currencySymbol = currencyCode; + const tmp = currencyCode.split('_'); + if (typeof tmp[0] !== 'undefined' && tmp[0] && tmp[1]) { + chainCode = tmp[0]; + currencySymbol = tmp[1]; + } + return { + chain_code: chainCode, + token_code: currencySymbol + }; +}; export const isFioAddressValid = (address) => { - if (address) { - try { - FIOSDK.isFioAddressValid(address) - return true - } catch (e) { - } - } - return false -} + if (address) { + try { + FIOSDK.isFioAddressValid(address); + return true; + } catch (e) {} + } + return false; +}; export const isFioAddressRegistered = async (address) => { - if (!isFioAddressValid(address)) { - return false - } + if (!isFioAddressValid(address)) { + return false; + } - try { - const response = await getFioSdk().isAvailable(address) - return response['is_registered'] === 1 - } catch (e) { - formatError('FIO isFioAddressRegistered ', e) - return false - } -} + try { + const response = await getFioSdk().isAvailable(address); + return response['is_registered'] === 1; + } catch (e) { + formatError('FIO isFioAddressRegistered ', e); + return false; + } +}; export const getPubAddress = async (fioAddress, chainCode, tokenCode) => { - try { - const response = await getFioSdk().getPublicAddress(fioAddress, chainCode, tokenCode) - return response['public_address'] - } catch (e) { - formatError('FIO getPubAddress', e) - return null - } -} + try { + const response = await getFioSdk().getPublicAddress( + fioAddress, + chainCode, + tokenCode + ); + return response['public_address']; + } catch (e) { + formatError('FIO getPubAddress', e); + return null; + } +}; export const getAccountFioName = async () => { - try { - const sdk = getFioSdk() - const fioPublicKey = sdk.getFioPublicKey() - const response = await getFioSdk().getFioNames(fioPublicKey) - const addresses = response['fio_addresses'] || [] - return addresses[0]?.fio_address - } catch (e) { - formatError('FIO.getAccountFioName ', e) - return null - } -} + try { + const sdk = getFioSdk(); + const fioPublicKey = sdk.getFioPublicKey(); + const response = await getFioSdk().getFioNames(fioPublicKey); + const addresses = response['fio_addresses'] || []; + return addresses[0]?.fio_address; + } catch (e) { + formatError('FIO.getAccountFioName ', e); + return null; + } +}; /** * Returns FIO Addresses and FIO Domains owned by this public key. @@ -99,48 +102,60 @@ export const getAccountFioName = async () => { * @return Promise<[ { fio_address:*, expiration:* } ]> */ export const getFioNames = async (fioPublicKey) => { - try { - const response = await getFioSdk().getFioNames(fioPublicKey) - return response['fio_addresses'] || [] - } catch (e) { - formatError('FIO getFioNames', e) - return [] - } -} + try { + const response = await getFioSdk().getFioNames(fioPublicKey); + return response['fio_addresses'] || []; + } catch (e) { + formatError('FIO getFioNames', e); + return []; + } +}; export const getFioBalance = async (fioPublicKey) => { - try { - const response = await getFioSdk().getFioBalance(fioPublicKey) - return response['balance'] || 0 - } catch (e) { - formatError('FIO getFioBalance', e) - return 0 - } -} + try { + const response = await getFioSdk().getFioBalance(fioPublicKey); + return response['balance'] || 0; + } catch (e) { + formatError('FIO getFioBalance', e); + return 0; + } +}; -export const getSentFioRequests = async (fioPublicKey, limit = 100, offset = 0) => { - try { - BlocksoftCryptoLog.log(`FIO getSentFioRequests started ${fioPublicKey}`) - const response = await getFioSdk().getSentFioRequests(limit, offset) - const requests = response['requests'] || [] - return requests.sort((a, b) => new Date(b.time_stamp) - new Date(a.time_stamp)) - } catch (e) { - formatError('FIO getSentFioRequests', e) - return [] - } -} +export const getSentFioRequests = async ( + fioPublicKey, + limit = 100, + offset = 0 +) => { + try { + BlocksoftCryptoLog.log(`FIO getSentFioRequests started ${fioPublicKey}`); + const response = await getFioSdk().getSentFioRequests(limit, offset); + const requests = response['requests'] || []; + return requests.sort( + (a, b) => new Date(b.time_stamp) - new Date(a.time_stamp) + ); + } catch (e) { + formatError('FIO getSentFioRequests', e); + return []; + } +}; -export const getPendingFioRequests = async (fioPublicKey, limit = 100, offset = 0) => { - try { - BlocksoftCryptoLog.log(`FIO getPendingFioRequests started ${fioPublicKey}`) - const response = await getFioSdk().getPendingFioRequests(limit, offset) - const requests = response['requests'] || [] - return requests.sort((a, b) => new Date(b.time_stamp) - new Date(a.time_stamp)) - } catch (e) { - formatError('FIO getPendingFioRequests', e) - return [] - } -} +export const getPendingFioRequests = async ( + fioPublicKey, + limit = 100, + offset = 0 +) => { + try { + BlocksoftCryptoLog.log(`FIO getPendingFioRequests started ${fioPublicKey}`); + const response = await getFioSdk().getPendingFioRequests(limit, offset); + const requests = response['requests'] || []; + return requests.sort( + (a, b) => new Date(b.time_stamp) - new Date(a.time_stamp) + ); + } catch (e) { + formatError('FIO getPendingFioRequests', e); + return []; + } +}; /** * This call allows a public address of the specific blockchain type to be added to the FIO Address. @@ -150,76 +165,94 @@ export const getPendingFioRequests = async (fioPublicKey, limit = 100, offset = * @param tokenCode Token code to be used with that public address. * @param publicAddress The public address to be added to the FIO Address for the specified token. */ -export const addCryptoPublicAddress = async ({ fioName, chainCode, tokenCode, publicAddress }) => { - try { - const { fee = 0 } = await getFioSdk().getFeeForAddPublicAddress(fioName) - const response = await getFioSdk().addPublicAddress( - fioName, - chainCode, - tokenCode, - publicAddress, - fee, - null - ) - const isOK = response['status'] === 'OK' - if (!isOK) { - await BlocksoftCryptoLog.log('FIO addPublicAddress error', response) - } - return isOK - } catch (e) { - formatError('FIO addPubAddress', e) +export const addCryptoPublicAddress = async ({ + fioName, + chainCode, + tokenCode, + publicAddress +}) => { + try { + const { fee = 0 } = await getFioSdk().getFeeForAddPublicAddress(fioName); + const response = await getFioSdk().addPublicAddress( + fioName, + chainCode, + tokenCode, + publicAddress, + fee, + null + ); + const isOK = response['status'] === 'OK'; + if (!isOK) { + await BlocksoftCryptoLog.log('FIO addPublicAddress error', response); } -} + return isOK; + } catch (e) { + formatError('FIO addPubAddress', e); + } +}; -export const addCryptoPublicAddresses = async ({ fioName, publicAddresses }) => { - if (!publicAddresses || Object.keys(publicAddresses).length === 0) return true +export const addCryptoPublicAddresses = async ({ + fioName, + publicAddresses +}) => { + if (!publicAddresses || Object.keys(publicAddresses).length === 0) + return true; - let isOK = true - for await (const publicAddressesChunk of chunk(publicAddresses, 5)) { - try { - const { fee = 0 } = await getFioSdk().getFeeForAddPublicAddress(fioName) - const response = await getFioSdk().addPublicAddresses( - fioName, - publicAddressesChunk, - fee, - null - ) + let isOK = true; + for await (const publicAddressesChunk of chunk(publicAddresses, 5)) { + try { + const { fee = 0 } = await getFioSdk().getFeeForAddPublicAddress(fioName); + const response = await getFioSdk().addPublicAddresses( + fioName, + publicAddressesChunk, + fee, + null + ); - if (response['status'] !== 'OK') { - await BlocksoftCryptoLog.log('FIO addPublicAddress error', response) - isOK = false - } - } catch (e) { - formatError('FIO addPubAddress', e) - } + if (response['status'] !== 'OK') { + await BlocksoftCryptoLog.log('FIO addPublicAddress error', response); + isOK = false; + } + } catch (e) { + formatError('FIO addPubAddress', e); } - return isOK -} + } + return isOK; +}; -export const removeCryptoPublicAddresses = async ({ fioName, publicAddresses }) => { - if (!publicAddresses || Object.keys(publicAddresses).length === 0) return true +export const removeCryptoPublicAddresses = async ({ + fioName, + publicAddresses +}) => { + if (!publicAddresses || Object.keys(publicAddresses).length === 0) + return true; - let isOK = true - for await (const publicAddressesChunk of chunk(publicAddresses, 5)) { - try { - const { fee = 0 } = await getFioSdk().getFeeForRemovePublicAddresses(fioName) - const response = await getFioSdk().removePublicAddresses( - fioName, - publicAddressesChunk, - fee, - null - ) + let isOK = true; + for await (const publicAddressesChunk of chunk(publicAddresses, 5)) { + try { + const { fee = 0 } = await getFioSdk().getFeeForRemovePublicAddresses( + fioName + ); + const response = await getFioSdk().removePublicAddresses( + fioName, + publicAddressesChunk, + fee, + null + ); - if (response['status'] !== 'OK') { - await BlocksoftCryptoLog.log('FIO removeCryptoPublicAddresses error', response) - isOK = false - } - } catch (e) { - formatError('FIO removeCryptoPublicAddresses', e) - } + if (response['status'] !== 'OK') { + await BlocksoftCryptoLog.log( + 'FIO removeCryptoPublicAddresses error', + response + ); + isOK = false; + } + } catch (e) { + formatError('FIO removeCryptoPublicAddresses', e); } - return isOK -} + } + return isOK; +}; /** * Create a new funds request on the FIO chain. @@ -232,79 +265,95 @@ export const removeCryptoPublicAddresses = async ({ fioName, publicAddresses }) * @param tokenCode Code of the token represented in amount requested. * @param memo */ -export const requestFunds = async ({ payerFioAddress, payeeFioAddress, payeeTokenPublicAddress, amount, chainCode, tokenCode, memo }) => { - try { - BlocksoftCryptoLog.log(`FIO requestFunds started ${payerFioAddress} -> ${payeeFioAddress} ${amount} ${tokenCode} (${chainCode})`) - const { fee = 0 } = await getFioSdk().getFeeForNewFundsRequest(payeeFioAddress) - const response = await getFioSdk().requestFunds( - payerFioAddress, - payeeFioAddress, - payeeTokenPublicAddress, - amount, - chainCode, - tokenCode, - memo, - fee, - null, - null, - null, - null - ) +export const requestFunds = async ({ + payerFioAddress, + payeeFioAddress, + payeeTokenPublicAddress, + amount, + chainCode, + tokenCode, + memo +}) => { + try { + BlocksoftCryptoLog.log( + `FIO requestFunds started ${payerFioAddress} -> ${payeeFioAddress} ${amount} ${tokenCode} (${chainCode})` + ); + const { fee = 0 } = await getFioSdk().getFeeForNewFundsRequest( + payeeFioAddress + ); + const response = await getFioSdk().requestFunds( + payerFioAddress, + payeeFioAddress, + payeeTokenPublicAddress, + amount, + chainCode, + tokenCode, + memo, + fee, + null, + null, + null, + null + ); - await BlocksoftCryptoLog.log('FIO requestFunds result', response) - return response - } catch (e) { - formatError('FIO requestFunds', e) - const errorMessage = e.json?.fields - ? e.json?.fields[0].error - : e.json?.message + await BlocksoftCryptoLog.log('FIO requestFunds result', response); + return response; + } catch (e) { + formatError('FIO requestFunds', e); + const errorMessage = e.json?.fields + ? e.json?.fields[0].error + : e.json?.message; - return { - error: errorMessage || 'FIO request creation error' - } - } -} + return { + error: errorMessage || 'FIO request creation error' + }; + } +}; export const getTransactions = async (publicKey) => { - - try { - const link = BlocksoftExternalSettings.getStatic('FIO_HISTORY_URL') - const accountHash = Fio.accountHash(publicKey) - const response = await BlocksoftAxios.post(link + 'get_actions', { - 'account_name': accountHash, - 'pos': -1 - }) - return response?.data - } catch (e) { - formatError('FIO getTransactions', e) - return [] - } -} + try { + const link = BlocksoftExternalSettings.getStatic('FIO_HISTORY_URL'); + const accountHash = Fio.accountHash(publicKey); + const response = await BlocksoftAxios.post(link + 'get_actions', { + account_name: accountHash, + pos: -1 + }); + return response?.data; + } catch (e) { + formatError('FIO getTransactions', e); + return []; + } +}; export const transferTokens = async (addressTo, amount) => { - try { - const { fee = 0 } = await getFioSdk().getFee('transfer_tokens_pub_key') - const result = await getFioSdk().transferTokens(addressTo, amount, fee, null) - return result['transaction_id'] - } catch (e) { - formatError('FIO transferTokens', e) - const errorMessage = e.json?.fields - ? e.json?.fields[0].error - : e.json?.message - throw new Error(errorMessage || 'FIO token transfer error') - } -} + try { + const { fee = 0 } = await getFioSdk().getFee('transfer_tokens_pub_key'); + const result = await getFioSdk().transferTokens( + addressTo, + amount, + fee, + null + ); + return result['transaction_id']; + } catch (e) { + formatError('FIO transferTokens', e); + const errorMessage = e.json?.fields + ? e.json?.fields[0].error + : e.json?.message; + throw new Error(errorMessage || 'FIO token transfer error'); + } +}; export const rejectFioFundsRequest = async (fioRequestId, payerFioAddress) => { - try { - const sdk = getFioSdk() - const { fee = 0 } = await sdk.getFeeForRejectFundsRequest(payerFioAddress) - const result = await sdk.rejectFundsRequest(`${fioRequestId}`, fee, null) - return result['status'] === 'request_rejected' - } catch (e) { - formatError('FIO rejectFioRequest', e) - } -} + try { + const sdk = getFioSdk(); + const { fee = 0 } = await sdk.getFeeForRejectFundsRequest(payerFioAddress); + const result = await sdk.rejectFundsRequest(`${fioRequestId}`, fee, null); + return result['status'] === 'request_rejected'; + } catch (e) { + formatError('FIO rejectFioRequest', e); + } +}; /** * @@ -323,47 +372,71 @@ export const rejectFioFundsRequest = async (fioRequestId, payerFioAddress) => { * @param memo */ export const recordFioObtData = async ({ - fioRequestId = '', - payerFioAddress = '', - payeeFioAddress = '', - payerTokenPublicAddress, - payeeTokenPublicAddress, - amount, - chainCode, - tokenCode, - obtId, - memo - }) => { - try { - const sdk = getFioSdk() - const { fee = 0 } = await sdk.getFeeForRecordObtData(payerFioAddress) - const result = await sdk.recordObtData(fioRequestId, payerFioAddress, payeeFioAddress, payerTokenPublicAddress, payeeTokenPublicAddress, amount, chainCode, tokenCode, 'sent_to_blockchain', obtId, fee, null, null, memo, null, null) - return !!result['status'] - } catch (e) { - formatError('FIO recordFioObtData', e) - } -} + fioRequestId = '', + payerFioAddress = '', + payeeFioAddress = '', + payerTokenPublicAddress, + payeeTokenPublicAddress, + amount, + chainCode, + tokenCode, + obtId, + memo +}) => { + try { + const sdk = getFioSdk(); + const { fee = 0 } = await sdk.getFeeForRecordObtData(payerFioAddress); + const result = await sdk.recordObtData( + fioRequestId, + payerFioAddress, + payeeFioAddress, + payerTokenPublicAddress, + payeeTokenPublicAddress, + amount, + chainCode, + tokenCode, + 'sent_to_blockchain', + obtId, + fee, + null, + null, + memo, + null, + null + ); + return !!result['status']; + } catch (e) { + formatError('FIO recordFioObtData', e); + } +}; export const getFioObtData = async (tokenCode, offset = 0, limit = 100) => { - let res = false - try { - res = await getFioSdk().getObtData(limit, offset, tokenCode) - } catch (e) { - formatError('FIO.getFioObtData ' + tokenCode + ' ' + limit + ' ' + offset, e) - } - return res -} + let res = false; + try { + res = await getFioSdk().getObtData(limit, offset, tokenCode); + } catch (e) { + formatError( + 'FIO.getFioObtData ' + tokenCode + ' ' + limit + ' ' + offset, + e + ); + } + return res; +}; const formatError = (title, e) => { - if (config.debug.fioErrors) { - console.log(title + ' error', e.json, e) - } - if (e.message.indexOf('Error 404') === -1 && e.message.indexOf('Network request failed') === -1) { - BlocksoftCryptoLog.err(title + ' error ' + e.message, e.json || false) - } else { - const msg = title + ' 404 notice ' + e.message + ' ' + JSON.stringify(e.json) - if (msg.indexOf('No FIO Requests') === -1) { - BlocksoftCryptoLog.log(msg) - } + if (config.debug.fioErrors) { + console.log(title + ' error', e.json, e); + } + if ( + e.message.indexOf('Error 404') === -1 && + e.message.indexOf('Network request failed') === -1 + ) { + BlocksoftCryptoLog.err(title + ' error ' + e.message, e.json || false); + } else { + const msg = + title + ' 404 notice ' + e.message + ' ' + JSON.stringify(e.json); + if (msg.indexOf('No FIO Requests') === -1) { + BlocksoftCryptoLog.log(msg); } -} + } +}; diff --git a/crypto/blockchains/metis/MetisScannerProcessor.js b/crypto/blockchains/metis/MetisScannerProcessor.js index 111fc1455..4efc2322c 100644 --- a/crypto/blockchains/metis/MetisScannerProcessor.js +++ b/crypto/blockchains/metis/MetisScannerProcessor.js @@ -1,34 +1,44 @@ /** * @version 0.5 */ -import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog' +import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog'; - -import EthScannerProcessor from '@crypto/blockchains/eth/EthScannerProcessor' +import EthScannerProcessor from '@crypto/blockchains/eth/EthScannerProcessor'; export default class MetisScannerProcessor extends EthScannerProcessor { + /** + * @param {string} scanData.account.address + * @return {Promise<[UnifiedTransaction]>} + */ + async getTransactionsBlockchain(scanData) { + const address = scanData.account.address; + await BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' MetisScannerProcessor.getTransactions started ' + + address + ); + const transactions = await super.getTransactionsBlockchain(scanData); - /** - * @param {string} scanData.account.address - * @return {Promise<[UnifiedTransaction]>} - */ - async getTransactionsBlockchain(scanData) { - const address = scanData.account.address - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' MetisScannerProcessor.getTransactions started ' + address) - const transactions = await super.getTransactionsBlockchain(scanData) - - // https://andromeda-explorer.metis.io/token/0xDeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD0000/token-transfers - // actual deposits and withdrawals done as erc20 token transfer - const tmp = this._etherscanApiPath.split('/') - const depositLink = `https://${tmp[2]}/api?module=account&action=tokentx&sort=desc&contractaddress=0xDeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD0000&address=${address}` - const depositLogTitle = this._settings.currencyCode + ' EthScannerProcessor.getTransactions etherscan deposits' - const depositTransactions = await this._getFromEtherscan(address, depositLink, depositLogTitle, false, {}) - if (depositTransactions) { - for (const transactionHash in depositTransactions) { - transactions.push(depositTransactions[transactionHash]) - } - } - - return transactions + // https://andromeda-explorer.metis.io/token/0xDeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD0000/token-transfers + // actual deposits and withdrawals done as erc20 token transfer + const tmp = this._etherscanApiPath.split('/'); + const depositLink = `https://${tmp[2]}/api?module=account&action=tokentx&sort=desc&contractaddress=0xDeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD0000&address=${address}`; + const depositLogTitle = + this._settings.currencyCode + + ' EthScannerProcessor.getTransactions etherscan deposits'; + const depositTransactions = await this._getFromEtherscan( + address, + depositLink, + depositLogTitle, + false, + {} + ); + if (depositTransactions) { + for (const transactionHash in depositTransactions) { + transactions.push(depositTransactions[transactionHash]); + } } + + return transactions; + } } diff --git a/crypto/blockchains/metis/MetisTransferProcessor.ts b/crypto/blockchains/metis/MetisTransferProcessor.ts index f2acb3d9f..1b5d5a92a 100644 --- a/crypto/blockchains/metis/MetisTransferProcessor.ts +++ b/crypto/blockchains/metis/MetisTransferProcessor.ts @@ -2,57 +2,76 @@ * @author Ksu * @version 0.43 */ -import { BlocksoftBlockchainTypes } from '@crypto/blockchains/BlocksoftBlockchainTypes' -import EthTransferProcessor from '@crypto/blockchains/eth/EthTransferProcessor' -import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings' -import BlocksoftAxios from '@crypto/common/BlocksoftAxios' -import BlocksoftUtils from '@crypto/common/BlocksoftUtils' +import { BlocksoftBlockchainTypes } from '@crypto/blockchains/BlocksoftBlockchainTypes'; +import EthTransferProcessor from '@crypto/blockchains/eth/EthTransferProcessor'; +import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings'; +import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; +import BlocksoftUtils from '@crypto/common/BlocksoftUtils'; +export default class MetisTransferProcessor + extends EthTransferProcessor + implements BlocksoftBlockchainTypes.TransferProcessor +{ + async getFeeRate( + data: BlocksoftBlockchainTypes.TransferData, + privateData: BlocksoftBlockchainTypes.TransferPrivateData, + additionalData: {} = {} + ): Promise { + if ( + typeof additionalData.gasPrice === 'undefined' || + !additionalData.gasPrice + ) { + additionalData.gasPrice = BlocksoftExternalSettings.getStatic( + this._mainCurrencyCode + '_PRICE' + ); + additionalData.gasPriceTitle = 'speed_blocks_2'; + } -export default class MetisTransferProcessor extends EthTransferProcessor implements BlocksoftBlockchainTypes.TransferProcessor { - - async getFeeRate(data: BlocksoftBlockchainTypes.TransferData, privateData: BlocksoftBlockchainTypes.TransferPrivateData, additionalData: {} = {}): Promise { - if (typeof additionalData.gasPrice === 'undefined' || !additionalData.gasPrice) { - additionalData.gasPrice = BlocksoftExternalSettings.getStatic(this._mainCurrencyCode + '_PRICE') - additionalData.gasPriceTitle = 'speed_blocks_2' - } - - let value = 0 - try { - if (data.amount.indexOf('0x') === 0) { - value = data.amount - } else { - value = '0x' + BlocksoftUtils.decimalToHex(data.amount) - } - } catch (e) { - throw new Error(e.message + ' with data.amount ' + data.amount) - } - const params = { - 'jsonrpc': '2.0', - 'method': 'eth_estimateGas', - 'params': [ - { - 'from': data.addressFrom, - 'to': data.addressTo, - 'value': value, - 'data': '0x' - } - ], - 'id': 1 + let value = 0; + try { + if (data.amount.indexOf('0x') === 0) { + value = data.amount; + } else { + value = '0x' + BlocksoftUtils.decimalToHex(data.amount); + } + } catch (e) { + throw new Error(e.message + ' with data.amount ' + data.amount); + } + const params = { + jsonrpc: '2.0', + method: 'eth_estimateGas', + params: [ + { + from: data.addressFrom, + to: data.addressTo, + value: value, + data: '0x' } - const tmp = await BlocksoftAxios.post( BlocksoftExternalSettings.getStatic('METIS_SERVER'), params) + ], + id: 1 + }; + const tmp = await BlocksoftAxios.post( + BlocksoftExternalSettings.getStatic('METIS_SERVER'), + params + ); - if (typeof tmp !== 'undefined' && typeof tmp.data !== 'undefined') { - if (typeof tmp.data.result !== 'undefined') { - additionalData.gasLimit = BlocksoftUtils.hexToDecimalWalletConnect(tmp.data.result) - } else if (typeof tmp.data.error !== 'undefined') { - throw new Error(tmp.data.error.message) - } - } - return super.getFeeRate(data, privateData, additionalData) + if (typeof tmp !== 'undefined' && typeof tmp.data !== 'undefined') { + if (typeof tmp.data.result !== 'undefined') { + additionalData.gasLimit = BlocksoftUtils.hexToDecimalWalletConnect( + tmp.data.result + ); + } else if (typeof tmp.data.error !== 'undefined') { + throw new Error(tmp.data.error.message); + } } + return super.getFeeRate(data, privateData, additionalData); + } - canRBF(data: BlocksoftBlockchainTypes.DbAccount, transaction: BlocksoftBlockchainTypes.DbTransaction, source: string): boolean { - return false - } + canRBF( + data: BlocksoftBlockchainTypes.DbAccount, + transaction: BlocksoftBlockchainTypes.DbTransaction, + source: string + ): boolean { + return false; + } } diff --git a/crypto/blockchains/one/OneScannerProcessor.js b/crypto/blockchains/one/OneScannerProcessor.js index 13c76f6f5..419b435c2 100644 --- a/crypto/blockchains/one/OneScannerProcessor.js +++ b/crypto/blockchains/one/OneScannerProcessor.js @@ -2,178 +2,233 @@ * https://docs.harmony.one/home/developers/api/methods/account-methods/hmy_getbalance * https://docs.harmony.one/home/developers/api/methods/transaction-related-methods/hmy_gettransactionshistory#api-v2 */ -import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog' -import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings' -import BlocksoftAxios from '@crypto/common/BlocksoftAxios' -import OneUtils from '@crypto/blockchains/one/ext/OneUtils' -import BlocksoftUtils from '@crypto/common/BlocksoftUtils' -import config from '@app/config/config' +import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; +import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings'; +import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; +import OneUtils from '@crypto/blockchains/one/ext/OneUtils'; +import BlocksoftUtils from '@crypto/common/BlocksoftUtils'; +import config from '@app/config/config'; export default class OneScannerProcessor { + _blocksToConfirm = 10; - _blocksToConfirm = 10 + constructor(settings) { + this._settings = settings; + } - constructor(settings) { - this._settings = settings + /** + * @param {string} address + * @param {*} additionalData + * @param {string} walletHash + * @return {Promise<{balance:*, unconfirmed:*, provider:string}>} + */ + async getBalanceBlockchain(address, additionalData, walletHash) { + const oneAddress = OneUtils.toOneAddress(address); + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' OneScannerProcessor.getBalanceBlockchain started ' + + address + + ' ' + + oneAddress + ); + try { + const apiPath = BlocksoftExternalSettings.getStatic('ONE_SERVER'); + const data = { + jsonrpc: '2.0', + id: 1, + method: 'hmy_getBalance', + params: [oneAddress, 'latest'] + }; + const res = await BlocksoftAxios._request(apiPath, 'POST', data); + if (typeof res.data === 'undefined') { + return false; + } + if (typeof res.data.error !== 'undefined') { + throw new Error(JSON.stringify(res.data.error)); + } + if (typeof res.data.result === 'undefined') { + return false; + } + const balance = BlocksoftUtils.hexToDecimalBigger(res.data.result); + return { + balance, + unconfirmed: 0 + }; + } catch (e) { + if (config.debug.cryptoErrors) { + console.log( + this._settings.currencyCode + + ' OneScannerProcessor.getBalanceBlockchain address ' + + address + + ' ' + + oneAddress + + ' error ' + + e.message + ); + } + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' OneScannerProcessor.getBalanceBlockchain address ' + + address + + ' ' + + oneAddress + + ' error ' + + e.message + ); + return false; } + } - /** - * @param {string} address - * @param {*} additionalData - * @param {string} walletHash - * @return {Promise<{balance:*, unconfirmed:*, provider:string}>} - */ - async getBalanceBlockchain(address, additionalData, walletHash) { - const oneAddress = OneUtils.toOneAddress(address) - BlocksoftCryptoLog.log(this._settings.currencyCode + ' OneScannerProcessor.getBalanceBlockchain started ' + address + ' ' + oneAddress) - try { - - const apiPath = BlocksoftExternalSettings.getStatic('ONE_SERVER') - const data = { - 'jsonrpc': '2.0', - 'id': 1, - 'method': 'hmy_getBalance', - 'params': [ - oneAddress, - 'latest' - ] - } - const res = await BlocksoftAxios._request(apiPath, 'POST', data) - if (typeof res.data === 'undefined') { - return false - } - if (typeof res.data.error !== 'undefined') { - throw new Error(JSON.stringify(res.data.error)) - } - if (typeof res.data.result === 'undefined') { - return false - } - const balance = BlocksoftUtils.hexToDecimalBigger(res.data.result) - return { - balance, - unconfirmed: 0 - } - } catch (e) { - if (config.debug.cryptoErrors) { - console.log(this._settings.currencyCode + ' OneScannerProcessor.getBalanceBlockchain address ' + address + ' ' + oneAddress + ' error ' + e.message) - } - BlocksoftCryptoLog.log(this._settings.currencyCode + ' OneScannerProcessor.getBalanceBlockchain address ' + address + ' ' + oneAddress + ' error ' + e.message) - return false + /** + * @param {string} scanData.account.address + * @param {string} scanData.account.walletHash + * @return {Promise<[UnifiedTransaction]>} + */ + async getTransactionsBlockchain(scanData) { + const { address } = scanData.account; + const oneAddress = OneUtils.toOneAddress(address); + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' OneScannerProcessor.getTransactionsBlockchain started ' + + address + + ' ' + + oneAddress + ); + try { + const apiPath = BlocksoftExternalSettings.getStatic('ONE_SERVER'); + const data = { + jsonrpc: '2.0', + id: 1, + method: 'hmyv2_getTransactionsHistory', + params: [ + { + address: oneAddress, + pageIndex: 0, + pageSize: 20, + fullTx: true, + txType: 'ALL', + order: 'DESC' + } + ] + }; + const res = await BlocksoftAxios._request(apiPath, 'POST', data); + if ( + typeof res.data === 'undefined' || + typeof res.data.result === 'undefined' || + typeof res.data.result.transactions === 'undefined' + ) { + return false; + } + const transactions = []; + for (const tx of res.data.result.transactions) { + const transaction = await this._unifyTransaction( + address, + oneAddress, + tx + ); + if (transaction) { + transactions.push(transaction); } + } + return transactions; + } catch (e) { + if (config.debug.cryptoErrors) { + console.log( + this._settings.currencyCode + + ' OneScannerProcessor.getTransactionsBlockchain address ' + + address + + ' error ' + + e.message + ); + } + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' OneScannerProcessor.getTransactionsBlockchain address ' + + address + + ' error ' + + e.message + ); + return false; } + } - /** - * @param {string} scanData.account.address - * @param {string} scanData.account.walletHash - * @return {Promise<[UnifiedTransaction]>} - */ - async getTransactionsBlockchain(scanData) { - const { address } = scanData.account - const oneAddress = OneUtils.toOneAddress(address) - BlocksoftCryptoLog.log(this._settings.currencyCode + ' OneScannerProcessor.getTransactionsBlockchain started ' + address + ' ' + oneAddress) - try { - - const apiPath = BlocksoftExternalSettings.getStatic('ONE_SERVER') - const data = { - 'jsonrpc': '2.0', - 'id': 1, - 'method': 'hmyv2_getTransactionsHistory', - 'params': [{ - 'address': oneAddress, - 'pageIndex': 0, - 'pageSize': 20, - 'fullTx': true, - 'txType': 'ALL', - 'order': 'DESC' - }] - } - const res = await BlocksoftAxios._request(apiPath, 'POST', data) - if (typeof res.data === 'undefined' || typeof res.data.result === 'undefined' || typeof res.data.result.transactions === 'undefined') { - return false - } - const transactions = [] - for (const tx of res.data.result.transactions) { - const transaction = await this._unifyTransaction(address, oneAddress, tx) - if (transaction) { - transactions.push(transaction) - } - } - return transactions - } catch (e) { - if (config.debug.cryptoErrors) { - console.log(this._settings.currencyCode + ' OneScannerProcessor.getTransactionsBlockchain address ' + address + ' error ' + e.message) - } - BlocksoftCryptoLog.log(this._settings.currencyCode + ' OneScannerProcessor.getTransactionsBlockchain address ' + address + ' error ' + e.message) - return false - } + /** + * + * @param {string} address + * @param {string} oneAddress + * @param {Object} transaction + * @param {string} transaction.blockHash + * @param {string} transaction.blockNumber + * @param {string} transaction.ethHash + * @param {string} transaction.from + * @param {string} transaction.gas + * @param {string} transaction.gasPrice + * @param {string} transaction.hash + * @param {string} transaction.input "0x095ea7b3000000000000000000000000d0cb3e55449646c9735d53e83eea5eb7e97a52dcffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + * @param {string} transaction.nonce + * @param {string} transaction.shardID + * @param {string} transaction.timestamp + * @param {string} transaction.to + * @param {string} transaction.toShardID + * @param {string} transaction.value + * @return {Promise} + * @private + */ + async _unifyTransaction(address, oneAddress, transaction) { + let formattedTime = transaction.timestamp; + try { + formattedTime = BlocksoftUtils.toDate(transaction.timestamp); + } catch (e) { + e.message += + ' timestamp error transaction data ' + JSON.stringify(transaction); + throw e; } - /** - * - * @param {string} address - * @param {string} oneAddress - * @param {Object} transaction - * @param {string} transaction.blockHash - * @param {string} transaction.blockNumber - * @param {string} transaction.ethHash - * @param {string} transaction.from - * @param {string} transaction.gas - * @param {string} transaction.gasPrice - * @param {string} transaction.hash - * @param {string} transaction.input "0x095ea7b3000000000000000000000000d0cb3e55449646c9735d53e83eea5eb7e97a52dcffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", - * @param {string} transaction.nonce - * @param {string} transaction.shardID - * @param {string} transaction.timestamp - * @param {string} transaction.to - * @param {string} transaction.toShardID - * @param {string} transaction.value - * @return {Promise} - * @private - */ - async _unifyTransaction(address, oneAddress, transaction) { - - let formattedTime = transaction.timestamp - try { - formattedTime = BlocksoftUtils.toDate(transaction.timestamp) - } catch (e) { - e.message += ' timestamp error transaction data ' + JSON.stringify(transaction) - throw e - } - - const confirmations = (new Date().getTime() - transaction.timestamp) / 60 - const addressAmount = transaction.value + const confirmations = (new Date().getTime() - transaction.timestamp) / 60; + const addressAmount = transaction.value; - let transactionStatus = 'confirming' - if (confirmations > 2) { - transactionStatus = 'success' - } + let transactionStatus = 'confirming'; + if (confirmations > 2) { + transactionStatus = 'success'; + } - const isOutcome = address.toLowerCase() === transaction.from.toLowerCase() || oneAddress.toLowerCase() === transaction.from.toLowerCase() - const isIncome = address.toLowerCase() === transaction.to.toLowerCase() || oneAddress.toLowerCase() === transaction.to.toLowerCase() - const tx = { - transactionHash: transaction.ethHash.toLowerCase(), - blockHash: transaction.blockHash, - blockNumber: +transaction.blockNumber, - blockTime: formattedTime, - blockConfirmations: confirmations, - transactionDirection: isOutcome ? (isIncome ? 'self' : 'outcome') : 'income', - addressFrom: isOutcome ? '' : transaction.from, - addressFromBasic: transaction.from.toLowerCase(), - addressTo: isIncome ? '' : transaction.to, - addressToBasic : transaction.to, - addressAmount, - transactionStatus: transactionStatus, - inputValue: transaction.input - } - const additional = { - nonce : transaction.nonce, - gas: transaction.gas, - gasPrice: transaction.gasPrice, - transactionIndex: transaction.transactionIndex - } - tx.transactionJson = additional - tx.transactionFee = BlocksoftUtils.mul(transaction.gasUsed, transaction.gasPrice).toString() + const isOutcome = + address.toLowerCase() === transaction.from.toLowerCase() || + oneAddress.toLowerCase() === transaction.from.toLowerCase(); + const isIncome = + address.toLowerCase() === transaction.to.toLowerCase() || + oneAddress.toLowerCase() === transaction.to.toLowerCase(); + const tx = { + transactionHash: transaction.ethHash.toLowerCase(), + blockHash: transaction.blockHash, + blockNumber: +transaction.blockNumber, + blockTime: formattedTime, + blockConfirmations: confirmations, + transactionDirection: isOutcome + ? isIncome + ? 'self' + : 'outcome' + : 'income', + addressFrom: isOutcome ? '' : transaction.from, + addressFromBasic: transaction.from.toLowerCase(), + addressTo: isIncome ? '' : transaction.to, + addressToBasic: transaction.to, + addressAmount, + transactionStatus: transactionStatus, + inputValue: transaction.input + }; + const additional = { + nonce: transaction.nonce, + gas: transaction.gas, + gasPrice: transaction.gasPrice, + transactionIndex: transaction.transactionIndex + }; + tx.transactionJson = additional; + tx.transactionFee = BlocksoftUtils.mul( + transaction.gasUsed, + transaction.gasPrice + ).toString(); - return tx - } + return tx; + } } diff --git a/crypto/blockchains/one/OneScannerProcessorErc20.js b/crypto/blockchains/one/OneScannerProcessorErc20.js index 1298452fe..d5923deaf 100644 --- a/crypto/blockchains/one/OneScannerProcessorErc20.js +++ b/crypto/blockchains/one/OneScannerProcessorErc20.js @@ -2,180 +2,215 @@ * https://docs.harmony.one/home/developers/api/methods/account-methods/hmy_getbalance * https://docs.harmony.one/home/developers/api/methods/transaction-related-methods/hmy_gettransactionshistory#api-v2 */ -import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog' -import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings' -import BlocksoftAxios from '@crypto/common/BlocksoftAxios' -import OneUtils from '@crypto/blockchains/one/ext/OneUtils' -import BlocksoftUtils from '@crypto/common/BlocksoftUtils' -import EthScannerProcessorErc20 from '@crypto/blockchains/eth/EthScannerProcessorErc20' +import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; +import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings'; +import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; +import OneUtils from '@crypto/blockchains/one/ext/OneUtils'; +import BlocksoftUtils from '@crypto/common/BlocksoftUtils'; +import EthScannerProcessorErc20 from '@crypto/blockchains/eth/EthScannerProcessorErc20'; -import config from '@app/config/config' -import OneTmpDS from './stores/OneTmpDS' +import config from '@app/config/config'; +import OneTmpDS from './stores/OneTmpDS'; -const CACHE_TOKENS = {} +const CACHE_TOKENS = {}; export default class OneScannerProcessorErc20 extends EthScannerProcessorErc20 { + _blocksToConfirm = 10; - _blocksToConfirm = 10 - - /** - * @param {string} scanData.account.address - * @param {string} scanData.account.walletHash - * @return {Promise<[UnifiedTransaction]>} - */ - async getTransactionsBlockchain(scanData) { - const { address } = scanData.account - const oneAddress = OneUtils.toOneAddress(address) - BlocksoftCryptoLog.log(this._settings.currencyCode + ' OneScannerProcessorErc20.getTransactionsBlockchain started ' + address + ' ' + oneAddress) - try { - - CACHE_TOKENS[address] = await OneTmpDS.getCache(address) - const apiPath = BlocksoftExternalSettings.getStatic('ONE_SERVER') - const data = { - 'jsonrpc': '2.0', - 'id': 1, - 'method': 'hmyv2_getTransactionsHistory', - 'params': [{ - 'address': oneAddress, - 'pageIndex': 0, - 'pageSize': 20, - 'fullTx': true, - 'txType': 'ALL', - 'order': 'DESC' - }] - } - const res = await BlocksoftAxios._request(apiPath, 'POST', data) - if (typeof res.data === 'undefined' || typeof res.data.result === 'undefined' || typeof res.data.result.transactions === 'undefined') { - return false - } - const transactions = [] - let firstTransaction = false - for (const tx of res.data.result.transactions) { - if (typeof CACHE_TOKENS[address] !== 'undefined' && typeof CACHE_TOKENS[address][this._tokenAddress] !== 'undefined') { - const diff = tx.timestamp - CACHE_TOKENS[address][this._tokenAddress] - const diffNow = (new Date().getTime() - tx.timestamp) / 60 - if (diff < -20) { - continue - } - if (diff <= 0) { - if (diffNow > 100) { - continue - } - } - } - if (!firstTransaction) { - firstTransaction = tx.timestamp - } - const transaction = await this._unifyTransaction(address, oneAddress, tx) - if (transaction) { - transactions.push(transaction) - } - } - CACHE_TOKENS[address][this._tokenAddress] = firstTransaction - await OneTmpDS.saveCache(address, this._tokenAddress, firstTransaction) - return transactions - } catch (e) { - if (config.debug.cryptoErrors) { - console.log(this._settings.currencyCode + ' OneScannerProcessorErc20.getTransactionsBlockchain address ' + address + ' error ' + e.message) - } - BlocksoftCryptoLog.log(this._settings.currencyCode + ' OneScannerProcessorErc20.getTransactionsBlockchain address ' + address + ' error ' + e.message) - return false - } - } - - /** - * - * @param {string} address - * @param {string} oneAddress - * @param {Object} transaction - * @param {string} transaction.blockHash - * @param {string} transaction.blockNumber - * @param {string} transaction.ethHash - * @param {string} transaction.from - * @param {string} transaction.gas - * @param {string} transaction.gasPrice - * @param {string} transaction.hash - * @param {string} transaction.input "0x095ea7b3000000000000000000000000d0cb3e55449646c9735d53e83eea5eb7e97a52dcffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", - * @param {string} transaction.nonce - * @param {string} transaction.shardID - * @param {string} transaction.timestamp - * @param {string} transaction.to - * @param {string} transaction.toShardID - * @param {string} transaction.value - * @return {Promise} - * @private - */ - async _unifyTransaction(address, oneAddress, transaction) { - - const contractEvents = await this._token.getPastEvents('Transfer', { - fromBlock: transaction.blockNumber, - toBlock: transaction.blockNumber - }) - if (!contractEvents) { - return false - } - let foundEventFrom = false - let foundEventTo = false - let foundEventSelf = false - let addressAmount = 0 - for (const tmp of contractEvents) { - if (tmp.transactionHash !== transaction.ethHash) { - continue - } - if (tmp.returnValues.to.toLowerCase() === address.toLowerCase()) { - if (tmp.returnValues.from.toLowerCase() === address.toLowerCase()) { - foundEventSelf = tmp - } else { - foundEventTo = tmp - addressAmount = addressAmount * 1 + tmp.returnValues.value * 1 - } - } else if (tmp.returnValues.from.toLowerCase() === address.toLowerCase()) { - foundEventFrom = tmp - addressAmount = addressAmount * 1 - tmp.returnValues.value * 1 + /** + * @param {string} scanData.account.address + * @param {string} scanData.account.walletHash + * @return {Promise<[UnifiedTransaction]>} + */ + async getTransactionsBlockchain(scanData) { + const { address } = scanData.account; + const oneAddress = OneUtils.toOneAddress(address); + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' OneScannerProcessorErc20.getTransactionsBlockchain started ' + + address + + ' ' + + oneAddress + ); + try { + CACHE_TOKENS[address] = await OneTmpDS.getCache(address); + const apiPath = BlocksoftExternalSettings.getStatic('ONE_SERVER'); + const data = { + jsonrpc: '2.0', + id: 1, + method: 'hmyv2_getTransactionsHistory', + params: [ + { + address: oneAddress, + pageIndex: 0, + pageSize: 20, + fullTx: true, + txType: 'ALL', + order: 'DESC' + } + ] + }; + const res = await BlocksoftAxios._request(apiPath, 'POST', data); + if ( + typeof res.data === 'undefined' || + typeof res.data.result === 'undefined' || + typeof res.data.result.transactions === 'undefined' + ) { + return false; + } + const transactions = []; + let firstTransaction = false; + for (const tx of res.data.result.transactions) { + if ( + typeof CACHE_TOKENS[address] !== 'undefined' && + typeof CACHE_TOKENS[address][this._tokenAddress] !== 'undefined' + ) { + const diff = tx.timestamp - CACHE_TOKENS[address][this._tokenAddress]; + const diffNow = (new Date().getTime() - tx.timestamp) / 60; + if (diff < -20) { + continue; + } + if (diff <= 0) { + if (diffNow > 100) { + continue; } + } } - if (!foundEventSelf && !foundEventTo && !foundEventFrom) { - return false + if (!firstTransaction) { + firstTransaction = tx.timestamp; } - - let formattedTime = transaction.timestamp - try { - formattedTime = BlocksoftUtils.toDate(transaction.timestamp) - } catch (e) { - e.message += ' timestamp error transaction data ' + JSON.stringify(transaction) - throw e + const transaction = await this._unifyTransaction( + address, + oneAddress, + tx + ); + if (transaction) { + transactions.push(transaction); } + } + CACHE_TOKENS[address][this._tokenAddress] = firstTransaction; + await OneTmpDS.saveCache(address, this._tokenAddress, firstTransaction); + return transactions; + } catch (e) { + if (config.debug.cryptoErrors) { + console.log( + this._settings.currencyCode + + ' OneScannerProcessorErc20.getTransactionsBlockchain address ' + + address + + ' error ' + + e.message + ); + } + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' OneScannerProcessorErc20.getTransactionsBlockchain address ' + + address + + ' error ' + + e.message + ); + return false; + } + } - const confirmations = (new Date().getTime() - transaction.timestamp) / 60 - let transactionStatus = 'confirming' - if (confirmations > 2) { - transactionStatus = 'success' + /** + * + * @param {string} address + * @param {string} oneAddress + * @param {Object} transaction + * @param {string} transaction.blockHash + * @param {string} transaction.blockNumber + * @param {string} transaction.ethHash + * @param {string} transaction.from + * @param {string} transaction.gas + * @param {string} transaction.gasPrice + * @param {string} transaction.hash + * @param {string} transaction.input "0x095ea7b3000000000000000000000000d0cb3e55449646c9735d53e83eea5eb7e97a52dcffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + * @param {string} transaction.nonce + * @param {string} transaction.shardID + * @param {string} transaction.timestamp + * @param {string} transaction.to + * @param {string} transaction.toShardID + * @param {string} transaction.value + * @return {Promise} + * @private + */ + async _unifyTransaction(address, oneAddress, transaction) { + const contractEvents = await this._token.getPastEvents('Transfer', { + fromBlock: transaction.blockNumber, + toBlock: transaction.blockNumber + }); + if (!contractEvents) { + return false; + } + let foundEventFrom = false; + let foundEventTo = false; + let foundEventSelf = false; + let addressAmount = 0; + for (const tmp of contractEvents) { + if (tmp.transactionHash !== transaction.ethHash) { + continue; + } + if (tmp.returnValues.to.toLowerCase() === address.toLowerCase()) { + if (tmp.returnValues.from.toLowerCase() === address.toLowerCase()) { + foundEventSelf = tmp; + } else { + foundEventTo = tmp; + addressAmount = addressAmount * 1 + tmp.returnValues.value * 1; } + } else if ( + tmp.returnValues.from.toLowerCase() === address.toLowerCase() + ) { + foundEventFrom = tmp; + addressAmount = addressAmount * 1 - tmp.returnValues.value * 1; + } + } + if (!foundEventSelf && !foundEventTo && !foundEventFrom) { + return false; + } - const tx = { - transactionHash: transaction.ethHash.toLowerCase(), - blockHash: transaction.blockHash, - blockNumber: +transaction.blockNumber, - blockTime: formattedTime, - blockConfirmations: confirmations, - transactionDirection: addressAmount * 1 <= 0 ? (foundEventTo ? 'outcome' : 'self') : 'income', - addressFrom: foundEventFrom ? transaction.from : '', - addressFromBasic: transaction.from.toLowerCase(), - addressTo: foundEventTo ? transaction.to : '', - addressToBasic: transaction.to, - addressAmount, - transactionStatus: transactionStatus, - inputValue: transaction.input - } - const additional = { - nonce: transaction.nonce, - gas: transaction.gas, - gasPrice: transaction.gasPrice, - transactionIndex: transaction.transactionIndex - } - tx.transactionJson = additional - tx.transactionFee = BlocksoftUtils.mul(transaction.gasUsed, transaction.gasPrice).toString() + let formattedTime = transaction.timestamp; + try { + formattedTime = BlocksoftUtils.toDate(transaction.timestamp); + } catch (e) { + e.message += + ' timestamp error transaction data ' + JSON.stringify(transaction); + throw e; + } - return tx + const confirmations = (new Date().getTime() - transaction.timestamp) / 60; + let transactionStatus = 'confirming'; + if (confirmations > 2) { + transactionStatus = 'success'; } + + const tx = { + transactionHash: transaction.ethHash.toLowerCase(), + blockHash: transaction.blockHash, + blockNumber: +transaction.blockNumber, + blockTime: formattedTime, + blockConfirmations: confirmations, + transactionDirection: + addressAmount * 1 <= 0 ? (foundEventTo ? 'outcome' : 'self') : 'income', + addressFrom: foundEventFrom ? transaction.from : '', + addressFromBasic: transaction.from.toLowerCase(), + addressTo: foundEventTo ? transaction.to : '', + addressToBasic: transaction.to, + addressAmount, + transactionStatus: transactionStatus, + inputValue: transaction.input + }; + const additional = { + nonce: transaction.nonce, + gas: transaction.gas, + gasPrice: transaction.gasPrice, + transactionIndex: transaction.transactionIndex + }; + tx.transactionJson = additional; + tx.transactionFee = BlocksoftUtils.mul( + transaction.gasUsed, + transaction.gasPrice + ).toString(); + + return tx; + } } diff --git a/crypto/blockchains/sol/SolScannerProcessor.js b/crypto/blockchains/sol/SolScannerProcessor.js index 411f73ff2..389cbc230 100644 --- a/crypto/blockchains/sol/SolScannerProcessor.js +++ b/crypto/blockchains/sol/SolScannerProcessor.js @@ -1,337 +1,408 @@ /** * @version 0.52 */ -import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog' -import BlocksoftAxios from '@crypto/common/BlocksoftAxios' -import BlocksoftUtils from '@crypto/common/BlocksoftUtils' -import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings' +import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; +import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; +import BlocksoftUtils from '@crypto/common/BlocksoftUtils'; +import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings'; -import SolTmpDS from '@crypto/blockchains/sol/stores/SolTmpDS' +import SolTmpDS from '@crypto/blockchains/sol/stores/SolTmpDS'; -import config from '@app/config/config' -import SolUtils from '@crypto/blockchains/sol/ext/SolUtils' +import config from '@app/config/config'; +import SolUtils from '@crypto/blockchains/sol/ext/SolUtils'; -const CACHE_FROM_DB = {} -const CACHE_TXS = {} -const CACHE_VALID_TIME = 120000 -let CACHE_LAST_BLOCK = 0 +const CACHE_FROM_DB = {}; +const CACHE_TXS = {}; +const CACHE_VALID_TIME = 120000; +let CACHE_LAST_BLOCK = 0; export default class SolScannerProcessor { - - constructor(settings) { - this._settings = settings - this.tokenAddress = typeof settings.tokenAddress !== 'undefined' ? settings.tokenAddress : '' + constructor(settings) { + this._settings = settings; + this.tokenAddress = + typeof settings.tokenAddress !== 'undefined' ? settings.tokenAddress : ''; + } + + /** + * @param {string} address + * @return {Promise<{balance, provider}>} + * https://docs.solana.com/developing/clients/jsonrpc-api#getaccountinfo + * https://docs.solana.com/developing/clients/jsonrpc-api#getconfirmedsignaturesforaddress2 + * curl https://solana-api.projectserum.com -X POST -H "Content-Type: application/json" -d '{'jsonrpc":"2.0", "id":1, "method":"getBalance", "params":["9mnBdsuL1x24HbU4oeNDBAYVAGg2vVndkRAc18kPNqCJ']}' + */ + async getBalanceBlockchain(address) { + address = address.trim(); + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' SolScannerProcessor getBalanceBlockchain address ' + + address + ); + + let balance = 0; + try { + await SolUtils.getEpoch(); + + const apiPath = BlocksoftExternalSettings.getStatic('SOL_SERVER'); + const data = { + jsonrpc: '2.0', + id: 1, + method: 'getBalance', + params: [address] + }; + const res = await BlocksoftAxios._request(apiPath, 'POST', data); + + if ( + typeof res.data.result === 'undefined' || + typeof res.data.result.value === 'undefined' + ) { + return false; + } + if ( + typeof res.data.result.context !== 'undefined' && + typeof res.data.result.context.slot !== 'undefined' + ) { + CACHE_LAST_BLOCK = res.data.result.context.slot * 1; + } + balance = res.data.result.value; + } catch (e) { + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' SolScannerProcessor getBalanceBlockchain address ' + + address + + ' error ' + + e.message + ); + return false; } - - /** - * @param {string} address - * @return {Promise<{balance, provider}>} - * https://docs.solana.com/developing/clients/jsonrpc-api#getaccountinfo - * https://docs.solana.com/developing/clients/jsonrpc-api#getconfirmedsignaturesforaddress2 - * curl https://solana-api.projectserum.com -X POST -H "Content-Type: application/json" -d '{'jsonrpc":"2.0", "id":1, "method":"getBalance", "params":["9mnBdsuL1x24HbU4oeNDBAYVAGg2vVndkRAc18kPNqCJ']}' - */ - async getBalanceBlockchain(address) { - address = address.trim() - BlocksoftCryptoLog.log(this._settings.currencyCode + ' SolScannerProcessor getBalanceBlockchain address ' + address) - - let balance = 0 - try { - await SolUtils.getEpoch() - - const apiPath = BlocksoftExternalSettings.getStatic('SOL_SERVER') - const data = { - 'jsonrpc': '2.0', - 'id': 1, - 'method': 'getBalance', - 'params': [address] - } - const res = await BlocksoftAxios._request(apiPath, 'POST', data) - - if (typeof res.data.result === 'undefined' || typeof res.data.result.value === 'undefined') { - return false - } - if (typeof res.data.result.context !== 'undefined' && typeof res.data.result.context.slot !== 'undefined') { - CACHE_LAST_BLOCK = res.data.result.context.slot * 1 - } - balance = res.data.result.value - } catch (e) { - BlocksoftCryptoLog.log(this._settings.currencyCode + ' SolScannerProcessor getBalanceBlockchain address ' + address + ' error ' + e.message) - return false - } - return { balance, unconfirmed: 0, provider: 'solana-api' } + return { balance, unconfirmed: 0, provider: 'solana-api' }; + } + + /** + * @param {string} scanData.account.address + * @return {Promise<[UnifiedTransaction]>} + * https://docs.solana.com/developing/clients/jsonrpc-api#getsignaturesforaddress + * curl https://api.mainnet-beta.solana.com -X POST -H "Content-Type: application/json" -d '{'jsonrpc": "2.0","id": 1,"method": "getConfirmedSignaturesForAddress2","params": ["9mnBdsuL1x24HbU4oeNDBAYVAGg2vVndkRAc18kPNqCJ",{"limit': 1}]}' + */ + async getTransactionsBlockchain(scanData, source) { + const address = scanData.account.address.trim(); + const lastHashVar = address + this.tokenAddress; + this._cleanCache(); + try { + if (typeof CACHE_FROM_DB[lastHashVar] === 'undefined') { + CACHE_FROM_DB[lastHashVar] = await SolTmpDS.getCache(lastHashVar); + } + + const data = { + jsonrpc: '2.0', + id: 1, + method: 'getConfirmedSignaturesForAddress2', + params: [ + address, + { + limit: 100 + } + ] + }; + if ( + CACHE_FROM_DB[lastHashVar] && + typeof CACHE_FROM_DB[lastHashVar]['last_hash'] !== 'undefined' + ) { + data.params[1].until = CACHE_FROM_DB[lastHashVar]['last_hash']; + } + const apiPath = BlocksoftExternalSettings.getStatic('SOL_SERVER'); + const res = await BlocksoftAxios._request(apiPath, 'POST', data); + if (typeof res.data.result === 'undefined' || !res.data.result) { + return false; + } + + const transactions = await this._unifyTransactions( + address, + res.data.result, + lastHashVar + ); + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' SolScannerProcessor.getTransactions finished ' + + address + ); + return transactions; + } catch (e) { + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' SolScannerProcessor getTransactionsBlockchain address ' + + address + + ' error ' + + e.message + ); + return false; } - - - /** - * @param {string} scanData.account.address - * @return {Promise<[UnifiedTransaction]>} - * https://docs.solana.com/developing/clients/jsonrpc-api#getsignaturesforaddress - * curl https://api.mainnet-beta.solana.com -X POST -H "Content-Type: application/json" -d '{'jsonrpc": "2.0","id": 1,"method": "getConfirmedSignaturesForAddress2","params": ["9mnBdsuL1x24HbU4oeNDBAYVAGg2vVndkRAc18kPNqCJ",{"limit': 1}]}' - */ - async getTransactionsBlockchain(scanData, source) { - const address = scanData.account.address.trim() - const lastHashVar = address + this.tokenAddress - this._cleanCache() - try { - if (typeof CACHE_FROM_DB[lastHashVar] === 'undefined') { - CACHE_FROM_DB[lastHashVar] = await SolTmpDS.getCache(lastHashVar) - } - - const data = { - 'jsonrpc': '2.0', - 'id': 1, - 'method': 'getConfirmedSignaturesForAddress2', - 'params': [ - address, - { - 'limit': 100 - } - ] - } - if (CACHE_FROM_DB[lastHashVar] && typeof CACHE_FROM_DB[lastHashVar]['last_hash'] !== 'undefined') { - data.params[1].until = CACHE_FROM_DB[lastHashVar]['last_hash'] - } - const apiPath = BlocksoftExternalSettings.getStatic('SOL_SERVER') - const res = await BlocksoftAxios._request(apiPath, 'POST', data) - if (typeof res.data.result === 'undefined' || !res.data.result) { - return false - } - - const transactions = await this._unifyTransactions(address, res.data.result, lastHashVar) - BlocksoftCryptoLog.log(this._settings.currencyCode + ' SolScannerProcessor.getTransactions finished ' + address) - return transactions - } catch (e) { - BlocksoftCryptoLog.log(this._settings.currencyCode + ' SolScannerProcessor getTransactionsBlockchain address ' + address + ' error ' + e.message) - return false + } + + async _unifyTransactions(address, result, lastHashVar) { + const transactions = []; + let lastHash = false; + let hasError = false; + for (const tx of result) { + try { + const transaction = await this._unifyTransaction(address, tx); + if (transaction) { + transactions.push(transaction); + if ( + transaction.transactionStatus === 'success' && + !lastHash && + !hasError + ) { + lastHash = transaction.transactionHash; + } } + } catch (e) { + hasError = true; + if (e.message.indexOf('request failed') === -1) { + if (config.debug.appErrors) { + console.log( + this._settings.currencyCode + + ' SolScannerProcessor._unifyTransactions ' + + tx.signature + + ' error ' + + e.message + ); + } + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' SolScannerProcessor._unifyTransactions ' + + tx.signature + + ' error ' + + e.message + ); + } + } } - async _unifyTransactions(address, result, lastHashVar) { - const transactions = [] - let lastHash = false - let hasError = false - for (const tx of result) { - try { - const transaction = await this._unifyTransaction(address, tx) - if (transaction) { - transactions.push(transaction) - if (transaction.transactionStatus === 'success' && !lastHash && !hasError) { - lastHash = transaction.transactionHash - } - } - } catch (e) { - hasError = true - if (e.message.indexOf('request failed') === -1) { - if (config.debug.appErrors) { - console.log(this._settings.currencyCode + ' SolScannerProcessor._unifyTransactions ' + tx.signature + ' error ' + e.message) - } - BlocksoftCryptoLog.log(this._settings.currencyCode + ' SolScannerProcessor._unifyTransactions ' + tx.signature + ' error ' + e.message) - } - } + if (lastHash) { + if (!CACHE_FROM_DB[lastHashVar]) { + CACHE_FROM_DB[lastHashVar] = { last_hash: lastHash }; + await SolTmpDS.saveCache(lastHashVar, 'last_hash', lastHash); + } else if ( + typeof CACHE_FROM_DB[lastHashVar]['last_hash'] === 'undefined' + ) { + CACHE_FROM_DB[lastHashVar]['last_hash'] = lastHash; + await SolTmpDS.saveCache(lastHashVar, 'last_hash', lastHash); + } else { + CACHE_FROM_DB[lastHashVar]['last_hash'] = lastHash; + await SolTmpDS.updateCache(lastHashVar, 'last_hash', lastHash); + } + } + return transactions; + } + + _cleanCache() { + const now = new Date().getTime(); + for (const key in CACHE_TXS) { + const t = now - CACHE_TXS[key].now; + if (t > CACHE_VALID_TIME) { + delete CACHE_TXS[key]; + } + } + } + + async _unifyTransaction(address, transaction) { + const data = { + jsonrpc: '2.0', + id: 1, + method: 'getConfirmedTransaction', + params: [transaction.signature, { encoding: 'jsonParsed' }] + }; + + let additional; + if (typeof CACHE_TXS[transaction.signature] === 'undefined') { + const apiPath = BlocksoftExternalSettings.getStatic('SOL_SERVER'); + try { + const res = await BlocksoftAxios._request(apiPath, 'POST', data); + if (typeof res.data.result === 'undefined' || !res.data.result) { + return false; } - - if (lastHash) { - if (!CACHE_FROM_DB[lastHashVar]) { - CACHE_FROM_DB[lastHashVar] = { 'last_hash': lastHash } - await SolTmpDS.saveCache(lastHashVar, 'last_hash', lastHash) - } else if (typeof CACHE_FROM_DB[lastHashVar]['last_hash'] === 'undefined') { - CACHE_FROM_DB[lastHashVar]['last_hash'] = lastHash - await SolTmpDS.saveCache(lastHashVar, 'last_hash', lastHash) - } else { - CACHE_FROM_DB[lastHashVar]['last_hash'] = lastHash - await SolTmpDS.updateCache(lastHashVar, 'last_hash', lastHash) - } + additional = res.data.result; + CACHE_TXS[transaction.signature] = { + data: additional, + now: new Date().getTime() + }; + } catch (e) { + if (config.debug.cryptoErrors) { + console.log( + this._settings.currencyCode + + ' SolScannerProcessor._unifyTransaction ' + + transaction.signature + + ' request error ' + + e.message + ); } - return transactions + throw e; + } + } else { + additional = CACHE_TXS[transaction.signature].data; } - _cleanCache() { - const now = new Date().getTime() - for (const key in CACHE_TXS) { - const t = (now - CACHE_TXS[key].now) - if (t > CACHE_VALID_TIME) { - delete CACHE_TXS[key] - } - } + let addressFrom = false; + let addressTo = false; + let addressAmount = 0; + let anyFromAddress = false; + let anyToAddress = false; + + const indexedPre = {}; + const indexedPost = {}; + const indexedCreated = {}; + const indexedAssociated = {}; + + if (this.tokenAddress) { + for (const tmp of additional.meta.preTokenBalances) { + if (tmp.mint !== this.tokenAddress) continue; + const realIndex = tmp.accountIndex; + indexedPre[realIndex] = tmp.uiTokenAmount.amount; + } + + for (const tmp of additional.meta.postTokenBalances) { + if (tmp.mint !== this.tokenAddress) continue; + const realIndex = tmp.accountIndex; + indexedPost[realIndex] = tmp.uiTokenAmount.amount; + } + + for (const tmp of additional.transaction.message.instructions) { + if (tmp.program !== 'spl-associated-token-account') continue; + indexedCreated[tmp.parsed.info.account] = tmp.parsed.info.wallet; + } + + for ( + let i = 0, ic = additional.transaction.message.accountKeys.length; + i < ic; + i++ + ) { + const tmpAddress = additional.transaction.message.accountKeys[i]; + if (tmpAddress.pubkey === '11111111111111111111111111111111') continue; + const sourceAssociatedTokenAddress = + await SolUtils.findAssociatedTokenAddress( + tmpAddress.pubkey, + this.tokenAddress + ); + indexedAssociated[sourceAssociatedTokenAddress] = tmpAddress; + } + } else { + // do nothing! } - async _unifyTransaction(address, transaction) { - - const data = { - 'jsonrpc': '2.0', - 'id': 1, - 'method': 'getConfirmedTransaction', - 'params': [ - transaction.signature, - {encoding : 'jsonParsed'} - ] - } - - let additional - if (typeof CACHE_TXS[transaction.signature] === 'undefined') { - const apiPath = BlocksoftExternalSettings.getStatic('SOL_SERVER') - try { - const res = await BlocksoftAxios._request(apiPath, 'POST', data) - if (typeof res.data.result === 'undefined' || !res.data.result) { - return false - } - additional = res.data.result - CACHE_TXS[transaction.signature] = {data : additional, now : new Date().getTime() } - } catch (e) { - if (config.debug.cryptoErrors) { - console.log(this._settings.currencyCode + ' SolScannerProcessor._unifyTransaction ' + transaction.signature + ' request error ' + e.message) - } - throw e - } + let anySigner = false; + let addressAmountPlus = false; + for ( + let i = 0, ic = additional.transaction.message.accountKeys.length; + i < ic; + i++ + ) { + let tmpAddress = additional.transaction.message.accountKeys[i]; + if (tmpAddress.pubkey === '11111111111111111111111111111111') continue; + let tmpAmount = '0'; + if (typeof indexedAssociated[tmpAddress.pubkey] !== 'undefined') { + tmpAddress = indexedAssociated[tmpAddress.pubkey]; + } + if (this.tokenAddress) { + const to = typeof indexedPost[i] !== 'undefined' ? indexedPost[i] : 0; + const from = typeof indexedPre[i] !== 'undefined' ? indexedPre[i] : 0; + tmpAmount = BlocksoftUtils.diff(to, from).toString(); + } else { + tmpAmount = BlocksoftUtils.diff( + additional.meta.postBalances[i], + additional.meta.preBalances[i] + ).toString(); + } + + if (tmpAddress.pubkey && tmpAddress.signer) { + anySigner = tmpAddress.pubkey; + } + + if (tmpAmount === '0') continue; + + if ( + tmpAddress.pubkey === address || + (typeof indexedCreated[tmpAddress.pubkey] !== 'undefined' && + indexedCreated[tmpAddress.pubkey] === address) + ) { + if (tmpAmount.indexOf('-') === -1) { + addressTo = tmpAddress.pubkey; + addressAmount = tmpAmount; + addressAmountPlus = true; } else { - additional = CACHE_TXS[transaction.signature].data + addressFrom = tmpAddress.pubkey; + addressAmount = tmpAmount.replace('-', ''); } - - let addressFrom = false - let addressTo = false - let addressAmount = 0 - let anyFromAddress = false - let anyToAddress = false - - const indexedPre = {} - const indexedPost = {} - const indexedCreated = {} - const indexedAssociated = {} - - if (this.tokenAddress) { - for (const tmp of additional.meta.preTokenBalances) { - if (tmp.mint !== this.tokenAddress) continue - const realIndex = tmp.accountIndex - indexedPre[realIndex] = tmp.uiTokenAmount.amount - } - - for (const tmp of additional.meta.postTokenBalances) { - if (tmp.mint !== this.tokenAddress) continue - const realIndex = tmp.accountIndex - indexedPost[realIndex] = tmp.uiTokenAmount.amount - } - - for (const tmp of additional.transaction.message.instructions) { - if (tmp.program !== 'spl-associated-token-account') continue - indexedCreated[tmp.parsed.info.account] = tmp.parsed.info.wallet - } - - for (let i = 0, ic = additional.transaction.message.accountKeys.length; i < ic; i++) { - const tmpAddress = additional.transaction.message.accountKeys[i] - if (tmpAddress.pubkey === '11111111111111111111111111111111') continue - const sourceAssociatedTokenAddress = await SolUtils.findAssociatedTokenAddress( - tmpAddress.pubkey, - this.tokenAddress - ) - indexedAssociated[sourceAssociatedTokenAddress] = tmpAddress - } + } else { + if (tmpAddress.signer) { + anyFromAddress = tmpAddress.pubkey; } else { - // do nothing! - } - - let anySigner = false - let addressAmountPlus = false - for (let i = 0, ic = additional.transaction.message.accountKeys.length; i < ic; i++) { - let tmpAddress = additional.transaction.message.accountKeys[i] - if (tmpAddress.pubkey === '11111111111111111111111111111111') continue - let tmpAmount = '0' - if (typeof indexedAssociated[tmpAddress.pubkey] !== 'undefined') { - tmpAddress = indexedAssociated[tmpAddress.pubkey] - } - if (this.tokenAddress) { - const to = typeof indexedPost[i] !== 'undefined' ? indexedPost[i] : 0 - const from = typeof indexedPre[i] !== 'undefined' ? indexedPre[i] : 0 - tmpAmount = BlocksoftUtils.diff(to, from).toString() - } else { - tmpAmount = BlocksoftUtils.diff(additional.meta.postBalances[i], additional.meta.preBalances[i]).toString() - } - - if (tmpAddress.pubkey && tmpAddress.signer) { - anySigner = tmpAddress.pubkey - } - - if (tmpAmount === '0') continue - - if (tmpAddress.pubkey === address || - ( - typeof indexedCreated[tmpAddress.pubkey] !== 'undefined' && indexedCreated[tmpAddress.pubkey] === address - ) - ) { - if (tmpAmount.indexOf('-') === -1) { - addressTo = tmpAddress.pubkey - addressAmount = tmpAmount - addressAmountPlus = true - } else { - addressFrom = tmpAddress.pubkey - addressAmount = tmpAmount.replace('-', '') - } - } else { - if (tmpAddress.signer) { - anyFromAddress = tmpAddress.pubkey - } else { - anyToAddress = tmpAddress.pubkey - } - } + anyToAddress = tmpAddress.pubkey; } + } + } - if (!addressFrom && anySigner !== addressTo) { - addressFrom = anySigner - } - if (!addressFrom && !addressTo) { - return false - } - if (anyFromAddress && !addressFrom) { - addressFrom = anyFromAddress - } - if (anyToAddress && !addressTo) { - addressTo = anyToAddress - } - if (!addressTo) { - addressTo = 'System' - } + if (!addressFrom && anySigner !== addressTo) { + addressFrom = anySigner; + } + if (!addressFrom && !addressTo) { + return false; + } + if (anyFromAddress && !addressFrom) { + addressFrom = anyFromAddress; + } + if (anyToAddress && !addressTo) { + addressTo = anyToAddress; + } + if (!addressTo) { + addressTo = 'System'; + } - let formattedTime = transaction.blockTime - try { - formattedTime = BlocksoftUtils.toDate(transaction.blockTime) - } catch (e) { - e.message += ' timestamp error transaction2 data ' + JSON.stringify(transaction) - throw e - } - let transactionStatus = 'new' - if (transaction.confirmationStatus === 'finalized') { - transactionStatus = 'success' - } else if (transaction.confirmationStatus === 'confirmed') { - transactionStatus = 'confirming' - } - if (typeof transaction.err !== 'undefined' && transaction.err) { - transactionStatus = 'fail' - } + let formattedTime = transaction.blockTime; + try { + formattedTime = BlocksoftUtils.toDate(transaction.blockTime); + } catch (e) { + e.message += + ' timestamp error transaction2 data ' + JSON.stringify(transaction); + throw e; + } + let transactionStatus = 'new'; + if (transaction.confirmationStatus === 'finalized') { + transactionStatus = 'success'; + } else if (transaction.confirmationStatus === 'confirmed') { + transactionStatus = 'confirming'; + } + if (typeof transaction.err !== 'undefined' && transaction.err) { + transactionStatus = 'fail'; + } - let transactionDirection = addressFrom === address ? 'outcome' : 'income' - if (!addressFrom && anySigner === addressTo) { - if (addressAmountPlus) { - transactionDirection = 'swap_income' - } else { - transactionDirection = 'swap_outcome' - } - } - const blockConfirmations = CACHE_LAST_BLOCK > 0 ? Math.round(CACHE_LAST_BLOCK - additional.slot * 1) : 0 - const tx = { - transactionHash: transaction.signature, - blockHash: additional.transaction.message.recentBlockhash, - blockNumber: transaction.slot, - blockTime: formattedTime, - blockConfirmations, - transactionDirection, - addressFrom: addressFrom === address ? '' : addressFrom, - addressTo: addressTo === address ? '' : addressTo, - addressAmount, - transactionStatus, - transactionFee: additional.meta.fee - } - if (typeof transaction.memo !== 'undefined' && transaction.memo) { - tx.transactionJson = { memo: transaction.memo } - } - return tx + let transactionDirection = addressFrom === address ? 'outcome' : 'income'; + if (!addressFrom && anySigner === addressTo) { + if (addressAmountPlus) { + transactionDirection = 'swap_income'; + } else { + transactionDirection = 'swap_outcome'; + } + } + const blockConfirmations = + CACHE_LAST_BLOCK > 0 + ? Math.round(CACHE_LAST_BLOCK - additional.slot * 1) + : 0; + const tx = { + transactionHash: transaction.signature, + blockHash: additional.transaction.message.recentBlockhash, + blockNumber: transaction.slot, + blockTime: formattedTime, + blockConfirmations, + transactionDirection, + addressFrom: addressFrom === address ? '' : addressFrom, + addressTo: addressTo === address ? '' : addressTo, + addressAmount, + transactionStatus, + transactionFee: additional.meta.fee + }; + if (typeof transaction.memo !== 'undefined' && transaction.memo) { + tx.transactionJson = { memo: transaction.memo }; } + return tx; + } } diff --git a/crypto/blockchains/sol/SolScannerProcessorSpl.js b/crypto/blockchains/sol/SolScannerProcessorSpl.js index 09ba22400..f78ece4ca 100644 --- a/crypto/blockchains/sol/SolScannerProcessorSpl.js +++ b/crypto/blockchains/sol/SolScannerProcessorSpl.js @@ -1,67 +1,94 @@ /** * @version 0.52 */ -import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog' -import BlocksoftAxios from '@crypto/common/BlocksoftAxios' -import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings' +import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; +import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; +import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings'; -import SolScannerProcessor from '@crypto/blockchains/sol/SolScannerProcessor' +import SolScannerProcessor from '@crypto/blockchains/sol/SolScannerProcessor'; -const CACHE_BALANCES = {} -const CACHE_VALID_TIME = 30000 // 30 seconds +const CACHE_BALANCES = {}; +const CACHE_VALID_TIME = 30000; // 30 seconds export default class SolScannerProcessorSpl extends SolScannerProcessor { + /** + * @param {string} address + * @return {Promise<{balance, provider}>} + */ + async getBalanceBlockchain(address) { + address = address.trim(); + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' SolScannerProcessorSpl getBalanceBlockchain address ' + + address + ); - /** - * @param {string} address - * @return {Promise<{balance, provider}>} - */ - async getBalanceBlockchain(address) { - address = address.trim() - BlocksoftCryptoLog.log(this._settings.currencyCode + ' SolScannerProcessorSpl getBalanceBlockchain address ' + address) + const now = new Date().getTime(); + let balance = 0; + try { + if ( + typeof CACHE_BALANCES[address] === 'undefined' || + typeof CACHE_BALANCES[address].time === 'undefined' || + now - CACHE_BALANCES[address].time < CACHE_VALID_TIME + ) { + CACHE_BALANCES[address] = {}; + const apiPath = BlocksoftExternalSettings.getStatic('SOL_SERVER'); - const now = new Date().getTime() - let balance = 0 - try { - if (typeof CACHE_BALANCES[address] === 'undefined' || typeof CACHE_BALANCES[address].time === 'undefined' || (now - CACHE_BALANCES[address].time < CACHE_VALID_TIME)) { - CACHE_BALANCES[address] = {} - const apiPath = BlocksoftExternalSettings.getStatic('SOL_SERVER') + const data = { + jsonrpc: '2.0', + id: 1, + method: 'getTokenAccountsByOwner', + params: [ + address, + { + programId: 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA' + }, + { encoding: 'jsonParsed', commitment: 'processed' } + ] + }; - const data = { - 'jsonrpc': '2.0', - 'id': 1, - 'method': 'getTokenAccountsByOwner', - 'params': [ - address, - { - programId: 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA' - }, - { encoding: 'jsonParsed', commitment: 'processed' } - ] - } - - const res = await BlocksoftAxios._request(apiPath, 'POST', data) - if (typeof res.data.result === 'undefined' || typeof res.data.result.value === 'undefined') { - return false - } - for (const account of res.data.result.value) { - if (typeof account.account === 'undefined') continue - if (typeof account.account.data.program === 'undefined' || account.account.data.program !== 'spl-token') continue - const parsed = account.account.data.parsed.info - if (typeof parsed.state === 'undefined' && parsed.state !== 'initialized') continue - CACHE_BALANCES[address][parsed.mint] = parsed.tokenAmount // "amount": "1606300", "decimals": 6, "uiAmount": 1.6063, "uiAmountString": "1.6063" - } - CACHE_BALANCES[address].time = now - } - if (typeof CACHE_BALANCES[address][this.tokenAddress] === 'undefined' || typeof CACHE_BALANCES[address][this.tokenAddress].amount === 'undefined') { - balance = 0 - } else { - balance = CACHE_BALANCES[address][this.tokenAddress].amount * 1 - } - } catch (e) { - BlocksoftCryptoLog.log(this._settings.currencyCode + ' SolScannerProcessorSpl getBalanceBlockchain address ' + address + ' error ' + e.message) - return false + const res = await BlocksoftAxios._request(apiPath, 'POST', data); + if ( + typeof res.data.result === 'undefined' || + typeof res.data.result.value === 'undefined' + ) { + return false; + } + for (const account of res.data.result.value) { + if (typeof account.account === 'undefined') continue; + if ( + typeof account.account.data.program === 'undefined' || + account.account.data.program !== 'spl-token' + ) + continue; + const parsed = account.account.data.parsed.info; + if ( + typeof parsed.state === 'undefined' && + parsed.state !== 'initialized' + ) + continue; + CACHE_BALANCES[address][parsed.mint] = parsed.tokenAmount; // "amount": "1606300", "decimals": 6, "uiAmount": 1.6063, "uiAmountString": "1.6063" } - return { balance, unconfirmed: 0, provider: 'solana-api' } + CACHE_BALANCES[address].time = now; + } + if ( + typeof CACHE_BALANCES[address][this.tokenAddress] === 'undefined' || + typeof CACHE_BALANCES[address][this.tokenAddress].amount === 'undefined' + ) { + balance = 0; + } else { + balance = CACHE_BALANCES[address][this.tokenAddress].amount * 1; + } + } catch (e) { + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' SolScannerProcessorSpl getBalanceBlockchain address ' + + address + + ' error ' + + e.message + ); + return false; } + return { balance, unconfirmed: 0, provider: 'solana-api' }; + } } diff --git a/crypto/blockchains/sol/SolTokenProcessor.js b/crypto/blockchains/sol/SolTokenProcessor.js index d13943b6f..601ac5d4f 100644 --- a/crypto/blockchains/sol/SolTokenProcessor.js +++ b/crypto/blockchains/sol/SolTokenProcessor.js @@ -1,89 +1,100 @@ /** * @version 0.52 */ -import BlocksoftAxios from '@crypto/common/BlocksoftAxios' -import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings' -import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog' +import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; +import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings'; +import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; export default class SolTokenProcessor { + /** + * @param {string} tokenAddress + * @returns {Promise<{tokenAddress: *, currencyName: *, provider: string, tokenDecimals: *, icon: *, description: *, tokenType: string, currencyCode: *}|boolean>} + */ + async getTokenDetails(tokenAddress) { + const link = await BlocksoftExternalSettings.get('SOL_TOKENS_LIST'); + const res = await BlocksoftAxios.get(link); + if (!res || typeof res.data.tokens === 'undefined' || !res.data.tokens) { + return false; + } - /** - * @param {string} tokenAddress - * @returns {Promise<{tokenAddress: *, currencyName: *, provider: string, tokenDecimals: *, icon: *, description: *, tokenType: string, currencyCode: *}|boolean>} - */ - async getTokenDetails(tokenAddress) { - const link = await BlocksoftExternalSettings.get('SOL_TOKENS_LIST') - const res = await BlocksoftAxios.get(link) - if (!res || typeof res.data.tokens === 'undefined' || !res.data.tokens) { - return false - } - - let tmp = false - for (const token of res.data.tokens) { - if (token.address === tokenAddress) { - if (token.chainId !== 101) continue - tmp = token - break - } - } - if (tmp) { - - - return { - currencyCodePrefix: 'CUSTOM_SOL_', - currencyCode: tmp.symbol, - currencyName: tmp.name, - tokenType: 'SOL', - tokenAddress: tokenAddress, - tokenDecimals: tmp.decimals, - icon: tmp.logoURI, - description: tmp.website, - coingeckoId: tmp.coingeckoId, - provider: 'sol' - } - } - - - let decimals = 6 - try { - const apiPath = BlocksoftExternalSettings.getStatic('SOL_SERVER') - const data = { - 'jsonrpc': '2.0', - 'id': 1, - 'method': 'getAccountInfo', - 'params': [ - tokenAddress, - { - 'encoding': 'jsonParsed' - } - ] - } - const res = await BlocksoftAxios._request(apiPath, 'POST', data) - if (typeof res.data.result === 'undefined' || typeof res.data.result.value === 'undefined') { - return false - } - if (typeof res.data.result.value.owner === 'undefined' || res.data.result.value.owner !== 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA') { - return false - } - if (typeof res.data.result.value.data.program === 'undefined' || res.data.result.value.data.program !== 'spl-token') { - return false - } - decimals = res.data.result.value.data.parsed.info.decimals - - } catch (e) { - BlocksoftCryptoLog.log(this._settings.currencyCode + ' SolTokenProcessor getTokenDetails tokenAddress ' + tokenAddress + ' error ' + e.message) - return false - } + let tmp = false; + for (const token of res.data.tokens) { + if (token.address === tokenAddress) { + if (token.chainId !== 101) continue; + tmp = token; + break; + } + } + if (tmp) { + return { + currencyCodePrefix: 'CUSTOM_SOL_', + currencyCode: tmp.symbol, + currencyName: tmp.name, + tokenType: 'SOL', + tokenAddress: tokenAddress, + tokenDecimals: tmp.decimals, + icon: tmp.logoURI, + description: tmp.website, + coingeckoId: tmp.coingeckoId, + provider: 'sol' + }; + } - return { - currencyCodePrefix: 'CUSTOM_SOL_', - currencyCode: 'UNKNOWN_TOKEN_' + tokenAddress, - currencySymbol: 'UNKNOWN', - currencyName: tokenAddress, - tokenType: 'SOL', - tokenAddress: tokenAddress, - tokenDecimals: decimals, - provider: 'sol' - } + let decimals = 6; + try { + const apiPath = BlocksoftExternalSettings.getStatic('SOL_SERVER'); + const data = { + jsonrpc: '2.0', + id: 1, + method: 'getAccountInfo', + params: [ + tokenAddress, + { + encoding: 'jsonParsed' + } + ] + }; + const res = await BlocksoftAxios._request(apiPath, 'POST', data); + if ( + typeof res.data.result === 'undefined' || + typeof res.data.result.value === 'undefined' + ) { + return false; + } + if ( + typeof res.data.result.value.owner === 'undefined' || + res.data.result.value.owner !== + 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA' + ) { + return false; + } + if ( + typeof res.data.result.value.data.program === 'undefined' || + res.data.result.value.data.program !== 'spl-token' + ) { + return false; + } + decimals = res.data.result.value.data.parsed.info.decimals; + } catch (e) { + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' SolTokenProcessor getTokenDetails tokenAddress ' + + tokenAddress + + ' error ' + + e.message + ); + return false; } + + return { + currencyCodePrefix: 'CUSTOM_SOL_', + currencyCode: 'UNKNOWN_TOKEN_' + tokenAddress, + currencySymbol: 'UNKNOWN', + currencyName: tokenAddress, + tokenType: 'SOL', + tokenAddress: tokenAddress, + tokenDecimals: decimals, + provider: 'sol' + }; + } } diff --git a/crypto/blockchains/sol/SolTransferProcessor.ts b/crypto/blockchains/sol/SolTransferProcessor.ts index 9461deb3d..78558eeec 100644 --- a/crypto/blockchains/sol/SolTransferProcessor.ts +++ b/crypto/blockchains/sol/SolTransferProcessor.ts @@ -1,168 +1,263 @@ /** * @version 0.52 */ -import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog' -import BlocksoftUtils from '@crypto/common/BlocksoftUtils' -import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings' -import BlocksoftBalances from '@crypto/actions/BlocksoftBalances/BlocksoftBalances' +import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; +import BlocksoftUtils from '@crypto/common/BlocksoftUtils'; +import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings'; +import BlocksoftBalances from '@crypto/actions/BlocksoftBalances/BlocksoftBalances'; // eslint-disable-next-line no-unused-vars -import { BlocksoftBlockchainTypes } from '@crypto/blockchains/BlocksoftBlockchainTypes' - -import config from '@app/config/config' - -import { PublicKey, SystemProgram, Transaction, StakeProgram, Authorized } from '@solana/web3.js/src/index' -import SolUtils from '@crypto/blockchains/sol/ext/SolUtils' -import SolTmpDS from '@crypto/blockchains/sol/stores/SolTmpDS' -import SolStakeUtils from '@crypto/blockchains/sol/ext/SolStakeUtils' -import { Buffer } from 'buffer' -import BlocksoftCryptoUtils from '@crypto/common/BlocksoftCryptoUtils' -import BlocksoftAxios from '@crypto/common/BlocksoftAxios' - -export default class SolTransferProcessor implements BlocksoftBlockchainTypes.TransferProcessor { - private _settings: { network: string; currencyCode: string } - - constructor(settings: { network: string; currencyCode: string }) { - this._settings = settings - } - - needPrivateForFee(): boolean { - return false +import { BlocksoftBlockchainTypes } from '@crypto/blockchains/BlocksoftBlockchainTypes'; + +import config from '@app/config/config'; + +import { + PublicKey, + SystemProgram, + Transaction, + StakeProgram, + Authorized +} from '@solana/web3.js/src/index'; +import SolUtils from '@crypto/blockchains/sol/ext/SolUtils'; +import SolTmpDS from '@crypto/blockchains/sol/stores/SolTmpDS'; +import SolStakeUtils from '@crypto/blockchains/sol/ext/SolStakeUtils'; +import { Buffer } from 'buffer'; +import BlocksoftCryptoUtils from '@crypto/common/BlocksoftCryptoUtils'; +import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; + +export default class SolTransferProcessor + implements BlocksoftBlockchainTypes.TransferProcessor +{ + private _settings: { network: string; currencyCode: string }; + + constructor(settings: { network: string; currencyCode: string }) { + this._settings = settings; + } + + needPrivateForFee(): boolean { + return false; + } + + checkSendAllModal(data: { currencyCode: any }): boolean { + return false; + } + + async getFeeRate( + data: BlocksoftBlockchainTypes.TransferData, + privateData: BlocksoftBlockchainTypes.TransferPrivateData, + additionalData: {} = {} + ): Promise { + const result: BlocksoftBlockchainTypes.FeeRateResult = { + selectedFeeIndex: -3, + shouldShowFees: false + } as BlocksoftBlockchainTypes.FeeRateResult; + + const feeForTx = BlocksoftExternalSettings.getStatic('SOL_PRICE'); + result.fees = [ + { + langMsg: 'xrp_speed_one', + feeForTx, + amountForTx: data.amount + } + ]; + result.selectedFeeIndex = 0; + + return result; + } + + async getTransferAllBalance( + data: BlocksoftBlockchainTypes.TransferData, + privateData: BlocksoftBlockchainTypes.TransferPrivateData, + additionalData: BlocksoftBlockchainTypes.TransferAdditionalData = {} + ): Promise { + const address = data.addressFrom.trim(); + let rent = 0; + let balance = data.amount; + try { + const beachPath = + 'https://public-api.solanabeach.io/v1/account/' + address + '?'; + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' SolTransferProcessor.getTransferAllBalance address ' + + address + + ' beach link ' + + beachPath + ); + const res = await BlocksoftAxios.get(beachPath); + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' SolTransferProcessor.getTransferAllBalance address ' + + address + + ' beach res ', + res?.data + ); + if ( + typeof res?.data?.value !== 'undefined' && + typeof res?.data?.value?.base !== 'undefined' + ) { + if (res?.data?.value?.base?.address?.address !== address) { + throw new Error( + 'wrong value address ' + res?.data?.value?.base?.address?.address + ); + } + balance = res?.data?.value?.base?.balance || data.amount; + rent = res?.data?.value?.base?.rentExemptReserve || 0; + if (rent) { + balance = BlocksoftUtils.diff(balance, rent); + } + } + } catch (e) { + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' SolTransferProcessor.getTransferAllBalance address ' + + address + + ' beach error ' + + e.message + ); } - checkSendAllModal(data: { currencyCode: any }): boolean { - return false + // @ts-ignore + await BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' SolTransferProcessor.getTransferAllBalance ', + data.addressFrom + ' => ' + balance + ); + + const fees = await this.getFeeRate(data, privateData, additionalData); + + const amount = BlocksoftUtils.diff( + balance, + fees.fees[0].feeForTx + ).toString(); + + return { + ...fees, + shouldShowFees: false, + selectedTransferAllBalance: amount + }; + } + + /** + * @param data + * @param privateData + * @param uiData + */ + async sendTx( + data: BlocksoftBlockchainTypes.TransferData, + privateData: BlocksoftBlockchainTypes.TransferPrivateData, + uiData: BlocksoftBlockchainTypes.TransferUiData + ): Promise { + if (typeof privateData.privateKey === 'undefined') { + throw new Error('SOL transaction required privateKey (derivedSeed)'); } - - async getFeeRate(data: BlocksoftBlockchainTypes.TransferData, privateData: BlocksoftBlockchainTypes.TransferPrivateData, additionalData: {} = {}): Promise { - const result: BlocksoftBlockchainTypes.FeeRateResult = { - selectedFeeIndex: -3, - shouldShowFees: false - } as BlocksoftBlockchainTypes.FeeRateResult - - const feeForTx = BlocksoftExternalSettings.getStatic('SOL_PRICE') - result.fees = [ - { - langMsg: 'xrp_speed_one', - feeForTx, - amountForTx: data.amount - } - ] - result.selectedFeeIndex = 0 - - - return result + if (typeof data.addressTo === 'undefined') { + throw new Error('SOL transaction required addressTo'); } - async getTransferAllBalance(data: BlocksoftBlockchainTypes.TransferData, privateData: BlocksoftBlockchainTypes.TransferPrivateData, additionalData: BlocksoftBlockchainTypes.TransferAdditionalData = {}): Promise { - const address = data.addressFrom.trim() - let rent = 0 - let balance = data.amount - try { - const beachPath = 'https://public-api.solanabeach.io/v1/account/' + address + '?' - BlocksoftCryptoLog.log(this._settings.currencyCode + ' SolTransferProcessor.getTransferAllBalance address ' + address + ' beach link ' + beachPath) - const res = await BlocksoftAxios.get(beachPath) - BlocksoftCryptoLog.log(this._settings.currencyCode + ' SolTransferProcessor.getTransferAllBalance address ' + address + ' beach res ', res?.data) - if (typeof res?.data?.value !== 'undefined' && typeof res?.data?.value?.base !== 'undefined') { - if (res?.data?.value?.base?.address?.address !== address) { - throw new Error('wrong value address ' + res?.data?.value?.base?.address?.address) - } - balance = res?.data?.value?.base?.balance || data.amount - rent = res?.data?.value?.base?.rentExemptReserve || 0 - if (rent) { - balance = BlocksoftUtils.diff(balance, rent) - } - } - } catch (e) { - BlocksoftCryptoLog.log(this._settings.currencyCode + ' SolTransferProcessor.getTransferAllBalance address ' + address + ' beach error ' + e.message) - } - - // @ts-ignore - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' SolTransferProcessor.getTransferAllBalance ', data.addressFrom + ' => ' + balance) - - const fees = await this.getFeeRate(data, privateData, additionalData) - - const amount = BlocksoftUtils.diff(balance, fees.fees[0].feeForTx).toString() - - return { - ...fees, - shouldShowFees: false, - selectedTransferAllBalance: amount + if ( + uiData && + typeof uiData.uiErrorConfirmed !== 'undefined' && + (uiData.uiErrorConfirmed === 'UI_CONFIRM_ADDRESS_TO_EMPTY_BALANCE' || + uiData.uiErrorConfirmed === 'UI_CONFIRM_DOUBLE_SEND') + ) { + // do nothing + } else if ( + data.addressTo !== 'STAKE' && + data.addressTo.indexOf('UNSTAKE') === -1 + ) { + if (typeof uiData?.selectedFee?.bseOrderId === 'undefined') { + const balance = await BlocksoftBalances.setCurrencyCode('SOL') + .setAddress(data.addressTo) + .getBalance('SolSendTx'); + if ( + !balance || + typeof balance.balance === 'undefined' || + balance.balance === 0 + ) { + throw new Error('UI_CONFIRM_ADDRESS_TO_EMPTY_BALANCE'); } + } } - /** - * @param data - * @param privateData - * @param uiData - */ - async sendTx(data: BlocksoftBlockchainTypes.TransferData, privateData: BlocksoftBlockchainTypes.TransferPrivateData, uiData: BlocksoftBlockchainTypes.TransferUiData): Promise { - - if (typeof privateData.privateKey === 'undefined') { - throw new Error('SOL transaction required privateKey (derivedSeed)') + const tx = new Transaction(); + + let seed, + stakeAddress = false; + try { + const fromPubkey = new PublicKey(data.addressFrom); + if (data.addressTo.indexOf('UNSTAKE') === 0) { + if (data.amount === 'ALL') { + tx.add( + StakeProgram.deactivate({ + authorizedPubkey: fromPubkey, + stakePubkey: new PublicKey(data.blockchainData.stakeAddress) + }) + ); + } else { + tx.add( + StakeProgram.withdraw({ + authorizedPubkey: fromPubkey, + stakePubkey: new PublicKey(data.blockchainData.stakeAddress), + lamports: data.amount * 1, + toPubkey: fromPubkey + }) + ); } - if (typeof data.addressTo === 'undefined') { - throw new Error('SOL transaction required addressTo') + } else if (data.addressTo === 'STAKE') { + await BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' SolTransferProcessor.sendTx ' + + data.addressFrom + + ' => ' + + data.addressTo + + ' ' + + data.amount + + ' build start' + ); + const validator = data.blockchainData.voteAddress; + const authorized = new Authorized(fromPubkey, fromPubkey); + if (typeof validator === 'undefined' || !validator) { + throw new Error('no validator field'); } - - if (uiData && typeof uiData.uiErrorConfirmed !== 'undefined' && - ( - uiData.uiErrorConfirmed === 'UI_CONFIRM_ADDRESS_TO_EMPTY_BALANCE' - || uiData.uiErrorConfirmed === 'UI_CONFIRM_DOUBLE_SEND' - ) + await BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' SolTransferProcessor.sendTx ' + + data.addressFrom + + ' => ' + + data.addressTo + + ' ' + + data.amount + + ' build authorized' + ); + + // https://github.com/velas/JsWallet/blob/251ad92bb5c2cd9a62477746a3db934b6dce0c4b/velas/velas-staking.js + // https://explorer.solana.com/tx/2ffmtkj3Yj51ZWCEHG6jb6s78F73eoiQdqURV7z65kSVLiPcm8Y9NE45FgfgwbddJD8kfgCiTpmrEu7J8WKpAQeE + await SolStakeUtils.getAccountStaked(data.addressFrom); + + await BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' SolTransferProcessor.sendTx ' + + data.addressFrom + + ' => ' + + data.addressTo + + ' ' + + data.amount + + ' build createWithSeed started' + ); + let start = 0; + let lastSeed = await SolTmpDS.getCache(data.addressFrom); + if ( + typeof lastSeed !== 'undefined' && + lastSeed && + typeof lastSeed.seed !== 'undefined' && + lastSeed.seed ) { - // do nothing - } else if (data.addressTo !== 'STAKE' && data.addressTo.indexOf('UNSTAKE') === -1) { - if (typeof uiData?.selectedFee?.bseOrderId === 'undefined') { - const balance = await (BlocksoftBalances.setCurrencyCode('SOL').setAddress(data.addressTo)).getBalance('SolSendTx') - if (!balance || typeof balance.balance === 'undefined' || balance.balance === 0) { - throw new Error('UI_CONFIRM_ADDRESS_TO_EMPTY_BALANCE') - } - } + start = lastSeed.seed * 1; } + for (let i = 1; i <= 10000; i++) { + const tmpSeed = (i + start).toString(); - const tx = new Transaction() - - let seed, stakeAddress = false - try { - const fromPubkey = new PublicKey(data.addressFrom) - if (data.addressTo.indexOf('UNSTAKE') === 0) { - if (data.amount === 'ALL') { - tx.add(StakeProgram.deactivate({ - authorizedPubkey: fromPubkey, - stakePubkey: new PublicKey(data.blockchainData.stakeAddress), - })); - } else { - tx.add(StakeProgram.withdraw({ - authorizedPubkey: fromPubkey, - stakePubkey: new PublicKey(data.blockchainData.stakeAddress), - lamports: data.amount * 1, - toPubkey: fromPubkey - })) - } - } else if (data.addressTo === 'STAKE') { - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' SolTransferProcessor.sendTx ' + data.addressFrom + ' => ' + data.addressTo + ' ' + data.amount + ' build start') - const validator = data.blockchainData.voteAddress - const authorized = new Authorized(fromPubkey, fromPubkey) - if (typeof validator === 'undefined' || !validator) { - throw new Error('no validator field') - } - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' SolTransferProcessor.sendTx ' + data.addressFrom + ' => ' + data.addressTo + ' ' + data.amount + ' build authorized') - - // https://github.com/velas/JsWallet/blob/251ad92bb5c2cd9a62477746a3db934b6dce0c4b/velas/velas-staking.js - // https://explorer.solana.com/tx/2ffmtkj3Yj51ZWCEHG6jb6s78F73eoiQdqURV7z65kSVLiPcm8Y9NE45FgfgwbddJD8kfgCiTpmrEu7J8WKpAQeE - await SolStakeUtils.getAccountStaked(data.addressFrom) - - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' SolTransferProcessor.sendTx ' + data.addressFrom + ' => ' + data.addressTo + ' ' + data.amount + ' build createWithSeed started') - let start = 0 - let lastSeed = await SolTmpDS.getCache(data.addressFrom) - if (typeof lastSeed !== 'undefined' && lastSeed && typeof lastSeed.seed !== 'undefined' && lastSeed.seed) { - start = lastSeed.seed * 1 - } - for (let i = 1; i <= 10000; i++) { - const tmpSeed = (i + start).toString() - - /*try { + /*try { stakeAccount = await PublicKey.createWithSeed( fromPubkey, tmpSeed, @@ -172,115 +267,214 @@ export default class SolTransferProcessor implements BlocksoftBlockchainTypes.Tr await BlocksoftCryptoLog.log(this._settings.currencyCode + ' SolTransferProcessor.sendTx ' + data.addressFrom + ' => ' + data.addressTo + ' ' + data.amount + ' build createWithSeed error ' + e1.message) }*/ - const buffer = Buffer.concat([ - fromPubkey.toBuffer(), - Buffer.from(tmpSeed), - StakeProgram.programId.toBuffer(), - ]) - const hash = Buffer.from(await BlocksoftCryptoUtils.sha256(buffer.toString('hex')), 'hex') - const stakeAccount = new PublicKey(Buffer.from(hash, 'hex')) - - stakeAddress = stakeAccount.toBase58() - const isUsed = SolStakeUtils.checkAccountStaked(data.addressFrom, stakeAddress) - if (!isUsed) { - await SolTmpDS.saveCache(data.addressFrom, 'seed', tmpSeed) - seed = tmpSeed - break - } - } - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' SolTransferProcessor.sendTx ' + data.addressFrom + ' => ' + data.addressTo + ' ' + data.amount + ' build createWithSeed finished') - - if (!stakeAddress) { - throw new Error('Stake address seed is not found') - } - - BlocksoftCryptoLog.log(this._settings.currencyCode + ' SolTransferProcessor.sendTx ' + data.addressFrom + ' => ' + data.addressTo + ' ' + data.amount + ' stakeAddress ' + stakeAddress + ' seed ' + seed) - - const amount = data.amount * 1 - tx.add(StakeProgram.createAccountWithSeed({ - authorized, - fromPubkey, - stakePubkey: new PublicKey(stakeAddress), - basePubkey: fromPubkey, - seed, - lamports: amount - })) - - // https://github.com/solana-labs/solana-web3.js/blob/35f0608a8363d3878d045bdb09cdd13af696bc6b/test/transaction.test.ts - tx.add( - StakeProgram.delegate({ - stakePubkey: new PublicKey(stakeAddress), - authorizedPubkey: new PublicKey(data.addressFrom), - votePubkey: new PublicKey(validator) - }) - ) - - } else { - // @ts-ignore - tx.add( - SystemProgram.transfer({ - fromPubkey: new PublicKey(data.addressFrom), - toPubkey: new PublicKey(data.addressTo), - lamports: data.amount * 1 - }) - ) - } - } catch (e) { - if (config.debug.cryptoErrors) { - console.log(this._settings.currencyCode + ' SolTransferProcessor.sendTx ' + data.addressFrom + ' => ' + data.addressTo + ' ' + data.amount + ' build error ') - console.log(e) - } - BlocksoftCryptoLog.log(this._settings.currencyCode + ' SolTransferProcessor.sendTx ' + data.addressFrom + ' => ' + data.addressTo + ' ' + data.amount + ' build error ' + e.message) - this.trxError(e.message) + const buffer = Buffer.concat([ + fromPubkey.toBuffer(), + Buffer.from(tmpSeed), + StakeProgram.programId.toBuffer() + ]); + const hash = Buffer.from( + await BlocksoftCryptoUtils.sha256(buffer.toString('hex')), + 'hex' + ); + const stakeAccount = new PublicKey(Buffer.from(hash, 'hex')); + + stakeAddress = stakeAccount.toBase58(); + const isUsed = SolStakeUtils.checkAccountStaked( + data.addressFrom, + stakeAddress + ); + if (!isUsed) { + await SolTmpDS.saveCache(data.addressFrom, 'seed', tmpSeed); + seed = tmpSeed; + break; + } } - - await SolUtils.signTransaction(tx, privateData.privateKey, data.addressFrom) - - // @ts-ignore - const signedData = tx.serialize().toString('base64') - if (typeof uiData !== 'undefined' && typeof uiData.selectedFee !== 'undefined' && typeof uiData.selectedFee.rawOnly !== 'undefined' && uiData.selectedFee.rawOnly) { - return { rawOnly: uiData.selectedFee.rawOnly, raw : signedData} + await BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' SolTransferProcessor.sendTx ' + + data.addressFrom + + ' => ' + + data.addressTo + + ' ' + + data.amount + + ' build createWithSeed finished' + ); + + if (!stakeAddress) { + throw new Error('Stake address seed is not found'); } - BlocksoftCryptoLog.log(this._settings.currencyCode + ' SolTransferProcessor.sendTx ' + data.addressFrom + ' => ' + data.addressTo + ' ' + data.amount, signedData) - - const result = {} as BlocksoftBlockchainTypes.SendTxResult - try { - const sendRes = await SolUtils.sendTransaction(signedData) - BlocksoftCryptoLog.log(this._settings.currencyCode + ' SolTransferProcessor.sendTx ' + data.addressFrom + ' => ' + data.addressTo + ' ' + data.amount, sendRes) - if (typeof sendRes === 'undefined' || !sendRes || typeof sendRes === 'undefined') { - throw new Error('SYSTEM_ERROR') - } - result.transactionHash = sendRes - if (stakeAddress) { - SolStakeUtils.setAccountStaked(data.addressFrom, stakeAddress) - } - if (data.addressTo.indexOf('UNSTAKE') === 0) { - await SolStakeUtils.getAccountStaked(data.addressFrom, true) - } - } catch (e) { - if (config.debug.cryptoErrors) { - console.log(this._settings.currencyCode + ' SolTransferProcessor.sendTx ' + data.addressFrom + ' => ' + data.addressTo + ' ' + data.amount + ' send error ') - console.log(e) - } - BlocksoftCryptoLog.log(this._settings.currencyCode + ' SolTransferProcessor.sendTx ' + data.addressFrom + ' => ' + data.addressTo + ' ' + data.amount + ' send error ' + e.message) - this.trxError(e.message, data.addressTo) - } - return result + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' SolTransferProcessor.sendTx ' + + data.addressFrom + + ' => ' + + data.addressTo + + ' ' + + data.amount + + ' stakeAddress ' + + stakeAddress + + ' seed ' + + seed + ); + + const amount = data.amount * 1; + tx.add( + StakeProgram.createAccountWithSeed({ + authorized, + fromPubkey, + stakePubkey: new PublicKey(stakeAddress), + basePubkey: fromPubkey, + seed, + lamports: amount + }) + ); + + // https://github.com/solana-labs/solana-web3.js/blob/35f0608a8363d3878d045bdb09cdd13af696bc6b/test/transaction.test.ts + tx.add( + StakeProgram.delegate({ + stakePubkey: new PublicKey(stakeAddress), + authorizedPubkey: new PublicKey(data.addressFrom), + votePubkey: new PublicKey(validator) + }) + ); + } else { + // @ts-ignore + tx.add( + SystemProgram.transfer({ + fromPubkey: new PublicKey(data.addressFrom), + toPubkey: new PublicKey(data.addressTo), + lamports: data.amount * 1 + }) + ); + } + } catch (e) { + if (config.debug.cryptoErrors) { + console.log( + this._settings.currencyCode + + ' SolTransferProcessor.sendTx ' + + data.addressFrom + + ' => ' + + data.addressTo + + ' ' + + data.amount + + ' build error ' + ); + console.log(e); + } + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' SolTransferProcessor.sendTx ' + + data.addressFrom + + ' => ' + + data.addressTo + + ' ' + + data.amount + + ' build error ' + + e.message + ); + this.trxError(e.message); } + await SolUtils.signTransaction( + tx, + privateData.privateKey, + data.addressFrom + ); + + // @ts-ignore + const signedData = tx.serialize().toString('base64'); + if ( + typeof uiData !== 'undefined' && + typeof uiData.selectedFee !== 'undefined' && + typeof uiData.selectedFee.rawOnly !== 'undefined' && + uiData.selectedFee.rawOnly + ) { + return { rawOnly: uiData.selectedFee.rawOnly, raw: signedData }; + } - trxError(msg: string, addressTo : string) { - if (msg.indexOf('insufficient funds for instruction') !== -1) { - if (addressTo === 'STAKE') { - throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_STAKE_SOL') - } else { - throw new Error('SERVER_RESPONSE_NOT_ENOUGH_BALANCE_SOL') - } - } else if (msg.indexOf('incorrect program id for instruction') !== -1 && addressTo === 'STAKE') { - throw new Error('SERVER_RESPONSE_NOT_VALIDATOR_STAKE_SOL') - } else { - throw new Error(msg) - } + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' SolTransferProcessor.sendTx ' + + data.addressFrom + + ' => ' + + data.addressTo + + ' ' + + data.amount, + signedData + ); + + const result = {} as BlocksoftBlockchainTypes.SendTxResult; + try { + const sendRes = await SolUtils.sendTransaction(signedData); + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' SolTransferProcessor.sendTx ' + + data.addressFrom + + ' => ' + + data.addressTo + + ' ' + + data.amount, + sendRes + ); + if ( + typeof sendRes === 'undefined' || + !sendRes || + typeof sendRes === 'undefined' + ) { + throw new Error('SYSTEM_ERROR'); + } + result.transactionHash = sendRes; + if (stakeAddress) { + SolStakeUtils.setAccountStaked(data.addressFrom, stakeAddress); + } + if (data.addressTo.indexOf('UNSTAKE') === 0) { + await SolStakeUtils.getAccountStaked(data.addressFrom, true); + } + } catch (e) { + if (config.debug.cryptoErrors) { + console.log( + this._settings.currencyCode + + ' SolTransferProcessor.sendTx ' + + data.addressFrom + + ' => ' + + data.addressTo + + ' ' + + data.amount + + ' send error ' + ); + console.log(e); + } + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' SolTransferProcessor.sendTx ' + + data.addressFrom + + ' => ' + + data.addressTo + + ' ' + + data.amount + + ' send error ' + + e.message + ); + this.trxError(e.message, data.addressTo); + } + return result; + } + + trxError(msg: string, addressTo: string) { + if (msg.indexOf('insufficient funds for instruction') !== -1) { + if (addressTo === 'STAKE') { + throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_STAKE_SOL'); + } else { + throw new Error('SERVER_RESPONSE_NOT_ENOUGH_BALANCE_SOL'); + } + } else if ( + msg.indexOf('incorrect program id for instruction') !== -1 && + addressTo === 'STAKE' + ) { + throw new Error('SERVER_RESPONSE_NOT_VALIDATOR_STAKE_SOL'); + } else { + throw new Error(msg); } + } } diff --git a/crypto/blockchains/sol/SolTransferProcessorSpl.ts b/crypto/blockchains/sol/SolTransferProcessorSpl.ts index 6416ea2e8..b494e2fa0 100644 --- a/crypto/blockchains/sol/SolTransferProcessorSpl.ts +++ b/crypto/blockchains/sol/SolTransferProcessorSpl.ts @@ -3,196 +3,341 @@ */ // eslint-disable-next-line no-unused-vars -import { BlocksoftBlockchainTypes } from '@crypto/blockchains/BlocksoftBlockchainTypes' -import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog' -import BlocksoftBalances from '@crypto/actions/BlocksoftBalances/BlocksoftBalances' - -import { PublicKey, TransactionInstruction, Transaction } from '@solana/web3.js/src/index' -import SolUtils from '@crypto/blockchains/sol/ext/SolUtils' -import SolTransferProcessor from '@crypto/blockchains/sol/SolTransferProcessor' -import SolInstructions from '@crypto/blockchains/sol/ext/SolInstructions' -import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings' - - -export default class SolTransferProcessorSpl extends SolTransferProcessor implements BlocksoftBlockchainTypes.TransferProcessor { - - async getFeeRate(data: BlocksoftBlockchainTypes.TransferData, privateData: BlocksoftBlockchainTypes.TransferPrivateData, additionalData: {} = {}): Promise { - const result: BlocksoftBlockchainTypes.FeeRateResult = { - selectedFeeIndex: -3, - shouldShowFees: false - } as BlocksoftBlockchainTypes.FeeRateResult - - - const destinationAssociatedTokenAddress = await SolUtils.findAssociatedTokenAddress( - data.addressTo, - this._settings.tokenAddress - ) - const destinationAccountInfo = await SolUtils.getAccountInfo(destinationAssociatedTokenAddress) - let feeForTx = BlocksoftExternalSettings.getStatic('SOL_PRICE') // ◎0.000005 - if ( - destinationAccountInfo && typeof destinationAccountInfo.owner !== 'undefined' && destinationAccountInfo.owner === SolUtils.getTokenProgramID() - ) { - // do nothing - } else { - // will create new account - feeForTx = BlocksoftExternalSettings.getStatic('SOL_PRICE_NEW_SPL') // // ◎0.00203928 + 0.000005 = 0.00204428 - } +import { BlocksoftBlockchainTypes } from '@crypto/blockchains/BlocksoftBlockchainTypes'; +import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; +import BlocksoftBalances from '@crypto/actions/BlocksoftBalances/BlocksoftBalances'; - result.fees = [ - { - langMsg: 'xrp_speed_one', - feeForTx, - amountForTx: data.amount - } - ] - result.selectedFeeIndex = 0 +import { + PublicKey, + TransactionInstruction, + Transaction +} from '@solana/web3.js/src/index'; +import SolUtils from '@crypto/blockchains/sol/ext/SolUtils'; +import SolTransferProcessor from '@crypto/blockchains/sol/SolTransferProcessor'; +import SolInstructions from '@crypto/blockchains/sol/ext/SolInstructions'; +import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings'; +export default class SolTransferProcessorSpl + extends SolTransferProcessor + implements BlocksoftBlockchainTypes.TransferProcessor +{ + async getFeeRate( + data: BlocksoftBlockchainTypes.TransferData, + privateData: BlocksoftBlockchainTypes.TransferPrivateData, + additionalData: {} = {} + ): Promise { + const result: BlocksoftBlockchainTypes.FeeRateResult = { + selectedFeeIndex: -3, + shouldShowFees: false + } as BlocksoftBlockchainTypes.FeeRateResult; - return result + const destinationAssociatedTokenAddress = + await SolUtils.findAssociatedTokenAddress( + data.addressTo, + this._settings.tokenAddress + ); + const destinationAccountInfo = await SolUtils.getAccountInfo( + destinationAssociatedTokenAddress + ); + let feeForTx = BlocksoftExternalSettings.getStatic('SOL_PRICE'); // ◎0.000005 + if ( + destinationAccountInfo && + typeof destinationAccountInfo.owner !== 'undefined' && + destinationAccountInfo.owner === SolUtils.getTokenProgramID() + ) { + // do nothing + } else { + // will create new account + feeForTx = BlocksoftExternalSettings.getStatic('SOL_PRICE_NEW_SPL'); // // ◎0.00203928 + 0.000005 = 0.00204428 } - async getTransferAllBalance(data: BlocksoftBlockchainTypes.TransferData, privateData: BlocksoftBlockchainTypes.TransferPrivateData, additionalData: BlocksoftBlockchainTypes.TransferAdditionalData = {}): Promise { - const balance = data.amount - // @ts-ignore - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' SolTransferProcessorSpl.getTransferAllBalance ', data.addressFrom + ' => ' + balance) + result.fees = [ + { + langMsg: 'xrp_speed_one', + feeForTx, + amountForTx: data.amount + } + ]; + result.selectedFeeIndex = 0; - const fees = await this.getFeeRate(data, privateData, additionalData) + return result; + } - return { - ...fees, - shouldShowFees: false, - selectedTransferAllBalance: balance - } - } + async getTransferAllBalance( + data: BlocksoftBlockchainTypes.TransferData, + privateData: BlocksoftBlockchainTypes.TransferPrivateData, + additionalData: BlocksoftBlockchainTypes.TransferAdditionalData = {} + ): Promise { + const balance = data.amount; + // @ts-ignore + await BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' SolTransferProcessorSpl.getTransferAllBalance ', + data.addressFrom + ' => ' + balance + ); - /** - * @param data - * @param privateData - * @param uiData - */ - async sendTx(data: BlocksoftBlockchainTypes.TransferData, privateData: BlocksoftBlockchainTypes.TransferPrivateData, uiData: BlocksoftBlockchainTypes.TransferUiData): Promise { + const fees = await this.getFeeRate(data, privateData, additionalData); - if (typeof privateData.privateKey === 'undefined') { - throw new Error('SPL transaction required privateKey (derivedSeed)') - } - if (typeof data.addressTo === 'undefined') { - throw new Error('SPL transaction required addressTo') - } + return { + ...fees, + shouldShowFees: false, + selectedTransferAllBalance: balance + }; + } - const sourceAssociatedTokenAddress = await SolUtils.findAssociatedTokenAddress( - data.addressFrom, - this._settings.tokenAddress - ) - const sourceAccountInfo = await SolUtils.getAccountInfo(sourceAssociatedTokenAddress) - if (!sourceAccountInfo || typeof sourceAccountInfo.lamports === 'undefined' || sourceAccountInfo.lamports * 1 === 0) { - throw new Error('Cannot send from address with zero SOL balance') - } + /** + * @param data + * @param privateData + * @param uiData + */ + async sendTx( + data: BlocksoftBlockchainTypes.TransferData, + privateData: BlocksoftBlockchainTypes.TransferPrivateData, + uiData: BlocksoftBlockchainTypes.TransferUiData + ): Promise { + if (typeof privateData.privateKey === 'undefined') { + throw new Error('SPL transaction required privateKey (derivedSeed)'); + } + if (typeof data.addressTo === 'undefined') { + throw new Error('SPL transaction required addressTo'); + } + const sourceAssociatedTokenAddress = + await SolUtils.findAssociatedTokenAddress( + data.addressFrom, + this._settings.tokenAddress + ); + const sourceAccountInfo = await SolUtils.getAccountInfo( + sourceAssociatedTokenAddress + ); + if ( + !sourceAccountInfo || + typeof sourceAccountInfo.lamports === 'undefined' || + sourceAccountInfo.lamports * 1 === 0 + ) { + throw new Error('Cannot send from address with zero SOL balance'); + } + const tx = new Transaction(); + let txData = false; + let destinationAccountInfo = await SolUtils.getAccountInfo(data.addressTo); + if ( + destinationAccountInfo && + typeof destinationAccountInfo.owner !== 'undefined' && + destinationAccountInfo.owner === SolUtils.getTokenProgramID() + ) { + txData = { + mint: this._settings.tokenAddress, + decimals: this._settings.decimals, + to: data.addressTo, + amount: data.amount + }; + } else { + if ( + !destinationAccountInfo || + typeof destinationAccountInfo.lamports === 'undefined' || + destinationAccountInfo.lamports * 1 === 0 + ) { + throw new Error('SERVER_RESPONSE_RECEIVER_EMPTY_BALANCE'); + } - const tx = new Transaction() - let txData = false - let destinationAccountInfo = await SolUtils.getAccountInfo(data.addressTo) - if ( - destinationAccountInfo && typeof destinationAccountInfo.owner !== 'undefined' && destinationAccountInfo.owner === SolUtils.getTokenProgramID() - ) { - txData = { - mint: this._settings.tokenAddress, - decimals: this._settings.decimals, - to: data.addressTo, - amount: data.amount + const destinationAssociatedTokenAddress = + await SolUtils.findAssociatedTokenAddress( + data.addressTo, + this._settings.tokenAddress + ); + destinationAccountInfo = await SolUtils.getAccountInfo( + destinationAssociatedTokenAddress + ); + if ( + destinationAccountInfo && + typeof destinationAccountInfo.owner !== 'undefined' && + destinationAccountInfo.owner === SolUtils.getTokenProgramID() + ) { + // do nothing + } else { + const tmp1 = new TransactionInstruction({ + keys: [ + { + pubkey: new PublicKey(data.addressTo), + isSigner: false, + isWritable: false } - } else { + ], + data: SolInstructions.encodeOwnerValidationInstruction({ + account: new PublicKey('11111111111111111111111111111111') + }), + programId: SolUtils.getOwnerValidationProgramId() + }); + tx.add(tmp1); - if (!destinationAccountInfo || typeof destinationAccountInfo.lamports === 'undefined' || destinationAccountInfo.lamports * 1 === 0) { - throw new Error('SERVER_RESPONSE_RECEIVER_EMPTY_BALANCE') - } + const keys = [ + { + pubkey: new PublicKey(data.addressFrom), + isSigner: true, + isWritable: true + }, + { + pubkey: new PublicKey(destinationAssociatedTokenAddress), + isSigner: false, + isWritable: true + }, + { + pubkey: new PublicKey(data.addressTo), + isSigner: false, + isWritable: false + }, + { + pubkey: new PublicKey(this._settings.tokenAddress), + isSigner: false, + isWritable: false + }, + { + pubkey: new PublicKey('11111111111111111111111111111111'), + isSigner: false, + isWritable: false + }, + { + pubkey: new PublicKey(SolUtils.getTokenProgramID()), + isSigner: false, + isWritable: false + }, + { + pubkey: new PublicKey( + 'SysvarRent111111111111111111111111111111111' + ), + isSigner: false, + isWritable: false + } + ]; + const tmp2 = new TransactionInstruction({ + keys, + programId: SolUtils.getAssociatedTokenProgramId(), + data: Buffer.from([]) + }); + tx.add(tmp2); + // add owner + } + txData = { + mint: this._settings.tokenAddress, + decimals: this._settings.decimals, + to: destinationAssociatedTokenAddress, + amount: data.amount + }; + } - const destinationAssociatedTokenAddress = await SolUtils.findAssociatedTokenAddress( - data.addressTo, - this._settings.tokenAddress - ) - destinationAccountInfo = await SolUtils.getAccountInfo(destinationAssociatedTokenAddress) - if ( - destinationAccountInfo && typeof destinationAccountInfo.owner !== 'undefined' && destinationAccountInfo.owner === SolUtils.getTokenProgramID() - ) { - // do nothing - } else { - const tmp1 = new TransactionInstruction({ - keys: [{ pubkey: new PublicKey(data.addressTo), isSigner: false, isWritable: false }], - data: SolInstructions.encodeOwnerValidationInstruction({ account: new PublicKey('11111111111111111111111111111111') }), - programId: SolUtils.getOwnerValidationProgramId() - }) - tx.add(tmp1) - - const keys = [ - { pubkey: new PublicKey(data.addressFrom), isSigner: true, isWritable: true }, - { pubkey: new PublicKey(destinationAssociatedTokenAddress), isSigner: false, isWritable: true }, - { pubkey: new PublicKey(data.addressTo), isSigner: false, isWritable: false }, - { pubkey: new PublicKey(this._settings.tokenAddress), isSigner: false, isWritable: false }, - { pubkey: new PublicKey('11111111111111111111111111111111'), isSigner: false, isWritable: false }, - { pubkey: new PublicKey(SolUtils.getTokenProgramID()), isSigner: false, isWritable: false }, - { pubkey: new PublicKey('SysvarRent111111111111111111111111111111111'), isSigner: false, isWritable: false } - ] - const tmp2 = new TransactionInstruction({ - keys, - programId: SolUtils.getAssociatedTokenProgramId(), - data: Buffer.from([]) - }) - tx.add(tmp2) - // add owner - } - txData = { - mint: this._settings.tokenAddress, - decimals: this._settings.decimals, - to: destinationAssociatedTokenAddress, - amount: data.amount - } - } + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' SolTransferProcessorSpl.sendTx ' + + data.addressFrom + + ' => ' + + data.addressTo + + ' ' + + data.amount, + txData + ); - BlocksoftCryptoLog.log(this._settings.currencyCode + ' SolTransferProcessorSpl.sendTx ' + data.addressFrom + ' => ' + data.addressTo + ' ' + data.amount, txData) - - - try { - // https://github.com/project-serum/spl-token-wallet/blob/eda316d30bb7be4250dc622e41b6fda6f54ca7d8/src/utils/tokens/instructions.js#L99 - const keys = [ - { pubkey: new PublicKey(sourceAssociatedTokenAddress), isSigner: false, isWritable: true }, - { pubkey: new PublicKey(txData.mint), isSigner: false, isWritable: false }, - { pubkey: new PublicKey(txData.to), isSigner: false, isWritable: true }, - { pubkey: new PublicKey(data.addressFrom), isSigner: true, isWritable: false } - ] - tx.add(new TransactionInstruction({ - keys, - data: SolInstructions.encodeTokenInstructionData({ - transferChecked: { amount: txData.amount, decimals: txData.decimals } - }), - programId: SolUtils.getTokenProgramID() - })) - - } catch (e) { - BlocksoftCryptoLog.log(this._settings.currencyCode + ' SolTransferProcessorSpl.sendTx ' + data.addressFrom + ' => ' + data.addressTo + ' ' + data.amount + ' error ' + e.message) - throw new Error(e.message) + try { + // https://github.com/project-serum/spl-token-wallet/blob/eda316d30bb7be4250dc622e41b6fda6f54ca7d8/src/utils/tokens/instructions.js#L99 + const keys = [ + { + pubkey: new PublicKey(sourceAssociatedTokenAddress), + isSigner: false, + isWritable: true + }, + { + pubkey: new PublicKey(txData.mint), + isSigner: false, + isWritable: false + }, + { pubkey: new PublicKey(txData.to), isSigner: false, isWritable: true }, + { + pubkey: new PublicKey(data.addressFrom), + isSigner: true, + isWritable: false } + ]; + tx.add( + new TransactionInstruction({ + keys, + data: SolInstructions.encodeTokenInstructionData({ + transferChecked: { + amount: txData.amount, + decimals: txData.decimals + } + }), + programId: SolUtils.getTokenProgramID() + }) + ); + } catch (e) { + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' SolTransferProcessorSpl.sendTx ' + + data.addressFrom + + ' => ' + + data.addressTo + + ' ' + + data.amount + + ' error ' + + e.message + ); + throw new Error(e.message); + } - await SolUtils.signTransaction(tx, privateData.privateKey, data.addressFrom) - - // @ts-ignore - const signedData = tx.serialize().toString('base64') - BlocksoftCryptoLog.log(this._settings.currencyCode + ' SolTransferProcessorSpl.sendTx ' + data.addressFrom + ' => ' + data.addressTo + ' ' + data.amount, signedData) + await SolUtils.signTransaction( + tx, + privateData.privateKey, + data.addressFrom + ); + // @ts-ignore + const signedData = tx.serialize().toString('base64'); + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' SolTransferProcessorSpl.sendTx ' + + data.addressFrom + + ' => ' + + data.addressTo + + ' ' + + data.amount, + signedData + ); - const result = {} as BlocksoftBlockchainTypes.SendTxResult - try { - const sendRes = await SolUtils.sendTransaction(signedData) - BlocksoftCryptoLog.log(this._settings.currencyCode + ' SolTransferProcessorSpl.sendTx ' + data.addressFrom + ' => ' + data.addressTo + ' ' + data.amount, sendRes) - if (typeof sendRes === 'undefined' || !sendRes || typeof sendRes === 'undefined') { - throw new Error('SYSTEM_ERROR') - } - result.transactionHash = sendRes - } catch (e) { - BlocksoftCryptoLog.log(this._settings.currencyCode + ' SolTransferProcessorSpl.sendTx ' + data.addressFrom + ' => ' + data.addressTo + ' ' + data.amount + ' send error ' + e.message) - throw e - } - return result + const result = {} as BlocksoftBlockchainTypes.SendTxResult; + try { + const sendRes = await SolUtils.sendTransaction(signedData); + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' SolTransferProcessorSpl.sendTx ' + + data.addressFrom + + ' => ' + + data.addressTo + + ' ' + + data.amount, + sendRes + ); + if ( + typeof sendRes === 'undefined' || + !sendRes || + typeof sendRes === 'undefined' + ) { + throw new Error('SYSTEM_ERROR'); + } + result.transactionHash = sendRes; + } catch (e) { + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' SolTransferProcessorSpl.sendTx ' + + data.addressFrom + + ' => ' + + data.addressTo + + ' ' + + data.amount + + ' send error ' + + e.message + ); + throw e; } + return result; + } } diff --git a/crypto/blockchains/sol/ext/SolStakeUtils.js b/crypto/blockchains/sol/ext/SolStakeUtils.js index cb402d996..cf972ef0c 100644 --- a/crypto/blockchains/sol/ext/SolStakeUtils.js +++ b/crypto/blockchains/sol/ext/SolStakeUtils.js @@ -1,139 +1,154 @@ /** * @version 0.52 */ -import BlocksoftAxios from '@crypto/common/BlocksoftAxios' -import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings' +import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; +import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings'; -import config from '@app/config/config' -import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog' -import BlocksoftUtils from '@crypto/common/BlocksoftUtils' -import SolUtils from '@crypto/blockchains/sol/ext/SolUtils' +import config from '@app/config/config'; +import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; +import BlocksoftUtils from '@crypto/common/BlocksoftUtils'; +import SolUtils from '@crypto/blockchains/sol/ext/SolUtils'; - -const CACHE_STAKED = {} +const CACHE_STAKED = {}; const CACHE_VOTES = { - data: [], - time: 0 -} -const CACHE_VALID_TIME = 12000000 // 200 minute + data: [], + time: 0 +}; +const CACHE_VALID_TIME = 12000000; // 200 minute -const validatorsConstants = require('@crypto/blockchains/sol/ext/validators') +const validatorsConstants = require('@crypto/blockchains/sol/ext/validators'); const validators = { - time: 0, - data: {} -} + time: 0, + data: {} +}; for (const tmp of validatorsConstants) { - validators.data[tmp.id] = tmp + validators.data[tmp.id] = tmp; } - const init = async () => { - const link = await BlocksoftExternalSettings.get('SOL_VALIDATORS_LIST') - const res = await BlocksoftAxios.get(link) - if (!res.data || typeof res.data[0] === 'undefined' || !res.data[0]) { - return false - } - validators.data = {} - for (const tmp of res.data) { - if (typeof tmp.id === 'undefined') continue - validators.data[tmp.id] = tmp - } - validators.time = new Date().getTime() -} + const link = await BlocksoftExternalSettings.get('SOL_VALIDATORS_LIST'); + const res = await BlocksoftAxios.get(link); + if (!res.data || typeof res.data[0] === 'undefined' || !res.data[0]) { + return false; + } + validators.data = {}; + for (const tmp of res.data) { + if (typeof tmp.id === 'undefined') continue; + validators.data[tmp.id] = tmp; + } + validators.time = new Date().getTime(); +}; export default { - - - // https://docs.solana.com/developing/clients/jsonrpc-api#getvoteaccounts - async getVoteAddresses() { - try { - const now = new Date().getTime() - if (CACHE_VOTES.time && now - CACHE_VOTES.time < CACHE_VALID_TIME) { - return CACHE_VOTES.data - } - if (!validators.time) { - await init() - } - - const apiPath = BlocksoftExternalSettings.getStatic('SOL_SERVER') - const getVote = { 'jsonrpc': '2.0', 'id': 1, 'method': 'getVoteAccounts' } - const resVote = await BlocksoftAxios._request(apiPath, 'POST', getVote) - if (!resVote || typeof resVote.data === 'undefined' || typeof resVote.data.result === 'undefined' || !resVote.data.result || typeof resVote.data.result.current === 'undefined') { - return CACHE_VOTES.data - } - CACHE_VOTES.data = [] - for (const tmp of resVote.data.result.current) { - const address = tmp.votePubkey - if (typeof validators.data[address] === 'undefined') continue - - const validator = { address, commission: tmp.commission, activatedStake: tmp.activatedStake, name: '', description: '', website: '', index : 100 } - validator.index = typeof validators.data[validator.address].index !== 'undefined' ? validators.data[validator.address].index : 0 - validator.name = validators.data[validator.address].name - validator.description = validators.data[validator.address].description - validator.website = validators.data[validator.address].website - - CACHE_VOTES.data.push(validator) - } - CACHE_VOTES.data.sort((a, b) => { - if (a.index*1 === b.index*1) { - const diff = a.commission - b.commission - if (diff <= 0.1 && diff >= -0.1) { - return b.activatedStake - a.activatedStake - } - return diff - } else { - return b.index - a.index - } - }) - CACHE_VOTES.time = now - } catch (e) { - if (config.debug.cryptoErrors) { - console.log('SolStakeUtils.getVoteAddresses error ' + e.message) - } - BlocksoftCryptoLog.log('SolStakeUtils.getVoteAddresses error ' + e.message) - } - return CACHE_VOTES.data - }, - - checkAccountStaked(address, subaddress) { - return typeof CACHE_STAKED[address].all[subaddress] !== 'undefined' - }, - - setAccountStaked(address, subaddress) { - CACHE_STAKED[address].all[subaddress] = true - }, - - // https://prod-api.solana.surf/v1/account/3siLSmroYvPHPZCrK5VYR3gmFhQFWefVGGpasXdzSPnn/stake-rewards - async getAccountRewards(address, stakedAddresses) { - if (typeof CACHE_STAKED[address] === 'undefined') { - CACHE_STAKED[address] = { - all: {}, - active: [], - rewards: [] - } + // https://docs.solana.com/developing/clients/jsonrpc-api#getvoteaccounts + async getVoteAddresses() { + try { + const now = new Date().getTime(); + if (CACHE_VOTES.time && now - CACHE_VOTES.time < CACHE_VALID_TIME) { + return CACHE_VOTES.data; + } + if (!validators.time) { + await init(); + } + + const apiPath = BlocksoftExternalSettings.getStatic('SOL_SERVER'); + const getVote = { jsonrpc: '2.0', id: 1, method: 'getVoteAccounts' }; + const resVote = await BlocksoftAxios._request(apiPath, 'POST', getVote); + if ( + !resVote || + typeof resVote.data === 'undefined' || + typeof resVote.data.result === 'undefined' || + !resVote.data.result || + typeof resVote.data.result.current === 'undefined' + ) { + return CACHE_VOTES.data; + } + CACHE_VOTES.data = []; + for (const tmp of resVote.data.result.current) { + const address = tmp.votePubkey; + if (typeof validators.data[address] === 'undefined') continue; + + const validator = { + address, + commission: tmp.commission, + activatedStake: tmp.activatedStake, + name: '', + description: '', + website: '', + index: 100 + }; + validator.index = + typeof validators.data[validator.address].index !== 'undefined' + ? validators.data[validator.address].index + : 0; + validator.name = validators.data[validator.address].name; + validator.description = validators.data[validator.address].description; + validator.website = validators.data[validator.address].website; + + CACHE_VOTES.data.push(validator); + } + CACHE_VOTES.data.sort((a, b) => { + if (a.index * 1 === b.index * 1) { + const diff = a.commission - b.commission; + if (diff <= 0.1 && diff >= -0.1) { + return b.activatedStake - a.activatedStake; + } + return diff; + } else { + return b.index - a.index; } - const askAddresses = [ address ] - if (stakedAddresses) { - for(let tmp of stakedAddresses) { - askAddresses.push(tmp.stakeAddress) - } - } - try { - const link = BlocksoftExternalSettings.getStatic('SOL_SERVER') - const data = { - 'method': 'getInflationReward', - 'jsonrpc': '2.0', - 'params': [ - askAddresses, - { - 'commitment': 'confirmed' - } - ], - 'id': '1' - } - - /*console.log(` + }); + CACHE_VOTES.time = now; + } catch (e) { + if (config.debug.cryptoErrors) { + console.log('SolStakeUtils.getVoteAddresses error ' + e.message); + } + BlocksoftCryptoLog.log( + 'SolStakeUtils.getVoteAddresses error ' + e.message + ); + } + return CACHE_VOTES.data; + }, + + checkAccountStaked(address, subaddress) { + return typeof CACHE_STAKED[address].all[subaddress] !== 'undefined'; + }, + + setAccountStaked(address, subaddress) { + CACHE_STAKED[address].all[subaddress] = true; + }, + + // https://prod-api.solana.surf/v1/account/3siLSmroYvPHPZCrK5VYR3gmFhQFWefVGGpasXdzSPnn/stake-rewards + async getAccountRewards(address, stakedAddresses) { + if (typeof CACHE_STAKED[address] === 'undefined') { + CACHE_STAKED[address] = { + all: {}, + active: [], + rewards: [] + }; + } + const askAddresses = [address]; + if (stakedAddresses) { + for (let tmp of stakedAddresses) { + askAddresses.push(tmp.stakeAddress); + } + } + try { + const link = BlocksoftExternalSettings.getStatic('SOL_SERVER'); + const data = { + method: 'getInflationReward', + jsonrpc: '2.0', + params: [ + askAddresses, + { + commitment: 'confirmed' + } + ], + id: '1' + }; + + /*console.log(` curl ${link} -X POST -H "Content-Type: application/json" -d '${JSON.stringify(data)}' @@ -141,77 +156,94 @@ export default { `) */ - const res = await BlocksoftAxios.post(link, data) - if (res.data && typeof res.data.result !== 'undefined' && typeof res.data.result[0] !== 'undefined') { - CACHE_STAKED[address].rewards = res.data.result[0] - if (stakedAddresses) { - let count = 0 - if (typeof CACHE_STAKED[address].rewards !== 'undefined') { - count = 100; - } else { - for(let tmp of res.data.result) { - if (tmp && typeof tmp.amount !== 'undefined' && tmp.amount * 1 > 0) { - CACHE_STAKED[address].rewards = tmp - count++ - } - } - } - if (count > 1) { - if (typeof CACHE_STAKED[address].rewards !== 'undefined') { - CACHE_STAKED[address].rewards.amount = 0; - } - for (let tmp of res.data.result) { - if (tmp && typeof tmp.amount !== 'undefined' && tmp.amount * 1 > 0) { - CACHE_STAKED[address].rewards.amount += tmp.amount * 1 - } - } - } - } + const res = await BlocksoftAxios.post(link, data); + if ( + res.data && + typeof res.data.result !== 'undefined' && + typeof res.data.result[0] !== 'undefined' + ) { + CACHE_STAKED[address].rewards = res.data.result[0]; + if (stakedAddresses) { + let count = 0; + if (typeof CACHE_STAKED[address].rewards !== 'undefined') { + count = 100; + } else { + for (let tmp of res.data.result) { + if ( + tmp && + typeof tmp.amount !== 'undefined' && + tmp.amount * 1 > 0 + ) { + CACHE_STAKED[address].rewards = tmp; + count++; + } } - } catch (e) { - if (config.debug.cryptoErrors) { - console.log('SolStakeUtils.getAccountRewards ' + address + ' error ' + e.message) + } + if (count > 1) { + if (typeof CACHE_STAKED[address].rewards !== 'undefined') { + CACHE_STAKED[address].rewards.amount = 0; } - BlocksoftCryptoLog.log('SolStakeUtils.getAccountRewards ' + address + ' error ' + e.message) - } - //{"amount": 96096, "apr": 7.044036109546499, "effectiveSlot": 99360012, "epoch": 229, "percentChange": 0.05205832165890872, "postBalance": 184689062, "timestamp": 1633153114}, - return CACHE_STAKED[address].rewards - }, - - // https://docs.solana.com/developing/clients/jsonrpc-api#getprogramaccounts - async getAccountStaked(address, isForce = false) { - let accountInfo = false - if (typeof CACHE_STAKED[address] === 'undefined' || isForce) { - CACHE_STAKED[address] = { - all: {}, - active: [], - rewards: [] + for (let tmp of res.data.result) { + if ( + tmp && + typeof tmp.amount !== 'undefined' && + tmp.amount * 1 > 0 + ) { + CACHE_STAKED[address].rewards.amount += tmp.amount * 1; + } } + } } - try { - const apiPath = BlocksoftExternalSettings.getStatic('SOL_SERVER') - const currentEpoch = await SolUtils.getEpoch() - - const checkData = { - 'jsonrpc': '2.0', - 'id': 1, - 'method': 'getProgramAccounts', - 'params': [ - 'Stake11111111111111111111111111111111111111', - { - 'encoding': 'jsonParsed', - filters: - [{ - memcmp: { - offset: 0xc, - bytes: address - } - }] - } - ] - } + } + } catch (e) { + if (config.debug.cryptoErrors) { + console.log( + 'SolStakeUtils.getAccountRewards ' + address + ' error ' + e.message + ); + } + BlocksoftCryptoLog.log( + 'SolStakeUtils.getAccountRewards ' + address + ' error ' + e.message + ); + } + //{"amount": 96096, "apr": 7.044036109546499, "effectiveSlot": 99360012, "epoch": 229, "percentChange": 0.05205832165890872, "postBalance": 184689062, "timestamp": 1633153114}, + return CACHE_STAKED[address].rewards; + }, + + // https://docs.solana.com/developing/clients/jsonrpc-api#getprogramaccounts + async getAccountStaked(address, isForce = false) { + let accountInfo = false; + if (typeof CACHE_STAKED[address] === 'undefined' || isForce) { + CACHE_STAKED[address] = { + all: {}, + active: [], + rewards: [] + }; + } + try { + const apiPath = BlocksoftExternalSettings.getStatic('SOL_SERVER'); + const currentEpoch = await SolUtils.getEpoch(); + + const checkData = { + jsonrpc: '2.0', + id: 1, + method: 'getProgramAccounts', + params: [ + 'Stake11111111111111111111111111111111111111', + { + encoding: 'jsonParsed', + filters: [ + { + memcmp: { + offset: 0xc, + bytes: address + } + } + ] + } + ] + }; - /* + /* console.log(` @@ -219,89 +251,122 @@ export default { `) */ - let res - accountInfo = [] - try { - res = await BlocksoftAxios._request(apiPath, 'POST', checkData) - - for (const tmp of res.data.result) { - const parsed = tmp.account.data.parsed - const item = { amount: tmp.account.lamports, stakeAddress: tmp.pubkey, reserved: 0, active: true, status: '' } - if (typeof parsed.info !== 'undefined') { - if (typeof parsed.info.meta !== 'undefined') { - if (typeof parsed.info.meta.rentExemptReserve !== 'undefined') { - item.reserved = parsed.info.meta.rentExemptReserve - } - } - const deactivationEpoch = parsed.info.stake.delegation.deactivationEpoch || 0 - const activationEpoch = parsed.info.stake.delegation.activationEpoch || 0 - if (currentEpoch && currentEpoch * 1 >= deactivationEpoch * 1) { - item.order = 1 - item.active = false - item.status = 'inactive' - } else if (currentEpoch && currentEpoch === activationEpoch) { - item.order = 3 - item.status = 'activating' - } else { - item.order = 2 - item.status = 'staked' - } - } - item.diff = BlocksoftUtils.diff(item.amount, item.reserved).toString() - accountInfo.push(item) - CACHE_STAKED[address].all[item.stakeAddress] = true - } - - } catch (e) { - BlocksoftCryptoLog.log('SolStakeUtils getAccountStaked request ' + apiPath + ' error ' + e.message) - - const apiPath2 = 'https://prod-api.solana.surf/v1/account/' + address + '/stakes?limit=10&offset=0' - const res2 = await BlocksoftAxios.get(apiPath2) - - for (const tmp of res2.data.data) { - const item = { amount: tmp.lamports, stakeAddress: tmp.pubkey.address, reserved: 0, active: true, status: '' } - - if (typeof tmp.data !== 'undefined') { - if (typeof tmp.data.meta !== 'undefined') { - if (typeof tmp.data.meta.rent_exempt_reserve !== 'undefined') { - item.reserved = tmp.data.meta.rent_exempt_reserve - } - } - const deactivationEpoch = tmp.data.stake.delegation.deactivation_epoch || 0 - const activationEpoch = tmp.data.stake.delegation.activation_epoch|| 0 - if (currentEpoch && currentEpoch * 1 >= deactivationEpoch * 1) { - item.order = 1 - item.active = false - item.status = 'inactive' - } else if (currentEpoch && currentEpoch === activationEpoch) { - item.order = 3 - item.status = 'activating' - } else { - item.order = 2 - item.status = 'staked' - } - } - item.diff = BlocksoftUtils.diff(item.amount, item.reserved).toString() - accountInfo.push(item) - CACHE_STAKED[address].all[item.stakeAddress] = true - } + let res; + accountInfo = []; + try { + res = await BlocksoftAxios._request(apiPath, 'POST', checkData); + + for (const tmp of res.data.result) { + const parsed = tmp.account.data.parsed; + const item = { + amount: tmp.account.lamports, + stakeAddress: tmp.pubkey, + reserved: 0, + active: true, + status: '' + }; + if (typeof parsed.info !== 'undefined') { + if (typeof parsed.info.meta !== 'undefined') { + if (typeof parsed.info.meta.rentExemptReserve !== 'undefined') { + item.reserved = parsed.info.meta.rentExemptReserve; + } } - - accountInfo.sort((a, b) => { - if (b.order === a.order) { - return BlocksoftUtils.diff(b.diff, a.diff) * 1 - } else { - return b.order - a.order - } - }) - CACHE_STAKED[address].active = accountInfo - } catch (e) { - if (config.debug.cryptoErrors) { - console.log('SolStakeUtils.getAccountStaked ' + address + ' error ' + e.message) + const deactivationEpoch = + parsed.info.stake.delegation.deactivationEpoch || 0; + const activationEpoch = + parsed.info.stake.delegation.activationEpoch || 0; + if (currentEpoch && currentEpoch * 1 >= deactivationEpoch * 1) { + item.order = 1; + item.active = false; + item.status = 'inactive'; + } else if (currentEpoch && currentEpoch === activationEpoch) { + item.order = 3; + item.status = 'activating'; + } else { + item.order = 2; + item.status = 'staked'; } - BlocksoftCryptoLog.log('SolStakeUtils.getAccountStaked ' + address + ' error ' + e.message) - return CACHE_STAKED[address].active + } + item.diff = BlocksoftUtils.diff( + item.amount, + item.reserved + ).toString(); + accountInfo.push(item); + CACHE_STAKED[address].all[item.stakeAddress] = true; } - return accountInfo + } catch (e) { + BlocksoftCryptoLog.log( + 'SolStakeUtils getAccountStaked request ' + + apiPath + + ' error ' + + e.message + ); + + const apiPath2 = + 'https://prod-api.solana.surf/v1/account/' + + address + + '/stakes?limit=10&offset=0'; + const res2 = await BlocksoftAxios.get(apiPath2); + + for (const tmp of res2.data.data) { + const item = { + amount: tmp.lamports, + stakeAddress: tmp.pubkey.address, + reserved: 0, + active: true, + status: '' + }; + + if (typeof tmp.data !== 'undefined') { + if (typeof tmp.data.meta !== 'undefined') { + if (typeof tmp.data.meta.rent_exempt_reserve !== 'undefined') { + item.reserved = tmp.data.meta.rent_exempt_reserve; + } + } + const deactivationEpoch = + tmp.data.stake.delegation.deactivation_epoch || 0; + const activationEpoch = + tmp.data.stake.delegation.activation_epoch || 0; + if (currentEpoch && currentEpoch * 1 >= deactivationEpoch * 1) { + item.order = 1; + item.active = false; + item.status = 'inactive'; + } else if (currentEpoch && currentEpoch === activationEpoch) { + item.order = 3; + item.status = 'activating'; + } else { + item.order = 2; + item.status = 'staked'; + } + } + item.diff = BlocksoftUtils.diff( + item.amount, + item.reserved + ).toString(); + accountInfo.push(item); + CACHE_STAKED[address].all[item.stakeAddress] = true; + } + } + + accountInfo.sort((a, b) => { + if (b.order === a.order) { + return BlocksoftUtils.diff(b.diff, a.diff) * 1; + } else { + return b.order - a.order; + } + }); + CACHE_STAKED[address].active = accountInfo; + } catch (e) { + if (config.debug.cryptoErrors) { + console.log( + 'SolStakeUtils.getAccountStaked ' + address + ' error ' + e.message + ); + } + BlocksoftCryptoLog.log( + 'SolStakeUtils.getAccountStaked ' + address + ' error ' + e.message + ); + return CACHE_STAKED[address].active; } -} + return accountInfo; + } +}; diff --git a/crypto/blockchains/sol/ext/SolUtils.js b/crypto/blockchains/sol/ext/SolUtils.js index 6ac3fa6d9..39cfb40e6 100644 --- a/crypto/blockchains/sol/ext/SolUtils.js +++ b/crypto/blockchains/sol/ext/SolUtils.js @@ -1,228 +1,239 @@ /** * @version 0.52 */ -import BlocksoftAxios from '@crypto/common/BlocksoftAxios' -import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings' +import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; +import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings'; -import { PublicKey } from '@solana/web3.js/src/index' -import { Account } from '@solana/web3.js/src/account' +import { PublicKey } from '@solana/web3.js/src/index'; +import { Account } from '@solana/web3.js/src/account'; -import config from '@app/config/config' -import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog' -import settingsActions from '@app/appstores/Stores/Settings/SettingsActions' +import config from '@app/config/config'; +import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; +import settingsActions from '@app/appstores/Stores/Settings/SettingsActions'; -const TOKEN_PROGRAM_ID = 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA' -const ASSOCIATED_TOKEN_PROGRAM_ID = 'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL' -const OWNER_VALIDATION_PROGRAM_ID = '4MNPdKu9wFMvEeZBMt3Eipfs5ovVWTJb31pEXDJAAxX5' +const TOKEN_PROGRAM_ID = 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'; +const ASSOCIATED_TOKEN_PROGRAM_ID = + 'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL'; +const OWNER_VALIDATION_PROGRAM_ID = + '4MNPdKu9wFMvEeZBMt3Eipfs5ovVWTJb31pEXDJAAxX5'; -const CACHE_VALID_TIME = 12000000 // 200 minute +const CACHE_VALID_TIME = 12000000; // 200 minute const CACHE_EPOCH = { - ts: 0, - value: 240 -} - + ts: 0, + value: 240 +}; export default { - - getTokenProgramID() { - return TOKEN_PROGRAM_ID - }, - - getOwnerValidationProgramId() { - return OWNER_VALIDATION_PROGRAM_ID - }, - - getAssociatedTokenProgramId() { - return ASSOCIATED_TOKEN_PROGRAM_ID - }, - - async findAssociatedTokenAddress(walletAddress, tokenMintAddress) { - try { - const seeds = [ - new PublicKey(walletAddress).toBuffer(), - new PublicKey(TOKEN_PROGRAM_ID).toBuffer(), - new PublicKey(tokenMintAddress).toBuffer() - ] - const res = - await PublicKey.findProgramAddress( - seeds, - new PublicKey( - ASSOCIATED_TOKEN_PROGRAM_ID - ) - ) - return res[0].toBase58() - } catch (e) { - if (config.debug.cryptoErrors) { - console.log('SolUtils.findAssociatedTokenAddress ' + e.message) - } - throw new Error('SYSTEM_ERROR') - } - }, - - async getAccountInfo(address) { - let accountInfo = false - try { - - const apiPath = BlocksoftExternalSettings.getStatic('SOL_SERVER') - const checkData = { - 'jsonrpc': '2.0', - 'id': 1, - 'method': 'getAccountInfo', - 'params': [ - address, - { - 'encoding': 'jsonParsed' - } - ] - } - const res = await BlocksoftAxios._request(apiPath, 'POST', checkData) - accountInfo = res.data.result.value - } catch (e) { - if (config.debug.cryptoErrors) { - console.log('SolUtils.getAccountInfo ' + address + ' error ' + e.message) - } - BlocksoftCryptoLog.log('SolUtils.getAccountInfo ' + address + ' error ' + e.message) - } - return accountInfo - }, - - isAddressValid(value) { - new PublicKey(value) - return true - }, - - /** - * https://docs.solana.com/developing/clients/jsonrpc-api#sendtransaction - * @param raw - * @returns {Promise} - */ - async sendTransaction(raw) { - const sendData = { - 'jsonrpc': '2.0', - 'id': 1, - 'method': 'sendTransaction', - 'params': [ - raw, - { - encoding: 'base64' - } - ] - } - const apiPath = BlocksoftExternalSettings.getStatic('SOL_SERVER') - const apiPath_2 = BlocksoftExternalSettings.getStatic('SOL_SERVER_2') - let try_2 = false - let sendRes - try { - sendRes = await BlocksoftAxios._request(apiPath, 'POST', sendData) - if (!sendRes || typeof sendRes.data === 'undefined') { - if (apiPath_2) { - try_2 = true - } else { - throw new Error('SERVER_RESPONSE_BAD_INTERNET') - } - } - if (typeof sendRes.data.error !== 'undefined' && typeof sendRes.data.error.message !== 'undefined') { - if (sendRes.data.error.message === 'Node is unhealthy') { - try_2 = true - } else { - throw new Error(sendRes.data.error.message) - } - } - } catch (e) { - try_2 = true - } - - if (try_2 && apiPath_2 && apiPath_2 !== apiPath) { - const sendRes_2 = await BlocksoftAxios._request(apiPath_2, 'POST', sendData) - if (!sendRes_2 || typeof sendRes_2.data === 'undefined') { - throw new Error('SERVER_RESPONSE_BAD_INTERNET') - } - if (typeof sendRes_2.data.error !== 'undefined' && typeof sendRes_2.data.error.message !== 'undefined') { - throw new Error(sendRes_2.data.error.message) - } - return sendRes_2.data.result - } - - return sendRes.data.result - }, - - /** - * @returns {Promise<{blockhash: string, feeCalculator: {lamportsPerSignature: number}}>} - */ - async getBlockData() { - const getRecentBlockhashData = { - 'jsonrpc': '2.0', - 'id': 1, - 'method': 'getRecentBlockhash' + getTokenProgramID() { + return TOKEN_PROGRAM_ID; + }, + + getOwnerValidationProgramId() { + return OWNER_VALIDATION_PROGRAM_ID; + }, + + getAssociatedTokenProgramId() { + return ASSOCIATED_TOKEN_PROGRAM_ID; + }, + + async findAssociatedTokenAddress(walletAddress, tokenMintAddress) { + try { + const seeds = [ + new PublicKey(walletAddress).toBuffer(), + new PublicKey(TOKEN_PROGRAM_ID).toBuffer(), + new PublicKey(tokenMintAddress).toBuffer() + ]; + const res = await PublicKey.findProgramAddress( + seeds, + new PublicKey(ASSOCIATED_TOKEN_PROGRAM_ID) + ); + return res[0].toBase58(); + } catch (e) { + if (config.debug.cryptoErrors) { + console.log('SolUtils.findAssociatedTokenAddress ' + e.message); + } + throw new Error('SYSTEM_ERROR'); + } + }, + + async getAccountInfo(address) { + let accountInfo = false; + try { + const apiPath = BlocksoftExternalSettings.getStatic('SOL_SERVER'); + const checkData = { + jsonrpc: '2.0', + id: 1, + method: 'getAccountInfo', + params: [ + address, + { + encoding: 'jsonParsed' + } + ] + }; + const res = await BlocksoftAxios._request(apiPath, 'POST', checkData); + accountInfo = res.data.result.value; + } catch (e) { + if (config.debug.cryptoErrors) { + console.log( + 'SolUtils.getAccountInfo ' + address + ' error ' + e.message + ); + } + BlocksoftCryptoLog.log( + 'SolUtils.getAccountInfo ' + address + ' error ' + e.message + ); + } + return accountInfo; + }, + + isAddressValid(value) { + new PublicKey(value); + return true; + }, + + /** + * https://docs.solana.com/developing/clients/jsonrpc-api#sendtransaction + * @param raw + * @returns {Promise} + */ + async sendTransaction(raw) { + const sendData = { + jsonrpc: '2.0', + id: 1, + method: 'sendTransaction', + params: [ + raw, + { + encoding: 'base64' } - const apiPath = BlocksoftExternalSettings.getStatic('SOL_SERVER') - const getRecentBlockhashRes = await BlocksoftAxios._request(apiPath, 'POST', getRecentBlockhashData) - return getRecentBlockhashRes.data.result.value - }, - - async getEpoch() { - const now = new Date().getTime() - if (CACHE_EPOCH.ts > 0) { - if (now - CACHE_EPOCH.ts < CACHE_VALID_TIME) { - return CACHE_EPOCH.value - } + ] + }; + const apiPath = BlocksoftExternalSettings.getStatic('SOL_SERVER'); + const apiPath_2 = BlocksoftExternalSettings.getStatic('SOL_SERVER_2'); + let try_2 = false; + let sendRes; + try { + sendRes = await BlocksoftAxios._request(apiPath, 'POST', sendData); + if (!sendRes || typeof sendRes.data === 'undefined') { + if (apiPath_2) { + try_2 = true; } else { - const tmp = settingsActions.getSettings('SOL_epoch') - if (tmp * 1 > CACHE_EPOCH.value) { - CACHE_EPOCH.value = tmp * 1 - } + throw new Error('SERVER_RESPONSE_BAD_INTERNET'); } - const apiPath = BlocksoftExternalSettings.getStatic('SOL_SERVER') - const getEpoch = { 'jsonrpc': '2.0', 'id': 1, 'method': 'getEpochInfo' } - try { - const resEpoch = await BlocksoftAxios._request(apiPath, 'POST', getEpoch) - const tmp = resEpoch.data.result.epoch * 1 - if (tmp > 0 && tmp !== CACHE_EPOCH.value) { - CACHE_EPOCH.value = tmp - settingsActions.setSettings('SOL_epoch', tmp) - } - CACHE_EPOCH.ts = now - } catch (e) { - BlocksoftCryptoLog.log('SolUtils.getEpoch error ' + e.message) - // nothing + } + if ( + typeof sendRes.data.error !== 'undefined' && + typeof sendRes.data.error.message !== 'undefined' + ) { + if (sendRes.data.error.message === 'Node is unhealthy') { + try_2 = true; + } else { + throw new Error(sendRes.data.error.message); } - return CACHE_EPOCH.value - }, - - async signTransaction(transaction, walletPrivKey, walletPubKey) { + } + } catch (e) { + try_2 = true; + } - let account = false - try { - account = new Account(Buffer.from(walletPrivKey, 'hex')) - } catch (e) { - e.message += ' while create account' - throw e - } + if (try_2 && apiPath_2 && apiPath_2 !== apiPath) { + const sendRes_2 = await BlocksoftAxios._request( + apiPath_2, + 'POST', + sendData + ); + if (!sendRes_2 || typeof sendRes_2.data === 'undefined') { + throw new Error('SERVER_RESPONSE_BAD_INTERNET'); + } + if ( + typeof sendRes_2.data.error !== 'undefined' && + typeof sendRes_2.data.error.message !== 'undefined' + ) { + throw new Error(sendRes_2.data.error.message); + } + return sendRes_2.data.result; + } - try { - const data = await this.getBlockData() - transaction.recentBlockhash = data.blockhash - } catch (e) { - e.message += ' while getBlockData' - throw e - } - try { - transaction.setSigners( - new PublicKey(walletPubKey) - ) - } catch (e) { - e.message += ' while setSigners' - throw e - } + return sendRes.data.result; + }, + + /** + * @returns {Promise<{blockhash: string, feeCalculator: {lamportsPerSignature: number}}>} + */ + async getBlockData() { + const getRecentBlockhashData = { + jsonrpc: '2.0', + id: 1, + method: 'getRecentBlockhash' + }; + const apiPath = BlocksoftExternalSettings.getStatic('SOL_SERVER'); + const getRecentBlockhashRes = await BlocksoftAxios._request( + apiPath, + 'POST', + getRecentBlockhashData + ); + return getRecentBlockhashRes.data.result.value; + }, + + async getEpoch() { + const now = new Date().getTime(); + if (CACHE_EPOCH.ts > 0) { + if (now - CACHE_EPOCH.ts < CACHE_VALID_TIME) { + return CACHE_EPOCH.value; + } + } else { + const tmp = settingsActions.getSettings('SOL_epoch'); + if (tmp * 1 > CACHE_EPOCH.value) { + CACHE_EPOCH.value = tmp * 1; + } + } + const apiPath = BlocksoftExternalSettings.getStatic('SOL_SERVER'); + const getEpoch = { jsonrpc: '2.0', id: 1, method: 'getEpochInfo' }; + try { + const resEpoch = await BlocksoftAxios._request(apiPath, 'POST', getEpoch); + const tmp = resEpoch.data.result.epoch * 1; + if (tmp > 0 && tmp !== CACHE_EPOCH.value) { + CACHE_EPOCH.value = tmp; + settingsActions.setSettings('SOL_epoch', tmp); + } + CACHE_EPOCH.ts = now; + } catch (e) { + BlocksoftCryptoLog.log('SolUtils.getEpoch error ' + e.message); + // nothing + } + return CACHE_EPOCH.value; + }, + + async signTransaction(transaction, walletPrivKey, walletPubKey) { + let account = false; + try { + account = new Account(Buffer.from(walletPrivKey, 'hex')); + } catch (e) { + e.message += ' while create account'; + throw e; + } - try { - transaction.partialSign(account) - } catch (e) { - e.message += ' while transaction.partialSign with account' - throw e - } + try { + const data = await this.getBlockData(); + transaction.recentBlockhash = data.blockhash; + } catch (e) { + e.message += ' while getBlockData'; + throw e; + } + try { + transaction.setSigners(new PublicKey(walletPubKey)); + } catch (e) { + e.message += ' while setSigners'; + throw e; + } - return transaction + try { + transaction.partialSign(account); + } catch (e) { + e.message += ' while transaction.partialSign with account'; + throw e; } -} + + return transaction; + } +}; diff --git a/crypto/blockchains/trx/TrxScannerProcessor.js b/crypto/blockchains/trx/TrxScannerProcessor.js index dada23ab6..c933bead2 100644 --- a/crypto/blockchains/trx/TrxScannerProcessor.js +++ b/crypto/blockchains/trx/TrxScannerProcessor.js @@ -2,166 +2,269 @@ * @version 0.5 * https://github.com/tronscan/tronscan-frontend/wiki/TRONSCAN-API */ -import TronUtils from './ext/TronUtils' -import TrxTronscanProvider from './basic/TrxTronscanProvider' -import TrxTrongridProvider from './basic/TrxTrongridProvider' -import TrxTransactionsProvider from './basic/TrxTransactionsProvider' -import TrxTransactionsTrc20Provider from './basic/TrxTransactionsTrc20Provider' -import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog' -import Database from '@app/appstores/DataSource/Database/main' -import BlocksoftAxios from '@crypto/common/BlocksoftAxios' -import config from '@app/config/config' -import BlocksoftUtils from '@crypto/common/BlocksoftUtils' -import transactionDS from '@app/appstores/DataSource/Transaction/Transaction' -import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings' -import TronStakeUtils from '@crypto/blockchains/trx/ext/TronStakeUtils' +import TronUtils from './ext/TronUtils'; +import TrxTronscanProvider from './basic/TrxTronscanProvider'; +import TrxTrongridProvider from './basic/TrxTrongridProvider'; +import TrxTransactionsProvider from './basic/TrxTransactionsProvider'; +import TrxTransactionsTrc20Provider from './basic/TrxTransactionsTrc20Provider'; +import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog'; +import Database from '@app/appstores/DataSource/Database/main'; +import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; +import config from '@app/config/config'; +import BlocksoftUtils from '@crypto/common/BlocksoftUtils'; +import transactionDS from '@app/appstores/DataSource/Transaction/Transaction'; +import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings'; +import TronStakeUtils from '@crypto/blockchains/trx/ext/TronStakeUtils'; - -let CACHE_PENDING_TXS = false +let CACHE_PENDING_TXS = false; export default class TrxScannerProcessor { - - constructor(settings) { - this._settings = settings - this._tokenName = '_' - if (typeof settings.tokenName !== 'undefined') { - this._tokenName = settings.tokenName - } - this._tronscanProvider = new TrxTronscanProvider() - this._trongridProvider = new TrxTrongridProvider() - this._transactionsProvider = new TrxTransactionsProvider() - this._transactionsTrc20Provider = new TrxTransactionsTrc20Provider() + constructor(settings) { + this._settings = settings; + this._tokenName = '_'; + if (typeof settings.tokenName !== 'undefined') { + this._tokenName = settings.tokenName; } + this._tronscanProvider = new TrxTronscanProvider(); + this._trongridProvider = new TrxTrongridProvider(); + this._transactionsProvider = new TrxTransactionsProvider(); + this._transactionsTrc20Provider = new TrxTransactionsTrc20Provider(); + } - async isMultisigBlockchain(address) { - address = address.trim() - let addressHex = address - if (address.substr(0, 1) === 'T') { - addressHex = await TronUtils.addressToHex(address) - } - return this._trongridProvider.isMultisigTrongrid(addressHex) + async isMultisigBlockchain(address) { + address = address.trim(); + let addressHex = address; + if (address.substr(0, 1) === 'T') { + addressHex = await TronUtils.addressToHex(address); } + return this._trongridProvider.isMultisigTrongrid(addressHex); + } - /** - * https://developers.tron.network/reference#addresses-accounts - * @param {string} address - * @return {Promise<{balance, frozen, frozenEnergy, balanceStaked, unconfirmed, provider}>} - */ - async getBalanceBlockchain(address, jsonData, walletHash, source) { - address = address.trim() - BlocksoftCryptoLog.log(this._tokenName + ' TrxScannerProcessor getBalanceBlockchain address ' + address + ' from ' + source) - let addressHex = address - if (address.substr(0, 1) === 'T') { - addressHex = await TronUtils.addressToHex(address) - } else { - address = await TronUtils.addressHexToStr(addressHex) - } - const useTronscan = BlocksoftExternalSettings.getStatic('TRX_USE_TRONSCAN') * 1 > 0 - let result = false - let subresult = false - if (useTronscan) { - result = await this._tronscanProvider.get(address, this._tokenName, source === 'AccountScreen') - BlocksoftCryptoLog.log(this._tokenName + ' TrxScannerProcessor getBalanceBlockchain address ' + address + ' result tronScan ' + JSON.stringify(result) + ' from ' + source) - } + /** + * https://developers.tron.network/reference#addresses-accounts + * @param {string} address + * @return {Promise<{balance, frozen, frozenEnergy, balanceStaked, unconfirmed, provider}>} + */ + async getBalanceBlockchain(address, jsonData, walletHash, source) { + address = address.trim(); + BlocksoftCryptoLog.log( + this._tokenName + + ' TrxScannerProcessor getBalanceBlockchain address ' + + address + + ' from ' + + source + ); + let addressHex = address; + if (address.substr(0, 1) === 'T') { + addressHex = await TronUtils.addressToHex(address); + } else { + address = await TronUtils.addressHexToStr(addressHex); + } + const useTronscan = + BlocksoftExternalSettings.getStatic('TRX_USE_TRONSCAN') * 1 > 0; + let result = false; + let subresult = false; + if (useTronscan) { + result = await this._tronscanProvider.get( + address, + this._tokenName, + source === 'AccountScreen' + ); + BlocksoftCryptoLog.log( + this._tokenName + + ' TrxScannerProcessor getBalanceBlockchain address ' + + address + + ' result tronScan ' + + JSON.stringify(result) + + ' from ' + + source + ); + } - if (result === false || result === 0) { - if (this._tokenName !== '_' && this._tokenName.substr(0, 1) === 'T') { - // https://developers.tron.network/docs/trc20-contract-interaction#balanceof - try { - const sendLink = BlocksoftExternalSettings.getStatic('TRX_SEND_LINK') - const params = { - "contract_address": await TronUtils.addressToHex(this._tokenName), - "function_selector": "balanceOf(address)", - "parameter": "0000000000000000000000" + addressHex, - "owner_address": addressHex - } - const tmp = await BlocksoftAxios.post(sendLink + '/wallet/triggerconstantcontract', params) - if (typeof tmp.data !== 'undefined' && typeof tmp.data.constant_result !== 'undefined') { - BlocksoftCryptoLog.log(this._tokenName + ' TrxScannerProcessor getBalanceBlockchain address ' + address + ' result tronwallet ' + JSON.stringify(tmp.data) + ' from ' + source) - return { balance: BlocksoftUtils.hexToDecimal('0x' + tmp.data.constant_result), unconfirmed: 0, provider: 'tronwallet-raw-call' } - } - } catch (e) { - BlocksoftCryptoLog.log(this._tokenName + ' TrxScannerProcessor getBalanceBlockchain address ' + address + ' error tronwallet ' + e.message) - } - result = await this._tronscanProvider.get(address, this._tokenName) - } else { - result = await this._trongridProvider.get(addressHex, this._tokenName, source === 'AccountScreen') - } - BlocksoftCryptoLog.log(this._tokenName + ' TrxScannerProcessor getBalanceBlockchain address ' + address + ' result tronGrid ' + JSON.stringify(result) + ' from ' + source) + if (result === false || result === 0) { + if (this._tokenName !== '_' && this._tokenName.substr(0, 1) === 'T') { + // https://developers.tron.network/docs/trc20-contract-interaction#balanceof + try { + const sendLink = BlocksoftExternalSettings.getStatic('TRX_SEND_LINK'); + const params = { + contract_address: await TronUtils.addressToHex(this._tokenName), + function_selector: 'balanceOf(address)', + parameter: '0000000000000000000000' + addressHex, + owner_address: addressHex + }; + const tmp = await BlocksoftAxios.post( + sendLink + '/wallet/triggerconstantcontract', + params + ); + if ( + typeof tmp.data !== 'undefined' && + typeof tmp.data.constant_result !== 'undefined' + ) { + BlocksoftCryptoLog.log( + this._tokenName + + ' TrxScannerProcessor getBalanceBlockchain address ' + + address + + ' result tronwallet ' + + JSON.stringify(tmp.data) + + ' from ' + + source + ); + return { + balance: BlocksoftUtils.hexToDecimal( + '0x' + tmp.data.constant_result + ), + unconfirmed: 0, + provider: 'tronwallet-raw-call' + }; + } + } catch (e) { + BlocksoftCryptoLog.log( + this._tokenName + + ' TrxScannerProcessor getBalanceBlockchain address ' + + address + + ' error tronwallet ' + + e.message + ); } + result = await this._tronscanProvider.get(address, this._tokenName); + } else { + result = await this._trongridProvider.get( + addressHex, + this._tokenName, + source === 'AccountScreen' + ); + } + BlocksoftCryptoLog.log( + this._tokenName + + ' TrxScannerProcessor getBalanceBlockchain address ' + + address + + ' result tronGrid ' + + JSON.stringify(result) + + ' from ' + + source + ); + } - if (result === false && this._tokenName !== '_') { - - subresult = await this._tronscanProvider.get(address, '_', source === 'AccountScreen') - BlocksoftCryptoLog.log(this._tokenName + ' TrxScannerProcessor getBalanceBlockchain address ' + address + ' result tronScan2 ' + JSON.stringify(result) + ' from ' + source) + if (result === false && this._tokenName !== '_') { + subresult = await this._tronscanProvider.get( + address, + '_', + source === 'AccountScreen' + ); + BlocksoftCryptoLog.log( + this._tokenName + + ' TrxScannerProcessor getBalanceBlockchain address ' + + address + + ' result tronScan2 ' + + JSON.stringify(result) + + ' from ' + + source + ); - if (subresult !== false) { - BlocksoftCryptoLog.log(this._tokenName + ' TrxScannerProcessor getBalanceBlockchain address ' + address + ' subresult tronScan ' + JSON.stringify(subresult) + ' from ' + source) - return { balance: 0, unconfirmed: 0, balanceStaked : 0, balanceAvailable : 0, provider: 'tronscan-ok-but-no-token' } - } - } - result.balanceStaked = typeof result.frozen !== 'undefined' ? (result.frozen * 1 + result.frozenEnergy * 1) : 0 - result.balanceAvailable = result.balance - if (result.balanceStaked * 1 > 0) { - result.balance = result.balance * 1 + result.balanceStaked * 1 - } - return result + if (subresult !== false) { + BlocksoftCryptoLog.log( + this._tokenName + + ' TrxScannerProcessor getBalanceBlockchain address ' + + address + + ' subresult tronScan ' + + JSON.stringify(subresult) + + ' from ' + + source + ); + return { + balance: 0, + unconfirmed: 0, + balanceStaked: 0, + balanceAvailable: 0, + provider: 'tronscan-ok-but-no-token' + }; + } } + result.balanceStaked = + typeof result.frozen !== 'undefined' + ? result.frozen * 1 + result.frozenEnergy * 1 + : 0; + result.balanceAvailable = result.balance; + if (result.balanceStaked * 1 > 0) { + result.balance = result.balance * 1 + result.balanceStaked * 1; + } + return result; + } - - /** - * @param {string} address - * @return {Promise<*>} - */ - async getResourcesBlockchain(address) { - address = address.trim() - BlocksoftCryptoLog.log(this._tokenName + ' TrxScannerProcessor getResourcesBlockchain address ' + address) - let addressHex = address - if (address.substr(0, 1) === 'T') { - addressHex = await TronUtils.addressToHex(address) - } - const result = await this._trongridProvider.getResources(addressHex, this._tokenName) - return result + /** + * @param {string} address + * @return {Promise<*>} + */ + async getResourcesBlockchain(address) { + address = address.trim(); + BlocksoftCryptoLog.log( + this._tokenName + + ' TrxScannerProcessor getResourcesBlockchain address ' + + address + ); + let addressHex = address; + if (address.substr(0, 1) === 'T') { + addressHex = await TronUtils.addressToHex(address); } + const result = await this._trongridProvider.getResources( + addressHex, + this._tokenName + ); + return result; + } - /** - * https://github.com/jakeonchain/tron-wallet-chrome/blob/fecea42771cc5cbda3fada4a1c8cfe8de251c008/src/App.js - * @param {string} scanData.account.address - * @return {Promise<[UnifiedTransaction]>} - */ - async getTransactionsBlockchain(scanData, source) { - let result - let lastBlock = false - if (this._tokenName[0] === 'T') { - this._transactionsTrc20Provider.setLink(this._tokenName) - result = await this._transactionsTrc20Provider.get(scanData, this._tokenName) - lastBlock = this._transactionsTrc20Provider._lastBlock - } else { - result = await this._transactionsProvider.get(scanData, this._tokenName) - lastBlock = this._transactionsProvider._lastBlock - } - await this.getTransactionsPendingBlockchain(scanData, source, lastBlock) - return result + /** + * https://github.com/jakeonchain/tron-wallet-chrome/blob/fecea42771cc5cbda3fada4a1c8cfe8de251c008/src/App.js + * @param {string} scanData.account.address + * @return {Promise<[UnifiedTransaction]>} + */ + async getTransactionsBlockchain(scanData, source) { + let result; + let lastBlock = false; + if (this._tokenName[0] === 'T') { + this._transactionsTrc20Provider.setLink(this._tokenName); + result = await this._transactionsTrc20Provider.get( + scanData, + this._tokenName + ); + lastBlock = this._transactionsTrc20Provider._lastBlock; + } else { + result = await this._transactionsProvider.get(scanData, this._tokenName); + lastBlock = this._transactionsProvider._lastBlock; } + await this.getTransactionsPendingBlockchain(scanData, source, lastBlock); + return result; + } - async resetTransactionsPendingBlockchain(scanData, source, lastBlock = false) { - CACHE_PENDING_TXS = scanData.resetTime || 0 - if (typeof scanData.specialActionNeeded !== 'undefined' && scanData.specialActionNeeded && typeof scanData.account.address !== 'undefined') { - await Database.query(` + async resetTransactionsPendingBlockchain( + scanData, + source, + lastBlock = false + ) { + CACHE_PENDING_TXS = scanData.resetTime || 0; + if ( + typeof scanData.specialActionNeeded !== 'undefined' && + scanData.specialActionNeeded && + typeof scanData.account.address !== 'undefined' + ) { + await Database.query(` UPDATE transactions SET special_action_needed='' WHERE special_action_needed='${scanData.specialActionNeeded}' AND address_from_basic='${scanData.account.address}' - `) - } - return false + `); } + return false; + } - async getTransactionsPendingBlockchain(scanData, source, lastBlock = false) { - if (CACHE_PENDING_TXS > 0 && CACHE_PENDING_TXS - new Date().getTime() < 60000) { - return false - } - // id, transaction_hash, block_number, block_confirmations, transaction_status, - const sql = `SELECT t.id, + async getTransactionsPendingBlockchain(scanData, source, lastBlock = false) { + if ( + CACHE_PENDING_TXS > 0 && + CACHE_PENDING_TXS - new Date().getTime() < 60000 + ) { + return false; + } + // id, transaction_hash, block_number, block_confirmations, transaction_status, + const sql = `SELECT t.id, t.wallet_hash AS walletHash, t.transaction_hash AS transactionHash, t.transactions_scan_log AS transactionsScanLog, @@ -177,125 +280,211 @@ export default class TrxScannerProcessor { ORDER BY created_at DESC LIMIT 10 - ` - const res = await Database.query(sql) - if (!res || typeof res.array === 'undefined' || !res.array || res.array.length === 0) { - CACHE_PENDING_TXS = new Date().getTime() - return false - } + `; + const res = await Database.query(sql); + if ( + !res || + typeof res.array === 'undefined' || + !res.array || + res.array.length === 0 + ) { + CACHE_PENDING_TXS = new Date().getTime(); + return false; + } - const sendLink = BlocksoftExternalSettings.getStatic('TRX_SEND_LINK') - let needUpdateBalance = -1 - if (lastBlock === false) { - needUpdateBalance = 0 - try { - const link2 = sendLink + '/wallet/getnowblock' - const block = await BlocksoftAxios.get(link2) - if (typeof block !== 'undefined' && block && typeof block.data !== 'undefined') { - lastBlock = block.data.block_header.raw_data.number - } - } catch (e1) { - if (config.debug.cryptoErrors) { - console.log(this._settings.currencyCode + ' TrxScannerProcessor.getTransactionsPendingBlockchain lastBlock', e1) - } - } + const sendLink = BlocksoftExternalSettings.getStatic('TRX_SEND_LINK'); + let needUpdateBalance = -1; + if (lastBlock === false) { + needUpdateBalance = 0; + try { + const link2 = sendLink + '/wallet/getnowblock'; + const block = await BlocksoftAxios.get(link2); + if ( + typeof block !== 'undefined' && + block && + typeof block.data !== 'undefined' + ) { + lastBlock = block.data.block_header.raw_data.number; } + } catch (e1) { + if (config.debug.cryptoErrors) { + console.log( + this._settings.currencyCode + + ' TrxScannerProcessor.getTransactionsPendingBlockchain lastBlock', + e1 + ); + } + } + } - const unique = {} - for (const row of res.array) { - const linkRecheck = sendLink + '/wallet/gettransactioninfobyid' - try { - const recheck = await BlocksoftAxios.post(linkRecheck, { - value: row.transactionHash - }) - if (typeof recheck.data !== 'undefined') { - const isSuccess = await this._unifyFromReceipt(recheck.data, row, lastBlock) - if (isSuccess && needUpdateBalance === 0) { - needUpdateBalance = 1 - } - if (isSuccess && row.specialActionNeeded && row.addressFromBasic) { - row.confirmations = lastBlock - recheck.data.blockNumber - if (typeof unique[row.addressFromBasic] === 'undefined') { - unique[row.addressFromBasic] = row - } else { - if (unique[row.addressFromBasic].confirmations > row.confirmations) { - unique[row.addressFromBasic].confirmations = row.confirmations - } - if (unique[row.addressFromBasic].specialActionNeeded === 'vote_after_unfreeze') { - unique[row.addressFromBasic].specialActionNeeded = row.specialActionNeeded - } - } - - } - } - } catch (e1) { - if (config.debug.cryptoErrors) { - console.log(this._settings.currencyCode + ' TrxScannerProcessor.getTransactionsPendingBlockchain recheck', e1) - } + const unique = {}; + for (const row of res.array) { + const linkRecheck = sendLink + '/wallet/gettransactioninfobyid'; + try { + const recheck = await BlocksoftAxios.post(linkRecheck, { + value: row.transactionHash + }); + if (typeof recheck.data !== 'undefined') { + const isSuccess = await this._unifyFromReceipt( + recheck.data, + row, + lastBlock + ); + if (isSuccess && needUpdateBalance === 0) { + needUpdateBalance = 1; + } + if (isSuccess && row.specialActionNeeded && row.addressFromBasic) { + row.confirmations = lastBlock - recheck.data.blockNumber; + if (typeof unique[row.addressFromBasic] === 'undefined') { + unique[row.addressFromBasic] = row; + } else { + if ( + unique[row.addressFromBasic].confirmations > row.confirmations + ) { + unique[row.addressFromBasic].confirmations = row.confirmations; + } + if ( + unique[row.addressFromBasic].specialActionNeeded === + 'vote_after_unfreeze' + ) { + unique[row.addressFromBasic].specialActionNeeded = + row.specialActionNeeded; + } } + } + } + } catch (e1) { + if (config.debug.cryptoErrors) { + console.log( + this._settings.currencyCode + + ' TrxScannerProcessor.getTransactionsPendingBlockchain recheck', + e1 + ); } + } + } - if (unique) { - for (const address in unique) { - const {walletHash, derivationPath, confirmations, specialActionNeeded } = unique[address] - if (confirmations < 20) { - BlocksoftCryptoLog.log(this._settings.currencyCode + ' TrxScannerProcessor.getTransactionsPendingBlockchain vote all skipped by ' + confirmations + ' for ' + address) - continue - } + if (unique) { + for (const address in unique) { + const { + walletHash, + derivationPath, + confirmations, + specialActionNeeded + } = unique[address]; + if (confirmations < 20) { + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' TrxScannerProcessor.getTransactionsPendingBlockchain vote all skipped by ' + + confirmations + + ' for ' + + address + ); + continue; + } - BlocksoftCryptoLog.log(this._settings.currencyCode + ' TrxScannerProcessor.getTransactionsPendingBlockchain vote all inited for ' + address + ' action ' + specialActionNeeded) - try { - if (await TronStakeUtils.sendVoteAll(address, derivationPath, walletHash, specialActionNeeded)) { - await Database.query(` + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' TrxScannerProcessor.getTransactionsPendingBlockchain vote all inited for ' + + address + + ' action ' + + specialActionNeeded + ); + try { + if ( + await TronStakeUtils.sendVoteAll( + address, + derivationPath, + walletHash, + specialActionNeeded + ) + ) { + await Database.query(` UPDATE transactions SET special_action_needed='' WHERE special_action_needed='vote' OR special_action_needed='vote_after_unfreeze' AND address_from_basic='${address}' - `) - BlocksoftCryptoLog.log(this._settings.currencyCode + ' TrxScannerProcessor.getTransactionsPendingBlockchain vote all finished for ' + address) - } - } catch (e) { - if (config.debug.cryptoErrors) { - console.log(this._settings.currencyCode + ' TrxScannerProcessor.getTransactionsPendingBlockchain vote all error for ' + address + ' ' + e.message) - } - BlocksoftCryptoLog.log(this._settings.currencyCode + ' TrxScannerProcessor.getTransactionsPendingBlockchain vote all error for ' + address + ' ' + e.message) - } - } + `); + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' TrxScannerProcessor.getTransactionsPendingBlockchain vote all finished for ' + + address + ); + } + } catch (e) { + if (config.debug.cryptoErrors) { + console.log( + this._settings.currencyCode + + ' TrxScannerProcessor.getTransactionsPendingBlockchain vote all error for ' + + address + + ' ' + + e.message + ); + } + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' TrxScannerProcessor.getTransactionsPendingBlockchain vote all error for ' + + address + + ' ' + + e.message + ); } - - return needUpdateBalance > 0 + } } - async _unifyFromReceipt(transaction, row, lastBlock) { - /** - * {"id":"fb7580e4bb6161e0812beb05cf4a1b6463ba55e33def5dd7f3f5c1561c91a49e","blockNumber":29134019,"blockTimeStamp":1617823467000, - * "receipt":{'origin_energy_usage":4783,"energy_usage_total":4783,"net_usage":345,"result":"OUT_OF_ENERGY'}, - * "result":"FAILED" - */ - if (typeof transaction.blockNumber === 'undefined' || transaction.blockNumber * 1 <= 1) return false + return needUpdateBalance > 0; + } - let transactionStatus = 'success' - if (typeof transaction.result !== 'undefined' && transaction.result === 'FAILED') { - transactionStatus = 'fail' - if (typeof transaction.receipt !== 'undefined' && typeof transaction.receipt.result !== 'undefined') { - if (transaction.receipt.result === 'OUT_OF_ENERGY') { - transactionStatus = 'out_of_energy' - } - } - } - let formattedTime - try { - formattedTime = BlocksoftUtils.toDate(transaction.blockTimeStamp / 1000) - } catch (e) { - e.message += ' timestamp error transaction2 data ' + JSON.stringify(transaction) - throw e - } + async _unifyFromReceipt(transaction, row, lastBlock) { + /** + * {"id":"fb7580e4bb6161e0812beb05cf4a1b6463ba55e33def5dd7f3f5c1561c91a49e","blockNumber":29134019,"blockTimeStamp":1617823467000, + * "receipt":{'origin_energy_usage":4783,"energy_usage_total":4783,"net_usage":345,"result":"OUT_OF_ENERGY'}, + * "result":"FAILED" + */ + if ( + typeof transaction.blockNumber === 'undefined' || + transaction.blockNumber * 1 <= 1 + ) + return false; - await transactionDS.saveTransaction({ - blockNumber: transaction.blockNumber, - blockTime: formattedTime, - blockConfirmations: lastBlock - transaction.blockNumber, - transactionStatus, - transactionsScanLog: new Date().toISOString() + ' RECEIPT RECHECK ' + JSON.stringify(transaction) + ' ' + row.transactionsScanLog - }, row.id, 'receipt') - return transactionStatus === 'success' + let transactionStatus = 'success'; + if ( + typeof transaction.result !== 'undefined' && + transaction.result === 'FAILED' + ) { + transactionStatus = 'fail'; + if ( + typeof transaction.receipt !== 'undefined' && + typeof transaction.receipt.result !== 'undefined' + ) { + if (transaction.receipt.result === 'OUT_OF_ENERGY') { + transactionStatus = 'out_of_energy'; + } + } + } + let formattedTime; + try { + formattedTime = BlocksoftUtils.toDate(transaction.blockTimeStamp / 1000); + } catch (e) { + e.message += + ' timestamp error transaction2 data ' + JSON.stringify(transaction); + throw e; } + + await transactionDS.saveTransaction( + { + blockNumber: transaction.blockNumber, + blockTime: formattedTime, + blockConfirmations: lastBlock - transaction.blockNumber, + transactionStatus, + transactionsScanLog: + new Date().toISOString() + + ' RECEIPT RECHECK ' + + JSON.stringify(transaction) + + ' ' + + row.transactionsScanLog + }, + row.id, + 'receipt' + ); + return transactionStatus === 'success'; + } } diff --git a/crypto/blockchains/trx/TrxTransferProcessor.ts b/crypto/blockchains/trx/TrxTransferProcessor.ts index 6b7a4ddf3..a8aa37190 100644 --- a/crypto/blockchains/trx/TrxTransferProcessor.ts +++ b/crypto/blockchains/trx/TrxTransferProcessor.ts @@ -1,634 +1,1039 @@ /** * @version 0.20 */ -import BlocksoftAxios from '../../common/BlocksoftAxios' -import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog' -import BlocksoftUtils from '../../common/BlocksoftUtils' -import { BlocksoftBlockchainTypes } from '../BlocksoftBlockchainTypes' +import BlocksoftAxios from '../../common/BlocksoftAxios'; +import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog'; +import BlocksoftUtils from '../../common/BlocksoftUtils'; +import { BlocksoftBlockchainTypes } from '../BlocksoftBlockchainTypes'; -import TronUtils from './ext/TronUtils' -import TrxTronscanProvider from './basic/TrxTronscanProvider' -import TrxTrongridProvider from './basic/TrxTrongridProvider' -import TrxSendProvider from '@crypto/blockchains/trx/providers/TrxSendProvider' +import TronUtils from './ext/TronUtils'; +import TrxTronscanProvider from './basic/TrxTronscanProvider'; +import TrxTrongridProvider from './basic/TrxTrongridProvider'; +import TrxSendProvider from '@crypto/blockchains/trx/providers/TrxSendProvider'; +import BlocksoftDispatcher from '../BlocksoftDispatcher'; +import config from '@app/config/config'; +import { strings, sublocale } from '@app/services/i18n'; -import BlocksoftDispatcher from '../BlocksoftDispatcher' -import config from '@app/config/config' -import { strings, sublocale } from '@app/services/i18n' - -import settingsActions from '@app/appstores/Stores/Settings/SettingsActions' -import MarketingEvent from '@app/services/Marketing/MarketingEvent' -import BlocksoftTransactions from '@crypto/actions/BlocksoftTransactions/BlocksoftTransactions' -import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings' -import BlocksoftBalances from '@crypto/actions/BlocksoftBalances/BlocksoftBalances' +import settingsActions from '@app/appstores/Stores/Settings/SettingsActions'; +import MarketingEvent from '@app/services/Marketing/MarketingEvent'; +import BlocksoftTransactions from '@crypto/actions/BlocksoftTransactions/BlocksoftTransactions'; +import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings'; +import BlocksoftBalances from '@crypto/actions/BlocksoftBalances/BlocksoftBalances'; // https://developers.tron.network/docs/parameter-and-return-value-encoding-and-decoding -const ethers = require('ethers') -const ADDRESS_PREFIX_REGEX = /^(41)/ -const AbiCoder = ethers.utils.AbiCoder -const PROXY_FEE = 'https://proxy.trustee.deals/trx/countFee' - -export default class TrxTransferProcessor implements BlocksoftBlockchainTypes.TransferProcessor { - private _settings: any - private _tronscanProvider: TrxTronscanProvider - private _trongridProvider: TrxTrongridProvider - private _tokenName: string - private _isToken20: boolean - private sendProvider: TrxSendProvider - - constructor(settings: any) { - this._settings = settings - this._tronscanProvider = new TrxTronscanProvider() - this._trongridProvider = new TrxTrongridProvider() - this._tokenName = '_' - this._isToken20 = false - if (typeof settings.tokenName !== 'undefined') { - this._tokenName = settings.tokenName - if (this._tokenName[0] === 'T') { - this._isToken20 = true - } - } - this.sendProvider = new TrxSendProvider(this._settings, 'TRX') - } +const ethers = require('ethers'); +const ADDRESS_PREFIX_REGEX = /^(41)/; +const AbiCoder = ethers.utils.AbiCoder; +const PROXY_FEE = 'https://proxy.trustee.deals/trx/countFee'; - needPrivateForFee(): boolean { - return false - } +export default class TrxTransferProcessor + implements BlocksoftBlockchainTypes.TransferProcessor +{ + private _settings: any; + private _tronscanProvider: TrxTronscanProvider; + private _trongridProvider: TrxTrongridProvider; + private _tokenName: string; + private _isToken20: boolean; + private sendProvider: TrxSendProvider; - checkSendAllModal(data: { currencyCode: any }): boolean { - return false + constructor(settings: any) { + this._settings = settings; + this._tronscanProvider = new TrxTronscanProvider(); + this._trongridProvider = new TrxTrongridProvider(); + this._tokenName = '_'; + this._isToken20 = false; + if (typeof settings.tokenName !== 'undefined') { + this._tokenName = settings.tokenName; + if (this._tokenName[0] === 'T') { + this._isToken20 = true; + } } + this.sendProvider = new TrxSendProvider(this._settings, 'TRX'); + } - async checkTransferHasError(data: BlocksoftBlockchainTypes.CheckTransferHasErrorData): Promise { - // @ts-ignore - if (!this._isToken20 || data.amount && data.amount * 1 > 0) { - return { isOk: true } - } - /** - * @type {TrxScannerProcessor} - */ - const balanceProvider = BlocksoftDispatcher.getScannerProcessor(this._settings.currencyCode) - const balanceRaw = await balanceProvider.getBalanceBlockchain(data.addressTo) - if (balanceRaw && typeof balanceRaw.balance !== 'undefined' && balanceRaw.balance > 0) { - return { isOk: true } - } + needPrivateForFee(): boolean { + return false; + } - const balanceProviderBasic = BlocksoftDispatcher.getScannerProcessor('TRX') - const balanceRawBasic = await balanceProviderBasic.getBalanceBlockchain(data.addressTo) - if (balanceRawBasic && typeof balanceRawBasic.balance !== 'undefined' && balanceRawBasic.balance > 0) { - return { isOk: true } - } + checkSendAllModal(data: { currencyCode: any }): boolean { + return false; + } - const transactionsBasic = await balanceProviderBasic.getTransactionsBlockchain({ account: { address: data.addressTo } }) - if (transactionsBasic !== false) { - return { isOk: true } - } - return { isOk: false, code: 'TRX_20', address: data.addressTo } + async checkTransferHasError( + data: BlocksoftBlockchainTypes.CheckTransferHasErrorData + ): Promise { + // @ts-ignore + if (!this._isToken20 || (data.amount && data.amount * 1 > 0)) { + return { isOk: true }; + } + /** + * @type {TrxScannerProcessor} + */ + const balanceProvider = BlocksoftDispatcher.getScannerProcessor( + this._settings.currencyCode + ); + const balanceRaw = await balanceProvider.getBalanceBlockchain( + data.addressTo + ); + if ( + balanceRaw && + typeof balanceRaw.balance !== 'undefined' && + balanceRaw.balance > 0 + ) { + return { isOk: true }; } - async getFeeRate(data: BlocksoftBlockchainTypes.TransferData, privateData: BlocksoftBlockchainTypes.TransferPrivateData, additionalData: {} = {}): Promise { - const addressHexTo = TronUtils.addressToHex(data.addressTo) - if (TronUtils.addressHexToStr(addressHexTo) !== data.addressTo) { - BlocksoftCryptoLog.log('TrxTransferProcessor.getFeeRateOld check address ' + data.addressTo + ' hex ' + addressHexTo + ' => ' + TronUtils.addressHexToStr(addressHexTo)) - throw new Error('TRX SYSTEM ERROR - Please check address ' + data.addressTo) - } + const balanceProviderBasic = BlocksoftDispatcher.getScannerProcessor('TRX'); + const balanceRawBasic = await balanceProviderBasic.getBalanceBlockchain( + data.addressTo + ); + if ( + balanceRawBasic && + typeof balanceRawBasic.balance !== 'undefined' && + balanceRawBasic.balance > 0 + ) { + return { isOk: true }; + } - try { - const link = PROXY_FEE + '?from=' + data.addressFrom + '&fromHex=' + TronUtils.addressToHex(data.addressFrom) + '&to=' + data.addressTo + '&toHex=' + addressHexTo - + '&token=' + this._tokenName + '&tokenHex=' + (this._isToken20 ? TronUtils.addressToHex( this._tokenName) : '') - + '&amount=' + data.amount + '&isTransferAll=' + (data.isTransferAll ? 1 : 0) - let res = false - try { - res = await BlocksoftAxios.get(link) - } catch (e) { - throw new Error('no proxy fee for ' + link) - } - res = res.data - if (config.debug.cryptoErrors) { - console.log(this._settings.currencyCode + ' TrxTransferProcessor.getFeeRate ' + link + ' res ', res) - } - BlocksoftCryptoLog.log(this._settings.currencyCode + ' TrxTransferProcessor.getFeeRate ' + link + ' res ', res) - if (typeof res.feeForTx === 'undefined') { - throw new Error('no res?.feeForTx') - } + const transactionsBasic = + await balanceProviderBasic.getTransactionsBlockchain({ + account: { address: data.addressTo } + }); + if (transactionsBasic !== false) { + return { isOk: true }; + } + return { isOk: false, code: 'TRX_20', address: data.addressTo }; + } - let result - if (res.feeForTx * 1 > 0) { - result = { - selectedFeeIndex: 0, - shouldShowFees: false, - fees: [ - { - langMsg: 'xrp_speed_one', - feeForTx: Math.round(res.feeForTx).toString(), - amountForTx: res?.amountForTx, - selectedTransferAllBalance: res?.selectedTransferAllBalance, - isErrorFee: res?.isErrorFee - } - ] - } - } else { - result = { - selectedFeeIndex: -3, - shouldShowFees: false - } - } - return result as BlocksoftBlockchainTypes.FeeRateResult - } catch (e) { - if (e.message.indexOf('SERVER_RESPONSE_') === 0) { - throw e - } - if (config.debug.cryptoErrors) { - console.log(this._settings.currencyCode + ' TrxTransferProcessor.getFeeRate new error ' + e.message) + async getFeeRate( + data: BlocksoftBlockchainTypes.TransferData, + privateData: BlocksoftBlockchainTypes.TransferPrivateData, + additionalData: {} = {} + ): Promise { + const addressHexTo = TronUtils.addressToHex(data.addressTo); + if (TronUtils.addressHexToStr(addressHexTo) !== data.addressTo) { + BlocksoftCryptoLog.log( + 'TrxTransferProcessor.getFeeRateOld check address ' + + data.addressTo + + ' hex ' + + addressHexTo + + ' => ' + + TronUtils.addressHexToStr(addressHexTo) + ); + throw new Error( + 'TRX SYSTEM ERROR - Please check address ' + data.addressTo + ); + } + + try { + const link = + PROXY_FEE + + '?from=' + + data.addressFrom + + '&fromHex=' + + TronUtils.addressToHex(data.addressFrom) + + '&to=' + + data.addressTo + + '&toHex=' + + addressHexTo + + '&token=' + + this._tokenName + + '&tokenHex=' + + (this._isToken20 ? TronUtils.addressToHex(this._tokenName) : '') + + '&amount=' + + data.amount + + '&isTransferAll=' + + (data.isTransferAll ? 1 : 0); + let res = false; + try { + res = await BlocksoftAxios.get(link); + } catch (e) { + throw new Error('no proxy fee for ' + link); + } + res = res.data; + if (config.debug.cryptoErrors) { + console.log( + this._settings.currencyCode + + ' TrxTransferProcessor.getFeeRate ' + + link + + ' res ', + res + ); + } + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' TrxTransferProcessor.getFeeRate ' + + link + + ' res ', + res + ); + if (typeof res.feeForTx === 'undefined') { + throw new Error('no res?.feeForTx'); + } + + let result; + if (res.feeForTx * 1 > 0) { + result = { + selectedFeeIndex: 0, + shouldShowFees: false, + fees: [ + { + langMsg: 'xrp_speed_one', + feeForTx: Math.round(res.feeForTx).toString(), + amountForTx: res?.amountForTx, + selectedTransferAllBalance: res?.selectedTransferAllBalance, + isErrorFee: res?.isErrorFee } - BlocksoftCryptoLog.log(this._settings.currencyCode + ' TrxTransferProcessor.getFeeRate new error ' + e.message) - return this.getFeeRateOld(data, privateData, additionalData) - } + ] + }; + } else { + result = { + selectedFeeIndex: -3, + shouldShowFees: false + }; + } + return result as BlocksoftBlockchainTypes.FeeRateResult; + } catch (e) { + if (e.message.indexOf('SERVER_RESPONSE_') === 0) { + throw e; + } + if (config.debug.cryptoErrors) { + console.log( + this._settings.currencyCode + + ' TrxTransferProcessor.getFeeRate new error ' + + e.message + ); + } + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' TrxTransferProcessor.getFeeRate new error ' + + e.message + ); + return this.getFeeRateOld(data, privateData, additionalData); } + } - async getFeeRateOld(data: BlocksoftBlockchainTypes.TransferData, privateData: BlocksoftBlockchainTypes.TransferPrivateData, additionalData: {} = {}): Promise { - const addressHexTo = TronUtils.addressToHex(data.addressTo) - const result: BlocksoftBlockchainTypes.FeeRateResult = { - selectedFeeIndex: -3, - shouldShowFees: false - } as BlocksoftBlockchainTypes.FeeRateResult + async getFeeRateOld( + data: BlocksoftBlockchainTypes.TransferData, + privateData: BlocksoftBlockchainTypes.TransferPrivateData, + additionalData: {} = {} + ): Promise { + const addressHexTo = TronUtils.addressToHex(data.addressTo); + const result: BlocksoftBlockchainTypes.FeeRateResult = { + selectedFeeIndex: -3, + shouldShowFees: false + } as BlocksoftBlockchainTypes.FeeRateResult; - const sendLink = BlocksoftExternalSettings.getStatic('TRX_SEND_LINK') - const link = sendLink + '/wallet/getaccountresource' // http://trx.trusteeglobal.com:8090/wallet + const sendLink = BlocksoftExternalSettings.getStatic('TRX_SEND_LINK'); + const link = sendLink + '/wallet/getaccountresource'; // http://trx.trusteeglobal.com:8090/wallet - try { - let feeForTx = 0 - try { - const res = await (BlocksoftBalances.setCurrencyCode('TRX').setAddress(data.addressFrom)).getResources('TrxSendTx') - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' TrxTransferProcessor.getFeeRate result resources from ' + data.addressFrom, res) - if (this._isToken20) { - const bandForTx = BlocksoftExternalSettings.getStatic('TRX_TRC20_BAND_PER_TX') - const priceForBand = BlocksoftExternalSettings.getStatic('TRX_TRC20_PRICE_PER_BAND') - const fullPriceBand = bandForTx * priceForBand - let feeLog = '' - if (res.leftBand <= 0) { - feeForTx = fullPriceBand - feeLog += ' res.leftBand<=0 bandFee=' + bandForTx + '*' + priceForBand + '=' + fullPriceBand - } else { - const diffB = bandForTx - res.leftBand - feeLog += ' diffB=' + bandForTx + '-' + res.leftBand + '=' + diffB - if (diffB > 0) { - feeForTx = BlocksoftUtils.mul(fullPriceBand, BlocksoftUtils.div(diffB, bandForTx)) - feeLog += ' fullPriceBand=' + bandForTx + '*' + priceForBand + '=' + fullPriceBand - feeLog += ' bandFee=' + fullPriceBand + '*' + diffB + '/' + bandForTx + '=' + feeForTx - } - } - const energyForTx = BlocksoftExternalSettings.getStatic('TRX_TRC20_ENERGY_PER_TX') - const priceForEnergy = BlocksoftExternalSettings.getStatic('TRX_TRC20_PRICE_PER_ENERGY') - const fullPriceEnergy = energyForTx * priceForEnergy - if (res.leftEnergy <= 0) { - feeForTx = feeForTx * 1 + fullPriceEnergy - feeLog += ' res.leftEnergy<=0 energyFee=' + energyForTx + '*' + priceForEnergy + '=' + fullPriceEnergy - } else { - const diffE = energyForTx - res.leftEnergy - feeLog += ' diffE=' + energyForTx + '-' + res.leftEnergy + '=' + diffE - if (diffE > 0) { - const energyFee = BlocksoftUtils.mul(fullPriceEnergy, BlocksoftUtils.div(diffE / energyForTx)) * 1 - feeForTx = feeForTx * 1 + energyFee - feeLog += ' fullPriceEnergy=' + energyForTx + '*' + priceForEnergy + '=' + fullPriceEnergy - feeLog += ' energyFee=' + fullPriceEnergy + '*' + diffE + '/' + bandForTx + '=' + energyFee - } - } - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' TrxTransferProcessor.getFeeRate feeForTx ' + feeForTx + ' calculated by ' + feeLog) - } else { - // @ts-ignore - if (res.leftBand <= 0) { - feeForTx = BlocksoftExternalSettings.getStatic('TRX_BASIC_PRICE_WHEN_NO_BAND') - } - } - } catch (e) { - // do nothing - if (config.debug.cryptoErrors) { - console.log(this._settings.currencyCode + ' TrxTransferProcessor.getFeeRate addressFrom data error ' + e.message) - } - BlocksoftCryptoLog.log(this._settings.currencyCode + ' TrxTransferProcessor.getFeeRate addressFrom data error ' + e.message) + try { + let feeForTx = 0; + try { + const res = await BlocksoftBalances.setCurrencyCode('TRX') + .setAddress(data.addressFrom) + .getResources('TrxSendTx'); + await BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' TrxTransferProcessor.getFeeRate result resources from ' + + data.addressFrom, + res + ); + if (this._isToken20) { + const bandForTx = BlocksoftExternalSettings.getStatic( + 'TRX_TRC20_BAND_PER_TX' + ); + const priceForBand = BlocksoftExternalSettings.getStatic( + 'TRX_TRC20_PRICE_PER_BAND' + ); + const fullPriceBand = bandForTx * priceForBand; + let feeLog = ''; + if (res.leftBand <= 0) { + feeForTx = fullPriceBand; + feeLog += + ' res.leftBand<=0 bandFee=' + + bandForTx + + '*' + + priceForBand + + '=' + + fullPriceBand; + } else { + const diffB = bandForTx - res.leftBand; + feeLog += ' diffB=' + bandForTx + '-' + res.leftBand + '=' + diffB; + if (diffB > 0) { + feeForTx = BlocksoftUtils.mul( + fullPriceBand, + BlocksoftUtils.div(diffB, bandForTx) + ); + feeLog += + ' fullPriceBand=' + + bandForTx + + '*' + + priceForBand + + '=' + + fullPriceBand; + feeLog += + ' bandFee=' + + fullPriceBand + + '*' + + diffB + + '/' + + bandForTx + + '=' + + feeForTx; + } + } + const energyForTx = BlocksoftExternalSettings.getStatic( + 'TRX_TRC20_ENERGY_PER_TX' + ); + const priceForEnergy = BlocksoftExternalSettings.getStatic( + 'TRX_TRC20_PRICE_PER_ENERGY' + ); + const fullPriceEnergy = energyForTx * priceForEnergy; + if (res.leftEnergy <= 0) { + feeForTx = feeForTx * 1 + fullPriceEnergy; + feeLog += + ' res.leftEnergy<=0 energyFee=' + + energyForTx + + '*' + + priceForEnergy + + '=' + + fullPriceEnergy; + } else { + const diffE = energyForTx - res.leftEnergy; + feeLog += + ' diffE=' + energyForTx + '-' + res.leftEnergy + '=' + diffE; + if (diffE > 0) { + const energyFee = + BlocksoftUtils.mul( + fullPriceEnergy, + BlocksoftUtils.div(diffE / energyForTx) + ) * 1; + feeForTx = feeForTx * 1 + energyFee; + feeLog += + ' fullPriceEnergy=' + + energyForTx + + '*' + + priceForEnergy + + '=' + + fullPriceEnergy; + feeLog += + ' energyFee=' + + fullPriceEnergy + + '*' + + diffE + + '/' + + bandForTx + + '=' + + energyFee; } + } + await BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' TrxTransferProcessor.getFeeRate feeForTx ' + + feeForTx + + ' calculated by ' + + feeLog + ); + } else { + // @ts-ignore + if (res.leftBand <= 0) { + feeForTx = BlocksoftExternalSettings.getStatic( + 'TRX_BASIC_PRICE_WHEN_NO_BAND' + ); + } + } + } catch (e) { + // do nothing + if (config.debug.cryptoErrors) { + console.log( + this._settings.currencyCode + + ' TrxTransferProcessor.getFeeRate addressFrom data error ' + + e.message + ); + } + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' TrxTransferProcessor.getFeeRate addressFrom data error ' + + e.message + ); + } + let isErrorFee = false; + const balance = await BlocksoftBalances.setCurrencyCode('TRX') + .setAddress(data.addressFrom) + .getBalance('TrxSendTx'); + if (this._isToken20) { + if (!balance || balance.balanceAvailable <= feeForTx) { + isErrorFee = true; + // throw new Error('SERVER_RESPONSE_NOT_ENOUGH_FEE') + } + } else if (this._tokenName === '_') { + if ( + !balance || + balance.balanceAvailable <= feeForTx * 1 + data.amount * 1 + ) { + isErrorFee = true; + // throw new Error('SERVER_RESPONSE_NOT_ENOUGH_FEE') + } + } - let isErrorFee = false - const balance = await (BlocksoftBalances.setCurrencyCode('TRX').setAddress(data.addressFrom)).getBalance('TrxSendTx') - if (this._isToken20) { - if (!balance || balance.balanceAvailable <= feeForTx) { - isErrorFee = true - // throw new Error('SERVER_RESPONSE_NOT_ENOUGH_FEE') - } - } else if (this._tokenName === '_') { - if (!balance || (balance.balanceAvailable <= feeForTx * 1 + data.amount * 1)) { - isErrorFee = true - // throw new Error('SERVER_RESPONSE_NOT_ENOUGH_FEE') - } - } + if (typeof data.dexOrderData === 'undefined' || !data.dexOrderData) { + try { + const res2 = await BlocksoftAxios.post(link, { + address: addressHexTo + }); + const tronData2 = res2.data; + delete tronData2.assetNetUsed; + delete tronData2.assetNetLimit; + await BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' TrxTransferProcessor.getFeeRate result ' + + link + + ' to ' + + data.addressTo, + tronData2 + ); + if (typeof tronData2.freeNetLimit === 'undefined') { + feeForTx = feeForTx * 1 + 1100000; + } + } catch (e) { + // do nothing + if (config.debug.cryptoErrors) { + console.log( + this._settings.currencyCode + + ' TrxTransferProcessor.getFeeRate addressTo data error ' + + e.message + ); + } + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' TrxTransferProcessor.getFeeRate addressTo data error ' + + e.message + ); + } + } - if (typeof data.dexOrderData === 'undefined' || !data.dexOrderData) { - try { - const res2 = await BlocksoftAxios.post(link, { address: addressHexTo }) - const tronData2 = res2.data - delete tronData2.assetNetUsed - delete tronData2.assetNetLimit - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' TrxTransferProcessor.getFeeRate result ' + link + ' to ' + data.addressTo, tronData2) - if (typeof tronData2.freeNetLimit === 'undefined') { - feeForTx = feeForTx * 1 + 1100000 - } - } catch (e) { - // do nothing - if (config.debug.cryptoErrors) { - console.log(this._settings.currencyCode + ' TrxTransferProcessor.getFeeRate addressTo data error ' + e.message) - } - BlocksoftCryptoLog.log(this._settings.currencyCode + ' TrxTransferProcessor.getFeeRate addressTo data error ' + e.message) - } + if (feeForTx !== 0) { + let amountForTx = data.amount; + let selectedTransferAllBalance = data.amount; + if (this._tokenName === '_') { + const balance = await BlocksoftBalances.setCurrencyCode('TRX') + .setAddress(data.addressFrom) + .getBalance('TrxSendTx'); + if (balance && typeof balance.balance !== 'undefined') { + if (balance.balance === 0) { + amountForTx = 0; + selectedTransferAllBalance = 0; + } else { + selectedTransferAllBalance = BlocksoftUtils.diff( + balance.balance, + feeForTx + ); + let test = BlocksoftUtils.diff(data.amount, feeForTx); + if (test * 1 > balance.balance * 1) { + amountForTx = selectedTransferAllBalance; + } } + } + } - if (feeForTx !== 0) { - let amountForTx = data.amount - let selectedTransferAllBalance = data.amount - if (this._tokenName === '_') { - const balance = await (BlocksoftBalances.setCurrencyCode('TRX').setAddress(data.addressFrom)).getBalance('TrxSendTx') - if (balance && typeof balance.balance !== 'undefined') { - if (balance.balance === 0) { - amountForTx = 0 - selectedTransferAllBalance = 0 - } else { - selectedTransferAllBalance = BlocksoftUtils.diff(balance.balance, feeForTx) - let test = BlocksoftUtils.diff(data.amount, feeForTx) - if (test * 1 > balance.balance * 1) { - amountForTx = selectedTransferAllBalance - } - } - } - } - - result.fees = [ - { - langMsg: 'xrp_speed_one', - feeForTx: Math.round(feeForTx).toString(), - amountForTx, - selectedTransferAllBalance, - isErrorFee - } - ] - /* + result.fees = [ + { + langMsg: 'xrp_speed_one', + feeForTx: Math.round(feeForTx).toString(), + amountForTx, + selectedTransferAllBalance, + isErrorFee + } + ]; + /* if (res.data.balance * 1 < feeForTx * 1) { throw new Error('SERVER_RESPONSE_BANDWITH_ERROR_TRX') } */ - result.selectedFeeIndex = 0 - } - } catch (e) { - if (e.message.indexOf('SERVER_RESPONSE_') === 0) { - throw e - } - if (config.debug.cryptoErrors) { - console.log(this._settings.currencyCode + ' TrxTransferProcessor.getFeeRate error ' + e.message) - } - BlocksoftCryptoLog.log(this._settings.currencyCode + ' TrxTransferProcessor.getFeeRate error ' + e.message) - } - return result + result.selectedFeeIndex = 0; + } + } catch (e) { + if (e.message.indexOf('SERVER_RESPONSE_') === 0) { + throw e; + } + if (config.debug.cryptoErrors) { + console.log( + this._settings.currencyCode + + ' TrxTransferProcessor.getFeeRate error ' + + e.message + ); + } + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' TrxTransferProcessor.getFeeRate error ' + + e.message + ); } + return result; + } - async getTransferAllBalance(data: BlocksoftBlockchainTypes.TransferData, privateData: BlocksoftBlockchainTypes.TransferPrivateData, additionalData: BlocksoftBlockchainTypes.TransferAdditionalData = {}): Promise { - data.isTransferAll = true - const balance = data.amount - // @ts-ignore - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' TrxTransferProcessor.getTransferAllBalance ', data.addressFrom + ' => ' + balance) - // noinspection EqualityComparisonWithCoercionJS - if (balance === '0') { - return { - selectedTransferAllBalance: '0', - selectedFeeIndex: -1, - fees: [], - shouldShowFees: false, - countedForBasicBalance: '0' - } - } - const fees = await this.getFeeRate(data, privateData, additionalData) - if (!fees || fees.selectedFeeIndex < 0) { - return { - selectedTransferAllBalance: balance, - selectedFeeIndex: -3, - fees: [], - shouldShowFees: false, - countedForBasicBalance: balance - } - } - return { - ...fees, - shouldShowFees: false, - selectedTransferAllBalance: fees.fees[fees.selectedFeeIndex].selectedTransferAllBalance - } + async getTransferAllBalance( + data: BlocksoftBlockchainTypes.TransferData, + privateData: BlocksoftBlockchainTypes.TransferPrivateData, + additionalData: BlocksoftBlockchainTypes.TransferAdditionalData = {} + ): Promise { + data.isTransferAll = true; + const balance = data.amount; + // @ts-ignore + await BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' TrxTransferProcessor.getTransferAllBalance ', + data.addressFrom + ' => ' + balance + ); + // noinspection EqualityComparisonWithCoercionJS + if (balance === '0') { + return { + selectedTransferAllBalance: '0', + selectedFeeIndex: -1, + fees: [], + shouldShowFees: false, + countedForBasicBalance: '0' + }; } + const fees = await this.getFeeRate(data, privateData, additionalData); + if (!fees || fees.selectedFeeIndex < 0) { + return { + selectedTransferAllBalance: balance, + selectedFeeIndex: -3, + fees: [], + shouldShowFees: false, + countedForBasicBalance: balance + }; + } + return { + ...fees, + shouldShowFees: false, + selectedTransferAllBalance: + fees.fees[fees.selectedFeeIndex].selectedTransferAllBalance + }; + } - /** - * https://developers.tron.network/reference#walletcreatetransaction - * https://developers.tron.network/docs/trc20-introduction#section-8usdt-transfer - */ - async sendTx(data: BlocksoftBlockchainTypes.TransferData, privateData: BlocksoftBlockchainTypes.TransferPrivateData, uiData: BlocksoftBlockchainTypes.TransferUiData): Promise { - if (typeof privateData.privateKey === 'undefined') { - throw new Error('TRX transaction required privateKey') - } - if (uiData.selectedFee.isErrorFee && (typeof uiData.uiErrorConfirmed === 'undefined' || !uiData.uiErrorConfirmed)) { - if (config.debug.cryptoErrors) { - console.log(`uiData.selectedFee.isErrorFee`, uiData) - } - throw new Error('SERVER_RESPONSE_NOT_ENOUGH_FEE') - } - - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' TrxTransferProcessor.sendTx started ' + data.addressFrom + ' => ' + data.addressTo) + /** + * https://developers.tron.network/reference#walletcreatetransaction + * https://developers.tron.network/docs/trc20-introduction#section-8usdt-transfer + */ + async sendTx( + data: BlocksoftBlockchainTypes.TransferData, + privateData: BlocksoftBlockchainTypes.TransferPrivateData, + uiData: BlocksoftBlockchainTypes.TransferUiData + ): Promise { + if (typeof privateData.privateKey === 'undefined') { + throw new Error('TRX transaction required privateKey'); + } + if ( + uiData.selectedFee.isErrorFee && + (typeof uiData.uiErrorConfirmed === 'undefined' || + !uiData.uiErrorConfirmed) + ) { + if (config.debug.cryptoErrors) { + console.log(`uiData.selectedFee.isErrorFee`, uiData); + } + throw new Error('SERVER_RESPONSE_NOT_ENOUGH_FEE'); + } + await BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' TrxTransferProcessor.sendTx started ' + + data.addressFrom + + ' => ' + + data.addressTo + ); - const logData = {} - logData.currencyCode = this._settings.currencyCode - logData.selectedFee = uiData.selectedFee - logData.from = data.addressFrom - logData.basicAddressTo = data.addressTo - logData.basicAmount = data.amount - logData.pushLocale = sublocale() - logData.pushSetting = await settingsActions.getSetting('transactionsNotifs') - logData.basicToken = this._tokenName + const logData = {}; + logData.currencyCode = this._settings.currencyCode; + logData.selectedFee = uiData.selectedFee; + logData.from = data.addressFrom; + logData.basicAddressTo = data.addressTo; + logData.basicAmount = data.amount; + logData.pushLocale = sublocale(); + logData.pushSetting = await settingsActions.getSetting( + 'transactionsNotifs' + ); + logData.basicToken = this._tokenName; + const sendLink = BlocksoftExternalSettings.getStatic('TRX_SEND_LINK'); + let tx; + if (typeof data.blockchainData !== 'undefined' && data.blockchainData) { + tx = data.blockchainData; + } else { + let link, res, params; - const sendLink = BlocksoftExternalSettings.getStatic('TRX_SEND_LINK') - let tx - if (typeof data.blockchainData !== 'undefined' && data.blockchainData) { - tx = data.blockchainData - } else { + if (typeof data.dexOrderData !== 'undefined' && data.dexOrderData) { + // {"tokenContract":"41a2726afbecbd8e936000ed684cef5e2f5cf43008","contractMethod":"trxToTokenSwapInput(uint256)","options":{"callValue":"1000000"},"params":[{"type":"uint256","value":"116256"}]} + let ownerAddress; - let link, res, params + const abiCoder = new AbiCoder(); + try { + ownerAddress = TronUtils.addressToHex(data.addressFrom); + } catch (e) { + e.message += + ' inside TronUtils.addressToHex owner_address ' + data.addressFrom; + throw e; + } - if (typeof data.dexOrderData !== 'undefined' && data.dexOrderData) { - // {"tokenContract":"41a2726afbecbd8e936000ed684cef5e2f5cf43008","contractMethod":"trxToTokenSwapInput(uint256)","options":{"callValue":"1000000"},"params":[{"type":"uint256","value":"116256"}]} - let ownerAddress + const link = sendLink + '/wallet/triggersmartcontract'; + const total = data.dexOrderData.length; + let index = 0; + for (const order of data.dexOrderData) { + index++; + let parameter = ''; - const abiCoder = new AbiCoder() + if (order.params) { + const types = []; + const values = []; + try { + for (const tmp of order.params) { + let type, value; try { - ownerAddress = TronUtils.addressToHex(data.addressFrom) + type = tmp.type; + value = tmp.value; + if (type === 'address') { + value = TronUtils.addressToHex(value).replace( + ADDRESS_PREFIX_REGEX, + '0x' + ); + } else if (type === 'address[]') { + value = value.map((v) => + TronUtils.addressToHex(v).replace( + ADDRESS_PREFIX_REGEX, + '0x' + ) + ); + } + types.push(type); + values.push(value); } catch (e) { - e.message += ' inside TronUtils.addressToHex owner_address ' + data.addressFrom - throw e + throw new Error( + e.message + + ' type ' + + type + + ' tmp.value ' + + tmp.value + + ' value ' + + value + ); } + } + parameter = abiCoder.encode(types, values).replace(/^(0x)/, ''); + } catch (e) { + throw new Error(e.message + ' in abiCoder'); + } + } - const link = sendLink + '/wallet/triggersmartcontract' - const total = data.dexOrderData.length - let index = 0 - for (const order of data.dexOrderData) { - index++ - let parameter = '' - - if (order.params) { - const types = [] - const values = [] - try { - for (const tmp of order.params) { - let type, value - try { - type = tmp.type - value = tmp.value - if (type === 'address') { - value = TronUtils.addressToHex(value).replace(ADDRESS_PREFIX_REGEX, '0x') - } else if (type === 'address[]') { - value = value.map(v => TronUtils.addressToHex(v).replace(ADDRESS_PREFIX_REGEX, '0x')) - } - types.push(type) - values.push(value) - } catch (e) { - throw new Error(e.message + ' type ' + type + ' tmp.value ' + tmp.value + ' value ' + value) - } - } - parameter = abiCoder.encode(types, values).replace(/^(0x)/, '') - } catch (e) { - throw new Error(e.message + ' in abiCoder') - } - } + let params; + try { + params = { + owner_address: ownerAddress, + contract_address: order.tokenContract, + function_selector: order.contractMethod, + // @ts-ignore + parameter, + fee_limit: BlocksoftExternalSettings.getStatic( + 'TRX_TRC20_MAX_LIMIT' + ) + }; + if ( + typeof order.options !== 'undefined' && + typeof order.options.callValue !== 'undefined' + ) { + params.call_value = order.options.callValue * 1; + } + } catch (e1) { + throw new Error(e1.message + ' in params build'); + } + if (index < total) { + res = await BlocksoftAxios.post(link, params); - let params - try { - params = { - owner_address: ownerAddress, - contract_address: order.tokenContract, - function_selector: order.contractMethod, - // @ts-ignore - parameter, - fee_limit: BlocksoftExternalSettings.getStatic('TRX_TRC20_MAX_LIMIT') - } - if (typeof order.options !== 'undefined' && typeof order.options.callValue !== 'undefined') { - params.call_value = order.options.callValue * 1 - } - } catch (e1) { - throw new Error(e1.message + ' in params build') - } - if (index < total) { - res = await BlocksoftAxios.post(link, params) - - tx = res.data.transaction - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' TrxTxProcessor.sendSubTx tx', tx) - - tx.signature = [TronUtils.ECKeySign(Buffer.from(tx.txID, 'hex'), Buffer.from(privateData.privateKey, 'hex'))] - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' TrxTxProcessor.sendSubTx signed', tx) - - let resultSub = {} as BlocksoftBlockchainTypes.SendTxResult - try { - resultSub = await this.sendProvider.sendTx(tx, '', false, logData) - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' TrxTxProcessor.sendSubTx broadcasted') - } catch (e) { - if (config.debug.cryptoErrors) { - console.log(this._settings.currencyCode + ' TrxTransferProcessor.sendSubTx error', e, uiData) - } - BlocksoftCryptoLog.log(this._settings.currencyCode + ' TrxTransferProcessor.sendSubTx error ' + e.message) - // noinspection ES6MissingAwait - MarketingEvent.logOnlyRealTime('v20_trx_tx_sub_error ' + this._settings.currencyCode + ' ' + data.addressFrom + ' => ' + data.addressTo + ' ' + e.message, logData) - throw e - } - - const linkRecheck = sendLink + '/wallet/gettransactioninfobyid' - let checks = 0 - let mined = false - do { - checks++ - try { - const recheck = await BlocksoftAxios.post(linkRecheck, { - value: tx.txID - }) - if (typeof recheck.data !== 'undefined') { - if (typeof recheck.data.id !== 'undefined' && typeof recheck.data.blockNumber !== 'undefined' - && typeof recheck.data.receipt !== 'undefined' && typeof recheck.data.receipt.result !== 'undefined' - ) { - - // @ts-ignore - BlocksoftCryptoLog.log(this._settings.currencyCode + ' TrxTransferProcessor.sendSubTx recheck ', { - id: recheck.data.id, - blockNumber: recheck.data.blockNumber, - receipt: recheck.data.receipt - }) - mined = true - const minedStatus = recheck.data.receipt.result.toUpperCase() - if (minedStatus === 'OUT_OF_ENERGY') { - strings(`account.transactionStatuses.out_of_energy`) - } else if (minedStatus === 'FAILED') { - strings(`account.transactionStatuses.fail`) - } else if (minedStatus !== 'SUCCESS') { - throw new Error('Bad tx status ' + JSON.stringify(recheck.data.receipt)) - } - break - } - } - } catch (e1) { - if (config.debug.cryptoErrors) { - console.log(this._settings.currencyCode + ' TRX transaction recheck error ', e1) - } - BlocksoftCryptoLog.log(this._settings.currencyCode + ' TRX transaction recheck error ' + e1.message) - } - } while (checks < 100 && !mined) - - } else { - res = await BlocksoftAxios.post(link, params) - } - } - } else { + tx = res.data.transaction; + await BlocksoftCryptoLog.log( + this._settings.currencyCode + ' TrxTxProcessor.sendSubTx tx', + tx + ); - if (typeof data.addressTo === 'undefined') { - throw new Error('TRX transaction required addressTo') - } - if (data.addressFrom === data.addressTo) { - throw new Error('SERVER_RESPONSE_SELF_TX_FORBIDDEN') - } - // check error - await this.getFeeRate(data, privateData) + tx.signature = [ + TronUtils.ECKeySign( + Buffer.from(tx.txID, 'hex'), + Buffer.from(privateData.privateKey, 'hex') + ) + ]; + await BlocksoftCryptoLog.log( + this._settings.currencyCode + ' TrxTxProcessor.sendSubTx signed', + tx + ); - let toAddress, ownerAddress + let resultSub = {} as BlocksoftBlockchainTypes.SendTxResult; + try { + resultSub = await this.sendProvider.sendTx( + tx, + '', + false, + logData + ); + await BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' TrxTxProcessor.sendSubTx broadcasted' + ); + } catch (e) { + if (config.debug.cryptoErrors) { + console.log( + this._settings.currencyCode + + ' TrxTransferProcessor.sendSubTx error', + e, + uiData + ); + } + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' TrxTransferProcessor.sendSubTx error ' + + e.message + ); + // noinspection ES6MissingAwait + MarketingEvent.logOnlyRealTime( + 'v20_trx_tx_sub_error ' + + this._settings.currencyCode + + ' ' + + data.addressFrom + + ' => ' + + data.addressTo + + ' ' + + e.message, + logData + ); + throw e; + } - try { - toAddress = TronUtils.addressToHex(data.addressTo) - } catch (e) { - e.message += ' inside TronUtils.addressToHex to_address ' + data.addressTo - throw e + const linkRecheck = sendLink + '/wallet/gettransactioninfobyid'; + let checks = 0; + let mined = false; + do { + checks++; + try { + const recheck = await BlocksoftAxios.post(linkRecheck, { + value: tx.txID + }); + if (typeof recheck.data !== 'undefined') { + if ( + typeof recheck.data.id !== 'undefined' && + typeof recheck.data.blockNumber !== 'undefined' && + typeof recheck.data.receipt !== 'undefined' && + typeof recheck.data.receipt.result !== 'undefined' + ) { + // @ts-ignore + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' TrxTransferProcessor.sendSubTx recheck ', + { + id: recheck.data.id, + blockNumber: recheck.data.blockNumber, + receipt: recheck.data.receipt + } + ); + mined = true; + const minedStatus = + recheck.data.receipt.result.toUpperCase(); + if (minedStatus === 'OUT_OF_ENERGY') { + strings(`account.transactionStatuses.out_of_energy`); + } else if (minedStatus === 'FAILED') { + strings(`account.transactionStatuses.fail`); + } else if (minedStatus !== 'SUCCESS') { + throw new Error( + 'Bad tx status ' + JSON.stringify(recheck.data.receipt) + ); + } + break; + } } - - if (TronUtils.addressHexToStr(toAddress) !== data.addressTo) { - BlocksoftCryptoLog.log('TrxTransferProcessor.sendTx heck address ' + data.addressTo + ' hex ' + toAddress + ' => ' + TronUtils.addressHexToStr(toAddress)) - throw new Error('TRX SYSTEM ERROR - Please check address ' + data.addressTo) + } catch (e1) { + if (config.debug.cryptoErrors) { + console.log( + this._settings.currencyCode + + ' TRX transaction recheck error ', + e1 + ); } + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' TRX transaction recheck error ' + + e1.message + ); + } + } while (checks < 100 && !mined); + } else { + res = await BlocksoftAxios.post(link, params); + } + } + } else { + if (typeof data.addressTo === 'undefined') { + throw new Error('TRX transaction required addressTo'); + } + if (data.addressFrom === data.addressTo) { + throw new Error('SERVER_RESPONSE_SELF_TX_FORBIDDEN'); + } + // check error + await this.getFeeRate(data, privateData); + let toAddress, ownerAddress; - try { - ownerAddress = TronUtils.addressToHex(data.addressFrom) - } catch (e) { - e.message += ' inside TronUtils.addressToHex owner_address ' + data.addressFrom - throw e - } - - if (this._tokenName[0] === 'T') { - link = sendLink + '/wallet/triggersmartcontract' - const parameter = '0000000000000000000000' + toAddress.toUpperCase() + '000000000000000000000000' + BlocksoftUtils.decimalToHex(BlocksoftUtils.round(data.amount), 40) - params = { - owner_address: ownerAddress, - contract_address: TronUtils.addressToHex(this._tokenName), - function_selector: 'transfer(address,uint256)', - parameter, - fee_limit: BlocksoftExternalSettings.getStatic('TRX_TRC20_MAX_LIMIT'), - call_value: 0 - } - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' TrxTransferProcessor.sendTx inited1' + data.addressFrom + ' => ' + data.addressTo + ' ' + link, params) - res = await BlocksoftAxios.post(link, params) - } else { - params = { - owner_address: ownerAddress, - to_address: toAddress, - // @ts-ignore - amount: BlocksoftUtils.round(data.amount) * 1 - } + try { + toAddress = TronUtils.addressToHex(data.addressTo); + } catch (e) { + e.message += + ' inside TronUtils.addressToHex to_address ' + data.addressTo; + throw e; + } - if (this._tokenName === '_') { - link = sendLink + '/wallet/createtransaction' - } else { - // @ts-ignore - params.asset_name = '0x' + Buffer.from(this._tokenName).toString('hex') - link = sendLink + '/wallet/transferasset' - } + if (TronUtils.addressHexToStr(toAddress) !== data.addressTo) { + BlocksoftCryptoLog.log( + 'TrxTransferProcessor.sendTx heck address ' + + data.addressTo + + ' hex ' + + toAddress + + ' => ' + + TronUtils.addressHexToStr(toAddress) + ); + throw new Error( + 'TRX SYSTEM ERROR - Please check address ' + data.addressTo + ); + } - try { - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' TrxTransferProcessor.sendTx inited2 ' + data.addressFrom + ' => ' + data.addressTo + ' ' + link, params) - res = await BlocksoftAxios.post(link, params) - } catch (e) { - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' TrxTransferProcessor.sendTx result2' + data.addressFrom + ' => ' + data.addressTo + ' ' + link + ' ' + e.message) - if (e.message.indexOf('timeout of') !== -1 || e.message.indexOf('network') !== -1) { - throw new Error('SERVER_RESPONSE_NOT_CONNECTED') - } else { - throw e - } - } - } - } + try { + ownerAddress = TronUtils.addressToHex(data.addressFrom); + } catch (e) { + e.message += + ' inside TronUtils.addressToHex owner_address ' + data.addressFrom; + throw e; + } + if (this._tokenName[0] === 'T') { + link = sendLink + '/wallet/triggersmartcontract'; + const parameter = + '0000000000000000000000' + + toAddress.toUpperCase() + + '000000000000000000000000' + + BlocksoftUtils.decimalToHex(BlocksoftUtils.round(data.amount), 40); + params = { + owner_address: ownerAddress, + contract_address: TronUtils.addressToHex(this._tokenName), + function_selector: 'transfer(address,uint256)', + parameter, + fee_limit: BlocksoftExternalSettings.getStatic( + 'TRX_TRC20_MAX_LIMIT' + ), + call_value: 0 + }; + await BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' TrxTransferProcessor.sendTx inited1' + + data.addressFrom + + ' => ' + + data.addressTo + + ' ' + + link, + params + ); + res = await BlocksoftAxios.post(link, params); + } else { + params = { + owner_address: ownerAddress, + to_address: toAddress, // @ts-ignore - if (typeof res.data.Error !== 'undefined') { - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' TrxTransferProcessor.sendTx error ' + data.addressFrom + ' => ' + data.addressTo + ' ', res.data) - // @ts-ignore - this.sendProvider.trxError(res.data.Error.message || res.data.Error) - } + amount: BlocksoftUtils.round(data.amount) * 1 + }; + if (this._tokenName === '_') { + link = sendLink + '/wallet/createtransaction'; + } else { // @ts-ignore - tx = res.data - if ((typeof data.dexOrderData !== 'undefined' && data.dexOrderData) || (this._tokenName[0] === 'T')) { - // @ts-ignore - if (typeof res.data.transaction === 'undefined' || typeof res.data.result === 'undefined') { - // @ts-ignore - if (typeof res.data.result.message !== 'undefined') { - // @ts-ignore - res.data.result.message = BlocksoftUtils.hexToUtf('0x' + res.data.result.message) - } - // @ts-ignore - this.sendProvider.trxError('No tx in contract data ' + JSON.stringify(res.data)) - } - // @ts-ignore - tx = res.data.transaction + params.asset_name = + '0x' + Buffer.from(this._tokenName).toString('hex'); + link = sendLink + '/wallet/transferasset'; + } + + try { + await BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' TrxTransferProcessor.sendTx inited2 ' + + data.addressFrom + + ' => ' + + data.addressTo + + ' ' + + link, + params + ); + res = await BlocksoftAxios.post(link, params); + } catch (e) { + await BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' TrxTransferProcessor.sendTx result2' + + data.addressFrom + + ' => ' + + data.addressTo + + ' ' + + link + + ' ' + + e.message + ); + if ( + e.message.indexOf('timeout of') !== -1 || + e.message.indexOf('network') !== -1 + ) { + throw new Error('SERVER_RESPONSE_NOT_CONNECTED'); } else { - // @ts-ignore - if (typeof res.data.txID === 'undefined') { - // @ts-ignore - if (typeof res.data.result.message !== 'undefined') { - // @ts-ignore - res.data.result.message = BlocksoftUtils.hexToUtf('0x' + res.data.result.message) - } - // @ts-ignore - this.sendProvider.trxError('No txID in data ' + JSON.stringify(res.data)) - } + throw e; } + } } + } - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' TrxTxProcessor.sendTx token ' + this._tokenName + ' tx', tx) + // @ts-ignore + if (typeof res.data.Error !== 'undefined') { + await BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' TrxTransferProcessor.sendTx error ' + + data.addressFrom + + ' => ' + + data.addressTo + + ' ', + res.data + ); + // @ts-ignore + this.sendProvider.trxError(res.data.Error.message || res.data.Error); + } - tx.signature = [TronUtils.ECKeySign(Buffer.from(tx.txID, 'hex'), Buffer.from(privateData.privateKey, 'hex'))] - if (typeof uiData !== 'undefined' && typeof uiData.selectedFee !== 'undefined' && typeof uiData.selectedFee.rawOnly !== 'undefined' && uiData.selectedFee.rawOnly) { - return { rawOnly: uiData.selectedFee.rawOnly, raw: JSON.stringify(tx) } + // @ts-ignore + tx = res.data; + if ( + (typeof data.dexOrderData !== 'undefined' && data.dexOrderData) || + this._tokenName[0] === 'T' + ) { + // @ts-ignore + if ( + typeof res.data.transaction === 'undefined' || + typeof res.data.result === 'undefined' + ) { + // @ts-ignore + if (typeof res.data.result.message !== 'undefined') { + // @ts-ignore + res.data.result.message = BlocksoftUtils.hexToUtf( + '0x' + res.data.result.message + ); + } + // @ts-ignore + this.sendProvider.trxError( + 'No tx in contract data ' + JSON.stringify(res.data) + ); + } + // @ts-ignore + tx = res.data.transaction; + } else { + // @ts-ignore + if (typeof res.data.txID === 'undefined') { + // @ts-ignore + if (typeof res.data.result.message !== 'undefined') { + // @ts-ignore + res.data.result.message = BlocksoftUtils.hexToUtf( + '0x' + res.data.result.message + ); + } + // @ts-ignore + this.sendProvider.trxError( + 'No txID in data ' + JSON.stringify(res.data) + ); } + } + } - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' TrxTxProcessor.sendTx signed', tx) + await BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' TrxTxProcessor.sendTx token ' + + this._tokenName + + ' tx', + tx + ); - let result = {} as BlocksoftBlockchainTypes.SendTxResult - try { - result = await this.sendProvider.sendTx(tx, '', false, logData) - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' TrxTxProcessor.sendTx broadcasted') - } catch (e) { - if (config.debug.cryptoErrors) { - console.log(this._settings.currencyCode + ' TrxTransferProcessor.sendTx error', e, uiData) - } - BlocksoftCryptoLog.log(this._settings.currencyCode + ' TrxTransferProcessor.sendTx error ' + e.message) - // noinspection ES6MissingAwait - MarketingEvent.logOnlyRealTime('v20_trx_tx_error ' + this._settings.currencyCode + ' ' + data.addressFrom + ' => ' + data.addressTo + ' ' + e.message, logData) - throw e - } - // noinspection ES6MissingAwait - MarketingEvent.logOnlyRealTime('v20_trx_tx_success ' + this._settings.currencyCode + ' ' + data.addressFrom + ' => ' + data.addressTo, logData) + tx.signature = [ + TronUtils.ECKeySign( + Buffer.from(tx.txID, 'hex'), + Buffer.from(privateData.privateKey, 'hex') + ) + ]; + if ( + typeof uiData !== 'undefined' && + typeof uiData.selectedFee !== 'undefined' && + typeof uiData.selectedFee.rawOnly !== 'undefined' && + uiData.selectedFee.rawOnly + ) { + return { rawOnly: uiData.selectedFee.rawOnly, raw: JSON.stringify(tx) }; + } - await (BlocksoftTransactions.resetTransactionsPending({ account: { currencyCode: 'TRX' } }, 'AccountRunPending')) + await BlocksoftCryptoLog.log( + this._settings.currencyCode + ' TrxTxProcessor.sendTx signed', + tx + ); - if (config.debug.cryptoErrors) { - console.log(this._settings.currencyCode + ' TrxTransferProcessor.sendTx result', JSON.parse(JSON.stringify(result))) - } - return result + let result = {} as BlocksoftBlockchainTypes.SendTxResult; + try { + result = await this.sendProvider.sendTx(tx, '', false, logData); + await BlocksoftCryptoLog.log( + this._settings.currencyCode + ' TrxTxProcessor.sendTx broadcasted' + ); + } catch (e) { + if (config.debug.cryptoErrors) { + console.log( + this._settings.currencyCode + ' TrxTransferProcessor.sendTx error', + e, + uiData + ); + } + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' TrxTransferProcessor.sendTx error ' + + e.message + ); + // noinspection ES6MissingAwait + MarketingEvent.logOnlyRealTime( + 'v20_trx_tx_error ' + + this._settings.currencyCode + + ' ' + + data.addressFrom + + ' => ' + + data.addressTo + + ' ' + + e.message, + logData + ); + throw e; + } + // noinspection ES6MissingAwait + MarketingEvent.logOnlyRealTime( + 'v20_trx_tx_success ' + + this._settings.currencyCode + + ' ' + + data.addressFrom + + ' => ' + + data.addressTo, + logData + ); + + await BlocksoftTransactions.resetTransactionsPending( + { account: { currencyCode: 'TRX' } }, + 'AccountRunPending' + ); + + if (config.debug.cryptoErrors) { + console.log( + this._settings.currencyCode + ' TrxTransferProcessor.sendTx result', + JSON.parse(JSON.stringify(result)) + ); } + return result; + } } diff --git a/crypto/blockchains/trx/basic/TrxNodeInfoProvider.js b/crypto/blockchains/trx/basic/TrxNodeInfoProvider.js index 33de59653..a3755d85b 100644 --- a/crypto/blockchains/trx/basic/TrxNodeInfoProvider.js +++ b/crypto/blockchains/trx/basic/TrxNodeInfoProvider.js @@ -1,33 +1,45 @@ -import BlocksoftAxios from '../../../common/BlocksoftAxios' -import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog' -import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings' +import BlocksoftAxios from '../../../common/BlocksoftAxios'; +import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; +import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings'; -const INFO_MAX_TRY = 50 // max tries before error appear in axios get +const INFO_MAX_TRY = 50; // max tries before error appear in axios get -let CACHE_LAST_BLOCK = 0 +let CACHE_LAST_BLOCK = 0; export default class TrxNodeInfoProvider { - /** - * @returns {Promise} - */ - async getLastBlock() { - try { - const sendLink = BlocksoftExternalSettings.getStatic('TRX_SEND_LINK') - const link = sendLink + '/wallet/getnodeinfo' - let info = await BlocksoftAxios.getWithoutBraking(link, INFO_MAX_TRY) - if (info && typeof info.data !== 'undefined' && typeof info.data.block !== 'undefined') { - info = info.data.block.split(',ID') - info = info[0].substr(4) * 1 - if (info > CACHE_LAST_BLOCK) { - CACHE_LAST_BLOCK = info - } - BlocksoftCryptoLog.log('TrxNodeInfoProvider.getLastBlock currentBlock ' + JSON.stringify(info)) - } else { - BlocksoftCryptoLog.log('TrxNodeInfoProvider.getLastBlock currentBlock warning ' + JSON.stringify(info)) - } - } catch (e) { - BlocksoftCryptoLog.log('TrxNodeInfoProvider.getLastBlock currentBlock error ' + e.message) + /** + * @returns {Promise} + */ + async getLastBlock() { + try { + const sendLink = BlocksoftExternalSettings.getStatic('TRX_SEND_LINK'); + const link = sendLink + '/wallet/getnodeinfo'; + let info = await BlocksoftAxios.getWithoutBraking(link, INFO_MAX_TRY); + if ( + info && + typeof info.data !== 'undefined' && + typeof info.data.block !== 'undefined' + ) { + info = info.data.block.split(',ID'); + info = info[0].substr(4) * 1; + if (info > CACHE_LAST_BLOCK) { + CACHE_LAST_BLOCK = info; } - return CACHE_LAST_BLOCK + BlocksoftCryptoLog.log( + 'TrxNodeInfoProvider.getLastBlock currentBlock ' + + JSON.stringify(info) + ); + } else { + BlocksoftCryptoLog.log( + 'TrxNodeInfoProvider.getLastBlock currentBlock warning ' + + JSON.stringify(info) + ); + } + } catch (e) { + BlocksoftCryptoLog.log( + 'TrxNodeInfoProvider.getLastBlock currentBlock error ' + e.message + ); } + return CACHE_LAST_BLOCK; + } } diff --git a/crypto/blockchains/trx/basic/TrxTransactionsProvider.js b/crypto/blockchains/trx/basic/TrxTransactionsProvider.js index e045a70da..16f0ac4e1 100644 --- a/crypto/blockchains/trx/basic/TrxTransactionsProvider.js +++ b/crypto/blockchains/trx/basic/TrxTransactionsProvider.js @@ -1,209 +1,271 @@ /** * @version 0.5 */ -import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog' -import BlocksoftAxios from '../../../common/BlocksoftAxios' -import BlocksoftUtils from '../../../common/BlocksoftUtils' -import TrxNodeInfoProvider from './TrxNodeInfoProvider' -import TransactionFilterTypeDict from '@appV2/dicts/transactionFilterTypeDict' -import BlocksoftPrettyNumbers from '@crypto/common/BlocksoftPrettyNumbers' -import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings' +import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; +import BlocksoftAxios from '../../../common/BlocksoftAxios'; +import BlocksoftUtils from '../../../common/BlocksoftUtils'; +import TrxNodeInfoProvider from './TrxNodeInfoProvider'; +import TransactionFilterTypeDict from '@appV2/dicts/transactionFilterTypeDict'; +import BlocksoftPrettyNumbers from '@crypto/common/BlocksoftPrettyNumbers'; +import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings'; -const TXS_MAX_TRY = 10 +const TXS_MAX_TRY = 10; -const CACHE_OF_TRANSACTIONS = {} -const CACHE_VALID_TIME = 30000 // 30 seconds +const CACHE_OF_TRANSACTIONS = {}; +const CACHE_VALID_TIME = 30000; // 30 seconds export default class TrxTransactionsProvider { + /** + * @type {number} + * @private + */ + _lastBlock = 15850641; - /** - * @type {number} - * @private - */ - _lastBlock = 15850641 + /** + * @type {string} + * @private + */ + _tronscanLink = + 'https://api.tronscan.org/api/transaction?sort=-timestamp&count=true&limit=50&address='; - /** - * @type {string} - * @private - */ - _tronscanLink = 'https://api.tronscan.org/api/transaction?sort=-timestamp&count=true&limit=50&address=' + constructor() { + this._nodeInfo = new TrxNodeInfoProvider(); + } - constructor() { - this._nodeInfo = new TrxNodeInfoProvider() + /** + * @param scanData.account.address + * @param tokenName + * @returns {Promise} + */ + async get(scanData, tokenName) { + const address = scanData.account.address.trim(); + const now = new Date().getTime(); + if ( + typeof CACHE_OF_TRANSACTIONS[address] !== 'undefined' && + now - CACHE_OF_TRANSACTIONS[address].time < CACHE_VALID_TIME + ) { + if (typeof CACHE_OF_TRANSACTIONS[address][tokenName] !== 'undefined') { + BlocksoftCryptoLog.log( + ' TrxTransactionsProvider.get from cache', + address + ' => ' + tokenName + ); + return CACHE_OF_TRANSACTIONS[address][tokenName]; + } } - /** - * @param scanData.account.address - * @param tokenName - * @returns {Promise} - */ - async get(scanData, tokenName) { - const address = scanData.account.address.trim() - const now = new Date().getTime() - if (typeof CACHE_OF_TRANSACTIONS[address] !== 'undefined' && (now - CACHE_OF_TRANSACTIONS[address].time) < CACHE_VALID_TIME) { - if (typeof CACHE_OF_TRANSACTIONS[address][tokenName] !== 'undefined') { - BlocksoftCryptoLog.log(' TrxTransactionsProvider.get from cache', address + ' => ' + tokenName) - return CACHE_OF_TRANSACTIONS[address][tokenName] - } - } + const res = await BlocksoftAxios.getWithoutBraking( + this._tronscanLink + address, + TXS_MAX_TRY + ); - const res = await BlocksoftAxios.getWithoutBraking(this._tronscanLink + address, TXS_MAX_TRY) - - if (!res || !res.data || typeof res.data.data === 'undefined' || res.data.data.length === 0) return false - - this._lastBlock = await this._nodeInfo.getLastBlock() - - CACHE_OF_TRANSACTIONS[address] = {} - CACHE_OF_TRANSACTIONS[address].time = new Date().getTime() - CACHE_OF_TRANSACTIONS[address][tokenName] = [] - let tx - for (tx of res.data.data) { - let tmp = false - try { - tmp = await this._unifyTransaction(scanData, tx, tokenName) - } catch (e) { - BlocksoftCryptoLog.log('TrxTransactionsProvider.get unify error ' + e.message + ' tx ' + tx?.transactionHash) - } - if (!tmp) continue - - const transaction = tmp?.res - - let txTokenName = '_' - if (typeof tmp.txTokenName !== 'undefined' && tmp.txTokenName) { - txTokenName = tmp.txTokenName - } else if (typeof tx.contractData === 'undefined') { - txTokenName = tokenName - } else if (typeof tx.contractData.contract_address !== 'undefined') { - txTokenName = tx.contractData.contract_address - } else if (typeof tx.contractData.asset_name !== 'undefined') { - txTokenName = tx.contractData.asset_name - } - if (typeof CACHE_OF_TRANSACTIONS[address][txTokenName] === 'undefined') { - CACHE_OF_TRANSACTIONS[address][txTokenName] = [] - } - CACHE_OF_TRANSACTIONS[address][txTokenName].push(transaction) - } - return CACHE_OF_TRANSACTIONS[address][tokenName] + if ( + !res || + !res.data || + typeof res.data.data === 'undefined' || + res.data.data.length === 0 + ) + return false; + + this._lastBlock = await this._nodeInfo.getLastBlock(); + CACHE_OF_TRANSACTIONS[address] = {}; + CACHE_OF_TRANSACTIONS[address].time = new Date().getTime(); + CACHE_OF_TRANSACTIONS[address][tokenName] = []; + let tx; + for (tx of res.data.data) { + let tmp = false; + try { + tmp = await this._unifyTransaction(scanData, tx, tokenName); + } catch (e) { + BlocksoftCryptoLog.log( + 'TrxTransactionsProvider.get unify error ' + + e.message + + ' tx ' + + tx?.transactionHash + ); + } + if (!tmp) continue; + + const transaction = tmp?.res; + + let txTokenName = '_'; + if (typeof tmp.txTokenName !== 'undefined' && tmp.txTokenName) { + txTokenName = tmp.txTokenName; + } else if (typeof tx.contractData === 'undefined') { + txTokenName = tokenName; + } else if (typeof tx.contractData.contract_address !== 'undefined') { + txTokenName = tx.contractData.contract_address; + } else if (typeof tx.contractData.asset_name !== 'undefined') { + txTokenName = tx.contractData.asset_name; + } + if (typeof CACHE_OF_TRANSACTIONS[address][txTokenName] === 'undefined') { + CACHE_OF_TRANSACTIONS[address][txTokenName] = []; + } + CACHE_OF_TRANSACTIONS[address][txTokenName].push(transaction); } + return CACHE_OF_TRANSACTIONS[address][tokenName]; + } - /** - * @param {string} scanData.address.address - * @param {Object} transaction - * @param {string} transaction.amount 1000000 - * @param {string} transaction.ownerAddress 'TJcnzHwXiFvMsmGDwBstDmwQ5AWVWFPxTM' - * @param {string} transaction.data '' - * @param {string} transaction.contractData.amount '' - * @param {string} transaction.toAddress 'TGk5Nkv8gf7HShzLw7rHzJsQLzsALvPPnF' - * @param {string} transaction.block 14129705 - * @param {string} transaction.confirmed true - * @param {string} transaction.contractRet 'SUCCESS' - * @param {string} transaction.hash '74d0f84322b1ba1478ce3f272d7b4524563e5a44b1270325cc6cce7e600601e2' - * @param {string} transaction.timestamp 1572636390000 - * @return {UnifiedTransaction} - * @private - */ - async _unifyTransaction(scanData, transaction, tokenName) { - const address = scanData.account.address.trim() - let transactionStatus = 'new' - const now = new Date().getTime() - transaction.diffSeconds = Math.round((now - transaction.timestamp) / 1000) - if (transaction.confirmed) { - if (typeof transaction.contractRet === 'undefined') { - transactionStatus = 'success' - } else if (transaction.contractRet === 'SUCCESS') { - transactionStatus = 'success' - } else { - transactionStatus = 'fail' - } - } else if (transaction.block > 0) { - if (transaction.diffSeconds > 120) { - transactionStatus = 'fail' - } else { - transactionStatus = 'confirming' - } - } - if (transaction.block > this._lastBlock) { - this._lastBlock = transaction.block - } + /** + * @param {string} scanData.address.address + * @param {Object} transaction + * @param {string} transaction.amount 1000000 + * @param {string} transaction.ownerAddress 'TJcnzHwXiFvMsmGDwBstDmwQ5AWVWFPxTM' + * @param {string} transaction.data '' + * @param {string} transaction.contractData.amount '' + * @param {string} transaction.toAddress 'TGk5Nkv8gf7HShzLw7rHzJsQLzsALvPPnF' + * @param {string} transaction.block 14129705 + * @param {string} transaction.confirmed true + * @param {string} transaction.contractRet 'SUCCESS' + * @param {string} transaction.hash '74d0f84322b1ba1478ce3f272d7b4524563e5a44b1270325cc6cce7e600601e2' + * @param {string} transaction.timestamp 1572636390000 + * @return {UnifiedTransaction} + * @private + */ + async _unifyTransaction(scanData, transaction, tokenName) { + const address = scanData.account.address.trim(); + let transactionStatus = 'new'; + const now = new Date().getTime(); + transaction.diffSeconds = Math.round((now - transaction.timestamp) / 1000); + if (transaction.confirmed) { + if (typeof transaction.contractRet === 'undefined') { + transactionStatus = 'success'; + } else if (transaction.contractRet === 'SUCCESS') { + transactionStatus = 'success'; + } else { + transactionStatus = 'fail'; + } + } else if (transaction.block > 0) { + if (transaction.diffSeconds > 120) { + transactionStatus = 'fail'; + } else { + transactionStatus = 'confirming'; + } + } + if (transaction.block > this._lastBlock) { + this._lastBlock = transaction.block; + } - let blockConfirmations = this._lastBlock - transaction.block - if (blockConfirmations > 100 && transaction.diffSeconds < 600) { - blockConfirmations = transaction.diffSeconds - } + let blockConfirmations = this._lastBlock - transaction.block; + if (blockConfirmations > 100 && transaction.diffSeconds < 600) { + blockConfirmations = transaction.diffSeconds; + } - if (typeof transaction.timestamp === 'undefined') { - throw new Error(' no transaction.timeStamp error transaction data ' + JSON.stringify(transaction)) - } - let formattedTime = transaction.timestamp - try { - formattedTime = BlocksoftUtils.toDate(transaction.timestamp / 1000) - } catch (e) { - e.message += ' timestamp error transaction data ' + JSON.stringify(transaction) - throw e - } - let addressAmount = 0 - let transactionDirection = 'self' - let txTokenName = false - let addressFrom = (address.toLowerCase() === transaction.ownerAddress.toLowerCase()) ? '' : transaction.ownerAddress - let transactionFilterType = TransactionFilterTypeDict.USUAL - if (typeof transaction.contractData.amount === 'undefined') { - if (typeof transaction.contractData !== 'undefined' && typeof transaction.contractData.frozen_balance !== 'undefined') { - addressAmount = transaction.contractData.frozen_balance - transactionDirection = 'freeze' - transactionFilterType = TransactionFilterTypeDict.STAKE - } else if (typeof transaction.amount !== 'undefined' && typeof transaction.contractType !== 'undefined' && transaction.contractType === 13) { - addressAmount = transaction.amount - transactionDirection = 'claim' - transactionFilterType = TransactionFilterTypeDict.STAKE - } else if (typeof transaction.contractType !== 'undefined' && transaction.contractType === 12) { - addressAmount = transaction.amount - addressFrom = transaction.ownerAddress - transactionDirection = 'unfreeze' - transactionFilterType = TransactionFilterTypeDict.STAKE - } else if (typeof transaction.contractType !== 'undefined' && transaction.contractType === 4) { - // no vote tx - return false - addressAmount = BlocksoftPrettyNumbers.setCurrencyCode('TRX').makeUnPretty(transaction.amount) - addressFrom = transaction.ownerAddress - transactionDirection = 'vote' - transactionFilterType = TransactionFilterTypeDict.STAKE - } else { - if (transaction.contractType === 11 || transaction.contractType === 4 || transaction.contractType === 13) { - // freeze = 11, vote = 4, claim = 13 - } else { - // noinspection ES6MissingAwait - BlocksoftCryptoLog.log('TrxTransactionsProvider._unifyTransaction buggy tx ' + JSON.stringify(transaction)) - } - return false - } + if (typeof transaction.timestamp === 'undefined') { + throw new Error( + ' no transaction.timeStamp error transaction data ' + + JSON.stringify(transaction) + ); + } + let formattedTime = transaction.timestamp; + try { + formattedTime = BlocksoftUtils.toDate(transaction.timestamp / 1000); + } catch (e) { + e.message += + ' timestamp error transaction data ' + JSON.stringify(transaction); + throw e; + } + let addressAmount = 0; + let transactionDirection = 'self'; + let txTokenName = false; + let addressFrom = + address.toLowerCase() === transaction.ownerAddress.toLowerCase() + ? '' + : transaction.ownerAddress; + let transactionFilterType = TransactionFilterTypeDict.USUAL; + if (typeof transaction.contractData.amount === 'undefined') { + if ( + typeof transaction.contractData !== 'undefined' && + typeof transaction.contractData.frozen_balance !== 'undefined' + ) { + addressAmount = transaction.contractData.frozen_balance; + transactionDirection = 'freeze'; + transactionFilterType = TransactionFilterTypeDict.STAKE; + } else if ( + typeof transaction.amount !== 'undefined' && + typeof transaction.contractType !== 'undefined' && + transaction.contractType === 13 + ) { + addressAmount = transaction.amount; + transactionDirection = 'claim'; + transactionFilterType = TransactionFilterTypeDict.STAKE; + } else if ( + typeof transaction.contractType !== 'undefined' && + transaction.contractType === 12 + ) { + addressAmount = transaction.amount; + addressFrom = transaction.ownerAddress; + transactionDirection = 'unfreeze'; + transactionFilterType = TransactionFilterTypeDict.STAKE; + } else if ( + typeof transaction.contractType !== 'undefined' && + transaction.contractType === 4 + ) { + // no vote tx + return false; + addressAmount = BlocksoftPrettyNumbers.setCurrencyCode( + 'TRX' + ).makeUnPretty(transaction.amount); + addressFrom = transaction.ownerAddress; + transactionDirection = 'vote'; + transactionFilterType = TransactionFilterTypeDict.STAKE; + } else { + if ( + transaction.contractType === 11 || + transaction.contractType === 4 || + transaction.contractType === 13 + ) { + // freeze = 11, vote = 4, claim = 13 } else { - addressAmount = transaction.contractData.amount - transactionDirection = (address.toLowerCase() === transaction.ownerAddress.toLowerCase()) ? 'outcome' : 'income' - } - let transactionFee = 0 - if (typeof transaction.cost !== 'undefined' && typeof transaction.cost.fee !== 'undefined' && transaction.cost.fee) { - transactionFee = transaction.cost.fee * 1 - } - const res = { - transactionHash: transaction.hash, - blockHash: '', - blockNumber: transaction.block, - blockTime: formattedTime, - blockConfirmations: blockConfirmations, - transactionDirection, - addressFrom, - addressTo: (address.toLowerCase() === transaction.toAddress.toLowerCase()) ? '' : transaction.toAddress, - addressAmount, - transactionStatus, - transactionFee, - transactionFilterType, - inputValue: transaction.data + // noinspection ES6MissingAwait + BlocksoftCryptoLog.log( + 'TrxTransactionsProvider._unifyTransaction buggy tx ' + + JSON.stringify(transaction) + ); } - if (!res.addressTo && (!res.addressFrom || res.addressFrom.toLowerCase() === address.toLowerCase())) { - return false - } - - return { res, txTokenName } + return false; + } + } else { + addressAmount = transaction.contractData.amount; + transactionDirection = + address.toLowerCase() === transaction.ownerAddress.toLowerCase() + ? 'outcome' + : 'income'; + } + let transactionFee = 0; + if ( + typeof transaction.cost !== 'undefined' && + typeof transaction.cost.fee !== 'undefined' && + transaction.cost.fee + ) { + transactionFee = transaction.cost.fee * 1; } + const res = { + transactionHash: transaction.hash, + blockHash: '', + blockNumber: transaction.block, + blockTime: formattedTime, + blockConfirmations: blockConfirmations, + transactionDirection, + addressFrom, + addressTo: + address.toLowerCase() === transaction.toAddress.toLowerCase() + ? '' + : transaction.toAddress, + addressAmount, + transactionStatus, + transactionFee, + transactionFilterType, + inputValue: transaction.data + }; + if ( + !res.addressTo && + (!res.addressFrom || + res.addressFrom.toLowerCase() === address.toLowerCase()) + ) { + return false; + } + + return { res, txTokenName }; + } } diff --git a/crypto/blockchains/trx/basic/TrxTransactionsTrc20Provider.js b/crypto/blockchains/trx/basic/TrxTransactionsTrc20Provider.js index 378218cc6..c17eb5da2 100644 --- a/crypto/blockchains/trx/basic/TrxTransactionsTrc20Provider.js +++ b/crypto/blockchains/trx/basic/TrxTransactionsTrc20Provider.js @@ -2,159 +2,194 @@ * @version 0.5 * https://github.com/tronscan/tronscan-frontend/wiki/TRONSCAN-API */ -import TrxTransactionsProvider from './TrxTransactionsProvider' -import BlocksoftUtils from '../../../common/BlocksoftUtils' -import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog' -import BlocksoftAxios from '@crypto/common/BlocksoftAxios' -import Database from '@app/appstores/DataSource/Database/main' -import TransactionFilterTypeDict from '@appV2/dicts/transactionFilterTypeDict' +import TrxTransactionsProvider from './TrxTransactionsProvider'; +import BlocksoftUtils from '../../../common/BlocksoftUtils'; +import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; +import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; +import Database from '@app/appstores/DataSource/Database/main'; +import TransactionFilterTypeDict from '@appV2/dicts/transactionFilterTypeDict'; -const SWAPS = require('../dict/swaps') +const SWAPS = require('../dict/swaps'); export default class TrxTransactionsTrc20Provider extends TrxTransactionsProvider { - _token = false + _token = false; - setLink(token) { - this._token = token - this._tronscanLink = 'https://apilist.tronscan.org/api/contract/events?sort=-timestamp&count=true&limit=50&contract=' + token + '&address=' - } + setLink(token) { + this._token = token; + this._tronscanLink = + 'https://apilist.tronscan.org/api/contract/events?sort=-timestamp&count=true&limit=50&contract=' + + token + + '&address='; + } - /** - * @param {string} scanData.account.address - * @param {Object} transaction - * @param {string} transaction.amount 1000000 - * @param {string} transaction.transferFromAddress 'TUbHxAdhPk9ykkc7SDP5e9zUBEN14K65wk' - * @param {string} transaction.data '' - * @param {string} transaction.decimals 6 - * @param {string} transaction.tokenName 'Tether USD' - * @param {string} transaction.transferToAddress 'TUoyiQH9wSfYdJRhsXtgmgDvpWipPrQN8a' - * @param {string} transaction.block 15847100 - * @param {string} transaction.id '' - * @param {string} transaction.confirmed true - * @param {string} transaction.transactionHash '4999b0965c1a5b17cbaa862b9357a32c9b8d096e170f4eecee929159b0b73ad3' - * @param {string} transaction.timestamp: 1577796345000 - * @return {UnifiedTransaction} - * @private - */ - async _unifyTransaction(scanData, transaction) { - const address = scanData.account.address.trim() - let transactionStatus = 'new' - if (transaction.confirmed) { - transactionStatus = 'success' - } else if (transaction.block > 0) { - transactionStatus = 'fail' - } + /** + * @param {string} scanData.account.address + * @param {Object} transaction + * @param {string} transaction.amount 1000000 + * @param {string} transaction.transferFromAddress 'TUbHxAdhPk9ykkc7SDP5e9zUBEN14K65wk' + * @param {string} transaction.data '' + * @param {string} transaction.decimals 6 + * @param {string} transaction.tokenName 'Tether USD' + * @param {string} transaction.transferToAddress 'TUoyiQH9wSfYdJRhsXtgmgDvpWipPrQN8a' + * @param {string} transaction.block 15847100 + * @param {string} transaction.id '' + * @param {string} transaction.confirmed true + * @param {string} transaction.transactionHash '4999b0965c1a5b17cbaa862b9357a32c9b8d096e170f4eecee929159b0b73ad3' + * @param {string} transaction.timestamp: 1577796345000 + * @return {UnifiedTransaction} + * @private + */ + async _unifyTransaction(scanData, transaction) { + const address = scanData.account.address.trim(); + let transactionStatus = 'new'; + if (transaction.confirmed) { + transactionStatus = 'success'; + } else if (transaction.block > 0) { + transactionStatus = 'fail'; + } - let txTokenName = false - let formattedTime - try { - formattedTime = BlocksoftUtils.toDate(transaction.timestamp / 1000) - } catch (e) { - e.message += ' timestamp error transaction data ' + JSON.stringify(transaction) - throw e - } - if (typeof transaction.amount === 'undefined') { - // noinspection ES6MissingAwait - BlocksoftCryptoLog.err('TrxTransactionsTrc20Provider._unifyTransaction buggy tx ' + JSON.stringify(transaction)) - } + let txTokenName = false; + let formattedTime; + try { + formattedTime = BlocksoftUtils.toDate(transaction.timestamp / 1000); + } catch (e) { + e.message += + ' timestamp error transaction data ' + JSON.stringify(transaction); + throw e; + } + if (typeof transaction.amount === 'undefined') { + // noinspection ES6MissingAwait + BlocksoftCryptoLog.err( + 'TrxTransactionsTrc20Provider._unifyTransaction buggy tx ' + + JSON.stringify(transaction) + ); + } - const res = { - transactionHash: transaction.transactionHash, - blockHash: '', - blockNumber: transaction.block, - blockTime: formattedTime, - blockConfirmations: this._lastBlock - transaction.block, - transactionDirection: (address.toLowerCase() === transaction.transferFromAddress.toLowerCase()) ? 'outcome' : 'income', - addressFrom: (address.toLowerCase() === transaction.transferFromAddress.toLowerCase()) ? '' : transaction.transferFromAddress, - addressTo: (address.toLowerCase() === transaction.transferToAddress.toLowerCase()) ? '' : transaction.transferToAddress, - addressAmount: typeof transaction.amount !== 'undefined' ? transaction.amount.toString() : '0', - transactionStatus: transactionStatus, - transactionFee: 0, - inputValue: transaction.data - } + const res = { + transactionHash: transaction.transactionHash, + blockHash: '', + blockNumber: transaction.block, + blockTime: formattedTime, + blockConfirmations: this._lastBlock - transaction.block, + transactionDirection: + address.toLowerCase() === transaction.transferFromAddress.toLowerCase() + ? 'outcome' + : 'income', + addressFrom: + address.toLowerCase() === transaction.transferFromAddress.toLowerCase() + ? '' + : transaction.transferFromAddress, + addressTo: + address.toLowerCase() === transaction.transferToAddress.toLowerCase() + ? '' + : transaction.transferToAddress, + addressAmount: + typeof transaction.amount !== 'undefined' + ? transaction.amount.toString() + : '0', + transactionStatus: transactionStatus, + transactionFee: 0, + inputValue: transaction.data + }; - let needData = false - if (res.addressAmount.indexOf('115792089237316195423570985008687907853269984665640564039457') === 0) { - res.addressAmount = '0' - needData = true - } - if (typeof SWAPS[res.addressTo] !== 'undefined') { - res.addressTo = SWAPS[res.addressTo] - res.transactionDirection = 'swap_outcome' - res.addressAmount = '0' - needData = true - } else if (typeof SWAPS[res.addressFrom] !== 'undefined') { - res.addressFrom = SWAPS[res.addressFrom] - res.transactionDirection = 'swap_income' - res.addressAmount = '0' - needData = true - } else if (res.transactionDirection === 'outcome') { - needData = true - } + let needData = false; + if ( + res.addressAmount.indexOf( + '115792089237316195423570985008687907853269984665640564039457' + ) === 0 + ) { + res.addressAmount = '0'; + needData = true; + } + if (typeof SWAPS[res.addressTo] !== 'undefined') { + res.addressTo = SWAPS[res.addressTo]; + res.transactionDirection = 'swap_outcome'; + res.addressAmount = '0'; + needData = true; + } else if (typeof SWAPS[res.addressFrom] !== 'undefined') { + res.addressFrom = SWAPS[res.addressFrom]; + res.transactionDirection = 'swap_income'; + res.addressAmount = '0'; + needData = true; + } else if (res.transactionDirection === 'outcome') { + needData = true; + } + if (needData) { + const diff = + scanData.account.transactionsScanTime - transaction.timestamp / 1000; + if (diff > 6000) { + return false; + } + } + if (needData) { + const tmp = await BlocksoftAxios.get( + 'https://apilist.tronscan.org/api/transaction-info?hash=' + + res.transactionHash + ); + res.transactionFee = tmp.data.cost.fee * 1 + tmp.data.cost.energy_fee * 1; - if (needData) { - const diff = scanData.account.transactionsScanTime - transaction.timestamp / 1000 - if (diff > 6000) { - return false - } + if (res.transactionFee * 1 > 0 && res.addressAmount * 1 > 0) { + const savedTRX = await Database.query( + ` SELECT * FROM transactions WHERE transaction_hash='${res.transactionHash}' AND currency_code='TRX' ` + ); + if (!savedTRX || !savedTRX.array || savedTRX.array.length === 0) { + BlocksoftCryptoLog.log( + 'TrxTransactionsTrc20Provider._unifyTransaction added fee for ' + + res.transactionHash + + ' amount ' + + res.addressAmount + + ' fee ' + + res.transactionFee + ); + const saveFee = { + account_id: 0, + address_amount: 0, + address_from: res.addressFrom, + address_to: res.addressTo, + block_confirmations: res.blockConfirmations, + block_number: res.blockNumber, + block_time: res.blockTime, + created_at: res.blockTime, + currency_code: 'TRX', + mined_at: res.blockTime, + transaction_direction: res.transactionDirection, + transaction_fee: res.transactionFee, + transaction_filter_type: TransactionFilterTypeDict.FEE, + transaction_hash: res.transactionHash, + transaction_status: res.transactionStatus, + transactions_scan_time: new Date().getTime(), + wallet_hash: scanData.account.walletHash + }; + await Database.setTableName('transactions') + .setInsertData({ insertObjs: [saveFee] }) + .insert(); } - - - if (needData) { - const tmp = await BlocksoftAxios.get('https://apilist.tronscan.org/api/transaction-info?hash=' + res.transactionHash) - res.transactionFee = tmp.data.cost.fee * 1 + tmp.data.cost.energy_fee * 1 - - if (res.transactionFee * 1 > 0 && res.addressAmount * 1 > 0) { - const savedTRX = await Database.query(` SELECT * FROM transactions WHERE transaction_hash='${res.transactionHash}' AND currency_code='TRX' `) - if (!savedTRX || !savedTRX.array || savedTRX.array.length === 0) { - BlocksoftCryptoLog.log('TrxTransactionsTrc20Provider._unifyTransaction added fee for ' + res.transactionHash + ' amount ' + res.addressAmount + ' fee ' + res.transactionFee) - const saveFee = { - 'account_id': 0, - 'address_amount': 0, - 'address_from': res.addressFrom, - 'address_to': res.addressTo, - 'block_confirmations': res.blockConfirmations, - 'block_number': res.blockNumber, - 'block_time': res.blockTime, - 'created_at': res.blockTime, - 'currency_code': 'TRX', - 'mined_at': res.blockTime, - 'transaction_direction': res.transactionDirection, - 'transaction_fee': res.transactionFee, - 'transaction_filter_type': TransactionFilterTypeDict.FEE, - 'transaction_hash': res.transactionHash, - 'transaction_status': res.transactionStatus, - 'transactions_scan_time': new Date().getTime(), - 'wallet_hash': scanData.account.walletHash - } - await Database.setTableName('transactions').setInsertData({ insertObjs: [saveFee] }).insert() - } - } - if (typeof tmp.data.trc20TransferInfo !== 'undefined') { - for (const info of tmp.data.trc20TransferInfo) { - if (info.contract_address !== this._token) continue - if (info.from_address === address) { - if (info.to_address === address) { - res.transactionDirection = 'self' - } else { - res.transactionDirection = 'outcome' - } - } else if (info.to_address === address) { - res.transactionDirection = 'income' - res.addressAmount = info.amount_str - } else { - continue - } - } - } - if (res.transactionFee * 1 === 0 || res.addressAmount * 1 === 0) { - return false + } + if (typeof tmp.data.trc20TransferInfo !== 'undefined') { + for (const info of tmp.data.trc20TransferInfo) { + if (info.contract_address !== this._token) continue; + if (info.from_address === address) { + if (info.to_address === address) { + res.transactionDirection = 'self'; + } else { + res.transactionDirection = 'outcome'; } - } else if (res.addressAmount * 1 === 0) { - return false + } else if (info.to_address === address) { + res.transactionDirection = 'income'; + res.addressAmount = info.amount_str; + } else { + continue; + } } - - return { res, txTokenName } + } + if (res.transactionFee * 1 === 0 || res.addressAmount * 1 === 0) { + return false; + } + } else if (res.addressAmount * 1 === 0) { + return false; } + + return { res, txTokenName }; + } } diff --git a/crypto/blockchains/trx/basic/TrxTrongridProvider.js b/crypto/blockchains/trx/basic/TrxTrongridProvider.js index d00ab3237..d9a447e69 100644 --- a/crypto/blockchains/trx/basic/TrxTrongridProvider.js +++ b/crypto/blockchains/trx/basic/TrxTrongridProvider.js @@ -2,182 +2,287 @@ * @version 0.5 * https://github.com/tronscan/tronscan-frontend/wiki/TRONSCAN-API */ -import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog' -import BlocksoftAxios from '../../../common/BlocksoftAxios' -import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings' -import TronUtils from '@crypto/blockchains/trx/ext/TronUtils' +import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; +import BlocksoftAxios from '../../../common/BlocksoftAxios'; +import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings'; +import TronUtils from '@crypto/blockchains/trx/ext/TronUtils'; -const BALANCE_MAX_TRY = 10 +const BALANCE_MAX_TRY = 10; -const CACHE_TRONGRID = {} -const CACHE_VALID_TIME = 3000 // 3 seconds +const CACHE_TRONGRID = {}; +const CACHE_VALID_TIME = 3000; // 3 seconds export default class TrxTrongridProvider { - - async isMultisigTrongrid(address) { - if (typeof CACHE_TRONGRID[address] !== 'undefined') { - return CACHE_TRONGRID[address].isMultisig - } - const res = await this.get(address, '_', true) - return res?.isMultisig || false + async isMultisigTrongrid(address) { + if (typeof CACHE_TRONGRID[address] !== 'undefined') { + return CACHE_TRONGRID[address].isMultisig; } + const res = await this.get(address, '_', true); + return res?.isMultisig || false; + } - /** - * https://api.trongrid.io/walletsolidity/getaccount?address=41d4eead2ea047881ce54cae1a765dfe92a8bfdbe9 - * @param {string} address - * @param {string} tokenName - * @returns {Promise} - */ - async get(address, tokenName, useCache = true) { - const now = new Date().getTime() - if (useCache && typeof CACHE_TRONGRID[address] !== 'undefined' && (now - CACHE_TRONGRID[address].time) < CACHE_VALID_TIME) { - if (typeof CACHE_TRONGRID[address][tokenName] !== 'undefined') { - BlocksoftCryptoLog.log('TrxTrongridProvider.get from cache', address + ' => ' + tokenName + ' : ' + CACHE_TRONGRID[address][tokenName]) - const voteTotal = typeof CACHE_TRONGRID[address].voteTotal !== 'undefined' ? CACHE_TRONGRID[address].voteTotal : 0 - const frozen = typeof CACHE_TRONGRID[address][tokenName + 'frozen'] !== 'undefined' ? CACHE_TRONGRID[address][tokenName + 'frozen'] : 0 - const frozenExpireTime = typeof CACHE_TRONGRID[address][tokenName + 'frozenExpireTime'] !== 'undefined' ? CACHE_TRONGRID[address][tokenName + 'frozenExpireTime'] : 0 - const frozenOthers = typeof CACHE_TRONGRID[address][tokenName + 'frozenOthers'] !== 'undefined' ? CACHE_TRONGRID[address][tokenName + 'frozenOthers'] : 0 - const frozenEnergy = typeof CACHE_TRONGRID[address][tokenName + 'frozenEnergy'] !== 'undefined' ? CACHE_TRONGRID[address][tokenName + 'frozenEnergy'] : 0 - const frozenEnergyExpireTime = typeof CACHE_TRONGRID[address][tokenName + 'frozenEnergyExpireTime'] !== 'undefined' ? CACHE_TRONGRID[address][tokenName + 'frozenEnergyExpireTime'] : 0 - const frozenEnergyOthers = typeof CACHE_TRONGRID[address][tokenName + 'frozenEnergyOthers'] !== 'undefined' ? CACHE_TRONGRID[address][tokenName + 'frozenEnergyOthers'] : 0 - return { - isMultisig: CACHE_TRONGRID[address].isMultisig, - balance: CACHE_TRONGRID[address][tokenName], - voteTotal, - frozen, - frozenExpireTime, - frozenOthers, - frozenEnergy, - frozenEnergyExpireTime, - frozenEnergyOthers, - unconfirmed: 0, - provider: 'trongrid-cache', - time: CACHE_TRONGRID[address].time - } - } else if (tokenName !== '_') { - return false - // return { balance: 0, unconfirmed : 0, provider: 'trongrid-cache' } - } - } - - // curl -X POST http://trx.trusteeglobal.com:8091/walletsolidity/getassetissuebyname -d - const nodeLink = BlocksoftExternalSettings.getStatic('TRX_SOLIDITY_NODE') - const link = nodeLink + '/walletsolidity/getaccount' - const params = { address } - BlocksoftCryptoLog.log('TrxTrongridProvider.get ' + link + ' ' + JSON.stringify(params)) - const res = await BlocksoftAxios.postWithoutBraking(link, params, BALANCE_MAX_TRY) - if (!res || !res.data) { - return false - } - - let isMultisig = false - if (res.data.active_permission) { - for (const perm of res.data.active_permission) { - if (perm.keys[0].address !== address) { - isMultisig = TronUtils.addressHexToStr(perm.keys[0].address) - } - } - } - + /** + * https://api.trongrid.io/walletsolidity/getaccount?address=41d4eead2ea047881ce54cae1a765dfe92a8bfdbe9 + * @param {string} address + * @param {string} tokenName + * @returns {Promise} + */ + async get(address, tokenName, useCache = true) { + const now = new Date().getTime(); + if ( + useCache && + typeof CACHE_TRONGRID[address] !== 'undefined' && + now - CACHE_TRONGRID[address].time < CACHE_VALID_TIME + ) { + if (typeof CACHE_TRONGRID[address][tokenName] !== 'undefined') { + BlocksoftCryptoLog.log( + 'TrxTrongridProvider.get from cache', + address + + ' => ' + + tokenName + + ' : ' + + CACHE_TRONGRID[address][tokenName] + ); + const voteTotal = + typeof CACHE_TRONGRID[address].voteTotal !== 'undefined' + ? CACHE_TRONGRID[address].voteTotal + : 0; + const frozen = + typeof CACHE_TRONGRID[address][tokenName + 'frozen'] !== 'undefined' + ? CACHE_TRONGRID[address][tokenName + 'frozen'] + : 0; + const frozenExpireTime = + typeof CACHE_TRONGRID[address][tokenName + 'frozenExpireTime'] !== + 'undefined' + ? CACHE_TRONGRID[address][tokenName + 'frozenExpireTime'] + : 0; + const frozenOthers = + typeof CACHE_TRONGRID[address][tokenName + 'frozenOthers'] !== + 'undefined' + ? CACHE_TRONGRID[address][tokenName + 'frozenOthers'] + : 0; + const frozenEnergy = + typeof CACHE_TRONGRID[address][tokenName + 'frozenEnergy'] !== + 'undefined' + ? CACHE_TRONGRID[address][tokenName + 'frozenEnergy'] + : 0; + const frozenEnergyExpireTime = + typeof CACHE_TRONGRID[address][ + tokenName + 'frozenEnergyExpireTime' + ] !== 'undefined' + ? CACHE_TRONGRID[address][tokenName + 'frozenEnergyExpireTime'] + : 0; + const frozenEnergyOthers = + typeof CACHE_TRONGRID[address][tokenName + 'frozenEnergyOthers'] !== + 'undefined' + ? CACHE_TRONGRID[address][tokenName + 'frozenEnergyOthers'] + : 0; + return { + isMultisig: CACHE_TRONGRID[address].isMultisig, + balance: CACHE_TRONGRID[address][tokenName], + voteTotal, + frozen, + frozenExpireTime, + frozenOthers, + frozenEnergy, + frozenEnergyExpireTime, + frozenEnergyOthers, + unconfirmed: 0, + provider: 'trongrid-cache', + time: CACHE_TRONGRID[address].time + }; + } else if (tokenName !== '_') { + return false; + // return { balance: 0, unconfirmed : 0, provider: 'trongrid-cache' } + } + } - CACHE_TRONGRID[address] = {} - CACHE_TRONGRID[address].time = now - CACHE_TRONGRID[address]._ = typeof res.data.balance !== 'undefined' ? res.data.balance : 0 - CACHE_TRONGRID[address].isMultisig = isMultisig - CACHE_TRONGRID[address]._frozen = typeof res.data.frozen !== 'undefined' && typeof res.data.frozen[0] !== 'undefined' ? res.data.frozen[0].frozen_balance : 0 - CACHE_TRONGRID[address]._frozenExpireTime = typeof res.data.frozen !== 'undefined' && typeof res.data.frozen[0] !== 'undefined' ? res.data.frozen[0].expire_time : 0 - CACHE_TRONGRID[address]._frozenOthers = typeof res.data.delegated_frozen_balance_for_bandwidth !== 'undefined' ? res.data.delegated_frozen_balance_for_bandwidth : 0 - CACHE_TRONGRID[address]._frozenEnergy = typeof res.data.account_resource !== 'undefined' - && typeof res.data.account_resource.frozen_balance_for_energy !== 'undefined' - && typeof res.data.account_resource.frozen_balance_for_energy.frozen_balance !== 'undefined' - ? res.data.account_resource.frozen_balance_for_energy.frozen_balance : 0 - CACHE_TRONGRID[address]._frozenEnergyExpireTime = typeof res.data.account_resource !== 'undefined' - && typeof res.data.account_resource.frozen_balance_for_energy !== 'undefined' - && typeof res.data.account_resource.frozen_balance_for_energy.expire_time !== 'undefined' - ? res.data.account_resource.frozen_balance_for_energy.expire_time : 0 + // curl -X POST http://trx.trusteeglobal.com:8091/walletsolidity/getassetissuebyname -d + const nodeLink = BlocksoftExternalSettings.getStatic('TRX_SOLIDITY_NODE'); + const link = nodeLink + '/walletsolidity/getaccount'; + const params = { address }; + BlocksoftCryptoLog.log( + 'TrxTrongridProvider.get ' + link + ' ' + JSON.stringify(params) + ); + const res = await BlocksoftAxios.postWithoutBraking( + link, + params, + BALANCE_MAX_TRY + ); + if (!res || !res.data) { + return false; + } - CACHE_TRONGRID[address]._frozenEnergyOthers = 0 - if (typeof res.data.account_resource !== 'undefined' && typeof res.data.account_resource.delegated_frozen_balance_for_energy !== 'undefined' && res.data.account_resource.delegated_frozen_balance_for_energy * 1 > 0) { - CACHE_TRONGRID[address]._frozenEnergyOthers = res.data.account_resource.delegated_frozen_balance_for_energy * 1 + let isMultisig = false; + if (res.data.active_permission) { + for (const perm of res.data.active_permission) { + if (perm.keys[0].address !== address) { + isMultisig = TronUtils.addressHexToStr(perm.keys[0].address); } - CACHE_TRONGRID[address].voteTotal = typeof res.data.votes !== 'undefined' && typeof res.data.votes[0] !== 'undefined' ? res.data.votes[0].vote_count : 0 + } + } - if (res.data.assetV2) { - let token - for (token of res.data.assetV2) { - CACHE_TRONGRID[address][token.key] = token.value - } - } + CACHE_TRONGRID[address] = {}; + CACHE_TRONGRID[address].time = now; + CACHE_TRONGRID[address]._ = + typeof res.data.balance !== 'undefined' ? res.data.balance : 0; + CACHE_TRONGRID[address].isMultisig = isMultisig; + CACHE_TRONGRID[address]._frozen = + typeof res.data.frozen !== 'undefined' && + typeof res.data.frozen[0] !== 'undefined' + ? res.data.frozen[0].frozen_balance + : 0; + CACHE_TRONGRID[address]._frozenExpireTime = + typeof res.data.frozen !== 'undefined' && + typeof res.data.frozen[0] !== 'undefined' + ? res.data.frozen[0].expire_time + : 0; + CACHE_TRONGRID[address]._frozenOthers = + typeof res.data.delegated_frozen_balance_for_bandwidth !== 'undefined' + ? res.data.delegated_frozen_balance_for_bandwidth + : 0; + CACHE_TRONGRID[address]._frozenEnergy = + typeof res.data.account_resource !== 'undefined' && + typeof res.data.account_resource.frozen_balance_for_energy !== + 'undefined' && + typeof res.data.account_resource.frozen_balance_for_energy + .frozen_balance !== 'undefined' + ? res.data.account_resource.frozen_balance_for_energy.frozen_balance + : 0; + CACHE_TRONGRID[address]._frozenEnergyExpireTime = + typeof res.data.account_resource !== 'undefined' && + typeof res.data.account_resource.frozen_balance_for_energy !== + 'undefined' && + typeof res.data.account_resource.frozen_balance_for_energy.expire_time !== + 'undefined' + ? res.data.account_resource.frozen_balance_for_energy.expire_time + : 0; - if (typeof CACHE_TRONGRID[address][tokenName] === 'undefined') { - return false - // return { balance: 0, unconfirmed : 0, provider: 'trongrid' } - } + CACHE_TRONGRID[address]._frozenEnergyOthers = 0; + if ( + typeof res.data.account_resource !== 'undefined' && + typeof res.data.account_resource.delegated_frozen_balance_for_energy !== + 'undefined' && + res.data.account_resource.delegated_frozen_balance_for_energy * 1 > 0 + ) { + CACHE_TRONGRID[address]._frozenEnergyOthers = + res.data.account_resource.delegated_frozen_balance_for_energy * 1; + } + CACHE_TRONGRID[address].voteTotal = + typeof res.data.votes !== 'undefined' && + typeof res.data.votes[0] !== 'undefined' + ? res.data.votes[0].vote_count + : 0; - const balance = CACHE_TRONGRID[address][tokenName] - const frozen = typeof CACHE_TRONGRID[address][tokenName + 'frozen'] !== 'undefined' ? CACHE_TRONGRID[address][tokenName + 'frozen'] : 0 - const frozenExpireTime = typeof CACHE_TRONGRID[address][tokenName + 'frozenExpireTime'] !== 'undefined' ? CACHE_TRONGRID[address][tokenName + 'frozenExpireTime'] : 0 - const frozenOthers = typeof CACHE_TRONGRID[address][tokenName + 'frozenOthers'] !== 'undefined' ? CACHE_TRONGRID[address][tokenName + 'frozenOthers'] : 0 - const frozenEnergy = typeof CACHE_TRONGRID[address][tokenName + 'frozenEnergy'] !== 'undefined' ? CACHE_TRONGRID[address][tokenName + 'frozenEnergy'] : 0 - const frozenEnergyExpireTime = typeof CACHE_TRONGRID[address][tokenName + 'frozenEnergyExpireTime'] !== 'undefined' ? CACHE_TRONGRID[address][tokenName + 'frozenEnergyExpireTime'] : 0 - const frozenEnergyOthers = typeof CACHE_TRONGRID[address][tokenName + 'frozenEnergy'] !== 'undefined' ? CACHE_TRONGRID[address][tokenName + 'frozenEnergyOthers'] : 0 - const voteTotal = typeof CACHE_TRONGRID[address].voteTotal !== 'undefined' ? CACHE_TRONGRID[address].voteTotal : 0 - return { - isMultisig: CACHE_TRONGRID[address].isMultisig, - balance, - voteTotal, - frozen, - frozenExpireTime, - frozenOthers, - frozenEnergy, - frozenEnergyExpireTime, - frozenEnergyOthers, - unconfirmed: 0, - provider: 'trongrid ' + nodeLink, - time: CACHE_TRONGRID[address].time - } + if (res.data.assetV2) { + let token; + for (token of res.data.assetV2) { + CACHE_TRONGRID[address][token.key] = token.value; + } } - async getResources(address) { - const sendLink = BlocksoftExternalSettings.getStatic('TRX_SEND_LINK') - const link = sendLink + '/wallet/getaccountresource' - let leftBand = false - let totalBand = false - let leftEnergy = false - let totalEnergy = false - try { - const res = await BlocksoftAxios.post(link, { address }) - const tronData = res.data - delete tronData.assetNetUsed - delete tronData.assetNetLimit - await BlocksoftCryptoLog.log('TrxTrongridProvider.assets result ' + link + ' from ' + address, tronData) - totalBand = typeof tronData.freeNetLimit !== 'undefined' && tronData.freeNetLimit ? tronData.freeNetLimit : 0 - if (typeof tronData.NetLimit !== 'undefined' && tronData.NetLimit && tronData.NetLimit * 1 > 0) { - totalBand = totalBand * 1 + tronData.NetLimit * 1 - } + if (typeof CACHE_TRONGRID[address][tokenName] === 'undefined') { + return false; + // return { balance: 0, unconfirmed : 0, provider: 'trongrid' } + } - leftBand = totalBand - if (typeof tronData.freeNetUsed !== 'undefined' && tronData.freeNetUsed) { - leftBand = leftBand - tronData.freeNetUsed * 1 - } - if (typeof tronData.NetUsed !== 'undefined' && tronData.NetUsed) { - leftBand = leftBand - tronData.NetUsed * 1 - } + const balance = CACHE_TRONGRID[address][tokenName]; + const frozen = + typeof CACHE_TRONGRID[address][tokenName + 'frozen'] !== 'undefined' + ? CACHE_TRONGRID[address][tokenName + 'frozen'] + : 0; + const frozenExpireTime = + typeof CACHE_TRONGRID[address][tokenName + 'frozenExpireTime'] !== + 'undefined' + ? CACHE_TRONGRID[address][tokenName + 'frozenExpireTime'] + : 0; + const frozenOthers = + typeof CACHE_TRONGRID[address][tokenName + 'frozenOthers'] !== 'undefined' + ? CACHE_TRONGRID[address][tokenName + 'frozenOthers'] + : 0; + const frozenEnergy = + typeof CACHE_TRONGRID[address][tokenName + 'frozenEnergy'] !== 'undefined' + ? CACHE_TRONGRID[address][tokenName + 'frozenEnergy'] + : 0; + const frozenEnergyExpireTime = + typeof CACHE_TRONGRID[address][tokenName + 'frozenEnergyExpireTime'] !== + 'undefined' + ? CACHE_TRONGRID[address][tokenName + 'frozenEnergyExpireTime'] + : 0; + const frozenEnergyOthers = + typeof CACHE_TRONGRID[address][tokenName + 'frozenEnergy'] !== 'undefined' + ? CACHE_TRONGRID[address][tokenName + 'frozenEnergyOthers'] + : 0; + const voteTotal = + typeof CACHE_TRONGRID[address].voteTotal !== 'undefined' + ? CACHE_TRONGRID[address].voteTotal + : 0; + return { + isMultisig: CACHE_TRONGRID[address].isMultisig, + balance, + voteTotal, + frozen, + frozenExpireTime, + frozenOthers, + frozenEnergy, + frozenEnergyExpireTime, + frozenEnergyOthers, + unconfirmed: 0, + provider: 'trongrid ' + nodeLink, + time: CACHE_TRONGRID[address].time + }; + } - totalEnergy = typeof tronData.EnergyLimit !== 'undefined' && tronData.EnergyLimit ? tronData.EnergyLimit : 0 - leftEnergy = totalEnergy - if (typeof tronData.EnergyUsed !== 'undefined' && tronData.EnergyUsed) { - leftEnergy = leftEnergy - tronData.EnergyUsed * 1 - } + async getResources(address) { + const sendLink = BlocksoftExternalSettings.getStatic('TRX_SEND_LINK'); + const link = sendLink + '/wallet/getaccountresource'; + let leftBand = false; + let totalBand = false; + let leftEnergy = false; + let totalEnergy = false; + try { + const res = await BlocksoftAxios.post(link, { address }); + const tronData = res.data; + delete tronData.assetNetUsed; + delete tronData.assetNetLimit; + await BlocksoftCryptoLog.log( + 'TrxTrongridProvider.assets result ' + link + ' from ' + address, + tronData + ); + totalBand = + typeof tronData.freeNetLimit !== 'undefined' && tronData.freeNetLimit + ? tronData.freeNetLimit + : 0; + if ( + typeof tronData.NetLimit !== 'undefined' && + tronData.NetLimit && + tronData.NetLimit * 1 > 0 + ) { + totalBand = totalBand * 1 + tronData.NetLimit * 1; + } - } catch (e) { + leftBand = totalBand; + if (typeof tronData.freeNetUsed !== 'undefined' && tronData.freeNetUsed) { + leftBand = leftBand - tronData.freeNetUsed * 1; + } + if (typeof tronData.NetUsed !== 'undefined' && tronData.NetUsed) { + leftBand = leftBand - tronData.NetUsed * 1; + } - } - return { - leftBand, - totalBand, - leftEnergy, - totalEnergy - } - } + totalEnergy = + typeof tronData.EnergyLimit !== 'undefined' && tronData.EnergyLimit + ? tronData.EnergyLimit + : 0; + leftEnergy = totalEnergy; + if (typeof tronData.EnergyUsed !== 'undefined' && tronData.EnergyUsed) { + leftEnergy = leftEnergy - tronData.EnergyUsed * 1; + } + } catch (e) {} + return { + leftBand, + totalBand, + leftEnergy, + totalEnergy + }; + } } diff --git a/crypto/blockchains/trx/basic/TrxTronscanProvider.js b/crypto/blockchains/trx/basic/TrxTronscanProvider.js index 14aa49feb..4933300e7 100644 --- a/crypto/blockchains/trx/basic/TrxTronscanProvider.js +++ b/crypto/blockchains/trx/basic/TrxTronscanProvider.js @@ -2,80 +2,134 @@ * @version 0.5 * https://github.com/tronscan/tronscan-frontend/wiki/TRONSCAN-API */ -import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog' -import BlocksoftAxios from '../../../common/BlocksoftAxios' +import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; +import BlocksoftAxios from '../../../common/BlocksoftAxios'; -const BALANCE_PATH = 'https://apilist.tronscan.org/api/account?address=' -const BALANCE_MAX_TRY = 10 +const BALANCE_PATH = 'https://apilist.tronscan.org/api/account?address='; +const BALANCE_MAX_TRY = 10; -const CACHE_TRONSCAN = {} -const CACHE_VALID_TIME = 3000 // 3 seconds +const CACHE_TRONSCAN = {}; +const CACHE_VALID_TIME = 3000; // 3 seconds export default class TrxTronscanProvider { + /** + * https://apilist.tronscan.org/api/account?address=TUbHxAdhPk9ykkc7SDP5e9zUBEN14K65wk + * @param {string} address + * @param {string} tokenName + * @returns {Promise} + */ + async get(address, tokenName, useCache = true) { + const now = new Date().getTime(); + if ( + useCache && + typeof CACHE_TRONSCAN[address] !== 'undefined' && + now - CACHE_TRONSCAN[address].time < CACHE_VALID_TIME + ) { + if (typeof CACHE_TRONSCAN[address][tokenName] !== 'undefined') { + BlocksoftCryptoLog.log( + 'TrxTronscanProvider.get from cache', + address + + ' => ' + + tokenName + + ' : ' + + CACHE_TRONSCAN[address][tokenName] + ); + const frozen = + typeof CACHE_TRONSCAN[address][tokenName + 'frozen'] !== 'undefined' + ? CACHE_TRONSCAN[address][tokenName + 'frozen'] + : 0; + const frozenEnergy = + typeof CACHE_TRONSCAN[address][tokenName + 'frozenEnergy'] !== + 'undefined' + ? CACHE_TRONSCAN[address][tokenName + 'frozenEnergy'] + : 0; + const voteTotal = + typeof CACHE_TRONSCAN[address].voteTotal !== 'undefined' + ? CACHE_TRONSCAN[address].voteTotal + : 0; + return { + balance: CACHE_TRONSCAN[address][tokenName], + voteTotal, + frozen, + frozenEnergy, + unconfirmed: 0, + provider: 'tronscan-cache', + time: CACHE_TRONSCAN[address].time + }; + } else if (tokenName !== '_') { + return false; + } + } - /** - * https://apilist.tronscan.org/api/account?address=TUbHxAdhPk9ykkc7SDP5e9zUBEN14K65wk - * @param {string} address - * @param {string} tokenName - * @returns {Promise} - */ - async get(address, tokenName, useCache = true) { - const now = new Date().getTime() - if (useCache && typeof CACHE_TRONSCAN[address] !== 'undefined' && (now - CACHE_TRONSCAN[address].time) < CACHE_VALID_TIME) { - if (typeof CACHE_TRONSCAN[address][tokenName] !== 'undefined') { - BlocksoftCryptoLog.log('TrxTronscanProvider.get from cache', address + ' => ' + tokenName + ' : ' + CACHE_TRONSCAN[address][tokenName]) - const frozen = typeof CACHE_TRONSCAN[address][tokenName + 'frozen'] !== 'undefined' ? CACHE_TRONSCAN[address][tokenName + 'frozen'] : 0 - const frozenEnergy = typeof CACHE_TRONSCAN[address][tokenName + 'frozenEnergy'] !== 'undefined' ? CACHE_TRONSCAN[address][tokenName + 'frozenEnergy'] : 0 - const voteTotal = typeof CACHE_TRONSCAN[address].voteTotal !== 'undefined' ? CACHE_TRONSCAN[address].voteTotal : 0 - return { balance: CACHE_TRONSCAN[address][tokenName], voteTotal, frozen, frozenEnergy, unconfirmed : 0, provider: 'tronscan-cache', time : CACHE_TRONSCAN[address].time } - } else if (tokenName !== '_') { - return false - } - } - - const link = BALANCE_PATH + address - BlocksoftCryptoLog.log('TrxTronscanProvider.get ' + link) - const res = await BlocksoftAxios.getWithoutBraking(link, BALANCE_MAX_TRY) - if (!res || !res.data) { - return false - } - - CACHE_TRONSCAN[address] = {} - CACHE_TRONSCAN[address].time = now - CACHE_TRONSCAN[address]._ = res.data.balance - CACHE_TRONSCAN[address]._frozen = typeof res.data.frozen.total !== 'undefined' ? res.data.frozen.total : 0 - CACHE_TRONSCAN[address]._frozenEnergy = typeof res.data.accountResource !== 'undefined' - && typeof res.data.accountResource.frozen_balance_for_energy !== 'undefined' - && typeof res.data.accountResource.frozen_balance_for_energy.frozen_balance !== 'undefined' ? res.data.accountResource.frozen_balance_for_energy.frozen_balance : 0 + const link = BALANCE_PATH + address; + BlocksoftCryptoLog.log('TrxTronscanProvider.get ' + link); + const res = await BlocksoftAxios.getWithoutBraking(link, BALANCE_MAX_TRY); + if (!res || !res.data) { + return false; + } - CACHE_TRONSCAN[address].voteTotal = typeof res.data.voteTotal !== 'undefined' ? res.data.voteTotal : 0 - let token - if (res.data.tokenBalances) { - for (token of res.data.tokenBalances) { - const id = typeof token.name !== 'undefined' ? token.name : token.tokenId - CACHE_TRONSCAN[address][id] = token.balance - } - } + CACHE_TRONSCAN[address] = {}; + CACHE_TRONSCAN[address].time = now; + CACHE_TRONSCAN[address]._ = res.data.balance; + CACHE_TRONSCAN[address]._frozen = + typeof res.data.frozen.total !== 'undefined' ? res.data.frozen.total : 0; + CACHE_TRONSCAN[address]._frozenEnergy = + typeof res.data.accountResource !== 'undefined' && + typeof res.data.accountResource.frozen_balance_for_energy !== + 'undefined' && + typeof res.data.accountResource.frozen_balance_for_energy + .frozen_balance !== 'undefined' + ? res.data.accountResource.frozen_balance_for_energy.frozen_balance + : 0; - if (res.data.trc20token_balances) { - for (token of res.data.trc20token_balances) { - const id = typeof token.name !== 'undefined' ? token.name : token.tokenId - CACHE_TRONSCAN[address][id] = token.balance - } - } + CACHE_TRONSCAN[address].voteTotal = + typeof res.data.voteTotal !== 'undefined' ? res.data.voteTotal : 0; + let token; + if (res.data.tokenBalances) { + for (token of res.data.tokenBalances) { + const id = + typeof token.name !== 'undefined' ? token.name : token.tokenId; + CACHE_TRONSCAN[address][id] = token.balance; + } + } - if (typeof CACHE_TRONSCAN[address][tokenName] === 'undefined') { - if (tokenName.indexOf('T') === 0) { - return 0 - } else { - return false - } - } + if (res.data.trc20token_balances) { + for (token of res.data.trc20token_balances) { + const id = + typeof token.name !== 'undefined' ? token.name : token.tokenId; + CACHE_TRONSCAN[address][id] = token.balance; + } + } - const balance = CACHE_TRONSCAN[address][tokenName] - const frozen = typeof CACHE_TRONSCAN[address][tokenName + 'frozen'] !== 'undefined' ? CACHE_TRONSCAN[address][tokenName + 'frozen'] : 0 - const frozenEnergy = typeof CACHE_TRONSCAN[address][tokenName + 'frozenEnergy'] !== 'undefined' ? CACHE_TRONSCAN[address][tokenName + 'frozenEnergy'] : 0 - const voteTotal = typeof CACHE_TRONSCAN[address].voteTotal !== 'undefined' ? CACHE_TRONSCAN[address].voteTotal : 0 - return { balance, frozen, frozenEnergy, voteTotal, unconfirmed: 0, provider: 'tronscan', time : CACHE_TRONSCAN[address].time } + if (typeof CACHE_TRONSCAN[address][tokenName] === 'undefined') { + if (tokenName.indexOf('T') === 0) { + return 0; + } else { + return false; + } } + + const balance = CACHE_TRONSCAN[address][tokenName]; + const frozen = + typeof CACHE_TRONSCAN[address][tokenName + 'frozen'] !== 'undefined' + ? CACHE_TRONSCAN[address][tokenName + 'frozen'] + : 0; + const frozenEnergy = + typeof CACHE_TRONSCAN[address][tokenName + 'frozenEnergy'] !== 'undefined' + ? CACHE_TRONSCAN[address][tokenName + 'frozenEnergy'] + : 0; + const voteTotal = + typeof CACHE_TRONSCAN[address].voteTotal !== 'undefined' + ? CACHE_TRONSCAN[address].voteTotal + : 0; + return { + balance, + frozen, + frozenEnergy, + voteTotal, + unconfirmed: 0, + provider: 'tronscan', + time: CACHE_TRONSCAN[address].time + }; + } } diff --git a/crypto/blockchains/trx/ext/TronStakeUtils.js b/crypto/blockchains/trx/ext/TronStakeUtils.js index bef1e70f7..7827c2c34 100644 --- a/crypto/blockchains/trx/ext/TronStakeUtils.js +++ b/crypto/blockchains/trx/ext/TronStakeUtils.js @@ -1,116 +1,172 @@ -import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings' -import BlocksoftPrettyNumbers from '@crypto/common/BlocksoftPrettyNumbers' -import BlocksoftBalances from '@crypto/actions/BlocksoftBalances/BlocksoftBalances' -import TronUtils from '@crypto/blockchains/trx/ext/TronUtils' +import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings'; +import BlocksoftPrettyNumbers from '@crypto/common/BlocksoftPrettyNumbers'; +import BlocksoftBalances from '@crypto/actions/BlocksoftBalances/BlocksoftBalances'; +import TronUtils from '@crypto/blockchains/trx/ext/TronUtils'; -import BlocksoftAxios from '@crypto/common/BlocksoftAxios' -import Log from '@app/services/Log/Log' -import { BlocksoftTransfer } from '@crypto/actions/BlocksoftTransfer/BlocksoftTransfer' -import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog' +import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; +import Log from '@app/services/Log/Log'; +import { BlocksoftTransfer } from '@crypto/actions/BlocksoftTransfer/BlocksoftTransfer'; +import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; const TronStakeUtils = { + async getVoteAddresses() { + return BlocksoftExternalSettings.getStatic('TRX_VOTE_BEST'); + }, - async getVoteAddresses() { - return BlocksoftExternalSettings.getStatic('TRX_VOTE_BEST') - }, - - async getPrettyBalance(address) { - const balance = await (BlocksoftBalances.setCurrencyCode('TRX').setAddress(address).getBalance('TronStakeUtils')) - if (!balance) { - return false - } - balance.prettyBalanceAvailable = BlocksoftPrettyNumbers.setCurrencyCode('TRX').makePretty(balance.balanceAvailable) - balance.prettyFrozen = BlocksoftPrettyNumbers.setCurrencyCode('TRX').makePretty(balance.frozen) - balance.prettyFrozenOthers = BlocksoftPrettyNumbers.setCurrencyCode('TRX').makePretty(balance.frozenOthers) - balance.prettyFrozenEnergy = BlocksoftPrettyNumbers.setCurrencyCode('TRX').makePretty(balance.frozenEnergy) - balance.prettyFrozenEnergyOthers = BlocksoftPrettyNumbers.setCurrencyCode('TRX').makePretty(balance.frozenEnergyOthers) - balance.prettyVote = (balance.prettyFrozen * 1 + balance.prettyFrozenOthers * 1 + balance.prettyFrozenEnergy * 1 + balance.prettyFrozenEnergyOthers * 1).toString().split('.')[0] - - const maxExpire = balance.frozenEnergyExpireTime && balance.frozenEnergyExpireTime > balance.frozenExpireTime ? - balance.frozenEnergyExpireTime : balance.frozenExpireTime - if (maxExpire > 0) { - balance.diffLastStakeMinutes = 24 * 3 * 60 - (maxExpire - new Date().getTime()) / 60000 // default time = 3 days, so thats how many minutes from last stake - } else { - balance.diffLastStakeMinutes = -1 - } - return balance - }, - - async sendVoteAll(address, derivationPath, walletHash, specialActionNeeded) { - - const { prettyVote, diffLastStakeMinutes, voteTotal } = await TronStakeUtils.getPrettyBalance(address) - if (diffLastStakeMinutes === -1 && specialActionNeeded === 'vote_after_unfreeze') { - BlocksoftCryptoLog.log('TronStake.sendVoteAll ' + address + ' continue ' + diffLastStakeMinutes) - } else if (!diffLastStakeMinutes || diffLastStakeMinutes < 3) { - BlocksoftCryptoLog.log('TronStake.sendVoteAll ' + address + ' skipped vote1 by ' + diffLastStakeMinutes) - return false - } - if (!prettyVote || typeof prettyVote === 'undefined') { - BlocksoftCryptoLog.log('TronStake.sendVoteAll ' + address + ' skipped vote2') - return false - } else if (voteTotal * 1 === prettyVote * 1) { - if (diffLastStakeMinutes > 100) { - BlocksoftCryptoLog.log('TronStake.sendVoteAll ' + address + ' skipped vote3 ' + voteTotal + ' by ' + diffLastStakeMinutes) - return true // all done - } - BlocksoftCryptoLog.log('TronStake.sendVoteAll ' + address + ' skipped vote4 ' + voteTotal) - return false - } + async getPrettyBalance(address) { + const balance = await BlocksoftBalances.setCurrencyCode('TRX') + .setAddress(address) + .getBalance('TronStakeUtils'); + if (!balance) { + return false; + } + balance.prettyBalanceAvailable = BlocksoftPrettyNumbers.setCurrencyCode( + 'TRX' + ).makePretty(balance.balanceAvailable); + balance.prettyFrozen = BlocksoftPrettyNumbers.setCurrencyCode( + 'TRX' + ).makePretty(balance.frozen); + balance.prettyFrozenOthers = BlocksoftPrettyNumbers.setCurrencyCode( + 'TRX' + ).makePretty(balance.frozenOthers); + balance.prettyFrozenEnergy = BlocksoftPrettyNumbers.setCurrencyCode( + 'TRX' + ).makePretty(balance.frozenEnergy); + balance.prettyFrozenEnergyOthers = BlocksoftPrettyNumbers.setCurrencyCode( + 'TRX' + ).makePretty(balance.frozenEnergyOthers); + balance.prettyVote = ( + balance.prettyFrozen * 1 + + balance.prettyFrozenOthers * 1 + + balance.prettyFrozenEnergy * 1 + + balance.prettyFrozenEnergyOthers * 1 + ) + .toString() + .split('.')[0]; - BlocksoftCryptoLog.log('TronStake.sendVoteAll ' + address + ' started vote ' + prettyVote + ' by ' + diffLastStakeMinutes) + const maxExpire = + balance.frozenEnergyExpireTime && + balance.frozenEnergyExpireTime > balance.frozenExpireTime + ? balance.frozenEnergyExpireTime + : balance.frozenExpireTime; + if (maxExpire > 0) { + balance.diffLastStakeMinutes = + 24 * 3 * 60 - (maxExpire - new Date().getTime()) / 60000; // default time = 3 days, so thats how many minutes from last stake + } else { + balance.diffLastStakeMinutes = -1; + } + return balance; + }, - const voteAddress = await TronStakeUtils.getVoteAddresses() - return TronStakeUtils._send('/wallet/votewitnessaccount', { - owner_address: TronUtils.addressToHex(address), - votes: [ - { - vote_address: TronUtils.addressToHex(voteAddress), - vote_count: prettyVote * 1 - } - ] - }, 'vote ' + prettyVote + ' for ' + voteAddress, { - walletHash, - address, - derivationPath, - type: 'vote', - cryptoValue: BlocksoftPrettyNumbers.setCurrencyCode('TRX').makeUnPretty(prettyVote * 1), - callback: () => { - } - }) - }, + async sendVoteAll(address, derivationPath, walletHash, specialActionNeeded) { + const { prettyVote, diffLastStakeMinutes, voteTotal } = + await TronStakeUtils.getPrettyBalance(address); + if ( + diffLastStakeMinutes === -1 && + specialActionNeeded === 'vote_after_unfreeze' + ) { + BlocksoftCryptoLog.log( + 'TronStake.sendVoteAll ' + address + ' continue ' + diffLastStakeMinutes + ); + } else if (!diffLastStakeMinutes || diffLastStakeMinutes < 3) { + BlocksoftCryptoLog.log( + 'TronStake.sendVoteAll ' + + address + + ' skipped vote1 by ' + + diffLastStakeMinutes + ); + return false; + } + if (!prettyVote || typeof prettyVote === 'undefined') { + BlocksoftCryptoLog.log( + 'TronStake.sendVoteAll ' + address + ' skipped vote2' + ); + return false; + } else if (voteTotal * 1 === prettyVote * 1) { + if (diffLastStakeMinutes > 100) { + BlocksoftCryptoLog.log( + 'TronStake.sendVoteAll ' + + address + + ' skipped vote3 ' + + voteTotal + + ' by ' + + diffLastStakeMinutes + ); + return true; // all done + } + BlocksoftCryptoLog.log( + 'TronStake.sendVoteAll ' + address + ' skipped vote4 ' + voteTotal + ); + return false; + } - async _send(shortLink, params, langMsg, uiParams) { + BlocksoftCryptoLog.log( + 'TronStake.sendVoteAll ' + + address + + ' started vote ' + + prettyVote + + ' by ' + + diffLastStakeMinutes + ); - const sendLink = BlocksoftExternalSettings.getStatic('TRX_SEND_LINK') - const link = sendLink + shortLink - const tmp = await BlocksoftAxios.post(link, params) - let blockchainData + const voteAddress = await TronStakeUtils.getVoteAddresses(); + return TronStakeUtils._send( + '/wallet/votewitnessaccount', + { + owner_address: TronUtils.addressToHex(address), + votes: [ + { + vote_address: TronUtils.addressToHex(voteAddress), + vote_count: prettyVote * 1 + } + ] + }, + 'vote ' + prettyVote + ' for ' + voteAddress, + { + walletHash, + address, + derivationPath, + type: 'vote', + cryptoValue: BlocksoftPrettyNumbers.setCurrencyCode('TRX').makeUnPretty( + prettyVote * 1 + ), + callback: () => {} + } + ); + }, - if (typeof tmp.data !== 'undefined') { - if (typeof tmp.data.raw_data_hex !== 'undefined') { - blockchainData = tmp.data - } else { - Log.log('TronStakeUtils._send no rawHex ' + link, params, tmp.data) - throw new Error(JSON.stringify(tmp.data)) - } - } else { - Log.log('TronStakeUtils rawHex empty data ' + link, params) - throw new Error('Empty data') - } + async _send(shortLink, params, langMsg, uiParams) { + const sendLink = BlocksoftExternalSettings.getStatic('TRX_SEND_LINK'); + const link = sendLink + shortLink; + const tmp = await BlocksoftAxios.post(link, params); + let blockchainData; - const txData = { - currencyCode: 'TRX', - walletHash: uiParams.walletHash, - derivationPath: uiParams.derivationPath, - addressFrom: uiParams.address, - addressTo: '', - blockchainData - } + if (typeof tmp.data !== 'undefined') { + if (typeof tmp.data.raw_data_hex !== 'undefined') { + blockchainData = tmp.data; + } else { + Log.log('TronStakeUtils._send no rawHex ' + link, params, tmp.data); + throw new Error(JSON.stringify(tmp.data)); + } + } else { + Log.log('TronStakeUtils rawHex empty data ' + link, params); + throw new Error('Empty data'); + } - const result = await BlocksoftTransfer.sendTx(txData, { selectedFee: { langMsg } }) - return result + const txData = { + currencyCode: 'TRX', + walletHash: uiParams.walletHash, + derivationPath: uiParams.derivationPath, + addressFrom: uiParams.address, + addressTo: '', + blockchainData + }; - } -} + const result = await BlocksoftTransfer.sendTx(txData, { + selectedFee: { langMsg } + }); + return result; + } +}; -export default TronStakeUtils +export default TronStakeUtils; diff --git a/crypto/blockchains/trx/providers/TrxSendProvider.ts b/crypto/blockchains/trx/providers/TrxSendProvider.ts index 2b77e0064..a98d3827d 100644 --- a/crypto/blockchains/trx/providers/TrxSendProvider.ts +++ b/crypto/blockchains/trx/providers/TrxSendProvider.ts @@ -1,144 +1,225 @@ /** * @version 0.41 */ -import { BlocksoftBlockchainTypes } from '@crypto/blockchains/BlocksoftBlockchainTypes' -import DogeSendProvider from '@crypto/blockchains/doge/providers/DogeSendProvider' -import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog' - -import BlocksoftAxios from '@crypto/common/BlocksoftAxios' -import config from '@app/config/config' -import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings' - -export default class TrxSendProvider extends DogeSendProvider implements BlocksoftBlockchainTypes.SendProvider { - +import { BlocksoftBlockchainTypes } from '@crypto/blockchains/BlocksoftBlockchainTypes'; +import DogeSendProvider from '@crypto/blockchains/doge/providers/DogeSendProvider'; +import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; + +import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; +import config from '@app/config/config'; +import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings'; + +export default class TrxSendProvider + extends DogeSendProvider + implements BlocksoftBlockchainTypes.SendProvider +{ + trxError(msg: string) { + if (config.debug.cryptoErrors) { + console.log(this._settings.currencyCode + ' TrxSendProvider ' + msg); + } + if ( + this._settings.currencyCode !== 'TRX' && + msg.indexOf('AccountResourceInsufficient') !== -1 + ) { + throw new Error('SERVER_RESPONSE_NOT_ENOUGH_FEE'); + } else if ( + msg.indexOf( + 'Validate TransferContract error, balance is not sufficient.' + ) !== -1 + ) { + throw new Error('SERVER_RESPONSE_NOTHING_TO_TRANSFER_FROM_ACTUAL_NODE'); + } else if (msg.indexOf('balance is not sufficient') !== -1) { + throw new Error('SERVER_RESPONSE_NOT_ENOUGH_FEE'); + } else if (msg.indexOf('account not exist') !== -1) { + throw new Error('SERVER_RESPONSE_NOT_ENOUGH_FEE'); + } else if (msg.indexOf('Amount must greater than 0') !== -1) { + throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_DUST'); + } else if ( + msg.indexOf('assetBalance must be greater than 0') !== -1 || + msg.indexOf('assetBalance is not sufficient') !== -1 + ) { + throw new Error('SERVER_RESPONSE_NOTHING_TO_TRANSFER_FROM_ACTUAL_NODE'); + } else { + throw new Error(msg); + } + } + + async _sendTx( + tx: any, + subtitle: string, + txRBF: any, + logData: any + ): Promise<{ transactionHash: string; logData: any }> { + await BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' TrxSendProvider._sendTx ' + + subtitle + + ' started ', + logData + ); + + const sendLink = BlocksoftExternalSettings.getStatic('TRX_SEND_LINK'); + const link = sendLink + '/wallet/broadcasttransaction'; + if (config.debug.cryptoErrors) { + console.log( + new Date().toISOString() + + ' ' + + this._settings.currencyCode + + ' TrxSendProvider._sendTx ' + + subtitle + + ' started check ' + ); + } + logData = await this._check(tx.raw_data_hex, subtitle, txRBF, logData); + if (config.debug.cryptoErrors) { + BlocksoftCryptoLog.log( + new Date().toISOString() + + ' ' + + this._settings.currencyCode + + ' TrxSendProvider._sendTx ' + + subtitle + + ' ended check ' + ); + } - trxError(msg: string) { - if (config.debug.cryptoErrors) { - console.log(this._settings.currencyCode + ' TrxSendProvider ' + msg) - } - if (this._settings.currencyCode !== 'TRX' && msg.indexOf('AccountResourceInsufficient') !== -1) { - throw new Error('SERVER_RESPONSE_NOT_ENOUGH_FEE') - } else if (msg.indexOf('Validate TransferContract error, balance is not sufficient.') !== -1) { - throw new Error('SERVER_RESPONSE_NOTHING_TO_TRANSFER_FROM_ACTUAL_NODE') - } else if (msg.indexOf('balance is not sufficient') !== -1) { - throw new Error('SERVER_RESPONSE_NOT_ENOUGH_FEE') - } else if (msg.indexOf('account not exist') !== -1) { - throw new Error('SERVER_RESPONSE_NOT_ENOUGH_FEE') - } else if (msg.indexOf('Amount must greater than 0') !== -1) { - throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_DUST') - } else if (msg.indexOf('assetBalance must be greater than 0') !== -1 || msg.indexOf('assetBalance is not sufficient') !== -1) { - throw new Error('SERVER_RESPONSE_NOTHING_TO_TRANSFER_FROM_ACTUAL_NODE') - } else { - throw new Error(msg) - } + let send = false; + try { + send = await BlocksoftAxios.post(link, tx); + } catch (e) { + if (config.debug.cryptoErrors) { + console.log( + this._settings.currencyCode + + ' TrxSendProvider._sendTx broadcast error ' + + e.message + ); + } } - async _sendTx(tx: any, subtitle: string, txRBF: any, logData: any): Promise<{ transactionHash: string, logData : any }> { - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' TrxSendProvider._sendTx ' + subtitle + ' started ', logData) + // @ts-ignore + if (!send || typeof send.data === 'undefined' || !send.data) { + throw new Error('SERVER_RESPONSE_NOT_CONNECTED'); + } - const sendLink = BlocksoftExternalSettings.getStatic('TRX_SEND_LINK') - const link = sendLink + '/wallet/broadcasttransaction' - if (config.debug.cryptoErrors) { - console.log(new Date().toISOString() + ' ' + this._settings.currencyCode + ' TrxSendProvider._sendTx ' + subtitle + ' started check ') - } - logData = await this._check(tx.raw_data_hex, subtitle, txRBF, logData) - if (config.debug.cryptoErrors) { - BlocksoftCryptoLog.log(new Date().toISOString() + ' ' + this._settings.currencyCode + ' TrxSendProvider._sendTx ' + subtitle + ' ended check ') - } + await BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' TrxSendProvider._sendTx ' + + subtitle + + ' result ', + send.data + ); + + if (typeof send.data.code !== 'undefined') { + if (send.data.code === 'BANDWITH_ERROR') { + throw new Error('SERVER_RESPONSE_BANDWITH_ERROR_TRX'); + } else if (send.data.code === 'SERVER_BUSY') { + throw new Error('SERVER_RESPONSE_NOT_CONNECTED'); + } + } - let send = false + // @ts-ignore + if (typeof send.data.Error !== 'undefined') { + await BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' TrxSendProvider._sendTx error ' + + send.data.Error + ); + // @ts-ignore + throw new Error(send.data.Error); + } + // @ts-ignore + if (typeof send.data.result === 'undefined') { + // @ts-ignore + if (typeof send.data.message !== 'undefined') { + let msg = false; try { - send = await BlocksoftAxios.post(link, tx) + // @ts-ignore + const buf = Buffer.from(send.data.message, 'hex'); + // @ts-ignore + msg = buf.toString(''); } catch (e) { - if (config.debug.cryptoErrors) { - console.log(this._settings.currencyCode + ' TrxSendProvider._sendTx broadcast error ' + e.message) - } - } - - // @ts-ignore - if (!send || typeof send.data === 'undefined' || !send.data) { - throw new Error('SERVER_RESPONSE_NOT_CONNECTED') + // do nothing } - - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' TrxSendProvider._sendTx ' + subtitle + ' result ', send.data) - - if (typeof send.data.code !== 'undefined') { - if (send.data.code === 'BANDWITH_ERROR') { - throw new Error('SERVER_RESPONSE_BANDWITH_ERROR_TRX') - } else if (send.data.code === 'SERVER_BUSY') { - throw new Error('SERVER_RESPONSE_NOT_CONNECTED') - } + await BlocksoftCryptoLog.log( + this._settings.currencyCode + ' TrxSendProvider._sendTx msg ' + msg + ); + if (msg) { + // @ts-ignore + send.data.decoded = msg; + // @ts-ignore + this.trxError(msg); } - + } + // @ts-ignore + this.trxError('no transaction result ' + JSON.stringify(send.data)); + } else { + // @ts-ignore + if (send.data.result !== true) { // @ts-ignore - if (typeof send.data.Error !== 'undefined') { - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' TrxSendProvider._sendTx error ' + send.data.Error) - // @ts-ignore - throw new Error(send.data.Error) - } - // @ts-ignore - if (typeof send.data.result === 'undefined') { - // @ts-ignore - if (typeof send.data.message !== 'undefined') { - let msg = false - try { - // @ts-ignore - const buf = Buffer.from(send.data.message, 'hex') - // @ts-ignore - msg = buf.toString('') - } catch (e) { - // do nothing - } - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' TrxSendProvider._sendTx msg ' + msg) - if (msg) { - // @ts-ignore - send.data.decoded = msg - // @ts-ignore - this.trxError(msg) - } - } - // @ts-ignore - this.trxError('no transaction result ' + JSON.stringify(send.data)) - } else { - // @ts-ignore - if (send.data.result !== true) { - // @ts-ignore - this.trxError('transaction result is false ' + JSON.stringify(send.data)) - } - } - - return {transactionHash : tx.txID, logData} + this.trxError( + 'transaction result is false ' + JSON.stringify(send.data) + ); + } } - async sendTx(tx: any, subtitle: string, txRBF: any, logData: any): Promise<{ transactionHash: string, transactionJson: any, logData }> { - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' TrxSendProvider.sendTx ' + subtitle + ' started ', logData) - - let send, transactionHash - try { - send = await this._sendTx(tx, subtitle, txRBF, logData) - transactionHash = send.transactionHash - } catch (e) { - if (config.debug.cryptoErrors) { - console.log(this._settings.currencyCode + ' TrxSendProvider.sendTx error ', e) - } - try { - logData.error = e.message - await this._checkError(tx.raw_data_hex, subtitle, txRBF, logData) - } catch (e2) { - if (config.debug.cryptoErrors) { - console.log(this._settings.currencyCode + ' TrxSendProvider.send proxy error errorTx ' + e.message) - } - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' TrxSendProvider.send proxy error errorTx ' + e2.message) - } - throw e + return { transactionHash: tx.txID, logData }; + } + + async sendTx( + tx: any, + subtitle: string, + txRBF: any, + logData: any + ): Promise<{ transactionHash: string; transactionJson: any; logData }> { + await BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' TrxSendProvider.sendTx ' + + subtitle + + ' started ', + logData + ); + + let send, transactionHash; + try { + send = await this._sendTx(tx, subtitle, txRBF, logData); + transactionHash = send.transactionHash; + } catch (e) { + if (config.debug.cryptoErrors) { + console.log( + this._settings.currencyCode + ' TrxSendProvider.sendTx error ', + e + ); + } + try { + logData.error = e.message; + await this._checkError(tx.raw_data_hex, subtitle, txRBF, logData); + } catch (e2) { + if (config.debug.cryptoErrors) { + console.log( + this._settings.currencyCode + + ' TrxSendProvider.send proxy error errorTx ' + + e.message + ); } + await BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' TrxSendProvider.send proxy error errorTx ' + + e2.message + ); + } + throw e; + } - - try { - logData = await this._checkSuccess(transactionHash, tx.raw_data_hex, subtitle, txRBF, logData) - } catch (e) { - throw new Error(e.message + ' in _checkSuccess wrapped TRX') - } - return { transactionHash, transactionJson: {}, logData } + try { + logData = await this._checkSuccess( + transactionHash, + tx.raw_data_hex, + subtitle, + txRBF, + logData + ); + } catch (e) { + throw new Error(e.message + ' in _checkSuccess wrapped TRX'); } + return { transactionHash, transactionJson: {}, logData }; + } } diff --git a/crypto/blockchains/usdt/UsdtScannerProcessor.js b/crypto/blockchains/usdt/UsdtScannerProcessor.js index 7563121bf..d24bbec62 100644 --- a/crypto/blockchains/usdt/UsdtScannerProcessor.js +++ b/crypto/blockchains/usdt/UsdtScannerProcessor.js @@ -1,228 +1,284 @@ /** * @version 0.5 */ -import BlocksoftUtils from '../../common/BlocksoftUtils' -import BlocksoftAxios from '../../common/BlocksoftAxios' -import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog' -import BlocksoftDispatcher from '../BlocksoftDispatcher' +import BlocksoftUtils from '../../common/BlocksoftUtils'; +import BlocksoftAxios from '../../common/BlocksoftAxios'; +import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog'; +import BlocksoftDispatcher from '../BlocksoftDispatcher'; -const USDT_API = 'https://microscanners.trustee.deals/usdt' // https://microscanners.trustee.deals/usdt/1CmAoxq8BTxANRDwheJUpaGy6ngWNYX85 -const USDT_API_MASS = 'https://microscanners.trustee.deals/balanceMass' +const USDT_API = 'https://microscanners.trustee.deals/usdt'; // https://microscanners.trustee.deals/usdt/1CmAoxq8BTxANRDwheJUpaGy6ngWNYX85 +const USDT_API_MASS = 'https://microscanners.trustee.deals/balanceMass'; -const CACHE_VALID_TIME = 30000 // 30 seconds -const CACHE = {} +const CACHE_VALID_TIME = 30000; // 30 seconds +const CACHE = {}; export default class UsdtScannerProcessor { - /** - * @type {number} - */ - lastBlock = 0 + /** + * @type {number} + */ + lastBlock = 0; - /** - * @type {number} - * @private - */ - _blocksToConfirm = 1 + /** + * @type {number} + * @private + */ + _blocksToConfirm = 1; - /** - * @type {boolean|BtcScannerProcessor} - * @private - */ - _btcProvider = false + /** + * @type {boolean|BtcScannerProcessor} + * @private + */ + _btcProvider = false; - - /** - * @param address - * @returns {Promise} - * @private - */ - async _get(address) { - const now = new Date().getTime() - if (typeof CACHE[address] !== 'undefined' && (now - CACHE[address].time < CACHE_VALID_TIME)) { - CACHE[address].provider = 'usdt-cache' - return CACHE[address] - } - const link = `${USDT_API}/${address}` - const res = await BlocksoftAxios.getWithoutBraking(link) - if (!res || typeof res.data === 'undefined' || !res.data) { - return false - } - if (typeof res.data.status === 'undefined') { - throw new Error(' UsdtScannerProcessor._get bad status loaded for address ' + link, res) - } - if (typeof res.data.data === 'undefined' || typeof res.data.data.balance === 'undefined') { - throw new Error(' UsdtScannerProcessor._get nothing loaded for address ' + link) - } - if (typeof CACHE[address] !== 'undefined') { - if (CACHE[address].data.block > res.data.data.block) { - return false - } - } - CACHE[address] = { - data: res.data.data, - time: now, - provider: 'usdt' - } - return CACHE[address] + /** + * @param address + * @returns {Promise} + * @private + */ + async _get(address) { + const now = new Date().getTime(); + if ( + typeof CACHE[address] !== 'undefined' && + now - CACHE[address].time < CACHE_VALID_TIME + ) { + CACHE[address].provider = 'usdt-cache'; + return CACHE[address]; + } + const link = `${USDT_API}/${address}`; + const res = await BlocksoftAxios.getWithoutBraking(link); + if (!res || typeof res.data === 'undefined' || !res.data) { + return false; + } + if (typeof res.data.status === 'undefined') { + throw new Error( + ' UsdtScannerProcessor._get bad status loaded for address ' + link, + res + ); + } + if ( + typeof res.data.data === 'undefined' || + typeof res.data.data.balance === 'undefined' + ) { + throw new Error( + ' UsdtScannerProcessor._get nothing loaded for address ' + link + ); } + if (typeof CACHE[address] !== 'undefined') { + if (CACHE[address].data.block > res.data.data.block) { + return false; + } + } + CACHE[address] = { + data: res.data.data, + time: now, + provider: 'usdt' + }; + return CACHE[address]; + } - /** - * @param address - * @returns {Promise} - * @private - */ - async _getMass(address) { - const now = new Date().getTime() + /** + * @param address + * @returns {Promise} + * @private + */ + async _getMass(address) { + const now = new Date().getTime(); - // mass ask - const link = `${USDT_API_MASS}` - const res = await BlocksoftAxios.postWithoutBraking(link, address) + // mass ask + const link = `${USDT_API_MASS}`; + const res = await BlocksoftAxios.postWithoutBraking(link, address); - if (!res || typeof res.data === 'undefined') { - return false - } - return { - data: res.data.data, - time: now, - provider: 'usdt' - } + if (!res || typeof res.data === 'undefined') { + return false; } + return { + data: res.data.data, + time: now, + provider: 'usdt' + }; + } - /** - * @param {string} address - * @return {Promise<{int:balance, int:provider}>} - */ - async getBalanceBlockchain(address) { - if (typeof address === 'object') { - BlocksoftCryptoLog.log('UsdtScannerProcessor.getBalance started MASS ' + JSON.stringify(address)) - return this._getMass(address) - } + /** + * @param {string} address + * @return {Promise<{int:balance, int:provider}>} + */ + async getBalanceBlockchain(address) { + if (typeof address === 'object') { + BlocksoftCryptoLog.log( + 'UsdtScannerProcessor.getBalance started MASS ' + + JSON.stringify(address) + ); + return this._getMass(address); + } - BlocksoftCryptoLog.log('UsdtScannerProcessor.getBalance started ' + address) - const tmp = await this._get(address) - if (typeof tmp === 'undefined' || !tmp || typeof tmp.data === 'undefined') { - BlocksoftCryptoLog.log('UsdtScannerProcessor.getBalance bad tmp ', tmp) - return false - } - if (!tmp.data || typeof tmp.data.balance === 'undefined' || tmp.data.balance === false) { - BlocksoftCryptoLog.log('UsdtScannerProcessor.getBalance bad tmp.data ', tmp.data) - return false - } - const balance = tmp.data.balance - BlocksoftCryptoLog.log('UsdtScannerProcessor.getBalance finished', address + ' => ' + balance) - return { balance, provider: tmp.provider, time: tmp.time, unconfirmed: 0, balanceScanBlock: tmp.data.block } + BlocksoftCryptoLog.log( + 'UsdtScannerProcessor.getBalance started ' + address + ); + const tmp = await this._get(address); + if (typeof tmp === 'undefined' || !tmp || typeof tmp.data === 'undefined') { + BlocksoftCryptoLog.log('UsdtScannerProcessor.getBalance bad tmp ', tmp); + return false; + } + if ( + !tmp.data || + typeof tmp.data.balance === 'undefined' || + tmp.data.balance === false + ) { + BlocksoftCryptoLog.log( + 'UsdtScannerProcessor.getBalance bad tmp.data ', + tmp.data + ); + return false; } + const balance = tmp.data.balance; + BlocksoftCryptoLog.log( + 'UsdtScannerProcessor.getBalance finished', + address + ' => ' + balance + ); + return { + balance, + provider: tmp.provider, + time: tmp.time, + unconfirmed: 0, + balanceScanBlock: tmp.data.block + }; + } - /** - * @param {string} scanData.account.address - * @return {Promise} - */ - async getTransactionsBlockchain(scanData, source = '') { - const address = scanData.account.address.trim() - BlocksoftCryptoLog.log('UsdtScannerProcessor.getTransactions started ' + address) - let tmp = await this._get(address) - if (!tmp || typeof tmp.data === 'undefined') { - BlocksoftCryptoLog.log('UsdtScannerProcessor.getTransactions bad tmp ', tmp) - return [] - } - if (!tmp.data) { - BlocksoftCryptoLog.log('UsdtScannerProcessor.getTransactions bad tmp.data ', tmp.data) - return [] - } - - tmp = tmp.data - if (typeof tmp.data !== 'undefined') { - tmp = tmp.data // wtf but ok to support old wallets - } - if (typeof tmp.txs === 'undefined') { - throw new Error('Undefined txs ' + JSON.stringify(tmp)) - } + /** + * @param {string} scanData.account.address + * @return {Promise} + */ + async getTransactionsBlockchain(scanData, source = '') { + const address = scanData.account.address.trim(); + BlocksoftCryptoLog.log( + 'UsdtScannerProcessor.getTransactions started ' + address + ); + let tmp = await this._get(address); + if (!tmp || typeof tmp.data === 'undefined') { + BlocksoftCryptoLog.log( + 'UsdtScannerProcessor.getTransactions bad tmp ', + tmp + ); + return []; + } + if (!tmp.data) { + BlocksoftCryptoLog.log( + 'UsdtScannerProcessor.getTransactions bad tmp.data ', + tmp.data + ); + return []; + } - const transactions = [] - if (tmp.block > this.lastBlock) { - this.lastBlock = tmp.block - } - let tx - const unique = {} - if (tmp.txs && tmp.txs.length > 0) { - for (tx of tmp.txs) { - const transaction = await this._unifyTransaction(address, tx) - transactions.push(transaction) - unique[transaction.transactionHash] = 1 - } - } - let btcTxs = false - try { - if (!this._btcProvider) { - this._btcProvider = await (new BlocksoftDispatcher()).getScannerProcessor({ currencyCode: 'BTC' }) - } - btcTxs = await this._btcProvider.getTransactionsBlockchain(scanData, source + ' UsdtScannerProcessor') - } catch (e) { + tmp = tmp.data; + if (typeof tmp.data !== 'undefined') { + tmp = tmp.data; // wtf but ok to support old wallets + } + if (typeof tmp.txs === 'undefined') { + throw new Error('Undefined txs ' + JSON.stringify(tmp)); + } - } - if (btcTxs && btcTxs.length > 0) { - for (tx of btcTxs) { - if (typeof unique[tx.transactionHash] !== 'undefined') continue - transactions.push({ - blockConfirmations: tx.blockConfirmations, - blockTime: tx.blockTime, - transactionDirection: tx.transactionDirection, - transactionHash: tx.transactionHash, - transactionStatus: tx.transactionStatus - }) - } - } - BlocksoftCryptoLog.log('UsdtScannerProcessor.getTransactions finished ' + address + ' total: ' + transactions.length) - return transactions + const transactions = []; + if (tmp.block > this.lastBlock) { + this.lastBlock = tmp.block; } + let tx; + const unique = {}; + if (tmp.txs && tmp.txs.length > 0) { + for (tx of tmp.txs) { + const transaction = await this._unifyTransaction(address, tx); + transactions.push(transaction); + unique[transaction.transactionHash] = 1; + } + } + let btcTxs = false; + try { + if (!this._btcProvider) { + this._btcProvider = await new BlocksoftDispatcher().getScannerProcessor( + { currencyCode: 'BTC' } + ); + } + btcTxs = await this._btcProvider.getTransactionsBlockchain( + scanData, + source + ' UsdtScannerProcessor' + ); + } catch (e) {} + if (btcTxs && btcTxs.length > 0) { + for (tx of btcTxs) { + if (typeof unique[tx.transactionHash] !== 'undefined') continue; + transactions.push({ + blockConfirmations: tx.blockConfirmations, + blockTime: tx.blockTime, + transactionDirection: tx.transactionDirection, + transactionHash: tx.transactionHash, + transactionStatus: tx.transactionStatus + }); + } + } + BlocksoftCryptoLog.log( + 'UsdtScannerProcessor.getTransactions finished ' + + address + + ' total: ' + + transactions.length + ); + return transactions; + } - /** - * - * @param {string} address - * @param {Object} transaction - * @param {string} transaction.block_number: 467352, - * @param {string} transaction.transaction_block_hash: '0000000000000000018e86423804e917c75348090419a46e506bc2d4818c2827', - * @param {string} transaction.transaction_hash: '7daaa478c829445c967d4607345227286a23acd20f5bc80709e418d0e286ecf1', - * @param {string} transaction.transaction_txid: '7daaa478c829445c967d4607345227286a23acd20f5bc80709e418d0e286ecf1', - * @param {string} transaction.from_address: '1GYmxyavRvjCMsmfDR2uZLMsCPoFNYw9zM', - * @param {string} transaction.to_address: '1Po1oWkD2LmodfkBYiAktwh76vkF93LKnh', - * @param {string} transaction.amount: 0.744019, - * @param {string} transaction.fee: 0.0008, - * @param {string} transaction.custom_type: '', - * @param {string} transaction.custom_valid: '', - * @param {string} transaction.created_time: '2017-05-20T22:28:15.000Z', - * @param {string} transaction.updated_time: null, - * @param {string} transaction.removed_time: null, - * @param {string} transaction._removed: 0, - * @return {UnifiedTransaction} - * @private - */ - async _unifyTransaction(address, transaction) { - const confirmations = this.lastBlock - transaction.block_number - let transactionStatus = 'new' - if (confirmations >= this._blocksToConfirm) { - transactionStatus = 'success' - } else if (confirmations > 0) { - transactionStatus = 'confirming' - } - const tx = { - transactionHash: transaction.transaction_txid, - blockHash: transaction.transaction_block_hash, - blockNumber: +transaction.block_number, - blockTime: transaction.created_time, - blockConfirmations: confirmations, - transactionDirection: (address.toLowerCase() === transaction.from_address.toLowerCase()) ? 'outcome' : 'income', - addressFrom: transaction.from_address === address ? '' : transaction.from_address, - addressTo: transaction.to_address === address ? '' : transaction.to_address, - addressAmount: transaction.amount, - transactionStatus: (transaction.custom_valid.toString() === '1' && transaction._removed.toString() === '0') ? transactionStatus : 'fail', - transactionFee: BlocksoftUtils.toSatoshi(transaction.fee), - inputValue: transaction.custom_type - } - if (tx.addressTo === '' && tx.addressFrom === '') { - tx.transactionDirection = 'self' - tx.addressAmount = 0 - } - return tx + /** + * + * @param {string} address + * @param {Object} transaction + * @param {string} transaction.block_number: 467352, + * @param {string} transaction.transaction_block_hash: '0000000000000000018e86423804e917c75348090419a46e506bc2d4818c2827', + * @param {string} transaction.transaction_hash: '7daaa478c829445c967d4607345227286a23acd20f5bc80709e418d0e286ecf1', + * @param {string} transaction.transaction_txid: '7daaa478c829445c967d4607345227286a23acd20f5bc80709e418d0e286ecf1', + * @param {string} transaction.from_address: '1GYmxyavRvjCMsmfDR2uZLMsCPoFNYw9zM', + * @param {string} transaction.to_address: '1Po1oWkD2LmodfkBYiAktwh76vkF93LKnh', + * @param {string} transaction.amount: 0.744019, + * @param {string} transaction.fee: 0.0008, + * @param {string} transaction.custom_type: '', + * @param {string} transaction.custom_valid: '', + * @param {string} transaction.created_time: '2017-05-20T22:28:15.000Z', + * @param {string} transaction.updated_time: null, + * @param {string} transaction.removed_time: null, + * @param {string} transaction._removed: 0, + * @return {UnifiedTransaction} + * @private + */ + async _unifyTransaction(address, transaction) { + const confirmations = this.lastBlock - transaction.block_number; + let transactionStatus = 'new'; + if (confirmations >= this._blocksToConfirm) { + transactionStatus = 'success'; + } else if (confirmations > 0) { + transactionStatus = 'confirming'; + } + const tx = { + transactionHash: transaction.transaction_txid, + blockHash: transaction.transaction_block_hash, + blockNumber: +transaction.block_number, + blockTime: transaction.created_time, + blockConfirmations: confirmations, + transactionDirection: + address.toLowerCase() === transaction.from_address.toLowerCase() + ? 'outcome' + : 'income', + addressFrom: + transaction.from_address === address ? '' : transaction.from_address, + addressTo: + transaction.to_address === address ? '' : transaction.to_address, + addressAmount: transaction.amount, + transactionStatus: + transaction.custom_valid.toString() === '1' && + transaction._removed.toString() === '0' + ? transactionStatus + : 'fail', + transactionFee: BlocksoftUtils.toSatoshi(transaction.fee), + inputValue: transaction.custom_type + }; + if (tx.addressTo === '' && tx.addressFrom === '') { + tx.transactionDirection = 'self'; + tx.addressAmount = 0; } + return tx; + } } diff --git a/crypto/blockchains/usdt/UsdtTransferProcessor.ts b/crypto/blockchains/usdt/UsdtTransferProcessor.ts index d31d77a91..18b801c06 100644 --- a/crypto/blockchains/usdt/UsdtTransferProcessor.ts +++ b/crypto/blockchains/usdt/UsdtTransferProcessor.ts @@ -1,102 +1,137 @@ /** * @version 0.20 */ -import { BlocksoftBlockchainTypes } from '../BlocksoftBlockchainTypes' -import BtcUnspentsProvider from '../btc/providers/BtcUnspentsProvider' -import DogeSendProvider from '../doge/providers/DogeSendProvider' -import UsdtTxInputsOutputs from './tx/UsdtTxInputsOutputs' -import UsdtTxBuilder from './tx/UsdtTxBuilder' -import BtcNetworkPrices from '../btc/basic/BtcNetworkPrices' -import BtcTransferProcessor from '../btc/BtcTransferProcessor' -import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog' -import DaemonCache from '../../../app/daemons/DaemonCache' +import { BlocksoftBlockchainTypes } from '../BlocksoftBlockchainTypes'; +import BtcUnspentsProvider from '../btc/providers/BtcUnspentsProvider'; +import DogeSendProvider from '../doge/providers/DogeSendProvider'; +import UsdtTxInputsOutputs from './tx/UsdtTxInputsOutputs'; +import UsdtTxBuilder from './tx/UsdtTxBuilder'; +import BtcNetworkPrices from '../btc/basic/BtcNetworkPrices'; +import BtcTransferProcessor from '../btc/BtcTransferProcessor'; +import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog'; +import DaemonCache from '../../../app/daemons/DaemonCache'; -export default class UsdtTransferProcessor extends BtcTransferProcessor implements BlocksoftBlockchainTypes.TransferProcessor { +export default class UsdtTransferProcessor + extends BtcTransferProcessor + implements BlocksoftBlockchainTypes.TransferProcessor +{ + _trezorServerCode = 'BTC_TREZOR_SERVER'; - _trezorServerCode = 'BTC_TREZOR_SERVER' + _builderSettings: BlocksoftBlockchainTypes.BuilderSettings = { + minOutputDustReadable: 0.000001, + minChangeDustReadable: 0.000001, + feeMaxForByteSatoshi: 1000, // for tx builder + feeMaxAutoReadable2: 0.01, // for fee calc, + feeMaxAutoReadable6: 0.005, // for fee calc + feeMaxAutoReadable12: 0.001, // for fee calc + changeTogether: false, + minRbfStepSatoshi: 50, + minSpeedUpMulti: 1.5 + }; - _builderSettings: BlocksoftBlockchainTypes.BuilderSettings = { - minOutputDustReadable: 0.000001, - minChangeDustReadable: 0.000001, - feeMaxForByteSatoshi: 1000, // for tx builder - feeMaxAutoReadable2: 0.01, // for fee calc, - feeMaxAutoReadable6: 0.005, // for fee calc - feeMaxAutoReadable12: 0.001, // for fee calc - changeTogether: false, - minRbfStepSatoshi: 50, - minSpeedUpMulti : 1.5 - } + _initProviders() { + if (this._initedProviders) return false; + this.unspentsProvider = new BtcUnspentsProvider( + this._settings, + this._trezorServerCode + ); + this.sendProvider = new DogeSendProvider( + this._settings, + this._trezorServerCode + ); + this.txPrepareInputsOutputs = new UsdtTxInputsOutputs( + this._settings, + this._builderSettings + ); + this.txBuilder = new UsdtTxBuilder(this._settings, this._builderSettings); + this.networkPrices = new BtcNetworkPrices(); + this._initedProviders = true; + } - _initProviders() { - if (this._initedProviders) return false - this.unspentsProvider = new BtcUnspentsProvider(this._settings, this._trezorServerCode) - this.sendProvider = new DogeSendProvider(this._settings, this._trezorServerCode) - this.txPrepareInputsOutputs = new UsdtTxInputsOutputs(this._settings, this._builderSettings) - this.txBuilder = new UsdtTxBuilder(this._settings, this._builderSettings) - this.networkPrices = new BtcNetworkPrices() - this._initedProviders = true + async checkTransferHasError( + data: BlocksoftBlockchainTypes.CheckTransferHasErrorData + ): Promise { + // @ts-ignore + const tmp = await DaemonCache.getCacheAccount(data.walletHash, 'BTC'); + if (tmp.balance * 1 > 0) { + return { isOk: true }; + } else { + return { + isOk: false, + code: 'TOKEN', + parentBlockchain: 'Bitcoin', + parentCurrency: 'BTC' + }; } + } - async checkTransferHasError(data: BlocksoftBlockchainTypes.CheckTransferHasErrorData): Promise { - // @ts-ignore - const tmp = await DaemonCache.getCacheAccount(data.walletHash, 'BTC') - if (tmp.balance * 1 > 0) { - return { isOk: true } - } else { - return { isOk: false, code: 'TOKEN', parentBlockchain: 'Bitcoin', parentCurrency: 'BTC' } - } - } + checkSendAllModal(data: { currencyCode: any }): boolean { + return true; + } - checkSendAllModal(data: { currencyCode: any }): boolean { - return true + async getFeeRate( + data: BlocksoftBlockchainTypes.TransferData, + privateData: BlocksoftBlockchainTypes.TransferPrivateData, + additionalData: BlocksoftBlockchainTypes.TransferAdditionalData = {} + ): Promise { + const tmpData = { ...data }; + tmpData.isTransferAll = false; + // @ts-ignore + BlocksoftCryptoLog.log( + this._settings.currencyCode + ' UsdtTxProcessor.getFeeRate started' + ); + const result = await super.getFeeRate(tmpData, privateData, additionalData); + for (const fee of result.fees) { + fee.amountForTx = data.amount; } + return result; + } - async getFeeRate(data: BlocksoftBlockchainTypes.TransferData, privateData: BlocksoftBlockchainTypes.TransferPrivateData, additionalData: BlocksoftBlockchainTypes.TransferAdditionalData = {}) - : Promise { - const tmpData = { ...data } - tmpData.isTransferAll = false - // @ts-ignore - BlocksoftCryptoLog.log(this._settings.currencyCode + ' UsdtTxProcessor.getFeeRate started') - const result = await super.getFeeRate(tmpData, privateData, additionalData) - for (const fee of result.fees) { - fee.amountForTx = data.amount - } - return result + async getTransferAllBalance( + data: BlocksoftBlockchainTypes.TransferData, + privateData: BlocksoftBlockchainTypes.TransferPrivateData, + additionalData: BlocksoftBlockchainTypes.TransferAdditionalData = {} + ): Promise { + const balance = data.amount; + // @ts-ignore + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' UsdtTransferProcessor.getTransferAllBalance ', + data.addressFrom + ' => ' + balance + ); + // noinspection EqualityComparisonWithCoercionJS + if (balance === '0') { + return { + selectedTransferAllBalance: '0', + selectedFeeIndex: -1, + fees: [], + countedForBasicBalance: '0', + countedTime: new Date().getTime() + }; } - - async getTransferAllBalance(data: BlocksoftBlockchainTypes.TransferData, privateData: BlocksoftBlockchainTypes.TransferPrivateData, additionalData: BlocksoftBlockchainTypes.TransferAdditionalData = {}): Promise { - const balance = data.amount - // @ts-ignore - BlocksoftCryptoLog.log(this._settings.currencyCode + ' UsdtTransferProcessor.getTransferAllBalance ', data.addressFrom + ' => ' + balance) - // noinspection EqualityComparisonWithCoercionJS - if (balance === '0') { - return { - selectedTransferAllBalance: '0', - selectedFeeIndex: -1, - fees: [], - countedForBasicBalance: '0', - countedTime : new Date().getTime() - } - } - const fees = await this.getFeeRate(data, privateData, additionalData) - if (!fees || fees.selectedFeeIndex < 0) { - return { - selectedTransferAllBalance: balance, - selectedFeeIndex: -2, - fees: [], - countedForBasicBalance: balance, - countedTime : new Date().getTime() - } - } - return { - ...fees, - selectedTransferAllBalance: balance - } + const fees = await this.getFeeRate(data, privateData, additionalData); + if (!fees || fees.selectedFeeIndex < 0) { + return { + selectedTransferAllBalance: balance, + selectedFeeIndex: -2, + fees: [], + countedForBasicBalance: balance, + countedTime: new Date().getTime() + }; } + return { + ...fees, + selectedTransferAllBalance: balance + }; + } - async sendTx(data: BlocksoftBlockchainTypes.TransferData, privateData: BlocksoftBlockchainTypes.TransferPrivateData, uiData: BlocksoftBlockchainTypes.TransferUiData): Promise { - const result = await super.sendTx(data, privateData, uiData) - result.transactionFeeCurrencyCode = 'BTC' - return result - } + async sendTx( + data: BlocksoftBlockchainTypes.TransferData, + privateData: BlocksoftBlockchainTypes.TransferPrivateData, + uiData: BlocksoftBlockchainTypes.TransferUiData + ): Promise { + const result = await super.sendTx(data, privateData, uiData); + result.transactionFeeCurrencyCode = 'BTC'; + return result; + } } diff --git a/crypto/blockchains/usdt/tx/UsdtTxBuilder.ts b/crypto/blockchains/usdt/tx/UsdtTxBuilder.ts index abdcf7b9c..276cd4fe0 100644 --- a/crypto/blockchains/usdt/tx/UsdtTxBuilder.ts +++ b/crypto/blockchains/usdt/tx/UsdtTxBuilder.ts @@ -1,45 +1,55 @@ /** * @version 0.20 */ -import { BlocksoftBlockchainTypes } from '../../BlocksoftBlockchainTypes' -import BtcTxBuilder from '../../btc/tx/BtcTxBuilder' -import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog' +import { BlocksoftBlockchainTypes } from '../../BlocksoftBlockchainTypes'; +import BtcTxBuilder from '../../btc/tx/BtcTxBuilder'; +import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; -import { TransactionBuilder, script, opcodes } from 'bitcoinjs-lib' +import { TransactionBuilder, script, opcodes } from 'bitcoinjs-lib'; -const USDT_TOKEN_ID = 31 +const USDT_TOKEN_ID = 31; function toPaddedHexString(num, len) { - const str = num.toString(16) - return "0".repeat(len - str.length) + str + const str = num.toString(16); + return '0'.repeat(len - str.length) + str; } function createOmniSimpleSend(amountInUSD: string, propertyID = USDT_TOKEN_ID) { - BlocksoftCryptoLog.log('UsdtTxBuilder.createOmniSimpleSend started') - const simpleSend = [ - '6f6d6e69', // omni - '0000', // tx type - '0000', // version - toPaddedHexString(propertyID, 8), - toPaddedHexString(Math.floor(amountInUSD * 100000000), 16) - ].join('') + BlocksoftCryptoLog.log('UsdtTxBuilder.createOmniSimpleSend started'); + const simpleSend = [ + '6f6d6e69', // omni + '0000', // tx type + '0000', // version + toPaddedHexString(propertyID, 8), + toPaddedHexString(Math.floor(amountInUSD * 100000000), 16) + ].join(''); - return script.compile([ - opcodes.OP_RETURN, - Buffer.from(simpleSend, 'hex') - ]) + return script.compile([opcodes.OP_RETURN, Buffer.from(simpleSend, 'hex')]); } -export default class UsdtTxBuilder extends BtcTxBuilder implements BlocksoftBlockchainTypes.TxBuilder { - _getRawTxAddOutput(txb: TransactionBuilder, output: BlocksoftBlockchainTypes.OutputTx): void { - if (typeof output.tokenAmount !== 'undefined' && output.tokenAmount && output.tokenAmount !== '0') { - const omniOutput = createOmniSimpleSend(output.tokenAmount) - txb.addOutput(omniOutput, 0) - } else { - if (typeof output.amount !== 'undefined' && output.amount.toString() === '0') { - output.amount = '546' - } - super._getRawTxAddOutput(txb, output) - } +export default class UsdtTxBuilder + extends BtcTxBuilder + implements BlocksoftBlockchainTypes.TxBuilder +{ + _getRawTxAddOutput( + txb: TransactionBuilder, + output: BlocksoftBlockchainTypes.OutputTx + ): void { + if ( + typeof output.tokenAmount !== 'undefined' && + output.tokenAmount && + output.tokenAmount !== '0' + ) { + const omniOutput = createOmniSimpleSend(output.tokenAmount); + txb.addOutput(omniOutput, 0); + } else { + if ( + typeof output.amount !== 'undefined' && + output.amount.toString() === '0' + ) { + output.amount = '546'; + } + super._getRawTxAddOutput(txb, output); } + } } diff --git a/crypto/blockchains/usdt/tx/UsdtTxInputsOutputs.ts b/crypto/blockchains/usdt/tx/UsdtTxInputsOutputs.ts index e35e5cc8e..de3a390f1 100644 --- a/crypto/blockchains/usdt/tx/UsdtTxInputsOutputs.ts +++ b/crypto/blockchains/usdt/tx/UsdtTxInputsOutputs.ts @@ -1,271 +1,371 @@ /** * @version 0.20 */ -import { BlocksoftBlockchainTypes } from '../../BlocksoftBlockchainTypes' -import BtcTxInputsOutputs from '../../btc/tx/BtcTxInputsOutputs' -import BlocksoftBN from '../../../common/BlocksoftBN' -import BlocksoftUtils from '../../../common/BlocksoftUtils' -import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog' -import DaemonCache from '../../../../app/daemons/DaemonCache' +import { BlocksoftBlockchainTypes } from '../../BlocksoftBlockchainTypes'; +import BtcTxInputsOutputs from '../../btc/tx/BtcTxInputsOutputs'; +import BlocksoftBN from '../../../common/BlocksoftBN'; +import BlocksoftUtils from '../../../common/BlocksoftUtils'; +import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; +import DaemonCache from '../../../../app/daemons/DaemonCache'; -export default class UsdtTxInputsOutputs extends BtcTxInputsOutputs implements BlocksoftBlockchainTypes.TxInputsOutputs { +export default class UsdtTxInputsOutputs + extends BtcTxInputsOutputs + implements BlocksoftBlockchainTypes.TxInputsOutputs +{ + DUST_FIRST_TRY = 546; + SIZE_FOR_BASIC = 442; - DUST_FIRST_TRY = 546 - SIZE_FOR_BASIC = 442 + _coinSelectTargets( + data: BlocksoftBlockchainTypes.TransferData, + unspents: BlocksoftBlockchainTypes.UnspentTx[], + feeForByte: string, + multiAddress: string[], + subtitle: string + ) { + const targets = [ + { address: data.addressTo, value: 0, logType: 'FOR_USDT_AMOUNT' }, + { + address: data.addressTo, + value: this.DUST_FIRST_TRY, + logType: 'FOR_USDT_BTC_OUTPUT' + } + ]; + return targets; + } - _coinSelectTargets(data: BlocksoftBlockchainTypes.TransferData, unspents: BlocksoftBlockchainTypes.UnspentTx[], feeForByte: string, multiAddress: string[], subtitle: string) { - const targets = [ - { address: data.addressTo, value: 0, logType: 'FOR_USDT_AMOUNT' }, - { address: data.addressTo, value: this.DUST_FIRST_TRY, logType: 'FOR_USDT_BTC_OUTPUT' } - ] - return targets - } + _usualTargets( + data: BlocksoftBlockchainTypes.TransferData, + unspents: BlocksoftBlockchainTypes.UnspentTx[] + ) { + const basicWishedAmountBN = new BlocksoftBN(0); + const wishedAmountBN = new BlocksoftBN(basicWishedAmountBN); - _usualTargets(data: BlocksoftBlockchainTypes.TransferData, unspents: BlocksoftBlockchainTypes.UnspentTx[]) { - const basicWishedAmountBN = new BlocksoftBN(0) - const wishedAmountBN = new BlocksoftBN(basicWishedAmountBN) + const outputs = []; - const outputs = [] + outputs.push({ + to: data.addressTo, + amount: 0, + logType: 'FOR_USDT_AMOUNT' + }); - outputs.push({ - to: data.addressTo, - amount: 0, - logType: 'FOR_USDT_AMOUNT' - }) + return { + multiAddress: [], + basicWishedAmountBN, + wishedAmountBN, + outputs + }; + } - return { - multiAddress: [], - basicWishedAmountBN, - wishedAmountBN, - outputs - } - } + _addressForChange(data: BlocksoftBlockchainTypes.TransferData): string { + return data.addressFrom; + } - _addressForChange(data: BlocksoftBlockchainTypes.TransferData): string { - return data.addressFrom - } - - async getInputsOutputs(data: BlocksoftBlockchainTypes.TransferData, unspents: BlocksoftBlockchainTypes.UnspentTx[], - feeToCount: { feeForByte?: string, feeForAll?: string, autoFeeLimitReadable?: string | number }, - additionalData: BlocksoftBlockchainTypes.TransferAdditionalData, - subtitle: string = 'default') - : Promise { - let res = await super._getInputsOutputs(data, unspents, feeToCount, additionalData, subtitle + ' usdted') - let inputIsFound = false - let newInputs = [] - let oldInputs = [] - let addressFromUsdtOutputs = 0 - let newInputAdded = false - for (const input of res.inputs) { - if (input.address === data.addressFrom) { - if (!inputIsFound) { - newInputs.push(input) - } else { - oldInputs.push(input) - } - inputIsFound = true - addressFromUsdtOutputs++ - } else { - oldInputs.push(input) - } - } + async getInputsOutputs( + data: BlocksoftBlockchainTypes.TransferData, + unspents: BlocksoftBlockchainTypes.UnspentTx[], + feeToCount: { + feeForByte?: string; + feeForAll?: string; + autoFeeLimitReadable?: string | number; + }, + additionalData: BlocksoftBlockchainTypes.TransferAdditionalData, + subtitle: string = 'default' + ): Promise { + let res = await super._getInputsOutputs( + data, + unspents, + feeToCount, + additionalData, + subtitle + ' usdted' + ); + let inputIsFound = false; + let newInputs = []; + let oldInputs = []; + let addressFromUsdtOutputs = 0; + let newInputAdded = false; + for (const input of res.inputs) { + if (input.address === data.addressFrom) { if (!inputIsFound) { - for (const unspent of unspents) { - if (unspent.address === data.addressFrom) { - if (!inputIsFound) { - newInputs.push(unspent) - newInputAdded = unspent - } - inputIsFound = true - addressFromUsdtOutputs++ - } - } - } - for (const input of oldInputs) { - newInputs.push(input) - } - if (newInputAdded) { - let changeIsFound = false - for (const output of res.outputs) { - if (typeof output.isChange !== 'undefined' && output.isChange) { - output.amount = BlocksoftUtils.add(output.amount, newInputAdded.value) - changeIsFound = true - } - } - if (!changeIsFound && newInputAdded.value !== '546') { - res.outputs.push({ - // @ts-ignore - to: data.addressFrom, - // @ts-ignore - amount: newInputAdded.value.toString(), - // @ts-ignore - isChange: true - }) - } - } - const tmp = typeof additionalData.balance !== 'undefined' && additionalData.balance ? { balance: additionalData.balance } : DaemonCache.getCacheAccountStatic(data.walletHash, 'USDT') - let needOneOutput = false - if (tmp.balance > 0) { - const diff = BlocksoftUtils.diff(tmp.balance, data.amount) - BlocksoftCryptoLog.log('USDT addressFromUsdtOutputs = ' + addressFromUsdtOutputs + ' balance ' + tmp.balance + ' diff ' + diff + '>0=' + (diff > 0 ? 'true' : 'false')) - if (addressFromUsdtOutputs < 2 && diff > 0) { - needOneOutput = true - } + newInputs.push(input); + } else { + oldInputs.push(input); } - if (res.inputs.length === 0 && (!inputIsFound || newInputAdded.value === '546')) { - throw new Error('SERVER_RESPONSE_NOT_ENOUGH_FEE_JUST_DUST') + inputIsFound = true; + addressFromUsdtOutputs++; + } else { + oldInputs.push(input); + } + } + if (!inputIsFound) { + for (const unspent of unspents) { + if (unspent.address === data.addressFrom) { + if (!inputIsFound) { + newInputs.push(unspent); + newInputAdded = unspent; + } + inputIsFound = true; + addressFromUsdtOutputs++; } - res.inputs = newInputs - if (res.inputs.length === 0 || !inputIsFound) { - throw new Error('SERVER_RESPONSE_LEGACY_BALANCE_NEEDED_USDT') + } + } + for (const input of oldInputs) { + newInputs.push(input); + } + if (newInputAdded) { + let changeIsFound = false; + for (const output of res.outputs) { + if (typeof output.isChange !== 'undefined' && output.isChange) { + output.amount = BlocksoftUtils.add( + output.amount, + newInputAdded.value + ); + changeIsFound = true; } + } + if (!changeIsFound && newInputAdded.value !== '546') { + res.outputs.push({ + // @ts-ignore + to: data.addressFrom, + // @ts-ignore + amount: newInputAdded.value.toString(), + // @ts-ignore + isChange: true + }); + } + } + const tmp = + typeof additionalData.balance !== 'undefined' && additionalData.balance + ? { balance: additionalData.balance } + : DaemonCache.getCacheAccountStatic(data.walletHash, 'USDT'); + let needOneOutput = false; + if (tmp.balance > 0) { + const diff = BlocksoftUtils.diff(tmp.balance, data.amount); + BlocksoftCryptoLog.log( + 'USDT addressFromUsdtOutputs = ' + + addressFromUsdtOutputs + + ' balance ' + + tmp.balance + + ' diff ' + + diff + + '>0=' + + (diff > 0 ? 'true' : 'false') + ); + if (addressFromUsdtOutputs < 2 && diff > 0) { + needOneOutput = true; + } + } + if ( + res.inputs.length === 0 && + (!inputIsFound || newInputAdded.value === '546') + ) { + throw new Error('SERVER_RESPONSE_NOT_ENOUGH_FEE_JUST_DUST'); + } + res.inputs = newInputs; + if (res.inputs.length === 0 || !inputIsFound) { + throw new Error('SERVER_RESPONSE_LEGACY_BALANCE_NEEDED_USDT'); + } - const totalOuts = res.outputs.length - if (totalOuts === 0) { - const newRes = JSON.parse(JSON.stringify(res)) - newRes.outputs = [] - if (needOneOutput && - ( - res.inputs.length > 1 || res.inputs[0].value * 1 >= this.DUST_FIRST_TRY * 2 - ) - ) { - newRes.outputs.push({ - isUsdt: true, - amount: this.DUST_FIRST_TRY.toString(), - to: data.addressFrom, - logType: 'FOR_LEGACY_USDT_KEEP' - }) - } - newRes.outputs.push({ - isUsdt: true, - amount: this.DUST_FIRST_TRY.toString(), - to: data.addressTo, - logType: 'FOR_USDT_AMOUNT' - }) - newRes.outputs.push({ - isUsdt: true, - tokenAmount: data.amount, - amount: '0', - to: data.addressTo, - logType: 'FOR_USDT_BTC_OUTPUT' - }) - newRes.countedFor = 'USDT' - return newRes - } else { - BlocksoftCryptoLog.log('UsdtTxInputsOutputs ' + data.addressFrom + ' => ' + data.addressTo + ' old outputs ' + JSON.stringify(res.outputs) + ' needOneOutput ' + needOneOutput ? 'true' : 'false') - res = this._innerResort(res, needOneOutput, data.addressFrom, data.addressTo, data.amount, 'getInputsOutputs ' + subtitle) - BlocksoftCryptoLog.log('UsdtTxInputsOutputs ' + data.addressFrom + ' => ' + data.addressTo + ' new outputs ' + JSON.stringify(res.outputs)) - res.countedFor = 'USDT' - return res - } + const totalOuts = res.outputs.length; + if (totalOuts === 0) { + const newRes = JSON.parse(JSON.stringify(res)); + newRes.outputs = []; + if ( + needOneOutput && + (res.inputs.length > 1 || + res.inputs[0].value * 1 >= this.DUST_FIRST_TRY * 2) + ) { + newRes.outputs.push({ + isUsdt: true, + amount: this.DUST_FIRST_TRY.toString(), + to: data.addressFrom, + logType: 'FOR_LEGACY_USDT_KEEP' + }); + } + newRes.outputs.push({ + isUsdt: true, + amount: this.DUST_FIRST_TRY.toString(), + to: data.addressTo, + logType: 'FOR_USDT_AMOUNT' + }); + newRes.outputs.push({ + isUsdt: true, + tokenAmount: data.amount, + amount: '0', + to: data.addressTo, + logType: 'FOR_USDT_BTC_OUTPUT' + }); + newRes.countedFor = 'USDT'; + return newRes; + } else { + BlocksoftCryptoLog.log( + 'UsdtTxInputsOutputs ' + + data.addressFrom + + ' => ' + + data.addressTo + + ' old outputs ' + + JSON.stringify(res.outputs) + + ' needOneOutput ' + + needOneOutput + ? 'true' + : 'false' + ); + res = this._innerResort( + res, + needOneOutput, + data.addressFrom, + data.addressTo, + data.amount, + 'getInputsOutputs ' + subtitle + ); + BlocksoftCryptoLog.log( + 'UsdtTxInputsOutputs ' + + data.addressFrom + + ' => ' + + data.addressTo + + ' new outputs ' + + JSON.stringify(res.outputs) + ); + res.countedFor = 'USDT'; + return res; } + } - _innerResort(res: any, needOneOutput: boolean, addressFrom: string, addressTo: string, amount: string, source: string = '') { - const totalOuts = res.outputs.length + _innerResort( + res: any, + needOneOutput: boolean, + addressFrom: string, + addressTo: string, + amount: string, + source: string = '' + ) { + const totalOuts = res.outputs.length; - res.outputs = this._innerToUp(res.outputs, addressTo) - if (res.outputs[0].amount !== '0') { - if (totalOuts > 1) { - if (res.outputs[0].to !== addressTo) { - throw new Error('usdt addressTo is invalid1.1 ' + JSON.stringify(res.outputs)) - } else if (res.outputs[1].to !== res.outputs[0].to) { - throw new Error('usdt addressTo is invalid1.2 ' + JSON.stringify(res)) - } - if (res.outputs[1].to !== res.outputs[0].to) { - throw new Error('usdt addressTo is invalid1.2 ' + JSON.stringify(res.outputs)) - } - res.outputs[1].amount = BlocksoftUtils.add(res.outputs[0].amount, res.outputs[1].amount).toString() - } else { - if (res.outputs[0].to !== addressTo) { - throw new Error('usdt addressTo is invalid2 ' + JSON.stringify(res.outputs)) - } - res.outputs[1] = JSON.parse(JSON.stringify(res.outputs[0])) - } - res.outputs[1].isChange = true - res.outputs[1].isUsdt = true - res.outputs[0].isChange = false - res.outputs[0].isUsdt = true - res.outputs[0].tokenAmount = amount - res.outputs[1].tokenAmount = '0' - res.outputs[0].amount = '0' - } else { - res.outputs[0].isUsdt = true - res.outputs[0].tokenAmount = amount - res.outputs[0].amount = '0' - if (totalOuts > 1) { - if (res.outputs[1].to !== addressTo) { - throw new Error('usdt addressTo is invalid3 ' + JSON.stringify(res.outputs)) - } - res.outputs[1].isUsdt = true - } else { - throw new Error('usdt addressTo is empty ' + JSON.stringify(res.outputs)) - } + res.outputs = this._innerToUp(res.outputs, addressTo); + if (res.outputs[0].amount !== '0') { + if (totalOuts > 1) { + if (res.outputs[0].to !== addressTo) { + throw new Error( + 'usdt addressTo is invalid1.1 ' + JSON.stringify(res.outputs) + ); + } else if (res.outputs[1].to !== res.outputs[0].to) { + throw new Error( + 'usdt addressTo is invalid1.2 ' + JSON.stringify(res) + ); + } + if (res.outputs[1].to !== res.outputs[0].to) { + throw new Error( + 'usdt addressTo is invalid1.2 ' + JSON.stringify(res.outputs) + ); } - const newOutputs = [] - if (needOneOutput) { - if (typeof res.outputs[2] === 'undefined' || res.outputs[2].to !== addressFrom) { - newOutputs.push({ - isUsdt: true, - amount: this.DUST_FIRST_TRY.toString(), - to: addressFrom, - logType: 'FOR_LEGACY_USDT_KEEP3' - }) - } + res.outputs[1].amount = BlocksoftUtils.add( + res.outputs[0].amount, + res.outputs[1].amount + ).toString(); + } else { + if (res.outputs[0].to !== addressTo) { + throw new Error( + 'usdt addressTo is invalid2 ' + JSON.stringify(res.outputs) + ); } - for (let i = res.outputs.length - 1; i >= 0; i--) { - newOutputs.push(res.outputs[i]) + res.outputs[1] = JSON.parse(JSON.stringify(res.outputs[0])); + } + res.outputs[1].isChange = true; + res.outputs[1].isUsdt = true; + res.outputs[0].isChange = false; + res.outputs[0].isUsdt = true; + res.outputs[0].tokenAmount = amount; + res.outputs[1].tokenAmount = '0'; + res.outputs[0].amount = '0'; + } else { + res.outputs[0].isUsdt = true; + res.outputs[0].tokenAmount = amount; + res.outputs[0].amount = '0'; + if (totalOuts > 1) { + if (res.outputs[1].to !== addressTo) { + throw new Error( + 'usdt addressTo is invalid3 ' + JSON.stringify(res.outputs) + ); } - res.outputs = newOutputs - return res + res.outputs[1].isUsdt = true; + } else { + throw new Error( + 'usdt addressTo is empty ' + JSON.stringify(res.outputs) + ); + } } + const newOutputs = []; + if (needOneOutput) { + if ( + typeof res.outputs[2] === 'undefined' || + res.outputs[2].to !== addressFrom + ) { + newOutputs.push({ + isUsdt: true, + amount: this.DUST_FIRST_TRY.toString(), + to: addressFrom, + logType: 'FOR_LEGACY_USDT_KEEP3' + }); + } + } + for (let i = res.outputs.length - 1; i >= 0; i--) { + newOutputs.push(res.outputs[i]); + } + res.outputs = newOutputs; + return res; + } - _innerToUp(outputs, addressTo) { - let result = [] - for (const output of outputs) { - if (output.to === addressTo) { - result.push(output) - } - } - if (result.length === 1) { - let amount = result[0].amount.toString() - if (amount === '0' || BlocksoftUtils.diff(amount, this.DUST_FIRST_TRY.toString()).toString().indexOf('-') !== -1) { - amount = this.DUST_FIRST_TRY.toString() - } - result = [] - result.push({ - isUsdt: true, - tokenAmount: '?', - amount: '0', - to: addressTo, - logType: 'FOR_USDT_BTC_OUTPUT1' - }) - result.push({ - isUsdt: true, - amount, - to: addressTo, - logType: 'FOR_USDT_AMOUNT1' - }) - } else if (result.length === 0) { - result.push({ - isUsdt: true, - tokenAmount: '?', - amount: '0', - to: addressTo, - logType: 'FOR_USDT_BTC_OUTPUT2' - }) - result.push({ - isUsdt: true, - amount: this.DUST_FIRST_TRY.toString(), - to: addressTo, - logType: 'FOR_USDT_AMOUNT2' - }) - } - for (const output of outputs) { - if (output.to !== addressTo) { - result.push(output) - } - } - BlocksoftCryptoLog.log('USDT addressTo upTo', result) - return result + _innerToUp(outputs, addressTo) { + let result = []; + for (const output of outputs) { + if (output.to === addressTo) { + result.push(output); + } + } + if (result.length === 1) { + let amount = result[0].amount.toString(); + if ( + amount === '0' || + BlocksoftUtils.diff(amount, this.DUST_FIRST_TRY.toString()) + .toString() + .indexOf('-') !== -1 + ) { + amount = this.DUST_FIRST_TRY.toString(); + } + result = []; + result.push({ + isUsdt: true, + tokenAmount: '?', + amount: '0', + to: addressTo, + logType: 'FOR_USDT_BTC_OUTPUT1' + }); + result.push({ + isUsdt: true, + amount, + to: addressTo, + logType: 'FOR_USDT_AMOUNT1' + }); + } else if (result.length === 0) { + result.push({ + isUsdt: true, + tokenAmount: '?', + amount: '0', + to: addressTo, + logType: 'FOR_USDT_BTC_OUTPUT2' + }); + result.push({ + isUsdt: true, + amount: this.DUST_FIRST_TRY.toString(), + to: addressTo, + logType: 'FOR_USDT_AMOUNT2' + }); + } + for (const output of outputs) { + if (output.to !== addressTo) { + result.push(output); + } } + BlocksoftCryptoLog.log('USDT addressTo upTo', result); + return result; + } } diff --git a/crypto/blockchains/vet/VetScannerProcessor.js b/crypto/blockchains/vet/VetScannerProcessor.js index 81f63cc9f..6dd3c3a30 100644 --- a/crypto/blockchains/vet/VetScannerProcessor.js +++ b/crypto/blockchains/vet/VetScannerProcessor.js @@ -2,266 +2,318 @@ * @version 0.5 * https://docs.vechain.org/thor/get-started/api.html */ -import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog' -import BlocksoftAxios from '@crypto/common/BlocksoftAxios' -import BlocksoftUtils from '@crypto/common/BlocksoftUtils' +import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog'; +import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; +import BlocksoftUtils from '@crypto/common/BlocksoftUtils'; -const API_PATH = 'https://sync-mainnet.vechain.org' -const CACHE_VALID_TIME = 30000 // 30 seconds -const CACHE = {} +const API_PATH = 'https://sync-mainnet.vechain.org'; +const CACHE_VALID_TIME = 30000; // 30 seconds +const CACHE = {}; export default class VetScannerProcessor { + constructor(settings) { + this._settings = settings; + this._lastBlock = 9243725; + } - constructor(settings) { - this._settings = settings - this._lastBlock = 9243725 - } - - async _get(_address) { - const address = _address.toLowerCase() - - const now = new Date().getTime() - if (typeof CACHE[address] !== 'undefined' && (now - CACHE[address].time < CACHE_VALID_TIME)) { - CACHE[address].provider = 'cache' - return CACHE[address] - } + async _get(_address) { + const address = _address.toLowerCase(); - const link = API_PATH + '/accounts/' + address - const res = await BlocksoftAxios.getWithoutBraking(link) - if (!res || typeof res.data === 'undefined' || typeof res.data.balance === 'undefined') { - BlocksoftCryptoLog.log(this._settings.currencyCode + ' VeChain ScannerProcessor getBalanceBlockchain ' + link + ' error ', res) - return false - } + const now = new Date().getTime(); + if ( + typeof CACHE[address] !== 'undefined' && + now - CACHE[address].time < CACHE_VALID_TIME + ) { + CACHE[address].provider = 'cache'; + return CACHE[address]; + } - CACHE[address] = { - balance: res.data.balance, - energy: res.data.energy, - provider: 'loaded', - time: now - } - return CACHE[address] + const link = API_PATH + '/accounts/' + address; + const res = await BlocksoftAxios.getWithoutBraking(link); + if ( + !res || + typeof res.data === 'undefined' || + typeof res.data.balance === 'undefined' + ) { + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' VeChain ScannerProcessor getBalanceBlockchain ' + + link + + ' error ', + res + ); + return false; } - /** - * https://docs.vechain.org/thor/get-started/api.html#get-accounts-address - * https://vethor-node.vechain.com/doc/swagger-ui/#/ - * @param {string} address - * @return {Promise<{balance, provider}>} - */ - async getBalanceBlockchain(address) { - address = address.trim() - BlocksoftCryptoLog.log(this._settings.currencyCode + ' VeChain ScannerProcessor getBalanceBlockchain address ' + address) + CACHE[address] = { + balance: res.data.balance, + energy: res.data.energy, + provider: 'loaded', + time: now + }; + return CACHE[address]; + } - const res = await this._get(address) - if (!res) { - return false - } - try { - const hex = this._settings.currencyCode === 'VET' ? res.balance : res.energy - const balance = BlocksoftUtils.hexToDecimalBigger(hex) - return { balance, unconfirmed: 0, provider: 'vethor-node.vechain.com' } - } catch (e) { - BlocksoftCryptoLog.log(this._settings.currencyCode + ' VeChain ScannerProcessor getBalanceBlockchain address ' + address + ' balance ' + JSON.stringify(res) + ' to hex error ' + e.message) - } - return false + /** + * https://docs.vechain.org/thor/get-started/api.html#get-accounts-address + * https://vethor-node.vechain.com/doc/swagger-ui/#/ + * @param {string} address + * @return {Promise<{balance, provider}>} + */ + async getBalanceBlockchain(address) { + address = address.trim(); + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' VeChain ScannerProcessor getBalanceBlockchain address ' + + address + ); + const res = await this._get(address); + if (!res) { + return false; } + try { + const hex = + this._settings.currencyCode === 'VET' ? res.balance : res.energy; + const balance = BlocksoftUtils.hexToDecimalBigger(hex); + return { balance, unconfirmed: 0, provider: 'vethor-node.vechain.com' }; + } catch (e) { + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' VeChain ScannerProcessor getBalanceBlockchain address ' + + address + + ' balance ' + + JSON.stringify(res) + + ' to hex error ' + + e.message + ); + } + return false; + } + /** + * https://docs.vechain.org/thor/get-started/api.html#post-logs-transfer + * https://github.com/trustwallet/blockatlas/blob/696fb97b7b3197a7da4bb692122a8028ea4e07cf/platform/vechain/client.go + * @param {string} scanData.account.address + * @return {Promise<[UnifiedTransaction]>} + */ + async getTransactionsBlockchain(scanData, source) { + const address = scanData.account.address.trim(); - /** - * https://docs.vechain.org/thor/get-started/api.html#post-logs-transfer - * https://github.com/trustwallet/blockatlas/blob/696fb97b7b3197a7da4bb692122a8028ea4e07cf/platform/vechain/client.go - * @param {string} scanData.account.address - * @return {Promise<[UnifiedTransaction]>} - */ - async getTransactionsBlockchain(scanData, source) { - const address = scanData.account.address.trim() - - if (this._settings.currencyCode === 'VET') { - try { - const linkNumber = API_PATH + '/blocks/best' - const resultNumber = await BlocksoftAxios.get(linkNumber) - if (resultNumber && typeof resultNumber.data !== 'undefined' && typeof resultNumber.data.number !== 'undefined') { - const tmp = resultNumber.data.number * 1 - if (tmp > this._lastBlock) { - this._lastBlock = tmp - } - } - } catch (e) { - BlocksoftCryptoLog.log('VetScannerProcessor.getTransactions lastBlock ' + e.message) - } - - const link = API_PATH + '/logs/transfer' - const result = await BlocksoftAxios.post(link, { - 'options': { - 'offset': 0, - 'limit': 100 - }, - 'criteriaSet': [ - { - 'txOrigin': address - }, - { - 'sender': address - }, - { - 'recipient': address - } - ], - 'order': 'desc' - }) + if (this._settings.currencyCode === 'VET') { + try { + const linkNumber = API_PATH + '/blocks/best'; + const resultNumber = await BlocksoftAxios.get(linkNumber); + if ( + resultNumber && + typeof resultNumber.data !== 'undefined' && + typeof resultNumber.data.number !== 'undefined' + ) { + const tmp = resultNumber.data.number * 1; + if (tmp > this._lastBlock) { + this._lastBlock = tmp; + } + } + } catch (e) { + BlocksoftCryptoLog.log( + 'VetScannerProcessor.getTransactions lastBlock ' + e.message + ); + } - if (!result.data || result.data.length === 0) return false + const link = API_PATH + '/logs/transfer'; + const result = await BlocksoftAxios.post(link, { + options: { + offset: 0, + limit: 100 + }, + criteriaSet: [ + { + txOrigin: address + }, + { + sender: address + }, + { + recipient: address + } + ], + order: 'desc' + }); - const transactions = await this._unifyTransactions(address, result.data) - BlocksoftCryptoLog.log('VetScannerProcessor.getTransactions finished ' + address) - return transactions - } else { - const link = API_PATH + '/logs/event' - const tokenHex = '0x000000000000000000000000' + address.toLowerCase().substr(2) - const result = await BlocksoftAxios.post(link, { - 'options': { - 'offset': 0, - 'limit': 100 - }, - 'criteriaSet': [ - { - address : '0x0000000000000000000000000000456e65726779', - Topic1: tokenHex - }, - { - address : '0x0000000000000000000000000000456e65726779', - Topic2: tokenHex - }, - ], - 'order': 'desc' - }) - if (!result.data || result.data.length === 0) return false - const transactions = await this._unifyTransactionsToken(address, result.data) - BlocksoftCryptoLog.log('VetScannerProcessor.getTransactions finished ' + tokenHex) - return transactions - } + if (!result.data || result.data.length === 0) return false; + const transactions = await this._unifyTransactions(address, result.data); + BlocksoftCryptoLog.log( + 'VetScannerProcessor.getTransactions finished ' + address + ); + return transactions; + } else { + const link = API_PATH + '/logs/event'; + const tokenHex = + '0x000000000000000000000000' + address.toLowerCase().substr(2); + const result = await BlocksoftAxios.post(link, { + options: { + offset: 0, + limit: 100 + }, + criteriaSet: [ + { + address: '0x0000000000000000000000000000456e65726779', + Topic1: tokenHex + }, + { + address: '0x0000000000000000000000000000456e65726779', + Topic2: tokenHex + } + ], + order: 'desc' + }); + if (!result.data || result.data.length === 0) return false; + const transactions = await this._unifyTransactionsToken( + address, + result.data + ); + BlocksoftCryptoLog.log( + 'VetScannerProcessor.getTransactions finished ' + tokenHex + ); + return transactions; } + } - async _unifyTransactions(address, result) { - const transactions = [] - let tx - for (tx of result) { - const transaction = await this._unifyTransaction(address, tx) - if (transaction) { - transactions.push(transaction) - } - } - return transactions + async _unifyTransactions(address, result) { + const transactions = []; + let tx; + for (tx of result) { + const transaction = await this._unifyTransaction(address, tx); + if (transaction) { + transactions.push(transaction); + } } + return transactions; + } - async _unifyTransactionsToken(address, result) { - const transactions = [] - let tx - for (tx of result) { - const transaction = await this._unifyTransactionToken(address, tx) - if (transaction) { - transactions.push(transaction) - } - } - return transactions + async _unifyTransactionsToken(address, result) { + const transactions = []; + let tx; + for (tx of result) { + const transaction = await this._unifyTransactionToken(address, tx); + if (transaction) { + transactions.push(transaction); + } } + return transactions; + } - async _unifyTransaction(address, transaction) { - /** - * "sender":"0xa4adafaef9ec07bc4dc6de146934c7119341ee25", - * "recipient":"0xf3ce5d5d8ff44cb6b4f77512b94ddd6e04d820a0", - * "amount":"0x352e15037328a70000", - * "meta":{ - * "blockID":"0x008c4643ee0fb483412b2b6aa34c76c7925093cd1749f33238fa14f6ab340046", - * "blockNumber":9193027, - * "blockTimestamp":1622441190, - * "txID":"0x46bb9fb1e71845fee9289e7626aa4eba26fb834d1a17661c6fbbb333958fcc67", - * "txOrigin":"0xa4adafaef9ec07bc4dc6de146934c7119341ee25", - * "clauseIndex":0 - */ + async _unifyTransaction(address, transaction) { + /** + * "sender":"0xa4adafaef9ec07bc4dc6de146934c7119341ee25", + * "recipient":"0xf3ce5d5d8ff44cb6b4f77512b94ddd6e04d820a0", + * "amount":"0x352e15037328a70000", + * "meta":{ + * "blockID":"0x008c4643ee0fb483412b2b6aa34c76c7925093cd1749f33238fa14f6ab340046", + * "blockNumber":9193027, + * "blockTimestamp":1622441190, + * "txID":"0x46bb9fb1e71845fee9289e7626aa4eba26fb834d1a17661c6fbbb333958fcc67", + * "txOrigin":"0xa4adafaef9ec07bc4dc6de146934c7119341ee25", + * "clauseIndex":0 + */ - const amount = BlocksoftUtils.hexToDecimalBigger(transaction.amount) - let formattedTime = transaction.meta.blockTimestamp - try { - formattedTime = BlocksoftUtils.toDate(transaction.meta.blockTimestamp) - } catch (e) { - e.message += ' timestamp error transaction2 data ' + JSON.stringify(transaction) - throw e - } - const now = new Date().getTime() - const diffSeconds = Math.round((now - transaction.meta.blockTimestamp) / 1000) - let blockConfirmations = this._lastBlock - transaction.meta.blockNumber - if (blockConfirmations > 100 && diffSeconds < 600) { - blockConfirmations = diffSeconds - } else if (blockConfirmations < 0) { - blockConfirmations = diffSeconds > 60 ? 2 : 0 - } - const tx = { - transactionHash: transaction.meta.txID, - blockHash: transaction.meta.blockID, - blockNumber: transaction.meta.blockNumber, - blockTime: formattedTime, - blockConfirmations, - transactionDirection: (address.toLowerCase() === transaction.sender.toLowerCase()) ? 'outcome' : 'income', - addressFrom: transaction.sender === address ? '' : transaction.sender, - addressTo: transaction.recipient === address ? '' : transaction.recipient, - addressAmount: amount, - transactionStatus: blockConfirmations > 20 ? 'success' : 'new', - transactionFee: '0' - } - return tx + const amount = BlocksoftUtils.hexToDecimalBigger(transaction.amount); + let formattedTime = transaction.meta.blockTimestamp; + try { + formattedTime = BlocksoftUtils.toDate(transaction.meta.blockTimestamp); + } catch (e) { + e.message += + ' timestamp error transaction2 data ' + JSON.stringify(transaction); + throw e; + } + const now = new Date().getTime(); + const diffSeconds = Math.round( + (now - transaction.meta.blockTimestamp) / 1000 + ); + let blockConfirmations = this._lastBlock - transaction.meta.blockNumber; + if (blockConfirmations > 100 && diffSeconds < 600) { + blockConfirmations = diffSeconds; + } else if (blockConfirmations < 0) { + blockConfirmations = diffSeconds > 60 ? 2 : 0; } + const tx = { + transactionHash: transaction.meta.txID, + blockHash: transaction.meta.blockID, + blockNumber: transaction.meta.blockNumber, + blockTime: formattedTime, + blockConfirmations, + transactionDirection: + address.toLowerCase() === transaction.sender.toLowerCase() + ? 'outcome' + : 'income', + addressFrom: transaction.sender === address ? '' : transaction.sender, + addressTo: transaction.recipient === address ? '' : transaction.recipient, + addressAmount: amount, + transactionStatus: blockConfirmations > 20 ? 'success' : 'new', + transactionFee: '0' + }; + return tx; + } - async _unifyTransactionToken(address, transaction) { - /** - * "address": "0x0000000000000000000000000000456e65726779", - * "topics": [ - * "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", - * "0x000000000000000000000000a4adafaef9ec07bc4dc6de146934c7119341ee25", - * "0x000000000000000000000000f3ce5d5d8ff44cb6b4f77512b94ddd6e04d820a0" - * ], - * "data": "0x00000000000000000000000000000000000000000000006a227cf2eb4ac80000", - * "meta": { - * "blockID": "0x008c525b58528178e3e2ea89e6a6864c0356127a21b5b06aeaa26f531690abf8", - * "blockNumber": 9196123, - * "blockTimestamp": 1622472160, - * "txID": "0x190406bc9491484ca763774dfb074118ef7b5d6f594ac484402da265d00c3eff", - * "txOrigin": "0xa4adafaef9ec07bc4dc6de146934c7119341ee25", - * "clauseIndex": 5 - */ + async _unifyTransactionToken(address, transaction) { + /** + * "address": "0x0000000000000000000000000000456e65726779", + * "topics": [ + * "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + * "0x000000000000000000000000a4adafaef9ec07bc4dc6de146934c7119341ee25", + * "0x000000000000000000000000f3ce5d5d8ff44cb6b4f77512b94ddd6e04d820a0" + * ], + * "data": "0x00000000000000000000000000000000000000000000006a227cf2eb4ac80000", + * "meta": { + * "blockID": "0x008c525b58528178e3e2ea89e6a6864c0356127a21b5b06aeaa26f531690abf8", + * "blockNumber": 9196123, + * "blockTimestamp": 1622472160, + * "txID": "0x190406bc9491484ca763774dfb074118ef7b5d6f594ac484402da265d00c3eff", + * "txOrigin": "0xa4adafaef9ec07bc4dc6de146934c7119341ee25", + * "clauseIndex": 5 + */ - const amount = BlocksoftUtils.hexToDecimalBigger(transaction.data) - let formattedTime = transaction.meta.blockTimestamp - try { - formattedTime = BlocksoftUtils.toDate(transaction.meta.blockTimestamp) - } catch (e) { - e.message += ' timestamp error transaction2 data ' + JSON.stringify(transaction) - throw e - } - const now = new Date().getTime() - const diffSeconds = Math.round((now - transaction.meta.blockTimestamp) / 1000) - let blockConfirmations = this._lastBlock - transaction.meta.blockNumber - if (blockConfirmations > 100 && diffSeconds < 600) { - blockConfirmations = diffSeconds - } else if (blockConfirmations < 0) { - blockConfirmations = diffSeconds > 60 ? 2 : 0 - } - const addressFrom = typeof transaction.topics[1] !== 'undefined' ? transaction.topics[1].replace('0x000000000000000000000000', '0x') : '' - const addressTo = typeof transaction.topics[2] !== 'undefined' ? transaction.topics[2].replace('0x000000000000000000000000', '0x') : '' - const tx = { - transactionHash: transaction.meta.txID, - blockHash: transaction.meta.blockID, - blockNumber: transaction.meta.blockNumber, - blockTime: formattedTime, - blockConfirmations, - transactionDirection : addressFrom === address.toLowerCase() ? 'outcome' : 'income' , - addressFrom: addressFrom === address.toLowerCase() ? '' : addressFrom, - addressTo: addressTo === address.toLowerCase() ? '' : addressTo, - addressAmount: amount, - transactionStatus: blockConfirmations > 20 ? 'success' : 'new', - transactionFee: '0' - } - return tx + const amount = BlocksoftUtils.hexToDecimalBigger(transaction.data); + let formattedTime = transaction.meta.blockTimestamp; + try { + formattedTime = BlocksoftUtils.toDate(transaction.meta.blockTimestamp); + } catch (e) { + e.message += + ' timestamp error transaction2 data ' + JSON.stringify(transaction); + throw e; + } + const now = new Date().getTime(); + const diffSeconds = Math.round( + (now - transaction.meta.blockTimestamp) / 1000 + ); + let blockConfirmations = this._lastBlock - transaction.meta.blockNumber; + if (blockConfirmations > 100 && diffSeconds < 600) { + blockConfirmations = diffSeconds; + } else if (blockConfirmations < 0) { + blockConfirmations = diffSeconds > 60 ? 2 : 0; } + const addressFrom = + typeof transaction.topics[1] !== 'undefined' + ? transaction.topics[1].replace('0x000000000000000000000000', '0x') + : ''; + const addressTo = + typeof transaction.topics[2] !== 'undefined' + ? transaction.topics[2].replace('0x000000000000000000000000', '0x') + : ''; + const tx = { + transactionHash: transaction.meta.txID, + blockHash: transaction.meta.blockID, + blockNumber: transaction.meta.blockNumber, + blockTime: formattedTime, + blockConfirmations, + transactionDirection: + addressFrom === address.toLowerCase() ? 'outcome' : 'income', + addressFrom: addressFrom === address.toLowerCase() ? '' : addressFrom, + addressTo: addressTo === address.toLowerCase() ? '' : addressTo, + addressAmount: amount, + transactionStatus: blockConfirmations > 20 ? 'success' : 'new', + transactionFee: '0' + }; + return tx; + } } diff --git a/crypto/blockchains/vet/VetTransferProcessor.ts b/crypto/blockchains/vet/VetTransferProcessor.ts index 3e433aa60..3973f55ed 100644 --- a/crypto/blockchains/vet/VetTransferProcessor.ts +++ b/crypto/blockchains/vet/VetTransferProcessor.ts @@ -1,72 +1,78 @@ /** * @version 0.20 */ -import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog' -import BlocksoftUtils from '../../common/BlocksoftUtils' +import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog'; +import BlocksoftUtils from '../../common/BlocksoftUtils'; -import { BlocksoftBlockchainTypes } from '../BlocksoftBlockchainTypes' -import { thorify } from 'thorify' -import BlocksoftAxios from '@crypto/common/BlocksoftAxios' -import MarketingEvent from '@app/services/Marketing/MarketingEvent' +import { BlocksoftBlockchainTypes } from '../BlocksoftBlockchainTypes'; +import { thorify } from 'thorify'; +import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; +import MarketingEvent from '@app/services/Marketing/MarketingEvent'; -const Web3 = require('web3') +const Web3 = require('web3'); const abi = [ - { - 'constant': false, - 'inputs': [ - { - 'name': 'to', - 'type': 'address' - }, - { - 'name': 'value', - 'type': 'uint256' - } - ], - 'name': 'transfer', - 'outputs': [ - { - 'name': 'ok', - 'type': 'bool' - } - ], - 'payable': false, - 'stateMutability': 'nonpayable', - 'type': 'function' - } -] -const tokenAddress = '0x0000000000000000000000000000456e65726779' + { + constant: false, + inputs: [ + { + name: 'to', + type: 'address' + }, + { + name: 'value', + type: 'uint256' + } + ], + name: 'transfer', + outputs: [ + { + name: 'ok', + type: 'bool' + } + ], + payable: false, + stateMutability: 'nonpayable', + type: 'function' + } +]; +const tokenAddress = '0x0000000000000000000000000000456e65726779'; -const API_PATH = 'https://sync-mainnet.vechain.org' +const API_PATH = 'https://sync-mainnet.vechain.org'; -export default class VetTransferProcessor implements BlocksoftBlockchainTypes.TransferProcessor { - private _settings: { network: string; currencyCode: string } - private _web3: any - private _token: any +export default class VetTransferProcessor + implements BlocksoftBlockchainTypes.TransferProcessor +{ + private _settings: { network: string; currencyCode: string }; + private _web3: any; + private _token: any; - constructor(settings: { network: string; currencyCode: string }) { - this._settings = settings - this._web3 = thorify(new Web3(), API_PATH) - this._token = new this._web3.eth.Contract(abi, tokenAddress) - } + constructor(settings: { network: string; currencyCode: string }) { + this._settings = settings; + this._web3 = thorify(new Web3(), API_PATH); + this._token = new this._web3.eth.Contract(abi, tokenAddress); + } - needPrivateForFee(): boolean { - return false - } + needPrivateForFee(): boolean { + return false; + } - checkSendAllModal(data: { currencyCode: any }): boolean { - return false - } + checkSendAllModal(data: { currencyCode: any }): boolean { + return false; + } - async getFeeRate(data: BlocksoftBlockchainTypes.TransferData, privateData: BlocksoftBlockchainTypes.TransferPrivateData, additionalData: {} = {}): Promise { - const result: BlocksoftBlockchainTypes.FeeRateResult = { - selectedFeeIndex: -3, - shouldShowFees: false - } as BlocksoftBlockchainTypes.FeeRateResult + async getFeeRate( + data: BlocksoftBlockchainTypes.TransferData, + privateData: BlocksoftBlockchainTypes.TransferPrivateData, + additionalData: {} = {} + ): Promise { + const result: BlocksoftBlockchainTypes.FeeRateResult = { + selectedFeeIndex: -3, + shouldShowFees: false + } as BlocksoftBlockchainTypes.FeeRateResult; - if (this._settings.currencyCode === 'VET') { - // @todo - /* + if (this._settings.currencyCode === 'VET') { + // @todo + /* result.fees = [ { langMsg: 'xrp_speed_one', @@ -76,136 +82,224 @@ export default class VetTransferProcessor implements BlocksoftBlockchainTypes.Tr ] result.selectedFeeIndex = 0 */ - } else { - result.fees = [ - { - langMsg: 'xrp_speed_one', - feeForTx: '536300000000000000', - amountForTx: data.amount - } - ] - result.selectedFeeIndex = 0 + } else { + result.fees = [ + { + langMsg: 'xrp_speed_one', + feeForTx: '536300000000000000', + amountForTx: data.amount } + ]; + result.selectedFeeIndex = 0; + } + return result; + } - return result - } + async getTransferAllBalance( + data: BlocksoftBlockchainTypes.TransferData, + privateData: BlocksoftBlockchainTypes.TransferPrivateData, + additionalData: BlocksoftBlockchainTypes.TransferAdditionalData = {} + ): Promise { + const balance = data.amount; + // @ts-ignore + await BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' VetTransferProcessor.getTransferAllBalance ', + data.addressFrom + ' => ' + balance + ); - async getTransferAllBalance(data: BlocksoftBlockchainTypes.TransferData, privateData: BlocksoftBlockchainTypes.TransferPrivateData, additionalData: BlocksoftBlockchainTypes.TransferAdditionalData = {}): Promise { - const balance = data.amount - // @ts-ignore - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' VetTransferProcessor.getTransferAllBalance ', data.addressFrom + ' => ' + balance) + const fees = await this.getFeeRate(data, privateData, additionalData); - const fees = await this.getFeeRate(data, privateData, additionalData) + let amount; + if (this._settings.currencyCode === 'VET') { + amount = balance; + } else { + amount = BlocksoftUtils.diff(balance, '536300000000000000').toString(); + } - let amount - if (this._settings.currencyCode === 'VET') { - amount = balance - } else { - amount = BlocksoftUtils.diff(balance, '536300000000000000').toString() - } + return { + ...fees, + shouldShowFees: false, + selectedTransferAllBalance: amount + }; + } - return { - ...fees, - shouldShowFees: false, - selectedTransferAllBalance: amount - } + /** + * https://thorify.vecha.in/#/?id=how-do-i-send-vtho-token + * https://thorify.vecha.in/#/?id=send-transaction-1 + * https://github.com/vechain/thorify/blob/bb7a97e5e62b46af0d45fa119e99539d57e2302a/test/web3/eth.test.ts + * @param data + * @param privateData + * @param uiData + */ + async sendTx( + data: BlocksoftBlockchainTypes.TransferData, + privateData: BlocksoftBlockchainTypes.TransferPrivateData, + uiData: BlocksoftBlockchainTypes.TransferUiData + ): Promise { + if (typeof privateData.privateKey === 'undefined') { + throw new Error('VET transaction required privateKey'); + } + if (typeof data.addressTo === 'undefined') { + throw new Error('VET transaction required addressTo'); } - /** - * https://thorify.vecha.in/#/?id=how-do-i-send-vtho-token - * https://thorify.vecha.in/#/?id=send-transaction-1 - * https://github.com/vechain/thorify/blob/bb7a97e5e62b46af0d45fa119e99539d57e2302a/test/web3/eth.test.ts - * @param data - * @param privateData - * @param uiData - */ - async sendTx(data: BlocksoftBlockchainTypes.TransferData, privateData: BlocksoftBlockchainTypes.TransferPrivateData, uiData: BlocksoftBlockchainTypes.TransferUiData): Promise { - - if (typeof privateData.privateKey === 'undefined') { - throw new Error('VET transaction required privateKey') - } - if (typeof data.addressTo === 'undefined') { - throw new Error('VET transaction required addressTo') - } + let signedData = false; + try { + this._web3.eth.accounts.wallet.add(privateData.privateKey); - let signedData = false - try { - this._web3.eth.accounts.wallet.add(privateData.privateKey) - - if (this._settings.currencyCode === 'VET') { - signedData = await this._web3.eth.accounts.signTransaction({ - from: data.addressFrom, - to: data.addressTo, - value: data.amount, - }, privateData.privateKey) - } else { - const basicAddressTo = data.addressTo.toLowerCase() - const encoded = this._token.methods.transfer(basicAddressTo, data.amount).encodeABI() - signedData = await this._web3.eth.accounts.signTransaction({ - from: data.addressFrom, - to: tokenAddress, - data : encoded, - value: 0, - }, privateData.privateKey) - } - - } catch (e) { - BlocksoftCryptoLog.log(this._settings.currencyCode + ' VetTransferProcessor.sendTx ' + data.addressFrom + ' => ' + data.addressTo + ' ' + data.amount + ' error ' + e.message) - throw new Error(e.message) - } - BlocksoftCryptoLog.log(this._settings.currencyCode + ' VetTransferProcessor.sendTx ' + data.addressFrom + ' => ' + data.addressTo + ' ' + data.amount, signedData) - if (!signedData || typeof signedData.rawTransaction === 'undefined' || !signedData.rawTransaction) { - throw new Error('SYSTEM_ERROR') - } - if (typeof uiData !== 'undefined' && typeof uiData.selectedFee !== 'undefined' && typeof uiData.selectedFee.rawOnly !== 'undefined' && uiData.selectedFee.rawOnly) { - return { rawOnly: uiData.selectedFee.rawOnly, raw : signedData.rawTransaction} - } + if (this._settings.currencyCode === 'VET') { + signedData = await this._web3.eth.accounts.signTransaction( + { + from: data.addressFrom, + to: data.addressTo, + value: data.amount + }, + privateData.privateKey + ); + } else { + const basicAddressTo = data.addressTo.toLowerCase(); + const encoded = this._token.methods + .transfer(basicAddressTo, data.amount) + .encodeABI(); + signedData = await this._web3.eth.accounts.signTransaction( + { + from: data.addressFrom, + to: tokenAddress, + data: encoded, + value: 0 + }, + privateData.privateKey + ); + } + } catch (e) { + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' VetTransferProcessor.sendTx ' + + data.addressFrom + + ' => ' + + data.addressTo + + ' ' + + data.amount + + ' error ' + + e.message + ); + throw new Error(e.message); + } + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' VetTransferProcessor.sendTx ' + + data.addressFrom + + ' => ' + + data.addressTo + + ' ' + + data.amount, + signedData + ); + if ( + !signedData || + typeof signedData.rawTransaction === 'undefined' || + !signedData.rawTransaction + ) { + throw new Error('SYSTEM_ERROR'); + } + if ( + typeof uiData !== 'undefined' && + typeof uiData.selectedFee !== 'undefined' && + typeof uiData.selectedFee.rawOnly !== 'undefined' && + uiData.selectedFee.rawOnly + ) { + return { + rawOnly: uiData.selectedFee.rawOnly, + raw: signedData.rawTransaction + }; + } - let result = {} as BlocksoftBlockchainTypes.SendTxResult - try { - const send = await BlocksoftAxios.post(API_PATH + '/transactions', { raw: signedData.rawTransaction }, false) - BlocksoftCryptoLog.log(this._settings.currencyCode + ' VetTransferProcessor.sendTx ' + data.addressFrom + ' => ' + data.addressTo + ' ' + data.amount, send.data) - if (typeof send.data === 'undefined' || !send.data || typeof send.data.id === 'undefined') { - throw new Error('SYSTEM_ERROR') - } - result.transactionHash = send.data.id - } catch (e) { - BlocksoftCryptoLog.log(this._settings.currencyCode + ' VetTransferProcessor.sendTx ' + data.addressFrom + ' => ' + data.addressTo + ' ' + data.amount + ' send error ' + e.message) - this.checkError(e, data, false) - } - return result + let result = {} as BlocksoftBlockchainTypes.SendTxResult; + try { + const send = await BlocksoftAxios.post( + API_PATH + '/transactions', + { raw: signedData.rawTransaction }, + false + ); + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' VetTransferProcessor.sendTx ' + + data.addressFrom + + ' => ' + + data.addressTo + + ' ' + + data.amount, + send.data + ); + if ( + typeof send.data === 'undefined' || + !send.data || + typeof send.data.id === 'undefined' + ) { + throw new Error('SYSTEM_ERROR'); + } + result.transactionHash = send.data.id; + } catch (e) { + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' VetTransferProcessor.sendTx ' + + data.addressFrom + + ' => ' + + data.addressTo + + ' ' + + data.amount + + ' send error ' + + e.message + ); + this.checkError(e, data, false); } + return result; + } - checkError(e, data, txRBF = false) { - - if (e.message.indexOf('nonce too low') !== -1) { - BlocksoftCryptoLog.log('VeChain checkError0.1 ' + e.message + ' for ' + data.addressFrom) - let e2 - if (txRBF) { - e2 = new Error('SERVER_RESPONSE_TRANSACTION_ALREADY_MINED') - } else { - e2 = new Error('SERVER_RESPONSE_NONCE_ALREADY_MINED') - } - throw e2 - } else if (e.message.indexOf('insufficient funds') !== -1) { - BlocksoftCryptoLog.log('VeChain checkError0.3 ' + e.message + ' for ' + data.addressFrom) - if ((this._settings.currencyCode === 'ETH' || this._settings.currencyCode === 'BNB_SMART') && data.amount * 1 > 0) { - throw new Error('SERVER_RESPONSE_NOTHING_LEFT_FOR_FEE') - } else { - throw new Error('SERVER_RESPONSE_NOT_ENOUGH_FEE') - } - } else if (e.message.indexOf('underpriced') !== -1) { - BlocksoftCryptoLog.log('VeChain checkError0.4 ' + e.message + ' for ' + data.addressFrom) - throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_FEE') - } else if (e.message.indexOf('already known') !== -1) { - BlocksoftCryptoLog.log('VeChain checkError0.5 ' + e.message + ' for ' + data.addressFrom) - throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_FEE') - } else if (e.message.indexOf('insufficient energy') !== -1) { - BlocksoftCryptoLog.log('VeChain checkError0.6 ' + e.message + ' for ' + data.addressFrom) - throw new Error('SERVER_RESPONSE_ENERGY_ERROR_VET') - } else { - throw e - } + checkError(e, data, txRBF = false) { + if (e.message.indexOf('nonce too low') !== -1) { + BlocksoftCryptoLog.log( + 'VeChain checkError0.1 ' + e.message + ' for ' + data.addressFrom + ); + let e2; + if (txRBF) { + e2 = new Error('SERVER_RESPONSE_TRANSACTION_ALREADY_MINED'); + } else { + e2 = new Error('SERVER_RESPONSE_NONCE_ALREADY_MINED'); + } + throw e2; + } else if (e.message.indexOf('insufficient funds') !== -1) { + BlocksoftCryptoLog.log( + 'VeChain checkError0.3 ' + e.message + ' for ' + data.addressFrom + ); + if ( + (this._settings.currencyCode === 'ETH' || + this._settings.currencyCode === 'BNB_SMART') && + data.amount * 1 > 0 + ) { + throw new Error('SERVER_RESPONSE_NOTHING_LEFT_FOR_FEE'); + } else { + throw new Error('SERVER_RESPONSE_NOT_ENOUGH_FEE'); + } + } else if (e.message.indexOf('underpriced') !== -1) { + BlocksoftCryptoLog.log( + 'VeChain checkError0.4 ' + e.message + ' for ' + data.addressFrom + ); + throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_FEE'); + } else if (e.message.indexOf('already known') !== -1) { + BlocksoftCryptoLog.log( + 'VeChain checkError0.5 ' + e.message + ' for ' + data.addressFrom + ); + throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_FEE'); + } else if (e.message.indexOf('insufficient energy') !== -1) { + BlocksoftCryptoLog.log( + 'VeChain checkError0.6 ' + e.message + ' for ' + data.addressFrom + ); + throw new Error('SERVER_RESPONSE_ENERGY_ERROR_VET'); + } else { + throw e; } + } } diff --git a/crypto/blockchains/waves/WavesScannerProcessor.js b/crypto/blockchains/waves/WavesScannerProcessor.js index f7cf50201..792f6d490 100644 --- a/crypto/blockchains/waves/WavesScannerProcessor.js +++ b/crypto/blockchains/waves/WavesScannerProcessor.js @@ -1,162 +1,184 @@ /** * @version 0.5 */ -import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog' -import BlocksoftAxios from '@crypto/common/BlocksoftAxios' -import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings' -import TransactionFilterTypeDict from '@appV2/dicts/transactionFilterTypeDict' -import WavesTransactionsProvider from '@crypto/blockchains/waves/providers/WavesTransactionsProvider' +import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog'; +import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; +import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings'; +import TransactionFilterTypeDict from '@appV2/dicts/transactionFilterTypeDict'; +import WavesTransactionsProvider from '@crypto/blockchains/waves/providers/WavesTransactionsProvider'; export default class WavesScannerProcessor { - - constructor(settings) { - this._settings = settings - this._provider = new WavesTransactionsProvider() - this._mainCurrencyCode = 'WAVES' - if (this._settings.currencyCode === 'ASH' || this._settings.currencyCode.indexOf('ASH_') === 0) { - this._mainCurrencyCode = 'ASH' - } + constructor(settings) { + this._settings = settings; + this._provider = new WavesTransactionsProvider(); + this._mainCurrencyCode = 'WAVES'; + if ( + this._settings.currencyCode === 'ASH' || + this._settings.currencyCode.indexOf('ASH_') === 0 + ) { + this._mainCurrencyCode = 'ASH'; } + } - /** - * https://nodes.wavesnodes.com/addresses/balance/details/3P274YB5qseSE9DTTL3bpSjosZrYBPDpJ8k - * https://nodes.wavesnodes.com/api-docs/index.html#/addresses/getWavesBalances - * https://docs.waves.tech/en/blockchain/account/account-balance#account-balance-in-waves - * @return {Promise<{balance, provider}>} - */ - async getBalanceBlockchain(address) { - if (this._mainCurrencyCode == 'ASH') { - this._apiPath = await BlocksoftExternalSettings.get('ASH_SERVER') - } else { - this._apiPath = await BlocksoftExternalSettings.get('WAVES_SERVER') - } - address = address.trim() - BlocksoftCryptoLog.log(this._settings.currencyCode + ' WavesScannerProcessor getBalanceBlockchain address ' + address) - - const link = this._apiPath + '/addresses/balance/details/' + address - const res = await BlocksoftAxios.get(link) - if (!res) { - return false - } - try { - return { balance: res.data.available, unconfirmed: 0, provider: 'wavesnodes.com' } - } catch (e) { - BlocksoftCryptoLog.log(this._settings.currencyCode + ' WavesProcessor getBalanceBlockchain address ' + address + ' balance ' + JSON.stringify(res) + ' to hex error ' + e.message) - } - return false + /** + * https://nodes.wavesnodes.com/addresses/balance/details/3P274YB5qseSE9DTTL3bpSjosZrYBPDpJ8k + * https://nodes.wavesnodes.com/api-docs/index.html#/addresses/getWavesBalances + * https://docs.waves.tech/en/blockchain/account/account-balance#account-balance-in-waves + * @return {Promise<{balance, provider}>} + */ + async getBalanceBlockchain(address) { + if (this._mainCurrencyCode == 'ASH') { + this._apiPath = await BlocksoftExternalSettings.get('ASH_SERVER'); + } else { + this._apiPath = await BlocksoftExternalSettings.get('WAVES_SERVER'); } + address = address.trim(); + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' WavesScannerProcessor getBalanceBlockchain address ' + + address + ); - /** - * https://nodes.wavesnodes.com/transactions/address/3P274YB5qseSE9DTTL3bpSjosZrYBPDpJ8k/limit/100 - * @param {string} scanData.account.address - * @return {Promise<[UnifiedTransaction]>} - */ - async getTransactionsBlockchain(scanData, source) { - const address = scanData.account.address.trim() - const data = await this._provider.get(address, this._mainCurrencyCode) - const transactions = [] - for (const tx of data) { - const transaction = await this._unifyTransaction(address, tx) - if (transaction) { - transactions.push(transaction) - } - } - return transactions + const link = this._apiPath + '/addresses/balance/details/' + address; + const res = await BlocksoftAxios.get(link); + if (!res) { + return false; + } + try { + return { + balance: res.data.available, + unconfirmed: 0, + provider: 'wavesnodes.com' + }; + } catch (e) { + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' WavesProcessor getBalanceBlockchain address ' + + address + + ' balance ' + + JSON.stringify(res) + + ' to hex error ' + + e.message + ); } + return false; + } - /** - * @param address - * @param transaction.amount 100000000 - * @param transaction.applicationStatus succeeded - * @param transaction.assetId null - * @param transaction.attachment - * @param transaction.fee 100000 - * @param transaction.feeAsset - * @param transaction.feeAssetId - * @param transaction.height 2715839 - * @param transaction.id GxnhfderDpMwdrSfWbTN53NGB1Q2NRQXcGvYdXbzQBXo - * @param transaction.recipient 3PQQUuGM1Fo8zz72i62dNYkB5kRxqmJkoSu - * @param transaction.sender 3PLPGmXoDNKeWxSgJRU5vDNogbPj7hJiWQx - * @param transaction.senderPublicKey GP9hPWAiGDfNYyCTNw6ZWoLCzUqWiYj7MybtPcu8mpkg - * @param transaction.signature 4FewzMCYLvfQridUZtSFrDXRbDvmawUWtBxWdiEE5CeruG1qfbKbfTkudGyW6Eqs3kW4hTpABQxrhBSBuKV7uHFa - * @param transaction.timestamp 1628523063656 - * @param transaction.type 4 - * @param transaction.version 1 - * - * @returns {Promise} - * @private - */ - async _unifyTransaction(address, transaction) { - let transactionStatus = 'confirming' - if (transaction.applicationStatus === 'succeeded') { - transactionStatus = 'success' - } else if (transaction.applicationStatus === 'script_execution_failed') { - transactionStatus = 'fail' - } - let formattedTime = transaction.timestamp - const blockConfirmations = Math.round((new Date().getTime() - transaction.timestamp) / 6000) - try { - formattedTime = new Date(transaction.timestamp).toISOString() - } catch (e) { - e.message += ' timestamp error transaction2 data ' + JSON.stringify(transaction) - throw e - } - const addressFrom = transaction.sender - const addressTo = transaction.recipient || address + /** + * https://nodes.wavesnodes.com/transactions/address/3P274YB5qseSE9DTTL3bpSjosZrYBPDpJ8k/limit/100 + * @param {string} scanData.account.address + * @return {Promise<[UnifiedTransaction]>} + */ + async getTransactionsBlockchain(scanData, source) { + const address = scanData.account.address.trim(); + const data = await this._provider.get(address, this._mainCurrencyCode); + const transactions = []; + for (const tx of data) { + const transaction = await this._unifyTransaction(address, tx); + if (transaction) { + transactions.push(transaction); + } + } + return transactions; + } - let transactionFilterType = TransactionFilterTypeDict.USUAL - let transactionDirection = 'self' - if (addressFrom === address) { - if (addressTo !== address) { - transactionDirection = 'outcome' - } - } else if (addressTo === address) { - transactionDirection = 'income' - } + /** + * @param address + * @param transaction.amount 100000000 + * @param transaction.applicationStatus succeeded + * @param transaction.assetId null + * @param transaction.attachment + * @param transaction.fee 100000 + * @param transaction.feeAsset + * @param transaction.feeAssetId + * @param transaction.height 2715839 + * @param transaction.id GxnhfderDpMwdrSfWbTN53NGB1Q2NRQXcGvYdXbzQBXo + * @param transaction.recipient 3PQQUuGM1Fo8zz72i62dNYkB5kRxqmJkoSu + * @param transaction.sender 3PLPGmXoDNKeWxSgJRU5vDNogbPj7hJiWQx + * @param transaction.senderPublicKey GP9hPWAiGDfNYyCTNw6ZWoLCzUqWiYj7MybtPcu8mpkg + * @param transaction.signature 4FewzMCYLvfQridUZtSFrDXRbDvmawUWtBxWdiEE5CeruG1qfbKbfTkudGyW6Eqs3kW4hTpABQxrhBSBuKV7uHFa + * @param transaction.timestamp 1628523063656 + * @param transaction.type 4 + * @param transaction.version 1 + * + * @returns {Promise} + * @private + */ + async _unifyTransaction(address, transaction) { + let transactionStatus = 'confirming'; + if (transaction.applicationStatus === 'succeeded') { + transactionStatus = 'success'; + } else if (transaction.applicationStatus === 'script_execution_failed') { + transactionStatus = 'fail'; + } + let formattedTime = transaction.timestamp; + const blockConfirmations = Math.round( + (new Date().getTime() - transaction.timestamp) / 6000 + ); + try { + formattedTime = new Date(transaction.timestamp).toISOString(); + } catch (e) { + e.message += + ' timestamp error transaction2 data ' + JSON.stringify(transaction); + throw e; + } + const addressFrom = transaction.sender; + const addressTo = transaction.recipient || address; - let addressAmount = 0 - if (typeof transaction.transfers !== 'undefined') { - for (const transfer of transaction.transfers) { - if (transfer.recipient === address && transfer.amount*1>0) { - addressAmount = addressAmount + transfer.amount*1 - transactionDirection = 'income' - } - } - } - if (addressAmount === 0 && typeof transaction.amount !== 'undefined'){ - addressAmount = transaction.amount - } + let transactionFilterType = TransactionFilterTypeDict.USUAL; + let transactionDirection = 'self'; + if (addressFrom === address) { + if (addressTo !== address) { + transactionDirection = 'outcome'; + } + } else if (addressTo === address) { + transactionDirection = 'income'; + } - const transactionFee = transaction.feeAsset && transaction.feeAssetId ? 0 : transaction.fee - if (transaction.assetId) { - return false + let addressAmount = 0; + if (typeof transaction.transfers !== 'undefined') { + for (const transfer of transaction.transfers) { + if (transfer.recipient === address && transfer.amount * 1 > 0) { + addressAmount = addressAmount + transfer.amount * 1; + transactionDirection = 'income'; } + } + } + if (addressAmount === 0 && typeof transaction.amount !== 'undefined') { + addressAmount = transaction.amount; + } - if (typeof transaction.order1 !== 'undefined') { - if (transaction.order2.amount === addressAmount) { - transactionDirection = 'swap_outcome' - } else { - transactionDirection = 'swap_income' - } - transactionFilterType = TransactionFilterTypeDict.SWAP - } + const transactionFee = + transaction.feeAsset && transaction.feeAssetId ? 0 : transaction.fee; + if (transaction.assetId) { + return false; + } - const tmp = { - transactionHash: transaction.id, - blockHash: transaction.height, - blockNumber: transaction.height, - blockTime: formattedTime, - blockConfirmations, - transactionDirection, - transactionFilterType, - addressFrom: addressFrom === address ? '' : addressFrom, - addressFromBasic: addressFrom, - addressTo: addressTo === address ? '' : addressTo, - addressToBasic: addressTo, - addressAmount, - transactionStatus, - transactionFee - } - return tmp + if (typeof transaction.order1 !== 'undefined') { + if (transaction.order2.amount === addressAmount) { + transactionDirection = 'swap_outcome'; + } else { + transactionDirection = 'swap_income'; + } + transactionFilterType = TransactionFilterTypeDict.SWAP; } + + const tmp = { + transactionHash: transaction.id, + blockHash: transaction.height, + blockNumber: transaction.height, + blockTime: formattedTime, + blockConfirmations, + transactionDirection, + transactionFilterType, + addressFrom: addressFrom === address ? '' : addressFrom, + addressFromBasic: addressFrom, + addressTo: addressTo === address ? '' : addressTo, + addressToBasic: addressTo, + addressAmount, + transactionStatus, + transactionFee + }; + return tmp; + } } diff --git a/crypto/blockchains/waves/WavesScannerProcessorErc20.js b/crypto/blockchains/waves/WavesScannerProcessorErc20.js index d3664d000..c25e8679a 100644 --- a/crypto/blockchains/waves/WavesScannerProcessorErc20.js +++ b/crypto/blockchains/waves/WavesScannerProcessorErc20.js @@ -1,113 +1,135 @@ /** * @version 0.5 */ -import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog' -import BlocksoftAxios from '@crypto/common/BlocksoftAxios' -import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings' -import WavesScannerProcessor from '@crypto/blockchains/waves/WavesScannerProcessor' -import TransactionFilterTypeDict from '@appV2/dicts/transactionFilterTypeDict' -import BlocksoftUtils from '@crypto/common/BlocksoftUtils' +import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; +import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; +import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings'; +import WavesScannerProcessor from '@crypto/blockchains/waves/WavesScannerProcessor'; +import TransactionFilterTypeDict from '@appV2/dicts/transactionFilterTypeDict'; +import BlocksoftUtils from '@crypto/common/BlocksoftUtils'; export default class WavesScannerProcessorErc20 extends WavesScannerProcessor { + constructor(settings) { + super(settings); + this._tokenAddress = settings.tokenAddress; + } - constructor(settings) { - super(settings) - this._tokenAddress = settings.tokenAddress + /** + * https://nodes.wavesnodes.com/api-docs/index.html#/assets/getAssetBalanceByAddress + * https://nodes.wavesnodes.com/assets/balance/3PGixfWP1pcuWwND2rDLf2n94J7egSf4uz4/DG2xFkPdDwKUoBkzGAhQtLpSGzfXLiCYPEzeKH2Ad24p + * @return {Promise<{balance, provider}>} + */ + async getBalanceBlockchain(address) { + if (this._mainCurrencyCode === 'ASH') { + this._apiPath = await BlocksoftExternalSettings.get('ASH_SERVER'); + } else { + this._apiPath = await BlocksoftExternalSettings.get('WAVES_SERVER'); } + address = address.trim(); + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' WavesScannerProcessorErc20 getBalanceBlockchain address ' + + address + ); - /** - * https://nodes.wavesnodes.com/api-docs/index.html#/assets/getAssetBalanceByAddress - * https://nodes.wavesnodes.com/assets/balance/3PGixfWP1pcuWwND2rDLf2n94J7egSf4uz4/DG2xFkPdDwKUoBkzGAhQtLpSGzfXLiCYPEzeKH2Ad24p - * @return {Promise<{balance, provider}>} - */ - async getBalanceBlockchain(address) { - - if (this._mainCurrencyCode === 'ASH') { - this._apiPath = await BlocksoftExternalSettings.get('ASH_SERVER') - } else { - this._apiPath = await BlocksoftExternalSettings.get('WAVES_SERVER') - } - address = address.trim() - BlocksoftCryptoLog.log(this._settings.currencyCode + ' WavesScannerProcessorErc20 getBalanceBlockchain address ' + address) - - const link = this._apiPath + '/assets/balance/' + address + '/' + this._tokenAddress - const res = await BlocksoftAxios.get(link) - if (!res) { - return false - } - try { - return { balance: res.data.balance, unconfirmed: 0, provider: 'wavesnodes.com' } - } catch (e) { - BlocksoftCryptoLog.log(this._settings.currencyCode + ' WavesProcessorErc20 getBalanceBlockchain address ' + address + ' balance ' + JSON.stringify(res) + ' to hex error ' + e.message) - } - return false + const link = + this._apiPath + '/assets/balance/' + address + '/' + this._tokenAddress; + const res = await BlocksoftAxios.get(link); + if (!res) { + return false; } + try { + return { + balance: res.data.balance, + unconfirmed: 0, + provider: 'wavesnodes.com' + }; + } catch (e) { + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' WavesProcessorErc20 getBalanceBlockchain address ' + + address + + ' balance ' + + JSON.stringify(res) + + ' to hex error ' + + e.message + ); + } + return false; + } - async _unifyTransaction(address, transaction) { - let transactionStatus = 'confirming' - if (transaction.applicationStatus === 'succeeded') { - transactionStatus = 'success' - } else if (transaction.applicationStatus === 'script_execution_failed') { - transactionStatus = 'fail' - } - let formattedTime = transaction.timestamp - const blockConfirmations = Math.round((new Date().getTime() - transaction.timestamp) / 6000) - try { - formattedTime = new Date(transaction.timestamp).toISOString() - } catch (e) { - e.message += ' timestamp error transaction2 data ' + JSON.stringify(transaction) - throw e - } - const addressFrom = transaction.sender - const addressTo = transaction.recipient || address - let addressAmount = transaction.amount - const transactionFee = transaction.feeAsset && transaction.feeAssetId ? 0 : transaction.fee - - let transactionDirection = false - let transactionFilterType = TransactionFilterTypeDict.USUAL + async _unifyTransaction(address, transaction) { + let transactionStatus = 'confirming'; + if (transaction.applicationStatus === 'succeeded') { + transactionStatus = 'success'; + } else if (transaction.applicationStatus === 'script_execution_failed') { + transactionStatus = 'fail'; + } + let formattedTime = transaction.timestamp; + const blockConfirmations = Math.round( + (new Date().getTime() - transaction.timestamp) / 6000 + ); + try { + formattedTime = new Date(transaction.timestamp).toISOString(); + } catch (e) { + e.message += + ' timestamp error transaction2 data ' + JSON.stringify(transaction); + throw e; + } + const addressFrom = transaction.sender; + const addressTo = transaction.recipient || address; + let addressAmount = transaction.amount; + const transactionFee = + transaction.feeAsset && transaction.feeAssetId ? 0 : transaction.fee; - if (typeof transaction.order1 !== 'undefined') { - if (transaction.order1.assetPair.priceAsset !== this._tokenAddress) { - return false - } - if (transaction.order2.amount === addressAmount) { - transactionDirection = 'swap_income' - addressAmount = transaction.order2.amount * transaction.order1.price - } else { - transactionDirection = 'swap_outcome' - addressAmount = transaction.order1.amount * transaction.order2.price - } - transactionFilterType = TransactionFilterTypeDict.SWAP - addressAmount = BlocksoftUtils.fromUnified(BlocksoftUtils.toUnified(addressAmount, 14), this._settings.decimals) - } else if (transaction.assetId === this._tokenAddress) { - transactionDirection = 'self' - if (addressFrom === address) { - if (addressTo !== address) { - transactionDirection = 'outcome' - } - } else if (addressTo === address) { - transactionDirection = 'income' - } - } else { - return false - } + let transactionDirection = false; + let transactionFilterType = TransactionFilterTypeDict.USUAL; - const tmp = { - transactionHash: transaction.id, - blockHash: transaction.height, - blockNumber: transaction.height, - blockTime: formattedTime, - blockConfirmations, - transactionDirection, - transactionFilterType, - addressFrom: addressFrom === address ? '' : addressFrom, - addressFromBasic: addressFrom, - addressTo: addressTo === address ? '' : addressTo, - addressToBasic: addressTo, - addressAmount, - transactionStatus, - transactionFee + if (typeof transaction.order1 !== 'undefined') { + if (transaction.order1.assetPair.priceAsset !== this._tokenAddress) { + return false; + } + if (transaction.order2.amount === addressAmount) { + transactionDirection = 'swap_income'; + addressAmount = transaction.order2.amount * transaction.order1.price; + } else { + transactionDirection = 'swap_outcome'; + addressAmount = transaction.order1.amount * transaction.order2.price; + } + transactionFilterType = TransactionFilterTypeDict.SWAP; + addressAmount = BlocksoftUtils.fromUnified( + BlocksoftUtils.toUnified(addressAmount, 14), + this._settings.decimals + ); + } else if (transaction.assetId === this._tokenAddress) { + transactionDirection = 'self'; + if (addressFrom === address) { + if (addressTo !== address) { + transactionDirection = 'outcome'; } - return tmp + } else if (addressTo === address) { + transactionDirection = 'income'; + } + } else { + return false; } + + const tmp = { + transactionHash: transaction.id, + blockHash: transaction.height, + blockNumber: transaction.height, + blockTime: formattedTime, + blockConfirmations, + transactionDirection, + transactionFilterType, + addressFrom: addressFrom === address ? '' : addressFrom, + addressFromBasic: addressFrom, + addressTo: addressTo === address ? '' : addressTo, + addressToBasic: addressTo, + addressAmount, + transactionStatus, + transactionFee + }; + return tmp; + } } diff --git a/crypto/blockchains/waves/WavesTransferProcessor.ts b/crypto/blockchains/waves/WavesTransferProcessor.ts index fbfcca1f4..237856cb7 100644 --- a/crypto/blockchains/waves/WavesTransferProcessor.ts +++ b/crypto/blockchains/waves/WavesTransferProcessor.ts @@ -1,159 +1,274 @@ /** * @version 0.5 */ -import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog' -import BlocksoftUtils from '@crypto/common/BlocksoftUtils' +import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; +import BlocksoftUtils from '@crypto/common/BlocksoftUtils'; -import { BlocksoftBlockchainTypes } from '@crypto/blockchains/BlocksoftBlockchainTypes' +import { BlocksoftBlockchainTypes } from '@crypto/blockchains/BlocksoftBlockchainTypes'; -import { transfer, broadcast } from '@waves/waves-transactions/src/index' -import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings' -import config from '@app/config/config' -import MarketingEvent from '@app/services/Marketing/MarketingEvent' +import { transfer, broadcast } from '@waves/waves-transactions/src/index'; +import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings'; +import config from '@app/config/config'; +import MarketingEvent from '@app/services/Marketing/MarketingEvent'; -export default class WavesTransferProcessor implements BlocksoftBlockchainTypes.TransferProcessor { - private _settings: { network: string; currencyCode: string } +export default class WavesTransferProcessor + implements BlocksoftBlockchainTypes.TransferProcessor +{ + private _settings: { network: string; currencyCode: string }; - private _tokenAddress: string + private _tokenAddress: string; - private _mainCurrencyCode: string + private _mainCurrencyCode: string; - constructor(settings: { network: string; currencyCode: string }) { - this._settings = settings - this._tokenAddress = typeof settings.tokenAddress !== 'undefined' ? settings.tokenAddress : false + constructor(settings: { network: string; currencyCode: string }) { + this._settings = settings; + this._tokenAddress = + typeof settings.tokenAddress !== 'undefined' + ? settings.tokenAddress + : false; - this._mainCurrencyCode = 'WAVES' - if (this._settings.currencyCode === 'ASH' || this._settings.currencyCode.indexOf('ASH_') === 0) { - this._mainCurrencyCode = 'ASH' - } + this._mainCurrencyCode = 'WAVES'; + if ( + this._settings.currencyCode === 'ASH' || + this._settings.currencyCode.indexOf('ASH_') === 0 + ) { + this._mainCurrencyCode = 'ASH'; } + } - needPrivateForFee(): boolean { - return false - } + needPrivateForFee(): boolean { + return false; + } + + checkSendAllModal(data: { currencyCode: any }): boolean { + return false; + } + + async getFeeRate( + data: BlocksoftBlockchainTypes.TransferData, + privateData: BlocksoftBlockchainTypes.TransferPrivateData, + additionalData: {} = {} + ): Promise { + const result: BlocksoftBlockchainTypes.FeeRateResult = { + selectedFeeIndex: -3, + shouldShowFees: false + } as BlocksoftBlockchainTypes.FeeRateResult; + + result.fees = [ + { + langMsg: 'xrp_speed_one', + feeForTx: '100000', + amountForTx: data.amount + } + ]; + result.selectedFeeIndex = 0; + + return result; + } + + async getTransferAllBalance( + data: BlocksoftBlockchainTypes.TransferData, + privateData: BlocksoftBlockchainTypes.TransferPrivateData, + additionalData: BlocksoftBlockchainTypes.TransferAdditionalData = {} + ): Promise { + const balance = data.amount; + // @ts-ignore + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' WavesTransferProcessor.getTransferAllBalance ', + data.addressFrom + ' => ' + balance + ); - checkSendAllModal(data: { currencyCode: any }): boolean { - return false + const res = await this.getFeeRate(data, privateData, additionalData); + let amount; + if (this._tokenAddress) { + amount = balance; + } else { + amount = BlocksoftUtils.diff(balance, '100000').toString(); + res.fees[0].amountForTx = amount; } - async getFeeRate(data: BlocksoftBlockchainTypes.TransferData, privateData: BlocksoftBlockchainTypes.TransferPrivateData, additionalData: {} = {}): Promise { - const result: BlocksoftBlockchainTypes.FeeRateResult = { - selectedFeeIndex: -3, - shouldShowFees: false - } as BlocksoftBlockchainTypes.FeeRateResult + return { + ...res, + shouldShowFees: false, + selectedTransferAllBalance: amount + }; + } - result.fees = [ - { - langMsg: 'xrp_speed_one', - feeForTx: '100000', - amountForTx: data.amount - } - ] - result.selectedFeeIndex = 0 + /** + * https://docs.waves.tech/en/building-apps/how-to/basic/transaction#sign-transaction-using-your-own-seed + * @param data + * @param privateData + * @param uiData + */ + async sendTx( + data: BlocksoftBlockchainTypes.TransferData, + privateData: BlocksoftBlockchainTypes.TransferPrivateData, + uiData: BlocksoftBlockchainTypes.TransferUiData + ): Promise { + if (typeof privateData.privateKey === 'undefined') { + throw new Error('WAVES transaction required privateKey'); + } + if (typeof data.addressTo === 'undefined') { + throw new Error('WAVES transaction required addressTo'); + } + let addressTo = data.addressTo; + let apiPath; + if (this._mainCurrencyCode === 'ASH') { + apiPath = await BlocksoftExternalSettings.get('ASH_SERVER'); + addressTo = addressTo.replace('Æx', ''); + } else { + apiPath = await BlocksoftExternalSettings.get('WAVES_SERVER'); + } - return result + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' WavesTransferProcessor.sendTx started ' + + data.addressFrom + + ' => ' + + data.addressTo + + ' ' + + data.amount + ); + + let signedData = false; + try { + const money = { + recipient: addressTo, + amount: data.amount + }; + if (this._tokenAddress) { + money.assetId = this._tokenAddress; + } + signedData = transfer(money, { privateKey: privateData.privateKey }); + } catch (e) { + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' WavesTransferProcessor.sendTx ' + + data.addressFrom + + ' => ' + + data.addressTo + + ' ' + + data.amount + + ' signedData error ' + + e.message + ); + throw new Error(e.message); + } + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' WavesTransferProcessor.sendTx ' + + data.addressFrom + + ' => ' + + data.addressTo + + ' ' + + data.amount + + ' signedData', + signedData + ); + if (!signedData || typeof signedData.id === 'undefined' || !signedData.id) { + throw new Error('SYSTEM_ERROR'); } - async getTransferAllBalance(data: BlocksoftBlockchainTypes.TransferData, privateData: BlocksoftBlockchainTypes.TransferPrivateData, additionalData: BlocksoftBlockchainTypes.TransferAdditionalData = {}): Promise { - const balance = data.amount - // @ts-ignore - BlocksoftCryptoLog.log(this._settings.currencyCode + ' WavesTransferProcessor.getTransferAllBalance ', data.addressFrom + ' => ' + balance) - - const res = await this.getFeeRate(data, privateData, additionalData) - let amount - if (this._tokenAddress) { - amount = balance - } else { - amount = BlocksoftUtils.diff(balance, '100000').toString() - res.fees[0].amountForTx = amount - } - - return { - ...res, - shouldShowFees: false, - selectedTransferAllBalance: amount, - } + if ( + typeof uiData !== 'undefined' && + typeof uiData.selectedFee !== 'undefined' && + typeof uiData.selectedFee.rawOnly !== 'undefined' && + uiData.selectedFee.rawOnly + ) { + return { + rawOnly: uiData.selectedFee.rawOnly, + raw: JSON.stringify(signedData) + }; } - /** - * https://docs.waves.tech/en/building-apps/how-to/basic/transaction#sign-transaction-using-your-own-seed - * @param data - * @param privateData - * @param uiData - */ - async sendTx(data: BlocksoftBlockchainTypes.TransferData, privateData: BlocksoftBlockchainTypes.TransferPrivateData, uiData: BlocksoftBlockchainTypes.TransferUiData): Promise { - if (typeof privateData.privateKey === 'undefined') { - throw new Error('WAVES transaction required privateKey') - } - if (typeof data.addressTo === 'undefined') { - throw new Error('WAVES transaction required addressTo') - } - - let addressTo = data.addressTo - let apiPath - if (this._mainCurrencyCode === 'ASH') { - apiPath = await BlocksoftExternalSettings.get('ASH_SERVER') - addressTo = addressTo.replace('Æx', '') - } else { - apiPath = await BlocksoftExternalSettings.get('WAVES_SERVER') - } - - BlocksoftCryptoLog.log(this._settings.currencyCode + ' WavesTransferProcessor.sendTx started ' + data.addressFrom + ' => ' + data.addressTo + ' ' + data.amount) - - let signedData = false - try { - const money = { - recipient: addressTo, - amount: data.amount - } - if (this._tokenAddress) { - money.assetId = this._tokenAddress - } - signedData = transfer(money, { privateKey: privateData.privateKey }) - - } catch (e) { - BlocksoftCryptoLog.log(this._settings.currencyCode + ' WavesTransferProcessor.sendTx ' + data.addressFrom + ' => ' + data.addressTo + ' ' + data.amount + ' signedData error ' + e.message) - throw new Error(e.message) - } - BlocksoftCryptoLog.log(this._settings.currencyCode + ' WavesTransferProcessor.sendTx ' + data.addressFrom + ' => ' + data.addressTo + ' ' + data.amount + ' signedData', signedData) - if (!signedData || typeof signedData.id === 'undefined' || !signedData.id) { - throw new Error('SYSTEM_ERROR') - } - - if (typeof uiData !== 'undefined' && typeof uiData.selectedFee !== 'undefined' && typeof uiData.selectedFee.rawOnly !== 'undefined' && uiData.selectedFee.rawOnly) { - return { rawOnly: uiData.selectedFee.rawOnly, raw: JSON.stringify(signedData) } - } - - let result = {} as BlocksoftBlockchainTypes.SendTxResult - try { - const resp = await new Promise((resolve, reject) => { - BlocksoftCryptoLog.log(this._settings.currencyCode + ' WavesTransferProcessor.sendTx ' + data.addressFrom + ' => ' + data.addressTo + ' ' + data.amount + ' will broadCast ' + JSON.stringify(apiPath)) - broadcast(signedData, apiPath).then(resp => { - resolve(resp) - }).catch(e => { - reject(e) - }) - - }) - BlocksoftCryptoLog.log(this._settings.currencyCode + ' WavesTransferProcessor.sendTx ' + data.addressFrom + ' => ' + data.addressTo + ' ' + data.amount + ' send res ' + resp) - result.transactionHash = signedData.id - } catch (e) { - if (config.debug.cryptoErrors) { - console.log(this._settings.currencyCode + ' WavesTransferProcessor.sendTx ' + data.addressFrom + ' => ' + data.addressTo + ' ' + data.amount + ' send error ' + e.message) - } - BlocksoftCryptoLog.log(this._settings.currencyCode + ' WavesTransferProcessor.sendTx ' + data.addressFrom + ' => ' + data.addressTo + ' ' + data.amount + ' send error ' + e.message) - this.checkError(e, data, false) - } - return result + let result = {} as BlocksoftBlockchainTypes.SendTxResult; + try { + const resp = await new Promise((resolve, reject) => { + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' WavesTransferProcessor.sendTx ' + + data.addressFrom + + ' => ' + + data.addressTo + + ' ' + + data.amount + + ' will broadCast ' + + JSON.stringify(apiPath) + ); + broadcast(signedData, apiPath) + .then((resp) => { + resolve(resp); + }) + .catch((e) => { + reject(e); + }); + }); + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' WavesTransferProcessor.sendTx ' + + data.addressFrom + + ' => ' + + data.addressTo + + ' ' + + data.amount + + ' send res ' + + resp + ); + result.transactionHash = signedData.id; + } catch (e) { + if (config.debug.cryptoErrors) { + console.log( + this._settings.currencyCode + + ' WavesTransferProcessor.sendTx ' + + data.addressFrom + + ' => ' + + data.addressTo + + ' ' + + data.amount + + ' send error ' + + e.message + ); + } + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' WavesTransferProcessor.sendTx ' + + data.addressFrom + + ' => ' + + data.addressTo + + ' ' + + data.amount + + ' send error ' + + e.message + ); + this.checkError(e, data, false); } + return result; + } - checkError(e, data, txRBF = false) { - if (e.message.indexOf('waves balance to (at least) temporary negative state') !== -1) { - throw new Error('SERVER_RESPONSE_NOTHING_LEFT_FOR_FEE') - } else { - MarketingEvent.logOnlyRealTime('v20_' + this._settings.currencyCode + '_tx_error ' + this._settings.currencyCode + ' ' + data.addressFrom + ' => ' + data.addressTo + ' ' + e.message, false) - throw e - } + checkError(e, data, txRBF = false) { + if ( + e.message.indexOf( + 'waves balance to (at least) temporary negative state' + ) !== -1 + ) { + throw new Error('SERVER_RESPONSE_NOTHING_LEFT_FOR_FEE'); + } else { + MarketingEvent.logOnlyRealTime( + 'v20_' + + this._settings.currencyCode + + '_tx_error ' + + this._settings.currencyCode + + ' ' + + data.addressFrom + + ' => ' + + data.addressTo + + ' ' + + e.message, + false + ); + throw e; } + } } diff --git a/crypto/blockchains/waves/providers/WavesTransactionsProvider.js b/crypto/blockchains/waves/providers/WavesTransactionsProvider.js index f64cf921b..b1fa63ba7 100644 --- a/crypto/blockchains/waves/providers/WavesTransactionsProvider.js +++ b/crypto/blockchains/waves/providers/WavesTransactionsProvider.js @@ -1,42 +1,50 @@ /** * @version 0.50 */ -import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings' -import BlocksoftAxios from '@crypto/common/BlocksoftAxios' -import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog' +import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings'; +import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; +import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; const CACHE_OF_TRANSACTIONS = { - ASH : {}, - WAVES : {} -} -const CACHE_VALID_TIME = 30000 // 30 seconds + ASH: {}, + WAVES: {} +}; +const CACHE_VALID_TIME = 30000; // 30 seconds export default class WavesTransactionsProvider { - - async get(address, mainCurrencyCode) { - let _apiPath - if (mainCurrencyCode === 'ASH') { - _apiPath = await BlocksoftExternalSettings.get('ASH_SERVER') - } else { - _apiPath = await BlocksoftExternalSettings.get('WAVES_SERVER') - } - const now = new Date().getTime() - if (typeof CACHE_OF_TRANSACTIONS[mainCurrencyCode][address] !== 'undefined' && (now - CACHE_OF_TRANSACTIONS[mainCurrencyCode][address].time) < CACHE_VALID_TIME) { - if (typeof CACHE_OF_TRANSACTIONS[mainCurrencyCode][address] !== 'undefined') { - BlocksoftCryptoLog.log(' WavesTransactionsProvider.get from cache', address + ' => ' + mainCurrencyCode) - return CACHE_OF_TRANSACTIONS[mainCurrencyCode][address].data - } - } - const link = _apiPath + '/transactions/address/' + address + '/limit/100' - const res = await BlocksoftAxios.get(link) - if (!res || !res.data || typeof res.data[0] === 'undefined') { - return false - } - - CACHE_OF_TRANSACTIONS[mainCurrencyCode][address] = { - data : res.data[0], - time : now - } - return res.data[0] + async get(address, mainCurrencyCode) { + let _apiPath; + if (mainCurrencyCode === 'ASH') { + _apiPath = await BlocksoftExternalSettings.get('ASH_SERVER'); + } else { + _apiPath = await BlocksoftExternalSettings.get('WAVES_SERVER'); } + const now = new Date().getTime(); + if ( + typeof CACHE_OF_TRANSACTIONS[mainCurrencyCode][address] !== 'undefined' && + now - CACHE_OF_TRANSACTIONS[mainCurrencyCode][address].time < + CACHE_VALID_TIME + ) { + if ( + typeof CACHE_OF_TRANSACTIONS[mainCurrencyCode][address] !== 'undefined' + ) { + BlocksoftCryptoLog.log( + ' WavesTransactionsProvider.get from cache', + address + ' => ' + mainCurrencyCode + ); + return CACHE_OF_TRANSACTIONS[mainCurrencyCode][address].data; + } + } + const link = _apiPath + '/transactions/address/' + address + '/limit/100'; + const res = await BlocksoftAxios.get(link); + if (!res || !res.data || typeof res.data[0] === 'undefined') { + return false; + } + + CACHE_OF_TRANSACTIONS[mainCurrencyCode][address] = { + data: res.data[0], + time: now + }; + return res.data[0]; + } } diff --git a/crypto/blockchains/xlm/XlmScannerProcessor.js b/crypto/blockchains/xlm/XlmScannerProcessor.js index 1d5de6cb6..b29be069a 100644 --- a/crypto/blockchains/xlm/XlmScannerProcessor.js +++ b/crypto/blockchains/xlm/XlmScannerProcessor.js @@ -1,212 +1,264 @@ /** * @version 0.20 */ -import BlocksoftAxios from '../../common/BlocksoftAxios' -import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog' -import BlocksoftUtils from '../../common/BlocksoftUtils' -import config from '../../../app/config/config' +import BlocksoftAxios from '../../common/BlocksoftAxios'; +import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog'; +import BlocksoftUtils from '../../common/BlocksoftUtils'; +import config from '../../../app/config/config'; -const API_PATH = 'https://horizon.stellar.org' -const FEE_DECIMALS = 7 +const API_PATH = 'https://horizon.stellar.org'; +const FEE_DECIMALS = 7; export default class XlmScannerProcessor { - - - /** - * https://horizon.stellar.org/accounts/GBH4TZYZ4IRCPO44CBOLFUHULU2WGALXTAVESQA6432MBJMABBB4GIYI - * @param {string} address - * @return {Promise<{balance, unconfirmed, provider}>} - */ - async getBalanceBlockchain(address) { - const link = `${API_PATH}/accounts/${address}` - let res = false - let balance = 0 - try { - res = await BlocksoftAxios.getWithoutBraking(link) - if (res && typeof res.data !== 'undefined' && res.data && typeof res.data.balances !== 'undefined') { - let row - for (row of res.data.balances) { - if (row.asset_type === 'native') { - balance = row.balance - break - } - } - } else { - return false - } - } catch (e) { - if (e.message.indexOf('timed out') === -1 && e.message.indexOf('account not found') === -1 && e.message.indexOf('the resource at the url requested was not found') === -1 ) { - throw e - } else { - return false - } + /** + * https://horizon.stellar.org/accounts/GBH4TZYZ4IRCPO44CBOLFUHULU2WGALXTAVESQA6432MBJMABBB4GIYI + * @param {string} address + * @return {Promise<{balance, unconfirmed, provider}>} + */ + async getBalanceBlockchain(address) { + const link = `${API_PATH}/accounts/${address}`; + let res = false; + let balance = 0; + try { + res = await BlocksoftAxios.getWithoutBraking(link); + if ( + res && + typeof res.data !== 'undefined' && + res.data && + typeof res.data.balances !== 'undefined' + ) { + let row; + for (row of res.data.balances) { + if (row.asset_type === 'native') { + balance = row.balance; + break; + } } - return { balance: balance, unconfirmed: 0, provider: 'horizon.stellar.org' } + } else { + return false; + } + } catch (e) { + if ( + e.message.indexOf('timed out') === -1 && + e.message.indexOf('account not found') === -1 && + e.message.indexOf('the resource at the url requested was not found') === + -1 + ) { + throw e; + } else { + return false; + } } + return { + balance: balance, + unconfirmed: 0, + provider: 'horizon.stellar.org' + }; + } + /** + * https://horizon.stellar.org/accounts/GBH4TZYZ4IRCPO44CBOLFUHULU2WGALXTAVESQA6432MBJMABBB4GIYI/payments + * @param {string} scanData.account.address + * @return {Promise} + */ + async getTransactionsBlockchain(scanData, source = '') { + const address = scanData.account.address.trim(); + BlocksoftCryptoLog.log( + 'XlmScannerProcessor.getTransactions started ' + address + ); + const linkTxs = `${API_PATH}/accounts/${address}/transactions?order=desc&limit=50`; + let res = false; + try { + res = await BlocksoftAxios.getWithoutBraking(linkTxs); + } catch (e) { + if ( + e.message.indexOf('account not found') === -1 && + e.message.indexOf('to retrieve payments') === -1 && + e.message.indexOf('limit exceeded') === -1 && + e.message.indexOf('timed out') === -1 && + e.message.indexOf('resource missing') === -1 + ) { + throw e; + } else { + return false; + } + } - /** - * https://horizon.stellar.org/accounts/GBH4TZYZ4IRCPO44CBOLFUHULU2WGALXTAVESQA6432MBJMABBB4GIYI/payments - * @param {string} scanData.account.address - * @return {Promise} - */ - async getTransactionsBlockchain(scanData, source = '') { - const address = scanData.account.address.trim() - BlocksoftCryptoLog.log('XlmScannerProcessor.getTransactions started ' + address) - const linkTxs = `${API_PATH}/accounts/${address}/transactions?order=desc&limit=50` - let res = false - try { - res = await BlocksoftAxios.getWithoutBraking(linkTxs) - } catch (e) { - if (e.message.indexOf('account not found') === -1 - && e.message.indexOf('to retrieve payments') === -1 - && e.message.indexOf('limit exceeded') === -1 - && e.message.indexOf('timed out') === -1 - && e.message.indexOf('resource missing') === -1 - ) { - throw e - } else { - return false - } - } + if (!res || typeof res.data === 'undefined' || !res.data) { + return false; + } + if ( + typeof res.data._embedded === 'undefined' || + typeof typeof res.data._embedded.records === 'undefined' + ) { + throw new Error( + 'Undefined basic txs ' + linkTxs + ' ' + JSON.stringify(res.data) + ); + } + if (typeof res.data._embedded.records === 'string') { + throw new Error( + 'Undefined basic txs ' + linkTxs + ' ' + res.data._embedded.records + ); + } + const basicTxs = {}; + for (const row of res.data._embedded.records) { + basicTxs[row.hash] = row; + } - if (!res || typeof res.data === 'undefined' || !res.data) { - return false - } - if (typeof res.data._embedded === 'undefined' || typeof typeof res.data._embedded.records === 'undefined') { - throw new Error('Undefined basic txs ' + linkTxs + ' ' + JSON.stringify(res.data)) - } - if (typeof res.data._embedded.records === 'string') { - throw new Error('Undefined basic txs ' + linkTxs + ' ' + res.data._embedded.records) - } - const basicTxs = {} - for (const row of res.data._embedded.records) { - basicTxs[row.hash] = row - } + const link = `${API_PATH}/accounts/${address}/payments?order=desc&limit=50`; + res = false; + try { + res = await BlocksoftAxios.getWithoutBraking(link); + } catch (e) { + if ( + e.message.indexOf('account not found') === -1 && + e.message.indexOf('to retrieve payments') === -1 && + e.message.indexOf('limit exceeded') === -1 && + e.message.indexOf('timed out') === -1 + ) { + throw e; + } else { + return false; + } + } + if (!res || typeof res.data === 'undefined' || !res.data) { + return false; + } + if ( + typeof res.data._embedded === 'undefined' || + typeof typeof res.data._embedded.records === 'undefined' + ) { + throw new Error('Undefined txs ' + link + ' ' + JSON.stringify(res.data)); + } + if (typeof res.data._embedded.records === 'string') { + throw new Error( + 'Undefined txs ' + link + ' ' + res.data._embedded.records + ); + } - const link = `${API_PATH}/accounts/${address}/payments?order=desc&limit=50` - res = false - try { - res = await BlocksoftAxios.getWithoutBraking(link) - } catch (e) { - if (e.message.indexOf('account not found') === -1 - && e.message.indexOf('to retrieve payments') === -1 - && e.message.indexOf('limit exceeded') === -1 - && e.message.indexOf('timed out') === -1 - ) { - throw e - } else { - return false - } - } + const transactions = await this._unifyTransactions( + address, + res.data._embedded.records, + basicTxs + ); + BlocksoftCryptoLog.log( + 'XlmScannerProcessor.getTransactions finished ' + address + ); + return transactions; + } + + async _unifyTransactions(address, result, basicTxs) { + const transactions = []; + let tx; + for (tx of result) { + const transaction = await this._unifyPayment( + address, + tx, + typeof basicTxs[tx.transaction_hash] + ? basicTxs[tx.transaction_hash] + : false + ); + if (transaction) { + transactions.push(transaction); + } + } + return transactions; + } - if (!res || typeof res.data === 'undefined' || !res.data) { - return false + /** + * @param {string} address + * @param {Object} transaction + * @param {string} transaction.amount "1.6387292" + * @param {string} transaction.asset_type "native" + * @param {string} transaction.created_at "2021-01-30T21:15:04Z" + * @param {string} transaction.from "GDK45DNCNF66HZ634ZGYHVB3KGF3MUFJJ3CKWCI2QTKHRPQW22PBP5OE" + * @param {string} transaction.id "145082573625237505" + * @param {string} transaction.paging_token "145082573625237505" + * @param {string} transaction.source_account "GDK45DNCNF66HZ634ZGYHVB3KGF3MUFJJ3CKWCI2QTKHRPQW22PBP5OE" + * @param {string} transaction.to "GBH4TZYZ4IRCPO44CBOLFUHULU2WGALXTAVESQA6432MBJMABBB4GIYI" + * @param {string} transaction.transaction_hash "d01d19b75638405a510db0ac0e937849548ee21ff411ebe376a30d25dc78b750" + * @param {string} transaction.transaction_successful true + * @param {string} transaction.type "payment" + * @param {string} transaction.type_i 1 + * @return {UnifiedTransaction} + * @private + **/ + async _unifyPayment(address, transaction, basicTransaction) { + try { + if (typeof transaction.asset_type !== 'undefined') { + if (transaction.asset_type !== 'native') { + return false; } - if (typeof res.data._embedded === 'undefined' || typeof typeof res.data._embedded.records === 'undefined') { - throw new Error('Undefined txs ' + link + ' ' + JSON.stringify(res.data)) + } else if (typeof transaction.type !== 'undefined') { + if (transaction.type === 'create_account') { + if ( + typeof transaction.amount === 'undefined' && + typeof transaction.starting_balance !== 'undefined' + ) { + transaction.amount = transaction.starting_balance; + } + if ( + typeof transaction.source_account === 'undefined' && + typeof transaction.funder !== 'undefined' + ) { + transaction.source_account = transaction.funder; + } + } else { + return false; } - if (typeof res.data._embedded.records === 'string') { - throw new Error('Undefined txs ' + link + ' ' + res.data._embedded.records) + } else { + return false; + } + const tx = { + transactionHash: transaction.transaction_hash, + blockHash: '', + blockNumber: '', + blockTime: transaction.created_at, + blockConfirmations: + transaction.transaction_successful === true ? 100 : 0, + transactionDirection: '?', + addressFrom: + transaction.source_account === address + ? '' + : transaction.source_account, + addressTo: transaction.account === address ? '' : transaction.account, + addressAmount: transaction.amount, + transactionStatus: + transaction.transaction_successful === true ? 'success' : 'new', + transactionFee: '0' + }; + if (tx.addressTo === '' || !tx.addressTo) { + if (tx.addressFrom === '') { + tx.transactionDirection = 'self'; + } else { + tx.transactionDirection = 'income'; } + } else { + tx.transactionDirection = 'outcome'; + } - const transactions = await this._unifyTransactions(address, res.data._embedded.records, basicTxs) - BlocksoftCryptoLog.log('XlmScannerProcessor.getTransactions finished ' + address) - return transactions - } - - async _unifyTransactions(address, result, basicTxs) { - const transactions = [] - let tx - for (tx of result) { - const transaction = await this._unifyPayment(address, tx, typeof basicTxs[tx.transaction_hash] ? basicTxs[tx.transaction_hash] : false) - if (transaction) { - transactions.push(transaction) - } + if (basicTransaction) { + if (typeof basicTransaction.fee_charged !== 'undefined') { + tx.transactionFee = BlocksoftUtils.toUnified( + basicTransaction.fee_charged, + FEE_DECIMALS + ); } - return transactions - } - - /** - * @param {string} address - * @param {Object} transaction - * @param {string} transaction.amount "1.6387292" - * @param {string} transaction.asset_type "native" - * @param {string} transaction.created_at "2021-01-30T21:15:04Z" - * @param {string} transaction.from "GDK45DNCNF66HZ634ZGYHVB3KGF3MUFJJ3CKWCI2QTKHRPQW22PBP5OE" - * @param {string} transaction.id "145082573625237505" - * @param {string} transaction.paging_token "145082573625237505" - * @param {string} transaction.source_account "GDK45DNCNF66HZ634ZGYHVB3KGF3MUFJJ3CKWCI2QTKHRPQW22PBP5OE" - * @param {string} transaction.to "GBH4TZYZ4IRCPO44CBOLFUHULU2WGALXTAVESQA6432MBJMABBB4GIYI" - * @param {string} transaction.transaction_hash "d01d19b75638405a510db0ac0e937849548ee21ff411ebe376a30d25dc78b750" - * @param {string} transaction.transaction_successful true - * @param {string} transaction.type "payment" - * @param {string} transaction.type_i 1 - * @return {UnifiedTransaction} - * @private - **/ - async _unifyPayment(address, transaction, basicTransaction) { - try { - if (typeof transaction.asset_type !== 'undefined') { - if (transaction.asset_type !== 'native') { - return false - } - } else if (typeof transaction.type !== 'undefined') { - if (transaction.type === 'create_account') { - if (typeof transaction.amount === 'undefined' && typeof transaction.starting_balance !== 'undefined') { - transaction.amount = transaction.starting_balance - } - if (typeof transaction.source_account === 'undefined' && typeof transaction.funder !== 'undefined') { - transaction.source_account = transaction.funder - } - } else { - return false - } - } else { - return false - } - const tx = { - transactionHash: transaction.transaction_hash, - blockHash: '', - blockNumber: '', - blockTime: transaction.created_at, - blockConfirmations: transaction.transaction_successful === true ? 100 : 0, - transactionDirection: '?', - addressFrom: transaction.source_account === address ? '' : transaction.source_account, - addressTo: transaction.account === address ? '' : transaction.account, - addressAmount: transaction.amount, - transactionStatus: transaction.transaction_successful === true ? 'success' : 'new', - transactionFee: '0' - } - if (tx.addressTo === '' || !tx.addressTo) { - if (tx.addressFrom === '') { - tx.transactionDirection = 'self' - } else { - tx.transactionDirection = 'income' - } - } else { - tx.transactionDirection = 'outcome' - } - - if (basicTransaction) { - if (typeof basicTransaction.fee_charged !== 'undefined') { - tx.transactionFee = BlocksoftUtils.toUnified(basicTransaction.fee_charged, FEE_DECIMALS) - } - if (typeof basicTransaction.ledger !== 'undefined') { - tx.blockHash = basicTransaction.ledger - tx.blockNumber = basicTransaction.ledger - } - if (typeof basicTransaction.memo !== 'undefined') { - tx.transactionJson = { memo: basicTransaction.memo } - } - } - return tx - } catch(e) { - if (config.debug.cryptoErrors) { - console.log('XLMScannerProcessor _unifyPayment error ' + e.message) - } - throw e + if (typeof basicTransaction.ledger !== 'undefined') { + tx.blockHash = basicTransaction.ledger; + tx.blockNumber = basicTransaction.ledger; + } + if (typeof basicTransaction.memo !== 'undefined') { + tx.transactionJson = { memo: basicTransaction.memo }; } + } + return tx; + } catch (e) { + if (config.debug.cryptoErrors) { + console.log('XLMScannerProcessor _unifyPayment error ' + e.message); + } + throw e; } + } } diff --git a/crypto/blockchains/xlm/XlmTransferProcessor.ts b/crypto/blockchains/xlm/XlmTransferProcessor.ts index d5c1a1143..264bc9cd8 100644 --- a/crypto/blockchains/xlm/XlmTransferProcessor.ts +++ b/crypto/blockchains/xlm/XlmTransferProcessor.ts @@ -21,202 +21,299 @@ wsl curl -X POST -F "tx=AAAAAgAAAACq+ux8eDBQfPoRzFjOTwHZKnFQjwRw0DSPL62mg02PjAAA } } */ -import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog' -import BlocksoftUtils from '../../common/BlocksoftUtils' -import BlocksoftDispatcher from '../BlocksoftDispatcher' -import MarketingEvent from '../../../app/services/Marketing/MarketingEvent' +import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog'; +import BlocksoftUtils from '../../common/BlocksoftUtils'; +import BlocksoftDispatcher from '../BlocksoftDispatcher'; +import MarketingEvent from '../../../app/services/Marketing/MarketingEvent'; -import { BlocksoftBlockchainTypes } from '../BlocksoftBlockchainTypes' -import { XlmTxSendProvider } from './basic/XlmTxSendProvider' +import { BlocksoftBlockchainTypes } from '../BlocksoftBlockchainTypes'; +import { XlmTxSendProvider } from './basic/XlmTxSendProvider'; +const FEE_DECIMALS = 7; -const FEE_DECIMALS = 7 +export default class XlmTransferProcessor + implements BlocksoftBlockchainTypes.TransferProcessor +{ + private _settings: { network: string; currencyCode: string }; + private _provider: XlmTxSendProvider; -export default class XlmTransferProcessor implements BlocksoftBlockchainTypes.TransferProcessor { - private _settings: { network: string; currencyCode: string } - private _provider: XlmTxSendProvider + constructor(settings: { network: string; currencyCode: string }) { + this._settings = settings; + this._provider = new XlmTxSendProvider(); + } - constructor(settings: { network: string; currencyCode: string }) { - this._settings = settings - this._provider = new XlmTxSendProvider() - } + needPrivateForFee(): boolean { + return false; + } - needPrivateForFee(): boolean { - return false - } + checkSendAllModal(data: { currencyCode: any }): boolean { + return false; + } - checkSendAllModal(data: { currencyCode: any }): boolean { - return false + async checkTransferHasError( + data: BlocksoftBlockchainTypes.CheckTransferHasErrorData + ): Promise { + // @ts-ignore + if (data.amount && data.amount * 1 > 20) { + return { isOk: true }; } - - async checkTransferHasError(data: BlocksoftBlockchainTypes.CheckTransferHasErrorData): Promise { - // @ts-ignore - if (data.amount && data.amount * 1 > 20) { - return { isOk: true } - } - /** - * @type {XlmScannerProcessor} - */ - const balanceProvider = BlocksoftDispatcher.getScannerProcessor(this._settings.currencyCode) - const balanceRaw = await balanceProvider.getBalanceBlockchain(data.addressTo) - if (balanceRaw && typeof balanceRaw.balance !== 'undefined' && balanceRaw.balance > 1) { - return { isOk: true } - } else { - return { isOk: false, code: 'XLM', address: data.addressTo } - } + /** + * @type {XlmScannerProcessor} + */ + const balanceProvider = BlocksoftDispatcher.getScannerProcessor( + this._settings.currencyCode + ); + const balanceRaw = await balanceProvider.getBalanceBlockchain( + data.addressTo + ); + if ( + balanceRaw && + typeof balanceRaw.balance !== 'undefined' && + balanceRaw.balance > 1 + ) { + return { isOk: true }; + } else { + return { isOk: false, code: 'XLM', address: data.addressTo }; } + } - async getFeeRate(data: BlocksoftBlockchainTypes.TransferData, privateData: BlocksoftBlockchainTypes.TransferPrivateData, additionalData: {} = {}): Promise { - const result: BlocksoftBlockchainTypes.FeeRateResult = { - selectedFeeIndex: -1, - shouldShowFees : false - } as BlocksoftBlockchainTypes.FeeRateResult + async getFeeRate( + data: BlocksoftBlockchainTypes.TransferData, + privateData: BlocksoftBlockchainTypes.TransferPrivateData, + additionalData: {} = {} + ): Promise { + const result: BlocksoftBlockchainTypes.FeeRateResult = { + selectedFeeIndex: -1, + shouldShowFees: false + } as BlocksoftBlockchainTypes.FeeRateResult; - // @ts-ignore - if (data.amount * 1 <= 0) { - BlocksoftCryptoLog.log(this._settings.currencyCode + ' XlmTransferProcessor.getFeeRate ' + data.addressFrom + ' => ' + data.addressTo + ' skipped as zero amount') - return result - } + // @ts-ignore + if (data.amount * 1 <= 0) { + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' XlmTransferProcessor.getFeeRate ' + + data.addressFrom + + ' => ' + + data.addressTo + + ' skipped as zero amount' + ); + return result; + } - BlocksoftCryptoLog.log(this._settings.currencyCode + ' XlmTransferProcessor.getFeeRate ' + data.addressFrom + ' => ' + data.addressTo + ' started amount: ' + data.amount) + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' XlmTransferProcessor.getFeeRate ' + + data.addressFrom + + ' => ' + + data.addressTo + + ' started amount: ' + + data.amount + ); - const getFee = await this._provider.getFee() + const getFee = await this._provider.getFee(); - if (!getFee) { - throw new Error('SERVER_RESPONSE_BAD_INTERNET') - } - // @ts-ignore - const fee = BlocksoftUtils.toUnified(getFee, FEE_DECIMALS) - - BlocksoftCryptoLog.log(this._settings.currencyCode + ' XlmTransferProcessor.getFeeRate ' + data.addressFrom + ' => ' + data.addressTo + ' finished amount: ' + data.amount + ' fee: ' + fee) - result.fees = [ - { - langMsg: 'xrp_speed_one', - feeForTx: fee, - amountForTx: data.amount, - blockchainData: getFee - } - ] - result.selectedFeeIndex = 0 - return result + if (!getFee) { + throw new Error('SERVER_RESPONSE_BAD_INTERNET'); } + // @ts-ignore + const fee = BlocksoftUtils.toUnified(getFee, FEE_DECIMALS); - async getTransferAllBalance(data: BlocksoftBlockchainTypes.TransferData, privateData: BlocksoftBlockchainTypes.TransferPrivateData, additionalData: BlocksoftBlockchainTypes.TransferAdditionalData = {}): Promise { - - const balance = data.amount - // @ts-ignore - BlocksoftCryptoLog.log(this._settings.currencyCode + ' XlmTransferProcessor.getTransferAllBalance ', data.addressFrom + ' => ' + balance) - // noinspection EqualityComparisonWithCoercionJS - if (BlocksoftUtils.diff(balance, 1) <= 0) { - return { - selectedTransferAllBalance: '0', - selectedFeeIndex: -1, - fees: [], - shouldShowFees : false, - countedForBasicBalance: '0' - } - } + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' XlmTransferProcessor.getFeeRate ' + + data.addressFrom + + ' => ' + + data.addressTo + + ' finished amount: ' + + data.amount + + ' fee: ' + + fee + ); + result.fees = [ + { + langMsg: 'xrp_speed_one', + feeForTx: fee, + amountForTx: data.amount, + blockchainData: getFee + } + ]; + result.selectedFeeIndex = 0; + return result; + } + async getTransferAllBalance( + data: BlocksoftBlockchainTypes.TransferData, + privateData: BlocksoftBlockchainTypes.TransferPrivateData, + additionalData: BlocksoftBlockchainTypes.TransferAdditionalData = {} + ): Promise { + const balance = data.amount; + // @ts-ignore + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' XlmTransferProcessor.getTransferAllBalance ', + data.addressFrom + ' => ' + balance + ); + // noinspection EqualityComparisonWithCoercionJS + if (BlocksoftUtils.diff(balance, 1) <= 0) { + return { + selectedTransferAllBalance: '0', + selectedFeeIndex: -1, + fees: [], + shouldShowFees: false, + countedForBasicBalance: '0' + }; + } - const result = await this.getFeeRate(data, privateData, additionalData) - // @ts-ignore - if (!result || result.selectedFeeIndex < 0) { - return { - selectedTransferAllBalance: '0', - selectedFeeIndex: -2, - fees: [], - shouldShowFees : false, - countedForBasicBalance: balance - } - } - // @ts-ignore - let newAmount = BlocksoftUtils.diff(result.fees[result.selectedFeeIndex].amountForTx, result.fees[result.selectedFeeIndex].feeForTx).toString() - newAmount = BlocksoftUtils.diff(newAmount, 1).toString() - /* + const result = await this.getFeeRate(data, privateData, additionalData); + // @ts-ignore + if (!result || result.selectedFeeIndex < 0) { + return { + selectedTransferAllBalance: '0', + selectedFeeIndex: -2, + fees: [], + shouldShowFees: false, + countedForBasicBalance: balance + }; + } + // @ts-ignore + let newAmount = BlocksoftUtils.diff( + result.fees[result.selectedFeeIndex].amountForTx, + result.fees[result.selectedFeeIndex].feeForTx + ).toString(); + newAmount = BlocksoftUtils.diff(newAmount, 1).toString(); + /* console.log(' ' + result.fees[result.selectedFeeIndex].amountForTx) console.log('--' + result.fees[result.selectedFeeIndex].feeForTx) console.log('=' + newAmount) */ - result.fees[result.selectedFeeIndex].amountForTx = newAmount - const tmp = { - ...result, - shouldShowFees : false, - selectedTransferAllBalance: result.fees[result.selectedFeeIndex].amountForTx - } - // console.log('tmp', JSON.stringify(tmp)) - return tmp - } + result.fees[result.selectedFeeIndex].amountForTx = newAmount; + const tmp = { + ...result, + shouldShowFees: false, + selectedTransferAllBalance: + result.fees[result.selectedFeeIndex].amountForTx + }; + // console.log('tmp', JSON.stringify(tmp)) + return tmp; + } - async sendTx(data: BlocksoftBlockchainTypes.TransferData, privateData: BlocksoftBlockchainTypes.TransferPrivateData, uiData: BlocksoftBlockchainTypes.TransferUiData): Promise { + async sendTx( + data: BlocksoftBlockchainTypes.TransferData, + privateData: BlocksoftBlockchainTypes.TransferPrivateData, + uiData: BlocksoftBlockchainTypes.TransferUiData + ): Promise { + if (typeof privateData.privateKey === 'undefined') { + throw new Error('XLM transaction required privateKey'); + } + if (typeof data.addressTo === 'undefined') { + throw new Error('XLM transaction required addressTo'); + } - if (typeof privateData.privateKey === 'undefined') { - throw new Error('XLM transaction required privateKey') - } - if (typeof data.addressTo === 'undefined') { - throw new Error('XLM transaction required addressTo') - } + if ( + typeof uiData.selectedFee === 'undefined' || + typeof uiData.selectedFee.blockchainData === 'undefined' + ) { + const getFee = await this._provider.getFee(); - if (typeof uiData.selectedFee === 'undefined' || typeof uiData.selectedFee.blockchainData === 'undefined') { - const getFee = await this._provider.getFee() - - if (!getFee) { - throw new Error('SERVER_RESPONSE_BAD_INTERNET') - } - if (typeof uiData.selectedFee === 'undefined') { - // @ts-ignore - uiData.selectedFee = {} - } - uiData.selectedFee.blockchainData = getFee - } + if (!getFee) { + throw new Error('SERVER_RESPONSE_BAD_INTERNET'); + } + if (typeof uiData.selectedFee === 'undefined') { + // @ts-ignore + uiData.selectedFee = {}; + } + uiData.selectedFee.blockchainData = getFee; + } - let transaction = false - try { - transaction = await this._provider.getPrepared(data, privateData, uiData) - } catch (e) { - if (e.message.indexOf('destination is invalid') !== -1) { - throw new Error('SERVER_RESPONSE_BAD_DESTINATION') - } - throw e - } - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' XlmTransferProcessor.sendTx prepared') - let raw = transaction.toEnvelope().toXDR('base64') - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' XlmTransferProcessor.sendTx base64', raw) - if (typeof uiData !== 'undefined' && typeof uiData.selectedFee !== 'undefined'&& typeof uiData.selectedFee.rawOnly !== 'undefined' && uiData.selectedFee.rawOnly) { - return { rawOnly: uiData.selectedFee.rawOnly, raw } - } + let transaction = false; + try { + transaction = await this._provider.getPrepared(data, privateData, uiData); + } catch (e) { + if (e.message.indexOf('destination is invalid') !== -1) { + throw new Error('SERVER_RESPONSE_BAD_DESTINATION'); + } + throw e; + } + await BlocksoftCryptoLog.log( + this._settings.currencyCode + ' XlmTransferProcessor.sendTx prepared' + ); + let raw = transaction.toEnvelope().toXDR('base64'); + await BlocksoftCryptoLog.log( + this._settings.currencyCode + ' XlmTransferProcessor.sendTx base64', + raw + ); + if ( + typeof uiData !== 'undefined' && + typeof uiData.selectedFee !== 'undefined' && + typeof uiData.selectedFee.rawOnly !== 'undefined' && + uiData.selectedFee.rawOnly + ) { + return { rawOnly: uiData.selectedFee.rawOnly, raw }; + } - let result = false - try { - result = await this._provider.sendRaw(raw) - } catch (e) { - if (e.message.indexOf('op_no_destination') !== -1) { - transaction = await this._provider.getPrepared(data, privateData, uiData, 'create_account') - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' XlmTransferProcessor.sendTx prepared create account') - raw = transaction.toEnvelope().toXDR('base64') - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' XlmTransferProcessor.sendTx base64 create account', raw) - result = await this._provider.sendRaw(raw) - } else { - MarketingEvent.logOnlyRealTime('v20_stellar_error ' + data.addressFrom + ' => ' + data.addressTo + ' ' + e.message, { - raw - }) - if (e.message === 'op_underfunded') { - throw new Error('SERVER_RESPONSE_NOTHING_TO_TRANSFER') - } else { - throw e - } - } + let result = false; + try { + result = await this._provider.sendRaw(raw); + } catch (e) { + if (e.message.indexOf('op_no_destination') !== -1) { + transaction = await this._provider.getPrepared( + data, + privateData, + uiData, + 'create_account' + ); + await BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' XlmTransferProcessor.sendTx prepared create account' + ); + raw = transaction.toEnvelope().toXDR('base64'); + await BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' XlmTransferProcessor.sendTx base64 create account', + raw + ); + result = await this._provider.sendRaw(raw); + } else { + MarketingEvent.logOnlyRealTime( + 'v20_stellar_error ' + + data.addressFrom + + ' => ' + + data.addressTo + + ' ' + + e.message, + { + raw + } + ); + if (e.message === 'op_underfunded') { + throw new Error('SERVER_RESPONSE_NOTHING_TO_TRANSFER'); + } else { + throw e; } - if (!result || typeof result.hash === 'undefined') { - MarketingEvent.logOnlyRealTime('v20_stellar_no_result ' + data.addressFrom + ' => ' + data.addressTo, { - raw - }) - throw new Error('SERVER_RESPONSE_NO_RESPONSE') + } + } + if (!result || typeof result.hash === 'undefined') { + MarketingEvent.logOnlyRealTime( + 'v20_stellar_no_result ' + data.addressFrom + ' => ' + data.addressTo, + { + raw } + ); + throw new Error('SERVER_RESPONSE_NO_RESPONSE'); + } - MarketingEvent.logOnlyRealTime('v20_stellar_success_result ' + data.addressFrom + ' => ' + data.addressTo + ' ' + result.hash, { - result - }) + MarketingEvent.logOnlyRealTime( + 'v20_stellar_success_result ' + + data.addressFrom + + ' => ' + + data.addressTo + + ' ' + + result.hash, + { + result + } + ); - return {transactionHash : result.hash} - } + return { transactionHash: result.hash }; + } } diff --git a/crypto/blockchains/xlm/basic/XlmTxSendProvider.ts b/crypto/blockchains/xlm/basic/XlmTxSendProvider.ts index 07ca78c31..caeccd110 100644 --- a/crypto/blockchains/xlm/basic/XlmTxSendProvider.ts +++ b/crypto/blockchains/xlm/basic/XlmTxSendProvider.ts @@ -3,157 +3,197 @@ * https://developers.stellar.org/docs/tutorials/send-and-receive-payments/ * https://www.stellar.org/developers/horizon/reference/endpoints/transactions-create.html */ -import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog' -import BlocksoftExternalSettings from '../../../common/BlocksoftExternalSettings' -import { BlocksoftBlockchainTypes } from '../../BlocksoftBlockchainTypes' +import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; +import BlocksoftExternalSettings from '../../../common/BlocksoftExternalSettings'; +import { BlocksoftBlockchainTypes } from '../../BlocksoftBlockchainTypes'; -import { XrpTxUtils } from '../../xrp/basic/XrpTxUtils' +import { XrpTxUtils } from '../../xrp/basic/XrpTxUtils'; -import config from '../../../../app/config/config' +import config from '../../../../app/config/config'; -const StellarSdk = require('stellar-sdk') +const StellarSdk = require('stellar-sdk'); +const CACHE_VALID_TIME = 600000; // 10 minute +let CACHE_FEES_TIME = 0; +let CACHE_FEES_VALUE = 0; -const CACHE_VALID_TIME = 600000 // 10 minute -let CACHE_FEES_TIME = 0 -let CACHE_FEES_VALUE = 0 - -const TX_TIMEOUT = 30 +const TX_TIMEOUT = 30; export class XlmTxSendProvider { - - private readonly _api: any - private readonly _server: any - - constructor() { - this._server = BlocksoftExternalSettings.getStatic('XLM_SERVER') - this._api = new StellarSdk.Server(this._server) - CACHE_FEES_VALUE = BlocksoftExternalSettings.getStatic('XLM_SERVER_PRICE') + private readonly _api: any; + private readonly _server: any; + + constructor() { + this._server = BlocksoftExternalSettings.getStatic('XLM_SERVER'); + this._api = new StellarSdk.Server(this._server); + CACHE_FEES_VALUE = BlocksoftExternalSettings.getStatic('XLM_SERVER_PRICE'); + } + + async getFee() { + const force = BlocksoftExternalSettings.getStatic('XLM_SERVER_PRICE_FORCE'); + if (force * 1 > 1) { + return force; } - async getFee() { - const force = BlocksoftExternalSettings.getStatic('XLM_SERVER_PRICE_FORCE') - if (force * 1 > 1) { - return force - } - - const now = new Date().getTime() - if (now - CACHE_FEES_TIME <= CACHE_VALID_TIME) { - return CACHE_FEES_VALUE - } + const now = new Date().getTime(); + if (now - CACHE_FEES_TIME <= CACHE_VALID_TIME) { + return CACHE_FEES_VALUE; + } - BlocksoftCryptoLog.log('XlmSendProvider.getFee link ' + this._server) - let res = CACHE_FEES_VALUE - try { - res = await this._api.fetchBaseFee() - if (res * 1 > 0) { - CACHE_FEES_VALUE = res * 1 - CACHE_FEES_TIME = now - } - } catch (e) { - BlocksoftCryptoLog.log('XlmSendProvider.getFee error ' + e.message + ' link ' + this._server) - res = CACHE_FEES_VALUE - } - return res + BlocksoftCryptoLog.log('XlmSendProvider.getFee link ' + this._server); + let res = CACHE_FEES_VALUE; + try { + res = await this._api.fetchBaseFee(); + if (res * 1 > 0) { + CACHE_FEES_VALUE = res * 1; + CACHE_FEES_TIME = now; + } + } catch (e) { + BlocksoftCryptoLog.log( + 'XlmSendProvider.getFee error ' + e.message + ' link ' + this._server + ); + res = CACHE_FEES_VALUE; + } + return res; + } + + async getPrepared( + data: BlocksoftBlockchainTypes.TransferData, + privateData: BlocksoftBlockchainTypes.TransferPrivateData, + uiData: BlocksoftBlockchainTypes.TransferUiData, + type = 'usual' + ) { + const account = await this._api.loadAccount(data.addressFrom); + + let transaction; + try { + let operation; + if (type === 'create_account') { + // https://stellar.stackexchange.com/questions/2144/create-multiple-trustlines-upon-account-creation + operation = StellarSdk.Operation.createAccount({ + destination: data.addressTo, + startingBalance: XrpTxUtils.amountPrep(data.amount) + }); + } else { + operation = StellarSdk.Operation.payment({ + destination: data.addressTo, + asset: StellarSdk.Asset.native(), + amount: XrpTxUtils.amountPrep(data.amount) + }); + } + + if ( + typeof data.memo !== 'undefined' && + data.memo && + data.memo.toString().trim().length > 0 + ) { + transaction = new StellarSdk.TransactionBuilder(account, { + fee: uiData.selectedFee.blockchainData, + networkPassphrase: StellarSdk.Networks.PUBLIC + }) + .addOperation(operation) + .addMemo(StellarSdk.Memo.text(data.memo)) + .setTimeout(TX_TIMEOUT) + .build(); + } else { + transaction = new StellarSdk.TransactionBuilder(account, { + fee: uiData.selectedFee.blockchainData, + networkPassphrase: StellarSdk.Networks.PUBLIC + }) + .addOperation(operation) + .setTimeout(TX_TIMEOUT) + .build(); + } + } catch (e) { + await BlocksoftCryptoLog.log( + 'XlmTxSendProvider builder create error ' + e.message + ); + throw e; } - async getPrepared(data: BlocksoftBlockchainTypes.TransferData, privateData: BlocksoftBlockchainTypes.TransferPrivateData, uiData: BlocksoftBlockchainTypes.TransferUiData, type = 'usual') { - const account = await this._api.loadAccount(data.addressFrom) - - let transaction - try { - let operation - if (type === 'create_account') { - // https://stellar.stackexchange.com/questions/2144/create-multiple-trustlines-upon-account-creation - operation = StellarSdk.Operation.createAccount({ - destination: data.addressTo, - startingBalance: XrpTxUtils.amountPrep(data.amount) - }) - } else { - operation = StellarSdk.Operation.payment({ - destination: data.addressTo, - asset: StellarSdk.Asset.native(), - amount: XrpTxUtils.amountPrep(data.amount) - }) - } - - if (typeof data.memo !== 'undefined' && data.memo && data.memo.toString().trim().length > 0) { - transaction = new StellarSdk.TransactionBuilder(account, { - fee: uiData.selectedFee.blockchainData, - networkPassphrase: StellarSdk.Networks.PUBLIC - }).addOperation(operation).addMemo(StellarSdk.Memo.text(data.memo)).setTimeout(TX_TIMEOUT).build() - } else { - transaction = new StellarSdk.TransactionBuilder(account, { - fee: uiData.selectedFee.blockchainData, - networkPassphrase: StellarSdk.Networks.PUBLIC - }).addOperation(operation).setTimeout(TX_TIMEOUT).build() - } - } catch (e) { - await BlocksoftCryptoLog.log('XlmTxSendProvider builder create error ' + e.message) - throw e + try { + transaction.sign(StellarSdk.Keypair.fromSecret(privateData.privateKey)); + } catch (e) { + await BlocksoftCryptoLog.log('XlmTxSendProvider sign error ' + e.message); + throw e; + } + return transaction; + } + + async sendRaw(raw: string) { + let result = false; + const link = BlocksoftExternalSettings.getStatic('XLM_SEND_LINK'); + BlocksoftCryptoLog.log('XlmSendProvider.sendRaw ' + link + ' raw ' + raw); + try { + // console.log(`curl -X POST -F "tx=${raw}" "https://horizon.stellar.org/transactions"`) + + const formData = new FormData(); + formData.append('tx', raw); + + const response = await fetch(link, { + method: 'POST', + credentials: 'same-origin', + mode: 'same-origin', + headers: { + 'Content-Type': 'multipart/form-data' + }, + body: formData + }); + result = await response.json(); + if ( + result && + typeof result.extras !== 'undefined' && + typeof result.extras.result_codes !== 'undefined' + ) { + if (config.debug.cryptoErrors) { + console.log( + 'XlmTransferProcessor.sendTx result.extras.result_codes ' + + JSON.stringify(result.extras.result_codes) + ); } + await BlocksoftCryptoLog.log( + 'XlmTransferProcessor.sendTx result.extras.result_codes ' + + JSON.stringify(result.extras.result_codes) + ); - try { - transaction.sign(StellarSdk.Keypair.fromSecret(privateData.privateKey)) - } catch (e) { - await BlocksoftCryptoLog.log('XlmTxSendProvider sign error ' + e.message) - throw e + if (typeof result.extras.result_codes.operations !== 'undefined') { + throw new Error(result.extras.result_codes.operations[0] + ' ' + raw); } - return transaction - } - - async sendRaw(raw: string) { - let result = false - const link = BlocksoftExternalSettings.getStatic('XLM_SEND_LINK') - BlocksoftCryptoLog.log('XlmSendProvider.sendRaw ' + link + ' raw ' + raw) - try { - // console.log(`curl -X POST -F "tx=${raw}" "https://horizon.stellar.org/transactions"`) - - const formData = new FormData() - formData.append('tx', raw) - - const response = await fetch(link, { - method: 'POST', - credentials: 'same-origin', - mode: 'same-origin', - headers: { - 'Content-Type': 'multipart/form-data' - }, - body: formData - }) - result = await response.json() - if (result && typeof result.extras !== 'undefined' && typeof result.extras.result_codes !== 'undefined') { - if (config.debug.cryptoErrors) { - console.log('XlmTransferProcessor.sendTx result.extras.result_codes ' + JSON.stringify(result.extras.result_codes)) - } - await BlocksoftCryptoLog.log('XlmTransferProcessor.sendTx result.extras.result_codes ' + JSON.stringify(result.extras.result_codes)) - - if (typeof result.extras.result_codes.operations !== 'undefined') { - throw new Error(result.extras.result_codes.operations[0] + ' ' + raw) - } - if (typeof result.extras.result_codes.transaction !== 'undefined') { - throw new Error(result.extras.result_codes.transaction + ' ' + raw) - } - } - if (typeof result.status !== 'undefined') { - if (result.status === 406 || result.status === 400 || result.status === 504) { - throw new Error(result.title) - } - } - } catch (e) { - if (config.debug.cryptoErrors) { - console.log('XlmTransferProcessor.sendTx error ' + e.message + ' link ' + link) - } - await BlocksoftCryptoLog.log('XlmTransferProcessor.sendTx error ' + e.message + ' link ' + link) - if (e.message.indexOf('status code 406') !== -1 || e.message.indexOf('status code 400') !== -1 || e.message.indexOf('status code 504') !== -1) { - throw new Error('SERVER_RESPONSE_NOT_CONNECTED') - } else if (e.message.indexOf('tx_insufficient_fee') !== -1) { - throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_FEE') - } else { - throw e - } + if (typeof result.extras.result_codes.transaction !== 'undefined') { + throw new Error(result.extras.result_codes.transaction + ' ' + raw); + } + } + if (typeof result.status !== 'undefined') { + if ( + result.status === 406 || + result.status === 400 || + result.status === 504 + ) { + throw new Error(result.title); } - await BlocksoftCryptoLog.log('XlmTransferProcessor.sendTx result ', result) - return result + } + } catch (e) { + if (config.debug.cryptoErrors) { + console.log( + 'XlmTransferProcessor.sendTx error ' + e.message + ' link ' + link + ); + } + await BlocksoftCryptoLog.log( + 'XlmTransferProcessor.sendTx error ' + e.message + ' link ' + link + ); + if ( + e.message.indexOf('status code 406') !== -1 || + e.message.indexOf('status code 400') !== -1 || + e.message.indexOf('status code 504') !== -1 + ) { + throw new Error('SERVER_RESPONSE_NOT_CONNECTED'); + } else if (e.message.indexOf('tx_insufficient_fee') !== -1) { + throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_FEE'); + } else { + throw e; + } } + await BlocksoftCryptoLog.log('XlmTransferProcessor.sendTx result ', result); + return result; + } } diff --git a/crypto/blockchains/xmr/XmrAddressProcessor.js b/crypto/blockchains/xmr/XmrAddressProcessor.js index b2ba8be75..c9d19d1d8 100644 --- a/crypto/blockchains/xmr/XmrAddressProcessor.js +++ b/crypto/blockchains/xmr/XmrAddressProcessor.js @@ -7,109 +7,141 @@ * let results = await BlocksoftKeys.discoverAddresses({ mnemonic, fullTree: false, fromIndex: 0, toIndex: 1, currencyCode: ['XMR'] }) * console.log('r', results['XMR'][0]) */ -import MoneroUtils from './ext/MoneroUtils' -import MoneroMnemonic from './ext/MoneroMnemonic' -import { soliditySha3 } from 'web3-utils' -import BlocksoftAxios from '../../common/BlocksoftAxios' -import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog' -import BlocksoftSecrets from '@crypto/actions/BlocksoftSecrets/BlocksoftSecrets' -import config from '@app/config/config' +import MoneroUtils from './ext/MoneroUtils'; +import MoneroMnemonic from './ext/MoneroMnemonic'; +import { soliditySha3 } from 'web3-utils'; +import BlocksoftAxios from '../../common/BlocksoftAxios'; +import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog'; +import BlocksoftSecrets from '@crypto/actions/BlocksoftSecrets/BlocksoftSecrets'; +import config from '@app/config/config'; -const bitcoin = require('bitcoinjs-lib') -const networksConstants = require('../../common/ext/networks-constants') - -const BTC = networksConstants['mainnet'].network +const bitcoin = require('bitcoinjs-lib'); +const networksConstants = require('../../common/ext/networks-constants'); +const BTC = networksConstants['mainnet'].network; export default class XmrAddressProcessor { - - _root = false - - async setBasicRoot(root) { - this._root = root + _root = false; + + async setBasicRoot(root) { + this._root = root; + } + + /** + * @param {string|Buffer} privateKey + * @param {*} data.publicKey + * @param {*} data.walletHash + * @param {*} data.derivationPath + * @param {*} data.derivationIndex + * @param {*} data.derivationType + * @returns {Promise<{privateKey: string, address: string, addedData: *}>} + */ + async getAddress(privateKey, data = {}, superPrivateData = {}) { + let walletMnemonic = false; + try { + walletMnemonic = await BlocksoftSecrets.getWords({ + currencyCode: 'XMR', + mnemonic: superPrivateData.mnemonic + }); + } catch (e) { + if (config.debug.cryptoErrors) { + console.log( + 'XmrAddressProcessor.getAddress recheck mnemonic error ' + e.message + ); + } + } + if (!walletMnemonic) { + return { + address: 'invalidRecheck1', + privateKey: '' + }; } + if ( + typeof data.derivationType !== 'undefined' && + data.derivationType && + data.derivationType !== 'main' + ) { + return false; + } + if ( + typeof data.derivationIndex === 'undefined' || + !data.derivationIndex || + data.derivationIndex === 0 + ) { + const child = this._root.derivePath("m/44'/128'/0'/0/0"); + privateKey = child.privateKey; + } else { + privateKey = Buffer.from(privateKey); + } + const keyPair = bitcoin.ECPair.fromPrivateKey(privateKey, { network: BTC }); + const rawPrivateKey = keyPair.privateKey; + const rawSecretSpendKey = soliditySha3(rawPrivateKey); + const rawSecretSpendKeyBuffer = Buffer.from( + rawSecretSpendKey.substr(2), + 'hex' + ); + + const secretSpendKey = MoneroUtils.sc_reduce32(rawSecretSpendKeyBuffer); + + const secretViewKey = MoneroUtils.hash_to_scalar(secretSpendKey); + + const words = MoneroMnemonic.secret_spend_key_to_words( + MoneroUtils.normString(secretSpendKey), + typeof data.walletHash !== 'undefined' ? data.walletHash : 'none' + ); + if (words !== walletMnemonic) { + return { + address: 'invalidRecheck2', + privateKey: '' + }; + } - /** - * @param {string|Buffer} privateKey - * @param {*} data.publicKey - * @param {*} data.walletHash - * @param {*} data.derivationPath - * @param {*} data.derivationIndex - * @param {*} data.derivationType - * @returns {Promise<{privateKey: string, address: string, addedData: *}>} - */ - async getAddress(privateKey, data = {}, superPrivateData = {}) { - let walletMnemonic = false - try { - walletMnemonic = await BlocksoftSecrets.getWords({currencyCode : 'XMR', mnemonic: superPrivateData.mnemonic}) - } catch (e) { - if (config.debug.cryptoErrors) { - console.log('XmrAddressProcessor.getAddress recheck mnemonic error ' + e.message) - } - } - if (!walletMnemonic) { - return { - address: 'invalidRecheck1', - privateKey: '' - } - } - - if (typeof data.derivationType !== 'undefined' && data.derivationType && data.derivationType !== 'main') { - return false - } - if (typeof data.derivationIndex === 'undefined' || !data.derivationIndex || data.derivationIndex === 0) { - const child = this._root.derivePath('m/44\'/128\'/0\'/0/0') - privateKey = child.privateKey - } else { - privateKey = Buffer.from(privateKey) - } - const keyPair = bitcoin.ECPair.fromPrivateKey(privateKey, { network: BTC }) - const rawPrivateKey = keyPair.privateKey - const rawSecretSpendKey = soliditySha3(rawPrivateKey) - const rawSecretSpendKeyBuffer = Buffer.from(rawSecretSpendKey.substr(2), 'hex') - - const secretSpendKey = MoneroUtils.sc_reduce32(rawSecretSpendKeyBuffer) - - const secretViewKey = MoneroUtils.hash_to_scalar(secretSpendKey) - - const words = MoneroMnemonic.secret_spend_key_to_words(MoneroUtils.normString(secretSpendKey), typeof data.walletHash !== 'undefined' ? data.walletHash : 'none') - if (words !== walletMnemonic) { - return { - address: 'invalidRecheck2', - privateKey: '' - } - } - - const publicSpendKey = MoneroUtils.secret_key_to_public_key(secretSpendKey).toString('hex') - - const publicViewKey = MoneroUtils.secret_key_to_public_key(secretViewKey).toString('hex') - - const address = MoneroUtils.pub_keys_to_address(0, publicSpendKey, publicViewKey) - - - let mymoneroError = 0 - let linkParamsLogin = {} - try { - linkParamsLogin = { - address: address, - view_key: MoneroUtils.normString(secretViewKey.toString('hex')), - create_account: true, - generated_locally: true - } - const resLogin = await BlocksoftAxios.post('https://api.mymonero.com:8443/login', linkParamsLogin) - if (typeof resLogin.data === 'undefined' || !resLogin.data || typeof resLogin.data.new_address === 'undefined') { - throw new Error('no data') - } - } catch (e) { - BlocksoftCryptoLog.err('XmrAddressProcessor !!!mymonero error!!! ' + e.message, { - linkParamsLogin, - publicSpendKeyL : publicSpendKey.length, - publicViewKeyL : publicViewKey.length}) - mymoneroError = 1 + const publicSpendKey = + MoneroUtils.secret_key_to_public_key(secretSpendKey).toString('hex'); + + const publicViewKey = + MoneroUtils.secret_key_to_public_key(secretViewKey).toString('hex'); + + const address = MoneroUtils.pub_keys_to_address( + 0, + publicSpendKey, + publicViewKey + ); + + let mymoneroError = 0; + let linkParamsLogin = {}; + try { + linkParamsLogin = { + address: address, + view_key: MoneroUtils.normString(secretViewKey.toString('hex')), + create_account: true, + generated_locally: true + }; + const resLogin = await BlocksoftAxios.post( + 'https://api.mymonero.com:8443/login', + linkParamsLogin + ); + if ( + typeof resLogin.data === 'undefined' || + !resLogin.data || + typeof resLogin.data.new_address === 'undefined' + ) { + throw new Error('no data'); + } + } catch (e) { + BlocksoftCryptoLog.err( + 'XmrAddressProcessor !!!mymonero error!!! ' + e.message, + { + linkParamsLogin, + publicSpendKeyL: publicSpendKey.length, + publicViewKeyL: publicViewKey.length } + ); + mymoneroError = 1; + } - /* + /* console.log({ derivationPath : data.derivationPath, secretSpendKey, @@ -123,15 +155,18 @@ export default class XmrAddressProcessor { }) */ - return { - address: address, - privateKey: MoneroUtils.normString(secretSpendKey.toString('hex')) + '_' + MoneroUtils.normString(secretViewKey.toString('hex')), - addedData: { - publicViewKey: MoneroUtils.normString(publicViewKey), - publicSpendKey: MoneroUtils.normString(publicSpendKey), - derivationIndex: 0, - mymoneroError - } - } - } + return { + address: address, + privateKey: + MoneroUtils.normString(secretSpendKey.toString('hex')) + + '_' + + MoneroUtils.normString(secretViewKey.toString('hex')), + addedData: { + publicViewKey: MoneroUtils.normString(publicViewKey), + publicSpendKey: MoneroUtils.normString(publicSpendKey), + derivationIndex: 0, + mymoneroError + } + }; + } } diff --git a/crypto/blockchains/xmr/XmrScannerProcessor.js b/crypto/blockchains/xmr/XmrScannerProcessor.js index e70ad540f..f0fd7ac8b 100644 --- a/crypto/blockchains/xmr/XmrScannerProcessor.js +++ b/crypto/blockchains/xmr/XmrScannerProcessor.js @@ -3,327 +3,410 @@ * https://api.mymonero.com:8443/get_address_info */ -import BlocksoftUtils from '@crypto/common/BlocksoftUtils' -import BlocksoftAxios from '@crypto/common/BlocksoftAxios' -import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog' -import BlocksoftPrivateKeysUtils from '@crypto/common/BlocksoftPrivateKeysUtils' +import BlocksoftUtils from '@crypto/common/BlocksoftUtils'; +import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; +import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; +import BlocksoftPrivateKeysUtils from '@crypto/common/BlocksoftPrivateKeysUtils'; -import MoneroUtilsParser from './ext/MoneroUtilsParser' +import MoneroUtilsParser from './ext/MoneroUtilsParser'; -import { showModal } from '@app/appstores/Stores/Modal/ModalActions' -import { strings } from '@app/services/i18n' -import config from '@app/config/config' +import { showModal } from '@app/appstores/Stores/Modal/ModalActions'; +import { strings } from '@app/services/i18n'; +import config from '@app/config/config'; -const CACHE_VALID_TIME = 30000 // 30 seconds -const CACHE = {} -const NEVER_LOGIN = {} -let CACHE_SHOWN_ERROR = 0 +const CACHE_VALID_TIME = 30000; // 30 seconds +const CACHE = {}; +const NEVER_LOGIN = {}; +let CACHE_SHOWN_ERROR = 0; export default class XmrScannerProcessor { + /** + * @private + */ + _serverUrl = false; - /** - * @private - */ - _serverUrl = false + _blocksToConfirm = 30; - _blocksToConfirm = 30 + _maxBlockNumber = 500000000; - _maxBlockNumber = 500000000 + constructor(settings) { + this._settings = settings; + } - constructor(settings) { - this._settings = settings + async _getCache(address, additionalData, walletHash) { + if (typeof CACHE[address] !== 'undefined') { + CACHE[address].provider = 'mymonero-cache-all'; + return CACHE[address]; + } else { + return false; } + } - async _getCache(address, additionalData, walletHash) { - if (typeof CACHE[address] !== 'undefined') { - CACHE[address].provider = 'mymonero-cache-all' - return CACHE[address] - } else { - return false - } - + /** + * @param address + * @param additionalData + * @param walletHash + * @returns {Promise} + * @private + */ + async _get(address, additionalData, walletHash) { + BlocksoftCryptoLog.log( + 'XMR XmrScannerProcessor._get ' + walletHash + ' ' + address + ); + const now = new Date().getTime(); + if ( + typeof CACHE[address] !== 'undefined' && + now - CACHE[address].time < CACHE_VALID_TIME + ) { + CACHE[address].provider = 'mymonero-cache'; + return CACHE[address]; } - /** - * @param address - * @param additionalData - * @param walletHash - * @returns {Promise} - * @private - */ - async _get(address, additionalData, walletHash) { - BlocksoftCryptoLog.log('XMR XmrScannerProcessor._get ' + walletHash + ' ' + address) - const now = new Date().getTime() - if (typeof CACHE[address] !== 'undefined' && (now - CACHE[address].time < CACHE_VALID_TIME)) { - CACHE[address].provider = 'mymonero-cache' - return CACHE[address] - } + //@todo nodes support + //this._serverUrl = await settingsActions.getSetting('xmrServer') + //if (!this._serverUrl || this._serverUrl === 'false') { + this._serverUrl = 'api.mymonero.com:8443'; + //} - //@todo nodes support - //this._serverUrl = await settingsActions.getSetting('xmrServer') - //if (!this._serverUrl || this._serverUrl === 'false') { - this._serverUrl = 'api.mymonero.com:8443' - //} + let link = this._serverUrl.trim(); + if (link.substr(0, 4).toLowerCase() !== 'http') { + link = 'https://' + this._serverUrl; + } + if (link[link.length - 1] !== '/') { + link = link + '/'; + } - let link = this._serverUrl.trim() - if (link.substr(0, 4).toLowerCase() !== 'http') { - link = 'https://' + this._serverUrl - } - if (link[link.length - 1] !== '/') { - link = link + '/' - } + const discoverFor = { + addressToCheck: address, + walletHash: walletHash, + currencyCode: 'XMR', + derivationPath: "m/44'/0'/0'/0/0", + derivationIndex: + typeof additionalData.derivationIndex !== 'undefined' + ? additionalData.derivationIndex + : 0 + }; - const discoverFor = { - addressToCheck: address, - walletHash: walletHash, - currencyCode: 'XMR', - derivationPath: 'm/44\'/0\'/0\'/0/0', - derivationIndex: typeof additionalData.derivationIndex !== 'undefined' ? additionalData.derivationIndex : 0 - } + const result = await BlocksoftPrivateKeysUtils.getPrivateKey( + discoverFor, + 'XmrScannerProcessor' + ); // privateSpend_privateView + const keys = result.privateKey.split('_'); + const spendKey = keys[0]; // private spend and view keys + let viewKey = keys[1]; + while (viewKey.length < 64) { + viewKey += '0'; + } + const linkParams = { address: address, view_key: viewKey }; - const result = await BlocksoftPrivateKeysUtils.getPrivateKey(discoverFor, 'XmrScannerProcessor') // privateSpend_privateView - const keys = result.privateKey.split('_') - const spendKey = keys[0] // private spend and view keys - let viewKey = keys[1] - while (viewKey.length < 64) { - viewKey += '0' + let res = false; + try { + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' XmrScannerProcessor._get start ' + + link + + 'get_address_info', + JSON.stringify(linkParams) + ); + res = await BlocksoftAxios.post(link + 'get_address_info', linkParams); + } catch (e) { + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' XmrScannerProcessor._get error ' + + e.message, + JSON.stringify(linkParams) + ); + if ( + CACHE_SHOWN_ERROR === 0 && + e.message.indexOf('invalid address and/or view key') !== -1 + ) { + showModal({ + type: 'INFO_MODAL', + icon: false, + title: strings('modal.walletLog.sorry'), + description: strings('settings.walletList.needReinstallXMR') + }); + CACHE_SHOWN_ERROR++; + if (CACHE_SHOWN_ERROR > 100) { + CACHE_SHOWN_ERROR = 0; } - const linkParams = { address: address, view_key: viewKey } - - - let res = false - try { - BlocksoftCryptoLog.log(this._settings.currencyCode + ' XmrScannerProcessor._get start ' + link + 'get_address_info', JSON.stringify(linkParams)) - res = await BlocksoftAxios.post(link + 'get_address_info', linkParams) - } catch (e) { - BlocksoftCryptoLog.log(this._settings.currencyCode + ' XmrScannerProcessor._get error ' + e.message, JSON.stringify(linkParams)) - if (CACHE_SHOWN_ERROR === 0 && e.message.indexOf('invalid address and/or view key') !== -1) { - showModal({ - type: 'INFO_MODAL', - icon: false, - title: strings('modal.walletLog.sorry'), - description: strings('settings.walletList.needReinstallXMR') - }) - CACHE_SHOWN_ERROR++ - if (CACHE_SHOWN_ERROR > 100) { - CACHE_SHOWN_ERROR = 0 - } - } - } - if (!res || !res.data) { - if (typeof NEVER_LOGIN[address] === 'undefined') { - const linkParamsLogin = { - address: address, - view_key: viewKey, - create_account: true, - generated_locally: true - } - try { - await BlocksoftAxios.post('https://api.mymonero.com:8443/login', linkParamsLogin) // login needed - } catch (e) { - BlocksoftCryptoLog.log(this._settings.currencyCode + ' XmrScannerProcessor._get login error ' + e.message, linkParamsLogin) - if (CACHE_SHOWN_ERROR === 0 && e.message.indexOf('invalid address and/or view key') !== -1) { - showModal({ - type: 'INFO_MODAL', - icon: false, - title: strings('modal.walletLog.sorry'), - description: strings('settings.walletList.needReinstallXMR') - }) - CACHE_SHOWN_ERROR++ - if (CACHE_SHOWN_ERROR > 100) { - CACHE_SHOWN_ERROR = 0 - } - } - } - } - return false - } - if (typeof res.data.spent_outputs === 'undefined') { - throw new Error('XMR XmrScannerProcessor._get nothing loaded for address ' + link) - } - - let parsed = false + } + } + if (!res || !res.data) { + if (typeof NEVER_LOGIN[address] === 'undefined') { + const linkParamsLogin = { + address: address, + view_key: viewKey, + create_account: true, + generated_locally: true + }; try { - parsed = await MoneroUtilsParser.parseAddressInfo(address, res.data, viewKey, additionalData.publicSpendKey, spendKey) + await BlocksoftAxios.post( + 'https://api.mymonero.com:8443/login', + linkParamsLogin + ); // login needed } catch (e) { - if (config.debug.cryptoErrors) { - console.log('XMR XmrScannerProcessor._get MoneroUtilsParser.parseAddressInfo error ' + e.message) + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' XmrScannerProcessor._get login error ' + + e.message, + linkParamsLogin + ); + if ( + CACHE_SHOWN_ERROR === 0 && + e.message.indexOf('invalid address and/or view key') !== -1 + ) { + showModal({ + type: 'INFO_MODAL', + icon: false, + title: strings('modal.walletLog.sorry'), + description: strings('settings.walletList.needReinstallXMR') + }); + CACHE_SHOWN_ERROR++; + if (CACHE_SHOWN_ERROR > 100) { + CACHE_SHOWN_ERROR = 0; } - await BlocksoftCryptoLog.log('XMR XmrScannerProcessor._get MoneroUtilsParser.parseAddressInfo error ' + e.message) - } - - const res2 = await BlocksoftAxios.postWithoutBraking(link + 'get_address_txs', linkParams) - if (!res2 || !res2.data) { - return false + } } + } + return false; + } + if (typeof res.data.spent_outputs === 'undefined') { + throw new Error( + 'XMR XmrScannerProcessor._get nothing loaded for address ' + link + ); + } - let parsed2 = false - try { - parsed2 = await MoneroUtilsParser.parseAddressTransactions(address, res2.data, viewKey, additionalData.publicSpendKey, spendKey) - } catch (e) { - if (config.debug.cryptoErrors) { - console.log('XMR XmrScannerProcessor._get MoneroUtilsParser.parseAddressTransactions error ' + e.message) - } - await BlocksoftCryptoLog.log('XMR XmrScannerProcessor._get MoneroUtilsParser.parseAddressTransactions error ' + e.message) - } + let parsed = false; + try { + parsed = await MoneroUtilsParser.parseAddressInfo( + address, + res.data, + viewKey, + additionalData.publicSpendKey, + spendKey + ); + } catch (e) { + if (config.debug.cryptoErrors) { + console.log( + 'XMR XmrScannerProcessor._get MoneroUtilsParser.parseAddressInfo error ' + + e.message + ); + } + await BlocksoftCryptoLog.log( + 'XMR XmrScannerProcessor._get MoneroUtilsParser.parseAddressInfo error ' + + e.message + ); + } - if (parsed && parsed2) { - CACHE[address] = { - outputs: parsed?.spent_outputs, - transactions: typeof parsed2.serialized_transactions !== 'undefined' ? parsed2.serialized_transactions : parsed2.transactions, - balance: typeof parsed.total_received_String !== 'undefined' ? BlocksoftUtils.diff(parsed.total_received_String, parsed.total_sent_String) : BlocksoftUtils.diff(parsed.total_received, parsed.total_sent), - account_scan_start_height: parsed2.account_scan_start_height, - scanned_block_height: parsed2.account_scanned_block_height, - account_scanned_height: parsed2.account_scanned_height, - blockchain_height: parsed2.blockchain_height, - transaction_height: parsed2.transaction_height, - time: now, - provider: 'mymonero' - } - return CACHE[address] - } - return false + const res2 = await BlocksoftAxios.postWithoutBraking( + link + 'get_address_txs', + linkParams + ); + if (!res2 || !res2.data) { + return false; } - /** - * @param {string} address - * @param {*} additionalData - * @param {string} walletHash - * @return {Promise<{balance:*, unconfirmed:*, provider:string}>} - */ - async getBalanceBlockchain(address, additionalData, walletHash) { - if (address === 'invalidRecheck1') { - return { balance: 0, unconfirmed: 0, provider: 'error'} - } - const res = await this._get(address, additionalData, walletHash) - if (!res) { - return false - } - return { balance: res.balance, unconfirmed: 0, provider: res.provider, time: res.time } + let parsed2 = false; + try { + parsed2 = await MoneroUtilsParser.parseAddressTransactions( + address, + res2.data, + viewKey, + additionalData.publicSpendKey, + spendKey + ); + } catch (e) { + if (config.debug.cryptoErrors) { + console.log( + 'XMR XmrScannerProcessor._get MoneroUtilsParser.parseAddressTransactions error ' + + e.message + ); + } + await BlocksoftCryptoLog.log( + 'XMR XmrScannerProcessor._get MoneroUtilsParser.parseAddressTransactions error ' + + e.message + ); } - /** - * @param {string} scanData.account.address - * @param {*} scanData.additional - * @param {string} scanData.account.walletHash - * @return {Promise} - */ - async getTransactionsBlockchain(scanData, source = '') { - const address = scanData.account.address.trim() - if (address === 'invalidRecheck1') { - return [] - } - const additionalData = scanData.additional - const walletHash = scanData.account.walletHash - const res = await this._get(address, additionalData, walletHash) - if (!res || typeof res === 'undefined') return [] + if (parsed && parsed2) { + CACHE[address] = { + outputs: parsed?.spent_outputs, + transactions: + typeof parsed2.serialized_transactions !== 'undefined' + ? parsed2.serialized_transactions + : parsed2.transactions, + balance: + typeof parsed.total_received_String !== 'undefined' + ? BlocksoftUtils.diff( + parsed.total_received_String, + parsed.total_sent_String + ) + : BlocksoftUtils.diff(parsed.total_received, parsed.total_sent), + account_scan_start_height: parsed2.account_scan_start_height, + scanned_block_height: parsed2.account_scanned_block_height, + account_scanned_height: parsed2.account_scanned_height, + blockchain_height: parsed2.blockchain_height, + transaction_height: parsed2.transaction_height, + time: now, + provider: 'mymonero' + }; + return CACHE[address]; + } + return false; + } - if (typeof res.transactions === 'undefined' || !res.transactions) return [] - let tx - const transactions = [] + /** + * @param {string} address + * @param {*} additionalData + * @param {string} walletHash + * @return {Promise<{balance:*, unconfirmed:*, provider:string}>} + */ + async getBalanceBlockchain(address, additionalData, walletHash) { + if (address === 'invalidRecheck1') { + return { balance: 0, unconfirmed: 0, provider: 'error' }; + } + const res = await this._get(address, additionalData, walletHash); + if (!res) { + return false; + } + return { + balance: res.balance, + unconfirmed: 0, + provider: res.provider, + time: res.time + }; + } - for (tx of res.transactions) { - const transaction = await this._unifyTransaction(address, res.scanned_block_height, tx) - if (transaction) { - transactions.push(transaction) - } - } - return transactions + /** + * @param {string} scanData.account.address + * @param {*} scanData.additional + * @param {string} scanData.account.walletHash + * @return {Promise} + */ + async getTransactionsBlockchain(scanData, source = '') { + const address = scanData.account.address.trim(); + if (address === 'invalidRecheck1') { + return []; } + const additionalData = scanData.additional; + const walletHash = scanData.account.walletHash; + const res = await this._get(address, additionalData, walletHash); + if (!res || typeof res === 'undefined') return []; - /** - * - * @param {string} address - * @param {string} lastBlock - * @param {Object} transaction - * @param {BigInteger} transaction.amount BigInteger {_d: Array(2), _s: -1} - * @param {string} transaction.approx_float_amount -0.00002724 - * @param {string} transaction.coinbase false - * @param {string} transaction.fee "27240000" - * @param {string} transaction.hash "ac319a3240f15dab342102fe248d3b95636f8a0bbfa962a5645521fac8fb86d3" - * @param {string} transaction.height 2152183 - * @param {string} transaction.id 10506991 - * @param {string} transaction.mempool: false - * @param {string} transaction.mixin 10 - * @param {string} transaction.payment_id "" - * @param {string} transaction.spent_outputs [{…}] - * @param {string} transaction.timestamp Tue Jul 28 2020 18:10:26 GMT+0300 (Восточная Европа, летнее время) {} - * @param {string} transaction.total_received "12354721582" - * @param {BigInteger} transaction.total_sent BigInteger {_d: Array(2), _s: 1} - * @param {string} transaction.unlock_time - * @return {Promise} - * @private - */ - async _unifyTransaction(address, lastBlock, transaction) { + if (typeof res.transactions === 'undefined' || !res.transactions) return []; + let tx; + const transactions = []; - let transactionStatus = 'new' - transaction.confirmations = lastBlock * 1 - transaction.height * 1 - //if (transaction.mempool === false) { - if (transaction.confirmations >= this._blocksToConfirm) { - transactionStatus = 'success' - } else if (transaction.confirmations > 0) { - transactionStatus = 'confirming' - } - // } + for (tx of res.transactions) { + const transaction = await this._unifyTransaction( + address, + res.scanned_block_height, + tx + ); + if (transaction) { + transactions.push(transaction); + } + } + return transactions; + } - if (typeof transaction.unlock_time !== 'undefined') { - const unlockTime = transaction.unlock_time * 1 - if (unlockTime > 0) { - if (unlockTime < this._maxBlockNumber) { - // then unlock time is block height - if (unlockTime > lastBlock) { - transactionStatus = 'locked' - } - } else { - // then unlock time is s timestamp as TimeInterval - const now = new Date().getTime() - if (unlockTime > now) { - transactionStatus = 'locked' - } - } - } - } + /** + * + * @param {string} address + * @param {string} lastBlock + * @param {Object} transaction + * @param {BigInteger} transaction.amount BigInteger {_d: Array(2), _s: -1} + * @param {string} transaction.approx_float_amount -0.00002724 + * @param {string} transaction.coinbase false + * @param {string} transaction.fee "27240000" + * @param {string} transaction.hash "ac319a3240f15dab342102fe248d3b95636f8a0bbfa962a5645521fac8fb86d3" + * @param {string} transaction.height 2152183 + * @param {string} transaction.id 10506991 + * @param {string} transaction.mempool: false + * @param {string} transaction.mixin 10 + * @param {string} transaction.payment_id "" + * @param {string} transaction.spent_outputs [{…}] + * @param {string} transaction.timestamp Tue Jul 28 2020 18:10:26 GMT+0300 (Восточная Европа, летнее время) {} + * @param {string} transaction.total_received "12354721582" + * @param {BigInteger} transaction.total_sent BigInteger {_d: Array(2), _s: 1} + * @param {string} transaction.unlock_time + * @return {Promise} + * @private + */ + async _unifyTransaction(address, lastBlock, transaction) { + let transactionStatus = 'new'; + transaction.confirmations = lastBlock * 1 - transaction.height * 1; + //if (transaction.mempool === false) { + if (transaction.confirmations >= this._blocksToConfirm) { + transactionStatus = 'success'; + } else if (transaction.confirmations > 0) { + transactionStatus = 'confirming'; + } + // } - let direction = 'self' - let amount - if (transaction.total_received !== '0') { - if (transaction.total_sent !== '0') { - const diff = BlocksoftUtils.diff(transaction.total_sent, transaction.total_received) - if (diff > 0) { - direction = 'outcome' - amount = diff - } else { - direction = 'income' - amount = -1 * diff - } - } else { - direction = 'income' - amount = transaction.total_received - } - } else if (transaction.total_sent !== '0') { - direction = 'outcome' - amount = transaction.total_sent - } - let formattedTime - try { - formattedTime = BlocksoftUtils.toDate(transaction.timestamp) - } catch (e) { - e.message += ' timestamp error transaction data ' + JSON.stringify(transaction) - throw e + if (typeof transaction.unlock_time !== 'undefined') { + const unlockTime = transaction.unlock_time * 1; + if (unlockTime > 0) { + if (unlockTime < this._maxBlockNumber) { + // then unlock time is block height + if (unlockTime > lastBlock) { + transactionStatus = 'locked'; + } + } else { + // then unlock time is s timestamp as TimeInterval + const now = new Date().getTime(); + if (unlockTime > now) { + transactionStatus = 'locked'; + } } + } + } - return { - transactionHash: transaction.hash, - blockHash: transaction.id, - blockNumber: +transaction.height, - blockTime: formattedTime, - blockConfirmations: transaction.confirmations, - transactionDirection: direction, - addressFrom: '', - addressTo: '', - addressAmount: amount, - transactionStatus: transactionStatus, - transactionFee: transaction.fee + let direction = 'self'; + let amount; + if (transaction.total_received !== '0') { + if (transaction.total_sent !== '0') { + const diff = BlocksoftUtils.diff( + transaction.total_sent, + transaction.total_received + ); + if (diff > 0) { + direction = 'outcome'; + amount = diff; + } else { + direction = 'income'; + amount = -1 * diff; } + } else { + direction = 'income'; + amount = transaction.total_received; + } + } else if (transaction.total_sent !== '0') { + direction = 'outcome'; + amount = transaction.total_sent; + } + let formattedTime; + try { + formattedTime = BlocksoftUtils.toDate(transaction.timestamp); + } catch (e) { + e.message += + ' timestamp error transaction data ' + JSON.stringify(transaction); + throw e; } + + return { + transactionHash: transaction.hash, + blockHash: transaction.id, + blockNumber: +transaction.height, + blockTime: formattedTime, + blockConfirmations: transaction.confirmations, + transactionDirection: direction, + addressFrom: '', + addressTo: '', + addressAmount: amount, + transactionStatus: transactionStatus, + transactionFee: transaction.fee + }; + } } diff --git a/crypto/blockchains/xmr/XmrTransferProcessor.ts b/crypto/blockchains/xmr/XmrTransferProcessor.ts index 9dd8f7908..b565a8fd4 100644 --- a/crypto/blockchains/xmr/XmrTransferProcessor.ts +++ b/crypto/blockchains/xmr/XmrTransferProcessor.ts @@ -1,280 +1,419 @@ /** * @version 0.20 */ -import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog' - -import MoneroUtilsParser from './ext/MoneroUtilsParser' -import XmrSendProvider from './providers/XmrSendProvider' -import XmrUnspentsProvider from './providers/XmrUnspentsProvider' - -import { BlocksoftBlockchainTypes } from '../BlocksoftBlockchainTypes' -import config from '../../../app/config/config' -import BlocksoftPrettyNumbers from '@crypto/common/BlocksoftPrettyNumbers' - -export default class XmrTransferProcessor implements BlocksoftBlockchainTypes.TransferProcessor { - private sendProvider: XmrSendProvider - private unspentsProvider: XmrUnspentsProvider - private _settings: any - - constructor(settings: any) { - this._settings = settings - this.sendProvider = new XmrSendProvider(settings) - this.unspentsProvider = new XmrUnspentsProvider(settings) +import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog'; + +import MoneroUtilsParser from './ext/MoneroUtilsParser'; +import XmrSendProvider from './providers/XmrSendProvider'; +import XmrUnspentsProvider from './providers/XmrUnspentsProvider'; + +import { BlocksoftBlockchainTypes } from '../BlocksoftBlockchainTypes'; +import config from '../../../app/config/config'; +import BlocksoftPrettyNumbers from '@crypto/common/BlocksoftPrettyNumbers'; + +export default class XmrTransferProcessor + implements BlocksoftBlockchainTypes.TransferProcessor +{ + private sendProvider: XmrSendProvider; + private unspentsProvider: XmrUnspentsProvider; + private _settings: any; + + constructor(settings: any) { + this._settings = settings; + this.sendProvider = new XmrSendProvider(settings); + this.unspentsProvider = new XmrUnspentsProvider(settings); + } + + needPrivateForFee(): boolean { + return true; + } + + checkSendAllModal(data: { currencyCode: any }): boolean { + return true; + } + + async getFeeRate( + data: BlocksoftBlockchainTypes.TransferData, + privateData: BlocksoftBlockchainTypes.TransferPrivateData, + additionalData: {} = {} + ): Promise { + const result: BlocksoftBlockchainTypes.FeeRateResult = { + selectedFeeIndex: -1 + } as BlocksoftBlockchainTypes.FeeRateResult; + + // @ts-ignore + if (data.amount * 1 <= 0) { + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' XmrTransferProcessor.getFeeRate ' + + data.addressFrom + + ' => ' + + data.addressTo + + ' skipped as zero amount' + ); + return result; } - needPrivateForFee(): boolean { - return true + if ( + typeof data.accountJson === 'undefined' || + !data.accountJson || + typeof data.accountJson.publicSpendKey === 'undefined' + ) { + throw new Error('XmrTransferProcessor public spend key is required'); } - - checkSendAllModal(data: { currencyCode: any }): boolean { - return true + const keys = privateData.privateKey.split('_'); + const privSpendKey = keys[0]; + const privViewKey = keys[1]; + const pubSpendKey = data.accountJson.publicSpendKey; + + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' XmrTransferProcessor.getFeeRate newSender ' + + data.addressFrom + + ' => ' + + data.addressTo + + ' started amount: ' + + data.amount + ); + + const apiClient = this.unspentsProvider; + + let core = await MoneroUtilsParser.getCore(); + if (!core || typeof core === 'undefined') { + core = await MoneroUtilsParser.getCore(); } - async getFeeRate(data: BlocksoftBlockchainTypes.TransferData, privateData: BlocksoftBlockchainTypes.TransferPrivateData, additionalData: {} = {}): Promise { - const result: BlocksoftBlockchainTypes.FeeRateResult = { - selectedFeeIndex: -1 - } as BlocksoftBlockchainTypes.FeeRateResult + result.fees = []; + + const logFees = []; + let noBalanceError = false; + apiClient.init(); + + const unspentOuts = await apiClient._getUnspents( + { + address: data.addressFrom, + view_key: privViewKey, + amount: '0', + app_name: 'MyMonero', + app_version: '1.3.2', + dust_threshold: '2000000000', + mixin: 15, + use_dust: true + }, + false + ); + + for (let i = 1; i <= 4; i++) { + try { + await BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' XmrTransferProcessor.getFeeRate ' + + data.addressFrom + + ' => ' + + data.addressTo + + ' start amount: ' + + data.amount + + ' fee ' + + i + ); // @ts-ignore - if (data.amount * 1 <= 0) { - BlocksoftCryptoLog.log(this._settings.currencyCode + ' XmrTransferProcessor.getFeeRate ' + data.addressFrom + ' => ' + data.addressTo + ' skipped as zero amount') - return result - } - - if (typeof data.accountJson === 'undefined' || !data.accountJson || typeof data.accountJson.publicSpendKey === 'undefined') { - throw new Error('XmrTransferProcessor public spend key is required') - } - const keys = privateData.privateKey.split('_') - const privSpendKey = keys[0] - const privViewKey = keys[1] - const pubSpendKey = data.accountJson.publicSpendKey - - BlocksoftCryptoLog.log(this._settings.currencyCode + ' XmrTransferProcessor.getFeeRate newSender ' + data.addressFrom + ' => ' + data.addressTo + ' started amount: ' + data.amount) - - const apiClient = this.unspentsProvider - - let core = await MoneroUtilsParser.getCore() - if (!core || typeof core === 'undefined') { - core = await MoneroUtilsParser.getCore() - } - - result.fees = [] - - const logFees = [] - let noBalanceError = false - apiClient.init() - - - const unspentOuts = await apiClient._getUnspents({ - address: data.addressFrom, - view_key: privViewKey, - amount: '0', - app_name: 'MyMonero', - app_version: '1.3.2', - dust_threshold: '2000000000', - mixin: 15, - use_dust: true - }, false) - - for (let i = 1; i <= 4; i++) { - try { - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' XmrTransferProcessor.getFeeRate ' + data.addressFrom + ' => ' + data.addressTo + ' start amount: ' + data.amount + ' fee ' + i) - - // @ts-ignore - const fee = await core.createTransaction({ - destinations: [{ to_address : data.addressTo, send_amount: data.isTransferAll ? 0 : BlocksoftPrettyNumbers.setCurrencyCode('XMR').makePretty(data.amount)}], - shouldSweep: data.isTransferAll ? true: false, - address: data.addressFrom, - privateViewKey: privViewKey, - privateSpendKey: privSpendKey, - publicSpendKey: pubSpendKey, - priority: '' + i, - nettype: 'MAINNET', - unspentOuts: unspentOuts, - randomOutsCb: (numberOfOuts) => { - const amounts = [] - for (let i = 0; i < numberOfOuts; i++) { - amounts.push('0') - } - return apiClient._getRandomOutputs({ - amounts, - app_name: 'MyMonero', - app_version: '1.3.2', - count: 16 - }) - } - }) - - if (typeof fee !== 'undefined' && fee && typeof fee.used_fee) { - const tmp = { - langMsg: 'xmr_speed_' + i, - feeForTx: fee.used_fee, - blockchainData: { - secretTxKey: fee.tx_key, - rawTxHex: fee.serialized_signed_tx, - rawTxHash: fee.tx_hash, - usingOuts: fee.using_outs, - simplePriority: i - } - } as BlocksoftBlockchainTypes.Fee - - const logTmp = { - langMsg: 'xmr_speed_' + i, - feeForTx: fee.used_fee, - blockchainData: { - secretTxKey: fee.tx_key, - rawTxHash: fee.tx_hash, - usingOuts: fee.using_outs, - simplePriority: i - }, - amountForTx: '?' - } - if (typeof fee.using_amount !== 'undefined') { - tmp.amountForTx = fee.using_amount - logTmp.amountForTx = fee.using_amount - } else { - tmp.amountForTx = data.amount - logTmp.amountForTx = data.amount - } - tmp.addressToTx = data.addressTo - result.fees.push(tmp) - logFees.push(logTmp) - } - } catch (e) { - if (e.message.indexOf('pendable balance too low') !== -1) { - // do nothing - noBalanceError = true - break - } else { - if (config.debug.cryptoErrors) { - console.log('XmrTransferProcessor error ', e) - } - if (e.message.indexOf('An error occurred while getting decoy outputs') !== -1) { - BlocksoftCryptoLog.log(this._settings.currencyCode + ' XmrTransferProcessor error will go out bad decoy') - throw new Error('SERVER_RESPONSE_BAD_CODE') - } else if (e.message.indexOf('decode address') !== -1) { - BlocksoftCryptoLog.log(this._settings.currencyCode + ' XmrTransferProcessor error will go out') - throw new Error('SERVER_RESPONSE_BAD_DESTINATION') - } else if (e.message.indexOf('Not enough spendables') !== -1) { - BlocksoftCryptoLog.log(this._settings.currencyCode + ' XmrTransferProcessor error not enough') - throw new Error('SERVER_RESPONSE_NO_RESPONSE_XMR') - } else { - BlocksoftCryptoLog.err(this._settings.currencyCode + ' XmrTransferProcessor.getFeeRate ' + data.addressFrom + ' => ' + data.addressTo + ' finished amount: ' + data.amount + ' error fee ' + i + ': ' + e.message) - throw e - } - } + const fee = await core.createTransaction({ + destinations: [ + { + to_address: data.addressTo, + send_amount: data.isTransferAll + ? 0 + : BlocksoftPrettyNumbers.setCurrencyCode('XMR').makePretty( + data.amount + ) } + ], + shouldSweep: data.isTransferAll ? true : false, + address: data.addressFrom, + privateViewKey: privViewKey, + privateSpendKey: privSpendKey, + publicSpendKey: pubSpendKey, + priority: '' + i, + nettype: 'MAINNET', + unspentOuts: unspentOuts, + randomOutsCb: (numberOfOuts) => { + const amounts = []; + for (let i = 0; i < numberOfOuts; i++) { + amounts.push('0'); + } + return apiClient._getRandomOutputs({ + amounts, + app_name: 'MyMonero', + app_version: '1.3.2', + count: 16 + }); + } + }); + + if (typeof fee !== 'undefined' && fee && typeof fee.used_fee) { + const tmp = { + langMsg: 'xmr_speed_' + i, + feeForTx: fee.used_fee, + blockchainData: { + secretTxKey: fee.tx_key, + rawTxHex: fee.serialized_signed_tx, + rawTxHash: fee.tx_hash, + usingOuts: fee.using_outs, + simplePriority: i + } + } as BlocksoftBlockchainTypes.Fee; + + const logTmp = { + langMsg: 'xmr_speed_' + i, + feeForTx: fee.used_fee, + blockchainData: { + secretTxKey: fee.tx_key, + rawTxHash: fee.tx_hash, + usingOuts: fee.using_outs, + simplePriority: i + }, + amountForTx: '?' + }; + if (typeof fee.using_amount !== 'undefined') { + tmp.amountForTx = fee.using_amount; + logTmp.amountForTx = fee.using_amount; + } else { + tmp.amountForTx = data.amount; + logTmp.amountForTx = data.amount; + } + tmp.addressToTx = data.addressTo; + result.fees.push(tmp); + logFees.push(logTmp); } - - if (result.fees.length === 0 && noBalanceError) { - throw new Error('SERVER_RESPONSE_NO_RESPONSE_XMR') - } - - // @ts-ignore - BlocksoftCryptoLog.log(this._settings.currencyCode + ' XmrTransferProcessor.getFeeRate ' + data.addressFrom + ' => ' + data.addressTo + ' finished amount: ' + data.amount + ' fee: ', logFees) - - if (result.fees.length < 3) { - result.selectedFeeIndex = result.fees.length - 1 + } catch (e) { + if (e.message.indexOf('pendable balance too low') !== -1) { + // do nothing + noBalanceError = true; + break; } else { - result.selectedFeeIndex = 2 + if (config.debug.cryptoErrors) { + console.log('XmrTransferProcessor error ', e); + } + if ( + e.message.indexOf( + 'An error occurred while getting decoy outputs' + ) !== -1 + ) { + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' XmrTransferProcessor error will go out bad decoy' + ); + throw new Error('SERVER_RESPONSE_BAD_CODE'); + } else if (e.message.indexOf('decode address') !== -1) { + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' XmrTransferProcessor error will go out' + ); + throw new Error('SERVER_RESPONSE_BAD_DESTINATION'); + } else if (e.message.indexOf('Not enough spendables') !== -1) { + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' XmrTransferProcessor error not enough' + ); + throw new Error('SERVER_RESPONSE_NO_RESPONSE_XMR'); + } else { + BlocksoftCryptoLog.err( + this._settings.currencyCode + + ' XmrTransferProcessor.getFeeRate ' + + data.addressFrom + + ' => ' + + data.addressTo + + ' finished amount: ' + + data.amount + + ' error fee ' + + i + + ': ' + + e.message + ); + throw e; + } } - return result + } } - - async getTransferAllBalance(data: BlocksoftBlockchainTypes.TransferData, privateData: BlocksoftBlockchainTypes.TransferPrivateData, additionalData: BlocksoftBlockchainTypes.TransferAdditionalData = {}): Promise { - const balance = data.amount - - // @ts-ignore - BlocksoftCryptoLog.log(this._settings.currencyCode + ' XmrTransferProcessor.getTransferAllBalance ', data.addressFrom + ' => ' + balance) - - data.isTransferAll = true - const result = await this.getFeeRate(data, privateData, additionalData) - // @ts-ignore - if (!result || result.selectedFeeIndex < 0) { - return { - selectedTransferAllBalance: '0', - selectedFeeIndex: -2, - fees: [], - shouldShowFees : false, - countedForBasicBalance: balance - } - } - // @ts-ignore - return { - ...result, - shouldShowFees : false, - selectedTransferAllBalance: result.fees[result.selectedFeeIndex].amountForTx, - countedForBasicBalance: balance - } + if (result.fees.length === 0 && noBalanceError) { + throw new Error('SERVER_RESPONSE_NO_RESPONSE_XMR'); } - async sendTx(data: BlocksoftBlockchainTypes.TransferData, privateData: BlocksoftBlockchainTypes.TransferPrivateData, uiData: BlocksoftBlockchainTypes.TransferUiData): Promise { - - if (typeof privateData.privateKey === 'undefined') { - throw new Error('XMR transaction required privateKey') - } - if (typeof data.addressTo === 'undefined') { - throw new Error('XMR transaction required addressTo') - } + // @ts-ignore + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' XmrTransferProcessor.getFeeRate ' + + data.addressFrom + + ' => ' + + data.addressTo + + ' finished amount: ' + + data.amount + + ' fee: ', + logFees + ); + + if (result.fees.length < 3) { + result.selectedFeeIndex = result.fees.length - 1; + } else { + result.selectedFeeIndex = 2; + } + return result; + } + + async getTransferAllBalance( + data: BlocksoftBlockchainTypes.TransferData, + privateData: BlocksoftBlockchainTypes.TransferPrivateData, + additionalData: BlocksoftBlockchainTypes.TransferAdditionalData = {} + ): Promise { + const balance = data.amount; + + // @ts-ignore + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' XmrTransferProcessor.getTransferAllBalance ', + data.addressFrom + ' => ' + balance + ); + + data.isTransferAll = true; + const result = await this.getFeeRate(data, privateData, additionalData); + // @ts-ignore + if (!result || result.selectedFeeIndex < 0) { + return { + selectedTransferAllBalance: '0', + selectedFeeIndex: -2, + fees: [], + shouldShowFees: false, + countedForBasicBalance: balance + }; + } + // @ts-ignore + return { + ...result, + shouldShowFees: false, + selectedTransferAllBalance: + result.fees[result.selectedFeeIndex].amountForTx, + countedForBasicBalance: balance + }; + } + + async sendTx( + data: BlocksoftBlockchainTypes.TransferData, + privateData: BlocksoftBlockchainTypes.TransferPrivateData, + uiData: BlocksoftBlockchainTypes.TransferUiData + ): Promise { + if (typeof privateData.privateKey === 'undefined') { + throw new Error('XMR transaction required privateKey'); + } + if (typeof data.addressTo === 'undefined') { + throw new Error('XMR transaction required addressTo'); + } - if (typeof uiData.selectedFee === 'undefined') { - throw new Error('XMR transaction required selectedFee') - } + if (typeof uiData.selectedFee === 'undefined') { + throw new Error('XMR transaction required selectedFee'); + } - BlocksoftCryptoLog.log(this._settings.currencyCode + ' XmrTransferProcessor.sendTx started ' + data.addressFrom + '=>' + data.addressTo + ' fee', uiData.selectedFee) - let foundFee = uiData?.selectedFee - if (data.addressTo !== uiData.selectedFee.addressToTx) { - try { - BlocksoftCryptoLog.log(this._settings.currencyCode + ' XmrTransferProcessor.sendTx rechecked ' + data.addressFrom + '=>' + data.addressTo + ' fee rebuild start as got tx to ' + uiData.selectedFee.addressToTx) - const newSelectedFee = await this.getFeeRate(data, privateData) - if (typeof newSelectedFee.fees === 'undefined' || !newSelectedFee.fees) { - throw new Error('no fees') - } - foundFee = newSelectedFee.fees[newSelectedFee.selectedFeeIndex] - for (const fee of newSelectedFee.fees) { - if (fee.langMsg === uiData.selectedFee.langMsg) { - foundFee = fee - } - } - BlocksoftCryptoLog.log(this._settings.currencyCode + ' XmrTransferProcessor.sendTx rechecked ' + data.addressFrom + '=>' + data.addressTo + ' found fee', foundFee) - } catch (e) { - BlocksoftCryptoLog.log(this._settings.currencyCode + ' XmrTransferProcessor.sendTx rechecked ' + data.addressFrom + '=>' + data.addressTo + ' fee rebuild error ' + e.message) - throw new Error('XMR transaction invalid output - please try again') - } + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' XmrTransferProcessor.sendTx started ' + + data.addressFrom + + '=>' + + data.addressTo + + ' fee', + uiData.selectedFee + ); + let foundFee = uiData?.selectedFee; + if (data.addressTo !== uiData.selectedFee.addressToTx) { + try { + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' XmrTransferProcessor.sendTx rechecked ' + + data.addressFrom + + '=>' + + data.addressTo + + ' fee rebuild start as got tx to ' + + uiData.selectedFee.addressToTx + ); + const newSelectedFee = await this.getFeeRate(data, privateData); + if ( + typeof newSelectedFee.fees === 'undefined' || + !newSelectedFee.fees + ) { + throw new Error('no fees'); } - const rawTxHex = foundFee?.blockchainData?.rawTxHex - const rawTxHash = foundFee?.blockchainData?.rawTxHash - const secretTxKey = foundFee?.blockchainData?.secretTxKey - const usingOuts = foundFee?.blockchainData?.usingOuts - - const keys = privateData.privateKey.split('_') - const privViewKey = keys[1] - - if (typeof rawTxHex === 'undefined') { - throw new Error('SERVER_RESPONSE_NO_RESPONSE_XMR') + foundFee = newSelectedFee.fees[newSelectedFee.selectedFeeIndex]; + for (const fee of newSelectedFee.fees) { + if (fee.langMsg === uiData.selectedFee.langMsg) { + foundFee = fee; + } } + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' XmrTransferProcessor.sendTx rechecked ' + + data.addressFrom + + '=>' + + data.addressTo + + ' found fee', + foundFee + ); + } catch (e) { + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' XmrTransferProcessor.sendTx rechecked ' + + data.addressFrom + + '=>' + + data.addressTo + + ' fee rebuild error ' + + e.message + ); + throw new Error('XMR transaction invalid output - please try again'); + } + } + const rawTxHex = foundFee?.blockchainData?.rawTxHex; + const rawTxHash = foundFee?.blockchainData?.rawTxHash; + const secretTxKey = foundFee?.blockchainData?.secretTxKey; + const usingOuts = foundFee?.blockchainData?.usingOuts; - if (typeof uiData !== 'undefined' && typeof uiData.selectedFee !== 'undefined'&& typeof uiData.selectedFee.rawOnly !== 'undefined' && uiData.selectedFee.rawOnly) { - return { rawOnly: uiData.selectedFee.rawOnly, raw : rawTxHex} - } + const keys = privateData.privateKey.split('_'); + const privViewKey = keys[1]; - const send = await this.sendProvider.send({ - address: data.addressFrom, - tx: rawTxHex, - privViewKey, - secretTxKey, - usingOuts, - unspentsProvider: this.unspentsProvider - }) + if (typeof rawTxHex === 'undefined') { + throw new Error('SERVER_RESPONSE_NO_RESPONSE_XMR'); + } - // @ts-ignore - BlocksoftCryptoLog.log(this._settings.currencyCode + ' XmrTransferProcessor.sendTx result', send) + if ( + typeof uiData !== 'undefined' && + typeof uiData.selectedFee !== 'undefined' && + typeof uiData.selectedFee.rawOnly !== 'undefined' && + uiData.selectedFee.rawOnly + ) { + return { rawOnly: uiData.selectedFee.rawOnly, raw: rawTxHex }; + } - if (send.status === 'OK') { - return { transactionHash: rawTxHash, transactionJson: { secretTxKey }} - } else { - throw new Error(this._settings.currencyCode + ' XmrTransferProcessor.sendTx status error ' + JSON.stringify(send)) - } + const send = await this.sendProvider.send({ + address: data.addressFrom, + tx: rawTxHex, + privViewKey, + secretTxKey, + usingOuts, + unspentsProvider: this.unspentsProvider + }); + + // @ts-ignore + BlocksoftCryptoLog.log( + this._settings.currencyCode + ' XmrTransferProcessor.sendTx result', + send + ); + + if (send.status === 'OK') { + return { transactionHash: rawTxHash, transactionJson: { secretTxKey } }; + } else { + throw new Error( + this._settings.currencyCode + + ' XmrTransferProcessor.sendTx status error ' + + JSON.stringify(send) + ); } + } } diff --git a/crypto/blockchains/xmr/ext/MoneroMnemonic.js b/crypto/blockchains/xmr/ext/MoneroMnemonic.js index fe6747f6a..862fb8973 100644 --- a/crypto/blockchains/xmr/ext/MoneroMnemonic.js +++ b/crypto/blockchains/xmr/ext/MoneroMnemonic.js @@ -5,39 +5,64 @@ * based on * https://github.com/Coinomi/bip39-coinomi */ -import MoneroDict from './MoneroDict' -import BlocksoftUtils from '../../../common/BlocksoftUtils' +import MoneroDict from './MoneroDict'; +import BlocksoftUtils from '../../../common/BlocksoftUtils'; -const crc32 = require('buffer-crc32') +const crc32 = require('buffer-crc32'); export default { - secret_spend_key_to_words(secretSpendKeyBufferOrHex, walletHash) { - const buff = typeof secretSpendKeyBufferOrHex === 'string' ? Buffer.from(secretSpendKeyBufferOrHex, 'hex') : secretSpendKeyBufferOrHex - var seed = [] - var forChecksum = '' - for (var i = 0; i < 32; i += 4) { - var w0 = 0 - for (var j = 3; j >= 0; j--) { - w0 = w0 * 256 + buff[i + j] - if (typeof buff[i + j] === 'undefined') { - throw new Error('XMR word for wallet ' + walletHash + ' need to be rechecked as buff is too low') - } - } + secret_spend_key_to_words(secretSpendKeyBufferOrHex, walletHash) { + const buff = + typeof secretSpendKeyBufferOrHex === 'string' + ? Buffer.from(secretSpendKeyBufferOrHex, 'hex') + : secretSpendKeyBufferOrHex; + var seed = []; + var forChecksum = ''; + for (var i = 0; i < 32; i += 4) { + var w0 = 0; + for (var j = 3; j >= 0; j--) { + w0 = w0 * 256 + buff[i + j]; + if (typeof buff[i + j] === 'undefined') { + throw new Error( + 'XMR word for wallet ' + + walletHash + + ' need to be rechecked as buff is too low' + ); + } + } - var w1 = w0 % MoneroDict.monero_words_english.length - var w2 = ((w0 / MoneroDict.monero_words_english.length | 0) + w1) % MoneroDict.monero_words_english.length - var w3 = (((w0 / MoneroDict.monero_words_english.length | 0) / MoneroDict.monero_words_english.length | 0) + w2) % MoneroDict.monero_words_english.length + var w1 = w0 % MoneroDict.monero_words_english.length; + var w2 = + (((w0 / MoneroDict.monero_words_english.length) | 0) + w1) % + MoneroDict.monero_words_english.length; + var w3 = + (((((w0 / MoneroDict.monero_words_english.length) | 0) / + MoneroDict.monero_words_english.length) | + 0) + + w2) % + MoneroDict.monero_words_english.length; - seed.push(MoneroDict.monero_words_english[w1]) - seed.push(MoneroDict.monero_words_english[w2]) - seed.push(MoneroDict.monero_words_english[w3]) - forChecksum += MoneroDict.monero_words_english[w1].substring(0, MoneroDict.monero_words_english_prefix_len) - forChecksum += MoneroDict.monero_words_english[w2].substring(0, MoneroDict.monero_words_english_prefix_len) - forChecksum += MoneroDict.monero_words_english[w3].substring(0, MoneroDict.monero_words_english_prefix_len) - } - const crc32Res = crc32(forChecksum) - const crc32Decimal = BlocksoftUtils.hexToDecimal('0x' + crc32Res.toString('hex')) - seed.push(seed[crc32Decimal % 24]) - return seed.join(' ') + seed.push(MoneroDict.monero_words_english[w1]); + seed.push(MoneroDict.monero_words_english[w2]); + seed.push(MoneroDict.monero_words_english[w3]); + forChecksum += MoneroDict.monero_words_english[w1].substring( + 0, + MoneroDict.monero_words_english_prefix_len + ); + forChecksum += MoneroDict.monero_words_english[w2].substring( + 0, + MoneroDict.monero_words_english_prefix_len + ); + forChecksum += MoneroDict.monero_words_english[w3].substring( + 0, + MoneroDict.monero_words_english_prefix_len + ); } -} + const crc32Res = crc32(forChecksum); + const crc32Decimal = BlocksoftUtils.hexToDecimal( + '0x' + crc32Res.toString('hex') + ); + seed.push(seed[crc32Decimal % 24]); + return seed.join(' '); + } +}; diff --git a/crypto/blockchains/xmr/ext/MoneroUtilsParser.js b/crypto/blockchains/xmr/ext/MoneroUtilsParser.js index 8cb93c1ad..ed891effb 100644 --- a/crypto/blockchains/xmr/ext/MoneroUtilsParser.js +++ b/crypto/blockchains/xmr/ext/MoneroUtilsParser.js @@ -3,198 +3,251 @@ * @version 0.2 * https://github.com/mymonero/mymonero-utils/blob/8ea7ff51f931d3c5e27e2ffd2eb8945cdec8e050/packages/mymonero-response-parser-utils/index.js */ -import config from '../../../../app/config/config' +import config from '../../../../app/config/config'; -import * as payment from '@mymonero/mymonero-paymentid-utils' +import * as payment from '@mymonero/mymonero-paymentid-utils'; // import * as parser from '@mymonero/mymonero-response-parser-utils/ResponseParser' -import * as parser from './vendor/ResponseParser' -import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog' +import * as parser from './vendor/ResponseParser'; +import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; -const MyMoneroCoreBridgeRN = require('react-native-mymonero-core/src/index') -const MY_MONERO = { core: false } +const MyMoneroCoreBridgeRN = require('react-native-mymonero-core/src/index'); +const MY_MONERO = { core: false }; export default { + checkDestination(value) { + return payment.IsValidPaymentIDOrNoPaymentID(value); + }, - checkDestination(value) { - return payment.IsValidPaymentIDOrNoPaymentID(value) - }, - - async getCore() { - - if (MY_MONERO.core) { - return MY_MONERO.core - } - MY_MONERO.core = MyMoneroCoreBridgeRN - MY_MONERO.core.generate_key_image = async (txPublicKey, privateViewKey, publicSpendKey, privateSpendKey, outputIndex) => { - if (!txPublicKey || !privateViewKey || !publicSpendKey || !privateSpendKey) { - throw new Error('no keys 1') - } - if (typeof txPublicKey === 'undefined' || typeof privateViewKey === 'undefined' || typeof publicSpendKey === 'undefined' || typeof privateSpendKey === 'undefined') { - throw new Error('no keys 2') - } - if (txPublicKey === 'undefined' || privateViewKey === 'undefined' || publicSpendKey === 'undefined' || privateSpendKey === 'undefined') { - throw new Error('no keys 3') - } - if (typeof outputIndex === 'undefined') { - outputIndex = '' - } - + async getCore() { + if (MY_MONERO.core) { + return MY_MONERO.core; + } + MY_MONERO.core = MyMoneroCoreBridgeRN; + MY_MONERO.core.generate_key_image = async ( + txPublicKey, + privateViewKey, + publicSpendKey, + privateSpendKey, + outputIndex + ) => { + if ( + !txPublicKey || + !privateViewKey || + !publicSpendKey || + !privateSpendKey + ) { + throw new Error('no keys 1'); + } + if ( + typeof txPublicKey === 'undefined' || + typeof privateViewKey === 'undefined' || + typeof publicSpendKey === 'undefined' || + typeof privateSpendKey === 'undefined' + ) { + throw new Error('no keys 2'); + } + if ( + txPublicKey === 'undefined' || + privateViewKey === 'undefined' || + publicSpendKey === 'undefined' || + privateSpendKey === 'undefined' + ) { + throw new Error('no keys 3'); + } + if (typeof outputIndex === 'undefined') { + outputIndex = ''; + } + + try { + let res = await MY_MONERO.core.Module.generateKeyImage( + txPublicKey, + privateViewKey, + publicSpendKey, + privateSpendKey, + outputIndex + '' + ); + if (typeof res !== 'undefined' && res) { + if (typeof res === 'string') { try { - let res = await MY_MONERO.core.Module.generateKeyImage(txPublicKey, privateViewKey, publicSpendKey, privateSpendKey, outputIndex + '') - if (typeof res !== 'undefined' && res) { - if (typeof res === 'string') { - try { - const newRes = JSON.parse(res) - res = newRes - } catch (e) { - - } - } - if (typeof res.retVal !== 'undefined') { - return res.retVal - } - } - return res - } catch (e) { - BlocksoftCryptoLog.log('MoneroUtilsParser.generate_key_image ' + e.message) - throw new Error('MoneroUtilsParser.generate_key_image ' + e.message) - } + const newRes = JSON.parse(res); + res = newRes; + } catch (e) {} + } + if (typeof res.retVal !== 'undefined') { + return res.retVal; + } } - MY_MONERO.core.createTransaction = async (options) => { - if (options.privateViewKey.length !== 64) { - throw Error('Invalid privateViewKey length') - } - if (options.publicSpendKey.length !== 64) { - throw Error('Invalid publicSpendKey length') - } - if (options.privateSpendKey.length !== 64) { - throw Error('Invalid privateSpendKey length') - } - if (typeof options.randomOutsCb !== 'function') { - throw Error('Invalid randomsOutCB not a function') - } - if (!Array.isArray(options.destinations)) { - throw Error('Invalid destinations') - } - options.destinations.forEach(function (destination) { - if (!destination.hasOwnProperty('to_address') || !destination.hasOwnProperty('send_amount')) { - throw Error('Invalid destinations missing values') - } - }) - if (options.shouldSweep) { - if (options.destinations.length !== 1) { - throw Error('Invalid number of destinations must be 1') - } - if (options.destinations[0].send_amount !== 0) { - throw Error('Invalid amount when sweeping amount must be 0') - } - } - - // check if destinations is set correctly - const args = - { - destinations: options.destinations, - is_sweeping: options.shouldSweep, - from_address_string: options.address, - sec_viewKey_string: options.privateViewKey, - sec_spendKey_string: options.privateSpendKey, - pub_spendKey_string: options.publicSpendKey, - priority: '' + options.priority, - nettype_string: options.nettype, - unspentOuts: options.unspentOuts - } - - let retString - try { - retString = await MY_MONERO.core.Module.prepareTx(JSON.stringify(args, null, '')) - } catch (e) { - throw Error(' MY_MONERO.core.Module.prepareTx error ' + e.message) - } - - const ret = JSON.parse(retString) - // check for any errors passed back from WebAssembly - if (ret.err_msg) { - BlocksoftCryptoLog.log('MoneroUtilsParser ret.err_msg error ' + ret.err_msg) - return false - } - - const _getRandomOuts = async (numberOfOuts, randomOutsCb) => { - const randomOuts = await randomOutsCb(numberOfOuts) - if (typeof randomOuts.amount_outs === 'undefined' || !Array.isArray(randomOuts.amount_outs)) { - throw Error('Invalid amount_outs in randomOutsCb response') - } - return randomOuts - } - - BlocksoftCryptoLog.log('MoneroUtilsParser ret?.amounts?.length ' + ret?.amounts?.length) - - // fetch random decoys - const randomOuts = await _getRandomOuts(ret?.amounts?.length || 0, options.randomOutsCb) - // send random decoys on and complete the tx creation - const retString2 = await MY_MONERO.core.Module.createAndSignTx(JSON.stringify(randomOuts)) - const rawTx = JSON.parse(retString2) - // check for any errors passed back from WebAssembly - if (rawTx.err_msg) { - throw Error(rawTx.err_msg) - } - // parse variables ruturned as strings - rawTx.mixin = parseInt(rawTx.mixin) - rawTx.isXMRAddressIntegrated = rawTx.isXMRAddressIntegrated === 'true' - - return rawTx + return res; + } catch (e) { + BlocksoftCryptoLog.log( + 'MoneroUtilsParser.generate_key_image ' + e.message + ); + throw new Error('MoneroUtilsParser.generate_key_image ' + e.message); + } + }; + MY_MONERO.core.createTransaction = async (options) => { + if (options.privateViewKey.length !== 64) { + throw Error('Invalid privateViewKey length'); + } + if (options.publicSpendKey.length !== 64) { + throw Error('Invalid publicSpendKey length'); + } + if (options.privateSpendKey.length !== 64) { + throw Error('Invalid privateSpendKey length'); + } + if (typeof options.randomOutsCb !== 'function') { + throw Error('Invalid randomsOutCB not a function'); + } + if (!Array.isArray(options.destinations)) { + throw Error('Invalid destinations'); + } + options.destinations.forEach(function (destination) { + if ( + !destination.hasOwnProperty('to_address') || + !destination.hasOwnProperty('send_amount') + ) { + throw Error('Invalid destinations missing values'); } - return MY_MONERO.core - }, - - async parseAddressInfo(address, data, privViewKey, pubSpendKey, privSpendKey) { - try { - await this.getCore() - let resData = false - await parser.Parsed_AddressInfo__keyImageManaged( - data, - address, - privViewKey, - pubSpendKey, - privSpendKey, - MY_MONERO.core, - (e, returnValuesByKey) => { - if (e) { - console.log('MoneroUtilsParser.parseAddressInfo error2', e) - } - resData = returnValuesByKey - } - ) - return resData - } catch (e) { - if (config.debug.cryptoErrors) { - console.log('MoneroUtilsParser.parseAddressInfo error', e) - } + }); + if (options.shouldSweep) { + if (options.destinations.length !== 1) { + throw Error('Invalid number of destinations must be 1'); } - }, - - async parseAddressTransactions(address, data, privViewKey, pubSpendKey, privSpendKey) { - try { - await this.getCore() - let resData = false - await parser.Parsed_AddressTransactions__keyImageManaged( - data, - address, - privViewKey, - pubSpendKey, - privSpendKey, - MY_MONERO.core, - (e, returnValuesByKey) => { - if (e) { - console.log('MoneroUtilsParser.parseAddressTransactions error', e) - } - resData = returnValuesByKey - } - ) - return resData - } catch (e) { - if (config.debug.cryptoErrors) { - console.log('MoneroUtilsParser.parseAddressTransactions error', e) - } + if (options.destinations[0].send_amount !== 0) { + throw Error('Invalid amount when sweeping amount must be 0'); + } + } + + // check if destinations is set correctly + const args = { + destinations: options.destinations, + is_sweeping: options.shouldSweep, + from_address_string: options.address, + sec_viewKey_string: options.privateViewKey, + sec_spendKey_string: options.privateSpendKey, + pub_spendKey_string: options.publicSpendKey, + priority: '' + options.priority, + nettype_string: options.nettype, + unspentOuts: options.unspentOuts + }; + + let retString; + try { + retString = await MY_MONERO.core.Module.prepareTx( + JSON.stringify(args, null, '') + ); + } catch (e) { + throw Error(' MY_MONERO.core.Module.prepareTx error ' + e.message); + } + + const ret = JSON.parse(retString); + // check for any errors passed back from WebAssembly + if (ret.err_msg) { + BlocksoftCryptoLog.log( + 'MoneroUtilsParser ret.err_msg error ' + ret.err_msg + ); + return false; + } + + const _getRandomOuts = async (numberOfOuts, randomOutsCb) => { + const randomOuts = await randomOutsCb(numberOfOuts); + if ( + typeof randomOuts.amount_outs === 'undefined' || + !Array.isArray(randomOuts.amount_outs) + ) { + throw Error('Invalid amount_outs in randomOutsCb response'); + } + return randomOuts; + }; + + BlocksoftCryptoLog.log( + 'MoneroUtilsParser ret?.amounts?.length ' + ret?.amounts?.length + ); + + // fetch random decoys + const randomOuts = await _getRandomOuts( + ret?.amounts?.length || 0, + options.randomOutsCb + ); + // send random decoys on and complete the tx creation + const retString2 = await MY_MONERO.core.Module.createAndSignTx( + JSON.stringify(randomOuts) + ); + const rawTx = JSON.parse(retString2); + // check for any errors passed back from WebAssembly + if (rawTx.err_msg) { + throw Error(rawTx.err_msg); + } + // parse variables ruturned as strings + rawTx.mixin = parseInt(rawTx.mixin); + rawTx.isXMRAddressIntegrated = rawTx.isXMRAddressIntegrated === 'true'; + + return rawTx; + }; + return MY_MONERO.core; + }, + + async parseAddressInfo( + address, + data, + privViewKey, + pubSpendKey, + privSpendKey + ) { + try { + await this.getCore(); + let resData = false; + await parser.Parsed_AddressInfo__keyImageManaged( + data, + address, + privViewKey, + pubSpendKey, + privSpendKey, + MY_MONERO.core, + (e, returnValuesByKey) => { + if (e) { + console.log('MoneroUtilsParser.parseAddressInfo error2', e); + } + resData = returnValuesByKey; + } + ); + return resData; + } catch (e) { + if (config.debug.cryptoErrors) { + console.log('MoneroUtilsParser.parseAddressInfo error', e); + } + } + }, + + async parseAddressTransactions( + address, + data, + privViewKey, + pubSpendKey, + privSpendKey + ) { + try { + await this.getCore(); + let resData = false; + await parser.Parsed_AddressTransactions__keyImageManaged( + data, + address, + privViewKey, + pubSpendKey, + privSpendKey, + MY_MONERO.core, + (e, returnValuesByKey) => { + if (e) { + console.log('MoneroUtilsParser.parseAddressTransactions error', e); + } + resData = returnValuesByKey; } + ); + return resData; + } catch (e) { + if (config.debug.cryptoErrors) { + console.log('MoneroUtilsParser.parseAddressTransactions error', e); + } } -} + } +}; diff --git a/crypto/blockchains/xmr/providers/XmrSendProvider.js b/crypto/blockchains/xmr/providers/XmrSendProvider.js index dad5f484a..5b20112de 100644 --- a/crypto/blockchains/xmr/providers/XmrSendProvider.js +++ b/crypto/blockchains/xmr/providers/XmrSendProvider.js @@ -1,86 +1,92 @@ /** * @version 0.11 */ -import settingsActions from '../../../../app/appstores/Stores/Settings/SettingsActions' -import BlocksoftAxios from '../../../common/BlocksoftAxios' -import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog' +import settingsActions from '../../../../app/appstores/Stores/Settings/SettingsActions'; +import BlocksoftAxios from '../../../common/BlocksoftAxios'; +import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; export default class XmrSendProvider { + constructor(settings) { + this._settings = settings; + this._link = false; + } - constructor(settings) { - this._settings = settings - this._link = false - + async _init() { + if (this._link) return false; + this._serverUrl = await settingsActions.getSetting('xmrServerSend'); + if (!this._serverUrl || this._serverUrl === 'false') { + this._serverUrl = 'api.mymonero.com:8443'; } - async _init() { - if (this._link) return false - this._serverUrl = await settingsActions.getSetting('xmrServerSend') - if (!this._serverUrl || this._serverUrl === 'false') { - this._serverUrl = 'api.mymonero.com:8443' - } - - let link = this._serverUrl.trim() - if (link.substr(0, 4).toLowerCase() !== 'http') { - if (link.indexOf('mymonero.com') !== -1) { - link = 'https://' + this._serverUrl - } else { - link = 'http://' + this._serverUrl - } - } - if (link[link.length - 1] !== '/') { - link = link + '/' - } - - this._link = link + let link = this._serverUrl.trim(); + if (link.substr(0, 4).toLowerCase() !== 'http') { + if (link.indexOf('mymonero.com') !== -1) { + link = 'https://' + this._serverUrl; + } else { + link = 'http://' + this._serverUrl; + } + } + if (link[link.length - 1] !== '/') { + link = link + '/'; } - async send(params) { - await this._init() - // curl http://127.0.0.1:18081/send_raw_transaction -d '{"tx_as_hex":"de6a3...", "do_not_relay":false}' -H 'Content-Type: application/json' - // https://web.getmonero.org/resources/developer-guides/daemon-rpc.html#send_raw_transaction - // const resNode = await BlocksoftAxios.post('http://node.moneroworld.com:18089/send_raw_transaction', {tx_as_hex : params.tx, do_not_relay : false}) - // return resNode.data + this._link = link; + } + + async send(params) { + await this._init(); + // curl http://127.0.0.1:18081/send_raw_transaction -d '{"tx_as_hex":"de6a3...", "do_not_relay":false}' -H 'Content-Type: application/json' + // https://web.getmonero.org/resources/developer-guides/daemon-rpc.html#send_raw_transaction + // const resNode = await BlocksoftAxios.post('http://node.moneroworld.com:18089/send_raw_transaction', {tx_as_hex : params.tx, do_not_relay : false}) + // return resNode.data - if (this._link.indexOf('mymonero.com') !== -1) { - try { - const res = await BlocksoftAxios.post(this._link + 'submit_raw_tx', { - address: params.address, - view_key: params.privViewKey, - tx: params.tx - }) - BlocksoftCryptoLog.log('XmrSendProvider mymonero.com node ' + this._link, res.data) - return res.data - } catch (e) { - if (e.message.indexOf('double') !== -1) { - throw new Error('SERVER_RESPONSE_DOUBLE_SPEND') - } else { - throw e - } - } + if (this._link.indexOf('mymonero.com') !== -1) { + try { + const res = await BlocksoftAxios.post(this._link + 'submit_raw_tx', { + address: params.address, + view_key: params.privViewKey, + tx: params.tx + }); + BlocksoftCryptoLog.log( + 'XmrSendProvider mymonero.com node ' + this._link, + res.data + ); + return res.data; + } catch (e) { + if (e.message.indexOf('double') !== -1) { + throw new Error('SERVER_RESPONSE_DOUBLE_SPEND'); } else { - const resNode = await BlocksoftAxios.post(this._link + 'send_raw_transaction', { - tx_as_hex : params.tx, - do_not_relay : false - }) - BlocksoftCryptoLog.log('XmrSendProvider custom node ' + this._link, resNode.data) - if (typeof resNode.data.double_spend === 'undefined') { - throw new Error('SERVER_RESPONSE_BAD_SEND_NODE') - } - if (resNode.data.double_spend) { - throw new Error('SERVER_RESPONSE_DOUBLE_SPEND') - } - if (resNode.data.fee_too_low) { - throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_FEE') - } - if (resNode.data.overspend) { - throw new Error('SERVER_RESPONSE_TOO_BIG_FEE_FOR_TRANSACTION') - } - if (resNode.data.status === 'Failed') { - throw new Error(JSON.stringify(resNode.data)) - } - return resNode.data + throw e; } + } + } else { + const resNode = await BlocksoftAxios.post( + this._link + 'send_raw_transaction', + { + tx_as_hex: params.tx, + do_not_relay: false + } + ); + BlocksoftCryptoLog.log( + 'XmrSendProvider custom node ' + this._link, + resNode.data + ); + if (typeof resNode.data.double_spend === 'undefined') { + throw new Error('SERVER_RESPONSE_BAD_SEND_NODE'); + } + if (resNode.data.double_spend) { + throw new Error('SERVER_RESPONSE_DOUBLE_SPEND'); + } + if (resNode.data.fee_too_low) { + throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_FEE'); + } + if (resNode.data.overspend) { + throw new Error('SERVER_RESPONSE_TOO_BIG_FEE_FOR_TRANSACTION'); + } + if (resNode.data.status === 'Failed') { + throw new Error(JSON.stringify(resNode.data)); + } + return resNode.data; } - + } } diff --git a/crypto/blockchains/xmr/providers/XmrUnspentsProvider.js b/crypto/blockchains/xmr/providers/XmrUnspentsProvider.js index fd09c7820..22628f59f 100644 --- a/crypto/blockchains/xmr/providers/XmrUnspentsProvider.js +++ b/crypto/blockchains/xmr/providers/XmrUnspentsProvider.js @@ -1,44 +1,43 @@ /** * @version 0.11 */ -import settingsActions from '../../../../app/appstores/Stores/Settings/SettingsActions' -import BlocksoftAxios from '../../../common/BlocksoftAxios' -import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog' +import settingsActions from '../../../../app/appstores/Stores/Settings/SettingsActions'; +import BlocksoftAxios from '../../../common/BlocksoftAxios'; +import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; export default class XmrUnspentsProvider { + constructor(settings) { + this._settings = settings; + this._link = false; + this._cache = {}; + } - constructor(settings) { - this._settings = settings - this._link = false - this._cache = {} + init() { + if (this._link) return false; + this._serverUrl = settingsActions.getSettingStatic('xmrServer'); + if (!this._serverUrl || this._serverUrl === 'false') { + this._serverUrl = 'api.mymonero.com:8443'; } - init() { - if (this._link) return false - this._serverUrl = settingsActions.getSettingStatic('xmrServer') - if (!this._serverUrl || this._serverUrl === 'false') { - this._serverUrl = 'api.mymonero.com:8443' - } - - let link = this._serverUrl.trim() - if (link.substr(0, 4).toLowerCase() !== 'http') { - link = 'https://' + this._serverUrl - } - if (link[link.length - 1] !== '/') { - link = link + '/' - } - - this._link = link - this._cache = {} + let link = this._serverUrl.trim(); + if (link.substr(0, 4).toLowerCase() !== 'http') { + link = 'https://' + this._serverUrl; + } + if (link[link.length - 1] !== '/') { + link = link + '/'; } - async _getUnspents(params, fn) { - try { - const key = JSON.stringify(params) - let res = {} - if (typeof this._cache[key] === 'undefined') { - BlocksoftCryptoLog.log('XmrUnspentsProvider Xmr._getUnspents', key) - /* + this._link = link; + this._cache = {}; + } + + async _getUnspents(params, fn) { + try { + const key = JSON.stringify(params); + let res = {}; + if (typeof this._cache[key] === 'undefined') { + BlocksoftCryptoLog.log('XmrUnspentsProvider Xmr._getUnspents', key); + /* const linkParams = { address: params.address, view_key: params.privViewKey, @@ -48,33 +47,42 @@ export default class XmrUnspentsProvider { dust_threshold: '2000000000' } */ - res = await BlocksoftAxios.post(this._link + 'get_unspent_outs', params) - BlocksoftCryptoLog.log('XmrUnspentsProvider Xmr._getUnspents res ' + JSON.stringify(res.data).substr(0, 200)) - this._cache[key] = res.data - } else { - res = {data : this._cache[key]} - } - if (typeof fn === 'undefined' || !fn) { - return res.data - } else { - fn(null, res.data) - } - } catch (e) { - e.message += ' while Xmr._getUnspents' - fn(e, null) - if (typeof fn === 'undefined' || !fn) { - throw e - } else { - fn(e, null) - } - } + res = await BlocksoftAxios.post( + this._link + 'get_unspent_outs', + params + ); + BlocksoftCryptoLog.log( + 'XmrUnspentsProvider Xmr._getUnspents res ' + + JSON.stringify(res.data).substr(0, 200) + ); + this._cache[key] = res.data; + } else { + res = { data: this._cache[key] }; + } + if (typeof fn === 'undefined' || !fn) { + return res.data; + } else { + fn(null, res.data); + } + } catch (e) { + e.message += ' while Xmr._getUnspents'; + fn(e, null); + if (typeof fn === 'undefined' || !fn) { + throw e; + } else { + fn(e, null); + } } + } - async _getRandomOutputs(params, fn) { - try { - BlocksoftCryptoLog.log('XmrUnspentsProvider Xmr._getRandomOutputs', params) + async _getRandomOutputs(params, fn) { + try { + BlocksoftCryptoLog.log( + 'XmrUnspentsProvider Xmr._getRandomOutputs', + params + ); - /* + /* const amounts = usingOuts.map(o => (o.rct ? '0' : o.amount.toString())) const linkParams = { amounts, @@ -82,28 +90,39 @@ export default class XmrUnspentsProvider { } */ - const res = await BlocksoftAxios.post(this._link + 'get_random_outs', params) - await BlocksoftCryptoLog.log('XmrUnspentsProvider Xmr._getRandomOutputs res ' + JSON.stringify(res.data).substr(0, 200)) + const res = await BlocksoftAxios.post( + this._link + 'get_random_outs', + params + ); + await BlocksoftCryptoLog.log( + 'XmrUnspentsProvider Xmr._getRandomOutputs res ' + + JSON.stringify(res.data).substr(0, 200) + ); - if (typeof res.data === 'undefined' || !typeof res.data || typeof res.data.amount_outs === 'undefined' || !res.data.amount_outs || res.data.amount_outs.length === 0) { - throw new Error('SERVER_RESPONSE_NO_RESPONSE_XMR') - } + if ( + typeof res.data === 'undefined' || + !typeof res.data || + typeof res.data.amount_outs === 'undefined' || + !res.data.amount_outs || + res.data.amount_outs.length === 0 + ) { + throw new Error('SERVER_RESPONSE_NO_RESPONSE_XMR'); + } - if (typeof fn === 'undefined' || !fn) { - return res.data - } else { - fn(null, res.data) - } - } catch (e) { - if (e.message.indexOf('SERVER_RESPONSE') === -1) { - e.message += ' while Xmr._getRandomOutputs' - } - if (typeof fn === 'undefined' || !fn) { - throw e - } else { - fn(e, null) - } - } + if (typeof fn === 'undefined' || !fn) { + return res.data; + } else { + fn(null, res.data); + } + } catch (e) { + if (e.message.indexOf('SERVER_RESPONSE') === -1) { + e.message += ' while Xmr._getRandomOutputs'; + } + if (typeof fn === 'undefined' || !fn) { + throw e; + } else { + fn(e, null); + } } - + } } diff --git a/crypto/blockchains/xrp/XrpScannerProcessor.js b/crypto/blockchains/xrp/XrpScannerProcessor.js index b2d47f7f0..6b75467cb 100644 --- a/crypto/blockchains/xrp/XrpScannerProcessor.js +++ b/crypto/blockchains/xrp/XrpScannerProcessor.js @@ -1,89 +1,94 @@ /** * @version 0.5 */ -import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog' -import XrpTmpDS from './stores/XrpTmpDS' -import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings' -import XrpDataRippleProvider from '@crypto/blockchains/xrp/basic/XrpDataRippleProvider' -import XrpDataScanProvider from '@crypto/blockchains/xrp/basic/XrpDataScanProvider' +import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; +import XrpTmpDS from './stores/XrpTmpDS'; +import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings'; +import XrpDataRippleProvider from '@crypto/blockchains/xrp/basic/XrpDataRippleProvider'; +import XrpDataScanProvider from '@crypto/blockchains/xrp/basic/XrpDataScanProvider'; - -let CACHE_BLOCK_DATA = {} +let CACHE_BLOCK_DATA = {}; export default class XrpScannerProcessor { + _inited = false; - _inited = false - - async init() { - if (this._inited) { - return false - } - CACHE_BLOCK_DATA = await XrpTmpDS.getCache() - const serverType = BlocksoftExternalSettings.getStatic('XRP_SCANNER_TYPE') - if (serverType === 'dataripple') { - this.provider = new XrpDataRippleProvider() - } else { - this.provider = new XrpDataScanProvider() - } - this.provider.setCache(CACHE_BLOCK_DATA) - this._inited = true + async init() { + if (this._inited) { + return false; } - - /** - * https://data.ripple.com/v2/accounts/rL2SpzwrCZ4N2BaPm88pNGGHkPLzejZgB8/balances - * @param {string} address - * @return {Promise<{balance, unconfirmed, provider}>} - */ - async getBalanceBlockchain(address) { - await this.init() - - let res = false - let balance = 0 - let provider = 'none' - try { - res = await this.provider.getBalanceBlockchain(address) - if (res && typeof res.balance !== 'undefined') { - balance = res.balance - provider = res.provider - } - } catch (e) { - if (e.message.indexOf('timed out') === -1 && e.message.indexOf('account not found') === -1) { - throw e - } else { - return false - } - } - return { balance, unconfirmed: 0, provider } + CACHE_BLOCK_DATA = await XrpTmpDS.getCache(); + const serverType = BlocksoftExternalSettings.getStatic('XRP_SCANNER_TYPE'); + if (serverType === 'dataripple') { + this.provider = new XrpDataRippleProvider(); + } else { + this.provider = new XrpDataScanProvider(); } + this.provider.setCache(CACHE_BLOCK_DATA); + this._inited = true; + } + /** + * https://data.ripple.com/v2/accounts/rL2SpzwrCZ4N2BaPm88pNGGHkPLzejZgB8/balances + * @param {string} address + * @return {Promise<{balance, unconfirmed, provider}>} + */ + async getBalanceBlockchain(address) { + await this.init(); - /** - * @param {string} scanData.account.address - * @param {*} scanData.additional - * @param {string} scanData.account.walletHash - * @return {Promise} - */ - async getTransactionsBlockchain(scanData, source = '') { - await this.init() - const address = scanData.account.address.trim() - await BlocksoftCryptoLog.log('XrpScannerProcessor.getTransactions started ' + address) + let res = false; + let balance = 0; + let provider = 'none'; + try { + res = await this.provider.getBalanceBlockchain(address); + if (res && typeof res.balance !== 'undefined') { + balance = res.balance; + provider = res.provider; + } + } catch (e) { + if ( + e.message.indexOf('timed out') === -1 && + e.message.indexOf('account not found') === -1 + ) { + throw e; + } else { + return false; + } + } + return { balance, unconfirmed: 0, provider }; + } - let transactions = [] - try { - transactions = await this.provider.getTransactionsBlockchain(scanData) - } catch (e) { - if (e.message.indexOf('account not found') === -1 - && e.message.indexOf('to retrieve payments') === -1 - && e.message.indexOf('limit exceeded') === -1 - && e.message.indexOf('timed out') === -1 - ) { - throw e - } else { - return false - } - } + /** + * @param {string} scanData.account.address + * @param {*} scanData.additional + * @param {string} scanData.account.walletHash + * @return {Promise} + */ + async getTransactionsBlockchain(scanData, source = '') { + await this.init(); + const address = scanData.account.address.trim(); + await BlocksoftCryptoLog.log( + 'XrpScannerProcessor.getTransactions started ' + address + ); - await BlocksoftCryptoLog.log('XrpScannerProcessor.getTransactions finished ' + address) - return transactions + let transactions = []; + try { + transactions = await this.provider.getTransactionsBlockchain(scanData); + } catch (e) { + if ( + e.message.indexOf('account not found') === -1 && + e.message.indexOf('to retrieve payments') === -1 && + e.message.indexOf('limit exceeded') === -1 && + e.message.indexOf('timed out') === -1 + ) { + throw e; + } else { + return false; + } } + + await BlocksoftCryptoLog.log( + 'XrpScannerProcessor.getTransactions finished ' + address + ); + return transactions; + } } diff --git a/crypto/blockchains/xrp/XrpTransferProcessor.ts b/crypto/blockchains/xrp/XrpTransferProcessor.ts index 7a8722e96..871b3bee9 100644 --- a/crypto/blockchains/xrp/XrpTransferProcessor.ts +++ b/crypto/blockchains/xrp/XrpTransferProcessor.ts @@ -6,224 +6,323 @@ * https://xrpl.org/rippleapi-reference.html#sign * https://xrpl.org/rippleapi-reference.html#submit */ -import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog' -import BlocksoftUtils from '../../common/BlocksoftUtils' -import BlocksoftDispatcher from '../BlocksoftDispatcher' -import MarketingEvent from '../../../app/services/Marketing/MarketingEvent' - -import { BlocksoftBlockchainTypes } from '../BlocksoftBlockchainTypes' -import { XrpTxSendProvider } from './basic/XrpTxSendProvider' -import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings' - - -const FEE_DECIMALS = 6 - -export default class XrpTransferProcessor implements BlocksoftBlockchainTypes.TransferProcessor { - private _settings: { network: string; currencyCode: string } - private _provider: XrpTxSendProvider - private _inited : boolean = false - - constructor(settings: { network: string; currencyCode: string }) { - this._settings = settings - } - - needPrivateForFee(): boolean { - return false - } - - checkSendAllModal(data: { currencyCode: any }): boolean { - return false - } - - async checkTransferHasError(data: BlocksoftBlockchainTypes.CheckTransferHasErrorData): Promise { - // @ts-ignore - if (data.amount && data.amount * 1 > BlocksoftExternalSettings.getStatic('XRP_MIN')) { - return { isOk: true } - } - /** - * @type {XrpScannerProcessor} - */ - const balanceProvider = BlocksoftDispatcher.getScannerProcessor(this._settings.currencyCode) - const balanceRaw = await balanceProvider.getBalanceBlockchain(data.addressTo) - if (balanceRaw && typeof balanceRaw.balance !== 'undefined' && balanceRaw.balance > BlocksoftExternalSettings.getStatic('XRP_MIN')) { - return { isOk: true } - } else { - return { isOk: false, code: 'XRP', address: data.addressTo } - } - } - - async getFeeRate(data: BlocksoftBlockchainTypes.TransferData, privateData: BlocksoftBlockchainTypes.TransferPrivateData, additionalData: {} = {}): Promise { - const result: BlocksoftBlockchainTypes.FeeRateResult = { - selectedFeeIndex: -1, - shouldShowFees : false - } as BlocksoftBlockchainTypes.FeeRateResult - - // @ts-ignore - if (data.amount * 1 <= 0) { - BlocksoftCryptoLog.log(this._settings.currencyCode + ' XrpTransferProcessor.getFeeRate ' + data.addressFrom + ' => ' + data.addressTo + ' skipped as zero amount') - return result - } - - BlocksoftCryptoLog.log(this._settings.currencyCode + ' XrpTransferProcessor.getFeeRate ' + data.addressFrom + ' => ' + data.addressTo + ' started amount: ' + data.amount) - - - let txJson = false - try { - if (!this._inited) { - this._provider = new XrpTxSendProvider() - this._inited = true - } - txJson = await this._provider.getPrepared(data) - } catch (e) { - if (e.message.indexOf('connect() timed out after') !== -1) { - return false - } - if (e.message.indexOf('Account not found') !== -1) { - return false - } - if (e.message.indexOf('Destination does not exist. Too little XRP sent to create it') !== -1) { - throw new Error('SERVER_RESPONSE_NOT_ENOUGH_BALANCE_DEST_XRP') - } - throw e - } - if (!txJson) { - throw new Error('SERVER_RESPONSE_BAD_INTERNET') - } - // @ts-ignore - const fee = BlocksoftUtils.toUnified(txJson.Fee, FEE_DECIMALS) - - BlocksoftCryptoLog.log(this._settings.currencyCode + ' XrpTransferProcessor.getFeeRate ' + data.addressFrom + ' => ' + data.addressTo + ' finished amount: ' + data.amount + ' fee: ' + fee) - result.fees = [ - { - langMsg: 'xrp_speed_one', - feeForTx: fee, - amountForTx: data.amount, - blockchainData: txJson - } - ] - result.selectedFeeIndex = 0 - return result - } - - async getTransferAllBalance(data: BlocksoftBlockchainTypes.TransferData, privateData: BlocksoftBlockchainTypes.TransferPrivateData, additionalData: BlocksoftBlockchainTypes.TransferAdditionalData = {}): Promise { - const balance = data.amount - - // @ts-ignore - BlocksoftCryptoLog.log(this._settings.currencyCode + ' XrpTransferProcessor.getTransferAllBalance ', data.addressFrom + ' => ' + balance) - // noinspection EqualityComparisonWithCoercionJS - if (BlocksoftUtils.diff(balance, BlocksoftExternalSettings.getStatic('XRP_MIN')) <= 0) { - return { - selectedTransferAllBalance: '0', - selectedFeeIndex: -1, - fees: [], - shouldShowFees : false, - countedForBasicBalance: '0' - } - } - - - const result = await this.getFeeRate(data, privateData, additionalData) - // @ts-ignore - if (!result || result.selectedFeeIndex < 0) { - return { - selectedTransferAllBalance: '0', - selectedFeeIndex: -2, - fees: [], - shouldShowFees : false, - countedForBasicBalance: balance - } - } - // @ts-ignore - result.fees[result.selectedFeeIndex].amountForTx = BlocksoftUtils.diff(result.fees[result.selectedFeeIndex].amountForTx, BlocksoftExternalSettings.getStatic('XRP_MIN')).toString() - return { - ...result, - shouldShowFees : false, - selectedTransferAllBalance: result.fees[result.selectedFeeIndex].amountForTx - } - } - - async sendTx(data: BlocksoftBlockchainTypes.TransferData, privateData: BlocksoftBlockchainTypes.TransferPrivateData, uiData: BlocksoftBlockchainTypes.TransferUiData): Promise { - - if (typeof privateData.privateKey === 'undefined') { - throw new Error('XRP transaction required privateKey') - } - if (typeof data.addressTo === 'undefined') { - throw new Error('XRP transaction required addressTo') - } - - let txJson = false - try { - if (!this._inited) { - this._provider = new XrpTxSendProvider() - this._inited = true - } - txJson = await this._provider.getPrepared(data, false) - } catch (e) { - if (e.message.indexOf('connect() timed out after') !== -1) { - throw new Error('SERVER_RESPONSE_BAD_INTERNET') - } - if (e.message.indexOf('Account not found') !== -1) { - throw new Error('SERVER_RESPONSE_BAD_INTERNET') - } - if (e.message.indexOf('Destination does not exist. Too little XRP sent to create it') !== -1) { - throw new Error('SERVER_RESPONSE_NOT_ENOUGH_BALANCE_DEST_XRP') - } - throw e - } - - - // https://xrpl.org/rippleapi-reference.html#preparepayment - // @ts-ignore - BlocksoftCryptoLog.log(this._settings.currencyCode + ' XrpTransferProcessor.sendTx prepared', txJson) - - // https://xrpl.org/rippleapi-reference.html#sign - if (typeof data.accountJson !== 'object') { - try { - const tmp = JSON.parse(data.accountJson) - data.accountJson = tmp - } catch (e) { - BlocksoftCryptoLog.err(this._settings.currencyCode + ' XrpTransferProcessor.sendTx no accountJson ' + JSON.stringify(data.accountJson)) - } - } - if (typeof data.accountJson.publicKey === 'undefined') { - BlocksoftCryptoLog.err(this._settings.currencyCode + ' XrpTransferProcessor.sendTx no publicKey ' + JSON.stringify(data.accountJson)) - throw new Error('SERVER_RESPONSE_BAD_CODE') - } - - if (typeof uiData !== 'undefined' && typeof uiData.selectedFee !== 'undefined'&& typeof uiData.selectedFee.rawOnly !== 'undefined' && uiData.selectedFee.rawOnly) { - return { rawOnly: uiData.selectedFee.rawOnly, raw : this._provider.signTx(data, privateData, txJson)} - } - - const result = await this._provider.sendTx(data, privateData, txJson) - - // noinspection ES6MissingAwait - MarketingEvent.logOnlyRealTime('v20_rippled_any_result ' + data.addressFrom + ' => ' + data.addressTo, { - txJson, - result - }) - // @ts-ignore - BlocksoftCryptoLog.log(this._settings.currencyCode + ' XrpTransferProcessor.sendTx result', result) - - if (result.resultCode === 'tecNO_DST_INSUF_XRP') { - throw new Error(result.resultMessage) // not enough - could be replaced by translated - } else if (result.resultCode === 'tecUNFUNDED_PAYMENT') { - throw new Error('SERVER_RESPONSE_NOT_ENOUGH_BALANCE_XRP') // not enough to pay - } else if (result.resultCode === 'tecNO_DST_INSUF_XRP') { - throw new Error('SERVER_RESPONSE_NOT_ENOUGH_BALANCE_DEST_XRP') // not enough to create account - } else if (result.resultCode === 'tefBAD_AUTH') { - throw new Error(result.resultMessage) // not valid key - } else if (result.resultCode === 'tecDST_TAG_NEEDED') { - throw new Error('SERVER_RESPONSE_TAG_NEEDED_XRP') - } - - if (typeof result.tx_json === 'undefined' || typeof result.tx_json.hash === 'undefined') { - throw new Error(result.resultMessage) // not enough - } - - if (result.resultCode !== 'tesSUCCESS') { - return { transactionHash: result.tx_json.hash, successMessage: result.resultMessage } // Held until escalated fee drops - } - - return { transactionHash: result.tx_json.hash } +import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog'; +import BlocksoftUtils from '../../common/BlocksoftUtils'; +import BlocksoftDispatcher from '../BlocksoftDispatcher'; +import MarketingEvent from '../../../app/services/Marketing/MarketingEvent'; + +import { BlocksoftBlockchainTypes } from '../BlocksoftBlockchainTypes'; +import { XrpTxSendProvider } from './basic/XrpTxSendProvider'; +import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings'; + +const FEE_DECIMALS = 6; + +export default class XrpTransferProcessor + implements BlocksoftBlockchainTypes.TransferProcessor +{ + private _settings: { network: string; currencyCode: string }; + private _provider: XrpTxSendProvider; + private _inited: boolean = false; + + constructor(settings: { network: string; currencyCode: string }) { + this._settings = settings; + } + + needPrivateForFee(): boolean { + return false; + } + + checkSendAllModal(data: { currencyCode: any }): boolean { + return false; + } + + async checkTransferHasError( + data: BlocksoftBlockchainTypes.CheckTransferHasErrorData + ): Promise { + // @ts-ignore + if ( + data.amount && + data.amount * 1 > BlocksoftExternalSettings.getStatic('XRP_MIN') + ) { + return { isOk: true }; + } + /** + * @type {XrpScannerProcessor} + */ + const balanceProvider = BlocksoftDispatcher.getScannerProcessor( + this._settings.currencyCode + ); + const balanceRaw = await balanceProvider.getBalanceBlockchain( + data.addressTo + ); + if ( + balanceRaw && + typeof balanceRaw.balance !== 'undefined' && + balanceRaw.balance > BlocksoftExternalSettings.getStatic('XRP_MIN') + ) { + return { isOk: true }; + } else { + return { isOk: false, code: 'XRP', address: data.addressTo }; + } + } + + async getFeeRate( + data: BlocksoftBlockchainTypes.TransferData, + privateData: BlocksoftBlockchainTypes.TransferPrivateData, + additionalData: {} = {} + ): Promise { + const result: BlocksoftBlockchainTypes.FeeRateResult = { + selectedFeeIndex: -1, + shouldShowFees: false + } as BlocksoftBlockchainTypes.FeeRateResult; + + // @ts-ignore + if (data.amount * 1 <= 0) { + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' XrpTransferProcessor.getFeeRate ' + + data.addressFrom + + ' => ' + + data.addressTo + + ' skipped as zero amount' + ); + return result; + } + + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' XrpTransferProcessor.getFeeRate ' + + data.addressFrom + + ' => ' + + data.addressTo + + ' started amount: ' + + data.amount + ); + + let txJson = false; + try { + if (!this._inited) { + this._provider = new XrpTxSendProvider(); + this._inited = true; + } + txJson = await this._provider.getPrepared(data); + } catch (e) { + if (e.message.indexOf('connect() timed out after') !== -1) { + return false; + } + if (e.message.indexOf('Account not found') !== -1) { + return false; + } + if ( + e.message.indexOf( + 'Destination does not exist. Too little XRP sent to create it' + ) !== -1 + ) { + throw new Error('SERVER_RESPONSE_NOT_ENOUGH_BALANCE_DEST_XRP'); + } + throw e; + } + if (!txJson) { + throw new Error('SERVER_RESPONSE_BAD_INTERNET'); + } + // @ts-ignore + const fee = BlocksoftUtils.toUnified(txJson.Fee, FEE_DECIMALS); + + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' XrpTransferProcessor.getFeeRate ' + + data.addressFrom + + ' => ' + + data.addressTo + + ' finished amount: ' + + data.amount + + ' fee: ' + + fee + ); + result.fees = [ + { + langMsg: 'xrp_speed_one', + feeForTx: fee, + amountForTx: data.amount, + blockchainData: txJson + } + ]; + result.selectedFeeIndex = 0; + return result; + } + + async getTransferAllBalance( + data: BlocksoftBlockchainTypes.TransferData, + privateData: BlocksoftBlockchainTypes.TransferPrivateData, + additionalData: BlocksoftBlockchainTypes.TransferAdditionalData = {} + ): Promise { + const balance = data.amount; + + // @ts-ignore + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' XrpTransferProcessor.getTransferAllBalance ', + data.addressFrom + ' => ' + balance + ); + // noinspection EqualityComparisonWithCoercionJS + if ( + BlocksoftUtils.diff( + balance, + BlocksoftExternalSettings.getStatic('XRP_MIN') + ) <= 0 + ) { + return { + selectedTransferAllBalance: '0', + selectedFeeIndex: -1, + fees: [], + shouldShowFees: false, + countedForBasicBalance: '0' + }; + } + + const result = await this.getFeeRate(data, privateData, additionalData); + // @ts-ignore + if (!result || result.selectedFeeIndex < 0) { + return { + selectedTransferAllBalance: '0', + selectedFeeIndex: -2, + fees: [], + shouldShowFees: false, + countedForBasicBalance: balance + }; } + // @ts-ignore + result.fees[result.selectedFeeIndex].amountForTx = BlocksoftUtils.diff( + result.fees[result.selectedFeeIndex].amountForTx, + BlocksoftExternalSettings.getStatic('XRP_MIN') + ).toString(); + return { + ...result, + shouldShowFees: false, + selectedTransferAllBalance: + result.fees[result.selectedFeeIndex].amountForTx + }; + } + + async sendTx( + data: BlocksoftBlockchainTypes.TransferData, + privateData: BlocksoftBlockchainTypes.TransferPrivateData, + uiData: BlocksoftBlockchainTypes.TransferUiData + ): Promise { + if (typeof privateData.privateKey === 'undefined') { + throw new Error('XRP transaction required privateKey'); + } + if (typeof data.addressTo === 'undefined') { + throw new Error('XRP transaction required addressTo'); + } + + let txJson = false; + try { + if (!this._inited) { + this._provider = new XrpTxSendProvider(); + this._inited = true; + } + txJson = await this._provider.getPrepared(data, false); + } catch (e) { + if (e.message.indexOf('connect() timed out after') !== -1) { + throw new Error('SERVER_RESPONSE_BAD_INTERNET'); + } + if (e.message.indexOf('Account not found') !== -1) { + throw new Error('SERVER_RESPONSE_BAD_INTERNET'); + } + if ( + e.message.indexOf( + 'Destination does not exist. Too little XRP sent to create it' + ) !== -1 + ) { + throw new Error('SERVER_RESPONSE_NOT_ENOUGH_BALANCE_DEST_XRP'); + } + throw e; + } + + // https://xrpl.org/rippleapi-reference.html#preparepayment + // @ts-ignore + BlocksoftCryptoLog.log( + this._settings.currencyCode + ' XrpTransferProcessor.sendTx prepared', + txJson + ); + + // https://xrpl.org/rippleapi-reference.html#sign + if (typeof data.accountJson !== 'object') { + try { + const tmp = JSON.parse(data.accountJson); + data.accountJson = tmp; + } catch (e) { + BlocksoftCryptoLog.err( + this._settings.currencyCode + + ' XrpTransferProcessor.sendTx no accountJson ' + + JSON.stringify(data.accountJson) + ); + } + } + if (typeof data.accountJson.publicKey === 'undefined') { + BlocksoftCryptoLog.err( + this._settings.currencyCode + + ' XrpTransferProcessor.sendTx no publicKey ' + + JSON.stringify(data.accountJson) + ); + throw new Error('SERVER_RESPONSE_BAD_CODE'); + } + + if ( + typeof uiData !== 'undefined' && + typeof uiData.selectedFee !== 'undefined' && + typeof uiData.selectedFee.rawOnly !== 'undefined' && + uiData.selectedFee.rawOnly + ) { + return { + rawOnly: uiData.selectedFee.rawOnly, + raw: this._provider.signTx(data, privateData, txJson) + }; + } + + const result = await this._provider.sendTx(data, privateData, txJson); + + // noinspection ES6MissingAwait + MarketingEvent.logOnlyRealTime( + 'v20_rippled_any_result ' + data.addressFrom + ' => ' + data.addressTo, + { + txJson, + result + } + ); + // @ts-ignore + BlocksoftCryptoLog.log( + this._settings.currencyCode + ' XrpTransferProcessor.sendTx result', + result + ); + + if (result.resultCode === 'tecNO_DST_INSUF_XRP') { + throw new Error(result.resultMessage); // not enough - could be replaced by translated + } else if (result.resultCode === 'tecUNFUNDED_PAYMENT') { + throw new Error('SERVER_RESPONSE_NOT_ENOUGH_BALANCE_XRP'); // not enough to pay + } else if (result.resultCode === 'tecNO_DST_INSUF_XRP') { + throw new Error('SERVER_RESPONSE_NOT_ENOUGH_BALANCE_DEST_XRP'); // not enough to create account + } else if (result.resultCode === 'tefBAD_AUTH') { + throw new Error(result.resultMessage); // not valid key + } else if (result.resultCode === 'tecDST_TAG_NEEDED') { + throw new Error('SERVER_RESPONSE_TAG_NEEDED_XRP'); + } + + if ( + typeof result.tx_json === 'undefined' || + typeof result.tx_json.hash === 'undefined' + ) { + throw new Error(result.resultMessage); // not enough + } + + if (result.resultCode !== 'tesSUCCESS') { + return { + transactionHash: result.tx_json.hash, + successMessage: result.resultMessage + }; // Held until escalated fee drops + } + + return { transactionHash: result.tx_json.hash }; + } } diff --git a/crypto/blockchains/xrp/basic/XrpDataRippleProvider.js b/crypto/blockchains/xrp/basic/XrpDataRippleProvider.js index aa56f18e0..a5f1ab0da 100644 --- a/crypto/blockchains/xrp/basic/XrpDataRippleProvider.js +++ b/crypto/blockchains/xrp/basic/XrpDataRippleProvider.js @@ -1,216 +1,256 @@ -import BlocksoftAxios from '@crypto/common/BlocksoftAxios' -import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog' -import XrpTmpDS from '@crypto/blockchains/xrp/stores/XrpTmpDS' +import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; +import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; +import XrpTmpDS from '@crypto/blockchains/xrp/stores/XrpTmpDS'; -const CACHE_VALID_TIME = 60000 // 1 minute -let CACHE_BLOCK_DATA = {} +const CACHE_VALID_TIME = 60000; // 1 minute +let CACHE_BLOCK_DATA = {}; -const API_PATH = 'https://data.ripple.com/v2' +const API_PATH = 'https://data.ripple.com/v2'; export default class XrpDataRippleProvider { + setCache(tmp) { + CACHE_BLOCK_DATA = tmp; + } - setCache(tmp) { - CACHE_BLOCK_DATA = tmp - } - - async getBalanceBlockchain(address) { - const link = `${API_PATH}/accounts/${address}/balances` - let res = false - let balance = 0 + async getBalanceBlockchain(address) { + const link = `${API_PATH}/accounts/${address}/balances`; + let res = false; + let balance = 0; - try { - res = await BlocksoftAxios.getWithoutBraking(link) - if (res && typeof res.data !== 'undefined' && res.data && typeof res.data.balances !== 'undefined') { - let row - for (row of res.data.balances) { - if (row.currency === 'XRP') { - balance = row.value - break - } - } - } else { - return false - } - } catch (e) { - if (e.message.indexOf('timed out') === -1 && e.message.indexOf('account not found') === -1) { - throw e - } else { - return false - } + try { + res = await BlocksoftAxios.getWithoutBraking(link); + if ( + res && + typeof res.data !== 'undefined' && + res.data && + typeof res.data.balances !== 'undefined' + ) { + let row; + for (row of res.data.balances) { + if (row.currency === 'XRP') { + balance = row.value; + break; + } } - return { balance: balance, unconfirmed: 0, provider: 'ripple.com' } + } else { + return false; + } + } catch (e) { + if ( + e.message.indexOf('timed out') === -1 && + e.message.indexOf('account not found') === -1 + ) { + throw e; + } else { + return false; + } } + return { balance: balance, unconfirmed: 0, provider: 'ripple.com' }; + } - /** - * @param {string} scanData.account.address - * @param {*} scanData.additional - * @param {string} scanData.account.walletHash - * @return {Promise} - */ - async getTransactionsBlockchain(scanData, source = '') { - const address = scanData.account.address.trim() - const action = 'payments' - await BlocksoftCryptoLog.log('XrpScannerProcessor.DataRipple.getTransactions ' + action + ' started ' + address) - const link = `${API_PATH}/accounts/${address}/payments` - let res = false - try { - res = await BlocksoftAxios.getWithoutBraking(link) - } catch (e) { - if (e.message.indexOf('account not found') === -1 - && e.message.indexOf('to retrieve payments') === -1 - && e.message.indexOf('limit exceeded') === -1 - && e.message.indexOf('timed out') === -1 - ) { - throw e - } else { - return false - } - } - - if (!res || typeof res.data === 'undefined' || !res.data) { - return false - } - if (typeof res.data[action] === 'undefined') { - throw new Error('Undefined txs ' + link + ' ' + JSON.stringify(res.data)) - } - if (typeof res.data[action] === 'string') { - throw new Error('Undefined txs ' + link + ' ' + res.data[action]) - } + /** + * @param {string} scanData.account.address + * @param {*} scanData.additional + * @param {string} scanData.account.walletHash + * @return {Promise} + */ + async getTransactionsBlockchain(scanData, source = '') { + const address = scanData.account.address.trim(); + const action = 'payments'; + await BlocksoftCryptoLog.log( + 'XrpScannerProcessor.DataRipple.getTransactions ' + + action + + ' started ' + + address + ); + const link = `${API_PATH}/accounts/${address}/payments`; + let res = false; + try { + res = await BlocksoftAxios.getWithoutBraking(link); + } catch (e) { + if ( + e.message.indexOf('account not found') === -1 && + e.message.indexOf('to retrieve payments') === -1 && + e.message.indexOf('limit exceeded') === -1 && + e.message.indexOf('timed out') === -1 + ) { + throw e; + } else { + return false; + } + } - const transactions = await this._unifyTransactions(address, res.data[action], action) - await BlocksoftCryptoLog.log('XrpScannerProcessor.DataRipple.getTransactions ' + action + ' finished ' + address) - return transactions + if (!res || typeof res.data === 'undefined' || !res.data) { + return false; + } + if (typeof res.data[action] === 'undefined') { + throw new Error('Undefined txs ' + link + ' ' + JSON.stringify(res.data)); + } + if (typeof res.data[action] === 'string') { + throw new Error('Undefined txs ' + link + ' ' + res.data[action]); } - async _unifyTransactions(address, result) { - const transactions = [] - let tx - for (tx of result) { - const transaction = await this._unifyPayment(address, tx) - if (transaction) { - transactions.push(transaction) - } - } - return transactions + const transactions = await this._unifyTransactions( + address, + res.data[action], + action + ); + await BlocksoftCryptoLog.log( + 'XrpScannerProcessor.DataRipple.getTransactions ' + + action + + ' finished ' + + address + ); + return transactions; + } + + async _unifyTransactions(address, result) { + const transactions = []; + let tx; + for (tx of result) { + const transaction = await this._unifyPayment(address, tx); + if (transaction) { + transactions.push(transaction); + } } + return transactions; + } - /** - * @param {string} address - * @param {Object} transaction - * @param {string} transaction.amount '20.001' - * @param {string} transaction.delivered_amount '20.001', - * @param {Object} transaction.destination_balance_changes: [ { counterparty: '', currency: 'XRP', value: '20.001' } ], - * @param {Object} transaction.source_balance_changes: [ { counterparty: '', currency: 'XRP', value: '-20.001' } ], - * @param {string} transaction.tx_index 8 - * @param {string} transaction.currency 'XRP' - * @param {string} transaction.destination 'rL2SpzwrCZ4N2BaPm88pNGGHkPLzejZgB8' - * @param {string} transaction.destination_tag '1' - * @param {string} transaction.executed_time '2019-10-20T22:45:31Z' - * @param {string} transaction.ledger_index 50845930 - * @param {string} transaction.source 'rpJZ5WyotdphojwMLxCr2prhULvG3Voe3X' - * @param {string} transaction.source_currency 'XRP' - * @param {string} transaction.tx_hash '673F28303546CB8A0F45A0D80E6391B7A4A125DB8B72AD0DA635D625C3AD27F1' - * @param {string} transaction.transaction_cost '0.000012' - * @return {UnifiedTransaction} - * @private - **/ - async _unifyPayment(address, transaction) { - let direction, amount + /** + * @param {string} address + * @param {Object} transaction + * @param {string} transaction.amount '20.001' + * @param {string} transaction.delivered_amount '20.001', + * @param {Object} transaction.destination_balance_changes: [ { counterparty: '', currency: 'XRP', value: '20.001' } ], + * @param {Object} transaction.source_balance_changes: [ { counterparty: '', currency: 'XRP', value: '-20.001' } ], + * @param {string} transaction.tx_index 8 + * @param {string} transaction.currency 'XRP' + * @param {string} transaction.destination 'rL2SpzwrCZ4N2BaPm88pNGGHkPLzejZgB8' + * @param {string} transaction.destination_tag '1' + * @param {string} transaction.executed_time '2019-10-20T22:45:31Z' + * @param {string} transaction.ledger_index 50845930 + * @param {string} transaction.source 'rpJZ5WyotdphojwMLxCr2prhULvG3Voe3X' + * @param {string} transaction.source_currency 'XRP' + * @param {string} transaction.tx_hash '673F28303546CB8A0F45A0D80E6391B7A4A125DB8B72AD0DA635D625C3AD27F1' + * @param {string} transaction.transaction_cost '0.000012' + * @return {UnifiedTransaction} + * @private + **/ + async _unifyPayment(address, transaction) { + let direction, amount; - if (transaction.currency === 'XRP') { - if (transaction.source_currency === 'XRP') { - direction = (address === transaction.source) ? 'outcome' : 'income' - } else if (transaction.destination === address) { - direction = 'income' // USDT any => XRP my - } else { - // USDT my => XRP not my - return false // do nothing - } - } else if (transaction.source_currency === 'XRP') { - if (transaction.source === address) { - direction = 'outcome' // XRP my => USDT any - } else { - // XRP not my => USDT my - return false // do nothing - } - } else { - return false // USDT => USDT - } + if (transaction.currency === 'XRP') { + if (transaction.source_currency === 'XRP') { + direction = address === transaction.source ? 'outcome' : 'income'; + } else if (transaction.destination === address) { + direction = 'income'; // USDT any => XRP my + } else { + // USDT my => XRP not my + return false; // do nothing + } + } else if (transaction.source_currency === 'XRP') { + if (transaction.source === address) { + direction = 'outcome'; // XRP my => USDT any + } else { + // XRP not my => USDT my + return false; // do nothing + } + } else { + return false; // USDT => USDT + } - if (direction === 'income') { - amount = transaction.delivered_amount - } else { - amount = transaction.amount - } + if (direction === 'income') { + amount = transaction.delivered_amount; + } else { + amount = transaction.amount; + } - let transactionStatus = 'new' - let ledger = false - if (typeof transaction.ledger_index !== 'undefined' && transaction.ledger_index > 0) { - ledger = await this._getLedger(transaction.ledger_index) - if (ledger && ledger.transactionConfirmations > 5) { - transactionStatus = 'success' - } - } + let transactionStatus = 'new'; + let ledger = false; + if ( + typeof transaction.ledger_index !== 'undefined' && + transaction.ledger_index > 0 + ) { + ledger = await this._getLedger(transaction.ledger_index); + if (ledger && ledger.transactionConfirmations > 5) { + transactionStatus = 'success'; + } + } - if (typeof transaction.executed_time === 'undefined') { - transaction.executed_time = '' - } - const tx = { - transactionHash: transaction.tx_hash, - blockHash: ledger ? ledger.ledger_hash : '', - blockNumber: transaction.ledger_index, - blockTime: transaction.executed_time, - blockConfirmations: ledger ? ledger.transactionConfirmations : 0, - transactionDirection: direction, - addressFrom: transaction.source === address ? '' : transaction.source, - addressTo: transaction.destination === address ? '' : transaction.destination, - addressAmount: amount, - transactionStatus: transactionStatus, - transactionFee: transaction.transaction_cost - } - if (typeof transaction.destination_tag !== 'undefined') { - tx.transactionJson = { memo: transaction.destination_tag } - } - return tx + if (typeof transaction.executed_time === 'undefined') { + transaction.executed_time = ''; + } + const tx = { + transactionHash: transaction.tx_hash, + blockHash: ledger ? ledger.ledger_hash : '', + blockNumber: transaction.ledger_index, + blockTime: transaction.executed_time, + blockConfirmations: ledger ? ledger.transactionConfirmations : 0, + transactionDirection: direction, + addressFrom: transaction.source === address ? '' : transaction.source, + addressTo: + transaction.destination === address ? '' : transaction.destination, + addressAmount: amount, + transactionStatus: transactionStatus, + transactionFee: transaction.transaction_cost + }; + if (typeof transaction.destination_tag !== 'undefined') { + tx.transactionJson = { memo: transaction.destination_tag }; } + return tx; + } - async _getLedger(index) { - const now = new Date().getTime() - await BlocksoftCryptoLog.log('XrpScannerProcessor.DataRipple._getLedger started ' + index) - const link = `${API_PATH}/ledgers/${index}` - let res = false - if (typeof CACHE_BLOCK_DATA[index] === 'undefined' || - ( - now - CACHE_BLOCK_DATA[index].time > CACHE_VALID_TIME - && - CACHE_BLOCK_DATA[index].data.transactionConfirmations < 100 - ) + async _getLedger(index) { + const now = new Date().getTime(); + await BlocksoftCryptoLog.log( + 'XrpScannerProcessor.DataRipple._getLedger started ' + index + ); + const link = `${API_PATH}/ledgers/${index}`; + let res = false; + if ( + typeof CACHE_BLOCK_DATA[index] === 'undefined' || + (now - CACHE_BLOCK_DATA[index].time > CACHE_VALID_TIME && + CACHE_BLOCK_DATA[index].data.transactionConfirmations < 100) + ) { + try { + res = await BlocksoftAxios.getWithoutBraking(link); + if ( + res.data && + typeof res.data !== 'undefined' && + typeof res.data.ledger !== 'undefined' ) { - try { - res = await BlocksoftAxios.getWithoutBraking(link) - if (res.data && typeof res.data !== 'undefined' && typeof res.data.ledger !== 'undefined') { - await BlocksoftCryptoLog.log('XrpScannerProcessor.DataRipple._getLedger updated for index ' + index + ' ' + JSON.stringify(res.data.ledger)) - const ledger = { - close_time: res.data.ledger.close_time, - ledger_hash: res.data.ledger.ledger_hash, - transactionConfirmations : Math.round((now - res.data.ledger.close_time * 1000) / (60 * 1000)) // minutes - } - CACHE_BLOCK_DATA[index] = { - data: ledger, - time: now - } - } - await XrpTmpDS.saveCache(CACHE_BLOCK_DATA) - } catch (e) { - if (e.message.indexOf('timed out') === -1 && e.message.indexOf('account not found') === -1) { - throw e - } else { - res = false - } - } + await BlocksoftCryptoLog.log( + 'XrpScannerProcessor.DataRipple._getLedger updated for index ' + + index + + ' ' + + JSON.stringify(res.data.ledger) + ); + const ledger = { + close_time: res.data.ledger.close_time, + ledger_hash: res.data.ledger.ledger_hash, + transactionConfirmations: Math.round( + (now - res.data.ledger.close_time * 1000) / (60 * 1000) + ) // minutes + }; + CACHE_BLOCK_DATA[index] = { + data: ledger, + time: now + }; } - if (typeof CACHE_BLOCK_DATA[index] === 'undefined') { - return false + await XrpTmpDS.saveCache(CACHE_BLOCK_DATA); + } catch (e) { + if ( + e.message.indexOf('timed out') === -1 && + e.message.indexOf('account not found') === -1 + ) { + throw e; + } else { + res = false; } - return CACHE_BLOCK_DATA[index].data + } + } + if (typeof CACHE_BLOCK_DATA[index] === 'undefined') { + return false; } + return CACHE_BLOCK_DATA[index].data; + } } diff --git a/crypto/blockchains/xrp/basic/XrpDataScanProvider.js b/crypto/blockchains/xrp/basic/XrpDataScanProvider.js index 334201405..518a0b7ad 100644 --- a/crypto/blockchains/xrp/basic/XrpDataScanProvider.js +++ b/crypto/blockchains/xrp/basic/XrpDataScanProvider.js @@ -2,262 +2,327 @@ * @version 0.5 * https://xrpl.org/request-formatting.html */ -import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings' -import BlocksoftAxios from '@crypto/common/BlocksoftAxios' -import BlocksoftUtils from '@crypto/common/BlocksoftUtils' -import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog' -import XrpTmpDS from '@crypto/blockchains/xrp/stores/XrpTmpDS' +import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings'; +import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; +import BlocksoftUtils from '@crypto/common/BlocksoftUtils'; +import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; +import XrpTmpDS from '@crypto/blockchains/xrp/stores/XrpTmpDS'; -const CACHE_VALID_TIME = 60000 // 1 minute -let CACHE_BLOCK_DATA = {} +const CACHE_VALID_TIME = 60000; // 1 minute +let CACHE_BLOCK_DATA = {}; export default class XrpDataScanProvider { + setCache(tmp) { + CACHE_BLOCK_DATA = tmp; + } - setCache(tmp) { - CACHE_BLOCK_DATA = tmp - } - - async getBalanceBlockchain(address) { - const link = BlocksoftExternalSettings.getStatic('XRP_SCANNER_SERVER') - let res = false - let balance = 0 - try { - /** + async getBalanceBlockchain(address) { + const link = BlocksoftExternalSettings.getStatic('XRP_SCANNER_SERVER'); + let res = false; + let balance = 0; + try { + /** curl http://s1.ripple.com:51234/ -X POST -H "Content-Type: application/json" -d '{'method":"account_info","params":[{"account":"rEAgA9B8U8RCkwn6MprHqE1ZfXoeGQxz4P","strict":true,"ledger_index":"validated","api_version':1}]}' curl https://xrplcluster.com/ -X POST -H "Content-Type: application/json" -d '{'method":"account_info","params":[{"account":"rEAgA9B8U8RCkwn6MprHqE1ZfXoeGQxz4P","strict":true,"ledger_index":"validated","api_version':1}]}' */ - const data = { - method: 'account_info', - params: [ - { - account: address, - strict: true, - ledger_index: 'validated', - api_version: 1 - } - ] - } - res = await BlocksoftAxios.postWithoutBraking(link, data) + const data = { + method: 'account_info', + params: [ + { + account: address, + strict: true, + ledger_index: 'validated', + api_version: 1 + } + ] + }; + res = await BlocksoftAxios.postWithoutBraking(link, data); - if (res && typeof res.data !== 'undefined' && res.data && typeof res.data.result !== 'undefined' && res.data.result) { - if (typeof res.data.result.account !== 'undefined' && typeof res.data.result.error_code !== 'undefined' && res.data.result.error_code === 19 ) { - balance = 0 - } else if (typeof res.data.result.account_data !== 'undefined' && typeof res.data.result.account_data.Balance !== 'undefined') { - balance = BlocksoftUtils.toUnified(res.data.result.account_data.Balance, 6) - } - } else { - return false - } - } catch (e) { - if (e.message.indexOf('timed out') === -1 && e.message.indexOf('account not found') === -1) { - if (typeof res.data !== 'undefined' && res.data) { - e.message += ' in ' + JSON.stringify(res.data) - } else { - e.message += ' empty data' - } - throw e - } else { - return false - } + if ( + res && + typeof res.data !== 'undefined' && + res.data && + typeof res.data.result !== 'undefined' && + res.data.result + ) { + if ( + typeof res.data.result.account !== 'undefined' && + typeof res.data.result.error_code !== 'undefined' && + res.data.result.error_code === 19 + ) { + balance = 0; + } else if ( + typeof res.data.result.account_data !== 'undefined' && + typeof res.data.result.account_data.Balance !== 'undefined' + ) { + balance = BlocksoftUtils.toUnified( + res.data.result.account_data.Balance, + 6 + ); } - return { balance: balance, unconfirmed: 0, provider: link } + } else { + return false; + } + } catch (e) { + if ( + e.message.indexOf('timed out') === -1 && + e.message.indexOf('account not found') === -1 + ) { + if (typeof res.data !== 'undefined' && res.data) { + e.message += ' in ' + JSON.stringify(res.data); + } else { + e.message += ' empty data'; + } + throw e; + } else { + return false; + } } + return { balance: balance, unconfirmed: 0, provider: link }; + } - async getTransactionsBlockchain(scanData, source = '') { - const address = scanData.account.address.trim() - const link = BlocksoftExternalSettings.getStatic('XRP_SCANNER_SERVER') - let transactions = [] - let res = false - try { - // https://xrpl.org/account_tx.html - const data = { - method: 'account_tx', - params: [ - { - account: address, - binary: false, - forward: false, - ledger_index_max: -1, - ledger_index_min: -1, - limit: 100 - } - ] - } - res = await BlocksoftAxios.postWithoutBraking(link, data) + async getTransactionsBlockchain(scanData, source = '') { + const address = scanData.account.address.trim(); + const link = BlocksoftExternalSettings.getStatic('XRP_SCANNER_SERVER'); + let transactions = []; + let res = false; + try { + // https://xrpl.org/account_tx.html + const data = { + method: 'account_tx', + params: [ + { + account: address, + binary: false, + forward: false, + ledger_index_max: -1, + ledger_index_min: -1, + limit: 100 + } + ] + }; + res = await BlocksoftAxios.postWithoutBraking(link, data); - if (res && typeof res.data !== 'undefined' && res.data - && typeof res.data.result !== 'undefined' && res.data.result - && typeof res.data.result.transactions !== 'undefined' && res.data.result.transactions - ) { - transactions = await this._unifyTransactions(address, res.data.result.transactions, res.data.result.ledger_index_max) - } else { - return false - } - } catch (e) { - if (e.message.indexOf('timed out') === -1 && e.message.indexOf('account not found') === -1) { - throw e - } else { - return false - } - } - return transactions + if ( + res && + typeof res.data !== 'undefined' && + res.data && + typeof res.data.result !== 'undefined' && + res.data.result && + typeof res.data.result.transactions !== 'undefined' && + res.data.result.transactions + ) { + transactions = await this._unifyTransactions( + address, + res.data.result.transactions, + res.data.result.ledger_index_max + ); + } else { + return false; + } + } catch (e) { + if ( + e.message.indexOf('timed out') === -1 && + e.message.indexOf('account not found') === -1 + ) { + throw e; + } else { + return false; + } } + return transactions; + } - async _unifyTransactions(address, result, lastBlock) { - const transactions = [] - let tx - for (tx of result) { - const transaction = await this._unifyPayment(address, tx, lastBlock) - if (transaction) { - transactions.push(transaction) - } - } - return transactions + async _unifyTransactions(address, result, lastBlock) { + const transactions = []; + let tx; + for (tx of result) { + const transaction = await this._unifyPayment(address, tx, lastBlock); + if (transaction) { + transactions.push(transaction); + } } + return transactions; + } - /** - * @param {string} address - * @param {Object} transaction - * @param {bool} transaction.validated - * @param {string} transaction.tx.Account 'rEAgA9B8U8RCkwn6MprHqE1ZfXoeGQxz4P' - * @param {string} transaction.tx.Amount '2000000' - * @param {string} transaction.tx.Destination 'rDh2XemJY5WSNCPgXjhqnJt1PLGsTKbnix' - * @param {string} transaction.tx.DestinationTag - * @param {string} transaction.tx.Fee 127091 - * @param {string} transaction.tx.LastLedgerSequence 68101269 - * @param {string} transaction.tx.TransactionType 'Payment' - * @param {string} transaction.tx.date 691857661 - * @param {string} transaction.tx.hash '4D08316F83148C7C0EC955301E770A196B708EAF874BA2339260317BFDCE89E6' - * @param {string} transaction.tx.inLedger 68101268 - * @param {string} transaction.tx.ledger_index 68101268 - * @param {string} transaction.meta.delivered_amount '2000000' - * @param {string} transaction.meta.TransactionResult 'tesSUCCESS' - * @return {UnifiedTransaction} - * @private - **/ - async _unifyPayment(address, transaction, lastBlock = 0) { - if (transaction.tx.TransactionType !== 'Payment') { - return false - } - let direction, amount - if (transaction.tx.Account === address) { - direction = 'outcome' - } else { - direction = 'income' - } + /** + * @param {string} address + * @param {Object} transaction + * @param {bool} transaction.validated + * @param {string} transaction.tx.Account 'rEAgA9B8U8RCkwn6MprHqE1ZfXoeGQxz4P' + * @param {string} transaction.tx.Amount '2000000' + * @param {string} transaction.tx.Destination 'rDh2XemJY5WSNCPgXjhqnJt1PLGsTKbnix' + * @param {string} transaction.tx.DestinationTag + * @param {string} transaction.tx.Fee 127091 + * @param {string} transaction.tx.LastLedgerSequence 68101269 + * @param {string} transaction.tx.TransactionType 'Payment' + * @param {string} transaction.tx.date 691857661 + * @param {string} transaction.tx.hash '4D08316F83148C7C0EC955301E770A196B708EAF874BA2339260317BFDCE89E6' + * @param {string} transaction.tx.inLedger 68101268 + * @param {string} transaction.tx.ledger_index 68101268 + * @param {string} transaction.meta.delivered_amount '2000000' + * @param {string} transaction.meta.TransactionResult 'tesSUCCESS' + * @return {UnifiedTransaction} + * @private + **/ + async _unifyPayment(address, transaction, lastBlock = 0) { + if (transaction.tx.TransactionType !== 'Payment') { + return false; + } + let direction, amount; + if (transaction.tx.Account === address) { + direction = 'outcome'; + } else { + direction = 'income'; + } - amount = transaction.tx.Amount - if (direction === 'income' && typeof transaction.meta.delivered_amount !== 'undefined') { - amount = transaction.meta.delivered_amount - } + amount = transaction.tx.Amount; + if ( + direction === 'income' && + typeof transaction.meta.delivered_amount !== 'undefined' + ) { + amount = transaction.meta.delivered_amount; + } - let blockConfirmations = lastBlock - transaction.tx.ledger_index - if (blockConfirmations <= 0) blockConfirmations = 0 - let transactionStatus = 'new' - if (transaction.validated === true || transaction.meta.TransactionResult === 'tesSUCCESS') { - if (blockConfirmations > 5) { - transactionStatus = 'success' - } - } - const ledger = await this._getLedger(transaction.tx.ledger_index) - const blockTime = ledger && typeof ledger.close_time !== 'undefined' && ledger.close_time ? ledger.close_time : transaction.tx.date - const blockHash = ledger && typeof ledger.ledger_hash !== 'undefined' && ledger.ledger_hash ? ledger.ledger_hash : transaction.tx.ledger_index - const tx = { - transactionHash: transaction.tx.hash, - blockHash, - blockNumber: transaction.tx.ledger_index, - blockTime, - blockConfirmations, - transactionDirection: direction, - addressFrom: transaction.tx.Account === address ? '' : transaction.tx.Account, - addressTo: transaction.tx.Destination === address ? '' : transaction.tx.Destination, - addressAmount: BlocksoftUtils.toUnified(amount, 6), - transactionStatus: transactionStatus, - transactionFee: BlocksoftUtils.toUnified(transaction.tx.Fee, 6) - } - // https://blockchair.com/ripple/transaction/F56C6B0CA7BB6CD9AC74843E6C7BA605C7FFBB1F409E356CA235423F30F55F51?from=trustee - if (typeof transaction.tx.DestinationTag !== 'undefined') { - tx.transactionJson = { memo: transaction.tx.DestinationTag } - } - return tx + let blockConfirmations = lastBlock - transaction.tx.ledger_index; + if (blockConfirmations <= 0) blockConfirmations = 0; + let transactionStatus = 'new'; + if ( + transaction.validated === true || + transaction.meta.TransactionResult === 'tesSUCCESS' + ) { + if (blockConfirmations > 5) { + transactionStatus = 'success'; + } } + const ledger = await this._getLedger(transaction.tx.ledger_index); + const blockTime = + ledger && typeof ledger.close_time !== 'undefined' && ledger.close_time + ? ledger.close_time + : transaction.tx.date; + const blockHash = + ledger && typeof ledger.ledger_hash !== 'undefined' && ledger.ledger_hash + ? ledger.ledger_hash + : transaction.tx.ledger_index; + const tx = { + transactionHash: transaction.tx.hash, + blockHash, + blockNumber: transaction.tx.ledger_index, + blockTime, + blockConfirmations, + transactionDirection: direction, + addressFrom: + transaction.tx.Account === address ? '' : transaction.tx.Account, + addressTo: + transaction.tx.Destination === address + ? '' + : transaction.tx.Destination, + addressAmount: BlocksoftUtils.toUnified(amount, 6), + transactionStatus: transactionStatus, + transactionFee: BlocksoftUtils.toUnified(transaction.tx.Fee, 6) + }; + // https://blockchair.com/ripple/transaction/F56C6B0CA7BB6CD9AC74843E6C7BA605C7FFBB1F409E356CA235423F30F55F51?from=trustee + if (typeof transaction.tx.DestinationTag !== 'undefined') { + tx.transactionJson = { memo: transaction.tx.DestinationTag }; + } + return tx; + } - async _getLedger(index) { - const now = new Date().getTime() - await BlocksoftCryptoLog.log('XrpScannerProcessor.DataScan._getLedger started ' + index) - const link = BlocksoftExternalSettings.getStatic('XRP_SCANNER_SERVER') - let res = false - if (typeof CACHE_BLOCK_DATA[index] === 'undefined' || - ( - now - CACHE_BLOCK_DATA[index].time > CACHE_VALID_TIME - && - CACHE_BLOCK_DATA[index].data.transactionConfirmations < 100 - ) - ) { - try { - const data = { - method: 'ledger_data', - params: [ - { - binary: false, - ledger_index: index - } - ] - } - res = await BlocksoftAxios.postWithoutBraking(link, data) - if (res.data && typeof res.data !== 'undefined' && typeof res.data.result !== 'undefined' && typeof res.data.result.ledger !== 'undefined') { - await BlocksoftCryptoLog.log('XrpScannerProcessor.DataScan._getLedger updated for index ' + index + ' ' + JSON.stringify(res.data.result.ledger)) - const date = this._getDate(res.data.result.ledger.close_time_human) - const ledger = { - close_time: date, - ledger_hash: res.data.result.ledger.ledger_hash, - transactionConfirmations: Math.round((now - date * 1000) / (60 * 1000)) // minutes - } - CACHE_BLOCK_DATA[index] = { - data: ledger, - time: now - } - } - await XrpTmpDS.saveCache(CACHE_BLOCK_DATA) - } catch (e) { - if (e.message.indexOf('timed out') === -1 && e.message.indexOf('account not found') === -1) { - throw e - } else { - res = false - } + async _getLedger(index) { + const now = new Date().getTime(); + await BlocksoftCryptoLog.log( + 'XrpScannerProcessor.DataScan._getLedger started ' + index + ); + const link = BlocksoftExternalSettings.getStatic('XRP_SCANNER_SERVER'); + let res = false; + if ( + typeof CACHE_BLOCK_DATA[index] === 'undefined' || + (now - CACHE_BLOCK_DATA[index].time > CACHE_VALID_TIME && + CACHE_BLOCK_DATA[index].data.transactionConfirmations < 100) + ) { + try { + const data = { + method: 'ledger_data', + params: [ + { + binary: false, + ledger_index: index } + ] + }; + res = await BlocksoftAxios.postWithoutBraking(link, data); + if ( + res.data && + typeof res.data !== 'undefined' && + typeof res.data.result !== 'undefined' && + typeof res.data.result.ledger !== 'undefined' + ) { + await BlocksoftCryptoLog.log( + 'XrpScannerProcessor.DataScan._getLedger updated for index ' + + index + + ' ' + + JSON.stringify(res.data.result.ledger) + ); + const date = this._getDate(res.data.result.ledger.close_time_human); + const ledger = { + close_time: date, + ledger_hash: res.data.result.ledger.ledger_hash, + transactionConfirmations: Math.round( + (now - date * 1000) / (60 * 1000) + ) // minutes + }; + CACHE_BLOCK_DATA[index] = { + data: ledger, + time: now + }; } - if (typeof CACHE_BLOCK_DATA[index] === 'undefined') { - return false + await XrpTmpDS.saveCache(CACHE_BLOCK_DATA); + } catch (e) { + if ( + e.message.indexOf('timed out') === -1 && + e.message.indexOf('account not found') === -1 + ) { + throw e; + } else { + res = false; } - return CACHE_BLOCK_DATA[index].data - + } } - - - // 2021-Dec-03 14:41:01.00 - // const tmp = new Date(time) not working in emulator - _getDate(time) { - time = time.split('.')[0] - const months = { - 'Jan': 0, - 'Feb': 1, - 'Mar': 2, - 'Apr': 3, - 'May': 4, - 'Jun': 5, - 'Jul': 6, - 'Aug': 7, - 'Sep': 8, - 'Oct': 9, - 'Nov': 10, - 'Dec': 11 - } - const tmp0 = time.split(' ') - const tmp1 = tmp0[0].split('-') - const tmp2 = tmp0[1].split(':') - const tmp = new Date(tmp1[0], months[tmp1[1]], tmp1[2], tmp2[0], tmp2[1], tmp2[2]) - return tmp.getTime() - + if (typeof CACHE_BLOCK_DATA[index] === 'undefined') { + return false; } + return CACHE_BLOCK_DATA[index].data; + } + + // 2021-Dec-03 14:41:01.00 + // const tmp = new Date(time) not working in emulator + _getDate(time) { + time = time.split('.')[0]; + const months = { + Jan: 0, + Feb: 1, + Mar: 2, + Apr: 3, + May: 4, + Jun: 5, + Jul: 6, + Aug: 7, + Sep: 8, + Oct: 9, + Nov: 10, + Dec: 11 + }; + const tmp0 = time.split(' '); + const tmp1 = tmp0[0].split('-'); + const tmp2 = tmp0[1].split(':'); + const tmp = new Date( + tmp1[0], + months[tmp1[1]], + tmp1[2], + tmp2[0], + tmp2[1], + tmp2[2] + ); + return tmp.getTime(); + } } diff --git a/crypto/blockchains/xrp/basic/XrpTxSendProvider.ts b/crypto/blockchains/xrp/basic/XrpTxSendProvider.ts index 23127d7c8..17d3c413f 100644 --- a/crypto/blockchains/xrp/basic/XrpTxSendProvider.ts +++ b/crypto/blockchains/xrp/basic/XrpTxSendProvider.ts @@ -6,178 +6,258 @@ * https://xrpl.org/rippleapi-reference.html#sign * https://xrpl.org/rippleapi-reference.html#submit */ -import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog' -import BlocksoftExternalSettings from '../../../common/BlocksoftExternalSettings' -import { BlocksoftBlockchainTypes } from '../../BlocksoftBlockchainTypes' +import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; +import BlocksoftExternalSettings from '../../../common/BlocksoftExternalSettings'; +import { BlocksoftBlockchainTypes } from '../../BlocksoftBlockchainTypes'; -import { XrpTxUtils } from './XrpTxUtils' +import { XrpTxUtils } from './XrpTxUtils'; -import MarketingEvent from '../../../../app/services/Marketing/MarketingEvent' -import config from '../../../../app/config/config' +import MarketingEvent from '../../../../app/services/Marketing/MarketingEvent'; +import config from '../../../../app/config/config'; -const RippleAPI = require('ripple-lib').RippleAPI +const RippleAPI = require('ripple-lib').RippleAPI; export class XrpTxSendProvider { + private readonly _api: typeof RippleAPI; - private readonly _api: typeof RippleAPI + constructor() { + this._api = new RippleAPI({ + server: BlocksoftExternalSettings.getStatic('XRP_SERVER') + }); // Public rippled server + this._api.on('error', (errorCode: string, errorMessage: string) => { + BlocksoftCryptoLog.log( + 'XrpTransferProcessor constructor' + errorCode + ': ' + errorMessage + ); + }); + this._api.on('connected', () => {}); + this._api.on('disconnected', () => {}); + } - constructor() { - this._api = new RippleAPI({ server: BlocksoftExternalSettings.getStatic('XRP_SERVER') }) // Public rippled server - this._api.on('error', (errorCode: string, errorMessage: string) => { - BlocksoftCryptoLog.log('XrpTransferProcessor constructor' + errorCode + ': ' + errorMessage) - }) - this._api.on('connected', () => { - }) - this._api.on('disconnected', () => { + async getPrepared( + data: BlocksoftBlockchainTypes.TransferData, + toObject = true + ) { + const payment = { + source: { + address: data.addressFrom, + maxAmount: { + value: XrpTxUtils.amountPrep(data.amount), + currency: 'XRP' + } + }, + destination: { + address: data.addressTo, + amount: { + value: XrpTxUtils.amountPrep(data.amount), + currency: 'XRP' + } + } + }; - }) + if (data.addressFrom === data.addressTo) { + throw new Error('SERVER_RESPONSE_SELF_TX_FORBIDDEN'); } - async getPrepared(data: BlocksoftBlockchainTypes.TransferData, toObject = true) { - - const payment = { - 'source': { - 'address': data.addressFrom, - 'maxAmount': { - 'value': XrpTxUtils.amountPrep(data.amount), - 'currency': 'XRP' - } - }, - 'destination': { - 'address': data.addressTo, - 'amount': { - 'value': XrpTxUtils.amountPrep(data.amount), - 'currency': 'XRP' - } - } - } - - if (data.addressFrom === data.addressTo) { - throw new Error('SERVER_RESPONSE_SELF_TX_FORBIDDEN') + // https://xrpl.org/rippleapi-reference.html#payment + try { + if ( + typeof data.memo !== 'undefined' && + data.memo && + data.memo.toString().trim().length > 0 + ) { + // @ts-ignore + const int = data.memo.toString().trim() * 1; + if (int.toString() !== data.memo) { + throw new Error('Destination tag type validation error'); } - - // https://xrpl.org/rippleapi-reference.html#payment - try { - if (typeof data.memo !== 'undefined' && data.memo && data.memo.toString().trim().length > 0) { - // @ts-ignore - const int = data.memo.toString().trim() * 1 - if (int.toString() !== data.memo) { - throw new Error('Destination tag type validation error') - } - if (int > 4294967295) { - throw new Error('Destination tag couldnt be more then 4294967295') - } - // @ts-ignore - payment.destination.tag = int - } - } catch (e) { - // @ts-ignore - BlocksoftCryptoLog.log('XrpTransferProcessor._getPrepared memo error ' + e.message, data) + if (int > 4294967295) { + throw new Error('Destination tag couldnt be more then 4294967295'); } // @ts-ignore - BlocksoftCryptoLog.log('XrpTransferProcessor._getPrepared payment', payment) + payment.destination.tag = int; + } + } catch (e) { + // @ts-ignore + BlocksoftCryptoLog.log( + 'XrpTransferProcessor._getPrepared memo error ' + e.message, + data + ); + } + // @ts-ignore + BlocksoftCryptoLog.log( + 'XrpTransferProcessor._getPrepared payment', + payment + ); - const api = this._api + const api = this._api; - return new Promise((resolve, reject) => { - api.connect().then(() => { - api.preparePayment(data.addressFrom, payment).then((prepared: { txJSON: any }) => { - // https://xrpl.org/rippleapi-reference.html#preparepayment - if (typeof prepared.txJSON === 'undefined') { - reject(new Error('No txJSON inside ripple response ' + JSON.stringify(prepared))) - } - const txJson = prepared.txJSON - BlocksoftCryptoLog.log('XrpTxSendProvider._getPrepared prepared', txJson) - resolve(toObject ? JSON.parse(txJson) : txJson) - }).catch((error: { toString: () => string }) => { - MarketingEvent.logOnlyRealTime('v20_rippled_prepare_error ' + data.addressFrom + ' => ' + data.addressTo, { - payment, - msg: error.toString() - }) - BlocksoftCryptoLog.log('XrpTxSendProvider._getPrepared error ' + error.toString()) - reject(error) - }) - }).catch((error: { toString: () => string }) => { - MarketingEvent.logOnlyRealTime('v20_rippled_prepare_no_connection ' + data.addressFrom + ' => ' + data.addressTo, { - payment, - msg: error.toString() - }) - BlocksoftCryptoLog.log('XrpTxSendProvider._getPrepared connect error ' + error.toString()) - reject(error) + return new Promise((resolve, reject) => { + api + .connect() + .then(() => { + api + .preparePayment(data.addressFrom, payment) + .then((prepared: { txJSON: any }) => { + // https://xrpl.org/rippleapi-reference.html#preparepayment + if (typeof prepared.txJSON === 'undefined') { + reject( + new Error( + 'No txJSON inside ripple response ' + + JSON.stringify(prepared) + ) + ); + } + const txJson = prepared.txJSON; + BlocksoftCryptoLog.log( + 'XrpTxSendProvider._getPrepared prepared', + txJson + ); + resolve(toObject ? JSON.parse(txJson) : txJson); }) + .catch((error: { toString: () => string }) => { + MarketingEvent.logOnlyRealTime( + 'v20_rippled_prepare_error ' + + data.addressFrom + + ' => ' + + data.addressTo, + { + payment, + msg: error.toString() + } + ); + BlocksoftCryptoLog.log( + 'XrpTxSendProvider._getPrepared error ' + error.toString() + ); + reject(error); + }); }) - } + .catch((error: { toString: () => string }) => { + MarketingEvent.logOnlyRealTime( + 'v20_rippled_prepare_no_connection ' + + data.addressFrom + + ' => ' + + data.addressTo, + { + payment, + msg: error.toString() + } + ); + BlocksoftCryptoLog.log( + 'XrpTxSendProvider._getPrepared connect error ' + error.toString() + ); + reject(error); + }); + }); + } - signTx(data: BlocksoftBlockchainTypes.TransferData, privateData: BlocksoftBlockchainTypes.TransferPrivateData, txJson: any): Promise { - const api = this._api - const keypair = { - privateKey: privateData.privateKey, - publicKey: data.accountJson.publicKey.toUpperCase() - } - const signed = api.sign(txJson, keypair) - return signed.signedTransaction - } + signTx( + data: BlocksoftBlockchainTypes.TransferData, + privateData: BlocksoftBlockchainTypes.TransferPrivateData, + txJson: any + ): Promise { + const api = this._api; + const keypair = { + privateKey: privateData.privateKey, + publicKey: data.accountJson.publicKey.toUpperCase() + }; + const signed = api.sign(txJson, keypair); + return signed.signedTransaction; + } - async sendTx(data: BlocksoftBlockchainTypes.TransferData, privateData: BlocksoftBlockchainTypes.TransferPrivateData, txJson: any): Promise<{ - resultCode: string, - resultMessage: string, - // eslint-disable-next-line camelcase - tx_json?: { - hash: string - } - }> { - const api = this._api - let result - try { - const signed = this.signTx(data, privateData, txJson) - BlocksoftCryptoLog.log('XrpTransferProcessor.sendTx signed', signed) - result = await new Promise((resolve, reject) => { - api.connect().then(() => { - // https://xrpl.org/rippleapi-reference.html#submit - api.submit(signed).then((result: { - resultCode: '', - resultMessage: '' - }) => { - MarketingEvent.logOnlyRealTime('v20_rippled_success ' + data.addressFrom + ' => ' + data.addressTo, { - txJson, - result - }) - resolve(result) - }).catch((error: { toString: () => string }) => { - MarketingEvent.logOnlyRealTime('v20_rippled_send_error ' + data.addressFrom + ' => ' + data.addressTo, { - txJson, - msg: error.toString() - }) - BlocksoftCryptoLog.log('XrpTransferProcessor.submit error ' + error.toString()) - reject(error) - }) - }).catch((error: { toString: () => string }) => { - MarketingEvent.logOnlyRealTime('v20_rippled_send_no_connection ' + data.addressFrom + ' => ' + data.addressTo, { - txJson, - msg: error.toString() - }) - BlocksoftCryptoLog.log('XrpTransferProcessor.sendTx connect error ' + error.toString()) - reject(error) - }) - }) - } catch (e) { - if (config.debug.cryptoErrors) { - console.log('XrpTransferProcessor.sendTx error ', e) - } - MarketingEvent.logOnlyRealTime('v20_rippled_send2_error ' + data.addressFrom + ' => ' + data.addressTo, { + async sendTx( + data: BlocksoftBlockchainTypes.TransferData, + privateData: BlocksoftBlockchainTypes.TransferPrivateData, + txJson: any + ): Promise<{ + resultCode: string; + resultMessage: string; + // eslint-disable-next-line camelcase + tx_json?: { + hash: string; + }; + }> { + const api = this._api; + let result; + try { + const signed = this.signTx(data, privateData, txJson); + BlocksoftCryptoLog.log('XrpTransferProcessor.sendTx signed', signed); + result = await new Promise((resolve, reject) => { + api + .connect() + .then(() => { + // https://xrpl.org/rippleapi-reference.html#submit + api + .submit(signed) + .then((result: { resultCode: ''; resultMessage: '' }) => { + MarketingEvent.logOnlyRealTime( + 'v20_rippled_success ' + + data.addressFrom + + ' => ' + + data.addressTo, + { + txJson, + result + } + ); + resolve(result); + }) + .catch((error: { toString: () => string }) => { + MarketingEvent.logOnlyRealTime( + 'v20_rippled_send_error ' + + data.addressFrom + + ' => ' + + data.addressTo, + { + txJson, + msg: error.toString() + } + ); + BlocksoftCryptoLog.log( + 'XrpTransferProcessor.submit error ' + error.toString() + ); + reject(error); + }); + }) + .catch((error: { toString: () => string }) => { + MarketingEvent.logOnlyRealTime( + 'v20_rippled_send_no_connection ' + + data.addressFrom + + ' => ' + + data.addressTo, + { txJson, - msg: e.toString() - }) - BlocksoftCryptoLog.log('XrpTransferProcessor.send2 error ' + e.toString()) - if (typeof e.resultMessage !== 'undefined') { - throw new Error(e.resultMessage.toString()) - } else if (typeof e.message !== 'undefined') { - throw new Error(e.message.toString()) - } else { - throw new Error(e.toString()) - } + msg: error.toString() + } + ); + BlocksoftCryptoLog.log( + 'XrpTransferProcessor.sendTx connect error ' + error.toString() + ); + reject(error); + }); + }); + } catch (e) { + if (config.debug.cryptoErrors) { + console.log('XrpTransferProcessor.sendTx error ', e); + } + MarketingEvent.logOnlyRealTime( + 'v20_rippled_send2_error ' + data.addressFrom + ' => ' + data.addressTo, + { + txJson, + msg: e.toString() } - // @ts-ignore - return result + ); + BlocksoftCryptoLog.log( + 'XrpTransferProcessor.send2 error ' + e.toString() + ); + if (typeof e.resultMessage !== 'undefined') { + throw new Error(e.resultMessage.toString()); + } else if (typeof e.message !== 'undefined') { + throw new Error(e.message.toString()); + } else { + throw new Error(e.toString()); + } } + // @ts-ignore + return result; + } } diff --git a/crypto/blockchains/xvg/XvgScannerProcessor.js b/crypto/blockchains/xvg/XvgScannerProcessor.js index 50f8457b4..ecc5f1475 100644 --- a/crypto/blockchains/xvg/XvgScannerProcessor.js +++ b/crypto/blockchains/xvg/XvgScannerProcessor.js @@ -3,239 +3,282 @@ * https://github.com/bitpay/bitcore/blob/master/packages/bitcore-node/docs/api-documentation.md * https://api.vergecurrency.network/node/api/XVG/mainnet/address/DL5LtSf7wztH45VuYunL8oaQHtJbKLCHyw/balance */ -import BlocksoftAxios from '../../common/BlocksoftAxios' -import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog' +import BlocksoftAxios from '../../common/BlocksoftAxios'; +import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog'; -import XvgTmpDS from './stores/XvgTmpDS' -import XvgFindAddressFunction from './basic/XvgFindAddressFunction' +import XvgTmpDS from './stores/XvgTmpDS'; +import XvgFindAddressFunction from './basic/XvgFindAddressFunction'; -const API_PATH = 'https://api.vergecurrency.network/node/api/XVG/mainnet' -const CACHE_VALID_TIME = 30000 // 30 seconds -const CACHE = {} -let CACHE_FROM_DB = {} +const API_PATH = 'https://api.vergecurrency.network/node/api/XVG/mainnet'; +const CACHE_VALID_TIME = 30000; // 30 seconds +const CACHE = {}; +let CACHE_FROM_DB = {}; export default class XvgScannerProcessor { - /** - * @type {number} - * @private - */ - _blocksToConfirm = 20 - - /** - * @param {string} address - * @return {Promise<{balance:*, unconfirmed:*, provider:string}>} - */ - async getBalanceBlockchain(address) { - const link = `${API_PATH}/address/${address}/balance` - const res = await BlocksoftAxios.getWithoutBraking(link) - if (!res || !res.data) { - return false - } - if (typeof res.data.confirmed === 'undefined') { - throw new Error('XvgScannerProcessor.getBalance nothing loaded for address ' + link) - } - const balance = res.data.confirmed - return { balance, unconfirmed: 0, provider: 'api.vergecurrency' } - } - - /** - * @param {string} scanData.account.address - * @param {*} scanData.additional - * @param {string} scanData.account.walletHash - * @return {Promise} - */ - async getTransactionsBlockchain(scanData, source = '') { - const address = scanData.account.address.trim() - BlocksoftCryptoLog.log('XvgScannerProcessor.getTransactions started ' + address) - const link = `${API_PATH}/address/${address}/txs` - BlocksoftCryptoLog.log('XvgScannerProcessor.getTransactions call ' + link) - let tmp = await BlocksoftAxios.get(link) - if (tmp.status < 200 || tmp.status >= 300) { - throw new Error('not valid server response status ' + link) - } + /** + * @type {number} + * @private + */ + _blocksToConfirm = 20; - if (typeof tmp.data === 'undefined' || !tmp.data) { - throw new Error('Undefined txs ' + link + ' ' + JSON.stringify(tmp.data)) - } + /** + * @param {string} address + * @return {Promise<{balance:*, unconfirmed:*, provider:string}>} + */ + async getBalanceBlockchain(address) { + const link = `${API_PATH}/address/${address}/balance`; + const res = await BlocksoftAxios.getWithoutBraking(link); + if (!res || !res.data) { + return false; + } + if (typeof res.data.confirmed === 'undefined') { + throw new Error( + 'XvgScannerProcessor.getBalance nothing loaded for address ' + link + ); + } + const balance = res.data.confirmed; + return { balance, unconfirmed: 0, provider: 'api.vergecurrency' }; + } - tmp = tmp.data - if (tmp.data) { - tmp = tmp.data // wtf but ok to support old wallets - } + /** + * @param {string} scanData.account.address + * @param {*} scanData.additional + * @param {string} scanData.account.walletHash + * @return {Promise} + */ + async getTransactionsBlockchain(scanData, source = '') { + const address = scanData.account.address.trim(); + BlocksoftCryptoLog.log( + 'XvgScannerProcessor.getTransactions started ' + address + ); + const link = `${API_PATH}/address/${address}/txs`; + BlocksoftCryptoLog.log('XvgScannerProcessor.getTransactions call ' + link); + let tmp = await BlocksoftAxios.get(link); + if (tmp.status < 200 || tmp.status >= 300) { + throw new Error('not valid server response status ' + link); + } - const transactions = [] - const already = {} - CACHE_FROM_DB = await XvgTmpDS.getCache(address) - - let tx - for (tx of tmp) { // ASC order is important - const tmp2 = await this._unifyTransactionStep1(address, tx, already) - if (tmp2) { - if (tmp2.outcoming) { - if (typeof CACHE_FROM_DB[tmp2.outcoming.transactionHash + '_data'] === 'undefined') { - tmp2.outcoming = await this._unifyTransactionStep2(address, tmp2.outcoming) - if (tmp2.outcoming) { - already[tmp2.outcoming.transactionHash] = 1 - if (tmp2.outcoming.addressTo === '?') { - tmp2.outcoming.addressTo = 'self' - BlocksoftCryptoLog.log('XvgScannerProcessor.getTransactions consider as self ' + tmp2.outcoming.transactionHash) - } - transactions.push(tmp2.outcoming) - } - } else { - already[tmp2.outcoming.transactionHash] = 1 - } - } - if (tmp2.incoming) { - if (typeof CACHE_FROM_DB[tmp2.incoming.transactionHash + '_data'] === 'undefined') { - tmp2.incoming = await this._unifyTransactionStep2(address, tmp2.incoming) - if (tmp2.incoming) { - already[tmp2.incoming.transactionHash] = 1 - transactions.push(tmp2.incoming) - } - } else { - already[tmp2.incoming.transactionHash] = 1 - } - } - } - } - BlocksoftCryptoLog.log('XvgScannerProcessor.getTransactions finished ' + address + ' total: ' + transactions.length) - return transactions + if (typeof tmp.data === 'undefined' || !tmp.data) { + throw new Error('Undefined txs ' + link + ' ' + JSON.stringify(tmp.data)); + } + tmp = tmp.data; + if (tmp.data) { + tmp = tmp.data; // wtf but ok to support old wallets } - /** - * https://api.vergecurrency.network/node/api/XVG/mainnet/tx/abcda88bdb3968c5e444694ce3914cdec34f3afab73627bf201d34493d5e3aae/coins - * @param address - * @param transaction - * @returns {Promise} - * @private - */ - async _unifyTransactionStep2(address, transaction) { - if (!transaction) return false - - if (typeof CACHE[transaction.transactionHash] !== 'undefined') { - if (CACHE[transaction.transactionHash].data.blockConfirmations > 100) { - return CACHE[transaction.transactionHash].data + const transactions = []; + const already = {}; + CACHE_FROM_DB = await XvgTmpDS.getCache(address); + + let tx; + for (tx of tmp) { + // ASC order is important + const tmp2 = await this._unifyTransactionStep1(address, tx, already); + if (tmp2) { + if (tmp2.outcoming) { + if ( + typeof CACHE_FROM_DB[tmp2.outcoming.transactionHash + '_data'] === + 'undefined' + ) { + tmp2.outcoming = await this._unifyTransactionStep2( + address, + tmp2.outcoming + ); + if (tmp2.outcoming) { + already[tmp2.outcoming.transactionHash] = 1; + if (tmp2.outcoming.addressTo === '?') { + tmp2.outcoming.addressTo = 'self'; + BlocksoftCryptoLog.log( + 'XvgScannerProcessor.getTransactions consider as self ' + + tmp2.outcoming.transactionHash + ); + } + transactions.push(tmp2.outcoming); } - const now = new Date().getTime() - if (now - CACHE[transaction.transactionHash].time < CACHE_VALID_TIME) { - return CACHE[transaction.transactionHash].data + } else { + already[tmp2.outcoming.transactionHash] = 1; + } + } + if (tmp2.incoming) { + if ( + typeof CACHE_FROM_DB[tmp2.incoming.transactionHash + '_data'] === + 'undefined' + ) { + tmp2.incoming = await this._unifyTransactionStep2( + address, + tmp2.incoming + ); + if (tmp2.incoming) { + already[tmp2.incoming.transactionHash] = 1; + transactions.push(tmp2.incoming); } + } else { + already[tmp2.incoming.transactionHash] = 1; + } } + } + } + BlocksoftCryptoLog.log( + 'XvgScannerProcessor.getTransactions finished ' + + address + + ' total: ' + + transactions.length + ); + return transactions; + } - let tmp + /** + * https://api.vergecurrency.network/node/api/XVG/mainnet/tx/abcda88bdb3968c5e444694ce3914cdec34f3afab73627bf201d34493d5e3aae/coins + * @param address + * @param transaction + * @returns {Promise} + * @private + */ + async _unifyTransactionStep2(address, transaction) { + if (!transaction) return false; - const link = `${API_PATH}/tx/${transaction.transactionHash}/coins` - BlocksoftCryptoLog.log('XvgScannerProcessor._unifyTransactionStep2 call for outputs should be ' + link) + if (typeof CACHE[transaction.transactionHash] !== 'undefined') { + if (CACHE[transaction.transactionHash].data.blockConfirmations > 100) { + return CACHE[transaction.transactionHash].data; + } + const now = new Date().getTime(); + if (now - CACHE[transaction.transactionHash].time < CACHE_VALID_TIME) { + return CACHE[transaction.transactionHash].data; + } + } - if (typeof CACHE_FROM_DB[transaction.transactionHash + '_coins'] !== 'undefined') { - tmp = CACHE_FROM_DB[transaction.transactionHash + '_coins'] - } else { - BlocksoftCryptoLog.log('XvgScannerProcessor._unifyTransactionStep2 called ' + link) - tmp = await BlocksoftAxios.get(link) - tmp = tmp.data - // noinspection ES6MissingAwait - XvgTmpDS.saveCache(address, transaction.transactionHash, 'coins', tmp) - CACHE_FROM_DB[transaction.transactionHash + '_coins'] = tmp - } + let tmp; + const link = `${API_PATH}/tx/${transaction.transactionHash}/coins`; + BlocksoftCryptoLog.log( + 'XvgScannerProcessor._unifyTransactionStep2 call for outputs should be ' + + link + ); - let output - try { - output = await XvgFindAddressFunction(address, tmp) - } catch (e) { - e.message += ' while XvgFindAddressFunction' - throw e - } + if ( + typeof CACHE_FROM_DB[transaction.transactionHash + '_coins'] !== + 'undefined' + ) { + tmp = CACHE_FROM_DB[transaction.transactionHash + '_coins']; + } else { + BlocksoftCryptoLog.log( + 'XvgScannerProcessor._unifyTransactionStep2 called ' + link + ); + tmp = await BlocksoftAxios.get(link); + tmp = tmp.data; + // noinspection ES6MissingAwait + XvgTmpDS.saveCache(address, transaction.transactionHash, 'coins', tmp); + CACHE_FROM_DB[transaction.transactionHash + '_coins'] = tmp; + } - transaction.transactionDirection = output.direction - transaction.addressFrom = output.from - transaction.addressTo = output.to - transaction.addressAmount = output.value - - - const link2 = `${API_PATH}/tx/${transaction.transactionHash}` - BlocksoftCryptoLog.log('XvgScannerProcessor._unifyTransactionStep2 call for details ' + link2) - let tmp2 = await BlocksoftAxios.get(link2) - tmp2 = tmp2.data - transaction.blockHash = tmp2.blockHash - transaction.blockTime = tmp2.blockTimeNormalized - transaction.blockConfirmations = tmp2.confirmations * 1 - if (transaction.blockConfirmations < 0) transaction.blockConfirmations = transaction.blockConfirmations * -1 - - transaction.transaction_fee = tmp2.fee - transaction.transactionStatus = 'new' - if (transaction.blockConfirmations > this._blocksToConfirm) { - transaction.transactionStatus = 'success' - } else if (transaction.blockConfirmations > 0) { - transaction.transactionStatus = 'confirming' - } - if (transaction.transactionStatus === 'success') { - // noinspection ES6MissingAwait - XvgTmpDS.saveCache(address, transaction.transactionHash, 'data', tmp2) - CACHE_FROM_DB[transaction.transactionHash + '_data'] = 1 // no need all - just mark - } - BlocksoftCryptoLog.log('XvgScannerProcessor._unifyTransactionStep2 call for details result ', transaction) - CACHE[transaction.transactionHash] = {} - CACHE[transaction.transactionHash].time = new Date().getTime() - CACHE[transaction.transactionHash].data = transaction - return transaction + let output; + try { + output = await XvgFindAddressFunction(address, tmp); + } catch (e) { + e.message += ' while XvgFindAddressFunction'; + throw e; } - /** - * - * @param {string} address - * @param {Object} transaction - * @param {string} transaction._id 5dcedb83746f4c73710ff5ce - * @param {string} transaction.chain XVG - * @param {string} transaction.network mainnet - * @param {string} transaction.coinbase false - * @param {string} transaction.mintIndex 0 - * @param {string} transaction.spentTxid - * @param {string} transaction.mintTxid abcda88bdb3968c5e444694ce3914cdec34f3afab73627bf201d34493d5e3aae - * @param {string} transaction.mintHeight 3600363 - * @param {string} transaction.spentHeight - * @param {string} transaction.address DL5LtSf7wztH45VuYunL8oaQHtJbKLCHyw - * @param {string} transaction.script 76a914a3d43334ff9ea4c257a1796b63e4fa8330747d2e88ac - * @param {string} transaction.value 95000000 - * @param {string} transaction.confirmations - * @param {*} already - * @return {UnifiedTransaction} - * @private - */ - async _unifyTransactionStep1(address, transaction, already) { - if (transaction.chain !== 'XVG' || transaction.network !== 'mainnet') return false - const res = { incoming: false, outcoming: false } - if (transaction.spentTxid && typeof already[transaction.spentTxid] === 'undefined') { - res.outcoming = { - transactionHash: transaction.spentTxid, - blockHash: '?', - blockNumber: +transaction.spentHeight, - blockTime: '?', - blockConfirmations: '?', - transactionDirection: 'outcome', - addressFrom: transaction.address, - addressTo: '?', - addressAmount: '0', - transactionStatus: '?' - } - } - if (transaction.mintTxid && transaction.mintTxid !== transaction.spentTxid && typeof already[transaction.mintTxid] === 'undefined') { - res.incoming = { - transactionHash: transaction.mintTxid, - blockHash: '?', - blockNumber: +transaction.mintHeight, - blockTime: '?', - blockConfirmations: '?', - transactionDirection: 'income', - addressFrom: '?', - addressTo: transaction.address, - addressAmount: '0', // transaction.value - transactionStatus: '?' - } - } - return res + transaction.transactionDirection = output.direction; + transaction.addressFrom = output.from; + transaction.addressTo = output.to; + transaction.addressAmount = output.value; + const link2 = `${API_PATH}/tx/${transaction.transactionHash}`; + BlocksoftCryptoLog.log( + 'XvgScannerProcessor._unifyTransactionStep2 call for details ' + link2 + ); + let tmp2 = await BlocksoftAxios.get(link2); + tmp2 = tmp2.data; + transaction.blockHash = tmp2.blockHash; + transaction.blockTime = tmp2.blockTimeNormalized; + transaction.blockConfirmations = tmp2.confirmations * 1; + if (transaction.blockConfirmations < 0) + transaction.blockConfirmations = transaction.blockConfirmations * -1; + + transaction.transaction_fee = tmp2.fee; + transaction.transactionStatus = 'new'; + if (transaction.blockConfirmations > this._blocksToConfirm) { + transaction.transactionStatus = 'success'; + } else if (transaction.blockConfirmations > 0) { + transaction.transactionStatus = 'confirming'; + } + if (transaction.transactionStatus === 'success') { + // noinspection ES6MissingAwait + XvgTmpDS.saveCache(address, transaction.transactionHash, 'data', tmp2); + CACHE_FROM_DB[transaction.transactionHash + '_data'] = 1; // no need all - just mark + } + BlocksoftCryptoLog.log( + 'XvgScannerProcessor._unifyTransactionStep2 call for details result ', + transaction + ); + CACHE[transaction.transactionHash] = {}; + CACHE[transaction.transactionHash].time = new Date().getTime(); + CACHE[transaction.transactionHash].data = transaction; + return transaction; + } + + /** + * + * @param {string} address + * @param {Object} transaction + * @param {string} transaction._id 5dcedb83746f4c73710ff5ce + * @param {string} transaction.chain XVG + * @param {string} transaction.network mainnet + * @param {string} transaction.coinbase false + * @param {string} transaction.mintIndex 0 + * @param {string} transaction.spentTxid + * @param {string} transaction.mintTxid abcda88bdb3968c5e444694ce3914cdec34f3afab73627bf201d34493d5e3aae + * @param {string} transaction.mintHeight 3600363 + * @param {string} transaction.spentHeight + * @param {string} transaction.address DL5LtSf7wztH45VuYunL8oaQHtJbKLCHyw + * @param {string} transaction.script 76a914a3d43334ff9ea4c257a1796b63e4fa8330747d2e88ac + * @param {string} transaction.value 95000000 + * @param {string} transaction.confirmations + * @param {*} already + * @return {UnifiedTransaction} + * @private + */ + async _unifyTransactionStep1(address, transaction, already) { + if (transaction.chain !== 'XVG' || transaction.network !== 'mainnet') + return false; + const res = { incoming: false, outcoming: false }; + if ( + transaction.spentTxid && + typeof already[transaction.spentTxid] === 'undefined' + ) { + res.outcoming = { + transactionHash: transaction.spentTxid, + blockHash: '?', + blockNumber: +transaction.spentHeight, + blockTime: '?', + blockConfirmations: '?', + transactionDirection: 'outcome', + addressFrom: transaction.address, + addressTo: '?', + addressAmount: '0', + transactionStatus: '?' + }; + } + if ( + transaction.mintTxid && + transaction.mintTxid !== transaction.spentTxid && + typeof already[transaction.mintTxid] === 'undefined' + ) { + res.incoming = { + transactionHash: transaction.mintTxid, + blockHash: '?', + blockNumber: +transaction.mintHeight, + blockTime: '?', + blockConfirmations: '?', + transactionDirection: 'income', + addressFrom: '?', + addressTo: transaction.address, + addressAmount: '0', // transaction.value + transactionStatus: '?' + }; } + return res; + } } diff --git a/crypto/blockchains/xvg/basic/XvgFindAddressFunction.js b/crypto/blockchains/xvg/basic/XvgFindAddressFunction.js index adfd31015..80ed310cd 100644 --- a/crypto/blockchains/xvg/basic/XvgFindAddressFunction.js +++ b/crypto/blockchains/xvg/basic/XvgFindAddressFunction.js @@ -9,87 +9,92 @@ * @returns {Promise<{from: string, to: string, value: number, direction: string}>} * @constructor */ -import BlocksoftUtils from '../../../common/BlocksoftUtils' -import BlocksoftBN from '../../../common/BlocksoftBN' +import BlocksoftUtils from '../../../common/BlocksoftUtils'; +import BlocksoftBN from '../../../common/BlocksoftBN'; export default async function XvgFindAddressFunction(address, tmp) { + const inputMyBN = new BlocksoftBN(0); + const inputOthersBN = new BlocksoftBN(0); + const inputOthersAddresses = []; + const uniqueTmp = {}; - const inputMyBN = new BlocksoftBN(0) - const inputOthersBN = new BlocksoftBN(0) - const inputOthersAddresses = [] - const uniqueTmp = {} - - let input - for (input of tmp.inputs) { - if (input.address) { - const vinAddress = input.address - if (vinAddress === address) { - inputMyBN.add(input.value) - } else { - if (typeof uniqueTmp[vinAddress] === 'undefined') { - uniqueTmp[vinAddress] = 1 - inputOthersAddresses.push(vinAddress) - } - inputOthersBN.add(input.va) - } + let input; + for (input of tmp.inputs) { + if (input.address) { + const vinAddress = input.address; + if (vinAddress === address) { + inputMyBN.add(input.value); + } else { + if (typeof uniqueTmp[vinAddress] === 'undefined') { + uniqueTmp[vinAddress] = 1; + inputOthersAddresses.push(vinAddress); } + inputOthersBN.add(input.va); + } } + } - const outputMyBN = new BlocksoftBN(0) - const outputOthersBN = new BlocksoftBN(0) - const outputOthersAddresses = [] - const uniqueTmp2 = {} + const outputMyBN = new BlocksoftBN(0); + const outputOthersBN = new BlocksoftBN(0); + const outputOthersAddresses = []; + const uniqueTmp2 = {}; - let output - for (output of tmp.outputs) { - if (output.address) { - const voutAddress = output.address - if (output.address === address) { - outputMyBN.add(output.value) - } else { - if (typeof uniqueTmp2[voutAddress] === 'undefined') { - uniqueTmp2[voutAddress] = 1 - outputOthersAddresses.push(voutAddress) - } - outputOthersBN.add(output.value) - } + let output; + for (output of tmp.outputs) { + if (output.address) { + const voutAddress = output.address; + if (output.address === address) { + outputMyBN.add(output.value); + } else { + if (typeof uniqueTmp2[voutAddress] === 'undefined') { + uniqueTmp2[voutAddress] = 1; + outputOthersAddresses.push(voutAddress); } + outputOthersBN.add(output.value); + } } + } - if (inputMyBN.get() === '0') { // my only in output - output = { - direction: 'income', - from: inputOthersAddresses.length > 0 ? inputOthersAddresses.join(',') : '', - to: '', // address, - value: outputMyBN.get() - } - } else if (outputMyBN.get() === '0') { // my only in input - output = { - direction: 'outcome', - from: '', // address, - to: outputOthersAddresses.length > 0 ? outputOthersAddresses.join(',') : '', - value: (inputOthersBN.get() === '0') ? outputOthersBN.get() : inputMyBN.get() - } - } else { // both input and output - if (outputOthersAddresses.length > 0) {// there are other address - output = { - direction: 'outcome', - from: '', // address, - to: outputOthersAddresses.join(','), - value: outputOthersBN.get() - } - } else { - output = { - direction: 'self', - from: '', // address, - to: '', // address, - value: inputMyBN.diff(outputMyBN).get() - } - } + if (inputMyBN.get() === '0') { + // my only in output + output = { + direction: 'income', + from: + inputOthersAddresses.length > 0 ? inputOthersAddresses.join(',') : '', + to: '', // address, + value: outputMyBN.get() + }; + } else if (outputMyBN.get() === '0') { + // my only in input + output = { + direction: 'outcome', + from: '', // address, + to: + outputOthersAddresses.length > 0 ? outputOthersAddresses.join(',') : '', + value: + inputOthersBN.get() === '0' ? outputOthersBN.get() : inputMyBN.get() + }; + } else { + // both input and output + if (outputOthersAddresses.length > 0) { + // there are other address + output = { + direction: 'outcome', + from: '', // address, + to: outputOthersAddresses.join(','), + value: outputOthersBN.get() + }; + } else { + output = { + direction: 'self', + from: '', // address, + to: '', // address, + value: inputMyBN.diff(outputMyBN).get() + }; } - output.from = output.from.substr(0, 255) - output.to = output.to.substr(0, 255) - + } + output.from = output.from.substr(0, 255); + output.to = output.to.substr(0, 255); - return output + return output; } diff --git a/crypto/blockchains/xvg/providers/XvgSendProvider.ts b/crypto/blockchains/xvg/providers/XvgSendProvider.ts index 58ffb0637..ab26a717b 100644 --- a/crypto/blockchains/xvg/providers/XvgSendProvider.ts +++ b/crypto/blockchains/xvg/providers/XvgSendProvider.ts @@ -1,51 +1,87 @@ /** * @version 0.20 */ -import { BlocksoftBlockchainTypes } from '../../BlocksoftBlockchainTypes' -import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog' -import BlocksoftAxios from '../../../common/BlocksoftAxios' -import BlocksoftExternalSettings from '../../../common/BlocksoftExternalSettings' +import { BlocksoftBlockchainTypes } from '../../BlocksoftBlockchainTypes'; +import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; +import BlocksoftAxios from '../../../common/BlocksoftAxios'; +import BlocksoftExternalSettings from '../../../common/BlocksoftExternalSettings'; -export default class XvgSendProvider implements BlocksoftBlockchainTypes.SendProvider { +export default class XvgSendProvider + implements BlocksoftBlockchainTypes.SendProvider +{ + protected _settings: BlocksoftBlockchainTypes.CurrencySettings; - protected _settings: BlocksoftBlockchainTypes.CurrencySettings + constructor( + settings: BlocksoftBlockchainTypes.CurrencySettings, + serverCode: string + ) { + this._settings = settings; + } - constructor(settings: BlocksoftBlockchainTypes.CurrencySettings, serverCode: string) { - this._settings = settings + async sendTx( + hex: string, + subtitle: string + ): Promise<{ transactionHash: string; transactionJson: any }> { + const link = BlocksoftExternalSettings.getStatic('XVG_SEND_LINK'); + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' XvgSendProvider.sendTx ' + + subtitle + + ' started ' + + subtitle + + ' ' + + link + ); + let res; + try { + res = await BlocksoftAxios.post(link, { rawTx: hex }); + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' XvgSendProvider.sendTx ' + + subtitle + + ' error ' + + subtitle + + ' ok ' + + hex + ); + } catch (e) { + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' XvgSendProvider.sendTx ' + + subtitle + + ' error ' + + subtitle + + ' ' + + e.message + + ' ' + + hex + ); + if (e.message.indexOf('mandatory-script-verify-flag-failed') !== -1) { + throw new Error('SERVER_RESPONSE_PLEASE_CHECK_SYSTEM_TIME'); + } else if (e.message.indexOf('dust') !== -1) { + throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_DUST'); + } else if (e.message.indexOf('missing inputs') !== -1) { + throw new Error('SERVER_RESPONSE_NO_RESPONSE'); + } else if (e.message.indexOf('bad-txns-inputs-spent') !== -1) { + throw new Error('SERVER_RESPONSE_NO_RESPONSE'); + } else if (e.message.indexOf('txn-mempool-conflict') !== -1) { + throw new Error('SERVER_RESPONSE_NO_RESPONSE'); + } else if ( + e.message.indexOf('fee for relay') !== -1 || + e.message.indexOf('insufficient priority') !== -1 + ) { + throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_FEE'); + } else if (e.message.indexOf('rejecting replacement') !== -1) { + throw new Error( + 'SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_FEE_FOR_REPLACEMENT' + ); + } else { + throw e; + } } - - async sendTx(hex: string, subtitle: string): Promise<{ transactionHash: string, transactionJson: any }> { - const link = BlocksoftExternalSettings.getStatic('XVG_SEND_LINK') - BlocksoftCryptoLog.log(this._settings.currencyCode + ' XvgSendProvider.sendTx ' + subtitle + ' started ' + subtitle + ' ' + link) - let res - try { - res = await BlocksoftAxios.post(link, { rawTx: hex }) - BlocksoftCryptoLog.log(this._settings.currencyCode + ' XvgSendProvider.sendTx ' + subtitle + ' error ' + subtitle + ' ok ' + hex) - } catch (e) { - BlocksoftCryptoLog.log(this._settings.currencyCode + ' XvgSendProvider.sendTx ' + subtitle + ' error ' + subtitle + ' ' + e.message + ' ' + hex) - if (e.message.indexOf('mandatory-script-verify-flag-failed') !== -1) { - throw new Error('SERVER_RESPONSE_PLEASE_CHECK_SYSTEM_TIME') - } else if (e.message.indexOf('dust') !== -1) { - throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_DUST') - } else if (e.message.indexOf('missing inputs') !== -1) { - throw new Error('SERVER_RESPONSE_NO_RESPONSE') - } else if (e.message.indexOf('bad-txns-inputs-spent') !== -1) { - throw new Error('SERVER_RESPONSE_NO_RESPONSE') - } else if (e.message.indexOf('txn-mempool-conflict') !== -1) { - throw new Error('SERVER_RESPONSE_NO_RESPONSE') - } else if (e.message.indexOf('fee for relay') !== -1 || e.message.indexOf('insufficient priority') !== -1) { - throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_FEE') - } else if (e.message.indexOf('rejecting replacement') !== -1) { - throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_FEE_FOR_REPLACEMENT') - } else { - throw e - } - - } - if (typeof res.data.txid === 'undefined' || !res.data.txid) { - throw new Error('SERVER_RESPONSE_NOT_CONNECTED') - } - return { transactionHash: res.data.txid, transactionJson: {} } + if (typeof res.data.txid === 'undefined' || !res.data.txid) { + throw new Error('SERVER_RESPONSE_NOT_CONNECTED'); } + return { transactionHash: res.data.txid, transactionJson: {} }; + } } - diff --git a/crypto/blockchains/xvg/providers/XvgUnspentsProvider.ts b/crypto/blockchains/xvg/providers/XvgUnspentsProvider.ts index 695f8d101..7cd6f34af 100644 --- a/crypto/blockchains/xvg/providers/XvgUnspentsProvider.ts +++ b/crypto/blockchains/xvg/providers/XvgUnspentsProvider.ts @@ -2,67 +2,88 @@ * @version 0.20 * https://api.vergecurrency.network/node/api/XVG/mainnet/address/DL5LtSf7wztH45VuYunL8oaQHtJbKLCHyw/txs/?unspent=true */ -import { BlocksoftBlockchainTypes } from '../../BlocksoftBlockchainTypes' -import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog' -import BlocksoftAxios from '../../../common/BlocksoftAxios' +import { BlocksoftBlockchainTypes } from '../../BlocksoftBlockchainTypes'; +import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; +import BlocksoftAxios from '../../../common/BlocksoftAxios'; -export default class XvgUnspentsProvider implements BlocksoftBlockchainTypes.UnspentsProvider { +export default class XvgUnspentsProvider + implements BlocksoftBlockchainTypes.UnspentsProvider +{ + _apiPath = 'https://api.vergecurrency.network/node/api/XVG/mainnet/address/'; - _apiPath = 'https://api.vergecurrency.network/node/api/XVG/mainnet/address/' + protected _settings: BlocksoftBlockchainTypes.CurrencySettings; - protected _settings: BlocksoftBlockchainTypes.CurrencySettings + constructor( + settings: BlocksoftBlockchainTypes.CurrencySettings, + serverCode: string + ) { + this._settings = settings; + } - constructor(settings: BlocksoftBlockchainTypes.CurrencySettings, serverCode: string) { - this._settings = settings - } - - async getUnspents(address: string): Promise { - // @ts-ignore - BlocksoftCryptoLog.log(this._settings.currencyCode + ' XvgUnspentsProvider.getUnspents started', address) + async getUnspents( + address: string + ): Promise { + // @ts-ignore + BlocksoftCryptoLog.log( + this._settings.currencyCode + ' XvgUnspentsProvider.getUnspents started', + address + ); - const link = this._apiPath + address + '/txs/?unspent=true' - const res = await BlocksoftAxios.getWithoutBraking(link) - BlocksoftCryptoLog.log(this._settings.currencyCode + ' XvgUnspentsProvider.getUnspents link', link) - if (!res || typeof res.data === 'undefined') { - BlocksoftCryptoLog.log(this._settings.currencyCode + ' XvgUnspentsProvider.getUnspents nothing loaded for address ' + address + ' link ' + link) - throw new Error('SERVER_RESPONSE_NOT_CONNECTED') - } - if (!res.data || typeof res.data[0] === 'undefined') { - return [] - } - const sortedUnspents = [] - /** - * https://api.vergecurrency.network/node/api/XVG/mainnet/address/DL5LtSf7wztH45VuYunL8oaQHtJbKLCHyw/txs/?unspent=true - * @param {*} res.data[] - * @param {string} res.data[]._id "5e0b42fb746f4c73717c1d1d" - * @param {string} res.data[].chain "XVG" - * @param {string} res.data[].network "mainnet" - * @param {string} res.data[].coinbase false - * @param {string} res.data[].mintIndex 1 - * @param {string} res.data[].spentTxid - * @param {string} res.data[].mintTxid "50aae03bec6662a277c6e03ff2c58a200912e1bb78519d8403354c66c4d51892" - * @param {string} res.data[].mintHeight 3715825 - * @param {string} res.data[].spentHeight - * @param {string} res.data[].address "DL5LtSf7wztH45VuYunL8oaQHtJbKLCHyw" - * @param {string} res.data[].script "76a914a3d43334ff9ea4c257a1796b63e4fa8330747d2e88ac" - * @param {string} res.data[].value 91523000 - * @param {string} res.data[].confirmations -1 - */ - const already = {} - let unspent - for (unspent of res.data) { - if (typeof already[unspent.mintTxid] === 'undefined' || already[unspent.mintTxid] > unspent.value) { - sortedUnspents.push({ - txid: unspent.mintTxid, - vout: unspent.mintIndex, - value: unspent.value.toString(), - height: unspent.mintHeight, - confirmations: 1, - isRequired: false - }) - already[unspent.mintTxid] = unspent.value - } - } - return sortedUnspents + const link = this._apiPath + address + '/txs/?unspent=true'; + const res = await BlocksoftAxios.getWithoutBraking(link); + BlocksoftCryptoLog.log( + this._settings.currencyCode + ' XvgUnspentsProvider.getUnspents link', + link + ); + if (!res || typeof res.data === 'undefined') { + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' XvgUnspentsProvider.getUnspents nothing loaded for address ' + + address + + ' link ' + + link + ); + throw new Error('SERVER_RESPONSE_NOT_CONNECTED'); + } + if (!res.data || typeof res.data[0] === 'undefined') { + return []; + } + const sortedUnspents = []; + /** + * https://api.vergecurrency.network/node/api/XVG/mainnet/address/DL5LtSf7wztH45VuYunL8oaQHtJbKLCHyw/txs/?unspent=true + * @param {*} res.data[] + * @param {string} res.data[]._id "5e0b42fb746f4c73717c1d1d" + * @param {string} res.data[].chain "XVG" + * @param {string} res.data[].network "mainnet" + * @param {string} res.data[].coinbase false + * @param {string} res.data[].mintIndex 1 + * @param {string} res.data[].spentTxid + * @param {string} res.data[].mintTxid "50aae03bec6662a277c6e03ff2c58a200912e1bb78519d8403354c66c4d51892" + * @param {string} res.data[].mintHeight 3715825 + * @param {string} res.data[].spentHeight + * @param {string} res.data[].address "DL5LtSf7wztH45VuYunL8oaQHtJbKLCHyw" + * @param {string} res.data[].script "76a914a3d43334ff9ea4c257a1796b63e4fa8330747d2e88ac" + * @param {string} res.data[].value 91523000 + * @param {string} res.data[].confirmations -1 + */ + const already = {}; + let unspent; + for (unspent of res.data) { + if ( + typeof already[unspent.mintTxid] === 'undefined' || + already[unspent.mintTxid] > unspent.value + ) { + sortedUnspents.push({ + txid: unspent.mintTxid, + vout: unspent.mintIndex, + value: unspent.value.toString(), + height: unspent.mintHeight, + confirmations: 1, + isRequired: false + }); + already[unspent.mintTxid] = unspent.value; + } } + return sortedUnspents; + } } diff --git a/crypto/common/BlocksoftAxios.js b/crypto/common/BlocksoftAxios.js index e453a4feb..72361eda9 100644 --- a/crypto/common/BlocksoftAxios.js +++ b/crypto/common/BlocksoftAxios.js @@ -1,357 +1,433 @@ /** * @version 0.43 */ -import BlocksoftCryptoLog from './BlocksoftCryptoLog' +import BlocksoftCryptoLog from './BlocksoftCryptoLog'; -import axios from 'axios' -import config from '@app/config/config' -import { showModal } from '@app/appstores/Stores/Modal/ModalActions' -import { strings } from '@app/services/i18n' +import axios from 'axios'; +import config from '@app/config/config'; +import { showModal } from '@app/appstores/Stores/Modal/ModalActions'; +import { strings } from '@app/services/i18n'; -import { Platform } from 'react-native' -import CookieManager from '@react-native-cookies/cookies' +import { Platform } from 'react-native'; +import CookieManager from '@react-native-cookies/cookies'; -const CancelToken = axios && typeof axios.CancelToken !== 'undefined' ? axios.CancelToken : function() {} +const CancelToken = + axios && typeof axios.CancelToken !== 'undefined' + ? axios.CancelToken + : function () {}; -const CACHE_ERRORS_VALID_TIME = 60000 // 1 minute -const CACHE_ERRORS_BY_LINKS = {} +const CACHE_ERRORS_VALID_TIME = 60000; // 1 minute +const CACHE_ERRORS_BY_LINKS = {}; -const CACHE_STARTED = {} -const CACHE_STARTED_CANCEL = {} +const CACHE_STARTED = {}; +const CACHE_STARTED_CANCEL = {}; -let CACHE_TIMEOUT_ERRORS = 0 -let CACHE_TIMEOUT_ERROR_SHOWN = 0 +let CACHE_TIMEOUT_ERRORS = 0; +let CACHE_TIMEOUT_ERROR_SHOWN = 0; -const TIMEOUT = 20000 -const TIMEOUT_TRUSTEE = 7000 -const TIMEOUT_TRIES_2 = 10000 -const TIMEOUT_TRIES_10 = 15000 -const TIMEOUT_TRIES_INTERNET = 5000 -const TIMEOUT_TRIES_RATES = 20000 +const TIMEOUT = 20000; +const TIMEOUT_TRUSTEE = 7000; +const TIMEOUT_TRIES_2 = 10000; +const TIMEOUT_TRIES_10 = 15000; +const TIMEOUT_TRIES_INTERNET = 5000; +const TIMEOUT_TRIES_RATES = 20000; class BlocksoftAxios { - - /** - * @param link - * @param maxTry - * @returns {Promise} - */ - async getWithoutBraking(link, maxTry = 5, timeOut = false) { - let tmp = false - try { - tmp = await this.get(link, false, false, timeOut) - CACHE_ERRORS_BY_LINKS[link] = { time: 0, tries: 0 } - } catch (e) { - const now = new Date().getTime() - if (typeof CACHE_ERRORS_BY_LINKS[link] === 'undefined') { - // first time - CACHE_ERRORS_BY_LINKS[link] = { time: now, tries: 1 } - } else if ( - now - CACHE_ERRORS_BY_LINKS[link].time < CACHE_ERRORS_VALID_TIME - ) { - // no plus as too fast - } else if ( - CACHE_ERRORS_BY_LINKS[link].tries < maxTry - ) { - // plus as time passed - CACHE_ERRORS_BY_LINKS[link].tries++ - CACHE_ERRORS_BY_LINKS[link].time = now - } else { - // only here will error actual - e.code = 'ERROR_PROVIDER' - CACHE_ERRORS_BY_LINKS[link].time = now - throw e - } - BlocksoftCryptoLog.log('BlocksoftAxios.getWithoutBraking try ' + JSON.stringify(CACHE_ERRORS_BY_LINKS[link]) + ' error ' + e.message.substr(0, 300)) - } - - return tmp + /** + * @param link + * @param maxTry + * @returns {Promise} + */ + async getWithoutBraking(link, maxTry = 5, timeOut = false) { + let tmp = false; + try { + tmp = await this.get(link, false, false, timeOut); + CACHE_ERRORS_BY_LINKS[link] = { time: 0, tries: 0 }; + } catch (e) { + const now = new Date().getTime(); + if (typeof CACHE_ERRORS_BY_LINKS[link] === 'undefined') { + // first time + CACHE_ERRORS_BY_LINKS[link] = { time: now, tries: 1 }; + } else if ( + now - CACHE_ERRORS_BY_LINKS[link].time < + CACHE_ERRORS_VALID_TIME + ) { + // no plus as too fast + } else if (CACHE_ERRORS_BY_LINKS[link].tries < maxTry) { + // plus as time passed + CACHE_ERRORS_BY_LINKS[link].tries++; + CACHE_ERRORS_BY_LINKS[link].time = now; + } else { + // only here will error actual + e.code = 'ERROR_PROVIDER'; + CACHE_ERRORS_BY_LINKS[link].time = now; + throw e; + } + BlocksoftCryptoLog.log( + 'BlocksoftAxios.getWithoutBraking try ' + + JSON.stringify(CACHE_ERRORS_BY_LINKS[link]) + + ' error ' + + e.message.substr(0, 300) + ); } - async postWithoutBraking(link, data, maxTry = 5) { - let tmp = false - try { - tmp = await this.post(link, data, false) - CACHE_ERRORS_BY_LINKS[link] = { time: 0, tries: 0 } - } catch (e) { - const now = new Date().getTime() - if (typeof CACHE_ERRORS_BY_LINKS[link] === 'undefined') { - // first time - CACHE_ERRORS_BY_LINKS[link] = { time: now, tries: 1 } - } else if ( - now - CACHE_ERRORS_BY_LINKS[link].time < CACHE_ERRORS_VALID_TIME - ) { - // no plus as too fast - } else if ( - CACHE_ERRORS_BY_LINKS[link].tries < maxTry - ) { - // plus as time passed - CACHE_ERRORS_BY_LINKS[link].tries++ - CACHE_ERRORS_BY_LINKS[link].time = now - } else { - // only here will error actual - e.code = 'ERROR_PROVIDER' - CACHE_ERRORS_BY_LINKS[link].time = now - throw e - } - BlocksoftCryptoLog.log('BlocksoftAxios.postWithoutBraking try ' + JSON.stringify(CACHE_ERRORS_BY_LINKS[link]) + ' error ' + e.message.substr(0, 200)) - } - return tmp + return tmp; + } + + async postWithoutBraking(link, data, maxTry = 5) { + let tmp = false; + try { + tmp = await this.post(link, data, false); + CACHE_ERRORS_BY_LINKS[link] = { time: 0, tries: 0 }; + } catch (e) { + const now = new Date().getTime(); + if (typeof CACHE_ERRORS_BY_LINKS[link] === 'undefined') { + // first time + CACHE_ERRORS_BY_LINKS[link] = { time: now, tries: 1 }; + } else if ( + now - CACHE_ERRORS_BY_LINKS[link].time < + CACHE_ERRORS_VALID_TIME + ) { + // no plus as too fast + } else if (CACHE_ERRORS_BY_LINKS[link].tries < maxTry) { + // plus as time passed + CACHE_ERRORS_BY_LINKS[link].tries++; + CACHE_ERRORS_BY_LINKS[link].time = now; + } else { + // only here will error actual + e.code = 'ERROR_PROVIDER'; + CACHE_ERRORS_BY_LINKS[link].time = now; + throw e; + } + BlocksoftCryptoLog.log( + 'BlocksoftAxios.postWithoutBraking try ' + + JSON.stringify(CACHE_ERRORS_BY_LINKS[link]) + + ' error ' + + e.message.substr(0, 200) + ); } + return tmp; + } + + async postWithHeaders( + link, + data, + addHeaders, + errSend = true, + timeOut = false + ) { + let tmp = false; + try { + const headers = { + 'upgrade-insecure-requests': 1, + 'user-agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.128 Safari/537.36' + }; + const dataPrep = JSON.stringify(data); + headers['Content-Type'] = 'application/json'; + headers['Accept'] = 'application/json'; + for (let key in addHeaders) { + headers[key] = addHeaders[key]; + } + + const tmpInner = await fetch(link, { + method: 'POST', + credentials: 'same-origin', + mode: 'same-origin', + redirect: 'follow', + headers, + body: dataPrep + }); + if ( + tmpInner.status !== 200 && + tmpInner.status !== 201 && + tmpInner.status !== 202 + ) { + BlocksoftCryptoLog.log( + 'BlocksoftAxios.post fetch result ' + JSON.stringify(tmpInner) + ); + } else { + tmp = { data: await tmpInner.json() }; + } + } catch (e) { + BlocksoftCryptoLog.log( + 'BlocksoftAxios.postWithHeaders fetch result error ' + e.message + ); + } + return tmp; + } + + async getWithHeaders(link, addHeaders, errSend = true, timeOut = false) { + let tmp = false; + try { + const headers = { + 'upgrade-insecure-requests': 1, + 'user-agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.128 Safari/537.36' + }; + headers['Content-Type'] = 'application/json'; + headers['Accept'] = 'application/json'; + for (let key in addHeaders) { + headers[key] = addHeaders[key]; + } + + const tmpInner = await fetch(link, { + method: 'GET', + credentials: 'same-origin', + mode: 'same-origin', + redirect: 'follow', + headers + }); + if ( + tmpInner.status !== 200 && + tmpInner.status !== 201 && + tmpInner.status !== 202 + ) { + BlocksoftCryptoLog.log( + 'BlocksoftAxios.get fetch result ' + JSON.stringify(tmpInner) + ); + } else { + tmp = { data: await tmpInner.json() }; + } + } catch (e) { + console.log( + 'BlocksoftAxios.getWithHeaders fetch result error ' + e.message + ); + } + return tmp; + } + + async post(link, data, errSend = true, timeOut = false) { + let tmp = false; + let doOld = this._isTrustee(link); + if (!doOld) { + try { + await this._cookie(link, 'POST'); + let dataPrep; + const headers = { + 'upgrade-insecure-requests': 1, + 'user-agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.128 Safari/537.36' + }; + if (typeof data === 'object') { + dataPrep = JSON.stringify(data); + headers['Content-Type'] = 'application/json'; + headers['Accept'] = 'application/json'; + } else { + dataPrep = data; + headers['Content-Type'] = 'multipart/form-data'; + headers['Accept'] = 'multipart/form-data'; + } - async postWithHeaders(link, data, addHeaders, errSend = true, timeOut = false) { - let tmp = false - try { - const headers = { - 'upgrade-insecure-requests': 1, - 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.128 Safari/537.36' - } - const dataPrep = JSON.stringify(data) - headers['Content-Type'] = 'application/json' - headers['Accept'] = 'application/json' - for (let key in addHeaders) { - headers[key] = addHeaders[key] - } - - const tmpInner = await fetch(link, { - method: 'POST', - credentials: 'same-origin', - mode: 'same-origin', - redirect: 'follow', - headers, - body: dataPrep - }) - if (tmpInner.status !== 200 && tmpInner.status !== 201 && tmpInner.status !== 202) { - BlocksoftCryptoLog.log('BlocksoftAxios.post fetch result ' + JSON.stringify(tmpInner)) - } else { - tmp = { data: await tmpInner.json() } - } - } catch (e) { - BlocksoftCryptoLog.log('BlocksoftAxios.postWithHeaders fetch result error ' + e.message) + const tmpInner = await fetch(link, { + method: 'POST', + credentials: 'same-origin', + mode: 'same-origin', + redirect: 'follow', + headers, + body: dataPrep + }); + if ( + tmpInner.status !== 200 && + tmpInner.status !== 201 && + tmpInner.status !== 202 + ) { + BlocksoftCryptoLog.log( + 'BlocksoftAxios.post fetch result ' + JSON.stringify(tmpInner) + ); + doOld = true; + } else { + tmp = { data: await tmpInner.json() }; } - return tmp + } catch (e) { + BlocksoftCryptoLog.log( + 'BlocksoftAxios.post fetch result error ' + e.message + ); + doOld = true; + } } - - async getWithHeaders(link, addHeaders, errSend = true, timeOut = false) { - let tmp = false - try { - const headers = { - 'upgrade-insecure-requests': 1, - 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.128 Safari/537.36' - } - headers['Content-Type'] = 'application/json' - headers['Accept'] = 'application/json' - for (let key in addHeaders) { - headers[key] = addHeaders[key] + if (doOld) { + tmp = this._request(link, 'post', data, false, errSend, timeOut); + } + return tmp; + } + + async get(link, emptyIsBad = false, errSend = true, timeOut = false) { + let tmp = false; + let doOld = this._isTrustee(link); + if (!doOld) { + try { + await this._cookie(link, 'GET'); + let tryOneMore = false; + let antiCycle = 0; + do { + const tmpInner = await fetch(link, { + method: 'GET', + credentials: 'same-origin', + mode: 'same-origin', + redirect: 'follow', + headers: { + 'user-agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.128 Safari/537.36' } - - const tmpInner = await fetch(link, { - method: 'GET', - credentials: 'same-origin', - mode: 'same-origin', - redirect: 'follow', - headers - }) - if (tmpInner.status !== 200 && tmpInner.status !== 201 && tmpInner.status !== 202) { - BlocksoftCryptoLog.log('BlocksoftAxios.get fetch result ' + JSON.stringify(tmpInner)) - } else { - tmp = { data: await tmpInner.json() } + }); + if (tmpInner.status !== 200) { + doOld = true; + if (typeof tmpInner.headers.map['set-cookie'] !== 'undefined') { + tryOneMore = true; } - } catch (e) { - console.log('BlocksoftAxios.getWithHeaders fetch result error ' + e.message) - } - return tmp + } else { + tmp = { data: await tmpInner.json() }; + doOld = false; + tryOneMore = false; + } + antiCycle++; + } while (tryOneMore && antiCycle < 3); + } catch (e) { + BlocksoftCryptoLog.log( + 'BlocksoftAxios.get fetch result error ' + e.message + ); + doOld = true; + } } - - async post(link, data, errSend = true, timeOut = false) { - let tmp = false - let doOld = this._isTrustee(link) - if (!doOld) { - try { - await this._cookie(link, 'POST') - let dataPrep - const headers = { - 'upgrade-insecure-requests': 1, - 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.128 Safari/537.36' - } - if (typeof data === 'object') { - dataPrep = JSON.stringify(data) - headers['Content-Type'] = 'application/json' - headers['Accept'] = 'application/json' - } else { - dataPrep = data - headers['Content-Type'] = 'multipart/form-data' - headers['Accept'] = 'multipart/form-data' - } - - const tmpInner = await fetch(link, { - method: 'POST', - credentials: 'same-origin', - mode: 'same-origin', - redirect: 'follow', - headers, - body: dataPrep - }) - if (tmpInner.status !== 200 && tmpInner.status !== 201 && tmpInner.status !== 202) { - BlocksoftCryptoLog.log('BlocksoftAxios.post fetch result ' + JSON.stringify(tmpInner)) - doOld = true - } else { - tmp = { data: await tmpInner.json() } - } - } catch (e) { - BlocksoftCryptoLog.log('BlocksoftAxios.post fetch result error ' + e.message) - doOld = true - } - } - if (doOld) { - tmp = this._request(link, 'post', data, false, errSend, timeOut) - } - return tmp + if (doOld) { + tmp = this._request(link, 'get', {}, emptyIsBad, errSend, timeOut); } - - async get(link, emptyIsBad = false, errSend = true, timeOut = false) { - let tmp = false - let doOld = this._isTrustee(link) - if (!doOld) { - try { - await this._cookie(link, 'GET') - let tryOneMore = false - let antiCycle = 0 - do { - const tmpInner = await fetch(link, { - method: 'GET', - credentials: 'same-origin', - mode: 'same-origin', - redirect: 'follow', - headers: { - 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.128 Safari/537.36' - } - }) - if (tmpInner.status !== 200) { - doOld = true - if (typeof tmpInner.headers.map['set-cookie'] !== 'undefined') { - tryOneMore = true - } - } else { - tmp = { data: await tmpInner.json() } - doOld = false - tryOneMore = false - } - antiCycle++ - } while (tryOneMore && antiCycle < 3) - } catch (e) { - BlocksoftCryptoLog.log('BlocksoftAxios.get fetch result error ' + e.message) - doOld = true - } - } - if (doOld) { - tmp = this._request(link, 'get', {}, emptyIsBad, errSend, timeOut) - } - return tmp + return tmp; + } + + _isTrustee(link) { + const tmp = link.split('/'); + const domain = tmp[0] + '/' + tmp[1] + '/' + tmp[2]; + return !(domain.indexOf('trustee') === -1); + } + + async _cookie(link, method) { + const tmp = link.split('/'); + const domain = tmp[0] + '/' + tmp[1] + '/' + tmp[2]; + const domainShort = tmp[2]; + if (domain.indexOf('trustee') !== -1) { + return false; } - - _isTrustee(link) { - const tmp = link.split('/') - const domain = tmp[0] + '/' + tmp[1] + '/' + tmp[2] - return !(domain.indexOf('trustee') === -1) + const oldCookieObj = await CookieManager.get(link); + const oldCookie = JSON.stringify(oldCookieObj); + if (oldCookie === '{}') return false; + + // await CookieManager.clearAll() + if (Platform.OS === 'ios') { + await CookieManager.clearByName(domain); + await CookieManager.clearByName(link); + } else { + for (const key in oldCookieObj) { + const newData = { + name: key, + value: 'none', + domain: '.' + domainShort, + path: '/', + version: '1', + expires: new Date(new Date().getTime() + 24 * 360000).toISOString() + }; + await CookieManager.set(link, newData); + } } - - async _cookie(link, method) { - const tmp = link.split('/') - const domain = tmp[0] + '/' + tmp[1] + '/' + tmp[2] - const domainShort = tmp[2] - if (domain.indexOf('trustee') !== -1) { - return false - } - const oldCookieObj = await CookieManager.get(link) - const oldCookie = JSON.stringify(oldCookieObj) - if (oldCookie === '{}') return false - - // await CookieManager.clearAll() - if (Platform.OS === 'ios') { - await CookieManager.clearByName(domain) - await CookieManager.clearByName(link) - } else { - for (const key in oldCookieObj) { - const newData = { - name: key, - value: 'none', - domain: '.' + domainShort, - path: '/', - version: '1', - expires: new Date(new Date().getTime() + 24 * 360000).toISOString() - } - await CookieManager.set(link, newData) - } - } + } + + async _request( + link, + method = 'get', + data = {}, + emptyIsBad = false, + errSend = true, + timeOut = false + ) { + let tmp; + let cacheMD = link; + if (typeof data !== 'undefined') { + cacheMD += ' ' + JSON.stringify(data); } + try { + // noinspection JSUnresolvedFunction + const instance = axios.create(); + const cancelSource = CancelToken.source(); - async _request(link, method = 'get', data = {}, emptyIsBad = false, errSend = true, timeOut = false) { - let tmp - let cacheMD = link - if (typeof data !== 'undefined') { - cacheMD += ' ' + JSON.stringify(data) + if (!timeOut || typeof timeOut === 'undefined') { + timeOut = TIMEOUT; + if (this._isTrustee(link)) { + timeOut = TIMEOUT_TRUSTEE; } - try { - // noinspection JSUnresolvedFunction - const instance = axios.create() - - const cancelSource = CancelToken.source() - - if (!timeOut || typeof timeOut === 'undefined') { - timeOut = TIMEOUT - if (this._isTrustee(link)) { - timeOut = TIMEOUT_TRUSTEE - } - if (typeof CACHE_ERRORS_BY_LINKS[link] !== 'undefined') { - if (CACHE_ERRORS_BY_LINKS[link].tries > 2) { - timeOut = Math.round(TIMEOUT_TRIES_10) - } else { - timeOut = Math.round(TIMEOUT_TRIES_2) - } - } - if (link.indexOf('/fees') !== -1 || link.indexOf('/rates') !== -1) { - timeOut = Math.round(TIMEOUT_TRIES_RATES) - } else if (link.indexOf('/internet') !== -1) { - timeOut = Math.round(TIMEOUT_TRIES_INTERNET) - } - if (link.indexOf('solana.trusteeglobal.com') !== -1) { - timeOut = timeOut * 10 - } - } - - if (typeof CACHE_STARTED[cacheMD] !== 'undefined') { - const now = new Date().getTime() - const timeMsg = ' timeout ' + CACHE_STARTED[cacheMD].timeOut + ' started ' + CACHE_STARTED[cacheMD].time + ' diff ' + (now - CACHE_STARTED[cacheMD].time) - BlocksoftCryptoLog.log('PREV CALL WILL BE CANCELED ' + timeMsg) - await CACHE_STARTED_CANCEL[cacheMD].cancel('PREV CALL CANCELED ' + timeMsg) - } - instance.defaults.timeout = timeOut - instance.defaults.cancelToken = cancelSource.token - CACHE_STARTED[cacheMD] = { time: new Date().getTime(), timeOut } - CACHE_STARTED_CANCEL[cacheMD] = cancelSource - - const tmpTimer = setTimeout(() => { - cancelSource.cancel('TIMEOUT CANCELED ' + timeOut + ' ' + link + ' ' + JSON.stringify(data)) - }, timeOut) - if (method === 'get') { - tmp = await instance.get(link) - } else { - tmp = await instance.post(link, data) - } - clearTimeout(tmpTimer) - - if (emptyIsBad && (tmp.status !== 200 || !tmp.data)) { - // noinspection ExceptionCaughtLocallyJS - throw new Error('BlocksoftAxios.' + method + ' ' + link + ' status: ' + tmp.status + ' data: ' + tmp.data) - } - - if (typeof CACHE_STARTED[cacheMD] !== 'undefined') { - delete CACHE_STARTED[cacheMD] - delete CACHE_STARTED_CANCEL[cacheMD] - } - /* let txt = tmp.data + if (typeof CACHE_ERRORS_BY_LINKS[link] !== 'undefined') { + if (CACHE_ERRORS_BY_LINKS[link].tries > 2) { + timeOut = Math.round(TIMEOUT_TRIES_10); + } else { + timeOut = Math.round(TIMEOUT_TRIES_2); + } + } + if (link.indexOf('/fees') !== -1 || link.indexOf('/rates') !== -1) { + timeOut = Math.round(TIMEOUT_TRIES_RATES); + } else if (link.indexOf('/internet') !== -1) { + timeOut = Math.round(TIMEOUT_TRIES_INTERNET); + } + if (link.indexOf('solana.trusteeglobal.com') !== -1) { + timeOut = timeOut * 10; + } + } + + if (typeof CACHE_STARTED[cacheMD] !== 'undefined') { + const now = new Date().getTime(); + const timeMsg = + ' timeout ' + + CACHE_STARTED[cacheMD].timeOut + + ' started ' + + CACHE_STARTED[cacheMD].time + + ' diff ' + + (now - CACHE_STARTED[cacheMD].time); + BlocksoftCryptoLog.log('PREV CALL WILL BE CANCELED ' + timeMsg); + await CACHE_STARTED_CANCEL[cacheMD].cancel( + 'PREV CALL CANCELED ' + timeMsg + ); + } + instance.defaults.timeout = timeOut; + instance.defaults.cancelToken = cancelSource.token; + CACHE_STARTED[cacheMD] = { time: new Date().getTime(), timeOut }; + CACHE_STARTED_CANCEL[cacheMD] = cancelSource; + + const tmpTimer = setTimeout(() => { + cancelSource.cancel( + 'TIMEOUT CANCELED ' + + timeOut + + ' ' + + link + + ' ' + + JSON.stringify(data) + ); + }, timeOut); + if (method === 'get') { + tmp = await instance.get(link); + } else { + tmp = await instance.post(link, data); + } + clearTimeout(tmpTimer); + + if (emptyIsBad && (tmp.status !== 200 || !tmp.data)) { + // noinspection ExceptionCaughtLocallyJS + throw new Error( + 'BlocksoftAxios.' + + method + + ' ' + + link + + ' status: ' + + tmp.status + + ' data: ' + + tmp.data + ); + } + + if (typeof CACHE_STARTED[cacheMD] !== 'undefined') { + delete CACHE_STARTED[cacheMD]; + delete CACHE_STARTED_CANCEL[cacheMD]; + } + /* let txt = tmp.data if (typeof txt === 'string') { const newTxt = txt.split(' 1) { @@ -367,100 +443,175 @@ class BlocksoftAxios { } */ - CACHE_TIMEOUT_ERRORS = 0 - } catch (e) { - - if (typeof CACHE_STARTED[cacheMD] !== 'undefined') { - delete CACHE_STARTED[cacheMD] - } - let subdata = {} - if (typeof e.response === 'undefined' || typeof e.response.data === 'undefined') { - // do nothing - } else if (e.response.data) { - e.message = JSON.stringify(e.response.data) + ' ' + e.message - subdata = e.response.data - } - - const customError = new Error(link + ' ' + e.message.toLowerCase()) - customError.subdata = subdata - - if (config.debug.appErrors) { - // console.log('BlocksoftAxios._request ' + link + ' data ' + JSON.stringify(data) , e) - } - - if (e.message.indexOf('Network Error') !== -1 - || e.message.indexOf('network error') !== -1 - || e.message.indexOf('timeout') !== -1 - || e.message.indexOf('access is denied') !== -1 - || e.message.indexOf('500 internal') !== -1 - || e.message.indexOf('502') !== -1 - || e.message.indexOf('bad gateway') !== -1 - || e.message.indexOf('503 backend') !== -1 - || e.message.indexOf('error 503') !== -1 - || e.message.indexOf('504 gateway') !== -1 - || e.message.indexOf('server error') !== -1 - || e.message.indexOf('forbidden') !== -1 - || e.message.indexOf('unavailable') !== -1 - || e.message.indexOf('rate limit') !== -1 - || e.message.indexOf('offline') !== -1 - || e.message.indexOf('status code 500') !== -1 + CACHE_TIMEOUT_ERRORS = 0; + } catch (e) { + if (typeof CACHE_STARTED[cacheMD] !== 'undefined') { + delete CACHE_STARTED[cacheMD]; + } + let subdata = {}; + if ( + typeof e.response === 'undefined' || + typeof e.response.data === 'undefined' + ) { + // do nothing + } else if (e.response.data) { + e.message = JSON.stringify(e.response.data) + ' ' + e.message; + subdata = e.response.data; + } + + const customError = new Error(link + ' ' + e.message.toLowerCase()); + customError.subdata = subdata; + + if (config.debug.appErrors) { + // console.log('BlocksoftAxios._request ' + link + ' data ' + JSON.stringify(data) , e) + } + + if ( + e.message.indexOf('Network Error') !== -1 || + e.message.indexOf('network error') !== -1 || + e.message.indexOf('timeout') !== -1 || + e.message.indexOf('access is denied') !== -1 || + e.message.indexOf('500 internal') !== -1 || + e.message.indexOf('502') !== -1 || + e.message.indexOf('bad gateway') !== -1 || + e.message.indexOf('503 backend') !== -1 || + e.message.indexOf('error 503') !== -1 || + e.message.indexOf('504 gateway') !== -1 || + e.message.indexOf('server error') !== -1 || + e.message.indexOf('forbidden') !== -1 || + e.message.indexOf('unavailable') !== -1 || + e.message.indexOf('rate limit') !== -1 || + e.message.indexOf('offline') !== -1 || + e.message.indexOf('status code 500') !== -1 + ) { + if (link.indexOf('trustee.deals') !== -1) { + // noinspection ES6MissingAwait + BlocksoftCryptoLog.log( + 'BlocksoftAxios.' + + method + + ' ' + + link + + ' NOTICE INNER CONNECTION ' + + e.message + ); + } else { + if (e.message.indexOf('timeout') !== -1) { + CACHE_TIMEOUT_ERRORS++; + let now = new Date().getTime(); + if ( + CACHE_TIMEOUT_ERRORS > 10 && + now - CACHE_TIMEOUT_ERROR_SHOWN > 60000 ) { - if (link.indexOf('trustee.deals') !== -1) { - // noinspection ES6MissingAwait - BlocksoftCryptoLog.log('BlocksoftAxios.' + method + ' ' + link + ' NOTICE INNER CONNECTION ' + e.message) - } else { - if (e.message.indexOf('timeout') !== -1) { - CACHE_TIMEOUT_ERRORS++ - let now = new Date().getTime() - if (CACHE_TIMEOUT_ERRORS > 10 && (now - CACHE_TIMEOUT_ERROR_SHOWN) > 60000) { - CACHE_TIMEOUT_ERROR_SHOWN = now - showModal({ - type: 'INFO_MODAL', - icon: null, - title: strings('modal.exchange.sorry'), - description: strings('toast.badInternet') - }) - } - } - // noinspection ES6MissingAwait - BlocksoftCryptoLog.log('BlocksoftAxios.' + method + ' ' + link + ' NOTICE OUTER CONNECTION ' + e.message) - } - customError.code = 'ERROR_NOTICE' - } else if (e.message.indexOf('request failed with status code 525') !== -1 - || e.message.indexOf('api calls limits have been reached') !== -1 - || e.message.indexOf('loudflare') !== -1) { - // noinspection ES6MissingAwait - BlocksoftCryptoLog.log('BlocksoftAxios.' + method + ' ' + link + ' NOTICE TOO MUCH ' + e.message) - customError.code = 'ERROR_NOTICE' - } else if (link.indexOf('/api/v2/sendtx/') !== -1) { - // noinspection ES6MissingAwait - BlocksoftCryptoLog.log('BlocksoftAxios.' + method + ' ' + link + ' ' + e.message + ' GET EXTERNAL LINK ERROR1 ') - customError.code = 'ERROR_NOTICE' - } else if (e.message.indexOf('account not found') !== -1) { - // noinspection ES6MissingAwait - BlocksoftCryptoLog.log('BlocksoftAxios.' + method + ' ' + link + ' ' + e.message) // just nothing found - return false - } else if (errSend) { - // noinspection ES6MissingAwait - if (e.message.indexOf('PREV CALL CANCELED') === -1) { - if (link.indexOf('trustee.deals') !== -1 || link.indexOf('https://api.mainnet-beta.solana.com') !== -1) { - BlocksoftCryptoLog.log('BlocksoftAxios.' + method + ' ' + link + ' ' + e.message + ' GET EXTERNAL LINK ERROR3 ' + JSON.stringify(data)) - } else { - BlocksoftCryptoLog.err('BlocksoftAxios.' + method + ' ' + link + ' ' + e.message + ' GET EXTERNAL LINK ERROR2') - } - } else { - BlocksoftCryptoLog.log('BlocksoftAxios.' + method + ' ' + link + ' ' + e.message + ' GET EXTERNAL LINK ERROR4') - } - customError.code = 'ERROR_SYSTEM' - } else { - BlocksoftCryptoLog.log('BlocksoftAxios.' + method + ' ' + link + ' ' + e.message + ' GET EXTERNAL LINK NOTICE') - customError.code = 'ERROR_SYSTEM' + CACHE_TIMEOUT_ERROR_SHOWN = now; + showModal({ + type: 'INFO_MODAL', + icon: null, + title: strings('modal.exchange.sorry'), + description: strings('toast.badInternet') + }); } - - throw customError + } + // noinspection ES6MissingAwait + BlocksoftCryptoLog.log( + 'BlocksoftAxios.' + + method + + ' ' + + link + + ' NOTICE OUTER CONNECTION ' + + e.message + ); + } + customError.code = 'ERROR_NOTICE'; + } else if ( + e.message.indexOf('request failed with status code 525') !== -1 || + e.message.indexOf('api calls limits have been reached') !== -1 || + e.message.indexOf('loudflare') !== -1 + ) { + // noinspection ES6MissingAwait + BlocksoftCryptoLog.log( + 'BlocksoftAxios.' + + method + + ' ' + + link + + ' NOTICE TOO MUCH ' + + e.message + ); + customError.code = 'ERROR_NOTICE'; + } else if (link.indexOf('/api/v2/sendtx/') !== -1) { + // noinspection ES6MissingAwait + BlocksoftCryptoLog.log( + 'BlocksoftAxios.' + + method + + ' ' + + link + + ' ' + + e.message + + ' GET EXTERNAL LINK ERROR1 ' + ); + customError.code = 'ERROR_NOTICE'; + } else if (e.message.indexOf('account not found') !== -1) { + // noinspection ES6MissingAwait + BlocksoftCryptoLog.log( + 'BlocksoftAxios.' + method + ' ' + link + ' ' + e.message + ); // just nothing found + return false; + } else if (errSend) { + // noinspection ES6MissingAwait + if (e.message.indexOf('PREV CALL CANCELED') === -1) { + if ( + link.indexOf('trustee.deals') !== -1 || + link.indexOf('https://api.mainnet-beta.solana.com') !== -1 + ) { + BlocksoftCryptoLog.log( + 'BlocksoftAxios.' + + method + + ' ' + + link + + ' ' + + e.message + + ' GET EXTERNAL LINK ERROR3 ' + + JSON.stringify(data) + ); + } else { + BlocksoftCryptoLog.err( + 'BlocksoftAxios.' + + method + + ' ' + + link + + ' ' + + e.message + + ' GET EXTERNAL LINK ERROR2' + ); + } + } else { + BlocksoftCryptoLog.log( + 'BlocksoftAxios.' + + method + + ' ' + + link + + ' ' + + e.message + + ' GET EXTERNAL LINK ERROR4' + ); } - return tmp + customError.code = 'ERROR_SYSTEM'; + } else { + BlocksoftCryptoLog.log( + 'BlocksoftAxios.' + + method + + ' ' + + link + + ' ' + + e.message + + ' GET EXTERNAL LINK NOTICE' + ); + customError.code = 'ERROR_SYSTEM'; + } + + throw customError; } + return tmp; + } } -export default new BlocksoftAxios() +export default new BlocksoftAxios(); diff --git a/crypto/common/BlocksoftBN.js b/crypto/common/BlocksoftBN.js index 6d5a84029..e9b2bbaa5 100644 --- a/crypto/common/BlocksoftBN.js +++ b/crypto/common/BlocksoftBN.js @@ -1,98 +1,111 @@ -import {BigNumber} from 'bignumber.js' -import BlocksoftUtils from './BlocksoftUtils' +import { BigNumber } from 'bignumber.js'; +import BlocksoftUtils from './BlocksoftUtils'; class BlocksoftBN { + innerBN = false; - innerBN = false - - constructor(val) { - // console.log('BlocksoftBN construct', JSON.stringify(val)) - if (typeof val.innerBN !== 'undefined') { - try { - // noinspection JSCheckFunctionSignatures,JSUnresolvedVariable - this.innerBN = new BigNumber(val.innerBN.toString()) - } catch (e) { - throw new Error(e.message + ' while BlocksoftBN.constructor ' + val) - } - } else { - try { - // noinspection JSCheckFunctionSignatures,JSUnresolvedVariable - this.innerBN = new BigNumber(val) - } catch (e) { - throw new Error(e.message + ' while BlocksoftBN.constructor ' + val) - } - } + constructor(val) { + // console.log('BlocksoftBN construct', JSON.stringify(val)) + if (typeof val.innerBN !== 'undefined') { + try { + // noinspection JSCheckFunctionSignatures,JSUnresolvedVariable + this.innerBN = new BigNumber(val.innerBN.toString()); + } catch (e) { + throw new Error(e.message + ' while BlocksoftBN.constructor ' + val); + } + } else { + try { + // noinspection JSCheckFunctionSignatures,JSUnresolvedVariable + this.innerBN = new BigNumber(val); + } catch (e) { + throw new Error(e.message + ' while BlocksoftBN.constructor ' + val); + } } + } + get() { + return this.innerBN.toString(); + } - get() { - return this.innerBN.toString() - } + toString() { + return this.innerBN.toString(); + } - toString() { - return this.innerBN.toString() - } + lessThanZero() { + return this.innerBN.toString().indexOf('-') === 0; + } - lessThanZero() { - return this.innerBN.toString().indexOf('-') === 0 + add(val) { + // console.log('BlocksoftBN add ', JSON.stringify(val)) + if ( + typeof val === 'undefined' || + !val || + val.toString() === '0' || + val === 'null' || + val === 'false' + ) { + return this; } - - add(val) { - // console.log('BlocksoftBN add ', JSON.stringify(val)) - if (typeof val === 'undefined' || !val || val.toString() === '0' || val === 'null' || val === 'false') { - return this - } - let val2 - if (typeof val !== 'string' && typeof val !== 'number') { - if (typeof val.innerBN !== 'undefined') { - val2 = val.innerBN - } else { - throw new Error('BlocksoftBN.add unsupported type ' + (typeof val) + ' ' + JSON.stringify(val)) - } - } else { - try { - val = BlocksoftUtils.fromENumber(val) - val2 = BigNumber(val) - } catch (e) { - throw new Error(e.message + ' while BlocksoftBN.add transform ' + val) - } - } - try { - this.innerBN = this.innerBN.plus(val2) - } catch (e) { - throw new Error(e.message + ' while BlocksoftBN.add ' + val) - } - return this + let val2; + if (typeof val !== 'string' && typeof val !== 'number') { + if (typeof val.innerBN !== 'undefined') { + val2 = val.innerBN; + } else { + throw new Error( + 'BlocksoftBN.add unsupported type ' + + typeof val + + ' ' + + JSON.stringify(val) + ); + } + } else { + try { + val = BlocksoftUtils.fromENumber(val); + val2 = BigNumber(val); + } catch (e) { + throw new Error(e.message + ' while BlocksoftBN.add transform ' + val); + } } - - diff(val) { - // console.log('BlocksoftBN diff ', JSON.stringify(val)) - if (typeof val === 'undefined' || !val || val.toString() === '0') { - return this - } - let val2 - if (typeof val !== 'string' && typeof val !== 'number') { - if (typeof val.innerBN !== 'undefined') { - val2 = val.innerBN - } else { - throw new Error('BlocksoftBN.diff unsupported type ' + (typeof val) + ' ' + JSON.stringify(val)) - } - } else { - try { - val = BlocksoftUtils.fromENumber(val) - val2 = BigNumber(val) - } catch (e) { - throw new Error(e.message + ' while BlocksoftBN.diff transform ' + val) - } - } - try { - this.innerBN = this.innerBN.minus(val2) - } catch (e) { - throw new Error(e.message + ' while BlocksoftBN.minus ' + val) - } - return this + try { + this.innerBN = this.innerBN.plus(val2); + } catch (e) { + throw new Error(e.message + ' while BlocksoftBN.add ' + val); } + return this; + } + diff(val) { + // console.log('BlocksoftBN diff ', JSON.stringify(val)) + if (typeof val === 'undefined' || !val || val.toString() === '0') { + return this; + } + let val2; + if (typeof val !== 'string' && typeof val !== 'number') { + if (typeof val.innerBN !== 'undefined') { + val2 = val.innerBN; + } else { + throw new Error( + 'BlocksoftBN.diff unsupported type ' + + typeof val + + ' ' + + JSON.stringify(val) + ); + } + } else { + try { + val = BlocksoftUtils.fromENumber(val); + val2 = BigNumber(val); + } catch (e) { + throw new Error(e.message + ' while BlocksoftBN.diff transform ' + val); + } + } + try { + this.innerBN = this.innerBN.minus(val2); + } catch (e) { + throw new Error(e.message + ' while BlocksoftBN.minus ' + val); + } + return this; + } } -export default BlocksoftBN +export default BlocksoftBN; diff --git a/crypto/common/BlocksoftCustomLinks.js b/crypto/common/BlocksoftCustomLinks.js index 4e8f2655d..5fd33561c 100644 --- a/crypto/common/BlocksoftCustomLinks.js +++ b/crypto/common/BlocksoftCustomLinks.js @@ -2,28 +2,25 @@ * @version 0.54 * @author Vadym */ -import { ThemeContext } from '@app/theme/ThemeProvider' -import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings' -import { sublocale } from '@app/services/i18n' - +import { ThemeContext } from '@app/theme/ThemeProvider'; +import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings'; +import { sublocale } from '@app/services/i18n'; class BlocksoftCustomLinks { - getLink(link, isLight) { - const themeColor = isLight ? 'light' : 'dark' - const siteLang = sublocale() - const paramsLink = `themeColor=${themeColor}&siteLang=${siteLang}` - const subLink = BlocksoftExternalSettings.getStatic(link) + getLink(link, isLight) { + const themeColor = isLight ? 'light' : 'dark'; + const siteLang = sublocale(); + const paramsLink = `themeColor=${themeColor}&siteLang=${siteLang}`; + const subLink = BlocksoftExternalSettings.getStatic(link); - if (subLink.includes('?')) { - return `${subLink}&${paramsLink}` - } else { - return `${subLink}?${paramsLink}` - } + if (subLink.includes('?')) { + return `${subLink}&${paramsLink}`; + } else { + return `${subLink}?${paramsLink}`; } + } } -export default new BlocksoftCustomLinks() - -BlocksoftCustomLinks.contextType = ThemeContext - +export default new BlocksoftCustomLinks(); +BlocksoftCustomLinks.contextType = ThemeContext; diff --git a/crypto/common/BlocksoftPrivateKeysUtils.js b/crypto/common/BlocksoftPrivateKeysUtils.js index abae117c2..e57153f75 100644 --- a/crypto/common/BlocksoftPrivateKeysUtils.js +++ b/crypto/common/BlocksoftPrivateKeysUtils.js @@ -1,100 +1,166 @@ /** * @version 0.52 */ -import BlocksoftKeysStorage from '@crypto/actions/BlocksoftKeysStorage/BlocksoftKeysStorage' -import BlocksoftKeys from '@crypto/actions/BlocksoftKeys/BlocksoftKeys' -import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog' -import config from '@app/config/config' +import BlocksoftKeysStorage from '@crypto/actions/BlocksoftKeysStorage/BlocksoftKeysStorage'; +import BlocksoftKeys from '@crypto/actions/BlocksoftKeys/BlocksoftKeys'; +import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; +import config from '@app/config/config'; -const CACHE = [] +const CACHE = []; export default { - /** - * @param discoverFor.mnemonic - * @param discoverFor.derivationPath - * @param discoverFor.derivationIndex - * @param discoverFor.derivationType - * @param discoverFor.path - * @param discoverFor.currencyCode - * @param discoverFor.walletHash - * @param discoverFor.addressToCheck - * @returns {Promise<{privateKey: string}>} - */ - async getPrivateKey(discoverFor, source) { - const path = typeof discoverFor.path !== 'undefined' && discoverFor.path ? discoverFor.path : discoverFor.derivationPath - if (path === 'false' || !path) { - await BlocksoftCryptoLog.log('BlocksoftTransferPrivateKeysDiscover private key not discovered as path = false from ' + source) - } - const discoverForKey = BlocksoftKeysStorage.getAddressCacheKey(discoverFor.walletHash, path.replace(/[']/g, 'quote'), discoverFor.currencyCode) - await BlocksoftCryptoLog.log('BlocksoftTransferPrivateKeysDiscover.getPrivateKey actually inited ', { address: discoverFor.addressToCheck, path, discoverForKey }) - let result = CACHE[discoverForKey] - if (result) { - return result - } - result = await BlocksoftKeysStorage.getAddressCache(discoverForKey) - if (result) { - CACHE[discoverForKey] = result - return result - } - - try { - if (typeof discoverFor.mnemonic === 'undefined' || !discoverFor.mnemonic) { - await BlocksoftCryptoLog.log('BlocksoftTransferPrivateKeysDiscover.getPrivateKey actually redo mnemonic ' + discoverFor.walletHash), - discoverFor.mnemonic = await BlocksoftKeysStorage.getWalletMnemonic(discoverFor.walletHash, 'getPrivateKey') - } - result = await BlocksoftKeys.discoverOne(discoverFor) - if (discoverFor.addressToCheck && discoverFor.addressToCheck !== result.address) { + /** + * @param discoverFor.mnemonic + * @param discoverFor.derivationPath + * @param discoverFor.derivationIndex + * @param discoverFor.derivationType + * @param discoverFor.path + * @param discoverFor.currencyCode + * @param discoverFor.walletHash + * @param discoverFor.addressToCheck + * @returns {Promise<{privateKey: string}>} + */ + async getPrivateKey(discoverFor, source) { + const path = + typeof discoverFor.path !== 'undefined' && discoverFor.path + ? discoverFor.path + : discoverFor.derivationPath; + if (path === 'false' || !path) { + await BlocksoftCryptoLog.log( + 'BlocksoftTransferPrivateKeysDiscover private key not discovered as path = false from ' + + source + ); + } + const discoverForKey = BlocksoftKeysStorage.getAddressCacheKey( + discoverFor.walletHash, + path.replace(/[']/g, 'quote'), + discoverFor.currencyCode + ); + await BlocksoftCryptoLog.log( + 'BlocksoftTransferPrivateKeysDiscover.getPrivateKey actually inited ', + { address: discoverFor.addressToCheck, path, discoverForKey } + ); + let result = CACHE[discoverForKey]; + if (result) { + return result; + } + result = await BlocksoftKeysStorage.getAddressCache(discoverForKey); + if (result) { + CACHE[discoverForKey] = result; + return result; + } - await BlocksoftCryptoLog.log('BlocksoftTransferPrivateKeysDiscover private key discovered is not for address you path=' + discoverFor.derivationPath + ' set ' + result.address + '!=' + discoverFor.addressToCheck + ' key=' + discoverForKey + ' from ' + source) + try { + if ( + typeof discoverFor.mnemonic === 'undefined' || + !discoverFor.mnemonic + ) { + await BlocksoftCryptoLog.log( + 'BlocksoftTransferPrivateKeysDiscover.getPrivateKey actually redo mnemonic ' + + discoverFor.walletHash + ), + (discoverFor.mnemonic = await BlocksoftKeysStorage.getWalletMnemonic( + discoverFor.walletHash, + 'getPrivateKey' + )); + } + result = await BlocksoftKeys.discoverOne(discoverFor); + if ( + discoverFor.addressToCheck && + discoverFor.addressToCheck !== result.address + ) { + await BlocksoftCryptoLog.log( + 'BlocksoftTransferPrivateKeysDiscover private key discovered is not for address you path=' + + discoverFor.derivationPath + + ' set ' + + result.address + + '!=' + + discoverFor.addressToCheck + + ' key=' + + discoverForKey + + ' from ' + + source + ); - const tmpPath = [ - `m/84'/0'/0'/0/0`, - `m/84'/0'/0'/0/1`, - `m/44'/0'/0'/0/0`, - `m/44'/0'/0'/0/1`, - `m/49'/0'/0'/0/0`, - `m/49'/0'/0'/0/1`, - `m/44'/0`, - `m/44'/1`, - `m/49'/0`, - `m/49'/1`, - `m/84'/0`, - `m/84'/1`, - `m/0` - ] - let tmpFound = false - for (const path of tmpPath) { - const clone = JSON.parse(JSON.stringify(discoverFor)) - clone.derivationPath = path - const result2 = await BlocksoftKeys.discoverOne(clone) - if (discoverFor.addressToCheck === result2.address) { - await BlocksoftCryptoLog.log('BlocksoftTransferPrivateKeysDiscover private key rediscovered FOUND address path=' + clone.derivationPath + ' set ' + result2.address + '=' + discoverFor.addressToCheck + ' from ' + source) - result = result2 - tmpFound = true - break - } else { - await BlocksoftCryptoLog.log('BlocksoftTransferPrivateKeysDiscover private key rediscovered is not for address path=' + clone.derivationPath + ' set ' + result2.address + '!=' + discoverFor.addressToCheck + ' from ' + source) - } - } - if (!tmpFound) { - throw new Error('invalid address') - } - } - await BlocksoftKeysStorage.setAddressCache(discoverForKey, result) - } catch (e) { - if (config.debug.appErrors) { - console.log('BlocksoftTransferPrivateKeysDiscover private key error ' + e.message + ' from ' + source, e) - } - const clone = JSON.parse(JSON.stringify(discoverFor)) - const msg = e.message.toString().replace(discoverFor.mnemonic, '***') - if (clone.mnemonic === '***') { - clone.mnemonic = '*** already masked ***' - } else { - clone.mnemonic = '***' - } - throw new Error('BlocksoftTransferPrivateKeysDiscover private key error ' + msg + ' from ' + source + ' this._data=' + JSON.stringify(clone)) + const tmpPath = [ + `m/84'/0'/0'/0/0`, + `m/84'/0'/0'/0/1`, + `m/44'/0'/0'/0/0`, + `m/44'/0'/0'/0/1`, + `m/49'/0'/0'/0/0`, + `m/49'/0'/0'/0/1`, + `m/44'/0`, + `m/44'/1`, + `m/49'/0`, + `m/49'/1`, + `m/84'/0`, + `m/84'/1`, + `m/0` + ]; + let tmpFound = false; + for (const path of tmpPath) { + const clone = JSON.parse(JSON.stringify(discoverFor)); + clone.derivationPath = path; + const result2 = await BlocksoftKeys.discoverOne(clone); + if (discoverFor.addressToCheck === result2.address) { + await BlocksoftCryptoLog.log( + 'BlocksoftTransferPrivateKeysDiscover private key rediscovered FOUND address path=' + + clone.derivationPath + + ' set ' + + result2.address + + '=' + + discoverFor.addressToCheck + + ' from ' + + source + ); + result = result2; + tmpFound = true; + break; + } else { + await BlocksoftCryptoLog.log( + 'BlocksoftTransferPrivateKeysDiscover private key rediscovered is not for address path=' + + clone.derivationPath + + ' set ' + + result2.address + + '!=' + + discoverFor.addressToCheck + + ' from ' + + source + ); + } + } + if (!tmpFound) { + throw new Error('invalid address'); } - CACHE[discoverForKey] = result - return result + } + await BlocksoftKeysStorage.setAddressCache(discoverForKey, result); + } catch (e) { + if (config.debug.appErrors) { + console.log( + 'BlocksoftTransferPrivateKeysDiscover private key error ' + + e.message + + ' from ' + + source, + e + ); + } + const clone = JSON.parse(JSON.stringify(discoverFor)); + const msg = e.message.toString().replace(discoverFor.mnemonic, '***'); + if (clone.mnemonic === '***') { + clone.mnemonic = '*** already masked ***'; + } else { + clone.mnemonic = '***'; + } + throw new Error( + 'BlocksoftTransferPrivateKeysDiscover private key error ' + + msg + + ' from ' + + source + + ' this._data=' + + JSON.stringify(clone) + ); } -} + CACHE[discoverForKey] = result; + return result; + } +}; diff --git a/crypto/services/Web3Injected.js b/crypto/services/Web3Injected.js index a35c48765..193d852a5 100644 --- a/crypto/services/Web3Injected.js +++ b/crypto/services/Web3Injected.js @@ -1,87 +1,106 @@ /** * @version 0.41 */ -import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings' +import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings'; -const Web3 = require('web3') -const CACHE_WEB3 = {} +const Web3 = require('web3'); +const CACHE_WEB3 = {}; export const Web3Injected = (type) => { - let WEB3_LINK = `https://mainnet.infura.io/v3/${BlocksoftExternalSettings.getStatic('ETH_INFURA')}` - let MAIN_CURRENCY_CODE = 'ETH' - let MAIN_CHAIN_ID = 1 - if (!type || type === 0 || type === 1 || type === 'ethereum' || type === 'ETH' || type === 'mainnet') { - MAIN_CURRENCY_CODE = 'ETH' - MAIN_CHAIN_ID = 1 - } else if (type === 10001 || type === 'eth_pow' || type === 'ETH_POW' || type === 'ETHW' || type === 'ethw') { - MAIN_CURRENCY_CODE = 'ETH_POW' - WEB3_LINK = BlocksoftExternalSettings.getStatic('ETH_POW_SERVER') - MAIN_CHAIN_ID = 10001 - } else if (type === 3 || type === 'ropsten' || type === 'ETH_ROPSTEN') { - MAIN_CURRENCY_CODE = 'ETH_ROPSTEN' - WEB3_LINK = `https://ropsten.infura.io/v3/${BlocksoftExternalSettings.getStatic('ETH_INFURA')}` - MAIN_CHAIN_ID = 3 - } else if (type === 4 || type === 'rinkeby' || type === 'ETH_RINKEBY') { - MAIN_CURRENCY_CODE = 'ETH_RINKEBY' - WEB3_LINK = `https://rinkeby.infura.io/v3/${BlocksoftExternalSettings.getStatic('ETH_INFURA')}` - MAIN_CHAIN_ID = 4 - } else if (type === 56 || type === 'bnb' || type === 'BNB_SMART') { - MAIN_CURRENCY_CODE = 'BNB_SMART' - WEB3_LINK = BlocksoftExternalSettings.getStatic('BNB_SMART_SERVER') - MAIN_CHAIN_ID = 56 - } else if (type === 1088 || type === 'METIS') { - MAIN_CURRENCY_CODE = 'METIS' - WEB3_LINK = BlocksoftExternalSettings.getStatic('METIS_SERVER') - MAIN_CHAIN_ID = 1088 - } else if (type === 199 || type === 'BTTC') { - MAIN_CURRENCY_CODE = 'BTTC' - WEB3_LINK = BlocksoftExternalSettings.getStatic('BTTC_SERVER') - MAIN_CHAIN_ID = 199 - } else if (type === 106 || type === 'VLX') { - MAIN_CURRENCY_CODE = 'VLX' - WEB3_LINK = BlocksoftExternalSettings.getStatic('VLX_SERVER') - MAIN_CHAIN_ID = 106 // https://docs.velas.com/clusters/ - } else if (type === 1666600000 || type === 'ONE') { - MAIN_CURRENCY_CODE = 'ONE' - WEB3_LINK = BlocksoftExternalSettings.getStatic('ONE_SERVER') - MAIN_CHAIN_ID = 1666600000 - } else if (type === 10 || type === 'OPTIMISM') { - MAIN_CURRENCY_CODE = 'OPTIMISM' - WEB3_LINK = BlocksoftExternalSettings.getStatic('OPTIMISM_SERVER') - MAIN_CHAIN_ID = 10 - } else if (type === 137 || type === 'MATIC') { - MAIN_CURRENCY_CODE = 'MATIC' - WEB3_LINK = BlocksoftExternalSettings.getStatic('MATIC_SERVER') - MAIN_CHAIN_ID = 137 - } else if (type === 250 || type === 'FTM') { - MAIN_CURRENCY_CODE = 'FTM' - WEB3_LINK = BlocksoftExternalSettings.getStatic('FTM_SERVER') - MAIN_CHAIN_ID = 250 - } else if (type === 16718 || type === 'AMB') { - MAIN_CURRENCY_CODE = 'AMB' - WEB3_LINK = BlocksoftExternalSettings.getStatic('AMB_SERVER') - MAIN_CHAIN_ID = 16718 - } else if (type === 61 || type === 'ETC') { - MAIN_CURRENCY_CODE = 'ETC' - WEB3_LINK = BlocksoftExternalSettings.getStatic('ETC_SERVER') - MAIN_CHAIN_ID = 61 - } else if (type === 30 || type === 'RSK') { - MAIN_CURRENCY_CODE = 'RSK' - WEB3_LINK = BlocksoftExternalSettings.getStatic('RSK_SERVER') - MAIN_CHAIN_ID = 30 - } else { - throw new Error('PLEASE ADD SUPPORT FOR ETH NETWORK ' + type) - } + let WEB3_LINK = `https://mainnet.infura.io/v3/${BlocksoftExternalSettings.getStatic( + 'ETH_INFURA' + )}`; + let MAIN_CURRENCY_CODE = 'ETH'; + let MAIN_CHAIN_ID = 1; + if ( + !type || + type === 0 || + type === 1 || + type === 'ethereum' || + type === 'ETH' || + type === 'mainnet' + ) { + MAIN_CURRENCY_CODE = 'ETH'; + MAIN_CHAIN_ID = 1; + } else if ( + type === 10001 || + type === 'eth_pow' || + type === 'ETH_POW' || + type === 'ETHW' || + type === 'ethw' + ) { + MAIN_CURRENCY_CODE = 'ETH_POW'; + WEB3_LINK = BlocksoftExternalSettings.getStatic('ETH_POW_SERVER'); + MAIN_CHAIN_ID = 10001; + } else if (type === 3 || type === 'ropsten' || type === 'ETH_ROPSTEN') { + MAIN_CURRENCY_CODE = 'ETH_ROPSTEN'; + WEB3_LINK = `https://ropsten.infura.io/v3/${BlocksoftExternalSettings.getStatic( + 'ETH_INFURA' + )}`; + MAIN_CHAIN_ID = 3; + } else if (type === 4 || type === 'rinkeby' || type === 'ETH_RINKEBY') { + MAIN_CURRENCY_CODE = 'ETH_RINKEBY'; + WEB3_LINK = `https://rinkeby.infura.io/v3/${BlocksoftExternalSettings.getStatic( + 'ETH_INFURA' + )}`; + MAIN_CHAIN_ID = 4; + } else if (type === 56 || type === 'bnb' || type === 'BNB_SMART') { + MAIN_CURRENCY_CODE = 'BNB_SMART'; + WEB3_LINK = BlocksoftExternalSettings.getStatic('BNB_SMART_SERVER'); + MAIN_CHAIN_ID = 56; + } else if (type === 1088 || type === 'METIS') { + MAIN_CURRENCY_CODE = 'METIS'; + WEB3_LINK = BlocksoftExternalSettings.getStatic('METIS_SERVER'); + MAIN_CHAIN_ID = 1088; + } else if (type === 199 || type === 'BTTC') { + MAIN_CURRENCY_CODE = 'BTTC'; + WEB3_LINK = BlocksoftExternalSettings.getStatic('BTTC_SERVER'); + MAIN_CHAIN_ID = 199; + } else if (type === 106 || type === 'VLX') { + MAIN_CURRENCY_CODE = 'VLX'; + WEB3_LINK = BlocksoftExternalSettings.getStatic('VLX_SERVER'); + MAIN_CHAIN_ID = 106; // https://docs.velas.com/clusters/ + } else if (type === 1666600000 || type === 'ONE') { + MAIN_CURRENCY_CODE = 'ONE'; + WEB3_LINK = BlocksoftExternalSettings.getStatic('ONE_SERVER'); + MAIN_CHAIN_ID = 1666600000; + } else if (type === 10 || type === 'OPTIMISM') { + MAIN_CURRENCY_CODE = 'OPTIMISM'; + WEB3_LINK = BlocksoftExternalSettings.getStatic('OPTIMISM_SERVER'); + MAIN_CHAIN_ID = 10; + } else if (type === 137 || type === 'MATIC') { + MAIN_CURRENCY_CODE = 'MATIC'; + WEB3_LINK = BlocksoftExternalSettings.getStatic('MATIC_SERVER'); + MAIN_CHAIN_ID = 137; + } else if (type === 250 || type === 'FTM') { + MAIN_CURRENCY_CODE = 'FTM'; + WEB3_LINK = BlocksoftExternalSettings.getStatic('FTM_SERVER'); + MAIN_CHAIN_ID = 250; + } else if (type === 16718 || type === 'AMB') { + MAIN_CURRENCY_CODE = 'AMB'; + WEB3_LINK = BlocksoftExternalSettings.getStatic('AMB_SERVER'); + MAIN_CHAIN_ID = 16718; + } else if (type === 61 || type === 'ETC') { + MAIN_CURRENCY_CODE = 'ETC'; + WEB3_LINK = BlocksoftExternalSettings.getStatic('ETC_SERVER'); + MAIN_CHAIN_ID = 61; + } else if (type === 30 || type === 'RSK') { + MAIN_CURRENCY_CODE = 'RSK'; + WEB3_LINK = BlocksoftExternalSettings.getStatic('RSK_SERVER'); + MAIN_CHAIN_ID = 30; + } else { + throw new Error('PLEASE ADD SUPPORT FOR ETH NETWORK ' + type); + } - if (typeof CACHE_WEB3[WEB3_LINK] !== 'undefined') { - return CACHE_WEB3[WEB3_LINK] - } + if (typeof CACHE_WEB3[WEB3_LINK] !== 'undefined') { + return CACHE_WEB3[WEB3_LINK]; + } - const WEB3 = new Web3(new Web3.providers.HttpProvider(WEB3_LINK)) - WEB3.MAIN_CURRENCY_CODE = MAIN_CURRENCY_CODE - WEB3.LINK = WEB3_LINK - WEB3.MAIN_CHAIN_ID = MAIN_CHAIN_ID - CACHE_WEB3[WEB3_LINK] = WEB3 + const WEB3 = new Web3(new Web3.providers.HttpProvider(WEB3_LINK)); + WEB3.MAIN_CURRENCY_CODE = MAIN_CURRENCY_CODE; + WEB3.LINK = WEB3_LINK; + WEB3.MAIN_CHAIN_ID = MAIN_CHAIN_ID; + CACHE_WEB3[WEB3_LINK] = WEB3; - return WEB3 -} + return WEB3; +}; diff --git a/package.json b/package.json index 5202205eb..9ba4f6244 100644 --- a/package.json +++ b/package.json @@ -33,18 +33,23 @@ "@react-navigation/native": "^6.1.6", "@react-navigation/native-stack": "^6.9.12", "@shopify/react-native-skia": "0.1.183", + "@solana/web3.js": "git+https://git@github.com/trustee-wallet/solana-web3.js.git", "@tanstack/react-query": "^4.28.0", "@testing-library/jest-dom": "^5.16.5", "@testing-library/react-native": "^12.1.1", "@types/jest": "^29.5.1", "@waves/ts-lib-crypto": "^1.4.4-beta.1", "axios": "^1.3.4", + "bech32": "^2.0.0", + "bignumber.js": "^9.1.1", "bip32": "^4.0.0", "bip39": "^3.1.0", "bitcoinjs-lib": "git+https://git@github.com/trustee-wallet/bitcoinjs-lib", + "bn.js": "^5.2.1", "bs58check": "^3.0.1", "buffer": "^6.0.3", "create-hash": "^1.2.0", + "elliptic": "^6.5.4", "expo": "~48.0.18", "expo-barcode-scanner": "~12.3.2", "expo-build-properties": "~0.6.0", @@ -69,6 +74,7 @@ "patch-package": "^7.0.0", "postinstall-postinstall": "^2.1.0", "process": "^0.11.10", + "protocol-buffers-encodings": "^1.2.0", "react": "18.2.0", "react-native": "0.71.8", "react-native-calendar-picker": "^7.1.4", @@ -85,7 +91,9 @@ "react-native-tab-view": "^3.5.1", "react-native-view-shot": "3.5.0", "react-native-walkthrough-tooltip": "^1.5.0", - "use-context-selector": "^1.4.1" + "tiny-secp256k1": "^2.2.3", + "use-context-selector": "^1.4.1", + "web3": "^4.0.3" }, "devDependencies": { "@babel/core": "^7.20.0", diff --git a/yarn.lock b/yarn.lock index 1f49806eb..8d96915f8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12,6 +12,11 @@ resolved "https://registry.yarnpkg.com/@adobe/css-tools/-/css-tools-4.2.0.tgz#e1a84fca468f4b337816fcb7f0964beb620ba855" integrity sha512-E09FiIft46CmH5Qnjb0wsW54/YQd69LsxeKUOWawmws1XWvyFGURnAChH0mlr7YPFR1ofwvUQfcL0J3lMxXqPA== +"@adraffy/ens-normalize@^1.8.8": + version "1.9.4" + resolved "https://registry.yarnpkg.com/@adraffy/ens-normalize/-/ens-normalize-1.9.4.tgz#aae21cb858bbb0411949d5b7b3051f4209043f62" + integrity sha512-UK0bHA7hh9cR39V+4gl2/NnBBjoXIxkuWAPCaY4X7fbH4L/azIi7ilWOCjMUYfpJgraLUAqkRi2BqrjME8Rynw== + "@ampproject/remapping@^2.2.0": version "2.2.0" resolved "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz" @@ -1369,6 +1374,13 @@ dependencies: regenerator-runtime "^0.13.11" +"@babel/runtime@^7.12.5", "@babel/runtime@^7.17.2": + version "7.22.6" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.6.tgz#57d64b9ae3cff1d67eb067ae117dac087f5bd438" + integrity sha512-wDb5pWm4WDdF6LFUde3Jl8WzPA+3ZbxYqkC6xAXuD3irdEHN1k0NfTRrJD8ZD378SJ61miMLCqIOXYhd8x+AJQ== + dependencies: + regenerator-runtime "^0.13.11" + "@babel/runtime@^7.9.2": version "7.21.5" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.21.5.tgz#8492dddda9644ae3bda3b45eabe87382caee7200" @@ -1513,6 +1525,188 @@ resolved "https://registry.npmjs.org/@eslint/js/-/js-8.37.0.tgz" integrity sha512-x5vzdtOOGgFVDCUs81QRB2+liax8rFg3+7hqM+QhBG0/G3F1ZsoYl97UrqgHgQ9KKT7G6c4V+aTUCgu/n22v1A== +"@ethereumjs/rlp@^4.0.1": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@ethereumjs/rlp/-/rlp-4.0.1.tgz#626fabfd9081baab3d0a3074b0c7ecaf674aaa41" + integrity sha512-tqsQiBQDQdmPWE1xkkBq4rlSW5QZpLOUJ5RJh2/9fug+q9tnUhuZoVLk7s0scUIKTOzEtR72DFBXI4WiZcMpvw== + +"@ethersproject/abi@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/abi/-/abi-5.7.0.tgz#b3f3e045bbbeed1af3947335c247ad625a44e449" + integrity sha512-351ktp42TiRcYB3H1OP8yajPeAQstMW/yCFokj/AthP9bLHzQFPlOrxOcwYEDkUAICmOHljvN4K39OMTMUa9RA== + dependencies: + "@ethersproject/address" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/constants" "^5.7.0" + "@ethersproject/hash" "^5.7.0" + "@ethersproject/keccak256" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/strings" "^5.7.0" + +"@ethersproject/abstract-provider@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/abstract-provider/-/abstract-provider-5.7.0.tgz#b0a8550f88b6bf9d51f90e4795d48294630cb9ef" + integrity sha512-R41c9UkchKCpAqStMYUpdunjo3pkEvZC3FAwZn5S5MGbXoMQOHIdHItezTETxAO5bevtMApSyEhn9+CHcDsWBw== + dependencies: + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/networks" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/transactions" "^5.7.0" + "@ethersproject/web" "^5.7.0" + +"@ethersproject/abstract-signer@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/abstract-signer/-/abstract-signer-5.7.0.tgz#13f4f32117868452191a4649723cb086d2b596b2" + integrity sha512-a16V8bq1/Cz+TGCkE2OPMTOUDLS3grCpdjoJCYNnVBbdYEMSgKrU0+B90s8b6H+ByYTBZN7a3g76jdIJi7UfKQ== + dependencies: + "@ethersproject/abstract-provider" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + +"@ethersproject/address@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/address/-/address-5.7.0.tgz#19b56c4d74a3b0a46bfdbb6cfcc0a153fc697f37" + integrity sha512-9wYhYt7aghVGo758POM5nqcOMaE168Q6aRLJZwUmiqSrAungkG74gSSeKEIR7ukixesdRZGPgVqme6vmxs1fkA== + dependencies: + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/keccak256" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/rlp" "^5.7.0" + +"@ethersproject/base64@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/base64/-/base64-5.7.0.tgz#ac4ee92aa36c1628173e221d0d01f53692059e1c" + integrity sha512-Dr8tcHt2mEbsZr/mwTPIQAf3Ai0Bks/7gTw9dSqk1mQvhW3XvRlmDJr/4n+wg1JmCl16NZue17CDh8xb/vZ0sQ== + dependencies: + "@ethersproject/bytes" "^5.7.0" + +"@ethersproject/bignumber@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/bignumber/-/bignumber-5.7.0.tgz#e2f03837f268ba655ffba03a57853e18a18dc9c2" + integrity sha512-n1CAdIHRWjSucQO3MC1zPSVgV/6dy/fjL9pMrPP9peL+QxEg9wOsVqwD4+818B6LUEtaXzVHQiuivzRoxPxUGw== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + bn.js "^5.2.1" + +"@ethersproject/bytes@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/bytes/-/bytes-5.7.0.tgz#a00f6ea8d7e7534d6d87f47188af1148d71f155d" + integrity sha512-nsbxwgFXWh9NyYWo+U8atvmMsSdKJprTcICAkvbBffT75qDocbuggBU0SJiVK2MuTrp0q+xvLkTnGMPK1+uA9A== + dependencies: + "@ethersproject/logger" "^5.7.0" + +"@ethersproject/constants@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/constants/-/constants-5.7.0.tgz#df80a9705a7e08984161f09014ea012d1c75295e" + integrity sha512-DHI+y5dBNvkpYUMiRQyxRBYBefZkJfo70VUkUAsRjcPs47muV9evftfZ0PJVCXYbAiCgght0DtcF9srFQmIgWA== + dependencies: + "@ethersproject/bignumber" "^5.7.0" + +"@ethersproject/hash@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/hash/-/hash-5.7.0.tgz#eb7aca84a588508369562e16e514b539ba5240a7" + integrity sha512-qX5WrQfnah1EFnO5zJv1v46a8HW0+E5xuBBDTwMFZLuVTx0tbU2kkx15NqdjxecrLGatQN9FGQKpb1FKdHCt+g== + dependencies: + "@ethersproject/abstract-signer" "^5.7.0" + "@ethersproject/address" "^5.7.0" + "@ethersproject/base64" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/keccak256" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/strings" "^5.7.0" + +"@ethersproject/keccak256@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/keccak256/-/keccak256-5.7.0.tgz#3186350c6e1cd6aba7940384ec7d6d9db01f335a" + integrity sha512-2UcPboeL/iW+pSg6vZ6ydF8tCnv3Iu/8tUmLLzWWGzxWKFFqOBQFLo6uLUv6BDrLgCDfN28RJ/wtByx+jZ4KBg== + dependencies: + "@ethersproject/bytes" "^5.7.0" + js-sha3 "0.8.0" + +"@ethersproject/logger@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/logger/-/logger-5.7.0.tgz#6ce9ae168e74fecf287be17062b590852c311892" + integrity sha512-0odtFdXu/XHtjQXJYA3u9G0G8btm0ND5Cu8M7i5vhEcE8/HmF4Lbdqanwyv4uQTr2tx6b7fQRmgLrsnpQlmnig== + +"@ethersproject/networks@^5.7.0": + version "5.7.1" + resolved "https://registry.yarnpkg.com/@ethersproject/networks/-/networks-5.7.1.tgz#118e1a981d757d45ccea6bb58d9fd3d9db14ead6" + integrity sha512-n/MufjFYv3yFcUyfhnXotyDlNdFb7onmkSy8aQERi2PjNcnWQ66xXxa3XlS8nCcA8aJKJjIIMNJTC7tu80GwpQ== + dependencies: + "@ethersproject/logger" "^5.7.0" + +"@ethersproject/properties@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/properties/-/properties-5.7.0.tgz#a6e12cb0439b878aaf470f1902a176033067ed30" + integrity sha512-J87jy8suntrAkIZtecpxEPxY//szqr1mlBaYlQ0r4RCaiD2hjheqF9s1LVE8vVuJCXisjIP+JgtK/Do54ej4Sw== + dependencies: + "@ethersproject/logger" "^5.7.0" + +"@ethersproject/rlp@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/rlp/-/rlp-5.7.0.tgz#de39e4d5918b9d74d46de93af80b7685a9c21304" + integrity sha512-rBxzX2vK8mVF7b0Tol44t5Tb8gomOHkj5guL+HhzQ1yBh/ydjGnpw6at+X6Iw0Kp3OzzzkcKp8N9r0W4kYSs9w== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + +"@ethersproject/signing-key@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/signing-key/-/signing-key-5.7.0.tgz#06b2df39411b00bc57c7c09b01d1e41cf1b16ab3" + integrity sha512-MZdy2nL3wO0u7gkB4nA/pEf8lu1TlFswPNmy8AiYkfKTdO6eXBJyUdmHO/ehm/htHw9K/qF8ujnTyUAD+Ry54Q== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + bn.js "^5.2.1" + elliptic "6.5.4" + hash.js "1.1.7" + +"@ethersproject/strings@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/strings/-/strings-5.7.0.tgz#54c9d2a7c57ae8f1205c88a9d3a56471e14d5ed2" + integrity sha512-/9nu+lj0YswRNSH0NXYqrh8775XNyEdUQAuf3f+SmOrnVewcJ5SBNAjF7lpgehKi4abvNNXyf+HX86czCdJ8Mg== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/constants" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + +"@ethersproject/transactions@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/transactions/-/transactions-5.7.0.tgz#91318fc24063e057885a6af13fdb703e1f993d3b" + integrity sha512-kmcNicCp1lp8qanMTC3RIikGgoJ80ztTyvtsFvCYpSCfkjhD0jZ2LOrnbcuxuToLIUYYf+4XwD1rP+B/erDIhQ== + dependencies: + "@ethersproject/address" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/constants" "^5.7.0" + "@ethersproject/keccak256" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/rlp" "^5.7.0" + "@ethersproject/signing-key" "^5.7.0" + +"@ethersproject/web@^5.7.0": + version "5.7.1" + resolved "https://registry.yarnpkg.com/@ethersproject/web/-/web-5.7.1.tgz#de1f285b373149bee5928f4eb7bcb87ee5fbb4ae" + integrity sha512-Gueu8lSvyjBWL4cYsWsjh6MtMwM0+H4HvqFPZfB6dV8ctbP9zFAO73VG1cMWae0FLPCtz0peKPpZY8/ugJJX2w== + dependencies: + "@ethersproject/base64" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/strings" "^5.7.0" + "@expo/bunyan@4.0.0", "@expo/bunyan@^4.0.0": version "4.0.0" resolved "https://registry.npmjs.org/@expo/bunyan/-/bunyan-4.0.0.tgz" @@ -2228,7 +2422,14 @@ dependencies: crypto-js "^3.1.9-1" -"@noble/hashes@^1.2.0": +"@noble/curves@1.1.0", "@noble/curves@~1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.1.0.tgz#f13fc667c89184bc04cccb9b11e8e7bae27d8c3d" + integrity sha512-091oBExgENk/kGj3AZmtBDMpxQPDtxQABR2B9lb1JbVTs6ytdzZNwvhxQ4MWasRNEzlbEH8jCWFCwhF/Obj5AA== + dependencies: + "@noble/hashes" "1.3.1" + +"@noble/hashes@1.3.1", "@noble/hashes@^1.2.0", "@noble/hashes@~1.3.0", "@noble/hashes@~1.3.1": version "1.3.1" resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.1.tgz#8831ef002114670c603c458ab8b11328406953a9" integrity sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA== @@ -2554,11 +2755,28 @@ dependencies: nanoid "^3.1.23" -"@scure/base@^1.1.1": +"@scure/base@^1.1.1", "@scure/base@~1.1.0": version "1.1.1" resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.1.tgz#ebb651ee52ff84f420097055f4bf46cfba403938" integrity sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA== +"@scure/bip32@1.3.1": + version "1.3.1" + resolved "https://registry.yarnpkg.com/@scure/bip32/-/bip32-1.3.1.tgz#7248aea723667f98160f593d621c47e208ccbb10" + integrity sha512-osvveYtyzdEVbt3OfwwXFr4P2iVBL5u1Q3q4ONBfDY/UpOuXmOlbgwc1xECEboY8wIays8Yt6onaWMUdUbfl0A== + dependencies: + "@noble/curves" "~1.1.0" + "@noble/hashes" "~1.3.1" + "@scure/base" "~1.1.0" + +"@scure/bip39@1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@scure/bip39/-/bip39-1.2.1.tgz#5cee8978656b272a917b7871c981e0541ad6ac2a" + integrity sha512-Z3/Fsz1yr904dduJD0NpiyRHhRYHdcnyh73FZWiV+/qhWi83wNJ3NWolYqCEN+ZWsUz2TWwajJggcRE9r1zUYg== + dependencies: + "@noble/hashes" "~1.3.0" + "@scure/base" "~1.1.0" + "@segment/loosely-validate-event@^2.0.0": version "2.0.0" resolved "https://registry.npmjs.org/@segment/loosely-validate-event/-/loosely-validate-event-2.0.0.tgz" @@ -2617,6 +2835,32 @@ dependencies: "@sinonjs/commons" "^2.0.0" +"@solana/buffer-layout@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@solana/buffer-layout/-/buffer-layout-3.0.0.tgz#b9353caeb9a1589cb77a1b145bcb1a9a93114326" + integrity sha512-MVdgAKKL39tEs0l8je0hKaXLQFb7Rdfb0Xg2LjFZd8Lfdazkg6xiS98uAZrEKvaoF3i4M95ei9RydkGIDMeo3w== + dependencies: + buffer "~6.0.3" + +"@solana/web3.js@git+https://git@github.com/trustee-wallet/solana-web3.js.git": + version "0.0.0-development" + resolved "git+https://git@github.com/trustee-wallet/solana-web3.js.git#626665056389147825af2a9f982b59eec3207051" + dependencies: + "@babel/runtime" "^7.12.5" + "@solana/buffer-layout" "^3.0.0" + bn.js "^5.0.0" + borsh "^0.4.0" + bs58 "^4.0.1" + buffer "6.0.1" + crypto-hash "^1.2.2" + jayson "^3.4.4" + js-sha3 "^0.8.0" + node-fetch "^2.6.1" + rpc-websockets "^7.4.2" + secp256k1 "^4.0.2" + superstruct "^0.14.2" + tweetnacl "^1.0.0" + "@tanstack/query-core@4.27.0": version "4.27.0" resolved "https://registry.npmjs.org/@tanstack/query-core/-/query-core-4.27.0.tgz" @@ -2690,6 +2934,20 @@ dependencies: "@babel/types" "^7.3.0" +"@types/bn.js@^4.11.5": + version "4.11.6" + resolved "https://registry.yarnpkg.com/@types/bn.js/-/bn.js-4.11.6.tgz#c306c70d9358aaea33cd4eda092a742b9505967c" + integrity sha512-pqr857jrp2kPuO9uRjZ3PwnJTjoQy+fcdxvBTvHm6dkmEL9q+hDD/2j/0ELOBPtPnS8LjCX0gI9nbl8lVkadpg== + dependencies: + "@types/node" "*" + +"@types/connect@^3.4.33": + version "3.4.35" + resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.35.tgz#5fcf6ae445e4021d1fc2219a4873cc73a3bb2ad1" + integrity sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ== + dependencies: + "@types/node" "*" + "@types/graceful-fs@^4.1.3": version "4.1.6" resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.6.tgz#e14b2576a1c25026b7f02ede1de3b84c3a1efeae" @@ -2763,6 +3021,11 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-12.7.5.tgz#e19436e7f8e9b4601005d73673b6dc4784ffcc2f" integrity sha512-9fq4jZVhPNW8r+UYKnxF1e2HkDWOWKM5bC2/7c9wPV835I0aOrVbS/Hw/pWPk2uKrNXQqg9Z959Kz+IYDd5p3w== +"@types/node@^12.12.54": + version "12.20.55" + resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.55.tgz#c329cbd434c42164f846b909bd6f85b5537f6240" + integrity sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ== + "@types/pixelmatch@^5.2.4": version "5.2.4" resolved "https://registry.yarnpkg.com/@types/pixelmatch/-/pixelmatch-5.2.4.tgz#ca145cc5ede1388c71c68edf2d1f5190e5ddd0f6" @@ -2839,6 +3102,13 @@ resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-4.0.2.tgz#6286b4c7228d58ab7866d19716f3696e03a09397" integrity sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw== +"@types/ws@^7.4.4": + version "7.4.7" + resolved "https://registry.yarnpkg.com/@types/ws/-/ws-7.4.7.tgz#f7c390a36f7a0679aa69de2d501319f4f8d9b702" + integrity sha512-JQbbmxZTZehdc2iszGKs5oC3NFnjeay7mtAWrdt7qNtAVK0g19muApzAy4bm9byz79xa2ZnO/BOBC2R8RC5Lww== + dependencies: + "@types/node" "*" + "@types/ws@^8.5.3": version "8.5.4" resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.4.tgz#bb10e36116d6e570dd943735f86c933c1587b8a5" @@ -2998,6 +3268,14 @@ resolved "https://registry.yarnpkg.com/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz#e77a97fbd345b76d83245edcd17d393b1b41fb31" integrity sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ== +JSONStream@^1.3.5: + version "1.3.5" + resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.5.tgz#3208c1f08d3a4d99261ab64f92302bc15e111ca0" + integrity sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ== + dependencies: + jsonparse "^1.2.0" + through ">=2.2.7 <3" + abab@^2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.6.tgz#41b80f2c871d19686216b82309231cfd3cb3d291" @@ -3351,6 +3629,11 @@ axios@^1.3.4: form-data "^4.0.0" proxy-from-env "^1.1.0" +b4a@^1.6.0: + version "1.6.4" + resolved "https://registry.yarnpkg.com/b4a/-/b4a-1.6.4.tgz#ef1c1422cae5ce6535ec191baeed7567443f36c9" + integrity sha512-fpWrvyVHEKyeEvbKZTVOeZF3VSKKWtJxFIxX/jaVPf+cLbGUSitjb49pHLqPV2BUNNZ0LcoeEGfE/YCpyDYHIw== + babel-core@^7.0.0-bridge.0: version "7.0.0-bridge.0" resolved "https://registry.npmjs.org/babel-core/-/babel-core-7.0.0-bridge.0.tgz" @@ -3552,6 +3835,11 @@ bech32@^1.1.2: resolved "https://registry.yarnpkg.com/bech32/-/bech32-1.1.4.tgz#e38c9f37bf179b8eb16ae3a772b40c356d4832e9" integrity sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ== +bech32@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/bech32/-/bech32-2.0.0.tgz#078d3686535075c8c79709f054b1b226a133b355" + integrity sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg== + better-opn@~3.0.2: version "3.0.2" resolved "https://registry.npmjs.org/better-opn/-/better-opn-3.0.2.tgz" @@ -3564,6 +3852,11 @@ big-integer@1.6.x: resolved "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz" integrity sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg== +bignumber.js@^9.1.1: + version "9.1.1" + resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.1.1.tgz#c4df7dc496bd849d4c9464344c1aa74228b4dac6" + integrity sha512-pHm4LsMJ6lzgNGVfZHjMoO8sdoRhOzOH4MLmY65Jg70bpxCKu5iOHNJyfF6OyvYw7t8Fpf35RuzUyqnQsj8Vig== + bindings@^1.3.0: version "1.5.0" resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" @@ -3658,6 +3951,11 @@ bn.js@^4.11.8, bn.js@^4.11.9: resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA== +bn.js@^5.0.0, bn.js@^5.2.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.1.tgz#0bc527a6a0d18d0aa8d5b0538ce4a77dccfa7b70" + integrity sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ== + body-parser@^1.20.1: version "1.20.2" resolved "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz" @@ -3681,6 +3979,16 @@ boolbase@^1.0.0: resolved "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz" integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww== +borsh@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/borsh/-/borsh-0.4.0.tgz#9dd6defe741627f1315eac2a73df61421f6ddb9f" + integrity sha512-aX6qtLya3K0AkT66CmYWCCDr77qsE9arV05OmdFpmat9qu8Pg9J5tBUPDztAW5fNh/d/MyVG/OYziP52Ndzx1g== + dependencies: + "@types/bn.js" "^4.11.5" + bn.js "^5.0.0" + bs58 "^4.0.0" + text-encoding-utf-8 "^1.0.2" + bplist-creator@0.1.0: version "0.1.0" resolved "https://registry.npmjs.org/bplist-creator/-/bplist-creator-0.1.0.tgz" @@ -3748,7 +4056,7 @@ browserslist@^4.21.3, browserslist@^4.21.5: node-releases "^2.0.8" update-browserslist-db "^1.0.10" -bs58@^4.0.0: +bs58@^4.0.0, bs58@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/bs58/-/bs58-4.0.1.tgz#be161e76c354f6f788ae4071f63f34e8c4f0a42a" integrity sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw== @@ -3809,6 +4117,14 @@ buffer-from@^1.0.0: resolved "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz" integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== +buffer@6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.1.tgz#3cbea8c1463e5a0779e30b66d4c88c6ffa182ac2" + integrity sha512-rVAXBwEcEoYtxnHSO5iWyhzV/O1WMtkUYWlfdLS7FjU4PnSJJHEfHXi/uHPI5EwltmOA794gN3bm3/pzuctWjQ== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.2.1" + buffer@^5.5.0: version "5.7.1" resolved "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz" @@ -3817,7 +4133,7 @@ buffer@^5.5.0: base64-js "^1.3.1" ieee754 "^1.1.13" -buffer@^6.0.3: +buffer@^6.0.3, buffer@~6.0.3: version "6.0.3" resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== @@ -3825,6 +4141,13 @@ buffer@^6.0.3: base64-js "^1.3.1" ieee754 "^1.2.1" +bufferutil@^4.0.1: + version "4.0.7" + resolved "https://registry.yarnpkg.com/bufferutil/-/bufferutil-4.0.7.tgz#60c0d19ba2c992dd8273d3f73772ffc894c153ad" + integrity sha512-kukuqc39WOHtdxtw4UScxF/WVnMFVSQVKhtx3AjZJzhd0RGZZldcrfSEbVsWWe6KNH253574cq5F+wpv0G9pJw== + dependencies: + node-gyp-build "^4.3.0" + builtin-modules@^1.1.1: version "1.1.1" resolved "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz" @@ -4198,7 +4521,7 @@ commander@^10.0.0: resolved "https://registry.npmjs.org/commander/-/commander-10.0.0.tgz" integrity sha512-zS5PnTI22FIRM6ylNW8G4Ap0IEOyk62fhLSD0+uHRT9McRCLGpkVNvao4bjimpK/GShynyQkFFxHhwMcETmduA== -commander@^2.12.1, commander@^2.20.0: +commander@^2.12.1, commander@^2.20.0, commander@^2.20.3: version "2.20.3" resolved "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== @@ -4330,6 +4653,11 @@ cosmiconfig@^5.0.5, cosmiconfig@^5.1.0: js-yaml "^3.13.1" parse-json "^4.0.0" +crc-32@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/crc-32/-/crc-32-1.2.2.tgz#3cad35a934b8bf71f25ca524b6da51fb7eace2ff" + integrity sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ== + create-hash@^1.1.0, create-hash@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196" @@ -4385,6 +4713,11 @@ crypt@0.0.2, crypt@~0.0.1: resolved "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz" integrity sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow== +crypto-hash@^1.2.2: + version "1.3.0" + resolved "https://registry.yarnpkg.com/crypto-hash/-/crypto-hash-1.3.0.tgz#b402cb08f4529e9f4f09346c3e275942f845e247" + integrity sha512-lyAZ0EMyjDkVvz8WOeVnuCPvKVBXcMv1l5SVqO1yC7PzTwrD/pPje/BIRbWhMoPe436U+Y2nD7f5bFx0kt+Sbg== + crypto-js@^3.1.9-1: version "3.3.0" resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-3.3.0.tgz#846dd1cce2f68aacfa156c8578f926a609b7976b" @@ -4619,6 +4952,11 @@ del@^6.0.0: rimraf "^3.0.2" slash "^3.0.0" +delay@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/delay/-/delay-5.0.0.tgz#137045ef1b96e5071060dd5be60bf9334436bd1d" + integrity sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw== + delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz" @@ -4755,7 +5093,7 @@ electron-to-chromium@^1.4.284: resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.348.tgz" integrity sha512-gM7TdwuG3amns/1rlgxMbeeyNoBFPa+4Uu0c7FeROWh4qWmvSOnvcslKmWy51ggLKZ2n/F/4i2HJ+PVNxH9uCQ== -elliptic@^6.4.0: +elliptic@6.5.4, elliptic@^6.4.0, elliptic@^6.5.4: version "6.5.4" resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb" integrity sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ== @@ -4934,6 +5272,18 @@ es6-object-assign@^1.1.0: resolved "https://registry.yarnpkg.com/es6-object-assign/-/es6-object-assign-1.1.0.tgz#c2c3582656247c39ea107cb1e6652b6f9f24523c" integrity sha512-MEl9uirslVwqQU369iHNWZXsI8yaZYGg/D65aOgZkeyFJwHYSxilf7rQzXKI7DdDuBPrBXbfk3sl9hJhmd5AUw== +es6-promise@^4.0.3: + version "4.2.8" + resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a" + integrity sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w== + +es6-promisify@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203" + integrity sha512-C+d6UdsYDk0lMebHNR4S2NybQMMngAOnOwYBQjTOiv0MkoJMP0Myw2mgpDLBcpfCmRLxyFqYhS/CfOENq4SJhQ== + dependencies: + es6-promise "^4.0.3" + escalade@^3.1.1: version "3.1.1" resolved "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz" @@ -5225,11 +5575,26 @@ etag@~1.8.1: resolved "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz" integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== +ethereum-cryptography@^2.0.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ethereum-cryptography/-/ethereum-cryptography-2.1.2.tgz#18fa7108622e56481157a5cb7c01c0c6a672eb67" + integrity sha512-Z5Ba0T0ImZ8fqXrJbpHcbpAvIswRte2wGNR/KePnu8GbbvgJ47lMxT/ZZPG6i9Jaht4azPDop4HaM00J0J59ug== + dependencies: + "@noble/curves" "1.1.0" + "@noble/hashes" "1.3.1" + "@scure/bip32" "1.3.1" + "@scure/bip39" "1.2.1" + event-target-shim@^5.0.0, event-target-shim@^5.0.1: version "5.0.1" resolved "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz" integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== +eventemitter3@^4.0.7: + version "4.0.7" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" + integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== + exec-async@^2.2.0: version "2.2.0" resolved "https://registry.npmjs.org/exec-async/-/exec-async-2.2.0.tgz" @@ -5603,6 +5968,11 @@ extglob@^2.0.4: snapdragon "^0.8.1" to-regex "^3.0.1" +eyes@^0.1.8: + version "0.1.8" + resolved "https://registry.yarnpkg.com/eyes/-/eyes-0.1.8.tgz#62cf120234c683785d902348a800ef3e0cc20bc0" + integrity sha512-GipyPsXO1anza0AOZdy69Im7hGFCNB7Y/NGjDlZGJ3GJJLtwNSb2vrzYrTYJRrRloVx7pl+bhUaTB8yiccPvFQ== + fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz" @@ -5950,6 +6320,20 @@ functions-have-names@^1.2.2, functions-have-names@^1.2.3: resolved "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz" integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== +generate-function@^2.0.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/generate-function/-/generate-function-2.3.1.tgz#f069617690c10c868e73b8465746764f97c3479f" + integrity sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ== + dependencies: + is-property "^1.0.2" + +generate-object-property@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/generate-object-property/-/generate-object-property-1.2.0.tgz#9c0e1c40308ce804f4783618b937fa88f99d50d0" + integrity sha512-TuOwZWgJ2VAMEGJvAyPWvpqxSANF0LDpmyHauMjFYzaACvn+QTT/AZomvPCzVBV7yDN3OmwHQ5OvHaeLKre3JQ== + dependencies: + is-property "^1.0.0" + gensync@^1.0.0-beta.2: version "1.0.0-beta.2" resolved "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz" @@ -6204,7 +6588,7 @@ hash-base@^3.0.0: readable-stream "^3.6.0" safe-buffer "^5.2.0" -hash.js@^1.0.0, hash.js@^1.0.3: +hash.js@1.1.7, hash.js@^1.0.0, hash.js@^1.0.3: version "1.1.7" resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42" integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA== @@ -6636,6 +7020,22 @@ is-map@^2.0.1, is-map@^2.0.2: resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.2.tgz#00922db8c9bf73e81b7a335827bc2a43f2b91127" integrity sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg== +is-my-ip-valid@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-my-ip-valid/-/is-my-ip-valid-1.0.1.tgz#f7220d1146257c98672e6fba097a9f3f2d348442" + integrity sha512-jxc8cBcOWbNK2i2aTkCZP6i7wkHF1bqKFrwEHuN5Jtg5BSaZHUZQ/JTOJwoV41YvHnOaRyWWh72T/KvfNz9DJg== + +is-my-json-valid@^2.20.6: + version "2.20.6" + resolved "https://registry.yarnpkg.com/is-my-json-valid/-/is-my-json-valid-2.20.6.tgz#a9d89e56a36493c77bda1440d69ae0dc46a08387" + integrity sha512-1JQwulVNjx8UqkPE/bqDaxtH4PXCe/2VRh/y3p99heOV87HG4Id5/VfDswd+YiAfHcRTfDlWgISycnHuhZq1aw== + dependencies: + generate-function "^2.0.0" + generate-object-property "^1.1.0" + is-my-ip-valid "^1.0.0" + jsonpointer "^5.0.0" + xtend "^4.0.0" + is-nan@^1.2.1: version "1.3.2" resolved "https://registry.yarnpkg.com/is-nan/-/is-nan-1.3.2.tgz#043a54adea31748b55b6cd4e09aadafa69bd9e1d" @@ -6690,6 +7090,11 @@ is-potential-custom-element-name@^1.0.1: resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz#171ed6f19e3ac554394edf78caa05784a45bebb5" integrity sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ== +is-property@^1.0.0, is-property@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-property/-/is-property-1.0.2.tgz#57fe1c4e48474edd65b09911f26b1cd4095dda84" + integrity sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g== + is-regex@^1.1.4: version "1.1.4" resolved "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz" @@ -6839,6 +7244,16 @@ isomorphic-fetch@^2.1.1: node-fetch "^1.0.1" whatwg-fetch ">=0.10.0" +isomorphic-ws@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz#55fd4cd6c5e6491e76dc125938dd863f5cd4f2dc" + integrity sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w== + +isomorphic-ws@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/isomorphic-ws/-/isomorphic-ws-5.0.0.tgz#e5529148912ecb9b451b46ed44d53dae1ce04bbf" + integrity sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw== + istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz#189e7909d0a39fa5a3dfad5b03f71947770191d3" @@ -6881,6 +7296,25 @@ istanbul-reports@^3.1.3: html-escaper "^2.0.0" istanbul-lib-report "^3.0.0" +jayson@^3.4.4: + version "3.7.0" + resolved "https://registry.yarnpkg.com/jayson/-/jayson-3.7.0.tgz#b735b12d06d348639ae8230d7a1e2916cb078f25" + integrity sha512-tfy39KJMrrXJ+mFcMpxwBvFDetS8LAID93+rycFglIQM4kl3uNR3W4lBLE/FFhsoUCEox5Dt2adVpDm/XtebbQ== + dependencies: + "@types/connect" "^3.4.33" + "@types/node" "^12.12.54" + "@types/ws" "^7.4.4" + JSONStream "^1.3.5" + commander "^2.20.3" + delay "^5.0.0" + es6-promisify "^5.0.0" + eyes "^0.1.8" + isomorphic-ws "^4.0.1" + json-stringify-safe "^5.0.1" + lodash "^4.17.20" + uuid "^8.3.2" + ws "^7.4.5" + jest-changed-files@^29.5.0: version "29.5.0" resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-29.5.0.tgz#e88786dca8bf2aa899ec4af7644e16d9dcf9b23e" @@ -7371,7 +7805,7 @@ js-sdsl@^4.1.4: resolved "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.4.0.tgz" integrity sha512-FfVSdx6pJ41Oa+CF7RDaFmTnCaFhua+SNYQX74riGOpl96x+2jQCqEfQ2bnXu/5DPCqlRuiqyvTJM0Qjz26IVg== -js-sha3@^0.8.0: +js-sha3@0.8.0, js-sha3@^0.8.0: version "0.8.0" resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.8.0.tgz#b9b7a5da73afad7dedd0f8c463954cbde6818840" integrity sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q== @@ -7507,6 +7941,11 @@ json-stable-stringify-without-jsonify@^1.0.1: resolved "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz" integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== +json-stringify-safe@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" + integrity sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA== + json5@^0.5.1: version "0.5.1" resolved "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz" @@ -7540,6 +7979,16 @@ jsonfile@^6.0.1: optionalDependencies: graceful-fs "^4.1.6" +jsonparse@^1.2.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280" + integrity sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg== + +jsonpointer@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-5.0.1.tgz#2110e0af0900fd37467b5907ecd13a7884a1b559" + integrity sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ== + "jsx-ast-utils@^2.4.1 || ^3.0.0": version "3.3.3" resolved "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.3.tgz" @@ -7690,7 +8139,7 @@ lodash.throttle@^4.1.1: resolved "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz" integrity sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ== -lodash@^4.17.13, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.21, lodash@^4.17.4: +lodash@^4.17.13, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.4: version "4.17.21" resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -8412,6 +8861,11 @@ nocache@^3.0.1: resolved "https://registry.npmjs.org/nocache/-/nocache-3.0.4.tgz" integrity sha512-WDD0bdg9mbq6F4mRxEYcPWwfA1vxd0mrvKOyxI7Xj/atfRHVeutzuWByG//jfm4uPzp0y4Kj051EORCBSQMycw== +node-addon-api@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-2.0.2.tgz#432cfa82962ce494b132e9d72a15b29f71ff5d32" + integrity sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA== + node-dir@^0.1.17: version "0.1.17" resolved "https://registry.npmjs.org/node-dir/-/node-dir-0.1.17.tgz" @@ -8456,6 +8910,11 @@ node-git-hooks@^1.0.1: resolved "https://registry.npmjs.org/node-git-hooks/-/node-git-hooks-1.0.7.tgz" integrity sha512-IgIbLXsONu4sfHVaaTxQvVbNfo36ZxqCbzmataI/4hbwqmX2Eth4Vdxw9NvAAeroVuxzzmHqjVlV9TeHb3U2yw== +node-gyp-build@^4.2.0, node-gyp-build@^4.3.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.6.0.tgz#0c52e4cbf54bbd28b709820ef7b6a3c2d6209055" + integrity sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ== + node-int64@^0.4.0: version "0.4.0" resolved "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz" @@ -9129,6 +9588,15 @@ prop-types@15.5.8: dependencies: fbjs "^0.8.9" +protocol-buffers-encodings@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/protocol-buffers-encodings/-/protocol-buffers-encodings-1.2.0.tgz#39900b85dcff3172a23f15bdf3fda70daa2b38d3" + integrity sha512-daeNPuKh1NlLD1uDfbLpD+xyUTc07nEtfHwmBZmt/vH0B7VOM+JOCOpDcx9ZRpqHjAiIkGqyTDi+wfGSl17R9w== + dependencies: + b4a "^1.6.0" + signed-varint "^2.0.1" + varint "5.0.0" + proxy-from-env@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz" @@ -9780,6 +10248,19 @@ ripemd160@^2.0.0, ripemd160@^2.0.1: hash-base "^3.0.0" inherits "^2.0.1" +rpc-websockets@^7.4.2: + version "7.5.1" + resolved "https://registry.yarnpkg.com/rpc-websockets/-/rpc-websockets-7.5.1.tgz#e0a05d525a97e7efc31a0617f093a13a2e10c401" + integrity sha512-kGFkeTsmd37pHPMaHIgN1LVKXMi0JD782v4Ds9ZKtLlwdTKjn+CxM9A9/gLT2LaOuEcEFGL98h1QWQtlOIdW0w== + dependencies: + "@babel/runtime" "^7.17.2" + eventemitter3 "^4.0.7" + uuid "^8.3.2" + ws "^8.5.0" + optionalDependencies: + bufferutil "^4.0.1" + utf-8-validate "^5.0.2" + run-parallel@^1.1.9: version "1.2.0" resolved "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz" @@ -9856,6 +10337,15 @@ scheduler@^0.23.0: dependencies: loose-envify "^1.1.0" +secp256k1@^4.0.2: + version "4.0.3" + resolved "https://registry.yarnpkg.com/secp256k1/-/secp256k1-4.0.3.tgz#c4559ecd1b8d3c1827ed2d1b94190d69ce267303" + integrity sha512-NLZVf+ROMxwtEj3Xa562qgv2BK5e2WNmXPiOdVIPLgs6lyTzMvBq0aWTYMI5XCP9jZMVKOcqZLw/Wc4vDkuxhA== + dependencies: + elliptic "^6.5.4" + node-addon-api "^2.0.0" + node-gyp-build "^4.2.0" + semver@7.3.2: version "7.3.2" resolved "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz" @@ -10011,6 +10501,13 @@ signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7: resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== +signed-varint@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/signed-varint/-/signed-varint-2.0.1.tgz#50a9989da7c98c2c61dad119bc97470ef8528129" + integrity sha512-abgDPg1106vuZZOvw7cFwdCABddfJRz5akcCcchzTbhyhYnsG31y4AlZEgp315T7W3nQq5P4xeOm186ZiPVFzw== + dependencies: + varint "~5.0.0" + simple-plist@^1.1.0: version "1.3.1" resolved "https://registry.npmjs.org/simple-plist/-/simple-plist-1.3.1.tgz" @@ -10482,6 +10979,11 @@ sudo-prompt@^9.0.0: resolved "https://registry.npmjs.org/sudo-prompt/-/sudo-prompt-9.2.1.tgz" integrity sha512-Mu7R0g4ig9TUuGSxJavny5Rv0egCEtpZRNMrZaYS1vxkiIxGiGUwoezU3LazIQ+KE04hTrTfNPgxU5gzi7F5Pw== +superstruct@^0.14.2: + version "0.14.2" + resolved "https://registry.yarnpkg.com/superstruct/-/superstruct-0.14.2.tgz#0dbcdf3d83676588828f1cf5ed35cda02f59025b" + integrity sha512-nPewA6m9mR3d6k7WkZ8N8zpTWfenFH3q9pA2PkuiZxINr9DKB2+40wEQf0ixn8VaGuJ78AB6iWOtStI+/4FKZQ== + superstruct@^0.6.2: version "0.6.2" resolved "https://registry.yarnpkg.com/superstruct/-/superstruct-0.6.2.tgz#c5eb034806a17ff98d036674169ef85e4c7f6a1c" @@ -10613,6 +11115,11 @@ test-exclude@^6.0.0: glob "^7.1.4" minimatch "^3.0.4" +text-encoding-utf-8@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/text-encoding-utf-8/-/text-encoding-utf-8-1.0.2.tgz#585b62197b0ae437e3c7b5d0af27ac1021e10d13" + integrity sha512-8bw4MY9WjdsD2aMtO0OzOCY3pXGYNx2d2FfHRVUKkiCPDWjKuOlhLVASS+pD7VkLTVjW268LYJHwsnPFlBpbAg== + text-table@^0.2.0: version "0.2.0" resolved "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz" @@ -10645,7 +11152,7 @@ through2@^2.0.1: readable-stream "~2.3.6" xtend "~4.0.1" -through@2, through@^2.3.8: +through@2, "through@>=2.2.7 <3", through@^2.3.8: version "2.3.8" resolved "https://registry.npmjs.org/through/-/through-2.3.8.tgz" integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== @@ -10661,6 +11168,13 @@ tiny-secp256k1@^1.1.1, tiny-secp256k1@^1.1.3: elliptic "^6.4.0" nan "^2.13.2" +tiny-secp256k1@^2.2.3: + version "2.2.3" + resolved "https://registry.yarnpkg.com/tiny-secp256k1/-/tiny-secp256k1-2.2.3.tgz#fe1dde11a64fcee2091157d4b78bcb300feb9b65" + integrity sha512-SGcL07SxcPN2nGKHTCvRMkQLYPSoeFcvArUSCYtjVARiFAWU44cCIqYS0mYAU6nY7XfvwURuTIGo2Omt3ZQr0Q== + dependencies: + uint8array-tools "0.0.7" + tmp@^0.0.33: version "0.0.33" resolved "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz" @@ -10810,6 +11324,11 @@ tsutils@^3.21.0: dependencies: tslib "^1.8.1" +tweetnacl@^1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-1.0.3.tgz#ac0af71680458d8a6378d0d0d050ab1407d35596" + integrity sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw== + type-check@^0.4.0, type-check@~0.4.0: version "0.4.0" resolved "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz" @@ -10904,6 +11423,11 @@ uglify-es@^3.1.9: commander "~2.13.0" source-map "~0.6.1" +uint8array-tools@0.0.7: + version "0.0.7" + resolved "https://registry.yarnpkg.com/uint8array-tools/-/uint8array-tools-0.0.7.tgz#a7a2bb5d8836eae2fade68c771454e6a438b390d" + integrity sha512-vrrNZJiusLWoFWBqz5Y5KMCgP9W9hnjZHzZiZRT8oNAkq3d5Z5Oe76jAvVVSRh4U8GGR90N2X1dWtrhvx6L8UQ== + unbox-primitive@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz" @@ -11061,12 +11585,19 @@ use@^3.1.0: resolved "https://registry.npmjs.org/use/-/use-3.1.1.tgz" integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ== +utf-8-validate@^5.0.2: + version "5.0.10" + resolved "https://registry.yarnpkg.com/utf-8-validate/-/utf-8-validate-5.0.10.tgz#d7d10ea39318171ca982718b6b96a8d2442571a2" + integrity sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ== + dependencies: + node-gyp-build "^4.3.0" + util-deprecate@^1.0.1, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== -util@^0.12.0: +util@^0.12.0, util@^0.12.5: version "0.12.5" resolved "https://registry.yarnpkg.com/util/-/util-0.12.5.tgz#5f17a6059b73db61a875668781a1c2b136bd6fbc" integrity sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA== @@ -11118,6 +11649,16 @@ validate-npm-package-name@^3.0.0: dependencies: builtins "^1.0.3" +varint@5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/varint/-/varint-5.0.0.tgz#d826b89f7490732fabc0c0ed693ed475dcb29ebf" + integrity sha512-gC13b/bWrqQoKY2EmROCZ+AR0jitc6DnDGaQ6Ls9QpKmuSgJB1eQ7H3KETtQm7qSdMWMKCmsshyCmUwMLh3OAA== + +varint@~5.0.0: + version "5.0.2" + resolved "https://registry.yarnpkg.com/varint/-/varint-5.0.2.tgz#5b47f8a947eb668b848e034dcfa87d0ff8a7f7a4" + integrity sha512-lKxKYG6H03yCZUpAGOPOsMcGxd1RHCu1iKvEHYDPmTyq2HueGhD73ssNBqqQWfvYs04G9iUFRvmAVLW20Jw6ow== + varuint-bitcoin@^1.0.4: version "1.1.2" resolved "https://registry.yarnpkg.com/varuint-bitcoin/-/varuint-bitcoin-1.1.2.tgz#e76c138249d06138b480d4c5b40ef53693e24e92" @@ -11161,6 +11702,217 @@ wcwidth@^1.0.1: dependencies: defaults "^1.0.3" +web3-core@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/web3-core/-/web3-core-4.0.3.tgz#eab6cc23a43ff202d8f38bbd9801a7a2ec750cc2" + integrity sha512-KJaH1+ajm/gelvhImkXZx8HrBaGZDERqhOCRpikuwReVDTf4X3TlXqF+oKt153qf5HUXWR4CUL6NkNKNQWjhbA== + dependencies: + web3-errors "^1.0.2" + web3-eth-iban "^4.0.3" + web3-providers-http "^4.0.3" + web3-providers-ws "^4.0.3" + web3-types "^1.0.2" + web3-utils "^4.0.3" + web3-validator "^1.0.2" + optionalDependencies: + web3-providers-ipc "^4.0.3" + +web3-errors@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/web3-errors/-/web3-errors-1.0.2.tgz#e8ce6e22dfdfd9aeaf8d7535653e55b094b5accd" + integrity sha512-LtRUASAQKeCKyxHRhfyU5xiE9asUmo7KJ9bEzzaPlkVYLl5lzhUXzd6lvnQfSaSXJnlzoUXvhI5I0Hpzc8Lohg== + dependencies: + web3-types "^1.0.2" + +web3-eth-abi@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/web3-eth-abi/-/web3-eth-abi-4.0.3.tgz#cc06cc39868d8bcc181528aa46ae9d5c80ed93b6" + integrity sha512-is1sKkTna5LQri25iRbxJ43kQ6qlFR/Syi6dnpwsFua0qAyKuDTxLZDoMaBfdH8NvxvjuGWFUWALwuSk8gk5Xg== + dependencies: + "@ethersproject/abi" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + web3-errors "^1.0.2" + web3-types "^1.0.2" + web3-utils "^4.0.3" + +web3-eth-accounts@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/web3-eth-accounts/-/web3-eth-accounts-4.0.3.tgz#7e570b3170aca052b358975235637a94b5313826" + integrity sha512-qS4r25weJYlKzHPIneL3g33LG+I6QkRCs25ZtooK6elurlZY4HyRE04BIWv12xZswtsvdmMt4HysMUNKgLrgPg== + dependencies: + "@ethereumjs/rlp" "^4.0.1" + crc-32 "^1.2.2" + ethereum-cryptography "^2.0.0" + web3-errors "^1.0.2" + web3-types "^1.0.2" + web3-utils "^4.0.3" + web3-validator "^1.0.2" + +web3-eth-contract@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/web3-eth-contract/-/web3-eth-contract-4.0.3.tgz#667e8f8052034f49a9130e0f286976bcf43c5d77" + integrity sha512-x8YsIVVUeONwLCnUmswk5KD3luYxaKuN/xnSzxpb8fE4/KBA6eJswYcIGPrK9QILrVR26yDV/QQpgLU1IJS14g== + dependencies: + web3-core "^4.0.3" + web3-errors "^1.0.2" + web3-eth "^4.0.3" + web3-eth-abi "^4.0.3" + web3-types "^1.0.2" + web3-utils "^4.0.3" + web3-validator "^1.0.2" + +web3-eth-ens@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/web3-eth-ens/-/web3-eth-ens-4.0.3.tgz#9b17bdcdc262ddcb5b9fd0b4893c0a9a56bf07ca" + integrity sha512-1tk1WWJB6lsViRFxHR9kt8qgfMV0cySeNBa8H/bZ9/HZ1G8L/c2cboVrG4D0QsPO1im1jQl4Cf3ceKH0PW1KZg== + dependencies: + "@adraffy/ens-normalize" "^1.8.8" + web3-core "^4.0.3" + web3-errors "^1.0.2" + web3-eth "^4.0.3" + web3-eth-contract "^4.0.3" + web3-net "^4.0.3" + web3-types "^1.0.2" + web3-utils "^4.0.3" + web3-validator "^1.0.2" + +web3-eth-iban@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/web3-eth-iban/-/web3-eth-iban-4.0.3.tgz#3fca87323c00a29f1b3870d397153803eb0bcf4e" + integrity sha512-9gn6fb034fh3DvQeutuhaG3J9+ZSriPC/O/H7K+lgUWJZh/lpaZy5A06nhHzNcleCWC07Q6J7d7VZlNjaBPtOA== + dependencies: + web3-errors "^1.0.2" + web3-types "^1.0.2" + web3-utils "^4.0.3" + web3-validator "^1.0.2" + +web3-eth-personal@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/web3-eth-personal/-/web3-eth-personal-4.0.3.tgz#df4c59bf2a0e07cd6966259d1312be6b5b61846e" + integrity sha512-Gugz45w/D4wlUNbUth8iHWkv0c5fFZGWZqFvpACJul0z9h0Ou8HzuJMUv3U0xFOQJF5fniVegfp6l0FJQ3hGrQ== + dependencies: + web3-core "^4.0.3" + web3-eth "^4.0.3" + web3-rpc-methods "^1.0.2" + web3-types "^1.0.2" + web3-utils "^4.0.3" + web3-validator "^1.0.2" + +web3-eth@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/web3-eth/-/web3-eth-4.0.3.tgz#b7f311eba95151f547ccce285893af9917da9e35" + integrity sha512-4t1+lpqzk3ljubr0CKE9Ila82p2Pim6Bn7ZIruVfMt9AOA5wL6M0OeMTy0fWBODLJiZJ7R77Ugm0kvEVWD3lqg== + dependencies: + setimmediate "^1.0.5" + web3-core "^4.0.3" + web3-errors "^1.0.2" + web3-eth-abi "^4.0.3" + web3-eth-accounts "^4.0.3" + web3-net "^4.0.3" + web3-providers-ws "^4.0.3" + web3-rpc-methods "^1.0.2" + web3-types "^1.0.2" + web3-utils "^4.0.3" + web3-validator "^1.0.2" + +web3-net@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/web3-net/-/web3-net-4.0.3.tgz#9aeed6fa3d48adcf63d8377900acbe3e64020154" + integrity sha512-qe+stvVgYhO8AiPgDykZW5gS4mZ3GRWdQ8xn3eTvderresIMvdZYSAoUla2jWl1CgpcqzaoOSO9Pf8t43fr8SA== + dependencies: + web3-core "^4.0.3" + web3-rpc-methods "^1.0.2" + web3-types "^1.0.2" + web3-utils "^4.0.3" + +web3-providers-http@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/web3-providers-http/-/web3-providers-http-4.0.3.tgz#c6c8364ed56c4183e6bed58de20c1972f513c7ae" + integrity sha512-5E6nKjWrwlJdhGImOxyTnFDT6UcZu4waO6AJrENBRh2vdoCfP/Piiv3PLywHs71gwTMsAjy6CNPL5lZdGf+JQA== + dependencies: + cross-fetch "^3.1.5" + web3-errors "^1.0.2" + web3-types "^1.0.2" + web3-utils "^4.0.3" + +web3-providers-ipc@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/web3-providers-ipc/-/web3-providers-ipc-4.0.3.tgz#d7af699a2afae0f7396d08ef8cc82b5ab4374398" + integrity sha512-v+Ugp5XXUVcAQju/u4ThdjI3FM9lq674F6cJ7yz3R6uTel+wNPDiT47Se8hvm5grgHid7z3MbVYCQpDCiiAFHw== + dependencies: + web3-errors "^1.0.2" + web3-types "^1.0.2" + web3-utils "^4.0.3" + +web3-providers-ws@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/web3-providers-ws/-/web3-providers-ws-4.0.3.tgz#c611a0ae81ac022d8ccb01f71da761f7b4decd85" + integrity sha512-V2bYiMvhv+xBYxFdf8V1zGTwhJoAkBQNMECVGNjQIz1qBKuqu6hXHasmkYSJV780LD6qoL58KlfTggjf4SUSaA== + dependencies: + "@types/ws" "^8.5.3" + isomorphic-ws "^5.0.0" + web3-errors "^1.0.2" + web3-types "^1.0.2" + web3-utils "^4.0.3" + ws "^8.8.1" + +web3-rpc-methods@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/web3-rpc-methods/-/web3-rpc-methods-1.0.2.tgz#3ff35c5d4e38ad31ef3cf77eb3fe2fd08e2a3f4a" + integrity sha512-VhLHvgR62JUNgo0op8hP4LcRkvdF0WaHD9xhcEKGLcri9VfYvR1yTZ3CVh6NTgRCmfDePObbp5blHfbla1cC5Q== + dependencies: + web3-core "^4.0.3" + web3-types "^1.0.2" + web3-validator "^1.0.2" + +web3-types@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/web3-types/-/web3-types-1.0.2.tgz#1655a400d31984153fc26ca1f8960f547ca1f2df" + integrity sha512-tLzA9vevGGWdHlxXvPRJjEIIR0UnZBI5Kq9qiENRS/vSekTHAHp7u+WGDxt+6kP105gKlbep50TogQIvJqLfnA== + +web3-utils@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/web3-utils/-/web3-utils-4.0.3.tgz#80c077e56c0841528ea4513c67d83e460217b379" + integrity sha512-clBvm/vWR2mAc9nPnsPYBZMikIhVG9RAsXdrxvXI4e2jAQ3DTtHKMhqy+Cl214dQaAdAEYyVb5ILW5lKKqk2vA== + dependencies: + ethereum-cryptography "^2.0.0" + web3-errors "^1.0.2" + web3-types "^1.0.2" + web3-validator "^1.0.2" + +web3-validator@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/web3-validator/-/web3-validator-1.0.2.tgz#ca7d247b49f4f690db86e5b953272a627dc5950a" + integrity sha512-orx1CQAEnwJUnl/8iF2II2zSA4wiooNJvFmVE0Dbmt/kE370SugIDViQP76snhxtouG2AXzz4GyKbPCMlLGh/A== + dependencies: + ethereum-cryptography "^2.0.0" + is-my-json-valid "^2.20.6" + util "^0.12.5" + web3-errors "^1.0.2" + web3-types "^1.0.2" + +web3@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/web3/-/web3-4.0.3.tgz#afeb977c9f883ff683d630ab9f5937eb56bc7cf4" + integrity sha512-rUMxui5f52yPWjiMRQV6xqIrTQSovYM2CNhl57y+xj/fGXNLbI1D5FsLPnUMZjMaFHJBTteaBxq/sTEaw/1jNA== + dependencies: + web3-core "^4.0.3" + web3-errors "^1.0.2" + web3-eth "^4.0.3" + web3-eth-abi "^4.0.3" + web3-eth-accounts "^4.0.3" + web3-eth-contract "^4.0.3" + web3-eth-ens "^4.0.3" + web3-eth-iban "^4.0.3" + web3-eth-personal "^4.0.3" + web3-net "^4.0.3" + web3-providers-http "^4.0.3" + web3-providers-ws "^4.0.3" + web3-rpc-methods "^1.0.2" + web3-types "^1.0.2" + web3-utils "^4.0.3" + web3-validator "^1.0.2" + webidl-conversions@^3.0.0: version "3.0.1" resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz" @@ -11325,12 +12077,12 @@ ws@^6.2.2: dependencies: async-limiter "~1.0.0" -ws@^7, ws@^7.5.1: +ws@^7, ws@^7.4.5, ws@^7.5.1: version "7.5.9" resolved "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz" integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q== -ws@^8.11.0, ws@^8.12.1: +ws@^8.11.0, ws@^8.12.1, ws@^8.5.0, ws@^8.8.1: version "8.13.0" resolved "https://registry.yarnpkg.com/ws/-/ws-8.13.0.tgz#9a9fb92f93cf41512a0735c8f4dd09b8a1211cd0" integrity sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA== @@ -11383,7 +12135,7 @@ xmlchars@^2.2.0: resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== -xtend@~4.0.1: +xtend@^4.0.0, xtend@~4.0.1: version "4.0.2" resolved "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz" integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== From a07cf1928b3ffc461d3d32236d03832633a56d02 Mon Sep 17 00:00:00 2001 From: JavidHaji-zada Date: Wed, 26 Jul 2023 12:12:32 +0400 Subject: [PATCH 016/509] converted bnb_smart --- .../bnb_smart/BnbSmartTransferProcessor.ts | 110 ++++++------ .../BnbSmartTransferProcessorErc20.ts | 158 +++++++++--------- .../bnb_smart/basic/BnbSmartNetworkPrices.js | 154 ++++++++--------- 3 files changed, 208 insertions(+), 214 deletions(-) diff --git a/crypto/blockchains/bnb_smart/BnbSmartTransferProcessor.ts b/crypto/blockchains/bnb_smart/BnbSmartTransferProcessor.ts index 8b5fe97df..c711f55b4 100644 --- a/crypto/blockchains/bnb_smart/BnbSmartTransferProcessor.ts +++ b/crypto/blockchains/bnb_smart/BnbSmartTransferProcessor.ts @@ -1,55 +1,55 @@ -/** - * @author Ksu - * @version 0.20 - * https://api.etherscan.io/api?module=gastracker&action=gasoracle&apikey=YourApiKeyToken - */ -import { BlocksoftBlockchainTypes } from '../BlocksoftBlockchainTypes'; -import EthTransferProcessor from '../eth/EthTransferProcessor'; - -import BnbSmartNetworkPrices from './basic/BnbSmartNetworkPrices'; -import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings'; - -export default class BnbSmartTransferProcessor - extends EthTransferProcessor - implements BlocksoftBlockchainTypes.TransferProcessor -{ - async getFeeRate( - data: BlocksoftBlockchainTypes.TransferData, - privateData: BlocksoftBlockchainTypes.TransferPrivateData, - additionalData: {} = {} - ): Promise { - if ( - typeof additionalData.gasPrice === 'undefined' || - !additionalData.gasPrice - ) { - let minFee = BlocksoftExternalSettings.getStatic( - this._mainCurrencyCode + '_FORCE_PRICE' - ); - if (typeof minFee !== 'undefined' && minFee > 1) { - additionalData.gasPrice = minFee; - additionalData.gasPriceTitle = 'speed_blocks_2'; - } else { - let defaultFee = BlocksoftExternalSettings.getStatic( - this._mainCurrencyCode + '_PRICE' - ); - if (typeof defaultFee === 'undefined' || !defaultFee) { - defaultFee = 5000000000; - } - if (this._etherscanApiPathForFee) { - const tmpPrice = await BnbSmartNetworkPrices.getFees( - this._mainCurrencyCode, - this._etherscanApiPathForFee, - defaultFee, - 'BnbSmartTransferProcessor.getFeeRate' - ); - if (tmpPrice * 1 > defaultFee * 1) { - defaultFee = tmpPrice * 1; - } - } - additionalData.gasPrice = defaultFee; - additionalData.gasPriceTitle = 'speed_blocks_2'; - } - } - return super.getFeeRate(data, privateData, additionalData); - } -} +/** + * @author Ksu + * @version 0.20 + * https://api.etherscan.io/api?module=gastracker&action=gasoracle&apikey=YourApiKeyToken + */ +import { AirDAOBlockchainTypes } from '../AirDAOBlockchainTypes'; +import EthTransferProcessor from '../eth/EthTransferProcessor'; + +import BnbSmartNetworkPrices from './basic/BnbSmartNetworkPrices'; +import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings'; + +export default class BnbSmartTransferProcessor + extends EthTransferProcessor + implements AirDAOBlockchainTypes.TransferProcessor +{ + async getFeeRate( + data: AirDAOBlockchainTypes.TransferData, + privateData: AirDAOBlockchainTypes.TransferPrivateData, + additionalData: {} = {} + ): Promise { + if ( + typeof additionalData.gasPrice === 'undefined' || + !additionalData.gasPrice + ) { + const minFee = BlocksoftExternalSettings.getStatic( + this._mainCurrencyCode + '_FORCE_PRICE' + ); + if (typeof minFee !== 'undefined' && minFee > 1) { + additionalData.gasPrice = minFee; + additionalData.gasPriceTitle = 'speed_blocks_2'; + } else { + let defaultFee = BlocksoftExternalSettings.getStatic( + this._mainCurrencyCode + '_PRICE' + ); + if (typeof defaultFee === 'undefined' || !defaultFee) { + defaultFee = 5000000000; + } + if (this._etherscanApiPathForFee) { + const tmpPrice = await BnbSmartNetworkPrices.getFees( + this._mainCurrencyCode, + this._etherscanApiPathForFee, + defaultFee, + 'BnbSmartTransferProcessor.getFeeRate' + ); + if (tmpPrice * 1 > defaultFee * 1) { + defaultFee = tmpPrice * 1; + } + } + additionalData.gasPrice = defaultFee; + additionalData.gasPriceTitle = 'speed_blocks_2'; + } + } + return super.getFeeRate(data, privateData, additionalData); + } +} diff --git a/crypto/blockchains/bnb_smart/BnbSmartTransferProcessorErc20.ts b/crypto/blockchains/bnb_smart/BnbSmartTransferProcessorErc20.ts index 2aac7d29a..ef91df511 100644 --- a/crypto/blockchains/bnb_smart/BnbSmartTransferProcessorErc20.ts +++ b/crypto/blockchains/bnb_smart/BnbSmartTransferProcessorErc20.ts @@ -1,78 +1,80 @@ -/** - * @version 0.20 - */ -import { BlocksoftBlockchainTypes } from '../BlocksoftBlockchainTypes'; -import EthTransferProcessorErc20 from '../eth/EthTransferProcessorErc20'; -import BnbSmartNetworkPrices from './basic/BnbSmartNetworkPrices'; -import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings'; - -export default class BnbSmartTransferProcessorErc20 - extends EthTransferProcessorErc20 - implements BlocksoftBlockchainTypes.TransferProcessor -{ - async getFeeRate( - data: BlocksoftBlockchainTypes.TransferData, - privateData: BlocksoftBlockchainTypes.TransferPrivateData, - additionalData: {} = {} - ): Promise { - if ( - typeof additionalData.gasPrice === 'undefined' || - !additionalData.gasPrice - ) { - let minFee = BlocksoftExternalSettings.getStatic( - this._mainCurrencyCode + '_FORCE_PRICE_ERC20' - ); - if (typeof minFee !== 'undefined' && minFee > 1) { - additionalData.gasPrice = minFee; - additionalData.gasPriceTitle = 'speed_blocks_2'; - } else { - let defaultFee = BlocksoftExternalSettings.getStatic( - this._mainCurrencyCode + '_PRICE' - ); - if (typeof defaultFee === 'undefined' || !defaultFee) { - defaultFee = 5000000000; - } - if (!this._etherscanApiPathForFee) { - additionalData.gasPrice = defaultFee; - additionalData.gasPriceTitle = 'speed_blocks_2'; - } else { - additionalData.gasPrice = await BnbSmartNetworkPrices.getFees( - this._mainCurrencyCode, - this._etherscanApiPathForFee, - defaultFee, - 'BnbSmartTransferProcessorErc20.getFeeRate' - ); - additionalData.gasPriceTitle = 'speed_blocks_2'; - } - } - } - const result = await super.getFeeRate(data, privateData, additionalData); - result.shouldShowFees = true; - return result; - } - - async checkTransferHasError( - data: BlocksoftBlockchainTypes.CheckTransferHasErrorData - ): Promise { - // @ts-ignore - const balance = - data.addressFrom && data.addressFrom !== '' - ? await this._web3.eth.getBalance(data.addressFrom) - : 0; - if (balance > 0) { - return { isOk: true }; - } else { - const title = - this._mainCurrencyCode === 'BNB' - ? 'BNB Smart Chain' - : this._mainCurrencyCode; - // @ts-ignore - return { - isOk: false, - code: 'TOKEN', - parentBlockchain: title, - parentCurrency: title - }; - } - } -} +/** + * @version 0.20 + */ +import { AirDAOBlockchainTypes } from '../AirDAOBlockchainTypes'; +import EthTransferProcessorErc20 from '../eth/EthTransferProcessorErc20'; +import BnbSmartNetworkPrices from './basic/BnbSmartNetworkPrices'; +import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings'; + +export default class BnbSmartTransferProcessorErc20 + extends EthTransferProcessorErc20 + implements AirDAOBlockchainTypes.TransferProcessor +{ + async getFeeRate( + data: AirDAOBlockchainTypes.TransferData, + privateData: AirDAOBlockchainTypes.TransferPrivateData, + additionalData: {} = {} + ): Promise { + if ( + typeof additionalData.gasPrice === 'undefined' || + !additionalData.gasPrice + ) { + let minFee = BlocksoftExternalSettings.getStatic( + this._mainCurrencyCode + '_FORCE_PRICE_ERC20' + ); + if (typeof minFee !== 'undefined' && minFee > 1) { + additionalData.gasPrice = minFee; + additionalData.gasPriceTitle = 'speed_blocks_2'; + } else { + let defaultFee = BlocksoftExternalSettings.getStatic( + this._mainCurrencyCode + '_PRICE' + ); + if (typeof defaultFee === 'undefined' || !defaultFee) { + defaultFee = 5000000000; + } + if (!this._etherscanApiPathForFee) { + additionalData.gasPrice = defaultFee; + additionalData.gasPriceTitle = 'speed_blocks_2'; + } else { + additionalData.gasPrice = await BnbSmartNetworkPrices.getFees( + this._mainCurrencyCode, + this._etherscanApiPathForFee, + defaultFee, + 'BnbSmartTransferProcessorErc20.getFeeRate' + ); + additionalData.gasPriceTitle = 'speed_blocks_2'; + } + } + } + const result = await super.getFeeRate(data, privateData, additionalData); + result.shouldShowFees = true; + return result; + } + + async checkTransferHasError( + data: AirDAOBlockchainTypes.CheckTransferHasErrorData + ): Promise { + // @ts-ignore + const balance = + data.addressFrom && data.addressFrom !== '' + ? await this._web3.eth.getBalance(data.addressFrom) + : 0; + if (balance > 0) { + return { isOk: true }; + } else { + const title = + this._mainCurrencyCode === 'BNB' + ? 'BNB Smart Chain' + : this._mainCurrencyCode; + // @ts-ignore + return { + isOk: false, + code: 'TOKEN', + parentBlockchain: + title as AirDAOBlockchainTypes.CheckTransferHasErrorResult['parentBlockchain'], + parentCurrency: + title as AirDAOBlockchainTypes.CheckTransferHasErrorResult['parentCurrency'] + }; + } + } +} diff --git a/crypto/blockchains/bnb_smart/basic/BnbSmartNetworkPrices.js b/crypto/blockchains/bnb_smart/basic/BnbSmartNetworkPrices.js index a0363d87d..ea9e31495 100644 --- a/crypto/blockchains/bnb_smart/basic/BnbSmartNetworkPrices.js +++ b/crypto/blockchains/bnb_smart/basic/BnbSmartNetworkPrices.js @@ -1,81 +1,73 @@ -/** - * @version 0.20 - */ -import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; -import BlocksoftAxios from '../../../common/BlocksoftAxios'; -import BlocksoftUtils from '../../../common/BlocksoftUtils'; -import BlocksoftExternalSettings from '../../../common/BlocksoftExternalSettings'; -import config from '@app/config/config'; - -const CACHE_VALID_TIME = 120000; // 2 minute -const CACHE_FEES = { - BNB: { - fee: 5000000000, - ts: 0 - } -}; - -class BnbSmartNetworkPrices { - async getFees( - mainCurrencyCode, - etherscanApiPath, - defaultFee = 5000000000, - source = '' - ) { - const now = new Date().getTime(); - if ( - typeof CACHE_FEES[mainCurrencyCode] !== 'undefined' && - now - CACHE_FEES[mainCurrencyCode].ts < CACHE_VALID_TIME - ) { - return CACHE_FEES[mainCurrencyCode].fee; - } - const tmp = etherscanApiPath.split('/'); - const feesApiPath = `https://${tmp[2]}/api?module=proxy&action=eth_gasPrice&apikey=YourApiKeyToken`; - BlocksoftCryptoLog.log( - mainCurrencyCode + ' BnbSmartNetworkPricesProvider.getFees no cache load' - ); - try { - const res = await BlocksoftAxios.getWithoutBraking(feesApiPath); - if ( - res && - typeof res.data !== 'undefined' && - typeof res.data.result !== 'undefined' - ) { - const tmp = BlocksoftUtils.hexToDecimal(res.data.result); - if (tmp * 1 > 0) { - CACHE_FEES[mainCurrencyCode] = { - fee: (tmp * 1).toString().substr(0, 11), - time: now - }; - } else if ( - typeof CACHE_FEES[mainCurrencyCode] === 'undefined' || - !CACHE_FEES[mainCurrencyCode].fee - ) { - CACHE_FEES[mainCurrencyCode].fee = - await BlocksoftExternalSettings.getStatic('BNB_SMART_PRICE'); - } - } - } catch (e) { - if (config.debug.cryptoErrors) { - console.log( - mainCurrencyCode + - ' BnbSmartNetworkPricesProvider.getOnlyFees loaded prev fee as error' + - e.message - ); - } - BlocksoftCryptoLog.log( - mainCurrencyCode + - ' BnbSmartNetworkPricesProvider.getOnlyFees loaded prev fee as error' + - e.message - ); - // do nothing - } - - return typeof CACHE_FEES[mainCurrencyCode] !== 'undefined' - ? CACHE_FEES[mainCurrencyCode].fee - : defaultFee; - } -} - -const singleton = new BnbSmartNetworkPrices(); -export default singleton; +/** + * @version 0.20 + */ +import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; +import BlocksoftAxios from '../../../common/BlocksoftAxios'; +import BlocksoftUtils from '../../../common/BlocksoftUtils'; +import BlocksoftExternalSettings from '../../../common/BlocksoftExternalSettings'; + +const CACHE_VALID_TIME = 120000; // 2 minute +const CACHE_FEES = { + BNB: { + fee: 5000000000, + ts: 0 + } +}; + +class BnbSmartNetworkPrices { + async getFees( + mainCurrencyCode, + etherscanApiPath, + defaultFee = 5000000000, + source = '' + ) { + const now = new Date().getTime(); + if ( + typeof CACHE_FEES[mainCurrencyCode] !== 'undefined' && + now - CACHE_FEES[mainCurrencyCode].ts < CACHE_VALID_TIME + ) { + return CACHE_FEES[mainCurrencyCode].fee; + } + const tmp = etherscanApiPath.split('/'); + const feesApiPath = `https://${tmp[2]}/api?module=proxy&action=eth_gasPrice&apikey=YourApiKeyToken`; + BlocksoftCryptoLog.log( + mainCurrencyCode + ' BnbSmartNetworkPricesProvider.getFees no cache load' + ); + try { + const res = await BlocksoftAxios.getWithoutBraking(feesApiPath); + if ( + res && + typeof res.data !== 'undefined' && + typeof res.data.result !== 'undefined' + ) { + const tmp = BlocksoftUtils.hexToDecimal(res.data.result); + if (tmp * 1 > 0) { + CACHE_FEES[mainCurrencyCode] = { + fee: (tmp * 1).toString().substr(0, 11), + time: now + }; + } else if ( + typeof CACHE_FEES[mainCurrencyCode] === 'undefined' || + !CACHE_FEES[mainCurrencyCode].fee + ) { + CACHE_FEES[mainCurrencyCode].fee = + await BlocksoftExternalSettings.getStatic('BNB_SMART_PRICE'); + } + } + } catch (e) { + BlocksoftCryptoLog.log( + mainCurrencyCode + + ' BnbSmartNetworkPricesProvider.getOnlyFees loaded prev fee as error' + + e.message + ); + // do nothing + } + + return typeof CACHE_FEES[mainCurrencyCode] !== 'undefined' + ? CACHE_FEES[mainCurrencyCode].fee + : defaultFee; + } +} + +const singleton = new BnbSmartNetworkPrices(); +export default singleton; From b608df5c53674f53210cbe3e0df7445c11b291b1 Mon Sep 17 00:00:00 2001 From: JavidHaji-zada Date: Wed, 26 Jul 2023 12:53:31 +0400 Subject: [PATCH 017/509] converted bsv --- crypto/blockchains/bsv/BsvScannerProcessor.js | 4 - .../blockchains/bsv/BsvTransferProcessor.ts | 76 +++++++++++-------- .../bsv/providers/BsvSendProvider.ts | 26 +------ crypto/blockchains/bsv/stores/BsvTmpDS.js | 59 -------------- crypto/blockchains/bsv/stores/BsvTmpDS.ts | 70 +++++++++++++++++ crypto/blockchains/bsv/tx/BsvTxBuilder.ts | 73 +++++++++++------- src/appTypes/database/DatabaseTable.ts | 1 + src/database/Database.ts | 27 ++++++- src/database/main.ts | 3 +- src/database/models/index.ts | 1 + .../models/transaction-scanners-tmp.ts | 22 ++++++ src/database/schemas/index.ts | 3 +- .../schemas/transaction-scanners-tmp.ts | 32 ++++++++ 13 files changed, 249 insertions(+), 148 deletions(-) delete mode 100644 crypto/blockchains/bsv/stores/BsvTmpDS.js create mode 100644 crypto/blockchains/bsv/stores/BsvTmpDS.ts create mode 100644 src/database/models/transaction-scanners-tmp.ts create mode 100644 src/database/schemas/transaction-scanners-tmp.ts diff --git a/crypto/blockchains/bsv/BsvScannerProcessor.js b/crypto/blockchains/bsv/BsvScannerProcessor.js index 63bb29f28..e99e0a5c3 100644 --- a/crypto/blockchains/bsv/BsvScannerProcessor.js +++ b/crypto/blockchains/bsv/BsvScannerProcessor.js @@ -2,7 +2,6 @@ * @version 0.5 */ -import config from '@app/config/config'; import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; import BlocksoftUtils from '@crypto/common/BlocksoftUtils'; @@ -367,9 +366,6 @@ export default class BsvScannerProcessor { } return tx; } catch (e) { - if (config.debug.cryptoErrors) { - console.log('BsvScannerProcessor _unifyTransaction error ' + e.message); - } throw e; } } diff --git a/crypto/blockchains/bsv/BsvTransferProcessor.ts b/crypto/blockchains/bsv/BsvTransferProcessor.ts index 327652c36..e8226c606 100644 --- a/crypto/blockchains/bsv/BsvTransferProcessor.ts +++ b/crypto/blockchains/bsv/BsvTransferProcessor.ts @@ -1,39 +1,53 @@ /** * @version 0.5 */ -import { BlocksoftBlockchainTypes } from '../BlocksoftBlockchainTypes' -import DogeTransferProcessor from '../doge/DogeTransferProcessor' -import DogeTxInputsOutputs from '../doge/tx/DogeTxInputsOutputs' -import BsvTxBuilder from './tx/BsvTxBuilder' -import BsvUnspentsProvider from './providers/BsvUnspentsProvider' -import BsvSendProvider from "@crypto/blockchains/bsv/providers/BsvSendProvider"; +import { AirDAOBlockchainTypes } from '../AirDAOBlockchainTypes'; +import DogeTransferProcessor from '../doge/DogeTransferProcessor'; +import DogeTxInputsOutputs from '../doge/tx/DogeTxInputsOutputs'; +import BsvTxBuilder from './tx/BsvTxBuilder'; +import BsvUnspentsProvider from './providers/BsvUnspentsProvider'; +import BsvSendProvider from '@crypto/blockchains/bsv/providers/BsvSendProvider'; -export default class BsvTransferProcessor extends DogeTransferProcessor implements BlocksoftBlockchainTypes.TransferProcessor { +export default class BsvTransferProcessor + extends DogeTransferProcessor + implements AirDAOBlockchainTypes.TransferProcessor +{ + _trezorServerCode = ''; - _trezorServerCode = '' + _builderSettings: AirDAOBlockchainTypes.BuilderSettings = { + minOutputDustReadable: 0.000005, + minChangeDustReadable: 0.00001, + feeMaxForByteSatoshi: 10000, // for tx builder + feeMaxAutoReadable2: 0.2, // for fee calc, + feeMaxAutoReadable6: 0.1, // for fee calc + feeMaxAutoReadable12: 0.05, // for fee calc + changeTogether: true, + minRbfStepSatoshi: 10, + minSpeedUpMulti: 1.5 + }; - _builderSettings: BlocksoftBlockchainTypes.BuilderSettings = { - minOutputDustReadable: 0.000005, - minChangeDustReadable: 0.00001, - feeMaxForByteSatoshi: 10000, // for tx builder - feeMaxAutoReadable2: 0.2, // for fee calc, - feeMaxAutoReadable6: 0.1, // for fee calc - feeMaxAutoReadable12: 0.05, // for fee calc - changeTogether: true, - minRbfStepSatoshi: 10, - minSpeedUpMulti : 1.5 - } + canRBF( + data: AirDAOBlockchainTypes.DbAccount, + transaction: AirDAOBlockchainTypes.DbTransaction + ): boolean { + return false; + } - canRBF(data: BlocksoftBlockchainTypes.DbAccount, transaction: BlocksoftBlockchainTypes.DbTransaction): boolean { - return false - } - - _initProviders() { - if (this._initedProviders) return false - this.unspentsProvider = new BsvUnspentsProvider(this._settings, this._trezorServerCode) - this.sendProvider = new BsvSendProvider(this._settings, this._trezorServerCode) - this.txPrepareInputsOutputs = new DogeTxInputsOutputs(this._settings, this._builderSettings) - this.txBuilder = new BsvTxBuilder(this._settings, this._builderSettings) - this._initedProviders = true - } + _initProviders() { + if (this._initedProviders) return false; + this.unspentsProvider = new BsvUnspentsProvider( + this._settings, + this._trezorServerCode + ); + this.sendProvider = new BsvSendProvider( + this._settings, + this._trezorServerCode + ); + this.txPrepareInputsOutputs = new DogeTxInputsOutputs( + this._settings, + this._builderSettings + ); + this.txBuilder = new BsvTxBuilder(this._settings, this._builderSettings); + this._initedProviders = true; + } } diff --git a/crypto/blockchains/bsv/providers/BsvSendProvider.ts b/crypto/blockchains/bsv/providers/BsvSendProvider.ts index e223d09fd..4da3a008b 100644 --- a/crypto/blockchains/bsv/providers/BsvSendProvider.ts +++ b/crypto/blockchains/bsv/providers/BsvSendProvider.ts @@ -1,15 +1,14 @@ /** * @version 0.5 */ -import { BlocksoftBlockchainTypes } from '@crypto/blockchains/BlocksoftBlockchainTypes'; +import { AirDAOBlockchainTypes } from '@crypto/blockchains/AirDAOBlockchainTypes'; import DogeSendProvider from '@crypto/blockchains/doge/providers/DogeSendProvider'; import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; -import config from '@app/config/config'; export default class BsvSendProvider extends DogeSendProvider - implements BlocksoftBlockchainTypes.SendProvider + implements AirDAOBlockchainTypes.SendProvider { async sendTx( hex: string, @@ -25,7 +24,7 @@ export default class BsvSendProvider logData ); - let link = 'https://api.whatsonchain.com/v1/bsv/main/tx/raw'; + const link = 'https://api.whatsonchain.com/v1/bsv/main/tx/raw'; //logData = await this._check(hex, subtitle, txRBF, logData) @@ -33,12 +32,6 @@ export default class BsvSendProvider try { res = await BlocksoftAxios.post(link, { txhex: hex }); } catch (e) { - if (config.debug.cryptoErrors) { - console.log( - this._settings.currencyCode + ' BsvSendProvider.sendTx error ', - e - ); - } if (subtitle.indexOf('rawSend') !== -1) { throw e; } @@ -46,13 +39,6 @@ export default class BsvSendProvider logData.error = e.message; await this._checkError(hex, subtitle, txRBF, logData); } catch (e2) { - if (config.debug.cryptoErrors) { - console.log( - this._settings.currencyCode + - ' DogeSendProvider.send proxy error errorTx ' + - e.message - ); - } BlocksoftCryptoLog.log( this._settings.currencyCode + ' DogeSendProvider.send proxy error errorTx ' + @@ -98,12 +84,6 @@ export default class BsvSendProvider } } if (typeof res.data === 'undefined' || !res.data) { - if (config.debug.cryptoErrors) { - console.log( - this._settings.currencyCode + 'BsvSendProvider.send no txid', - res.data - ); - } throw new Error('SERVER_RESPONSE_NOT_CONNECTED'); } diff --git a/crypto/blockchains/bsv/stores/BsvTmpDS.js b/crypto/blockchains/bsv/stores/BsvTmpDS.js deleted file mode 100644 index 6bff8a13e..000000000 --- a/crypto/blockchains/bsv/stores/BsvTmpDS.js +++ /dev/null @@ -1,59 +0,0 @@ -import Database from '@app/appstores/DataSource/Database'; - -const tableName = 'transactions_scanners_tmp' - -class BsvTmpDS { - /** - * @type {string} - * @private - */ - _currencyCode = 'BSV' - - _valKey = 'txs' - - _isSaved = false - - async getCache() { - const res = await Database.query(` - SELECT id, tmp_key, tmp_sub_key, tmp_val - FROM ${tableName} - WHERE currency_code='${this._currencyCode}' - AND tmp_key='${this._valKey}' - `) - let tmp = {} - const idsToRemove = [] - if (res.array) { - let row - for (row of res.array) { - if (typeof tmp[row.tmp_sub_key] !== 'undefined') { - idsToRemove.push(row.id) - } else { - try { - tmp[row.tmp_sub_key] = JSON.parse(Database.unEscapeString(row.tmp_val)) - } catch (e) { - idsToRemove.push(row.id) - } - } - } - if (idsToRemove.length > 0) { - await Database.query(`DELETE FROM ${tableName} WHERE id IN (${idsToRemove.join(',')})`) - } - } - return tmp - } - - async saveCache(txid, value) { - const tmp = Database.escapeString(JSON.stringify(value)) - const now = new Date().toISOString() - const prepared = [{ - currency_code: this._currencyCode, - tmp_key: this._valKey, - tmp_sub_key: txid, - tmp_val: tmp, - created_at: now - }] - await Database.setTableName(tableName).setInsertData({insertObjs: prepared}).insert() - } -} - -export default new BsvTmpDS() diff --git a/crypto/blockchains/bsv/stores/BsvTmpDS.ts b/crypto/blockchains/bsv/stores/BsvTmpDS.ts new file mode 100644 index 000000000..441475a74 --- /dev/null +++ b/crypto/blockchains/bsv/stores/BsvTmpDS.ts @@ -0,0 +1,70 @@ +/* eslint-disable camelcase */ +import { DatabaseTable } from '@appTypes'; +import { Database } from '@database'; +import { TransactionScannersTmpDBModel } from '@database/models'; +import { Q } from '@nozbe/watermelondb'; + +const tableName = DatabaseTable.TransactionScannersTmp; + +class BsvTmpDS { + /** + * @type {string} + * @private + */ + _currencyCode = 'BSV'; + + _valKey = 'txs'; + + _isSaved = false; + + async getCache() { + const res: TransactionScannersTmpDBModel[] | undefined = + (await Database.query( + tableName, + Q.and( + Q.where('currency_code', Q.eq(this._currencyCode)), + Q.where('tmp_key', Q.eq(this._valKey)) + ) + )) as TransactionScannersTmpDBModel[]; + let tmp = {}; + const idsToRemove = []; + if (res) { + for (const row of res) { + if (typeof tmp[row.tmp_sub_key] !== 'undefined') { + idsToRemove.push(row.id); + } else { + try { + tmp[row.tmp_sub_key] = JSON.parse( + Database.unEscapeString(row.tmp_val) + ); + } catch (e) { + idsToRemove.push(row.id); + } + } + } + if (idsToRemove.length > 0) { + // TODO check this + await Database.unsafeRawQuery( + tableName, + `DELETE FROM ${tableName} WHERE id IN (${idsToRemove.join(',')})` + ); + } + } + return tmp; + } + + async saveCache(txid: string, value: unknown) { + const tmp = Database.escapeString(JSON.stringify(value)); + const now = new Date().toISOString(); + const prepared = { + currency_code: this._currencyCode, + tmp_key: this._valKey, + tmp_sub_key: txid, + tmp_val: tmp, + created_at: now + }; + await Database.createModel(DatabaseTable.TransactionScannersTmp, prepared); + } +} + +export default new BsvTmpDS(); diff --git a/crypto/blockchains/bsv/tx/BsvTxBuilder.ts b/crypto/blockchains/bsv/tx/BsvTxBuilder.ts index 4887aa6e3..f8bfc4c7a 100644 --- a/crypto/blockchains/bsv/tx/BsvTxBuilder.ts +++ b/crypto/blockchains/bsv/tx/BsvTxBuilder.ts @@ -1,35 +1,52 @@ /** * @version 0.5 */ -import { BlocksoftBlockchainTypes } from '../../BlocksoftBlockchainTypes' -import DogeTxBuilder from '../../doge/tx/DogeTxBuilder' -import BtcCashUtils from '../../bch/ext/BtcCashUtils' -import { ECPair, payments, TransactionBuilder } from 'bitcoinjs-lib' +import { AirDAOBlockchainTypes } from '../../AirDAOBlockchainTypes'; +import DogeTxBuilder from '../../doge/tx/DogeTxBuilder'; +import BtcCashUtils from '../../bch/ext/BtcCashUtils'; +import { ECPair, payments, TransactionBuilder } from 'bitcoinjs-lib'; - -export default class BsvTxBuilder extends DogeTxBuilder implements BlocksoftBlockchainTypes.TxBuilder { - - _getRawTxValidateKeyPair(privateData: BlocksoftBlockchainTypes.TransferPrivateData, data: BlocksoftBlockchainTypes.TransferData): void { - this.keyPair = false - try { - this.keyPair = ECPair.fromWIF(privateData.privateKey, this._bitcoinNetwork) - const address = payments.p2pkh({ - pubkey: this.keyPair.publicKey, - network: this._bitcoinNetwork - }).address - const legacyAddress = BtcCashUtils.toLegacyAddress(data.addressFrom) - if (address !== data.addressFrom && address !== legacyAddress) { - // noinspection ExceptionCaughtLocallyJS - throw new Error('not valid signing address ' + data.addressFrom + ' != ' + address + ' != ' + legacyAddress) - } - } catch (e) { - e.message += ' in privateKey BSV signature check ' - throw e - } +export default class BsvTxBuilder + extends DogeTxBuilder + implements AirDAOBlockchainTypes.TxBuilder +{ + _getRawTxValidateKeyPair( + privateData: AirDAOBlockchainTypes.TransferPrivateData, + data: AirDAOBlockchainTypes.TransferData + ): void { + this.keyPair = false; + try { + this.keyPair = ECPair.fromWIF( + privateData.privateKey, + this._bitcoinNetwork + ); + const address = payments.p2pkh({ + pubkey: this.keyPair.publicKey, + network: this._bitcoinNetwork + }).address; + const legacyAddress = BtcCashUtils.toLegacyAddress(data.addressFrom); + if (address !== data.addressFrom && address !== legacyAddress) { + // noinspection ExceptionCaughtLocallyJS + throw new Error( + 'not valid signing address ' + + data.addressFrom + + ' != ' + + address + + ' != ' + + legacyAddress + ); + } + } catch (e) { + e.message += ' in privateKey BSV signature check '; + throw e; } + } - _getRawTxAddOutput(txb: TransactionBuilder, output: BlocksoftBlockchainTypes.OutputTx): void { - const to = BtcCashUtils.toLegacyAddress(output.to) - txb.addOutput(to, output.amount * 1) - } + _getRawTxAddOutput( + txb: TransactionBuilder, + output: AirDAOBlockchainTypes.OutputTx + ): void { + const to = BtcCashUtils.toLegacyAddress(output.to); + txb.addOutput(to, output.amount * 1); + } } diff --git a/src/appTypes/database/DatabaseTable.ts b/src/appTypes/database/DatabaseTable.ts index b833b166a..2d67bec06 100644 --- a/src/appTypes/database/DatabaseTable.ts +++ b/src/appTypes/database/DatabaseTable.ts @@ -1,3 +1,4 @@ export enum DatabaseTable { + TransactionScannersTmp = 'transactions_scanners_tmp', Wallets = 'wallets' } diff --git a/src/database/Database.ts b/src/database/Database.ts index c684503bc..a8a7b8db1 100644 --- a/src/database/Database.ts +++ b/src/database/Database.ts @@ -1,7 +1,8 @@ /* eslint-disable @typescript-eslint/no-non-null-assertion */ import { DatabaseTable } from '@appTypes'; import { database } from './main'; -import { Database as WDB } from '@nozbe/watermelondb'; +import { Q, Database as WDB } from '@nozbe/watermelondb'; +import { Clause } from '@nozbe/watermelondb/QueryDescription'; class Database { private db: WDB | undefined; @@ -19,6 +20,7 @@ class Database { private async init() { this.db = database; + this.reset(); } getDatabase() { @@ -46,6 +48,29 @@ class Database { // ignore } } + + unEscapeString(goodString: string) { + if (!goodString) return false; + return goodString.replace(/quote/g, "'"); + } + + escapeString(badString: string) { + if (!badString) return false; + return badString.replace(/[']/g, 'quote'); + } + + async query(table: DatabaseTable, ...args: Clause[]) { + if (!this.db) this.init(); + try { + return await this.db?.get(table).query(args).fetch(); + } catch (error) { + // TODO ignore + } + } + + async unsafeRawQuery(table: DatabaseTable, query: string) { + return await database.get(table).query(Q.unsafeSqlQuery(query)).fetch(); + } } export default new Database(); diff --git a/src/database/main.ts b/src/database/main.ts index 2f0b68d14..a39889ff1 100644 --- a/src/database/main.ts +++ b/src/database/main.ts @@ -3,6 +3,7 @@ import SQLiteAdapter from '@nozbe/watermelondb/adapters/sqlite'; import { Database } from '@nozbe/watermelondb'; import { WalletDBModel } from './models'; import { schema } from './schemas'; +import { TransactionScannersTmpDBModel } from './models/transaction-scanners-tmp'; const adapter = new SQLiteAdapter({ schema, @@ -14,5 +15,5 @@ const adapter = new SQLiteAdapter({ export const database = new Database({ adapter, - modelClasses: [WalletDBModel] + modelClasses: [WalletDBModel, TransactionScannersTmpDBModel] }); diff --git a/src/database/models/index.ts b/src/database/models/index.ts index 3c5958cf6..f9548aafc 100644 --- a/src/database/models/index.ts +++ b/src/database/models/index.ts @@ -1 +1,2 @@ +export * from './transaction-scanners-tmp'; export * from './wallet'; diff --git a/src/database/models/transaction-scanners-tmp.ts b/src/database/models/transaction-scanners-tmp.ts new file mode 100644 index 000000000..8b07454ac --- /dev/null +++ b/src/database/models/transaction-scanners-tmp.ts @@ -0,0 +1,22 @@ +/* eslint-disable camelcase */ +import { DatabaseTable } from '@appTypes'; +import { Model } from '@nozbe/watermelondb'; +import { text, field } from '@nozbe/watermelondb/decorators'; + +export class TransactionScannersTmpDBModel extends Model { + static table = DatabaseTable.TransactionScannersTmp; + + // define fields + // @ts-ignore + @text('currency_code') name: string; + // @ts-ignore + @text('address') address: string; + // @ts-ignore + @text('tmp_key') tmp_key: string; + // @ts-ignore + @text('tmp_sub_key') tmp_sub_key: string; + // @ts-ignore + @text('tmp_val') tmp_val: string; + // @ts-ignore + @field('created_at') created_at: number; +} diff --git a/src/database/schemas/index.ts b/src/database/schemas/index.ts index 98c607e7c..29f4ade1a 100644 --- a/src/database/schemas/index.ts +++ b/src/database/schemas/index.ts @@ -1,7 +1,8 @@ import { appSchema } from '@nozbe/watermelondb'; import { WalletTable } from './wallet'; +import { TransactionScannersTmpTable } from './transaction-scanners-tmp'; export const schema = appSchema({ version: 1, - tables: [WalletTable] + tables: [WalletTable, TransactionScannersTmpTable] }); diff --git a/src/database/schemas/transaction-scanners-tmp.ts b/src/database/schemas/transaction-scanners-tmp.ts new file mode 100644 index 000000000..aa3fd79bb --- /dev/null +++ b/src/database/schemas/transaction-scanners-tmp.ts @@ -0,0 +1,32 @@ +import { DatabaseTable } from '@appTypes'; +import { tableSchema } from '@nozbe/watermelondb'; + +export const TransactionScannersTmpTable = tableSchema({ + name: DatabaseTable.TransactionScannersTmp, + columns: [ + { + name: 'currency_code', + type: 'string' + }, + { + name: 'address', + type: 'string' + }, + { + name: 'tmp_key', + type: 'string' + }, + { + name: 'tmp_sub_key', + type: 'number' + }, + { + name: 'tmp_val', + type: 'string' + }, + { + name: 'created_at', + type: 'number' + } + ] +}); From 6f0b89c889c1fe43a3d9cd7a87a2db39d4ae4c54 Mon Sep 17 00:00:00 2001 From: JavidHaji-zada Date: Tue, 1 Aug 2023 20:25:24 +0400 Subject: [PATCH 018/509] converted btc --- crypto/TransactionFilterTypeDict.ts | 10 + ...nerProcessor.js => BtcScannerProcessor.ts} | 78 ++- .../blockchains/btc/BtcTransferProcessor.ts | 71 ++- .../btc/address/BtcAddressProcessor.js | 58 +- .../btc/address/BtcSegwitAddressProcessor.js | 29 +- .../BtcSegwitCompatibleAddressProcessor.js | 28 +- .../btc/basic/BtcFindAddressFunction.js | 126 ---- .../btc/basic/BtcFindAddressFunction.ts | 159 +++++ .../blockchains/btc/basic/BtcNetworkPrices.ts | 28 +- .../btc/providers/BtcUnspentsProvider.ts | 153 +++-- crypto/blockchains/btc/tx/BtcTxBuilder.ts | 17 +- .../blockchains/btc/tx/BtcTxInputsOutputs.ts | 26 +- src/appTypes/database/DatabaseTable.ts | 4 +- src/daemons/Daemon.js | 42 ++ src/daemons/DaemonCache.js | 96 +++ src/daemons/Update.js | 48 ++ .../UpdateAccountBalanceAndTransactions.js | 444 +++++++++++++ .../UpdateAccountBalanceAndTransactionsHD.js | 319 ++++++++++ .../back/UpdateAccountPendingTransactions.js | 27 + src/daemons/back/UpdateAppNewsDaemon.js | 182 ++++++ src/daemons/back/UpdateAppTasksDaemon.js | 62 ++ src/daemons/back/UpdateCardsDaemon.js | 188 ++++++ src/daemons/back/UpdateCashBackDataDaemon.js | 104 +++ src/daemons/back/UpdateCurrencyRateDaemon.js | 122 ++++ src/daemons/back/UpdateOneByOneDaemon.js | 140 +++++ src/daemons/back/UpdateTradeOrdersDaemon.js | 413 ++++++++++++ src/daemons/back/UpdateWalletsDaemon.js | 130 ++++ .../AppTasksDiscoverBalancesHidden.js | 70 +++ .../AppTasksDiscoverBalancesNotAdded.js | 64 ++ .../back/apptasks/AppTasksDiscoverHD.js | 35 ++ .../apputils/AccountTransactionsRecheck.js | 420 +++++++++++++ src/daemons/view/UpdateAccountListDaemon.js | 592 ++++++++++++++++++ src/database/Database.ts | 6 + src/database/main.ts | 9 +- src/database/models/account.ts | 44 ++ src/database/models/wallet-pub.ts | 46 ++ src/database/schemas/account.ts | 69 ++ src/database/schemas/index.ts | 9 +- src/database/schemas/wallet-pub.ts | 77 +++ 39 files changed, 4207 insertions(+), 338 deletions(-) create mode 100644 crypto/TransactionFilterTypeDict.ts rename crypto/blockchains/btc/{BtcScannerProcessor.js => BtcScannerProcessor.ts} (91%) delete mode 100644 crypto/blockchains/btc/basic/BtcFindAddressFunction.js create mode 100644 crypto/blockchains/btc/basic/BtcFindAddressFunction.ts create mode 100644 src/daemons/Daemon.js create mode 100644 src/daemons/DaemonCache.js create mode 100644 src/daemons/Update.js create mode 100644 src/daemons/back/UpdateAccountBalanceAndTransactions.js create mode 100644 src/daemons/back/UpdateAccountBalanceAndTransactionsHD.js create mode 100644 src/daemons/back/UpdateAccountPendingTransactions.js create mode 100644 src/daemons/back/UpdateAppNewsDaemon.js create mode 100644 src/daemons/back/UpdateAppTasksDaemon.js create mode 100644 src/daemons/back/UpdateCardsDaemon.js create mode 100644 src/daemons/back/UpdateCashBackDataDaemon.js create mode 100644 src/daemons/back/UpdateCurrencyRateDaemon.js create mode 100644 src/daemons/back/UpdateOneByOneDaemon.js create mode 100644 src/daemons/back/UpdateTradeOrdersDaemon.js create mode 100644 src/daemons/back/UpdateWalletsDaemon.js create mode 100644 src/daemons/back/apptasks/AppTasksDiscoverBalancesHidden.js create mode 100644 src/daemons/back/apptasks/AppTasksDiscoverBalancesNotAdded.js create mode 100644 src/daemons/back/apptasks/AppTasksDiscoverHD.js create mode 100644 src/daemons/back/apputils/AccountTransactionsRecheck.js create mode 100644 src/daemons/view/UpdateAccountListDaemon.js create mode 100644 src/database/models/account.ts create mode 100644 src/database/models/wallet-pub.ts create mode 100644 src/database/schemas/account.ts create mode 100644 src/database/schemas/wallet-pub.ts diff --git a/crypto/TransactionFilterTypeDict.ts b/crypto/TransactionFilterTypeDict.ts new file mode 100644 index 000000000..638fe2665 --- /dev/null +++ b/crypto/TransactionFilterTypeDict.ts @@ -0,0 +1,10 @@ +enum TransactionFilterTypeDict { + SWAP = 'swap', + WALLET_CONNECT = 'walletConnect', + STAKE = 'stake', + FEE = 'fee', + USUAL = 'usual', + NO_SPAM = 'no_spam' +} + +export default TransactionFilterTypeDict; diff --git a/crypto/blockchains/btc/BtcScannerProcessor.js b/crypto/blockchains/btc/BtcScannerProcessor.ts similarity index 91% rename from crypto/blockchains/btc/BtcScannerProcessor.js rename to crypto/blockchains/btc/BtcScannerProcessor.ts index de136e41a..e7d23c16f 100644 --- a/crypto/blockchains/btc/BtcScannerProcessor.js +++ b/crypto/blockchains/btc/BtcScannerProcessor.ts @@ -8,16 +8,17 @@ import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings'; import BtcFindAddressFunction from './basic/BtcFindAddressFunction'; -import config from '@app/config/config'; -import Database from '@app/appstores/DataSource/Database'; -import TransactionFilterTypeDict from '@appV2/dicts/transactionFilterTypeDict'; +import { Database } from '@database'; +import { DatabaseTable } from '@appTypes'; +import { Q } from '@nozbe/watermelondb'; +import TransactionFilterTypeDict from '@crypto/TransactionFilterTypeDict'; const CACHE_VALID_TIME = 60000; // 60 seconds -const CACHE = {}; -const CACHE_WALLET_PUBS = {}; +const CACHE: { [key: string]: any } = {}; // TODO fix any +const CACHE_WALLET_PUBS: { [key: string]: any } = {}; // TODO fix any const TIMEOUT_BTC = 60000; -const PROXY_TXS = 'https://proxy.trustee.deals/btc/getTxs'; +const PROXY_TXS = 'https://proxy.trustee.deals/btc/getTxs'; // TODO we may need to put our BE export default class BtcScannerProcessor { /** @@ -37,7 +38,9 @@ export default class BtcScannerProcessor { */ _trezorServer = false; - constructor(settings) { + _settings: unknown; // TODO fix type + + constructor(settings: unknown) { this._settings = settings; } @@ -47,7 +50,11 @@ export default class BtcScannerProcessor { * @returns {Promise} * @private */ - async _get(address, additionalData, source = '') { + async _get( + address: string, + additionalData: { addresses: string[] }, + source = '' + ) { const now = new Date().getTime(); if ( typeof CACHE[address] !== 'undefined' && @@ -196,7 +203,13 @@ export default class BtcScannerProcessor { FROM wallet_pub WHERE wallet_hash = '${walletHash}' AND currency_code='BTC'`; - const resPub = await Database.query(sqlPub); + const resPub = await Database.query( + DatabaseTable.WalletPub, + Q.and( + Q.where('hash', Q.eq(walletHash)), + Q.where('currency_code', Q.eq('BTC')) + ) + ); CACHE_WALLET_PUBS[walletHash] = {}; if (resPub && resPub.array && resPub.array.length > 0) { for (const row of resPub.array) { @@ -241,13 +254,6 @@ export default class BtcScannerProcessor { const withBalances = typeof scanData.withBalances !== 'undefined' && scanData.withBalances; if (!withBalances) { - if (config.debug.cryptoErrors) { - console.log( - this._settings.currencyCode + - ' BtcScannerProcessor.getAddresses started withoutBalances (KSU!)', - address - ); - } BlocksoftCryptoLog.log( this._settings.currencyCode + ' BtcScannerProcessor.getAddresses started withoutBalances (KSU!)', @@ -293,14 +299,6 @@ export default class BtcScannerProcessor { } } } catch (e) { - if (config.debug.cryptoErrors) { - console.log( - this._settings.currencyCode + - ' BtcScannerProcessor.getAddresses load from all addresses error ' + - e.message, - e - ); - } BlocksoftCryptoLog.log( this._settings.currencyCode + ' BtcScannerProcessor.getAddresses load from all addresses error ' + @@ -483,7 +481,37 @@ export default class BtcScannerProcessor { * @return {Promise} * @private */ - async _unifyTransaction(address, addresses, transaction) { + async _unifyTransaction( + address: string, + addresses: string, + transaction: { + blockHash: string; + blockHeight: number; + confirmations: number; + blockTime: number; + value: number; + valueIn: number; + fees: number; + hex: string; + txid: string; + version: number; + vin: { + txid: string; + sequence: number; + n: number; + addresses: string[]; + value: number; + hex: string; + }[]; + vout: { + value: number; + n: number; + spent: boolean; + hex: string; + addresses: string[]; + }[]; + } + ) { let showAddresses = false; try { showAddresses = await BtcFindAddressFunction(addresses, transaction); diff --git a/crypto/blockchains/btc/BtcTransferProcessor.ts b/crypto/blockchains/btc/BtcTransferProcessor.ts index 12c8a8257..45edfdafd 100644 --- a/crypto/blockchains/btc/BtcTransferProcessor.ts +++ b/crypto/blockchains/btc/BtcTransferProcessor.ts @@ -1,37 +1,48 @@ /** * @version 0.20 */ -import { BlocksoftBlockchainTypes } from '../BlocksoftBlockchainTypes' -import DogeTransferProcessor from '../doge/DogeTransferProcessor' -import BtcUnspentsProvider from './providers/BtcUnspentsProvider' -import DogeSendProvider from '../doge/providers/DogeSendProvider' -import BtcTxInputsOutputs from './tx/BtcTxInputsOutputs' -import BtcTxBuilder from './tx/BtcTxBuilder' -import BtcNetworkPrices from './basic/BtcNetworkPrices' +import { AirDAOBlockchainTypes } from '../AirDAOBlockchainTypes'; +import DogeTransferProcessor from '../doge/DogeTransferProcessor'; +import BtcUnspentsProvider from './providers/BtcUnspentsProvider'; +import DogeSendProvider from '../doge/providers/DogeSendProvider'; +import BtcTxInputsOutputs from './tx/BtcTxInputsOutputs'; +import BtcTxBuilder from './tx/BtcTxBuilder'; +import BtcNetworkPrices from './basic/BtcNetworkPrices'; -export default class BtcTransferProcessor extends DogeTransferProcessor implements BlocksoftBlockchainTypes.TransferProcessor { +export default class BtcTransferProcessor + extends DogeTransferProcessor + implements AirDAOBlockchainTypes.TransferProcessor +{ + _trezorServerCode = 'BTC_TREZOR_SERVER'; - _trezorServerCode = 'BTC_TREZOR_SERVER' + _builderSettings: AirDAOBlockchainTypes.BuilderSettings = { + minOutputDustReadable: 0.000001, + minChangeDustReadable: 0.000001, + feeMaxForByteSatoshi: 1000, // for tx builder + feeMaxAutoReadable2: 0.01, // for fee calc, + feeMaxAutoReadable6: 0.005, // for fee calc + feeMaxAutoReadable12: 0.001, // for fee calc + changeTogether: true, + minRbfStepSatoshi: 30, + minSpeedUpMulti: 1.7 + }; - _builderSettings: BlocksoftBlockchainTypes.BuilderSettings = { - minOutputDustReadable: 0.000001, - minChangeDustReadable: 0.000001, - feeMaxForByteSatoshi: 1000, // for tx builder - feeMaxAutoReadable2: 0.01, // for fee calc, - feeMaxAutoReadable6: 0.005, // for fee calc - feeMaxAutoReadable12: 0.001, // for fee calc - changeTogether: true, - minRbfStepSatoshi: 30, - minSpeedUpMulti : 1.7 - } - - _initProviders() { - if (this._initedProviders) return false - this.unspentsProvider = new BtcUnspentsProvider(this._settings, this._trezorServerCode) - this.sendProvider = new DogeSendProvider(this._settings, this._trezorServerCode) - this.txPrepareInputsOutputs = new BtcTxInputsOutputs(this._settings, this._builderSettings) - this.txBuilder = new BtcTxBuilder(this._settings, this._builderSettings) - this.networkPrices = new BtcNetworkPrices() - this._initedProviders = true - } + _initProviders() { + if (this._initedProviders) return false; + this.unspentsProvider = new BtcUnspentsProvider( + this._settings, + this._trezorServerCode + ); + this.sendProvider = new DogeSendProvider( + this._settings, + this._trezorServerCode + ); + this.txPrepareInputsOutputs = new BtcTxInputsOutputs( + this._settings, + this._builderSettings + ); + this.txBuilder = new BtcTxBuilder(this._settings, this._builderSettings); + this.networkPrices = new BtcNetworkPrices(); + this._initedProviders = true; + } } diff --git a/crypto/blockchains/btc/address/BtcAddressProcessor.js b/crypto/blockchains/btc/address/BtcAddressProcessor.js index e89f98c35..5b6b9fa35 100644 --- a/crypto/blockchains/btc/address/BtcAddressProcessor.js +++ b/crypto/blockchains/btc/address/BtcAddressProcessor.js @@ -1,36 +1,42 @@ /** * @version 0.5 */ -const bitcoin = require('bitcoinjs-lib') +import bitcoin from 'bitcoinjs-lib'; -const networksConstants = require('../../../common/ext/networks-constants') +import networksConstants from '../../../common/ext/networks-constants'; export default class BtcAddressProcessor { - constructor(settings) { - if (typeof settings === 'undefined' || !settings) { - throw new Error('BtcAddressProcessor requires settings') - } - if (typeof settings.network === 'undefined') { - throw new Error('BtcAddressProcessor requires settings.network') - } - if (typeof networksConstants[settings.network] === 'undefined') { - throw new Error('while retrieving Bitcoin address - unknown Bitcoin network specified. Got : ' + settings.network) - } - this._currentBitcoinNetwork = networksConstants[settings.network].network + constructor(settings) { + if (typeof settings === 'undefined' || !settings) { + throw new Error('BtcAddressProcessor requires settings'); } - - setBasicRoot(root) { - + if (typeof settings.network === 'undefined') { + throw new Error('BtcAddressProcessor requires settings.network'); } - - /** - * @param {string|Buffer} privateKey - * @param {*} data - * @returns {Promise<{privateKey: string, address: string, addedData: *}>} - */ - async getAddress(privateKey, data = {}) { - const keyPair = bitcoin.ECPair.fromPrivateKey(privateKey, { network: this._currentBitcoinNetwork }) - const address = bitcoin.payments.p2pkh({ pubkey: keyPair.publicKey, network: this._currentBitcoinNetwork}).address - return { address, privateKey: keyPair.toWIF() } + if (typeof networksConstants[settings.network] === 'undefined') { + throw new Error( + 'while retrieving Bitcoin address - unknown Bitcoin network specified. Got : ' + + settings.network + ); } + this._currentBitcoinNetwork = networksConstants[settings.network].network; + } + + setBasicRoot(root) {} + + /** + * @param {string|Buffer} privateKey + * @param {*} data + * @returns {Promise<{privateKey: string, address: string, addedData: *}>} + */ + async getAddress(privateKey, data = {}) { + const keyPair = bitcoin.ECPair.fromPrivateKey(privateKey, { + network: this._currentBitcoinNetwork + }); + const address = bitcoin.payments.p2pkh({ + pubkey: keyPair.publicKey, + network: this._currentBitcoinNetwork + }).address; + return { address, privateKey: keyPair.toWIF() }; + } } diff --git a/crypto/blockchains/btc/address/BtcSegwitAddressProcessor.js b/crypto/blockchains/btc/address/BtcSegwitAddressProcessor.js index 73f1251c3..1beb04333 100644 --- a/crypto/blockchains/btc/address/BtcSegwitAddressProcessor.js +++ b/crypto/blockchains/btc/address/BtcSegwitAddressProcessor.js @@ -1,19 +1,24 @@ /** * @version 0.5 */ -import BtcAddressProcessor from './BtcAddressProcessor' +import BtcAddressProcessor from './BtcAddressProcessor'; -const bitcoin = require('bitcoinjs-lib') +import bitcoin from 'bitcoinjs-lib'; export default class BtcSegwitAddressProcessor extends BtcAddressProcessor { - /** - * @param {string|Buffer} privateKey - * @param {*} data - * @returns {Promise<{privateKey: string, address: string, addedData: *}>} - */ - async getAddress(privateKey, data = {}) { - const keyPair = bitcoin.ECPair.fromPrivateKey(privateKey, { network: this._currentBitcoinNetwork }) - const address = bitcoin.payments.p2wpkh({pubkey: keyPair.publicKey, network: this._currentBitcoinNetwork}).address - return { address, privateKey: keyPair.toWIF() } - } + /** + * @param {string|Buffer} privateKey + * @param {*} data + * @returns {Promise<{privateKey: string, address: string, addedData: *}>} + */ + async getAddress(privateKey, data = {}) { + const keyPair = bitcoin.ECPair.fromPrivateKey(privateKey, { + network: this._currentBitcoinNetwork + }); + const address = bitcoin.payments.p2wpkh({ + pubkey: keyPair.publicKey, + network: this._currentBitcoinNetwork + }).address; + return { address, privateKey: keyPair.toWIF() }; + } } diff --git a/crypto/blockchains/btc/address/BtcSegwitCompatibleAddressProcessor.js b/crypto/blockchains/btc/address/BtcSegwitCompatibleAddressProcessor.js index 6dd004958..f16ae8e21 100644 --- a/crypto/blockchains/btc/address/BtcSegwitCompatibleAddressProcessor.js +++ b/crypto/blockchains/btc/address/BtcSegwitCompatibleAddressProcessor.js @@ -1,19 +1,23 @@ /** * @version 0.5 */ -import BtcAddressProcessor from './BtcAddressProcessor' +import BtcAddressProcessor from './BtcAddressProcessor'; -const bitcoin = require('bitcoinjs-lib') +import bitcoin from 'bitcoinjs-lib'; export default class BtcSegwitCompatibleAddressProcessor extends BtcAddressProcessor { - /** - * @param {string|Buffer} privateKey - * @param {*} data - * @returns {Promise<{privateKey: string, address: string, addedData: *}>} - */ - async getAddress(privateKey, data = {}) { - const keyPair = bitcoin.ECPair.fromPrivateKey(privateKey, { network: this._currentBitcoinNetwork }) - const address = bitcoin.payments.p2sh({ redeem: bitcoin.payments.p2wpkh({ pubkey: keyPair.publicKey }) }).address - return { address, privateKey: keyPair.toWIF() } - } + /** + * @param {string|Buffer} privateKey + * @param {*} data + * @returns {Promise<{privateKey: string, address: string, addedData: *}>} + */ + async getAddress(privateKey, data = {}) { + const keyPair = bitcoin.ECPair.fromPrivateKey(privateKey, { + network: this._currentBitcoinNetwork + }); + const address = bitcoin.payments.p2sh({ + redeem: bitcoin.payments.p2wpkh({ pubkey: keyPair.publicKey }) + }).address; + return { address, privateKey: keyPair.toWIF() }; + } } diff --git a/crypto/blockchains/btc/basic/BtcFindAddressFunction.js b/crypto/blockchains/btc/basic/BtcFindAddressFunction.js deleted file mode 100644 index 91a51ae94..000000000 --- a/crypto/blockchains/btc/basic/BtcFindAddressFunction.js +++ /dev/null @@ -1,126 +0,0 @@ -/** - * @version 0.5 - * @param {string} addresses[] - * @param {string} transaction.hex - * @param {string} transaction.address - * @param {string} transaction.vin[].txid - * @param {string} transaction.vin[].sequence - * @param {string} transaction.vin[].n 0 - * @param {string} transaction.vin[].addresses[] - * @param {string} transaction.vin[].addr - * @param {string} transaction.vin[].value - * @param {string} transaction.vin[].hex - * @param {string} transaction.vout[].value - * @param {string} transaction.vout[].n 0 - * @param {string} transaction.vout[].spent - * @param {string} transaction.vout[].hex - * @param {string} transaction.vout[].addresses[] - * @param {string} transaction.vout[].scriptPubKey.addresses[] - * @returns {Promise<{from: string, to: string, value: number, direction: string}>} - * @constructor - */ -import BlocksoftBN from '../../../common/BlocksoftBN' - -export default async function BtcFindAddressFunction(indexedAddresses, transaction) { - const inputMyBN = new BlocksoftBN(0) - const inputOthersBN = new BlocksoftBN(0) - let inputMyAddress = '' - const inputOthersAddresses = [] - const uniqueTmp = {} - if (transaction.vin) { - for (let i = 0, ic = transaction.vin.length; i < ic; i++) { - let vinAddress - const vinValue = transaction.vin[i].value - if (typeof transaction.vin[i].addresses !== 'undefined') { - vinAddress = transaction.vin[i].addresses[0] - } else if (typeof transaction.vin[i].addr !== 'undefined') { - vinAddress = transaction.vin[i].addr - } - if (!vinAddress) continue - if (vinAddress.indexOf('OP_RETURN (omni') !== -1) { - vinAddress = 'OMNI' - } - if (typeof indexedAddresses[vinAddress] !== 'undefined') { - inputMyBN.add(vinValue) - inputMyAddress = vinAddress - } else { - if (typeof uniqueTmp[vinAddress] === 'undefined') { - uniqueTmp[vinAddress] = 1 - inputOthersAddresses.push(vinAddress) - } - inputOthersBN.add(vinValue) - } - } - } - - const outputMyBN = new BlocksoftBN(0) - const outputOthersBN = new BlocksoftBN(0) - - let outputMyAddress = '' - const allMyAddresses = [] - const outputOthersAddresses = [] - const uniqueTmp2 = {} - if (transaction.vout) { - for (let j = 0, jc = transaction.vout.length; j < jc; j++) { - let voutAddress - const voutValue = transaction.vout[j].value - if (typeof transaction.vout[j].addresses !== 'undefined') { - voutAddress = transaction.vout[j].addresses[0] - } else if (typeof transaction.vout[j].scriptPubKey !== 'undefined' && typeof transaction.vout[j].scriptPubKey.addresses !== 'undefined') { - voutAddress = transaction.vout[j].scriptPubKey.addresses[0] - } - if (voutAddress.indexOf('OP_RETURN (omni') !== -1) { - voutAddress = 'OMNI' - } - - if (typeof indexedAddresses[voutAddress] !== 'undefined') { - outputMyBN.add(voutValue) - outputMyAddress = voutAddress - allMyAddresses.push(outputMyAddress) - } else { - if (typeof uniqueTmp2[voutAddress] === 'undefined') { - uniqueTmp2[voutAddress] = 1 - outputOthersAddresses.push(voutAddress) - } - outputOthersBN.add(voutValue) - } - } - } - - let output - if (inputMyBN.get() === '0') { // my only in output - output = { - direction: 'income', - from: inputOthersAddresses.length > 0 ? inputOthersAddresses.join(',') : '', - to: '', // outputMyAddress, - value: outputMyBN.get() - } - } else if (outputMyBN.get() === '0') { // my only in input - output = { - direction: 'outcome', - from: '', // inputMyAddress, - to: outputOthersAddresses.length > 0 ? outputOthersAddresses.join(',') : '', - value: (inputOthersBN.get() === '0') ? outputOthersBN.get() : inputMyBN.get() - } - } else { // both input and output - if (outputOthersAddresses.length > 0) {// there are other address - output = { - direction: 'outcome', - from: '', // inputMyAddress, - to: outputOthersAddresses.join(','), - value: outputOthersBN.get() - } - } else { - output = { - direction: 'self', - from: '', // inputMyAddress, - to: '', // outputMyAddress, - value: Math.abs(inputMyBN.diff(outputMyBN).get()) - } - } - } - output.from = output.from.substr(0, 255) - output.to = output.to.substr(0, 255) - output.allMyAddresses = allMyAddresses - return output -} diff --git a/crypto/blockchains/btc/basic/BtcFindAddressFunction.ts b/crypto/blockchains/btc/basic/BtcFindAddressFunction.ts new file mode 100644 index 000000000..b24208d38 --- /dev/null +++ b/crypto/blockchains/btc/basic/BtcFindAddressFunction.ts @@ -0,0 +1,159 @@ +/** + * @version 0.5 + * @param {string} addresses[] + * @param {string} transaction.hex + * @param {string} transaction.address + * @param {string} transaction.vin[].txid + * @param {string} transaction.vin[].sequence + * @param {string} transaction.vin[].n 0 + * @param {string} transaction.vin[].addresses[] + * @param {string} transaction.vin[].addr + * @param {string} transaction.vin[].value + * @param {string} transaction.vin[].hex + * @param {string} transaction.vout[].value + * @param {string} transaction.vout[].n 0 + * @param {string} transaction.vout[].spent + * @param {string} transaction.vout[].hex + * @param {string} transaction.vout[].addresses[] + * @param {string} transaction.vout[].scriptPubKey.addresses[] + * @returns {Promise<{from: string, to: string, value: number, direction: string}>} + * @constructor + */ +import BlocksoftBN from '../../../common/BlocksoftBN'; + +export default async function BtcFindAddressFunction( + indexedAddresses: string[], + transaction: { + hex: string; + address: string; + vin: { + txid: string; + sequence: string; + n: number; + addresses: string[]; + addr: string; + value: string; + hex: string; + }[]; + vout: { + value: string; + n: number; + spent: string; + hex: string; + addresses: string[]; + scriptPubKey: { addresses: string[] }; + }[]; + } +) { + const inputMyBN = new BlocksoftBN(0); + const inputOthersBN = new BlocksoftBN(0); + let inputMyAddress = ''; + const inputOthersAddresses = []; + const uniqueTmp = {}; + if (transaction.vin) { + for (let i = 0, ic = transaction.vin.length; i < ic; i++) { + let vinAddress; + const vinValue = transaction.vin[i].value; + if (typeof transaction.vin[i].addresses !== 'undefined') { + vinAddress = transaction.vin[i].addresses[0]; + } else if (typeof transaction.vin[i].addr !== 'undefined') { + vinAddress = transaction.vin[i].addr; + } + if (!vinAddress) continue; + if (vinAddress.indexOf('OP_RETURN (omni') !== -1) { + vinAddress = 'OMNI'; + } + if (typeof indexedAddresses[vinAddress] !== 'undefined') { + inputMyBN.add(vinValue); + inputMyAddress = vinAddress; + } else { + if (typeof uniqueTmp[vinAddress] === 'undefined') { + uniqueTmp[vinAddress] = 1; + inputOthersAddresses.push(vinAddress); + } + inputOthersBN.add(vinValue); + } + } + } + + const outputMyBN = new BlocksoftBN(0); + const outputOthersBN = new BlocksoftBN(0); + + let outputMyAddress = ''; + const allMyAddresses = []; + const outputOthersAddresses = []; + const uniqueTmp2 = {}; + if (transaction.vout) { + for (let j = 0, jc = transaction.vout.length; j < jc; j++) { + let voutAddress; + const voutValue = transaction.vout[j].value; + if (typeof transaction.vout[j].addresses !== 'undefined') { + voutAddress = transaction.vout[j].addresses[0]; + } else if ( + typeof transaction.vout[j].scriptPubKey !== 'undefined' && + typeof transaction.vout[j].scriptPubKey.addresses !== 'undefined' + ) { + voutAddress = transaction.vout[j].scriptPubKey.addresses[0]; + } + if (voutAddress.indexOf('OP_RETURN (omni') !== -1) { + voutAddress = 'OMNI'; + } + + if (typeof indexedAddresses[voutAddress] !== 'undefined') { + outputMyBN.add(voutValue); + outputMyAddress = voutAddress; + allMyAddresses.push(outputMyAddress); + } else { + if (typeof uniqueTmp2[voutAddress] === 'undefined') { + uniqueTmp2[voutAddress] = 1; + outputOthersAddresses.push(voutAddress); + } + outputOthersBN.add(voutValue); + } + } + } + + let output; + if (inputMyBN.get() === '0') { + // my only in output + output = { + direction: 'income', + from: + inputOthersAddresses.length > 0 ? inputOthersAddresses.join(',') : '', + to: '', // outputMyAddress, + value: outputMyBN.get() + }; + } else if (outputMyBN.get() === '0') { + // my only in input + output = { + direction: 'outcome', + from: '', // inputMyAddress, + to: + outputOthersAddresses.length > 0 ? outputOthersAddresses.join(',') : '', + value: + inputOthersBN.get() === '0' ? outputOthersBN.get() : inputMyBN.get() + }; + } else { + // both input and output + if (outputOthersAddresses.length > 0) { + // there are other address + output = { + direction: 'outcome', + from: '', // inputMyAddress, + to: outputOthersAddresses.join(','), + value: outputOthersBN.get() + }; + } else { + output = { + direction: 'self', + from: '', // inputMyAddress, + to: '', // outputMyAddress, + value: Math.abs(inputMyBN.diff(outputMyBN).get()) + }; + } + } + output.from = output.from.substr(0, 255); + output.to = output.to.substr(0, 255); + output.allMyAddresses = allMyAddresses; + return output; +} diff --git a/crypto/blockchains/btc/basic/BtcNetworkPrices.ts b/crypto/blockchains/btc/basic/BtcNetworkPrices.ts index 120168ef6..eabdf9bda 100644 --- a/crypto/blockchains/btc/basic/BtcNetworkPrices.ts +++ b/crypto/blockchains/btc/basic/BtcNetworkPrices.ts @@ -1,12 +1,12 @@ +/* eslint-disable camelcase */ /** * @version 0.20 **/ -import { BlocksoftBlockchainTypes } from '../../BlocksoftBlockchainTypes'; +import { AirDAOBlockchainTypes } from '../../AirDAOBlockchainTypes'; import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; import BlocksoftAxios from '../../../common/BlocksoftAxios'; import BlocksoftExternalSettings from '../../../common/BlocksoftExternalSettings'; -import MarketingEvent from '../../../../app/services/Marketing/MarketingEvent'; import BlocksoftUtils from '../../../common/BlocksoftUtils'; const ESTIMATE_PATH = 'https://mempool.space/api/v1/fees/recommended'; @@ -41,7 +41,7 @@ let CACHE_PREV_PREV_DATA = { }; export default class BtcNetworkPrices - implements BlocksoftBlockchainTypes.NetworkPrices + implements AirDAOBlockchainTypes.NetworkPrices { private _trezorServerCode = 'BTC_TREZOR_SERVER'; private _trezorServer: any; @@ -60,8 +60,6 @@ export default class BtcNetworkPrices }; const now = new Date().getTime(); if (CACHE_FEES_BTC && now - CACHE_FEES_BTC_TIME < CACHE_VALID_TIME) { - // noinspection ES6MissingAwait - MarketingEvent.logEvent('estimate_fee_btc_result', logData); // @ts-ignore BlocksoftCryptoLog.log( 'BtcNetworkPricesProvider ' + @@ -128,12 +126,6 @@ export default class BtcNetworkPrices link = 'prev'; } } catch (e) { - // noinspection ES6MissingAwait - MarketingEvent.logEvent('estimate_fee_btc_load_error', { - currencyCode, - link, - data: e.toString() - }); // do nothing } } @@ -181,16 +173,8 @@ export default class BtcNetworkPrices } await this._parseLoaded(currencyCode, cachedWithTime, link); } catch (e) { - // noinspection ES6MissingAwait - MarketingEvent.logEvent('estimate_fee_btc_parse_error', { - currencyCode, - link, - data: e.toString() - }); // do nothing } - // noinspection ES6MissingAwait - MarketingEvent.logEvent('estimate_fee_btc_result', logData); return CACHE_FEES_BTC; } @@ -280,12 +264,6 @@ export default class BtcNetworkPrices ); } else { // noinspection ES6MissingAwait - MarketingEvent.logEvent('estimate_fee_btc_error', { - currencyCode, - link, - json, - externalSettings - }); } } } diff --git a/crypto/blockchains/btc/providers/BtcUnspentsProvider.ts b/crypto/blockchains/btc/providers/BtcUnspentsProvider.ts index ae3fbd976..a882de27f 100644 --- a/crypto/blockchains/btc/providers/BtcUnspentsProvider.ts +++ b/crypto/blockchains/btc/providers/BtcUnspentsProvider.ts @@ -3,19 +3,23 @@ * https://github.com/trezor/blockbook/blob/master/docs/api.md * https://doge1.trezor.io/api/v2/utxo/D5oKvWEibVe74CXLASmhpkRpLoyjgZhm71 */ -import { BlocksoftBlockchainTypes } from '../../BlocksoftBlockchainTypes'; +import { AirDAOBlockchainTypes } from '../../AirDAOBlockchainTypes'; import DogeUnspentsProvider from '../../doge/providers/DogeUnspentsProvider'; -import Database from '@app/appstores/DataSource/Database'; +// import Database from '@app/appstores/DataSource/Database'; +import { Database } from '@database'; import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; import BlocksoftDict from '@crypto/common/BlocksoftDict'; -import main from '@app/appstores/DataSource/Database'; +import { Q } from '@nozbe/watermelondb'; +import { DatabaseTable } from '@appTypes'; +import { AccountDBModel } from '@database/models/account'; +import { WalletPubDBModel } from '@database/models/wallet-pub'; -const CACHE_FOR_CHANGE = {}; +const CACHE_FOR_CHANGE: { [key: string]: any } = {}; export default class BtcUnspentsProvider extends DogeUnspentsProvider - implements BlocksoftBlockchainTypes.UnspentsProvider + implements AirDAOBlockchainTypes.UnspentsProvider { static async getCache(walletHash: string, currencyCode = 'BTC') { if (typeof CACHE_FOR_CHANGE[walletHash] !== 'undefined') { @@ -36,22 +40,40 @@ export default class BtcUnspentsProvider JSON.stringify(CACHE_FOR_CHANGE[walletHash]) ); - const sqlPub = `SELECT wallet_pub_value as walletPub - FROM wallet_pub - WHERE wallet_hash = '${walletHash} - AND currency_code='${mainCurrencyCode}' - `; - const resPub = await Database.query(sqlPub); - if (resPub && resPub.array && resPub.array.length > 0) { - const sql = `SELECT account.address - FROM account - WHERE account.wallet_hash = '${walletHash} - AND currency_code='${mainCurrencyCode}' AND (already_shown IS NULL OR already_shown=0) - AND derivation_type!='main' - ORDER BY derivation_index ASC - `; - const res = await Database.query(sql); - for (const row of res.array) { + // const sqlPub = `SELECT wallet_pub_value as walletPub + // FROM wallet_pub + // WHERE wallet_hash = '${walletHash} + // AND currency_code='${mainCurrencyCode}' + // `; + const resPub = (await Database.query( + DatabaseTable.WalletPub, + Q.and( + Q.where('hash', Q.eq(walletHash)), + Q.where('currency_code', Q.eq(mainCurrencyCode)) + ) + )) as WalletPubDBModel[]; + if (resPub && resPub && resPub.length > 0) { + // const sql = `SELECT account.address + // FROM account + // WHERE account.wallet_hash = '${walletHash} + // AND currency_code='${mainCurrencyCode}' AND (already_shown IS NULL OR already_shown=0) + // AND derivation_type!='main' + // ORDER BY derivation_index ASC + // `; + const res = (await Database.query( + DatabaseTable.Accounts, + Q.and( + Q.where('hash', Q.eq(walletHash)), + Q.where('currency_code', Q.eq(mainCurrencyCode)), + Q.or( + Q.where('already_shown', Q.eq(null)), + Q.where('already_shown', Q.eq(0)) + ), + Q.where('derivation_type', Q.notEq('main')) + ), + Q.sortBy('derivation_index', 'asc') + )) as AccountDBModel[]; + for (const row of res) { const prefix = row.address.indexOf(segwitPrefix) === 0 ? segwitPrefix @@ -90,13 +112,19 @@ export default class BtcUnspentsProvider } } } else { - const sql = `SELECT account.address - FROM account - WHERE account.wallet_hash = '${walletHash}' - AND currency_code='${mainCurrencyCode}' - `; - const res = await Database.query(sql); - for (const row of res.array) { + // const sql = `SELECT account.address + // FROM account + // WHERE account.wallet_hash = '${walletHash}' + // AND currency_code='${mainCurrencyCode}' + // `; + const res = (await Database.query( + DatabaseTable.Accounts, + Q.and( + Q.where('hash', Q.eq(walletHash)), + Q.where('currency_code', mainCurrencyCode) + ) + )) as AccountDBModel[]; + for (const row of res) { // @ts-ignore await BlocksoftCryptoLog.log( currencyCode + @@ -157,42 +185,45 @@ export default class BtcUnspentsProvider async getUnspents( address: string - ): Promise { + ): Promise { const mainCurrencyCode = this._settings.currencyCode === 'LTC' ? 'LTC' : 'BTC'; const segwitPrefix = BlocksoftDict.CurrenciesForTests[mainCurrencyCode + '_SEGWIT'] .addressPrefix; - const sqlPub = `SELECT wallet_pub_value as walletPub - FROM wallet_pub - WHERE wallet_hash = (SELECT wallet_hash FROM account WHERE address='${address}') + const sqlPub = `SELECT wallet_pub_value as walletPubValue + FROM ${DatabaseTable.WalletPub} + WHERE hash = (SELECT hash FROM ${DatabaseTable.Accounts} WHERE address='${address}') AND currency_code='${mainCurrencyCode}' `; const totalUnspents = []; - const resPub = await Database.query(sqlPub); - if (resPub && resPub.array && resPub.array.length > 0) { - for (const row of resPub.array) { - const unspents = await super.getUnspents(row.walletPub); + // const resPub = await Database.query(sqlPub); + const resPub = (await Database.unsafeRawQuery( + DatabaseTable.WalletPub, + sqlPub + )) as WalletPubDBModel[]; + if (resPub && resPub && resPub.length > 0) { + for (const row of resPub) { + const unspents = await super.getUnspents(row.walletPubValue); if (unspents) { for (const unspent of unspents) { totalUnspents.push(unspent); } } } - const sqlAdditional = `SELECT account.address, account.derivation_path as derivationPath, wallet_hash AS walletHash - FROM account - WHERE account.wallet_hash = (SELECT wallet_hash FROM account WHERE address='${address}') + const sqlAdditional = `SELECT account.address, account.derivation_path as derivationPath, hash + FROM ${DatabaseTable.Accounts} + WHERE account.hash = (SELECT hash FROM ${DatabaseTable.Accounts} WHERE address='${address}') AND account.derivation_path = 'm/49quote/0quote/0/1/0' AND currency_code='${mainCurrencyCode}' `; - const resAdditional = await Database.query(sqlAdditional); - if ( - resAdditional && - resAdditional.array && - resAdditional.array.length > 0 - ) { - for (const row of resAdditional.array) { + const resAdditional = (await Database.unsafeRawQuery( + DatabaseTable.Accounts, + sqlAdditional + )) as AccountDBModel[]; + if (resAdditional && resAdditional && resAdditional.length > 0) { + for (const row of resAdditional) { const unspents = await super.getUnspents(row.address); if (unspents) { for (const unspent of unspents) { @@ -206,20 +237,23 @@ export default class BtcUnspentsProvider } } - const sql = `SELECT account.address, account.derivation_path as derivationPath, wallet_hash AS walletHash + const sql = `SELECT account.address, account.derivation_path as derivationPath, hash FROM account - WHERE account.wallet_hash = (SELECT wallet_hash FROM account WHERE address='${address}') + WHERE account.hash = (SELECT hash FROM account WHERE address='${address}') AND currency_code='${mainCurrencyCode}' AND (already_shown IS NULL OR already_shown=0) AND derivation_type!='main' ORDER BY derivation_index ASC `; - const res = await Database.query(sql); - for (const row of res.array) { - const walletHash = row.walletHash; + const res = (await Database.unsafeRawQuery( + DatabaseTable.Accounts, + sql + )) as AccountDBModel[]; + for (const row of res) { + const walletHash = row.hash.hash; const prefix = row.address.indexOf(segwitPrefix) === 0 ? segwitPrefix - : row.address.substr(0, 1); + : row.address.substring(0, 1); await BlocksoftCryptoLog.log( this._settings.currencyCode + ' ' + @@ -258,14 +292,17 @@ export default class BtcUnspentsProvider } } } else { - const sql = `SELECT account.address, account.derivation_path as derivationPath, wallet_hash AS walletHash - FROM account - WHERE account.wallet_hash = (SELECT wallet_hash FROM account WHERE address='${address}') + const sql = `SELECT account.address, account.derivation_path as derivationPath, hash + FROM ${DatabaseTable.Accounts} + WHERE account.hash = (SELECT hash FROM ${DatabaseTable.Accounts} WHERE address='${address}') AND currency_code='${mainCurrencyCode}' `; - const res = await Database.query(sql); - for (const row of res.array) { - const walletHash = row.walletHash; + const res = (await Database.unsafeRawQuery( + DatabaseTable.Accounts, + sql + )) as AccountDBModel[]; + for (const row of res) { + const walletHash = row.hash.hash; const unspents = await super.getUnspents(row.address); // @ts-ignore await BlocksoftCryptoLog.log( diff --git a/crypto/blockchains/btc/tx/BtcTxBuilder.ts b/crypto/blockchains/btc/tx/BtcTxBuilder.ts index 69142cb8b..4691fdf88 100644 --- a/crypto/blockchains/btc/tx/BtcTxBuilder.ts +++ b/crypto/blockchains/btc/tx/BtcTxBuilder.ts @@ -1,27 +1,26 @@ /** * @version 0.20 */ -import { BlocksoftBlockchainTypes } from '../../BlocksoftBlockchainTypes'; +import { AirDAOBlockchainTypes } from '../../AirDAOBlockchainTypes'; import DogeTxBuilder from '../../doge/tx/DogeTxBuilder'; import BlocksoftPrivateKeysUtils from '../../../common/BlocksoftPrivateKeysUtils'; import { ECPair, payments, TransactionBuilder } from 'bitcoinjs-lib'; import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; import BlocksoftDict from '@crypto/common/BlocksoftDict'; -import main from '@app/appstores/DataSource/Database'; export default class BtcTxBuilder extends DogeTxBuilder - implements BlocksoftBlockchainTypes.TxBuilder + implements AirDAOBlockchainTypes.TxBuilder { - private mnemonic: string = ''; - private walletHash: string = ''; + private mnemonic = ''; + private walletHash = ''; private keyPairBTC: any = {}; private p2wpkhBTC: any = {}; private p2shBTC: any = {}; _getRawTxValidateKeyPair( - privateData: BlocksoftBlockchainTypes.TransferPrivateData, - data: BlocksoftBlockchainTypes.TransferData + privateData: AirDAOBlockchainTypes.TransferPrivateData, + data: AirDAOBlockchainTypes.TransferData ): void { if (this.mnemonic === privateData.privateKey) return; @@ -35,7 +34,7 @@ export default class BtcTxBuilder async _getRawTxAddInput( txb: TransactionBuilder, i: number, - input: BlocksoftBlockchainTypes.UnspentTx, + input: AirDAOBlockchainTypes.UnspentTx, nSequence: number ): Promise { if (typeof input.address === 'undefined') { @@ -193,7 +192,7 @@ export default class BtcTxBuilder async _getRawTxSign( txb: TransactionBuilder, i: number, - input: BlocksoftBlockchainTypes.UnspentTx + input: AirDAOBlockchainTypes.UnspentTx ): Promise { if (typeof input.address === 'undefined') { throw new Error('no address in input ' + JSON.stringify(input)); diff --git a/crypto/blockchains/btc/tx/BtcTxInputsOutputs.ts b/crypto/blockchains/btc/tx/BtcTxInputsOutputs.ts index b9b127888..842a09238 100644 --- a/crypto/blockchains/btc/tx/BtcTxInputsOutputs.ts +++ b/crypto/blockchains/btc/tx/BtcTxInputsOutputs.ts @@ -1,23 +1,25 @@ /** * @version 0.20 */ -import { BlocksoftBlockchainTypes } from '../../BlocksoftBlockchainTypes'; +import { AirDAOBlockchainTypes } from '../../AirDAOBlockchainTypes'; import BtcUnspentsProvider from '../providers/BtcUnspentsProvider'; import DogeTxInputsOutputs from '../../doge/tx/DogeTxInputsOutputs'; -import settingsActions from '../../../../app/appstores/Stores/Settings/SettingsActions'; import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; -import DaemonCache from '../../../../app/daemons/DaemonCache'; +import DaemonCache from '../../../../src/daemons/DaemonCache'; import BlocksoftDict from '@crypto/common/BlocksoftDict'; +import { Database } from '@database'; export default class BtcTxInputsOutputs extends DogeTxInputsOutputs - implements BlocksoftBlockchainTypes.TxInputsOutputs + implements AirDAOBlockchainTypes.TxInputsOutputs { - async _addressForChange(data: BlocksoftBlockchainTypes.TransferData): string { - const btcShowTwoAddress = await settingsActions.getSetting( + async _addressForChange( + data: AirDAOBlockchainTypes.TransferData + ): Promise { + const btcShowTwoAddress = await Database.localStorage.get( 'btcShowTwoAddress' ); - const btcLegacyOrSegwit = await settingsActions.getSetting( + const btcLegacyOrSegwit = await Database.localStorage.get( 'btc_legacy_or_segwit' ); @@ -90,16 +92,16 @@ export default class BtcTxInputsOutputs } async getInputsOutputs( - data: BlocksoftBlockchainTypes.TransferData, - unspents: BlocksoftBlockchainTypes.UnspentTx[], + data: AirDAOBlockchainTypes.TransferData, + unspents: AirDAOBlockchainTypes.UnspentTx[], feeToCount: { feeForByte?: string; feeForAll?: string; autoFeeLimitReadable?: string | number; }, - additionalData: BlocksoftBlockchainTypes.TransferAdditionalData, - subtitle: string = 'default' - ): Promise { + additionalData: AirDAOBlockchainTypes.TransferAdditionalData, + subtitle = 'default' + ): Promise { const res = await super._getInputsOutputs( data, unspents, diff --git a/src/appTypes/database/DatabaseTable.ts b/src/appTypes/database/DatabaseTable.ts index 2d67bec06..4e670ece6 100644 --- a/src/appTypes/database/DatabaseTable.ts +++ b/src/appTypes/database/DatabaseTable.ts @@ -1,4 +1,6 @@ export enum DatabaseTable { + Accounts = 'accounts', TransactionScannersTmp = 'transactions_scanners_tmp', - Wallets = 'wallets' + Wallets = 'wallets', + WalletPub = 'wallet-pub' } diff --git a/src/daemons/Daemon.js b/src/daemons/Daemon.js new file mode 100644 index 000000000..f8efb829c --- /dev/null +++ b/src/daemons/Daemon.js @@ -0,0 +1,42 @@ +/** + * @version 0.11 + */ +import UpdateOneByOneDaemon from './back/UpdateOneByOneDaemon' + +import UpdateAccountListDaemon from './view/UpdateAccountListDaemon' +import UpdateCurrencyRateDaemon from './back/UpdateCurrencyRateDaemon' +import UpdateCashBackDataDaemon from './back/UpdateCashBackDataDaemon' + +import config from '../config/config' +import UpdateCardsDaemon from '@app/daemons/back/UpdateCardsDaemon' + +let CACHE_STARTED = false + +export default { + + async start () { + if (CACHE_STARTED) return false + const { daemon } = config + UpdateOneByOneDaemon + .setTime(daemon.updateTimes.oneByOne) + .start() + UpdateAccountListDaemon + .setTime(daemon.updateTimes.view) + .start() + CACHE_STARTED = true + }, + + async forceAll(params) { + if (typeof params.noRatesApi === 'undefined') { + await UpdateCurrencyRateDaemon.updateCurrencyRate(params) + } + await UpdateAccountListDaemon.forceDaemonUpdate(params) + // await UpdateAppNewsDaemon.updateAppNewsDaemon(params) + if (typeof params.noCashbackApi === 'undefined') { + await UpdateCashBackDataDaemon.updateCashBackDataDaemon(params) + } + if (typeof params.noCards === 'undefined') { + await UpdateCardsDaemon.updateCardsDaemon(params) + } + } +} diff --git a/src/daemons/DaemonCache.js b/src/daemons/DaemonCache.js new file mode 100644 index 000000000..c183d6714 --- /dev/null +++ b/src/daemons/DaemonCache.js @@ -0,0 +1,96 @@ +/** + * @version 0.11 + */ +import Database from '@app/appstores/DataSource/Database'; + +import transactionDS from '../appstores/DataSource/Transaction/Transaction' + +import BlocksoftFixBalance from '../../crypto/common/BlocksoftFixBalance' + +class DaemonCache { + + CACHE_WALLET_COUNT = 0 + + CACHE_WALLET_SUMS = {} + CACHE_WALLET_TOTAL = { balance: 0, unconfirmed: 0 } + CACHE_RATES = {} + CACHE_ALL_ACCOUNTS = {} + CACHE_FIO_MEMOS = {} + + CACHE_ACCOUNT_TX = {} + + /** + * @param walletHash + * @returns {{unconfirmed: number, balance: number, basicCurrencySymbol: string}} + */ + getCache(walletHash = false) { + if (!walletHash) { + return this.CACHE_WALLET_TOTAL + } + if (typeof this.CACHE_WALLET_SUMS[walletHash] === 'undefined') return false + return this.CACHE_WALLET_SUMS[walletHash] + } + + /** + * @param {string} currencyCode + * @returns {{basicCurrencySymbol: string, basicCurrencyRate: number}} + */ + getCacheRates(currencyCode) { + if (typeof this.CACHE_RATES[currencyCode] === 'undefined') { + return { basicCurrencySymbol: '', basicCurrencyRate: '' } + } + return this.CACHE_RATES[currencyCode] + } + + cleanCacheTxsCount(account) { + let cacheTitle = account.walletHash + '_' + account.currencyCode + if (typeof this.CACHE_ACCOUNT_TX[cacheTitle] !== 'undefined') { + this.CACHE_ACCOUNT_TX[cacheTitle] = -1 + } + cacheTitle += '_noZero' + if (typeof this.CACHE_ACCOUNT_TX[cacheTitle] !== 'undefined') { + this.CACHE_ACCOUNT_TX[cacheTitle] = -1 + } + } + + getFioMemo(currencyCode) { + return this.CACHE_FIO_MEMOS[currencyCode] ?? {} + } + + async _getFromDB(walletHash, currencyCode) { + const sql = ` SELECT balance_fix AS balanceFix, balance_txt AS balanceTxt FROM account_balance WHERE currency_code='${currencyCode}' AND wallet_hash='${walletHash}'` + const res = await Database.query(sql) + if (!res || !res.array || res.array.length === 0) { + return {balance : 0, from : 'noDb'} + } + let account + let totalBalance = 0 + for (account of res.array) { + const balance = BlocksoftFixBalance(account, 'balance') + if (balance > 0) { + totalBalance += balance + } + } + return {balance : totalBalance, from : 'sumDb'} + } + + async getCacheAccount(walletHash, currencyCode) { + if (typeof this.CACHE_ALL_ACCOUNTS[walletHash] === 'undefined') { + return this._getFromDB(walletHash, currencyCode) + } + if (typeof this.CACHE_ALL_ACCOUNTS[walletHash][currencyCode] === 'undefined') { + return this._getFromDB(walletHash, currencyCode) + } + return this.CACHE_ALL_ACCOUNTS[walletHash][currencyCode] + } + + getCacheAccountStatic(walletHash, currencyCode) { + if (typeof this.CACHE_ALL_ACCOUNTS[walletHash] === 'undefined' || typeof this.CACHE_ALL_ACCOUNTS[walletHash][currencyCode] === 'undefined') { + return {balance : '0'} + } + return this.CACHE_ALL_ACCOUNTS[walletHash][currencyCode] + } +} + +const single = new DaemonCache() +export default single diff --git a/src/daemons/Update.js b/src/daemons/Update.js new file mode 100644 index 000000000..63d895cca --- /dev/null +++ b/src/daemons/Update.js @@ -0,0 +1,48 @@ +/** + * @version 0.11 + */ +class Update { + + updateTime = 20000 + updateFunction = () => {} + + _updateFirstCall = true + _updateTimer = {} + + start = async () => { + this._localDaemon() + } + + setTime = (updateTime) => { + this.updateTime = updateTime + return this + } + + forceDaemonUpdate = async (params) => { + if (typeof params === 'undefined') { + params = { force: true } + } else { + params.force = true + } + await this.updateFunction(params) + } + + _localDaemon = async () => { + const { + updateTime, + updateFunction + } = this + + if (this._updateFirstCall) { + this._updateFirstCall = false + await updateFunction({ force: false }) + } + + this._updateTimer = setTimeout(async () => { + await updateFunction({ force: false }) + this._localDaemon() + }, updateTime) + } +} + +export default Update diff --git a/src/daemons/back/UpdateAccountBalanceAndTransactions.js b/src/daemons/back/UpdateAccountBalanceAndTransactions.js new file mode 100644 index 000000000..2e764db96 --- /dev/null +++ b/src/daemons/back/UpdateAccountBalanceAndTransactions.js @@ -0,0 +1,444 @@ +/** + * @version 0.11 + */ +import BlocksoftKeysStorage from '../../../crypto/actions/BlocksoftKeysStorage/BlocksoftKeysStorage' + +import BlocksoftBalances from '../../../crypto/actions/BlocksoftBalances/BlocksoftBalances' +import BlocksoftTransactions from '../../../crypto/actions/BlocksoftTransactions/BlocksoftTransactions' + +import Log from '../../services/Log/Log' +import MarketingEvent from '../../services/Marketing/MarketingEvent' + +import accountScanningDS from '../../appstores/DataSource/Account/AccountScanning' +import accountBalanceDS from '../../appstores/DataSource/AccountBalance/AccountBalance' +import accountDS from '../../appstores/DataSource/Account/Account' + +import AccountTransactionsRecheck from './apputils/AccountTransactionsRecheck' +import settingsActions from '../../appstores/Stores/Settings/SettingsActions' + +import config from '../../config/config' +import { getFioObtData, resolveCryptoCodes } from '../../../crypto/blockchains/fio/FioUtils' +import DaemonCache from '../DaemonCache' +import store from '@app/store' +import UpdateAccountListDaemon from '@app/daemons/view/UpdateAccountListDaemon' + +const CACHE_SCANNING = {} +const CACHE_VALID_TIME = 60000 // 1 minute +const CACHE_VALID_10MIN_TIME = 600000 // 10 minutes +const CACHE_CUSTOM_TIME = { + 'BCH': 60000, // 10 minutes + 'BSV': 60000 // 10 minutes +} +let CACHE_LAST_TIME = false +let CACHE_ONE_ACCOUNTS = {} + +class UpdateAccountBalanceAndTransactions { + + getTime() { + if (CACHE_LAST_TIME > 0) { + return new Date(CACHE_LAST_TIME).toLocaleTimeString() + } + return '-' + } + + /** + * @param {string} callParams.source + * @param {boolean} callParams.force + * @param {boolean} callParams.allWallets + * @param {boolean} callParams.onlyBalances + * @param {string} callParams.currencyCode + * @returns {Promise} + */ + updateAccountBalanceAndTransactions = async (callParams) => { + const source = callParams.source || 'FRONT' + const force = callParams.force || false + const allWallets = callParams.allWallets || false + const onlyBalances = callParams.onlyBalances || false + if (!force || source === 'BACK') { + const setting = await settingsActions.getSetting('scannerCode') + if (!setting) { + await settingsActions.setSettings('scannerCode', '1min') + } + if (setting === 'none') { + return false + } else if (CACHE_LAST_TIME && setting === '10min') { + const now = new Date().getTime() + const diff = now - CACHE_LAST_TIME + if (diff < CACHE_VALID_10MIN_TIME) { + Log.daemon('UpdateAccountBalanceAndTransactions skipped by diff ' + diff) + return false + } + } + } + + let tmpAction = '' + try { + const params = { + force + } + + if (!allWallets && source !== 'BACK') { + params.walletHash = await settingsActions.getSelectedWallet('UpdateAccountBalanceAndTransactions') + } + + tmpAction = 'params init' + if (typeof callParams !== 'undefined' && callParams && typeof callParams.currencyCode !== 'undefined') { + + if (force) { + if (callParams.currencyCode.indexOf('TRX') === 0) { + params.currencyFamily = 'TRX' + } else if (callParams.currencyCode.indexOf('ETH') === 0) { + params.currencyFamily = 'ETH' + } else { + params.currencyCode = callParams.currencyCode + } + } else { + params.currencyCode = callParams.currencyCode + } + } + + + Log.daemon('UpdateAccountBalanceAndTransactions called ' + source) + + tmpAction = 'accounts init' + + let accounts = await accountScanningDS.getAccountsForScan({ ...params, force: false }) + + if (force) { + if (!accounts || accounts.length === 0) { + accounts = await accountScanningDS.getAccountsForScan(params) + } + } + + if (!accounts || accounts.length === 0) { + Log.daemon('UpdateAccountBalanceAndTransactions called - no account') + return false + } + + tmpAction = 'accounts log' + let account + for (account of accounts) { + Log.daemon('UpdateAccountBalanceAndTransactions called - todo account ' + account.id + ' ' + account.currencyCode + ' ' + account.address) + } + + tmpAction = 'accounts run main' + let running = 0 + CACHE_ONE_ACCOUNTS = {} + let shouldUpdateBalance = false + for (account of accounts) { + if (typeof CACHE_CUSTOM_TIME[account.currencyCode] !== 'undefined') { + continue + } + if (typeof CACHE_ONE_ACCOUNTS[account.currencyCode + '_' + account.address] !== 'undefined') { + continue + } + tmpAction = 'account run ' + JSON.stringify(account) + if (await this._accountRun(account, accounts, source, CACHE_VALID_TIME, force, onlyBalances)) { + shouldUpdateBalance = true + } + running++ + } + + tmpAction = 'accounts run custom' + + for (account of accounts) { + if (typeof CACHE_ONE_ACCOUNTS[account.currencyCode + '_' + account.address] !== 'undefined') { + continue + } + if (typeof CACHE_CUSTOM_TIME[account.currencyCode] !== 'undefined') { + // if its the only ones not updated - lets do them faster + if (await this._accountRun(account, accounts, source, running > 0 ? CACHE_CUSTOM_TIME[account.currencyCode] : CACHE_VALID_TIME, force, onlyBalances)) { + shouldUpdateBalance = true + } + } + } + + CACHE_LAST_TIME = new Date().getTime() + if (shouldUpdateBalance) { + await UpdateAccountListDaemon.updateAccountListDaemon({ force: true, source: 'SHOULD_UPDATE_BALANCE' }) + } + } catch (e) { + if (config.debug.appErrors) { + console.log('UpdateAccountBalanceAndTransactions balance error ' + source + ' ' + e.message, e) + } + Log.errDaemon('UpdateAccountBalanceAndTransactions balance error ' + source + ' ' + e.message + ' ' + tmpAction) + return false + } + return true + } + + loadFioData = async (currencyCode) => { + const currencies = store.getState().currencyStore.cryptoCurrencies + let foundFio = false + for (const tmp of currencies) { + if (tmp.currencyCode === 'FIO' && !tmp.maskedHidden) { + foundFio = true + break + } + } + if (!foundFio) return false + Log.daemon('UpdateAccountBalanceAndTransactions loadFioData ' + currencyCode) + try { + // eslint-disable-next-line camelcase + const { token_code } = resolveCryptoCodes(currencyCode) + const result = await getFioObtData(token_code) + if (result && result['obt_data_records']) { + const fioData = result['obt_data_records'].reduce((res, item) => { + if (!item.content?.memo) { + return res + } + + return !item.content?.obt_id ? res : { + ...res, + [item.content?.obt_id]: item.content?.memo + } + }, {}) + + DaemonCache.CACHE_FIO_MEMOS[currencyCode] = { + ...DaemonCache.getFioMemo(currencyCode), + ...fioData + } + } + } catch (e) { + if (config.debug.cryptoErrors) { + console.log('UpdateAccountBalanceAndTransactions error on loadFioData ' + e.message, e) + } + Log.errDaemon('UpdateAccountBalanceAndTransactions error on loadFioData ' + e.message) + } + } + + async _accountRun(account, accounts, source, time, force, onlyBalances = false) { + + let newBalance = false + let addressToScan = account.address + + Log.daemon('UpdateAccountBalanceAndTransactions _accountRun init ' + account.id + ' ' + account.currencyCode + ' ' + account.address) + + if (account.accountJson && typeof account.accountJson.addressHex !== 'undefined') { + addressToScan = account.accountJson.addressHex + Log.daemon('UpdateAccountBalanceAndTransactions changing address ' + account.currencyCode + ' ' + account.address + ' => ' + addressToScan) + } + const now = new Date().getTime() + if (!force && typeof CACHE_SCANNING[account.currencyCode + ' ' + addressToScan] !== 'undefined') { + const diff = now - CACHE_SCANNING[account.currencyCode + ' ' + addressToScan] + if (diff < time) { + Log.daemon('UpdateAccountBalanceAndTransactions skipped as running ' + account.currencyCode + ' ' + account.address + ' => diff:' + diff + ' time: ' + time) + return false + } + } + CACHE_SCANNING[account.currencyCode + ' ' + addressToScan] = now + + const updateObj = { + balanceScanTime: Math.round(new Date().getTime() / 1000) + } + + let balanceError + try { + Log.daemon('UpdateAccountBalanceAndTransactions newBalance ' + account.currencyCode + ' ' + addressToScan) + + if (account.currencyCode === 'BTC') { + const additional = {} + if (account.walletIsHd && account.derivationPath !== 'm/49quote/0quote/0/1/0') { + updateObj.balanceScanLog = account.address + ' should be HD ' + updateObj.balanceScanError = '' + await accountBalanceDS.updateAccountBalance({ updateObj }, account) + return false + } else { + newBalance = await (BlocksoftBalances.setCurrencyCode(account.currencyCode).setAddress(addressToScan).setAdditional(additional).setWalletHash(account.walletHash)).getBalance('AccountRunBalancesBtc') + } + } else { + newBalance = await (BlocksoftBalances.setCurrencyCode(account.currencyCode).setAddress(addressToScan).setAdditional(account.accountJson).setWalletHash(account.walletHash)).getBalance('AccountRunBalances') + } + if (!newBalance || typeof newBalance.balance === 'undefined') { + if (account.balanceScanBlock === 0 && account.balanceScanTime === 0) { + updateObj.balanceScanLog = account.address + ' empty response, old balance ' + account.balance + ', ' + JSON.stringify(newBalance) + updateObj.balanceScanError = 'account.balanceBadNetwork' + await accountBalanceDS.updateAccountBalance({ updateObj }, account) + return false + } + balanceError = ' something wrong with balance ' + account.currencyCode + ' ' + addressToScan + ' => ' + JSON.stringify(newBalance) + Log.daemon('UpdateAccountBalanceAndTransactions newBalance something wrong ' + account.currencyCode + ' ' + addressToScan + ' => ' + JSON.stringify(newBalance)) + } else { + balanceError = ' found in one ' + JSON.stringify(newBalance) + } + } catch (e) { + if (config.debug.appErrors) { + console.log('UpdateAccountBalanceAndTransactions newBalance from ' + source + ' loaded ' + account.currencyCode + ' ' + addressToScan + ' error ' + e.message) + } + balanceError = ' found balanceError ' + e.message + } + + Log.daemon('UpdateAccountBalanceAndTransactions newBalance from ' + source + ' loaded ' + account.currencyCode + ' ' + addressToScan, JSON.stringify(newBalance)) + let continueWithTx = true + let shouldUpdateBalance = false + try { + + if (newBalance && typeof newBalance.balance !== 'undefined') { + + if (typeof account.balance === 'undefined') { + shouldUpdateBalance = true + } else if (newBalance.balance.toString() !== account.balance.toString() || newBalance.unconfirmed.toString() !== account.unconfirmed.toString() ) { + shouldUpdateBalance = true + } else if (typeof newBalance.balanceStaked !== 'undefined') { + if (typeof account.balanceStaked === 'undefined') { + shouldUpdateBalance = true + } else if (newBalance.balanceStaked * 1 !== account.balanceStaked * 1) { // toString here somehow do undefined sometimes + shouldUpdateBalance = true + } + } + + if (typeof newBalance.balanceScanBlock !== 'undefined' && (typeof account.balanceScanBlock === 'undefined' || newBalance.balanceScanBlock * 1 < account.balanceScanBlock * 1)) { + continueWithTx = false + updateObj.balanceProvider = newBalance.provider + updateObj.balanceScanLog = account.address + ' block error, ignored new ' + newBalance.balance + ' block ' + newBalance.balanceScanBlock + ', old balance ' + account.balance + ' block ' + account.balanceScanBlock + updateObj.balanceScanError = 'account.balanceBadBlock' + } else if (shouldUpdateBalance) { + updateObj.balanceFix = newBalance.balance // lets send to db totally not changed big number string + updateObj.balanceTxt = newBalance.balance.toString() // and string for any case + updateObj.unconfirmedFix = newBalance.unconfirmed || 0 // lets send to db totally not changed big number string + updateObj.unconfirmedTxt = newBalance.unconfirmed || '' // and string for any case + updateObj.balanceStakedTxt = newBalance.balanceStaked || '0' + updateObj.balanceProvider = newBalance.provider + if (typeof newBalance.balanceScanBlock !== 'undefined') { + updateObj.balanceScanBlock = newBalance.balanceScanBlock + } + updateObj.balanceScanLog = account.address + ' all ok, new balance ' + newBalance.balance + ', old balance ' + account.balance + ', ' + balanceError + updateObj.balanceScanError = '' + const logData = {} + logData.walletHash = account.walletHash + logData.currencyCode = account.currencyCode + logData.address = account.address + logData.addressShort = account.address ? account.address.slice(0, 10) : 'none' + logData.balanceScanTime = account.balanceScanTime + '' + logData.balanceProvider = account.balanceProvider + '' + logData.balance = account.balance + '' + logData.newBalanceProvider = account.newBalanceProvider + '' + logData.newBalance = (newBalance.balance * 1) + '' + MarketingEvent.setBalance(logData.walletHash, logData.currencyCode, logData.newBalance, logData) + } else { + updateObj.balanceScanLog = account.address + ' not changed, old balance ' + account.balance + ', ' + balanceError + updateObj.balanceScanError = '' + if (typeof newBalance.provider !== 'undefined') { + updateObj.balanceProvider = newBalance.provider + } + } + Log.daemon('UpdateAccountBalanceAndTransactions newBalance ok Prepared ' + account.currencyCode + ' ' + account.address + ' new balance ' + newBalance.balance + ' provider ' + newBalance.provider + ' old balance ' + account.balance, JSON.stringify(updateObj)) + } else { + updateObj.balanceScanLog = account.address + ' no balance, old balance ' + account.balance + ', ' + balanceError + updateObj.balanceScanError = 'account.balanceBadNetwork' + Log.daemon('UpdateAccountBalanceAndTransactions newBalance not Prepared ' + account.currencyCode + ' ' + account.address + ' old balance ' + account.balance, JSON.stringify(updateObj)) + } + } catch (e) { + if (config.debug.appErrors) { + console.log('UpdateAccountBalanceAndTransactions newBalance from ' + source + ' loaded ' + account.currencyCode + ' ' + addressToScan + ' format error ' + e.message) + } + e.message += ' while accountBalanceDS.updateAccountBalance formatting' + throw e + } + + if (account.balanceScanLog) { + updateObj.balanceScanLog += ' ' + account.balanceScanLog + } + + try { + updateObj.balanceScanLog = new Date().toISOString() + ' ' + updateObj.balanceScanLog.substr(0, 1000) + await accountBalanceDS.updateAccountBalance({ updateObj }, account) + } catch (e) { + e.message += ' while accountBalanceDS.updateAccountBalance' + throw e + } + + if (!continueWithTx || onlyBalances) { + return shouldUpdateBalance // balance error - tx will not be good also + } + try { + let transactionsError = ' ' + let newTransactions = false + try { + Log.daemon('UpdateAccountBalanceAndTransactions newTransactions ' + account.currencyCode + ' ' + account.address) + if (account.currencyCode === 'BTC' || account.currencyCode === 'LTC') { + const additional = {... account.accountJson} + additional.addresses = await accountScanningDS.getAddresses({ + currencyCode: account.currencyCode, + walletHash: account.walletHash + }) + if (account.walletIsHd && account.currencyCode !== 'LTC') { + additional.walletPub = true // actually not needed pub - just flag + } + newTransactions = await BlocksoftTransactions.getTransactions({ account, additional }, 'AccountRunTransactionsBtc') + } else { + newTransactions = await BlocksoftTransactions.getTransactions({ account, additional: account.accountJson }, 'AccountRunTransactions') + } + if (!newTransactions || newTransactions.length === 0) { + transactionsError = ' empty transactions ' + account.currencyCode + ' ' + account.address + } else { + transactionsError = ' found transactions ' + newTransactions.length + } + } catch (e) { + if (config.debug.appErrors) { + console.log('UpdateAccountBalanceAndTransactions newTransactions something wrong ' + account.currencyCode + ' ' + account.address + ' => transactionsError ' + e.message, e) + } + Log.errDaemon('UpdateAccountBalanceAndTransactions newTransactions something wrong ' + account.currencyCode + ' ' + account.address + ' => transactionsError ' + e.message) + + transactionsError = ' found transactionsError ' + e.message + } + + Log.daemon('UpdateAccountBalanceAndTransactions newTransactions loaded ' + account.currencyCode + ' ' + addressToScan) + + let transactionUpdateObj + try { + transactionUpdateObj = await AccountTransactionsRecheck(newTransactions, account, 'RECHECK ' + source) + } catch (e) { + e.message += ' while AccountTransactionsRecheck' + throw e + } + // Log.daemon('res', transactionUpdateObj) + try { + transactionUpdateObj.transactionsScanLog = new Date().toISOString() + ' ' + transactionsError + ', ' + transactionUpdateObj.transactionsScanLog + if (account.transactionsScanLog) { + transactionUpdateObj.transactionsScanLog += ' ' + account.transactionsScanLog + } + await accountDS.updateAccount({ updateObj: transactionUpdateObj }, account) + } catch (e) { + e.message += ' while accountDS.updateAccount' + throw e + } + } catch (e) { + if (config.debug.appErrors) { + console.log('UpdateAccountBalanceAndTransactions newTransactions something wrong ' + account.currencyCode + ' ' + account.address + ' => transactionsError2 ' + e.message, e) + } + Log.errDaemon('UpdateAccountBalanceAndTransactions newTransactions something wrong ' + account.currencyCode + ' ' + account.address + ' => transactionsError2 ' + e.message) + } + + CACHE_ONE_ACCOUNTS[account.currencyCode + '_' + account.address] = 1 + + if (account.currencyCode === 'TRX') { + for (const sub of accounts) { + if (sub.currencyCode === 'TRX_USDT') { + if (await this._accountRun(sub, accounts, source + ' GONE INNER', CACHE_VALID_TIME, true)) { + shouldUpdateBalance = true + } + break + } + } + for (const sub of accounts) { + if (sub.currencyCode !== 'TRX_USDT' && sub.currencyCode.indexOf('TRX_') === 0) { + if (await this._accountRun(sub, accounts, source + ' GONE INNER2', CACHE_VALID_TIME, true)) { + shouldUpdateBalance = true + } + } + } + } + + await this.loadFioData(account.currencyCode) + + Log.daemon('UpdateAccountBalanceAndTransactions _accountRun finish ' + account.id + ' ' + account.currencyCode + ' ' + account.address) + + return shouldUpdateBalance + } + +} + + +const singleton = new UpdateAccountBalanceAndTransactions() +export default singleton diff --git a/src/daemons/back/UpdateAccountBalanceAndTransactionsHD.js b/src/daemons/back/UpdateAccountBalanceAndTransactionsHD.js new file mode 100644 index 000000000..4a72633f6 --- /dev/null +++ b/src/daemons/back/UpdateAccountBalanceAndTransactionsHD.js @@ -0,0 +1,319 @@ +/** + * @version 0.11 + */ +import BlocksoftKeysStorage from '../../../crypto/actions/BlocksoftKeysStorage/BlocksoftKeysStorage' + +import BlocksoftBalances from '../../../crypto/actions/BlocksoftBalances/BlocksoftBalances' +import BlocksoftTransactions from '../../../crypto/actions/BlocksoftTransactions/BlocksoftTransactions' + +import Log from '../../services/Log/Log' +import MarketingEvent from '../../services/Marketing/MarketingEvent' + +import walletPubScanningDS from '../../appstores/DataSource/Wallet/WalletPubScanning' +import accountScanningDS from '../../appstores/DataSource/Account/AccountScanning' + +import AccountTransactionsRecheck from './apputils/AccountTransactionsRecheck' +import accountDS from '../../appstores/DataSource/Account/Account' +import settingsActions from '../../appstores/Stores/Settings/SettingsActions' +import appNewsDS from '../../appstores/DataSource/AppNews/AppNews' +import config from '@app/config/config' +import accountBalanceDS from '@app/appstores/DataSource/AccountBalance/AccountBalance' + +let CACHE_LAST_TIME = false +const CACHE_VALID_10MIN_TIME = 600000 // 10 minutes +let CACHE_WALLETS_HASH = {} +class UpdateAccountBalanceAndTransactionsHD { + + /** + * @param {string} callParams.source + * @param {boolean} callParams.force + * @returns {Promise} + */ + updateAccountBalanceAndTransactionsHD = async (callParams) => { + const source = callParams.source || 'FRONT' + const force = callParams.force || false + + if (!force || source === 'BACK') { + const setting = await settingsActions.getSetting('scannerCode') + if (setting === 'none') { + return false + } else if (CACHE_LAST_TIME && setting === '10min') { + const now = new Date().getTime() + const diff = now - CACHE_LAST_TIME + if (diff < CACHE_VALID_10MIN_TIME) { + Log.daemon('UpdateAccountBalanceAndTransactionsHD skipped by diff ' + diff) + return false + } + } + } + + try { + const params = { + force + } + if (source !== 'BACK') { + params.walletHash = await settingsActions.getSelectedWallet('UpdateAccountBalanceAndTransactionsHD') + } + + Log.daemon('UpdateAccountBalanceHD called ' + source + ' ' + JSON.stringify(params)) + + const walletPubs = await walletPubScanningDS.getWalletPubsForScan(params) + + if (!walletPubs || walletPubs.length === 0) return false + + this._logNews = {} + CACHE_WALLETS_HASH = {} + for (const walletPub of walletPubs) { + await this._walletRun(walletPub, source) + } + + if (this._logNews) { + let key + for (key in this._logNews) { + await appNewsDS.saveAppNews({ + onlyOne: true, walletHash: key, currencyCode: 'BTC', newsGroup: 'ONE_BY_ONE_SCANNER', newsName: 'HD_SCANNED_LAST_TIME', + newsJson: { log: this._logNews[key].substr(0, 50) + '...' } + }) + } + } + + CACHE_LAST_TIME = new Date().getTime() + } catch (e) { + Log.errDaemon('UpdateAccountBalanceHD balanceError ' + source + ' ' + e.message) + return false + } + return true + } + + /** + * @param {Object} walletPub + * @param {string} walletPub.id + * @param {string} walletPub.currencyCode + * @param {string} walletPub.walletHash + * @param {string} walletPub.walletPubType + * @param {string} walletPub.walletPubValue + * @param {string} walletPub.transactionsScanTime + * @param {string} walletPub.balance + * @param {string} walletPub.balanceFix + * @param {string} walletPub.balanceTxt + * @param {string} walletPub.unconfirmed + * @param {string} walletPub.unconfirmedFix + * @param {string} walletPub.unconfirmedTxt + * @param {string} walletPub.balanceProvider + * @param {string} walletPub.balanceScanTime + * @param {string} walletPub.balanceScanLog + * @param {string} source + * @returns {Promise} + * @private + */ + async _walletRun(walletPub, source) { + let newBalance = false + let balanceError = false + const addressToScan = walletPub.walletPubValue + if (config.debug.appErrors) { + console.log(new Date().toISOString() + ' UpdateAccountBalanceAndTransactionsHD newBalance started ' + walletPub.currencyCode + ' ' + addressToScan) + } + try { + Log.daemon('UpdateAccountBalanceAndTransactionsHD newBalance ' + walletPub.currencyCode + ' ' + addressToScan) + newBalance = await (BlocksoftBalances.setCurrencyCode(walletPub.currencyCode).setAddress(addressToScan).setWalletHash(walletPub.walletHash)).getBalance('AccountRunHD') + if (!newBalance || typeof newBalance.balance === 'undefined') { + balanceError = ' something wrong with balance ' + walletPub.currencyCode + ' ' + addressToScan + ' => ' + JSON.stringify(newBalance) + Log.daemon('UpdateAccountBalanceAndTransactionsHD newBalance something wrong ' + walletPub.currencyCode + ' ' + addressToScan + ' => ' + JSON.stringify(newBalance)) + if (config.debug.appErrors) { + console.log('UpdateAccountBalanceAndTransactionsHD newBalance error ' + balanceError) + } + } else { + balanceError = ' found in one ' + JSON.stringify(newBalance) + } + } catch (e) { + balanceError = ' found balanceError ' + e.message + if (config.debug.appErrors) { + console.log('UpdateAccountBalanceAndTransactionsHD newBalance error ' + balanceError) + } + } + + Log.daemon('UpdateAccountBalanceAndTransactionsHD newBalance loaded ' + walletPub.currencyCode + ' ' + addressToScan, JSON.stringify(newBalance)) + const updateObj = { + balanceScanTime: Math.round(new Date().getTime() / 1000) + } + if (newBalance) { + if (newBalance.balance * 1 !== walletPub.balance * 1 || newBalance.unconfirmed * 1 !== walletPub.unconfirmed * 1) { + if (typeof newBalance.specialMark !== 'undefined' && newBalance.specialMark === 'badServer' && walletPub.balance * 1 > 0) { + updateObj.balanceScanLog = 'badServer so not changed, old balance ' + walletPub.balance + ', ' + balanceError + if (typeof newBalance.provider !== 'undefined') { + updateObj.balanceProvider = newBalance.provider + } + } else { + updateObj.balanceFix = newBalance.balance // lets send to db totally not changed big number string + updateObj.balanceTxt = newBalance.balance // and string for any case + updateObj.unconfirmedFix = newBalance.unconfirmed || 0 // lets send to db totally not changed big number string + updateObj.unconfirmedTxt = newBalance.unconfirmed || '' // and string for any case + updateObj.balanceProvider = newBalance.provider + updateObj.balanceScanLog = 'all ok, new balance ' + newBalance.balance + ', old balance ' + walletPub.balance + ', ' + balanceError + + const logData = {} + logData.walletHash = walletPub.walletHash + logData.currencyCode = walletPub.currencyCode + logData.address = walletPub.walletPubValue + logData.addressShort = walletPub.walletPubValue ? walletPub.walletPubValue.slice(0, 10) : 'none' + logData.balanceScanTime = walletPub.balanceScanTime + '' + logData.balanceProvider = walletPub.balanceProvider + '' + logData.balance = walletPub.balance + '' + logData.newBalanceProvider = walletPub.newBalanceProvider + '' + logData.newBalance = (newBalance.balance * 1) + '' + MarketingEvent.setBalance(logData.walletHash, logData.currencyCode, logData.newBalance, logData) + } + } else { + updateObj.balanceScanLog = 'not changed, old balance ' + walletPub.balance + ', ' + balanceError + if (typeof newBalance.provider !== 'undefined') { + updateObj.balanceProvider = newBalance.provider + } + } + Log.daemon('UpdateAccountBalanceAndTransactionsHD newBalance okPrepared ' + walletPub.currencyCode + ' ' + walletPub.walletPubValue + ' new balance ' + newBalance.balance + ' provider ' + newBalance.provider + ' old balance ' + walletPub.balance, JSON.stringify(updateObj)) + } else { + updateObj.balanceScanLog = 'no balance, old balance ' + walletPub.balance + ', ' + balanceError + Log.daemon('UpdateAccountBalanceAndTransactions newBalance notPrepared ' + walletPub.currencyCode + ' ' + walletPub.walletPubValue + ' old balance ' + walletPub.balance, JSON.stringify(updateObj)) + } + + if (typeof CACHE_WALLETS_HASH[walletPub.walletHash] !== 'undefined') { + + const transactionUpdateObj = { + transactionsScanTime: Math.round(new Date().getTime() / 1000), + transactionsScanLog: new Date().toISOString() + ' transaction prev scanned by ' + CACHE_WALLETS_HASH[walletPub.walletHash] + } + if (walletPub.transactionsScanLog) { + transactionUpdateObj.transactionsScanLog += ' ' + walletPub.transactionsScanLog + } + await walletPubScanningDS.updateTransactions({ updateObj: transactionUpdateObj }, walletPub) + return false + } + CACHE_WALLETS_HASH[walletPub.walletHash] = walletPub.walletPubValue + + try { + if (typeof this._logNews[walletPub.walletHash] === 'undefined') { + this._logNews[walletPub.walletHash] = '' + } + this._logNews[walletPub.walletHash] += ' ' + walletPub.walletPubType + ' ' + updateObj.balanceScanLog + updateObj.balanceScanLog = new Date().toISOString() + ' ' + updateObj.balanceScanLog + if (walletPub.balanceScanLog) { + updateObj.balanceScanLog += ' ' + walletPub.balanceScanLog.substr(0, 1000) + } + await walletPubScanningDS.updateBalance({ updateObj }, walletPub) + } catch (e) { + e.message += ' while accountBalanceDS.updateAccountBalance' + throw e + } + + let transactionsError = ' ' + let newTransactions = false + + let addresses = await accountScanningDS.getAddresses({ currencyCode: walletPub.currencyCode, walletHash: walletPub.walletHash, withBalances : true }) + try { + const addressesBlockchain = await BlocksoftTransactions.getAddresses({ + account : { currencyCode : walletPub.currencyCode, address : walletPub.walletPubValue, walletHash : walletPub.walletHash}, + additional : { walletPub }, + withBalances : true + }, 'UpdateAccountBalanceAndTransactionsHD addressesBlockchain') + + const sql = [] + const derivations = [] + let count = 0 + if (addressesBlockchain) { + for (const address in addressesBlockchain) { + const path = addressesBlockchain[address].path + const balance = addressesBlockchain[address].balance + if (typeof addresses[address] !== 'undefined') { + if (addresses[address].balanceTxt !== balance) { + const updateObj = { + balanceScanTime: Math.round(new Date().getTime() / 1000), + balanceScanLog: ' newBalance ' + balance, + balanceScanError: '', + balanceTxt: balance + } + await accountBalanceDS.updateAccountBalance({ updateObj }, + { id : addresses[address].id, currencyCode : walletPub.currencyCode, address : walletPub.walletPubValue, walletHash : walletPub.walletHash} + ) + } + if (addresses[address].alreadyShown === 1) { + // do nothing - can log + } else { + sql.push(`'` + address + `'`) + } + } else { + if (path.toString().length < 2) continue + const tmp = { + address, + path, + alreadyShown: 1, + walletPubId: walletPub.id + } + count++ + derivations.push(tmp) + } + } + if (count > 0) { + await accountDS.discoverAccountsFromHD({ currencyCode : 'BTC', walletHash: walletPub.walletHash, source, derivations }, source) + addresses = await accountScanningDS.getAddresses({ currencyCode: walletPub.currencyCode, walletHash: walletPub.walletHash }) + } + } + if (sql.length > 0) { + await accountDS.massUpdateAccount(`address IN (` + sql.join(',') + ') AND (already_shown IS NULL OR already_shown=0)', 'already_shown=1') + } + } catch (e) { + if (config.debug.appErrors) { + console.log(' transactionsAddressesError ' + e.message) + } + transactionsError = ' found transactionsAddressesError ' + e.message + } + try { + Log.daemon('UpdateAccountBalanceAndTransactionsHD newTransactions ' + walletPub.currencyCode + ' ' + walletPub.walletPubValue) + newTransactions = await BlocksoftTransactions.getTransactions({ + account : { + currencyCode: walletPub.currencyCode, + address: walletPub.walletPubValue, + walletHash : walletPub.walletHash + }, + additional : { addresses, walletPub } + },'AccountRunHD') + + if (!newTransactions || newTransactions.length === 0) { + transactionsError += ' something wrong with balance ' + walletPub.currencyCode + ' ' + walletPub.walletPubValue + ' => ' + JSON.stringify(newTransactions) + Log.daemon('UpdateAccountBalanceAndTransactionsHD newTransactions something wrong ' + walletPub.currencyCode + ' ' + walletPub.walletPubValue + ' => ' + JSON.stringify(newTransactions)) + } else { + transactionsError += ' found transactions ' + newTransactions.length + } + } catch (e) { + if (config.debug.appErrors) { + console.log(' found transactionsError ' + e.message) + } + transactionsError += ' found transactionsError ' + e.message + } + + Log.daemon('UpdateAccountBalanceAndTransactionsHD newTransactions loaded ' + walletPub.currencyCode + ' ' + addressToScan) + + const transactionUpdateObj = await AccountTransactionsRecheck(newTransactions, walletPub, source) + + try { + transactionUpdateObj.transactionsScanLog = new Date().toISOString() + ' ' + transactionsError + ' ' + transactionUpdateObj.transactionsScanLog + if (walletPub.transactionsScanLog) { + transactionUpdateObj.transactionsScanLog += ' ' + walletPub.transactionsScanLog + } + await walletPubScanningDS.updateTransactions({ updateObj: transactionUpdateObj }, walletPub) + } catch (e) { + if (config.debug.appErrors) { + console.log(' walletPubScanningDS.updateWalletPub ' + e.message) + } + e.message += ' while walletPubScanningDS.updateWalletPub' + throw e + } + if (config.debug.appErrors) { + console.log(new Date().toISOString() + ' UpdateAccountBalanceAndTransactionsHD newBalance finished ' + walletPub.currencyCode + ' ' + addressToScan) + } + return true + } + +} + + +const singleton = new UpdateAccountBalanceAndTransactionsHD() +export default singleton diff --git a/src/daemons/back/UpdateAccountPendingTransactions.js b/src/daemons/back/UpdateAccountPendingTransactions.js new file mode 100644 index 000000000..aaedb09cc --- /dev/null +++ b/src/daemons/back/UpdateAccountPendingTransactions.js @@ -0,0 +1,27 @@ +/** + * @version 0.11 + */ +import Log from '@app/services/Log/Log' +import config from '@app/config/config' +import BlocksoftTransactions from '@crypto/actions/BlocksoftTransactions/BlocksoftTransactions' + +class UpdateAccountPendingTransactions { + + updateAccountPendingTransactions = async (callParams = {}) => { + const source = callParams.source || 'FRONT' + let result = false + try { + result = await (BlocksoftTransactions.getTransactionsPending({account : {currencyCode : 'TRX'}}, 'AccountRunPending from ' + source)) // only trx for now + } catch (e) { + if (config.debug.appErrors) { + console.log('UpdateAccountPendingTransactions error ' + source + ' ' + e.message, e) + } + Log.errDaemon('UpdateAccountPendingTransactions error ' + source + ' ' + e.message) + } + return result + } +} + + +const singleton = new UpdateAccountPendingTransactions() +export default singleton diff --git a/src/daemons/back/UpdateAppNewsDaemon.js b/src/daemons/back/UpdateAppNewsDaemon.js new file mode 100644 index 000000000..46f9912e1 --- /dev/null +++ b/src/daemons/back/UpdateAppNewsDaemon.js @@ -0,0 +1,182 @@ +/** + * @version 0.43 + */ +import Log from '@app/services/Log/Log' +import ApiProxy from '@app/services/Api/ApiProxy' +import config from '@app/config/config' + +import appNewsDS from '@app/appstores/DataSource/AppNews/AppNews' +import cryptoWalletsDS from '@app/appstores/DataSource/CryptoWallets/CryptoWallets' +import appNewsInitStore from '@app/appstores/Stores/AppNews/AppNewsInitStore' +import store from '@app/store' +import settingsActions from '@app/appstores/Stores/Settings/SettingsActions' + +let CACHE_NEWS_HASH = '' +let CACHE_LAST_TIME = false +const CACHE_VALID_TIME = 120000 // 2 minute + +class UpdateAppNewsDaemon { + + _canUpdate = true + + _goToNotifications = false + + goToNotifications = (code) => { + if (this._goToNotifications === 'INITED_APP') return false // its final status + this._goToNotifications = code + } + + isGoToNotifications = (code) => { + if (!this._goToNotifications) return false + return this._goToNotifications === code + } + + /** + * @return {Promise} + */ + updateAppNewsDaemon = async (params = {}, dataUpdate = false) => { + if (typeof params === 'undefined' || typeof params.force === 'undefined' || !params) { + if (!this._canUpdate) { + return false + } + const now = new Date().getTime() + const diff = now - CACHE_LAST_TIME + if (diff < CACHE_VALID_TIME) { + Log.daemon('UpdateAppNews skipped by diff ' + diff) + return false + } + } + this._canUpdate = false + + const walletHash = await settingsActions.getSelectedWallet('UpdateNewsDaemon') + let res + let asked = false + if (!dataUpdate) { + if (config.debug.appErrors) { + console.log(new Date().toISOString() + ' UpdateNewsDaemon loading new') + } + asked = true + try { + res = await ApiProxy.getAll({...params, source: 'UpdateAppNewsDaemon.updateAppNews' }) + } catch (e) { + this._canUpdate = true + return false + } + } else { + res = dataUpdate + } + + if (store.getState().appNewsStore.appNewsList.length === 0) { + await appNewsInitStore() + } + + if (!res || typeof res === 'undefined' || typeof res.news === 'undefined' || !res.news || res.news.length === 0) { + this._canUpdate = true + return false + } + if (res.newsHash === CACHE_NEWS_HASH) { + // can put log for recheck hashing cache + this._canUpdate = true + return false + } + + if (!asked) { + if (config.debug.appErrors) { + console.log(new Date().toISOString() + ' UpdateNewsDaemon loaded proxy') + } + } + + CACHE_NEWS_HASH = typeof res.newsHash !== 'undefined' ? res.newsHash : '' + + const keys = { + currencyCode: 'currencyCode', + newsSource: 'source', + newsGroup: 'group', + newsPriority: 'priority', + newsName: 'name', + newsJson: 'data', + newsCustomTitle: 'title', + newsCustomText: 'text', + newsImage: 'image', + newsUrl: 'url', + newsCustomCreated: 'createdAt', + newsUniqueKey: 'serverId', + newsServerId: 'serverId', + newsServerHash: 'status' + } + let savedAny = false + const allNews = store.getState().appNewsStore.appNewsList + const allNewsIndexed = {} + if (allNews.length) { + for (const news of allNews) { + allNewsIndexed[news.newsServerId] = { + newsName : news.newsName, + newsServerHash: news.newsServerHash + } + } + } + + try { + let index = 0 + for (const row of res.news) { + if (index > 2) { + // break + } + index++ + const toSave = { + newsNeedPopup: row.needPopup ? 1 : 0, + newsLog: new Date().toISOString() + ' loaded from Server' + } + for (const saveField in keys) { + const serverField = keys[saveField] + if (typeof row[serverField] !== 'undefined' && row[serverField]) { + toSave[saveField] = row[serverField] + } + if (typeof row[serverField] !== 'undefined' && row[serverField]) { + toSave[saveField] = row[serverField] + } + } + let fromStoreStatus = 'no_cache' + let fromStore = false + if (typeof row.isBroadcast === 'undefined' || row.isBroadcast === false) { + if (row.newsGroup !== 'GOOGLE_EVENTS' && typeof allNewsIndexed[toSave.newsUniqueKey] !== 'undefined') { + fromStore = allNewsIndexed[toSave.newsUniqueKey] + fromStoreStatus = 'check_update_ind' + // not loaded + } + toSave.walletHash = walletHash + } else if (typeof allNewsIndexed[toSave.newsUniqueKey] !== 'undefined') { + fromStore = allNewsIndexed[toSave.newsUniqueKey] + fromStoreStatus = 'check_update' + } else { + fromStoreStatus = 'can_insert' + } + if (typeof row.status !== 'undefined' && row.status && row.status.toString() === '33') { + toSave.removed = 33 + } + const saved = await appNewsDS.saveAppNews(toSave, fromStoreStatus, fromStore) + if (saved.updated) { + savedAny = true + } else { + await appNewsDS.pushAppNewsForApi(saved) + } + } + CACHE_LAST_TIME = new Date().getTime() + } catch (e) { + this._canUpdate = true + Log.err('UpdateAppNews saving result error ' + e.message) + } + + if (savedAny || allNews.length === 0) { + try { + await appNewsInitStore() + } catch (e) { + Log.err('UpdateAppNews appNewsInitStore call error ' + e.message) + } + } + + this._canUpdate = true + } +} + +export default new UpdateAppNewsDaemon diff --git a/src/daemons/back/UpdateAppTasksDaemon.js b/src/daemons/back/UpdateAppTasksDaemon.js new file mode 100644 index 000000000..ac667adde --- /dev/null +++ b/src/daemons/back/UpdateAppTasksDaemon.js @@ -0,0 +1,62 @@ +/** + * @version 0.11 + * @todo remove as deprecated if will be not shown + */ +import Log from '../../services/Log/Log' + +import appTaskDoingDS from '../../appstores/DataSource/AppTask/AppTaskDoing' + +import AppTasksDiscoverHD from './apptasks/AppTasksDiscoverHD' +import AppTasksDiscoverBalancesHidden from './apptasks/AppTasksDiscoverBalancesHidden' +import AppTasksDiscoverBalancesNotAdded from './apptasks/AppTasksDiscoverBalancesNotAdded' + + +class UpdateAppTasksDaemon { + + /** + * @param {string} params.taskName + * @return {Promise} + */ + updateAppTasksDaemon = async (params) => { + return false + Log.daemon('UpdateAppTaskDaemon called') + + const appTasks = await appTaskDoingDS.getTasksForRun(params) + if (!appTasks) { + return false + } + let appTask + for (appTask of appTasks) { + try { + Log.daemon('UpdateAppTaskDaemon started #' + appTask.id + ' ' + appTask.taskName, appTask) + await appTaskDoingDS.setStarted(appTask) + let log + switch (appTask.taskName) { + case 'DISCOVER_HD': + log = await AppTasksDiscoverHD.run(appTask) + break + case 'DISCOVER_BALANCES_HIDDEN': + log = await AppTasksDiscoverBalancesHidden.run(appTask) + break + case 'DISCOVER_BALANCES_NOT_ADDED': + log = await AppTasksDiscoverBalancesNotAdded.run(appTask) + break + default: + Log.errDaemon('UpdateAppTask unknown name ' + appTask.taskName, appTask) + break + } + if (log) { + appTask.taskLog = log + } + Log.daemon('UpdateAppTaskDaemon finished #' + appTask.id + ' ' + appTask.taskName + ' Log ' + log, appTask) + await appTaskDoingDS.setFinished(appTask) + } catch (e) { + Log.errDaemon('UpdateAppTaskDaemon finished #' + appTask.id + ' ' + appTask.taskName + ' error ' + e.message, appTask) + } + } + + Log.daemon('UpdateAppTaskDaemon finished') + } +} + +export default new UpdateAppTasksDaemon diff --git a/src/daemons/back/UpdateCardsDaemon.js b/src/daemons/back/UpdateCardsDaemon.js new file mode 100644 index 000000000..f56c5d87c --- /dev/null +++ b/src/daemons/back/UpdateCardsDaemon.js @@ -0,0 +1,188 @@ +/** + * @version 0.41 + */ +import Log from '@app/services/Log/Log' + +import config from '@app/config/config' + +import cardDS from '@app/appstores/DataSource/Card/Card' +import cryptoWalletsDS from '@app/appstores/DataSource/CryptoWallets/CryptoWallets' +import ApiProxy from '@app/services/Api/ApiProxy' +import settingsActions from '@app/appstores/Stores/Settings/SettingsActions' + +class UpdateCardsDaemon { + + _canUpdate = true + + /** + * @string params.numberCard + * @return {Promise} + */ + updateCardsDaemon = async (params = {}, dataUpdate = false) => { + if (typeof params !== 'undefined' && params && typeof params.force === 'undefined' || !params.force) { + if (!this._canUpdate && !dataUpdate) return false + } + + this._canUpdate = false + const res = await this._updateCardsDaemon(params, dataUpdate) + this._canUpdate = true + + return res + } + + _updateCardsDaemon = async (params, dataUpdate = false) => { + + Log.daemon('UpdateCardsDaemon called') + + let asked = false + if (!dataUpdate) { + const authHash = await settingsActions.getSelectedWallet('UpdateCardsDaemon') + if (!authHash) { + Log.daemon('UpdateCardsDaemon skipped as no auth') + return false + } + if (config.debug.appErrors) { + console.log(new Date().toISOString() + ' UpdateCardsDaemon loading new') + } + asked = true + try { + dataUpdate = await ApiProxy.getAll({ ...params, source: 'UpdateCardsDaemon.updateCards' }) + } catch (e) { + if (config.debug.appErrors) { + console.log('UpdateCardsDaemon error ' + e.message) + } + return false + } + } + + if (!dataUpdate) { + return false + } + + if (typeof dataUpdate.forCardsAll !== 'undefined' && dataUpdate.forCardsAll) { + if (!asked) { + if (config.debug.appErrors) { + console.log(new Date().toISOString() + ' UpdateCardsDaemon loaded proxy forCardsAll') + } + } + try { + const saved = await cardDS.getCards() + const cardsSaved = {} + if (saved) { + for (const row of saved) { + cardsSaved[row.number] = row + if (row.cardToSendId > 0) { + cardsSaved['sid_' + row.cardToSendId] = row + } + } + } + for (const number in dataUpdate.forCardsAll) { + const dataOne = dataUpdate.forCardsAll[number] + if (!dataOne) continue + const mapping = { + cardToSendStatus: dataOne.card_to_send_status, + cardToSendId: dataOne.card_to_send_id, + number: dataOne.card_number, + expirationDate: dataOne.card_expiration_date, + type: dataOne.card_type, + countryCode: dataOne.card_country_code, + cardName: dataOne.card_name, + cardHolder: dataOne.card_holder, + currency: dataOne.card_currency, + walletHash: dataOne.card_wallet_hash, + verificationServer: dataOne.card_verification_server, + cardEmail: dataOne.card_email, + cardDetailsJson: dataOne.card_details_json, + cardVerificationJson: dataOne.card_verification_json, + cardCreateWalletHash: dataOne.log_wallet + } + + let currentToUpdate = false + if (typeof cardsSaved[number] === 'undefined' || !cardsSaved[number]) { + if (typeof dataOne.card_to_send_id === 'undefined' || !dataOne.card_to_send_id + || typeof cardsSaved['sid_' + dataOne.card_to_send_id] === 'undefined' || !cardsSaved['sid_' + dataOne.card_to_send_id] + ) { + // do nothing to insert + } else { + currentToUpdate = cardsSaved['sid_' + dataOne.card_to_send_id] + } + } else { + currentToUpdate = cardsSaved[number] + } + + if (currentToUpdate === false && typeof currentToUpdate !== 'undefined') { + await cardDS.saveCard({ + insertObjs: [mapping] + }) + } else { + if (currentToUpdate.cardToSendStatus && currentToUpdate.cardToSendStatus * 1 > dataOne.card_to_send_status * 1) { + // skip + } else { + const updateObj = { + cardVerificationJson: dataOne.card_verification_json + } + for (const key in mapping) { + if (key === 'cardCreateWalletHash') continue // to skip + if (currentToUpdate[key] !== mapping[key]) { + updateObj[key] = mapping[key] + } + } + const dataForSave = { + key: { + id : currentToUpdate.id + }, + updateObj + } + await cardDS.updateCard(dataForSave) + } + } + } + + } catch (e) { + if (config.debug.appErrors) { + console.log('UpdateCardsDaemon save error1 ' + e.message, e) + } + Log.errDaemon('UpdateCardsDaemon save error1 ' + e.message) + return false + } + } else if (typeof dataUpdate.forCardsOk !== 'undefined' && dataUpdate.forCardsOk) { + if (!asked) { + console.log(new Date().toISOString() + ' UpdateCardsDaemon loaded proxy forCardsOk', JSON.stringify(params)) + } + try { + for (const number in dataUpdate.forCardsOk) { + const dataOne = dataUpdate.forCardsOk[number] + if (!dataOne) continue + const dataForSave = { + key: { + number + }, + updateObj: { + cardVerificationJson: JSON.stringify(dataOne) + } + } + await cardDS.updateCard(dataForSave) + } + } catch (e) { + if (config.debug.appErrors) { + console.log('UpdateCardsDaemon status save error2 ' + e.message) + } + Log.errDaemon('UpdateCardsDaemon status save error2 ' + e.message) + return false + } + } + + if (typeof params !== 'undefined' && params && params.numberCard !== 'undefined') { + if (dataUpdate && dataUpdate.forCardsOk && typeof dataUpdate.forCardsOk[params.numberCard] !== 'undefined' && dataUpdate.forCardsOk[params.numberCard]) { + return dataUpdate.forCardsOk[params.numberCard] + } else { + return false + } + } + + return false + } + +} + +export default new UpdateCardsDaemon diff --git a/src/daemons/back/UpdateCashBackDataDaemon.js b/src/daemons/back/UpdateCashBackDataDaemon.js new file mode 100644 index 000000000..6c9d3301b --- /dev/null +++ b/src/daemons/back/UpdateCashBackDataDaemon.js @@ -0,0 +1,104 @@ +/** + * @version 0.42 + */ +import Log from '@app/services/Log/Log' + +import cashBackActions from '@app/appstores/Stores/CashBack/CashBackActions' +import CashBackUtils from '@app/appstores/Stores/CashBack/CashBackUtils' +import ApiProxy from '@app/services/Api/ApiProxy' + +import config from '@app/config/config' +import { StreamSupportActions } from '@app/appstores/Stores/StreamSupport/StreamSupportStoreActions' + +class UpdateCashBackDataDaemon { + + _canUpdate = true + + /** + * @return {Promise} + */ + updateCashBackDataDaemon = async (params = {}, dataUpdate = false) => { + if (!this._canUpdate) return false + + this._canUpdate = false + + let data = false + let asked = false + let cashbackToken = false + if (!dataUpdate) { + if (config.debug.appErrors) { + console.log(new Date().toISOString() + ' UpdateCashBackDataDaemon loaded new') + } + asked = true + try { + data = await ApiProxy.getAll({ ...params, source: 'UpdateCashBackDataDaemon.updateCashBackData' }) + } catch (e) { + if (config.debug.appErrors) { + console.log('UpdateCashBackDataDaemon error ' + e.message) + } + await cashBackActions.updateAll({ error : { + title: e.message, + time: new Date().getTime() + }}) + this._canUpdate = true + return + } + } else { + data = dataUpdate + } + + if (typeof data.cbChatToken !== 'undefined' && data.cbChatToken ) { + StreamSupportActions.setData(data.cbChatToken) + } + + let customToken = CashBackUtils.getWalletToken() + try { + if (typeof data.cbData !== 'undefined' && typeof data.cbData.data !== 'undefined') { + if (typeof data.cashbackToken !== 'undefined') { + cashbackToken = data.cashbackToken // general cashback token of ask!!!! + } + data = data.cbData.data + if (typeof data !== 'undefined' && typeof data.cashbackToken !== 'undefined') { + customToken = data.cashbackToken + } + } else { + this._canUpdate = true + return + } + } catch (e) { + if (config.debug.appErrors) { + console.log('UpdateCashBackDataDaemon error ' + e.message) + } + await cashBackActions.updateAll({error : { + title: e.message, + time: new Date().getTime() + }}) + this._canUpdate = true + return + } + + if (!asked) { + if (config.debug.appErrors) { + console.log(new Date().toISOString() + ' UpdateCashBackDataDaemon loaded proxy') + } + } + + try { + Log.daemon('UpdateCashBackDataDaemon result ', data) + data.time = new Date().getTime() + data.cashbackToken = cashbackToken + data.customToken = typeof data.customToken !== 'undefined' && data.customToken ? data.customToken : customToken + await CashBackUtils.setCashBackDataFromApi(data) + } catch (e) { + if (config.debug.appErrors) { + console.log('UpdateCashBackDataDaemon result error ' + e.message ) + } + this._canUpdate = true + Log.err('UpdateCashBackDataDaemon result error ' + e.message) + } + this._canUpdate = true + } + +} + +export default new UpdateCashBackDataDaemon diff --git a/src/daemons/back/UpdateCurrencyRateDaemon.js b/src/daemons/back/UpdateCurrencyRateDaemon.js new file mode 100644 index 000000000..841f3e168 --- /dev/null +++ b/src/daemons/back/UpdateCurrencyRateDaemon.js @@ -0,0 +1,122 @@ +/** + * @version 0.41 + */ + +import Log from '@app/services/Log/Log' +import ApiRates from '@app/services/Api/ApiRates' + +import currencyActions from '@app/appstores/Stores/Currency/CurrencyActions' +import currencyDS from '@app/appstores/DataSource/Currency/Currency' +import store from '@app/store' +import UpdateAccountListDaemon from '@app/daemons/view/UpdateAccountListDaemon' + +const CACHE_SAVED = {} + +class UpdateCurrencyRateDaemon { + + /** + * @return {Promise} + */ + updateCurrencyRate = async (params, dataUpdate = false) => { + Log.daemon('UpdateCurrencyRateDaemon started ' + params.source) + + const res = await ApiRates.getRates(params, dataUpdate) + if (!res || typeof res.cryptoCurrencies === 'undefined') { + return [] + } + + const currencies = store.getState().currencyStore.cryptoCurrencies + if (typeof currencies === 'undefined' || !currencies || currencies.length === 0) { + Log.daemon('UpdateCurrencyRateDaemon warning - no currencies') + return false + } + + const indexed = {} + let row = false + try { + for (row of res.cryptoCurrencies) { + if (typeof row === 'undefined' || !row) continue + if (typeof row.tokenAddress !== 'undefined' && row.tokenAddress) { + indexed['token_' + row.tokenAddress.toUpperCase()] = row + } + if (typeof row.currencyCode !== 'undefined') { + indexed[row.currencyCode] = row + } + } + } catch (e) { + e.message += ' in indexing cryptoCurrencies ' + (row ? JSON.stringify(row) : ' no row') + throw e + } + + const scanned = res.scanned + const updatedCurrencies = [] + try { + for (const dbCurrency of currencies) { + let currency = false + + if (typeof dbCurrency.tokenAddress !== 'undefined' && dbCurrency.tokenAddress) { + if (typeof indexed['token_' + dbCurrency.tokenAddress.toUpperCase()] !== 'undefined') { + currency = indexed['token_' + dbCurrency.tokenAddress.toUpperCase()] + } + } + + if (!currency) { + if (dbCurrency.currencyCode.indexOf('CUSTOM_') === 0) { + if (typeof dbCurrency.tokenName !== 'undefined' && typeof indexed[dbCurrency.tokenName] !== 'undefined') { + currency = indexed[dbCurrency.tokenName] + } + } else { + if (typeof dbCurrency.ratesCurrencyCode !== 'undefined' && dbCurrency.ratesCurrencyCode) { + if (typeof indexed[dbCurrency.ratesCurrencyCode] !== 'undefined') { + currency = indexed[dbCurrency.ratesCurrencyCode] + } + } else { + if (typeof indexed[dbCurrency.currencyCode] !== 'undefined') { + currency = indexed[dbCurrency.currencyCode] + } + } + } + } + + if (!currency) { + Log.daemon('UpdateCurrencyRateDaemon warning - no currency rate for ' + dbCurrency.currencyCode) + continue + } + if (typeof CACHE_SAVED[dbCurrency.currencyCode] !== 'undefined' && CACHE_SAVED[dbCurrency.currencyCode] === currency.currencyRateScanTime) { + continue + } + const updateObj = { + currencyRateUsd: currency.currencyRateUsd, + currencyRateJson: currency.currencyRateJson, + priceProvider: currency.priceProvider, + priceChangePercentage24h: currency.priceChangePercentage24h, + priceLastUpdated: currency.priceLastUpdated, + currencyRateScanTime: scanned + } + updatedCurrencies.push({ + ...updateObj, + currencyCode: dbCurrency.currencyCode + }) + + CACHE_SAVED[dbCurrency.currencyCode] = updateObj.currencyRateScanTime + await currencyDS.updateCurrency({ updateObj, key: { currencyCode: dbCurrency.currencyCode } }) + } + } catch (e) { + e.message += ' in res.cryptoCurrencies' + throw e + } + + try { + if (updatedCurrencies.length) { + await currencyActions.updateCryptoCurrencies(updatedCurrencies) + await UpdateAccountListDaemon.updateAccountListDaemon({force: true, source: 'UpdateCurrencyRateDaemon'}) + } + } catch (e) { + e.message += 'in setCryptoCurrencies' + throw e + } + } + +} + +export default new UpdateCurrencyRateDaemon diff --git a/src/daemons/back/UpdateOneByOneDaemon.js b/src/daemons/back/UpdateOneByOneDaemon.js new file mode 100644 index 000000000..81e1eedf8 --- /dev/null +++ b/src/daemons/back/UpdateOneByOneDaemon.js @@ -0,0 +1,140 @@ +/** + * @version 0.11 + */ +import Update from '../Update' + +import UpdateAccountBalanceAndTransactions from '@app/daemons/back/UpdateAccountBalanceAndTransactions' +import UpdateAccountBalanceAndTransactionsHD from '@app/daemons/back/UpdateAccountBalanceAndTransactionsHD' +import UpdateAccountPendingTransactions from '@app/daemons/back/UpdateAccountPendingTransactions' +import UpdateAppNewsDaemon from '@app/daemons/back/UpdateAppNewsDaemon' + +import Log from '@app/services/Log/Log' +import settingsActions from '@app/appstores/Stores/Settings/SettingsActions' +import Database from '@app/appstores/DataSource/Database' + +const STEPS_ORDER = [ + 'UPDATE_PROXIED', + 'UPDATE_ACCOUNT_PENDING_DAEMON', + 'UPDATE_ACCOUNT_BALANCES_DAEMON', + 'UPDATE_ACCOUNT_PENDING_DAEMON', + 'UPDATE_ACCOUNT_BALANCES_HD_DAEMON', + 'UPDATE_PROXIED', + 'UPDATE_ACCOUNT_PENDING_DAEMON', + 'UPDATE_ACCOUNT_BALANCES_DAEMON', + 'UPDATE_ACCOUNT_PENDING_DAEMON', + 'UPDATE_ACCOUNT_BALANCES_DAEMON', +] + +let CACHE_PAUSE = 0 + +const CACHE_TIMES = {} + +let CACHE_STOPPED = false + +const CACHE_VALID_TIME = { + 'PAUSE' : 60000, // 60 seconds + 'UPDATE_ACCOUNT_PENDING_DAEMON' : 30000, // 30 seconds + 'UPDATE_PROXIED' : 120000, // 120 seconds + 'UPDATE_ACCOUNT_BALANCES_DAEMON': 10000, // 10 sec + 'UPDATE_ACCOUNT_BALANCES_DAEMON_ALL': 100000, // 100 sec + 'UPDATE_ACCOUNT_BALANCES_HD_DAEMON': 30000 // 30 sec +} + +class UpdateOneByOneDaemon extends Update { + + _currentStep = 0 + + constructor(props) { + super(props) + this.updateFunction = this.updateOneByOneDaemon + this._canUpdate = true + } + + init = async () => { + // nothing + } + + stop = () => { + CACHE_STOPPED = true + } + + unstop = () => { + CACHE_STOPPED = false + } + + pause = () => { + CACHE_PAUSE = new Date().getTime() + } + + unpause = () => { + CACHE_PAUSE = 0 + } + + updateOneByOneDaemon = async (params, level = 0) => { + + await Database.checkVersion() + + if (CACHE_STOPPED) { + return false + } + + const tmpAuthHash = await settingsActions.getSelectedWallet('updateOneByOneDaemon') + if (!tmpAuthHash) { + return false + } + + const source = params.source || 'FRONT' + if (!this._canUpdate) { + return false + } + const now = new Date().getTime() + if (CACHE_PAUSE > 0 && now - CACHE_PAUSE < CACHE_VALID_TIME.PAUSE) { + return false + } + this._canUpdate = false + + try { + this._currentStep++ + if (this._currentStep >= STEPS_ORDER.length) { + this._currentStep = 0 + } + const step = STEPS_ORDER[this._currentStep] + if (typeof CACHE_TIMES[step] !== 'undefined' && now - CACHE_TIMES[step] < CACHE_VALID_TIME[step]) { + // console.log(new Date().toISOString() + ' ' + this._currentStep + ' skipped ' + step) + this._canUpdate = true + if (level < 10) { + await this.updateOneByOneDaemon(params, level + 1) + } + return + } + // console.log(new Date().toISOString() + ' ' + this._currentStep + ' step in ' + step) + CACHE_TIMES[step] = now + switch (step) { + case 'UPDATE_PROXIED' : + await UpdateAppNewsDaemon.updateAppNewsDaemon({source}) + break + case 'UPDATE_ACCOUNT_PENDING_DAEMON': + await UpdateAccountPendingTransactions.updateAccountPendingTransactions({source}) + break + case 'UPDATE_ACCOUNT_BALANCES_DAEMON': + await UpdateAccountBalanceAndTransactions.updateAccountBalanceAndTransactions({ source }) + break + case 'UPDATE_ACCOUNT_BALANCES_DAEMON_ALL': + await UpdateAccountBalanceAndTransactions.updateAccountBalanceAndTransactions({ source, allWallets : true }) + break + case 'UPDATE_ACCOUNT_BALANCES_HD_DAEMON': + await UpdateAccountBalanceAndTransactionsHD.updateAccountBalanceAndTransactionsHD({ source }) + break + default: + // do nothing + break + } + } catch (e) { + Log.errDaemon('UpdateOneByOne error ' + e.message) + } + + this._canUpdate = true + } +} + +export default new UpdateOneByOneDaemon() diff --git a/src/daemons/back/UpdateTradeOrdersDaemon.js b/src/daemons/back/UpdateTradeOrdersDaemon.js new file mode 100644 index 000000000..19a677f83 --- /dev/null +++ b/src/daemons/back/UpdateTradeOrdersDaemon.js @@ -0,0 +1,413 @@ +/** + * @version 0.30 + */ + +import Database from '@app/appstores/DataSource/Database' + +import Log from '@app/services/Log/Log' +import ApiV3 from '@app/services/Api/ApiV3' +import ApiProxy from '@app/services/Api/ApiProxy' + +import config from '@app/config/config' +import store from '@app/store' +import NavStore from '@app/components/navigation/NavStore' +import BlocksoftPrettyNumbers from '@crypto/common/BlocksoftPrettyNumbers' +import BlocksoftUtils from '@crypto/common/BlocksoftUtils' +import settingsActions from '@app/appstores/Stores/Settings/SettingsActions' + +const { dispatch } = store + +const CACHE_VALID_TIME = 20000 // 2 minute +let CACHE_LAST_TIME = false +let TRY_COUNTER = 0 + +let CACHE_ORDERS_HASH = '' +const CACHE_ONE_ORDER = {} + +const LIMIT_FOR_CURRENCY = 20 + +class UpdateTradeOrdersDaemon { + + getSavedOrdersHash = () => { + return CACHE_ORDERS_HASH + } + + fromApi = async (walletHash, orderHash) => { + + const now = new Date().getTime() + if (typeof CACHE_ONE_ORDER[orderHash] !== 'undefined') { + const diff = now - CACHE_ONE_ORDER[orderHash].now + if (diff < CACHE_VALID_TIME) { + Log.daemon('UpdateTradeOrders.fromApi ' + orderHash + ' skipped by diff ' + diff) + return CACHE_ONE_ORDER[orderHash].one + } + } + + Log.daemon('UpdateTradeOrders.fromApi ' + orderHash + ' loading start') + + try { + + const tmpTradeOrdersV3 = await ApiV3.getExchangeOrders(walletHash) + if (typeof tmpTradeOrdersV3 !== 'undefined' && tmpTradeOrdersV3 && tmpTradeOrdersV3.length > 0) { + for (const one of tmpTradeOrdersV3) { + if (one.orderHash === orderHash) { + CACHE_ONE_ORDER[orderHash] = { one, now } + return one + } + } + } + + } catch (e) { + if (config.debug.appErrors) { + console.log('UpdateTradeOrdersDaemon.fromApi error ' + e.message + ' tmpOneOrder') + } + throw new Error(e.message + ' tmpOneOrder') + } + return false + } + + fromDB = async () => { + // do nothing + } + + removeId = async (removeId) => { + Log.daemon('UpdateTradeOrders removeId ' + removeId) + try { + const nowAt = new Date().toISOString() + const found = await Database.query(` SELECT id, hidden_at FROM transactions WHERE bse_order_id = '${removeId}' `, true) + if (found && found.array && found.array.length > 0) { + const row = found.array[0] + if (!row.hidden_at || row.hidden_at === 'null' || row.hidden_at === '') { + const sql = ` UPDATE transactions SET hidden_at='${nowAt}' WHERE bse_order_id = '${removeId}' ` + await Database.query(sql, true) + } + } + + const account = store.getState().mainStore.selectedAccount + const { transactionsToView } = store.getState().mainStore.selectedAccountTransactions + if (account) { + let found = false + const newTransactions = [] + if (transactionsToView) { + for (const transaction of transactionsToView) { + if (transaction.bseOrderData && typeof transaction.bseOrderData.orderId !== 'undefined' && transaction.bseOrderData.orderId == removeId) { + found = true + } else { + newTransactions.push(transaction) + } + } + } + if (found) { + dispatch({ + type: 'SET_SELECTED_ACCOUNT_TRANSACTIONS', + selectedAccountTransactions: { + transactionsToView : newTransactions, + transactionsLoaded : new Date().getTime() + } + }) + } + } + NavStore.goBack() + } catch (e) { + Log.errDaemon('UpdateTradeOrders removeId ' + removeId + ' error ' + e.message) + } + } + + /** + * @param params.force + * @param params.source + * @returns {Promise} + */ + updateTradeOrdersDaemon = async (params, dataUpdate = false) => { + + if (typeof params.source !== 'undefined' && params.source === 'ACCOUNT_OPEN' && CACHE_LAST_TIME) { + const now = new Date().getTime() + const diff = now - CACHE_LAST_TIME + if (diff < CACHE_VALID_TIME) { + Log.daemon('UpdateTradeOrders skipped by diff ' + diff) + return false + } + } + + Log.daemon('UpdateTradeOrders called ' + JSON.stringify(params)) + + const walletHash = await settingsActions.getSelectedWallet('UpdateTradeOrdersDaemon') + if (!walletHash) { + return false + } + + const nowAt = new Date().toISOString() + try { + + let res = false + let asked = false + if (!dataUpdate) { + if (config.debug.appErrors) { + console.log(new Date().toISOString() + ' UpdateTradeOrdersDaemon loading new') + } + asked = true + res = await ApiProxy.getAll({ source: 'UpdateTradeOrdersDaemon.updateTradeOrders' }) + if (config.debug.appErrors) { + console.log(new Date().toISOString() + ' UpdateTradeOrdersDaemon loaded new finished') + } + } else { + res = dataUpdate + } + + const tmpTradeOrders = typeof res !== 'undefined' && typeof res.cbOrders !== 'undefined' ? res.cbOrders : false + + + /* + sometimes transaction hash should be unified with order status + if (typeof res.cbOrdersHash !== 'undefined' && (res.cbOrdersHash === CACHE_ORDERS_HASH || typeof params.removeId === 'undefined')) { + return false + } + */ + + try { + + if (typeof tmpTradeOrders === 'undefined' || !tmpTradeOrders || !tmpTradeOrders.length) { + return false + } + + if (!asked) { + if (config.debug.appErrors) { + console.log(new Date().toISOString() + ' UpdateTradeOrdersDaemon loaded proxy') + } + } + + let item + + const index = {} + let total = 0 + + for (item of tmpTradeOrders) { + if (total > 100) { + break + } + if (typeof item.orderId === 'undefined' || !item.orderId || item.orderId === '' || item.orderId === 'undefined') { + continue + } + + if (typeof item.uiApiVersion === 'undefined') { + item.uiApiVersion = 'v2' + } + + try { + const tmps = [] + if (item.exchangeWayType !== 'BUY') { + tmps.push({ + currencyCode: item.requestedInAmount.currencyCode || false, + addressAmount: item.requestedInAmount.amount || 0, + addressTo: typeof item.depositAddress !== 'undefined' ? item.depositAddress : false, + updateHash: item.inTxHash || false, + suffix: 'in' + }) + } + if (item.exchangeWayType !== 'SELL') { + tmps.push({ + currencyCode: item.requestedOutAmount.currencyCode || false, + addressAmount: item.requestedOutAmount.amount || 0, + addressTo: typeof item.outDestination !== 'undefined' && item.outDestination && item.outDestination.toString().indexOf('*') === -1 ? item.outDestination : false, + updateHash: item.outTxHash || false, + suffix: 'out' + }) + } + + let currencyCode = 'NONE' + let someIsUpdatedAllWillUpdate = false + + for (const tmp of tmps) { + if (!tmp.currencyCode) continue + if (typeof index[tmp.currencyCode] === 'undefined') { + index[tmp.currencyCode] = 1 + } else { + index[tmp.currencyCode]++ + } + if (index[tmp.currencyCode] < LIMIT_FOR_CURRENCY) { + someIsUpdatedAllWillUpdate = true + } + } + + if (!someIsUpdatedAllWillUpdate) continue + + + const savedToTx = {} + for (const tmp of tmps) { + if (!tmp.currencyCode) continue + currencyCode = tmp.currencyCode + let sql + let sqlUpdateDir = '' + let noHash = true + if (tmp.updateHash && tmp.updateHash !== '' && tmp.updateHash !== 'null') { + noHash = false + sqlUpdateDir = `bse_order_id_${tmp.suffix}='${item.orderId}', bse_order_id='${item.orderId}', ` + sql = ` + SELECT id, bse_order_data, transaction_hash, transactions_scan_log, hidden_at FROM transactions + WHERE (transaction_hash='${tmp.updateHash}' OR bse_order_id='${item.orderId}') + AND currency_code='${tmp.currencyCode}' + ` + + } else { + sql = ` + SELECT id, bse_order_data, transaction_hash, transactions_scan_log, hidden_at FROM transactions + WHERE bse_order_id='${item.orderId}' AND currency_code='${tmp.currencyCode}' + ` + } + + let found = await Database.query(sql, true) + + if (!found || !found.array || found.array.length === 0 || found.array[0].transaction_hash === '') { + if (typeof item.searchHashByAmount !== 'undefined' && item.searchHashByAmount && noHash) { + Log.daemon('UpdateTradeOrders called inner search ' + JSON.stringify(item)) + let sql2 = '' + const rawAmount = BlocksoftPrettyNumbers.setCurrencyCode(tmp.currencyCode).makeUnPretty(tmp.addressAmount) + if (typeof tmp.addressTo !== 'undefined' && tmp.addressTo) { + sql2 = ` + SELECT id, bse_order_data, transaction_hash, transactions_scan_log, hidden_at, created_at, address_amount FROM transactions + WHERE transaction_hash != '' AND (bse_order_id IS NULL OR bse_order_id='') AND currency_code='${tmp.currencyCode}' + AND LOWER(address_to)='${tmp.addressTo.toLowerCase()}' + ` + } + if (sql2) { + const found2 = await Database.query(sql2, true) + if (found2 && found2.array) { + for (const found22 of found2.array) { + if (Math.abs(BlocksoftUtils.diff(found22.address_amount, rawAmount).toString() * 1) > 10) continue + const createdAt = new Date(found22.created_at).getTime() + if (Math.abs(createdAt - item.createdAt) > 12000000) continue //60 * 1000 * 200 minutes + if (!found || !found.array) { + found = { array: [] } + } + sqlUpdateDir = `bse_order_id='${item.orderId}', ` + found.array.push(found22) + } + } + } + } + } + + if (found && found.array && found.array.length > 0) { + + savedToTx[tmp.currencyCode] = true + + let id = 0 + let id2 = 0 + let id3 = 0 + const toRemove = [] + if (found.array.length > 1) { + for (const row of found.array) { + if (row.transaction_hash && row.transaction_hash !== '') { + id = row.id + } else if (row.hidden_at && row.hidden_at !== 'null' && row.hidden_at !== '') { + id2 = row.id + } else { + id3 = row.id + } + } + if (id === 0) { + id = id2 + } + if (id === 0) { + id = id3 + } + for (const row of found.array) { + if (row.id !== id) { + toRemove.push(row.id) + } + } + + } else { + id = found.array[0].id + } + + + for (const row of found.array) { + if (id !== row.id) continue + if (row.hidden_at && row.hidden_at !== '' && row.hidden_at !== 'null') { + savedToTx[tmp.currencyCode] = 'id : ' + id + continue + } + const escaped = Database.escapeString(JSON.stringify(item)) + + if (!(row.bse_order_data === escaped)) { + + let scanLog = row.transactions_scan_log + if (scanLog.indexOf(` UPDATED ORDER ${item.orderId} `) === -1) { + scanLog = ` ${nowAt} UPDATED ORDER ${item.orderId} / ${scanLog}` + if (scanLog.length > 1000) { + scanLog = scanLog.substr(0, 1000) + } + sqlUpdateDir += `transactions_scan_log = '${scanLog}', ` + } + + const sql2 = ` UPDATE transactions SET ${sqlUpdateDir} bse_order_data='${escaped}' WHERE id=${row.id} ` + await Database.query(sql2, true) + } + } + if (toRemove.length > 0) { + const sql3 = ` DELETE FROM transactions WHERE id IN (${toRemove.join(',')}) AND id != ${id} ` + await Database.query(sql3, true) + } + } + } + + + for (const tmp of tmps) { + if (!tmp.currencyCode || tmp.addressAmount === 0) continue + if (typeof savedToTx[tmp.currencyCode] !== 'undefined') continue + if (tmp.suffix === 'out' && !tmp.updateHash) continue + + currencyCode = tmp.currencyCode + + const createdAt = new Date(item.createdAt).toISOString() + const sql = ` + INSERT INTO transactions (currency_code, wallet_hash, account_id, transaction_hash, transaction_hash_basic, transaction_status, transactions_scan_log, created_at, + address_amount, address_to, bse_order_id, bse_order_id_${tmp.suffix}, bse_order_data) + VALUES ('${currencyCode}', '${walletHash}', '0', '', '${tmp.updateHash ? tmp.updateHash : ''}', '', 'FROM ORDER ${createdAt} ${item.orderId}', '${createdAt}', + '${tmp.addressAmount}', '', '${item.orderId}', '${item.orderId}', '${Database.escapeString(JSON.stringify(item))}') + ` + await Database.query(sql, true) + savedToTx[tmp.currencyCode] = 1 + } + + total++ + + } catch (e) { + if (config.debug.appErrors) { + console.log('UpdateTradeOrders one order error ' + e.message, JSON.parse(JSON.stringify(item))) + } + Log.err('UpdateTradeOrders one order error ' + e.message, item) + } + } + + TRY_COUNTER = 0 + CACHE_ORDERS_HASH = res.cbOrdersHash + + + } catch (e) { + if (config.debug.appErrors) { + console.log('UpdateTradeOrders all orders error ' + e.message, e, JSON.parse(JSON.stringify(tmpTradeOrders))) + } + throw new Error('UpdateTradeOrders all orders error ' + e.message + ' ' + JSON.stringify(tmpTradeOrders)) + } + + } catch (e) { + if (config.debug.appErrors) { + console.log('UpdateTradeOrders get orders error ' + e.message, e) + } + if (Log.isNetworkError(e.message) && TRY_COUNTER < 10) { + TRY_COUNTER++ + Log.daemon('UpdateTradeOrders network try ' + TRY_COUNTER + ' ' + e.message) + } else if (e.message === 'No cashbackToken') { + Log.daemon('UpdateTradeOrders notice ' + e.message) + } else { + Log.errDaemon('UpdateTradeOrders error ' + e.message) + } + } + CACHE_LAST_TIME = new Date().getTime() + return true + } +} + +export default new UpdateTradeOrdersDaemon diff --git a/src/daemons/back/UpdateWalletsDaemon.js b/src/daemons/back/UpdateWalletsDaemon.js new file mode 100644 index 000000000..b08c7ce8c --- /dev/null +++ b/src/daemons/back/UpdateWalletsDaemon.js @@ -0,0 +1,130 @@ +/** + * @version 0.41 + * for data sync from proxy + */ +import Log from '@app/services/Log/Log' + +import config from '@app/config/config' + +import ApiProxy from '@app/services/Api/ApiProxy' +import walletDS from '@app/appstores/DataSource/Wallet/Wallet' +import cryptoWalletsDS from '@app/appstores/DataSource/CryptoWallets/CryptoWallets' +import settingsActions from '@app/appstores/Stores/Settings/SettingsActions' + +class UpdateWalletsDaemon { + + _canUpdate = true + + /** + * @return {Promise} + */ + updateWalletsDaemon = async (params = {}, dataUpdate = false) => { + if (typeof params !== 'undefined' && params && typeof params.force === 'undefined' || !params.force) { + if (!this._canUpdate && !dataUpdate) return false + } + + this._canUpdate = false + const res = await this._updateWalletsDaemon(params, dataUpdate) + this._canUpdate = true + + return res + } + + _updateWalletsDaemon = async (params, dataUpdate = false) => { + + Log.daemon('UpdateWalletsDaemon called') + + let asked = false + if (!dataUpdate) { + const authHash = await settingsActions.getSelectedWallet('_updateWalletsDaemon') + if (!authHash) { + Log.daemon('UpdateWalletsDaemon skipped as no auth') + return false + } + if (config.debug.appErrors) { + console.log(new Date().toISOString() + ' UpdateWalletsDaemon loading new') + } + asked = true + try { + dataUpdate = await ApiProxy.getAll({ ...params, source: 'UpdateWalletsDaemon.updateWallets' }) + } catch (e) { + if (config.debug.appErrors) { + console.log('UpdateWalletsDaemon error ' + e.message) + } + return false + } + } + + if (!dataUpdate) { + return false + } + + if (typeof dataUpdate.forWalletsAll !== 'undefined' && dataUpdate.forWalletsAll) { + if (!asked) { + if (config.debug.appErrors) { + console.log(new Date().toISOString() + ' UpdateWalletsDaemon loaded proxy forWalletsAll') + } + } + try { + + const saved = await walletDS.getWallets() + const walletsSaved = {} + if (saved) { + for (const row of saved) { + walletsSaved[row.walletHash] = row + } + } + + for (const walletHash in dataUpdate.forWalletsAll) { + const dataOne = dataUpdate.forWalletsAll[walletHash] + if (!dataOne) continue + if (typeof walletsSaved[walletHash] === 'undefined') { + throw new Error('Saved wallet is not correct ' + JSON.stringify(dataOne)) + } + + let mapping = { + walletAllowReplaceByFee: dataOne.wallet_allow_replace_by_fee, + walletIsHd: dataOne.wallet_is_hd, + walletIsHideTransactionForFee: dataOne.wallet_is_hide_transaction_for_free, + walletName: dataOne.wallet_name, + walletToSendStatus: dataOne.wallet_to_send_status, + walletUseLegacy: dataOne.wallet_use_legacy, + walletUseUnconfirmed: dataOne.wallet_use_unconfirmed, + walletIsCreatedHere: dataOne.wallet_is_created_here + } + mapping = walletDS._prepWallet(mapping) + + if (walletsSaved[walletHash].walletToSendStatus && walletsSaved[walletHash].walletToSendStatus * 1 >= dataOne.wallet_to_send_status * 1) { + // skip + } else { + const updateObj = {} + for (const key in mapping) { + if (walletsSaved[walletHash][key] !== mapping[key]) { + updateObj[key] = mapping[key] + } + } + updateObj.walletToSendStatus = dataOne.wallet_to_send_status ? dataOne.wallet_to_send_status*1 : 0 + const dataForSave = { + key: { + walletHash + }, + updateObj + } + await walletDS.updateWallet(dataForSave) + } + } + } catch (e) { + if (config.debug.appErrors) { + console.log('UpdateWalletsDaemon save error ' + e.message) + } + Log.errDaemon('UpdateWalletsDaemon save error ' + e.message) + return false + } + } + + return false + } + +} + +export default new UpdateWalletsDaemon diff --git a/src/daemons/back/apptasks/AppTasksDiscoverBalancesHidden.js b/src/daemons/back/apptasks/AppTasksDiscoverBalancesHidden.js new file mode 100644 index 000000000..cbcb4aa81 --- /dev/null +++ b/src/daemons/back/apptasks/AppTasksDiscoverBalancesHidden.js @@ -0,0 +1,70 @@ +/** + * @version 0.11 + */ +import Database from '@app/appstores/DataSource/Database'; +import BlocksoftBalances from '../../../../crypto/actions/BlocksoftBalances/BlocksoftBalances' + +import appNewsDS from '../../../appstores/DataSource/AppNews/AppNews' +import Log from '../../../services/Log/Log' + +class AppTasksDiscoverBalancesHidden { + /** + * + * @param {string} appTask.walletHash + * @param {string} appTask.currencyCode + * @returns {Promise} + */ + run = async (appTask) => { + const sql =` + SELECT + currency_code AS currencyCode, + address, + account_json AS accountJson, + wallet_hash AS walletHash + FROM account + WHERE currency_code='${appTask.currencyCode}' + LIMIT 1` + const tmp = await Database.query(sql) + if (!tmp || typeof tmp.array === 'undefined' || !tmp.array) return 'no account' + const account = tmp.array[0] + let addressToScan = account.address + if (account.accountJson) { + try { + account.accountJson = JSON.parse(account.accountJson) + if (typeof account.accountJson.addressHex !== 'undefined') { + addressToScan = account.accountJson.addressHex + Log.daemon('AppTasksDiscoverBalancesHidden changing address ' + appTask.currencyCode + ' ' + account.address + ' => ' + addressToScan) + } + } catch (e) { + // do nothing + } + } + let newBalance + try { + newBalance = await (BlocksoftBalances.setCurrencyCode(appTask.currencyCode).setAddress(addressToScan).setAdditional(account.accountJson).setWalletHash(account.walletHash)).getBalance() + Log.daemon('AppTasksDiscoverBalancesHidden loaded address ' + appTask.currencyCode + ' ' + addressToScan, newBalance) + } catch (e) { + e.message += ' scanning ' + appTask.currencyCode + ' address ' + addressToScan + throw e + } + if (!newBalance) { + return addressToScan + ' no balance' + } + if (newBalance.balance*1 === 0 && newBalance.unconfirmed*1 === 0) { + return addressToScan + ' zero balance ' + JSON.stringify(newBalance) + } + newBalance.address = addressToScan + + await appNewsDS.saveAppNews({ + walletHash : appTask.walletHash, + currencyCode : appTask.currencyCode, + newsGroup : 'DAEMON', + newsName : 'DAEMON_HAS_FOUND_BALANCE', + newsJson : newBalance + }) + + return addressToScan + ' not zero balance ' + JSON.stringify(newBalance) + + } +} +export default new AppTasksDiscoverBalancesHidden() diff --git a/src/daemons/back/apptasks/AppTasksDiscoverBalancesNotAdded.js b/src/daemons/back/apptasks/AppTasksDiscoverBalancesNotAdded.js new file mode 100644 index 000000000..4c02ddeb1 --- /dev/null +++ b/src/daemons/back/apptasks/AppTasksDiscoverBalancesNotAdded.js @@ -0,0 +1,64 @@ +/** + * @version 0.11 + */ +import BlocksoftBalances from '../../../../crypto/actions/BlocksoftBalances/BlocksoftBalances' + +import appNewsDS from '../../../appstores/DataSource/AppNews/AppNews' +import Log from '../../../services/Log/Log' +import BlocksoftKeys from '../../../../crypto/actions/BlocksoftKeys/BlocksoftKeys' +import BlocksoftKeysStorage from '../../../../crypto/actions/BlocksoftKeysStorage/BlocksoftKeysStorage' + +class AppTasksDiscoverBalancesNotAdded { + /** + * + * @param {string} appTask.walletHash + * @param {string} appTask.currencyCode + * @returns {Promise} + */ + run = async (appTask) => { + + const mnemonic = await BlocksoftKeysStorage.getWalletMnemonic(appTask.walletHash, 'AppTasksDiscoverBalancesNotAdded.run') + const accounts = await BlocksoftKeys.discoverAddresses({ mnemonic, fullTree: false, fromIndex: 0, toIndex: 1, currencyCode: appTask.currencyCode }, 'APP_TASK') + if (!accounts) return 'no address' + const account = accounts[appTask.currencyCode][0] + let addressToScan = account.address + if (account.accountJson) { + try { + if (typeof account.addedData.addressHex !== 'undefined') { + addressToScan = account.addedData.addressHex + Log.daemon('AppTasksDiscoverBalancesNotAdded changing address ' + appTask.currencyCode + ' ' + account.address + ' => ' + addressToScan) + } + } catch (e) { + // do nothing + } + } + let newBalance + try { + newBalance = await (BlocksoftBalances.setCurrencyCode(appTask.currencyCode).setAddress(addressToScan).setAdditional(account.addedData).setWalletHash(appTask.walletHash)).getBalance() + Log.daemon('AppTasksDiscoverBalanceNotAdded loaded address ' + appTask.currencyCode + ' ' + addressToScan, newBalance) + } catch (e) { + e.message += ' scanning ' + appTask.currencyCode + ' address ' + addressToScan + throw e + } + if (!newBalance) { + return addressToScan + ' no balance' + } + if (newBalance.balance*1 === 0 && newBalance.unconfirmed*1 === 0) { + return addressToScan + ' zero balance ' + JSON.stringify(newBalance) + } + newBalance.address = addressToScan + + await appNewsDS.saveAppNews({ + walletHash: appTask.walletHash, + currencyCode: appTask.currencyCode, + newsGroup: 'DAEMON', + newsName: 'DAEMON_HAS_FOUND_BALANCE_NOT_ADDED', + newsJson: newBalance + }) + + return addressToScan + ' not zero balance ' + JSON.stringify(newBalance) + + } +} + +export default new AppTasksDiscoverBalancesNotAdded() diff --git a/src/daemons/back/apptasks/AppTasksDiscoverHD.js b/src/daemons/back/apptasks/AppTasksDiscoverHD.js new file mode 100644 index 000000000..e2f2b5718 --- /dev/null +++ b/src/daemons/back/apptasks/AppTasksDiscoverHD.js @@ -0,0 +1,35 @@ +/** + * @version 0.11 + */ +import appNewsDS from '../../../appstores/DataSource/AppNews/AppNews' + +import App from '../../../appstores/Actions/App/App' +import WalletHDActions from '../../../appstores/Actions/WalletHDActions' + +class AppTasksDiscoverHD { + /** + * + * @param {string} appTask.walletHash + * @param {string} appTask.currencyCode + * @returns {Promise} + */ + run = async (appTask) => { + let derivations = false + try { + derivations = await WalletHDActions.hdFromTrezor({walletHash : appTask.walletHash, force : false, currencyCode : appTask.currencyCode}, 'APP_TASK') + } catch (e) { + return e.message + } + await appNewsDS.saveAppNews({ + walletHash : appTask.walletHash, + currencyCode : appTask.currencyCode, + newsGroup : 'DAEMON', + newsName : 'DAEMON_HAS_FOUND_HD' + }) + + await App.refreshWalletsStore({firstTimeCall : false, source : 'AppTasks discoverHD'}) + return ' there are derivations ' + JSON.stringify(derivations) + + } +} +export default new AppTasksDiscoverHD() diff --git a/src/daemons/back/apputils/AccountTransactionsRecheck.js b/src/daemons/back/apputils/AccountTransactionsRecheck.js new file mode 100644 index 000000000..2a641bad6 --- /dev/null +++ b/src/daemons/back/apputils/AccountTransactionsRecheck.js @@ -0,0 +1,420 @@ +/** + * @version 0.11 + */ +import Log from '@app/services/Log/Log' +import transactionDS from '@app/appstores/DataSource/Transaction/Transaction' +import appNewsDS from '@app/appstores/DataSource/AppNews/AppNews' +import { BlocksoftTransfer } from '@crypto/actions/BlocksoftTransfer/BlocksoftTransfer' +import settingsActions from '@app/appstores/Stores/Settings/SettingsActions' +import config from '@app/config/config' + +const CACHE_TO_REMOVE = {} // couldnt remove on first scan - as BTC is scanned in few accounts + +export default async function AccountTransactionsRecheck(newTransactions, account, source) { + + const transactionUpdateObj = { + transactionsScanTime: Math.round(new Date().getTime() / 1000) + } + + if (!newTransactions || typeof newTransactions === 'undefined') { + transactionUpdateObj.transactionsScanLog = 'not txs ' + return transactionUpdateObj + } + + if (typeof CACHE_TO_REMOVE[account.currencyCode] === 'undefined') { + CACHE_TO_REMOVE[account.currencyCode] = {} + } + + const dbTransactions = {} + const toRemove = [] + const dbNonces = {} + + try { + const tmps = await transactionDS.getTransactions({ + currencyCode: account.currencyCode, + walletHash: account.walletHash, + noOrder: true, + noOld: true + }, 'AccountTransactionsRecheck dbTransactions ' + source) + if (tmps && tmps.length > 0) { + let tmp + for (tmp of tmps) { + if (tmp.addressFrom === '' && typeof tmp.transactionJson !== 'undefined' && tmp.transactionJson && typeof tmp.transactionJson.nonce !== 'undefined' && typeof tmp.transactionJson.delegatedNonce === 'undefined') { + const key = tmp.addressFromBasic + '_' + tmp.transactionJson.nonce + if (typeof dbNonces[key] === 'undefined') { + dbNonces[key] = [] + } + dbNonces[key].push(tmp) + } + if (typeof dbTransactions[tmp.transactionHash] !== 'undefined') { + // rmv double + Log.daemon('AccountTransactionsRecheck dbTransactions will remove ' + tmp.id) + toRemove.push(tmp.id) + continue + } else { + dbTransactions[tmp.transactionHash] = tmp + } + if (typeof tmp.transactionsOtherHashes !== 'undefined' && tmp.transactionsOtherHashes) { + const tmp2 = tmp.transactionsOtherHashes.split(',') + if (tmp2) { + let part + for (part of tmp2) { + dbTransactions[part] = dbTransactions[tmp.transactionHash] + } + } + } + } + } + } catch (e) { + Log.errDaemon('AccountTransactionsRecheck dbTransactions something wrong ' + account.currencyCode + ' ' + e.message) + } + + if (toRemove.length > 0) { + await transactionDS.removeTransactions(toRemove) + } + if (!account.transactionsScanLog || account.transactionsScanLog.length < 10) { + source = 'FIRST' + } + + /** + * @property {*} transactionHash + * @property {*} blockHash + * @property {*} blockNumber + * @property {*} blockTime + * @property {*} blockConfirmations + * @property {*} transactionDirection + * @property {*} addressFrom + * @property {*} addressTo + * @property {*} addressAmount + * @property {*} transactionStatus + * @property {*} transactionFee + * @property {*} contractAddress + * @property {*} inputValue + * @property {*} transactionJson + */ + let transaction + let transactionsError = '' + let changesCount = 0 + const unique = {} + try { + for (transaction of newTransactions) { + if (typeof unique[transaction.transactionHash] !== 'undefined') { + continue + } + unique[transaction.transactionHash] = 1 + try { + const tmp = await AccountTransactionRecheck(transaction, dbTransactions[transaction.transactionHash], account, source) + if (typeof dbTransactions[transaction.transactionHash] !== 'undefined') { + delete dbTransactions[transaction.transactionHash] + if (typeof CACHE_TO_REMOVE[account.currencyCode][transaction.transactionHash] !== 'undefined') { + delete CACHE_TO_REMOVE[account.currencyCode][transaction.transactionHash] + } + } + if (tmp.isChanged > 0) { + + if (transaction.transactionStatus === 'success' || transaction.transactionStatus === 'confirming') { + if (typeof transaction.transactionJson !== 'undefined' && transaction.transactionJson && typeof transaction.transactionJson.nonce !== 'undefined' && typeof transaction.transactionJson.delegatedNonce === 'undefined') { + let key = transaction.addressFromBasic + '_' + transaction.transactionJson.nonce + if (typeof dbNonces[key] !== 'undefined' && dbNonces[key].length > 1) { + for (let nonced of dbNonces[key]) { + if (nonced.transactionHash !== transaction.transactionHash) { + await transactionDS.saveTransaction({ + transactionStatus: 'replaced', + transactionsScanLog: nonced.transactionsScanLog + ' dropped by nonce' + }, nonced.id, 'replaced') + } + } + } + } + } + + changesCount++ + } + } catch (e) { + e.message = ' TX ' + transaction.transactionHash + ' ' + e.message + // noinspection ExceptionCaughtLocallyJS + throw e + } + } + } catch (e) { + if (config.debug.appErrors) { + console.log('AccountTransactionsRecheck parsing error ' + e.message, e) + } + transactionsError += ' parsing error ' + e.message + } + + if (dbTransactions) { + const now = new Date().getTime() + for (const hash in dbTransactions) { + const dbTransaction = dbTransactions[hash] + if (dbTransaction.blockConfirmations > 40) { + continue + } + + let minutesToWait = 0 + let minutesToInform = 0 + if (account.currencyCode === 'USDT') { + minutesToWait = 1200 + minutesToInform = 12000 + } else if (account.currencyCode === 'ETH' || account.currencyCode.indexOf('ETH_') === 0) { + minutesToWait = 240 + minutesToInform = 400 + } else if (dbTransaction.transactionStatus === 'new' || dbTransaction.transactionStatus === 'pending') { + minutesToWait = 20 // pending tx will be in the list - checked in trezor + minutesToInform = 200 + } else if (dbTransaction.transactionStatus === 'confirming') { + minutesToWait = 10 + minutesToInform = 200 + } else { + continue + } + + if (typeof CACHE_TO_REMOVE[account.currencyCode][hash] === 'undefined') { + CACHE_TO_REMOVE[account.currencyCode][hash] = { ts: now, count: 0 } + continue + } + + const minutesFromScan = Math.round((now - CACHE_TO_REMOVE[account.currencyCode][hash].ts) / 60000) + CACHE_TO_REMOVE[account.currencyCode][hash].count++ + if (account.currencyCode === 'BTC') { + if (minutesFromScan < 20 || CACHE_TO_REMOVE[account.currencyCode][hash].count < 10) { + continue // 10 minutes from last recheck as btc are in several accounts + } + } else { + if (minutesFromScan < 2 || CACHE_TO_REMOVE[account.currencyCode][hash].count < 3) { + continue // other currencies are scanned in one account + } + } + + const time = dbTransaction.updatedAt || dbTransaction.createdAt + const minutes = Math.round((now - new Date(time).getTime()) / 60000) + if (minutes > minutesToWait && (dbTransaction.transactionStatus !== 'fail' && dbTransaction.transactionStatus !== 'no_energy')) { + await BlocksoftTransfer.setMissingTx(account, dbTransaction) + + await transactionDS.saveTransaction({ + transactionStatus: 'missing', + transactionsScanLog: dbTransaction.transactionsScanLog + ' dropped after ' + minutes + ' from ' + time + }, dbTransaction.id, 'missing') + + if (dbTransaction.addressAmount > 0 && minutes < minutesToInform) { + await addNews(dbTransaction, account, source, 'FOUND_OUT_TX_STATUS_MISSING') + } + } + } + } + + if (changesCount > 0) { + transactionUpdateObj.transactionsScanLog = 'all ok, changed ' + changesCount + if (transactionsError) { + transactionUpdateObj.transactionsScanLog += ', ' + transactionsError + } + } else { + transactionUpdateObj.transactionsScanLog = 'not changed txs ' + transactionsError + } + return transactionUpdateObj +} + +async function AccountTransactionRecheck(transaction, old, account, source) { + // Log.daemon('AccountTransactionRecheck in ' + JSON.stringify(transaction) + ' old ' + JSON.stringify(old)) + let blocksToUpdate = 50 + if (account.currencyCode.indexOf('TRX') !== -1) { + blocksToUpdate = 1000 + } + const line = new Date().toISOString().replace(/T/, ' ').replace(/\..+/, '') + let tmpMsg = '' + + let newAddressTo = transaction.addressTo + if (typeof newAddressTo === 'undefined' || newAddressTo === false) { + newAddressTo = '' + } + + if (typeof transaction.addressAmount === 'undefined') { + // Log.daemon('UpdateAccountTransactions ' + account.currencyCode + ' skip as no addressAmount', tmpMsg, transaction) + return false + } + let newAmount = typeof transaction.addressAmount === 'undefined' ? '0' : transaction.addressAmount.toString() + if (newAmount.length > 10) { + newAmount = newAmount.substring(0, 10) + } + if (newAmount === '000000000') { + newAmount = 0 + } + + if (typeof old === 'undefined' || !old) { + if (newAmount === 0) { + tmpMsg = `SKIP AS ZERO AMOUNT ${account.currencyCode} HASH ${transaction.transactionHash} CONF ${transaction.blockConfirmations} AMOUNT ${transaction.addressAmount} FROM ${transaction.addressFrom} TO ${transaction.addressTo}` + // Log.daemon('UpdateAccountTransactions ' + account.currencyCode + ' skip 1', tmpMsg, transaction) + return { isChanged: 0, tmpMsg } + } + + tmpMsg = ` INSERT ${account.currencyCode} HASH ${transaction.transactionHash} CONF ${transaction.blockConfirmations} AMOUNT ${transaction.addressAmount} FROM ${transaction.addressFrom} TO ${transaction.addressTo}` + if (source !== 'FIRST' && transaction.addressAmount > 0) { + tmpMsg += ' with PUSH ' + source + } + transaction.currencyCode = account.currencyCode + transaction.walletHash = account.walletHash + transaction.accountId = account.id + transaction.createdAt = transaction.blockTime // to make created tx more accurate + transaction.minedAt = transaction.blockTime + transaction.transactionsScanTime = Math.round(new Date().getTime() / 1000) + transaction.transactionsScanLog = line + ' ' + tmpMsg + if (!transaction.transactionDirection) { + transaction.transactionDirection = 'outcome' + } + + await transactionDS.saveTransaction(transaction, false, 'old undefined ' + tmpMsg) + // Log.daemon('UpdateAccountTransactions ' + account.currencyCode + ' added', tmpMsg, transaction) + if (source !== 'FIRST' && transaction.addressAmount > 0) { + await addNews(transaction, account, source, 'new') + } + return { isChanged: 1, tmpMsg } + } + + if (old.transactionHash !== transaction.transactionHash) { + // Log.daemon('UpdateAccountTransactions ' + account.currencyCode + ' old is replaced - would not place DB HASH ' + old.transactionHash + ' != SCANNED HASH ' + transaction.transactionHash, old, transaction) + return { isChanged: 0, tmpMsg: ` DROPPED SCANNED ${transaction.transactionHash} DB ${old.transactionHash}` } + } + + if (typeof old.updateSkip !== 'undefined') { + return { isChanged: 0, tmpMsg: ` SKIPPED SCANNED ${transaction.transactionHash} DB ${old.transactionHash}` } + } + + let oldAmount = typeof old.addressAmount === 'undefined' ? '0' : old.addressAmount.toString() + if (oldAmount.length > 10) { + oldAmount = oldAmount.substring(0, 10) + } + if (oldAmount === '000000000') { + oldAmount = 0 + } + + + let oldAddressTo = old.addressTo + if (typeof oldAddressTo === 'undefined' || !oldAddressTo) { + oldAddressTo = '' + } + + if (old.transactionOfTrusteeWallet > 0) { + const transactionPart = { + transactionStatus: transaction.transactionStatus, + blockConfirmations: transaction.blockConfirmations + } + + if (!old.createdAt) { + tmpMsg = ` TWALLET UPDATE ${account.id} ${account.currencyCode} HASH ${transaction.transactionHash} by CREATED NEW ${transaction.blockTime} OLD ${old.createdAt} AMOUNT ${transaction.addressAmount} FROM ${transaction.addressFrom} TO ${transaction.addressTo}` + } else if (transaction.blockTime && (!old.minedAt || old.minedAt !== transaction.blockTime)) { + tmpMsg = ` TWALLET UPDATE ${account.id} ${account.currencyCode} HASH ${transaction.transactionHash} by MINED NEW ${transaction.blockTime} OLD ${old.minedAt} AMOUNT ${transaction.addressAmount} FROM ${transaction.addressFrom} TO ${transaction.addressTo}` + } else if (old.transactionStatus !== transaction.transactionStatus || !old.createdAt) { + tmpMsg = ` TWALLET UPDATE ${account.id} ${account.currencyCode} HASH ${transaction.transactionHash} by STATUS NEW ${transaction.transactionStatus} OLD ${old.transactionStatus} AMOUNT ${transaction.addressAmount} FROM ${transaction.addressFrom} TO ${transaction.addressTo}` + } else if (!old.transactionFee && old.transactionFee !== transaction.transactionFee) { + tmpMsg = ` TWALLET UPDATE ${account.id} ${account.currencyCode} HASH ${transaction.transactionHash} by FEE ${transaction.transactionFee} AMOUNT ${transaction.addressAmount} FROM ${transaction.addressFrom} TO ${transaction.addressTo}` + } else if (!old.addressAmount || old.addressAmount * 1 !== transaction.addressAmount * 1) { + tmpMsg = ` TWALLET UPDATE ${account.id} ${account.currencyCode} HASH ${transaction.transactionHash} by AMOUNT NEW ${transaction.addressAmount} OLD ${old.addressAmount} FROM ${transaction.addressFrom} TO ${transaction.addressTo}` + } else if (old.blockConfirmations === transaction.blockConfirmations || (old.blockConfirmations > blocksToUpdate && transaction.blockConfirmations > blocksToUpdate)) { + tmpMsg = ` TWALLET SKIP ${account.id} ${account.currencyCode} HASH ${transaction.transactionHash} CONF ${transaction.blockConfirmations} OLD CONF ${old.blockConfirmations} STATUS ${old.transactionStatus}` + return { isChanged: 0, tmpMsg } + } else { + tmpMsg = ` TWALLET UPDATE ${account.id} ${account.currencyCode} HASH ${transaction.transactionHash} by CONF NEW ${transaction.blockConfirmations} OLD ${old.blockConfirmations} STATUS ${transaction.transactionStatus} AMOUNT ${transaction.addressAmount} FROM ${transaction.addressFrom} TO ${transaction.addressTo}` + } + + if (!old.transactionFee && transaction.transactionFee) { + tmpMsg += ' PLUS FEE ' + transaction.transactionFee + transactionPart.transactionFee = transaction.transactionFee + transactionPart.transactionFeeCurrencyCode = transaction.transactionFeeCurrencyCode || '' + } + if (!old.createdAt) { + transactionPart.createdAt = transaction.blockTime + } + transactionPart.minedAt = transaction.blockTime + transactionPart.transactionsScanTime = Math.round(new Date().getTime() / 1000) + transactionPart.transactionsScanLog = line + ' ' + tmpMsg + if (old && old.transactionsScanLog) { + transactionPart.transactionsScanLog += ' ' + old.transactionsScanLog + } + if (old.transactionDirection !== transaction.transactionDirection) { + Log.daemon('UpdateAccountTransactions ' + account.currencyCode + ' by TWALLET DIRECTION id ' + old.id + ' address ' + account.address + ' hash ' + transaction.transactionHash + ' OLD ' + old.transactionDirection + ' NEW ' + transaction.transactionDirection) + } + await transactionDS.saveTransaction(transactionPart, old.id, 'old trustee ' + tmpMsg) + + if (old.transactionStatus !== transaction.transactionStatus && source !== 'FIRST' && transaction.addressAmount > 0) { + await addNews(transaction, account, source, 'old') + } + return { isChanged: 1, tmpMsg } + } + + if (!old.createdAt) { + tmpMsg = ` FULL UPDATE ${account.id} ${account.currencyCode} HASH ${transaction.transactionHash} by CREATED NEW ${transaction.blockTime} OLD ${old.createdAt} AMOUNT ${transaction.addressAmount} FROM ${transaction.addressFrom} TO ${transaction.addressTo}` + } else if (transaction.blockTime && (!old.minedAt || old.minedAt !== transaction.blockTime)) { + tmpMsg = ` FULL UPDATE ${account.id} ${account.currencyCode} HASH ${transaction.transactionHash} by MINED NEW ${transaction.blockTime} OLD ${old.minedAt} AMOUNT ${transaction.addressAmount} FROM ${transaction.addressFrom} TO ${transaction.addressTo}` + } else if (old.transactionStatus !== transaction.transactionStatus) { + tmpMsg = ` FULL UPDATE ${account.id} ${account.currencyCode} HASH ${transaction.transactionHash} by STATUS NEW ${transaction.transactionStatus} OLD ${old.transactionStatus} AMOUNT ${transaction.addressAmount} FROM ${transaction.addressFrom} TO ${transaction.addressTo}` + } else if (oldAmount !== newAmount) { + tmpMsg = ` FULL UPDATE ${account.id} ${account.currencyCode} HASH ${transaction.transactionHash} by AMOUNT NEW ${newAmount} OLD ${oldAmount} AMOUNT ${transaction.addressAmount} FROM ${transaction.addressFrom} TO ${transaction.addressTo}` + } else if (oldAddressTo !== newAddressTo) { + tmpMsg = ` FULL UPDATE ${account.id} ${account.currencyCode} HASH ${transaction.transactionHash} by ADDRESS_TO ${newAddressTo} OLD ${oldAddressTo} AMOUNT ${transaction.addressAmount} FROM ${transaction.addressFrom} TO ${transaction.addressTo}` + } else if (old.addressFrom !== transaction.addressFrom) { + tmpMsg = ` FULL UPDATE ${account.id} ${account.currencyCode} HASH ${transaction.transactionHash} by ADDRESS_FROM ${transaction.addressFrom} OLD ${old.addressFrom} AMOUNT ${transaction.addressAmount} FROM ${transaction.addressFrom} TO ${transaction.addressTo}` + } else if (old.blockConfirmations === transaction.blockConfirmations || (old.blockConfirmations > blocksToUpdate && transaction.blockConfirmations > blocksToUpdate)) { + tmpMsg = ` SKIP ${account.id} ${account.currencyCode} HASH ${transaction.transactionHash} CONF ${transaction.blockConfirmations} OLD CONF ${old.blockConfirmations} STATUS ${old.transactionStatus}` + // Log.daemon('UpdateAccountTransactions ' + account.currencyCode + ' skip 3 ' + transaction.transactionHash) + return { isChanged: 0, tmpMsg } + } else { + tmpMsg = ` FULL UPDATE ${account.currencyCode} HASH ${transaction.transactionHash} by CONF NEW ${transaction.blockConfirmations} OLD ${old.blockConfirmations} STATUS ${transaction.transactionStatus} AMOUNT ${transaction.addressAmount} FROM ${transaction.addressFrom} TO ${transaction.addressTo}` + } + transaction.createdAt = transaction.blockTime + transaction.minedAt = transaction.blockTime + if (old.transactionDirection !== transaction.transactionDirection) { + Log.daemon('UpdateAccountTransactions ' + account.currencyCode + ' by DIRECTION id ' + old.id + ' address ' + account.address + ' hash ' + transaction.transactionHash + ' OLD ' + old.transactionDirection + ' NEW ' + transaction.transactionDirection) + delete transaction.transactionDirection + } + transaction.transactionsScanTime = Math.round(new Date().getTime() / 1000) + transaction.transactionsScanLog = line + ' ' + tmpMsg + ' ' + if (old && old.transactionsScanLog) { + transaction.transactionsScanLog += ' ' + old.transactionsScanLog + } + await transactionDS.saveTransaction(transaction, old.id, 'old ' + tmpMsg) + // Log.daemon('UpdateAccountTransactions ' + account.currencyCode + ' update 2', tmpMsg, transaction) + return { isChanged: 1, tmpMsg } +} + +async function addNews(transaction, account, source, type) { + const transactionsNotifs = await settingsActions.getSetting('transactionsNotifs') + if (transactionsNotifs !== '1') { + return + } + if (type === 'new') { + const now = new Date().getTime() + const time = transaction.createdAt + const minutes = Math.round((now - new Date(time).getTime()) / 60000) + if (minutes > 5760 ) { // 4 day + return false + } + } + let needToPopup = 1 + if (transaction.transactionStatus === 'confirming') { + needToPopup = 0 + } else if (transaction.transactionStatus === 'delegated') { + needToPopup = 0 + } + let name = '' + if (type === 'FOUND_OUT_TX_STATUS_MISSING') { + name = type + } else if (transaction.transactionDirection === 'income') { + name = 'FOUND_IN_TX' + } else { + name = 'FOUND_OUT_TX_STATUS_' + transaction.transactionStatus.toUpperCase() + } + + const data = { + walletHash: account.walletHash, + currencyCode: account.currencyCode, + newsSource: source, + newsNeedPopup: needToPopup, + newsGroup: 'TX_SCANNER', + newsName: name, + newsJson: transaction, + newsUniqueKey: transaction.transactionHash + } + await appNewsDS.saveAppNews( + data + ) +} diff --git a/src/daemons/view/UpdateAccountListDaemon.js b/src/daemons/view/UpdateAccountListDaemon.js new file mode 100644 index 000000000..0229555aa --- /dev/null +++ b/src/daemons/view/UpdateAccountListDaemon.js @@ -0,0 +1,592 @@ +/** + * @version 0.11 + */ +import Update from '../Update' + +import Log from '../../services/Log/Log' + +import store from '../../store' + +import accountDS from '../../appstores/DataSource/Account/Account' +import walletPubDS from '../../appstores/DataSource/Wallet/WalletPub' + +import BlocksoftDict from '../../../crypto/common/BlocksoftDict' +import BlocksoftPrettyNumbers from '../../../crypto/common/BlocksoftPrettyNumbers' +import BlocksoftUtils from '../../../crypto/common/BlocksoftUtils' + +import DaemonCache from '../DaemonCache' +import { setSelectedAccount, setSelectedAccountTransactions } from '../../appstores/Stores/Main/MainStoreActions' +import BlocksoftBN from '../../../crypto/common/BlocksoftBN' +import MarketingEvent from '../../services/Marketing/MarketingEvent' +import { BlocksoftTransferUtils } from '@crypto/actions/BlocksoftTransfer/BlocksoftTransferUtils' +import walletActions from '@app/appstores/Stores/Wallet/WalletActions' +import BlocksoftKeysStorage from '@crypto/actions/BlocksoftKeysStorage/BlocksoftKeysStorage' +import BlocksoftKeys from '@crypto/actions/BlocksoftKeys/BlocksoftKeys' +import config from '@app/config/config' +import Database from '@app/appstores/DataSource/Database/main' + +let CACHE_PAUSE = 0 + +const CACHE_VALID_TIME_PAUSE = 10000 + +let CACHE_STOPPED = false + +class UpdateAccountListDaemon extends Update { + + constructor(props) { + super(props) + this.updateFunction = this.updateAccountListDaemon + this._canUpdate = true + } + + stop = () => { + CACHE_STOPPED = true + } + + unstop = () => { + CACHE_STOPPED = false + } + + pause = () => { + CACHE_PAUSE = new Date().getTime() + } + + + /** + * @return {Promise} + */ + updateAccountListDaemon = async (params) => { + const source = params.source || 'none' + const force = params.force || false + const now = new Date().getTime() + if (CACHE_STOPPED && !force) return false + + if (!force && DaemonCache.CACHE_WALLET_COUNT > 0) { + if ( + (CACHE_PAUSE > 0 && now - CACHE_PAUSE < CACHE_VALID_TIME_PAUSE) + || !this._canUpdate + ) { + if (store.getState().accountStore.accountList !== {}) { + // do nothing @todo testing if its the key + } else { + store.dispatch({ + type: 'SET_ACCOUNT_LIST', + accountList: DaemonCache.CACHE_ALL_ACCOUNTS + }) + } + return false + } + } + this._canUpdate = false + try { + + const currentStore = store.getState() + const allWallets = currentStore.walletStore.wallets + const selectedAccount = currentStore.mainStore.selectedAccount + + let basicCurrency = currentStore.mainStore.selectedBasicCurrency + if (!basicCurrency || typeof basicCurrency.symbol === 'undefined') { + basicCurrency = { + currencyCode : 'USD', + symbol : '$' + } + } + const basicCurrencyCode = basicCurrency.currencyCode || 'USD' + + const walletPub = await walletPubDS.getWalletPubs() + const notWalletHashes = [] + if (walletPub) { + let walletPubKey + for (walletPubKey in walletPub) { + notWalletHashes.push(walletPubKey) + } + } + const tmps = await accountDS.getAccountData({ notWalletHashes }) + + const reformatted = {} + const alreadyCounted = {} + let tmpCurrency + const tmpWalletHashes = [] + for (tmpCurrency of tmps) { + if (typeof reformatted[tmpCurrency.walletHash] === 'undefined') { + reformatted[tmpCurrency.walletHash] = {} + alreadyCounted[tmpCurrency.walletHash] = {} + tmpWalletHashes.push(tmpCurrency.walletHash) + } + if (tmpCurrency.currencyCode === 'BTC' && typeof walletPub[tmpCurrency.walletHash] !== 'undefined') { + + if (typeof reformatted[tmpCurrency.walletHash][tmpCurrency.currencyCode] !== 'undefined') { + if (reformatted[tmpCurrency.walletHash][tmpCurrency.currencyCode].address.substr(0, 1) === '3') { + reformatted[tmpCurrency.walletHash][tmpCurrency.currencyCode].address = tmpCurrency.address + reformatted[tmpCurrency.walletHash][tmpCurrency.currencyCode].derivationPath = tmpCurrency.derivationPath + } + reformatted[tmpCurrency.walletHash][tmpCurrency.currencyCode].walletPubs = walletPub[tmpCurrency.walletHash] + + if (tmpCurrency.address.indexOf(BlocksoftDict.Currencies[tmpCurrency.currencyCode].addressPrefix) === 0) { + if (typeof reformatted[tmpCurrency.walletHash][tmpCurrency.currencyCode].legacy === 'undefined') { + reformatted[tmpCurrency.walletHash][tmpCurrency.currencyCode].legacy = tmpCurrency.address + } + } else if (tmpCurrency.address.indexOf(BlocksoftDict.CurrenciesForTests[tmpCurrency.currencyCode + '_SEGWIT'].addressPrefix) === 0) { + if (typeof reformatted[tmpCurrency.walletHash][tmpCurrency.currencyCode].segwit === 'undefined') { + reformatted[tmpCurrency.walletHash][tmpCurrency.currencyCode].segwit = tmpCurrency.address + } + } + + continue + } + const pub = walletPub[tmpCurrency.walletHash] + reformatted[tmpCurrency.walletHash][tmpCurrency.currencyCode] = tmpCurrency + + let tmpBalanceScanTime = 0 + let tmpTransactionsScanTime = 0 + let tmpBalance = new BlocksoftBN(0) + let tmpUnconfirmed = new BlocksoftBN(0) + let tmpBalanceLog = '' + let tmpBalanceScanError = '' + const keys = ['btc.44', 'btc.49', 'btc.84'] + let key + for (key of keys) { + if (typeof pub[key] === 'undefined') { + continue + } + if (tmpBalanceScanTime > 0) { + if (pub[key].balanceScanTime * 1 > 0) { + tmpBalanceScanTime = Math.min(tmpBalanceScanTime, pub[key].balanceScanTime) + } else { + tmpBalanceScanTime = 0 + } + } else { + tmpBalanceScanTime = pub[key].balanceScanTime * 1 + } + if (tmpTransactionsScanTime > 0) { + if (pub[key].transactionsScanTime * 1 > 0) { + tmpTransactionsScanTime = Math.max(tmpTransactionsScanTime, pub[key].transactionsScanTime) + } + } else { + tmpTransactionsScanTime = pub[key].transactionsScanTime * 1 + } + + if (pub[key].balance) { + tmpBalance.add(pub[key].balance) + tmpBalanceLog += ' ' + pub[key].balance + '(' + key + ')' + } + if (pub[key].balanceScanError && pub[key].balanceScanError !== '' && pub[key].balanceScanError !== 'null') { + tmpBalanceScanError = pub[key].balanceScanError + } + + if (pub[key].unconfirmed) { + try { + tmpUnconfirmed.add(pub[key].unconfirmed) + } catch (e) { + Log.errDaemon('UpdateAccountListDaemon error on tmpUnconfirmed ' + e.message) + } + } + } + + const badAddresses = await accountDS.getAccountData({ derivationPath: 'm/49quote/0quote/0/1/0' }) + if (badAddresses) { + let bad + for (bad of badAddresses) { + + if (bad.balance) { + try { + tmpBalance.add(bad.balance) + tmpBalanceLog += ' ' + bad.balance + '(' + bad.address + ')' + } catch (e) { + Log.errDaemon('UpdateAccountListDaemon error on tmpBalance ' + e.message) + } + } + + if (bad.unconfirmed) { + try { + tmpUnconfirmed.add(bad.unconfirmed) + } catch (e) { + Log.errDaemon('UpdateAccountListDaemon error on tmpUnconfirmed2 ' + e.message) + } + } + + } + } + + tmpBalance = tmpBalance.get() + tmpUnconfirmed = tmpUnconfirmed.get() + + reformatted[tmpCurrency.walletHash][tmpCurrency.currencyCode].balance = tmpBalance + reformatted[tmpCurrency.walletHash][tmpCurrency.currencyCode].unconfirmed = tmpUnconfirmed + reformatted[tmpCurrency.walletHash][tmpCurrency.currencyCode].balanceTxt = '' + reformatted[tmpCurrency.walletHash][tmpCurrency.currencyCode].balanceFix = '' + reformatted[tmpCurrency.walletHash][tmpCurrency.currencyCode].unconfirmedTxt = '' + reformatted[tmpCurrency.walletHash][tmpCurrency.currencyCode].unconfirmedFix = '' + reformatted[tmpCurrency.walletHash][tmpCurrency.currencyCode].balanceStaked = '' + reformatted[tmpCurrency.walletHash][tmpCurrency.currencyCode].balanceAddingLog = tmpBalanceLog + reformatted[tmpCurrency.walletHash][tmpCurrency.currencyCode].balanceScanTime = tmpBalanceScanTime > tmpTransactionsScanTime ? tmpBalanceScanTime : tmpTransactionsScanTime + reformatted[tmpCurrency.walletHash][tmpCurrency.currencyCode].balanceScanError = tmpBalanceScanError + reformatted[tmpCurrency.walletHash][tmpCurrency.currencyCode].transactionsScanTime = tmpTransactionsScanTime + + } else { + if (typeof reformatted[tmpCurrency.walletHash][tmpCurrency.currencyCode] !== 'undefined') { + if (typeof alreadyCounted[tmpCurrency.walletHash][tmpCurrency.currencyCode][tmpCurrency.address] === 'undefined') { + if (reformatted[tmpCurrency.walletHash][tmpCurrency.currencyCode].balanceScanTime * 1 > 0) { + if (tmpCurrency.balanceScanTime * 1 > 0) { + reformatted[tmpCurrency.walletHash][tmpCurrency.currencyCode].balanceScanTime = Math.min(tmpCurrency.balanceScanTime * 1, reformatted[tmpCurrency.walletHash][tmpCurrency.currencyCode].balanceScanTime * 1) + } else { + reformatted[tmpCurrency.walletHash][tmpCurrency.currencyCode].balanceScanTime = 0 + } + } else { + reformatted[tmpCurrency.walletHash][tmpCurrency.currencyCode].balanceScanTime = tmpCurrency.balanceScanTime * 1 + } + reformatted[tmpCurrency.walletHash][tmpCurrency.currencyCode].transactionsScanTime = 0 + try { + reformatted[tmpCurrency.walletHash][tmpCurrency.currencyCode].balance = BlocksoftUtils.add(reformatted[tmpCurrency.walletHash][tmpCurrency.currencyCode].balance, tmpCurrency.balance).toString() + } catch (e) { + Log.errDaemon('UpdateAccountListDaemon error on reformatted ' + e.message) + } + try { + reformatted[tmpCurrency.walletHash][tmpCurrency.currencyCode].unconfirmed = BlocksoftUtils.add(reformatted[tmpCurrency.walletHash][tmpCurrency.currencyCode].unconfirmed, tmpCurrency.unconfirmed).toString() + } catch (e) { + Log.errDaemon('UpdateAccountListDaemon error on reformatted2 ' + e.message) + } + try { + reformatted[tmpCurrency.walletHash][tmpCurrency.currencyCode].balanceStaked = BlocksoftUtils.add(reformatted[tmpCurrency.walletHash][tmpCurrency.currencyCode].balanceStaked, tmpCurrency.balanceStaked).toString() + } catch (e) { + Log.errDaemon('UpdateAccountListDaemon error on reformatted3 ' + e.message) + } + reformatted[tmpCurrency.walletHash][tmpCurrency.currencyCode].balanceTxt = '' + reformatted[tmpCurrency.walletHash][tmpCurrency.currencyCode].balanceFix = '' + reformatted[tmpCurrency.walletHash][tmpCurrency.currencyCode].unconfirmedTxt = '' + reformatted[tmpCurrency.walletHash][tmpCurrency.currencyCode].unconfirmedFix = '' + try { + reformatted[tmpCurrency.walletHash][tmpCurrency.currencyCode].balanceAddingLog += ' ' + tmpCurrency.balance + ' (' + tmpCurrency.address + '/' + tmpCurrency.balanceScanTime + ')' + } catch (e) { + Log.errDaemon('UpdateAccountListDaemon error on balanceAddingLog ' + e.message) + } + alreadyCounted[tmpCurrency.walletHash][tmpCurrency.currencyCode][tmpCurrency.address] = 1 + } + } else { + alreadyCounted[tmpCurrency.walletHash][tmpCurrency.currencyCode] = {} + alreadyCounted[tmpCurrency.walletHash][tmpCurrency.currencyCode][tmpCurrency.address] = 1 + reformatted[tmpCurrency.walletHash][tmpCurrency.currencyCode] = tmpCurrency + try { + reformatted[tmpCurrency.walletHash][tmpCurrency.currencyCode].balanceAddingLog = tmpCurrency.balance + ' (' + tmpCurrency.address + '/' + tmpCurrency.balanceScanTime + ')' + } catch (e) { + Log.errDaemon('UpdateAccountListDaemon error on balanceAddingLog2 ' + e.message) + } + } + + if (typeof BlocksoftDict.CurrenciesForTests[tmpCurrency.currencyCode + '_SEGWIT'] !== 'undefined') { + if (tmpCurrency.address.indexOf(BlocksoftDict.Currencies[tmpCurrency.currencyCode].addressPrefix) === 0) { + reformatted[tmpCurrency.walletHash][tmpCurrency.currencyCode].legacy = tmpCurrency.address + } else if (tmpCurrency.address.indexOf(BlocksoftDict.CurrenciesForTests[tmpCurrency.currencyCode + '_SEGWIT'].addressPrefix) === 0) { + reformatted[tmpCurrency.walletHash][tmpCurrency.currencyCode].segwit = tmpCurrency.address + } + } + } + } + + let tmpWalletHash + DaemonCache.CACHE_WALLET_COUNT = 0 + for (tmpWalletHash of tmpWalletHashes) { + DaemonCache.CACHE_ALL_ACCOUNTS[tmpWalletHash] = {} + DaemonCache.CACHE_WALLET_SUMS[tmpWalletHash] = { + balance: 0, + unconfirmed: 0, + balanceStaked : 0, + balanceAddingLog: '', + balanceAddingLogHidden : '', + balanceAddingLogZero : '', + basicCurrencySymbol: basicCurrency.symbol || '$' + } + DaemonCache.CACHE_WALLET_TOTAL.balance = 0 + DaemonCache.CACHE_WALLET_TOTAL.unconfirmed = 0 + DaemonCache.CACHE_WALLET_TOTAL.basicCurrencySymbol = basicCurrency.symbol || '$' + DaemonCache.CACHE_WALLET_COUNT++ + } + + for (tmpCurrency of currentStore.currencyStore.cryptoCurrencies) { + const currencyCode = tmpCurrency.currencyCode + if (currencyCode === 'NFT' || currencyCode==='CASHBACK') continue + let rate = 0 + if (!tmpCurrency.currencyRateJson || typeof tmpCurrency.currencyRateJson[basicCurrencyCode] === 'undefined') { + if (basicCurrencyCode === 'USD') { + rate = tmpCurrency.currencyRateUsd || 0 + } + } else { + rate = tmpCurrency.currencyRateJson[basicCurrencyCode] || 0 + } + if (rate.toString().indexOf('e-')) { + rate = BlocksoftUtils.fromENumber(rate.toString()) + } + if (currencyCode === 'BTC') { + Log.daemon('UpdateAccountListDaemon rate BTC ' + rate + ' with ' + JSON.stringify(basicCurrency)) + } + DaemonCache.CACHE_RATES[currencyCode] = { + basicCurrencyRate: rate, + basicCurrencySymbol: basicCurrency.symbol || '$' + } + + for (tmpWalletHash of tmpWalletHashes) { + // console.log('updateAccounts ' + tmpWalletHash + ' ' + currencyCode + ' ' + rate, reformatted[tmpWalletHash][currencyCode]) + if (typeof reformatted[tmpWalletHash][currencyCode] === 'undefined') { + if (currencyCode === 'BTC' && typeof walletPub[tmpWalletHash] !== 'undefined') { + Log.daemon('UpdateAccountListDaemon need to generate ' + tmpWalletHash + ' ' + currencyCode) + + const res = await walletPubDS.discoverMoreAccounts({ + currencyCode: 'BTC', + walletHash: tmpWalletHash, + needLegacy: true, + needSegwit: true + }, '_SET_ACCOUNT_LIST') + + if (res && source !== 'regenerate') { + this._canUpdate = true + return this.updateAccountListDaemon({ source: 'regenerate' }) + } + } else { + if (config.debug.appErrors) { + //console.log('UpdateAccountListDaemon skip as no account ' + tmpWalletHash + ' ' + currencyCode + ' but started regeneration') + } + //Log.daemon('UpdateAccountListDaemon skip as no account ' + tmpWalletHash + ' ' + currencyCode + ' but started regeneration') + + // low code as something strange if no account but there is currency + const mnemonic = await BlocksoftKeysStorage.getWalletMnemonic(tmpWalletHash, 'UpdateAccountListDaemon') + let accounts = await BlocksoftKeys.discoverAddresses({ mnemonic, fullTree: false, fromIndex: 0, toIndex: 1, currencyCode }, 'APP_ACCOUNTS_LIST') + if (typeof accounts[currencyCode] === 'undefined') { + if (config.debug.appErrors) { + //console.log('UpdateAccountListDaemon skip as no account ' + tmpWalletHash + ' ' + currencyCode + ' fail regeneration') + } + //Log.daemon('UpdateAccountListDaemon skip as no account ' + tmpWalletHash + ' ' + currencyCode + ' fail regeneration') + } else { + const prepare = [] + for (const account of accounts[currencyCode]) { + const derivationPath = Database.escapeString(account.path) + let accountJson = '' + if (typeof (account.addedData) !== 'undefined') { + accountJson = Database.escapeString(JSON.stringify(account.addedData)) + } + const tmp = { + address: account.address, + name: '', + derivationPath: derivationPath, + derivationIndex: account.index, + derivationType: account.type, + alreadyShown: account.alreadyShown ? 1 : 0, + status: 0, + currencyCode, + walletHash: tmpWalletHash, + walletPubId: 0, + accountJson: accountJson, + transactionsScanTime: 0 + } + if (typeof account.walletPubId !== 'undefined') { + tmp.walletPubId = account.walletPubId + params.walletPubId = tmp.walletPubId + } else if (typeof params.walletPubId !== 'undefined') { + tmp.walletPubId = params.walletPubId + } + prepare.push(tmp) + } + if (prepare.length === 0) { + if (config.debug.appErrors) { + console.log('UpdateAccountListDaemon skip as no account ' + tmpWalletHash + ' ' + currencyCode + ' fail prepare') + } + Log.daemon('UpdateAccountListDaemon skip as no account ' + tmpWalletHash + ' ' + currencyCode + ' fail prepare') + } else { + await Database.setTableName('account').setInsertData({ insertObjs: prepare }).insert() + } + } + // end low code + } + continue + } + const accountWallet = allWallets.find(item => item.walletHash === tmpWalletHash) + const account = reformatted[tmpWalletHash][currencyCode] + + const extendCurrencyCode = BlocksoftDict.getCurrencyAllSettings(account.currencyCode) + account.feesCurrencyCode = extendCurrencyCode.feesCurrencyCode || account.currencyCode + const extendedFeesCode = BlocksoftDict.getCurrencyAllSettings(account.feesCurrencyCode) + account.feesCurrencySymbol = extendedFeesCode.currencySymbol || extendedFeesCode.currencyCode + + account.feeRates = DaemonCache.getCacheRates(account.feesCurrencyCode) + account.walletUseUnconfirmed = accountWallet?.walletUseUnconfirmed + try { + account.balanceRaw = + BlocksoftTransferUtils.getBalanceForTransfer({ + balance : account.balance, + unconfirmed : (accountWallet && accountWallet?.walletUseUnconfirmed === 1) ? account.unconfirmed : false, + balanceStaked: account.balanceStaked, + currencyCode + }) + } catch (e) { + Log.errDaemon('UpdateAccountListDaemon error on account.balanceRaw ' + e.message) + account.balanceRaw = account.balance + } + account.currencySymbol = tmpCurrency.currencySymbol + account.basicCurrencyRate = rate + account.basicCurrencyCode = basicCurrencyCode + account.basicCurrencySymbol = basicCurrency.symbol || '$' + account.balancePretty = 0 + if (account.balance > 0) { + try { + account.balancePretty = BlocksoftPrettyNumbers.setCurrencyCode(currencyCode).makePretty(account.balance, 'updateAccountListDaemon.balance') + } catch (e) { + Log.errDaemon('UpdateAccountListDaemon error on account.balance makePretty ' + e.message) + } + } + account.unconfirmedPretty = 0 + if (account.unconfirmed > 0) { + try { + account.unconfirmedPretty = BlocksoftPrettyNumbers.setCurrencyCode(currencyCode).makePretty(account.unconfirmed, 'updateAccountListDaemon.unconfirmed') + } catch (e) { + Log.errDaemon('UpdateAccountListDaemon error on account.unconfirmed makePretty ' + e.message) + } + } + account.balanceStakedPretty = 0 + if (account.balanceStaked > 0) { + try { + account.balanceStakedPretty = BlocksoftPrettyNumbers.setCurrencyCode(currencyCode).makePretty(account.balanceStaked, 'updateAccountListDaemon.balanceStaked') + } catch (e) { + Log.errDaemon('UpdateAccountListDaemon error on account.balanceStaked makePretty ' + e.message) + } + } + account.balanceTotalPretty = 0 + if (account.balanceRaw > 0) { + try { + account.balanceTotalPretty = BlocksoftPrettyNumbers.setCurrencyCode(currencyCode).makePretty(account.balanceRaw, 'updateAccountListDaemon.balanceRaw') + } catch (e) { + Log.errDaemon('UpdateAccountListDaemon error on account.balanceRaw makePretty ' + e.message) + } + } + if (rate > 0) { + account.basicCurrencyBalanceNorm = account.balancePretty + account.basicCurrencyUnconfirmedNorm = account.unconfirmedPretty + account.basicCurrencyBalanceTotalNorm = account.balanceTotalPretty + if (rate !== 1) { + account.basicCurrencyBalanceNorm = BlocksoftUtils.mul(account.balancePretty, rate) + account.basicCurrencyUnconfirmedNorm = BlocksoftUtils.mul(account.unconfirmedPretty, rate) + account.basicCurrencyBalanceTotalNorm = BlocksoftUtils.mul(account.balanceTotalPretty, rate) + } + account.basicCurrencyBalance = BlocksoftPrettyNumbers.makeCut(account.basicCurrencyBalanceNorm, 2).separated + account.basicCurrencyUnconfirmed = BlocksoftPrettyNumbers.makeCut(account.basicCurrencyUnconfirmedNorm, 2).separated + account.basicCurrencyBalanceTotal = BlocksoftPrettyNumbers.makeCut(account.basicCurrencyBalanceTotalNorm, 2).separated + + let str = '' + str += account.balancePretty + ' ' + account.currencyCode + if (account.address) { + str += ' ' + account.address + } + str += ' ' + account.basicCurrencyBalance + ' ' + account.basicCurrencyCode + ', ' + + const mask = Number(tmpCurrency.isHidden || 0).toString(2).split('').reverse() // split to binary + let maskedHidden = true + if (accountWallet) { + if (typeof mask[accountWallet?.walletNumber] === 'undefined') { + maskedHidden = mask.length === 1 ? (mask[mask.length - 1] === '1') : false + } else { + maskedHidden = mask[accountWallet?.walletNumber] === '1' + } + } + + if (!maskedHidden) { + + if (account.basicCurrencyBalanceNorm > 0) { + try { + DaemonCache.CACHE_WALLET_SUMS[tmpWalletHash].balanceAddingLog += str + DaemonCache.CACHE_WALLET_SUMS[tmpWalletHash].balance = BlocksoftUtils.add(DaemonCache.CACHE_WALLET_SUMS[tmpWalletHash].balance, account.basicCurrencyBalanceNorm) + DaemonCache.CACHE_WALLET_TOTAL.balance = BlocksoftUtils.add(DaemonCache.CACHE_WALLET_TOTAL.balance, account.basicCurrencyBalanceNorm) + } catch (e) { + Log.errDaemon('UpdateAccountListDaemon error on sum ' + e.message) + } + } else { + DaemonCache.CACHE_WALLET_SUMS[tmpWalletHash].balanceAddingLogZero += str + } + + + if (account.basicCurrencyUnconfirmedNorm > 0) { + try { + DaemonCache.CACHE_WALLET_SUMS[tmpWalletHash].unconfirmed = BlocksoftUtils.add(DaemonCache.CACHE_WALLET_SUMS[tmpWalletHash].unconfirmed, account.basicCurrencyUnconfirmedNorm) + DaemonCache.CACHE_WALLET_TOTAL.unconfirmed = BlocksoftUtils.add(DaemonCache.CACHE_WALLET_TOTAL.unconfirmed, account.basicCurrencyUnconfirmedNorm) + } catch (e) { + Log.errDaemon('UpdateAccountListDaemon error on sum2 ' + e.message) + } + } + + } else { + if (account.basicCurrencyBalanceNorm > 0) { + DaemonCache.CACHE_WALLET_SUMS[tmpWalletHash].balanceAddingLogHidden += str + } else { + DaemonCache.CACHE_WALLET_SUMS[tmpWalletHash].balanceAddingLogZero += str + } + } + } else { + account.basicCurrencyBalance = 0 + account.basicCurrencyUnconfirmed = 0 + account.basicCurrencyBalanceNorm = 0 + account.basicCurrencyUnconfirmedNorm = 0 + account.basicCurrencyBalanceTotal = 0 + account.basicCurrencyBalanceTotalNorm = 0 + } + + DaemonCache.CACHE_ALL_ACCOUNTS[tmpWalletHash][currencyCode] = account + DaemonCache.CACHE_WALLET_SUMS[tmpWalletHash].balance = BlocksoftPrettyNumbers.makeCut(DaemonCache.CACHE_WALLET_SUMS[tmpWalletHash].balance, 2).justCutted + DaemonCache.CACHE_WALLET_SUMS[tmpWalletHash].unconfirmed = BlocksoftPrettyNumbers.makeCut(DaemonCache.CACHE_WALLET_SUMS[tmpWalletHash].unconfirmed, 2).justCutted + } + DaemonCache.CACHE_WALLET_TOTAL.balance = BlocksoftPrettyNumbers.makeCut(DaemonCache.CACHE_WALLET_TOTAL.balance, 2).justCutted + DaemonCache.CACHE_WALLET_TOTAL.unconfirmed = BlocksoftPrettyNumbers.makeCut(DaemonCache.CACHE_WALLET_TOTAL.unconfirmed, 2).justCutted + } + + for (const tmpWalletHash in DaemonCache.CACHE_WALLET_SUMS) { + const cacheBalanceString = DaemonCache.CACHE_WALLET_SUMS[tmpWalletHash].balanceAddingLog + DaemonCache.CACHE_WALLET_SUMS[tmpWalletHash].balanceAddingLogHidden + MarketingEvent.setBalance(tmpWalletHash, 'TOTAL', cacheBalanceString, { + totalBalance : DaemonCache.CACHE_WALLET_SUMS[tmpWalletHash].balance, + totalBalanceString : DaemonCache.CACHE_WALLET_SUMS[tmpWalletHash].balanceAddingLog, + hiddenBalanceString : DaemonCache.CACHE_WALLET_SUMS[tmpWalletHash].balanceAddingLogHidden, + basicCurrencyCode, + walletHash : tmpWalletHash }) + } + + if (typeof DaemonCache.CACHE_ALL_ACCOUNTS !== 'undefined') { + store.dispatch({ + type: 'SET_ACCOUNT_LIST', + accountList: DaemonCache.CACHE_ALL_ACCOUNTS + }) + } + walletActions.setWalletsGeneralData(DaemonCache.CACHE_WALLET_TOTAL.balance, DaemonCache.CACHE_WALLET_TOTAL.basicCurrencySymbol) + + try { + if (selectedAccount && typeof selectedAccount !== 'undefined' + && typeof selectedAccount.currencyCode !== 'undefined' + && typeof selectedAccount.walletHash !== 'undefined' + && selectedAccount.currencyCode !== 'NFT' + && selectedAccount.currencyCode !== 'CASHBACK' + ) { + if (typeof DaemonCache.CACHE_ALL_ACCOUNTS[selectedAccount.walletHash][selectedAccount.currencyCode] === 'undefined') { + Log.daemon('UpdateAccountListDaemon error when no selected ' + selectedAccount.currencyCode) + } else { + if (DaemonCache.CACHE_ALL_ACCOUNTS[selectedAccount.walletHash][selectedAccount.currencyCode].balance !== selectedAccount.balance) { + await setSelectedAccount('UpdateAccountListDaemon') + await setSelectedAccountTransactions('UpdateAccountListDaemon') + } + } + } + } catch (e) { + Log.errDaemon('UpdateAccountListDaemon error on setSelected ' + e.message) + } + + // FIXME: remove it, temporary solution to fix UI changing on home screen when user change local currency + store.dispatch({ + type: 'SET_SELECTED_BASIC_CURRENCY', + selectedBasicCurrency: currentStore.mainStore.selectedBasicCurrency + }) + + this._canUpdate = true + + } catch (e) { + this._canUpdate = true + Log.errDaemon('UpdateAccountListDaemon error ' + e.message) + } + return DaemonCache.CACHE_ALL_ACCOUNTS + } + +} + +export default new UpdateAccountListDaemon() diff --git a/src/database/Database.ts b/src/database/Database.ts index a8a7b8db1..a49005c42 100644 --- a/src/database/Database.ts +++ b/src/database/Database.ts @@ -3,6 +3,7 @@ import { DatabaseTable } from '@appTypes'; import { database } from './main'; import { Q, Database as WDB } from '@nozbe/watermelondb'; import { Clause } from '@nozbe/watermelondb/QueryDescription'; +import LocalStorage from '@nozbe/watermelondb/Database/LocalStorage'; class Database { private db: WDB | undefined; @@ -11,6 +12,11 @@ class Database { this.init(); } + get localStorage(): LocalStorage { + if (!this.db) this.init(); + return this.db!.localStorage; + } + private async reset() { if (!this.db) this.init(); await this.db?.write(async () => { diff --git a/src/database/main.ts b/src/database/main.ts index a39889ff1..b8922a884 100644 --- a/src/database/main.ts +++ b/src/database/main.ts @@ -4,6 +4,8 @@ import { Database } from '@nozbe/watermelondb'; import { WalletDBModel } from './models'; import { schema } from './schemas'; import { TransactionScannersTmpDBModel } from './models/transaction-scanners-tmp'; +import { WalletPubDBModel } from './models/wallet-pub'; +import { AccountDBModel } from './models/account'; const adapter = new SQLiteAdapter({ schema, @@ -15,5 +17,10 @@ const adapter = new SQLiteAdapter({ export const database = new Database({ adapter, - modelClasses: [WalletDBModel, TransactionScannersTmpDBModel] + modelClasses: [ + AccountDBModel, + TransactionScannersTmpDBModel, + WalletDBModel, + WalletPubDBModel + ] }); diff --git a/src/database/models/account.ts b/src/database/models/account.ts new file mode 100644 index 000000000..6e1710c91 --- /dev/null +++ b/src/database/models/account.ts @@ -0,0 +1,44 @@ +/* eslint-disable camelcase */ +import { DatabaseTable } from '@appTypes'; +import { Model } from '@nozbe/watermelondb'; +import { text, field, relation } from '@nozbe/watermelondb/decorators'; +import { WalletDBModel } from './wallet'; + +export class AccountDBModel extends Model { + static table = DatabaseTable.Accounts; + + // define fields + + // @ts-ignore + @text('address') address: string; + // @ts-ignore + @relation(DatabaseTable.Wallets, 'hash') hash: WalletDBModel; + // @ts-ignore + @text('name') name: string; + // @ts-ignore + @text('derivation_path') derivationPath: string; + // @ts-ignore + @field('derivation_index') derivationIndex: number; + // @ts-ignore + @text('derivation_type') derivationType: string; + // @ts-ignore + @field('already_shown') alreadyShown: number; + // @ts-ignore + @field('wallet_pub_id') walletPubId: number; + // @ts-ignore + @field('status') status: number; + // @ts-ignore + @field('is_main') isMain: number; + // @ts-ignore + @field('is_main') isMain: number; + // @ts-ignore + @field('transactions_scan_time') transactionsScanTime: number; + // @ts-ignore + @text('transactions_scan_log') transactionsScanLog: string; + // @ts-ignore + @text('transactions_scan_error') transactionsScanError: string; + // @ts-ignore + @text('changes_log') changesLog: string; + // @ts-ignore + @text('currency_code') currencyCode: string; +} diff --git a/src/database/models/wallet-pub.ts b/src/database/models/wallet-pub.ts new file mode 100644 index 000000000..197b136fe --- /dev/null +++ b/src/database/models/wallet-pub.ts @@ -0,0 +1,46 @@ +/* eslint-disable camelcase */ +import { DatabaseTable } from '@appTypes'; +import { Model } from '@nozbe/watermelondb'; +import { text, field, relation } from '@nozbe/watermelondb/decorators'; +import { WalletDBModel } from './wallet'; + +export class WalletPubDBModel extends Model { + static table = DatabaseTable.WalletPub; + + // define fields + + // @ts-ignore + @text('currency_code') currency_code: string; + // @ts-ignore + @relation(DatabaseTable.Wallets, 'hash') hash: WalletDBModel; + // @ts-ignore + @text('wallet_pub_type') walletPubType: string; + // @ts-ignore + @text('wallet_pub_value') walletPubValue: string; + // @ts-ignore + @field('wallet_pub_last_index') walletPubLastIndex: number; + // @ts-ignore + @field('status') status: number; + // @ts-ignore + @field('balance_fix') balanceFix: number; + // @ts-ignore + @text('balance_txt') balanceTxt: string; + // @ts-ignore + @field('unconfirmed_fix') unconfirmedFix: number; + // @ts-ignore + @text('unconfirmed_txt') unconfirmedTxt: string; + // @ts-ignore + @text('balance_provider') balanceProvider: number; + // @ts-ignore + @field('balance_scan_time') balanceScanTime: number; + // @ts-ignore + @text('balance_scan_error') balanceScanError: string; + // @ts-ignore + @text('balance_scan_log') balanceScanLog: string; + // @ts-ignore + @text('balance_scan_block') balanceScanBlock: string; + // @ts-ignore + @field('transactions_scan_time') transactionsScanTime: number; + // @ts-ignore + @text('transactions_scan_log') transactionsScanLog: string; +} diff --git a/src/database/schemas/account.ts b/src/database/schemas/account.ts new file mode 100644 index 000000000..f4063b53c --- /dev/null +++ b/src/database/schemas/account.ts @@ -0,0 +1,69 @@ +import { DatabaseTable } from '@appTypes'; +import { tableSchema } from '@nozbe/watermelondb'; + +export const AccountsTable = tableSchema({ + name: DatabaseTable.Accounts, + columns: [ + { + name: 'address', + type: 'string' + }, + { + name: 'name', + type: 'string' + }, + { + name: 'derivation_path', + type: 'string' + }, + { + name: 'derivation_index', + type: 'number' + }, + { + name: 'derivation_type', + type: 'string' + }, + { + name: 'already_shown', + type: 'number' + }, + { + name: 'wallet_pub_id', + type: 'number' + }, + + { + name: 'status', + type: 'number' + }, + { + name: 'is_main', // default 1 + type: 'number' + }, + { + name: 'transactions_scan_time', + type: 'number' + }, + { + name: 'transactions_scan_error', + type: 'string' + }, + { + name: 'transactions_scan_log', + type: 'string' + }, + { + name: 'changes_log', + type: 'string' + }, + { + name: 'currency_code', + type: 'string' + }, + { + name: 'hash', + type: 'string' + } + ] +}); diff --git a/src/database/schemas/index.ts b/src/database/schemas/index.ts index 29f4ade1a..8e3bda3dd 100644 --- a/src/database/schemas/index.ts +++ b/src/database/schemas/index.ts @@ -1,8 +1,15 @@ import { appSchema } from '@nozbe/watermelondb'; import { WalletTable } from './wallet'; import { TransactionScannersTmpTable } from './transaction-scanners-tmp'; +import { WalletPubTable } from './wallet-pub'; +import { AccountsTable } from './account'; export const schema = appSchema({ version: 1, - tables: [WalletTable, TransactionScannersTmpTable] + tables: [ + AccountsTable, + TransactionScannersTmpTable, + WalletTable, + WalletPubTable + ] }); diff --git a/src/database/schemas/wallet-pub.ts b/src/database/schemas/wallet-pub.ts new file mode 100644 index 000000000..78c1f44fb --- /dev/null +++ b/src/database/schemas/wallet-pub.ts @@ -0,0 +1,77 @@ +import { DatabaseTable } from '@appTypes'; +import { tableSchema } from '@nozbe/watermelondb'; + +export const WalletPubTable = tableSchema({ + name: DatabaseTable.WalletPub, + columns: [ + { + name: 'currency_code', + type: 'string' + }, + { + name: 'wallet_hash', + type: 'string' + }, + { + name: 'wallet_pub_type', + type: 'string' + }, + { + name: 'wallet_pub_value', + type: 'string' + }, + { + name: 'wallet_pub_last_index', + type: 'number' + }, + { + name: 'status', + type: 'number' + }, + { + name: 'balance_fix', + type: 'number' + }, + { + name: 'balance_txt', + type: 'string' + }, + { + name: 'unconfirmed_fix', + type: 'number' + }, + { + name: 'unconfirmed_txt', + type: 'string' + }, + { + name: 'balance_provider', + type: 'string' + }, + { + name: 'balance_scan_time', + type: 'number' + }, + { + name: 'balance_scan_error', + type: 'string' + }, + { + name: 'balance_scan_log', + type: 'string' + }, + { + name: 'balance_scan_block', + type: 'string' + }, + { + name: 'transactions_scan_time', + type: 'number' + }, + { + name: 'transactions_scan_log', + type: 'string' + }, + { name: 'hash', type: 'string' } + ] +}); From be345545ceabc402a90c9e869c9d75c2a052165a Mon Sep 17 00:00:00 2001 From: JavidHaji-zada Date: Tue, 1 Aug 2023 20:27:30 +0400 Subject: [PATCH 019/509] converted btc_test and btg --- .../btc_test/BtcTestTransferProcessor.ts | 76 +++++++++++-------- .../btc_test/providers/BtcTestSendProvider.ts | 25 +----- .../providers/BtcTestUnspentsProvider.ts | 10 +-- crypto/blockchains/btg/BtgScannerProcessor.js | 23 +++--- .../blockchains/btg/BtgTransferProcessor.ts | 51 +++++++------ 5 files changed, 91 insertions(+), 94 deletions(-) diff --git a/crypto/blockchains/btc_test/BtcTestTransferProcessor.ts b/crypto/blockchains/btc_test/BtcTestTransferProcessor.ts index 551ed4d15..63e0f62ec 100644 --- a/crypto/blockchains/btc_test/BtcTestTransferProcessor.ts +++ b/crypto/blockchains/btc_test/BtcTestTransferProcessor.ts @@ -1,39 +1,53 @@ /** * @version 0.52 */ -import { BlocksoftBlockchainTypes } from '@crypto/blockchains/BlocksoftBlockchainTypes' -import DogeTransferProcessor from '@crypto/blockchains/doge/DogeTransferProcessor' -import BtcTestUnspentsProvider from '@crypto/blockchains/btc_test/providers/BtcTestUnspentsProvider' -import BtcTestSendProvider from '@crypto/blockchains/btc_test/providers/BtcTestSendProvider' -import DogeTxInputsOutputs from '@crypto/blockchains/doge/tx/DogeTxInputsOutputs' -import DogeTxBuilder from '@crypto/blockchains/doge/tx/DogeTxBuilder' +import { AirDAOBlockchainTypes } from '@crypto/blockchains/AirDAOBlockchainTypes'; +import DogeTransferProcessor from '@crypto/blockchains/doge/DogeTransferProcessor'; +import BtcTestUnspentsProvider from '@crypto/blockchains/btc_test/providers/BtcTestUnspentsProvider'; +import BtcTestSendProvider from '@crypto/blockchains/btc_test/providers/BtcTestSendProvider'; +import DogeTxInputsOutputs from '@crypto/blockchains/doge/tx/DogeTxInputsOutputs'; +import DogeTxBuilder from '@crypto/blockchains/doge/tx/DogeTxBuilder'; -export default class BtcTestTransferProcessor extends DogeTransferProcessor implements BlocksoftBlockchainTypes.TransferProcessor { +export default class BtcTestTransferProcessor + extends DogeTransferProcessor + implements AirDAOBlockchainTypes.TransferProcessor +{ + _trezorServerCode = 'NONE'; - _trezorServerCode = 'NONE' + _builderSettings: AirDAOBlockchainTypes.BuilderSettings = { + minOutputDustReadable: 0.000005, + minChangeDustReadable: 0.00001, + feeMaxForByteSatoshi: 10000, // for tx builder + feeMaxAutoReadable2: 0.2, // for fee calc, + feeMaxAutoReadable6: 0.1, // for fee calc + feeMaxAutoReadable12: 0.05, // for fee calc + changeTogether: true, + minRbfStepSatoshi: 10, + minSpeedUpMulti: 1.5 + }; - _builderSettings: BlocksoftBlockchainTypes.BuilderSettings = { - minOutputDustReadable: 0.000005, - minChangeDustReadable: 0.00001, - feeMaxForByteSatoshi: 10000, // for tx builder - feeMaxAutoReadable2: 0.2, // for fee calc, - feeMaxAutoReadable6: 0.1, // for fee calc - feeMaxAutoReadable12: 0.05, // for fee calc - changeTogether: true, - minRbfStepSatoshi: 10, - minSpeedUpMulti : 1.5 - } + canRBF( + data: AirDAOBlockchainTypes.DbAccount, + transaction: AirDAOBlockchainTypes.DbTransaction + ): boolean { + return false; + } - canRBF(data: BlocksoftBlockchainTypes.DbAccount, transaction: BlocksoftBlockchainTypes.DbTransaction): boolean { - return false - } - - _initProviders() { - if (this._initedProviders) return false - this.unspentsProvider = new BtcTestUnspentsProvider(this._settings, this._trezorServerCode) - this.sendProvider = new BtcTestSendProvider(this._settings, this._trezorServerCode) - this.txPrepareInputsOutputs = new DogeTxInputsOutputs(this._settings, this._builderSettings) - this.txBuilder = new DogeTxBuilder(this._settings, this._builderSettings) - this._initedProviders = true - } + _initProviders() { + if (this._initedProviders) return false; + this.unspentsProvider = new BtcTestUnspentsProvider( + this._settings, + this._trezorServerCode + ); + this.sendProvider = new BtcTestSendProvider( + this._settings, + this._trezorServerCode + ); + this.txPrepareInputsOutputs = new DogeTxInputsOutputs( + this._settings, + this._builderSettings + ); + this.txBuilder = new DogeTxBuilder(this._settings, this._builderSettings); + this._initedProviders = true; + } } diff --git a/crypto/blockchains/btc_test/providers/BtcTestSendProvider.ts b/crypto/blockchains/btc_test/providers/BtcTestSendProvider.ts index b6d140b1c..33ff77409 100644 --- a/crypto/blockchains/btc_test/providers/BtcTestSendProvider.ts +++ b/crypto/blockchains/btc_test/providers/BtcTestSendProvider.ts @@ -1,18 +1,17 @@ /** * @version 0.52 */ -import { BlocksoftBlockchainTypes } from '@crypto/blockchains/BlocksoftBlockchainTypes'; +import { AirDAOBlockchainTypes } from '@crypto/blockchains/AirDAOBlockchainTypes'; import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; import DogeSendProvider from '@crypto/blockchains/doge/providers/DogeSendProvider'; -import config from '@app/config/config'; const API_URL = 'https://api.blockchair.com/bitcoin/testnet/push/transaction'; export default class BtcTestSendProvider extends DogeSendProvider - implements BlocksoftBlockchainTypes.SendProvider + implements AirDAOBlockchainTypes.SendProvider { async sendTx( hex: string, @@ -34,23 +33,10 @@ export default class BtcTestSendProvider try { res = await BlocksoftAxios.post(API_URL, { data: hex }); } catch (e) { - if (config.debug.cryptoErrors) { - console.log( - this._settings.currencyCode + ' BtcTestSendProvider.sendTx error ', - e - ); - } try { logData.error = e.message; // await this._checkError(hex, subtitle, txRBF, logData) } catch (e2) { - if (config.debug.cryptoErrors) { - console.log( - this._settings.currencyCode + - ' BtcTestSendProvider.send proxy error errorTx ' + - e.message - ); - } await BlocksoftCryptoLog.log( this._settings.currencyCode + ' BtcTestSendProvider.send proxy error errorTx ' + @@ -100,13 +86,6 @@ export default class BtcTestSendProvider } } if (txid === '') { - if (config.debug.cryptoErrors) { - // @ts-ignore - console.log( - this._settings.currencyCode + ' BtcTestSendProvider.send no txid', - res.data - ); - } throw new Error('SERVER_RESPONSE_NOT_CONNECTED'); } diff --git a/crypto/blockchains/btc_test/providers/BtcTestUnspentsProvider.ts b/crypto/blockchains/btc_test/providers/BtcTestUnspentsProvider.ts index 8a9ae928f..58aed01da 100644 --- a/crypto/blockchains/btc_test/providers/BtcTestUnspentsProvider.ts +++ b/crypto/blockchains/btc_test/providers/BtcTestUnspentsProvider.ts @@ -1,7 +1,7 @@ /** * @version 0.52 */ -import { BlocksoftBlockchainTypes } from '@crypto/blockchains/BlocksoftBlockchainTypes'; +import { AirDAOBlockchainTypes } from '@crypto/blockchains/AirDAOBlockchainTypes'; import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; @@ -9,12 +9,12 @@ import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; const API_PATH = 'https://blockstream.info/testnet/api/'; export default class BtcTestUnspentsProvider - implements BlocksoftBlockchainTypes.UnspentsProvider + implements AirDAOBlockchainTypes.UnspentsProvider { - protected _settings: BlocksoftBlockchainTypes.CurrencySettings; + protected _settings: AirDAOBlockchainTypes.CurrencySettings; constructor( - settings: BlocksoftBlockchainTypes.CurrencySettings, + settings: AirDAOBlockchainTypes.CurrencySettings, serverCode: string ) { this._settings = settings; @@ -26,7 +26,7 @@ export default class BtcTestUnspentsProvider */ async getUnspents( address: string - ): Promise { + ): Promise { // @ts-ignore BlocksoftCryptoLog.log( this._settings.currencyCode + diff --git a/crypto/blockchains/btg/BtgScannerProcessor.js b/crypto/blockchains/btg/BtgScannerProcessor.js index fda35b4f5..47a67f200 100644 --- a/crypto/blockchains/btg/BtgScannerProcessor.js +++ b/crypto/blockchains/btg/BtgScannerProcessor.js @@ -3,19 +3,18 @@ * https://github.com/trezor/blockbook/blob/master/docs/api.md */ -import DogeScannerProcessor from '../doge/DogeScannerProcessor' +import DogeScannerProcessor from '../doge/DogeScannerProcessor'; export default class BtgScannerProcessor extends DogeScannerProcessor { + /** + * @type {number} + * @private + */ + _blocksToConfirm = 10; - /** - * @type {number} - * @private - */ - _blocksToConfirm = 10 - - /** - * @type {string} - * @private - */ - _trezorServerCode = 'BTG_TREZOR_SERVER' + /** + * @type {string} + * @private + */ + _trezorServerCode = 'BTG_TREZOR_SERVER'; } diff --git a/crypto/blockchains/btg/BtgTransferProcessor.ts b/crypto/blockchains/btg/BtgTransferProcessor.ts index fd7ffb5e8..0cc55b60e 100644 --- a/crypto/blockchains/btg/BtgTransferProcessor.ts +++ b/crypto/blockchains/btg/BtgTransferProcessor.ts @@ -2,30 +2,35 @@ * @version 0.20 */ -import DogeTransferProcessor from '../doge/DogeTransferProcessor' -import { BlocksoftBlockchainTypes } from '../BlocksoftBlockchainTypes' +import DogeTransferProcessor from '../doge/DogeTransferProcessor'; +import { AirDAOBlockchainTypes } from '../AirDAOBlockchainTypes'; -export default class BtgTransferProcessor extends DogeTransferProcessor implements BlocksoftBlockchainTypes.TransferProcessor { +export default class BtgTransferProcessor + extends DogeTransferProcessor + implements AirDAOBlockchainTypes.TransferProcessor +{ + /** + * @type {string} + * @private + */ + _trezorServerCode = 'BTG_TREZOR_SERVER'; - /** - * @type {string} - * @private - */ - _trezorServerCode = 'BTG_TREZOR_SERVER' + _builderSettings: AirDAOBlockchainTypes.BuilderSettings = { + minOutputDustReadable: 0.00001, + minChangeDustReadable: 0.00001, + feeMaxForByteSatoshi: 10000, // for tx builder + feeMaxAutoReadable2: 1, // for fee calc, + feeMaxAutoReadable6: 0.5, // for fee calc + feeMaxAutoReadable12: 0.2, // for fee calc + changeTogether: true, + minRbfStepSatoshi: 10, + minSpeedUpMulti: 1.5 + }; - _builderSettings: BlocksoftBlockchainTypes.BuilderSettings = { - minOutputDustReadable: 0.00001, - minChangeDustReadable: 0.00001, - feeMaxForByteSatoshi: 10000, // for tx builder - feeMaxAutoReadable2: 1, // for fee calc, - feeMaxAutoReadable6: 0.5, // for fee calc - feeMaxAutoReadable12: 0.2, // for fee calc - changeTogether: true, - minRbfStepSatoshi: 10, - minSpeedUpMulti : 1.5 - } - - canRBF(data: BlocksoftBlockchainTypes.DbAccount, transaction: BlocksoftBlockchainTypes.DbTransaction): boolean { - return false - } + canRBF( + data: AirDAOBlockchainTypes.DbAccount, + transaction: AirDAOBlockchainTypes.DbTransaction + ): boolean { + return false; + } } From 6474af72eb205099bb416fd1522900c3c76481c7 Mon Sep 17 00:00:00 2001 From: illiaa Date: Tue, 1 Aug 2023 19:56:05 +0300 Subject: [PATCH 020/509] rewrite blockchains folders --- ...nerProcessor.js => OneScannerProcessor.ts} | 0 ...orErc20.js => OneScannerProcessorErc20.ts} | 0 .../one/ext/{OneUtils.js => OneUtils.ts} | 478 ++-- .../one/stores/{OneTmpDS.js => OneTmpDS.ts} | 0 ...essProcessor.js => SolAddressProcessor.ts} | 0 ...nerProcessor.js => SolScannerProcessor.ts} | 0 ...cessorSpl.js => SolScannerProcessorSpl.ts} | 188 +- ...TokenProcessor.js => SolTokenProcessor.ts} | 0 crypto/blockchains/sol/ext/SolInstructions.js | 79 - crypto/blockchains/sol/ext/SolInstructions.ts | 79 + .../{SolStakeUtils.js => SolStakeUtils.ts} | 0 .../sol/ext/{SolUtils.js => SolUtils.ts} | 478 ++-- crypto/blockchains/sol/ext/validators.js | 243 -- crypto/blockchains/sol/ext/validators.ts | 266 ++ .../sol/stores/{SolTmpDS.js => SolTmpDS.ts} | 0 ...essProcessor.js => TrxAddressProcessor.ts} | 0 ...nerProcessor.js => TrxScannerProcessor.ts} | 125 +- crypto/blockchains/trx/TrxTokenProcessor.js | 55 - crypto/blockchains/trx/TrxTokenProcessor.ts | 75 + .../blockchains/trx/TrxTransferProcessor.ts | 43 +- ...InfoProvider.js => TrxNodeInfoProvider.ts} | 0 ...Provider.js => TrxTransactionsProvider.ts} | 0 ...der.js => TrxTransactionsTrc20Provider.ts} | 0 ...gridProvider.js => TrxTrongridProvider.ts} | 0 ...scanProvider.js => TrxTronscanProvider.ts} | 0 crypto/blockchains/trx/dict/swaps.js | 12 - crypto/blockchains/trx/dict/swaps.ts | 12 + .../{TronStakeUtils.js => TronStakeUtils.ts} | 344 +-- .../trx/ext/{TronUtils.js => TronUtils.ts} | 0 ...erProcessor.js => UsdtScannerProcessor.ts} | 101 +- .../blockchains/usdt/UsdtTransferProcessor.ts | 4 +- crypto/blockchains/usdt/tx/UsdtTxBuilder.ts | 3 +- .../usdt/tx/UsdtTxInputsOutputs.ts | 14 +- ...nerProcessor.js => VetScannerProcessor.ts} | 56 +- .../blockchains/vet/VetTransferProcessor.ts | 13 +- .../waves/WavesAddressProcessor.js | 27 - .../waves/WavesAddressProcessor.ts | 35 + .../waves/WavesScannerProcessor.js | 184 -- .../waves/WavesScannerProcessor.ts | 147 ++ ...Erc20.js => WavesScannerProcessorErc20.ts} | 284 ++- .../waves/WavesTransferProcessor.ts | 8 +- ...ovider.js => WavesTransactionsProvider.ts} | 30 +- crypto/blockchains/xlm/XlmAddressProcessor.js | 31 - crypto/blockchains/xlm/XlmAddressProcessor.ts | 31 + ...nerProcessor.js => XlmScannerProcessor.ts} | 68 +- .../blockchains/xlm/XlmTransferProcessor.ts | 40 +- .../xlm/basic/XlmTxSendProvider.ts | 11 +- crypto/blockchains/xlm/ext/XlmDerivePath.js | 49 - crypto/blockchains/xlm/ext/XlmDerivePath.ts | 58 + crypto/blockchains/xmr/XmrAddressProcessor.ts | 209 ++ crypto/blockchains/xmr/XmrScannerProcessor.ts | 412 +++ crypto/blockchains/xmr/XmrSecretsProcessor.js | 66 - crypto/blockchains/xmr/XmrSecretsProcessor.ts | 76 + .../blockchains/xmr/XmrTransferProcessor.ts | 48 + crypto/blockchains/xmr/ext/MoneroDict.js | 1634 ------------ crypto/blockchains/xmr/ext/MoneroDict.ts | 1634 ++++++++++++ crypto/blockchains/xmr/ext/MoneroMnemonic.js | 68 - crypto/blockchains/xmr/ext/MoneroMnemonic.ts | 80 + crypto/blockchains/xmr/ext/MoneroUtils.js | 168 -- crypto/blockchains/xmr/ext/MoneroUtils.ts | 216 ++ .../xmr/ext/MoneroUtilsParser.oldAndroid.js | 28 - .../xmr/ext/MoneroUtilsParser.oldAndroid.ts | 39 + ...eroUtilsParser.js => MoneroUtilsParser.ts} | 109 +- .../blockchains/xmr/ext/vendor/biginteger.js | 2242 ++++++++--------- ...{XmrSendProvider.js => XmrSendProvider.ts} | 18 +- .../xmr/providers/XmrUnspentsProvider.ts | 137 + crypto/blockchains/xrp/XrpAddressProcessor.js | 34 - crypto/blockchains/xrp/XrpAddressProcessor.ts | 44 + crypto/blockchains/xrp/XrpScannerProcessor.ts | 101 + .../blockchains/xrp/XrpTransferProcessor.ts | 21 + .../xrp/basic/XrpDataRippleProvider.ts | 230 ++ .../xrp/basic/XrpDataScanProvider.ts | 325 +++ .../xrp/basic/XrpTxSendProvider.ts | 27 + crypto/blockchains/xrp/basic/XrpTxUtils.ts | 12 +- crypto/blockchains/xrp/stores/XrpTmpDS.js | 67 - crypto/blockchains/xrp/stores/XrpTmpDS.ts | 72 + .../xvg/basic/XvgFindAddressFunction.ts | 98 + .../xvg/providers/XvgSendProvider.ts | 23 + .../xvg/providers/XvgUnspentsProvider.ts | 16 + crypto/blockchains/xvg/stores/XvgTmpDS.js | 49 - crypto/blockchains/xvg/stores/XvgTmpDS.ts | 48 + crypto/common/BlocksoftUtils.js | 769 +++--- crypto/common/ext/networks-constants.js | 175 +- package.json | 10 +- src/constants/config.ts | 6 +- src/lib/AirDAOKeys.ts | 260 +- src/lib/BlocksoftDispatcher.js | 491 ++-- src/lib/BlocksoftKeys.js | 9 +- src/lib/common/ext/network-constants.ts | 117 + yarn.lock | 147 +- 90 files changed, 8273 insertions(+), 5733 deletions(-) rename crypto/blockchains/one/{OneScannerProcessor.js => OneScannerProcessor.ts} (100%) rename crypto/blockchains/one/{OneScannerProcessorErc20.js => OneScannerProcessorErc20.ts} (100%) rename crypto/blockchains/one/ext/{OneUtils.js => OneUtils.ts} (96%) rename crypto/blockchains/one/stores/{OneTmpDS.js => OneTmpDS.ts} (100%) rename crypto/blockchains/sol/{SolAddressProcessor.js => SolAddressProcessor.ts} (100%) rename crypto/blockchains/sol/{SolScannerProcessor.js => SolScannerProcessor.ts} (100%) rename crypto/blockchains/sol/{SolScannerProcessorSpl.js => SolScannerProcessorSpl.ts} (97%) rename crypto/blockchains/sol/{SolTokenProcessor.js => SolTokenProcessor.ts} (100%) delete mode 100644 crypto/blockchains/sol/ext/SolInstructions.js create mode 100644 crypto/blockchains/sol/ext/SolInstructions.ts rename crypto/blockchains/sol/ext/{SolStakeUtils.js => SolStakeUtils.ts} (100%) rename crypto/blockchains/sol/ext/{SolUtils.js => SolUtils.ts} (95%) delete mode 100644 crypto/blockchains/sol/ext/validators.js create mode 100644 crypto/blockchains/sol/ext/validators.ts rename crypto/blockchains/sol/stores/{SolTmpDS.js => SolTmpDS.ts} (100%) rename crypto/blockchains/trx/{TrxAddressProcessor.js => TrxAddressProcessor.ts} (100%) rename crypto/blockchains/trx/{TrxScannerProcessor.js => TrxScannerProcessor.ts} (85%) delete mode 100644 crypto/blockchains/trx/TrxTokenProcessor.js create mode 100644 crypto/blockchains/trx/TrxTokenProcessor.ts rename crypto/blockchains/trx/basic/{TrxNodeInfoProvider.js => TrxNodeInfoProvider.ts} (100%) rename crypto/blockchains/trx/basic/{TrxTransactionsProvider.js => TrxTransactionsProvider.ts} (100%) rename crypto/blockchains/trx/basic/{TrxTransactionsTrc20Provider.js => TrxTransactionsTrc20Provider.ts} (100%) rename crypto/blockchains/trx/basic/{TrxTrongridProvider.js => TrxTrongridProvider.ts} (100%) rename crypto/blockchains/trx/basic/{TrxTronscanProvider.js => TrxTronscanProvider.ts} (100%) delete mode 100644 crypto/blockchains/trx/dict/swaps.js create mode 100644 crypto/blockchains/trx/dict/swaps.ts rename crypto/blockchains/trx/ext/{TronStakeUtils.js => TronStakeUtils.ts} (96%) rename crypto/blockchains/trx/ext/{TronUtils.js => TronUtils.ts} (100%) rename crypto/blockchains/usdt/{UsdtScannerProcessor.js => UsdtScannerProcessor.ts} (74%) rename crypto/blockchains/vet/{VetScannerProcessor.js => VetScannerProcessor.ts} (90%) delete mode 100644 crypto/blockchains/waves/WavesAddressProcessor.js create mode 100644 crypto/blockchains/waves/WavesAddressProcessor.ts delete mode 100644 crypto/blockchains/waves/WavesScannerProcessor.js create mode 100644 crypto/blockchains/waves/WavesScannerProcessor.ts rename crypto/blockchains/waves/{WavesScannerProcessorErc20.js => WavesScannerProcessorErc20.ts} (76%) rename crypto/blockchains/waves/providers/{WavesTransactionsProvider.js => WavesTransactionsProvider.ts} (65%) delete mode 100644 crypto/blockchains/xlm/XlmAddressProcessor.js create mode 100644 crypto/blockchains/xlm/XlmAddressProcessor.ts rename crypto/blockchains/xlm/{XlmScannerProcessor.js => XlmScannerProcessor.ts} (84%) delete mode 100644 crypto/blockchains/xlm/ext/XlmDerivePath.js create mode 100644 crypto/blockchains/xlm/ext/XlmDerivePath.ts create mode 100644 crypto/blockchains/xmr/XmrAddressProcessor.ts create mode 100644 crypto/blockchains/xmr/XmrScannerProcessor.ts delete mode 100644 crypto/blockchains/xmr/XmrSecretsProcessor.js create mode 100644 crypto/blockchains/xmr/XmrSecretsProcessor.ts delete mode 100644 crypto/blockchains/xmr/ext/MoneroDict.js create mode 100644 crypto/blockchains/xmr/ext/MoneroDict.ts delete mode 100644 crypto/blockchains/xmr/ext/MoneroMnemonic.js create mode 100644 crypto/blockchains/xmr/ext/MoneroMnemonic.ts delete mode 100644 crypto/blockchains/xmr/ext/MoneroUtils.js create mode 100644 crypto/blockchains/xmr/ext/MoneroUtils.ts delete mode 100644 crypto/blockchains/xmr/ext/MoneroUtilsParser.oldAndroid.js create mode 100644 crypto/blockchains/xmr/ext/MoneroUtilsParser.oldAndroid.ts rename crypto/blockchains/xmr/ext/{MoneroUtilsParser.js => MoneroUtilsParser.ts} (74%) rename crypto/blockchains/xmr/providers/{XmrSendProvider.js => XmrSendProvider.ts} (88%) create mode 100644 crypto/blockchains/xmr/providers/XmrUnspentsProvider.ts delete mode 100644 crypto/blockchains/xrp/XrpAddressProcessor.js create mode 100644 crypto/blockchains/xrp/XrpAddressProcessor.ts create mode 100644 crypto/blockchains/xrp/XrpScannerProcessor.ts create mode 100644 crypto/blockchains/xrp/basic/XrpDataRippleProvider.ts create mode 100644 crypto/blockchains/xrp/basic/XrpDataScanProvider.ts delete mode 100644 crypto/blockchains/xrp/stores/XrpTmpDS.js create mode 100644 crypto/blockchains/xrp/stores/XrpTmpDS.ts create mode 100644 crypto/blockchains/xvg/basic/XvgFindAddressFunction.ts delete mode 100644 crypto/blockchains/xvg/stores/XvgTmpDS.js create mode 100644 crypto/blockchains/xvg/stores/XvgTmpDS.ts create mode 100644 src/lib/common/ext/network-constants.ts diff --git a/crypto/blockchains/one/OneScannerProcessor.js b/crypto/blockchains/one/OneScannerProcessor.ts similarity index 100% rename from crypto/blockchains/one/OneScannerProcessor.js rename to crypto/blockchains/one/OneScannerProcessor.ts diff --git a/crypto/blockchains/one/OneScannerProcessorErc20.js b/crypto/blockchains/one/OneScannerProcessorErc20.ts similarity index 100% rename from crypto/blockchains/one/OneScannerProcessorErc20.js rename to crypto/blockchains/one/OneScannerProcessorErc20.ts diff --git a/crypto/blockchains/one/ext/OneUtils.js b/crypto/blockchains/one/ext/OneUtils.ts similarity index 96% rename from crypto/blockchains/one/ext/OneUtils.js rename to crypto/blockchains/one/ext/OneUtils.ts index 531b9209a..80b02caa2 100644 --- a/crypto/blockchains/one/ext/OneUtils.js +++ b/crypto/blockchains/one/ext/OneUtils.ts @@ -1,239 +1,239 @@ -/** - * @harmony-js/crypto/src/bech32.ts - * https://github.com/harmony-one/sdk/blob/master/packages/harmony-crypto/src/bech32.ts - * const { toBech32 } = require("@harmony-js/crypto"); - * console.log("Using account: " + toBech32("0xxxxx", "one1")) - */ - -const CHARSET = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l' -const GENERATOR = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3] - -const polymod = (values) => { - let chk = 1 - // tslint:disable-next-line - for (let p = 0; p < values.length; ++p) { - const top = chk >> 25 - chk = ((chk & 0x1ffffff) << 5) ^ values[p] - for (let i = 0; i < 5; ++i) { - if ((top >> i) & 1) { - chk ^= GENERATOR[i] - } - } - } - return chk -} - -const hrpExpand = (hrp) => { - const ret = [] - let p - for (p = 0; p < hrp.length; ++p) { - ret.push(hrp.charCodeAt(p) >> 5) - } - ret.push(0) - for (p = 0; p < hrp.length; ++p) { - ret.push(hrp.charCodeAt(p) & 31) - } - return Buffer.from(ret) -} - -function createChecksum(hrp, data) { - const values = Buffer.concat([ - Buffer.from(hrpExpand(hrp)), - data, - Buffer.from([0, 0, 0, 0, 0, 0]) - ]) - // var values = hrpExpand(hrp).concat(data).concat([0, 0, 0, 0, 0, 0]); - const mod = polymod(values) ^ 1 - const ret = [] - for (let p = 0; p < 6; ++p) { - ret.push((mod >> (5 * (5 - p))) & 31) - } - return Buffer.from(ret) -} - -const bech32Encode = (hrp, data) => { - const combined = Buffer.concat([data, createChecksum(hrp, data)]) - let ret = hrp + '1' - // tslint:disable-next-line - for (let p = 0; p < combined.length; ++p) { - ret += CHARSET.charAt(combined[p]) - } - return ret -} - - -const convertBits = ( - data, - fromWidth, - toWidth, - pad = true -) => { - let acc = 0 - let bits = 0 - const ret = [] - const maxv = (1 << toWidth) - 1 - // tslint:disable-next-line - for (let p = 0; p < data.length; ++p) { - const value = data[p] - if (value < 0 || value >> fromWidth !== 0) { - return null - } - acc = (acc << fromWidth) | value - bits += fromWidth - while (bits >= toWidth) { - bits -= toWidth - ret.push((acc >> bits) & maxv) - } - } - - if (pad) { - if (bits > 0) { - ret.push((acc << (toWidth - bits)) & maxv) - } - } else if (bits >= fromWidth || (acc << (toWidth - bits)) & maxv) { - return null - } - - return Buffer.from(ret) -} - -const bech32Decode = (bechString) => { - let p; - let hasLower = false; - let hasUpper = false; - for (p = 0; p < bechString.length; ++p) { - if (bechString.charCodeAt(p) < 33 || bechString.charCodeAt(p) > 126) { - return null; - } - if (bechString.charCodeAt(p) >= 97 && bechString.charCodeAt(p) <= 122) { - hasLower = true; - } - if (bechString.charCodeAt(p) >= 65 && bechString.charCodeAt(p) <= 90) { - hasUpper = true; - } - } - if (hasLower && hasUpper) { - return null; - } - bechString = bechString.toLowerCase(); - const pos = bechString.lastIndexOf('1'); - if (pos < 1 || pos + 7 > bechString.length || bechString.length > 90) { - return null; - } - const hrp = bechString.substring(0, pos); - const data = []; - for (p = pos + 1; p < bechString.length; ++p) { - const d = CHARSET.indexOf(bechString.charAt(p)); - if (d === -1) { - return null; - } - data.push(d); - } - - try { - if (!verifyChecksum(hrp, Buffer.from(data))) { - return null; - } - } catch (e) { - e.message += ' in verifyChecksum' - throw e - } - - return { hrp, data: Buffer.from(data.slice(0, data.length - 6)) }; -}; - -function verifyChecksum(hrp, data) { - return polymod(Buffer.concat([hrpExpand(hrp), data])) === 1; -} - -const toChecksumAddress = (address)=> { - if (typeof address !== 'string' || !address.match(/^0x[0-9A-Fa-f]{40}$/)) { - throw new Error('invalid address ' + address); - } - - address = address.toLowerCase(); - - const chars = address.substring(2).split(''); - - let hashed = new Uint8Array(40); - for (let i = 0; i < 40; i++) { - hashed[i] = chars[i].charCodeAt(0); - } - // hashed = bytes.arrayify(keccak256(hashed)) || hashed; - - for (let i = 0; i < 40; i += 2) { - if (hashed[i >> 1] >> 4 >= 8) { - chars[i] = chars[i].toUpperCase(); - } - if ((hashed[i >> 1] & 0x0f) >= 8) { - chars[i + 1] = chars[i + 1].toUpperCase(); - } - } - - return '0x' + chars.join(''); -}; - - -export default { - isOneAddress(address) { - if (typeof address === 'undefined' || typeof address.match === 'undefined') { - return false - } - try { - return !!address.match(/^one1[qpzry9x8gf2tvdw0s3jn54khce6mua7l]{38}/); - } catch (e) { - e.message += ' in match - address ' + JSON.stringify(address) - throw e - } - }, - - toOneAddress(address, useHRP = 'one') { - if (address.indexOf('one') === 0) { - return address - } - - const prepAddr = address.replace('0x', '').toLowerCase() - - const addrBz = convertBits( Buffer.from(prepAddr, 'hex'), 8, 5) - - if (addrBz === null) { - throw new Error('Could not convert byte Buffer to 5-bit Buffer') - } - - return bech32Encode(useHRP, addrBz) - }, - - fromOneAddress(address, useHRP = 'one') { - let res - try { - res = bech32Decode(address) - } catch (e) { - e.message += ' in bech32Decode - address ' + JSON.stringify(address) - throw e - } - - if (res === null) { - throw new Error('Invalid bech32 address') - } - - const { hrp, data } = res - - if (hrp !== useHRP) { - throw new Error(`Expected hrp to be ${useHRP} but got ${hrp}`) - } - - const buf = convertBits(data, 5, 8, false) - - if (buf === null) { - throw new Error('Could not convert buffer to bytes') - } - - try { - const tmp = toChecksumAddress('0x' + buf.toString('hex')) - return tmp - } catch (e) { - e.message += ' in toChecksumAddress' - throw e - } - } -} +/** + * @harmony-js/crypto/src/bech32.ts + * https://github.com/harmony-one/sdk/blob/master/packages/harmony-crypto/src/bech32.ts + * const { toBech32 } = require("@harmony-js/crypto"); + * console.log("Using account: " + toBech32("0xxxxx", "one1")) + */ + +const CHARSET = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l' +const GENERATOR = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3] + +const polymod = (values) => { + let chk = 1 + // tslint:disable-next-line + for (let p = 0; p < values.length; ++p) { + const top = chk >> 25 + chk = ((chk & 0x1ffffff) << 5) ^ values[p] + for (let i = 0; i < 5; ++i) { + if ((top >> i) & 1) { + chk ^= GENERATOR[i] + } + } + } + return chk +} + +const hrpExpand = (hrp) => { + const ret = [] + let p + for (p = 0; p < hrp.length; ++p) { + ret.push(hrp.charCodeAt(p) >> 5) + } + ret.push(0) + for (p = 0; p < hrp.length; ++p) { + ret.push(hrp.charCodeAt(p) & 31) + } + return Buffer.from(ret) +} + +function createChecksum(hrp, data) { + const values = Buffer.concat([ + Buffer.from(hrpExpand(hrp)), + data, + Buffer.from([0, 0, 0, 0, 0, 0]) + ]) + // var values = hrpExpand(hrp).concat(data).concat([0, 0, 0, 0, 0, 0]); + const mod = polymod(values) ^ 1 + const ret = [] + for (let p = 0; p < 6; ++p) { + ret.push((mod >> (5 * (5 - p))) & 31) + } + return Buffer.from(ret) +} + +const bech32Encode = (hrp, data) => { + const combined = Buffer.concat([data, createChecksum(hrp, data)]) + let ret = hrp + '1' + // tslint:disable-next-line + for (let p = 0; p < combined.length; ++p) { + ret += CHARSET.charAt(combined[p]) + } + return ret +} + + +const convertBits = ( + data, + fromWidth, + toWidth, + pad = true +) => { + let acc = 0 + let bits = 0 + const ret = [] + const maxv = (1 << toWidth) - 1 + // tslint:disable-next-line + for (let p = 0; p < data.length; ++p) { + const value = data[p] + if (value < 0 || value >> fromWidth !== 0) { + return null + } + acc = (acc << fromWidth) | value + bits += fromWidth + while (bits >= toWidth) { + bits -= toWidth + ret.push((acc >> bits) & maxv) + } + } + + if (pad) { + if (bits > 0) { + ret.push((acc << (toWidth - bits)) & maxv) + } + } else if (bits >= fromWidth || (acc << (toWidth - bits)) & maxv) { + return null + } + + return Buffer.from(ret) +} + +const bech32Decode = (bechString) => { + let p; + let hasLower = false; + let hasUpper = false; + for (p = 0; p < bechString.length; ++p) { + if (bechString.charCodeAt(p) < 33 || bechString.charCodeAt(p) > 126) { + return null; + } + if (bechString.charCodeAt(p) >= 97 && bechString.charCodeAt(p) <= 122) { + hasLower = true; + } + if (bechString.charCodeAt(p) >= 65 && bechString.charCodeAt(p) <= 90) { + hasUpper = true; + } + } + if (hasLower && hasUpper) { + return null; + } + bechString = bechString.toLowerCase(); + const pos = bechString.lastIndexOf('1'); + if (pos < 1 || pos + 7 > bechString.length || bechString.length > 90) { + return null; + } + const hrp = bechString.substring(0, pos); + const data = []; + for (p = pos + 1; p < bechString.length; ++p) { + const d = CHARSET.indexOf(bechString.charAt(p)); + if (d === -1) { + return null; + } + data.push(d); + } + + try { + if (!verifyChecksum(hrp, Buffer.from(data))) { + return null; + } + } catch (e) { + e.message += ' in verifyChecksum' + throw e + } + + return { hrp, data: Buffer.from(data.slice(0, data.length - 6)) }; +}; + +function verifyChecksum(hrp, data) { + return polymod(Buffer.concat([hrpExpand(hrp), data])) === 1; +} + +const toChecksumAddress = (address)=> { + if (typeof address !== 'string' || !address.match(/^0x[0-9A-Fa-f]{40}$/)) { + throw new Error('invalid address ' + address); + } + + address = address.toLowerCase(); + + const chars = address.substring(2).split(''); + + let hashed = new Uint8Array(40); + for (let i = 0; i < 40; i++) { + hashed[i] = chars[i].charCodeAt(0); + } + // hashed = bytes.arrayify(keccak256(hashed)) || hashed; + + for (let i = 0; i < 40; i += 2) { + if (hashed[i >> 1] >> 4 >= 8) { + chars[i] = chars[i].toUpperCase(); + } + if ((hashed[i >> 1] & 0x0f) >= 8) { + chars[i + 1] = chars[i + 1].toUpperCase(); + } + } + + return '0x' + chars.join(''); +}; + + +export default { + isOneAddress(address) { + if (typeof address === 'undefined' || typeof address.match === 'undefined') { + return false + } + try { + return !!address.match(/^one1[qpzry9x8gf2tvdw0s3jn54khce6mua7l]{38}/); + } catch (e) { + e.message += ' in match - address ' + JSON.stringify(address) + throw e + } + }, + + toOneAddress(address, useHRP = 'one') { + if (address.indexOf('one') === 0) { + return address + } + + const prepAddr = address.replace('0x', '').toLowerCase() + + const addrBz = convertBits( Buffer.from(prepAddr, 'hex'), 8, 5) + + if (addrBz === null) { + throw new Error('Could not convert byte Buffer to 5-bit Buffer') + } + + return bech32Encode(useHRP, addrBz) + }, + + fromOneAddress(address, useHRP = 'one') { + let res + try { + res = bech32Decode(address) + } catch (e) { + e.message += ' in bech32Decode - address ' + JSON.stringify(address) + throw e + } + + if (res === null) { + throw new Error('Invalid bech32 address') + } + + const { hrp, data } = res + + if (hrp !== useHRP) { + throw new Error(`Expected hrp to be ${useHRP} but got ${hrp}`) + } + + const buf = convertBits(data, 5, 8, false) + + if (buf === null) { + throw new Error('Could not convert buffer to bytes') + } + + try { + const tmp = toChecksumAddress('0x' + buf.toString('hex')) + return tmp + } catch (e) { + e.message += ' in toChecksumAddress' + throw e + } + } +} diff --git a/crypto/blockchains/one/stores/OneTmpDS.js b/crypto/blockchains/one/stores/OneTmpDS.ts similarity index 100% rename from crypto/blockchains/one/stores/OneTmpDS.js rename to crypto/blockchains/one/stores/OneTmpDS.ts diff --git a/crypto/blockchains/sol/SolAddressProcessor.js b/crypto/blockchains/sol/SolAddressProcessor.ts similarity index 100% rename from crypto/blockchains/sol/SolAddressProcessor.js rename to crypto/blockchains/sol/SolAddressProcessor.ts diff --git a/crypto/blockchains/sol/SolScannerProcessor.js b/crypto/blockchains/sol/SolScannerProcessor.ts similarity index 100% rename from crypto/blockchains/sol/SolScannerProcessor.js rename to crypto/blockchains/sol/SolScannerProcessor.ts diff --git a/crypto/blockchains/sol/SolScannerProcessorSpl.js b/crypto/blockchains/sol/SolScannerProcessorSpl.ts similarity index 97% rename from crypto/blockchains/sol/SolScannerProcessorSpl.js rename to crypto/blockchains/sol/SolScannerProcessorSpl.ts index f78ece4ca..69fa66ba3 100644 --- a/crypto/blockchains/sol/SolScannerProcessorSpl.js +++ b/crypto/blockchains/sol/SolScannerProcessorSpl.ts @@ -1,94 +1,94 @@ -/** - * @version 0.52 - */ -import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; -import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; -import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings'; - -import SolScannerProcessor from '@crypto/blockchains/sol/SolScannerProcessor'; - -const CACHE_BALANCES = {}; -const CACHE_VALID_TIME = 30000; // 30 seconds - -export default class SolScannerProcessorSpl extends SolScannerProcessor { - /** - * @param {string} address - * @return {Promise<{balance, provider}>} - */ - async getBalanceBlockchain(address) { - address = address.trim(); - BlocksoftCryptoLog.log( - this._settings.currencyCode + - ' SolScannerProcessorSpl getBalanceBlockchain address ' + - address - ); - - const now = new Date().getTime(); - let balance = 0; - try { - if ( - typeof CACHE_BALANCES[address] === 'undefined' || - typeof CACHE_BALANCES[address].time === 'undefined' || - now - CACHE_BALANCES[address].time < CACHE_VALID_TIME - ) { - CACHE_BALANCES[address] = {}; - const apiPath = BlocksoftExternalSettings.getStatic('SOL_SERVER'); - - const data = { - jsonrpc: '2.0', - id: 1, - method: 'getTokenAccountsByOwner', - params: [ - address, - { - programId: 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA' - }, - { encoding: 'jsonParsed', commitment: 'processed' } - ] - }; - - const res = await BlocksoftAxios._request(apiPath, 'POST', data); - if ( - typeof res.data.result === 'undefined' || - typeof res.data.result.value === 'undefined' - ) { - return false; - } - for (const account of res.data.result.value) { - if (typeof account.account === 'undefined') continue; - if ( - typeof account.account.data.program === 'undefined' || - account.account.data.program !== 'spl-token' - ) - continue; - const parsed = account.account.data.parsed.info; - if ( - typeof parsed.state === 'undefined' && - parsed.state !== 'initialized' - ) - continue; - CACHE_BALANCES[address][parsed.mint] = parsed.tokenAmount; // "amount": "1606300", "decimals": 6, "uiAmount": 1.6063, "uiAmountString": "1.6063" - } - CACHE_BALANCES[address].time = now; - } - if ( - typeof CACHE_BALANCES[address][this.tokenAddress] === 'undefined' || - typeof CACHE_BALANCES[address][this.tokenAddress].amount === 'undefined' - ) { - balance = 0; - } else { - balance = CACHE_BALANCES[address][this.tokenAddress].amount * 1; - } - } catch (e) { - BlocksoftCryptoLog.log( - this._settings.currencyCode + - ' SolScannerProcessorSpl getBalanceBlockchain address ' + - address + - ' error ' + - e.message - ); - return false; - } - return { balance, unconfirmed: 0, provider: 'solana-api' }; - } -} +/** + * @version 0.52 + */ +import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; +import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; +import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings'; + +import SolScannerProcessor from '@crypto/blockchains/sol/SolScannerProcessor'; + +const CACHE_BALANCES = {}; +const CACHE_VALID_TIME = 30000; // 30 seconds + +export default class SolScannerProcessorSpl extends SolScannerProcessor { + /** + * @param {string} address + * @return {Promise<{balance, provider}>} + */ + async getBalanceBlockchain(address) { + address = address.trim(); + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' SolScannerProcessorSpl getBalanceBlockchain address ' + + address + ); + + const now = new Date().getTime(); + let balance = 0; + try { + if ( + typeof CACHE_BALANCES[address] === 'undefined' || + typeof CACHE_BALANCES[address].time === 'undefined' || + now - CACHE_BALANCES[address].time < CACHE_VALID_TIME + ) { + CACHE_BALANCES[address] = {}; + const apiPath = BlocksoftExternalSettings.getStatic('SOL_SERVER'); + + const data = { + jsonrpc: '2.0', + id: 1, + method: 'getTokenAccountsByOwner', + params: [ + address, + { + programId: 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA' + }, + { encoding: 'jsonParsed', commitment: 'processed' } + ] + }; + + const res = await BlocksoftAxios._request(apiPath, 'POST', data); + if ( + typeof res.data.result === 'undefined' || + typeof res.data.result.value === 'undefined' + ) { + return false; + } + for (const account of res.data.result.value) { + if (typeof account.account === 'undefined') continue; + if ( + typeof account.account.data.program === 'undefined' || + account.account.data.program !== 'spl-token' + ) + continue; + const parsed = account.account.data.parsed.info; + if ( + typeof parsed.state === 'undefined' && + parsed.state !== 'initialized' + ) + continue; + CACHE_BALANCES[address][parsed.mint] = parsed.tokenAmount; // "amount": "1606300", "decimals": 6, "uiAmount": 1.6063, "uiAmountString": "1.6063" + } + CACHE_BALANCES[address].time = now; + } + if ( + typeof CACHE_BALANCES[address][this.tokenAddress] === 'undefined' || + typeof CACHE_BALANCES[address][this.tokenAddress].amount === 'undefined' + ) { + balance = 0; + } else { + balance = CACHE_BALANCES[address][this.tokenAddress].amount * 1; + } + } catch (e) { + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' SolScannerProcessorSpl getBalanceBlockchain address ' + + address + + ' error ' + + e.message + ); + return false; + } + return { balance, unconfirmed: 0, provider: 'solana-api' }; + } +} diff --git a/crypto/blockchains/sol/SolTokenProcessor.js b/crypto/blockchains/sol/SolTokenProcessor.ts similarity index 100% rename from crypto/blockchains/sol/SolTokenProcessor.js rename to crypto/blockchains/sol/SolTokenProcessor.ts diff --git a/crypto/blockchains/sol/ext/SolInstructions.js b/crypto/blockchains/sol/ext/SolInstructions.js deleted file mode 100644 index b4ce98064..000000000 --- a/crypto/blockchains/sol/ext/SolInstructions.js +++ /dev/null @@ -1,79 +0,0 @@ -/** - * @version 0.52 - * https://github.com/project-serum/spl-token-wallet/blob/master/src/utils/tokens/instructions.js - */ -import * as BufferLayout from '@solana/buffer-layout' -import { PublicKey } from '@solana/web3.js/src/index' - -const LAYOUT = BufferLayout.union(BufferLayout.u8('instruction')) -LAYOUT.addVariant( - 0, - BufferLayout.struct([ - BufferLayout.u8('decimals'), - BufferLayout.blob(32, 'mintAuthority'), - BufferLayout.u8('freezeAuthorityOption'), - BufferLayout.blob(32, 'freezeAuthority') - ]), - 'initializeMint' -) -LAYOUT.addVariant(1, BufferLayout.struct([]), 'initializeAccount') -LAYOUT.addVariant( - 7, - BufferLayout.struct([BufferLayout.nu64('amount')]), - 'mintTo' -) -LAYOUT.addVariant( - 8, - BufferLayout.struct([BufferLayout.nu64('amount')]), - 'burn' -) -LAYOUT.addVariant(9, BufferLayout.struct([]), 'closeAccount') -LAYOUT.addVariant( - 12, - BufferLayout.struct([BufferLayout.nu64('amount'), BufferLayout.u8('decimals')]), - 'transferChecked' -) - -const instructionMaxSpan = Math.max( - ...Object.values(LAYOUT.registry).map((r) => r.span) -) - -class PublicKeyLayout extends BufferLayout.Blob { - constructor(property) { - super(32, property) - } - - decode(b, offset) { - return new PublicKey(super.decode(b, offset)) - } - - encode(src, b, offset) { - return super.encode(src.toBuffer(), b, offset) - } -} - -function publicKeyLayout(property) { - return new PublicKeyLayout(property) -} - - -export const OWNER_VALIDATION_LAYOUT = BufferLayout.struct([ - publicKeyLayout('account') -]) - -export default { - - encodeTokenInstructionData(instruction) { - let b = Buffer.alloc(instructionMaxSpan) - let span = LAYOUT.encode(instruction, b) - const res = b.slice(0, span) - return res - }, - - encodeOwnerValidationInstruction(instruction) { - const b = Buffer.alloc(OWNER_VALIDATION_LAYOUT.span) - const span = OWNER_VALIDATION_LAYOUT.encode(instruction, b) - return b.slice(0, span) - } - -} diff --git a/crypto/blockchains/sol/ext/SolInstructions.ts b/crypto/blockchains/sol/ext/SolInstructions.ts new file mode 100644 index 000000000..27f6c5b84 --- /dev/null +++ b/crypto/blockchains/sol/ext/SolInstructions.ts @@ -0,0 +1,79 @@ +/** + * @version 0.52 + * https://github.com/project-serum/spl-token-wallet/blob/master/src/utils/tokens/instructions.js + */ +import * as BufferLayout from '@solana/buffer-layout'; +import { PublicKey } from '@solana/web3.js/src'; + +const LAYOUT = BufferLayout.union(BufferLayout.u8('instruction')); +LAYOUT.addVariant( + 0, + BufferLayout.struct([ + BufferLayout.u8('decimals'), + BufferLayout.blob(32, 'mintAuthority'), + BufferLayout.u8('freezeAuthorityOption'), + BufferLayout.blob(32, 'freezeAuthority') + ]), + 'initializeMint' +); +LAYOUT.addVariant(1, BufferLayout.struct([]), 'initializeAccount'); +LAYOUT.addVariant( + 7, + BufferLayout.struct([BufferLayout.nu64('amount')]), + 'mintTo' +); +LAYOUT.addVariant( + 8, + BufferLayout.struct([BufferLayout.nu64('amount')]), + 'burn' +); +LAYOUT.addVariant(9, BufferLayout.struct([]), 'closeAccount'); +LAYOUT.addVariant( + 12, + BufferLayout.struct([ + BufferLayout.nu64('amount'), + BufferLayout.u8('decimals') + ]), + 'transferChecked' +); + +const instructionMaxSpan = Math.max( + ...Object.values(LAYOUT.registry).map((r) => r.span) +); + +class PublicKeyLayout extends BufferLayout.Blob { + constructor(property) { + super(32, property); + } + + decode(b, offset) { + return new PublicKey(super.decode(b, offset)); + } + + encode(src, b, offset) { + return super.encode(src.toBuffer(), b, offset); + } +} + +function publicKeyLayout(property) { + return new PublicKeyLayout(property); +} + +export const OWNER_VALIDATION_LAYOUT = BufferLayout.struct([ + publicKeyLayout('account') +]); + +export default { + encodeTokenInstructionData(instruction) { + let b = Buffer.alloc(instructionMaxSpan); + let span = LAYOUT.encode(instruction, b); + const res = b.slice(0, span); + return res; + }, + + encodeOwnerValidationInstruction(instruction) { + const b = Buffer.alloc(OWNER_VALIDATION_LAYOUT.span); + const span = OWNER_VALIDATION_LAYOUT.encode(instruction, b); + return b.slice(0, span); + } +}; diff --git a/crypto/blockchains/sol/ext/SolStakeUtils.js b/crypto/blockchains/sol/ext/SolStakeUtils.ts similarity index 100% rename from crypto/blockchains/sol/ext/SolStakeUtils.js rename to crypto/blockchains/sol/ext/SolStakeUtils.ts diff --git a/crypto/blockchains/sol/ext/SolUtils.js b/crypto/blockchains/sol/ext/SolUtils.ts similarity index 95% rename from crypto/blockchains/sol/ext/SolUtils.js rename to crypto/blockchains/sol/ext/SolUtils.ts index 39cfb40e6..3d348b8fb 100644 --- a/crypto/blockchains/sol/ext/SolUtils.js +++ b/crypto/blockchains/sol/ext/SolUtils.ts @@ -1,239 +1,239 @@ -/** - * @version 0.52 - */ -import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; -import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings'; - -import { PublicKey } from '@solana/web3.js/src/index'; -import { Account } from '@solana/web3.js/src/account'; - -import config from '@app/config/config'; -import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; -import settingsActions from '@app/appstores/Stores/Settings/SettingsActions'; - -const TOKEN_PROGRAM_ID = 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'; -const ASSOCIATED_TOKEN_PROGRAM_ID = - 'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL'; -const OWNER_VALIDATION_PROGRAM_ID = - '4MNPdKu9wFMvEeZBMt3Eipfs5ovVWTJb31pEXDJAAxX5'; - -const CACHE_VALID_TIME = 12000000; // 200 minute - -const CACHE_EPOCH = { - ts: 0, - value: 240 -}; - -export default { - getTokenProgramID() { - return TOKEN_PROGRAM_ID; - }, - - getOwnerValidationProgramId() { - return OWNER_VALIDATION_PROGRAM_ID; - }, - - getAssociatedTokenProgramId() { - return ASSOCIATED_TOKEN_PROGRAM_ID; - }, - - async findAssociatedTokenAddress(walletAddress, tokenMintAddress) { - try { - const seeds = [ - new PublicKey(walletAddress).toBuffer(), - new PublicKey(TOKEN_PROGRAM_ID).toBuffer(), - new PublicKey(tokenMintAddress).toBuffer() - ]; - const res = await PublicKey.findProgramAddress( - seeds, - new PublicKey(ASSOCIATED_TOKEN_PROGRAM_ID) - ); - return res[0].toBase58(); - } catch (e) { - if (config.debug.cryptoErrors) { - console.log('SolUtils.findAssociatedTokenAddress ' + e.message); - } - throw new Error('SYSTEM_ERROR'); - } - }, - - async getAccountInfo(address) { - let accountInfo = false; - try { - const apiPath = BlocksoftExternalSettings.getStatic('SOL_SERVER'); - const checkData = { - jsonrpc: '2.0', - id: 1, - method: 'getAccountInfo', - params: [ - address, - { - encoding: 'jsonParsed' - } - ] - }; - const res = await BlocksoftAxios._request(apiPath, 'POST', checkData); - accountInfo = res.data.result.value; - } catch (e) { - if (config.debug.cryptoErrors) { - console.log( - 'SolUtils.getAccountInfo ' + address + ' error ' + e.message - ); - } - BlocksoftCryptoLog.log( - 'SolUtils.getAccountInfo ' + address + ' error ' + e.message - ); - } - return accountInfo; - }, - - isAddressValid(value) { - new PublicKey(value); - return true; - }, - - /** - * https://docs.solana.com/developing/clients/jsonrpc-api#sendtransaction - * @param raw - * @returns {Promise} - */ - async sendTransaction(raw) { - const sendData = { - jsonrpc: '2.0', - id: 1, - method: 'sendTransaction', - params: [ - raw, - { - encoding: 'base64' - } - ] - }; - const apiPath = BlocksoftExternalSettings.getStatic('SOL_SERVER'); - const apiPath_2 = BlocksoftExternalSettings.getStatic('SOL_SERVER_2'); - let try_2 = false; - let sendRes; - try { - sendRes = await BlocksoftAxios._request(apiPath, 'POST', sendData); - if (!sendRes || typeof sendRes.data === 'undefined') { - if (apiPath_2) { - try_2 = true; - } else { - throw new Error('SERVER_RESPONSE_BAD_INTERNET'); - } - } - if ( - typeof sendRes.data.error !== 'undefined' && - typeof sendRes.data.error.message !== 'undefined' - ) { - if (sendRes.data.error.message === 'Node is unhealthy') { - try_2 = true; - } else { - throw new Error(sendRes.data.error.message); - } - } - } catch (e) { - try_2 = true; - } - - if (try_2 && apiPath_2 && apiPath_2 !== apiPath) { - const sendRes_2 = await BlocksoftAxios._request( - apiPath_2, - 'POST', - sendData - ); - if (!sendRes_2 || typeof sendRes_2.data === 'undefined') { - throw new Error('SERVER_RESPONSE_BAD_INTERNET'); - } - if ( - typeof sendRes_2.data.error !== 'undefined' && - typeof sendRes_2.data.error.message !== 'undefined' - ) { - throw new Error(sendRes_2.data.error.message); - } - return sendRes_2.data.result; - } - - return sendRes.data.result; - }, - - /** - * @returns {Promise<{blockhash: string, feeCalculator: {lamportsPerSignature: number}}>} - */ - async getBlockData() { - const getRecentBlockhashData = { - jsonrpc: '2.0', - id: 1, - method: 'getRecentBlockhash' - }; - const apiPath = BlocksoftExternalSettings.getStatic('SOL_SERVER'); - const getRecentBlockhashRes = await BlocksoftAxios._request( - apiPath, - 'POST', - getRecentBlockhashData - ); - return getRecentBlockhashRes.data.result.value; - }, - - async getEpoch() { - const now = new Date().getTime(); - if (CACHE_EPOCH.ts > 0) { - if (now - CACHE_EPOCH.ts < CACHE_VALID_TIME) { - return CACHE_EPOCH.value; - } - } else { - const tmp = settingsActions.getSettings('SOL_epoch'); - if (tmp * 1 > CACHE_EPOCH.value) { - CACHE_EPOCH.value = tmp * 1; - } - } - const apiPath = BlocksoftExternalSettings.getStatic('SOL_SERVER'); - const getEpoch = { jsonrpc: '2.0', id: 1, method: 'getEpochInfo' }; - try { - const resEpoch = await BlocksoftAxios._request(apiPath, 'POST', getEpoch); - const tmp = resEpoch.data.result.epoch * 1; - if (tmp > 0 && tmp !== CACHE_EPOCH.value) { - CACHE_EPOCH.value = tmp; - settingsActions.setSettings('SOL_epoch', tmp); - } - CACHE_EPOCH.ts = now; - } catch (e) { - BlocksoftCryptoLog.log('SolUtils.getEpoch error ' + e.message); - // nothing - } - return CACHE_EPOCH.value; - }, - - async signTransaction(transaction, walletPrivKey, walletPubKey) { - let account = false; - try { - account = new Account(Buffer.from(walletPrivKey, 'hex')); - } catch (e) { - e.message += ' while create account'; - throw e; - } - - try { - const data = await this.getBlockData(); - transaction.recentBlockhash = data.blockhash; - } catch (e) { - e.message += ' while getBlockData'; - throw e; - } - try { - transaction.setSigners(new PublicKey(walletPubKey)); - } catch (e) { - e.message += ' while setSigners'; - throw e; - } - - try { - transaction.partialSign(account); - } catch (e) { - e.message += ' while transaction.partialSign with account'; - throw e; - } - - return transaction; - } -}; +/** + * @version 0.52 + */ +import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; +import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings'; + +import { PublicKey } from '@solana/web3.js/src'; +import { Account } from '@solana/web3.js/src/account'; + +import config from '@app/config/config'; +import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; +import settingsActions from '@app/appstores/Stores/Settings/SettingsActions'; + +const TOKEN_PROGRAM_ID = 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'; +const ASSOCIATED_TOKEN_PROGRAM_ID = + 'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL'; +const OWNER_VALIDATION_PROGRAM_ID = + '4MNPdKu9wFMvEeZBMt3Eipfs5ovVWTJb31pEXDJAAxX5'; + +const CACHE_VALID_TIME = 12000000; // 200 minute + +const CACHE_EPOCH = { + ts: 0, + value: 240 +}; + +export default { + getTokenProgramID() { + return TOKEN_PROGRAM_ID; + }, + + getOwnerValidationProgramId() { + return OWNER_VALIDATION_PROGRAM_ID; + }, + + getAssociatedTokenProgramId() { + return ASSOCIATED_TOKEN_PROGRAM_ID; + }, + + async findAssociatedTokenAddress(walletAddress, tokenMintAddress) { + try { + const seeds = [ + new PublicKey(walletAddress).toBuffer(), + new PublicKey(TOKEN_PROGRAM_ID).toBuffer(), + new PublicKey(tokenMintAddress).toBuffer() + ]; + const res = await PublicKey.findProgramAddress( + seeds, + new PublicKey(ASSOCIATED_TOKEN_PROGRAM_ID) + ); + return res[0].toBase58(); + } catch (e) { + if (config.debug.cryptoErrors) { + console.log('SolUtils.findAssociatedTokenAddress ' + e.message); + } + throw new Error('SYSTEM_ERROR'); + } + }, + + async getAccountInfo(address) { + let accountInfo = false; + try { + const apiPath = BlocksoftExternalSettings.getStatic('SOL_SERVER'); + const checkData = { + jsonrpc: '2.0', + id: 1, + method: 'getAccountInfo', + params: [ + address, + { + encoding: 'jsonParsed' + } + ] + }; + const res = await BlocksoftAxios._request(apiPath, 'POST', checkData); + accountInfo = res.data.result.value; + } catch (e) { + if (config.debug.cryptoErrors) { + console.log( + 'SolUtils.getAccountInfo ' + address + ' error ' + e.message + ); + } + BlocksoftCryptoLog.log( + 'SolUtils.getAccountInfo ' + address + ' error ' + e.message + ); + } + return accountInfo; + }, + + isAddressValid(value) { + new PublicKey(value); + return true; + }, + + /** + * https://docs.solana.com/developing/clients/jsonrpc-api#sendtransaction + * @param raw + * @returns {Promise} + */ + async sendTransaction(raw) { + const sendData = { + jsonrpc: '2.0', + id: 1, + method: 'sendTransaction', + params: [ + raw, + { + encoding: 'base64' + } + ] + }; + const apiPath = BlocksoftExternalSettings.getStatic('SOL_SERVER'); + const apiPath_2 = BlocksoftExternalSettings.getStatic('SOL_SERVER_2'); + let try_2 = false; + let sendRes; + try { + sendRes = await BlocksoftAxios._request(apiPath, 'POST', sendData); + if (!sendRes || typeof sendRes.data === 'undefined') { + if (apiPath_2) { + try_2 = true; + } else { + throw new Error('SERVER_RESPONSE_BAD_INTERNET'); + } + } + if ( + typeof sendRes.data.error !== 'undefined' && + typeof sendRes.data.error.message !== 'undefined' + ) { + if (sendRes.data.error.message === 'Node is unhealthy') { + try_2 = true; + } else { + throw new Error(sendRes.data.error.message); + } + } + } catch (e) { + try_2 = true; + } + + if (try_2 && apiPath_2 && apiPath_2 !== apiPath) { + const sendRes_2 = await BlocksoftAxios._request( + apiPath_2, + 'POST', + sendData + ); + if (!sendRes_2 || typeof sendRes_2.data === 'undefined') { + throw new Error('SERVER_RESPONSE_BAD_INTERNET'); + } + if ( + typeof sendRes_2.data.error !== 'undefined' && + typeof sendRes_2.data.error.message !== 'undefined' + ) { + throw new Error(sendRes_2.data.error.message); + } + return sendRes_2.data.result; + } + + return sendRes.data.result; + }, + + /** + * @returns {Promise<{blockhash: string, feeCalculator: {lamportsPerSignature: number}}>} + */ + async getBlockData() { + const getRecentBlockhashData = { + jsonrpc: '2.0', + id: 1, + method: 'getRecentBlockhash' + }; + const apiPath = BlocksoftExternalSettings.getStatic('SOL_SERVER'); + const getRecentBlockhashRes = await BlocksoftAxios._request( + apiPath, + 'POST', + getRecentBlockhashData + ); + return getRecentBlockhashRes.data.result.value; + }, + + async getEpoch() { + const now = new Date().getTime(); + if (CACHE_EPOCH.ts > 0) { + if (now - CACHE_EPOCH.ts < CACHE_VALID_TIME) { + return CACHE_EPOCH.value; + } + } else { + const tmp = settingsActions.getSettings('SOL_epoch'); + if (tmp * 1 > CACHE_EPOCH.value) { + CACHE_EPOCH.value = tmp * 1; + } + } + const apiPath = BlocksoftExternalSettings.getStatic('SOL_SERVER'); + const getEpoch = { jsonrpc: '2.0', id: 1, method: 'getEpochInfo' }; + try { + const resEpoch = await BlocksoftAxios._request(apiPath, 'POST', getEpoch); + const tmp = resEpoch.data.result.epoch * 1; + if (tmp > 0 && tmp !== CACHE_EPOCH.value) { + CACHE_EPOCH.value = tmp; + settingsActions.setSettings('SOL_epoch', tmp); + } + CACHE_EPOCH.ts = now; + } catch (e) { + BlocksoftCryptoLog.log('SolUtils.getEpoch error ' + e.message); + // nothing + } + return CACHE_EPOCH.value; + }, + + async signTransaction(transaction, walletPrivKey, walletPubKey) { + let account = false; + try { + account = new Account(Buffer.from(walletPrivKey, 'hex')); + } catch (e) { + e.message += ' while create account'; + throw e; + } + + try { + const data = await this.getBlockData(); + transaction.recentBlockhash = data.blockhash; + } catch (e) { + e.message += ' while getBlockData'; + throw e; + } + try { + transaction.setSigners(new PublicKey(walletPubKey)); + } catch (e) { + e.message += ' while setSigners'; + throw e; + } + + try { + transaction.partialSign(account); + } catch (e) { + e.message += ' while transaction.partialSign with account'; + throw e; + } + + return transaction; + } +}; diff --git a/crypto/blockchains/sol/ext/validators.js b/crypto/blockchains/sol/ext/validators.js deleted file mode 100644 index bc533dc8b..000000000 --- a/crypto/blockchains/sol/ext/validators.js +++ /dev/null @@ -1,243 +0,0 @@ -module.exports = [{ - "id": "GA2t11gJcmuZ4y7pShTzgYDkxVaJaVQJqkVUqojhPPsT", - "index": 1, - "name": "SolBrothers", - "description": "", - "website": "" - }, - { - "id": "9QU2QSxhb24FUX3Tu2FpczXjpK3VYrvRudywSZaM29mF", - "index": 2, - "name": "Everstake", - "description": "Everstake is a team of seasoned developers, financial experts and blockchain enthusiasts. We run secure and reliable nodes for PoS protocols", - "website": "https://everstake.one" - }, - { - "id": "2het6nBRLq9LLZER8fqUEk7j5pbLxq2mVGqSse2nS3tf", - "name": "Maggie's Crypto Farm", - "description": "Making the blocks, all day and all night.", - "website": "https://mcf.rocks/" - }, - { - "id": "3r5ZXC1yFqMmk8VwDdUJbEdPmZ8KZvEkzd5ThEYRetTk", - "name": "Vnode", - "description": "We are the staking service provider for blockchain projects.", - "website": "" - }, - { - "id": "49DJjUX3cwFvaZD5rCAwubiz7qdRWDez9xmB381XdHru", - "name": "Staker Space", - "description": "Secure Validating Services", - "website": "https://staker.space" - }, - { - "id": "4PsiLMyoUQ7QRn1FFiFCvej4hsUTFzfvJnyN4bj1tmSN", - "name": "Stakin", - "description": "Your Trusted Crypto Rewards", - "website": "https://stakin.com/" - }, - { - "id": "51JBzSTU5rAM8gLAVQKgp4WoZerQcSqWC7BitBzgUNAm", - "name": "Staked", - "description": "Staked operates highly reliable and secure staking infrastructure for 20+ PoS protocols on behalf of the leading investors in the market.", - "website": "https://staked.us" - }, - { - "id": "6UDU4Z9TTbYy8gcRKBd7RX3Lm2qMsSR4PMuzoyYPzLma", - "name": "dcipher", - "description": "dCipher is a company that has the purpose to combine emerging technologies like Blockchain, IoT and Artificial Intelligence.", - "website": "https://dcipher.io" - }, - { - "id": "76nwV8zz8tLz97SBRXH6uwHvgHXtqJDLQfF66jZhQ857", - "name": "Forbole Limited", - "description": "We are a pioneer in blockchain technology and UX solution. We provide enterprise-level network infrastructure and software development.", - "website": "https://forbole.com" - }, - { - "id": "7Hs9z4qsGCbQE9cy2aqgsvWupeZZGiKJgeb1eG4ZKYUH", - "name": "Kytzu", - "description": "Tendermint tech consultant and developer", - "website": "http://kytzu.com/" - }, - { - "id": "7PmWxxiTneGteGxEYvzj5pGDVMQ4nuN9DfUypEXmaA8o", - "name": "Syncnode SRL", - "description": "Syncnode is a software development and cyber security company with global presence and headquarters in Switzerland and Romania.", - "website": "https://wallet.syncnode.ro/" - }, - { - "id": "7VGU4ZwR1e1AFekqbqv2gvjeg47e1PwMPm4BfLt6rxNk", - "name": "stake.fish", - "description": "We are the leading staking service provider for blockchain projects. Join our community to stake and earn rewards. We know staking.", - "website": "https://stake.fish/" - }, - { - "id": "9GJmEHGom9eWo4np4L5vC6b6ri1Df2xN8KFoWixvD1Bs", - "name": "BL", - "description": "", - "website": "" - }, - { - "id": "9iEFfC6qCU6DBTWxL84YQdpNmpZ9yBBu4sW62yeiEVKe", - "name": "Izo Data Network", - "description": "If we could describe the business relationship we have with our clients in one word, it would be TRUST.", - "website": "http://www.izo.ro/" - }, - { - "id": "9sWYTuuR4s12Q4SuSfo5CfWaFggQwA6Z8pf8dWowN5rk", - "name": "Ubik Capital", - "description": "Ubik Capital's team priority is to provide a highly resilient and secure validator for the Solana community.", - "website": "https://ubik.capital/" - }, - { - "id": "9tedbEYypEKXAMkHcg42rn3fXY1B8hB6cdE3ZTFouXLL", - "name": "Stake Systems", - "description": "Building infrastructure to support awesome projects running in the blockchain landscape", - "website": "https://stake.systems" - }, - { - "id": "9v5gci7uDiaGKRmQ2dn6WJMB94YqFaVFBTiFzBzNhyaw", - "name": "Inotel", - "description": "", - "website": "" - }, - { - "id": "AGXZemZbyZjz5NBhufcob2pf8AXnr9HaGFUGNCfooWrB", - "name": "RockX", - "description": "RockX is a professional digital asset platform that aims to bring crypto investing to the mainstream in the smartest way.", - "website": "https://rockx.com/" - }, - { - "id": "ateamaZDqNWDztxnVKZhRsp4ac53KvT1rVKyU5LnL6o", - "name": "Node A-Team", - "description": "", - "website": "https://nodeateam.com/" - }, - { - "id": "beefKGBWeSpHzYBHZXwp5So7wdQGX6mu4ZHCsH3uTar", - "name": "Bison Trails", - "description": "We provide secure and reliable enterprise-grade infrastructure with multi-cloud / region distribution and a 99% node uptime guarantee", - "website": "https://bisontrails.co/" - }, - { - "id": "BH7asDZbKkTmT3UWiNfmMVRgQEEpXoVThGPmQfgWwDhg", - "name": "01Node", - "description": "01node | Professional Staking Services for Cosmos, Iris, Terra, Solana, Kava, Polkadot, Skale", - "website": "https://01node.com" - }, - { - "id": "BxFf75Vtzro2Hy3coFHKxFMZo5au8W7J8BmLC3gCMotU", - "name": "Chainode", - "description": "Chainode Tech is your reliable partner for distributed ledger technology (DLT) validation services and blockchain consultancy.", - "website": "https://chainode.tech/" - }, - { - "id": "CAf8jfgqhia5VNrEF4A7Y9VLD3numMq9DVSceq7cPhNY", - "name": "Chainflow", - "description": "Chainflow's a small, independent, capable & dedicated validator operator, started in 2017 by @cjremus, working to keep stake decentralized.", - "website": "https://chainflow.io/staking" - }, - { - "id": "CcaHc2L43ZWjwCHART3oZoJvHLAe9hzT2DJNUpBzoTN1", - "name": "Figment Networks", - "description": "", - "website": "https://figment.network/" - }, - { - "id": "CertusDeBmqN8ZawdkxK5kFGMwBXdudvWHYwtNgNhvLu", - "name": "Certus One", - "description": "", - "website": "https://certus.one" - }, - { - "id": "Chorus6Kis8tFHA7AowrPMcRJk3LbApHTYpgSNXzY5KE", - "name": "Chorus One", - "description": "Secure Solana and shape its future by delegating to Chorus One, a leading provider of validation infrastructure and staking services.", - "website": "https://chorus.one" - }, - { - "id": "CRzMxdyS56N2vkb55X5q155sSdVkjZhiFedWcbscCf7K", - "name": "HashQuark", - "description": "HashQuark is a new generation Proof-of-Stake pool focused on POS, DPOS and other public chains.", - "website": "https://hashquark.io/" - }, - { - "id": "D3DfFvmLBKkX9JJNEpJRXpM1pYTVPQ5dpPQRc9F49xk4", - "name": "Easy 2 Stake", - "description": "Professional PoS staking services.", - "website": "https://easy2stake.com/" - }, - { - "id": "DQ7D6ZRtKbBSxCcAunEkoTzQhCBKLPdzTjPRRnM6wo1f", - "name": "Stakewolf", - "description": "", - "website": "" - }, - { - "id": "DumiCKHVqoCQKD8roLApzR5Fit8qGV5fVQsJV9sTZk4a", - "name": "Staking Facilities", - "description": "Staking Facilities is a Munich-based staking service provider, who operates institutional-grade, custom built validator architecture.", - "website": "https://stakingfacilities.com/" - }, - { - "id": "edu1fZt5i82cFm6ujUoyXLMdujWxZyWYC8fkydWHRNT", - "name": "Solstaking", - "description": "Specialized in Solana Staking", - "website": "https://solstaking.com/" - }, - { - "id": "FKsC411dik9ktS6xPADxs4Fk2SCENvAiuccQHLAPndvk", - "name": "P2P.ORG - P2P Validator", - "description": "Secure Non-Custodial Staking", - "website": "https://p2p.org/" - }, - { - "id": "GB44NXtM7zGm6QnzQjzHZcRKSswkJbox8aJsKiXGbFJr", - "name": "Rustiq Technology", - "description": "", - "website": "" - }, - { - "id": "GhKEDkvGkf2kceG45ppzqnPD6BPXi1PyW1xGNWJdh5QW", - "name": "Stefan Condurachi", - "description": "I build & design intuitive blockchain tools.", - "website": "http://stefancondurachi.com/" - }, - { - "id": "GMpKrAwQ9oa4sJqEYQezLr8Z2TUAU72tXD4iMyfoJjbh", - "name": "Moonlet", - "description": "Moonlet operates nodes for next-generation blockchain networks and is committed to support the decentralisation movement forward.", - "website": "https://moonlet.xyz/" - }, - { - "id": "H3GhqPMwvGLdxWg3QJGjXDSkFSJCsFk3Wx9XBTdYZykc", - "name": "DokiaCapital", - "description": "We operate an enterprise-grade infrastructure that is robust and secure. Downtime is not an option for us. Stake now and manage your reward.", - "website": "https://staking.dokia.cloud/" - }, - { - "id": "LunaFpQkZsZVJL2P2BUqNDJqyVYqrw9buQnjQtMLXdK", - "name": "LunaNova Technologies", - "description": "Experienced team runs Solana-optimised, monitored hardware in top-tier UK datacentres - uptime guaranteed - 10% of our profits to charity", - "website": "https://lunanova.tech/" - }, - { - "id": "SFund7s2YPS7iCu7W2TobbuQEpVEAv9ZU7zHKiN1Gow", - "name": "Staking Fund", - "description": "", - "website": "https://staking.fund" - }, - { - "id": "D3DfFvmLBKkX9JJNEpJRXpM1pYTVPQ5dpPQRc9F49xk4", - "name": "Easy 2 Stake", - "description": "Easy.Stake.Trust. As easy and as simple as you would click next. Complete transparency and trust with a secure and stable validator.", - "website": "https://easy2stake.com" - }, - { - "id": "EFEKcHrUBsRoQkuTSJAQNZnj1u8h9oE4LoCYerednc3F", - "name": "Genesis Lab", - "description": "Genesis Lab is a validation nodes operator in PoS networks and blockchain-focused software development company", - "website": "https://genesislab.net" - } -] diff --git a/crypto/blockchains/sol/ext/validators.ts b/crypto/blockchains/sol/ext/validators.ts new file mode 100644 index 000000000..acfc9f4e1 --- /dev/null +++ b/crypto/blockchains/sol/ext/validators.ts @@ -0,0 +1,266 @@ +module.exports = [ + { + id: 'GA2t11gJcmuZ4y7pShTzgYDkxVaJaVQJqkVUqojhPPsT', + index: 1, + name: 'SolBrothers', + description: '', + website: '' + }, + { + id: '9QU2QSxhb24FUX3Tu2FpczXjpK3VYrvRudywSZaM29mF', + index: 2, + name: 'Everstake', + description: + 'Everstake is a team of seasoned developers, financial experts and blockchain enthusiasts. We run secure and reliable nodes for PoS protocols', + website: 'https://everstake.one' + }, + { + id: '2het6nBRLq9LLZER8fqUEk7j5pbLxq2mVGqSse2nS3tf', + name: "Maggie's Crypto Farm", + description: 'Making the blocks, all day and all night.', + website: 'https://mcf.rocks/' + }, + { + id: '3r5ZXC1yFqMmk8VwDdUJbEdPmZ8KZvEkzd5ThEYRetTk', + name: 'Vnode', + description: 'We are the staking service provider for blockchain projects.', + website: '' + }, + { + id: '49DJjUX3cwFvaZD5rCAwubiz7qdRWDez9xmB381XdHru', + name: 'Staker Space', + description: 'Secure Validating Services', + website: 'https://staker.space' + }, + { + id: '4PsiLMyoUQ7QRn1FFiFCvej4hsUTFzfvJnyN4bj1tmSN', + name: 'Stakin', + description: 'Your Trusted Crypto Rewards', + website: 'https://stakin.com/' + }, + { + id: '51JBzSTU5rAM8gLAVQKgp4WoZerQcSqWC7BitBzgUNAm', + name: 'Staked', + description: + 'Staked operates highly reliable and secure staking infrastructure for 20+ PoS protocols on behalf of the leading investors in the market.', + website: 'https://staked.us' + }, + { + id: '6UDU4Z9TTbYy8gcRKBd7RX3Lm2qMsSR4PMuzoyYPzLma', + name: 'dcipher', + description: + 'dCipher is a company that has the purpose to combine emerging technologies like Blockchain, IoT and Artificial Intelligence.', + website: 'https://dcipher.io' + }, + { + id: '76nwV8zz8tLz97SBRXH6uwHvgHXtqJDLQfF66jZhQ857', + name: 'Forbole Limited', + description: + 'We are a pioneer in blockchain technology and UX solution. We provide enterprise-level network infrastructure and software development.', + website: 'https://forbole.com' + }, + { + id: '7Hs9z4qsGCbQE9cy2aqgsvWupeZZGiKJgeb1eG4ZKYUH', + name: 'Kytzu', + description: 'Tendermint tech consultant and developer', + website: 'http://kytzu.com/' + }, + { + id: '7PmWxxiTneGteGxEYvzj5pGDVMQ4nuN9DfUypEXmaA8o', + name: 'Syncnode SRL', + description: + 'Syncnode is a software development and cyber security company with global presence and headquarters in Switzerland and Romania.', + website: 'https://wallet.syncnode.ro/' + }, + { + id: '7VGU4ZwR1e1AFekqbqv2gvjeg47e1PwMPm4BfLt6rxNk', + name: 'stake.fish', + description: + 'We are the leading staking service provider for blockchain projects. Join our community to stake and earn rewards. We know staking.', + website: 'https://stake.fish/' + }, + { + id: '9GJmEHGom9eWo4np4L5vC6b6ri1Df2xN8KFoWixvD1Bs', + name: 'BL', + description: '', + website: '' + }, + { + id: '9iEFfC6qCU6DBTWxL84YQdpNmpZ9yBBu4sW62yeiEVKe', + name: 'Izo Data Network', + description: + 'If we could describe the business relationship we have with our clients in one word, it would be TRUST.', + website: 'http://www.izo.ro/' + }, + { + id: '9sWYTuuR4s12Q4SuSfo5CfWaFggQwA6Z8pf8dWowN5rk', + name: 'Ubik Capital', + description: + "Ubik Capital's team priority is to provide a highly resilient and secure validator for the Solana community.", + website: 'https://ubik.capital/' + }, + { + id: '9tedbEYypEKXAMkHcg42rn3fXY1B8hB6cdE3ZTFouXLL', + name: 'Stake Systems', + description: + 'Building infrastructure to support awesome projects running in the blockchain landscape', + website: 'https://stake.systems' + }, + { + id: '9v5gci7uDiaGKRmQ2dn6WJMB94YqFaVFBTiFzBzNhyaw', + name: 'Inotel', + description: '', + website: '' + }, + { + id: 'AGXZemZbyZjz5NBhufcob2pf8AXnr9HaGFUGNCfooWrB', + name: 'RockX', + description: + 'RockX is a professional digital asset platform that aims to bring crypto investing to the mainstream in the smartest way.', + website: 'https://rockx.com/' + }, + { + id: 'ateamaZDqNWDztxnVKZhRsp4ac53KvT1rVKyU5LnL6o', + name: 'Node A-Team', + description: '', + website: 'https://nodeateam.com/' + }, + { + id: 'beefKGBWeSpHzYBHZXwp5So7wdQGX6mu4ZHCsH3uTar', + name: 'Bison Trails', + description: + 'We provide secure and reliable enterprise-grade infrastructure with multi-cloud / region distribution and a 99% node uptime guarantee', + website: 'https://bisontrails.co/' + }, + { + id: 'BH7asDZbKkTmT3UWiNfmMVRgQEEpXoVThGPmQfgWwDhg', + name: '01Node', + description: + '01node | Professional Staking Services for Cosmos, Iris, Terra, Solana, Kava, Polkadot, Skale', + website: 'https://01node.com' + }, + { + id: 'BxFf75Vtzro2Hy3coFHKxFMZo5au8W7J8BmLC3gCMotU', + name: 'Chainode', + description: + 'Chainode Tech is your reliable partner for distributed ledger technology (DLT) validation services and blockchain consultancy.', + website: 'https://chainode.tech/' + }, + { + id: 'CAf8jfgqhia5VNrEF4A7Y9VLD3numMq9DVSceq7cPhNY', + name: 'Chainflow', + description: + "Chainflow's a small, independent, capable & dedicated validator operator, started in 2017 by @cjremus, working to keep stake decentralized.", + website: 'https://chainflow.io/staking' + }, + { + id: 'CcaHc2L43ZWjwCHART3oZoJvHLAe9hzT2DJNUpBzoTN1', + name: 'Figment Networks', + description: '', + website: 'https://figment.network/' + }, + { + id: 'CertusDeBmqN8ZawdkxK5kFGMwBXdudvWHYwtNgNhvLu', + name: 'Certus One', + description: '', + website: 'https://certus.one' + }, + { + id: 'Chorus6Kis8tFHA7AowrPMcRJk3LbApHTYpgSNXzY5KE', + name: 'Chorus One', + description: + 'Secure Solana and shape its future by delegating to Chorus One, a leading provider of validation infrastructure and staking services.', + website: 'https://chorus.one' + }, + { + id: 'CRzMxdyS56N2vkb55X5q155sSdVkjZhiFedWcbscCf7K', + name: 'HashQuark', + description: + 'HashQuark is a new generation Proof-of-Stake pool focused on POS, DPOS and other public chains.', + website: 'https://hashquark.io/' + }, + { + id: 'D3DfFvmLBKkX9JJNEpJRXpM1pYTVPQ5dpPQRc9F49xk4', + name: 'Easy 2 Stake', + description: 'Professional PoS staking services.', + website: 'https://easy2stake.com/' + }, + { + id: 'DQ7D6ZRtKbBSxCcAunEkoTzQhCBKLPdzTjPRRnM6wo1f', + name: 'Stakewolf', + description: '', + website: '' + }, + { + id: 'DumiCKHVqoCQKD8roLApzR5Fit8qGV5fVQsJV9sTZk4a', + name: 'Staking Facilities', + description: + 'Staking Facilities is a Munich-based staking service provider, who operates institutional-grade, custom built validator architecture.', + website: 'https://stakingfacilities.com/' + }, + { + id: 'edu1fZt5i82cFm6ujUoyXLMdujWxZyWYC8fkydWHRNT', + name: 'Solstaking', + description: 'Specialized in Solana Staking', + website: 'https://solstaking.com/' + }, + { + id: 'FKsC411dik9ktS6xPADxs4Fk2SCENvAiuccQHLAPndvk', + name: 'P2P.ORG - P2P Validator', + description: 'Secure Non-Custodial Staking', + website: 'https://p2p.org/' + }, + { + id: 'GB44NXtM7zGm6QnzQjzHZcRKSswkJbox8aJsKiXGbFJr', + name: 'Rustiq Technology', + description: '', + website: '' + }, + { + id: 'GhKEDkvGkf2kceG45ppzqnPD6BPXi1PyW1xGNWJdh5QW', + name: 'Stefan Condurachi', + description: 'I build & design intuitive blockchain tools.', + website: 'http://stefancondurachi.com/' + }, + { + id: 'GMpKrAwQ9oa4sJqEYQezLr8Z2TUAU72tXD4iMyfoJjbh', + name: 'Moonlet', + description: + 'Moonlet operates nodes for next-generation blockchain networks and is committed to support the decentralisation movement forward.', + website: 'https://moonlet.xyz/' + }, + { + id: 'H3GhqPMwvGLdxWg3QJGjXDSkFSJCsFk3Wx9XBTdYZykc', + name: 'DokiaCapital', + description: + 'We operate an enterprise-grade infrastructure that is robust and secure. Downtime is not an option for us. Stake now and manage your reward.', + website: 'https://staking.dokia.cloud/' + }, + { + id: 'LunaFpQkZsZVJL2P2BUqNDJqyVYqrw9buQnjQtMLXdK', + name: 'LunaNova Technologies', + description: + 'Experienced team runs Solana-optimised, monitored hardware in top-tier UK datacentres - uptime guaranteed - 10% of our profits to charity', + website: 'https://lunanova.tech/' + }, + { + id: 'SFund7s2YPS7iCu7W2TobbuQEpVEAv9ZU7zHKiN1Gow', + name: 'Staking Fund', + description: '', + website: 'https://staking.fund' + }, + { + id: 'D3DfFvmLBKkX9JJNEpJRXpM1pYTVPQ5dpPQRc9F49xk4', + name: 'Easy 2 Stake', + description: + 'Easy.Stake.Trust. As easy and as simple as you would click next. Complete transparency and trust with a secure and stable validator.', + website: 'https://easy2stake.com' + }, + { + id: 'EFEKcHrUBsRoQkuTSJAQNZnj1u8h9oE4LoCYerednc3F', + name: 'Genesis Lab', + description: + 'Genesis Lab is a validation nodes operator in PoS networks and blockchain-focused software development company', + website: 'https://genesislab.net' + } +]; diff --git a/crypto/blockchains/sol/stores/SolTmpDS.js b/crypto/blockchains/sol/stores/SolTmpDS.ts similarity index 100% rename from crypto/blockchains/sol/stores/SolTmpDS.js rename to crypto/blockchains/sol/stores/SolTmpDS.ts diff --git a/crypto/blockchains/trx/TrxAddressProcessor.js b/crypto/blockchains/trx/TrxAddressProcessor.ts similarity index 100% rename from crypto/blockchains/trx/TrxAddressProcessor.js rename to crypto/blockchains/trx/TrxAddressProcessor.ts diff --git a/crypto/blockchains/trx/TrxScannerProcessor.js b/crypto/blockchains/trx/TrxScannerProcessor.ts similarity index 85% rename from crypto/blockchains/trx/TrxScannerProcessor.js rename to crypto/blockchains/trx/TrxScannerProcessor.ts index c933bead2..6f9c49314 100644 --- a/crypto/blockchains/trx/TrxScannerProcessor.js +++ b/crypto/blockchains/trx/TrxScannerProcessor.ts @@ -10,16 +10,37 @@ import TrxTransactionsTrc20Provider from './basic/TrxTransactionsTrc20Provider'; import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog'; import Database from '@app/appstores/DataSource/Database/main'; import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; -import config from '@app/config/config'; import BlocksoftUtils from '@crypto/common/BlocksoftUtils'; import transactionDS from '@app/appstores/DataSource/Transaction/Transaction'; import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings'; import TronStakeUtils from '@crypto/blockchains/trx/ext/TronStakeUtils'; +import config from '@constants/config'; let CACHE_PENDING_TXS = false; +interface TronScannerSettings { + tokenName?: string; + currencyCode: string; +} + +interface TronBalanceResult { + balance: string; + frozen: string; + frozenEnergy: string; + balanceStaked: string; + unconfirmed: number; + provider: string; +} + export default class TrxScannerProcessor { - constructor(settings) { + private _settings: TronScannerSettings; + private _tokenName: string; + private _tronscanProvider: TrxTronscanProvider; + private _trongridProvider: TrxTrongridProvider; + private _transactionsProvider: TrxTransactionsProvider; + private _transactionsTrc20Provider: TrxTransactionsTrc20Provider; + + constructor(settings: TronScannerSettings) { this._settings = settings; this._tokenName = '_'; if (typeof settings.tokenName !== 'undefined') { @@ -31,7 +52,7 @@ export default class TrxScannerProcessor { this._transactionsTrc20Provider = new TrxTransactionsTrc20Provider(); } - async isMultisigBlockchain(address) { + async isMultisigBlockchain(address: string): Promise { address = address.trim(); let addressHex = address; if (address.substr(0, 1) === 'T') { @@ -40,12 +61,12 @@ export default class TrxScannerProcessor { return this._trongridProvider.isMultisigTrongrid(addressHex); } - /** - * https://developers.tron.network/reference#addresses-accounts - * @param {string} address - * @return {Promise<{balance, frozen, frozenEnergy, balanceStaked, unconfirmed, provider}>} - */ - async getBalanceBlockchain(address, jsonData, walletHash, source) { + async getBalanceBlockchain( + address: string, + jsonData: any, + walletHash: string, + source: string + ): Promise { address = address.trim(); BlocksoftCryptoLog.log( this._tokenName + @@ -62,8 +83,8 @@ export default class TrxScannerProcessor { } const useTronscan = BlocksoftExternalSettings.getStatic('TRX_USE_TRONSCAN') * 1 > 0; - let result = false; - let subresult = false; + let result: TronBalanceResult | boolean = false; + let subresult: boolean | TronBalanceResult = false; if (useTronscan) { result = await this._tronscanProvider.get( address, @@ -81,7 +102,7 @@ export default class TrxScannerProcessor { ); } - if (result === false || result === 0) { + if (!result) { if (this._tokenName !== '_' && this._tokenName.substr(0, 1) === 'T') { // https://developers.tron.network/docs/trc20-contract-interaction#balanceof try { @@ -110,6 +131,10 @@ export default class TrxScannerProcessor { source ); return { + // TODO + balanceStaked: '', + frozen: '', + frozenEnergy: '', balance: BlocksoftUtils.hexToDecimal( '0x' + tmp.data.constant_result ), @@ -117,7 +142,7 @@ export default class TrxScannerProcessor { provider: 'tronwallet-raw-call' }; } - } catch (e) { + } catch (e: any) { BlocksoftCryptoLog.log( this._tokenName + ' TrxScannerProcessor getBalanceBlockchain address ' + @@ -161,7 +186,7 @@ export default class TrxScannerProcessor { source ); - if (subresult !== false) { + if (subresult) { BlocksoftCryptoLog.log( this._tokenName + ' TrxScannerProcessor getBalanceBlockchain address ' + @@ -172,30 +197,30 @@ export default class TrxScannerProcessor { source ); return { - balance: 0, + frozen: '', + frozenEnergy: '', + balance: '0', unconfirmed: 0, - balanceStaked: 0, - balanceAvailable: 0, + balanceStaked: '0', + balanceAvailable: '0', provider: 'tronscan-ok-but-no-token' }; } } - result.balanceStaked = + + (result as TronBalanceResult).balanceStaked = typeof result.frozen !== 'undefined' - ? result.frozen * 1 + result.frozenEnergy * 1 - : 0; - result.balanceAvailable = result.balance; - if (result.balanceStaked * 1 > 0) { - result.balance = result.balance * 1 + result.balanceStaked * 1; + ? (result.frozen as any) * 1 + (result.frozenEnergy as any) * 1 + : '0'; + (result as TronBalanceResult).balanceAvailable = (result as any).balance; + if ((result as any).balanceStaked * 1 > 0) { + (result as any).balance = + (result as any).balance * 1 + (result as any).balanceStaked * 1; } - return result; + return result as TronBalanceResult; } - /** - * @param {string} address - * @return {Promise<*>} - */ - async getResourcesBlockchain(address) { + async getResourcesBlockchain(address: string): Promise { address = address.trim(); BlocksoftCryptoLog.log( this._tokenName + @@ -213,12 +238,10 @@ export default class TrxScannerProcessor { return result; } - /** - * https://github.com/jakeonchain/tron-wallet-chrome/blob/fecea42771cc5cbda3fada4a1c8cfe8de251c008/src/App.js - * @param {string} scanData.account.address - * @return {Promise<[UnifiedTransaction]>} - */ - async getTransactionsBlockchain(scanData, source) { + async getTransactionsBlockchain( + scanData: { account: { address: string } }, + source: string + ): Promise { let result; let lastBlock = false; if (this._tokenName[0] === 'T') { @@ -237,10 +260,10 @@ export default class TrxScannerProcessor { } async resetTransactionsPendingBlockchain( - scanData, - source, + scanData: any, + source: string, lastBlock = false - ) { + ): Promise { CACHE_PENDING_TXS = scanData.resetTime || 0; if ( typeof scanData.specialActionNeeded !== 'undefined' && @@ -248,7 +271,7 @@ export default class TrxScannerProcessor { typeof scanData.account.address !== 'undefined' ) { await Database.query(` - UPDATE transactions SET special_action_needed='' + UPDATE transactions SET special_action_needed='' WHERE special_action_needed='${scanData.specialActionNeeded}' AND address_from_basic='${scanData.account.address}' `); @@ -256,7 +279,11 @@ export default class TrxScannerProcessor { return false; } - async getTransactionsPendingBlockchain(scanData, source, lastBlock = false) { + async getTransactionsPendingBlockchain( + scanData: any, + source: string, + lastBlock = false + ): Promise { if ( CACHE_PENDING_TXS > 0 && CACHE_PENDING_TXS - new Date().getTime() < 60000 @@ -264,20 +291,20 @@ export default class TrxScannerProcessor { return false; } // id, transaction_hash, block_number, block_confirmations, transaction_status, - const sql = `SELECT t.id, + const sql = `SELECT t.id, t.wallet_hash AS walletHash, t.transaction_hash AS transactionHash, - t.transactions_scan_log AS transactionsScanLog, + t.transactions_scan_log AS transactionsScanLog, t.address_from_basic AS addressFromBasic, t.special_action_needed AS specialActionNeeded, a.derivation_path AS derivationPath FROM transactions AS t LEFT JOIN account AS a ON a.address = t.address_from_basic - WHERE + WHERE (t.currency_code='${this._settings.currencyCode}' OR t.currency_code LIKE 'TRX%') AND t.transaction_of_trustee_wallet=1 AND (t.block_number IS NULL OR t.block_number<20 OR t.special_action_needed='vote' OR t.special_action_needed='vote_after_unfreeze') - + ORDER BY created_at DESC LIMIT 10 `; @@ -294,7 +321,7 @@ export default class TrxScannerProcessor { const sendLink = BlocksoftExternalSettings.getStatic('TRX_SEND_LINK'); let needUpdateBalance = -1; - if (lastBlock === false) { + if (!lastBlock) { needUpdateBalance = 0; try { const link2 = sendLink + '/wallet/getnowblock'; @@ -317,7 +344,7 @@ export default class TrxScannerProcessor { } } - const unique = {}; + const unique: { [key: string]: any } = {}; for (const row of res.array) { const linkRecheck = sendLink + '/wallet/gettransactioninfobyid'; try { @@ -433,7 +460,11 @@ export default class TrxScannerProcessor { return needUpdateBalance > 0; } - async _unifyFromReceipt(transaction, row, lastBlock) { + async _unifyFromReceipt( + transaction: any, + row: any, + lastBlock: number + ): Promise { /** * {"id":"fb7580e4bb6161e0812beb05cf4a1b6463ba55e33def5dd7f3f5c1561c91a49e","blockNumber":29134019,"blockTimeStamp":1617823467000, * "receipt":{'origin_energy_usage":4783,"energy_usage_total":4783,"net_usage":345,"result":"OUT_OF_ENERGY'}, diff --git a/crypto/blockchains/trx/TrxTokenProcessor.js b/crypto/blockchains/trx/TrxTokenProcessor.js deleted file mode 100644 index 4770bdddc..000000000 --- a/crypto/blockchains/trx/TrxTokenProcessor.js +++ /dev/null @@ -1,55 +0,0 @@ -/** - * @version 0.5 - * https://apilist.tronscan.org/api/contract?contract=TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t - * [ { address: 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t', balance: 7208332710, verify_status: 0, balanceInUsd: 0, trxCount: 758742, date_created: 1555400628000, creator: [Object] } ] } - */ -import BlocksoftAxios from '../../common/BlocksoftAxios' - -export default class TrxTokenProcessor { - constructor() { - this._tokenTronscanPath20 = 'https://apilist.tronscan.org/api/token_trc20?contract=' - this._tokenTronscanPath10 = 'https://apilist.tronscan.org/api/token?id=' - } - - /** - * https://apilist.tronscan.org/api/token_trc20?contract=TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t - * @param {string} tokenAddress - * @returns {Promise<{tokenAddress: *, currencyName: *, provider: string, tokenDecimals: *, icon: *, description: *, tokenType: string, currencyCode: *}|boolean>} - */ - async getTokenDetails(tokenAddress) { - if (tokenAddress[0] === 'T') { - const res = await BlocksoftAxios.get(this._tokenTronscanPath20 + tokenAddress) - if (typeof res.data.trc20_tokens[0] !== 'undefined') { - const tmp = res.data.trc20_tokens[0] - return { - currencyCodePrefix : 'CUSTOM_TRX_', - currencyCode: tmp.symbol, - currencyName: tmp.name, - tokenType : 'TRX', // 'TRX' - tokenAddress: tmp.contract_address, - tokenDecimals: tmp.decimals, - icon: tmp.icon_url, - description: tmp.token_desc, - provider : 'tronscan20' - } - } - } else { - const res = await BlocksoftAxios.get(this._tokenTronscanPath10 + tokenAddress) - if (typeof res.data.data[0] !== 'undefined') { - const tmp = res.data.data[0] - return { - currencyCodePrefix : 'CUSTOM_TRX_', - currencyCode: tmp.abbr, - currencyName: tmp.name, - tokenType : 'TRX', // 'TRX' - tokenAddress: tmp.tokenID, - tokenDecimals: tmp.precision, - icon: tmp.imgUrl, - description: tmp.description, - provider : 'tronscan10' - } - } - } - return false - } -} diff --git a/crypto/blockchains/trx/TrxTokenProcessor.ts b/crypto/blockchains/trx/TrxTokenProcessor.ts new file mode 100644 index 000000000..bdf30e46d --- /dev/null +++ b/crypto/blockchains/trx/TrxTokenProcessor.ts @@ -0,0 +1,75 @@ +/** + * @version 0.5 + * https://apilist.tronscan.org/api/contract?contract=TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t + * [ { address: 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t', balance: 7208332710, verify_status: 0, balanceInUsd: 0, trxCount: 758742, date_created: 1555400628000, creator: [Object] } ] } + */ +import BlocksoftAxios from '../../common/BlocksoftAxios'; + +interface TokenDetails { + currencyCodePrefix: string; + currencyCode: string; + currencyName: string; + tokenType: string; + tokenAddress: string; + tokenDecimals: number; + icon: string; + description: string; + provider: string; +} + +export default class TrxTokenProcessor { + private _tokenTronscanPath20: string; + private _tokenTronscanPath10: string; + + constructor() { + this._tokenTronscanPath20 = + 'https://apilist.tronscan.org/api/token_trc20?contract='; + this._tokenTronscanPath10 = 'https://apilist.tronscan.org/api/token?id='; + } + + /** + * https://apilist.tronscan.org/api/token_trc20?contract=TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t + * @param {string} tokenAddress + * @returns {Promise} + */ + async getTokenDetails(tokenAddress: string): Promise { + if (tokenAddress[0] === 'T') { + const res = await BlocksoftAxios.get( + this._tokenTronscanPath20 + tokenAddress + ); + if (typeof res.data[0] !== 'undefined') { + const tmp = res.data[0]; + return { + currencyCodePrefix: 'CUSTOM_TRX_', + currencyCode: tmp.symbol, + currencyName: tmp.name, + tokenType: 'TRX', // 'TRX' + tokenAddress: tmp.contract_address, + tokenDecimals: tmp.decimals, + icon: tmp.icon_url, + description: tmp.token_desc, + provider: 'tronscan20' + }; + } + } else { + const res = await BlocksoftAxios.get( + this._tokenTronscanPath10 + tokenAddress + ); + if (typeof res.data[0] !== 'undefined') { + const tmp = res.data[0]; + return { + currencyCodePrefix: 'CUSTOM_TRX_', + currencyCode: tmp.abbr, + currencyName: tmp.name, + tokenType: 'TRX', // 'TRX' + tokenAddress: tmp.tokenID, + tokenDecimals: tmp.precision, + icon: tmp.imgUrl, + description: tmp.description, + provider: 'tronscan10' + }; + } + } + return false; + } +} diff --git a/crypto/blockchains/trx/TrxTransferProcessor.ts b/crypto/blockchains/trx/TrxTransferProcessor.ts index a8aa37190..f51d2f019 100644 --- a/crypto/blockchains/trx/TrxTransferProcessor.ts +++ b/crypto/blockchains/trx/TrxTransferProcessor.ts @@ -11,8 +11,6 @@ import TrxTronscanProvider from './basic/TrxTronscanProvider'; import TrxTrongridProvider from './basic/TrxTrongridProvider'; import TrxSendProvider from '@crypto/blockchains/trx/providers/TrxSendProvider'; -import BlocksoftDispatcher from '../BlocksoftDispatcher'; -import config from '@app/config/config'; import { strings, sublocale } from '@app/services/i18n'; import settingsActions from '@app/appstores/Stores/Settings/SettingsActions'; @@ -20,6 +18,8 @@ import MarketingEvent from '@app/services/Marketing/MarketingEvent'; import BlocksoftTransactions from '@crypto/actions/BlocksoftTransactions/BlocksoftTransactions'; import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings'; import BlocksoftBalances from '@crypto/actions/BlocksoftBalances/BlocksoftBalances'; +import BlocksoftDispatcher from '@lib/BlocksoftDispatcher'; +import config from '@constants/config'; // https://developers.tron.network/docs/parameter-and-return-value-encoding-and-decoding const ethers = require('ethers'); @@ -63,7 +63,6 @@ export default class TrxTransferProcessor async checkTransferHasError( data: BlocksoftBlockchainTypes.CheckTransferHasErrorData ): Promise { - // @ts-ignore if (!this._isToken20 || (data.amount && data.amount * 1 > 0)) { return { isOk: true }; } @@ -194,7 +193,7 @@ export default class TrxTransferProcessor }; } return result as BlocksoftBlockchainTypes.FeeRateResult; - } catch (e) { + } catch (e: any) { if (e.message.indexOf('SERVER_RESPONSE_') === 0) { throw e; } @@ -344,7 +343,7 @@ export default class TrxTransferProcessor ); } } - } catch (e) { + } catch (e: any) { // do nothing if (config.debug.cryptoErrors) { console.log( @@ -398,7 +397,7 @@ export default class TrxTransferProcessor if (typeof tronData2.freeNetLimit === 'undefined') { feeForTx = feeForTx * 1 + 1100000; } - } catch (e) { + } catch (e: any) { // do nothing if (config.debug.cryptoErrors) { console.log( @@ -419,6 +418,7 @@ export default class TrxTransferProcessor let amountForTx = data.amount; let selectedTransferAllBalance = data.amount; if (this._tokenName === '_') { + // tslint:disable-next-line:no-shadowed-variable const balance = await BlocksoftBalances.setCurrencyCode('TRX') .setAddress(data.addressFrom) .getBalance('TrxSendTx'); @@ -431,7 +431,7 @@ export default class TrxTransferProcessor balance.balance, feeForTx ); - let test = BlocksoftUtils.diff(data.amount, feeForTx); + const test = BlocksoftUtils.diff(data.amount, feeForTx); if (test * 1 > balance.balance * 1) { amountForTx = selectedTransferAllBalance; } @@ -455,7 +455,7 @@ export default class TrxTransferProcessor */ result.selectedFeeIndex = 0; } - } catch (e) { + } catch (e: any) { if (e.message.indexOf('SERVER_RESPONSE_') === 0) { throw e; } @@ -485,8 +485,8 @@ export default class TrxTransferProcessor // @ts-ignore await BlocksoftCryptoLog.log( this._settings.currencyCode + - ' TrxTransferProcessor.getTransferAllBalance ', - data.addressFrom + ' => ' + balance + ` TrxTransferProcessor.getTransferAllBalance ', + ${data.addressFrom + ' => ' + balance}` ); // noinspection EqualityComparisonWithCoercionJS if (balance === '0') { @@ -564,7 +564,9 @@ export default class TrxTransferProcessor if (typeof data.blockchainData !== 'undefined' && data.blockchainData) { tx = data.blockchainData; } else { - let link, res, params; + let link; + let res; + let params; if (typeof data.dexOrderData !== 'undefined' && data.dexOrderData) { // {"tokenContract":"41a2726afbecbd8e936000ed684cef5e2f5cf43008","contractMethod":"trxToTokenSwapInput(uint256)","options":{"callValue":"1000000"},"params":[{"type":"uint256","value":"116256"}]} @@ -573,12 +575,13 @@ export default class TrxTransferProcessor const abiCoder = new AbiCoder(); try { ownerAddress = TronUtils.addressToHex(data.addressFrom); - } catch (e) { + } catch (e: any) { e.message += ' inside TronUtils.addressToHex owner_address ' + data.addressFrom; throw e; } + // tslint:disable-next-line:no-shadowed-variable const link = sendLink + '/wallet/triggersmartcontract'; const total = data.dexOrderData.length; let index = 0; @@ -591,7 +594,8 @@ export default class TrxTransferProcessor const values = []; try { for (const tmp of order.params) { - let type, value; + let type; + let value; try { type = tmp.type; value = tmp.value; @@ -628,6 +632,7 @@ export default class TrxTransferProcessor } } + // tslint:disable-next-line:no-shadowed-variable let params; try { params = { @@ -681,7 +686,7 @@ export default class TrxTransferProcessor this._settings.currencyCode + ' TrxTxProcessor.sendSubTx broadcasted' ); - } catch (e) { + } catch (e: any) { if (config.debug.cryptoErrors) { console.log( this._settings.currencyCode + @@ -751,7 +756,7 @@ export default class TrxTransferProcessor break; } } - } catch (e1) { + } catch (e1: any) { if (config.debug.cryptoErrors) { console.log( this._settings.currencyCode + @@ -784,7 +789,7 @@ export default class TrxTransferProcessor try { toAddress = TronUtils.addressToHex(data.addressTo); - } catch (e) { + } catch (e: any) { e.message += ' inside TronUtils.addressToHex to_address ' + data.addressTo; throw e; @@ -806,7 +811,7 @@ export default class TrxTransferProcessor try { ownerAddress = TronUtils.addressToHex(data.addressFrom); - } catch (e) { + } catch (e: any) { e.message += ' inside TronUtils.addressToHex owner_address ' + data.addressFrom; throw e; @@ -869,7 +874,7 @@ export default class TrxTransferProcessor params ); res = await BlocksoftAxios.post(link, params); - } catch (e) { + } catch (e: any) { await BlocksoftCryptoLog.log( this._settings.currencyCode + ' TrxTransferProcessor.sendTx result2' + @@ -985,7 +990,7 @@ export default class TrxTransferProcessor await BlocksoftCryptoLog.log( this._settings.currencyCode + ' TrxTxProcessor.sendTx broadcasted' ); - } catch (e) { + } catch (e: any) { if (config.debug.cryptoErrors) { console.log( this._settings.currencyCode + ' TrxTransferProcessor.sendTx error', diff --git a/crypto/blockchains/trx/basic/TrxNodeInfoProvider.js b/crypto/blockchains/trx/basic/TrxNodeInfoProvider.ts similarity index 100% rename from crypto/blockchains/trx/basic/TrxNodeInfoProvider.js rename to crypto/blockchains/trx/basic/TrxNodeInfoProvider.ts diff --git a/crypto/blockchains/trx/basic/TrxTransactionsProvider.js b/crypto/blockchains/trx/basic/TrxTransactionsProvider.ts similarity index 100% rename from crypto/blockchains/trx/basic/TrxTransactionsProvider.js rename to crypto/blockchains/trx/basic/TrxTransactionsProvider.ts diff --git a/crypto/blockchains/trx/basic/TrxTransactionsTrc20Provider.js b/crypto/blockchains/trx/basic/TrxTransactionsTrc20Provider.ts similarity index 100% rename from crypto/blockchains/trx/basic/TrxTransactionsTrc20Provider.js rename to crypto/blockchains/trx/basic/TrxTransactionsTrc20Provider.ts diff --git a/crypto/blockchains/trx/basic/TrxTrongridProvider.js b/crypto/blockchains/trx/basic/TrxTrongridProvider.ts similarity index 100% rename from crypto/blockchains/trx/basic/TrxTrongridProvider.js rename to crypto/blockchains/trx/basic/TrxTrongridProvider.ts diff --git a/crypto/blockchains/trx/basic/TrxTronscanProvider.js b/crypto/blockchains/trx/basic/TrxTronscanProvider.ts similarity index 100% rename from crypto/blockchains/trx/basic/TrxTronscanProvider.js rename to crypto/blockchains/trx/basic/TrxTronscanProvider.ts diff --git a/crypto/blockchains/trx/dict/swaps.js b/crypto/blockchains/trx/dict/swaps.js deleted file mode 100644 index 51da6f4a9..000000000 --- a/crypto/blockchains/trx/dict/swaps.js +++ /dev/null @@ -1,12 +0,0 @@ -module.exports = { - TTnSHzUoho1CU6zFYVzVSCKq8EX8ZddkVv: 'JUSTSWAP-WTRX-TRX', - TJmTeYk5zmg8pNPGYbDb2psadwVLYDDYDr: 'JUSTSWAP-DICE-TRX', - TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE: 'JUSTSWAP-USDT-TRX', - TQcia2H2TU3WrFk9sKtdK9qCfkW8XirfPQ: 'JUSTSWAP-USDJ-TRX', - TLLBBiX3HqVZZsUQTBXgurA3pdw317PmjM: 'JUSTSWAP-HT-TRX', - TYN6Wh11maRfzgG7n5B6nM5VW1jfGs9chu: 'JUSTSWAP-WIN-TRX', - TUEYcyPAqc4hTg1fSuBCPc18vGWcJDECVw: 'JUSTSWAP-SUN-TRX', - TKAtLoCB529zusLfLVkGvLNis6okwjB7jf: 'JUSTSWAP-BTC-TRX', - TYukBQZ2XXCcRCReAUguyXncCWNY9CEiDQ: 'JUSTSWAP-JST-TRX', - TH2mEwTKNgtg8psR6Qx2RBUXZ48Lon1ygu: 'JUSTSWAP-WBTT-TRX' -} diff --git a/crypto/blockchains/trx/dict/swaps.ts b/crypto/blockchains/trx/dict/swaps.ts new file mode 100644 index 000000000..7b0f57045 --- /dev/null +++ b/crypto/blockchains/trx/dict/swaps.ts @@ -0,0 +1,12 @@ +module.exports = { + TTnSHzUoho1CU6zFYVzVSCKq8EX8ZddkVv: 'JUSTSWAP-WTRX-TRX', + TJmTeYk5zmg8pNPGYbDb2psadwVLYDDYDr: 'JUSTSWAP-DICE-TRX', + TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE: 'JUSTSWAP-USDT-TRX', + TQcia2H2TU3WrFk9sKtdK9qCfkW8XirfPQ: 'JUSTSWAP-USDJ-TRX', + TLLBBiX3HqVZZsUQTBXgurA3pdw317PmjM: 'JUSTSWAP-HT-TRX', + TYN6Wh11maRfzgG7n5B6nM5VW1jfGs9chu: 'JUSTSWAP-WIN-TRX', + TUEYcyPAqc4hTg1fSuBCPc18vGWcJDECVw: 'JUSTSWAP-SUN-TRX', + TKAtLoCB529zusLfLVkGvLNis6okwjB7jf: 'JUSTSWAP-BTC-TRX', + TYukBQZ2XXCcRCReAUguyXncCWNY9CEiDQ: 'JUSTSWAP-JST-TRX', + TH2mEwTKNgtg8psR6Qx2RBUXZ48Lon1ygu: 'JUSTSWAP-WBTT-TRX' +}; diff --git a/crypto/blockchains/trx/ext/TronStakeUtils.js b/crypto/blockchains/trx/ext/TronStakeUtils.ts similarity index 96% rename from crypto/blockchains/trx/ext/TronStakeUtils.js rename to crypto/blockchains/trx/ext/TronStakeUtils.ts index 7827c2c34..25de9eb00 100644 --- a/crypto/blockchains/trx/ext/TronStakeUtils.js +++ b/crypto/blockchains/trx/ext/TronStakeUtils.ts @@ -1,172 +1,172 @@ -import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings'; -import BlocksoftPrettyNumbers from '@crypto/common/BlocksoftPrettyNumbers'; -import BlocksoftBalances from '@crypto/actions/BlocksoftBalances/BlocksoftBalances'; -import TronUtils from '@crypto/blockchains/trx/ext/TronUtils'; - -import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; -import Log from '@app/services/Log/Log'; -import { BlocksoftTransfer } from '@crypto/actions/BlocksoftTransfer/BlocksoftTransfer'; -import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; - -const TronStakeUtils = { - async getVoteAddresses() { - return BlocksoftExternalSettings.getStatic('TRX_VOTE_BEST'); - }, - - async getPrettyBalance(address) { - const balance = await BlocksoftBalances.setCurrencyCode('TRX') - .setAddress(address) - .getBalance('TronStakeUtils'); - if (!balance) { - return false; - } - balance.prettyBalanceAvailable = BlocksoftPrettyNumbers.setCurrencyCode( - 'TRX' - ).makePretty(balance.balanceAvailable); - balance.prettyFrozen = BlocksoftPrettyNumbers.setCurrencyCode( - 'TRX' - ).makePretty(balance.frozen); - balance.prettyFrozenOthers = BlocksoftPrettyNumbers.setCurrencyCode( - 'TRX' - ).makePretty(balance.frozenOthers); - balance.prettyFrozenEnergy = BlocksoftPrettyNumbers.setCurrencyCode( - 'TRX' - ).makePretty(balance.frozenEnergy); - balance.prettyFrozenEnergyOthers = BlocksoftPrettyNumbers.setCurrencyCode( - 'TRX' - ).makePretty(balance.frozenEnergyOthers); - balance.prettyVote = ( - balance.prettyFrozen * 1 + - balance.prettyFrozenOthers * 1 + - balance.prettyFrozenEnergy * 1 + - balance.prettyFrozenEnergyOthers * 1 - ) - .toString() - .split('.')[0]; - - const maxExpire = - balance.frozenEnergyExpireTime && - balance.frozenEnergyExpireTime > balance.frozenExpireTime - ? balance.frozenEnergyExpireTime - : balance.frozenExpireTime; - if (maxExpire > 0) { - balance.diffLastStakeMinutes = - 24 * 3 * 60 - (maxExpire - new Date().getTime()) / 60000; // default time = 3 days, so thats how many minutes from last stake - } else { - balance.diffLastStakeMinutes = -1; - } - return balance; - }, - - async sendVoteAll(address, derivationPath, walletHash, specialActionNeeded) { - const { prettyVote, diffLastStakeMinutes, voteTotal } = - await TronStakeUtils.getPrettyBalance(address); - if ( - diffLastStakeMinutes === -1 && - specialActionNeeded === 'vote_after_unfreeze' - ) { - BlocksoftCryptoLog.log( - 'TronStake.sendVoteAll ' + address + ' continue ' + diffLastStakeMinutes - ); - } else if (!diffLastStakeMinutes || diffLastStakeMinutes < 3) { - BlocksoftCryptoLog.log( - 'TronStake.sendVoteAll ' + - address + - ' skipped vote1 by ' + - diffLastStakeMinutes - ); - return false; - } - if (!prettyVote || typeof prettyVote === 'undefined') { - BlocksoftCryptoLog.log( - 'TronStake.sendVoteAll ' + address + ' skipped vote2' - ); - return false; - } else if (voteTotal * 1 === prettyVote * 1) { - if (diffLastStakeMinutes > 100) { - BlocksoftCryptoLog.log( - 'TronStake.sendVoteAll ' + - address + - ' skipped vote3 ' + - voteTotal + - ' by ' + - diffLastStakeMinutes - ); - return true; // all done - } - BlocksoftCryptoLog.log( - 'TronStake.sendVoteAll ' + address + ' skipped vote4 ' + voteTotal - ); - return false; - } - - BlocksoftCryptoLog.log( - 'TronStake.sendVoteAll ' + - address + - ' started vote ' + - prettyVote + - ' by ' + - diffLastStakeMinutes - ); - - const voteAddress = await TronStakeUtils.getVoteAddresses(); - return TronStakeUtils._send( - '/wallet/votewitnessaccount', - { - owner_address: TronUtils.addressToHex(address), - votes: [ - { - vote_address: TronUtils.addressToHex(voteAddress), - vote_count: prettyVote * 1 - } - ] - }, - 'vote ' + prettyVote + ' for ' + voteAddress, - { - walletHash, - address, - derivationPath, - type: 'vote', - cryptoValue: BlocksoftPrettyNumbers.setCurrencyCode('TRX').makeUnPretty( - prettyVote * 1 - ), - callback: () => {} - } - ); - }, - - async _send(shortLink, params, langMsg, uiParams) { - const sendLink = BlocksoftExternalSettings.getStatic('TRX_SEND_LINK'); - const link = sendLink + shortLink; - const tmp = await BlocksoftAxios.post(link, params); - let blockchainData; - - if (typeof tmp.data !== 'undefined') { - if (typeof tmp.data.raw_data_hex !== 'undefined') { - blockchainData = tmp.data; - } else { - Log.log('TronStakeUtils._send no rawHex ' + link, params, tmp.data); - throw new Error(JSON.stringify(tmp.data)); - } - } else { - Log.log('TronStakeUtils rawHex empty data ' + link, params); - throw new Error('Empty data'); - } - - const txData = { - currencyCode: 'TRX', - walletHash: uiParams.walletHash, - derivationPath: uiParams.derivationPath, - addressFrom: uiParams.address, - addressTo: '', - blockchainData - }; - - const result = await BlocksoftTransfer.sendTx(txData, { - selectedFee: { langMsg } - }); - return result; - } -}; - -export default TronStakeUtils; +import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings'; +import BlocksoftPrettyNumbers from '@crypto/common/BlocksoftPrettyNumbers'; +import BlocksoftBalances from '@crypto/actions/BlocksoftBalances/BlocksoftBalances'; +import TronUtils from '@crypto/blockchains/trx/ext/TronUtils'; + +import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; +import Log from '@app/services/Log/Log'; +import { BlocksoftTransfer } from '@crypto/actions/BlocksoftTransfer/BlocksoftTransfer'; +import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; + +const TronStakeUtils = { + async getVoteAddresses() { + return BlocksoftExternalSettings.getStatic('TRX_VOTE_BEST'); + }, + + async getPrettyBalance(address) { + const balance = await BlocksoftBalances.setCurrencyCode('TRX') + .setAddress(address) + .getBalance('TronStakeUtils'); + if (!balance) { + return false; + } + balance.prettyBalanceAvailable = BlocksoftPrettyNumbers.setCurrencyCode( + 'TRX' + ).makePretty(balance.balanceAvailable); + balance.prettyFrozen = BlocksoftPrettyNumbers.setCurrencyCode( + 'TRX' + ).makePretty(balance.frozen); + balance.prettyFrozenOthers = BlocksoftPrettyNumbers.setCurrencyCode( + 'TRX' + ).makePretty(balance.frozenOthers); + balance.prettyFrozenEnergy = BlocksoftPrettyNumbers.setCurrencyCode( + 'TRX' + ).makePretty(balance.frozenEnergy); + balance.prettyFrozenEnergyOthers = BlocksoftPrettyNumbers.setCurrencyCode( + 'TRX' + ).makePretty(balance.frozenEnergyOthers); + balance.prettyVote = ( + balance.prettyFrozen * 1 + + balance.prettyFrozenOthers * 1 + + balance.prettyFrozenEnergy * 1 + + balance.prettyFrozenEnergyOthers * 1 + ) + .toString() + .split('.')[0]; + + const maxExpire = + balance.frozenEnergyExpireTime && + balance.frozenEnergyExpireTime > balance.frozenExpireTime + ? balance.frozenEnergyExpireTime + : balance.frozenExpireTime; + if (maxExpire > 0) { + balance.diffLastStakeMinutes = + 24 * 3 * 60 - (maxExpire - new Date().getTime()) / 60000; // default time = 3 days, so thats how many minutes from last stake + } else { + balance.diffLastStakeMinutes = -1; + } + return balance; + }, + + async sendVoteAll(address, derivationPath, walletHash, specialActionNeeded) { + const { prettyVote, diffLastStakeMinutes, voteTotal } = + await TronStakeUtils.getPrettyBalance(address); + if ( + diffLastStakeMinutes === -1 && + specialActionNeeded === 'vote_after_unfreeze' + ) { + BlocksoftCryptoLog.log( + 'TronStake.sendVoteAll ' + address + ' continue ' + diffLastStakeMinutes + ); + } else if (!diffLastStakeMinutes || diffLastStakeMinutes < 3) { + BlocksoftCryptoLog.log( + 'TronStake.sendVoteAll ' + + address + + ' skipped vote1 by ' + + diffLastStakeMinutes + ); + return false; + } + if (!prettyVote || typeof prettyVote === 'undefined') { + BlocksoftCryptoLog.log( + 'TronStake.sendVoteAll ' + address + ' skipped vote2' + ); + return false; + } else if (voteTotal * 1 === prettyVote * 1) { + if (diffLastStakeMinutes > 100) { + BlocksoftCryptoLog.log( + 'TronStake.sendVoteAll ' + + address + + ' skipped vote3 ' + + voteTotal + + ' by ' + + diffLastStakeMinutes + ); + return true; // all done + } + BlocksoftCryptoLog.log( + 'TronStake.sendVoteAll ' + address + ' skipped vote4 ' + voteTotal + ); + return false; + } + + BlocksoftCryptoLog.log( + 'TronStake.sendVoteAll ' + + address + + ' started vote ' + + prettyVote + + ' by ' + + diffLastStakeMinutes + ); + + const voteAddress = await TronStakeUtils.getVoteAddresses(); + return TronStakeUtils._send( + '/wallet/votewitnessaccount', + { + owner_address: TronUtils.addressToHex(address), + votes: [ + { + vote_address: TronUtils.addressToHex(voteAddress), + vote_count: prettyVote * 1 + } + ] + }, + 'vote ' + prettyVote + ' for ' + voteAddress, + { + walletHash, + address, + derivationPath, + type: 'vote', + cryptoValue: BlocksoftPrettyNumbers.setCurrencyCode('TRX').makeUnPretty( + prettyVote * 1 + ), + callback: () => {} + } + ); + }, + + async _send(shortLink, params, langMsg, uiParams) { + const sendLink = BlocksoftExternalSettings.getStatic('TRX_SEND_LINK'); + const link = sendLink + shortLink; + const tmp = await BlocksoftAxios.post(link, params); + let blockchainData; + + if (typeof tmp.data !== 'undefined') { + if (typeof tmp.data.raw_data_hex !== 'undefined') { + blockchainData = tmp.data; + } else { + Log.log('TronStakeUtils._send no rawHex ' + link, params, tmp.data); + throw new Error(JSON.stringify(tmp.data)); + } + } else { + Log.log('TronStakeUtils rawHex empty data ' + link, params); + throw new Error('Empty data'); + } + + const txData = { + currencyCode: 'TRX', + walletHash: uiParams.walletHash, + derivationPath: uiParams.derivationPath, + addressFrom: uiParams.address, + addressTo: '', + blockchainData + }; + + const result = await BlocksoftTransfer.sendTx(txData, { + selectedFee: { langMsg } + }); + return result; + } +}; + +export default TronStakeUtils; diff --git a/crypto/blockchains/trx/ext/TronUtils.js b/crypto/blockchains/trx/ext/TronUtils.ts similarity index 100% rename from crypto/blockchains/trx/ext/TronUtils.js rename to crypto/blockchains/trx/ext/TronUtils.ts diff --git a/crypto/blockchains/usdt/UsdtScannerProcessor.js b/crypto/blockchains/usdt/UsdtScannerProcessor.ts similarity index 74% rename from crypto/blockchains/usdt/UsdtScannerProcessor.js rename to crypto/blockchains/usdt/UsdtScannerProcessor.ts index d24bbec62..ab569911e 100644 --- a/crypto/blockchains/usdt/UsdtScannerProcessor.js +++ b/crypto/blockchains/usdt/UsdtScannerProcessor.ts @@ -4,13 +4,15 @@ import BlocksoftUtils from '../../common/BlocksoftUtils'; import BlocksoftAxios from '../../common/BlocksoftAxios'; import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog'; -import BlocksoftDispatcher from '../BlocksoftDispatcher'; +import BlocksoftDispatcher from '@lib/BlocksoftDispatcher'; const USDT_API = 'https://microscanners.trustee.deals/usdt'; // https://microscanners.trustee.deals/usdt/1CmAoxq8BTxANRDwheJUpaGy6ngWNYX85 const USDT_API_MASS = 'https://microscanners.trustee.deals/balanceMass'; const CACHE_VALID_TIME = 30000; // 30 seconds -const CACHE = {}; +const CACHE: { + [address: string]: { data: any; time: number; provider: string }; +} = {}; export default class UsdtScannerProcessor { /** @@ -22,20 +24,20 @@ export default class UsdtScannerProcessor { * @type {number} * @private */ - _blocksToConfirm = 1; + private _blocksToConfirm = 1; /** * @type {boolean|BtcScannerProcessor} * @private */ - _btcProvider = false; + private _btcProvider: boolean | BtcScannerProcessor = false; /** * @param address * @returns {Promise} * @private */ - async _get(address) { + private async _get(address: string): Promise { const now = new Date().getTime(); if ( typeof CACHE[address] !== 'undefined' && @@ -51,8 +53,7 @@ export default class UsdtScannerProcessor { } if (typeof res.data.status === 'undefined') { throw new Error( - ' UsdtScannerProcessor._get bad status loaded for address ' + link, - res + 'UsdtScannerProcessor._get bad status loaded for address ' + link ); } if ( @@ -60,7 +61,7 @@ export default class UsdtScannerProcessor { typeof res.data.data.balance === 'undefined' ) { throw new Error( - ' UsdtScannerProcessor._get nothing loaded for address ' + link + 'UsdtScannerProcessor._get nothing loaded for address ' + link ); } if (typeof CACHE[address] !== 'undefined') { @@ -81,7 +82,7 @@ export default class UsdtScannerProcessor { * @returns {Promise} * @private */ - async _getMass(address) { + private async _getMass(address: string): Promise { const now = new Date().getTime(); // mass ask @@ -102,7 +103,16 @@ export default class UsdtScannerProcessor { * @param {string} address * @return {Promise<{int:balance, int:provider}>} */ - async getBalanceBlockchain(address) { + public async getBalanceBlockchain(address: string): Promise< + | { + balance: number; + provider: string; + time: number; + unconfirmed: number; + balanceScanBlock: number; + } + | false + > { if (typeof address === 'object') { BlocksoftCryptoLog.log( 'UsdtScannerProcessor.getBalance started MASS ' + @@ -130,10 +140,10 @@ export default class UsdtScannerProcessor { ); return false; } - const balance = tmp.data.balance; + const balance: number = tmp.data.balance; BlocksoftCryptoLog.log( - 'UsdtScannerProcessor.getBalance finished', - address + ' => ' + balance + `UsdtScannerProcessor.getBalance finished + ${address + ' => ' + balance}` ); return { balance, @@ -148,7 +158,10 @@ export default class UsdtScannerProcessor { * @param {string} scanData.account.address * @return {Promise} */ - async getTransactionsBlockchain(scanData, source = '') { + public async getTransactionsBlockchain( + scanData: { account: { address: string } }, + source = '' + ): Promise { const address = scanData.account.address.trim(); BlocksoftCryptoLog.log( 'UsdtScannerProcessor.getTransactions started ' + address @@ -177,12 +190,12 @@ export default class UsdtScannerProcessor { throw new Error('Undefined txs ' + JSON.stringify(tmp)); } - const transactions = []; + const transactions: UnifiedTransaction[] = []; if (tmp.block > this.lastBlock) { this.lastBlock = tmp.block; } let tx; - const unique = {}; + const unique: { [transactionHash: string]: number } = {}; if (tmp.txs && tmp.txs.length > 0) { for (tx of tmp.txs) { const transaction = await this._unifyTransaction(address, tx); @@ -190,22 +203,32 @@ export default class UsdtScannerProcessor { unique[transaction.transactionHash] = 1; } } - let btcTxs = false; + let btcTxs: UnifiedTransaction[] | false = false; try { if (!this._btcProvider) { - this._btcProvider = await new BlocksoftDispatcher().getScannerProcessor( - { currencyCode: 'BTC' } - ); + this._btcProvider = + (await new BlocksoftDispatcher().getScannerProcessor({ + currencyCode: 'BTC' + })) as BtcScannerProcessor; } btcTxs = await this._btcProvider.getTransactionsBlockchain( scanData, source + ' UsdtScannerProcessor' ); - } catch (e) {} + } catch (e: any) { + throw e; + } if (btcTxs && btcTxs.length > 0) { for (tx of btcTxs) { if (typeof unique[tx.transactionHash] !== 'undefined') continue; transactions.push({ + addressAmount: tx.addressAmount, + addressFrom: tx.addressFrom, + addressTo: tx.addressTo, + blockHash: tx.blockHash, + blockNumber: tx.blockNumber, + inputValue: tx.inputValue, + transactionFee: tx.transactionFee, blockConfirmations: tx.blockConfirmations, blockTime: tx.blockTime, transactionDirection: tx.transactionDirection, @@ -244,15 +267,18 @@ export default class UsdtScannerProcessor { * @return {UnifiedTransaction} * @private */ - async _unifyTransaction(address, transaction) { - const confirmations = this.lastBlock - transaction.block_number; - let transactionStatus = 'new'; + private async _unifyTransaction( + address: string, + transaction: any + ): Promise { + const confirmations: number = this.lastBlock - transaction.block_number; + let transactionStatus: 'new' | 'success' | 'confirming' = 'new'; if (confirmations >= this._blocksToConfirm) { transactionStatus = 'success'; } else if (confirmations > 0) { transactionStatus = 'confirming'; } - const tx = { + const tx: UnifiedTransaction = { transactionHash: transaction.transaction_txid, blockHash: transaction.transaction_block_hash, blockNumber: +transaction.block_number, @@ -282,3 +308,28 @@ export default class UsdtScannerProcessor { return tx; } } + +type UnifiedTransaction = { + transactionHash: string; + blockHash: string; + blockNumber: number; + blockTime: string; + blockConfirmations: number; + transactionDirection: 'income' | 'outcome' | 'self'; + addressFrom: string; + addressTo: string; + addressAmount: string; + transactionStatus: 'new' | 'success' | 'confirming' | 'fail'; + transactionFee: number; + inputValue: string; +}; + +/** + * BtcScannerProcessor interface for TypeScript + */ +interface BtcScannerProcessor { + getTransactionsBlockchain( + scanData: { account: { address: string } }, + source: string + ): Promise; +} diff --git a/crypto/blockchains/usdt/UsdtTransferProcessor.ts b/crypto/blockchains/usdt/UsdtTransferProcessor.ts index 18b801c06..ebd3a9b24 100644 --- a/crypto/blockchains/usdt/UsdtTransferProcessor.ts +++ b/crypto/blockchains/usdt/UsdtTransferProcessor.ts @@ -96,8 +96,8 @@ export default class UsdtTransferProcessor // @ts-ignore BlocksoftCryptoLog.log( this._settings.currencyCode + - ' UsdtTransferProcessor.getTransferAllBalance ', - data.addressFrom + ' => ' + balance + ` UsdtTransferProcessor.getTransferAllBalance + ${data.addressFrom + ' => ' + balance}` ); // noinspection EqualityComparisonWithCoercionJS if (balance === '0') { diff --git a/crypto/blockchains/usdt/tx/UsdtTxBuilder.ts b/crypto/blockchains/usdt/tx/UsdtTxBuilder.ts index 276cd4fe0..952b353fc 100644 --- a/crypto/blockchains/usdt/tx/UsdtTxBuilder.ts +++ b/crypto/blockchains/usdt/tx/UsdtTxBuilder.ts @@ -9,7 +9,7 @@ import { TransactionBuilder, script, opcodes } from 'bitcoinjs-lib'; const USDT_TOKEN_ID = 31; -function toPaddedHexString(num, len) { +function toPaddedHexString(num: number, len: number) { const str = num.toString(16); return '0'.repeat(len - str.length) + str; } @@ -21,6 +21,7 @@ function createOmniSimpleSend(amountInUSD: string, propertyID = USDT_TOKEN_ID) { '0000', // tx type '0000', // version toPaddedHexString(propertyID, 8), + // @ts-ignore toPaddedHexString(Math.floor(amountInUSD * 100000000), 16) ].join(''); diff --git a/crypto/blockchains/usdt/tx/UsdtTxInputsOutputs.ts b/crypto/blockchains/usdt/tx/UsdtTxInputsOutputs.ts index de3a390f1..1bffca168 100644 --- a/crypto/blockchains/usdt/tx/UsdtTxInputsOutputs.ts +++ b/crypto/blockchains/usdt/tx/UsdtTxInputsOutputs.ts @@ -69,7 +69,7 @@ export default class UsdtTxInputsOutputs autoFeeLimitReadable?: string | number; }, additionalData: BlocksoftBlockchainTypes.TransferAdditionalData, - subtitle: string = 'default' + subtitle = 'default' ): Promise { let res = await super._getInputsOutputs( data, @@ -79,8 +79,8 @@ export default class UsdtTxInputsOutputs subtitle + ' usdted' ); let inputIsFound = false; - let newInputs = []; - let oldInputs = []; + const newInputs = []; + const oldInputs = []; let addressFromUsdtOutputs = 0; let newInputAdded = false; for (const input of res.inputs) { @@ -124,11 +124,9 @@ export default class UsdtTxInputsOutputs } if (!changeIsFound && newInputAdded.value !== '546') { res.outputs.push({ - // @ts-ignore to: data.addressFrom, // @ts-ignore amount: newInputAdded.value.toString(), - // @ts-ignore isChange: true }); } @@ -235,8 +233,7 @@ export default class UsdtTxInputsOutputs needOneOutput: boolean, addressFrom: string, addressTo: string, - amount: string, - source: string = '' + amount: string ) { const totalOuts = res.outputs.length; @@ -314,7 +311,7 @@ export default class UsdtTxInputsOutputs return res; } - _innerToUp(outputs, addressTo) { + _innerToUp(outputs: any, addressTo: string) { let result = []; for (const output of outputs) { if (output.to === addressTo) { @@ -365,6 +362,7 @@ export default class UsdtTxInputsOutputs result.push(output); } } + // @ts-ignore BlocksoftCryptoLog.log('USDT addressTo upTo', result); return result; } diff --git a/crypto/blockchains/vet/VetScannerProcessor.js b/crypto/blockchains/vet/VetScannerProcessor.ts similarity index 90% rename from crypto/blockchains/vet/VetScannerProcessor.js rename to crypto/blockchains/vet/VetScannerProcessor.ts index 6dd3c3a30..3a980bf4b 100644 --- a/crypto/blockchains/vet/VetScannerProcessor.js +++ b/crypto/blockchains/vet/VetScannerProcessor.ts @@ -8,14 +8,25 @@ import BlocksoftUtils from '@crypto/common/BlocksoftUtils'; const API_PATH = 'https://sync-mainnet.vechain.org'; const CACHE_VALID_TIME = 30000; // 30 seconds -const CACHE = {}; +const CACHE: { + [address: string]: { + balance: string; + energy: string; + provider: string; + time: number; + }; +} = {}; + export default class VetScannerProcessor { - constructor(settings) { + private _settings: any; + private _lastBlock: number; + + constructor(settings: any) { this._settings = settings; this._lastBlock = 9243725; } - async _get(_address) { + private async _get(_address: string) { const address = _address.toLowerCase(); const now = new Date().getTime(); @@ -59,7 +70,11 @@ export default class VetScannerProcessor { * @param {string} address * @return {Promise<{balance, provider}>} */ - async getBalanceBlockchain(address) { + async getBalanceBlockchain( + address: string + ): Promise< + { balance: string; unconfirmed: number; provider: string } | false + > { address = address.trim(); BlocksoftCryptoLog.log( this._settings.currencyCode + @@ -76,7 +91,7 @@ export default class VetScannerProcessor { this._settings.currencyCode === 'VET' ? res.balance : res.energy; const balance = BlocksoftUtils.hexToDecimalBigger(hex); return { balance, unconfirmed: 0, provider: 'vethor-node.vechain.com' }; - } catch (e) { + } catch (e: any) { BlocksoftCryptoLog.log( this._settings.currencyCode + ' VeChain ScannerProcessor getBalanceBlockchain address ' + @@ -96,7 +111,10 @@ export default class VetScannerProcessor { * @param {string} scanData.account.address * @return {Promise<[UnifiedTransaction]>} */ - async getTransactionsBlockchain(scanData, source) { + async getTransactionsBlockchain( + scanData: { account: { address: string } }, + source: any + ): Promise { const address = scanData.account.address.trim(); if (this._settings.currencyCode === 'VET') { @@ -113,7 +131,7 @@ export default class VetScannerProcessor { this._lastBlock = tmp; } } - } catch (e) { + } catch (e: any) { BlocksoftCryptoLog.log( 'VetScannerProcessor.getTransactions lastBlock ' + e.message ); @@ -179,7 +197,10 @@ export default class VetScannerProcessor { } } - async _unifyTransactions(address, result) { + private async _unifyTransactions( + address: string, + result: any[] + ): Promise { const transactions = []; let tx; for (tx of result) { @@ -191,7 +212,10 @@ export default class VetScannerProcessor { return transactions; } - async _unifyTransactionsToken(address, result) { + private async _unifyTransactionsToken( + address: string, + result: any[] + ): Promise { const transactions = []; let tx; for (tx of result) { @@ -203,7 +227,10 @@ export default class VetScannerProcessor { return transactions; } - async _unifyTransaction(address, transaction) { + private async _unifyTransaction( + address: string, + transaction: any + ): Promise { /** * "sender":"0xa4adafaef9ec07bc4dc6de146934c7119341ee25", * "recipient":"0xf3ce5d5d8ff44cb6b4f77512b94ddd6e04d820a0", @@ -221,7 +248,7 @@ export default class VetScannerProcessor { let formattedTime = transaction.meta.blockTimestamp; try { formattedTime = BlocksoftUtils.toDate(transaction.meta.blockTimestamp); - } catch (e) { + } catch (e: any) { e.message += ' timestamp error transaction2 data ' + JSON.stringify(transaction); throw e; @@ -255,7 +282,10 @@ export default class VetScannerProcessor { return tx; } - async _unifyTransactionToken(address, transaction) { + private async _unifyTransactionToken( + address: string, + transaction: any + ): Promise { /** * "address": "0x0000000000000000000000000000456e65726779", * "topics": [ @@ -277,7 +307,7 @@ export default class VetScannerProcessor { let formattedTime = transaction.meta.blockTimestamp; try { formattedTime = BlocksoftUtils.toDate(transaction.meta.blockTimestamp); - } catch (e) { + } catch (e: any) { e.message += ' timestamp error transaction2 data ' + JSON.stringify(transaction); throw e; diff --git a/crypto/blockchains/vet/VetTransferProcessor.ts b/crypto/blockchains/vet/VetTransferProcessor.ts index 3973f55ed..5615c0fee 100644 --- a/crypto/blockchains/vet/VetTransferProcessor.ts +++ b/crypto/blockchains/vet/VetTransferProcessor.ts @@ -7,7 +7,6 @@ import BlocksoftUtils from '../../common/BlocksoftUtils'; import { BlocksoftBlockchainTypes } from '../BlocksoftBlockchainTypes'; import { thorify } from 'thorify'; import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; -import MarketingEvent from '@app/services/Marketing/MarketingEvent'; const Web3 = require('web3'); const abi = [ @@ -61,9 +60,7 @@ export default class VetTransferProcessor } async getFeeRate( - data: BlocksoftBlockchainTypes.TransferData, - privateData: BlocksoftBlockchainTypes.TransferPrivateData, - additionalData: {} = {} + data: BlocksoftBlockchainTypes.TransferData ): Promise { const result: BlocksoftBlockchainTypes.FeeRateResult = { selectedFeeIndex: -3, @@ -173,7 +170,7 @@ export default class VetTransferProcessor privateData.privateKey ); } - } catch (e) { + } catch (e: any) { BlocksoftCryptoLog.log( this._settings.currencyCode + ' VetTransferProcessor.sendTx ' + @@ -216,7 +213,7 @@ export default class VetTransferProcessor }; } - let result = {} as BlocksoftBlockchainTypes.SendTxResult; + const result = {} as BlocksoftBlockchainTypes.SendTxResult; try { const send = await BlocksoftAxios.post( API_PATH + '/transactions', @@ -241,7 +238,7 @@ export default class VetTransferProcessor throw new Error('SYSTEM_ERROR'); } result.transactionHash = send.data.id; - } catch (e) { + } catch (e: any) { BlocksoftCryptoLog.log( this._settings.currencyCode + ' VetTransferProcessor.sendTx ' + @@ -258,7 +255,7 @@ export default class VetTransferProcessor return result; } - checkError(e, data, txRBF = false) { + checkError(e: any, data: any, txRBF = false) { if (e.message.indexOf('nonce too low') !== -1) { BlocksoftCryptoLog.log( 'VeChain checkError0.1 ' + e.message + ' for ' + data.addressFrom diff --git a/crypto/blockchains/waves/WavesAddressProcessor.js b/crypto/blockchains/waves/WavesAddressProcessor.js deleted file mode 100644 index 5bbb26ed3..000000000 --- a/crypto/blockchains/waves/WavesAddressProcessor.js +++ /dev/null @@ -1,27 +0,0 @@ -/** - * @version 0.5 - */ -import { crypto, base58Encode } from '@waves/ts-lib-crypto' - -export default class WavesAddressProcessor { - - async setBasicRoot(root) { - - } - - /** - * @param {string|Buffer} _privateKey - * @param {*} data - * @returns {Promise<{privateKey: string, address: string, addedData: *}>} - */ - async getAddress(_privateKey, data = {}, superPrivateData = {}) { - - const all = crypto({seed: superPrivateData.mnemonic}) - const buff1 = all.address() - const address = base58Encode(buff1) - const key2 = all.keyPair() - const pubKey = base58Encode(key2.publicKey) - const privKey = base58Encode(key2.privateKey) - return { address, privateKey: privKey, addedData: { pubKey} } - } -} diff --git a/crypto/blockchains/waves/WavesAddressProcessor.ts b/crypto/blockchains/waves/WavesAddressProcessor.ts new file mode 100644 index 000000000..c473a3ec7 --- /dev/null +++ b/crypto/blockchains/waves/WavesAddressProcessor.ts @@ -0,0 +1,35 @@ +/** + * @version 0.5 + */ +import { crypto, base58Encode } from '@waves/ts-lib-crypto'; + +interface SuperPrivateData { + mnemonic: string; +} + +export default class WavesAddressProcessor { + async setBasicRoot(root: any): Promise { + // Implement the setBasicRoot method according to its functionality. + // Since the original function doesn't have any implementation, I'm leaving this empty. + } + + /** + * @param {string|Buffer} _privateKey + * @param {any} data + * @param {SuperPrivateData} superPrivateData + * @returns {Promise<{privateKey: string, address: string, addedData: any}>} + */ + async getAddress( + _privateKey: string | Buffer, + data: any = {}, + superPrivateData: SuperPrivateData + ): Promise<{ privateKey: string; address: string; addedData: any }> { + const all = crypto({ seed: superPrivateData.mnemonic }); + const buff1 = all.address(); + const address = base58Encode(buff1); + const key2 = all.keyPair(); + const pubKey = base58Encode(key2.publicKey); + const privKey = base58Encode(key2.privateKey); + return { address, privateKey: privKey, addedData: { pubKey } }; + } +} diff --git a/crypto/blockchains/waves/WavesScannerProcessor.js b/crypto/blockchains/waves/WavesScannerProcessor.js deleted file mode 100644 index 792f6d490..000000000 --- a/crypto/blockchains/waves/WavesScannerProcessor.js +++ /dev/null @@ -1,184 +0,0 @@ -/** - * @version 0.5 - */ -import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog'; -import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; -import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings'; -import TransactionFilterTypeDict from '@appV2/dicts/transactionFilterTypeDict'; -import WavesTransactionsProvider from '@crypto/blockchains/waves/providers/WavesTransactionsProvider'; - -export default class WavesScannerProcessor { - constructor(settings) { - this._settings = settings; - this._provider = new WavesTransactionsProvider(); - this._mainCurrencyCode = 'WAVES'; - if ( - this._settings.currencyCode === 'ASH' || - this._settings.currencyCode.indexOf('ASH_') === 0 - ) { - this._mainCurrencyCode = 'ASH'; - } - } - - /** - * https://nodes.wavesnodes.com/addresses/balance/details/3P274YB5qseSE9DTTL3bpSjosZrYBPDpJ8k - * https://nodes.wavesnodes.com/api-docs/index.html#/addresses/getWavesBalances - * https://docs.waves.tech/en/blockchain/account/account-balance#account-balance-in-waves - * @return {Promise<{balance, provider}>} - */ - async getBalanceBlockchain(address) { - if (this._mainCurrencyCode == 'ASH') { - this._apiPath = await BlocksoftExternalSettings.get('ASH_SERVER'); - } else { - this._apiPath = await BlocksoftExternalSettings.get('WAVES_SERVER'); - } - address = address.trim(); - BlocksoftCryptoLog.log( - this._settings.currencyCode + - ' WavesScannerProcessor getBalanceBlockchain address ' + - address - ); - - const link = this._apiPath + '/addresses/balance/details/' + address; - const res = await BlocksoftAxios.get(link); - if (!res) { - return false; - } - try { - return { - balance: res.data.available, - unconfirmed: 0, - provider: 'wavesnodes.com' - }; - } catch (e) { - BlocksoftCryptoLog.log( - this._settings.currencyCode + - ' WavesProcessor getBalanceBlockchain address ' + - address + - ' balance ' + - JSON.stringify(res) + - ' to hex error ' + - e.message - ); - } - return false; - } - - /** - * https://nodes.wavesnodes.com/transactions/address/3P274YB5qseSE9DTTL3bpSjosZrYBPDpJ8k/limit/100 - * @param {string} scanData.account.address - * @return {Promise<[UnifiedTransaction]>} - */ - async getTransactionsBlockchain(scanData, source) { - const address = scanData.account.address.trim(); - const data = await this._provider.get(address, this._mainCurrencyCode); - const transactions = []; - for (const tx of data) { - const transaction = await this._unifyTransaction(address, tx); - if (transaction) { - transactions.push(transaction); - } - } - return transactions; - } - - /** - * @param address - * @param transaction.amount 100000000 - * @param transaction.applicationStatus succeeded - * @param transaction.assetId null - * @param transaction.attachment - * @param transaction.fee 100000 - * @param transaction.feeAsset - * @param transaction.feeAssetId - * @param transaction.height 2715839 - * @param transaction.id GxnhfderDpMwdrSfWbTN53NGB1Q2NRQXcGvYdXbzQBXo - * @param transaction.recipient 3PQQUuGM1Fo8zz72i62dNYkB5kRxqmJkoSu - * @param transaction.sender 3PLPGmXoDNKeWxSgJRU5vDNogbPj7hJiWQx - * @param transaction.senderPublicKey GP9hPWAiGDfNYyCTNw6ZWoLCzUqWiYj7MybtPcu8mpkg - * @param transaction.signature 4FewzMCYLvfQridUZtSFrDXRbDvmawUWtBxWdiEE5CeruG1qfbKbfTkudGyW6Eqs3kW4hTpABQxrhBSBuKV7uHFa - * @param transaction.timestamp 1628523063656 - * @param transaction.type 4 - * @param transaction.version 1 - * - * @returns {Promise} - * @private - */ - async _unifyTransaction(address, transaction) { - let transactionStatus = 'confirming'; - if (transaction.applicationStatus === 'succeeded') { - transactionStatus = 'success'; - } else if (transaction.applicationStatus === 'script_execution_failed') { - transactionStatus = 'fail'; - } - let formattedTime = transaction.timestamp; - const blockConfirmations = Math.round( - (new Date().getTime() - transaction.timestamp) / 6000 - ); - try { - formattedTime = new Date(transaction.timestamp).toISOString(); - } catch (e) { - e.message += - ' timestamp error transaction2 data ' + JSON.stringify(transaction); - throw e; - } - const addressFrom = transaction.sender; - const addressTo = transaction.recipient || address; - - let transactionFilterType = TransactionFilterTypeDict.USUAL; - let transactionDirection = 'self'; - if (addressFrom === address) { - if (addressTo !== address) { - transactionDirection = 'outcome'; - } - } else if (addressTo === address) { - transactionDirection = 'income'; - } - - let addressAmount = 0; - if (typeof transaction.transfers !== 'undefined') { - for (const transfer of transaction.transfers) { - if (transfer.recipient === address && transfer.amount * 1 > 0) { - addressAmount = addressAmount + transfer.amount * 1; - transactionDirection = 'income'; - } - } - } - if (addressAmount === 0 && typeof transaction.amount !== 'undefined') { - addressAmount = transaction.amount; - } - - const transactionFee = - transaction.feeAsset && transaction.feeAssetId ? 0 : transaction.fee; - if (transaction.assetId) { - return false; - } - - if (typeof transaction.order1 !== 'undefined') { - if (transaction.order2.amount === addressAmount) { - transactionDirection = 'swap_outcome'; - } else { - transactionDirection = 'swap_income'; - } - transactionFilterType = TransactionFilterTypeDict.SWAP; - } - - const tmp = { - transactionHash: transaction.id, - blockHash: transaction.height, - blockNumber: transaction.height, - blockTime: formattedTime, - blockConfirmations, - transactionDirection, - transactionFilterType, - addressFrom: addressFrom === address ? '' : addressFrom, - addressFromBasic: addressFrom, - addressTo: addressTo === address ? '' : addressTo, - addressToBasic: addressTo, - addressAmount, - transactionStatus, - transactionFee - }; - return tmp; - } -} diff --git a/crypto/blockchains/waves/WavesScannerProcessor.ts b/crypto/blockchains/waves/WavesScannerProcessor.ts new file mode 100644 index 000000000..72a03997b --- /dev/null +++ b/crypto/blockchains/waves/WavesScannerProcessor.ts @@ -0,0 +1,147 @@ +/** + * @version 0.5 + */ +import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog'; +import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; +import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings'; +import TransactionFilterTypeDict from '@appV2/dicts/transactionFilterTypeDict'; +import WavesTransactionsProvider from '@crypto/blockchains/waves/providers/WavesTransactionsProvider'; + +export default class WavesScannerProcessor { + private _settings: any; + private _provider: WavesTransactionsProvider; + private _mainCurrencyCode: string; + private _apiPath: string | undefined; + + constructor(settings: any) { + this._settings = settings; + this._provider = new WavesTransactionsProvider(); + this._mainCurrencyCode = 'WAVES'; + if ( + this._settings.currencyCode === 'ASH' || + this._settings.currencyCode.indexOf('ASH_') === 0 + ) { + this._mainCurrencyCode = 'ASH'; + } + } + + /** + * https://nodes.wavesnodes.com/addresses/balance/details/3P274YB5qseSE9DTTL3bpSjosZrYBPDpJ8k + * https://nodes.wavesnodes.com/api-docs/index.html#/addresses/getWavesBalances + * https://docs.waves.tech/en/blockchain/account/account-balance#account-balance-in-waves + * @return {Promise<{balance, provider}>} + */ + async getBalanceBlockchain(address: string): Promise { + if (this._mainCurrencyCode === 'ASH') { + this._apiPath = await BlocksoftExternalSettings.get('ASH_SERVER'); + } else { + this._apiPath = await BlocksoftExternalSettings.get('WAVES_SERVER'); + } + address = address.trim(); + BlocksoftCryptoLog.log( + `${this._settings.currencyCode} WavesScannerProcessor getBalanceBlockchain address ${address}` + ); + + const link = `${this._apiPath}/addresses/balance/details/${address}`; + const res = await BlocksoftAxios.get(link); + if (!res) { + return false; + } + try { + return { + balance: res.data.available, + unconfirmed: 0, + provider: 'wavesnodes.com' + }; + } catch (e: any) { + BlocksoftCryptoLog.log( + `${ + this._settings.currencyCode + } WavesProcessor getBalanceBlockchain address ${address} balance ${JSON.stringify( + res + )} to hex error ${e.message}` + ); + } + return false; + } + + /** + * https://nodes.wavesnodes.com/transactions/address/3P274YB5qseSE9DTTL3bpSjosZrYBPDpJ8k/limit/100 + * @param {string} scanData.account.address + * @return {Promise<[UnifiedTransaction]>} + */ + async getTransactionsBlockchain(scanData: any, source: any): Promise { + const address = scanData.account.address.trim(); + const data = await this._provider.get(address, this._mainCurrencyCode); + const transactions: any[] = []; + for (const tx of data) { + const transaction = await this._unifyTransaction(address, tx); + if (transaction) { + transactions.push(transaction); + } + } + return transactions; + } + + /** + * @param address + * @param transaction.amount 100000000 + * @param transaction.applicationStatus succeeded + * @param transaction.assetId null + * @param transaction.attachment + * @param transaction.fee 100000 + * @param transaction.feeAsset + * @param transaction.feeAssetId + * @param transaction.height 2715839 + * @param transaction.id GxnhfderDpMwdrSfWbTN53NGB1Q2NRQXcGvYdXbzQBXo + * @param transaction.recipient 3PQQUuGM1Fo8zz72i62dNYkB5kRxqmJkoSu + * @param transaction.sender 3PLPGmXoDNKeWxSgJRU5vDNogbPj7hJiWQx + * @param transaction.senderPublicKey GP9hPWAiGDfNYyCTNw6ZWoLCzUqWiYj7MybtPcu8mpkg + * @param transaction.signature 4FewzMCYLvfQridUZtSFrDXRbDvmawUWtBxWdiEE5CeruG1qfbKbfTkudGyW6Eqs3kW4hTpABQxrhBSBuKV7```typescript + * /** + * @param address + * @param transaction.amount 100000000 + * @param transaction.applicationStatus succeeded + * @param transaction.assetId null + * @param transaction.attachment + * @param transaction.fee 100000 + * @param transaction.feeAsset + * @param transaction.feeAssetId + * @param transaction.height 2715839 + * @param transaction.id GxnhfderDpMwdrSfWbTN53NGB1Q2NRQXcGvYdXbzQBXo + * @param transaction.recipient 3PQQUuGM1Fo8zz72i62dNYkB5kRxqmJkoSu + * @param transaction.sender 3PLPGmXoDNKeWxSgJRU5vDNogbPj7hJiWQx + * @param transaction.senderPublicKey GP9hPWAiGDfNYyCTNw6ZWoLCzUqWiYj7MybtPcu8mpkg + * @param transaction.signature 4FewzMCYLvfQridUZtSFrDXRbDvmawUWtBxWdiEE5CeruG1qfbKbfTkudGyW6Eqs3kW4hTpABQxrhBSBuKV7 + */ + private async _unifyTransaction( + address: string, + transaction: any + ): Promise { + const unifiedTransaction: any = {}; + + unifiedTransaction.hash = transaction.id; + unifiedTransaction.blockHash = null; + unifiedTransaction.blockNumber = transaction.height; + unifiedTransaction.confirmations = await this._provider.getConfirmations( + transaction.height + ); + unifiedTransaction.timestamp = transaction.timestamp; + unifiedTransaction.from = transaction.sender; + unifiedTransaction.to = transaction.recipient; + unifiedTransaction.value = transaction.amount; + unifiedTransaction.fee = transaction.fee; + unifiedTransaction.data = { + applicationStatus: transaction.applicationStatus, + assetId: transaction.assetId, + attachment: transaction.attachment, + feeAsset: transaction.feeAsset, + feeAssetId: transaction.feeAssetId, + senderPublicKey: transaction.senderPublicKey, + signature: transaction.signature + }; + unifiedTransaction.status = TransactionFilterTypeDict.CRYPTO_STATUS_PENDING; // pending + + return unifiedTransaction; + } +} diff --git a/crypto/blockchains/waves/WavesScannerProcessorErc20.js b/crypto/blockchains/waves/WavesScannerProcessorErc20.ts similarity index 76% rename from crypto/blockchains/waves/WavesScannerProcessorErc20.js rename to crypto/blockchains/waves/WavesScannerProcessorErc20.ts index c25e8679a..2c0e5a222 100644 --- a/crypto/blockchains/waves/WavesScannerProcessorErc20.js +++ b/crypto/blockchains/waves/WavesScannerProcessorErc20.ts @@ -1,135 +1,149 @@ -/** - * @version 0.5 - */ -import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; -import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; -import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings'; -import WavesScannerProcessor from '@crypto/blockchains/waves/WavesScannerProcessor'; -import TransactionFilterTypeDict from '@appV2/dicts/transactionFilterTypeDict'; -import BlocksoftUtils from '@crypto/common/BlocksoftUtils'; - -export default class WavesScannerProcessorErc20 extends WavesScannerProcessor { - constructor(settings) { - super(settings); - this._tokenAddress = settings.tokenAddress; - } - - /** - * https://nodes.wavesnodes.com/api-docs/index.html#/assets/getAssetBalanceByAddress - * https://nodes.wavesnodes.com/assets/balance/3PGixfWP1pcuWwND2rDLf2n94J7egSf4uz4/DG2xFkPdDwKUoBkzGAhQtLpSGzfXLiCYPEzeKH2Ad24p - * @return {Promise<{balance, provider}>} - */ - async getBalanceBlockchain(address) { - if (this._mainCurrencyCode === 'ASH') { - this._apiPath = await BlocksoftExternalSettings.get('ASH_SERVER'); - } else { - this._apiPath = await BlocksoftExternalSettings.get('WAVES_SERVER'); - } - address = address.trim(); - BlocksoftCryptoLog.log( - this._settings.currencyCode + - ' WavesScannerProcessorErc20 getBalanceBlockchain address ' + - address - ); - - const link = - this._apiPath + '/assets/balance/' + address + '/' + this._tokenAddress; - const res = await BlocksoftAxios.get(link); - if (!res) { - return false; - } - try { - return { - balance: res.data.balance, - unconfirmed: 0, - provider: 'wavesnodes.com' - }; - } catch (e) { - BlocksoftCryptoLog.log( - this._settings.currencyCode + - ' WavesProcessorErc20 getBalanceBlockchain address ' + - address + - ' balance ' + - JSON.stringify(res) + - ' to hex error ' + - e.message - ); - } - return false; - } - - async _unifyTransaction(address, transaction) { - let transactionStatus = 'confirming'; - if (transaction.applicationStatus === 'succeeded') { - transactionStatus = 'success'; - } else if (transaction.applicationStatus === 'script_execution_failed') { - transactionStatus = 'fail'; - } - let formattedTime = transaction.timestamp; - const blockConfirmations = Math.round( - (new Date().getTime() - transaction.timestamp) / 6000 - ); - try { - formattedTime = new Date(transaction.timestamp).toISOString(); - } catch (e) { - e.message += - ' timestamp error transaction2 data ' + JSON.stringify(transaction); - throw e; - } - const addressFrom = transaction.sender; - const addressTo = transaction.recipient || address; - let addressAmount = transaction.amount; - const transactionFee = - transaction.feeAsset && transaction.feeAssetId ? 0 : transaction.fee; - - let transactionDirection = false; - let transactionFilterType = TransactionFilterTypeDict.USUAL; - - if (typeof transaction.order1 !== 'undefined') { - if (transaction.order1.assetPair.priceAsset !== this._tokenAddress) { - return false; - } - if (transaction.order2.amount === addressAmount) { - transactionDirection = 'swap_income'; - addressAmount = transaction.order2.amount * transaction.order1.price; - } else { - transactionDirection = 'swap_outcome'; - addressAmount = transaction.order1.amount * transaction.order2.price; - } - transactionFilterType = TransactionFilterTypeDict.SWAP; - addressAmount = BlocksoftUtils.fromUnified( - BlocksoftUtils.toUnified(addressAmount, 14), - this._settings.decimals - ); - } else if (transaction.assetId === this._tokenAddress) { - transactionDirection = 'self'; - if (addressFrom === address) { - if (addressTo !== address) { - transactionDirection = 'outcome'; - } - } else if (addressTo === address) { - transactionDirection = 'income'; - } - } else { - return false; - } - - const tmp = { - transactionHash: transaction.id, - blockHash: transaction.height, - blockNumber: transaction.height, - blockTime: formattedTime, - blockConfirmations, - transactionDirection, - transactionFilterType, - addressFrom: addressFrom === address ? '' : addressFrom, - addressFromBasic: addressFrom, - addressTo: addressTo === address ? '' : addressTo, - addressToBasic: addressTo, - addressAmount, - transactionStatus, - transactionFee - }; - return tmp; - } -} +/** + * @version 0.5 + */ +import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; +import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; +import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings'; +import WavesScannerProcessor from '@crypto/blockchains/waves/WavesScannerProcessor'; +import TransactionFilterTypeDict from '@appV2/dicts/transactionFilterTypeDict'; +import BlocksoftUtils from '@crypto/common/BlocksoftUtils'; + +interface WavesScannerProcessorErc20Settings { + currencyCode: string; + decimals: number; + tokenAddress: string; +} + +export default class WavesScannerProcessorErc20 extends WavesScannerProcessor { + private readonly _settings: WavesScannerProcessorErc20Settings; + private _apiPath: string | undefined; + private readonly _tokenAddress: string; + + constructor(settings: WavesScannerProcessorErc20Settings) { + super(settings); + this._settings = settings; + this._tokenAddress = settings.tokenAddress; + } + + /** + * https://nodes.wavesnodes.com/api-docs/index.html#/assets/getAssetBalanceByAddress + * https://nodes.wavesnodes.com/assets/balance/3PGixfWP1pcuWwND2rDLf2n94J7egSf4uz4/DG2xFkPdDwKUoBkzGAhQtLpSGzfXLiCYPEzeKH2Ad24p + * @return {Promise<{balance, provider}>} + */ + async getBalanceBlockchain( + address: string + ): Promise< + { balance: number; unconfirmed: number; provider: string } | false + > { + if (this._settings.currencyCode === 'ASH') { + this._apiPath = await BlocksoftExternalSettings.get('ASH_SERVER'); + } else { + this._apiPath = await BlocksoftExternalSettings.get('WAVES_SERVER'); + } + address = address.trim(); + BlocksoftCryptoLog.log( + `${this._settings.currencyCode} WavesScannerProcessorErc20 getBalanceBlockchain address ${address}` + ); + + const link = `${this._apiPath}/assets/balance/${address}/${this._tokenAddress}`; + const res = await BlocksoftAxios.get(link); + if (!res) { + return false; + } + try { + return { + balance: res.data.balance, + unconfirmed: 0, + provider: 'wavesnodes.com' + }; + } catch (e: any) { + BlocksoftCryptoLog.log( + `${ + this._settings.currencyCode + } WavesProcessorErc20 getBalanceBlockchain address ${address} balance ${JSON.stringify( + res + )} to hex error ${e.message}` + ); + } + return false; + } + + async _unifyTransaction( + address: string, + transaction: any + ): Promise { + let transactionStatus = 'confirming'; + if (transaction.applicationStatus === 'succeeded') { + transactionStatus = 'success'; + } else if (transaction.applicationStatus === 'script_execution_failed') { + transactionStatus = 'fail'; + } + let formattedTime = transaction.timestamp; + const blockConfirmations = Math.round( + (new Date().getTime() - transaction.timestamp) / 6000 + ); + try { + formattedTime = new Date(transaction.timestamp).toISOString(); + } catch (e: any) { + e.message += ` timestamp error transaction2 data ${JSON.stringify( + transaction + )}`; + throw e; + } + const addressFrom = transaction.sender; + const addressTo = transaction.recipient || address; + let addressAmount = transaction.amount; + const transactionFee = + transaction.feeAsset && transaction.feeAssetId ? 0 : transaction.fee; + + let transactionDirection = false; + let transactionFilterType = TransactionFilterTypeDict.USUAL; + + if (typeof transaction.order1 !== 'undefined') { + if (transaction.order1.assetPair.priceAsset !== this._tokenAddress) { + return false; + } + if (transaction.order2.amount === addressAmount) { + transactionDirection = 'swap_income'; + addressAmount = transaction.order2.amount * transaction.order1.price; + } else { + transactionDirection = 'swap_outcome'; + addressAmount = transaction.order1.amount * transaction.order2.price; + } + transactionFilterType = TransactionFilterTypeDict.SWAP; + addressAmount = BlocksoftUtils.fromUnified( + BlocksoftUtils.toUnified(addressAmount, 14), + this._settings.decimals + ); + } else if (transaction.assetId === this._tokenAddress) { + transactionDirection = 'self'; + if (addressFrom === address) { + if (addressTo !== address) { + transactionDirection = 'outcome'; + } + } else if (addressTo === address) { + transactionDirection = 'income'; + } + } else { + return false; + } + + const tmp = { + transactionHash: transaction.id, + blockHash: transaction.height, + blockNumber: transaction.height, + blockTime: formattedTime, + blockConfirmations, + transactionDirection, + transactionFilterType, + addressFrom: addressFrom === address ? '' : addressFrom, + addressFromBasic: addressFrom, + addressTo: addressTo === address ? '' : addressTo, + addressToBasic: addressTo, + addressAmount, + transactionStatus, + transactionFee + }; + return tmp; + } +} diff --git a/crypto/blockchains/waves/WavesTransferProcessor.ts b/crypto/blockchains/waves/WavesTransferProcessor.ts index 237856cb7..73b64fd30 100644 --- a/crypto/blockchains/waves/WavesTransferProcessor.ts +++ b/crypto/blockchains/waves/WavesTransferProcessor.ts @@ -183,7 +183,7 @@ export default class WavesTransferProcessor }; } - let result = {} as BlocksoftBlockchainTypes.SendTxResult; + const result = {} as BlocksoftBlockchainTypes.SendTxResult; try { const resp = await new Promise((resolve, reject) => { BlocksoftCryptoLog.log( @@ -247,7 +247,11 @@ export default class WavesTransferProcessor return result; } - checkError(e, data, txRBF = false) { + checkError( + e: any, + data: { addressFrom: string; addressTo: string }, + txRBF = false + ) { if ( e.message.indexOf( 'waves balance to (at least) temporary negative state' diff --git a/crypto/blockchains/waves/providers/WavesTransactionsProvider.js b/crypto/blockchains/waves/providers/WavesTransactionsProvider.ts similarity index 65% rename from crypto/blockchains/waves/providers/WavesTransactionsProvider.js rename to crypto/blockchains/waves/providers/WavesTransactionsProvider.ts index b1fa63ba7..fdd5d91cb 100644 --- a/crypto/blockchains/waves/providers/WavesTransactionsProvider.js +++ b/crypto/blockchains/waves/providers/WavesTransactionsProvider.ts @@ -5,38 +5,47 @@ import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings' import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; -const CACHE_OF_TRANSACTIONS = { +interface TransactionData { + data: any; + time: number; +} + +const CACHE_OF_TRANSACTIONS: Record> = { ASH: {}, WAVES: {} }; + const CACHE_VALID_TIME = 30000; // 30 seconds export default class WavesTransactionsProvider { - async get(address, mainCurrencyCode) { - let _apiPath; + async get(address: string, mainCurrencyCode: string): Promise { + let _apiPath: string; + if (mainCurrencyCode === 'ASH') { _apiPath = await BlocksoftExternalSettings.get('ASH_SERVER'); } else { _apiPath = await BlocksoftExternalSettings.get('WAVES_SERVER'); } + const now = new Date().getTime(); + if ( - typeof CACHE_OF_TRANSACTIONS[mainCurrencyCode][address] !== 'undefined' && + CACHE_OF_TRANSACTIONS[mainCurrencyCode] && + CACHE_OF_TRANSACTIONS[mainCurrencyCode][address] && now - CACHE_OF_TRANSACTIONS[mainCurrencyCode][address].time < CACHE_VALID_TIME ) { - if ( - typeof CACHE_OF_TRANSACTIONS[mainCurrencyCode][address] !== 'undefined' - ) { + if (CACHE_OF_TRANSACTIONS[mainCurrencyCode][address]) { BlocksoftCryptoLog.log( - ' WavesTransactionsProvider.get from cache', - address + ' => ' + mainCurrencyCode + `WavesTransactionsProvider.get from cache ${address} => ${mainCurrencyCode}` ); return CACHE_OF_TRANSACTIONS[mainCurrencyCode][address].data; } } - const link = _apiPath + '/transactions/address/' + address + '/limit/100'; + + const link = `${_apiPath}/transactions/address/${address}/limit/100`; const res = await BlocksoftAxios.get(link); + if (!res || !res.data || typeof res.data[0] === 'undefined') { return false; } @@ -45,6 +54,7 @@ export default class WavesTransactionsProvider { data: res.data[0], time: now }; + return res.data[0]; } } diff --git a/crypto/blockchains/xlm/XlmAddressProcessor.js b/crypto/blockchains/xlm/XlmAddressProcessor.js deleted file mode 100644 index 2057fb088..000000000 --- a/crypto/blockchains/xlm/XlmAddressProcessor.js +++ /dev/null @@ -1,31 +0,0 @@ -/** - * @version 0.20 - * https://github.com/chatch/stellar-hd-wallet/blob/master/src/stellar-hd-wallet.js - */ -import BlocksoftKeys from '../../actions/BlocksoftKeys/BlocksoftKeys' -import XlmDerivePath from '@crypto/blockchains/xlm/ext/XlmDerivePath' - -const StellarSdk = require('stellar-sdk') - -export default class XlmAddressProcessor { - - async setBasicRoot(root) { - - } - - /** - * @param {string|Buffer} privateKey - * @param {*} data - * @returns {Promise<{privateKey: string, address: string}>} - */ - async getAddress(privateKey, data = {}, superPrivateData = {}) { - const seed = await BlocksoftKeys.getSeedCached(superPrivateData.mnemonic) - const seedHex = seed.toString('hex') - if (seedHex.length < 128) { - throw new Error('bad seedHex') - } - const res = XlmDerivePath(seedHex, `m/44'/148'/0'`) - const keypair = StellarSdk.Keypair.fromRawEd25519Seed(res.key) - return { address: keypair.publicKey(), privateKey: keypair.secret() } - } -} diff --git a/crypto/blockchains/xlm/XlmAddressProcessor.ts b/crypto/blockchains/xlm/XlmAddressProcessor.ts new file mode 100644 index 000000000..4c534cbc8 --- /dev/null +++ b/crypto/blockchains/xlm/XlmAddressProcessor.ts @@ -0,0 +1,31 @@ +/** + * @version 0.20 + * https://github.com/chatch/stellar-hd-wallet/blob/master/src/stellar-hd-wallet.js + */ +import BlocksoftKeys from '../../actions/BlocksoftKeys/BlocksoftKeys'; +import XlmDerivePath from '@crypto/blockchains/xlm/ext/XlmDerivePath'; +import StellarSdk from 'stellar-sdk'; + +export default class XlmAddressProcessor { + async setBasicRoot(root: any) {} + + /** + * @param {string|Buffer} privateKey + * @param {*} data + * @returns {Promise<{privateKey: string, address: string}>} + */ + async getAddress( + privateKey: string | Buffer, + data: any = {}, + superPrivateData: any = {} + ): Promise<{ privateKey: string; address: string }> { + const seed = await BlocksoftKeys.getSeedCached(superPrivateData.mnemonic); + const seedHex = seed.toString('hex'); + if (seedHex.length < 128) { + throw new Error('bad seedHex'); + } + const res = XlmDerivePath(seedHex, `m/44'/148'/0'`); + const keypair = StellarSdk.Keypair.fromRawEd25519Seed(res.key); + return { address: keypair.publicKey(), privateKey: keypair.secret() }; + } +} diff --git a/crypto/blockchains/xlm/XlmScannerProcessor.js b/crypto/blockchains/xlm/XlmScannerProcessor.ts similarity index 84% rename from crypto/blockchains/xlm/XlmScannerProcessor.js rename to crypto/blockchains/xlm/XlmScannerProcessor.ts index b29be069a..f22cb0bcb 100644 --- a/crypto/blockchains/xlm/XlmScannerProcessor.js +++ b/crypto/blockchains/xlm/XlmScannerProcessor.ts @@ -4,20 +4,39 @@ import BlocksoftAxios from '../../common/BlocksoftAxios'; import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog'; import BlocksoftUtils from '../../common/BlocksoftUtils'; -import config from '../../../app/config/config'; +import config from '@constants/config'; const API_PATH = 'https://horizon.stellar.org'; const FEE_DECIMALS = 7; +interface UnifiedTransaction { + transactionHash: string; + blockHash: string; + blockNumber: string; + blockTime: string; + blockConfirmations: number; + transactionDirection: string; + addressFrom: string; + addressTo: string; + addressAmount: string; + transactionStatus: string; + transactionFee: string; + transactionJson?: { memo: string }; +} + export default class XlmScannerProcessor { /** * https://horizon.stellar.org/accounts/GBH4TZYZ4IRCPO44CBOLFUHULU2WGALXTAVESQA6432MBJMABBB4GIYI * @param {string} address * @return {Promise<{balance, unconfirmed, provider}>} */ - async getBalanceBlockchain(address) { + async getBalanceBlockchain( + address: string + ): Promise< + { balance: number; unconfirmed: number; provider: string } | false + > { const link = `${API_PATH}/accounts/${address}`; - let res = false; + let res: any = false; let balance = 0; try { res = await BlocksoftAxios.getWithoutBraking(link); @@ -37,7 +56,7 @@ export default class XlmScannerProcessor { } else { return false; } - } catch (e) { + } catch (e: any) { if ( e.message.indexOf('timed out') === -1 && e.message.indexOf('account not found') === -1 && @@ -50,7 +69,7 @@ export default class XlmScannerProcessor { } } return { - balance: balance, + balance, unconfirmed: 0, provider: 'horizon.stellar.org' }; @@ -61,16 +80,18 @@ export default class XlmScannerProcessor { * @param {string} scanData.account.address * @return {Promise} */ - async getTransactionsBlockchain(scanData, source = '') { + async getTransactionsBlockchain(scanData: { + account: { address: string }; + }): Promise { const address = scanData.account.address.trim(); BlocksoftCryptoLog.log( 'XlmScannerProcessor.getTransactions started ' + address ); const linkTxs = `${API_PATH}/accounts/${address}/transactions?order=desc&limit=50`; - let res = false; + let res: any = false; try { res = await BlocksoftAxios.getWithoutBraking(linkTxs); - } catch (e) { + } catch (e: any) { if ( e.message.indexOf('account not found') === -1 && e.message.indexOf('to retrieve payments') === -1 && @@ -89,7 +110,7 @@ export default class XlmScannerProcessor { } if ( typeof res.data._embedded === 'undefined' || - typeof typeof res.data._embedded.records === 'undefined' + typeof res.data._embedded.records === 'undefined' ) { throw new Error( 'Undefined basic txs ' + linkTxs + ' ' + JSON.stringify(res.data) @@ -100,7 +121,7 @@ export default class XlmScannerProcessor { 'Undefined basic txs ' + linkTxs + ' ' + res.data._embedded.records ); } - const basicTxs = {}; + const basicTxs: Record = {}; for (const row of res.data._embedded.records) { basicTxs[row.hash] = row; } @@ -109,7 +130,7 @@ export default class XlmScannerProcessor { res = false; try { res = await BlocksoftAxios.getWithoutBraking(link); - } catch (e) { + } catch (e: any) { if ( e.message.indexOf('account not found') === -1 && e.message.indexOf('to retrieve payments') === -1 && @@ -127,7 +148,7 @@ export default class XlmScannerProcessor { } if ( typeof res.data._embedded === 'undefined' || - typeof typeof res.data._embedded.records === 'undefined' + typeof res.data._embedded.records === 'undefined' ) { throw new Error('Undefined txs ' + link + ' ' + JSON.stringify(res.data)); } @@ -148,8 +169,12 @@ export default class XlmScannerProcessor { return transactions; } - async _unifyTransactions(address, result, basicTxs) { - const transactions = []; + async _unifyTransactions( + address: string, + result: any[], + basicTxs: Record + ): Promise { + const transactions: UnifiedTransaction[] = []; let tx; for (tx of result) { const transaction = await this._unifyPayment( @@ -183,8 +208,12 @@ export default class XlmScannerProcessor { * @param {string} transaction.type_i 1 * @return {UnifiedTransaction} * @private - **/ - async _unifyPayment(address, transaction, basicTransaction) { + */ + async _unifyPayment( + address: string, + transaction: any, + basicTransaction: any + ): Promise { try { if (typeof transaction.asset_type !== 'undefined') { if (transaction.asset_type !== 'native') { @@ -202,6 +231,7 @@ export default class XlmScannerProcessor { typeof transaction.source_account === 'undefined' && typeof transaction.funder !== 'undefined' ) { + // eslint-disable-next-line camelcase transaction.source_account = transaction.funder; } } else { @@ -210,7 +240,7 @@ export default class XlmScannerProcessor { } else { return false; } - const tx = { + const tx: UnifiedTransaction = { transactionHash: transaction.transaction_hash, blockHash: '', blockNumber: '', @@ -243,7 +273,7 @@ export default class XlmScannerProcessor { tx.transactionFee = BlocksoftUtils.toUnified( basicTransaction.fee_charged, FEE_DECIMALS - ); + ).toString(); } if (typeof basicTransaction.ledger !== 'undefined') { tx.blockHash = basicTransaction.ledger; @@ -254,7 +284,7 @@ export default class XlmScannerProcessor { } } return tx; - } catch (e) { + } catch (e: any) { if (config.debug.cryptoErrors) { console.log('XLMScannerProcessor _unifyPayment error ' + e.message); } diff --git a/crypto/blockchains/xlm/XlmTransferProcessor.ts b/crypto/blockchains/xlm/XlmTransferProcessor.ts index 264bc9cd8..3206b87fd 100644 --- a/crypto/blockchains/xlm/XlmTransferProcessor.ts +++ b/crypto/blockchains/xlm/XlmTransferProcessor.ts @@ -6,28 +6,28 @@ * https://www.stellar.org/developers/js-stellar-sdk/reference/examples.html * https://www.stellar.org/developers/js-stellar-sdk/reference/ * -wsl curl -X POST -F "tx=AAAAAgAAAACq+ux8eDBQfPoRzFjOTwHZKnFQjwRw0DSPL62mg02PjAAAAGQCA52cAAAAAQAAAAEAAAAAAAAAAAAAAABgF7+bAAAAAAAAAAEAAAAAAAAAAQAAAABUMjNVlZnxhC4rIKoE2qO/3QIFfy3nqF5/ObsdmmRWaAAAAAAAAAAABfXhAAAAAAAAAAABg02PjAAAAED6lRqsmOkqB8nRkI0tQTUSRAaHs/0mLuy6G58PvXzVQtlQiE2RPm9KC7Dv6c/a/0HS7F5mPXBFVshwtZS5WcgB" "https://horizon.stellar.org/transactions" - { - "type": "https://stellar.org/horizon-errors/transaction_failed", - "title": "Transaction Failed", - "status": 400, - "detail": "The transaction failed when submitted to the stellar network. The `extras.result_codes` field on this response contains further details. Descriptions of each code can be found at: https://www.stellar.org/developers/guides/concepts/list-of-operations.html", - "extras": { - "envelope_xdr": "AAAAAgAAAACq+ux8eDBQfPoRzFjOTwHZKnFQjwRw0DSPL62mg02PjAAAAGQCA52cAAAAAQAAAAEAAAAAAAAAAAAAAABgF7+bAAAAAAAAAAEAAAAAAAAAAQAAAABUMjNVlZnxhC4rIKoE2qO/3QIFfy3nqF5/ObsdmmRWaAAAAAAAAAAABfXhAAAAAAAAAAABg02PjAAAAED6lRqsmOkqB8nRkI0tQTUSRAaHs/0mLuy6G58PvXzVQtlQiE2RPm9KC7Dv6c/a/0HS7F5mPXBFVshwtZS5WcgB", - "result_codes": { - "transaction": "tx_too_late" - }, - "result_xdr": "AAAAAAAAAGT////9AAAAAA==" - } -} + * wsl curl -X POST -F "tx=AAAAAgAAAACq+ux8eDBQfPoRzFjOTwHZKnFQjwRw0DSPL62mg02PjAAAAGQCA52cAAAAAQAAAAEAAAAAAAAAAAAAAABgF7+bAAAAAAAAAAEAAAAAAAAAAQAAAABUMjNVlZnxhC4rIKoE2qO/3QIFfy3nqF5/ObsdmmRWaAAAAAAAAAAABfXhAAAAAAAAAAABg02PjAAAAED6lRqsmOkqB8nRkI0tQTUSRAaHs/0mLuy6G58PvXzVQtlQiE2RPm9KC7Dv6c/a/0HS7F5mPXBFVshwtZS5WcgB" "https://horizon.stellar.org/transactions" + * { + * "type": "https://stellar.org/horizon-errors/transaction_failed", + * "title": "Transaction Failed", + * "status": 400, + * "detail": "The transaction failed when submitted to the stellar network. The `extras.result_codes` field on this response contains further details. Descriptions of each code can be found at: https://www.stellar.org/developers/guides/concepts/list-of-operations.html", + * "extras": { + * "envelope_xdr": "AAAAAgAAAACq+ux8eDBQfPoRzFjOTwHZKnFQjwRw0DSPL62mg02PjAAAAGQCA52cAAAAAQAAAAEAAAAAAAAAAAAAAABgF7+bAAAAAAAAAAEAAAAAAAAAAQAAAABUMjNVlZnxhC4rIKoE2qO/3QIFfy3nqF5/ObsdmmRWaAAAAAAAAAAABfXhAAAAAAAAAAABg02PjAAAAED6lRqsmOkqB8nRkI0tQTUSRAaHs/0mLuy6G58PvXzVQtlQiE2RPm9KC7Dv6c/a/0HS7F5mPXBFVshwtZS5WcgB", + * "result_codes": { + * "transaction": "tx_too_late" + * }, + * "result_xdr": "AAAAAAAAAGT////9AAAAAA==" + * } + * } */ import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog'; import BlocksoftUtils from '../../common/BlocksoftUtils'; -import BlocksoftDispatcher from '../BlocksoftDispatcher'; import MarketingEvent from '../../../app/services/Marketing/MarketingEvent'; import { BlocksoftBlockchainTypes } from '../BlocksoftBlockchainTypes'; import { XlmTxSendProvider } from './basic/XlmTxSendProvider'; +import BlocksoftDispatcher from '@lib/BlocksoftDispatcher'; const FEE_DECIMALS = 7; @@ -150,8 +150,10 @@ export default class XlmTransferProcessor // @ts-ignore BlocksoftCryptoLog.log( this._settings.currencyCode + - ' XlmTransferProcessor.getTransferAllBalance ', - data.addressFrom + ' => ' + balance + ' XlmTransferProcessor.getTransferAllBalance ' + + data.addressFrom + + ' => ' + + balance ); // noinspection EqualityComparisonWithCoercionJS if (BlocksoftUtils.diff(balance, 1) <= 0) { @@ -228,7 +230,7 @@ export default class XlmTransferProcessor let transaction = false; try { transaction = await this._provider.getPrepared(data, privateData, uiData); - } catch (e) { + } catch (e: any) { if (e.message.indexOf('destination is invalid') !== -1) { throw new Error('SERVER_RESPONSE_BAD_DESTINATION'); } @@ -254,7 +256,7 @@ export default class XlmTransferProcessor let result = false; try { result = await this._provider.sendRaw(raw); - } catch (e) { + } catch (e: any) { if (e.message.indexOf('op_no_destination') !== -1) { transaction = await this._provider.getPrepared( data, diff --git a/crypto/blockchains/xlm/basic/XlmTxSendProvider.ts b/crypto/blockchains/xlm/basic/XlmTxSendProvider.ts index caeccd110..1b7f3020d 100644 --- a/crypto/blockchains/xlm/basic/XlmTxSendProvider.ts +++ b/crypto/blockchains/xlm/basic/XlmTxSendProvider.ts @@ -8,8 +8,7 @@ import BlocksoftExternalSettings from '../../../common/BlocksoftExternalSettings import { BlocksoftBlockchainTypes } from '../../BlocksoftBlockchainTypes'; import { XrpTxUtils } from '../../xrp/basic/XrpTxUtils'; - -import config from '../../../../app/config/config'; +import config from '@constants/config'; const StellarSdk = require('stellar-sdk'); @@ -48,7 +47,7 @@ export class XlmTxSendProvider { CACHE_FEES_VALUE = res * 1; CACHE_FEES_TIME = now; } - } catch (e) { + } catch (e: any) { BlocksoftCryptoLog.log( 'XlmSendProvider.getFee error ' + e.message + ' link ' + this._server ); @@ -104,7 +103,7 @@ export class XlmTxSendProvider { .setTimeout(TX_TIMEOUT) .build(); } - } catch (e) { + } catch (e: any) { await BlocksoftCryptoLog.log( 'XlmTxSendProvider builder create error ' + e.message ); @@ -113,7 +112,7 @@ export class XlmTxSendProvider { try { transaction.sign(StellarSdk.Keypair.fromSecret(privateData.privateKey)); - } catch (e) { + } catch (e: any) { await BlocksoftCryptoLog.log('XlmTxSendProvider sign error ' + e.message); throw e; } @@ -172,7 +171,7 @@ export class XlmTxSendProvider { throw new Error(result.title); } } - } catch (e) { + } catch (e: any) { if (config.debug.cryptoErrors) { console.log( 'XlmTransferProcessor.sendTx error ' + e.message + ' link ' + link diff --git a/crypto/blockchains/xlm/ext/XlmDerivePath.js b/crypto/blockchains/xlm/ext/XlmDerivePath.js deleted file mode 100644 index e034a5a05..000000000 --- a/crypto/blockchains/xlm/ext/XlmDerivePath.js +++ /dev/null @@ -1,49 +0,0 @@ -/** - * @version 0.20 - * actual derivePath from 'ed25519-hd-key' - */ -const createHmac = require('create-hmac') -const ED25519_CURVE = 'ed25519 seed' -const HARDENED_OFFSET = 0x80000000 - -function getMasterKeyFromSeed(seed) { - const hmac = createHmac('sha512', ED25519_CURVE) - const I = hmac.update(Buffer.from(seed, 'hex')).digest() - const IL = I.slice(0, 32) - const IR = I.slice(32) - return { - key: IL, - chainCode: IR - } -} - -function CKDPriv(_ref, index) { - const key = _ref.key - const chainCode = _ref.chainCode - const indexBuffer = Buffer.allocUnsafe(4) - indexBuffer.writeUInt32BE(index, 0) - const data = Buffer.concat([Buffer.alloc(1, 0), key, indexBuffer]) - const I = createHmac('sha512', chainCode).update(data).digest() - const IL = I.slice(0, 32) - const IR = I.slice(32) - return { - key: IL, - chainCode: IR - } -} - -export default (seed, derivationPath) => { - const getMaster = getMasterKeyFromSeed(seed) - const key = getMaster.key - const chainCode = getMaster.chainCode - const segments = derivationPath - .split('/') - .slice(1) - .map(el => el.replace("'", '')) - .map(el => parseInt(el, 10)) - - const res = segments.reduce(function(parentKeys, segment) { - return CKDPriv(parentKeys, segment + HARDENED_OFFSET) - }, { key: key, chainCode: chainCode }) - return res -} \ No newline at end of file diff --git a/crypto/blockchains/xlm/ext/XlmDerivePath.ts b/crypto/blockchains/xlm/ext/XlmDerivePath.ts new file mode 100644 index 000000000..2adb45ac0 --- /dev/null +++ b/crypto/blockchains/xlm/ext/XlmDerivePath.ts @@ -0,0 +1,58 @@ +/** + * @version 0.20 + * actual derivePath from 'ed25519-hd-key' + */ +// @ts-ignore +import createHmac from 'create-hmac'; + +const ED25519_CURVE = 'ed25519 seed'; +const HARDENED_OFFSET = 0x80000000; + +interface KeyPair { + key: Buffer; + chainCode: Buffer; +} + +function getMasterKeyFromSeed(seed: string): KeyPair { + const hmac = createHmac('sha512', ED25519_CURVE); + const I = hmac.update(Buffer.from(seed, 'hex')).digest(); + const IL = I.slice(0, 32); + const IR = I.slice(32); + return { + key: IL, + chainCode: IR + }; +} + +function CKDPriv({ key, chainCode }: KeyPair, index: number): KeyPair { + const indexBuffer = Buffer.allocUnsafe(4); + indexBuffer.writeUInt32BE(index, 0); + const data = Buffer.concat([Buffer.alloc(1, 0), key, indexBuffer]); + const I = createHmac('sha512', chainCode).update(data).digest(); + const IL = I.slice(0, 32); + const IR = I.slice(32); + return { + key: IL, + chainCode: IR + }; +} + +export default (seed: string, derivationPath: string): KeyPair => { + const getMaster = getMasterKeyFromSeed(seed); + const key = getMaster.key; + const chainCode = getMaster.chainCode; + const segments = derivationPath + .split('/') + .slice(1) + .map((el) => el.replace("'", '')) + .map((el) => parseInt(el, 10)); + + const res = segments.reduce( + (parentKeys, segment) => { + return CKDPriv(parentKeys, segment + HARDENED_OFFSET); + }, + { key, chainCode } + ); + + return res; +}; diff --git a/crypto/blockchains/xmr/XmrAddressProcessor.ts b/crypto/blockchains/xmr/XmrAddressProcessor.ts new file mode 100644 index 000000000..345d53580 --- /dev/null +++ b/crypto/blockchains/xmr/XmrAddressProcessor.ts @@ -0,0 +1,209 @@ +/** + * @version 0.11 + * https://coinomi.github.io/bip39-monero/ + * + * + * let mnemonic = '' + * let results = await BlocksoftKeys.discoverAddresses({ mnemonic, fullTree: false, fromIndex: 0, toIndex: 1, currencyCode: ['XMR'] }) + * console.log('r', results['XMR'][0]) + */ +import MoneroUtils from './ext/MoneroUtils'; +import MoneroMnemonic from './ext/MoneroMnemonic'; +import { soliditySha3 } from 'web3-utils'; +import BlocksoftAxios from '../../common/BlocksoftAxios'; +import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog'; +import BlocksoftSecrets from '@crypto/actions/BlocksoftSecrets/BlocksoftSecrets'; +import config from '@constants/config'; + +import bitcoin from 'bitcoinjs-lib'; +import networksConstants from '../../common/ext/networks-constants'; + +const BTC = networksConstants.mainnet.network; + +export default class XmrAddressProcessor { + private _root: boolean | any = false; + + async setBasicRoot(root: any) { + this._root = root; + } + + /** + * @param {string|Buffer} privateKey + * @param {*} data.publicKey + * @param {*} data.walletHash + * @param {*} data.derivationPath + * @param {*} data.derivationIndex + * @param {*} data.derivationType + * @returns {Promise<{privateKey: string, address: string, addedData: *}>} + */ + async getAddress( + privateKey: string | Buffer, + data: any = {}, + superPrivateData: any = {} + ): Promise<{ + address: string; + privateKey: string; + addedData: { + publicViewKey: string; + publicSpendKey: string; + derivationIndex: number; + mymoneroError: number; + }; + }> { + let walletMnemonic = false; + try { + walletMnemonic = await BlocksoftSecrets.getWords({ + currencyCode: 'XMR', + mnemonic: superPrivateData.mnemonic + }); + } catch (e: any) { + if (config.debug.cryptoErrors) { + console.log( + 'XmrAddressProcessor.getAddress recheck mnemonic error ' + e.message + ); + } + } + if (!walletMnemonic) { + return { + address: 'invalidRecheck1', + privateKey: '', + addedData: { + publicViewKey: '', + publicSpendKey: '', + derivationIndex: 0, + mymoneroError: 0 + } + }; + } + + if ( + typeof data.derivationType !== 'undefined' && + data.derivationType && + data.derivationType !== 'main' + ) { + return { + address: '', + privateKey: '', + addedData: { + publicViewKey: '', + publicSpendKey: '', + derivationIndex: 0, + mymoneroError: 0 + } + }; + } + if ( + typeof data.derivationIndex === 'undefined' || + !data.derivationIndex || + data.derivationIndex === 0 + ) { + const child = this._root.derivePath("m/44'/128'/0'/0/0"); + privateKey = child.privateKey; + } else { + privateKey = Buffer.from(privateKey); + } + const keyPair = bitcoin.ECPair.fromPrivateKey(privateKey, { network: BTC }); + const rawPrivateKey = keyPair.privateKey; + const rawSecretSpendKey = soliditySha3(rawPrivateKey); + const rawSecretSpendKeyBuffer = Buffer.from( + rawSecretSpendKey.substr(2), + 'hex' + ); + + const secretSpendKey = MoneroUtils.sc_reduce32(rawSecretSpendKeyBuffer); + + const secretViewKey = MoneroUtils.hash_to_scalar(secretSpendKey); + + const words = MoneroMnemonic.secret_spend_key_to_words( + MoneroUtils.normString(secretSpendKey), + typeof data.walletHash !== 'undefined' ? data.walletHash : 'none' + ); + + if (words !== walletMnemonic.toString()) { + return { + address: 'invalidRecheck2', + privateKey: '', + addedData: { + publicViewKey: '', + publicSpendKey: '', + derivationIndex: 0, + mymoneroError: 0 + } + }; + } + + const publicSpendKey = + MoneroUtils.secret_key_to_public_key(secretSpendKey).toString('hex'); + + const publicViewKey = + MoneroUtils.secret_key_to_public_key(secretViewKey).toString('hex'); + + const address = MoneroUtils.pub_keys_to_address( + 0, + publicSpendKey, + publicViewKey + ); + + let mymoneroError = 0; + let linkParamsLogin = {}; + try { + linkParamsLogin = { + address, + view_key: MoneroUtils.normString(secretViewKey.toString('hex')), + create_account: true, + generated_locally: true + }; + const resLogin = await BlocksoftAxios.post( + 'https://api.mymonero.com:8443/login', + linkParamsLogin + ); + if ( + typeof resLogin.data === 'undefined' || + !resLogin.data || + typeof resLogin.data.new_address === 'undefined' + ) { + throw new Error('no data'); + } + } catch (e: any) { + BlocksoftCryptoLog.err( + 'XmrAddressProcessor !!!mymonero error!!! ' + e.message, + JSON.stringify({ + linkParamsLogin, + publicSpendKeyL: publicSpendKey.length, + publicViewKeyL: publicViewKey.length + }) + ); + mymoneroError = 1; + } + + /* + console.log({ + derivationPath : data.derivationPath, + secretSpendKey, + ss : Buffer.from(secretSpendKey, 'hex'), + secretViewKey, + sv : Buffer.from(secretViewKey, 'hex'), + words, + publicViewKey: publicViewKey.toString('hex'), + publicSpendKey: publicSpendKey.toString('hex'), + address + }) + */ + + return { + address, + privateKey: + // @ts-ignore + MoneroUtils.normString(secretSpendKey.toString('hex')) + + '_' + + // @ts-ignore + MoneroUtils.normString(secretViewKey.toString('hex')), + addedData: { + publicViewKey: MoneroUtils.normString(publicViewKey), + publicSpendKey: MoneroUtils.normString(publicSpendKey), + derivationIndex: 0, + mymoneroError + } + }; + } +} diff --git a/crypto/blockchains/xmr/XmrScannerProcessor.ts b/crypto/blockchains/xmr/XmrScannerProcessor.ts new file mode 100644 index 000000000..8822ebcad --- /dev/null +++ b/crypto/blockchains/xmr/XmrScannerProcessor.ts @@ -0,0 +1,412 @@ +/** + * @version 0.11 + * https://api.mymonero.com:8443/get_address_info + */ + +import BlocksoftUtils from '@crypto/common/BlocksoftUtils'; +import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; +import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; +import BlocksoftPrivateKeysUtils from '@crypto/common/BlocksoftPrivateKeysUtils'; + +import MoneroUtilsParser from './ext/MoneroUtilsParser'; + +import { showModal } from '@app/appstores/Stores/Modal/ModalActions'; +import { strings } from '@app/services/i18n'; +import config from '@constants/config'; + +const CACHE_VALID_TIME = 30000; +const CACHE = {}; +const NEVER_LOGIN = {}; +let CACHE_SHOWN_ERROR = 0; + +export default class XmrScannerProcessor { + /** + * @private + */ + _serverUrl = false; + + _blocksToConfirm = 30; + + _maxBlockNumber = 500000000; + + constructor(settings) { + this._settings = settings; + } + + async _getCache(address, additionalData, walletHash) { + if (typeof CACHE[address] !== 'undefined') { + CACHE[address].provider = 'mymonero-cache-all'; + return CACHE[address]; + } else { + return false; + } + } + + /** + * @param address + * @param additionalData + * @param walletHash + * @returns {Promise} + * @private + */ + async _get(address, additionalData, walletHash) { + BlocksoftCryptoLog.log( + 'XMR XmrScannerProcessor._get ' + walletHash + ' ' + address + ); + const now = new Date().getTime(); + if ( + typeof CACHE[address] !== 'undefined' && + now - CACHE[address].time < CACHE_VALID_TIME + ) { + CACHE[address].provider = 'mymonero-cache'; + return CACHE[address]; + } + + // @todo nodes support + // this._serverUrl = await settingsActions.getSetting('xmrServer') + // if (!this._serverUrl || this._serverUrl === 'false') { + this._serverUrl = 'api.mymonero.com:8443'; + // } + + let link = this._serverUrl.trim(); + if (link.substr(0, 4).toLowerCase() !== 'http') { + link = 'https://' + this._serverUrl; + } + if (link[link.length - 1] !== '/') { + link = link + '/'; + } + + const discoverFor = { + addressToCheck: address, + walletHash, + currencyCode: 'XMR', + derivationPath: "m/44'/0'/0'/0/0", + derivationIndex: + typeof additionalData.derivationIndex !== 'undefined' + ? additionalData.derivationIndex + : 0 + }; + + const result = await BlocksoftPrivateKeysUtils.getPrivateKey( + discoverFor, + 'XmrScannerProcessor' + ); // privateSpend_privateView + const keys = result.privateKey.split('_'); + const spendKey = keys[0]; // private spend and view keys + let viewKey = keys[1]; + while (viewKey.length < 64) { + viewKey += '0'; + } + const linkParams = { address, view_key: viewKey }; + + let res = false; + try { + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' XmrScannerProcessor._get start ' + + link + + 'get_address_info', + JSON.stringify(linkParams) + ); + res = await BlocksoftAxios.post(link + 'get_address_info', linkParams); + } catch (e: any) { + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' XmrScannerProcessor._get error ' + + e.message, + JSON.stringify(linkParams) + ); + if ( + CACHE_SHOWN_ERROR === 0 && + e.message.indexOf('invalid address and/or view key') !== -1 + ) { + showModal({ + type: 'INFO_MODAL', + icon: false, + title: strings('modal.walletLog.sorry'), + description: strings('settings.walletList.needReinstallXMR') + }); + CACHE_SHOWN_ERROR++; + if (CACHE_SHOWN_ERROR > 100) { + CACHE_SHOWN_ERROR = 0; + } + } + } + if (!res || !res.data) { + if (typeof NEVER_LOGIN[address] === 'undefined') { + const linkParamsLogin = { + address, + view_key: viewKey, + create_account: true, + generated_locally: true + }; + try { + await BlocksoftAxios.post( + 'https://api.mymonero.com:8443/login', + linkParamsLogin + ); // login needed + } catch (e: any) { + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' XmrScannerProcessor._get login error ' + + e.message, + linkParamsLogin + ); + if ( + CACHE_SHOWN_ERROR === 0 && + e.message.indexOf('invalid address and/or view key') !== -1 + ) { + showModal({ + type: 'INFO_MODAL', + icon: false, + title: strings('modal.walletLog.sorry'), + description: strings('settings.walletList.needReinstallXMR') + }); + CACHE_SHOWN_ERROR++; + if (CACHE_SHOWN_ERROR > 100) { + CACHE_SHOWN_ERROR = 0; + } + } + } + } + return false; + } + if (typeof res.data.spent_outputs === 'undefined') { + throw new Error( + 'XMR XmrScannerProcessor._get nothing loaded for address ' + link + ); + } + + let parsed = false; + try { + parsed = await MoneroUtilsParser.parseAddressInfo( + address, + res.data, + viewKey, + additionalData.publicSpendKey, + spendKey + ); + } catch (e) { + if (config.debug.cryptoErrors) { + console.log( + 'XMR XmrScannerProcessor._get MoneroUtilsParser.parseAddressInfo error ' + + e.message + ); + } + await BlocksoftCryptoLog.log( + 'XMR XmrScannerProcessor._get MoneroUtilsParser.parseAddressInfo error ' + + e.message + ); + } + + const res2 = await BlocksoftAxios.postWithoutBraking( + link + 'get_address_txs', + linkParams + ); + if (!res2 || !res2.data) { + return false; + } + + let parsed2 = false; + try { + parsed2 = await MoneroUtilsParser.parseAddressTransactions( + address, + res2.data, + viewKey, + additionalData.publicSpendKey, + spendKey + ); + } catch (e: any) { + if (config.debug.cryptoErrors) { + console.log( + 'XMR XmrScannerProcessor._get MoneroUtilsParser.parseAddressTransactions error ' + + e.message + ); + } + await BlocksoftCryptoLog.log( + 'XMR XmrScannerProcessor._get MoneroUtilsParser.parseAddressTransactions error ' + + e.message + ); + } + + if (parsed && parsed2) { + CACHE[address] = { + outputs: parsed?.spent_outputs, + transactions: + typeof parsed2.serialized_transactions !== 'undefined' + ? parsed2.serialized_transactions + : parsed2.transactions, + balance: + typeof parsed.total_received_String !== 'undefined' + ? BlocksoftUtils.diff( + parsed.total_received_String, + parsed.total_sent_String + ) + : BlocksoftUtils.diff(parsed.total_received, parsed.total_sent), + account_scan_start_height: parsed2.account_scan_start_height, + scanned_block_height: parsed2.account_scanned_block_height, + account_scanned_height: parsed2.account_scanned_height, + blockchain_height: parsed2.blockchain_height, + transaction_height: parsed2.transaction_height, + time: now, + provider: 'mymonero' + }; + return CACHE[address]; + } + return false; + } + + /** + * @param {string} address + * @param {*} additionalData + * @param {string} walletHash + * @return {Promise<{balance:*, unconfirmed:*, provider:string}>} + */ + async getBalanceBlockchain(address, additionalData, walletHash) { + if (address === 'invalidRecheck1') { + return { balance: 0, unconfirmed: 0, provider: 'error' }; + } + const res = await this._get(address, additionalData, walletHash); + if (!res) { + return false; + } + return { + balance: res.balance, + unconfirmed: 0, + provider: res.provider, + time: res.time + }; + } + + /** + * @param {string} scanData.account.address + * @param {*} scanData.additional + * @param {string} scanData.account.walletHash + * @return {Promise} + */ + async getTransactionsBlockchain(scanData, source = '') { + const address = scanData.account.address.trim(); + if (address === 'invalidRecheck1') { + return []; + } + const additionalData = scanData.additional; + const walletHash = scanData.account.walletHash; + const res = await this._get(address, additionalData, walletHash); + if (!res || typeof res === 'undefined') return []; + + if (typeof res.transactions === 'undefined' || !res.transactions) return []; + let tx; + const transactions = []; + + for (tx of res.transactions) { + const transaction = await this._unifyTransaction( + address, + res.scanned_block_height, + tx + ); + if (transaction) { + transactions.push(transaction); + } + } + return transactions; + } + + /** + * + * @param {string} address + * @param {string} lastBlock + * @param {Object} transaction + * @param {BigInteger} transaction.amount BigInteger {_d: Array(2), _s: -1} + * @param {string} transaction.approx_float_amount -0.00002724 + * @param {string} transaction.coinbase false + * @param {string} transaction.fee "27240000" + * @param {string} transaction.hash "ac319a3240f15dab342102fe248d3b95636f8a0bbfa962a5645521fac8fb86d3" + * @param {string} transaction.height 2152183 + * @param {string} transaction.id 10506991 + * @param {string} transaction.mempool: false + * @param {string} transaction.mixin 10 + * @param {string} transaction.payment_id "" + * @param {string} transaction.spent_outputs [{…}] + * @param {string} transaction.timestamp Tue Jul 28 2020 18:10:26 GMT+0300 (Восточная Европа, летнее время) {} + * @param {string} transaction.total_received "12354721582" + * @param {BigInteger} transaction.total_sent BigInteger {_d: Array(2), _s: 1} + * @param {string} transaction.unlock_time + * @return {Promise} + * @private + */ + async _unifyTransaction(address, lastBlock, transaction) { + let transactionStatus = 'new'; + transaction.confirmations = lastBlock * 1 - transaction.height * 1; + // if (transaction.mempool === false) { + if (transaction.confirmations >= this._blocksToConfirm) { + transactionStatus = 'success'; + } else if (transaction.confirmations > 0) { + transactionStatus = 'confirming'; + } + // } + + if (typeof transaction.unlock_time !== 'undefined') { + const unlockTime = transaction.unlock_time * 1; + if (unlockTime > 0) { + if (unlockTime < this._maxBlockNumber) { + // then unlock time is block height + if (unlockTime > lastBlock) { + transactionStatus = 'locked'; + } + } else { + // then unlock time is s timestamp as TimeInterval + const now = new Date().getTime(); + if (unlockTime > now) { + transactionStatus = 'locked'; + } + } + } + } + + let direction = 'self'; + let amount; + if (transaction.total_received !== '0') { + if (transaction.total_sent !== '0') { + const diff = BlocksoftUtils.diff( + transaction.total_sent, + transaction.total_received + ); + if (diff > 0) { + direction = 'outcome'; + amount = diff; + } else { + direction = 'income'; + amount = -1 * diff; + } + } else { + direction = 'income'; + amount = transaction.total_received; + } + } else if (transaction.total_sent !== '0') { + direction = 'outcome'; + amount = transaction.total_sent; + } + let formattedTime; + try { + formattedTime = BlocksoftUtils.toDate(transaction.timestamp); + } catch (e: any) { + e.message += + ' timestamp error transaction data ' + JSON.stringify(transaction); + throw e; + } + + return { + transactionHash: transaction.hash, + blockHash: transaction.id, + blockNumber: +transaction.height, + blockTime: formattedTime, + blockConfirmations: transaction.confirmations, + transactionDirection: direction, + addressFrom: '', + addressTo: '', + addressAmount: amount, + transactionStatus, + transactionFee: transaction.fee + }; + } +} diff --git a/crypto/blockchains/xmr/XmrSecretsProcessor.js b/crypto/blockchains/xmr/XmrSecretsProcessor.js deleted file mode 100644 index a61eb862d..000000000 --- a/crypto/blockchains/xmr/XmrSecretsProcessor.js +++ /dev/null @@ -1,66 +0,0 @@ -/** - * @version 0.11 - * https://github.com/Coinomi/bip39-coinomi/releases - */ -import BlocksoftKeys from '../../actions/BlocksoftKeys/BlocksoftKeys' - -import { soliditySha3 } from 'web3-utils' -import MoneroUtils from './ext/MoneroUtils' -import MoneroMnemonic from './ext/MoneroMnemonic' - -const bip32 = require('bip32') -const bitcoin = require('bitcoinjs-lib') -const networksConstants = require('../../common/ext/networks-constants') - -const BTC = networksConstants['mainnet'].network - -export default class XmrSecretsProcessor { - - /** - * @param {string} data.mnemonic - */ - async getWords(data) { - const seed = await BlocksoftKeys.getSeedCached(data.mnemonic) - const seedHex = seed.toString('hex') - if (seedHex.length < 128) { - throw new Error('bad seedHex') - } - const root = bip32.fromSeed(seed) - const child = root.derivePath('m/44\'/128\'/0\'/0/0') - const keyPair = bitcoin.ECPair.fromPrivateKey(child.privateKey, { network: BTC }) - - const rawPrivateKey = keyPair.privateKey - const rawSecretSpendKey = soliditySha3(rawPrivateKey) - const rawSecretSpendKeyBuffer = Buffer.from(rawSecretSpendKey.substr(2), 'hex') - - let secretSpendKey = MoneroUtils.sc_reduce32(rawSecretSpendKeyBuffer) - const secretSpendLength = secretSpendKey.length - if (secretSpendLength < 64) { - for (let i = secretSpendLength; i<64; i++) { - secretSpendKey = secretSpendKey + '0' - } - } - - const secretViewKey = MoneroUtils.hash_to_scalar(secretSpendKey) - - const words = MoneroMnemonic.secret_spend_key_to_words(secretSpendKey) - - const publicSpendKey = MoneroUtils.secret_key_to_public_key(secretSpendKey) - - const publicViewKey = MoneroUtils.secret_key_to_public_key(secretViewKey) - - const address = MoneroUtils.pub_keys_to_address(0, publicSpendKey, publicViewKey) - - - /*console.log({ - secretSpendKey, - secretViewKey, - words, - publicViewKey: publicViewKey.toString('hex'), - publicSpendKey: publicSpendKey.toString('hex'), - address - })*/ - - return words - } -} diff --git a/crypto/blockchains/xmr/XmrSecretsProcessor.ts b/crypto/blockchains/xmr/XmrSecretsProcessor.ts new file mode 100644 index 000000000..d5b60cf9e --- /dev/null +++ b/crypto/blockchains/xmr/XmrSecretsProcessor.ts @@ -0,0 +1,76 @@ +/** + * @version 0.11 + * https://github.com/Coinomi/bip39-coinomi/releases + */ +import BlocksoftKeys from '../../actions/BlocksoftKeys/BlocksoftKeys'; + +import { soliditySha3 } from 'web3-utils'; +import MoneroUtils from './ext/MoneroUtils'; +import MoneroMnemonic from './ext/MoneroMnemonic'; + +const bip32 = require('bip32'); +const bitcoin = require('bitcoinjs-lib'); +const networksConstants = require('../../common/ext/networks-constants'); + +const BTC = networksConstants.mainnet.network; + +export default class XmrSecretsProcessor { + /** + * @param {string} data.mnemonic + */ + async getWords(data: { mnemonic: string }) { + const seed = await BlocksoftKeys.getSeedCached(data.mnemonic); + const seedHex = seed.toString('hex'); + if (seedHex.length < 128) { + throw new Error('bad seedHex'); + } + const root = bip32.fromSeed(seed); + const child = root.derivePath("m/44'/128'/0'/0/0"); + const keyPair = bitcoin.ECPair.fromPrivateKey(child.privateKey, { + network: BTC + }); + + const rawPrivateKey = keyPair.privateKey; + const rawSecretSpendKey = soliditySha3(rawPrivateKey); + if (!rawSecretSpendKey) { + throw new Error('bad private key'); + } + const rawSecretSpendKeyBuffer = Buffer.from( + rawSecretSpendKey.substr(2), + 'hex' + ); + + let secretSpendKey = MoneroUtils.sc_reduce32(rawSecretSpendKeyBuffer); + const secretSpendLength = secretSpendKey.length; + if (secretSpendLength < 64) { + for (let i = secretSpendLength; i < 64; i++) { + secretSpendKey = secretSpendKey + '0'; + } + } + + const secretViewKey = MoneroUtils.hash_to_scalar(secretSpendKey); + + const words = MoneroMnemonic.secret_spend_key_to_words(secretSpendKey); + + const publicSpendKey = MoneroUtils.secret_key_to_public_key(secretSpendKey); + + const publicViewKey = MoneroUtils.secret_key_to_public_key(secretViewKey); + + const address = MoneroUtils.pub_keys_to_address( + 0, + publicSpendKey, + publicViewKey + ); + + /*console.log({ + secretSpendKey, + secretViewKey, + words, + publicViewKey: publicViewKey.toString('hex'), + publicSpendKey: publicSpendKey.toString('hex'), + address + })*/ + + return words; + } +} diff --git a/crypto/blockchains/xmr/XmrTransferProcessor.ts b/crypto/blockchains/xmr/XmrTransferProcessor.ts index b565a8fd4..71c52d90c 100644 --- a/crypto/blockchains/xmr/XmrTransferProcessor.ts +++ b/crypto/blockchains/xmr/XmrTransferProcessor.ts @@ -6,10 +6,17 @@ import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog'; import MoneroUtilsParser from './ext/MoneroUtilsParser'; import XmrSendProvider from './providers/XmrSendProvider'; import XmrUnspentsProvider from './providers/XmrUnspentsProvider'; +<<<<<<< Updated upstream import { BlocksoftBlockchainTypes } from '../BlocksoftBlockchainTypes'; import config from '../../../app/config/config'; import BlocksoftPrettyNumbers from '@crypto/common/BlocksoftPrettyNumbers'; +======= +import config from '@constants/config'; + +import BlocksoftPrettyNumbers from '@crypto/common/BlocksoftPrettyNumbers'; +import { BlocksoftBlockchainTypes } from '@lib/blockchains/BlocksoftBlockchainTypes'; +>>>>>>> Stashed changes export default class XmrTransferProcessor implements BlocksoftBlockchainTypes.TransferProcessor @@ -129,16 +136,27 @@ export default class XmrTransferProcessor ) } ], +<<<<<<< Updated upstream shouldSweep: data.isTransferAll ? true : false, +======= + shouldSweep: data.isTransferAll, +>>>>>>> Stashed changes address: data.addressFrom, privateViewKey: privViewKey, privateSpendKey: privSpendKey, publicSpendKey: pubSpendKey, priority: '' + i, nettype: 'MAINNET', +<<<<<<< Updated upstream unspentOuts: unspentOuts, randomOutsCb: (numberOfOuts) => { const amounts = []; +======= + unspentOuts, + randomOutsCb: (numberOfOuts: number) => { + const amounts = []; + // tslint:disable-next-line:no-shadowed-variable +>>>>>>> Stashed changes for (let i = 0; i < numberOfOuts; i++) { amounts.push('0'); } @@ -186,7 +204,11 @@ export default class XmrTransferProcessor result.fees.push(tmp); logFees.push(logTmp); } +<<<<<<< Updated upstream } catch (e) { +======= + } catch (e: any) { +>>>>>>> Stashed changes if (e.message.indexOf('pendable balance too low') !== -1) { // do nothing noBalanceError = true; @@ -251,6 +273,10 @@ export default class XmrTransferProcessor ' finished amount: ' + data.amount + ' fee: ', +<<<<<<< Updated upstream +======= + // @ts-ignore +>>>>>>> Stashed changes logFees ); @@ -269,10 +295,17 @@ export default class XmrTransferProcessor ): Promise { const balance = data.amount; +<<<<<<< Updated upstream // @ts-ignore BlocksoftCryptoLog.log( this._settings.currencyCode + ' XmrTransferProcessor.getTransferAllBalance ', +======= + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' XmrTransferProcessor.getTransferAllBalance ', + // @ts-ignore +>>>>>>> Stashed changes data.addressFrom + ' => ' + balance ); @@ -321,6 +354,10 @@ export default class XmrTransferProcessor '=>' + data.addressTo + ' fee', +<<<<<<< Updated upstream +======= + // @ts-ignore +>>>>>>> Stashed changes uiData.selectedFee ); let foundFee = uiData?.selectedFee; @@ -355,9 +392,16 @@ export default class XmrTransferProcessor '=>' + data.addressTo + ' found fee', +<<<<<<< Updated upstream foundFee ); } catch (e) { +======= + // @ts-ignore + foundFee + ); + } catch (e: any) { +>>>>>>> Stashed changes BlocksoftCryptoLog.log( this._settings.currencyCode + ' XmrTransferProcessor.sendTx rechecked ' + @@ -388,6 +432,10 @@ export default class XmrTransferProcessor typeof uiData.selectedFee.rawOnly !== 'undefined' && uiData.selectedFee.rawOnly ) { +<<<<<<< Updated upstream +======= + // TODO fix this ts error +>>>>>>> Stashed changes return { rawOnly: uiData.selectedFee.rawOnly, raw: rawTxHex }; } diff --git a/crypto/blockchains/xmr/ext/MoneroDict.js b/crypto/blockchains/xmr/ext/MoneroDict.js deleted file mode 100644 index ce521ca03..000000000 --- a/crypto/blockchains/xmr/ext/MoneroDict.js +++ /dev/null @@ -1,1634 +0,0 @@ -const monero_words_english_prefix_len = 3; -const monero_words_english = [ - "abbey", - "abducts", - "ability", - "ablaze", - "abnormal", - "abort", - "abrasive", - "absorb", - "abyss", - "academy", - "aces", - "aching", - "acidic", - "acoustic", - "acquire", - "across", - "actress", - "acumen", - "adapt", - "addicted", - "adept", - "adhesive", - "adjust", - "adopt", - "adrenalin", - "adult", - "adventure", - "aerial", - "afar", - "affair", - "afield", - "afloat", - "afoot", - "afraid", - "after", - "against", - "agenda", - "aggravate", - "agile", - "aglow", - "agnostic", - "agony", - "agreed", - "ahead", - "aided", - "ailments", - "aimless", - "airport", - "aisle", - "ajar", - "akin", - "alarms", - "album", - "alchemy", - "alerts", - "algebra", - "alkaline", - "alley", - "almost", - "aloof", - "alpine", - "already", - "also", - "altitude", - "alumni", - "always", - "amaze", - "ambush", - "amended", - "amidst", - "ammo", - "amnesty", - "among", - "amply", - "amused", - "anchor", - "android", - "anecdote", - "angled", - "ankle", - "annoyed", - "answers", - "antics", - "anvil", - "anxiety", - "anybody", - "apart", - "apex", - "aphid", - "aplomb", - "apology", - "apply", - "apricot", - "aptitude", - "aquarium", - "arbitrary", - "archer", - "ardent", - "arena", - "argue", - "arises", - "army", - "around", - "arrow", - "arsenic", - "artistic", - "ascend", - "ashtray", - "aside", - "asked", - "asleep", - "aspire", - "assorted", - "asylum", - "athlete", - "atlas", - "atom", - "atrium", - "attire", - "auburn", - "auctions", - "audio", - "august", - "aunt", - "austere", - "autumn", - "avatar", - "avidly", - "avoid", - "awakened", - "awesome", - "awful", - "awkward", - "awning", - "awoken", - "axes", - "axis", - "axle", - "aztec", - "azure", - "baby", - "bacon", - "badge", - "baffles", - "bagpipe", - "bailed", - "bakery", - "balding", - "bamboo", - "banjo", - "baptism", - "basin", - "batch", - "bawled", - "bays", - "because", - "beer", - "befit", - "begun", - "behind", - "being", - "below", - "bemused", - "benches", - "berries", - "bested", - "betting", - "bevel", - "beware", - "beyond", - "bias", - "bicycle", - "bids", - "bifocals", - "biggest", - "bikini", - "bimonthly", - "binocular", - "biology", - "biplane", - "birth", - "biscuit", - "bite", - "biweekly", - "blender", - "blip", - "bluntly", - "boat", - "bobsled", - "bodies", - "bogeys", - "boil", - "boldly", - "bomb", - "border", - "boss", - "both", - "bounced", - "bovine", - "bowling", - "boxes", - "boyfriend", - "broken", - "brunt", - "bubble", - "buckets", - "budget", - "buffet", - "bugs", - "building", - "bulb", - "bumper", - "bunch", - "business", - "butter", - "buying", - "buzzer", - "bygones", - "byline", - "bypass", - "cabin", - "cactus", - "cadets", - "cafe", - "cage", - "cajun", - "cake", - "calamity", - "camp", - "candy", - "casket", - "catch", - "cause", - "cavernous", - "cease", - "cedar", - "ceiling", - "cell", - "cement", - "cent", - "certain", - "chlorine", - "chrome", - "cider", - "cigar", - "cinema", - "circle", - "cistern", - "citadel", - "civilian", - "claim", - "click", - "clue", - "coal", - "cobra", - "cocoa", - "code", - "coexist", - "coffee", - "cogs", - "cohesive", - "coils", - "colony", - "comb", - "cool", - "copy", - "corrode", - "costume", - "cottage", - "cousin", - "cowl", - "criminal", - "cube", - "cucumber", - "cuddled", - "cuffs", - "cuisine", - "cunning", - "cupcake", - "custom", - "cycling", - "cylinder", - "cynical", - "dabbing", - "dads", - "daft", - "dagger", - "daily", - "damp", - "dangerous", - "dapper", - "darted", - "dash", - "dating", - "dauntless", - "dawn", - "daytime", - "dazed", - "debut", - "decay", - "dedicated", - "deepest", - "deftly", - "degrees", - "dehydrate", - "deity", - "dejected", - "delayed", - "demonstrate", - "dented", - "deodorant", - "depth", - "desk", - "devoid", - "dewdrop", - "dexterity", - "dialect", - "dice", - "diet", - "different", - "digit", - "dilute", - "dime", - "dinner", - "diode", - "diplomat", - "directed", - "distance", - "ditch", - "divers", - "dizzy", - "doctor", - "dodge", - "does", - "dogs", - "doing", - "dolphin", - "domestic", - "donuts", - "doorway", - "dormant", - "dosage", - "dotted", - "double", - "dove", - "down", - "dozen", - "dreams", - "drinks", - "drowning", - "drunk", - "drying", - "dual", - "dubbed", - "duckling", - "dude", - "duets", - "duke", - "dullness", - "dummy", - "dunes", - "duplex", - "duration", - "dusted", - "duties", - "dwarf", - "dwelt", - "dwindling", - "dying", - "dynamite", - "dyslexic", - "each", - "eagle", - "earth", - "easy", - "eating", - "eavesdrop", - "eccentric", - "echo", - "eclipse", - "economics", - "ecstatic", - "eden", - "edgy", - "edited", - "educated", - "eels", - "efficient", - "eggs", - "egotistic", - "eight", - "either", - "eject", - "elapse", - "elbow", - "eldest", - "eleven", - "elite", - "elope", - "else", - "eluded", - "emails", - "ember", - "emerge", - "emit", - "emotion", - "empty", - "emulate", - "energy", - "enforce", - "enhanced", - "enigma", - "enjoy", - "enlist", - "enmity", - "enough", - "enraged", - "ensign", - "entrance", - "envy", - "epoxy", - "equip", - "erase", - "erected", - "erosion", - "error", - "eskimos", - "espionage", - "essential", - "estate", - "etched", - "eternal", - "ethics", - "etiquette", - "evaluate", - "evenings", - "evicted", - "evolved", - "examine", - "excess", - "exhale", - "exit", - "exotic", - "exquisite", - "extra", - "exult", - "fabrics", - "factual", - "fading", - "fainted", - "faked", - "fall", - "family", - "fancy", - "farming", - "fatal", - "faulty", - "fawns", - "faxed", - "fazed", - "feast", - "february", - "federal", - "feel", - "feline", - "females", - "fences", - "ferry", - "festival", - "fetches", - "fever", - "fewest", - "fiat", - "fibula", - "fictional", - "fidget", - "fierce", - "fifteen", - "fight", - "films", - "firm", - "fishing", - "fitting", - "five", - "fixate", - "fizzle", - "fleet", - "flippant", - "flying", - "foamy", - "focus", - "foes", - "foggy", - "foiled", - "folding", - "fonts", - "foolish", - "fossil", - "fountain", - "fowls", - "foxes", - "foyer", - "framed", - "friendly", - "frown", - "fruit", - "frying", - "fudge", - "fuel", - "fugitive", - "fully", - "fuming", - "fungal", - "furnished", - "fuselage", - "future", - "fuzzy", - "gables", - "gadget", - "gags", - "gained", - "galaxy", - "gambit", - "gang", - "gasp", - "gather", - "gauze", - "gave", - "gawk", - "gaze", - "gearbox", - "gecko", - "geek", - "gels", - "gemstone", - "general", - "geometry", - "germs", - "gesture", - "getting", - "geyser", - "ghetto", - "ghost", - "giant", - "giddy", - "gifts", - "gigantic", - "gills", - "gimmick", - "ginger", - "girth", - "giving", - "glass", - "gleeful", - "glide", - "gnaw", - "gnome", - "goat", - "goblet", - "godfather", - "goes", - "goggles", - "going", - "goldfish", - "gone", - "goodbye", - "gopher", - "gorilla", - "gossip", - "gotten", - "gourmet", - "governing", - "gown", - "greater", - "grunt", - "guarded", - "guest", - "guide", - "gulp", - "gumball", - "guru", - "gusts", - "gutter", - "guys", - "gymnast", - "gypsy", - "gyrate", - "habitat", - "hacksaw", - "haggled", - "hairy", - "hamburger", - "happens", - "hashing", - "hatchet", - "haunted", - "having", - "hawk", - "haystack", - "hazard", - "hectare", - "hedgehog", - "heels", - "hefty", - "height", - "hemlock", - "hence", - "heron", - "hesitate", - "hexagon", - "hickory", - "hiding", - "highway", - "hijack", - "hiker", - "hills", - "himself", - "hinder", - "hippo", - "hire", - "history", - "hitched", - "hive", - "hoax", - "hobby", - "hockey", - "hoisting", - "hold", - "honked", - "hookup", - "hope", - "hornet", - "hospital", - "hotel", - "hounded", - "hover", - "howls", - "hubcaps", - "huddle", - "huge", - "hull", - "humid", - "hunter", - "hurried", - "husband", - "huts", - "hybrid", - "hydrogen", - "hyper", - "iceberg", - "icing", - "icon", - "identity", - "idiom", - "idled", - "idols", - "igloo", - "ignore", - "iguana", - "illness", - "imagine", - "imbalance", - "imitate", - "impel", - "inactive", - "inbound", - "incur", - "industrial", - "inexact", - "inflamed", - "ingested", - "initiate", - "injury", - "inkling", - "inline", - "inmate", - "innocent", - "inorganic", - "input", - "inquest", - "inroads", - "insult", - "intended", - "inundate", - "invoke", - "inwardly", - "ionic", - "irate", - "iris", - "irony", - "irritate", - "island", - "isolated", - "issued", - "italics", - "itches", - "items", - "itinerary", - "itself", - "ivory", - "jabbed", - "jackets", - "jaded", - "jagged", - "jailed", - "jamming", - "january", - "jargon", - "jaunt", - "javelin", - "jaws", - "jazz", - "jeans", - "jeers", - "jellyfish", - "jeopardy", - "jerseys", - "jester", - "jetting", - "jewels", - "jigsaw", - "jingle", - "jittery", - "jive", - "jobs", - "jockey", - "jogger", - "joining", - "joking", - "jolted", - "jostle", - "journal", - "joyous", - "jubilee", - "judge", - "juggled", - "juicy", - "jukebox", - "july", - "jump", - "junk", - "jury", - "justice", - "juvenile", - "kangaroo", - "karate", - "keep", - "kennel", - "kept", - "kernels", - "kettle", - "keyboard", - "kickoff", - "kidneys", - "king", - "kiosk", - "kisses", - "kitchens", - "kiwi", - "knapsack", - "knee", - "knife", - "knowledge", - "knuckle", - "koala", - "laboratory", - "ladder", - "lagoon", - "lair", - "lakes", - "lamb", - "language", - "laptop", - "large", - "last", - "later", - "launching", - "lava", - "lawsuit", - "layout", - "lazy", - "lectures", - "ledge", - "leech", - "left", - "legion", - "leisure", - "lemon", - "lending", - "leopard", - "lesson", - "lettuce", - "lexicon", - "liar", - "library", - "licks", - "lids", - "lied", - "lifestyle", - "light", - "likewise", - "lilac", - "limits", - "linen", - "lion", - "lipstick", - "liquid", - "listen", - "lively", - "loaded", - "lobster", - "locker", - "lodge", - "lofty", - "logic", - "loincloth", - "long", - "looking", - "lopped", - "lordship", - "losing", - "lottery", - "loudly", - "love", - "lower", - "loyal", - "lucky", - "luggage", - "lukewarm", - "lullaby", - "lumber", - "lunar", - "lurk", - "lush", - "luxury", - "lymph", - "lynx", - "lyrics", - "macro", - "madness", - "magically", - "mailed", - "major", - "makeup", - "malady", - "mammal", - "maps", - "masterful", - "match", - "maul", - "maverick", - "maximum", - "mayor", - "maze", - "meant", - "mechanic", - "medicate", - "meeting", - "megabyte", - "melting", - "memoir", - "menu", - "merger", - "mesh", - "metro", - "mews", - "mice", - "midst", - "mighty", - "mime", - "mirror", - "misery", - "mittens", - "mixture", - "moat", - "mobile", - "mocked", - "mohawk", - "moisture", - "molten", - "moment", - "money", - "moon", - "mops", - "morsel", - "mostly", - "motherly", - "mouth", - "movement", - "mowing", - "much", - "muddy", - "muffin", - "mugged", - "mullet", - "mumble", - "mundane", - "muppet", - "mural", - "musical", - "muzzle", - "myriad", - "mystery", - "myth", - "nabbing", - "nagged", - "nail", - "names", - "nanny", - "napkin", - "narrate", - "nasty", - "natural", - "nautical", - "navy", - "nearby", - "necklace", - "needed", - "negative", - "neither", - "neon", - "nephew", - "nerves", - "nestle", - "network", - "neutral", - "never", - "newt", - "nexus", - "nibs", - "niche", - "niece", - "nifty", - "nightly", - "nimbly", - "nineteen", - "nirvana", - "nitrogen", - "nobody", - "nocturnal", - "nodes", - "noises", - "nomad", - "noodles", - "northern", - "nostril", - "noted", - "nouns", - "novelty", - "nowhere", - "nozzle", - "nuance", - "nucleus", - "nudged", - "nugget", - "nuisance", - "null", - "number", - "nuns", - "nurse", - "nutshell", - "nylon", - "oaks", - "oars", - "oasis", - "oatmeal", - "obedient", - "object", - "obliged", - "obnoxious", - "observant", - "obtains", - "obvious", - "occur", - "ocean", - "october", - "odds", - "odometer", - "offend", - "often", - "oilfield", - "ointment", - "okay", - "older", - "olive", - "olympics", - "omega", - "omission", - "omnibus", - "onboard", - "oncoming", - "oneself", - "ongoing", - "onion", - "online", - "onslaught", - "onto", - "onward", - "oozed", - "opacity", - "opened", - "opposite", - "optical", - "opus", - "orange", - "orbit", - "orchid", - "orders", - "organs", - "origin", - "ornament", - "orphans", - "oscar", - "ostrich", - "otherwise", - "otter", - "ouch", - "ought", - "ounce", - "ourselves", - "oust", - "outbreak", - "oval", - "oven", - "owed", - "owls", - "owner", - "oxidant", - "oxygen", - "oyster", - "ozone", - "pact", - "paddles", - "pager", - "pairing", - "palace", - "pamphlet", - "pancakes", - "paper", - "paradise", - "pastry", - "patio", - "pause", - "pavements", - "pawnshop", - "payment", - "peaches", - "pebbles", - "peculiar", - "pedantic", - "peeled", - "pegs", - "pelican", - "pencil", - "people", - "pepper", - "perfect", - "pests", - "petals", - "phase", - "pheasants", - "phone", - "phrases", - "physics", - "piano", - "picked", - "pierce", - "pigment", - "piloted", - "pimple", - "pinched", - "pioneer", - "pipeline", - "pirate", - "pistons", - "pitched", - "pivot", - "pixels", - "pizza", - "playful", - "pledge", - "pliers", - "plotting", - "plus", - "plywood", - "poaching", - "pockets", - "podcast", - "poetry", - "point", - "poker", - "polar", - "ponies", - "pool", - "popular", - "portents", - "possible", - "potato", - "pouch", - "poverty", - "powder", - "pram", - "present", - "pride", - "problems", - "pruned", - "prying", - "psychic", - "public", - "puck", - "puddle", - "puffin", - "pulp", - "pumpkins", - "punch", - "puppy", - "purged", - "push", - "putty", - "puzzled", - "pylons", - "pyramid", - "python", - "queen", - "quick", - "quote", - "rabbits", - "racetrack", - "radar", - "rafts", - "rage", - "railway", - "raking", - "rally", - "ramped", - "randomly", - "rapid", - "rarest", - "rash", - "rated", - "ravine", - "rays", - "razor", - "react", - "rebel", - "recipe", - "reduce", - "reef", - "refer", - "regular", - "reheat", - "reinvest", - "rejoices", - "rekindle", - "relic", - "remedy", - "renting", - "reorder", - "repent", - "request", - "reruns", - "rest", - "return", - "reunion", - "revamp", - "rewind", - "rhino", - "rhythm", - "ribbon", - "richly", - "ridges", - "rift", - "rigid", - "rims", - "ringing", - "riots", - "ripped", - "rising", - "ritual", - "river", - "roared", - "robot", - "rockets", - "rodent", - "rogue", - "roles", - "romance", - "roomy", - "roped", - "roster", - "rotate", - "rounded", - "rover", - "rowboat", - "royal", - "ruby", - "rudely", - "ruffled", - "rugged", - "ruined", - "ruling", - "rumble", - "runway", - "rural", - "rustled", - "ruthless", - "sabotage", - "sack", - "sadness", - "safety", - "saga", - "sailor", - "sake", - "salads", - "sample", - "sanity", - "sapling", - "sarcasm", - "sash", - "satin", - "saucepan", - "saved", - "sawmill", - "saxophone", - "sayings", - "scamper", - "scenic", - "school", - "science", - "scoop", - "scrub", - "scuba", - "seasons", - "second", - "sedan", - "seeded", - "segments", - "seismic", - "selfish", - "semifinal", - "sensible", - "september", - "sequence", - "serving", - "session", - "setup", - "seventh", - "sewage", - "shackles", - "shelter", - "shipped", - "shocking", - "shrugged", - "shuffled", - "shyness", - "siblings", - "sickness", - "sidekick", - "sieve", - "sifting", - "sighting", - "silk", - "simplest", - "sincerely", - "sipped", - "siren", - "situated", - "sixteen", - "sizes", - "skater", - "skew", - "skirting", - "skulls", - "skydive", - "slackens", - "sleepless", - "slid", - "slower", - "slug", - "smash", - "smelting", - "smidgen", - "smog", - "smuggled", - "snake", - "sneeze", - "sniff", - "snout", - "snug", - "soapy", - "sober", - "soccer", - "soda", - "software", - "soggy", - "soil", - "solved", - "somewhere", - "sonic", - "soothe", - "soprano", - "sorry", - "southern", - "sovereign", - "sowed", - "soya", - "space", - "speedy", - "sphere", - "spiders", - "splendid", - "spout", - "sprig", - "spud", - "spying", - "square", - "stacking", - "stellar", - "stick", - "stockpile", - "strained", - "stunning", - "stylishly", - "subtly", - "succeed", - "suddenly", - "suede", - "suffice", - "sugar", - "suitcase", - "sulking", - "summon", - "sunken", - "superior", - "surfer", - "sushi", - "suture", - "swagger", - "swept", - "swiftly", - "sword", - "swung", - "syllabus", - "symptoms", - "syndrome", - "syringe", - "system", - "taboo", - "tacit", - "tadpoles", - "tagged", - "tail", - "taken", - "talent", - "tamper", - "tanks", - "tapestry", - "tarnished", - "tasked", - "tattoo", - "taunts", - "tavern", - "tawny", - "taxi", - "teardrop", - "technical", - "tedious", - "teeming", - "tell", - "template", - "tender", - "tepid", - "tequila", - "terminal", - "testing", - "tether", - "textbook", - "thaw", - "theatrics", - "thirsty", - "thorn", - "threaten", - "thumbs", - "thwart", - "ticket", - "tidy", - "tiers", - "tiger", - "tilt", - "timber", - "tinted", - "tipsy", - "tirade", - "tissue", - "titans", - "toaster", - "tobacco", - "today", - "toenail", - "toffee", - "together", - "toilet", - "token", - "tolerant", - "tomorrow", - "tonic", - "toolbox", - "topic", - "torch", - "tossed", - "total", - "touchy", - "towel", - "toxic", - "toyed", - "trash", - "trendy", - "tribal", - "trolling", - "truth", - "trying", - "tsunami", - "tubes", - "tucks", - "tudor", - "tuesday", - "tufts", - "tugs", - "tuition", - "tulips", - "tumbling", - "tunnel", - "turnip", - "tusks", - "tutor", - "tuxedo", - "twang", - "tweezers", - "twice", - "twofold", - "tycoon", - "typist", - "tyrant", - "ugly", - "ulcers", - "ultimate", - "umbrella", - "umpire", - "unafraid", - "unbending", - "uncle", - "under", - "uneven", - "unfit", - "ungainly", - "unhappy", - "union", - "unjustly", - "unknown", - "unlikely", - "unmask", - "unnoticed", - "unopened", - "unplugs", - "unquoted", - "unrest", - "unsafe", - "until", - "unusual", - "unveil", - "unwind", - "unzip", - "upbeat", - "upcoming", - "update", - "upgrade", - "uphill", - "upkeep", - "upload", - "upon", - "upper", - "upright", - "upstairs", - "uptight", - "upwards", - "urban", - "urchins", - "urgent", - "usage", - "useful", - "usher", - "using", - "usual", - "utensils", - "utility", - "utmost", - "utopia", - "uttered", - "vacation", - "vague", - "vain", - "value", - "vampire", - "vane", - "vapidly", - "vary", - "vastness", - "vats", - "vaults", - "vector", - "veered", - "vegan", - "vehicle", - "vein", - "velvet", - "venomous", - "verification", - "vessel", - "veteran", - "vexed", - "vials", - "vibrate", - "victim", - "video", - "viewpoint", - "vigilant", - "viking", - "village", - "vinegar", - "violin", - "vipers", - "virtual", - "visited", - "vitals", - "vivid", - "vixen", - "vocal", - "vogue", - "voice", - "volcano", - "vortex", - "voted", - "voucher", - "vowels", - "voyage", - "vulture", - "wade", - "waffle", - "wagtail", - "waist", - "waking", - "wallets", - "wanted", - "warped", - "washing", - "water", - "waveform", - "waxing", - "wayside", - "weavers", - "website", - "wedge", - "weekday", - "weird", - "welders", - "went", - "wept", - "were", - "western", - "wetsuit", - "whale", - "when", - "whipped", - "whole", - "wickets", - "width", - "wield", - "wife", - "wiggle", - "wildly", - "winter", - "wipeout", - "wiring", - "wise", - "withdrawn", - "wives", - "wizard", - "wobbly", - "woes", - "woken", - "wolf", - "womanly", - "wonders", - "woozy", - "worry", - "wounded", - "woven", - "wrap", - "wrist", - "wrong", - "yacht", - "yahoo", - "yanks", - "yard", - "yawning", - "yearbook", - "yellow", - "yesterday", - "yeti", - "yields", - "yodel", - "yoga", - "younger", - "yoyo", - "zapped", - "zeal", - "zebra", - "zero", - "zesty", - "zigzags", - "zinger", - "zippers", - "zodiac", - "zombie", - "zones", - "zoom", -]; - -export default { - monero_words_english, - monero_words_english_prefix_len -} diff --git a/crypto/blockchains/xmr/ext/MoneroDict.ts b/crypto/blockchains/xmr/ext/MoneroDict.ts new file mode 100644 index 000000000..c3633fede --- /dev/null +++ b/crypto/blockchains/xmr/ext/MoneroDict.ts @@ -0,0 +1,1634 @@ +const monero_words_english_prefix_len = 3; +const monero_words_english = [ + 'abbey', + 'abducts', + 'ability', + 'ablaze', + 'abnormal', + 'abort', + 'abrasive', + 'absorb', + 'abyss', + 'academy', + 'aces', + 'aching', + 'acidic', + 'acoustic', + 'acquire', + 'across', + 'actress', + 'acumen', + 'adapt', + 'addicted', + 'adept', + 'adhesive', + 'adjust', + 'adopt', + 'adrenalin', + 'adult', + 'adventure', + 'aerial', + 'afar', + 'affair', + 'afield', + 'afloat', + 'afoot', + 'afraid', + 'after', + 'against', + 'agenda', + 'aggravate', + 'agile', + 'aglow', + 'agnostic', + 'agony', + 'agreed', + 'ahead', + 'aided', + 'ailments', + 'aimless', + 'airport', + 'aisle', + 'ajar', + 'akin', + 'alarms', + 'album', + 'alchemy', + 'alerts', + 'algebra', + 'alkaline', + 'alley', + 'almost', + 'aloof', + 'alpine', + 'already', + 'also', + 'altitude', + 'alumni', + 'always', + 'amaze', + 'ambush', + 'amended', + 'amidst', + 'ammo', + 'amnesty', + 'among', + 'amply', + 'amused', + 'anchor', + 'android', + 'anecdote', + 'angled', + 'ankle', + 'annoyed', + 'answers', + 'antics', + 'anvil', + 'anxiety', + 'anybody', + 'apart', + 'apex', + 'aphid', + 'aplomb', + 'apology', + 'apply', + 'apricot', + 'aptitude', + 'aquarium', + 'arbitrary', + 'archer', + 'ardent', + 'arena', + 'argue', + 'arises', + 'army', + 'around', + 'arrow', + 'arsenic', + 'artistic', + 'ascend', + 'ashtray', + 'aside', + 'asked', + 'asleep', + 'aspire', + 'assorted', + 'asylum', + 'athlete', + 'atlas', + 'atom', + 'atrium', + 'attire', + 'auburn', + 'auctions', + 'audio', + 'august', + 'aunt', + 'austere', + 'autumn', + 'avatar', + 'avidly', + 'avoid', + 'awakened', + 'awesome', + 'awful', + 'awkward', + 'awning', + 'awoken', + 'axes', + 'axis', + 'axle', + 'aztec', + 'azure', + 'baby', + 'bacon', + 'badge', + 'baffles', + 'bagpipe', + 'bailed', + 'bakery', + 'balding', + 'bamboo', + 'banjo', + 'baptism', + 'basin', + 'batch', + 'bawled', + 'bays', + 'because', + 'beer', + 'befit', + 'begun', + 'behind', + 'being', + 'below', + 'bemused', + 'benches', + 'berries', + 'bested', + 'betting', + 'bevel', + 'beware', + 'beyond', + 'bias', + 'bicycle', + 'bids', + 'bifocals', + 'biggest', + 'bikini', + 'bimonthly', + 'binocular', + 'biology', + 'biplane', + 'birth', + 'biscuit', + 'bite', + 'biweekly', + 'blender', + 'blip', + 'bluntly', + 'boat', + 'bobsled', + 'bodies', + 'bogeys', + 'boil', + 'boldly', + 'bomb', + 'border', + 'boss', + 'both', + 'bounced', + 'bovine', + 'bowling', + 'boxes', + 'boyfriend', + 'broken', + 'brunt', + 'bubble', + 'buckets', + 'budget', + 'buffet', + 'bugs', + 'building', + 'bulb', + 'bumper', + 'bunch', + 'business', + 'butter', + 'buying', + 'buzzer', + 'bygones', + 'byline', + 'bypass', + 'cabin', + 'cactus', + 'cadets', + 'cafe', + 'cage', + 'cajun', + 'cake', + 'calamity', + 'camp', + 'candy', + 'casket', + 'catch', + 'cause', + 'cavernous', + 'cease', + 'cedar', + 'ceiling', + 'cell', + 'cement', + 'cent', + 'certain', + 'chlorine', + 'chrome', + 'cider', + 'cigar', + 'cinema', + 'circle', + 'cistern', + 'citadel', + 'civilian', + 'claim', + 'click', + 'clue', + 'coal', + 'cobra', + 'cocoa', + 'code', + 'coexist', + 'coffee', + 'cogs', + 'cohesive', + 'coils', + 'colony', + 'comb', + 'cool', + 'copy', + 'corrode', + 'costume', + 'cottage', + 'cousin', + 'cowl', + 'criminal', + 'cube', + 'cucumber', + 'cuddled', + 'cuffs', + 'cuisine', + 'cunning', + 'cupcake', + 'custom', + 'cycling', + 'cylinder', + 'cynical', + 'dabbing', + 'dads', + 'daft', + 'dagger', + 'daily', + 'damp', + 'dangerous', + 'dapper', + 'darted', + 'dash', + 'dating', + 'dauntless', + 'dawn', + 'daytime', + 'dazed', + 'debut', + 'decay', + 'dedicated', + 'deepest', + 'deftly', + 'degrees', + 'dehydrate', + 'deity', + 'dejected', + 'delayed', + 'demonstrate', + 'dented', + 'deodorant', + 'depth', + 'desk', + 'devoid', + 'dewdrop', + 'dexterity', + 'dialect', + 'dice', + 'diet', + 'different', + 'digit', + 'dilute', + 'dime', + 'dinner', + 'diode', + 'diplomat', + 'directed', + 'distance', + 'ditch', + 'divers', + 'dizzy', + 'doctor', + 'dodge', + 'does', + 'dogs', + 'doing', + 'dolphin', + 'domestic', + 'donuts', + 'doorway', + 'dormant', + 'dosage', + 'dotted', + 'double', + 'dove', + 'down', + 'dozen', + 'dreams', + 'drinks', + 'drowning', + 'drunk', + 'drying', + 'dual', + 'dubbed', + 'duckling', + 'dude', + 'duets', + 'duke', + 'dullness', + 'dummy', + 'dunes', + 'duplex', + 'duration', + 'dusted', + 'duties', + 'dwarf', + 'dwelt', + 'dwindling', + 'dying', + 'dynamite', + 'dyslexic', + 'each', + 'eagle', + 'earth', + 'easy', + 'eating', + 'eavesdrop', + 'eccentric', + 'echo', + 'eclipse', + 'economics', + 'ecstatic', + 'eden', + 'edgy', + 'edited', + 'educated', + 'eels', + 'efficient', + 'eggs', + 'egotistic', + 'eight', + 'either', + 'eject', + 'elapse', + 'elbow', + 'eldest', + 'eleven', + 'elite', + 'elope', + 'else', + 'eluded', + 'emails', + 'ember', + 'emerge', + 'emit', + 'emotion', + 'empty', + 'emulate', + 'energy', + 'enforce', + 'enhanced', + 'enigma', + 'enjoy', + 'enlist', + 'enmity', + 'enough', + 'enraged', + 'ensign', + 'entrance', + 'envy', + 'epoxy', + 'equip', + 'erase', + 'erected', + 'erosion', + 'error', + 'eskimos', + 'espionage', + 'essential', + 'estate', + 'etched', + 'eternal', + 'ethics', + 'etiquette', + 'evaluate', + 'evenings', + 'evicted', + 'evolved', + 'examine', + 'excess', + 'exhale', + 'exit', + 'exotic', + 'exquisite', + 'extra', + 'exult', + 'fabrics', + 'factual', + 'fading', + 'fainted', + 'faked', + 'fall', + 'family', + 'fancy', + 'farming', + 'fatal', + 'faulty', + 'fawns', + 'faxed', + 'fazed', + 'feast', + 'february', + 'federal', + 'feel', + 'feline', + 'females', + 'fences', + 'ferry', + 'festival', + 'fetches', + 'fever', + 'fewest', + 'fiat', + 'fibula', + 'fictional', + 'fidget', + 'fierce', + 'fifteen', + 'fight', + 'films', + 'firm', + 'fishing', + 'fitting', + 'five', + 'fixate', + 'fizzle', + 'fleet', + 'flippant', + 'flying', + 'foamy', + 'focus', + 'foes', + 'foggy', + 'foiled', + 'folding', + 'fonts', + 'foolish', + 'fossil', + 'fountain', + 'fowls', + 'foxes', + 'foyer', + 'framed', + 'friendly', + 'frown', + 'fruit', + 'frying', + 'fudge', + 'fuel', + 'fugitive', + 'fully', + 'fuming', + 'fungal', + 'furnished', + 'fuselage', + 'future', + 'fuzzy', + 'gables', + 'gadget', + 'gags', + 'gained', + 'galaxy', + 'gambit', + 'gang', + 'gasp', + 'gather', + 'gauze', + 'gave', + 'gawk', + 'gaze', + 'gearbox', + 'gecko', + 'geek', + 'gels', + 'gemstone', + 'general', + 'geometry', + 'germs', + 'gesture', + 'getting', + 'geyser', + 'ghetto', + 'ghost', + 'giant', + 'giddy', + 'gifts', + 'gigantic', + 'gills', + 'gimmick', + 'ginger', + 'girth', + 'giving', + 'glass', + 'gleeful', + 'glide', + 'gnaw', + 'gnome', + 'goat', + 'goblet', + 'godfather', + 'goes', + 'goggles', + 'going', + 'goldfish', + 'gone', + 'goodbye', + 'gopher', + 'gorilla', + 'gossip', + 'gotten', + 'gourmet', + 'governing', + 'gown', + 'greater', + 'grunt', + 'guarded', + 'guest', + 'guide', + 'gulp', + 'gumball', + 'guru', + 'gusts', + 'gutter', + 'guys', + 'gymnast', + 'gypsy', + 'gyrate', + 'habitat', + 'hacksaw', + 'haggled', + 'hairy', + 'hamburger', + 'happens', + 'hashing', + 'hatchet', + 'haunted', + 'having', + 'hawk', + 'haystack', + 'hazard', + 'hectare', + 'hedgehog', + 'heels', + 'hefty', + 'height', + 'hemlock', + 'hence', + 'heron', + 'hesitate', + 'hexagon', + 'hickory', + 'hiding', + 'highway', + 'hijack', + 'hiker', + 'hills', + 'himself', + 'hinder', + 'hippo', + 'hire', + 'history', + 'hitched', + 'hive', + 'hoax', + 'hobby', + 'hockey', + 'hoisting', + 'hold', + 'honked', + 'hookup', + 'hope', + 'hornet', + 'hospital', + 'hotel', + 'hounded', + 'hover', + 'howls', + 'hubcaps', + 'huddle', + 'huge', + 'hull', + 'humid', + 'hunter', + 'hurried', + 'husband', + 'huts', + 'hybrid', + 'hydrogen', + 'hyper', + 'iceberg', + 'icing', + 'icon', + 'identity', + 'idiom', + 'idled', + 'idols', + 'igloo', + 'ignore', + 'iguana', + 'illness', + 'imagine', + 'imbalance', + 'imitate', + 'impel', + 'inactive', + 'inbound', + 'incur', + 'industrial', + 'inexact', + 'inflamed', + 'ingested', + 'initiate', + 'injury', + 'inkling', + 'inline', + 'inmate', + 'innocent', + 'inorganic', + 'input', + 'inquest', + 'inroads', + 'insult', + 'intended', + 'inundate', + 'invoke', + 'inwardly', + 'ionic', + 'irate', + 'iris', + 'irony', + 'irritate', + 'island', + 'isolated', + 'issued', + 'italics', + 'itches', + 'items', + 'itinerary', + 'itself', + 'ivory', + 'jabbed', + 'jackets', + 'jaded', + 'jagged', + 'jailed', + 'jamming', + 'january', + 'jargon', + 'jaunt', + 'javelin', + 'jaws', + 'jazz', + 'jeans', + 'jeers', + 'jellyfish', + 'jeopardy', + 'jerseys', + 'jester', + 'jetting', + 'jewels', + 'jigsaw', + 'jingle', + 'jittery', + 'jive', + 'jobs', + 'jockey', + 'jogger', + 'joining', + 'joking', + 'jolted', + 'jostle', + 'journal', + 'joyous', + 'jubilee', + 'judge', + 'juggled', + 'juicy', + 'jukebox', + 'july', + 'jump', + 'junk', + 'jury', + 'justice', + 'juvenile', + 'kangaroo', + 'karate', + 'keep', + 'kennel', + 'kept', + 'kernels', + 'kettle', + 'keyboard', + 'kickoff', + 'kidneys', + 'king', + 'kiosk', + 'kisses', + 'kitchens', + 'kiwi', + 'knapsack', + 'knee', + 'knife', + 'knowledge', + 'knuckle', + 'koala', + 'laboratory', + 'ladder', + 'lagoon', + 'lair', + 'lakes', + 'lamb', + 'language', + 'laptop', + 'large', + 'last', + 'later', + 'launching', + 'lava', + 'lawsuit', + 'layout', + 'lazy', + 'lectures', + 'ledge', + 'leech', + 'left', + 'legion', + 'leisure', + 'lemon', + 'lending', + 'leopard', + 'lesson', + 'lettuce', + 'lexicon', + 'liar', + 'library', + 'licks', + 'lids', + 'lied', + 'lifestyle', + 'light', + 'likewise', + 'lilac', + 'limits', + 'linen', + 'lion', + 'lipstick', + 'liquid', + 'listen', + 'lively', + 'loaded', + 'lobster', + 'locker', + 'lodge', + 'lofty', + 'logic', + 'loincloth', + 'long', + 'looking', + 'lopped', + 'lordship', + 'losing', + 'lottery', + 'loudly', + 'love', + 'lower', + 'loyal', + 'lucky', + 'luggage', + 'lukewarm', + 'lullaby', + 'lumber', + 'lunar', + 'lurk', + 'lush', + 'luxury', + 'lymph', + 'lynx', + 'lyrics', + 'macro', + 'madness', + 'magically', + 'mailed', + 'major', + 'makeup', + 'malady', + 'mammal', + 'maps', + 'masterful', + 'match', + 'maul', + 'maverick', + 'maximum', + 'mayor', + 'maze', + 'meant', + 'mechanic', + 'medicate', + 'meeting', + 'megabyte', + 'melting', + 'memoir', + 'menu', + 'merger', + 'mesh', + 'metro', + 'mews', + 'mice', + 'midst', + 'mighty', + 'mime', + 'mirror', + 'misery', + 'mittens', + 'mixture', + 'moat', + 'mobile', + 'mocked', + 'mohawk', + 'moisture', + 'molten', + 'moment', + 'money', + 'moon', + 'mops', + 'morsel', + 'mostly', + 'motherly', + 'mouth', + 'movement', + 'mowing', + 'much', + 'muddy', + 'muffin', + 'mugged', + 'mullet', + 'mumble', + 'mundane', + 'muppet', + 'mural', + 'musical', + 'muzzle', + 'myriad', + 'mystery', + 'myth', + 'nabbing', + 'nagged', + 'nail', + 'names', + 'nanny', + 'napkin', + 'narrate', + 'nasty', + 'natural', + 'nautical', + 'navy', + 'nearby', + 'necklace', + 'needed', + 'negative', + 'neither', + 'neon', + 'nephew', + 'nerves', + 'nestle', + 'network', + 'neutral', + 'never', + 'newt', + 'nexus', + 'nibs', + 'niche', + 'niece', + 'nifty', + 'nightly', + 'nimbly', + 'nineteen', + 'nirvana', + 'nitrogen', + 'nobody', + 'nocturnal', + 'nodes', + 'noises', + 'nomad', + 'noodles', + 'northern', + 'nostril', + 'noted', + 'nouns', + 'novelty', + 'nowhere', + 'nozzle', + 'nuance', + 'nucleus', + 'nudged', + 'nugget', + 'nuisance', + 'null', + 'number', + 'nuns', + 'nurse', + 'nutshell', + 'nylon', + 'oaks', + 'oars', + 'oasis', + 'oatmeal', + 'obedient', + 'object', + 'obliged', + 'obnoxious', + 'observant', + 'obtains', + 'obvious', + 'occur', + 'ocean', + 'october', + 'odds', + 'odometer', + 'offend', + 'often', + 'oilfield', + 'ointment', + 'okay', + 'older', + 'olive', + 'olympics', + 'omega', + 'omission', + 'omnibus', + 'onboard', + 'oncoming', + 'oneself', + 'ongoing', + 'onion', + 'online', + 'onslaught', + 'onto', + 'onward', + 'oozed', + 'opacity', + 'opened', + 'opposite', + 'optical', + 'opus', + 'orange', + 'orbit', + 'orchid', + 'orders', + 'organs', + 'origin', + 'ornament', + 'orphans', + 'oscar', + 'ostrich', + 'otherwise', + 'otter', + 'ouch', + 'ought', + 'ounce', + 'ourselves', + 'oust', + 'outbreak', + 'oval', + 'oven', + 'owed', + 'owls', + 'owner', + 'oxidant', + 'oxygen', + 'oyster', + 'ozone', + 'pact', + 'paddles', + 'pager', + 'pairing', + 'palace', + 'pamphlet', + 'pancakes', + 'paper', + 'paradise', + 'pastry', + 'patio', + 'pause', + 'pavements', + 'pawnshop', + 'payment', + 'peaches', + 'pebbles', + 'peculiar', + 'pedantic', + 'peeled', + 'pegs', + 'pelican', + 'pencil', + 'people', + 'pepper', + 'perfect', + 'pests', + 'petals', + 'phase', + 'pheasants', + 'phone', + 'phrases', + 'physics', + 'piano', + 'picked', + 'pierce', + 'pigment', + 'piloted', + 'pimple', + 'pinched', + 'pioneer', + 'pipeline', + 'pirate', + 'pistons', + 'pitched', + 'pivot', + 'pixels', + 'pizza', + 'playful', + 'pledge', + 'pliers', + 'plotting', + 'plus', + 'plywood', + 'poaching', + 'pockets', + 'podcast', + 'poetry', + 'point', + 'poker', + 'polar', + 'ponies', + 'pool', + 'popular', + 'portents', + 'possible', + 'potato', + 'pouch', + 'poverty', + 'powder', + 'pram', + 'present', + 'pride', + 'problems', + 'pruned', + 'prying', + 'psychic', + 'public', + 'puck', + 'puddle', + 'puffin', + 'pulp', + 'pumpkins', + 'punch', + 'puppy', + 'purged', + 'push', + 'putty', + 'puzzled', + 'pylons', + 'pyramid', + 'python', + 'queen', + 'quick', + 'quote', + 'rabbits', + 'racetrack', + 'radar', + 'rafts', + 'rage', + 'railway', + 'raking', + 'rally', + 'ramped', + 'randomly', + 'rapid', + 'rarest', + 'rash', + 'rated', + 'ravine', + 'rays', + 'razor', + 'react', + 'rebel', + 'recipe', + 'reduce', + 'reef', + 'refer', + 'regular', + 'reheat', + 'reinvest', + 'rejoices', + 'rekindle', + 'relic', + 'remedy', + 'renting', + 'reorder', + 'repent', + 'request', + 'reruns', + 'rest', + 'return', + 'reunion', + 'revamp', + 'rewind', + 'rhino', + 'rhythm', + 'ribbon', + 'richly', + 'ridges', + 'rift', + 'rigid', + 'rims', + 'ringing', + 'riots', + 'ripped', + 'rising', + 'ritual', + 'river', + 'roared', + 'robot', + 'rockets', + 'rodent', + 'rogue', + 'roles', + 'romance', + 'roomy', + 'roped', + 'roster', + 'rotate', + 'rounded', + 'rover', + 'rowboat', + 'royal', + 'ruby', + 'rudely', + 'ruffled', + 'rugged', + 'ruined', + 'ruling', + 'rumble', + 'runway', + 'rural', + 'rustled', + 'ruthless', + 'sabotage', + 'sack', + 'sadness', + 'safety', + 'saga', + 'sailor', + 'sake', + 'salads', + 'sample', + 'sanity', + 'sapling', + 'sarcasm', + 'sash', + 'satin', + 'saucepan', + 'saved', + 'sawmill', + 'saxophone', + 'sayings', + 'scamper', + 'scenic', + 'school', + 'science', + 'scoop', + 'scrub', + 'scuba', + 'seasons', + 'second', + 'sedan', + 'seeded', + 'segments', + 'seismic', + 'selfish', + 'semifinal', + 'sensible', + 'september', + 'sequence', + 'serving', + 'session', + 'setup', + 'seventh', + 'sewage', + 'shackles', + 'shelter', + 'shipped', + 'shocking', + 'shrugged', + 'shuffled', + 'shyness', + 'siblings', + 'sickness', + 'sidekick', + 'sieve', + 'sifting', + 'sighting', + 'silk', + 'simplest', + 'sincerely', + 'sipped', + 'siren', + 'situated', + 'sixteen', + 'sizes', + 'skater', + 'skew', + 'skirting', + 'skulls', + 'skydive', + 'slackens', + 'sleepless', + 'slid', + 'slower', + 'slug', + 'smash', + 'smelting', + 'smidgen', + 'smog', + 'smuggled', + 'snake', + 'sneeze', + 'sniff', + 'snout', + 'snug', + 'soapy', + 'sober', + 'soccer', + 'soda', + 'software', + 'soggy', + 'soil', + 'solved', + 'somewhere', + 'sonic', + 'soothe', + 'soprano', + 'sorry', + 'southern', + 'sovereign', + 'sowed', + 'soya', + 'space', + 'speedy', + 'sphere', + 'spiders', + 'splendid', + 'spout', + 'sprig', + 'spud', + 'spying', + 'square', + 'stacking', + 'stellar', + 'stick', + 'stockpile', + 'strained', + 'stunning', + 'stylishly', + 'subtly', + 'succeed', + 'suddenly', + 'suede', + 'suffice', + 'sugar', + 'suitcase', + 'sulking', + 'summon', + 'sunken', + 'superior', + 'surfer', + 'sushi', + 'suture', + 'swagger', + 'swept', + 'swiftly', + 'sword', + 'swung', + 'syllabus', + 'symptoms', + 'syndrome', + 'syringe', + 'system', + 'taboo', + 'tacit', + 'tadpoles', + 'tagged', + 'tail', + 'taken', + 'talent', + 'tamper', + 'tanks', + 'tapestry', + 'tarnished', + 'tasked', + 'tattoo', + 'taunts', + 'tavern', + 'tawny', + 'taxi', + 'teardrop', + 'technical', + 'tedious', + 'teeming', + 'tell', + 'template', + 'tender', + 'tepid', + 'tequila', + 'terminal', + 'testing', + 'tether', + 'textbook', + 'thaw', + 'theatrics', + 'thirsty', + 'thorn', + 'threaten', + 'thumbs', + 'thwart', + 'ticket', + 'tidy', + 'tiers', + 'tiger', + 'tilt', + 'timber', + 'tinted', + 'tipsy', + 'tirade', + 'tissue', + 'titans', + 'toaster', + 'tobacco', + 'today', + 'toenail', + 'toffee', + 'together', + 'toilet', + 'token', + 'tolerant', + 'tomorrow', + 'tonic', + 'toolbox', + 'topic', + 'torch', + 'tossed', + 'total', + 'touchy', + 'towel', + 'toxic', + 'toyed', + 'trash', + 'trendy', + 'tribal', + 'trolling', + 'truth', + 'trying', + 'tsunami', + 'tubes', + 'tucks', + 'tudor', + 'tuesday', + 'tufts', + 'tugs', + 'tuition', + 'tulips', + 'tumbling', + 'tunnel', + 'turnip', + 'tusks', + 'tutor', + 'tuxedo', + 'twang', + 'tweezers', + 'twice', + 'twofold', + 'tycoon', + 'typist', + 'tyrant', + 'ugly', + 'ulcers', + 'ultimate', + 'umbrella', + 'umpire', + 'unafraid', + 'unbending', + 'uncle', + 'under', + 'uneven', + 'unfit', + 'ungainly', + 'unhappy', + 'union', + 'unjustly', + 'unknown', + 'unlikely', + 'unmask', + 'unnoticed', + 'unopened', + 'unplugs', + 'unquoted', + 'unrest', + 'unsafe', + 'until', + 'unusual', + 'unveil', + 'unwind', + 'unzip', + 'upbeat', + 'upcoming', + 'update', + 'upgrade', + 'uphill', + 'upkeep', + 'upload', + 'upon', + 'upper', + 'upright', + 'upstairs', + 'uptight', + 'upwards', + 'urban', + 'urchins', + 'urgent', + 'usage', + 'useful', + 'usher', + 'using', + 'usual', + 'utensils', + 'utility', + 'utmost', + 'utopia', + 'uttered', + 'vacation', + 'vague', + 'vain', + 'value', + 'vampire', + 'vane', + 'vapidly', + 'vary', + 'vastness', + 'vats', + 'vaults', + 'vector', + 'veered', + 'vegan', + 'vehicle', + 'vein', + 'velvet', + 'venomous', + 'verification', + 'vessel', + 'veteran', + 'vexed', + 'vials', + 'vibrate', + 'victim', + 'video', + 'viewpoint', + 'vigilant', + 'viking', + 'village', + 'vinegar', + 'violin', + 'vipers', + 'virtual', + 'visited', + 'vitals', + 'vivid', + 'vixen', + 'vocal', + 'vogue', + 'voice', + 'volcano', + 'vortex', + 'voted', + 'voucher', + 'vowels', + 'voyage', + 'vulture', + 'wade', + 'waffle', + 'wagtail', + 'waist', + 'waking', + 'wallets', + 'wanted', + 'warped', + 'washing', + 'water', + 'waveform', + 'waxing', + 'wayside', + 'weavers', + 'website', + 'wedge', + 'weekday', + 'weird', + 'welders', + 'went', + 'wept', + 'were', + 'western', + 'wetsuit', + 'whale', + 'when', + 'whipped', + 'whole', + 'wickets', + 'width', + 'wield', + 'wife', + 'wiggle', + 'wildly', + 'winter', + 'wipeout', + 'wiring', + 'wise', + 'withdrawn', + 'wives', + 'wizard', + 'wobbly', + 'woes', + 'woken', + 'wolf', + 'womanly', + 'wonders', + 'woozy', + 'worry', + 'wounded', + 'woven', + 'wrap', + 'wrist', + 'wrong', + 'yacht', + 'yahoo', + 'yanks', + 'yard', + 'yawning', + 'yearbook', + 'yellow', + 'yesterday', + 'yeti', + 'yields', + 'yodel', + 'yoga', + 'younger', + 'yoyo', + 'zapped', + 'zeal', + 'zebra', + 'zero', + 'zesty', + 'zigzags', + 'zinger', + 'zippers', + 'zodiac', + 'zombie', + 'zones', + 'zoom' +]; + +export default { + monero_words_english, + monero_words_english_prefix_len +}; diff --git a/crypto/blockchains/xmr/ext/MoneroMnemonic.js b/crypto/blockchains/xmr/ext/MoneroMnemonic.js deleted file mode 100644 index 862fb8973..000000000 --- a/crypto/blockchains/xmr/ext/MoneroMnemonic.js +++ /dev/null @@ -1,68 +0,0 @@ -/** - * @author Ksu - * @version 0.11 - * - * based on - * https://github.com/Coinomi/bip39-coinomi - */ -import MoneroDict from './MoneroDict'; -import BlocksoftUtils from '../../../common/BlocksoftUtils'; - -const crc32 = require('buffer-crc32'); - -export default { - secret_spend_key_to_words(secretSpendKeyBufferOrHex, walletHash) { - const buff = - typeof secretSpendKeyBufferOrHex === 'string' - ? Buffer.from(secretSpendKeyBufferOrHex, 'hex') - : secretSpendKeyBufferOrHex; - var seed = []; - var forChecksum = ''; - for (var i = 0; i < 32; i += 4) { - var w0 = 0; - for (var j = 3; j >= 0; j--) { - w0 = w0 * 256 + buff[i + j]; - if (typeof buff[i + j] === 'undefined') { - throw new Error( - 'XMR word for wallet ' + - walletHash + - ' need to be rechecked as buff is too low' - ); - } - } - - var w1 = w0 % MoneroDict.monero_words_english.length; - var w2 = - (((w0 / MoneroDict.monero_words_english.length) | 0) + w1) % - MoneroDict.monero_words_english.length; - var w3 = - (((((w0 / MoneroDict.monero_words_english.length) | 0) / - MoneroDict.monero_words_english.length) | - 0) + - w2) % - MoneroDict.monero_words_english.length; - - seed.push(MoneroDict.monero_words_english[w1]); - seed.push(MoneroDict.monero_words_english[w2]); - seed.push(MoneroDict.monero_words_english[w3]); - forChecksum += MoneroDict.monero_words_english[w1].substring( - 0, - MoneroDict.monero_words_english_prefix_len - ); - forChecksum += MoneroDict.monero_words_english[w2].substring( - 0, - MoneroDict.monero_words_english_prefix_len - ); - forChecksum += MoneroDict.monero_words_english[w3].substring( - 0, - MoneroDict.monero_words_english_prefix_len - ); - } - const crc32Res = crc32(forChecksum); - const crc32Decimal = BlocksoftUtils.hexToDecimal( - '0x' + crc32Res.toString('hex') - ); - seed.push(seed[crc32Decimal % 24]); - return seed.join(' '); - } -}; diff --git a/crypto/blockchains/xmr/ext/MoneroMnemonic.ts b/crypto/blockchains/xmr/ext/MoneroMnemonic.ts new file mode 100644 index 000000000..9de2141d8 --- /dev/null +++ b/crypto/blockchains/xmr/ext/MoneroMnemonic.ts @@ -0,0 +1,80 @@ +import MoneroDict from './MoneroDict'; +import BlocksoftUtils from '../../../common/BlocksoftUtils'; + +const crc32 = require('buffer-crc32'); + +type SecretSpendKey = string | Buffer; + +interface DictionaryWord { + word: string; + prefix: string; +} + +interface MoneroDictionary { + monero_words_english: DictionaryWord[]; + monero_words_english_prefix_len: number; +} + +const secret_spend_key_to_words = ( + secretSpendKeyBufferOrHex: SecretSpendKey, + walletHash: string +): string => { + const buff = + typeof secretSpendKeyBufferOrHex === 'string' + ? Buffer.from(secretSpendKeyBufferOrHex, 'hex') + : secretSpendKeyBufferOrHex; + + const seed: string[] = []; + let forChecksum = ''; + + for (let i = 0; i < 32; i += 4) { + let w0 = 0; + for (let j = 3; j >= 0; j--) { + w0 = w0 * 256 + buff[i + j]; + if (typeof buff[i + j] === 'undefined') { + throw new Error( + `XMR word for wallet ${walletHash} needs to be rechecked as buff is too low` + ); + } + } + + const moneroDict: MoneroDictionary = MoneroDict as MoneroDictionary; + const moneroWords: DictionaryWord[] = moneroDict.monero_words_english; + + const w1 = w0 % moneroWords.length; + const w2 = (Math.floor(w0 / moneroWords.length) + w1) % moneroWords.length; + const w3 = + (Math.floor(Math.floor(w0 / moneroWords.length) / moneroWords.length) + + w2) % + moneroWords.length; + + seed.push(moneroWords[w1].word); + seed.push(moneroWords[w2].word); + seed.push(moneroWords[w3].word); + + forChecksum += moneroWords[w1].prefix.substring( + 0, + moneroDict.monero_words_english_prefix_len + ); + forChecksum += moneroWords[w2].prefix.substring( + 0, + moneroDict.monero_words_english_prefix_len + ); + forChecksum += moneroWords[w3].prefix.substring( + 0, + moneroDict.monero_words_english_prefix_len + ); + } + + const crc32Res = crc32(forChecksum); + const crc32Decimal = BlocksoftUtils.hexToDecimal( + '0x' + crc32Res.toString('hex') + ); + seed.push(seed[crc32Decimal % 24]); + + return seed.join(' '); +}; + +export default { + secret_spend_key_to_words +}; diff --git a/crypto/blockchains/xmr/ext/MoneroUtils.js b/crypto/blockchains/xmr/ext/MoneroUtils.js deleted file mode 100644 index 717e7ed6b..000000000 --- a/crypto/blockchains/xmr/ext/MoneroUtils.js +++ /dev/null @@ -1,168 +0,0 @@ -/** - * @author Ksu - * @version 0.11 - * - * based on - * https://github.com/mymonero/mymonero-core-js/blob/c04651731e238d23baec9682753acd967679a36e/src/index.cpp - */ -import { soliditySha3 } from 'web3-utils' - -const elliptic = require('elliptic') -const Ed25519 = elliptic.eddsa('ed25519') - - -/** Monero base58 is not like Bitcoin base58, bytes are converted in 8-byte blocks. - * https://docs.rs/base58-monero/0.2.0/base58_monero/ - */ -function base58_encode(data) { - var ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' - var BYTES_TO_LENGTHS = [0, 2, 3, 5, 6, 7, 9, 10, 11] - var ALPHABET_MAP = {} - var BASE = ALPHABET.length - - // pre-compute lookup table - for (var z = 0; z < ALPHABET.length; z++) { - var x = ALPHABET.charAt(z) - if (ALPHABET_MAP[x] !== undefined) throw new TypeError(x + ' is ambiguous') - ALPHABET_MAP[x] = z - } - - function encode_partial(data, pos) { - var len = 8 - if (pos + len > data.length) len = data.length - pos - var digits = [0] - for (var i = 0; i < len; ++i) { - for (var j = 0, carry = data[pos + i]; j < digits.length; ++j) { - carry += digits[j] << 8 - digits[j] = carry % BASE - carry = (carry / BASE) | 0 - } - - while (carry > 0) { - digits.push(carry % BASE) - carry = (carry / BASE) | 0 - } - } - - var res = '' - // deal with leading zeros - for (var k = digits.length; k < BYTES_TO_LENGTHS[len]; ++k) res += ALPHABET[0] - // convert digits to a string - for (var q = digits.length - 1; q >= 0; --q) res += ALPHABET[digits[q]] - return res - } - - var res = '' - for (var i = 0; i < data.length; i += 8) { - res += encode_partial(data, i) - } - return res -} - -export default { - /** - Takes a 32-byte integer and outputs the integer modulo q - Input: - s[0]+256*s[1]+...+256^63*s[63] = s - - Output: - s[0]+256*s[1]+...+256^31*s[31] = s mod l - where l = 2^252 + 27742317777372353535851937790883648493. - - Actually should be valid ed25519 scalar so make with umode and checked - */ - sc_reduce32(dataBufferOrHex) { - const buff = typeof dataBufferOrHex === 'string' ? Buffer.from(dataBufferOrHex,'hex') : dataBufferOrHex - const hex = elliptic.utils.intFromLE(buff).umod(Ed25519.curve.n).toString('hex') - return this.reverse(hex) - }, - - /** - Scalar add - is taking two big numbers, which are stored as binary in byte (char) arrays and returning the result of adding them together in another byte array, mod the order of the base point. - */ - sc_add(dataBufferOrHex, dataBufferOrHex2) { - const buff = typeof dataBufferOrHex === 'string' ? Buffer.from(dataBufferOrHex,'hex') : dataBufferOrHex - const buff2 = typeof dataBufferOrHex2 === 'string' ? Buffer.from(dataBufferOrHex2,'hex') : dataBufferOrHex2 - const hex = elliptic.utils.intFromLE(buff).add(elliptic.utils.intFromLE(buff2)).toString('hex') - return this.reverse(hex) - }, - - reverse(hex) { - let result = '' - const ic = hex.length - for (let i = ic - 1; i >= 0; i = i - 2) { - const tmp = i > 0 ? (hex[i - 1] + hex[i]) : ('0' + hex[i]) - result += tmp - } - return result - }, - - /** - Inputs data (for example, a point P on ed25519) and outputs Hs (P), which is - the Keccak1600 hash of the data. The function then converts the hashed data to a - 32-byte integer modulo q - - void hash_to_scalar(const void *data, size_t length, ec_scalar &res) { - cn_fast_hash(data, length, reinterpret_cast(res)); - sc_reduce32(&res); - } - */ - hash_to_scalar(dataBufferOrHex) { - return this.sc_reduce32(this.cn_fast_hash(dataBufferOrHex)) - }, - - /** - cn_fast_hash = keccak1600 = sha3 !!!! - parameters b = 1600 and c = 512 - Function keccak1600 is defined as a special case of function keccak with parameter mdlen equal to the size in bits of the type state_t. This type is an array of 25 uint64_t so the total size is 25*64=1600 - */ - cn_fast_hash(dataBufferOrHex) { - const str = typeof dataBufferOrHex === 'string' ? dataBufferOrHex : dataBufferOrHex.toString('hex') - const hash = soliditySha3('0x' + str) - return hash.substr(2) - }, - - /** - https://monerodocs.org/cryptography/asymmetric/public-key/ - https://github.com/indutny/elliptic - Actually this function multiplies base G by its input - - Inputs a secret key, checks it for some uniformity conditions, and outputs the corresponding public key, which is essentially just 8 times the base point times the - point. - */ - secret_key_to_public_key(secretSpendKeyBufferOrHex) { - const buff = typeof secretSpendKeyBufferOrHex === 'string' ? Buffer.from(secretSpendKeyBufferOrHex,'hex') : secretSpendKeyBufferOrHex - const publicSpendKey = Ed25519.curve.g.mul(Ed25519.decodeInt(buff.toString('hex'))) - return Buffer.from(Ed25519.encodePoint(publicSpendKey)) - /* - !!! NOPE - constkey = Ed25519.keyFromSecret(secretSpendKeyBuffer) - const publicSpendKey2 = key.getPublic() - console.log(' publicSpendKey2 ', publicSpendKey2, publicSpendKey2.toString('hex'), Buffer.from(publicSpendKey2), Buffer.from(publicSpendKey2).toString('hex')) - */ - }, - - /** - checked - */ - pub_keys_to_address(index, publicSpendKeyBufferOrHex, publicViewKeyHexBufferOrHex) { - const str = typeof publicSpendKeyBufferOrHex === 'string' ? publicSpendKeyBufferOrHex : publicSpendKeyBufferOrHex.toString('hex') - const str2 = typeof publicViewKeyHexBufferOrHex === 'string' ? publicViewKeyHexBufferOrHex : publicViewKeyHexBufferOrHex.toString('hex') - const prefix = (index > 0) ? '2A' : '12' - - let hex = prefix + str + str2 //this.normString(str) + this.normString(str2) - const hash = this.cn_fast_hash(hex) - hex += hash.substring(0, 8) - - hex = Buffer.from(hex, 'hex') - - return base58_encode(hex) - }, - - normString(str) { - while (str.length < 64) { - str += '0' - } - return str - } -} diff --git a/crypto/blockchains/xmr/ext/MoneroUtils.ts b/crypto/blockchains/xmr/ext/MoneroUtils.ts new file mode 100644 index 000000000..aaadcdb94 --- /dev/null +++ b/crypto/blockchains/xmr/ext/MoneroUtils.ts @@ -0,0 +1,216 @@ +/** + * @author Ksu + * @version 0.11 + * + * based on + * https://github.com/mymonero/mymonero-core-js/blob/c04651731e238d23baec9682753acd967679a36e/src/index.cpp + */ +import { soliditySha3 } from 'web3-utils'; +// @ts-ignore +import * as elliptic from 'elliptic'; +const Ed25519 = elliptic.eddsa('ed25519'); + +/** Monero base58 is not like Bitcoin base58, bytes are converted in 8-byte blocks. + * https://docs.rs/base58-monero/0.2.0/base58_monero/ + */ +// eslint-disable-next-line camelcase +function base58_encode(data: Buffer): string { + const ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'; + const BYTES_TO_LENGTHS = [0, 2, 3, 5, 6, 7, 9, 10, 11]; + const ALPHABET_MAP: { [char: string]: number } = {}; + const BASE = ALPHABET.length; + + // pre-compute lookup table + for (let z = 0; z < ALPHABET.length; z++) { + const x = ALPHABET.charAt(z); + if (ALPHABET_MAP[x] !== undefined) throw new TypeError(x + ' is ambiguous'); + ALPHABET_MAP[x] = z; + } + + // tslint:disable-next-line:no-shadowed-variable + function encode_partial(data: Buffer, pos: number): string { + let len = 8; + if (pos + len > data.length) { + len = data.length - pos; + } + const digits = [0]; + for (let i = 0; i < len; ++i) { + let carry = data[pos + i]; // Declare 'carry' here + for (let j = 0; j < digits.length; ++j) { + carry += digits[j] * BASE; + digits[j] = carry % BASE; + carry = Math.floor(carry / BASE); + } + + while (carry > 0) { + digits.push(carry % BASE); + carry = Math.floor(carry / BASE); + } + } + + res = ''; + // deal with leading zeros + for (let k = digits.length; k < BYTES_TO_LENGTHS[len]; ++k) { + res += ALPHABET[0]; + } + // convert digits to a string + for (let q = digits.length - 1; q >= 0; --q) { + res += ALPHABET[digits[q]]; + } + return res; + } + + let res = ''; + for (let i = 0; i < data.length; i += 8) { + res += encode_partial(data, i); + } + return res; +} + +export default { + /** + * Takes a 32-byte integer and outputs the integer modulo q + * Input: + * s[0]+256*s[1]+...+256^63*s[63] = s + * + * Output: + * s[0]+256*s[1]+...+256^31*s[31] = s mod l + * where l = 2^252 + 27742317777372353535851937790883648493. + * + * Actually should be valid ed25519 scalar so make with umode and checked + */ + // eslint-disable-next-line camelcase + sc_reduce32(dataBufferOrHex: Buffer | string): string { + const buff = + typeof dataBufferOrHex === 'string' + ? Buffer.from(dataBufferOrHex, 'hex') + : dataBufferOrHex; + const hex = elliptic.utils + .intFromLE(buff) + .umod(Ed25519.curve.n) + .toString('hex'); + return this.reverse(hex); + }, + + /** + * Scalar add - is taking two big numbers, which are stored as binary in byte (char) arrays and returning the result of adding them together in another byte array, mod the order of the base point. + */ + // eslint-disable-next-line camelcase + sc_add( + dataBufferOrHex: Buffer | string, + dataBufferOrHex2: Buffer | string + ): string { + const buff = + typeof dataBufferOrHex === 'string' + ? Buffer.from(dataBufferOrHex, 'hex') + : dataBufferOrHex; + const buff2 = + typeof dataBufferOrHex2 === 'string' + ? Buffer.from(dataBufferOrHex2, 'hex') + : dataBufferOrHex2; + const hex = elliptic.utils + .intFromLE(buff) + .add(elliptic.utils.intFromLE(buff2)) + .toString('hex'); + return this.reverse(hex); + }, + + reverse(hex: string): string { + let result = ''; + const ic = hex.length; + for (let i = ic - 1; i >= 0; i = i - 2) { + const tmp = i > 0 ? hex[i - 1] + hex[i] : '0' + hex[i]; + result += tmp; + } + return result; + }, + + /** + * Inputs data (for example, a point P on ed25519) and outputs Hs (P), which is + * the Keccak1600 hash of the data. The function then converts the hashed data to a + * 32-byte integer modulo q + * + * void hash_to_scalar(const void *data, size_t length, ec_scalar &res) { + * cn_fast_hash(data, length, reinterpret_cast(res)); + * sc_reduce32(&res); + * } + */ + // eslint-disable-next-line camelcase + hash_to_scalar(dataBufferOrHex: Buffer | string): string { + return this.sc_reduce32(this.cn_fast_hash(dataBufferOrHex)); + }, + + /** + * cn_fast_hash = keccak1600 = sha3 !!!! + * parameters b = 1600 and c = 512 + * Function keccak1600 is defined as a special case of function keccak with parameter mdlen equal to the size in bits of the type state_t. This type is an array of 25 uint64_t so the total size is 25*64=1600 + */ + // eslint-disable-next-line camelcase + cn_fast_hash(dataBufferOrHex: Buffer | string): string { + const str = + typeof dataBufferOrHex === 'string' + ? dataBufferOrHex + : dataBufferOrHex.toString('hex'); + const hash = soliditySha3('0x' + str); + return hash?.substr(2) || ''; + }, + + /** + * https://monerodocs.org/cryptography/asymmetric/public-key/ + * https://github.com/indutny/elliptic + * Actually this function multiplies base G by its input + * + * Inputs a secret key, checks it for some uniformity conditions, and outputs the corresponding public key, which is essentially just 8 times the base point times the + * point. + */ + // eslint-disable-next-line camelcase + secret_key_to_public_key(secretSpendKeyBufferOrHex: Buffer | string): Buffer { + const buff = + typeof secretSpendKeyBufferOrHex === 'string' + ? Buffer.from(secretSpendKeyBufferOrHex, 'hex') + : secretSpendKeyBufferOrHex; + const publicSpendKey = Ed25519.curve.g.mul( + Ed25519.decodeInt(buff.toString('hex')) + ); + return Buffer.from(Ed25519.encodePoint(publicSpendKey)); + /** + * !!! NOPE + * constkey = Ed25519.keyFromSecret(secretSpendKeyBuffer) + * const publicSpendKey2 = key.getPublic() + * console.log(' publicSpendKey2 ', publicSpendKey2, publicSpendKey2.toString('hex'), Buffer.from(publicSpendKey2), Buffer.from(publicSpendKey2).toString('hex')) + */ + }, + + /* checked */ + // eslint-disable-next-line camelcase + pub_keys_to_address( + index: number, + publicSpendKeyBufferOrHex: Buffer | string, + publicViewKeyHexBufferOrHex: Buffer | string + ): string { + const str = + typeof publicSpendKeyBufferOrHex === 'string' + ? publicSpendKeyBufferOrHex + : publicSpendKeyBufferOrHex.toString('hex'); + const str2 = + typeof publicViewKeyHexBufferOrHex === 'string' + ? publicViewKeyHexBufferOrHex + : publicViewKeyHexBufferOrHex.toString('hex'); + const prefix = index > 0 ? '2A' : '12'; + + let hex = prefix + str + str2; // this.normString(str) + this.normString(str2) + const hash = this.cn_fast_hash(hex); + hex += hash.substring(0, 8); + + hex = Buffer.from(hex, 'hex').toString('hex'); + + return base58_encode(Buffer.from(hex, 'hex')); + }, + + normString(str: string): string { + while (str.length < 64) { + str += '0'; + } + return str; + } +}; diff --git a/crypto/blockchains/xmr/ext/MoneroUtilsParser.oldAndroid.js b/crypto/blockchains/xmr/ext/MoneroUtilsParser.oldAndroid.js deleted file mode 100644 index a21bc1f78..000000000 --- a/crypto/blockchains/xmr/ext/MoneroUtilsParser.oldAndroid.js +++ /dev/null @@ -1,28 +0,0 @@ -/** - * @author Ksu - * @version 0.11 - * for old androids - */ - -export default { - - checkDestination(value) { - return false - }, - - async getCore() { - return false - }, - - async getCoreWasm() { - return false - }, - - async parseAddressInfo(address, data, privViewKey, pubSpendKey, privSpendKey) { - return false - }, - - async parseAddressTransactions(address, data, privViewKey, pubSpendKey, privSpendKey) { - return false - } -} diff --git a/crypto/blockchains/xmr/ext/MoneroUtilsParser.oldAndroid.ts b/crypto/blockchains/xmr/ext/MoneroUtilsParser.oldAndroid.ts new file mode 100644 index 000000000..d993cef0a --- /dev/null +++ b/crypto/blockchains/xmr/ext/MoneroUtilsParser.oldAndroid.ts @@ -0,0 +1,39 @@ +/** + * @author Ksu + * @version 0.11 + * for old androids + */ + +export default { + checkDestination(value) { + return false; + }, + + async getCore() { + return false; + }, + + async getCoreWasm() { + return false; + }, + + async parseAddressInfo( + address, + data, + privViewKey, + pubSpendKey, + privSpendKey + ) { + return false; + }, + + async parseAddressTransactions( + address, + data, + privViewKey, + pubSpendKey, + privSpendKey + ) { + return false; + } +}; diff --git a/crypto/blockchains/xmr/ext/MoneroUtilsParser.js b/crypto/blockchains/xmr/ext/MoneroUtilsParser.ts similarity index 74% rename from crypto/blockchains/xmr/ext/MoneroUtilsParser.js rename to crypto/blockchains/xmr/ext/MoneroUtilsParser.ts index ed891effb..74b6ffcff 100644 --- a/crypto/blockchains/xmr/ext/MoneroUtilsParser.js +++ b/crypto/blockchains/xmr/ext/MoneroUtilsParser.ts @@ -3,33 +3,57 @@ * @version 0.2 * https://github.com/mymonero/mymonero-utils/blob/8ea7ff51f931d3c5e27e2ffd2eb8945cdec8e050/packages/mymonero-response-parser-utils/index.js */ -import config from '../../../../app/config/config'; +// @ts-ignore import * as payment from '@mymonero/mymonero-paymentid-utils'; // import * as parser from '@mymonero/mymonero-response-parser-utils/ResponseParser' import * as parser from './vendor/ResponseParser'; import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; +import config from '@constants/config'; const MyMoneroCoreBridgeRN = require('react-native-mymonero-core/src/index'); -const MY_MONERO = { core: false }; +interface MyMoneroCore { + generate_key_image: ( + txPublicKey: string, + privateViewKey: string, + publicSpendKey: string, + privateSpendKey: string, + outputIndex: string + ) => Promise; + createTransaction: (options: { + privateViewKey: string; + publicSpendKey: string; + privateSpendKey: string; + randomOutsCb: () => void; + destinations: { to_address: string; send_amount: number }[]; + shouldSweep: boolean; + address: string; + priority: number; + nettype: string; + unspentOuts: any; + }) => Promise; +} +const MY_MONERO: { core: MyMoneroCore | false } = { core: false }; export default { - checkDestination(value) { + checkDestination(value: string): boolean { return payment.IsValidPaymentIDOrNoPaymentID(value); }, - async getCore() { + async getCore(): Promise { if (MY_MONERO.core) { return MY_MONERO.core; } - MY_MONERO.core = MyMoneroCoreBridgeRN; - MY_MONERO.core.generate_key_image = async ( - txPublicKey, - privateViewKey, - publicSpendKey, - privateSpendKey, - outputIndex - ) => { + + const core: MyMoneroCore = MyMoneroCoreBridgeRN; + // eslint-disable-next-line camelcase + core.generate_key_image = async ( + txPublicKey: string, + privateViewKey: string, + publicSpendKey: string, + privateSpendKey: string, + outputIndex: string + ): Promise => { if ( !txPublicKey || !privateViewKey || @@ -64,14 +88,16 @@ export default { privateViewKey, publicSpendKey, privateSpendKey, - outputIndex + '' + Number(outputIndex + '') ); if (typeof res !== 'undefined' && res) { if (typeof res === 'string') { try { const newRes = JSON.parse(res); res = newRes; - } catch (e) {} + } catch (e) { + console.log(e); + } } if (typeof res.retVal !== 'undefined') { return res.retVal; @@ -85,7 +111,18 @@ export default { throw new Error('MoneroUtilsParser.generate_key_image ' + e.message); } }; - MY_MONERO.core.createTransaction = async (options) => { + MY_MONERO.core.createTransaction = async (options: { + privateViewKey: string; + publicSpendKey: string; + privateSpendKey: string; + randomOutsCb: () => void; + destinations: { to_address: string; send_amount: number }[]; + shouldSweep: boolean; + address: string; + priority: number; + nettype: string; + unspentOuts: any; + }): Promise => { if (options.privateViewKey.length !== 64) { throw Error('Invalid privateViewKey length'); } @@ -101,7 +138,7 @@ export default { if (!Array.isArray(options.destinations)) { throw Error('Invalid destinations'); } - options.destinations.forEach(function (destination) { + options.destinations.forEach((destination) => { if ( !destination.hasOwnProperty('to_address') || !destination.hasOwnProperty('send_amount') @@ -149,7 +186,10 @@ export default { return false; } - const _getRandomOuts = async (numberOfOuts, randomOutsCb) => { + const _getRandomOuts = async ( + numberOfOuts: number, + randomOutsCb: Function + ) => { const randomOuts = await randomOutsCb(numberOfOuts); if ( typeof randomOuts.amount_outs === 'undefined' || @@ -178,8 +218,7 @@ export default { if (rawTx.err_msg) { throw Error(rawTx.err_msg); } - // parse variables ruturned as strings - rawTx.mixin = parseInt(rawTx.mixin); + rawTx.mixin = parseInt(rawTx.mixin, 20); rawTx.isXMRAddressIntegrated = rawTx.isXMRAddressIntegrated === 'true'; return rawTx; @@ -188,15 +227,15 @@ export default { }, async parseAddressInfo( - address, - data, - privViewKey, - pubSpendKey, - privSpendKey - ) { + address: string, + data: any, + privViewKey: string, + pubSpendKey: string, + privSpendKey: string + ): Promise { try { await this.getCore(); - let resData = false; + let resData: any = false; await parser.Parsed_AddressInfo__keyImageManaged( data, address, @@ -204,7 +243,7 @@ export default { pubSpendKey, privSpendKey, MY_MONERO.core, - (e, returnValuesByKey) => { + (e: any, returnValuesByKey: any) => { if (e) { console.log('MoneroUtilsParser.parseAddressInfo error2', e); } @@ -220,15 +259,15 @@ export default { }, async parseAddressTransactions( - address, - data, - privViewKey, - pubSpendKey, - privSpendKey - ) { + address: string, + data: any, + privViewKey: string, + pubSpendKey: string, + privSpendKey: string + ): Promise { try { await this.getCore(); - let resData = false; + let resData: any = false; await parser.Parsed_AddressTransactions__keyImageManaged( data, address, @@ -236,7 +275,7 @@ export default { pubSpendKey, privSpendKey, MY_MONERO.core, - (e, returnValuesByKey) => { + (e: any, returnValuesByKey: any) => { if (e) { console.log('MoneroUtilsParser.parseAddressTransactions error', e); } diff --git a/crypto/blockchains/xmr/ext/vendor/biginteger.js b/crypto/blockchains/xmr/ext/vendor/biginteger.js index f7d46d729..1f3aa9252 100644 --- a/crypto/blockchains/xmr/ext/vendor/biginteger.js +++ b/crypto/blockchains/xmr/ext/vendor/biginteger.js @@ -21,9 +21,9 @@ This file has been modified by Paul Shapiro to bring in the function lowVal whic */ -(function(exports) { - "use strict"; - /* +(function (exports) { + 'use strict'; + /* Class: BigInteger An arbitrarily-large integer. @@ -44,9 +44,9 @@ This file has been modified by Paul Shapiro to bring in the function lowVal whic > var a = BigInteger.toJSValue("0b101010"); // Not completely useless... */ - var CONSTRUCT = {}; // Unique token to call "private" version of constructor + var CONSTRUCT = {}; // Unique token to call "private" version of constructor - /* + /* Constructor: BigInteger() Convert a value to a . @@ -81,61 +81,61 @@ This file has been modified by Paul Shapiro to bring in the function lowVal whic , */ - function BigInteger(n, s, token) { - if (token !== CONSTRUCT) { - if (n instanceof BigInteger) { - return n; - } else if (typeof n === "undefined") { - return ZERO; - } - return BigInteger.parse(n); - } - - n = n || []; // Provide the nullary constructor for subclasses. - while (n.length && !n[n.length - 1]) { - --n.length; - } - this._d = n; - this._s = n.length ? s || 1 : 0; - } - - BigInteger._construct = function(n, s) { - return new BigInteger(n, s, CONSTRUCT); - }; - - // Base-10 speedup hacks in parse, toString, exp10 and log functions - // require base to be a power of 10. 10^7 is the largest such power - // that won't cause a precision loss when digits are multiplied. - var BigInteger_base = 10000000; - var BigInteger_base_log10 = 7; - - BigInteger.base = BigInteger_base; - BigInteger.base_log10 = BigInteger_base_log10; - - var ZERO = new BigInteger([], 0, CONSTRUCT); - // Constant: ZERO - // 0. - BigInteger.ZERO = ZERO; - - var ONE = new BigInteger([1], 1, CONSTRUCT); - // Constant: ONE - // 1. - BigInteger.ONE = ONE; - - var M_ONE = new BigInteger(ONE._d, -1, CONSTRUCT); - // Constant: M_ONE - // -1. - BigInteger.M_ONE = M_ONE; - - // Constant: _0 - // Shortcut for . - BigInteger._0 = ZERO; - - // Constant: _1 - // Shortcut for . - BigInteger._1 = ONE; - - /* + function BigInteger(n, s, token) { + if (token !== CONSTRUCT) { + if (n instanceof BigInteger) { + return n; + } else if (typeof n === 'undefined') { + return ZERO; + } + return BigInteger.parse(n); + } + + n = n || []; // Provide the nullary constructor for subclasses. + while (n.length && !n[n.length - 1]) { + --n.length; + } + this._d = n; + this._s = n.length ? s || 1 : 0; + } + + BigInteger._construct = function (n, s) { + return new BigInteger(n, s, CONSTRUCT); + }; + + // Base-10 speedup hacks in parse, toString, exp10 and log functions + // require base to be a power of 10. 10^7 is the largest such power + // that won't cause a precision loss when digits are multiplied. + var BigInteger_base = 10000000; + var BigInteger_base_log10 = 7; + + BigInteger.base = BigInteger_base; + BigInteger.base_log10 = BigInteger_base_log10; + + var ZERO = new BigInteger([], 0, CONSTRUCT); + // Constant: ZERO + // 0. + BigInteger.ZERO = ZERO; + + var ONE = new BigInteger([1], 1, CONSTRUCT); + // Constant: ONE + // 1. + BigInteger.ONE = ONE; + + var M_ONE = new BigInteger(ONE._d, -1, CONSTRUCT); + // Constant: M_ONE + // -1. + BigInteger.M_ONE = M_ONE; + + // Constant: _0 + // Shortcut for . + BigInteger._0 = ZERO; + + // Constant: _1 + // Shortcut for . + BigInteger._1 = ONE; + + /* Constant: small Array of from 0 to 36. @@ -146,51 +146,51 @@ This file has been modified by Paul Shapiro to bring in the function lowVal whic , , <_0>, <_1> */ - BigInteger.small = [ - ZERO, - ONE, - /* Assuming BigInteger_base > 36 */ - new BigInteger([2], 1, CONSTRUCT), - new BigInteger([3], 1, CONSTRUCT), - new BigInteger([4], 1, CONSTRUCT), - new BigInteger([5], 1, CONSTRUCT), - new BigInteger([6], 1, CONSTRUCT), - new BigInteger([7], 1, CONSTRUCT), - new BigInteger([8], 1, CONSTRUCT), - new BigInteger([9], 1, CONSTRUCT), - new BigInteger([10], 1, CONSTRUCT), - new BigInteger([11], 1, CONSTRUCT), - new BigInteger([12], 1, CONSTRUCT), - new BigInteger([13], 1, CONSTRUCT), - new BigInteger([14], 1, CONSTRUCT), - new BigInteger([15], 1, CONSTRUCT), - new BigInteger([16], 1, CONSTRUCT), - new BigInteger([17], 1, CONSTRUCT), - new BigInteger([18], 1, CONSTRUCT), - new BigInteger([19], 1, CONSTRUCT), - new BigInteger([20], 1, CONSTRUCT), - new BigInteger([21], 1, CONSTRUCT), - new BigInteger([22], 1, CONSTRUCT), - new BigInteger([23], 1, CONSTRUCT), - new BigInteger([24], 1, CONSTRUCT), - new BigInteger([25], 1, CONSTRUCT), - new BigInteger([26], 1, CONSTRUCT), - new BigInteger([27], 1, CONSTRUCT), - new BigInteger([28], 1, CONSTRUCT), - new BigInteger([29], 1, CONSTRUCT), - new BigInteger([30], 1, CONSTRUCT), - new BigInteger([31], 1, CONSTRUCT), - new BigInteger([32], 1, CONSTRUCT), - new BigInteger([33], 1, CONSTRUCT), - new BigInteger([34], 1, CONSTRUCT), - new BigInteger([35], 1, CONSTRUCT), - new BigInteger([36], 1, CONSTRUCT), - ]; - - // Used for parsing/radix conversion - BigInteger.digits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ".split(""); - - /* + BigInteger.small = [ + ZERO, + ONE, + /* Assuming BigInteger_base > 36 */ + new BigInteger([2], 1, CONSTRUCT), + new BigInteger([3], 1, CONSTRUCT), + new BigInteger([4], 1, CONSTRUCT), + new BigInteger([5], 1, CONSTRUCT), + new BigInteger([6], 1, CONSTRUCT), + new BigInteger([7], 1, CONSTRUCT), + new BigInteger([8], 1, CONSTRUCT), + new BigInteger([9], 1, CONSTRUCT), + new BigInteger([10], 1, CONSTRUCT), + new BigInteger([11], 1, CONSTRUCT), + new BigInteger([12], 1, CONSTRUCT), + new BigInteger([13], 1, CONSTRUCT), + new BigInteger([14], 1, CONSTRUCT), + new BigInteger([15], 1, CONSTRUCT), + new BigInteger([16], 1, CONSTRUCT), + new BigInteger([17], 1, CONSTRUCT), + new BigInteger([18], 1, CONSTRUCT), + new BigInteger([19], 1, CONSTRUCT), + new BigInteger([20], 1, CONSTRUCT), + new BigInteger([21], 1, CONSTRUCT), + new BigInteger([22], 1, CONSTRUCT), + new BigInteger([23], 1, CONSTRUCT), + new BigInteger([24], 1, CONSTRUCT), + new BigInteger([25], 1, CONSTRUCT), + new BigInteger([26], 1, CONSTRUCT), + new BigInteger([27], 1, CONSTRUCT), + new BigInteger([28], 1, CONSTRUCT), + new BigInteger([29], 1, CONSTRUCT), + new BigInteger([30], 1, CONSTRUCT), + new BigInteger([31], 1, CONSTRUCT), + new BigInteger([32], 1, CONSTRUCT), + new BigInteger([33], 1, CONSTRUCT), + new BigInteger([34], 1, CONSTRUCT), + new BigInteger([35], 1, CONSTRUCT), + new BigInteger([36], 1, CONSTRUCT) + ]; + + // Used for parsing/radix conversion + BigInteger.digits = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split(''); + + /* Method: toString Convert a to a string. @@ -205,87 +205,86 @@ This file has been modified by Paul Shapiro to bring in the function lowVal whic The string representation of the . */ - BigInteger.prototype.toString = function(base) { - base = +base || 10; - if (base < 2 || base > 36) { - throw new Error("illegal radix " + base + "."); - } - if (this._s === 0) { - return "0"; - } - if (base === 10) { - var str = this._s < 0 ? "-" : ""; - str += this._d[this._d.length - 1].toString(); - for (var i = this._d.length - 2; i >= 0; i--) { - var group = this._d[i].toString(); - while (group.length < BigInteger_base_log10) - group = "0" + group; - str += group; - } - return str; - } else { - var numerals = BigInteger.digits; - base = BigInteger.small[base]; - var sign = this._s; - - var n = this.abs(); - var digits = []; - var digit; - - while (n._s !== 0) { - var divmod = n.divRem(base); - n = divmod[0]; - digit = divmod[1]; - // TODO: This could be changed to unshift instead of reversing at the end. - // Benchmark both to compare speeds. - digits.push(numerals[digit.valueOf()]); - } - return (sign < 0 ? "-" : "") + digits.reverse().join(""); - } - }; - - // Verify strings for parsing - BigInteger.radixRegex = [ - /^$/, - /^$/, - /^[01]*$/, - /^[012]*$/, - /^[0-3]*$/, - /^[0-4]*$/, - /^[0-5]*$/, - /^[0-6]*$/, - /^[0-7]*$/, - /^[0-8]*$/, - /^[0-9]*$/, - /^[0-9aA]*$/, - /^[0-9abAB]*$/, - /^[0-9abcABC]*$/, - /^[0-9a-dA-D]*$/, - /^[0-9a-eA-E]*$/, - /^[0-9a-fA-F]*$/, - /^[0-9a-gA-G]*$/, - /^[0-9a-hA-H]*$/, - /^[0-9a-iA-I]*$/, - /^[0-9a-jA-J]*$/, - /^[0-9a-kA-K]*$/, - /^[0-9a-lA-L]*$/, - /^[0-9a-mA-M]*$/, - /^[0-9a-nA-N]*$/, - /^[0-9a-oA-O]*$/, - /^[0-9a-pA-P]*$/, - /^[0-9a-qA-Q]*$/, - /^[0-9a-rA-R]*$/, - /^[0-9a-sA-S]*$/, - /^[0-9a-tA-T]*$/, - /^[0-9a-uA-U]*$/, - /^[0-9a-vA-V]*$/, - /^[0-9a-wA-W]*$/, - /^[0-9a-xA-X]*$/, - /^[0-9a-yA-Y]*$/, - /^[0-9a-zA-Z]*$/, - ]; - - /* + BigInteger.prototype.toString = function (base) { + base = +base || 10; + if (base < 2 || base > 36) { + throw new Error('illegal radix ' + base + '.'); + } + if (this._s === 0) { + return '0'; + } + if (base === 10) { + var str = this._s < 0 ? '-' : ''; + str += this._d[this._d.length - 1].toString(); + for (var i = this._d.length - 2; i >= 0; i--) { + var group = this._d[i].toString(); + while (group.length < BigInteger_base_log10) group = '0' + group; + str += group; + } + return str; + } else { + var numerals = BigInteger.digits; + base = BigInteger.small[base]; + var sign = this._s; + + var n = this.abs(); + var digits = []; + var digit; + + while (n._s !== 0) { + var divmod = n.divRem(base); + n = divmod[0]; + digit = divmod[1]; + // TODO: This could be changed to unshift instead of reversing at the end. + // Benchmark both to compare speeds. + digits.push(numerals[digit.valueOf()]); + } + return (sign < 0 ? '-' : '') + digits.reverse().join(''); + } + }; + + // Verify strings for parsing + BigInteger.radixRegex = [ + /^$/, + /^$/, + /^[01]*$/, + /^[012]*$/, + /^[0-3]*$/, + /^[0-4]*$/, + /^[0-5]*$/, + /^[0-6]*$/, + /^[0-7]*$/, + /^[0-8]*$/, + /^[0-9]*$/, + /^[0-9aA]*$/, + /^[0-9abAB]*$/, + /^[0-9abcABC]*$/, + /^[0-9a-dA-D]*$/, + /^[0-9a-eA-E]*$/, + /^[0-9a-fA-F]*$/, + /^[0-9a-gA-G]*$/, + /^[0-9a-hA-H]*$/, + /^[0-9a-iA-I]*$/, + /^[0-9a-jA-J]*$/, + /^[0-9a-kA-K]*$/, + /^[0-9a-lA-L]*$/, + /^[0-9a-mA-M]*$/, + /^[0-9a-nA-N]*$/, + /^[0-9a-oA-O]*$/, + /^[0-9a-pA-P]*$/, + /^[0-9a-qA-Q]*$/, + /^[0-9a-rA-R]*$/, + /^[0-9a-sA-S]*$/, + /^[0-9a-tA-T]*$/, + /^[0-9a-uA-U]*$/, + /^[0-9a-vA-V]*$/, + /^[0-9a-wA-W]*$/, + /^[0-9a-xA-X]*$/, + /^[0-9a-yA-Y]*$/, + /^[0-9a-zA-Z]*$/ + ]; + + /* Function: parse Parse a string into a . @@ -317,130 +316,127 @@ This file has been modified by Paul Shapiro to bring in the function lowVal whic a instance. */ - BigInteger.parse = function(s, base) { - // Expands a number in exponential form to decimal form. - // expandExponential("-13.441*10^5") === "1344100"; - // expandExponential("1.12300e-1") === "0.112300"; - // expandExponential(1000000000000000000000000000000) === "1000000000000000000000000000000"; - function expandExponential(str) { - str = str.replace(/\s*[*xX]\s*10\s*(\^|\*\*)\s*/, "e"); - - return str.replace( - /^([+\-])?(\d+)\.?(\d*)[eE]([+\-]?\d+)$/, - function(x, s, n, f, c) { - c = +c; - var l = c < 0; - var i = n.length + c; - x = (l ? n : f).length; - c = (c = Math.abs(c)) >= x ? c - x + l : 0; - var z = new Array(c + 1).join("0"); - var r = n + f; - return ( - (s || "") + - (l ? (r = z + r) : (r += z)).substr( - 0, - (i += l ? z.length : 0), - ) + - (i < r.length ? "." + r.substr(i) : "") - ); - }, - ); - } - - s = s.toString(); - if (typeof base === "undefined" || +base === 10) { - s = expandExponential(s); - } - - var prefixRE; - if (typeof base === "undefined") { - prefixRE = "0[xcb]"; - } else if (base == 16) { - prefixRE = "0x"; - } else if (base == 8) { - prefixRE = "0c"; - } else if (base == 2) { - prefixRE = "0b"; - } else { - prefixRE = ""; - } - var parts = new RegExp( - "^([+\\-]?)(" + prefixRE + ")?([0-9a-z]*)(?:\\.\\d*)?$", - "i", - ).exec(s); - if (parts) { - var sign = parts[1] || "+"; - var baseSection = parts[2] || ""; - var digits = parts[3] || ""; - - if (typeof base === "undefined") { - // Guess base - if (baseSection === "0x" || baseSection === "0X") { - // Hex - base = 16; - } else if (baseSection === "0c" || baseSection === "0C") { - // Octal - base = 8; - } else if (baseSection === "0b" || baseSection === "0B") { - // Binary - base = 2; - } else { - base = 10; - } - } else if (base < 2 || base > 36) { - throw new Error("Illegal radix " + base + "."); - } - - base = +base; - - // Check for digits outside the range - if (!BigInteger.radixRegex[base].test(digits)) { - throw new Error("Bad digit for radix " + base); - } - - // Strip leading zeros, and convert to array - digits = digits.replace(/^0+/, "").split(""); - if (digits.length === 0) { - return ZERO; - } - - // Get the sign (we know it's not zero) - sign = sign === "-" ? -1 : 1; - - // Optimize 10 - if (base == 10) { - var d = []; - while (digits.length >= BigInteger_base_log10) { - d.push( - parseInt( - digits - .splice( - digits.length - BigInteger.base_log10, - BigInteger.base_log10, - ) - .join(""), - 10, - ), - ); - } - d.push(parseInt(digits.join(""), 10)); - return new BigInteger(d, sign, CONSTRUCT); - } - - // Do the conversion - var d = ZERO; - base = BigInteger.small[base]; - var small = BigInteger.small; - for (var i = 0; i < digits.length; i++) { - d = d.multiply(base).add(small[parseInt(digits[i], 36)]); - } - return new BigInteger(d._d, sign, CONSTRUCT); - } else { - throw new Error("Invalid BigInteger format: " + s); - } - }; - - /* + BigInteger.parse = function (s, base) { + // Expands a number in exponential form to decimal form. + // expandExponential("-13.441*10^5") === "1344100"; + // expandExponential("1.12300e-1") === "0.112300"; + // expandExponential(1000000000000000000000000000000) === "1000000000000000000000000000000"; + function expandExponential(str) { + str = str.replace(/\s*[*xX]\s*10\s*(\^|\*\*)\s*/, 'e'); + + return str.replace( + /^([+\-])?(\d+)\.?(\d*)[eE]([+\-]?\d+)$/, + (x, s, n, f, c) => { + c = +c; + var l = c < 0; + var i = n.length + c; + x = (l ? n : f).length; + c = (c = Math.abs(c)) >= x ? c - x + l : 0; + var z = new Array(c + 1).join('0'); + var r = n + f; + return ( + (s || '') + + (l ? (r = z + r) : (r += z)).substr(0, (i += l ? z.length : 0)) + + (i < r.length ? '.' + r.substr(i) : '') + ); + } + ); + } + + s = s.toString(); + if (typeof base === 'undefined' || +base === 10) { + s = expandExponential(s); + } + + var prefixRE; + if (typeof base === 'undefined') { + prefixRE = '0[xcb]'; + } else if (base == 16) { + prefixRE = '0x'; + } else if (base == 8) { + prefixRE = '0c'; + } else if (base == 2) { + prefixRE = '0b'; + } else { + prefixRE = ''; + } + var parts = new RegExp( + '^([+\\-]?)(' + prefixRE + ')?([0-9a-z]*)(?:\\.\\d*)?$', + 'i' + ).exec(s); + if (parts) { + var sign = parts[1] || '+'; + var baseSection = parts[2] || ''; + var digits = parts[3] || ''; + + if (typeof base === 'undefined') { + // Guess base + if (baseSection === '0x' || baseSection === '0X') { + // Hex + base = 16; + } else if (baseSection === '0c' || baseSection === '0C') { + // Octal + base = 8; + } else if (baseSection === '0b' || baseSection === '0B') { + // Binary + base = 2; + } else { + base = 10; + } + } else if (base < 2 || base > 36) { + throw new Error('Illegal radix ' + base + '.'); + } + + base = +base; + + // Check for digits outside the range + if (!BigInteger.radixRegex[base].test(digits)) { + throw new Error('Bad digit for radix ' + base); + } + + // Strip leading zeros, and convert to array + digits = digits.replace(/^0+/, '').split(''); + if (digits.length === 0) { + return ZERO; + } + + // Get the sign (we know it's not zero) + sign = sign === '-' ? -1 : 1; + + // Optimize 10 + if (base == 10) { + var d = []; + while (digits.length >= BigInteger_base_log10) { + d.push( + parseInt( + digits + .splice( + digits.length - BigInteger.base_log10, + BigInteger.base_log10 + ) + .join(''), + 10 + ) + ); + } + d.push(parseInt(digits.join(''), 10)); + return new BigInteger(d, sign, CONSTRUCT); + } + + // Do the conversion + var d = ZERO; + base = BigInteger.small[base]; + var small = BigInteger.small; + for (var i = 0; i < digits.length; i++) { + d = d.multiply(base).add(small[parseInt(digits[i], 36)]); + } + return new BigInteger(d._d, sign, CONSTRUCT); + } else { + throw new Error('Invalid BigInteger format: ' + s); + } + }; + + /* Function: add Add two . @@ -456,55 +452,55 @@ This file has been modified by Paul Shapiro to bring in the function lowVal whic , , , */ - BigInteger.prototype.add = function(n) { - if (this._s === 0) { - return BigInteger(n); - } - - n = BigInteger(n); - if (n._s === 0) { - return this; - } - if (this._s !== n._s) { - n = n.negate(); - return this.subtract(n); - } - - var a = this._d; - var b = n._d; - var al = a.length; - var bl = b.length; - var sum = new Array(Math.max(al, bl) + 1); - var size = Math.min(al, bl); - var carry = 0; - var digit; - - for (var i = 0; i < size; i++) { - digit = a[i] + b[i] + carry; - sum[i] = digit % BigInteger_base; - carry = (digit / BigInteger_base) | 0; - } - if (bl > al) { - a = b; - al = bl; - } - for (i = size; carry && i < al; i++) { - digit = a[i] + carry; - sum[i] = digit % BigInteger_base; - carry = (digit / BigInteger_base) | 0; - } - if (carry) { - sum[i] = carry; - } - - for (; i < al; i++) { - sum[i] = a[i]; - } - - return new BigInteger(sum, this._s, CONSTRUCT); - }; - - /* + BigInteger.prototype.add = function (n) { + if (this._s === 0) { + return BigInteger(n); + } + + n = BigInteger(n); + if (n._s === 0) { + return this; + } + if (this._s !== n._s) { + n = n.negate(); + return this.subtract(n); + } + + var a = this._d; + var b = n._d; + var al = a.length; + var bl = b.length; + var sum = new Array(Math.max(al, bl) + 1); + var size = Math.min(al, bl); + var carry = 0; + var digit; + + for (var i = 0; i < size; i++) { + digit = a[i] + b[i] + carry; + sum[i] = digit % BigInteger_base; + carry = (digit / BigInteger_base) | 0; + } + if (bl > al) { + a = b; + al = bl; + } + for (i = size; carry && i < al; i++) { + digit = a[i] + carry; + sum[i] = digit % BigInteger_base; + carry = (digit / BigInteger_base) | 0; + } + if (carry) { + sum[i] = carry; + } + + for (; i < al; i++) { + sum[i] = a[i]; + } + + return new BigInteger(sum, this._s, CONSTRUCT); + }; + + /* Function: negate Get the additive inverse of a . @@ -516,11 +512,11 @@ This file has been modified by Paul Shapiro to bring in the function lowVal whic */ - BigInteger.prototype.negate = function() { - return new BigInteger(this._d, -this._s | 0, CONSTRUCT); - }; + BigInteger.prototype.negate = function () { + return new BigInteger(this._d, -this._s | 0, CONSTRUCT); + }; - /* + /* Function: abs Get the absolute value of a . @@ -532,11 +528,11 @@ This file has been modified by Paul Shapiro to bring in the function lowVal whic */ - BigInteger.prototype.abs = function() { - return this._s < 0 ? this.negate() : this; - }; + BigInteger.prototype.abs = function () { + return this._s < 0 ? this.negate() : this; + }; - /* + /* Function: subtract Subtract two . @@ -552,115 +548,115 @@ This file has been modified by Paul Shapiro to bring in the function lowVal whic , , , */ - BigInteger.prototype.subtract = function(n) { - if (this._s === 0) { - return BigInteger(n).negate(); - } - - n = BigInteger(n); - if (n._s === 0) { - return this; - } - if (this._s !== n._s) { - n = n.negate(); - return this.add(n); - } - - var m = this; - // negative - negative => -|a| - -|b| => -|a| + |b| => |b| - |a| - if (this._s < 0) { - m = new BigInteger(n._d, 1, CONSTRUCT); - n = new BigInteger(this._d, 1, CONSTRUCT); - } - - // Both are positive => a - b - var sign = m.compareAbs(n); - if (sign === 0) { - return ZERO; - } else if (sign < 0) { - // swap m and n - var t = n; - n = m; - m = t; - } - - // a > b - var a = m._d; - var b = n._d; - var al = a.length; - var bl = b.length; - var diff = new Array(al); // al >= bl since a > b - var borrow = 0; - var i; - var digit; - - for (i = 0; i < bl; i++) { - digit = a[i] - borrow - b[i]; - if (digit < 0) { - digit += BigInteger_base; - borrow = 1; - } else { - borrow = 0; - } - diff[i] = digit; - } - for (i = bl; i < al; i++) { - digit = a[i] - borrow; - if (digit < 0) { - digit += BigInteger_base; - } else { - diff[i++] = digit; - break; - } - diff[i] = digit; - } - for (; i < al; i++) { - diff[i] = a[i]; - } - - return new BigInteger(diff, sign, CONSTRUCT); - }; - - (function() { - function addOne(n, sign) { - var a = n._d; - var sum = a.slice(); - var carry = true; - var i = 0; - - while (true) { - var digit = (a[i] || 0) + 1; - sum[i] = digit % BigInteger_base; - if (digit <= BigInteger_base - 1) { - break; - } - ++i; - } - - return new BigInteger(sum, sign, CONSTRUCT); - } - - function subtractOne(n, sign) { - var a = n._d; - var sum = a.slice(); - var borrow = true; - var i = 0; - - while (true) { - var digit = (a[i] || 0) - 1; - if (digit < 0) { - sum[i] = digit + BigInteger_base; - } else { - sum[i] = digit; - break; - } - ++i; - } - - return new BigInteger(sum, sign, CONSTRUCT); - } - - /* + BigInteger.prototype.subtract = function (n) { + if (this._s === 0) { + return BigInteger(n).negate(); + } + + n = BigInteger(n); + if (n._s === 0) { + return this; + } + if (this._s !== n._s) { + n = n.negate(); + return this.add(n); + } + + var m = this; + // negative - negative => -|a| - -|b| => -|a| + |b| => |b| - |a| + if (this._s < 0) { + m = new BigInteger(n._d, 1, CONSTRUCT); + n = new BigInteger(this._d, 1, CONSTRUCT); + } + + // Both are positive => a - b + var sign = m.compareAbs(n); + if (sign === 0) { + return ZERO; + } else if (sign < 0) { + // swap m and n + var t = n; + n = m; + m = t; + } + + // a > b + var a = m._d; + var b = n._d; + var al = a.length; + var bl = b.length; + var diff = new Array(al); // al >= bl since a > b + var borrow = 0; + var i; + var digit; + + for (i = 0; i < bl; i++) { + digit = a[i] - borrow - b[i]; + if (digit < 0) { + digit += BigInteger_base; + borrow = 1; + } else { + borrow = 0; + } + diff[i] = digit; + } + for (i = bl; i < al; i++) { + digit = a[i] - borrow; + if (digit < 0) { + digit += BigInteger_base; + } else { + diff[i++] = digit; + break; + } + diff[i] = digit; + } + for (; i < al; i++) { + diff[i] = a[i]; + } + + return new BigInteger(diff, sign, CONSTRUCT); + }; + + (function () { + function addOne(n, sign) { + var a = n._d; + var sum = a.slice(); + var carry = true; + var i = 0; + + while (true) { + var digit = (a[i] || 0) + 1; + sum[i] = digit % BigInteger_base; + if (digit <= BigInteger_base - 1) { + break; + } + ++i; + } + + return new BigInteger(sum, sign, CONSTRUCT); + } + + function subtractOne(n, sign) { + var a = n._d; + var sum = a.slice(); + var borrow = true; + var i = 0; + + while (true) { + var digit = (a[i] || 0) - 1; + if (digit < 0) { + sum[i] = digit + BigInteger_base; + } else { + sum[i] = digit; + break; + } + ++i; + } + + return new BigInteger(sum, sign, CONSTRUCT); + } + + /* Function: next Get the next (add one). @@ -672,19 +668,19 @@ This file has been modified by Paul Shapiro to bring in the function lowVal whic , */ - BigInteger.prototype.next = function() { - switch (this._s) { - case 0: - return ONE; - case -1: - return subtractOne(this, -1); - // case 1: - default: - return addOne(this, 1); - } - }; - - /* + BigInteger.prototype.next = function () { + switch (this._s) { + case 0: + return ONE; + case -1: + return subtractOne(this, -1); + // case 1: + default: + return addOne(this, 1); + } + }; + + /* Function: prev Get the previous (subtract one). @@ -696,20 +692,20 @@ This file has been modified by Paul Shapiro to bring in the function lowVal whic , */ - BigInteger.prototype.prev = function() { - switch (this._s) { - case 0: - return M_ONE; - case -1: - return addOne(this, -1); - // case 1: - default: - return subtractOne(this, 1); - } - }; - })(); - - /* + BigInteger.prototype.prev = function () { + switch (this._s) { + case 0: + return M_ONE; + case -1: + return addOne(this, -1); + // case 1: + default: + return subtractOne(this, 1); + } + }; + })(); + + /* Function: compareAbs Compare the absolute value of two . @@ -727,45 +723,45 @@ This file has been modified by Paul Shapiro to bring in the function lowVal whic , */ - BigInteger.prototype.compareAbs = function(n) { - if (this === n) { - return 0; - } - - if (!(n instanceof BigInteger)) { - if (!isFinite(n)) { - return isNaN(n) ? n : -1; - } - n = BigInteger(n); - } - - if (this._s === 0) { - return n._s !== 0 ? -1 : 0; - } - if (n._s === 0) { - return 1; - } - - var l = this._d.length; - var nl = n._d.length; - if (l < nl) { - return -1; - } else if (l > nl) { - return 1; - } - - var a = this._d; - var b = n._d; - for (var i = l - 1; i >= 0; i--) { - if (a[i] !== b[i]) { - return a[i] < b[i] ? -1 : 1; - } - } - - return 0; - }; - - /* + BigInteger.prototype.compareAbs = function (n) { + if (this === n) { + return 0; + } + + if (!(n instanceof BigInteger)) { + if (!isFinite(n)) { + return isNaN(n) ? n : -1; + } + n = BigInteger(n); + } + + if (this._s === 0) { + return n._s !== 0 ? -1 : 0; + } + if (n._s === 0) { + return 1; + } + + var l = this._d.length; + var nl = n._d.length; + if (l < nl) { + return -1; + } else if (l > nl) { + return 1; + } + + var a = this._d; + var b = n._d; + for (var i = l - 1; i >= 0; i--) { + if (a[i] !== b[i]) { + return a[i] < b[i] ? -1 : 1; + } + } + + return 0; + }; + + /* Function: compare Compare two . @@ -781,27 +777,27 @@ This file has been modified by Paul Shapiro to bring in the function lowVal whic , , , */ - BigInteger.prototype.compare = function(n) { - if (this === n) { - return 0; - } - - n = BigInteger(n); - - if (this._s === 0) { - return -n._s; - } - - if (this._s === n._s) { - // both positive or both negative - var cmp = this.compareAbs(n); - return cmp * this._s; - } else { - return this._s; - } - }; - - /* + BigInteger.prototype.compare = function (n) { + if (this === n) { + return 0; + } + + n = BigInteger(n); + + if (this._s === 0) { + return -n._s; + } + + if (this._s === n._s) { + // both positive or both negative + var cmp = this.compareAbs(n); + return cmp * this._s; + } else { + return this._s; + } + }; + + /* Function: isUnit Return true iff *this* is either 1 or -1. @@ -814,15 +810,15 @@ This file has been modified by Paul Shapiro to bring in the function lowVal whic , , , , , , */ - BigInteger.prototype.isUnit = function() { - return ( - this === ONE || - this === M_ONE || - (this._d.length === 1 && this._d[0] === 1) - ); - }; - - /* + BigInteger.prototype.isUnit = function () { + return ( + this === ONE || + this === M_ONE || + (this._d.length === 1 && this._d[0] === 1) + ); + }; + + /* Function: multiply Multiply two . @@ -839,121 +835,118 @@ This file has been modified by Paul Shapiro to bring in the function lowVal whic , , , */ - BigInteger.prototype.multiply = function(n) { - // TODO: Consider adding Karatsuba multiplication for large numbers - if (this._s === 0) { - return ZERO; - } - - n = BigInteger(n); - if (n._s === 0) { - return ZERO; - } - if (this.isUnit()) { - if (this._s < 0) { - return n.negate(); - } - return n; - } - if (n.isUnit()) { - if (n._s < 0) { - return this.negate(); - } - return this; - } - if (this === n) { - return this.square(); - } - - var r = this._d.length >= n._d.length; - var a = (r ? this : n)._d; // a will be longer than b - var b = (r ? n : this)._d; - var al = a.length; - var bl = b.length; - - var pl = al + bl; - var partial = new Array(pl); - var i; - for (i = 0; i < pl; i++) { - partial[i] = 0; - } - - for (i = 0; i < bl; i++) { - var carry = 0; - var bi = b[i]; - var jlimit = al + i; - var digit; - for (var j = i; j < jlimit; j++) { - digit = partial[j] + bi * a[j - i] + carry; - carry = (digit / BigInteger_base) | 0; - partial[j] = digit % BigInteger_base | 0; - } - if (carry) { - digit = partial[j] + carry; - carry = (digit / BigInteger_base) | 0; - partial[j] = digit % BigInteger_base; - } - } - return new BigInteger(partial, this._s * n._s, CONSTRUCT); - }; - - // Multiply a BigInteger by a single-digit native number - // Assumes that this and n are >= 0 - // This is not really intended to be used outside the library itself - BigInteger.prototype.multiplySingleDigit = function(n) { - if (n === 0 || this._s === 0) { - return ZERO; - } - if (n === 1) { - return this; - } - - var digit; - if (this._d.length === 1) { - digit = this._d[0] * n; - if (digit >= BigInteger_base) { - return new BigInteger( - [ - digit % BigInteger_base | 0, - (digit / BigInteger_base) | 0, - ], - 1, - CONSTRUCT, - ); - } - return new BigInteger([digit], 1, CONSTRUCT); - } - - if (n === 2) { - return this.add(this); - } - if (this.isUnit()) { - return new BigInteger([n], 1, CONSTRUCT); - } - - var a = this._d; - var al = a.length; - - var pl = al + 1; - var partial = new Array(pl); - for (var i = 0; i < pl; i++) { - partial[i] = 0; - } - - var carry = 0; - for (var j = 0; j < al; j++) { - digit = n * a[j] + carry; - carry = (digit / BigInteger_base) | 0; - partial[j] = digit % BigInteger_base | 0; - } - if (carry) { - partial[j] = carry; - } - - return new BigInteger(partial, 1, CONSTRUCT); - }; - - /* + BigInteger.prototype.multiply = function (n) { + // TODO: Consider adding Karatsuba multiplication for large numbers + if (this._s === 0) { + return ZERO; + } + + n = BigInteger(n); + if (n._s === 0) { + return ZERO; + } + if (this.isUnit()) { + if (this._s < 0) { + return n.negate(); + } + return n; + } + if (n.isUnit()) { + if (n._s < 0) { + return this.negate(); + } + return this; + } + if (this === n) { + return this.square(); + } + + var r = this._d.length >= n._d.length; + var a = (r ? this : n)._d; // a will be longer than b + var b = (r ? n : this)._d; + var al = a.length; + var bl = b.length; + + var pl = al + bl; + var partial = new Array(pl); + var i; + for (i = 0; i < pl; i++) { + partial[i] = 0; + } + + for (i = 0; i < bl; i++) { + var carry = 0; + var bi = b[i]; + var jlimit = al + i; + var digit; + for (var j = i; j < jlimit; j++) { + digit = partial[j] + bi * a[j - i] + carry; + carry = (digit / BigInteger_base) | 0; + partial[j] = digit % BigInteger_base | 0; + } + if (carry) { + digit = partial[j] + carry; + carry = (digit / BigInteger_base) | 0; + partial[j] = digit % BigInteger_base; + } + } + return new BigInteger(partial, this._s * n._s, CONSTRUCT); + }; + + // Multiply a BigInteger by a single-digit native number + // Assumes that this and n are >= 0 + // This is not really intended to be used outside the library itself + BigInteger.prototype.multiplySingleDigit = function (n) { + if (n === 0 || this._s === 0) { + return ZERO; + } + if (n === 1) { + return this; + } + + var digit; + if (this._d.length === 1) { + digit = this._d[0] * n; + if (digit >= BigInteger_base) { + return new BigInteger( + [digit % BigInteger_base | 0, (digit / BigInteger_base) | 0], + 1, + CONSTRUCT + ); + } + return new BigInteger([digit], 1, CONSTRUCT); + } + + if (n === 2) { + return this.add(this); + } + if (this.isUnit()) { + return new BigInteger([n], 1, CONSTRUCT); + } + + var a = this._d; + var al = a.length; + + var pl = al + 1; + var partial = new Array(pl); + for (var i = 0; i < pl; i++) { + partial[i] = 0; + } + + var carry = 0; + for (var j = 0; j < al; j++) { + digit = n * a[j] + carry; + carry = (digit / BigInteger_base) | 0; + partial[j] = digit % BigInteger_base | 0; + } + if (carry) { + partial[j] = carry; + } + + return new BigInteger(partial, 1, CONSTRUCT); + }; + + /* Function: square Multiply a by itself. @@ -967,54 +960,54 @@ This file has been modified by Paul Shapiro to bring in the function lowVal whic See Also: */ - BigInteger.prototype.square = function() { - // Normally, squaring a 10-digit number would take 100 multiplications. - // Of these 10 are unique diagonals, of the remaining 90 (100-10), 45 are repeated. - // This procedure saves (N*(N-1))/2 multiplications, (e.g., 45 of 100 multiplies). - // Based on code by Gary Darby, Intellitech Systems Inc., www.DelphiForFun.org - - if (this._s === 0) { - return ZERO; - } - if (this.isUnit()) { - return ONE; - } - - var digits = this._d; - var length = digits.length; - var imult1 = new Array(length + length + 1); - var product, carry, k; - var i; - - // Calculate diagonal - for (i = 0; i < length; i++) { - k = i * 2; - product = digits[i] * digits[i]; - carry = (product / BigInteger_base) | 0; - imult1[k] = product % BigInteger_base; - imult1[k + 1] = carry; - } - - // Calculate repeating part - for (i = 0; i < length; i++) { - carry = 0; - k = i * 2 + 1; - for (var j = i + 1; j < length; j++, k++) { - product = digits[j] * digits[i] * 2 + imult1[k] + carry; - carry = (product / BigInteger_base) | 0; - imult1[k] = product % BigInteger_base; - } - k = length + i; - var digit = carry + imult1[k]; - carry = (digit / BigInteger_base) | 0; - imult1[k] = digit % BigInteger_base; - imult1[k + 1] += carry; - } - - return new BigInteger(imult1, 1, CONSTRUCT); - }; - - /* + BigInteger.prototype.square = function () { + // Normally, squaring a 10-digit number would take 100 multiplications. + // Of these 10 are unique diagonals, of the remaining 90 (100-10), 45 are repeated. + // This procedure saves (N*(N-1))/2 multiplications, (e.g., 45 of 100 multiplies). + // Based on code by Gary Darby, Intellitech Systems Inc., www.DelphiForFun.org + + if (this._s === 0) { + return ZERO; + } + if (this.isUnit()) { + return ONE; + } + + var digits = this._d; + var length = digits.length; + var imult1 = new Array(length + length + 1); + var product, carry, k; + var i; + + // Calculate diagonal + for (i = 0; i < length; i++) { + k = i * 2; + product = digits[i] * digits[i]; + carry = (product / BigInteger_base) | 0; + imult1[k] = product % BigInteger_base; + imult1[k + 1] = carry; + } + + // Calculate repeating part + for (i = 0; i < length; i++) { + carry = 0; + k = i * 2 + 1; + for (var j = i + 1; j < length; j++, k++) { + product = digits[j] * digits[i] * 2 + imult1[k] + carry; + carry = (product / BigInteger_base) | 0; + imult1[k] = product % BigInteger_base; + } + k = length + i; + var digit = carry + imult1[k]; + carry = (digit / BigInteger_base) | 0; + imult1[k] = digit % BigInteger_base; + imult1[k + 1] += carry; + } + + return new BigInteger(imult1, 1, CONSTRUCT); + }; + + /* Function: quotient Divide two and truncate towards zero. @@ -1032,17 +1025,17 @@ This file has been modified by Paul Shapiro to bring in the function lowVal whic , , , , */ - BigInteger.prototype.quotient = function(n) { - return this.divRem(n)[0]; - }; + BigInteger.prototype.quotient = function (n) { + return this.divRem(n)[0]; + }; - /* + /* Function: divide Deprecated synonym for . */ - BigInteger.prototype.divide = BigInteger.prototype.quotient; + BigInteger.prototype.divide = BigInteger.prototype.quotient; - /* + /* Function: remainder Calculate the remainder of two . @@ -1061,11 +1054,11 @@ This file has been modified by Paul Shapiro to bring in the function lowVal whic , */ - BigInteger.prototype.remainder = function(n) { - return this.divRem(n)[1]; - }; + BigInteger.prototype.remainder = function (n) { + return this.divRem(n)[1]; + }; - /* + /* Function: divRem Calculate the integer quotient and remainder of two . @@ -1091,168 +1084,165 @@ This file has been modified by Paul Shapiro to bring in the function lowVal whic , */ - BigInteger.prototype.divRem = function(n) { - n = BigInteger(n); - if (n._s === 0) { - throw new Error("Divide by zero"); - } - if (this._s === 0) { - return [ZERO, ZERO]; - } - if (n._d.length === 1) { - return this.divRemSmall(n._s * n._d[0]); - } - - // Test for easy cases -- |n1| <= |n2| - switch (this.compareAbs(n)) { - case 0: // n1 == n2 - return [this._s === n._s ? ONE : M_ONE, ZERO]; - case -1: // |n1| < |n2| - return [ZERO, this]; - } - - var sign = this._s * n._s; - var a = n.abs(); - var b_digits = this._d; - var b_index = b_digits.length; - var digits = n._d.length; - var quot = []; - var guess; - - var part = new BigInteger([], 0, CONSTRUCT); - - while (b_index) { - part._d.unshift(b_digits[--b_index]); - part = new BigInteger(part._d, 1, CONSTRUCT); - - if (part.compareAbs(n) < 0) { - quot.push(0); - continue; - } - if (part._s === 0) { - guess = 0; - } else { - var xlen = part._d.length, - ylen = a._d.length; - var highx = - part._d[xlen - 1] * BigInteger_base + part._d[xlen - 2]; - var highy = a._d[ylen - 1] * BigInteger_base + a._d[ylen - 2]; - if (part._d.length > a._d.length) { - // The length of part._d can either match a._d length, - // or exceed it by one. - highx = (highx + 1) * BigInteger_base; - } - guess = Math.ceil(highx / highy); - } - do { - var check = a.multiplySingleDigit(guess); - if (check.compareAbs(part) <= 0) { - break; - } - guess--; - } while (guess); - - quot.push(guess); - if (!guess) { - continue; - } - var diff = part.subtract(check); - part._d = diff._d.slice(); - } - - return [ - new BigInteger(quot.reverse(), sign, CONSTRUCT), - new BigInteger(part._d, this._s, CONSTRUCT), - ]; - }; - - // Throws an exception if n is outside of (-BigInteger.base, -1] or - // [1, BigInteger.base). It's not necessary to call this, since the - // other division functions will call it if they are able to. - BigInteger.prototype.divRemSmall = function(n) { - var r; - n = +n; - if (n === 0) { - throw new Error("Divide by zero"); - } - - var n_s = n < 0 ? -1 : 1; - var sign = this._s * n_s; - n = Math.abs(n); - - if (n < 1 || n >= BigInteger_base) { - throw new Error("Argument out of range"); - } - - if (this._s === 0) { - return [ZERO, ZERO]; - } - - if (n === 1 || n === -1) { - return [ - sign === 1 - ? this.abs() - : new BigInteger(this._d, sign, CONSTRUCT), - ZERO, - ]; - } - - // 2 <= n < BigInteger_base - - // divide a single digit by a single digit - if (this._d.length === 1) { - var q = new BigInteger([(this._d[0] / n) | 0], 1, CONSTRUCT); - r = new BigInteger([this._d[0] % n | 0], 1, CONSTRUCT); - if (sign < 0) { - q = q.negate(); - } - if (this._s < 0) { - r = r.negate(); - } - return [q, r]; - } - - var digits = this._d.slice(); - var quot = new Array(digits.length); - var part = 0; - var diff = 0; - var i = 0; - var guess; - - while (digits.length) { - part = part * BigInteger_base + digits[digits.length - 1]; - if (part < n) { - quot[i++] = 0; - digits.pop(); - diff = BigInteger_base * diff + part; - continue; - } - if (part === 0) { - guess = 0; - } else { - guess = (part / n) | 0; - } - - var check = n * guess; - diff = part - check; - quot[i++] = guess; - if (!guess) { - digits.pop(); - continue; - } - - digits.pop(); - part = diff; - } - - r = new BigInteger([diff], 1, CONSTRUCT); - if (this._s < 0) { - r = r.negate(); - } - return [new BigInteger(quot.reverse(), sign, CONSTRUCT), r]; - }; - - /* + BigInteger.prototype.divRem = function (n) { + n = BigInteger(n); + if (n._s === 0) { + throw new Error('Divide by zero'); + } + if (this._s === 0) { + return [ZERO, ZERO]; + } + if (n._d.length === 1) { + return this.divRemSmall(n._s * n._d[0]); + } + + // Test for easy cases -- |n1| <= |n2| + switch (this.compareAbs(n)) { + case 0: // n1 == n2 + return [this._s === n._s ? ONE : M_ONE, ZERO]; + case -1: // |n1| < |n2| + return [ZERO, this]; + } + + var sign = this._s * n._s; + var a = n.abs(); + var b_digits = this._d; + var b_index = b_digits.length; + var digits = n._d.length; + var quot = []; + var guess; + + var part = new BigInteger([], 0, CONSTRUCT); + + while (b_index) { + part._d.unshift(b_digits[--b_index]); + part = new BigInteger(part._d, 1, CONSTRUCT); + + if (part.compareAbs(n) < 0) { + quot.push(0); + continue; + } + if (part._s === 0) { + guess = 0; + } else { + var xlen = part._d.length, + ylen = a._d.length; + var highx = part._d[xlen - 1] * BigInteger_base + part._d[xlen - 2]; + var highy = a._d[ylen - 1] * BigInteger_base + a._d[ylen - 2]; + if (part._d.length > a._d.length) { + // The length of part._d can either match a._d length, + // or exceed it by one. + highx = (highx + 1) * BigInteger_base; + } + guess = Math.ceil(highx / highy); + } + do { + var check = a.multiplySingleDigit(guess); + if (check.compareAbs(part) <= 0) { + break; + } + guess--; + } while (guess); + + quot.push(guess); + if (!guess) { + continue; + } + var diff = part.subtract(check); + part._d = diff._d.slice(); + } + + return [ + new BigInteger(quot.reverse(), sign, CONSTRUCT), + new BigInteger(part._d, this._s, CONSTRUCT) + ]; + }; + + // Throws an exception if n is outside of (-BigInteger.base, -1] or + // [1, BigInteger.base). It's not necessary to call this, since the + // other division functions will call it if they are able to. + BigInteger.prototype.divRemSmall = function (n) { + var r; + n = +n; + if (n === 0) { + throw new Error('Divide by zero'); + } + + var n_s = n < 0 ? -1 : 1; + var sign = this._s * n_s; + n = Math.abs(n); + + if (n < 1 || n >= BigInteger_base) { + throw new Error('Argument out of range'); + } + + if (this._s === 0) { + return [ZERO, ZERO]; + } + + if (n === 1 || n === -1) { + return [ + sign === 1 ? this.abs() : new BigInteger(this._d, sign, CONSTRUCT), + ZERO + ]; + } + + // 2 <= n < BigInteger_base + + // divide a single digit by a single digit + if (this._d.length === 1) { + var q = new BigInteger([(this._d[0] / n) | 0], 1, CONSTRUCT); + r = new BigInteger([this._d[0] % n | 0], 1, CONSTRUCT); + if (sign < 0) { + q = q.negate(); + } + if (this._s < 0) { + r = r.negate(); + } + return [q, r]; + } + + var digits = this._d.slice(); + var quot = new Array(digits.length); + var part = 0; + var diff = 0; + var i = 0; + var guess; + + while (digits.length) { + part = part * BigInteger_base + digits[digits.length - 1]; + if (part < n) { + quot[i++] = 0; + digits.pop(); + diff = BigInteger_base * diff + part; + continue; + } + if (part === 0) { + guess = 0; + } else { + guess = (part / n) | 0; + } + + var check = n * guess; + diff = part - check; + quot[i++] = guess; + if (!guess) { + digits.pop(); + continue; + } + + digits.pop(); + part = diff; + } + + r = new BigInteger([diff], 1, CONSTRUCT); + if (this._s < 0) { + r = r.negate(); + } + return [new BigInteger(quot.reverse(), sign, CONSTRUCT), r]; + }; + + /* Function: isEven Return true iff *this* is divisible by two. @@ -1266,12 +1256,12 @@ This file has been modified by Paul Shapiro to bring in the function lowVal whic */ - BigInteger.prototype.isEven = function() { - var digits = this._d; - return this._s === 0 || digits.length === 0 || digits[0] % 2 === 0; - }; + BigInteger.prototype.isEven = function () { + var digits = this._d; + return this._s === 0 || digits.length === 0 || digits[0] % 2 === 0; + }; - /* + /* Function: isOdd Return true iff *this* is not divisible by two. @@ -1283,11 +1273,11 @@ This file has been modified by Paul Shapiro to bring in the function lowVal whic */ - BigInteger.prototype.isOdd = function() { - return !this.isEven(); - }; + BigInteger.prototype.isOdd = function () { + return !this.isEven(); + }; - /* + /* Function: sign Get the sign of a . @@ -1301,11 +1291,11 @@ This file has been modified by Paul Shapiro to bring in the function lowVal whic , , , , */ - BigInteger.prototype.sign = function() { - return this._s; - }; + BigInteger.prototype.sign = function () { + return this._s; + }; - /* + /* Function: isPositive Return true iff *this* > 0. @@ -1317,11 +1307,11 @@ This file has been modified by Paul Shapiro to bring in the function lowVal whic , , , , , */ - BigInteger.prototype.isPositive = function() { - return this._s > 0; - }; + BigInteger.prototype.isPositive = function () { + return this._s > 0; + }; - /* + /* Function: isNegative Return true iff *this* < 0. @@ -1333,11 +1323,11 @@ This file has been modified by Paul Shapiro to bring in the function lowVal whic , , , , , */ - BigInteger.prototype.isNegative = function() { - return this._s < 0; - }; + BigInteger.prototype.isNegative = function () { + return this._s < 0; + }; - /* + /* Function: isZero Return true iff *this* == 0. @@ -1349,11 +1339,11 @@ This file has been modified by Paul Shapiro to bring in the function lowVal whic , , , , */ - BigInteger.prototype.isZero = function() { - return this._s === 0; - }; + BigInteger.prototype.isZero = function () { + return this._s === 0; + }; - /* + /* Function: exp10 Multiply a by a power of 10. @@ -1380,45 +1370,41 @@ This file has been modified by Paul Shapiro to bring in the function lowVal whic , */ - BigInteger.prototype.exp10 = function(n) { - n = +n; - if (n === 0) { - return this; - } - if (Math.abs(n) > Number(MAX_EXP)) { - throw new Error("exponent too large in BigInteger.exp10"); - } - // Optimization for this == 0. This also keeps us from having to trim zeros in the positive n case - if (this._s === 0) { - return ZERO; - } - if (n > 0) { - var k = new BigInteger(this._d.slice(), this._s, CONSTRUCT); - - for (; n >= BigInteger_base_log10; n -= BigInteger_base_log10) { - k._d.unshift(0); - } - if (n == 0) return k; - k._s = 1; - k = k.multiplySingleDigit(Math.pow(10, n)); - return this._s < 0 ? k.negate() : k; - } else if (-n >= this._d.length * BigInteger_base_log10) { - return ZERO; - } else { - var k = new BigInteger(this._d.slice(), this._s, CONSTRUCT); - - for ( - n = -n; - n >= BigInteger_base_log10; - n -= BigInteger_base_log10 - ) { - k._d.shift(); - } - return n == 0 ? k : k.divRemSmall(Math.pow(10, n))[0]; - } - }; - - /* + BigInteger.prototype.exp10 = function (n) { + n = +n; + if (n === 0) { + return this; + } + if (Math.abs(n) > Number(MAX_EXP)) { + throw new Error('exponent too large in BigInteger.exp10'); + } + // Optimization for this == 0. This also keeps us from having to trim zeros in the positive n case + if (this._s === 0) { + return ZERO; + } + if (n > 0) { + var k = new BigInteger(this._d.slice(), this._s, CONSTRUCT); + + for (; n >= BigInteger_base_log10; n -= BigInteger_base_log10) { + k._d.unshift(0); + } + if (n == 0) return k; + k._s = 1; + k = k.multiplySingleDigit(Math.pow(10, n)); + return this._s < 0 ? k.negate() : k; + } else if (-n >= this._d.length * BigInteger_base_log10) { + return ZERO; + } else { + var k = new BigInteger(this._d.slice(), this._s, CONSTRUCT); + + for (n = -n; n >= BigInteger_base_log10; n -= BigInteger_base_log10) { + k._d.shift(); + } + return n == 0 ? k : k.divRemSmall(Math.pow(10, n))[0]; + } + }; + + /* Function: pow Raise a to a power. @@ -1437,54 +1423,54 @@ This file has been modified by Paul Shapiro to bring in the function lowVal whic */ - BigInteger.prototype.pow = function(n) { - if (this.isUnit()) { - if (this._s > 0) { - return this; - } else { - return BigInteger(n).isOdd() ? this : this.negate(); - } - } - - n = BigInteger(n); - if (n._s === 0) { - return ONE; - } else if (n._s < 0) { - if (this._s === 0) { - throw new Error("Divide by zero"); - } else { - return ZERO; - } - } - if (this._s === 0) { - return ZERO; - } - if (n.isUnit()) { - return this; - } - - if (n.compareAbs(MAX_EXP) > 0) { - throw new Error("exponent too large in BigInteger.pow"); - } - var x = this; - var aux = ONE; - var two = BigInteger.small[2]; - - while (n.isPositive()) { - if (n.isOdd()) { - aux = aux.multiply(x); - if (n.isUnit()) { - return aux; - } - } - x = x.square(); - n = n.quotient(two); - } - - return aux; - }; - - /* + BigInteger.prototype.pow = function (n) { + if (this.isUnit()) { + if (this._s > 0) { + return this; + } else { + return BigInteger(n).isOdd() ? this : this.negate(); + } + } + + n = BigInteger(n); + if (n._s === 0) { + return ONE; + } else if (n._s < 0) { + if (this._s === 0) { + throw new Error('Divide by zero'); + } else { + return ZERO; + } + } + if (this._s === 0) { + return ZERO; + } + if (n.isUnit()) { + return this; + } + + if (n.compareAbs(MAX_EXP) > 0) { + throw new Error('exponent too large in BigInteger.pow'); + } + var x = this; + var aux = ONE; + var two = BigInteger.small[2]; + + while (n.isPositive()) { + if (n.isOdd()) { + aux = aux.multiply(x); + if (n.isUnit()) { + return aux; + } + } + x = x.square(); + n = n.quotient(two); + } + + return aux; + }; + + /* Function: modPow Raise a to a power (mod m). @@ -1504,25 +1490,25 @@ This file has been modified by Paul Shapiro to bring in the function lowVal whic , */ - BigInteger.prototype.modPow = function(exponent, modulus) { - var result = ONE; - var base = this; + BigInteger.prototype.modPow = function (exponent, modulus) { + var result = ONE; + var base = this; - while (exponent.isPositive()) { - if (exponent.isOdd()) { - result = result.multiply(base).remainder(modulus); - } + while (exponent.isPositive()) { + if (exponent.isOdd()) { + result = result.multiply(base).remainder(modulus); + } - exponent = exponent.quotient(BigInteger.small[2]); - if (exponent.isPositive()) { - base = base.square().remainder(modulus); - } - } + exponent = exponent.quotient(BigInteger.small[2]); + if (exponent.isPositive()) { + base = base.square().remainder(modulus); + } + } - return result; - }; + return result; + }; - /* + /* Function: log Get the natural logarithm of a as a native JavaScript number. @@ -1540,30 +1526,30 @@ This file has been modified by Paul Shapiro to bring in the function lowVal whic */ - BigInteger.prototype.log = function() { - switch (this._s) { - case 0: - return -Infinity; - case -1: - return NaN; - default: // Fall through. - } - - var l = this._d.length; - - if (l * BigInteger_base_log10 < 30) { - return Math.log(this.valueOf()); - } - - var N = Math.ceil(30 / BigInteger_base_log10); - var firstNdigits = this._d.slice(l - N); - return ( - Math.log(new BigInteger(firstNdigits, 1, CONSTRUCT).valueOf()) + - (l - N) * Math.log(BigInteger_base) - ); - }; - - /* + BigInteger.prototype.log = function () { + switch (this._s) { + case 0: + return -Infinity; + case -1: + return NaN; + default: // Fall through. + } + + var l = this._d.length; + + if (l * BigInteger_base_log10 < 30) { + return Math.log(this.valueOf()); + } + + var N = Math.ceil(30 / BigInteger_base_log10); + var firstNdigits = this._d.slice(l - N); + return ( + Math.log(new BigInteger(firstNdigits, 1, CONSTRUCT).valueOf()) + + (l - N) * Math.log(BigInteger_base) + ); + }; + + /* Function: valueOf Convert a to a native JavaScript integer. @@ -1578,11 +1564,11 @@ This file has been modified by Paul Shapiro to bring in the function lowVal whic , */ - BigInteger.prototype.valueOf = function() { - return parseInt(this.toString(), 10); - }; + BigInteger.prototype.valueOf = function () { + return parseInt(this.toString(), 10); + }; - /* + /* Function: toJSValue Convert a to a native JavaScript integer. @@ -1596,72 +1582,74 @@ This file has been modified by Paul Shapiro to bring in the function lowVal whic , */ - BigInteger.prototype.toJSValue = function() { - return parseInt(this.toString(), 10); - }; + BigInteger.prototype.toJSValue = function () { + return parseInt(this.toString(), 10); + }; - /* + /* Function: lowVal Author: Lucas Jones */ - BigInteger.prototype.lowVal = function() { - return this._d[0] || 0; - }; - - var MAX_EXP = BigInteger(0x7fffffff); - // Constant: MAX_EXP - // The largest exponent allowed in and (0x7FFFFFFF or 2147483647). - BigInteger.MAX_EXP = MAX_EXP; - - (function() { - function makeUnary(fn) { - return function(a) { - return fn.call(BigInteger(a)); - }; - } - - function makeBinary(fn) { - return function(a, b) { - return fn.call(BigInteger(a), BigInteger(b)); - }; - } - - function makeTrinary(fn) { - return function(a, b, c) { - return fn.call(BigInteger(a), BigInteger(b), BigInteger(c)); - }; - } - - (function() { - var i, fn; - var unary = "toJSValue,isEven,isOdd,sign,isZero,isNegative,abs,isUnit,square,negate,isPositive,toString,next,prev,log".split( - ",", - ); - var binary = "compare,remainder,divRem,subtract,add,quotient,divide,multiply,pow,compareAbs".split( - ",", - ); - var trinary = ["modPow"]; - - for (i = 0; i < unary.length; i++) { - fn = unary[i]; - BigInteger[fn] = makeUnary(BigInteger.prototype[fn]); - } - - for (i = 0; i < binary.length; i++) { - fn = binary[i]; - BigInteger[fn] = makeBinary(BigInteger.prototype[fn]); - } - - for (i = 0; i < trinary.length; i++) { - fn = trinary[i]; - BigInteger[fn] = makeTrinary(BigInteger.prototype[fn]); - } - - BigInteger.exp10 = function(x, n) { - return BigInteger(x).exp10(n); - }; - })(); - })(); - - exports.BigInteger = BigInteger; -})(typeof exports !== "undefined" ? exports : this); + BigInteger.prototype.lowVal = function () { + return this._d[0] || 0; + }; + + var MAX_EXP = BigInteger(0x7fffffff); + // Constant: MAX_EXP + // The largest exponent allowed in and (0x7FFFFFFF or 2147483647). + BigInteger.MAX_EXP = MAX_EXP; + + (function () { + function makeUnary(fn) { + return function (a) { + return fn.call(BigInteger(a)); + }; + } + + function makeBinary(fn) { + return function (a, b) { + return fn.call(BigInteger(a), BigInteger(b)); + }; + } + + function makeTrinary(fn) { + return function (a, b, c) { + return fn.call(BigInteger(a), BigInteger(b), BigInteger(c)); + }; + } + + (function () { + var i, fn; + var unary = + 'toJSValue,isEven,isOdd,sign,isZero,isNegative,abs,isUnit,square,negate,isPositive,toString,next,prev,log'.split( + ',' + ); + var binary = + 'compare,remainder,divRem,subtract,add,quotient,divide,multiply,pow,compareAbs'.split( + ',' + ); + var trinary = ['modPow']; + + for (i = 0; i < unary.length; i++) { + fn = unary[i]; + BigInteger[fn] = makeUnary(BigInteger.prototype[fn]); + } + + for (i = 0; i < binary.length; i++) { + fn = binary[i]; + BigInteger[fn] = makeBinary(BigInteger.prototype[fn]); + } + + for (i = 0; i < trinary.length; i++) { + fn = trinary[i]; + BigInteger[fn] = makeTrinary(BigInteger.prototype[fn]); + } + + BigInteger.exp10 = function (x, n) { + return BigInteger(x).exp10(n); + }; + })(); + })(); + + exports.BigInteger = BigInteger; +})(typeof exports !== 'undefined' ? exports : this); diff --git a/crypto/blockchains/xmr/providers/XmrSendProvider.js b/crypto/blockchains/xmr/providers/XmrSendProvider.ts similarity index 88% rename from crypto/blockchains/xmr/providers/XmrSendProvider.js rename to crypto/blockchains/xmr/providers/XmrSendProvider.ts index 5b20112de..32e74acc8 100644 --- a/crypto/blockchains/xmr/providers/XmrSendProvider.js +++ b/crypto/blockchains/xmr/providers/XmrSendProvider.ts @@ -6,12 +6,16 @@ import BlocksoftAxios from '../../../common/BlocksoftAxios'; import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; export default class XmrSendProvider { - constructor(settings) { + private _settings: any; + private _link: string | boolean; + private _serverUrl: string | undefined; + + constructor(settings: any) { this._settings = settings; this._link = false; } - async _init() { + private async _init(): Promise { if (this._link) return false; this._serverUrl = await settingsActions.getSetting('xmrServerSend'); if (!this._serverUrl || this._serverUrl === 'false') { @@ -31,16 +35,20 @@ export default class XmrSendProvider { } this._link = link; + return true; } - async send(params) { + public async send(params: any): Promise { await this._init(); // curl http://127.0.0.1:18081/send_raw_transaction -d '{"tx_as_hex":"de6a3...", "do_not_relay":false}' -H 'Content-Type: application/json' // https://web.getmonero.org/resources/developer-guides/daemon-rpc.html#send_raw_transaction // const resNode = await BlocksoftAxios.post('http://node.moneroworld.com:18089/send_raw_transaction', {tx_as_hex : params.tx, do_not_relay : false}) // return resNode.data - if (this._link.indexOf('mymonero.com') !== -1) { + if ( + typeof this._link !== 'boolean' && + this._link?.indexOf('mymonero.com') !== -1 + ) { try { const res = await BlocksoftAxios.post(this._link + 'submit_raw_tx', { address: params.address, @@ -52,7 +60,7 @@ export default class XmrSendProvider { res.data ); return res.data; - } catch (e) { + } catch (e: any) { if (e.message.indexOf('double') !== -1) { throw new Error('SERVER_RESPONSE_DOUBLE_SPEND'); } else { diff --git a/crypto/blockchains/xmr/providers/XmrUnspentsProvider.ts b/crypto/blockchains/xmr/providers/XmrUnspentsProvider.ts new file mode 100644 index 000000000..27c5b588c --- /dev/null +++ b/crypto/blockchains/xmr/providers/XmrUnspentsProvider.ts @@ -0,0 +1,137 @@ +/** + * @version 0.11 + */ +import settingsActions from '../../../../app/appstores/Stores/Settings/SettingsActions'; +import BlocksoftAxios from '../../../common/BlocksoftAxios'; +import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; + +export default class XmrUnspentsProvider { + private _settings: any; + private _link: string | false; + private _cache: { [key: string]: any }; + + constructor(settings: any) { + this._settings = settings; + this._link = false; + this._cache = {}; + } + + init(): void { + if (this._link) return; + this._serverUrl = settingsActions.getSettingStatic('xmrServer'); + if (!this._serverUrl || this._serverUrl === 'false') { + this._serverUrl = 'api.mymonero.com:8443'; + } + + let link = this._serverUrl.trim(); + if (link.substr(0, 4).toLowerCase() !== 'http') { + link = 'https://' + this._serverUrl; + } + if (link[link.length - 1] !== '/') { + link = link + '/'; + } + + this._link = link; + this._cache = {}; + } + + async _getUnspents( + params: any, + fn?: (err: Error | null, data: any) => void + ): Promise { + try { + const key = JSON.stringify(params); + let res: { data: any } = { data: {} }; + if (typeof this._cache[key] === 'undefined') { + BlocksoftCryptoLog.log('XmrUnspentsProvider Xmr._getUnspents', key); + /* + const linkParams = { + address: params.address, + view_key: params.privViewKey, + amount: params.amount.toString(), + mixin: '10', + use_dust: true, + dust_threshold: '2000000000' + } + */ + res = await BlocksoftAxios.post( + this._link + 'get_unspent_outs', + params + ); + BlocksoftCryptoLog.log( + 'XmrUnspentsProvider Xmr._getUnspents res ' + + JSON.stringify(res.data).substr(0, 200) + ); + this._cache[key] = res.data; + } else { + res = { data: this._cache[key] }; + } + if (typeof fn === 'undefined' || !fn) { + return res.data; + } else { + fn(null, res.data); + } + } catch (e: any) { + e.message += ' while Xmr._getUnspents'; + if (typeof fn === 'undefined' || !fn) { + throw e; + } else { + fn(e, null); + } + } + } + + async _getRandomOutputs( + params: any, + fn?: (err: Error | null, data: any) => void + ): Promise { + try { + BlocksoftCryptoLog.log( + 'XmrUnspentsProvider Xmr._getRandomOutputs', + params + ); + + /* + const amounts = usingOuts.map(o => (o.rct ? '0' : o.amount.toString())) + const linkParams = { + amounts, + count: (mixin * 1 + 1) + } + */ + + const res = await BlocksoftAxios.post( + this._link + 'get_random_outs', + params + ); + await BlocksoftCryptoLog.log( + 'XmrUnspentsProvider Xmr._getRandomOutputs res ' + + JSON.stringify(res.data).substr(0, 200) + ); + + if ( + typeof res.data === 'undefined' || + !typeof res.data || + typeof res.data.amount_outs === 'undefined' || + !res.data.amount_outs || + res.data.amount_outs.length === 0 + ) { + throw new Error('SERVER_RESPONSE_NO_RESPONSE_XMR'); + } + + if (typeof fn === 'undefined' || !fn) { + return res.data; + } else { + fn(null, res.data); + } + } catch (e: any) { + if (e.message.indexOf('SERVER_RESPONSE') === -1) { + e.message += ' while Xmr._getRandomOutputs'; + } + if (typeof fn === 'undefined' || !fn) { + throw e; + } else { + fn(e, null); + } + } + } +} diff --git a/crypto/blockchains/xrp/XrpAddressProcessor.js b/crypto/blockchains/xrp/XrpAddressProcessor.js deleted file mode 100644 index cdc05f9b5..000000000 --- a/crypto/blockchains/xrp/XrpAddressProcessor.js +++ /dev/null @@ -1,34 +0,0 @@ -/** - * @version 0.5 - * https://github.com/iancoleman/bip39/blob/aa793f572f26ad20740f28040de13431c973dfb8/src/js/ripple-util.js - */ -const bitcoin = require('bitcoinjs-lib') -const basex = require('base-x') - -export default class XrpAddressProcessor { - - async setBasicRoot(root) { - - } - - /** - * @param {string|Buffer} privateKey - * @param {*} data - * @returns {Promise<{privateKey: string, address: string, addedData: *}>} - */ - async getAddress(privateKey, data = {}) { - privateKey = Buffer.from(privateKey) - const keyPair = bitcoin.ECPair.fromPrivateKey(privateKey) - const btcPrivateKey = keyPair.toWIF() - const ripplePrivateKey = basex('123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz').decode(btcPrivateKey).toString("hex").slice(2,66) - const btcAddress = bitcoin.payments.p2pkh({pubkey: keyPair.publicKey, network: bitcoin.networks.bitcoin}).address - const rippleAddress = basex('rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz').encode( - basex('123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz').decode(btcAddress) - ) - const addedData = {} - if (typeof data !== 'undefined' && typeof data.publicKey !== 'undefined') { - addedData.publicKey = data.publicKey.toString('hex') - } - return {address: rippleAddress, privateKey: ripplePrivateKey, addedData} - } -} diff --git a/crypto/blockchains/xrp/XrpAddressProcessor.ts b/crypto/blockchains/xrp/XrpAddressProcessor.ts new file mode 100644 index 000000000..e38d4d799 --- /dev/null +++ b/crypto/blockchains/xrp/XrpAddressProcessor.ts @@ -0,0 +1,44 @@ +import * as bitcoin from 'bitcoinjs-lib'; +import * as basexLib from 'base-x'; + +const basex = basexLib.default( + '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' +); + +export default class XrpAddressProcessor { + async setBasicRoot(root: string): Promise {} + + /** + * @param {string|Buffer} privateKey + * @param {*} data + * @returns {Promise<{privateKey: string, address: string, addedData: *}>} + */ + async getAddress( + privateKey: string | Buffer, + data: any = {} + ): Promise<{ privateKey: string; address: string; addedData: any }> { + privateKey = Buffer.from(privateKey); + const keyPair = bitcoin.ECPair.fromPrivateKey(privateKey); + const btcPrivateKey: string = keyPair.toWIF(); + const ripplePrivateKey: string = basex + .decode(btcPrivateKey) + // @ts-ignore + .toString('hex') + .slice(2, 66); + // @ts-ignore + const btcAddress: string = bitcoin.payments.p2pkh({ + pubkey: keyPair.publicKey, + network: bitcoin.networks.bitcoin + }).address; + const rippleAddress: string = basex.encode( + basexLib + .default('123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz') + .decode(btcAddress) + ); + const addedData: { publicKey?: string } = {}; + if (data && data.publicKey) { + addedData.publicKey = data.publicKey.toString('hex'); + } + return { address: rippleAddress, privateKey: ripplePrivateKey, addedData }; + } +} diff --git a/crypto/blockchains/xrp/XrpScannerProcessor.ts b/crypto/blockchains/xrp/XrpScannerProcessor.ts new file mode 100644 index 000000000..ab89ba386 --- /dev/null +++ b/crypto/blockchains/xrp/XrpScannerProcessor.ts @@ -0,0 +1,101 @@ +import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; +import XrpTmpDS from './stores/XrpTmpDS'; +import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings'; +import XrpDataRippleProvider from '@crypto/blockchains/xrp/basic/XrpDataRippleProvider'; +import XrpDataScanProvider from '@crypto/blockchains/xrp/basic/XrpDataScanProvider'; + +interface UnifiedTransaction { + // TODO +} + +interface BalanceResult { + balance: number; + unconfirmed: number; + provider: string; +} + +let CACHE_BLOCK_DATA: any = {}; + +export default class XrpScannerProcessor { + private _inited = false; + private provider!: XrpDataRippleProvider | XrpDataScanProvider; + + async init(): Promise { + if (this._inited) { + return false; + } + CACHE_BLOCK_DATA = await XrpTmpDS.getCache(); + const serverType = BlocksoftExternalSettings.getStatic('XRP_SCANNER_TYPE'); + if (serverType === 'dataripple') { + this.provider = new XrpDataRippleProvider(); + } else { + this.provider = new XrpDataScanProvider(); + } + this.provider.setCache(CACHE_BLOCK_DATA); + this._inited = true; + return true; + } + + async getBalanceBlockchain(address: string): Promise { + await this.init(); + + let res: any = false; + let balance = 0; + let provider = 'none'; + try { + res = await this.provider.getBalanceBlockchain(address); + if (res && typeof res.balance !== 'undefined') { + balance = res.balance; + provider = res.provider; + } + } catch (e: any) { + if ( + e.message.indexOf('timed out') === -1 && + e.message.indexOf('account not found') === -1 + ) { + throw e; + } else { + return false; + } + } + return { balance, unconfirmed: 0, provider }; + } + + async getTransactionsBlockchain( + scanData: { + account: { + address: string; + walletHash: string; + }; + additional: any; + }, + source = '' + ): Promise { + await this.init(); + const address = scanData.account.address.trim(); + await BlocksoftCryptoLog.log( + 'XrpScannerProcessor.getTransactions started ' + address + ); + + let transactions: UnifiedTransaction[] | false = []; + try { + transactions = await this.provider.getTransactionsBlockchain(scanData); + } catch (e: any) { + if ( + e.message.indexOf('account not found') === -1 && + e.message.indexOf('to retrieve payments') === -1 && + e.message.indexOf('limit exceeded') === -1 && + e.message.indexOf('timed out') === -1 + ) { + throw e; + } else { + return false; + } + } + + await BlocksoftCryptoLog.log( + 'XrpScannerProcessor.getTransactions finished ' + address + ); + return transactions; + } +} diff --git a/crypto/blockchains/xrp/XrpTransferProcessor.ts b/crypto/blockchains/xrp/XrpTransferProcessor.ts index 871b3bee9..de7d26834 100644 --- a/crypto/blockchains/xrp/XrpTransferProcessor.ts +++ b/crypto/blockchains/xrp/XrpTransferProcessor.ts @@ -8,12 +8,21 @@ */ import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog'; import BlocksoftUtils from '../../common/BlocksoftUtils'; +<<<<<<< Updated upstream import BlocksoftDispatcher from '../BlocksoftDispatcher'; import MarketingEvent from '../../../app/services/Marketing/MarketingEvent'; import { BlocksoftBlockchainTypes } from '../BlocksoftBlockchainTypes'; import { XrpTxSendProvider } from './basic/XrpTxSendProvider'; import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings'; +======= +import MarketingEvent from '../../../app/services/Marketing/MarketingEvent'; + +import { XrpTxSendProvider } from './basic/XrpTxSendProvider'; +import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings'; +import { BlocksoftBlockchainTypes } from '@lib/blockchains/BlocksoftBlockchainTypes'; +import BlocksoftDispatcher from '@lib/blockchains/BlocksoftDispatcher'; +>>>>>>> Stashed changes const FEE_DECIMALS = 6; @@ -22,7 +31,11 @@ export default class XrpTransferProcessor { private _settings: { network: string; currencyCode: string }; private _provider: XrpTxSendProvider; +<<<<<<< Updated upstream private _inited: boolean = false; +======= + private _inited = false; +>>>>>>> Stashed changes constructor(settings: { network: string; currencyCode: string }) { this._settings = settings; @@ -106,7 +119,11 @@ export default class XrpTransferProcessor this._inited = true; } txJson = await this._provider.getPrepared(data); +<<<<<<< Updated upstream } catch (e) { +======= + } catch (e: any) { +>>>>>>> Stashed changes if (e.message.indexOf('connect() timed out after') !== -1) { return false; } @@ -223,7 +240,11 @@ export default class XrpTransferProcessor this._inited = true; } txJson = await this._provider.getPrepared(data, false); +<<<<<<< Updated upstream } catch (e) { +======= + } catch (e: any) { +>>>>>>> Stashed changes if (e.message.indexOf('connect() timed out after') !== -1) { throw new Error('SERVER_RESPONSE_BAD_INTERNET'); } diff --git a/crypto/blockchains/xrp/basic/XrpDataRippleProvider.ts b/crypto/blockchains/xrp/basic/XrpDataRippleProvider.ts new file mode 100644 index 000000000..ec26df0e4 --- /dev/null +++ b/crypto/blockchains/xrp/basic/XrpDataRippleProvider.ts @@ -0,0 +1,230 @@ +import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; +import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; +import XrpTmpDS from '@crypto/blockchains/xrp/stores/XrpTmpDS'; + +const CACHE_VALID_TIME = 60000; +let CACHE_BLOCK_DATA = {}; + +const API_PATH = 'https://data.ripple.com/v2'; +export default class XrpDataRippleProvider { + setCache(tmp) { + CACHE_BLOCK_DATA = tmp; + } + + async getBalanceBlockchain(address: string) { + const link = `${API_PATH}/accounts/${address}/balances`; + let res = false; + let balance = 0; + + try { + res = await BlocksoftAxios.getWithoutBraking(link); + if ( + res && + typeof res.data !== 'undefined' && + res.data && + typeof res.data.balances !== 'undefined' + ) { + let row; + for (row of res.data.balances) { + if (row.currency === 'XRP') { + balance = row.value; + break; + } + } + } else { + return false; + } + } catch (e: any) { + if ( + e.message.indexOf('timed out') === -1 && + e.message.indexOf('account not found') === -1 + ) { + throw e; + } else { + return false; + } + } + return { balance: balance, unconfirmed: 0, provider: 'ripple.com' }; + } + + async getTransactionsBlockchain(scanData) { + const address = scanData.account.address.trim(); + const action = 'payments'; + await BlocksoftCryptoLog.log( + 'XrpScannerProcessor.DataRipple.getTransactions ' + + action + + ' started ' + + address + ); + const link = `${API_PATH}/accounts/${address}/payments`; + let res = false; + try { + res = await BlocksoftAxios.getWithoutBraking(link); + } catch (e: any) { + if ( + e.message.indexOf('account not found') === -1 && + e.message.indexOf('to retrieve payments') === -1 && + e.message.indexOf('limit exceeded') === -1 && + e.message.indexOf('timed out') === -1 + ) { + throw e; + } else { + return false; + } + } + + if (!res || typeof res.data === 'undefined' || !res.data) { + return false; + } + if (typeof res.data[action] === 'undefined') { + throw new Error('Undefined txs ' + link + ' ' + JSON.stringify(res.data)); + } + if (typeof res.data[action] === 'string') { + throw new Error('Undefined txs ' + link + ' ' + res.data[action]); + } + + const transactions = await this._unifyTransactions( + address, + res.data[action], + action + ); + await BlocksoftCryptoLog.log( + 'XrpScannerProcessor.DataRipple.getTransactions ' + + action + + ' finished ' + + address + ); + return transactions; + } + + async _unifyTransactions(address: string, result: string) { + const transactions = []; + let tx; + for (tx of result) { + const transaction = await this._unifyPayment(address, tx); + if (transaction) { + transactions.push(transaction); + } + } + return transactions; + } + + async _unifyPayment(address: string, transaction) { + let direction, amount; + + if (transaction.currency === 'XRP') { + if (transaction.source_currency === 'XRP') { + direction = address === transaction.source ? 'outcome' : 'income'; + } else if (transaction.destination === address) { + direction = 'income'; // USDT any => XRP my + } else { + // USDT my => XRP not my + return false; // do nothing + } + } else if (transaction.source_currency === 'XRP') { + if (transaction.source === address) { + direction = 'outcome'; // XRP my => USDT any + } else { + // XRP not my => USDT my + return false; // do nothing + } + } else { + return false; // USDT => USDT + } + + if (direction === 'income') { + amount = transaction.delivered_amount; + } else { + amount = transaction.amount; + } + + let transactionStatus = 'new'; + let ledger = false; + if ( + typeof transaction.ledger_index !== 'undefined' && + transaction.ledger_index > 0 + ) { + ledger = await this._getLedger(transaction.ledger_index); + if (ledger && ledger.transactionConfirmations > 5) { + transactionStatus = 'success'; + } + } + + if (typeof transaction.executed_time === 'undefined') { + transaction.executed_time = ''; + } + const tx = { + transactionHash: transaction.tx_hash, + blockHash: ledger ? ledger.ledger_hash : '', + blockNumber: transaction.ledger_index, + blockTime: transaction.executed_time, + blockConfirmations: ledger ? ledger.transactionConfirmations : 0, + transactionDirection: direction, + addressFrom: transaction.source === address ? '' : transaction.source, + addressTo: + transaction.destination === address ? '' : transaction.destination, + addressAmount: amount, + transactionStatus: transactionStatus, + transactionFee: transaction.transaction_cost + }; + if (typeof transaction.destination_tag !== 'undefined') { + tx.transactionJson = { memo: transaction.destination_tag }; + } + return tx; + } + + async _getLedger(index: string) { + const now = new Date().getTime(); + await BlocksoftCryptoLog.log( + 'XrpScannerProcessor.DataRipple._getLedger started ' + index + ); + const link = `${API_PATH}/ledgers/${index}`; + let res = false; + if ( + typeof CACHE_BLOCK_DATA[index] === 'undefined' || + (now - CACHE_BLOCK_DATA[index].time > CACHE_VALID_TIME && + CACHE_BLOCK_DATA[index].data.transactionConfirmations < 100) + ) { + try { + res = await BlocksoftAxios.getWithoutBraking(link); + if ( + res.data && + typeof res.data !== 'undefined' && + typeof res.data.ledger !== 'undefined' + ) { + await BlocksoftCryptoLog.log( + 'XrpScannerProcessor.DataRipple._getLedger updated for index ' + + index + + ' ' + + JSON.stringify(res.data.ledger) + ); + const ledger = { + close_time: res.data.ledger.close_time, + ledger_hash: res.data.ledger.ledger_hash, + transactionConfirmations: Math.round( + (now - res.data.ledger.close_time * 1000) / (60 * 1000) + ) // minutes + }; + CACHE_BLOCK_DATA[index] = { + data: ledger, + time: now + }; + } + await XrpTmpDS.saveCache(CACHE_BLOCK_DATA); + } catch (e: any) { + if ( + e.message.indexOf('timed out') === -1 && + e.message.indexOf('account not found') === -1 + ) { + throw e; + } else { + res = false; + } + } + } + if (typeof CACHE_BLOCK_DATA[index] === 'undefined') { + return false; + } + return CACHE_BLOCK_DATA[index].data; + } +} diff --git a/crypto/blockchains/xrp/basic/XrpDataScanProvider.ts b/crypto/blockchains/xrp/basic/XrpDataScanProvider.ts new file mode 100644 index 000000000..0e153085e --- /dev/null +++ b/crypto/blockchains/xrp/basic/XrpDataScanProvider.ts @@ -0,0 +1,325 @@ +import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings'; +import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; +import BlocksoftUtils from '@crypto/common/BlocksoftUtils'; +import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; +import XrpTmpDS from '@crypto/blockchains/xrp/stores/XrpTmpDS'; + +const CACHE_VALID_TIME = 60000; +let CACHE_BLOCK_DATA = {}; + +export default class XrpDataScanProvider { + setCache(tmp) { + CACHE_BLOCK_DATA = tmp; + } + + async getBalanceBlockchain(address) { + const link = BlocksoftExternalSettings.getStatic('XRP_SCANNER_SERVER'); + let res = false; + let balance = 0; + try { + /** + curl http://s1.ripple.com:51234/ -X POST -H "Content-Type: application/json" -d '{'method":"account_info","params":[{"account":"rEAgA9B8U8RCkwn6MprHqE1ZfXoeGQxz4P","strict":true,"ledger_index":"validated","api_version':1}]}' + curl https://xrplcluster.com/ -X POST -H "Content-Type: application/json" -d '{'method":"account_info","params":[{"account":"rEAgA9B8U8RCkwn6MprHqE1ZfXoeGQxz4P","strict":true,"ledger_index":"validated","api_version':1}]}' + */ + const data = { + method: 'account_info', + params: [ + { + account: address, + strict: true, + ledger_index: 'validated', + api_version: 1 + } + ] + }; + + res = await BlocksoftAxios.postWithoutBraking(link, data); + + if ( + res && + typeof res.data !== 'undefined' && + res.data && + typeof res.data.result !== 'undefined' && + res.data.result + ) { + if ( + typeof res.data.result.account !== 'undefined' && + typeof res.data.result.error_code !== 'undefined' && + res.data.result.error_code === 19 + ) { + balance = 0; + } else if ( + typeof res.data.result.account_data !== 'undefined' && + typeof res.data.result.account_data.Balance !== 'undefined' + ) { + balance = BlocksoftUtils.toUnified( + res.data.result.account_data.Balance, + 6 + ); + } + } else { + return false; + } + } catch (e) { + if ( + e.message.indexOf('timed out') === -1 && + e.message.indexOf('account not found') === -1 + ) { + if (typeof res.data !== 'undefined' && res.data) { + e.message += ' in ' + JSON.stringify(res.data); + } else { + e.message += ' empty data'; + } + throw e; + } else { + return false; + } + } + return { balance, unconfirmed: 0, provider: link }; + } + + async getTransactionsBlockchain(scanData) { + const address = scanData.account.address.trim(); + const link = BlocksoftExternalSettings.getStatic('XRP_SCANNER_SERVER'); + let transactions = []; + let res = false; + try { + // https://xrpl.org/account_tx.html + const data = { + method: 'account_tx', + params: [ + { + account: address, + binary: false, + forward: false, + ledger_index_max: -1, + ledger_index_min: -1, + limit: 100 + } + ] + }; + res = await BlocksoftAxios.postWithoutBraking(link, data); + + if ( + res && + typeof res.data !== 'undefined' && + res.data && + typeof res.data.result !== 'undefined' && + res.data.result && + typeof res.data.result.transactions !== 'undefined' && + res.data.result.transactions + ) { + transactions = await this._unifyTransactions( + address, + res.data.result.transactions, + res.data.result.ledger_index_max + ); + } else { + return false; + } + } catch (e) { + if ( + e.message.indexOf('timed out') === -1 && + e.message.indexOf('account not found') === -1 + ) { + throw e; + } else { + return false; + } + } + return transactions; + } + + async _unifyTransactions(address, result, lastBlock) { + const transactions = []; + let tx; + for (tx of result) { + const transaction = await this._unifyPayment(address, tx, lastBlock); + if (transaction) { + transactions.push(transaction); + } + } + return transactions; + } + + /** + * @param {string} address + * @param {Object} transaction + * @param {bool} transaction.validated + * @param {string} transaction.tx.Account 'rEAgA9B8U8RCkwn6MprHqE1ZfXoeGQxz4P' + * @param {string} transaction.tx.Amount '2000000' + * @param {string} transaction.tx.Destination 'rDh2XemJY5WSNCPgXjhqnJt1PLGsTKbnix' + * @param {string} transaction.tx.DestinationTag + * @param {string} transaction.tx.Fee 127091 + * @param {string} transaction.tx.LastLedgerSequence 68101269 + * @param {string} transaction.tx.TransactionType 'Payment' + * @param {string} transaction.tx.date 691857661 + * @param {string} transaction.tx.hash '4D08316F83148C7C0EC955301E770A196B708EAF874BA2339260317BFDCE89E6' + * @param {string} transaction.tx.inLedger 68101268 + * @param {string} transaction.tx.ledger_index 68101268 + * @param {string} transaction.meta.delivered_amount '2000000' + * @param {string} transaction.meta.TransactionResult 'tesSUCCESS' + * @return {UnifiedTransaction} + * @private + **/ + async _unifyPayment(address, transaction, lastBlock = 0) { + if (transaction.tx.TransactionType !== 'Payment') { + return false; + } + let direction, amount; + if (transaction.tx.Account === address) { + direction = 'outcome'; + } else { + direction = 'income'; + } + + amount = transaction.tx.Amount; + if ( + direction === 'income' && + typeof transaction.meta.delivered_amount !== 'undefined' + ) { + amount = transaction.meta.delivered_amount; + } + + let blockConfirmations = lastBlock - transaction.tx.ledger_index; + if (blockConfirmations <= 0) blockConfirmations = 0; + let transactionStatus = 'new'; + if ( + transaction.validated === true || + transaction.meta.TransactionResult === 'tesSUCCESS' + ) { + if (blockConfirmations > 5) { + transactionStatus = 'success'; + } + } + const ledger = await this._getLedger(transaction.tx.ledger_index); + const blockTime = + ledger && typeof ledger.close_time !== 'undefined' && ledger.close_time + ? ledger.close_time + : transaction.tx.date; + const blockHash = + ledger && typeof ledger.ledger_hash !== 'undefined' && ledger.ledger_hash + ? ledger.ledger_hash + : transaction.tx.ledger_index; + const tx = { + transactionHash: transaction.tx.hash, + blockHash, + blockNumber: transaction.tx.ledger_index, + blockTime, + blockConfirmations, + transactionDirection: direction, + addressFrom: + transaction.tx.Account === address ? '' : transaction.tx.Account, + addressTo: + transaction.tx.Destination === address + ? '' + : transaction.tx.Destination, + addressAmount: BlocksoftUtils.toUnified(amount, 6), + transactionStatus, + transactionFee: BlocksoftUtils.toUnified(transaction.tx.Fee, 6) + }; + // https://blockchair.com/ripple/transaction/F56C6B0CA7BB6CD9AC74843E6C7BA605C7FFBB1F409E356CA235423F30F55F51?from=trustee + if (typeof transaction.tx.DestinationTag !== 'undefined') { + tx.transactionJson = { memo: transaction.tx.DestinationTag }; + } + return tx; + } + + async _getLedger(index) { + const now = new Date().getTime(); + await BlocksoftCryptoLog.log( + 'XrpScannerProcessor.DataScan._getLedger started ' + index + ); + const link = BlocksoftExternalSettings.getStatic('XRP_SCANNER_SERVER'); + let res = false; + if ( + typeof CACHE_BLOCK_DATA[index] === 'undefined' || + (now - CACHE_BLOCK_DATA[index].time > CACHE_VALID_TIME && + CACHE_BLOCK_DATA[index].data.transactionConfirmations < 100) + ) { + try { + const data = { + method: 'ledger_data', + params: [ + { + binary: false, + ledger_index: index + } + ] + }; + res = await BlocksoftAxios.postWithoutBraking(link, data); + if ( + res.data && + typeof res.data !== 'undefined' && + typeof res.data.result !== 'undefined' && + typeof res.data.result.ledger !== 'undefined' + ) { + await BlocksoftCryptoLog.log( + 'XrpScannerProcessor.DataScan._getLedger updated for index ' + + index + + ' ' + + JSON.stringify(res.data.result.ledger) + ); + const date = this._getDate(res.data.result.ledger.close_time_human); + const ledger = { + close_time: date, + ledger_hash: res.data.result.ledger.ledger_hash, + transactionConfirmations: Math.round( + (now - date * 1000) / (60 * 1000) + ) // minutes + }; + CACHE_BLOCK_DATA[index] = { + data: ledger, + time: now + }; + } + await XrpTmpDS.saveCache(CACHE_BLOCK_DATA); + } catch (e) { + if ( + e.message.indexOf('timed out') === -1 && + e.message.indexOf('account not found') === -1 + ) { + throw e; + } else { + res = false; + } + } + } + if (typeof CACHE_BLOCK_DATA[index] === 'undefined') { + return false; + } + return CACHE_BLOCK_DATA[index].data; + } + + // 2021-Dec-03 14:41:01.00 + // const tmp = new Date(time) not working in emulator + _getDate(time) { + time = time.split('.')[0]; + const months = { + Jan: 0, + Feb: 1, + Mar: 2, + Apr: 3, + May: 4, + Jun: 5, + Jul: 6, + Aug: 7, + Sep: 8, + Oct: 9, + Nov: 10, + Dec: 11 + }; + const tmp0 = time.split(' '); + const tmp1 = tmp0[0].split('-'); + const tmp2 = tmp0[1].split(':'); + const tmp = new Date( + tmp1[0], + months[tmp1[1]], + tmp1[2], + tmp2[0], + tmp2[1], + tmp2[2] + ); + return tmp.getTime(); + } +} diff --git a/crypto/blockchains/xrp/basic/XrpTxSendProvider.ts b/crypto/blockchains/xrp/basic/XrpTxSendProvider.ts index 17d3c413f..e49d49ff6 100644 --- a/crypto/blockchains/xrp/basic/XrpTxSendProvider.ts +++ b/crypto/blockchains/xrp/basic/XrpTxSendProvider.ts @@ -8,12 +8,22 @@ */ import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; import BlocksoftExternalSettings from '../../../common/BlocksoftExternalSettings'; +<<<<<<< Updated upstream import { BlocksoftBlockchainTypes } from '../../BlocksoftBlockchainTypes'; import { XrpTxUtils } from './XrpTxUtils'; import MarketingEvent from '../../../../app/services/Marketing/MarketingEvent'; import config from '../../../../app/config/config'; +======= + +import { XrpTxUtils } from './XrpTxUtils'; + +// TODO rewrite MarketingEvent file to use it here +// import MarketingEvent from '../../../../app/services/Marketing/MarketingEvent'; +import { BlocksoftBlockchainTypes } from '@lib/blockchains/BlocksoftBlockchainTypes'; +import config from '@constants/config'; +>>>>>>> Stashed changes const RippleAPI = require('ripple-lib').RippleAPI; @@ -76,7 +86,11 @@ export class XrpTxSendProvider { // @ts-ignore payment.destination.tag = int; } +<<<<<<< Updated upstream } catch (e) { +======= + } catch (e: any) { +>>>>>>> Stashed changes // @ts-ignore BlocksoftCryptoLog.log( 'XrpTransferProcessor._getPrepared memo error ' + e.message, @@ -180,6 +194,10 @@ export class XrpTxSendProvider { let result; try { const signed = this.signTx(data, privateData, txJson); +<<<<<<< Updated upstream +======= + // @ts-ignore +>>>>>>> Stashed changes BlocksoftCryptoLog.log('XrpTransferProcessor.sendTx signed', signed); result = await new Promise((resolve, reject) => { api @@ -188,7 +206,12 @@ export class XrpTxSendProvider { // https://xrpl.org/rippleapi-reference.html#submit api .submit(signed) +<<<<<<< Updated upstream .then((result: { resultCode: ''; resultMessage: '' }) => { +======= + // tslint:disable-next-line:no-shadowed-variable + .then((result: string) => { +>>>>>>> Stashed changes MarketingEvent.logOnlyRealTime( 'v20_rippled_success ' + data.addressFrom + @@ -235,7 +258,11 @@ export class XrpTxSendProvider { reject(error); }); }); +<<<<<<< Updated upstream } catch (e) { +======= + } catch (e: any) { +>>>>>>> Stashed changes if (config.debug.cryptoErrors) { console.log('XrpTransferProcessor.sendTx error ', e); } diff --git a/crypto/blockchains/xrp/basic/XrpTxUtils.ts b/crypto/blockchains/xrp/basic/XrpTxUtils.ts index 4005c72fd..d93b76109 100644 --- a/crypto/blockchains/xrp/basic/XrpTxUtils.ts +++ b/crypto/blockchains/xrp/basic/XrpTxUtils.ts @@ -1,9 +1,9 @@ export namespace XrpTxUtils { - export const amountPrep = function( current: string) : string { - const tmp = current.toString().split('.') - if (typeof tmp[1] !== 'undefined' && tmp[1].length > 6) { - current = tmp[0] + '.' + tmp[1].substr(0, 6) - } - return current.toString() + export const amountPrep = function (current: string): string { + const tmp = current.toString().split('.'); + if (typeof tmp[1] !== 'undefined' && tmp[1].length > 6) { + current = tmp[0] + '.' + tmp[1].substr(0, 6); } + return current.toString(); + }; } diff --git a/crypto/blockchains/xrp/stores/XrpTmpDS.js b/crypto/blockchains/xrp/stores/XrpTmpDS.js deleted file mode 100644 index dd0bea3c0..000000000 --- a/crypto/blockchains/xrp/stores/XrpTmpDS.js +++ /dev/null @@ -1,67 +0,0 @@ - -import Database from '@app/appstores/DataSource/Database'; - -const tableName = 'transactions_scanners_tmp' - -class XrpTmpDS { - /** - * @type {string} - * @private - */ - _currencyCode = 'XRP' - - _valKey = 'ledg' - - _isSaved = false - - async getCache() { - const res = await Database.query(` - SELECT id, tmp_key, tmp_val - FROM ${tableName} - WHERE currency_code='${this._currencyCode}' - AND tmp_key='${this._valKey}' - `) - let tmp = {} - const idsToRemove = [] - if (res.array) { - let row - let found = false - for (row of res.array) { - if (found) { - idsToRemove.push(row.id) - } else { - try { - tmp = JSON.parse(Database.unEscapeString(row.tmp_val)) - this._isSaved = true - found = true - } catch (e) { - idsToRemove.push(row.id) - } - } - } - if (idsToRemove.length > 0) { - await Database.query(`DELETE FROM ${tableName} WHERE id IN (${idsToRemove.join(',')})`) - } - } - return tmp - } - - async saveCache(value) { - const tmp = Database.escapeString(JSON.stringify(value)) - const now = new Date().toISOString() - if (this._isSaved) { - await Database.query(`UPDATE ${tableName} SET tmp_val='${tmp}' WHERE tmp_key='${this._valKey}' AND currency_code='${this._currencyCode}'`) - } else { - const prepared = [{ - currency_code: this._currencyCode, - tmp_key: this._valKey, - tmp_val: tmp, - created_at: now - }] - await Database.setTableName(tableName).setInsertData({ insertObjs: prepared }).insert() - this._isSaved = true - } - } -} - -export default new XrpTmpDS() diff --git a/crypto/blockchains/xrp/stores/XrpTmpDS.ts b/crypto/blockchains/xrp/stores/XrpTmpDS.ts new file mode 100644 index 000000000..25c885684 --- /dev/null +++ b/crypto/blockchains/xrp/stores/XrpTmpDS.ts @@ -0,0 +1,72 @@ +const tableName = 'transactions_scanners_tmp'; + +class XrpTmpDS { + /** + * @type {string} + * @private + */ + _currencyCode = 'XRP'; + + _valKey = 'ledg'; + + _isSaved = false; + + async getCache() { + const res = await Database.query(` + SELECT id, tmp_key, tmp_val + FROM ${tableName} + WHERE currency_code='${this._currencyCode}' + AND tmp_key='${this._valKey}' + `); + let tmp = {}; + const idsToRemove = []; + if (res.array) { + let row; + let found = false; + for (row of res.array) { + if (found) { + idsToRemove.push(row.id); + } else { + try { + tmp = JSON.parse(Database.unEscapeString(row.tmp_val)); + this._isSaved = true; + found = true; + } catch (e) { + idsToRemove.push(row.id); + } + } + } + if (idsToRemove.length > 0) { + await Database.query( + `DELETE FROM ${tableName} WHERE id IN (${idsToRemove.join(',')})` + ); + } + } + return tmp; + } + + async saveCache(value) { + const tmp = Database.escapeString(JSON.stringify(value)); + const now = new Date().toISOString(); + if (this._isSaved) { + await Database.query( + `UPDATE ${tableName} SET tmp_val='${tmp}' WHERE tmp_key='${this._valKey}' AND currency_code='${this._currencyCode}'` + ); + } else { + const prepared = [ + { + currency_code: this._currencyCode, + tmp_key: this._valKey, + tmp_val: tmp, + created_at: now + } + ]; + await Database.setTableName(tableName) + .setInsertData({ insertObjs: prepared }) + .insert(); + this._isSaved = true; + } + } +} + +export default new XrpTmpDS(); diff --git a/crypto/blockchains/xvg/basic/XvgFindAddressFunction.ts b/crypto/blockchains/xvg/basic/XvgFindAddressFunction.ts new file mode 100644 index 000000000..f94bce583 --- /dev/null +++ b/crypto/blockchains/xvg/basic/XvgFindAddressFunction.ts @@ -0,0 +1,98 @@ +import BlocksoftBN from '../../../common/BlocksoftBN'; + +interface TransactionIO { + address: string; + value: string; + direction: 'income' | 'outcome' | 'self'; +} + +interface TransactionSummary { + direction: 'income' | 'outcome' | 'self'; + inputAddresses: string[]; + outputAddresses: string[]; + value: string; +} + +export default async function XvgFindAddressFunction( + targetAddress: string, + txIO: { inputs: TransactionIO[]; outputs: TransactionIO[] } +): Promise { + const inputFromOthersBN = new BlocksoftBN(0); + const inputFromTargetBN = new BlocksoftBN(0); + const inputFromOthersAddresses: string[] = []; + const uniqueInputAddresses: Record = {}; + + let input: TransactionIO; + for (input of txIO.inputs) { + if (input.address) { + if (input.address === targetAddress) { + inputFromTargetBN.add(input.value); + } else { + if (typeof uniqueInputAddresses[input.address] === 'undefined') { + uniqueInputAddresses[input.address] = 1; + inputFromOthersAddresses.push(input.address); + } + inputFromOthersBN.add(input.value); + } + } + } + + const outputToOthersBN = new BlocksoftBN(0); + const outputToTargetBN = new BlocksoftBN(0); + const outputToOthersAddresses: string[] = []; + const uniqueOutputAddresses: Record = {}; + + let output: TransactionIO; + for (output of txIO.outputs) { + if (output.address) { + if (output.address === targetAddress) { + outputToTargetBN.add(output.value); + } else { + if (typeof uniqueOutputAddresses[output.address] === 'undefined') { + uniqueOutputAddresses[output.address] = 1; + outputToOthersAddresses.push(output.address); + } + outputToOthersBN.add(output.value); + } + } + } + + if (inputFromTargetBN.get() === '0') { + // target only in output + return { + direction: 'income', + inputAddresses: inputFromOthersAddresses, + outputAddresses: [], + value: outputToTargetBN.get() + }; + } else if (outputToTargetBN.get() === '0') { + // target only in input + return { + direction: 'outcome', + inputAddresses: [], + outputAddresses: outputToOthersAddresses, + value: + inputFromOthersBN.get() === '0' + ? outputToOthersBN.get() + : inputFromTargetBN.get() + }; + } else { + // both input and output + if (outputToOthersAddresses.length > 0) { + // there are other addresses + return { + direction: 'outcome', + inputAddresses: [], + outputAddresses: outputToOthersAddresses, + value: outputToOthersBN.get() + }; + } else { + return { + direction: 'self', + inputAddresses: [], + outputAddresses: [], + value: inputFromTargetBN.diff(outputToTargetBN).get() + }; + } + } +} diff --git a/crypto/blockchains/xvg/providers/XvgSendProvider.ts b/crypto/blockchains/xvg/providers/XvgSendProvider.ts index ab26a717b..94b6c649f 100644 --- a/crypto/blockchains/xvg/providers/XvgSendProvider.ts +++ b/crypto/blockchains/xvg/providers/XvgSendProvider.ts @@ -1,20 +1,31 @@ /** * @version 0.20 */ +<<<<<<< Updated upstream import { BlocksoftBlockchainTypes } from '../../BlocksoftBlockchainTypes'; import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; import BlocksoftAxios from '../../../common/BlocksoftAxios'; import BlocksoftExternalSettings from '../../../common/BlocksoftExternalSettings'; +======= +import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; +import BlocksoftAxios from '../../../common/BlocksoftAxios'; +import BlocksoftExternalSettings from '../../../common/BlocksoftExternalSettings'; +import { BlocksoftBlockchainTypes } from '@lib/blockchains/BlocksoftBlockchainTypes'; +>>>>>>> Stashed changes export default class XvgSendProvider implements BlocksoftBlockchainTypes.SendProvider { protected _settings: BlocksoftBlockchainTypes.CurrencySettings; +<<<<<<< Updated upstream constructor( settings: BlocksoftBlockchainTypes.CurrencySettings, serverCode: string ) { +======= + constructor(settings: BlocksoftBlockchainTypes.CurrencySettings) { +>>>>>>> Stashed changes this._settings = settings; } @@ -44,7 +55,11 @@ export default class XvgSendProvider ' ok ' + hex ); +<<<<<<< Updated upstream } catch (e) { +======= + } catch (e: any) { +>>>>>>> Stashed changes BlocksoftCryptoLog.log( this._settings.currencyCode + ' XvgSendProvider.sendTx ' + @@ -79,9 +94,17 @@ export default class XvgSendProvider throw e; } } +<<<<<<< Updated upstream + if (typeof res.data.txid === 'undefined' || !res.data.txid) { + throw new Error('SERVER_RESPONSE_NOT_CONNECTED'); + } +======= + // @ts-ignore if (typeof res.data.txid === 'undefined' || !res.data.txid) { throw new Error('SERVER_RESPONSE_NOT_CONNECTED'); } + // @ts-ignore +>>>>>>> Stashed changes return { transactionHash: res.data.txid, transactionJson: {} }; } } diff --git a/crypto/blockchains/xvg/providers/XvgUnspentsProvider.ts b/crypto/blockchains/xvg/providers/XvgUnspentsProvider.ts index 7cd6f34af..724b91dda 100644 --- a/crypto/blockchains/xvg/providers/XvgUnspentsProvider.ts +++ b/crypto/blockchains/xvg/providers/XvgUnspentsProvider.ts @@ -1,3 +1,4 @@ +<<<<<<< Updated upstream /** * @version 0.20 * https://api.vergecurrency.network/node/api/XVG/mainnet/address/DL5LtSf7wztH45VuYunL8oaQHtJbKLCHyw/txs/?unspent=true @@ -5,6 +6,11 @@ import { BlocksoftBlockchainTypes } from '../../BlocksoftBlockchainTypes'; import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; import BlocksoftAxios from '../../../common/BlocksoftAxios'; +======= +import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; +import BlocksoftAxios from '../../../common/BlocksoftAxios'; +import { BlocksoftBlockchainTypes } from '@lib/blockchains/BlocksoftBlockchainTypes'; +>>>>>>> Stashed changes export default class XvgUnspentsProvider implements BlocksoftBlockchainTypes.UnspentsProvider @@ -13,17 +19,24 @@ export default class XvgUnspentsProvider protected _settings: BlocksoftBlockchainTypes.CurrencySettings; +<<<<<<< Updated upstream constructor( settings: BlocksoftBlockchainTypes.CurrencySettings, serverCode: string ) { +======= + constructor(settings: BlocksoftBlockchainTypes.CurrencySettings) { +>>>>>>> Stashed changes this._settings = settings; } async getUnspents( address: string ): Promise { +<<<<<<< Updated upstream // @ts-ignore +======= +>>>>>>> Stashed changes BlocksoftCryptoLog.log( this._settings.currencyCode + ' XvgUnspentsProvider.getUnspents started', address @@ -49,6 +62,7 @@ export default class XvgUnspentsProvider return []; } const sortedUnspents = []; +<<<<<<< Updated upstream /** * https://api.vergecurrency.network/node/api/XVG/mainnet/address/DL5LtSf7wztH45VuYunL8oaQHtJbKLCHyw/txs/?unspent=true * @param {*} res.data[] @@ -66,6 +80,8 @@ export default class XvgUnspentsProvider * @param {string} res.data[].value 91523000 * @param {string} res.data[].confirmations -1 */ +======= +>>>>>>> Stashed changes const already = {}; let unspent; for (unspent of res.data) { diff --git a/crypto/blockchains/xvg/stores/XvgTmpDS.js b/crypto/blockchains/xvg/stores/XvgTmpDS.js deleted file mode 100644 index fa3465527..000000000 --- a/crypto/blockchains/xvg/stores/XvgTmpDS.js +++ /dev/null @@ -1,49 +0,0 @@ - -import Database from '@app/appstores/DataSource/Database'; - -const tableName = ' transactions_scanners_tmp' - -class XvgTmpDS { - /** - * @type {string} - * @private - */ - _currencyCode = 'XVG' - - async getCache(address) { - const res = await Database.query(` - SELECT tmp_key, tmp_sub_key, tmp_val - FROM ${tableName} - WHERE currency_code='${this._currencyCode}' - AND address='${address}' - AND (tmp_sub_key='coins' OR tmp_sub_key='data') - `) - const tmp = {} - if (res.array) { - let row - for (row of res.array) { - let val = 1 - if (row.tmp_sub_key !== 'data') { - val = JSON.parse(Database.unEscapeString(row.tmp_val)) - } - tmp[row.tmp_key + '_' + row.tmp_sub_key] = val - } - } - return tmp - } - - async saveCache(address, key, subKey, value) { - const now = new Date().toISOString() - const prepared = [{ - currency_code : this._currencyCode, - address : address, - tmp_key : key, - tmp_sub_key : subKey, - tmp_val : Database.escapeString(JSON.stringify(value)), - created_at : now - }] - await Database.setTableName(tableName).setInsertData({insertObjs : prepared}).insert() - } -} - -export default new XvgTmpDS() diff --git a/crypto/blockchains/xvg/stores/XvgTmpDS.ts b/crypto/blockchains/xvg/stores/XvgTmpDS.ts new file mode 100644 index 000000000..acdf7cb3f --- /dev/null +++ b/crypto/blockchains/xvg/stores/XvgTmpDS.ts @@ -0,0 +1,48 @@ +import Database from '@app/appstores/DataSource/Database'; + +const tableName = ' transactions_scanners_tmp'; + +class XvgTmpDS { + _currencyCode = 'XVG'; + + async getCache(address) { + const res = await Database.query(` + SELECT tmp_key, tmp_sub_key, tmp_val + FROM ${tableName} + WHERE currency_code='${this._currencyCode}' + AND address='${address}' + AND (tmp_sub_key='coins' OR tmp_sub_key='data') + `); + const tmp = {}; + if (res.array) { + let row; + for (row of res.array) { + let val = 1; + if (row.tmp_sub_key !== 'data') { + val = JSON.parse(Database.unEscapeString(row.tmp_val)); + } + tmp[row.tmp_key + '_' + row.tmp_sub_key] = val; + } + } + return tmp; + } + + async saveCache(address, key, subKey, value) { + const now = new Date().toISOString(); + const prepared = [ + { + currency_code: this._currencyCode, + address: address, + tmp_key: key, + tmp_sub_key: subKey, + tmp_val: Database.escapeString(JSON.stringify(value)), + created_at: now + } + ]; + await Database.setTableName(tableName) + .setInsertData({ insertObjs: prepared }) + .insert(); + } +} + +export default new XvgTmpDS(); diff --git a/crypto/common/BlocksoftUtils.js b/crypto/common/BlocksoftUtils.js index 108142591..38b3de303 100644 --- a/crypto/common/BlocksoftUtils.js +++ b/crypto/common/BlocksoftUtils.js @@ -1,375 +1,394 @@ -import { BigNumber } from 'bignumber.js' -import { hexToBn, bnToHex } from '../blockchains/eth/ext/estimateGas/util' -import Log from '../../app/services/Log/Log' - -import BigIntXmr from '@crypto/blockchains/xmr/ext/vendor/biginteger' -const Web3 = require('web3') - -class BlocksoftUtils { - - static cutZeros(val) { - const tmp = val.toString().split('.') - if (typeof tmp[1] === 'undefined' || !tmp[1]) return tmp[0] - - let firstNonZero = -1 - let i = tmp[1].length - 1 - do { - const char = tmp[1][i] - if (char !== '0') { - firstNonZero = i - } - i-- - } while (firstNonZero === -1 && i >= 0) - const last = tmp[1].substr(0, firstNonZero + 1) - if (!last || last === '') return tmp[0] - return tmp[0] + '.' + last - } - - static round(val) { - const tmp = val.toString().split('.') - return tmp[0].replace(' ', '') - } - - // // console.log('added', BlocksoftUtils.add(967282001717650,87696220292905380)) - static add(val1, val2) { - // console.log('BlocksoftUtils add ', JSON.stringify({ val1, val2})) - let res = 0 - if (typeof val1 === 'undefined') { - res = val2 || '' - } else if (typeof val2 === 'undefined' || val2 === 0 || val2 === '0' || !val2) { - res = val1 - } else if (typeof val1.innerBN !== 'undefined') { - if (typeof val2.innerBN !== 'undefined') { - res = val1.innerBN.plus(val2.innerBN).toString() - } else { - res = val1.innerBN.plus(BigNumber(val2)).toString() - } - } else if (!val2 || !(val2 * 1 > 0)) { - res = val1 - } else { - const str = val1.toString() + val2.toString() - if (str.indexOf('.') !== -1 || str.indexOf(',') !== -1) { - res = val1 * 1 + val2 * 1 - } else { - try { - res = BigNumber(val1).plus(BigNumber(val2)).toString() - } catch (e) { - res = val1 * 1 + val2 * 1 - } - } - } - // console.log('BlocksoftUtils added ', JSON.stringify({ val1, val2, res})) - return BlocksoftUtils.fromENumber(res) - } - - static mul(val1, val2) { - // console.log('BlocksoftUtils mul ', JSON.stringify({ val1, val2 })) - if (typeof val1 === 'undefined') { - return '' - } - if (typeof val2 === 'undefined') { - return val1 - } - if (val2 === '1' || val2 === 1) { - return val1 - } - if (typeof val1.innerBN !== 'undefined') { - if (typeof val2.innerBN !== 'undefined') { - return val1.innerBN.times(val2.innerBN).toString() - } else { - return val1.innerBN.times(BigNumber(val2)).toString() - } - } - const str = val1.toString() + val2.toString() - let res = 0 - if (str.indexOf('.') !== -1 || str.indexOf(',') !== -1) { - res = val1 * val2 - } else { - try { - res = BigNumber(val1).times(BigNumber(val2)).toString() - } catch (e) { - res = val1 * val2 - } - } - return BlocksoftUtils.fromENumber(res) - } - - static div(val1, val2) { - // console.log('BlocksoftUtils div ', JSON.stringify({ val1, val2 })) - if (typeof val1 === 'undefined') { - return '' - } - if (typeof val2 === 'undefined') { - return val1 - } - if (val2 === '1' || val2 === 1) { - return val1 - } - if (typeof val1.innerBN !== 'undefined') { - if (typeof val2.innerBN !== 'undefined') { - return val1.innerBN.dividedBy(val2.innerBN).toString() - } else { - return val1.innerBN.dividedBy(BigNumber(val2 + '')).toString() - } - } - const str = val1.toString() + val2.toString() - let res = 0 - if (str.indexOf('.') !== -1 || str.indexOf(',') !== -1) { - res = val1 / val2 - } else { - let addedZeros = false - if (val1.length <= val2.length + 2) { - val1 += '00000000' - addedZeros = true - } - try { - res = BigNumber(val1).dividedBy(BigNumber(val2)).toString() - if (addedZeros) { - res = res / 100000000 - } - } catch (e) { - res = val1 / val2 - } - } - return BlocksoftUtils.fromENumber(res) - } - - static diff(val1, val2) { - // console.log('BlocksoftUtils diff ', JSON.stringify({ val1, val2 })) - if (typeof val1 === 'undefined') { - return val2 || '' - } - if (typeof val2 === 'undefined') { - return val1 - } - if (!val2) { - return val1 - } - if (!val1) { - return -1 * val2 - } - if (typeof val1.innerBN !== 'undefined') { - if (typeof val2.innerBN !== 'undefined') { - return val1.innerBN.minus(val2.innerBN).toString() - } else { - return val1.innerBN.minus(BigNumber(val2 + '')).toString() - } - } - const str = val1.toString() + val2.toString() - let res = 0 - if (str.indexOf('.') !== -1 || str.indexOf(',') !== -1) { - res = val1 - val2 - } else { - try { - res = BigNumber(val1).minus(BigNumber(val2 + '')).toString() - } catch (e) { - res = val1 - val2 - } - } - return BlocksoftUtils.fromENumber(res) - } - - static fromENumber(val) { - // console.log('BlocksoftUtils fromE ', JSON.stringify(val)) - if (val === null || typeof (val) === 'undefined' || !val) { - return 0 - } - val = val.toString().toLowerCase() - if (val.indexOf('0x') === 0) { - return BigIntXmr.BigInteger(val).toString() - } - if (val.indexOf('e') === -1) { - return val - } - const parts = val.split('e') - const number = parts[1].substr(0, 1) - const power = parts[1].substr(1) - const first = parts[0].split('.') - if (number === '+') { - return this.fromUnified(parts[0], power) - } else if (typeof power !== 'undefined' && power*1 > 0) { - return '0.' + ('0'.repeat(power - 1)) + first[0] + (typeof first[1] !== 'undefined' ? first[1] : '') - } else { - return '0.0' - } - } - - static toSatoshi(val) { - return this.fromUnified(val, 8) - } - - static toBtc(val) { - return this.toUnified(val, 8) - } - - static toUnified(val, decimals = 8) { - if (typeof val === 'undefined' || val === 'undefined' || !val) { - return 0 - } - if (typeof val === 'object') { - val = val.toString() - } - if (typeof val === 'number') { - val += '' - } - // noinspection JSUnresolvedVariable,JSCheckFunctionSignatures - let added = '' - const till = 18 - decimals - if (till < 0) { - throw new Error('toUnified till is less then 0, decimals = ' + decimals) - } - for (let i = 0; i < till; i++) { - added += '0' - } - const parts = val.split('.') - // noinspection JSUnresolvedVariable - const tmp = parts[0] + added - const res = Web3.utils.fromWei(tmp, 'ether') - // console.log('BlocksoftUtils toUnified ', JSON.stringify(val), JSON.stringify(res)) - return res - } - - static fromUnified(val, decimals = 8) { - // console.log('BlocksoftUtils fromUnified ', JSON.stringify(val)) - if (typeof val === 'undefined') return 0 - val = val.toString() - const parts = val.split('.') - let number = parts[0] - if (!parts[1] || !parts[1].length) return (number + '0'.repeat(decimals)) - - // fill the letters after point - const letters = parts[1].split('') - let needToFill = decimals - for (let i = 0, ic = letters.length; i < ic; i++) { - needToFill-- - number += letters[i] - if (needToFill === 0) break - } - for (let i = 0; i < needToFill; i++) { - number += '0' - } - - // cut first 0 - let cutted = '' - let started = false - for (let i = 0, ic = number.length; i < ic; i++) { - if (!started && number[i] === '0') continue - cutted += number[i] - started = true - } - - return cutted || 0 - } - - static toWei(val, from = 'ether') { - // console.log('BlocksoftUtils toWei ', JSON.stringify(val)) - if (typeof val === 'undefined') { - throw new Error('toWei val is undefined') - } - if (typeof val === 'number') { - val += '' - } - const parts = val.toString().split('.') - if (typeof parts[1] === 'undefined' || parts[1] === '' || !parts[1]) { - // noinspection JSUnresolvedVariable - return Web3.utils.toWei(val, from) - } - - let decimals = 18 - if (from === 'gwei') { - decimals = 9 - } - const newVal = parts[0] + '.' + parts[1].substring(0, decimals) - return Web3.utils.toWei(newVal, from) - } - - static toGwei(val) { - // console.log('BlocksoftUtils toGwei ', JSON.stringify(val)) - if (typeof val === 'number') { - val += '' - } - - // noinspection JSUnresolvedVariable - let newVal = 0 - try { - // noinspection JSUnresolvedVariable,JSCheckFunctionSignatures - const tmp = val.split('.') - newVal = Web3.utils.fromWei(tmp[0], 'gwei') - } catch (e) { - e.message = JSON.stringify(val) + ' ' + e.message - Log.err('BlocksoftUtils.toGwei error ' + e.message) - } - return newVal - } - - static toEther(val) { - // console.log('BlocksoftUtils toEth ', JSON.stringify(val)) - if (typeof val === 'number') { - val += '' - } - - // noinspection JSUnresolvedVariable - let newVal - try { - // noinspection JSUnresolvedVariable - newVal = Web3.utils.fromWei(val, 'ether') - } catch (e) { - e.message = JSON.stringify(val) + ' ' + e.message - } - return newVal - } - - static toDate(timeStamp, multiply = true) { - if (timeStamp.toString().indexOf('T') !== -1) { - return timeStamp - } else if (timeStamp && timeStamp > 0) { - if (multiply) { - timeStamp = timeStamp * 1000 - } - return (new Date(timeStamp)).toISOString() - } else { - return new Date().toISOString() - } - } - - static hexToUtf(hex) { - return Web3.utils.hexToUtf8(hex) - } - - static utfToHex(str) { - return Web3.utils.utf8ToHex(str) - } - - - static hexToDecimal(hex) { - if (hex.toString().indexOf('0x') === 0) { - return Web3.utils.hexToNumber(hex) - } - return hex - } - - static hexToDecimalWalletConnect(hex) { - return hexToBn(hex).toString() - } - - static decimalToHexWalletConnect(decimal) { - return bnToHex(decimal) - } - - static decimalToHex(decimal, len = 0) { - let str = Web3.utils.toHex(decimal).substr(2) - if (len > 0) { - if (len < str.length) { - throw new Error('hex ' + decimal + ' => ' + str + ' is longer then ' + len + ' and equal ' + str.length ) - } - str = '0'.repeat(len - str.length) + str - } - return str - } - - static hexToDecimalBigger(hex) { - return BigIntXmr.BigInteger(hex).toString() - } -} - -export default BlocksoftUtils +import { BigNumber } from 'bignumber.js'; +import { hexToBn, bnToHex } from '../blockchains/eth/ext/estimateGas/util'; +import Log from '../../app/services/Log/Log'; + +import BigIntXmr from '@crypto/blockchains/xmr/ext/vendor/biginteger'; +const Web3 = require('web3'); + +class BlocksoftUtils { + static cutZeros(val) { + const tmp = val.toString().split('.'); + if (typeof tmp[1] === 'undefined' || !tmp[1]) return tmp[0]; + + let firstNonZero = -1; + let i = tmp[1].length - 1; + do { + const char = tmp[1][i]; + if (char !== '0') { + firstNonZero = i; + } + i--; + } while (firstNonZero === -1 && i >= 0); + const last = tmp[1].substr(0, firstNonZero + 1); + if (!last || last === '') return tmp[0]; + return tmp[0] + '.' + last; + } + + static round(val) { + const tmp = val.toString().split('.'); + return tmp[0].replace(' ', ''); + } + + // // console.log('added', BlocksoftUtils.add(967282001717650,87696220292905380)) + static add(val1, val2) { + // console.log('BlocksoftUtils add ', JSON.stringify({ val1, val2})) + let res = 0; + if (typeof val1 === 'undefined') { + res = val2 || ''; + } else if ( + typeof val2 === 'undefined' || + val2 === 0 || + val2 === '0' || + !val2 + ) { + res = val1; + } else if (typeof val1.innerBN !== 'undefined') { + if (typeof val2.innerBN !== 'undefined') { + res = val1.innerBN.plus(val2.innerBN).toString(); + } else { + res = val1.innerBN.plus(BigNumber(val2)).toString(); + } + } else if (!val2 || !(val2 * 1 > 0)) { + res = val1; + } else { + const str = val1.toString() + val2.toString(); + if (str.indexOf('.') !== -1 || str.indexOf(',') !== -1) { + res = val1 * 1 + val2 * 1; + } else { + try { + res = BigNumber(val1).plus(BigNumber(val2)).toString(); + } catch (e) { + res = val1 * 1 + val2 * 1; + } + } + } + // console.log('BlocksoftUtils added ', JSON.stringify({ val1, val2, res})) + return BlocksoftUtils.fromENumber(res); + } + + static mul(val1, val2) { + // console.log('BlocksoftUtils mul ', JSON.stringify({ val1, val2 })) + if (typeof val1 === 'undefined') { + return ''; + } + if (typeof val2 === 'undefined') { + return val1; + } + if (val2 === '1' || val2 === 1) { + return val1; + } + if (typeof val1.innerBN !== 'undefined') { + if (typeof val2.innerBN !== 'undefined') { + return val1.innerBN.times(val2.innerBN).toString(); + } else { + return val1.innerBN.times(BigNumber(val2)).toString(); + } + } + const str = val1.toString() + val2.toString(); + let res = 0; + if (str.indexOf('.') !== -1 || str.indexOf(',') !== -1) { + res = val1 * val2; + } else { + try { + res = BigNumber(val1).times(BigNumber(val2)).toString(); + } catch (e) { + res = val1 * val2; + } + } + return BlocksoftUtils.fromENumber(res); + } + + static div(val1, val2) { + // console.log('BlocksoftUtils div ', JSON.stringify({ val1, val2 })) + if (typeof val1 === 'undefined') { + return ''; + } + if (typeof val2 === 'undefined') { + return val1; + } + if (val2 === '1' || val2 === 1) { + return val1; + } + if (typeof val1.innerBN !== 'undefined') { + if (typeof val2.innerBN !== 'undefined') { + return val1.innerBN.dividedBy(val2.innerBN).toString(); + } else { + return val1.innerBN.dividedBy(BigNumber(val2 + '')).toString(); + } + } + const str = val1.toString() + val2.toString(); + let res = 0; + if (str.indexOf('.') !== -1 || str.indexOf(',') !== -1) { + res = val1 / val2; + } else { + let addedZeros = false; + if (val1.length <= val2.length + 2) { + val1 += '00000000'; + addedZeros = true; + } + try { + res = BigNumber(val1).dividedBy(BigNumber(val2)).toString(); + if (addedZeros) { + res = res / 100000000; + } + } catch (e) { + res = val1 / val2; + } + } + return BlocksoftUtils.fromENumber(res); + } + + static diff(val1, val2) { + // console.log('BlocksoftUtils diff ', JSON.stringify({ val1, val2 })) + if (typeof val1 === 'undefined') { + return val2 || ''; + } + if (typeof val2 === 'undefined') { + return val1; + } + if (!val2) { + return val1; + } + if (!val1) { + return -1 * val2; + } + if (typeof val1.innerBN !== 'undefined') { + if (typeof val2.innerBN !== 'undefined') { + return val1.innerBN.minus(val2.innerBN).toString(); + } else { + return val1.innerBN.minus(BigNumber(val2 + '')).toString(); + } + } + const str = val1.toString() + val2.toString(); + let res = 0; + if (str.indexOf('.') !== -1 || str.indexOf(',') !== -1) { + res = val1 - val2; + } else { + try { + res = BigNumber(val1) + .minus(BigNumber(val2 + '')) + .toString(); + } catch (e) { + res = val1 - val2; + } + } + return BlocksoftUtils.fromENumber(res); + } + + static fromENumber(val) { + // console.log('BlocksoftUtils fromE ', JSON.stringify(val)) + if (val === null || typeof val === 'undefined' || !val) { + return 0; + } + val = val.toString().toLowerCase(); + if (val.indexOf('0x') === 0) { + return BigIntXmr.BigInteger(val).toString(); + } + if (val.indexOf('e') === -1) { + return val; + } + const parts = val.split('e'); + const number = parts[1].substr(0, 1); + const power = parts[1].substr(1); + const first = parts[0].split('.'); + if (number === '+') { + return this.fromUnified(parts[0], power); + } else if (typeof power !== 'undefined' && power * 1 > 0) { + return ( + '0.' + + '0'.repeat(power - 1) + + first[0] + + (typeof first[1] !== 'undefined' ? first[1] : '') + ); + } else { + return '0.0'; + } + } + + static toSatoshi(val) { + return this.fromUnified(val, 8); + } + + static toBtc(val) { + return this.toUnified(val, 8); + } + + static toUnified(val, decimals = 8) { + if (typeof val === 'undefined' || val === 'undefined' || !val) { + return 0; + } + if (typeof val === 'object') { + val = val.toString(); + } + if (typeof val === 'number') { + val += ''; + } + // noinspection JSUnresolvedVariable,JSCheckFunctionSignatures + let added = ''; + const till = 18 - decimals; + if (till < 0) { + throw new Error('toUnified till is less then 0, decimals = ' + decimals); + } + for (let i = 0; i < till; i++) { + added += '0'; + } + const parts = val.split('.'); + // noinspection JSUnresolvedVariable + const tmp = parts[0] + added; + const res = Web3.utils.fromWei(tmp, 'ether'); + // console.log('BlocksoftUtils toUnified ', JSON.stringify(val), JSON.stringify(res)) + return res; + } + + static fromUnified(val, decimals = 8) { + // console.log('BlocksoftUtils fromUnified ', JSON.stringify(val)) + if (typeof val === 'undefined') return 0; + val = val.toString(); + const parts = val.split('.'); + let number = parts[0]; + if (!parts[1] || !parts[1].length) return number + '0'.repeat(decimals); + + // fill the letters after point + const letters = parts[1].split(''); + let needToFill = decimals; + for (let i = 0, ic = letters.length; i < ic; i++) { + needToFill--; + number += letters[i]; + if (needToFill === 0) break; + } + for (let i = 0; i < needToFill; i++) { + number += '0'; + } + + // cut first 0 + let cutted = ''; + let started = false; + for (let i = 0, ic = number.length; i < ic; i++) { + if (!started && number[i] === '0') continue; + cutted += number[i]; + started = true; + } + + return cutted || 0; + } + + static toWei(val, from = 'ether') { + // console.log('BlocksoftUtils toWei ', JSON.stringify(val)) + if (typeof val === 'undefined') { + throw new Error('toWei val is undefined'); + } + if (typeof val === 'number') { + val += ''; + } + const parts = val.toString().split('.'); + if (typeof parts[1] === 'undefined' || parts[1] === '' || !parts[1]) { + // noinspection JSUnresolvedVariable + return Web3.utils.toWei(val, from); + } + + let decimals = 18; + if (from === 'gwei') { + decimals = 9; + } + const newVal = parts[0] + '.' + parts[1].substring(0, decimals); + return Web3.utils.toWei(newVal, from); + } + + static toGwei(val) { + // console.log('BlocksoftUtils toGwei ', JSON.stringify(val)) + if (typeof val === 'number') { + val += ''; + } + + // noinspection JSUnresolvedVariable + let newVal = 0; + try { + // noinspection JSUnresolvedVariable,JSCheckFunctionSignatures + const tmp = val.split('.'); + newVal = Web3.utils.fromWei(tmp[0], 'gwei'); + } catch (e) { + e.message = JSON.stringify(val) + ' ' + e.message; + Log.err('BlocksoftUtils.toGwei error ' + e.message); + } + return newVal; + } + + static toEther(val) { + // console.log('BlocksoftUtils toEth ', JSON.stringify(val)) + if (typeof val === 'number') { + val += ''; + } + + // noinspection JSUnresolvedVariable + let newVal; + try { + // noinspection JSUnresolvedVariable + newVal = Web3.utils.fromWei(val, 'ether'); + } catch (e) { + e.message = JSON.stringify(val) + ' ' + e.message; + } + return newVal; + } + + static toDate(timeStamp, multiply = true) { + if (timeStamp.toString().indexOf('T') !== -1) { + return timeStamp; + } else if (timeStamp && timeStamp > 0) { + if (multiply) { + timeStamp = timeStamp * 1000; + } + return new Date(timeStamp).toISOString(); + } else { + return new Date().toISOString(); + } + } + + static hexToUtf(hex) { + return Web3.utils.hexToUtf8(hex); + } + + static utfToHex(str) { + return Web3.utils.utf8ToHex(str); + } + + static hexToDecimal(hex) { + if (hex.toString().indexOf('0x') === 0) { + return Web3.utils.hexToNumber(hex); + } + return hex; + } + + static hexToDecimalWalletConnect(hex) { + return hexToBn(hex).toString(); + } + + static decimalToHexWalletConnect(decimal) { + return bnToHex(decimal); + } + + static decimalToHex(decimal, len = 0) { + let str = Web3.utils.toHex(decimal).substr(2); + if (len > 0) { + if (len < str.length) { + throw new Error( + 'hex ' + + decimal + + ' => ' + + str + + ' is longer then ' + + len + + ' and equal ' + + str.length + ); + } + str = '0'.repeat(len - str.length) + str; + } + return str; + } + + static hexToDecimalBigger(hex) { + return BigIntXmr.BigInteger(hex).toString(); + } +} + +export default BlocksoftUtils; diff --git a/crypto/common/ext/networks-constants.js b/crypto/common/ext/networks-constants.js index 58860f9cf..a4e243f0e 100644 --- a/crypto/common/ext/networks-constants.js +++ b/crypto/common/ext/networks-constants.js @@ -1,59 +1,58 @@ // https://github.com/iancoleman/bip39/blob/0a23f51792722f094328d695242556c4c0195a8b/src/js/bitcoinjs-extensions.js -const bitcoin = require('bitcoinjs-lib') +const bitcoin = require('bitcoinjs-lib'); module.exports = { - 'mainnet': { - network: bitcoin.networks.bitcoin, - langPrefix: 'btc' + mainnet: { + network: bitcoin.networks.bitcoin, + langPrefix: 'btc' + }, + testnet: { + network: bitcoin.networks.testnet, + langPrefix: 'btc' + }, + litecoin: { + network: { + bech32: 'ltc', + messagePrefix: '\x19Litecoin Signed Message:\n', + pubKeyHash: 0x30, // change to 0x6f + scriptHash: 0x32, + wif: 0xb0, + bip32: { + public: 0x019da462, + private: 0x019d9cfe + } }, - 'testnet': { - network: bitcoin.networks.testnet, - langPrefix: 'btc' + langPrefix: 'ltc' + }, + dogecoin: { + network: { + messagePrefix: '\x19Dogecoin Signed Message:\n', + bip32: { + public: 0x02facafd, + private: 0x02fac398 + }, + pubKeyHash: 0x1e, + scriptHash: 0x16, + wif: 0x9e }, - 'litecoin': { - network: { - bech32: 'ltc', - messagePrefix: '\x19Litecoin Signed Message:\n', - pubKeyHash: 0x30, // change to 0x6f - scriptHash: 0x32, - wif: 0xb0, - bip32: { - public: 0x019da462, - private: 0x019d9cfe - } - }, - langPrefix: 'ltc' - }, - 'dogecoin': { - network: { - messagePrefix: '\x19Dogecoin Signed Message:\n', - bip32: { - public: 0x02facafd, - private: 0x02fac398 - }, - pubKeyHash: 0x1e, - scriptHash: 0x16, - wif: 0x9e - }, - langPrefix: 'ltc' + langPrefix: 'ltc' + }, + verge: { + network: { + messagePrefix: '\x18VERGE Signed Message:\n', + bip32: { + public: 0x0488b21e, + private: 0x0488ade4 + }, + pubKeyHash: 0x1e, + scriptHash: 0x21, + wif: 0x9e }, - 'verge': { - network: { - messagePrefix: '\x18VERGE Signed Message:\n', - bip32: { - public: 0x0488b21e, - private: 0x0488ade4 - }, - pubKeyHash: 0x1e, - scriptHash: 0x21, - wif: 0x9e - }, - langPrefix: 'ltc' - }, - 'bitcoincash': { - network: { - - /* messagePrefix: 'unused', + langPrefix: 'ltc' + }, + bitcoincash: { + network: { + /* messagePrefix: 'unused', bip32: { public: 0x0488b21e, private: 0x0488ade4 @@ -62,43 +61,43 @@ module.exports = { scriptHash: 0x05, wif: 0x80 */ - messagePrefix: '\u0018Bitcoin Signed Message:\n', - bech32: 'bc', - bip32: { public: 76067358, private: 76066276 }, - pubKeyHash: 0, - scriptHash: 5, - wif: 128, - BTCFork : 'BCH' - }, - langPrefix: 'bch' + messagePrefix: '\u0018Bitcoin Signed Message:\n', + bech32: 'bc', + bip32: { public: 76067358, private: 76066276 }, + pubKeyHash: 0, + scriptHash: 5, + wif: 128, + BTCFork: 'BCH' }, - 'bitcoinsv': { - network: { - messagePrefix: 'unused', - bip32: { - public: 0x0488b21e, - private: 0x0488ade4 - }, - pubKeyHash: 0x00, - scriptHash: 0x05, - wif: 0x80, - BTCFork : 'BCH' - }, - langPrefix: 'bch' + langPrefix: 'bch' + }, + bitcoinsv: { + network: { + messagePrefix: 'unused', + bip32: { + public: 0x0488b21e, + private: 0x0488ade4 + }, + pubKeyHash: 0x00, + scriptHash: 0x05, + wif: 0x80, + BTCFork: 'BCH' }, - 'bitcoingold': { - network: { - bech32: 'btg', - messagePrefix: '\x1DBitcoin Gold Signed Message:\n', - bip32: { - public: 0x0488b21e, - private: 0x0488ade4 - }, - pubKeyHash: 38, - scriptHash: 23, - wif: 128, - BTCFork : 'BTG' - }, - langPrefix: 'ltc' - } -} + langPrefix: 'bch' + }, + bitcoingold: { + network: { + bech32: 'btg', + messagePrefix: '\x1DBitcoin Gold Signed Message:\n', + bip32: { + public: 0x0488b21e, + private: 0x0488ade4 + }, + pubKeyHash: 38, + scriptHash: 23, + wif: 128, + BTCFork: 'BTG' + }, + langPrefix: 'ltc' + } +}; diff --git a/package.json b/package.json index 9ba4f6244..8d1e3f0d1 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "dependencies": { "@babel/preset-env": "^7.21.5", "@morrowdigital/watermelondb-expo-plugin": "^2.1.2", + "@mymonero/mymonero-paymentid-utils": "^3.0.0", "@neverdull-agency/expo-unlimited-secure-store": "^1.0.10", "@nozbe/watermelondb": "^0.26.0", "@nozbe/with-observables": "^1.6.0", @@ -40,6 +41,7 @@ "@types/jest": "^29.5.1", "@waves/ts-lib-crypto": "^1.4.4-beta.1", "axios": "^1.3.4", + "base-x": "^4.0.0", "bech32": "^2.0.0", "bignumber.js": "^9.1.1", "bip32": "^4.0.0", @@ -48,6 +50,7 @@ "bn.js": "^5.2.1", "bs58check": "^3.0.1", "buffer": "^6.0.3", + "buffer-crc32": "^0.2.13", "create-hash": "^1.2.0", "elliptic": "^6.5.4", "expo": "~48.0.18", @@ -81,6 +84,7 @@ "react-native-gesture-handler": "~2.9.0", "react-native-graph": "^1.0.1", "react-native-modal": "^13.0.1", + "react-native-mymonero-core": "^0.3.1", "react-native-pager-view": "6.1.2", "react-native-popover-view": "^5.1.7", "react-native-reanimated": "~2.14.4", @@ -91,9 +95,11 @@ "react-native-tab-view": "^3.5.1", "react-native-view-shot": "3.5.0", "react-native-walkthrough-tooltip": "^1.5.0", - "tiny-secp256k1": "^2.2.3", + "ripple-lib": "^1.10.1", "use-context-selector": "^1.4.1", - "web3": "^4.0.3" + "web3": "^4.0.3", + "web3-utils": "^4.0.3", + "tiny-secp256k1": "^2.2.3" }, "devDependencies": { "@babel/core": "^7.20.0", diff --git a/src/constants/config.ts b/src/constants/config.ts index 53a25d460..4c5b9dc2d 100644 --- a/src/constants/config.ts +++ b/src/constants/config.ts @@ -6,7 +6,8 @@ const envs = { EXPLORER_API_URL: 'https://explorer-api.ambrosus.io', env: 'prod', debug: { - appBuildVersion: '1.0.0' + appBuildVersion: '1.0.0', + cryptoErrors: true } }, stage: { @@ -15,7 +16,8 @@ const envs = { EXPLORER_API_URL: 'https://explorer-api.ambrosus-test.io', env: 'stage', debug: { - appBuildVersion: '1.0.0' + appBuildVersion: '1.0.0', + cryptoErrors: true } } }; diff --git a/src/lib/AirDAOKeys.ts b/src/lib/AirDAOKeys.ts index 73e13462a..ecbcc400e 100644 --- a/src/lib/AirDAOKeys.ts +++ b/src/lib/AirDAOKeys.ts @@ -1,6 +1,10 @@ import * as SecureStore from 'expo-secure-store'; import KeysUtills from '@utils/keys'; import config from '@constants/config'; +import BlocksoftDict from '@crypto/common/BlocksoftDict'; +import { bip32 } from 'bitcoinjs-lib'; +import BlocksoftDispatcher from '@lib/BlocksoftDispatcher'; +const networksConstants = require('../lib/common/ext/network-constants'); const bip39 = require('bip39'); const bip32 = require('bip32'); const bs58check = require('bs58check'); @@ -22,7 +26,11 @@ type Network = { coin: string; }; +const ETH_CACHE = {}; +const CACHE = {}; + class AirDAOKeys { + private _bipHex: any; private async getRandomBytes(size: number): Promise { // TODO implement the function to generate random bytes return ''; @@ -104,7 +112,257 @@ class AirDAOKeys { return root; } - // TODO discoverAddresses + async discoverAddresses(data: any, source: string) { + const logData = { ...data }; + if (typeof logData.mnemonic !== 'undefined') logData.mnemonic = '***'; + let toDiscover = BlocksoftDict.Codes; + if (data.currencyCode) { + if (typeof data.currencyCode === 'string') { + toDiscover = [data.currencyCode]; + } else { + toDiscover = data.currencyCode; + } + } + const fromIndex = data.fromIndex ? data.fromIndex : 0; + const toIndex = data.toIndex ? data.toIndex : 10; + const fullTree = data.fullTree ? data.fullTree : false; + const results = {}; + const mnemonicCache = data.mnemonic.toLowerCase(); + let bitcoinRoot = false; + let currencyCode; + let settings; + const seed = await KeysUtills.bip39MnemonicToSeed( + data.mnemonic.toLowerCase() + ); + for (currencyCode of toDiscover) { + results[currencyCode] = []; + try { + settings = BlocksoftDict.getCurrencyAllSettings( + currencyCode, + 'BlocksoftKeys' + ); + } catch (e) { + // do nothing for now + continue; + } + + let hexes = []; + if (settings.addressCurrencyCode) { + hexes.push(this._bipHex[settings.addressCurrencyCode]); + if (!this._bipHex[settings.addressCurrencyCode]) { + throw new Error( + 'UNKNOWN_CURRENCY_CODE SETTED ' + settings.addressCurrencyCode + ); + } + } + + if (this._bipHex[currencyCode]) { + hexes.push(this._bipHex[currencyCode]); + } else if (!settings.addressCurrencyCode) { + if ( + settings.extendsProcessor && + this._bipHex[settings.extendsProcessor] + ) { + hexes.push(this._bipHex[settings.extendsProcessor]); + } else { + throw new Error( + 'UNKNOWN_CURRENCY_CODE ' + + currencyCode + + ' in bipHex AND NO SETTED addressCurrencyCode' + ); + } + } + + let isAlreadyMain = false; + + if (!data.fullTree) { + hexes = [hexes[0]]; + } + + const hexesCache = + mnemonicCache + + '_' + + settings.addressProcessor + + '_fromINDEX_' + + fromIndex + + '_' + + JSON.stringify(hexes); + let hasDerivations = false; + if ( + typeof data.derivations !== 'undefined' && + typeof data.derivations[currencyCode] !== 'undefined' && + data.derivations[currencyCode] + ) { + hasDerivations = true; + } + if (typeof CACHE[hexesCache] === 'undefined' || hasDerivations) { + // BlocksoftCryptoLog.log(`BlocksoftKeys will discover ${settings.addressProcessor}`) + let root = false; + if (typeof networksConstants[currencyCode] !== 'undefined') { + root = await this.getBip32Cached( + data.mnemonic, + networksConstants[currencyCode], + seed + ); + } else { + if (!bitcoinRoot) { + bitcoinRoot = await this.getBip32Cached(data.mnemonic); + } + root = bitcoinRoot; + } + // BIP32 Extended Private Key to check - uncomment + // let childFirst = root.derivePath('m/44\'/2\'/0\'/0') + // BlocksoftCryptoLog.log(childFirst.toBase58()) + + /** + * @type {EthAddressProcessor|BtcAddressProcessor} + */ + const processor = await BlocksoftDispatcher.innerGetAddressProcessor( + settings + ); + + try { + await processor.setBasicRoot(root); + } catch (e) { + e.message += ' while doing ' + JSON.stringify(settings); + throw e; + } + let currentFromIndex = fromIndex; + let currentToIndex = toIndex; + let currentFullTree = fullTree; + if (hasDerivations) { + let derivation = { path: '', alreadyShown: 0, walletPubId: 0 }; + let maxIndex = 0; + for (derivation of data.derivations[currencyCode]) { + const child = root.derivePath(derivation.path); + const tmp = derivation.path.split('/'); + const result = await processor.getAddress( + child.privateKey, + { + publicKey: child.publicKey, + walletHash: data.walletHash, + derivationPath: derivation.path + }, + data, + seed, + 'discoverAddresses' + ); + result.basicPrivateKey = child.privateKey.toString('hex'); + result.basicPublicKey = child.publicKey.toString('hex'); + result.path = derivation.path; + result.alreadyShown = derivation.alreadyShown; + result.walletPubId = derivation.walletPubId || 0; + result.index = tmp[5]; + if (maxIndex < result.index) { + maxIndex = result.index * 1; + } + result.type = 'main'; + results[currencyCode].push(result); + } + if (maxIndex > 0) { + // noinspection PointlessArithmeticExpressionJS + currentFromIndex = maxIndex * 1 + 1; + currentToIndex = currentFromIndex * 1 + 10; + currentFullTree = true; + } + } + + let suffixes; + if (currencyCode === 'SOL') { + suffixes = [ + { type: 'main', suffix: false, after: `'/0'` }, + { type: 'no_scan', suffix: false, after: `'` } + ]; + } else if (currentFullTree) { + suffixes = [ + { type: 'main', suffix: `0'/0` }, + { type: 'change', suffix: `0'/1` } + // { 'type': 'second', 'suffix': `1'/0` }, + // { 'type': 'secondchange', 'suffix': `1'/1` } + ]; + } else { + suffixes = [{ type: 'main', suffix: `0'/0` }]; + if (currencyCode === 'BTC_SEGWIT_COMPATIBLE') { + suffixes = [ + { type: 'main', suffix: '0/1' } // heh + ]; + } + hexes = [hexes[0]]; + } + + let hex; + for (hex of hexes) { + if (isAlreadyMain) { + suffixes[0].type = 'second'; + } + isAlreadyMain = true; + + if (currentFromIndex >= 0 && currentToIndex >= 0) { + for ( + let index = currentFromIndex; + index < currentToIndex; + index++ + ) { + let suffix; + for (suffix of suffixes) { + const path = suffix.suffix + ? `m/${hex}'/${suffix.suffix}/${index}` + : `m/${hex}'/${index}${suffix.after}`; + let privateKey = false; + let publicKey = false; + if ( + currencyCode === 'SOL' || + currencyCode === 'XLM' || + currencyCode === 'WAVES' || + currencyCode === 'ASH' + ) { + // @todo move to coin address processor + } else { + const child = root.derivePath(path); + privateKey = child.privateKey; + publicKey = child.publicKey; + } + const result = await processor.getAddress( + privateKey, + { + publicKey, + walletHash: data.walletHash, + derivationPath: path, + derivationIndex: index, + derivationType: suffix.type + }, + data, + seed, + 'discoverAddresses2' + ); + if (result) { + if (privateKey) { + result.basicPrivateKey = privateKey.toString('hex'); + result.basicPublicKey = publicKey.toString('hex'); + } + result.path = path; + result.index = index; + result.alreadyShown = 0; + result.type = suffix.type; + results[currencyCode].push(result); + } + } + } + } + } + CACHE[hexesCache] = results[currencyCode]; + if (currencyCode === 'ETH') { + ETH_CACHE[mnemonicCache] = results[currencyCode][0]; + } + } else { + results[currencyCode] = CACHE[hexesCache]; + if (currencyCode === 'USDT') { + results[currencyCode] = [results[currencyCode][0]]; + } + } + } + return results; + } // TODO discoverOne diff --git a/src/lib/BlocksoftDispatcher.js b/src/lib/BlocksoftDispatcher.js index ce3c173b2..d7dfcbd2f 100644 --- a/src/lib/BlocksoftDispatcher.js +++ b/src/lib/BlocksoftDispatcher.js @@ -2,253 +2,314 @@ * @author Ksu * @version 0.5 */ -import BlocksoftDict from '../common/BlocksoftDict' +import BlocksoftDict from '../common/BlocksoftDict'; -import BchAddressProcessor from './bch/BchAddressProcessor' -import BchScannerProcessor from './bch/BchScannerProcessor' +import BchAddressProcessor from './bch/BchAddressProcessor'; +import BchScannerProcessor from './bch/BchScannerProcessor'; -import BsvScannerProcessor from './bsv/BsvScannerProcessor' +import BsvScannerProcessor from './bsv/BsvScannerProcessor'; -import BtcAddressProcessor from './btc/address/BtcAddressProcessor' -import BtcScannerProcessor from './btc/BtcScannerProcessor' +import BtcAddressProcessor from './btc/address/BtcAddressProcessor'; +import BtcScannerProcessor from './btc/BtcScannerProcessor'; -import BtcSegwitCompatibleAddressProcessor from './btc/address/BtcSegwitCompatibleAddressProcessor' -import BtcSegwitAddressProcessor from './btc/address/BtcSegwitAddressProcessor' +import BtcSegwitCompatibleAddressProcessor from './btc/address/BtcSegwitCompatibleAddressProcessor'; +import BtcSegwitAddressProcessor from './btc/address/BtcSegwitAddressProcessor'; -import BtcTestScannerProcessor from './btc_test/BtcTestScannerProcessor' +import BtcTestScannerProcessor from './btc_test/BtcTestScannerProcessor'; -import BtgScannerProcessor from './btg/BtgScannerProcessor' +import BtgScannerProcessor from './btg/BtgScannerProcessor'; -import DogeScannerProcessor from './doge/DogeScannerProcessor' +import DogeScannerProcessor from './doge/DogeScannerProcessor'; -import EthAddressProcessor from './eth/EthAddressProcessor' -import EthScannerProcessor from './eth/EthScannerProcessor' -import EthScannerProcessorErc20 from './eth/EthScannerProcessorErc20' -import EthScannerProcessorSoul from './eth/forks/EthScannerProcessorSoul' -import EthTokenProcessorErc20 from './eth/EthTokenProcessorErc20' +import EthAddressProcessor from './eth/EthAddressProcessor'; +import EthScannerProcessor from './eth/EthScannerProcessor'; +import EthScannerProcessorErc20 from './eth/EthScannerProcessorErc20'; +import EthScannerProcessorSoul from './eth/forks/EthScannerProcessorSoul'; +import EthTokenProcessorErc20 from './eth/EthTokenProcessorErc20'; -import LtcScannerProcessor from './ltc/LtcScannerProcessor' +import LtcScannerProcessor from './ltc/LtcScannerProcessor'; -import TrxAddressProcessor from './trx/TrxAddressProcessor' -import TrxScannerProcessor from './trx/TrxScannerProcessor' -import TrxTokenProcessor from './trx/TrxTokenProcessor' +import TrxAddressProcessor from './trx/TrxAddressProcessor'; +import TrxScannerProcessor from './trx/TrxScannerProcessor'; +import TrxTokenProcessor from './trx/TrxTokenProcessor'; -import UsdtScannerProcessor from './usdt/UsdtScannerProcessor' +import UsdtScannerProcessor from './usdt/UsdtScannerProcessor'; -import XrpAddressProcessor from './xrp/XrpAddressProcessor' -import XrpScannerProcessor from './xrp/XrpScannerProcessor' +import XrpAddressProcessor from './xrp/XrpAddressProcessor'; +import XrpScannerProcessor from './xrp/XrpScannerProcessor'; -import XlmAddressProcessor from './xlm/XlmAddressProcessor' -import XlmScannerProcessor from './xlm/XlmScannerProcessor' +import XlmAddressProcessor from './xlm/XlmAddressProcessor'; +import XlmScannerProcessor from './xlm/XlmScannerProcessor'; -import XvgScannerProcessor from './xvg/XvgScannerProcessor' +import XvgScannerProcessor from './xvg/XvgScannerProcessor'; -import XmrAddressProcessor from './xmr/XmrAddressProcessor' -import XmrScannerProcessor from './xmr/XmrScannerProcessor' -import XmrSecretsProcessor from './xmr/XmrSecretsProcessor' -import FioAddressProcessor from './fio/FioAddressProcessor' -import FioScannerProcessor from './fio/FioScannerProcessor' +import XmrAddressProcessor from './xmr/XmrAddressProcessor'; +import XmrScannerProcessor from './xmr/XmrScannerProcessor'; +import XmrSecretsProcessor from './xmr/XmrSecretsProcessor'; +import FioAddressProcessor from './fio/FioAddressProcessor'; +import FioScannerProcessor from './fio/FioScannerProcessor'; +import BnbAddressProcessor from './bnb/BnbAddressProcessor'; +import BnbScannerProcessor from './bnb/BnbScannerProcessor'; +import VetScannerProcessor from '@crypto/blockchains/vet/VetScannerProcessor'; -import BnbAddressProcessor from './bnb/BnbAddressProcessor' -import BnbScannerProcessor from './bnb/BnbScannerProcessor' -import VetScannerProcessor from '@crypto/blockchains/vet/VetScannerProcessor' +import SolAddressProcessor from '@crypto/blockchains/sol/SolAddressProcessor'; +import SolScannerProcessor from '@crypto/blockchains/sol/SolScannerProcessor'; -import SolAddressProcessor from '@crypto/blockchains/sol/SolAddressProcessor' -import SolScannerProcessor from '@crypto/blockchains/sol/SolScannerProcessor' +import WavesAddressProcessor from '@crypto/blockchains/waves/WavesAddressProcessor'; +import WavesScannerProcessor from '@crypto/blockchains/waves/WavesScannerProcessor'; -import WavesAddressProcessor from '@crypto/blockchains/waves/WavesAddressProcessor' -import WavesScannerProcessor from '@crypto/blockchains/waves/WavesScannerProcessor' +import SolScannerProcessorSpl from '@crypto/blockchains/sol/SolScannerProcessorSpl'; +import SolTokenProcessor from '@crypto/blockchains/sol/SolTokenProcessor'; +import EthTokenProcessorNft from '@crypto/blockchains/eth/EthTokenProcessorNft'; +import AshAddressProcessor from '@crypto/blockchains/ash/AshAddressProcessor'; -import SolScannerProcessorSpl from '@crypto/blockchains/sol/SolScannerProcessorSpl' -import SolTokenProcessor from '@crypto/blockchains/sol/SolTokenProcessor' -import EthTokenProcessorNft from '@crypto/blockchains/eth/EthTokenProcessorNft' -import AshAddressProcessor from '@crypto/blockchains/ash/AshAddressProcessor' +import MetisScannerProcessor from '@crypto/blockchains/metis/MetisScannerProcessor'; +import OneScannerProcessor from '@crypto/blockchains/one/OneScannerProcessor'; +import OneScannerProcessorErc20 from '@crypto/blockchains/one/OneScannerProcessorErc20'; -import MetisScannerProcessor from '@crypto/blockchains/metis/MetisScannerProcessor' -import OneScannerProcessor from '@crypto/blockchains/one/OneScannerProcessor' -import OneScannerProcessorErc20 from '@crypto/blockchains/one/OneScannerProcessorErc20' - -import WavesScannerProcessorErc20 from '@crypto/blockchains/waves/WavesScannerProcessorErc20' +import WavesScannerProcessorErc20 from '@crypto/blockchains/waves/WavesScannerProcessorErc20'; class BlocksoftDispatcher { + /** + * @param {string} currencyCode + * @return {EthAddressProcessor|BtcAddressProcessor} + */ + getAddressProcessor(currencyCode) { + const currencyDictSettings = + BlocksoftDict.getCurrencyAllSettings(currencyCode); + return this.innerGetAddressProcessor(currencyDictSettings); + } - /** - * @param {string} currencyCode - * @return {EthAddressProcessor|BtcAddressProcessor} - */ - getAddressProcessor(currencyCode) { - const currencyDictSettings = BlocksoftDict.getCurrencyAllSettings(currencyCode) - return this.innerGetAddressProcessor(currencyDictSettings) - } - - /** - * @param {Object} currencyDictSettings - * @return {EthAddressProcessor|BtcAddressProcessor|TrxAddressProcessor} - */ - innerGetAddressProcessor(currencyDictSettings) { - switch (currencyDictSettings.addressProcessor) { - case 'BCH': - return new BchAddressProcessor(currencyDictSettings) - case 'BTC': - return new BtcAddressProcessor(currencyDictSettings) - case 'BTC_SEGWIT': case 'LTC_SEGWIT': - return new BtcSegwitAddressProcessor(currencyDictSettings) - case 'BTC_SEGWIT_COMPATIBLE': - return new BtcSegwitCompatibleAddressProcessor(currencyDictSettings) - case 'ETH': - return new EthAddressProcessor(currencyDictSettings) - case 'TRX': - return new TrxAddressProcessor() - case 'XRP': - return new XrpAddressProcessor() - case 'XLM': - return new XlmAddressProcessor() - case 'XMR': - return new XmrAddressProcessor() - case 'FIO': - return new FioAddressProcessor() - case 'BNB': - return new BnbAddressProcessor() - case 'SOL': - return new SolAddressProcessor() - case 'WAVES': - return new WavesAddressProcessor() - case 'ASH' : - return new AshAddressProcessor() - default: - throw new Error('Unknown addressProcessor ' + currencyDictSettings.addressProcessor) - } + /** + * @param {Object} currencyDictSettings + * @return {EthAddressProcessor|BtcAddressProcessor|TrxAddressProcessor} + */ + innerGetAddressProcessor(currencyDictSettings) { + switch (currencyDictSettings.addressProcessor) { + case 'BCH': + return new BchAddressProcessor(currencyDictSettings); + case 'BTC': + return new BtcAddressProcessor(currencyDictSettings); + case 'BTC_SEGWIT': + case 'LTC_SEGWIT': + return new BtcSegwitAddressProcessor(currencyDictSettings); + case 'BTC_SEGWIT_COMPATIBLE': + return new BtcSegwitCompatibleAddressProcessor(currencyDictSettings); + case 'ETH': + return new EthAddressProcessor(currencyDictSettings); + case 'TRX': + return new TrxAddressProcessor(); + case 'XRP': + return new XrpAddressProcessor(); + case 'XLM': + return new XlmAddressProcessor(); + case 'XMR': + return new XmrAddressProcessor(); + case 'FIO': + return new FioAddressProcessor(); + case 'BNB': + return new BnbAddressProcessor(); + case 'SOL': + return new SolAddressProcessor(); + case 'WAVES': + return new WavesAddressProcessor(); + case 'ASH': + return new AshAddressProcessor(); + default: + throw new Error( + 'Unknown addressProcessor ' + currencyDictSettings.addressProcessor + ); } + } - /** - * @param {string} currencyCode - * @returns {BsvScannerProcessor|BtcScannerProcessor|UsdtScannerProcessor|EthScannerProcessorErc20|BchScannerProcessor|LtcScannerProcessor|XvgScannerProcessor|BtcTestScannerProcessor|DogeScannerProcessor|EthScannerProcessorSoul|EthScannerProcessor|BtgScannerProcessor|TrxScannerProcessor} - */ - getScannerProcessor(currencyCode) { - const currencyDictSettings = BlocksoftDict.getCurrencyAllSettings(currencyCode) - switch (currencyDictSettings.scannerProcessor) { - case 'BCH': - return new BchScannerProcessor(currencyDictSettings) - case 'BSV': - return new BsvScannerProcessor(currencyDictSettings) - case 'BTC': case 'BTC_SEGWIT': case 'BTC_SEGWIT_COMPATIBLE': - return new BtcScannerProcessor(currencyDictSettings) - case 'BTC_TEST': - return new BtcTestScannerProcessor(currencyDictSettings) - case 'BTG': - return new BtgScannerProcessor(currencyDictSettings) - case 'DOGE': - return new DogeScannerProcessor(currencyDictSettings) - case 'ETH': - return new EthScannerProcessor(currencyDictSettings) - case 'ETH_ERC_20': - return new EthScannerProcessorErc20(currencyDictSettings) - case 'ETH_SOUL': - return new EthScannerProcessorSoul(currencyDictSettings) - case 'LTC': - return new LtcScannerProcessor(currencyDictSettings) - case 'TRX': - return new TrxScannerProcessor(currencyDictSettings) - case 'USDT': - return new UsdtScannerProcessor(currencyDictSettings) - case 'XRP': - return new XrpScannerProcessor(currencyDictSettings) - case 'XLM': - return new XlmScannerProcessor(currencyDictSettings) - case 'XVG': - return new XvgScannerProcessor(currencyDictSettings) - case 'XMR': - return new XmrScannerProcessor(currencyDictSettings) - case 'FIO': - return new FioScannerProcessor(currencyDictSettings) - case 'BNB': - return new BnbScannerProcessor(currencyDictSettings) - case 'VET': - return new VetScannerProcessor(currencyDictSettings) - case 'SOL': - return new SolScannerProcessor(currencyDictSettings) - case 'SOL_SPL': - return new SolScannerProcessorSpl(currencyDictSettings) - case 'WAVES': - return new WavesScannerProcessor(currencyDictSettings) - case 'METIS': - return new MetisScannerProcessor(currencyDictSettings) - case 'ONE': - return new OneScannerProcessor(currencyDictSettings) - case 'ONE_ERC_20': - return new OneScannerProcessorErc20(currencyDictSettings) - case 'WAVES_ERC_20': - return new WavesScannerProcessorErc20(currencyDictSettings) - default: - throw new Error('Unknown scannerProcessor ' + currencyDictSettings.scannerProcessor) - } + /** + * @param {string} currencyCode + * @returns {BsvScannerProcessor|BtcScannerProcessor|UsdtScannerProcessor|EthScannerProcessorErc20|BchScannerProcessor|LtcScannerProcessor|XvgScannerProcessor|BtcTestScannerProcessor|DogeScannerProcessor|EthScannerProcessorSoul|EthScannerProcessor|BtgScannerProcessor|TrxScannerProcessor} + */ + getScannerProcessor(currencyCode) { + const currencyDictSettings = + BlocksoftDict.getCurrencyAllSettings(currencyCode); + switch (currencyDictSettings.scannerProcessor) { + case 'BCH': + return new BchScannerProcessor(currencyDictSettings); + case 'BSV': + return new BsvScannerProcessor(currencyDictSettings); + case 'BTC': + case 'BTC_SEGWIT': + case 'BTC_SEGWIT_COMPATIBLE': + return new BtcScannerProcessor(currencyDictSettings); + case 'BTC_TEST': + return new BtcTestScannerProcessor(currencyDictSettings); + case 'BTG': + return new BtgScannerProcessor(currencyDictSettings); + case 'DOGE': + return new DogeScannerProcessor(currencyDictSettings); + case 'ETH': + return new EthScannerProcessor(currencyDictSettings); + case 'ETH_ERC_20': + return new EthScannerProcessorErc20(currencyDictSettings); + case 'ETH_SOUL': + return new EthScannerProcessorSoul(currencyDictSettings); + case 'LTC': + return new LtcScannerProcessor(currencyDictSettings); + case 'TRX': + return new TrxScannerProcessor(currencyDictSettings); + case 'USDT': + return new UsdtScannerProcessor(currencyDictSettings); + case 'XRP': + return new XrpScannerProcessor(currencyDictSettings); + case 'XLM': + return new XlmScannerProcessor(currencyDictSettings); + case 'XVG': + return new XvgScannerProcessor(currencyDictSettings); + case 'XMR': + return new XmrScannerProcessor(currencyDictSettings); + case 'FIO': + return new FioScannerProcessor(currencyDictSettings); + case 'BNB': + return new BnbScannerProcessor(currencyDictSettings); + case 'VET': + return new VetScannerProcessor(currencyDictSettings); + case 'SOL': + return new SolScannerProcessor(currencyDictSettings); + case 'SOL_SPL': + return new SolScannerProcessorSpl(currencyDictSettings); + case 'WAVES': + return new WavesScannerProcessor(currencyDictSettings); + case 'METIS': + return new MetisScannerProcessor(currencyDictSettings); + case 'ONE': + return new OneScannerProcessor(currencyDictSettings); + case 'ONE_ERC_20': + return new OneScannerProcessorErc20(currencyDictSettings); + case 'WAVES_ERC_20': + return new WavesScannerProcessorErc20(currencyDictSettings); + default: + throw new Error( + 'Unknown scannerProcessor ' + currencyDictSettings.scannerProcessor + ); } + } - /** - * @param tokenType - * @returns {TrxTokenProcessor|EthTokenProcessorErc20} - */ - getTokenProcessor(tokenType) { - switch (tokenType) { - case 'ETH_ERC_20': - return new EthTokenProcessorErc20({ network: 'mainnet', tokenBlockchain: 'ETHEREUM' }) - case 'BNB_SMART_20': - return new EthTokenProcessorErc20({ network: 'mainnet', tokenBlockchain : 'BNB' }) - case 'MATIC_ERC_20': - return new EthTokenProcessorErc20({ network: 'mainnet', tokenBlockchain : 'MATIC' }) - case 'FTM_ERC_20': - return new EthTokenProcessorErc20({ network: 'mainnet', tokenBlockchain : 'FTM' }) - case 'VLX_ERC_20': - return new EthTokenProcessorErc20({ network: 'mainnet', tokenBlockchain : 'VLX' }) - case 'ONE_ERC_20': - return new EthTokenProcessorErc20({ network: 'mainnet', tokenBlockchain : 'ONE' }) - case 'METIS_ERC_20': - return new EthTokenProcessorErc20({ network: 'mainnet', tokenBlockchain : 'METIS' }) - case 'TRX': - return new TrxTokenProcessor() - case 'SOL': - return new SolTokenProcessor() - default: - throw new Error('Unknown tokenProcessor ' + tokenType) - } + /** + * @param tokenType + * @returns {TrxTokenProcessor|EthTokenProcessorErc20} + */ + getTokenProcessor(tokenType) { + switch (tokenType) { + case 'ETH_ERC_20': + return new EthTokenProcessorErc20({ + network: 'mainnet', + tokenBlockchain: 'ETHEREUM' + }); + case 'BNB_SMART_20': + return new EthTokenProcessorErc20({ + network: 'mainnet', + tokenBlockchain: 'BNB' + }); + case 'MATIC_ERC_20': + return new EthTokenProcessorErc20({ + network: 'mainnet', + tokenBlockchain: 'MATIC' + }); + case 'FTM_ERC_20': + return new EthTokenProcessorErc20({ + network: 'mainnet', + tokenBlockchain: 'FTM' + }); + case 'VLX_ERC_20': + return new EthTokenProcessorErc20({ + network: 'mainnet', + tokenBlockchain: 'VLX' + }); + case 'ONE_ERC_20': + return new EthTokenProcessorErc20({ + network: 'mainnet', + tokenBlockchain: 'ONE' + }); + case 'METIS_ERC_20': + return new EthTokenProcessorErc20({ + network: 'mainnet', + tokenBlockchain: 'METIS' + }); + case 'TRX': + return new TrxTokenProcessor(); + case 'SOL': + return new SolTokenProcessor(); + default: + throw new Error('Unknown tokenProcessor ' + tokenType); } + } - /** - * @param tokenBlockchainCode - * @returns {EthTokenProcessorNft} - */ - getTokenNftsProcessor(tokenBlockchainCode) { - switch (tokenBlockchainCode) { - case 'ETH': case 'NFT_ETH': - return new EthTokenProcessorNft({ network: 'mainnet', tokenBlockchain: 'ETHEREUM', tokenBlockchainCode : 'ETH' }) - case 'ETH_RINKEBY': case 'NFT_RINKEBY': - return new EthTokenProcessorNft({ network: 'rinkeby', tokenBlockchain: 'RINKEBY', tokenBlockchainCode : 'ETH_RINKEBY' }) - case 'MATIC': case 'NFT_MATIC': - return new EthTokenProcessorNft({ network: 'mainnet', tokenBlockchain : 'MATIC', tokenBlockchainCode : 'MATIC' }) - case 'BNB': case 'NFT_BNB': - return new EthTokenProcessorNft({ network: 'mainnet', tokenBlockchain : 'BNB', tokenBlockchainCode : 'BNB' }) - case 'ONE': case 'NFT_ONE': - return new EthTokenProcessorNft({ network: 'mainnet', tokenBlockchain : 'ONE', tokenBlockchainCode : 'ONE' }) - case 'ETH_ROPSTEN': case 'NFT_ROPSTEN': - return new EthTokenProcessorNft({ network: 'ropsten', tokenBlockchain : 'ROPSTEN', tokenBlockchainCode : 'ETH_ROPSTEN' }) - default: - throw new Error('Unknown NFT tokenProcessor ' + tokenBlockchainCode) - } + /** + * @param tokenBlockchainCode + * @returns {EthTokenProcessorNft} + */ + getTokenNftsProcessor(tokenBlockchainCode) { + switch (tokenBlockchainCode) { + case 'ETH': + case 'NFT_ETH': + return new EthTokenProcessorNft({ + network: 'mainnet', + tokenBlockchain: 'ETHEREUM', + tokenBlockchainCode: 'ETH' + }); + case 'ETH_RINKEBY': + case 'NFT_RINKEBY': + return new EthTokenProcessorNft({ + network: 'rinkeby', + tokenBlockchain: 'RINKEBY', + tokenBlockchainCode: 'ETH_RINKEBY' + }); + case 'MATIC': + case 'NFT_MATIC': + return new EthTokenProcessorNft({ + network: 'mainnet', + tokenBlockchain: 'MATIC', + tokenBlockchainCode: 'MATIC' + }); + case 'BNB': + case 'NFT_BNB': + return new EthTokenProcessorNft({ + network: 'mainnet', + tokenBlockchain: 'BNB', + tokenBlockchainCode: 'BNB' + }); + case 'ONE': + case 'NFT_ONE': + return new EthTokenProcessorNft({ + network: 'mainnet', + tokenBlockchain: 'ONE', + tokenBlockchainCode: 'ONE' + }); + case 'ETH_ROPSTEN': + case 'NFT_ROPSTEN': + return new EthTokenProcessorNft({ + network: 'ropsten', + tokenBlockchain: 'ROPSTEN', + tokenBlockchainCode: 'ETH_ROPSTEN' + }); + default: + throw new Error('Unknown NFT tokenProcessor ' + tokenBlockchainCode); } + } - /** - * @param {string} currencyCode - * @return {XmrSecretsProcessor} - */ - getSecretsProcessor(currencyCode) { - const currencyDictSettings = BlocksoftDict.getCurrencyAllSettings(currencyCode) - if (currencyDictSettings.currencyCode !== 'XMR') { - throw new Error('Unknown secretsProcessor ' + currencyDictSettings.currencyCode) - } - return new XmrSecretsProcessor() + /** + * @param {string} currencyCode + * @return {XmrSecretsProcessor} + */ + getSecretsProcessor(currencyCode) { + const currencyDictSettings = + BlocksoftDict.getCurrencyAllSettings(currencyCode); + if (currencyDictSettings.currencyCode !== 'XMR') { + throw new Error( + 'Unknown secretsProcessor ' + currencyDictSettings.currencyCode + ); } + return new XmrSecretsProcessor(); + } } -const singleBlocksoftDispatcher = new BlocksoftDispatcher() -export default singleBlocksoftDispatcher +const singleBlocksoftDispatcher = new BlocksoftDispatcher(); +export default singleBlocksoftDispatcher; diff --git a/src/lib/BlocksoftKeys.js b/src/lib/BlocksoftKeys.js index b50a4898a..4c6c50457 100644 --- a/src/lib/BlocksoftKeys.js +++ b/src/lib/BlocksoftKeys.js @@ -18,7 +18,7 @@ const networksConstants = require('../../common/ext/networks-constants'); const bs58check = require('bs58check'); -const ETH_CACHE = {}; +const ETH_CACHE = {} const CACHE = {}; const CACHE_ROOTS = {}; @@ -84,7 +84,7 @@ class BlocksoftKeys { return result; } - /** + /** * @param {string} data.mnemonic * @param {string} data.walletHash * @param {string|string[]} data.currencyCode = all @@ -265,9 +265,8 @@ class BlocksoftKeys { `BlocksoftKeys ${currencyCode} discoverAddresses currentFromIndex.2 ${currentFromIndex}` ); } - } - let suffixes; + let suffixes if (currencyCode === 'SOL') { suffixes = [ { type: 'main', suffix: false, after: `'/0'` }, @@ -290,7 +289,7 @@ class BlocksoftKeys { hexes = [hexes[0]]; } - let hex; + let hex for (hex of hexes) { if (isAlreadyMain) { suffixes[0].type = 'second'; diff --git a/src/lib/common/ext/network-constants.ts b/src/lib/common/ext/network-constants.ts new file mode 100644 index 000000000..5f9eb63d7 --- /dev/null +++ b/src/lib/common/ext/network-constants.ts @@ -0,0 +1,117 @@ +import * as bitcoin from 'bitcoinjs-lib'; + +export interface NetworkConfig { + messagePrefix: string; + bech32?: string; + bip32: { + public: number; + private: number; + }; + pubKeyHash: number; + scriptHash: number; + wif: number; + BTCFork?: string; +} + +export interface CoinConfig { + network: NetworkConfig; + langPrefix: string; +} + +export interface CoinConfigs { + [key: string]: CoinConfig; +} + +const coinConfigs: CoinConfigs = { + mainnet: { + network: bitcoin.networks.bitcoin, + langPrefix: 'btc' + }, + testnet: { + network: bitcoin.networks.testnet, + langPrefix: 'btc' + }, + litecoin: { + network: { + bech32: 'ltc', + messagePrefix: '\x19Litecoin Signed Message:\n', + pubKeyHash: 0x30, + scriptHash: 0x32, + wif: 0xb0, + bip32: { + public: 0x019da462, + private: 0x019d9cfe + } + }, + langPrefix: 'ltc' + }, + dogecoin: { + network: { + messagePrefix: '\x19Dogecoin Signed Message:\n', + bip32: { + public: 0x02facafd, + private: 0x02fac398 + }, + pubKeyHash: 0x1e, + scriptHash: 0x16, + wif: 0x9e + }, + langPrefix: 'ltc' + }, + verge: { + network: { + messagePrefix: '\x18VERGE Signed Message:\n', + bip32: { + public: 0x0488b21e, + private: 0x0488ade4 + }, + pubKeyHash: 0x1e, + scriptHash: 0x21, + wif: 0x9e + }, + langPrefix: 'ltc' + }, + bitcoincash: { + network: { + messagePrefix: '\u0018Bitcoin Signed Message:\n', + bech32: 'bc', + bip32: { public: 76067358, private: 76066276 }, + pubKeyHash: 0, + scriptHash: 5, + wif: 128, + BTCFork: 'BCH' + }, + langPrefix: 'bch' + }, + bitcoinsv: { + network: { + messagePrefix: 'unused', + bip32: { + public: 0x0488b21e, + private: 0x0488ade4 + }, + pubKeyHash: 0x00, + scriptHash: 0x05, + wif: 0x80, + BTCFork: 'BCH' + }, + langPrefix: 'bch' + }, + bitcoingold: { + network: { + bech32: 'btg', + messagePrefix: '\x1DBitcoin Gold Signed Message:\n', + bip32: { + public: 0x0488b21e, + private: 0x0488ade4 + }, + pubKeyHash: 38, + scriptHash: 23, + wif: 128, + BTCFork: 'BTG' + }, + langPrefix: 'ltc' + } +}; + +export default coinConfigs; diff --git a/yarn.lock b/yarn.lock index 8d96915f8..bc6b24f47 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2415,6 +2415,28 @@ resolved "https://registry.yarnpkg.com/@morrowdigital/watermelondb-expo-plugin/-/watermelondb-expo-plugin-2.1.2.tgz#7c511d671cba68984d5b77a78d3b998bfb57a750" integrity sha512-mN/CISFEoA4w9HH62gQ7hHefpoNEGSp3eEs8kRUwtNpumBEr1Of8EEN1sNGmfB0IhB62Rj5gbFC+eBVhas4YXQ== +"@mymonero/mymonero-bigint@^1.4.2": + version "1.4.2" + resolved "https://registry.yarnpkg.com/@mymonero/mymonero-bigint/-/mymonero-bigint-1.4.2.tgz#23174269ffdb983181972f021fb237069d135a7f" + integrity sha512-JVGPBWT9jeCNaXAW1vnZEHnX+flP8Wl/FC6sq2bwan9nblAkRB59UsAzpOm8VjiPwmUqm073sekLsIpAgUB2IQ== + +"@mymonero/mymonero-money-format@^1.4.2": + version "1.4.2" + resolved "https://registry.yarnpkg.com/@mymonero/mymonero-money-format/-/mymonero-money-format-1.4.2.tgz#cf852de67bc02522c64cf4020d6ec3900d87aee4" + integrity sha512-M+VXwrF/fnkGOSsy9f+DwtLz940MeMknkpiPICtFXDtL8/vluQBenxiq+qC5pr5LYYPqvP4TGK2wKxqSORORZw== + dependencies: + "@mymonero/mymonero-bigint" "^1.4.2" + +"@mymonero/mymonero-nettype@^1.4.2": + version "1.4.2" + resolved "https://registry.yarnpkg.com/@mymonero/mymonero-nettype/-/mymonero-nettype-1.4.2.tgz#18b9ec8060a5a2cb0f681cdfbf42055757764fec" + integrity sha512-3QocFvyzUEIUdqLL5lZvIbgLTXuuapQuztPzjtTok0P6uoPFm0KMLgYiOK4hOAtalr3B6FxlbojhPNL0IHsw4A== + +"@mymonero/mymonero-paymentid-utils@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@mymonero/mymonero-paymentid-utils/-/mymonero-paymentid-utils-3.0.0.tgz#6977ff29abcfe5dd4921da4c8ffb4a369b26e3a0" + integrity sha512-kDbhaL9NxJp1Z9k9/gAFjWOoF8l6E4twv8bdS3wc+0i4b+4Iot582tfzIdRrZ5e5N1G+rSb7wK8zAqTFgnOcEw== + "@neverdull-agency/expo-unlimited-secure-store@^1.0.10": version "1.0.10" resolved "https://registry.yarnpkg.com/@neverdull-agency/expo-unlimited-secure-store/-/expo-unlimited-secure-store-1.0.10.tgz#1e78b502257b267fc918a85eaa41aa01a46d2007" @@ -3006,6 +3028,11 @@ resolved "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz" integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== +"@types/lodash@^4.14.136": + version "4.14.195" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.195.tgz#bafc975b252eb6cea78882ce8a7b6bf22a6de632" + integrity sha512-Hwx9EUgdwf2GLarOjQp5ZH8ZmblzcbTBC2wtQWNKARBSxM9ezRIAUpeDTgoQRAFB0+8CNWXVA9+MaSOzOF3nPg== + "@types/node@*": version "18.15.11" resolved "https://registry.npmjs.org/@types/node/-/node-18.15.11.tgz" @@ -3800,7 +3827,7 @@ balanced-match@^1.0.0: resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== -base-x@^3.0.2: +base-x@^3.0.2, base-x@^3.0.9: version "3.0.9" resolved "https://registry.yarnpkg.com/base-x/-/base-x-3.0.9.tgz#6349aaabb58526332de9f60995e548a53fe21320" integrity sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ== @@ -3847,7 +3874,7 @@ better-opn@~3.0.2: dependencies: open "^8.0.4" -big-integer@1.6.x: +big-integer@1.6.x, big-integer@^1.6.48: version "1.6.51" resolved "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz" integrity sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg== @@ -3892,6 +3919,16 @@ bip32@^4.0.0: typeforce "^1.11.5" wif "^2.0.6" +bip32@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/bip32/-/bip32-4.0.0.tgz#7fac3c05072188d2d355a4d6596b37188f06aa2f" + integrity sha512-aOGy88DDlVUhspIXJN+dVEtclhIsfAUppD43V0j40cPTld3pv/0X/MlrZSZ6jowIaQQzFwP8M6rFU2z2mVYjDQ== + dependencies: + "@noble/hashes" "^1.2.0" + "@scure/base" "^1.1.1" + typeforce "^1.11.5" + wif "^2.0.6" + bip39@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/bip39/-/bip39-3.1.0.tgz#c55a418deaf48826a6ceb34ac55b3ee1577e18a3" @@ -3951,7 +3988,7 @@ bn.js@^4.11.8, bn.js@^4.11.9: resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA== -bn.js@^5.0.0, bn.js@^5.2.1: +bn.js@^5.1.1, bn.js@^5.2.1: version "5.2.1" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.1.tgz#0bc527a6a0d18d0aa8d5b0538ce4a77dccfa7b70" integrity sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ== @@ -4041,7 +4078,7 @@ braces@^3.0.2: dependencies: fill-range "^7.0.1" -brorand@^1.1.0: +brorand@^1.0.5, brorand@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" integrity sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w== @@ -4107,6 +4144,11 @@ buffer-alloc@^1.1.0: buffer-alloc-unsafe "^1.1.0" buffer-fill "^1.0.0" +buffer-crc32@^0.2.13: + version "0.2.13" + resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" + integrity sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ== + buffer-fill@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz" @@ -4117,10 +4159,10 @@ buffer-from@^1.0.0: resolved "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz" integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== -buffer@6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.1.tgz#3cbea8c1463e5a0779e30b66d4c88c6ffa182ac2" - integrity sha512-rVAXBwEcEoYtxnHSO5iWyhzV/O1WMtkUYWlfdLS7FjU4PnSJJHEfHXi/uHPI5EwltmOA794gN3bm3/pzuctWjQ== +buffer@6.0.3, buffer@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" + integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== dependencies: base64-js "^1.3.1" ieee754 "^1.2.1" @@ -4658,7 +4700,7 @@ crc-32@^1.2.2: resolved "https://registry.yarnpkg.com/crc-32/-/crc-32-1.2.2.tgz#3cad35a934b8bf71f25ca524b6da51fb7eace2ff" integrity sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ== -create-hash@^1.1.0, create-hash@^1.2.0: +create-hash@^1.1.0, create-hash@^1.1.2, create-hash@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196" integrity sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg== @@ -4829,7 +4871,7 @@ decamelize@^1.2.0: resolved "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz" integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA== -decimal.js@^10.4.2: +decimal.js@^10.2.0, decimal.js@^10.4.2: version "10.4.3" resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.3.tgz#1044092884d245d1b7f65725fa4ad4c6f781cc23" integrity sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA== @@ -6670,7 +6712,7 @@ http-proxy-agent@^5.0.0: agent-base "6" debug "4" -https-proxy-agent@^5.0.1: +https-proxy-agent@^5.0.0, https-proxy-agent@^5.0.1: version "5.0.1" resolved "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz" integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA== @@ -7244,10 +7286,10 @@ isomorphic-fetch@^2.1.1: node-fetch "^1.0.1" whatwg-fetch ">=0.10.0" -isomorphic-ws@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz#55fd4cd6c5e6491e76dc125938dd863f5cd4f2dc" - integrity sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w== +isomorphic-ws@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/isomorphic-ws/-/isomorphic-ws-5.0.0.tgz#e5529148912ecb9b451b46ed44d53dae1ce04bbf" + integrity sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw== isomorphic-ws@^5.0.0: version "5.0.0" @@ -7979,16 +8021,16 @@ jsonfile@^6.0.1: optionalDependencies: graceful-fs "^4.1.6" -jsonparse@^1.2.0: - version "1.3.1" - resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280" - integrity sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg== - jsonpointer@^5.0.0: version "5.0.1" resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-5.0.1.tgz#2110e0af0900fd37467b5907ecd13a7884a1b559" integrity sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ== +jsonschema@1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/jsonschema/-/jsonschema-1.2.2.tgz#83ab9c63d65bf4d596f91d81195e78772f6452bc" + integrity sha512-iX5OFQ6yx9NgbHCwse51ohhKgLuLL7Z5cNOeZOPIlDUtAMrxlruHLzVZxbltdHE5mEDXN+75oFOwq6Gn0MZwsA== + "jsx-ast-utils@^2.4.1 || ^3.0.0": version "3.3.3" resolved "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.3.tgz" @@ -9784,6 +9826,15 @@ react-native-modal@^13.0.1: prop-types "^15.6.2" react-native-animatable "1.3.3" +react-native-mymonero-core@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/react-native-mymonero-core/-/react-native-mymonero-core-0.3.1.tgz#5ef77034d2eec9f494e3a4cba78b7f91bf115901" + integrity sha512-FD0ePHvF3udSYvGMktQ8SruQ882+cOhlxp6plSCaqtI/Q/D+LTLFLYZ9bukA7iFA4AJ24Y3mkN8Aj5WZjSOOnw== + dependencies: + "@mymonero/mymonero-bigint" "^1.4.2" + "@mymonero/mymonero-money-format" "^1.4.2" + "@mymonero/mymonero-nettype" "^1.4.2" + react-native-pager-view@6.1.2: version "6.1.2" resolved "https://registry.yarnpkg.com/react-native-pager-view/-/react-native-pager-view-6.1.2.tgz#3522079b9a9d6634ca5e8d153bc0b4d660254552" @@ -10248,6 +10299,62 @@ ripemd160@^2.0.0, ripemd160@^2.0.1: hash-base "^3.0.0" inherits "^2.0.1" +ripple-address-codec@^4.1.1, ripple-address-codec@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ripple-address-codec/-/ripple-address-codec-4.3.0.tgz#45edeb0312b4fe4607b37b7c4cff467802ad571d" + integrity sha512-Tvd81i7hpDmNqHvkj6iYlj8Tv3I1Romw5gfjni9eacewJvGV2xe+p2y0FAw39z72qfciRMhQyHvpnviBcWVBNw== + dependencies: + base-x "^3.0.9" + create-hash "^1.1.2" + +ripple-binary-codec@^1.1.3: + version "1.7.1" + resolved "https://registry.yarnpkg.com/ripple-binary-codec/-/ripple-binary-codec-1.7.1.tgz#ae352eba8c3ebc6f509954e239acaa63b0f5dce9" + integrity sha512-DuVCZFSOXXPj4Njaj4+8XbYYXQBB+rrnhKd3ON+TtlmtwJUXryc59jKLMGUItqkdf8TAc89pb9+iU3WKlQrhtw== + dependencies: + assert "^2.0.0" + big-integer "^1.6.48" + buffer "6.0.3" + create-hash "^1.2.0" + decimal.js "^10.2.0" + ripple-address-codec "^4.3.0" + +ripple-keypairs@^1.0.3: + version "1.3.0" + resolved "https://registry.yarnpkg.com/ripple-keypairs/-/ripple-keypairs-1.3.0.tgz#fb28f15d0c764e36af7b25c4c782c3997abf84ad" + integrity sha512-LzM3Up9Pwz3dYqnczzNptimN3AxtjeGbDGeiOzREzbkslKiZcJ615b/ghBN4H23SC6W1GAL95juEzzimDi4THw== + dependencies: + bn.js "^5.1.1" + brorand "^1.0.5" + elliptic "^6.5.4" + hash.js "^1.0.3" + ripple-address-codec "^4.3.0" + +ripple-lib-transactionparser@0.8.2: + version "0.8.2" + resolved "https://registry.yarnpkg.com/ripple-lib-transactionparser/-/ripple-lib-transactionparser-0.8.2.tgz#7aaad3ba1e1aeee1d5bcff32334a7a838f834dce" + integrity sha512-1teosQLjYHLyOQrKUQfYyMjDR3MAq/Ga+MJuLUfpBMypl4LZB4bEoMcmG99/+WVTEiZOezJmH9iCSvm/MyxD+g== + dependencies: + bignumber.js "^9.0.0" + lodash "^4.17.15" + +ripple-lib@^1.10.1: + version "1.10.1" + resolved "https://registry.yarnpkg.com/ripple-lib/-/ripple-lib-1.10.1.tgz#9c353702792b25465cdb269265d6f5bb27b1471b" + integrity sha512-OQk+Syl2JfxKxV2KuF/kBMtnh012I5tNnziP3G4WDGCGSIAgeqkOgkR59IQ0YDNrs1YW8GbApxrdMSRi/QClcA== + dependencies: + "@types/lodash" "^4.14.136" + "@types/ws" "^7.2.0" + bignumber.js "^9.0.0" + https-proxy-agent "^5.0.0" + jsonschema "1.2.2" + lodash "^4.17.4" + ripple-address-codec "^4.1.1" + ripple-binary-codec "^1.1.3" + ripple-keypairs "^1.0.3" + ripple-lib-transactionparser "0.8.2" + ws "^7.2.0" + rpc-websockets@^7.4.2: version "7.5.1" resolved "https://registry.yarnpkg.com/rpc-websockets/-/rpc-websockets-7.5.1.tgz#e0a05d525a97e7efc31a0617f093a13a2e10c401" From 33281e251bb7b65250f5f2c9b699cf5400d383b0 Mon Sep 17 00:00:00 2001 From: illiaa Date: Tue, 1 Aug 2023 19:56:40 +0300 Subject: [PATCH 021/509] rewrite blockchains folders --- crypto/blockchains/one/OneScannerProcessor.ts | 518 ++++++------ .../one/OneScannerProcessorErc20.ts | 463 ++++++----- crypto/blockchains/one/ext/OneUtils.ts | 415 +++++----- crypto/blockchains/one/stores/OneTmpDS.ts | 66 +- crypto/blockchains/sol/SolAddressProcessor.ts | 104 ++- crypto/blockchains/sol/SolScannerProcessor.ts | 110 +-- .../blockchains/sol/SolScannerProcessorSpl.ts | 49 +- crypto/blockchains/sol/SolTokenProcessor.ts | 53 +- .../blockchains/sol/SolTransferProcessor.ts | 7 +- .../sol/SolTransferProcessorSpl.ts | 12 +- crypto/blockchains/sol/ext/SolInstructions.ts | 22 +- crypto/blockchains/sol/ext/SolStakeUtils.ts | 780 +++++++++--------- crypto/blockchains/sol/ext/SolUtils.ts | 42 +- crypto/blockchains/sol/stores/SolTmpDS.ts | 87 +- crypto/blockchains/trx/TrxAddressProcessor.ts | 44 +- .../trx/basic/TrxNodeInfoProvider.ts | 16 +- .../trx/basic/TrxTransactionsProvider.ts | 172 ++-- .../trx/basic/TrxTransactionsTrc20Provider.ts | 125 +-- .../trx/basic/TrxTrongridProvider.ts | 84 +- .../trx/basic/TrxTronscanProvider.ts | 72 +- crypto/blockchains/trx/ext/TronStakeUtils.ts | 92 ++- crypto/blockchains/trx/ext/TronUtils.ts | 149 ++-- .../trx/providers/TrxSendProvider.ts | 439 +++++----- src/constants/config.ts | 6 +- 24 files changed, 2161 insertions(+), 1766 deletions(-) diff --git a/crypto/blockchains/one/OneScannerProcessor.ts b/crypto/blockchains/one/OneScannerProcessor.ts index 419b435c2..622027555 100644 --- a/crypto/blockchains/one/OneScannerProcessor.ts +++ b/crypto/blockchains/one/OneScannerProcessor.ts @@ -1,234 +1,284 @@ -/** - * https://docs.harmony.one/home/developers/api/methods/account-methods/hmy_getbalance - * https://docs.harmony.one/home/developers/api/methods/transaction-related-methods/hmy_gettransactionshistory#api-v2 - */ -import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; -import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings'; -import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; -import OneUtils from '@crypto/blockchains/one/ext/OneUtils'; -import BlocksoftUtils from '@crypto/common/BlocksoftUtils'; -import config from '@app/config/config'; - -export default class OneScannerProcessor { - _blocksToConfirm = 10; - - constructor(settings) { - this._settings = settings; - } - - /** - * @param {string} address - * @param {*} additionalData - * @param {string} walletHash - * @return {Promise<{balance:*, unconfirmed:*, provider:string}>} - */ - async getBalanceBlockchain(address, additionalData, walletHash) { - const oneAddress = OneUtils.toOneAddress(address); - BlocksoftCryptoLog.log( - this._settings.currencyCode + - ' OneScannerProcessor.getBalanceBlockchain started ' + - address + - ' ' + - oneAddress - ); - try { - const apiPath = BlocksoftExternalSettings.getStatic('ONE_SERVER'); - const data = { - jsonrpc: '2.0', - id: 1, - method: 'hmy_getBalance', - params: [oneAddress, 'latest'] - }; - const res = await BlocksoftAxios._request(apiPath, 'POST', data); - if (typeof res.data === 'undefined') { - return false; - } - if (typeof res.data.error !== 'undefined') { - throw new Error(JSON.stringify(res.data.error)); - } - if (typeof res.data.result === 'undefined') { - return false; - } - const balance = BlocksoftUtils.hexToDecimalBigger(res.data.result); - return { - balance, - unconfirmed: 0 - }; - } catch (e) { - if (config.debug.cryptoErrors) { - console.log( - this._settings.currencyCode + - ' OneScannerProcessor.getBalanceBlockchain address ' + - address + - ' ' + - oneAddress + - ' error ' + - e.message - ); - } - BlocksoftCryptoLog.log( - this._settings.currencyCode + - ' OneScannerProcessor.getBalanceBlockchain address ' + - address + - ' ' + - oneAddress + - ' error ' + - e.message - ); - return false; - } - } - - /** - * @param {string} scanData.account.address - * @param {string} scanData.account.walletHash - * @return {Promise<[UnifiedTransaction]>} - */ - async getTransactionsBlockchain(scanData) { - const { address } = scanData.account; - const oneAddress = OneUtils.toOneAddress(address); - BlocksoftCryptoLog.log( - this._settings.currencyCode + - ' OneScannerProcessor.getTransactionsBlockchain started ' + - address + - ' ' + - oneAddress - ); - try { - const apiPath = BlocksoftExternalSettings.getStatic('ONE_SERVER'); - const data = { - jsonrpc: '2.0', - id: 1, - method: 'hmyv2_getTransactionsHistory', - params: [ - { - address: oneAddress, - pageIndex: 0, - pageSize: 20, - fullTx: true, - txType: 'ALL', - order: 'DESC' - } - ] - }; - const res = await BlocksoftAxios._request(apiPath, 'POST', data); - if ( - typeof res.data === 'undefined' || - typeof res.data.result === 'undefined' || - typeof res.data.result.transactions === 'undefined' - ) { - return false; - } - const transactions = []; - for (const tx of res.data.result.transactions) { - const transaction = await this._unifyTransaction( - address, - oneAddress, - tx - ); - if (transaction) { - transactions.push(transaction); - } - } - return transactions; - } catch (e) { - if (config.debug.cryptoErrors) { - console.log( - this._settings.currencyCode + - ' OneScannerProcessor.getTransactionsBlockchain address ' + - address + - ' error ' + - e.message - ); - } - BlocksoftCryptoLog.log( - this._settings.currencyCode + - ' OneScannerProcessor.getTransactionsBlockchain address ' + - address + - ' error ' + - e.message - ); - return false; - } - } - - /** - * - * @param {string} address - * @param {string} oneAddress - * @param {Object} transaction - * @param {string} transaction.blockHash - * @param {string} transaction.blockNumber - * @param {string} transaction.ethHash - * @param {string} transaction.from - * @param {string} transaction.gas - * @param {string} transaction.gasPrice - * @param {string} transaction.hash - * @param {string} transaction.input "0x095ea7b3000000000000000000000000d0cb3e55449646c9735d53e83eea5eb7e97a52dcffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", - * @param {string} transaction.nonce - * @param {string} transaction.shardID - * @param {string} transaction.timestamp - * @param {string} transaction.to - * @param {string} transaction.toShardID - * @param {string} transaction.value - * @return {Promise} - * @private - */ - async _unifyTransaction(address, oneAddress, transaction) { - let formattedTime = transaction.timestamp; - try { - formattedTime = BlocksoftUtils.toDate(transaction.timestamp); - } catch (e) { - e.message += - ' timestamp error transaction data ' + JSON.stringify(transaction); - throw e; - } - - const confirmations = (new Date().getTime() - transaction.timestamp) / 60; - const addressAmount = transaction.value; - - let transactionStatus = 'confirming'; - if (confirmations > 2) { - transactionStatus = 'success'; - } - - const isOutcome = - address.toLowerCase() === transaction.from.toLowerCase() || - oneAddress.toLowerCase() === transaction.from.toLowerCase(); - const isIncome = - address.toLowerCase() === transaction.to.toLowerCase() || - oneAddress.toLowerCase() === transaction.to.toLowerCase(); - const tx = { - transactionHash: transaction.ethHash.toLowerCase(), - blockHash: transaction.blockHash, - blockNumber: +transaction.blockNumber, - blockTime: formattedTime, - blockConfirmations: confirmations, - transactionDirection: isOutcome - ? isIncome - ? 'self' - : 'outcome' - : 'income', - addressFrom: isOutcome ? '' : transaction.from, - addressFromBasic: transaction.from.toLowerCase(), - addressTo: isIncome ? '' : transaction.to, - addressToBasic: transaction.to, - addressAmount, - transactionStatus: transactionStatus, - inputValue: transaction.input - }; - const additional = { - nonce: transaction.nonce, - gas: transaction.gas, - gasPrice: transaction.gasPrice, - transactionIndex: transaction.transactionIndex - }; - tx.transactionJson = additional; - tx.transactionFee = BlocksoftUtils.mul( - transaction.gasUsed, - transaction.gasPrice - ).toString(); - - return tx; - } -} +/** + * https://docs.harmony.one/home/developers/api/methods/account-methods/hmy_getbalance + * https://docs.harmony.one/home/developers/api/methods/transaction-related-methods/hmy_gettransactionshistory#api-v2 + */ +import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; +import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings'; +import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; +import OneUtils from '@crypto/blockchains/one/ext/OneUtils'; +import BlocksoftUtils from '@crypto/common/BlocksoftUtils'; +import config from '@constants/config'; + +interface UnifiedTransaction { + transactionHash: string; + blockHash: string; + blockNumber: number; + blockTime: string; + blockConfirmations: number; + transactionDirection: 'self' | 'outcome' | 'income'; + addressFrom: string; + addressFromBasic: string; + addressTo: string; + addressToBasic: string; + addressAmount: string; + transactionStatus: string; + inputValue: string; + transactionJson: { + nonce: string; + gas: string; + gasPrice: string; + transactionIndex: string; + }; + transactionFee: string; +} + +export default class OneScannerProcessor { + private _blocksToConfirm = 10; + private _settings: any; // Replace 'any' with the actual type of settings + + constructor(settings: any) { + this._settings = settings; + } + + /** + * @param {string} address + * @param {*} additionalData + * @param {string} walletHash + * @return {Promise<{balance:*, unconfirmed:*, provider:string}>} + */ + async getBalanceBlockchain( + address: string, + additionalData: any, + walletHash: string + ): Promise<{ balance: any; unconfirmed: any; provider: string }> { + const oneAddress = OneUtils.toOneAddress(address); + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' OneScannerProcessor.getBalanceBlockchain started ' + + address + + ' ' + + oneAddress + ); + try { + const apiPath = BlocksoftExternalSettings.getStatic('ONE_SERVER'); + const data = { + jsonrpc: '2.0', + id: 1, + method: 'hmy_getBalance', + params: [oneAddress, 'latest'] + }; + const res = await BlocksoftAxios._request(apiPath, 'POST', data); + if (typeof res.data === 'undefined') { + return false; + } + if (typeof res.data.error !== 'undefined') { + throw new Error(JSON.stringify(res.data.error)); + } + if (typeof res.data.result === 'undefined') { + return false; + } + const balance = BlocksoftUtils.hexToDecimalBigger(res.data.result); + return { + provider: '', + balance, + unconfirmed: 0 + }; + } catch (e: any) { + if (config.debug.cryptoErrors) { + console.log( + this._settings.currencyCode + + ' OneScannerProcessor.getBalanceBlockchain address ' + + address + + ' ' + + oneAddress + + ' error ' + + e.message + ); + } + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' OneScannerProcessor.getBalanceBlockchain address ' + + address + + ' ' + + oneAddress + + ' error ' + + e.message + ); + return false; + } + } + + /** + * @param {string} scanData.account.address + * @param {string} scanData.account.walletHash + * @return {Promise<[UnifiedTransaction]>} + */ + async getTransactionsBlockchain(scanData: { + account: { address: string; walletHash: string }; + }): Promise { + const { address } = scanData.account; + const oneAddress = OneUtils.toOneAddress(address); + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' OneScannerProcessor.getTransactionsBlockchain started ' + + address + + ' ' + + oneAddress + ); + try { + const apiPath = BlocksoftExternalSettings.getStatic('ONE_SERVER'); + const data = { + jsonrpc: '2.0', + id: 1, + method: 'hmyv2_getTransactionsHistory', + params: [ + { + address: oneAddress, + pageIndex: 0, + pageSize: 20, + fullTx: true, + txType: 'ALL', + order: 'DESC' + } + ] + }; + const res = await BlocksoftAxios._request(apiPath, 'POST', data); + if ( + typeof res.data === 'undefined' || + typeof res.data.result === 'undefined' || + typeof res.data.result.transactions === 'undefined' + ) { + return false; + } + const transactions: UnifiedTransaction[] = []; + for (const tx of res.data.result.transactions) { + const transaction = await this._unifyTransaction( + address, + oneAddress, + tx + ); + if (transaction) { + transactions.push(transaction); + } + } + return transactions; + } catch (e: any) { + if (config.debug.cryptoErrors) { + console.log( + this._settings.currencyCode + + ' OneScannerProcessor.getTransactionsBlockchain address ' + + address + + ' error ' + + e.message + ); + } + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' OneScannerProcessor.getTransactionsBlockchain address ' + + address + + ' error ' + + e.message + ); + return false; + } + } + + /** + * + * @param {string} address + * @param {string} oneAddress + * @param {Object} transaction + * @param {string} transaction.blockHash + * @param {string} transaction.blockNumber + * @param {string} transaction.ethHash + * @param {string} transaction.from + * @param {string} transaction.gas + * @param {string} transaction.gasPrice + * @param {string} transaction.hash + * @param {string} transaction.input "0x095ea7b3000000000000000000000000d0cb3e55449646c9735d53e83eea5eb7e97a52dcffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + * @param {string} transaction.nonce + * @param {string} transaction.shardID + * @param {string} transaction.timestamp + * @param {string} transaction.to + * @param {string} transaction.toShardID + * @param {string} transaction.value + * @return {Promise} + * @private + */ + private async _unifyTransaction( + address: string, + oneAddress: string, + transaction: { + blockHash: string; + blockNumber: string; + ethHash: string; + from: string; + gas: string; + gasPrice: string; + hash: string; + input: string; + nonce: string; + shardID: string; + timestamp: string; + to: string; + toShardID: string; + value: string; + } + ): Promise { + let formattedTime = transaction.timestamp; + try { + formattedTime = BlocksoftUtils.toDate(transaction.timestamp); + } catch (e: any) { + e.message += + ' timestamp error transaction data ' + JSON.stringify(transaction); + throw e; + } + + const confirmations = + (new Date().getTime() - Number(transaction.timestamp)) / 60; + const addressAmount = transaction.value; + + let transactionStatus = 'confirming'; + if (confirmations > 2) { + transactionStatus = 'success'; + } + + const isOutcome = + address.toLowerCase() === transaction.from.toLowerCase() || + oneAddress.toLowerCase() === transaction.from.toLowerCase(); + const isIncome = + address.toLowerCase() === transaction.to.toLowerCase() || + oneAddress.toLowerCase() === transaction.to.toLowerCase(); + const tx: UnifiedTransaction = { + transactionHash: transaction.ethHash.toLowerCase(), + blockHash: transaction.blockHash, + blockNumber: +transaction.blockNumber, + blockTime: formattedTime, + blockConfirmations: confirmations, + transactionDirection: isOutcome + ? isIncome + ? 'self' + : 'outcome' + : 'income', + addressFrom: isOutcome ? '' : transaction.from, + addressFromBasic: transaction.from.toLowerCase(), + addressTo: isIncome ? '' : transaction.to, + addressToBasic: transaction.to, + addressAmount, + transactionStatus, + inputValue: transaction.input, + transactionJson: { + nonce: transaction.nonce, + gas: transaction.gas, + gasPrice: transaction.gasPrice, + transactionIndex: transaction.shardID // Change 'shardID' to the actual transaction index property name + }, + transactionFee: BlocksoftUtils.mul( + transaction.gasUsed, + transaction.gasPrice + ).toString() + }; + + return tx; + } +} diff --git a/crypto/blockchains/one/OneScannerProcessorErc20.ts b/crypto/blockchains/one/OneScannerProcessorErc20.ts index d5923deaf..cf1528019 100644 --- a/crypto/blockchains/one/OneScannerProcessorErc20.ts +++ b/crypto/blockchains/one/OneScannerProcessorErc20.ts @@ -1,216 +1,247 @@ -/** - * https://docs.harmony.one/home/developers/api/methods/account-methods/hmy_getbalance - * https://docs.harmony.one/home/developers/api/methods/transaction-related-methods/hmy_gettransactionshistory#api-v2 - */ -import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; -import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings'; -import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; -import OneUtils from '@crypto/blockchains/one/ext/OneUtils'; -import BlocksoftUtils from '@crypto/common/BlocksoftUtils'; -import EthScannerProcessorErc20 from '@crypto/blockchains/eth/EthScannerProcessorErc20'; - -import config from '@app/config/config'; -import OneTmpDS from './stores/OneTmpDS'; - -const CACHE_TOKENS = {}; - -export default class OneScannerProcessorErc20 extends EthScannerProcessorErc20 { - _blocksToConfirm = 10; - - /** - * @param {string} scanData.account.address - * @param {string} scanData.account.walletHash - * @return {Promise<[UnifiedTransaction]>} - */ - async getTransactionsBlockchain(scanData) { - const { address } = scanData.account; - const oneAddress = OneUtils.toOneAddress(address); - BlocksoftCryptoLog.log( - this._settings.currencyCode + - ' OneScannerProcessorErc20.getTransactionsBlockchain started ' + - address + - ' ' + - oneAddress - ); - try { - CACHE_TOKENS[address] = await OneTmpDS.getCache(address); - const apiPath = BlocksoftExternalSettings.getStatic('ONE_SERVER'); - const data = { - jsonrpc: '2.0', - id: 1, - method: 'hmyv2_getTransactionsHistory', - params: [ - { - address: oneAddress, - pageIndex: 0, - pageSize: 20, - fullTx: true, - txType: 'ALL', - order: 'DESC' - } - ] - }; - const res = await BlocksoftAxios._request(apiPath, 'POST', data); - if ( - typeof res.data === 'undefined' || - typeof res.data.result === 'undefined' || - typeof res.data.result.transactions === 'undefined' - ) { - return false; - } - const transactions = []; - let firstTransaction = false; - for (const tx of res.data.result.transactions) { - if ( - typeof CACHE_TOKENS[address] !== 'undefined' && - typeof CACHE_TOKENS[address][this._tokenAddress] !== 'undefined' - ) { - const diff = tx.timestamp - CACHE_TOKENS[address][this._tokenAddress]; - const diffNow = (new Date().getTime() - tx.timestamp) / 60; - if (diff < -20) { - continue; - } - if (diff <= 0) { - if (diffNow > 100) { - continue; - } - } - } - if (!firstTransaction) { - firstTransaction = tx.timestamp; - } - const transaction = await this._unifyTransaction( - address, - oneAddress, - tx - ); - if (transaction) { - transactions.push(transaction); - } - } - CACHE_TOKENS[address][this._tokenAddress] = firstTransaction; - await OneTmpDS.saveCache(address, this._tokenAddress, firstTransaction); - return transactions; - } catch (e) { - if (config.debug.cryptoErrors) { - console.log( - this._settings.currencyCode + - ' OneScannerProcessorErc20.getTransactionsBlockchain address ' + - address + - ' error ' + - e.message - ); - } - BlocksoftCryptoLog.log( - this._settings.currencyCode + - ' OneScannerProcessorErc20.getTransactionsBlockchain address ' + - address + - ' error ' + - e.message - ); - return false; - } - } - - /** - * - * @param {string} address - * @param {string} oneAddress - * @param {Object} transaction - * @param {string} transaction.blockHash - * @param {string} transaction.blockNumber - * @param {string} transaction.ethHash - * @param {string} transaction.from - * @param {string} transaction.gas - * @param {string} transaction.gasPrice - * @param {string} transaction.hash - * @param {string} transaction.input "0x095ea7b3000000000000000000000000d0cb3e55449646c9735d53e83eea5eb7e97a52dcffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", - * @param {string} transaction.nonce - * @param {string} transaction.shardID - * @param {string} transaction.timestamp - * @param {string} transaction.to - * @param {string} transaction.toShardID - * @param {string} transaction.value - * @return {Promise} - * @private - */ - async _unifyTransaction(address, oneAddress, transaction) { - const contractEvents = await this._token.getPastEvents('Transfer', { - fromBlock: transaction.blockNumber, - toBlock: transaction.blockNumber - }); - if (!contractEvents) { - return false; - } - let foundEventFrom = false; - let foundEventTo = false; - let foundEventSelf = false; - let addressAmount = 0; - for (const tmp of contractEvents) { - if (tmp.transactionHash !== transaction.ethHash) { - continue; - } - if (tmp.returnValues.to.toLowerCase() === address.toLowerCase()) { - if (tmp.returnValues.from.toLowerCase() === address.toLowerCase()) { - foundEventSelf = tmp; - } else { - foundEventTo = tmp; - addressAmount = addressAmount * 1 + tmp.returnValues.value * 1; - } - } else if ( - tmp.returnValues.from.toLowerCase() === address.toLowerCase() - ) { - foundEventFrom = tmp; - addressAmount = addressAmount * 1 - tmp.returnValues.value * 1; - } - } - if (!foundEventSelf && !foundEventTo && !foundEventFrom) { - return false; - } - - let formattedTime = transaction.timestamp; - try { - formattedTime = BlocksoftUtils.toDate(transaction.timestamp); - } catch (e) { - e.message += - ' timestamp error transaction data ' + JSON.stringify(transaction); - throw e; - } - - const confirmations = (new Date().getTime() - transaction.timestamp) / 60; - let transactionStatus = 'confirming'; - if (confirmations > 2) { - transactionStatus = 'success'; - } - - const tx = { - transactionHash: transaction.ethHash.toLowerCase(), - blockHash: transaction.blockHash, - blockNumber: +transaction.blockNumber, - blockTime: formattedTime, - blockConfirmations: confirmations, - transactionDirection: - addressAmount * 1 <= 0 ? (foundEventTo ? 'outcome' : 'self') : 'income', - addressFrom: foundEventFrom ? transaction.from : '', - addressFromBasic: transaction.from.toLowerCase(), - addressTo: foundEventTo ? transaction.to : '', - addressToBasic: transaction.to, - addressAmount, - transactionStatus: transactionStatus, - inputValue: transaction.input - }; - const additional = { - nonce: transaction.nonce, - gas: transaction.gas, - gasPrice: transaction.gasPrice, - transactionIndex: transaction.transactionIndex - }; - tx.transactionJson = additional; - tx.transactionFee = BlocksoftUtils.mul( - transaction.gasUsed, - transaction.gasPrice - ).toString(); - - return tx; - } -} +/** + * https://docs.harmony.one/home/developers/api/methods/account-methods/hmy_getbalance + * https://docs.harmony.one/home/developers/api/methods/transaction-related-methods/hmy_gettransactionshistory#api-v2 + */ +import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; +import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings'; +import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; +import OneUtils from '@crypto/blockchains/one/ext/OneUtils'; +import BlocksoftUtils from '@crypto/common/BlocksoftUtils'; +import EthScannerProcessorErc20 from '@crypto/blockchains/eth/EthScannerProcessorErc20'; + +import OneTmpDS from './stores/OneTmpDS'; +import config from '@constants/config'; + +interface UnifiedTransaction { + // TODO +} + +interface CacheTokens { + [address: string]: { [tokenAddress: string]: number }; +} + +const CACHE_TOKENS: CacheTokens = {}; + +export default class OneScannerProcessorErc20 extends EthScannerProcessorErc20 { + private _blocksToConfirm = 10; + + /** + * @param {string} scanData.account.address + * @param {string} scanData.account.walletHash + * @return {Promise<[UnifiedTransaction]>} + */ + async getTransactionsBlockchain(scanData: { + account: { address: string; walletHash: string }; + }): Promise { + const { address } = scanData.account; + const oneAddress = OneUtils.toOneAddress(address); + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' OneScannerProcessorErc20.getTransactionsBlockchain started ' + + address + + ' ' + + oneAddress + ); + try { + CACHE_TOKENS[address] = await OneTmpDS.getCache(address); + const apiPath = BlocksoftExternalSettings.getStatic('ONE_SERVER'); + const data = { + jsonrpc: '2.0', + id: 1, + method: 'hmyv2_getTransactionsHistory', + params: [ + { + address: oneAddress, + pageIndex: 0, + pageSize: 20, + fullTx: true, + txType: 'ALL', + order: 'DESC' + } + ] + }; + const res = await BlocksoftAxios._request(apiPath, 'POST', data); + if ( + typeof res.data === 'undefined' || + typeof res.data.result === 'undefined' || + typeof res.data.result.transactions === 'undefined' + ) { + return []; + } + const transactions: UnifiedTransaction[] = []; + let firstTransaction = false; + for (const tx of res.data.result.transactions) { + if ( + typeof CACHE_TOKENS[address] !== 'undefined' && + typeof CACHE_TOKENS[address][this._tokenAddress] !== 'undefined' + ) { + const diff = tx.timestamp - CACHE_TOKENS[address][this._tokenAddress]; + const diffNow = (new Date().getTime() - tx.timestamp) / 60; + if (diff < -20) { + continue; + } + if (diff <= 0) { + if (diffNow > 100) { + continue; + } + } + } + if (!firstTransaction) { + firstTransaction = tx.timestamp; + } + const transaction = await this._unifyTransaction( + address, + oneAddress, + tx + ); + if (transaction) { + transactions.push(transaction); + } + } + CACHE_TOKENS[address][this._tokenAddress] = Number(firstTransaction); + await OneTmpDS.saveCache(address, this._tokenAddress, firstTransaction); + return transactions; + } catch (e: any) { + if (config.debug.cryptoErrors) { + console.log( + this._settings.currencyCode + + ' OneScannerProcessorErc20.getTransactionsBlockchain address ' + + address + + ' error ' + + e.message + ); + } + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' OneScannerProcessorErc20.getTransactionsBlockchain address ' + + address + + ' error ' + + e.message + ); + return []; + } + } + + /** + * + * @param {string} address + * @param {string} oneAddress + * @param {Object} transaction + * @param {string} transaction.blockHash + * @param {string} transaction.blockNumber + * @param {string} transaction.ethHash + * @param {string} transaction.from + * @param {string} transaction.gas + * @param {string} transaction.gasPrice + * @param {string} transaction.hash + * @param {string} transaction.input "0x095ea7b3000000000000000000000000d0cb3e55449646c9735d53e83eea5eb7e97a52dcffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + * @param {string} transaction.nonce + * @param {string} transaction.shardID + * @param {string} transaction.timestamp + * @param {string} transaction.to + * @param {string} transaction.toShardID + * @param {string} transaction.value + * @return {Promise} + * @private + */ + async _unifyTransaction( + address: string, + oneAddress: string, + transaction: { + blockHash: string; + blockNumber: string; + ethHash: string; + from: string; + gas: string; + gasPrice: string; + hash: string; + input: string; + nonce: string; + shardID: string; + timestamp: string; + to: string; + toShardID: string; + value: string; + } + ): Promise { + const contractEvents = await this._token.getPastEvents('Transfer', { + fromBlock: transaction.blockNumber, + toBlock: transaction.blockNumber + }); + if (!contractEvents) { + return false; + } + let foundEventFrom = false; + let foundEventTo = false; + let foundEventSelf = false; + let addressAmount = 0; + for (const tmp of contractEvents) { + if (tmp.transactionHash !== transaction.ethHash) { + continue; + } + if (tmp.returnValues.to.toLowerCase() === address.toLowerCase()) { + if (tmp.returnValues.from.toLowerCase() === address.toLowerCase()) { + foundEventSelf = tmp; + } else { + foundEventTo = tmp; + addressAmount = addressAmount * 1 + tmp.returnValues.value * 1; + } + } else if ( + tmp.returnValues.from.toLowerCase() === address.toLowerCase() + ) { + foundEventFrom = tmp; + addressAmount = addressAmount * 1 - tmp.returnValues.value * 1; + } + } + if (!foundEventSelf && !foundEventTo && !foundEventFrom) { + return false; + } + + let formattedTime = transaction.timestamp; + try { + formattedTime = BlocksoftUtils.toDate(transaction.timestamp); + } catch (e: any) { + e.message += + ' timestamp error transaction data ' + JSON.stringify(transaction); + throw e; + } + + const confirmations = + // tslint:disable-next-line:radix + (new Date().getTime() - parseInt(transaction.timestamp)) / 60; + let transactionStatus = 'confirming'; + if (confirmations > 2) { + transactionStatus = 'success'; + } + + const tx: UnifiedTransaction = { + transactionHash: transaction.ethHash.toLowerCase(), + blockHash: transaction.blockHash, + blockNumber: +transaction.blockNumber, + blockTime: formattedTime, + blockConfirmations: confirmations, + transactionDirection: + addressAmount * 1 <= 0 ? (foundEventTo ? 'outcome' : 'self') : 'income', + addressFrom: foundEventFrom ? transaction.from : '', + addressFromBasic: transaction.from.toLowerCase(), + addressTo: foundEventTo ? transaction.to : '', + addressToBasic: transaction.to, + addressAmount, + transactionStatus: transactionStatus, + inputValue: transaction.input + }; + const additional = { + nonce: transaction.nonce, + gas: transaction.gas, + gasPrice: transaction.gasPrice, + transactionIndex: transaction.transactionIndex + }; + tx.transactionJson = additional; + tx.transactionFee = BlocksoftUtils.mul( + transaction.gasUsed, + transaction.gasPrice + ).toString(); + + return tx; + } +} diff --git a/crypto/blockchains/one/ext/OneUtils.ts b/crypto/blockchains/one/ext/OneUtils.ts index 80b02caa2..4b552b566 100644 --- a/crypto/blockchains/one/ext/OneUtils.ts +++ b/crypto/blockchains/one/ext/OneUtils.ts @@ -1,239 +1,238 @@ /** * @harmony-js/crypto/src/bech32.ts * https://github.com/harmony-one/sdk/blob/master/packages/harmony-crypto/src/bech32.ts - * const { toBech32 } = require("@harmony-js/crypto"); - * console.log("Using account: " + toBech32("0xxxxx", "one1")) + * import { toBech32 } from "@harmony-js/crypto"; + * console.log("Using account: " + toBech32("0xxxxx", "one1")); */ -const CHARSET = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l' -const GENERATOR = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3] - -const polymod = (values) => { - let chk = 1 - // tslint:disable-next-line - for (let p = 0; p < values.length; ++p) { - const top = chk >> 25 - chk = ((chk & 0x1ffffff) << 5) ^ values[p] - for (let i = 0; i < 5; ++i) { - if ((top >> i) & 1) { - chk ^= GENERATOR[i] - } - } - } - return chk -} - -const hrpExpand = (hrp) => { - const ret = [] - let p - for (p = 0; p < hrp.length; ++p) { - ret.push(hrp.charCodeAt(p) >> 5) - } - ret.push(0) - for (p = 0; p < hrp.length; ++p) { - ret.push(hrp.charCodeAt(p) & 31) - } - return Buffer.from(ret) -} +const CHARSET = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l'; +const GENERATOR = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3]; + +const polymod = (values: number[]): number => { + let chk = 1; + for (let p = 0; p < values.length; ++p) { + const top = chk >>> 25; + chk = ((chk & 0x1ffffff) << 5) ^ values[p]; + for (let i = 0; i < 5; ++i) { + if ((top >>> i) & 1) { + chk ^= GENERATOR[i]; + } + } + } + return chk; +}; -function createChecksum(hrp, data) { - const values = Buffer.concat([ - Buffer.from(hrpExpand(hrp)), - data, - Buffer.from([0, 0, 0, 0, 0, 0]) - ]) - // var values = hrpExpand(hrp).concat(data).concat([0, 0, 0, 0, 0, 0]); - const mod = polymod(values) ^ 1 - const ret = [] - for (let p = 0; p < 6; ++p) { - ret.push((mod >> (5 * (5 - p))) & 31) - } - return Buffer.from(ret) -} +const hrpExpand = (hrp: string): Buffer => { + const ret = []; + for (let p = 0; p < hrp.length; ++p) { + ret.push(hrp.charCodeAt(p) >>> 5); + } + ret.push(0); + for (let p = 0; p < hrp.length; ++p) { + ret.push(hrp.charCodeAt(p) & 31); + } + return Buffer.from(ret); +}; -const bech32Encode = (hrp, data) => { - const combined = Buffer.concat([data, createChecksum(hrp, data)]) - let ret = hrp + '1' - // tslint:disable-next-line - for (let p = 0; p < combined.length; ++p) { - ret += CHARSET.charAt(combined[p]) - } - return ret +function createChecksum(hrp: string, data: Buffer): Buffer { + const values = Buffer.concat([ + hrpExpand(hrp), + data, + Buffer.from([0, 0, 0, 0, 0, 0]) + ]); + const mod = polymod(Array.from(values)) ^ 1; + const ret = []; + for (let p = 0; p < 6; ++p) { + ret.push((mod >>> (5 * (5 - p))) & 31); + } + return Buffer.from(ret); } +const bech32Encode = (hrp: string, data: Buffer): string => { + const combined = Buffer.concat([data, createChecksum(hrp, data)]); + let ret = hrp + '1'; + for (let p = 0; p < combined.length; ++p) { + ret += CHARSET.charAt(combined[p]); + } + return ret; +}; const convertBits = ( - data, - fromWidth, - toWidth, - pad = true -) => { - let acc = 0 - let bits = 0 - const ret = [] - const maxv = (1 << toWidth) - 1 - // tslint:disable-next-line - for (let p = 0; p < data.length; ++p) { - const value = data[p] - if (value < 0 || value >> fromWidth !== 0) { - return null - } - acc = (acc << fromWidth) | value - bits += fromWidth - while (bits >= toWidth) { - bits -= toWidth - ret.push((acc >> bits) & maxv) - } - } - - if (pad) { - if (bits > 0) { - ret.push((acc << (toWidth - bits)) & maxv) - } - } else if (bits >= fromWidth || (acc << (toWidth - bits)) & maxv) { - return null - } - - return Buffer.from(ret) + data: Buffer, + fromWidth: number, + toWidth: number, + pad = true +): Buffer | null => { + let acc = 0; + let bits = 0; + const ret = []; + const maxv = (1 << toWidth) - 1; + for (let p = 0; p < data.length; ++p) { + const value = data[p]; + if (value < 0 || value >> fromWidth !== 0) { + return null; + } + acc = (acc << fromWidth) | value; + bits += fromWidth; + while (bits >= toWidth) { + bits -= toWidth; + ret.push((acc >>> bits) & maxv); + } + } + + if (pad) { + if (bits > 0) { + ret.push((acc << (toWidth - bits)) & maxv); + } + } else if (bits >= fromWidth || (acc << (toWidth - bits)) & maxv) { + return null; + } + + return Buffer.from(ret); +}; + +const bech32Decode = ( + bechString: string +): { hrp: string; data: Buffer } | null => { + let hasLower = false; + let hasUpper = false; + for (let p = 0; p < bechString.length; ++p) { + if (bechString.charCodeAt(p) < 33 || bechString.charCodeAt(p) > 126) { + return null; + } + if (bechString.charCodeAt(p) >= 97 && bechString.charCodeAt(p) <= 122) { + hasLower = true; + } + if (bechString.charCodeAt(p) >= 65 && bechString.charCodeAt(p) <= 90) { + hasUpper = true; + } + } + if (hasLower && hasUpper) { + return null; + } + bechString = bechString.toLowerCase(); + const pos = bechString.lastIndexOf('1'); + if (pos < 1 || pos + 7 > bechString.length || bechString.length > 90) { + return null; + } + const hrp = bechString.substring(0, pos); + const data = []; + for (let p = pos + 1; p < bechString.length; ++p) { + const d = CHARSET.indexOf(bechString.charAt(p)); + if (d === -1) { + return null; + } + data.push(d); + } + + try { + if (!verifyChecksum(hrp, Buffer.from(data))) { + return null; + } + } catch (e: any) { + e.message += ' in verifyChecksum'; + throw e; + } + + return { hrp, data: Buffer.from(data.slice(0, data.length - 6)) }; +}; + +function verifyChecksum(hrp: string, data: Buffer): boolean { + return polymod(Buffer.concat([hrpExpand(hrp), data])) === 1; } -const bech32Decode = (bechString) => { - let p; - let hasLower = false; - let hasUpper = false; - for (p = 0; p < bechString.length; ++p) { - if (bechString.charCodeAt(p) < 33 || bechString.charCodeAt(p) > 126) { - return null; - } - if (bechString.charCodeAt(p) >= 97 && bechString.charCodeAt(p) <= 122) { - hasLower = true; - } - if (bechString.charCodeAt(p) >= 65 && bechString.charCodeAt(p) <= 90) { - hasUpper = true; - } - } - if (hasLower && hasUpper) { - return null; - } - bechString = bechString.toLowerCase(); - const pos = bechString.lastIndexOf('1'); - if (pos < 1 || pos + 7 > bechString.length || bechString.length > 90) { - return null; - } - const hrp = bechString.substring(0, pos); - const data = []; - for (p = pos + 1; p < bechString.length; ++p) { - const d = CHARSET.indexOf(bechString.charAt(p)); - if (d === -1) { - return null; - } - data.push(d); +const toChecksumAddress = (address: string): string => { + if (typeof address !== 'string' || !address.match(/^0x[0-9A-Fa-f]{40}$/)) { + throw new Error('invalid address ' + address); + } + + address = address.toLowerCase(); + + const chars = address.substring(2).split(''); + + const hashed = new Uint8Array(40); + for (let i = 0; i < 40; i++) { + hashed[i] = chars[i].charCodeAt(0); + } + // hashed = bytes.arrayify(keccak256(hashed)) || hashed; + + for (let i = 0; i < 40; i += 2) { + if (hashed[i >> 1] >> 4 >= 8) { + chars[i] = chars[i].toUpperCase(); + } + if ((hashed[i >> 1] & 0x0f) >= 8) { + chars[i + 1] = chars[i + 1].toUpperCase(); } + } + return '0x' + chars.join(''); +}; + +const bech32Util = { + isOneAddress(address: string): boolean { + if ( + typeof address === 'undefined' || + typeof address.match === 'undefined' + ) { + return false; + } try { - if (!verifyChecksum(hrp, Buffer.from(data))) { - return null; - } - } catch (e) { - e.message += ' in verifyChecksum' - throw e + return !!address.match(/^one1[qpzry9x8gf2tvdw0s3jn54khce6mua7l]{38}/); + } catch (e: any) { + e.message += ' in match - address ' + JSON.stringify(address); + throw e; } + }, - return { hrp, data: Buffer.from(data.slice(0, data.length - 6)) }; -}; + toOneAddress(address: string, useHRP = 'one'): string { + if (address.indexOf('one') === 0) { + return address; + } -function verifyChecksum(hrp, data) { - return polymod(Buffer.concat([hrpExpand(hrp), data])) === 1; -} + const prepAddr = address.replace('0x', '').toLowerCase(); + + const addrBz = convertBits(Buffer.from(prepAddr, 'hex'), 8, 5); -const toChecksumAddress = (address)=> { - if (typeof address !== 'string' || !address.match(/^0x[0-9A-Fa-f]{40}$/)) { - throw new Error('invalid address ' + address); + if (addrBz === null) { + throw new Error('Could not convert byte Buffer to 5-bit Buffer'); } - address = address.toLowerCase(); + return bech32Encode(useHRP, addrBz); + }, - const chars = address.substring(2).split(''); + fromOneAddress(address: string, useHRP = 'one'): string { + let res; + try { + res = bech32Decode(address); + } catch (e: any) { + e.message += ' in bech32Decode - address ' + JSON.stringify(address); + throw e; + } - let hashed = new Uint8Array(40); - for (let i = 0; i < 40; i++) { - hashed[i] = chars[i].charCodeAt(0); + if (res === null) { + throw new Error('Invalid bech32 address'); } - // hashed = bytes.arrayify(keccak256(hashed)) || hashed; - for (let i = 0; i < 40; i += 2) { - if (hashed[i >> 1] >> 4 >= 8) { - chars[i] = chars[i].toUpperCase(); - } - if ((hashed[i >> 1] & 0x0f) >= 8) { - chars[i + 1] = chars[i + 1].toUpperCase(); - } + const { hrp, data } = res; + + if (hrp !== useHRP) { + throw new Error(`Expected hrp to be ${useHRP} but got ${hrp}`); } - return '0x' + chars.join(''); -}; + const buf = convertBits(data, 5, 8, false); + if (buf === null) { + throw new Error('Could not convert buffer to bytes'); + } -export default { - isOneAddress(address) { - if (typeof address === 'undefined' || typeof address.match === 'undefined') { - return false - } - try { - return !!address.match(/^one1[qpzry9x8gf2tvdw0s3jn54khce6mua7l]{38}/); - } catch (e) { - e.message += ' in match - address ' + JSON.stringify(address) - throw e - } - }, - - toOneAddress(address, useHRP = 'one') { - if (address.indexOf('one') === 0) { - return address - } - - const prepAddr = address.replace('0x', '').toLowerCase() - - const addrBz = convertBits( Buffer.from(prepAddr, 'hex'), 8, 5) - - if (addrBz === null) { - throw new Error('Could not convert byte Buffer to 5-bit Buffer') - } - - return bech32Encode(useHRP, addrBz) - }, - - fromOneAddress(address, useHRP = 'one') { - let res - try { - res = bech32Decode(address) - } catch (e) { - e.message += ' in bech32Decode - address ' + JSON.stringify(address) - throw e - } - - if (res === null) { - throw new Error('Invalid bech32 address') - } - - const { hrp, data } = res - - if (hrp !== useHRP) { - throw new Error(`Expected hrp to be ${useHRP} but got ${hrp}`) - } - - const buf = convertBits(data, 5, 8, false) - - if (buf === null) { - throw new Error('Could not convert buffer to bytes') - } - - try { - const tmp = toChecksumAddress('0x' + buf.toString('hex')) - return tmp - } catch (e) { - e.message += ' in toChecksumAddress' - throw e - } + try { + const tmp = toChecksumAddress('0x' + buf.toString('hex')); + return tmp; + } catch (e: any) { + e.message += ' in toChecksumAddress'; + throw e; } -} + } +}; + +export default bech32Util; diff --git a/crypto/blockchains/one/stores/OneTmpDS.ts b/crypto/blockchains/one/stores/OneTmpDS.ts index bf7742b09..6f84529da 100644 --- a/crypto/blockchains/one/stores/OneTmpDS.ts +++ b/crypto/blockchains/one/stores/OneTmpDS.ts @@ -1,45 +1,49 @@ - +// @ts-ignore import Database from '@app/appstores/DataSource/Database'; -const tableName = ' transactions_scanners_tmp' +const tableName = 'transactions_scanners_tmp'; class OneTmpDS { - /** - * @type {string} - * @private - */ - _currencyCode = 'ONE' + /** + * @type {string} + * @private + */ + private _currencyCode = 'ONE'; - async getCache(address) { - const res = await Database.query(` + async getCache(address: string): Promise<{ [key: string]: any }> { + const res = await Database.query(` SELECT tmp_key, tmp_sub_key, tmp_val FROM ${tableName} WHERE currency_code='${this._currencyCode}' AND address='${address}' AND tmp_key='last_tx' - `) - const tmp = {} - if (res.array) { - let row - for (row of res.array) { - tmp[row.tmp_sub_key] = row.tmp_val - } - } - return tmp + `); + const tmp: { [key: string]: any } = {}; + if (res.array) { + let row; + for (row of res.array) { + tmp[row.tmp_sub_key] = row.tmp_val; + } } + return tmp; + } - async saveCache(address, subKey, value) { - const now = new Date().toISOString() - const prepared = [{ - currency_code : this._currencyCode, - address : address, - tmp_key : 'last_tx', - tmp_sub_key : subKey, - tmp_val : value, - created_at : now - }] - await Database.setTableName(tableName).setInsertData({insertObjs : prepared}).insert() - } + async saveCache(address: string, subKey: string, value: any): Promise { + const now = new Date().toISOString(); + const prepared = [ + { + currency_code: this._currencyCode, + address: address, + tmp_key: 'last_tx', + tmp_sub_key: subKey, + tmp_val: value, + created_at: now + } + ]; + await Database.setTableName(tableName) + .setInsertData({ insertObjs: prepared }) + .insert(); + } } -export default new OneTmpDS() +export default new OneTmpDS(); diff --git a/crypto/blockchains/sol/SolAddressProcessor.ts b/crypto/blockchains/sol/SolAddressProcessor.ts index a64a4af9b..13e0dd882 100644 --- a/crypto/blockchains/sol/SolAddressProcessor.ts +++ b/crypto/blockchains/sol/SolAddressProcessor.ts @@ -6,48 +6,78 @@ * https://github.com/project-serum/spl-token-wallet * https://github.com/project-serum/spl-token-wallet/blob/master/src/utils/walletProvider/localStorage.js#L30 */ -import BlocksoftKeys from '@crypto/actions/BlocksoftKeys/BlocksoftKeys' -import XlmDerivePath from '@crypto/blockchains/xlm/ext/XlmDerivePath' +import BlocksoftKeys from '@crypto/actions/BlocksoftKeys/BlocksoftKeys'; +import XlmDerivePath from '@crypto/blockchains/xlm/ext/XlmDerivePath'; -const nacl = require('tweetnacl') -const bs58 = require('bs58') +const nacl = require('tweetnacl'); +const bs58 = require('bs58'); + +interface PrivateData { + mnemonic?: string; +} export default class SolAddressProcessor { + private root: any; + + async setBasicRoot(root: any) { + this.root = root; + } - async setBasicRoot(root) { - this.root = root + /** + * @param {string|Buffer} privateKey - not used as bip32 private is outdated + * @param {*} data + * @param superPrivateData + * @param seed + * @param source + * @returns {Promise<{privateKey: string, address: string}>} + */ + async getAddress( + privateKey: string | Buffer, + data: any = {}, + superPrivateData: PrivateData = {}, + seed?: Buffer + ): Promise<{ privateKey: string; address: string }> { + if ( + typeof superPrivateData.mnemonic === 'undefined' || + !superPrivateData.mnemonic + ) { + throw new Error('need mnemonic'); + } + if (typeof seed === 'undefined') { + seed = await BlocksoftKeys.getSeedCached(superPrivateData.mnemonic); + } + if (typeof seed === 'undefined') { + throw new Error('seed is undefined'); + } + const seedHex = seed.toString('hex'); + let derivationPath = data.derivationPath; + if ( + derivationPath !== `m/44'/501'/0'` && + derivationPath !== `m/44'/501'/0'/0'` + ) { + derivationPath = `m/44'/501'/0'/0'`; } - /** - * @param {string|Buffer} privateKey - not used as bip32 private is outdated - * @param {*} data - * @returns {Promise<{privateKey: string, address: string}>} - */ - async getAddress(privateKey, data = {}, superPrivateData = {}, seed, source = '') { - if (typeof superPrivateData.mnemonic === 'undefined' || !superPrivateData.mnemonic) { - throw new Error('need mnemonic') - } - if (!seed) { - seed = await BlocksoftKeys.getSeedCached(superPrivateData.mnemonic) - } - const seedHex = seed.toString('hex') - let derivationPath = data.derivationPath - if (derivationPath !== `m/44'/501'/0'` && derivationPath !== `m/44'/501'/0'/0'`) { - derivationPath = `m/44'/501'/0'/0'` - } - - if (seedHex.length < 128) { - throw new Error('bad seedHex') - } - - const res = XlmDerivePath(seedHex, derivationPath) - const key = nacl.sign.keyPair.fromSeed(res.key) - - const naclPubKey = Buffer.from(key.publicKey).toString('hex') - const naclSecretKey = Buffer.from(key.secretKey).toString('hex') - const address = bs58.encode(key.publicKey) - - const ret = { address, privateKey : naclSecretKey, addedData: { naclPubKey, derivationPath : derivationPath.replace(/[']/g, 'quote'), seedHexPart: seedHex.substr(0, 5) } } - return ret + if (seedHex.length < 128) { + throw new Error('bad seedHex'); } + + const res = XlmDerivePath(seedHex, derivationPath); + const key = nacl.sign.keyPair.fromSeed(res.key); + + const naclPubKey = Buffer.from(key.publicKey).toString('hex'); + const naclSecretKey = Buffer.from(key.secretKey).toString('hex'); + const address = bs58.encode(key.publicKey); + + const ret = { + address, + privateKey: naclSecretKey, + addedData: { + naclPubKey, + derivationPath: derivationPath.replace(/[']/g, 'quote'), + seedHexPart: seedHex.substr(0, 5) + } + }; + return ret; + } } diff --git a/crypto/blockchains/sol/SolScannerProcessor.ts b/crypto/blockchains/sol/SolScannerProcessor.ts index 389cbc230..f63a8f782 100644 --- a/crypto/blockchains/sol/SolScannerProcessor.ts +++ b/crypto/blockchains/sol/SolScannerProcessor.ts @@ -8,29 +8,42 @@ import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings' import SolTmpDS from '@crypto/blockchains/sol/stores/SolTmpDS'; -import config from '@app/config/config'; import SolUtils from '@crypto/blockchains/sol/ext/SolUtils'; +import config from '@constants/config'; -const CACHE_FROM_DB = {}; -const CACHE_TXS = {}; +interface UnifiedTransaction { + transactionHash: string; + blockHash: string; + blockNumber: number; + blockTime: Date; + blockConfirmations: number; + transactionDirection: 'income' | 'outcome' | 'swap_income' | 'swap_outcome'; + addressFrom: string; + addressTo: string; + addressAmount: string; + transactionStatus: 'new' | 'success' | 'confirming' | 'fail'; + transactionFee: number; + transactionJson?: { memo: string }; +} + +const CACHE_FROM_DB: Record = {}; +const CACHE_TXS: Record = {}; const CACHE_VALID_TIME = 120000; let CACHE_LAST_BLOCK = 0; export default class SolScannerProcessor { - constructor(settings) { + private _settings: any; + private tokenAddress: string; + + constructor(settings: any) { this._settings = settings; this.tokenAddress = typeof settings.tokenAddress !== 'undefined' ? settings.tokenAddress : ''; } - /** - * @param {string} address - * @return {Promise<{balance, provider}>} - * https://docs.solana.com/developing/clients/jsonrpc-api#getaccountinfo - * https://docs.solana.com/developing/clients/jsonrpc-api#getconfirmedsignaturesforaddress2 - * curl https://solana-api.projectserum.com -X POST -H "Content-Type: application/json" -d '{'jsonrpc":"2.0", "id":1, "method":"getBalance", "params":["9mnBdsuL1x24HbU4oeNDBAYVAGg2vVndkRAc18kPNqCJ']}' - */ - async getBalanceBlockchain(address) { + async getBalanceBlockchain( + address: string + ): Promise<{ balance: number; provider: string } | false> { address = address.trim(); BlocksoftCryptoLog.log( this._settings.currencyCode + @@ -74,16 +87,12 @@ export default class SolScannerProcessor { ); return false; } - return { balance, unconfirmed: 0, provider: 'solana-api' }; + return { balance, provider: 'solana-api' }; } - /** - * @param {string} scanData.account.address - * @return {Promise<[UnifiedTransaction]>} - * https://docs.solana.com/developing/clients/jsonrpc-api#getsignaturesforaddress - * curl https://api.mainnet-beta.solana.com -X POST -H "Content-Type: application/json" -d '{'jsonrpc": "2.0","id": 1,"method": "getConfirmedSignaturesForAddress2","params": ["9mnBdsuL1x24HbU4oeNDBAYVAGg2vVndkRAc18kPNqCJ",{"limit': 1}]}' - */ - async getTransactionsBlockchain(scanData, source) { + async getTransactionsBlockchain(scanData: { + account: { address: string }; + }): Promise { const address = scanData.account.address.trim(); const lastHashVar = address + this.tokenAddress; this._cleanCache(); @@ -96,18 +105,13 @@ export default class SolScannerProcessor { jsonrpc: '2.0', id: 1, method: 'getConfirmedSignaturesForAddress2', - params: [ - address, - { - limit: 100 - } - ] + params: [address, { limit: 100 }] }; if ( CACHE_FROM_DB[lastHashVar] && - typeof CACHE_FROM_DB[lastHashVar]['last_hash'] !== 'undefined' + typeof CACHE_FROM_DB[lastHashVar].last_hash !== 'undefined' ) { - data.params[1].until = CACHE_FROM_DB[lastHashVar]['last_hash']; + data.params[1].until = CACHE_FROM_DB[lastHashVar].last_hash; } const apiPath = BlocksoftExternalSettings.getStatic('SOL_SERVER'); const res = await BlocksoftAxios._request(apiPath, 'POST', data); @@ -126,7 +130,7 @@ export default class SolScannerProcessor { address ); return transactions; - } catch (e) { + } catch (e: any) { BlocksoftCryptoLog.log( this._settings.currencyCode + ' SolScannerProcessor getTransactionsBlockchain address ' + @@ -138,8 +142,12 @@ export default class SolScannerProcessor { } } - async _unifyTransactions(address, result, lastHashVar) { - const transactions = []; + private async _unifyTransactions( + address: string, + result: any[], + lastHashVar: string + ): Promise { + const transactions: UnifiedTransaction[] = []; let lastHash = false; let hasError = false; for (const tx of result) { @@ -155,7 +163,7 @@ export default class SolScannerProcessor { lastHash = transaction.transactionHash; } } - } catch (e) { + } catch (e: any) { hasError = true; if (e.message.indexOf('request failed') === -1) { if (config.debug.appErrors) { @@ -182,20 +190,18 @@ export default class SolScannerProcessor { if (!CACHE_FROM_DB[lastHashVar]) { CACHE_FROM_DB[lastHashVar] = { last_hash: lastHash }; await SolTmpDS.saveCache(lastHashVar, 'last_hash', lastHash); - } else if ( - typeof CACHE_FROM_DB[lastHashVar]['last_hash'] === 'undefined' - ) { - CACHE_FROM_DB[lastHashVar]['last_hash'] = lastHash; + } else if (typeof CACHE_FROM_DB[lastHashVar].last_hash === 'undefined') { + CACHE_FROM_DB[lastHashVar].last_hash = lastHash; await SolTmpDS.saveCache(lastHashVar, 'last_hash', lastHash); } else { - CACHE_FROM_DB[lastHashVar]['last_hash'] = lastHash; + CACHE_FROM_DB[lastHashVar].last_hash = lastHash; await SolTmpDS.updateCache(lastHashVar, 'last_hash', lastHash); } } return transactions; } - _cleanCache() { + private _cleanCache() { const now = new Date().getTime(); for (const key in CACHE_TXS) { const t = now - CACHE_TXS[key].now; @@ -205,7 +211,10 @@ export default class SolScannerProcessor { } } - async _unifyTransaction(address, transaction) { + private async _unifyTransaction( + address: string, + transaction: any + ): Promise { const data = { jsonrpc: '2.0', id: 1, @@ -213,7 +222,7 @@ export default class SolScannerProcessor { params: [transaction.signature, { encoding: 'jsonParsed' }] }; - let additional; + let additional: any; if (typeof CACHE_TXS[transaction.signature] === 'undefined') { const apiPath = BlocksoftExternalSettings.getStatic('SOL_SERVER'); try { @@ -226,7 +235,7 @@ export default class SolScannerProcessor { data: additional, now: new Date().getTime() }; - } catch (e) { + } catch (e: any) { if (config.debug.cryptoErrors) { console.log( this._settings.currencyCode + @@ -244,14 +253,14 @@ export default class SolScannerProcessor { let addressFrom = false; let addressTo = false; - let addressAmount = 0; + let addressAmount = '0'; let anyFromAddress = false; let anyToAddress = false; - const indexedPre = {}; - const indexedPost = {}; - const indexedCreated = {}; - const indexedAssociated = {}; + const indexedPre: Record = {}; + const indexedPost: Record = {}; + const indexedCreated: Record = {}; + const indexedAssociated: Record = {}; if (this.tokenAddress) { for (const tmp of additional.meta.preTokenBalances) { @@ -360,12 +369,12 @@ export default class SolScannerProcessor { let formattedTime = transaction.blockTime; try { formattedTime = BlocksoftUtils.toDate(transaction.blockTime); - } catch (e) { + } catch (e: any) { e.message += ' timestamp error transaction2 data ' + JSON.stringify(transaction); throw e; } - let transactionStatus = 'new'; + let transactionStatus: 'new' | 'success' | 'confirming' | 'fail' = 'new'; if (transaction.confirmationStatus === 'finalized') { transactionStatus = 'success'; } else if (transaction.confirmationStatus === 'confirmed') { @@ -375,7 +384,8 @@ export default class SolScannerProcessor { transactionStatus = 'fail'; } - let transactionDirection = addressFrom === address ? 'outcome' : 'income'; + let transactionDirection: UnifiedTransaction['transactionDirection'] = + addressFrom === address ? 'outcome' : 'income'; if (!addressFrom && anySigner === addressTo) { if (addressAmountPlus) { transactionDirection = 'swap_income'; @@ -387,7 +397,7 @@ export default class SolScannerProcessor { CACHE_LAST_BLOCK > 0 ? Math.round(CACHE_LAST_BLOCK - additional.slot * 1) : 0; - const tx = { + const tx: UnifiedTransaction = { transactionHash: transaction.signature, blockHash: additional.transaction.message.recentBlockhash, blockNumber: transaction.slot, diff --git a/crypto/blockchains/sol/SolScannerProcessorSpl.ts b/crypto/blockchains/sol/SolScannerProcessorSpl.ts index 69fa66ba3..cdc053e31 100644 --- a/crypto/blockchains/sol/SolScannerProcessorSpl.ts +++ b/crypto/blockchains/sol/SolScannerProcessorSpl.ts @@ -4,18 +4,35 @@ import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings'; - import SolScannerProcessor from '@crypto/blockchains/sol/SolScannerProcessor'; +import { AxiosResponse } from 'axios'; + +interface ParsedInfo { + state?: string; + mint: string; + tokenAmount: number; +} + +interface CacheBalances { + [address: string]: { + [tokenAddress: string]: { + amount: number; + }; + time: number; + }; +} -const CACHE_BALANCES = {}; +const CACHE_BALANCES: CacheBalances = {}; const CACHE_VALID_TIME = 30000; // 30 seconds export default class SolScannerProcessorSpl extends SolScannerProcessor { /** * @param {string} address - * @return {Promise<{balance, provider}>} + * @return {Promise<{balance: number; provider: string} | false>} */ - async getBalanceBlockchain(address) { + async getBalanceBlockchain( + address: string + ): Promise<{ balance: number; provider: string } | false> { address = address.trim(); BlocksoftCryptoLog.log( this._settings.currencyCode + @@ -47,8 +64,13 @@ export default class SolScannerProcessorSpl extends SolScannerProcessor { ] }; - const res = await BlocksoftAxios._request(apiPath, 'POST', data); + const res: AxiosResponse = await BlocksoftAxios._request( + apiPath, + 'POST', + data + ); if ( + res.data === false || typeof res.data.result === 'undefined' || typeof res.data.result.value === 'undefined' ) { @@ -61,13 +83,13 @@ export default class SolScannerProcessorSpl extends SolScannerProcessor { account.account.data.program !== 'spl-token' ) continue; - const parsed = account.account.data.parsed.info; + const parsed: ParsedInfo = account.account.data.parsed.info; if ( - typeof parsed.state === 'undefined' && + typeof parsed.state === 'undefined' || parsed.state !== 'initialized' ) continue; - CACHE_BALANCES[address][parsed.mint] = parsed.tokenAmount; // "amount": "1606300", "decimals": 6, "uiAmount": 1.6063, "uiAmountString": "1.6063" + CACHE_BALANCES[address][parsed.mint] = { amount: parsed.tokenAmount }; // "amount": "1606300", "decimals": 6, "uiAmount": 1.6063, "uiAmountString": "1.6063" } CACHE_BALANCES[address].time = now; } @@ -79,16 +101,15 @@ export default class SolScannerProcessorSpl extends SolScannerProcessor { } else { balance = CACHE_BALANCES[address][this.tokenAddress].amount * 1; } - } catch (e) { + } catch (e: any) { BlocksoftCryptoLog.log( this._settings.currencyCode + - ' SolScannerProcessorSpl getBalanceBlockchain address ' + - address + - ' error ' + - e.message + ` SolScannerProcessorSpl getBalanceBlockchain address ` + + `${address} ` + + ` error ${e.message}` ); return false; } - return { balance, unconfirmed: 0, provider: 'solana-api' }; + return { balance, provider: 'solana-api' }; } } diff --git a/crypto/blockchains/sol/SolTokenProcessor.ts b/crypto/blockchains/sol/SolTokenProcessor.ts index 601ac5d4f..026956ae9 100644 --- a/crypto/blockchains/sol/SolTokenProcessor.ts +++ b/crypto/blockchains/sol/SolTokenProcessor.ts @@ -4,20 +4,35 @@ import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings'; import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; +import { AxiosResponse } from 'axios'; + +interface TokenDetails { + currencyCodePrefix: string; + currencyCode: string; + currencyName: string; + tokenType: string; + tokenAddress: string; + tokenDecimals: number; + icon?: string; + description?: string; + coingeckoId?: string; + provider: string; +} export default class SolTokenProcessor { /** * @param {string} tokenAddress - * @returns {Promise<{tokenAddress: *, currencyName: *, provider: string, tokenDecimals: *, icon: *, description: *, tokenType: string, currencyCode: *}|boolean>} + * @returns {Promise} */ - async getTokenDetails(tokenAddress) { + async getTokenDetails(tokenAddress: string): Promise { const link = await BlocksoftExternalSettings.get('SOL_TOKENS_LIST'); - const res = await BlocksoftAxios.get(link); + // @ts-ignore + const res: AxiosResponse = await BlocksoftAxios.get(link); if (!res || typeof res.data.tokens === 'undefined' || !res.data.tokens) { return false; } - let tmp = false; + let tmp: TokenDetails | false = false; for (const token of res.data.tokens) { if (token.address === tokenAddress) { if (token.chainId !== 101) continue; @@ -31,7 +46,7 @@ export default class SolTokenProcessor { currencyCode: tmp.symbol, currencyName: tmp.name, tokenType: 'SOL', - tokenAddress: tokenAddress, + tokenAddress, tokenDecimals: tmp.decimals, icon: tmp.logoURI, description: tmp.website, @@ -54,31 +69,34 @@ export default class SolTokenProcessor { } ] }; - const res = await BlocksoftAxios._request(apiPath, 'POST', data); + const response = await BlocksoftAxios._request(apiPath, 'POST', data); if ( - typeof res.data.result === 'undefined' || - typeof res.data.result.value === 'undefined' + response && + response.data && + typeof response.data.result !== 'undefined' && + typeof response.data.result.value !== 'undefined' ) { + decimals = response.data.result.value.data.parsed.info.decimals; + } else { return false; } if ( - typeof res.data.result.value.owner === 'undefined' || - res.data.result.value.owner !== + typeof response.data.result.value.owner === 'undefined' || + response.data.result.value.owner !== 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA' ) { return false; } if ( - typeof res.data.result.value.data.program === 'undefined' || - res.data.result.value.data.program !== 'spl-token' + typeof response.data.result.value.data.program === 'undefined' || + response.data.result.value.data.program !== 'spl-token' ) { return false; } - decimals = res.data.result.value.data.parsed.info.decimals; - } catch (e) { + decimals = response.data.result.value.data.parsed.info.decimals; + } catch (e: any) { BlocksoftCryptoLog.log( - this._settings.currencyCode + - ' SolTokenProcessor getTokenDetails tokenAddress ' + + 'SolTokenProcessor getTokenDetails tokenAddress ' + tokenAddress + ' error ' + e.message @@ -89,10 +107,9 @@ export default class SolTokenProcessor { return { currencyCodePrefix: 'CUSTOM_SOL_', currencyCode: 'UNKNOWN_TOKEN_' + tokenAddress, - currencySymbol: 'UNKNOWN', currencyName: tokenAddress, tokenType: 'SOL', - tokenAddress: tokenAddress, + tokenAddress, tokenDecimals: decimals, provider: 'sol' }; diff --git a/crypto/blockchains/sol/SolTransferProcessor.ts b/crypto/blockchains/sol/SolTransferProcessor.ts index 78558eeec..1450866be 100644 --- a/crypto/blockchains/sol/SolTransferProcessor.ts +++ b/crypto/blockchains/sol/SolTransferProcessor.ts @@ -9,21 +9,20 @@ import BlocksoftBalances from '@crypto/actions/BlocksoftBalances/BlocksoftBalanc // eslint-disable-next-line no-unused-vars import { BlocksoftBlockchainTypes } from '@crypto/blockchains/BlocksoftBlockchainTypes'; -import config from '@app/config/config'; - import { PublicKey, SystemProgram, Transaction, StakeProgram, Authorized -} from '@solana/web3.js/src/index'; +} from '@solana/web3.js/src'; import SolUtils from '@crypto/blockchains/sol/ext/SolUtils'; import SolTmpDS from '@crypto/blockchains/sol/stores/SolTmpDS'; import SolStakeUtils from '@crypto/blockchains/sol/ext/SolStakeUtils'; import { Buffer } from 'buffer'; import BlocksoftCryptoUtils from '@crypto/common/BlocksoftCryptoUtils'; import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; +import config from '@constants/config'; export default class SolTransferProcessor implements BlocksoftBlockchainTypes.TransferProcessor @@ -245,7 +244,7 @@ export default class SolTransferProcessor ' build createWithSeed started' ); let start = 0; - let lastSeed = await SolTmpDS.getCache(data.addressFrom); + const lastSeed = await SolTmpDS.getCache(data.addressFrom); if ( typeof lastSeed !== 'undefined' && lastSeed && diff --git a/crypto/blockchains/sol/SolTransferProcessorSpl.ts b/crypto/blockchains/sol/SolTransferProcessorSpl.ts index b494e2fa0..659822917 100644 --- a/crypto/blockchains/sol/SolTransferProcessorSpl.ts +++ b/crypto/blockchains/sol/SolTransferProcessorSpl.ts @@ -2,16 +2,14 @@ * @version 0.52 */ -// eslint-disable-next-line no-unused-vars import { BlocksoftBlockchainTypes } from '@crypto/blockchains/BlocksoftBlockchainTypes'; import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; -import BlocksoftBalances from '@crypto/actions/BlocksoftBalances/BlocksoftBalances'; import { PublicKey, TransactionInstruction, Transaction -} from '@solana/web3.js/src/index'; +} from '@solana/web3.js/src'; import SolUtils from '@crypto/blockchains/sol/ext/SolUtils'; import SolTransferProcessor from '@crypto/blockchains/sol/SolTransferProcessor'; import SolInstructions from '@crypto/blockchains/sol/ext/SolInstructions'; @@ -72,8 +70,8 @@ export default class SolTransferProcessorSpl // @ts-ignore await BlocksoftCryptoLog.log( this._settings.currencyCode + - ' SolTransferProcessorSpl.getTransferAllBalance ', - data.addressFrom + ' => ' + balance + ` SolTransferProcessorSpl.getTransferAllBalance, + ${data.addressFrom} + ' => ' + ${balance}` ); const fees = await this.getFeeRate(data, privateData, additionalData); @@ -269,7 +267,7 @@ export default class SolTransferProcessorSpl programId: SolUtils.getTokenProgramID() }) ); - } catch (e) { + } catch (e: any) { BlocksoftCryptoLog.log( this._settings.currencyCode + ' SolTransferProcessorSpl.sendTx ' + @@ -324,7 +322,7 @@ export default class SolTransferProcessorSpl throw new Error('SYSTEM_ERROR'); } result.transactionHash = sendRes; - } catch (e) { + } catch (e: any) { BlocksoftCryptoLog.log( this._settings.currencyCode + ' SolTransferProcessorSpl.sendTx ' + diff --git a/crypto/blockchains/sol/ext/SolInstructions.ts b/crypto/blockchains/sol/ext/SolInstructions.ts index 27f6c5b84..949f8aaa1 100644 --- a/crypto/blockchains/sol/ext/SolInstructions.ts +++ b/crypto/blockchains/sol/ext/SolInstructions.ts @@ -3,8 +3,9 @@ * https://github.com/project-serum/spl-token-wallet/blob/master/src/utils/tokens/instructions.js */ import * as BufferLayout from '@solana/buffer-layout'; -import { PublicKey } from '@solana/web3.js/src'; +import { PublicKey } from '@solana/web3.js'; +// @ts-ignore const LAYOUT = BufferLayout.union(BufferLayout.u8('instruction')); LAYOUT.addVariant( 0, @@ -42,36 +43,39 @@ const instructionMaxSpan = Math.max( ); class PublicKeyLayout extends BufferLayout.Blob { - constructor(property) { + constructor(property: string) { super(32, property); } - decode(b, offset) { + // @ts-ignore + decode(b: Buffer, offset: number) { return new PublicKey(super.decode(b, offset)); } - encode(src, b, offset) { + // @ts-ignore + encode(src: PublicKey, b: Buffer, offset: number) { return super.encode(src.toBuffer(), b, offset); } } -function publicKeyLayout(property) { +function publicKeyLayout(property: string) { return new PublicKeyLayout(property); } export const OWNER_VALIDATION_LAYOUT = BufferLayout.struct([ + // @ts-ignore publicKeyLayout('account') ]); export default { - encodeTokenInstructionData(instruction) { - let b = Buffer.alloc(instructionMaxSpan); - let span = LAYOUT.encode(instruction, b); + encodeTokenInstructionData(instruction: any) { + const b = Buffer.alloc(instructionMaxSpan); + const span = LAYOUT.encode(instruction, b); const res = b.slice(0, span); return res; }, - encodeOwnerValidationInstruction(instruction) { + encodeOwnerValidationInstruction(instruction: any) { const b = Buffer.alloc(OWNER_VALIDATION_LAYOUT.span); const span = OWNER_VALIDATION_LAYOUT.encode(instruction, b); return b.slice(0, span); diff --git a/crypto/blockchains/sol/ext/SolStakeUtils.ts b/crypto/blockchains/sol/ext/SolStakeUtils.ts index cf972ef0c..a717ea26a 100644 --- a/crypto/blockchains/sol/ext/SolStakeUtils.ts +++ b/crypto/blockchains/sol/ext/SolStakeUtils.ts @@ -1,372 +1,408 @@ -/** - * @version 0.52 - */ -import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; -import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings'; - -import config from '@app/config/config'; -import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; -import BlocksoftUtils from '@crypto/common/BlocksoftUtils'; -import SolUtils from '@crypto/blockchains/sol/ext/SolUtils'; - -const CACHE_STAKED = {}; -const CACHE_VOTES = { - data: [], - time: 0 -}; -const CACHE_VALID_TIME = 12000000; // 200 minute - -const validatorsConstants = require('@crypto/blockchains/sol/ext/validators'); - -const validators = { - time: 0, - data: {} -}; -for (const tmp of validatorsConstants) { - validators.data[tmp.id] = tmp; -} - -const init = async () => { - const link = await BlocksoftExternalSettings.get('SOL_VALIDATORS_LIST'); - const res = await BlocksoftAxios.get(link); - if (!res.data || typeof res.data[0] === 'undefined' || !res.data[0]) { - return false; - } - validators.data = {}; - for (const tmp of res.data) { - if (typeof tmp.id === 'undefined') continue; - validators.data[tmp.id] = tmp; - } - validators.time = new Date().getTime(); -}; - -export default { - // https://docs.solana.com/developing/clients/jsonrpc-api#getvoteaccounts - async getVoteAddresses() { - try { - const now = new Date().getTime(); - if (CACHE_VOTES.time && now - CACHE_VOTES.time < CACHE_VALID_TIME) { - return CACHE_VOTES.data; - } - if (!validators.time) { - await init(); - } - - const apiPath = BlocksoftExternalSettings.getStatic('SOL_SERVER'); - const getVote = { jsonrpc: '2.0', id: 1, method: 'getVoteAccounts' }; - const resVote = await BlocksoftAxios._request(apiPath, 'POST', getVote); - if ( - !resVote || - typeof resVote.data === 'undefined' || - typeof resVote.data.result === 'undefined' || - !resVote.data.result || - typeof resVote.data.result.current === 'undefined' - ) { - return CACHE_VOTES.data; - } - CACHE_VOTES.data = []; - for (const tmp of resVote.data.result.current) { - const address = tmp.votePubkey; - if (typeof validators.data[address] === 'undefined') continue; - - const validator = { - address, - commission: tmp.commission, - activatedStake: tmp.activatedStake, - name: '', - description: '', - website: '', - index: 100 - }; - validator.index = - typeof validators.data[validator.address].index !== 'undefined' - ? validators.data[validator.address].index - : 0; - validator.name = validators.data[validator.address].name; - validator.description = validators.data[validator.address].description; - validator.website = validators.data[validator.address].website; - - CACHE_VOTES.data.push(validator); - } - CACHE_VOTES.data.sort((a, b) => { - if (a.index * 1 === b.index * 1) { - const diff = a.commission - b.commission; - if (diff <= 0.1 && diff >= -0.1) { - return b.activatedStake - a.activatedStake; - } - return diff; - } else { - return b.index - a.index; - } - }); - CACHE_VOTES.time = now; - } catch (e) { - if (config.debug.cryptoErrors) { - console.log('SolStakeUtils.getVoteAddresses error ' + e.message); - } - BlocksoftCryptoLog.log( - 'SolStakeUtils.getVoteAddresses error ' + e.message - ); - } - return CACHE_VOTES.data; - }, - - checkAccountStaked(address, subaddress) { - return typeof CACHE_STAKED[address].all[subaddress] !== 'undefined'; - }, - - setAccountStaked(address, subaddress) { - CACHE_STAKED[address].all[subaddress] = true; - }, - - // https://prod-api.solana.surf/v1/account/3siLSmroYvPHPZCrK5VYR3gmFhQFWefVGGpasXdzSPnn/stake-rewards - async getAccountRewards(address, stakedAddresses) { - if (typeof CACHE_STAKED[address] === 'undefined') { - CACHE_STAKED[address] = { - all: {}, - active: [], - rewards: [] - }; - } - const askAddresses = [address]; - if (stakedAddresses) { - for (let tmp of stakedAddresses) { - askAddresses.push(tmp.stakeAddress); - } - } - try { - const link = BlocksoftExternalSettings.getStatic('SOL_SERVER'); - const data = { - method: 'getInflationReward', - jsonrpc: '2.0', - params: [ - askAddresses, - { - commitment: 'confirmed' - } - ], - id: '1' - }; - - /*console.log(` - - - curl ${link} -X POST -H "Content-Type: application/json" -d '${JSON.stringify(data)}' - - `) - */ - - const res = await BlocksoftAxios.post(link, data); - if ( - res.data && - typeof res.data.result !== 'undefined' && - typeof res.data.result[0] !== 'undefined' - ) { - CACHE_STAKED[address].rewards = res.data.result[0]; - if (stakedAddresses) { - let count = 0; - if (typeof CACHE_STAKED[address].rewards !== 'undefined') { - count = 100; - } else { - for (let tmp of res.data.result) { - if ( - tmp && - typeof tmp.amount !== 'undefined' && - tmp.amount * 1 > 0 - ) { - CACHE_STAKED[address].rewards = tmp; - count++; - } - } - } - if (count > 1) { - if (typeof CACHE_STAKED[address].rewards !== 'undefined') { - CACHE_STAKED[address].rewards.amount = 0; - } - for (let tmp of res.data.result) { - if ( - tmp && - typeof tmp.amount !== 'undefined' && - tmp.amount * 1 > 0 - ) { - CACHE_STAKED[address].rewards.amount += tmp.amount * 1; - } - } - } - } - } - } catch (e) { - if (config.debug.cryptoErrors) { - console.log( - 'SolStakeUtils.getAccountRewards ' + address + ' error ' + e.message - ); - } - BlocksoftCryptoLog.log( - 'SolStakeUtils.getAccountRewards ' + address + ' error ' + e.message - ); - } - //{"amount": 96096, "apr": 7.044036109546499, "effectiveSlot": 99360012, "epoch": 229, "percentChange": 0.05205832165890872, "postBalance": 184689062, "timestamp": 1633153114}, - return CACHE_STAKED[address].rewards; - }, - - // https://docs.solana.com/developing/clients/jsonrpc-api#getprogramaccounts - async getAccountStaked(address, isForce = false) { - let accountInfo = false; - if (typeof CACHE_STAKED[address] === 'undefined' || isForce) { - CACHE_STAKED[address] = { - all: {}, - active: [], - rewards: [] - }; - } - try { - const apiPath = BlocksoftExternalSettings.getStatic('SOL_SERVER'); - const currentEpoch = await SolUtils.getEpoch(); - - const checkData = { - jsonrpc: '2.0', - id: 1, - method: 'getProgramAccounts', - params: [ - 'Stake11111111111111111111111111111111111111', - { - encoding: 'jsonParsed', - filters: [ - { - memcmp: { - offset: 0xc, - bytes: address - } - } - ] - } - ] - }; - - /* - console.log(` - - - curl ${apiPath} -X POST -H "Content-Type: application/json" -d '${JSON.stringify(checkData)}' - - `) - */ - let res; - accountInfo = []; - try { - res = await BlocksoftAxios._request(apiPath, 'POST', checkData); - - for (const tmp of res.data.result) { - const parsed = tmp.account.data.parsed; - const item = { - amount: tmp.account.lamports, - stakeAddress: tmp.pubkey, - reserved: 0, - active: true, - status: '' - }; - if (typeof parsed.info !== 'undefined') { - if (typeof parsed.info.meta !== 'undefined') { - if (typeof parsed.info.meta.rentExemptReserve !== 'undefined') { - item.reserved = parsed.info.meta.rentExemptReserve; - } - } - const deactivationEpoch = - parsed.info.stake.delegation.deactivationEpoch || 0; - const activationEpoch = - parsed.info.stake.delegation.activationEpoch || 0; - if (currentEpoch && currentEpoch * 1 >= deactivationEpoch * 1) { - item.order = 1; - item.active = false; - item.status = 'inactive'; - } else if (currentEpoch && currentEpoch === activationEpoch) { - item.order = 3; - item.status = 'activating'; - } else { - item.order = 2; - item.status = 'staked'; - } - } - item.diff = BlocksoftUtils.diff( - item.amount, - item.reserved - ).toString(); - accountInfo.push(item); - CACHE_STAKED[address].all[item.stakeAddress] = true; - } - } catch (e) { - BlocksoftCryptoLog.log( - 'SolStakeUtils getAccountStaked request ' + - apiPath + - ' error ' + - e.message - ); - - const apiPath2 = - 'https://prod-api.solana.surf/v1/account/' + - address + - '/stakes?limit=10&offset=0'; - const res2 = await BlocksoftAxios.get(apiPath2); - - for (const tmp of res2.data.data) { - const item = { - amount: tmp.lamports, - stakeAddress: tmp.pubkey.address, - reserved: 0, - active: true, - status: '' - }; - - if (typeof tmp.data !== 'undefined') { - if (typeof tmp.data.meta !== 'undefined') { - if (typeof tmp.data.meta.rent_exempt_reserve !== 'undefined') { - item.reserved = tmp.data.meta.rent_exempt_reserve; - } - } - const deactivationEpoch = - tmp.data.stake.delegation.deactivation_epoch || 0; - const activationEpoch = - tmp.data.stake.delegation.activation_epoch || 0; - if (currentEpoch && currentEpoch * 1 >= deactivationEpoch * 1) { - item.order = 1; - item.active = false; - item.status = 'inactive'; - } else if (currentEpoch && currentEpoch === activationEpoch) { - item.order = 3; - item.status = 'activating'; - } else { - item.order = 2; - item.status = 'staked'; - } - } - item.diff = BlocksoftUtils.diff( - item.amount, - item.reserved - ).toString(); - accountInfo.push(item); - CACHE_STAKED[address].all[item.stakeAddress] = true; - } - } - - accountInfo.sort((a, b) => { - if (b.order === a.order) { - return BlocksoftUtils.diff(b.diff, a.diff) * 1; - } else { - return b.order - a.order; - } - }); - CACHE_STAKED[address].active = accountInfo; - } catch (e) { - if (config.debug.cryptoErrors) { - console.log( - 'SolStakeUtils.getAccountStaked ' + address + ' error ' + e.message - ); - } - BlocksoftCryptoLog.log( - 'SolStakeUtils.getAccountStaked ' + address + ' error ' + e.message - ); - return CACHE_STAKED[address].active; - } - return accountInfo; - } -}; +/** + * @version 0.52 + */ +import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; +import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings'; + +import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; +import BlocksoftUtils from '@crypto/common/BlocksoftUtils'; +import SolUtils from '@crypto/blockchains/sol/ext/SolUtils'; +import config from '@constants/config'; + +interface Validator { + address: string; + commission: number; + activatedStake: number; + name: string; + description: string; + website: string; + index: number; +} + +interface ValidatorData { + [address: string]: Validator; +} + +interface CacheVotes { + data: Validator[]; + time: number; +} + +interface CacheStaked { + [address: string]: { + all: { [subaddress: string]: boolean }; + active: { + amount: number; + stakeAddress: string; + reserved: number; + active: boolean; + status: string; + order: number; + diff: string; + }[]; + rewards: any[]; + }; +} + +const CACHE_STAKED: CacheStaked = {}; +const CACHE_VOTES: CacheVotes = { + data: [], + time: 0 +}; +const CACHE_VALID_TIME = 12000000; // 200 minutes + +const validatorsConstants: Validator[] = require('@crypto/blockchains/sol/ext/validators'); + +const validators: { time: number; data: ValidatorData } = { + time: 0, + data: {} +}; +for (const tmp of validatorsConstants) { + validators.data[tmp.address] = tmp; +} + +const init = async () => { + const link = await BlocksoftExternalSettings.get('SOL_VALIDATORS_LIST'); + const res = await BlocksoftAxios.get(link); + if (!res.data || typeof res.data[0] === 'undefined' || !res.data[0]) { + return false; + } + validators.data = {}; + for (const tmp of res.data) { + if (typeof tmp.address === 'undefined') continue; + validators.data[tmp.address] = tmp; + } + validators.time = new Date().getTime(); +}; + +export default { + // https://docs.solana.com/developing/clients/jsonrpc-api#getvoteaccounts + async getVoteAddresses(): Promise { + try { + const now = new Date().getTime(); + if (CACHE_VOTES.time && now - CACHE_VOTES.time < CACHE_VALID_TIME) { + return CACHE_VOTES.data; + } + if (!validators.time) { + await init(); + } + + const apiPath = BlocksoftExternalSettings.getStatic('SOL_SERVER'); + const getVote = { jsonrpc: '2.0', id: 1, method: 'getVoteAccounts' }; + const resVote = await BlocksoftAxios._request(apiPath, 'POST', getVote); + if ( + !resVote || + typeof resVote.data === 'undefined' || + typeof resVote.data.result === 'undefined' || + !resVote.data.result || + typeof resVote.data.result.current === 'undefined' + ) { + return CACHE_VOTES.data; + } + CACHE_VOTES.data = []; + for (const tmp of resVote.data.result.current) { + const address = tmp.votePubkey; + if (typeof validators.data[address] === 'undefined') continue; + + const validator: Validator = { + address, + commission: tmp.commission, + activatedStake: tmp.activatedStake, + name: '', + description: '', + website: '', + index: 100 + }; + validator.index = + typeof validators.data[validator.address].index !== 'undefined' + ? validators.data[validator.address].index + : 0; + validator.name = validators.data[validator.address].name; + validator.description = validators.data[validator.address].description; + validator.website = validators.data[validator.address].website; + + CACHE_VOTES.data.push(validator); + } + CACHE_VOTES.data.sort((a, b) => { + if (a.index === b.index) { + const diff = a.commission - b.commission; + if (diff <= 0.1 && diff >= -0.1) { + return b.activatedStake - a.activatedStake; + } + return diff; + } else { + return b.index - a.index; + } + }); + CACHE_VOTES.time = now; + } catch (e) { + if (config.debug.cryptoErrors) { + console.log('SolStakeUtils.getVoteAddresses error ' + e.message); + } + BlocksoftCryptoLog.log( + 'SolStakeUtils.getVoteAddresses error ' + e.message + ); + } + return CACHE_VOTES.data; + }, + + checkAccountStaked(address: string, subaddress: string): boolean { + return typeof CACHE_STAKED[address].all[subaddress] !== 'undefined'; + }, + + setAccountStaked(address: string, subaddress: string) { + CACHE_STAKED[address].all[subaddress] = true; + }, + + // https://prod-api.solana.surf/v1/account/3siLSmroYvPHPZCrK5VYR3gmFhQFWefVGGpasXdzSPnn/stake-rewards + async getAccountRewards( + address: string, + stakedAddresses: any[] + ): Promise { + if (typeof CACHE_STAKED[address] === 'undefined') { + CACHE_STAKED[address] = { + all: {}, + active: [], + rewards: [] + }; + } + const askAddresses = [address]; + if (stakedAddresses) { + for (const tmp of stakedAddresses) { + askAddresses.push(tmp.stakeAddress); + } + } + try { + const link = BlocksoftExternalSettings.getStatic('SOL_SERVER'); + const data = { + method: 'getInflationReward', + jsonrpc: '2.0', + params: [ + askAddresses, + { + commitment: 'confirmed' + } + ], + id: '1' + }; + + /*console.log(` + + curl ${link} -X POST -H "Content-Type: application/json" -d '${JSON.stringify(data)}' + + `) + */ + + const res = await BlocksoftAxios.post(link, data); + if ( + res.data && + typeof res.data.result !== 'undefined' && + typeof res.data.result[0] !== 'undefined' + ) { + CACHE_STAKED[address].rewards = res.data.result[0]; + if (stakedAddresses) { + let count = 0; + if (typeof CACHE_STAKED[address].rewards !== 'undefined') { + count = 100; + } else { + for (const tmp of res.data.result) { + if ( + tmp && + typeof tmp.amount !== 'undefined' && + tmp.amount * 1 > 0 + ) { + CACHE_STAKED[address].rewards = tmp; + count++; + } + } + } + if (count > 1) { + if (typeof CACHE_STAKED[address].rewards !== 'undefined') { + CACHE_STAKED[address].rewards.amount = 0; + } + for (const tmp of res.data.result) { + if ( + tmp && + typeof tmp.amount !== 'undefined' && + tmp.amount * 1 > 0 + ) { + CACHE_STAKED[address].rewards.amount += tmp.amount * 1; + } + } + } + } + } + } catch (e) { + if (config.debug.cryptoErrors) { + console.log( + 'SolStakeUtils.getAccountRewards ' + address + ' error ' + e.message + ); + } + BlocksoftCryptoLog.log( + 'SolStakeUtils.getAccountRewards ' + address + ' error ' + e.message + ); + } + // {"amount": 96096, "apr": 7.044036109546499, "effectiveSlot": 99360012, "epoch": 229, "percentChange": 0.05205832165890872, "postBalance": 184689062, "timestamp": 1633153114}, + return CACHE_STAKED[address].rewards; + }, + + // https://docs.solana.com/developing/clients/jsonrpc-api#getprogramaccounts + async getAccountStaked(address: string, isForce = false): Promise { + let accountInfo: any[] = []; + if (typeof CACHE_STAKED[address] === 'undefined' || isForce) { + CACHE_STAKED[address] = { + all: {}, + active: [], + rewards: [] + }; + } + try { + const apiPath = BlocksoftExternalSettings.getStatic('SOL_SERVER'); + const currentEpoch = await SolUtils.getEpoch(); + + const checkData = { + jsonrpc: '2.0', + id: 1, + method: 'getProgramAccounts', + params: [ + 'Stake11111111111111111111111111111111111111', + { + encoding: 'jsonParsed', + filters: [ + { + memcmp: { + offset: 0xc, + bytes: address + } + } + ] + } + ] + }; + + /* + console.log(` + + curl ${apiPath} -X POST -H "Content-Type: application/json" -d '${JSON.stringify(checkData)}' + + `) + */ + let res; + accountInfo = []; + try { + res = await BlocksoftAxios._request(apiPath, 'POST', checkData); + + for (const tmp of res.data.result) { + const parsed = tmp.account.data.parsed; + const item = { + amount: tmp.account.lamports, + stakeAddress: tmp.pubkey, + reserved: 0, + active: true, + status: '' + }; + if (typeof parsed.info !== 'undefined') { + if (typeof parsed.info.meta !== 'undefined') { + if (typeof parsed.info.meta.rentExemptReserve !== 'undefined') { + item.reserved = parsed.info.meta.rentExemptReserve; + } + } + const deactivationEpoch = + parsed.info.stake.delegation.deactivationEpoch || 0; + const activationEpoch = + parsed.info.stake.delegation.activationEpoch || 0; + if (currentEpoch && currentEpoch * 1 >= deactivationEpoch * 1) { + item.order = 1; + item.active = false; + item.status = 'inactive'; + } else if (currentEpoch && currentEpoch === activationEpoch) { + item.order = 3; + item.status = 'activating'; + } else { + item.order = 2; + item.status = 'staked'; + } + } + item.diff = BlocksoftUtils.diff( + item.amount, + item.reserved + ).toString(); + accountInfo.push(item); + CACHE_STAKED[address].all[item.stakeAddress] = true; + } + } catch (e: any) { + BlocksoftCryptoLog.log( + 'SolStakeUtils getAccountStaked request ' + + apiPath + + ' error ' + + e.message + ); + + const apiPath2 = + 'https://prod-api.solana.surf/v1/account/' + + address + + '/stakes?limit=10&offset=0'; + const res2 = await BlocksoftAxios.get(apiPath2); + + for (const tmp of res2.data.data) { + const item = { + amount: tmp.lamports, + stakeAddress: tmp.pubkey.address, + reserved: 0, + active: true, + status: '' + }; + + if (typeof tmp.data !== 'undefined') { + if (typeof tmp.data.meta !== 'undefined') { + if (typeof tmp.data.meta.rent_exempt_reserve !== 'undefined') { + item.reserved = tmp.data.meta.rent_exempt_reserve; + } + } + const deactivationEpoch = + tmp.data.stake.delegation.deactivation_epoch || 0; + const activationEpoch = + tmp.data.stake.delegation.activation_epoch || 0; + if (currentEpoch && currentEpoch >= deactivationEpoch * 1) { + item.order = 1; + item.active = false; + item.status = 'inactive'; + } else if (currentEpoch && currentEpoch === activationEpoch) { + item.order = 3; + item.status = 'activating'; + } else { + item.order = 2; + item.status = 'staked'; + } + } + item.diff = BlocksoftUtils.diff( + item.amount, + item.reserved + ).toString(); + accountInfo.push(item); + CACHE_STAKED[address].all[item.stakeAddress] = true; + } + } + + accountInfo.sort((a, b) => { + if (b.order === a.order) { + return BlocksoftUtils.diff(b.diff, a.diff) * 1; + } else { + return b.order - a.order; + } + }); + CACHE_STAKED[address].active = accountInfo; + } catch (e: any) { + if (config.debug.cryptoErrors) { + console.log( + 'SolStakeUtils.getAccountStaked ' + address + ' error ' + e.message + ); + } + BlocksoftCryptoLog.log( + 'SolStakeUtils.getAccountStaked ' + address + ' error ' + e.message + ); + return CACHE_STAKED[address].active; + } + return accountInfo; + } +}; diff --git a/crypto/blockchains/sol/ext/SolUtils.ts b/crypto/blockchains/sol/ext/SolUtils.ts index 3d348b8fb..dd04b52e4 100644 --- a/crypto/blockchains/sol/ext/SolUtils.ts +++ b/crypto/blockchains/sol/ext/SolUtils.ts @@ -7,9 +7,9 @@ import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings' import { PublicKey } from '@solana/web3.js/src'; import { Account } from '@solana/web3.js/src/account'; -import config from '@app/config/config'; import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; import settingsActions from '@app/appstores/Stores/Settings/SettingsActions'; +import config from '@constants/config'; const TOKEN_PROGRAM_ID = 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'; const ASSOCIATED_TOKEN_PROGRAM_ID = @@ -17,7 +17,7 @@ const ASSOCIATED_TOKEN_PROGRAM_ID = const OWNER_VALIDATION_PROGRAM_ID = '4MNPdKu9wFMvEeZBMt3Eipfs5ovVWTJb31pEXDJAAxX5'; -const CACHE_VALID_TIME = 12000000; // 200 minute +const CACHE_VALID_TIME = 12000000; // 200 minutes const CACHE_EPOCH = { ts: 0, @@ -25,19 +25,22 @@ const CACHE_EPOCH = { }; export default { - getTokenProgramID() { + getTokenProgramID(): string { return TOKEN_PROGRAM_ID; }, - getOwnerValidationProgramId() { + getOwnerValidationProgramId(): string { return OWNER_VALIDATION_PROGRAM_ID; }, - getAssociatedTokenProgramId() { + getAssociatedTokenProgramId(): string { return ASSOCIATED_TOKEN_PROGRAM_ID; }, - async findAssociatedTokenAddress(walletAddress, tokenMintAddress) { + async findAssociatedTokenAddress( + walletAddress: string, + tokenMintAddress: string + ): Promise { try { const seeds = [ new PublicKey(walletAddress).toBuffer(), @@ -57,7 +60,7 @@ export default { } }, - async getAccountInfo(address) { + async getAccountInfo(address: string): Promise { let accountInfo = false; try { const apiPath = BlocksoftExternalSettings.getStatic('SOL_SERVER'); @@ -87,7 +90,7 @@ export default { return accountInfo; }, - isAddressValid(value) { + isAddressValid(value: string): boolean { new PublicKey(value); return true; }, @@ -97,7 +100,7 @@ export default { * @param raw * @returns {Promise} */ - async sendTransaction(raw) { + async sendTransaction(raw: string): Promise { const sendData = { jsonrpc: '2.0', id: 1, @@ -160,7 +163,10 @@ export default { /** * @returns {Promise<{blockhash: string, feeCalculator: {lamportsPerSignature: number}}>} */ - async getBlockData() { + async getBlockData(): Promise<{ + blockhash: string; + feeCalculator: { lamportsPerSignature: number }; + }> { const getRecentBlockhashData = { jsonrpc: '2.0', id: 1, @@ -175,7 +181,7 @@ export default { return getRecentBlockhashRes.data.result.value; }, - async getEpoch() { + async getEpoch(): Promise { const now = new Date().getTime(); if (CACHE_EPOCH.ts > 0) { if (now - CACHE_EPOCH.ts < CACHE_VALID_TIME) { @@ -204,11 +210,15 @@ export default { return CACHE_EPOCH.value; }, - async signTransaction(transaction, walletPrivKey, walletPubKey) { + async signTransaction( + transaction: any, + walletPrivKey: string, + walletPubKey: string + ): Promise { let account = false; try { account = new Account(Buffer.from(walletPrivKey, 'hex')); - } catch (e) { + } catch (e: any) { e.message += ' while create account'; throw e; } @@ -216,20 +226,20 @@ export default { try { const data = await this.getBlockData(); transaction.recentBlockhash = data.blockhash; - } catch (e) { + } catch (e: any) { e.message += ' while getBlockData'; throw e; } try { transaction.setSigners(new PublicKey(walletPubKey)); - } catch (e) { + } catch (e: any) { e.message += ' while setSigners'; throw e; } try { transaction.partialSign(account); - } catch (e) { + } catch (e: any) { e.message += ' while transaction.partialSign with account'; throw e; } diff --git a/crypto/blockchains/sol/stores/SolTmpDS.ts b/crypto/blockchains/sol/stores/SolTmpDS.ts index 7d1449644..acd89c4ba 100644 --- a/crypto/blockchains/sol/stores/SolTmpDS.ts +++ b/crypto/blockchains/sol/stores/SolTmpDS.ts @@ -1,54 +1,59 @@ /** * @version 0.52 */ +// @ts-ignore import Database from '@app/appstores/DataSource/Database'; -const tableName = ' transactions_scanners_tmp' +const tableName = 'transactions_scanners_tmp'; class SolTmpDS { - /** - * @type {string} - * @private - */ - _currencyCode = 'SOL' + /** + * @type {string} + * @private + */ + private _currencyCode = 'SOL'; - async getCache(address) { - const sql = ` - SELECT tmp_key, tmp_val - FROM ${tableName} - WHERE currency_code='${this._currencyCode}' - AND address='${address}' - ` - const res = await Database.query(sql) - const tmp = {} - if (res.array) { - for (const row of res.array) { - tmp[row.tmp_key] = row.tmp_val - } - } - return tmp + async getCache(address: string): Promise<{ [key: string]: any }> { + const sql = ` + SELECT tmp_key, tmp_val + FROM ${tableName} + WHERE currency_code='${this._currencyCode}' + AND address='${address}' + `; + const res = await Database.query(sql); + const tmp: { [key: string]: any } = {}; + if (res.array) { + for (const row of res.array) { + tmp[row.tmp_key] = row.tmp_val; + } } + return tmp; + } - async saveCache(address, key, value) { - const now = new Date().toISOString() - const prepared = [{ - currency_code : this._currencyCode, - address : address, - tmp_key : key, - tmp_val : value, - created_at : now - }] - await Database.setTableName(tableName).setInsertData({insertObjs : prepared}).insert() - } + async saveCache(address: string, key: string, value: any): Promise { + const now = new Date().toISOString(); + const prepared = [ + { + currency_code: this._currencyCode, + address: address, + tmp_key: key, + tmp_val: value, + created_at: now + } + ]; + await Database.setTableName(tableName) + .setInsertData({ insertObjs: prepared }) + .insert(); + } - async updateCache(address, key, value) { - const sql = ` - UPDATE ${tableName} SET tmp_val='${value}' - WHERE address='${address}' AND tmp_key='${key}' - AND currency_code='${this._currencyCode}' - ` - await Database.query(sql) - } + async updateCache(address: string, key: string, value: any): Promise { + const sql = ` + UPDATE ${tableName} SET tmp_val='${value}' + WHERE address='${address}' AND tmp_key='${key}' + AND currency_code='${this._currencyCode}' + `; + await Database.query(sql); + } } -export default new SolTmpDS() +export default new SolTmpDS(); diff --git a/crypto/blockchains/trx/TrxAddressProcessor.ts b/crypto/blockchains/trx/TrxAddressProcessor.ts index b5971a854..a8d93dad1 100644 --- a/crypto/blockchains/trx/TrxAddressProcessor.ts +++ b/crypto/blockchains/trx/TrxAddressProcessor.ts @@ -1,22 +1,36 @@ /** * @version 0.5 */ -import TronUtils from './ext/TronUtils' +import TronUtils from './ext/TronUtils'; -export default class TrxAddressProcessor { +interface AddressData { + privateKey: string; + address: string; + addedData: { + addressHex: string; + pubKey: string; + }; +} - async setBasicRoot(root) { +export default class TrxAddressProcessor { + async setBasicRoot(root: any) {} - } - /** - * @param {string|Buffer} privateKey - * @param {*} data - * @returns {Promise<{privateKey: string, address: string, addedData: *}>} - */ - async getAddress(privateKey, data = {}) { - const pubKey = TronUtils.privHexToPubHex(privateKey) - const addressHex = TronUtils.pubHexToAddressHex(pubKey) - const address = TronUtils.addressHexToStr(addressHex) - return { address, privateKey : privateKey.toString('hex'), addedData: {addressHex, pubKey} } - } + /** + * @param {string|Buffer} privateKey + * @param {*} data + * @returns {Promise} + */ + async getAddress( + privateKey: string | Buffer, + data: any = {} + ): Promise { + const pubKey: string = TronUtils.privHexToPubHex(privateKey); + const addressHex: string = TronUtils.pubHexToAddressHex(pubKey); + const address: string = TronUtils.addressHexToStr(addressHex); + return { + address, + privateKey: privateKey.toString('hex'), + addedData: { addressHex, pubKey } + }; + } } diff --git a/crypto/blockchains/trx/basic/TrxNodeInfoProvider.ts b/crypto/blockchains/trx/basic/TrxNodeInfoProvider.ts index a3755d85b..258159cce 100644 --- a/crypto/blockchains/trx/basic/TrxNodeInfoProvider.ts +++ b/crypto/blockchains/trx/basic/TrxNodeInfoProvider.ts @@ -10,18 +10,22 @@ export default class TrxNodeInfoProvider { /** * @returns {Promise} */ - async getLastBlock() { + async getLastBlock(): Promise { try { - const sendLink = BlocksoftExternalSettings.getStatic('TRX_SEND_LINK'); - const link = sendLink + '/wallet/getnodeinfo'; - let info = await BlocksoftAxios.getWithoutBraking(link, INFO_MAX_TRY); + const sendLink: string = + BlocksoftExternalSettings.getStatic('TRX_SEND_LINK'); + const link = `${sendLink}/wallet/getnodeinfo`; + let info: any = await BlocksoftAxios.getWithoutBraking( + link, + INFO_MAX_TRY + ); if ( info && typeof info.data !== 'undefined' && typeof info.data.block !== 'undefined' ) { info = info.data.block.split(',ID'); - info = info[0].substr(4) * 1; + info = parseInt(info[0].substr(4), 20); if (info > CACHE_LAST_BLOCK) { CACHE_LAST_BLOCK = info; } @@ -35,7 +39,7 @@ export default class TrxNodeInfoProvider { JSON.stringify(info) ); } - } catch (e) { + } catch (e: any) { BlocksoftCryptoLog.log( 'TrxNodeInfoProvider.getLastBlock currentBlock error ' + e.message ); diff --git a/crypto/blockchains/trx/basic/TrxTransactionsProvider.ts b/crypto/blockchains/trx/basic/TrxTransactionsProvider.ts index 16f0ac4e1..d33380768 100644 --- a/crypto/blockchains/trx/basic/TrxTransactionsProvider.ts +++ b/crypto/blockchains/trx/basic/TrxTransactionsProvider.ts @@ -6,48 +6,56 @@ import BlocksoftAxios from '../../../common/BlocksoftAxios'; import BlocksoftUtils from '../../../common/BlocksoftUtils'; import TrxNodeInfoProvider from './TrxNodeInfoProvider'; import TransactionFilterTypeDict from '@appV2/dicts/transactionFilterTypeDict'; -import BlocksoftPrettyNumbers from '@crypto/common/BlocksoftPrettyNumbers'; -import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings'; const TXS_MAX_TRY = 10; -const CACHE_OF_TRANSACTIONS = {}; -const CACHE_VALID_TIME = 30000; // 30 seconds +interface CachedTransactionData { + time: number; + [tokenName: string]: UnifiedTransaction[] | number; +} -export default class TrxTransactionsProvider { - /** - * @type {number} - * @private - */ - _lastBlock = 15850641; +interface UnifiedTransaction { + transactionHash: string; + blockHash: string; + blockNumber: number; + blockTime: number | Date; + blockConfirmations: number; + transactionDirection: string; + addressFrom: string; + addressTo: string; + addressAmount: number; + transactionStatus: string; + transactionFee: number; + transactionFilterType: string; + inputValue: string; +} - /** - * @type {string} - * @private - */ - _tronscanLink = +export default class TrxTransactionsProvider { + private _lastBlock = 15850641; + private _tronscanLink = 'https://api.tronscan.org/api/transaction?sort=-timestamp&count=true&limit=50&address='; constructor() { this._nodeInfo = new TrxNodeInfoProvider(); } - /** - * @param scanData.account.address - * @param tokenName - * @returns {Promise} - */ - async get(scanData, tokenName) { + private _nodeInfo: TrxNodeInfoProvider; + + async get( + scanData: { account: { address: string } }, + tokenName: string + ): Promise { const address = scanData.account.address.trim(); const now = new Date().getTime(); + if ( typeof CACHE_OF_TRANSACTIONS[address] !== 'undefined' && now - CACHE_OF_TRANSACTIONS[address].time < CACHE_VALID_TIME ) { if (typeof CACHE_OF_TRANSACTIONS[address][tokenName] !== 'undefined') { BlocksoftCryptoLog.log( - ' TrxTransactionsProvider.get from cache', - address + ' => ' + tokenName + ` TrxTransactionsProvider.get from cache', + ${address} + ' => ' + ${tokenName}` ); return CACHE_OF_TRANSACTIONS[address][tokenName]; } @@ -68,7 +76,7 @@ export default class TrxTransactionsProvider { this._lastBlock = await this._nodeInfo.getLastBlock(); - CACHE_OF_TRANSACTIONS[address] = {}; + CACHE_OF_TRANSACTIONS[address] = {} as CachedTransactionData; CACHE_OF_TRANSACTIONS[address].time = new Date().getTime(); CACHE_OF_TRANSACTIONS[address][tokenName] = []; let tx; @@ -76,7 +84,7 @@ export default class TrxTransactionsProvider { let tmp = false; try { tmp = await this._unifyTransaction(scanData, tx, tokenName); - } catch (e) { + } catch (e: any) { BlocksoftCryptoLog.log( 'TrxTransactionsProvider.get unify error ' + e.message + @@ -106,27 +114,35 @@ export default class TrxTransactionsProvider { return CACHE_OF_TRANSACTIONS[address][tokenName]; } - /** - * @param {string} scanData.address.address - * @param {Object} transaction - * @param {string} transaction.amount 1000000 - * @param {string} transaction.ownerAddress 'TJcnzHwXiFvMsmGDwBstDmwQ5AWVWFPxTM' - * @param {string} transaction.data '' - * @param {string} transaction.contractData.amount '' - * @param {string} transaction.toAddress 'TGk5Nkv8gf7HShzLw7rHzJsQLzsALvPPnF' - * @param {string} transaction.block 14129705 - * @param {string} transaction.confirmed true - * @param {string} transaction.contractRet 'SUCCESS' - * @param {string} transaction.hash '74d0f84322b1ba1478ce3f272d7b4524563e5a44b1270325cc6cce7e600601e2' - * @param {string} transaction.timestamp 1572636390000 - * @return {UnifiedTransaction} - * @private - */ - async _unifyTransaction(scanData, transaction, tokenName) { + private async _unifyTransaction( + scanData: { account: { address: string } }, + transaction: { + diffSeconds: number; + amount: string; + ownerAddress: string; + data: string; + contractData: { + amount: string; + contract_address?: string; + asset_name?: string; + frozen_balance?: string; + }; + toAddress: string; + block: string; + confirmed: boolean; + contractRet: string; + hash: string; + timestamp: string; + }, + tokenName: string + ): Promise<{ res: UnifiedTransaction; txTokenName: string } | false> { const address = scanData.account.address.trim(); let transactionStatus = 'new'; const now = new Date().getTime(); - transaction.diffSeconds = Math.round((now - transaction.timestamp) / 1000); + transaction.diffSeconds = Math.round( + // tslint:disable-next-line:radix + (now - parseInt(transaction.timestamp)) / 1000 + ); if (transaction.confirmed) { if (typeof transaction.contractRet === 'undefined') { transactionStatus = 'success'; @@ -135,18 +151,22 @@ export default class TrxTransactionsProvider { } else { transactionStatus = 'fail'; } - } else if (transaction.block > 0) { + // tslint:disable-next-line:radix + } else if (parseInt(transaction.block) > 0) { if (transaction.diffSeconds > 120) { transactionStatus = 'fail'; } else { transactionStatus = 'confirming'; } } - if (transaction.block > this._lastBlock) { - this._lastBlock = transaction.block; + // tslint:disable-next-line:radix + if (parseInt(transaction.block) > this._lastBlock) { + // tslint:disable-next-line:radix + this._lastBlock = parseInt(transaction.block); } - let blockConfirmations = this._lastBlock - transaction.block; + // tslint:disable-next-line:radix + let blockConfirmations = this._lastBlock - parseInt(transaction.block); if (blockConfirmations > 100 && transaction.diffSeconds < 600) { blockConfirmations = transaction.diffSeconds; } @@ -157,17 +177,21 @@ export default class TrxTransactionsProvider { JSON.stringify(transaction) ); } - let formattedTime = transaction.timestamp; + // tslint:disable-next-line:radix + let formattedTime = parseInt(transaction.timestamp); try { - formattedTime = BlocksoftUtils.toDate(transaction.timestamp / 1000); - } catch (e) { + formattedTime = BlocksoftUtils.toDate( + // tslint:disable-next-line:radix + parseInt(transaction.timestamp) / 1000 + ); + } catch (e: any) { e.message += ' timestamp error transaction data ' + JSON.stringify(transaction); throw e; } let addressAmount = 0; let transactionDirection = 'self'; - let txTokenName = false; + const txTokenName = false; let addressFrom = address.toLowerCase() === transaction.ownerAddress.toLowerCase() ? '' @@ -178,42 +202,45 @@ export default class TrxTransactionsProvider { typeof transaction.contractData !== 'undefined' && typeof transaction.contractData.frozen_balance !== 'undefined' ) { - addressAmount = transaction.contractData.frozen_balance; + // tslint:disable-next-line:radix + addressAmount = parseInt(transaction.contractData.frozen_balance); transactionDirection = 'freeze'; transactionFilterType = TransactionFilterTypeDict.STAKE; } else if ( typeof transaction.amount !== 'undefined' && typeof transaction.contractType !== 'undefined' && - transaction.contractType === 13 + // tslint:disable-next-line:radix + parseInt(transaction.contractType) === 13 ) { - addressAmount = transaction.amount; + // tslint:disable-next-line:radix + addressAmount = parseInt(transaction.amount); transactionDirection = 'claim'; transactionFilterType = TransactionFilterTypeDict.STAKE; } else if ( typeof transaction.contractType !== 'undefined' && - transaction.contractType === 12 + // tslint:disable-next-line:radix + parseInt(transaction.contractType) === 12 ) { - addressAmount = transaction.amount; + // tslint:disable-next-line:radix + addressAmount = parseInt(transaction.amount); addressFrom = transaction.ownerAddress; transactionDirection = 'unfreeze'; transactionFilterType = TransactionFilterTypeDict.STAKE; } else if ( typeof transaction.contractType !== 'undefined' && - transaction.contractType === 4 + // tslint:disable-next-line:radix + parseInt(transaction.contractType) === 4 ) { // no vote tx return false; - addressAmount = BlocksoftPrettyNumbers.setCurrencyCode( - 'TRX' - ).makeUnPretty(transaction.amount); - addressFrom = transaction.ownerAddress; - transactionDirection = 'vote'; - transactionFilterType = TransactionFilterTypeDict.STAKE; } else { if ( - transaction.contractType === 11 || - transaction.contractType === 4 || - transaction.contractType === 13 + // tslint:disable-next-line:radix + parseInt(transaction.contractType) === 11 || + // tslint:disable-next-line:radix + parseInt(transaction.contractType) === 4 || + // tslint:disable-next-line:radix + parseInt(transaction.contractType) === 13 ) { // freeze = 11, vote = 4, claim = 13 } else { @@ -226,7 +253,8 @@ export default class TrxTransactionsProvider { return false; } } else { - addressAmount = transaction.contractData.amount; + // tslint:disable-next-line:radix + addressAmount = parseInt(transaction.contractData.amount); transactionDirection = address.toLowerCase() === transaction.ownerAddress.toLowerCase() ? 'outcome' @@ -238,12 +266,13 @@ export default class TrxTransactionsProvider { typeof transaction.cost.fee !== 'undefined' && transaction.cost.fee ) { - transactionFee = transaction.cost.fee * 1; + transactionFee = parseFloat(transaction.cost.fee); } - const res = { + const res: UnifiedTransaction = { transactionHash: transaction.hash, blockHash: '', - blockNumber: transaction.block, + // tslint:disable-next-line:radix + blockNumber: parseInt(transaction.block), blockTime: formattedTime, blockConfirmations: blockConfirmations, transactionDirection, @@ -269,3 +298,6 @@ export default class TrxTransactionsProvider { return { res, txTokenName }; } } + +const CACHE_OF_TRANSACTIONS: { [address: string]: CachedTransactionData } = {}; +const CACHE_VALID_TIME = 30000; // 30 seconds diff --git a/crypto/blockchains/trx/basic/TrxTransactionsTrc20Provider.ts b/crypto/blockchains/trx/basic/TrxTransactionsTrc20Provider.ts index c17eb5da2..07f980297 100644 --- a/crypto/blockchains/trx/basic/TrxTransactionsTrc20Provider.ts +++ b/crypto/blockchains/trx/basic/TrxTransactionsTrc20Provider.ts @@ -9,11 +9,33 @@ import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; import Database from '@app/appstores/DataSource/Database/main'; import TransactionFilterTypeDict from '@appV2/dicts/transactionFilterTypeDict'; -const SWAPS = require('../dict/swaps'); +const SWAPS: Record = require('../dict/swaps'); + +interface UnifiedTransaction { + transactionHash: string; + blockHash: string; + blockNumber: string; + blockTime: Date; + blockConfirmations: number; + transactionDirection: + | 'income' + | 'outcome' + | 'self' + | 'swap_income' + | 'swap_outcome'; + addressFrom: string; + addressTo: string; + addressAmount: string; + transactionStatus: 'new' | 'success' | 'fail'; + transactionFee: number; + inputValue: string; +} + export default class TrxTransactionsTrc20Provider extends TrxTransactionsProvider { - _token = false; + protected _token: string | false = false; + protected _tronscanLink = ''; - setLink(token) { + setLink(token: string) { this._token = token; this._tronscanLink = 'https://apilist.tronscan.org/api/contract/events?sort=-timestamp&count=true&limit=50&contract=' + @@ -21,37 +43,37 @@ export default class TrxTransactionsTrc20Provider extends TrxTransactionsProvide '&address='; } - /** - * @param {string} scanData.account.address - * @param {Object} transaction - * @param {string} transaction.amount 1000000 - * @param {string} transaction.transferFromAddress 'TUbHxAdhPk9ykkc7SDP5e9zUBEN14K65wk' - * @param {string} transaction.data '' - * @param {string} transaction.decimals 6 - * @param {string} transaction.tokenName 'Tether USD' - * @param {string} transaction.transferToAddress 'TUoyiQH9wSfYdJRhsXtgmgDvpWipPrQN8a' - * @param {string} transaction.block 15847100 - * @param {string} transaction.id '' - * @param {string} transaction.confirmed true - * @param {string} transaction.transactionHash '4999b0965c1a5b17cbaa862b9357a32c9b8d096e170f4eecee929159b0b73ad3' - * @param {string} transaction.timestamp: 1577796345000 - * @return {UnifiedTransaction} - * @private - */ - async _unifyTransaction(scanData, transaction) { + async _unifyTransaction( + scanData: { account: { address: string; transactionsScanTime: number } }, + transaction: { + amount?: string; + transferFromAddress?: string; + data?: string; + decimals?: string; + tokenName?: string; + transferToAddress?: string; + block?: string; + id?: string; + confirmed?: boolean; + transactionHash?: string; + timestamp?: number; + } + ): Promise { const address = scanData.account.address.trim(); - let transactionStatus = 'new'; + let transactionStatus: 'new' | 'success' | 'fail' = 'new'; if (transaction.confirmed) { transactionStatus = 'success'; - } else if (transaction.block > 0) { + } else if (transaction.block && parseInt(transaction.block) > 0) { transactionStatus = 'fail'; } - let txTokenName = false; - let formattedTime; + const txTokenName: string | false = false; + let formattedTime: Date; try { - formattedTime = BlocksoftUtils.toDate(transaction.timestamp / 1000); - } catch (e) { + formattedTime = BlocksoftUtils.toDate( + transaction.timestamp ? transaction.timestamp / 1000 : 0 + ); + } catch (e: any) { e.message += ' timestamp error transaction data ' + JSON.stringify(transaction); throw e; @@ -64,31 +86,36 @@ export default class TrxTransactionsTrc20Provider extends TrxTransactionsProvide ); } - const res = { - transactionHash: transaction.transactionHash, + const res: UnifiedTransaction = { + transactionHash: transaction.transactionHash || '', blockHash: '', - blockNumber: transaction.block, + blockNumber: transaction.block || '0', blockTime: formattedTime, - blockConfirmations: this._lastBlock - transaction.block, + blockConfirmations: this._lastBlock + ? this._lastBlock - parseInt(transaction.block || '0', 20) + : 0, transactionDirection: - address.toLowerCase() === transaction.transferFromAddress.toLowerCase() + address.toLowerCase() === + (transaction.transferFromAddress || '').toLowerCase() ? 'outcome' : 'income', addressFrom: - address.toLowerCase() === transaction.transferFromAddress.toLowerCase() + address.toLowerCase() === + (transaction.transferFromAddress || '').toLowerCase() ? '' - : transaction.transferFromAddress, + : transaction.transferFromAddress || '', addressTo: - address.toLowerCase() === transaction.transferToAddress.toLowerCase() + address.toLowerCase() === + (transaction.transferToAddress || '').toLowerCase() ? '' - : transaction.transferToAddress, + : transaction.transferToAddress || '', addressAmount: typeof transaction.amount !== 'undefined' ? transaction.amount.toString() : '0', - transactionStatus: transactionStatus, + transactionStatus, transactionFee: 0, - inputValue: transaction.data + inputValue: transaction.data || '' }; let needData = false; @@ -100,12 +127,12 @@ export default class TrxTransactionsTrc20Provider extends TrxTransactionsProvide res.addressAmount = '0'; needData = true; } - if (typeof SWAPS[res.addressTo] !== 'undefined') { + if (SWAPS[res.addressTo] !== undefined) { res.addressTo = SWAPS[res.addressTo]; res.transactionDirection = 'swap_outcome'; res.addressAmount = '0'; needData = true; - } else if (typeof SWAPS[res.addressFrom] !== 'undefined') { + } else if (SWAPS[res.addressFrom] !== undefined) { res.addressFrom = SWAPS[res.addressFrom]; res.transactionDirection = 'swap_income'; res.addressAmount = '0'; @@ -116,7 +143,8 @@ export default class TrxTransactionsTrc20Provider extends TrxTransactionsProvide if (needData) { const diff = - scanData.account.transactionsScanTime - transaction.timestamp / 1000; + scanData.account.transactionsScanTime - + (transaction.timestamp || 0) / 1000; if (diff > 6000) { return false; } @@ -127,9 +155,10 @@ export default class TrxTransactionsTrc20Provider extends TrxTransactionsProvide 'https://apilist.tronscan.org/api/transaction-info?hash=' + res.transactionHash ); - res.transactionFee = tmp.data.cost.fee * 1 + tmp.data.cost.energy_fee * 1; + res.transactionFee = + tmp.data.cost.fee * 1 + tmp.data.cost.energy_fee * 1 || 0; - if (res.transactionFee * 1 > 0 && res.addressAmount * 1 > 0) { + if (res.transactionFee > 0 && res.addressAmount * 1 > 0) { const savedTRX = await Database.query( ` SELECT * FROM transactions WHERE transaction_hash='${res.transactionHash}' AND currency_code='TRX' ` ); @@ -142,7 +171,7 @@ export default class TrxTransactionsTrc20Provider extends TrxTransactionsProvide ' fee ' + res.transactionFee ); - const saveFee = { + const saveFee: UnifiedTransaction = { account_id: 0, address_amount: 0, address_from: res.addressFrom, @@ -166,7 +195,7 @@ export default class TrxTransactionsTrc20Provider extends TrxTransactionsProvide .insert(); } } - if (typeof tmp.data.trc20TransferInfo !== 'undefined') { + if (tmp.data.trc20TransferInfo !== undefined) { for (const info of tmp.data.trc20TransferInfo) { if (info.contract_address !== this._token) continue; if (info.from_address === address) { @@ -177,13 +206,11 @@ export default class TrxTransactionsTrc20Provider extends TrxTransactionsProvide } } else if (info.to_address === address) { res.transactionDirection = 'income'; - res.addressAmount = info.amount_str; - } else { - continue; + res.addressAmount = info.amount_str || '0'; } } } - if (res.transactionFee * 1 === 0 || res.addressAmount * 1 === 0) { + if (res.transactionFee === 0 || res.addressAmount * 1 === 0) { return false; } } else if (res.addressAmount * 1 === 0) { diff --git a/crypto/blockchains/trx/basic/TrxTrongridProvider.ts b/crypto/blockchains/trx/basic/TrxTrongridProvider.ts index d9a447e69..2fd6c0498 100644 --- a/crypto/blockchains/trx/basic/TrxTrongridProvider.ts +++ b/crypto/blockchains/trx/basic/TrxTrongridProvider.ts @@ -9,11 +9,18 @@ import TronUtils from '@crypto/blockchains/trx/ext/TronUtils'; const BALANCE_MAX_TRY = 10; -const CACHE_TRONGRID = {}; +interface CacheData { + isMultisig: boolean; + time: number; + [tokenName: string]: any; +} + +const CACHE_TRONGRID: { [address: string]: CacheData } = {}; + const CACHE_VALID_TIME = 3000; // 3 seconds export default class TrxTrongridProvider { - async isMultisigTrongrid(address) { + async isMultisigTrongrid(address: string): Promise { if (typeof CACHE_TRONGRID[address] !== 'undefined') { return CACHE_TRONGRID[address].isMultisig; } @@ -27,7 +34,27 @@ export default class TrxTrongridProvider { * @param {string} tokenName * @returns {Promise} */ - async get(address, tokenName, useCache = true) { + async get( + address: string, + tokenName: string, + useCache = true + ): Promise< + | false + | { + isMultisig: boolean; + balance: any; + voteTotal: number; + frozen?: any; // Make it optional + frozenExpireTime?: any; // Make it optional + frozenOthers?: any; // Make it optional + frozenEnergy?: any; // Make it optional + frozenEnergyExpireTime?: any; // Make it optional + frozenEnergyOthers?: any; // Make it optional + unconfirmed: number; + provider: string; + time: number; + } + > { const now = new Date().getTime(); if ( useCache && @@ -36,42 +63,49 @@ export default class TrxTrongridProvider { ) { if (typeof CACHE_TRONGRID[address][tokenName] !== 'undefined') { BlocksoftCryptoLog.log( - 'TrxTrongridProvider.get from cache', - address + + `TrxTrongridProvider.get from cache', + ${address} + ' => ' + - tokenName + + ${tokenName} + ' : ' + - CACHE_TRONGRID[address][tokenName] + ${CACHE_TRONGRID[address][tokenName]}` ); + // tslint:disable-next-line:no-shadowed-variable const voteTotal = typeof CACHE_TRONGRID[address].voteTotal !== 'undefined' ? CACHE_TRONGRID[address].voteTotal : 0; + // tslint:disable-next-line:no-shadowed-variable const frozen = typeof CACHE_TRONGRID[address][tokenName + 'frozen'] !== 'undefined' ? CACHE_TRONGRID[address][tokenName + 'frozen'] : 0; + // tslint:disable-next-line:no-shadowed-variable const frozenExpireTime = typeof CACHE_TRONGRID[address][tokenName + 'frozenExpireTime'] !== 'undefined' ? CACHE_TRONGRID[address][tokenName + 'frozenExpireTime'] : 0; + // tslint:disable-next-line:no-shadowed-variable const frozenOthers = typeof CACHE_TRONGRID[address][tokenName + 'frozenOthers'] !== 'undefined' ? CACHE_TRONGRID[address][tokenName + 'frozenOthers'] : 0; + // tslint:disable-next-line:no-shadowed-variable const frozenEnergy = typeof CACHE_TRONGRID[address][tokenName + 'frozenEnergy'] !== 'undefined' ? CACHE_TRONGRID[address][tokenName + 'frozenEnergy'] : 0; + // tslint:disable-next-line:no-shadowed-variable const frozenEnergyExpireTime = typeof CACHE_TRONGRID[address][ tokenName + 'frozenEnergyExpireTime' ] !== 'undefined' ? CACHE_TRONGRID[address][tokenName + 'frozenEnergyExpireTime'] : 0; + // tslint:disable-next-line:no-shadowed-variable const frozenEnergyOthers = typeof CACHE_TRONGRID[address][tokenName + 'frozenEnergyOthers'] !== 'undefined' @@ -93,18 +127,16 @@ export default class TrxTrongridProvider { }; } else if (tokenName !== '_') { return false; - // return { balance: 0, unconfirmed : 0, provider: 'trongrid-cache' } } } - // curl -X POST http://trx.trusteeglobal.com:8091/walletsolidity/getassetissuebyname -d const nodeLink = BlocksoftExternalSettings.getStatic('TRX_SOLIDITY_NODE'); const link = nodeLink + '/walletsolidity/getaccount'; const params = { address }; BlocksoftCryptoLog.log( 'TrxTrongridProvider.get ' + link + ' ' + JSON.stringify(params) ); - const res = await BlocksoftAxios.postWithoutBraking( + const res: { data: any } = await BlocksoftAxios.postWithoutBraking( link, params, BALANCE_MAX_TRY @@ -122,11 +154,12 @@ export default class TrxTrongridProvider { } } - CACHE_TRONGRID[address] = {}; - CACHE_TRONGRID[address].time = now; + CACHE_TRONGRID[address] = { + isMultisig, + time: now + }; CACHE_TRONGRID[address]._ = typeof res.data.balance !== 'undefined' ? res.data.balance : 0; - CACHE_TRONGRID[address].isMultisig = isMultisig; CACHE_TRONGRID[address]._frozen = typeof res.data.frozen !== 'undefined' && typeof res.data.frozen[0] !== 'undefined' @@ -183,7 +216,6 @@ export default class TrxTrongridProvider { if (typeof CACHE_TRONGRID[address][tokenName] === 'undefined') { return false; - // return { balance: 0, unconfirmed : 0, provider: 'trongrid' } } const balance = CACHE_TRONGRID[address][tokenName]; @@ -233,15 +265,23 @@ export default class TrxTrongridProvider { }; } - async getResources(address) { + async getResources(address: string): Promise<{ + leftBand: any; + totalBand: any; + leftEnergy: any; + totalEnergy: any; + }> { const sendLink = BlocksoftExternalSettings.getStatic('TRX_SEND_LINK'); const link = sendLink + '/wallet/getaccountresource'; - let leftBand = false; - let totalBand = false; - let leftEnergy = false; - let totalEnergy = false; + let leftBand: any = false; + let totalBand: any = false; + let leftEnergy: any = false; + let totalEnergy: any = false; try { - const res = await BlocksoftAxios.post(link, { address }); + const res: { data: any } = await BlocksoftAxios.post(link, { address }); + if (!res || typeof res.data !== 'object') { + throw new Error('Invalid response from Trongrid'); + } const tronData = res.data; delete tronData.assetNetUsed; delete tronData.assetNetLimit; @@ -277,7 +317,9 @@ export default class TrxTrongridProvider { if (typeof tronData.EnergyUsed !== 'undefined' && tronData.EnergyUsed) { leftEnergy = leftEnergy - tronData.EnergyUsed * 1; } - } catch (e) {} + } catch (e) { + console.log(e); + } return { leftBand, totalBand, diff --git a/crypto/blockchains/trx/basic/TrxTronscanProvider.ts b/crypto/blockchains/trx/basic/TrxTronscanProvider.ts index 4933300e7..b8a73c055 100644 --- a/crypto/blockchains/trx/basic/TrxTronscanProvider.ts +++ b/crypto/blockchains/trx/basic/TrxTronscanProvider.ts @@ -8,7 +8,15 @@ import BlocksoftAxios from '../../../common/BlocksoftAxios'; const BALANCE_PATH = 'https://apilist.tronscan.org/api/account?address='; const BALANCE_MAX_TRY = 10; -const CACHE_TRONSCAN = {}; +interface TronScanCache { + [address: string]: { + time: number; + voteTotal?: number; + [tokenName: string]: number | undefined; + }; +} + +const CACHE_TRONSCAN: TronScanCache = {}; const CACHE_VALID_TIME = 3000; // 3 seconds export default class TrxTronscanProvider { @@ -18,7 +26,23 @@ export default class TrxTronscanProvider { * @param {string} tokenName * @returns {Promise} */ - async get(address, tokenName, useCache = true) { + async get( + address: string, + tokenName: string, + useCache = true + ): Promise< + | boolean + | number + | { + unconfirmed: number; + frozen: number; + frozenEnergy: number; + voteTotal: number; + balance: number; + provider: string; + time: number; + } + > { const now = new Date().getTime(); if ( useCache && @@ -27,28 +51,31 @@ export default class TrxTronscanProvider { ) { if (typeof CACHE_TRONSCAN[address][tokenName] !== 'undefined') { BlocksoftCryptoLog.log( - 'TrxTronscanProvider.get from cache', - address + + `TrxTronscanProvider.get from cache', + ${address} + ' => ' + - tokenName + + {tokenName} + ' : ' + - CACHE_TRONSCAN[address][tokenName] + ${CACHE_TRONSCAN[address][tokenName]}` ); - const frozen = + // tslint:disable-next-line:no-shadowed-variable + const frozen: number = typeof CACHE_TRONSCAN[address][tokenName + 'frozen'] !== 'undefined' - ? CACHE_TRONSCAN[address][tokenName + 'frozen'] + ? CACHE_TRONSCAN[address][tokenName + 'frozen']! : 0; - const frozenEnergy = + // tslint:disable-next-line:no-shadowed-variable + const frozenEnergy: number = typeof CACHE_TRONSCAN[address][tokenName + 'frozenEnergy'] !== 'undefined' - ? CACHE_TRONSCAN[address][tokenName + 'frozenEnergy'] + ? CACHE_TRONSCAN[address][tokenName + 'frozenEnergy']! : 0; - const voteTotal = + // tslint:disable-next-line:no-shadowed-variable + const voteTotal: number = typeof CACHE_TRONSCAN[address].voteTotal !== 'undefined' - ? CACHE_TRONSCAN[address].voteTotal + ? CACHE_TRONSCAN[address].voteTotal! : 0; return { - balance: CACHE_TRONSCAN[address][tokenName], + balance: CACHE_TRONSCAN[address][tokenName]!, voteTotal, frozen, frozenEnergy, @@ -64,11 +91,12 @@ export default class TrxTronscanProvider { const link = BALANCE_PATH + address; BlocksoftCryptoLog.log('TrxTronscanProvider.get ' + link); const res = await BlocksoftAxios.getWithoutBraking(link, BALANCE_MAX_TRY); - if (!res || !res.data) { + // @ts-ignore + if (!res || !('data' in res)) { return false; } - CACHE_TRONSCAN[address] = {}; + CACHE_TRONSCAN[address] = { time: now }; CACHE_TRONSCAN[address].time = now; CACHE_TRONSCAN[address]._ = res.data.balance; CACHE_TRONSCAN[address]._frozen = @@ -109,18 +137,18 @@ export default class TrxTronscanProvider { } } - const balance = CACHE_TRONSCAN[address][tokenName]; - const frozen = + const balance = CACHE_TRONSCAN[address][tokenName]!; + const frozen: number = typeof CACHE_TRONSCAN[address][tokenName + 'frozen'] !== 'undefined' - ? CACHE_TRONSCAN[address][tokenName + 'frozen'] + ? CACHE_TRONSCAN[address][tokenName + 'frozen']! : 0; - const frozenEnergy = + const frozenEnergy: number = typeof CACHE_TRONSCAN[address][tokenName + 'frozenEnergy'] !== 'undefined' - ? CACHE_TRONSCAN[address][tokenName + 'frozenEnergy'] + ? CACHE_TRONSCAN[address][tokenName + 'frozenEnergy']! : 0; - const voteTotal = + const voteTotal: number = typeof CACHE_TRONSCAN[address].voteTotal !== 'undefined' - ? CACHE_TRONSCAN[address].voteTotal + ? CACHE_TRONSCAN[address].voteTotal! : 0; return { balance, diff --git a/crypto/blockchains/trx/ext/TronStakeUtils.ts b/crypto/blockchains/trx/ext/TronStakeUtils.ts index 25de9eb00..368d9b7e8 100644 --- a/crypto/blockchains/trx/ext/TronStakeUtils.ts +++ b/crypto/blockchains/trx/ext/TronStakeUtils.ts @@ -8,38 +8,64 @@ import Log from '@app/services/Log/Log'; import { BlocksoftTransfer } from '@crypto/actions/BlocksoftTransfer/BlocksoftTransfer'; import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; +interface Balance { + balanceAvailable: number; + frozen: number; + frozenOthers: number; + frozenEnergy: number; + frozenEnergyOthers: number; + frozenExpireTime: number; + frozenEnergyExpireTime: number; + prettyBalanceAvailable: string; + prettyFrozen: string; + prettyFrozenOthers: string; + prettyFrozenEnergy: string; + prettyFrozenEnergyOthers: string; + prettyVote: string; + diffLastStakeMinutes: number; +} + +interface UiParams { + walletHash: string; + address: string; + derivationPath: string; + cryptoValue: number; + type: string; + callback: () => void; +} + const TronStakeUtils = { - async getVoteAddresses() { + async getVoteAddresses(): Promise { return BlocksoftExternalSettings.getStatic('TRX_VOTE_BEST'); }, - async getPrettyBalance(address) { + async getPrettyBalance(address: string): Promise { const balance = await BlocksoftBalances.setCurrencyCode('TRX') .setAddress(address) .getBalance('TronStakeUtils'); if (!balance) { return false; } - balance.prettyBalanceAvailable = BlocksoftPrettyNumbers.setCurrencyCode( + const prettyBalanceAvailable = BlocksoftPrettyNumbers.setCurrencyCode( 'TRX' ).makePretty(balance.balanceAvailable); - balance.prettyFrozen = BlocksoftPrettyNumbers.setCurrencyCode( + const prettyFrozen = BlocksoftPrettyNumbers.setCurrencyCode( 'TRX' ).makePretty(balance.frozen); - balance.prettyFrozenOthers = BlocksoftPrettyNumbers.setCurrencyCode( + const prettyFrozenOthers = BlocksoftPrettyNumbers.setCurrencyCode( 'TRX' ).makePretty(balance.frozenOthers); - balance.prettyFrozenEnergy = BlocksoftPrettyNumbers.setCurrencyCode( + const prettyFrozenEnergy = BlocksoftPrettyNumbers.setCurrencyCode( 'TRX' ).makePretty(balance.frozenEnergy); - balance.prettyFrozenEnergyOthers = BlocksoftPrettyNumbers.setCurrencyCode( + const prettyFrozenEnergyOthers = BlocksoftPrettyNumbers.setCurrencyCode( 'TRX' ).makePretty(balance.frozenEnergyOthers); - balance.prettyVote = ( - balance.prettyFrozen * 1 + - balance.prettyFrozenOthers * 1 + - balance.prettyFrozenEnergy * 1 + - balance.prettyFrozenEnergyOthers * 1 + const prettyVote = ( + parseFloat(prettyFrozen) + + parseFloat(prettyFrozenOthers) + + parseFloat(prettyFrozenEnergy) + + parseFloat(prettyFrozenEnergyOthers) ) .toString() .split('.')[0]; @@ -49,16 +75,29 @@ const TronStakeUtils = { balance.frozenEnergyExpireTime > balance.frozenExpireTime ? balance.frozenEnergyExpireTime : balance.frozenExpireTime; - if (maxExpire > 0) { - balance.diffLastStakeMinutes = - 24 * 3 * 60 - (maxExpire - new Date().getTime()) / 60000; // default time = 3 days, so thats how many minutes from last stake - } else { - balance.diffLastStakeMinutes = -1; - } - return balance; + const diffLastStakeMinutes = + maxExpire > 0 + ? 24 * 3 * 60 - (maxExpire - new Date().getTime()) / 60000 + : -1; + + return { + ...balance, + prettyBalanceAvailable, + prettyFrozen, + prettyFrozenOthers, + prettyFrozenEnergy, + prettyFrozenEnergyOthers, + prettyVote, + diffLastStakeMinutes + }; }, - async sendVoteAll(address, derivationPath, walletHash, specialActionNeeded) { + async sendVoteAll( + address: string, + derivationPath: string, + walletHash: string, + specialActionNeeded: string + ): Promise { const { prettyVote, diffLastStakeMinutes, voteTotal } = await TronStakeUtils.getPrettyBalance(address); if ( @@ -82,7 +121,7 @@ const TronStakeUtils = { 'TronStake.sendVoteAll ' + address + ' skipped vote2' ); return false; - } else if (voteTotal * 1 === prettyVote * 1) { + } else if (voteTotal * 1 === parseFloat(prettyVote)) { if (diffLastStakeMinutes > 100) { BlocksoftCryptoLog.log( 'TronStake.sendVoteAll ' + @@ -117,7 +156,7 @@ const TronStakeUtils = { votes: [ { vote_address: TronUtils.addressToHex(voteAddress), - vote_count: prettyVote * 1 + vote_count: parseFloat(prettyVote) } ] }, @@ -128,14 +167,19 @@ const TronStakeUtils = { derivationPath, type: 'vote', cryptoValue: BlocksoftPrettyNumbers.setCurrencyCode('TRX').makeUnPretty( - prettyVote * 1 + parseFloat(prettyVote) ), callback: () => {} } ); }, - async _send(shortLink, params, langMsg, uiParams) { + async _send( + shortLink: string, + params: any, + langMsg: string, + uiParams: UiParams + ): Promise { const sendLink = BlocksoftExternalSettings.getStatic('TRX_SEND_LINK'); const link = sendLink + shortLink; const tmp = await BlocksoftAxios.post(link, params); diff --git a/crypto/blockchains/trx/ext/TronUtils.ts b/crypto/blockchains/trx/ext/TronUtils.ts index 2d09adfab..07381127a 100644 --- a/crypto/blockchains/trx/ext/TronUtils.ts +++ b/crypto/blockchains/trx/ext/TronUtils.ts @@ -1,92 +1,91 @@ /** * @version 0.9 */ -const elliptic = require('elliptic') -const ec = new elliptic.ec('secp256k1') -const createHash = require('create-hash') +import * as elliptic from 'elliptic'; +import createHash from 'create-hash'; +import * as EthUtil from 'ethereumjs-util'; +import * as bs58 from 'bs58'; -const EthUtil = require('ethereumjs-util') +const ec: elliptic.ec = new elliptic.ec('secp256k1'); -function byte2hexStr(byte) { - if (typeof byte !== 'number') - throw new Error('Input must be a number') +function byte2hexStr(byte: number): string { + if (typeof byte !== 'number') throw new Error('Input must be a number'); + if (byte < 0 || byte > 255) throw new Error('Input must be a byte'); - if (byte < 0 || byte > 255) - throw new Error('Input must be a byte') - - const hexByteMap = '0123456789ABCDEF' + // let hexByteMap = '0123456789ABCDEF'; + const hexString = byte.toString(16).toUpperCase(); + return hexString.length === 1 ? '0' + hexString : hexString; +} - let str = '' - str += hexByteMap.charAt(byte >> 4) - str += hexByteMap.charAt(byte & 0x0f) - return str +interface Signature { + r: Buffer; + s: Buffer; + recoveryParam: number; } -// noinspection JSConstructorReturnsPrimitive export default { - // noinspection JSConstructorReturnsPrimitive - ECKeySign : function(hashBytes, privateBytes) { - const key = ec.keyFromPrivate(privateBytes, 'bytes') - const signature = key.sign(hashBytes) - const r = signature.r - const s = signature.s - const id = signature.recoveryParam - - let rHex = r.toString('hex') - - while (rHex.length < 64) { - rHex = `0${rHex}` - } - - let sHex = s.toString('hex') - - while (sHex.length < 64) { - sHex = `0${sHex}` - } + ECKeySign(hashBytes: Buffer, privateBytes: Buffer): string { + const key: elliptic.ec.KeyPair = ec.keyFromPrivate(privateBytes, 'bytes'); + const signature: Signature = key.sign(hashBytes); + const r: Buffer = signature.r; + const s: Buffer = signature.s; + const id: number = signature.recoveryParam; + + let rHex: string = r.toString('hex'); + while (rHex.length < 64) { + rHex = `0${rHex}`; + } - const idHex = byte2hexStr(id) - return rHex + sHex + idHex - }, + let sHex: string = s.toString('hex'); + while (sHex.length < 64) { + sHex = `0${sHex}`; + } - addressToHex: function(address) { - if (address.substr(0, 2) === '41') { - return address - } - const bs58 = require('bs58') - const decoded = bs58.decode(address.trim()) - return decoded.slice(0,21).toString('hex') - }, + const idHex: string = byte2hexStr(id); + return rHex + sHex + idHex; + }, - privHexToPubHex: function(privateHex) { - const key = ec.keyFromPrivate(privateHex, 'hex') - const pubkey = key.getPublic() - const x = pubkey.x - const y = pubkey.y + addressToHex(address: string): string { + if (address.substr(0, 2) === '41') { + return address; + } + const decoded: Buffer = bs58.decode(address.trim()); + return decoded.slice(0, 21).toString('hex'); + }, + + privHexToPubHex(privateHex: string): string { + const key: elliptic.ec.KeyPair = ec.keyFromPrivate(privateHex, 'hex'); + const pubkey: elliptic.ec.KeyPair = key.getPublic(); + const x: Buffer = pubkey.x; + const y: Buffer = pubkey.y; + + let xHex: string = x.toString('hex'); + while (xHex.length < 64) { + xHex = `0${xHex}`; + } - let xHex = x.toString('hex') - while (xHex.length < 64) { - xHex = `0${xHex}` - } - let yHex = y.toString('hex') - while (yHex.length < 64) { - yHex = `0${yHex}` - } - return `04${xHex}${yHex}` - }, + let yHex: string = y.toString('hex'); + while (yHex.length < 64) { + yHex = `0${yHex}`; + } - pubHexToAddressHex: function(pubHex) { // actually the same as direct but better code - if (pubHex.substr(0, 2) === '04') { - pubHex = '0x' + pubHex.substr(2) - } - return '41' + EthUtil.publicToAddress(pubHex).toString('hex') - }, + return `04${xHex}${yHex}`; + }, - addressHexToStr: function(addressHex) { - const one = createHash('sha256').update(addressHex, 'hex').digest('hex') - const hash = createHash('sha256').update(one, 'hex').digest() - const checksum = hash.slice(0, 4) // checkSum = the first 4 bytes of hash - const checkSummed = addressHex + checksum.toString('hex') - const bs58 = require('bs58') - return bs58.encode(Buffer.from(checkSummed, 'hex')) + pubHexToAddressHex(pubHex: string): string { + if (pubHex.substr(0, 2) === '04') { + pubHex = '0x' + pubHex.substr(2); } -} + return '41' + EthUtil.publicToAddress(pubHex).toString('hex'); + }, + + addressHexToStr(addressHex: string): string { + const one: string = createHash('sha256') + .update(addressHex, 'hex') + .digest('hex'); + const hash: Buffer = createHash('sha256').update(one, 'hex').digest(); + const checksum: Buffer = hash.slice(0, 4); // checkSum = the first 4 bytes of hash + const checkSummed: string = addressHex + checksum.toString('hex'); + return bs58.encode(Buffer.from(checkSummed, 'hex')); + } +}; diff --git a/crypto/blockchains/trx/providers/TrxSendProvider.ts b/crypto/blockchains/trx/providers/TrxSendProvider.ts index a98d3827d..6d294c6c4 100644 --- a/crypto/blockchains/trx/providers/TrxSendProvider.ts +++ b/crypto/blockchains/trx/providers/TrxSendProvider.ts @@ -1,225 +1,214 @@ -/** - * @version 0.41 - */ -import { BlocksoftBlockchainTypes } from '@crypto/blockchains/BlocksoftBlockchainTypes'; -import DogeSendProvider from '@crypto/blockchains/doge/providers/DogeSendProvider'; -import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; - -import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; -import config from '@app/config/config'; -import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings'; - -export default class TrxSendProvider - extends DogeSendProvider - implements BlocksoftBlockchainTypes.SendProvider -{ - trxError(msg: string) { - if (config.debug.cryptoErrors) { - console.log(this._settings.currencyCode + ' TrxSendProvider ' + msg); - } - if ( - this._settings.currencyCode !== 'TRX' && - msg.indexOf('AccountResourceInsufficient') !== -1 - ) { - throw new Error('SERVER_RESPONSE_NOT_ENOUGH_FEE'); - } else if ( - msg.indexOf( - 'Validate TransferContract error, balance is not sufficient.' - ) !== -1 - ) { - throw new Error('SERVER_RESPONSE_NOTHING_TO_TRANSFER_FROM_ACTUAL_NODE'); - } else if (msg.indexOf('balance is not sufficient') !== -1) { - throw new Error('SERVER_RESPONSE_NOT_ENOUGH_FEE'); - } else if (msg.indexOf('account not exist') !== -1) { - throw new Error('SERVER_RESPONSE_NOT_ENOUGH_FEE'); - } else if (msg.indexOf('Amount must greater than 0') !== -1) { - throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_DUST'); - } else if ( - msg.indexOf('assetBalance must be greater than 0') !== -1 || - msg.indexOf('assetBalance is not sufficient') !== -1 - ) { - throw new Error('SERVER_RESPONSE_NOTHING_TO_TRANSFER_FROM_ACTUAL_NODE'); - } else { - throw new Error(msg); - } - } - - async _sendTx( - tx: any, - subtitle: string, - txRBF: any, - logData: any - ): Promise<{ transactionHash: string; logData: any }> { - await BlocksoftCryptoLog.log( - this._settings.currencyCode + - ' TrxSendProvider._sendTx ' + - subtitle + - ' started ', - logData - ); - - const sendLink = BlocksoftExternalSettings.getStatic('TRX_SEND_LINK'); - const link = sendLink + '/wallet/broadcasttransaction'; - if (config.debug.cryptoErrors) { - console.log( - new Date().toISOString() + - ' ' + - this._settings.currencyCode + - ' TrxSendProvider._sendTx ' + - subtitle + - ' started check ' - ); - } - logData = await this._check(tx.raw_data_hex, subtitle, txRBF, logData); - if (config.debug.cryptoErrors) { - BlocksoftCryptoLog.log( - new Date().toISOString() + - ' ' + - this._settings.currencyCode + - ' TrxSendProvider._sendTx ' + - subtitle + - ' ended check ' - ); - } - - let send = false; - try { - send = await BlocksoftAxios.post(link, tx); - } catch (e) { - if (config.debug.cryptoErrors) { - console.log( - this._settings.currencyCode + - ' TrxSendProvider._sendTx broadcast error ' + - e.message - ); - } - } - - // @ts-ignore - if (!send || typeof send.data === 'undefined' || !send.data) { - throw new Error('SERVER_RESPONSE_NOT_CONNECTED'); - } - - await BlocksoftCryptoLog.log( - this._settings.currencyCode + - ' TrxSendProvider._sendTx ' + - subtitle + - ' result ', - send.data - ); - - if (typeof send.data.code !== 'undefined') { - if (send.data.code === 'BANDWITH_ERROR') { - throw new Error('SERVER_RESPONSE_BANDWITH_ERROR_TRX'); - } else if (send.data.code === 'SERVER_BUSY') { - throw new Error('SERVER_RESPONSE_NOT_CONNECTED'); - } - } - - // @ts-ignore - if (typeof send.data.Error !== 'undefined') { - await BlocksoftCryptoLog.log( - this._settings.currencyCode + - ' TrxSendProvider._sendTx error ' + - send.data.Error - ); - // @ts-ignore - throw new Error(send.data.Error); - } - // @ts-ignore - if (typeof send.data.result === 'undefined') { - // @ts-ignore - if (typeof send.data.message !== 'undefined') { - let msg = false; - try { - // @ts-ignore - const buf = Buffer.from(send.data.message, 'hex'); - // @ts-ignore - msg = buf.toString(''); - } catch (e) { - // do nothing - } - await BlocksoftCryptoLog.log( - this._settings.currencyCode + ' TrxSendProvider._sendTx msg ' + msg - ); - if (msg) { - // @ts-ignore - send.data.decoded = msg; - // @ts-ignore - this.trxError(msg); - } - } - // @ts-ignore - this.trxError('no transaction result ' + JSON.stringify(send.data)); - } else { - // @ts-ignore - if (send.data.result !== true) { - // @ts-ignore - this.trxError( - 'transaction result is false ' + JSON.stringify(send.data) - ); - } - } - - return { transactionHash: tx.txID, logData }; - } - - async sendTx( - tx: any, - subtitle: string, - txRBF: any, - logData: any - ): Promise<{ transactionHash: string; transactionJson: any; logData }> { - await BlocksoftCryptoLog.log( - this._settings.currencyCode + - ' TrxSendProvider.sendTx ' + - subtitle + - ' started ', - logData - ); - - let send, transactionHash; - try { - send = await this._sendTx(tx, subtitle, txRBF, logData); - transactionHash = send.transactionHash; - } catch (e) { - if (config.debug.cryptoErrors) { - console.log( - this._settings.currencyCode + ' TrxSendProvider.sendTx error ', - e - ); - } - try { - logData.error = e.message; - await this._checkError(tx.raw_data_hex, subtitle, txRBF, logData); - } catch (e2) { - if (config.debug.cryptoErrors) { - console.log( - this._settings.currencyCode + - ' TrxSendProvider.send proxy error errorTx ' + - e.message - ); - } - await BlocksoftCryptoLog.log( - this._settings.currencyCode + - ' TrxSendProvider.send proxy error errorTx ' + - e2.message - ); - } - throw e; - } - - try { - logData = await this._checkSuccess( - transactionHash, - tx.raw_data_hex, - subtitle, - txRBF, - logData - ); - } catch (e) { - throw new Error(e.message + ' in _checkSuccess wrapped TRX'); - } - return { transactionHash, transactionJson: {}, logData }; - } -} +/** + * @version 0.41 + */ +// @ts-ignore +import { BlocksoftBlockchainTypes } from '@crypto/blockchains/BlocksoftBlockchainTypes'; +import DogeSendProvider from '@crypto/blockchains/doge/providers/DogeSendProvider'; +import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; + +import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; +import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings'; +import config from '@constants/config'; + +export default class TrxSendProvider + extends DogeSendProvider + implements BlocksoftBlockchainTypes.SendProvider +{ + trxError(msg: string) { + if (config.debug.cryptoErrors) { + console.log(this._settings.currencyCode + ' TrxSendProvider ' + msg); + } + if ( + this._settings.currencyCode !== 'TRX' && + msg.indexOf('AccountResourceInsufficient') !== -1 + ) { + throw new Error('SERVER_RESPONSE_NOT_ENOUGH_FEE'); + } else if ( + msg.indexOf( + 'Validate TransferContract error, balance is not sufficient.' + ) !== -1 + ) { + throw new Error('SERVER_RESPONSE_NOTHING_TO_TRANSFER_FROM_ACTUAL_NODE'); + } else if (msg.indexOf('balance is not sufficient') !== -1) { + throw new Error('SERVER_RESPONSE_NOT_ENOUGH_FEE'); + } else if (msg.indexOf('account not exist') !== -1) { + throw new Error('SERVER_RESPONSE_NOT_ENOUGH_FEE'); + } else if (msg.indexOf('Amount must greater than 0') !== -1) { + throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_DUST'); + } else if ( + msg.indexOf('assetBalance must be greater than 0') !== -1 || + msg.indexOf('assetBalance is not sufficient') !== -1 + ) { + throw new Error('SERVER_RESPONSE_NOTHING_TO_TRANSFER_FROM_ACTUAL_NODE'); + } else { + throw new Error(msg); + } + } + + isResponseObject(obj: any): obj is { data: any } { + return typeof obj === 'object' && obj !== null && 'data' in obj; + } + + async _sendTx( + tx: any, + subtitle: string, + txRBF: any, + logData: any + ): Promise<{ transactionHash: string; logData: any }> { + await BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' TrxSendProvider._sendTx ' + + subtitle + + ' started ', + logData + ); + + const sendLink = BlocksoftExternalSettings.getStatic('TRX_SEND_LINK'); + const link = sendLink + '/wallet/broadcasttransaction'; + if (config.debug.cryptoErrors) { + console.log( + new Date().toISOString() + + ' ' + + this._settings.currencyCode + + ' TrxSendProvider._sendTx ' + + subtitle + + ' started check ' + ); + } + logData = await this._check(tx.raw_data_hex, subtitle, txRBF, logData); + if (config.debug.cryptoErrors) { + BlocksoftCryptoLog.log( + new Date().toISOString() + + ' ' + + this._settings.currencyCode + + ' TrxSendProvider._sendTx ' + + subtitle + + ' ended check ' + ); + } + + let send: any; + try { + send = await BlocksoftAxios.post(link, tx); + } catch (e: any) { + if (config.debug.cryptoErrors) { + console.log( + this._settings.currencyCode + + ' TrxSendProvider._sendTx broadcast error ' + + e.message + ); + } + } + + // Explicit type check for 'send' + if (typeof send !== 'boolean' && send.data) { + if (typeof send.data.code !== 'undefined') { + if (send.data.code === 'BANDWITH_ERROR') { + throw new Error('SERVER_RESPONSE_BANDWITH_ERROR_TRX'); + } else if (send.data.code === 'SERVER_BUSY') { + throw new Error('SERVER_RESPONSE_NOT_CONNECTED'); + } + } + + if (typeof send.data.Error !== 'undefined') { + await BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' TrxSendProvider._sendTx error ' + + send.data.Error + ); + throw new Error(send.data.Error); + } + + if (typeof send.data.result === 'undefined') { + if (typeof send.data.message !== 'undefined') { + let msg: string | false = false; + try { + const buf = Buffer.from(send.data.message, 'hex'); + // @ts-ignore + msg = buf.toString(''); + } catch (e) { + // do nothing + } + await BlocksoftCryptoLog.log( + this._settings.currencyCode + ' TrxSendProvider._sendTx msg ' + msg + ); + if (msg) { + send.data.decoded = msg; + this.trxError(msg); + } + } + this.trxError('no transaction result ' + JSON.stringify(send.data)); + } else { + if (send.data.result !== true) { + this.trxError( + 'transaction result is false ' + JSON.stringify(send.data) + ); + } + } + } else { + throw new Error('SERVER_RESPONSE_NOT_CONNECTED'); + } + + return { transactionHash: tx.txID, logData }; + } + + async sendTx( + tx: any, + subtitle: string, + txRBF: any, + logData: any + ): Promise<{ transactionHash: string; transactionJson: any; logData: any }> { + await BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' TrxSendProvider.sendTx ' + + subtitle + + ' started ', + logData + ); + + let send; + let transactionHash; + try { + send = await this._sendTx(tx, subtitle, txRBF, logData); + transactionHash = send.transactionHash; + } catch (e: any) { + if (config.debug.cryptoErrors) { + console.log( + this._settings.currencyCode + ' TrxSendProvider.sendTx error ', + e + ); + } + try { + logData.error = e.message; + await this._checkError(tx.raw_data_hex, subtitle, txRBF, logData); + } catch (e2: any) { + if (config.debug.cryptoErrors) { + console.log( + this._settings.currencyCode + + ' TrxSendProvider.send proxy error errorTx ' + + e.message + ); + } + await BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' TrxSendProvider.send proxy error errorTx ' + + e2.message + ); + } + throw e; + } + + try { + logData = await this._checkSuccess( + transactionHash, + tx.raw_data_hex, + subtitle, + txRBF, + logData + ); + } catch (e: any) { + throw new Error(e.message + ' in _checkSuccess wrapped TRX'); + } + return { transactionHash, transactionJson: {}, logData }; + } +} diff --git a/src/constants/config.ts b/src/constants/config.ts index 4c5b9dc2d..cf713e70d 100644 --- a/src/constants/config.ts +++ b/src/constants/config.ts @@ -7,7 +7,8 @@ const envs = { env: 'prod', debug: { appBuildVersion: '1.0.0', - cryptoErrors: true + cryptoErrors: true, + appErrors: false } }, stage: { @@ -17,7 +18,8 @@ const envs = { env: 'stage', debug: { appBuildVersion: '1.0.0', - cryptoErrors: true + cryptoErrors: true, + appErrors: false } } }; From 547e5bdf7e1a94e4c4e044d0df370ece9191853f Mon Sep 17 00:00:00 2001 From: JavidHaji-zada Date: Thu, 3 Aug 2023 00:33:09 +0400 Subject: [PATCH 022/509] converted doge --- ...erProcessor.js => DogeScannerProcessor.ts} | 30 +- .../blockchains/doge/DogeTransferProcessor.ts | 211 +++---------- ...Function.js => DogeFindAddressFunction.ts} | 7 +- crypto/blockchains/doge/basic/DogeLogs.ts | 22 +- .../doge/basic/DogeNetworkPrices.ts | 4 +- .../doge/providers/DogeSendProvider.ts | 297 +----------------- .../doge/providers/DogeUnspentsProvider.ts | 18 +- .../stores/{DogeRawDS.js => DogeRawDS.ts} | 44 ++- crypto/blockchains/doge/tx/DogeTxBuilder.ts | 69 +--- .../doge/tx/DogeTxInputsOutputs.ts | 59 ++-- crypto/common/ext/networks-constants.js | 177 ++++++----- package.json | 1 + src/appTypes/blockchain/Transaction.ts | 30 ++ src/appTypes/blockchain/index.ts | 1 + src/appTypes/database/DatabaseTable.ts | 1 + src/appTypes/index.ts | 1 + src/database/main.ts | 2 + .../models/transaction-scanners-tmp.ts | 2 +- src/database/models/transactions-raw.ts | 34 ++ src/database/schemas/index.ts | 2 + src/database/schemas/transaction-raw.ts | 56 ++++ yarn.lock | 5 + 22 files changed, 374 insertions(+), 699 deletions(-) rename crypto/blockchains/doge/{DogeScannerProcessor.js => DogeScannerProcessor.ts} (93%) rename crypto/blockchains/doge/basic/{DogeFindAddressFunction.js => DogeFindAddressFunction.ts} (95%) rename crypto/blockchains/doge/stores/{DogeRawDS.js => DogeRawDS.ts} (88%) create mode 100644 src/appTypes/blockchain/Transaction.ts create mode 100644 src/appTypes/blockchain/index.ts create mode 100644 src/database/models/transactions-raw.ts create mode 100644 src/database/schemas/transaction-raw.ts diff --git a/crypto/blockchains/doge/DogeScannerProcessor.js b/crypto/blockchains/doge/DogeScannerProcessor.ts similarity index 93% rename from crypto/blockchains/doge/DogeScannerProcessor.js rename to crypto/blockchains/doge/DogeScannerProcessor.ts index cd8ddde2c..080e48bfa 100644 --- a/crypto/blockchains/doge/DogeScannerProcessor.js +++ b/crypto/blockchains/doge/DogeScannerProcessor.ts @@ -27,10 +27,10 @@ import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog'; import DogeFindAddressFunction from './basic/DogeFindAddressFunction'; import BlocksoftExternalSettings from '../../common/BlocksoftExternalSettings'; import DogeRawDS from './stores/DogeRawDS'; -import EthRawDS from '../eth/stores/EthRawDS'; +import { BlockchainTransaction } from '@appTypes'; const CACHE_VALID_TIME = 30000; // 30 seconds -const CACHE = {}; +const CACHE: { [key: string]: unknown } = {}; //TODO fix type const TIMEOUT_DOGE = 60000; const PROXY_TXS = 'https://proxy.trustee.deals/btc/getTxs'; @@ -53,20 +53,17 @@ export default class DogeScannerProcessor { */ _trezorServer = false; - constructor(settings) { + _settings: unknown; // TODO fix type + + constructor(settings: unknown) { this._settings = settings; } - _addressesForFind(address, jsonData = {}) { + _addressesForFind(address: string, jsonData = {}) { return [address]; } - /** - * @param address - * @returns {Promise} - * @private - */ - async _get(address, jsonData) { + private async _get(address: string, jsonData): Promise { const now = new Date().getTime(); if ( typeof CACHE[address] !== 'undefined' && @@ -139,7 +136,7 @@ export default class DogeScannerProcessor { * @param {string} address * @return {Promise<{balance:*, unconfirmed:*, provider:string}>} */ - async getBalanceBlockchain(address, jsonData = {}) { + async getBalanceBlockchain(address: string, jsonData = {}) { BlocksoftCryptoLog.log( this._settings.currencyCode + ' DogeScannerProcessor.getBalance started ' + @@ -162,7 +159,10 @@ export default class DogeScannerProcessor { * @param {*} scanData.additional * @return {Promise} */ - async getTransactionsBlockchain(scanData) { + async getTransactionsBlockchain(scanData: { + account: { address: string }; + additional: unknown; // TODO fix type + }) { const address = scanData.account.address.trim(); const jsonData = scanData.additional; BlocksoftCryptoLog.log( @@ -276,7 +276,11 @@ export default class DogeScannerProcessor { * @return {Promise} * @private */ - async _unifyTransaction(address, transaction, jsonData = {}) { + async _unifyTransaction( + address: string, + transaction: BlockchainTransaction, + jsonData = {} + ) { let showAddresses = false; try { const tmp = this._addressesForFind(address, jsonData); diff --git a/crypto/blockchains/doge/DogeTransferProcessor.ts b/crypto/blockchains/doge/DogeTransferProcessor.ts index 43997f2af..e21341f45 100644 --- a/crypto/blockchains/doge/DogeTransferProcessor.ts +++ b/crypto/blockchains/doge/DogeTransferProcessor.ts @@ -1,7 +1,7 @@ /** * @version 0.20 */ -import { BlocksoftBlockchainTypes } from '../BlocksoftBlockchainTypes'; +import { AirDAOBlockchainTypes } from '../AirDAOBlockchainTypes'; import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog'; import BlocksoftUtils from '../../common/BlocksoftUtils'; @@ -13,22 +13,18 @@ import DogeSendProvider from './providers/DogeSendProvider'; import DogeRawDS from './stores/DogeRawDS'; import { DogeLogs } from './basic/DogeLogs'; -import MarketingEvent from '../../../app/services/Marketing/MarketingEvent'; -import config from '../../../app/config/config'; -import { err } from 'react-native-svg/lib/typescript/xml'; -import { sublocale } from '../../../app/services/i18n'; -import settingsActions from '../../../app/appstores/Stores/Settings/SettingsActions'; import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings'; +import { Database } from '@database'; -const networksConstants = require('../../common/ext/networks-constants'); +import networksConstants from '../../common/ext/networks-constants'; const MAX_UNSPENTS = 100; export default class DogeTransferProcessor - implements BlocksoftBlockchainTypes.TransferProcessor + implements AirDAOBlockchainTypes.TransferProcessor { _trezorServerCode = 'DOGE_TREZOR_SERVER'; - _builderSettings: BlocksoftBlockchainTypes.BuilderSettings = { + _builderSettings: AirDAOBlockchainTypes.BuilderSettings = { minOutputDustReadable: 0.001, minChangeDustReadable: 0.5, feeMaxForByteSatoshi: 100000000, // for tx builder @@ -42,28 +38,28 @@ export default class DogeTransferProcessor feeMinTotalReadable: 1 }; - _initedProviders: boolean = false; + _initedProviders = false; - _settings: BlocksoftBlockchainTypes.CurrencySettings; + _settings: AirDAOBlockchainTypes.CurrencySettings; _langPrefix: string; // @ts-ignore - networkPrices: BlocksoftBlockchainTypes.NetworkPrices; + networkPrices: AirDAOBlockchainTypes.NetworkPrices; // @ts-ignore - unspentsProvider: BlocksoftBlockchainTypes.UnspentsProvider; + unspentsProvider: AirDAOBlockchainTypes.UnspentsProvider; // @ts-ignore - sendProvider: BlocksoftBlockchainTypes.SendProvider; + sendProvider: AirDAOBlockchainTypes.SendProvider; // @ts-ignore - txPrepareInputsOutputs: BlocksoftBlockchainTypes.TxInputsOutputs; + txPrepareInputsOutputs: AirDAOBlockchainTypes.TxInputsOutputs; // @ts-ignore - txBuilder: BlocksoftBlockchainTypes.TxBuilder; + txBuilder: AirDAOBlockchainTypes.TxBuilder; - constructor(settings: BlocksoftBlockchainTypes.CurrencySettings) { + constructor(settings: AirDAOBlockchainTypes.CurrencySettings) { this._settings = settings; this._langPrefix = networksConstants[settings.network].langPrefix; this.networkPrices = new DogeNetworkPrices(); @@ -96,10 +92,10 @@ export default class DogeTransferProcessor } async getFeeRate( - data: BlocksoftBlockchainTypes.TransferData, - privateData: BlocksoftBlockchainTypes.TransferPrivateData, - additionalData: BlocksoftBlockchainTypes.TransferAdditionalData = {} - ): Promise { + data: AirDAOBlockchainTypes.TransferData, + privateData: AirDAOBlockchainTypes.TransferPrivateData, + additionalData: AirDAOBlockchainTypes.TransferAdditionalData = {} + ): Promise { this._initProviders(); let isStaticFee = @@ -258,10 +254,10 @@ export default class DogeTransferProcessor ); } - const result: BlocksoftBlockchainTypes.FeeRateResult = { + const result: AirDAOBlockchainTypes.FeeRateResult = { selectedFeeIndex: -1, - fees: [] as BlocksoftBlockchainTypes.Fee[] - } as BlocksoftBlockchainTypes.FeeRateResult; + fees: [] as AirDAOBlockchainTypes.Fee[] + } as AirDAOBlockchainTypes.FeeRateResult; const keys = ['speed_blocks_12', 'speed_blocks_6', 'speed_blocks_2']; const checkedPrices = {}; @@ -477,33 +473,6 @@ export default class DogeTransferProcessor continue; } } catch (e) { - if (config.debug.cryptoErrors) { - console.log( - this._settings.currencyCode + - ' DogeTransferProcessor.getFeeRate_' + - key + - ' ' + - feeForByte + - ' getInputsOutputs error', - e - ); - } - // noinspection ES6MissingAwait - MarketingEvent.logOnlyRealTime( - 'v20_doge_error_getfeerate_' + - key + - ' ' + - feeForByte + - ' ' + - this._settings.currencyCode + - ' ' + - data.addressFrom + - ' => ' + - data.addressTo + - ' ' + - e.message, - unspents - ); throw e; } @@ -650,53 +619,11 @@ export default class DogeTransferProcessor } } while (doBuild); } catch (e) { - if (config.debug.cryptoErrors) { - console.log( - this._settings.currencyCode + - ' DogeTransferProcessor.getRawTx error ' + - e.message - ); - /* - console.log('') - console.log('') - if (preparedInputsOutputs.inputs) { - let i = 0 - for (let input of preparedInputsOutputs.inputs) { - console.log('ERR inputs [' + i + ']', JSON.parse(JSON.stringify(input))) - i++ - } - } - if (preparedInputsOutputs.outputs) { - let i = 0 - for (let output of preparedInputsOutputs.outputs) { - console.log('ERR outputs [' + i + ']', JSON.parse(JSON.stringify(output))) - i++ - } - } - console.log('ERR fee msg ', preparedInputsOutputs.msg) - console.log('ERR diffInOutS', logInputsOutputs.diffInOut) - console.log('ERR diffInOutR', logInputsOutputs.diffInOutReadable) - console.log('---------------------') - console.log('') - */ - } BlocksoftCryptoLog.log( this._settings.currencyCode + ' DogeTransferProcessor.getRawTx error ' + e.message ); - MarketingEvent.logOnlyRealTime( - 'v20_doge_error_tx_builder_fees ' + - this._settings.currencyCode + - ' ' + - data.addressFrom + - ' => ' + - data.addressTo + - ' ' + - e.message.toString(), - logInputsOutputs - ); - if (e.message.indexOf('Transaction has absurd fees') !== -1) { isError = 'SERVER_RESPONSE_TOO_BIG_FEE_PER_BYTE_FOR_TRANSACTION'; continue; @@ -774,10 +701,10 @@ export default class DogeTransferProcessor } async getTransferAllBalance( - data: BlocksoftBlockchainTypes.TransferData, - privateData: BlocksoftBlockchainTypes.TransferPrivateData, - additionalData: BlocksoftBlockchainTypes.TransferAdditionalData = {} - ): Promise { + data: AirDAOBlockchainTypes.TransferData, + privateData: AirDAOBlockchainTypes.TransferPrivateData, + additionalData: AirDAOBlockchainTypes.TransferAdditionalData = {} + ): Promise { data.isTransferAll = true; const result = await this.getFeeRate(data, privateData, additionalData); // @ts-ignore @@ -799,10 +726,10 @@ export default class DogeTransferProcessor } async sendTx( - data: BlocksoftBlockchainTypes.TransferData, - privateData: BlocksoftBlockchainTypes.TransferPrivateData, - uiData: BlocksoftBlockchainTypes.TransferUiData - ): Promise { + data: AirDAOBlockchainTypes.TransferData, + privateData: AirDAOBlockchainTypes.TransferPrivateData, + uiData: AirDAOBlockchainTypes.TransferUiData + ): Promise { if ( typeof uiData.selectedFee.blockchainData === 'undefined' && typeof uiData.selectedFee.feeForTx === 'undefined' @@ -854,10 +781,8 @@ export default class DogeTransferProcessor logData.from = data.addressFrom; logData.basicAddressTo = data.addressTo; logData.basicAmount = data.amount; - logData.pushLocale = sublocale(); - logData.pushSetting = await settingsActions.getSetting( - 'transactionsNotifs' - ); + logData.pushLocale = 'en'; // TODO + logData.pushSetting = await Database.localStorage.get('transactionsNotifs'); if ( typeof uiData !== 'undefined' && @@ -871,7 +796,7 @@ export default class DogeTransferProcessor }; } - let result = {} as BlocksoftBlockchainTypes.SendTxResult; + let result = {} as AirDAOBlockchainTypes.SendTxResult; try { result = await this.sendProvider.sendTx( uiData.selectedFee.blockchainData.rawTxHex, @@ -880,29 +805,11 @@ export default class DogeTransferProcessor logData ); } catch (e) { - if (config.debug.cryptoErrors) { - console.log( - this._settings.currencyCode + ' DogeTransferProcessor.sent error', - e - ); - } BlocksoftCryptoLog.log( this._settings.currencyCode + ' DogeTransferProcessor.sent error ' + e.message ); - // noinspection ES6MissingAwait - MarketingEvent.logOnlyRealTime( - 'v20_doge_tx_error ' + - this._settings.currencyCode + - ' ' + - data.addressFrom + - ' => ' + - data.addressTo + - ' ' + - e.message, - logData - ); throw e; } @@ -974,54 +881,17 @@ export default class DogeTransferProcessor JSON.stringify(result.transactionJson) ); } catch (e) { - if (config.debug.cryptoErrors) { - console.log( - this._settings.currencyCode + - ' DogeTransferProcessor.sent error additional', - e, - uiData - ); - } BlocksoftCryptoLog.log( this._settings.currencyCode + ' DogeTransferProcessor.sent error additional' + e.message ); - // noinspection ES6MissingAwait - MarketingEvent.logOnlyRealTime( - 'v20_doge_tx_error2 ' + - this._settings.currencyCode + - ' ' + - data.addressFrom + - ' => ' + - data.addressTo + - ' ' + - e.message, - logData - ); - } - // noinspection ES6MissingAwait - MarketingEvent.logOnlyRealTime( - 'v20_doge_tx_success ' + - this._settings.currencyCode + - ' ' + - data.addressFrom + - ' => ' + - data.addressTo, - logData - ); - - if (config.debug.cryptoErrors) { - console.log( - this._settings.currencyCode + ' DogeTransferProcessor.sendTx result', - JSON.parse(JSON.stringify(result)) - ); } return result; } async sendRawTx( - data: BlocksoftBlockchainTypes.DbAccount, + data: AirDAOBlockchainTypes.DbAccount, rawTxHex: string, txRBF: any, logData: any @@ -1037,29 +907,20 @@ export default class DogeTransferProcessor } async setMissingTx( - data: BlocksoftBlockchainTypes.DbAccount, - transaction: BlocksoftBlockchainTypes.DbTransaction + data: AirDAOBlockchainTypes.DbAccount, + transaction: AirDAOBlockchainTypes.DbTransaction ): Promise { DogeRawDS.cleanRaw({ address: data.address, transactionHash: transaction.transactionHash, currencyCode: this._settings.currencyCode }); - MarketingEvent.logOnlyRealTime( - 'v20_doge_tx_set_missing ' + - this._settings.currencyCode + - ' ' + - data.address + - ' => ' + - transaction.addressTo, - transaction - ); return true; } canRBF( - data: BlocksoftBlockchainTypes.DbAccount, - transaction: BlocksoftBlockchainTypes.DbTransaction + data: AirDAOBlockchainTypes.DbAccount, + transaction: AirDAOBlockchainTypes.DbTransaction ): boolean { if (transaction.transactionDirection === 'income') { return true; diff --git a/crypto/blockchains/doge/basic/DogeFindAddressFunction.js b/crypto/blockchains/doge/basic/DogeFindAddressFunction.ts similarity index 95% rename from crypto/blockchains/doge/basic/DogeFindAddressFunction.js rename to crypto/blockchains/doge/basic/DogeFindAddressFunction.ts index b9aa310a3..328d6a150 100644 --- a/crypto/blockchains/doge/basic/DogeFindAddressFunction.js +++ b/crypto/blockchains/doge/basic/DogeFindAddressFunction.ts @@ -19,10 +19,13 @@ * @returns {Promise<{from: string, to: string, value: number, direction: string}>} * @constructor */ -import BlocksoftUtils from '../../../common/BlocksoftUtils'; import BlocksoftBN from '../../../common/BlocksoftBN'; +import { BlockchainTransaction } from '@appTypes'; -export default async function DogeFindAddressFunction(addresses, transaction) { +export default async function DogeFindAddressFunction( + addresses: string[], + transaction: BlockchainTransaction +): Promise<{ from: string; to: string; value: number; direction: string }> { const inputMyBN = new BlocksoftBN(0); const inputOthersBN = new BlocksoftBN(0); const inputOthersAddresses = []; diff --git a/crypto/blockchains/doge/basic/DogeLogs.ts b/crypto/blockchains/doge/basic/DogeLogs.ts index 8cccf7e3f..1c8762760 100644 --- a/crypto/blockchains/doge/basic/DogeLogs.ts +++ b/crypto/blockchains/doge/basic/DogeLogs.ts @@ -1,19 +1,19 @@ +/* eslint-disable @typescript-eslint/no-namespace */ /** * @version 0.20 */ -import { BlocksoftBlockchainTypes } from '../../BlocksoftBlockchainTypes'; +import { AirDAOBlockchainTypes } from '../../AirDAOBlockchainTypes'; import BlocksoftBN from '../../../common/BlocksoftBN'; import BlocksoftUtils from '../../../common/BlocksoftUtils'; import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; -import MarketingEvent from '../../../../app/services/Marketing/MarketingEvent'; export namespace DogeLogs { export const logInputsOutputs = function ( - data: BlocksoftBlockchainTypes.TransferData, - unspents: BlocksoftBlockchainTypes.UnspentTx[], + data: AirDAOBlockchainTypes.TransferData, + unspents: AirDAOBlockchainTypes.UnspentTx[], preparedInputsOutputs: { - inputs: BlocksoftBlockchainTypes.UnspentTx[]; - outputs: BlocksoftBlockchainTypes.OutputTx[]; + inputs: AirDAOBlockchainTypes.UnspentTx[]; + outputs: AirDAOBlockchainTypes.OutputTx[]; multiAddress: []; msg: string; }, @@ -99,16 +99,6 @@ export namespace DogeLogs { ); } // console.log('btc_info ' + this._settings.currencyCode + ' ' + data.addressFrom + ' => ' + data.addressTo, logInputsOutputs) - // noinspection JSIgnoredPromiseFromCall - MarketingEvent.logOnlyRealTime( - 'v20_doge_info ' + - settings.currencyCode + - ' ' + - data.addressFrom + - ' => ' + - data.addressTo, - logInputsOutputs - ); return logInputsOutputs; }; } diff --git a/crypto/blockchains/doge/basic/DogeNetworkPrices.ts b/crypto/blockchains/doge/basic/DogeNetworkPrices.ts index 56689fc15..923b48178 100644 --- a/crypto/blockchains/doge/basic/DogeNetworkPrices.ts +++ b/crypto/blockchains/doge/basic/DogeNetworkPrices.ts @@ -1,12 +1,12 @@ /** * @version 0.20 **/ -import { BlocksoftBlockchainTypes } from '../../BlocksoftBlockchainTypes'; +import { AirDAOBlockchainTypes } from '../../AirDAOBlockchainTypes'; import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; import BlocksoftExternalSettings from '../../../common/BlocksoftExternalSettings'; export default class DogeNetworkPrices - implements BlocksoftBlockchainTypes.NetworkPrices + implements AirDAOBlockchainTypes.NetworkPrices { async getNetworkPrices(currencyCode: string): Promise<{ speed_blocks_2: number; diff --git a/crypto/blockchains/doge/providers/DogeSendProvider.ts b/crypto/blockchains/doge/providers/DogeSendProvider.ts index 576caa97d..6d972dfab 100644 --- a/crypto/blockchains/doge/providers/DogeSendProvider.ts +++ b/crypto/blockchains/doge/providers/DogeSendProvider.ts @@ -2,192 +2,34 @@ * @version 0.20 * https://github.com/trezor/blockbook/blob/master/docs/api.md */ -import { BlocksoftBlockchainTypes } from '../../BlocksoftBlockchainTypes'; +import { AirDAOBlockchainTypes } from '../../AirDAOBlockchainTypes'; import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; import BlocksoftAxios from '../../../common/BlocksoftAxios'; import BlocksoftExternalSettings from '../../../common/BlocksoftExternalSettings'; -import config from '../../../../app/config/config'; -import MarketingEvent from '../../../../app/services/Marketing/MarketingEvent'; export default class DogeSendProvider - implements BlocksoftBlockchainTypes.SendProvider + implements AirDAOBlockchainTypes.SendProvider { - protected _trezorServerCode: string = ''; + protected _trezorServerCode = ''; - private _trezorServer: string = ''; + private _trezorServer = ''; - protected _settings: BlocksoftBlockchainTypes.CurrencySettings; - - private _proxy: string; - - private _errorProxy: string; - - private _successProxy: string; + protected _settings: AirDAOBlockchainTypes.CurrencySettings; constructor( - settings: BlocksoftBlockchainTypes.CurrencySettings, + settings: AirDAOBlockchainTypes.CurrencySettings, serverCode: string ) { this._settings = settings; this._trezorServerCode = serverCode; - - const { apiEndpoints } = config.proxy; - const baseURL = MarketingEvent.DATA.LOG_TESTER - ? apiEndpoints.baseURLTest - : apiEndpoints.baseURL; - this._proxy = baseURL + '/send/checktx'; - this._errorProxy = baseURL + '/send/errortx'; - this._successProxy = baseURL + '/send/sendtx'; } async _check(hex: string, subtitle: string, txRBF: any, logData: any) { - let checkResult = false; - try { - BlocksoftCryptoLog.log( - this._settings.currencyCode + - ' DogeSendProvider.sendTx ' + - subtitle + - ' proxy checkResult start ' + - this._proxy, - logData - ); - if (config.debug.cryptoErrors) { - console.log( - new Date().toISOString() + - ' ' + - this._settings.currencyCode + - ' DogeSendProvider.sendTx ' + - subtitle + - ' proxy checkResult start ' + - this._proxy - ); - } - checkResult = await BlocksoftAxios.post(this._proxy, { - raw: hex, - txRBF, - logData, - marketingData: MarketingEvent.DATA - }); - if (config.debug.cryptoErrors) { - console.log( - new Date().toISOString() + - ' ' + - this._settings.currencyCode + - ' DogeSendProvider.sendTx ' + - subtitle + - ' proxy checkResult end ' + - this._proxy - ); - } - } catch (e) { - if (config.debug.cryptoErrors) { - console.log( - this._settings.currencyCode + - ' DogeSendProvider.send proxy error checkResult ' + - e.message - ); - } - BlocksoftCryptoLog.log( - this._settings.currencyCode + - ' DogeSendProvider.send proxy error checkResult ' + - e.message - ); - } - - if (checkResult !== false) { - if (typeof checkResult.data !== 'undefined') { - BlocksoftCryptoLog.log( - this._settings.currencyCode + - ' DogeSendProvider.send proxy checkResult1 ', - checkResult.data - ); - if ( - typeof checkResult.data.status === 'undefined' || - checkResult.data.status === 'error' - ) { - if (config.debug.cryptoErrors) { - console.log( - this._settings.currencyCode + - ' DogeSendProvider.send proxy error checkResult1 ', - checkResult - ); - } - checkResult = false; - } else if (checkResult.data.status === 'notice') { - throw new Error(checkResult.data.msg); - } - } else { - BlocksoftCryptoLog.log( - this._settings.currencyCode + - ' DogeSendProvider.send proxy checkResult2 ', - checkResult - ); - if (config.debug.cryptoErrors) { - console.log( - this._settings.currencyCode + - ' DogeSendProvider.send proxy error checkResult2 ', - checkResult - ); - } - } - } else { - if (config.debug.cryptoErrors) { - console.log( - this._settings.currencyCode + - ' DogeSendProvider.send proxy error checkResultEmpty ', - checkResult - ); - } - } - if (typeof logData === 'undefined' || !logData) { - logData = {}; - } - logData.checkResult = - checkResult && typeof checkResult.data !== 'undefined' && checkResult.data - ? JSON.parse(JSON.stringify(checkResult.data)) - : false; - return logData; + return {}; } async _checkError(hex: string, subtitle: string, txRBF: any, logData: any) { - BlocksoftCryptoLog.log( - this._settings.currencyCode + - ' DogeSendProvider.send proxy errorTx start ' + - this._errorProxy, - logData - ); - if (config.debug.cryptoErrors) { - console.log( - new Date().toISOString() + - ' ' + - this._settings.currencyCode + - ' DogeSendProvider.sendTx ' + - subtitle + - ' proxy errorTx start ' + - this._errorProxy - ); - } - const res2 = await BlocksoftAxios.post(this._errorProxy, { - raw: hex, - txRBF, - logData, - marketingData: MarketingEvent.DATA - }); - if (config.debug.cryptoErrors) { - console.log( - new Date().toISOString() + - ' ' + - this._settings.currencyCode + - ' DogeSendProvider.sendTx ' + - subtitle + - ' proxy errorTx result ', - JSON.parse(JSON.stringify(res2.data)) - ); - } - BlocksoftCryptoLog.log( - this._settings.currencyCode + ' DogeSendProvider.send proxy errorTx', - typeof res2.data !== 'undefined' ? res2.data : res2 - ); + return {}; } async _checkSuccess( @@ -197,109 +39,7 @@ export default class DogeSendProvider txRBF: any, logData: any ) { - let checkResult = false; - try { - logData.txHash = transactionHash; - BlocksoftCryptoLog.log( - this._settings.currencyCode + - ' DogeSendProvider.send proxy successTx start ' + - this._successProxy, - logData - ); - if (config.debug.cryptoErrors) { - console.log( - new Date().toISOString() + - ' ' + - this._settings.currencyCode + - ' DogeSendProvider.sendTx ' + - subtitle + - ' proxy successTx start ' + - this._successProxy - ); - } - checkResult = await BlocksoftAxios.post(this._successProxy, { - raw: hex, - txRBF, - logData, - marketingData: MarketingEvent.DATA - }); - if (config.debug.cryptoErrors) { - console.log( - new Date().toISOString() + - ' ' + - this._settings.currencyCode + - ' DogeSendProvider.sendTx ' + - subtitle + - ' proxy successTx result ', - JSON.parse(JSON.stringify(checkResult.data)) - ); - } - } catch (e3) { - if (config.debug.cryptoErrors) { - console.log( - this._settings.currencyCode + - ' DogeSendProvider.send proxy error successTx ' + - e3.message - ); - } - BlocksoftCryptoLog.log( - this._settings.currencyCode + - ' DogeSendProvider.send proxy error successTx ' + - e3.message - ); - } - - if (checkResult !== false) { - if (typeof checkResult.data !== 'undefined') { - BlocksoftCryptoLog.log( - this._settings.currencyCode + - ' DogeSendProvider.send proxy successResult1 ', - checkResult.data - ); - if ( - typeof checkResult.data.status === 'undefined' || - checkResult.data.status === 'error' - ) { - if (config.debug.cryptoErrors) { - console.log( - this._settings.currencyCode + - ' DogeSendProvider.send proxy error successResult1 ', - checkResult - ); - } - checkResult = false; - } else if (checkResult.data.status === 'notice') { - throw new Error(checkResult.data.msg); - } - } else { - BlocksoftCryptoLog.log( - this._settings.currencyCode + - ' DogeSendProvider.send proxy successResult2 ', - checkResult - ); - if (config.debug.cryptoErrors) { - console.log( - this._settings.currencyCode + - ' DogeSendProvider.send proxy error successResult2 ', - checkResult - ); - } - } - } else { - if (config.debug.cryptoErrors) { - console.log( - this._settings.currencyCode + - ' DogeSendProvider.send proxy error successResultEmpty ', - checkResult - ); - } - } - logData.successResult = - checkResult && typeof checkResult.data !== 'undefined' && checkResult.data - ? JSON.parse(JSON.stringify(checkResult.data)) - : false; - logData.txRBF = txRBF; - return logData; + return {}; } async sendTx( @@ -333,12 +73,6 @@ export default class DogeSendProvider try { res = await BlocksoftAxios.post(link, hex); } catch (e) { - if (config.debug.cryptoErrors) { - console.log( - this._settings.currencyCode + ' DogeSendProvider.sendTx error ', - e - ); - } if (subtitle.indexOf('rawSend') !== -1) { throw e; } @@ -346,13 +80,6 @@ export default class DogeSendProvider logData.error = e.message; await this._checkError(hex, subtitle, txRBF, logData); } catch (e2) { - if (config.debug.cryptoErrors) { - console.log( - this._settings.currencyCode + - ' DogeSendProvider.send proxy error errorTx ' + - e.message - ); - } BlocksoftCryptoLog.log( this._settings.currencyCode + ' DogeSendProvider.send proxy error errorTx ' + @@ -402,12 +129,6 @@ export default class DogeSendProvider } } if (typeof res.data.result === 'undefined' || !res.data.result) { - if (config.debug.cryptoErrors) { - console.log( - this._settings.currencyCode + 'DogeSendProvider.send no txid', - res.data - ); - } throw new Error('SERVER_RESPONSE_NOT_CONNECTED'); } diff --git a/crypto/blockchains/doge/providers/DogeUnspentsProvider.ts b/crypto/blockchains/doge/providers/DogeUnspentsProvider.ts index bb84d07ce..39fe8c709 100644 --- a/crypto/blockchains/doge/providers/DogeUnspentsProvider.ts +++ b/crypto/blockchains/doge/providers/DogeUnspentsProvider.ts @@ -3,23 +3,23 @@ * https://github.com/trezor/blockbook/blob/master/docs/api.md * https://doge1.trezor.io/api/v2/utxo/D5oKvWEibVe74CXLASmhpkRpLoyjgZhm71 */ -import { BlocksoftBlockchainTypes } from '../../BlocksoftBlockchainTypes'; +import { AirDAOBlockchainTypes } from '../../AirDAOBlockchainTypes'; import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; import BlocksoftAxios from '../../../common/BlocksoftAxios'; import BlocksoftExternalSettings from '../../../common/BlocksoftExternalSettings'; import DogeRawDS from '../stores/DogeRawDS'; export default class DogeUnspentsProvider - implements BlocksoftBlockchainTypes.UnspentsProvider + implements AirDAOBlockchainTypes.UnspentsProvider { - private _trezorServerCode: string = ''; + private _trezorServerCode = ''; - private _trezorServer: string = ''; + private _trezorServer = ''; - protected _settings: BlocksoftBlockchainTypes.CurrencySettings; + protected _settings: AirDAOBlockchainTypes.CurrencySettings; constructor( - settings: BlocksoftBlockchainTypes.CurrencySettings, + settings: AirDAOBlockchainTypes.CurrencySettings, serverCode: string ) { this._settings = settings; @@ -28,7 +28,7 @@ export default class DogeUnspentsProvider async getUnspents( address: string - ): Promise { + ): Promise { // @ts-ignore BlocksoftCryptoLog.log( this._settings.currencyCode + @@ -89,9 +89,9 @@ export default class DogeUnspentsProvider async getTx( tx: string, address: string, - allUnspents: BlocksoftBlockchainTypes.UnspentTx[], + allUnspents: AirDAOBlockchainTypes.UnspentTx[], walletHash: string - ): Promise { + ): Promise { BlocksoftCryptoLog.log( this._settings.currencyCode + ' DogeUnspentsProvider.getTx started ' + tx ); diff --git a/crypto/blockchains/doge/stores/DogeRawDS.js b/crypto/blockchains/doge/stores/DogeRawDS.ts similarity index 88% rename from crypto/blockchains/doge/stores/DogeRawDS.js rename to crypto/blockchains/doge/stores/DogeRawDS.ts index d113097bd..9eeb03511 100644 --- a/crypto/blockchains/doge/stores/DogeRawDS.js +++ b/crypto/blockchains/doge/stores/DogeRawDS.ts @@ -1,11 +1,11 @@ -import Database from '@app/appstores/DataSource/Database'; -import BlocksoftExternalSettings from '../../../common/BlocksoftExternalSettings'; -import BlocksoftAxios from '../../../common/BlocksoftAxios'; import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; import { BlocksoftTransfer } from '../../../actions/BlocksoftTransfer/BlocksoftTransfer'; -import config from '../../../../app/config/config'; +import { Database } from '@database'; +import { DatabaseTable } from '@appTypes'; +import { Q } from '@nozbe/watermelondb'; +import { TransactionRawDBModel } from '@database/models/transactions-raw'; -const tableName = 'transactions_raw'; +const tableName = DatabaseTable.TransactionRaw; class DogeRawDS { _trezorServer = 'none'; @@ -154,15 +154,15 @@ class DogeRawDS { data.address.toLowerCase() + '_' + data.transactionHash; } BlocksoftCryptoLog.log('DogeRawDS cleanRaw ', data); - const now = new Date().toISOString(); - const sql = `UPDATE transactions_raw + const now = new Date().getTime(); + const sql = `UPDATE ${tableName} SET is_removed=1, removed_at = '${now}' WHERE (is_removed=0 OR is_removed IS NULL) AND currency_code='${data.currencyCode}' AND address='${data.address.toLowerCase()}' AND transaction_unique_key='${data.transactionUnique}'`; - await Database.query(sql); + await Database.unsafeRawQuery(tableName, sql); } async saveRaw(data) { @@ -170,16 +170,16 @@ class DogeRawDS { data.transactionUnique = data.address.toLowerCase() + '_' + data.transactionHash; } - const now = new Date().toISOString(); + const now = new Date().getTime(); - const sql = `UPDATE transactions_raw + const sql = `UPDATE ${tableName} SET is_removed=1, removed_at = '${now}' WHERE (is_removed=0 OR is_removed IS NULL) AND currency_code='${data.currencyCode}' AND address='${data.address.toLowerCase()}' AND transaction_unique_key='${data.transactionUnique}'`; - await Database.query(sql); + await Database.unsafeRawQuery(tableName, sql); const prepared = [ { @@ -196,13 +196,11 @@ class DogeRawDS { JSON.stringify(data.transactionLog) ); } - await Database.setTableName(tableName) - .setInsertData({ insertObjs: prepared }) - .insert(); + await Database.createModel(tableName, prepared); } async savePrefixed(data, prefix) { - const now = new Date().toISOString(); + const now = new Date().getTime(); const prepared = [ { @@ -220,9 +218,7 @@ class DogeRawDS { JSON.stringify(data.transactionLog) ); } - await Database.setTableName(tableName) - .setInsertData({ insertObjs: prepared }) - .insert(); + await Database.createModel(tableName, prepared); } async getPrefixed(data, prefix) { @@ -230,17 +226,19 @@ class DogeRawDS { FROM ${tableName} WHERE currency_code='${data.currencyCode}' AND transaction_unique_key='${prefix}_${data.transactionHash}' LIMIT 1`; - const res = await Database.query(sql); + const res = (await Database.unsafeRawQuery( + tableName, + sql + )) as TransactionRawDBModel[]; if ( !res || - !res.array || - typeof res.array[0] === 'undefined' || - typeof res.array[0].transactionRaw === 'undefined' + typeof res[0] === 'undefined' || + typeof res[0].transactionRaw === 'undefined' ) { return false; } try { - const str = Database.unEscapeString(res.array[0].transactionRaw); + const str = Database.unEscapeString(res[0].transactionRaw) || ''; return JSON.parse(str); } catch (e) { BlocksoftCryptoLog.err('DogeRawDS getInputs error ' + e.message); diff --git a/crypto/blockchains/doge/tx/DogeTxBuilder.ts b/crypto/blockchains/doge/tx/DogeTxBuilder.ts index c2b7faf98..f9e927bac 100644 --- a/crypto/blockchains/doge/tx/DogeTxBuilder.ts +++ b/crypto/blockchains/doge/tx/DogeTxBuilder.ts @@ -1,31 +1,27 @@ /** * @version 0.20 */ -import { BlocksoftBlockchainTypes } from '../../BlocksoftBlockchainTypes'; +import { AirDAOBlockchainTypes } from '../../AirDAOBlockchainTypes'; import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; -import BlocksoftUtils from '../../../common/BlocksoftUtils'; import { TransactionBuilder, ECPair, payments } from 'bitcoinjs-lib'; import BlocksoftExternalSettings from '../../../common/BlocksoftExternalSettings'; -import config from '../../../../app/config/config'; -const networksConstants = require('../../../common/ext/networks-constants'); +import networksConstants from '../../../common/ext/networks-constants'; const MAX_SEQ = 4294967294; // 0xfffffffe // no replace by fee const MIN_SEQ = 4294960000; // for RBF -export default class DogeTxBuilder - implements BlocksoftBlockchainTypes.TxBuilder -{ - protected _settings: BlocksoftBlockchainTypes.CurrencySettings; - private _builderSettings: BlocksoftBlockchainTypes.BuilderSettings; +export default class DogeTxBuilder implements AirDAOBlockchainTypes.TxBuilder { + protected _settings: AirDAOBlockchainTypes.CurrencySettings; + private _builderSettings: AirDAOBlockchainTypes.BuilderSettings; protected _bitcoinNetwork: any; private _feeMaxForByteSatoshi: number | any; protected keyPair: any; constructor( - settings: BlocksoftBlockchainTypes.CurrencySettings, - builderSettings: BlocksoftBlockchainTypes.BuilderSettings + settings: AirDAOBlockchainTypes.CurrencySettings, + builderSettings: AirDAOBlockchainTypes.BuilderSettings ) { this._settings = settings; this._builderSettings = builderSettings; @@ -52,8 +48,8 @@ export default class DogeTxBuilder } _getRawTxValidateKeyPair( - privateData: BlocksoftBlockchainTypes.TransferPrivateData, - data: BlocksoftBlockchainTypes.TransferData + privateData: AirDAOBlockchainTypes.TransferPrivateData, + data: AirDAOBlockchainTypes.TransferData ): void { if (typeof privateData.privateKey === 'undefined') { throw new Error('DogeTxBuilder.getRawTx requires privateKey'); @@ -85,7 +81,7 @@ export default class DogeTxBuilder async _getRawTxAddInput( txb: TransactionBuilder, i: number, - input: BlocksoftBlockchainTypes.UnspentTx, + input: AirDAOBlockchainTypes.UnspentTx, nSequence: number ): Promise { if (typeof input.vout === 'undefined') { @@ -100,7 +96,7 @@ export default class DogeTxBuilder async _getRawTxSign( txb: TransactionBuilder, i: number, - input: BlocksoftBlockchainTypes.UnspentTx + input: AirDAOBlockchainTypes.UnspentTx ): Promise { await BlocksoftCryptoLog.log('DogeTxBuilder.getRawTx sign', input); // @ts-ignore @@ -109,7 +105,7 @@ export default class DogeTxBuilder _getRawTxAddOutput( txb: TransactionBuilder, - output: BlocksoftBlockchainTypes.OutputTx + output: AirDAOBlockchainTypes.OutputTx ): void { // @ts-ignore const amount = Math.round(output.amount * 1); @@ -121,14 +117,14 @@ export default class DogeTxBuilder } async getRawTx( - data: BlocksoftBlockchainTypes.TransferData, - privateData: BlocksoftBlockchainTypes.TransferPrivateData, - preparedInputsOutputs: BlocksoftBlockchainTypes.PreparedInputsOutputsTx + data: AirDAOBlockchainTypes.TransferData, + privateData: AirDAOBlockchainTypes.TransferPrivateData, + preparedInputsOutputs: AirDAOBlockchainTypes.PreparedInputsOutputsTx ): Promise<{ rawTxHex: string; nSequence: number; txAllowReplaceByFee: boolean; - preparedInputsOutputs: BlocksoftBlockchainTypes.PreparedInputsOutputsTx; + preparedInputsOutputs: AirDAOBlockchainTypes.PreparedInputsOutputsTx; }> { await this._reInit(); @@ -212,14 +208,6 @@ export default class DogeTxBuilder input ); } catch (e) { - if (config.debug.cryptoErrors) { - console.log( - this._settings.currencyCode + - ' DogeTxBuilder.getRawTx input add error ', - e, - JSON.parse(JSON.stringify(input)) - ); - } await BlocksoftCryptoLog.log( this._settings.currencyCode + ' DogeTxBuilder.getRawTx input add error ', @@ -243,14 +231,6 @@ export default class DogeTxBuilder output ); } catch (e) { - if (config.debug.cryptoErrors) { - console.log( - this._settings.currencyCode + - ' DogeTxBuilder.getRawTx output add error ', - e, - JSON.parse(JSON.stringify(output)) - ); - } await BlocksoftCryptoLog.log( this._settings.currencyCode + ' DogeTxBuilder.getRawTx output add error ', @@ -271,23 +251,6 @@ export default class DogeTxBuilder this._settings.currencyCode + ' DogeTxBuilder.getRawTx sign added' ); } catch (e) { - if (config.debug.cryptoErrors) { - if (e.message.indexOf('Transaction needs outputs') !== -1) { - console.log( - this._settings.currencyCode + - ' DogeTxBuilder.getRawTx input sign error ' + - e.message, - JSON.parse(JSON.stringify(preparedInputsOutputs)) - ); - } else { - console.log( - this._settings.currencyCode + - ' DogeTxBuilder.getRawTx input sign error ', - e, - JSON.parse(JSON.stringify(input)) - ); - } - } await BlocksoftCryptoLog.log( this._settings.currencyCode + ' DogeTxBuilder.getRawTx input sign error ', diff --git a/crypto/blockchains/doge/tx/DogeTxInputsOutputs.ts b/crypto/blockchains/doge/tx/DogeTxInputsOutputs.ts index 49927cf22..45a064a30 100644 --- a/crypto/blockchains/doge/tx/DogeTxInputsOutputs.ts +++ b/crypto/blockchains/doge/tx/DogeTxInputsOutputs.ts @@ -1,20 +1,22 @@ /** * @version 0.20 */ -import { BlocksoftBlockchainTypes } from '../../BlocksoftBlockchainTypes'; +import { AirDAOBlockchainTypes } from '../../AirDAOBlockchainTypes'; import BlocksoftBN from '../../../common/BlocksoftBN'; import BlocksoftUtils from '../../../common/BlocksoftUtils'; import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; import BlocksoftDict from '@crypto/common/BlocksoftDict'; -const coinSelect = require('coinselect'); -const coinSplit = require('coinselect/split'); +// @ts-ignore +import coinSelect from 'coinselect'; +// @ts-ignore +import coinSplit from 'coinselect/split'; export default class DogeTxInputsOutputs - implements BlocksoftBlockchainTypes.TxInputsOutputs + implements AirDAOBlockchainTypes.TxInputsOutputs { - private _builderSettings: BlocksoftBlockchainTypes.BuilderSettings; - protected _settings: BlocksoftBlockchainTypes.CurrencySettings; + private _builderSettings: AirDAOBlockchainTypes.BuilderSettings; + protected _settings: AirDAOBlockchainTypes.CurrencySettings; private _minOutputDust: any; private _minChangeDust: any; @@ -24,8 +26,8 @@ export default class DogeTxInputsOutputs SIZE_FOR_BC = 75; constructor( - settings: BlocksoftBlockchainTypes.CurrencySettings, - builderSettings: BlocksoftBlockchainTypes.BuilderSettings + settings: AirDAOBlockchainTypes.CurrencySettings, + builderSettings: AirDAOBlockchainTypes.BuilderSettings ) { this._settings = settings; this._builderSettings = builderSettings; @@ -40,8 +42,8 @@ export default class DogeTxInputsOutputs } _coinSelectTargets( - data: BlocksoftBlockchainTypes.TransferData, - unspents: BlocksoftBlockchainTypes.UnspentTx[], + data: AirDAOBlockchainTypes.TransferData, + unspents: AirDAOBlockchainTypes.UnspentTx[], feeForByte: string, multiAddress: string[], subtitle: string @@ -86,13 +88,13 @@ export default class DogeTxInputsOutputs return targets; } - _addressForChange(data: BlocksoftBlockchainTypes.TransferData): string { + _addressForChange(data: AirDAOBlockchainTypes.TransferData): string { return data.addressFrom; } _usualTargets( - data: BlocksoftBlockchainTypes.TransferData, - unspents: BlocksoftBlockchainTypes.UnspentTx[] + data: AirDAOBlockchainTypes.TransferData, + unspents: AirDAOBlockchainTypes.UnspentTx[] ) { const multiAddress = []; const basicWishedAmountBN = new BlocksoftBN(data.amount); @@ -130,15 +132,15 @@ export default class DogeTxInputsOutputs } async _coinSelect( - data: BlocksoftBlockchainTypes.TransferData, - unspents: BlocksoftBlockchainTypes.UnspentTx[], + data: AirDAOBlockchainTypes.TransferData, + unspents: AirDAOBlockchainTypes.UnspentTx[], feeForByte: string, multiAddress: string[], subtitle: string - ): Promise { + ): Promise { const utxos = []; const isRequired: any = {}; - let isAllRequired: boolean = true; + let isAllRequired = true; const segwitPrefix = typeof BlocksoftDict.CurrenciesForTests[ this._settings.currencyCode + '_SEGWIT' @@ -317,16 +319,16 @@ export default class DogeTxInputsOutputs } async getInputsOutputs( - data: BlocksoftBlockchainTypes.TransferData, - unspents: BlocksoftBlockchainTypes.UnspentTx[], + data: AirDAOBlockchainTypes.TransferData, + unspents: AirDAOBlockchainTypes.UnspentTx[], feeToCount: { feeForByte?: string; feeForAll?: string; autoFeeLimitReadable?: string | number; }, - additionalData: BlocksoftBlockchainTypes.TransferAdditionalData, - subtitle: string = 'default' - ): Promise { + additionalData: AirDAOBlockchainTypes.TransferAdditionalData, + subtitle = 'default' + ): Promise { return this._getInputsOutputs( data, unspents, @@ -337,16 +339,16 @@ export default class DogeTxInputsOutputs } async _getInputsOutputs( - data: BlocksoftBlockchainTypes.TransferData, - unspents: BlocksoftBlockchainTypes.UnspentTx[], + data: AirDAOBlockchainTypes.TransferData, + unspents: AirDAOBlockchainTypes.UnspentTx[], feeToCount: { feeForByte?: string; feeForAll?: string; autoFeeLimitReadable?: string | number; }, - additionalData: BlocksoftBlockchainTypes.TransferAdditionalData, - subtitle: string = 'default' - ): Promise { + additionalData: AirDAOBlockchainTypes.TransferAdditionalData, + subtitle = 'default' + ): Promise { if (typeof data.addressFrom === 'undefined') { throw new Error( 'DogeTxInputsOutputs.getInputsOutputs requires addressFrom' @@ -419,6 +421,7 @@ export default class DogeTxInputsOutputs totalBalanceBN.add(unspent.value); } + // eslint-disable-next-line prefer-const let { multiAddress, wishedAmountBN, outputs } = this._usualTargets( data, unspents @@ -687,7 +690,7 @@ export default class DogeTxInputsOutputs to: addressForChange, amount: leftForChangeDiff.toString(), isChange: true - } as BlocksoftBlockchainTypes.OutputTx); + } as AirDAOBlockchainTypes.OutputTx); } return { inputs, diff --git a/crypto/common/ext/networks-constants.js b/crypto/common/ext/networks-constants.js index 58860f9cf..1b7291b87 100644 --- a/crypto/common/ext/networks-constants.js +++ b/crypto/common/ext/networks-constants.js @@ -1,59 +1,58 @@ // https://github.com/iancoleman/bip39/blob/0a23f51792722f094328d695242556c4c0195a8b/src/js/bitcoinjs-extensions.js -const bitcoin = require('bitcoinjs-lib') +import bitcoin from 'bitcoinjs-lib'; -module.exports = { - 'mainnet': { - network: bitcoin.networks.bitcoin, - langPrefix: 'btc' +export default { + mainnet: { + network: bitcoin.networks.bitcoin, + langPrefix: 'btc' + }, + testnet: { + network: bitcoin.networks.testnet, + langPrefix: 'btc' + }, + litecoin: { + network: { + bech32: 'ltc', + messagePrefix: '\x19Litecoin Signed Message:\n', + pubKeyHash: 0x30, // change to 0x6f + scriptHash: 0x32, + wif: 0xb0, + bip32: { + public: 0x019da462, + private: 0x019d9cfe + } }, - 'testnet': { - network: bitcoin.networks.testnet, - langPrefix: 'btc' + langPrefix: 'ltc' + }, + dogecoin: { + network: { + messagePrefix: '\x19Dogecoin Signed Message:\n', + bip32: { + public: 0x02facafd, + private: 0x02fac398 + }, + pubKeyHash: 0x1e, + scriptHash: 0x16, + wif: 0x9e }, - 'litecoin': { - network: { - bech32: 'ltc', - messagePrefix: '\x19Litecoin Signed Message:\n', - pubKeyHash: 0x30, // change to 0x6f - scriptHash: 0x32, - wif: 0xb0, - bip32: { - public: 0x019da462, - private: 0x019d9cfe - } - }, - langPrefix: 'ltc' - }, - 'dogecoin': { - network: { - messagePrefix: '\x19Dogecoin Signed Message:\n', - bip32: { - public: 0x02facafd, - private: 0x02fac398 - }, - pubKeyHash: 0x1e, - scriptHash: 0x16, - wif: 0x9e - }, - langPrefix: 'ltc' + langPrefix: 'ltc' + }, + verge: { + network: { + messagePrefix: '\x18VERGE Signed Message:\n', + bip32: { + public: 0x0488b21e, + private: 0x0488ade4 + }, + pubKeyHash: 0x1e, + scriptHash: 0x21, + wif: 0x9e }, - 'verge': { - network: { - messagePrefix: '\x18VERGE Signed Message:\n', - bip32: { - public: 0x0488b21e, - private: 0x0488ade4 - }, - pubKeyHash: 0x1e, - scriptHash: 0x21, - wif: 0x9e - }, - langPrefix: 'ltc' - }, - 'bitcoincash': { - network: { - - /* messagePrefix: 'unused', + langPrefix: 'ltc' + }, + bitcoincash: { + network: { + /* messagePrefix: 'unused', bip32: { public: 0x0488b21e, private: 0x0488ade4 @@ -62,43 +61,43 @@ module.exports = { scriptHash: 0x05, wif: 0x80 */ - messagePrefix: '\u0018Bitcoin Signed Message:\n', - bech32: 'bc', - bip32: { public: 76067358, private: 76066276 }, - pubKeyHash: 0, - scriptHash: 5, - wif: 128, - BTCFork : 'BCH' - }, - langPrefix: 'bch' + messagePrefix: '\u0018Bitcoin Signed Message:\n', + bech32: 'bc', + bip32: { public: 76067358, private: 76066276 }, + pubKeyHash: 0, + scriptHash: 5, + wif: 128, + BTCFork: 'BCH' }, - 'bitcoinsv': { - network: { - messagePrefix: 'unused', - bip32: { - public: 0x0488b21e, - private: 0x0488ade4 - }, - pubKeyHash: 0x00, - scriptHash: 0x05, - wif: 0x80, - BTCFork : 'BCH' - }, - langPrefix: 'bch' + langPrefix: 'bch' + }, + bitcoinsv: { + network: { + messagePrefix: 'unused', + bip32: { + public: 0x0488b21e, + private: 0x0488ade4 + }, + pubKeyHash: 0x00, + scriptHash: 0x05, + wif: 0x80, + BTCFork: 'BCH' }, - 'bitcoingold': { - network: { - bech32: 'btg', - messagePrefix: '\x1DBitcoin Gold Signed Message:\n', - bip32: { - public: 0x0488b21e, - private: 0x0488ade4 - }, - pubKeyHash: 38, - scriptHash: 23, - wif: 128, - BTCFork : 'BTG' - }, - langPrefix: 'ltc' - } -} + langPrefix: 'bch' + }, + bitcoingold: { + network: { + bech32: 'btg', + messagePrefix: '\x1DBitcoin Gold Signed Message:\n', + bip32: { + public: 0x0488b21e, + private: 0x0488ade4 + }, + pubKeyHash: 38, + scriptHash: 23, + wif: 128, + BTCFork: 'BTG' + }, + langPrefix: 'ltc' + } +}; diff --git a/package.json b/package.json index 9ba4f6244..cff185e28 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,7 @@ "bn.js": "^5.2.1", "bs58check": "^3.0.1", "buffer": "^6.0.3", + "coinselect": "^3.1.13", "create-hash": "^1.2.0", "elliptic": "^6.5.4", "expo": "~48.0.18", diff --git a/src/appTypes/blockchain/Transaction.ts b/src/appTypes/blockchain/Transaction.ts new file mode 100644 index 000000000..41d3873ee --- /dev/null +++ b/src/appTypes/blockchain/Transaction.ts @@ -0,0 +1,30 @@ +export interface BlockchainTransaction { + hex: string; + address: string; + vin: { + txid: string; + sequence: string; + n: number; + addresses: string[]; + addr: string; + value: string; + hex: string; + prevout: { + scriptpubkey_address: string; + value: string; + }; + }[]; + vout: { + value: string; + n: number; + spent: string; + hex: string; + addresses: string[]; + scriptPubKey: { addresses: string[] }; + scriptpubkey_address: string; + prevout: { + scriptpubkey_address: string; + value: string; + }; + }[]; +} diff --git a/src/appTypes/blockchain/index.ts b/src/appTypes/blockchain/index.ts new file mode 100644 index 000000000..bacbf811e --- /dev/null +++ b/src/appTypes/blockchain/index.ts @@ -0,0 +1 @@ +export * from './Transaction'; diff --git a/src/appTypes/database/DatabaseTable.ts b/src/appTypes/database/DatabaseTable.ts index 4e670ece6..c6fcaf417 100644 --- a/src/appTypes/database/DatabaseTable.ts +++ b/src/appTypes/database/DatabaseTable.ts @@ -1,6 +1,7 @@ export enum DatabaseTable { Accounts = 'accounts', TransactionScannersTmp = 'transactions_scanners_tmp', + TransactionRaw = 'transactions_raw', Wallets = 'wallets', WalletPub = 'wallet-pub' } diff --git a/src/appTypes/index.ts b/src/appTypes/index.ts index c1015173a..5069ee867 100644 --- a/src/appTypes/index.ts +++ b/src/appTypes/index.ts @@ -1,3 +1,4 @@ +export * from './blockchain'; export * from './CacheableAccount'; export * from './CacheableAccountList'; export * from './coinmarketcap'; diff --git a/src/database/main.ts b/src/database/main.ts index b8922a884..8fa27f3bb 100644 --- a/src/database/main.ts +++ b/src/database/main.ts @@ -6,6 +6,7 @@ import { schema } from './schemas'; import { TransactionScannersTmpDBModel } from './models/transaction-scanners-tmp'; import { WalletPubDBModel } from './models/wallet-pub'; import { AccountDBModel } from './models/account'; +import { TransactionRawDBModel } from './models/transactions-raw'; const adapter = new SQLiteAdapter({ schema, @@ -19,6 +20,7 @@ export const database = new Database({ adapter, modelClasses: [ AccountDBModel, + TransactionRawDBModel, TransactionScannersTmpDBModel, WalletDBModel, WalletPubDBModel diff --git a/src/database/models/transaction-scanners-tmp.ts b/src/database/models/transaction-scanners-tmp.ts index 8b07454ac..f34129b9d 100644 --- a/src/database/models/transaction-scanners-tmp.ts +++ b/src/database/models/transaction-scanners-tmp.ts @@ -8,7 +8,7 @@ export class TransactionScannersTmpDBModel extends Model { // define fields // @ts-ignore - @text('currency_code') name: string; + @text('currency_code') currency_code: string; // @ts-ignore @text('address') address: string; // @ts-ignore diff --git a/src/database/models/transactions-raw.ts b/src/database/models/transactions-raw.ts new file mode 100644 index 000000000..5e80b0ec9 --- /dev/null +++ b/src/database/models/transactions-raw.ts @@ -0,0 +1,34 @@ +/* eslint-disable camelcase */ +import { DatabaseTable } from '@appTypes'; +import { Model } from '@nozbe/watermelondb'; +import { text, field } from '@nozbe/watermelondb/decorators'; + +export class TransactionRawDBModel extends Model { + static table = DatabaseTable.TransactionRaw; + + // define fields + // @ts-ignore + @text('currency_code') currency_code: string; + // @ts-ignore + @text('address') address: string; + // @ts-ignore + @text('transaction_unique_key') transactionUniqueKey: string; + // @ts-ignore + @text('transaction_hash') transactionHash: string; + // @ts-ignore + @text('transaction_raw') transactionRaw: string; + // @ts-ignore + @text('transaction_log') transactionLog: string; + // @ts-ignore + @text('broadcast_log') broadcastLog: string; + // @ts-ignore + @field('created_at') createdAt: number; + // @ts-ignore + @field('updated_at') updatedAt: number; + // @ts-ignore + @field('broadcast_updated') broadcastUpdated: number; + // @ts-ignore + @field('removed_at') removedAt: number; + // @ts-ignore + @field('is_removed') isRemoved: number; +} diff --git a/src/database/schemas/index.ts b/src/database/schemas/index.ts index 8e3bda3dd..435d9dd83 100644 --- a/src/database/schemas/index.ts +++ b/src/database/schemas/index.ts @@ -3,11 +3,13 @@ import { WalletTable } from './wallet'; import { TransactionScannersTmpTable } from './transaction-scanners-tmp'; import { WalletPubTable } from './wallet-pub'; import { AccountsTable } from './account'; +import { TransactionRawTable } from './transaction-raw'; export const schema = appSchema({ version: 1, tables: [ AccountsTable, + TransactionRawTable, TransactionScannersTmpTable, WalletTable, WalletPubTable diff --git a/src/database/schemas/transaction-raw.ts b/src/database/schemas/transaction-raw.ts new file mode 100644 index 000000000..846353e8f --- /dev/null +++ b/src/database/schemas/transaction-raw.ts @@ -0,0 +1,56 @@ +import { DatabaseTable } from '@appTypes'; +import { tableSchema } from '@nozbe/watermelondb'; + +export const TransactionRawTable = tableSchema({ + name: DatabaseTable.TransactionRaw, + columns: [ + { + name: 'currency_code', + type: 'string' + }, + { + name: 'address', + type: 'string' + }, + { + name: 'transaction_unique_key', + type: 'string' + }, + { + name: 'transaction_hash', + type: 'number' + }, + { + name: 'transaction_raw', + type: 'string' + }, + { + name: 'transaction_log', + type: 'string' + }, + { + name: 'broadcast_log', + type: 'string' + }, + { + name: 'created_at', + type: 'number' + }, + { + name: 'updated_at', + type: 'number' + }, + { + name: 'broadcast_updated', + type: 'number' + }, + { + name: 'removed_at', + type: 'number' + }, + { + name: 'is_removed', + type: 'number' + } + ] +}); diff --git a/yarn.lock b/yarn.lock index 8d96915f8..7882e83cd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4441,6 +4441,11 @@ co@^4.6.0: resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" integrity sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ== +coinselect@^3.1.13: + version "3.1.13" + resolved "https://registry.yarnpkg.com/coinselect/-/coinselect-3.1.13.tgz#b88c7f9659ed4891d1f1d0c894105b1c10ef89a1" + integrity sha512-iJOrKH/7N9gX0jRkxgOHuGjvzvoxUMSeylDhH1sHn+CjLjdin5R0Hz2WEBu/jrZV5OrHcm+6DMzxwu9zb5mSZg== + collect-v8-coverage@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz#cc2c8e94fc18bbdffe64d6534570c8a673b27f59" From 2179387b3db178bd68b335db1f801cc0dd62418b Mon Sep 17 00:00:00 2001 From: JavidHaji-zada Date: Thu, 3 Aug 2023 00:50:53 +0400 Subject: [PATCH 023/509] converted etc --- .../blockchains/etc/EtcTransferProcessor.ts | 32 +++++++++++-------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/crypto/blockchains/etc/EtcTransferProcessor.ts b/crypto/blockchains/etc/EtcTransferProcessor.ts index 604fbca5c..ff64a8eb4 100644 --- a/crypto/blockchains/etc/EtcTransferProcessor.ts +++ b/crypto/blockchains/etc/EtcTransferProcessor.ts @@ -1,13 +1,19 @@ -/** - * @author Ksu - * @version 0.43 - */ -import { BlocksoftBlockchainTypes } from '@crypto/blockchains/BlocksoftBlockchainTypes' -import BnbSmartTransferProcessor from '@crypto/blockchains/bnb_smart/BnbSmartTransferProcessor' - -export default class EtcTransferProcessor extends BnbSmartTransferProcessor implements BlocksoftBlockchainTypes.TransferProcessor { - - canRBF(data: BlocksoftBlockchainTypes.DbAccount, transaction: BlocksoftBlockchainTypes.DbTransaction, source: string): boolean { - return false - } -} +/** + * @author Ksu + * @version 0.43 + */ +import { AirDAOBlockchainTypes } from '@crypto/blockchains/AirDAOBlockchainTypes'; +import BnbSmartTransferProcessor from '@crypto/blockchains/bnb_smart/BnbSmartTransferProcessor'; + +export default class EtcTransferProcessor + extends BnbSmartTransferProcessor + implements AirDAOBlockchainTypes.TransferProcessor +{ + canRBF( + data: AirDAOBlockchainTypes.DbAccount, + transaction: AirDAOBlockchainTypes.DbTransaction, + source: string + ): boolean { + return false; + } +} From b05cb772e9f371b681ef9e55ddb554afcd8af1ac Mon Sep 17 00:00:00 2001 From: JavidHaji-zada Date: Thu, 3 Aug 2023 02:13:44 +0400 Subject: [PATCH 024/509] converted eth --- crypto/blockchains/eth/EthAddressProcessor.js | 39 - crypto/blockchains/eth/EthAddressProcessor.ts | 41 + ...nerProcessor.js => EthScannerProcessor.ts} | 24 +- ...orErc20.js => EthScannerProcessorErc20.ts} | 15 +- .../blockchains/eth/EthTokenProcessorErc20.js | 72 -- .../blockchains/eth/EthTokenProcessorErc20.ts | 77 ++ .../blockchains/eth/EthTokenProcessorNft.js | 67 -- .../blockchains/eth/EthTokenProcessorNft.ts | 63 ++ .../blockchains/eth/EthTransferProcessor.ts | 137 ++-- .../eth/EthTransferProcessorErc20.ts | 73 +- .../apis/{EthNftMatic.js => EthNftMatic.ts} | 232 +++--- .../{EthNftOpensea.js => EthNftOpensea.ts} | 336 ++++----- .../eth/basic/{EthBasic.js => EthBasic.ts} | 18 - ...thNetworkPrices.js => EthNetworkPrices.ts} | 81 +- .../eth/basic/EthTxSendProvider.ts | 201 ++--- crypto/blockchains/eth/ext/EthEstimateGas.js | 24 - crypto/blockchains/eth/ext/EthEstimateGas.ts | 24 + crypto/blockchains/eth/ext/erc1155.js | 710 +++++++++++++++--- crypto/blockchains/eth/ext/erc20.js | 466 ++++++------ crypto/blockchains/eth/ext/erc721.js | 350 +++++++-- .../blockchains/eth/ext/estimateGas/enums.js | 36 +- .../blockchains/eth/ext/estimateGas/index.js | 57 +- .../eth/ext/estimateGas/tx-gas-utils.js | 207 +++-- .../blockchains/eth/ext/estimateGas/util.js | 156 ++-- ...ssorSoul.js => EthScannerProcessorSoul.ts} | 7 +- .../eth/stores/{EthRawDS.js => EthRawDS.ts} | 172 ++--- crypto/blockchains/eth/stores/EthTmpDS.js | 194 ----- crypto/blockchains/eth/stores/EthTmpDS.ts | 249 ++++++ package.json | 3 + src/appTypes/database/DatabaseTable.ts | 1 + src/database/Database.ts | 25 + src/database/main.ts | 14 +- src/database/models/index.ts | 4 + .../models/transaction-scanners-tmp.ts | 10 +- src/database/models/transactions.ts | 84 +++ src/database/schemas/index.ts | 2 + src/database/schemas/transactions.ts | 160 ++++ yarn.lock | 148 +++- 38 files changed, 2745 insertions(+), 1834 deletions(-) delete mode 100644 crypto/blockchains/eth/EthAddressProcessor.js create mode 100644 crypto/blockchains/eth/EthAddressProcessor.ts rename crypto/blockchains/eth/{EthScannerProcessor.js => EthScannerProcessor.ts} (98%) rename crypto/blockchains/eth/{EthScannerProcessorErc20.js => EthScannerProcessorErc20.ts} (89%) delete mode 100644 crypto/blockchains/eth/EthTokenProcessorErc20.js create mode 100644 crypto/blockchains/eth/EthTokenProcessorErc20.ts delete mode 100644 crypto/blockchains/eth/EthTokenProcessorNft.js create mode 100644 crypto/blockchains/eth/EthTokenProcessorNft.ts rename crypto/blockchains/eth/apis/{EthNftMatic.js => EthNftMatic.ts} (96%) rename crypto/blockchains/eth/apis/{EthNftOpensea.js => EthNftOpensea.ts} (97%) rename crypto/blockchains/eth/basic/{EthBasic.js => EthBasic.ts} (96%) rename crypto/blockchains/eth/basic/{EthNetworkPrices.js => EthNetworkPrices.ts} (84%) delete mode 100644 crypto/blockchains/eth/ext/EthEstimateGas.js create mode 100644 crypto/blockchains/eth/ext/EthEstimateGas.ts rename crypto/blockchains/eth/forks/{EthScannerProcessorSoul.js => EthScannerProcessorSoul.ts} (94%) rename crypto/blockchains/eth/stores/{EthRawDS.js => EthRawDS.ts} (70%) delete mode 100644 crypto/blockchains/eth/stores/EthTmpDS.js create mode 100644 crypto/blockchains/eth/stores/EthTmpDS.ts create mode 100644 src/database/models/transactions.ts create mode 100644 src/database/schemas/transactions.ts diff --git a/crypto/blockchains/eth/EthAddressProcessor.js b/crypto/blockchains/eth/EthAddressProcessor.js deleted file mode 100644 index 4cf7b25f5..000000000 --- a/crypto/blockchains/eth/EthAddressProcessor.js +++ /dev/null @@ -1,39 +0,0 @@ -/** - * @version 0.5 - */ -import EthBasic from './basic/EthBasic' - -export default class EthAddressProcessor extends EthBasic{ - - async setBasicRoot(root) { - - } - - /** - * @param {string|Buffer} privateKey - * @param {*} data - * @returns {Promise<{privateKey: string, address: string, addedData: *}>} - */ - async getAddress(privateKey, data = {}) { - // noinspection JSCheckFunctionSignatures - privateKey = '0x' + privateKey.toString('hex') - // noinspection JSUnresolvedVariable - const account = this._web3.eth.accounts.privateKeyToAccount(privateKey) - return { address: account.address, privateKey, addedData : false} - } - - /** - * @param {string} msg - * @param {string} privateKey - * @returns {Promise<{message: string, messageHash: string, v: string, r: string,s: string, signature: string}>} - */ - async signMessage(msg, privateKey) { - // noinspection JSUnresolvedVariable - if (privateKey.substr(0, 2) !== '0x') { - privateKey = '0x' + privateKey - } - // noinspection JSUnresolvedVariable - const signData = await this._web3.eth.accounts.sign(msg, privateKey) - return signData - } -} diff --git a/crypto/blockchains/eth/EthAddressProcessor.ts b/crypto/blockchains/eth/EthAddressProcessor.ts new file mode 100644 index 000000000..86196ebbc --- /dev/null +++ b/crypto/blockchains/eth/EthAddressProcessor.ts @@ -0,0 +1,41 @@ +/** + * @version 0.5 + */ +import EthBasic from './basic/EthBasic'; + +export default class EthAddressProcessor extends EthBasic { + async setBasicRoot(root) {} + + /** + * @param {string|Buffer} privateKey + * @param {*} data + * @returns {Promise<{privateKey: string, address: string, addedData: *}>} + */ + async getAddress(privateKey: string | Buffer, data = {}) { + // noinspection JSCheckFunctionSignatures + privateKey = '0x' + privateKey.toString('hex'); + // noinspection JSUnresolvedVariable + const account = this._web3.eth.accounts.privateKeyToAccount(privateKey); + return { address: account.address, privateKey, addedData: false }; + } + + async signMessage( + msg: string, + privateKey: string + ): Promise<{ + message: string; + messageHash: string; + v: string; + r: string; + s: string; + signature: string; + }> { + // noinspection JSUnresolvedVariable + if (privateKey.substr(0, 2) !== '0x') { + privateKey = '0x' + privateKey; + } + // noinspection JSUnresolvedVariable + const signData = await this._web3.eth.accounts.sign(msg, privateKey); + return signData; + } +} diff --git a/crypto/blockchains/eth/EthScannerProcessor.js b/crypto/blockchains/eth/EthScannerProcessor.ts similarity index 98% rename from crypto/blockchains/eth/EthScannerProcessor.js rename to crypto/blockchains/eth/EthScannerProcessor.ts index c16dfd709..b6b616a06 100644 --- a/crypto/blockchains/eth/EthScannerProcessor.js +++ b/crypto/blockchains/eth/EthScannerProcessor.ts @@ -10,8 +10,8 @@ import BlocksoftBN from '../../common/BlocksoftBN'; import EthTmpDS from './stores/EthTmpDS'; import EthRawDS from './stores/EthRawDS'; -import config from '@app/config/config'; import BlocksoftPrettyNumbers from '@crypto/common/BlocksoftPrettyNumbers'; +import { BlockchainTransaction } from '@appTypes'; const CACHE_GET_MAX_BLOCK = { ETH: { max_block_number: 0, confirmations: 0 }, @@ -216,15 +216,6 @@ export default class EthScannerProcessor extends EthBasic { } } } catch (e) { - if (config.debug.cryptoErrors) { - console.log( - this._settings.currencyCode + - ' EthScannerProcessor.getBalance ' + - address + - ' error ' + - e.message - ); - } await BlocksoftCryptoLog.log( this._settings.currencyCode + ' EthScannerProcessor.getBalance ' + @@ -248,17 +239,6 @@ export default class EthScannerProcessor extends EthBasic { time = 'now()'; return { balance, unconfirmed: 0, provider, time }; } catch (e) { - if (config.debug.cryptoErrors) { - console.log( - this._settings.currencyCode + - ' EthScannerProcessor.getBalance ' + - address + - ' ' + - this._web3.LINK + - ' rpc error ' + - e.message - ); - } await BlocksoftCryptoLog.log( this._settings.currencyCode + ' EthScannerProcessor.getBalance ' + @@ -977,7 +957,7 @@ export default class EthScannerProcessor extends EthBasic { * @return {UnifiedTransaction} * @protected */ - async _unifyTransaction(_address, transaction, isInternal = false) { + async _unifyTransaction(_address: string, transaction, isInternal = false) { if (typeof transaction.timeStamp === 'undefined') { throw new Error( ' no transaction.timeStamp error transaction data ' + diff --git a/crypto/blockchains/eth/EthScannerProcessorErc20.js b/crypto/blockchains/eth/EthScannerProcessorErc20.ts similarity index 89% rename from crypto/blockchains/eth/EthScannerProcessorErc20.js rename to crypto/blockchains/eth/EthScannerProcessorErc20.ts index fa4e61e89..652230859 100644 --- a/crypto/blockchains/eth/EthScannerProcessorErc20.js +++ b/crypto/blockchains/eth/EthScannerProcessorErc20.ts @@ -3,9 +3,7 @@ */ import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog'; import EthScannerProcessor from './EthScannerProcessor'; -import config from '@app/config/config'; - -const abi = require('./ext/erc20'); +import abi from './ext/erc20'; export default class EthScannerProcessorErc20 extends EthScannerProcessor { /** @@ -34,7 +32,7 @@ export default class EthScannerProcessorErc20 extends EthScannerProcessor { * @param {string} address * @return {Promise<{balance, unconfirmed, provider}>} */ - async getBalanceBlockchain(address) { + async getBalanceBlockchain(address: string) { BlocksoftCryptoLog.log( this._settings.currencyCode + ' EthScannerProcessorErc20.getBalance started ' + @@ -99,15 +97,6 @@ export default class EthScannerProcessorErc20 extends EthScannerProcessor { time = 'now()'; return { balance, unconfirmed: 0, provider, time }; } catch (e) { - if (config.debug.appErrors) { - console.log( - this._settings.currencyCode + - ' EthScannerProcessorErc20.getBalance ' + - address + - ' error ' + - e.message - ); - } BlocksoftCryptoLog.log( this._settings.currencyCode + ' EthScannerProcessorErc20.getBalance ' + diff --git a/crypto/blockchains/eth/EthTokenProcessorErc20.js b/crypto/blockchains/eth/EthTokenProcessorErc20.js deleted file mode 100644 index e07b14c59..000000000 --- a/crypto/blockchains/eth/EthTokenProcessorErc20.js +++ /dev/null @@ -1,72 +0,0 @@ -/** - * @version 0.5 - */ -import EthBasic from './basic/EthBasic' -import config from '../../../app/config/config' - -const abi = require('./ext/erc20.js') - -export default class EthTokenProcessorErc20 extends EthBasic { - /** - * @param {string} tokenAddress - * @returns {Promise<{tokenAddress: *, currencyName: *, provider: string, tokenDecimals: *, icon: boolean, description: boolean, tokenType: string, currencyCode: *}>} - */ - async getTokenDetails(tokenAddress) { - let token, name, symbol, decimals - - this.checkWeb3CurrentServerUpdated() - - try { - // noinspection JSUnresolvedVariable - token = new this._web3.eth.Contract(abi.ERC20, tokenAddress.toLowerCase()) - } catch (e) { - if (config.debug.appErrors) { - console.log('EthTokenProcessorErc20 erc20 init token error ' + e.message) - } - e.message = 'erc20 init token ' + e.message - throw e - } - try { - name = await token.methods.name().call() - } catch (e) { - e.message = 'erc20.name ' + e.message - throw e - } - - try { - symbol = await token.methods.symbol().call() - } catch (e) { - if (config.debug.appErrors) { - console.log('EthTokenProcessorErc20 erc20.symbol error ' + e.message) - } - e.message = 'erc20.symbol ' + e.message - throw e - } - - try { - decimals = await token.methods.decimals().call() - } catch (e) { - if (config.debug.appErrors) { - console.log('EthTokenProcessorErc20 erc20.decimals error ' + e.message) - } - e.message = 'erc20.decimals ' + e.message - throw e - } - - const res = { - currencyCodePrefix : 'CUSTOM_', - currencyCode: symbol, - currencyName: name, - tokenType : this._mainTokenType, - tokenAddress: tokenAddress.toLowerCase(), - tokenDecimals: decimals, - icon: false, - description: false, - provider : 'web3' - } - if (this._mainCurrencyCode !== 'ETH') { - res.currencyCodePrefix = 'CUSTOM_' + this._mainTokenType + '_' - } - return res - } -} diff --git a/crypto/blockchains/eth/EthTokenProcessorErc20.ts b/crypto/blockchains/eth/EthTokenProcessorErc20.ts new file mode 100644 index 000000000..9cf6fea9b --- /dev/null +++ b/crypto/blockchains/eth/EthTokenProcessorErc20.ts @@ -0,0 +1,77 @@ +/** + * @version 0.5 + */ +import EthBasic from './basic/EthBasic'; +import abi from './ext/erc20.js'; + +export default class EthTokenProcessorErc20 extends EthBasic { + /** + * @param {string} tokenAddress + * @returns {Promise<{tokenAddress: *, currencyName: *, provider: string, tokenDecimals: *, icon: boolean, description: boolean, tokenType: string, currencyCode: *}>} + */ + async getTokenDetails(tokenAddress: string): Promise<{ + tokenAddress: string; + currencyName: string; + provider: string; + tokenDecimals: string; + icon: boolean; + description: boolean; + tokenType: string; + currencyCode: string; + }> { + let token, name, symbol, decimals; + + this.checkWeb3CurrentServerUpdated(); + + try { + // noinspection JSUnresolvedVariable + token = new this._web3.eth.Contract( + abi.ERC20, + tokenAddress.toLowerCase() + ); + } catch (err) { + const e = err as unknown as any; + e.message = 'erc20 init token ' + e.message; + throw e; + } + try { + name = await token.methods.name().call(); + } catch (err) { + const e = err as unknown as any; + e.message = 'erc20.name ' + e.message; + throw e; + } + + try { + symbol = await token.methods.symbol().call(); + } catch (err) { + const e = err as unknown as any; + e.message = 'erc20.symbol ' + e.message; + throw e; + } + + try { + decimals = await token.methods.decimals().call(); + } catch (err) { + const e = err as unknown as any; + e.message = 'erc20.decimals ' + e.message; + throw e; + } + + const res = { + currencyCodePrefix: 'CUSTOM_', + currencyCode: symbol, + currencyName: name, + tokenType: this._mainTokenType, + tokenAddress: tokenAddress.toLowerCase(), + tokenDecimals: decimals, + icon: false, + description: false, + provider: 'web3' + }; + if (this._mainCurrencyCode !== 'ETH') { + res.currencyCodePrefix = 'CUSTOM_' + this._mainTokenType + '_'; + } + return res; + } +} diff --git a/crypto/blockchains/eth/EthTokenProcessorNft.js b/crypto/blockchains/eth/EthTokenProcessorNft.js deleted file mode 100644 index a44d7c640..000000000 --- a/crypto/blockchains/eth/EthTokenProcessorNft.js +++ /dev/null @@ -1,67 +0,0 @@ -/** - * @version 0.50 - */ -import EthBasic from './basic/EthBasic' -import EthNftOpensea from '@crypto/blockchains/eth/apis/EthNftOpensea' -import EthNftMatic from '@crypto/blockchains/eth/apis/EthNftMatic' -import abi from './ext/erc721.js' -import config from '@app/config/config' -import BlocksoftDictNfts from '@crypto/common/BlocksoftDictNfts' - -export default class EthTokenProcessorNft extends EthBasic { - - - /** - * @param data.address - * @param data.tokenBlockchainCode - */ - async getListBlockchain(data) { - - const settings = BlocksoftDictNfts.NftsIndexed[data.tokenBlockchainCode] - if ( - typeof settings !== 'undefined' && typeof settings.apiType !== 'undefined' && settings.apiType === 'OPENSEA' - ) { - return EthNftOpensea(data) - } else { - return EthNftMatic(data) - } - } - - async getNftDetails(nftAddress, nftType) { - this.checkWeb3CurrentServerUpdated() - - let token, name, symbol - try { - token = new this._web3.eth.Contract(abi.ERC721, nftAddress.toLowerCase()) - } catch (e) { - if (config.debug.appErrors) { - console.log('EthTokenProcessorNft erc721 init token error ' + e.message) - } - e.message = 'erc721 init token ' + e.message - throw e - } - - // @todo more checks! - try { - name = await token.methods.name().call() - } catch (e) { - name = nftAddress.substr(0, 32) - } - - try { - symbol = await token.methods.symbol().call() - } catch (e) { - symbol = name.substr(0, 5) - } - - const res = { - nftSymbol: symbol, - nftCode: symbol, - nftName: name, - nftType: nftType, - nftAddress: nftAddress.toLowerCase(), - provider: 'web3' - } - return res - } -} diff --git a/crypto/blockchains/eth/EthTokenProcessorNft.ts b/crypto/blockchains/eth/EthTokenProcessorNft.ts new file mode 100644 index 000000000..4ce5aa44c --- /dev/null +++ b/crypto/blockchains/eth/EthTokenProcessorNft.ts @@ -0,0 +1,63 @@ +/** + * @version 0.50 + */ +import EthBasic from './basic/EthBasic'; +import EthNftOpensea from '@crypto/blockchains/eth/apis/EthNftOpensea'; +import EthNftMatic from '@crypto/blockchains/eth/apis/EthNftMatic'; +import abi from './ext/erc721.js'; +import BlocksoftDictNfts from '@crypto/common/BlocksoftDictNfts'; + +export default class EthTokenProcessorNft extends EthBasic { + /** + * @param data.address + * @param data.tokenBlockchainCode + */ + async getListBlockchain(data) { + const settings = BlocksoftDictNfts.NftsIndexed[data.tokenBlockchainCode]; + if ( + typeof settings !== 'undefined' && + typeof settings.apiType !== 'undefined' && + settings.apiType === 'OPENSEA' + ) { + return EthNftOpensea(data); + } else { + return EthNftMatic(data); + } + } + + async getNftDetails(nftAddress: string, nftType: string) { + this.checkWeb3CurrentServerUpdated(); + + let token, name, symbol; + try { + token = new this._web3.eth.Contract(abi.ERC721, nftAddress.toLowerCase()); + } catch (err) { + const e = err as unknown as any; + e.message = 'erc721 init token ' + e.message; + throw e; + } + + // @todo more checks! + try { + name = await token.methods.name().call(); + } catch (e) { + name = nftAddress.substr(0, 32); + } + + try { + symbol = await token.methods.symbol().call(); + } catch (e) { + symbol = name.substr(0, 5); + } + + const res = { + nftSymbol: symbol, + nftCode: symbol, + nftName: name, + nftType: nftType, + nftAddress: nftAddress.toLowerCase(), + provider: 'web3' + }; + return res; + } +} diff --git a/crypto/blockchains/eth/EthTransferProcessor.ts b/crypto/blockchains/eth/EthTransferProcessor.ts index 7c8b6a6c5..ce8b2b74c 100644 --- a/crypto/blockchains/eth/EthTransferProcessor.ts +++ b/crypto/blockchains/eth/EthTransferProcessor.ts @@ -12,24 +12,21 @@ import EthBasic from './basic/EthBasic'; import EthNetworkPrices from './basic/EthNetworkPrices'; import EthTxSendProvider from './basic/EthTxSendProvider'; -import MarketingEvent from '../../../app/services/Marketing/MarketingEvent'; -import BlocksoftDispatcher from '../BlocksoftDispatcher'; -import { BlocksoftBlockchainTypes } from '../BlocksoftBlockchainTypes'; +import AirDAODispatcher from '../AirDAODispatcher'; +import { AirDAOBlockchainTypes } from '../AirDAOBlockchainTypes'; -import config from '../../../app/config/config'; -import settingsActions from '../../../app/appstores/Stores/Settings/SettingsActions'; import BlocksoftExternalSettings from '../../common/BlocksoftExternalSettings'; -import { sublocale } from '../../../app/services/i18n'; import abi721 from './ext/erc721.js'; import abi1155 from './ext/erc1155'; import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; import OneUtils from '@crypto/blockchains/one/ext/OneUtils'; +import { Database } from '@database'; export default class EthTransferProcessor extends EthBasic - implements BlocksoftBlockchainTypes.TransferProcessor + implements AirDAOBlockchainTypes.TransferProcessor { - _useThisBalance: boolean = true; + _useThisBalance = true; needPrivateForFee(): boolean { return false; @@ -40,10 +37,10 @@ export default class EthTransferProcessor } async getFeeRate( - data: BlocksoftBlockchainTypes.TransferData, - privateData?: BlocksoftBlockchainTypes.TransferPrivateData, - additionalData: BlocksoftBlockchainTypes.TransferAdditionalData = {} - ): Promise { + data: AirDAOBlockchainTypes.TransferData, + privateData?: AirDAOBlockchainTypes.TransferPrivateData, + additionalData: AirDAOBlockchainTypes.TransferAdditionalData = {} + ): Promise { let txRBFed = ''; let txRBF = false; @@ -123,7 +120,7 @@ export default class EthTransferProcessor ); if (oldGasPrice === false) { try { - const ethProvider = BlocksoftDispatcher.getScannerProcessor( + const ethProvider = AirDAODispatcher.getScannerProcessor( data.currencyCode ); const scannedTx = await ethProvider.getTransactionBlockchain(txRBF); @@ -188,10 +185,10 @@ export default class EthTransferProcessor this._mainCurrencyCode, data.addressFrom ); - const ethAllowBlockedBalance = await settingsActions.getSetting( + const ethAllowBlockedBalance = await Database.localStorage.get( 'ethAllowBlockedBalance' ); - const ethAllowLongQuery = await settingsActions.getSetting( + const ethAllowLongQuery = await Database.localStorage.get( 'ethAllowLongQuery' ); @@ -382,11 +379,6 @@ export default class EthTransferProcessor gasLimit = BlocksoftUtils.mul(gasLimit, 1.5); } } catch (e) { - if (config.debug.cryptoErrors) { - BlocksoftCryptoLog.log( - 'EthTransferProcessor data.contractCallData error ' + e.message - ); - } BlocksoftCryptoLog.log( 'EthTransferProcessor data.contractCallData error ' + e.message ); @@ -496,8 +488,8 @@ export default class EthTransferProcessor } ); - const result: BlocksoftBlockchainTypes.FeeRateResult = - {} as BlocksoftBlockchainTypes.FeeRateResult; + const result: AirDAOBlockchainTypes.FeeRateResult = + {} as AirDAOBlockchainTypes.FeeRateResult; result.fees = []; let balance = '0'; @@ -523,15 +515,16 @@ export default class EthTransferProcessor } else { nonceLog += ' from serverCheckNoLog '; } - if ( - MarketingEvent.DATA.LOG_TESTER && - typeof proxyPriceCheck.newNonceDEBUG !== 'undefined' - ) { - nonceForTxBasic = proxyPriceCheck.newNonceDEBUG; - nonceLog += ' used newNonceDEBUG'; - } else { - nonceLog += ' used newNonce '; - } + // if ( + // MarketingEvent.DATA.LOG_TESTER && + // typeof proxyPriceCheck.newNonceDEBUG !== 'undefined' + // ) { + // nonceForTxBasic = proxyPriceCheck.newNonceDEBUG; + // nonceLog += ' used newNonceDEBUG'; + // } else { + // nonceLog += ' used newNonce '; + // } + nonceLog += ' used newNonce '; if (nonceForTxBasic === 'maxValue+1') { if (maxNonceLocal.maxValue * 1 > -1) { @@ -633,7 +626,7 @@ export default class EthTransferProcessor } let fee = BlocksoftUtils.mul(gasPrice[key], gasLimit); let amount = data.amount; - let needSpeed = gasPrice[key].toString(); + const needSpeed = gasPrice[key].toString(); let newGasPrice = needSpeed; let changedFeeByBalance = false; if (actualCheckBalance) { @@ -1011,12 +1004,6 @@ export default class EthTransferProcessor } } } catch (e) { - if (config.debug.cryptoErrors) { - BlocksoftCryptoLog.log( - ' EthTransferProcessor.getFees ethAllowBlockedBalance inner error ' + - e.message - ); - } BlocksoftCryptoLog.log( ' EthTransferProcessor.getFees ethAllowBlockedBalance inner error ' + e.message @@ -1056,10 +1043,10 @@ export default class EthTransferProcessor } async getTransferAllBalance( - data: BlocksoftBlockchainTypes.TransferData, - privateData?: BlocksoftBlockchainTypes.TransferPrivateData, - additionalData: BlocksoftBlockchainTypes.TransferAdditionalData = {} - ): Promise { + data: AirDAOBlockchainTypes.TransferData, + privateData?: AirDAOBlockchainTypes.TransferPrivateData, + additionalData: AirDAOBlockchainTypes.TransferAdditionalData = {} + ): Promise { if (!data.amount || data.amount === '0') { await BlocksoftCryptoLog.log( this._settings.currencyCode + @@ -1141,10 +1128,10 @@ export default class EthTransferProcessor } async sendTx( - data: BlocksoftBlockchainTypes.TransferData, - privateData: BlocksoftBlockchainTypes.TransferPrivateData, - uiData: BlocksoftBlockchainTypes.TransferUiData - ): Promise { + data: AirDAOBlockchainTypes.TransferData, + privateData: AirDAOBlockchainTypes.TransferPrivateData, + uiData: AirDAOBlockchainTypes.TransferUiData + ): Promise { if (typeof privateData.privateKey === 'undefined') { throw new Error('ETH transaction required privateKey'); } @@ -1255,7 +1242,7 @@ export default class EthTransferProcessor throw new Error('SERVER_PLEASE_SELECT_FEE'); } - const tx: BlocksoftBlockchainTypes.EthTx = { + const tx: AirDAOBlockchainTypes.EthTx = { from: data.addressFrom, to: realAddressToLower, gasPrice: finalGasPrice, @@ -1336,12 +1323,11 @@ export default class EthTransferProcessor typeof data.basicAmount !== 'undefined' ? data.basicAmount : data.amount; logData.basicToken = typeof data.basicToken !== 'undefined' ? data.basicToken : ''; - logData.pushLocale = sublocale(); - logData.pushSetting = await settingsActions.getSetting( - 'transactionsNotifs' - ); + // logData.pushLocale = sublocale(); + logData.pushLocale = 'en'; + logData.pushSetting = await Database.localStorage.get('transactionsNotifs'); - let result = {} as BlocksoftBlockchainTypes.SendTxResult; + let result = {} as AirDAOBlockchainTypes.SendTxResult; try { if (txRBF) { let oldNonce = @@ -1358,7 +1344,7 @@ export default class EthTransferProcessor } if (oldNonce === false || oldNonce === -1) { try { - const ethProvider = BlocksoftDispatcher.getScannerProcessor( + const ethProvider = AirDAODispatcher.getScannerProcessor( data.currencyCode ); const scannedTx = await ethProvider.getTransactionBlockchain(txRBF); @@ -1430,15 +1416,6 @@ export default class EthTransferProcessor try { result = await sender.send(tx, privateData, txRBF, logData); } catch (e) { - if (config.debug.cryptoErrors) { - BlocksoftCryptoLog.log( - this._settings.currencyCode + - ' EthTransferProcessor.sent while ' + - (txRBF ? 'txRbf' : 'usual') + - ' sender.send error ' + - e.message - ); - } throw e; } @@ -1469,35 +1446,16 @@ export default class EthTransferProcessor JSON.stringify(result.transactionJson) ); } catch (e) { - if (config.debug.cryptoErrors) { - BlocksoftCryptoLog.log( - this._settings.currencyCode + - ' EthTransferProcessor.sent error ' + - e.message, - tx - ); - } this.checkError(e, data, txRBF, logData); } // @ts-ignore logData.result = result; - // noinspection ES6MissingAwait - MarketingEvent.logOnlyRealTime( - 'v20_eth_tx_success ' + - this._settings.currencyCode + - ' ' + - data.addressFrom + - ' => ' + - realAddressTo, - logData - ); - return result; } async setMissingTx( - data: BlocksoftBlockchainTypes.DbAccount, - transaction: BlocksoftBlockchainTypes.DbTransaction + data: AirDAOBlockchainTypes.DbAccount, + transaction: AirDAOBlockchainTypes.DbTransaction ): Promise { if ( typeof transaction.transactionJson !== 'undefined' && @@ -1517,21 +1475,12 @@ export default class EthTransferProcessor 'send_' + transaction.transactionHash ); } - MarketingEvent.logOnlyRealTime( - 'v20_eth_tx_set_missing ' + - this._settings.currencyCode + - ' ' + - data.address + - ' => ' + - transaction.addressTo, - transaction - ); return true; } canRBF( - data: BlocksoftBlockchainTypes.DbAccount, - transaction: BlocksoftBlockchainTypes.DbTransaction, + data: AirDAOBlockchainTypes.DbAccount, + transaction: AirDAOBlockchainTypes.DbTransaction, source: string ): boolean { if (transaction.transactionDirection === 'income') { diff --git a/crypto/blockchains/eth/EthTransferProcessorErc20.ts b/crypto/blockchains/eth/EthTransferProcessorErc20.ts index 079146f56..baf952169 100644 --- a/crypto/blockchains/eth/EthTransferProcessorErc20.ts +++ b/crypto/blockchains/eth/EthTransferProcessorErc20.ts @@ -3,19 +3,16 @@ * @version 0.20 */ import EthTransferProcessor from './EthTransferProcessor'; -import config from '@app/config/config'; -import MarketingEvent from '@app/services/Marketing/MarketingEvent'; - -import { BlocksoftBlockchainTypes } from '../BlocksoftBlockchainTypes'; +import { AirDAOBlockchainTypes } from '../AirDAOBlockchainTypes'; import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings'; -const abi = require('./ext/erc20.js'); +import abi from './ext/erc20.js'; export default class EthTransferProcessorErc20 extends EthTransferProcessor - implements BlocksoftBlockchainTypes.TransferProcessor + implements AirDAOBlockchainTypes.TransferProcessor { constructor(settings: { network?: string; tokenAddress: any }) { super(settings); @@ -30,8 +27,8 @@ export default class EthTransferProcessorErc20 _useThisBalance: boolean = false; async checkTransferHasError( - data: BlocksoftBlockchainTypes.CheckTransferHasErrorData - ): Promise { + data: AirDAOBlockchainTypes.CheckTransferHasErrorData + ): Promise { // @ts-ignore const balance = data.addressFrom && data.addressFrom !== '' @@ -55,10 +52,10 @@ export default class EthTransferProcessorErc20 } async getFeeRate( - data: BlocksoftBlockchainTypes.TransferData, - privateData?: BlocksoftBlockchainTypes.TransferPrivateData, - additionalData: BlocksoftBlockchainTypes.TransferAdditionalData = {} - ): Promise { + data: AirDAOBlockchainTypes.TransferData, + privateData?: AirDAOBlockchainTypes.TransferPrivateData, + additionalData: AirDAOBlockchainTypes.TransferAdditionalData = {} + ): Promise { if (this.checkWeb3CurrentServerUpdated()) { this._token = new this._web3.eth.Contract( abi.ERC20, @@ -133,13 +130,6 @@ export default class EthTransferProcessorErc20 firstAddressTo + ' from ' + data.addressFrom; - if (config.debug.cryptoErrors) { - console.log( - this._settings.currencyCode + - ' EthTxProcessorErc20.getFeeRate estimateGas error1 ' + - e.message - ); - } BlocksoftCryptoLog.log( this._settings.currencyCode + ' EthTxProcessorErc20.getFeeRate estimateGas error1 ' + @@ -158,13 +148,6 @@ export default class EthTransferProcessorErc20 firstAddressTo + ' from ' + data.addressFrom; - if (config.debug.cryptoErrors) { - console.log( - this._settings.currencyCode + - ' EthTxProcessorErc20.getFeeRate estimateGas error2 ' + - e.message - ); - } BlocksoftCryptoLog.log( this._settings.currencyCode + ' EthTxProcessorErc20.getFeeRate estimateGas error2 ' + @@ -185,13 +168,6 @@ export default class EthTransferProcessorErc20 firstAddressTo + ' from ' + data.addressFrom; - if (config.debug.cryptoErrors) { - console.log( - this._settings.currencyCode + - ' EthTxProcessorErc20.getFeeRate estimateGas error3 ' + - e.message - ); - } BlocksoftCryptoLog.log( this._settings.currencyCode + ' EthTxProcessorErc20.getFeeRate estimateGas error3 ' + @@ -220,21 +196,6 @@ export default class EthTransferProcessorErc20 if (estimatedGas < minGas) { estimatedGas = minGas; } - MarketingEvent.logOnlyRealTime( - eventTitle + - this._settings.currencyCode + - ' ' + - data.addressFrom + - ' => ' + - data.addressTo, - { - amount: data.amount + '', - estimatedGas, - serverEstimatedGas, - serverEstimatedGas2, - serverEstimatedGas3 - } - ); } catch (e) { this.checkError(e, data); } @@ -252,10 +213,10 @@ export default class EthTransferProcessorErc20 } async getTransferAllBalance( - data: BlocksoftBlockchainTypes.TransferData, - privateData?: BlocksoftBlockchainTypes.TransferPrivateData, - additionalData: BlocksoftBlockchainTypes.TransferAdditionalData = {} - ): Promise { + data: AirDAOBlockchainTypes.TransferData, + privateData?: AirDAOBlockchainTypes.TransferPrivateData, + additionalData: AirDAOBlockchainTypes.TransferAdditionalData = {} + ): Promise { const tmpData = { ...data }; if (!tmpData.amount || tmpData.amount === '0') { await BlocksoftCryptoLog.log( @@ -304,10 +265,10 @@ export default class EthTransferProcessorErc20 } async sendTx( - data: BlocksoftBlockchainTypes.TransferData, - privateData: BlocksoftBlockchainTypes.TransferPrivateData, - uiData: BlocksoftBlockchainTypes.TransferUiData - ): Promise { + data: AirDAOBlockchainTypes.TransferData, + privateData: AirDAOBlockchainTypes.TransferPrivateData, + uiData: AirDAOBlockchainTypes.TransferUiData + ): Promise { if (typeof data.dexOrderData !== 'undefined' && data.dexOrderData) { BlocksoftCryptoLog.log( this._settings.currencyCode + diff --git a/crypto/blockchains/eth/apis/EthNftMatic.js b/crypto/blockchains/eth/apis/EthNftMatic.ts similarity index 96% rename from crypto/blockchains/eth/apis/EthNftMatic.js rename to crypto/blockchains/eth/apis/EthNftMatic.ts index 57db930bd..bf87eeca4 100644 --- a/crypto/blockchains/eth/apis/EthNftMatic.js +++ b/crypto/blockchains/eth/apis/EthNftMatic.ts @@ -1,116 +1,116 @@ -/** - * @version 0.50 - */ -import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; -import BlocksoftUtils from '@crypto/common/BlocksoftUtils'; -import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; - -const API_PATH = 'https://microscanners.trustee.deals/getAllNfts/'; - -/** - * https://microscanners.trustee.deals/getMaticNfts/0xf1Cff704c6E6ce459e3E1544a9533cCcBDAD7B99 - * https://microscanners.trustee.deals/getAllNfts/0xf1Cff704c6E6ce459e3E1544a9533cCcBDAD7B99? - * @param data.address - * @param data.tokenBlockchainCode - * @param data.customAssets - */ -export default async function (data) { - if (!data.address) return false; - - const link = - API_PATH + - data.address + - '?tokenBlockchainCode=' + - data.tokenBlockchainCode + - '&tokens=' + - data.customAssets.join(','); - const result = await BlocksoftAxios.get(link); - - /** - * @var tmp.animation_url - * @var tmp.image - * @var tmp.name - * @var tmp.description - * @var tmp.token_index - * @var tmp.contract_address - */ - const formatted = []; - const collections = []; - let usdTotal = 0; - - if ( - result && - result.data && - typeof result.data !== 'undefined' && - result.data && - result.data.length - ) { - for (const tmp of result.data) { - const one = { - id: tmp.id, - tokenId: tmp.token_index, - contractAddress: tmp.contract_address, - contractSchema: tmp.contract_schema || 'ERC721', - tokenBlockchainCode: - tmp.token_blockchain_code || data.tokenBlockchainCode, - tokenBlockchain: tmp.token_blockchain || data.tokenBlockchain, - tokenQty: tmp.qty || 1, - img: tmp.image, - title: tmp.name || tmp.title, - subTitle: tmp.subtitle || '', - desc: tmp.description || '', - cryptoCurrencySymbol: tmp.crypto_currency_symbol || '', - cryptoValue: tmp.crypto_value || '', - usdValue: tmp.usd_value || '', - permalink: tmp.permalink || false - }; - try { - if (one.desc && !one.subTitle) { - one.subTitle = - one.desc.length > 20 ? one.desc.substring(0, 20) + '...' : one.desc; - } - } catch (e) { - BlocksoftCryptoLog.log( - 'EthTokenProcessorNft EthNftMatic name error ' + e.message - ); - } - - if (one.usdValue && one.usdValue * 1 > 0) { - usdTotal = usdTotal + one.usdValue * 1; - } - - let collectionKey = ''; - try { - if (typeof tmp.collection !== 'undefined') { - collectionKey = tmp.collection.name + one.contractAddress; - if (typeof collections[collectionKey] === 'undefined') { - collections[collectionKey] = { - numberAssets: 1, - title: tmp.collection.name, - img: tmp.collection.image, - walletCurrency: one.tokenBlockchainCode, - assets: [one] - }; - } else { - collections[collectionKey].numberAssets++; - collections[collectionKey].assets.push(one); - } - } - } catch (e) { - console.log( - 'EthTokenProcessorNft EthNftMatic collection error ' + e.message - ); - } - - formatted.push(one); - } - } - - const formattedCollections = []; - if (collections) { - for (const key in collections) { - formattedCollections.push(collections[key]); - } - } - return { assets: formatted, collections: formattedCollections, usdTotal }; -} +/** + * @version 0.50 + */ +import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; +import BlocksoftUtils from '@crypto/common/BlocksoftUtils'; +import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; + +const API_PATH = 'https://microscanners.trustee.deals/getAllNfts/'; + +/** + * https://microscanners.trustee.deals/getMaticNfts/0xf1Cff704c6E6ce459e3E1544a9533cCcBDAD7B99 + * https://microscanners.trustee.deals/getAllNfts/0xf1Cff704c6E6ce459e3E1544a9533cCcBDAD7B99? + * @param data.address + * @param data.tokenBlockchainCode + * @param data.customAssets + */ +export default async function (data) { + if (!data.address) return false; + + const link = + API_PATH + + data.address + + '?tokenBlockchainCode=' + + data.tokenBlockchainCode + + '&tokens=' + + data.customAssets.join(','); + const result = await BlocksoftAxios.get(link); + + /** + * @var tmp.animation_url + * @var tmp.image + * @var tmp.name + * @var tmp.description + * @var tmp.token_index + * @var tmp.contract_address + */ + const formatted = []; + const collections = []; + let usdTotal = 0; + + if ( + result && + result.data && + typeof result.data !== 'undefined' && + result.data && + result.data.length + ) { + for (const tmp of result.data) { + const one = { + id: tmp.id, + tokenId: tmp.token_index, + contractAddress: tmp.contract_address, + contractSchema: tmp.contract_schema || 'ERC721', + tokenBlockchainCode: + tmp.token_blockchain_code || data.tokenBlockchainCode, + tokenBlockchain: tmp.token_blockchain || data.tokenBlockchain, + tokenQty: tmp.qty || 1, + img: tmp.image, + title: tmp.name || tmp.title, + subTitle: tmp.subtitle || '', + desc: tmp.description || '', + cryptoCurrencySymbol: tmp.crypto_currency_symbol || '', + cryptoValue: tmp.crypto_value || '', + usdValue: tmp.usd_value || '', + permalink: tmp.permalink || false + }; + try { + if (one.desc && !one.subTitle) { + one.subTitle = + one.desc.length > 20 ? one.desc.substring(0, 20) + '...' : one.desc; + } + } catch (e) { + BlocksoftCryptoLog.log( + 'EthTokenProcessorNft EthNftMatic name error ' + e.message + ); + } + + if (one.usdValue && one.usdValue * 1 > 0) { + usdTotal = usdTotal + one.usdValue * 1; + } + + let collectionKey = ''; + try { + if (typeof tmp.collection !== 'undefined') { + collectionKey = tmp.collection.name + one.contractAddress; + if (typeof collections[collectionKey] === 'undefined') { + collections[collectionKey] = { + numberAssets: 1, + title: tmp.collection.name, + img: tmp.collection.image, + walletCurrency: one.tokenBlockchainCode, + assets: [one] + }; + } else { + collections[collectionKey].numberAssets++; + collections[collectionKey].assets.push(one); + } + } + } catch (e) { + console.log( + 'EthTokenProcessorNft EthNftMatic collection error ' + e.message + ); + } + + formatted.push(one); + } + } + + const formattedCollections = []; + if (collections) { + for (const key in collections) { + formattedCollections.push(collections[key]); + } + } + return { assets: formatted, collections: formattedCollections, usdTotal }; +} diff --git a/crypto/blockchains/eth/apis/EthNftOpensea.js b/crypto/blockchains/eth/apis/EthNftOpensea.ts similarity index 97% rename from crypto/blockchains/eth/apis/EthNftOpensea.js rename to crypto/blockchains/eth/apis/EthNftOpensea.ts index 00ff393e1..a6d6938e4 100644 --- a/crypto/blockchains/eth/apis/EthNftOpensea.js +++ b/crypto/blockchains/eth/apis/EthNftOpensea.ts @@ -1,168 +1,168 @@ -/** - * @version 0.50 - */ -import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; -import BlocksoftUtils from '@crypto/common/BlocksoftUtils'; -import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; - -const API_PATH = 'https://api.opensea.io/api/v1/'; -const API_TEST_PATH = 'https://testnets-api.opensea.io/api/v1/'; -/** - * https://docs.opensea.io/reference/getting-assets - * curl --request GET --url https://api.opensea.io/api/v1/assets?order_direction=desc&offset=0&limit=20&owner=0x6cdb97bf46d77233cc943264633c2ed56bcf6f1f - * curl --request GET --url https://testnets-api.opensea.io/api/v1/assets?order_direction=desc&offset=0&limit=20&owner=0x6cdb97bf46d77233cc943264633c2ed56bcf6f1f - * @param data.address - * @param data.tokenBlockchainCode - */ -export default async function (data) { - let link; - if (data.tokenBlockchainCode === 'ETH_RINKEBY') { - link = API_TEST_PATH; - } else { - link = API_PATH; - } - if (!data.address) return false; - link += 'assets?order_direction=desc&owner=' + data.address; - const result = await BlocksoftAxios.getWithoutBraking(link); - - /** - * @var tmp.id - * @var tmp.token_id - * @var tmp.image_thumbnail_url - * @var tmp.name - * @var tmp.title - * @var tmp.last_sale - * @var tmp.last_sale.total_price - * @var tmp.last_sale.payment_token - * @var tmp.last_sale.payment_token.symbol - * @var tmp.last_sale.payment_token.name - * @var tmp.last_sale.payment_token.decimals - * @var tmp.last_sale.payment_token.usd_price - * @var tmp.asset_contract.address - * @var tmp.asset_contract.schema_name ERC721 - */ - const formatted = []; - const collections = []; - let usdTotal = 0; - - if ( - result && - result.data && - typeof result.data.assets !== 'undefined' && - result.data.assets && - result.data.assets.length - ) { - for (const tmp of result.data.assets) { - const one = { - id: tmp.id, - tokenId: tmp.token_id, - contractAddress: '', - contractSchema: 'ERC721', - tokenBlockchainCode: data.tokenBlockchainCode, - tokenBlockchain: data.tokenBlockchain, - tokenQty: 1, - img: tmp.image_preview_url, - title: tmp.name || tmp.title, - subTitle: '', - desc: '', - cryptoCurrencySymbol: '', - cryptoValue: '', - usdValue: '', - permalink: tmp.permalink || false - }; - try { - if (!one.title || typeof one.title === 'undefined') { - if (typeof tmp.asset_contract.name !== 'undefined') { - one.title = tmp.asset_contract.name; - } - } - if ( - typeof tmp.asset_contract.description !== 'undefined' && - tmp.asset_contract.description - ) { - one.desc = tmp.asset_contract.description; - } - if (one.title.indexOf(tmp.token_id) === -1) { - one.subTitle = '#' + tmp.token_id; - } else if (one.desc) { - one.subTitle = - one.desc.length > 20 ? one.desc.substring(0, 20) + '...' : one.desc; - } - } catch (e) { - BlocksoftCryptoLog.log( - 'EthTokenProcessorNft EthNftOpensea name error ' + e.message - ); - } - - try { - if ( - typeof tmp.asset_contract.address !== 'undefined' && - tmp.asset_contract.address - ) { - one.contractAddress = tmp.asset_contract.address; - } - if ( - typeof tmp.asset_contract.schema_name !== 'undefined' && - tmp.asset_contract.schema_name - ) { - one.contractSchema = tmp.asset_contract.schema_name; - } - } catch (e) { - BlocksoftCryptoLog.log( - 'EthTokenProcessorNft EthNftOpensea contract error ' + e.message - ); - } - - try { - if (typeof tmp.last_sale !== 'undefined' && tmp.last_sale) { - one.cryptoCurrencySymbol = tmp.last_sale.payment_token.symbol; - one.cryptoValue = BlocksoftUtils.toUnified( - tmp.last_sale.total_price, - tmp.last_sale.payment_token.decimals - ); - one.usdValue = tmp.last_sale.payment_token.usd_price; - usdTotal = usdTotal + tmp.last_sale.payment_token.usd_price * 1; - } - } catch (e) { - BlocksoftCryptoLog.log( - 'EthTokenProcessorNft EthNftOpensealast_sale error ' + e.message, - JSON.stringify(tmp) - ); - } - - let collectionKey = ''; - try { - if (typeof tmp.collection !== 'undefined') { - collectionKey = - tmp.collection.name + '_' + tmp.collection.payout_address; - if (typeof collections[collectionKey] === 'undefined') { - collections[collectionKey] = { - numberAssets: 1, - title: tmp.collection.name, - img: tmp.collection.banner_image_url || tmp.collection.image_url, - walletCurrency: data.tokenBlockchainCode, - assets: [one] - }; - } else { - collections[collectionKey].numberAssets++; - collections[collectionKey].assets.push(one); - } - } - } catch (e) { - BlocksoftCryptoLog.log( - 'EthTokenProcessorNft EthNftOpensea collection error ' + e.message - ); - } - - formatted.push(one); - } - } - - const formattedCollections = []; - if (collections) { - for (const key in collections) { - formattedCollections.push(collections[key]); - } - } - return { assets: formatted, collections: formattedCollections, usdTotal }; -} +/** + * @version 0.50 + */ +import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; +import BlocksoftUtils from '@crypto/common/BlocksoftUtils'; +import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; + +const API_PATH = 'https://api.opensea.io/api/v1/'; +const API_TEST_PATH = 'https://testnets-api.opensea.io/api/v1/'; +/** + * https://docs.opensea.io/reference/getting-assets + * curl --request GET --url https://api.opensea.io/api/v1/assets?order_direction=desc&offset=0&limit=20&owner=0x6cdb97bf46d77233cc943264633c2ed56bcf6f1f + * curl --request GET --url https://testnets-api.opensea.io/api/v1/assets?order_direction=desc&offset=0&limit=20&owner=0x6cdb97bf46d77233cc943264633c2ed56bcf6f1f + * @param data.address + * @param data.tokenBlockchainCode + */ +export default async function (data) { + let link; + if (data.tokenBlockchainCode === 'ETH_RINKEBY') { + link = API_TEST_PATH; + } else { + link = API_PATH; + } + if (!data.address) return false; + link += 'assets?order_direction=desc&owner=' + data.address; + const result = await BlocksoftAxios.getWithoutBraking(link); + + /** + * @var tmp.id + * @var tmp.token_id + * @var tmp.image_thumbnail_url + * @var tmp.name + * @var tmp.title + * @var tmp.last_sale + * @var tmp.last_sale.total_price + * @var tmp.last_sale.payment_token + * @var tmp.last_sale.payment_token.symbol + * @var tmp.last_sale.payment_token.name + * @var tmp.last_sale.payment_token.decimals + * @var tmp.last_sale.payment_token.usd_price + * @var tmp.asset_contract.address + * @var tmp.asset_contract.schema_name ERC721 + */ + const formatted = []; + const collections = []; + let usdTotal = 0; + + if ( + result && + result.data && + typeof result.data.assets !== 'undefined' && + result.data.assets && + result.data.assets.length + ) { + for (const tmp of result.data.assets) { + const one = { + id: tmp.id, + tokenId: tmp.token_id, + contractAddress: '', + contractSchema: 'ERC721', + tokenBlockchainCode: data.tokenBlockchainCode, + tokenBlockchain: data.tokenBlockchain, + tokenQty: 1, + img: tmp.image_preview_url, + title: tmp.name || tmp.title, + subTitle: '', + desc: '', + cryptoCurrencySymbol: '', + cryptoValue: '', + usdValue: '', + permalink: tmp.permalink || false + }; + try { + if (!one.title || typeof one.title === 'undefined') { + if (typeof tmp.asset_contract.name !== 'undefined') { + one.title = tmp.asset_contract.name; + } + } + if ( + typeof tmp.asset_contract.description !== 'undefined' && + tmp.asset_contract.description + ) { + one.desc = tmp.asset_contract.description; + } + if (one.title.indexOf(tmp.token_id) === -1) { + one.subTitle = '#' + tmp.token_id; + } else if (one.desc) { + one.subTitle = + one.desc.length > 20 ? one.desc.substring(0, 20) + '...' : one.desc; + } + } catch (e) { + BlocksoftCryptoLog.log( + 'EthTokenProcessorNft EthNftOpensea name error ' + e.message + ); + } + + try { + if ( + typeof tmp.asset_contract.address !== 'undefined' && + tmp.asset_contract.address + ) { + one.contractAddress = tmp.asset_contract.address; + } + if ( + typeof tmp.asset_contract.schema_name !== 'undefined' && + tmp.asset_contract.schema_name + ) { + one.contractSchema = tmp.asset_contract.schema_name; + } + } catch (e) { + BlocksoftCryptoLog.log( + 'EthTokenProcessorNft EthNftOpensea contract error ' + e.message + ); + } + + try { + if (typeof tmp.last_sale !== 'undefined' && tmp.last_sale) { + one.cryptoCurrencySymbol = tmp.last_sale.payment_token.symbol; + one.cryptoValue = BlocksoftUtils.toUnified( + tmp.last_sale.total_price, + tmp.last_sale.payment_token.decimals + ); + one.usdValue = tmp.last_sale.payment_token.usd_price; + usdTotal = usdTotal + tmp.last_sale.payment_token.usd_price * 1; + } + } catch (e) { + BlocksoftCryptoLog.log( + 'EthTokenProcessorNft EthNftOpensealast_sale error ' + e.message, + JSON.stringify(tmp) + ); + } + + let collectionKey = ''; + try { + if (typeof tmp.collection !== 'undefined') { + collectionKey = + tmp.collection.name + '_' + tmp.collection.payout_address; + if (typeof collections[collectionKey] === 'undefined') { + collections[collectionKey] = { + numberAssets: 1, + title: tmp.collection.name, + img: tmp.collection.banner_image_url || tmp.collection.image_url, + walletCurrency: data.tokenBlockchainCode, + assets: [one] + }; + } else { + collections[collectionKey].numberAssets++; + collections[collectionKey].assets.push(one); + } + } + } catch (e) { + BlocksoftCryptoLog.log( + 'EthTokenProcessorNft EthNftOpensea collection error ' + e.message + ); + } + + formatted.push(one); + } + } + + const formattedCollections = []; + if (collections) { + for (const key in collections) { + formattedCollections.push(collections[key]); + } + } + return { assets: formatted, collections: formattedCollections, usdTotal }; +} diff --git a/crypto/blockchains/eth/basic/EthBasic.js b/crypto/blockchains/eth/basic/EthBasic.ts similarity index 96% rename from crypto/blockchains/eth/basic/EthBasic.js rename to crypto/blockchains/eth/basic/EthBasic.ts index 19477c252..492e55c46 100644 --- a/crypto/blockchains/eth/basic/EthBasic.js +++ b/crypto/blockchains/eth/basic/EthBasic.ts @@ -3,9 +3,7 @@ * https://etherscan.io/apis#accounts */ import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; -import MarketingEvent from '@app/services/Marketing/MarketingEvent'; import { Web3Injected } from '@crypto/services/Web3Injected'; -import config from '@app/config/config'; export default class EthBasic { /** @@ -309,9 +307,6 @@ export default class EthBasic { } checkError(e, data, txRBF = false, logData = {}) { - if (config.debug.cryptoErrors) { - console.log('EthBasic Error ' + e.message); - } if (e.message.indexOf('Transaction has been reverted by the EVM') !== -1) { BlocksoftCryptoLog.log( 'EthBasic checkError0.0 ' + e.message + ' for ' + data.addressFrom, @@ -383,19 +378,6 @@ export default class EthBasic { ); throw new Error('SERVER_RESPONSE_BAD_INTERNET'); } else { - MarketingEvent.logOnlyRealTime( - 'v20_' + - this._mainCurrencyCode.toLowerCase() + - '_tx_error ' + - this._settings.currencyCode + - ' ' + - data.addressFrom + - ' => ' + - data.addressTo + - ' ' + - e.message, - logData - ); throw e; } } diff --git a/crypto/blockchains/eth/basic/EthNetworkPrices.js b/crypto/blockchains/eth/basic/EthNetworkPrices.ts similarity index 84% rename from crypto/blockchains/eth/basic/EthNetworkPrices.js rename to crypto/blockchains/eth/basic/EthNetworkPrices.ts index 7548f7bdd..f4152f7e1 100644 --- a/crypto/blockchains/eth/basic/EthNetworkPrices.js +++ b/crypto/blockchains/eth/basic/EthNetworkPrices.ts @@ -5,8 +5,6 @@ import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; import BlocksoftAxios from '../../../common/BlocksoftAxios'; import BlocksoftUtils from '../../../common/BlocksoftUtils'; import BlocksoftExternalSettings from '../../../common/BlocksoftExternalSettings'; -import MarketingEvent from '../../../../app/services/Marketing/MarketingEvent'; -import config from '../../../../app/config/config'; import EthRawDS from '../stores/EthRawDS'; import EthTmpDS from '../stores/EthTmpDS'; @@ -31,11 +29,11 @@ class EthNetworkPrices { if (mainCurrencyCode !== 'ETH' || isTestnet) { return false; } - const { apiEndpoints } = config.proxy; - const baseURL = MarketingEvent.DATA.LOG_TESTER - ? apiEndpoints.baseURLTest - : apiEndpoints.baseURL; - const proxy = baseURL + '/eth/getFees'; + // const { apiEndpoints } = config.proxy; + // const baseURL = MarketingEvent.DATA.LOG_TESTER + // ? apiEndpoints.baseURLTest + // : apiEndpoints.baseURL; + // const proxy = baseURL + '/eth/getFees'; const now = new Date().getTime(); if ( CACHE_PROXY_DATA.address === address && @@ -56,23 +54,17 @@ class EthNetworkPrices { let index = 0; do { try { - checkResult = await BlocksoftAxios.post( - proxy, - { - address, - logData, - marketingData: MarketingEvent.DATA - }, - 20000 - ); - } catch (e) { - if (config.debug.cryptoErrors) { - console.log( - 'EthNetworkPricesProvider.getWithProxy proxy error checkError ' + - e.message - ); - } - } + // TODO + // checkResult = await BlocksoftAxios.post( + // proxy, + // { + // address, + // logData, + // marketingData: MarketingEvent.DATA + // }, + // 20000 + // ); + } catch (e) {} index++; } while (index < 3 && !checkResult); @@ -87,12 +79,6 @@ class EthNetworkPrices { typeof checkResult.data.status === 'undefined' || checkResult.data.status === 'error' ) { - if (config.debug.cryptoErrors) { - console.log( - 'EthNetworkPricesProvider.getWithProxy proxy error checkResult1 ', - checkResult - ); - } checkResult = false; } else if (checkResult.data.status === 'notice') { throw new Error(checkResult.data.msg); @@ -103,20 +89,8 @@ class EthNetworkPrices { ' EthNetworkPricesProvider.getWithProxy proxy checkResult2 ', checkResult ); - if (config.debug.cryptoErrors) { - console.log( - 'EthNetworkPricesProvider.getWithProxy proxy error checkResult2 ', - checkResult - ); - } } } else { - if (config.debug.cryptoErrors) { - console.log( - 'EthNetworkPricesProvider.getWithProxy proxy error checkResultEmpty ', - checkResult - ); - } } if (checkResult === false) { @@ -210,10 +184,6 @@ class EthNetworkPrices { logData.resultFeeCacheTime = CACHE_FEES_ETH_TIME; logData.resultFee = JSON.stringify(CACHE_FEES_ETH); // noinspection ES6MissingAwait - MarketingEvent.logEvent( - 'v20_estimate_fee_' + mainCurrencyCode.toLowerCase() + '_result', - logData - ); BlocksoftCryptoLog.log( mainCurrencyCode + ' EthNetworkPricesProvider.getOnlyFees used cache => ' + @@ -251,11 +221,6 @@ class EthNetworkPrices { ); } } catch (e) { - // noinspection ES6MissingAwait - MarketingEvent.logEvent('estimate_fee_eth_load_error', { - link, - data: e.message - }); BlocksoftCryptoLog.log( mainCurrencyCode + ' EthNetworkPricesProvider.getOnlyFees loaded prev fee as error', @@ -267,18 +232,6 @@ class EthNetworkPrices { try { await this._parseLoaded(mainCurrencyCode, CACHE_PREV_DATA, link); } catch (e) { - if (config.debug.cryptoErrors) { - console.log( - mainCurrencyCode + - ' EthNetworkPricesProvider.getOnlyFees _parseLoaded error ' + - e.message - ); - } - // noinspection ES6MissingAwait - MarketingEvent.logEvent('estimate_fee_eth_parse_error', { - link, - data: e.message - }); BlocksoftCryptoLog.log( mainCurrencyCode + ' EthNetworkPricesProvider.getOnlyFees _parseLoaded error ' + @@ -288,8 +241,6 @@ class EthNetworkPrices { } logData.resultFeeCacheTime = CACHE_FEES_ETH_TIME; logData.resultFee = JSON.stringify(CACHE_FEES_ETH); - MarketingEvent.logEvent('estimate_fee_eth_result', logData); - return this._format(); } diff --git a/crypto/blockchains/eth/basic/EthTxSendProvider.ts b/crypto/blockchains/eth/basic/EthTxSendProvider.ts index 3a98140cd..9ecd6c57c 100644 --- a/crypto/blockchains/eth/basic/EthTxSendProvider.ts +++ b/crypto/blockchains/eth/basic/EthTxSendProvider.ts @@ -2,15 +2,13 @@ * @author Ksu * @version 0.32 */ -import { BlocksoftBlockchainTypes } from '../../BlocksoftBlockchainTypes'; +import { AirDAOBlockchainTypes } from '../../AirDAOBlockchainTypes'; import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; import BlocksoftUtils from '../../../common/BlocksoftUtils'; import EthTmpDS from '../stores/EthTmpDS'; import EthRawDS from '../stores/EthRawDS'; import BlocksoftExternalSettings from '../../../common/BlocksoftExternalSettings'; import BlocksoftAxios from '../../../common/BlocksoftAxios'; -import config from '../../../../app/config/config'; -import MarketingEvent from '../../../../app/services/Marketing/MarketingEvent'; export default class EthTxSendProvider { private _web3: any; @@ -37,8 +35,8 @@ export default class EthTxSendProvider { } async sign( - tx: BlocksoftBlockchainTypes.EthTx, - privateData: BlocksoftBlockchainTypes.TransferPrivateData, + tx: AirDAOBlockchainTypes.EthTx, + privateData: AirDAOBlockchainTypes.TransferPrivateData, txRBF: any, logData: any ): Promise<{ transactionHash: string; transactionJson: any }> { @@ -75,8 +73,8 @@ export default class EthTxSendProvider { } async send( - tx: BlocksoftBlockchainTypes.EthTx, - privateData: BlocksoftBlockchainTypes.TransferPrivateData, + tx: AirDAOBlockchainTypes.EthTx, + privateData: AirDAOBlockchainTypes.TransferPrivateData, txRBF: any, logData: any ): Promise<{ transactionHash: string; transactionJson: any }> { @@ -117,35 +115,28 @@ export default class EthTxSendProvider { } } - const { apiEndpoints } = config.proxy; - const baseURL = MarketingEvent.DATA.LOG_TESTER - ? apiEndpoints.baseURLTest - : apiEndpoints.baseURL; - const proxy = baseURL + '/send/checktx'; - const errorProxy = baseURL + '/send/errortx'; - const successProxy = baseURL + '/send/sendtx'; + // const { apiEndpoints } = config.proxy; + // const baseURL = MarketingEvent.DATA.LOG_TESTER + // ? apiEndpoints.baseURLTest + // : apiEndpoints.baseURL; + // const proxy = baseURL + '/send/checktx'; + // const errorProxy = baseURL + '/send/errortx'; + // const successProxy = baseURL + '/send/sendtx'; let checkResult = false; try { - await BlocksoftCryptoLog.log( - this._settings.currencyCode + - ' EthTxSendProvider.send proxy checkResult start ' + - proxy, - logData - ); - checkResult = await BlocksoftAxios.post(proxy, { - raw: rawTransaction, - txRBF, - logData, - marketingData: MarketingEvent.DATA - }); + // await BlocksoftCryptoLog.log( + // this._settings.currencyCode + + // ' EthTxSendProvider.send proxy checkResult start ' + + // proxy, + // logData + // ); + // checkResult = await BlocksoftAxios.post(proxy, { + // raw: rawTransaction, + // txRBF, + // logData, + // marketingData: MarketingEvent.DATA + // }); } catch (e) { - if (config.debug.cryptoErrors) { - console.log( - this._settings.currencyCode + - ' EthTxSendProvider.send proxy error checkResult ' + - e.message - ); - } await BlocksoftCryptoLog.log( this._settings.currencyCode + ' EthTxSendProvider.send proxy error checkResult ' + @@ -164,13 +155,6 @@ export default class EthTxSendProvider { typeof checkResult.data.status === 'undefined' || checkResult.data.status === 'error' ) { - if (config.debug.cryptoErrors) { - console.log( - this._settings.currencyCode + - ' EthTxSendProvider.send proxy error checkResult1 ', - JSON.parse(JSON.stringify(checkResult.data)) - ); - } checkResult = false; } else if (checkResult.data.status === 'notice') { throw new Error(checkResult.data.msg); @@ -181,22 +165,8 @@ export default class EthTxSendProvider { ' EthTxSendProvider.send proxy checkResult2 ', checkResult ); - if (config.debug.cryptoErrors) { - console.log( - this._settings.currencyCode + - ' EthTxSendProvider.send proxy error checkResult2 ', - JSON.parse(JSON.stringify(checkResult.data)) - ); - } } } else { - if (config.debug.cryptoErrors) { - console.log( - this._settings.currencyCode + - ' EthTxSendProvider.send proxy error checkResultEmpty ', - JSON.stringify(checkResult.data) - ); - } } logData.checkResult = checkResult && typeof checkResult.data !== 'undefined' && checkResult.data @@ -291,52 +261,26 @@ export default class EthTxSendProvider { typeof result !== 'undefined' && result ? result.data : 'NO RESULT' ); } catch (e) { - if (config.debug.cryptoErrors) { - console.log( - this._settings.currencyCode + - ' ' + - this._mainCurrencyCode + - ' EthTxSendProvider.send trezor ' + - sendLink + - ' error ' + - e.message, - JSON.parse(JSON.stringify(logData)) - ); - } try { - logData.error = e.message; - await BlocksoftCryptoLog.log( - this._settings.currencyCode + - ' EthTxSendProvider.send proxy errorTx start ' + - errorProxy, - logData - ); - const res2 = await BlocksoftAxios.post(errorProxy, { - raw: rawTransaction, - txRBF, - logData, - marketingData: MarketingEvent.DATA - }); - if (config.debug.cryptoErrors) { - console.log( - this._settings.currencyCode + - ' EthTxSendProvider.send proxy errorTx result', - JSON.parse(JSON.stringify(res2.data)) - ); - } - await BlocksoftCryptoLog.log( - this._settings.currencyCode + ' EthTxSendProvider.send proxy errorTx', - typeof res2.data !== 'undefined' ? res2.data : res2 - ); - throw new Error('res2.data : ' + res2.data); + // logData.error = e.message; + // await BlocksoftCryptoLog.log( + // this._settings.currencyCode + + // ' EthTxSendProvider.send proxy errorTx start ' + + // errorProxy, + // logData + // ); + // const res2 = await BlocksoftAxios.post(errorProxy, { + // raw: rawTransaction, + // txRBF, + // logData, + // marketingData: MarketingEvent.DATA + // }); + // await BlocksoftCryptoLog.log( + // this._settings.currencyCode + ' EthTxSendProvider.send proxy errorTx', + // typeof res2.data !== 'undefined' ? res2.data : res2 + // ); + // throw new Error('res2.data : ' + res2.data); } catch (e2) { - if (config.debug.cryptoErrors) { - console.log( - this._settings.currencyCode + - ' EthTxSendProvider.send proxy error errorTx ' + - e.message - ); - } await BlocksoftCryptoLog.log( this._settings.currencyCode + ' EthTxSendProvider.send proxy error errorTx ' + @@ -400,34 +344,20 @@ export default class EthTxSendProvider { checkResult = false; try { - logData.txHash = transactionHash; - await BlocksoftCryptoLog.log( - this._settings.currencyCode + - ' EthTxSendProvider.send proxy successTx start ' + - successProxy, - logData - ); - checkResult = await BlocksoftAxios.post(successProxy, { - raw: rawTransaction, - txRBF, - logData, - marketingData: MarketingEvent.DATA - }); - if (config.debug.cryptoErrors) { - console.log( - this._settings.currencyCode + - ' EthTxSendProvider.send proxy successTx result ', - JSON.parse(JSON.stringify(checkResult.data)) - ); - } + // logData.txHash = transactionHash; + // await BlocksoftCryptoLog.log( + // this._settings.currencyCode + + // ' EthTxSendProvider.send proxy successTx start ' + + // successProxy, + // logData + // ); + // checkResult = await BlocksoftAxios.post(successProxy, { + // raw: rawTransaction, + // txRBF, + // logData, + // marketingData: MarketingEvent.DATA + // }); } catch (e3) { - if (config.debug.cryptoErrors) { - console.log( - this._settings.currencyCode + - ' EthTxSendProvider.send proxy error successTx ' + - e3.message - ); - } await BlocksoftCryptoLog.log( this._settings.currencyCode + ' EthTxSendProvider.send proxy error successTx ' + @@ -446,13 +376,6 @@ export default class EthTxSendProvider { typeof checkResult.data.status === 'undefined' || checkResult.data.status === 'error' ) { - if (config.debug.cryptoErrors) { - console.log( - this._settings.currencyCode + - ' EthTxSendProvider.send proxy error successResult1 ', - checkResult - ); - } checkResult = false; } else if (checkResult.data.status === 'notice') { throw new Error(checkResult.data.msg); @@ -463,22 +386,8 @@ export default class EthTxSendProvider { ' EthTxSendProvider.send proxy successResult2 ', checkResult ); - if (config.debug.cryptoErrors) { - console.log( - this._settings.currencyCode + - ' EthTxSendProvider.send proxy error successResult2 ', - checkResult - ); - } } } else { - if (config.debug.cryptoErrors) { - console.log( - this._settings.currencyCode + - ' EthTxSendProvider.send proxy error successResultEmpty ', - checkResult - ); - } } logData.successResult = checkResult && typeof checkResult.data !== 'undefined' && checkResult.data diff --git a/crypto/blockchains/eth/ext/EthEstimateGas.js b/crypto/blockchains/eth/ext/EthEstimateGas.js deleted file mode 100644 index b2f08281d..000000000 --- a/crypto/blockchains/eth/ext/EthEstimateGas.js +++ /dev/null @@ -1,24 +0,0 @@ -const TransactionController = require('./estimateGas/index.js'); - -export default async function(provider, gasPrice, from, to, value) { - if (from === to) return '21000' - - const gasPriceHex = '0x' + (+gasPrice).toString(16) - const valueHex = '0x' + (+value).toString(16) - - const txController = new TransactionController({ - provider: provider, - getGasPrice: gasPriceHex - }) - - const res = await txController.addTxGasDefaults({ - txParams: { - from: from, - to: to, - value: valueHex - }, - history: [{}] - }) - - return parseInt(res.txParams.gas, 16) -} diff --git a/crypto/blockchains/eth/ext/EthEstimateGas.ts b/crypto/blockchains/eth/ext/EthEstimateGas.ts new file mode 100644 index 000000000..9d97574b0 --- /dev/null +++ b/crypto/blockchains/eth/ext/EthEstimateGas.ts @@ -0,0 +1,24 @@ +import TransactionController from './estimateGas/index.js'; + +export default async function (provider, gasPrice, from, to, value) { + if (from === to) return '21000'; + + const gasPriceHex = '0x' + (+gasPrice).toString(16); + const valueHex = '0x' + (+value).toString(16); + + const txController = new TransactionController({ + provider: provider, + getGasPrice: gasPriceHex + }); + + const res = await txController.addTxGasDefaults({ + txParams: { + from: from, + to: to, + value: valueHex + }, + history: [{}] + }); + + return parseInt(res.txParams.gas, 16); +} diff --git a/crypto/blockchains/eth/ext/erc1155.js b/crypto/blockchains/eth/ext/erc1155.js index 211250743..215a141ea 100644 --- a/crypto/blockchains/eth/ext/erc1155.js +++ b/crypto/blockchains/eth/ext/erc1155.js @@ -1,106 +1,604 @@ -const ERC1155 = [{ 'inputs': [{ 'internalType': 'string', 'name': '_name', 'type': 'string' }, { 'internalType': 'string', 'name': '_symbol', 'type': 'string' }, { 'internalType': 'address', 'name': '_proxyRegistryAddress', 'type': 'address' }, { 'internalType': 'string', 'name': '_templateURI', 'type': 'string' }, { 'internalType': 'address', 'name': '_migrationAddress', 'type': 'address' }], 'stateMutability': 'nonpayable', 'type': 'constructor' }, { - 'anonymous': false, - 'inputs': [{ 'indexed': true, 'internalType': 'address', 'name': 'account', 'type': 'address' }, { 'indexed': true, 'internalType': 'address', 'name': 'operator', 'type': 'address' }, { 'indexed': false, 'internalType': 'bool', 'name': 'approved', 'type': 'bool' }], - 'name': 'ApprovalForAll', - 'type': 'event' -}, { 'anonymous': false, 'inputs': [{ 'indexed': true, 'internalType': 'uint256', 'name': '_id', 'type': 'uint256' }, { 'indexed': true, 'internalType': 'address', 'name': '_creator', 'type': 'address' }], 'name': 'CreatorChanged', 'type': 'event' }, { 'anonymous': false, 'inputs': [{ 'indexed': false, 'internalType': 'address', 'name': 'userAddress', 'type': 'address' }, { 'indexed': false, 'internalType': 'address payable', 'name': 'relayerAddress', 'type': 'address' }, { 'indexed': false, 'internalType': 'bytes', 'name': 'functionSignature', 'type': 'bytes' }], 'name': 'MetaTransactionExecuted', 'type': 'event' }, { - 'anonymous': false, - 'inputs': [{ 'indexed': true, 'internalType': 'address', 'name': 'previousOwner', 'type': 'address' }, { 'indexed': true, 'internalType': 'address', 'name': 'newOwner', 'type': 'address' }], - 'name': 'OwnershipTransferred', - 'type': 'event' -}, { 'anonymous': false, 'inputs': [{ 'indexed': false, 'internalType': 'address', 'name': 'account', 'type': 'address' }], 'name': 'Paused', 'type': 'event' }, { 'anonymous': false, 'inputs': [{ 'indexed': false, 'internalType': 'string', 'name': '_value', 'type': 'string' }, { 'indexed': true, 'internalType': 'uint256', 'name': '_id', 'type': 'uint256' }], 'name': 'PermanentURI', 'type': 'event' }, { - 'anonymous': false, - 'inputs': [{ 'indexed': true, 'internalType': 'address', 'name': 'operator', 'type': 'address' }, { 'indexed': true, 'internalType': 'address', 'name': 'from', 'type': 'address' }, { 'indexed': true, 'internalType': 'address', 'name': 'to', 'type': 'address' }, { 'indexed': false, 'internalType': 'uint256[]', 'name': 'ids', 'type': 'uint256[]' }, { 'indexed': false, 'internalType': 'uint256[]', 'name': 'values', 'type': 'uint256[]' }], - 'name': 'TransferBatch', - 'type': 'event' -}, { 'anonymous': false, 'inputs': [{ 'indexed': true, 'internalType': 'address', 'name': 'operator', 'type': 'address' }, { 'indexed': true, 'internalType': 'address', 'name': 'from', 'type': 'address' }, { 'indexed': true, 'internalType': 'address', 'name': 'to', 'type': 'address' }, { 'indexed': false, 'internalType': 'uint256', 'name': 'id', 'type': 'uint256' }, { 'indexed': false, 'internalType': 'uint256', 'name': 'value', 'type': 'uint256' }], 'name': 'TransferSingle', 'type': 'event' }, { - 'anonymous': false, - 'inputs': [{ 'indexed': false, 'internalType': 'string', 'name': 'value', 'type': 'string' }, { 'indexed': true, 'internalType': 'uint256', 'name': 'id', 'type': 'uint256' }], - 'name': 'URI', - 'type': 'event' -}, { 'anonymous': false, 'inputs': [{ 'indexed': false, 'internalType': 'address', 'name': 'account', 'type': 'address' }], 'name': 'Unpaused', 'type': 'event' }, { 'inputs': [], 'name': 'ERC712_VERSION', 'outputs': [{ 'internalType': 'string', 'name': '', 'type': 'string' }], 'stateMutability': 'view', 'type': 'function' }, { 'inputs': [{ 'internalType': 'address', 'name': '_address', 'type': 'address' }], 'name': 'addSharedProxyAddress', 'outputs': [], 'stateMutability': 'nonpayable', 'type': 'function' }, { - 'inputs': [{ 'internalType': 'address', 'name': '_owner', 'type': 'address' }, { - 'internalType': 'uint256', - 'name': '_id', - 'type': 'uint256' - }], 'name': 'balanceOf', 'outputs': [{ 'internalType': 'uint256', 'name': '', 'type': 'uint256' }], 'stateMutability': 'view', 'type': 'function' -}, { 'inputs': [{ 'internalType': 'address[]', 'name': 'accounts', 'type': 'address[]' }, { 'internalType': 'uint256[]', 'name': 'ids', 'type': 'uint256[]' }], 'name': 'balanceOfBatch', 'outputs': [{ 'internalType': 'uint256[]', 'name': '', 'type': 'uint256[]' }], 'stateMutability': 'view', 'type': 'function' }, { - 'inputs': [{ 'internalType': 'address', 'name': '_from', 'type': 'address' }, { 'internalType': 'uint256[]', 'name': '_ids', 'type': 'uint256[]' }, { 'internalType': 'uint256[]', 'name': '_quantities', 'type': 'uint256[]' }], - 'name': 'batchBurn', - 'outputs': [], - 'stateMutability': 'nonpayable', - 'type': 'function' -}, { 'inputs': [{ 'internalType': 'address', 'name': '_to', 'type': 'address' }, { 'internalType': 'uint256[]', 'name': '_ids', 'type': 'uint256[]' }, { 'internalType': 'uint256[]', 'name': '_quantities', 'type': 'uint256[]' }, { 'internalType': 'bytes', 'name': '_data', 'type': 'bytes' }], 'name': 'batchMint', 'outputs': [], 'stateMutability': 'nonpayable', 'type': 'function' }, { - 'inputs': [{ 'internalType': 'address', 'name': '_from', 'type': 'address' }, { 'internalType': 'uint256', 'name': '_id', 'type': 'uint256' }, { 'internalType': 'uint256', 'name': '_quantity', 'type': 'uint256' }], - 'name': 'burn', - 'outputs': [], - 'stateMutability': 'nonpayable', - 'type': 'function' -}, { 'inputs': [{ 'internalType': 'uint256', 'name': '_id', 'type': 'uint256' }], 'name': 'creator', 'outputs': [{ 'internalType': 'address', 'name': '', 'type': 'address' }], 'stateMutability': 'view', 'type': 'function' }, { 'inputs': [], 'name': 'disableMigrate', 'outputs': [], 'stateMutability': 'nonpayable', 'type': 'function' }, { - 'inputs': [{ 'internalType': 'address', 'name': 'userAddress', 'type': 'address' }, { 'internalType': 'bytes', 'name': 'functionSignature', 'type': 'bytes' }, { 'internalType': 'bytes32', 'name': 'sigR', 'type': 'bytes32' }, { - 'internalType': 'bytes32', - 'name': 'sigS', - 'type': 'bytes32' - }, { 'internalType': 'uint8', 'name': 'sigV', 'type': 'uint8' }], 'name': 'executeMetaTransaction', 'outputs': [{ 'internalType': 'bytes', 'name': '', 'type': 'bytes' }], 'stateMutability': 'payable', 'type': 'function' -}, { 'inputs': [{ 'internalType': 'uint256', 'name': '_id', 'type': 'uint256' }], 'name': 'exists', 'outputs': [{ 'internalType': 'bool', 'name': '', 'type': 'bool' }], 'stateMutability': 'view', 'type': 'function' }, { 'inputs': [], 'name': 'getChainId', 'outputs': [{ 'internalType': 'uint256', 'name': '', 'type': 'uint256' }], 'stateMutability': 'view', 'type': 'function' }, { 'inputs': [], 'name': 'getDomainSeperator', 'outputs': [{ 'internalType': 'bytes32', 'name': '', 'type': 'bytes32' }], 'stateMutability': 'view', 'type': 'function' }, { - 'inputs': [{ 'internalType': 'address', 'name': 'user', 'type': 'address' }], - 'name': 'getNonce', - 'outputs': [{ 'internalType': 'uint256', 'name': 'nonce', 'type': 'uint256' }], - 'stateMutability': 'view', - 'type': 'function' -}, { 'inputs': [{ 'internalType': 'address', 'name': '_owner', 'type': 'address' }, { 'internalType': 'address', 'name': '_operator', 'type': 'address' }], 'name': 'isApprovedForAll', 'outputs': [{ 'internalType': 'bool', 'name': 'isOperator', 'type': 'bool' }], 'stateMutability': 'view', 'type': 'function' }, { 'inputs': [{ 'internalType': 'uint256', 'name': '_id', 'type': 'uint256' }], 'name': 'isPermanentURI', 'outputs': [{ 'internalType': 'bool', 'name': '', 'type': 'bool' }], 'stateMutability': 'view', 'type': 'function' }, { - 'inputs': [{ 'internalType': 'uint256', 'name': '_id', 'type': 'uint256' }], - 'name': 'maxSupply', - 'outputs': [{ 'internalType': 'uint256', 'name': '', 'type': 'uint256' }], - 'stateMutability': 'pure', - 'type': 'function' -}, { 'inputs': [{ 'components': [{ 'internalType': 'uint256', 'name': 'id', 'type': 'uint256' }, { 'internalType': 'address', 'name': 'owner', 'type': 'address' }], 'internalType': 'struct AssetContractShared.Ownership[]', 'name': '_ownerships', 'type': 'tuple[]' }], 'name': 'migrate', 'outputs': [], 'stateMutability': 'nonpayable', 'type': 'function' }, { 'inputs': [], 'name': 'migrationTarget', 'outputs': [{ 'internalType': 'contract AssetContractShared', 'name': '', 'type': 'address' }], 'stateMutability': 'view', 'type': 'function' }, { - 'inputs': [{ - 'internalType': 'address', - 'name': '_to', - 'type': 'address' - }, { 'internalType': 'uint256', 'name': '_id', 'type': 'uint256' }, { 'internalType': 'uint256', 'name': '_quantity', 'type': 'uint256' }, { 'internalType': 'bytes', 'name': '_data', 'type': 'bytes' }], 'name': 'mint', 'outputs': [], 'stateMutability': 'nonpayable', 'type': 'function' -}, { 'inputs': [], 'name': 'name', 'outputs': [{ 'internalType': 'string', 'name': '', 'type': 'string' }], 'stateMutability': 'view', 'type': 'function' }, { 'inputs': [], 'name': 'openSeaVersion', 'outputs': [{ 'internalType': 'string', 'name': '', 'type': 'string' }], 'stateMutability': 'pure', 'type': 'function' }, { 'inputs': [], 'name': 'owner', 'outputs': [{ 'internalType': 'address', 'name': '', 'type': 'address' }], 'stateMutability': 'view', 'type': 'function' }, { 'inputs': [], 'name': 'pause', 'outputs': [], 'stateMutability': 'nonpayable', 'type': 'function' }, { - 'inputs': [], - 'name': 'paused', - 'outputs': [{ 'internalType': 'bool', 'name': '', 'type': 'bool' }], - 'stateMutability': 'view', - 'type': 'function' -}, { 'inputs': [], 'name': 'proxyRegistryAddress', 'outputs': [{ 'internalType': 'address', 'name': '', 'type': 'address' }], 'stateMutability': 'view', 'type': 'function' }, { 'inputs': [{ 'internalType': 'address', 'name': '_address', 'type': 'address' }], 'name': 'removeSharedProxyAddress', 'outputs': [], 'stateMutability': 'nonpayable', 'type': 'function' }, { 'inputs': [], 'name': 'renounceOwnership', 'outputs': [], 'stateMutability': 'nonpayable', 'type': 'function' }, { - 'inputs': [{ 'internalType': 'address', 'name': '_from', 'type': 'address' }, { - 'internalType': 'address', - 'name': '_to', - 'type': 'address' - }, { 'internalType': 'uint256[]', 'name': '_ids', 'type': 'uint256[]' }, { 'internalType': 'uint256[]', 'name': '_amounts', 'type': 'uint256[]' }, { 'internalType': 'bytes', 'name': '_data', 'type': 'bytes' }], 'name': 'safeBatchTransferFrom', 'outputs': [], 'stateMutability': 'nonpayable', 'type': 'function' -}, { 'inputs': [{ 'internalType': 'address', 'name': '_from', 'type': 'address' }, { 'internalType': 'address', 'name': '_to', 'type': 'address' }, { 'internalType': 'uint256', 'name': '_id', 'type': 'uint256' }, { 'internalType': 'uint256', 'name': '_amount', 'type': 'uint256' }, { 'internalType': 'bytes', 'name': '_data', 'type': 'bytes' }], 'name': 'safeTransferFrom', 'outputs': [], 'stateMutability': 'nonpayable', 'type': 'function' }, { - 'inputs': [{ 'internalType': 'address', 'name': 'operator', 'type': 'address' }, { 'internalType': 'bool', 'name': 'approved', 'type': 'bool' }], - 'name': 'setApprovalForAll', - 'outputs': [], - 'stateMutability': 'nonpayable', - 'type': 'function' -}, { 'inputs': [{ 'internalType': 'uint256', 'name': '_id', 'type': 'uint256' }, { 'internalType': 'address', 'name': '_to', 'type': 'address' }], 'name': 'setCreator', 'outputs': [], 'stateMutability': 'nonpayable', 'type': 'function' }, { 'inputs': [{ 'internalType': 'uint256', 'name': '_id', 'type': 'uint256' }, { 'internalType': 'string', 'name': '_uri', 'type': 'string' }], 'name': 'setPermanentURI', 'outputs': [], 'stateMutability': 'nonpayable', 'type': 'function' }, { - 'inputs': [{ 'internalType': 'address', 'name': '_address', 'type': 'address' }], - 'name': 'setProxyRegistryAddress', - 'outputs': [], - 'stateMutability': 'nonpayable', - 'type': 'function' -}, { 'inputs': [{ 'internalType': 'string', 'name': '_uri', 'type': 'string' }], 'name': 'setTemplateURI', 'outputs': [], 'stateMutability': 'nonpayable', 'type': 'function' }, { 'inputs': [{ 'internalType': 'uint256', 'name': '_id', 'type': 'uint256' }, { 'internalType': 'string', 'name': '_uri', 'type': 'string' }], 'name': 'setURI', 'outputs': [], 'stateMutability': 'nonpayable', 'type': 'function' }, { 'inputs': [{ 'internalType': 'address', 'name': '', 'type': 'address' }], 'name': 'sharedProxyAddresses', 'outputs': [{ 'internalType': 'bool', 'name': '', 'type': 'bool' }], 'stateMutability': 'view', 'type': 'function' }, { - 'inputs': [], - 'name': 'supportsFactoryInterface', - 'outputs': [{ 'internalType': 'bool', 'name': '', 'type': 'bool' }], - 'stateMutability': 'pure', - 'type': 'function' -}, { 'inputs': [{ 'internalType': 'bytes4', 'name': 'interfaceId', 'type': 'bytes4' }], 'name': 'supportsInterface', 'outputs': [{ 'internalType': 'bool', 'name': '', 'type': 'bool' }], 'stateMutability': 'view', 'type': 'function' }, { 'inputs': [], 'name': 'symbol', 'outputs': [{ 'internalType': 'string', 'name': '', 'type': 'string' }], 'stateMutability': 'view', 'type': 'function' }, { 'inputs': [], 'name': 'templateURI', 'outputs': [{ 'internalType': 'string', 'name': '', 'type': 'string' }], 'stateMutability': 'view', 'type': 'function' }, { - 'inputs': [{ 'internalType': 'uint256', 'name': '_id', 'type': 'uint256' }], - 'name': 'totalSupply', - 'outputs': [{ 'internalType': 'uint256', 'name': '', 'type': 'uint256' }], - 'stateMutability': 'view', - 'type': 'function' -}, { 'inputs': [{ 'internalType': 'address', 'name': 'newOwner', 'type': 'address' }], 'name': 'transferOwnership', 'outputs': [], 'stateMutability': 'nonpayable', 'type': 'function' }, { 'inputs': [], 'name': 'unpause', 'outputs': [], 'stateMutability': 'nonpayable', 'type': 'function' }, { 'inputs': [{ 'internalType': 'uint256', 'name': '_id', 'type': 'uint256' }], 'name': 'uri', 'outputs': [{ 'internalType': 'string', 'name': '', 'type': 'string' }], 'stateMutability': 'view', 'type': 'function' }] - -/** *********************Exports begin******************************************/ - -module.exports = { - ERC1155 -} -/** ********************Exports end*********************************************/ +const ERC1155 = [ + { + inputs: [ + { internalType: 'string', name: '_name', type: 'string' }, + { internalType: 'string', name: '_symbol', type: 'string' }, + { + internalType: 'address', + name: '_proxyRegistryAddress', + type: 'address' + }, + { internalType: 'string', name: '_templateURI', type: 'string' }, + { internalType: 'address', name: '_migrationAddress', type: 'address' } + ], + stateMutability: 'nonpayable', + type: 'constructor' + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'account', + type: 'address' + }, + { + indexed: true, + internalType: 'address', + name: 'operator', + type: 'address' + }, + { indexed: false, internalType: 'bool', name: 'approved', type: 'bool' } + ], + name: 'ApprovalForAll', + type: 'event' + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: 'uint256', name: '_id', type: 'uint256' }, + { + indexed: true, + internalType: 'address', + name: '_creator', + type: 'address' + } + ], + name: 'CreatorChanged', + type: 'event' + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'address', + name: 'userAddress', + type: 'address' + }, + { + indexed: false, + internalType: 'address payable', + name: 'relayerAddress', + type: 'address' + }, + { + indexed: false, + internalType: 'bytes', + name: 'functionSignature', + type: 'bytes' + } + ], + name: 'MetaTransactionExecuted', + type: 'event' + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'previousOwner', + type: 'address' + }, + { + indexed: true, + internalType: 'address', + name: 'newOwner', + type: 'address' + } + ], + name: 'OwnershipTransferred', + type: 'event' + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'address', + name: 'account', + type: 'address' + } + ], + name: 'Paused', + type: 'event' + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'string', + name: '_value', + type: 'string' + }, + { indexed: true, internalType: 'uint256', name: '_id', type: 'uint256' } + ], + name: 'PermanentURI', + type: 'event' + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'operator', + type: 'address' + }, + { indexed: true, internalType: 'address', name: 'from', type: 'address' }, + { indexed: true, internalType: 'address', name: 'to', type: 'address' }, + { + indexed: false, + internalType: 'uint256[]', + name: 'ids', + type: 'uint256[]' + }, + { + indexed: false, + internalType: 'uint256[]', + name: 'values', + type: 'uint256[]' + } + ], + name: 'TransferBatch', + type: 'event' + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'operator', + type: 'address' + }, + { indexed: true, internalType: 'address', name: 'from', type: 'address' }, + { indexed: true, internalType: 'address', name: 'to', type: 'address' }, + { indexed: false, internalType: 'uint256', name: 'id', type: 'uint256' }, + { + indexed: false, + internalType: 'uint256', + name: 'value', + type: 'uint256' + } + ], + name: 'TransferSingle', + type: 'event' + }, + { + anonymous: false, + inputs: [ + { indexed: false, internalType: 'string', name: 'value', type: 'string' }, + { indexed: true, internalType: 'uint256', name: 'id', type: 'uint256' } + ], + name: 'URI', + type: 'event' + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'address', + name: 'account', + type: 'address' + } + ], + name: 'Unpaused', + type: 'event' + }, + { + inputs: [], + name: 'ERC712_VERSION', + outputs: [{ internalType: 'string', name: '', type: 'string' }], + stateMutability: 'view', + type: 'function' + }, + { + inputs: [{ internalType: 'address', name: '_address', type: 'address' }], + name: 'addSharedProxyAddress', + outputs: [], + stateMutability: 'nonpayable', + type: 'function' + }, + { + inputs: [ + { internalType: 'address', name: '_owner', type: 'address' }, + { + internalType: 'uint256', + name: '_id', + type: 'uint256' + } + ], + name: 'balanceOf', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function' + }, + { + inputs: [ + { internalType: 'address[]', name: 'accounts', type: 'address[]' }, + { internalType: 'uint256[]', name: 'ids', type: 'uint256[]' } + ], + name: 'balanceOfBatch', + outputs: [{ internalType: 'uint256[]', name: '', type: 'uint256[]' }], + stateMutability: 'view', + type: 'function' + }, + { + inputs: [ + { internalType: 'address', name: '_from', type: 'address' }, + { internalType: 'uint256[]', name: '_ids', type: 'uint256[]' }, + { internalType: 'uint256[]', name: '_quantities', type: 'uint256[]' } + ], + name: 'batchBurn', + outputs: [], + stateMutability: 'nonpayable', + type: 'function' + }, + { + inputs: [ + { internalType: 'address', name: '_to', type: 'address' }, + { internalType: 'uint256[]', name: '_ids', type: 'uint256[]' }, + { internalType: 'uint256[]', name: '_quantities', type: 'uint256[]' }, + { internalType: 'bytes', name: '_data', type: 'bytes' } + ], + name: 'batchMint', + outputs: [], + stateMutability: 'nonpayable', + type: 'function' + }, + { + inputs: [ + { internalType: 'address', name: '_from', type: 'address' }, + { internalType: 'uint256', name: '_id', type: 'uint256' }, + { internalType: 'uint256', name: '_quantity', type: 'uint256' } + ], + name: 'burn', + outputs: [], + stateMutability: 'nonpayable', + type: 'function' + }, + { + inputs: [{ internalType: 'uint256', name: '_id', type: 'uint256' }], + name: 'creator', + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function' + }, + { + inputs: [], + name: 'disableMigrate', + outputs: [], + stateMutability: 'nonpayable', + type: 'function' + }, + { + inputs: [ + { internalType: 'address', name: 'userAddress', type: 'address' }, + { internalType: 'bytes', name: 'functionSignature', type: 'bytes' }, + { internalType: 'bytes32', name: 'sigR', type: 'bytes32' }, + { + internalType: 'bytes32', + name: 'sigS', + type: 'bytes32' + }, + { internalType: 'uint8', name: 'sigV', type: 'uint8' } + ], + name: 'executeMetaTransaction', + outputs: [{ internalType: 'bytes', name: '', type: 'bytes' }], + stateMutability: 'payable', + type: 'function' + }, + { + inputs: [{ internalType: 'uint256', name: '_id', type: 'uint256' }], + name: 'exists', + outputs: [{ internalType: 'bool', name: '', type: 'bool' }], + stateMutability: 'view', + type: 'function' + }, + { + inputs: [], + name: 'getChainId', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function' + }, + { + inputs: [], + name: 'getDomainSeperator', + outputs: [{ internalType: 'bytes32', name: '', type: 'bytes32' }], + stateMutability: 'view', + type: 'function' + }, + { + inputs: [{ internalType: 'address', name: 'user', type: 'address' }], + name: 'getNonce', + outputs: [{ internalType: 'uint256', name: 'nonce', type: 'uint256' }], + stateMutability: 'view', + type: 'function' + }, + { + inputs: [ + { internalType: 'address', name: '_owner', type: 'address' }, + { internalType: 'address', name: '_operator', type: 'address' } + ], + name: 'isApprovedForAll', + outputs: [{ internalType: 'bool', name: 'isOperator', type: 'bool' }], + stateMutability: 'view', + type: 'function' + }, + { + inputs: [{ internalType: 'uint256', name: '_id', type: 'uint256' }], + name: 'isPermanentURI', + outputs: [{ internalType: 'bool', name: '', type: 'bool' }], + stateMutability: 'view', + type: 'function' + }, + { + inputs: [{ internalType: 'uint256', name: '_id', type: 'uint256' }], + name: 'maxSupply', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'pure', + type: 'function' + }, + { + inputs: [ + { + components: [ + { internalType: 'uint256', name: 'id', type: 'uint256' }, + { internalType: 'address', name: 'owner', type: 'address' } + ], + internalType: 'struct AssetContractShared.Ownership[]', + name: '_ownerships', + type: 'tuple[]' + } + ], + name: 'migrate', + outputs: [], + stateMutability: 'nonpayable', + type: 'function' + }, + { + inputs: [], + name: 'migrationTarget', + outputs: [ + { + internalType: 'contract AssetContractShared', + name: '', + type: 'address' + } + ], + stateMutability: 'view', + type: 'function' + }, + { + inputs: [ + { + internalType: 'address', + name: '_to', + type: 'address' + }, + { internalType: 'uint256', name: '_id', type: 'uint256' }, + { internalType: 'uint256', name: '_quantity', type: 'uint256' }, + { internalType: 'bytes', name: '_data', type: 'bytes' } + ], + name: 'mint', + outputs: [], + stateMutability: 'nonpayable', + type: 'function' + }, + { + inputs: [], + name: 'name', + outputs: [{ internalType: 'string', name: '', type: 'string' }], + stateMutability: 'view', + type: 'function' + }, + { + inputs: [], + name: 'openSeaVersion', + outputs: [{ internalType: 'string', name: '', type: 'string' }], + stateMutability: 'pure', + type: 'function' + }, + { + inputs: [], + name: 'owner', + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function' + }, + { + inputs: [], + name: 'pause', + outputs: [], + stateMutability: 'nonpayable', + type: 'function' + }, + { + inputs: [], + name: 'paused', + outputs: [{ internalType: 'bool', name: '', type: 'bool' }], + stateMutability: 'view', + type: 'function' + }, + { + inputs: [], + name: 'proxyRegistryAddress', + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function' + }, + { + inputs: [{ internalType: 'address', name: '_address', type: 'address' }], + name: 'removeSharedProxyAddress', + outputs: [], + stateMutability: 'nonpayable', + type: 'function' + }, + { + inputs: [], + name: 'renounceOwnership', + outputs: [], + stateMutability: 'nonpayable', + type: 'function' + }, + { + inputs: [ + { internalType: 'address', name: '_from', type: 'address' }, + { + internalType: 'address', + name: '_to', + type: 'address' + }, + { internalType: 'uint256[]', name: '_ids', type: 'uint256[]' }, + { internalType: 'uint256[]', name: '_amounts', type: 'uint256[]' }, + { internalType: 'bytes', name: '_data', type: 'bytes' } + ], + name: 'safeBatchTransferFrom', + outputs: [], + stateMutability: 'nonpayable', + type: 'function' + }, + { + inputs: [ + { internalType: 'address', name: '_from', type: 'address' }, + { internalType: 'address', name: '_to', type: 'address' }, + { internalType: 'uint256', name: '_id', type: 'uint256' }, + { internalType: 'uint256', name: '_amount', type: 'uint256' }, + { internalType: 'bytes', name: '_data', type: 'bytes' } + ], + name: 'safeTransferFrom', + outputs: [], + stateMutability: 'nonpayable', + type: 'function' + }, + { + inputs: [ + { internalType: 'address', name: 'operator', type: 'address' }, + { internalType: 'bool', name: 'approved', type: 'bool' } + ], + name: 'setApprovalForAll', + outputs: [], + stateMutability: 'nonpayable', + type: 'function' + }, + { + inputs: [ + { internalType: 'uint256', name: '_id', type: 'uint256' }, + { internalType: 'address', name: '_to', type: 'address' } + ], + name: 'setCreator', + outputs: [], + stateMutability: 'nonpayable', + type: 'function' + }, + { + inputs: [ + { internalType: 'uint256', name: '_id', type: 'uint256' }, + { internalType: 'string', name: '_uri', type: 'string' } + ], + name: 'setPermanentURI', + outputs: [], + stateMutability: 'nonpayable', + type: 'function' + }, + { + inputs: [{ internalType: 'address', name: '_address', type: 'address' }], + name: 'setProxyRegistryAddress', + outputs: [], + stateMutability: 'nonpayable', + type: 'function' + }, + { + inputs: [{ internalType: 'string', name: '_uri', type: 'string' }], + name: 'setTemplateURI', + outputs: [], + stateMutability: 'nonpayable', + type: 'function' + }, + { + inputs: [ + { internalType: 'uint256', name: '_id', type: 'uint256' }, + { internalType: 'string', name: '_uri', type: 'string' } + ], + name: 'setURI', + outputs: [], + stateMutability: 'nonpayable', + type: 'function' + }, + { + inputs: [{ internalType: 'address', name: '', type: 'address' }], + name: 'sharedProxyAddresses', + outputs: [{ internalType: 'bool', name: '', type: 'bool' }], + stateMutability: 'view', + type: 'function' + }, + { + inputs: [], + name: 'supportsFactoryInterface', + outputs: [{ internalType: 'bool', name: '', type: 'bool' }], + stateMutability: 'pure', + type: 'function' + }, + { + inputs: [{ internalType: 'bytes4', name: 'interfaceId', type: 'bytes4' }], + name: 'supportsInterface', + outputs: [{ internalType: 'bool', name: '', type: 'bool' }], + stateMutability: 'view', + type: 'function' + }, + { + inputs: [], + name: 'symbol', + outputs: [{ internalType: 'string', name: '', type: 'string' }], + stateMutability: 'view', + type: 'function' + }, + { + inputs: [], + name: 'templateURI', + outputs: [{ internalType: 'string', name: '', type: 'string' }], + stateMutability: 'view', + type: 'function' + }, + { + inputs: [{ internalType: 'uint256', name: '_id', type: 'uint256' }], + name: 'totalSupply', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function' + }, + { + inputs: [{ internalType: 'address', name: 'newOwner', type: 'address' }], + name: 'transferOwnership', + outputs: [], + stateMutability: 'nonpayable', + type: 'function' + }, + { + inputs: [], + name: 'unpause', + outputs: [], + stateMutability: 'nonpayable', + type: 'function' + }, + { + inputs: [{ internalType: 'uint256', name: '_id', type: 'uint256' }], + name: 'uri', + outputs: [{ internalType: 'string', name: '', type: 'string' }], + stateMutability: 'view', + type: 'function' + } +]; + +/** *********************Exports begin******************************************/ + +export default { + ERC1155 +}; +/** ********************Exports end*********************************************/ diff --git a/crypto/blockchains/eth/ext/erc20.js b/crypto/blockchains/eth/ext/erc20.js index 4eb0ed99d..b6d4a7dbb 100644 --- a/crypto/blockchains/eth/ext/erc20.js +++ b/crypto/blockchains/eth/ext/erc20.js @@ -1,237 +1,237 @@ const ERC20 = [ - { - "constant": true, - "inputs": [], - "name": "name", - "outputs": [ - { - "name": "", - "type": "string" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "decimals", - "outputs": [ - { - "name": "", - "type": "uint8" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "symbol", - "outputs": [ - { - "name": "", - "type": "string" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - 'constant': true, - 'inputs': [], - 'name': 'totalSupply', - 'outputs': [ - { - 'name': '', - 'type': 'uint256' - } - ], - 'payable': false, - 'stateMutability': 'view', - 'type': 'function' - }, - { - 'constant': true, - 'inputs': [], - 'name': 'decimals', - 'outputs': [ - { - 'name': '', - 'type': 'uint8' - } - ], - 'payable': false, - 'stateMutability': 'view', - 'type': 'function' - }, - { - 'anonymous': false, - 'inputs': [ - { - 'indexed': true, - 'name': 'from', - 'type': 'address' - }, - { - 'indexed': true, - 'name': 'to', - 'type': 'address' - }, - { - 'indexed': false, - 'name': 'value', - 'type': 'uint256' - } - ], - 'name': 'Transfer', - 'type': 'event' - }, - { - 'anonymous': false, - 'inputs': [ - { - 'indexed': true, - 'name': 'owner', - 'type': 'address' - }, - { - 'indexed': true, - 'name': 'spender', - 'type': 'address' - }, - { - 'indexed': false, - 'name': 'value', - 'type': 'uint256' - } - ], - 'name': 'Approval', - 'type': 'event' - }, - { - 'constant': true, - 'inputs': [ - { - 'name': 'who', - 'type': 'address' - } - ], - 'name': 'balanceOf', - 'outputs': [ - { - 'name': '', - 'type': 'uint256' - } - ], - 'payable': false, - 'stateMutability': 'view', - 'type': 'function' - }, - { - 'constant': true, - 'inputs': [ - { - 'name': 'owner', - 'type': 'address' - }, - { - 'name': 'spender', - 'type': 'address' - } - ], - 'name': 'allowance', - 'outputs': [ - { - 'name': '', - 'type': 'uint256' - } - ], - 'payable': false, - 'stateMutability': 'view', - 'type': 'function' - }, - { - 'constant': false, - 'inputs': [ - { - 'name': 'to', - 'type': 'address' - }, - { - 'name': 'value', - 'type': 'uint256' - } - ], - 'name': 'transfer', - 'outputs': [ - { - 'name': 'ok', - 'type': 'bool' - } - ], - 'payable': false, - 'stateMutability': 'nonpayable', - 'type': 'function' - }, - { - 'constant': false, - 'inputs': [ - { - 'name': 'from', - 'type': 'address' - }, - { - 'name': 'to', - 'type': 'address' - }, - { - 'name': 'value', - 'type': 'uint256' - } - ], - 'name': 'transferFrom', - 'outputs': [ - { - 'name': 'ok', - 'type': 'bool' - } - ], - 'payable': false, - 'stateMutability': 'nonpayable', - 'type': 'function' - }, - { - 'constant': false, - 'inputs': [ - { - 'name': 'spender', - 'type': 'address' - }, - { - 'name': 'value', - 'type': 'uint256' - } - ], - 'name': 'approve', - 'outputs': [ - { - 'name': 'ok', - 'type': 'bool' - } - ], - 'payable': false, - 'stateMutability': 'nonpayable', - 'type': 'function' - } -] + { + constant: true, + inputs: [], + name: 'name', + outputs: [ + { + name: '', + type: 'string' + } + ], + payable: false, + stateMutability: 'view', + type: 'function' + }, + { + constant: true, + inputs: [], + name: 'decimals', + outputs: [ + { + name: '', + type: 'uint8' + } + ], + payable: false, + stateMutability: 'view', + type: 'function' + }, + { + constant: true, + inputs: [], + name: 'symbol', + outputs: [ + { + name: '', + type: 'string' + } + ], + payable: false, + stateMutability: 'view', + type: 'function' + }, + { + constant: true, + inputs: [], + name: 'totalSupply', + outputs: [ + { + name: '', + type: 'uint256' + } + ], + payable: false, + stateMutability: 'view', + type: 'function' + }, + { + constant: true, + inputs: [], + name: 'decimals', + outputs: [ + { + name: '', + type: 'uint8' + } + ], + payable: false, + stateMutability: 'view', + type: 'function' + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + name: 'from', + type: 'address' + }, + { + indexed: true, + name: 'to', + type: 'address' + }, + { + indexed: false, + name: 'value', + type: 'uint256' + } + ], + name: 'Transfer', + type: 'event' + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + name: 'owner', + type: 'address' + }, + { + indexed: true, + name: 'spender', + type: 'address' + }, + { + indexed: false, + name: 'value', + type: 'uint256' + } + ], + name: 'Approval', + type: 'event' + }, + { + constant: true, + inputs: [ + { + name: 'who', + type: 'address' + } + ], + name: 'balanceOf', + outputs: [ + { + name: '', + type: 'uint256' + } + ], + payable: false, + stateMutability: 'view', + type: 'function' + }, + { + constant: true, + inputs: [ + { + name: 'owner', + type: 'address' + }, + { + name: 'spender', + type: 'address' + } + ], + name: 'allowance', + outputs: [ + { + name: '', + type: 'uint256' + } + ], + payable: false, + stateMutability: 'view', + type: 'function' + }, + { + constant: false, + inputs: [ + { + name: 'to', + type: 'address' + }, + { + name: 'value', + type: 'uint256' + } + ], + name: 'transfer', + outputs: [ + { + name: 'ok', + type: 'bool' + } + ], + payable: false, + stateMutability: 'nonpayable', + type: 'function' + }, + { + constant: false, + inputs: [ + { + name: 'from', + type: 'address' + }, + { + name: 'to', + type: 'address' + }, + { + name: 'value', + type: 'uint256' + } + ], + name: 'transferFrom', + outputs: [ + { + name: 'ok', + type: 'bool' + } + ], + payable: false, + stateMutability: 'nonpayable', + type: 'function' + }, + { + constant: false, + inputs: [ + { + name: 'spender', + type: 'address' + }, + { + name: 'value', + type: 'uint256' + } + ], + name: 'approve', + outputs: [ + { + name: 'ok', + type: 'bool' + } + ], + payable: false, + stateMutability: 'nonpayable', + type: 'function' + } +]; /** *********************Exports begin******************************************/ -module.exports = { - ERC20 -} +export default { + ERC20 +}; /** ********************Exports end*********************************************/ diff --git a/crypto/blockchains/eth/ext/erc721.js b/crypto/blockchains/eth/ext/erc721.js index 57349a691..77e53de47 100644 --- a/crypto/blockchains/eth/ext/erc721.js +++ b/crypto/blockchains/eth/ext/erc721.js @@ -1,61 +1,289 @@ -const ERC721 = [ - { 'inputs': [], 'stateMutability': 'nonpayable', 'type': 'constructor' }, { 'anonymous': false, 'inputs': [{ 'indexed': true, 'internalType': 'address', 'name': 'owner', 'type': 'address' }, { 'indexed': true, 'internalType': 'address', 'name': 'approved', 'type': 'address' }, { 'indexed': true, 'internalType': 'uint256', 'name': 'tokenId', 'type': 'uint256' }], 'name': 'Approval', 'type': 'event' }, { - 'anonymous': false, - 'inputs': [{ 'indexed': true, 'internalType': 'address', 'name': 'owner', 'type': 'address' }, { 'indexed': true, 'internalType': 'address', 'name': 'operator', 'type': 'address' }, { 'indexed': false, 'internalType': 'bool', 'name': 'approved', 'type': 'bool' }], - 'name': 'ApprovalForAll', - 'type': 'event' - }, { 'anonymous': false, 'inputs': [{ 'indexed': true, 'internalType': 'address', 'name': 'previousOwner', 'type': 'address' }, { 'indexed': true, 'internalType': 'address', 'name': 'newOwner', 'type': 'address' }], 'name': 'OwnershipTransferred', 'type': 'event' }, { 'anonymous': false, 'inputs': [{ 'indexed': true, 'internalType': 'address', 'name': 'from', 'type': 'address' }, { 'indexed': true, 'internalType': 'address', 'name': 'to', 'type': 'address' }, { 'indexed': true, 'internalType': 'uint256', 'name': 'tokenId', 'type': 'uint256' }], 'name': 'Transfer', 'type': 'event' }, { - 'inputs': [], - 'name': 'BASE_TOKEN_URI', - 'outputs': [{ 'internalType': 'string', 'name': '', 'type': 'string' }], - 'stateMutability': 'view', - 'type': 'function' - }, { 'inputs': [], 'name': 'BASE_TOKEN_URI_AFTERPARTY', 'outputs': [{ 'internalType': 'string', 'name': '', 'type': 'string' }], 'stateMutability': 'view', 'type': 'function' }, { 'inputs': [], 'name': 'BASE_TOKEN_URI_VIP', 'outputs': [{ 'internalType': 'string', 'name': '', 'type': 'string' }], 'stateMutability': 'view', 'type': 'function' }, { 'inputs': [], 'name': 'MAX_AFTERPARTY', 'outputs': [{ 'internalType': 'uint256', 'name': '', 'type': 'uint256' }], 'stateMutability': 'view', 'type': 'function' }, { - 'inputs': [], - 'name': 'MAX_NUMBER', - 'outputs': [{ 'internalType': 'uint256', 'name': '', 'type': 'uint256' }], - 'stateMutability': 'view', - 'type': 'function' - }, { 'inputs': [], 'name': 'MAX_VIP', 'outputs': [{ 'internalType': 'uint256', 'name': '', 'type': 'uint256' }], 'stateMutability': 'view', 'type': 'function' }, { 'inputs': [{ 'internalType': 'address', 'name': 'to', 'type': 'address' }, { 'internalType': 'uint256', 'name': 'tokenId', 'type': 'uint256' }], 'name': 'approve', 'outputs': [], 'stateMutability': 'nonpayable', 'type': 'function' }, { 'inputs': [{ 'internalType': 'address', 'name': 'owner', 'type': 'address' }], 'name': 'balanceOf', 'outputs': [{ 'internalType': 'uint256', 'name': '', 'type': 'uint256' }], 'stateMutability': 'view', 'type': 'function' }, { - 'inputs': [], - 'name': 'contractOwner', - 'outputs': [{ 'internalType': 'address', 'name': '', 'type': 'address' }], - 'stateMutability': 'view', - 'type': 'function' - }, { 'inputs': [], 'name': 'currentTokenAmount', 'outputs': [{ 'internalType': 'uint256', 'name': '', 'type': 'uint256' }], 'stateMutability': 'view', 'type': 'function' }, { 'inputs': [{ 'internalType': 'uint256', 'name': 'tokenId', 'type': 'uint256' }], 'name': 'getApproved', 'outputs': [{ 'internalType': 'address', 'name': '', 'type': 'address' }], 'stateMutability': 'view', 'type': 'function' }, { - 'inputs': [{ 'internalType': 'address', 'name': 'owner', 'type': 'address' }, { 'internalType': 'address', 'name': 'operator', 'type': 'address' }], - 'name': 'isApprovedForAll', - 'outputs': [{ 'internalType': 'bool', 'name': '', 'type': 'bool' }], - 'stateMutability': 'view', - 'type': 'function' - }, { 'inputs': [{ 'internalType': 'address', 'name': 'to', 'type': 'address' }, { 'internalType': 'uint256', 'name': 'tokenId', 'type': 'uint256' }, { 'internalType': 'uint256', 'name': 'ticketType', 'type': 'uint256' }], 'name': 'mint', 'outputs': [], 'stateMutability': 'nonpayable', 'type': 'function' }, { 'inputs': [], 'name': 'name', 'outputs': [{ 'internalType': 'string', 'name': '', 'type': 'string' }], 'stateMutability': 'view', 'type': 'function' }, { - 'inputs': [], - 'name': 'owner', - 'outputs': [{ 'internalType': 'address', 'name': '', 'type': 'address' }], - 'stateMutability': 'view', - 'type': 'function' - }, { 'inputs': [{ 'internalType': 'uint256', 'name': 'tokenId', 'type': 'uint256' }], 'name': 'ownerOf', 'outputs': [{ 'internalType': 'address', 'name': '', 'type': 'address' }], 'stateMutability': 'view', 'type': 'function' }, { 'inputs': [], 'name': 'renounceOwnership', 'outputs': [], 'stateMutability': 'nonpayable', 'type': 'function' }, { - 'inputs': [{ 'internalType': 'address', 'name': 'from', 'type': 'address' }, { 'internalType': 'address', 'name': 'to', 'type': 'address' }, { 'internalType': 'uint256', 'name': 'tokenId', 'type': 'uint256' }], - 'name': 'safeTransferFrom', - 'outputs': [], - 'stateMutability': 'nonpayable', - 'type': 'function' - }, { 'inputs': [{ 'internalType': 'address', 'name': 'from', 'type': 'address' }, { 'internalType': 'address', 'name': 'to', 'type': 'address' }, { 'internalType': 'uint256', 'name': 'tokenId', 'type': 'uint256' }, { 'internalType': 'bytes', 'name': '_data', 'type': 'bytes' }], 'name': 'safeTransferFrom', 'outputs': [], 'stateMutability': 'nonpayable', 'type': 'function' }, { - 'inputs': [{ 'internalType': 'address', 'name': 'operator', 'type': 'address' }, { 'internalType': 'bool', 'name': 'approved', 'type': 'bool' }], - 'name': 'setApprovalForAll', - 'outputs': [], - 'stateMutability': 'nonpayable', - 'type': 'function' - }, { 'inputs': [{ 'internalType': 'bytes4', 'name': 'interfaceId', 'type': 'bytes4' }], 'name': 'supportsInterface', 'outputs': [{ 'internalType': 'bool', 'name': '', 'type': 'bool' }], 'stateMutability': 'view', 'type': 'function' }, { 'inputs': [], 'name': 'symbol', 'outputs': [{ 'internalType': 'string', 'name': '', 'type': 'string' }], 'stateMutability': 'view', 'type': 'function' }, { - 'inputs': [{ 'internalType': 'uint256', 'name': 'tokenId', 'type': 'uint256' }], - 'name': 'tokenURI', - 'outputs': [{ 'internalType': 'string', 'name': '', 'type': 'string' }], - 'stateMutability': 'view', - 'type': 'function' - }, { 'inputs': [{ 'internalType': 'address', 'name': 'from', 'type': 'address' }, { 'internalType': 'address', 'name': 'to', 'type': 'address' }, { 'internalType': 'uint256', 'name': 'tokenId', 'type': 'uint256' }], 'name': 'transferFrom', 'outputs': [], 'stateMutability': 'nonpayable', 'type': 'function' }, { 'inputs': [{ 'internalType': 'address', 'name': 'newOwner', 'type': 'address' }], 'name': 'transferOwnership', 'outputs': [], 'stateMutability': 'nonpayable', 'type': 'function' }] -/** *********************Exports begin******************************************/ - -module.exports = { - ERC721 -} -/** ********************Exports end*********************************************/ +const ERC721 = [ + { inputs: [], stateMutability: 'nonpayable', type: 'constructor' }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'owner', + type: 'address' + }, + { + indexed: true, + internalType: 'address', + name: 'approved', + type: 'address' + }, + { + indexed: true, + internalType: 'uint256', + name: 'tokenId', + type: 'uint256' + } + ], + name: 'Approval', + type: 'event' + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'owner', + type: 'address' + }, + { + indexed: true, + internalType: 'address', + name: 'operator', + type: 'address' + }, + { indexed: false, internalType: 'bool', name: 'approved', type: 'bool' } + ], + name: 'ApprovalForAll', + type: 'event' + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'previousOwner', + type: 'address' + }, + { + indexed: true, + internalType: 'address', + name: 'newOwner', + type: 'address' + } + ], + name: 'OwnershipTransferred', + type: 'event' + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: 'address', name: 'from', type: 'address' }, + { indexed: true, internalType: 'address', name: 'to', type: 'address' }, + { + indexed: true, + internalType: 'uint256', + name: 'tokenId', + type: 'uint256' + } + ], + name: 'Transfer', + type: 'event' + }, + { + inputs: [], + name: 'BASE_TOKEN_URI', + outputs: [{ internalType: 'string', name: '', type: 'string' }], + stateMutability: 'view', + type: 'function' + }, + { + inputs: [], + name: 'BASE_TOKEN_URI_AFTERPARTY', + outputs: [{ internalType: 'string', name: '', type: 'string' }], + stateMutability: 'view', + type: 'function' + }, + { + inputs: [], + name: 'BASE_TOKEN_URI_VIP', + outputs: [{ internalType: 'string', name: '', type: 'string' }], + stateMutability: 'view', + type: 'function' + }, + { + inputs: [], + name: 'MAX_AFTERPARTY', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function' + }, + { + inputs: [], + name: 'MAX_NUMBER', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function' + }, + { + inputs: [], + name: 'MAX_VIP', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function' + }, + { + inputs: [ + { internalType: 'address', name: 'to', type: 'address' }, + { internalType: 'uint256', name: 'tokenId', type: 'uint256' } + ], + name: 'approve', + outputs: [], + stateMutability: 'nonpayable', + type: 'function' + }, + { + inputs: [{ internalType: 'address', name: 'owner', type: 'address' }], + name: 'balanceOf', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function' + }, + { + inputs: [], + name: 'contractOwner', + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function' + }, + { + inputs: [], + name: 'currentTokenAmount', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function' + }, + { + inputs: [{ internalType: 'uint256', name: 'tokenId', type: 'uint256' }], + name: 'getApproved', + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function' + }, + { + inputs: [ + { internalType: 'address', name: 'owner', type: 'address' }, + { internalType: 'address', name: 'operator', type: 'address' } + ], + name: 'isApprovedForAll', + outputs: [{ internalType: 'bool', name: '', type: 'bool' }], + stateMutability: 'view', + type: 'function' + }, + { + inputs: [ + { internalType: 'address', name: 'to', type: 'address' }, + { internalType: 'uint256', name: 'tokenId', type: 'uint256' }, + { internalType: 'uint256', name: 'ticketType', type: 'uint256' } + ], + name: 'mint', + outputs: [], + stateMutability: 'nonpayable', + type: 'function' + }, + { + inputs: [], + name: 'name', + outputs: [{ internalType: 'string', name: '', type: 'string' }], + stateMutability: 'view', + type: 'function' + }, + { + inputs: [], + name: 'owner', + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function' + }, + { + inputs: [{ internalType: 'uint256', name: 'tokenId', type: 'uint256' }], + name: 'ownerOf', + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function' + }, + { + inputs: [], + name: 'renounceOwnership', + outputs: [], + stateMutability: 'nonpayable', + type: 'function' + }, + { + inputs: [ + { internalType: 'address', name: 'from', type: 'address' }, + { internalType: 'address', name: 'to', type: 'address' }, + { internalType: 'uint256', name: 'tokenId', type: 'uint256' } + ], + name: 'safeTransferFrom', + outputs: [], + stateMutability: 'nonpayable', + type: 'function' + }, + { + inputs: [ + { internalType: 'address', name: 'from', type: 'address' }, + { internalType: 'address', name: 'to', type: 'address' }, + { internalType: 'uint256', name: 'tokenId', type: 'uint256' }, + { internalType: 'bytes', name: '_data', type: 'bytes' } + ], + name: 'safeTransferFrom', + outputs: [], + stateMutability: 'nonpayable', + type: 'function' + }, + { + inputs: [ + { internalType: 'address', name: 'operator', type: 'address' }, + { internalType: 'bool', name: 'approved', type: 'bool' } + ], + name: 'setApprovalForAll', + outputs: [], + stateMutability: 'nonpayable', + type: 'function' + }, + { + inputs: [{ internalType: 'bytes4', name: 'interfaceId', type: 'bytes4' }], + name: 'supportsInterface', + outputs: [{ internalType: 'bool', name: '', type: 'bool' }], + stateMutability: 'view', + type: 'function' + }, + { + inputs: [], + name: 'symbol', + outputs: [{ internalType: 'string', name: '', type: 'string' }], + stateMutability: 'view', + type: 'function' + }, + { + inputs: [{ internalType: 'uint256', name: 'tokenId', type: 'uint256' }], + name: 'tokenURI', + outputs: [{ internalType: 'string', name: '', type: 'string' }], + stateMutability: 'view', + type: 'function' + }, + { + inputs: [ + { internalType: 'address', name: 'from', type: 'address' }, + { internalType: 'address', name: 'to', type: 'address' }, + { internalType: 'uint256', name: 'tokenId', type: 'uint256' } + ], + name: 'transferFrom', + outputs: [], + stateMutability: 'nonpayable', + type: 'function' + }, + { + inputs: [{ internalType: 'address', name: 'newOwner', type: 'address' }], + name: 'transferOwnership', + outputs: [], + stateMutability: 'nonpayable', + type: 'function' + } +]; +/** *********************Exports begin******************************************/ + +export default { + ERC721 +}; +/** ********************Exports end*********************************************/ diff --git a/crypto/blockchains/eth/ext/estimateGas/enums.js b/crypto/blockchains/eth/ext/estimateGas/enums.js index c0e115fe8..1e5e4f769 100644 --- a/crypto/blockchains/eth/ext/estimateGas/enums.js +++ b/crypto/blockchains/eth/ext/estimateGas/enums.js @@ -1,20 +1,20 @@ -const ENVIRONMENT_TYPE_POPUP = 'popup' -const ENVIRONMENT_TYPE_NOTIFICATION = 'notification' -const ENVIRONMENT_TYPE_FULLSCREEN = 'fullscreen' +const ENVIRONMENT_TYPE_POPUP = 'popup'; +const ENVIRONMENT_TYPE_NOTIFICATION = 'notification'; +const ENVIRONMENT_TYPE_FULLSCREEN = 'fullscreen'; -const PLATFORM_BRAVE = 'Brave' -const PLATFORM_CHROME = 'Chrome' -const PLATFORM_EDGE = 'Edge' -const PLATFORM_FIREFOX = 'Firefox' -const PLATFORM_OPERA = 'Opera' +const PLATFORM_BRAVE = 'Brave'; +const PLATFORM_CHROME = 'Chrome'; +const PLATFORM_EDGE = 'Edge'; +const PLATFORM_FIREFOX = 'Firefox'; +const PLATFORM_OPERA = 'Opera'; -module.exports = { - ENVIRONMENT_TYPE_POPUP, - ENVIRONMENT_TYPE_NOTIFICATION, - ENVIRONMENT_TYPE_FULLSCREEN, - PLATFORM_BRAVE, - PLATFORM_CHROME, - PLATFORM_EDGE, - PLATFORM_FIREFOX, - PLATFORM_OPERA -} +export default { + ENVIRONMENT_TYPE_POPUP, + ENVIRONMENT_TYPE_NOTIFICATION, + ENVIRONMENT_TYPE_FULLSCREEN, + PLATFORM_BRAVE, + PLATFORM_CHROME, + PLATFORM_EDGE, + PLATFORM_FIREFOX, + PLATFORM_OPERA +}; diff --git a/crypto/blockchains/eth/ext/estimateGas/index.js b/crypto/blockchains/eth/ext/estimateGas/index.js index a174e8599..edbc626be 100644 --- a/crypto/blockchains/eth/ext/estimateGas/index.js +++ b/crypto/blockchains/eth/ext/estimateGas/index.js @@ -1,40 +1,41 @@ -const EventEmitter = require('safe-event-emitter') -const ethUtil = require('ethereumjs-util') -const EthQuery = require('ethjs-query') -const TxGasUtil = require('./tx-gas-utils') -const HttpProvider = require('ethjs-provider-http') - +import EventEmitter from '@metamask/safe-event-emitter'; +import ethUtil from 'ethereumjs-util'; +import EthQuery from 'ethjs-query'; +import TxGasUtil from './tx-gas-utils'; +import HttpProvider from 'ethjs-provider-http'; class TransactionController extends EventEmitter { - constructor(opts) { - super(opts) - this.provider = new HttpProvider(opts.provider) - this.getGasPrice = opts.getGasPrice + constructor(opts) { + super(opts); + this.provider = new HttpProvider(opts.provider); + this.getGasPrice = opts.getGasPrice; - this.query = new EthQuery(this.provider) - this.txGasUtil = new TxGasUtil(this.provider) - } + this.query = new EthQuery(this.provider); + this.txGasUtil = new TxGasUtil(this.provider); + } - /** + /** adds the tx gas defaults: gas && gasPrice @param txMeta {Object} - the txMeta object @returns {Promise} resolves with txMeta */ - async addTxGasDefaults(txMeta) { - const txParams = txMeta.txParams - // ensure value - txParams.value = txParams.value ? ethUtil.addHexPrefix(txParams.value) : '0x0' - txMeta.gasPriceSpecified = Boolean(txParams.gasPrice) - // let gasPrice = txParams.gasPrice - const gasPrice = this.getGasPrice - /* if (!gasPrice) { + async addTxGasDefaults(txMeta) { + const txParams = txMeta.txParams; + // ensure value + txParams.value = txParams.value + ? ethUtil.addHexPrefix(txParams.value) + : '0x0'; + txMeta.gasPriceSpecified = Boolean(txParams.gasPrice); + // let gasPrice = txParams.gasPrice + const gasPrice = this.getGasPrice; + /* if (!gasPrice) { gasPrice = this.getGasPrice ? this.getGasPrice() : await this.query.gasPrice() } */ - // noinspection JSCheckFunctionSignatures - txParams.gasPrice = ethUtil.addHexPrefix(gasPrice.toString(16)) - // set gasLimit - return this.txGasUtil.analyzeGasUsage(txMeta) - } + // noinspection JSCheckFunctionSignatures + txParams.gasPrice = ethUtil.addHexPrefix(gasPrice.toString(16)); + // set gasLimit + return this.txGasUtil.analyzeGasUsage(txMeta); + } } -module.exports = TransactionController +export default TransactionController; diff --git a/crypto/blockchains/eth/ext/estimateGas/tx-gas-utils.js b/crypto/blockchains/eth/ext/estimateGas/tx-gas-utils.js index 886cdd3bc..fa9709fe1 100644 --- a/crypto/blockchains/eth/ext/estimateGas/tx-gas-utils.js +++ b/crypto/blockchains/eth/ext/estimateGas/tx-gas-utils.js @@ -1,13 +1,9 @@ -const EthQuery = require('ethjs-query') -const { - hexToBn, - BnMultiplyByFraction, - bnToHex -} = require('./util') -const { addHexPrefix } = require('ethereumjs-util') -const SIMPLE_GAS_COST = '0x5208' // Hex for 21000, cost of a simple send. +import EthQuery from 'ethjs-query'; +import { hexToBn, BnMultiplyByFraction, bnToHex } from './util'; +import { addHexPrefix } from 'ethereumjs-util'; +const SIMPLE_GAS_COST = '0x5208'; // Hex for 21000, cost of a simple send. -const TRANSACTION_NO_CONTRACT_ERROR_KEY = 'transactionErrorNoContract' +const TRANSACTION_NO_CONTRACT_ERROR_KEY = 'transactionErrorNoContract'; /** tx-gas-utils are gas utility methods for Transaction manager @@ -17,131 +13,134 @@ const TRANSACTION_NO_CONTRACT_ERROR_KEY = 'transactionErrorNoContract' */ class TxGasUtil { + constructor(provider) { + this.query = new EthQuery(provider); + } - constructor(provider) { - this.query = new EthQuery(provider) - } - - /** + /** @param txMeta {Object} - the txMeta object @returns {object} the txMeta object with the gas written to the txParams */ - async analyzeGasUsage(txMeta) { - // noinspection JSUnresolvedFunction - const block = await this.query.getBlockByNumber('latest', false) - let estimatedGasHex - try { - estimatedGasHex = await this.estimateTxGas(txMeta, block.gasLimit) - } catch (err) { - txMeta.simulationFails = { - reason: err.message, - errorKey: err.errorKey, - debug: { blockNumber: block.number, blockGasLimit: block.gasLimit } - } - - if (err.errorKey === TRANSACTION_NO_CONTRACT_ERROR_KEY) { - txMeta.simulationFails.debug.getCodeResponse = err.getCodeResponse - } - - return txMeta - } - this.setTxGas(txMeta, block.gasLimit, estimatedGasHex) - return txMeta + async analyzeGasUsage(txMeta) { + // noinspection JSUnresolvedFunction + const block = await this.query.getBlockByNumber('latest', false); + let estimatedGasHex; + try { + estimatedGasHex = await this.estimateTxGas(txMeta, block.gasLimit); + } catch (err) { + txMeta.simulationFails = { + reason: err.message, + errorKey: err.errorKey, + debug: { blockNumber: block.number, blockGasLimit: block.gasLimit } + }; + + if (err.errorKey === TRANSACTION_NO_CONTRACT_ERROR_KEY) { + txMeta.simulationFails.debug.getCodeResponse = err.getCodeResponse; + } + + return txMeta; } + this.setTxGas(txMeta, block.gasLimit, estimatedGasHex); + return txMeta; + } - /** + /** Estimates the tx's gas usage @param txMeta {Object} - the txMeta object @param blockGasLimitHex {string} - hex string of the block's gas limit @returns {string} the estimated gas limit as a hex string */ - async estimateTxGas(txMeta, blockGasLimitHex) { - const txParams = txMeta.txParams + async estimateTxGas(txMeta, blockGasLimitHex) { + const txParams = txMeta.txParams; - // check if gasLimit is already specified - txMeta.gasLimitSpecified = Boolean(txParams.gas) + // check if gasLimit is already specified + txMeta.gasLimitSpecified = Boolean(txParams.gas); - // if it is, use that value - if (txMeta.gasLimitSpecified) { - return txParams.gas - } + // if it is, use that value + if (txMeta.gasLimitSpecified) { + return txParams.gas; + } - const recipient = txParams.to - const hasRecipient = Boolean(recipient) - - // see if we can set the gas based on the recipient - if (hasRecipient) { - const code = await this.query.getCode(recipient) - // For an address with no code, geth will return '0x', and ganache-core v2.2.1 will return '0x0' - const codeIsEmpty = !code || code === '0x' || code === '0x0' - - if (codeIsEmpty) { - // if there's data in the params, but there's no contract code, it's not a valid transaction - if (txParams.data) { - throw new Error('TxGasUtil - Trying to call a function on a non-contract address') - } - - // This is a standard ether simple send, gas requirement is exactly 21k - txParams.gas = SIMPLE_GAS_COST - // prevents buffer addition - txMeta.simpleSend = true - return SIMPLE_GAS_COST - } + const recipient = txParams.to; + const hasRecipient = Boolean(recipient); + + // see if we can set the gas based on the recipient + if (hasRecipient) { + const code = await this.query.getCode(recipient); + // For an address with no code, geth will return '0x', and ganache-core v2.2.1 will return '0x0' + const codeIsEmpty = !code || code === '0x' || code === '0x0'; + + if (codeIsEmpty) { + // if there's data in the params, but there's no contract code, it's not a valid transaction + if (txParams.data) { + throw new Error( + 'TxGasUtil - Trying to call a function on a non-contract address' + ); } - // fallback to block gasLimit - const blockGasLimitBN = hexToBn(blockGasLimitHex) - const saferGasLimitBN = BnMultiplyByFraction(blockGasLimitBN, 19, 20) - txParams.gas = bnToHex(saferGasLimitBN) - - // estimate tx gas requirements - return this.query.estimateGas(txParams) + // This is a standard ether simple send, gas requirement is exactly 21k + txParams.gas = SIMPLE_GAS_COST; + // prevents buffer addition + txMeta.simpleSend = true; + return SIMPLE_GAS_COST; + } } - /** + // fallback to block gasLimit + const blockGasLimitBN = hexToBn(blockGasLimitHex); + const saferGasLimitBN = BnMultiplyByFraction(blockGasLimitBN, 19, 20); + txParams.gas = bnToHex(saferGasLimitBN); + + // estimate tx gas requirements + return this.query.estimateGas(txParams); + } + + /** Writes the gas on the txParams in the txMeta @param txMeta {Object} - the txMeta object to write to @param blockGasLimitHex {string} - the block gas limit hex @param estimatedGasHex {string} - the estimated gas hex */ - setTxGas(txMeta, blockGasLimitHex, estimatedGasHex) { - txMeta.estimatedGas = addHexPrefix(estimatedGasHex) - const txParams = txMeta.txParams - - // if gasLimit was specified and doesnt OOG, - // use original specified amount - if (txMeta.gasLimitSpecified || txMeta.simpleSend) { - txMeta.estimatedGas = txParams.gas - return - } - // if gasLimit not originally specified, - // try adding an additional gas buffer to our estimation for safety - txParams.gas = this.addGasBuffer(txMeta.estimatedGas, blockGasLimitHex) - return txParams + setTxGas(txMeta, blockGasLimitHex, estimatedGasHex) { + txMeta.estimatedGas = addHexPrefix(estimatedGasHex); + const txParams = txMeta.txParams; + + // if gasLimit was specified and doesnt OOG, + // use original specified amount + if (txMeta.gasLimitSpecified || txMeta.simpleSend) { + txMeta.estimatedGas = txParams.gas; + return; } + // if gasLimit not originally specified, + // try adding an additional gas buffer to our estimation for safety + txParams.gas = this.addGasBuffer(txMeta.estimatedGas, blockGasLimitHex); + return txParams; + } - /** + /** Adds a gas buffer with out exceeding the block gas limit @param initialGasLimitHex {string} - the initial gas limit to add the buffer too @param blockGasLimitHex {string} - the block gas limit @returns {string} the buffered gas limit as a hex string */ - addGasBuffer(initialGasLimitHex, blockGasLimitHex) { - const initialGasLimitBn = hexToBn(initialGasLimitHex) - const blockGasLimitBn = hexToBn(blockGasLimitHex) - // noinspection JSUnresolvedFunction - const upperGasLimitBn = blockGasLimitBn.muln(0.9) - // noinspection JSUnresolvedFunction - const bufferedGasLimitBn = initialGasLimitBn.muln(1.5) - - // if initialGasLimit is above blockGasLimit, dont modify it - if (initialGasLimitBn.gt(upperGasLimitBn)) return bnToHex(initialGasLimitBn) - // if bufferedGasLimit is below blockGasLimit, use bufferedGasLimit - if (bufferedGasLimitBn.lt(upperGasLimitBn)) return bnToHex(bufferedGasLimitBn) - // otherwise use blockGasLimit - return bnToHex(upperGasLimitBn) - } + addGasBuffer(initialGasLimitHex, blockGasLimitHex) { + const initialGasLimitBn = hexToBn(initialGasLimitHex); + const blockGasLimitBn = hexToBn(blockGasLimitHex); + // noinspection JSUnresolvedFunction + const upperGasLimitBn = blockGasLimitBn.muln(0.9); + // noinspection JSUnresolvedFunction + const bufferedGasLimitBn = initialGasLimitBn.muln(1.5); + + // if initialGasLimit is above blockGasLimit, dont modify it + if (initialGasLimitBn.gt(upperGasLimitBn)) + return bnToHex(initialGasLimitBn); + // if bufferedGasLimit is below blockGasLimit, use bufferedGasLimit + if (bufferedGasLimitBn.lt(upperGasLimitBn)) + return bnToHex(bufferedGasLimitBn); + // otherwise use blockGasLimit + return bnToHex(upperGasLimitBn); + } } -module.exports = TxGasUtil +export default TxGasUtil; diff --git a/crypto/blockchains/eth/ext/estimateGas/util.js b/crypto/blockchains/eth/ext/estimateGas/util.js index 81f3fbdaf..ac419107b 100644 --- a/crypto/blockchains/eth/ext/estimateGas/util.js +++ b/crypto/blockchains/eth/ext/estimateGas/util.js @@ -1,16 +1,17 @@ -const ethUtil = require('ethereumjs-util') -const assert = require('assert') -const BN = require('bn.js') +import { addHexPrefix, stripHexPrefix } from 'ethereumjs-util'; +import { strictEqual } from 'assert'; +import BN from 'bn.js'; +import enums from './enums'; const { - ENVIRONMENT_TYPE_POPUP, - ENVIRONMENT_TYPE_NOTIFICATION, - ENVIRONMENT_TYPE_FULLSCREEN, - PLATFORM_FIREFOX, - PLATFORM_OPERA, - PLATFORM_CHROME, - PLATFORM_EDGE, - PLATFORM_BRAVE -} = require('./enums') + ENVIRONMENT_TYPE_POPUP, + ENVIRONMENT_TYPE_NOTIFICATION, + ENVIRONMENT_TYPE_FULLSCREEN, + PLATFORM_FIREFOX, + PLATFORM_OPERA, + PLATFORM_CHROME, + PLATFORM_EDGE, + PLATFORM_BRAVE +} = enums; /** * Generates an example stack trace @@ -19,7 +20,7 @@ const { * */ function getStack() { - return new Error('Stack trace generator - not an error').stack + return new Error('Stack trace generator - not an error').stack; } /** @@ -32,14 +33,17 @@ function getStack() { * */ const getEnvironmentType = (url = window.location.href) => { - if (url.match(/popup.html(?:#.*)*$/)) { - return ENVIRONMENT_TYPE_POPUP - } else if (url.match(/home.html(?:\?.+)*$/) || url.match(/home.html(?:#.*)*$/)) { - return ENVIRONMENT_TYPE_FULLSCREEN - } else { - return ENVIRONMENT_TYPE_NOTIFICATION - } -} + if (url.match(/popup.html(?:#.*)*$/)) { + return ENVIRONMENT_TYPE_POPUP; + } else if ( + url.match(/home.html(?:\?.+)*$/) || + url.match(/home.html(?:#.*)*$/) + ) { + return ENVIRONMENT_TYPE_FULLSCREEN; + } else { + return ENVIRONMENT_TYPE_NOTIFICATION; + } +}; // noinspection JSUnusedLocalSymbols /** @@ -48,23 +52,23 @@ const getEnvironmentType = (url = window.location.href) => { * @returns {string} the platform ENUM * */ -const getPlatform = _ => { - const ua = navigator.userAgent - if (ua.search('Firefox') !== -1) { - return PLATFORM_FIREFOX +const getPlatform = (_) => { + const ua = navigator.userAgent; + if (ua.search('Firefox') !== -1) { + return PLATFORM_FIREFOX; + } else { + // noinspection JSUnresolvedVariable + if (window && window.chrome && window.chrome.ipcRenderer) { + return PLATFORM_BRAVE; + } else if (ua.search('Edge') !== -1) { + return PLATFORM_EDGE; + } else if (ua.search('OPR') !== -1) { + return PLATFORM_OPERA; } else { - // noinspection JSUnresolvedVariable - if (window && window.chrome && window.chrome.ipcRenderer) { - return PLATFORM_BRAVE - } else if (ua.search('Edge') !== -1) { - return PLATFORM_EDGE - } else if (ua.search('OPR') !== -1) { - return PLATFORM_OPERA - } else { - return PLATFORM_CHROME - } + return PLATFORM_CHROME; } -} + } +}; /** * Checks whether a given balance of ETH, represented as a hex string, is sufficient to pay a value plus a gas fee @@ -78,19 +82,27 @@ const getPlatform = _ => { * */ function sufficientBalance(txParams, hexBalance) { - // validate hexBalance is a hex string - assert.strictEqual(typeof hexBalance, 'string', 'sufficientBalance - hexBalance is not a hex string') - assert.strictEqual(hexBalance.slice(0, 2), '0x', 'sufficientBalance - hexBalance is not a hex string') + // validate hexBalance is a hex string + strictEqual( + typeof hexBalance, + 'string', + 'sufficientBalance - hexBalance is not a hex string' + ); + strictEqual( + hexBalance.slice(0, 2), + '0x', + 'sufficientBalance - hexBalance is not a hex string' + ); - const balance = hexToBn(hexBalance) - const value = hexToBn(txParams.value) - const gasLimit = hexToBn(txParams.gas) - const gasPrice = hexToBn(txParams.gasPrice) + const balance = hexToBn(hexBalance); + const value = hexToBn(txParams.value); + const gasLimit = hexToBn(txParams.gas); + const gasPrice = hexToBn(txParams.gasPrice); - // noinspection JSUnresolvedFunction - const maxCost = value.add(gasLimit.mul(gasPrice)) - // noinspection JSUnresolvedFunction - return balance.gte(maxCost) + // noinspection JSUnresolvedFunction + const maxCost = value.add(gasLimit.mul(gasPrice)); + // noinspection JSUnresolvedFunction + return balance.gte(maxCost); } /** @@ -101,8 +113,8 @@ function sufficientBalance(txParams, hexBalance) { * */ function bnToHex(inputBn) { - // noinspection JSCheckFunctionSignatures - return ethUtil.addHexPrefix(inputBn.toString(16)) + // noinspection JSCheckFunctionSignatures + return addHexPrefix(inputBn.toString(16)); } /** @@ -113,8 +125,8 @@ function bnToHex(inputBn) { * */ function hexToBn(inputHex) { - // noinspection JSUnresolvedFunction - return new BN(ethUtil.stripHexPrefix(inputHex), 16) + // noinspection JSUnresolvedFunction + return new BN(stripHexPrefix(inputHex), 16); } /** @@ -127,33 +139,33 @@ function hexToBn(inputHex) { * */ function BnMultiplyByFraction(targetBN, numerator, denominator) { - const numBN = new BN(numerator) - const denomBN = new BN(denominator) - // noinspection JSUnresolvedFunction - return targetBN.mul(numBN).div(denomBN) + const numBN = new BN(numerator); + const denomBN = new BN(denominator); + // noinspection JSUnresolvedFunction + return targetBN.mul(numBN).div(denomBN); } function applyListeners(listeners, emitter) { - Object.keys(listeners).forEach((key) => { - emitter.on(key, listeners[key]) - }) + Object.keys(listeners).forEach((key) => { + emitter.on(key, listeners[key]); + }); } function removeListeners(listeners, emitter) { - Object.keys(listeners).forEach((key) => { - emitter.removeListener(key, listeners[key]) - }) + Object.keys(listeners).forEach((key) => { + emitter.removeListener(key, listeners[key]); + }); } // noinspection JSUnusedGlobalSymbols -module.exports = { - removeListeners, - applyListeners, - getPlatform, - getStack, - getEnvironmentType, - sufficientBalance, - hexToBn, - bnToHex, - BnMultiplyByFraction -} +export default { + removeListeners, + applyListeners, + getPlatform, + getStack, + getEnvironmentType, + sufficientBalance, + hexToBn, + bnToHex, + BnMultiplyByFraction +}; diff --git a/crypto/blockchains/eth/forks/EthScannerProcessorSoul.js b/crypto/blockchains/eth/forks/EthScannerProcessorSoul.ts similarity index 94% rename from crypto/blockchains/eth/forks/EthScannerProcessorSoul.js rename to crypto/blockchains/eth/forks/EthScannerProcessorSoul.ts index a769a59c5..c6fc7cd9d 100644 --- a/crypto/blockchains/eth/forks/EthScannerProcessorSoul.js +++ b/crypto/blockchains/eth/forks/EthScannerProcessorSoul.ts @@ -16,7 +16,12 @@ export default class EthScannerProcessorSoul extends EthScannerProcessorErc20 { * @returns {Promise<[]>} * @private */ - async _unifyTransactions(address, result, isInternal, isTrezor = true) { + async _unifyTransactions( + address: string, + result, + isInternal: boolean, + isTrezor = true + ) { const transactions = []; const alreadyTransactions = {}; let count = 0; diff --git a/crypto/blockchains/eth/stores/EthRawDS.js b/crypto/blockchains/eth/stores/EthRawDS.ts similarity index 70% rename from crypto/blockchains/eth/stores/EthRawDS.js rename to crypto/blockchains/eth/stores/EthRawDS.ts index 360fb522b..f3a5cbcdc 100644 --- a/crypto/blockchains/eth/stores/EthRawDS.js +++ b/crypto/blockchains/eth/stores/EthRawDS.ts @@ -1,16 +1,16 @@ +/* eslint-disable camelcase */ /** - * @author Ksu + * @author Javid * @version 0.32 */ -import Database from '@app/appstores/DataSource/Database'; -import EthTxSendProvider from '../basic/EthTxSendProvider'; import BlocksoftExternalSettings from '../../../common/BlocksoftExternalSettings'; import BlocksoftAxios from '../../../common/BlocksoftAxios'; import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; -import MarketingEvent from '../../../../app/services/Marketing/MarketingEvent'; -import config from '../../../../app/config/config'; +import { DatabaseTable } from '@appTypes'; +import { Database } from '@database'; +import { TransactionRawDBModel } from '@database/models/transactions-raw'; -const tableName = 'transactions_raw'; +const tableName = DatabaseTable.TransactionRaw; class EthRawDS { /** @@ -20,6 +20,7 @@ class EthRawDS { _currencyCode = 'ETH'; _trezorServer = 'none'; + private _infuraProjectId: any; // TODO fix any async getForAddress(data) { try { @@ -29,7 +30,7 @@ class EthRawDS { } const sql = ` SELECT id, - transaction_unique_key AS transactionUnique, + transaction_unique_key AS transactionUniqueKey, transaction_hash AS transactionHash, transaction_raw AS transactionRaw, transaction_log AS transactionLog, @@ -42,12 +43,15 @@ class EthRawDS { (is_removed=0 OR is_removed IS NULL) AND currency_code='${this._currencyCode}' AND address='${data.address.toLowerCase()}'`; - const result = await Database.query(sql); - if (!result || !result.array || result.array.length === 0) { + const result = (await Database.unsafeRawQuery( + tableName, + sql + )) as TransactionRawDBModel[]; + if (!result || !result || result.length === 0) { return {}; } - const ret = {}; + const ret: { [key: string]: unknown } = {}; // TODO fix unknown if (this._trezorServer === 'none') { if (this._currencyCode === 'ETH') { @@ -72,11 +76,11 @@ class EthRawDS { } } - const now = new Date().toISOString(); + const now = new Date().getTime(); - for (const row of result.array) { + for (const row of result) { try { - ret[row.transactionUnique] = row; + ret[row.transactionUniqueKey] = row; let transactionLog; try { transactionLog = row.transactionLog @@ -92,55 +96,42 @@ class EthRawDS { (typeof transactionLog.successResult === 'undefined' || !transactionLog.successResult) ) { - const { apiEndpoints } = config.proxy; - const baseURL = MarketingEvent.DATA.LOG_TESTER - ? apiEndpoints.baseURLTest - : apiEndpoints.baseURL; - const successProxy = baseURL + '/send/sendtx'; - let checkResult = false; - try { - transactionLog.selectedFee.isRebroadcast = true; - checkResult = await BlocksoftAxios.post(successProxy, { - raw: row.transactionRaw, - txRBF: - typeof transactionLog.txRBF !== 'undefined' - ? transactionLog.txRBF - : false, - logData: transactionLog, - marketingData: MarketingEvent.DATA - }); - await BlocksoftCryptoLog.log( - this._currencyCode + ' EthRawDS.send proxy success result', - JSON.parse(JSON.stringify(checkResult)) - ); - } catch (e3) { - if (config.debug.cryptoErrors) { - console.log( - this._currencyCode + - ' EthRawDS.send proxy success error ' + - e3.message - ); - } - await BlocksoftCryptoLog.log( - this._currencyCode + - ' EthRawDS.send proxy success error ' + - e3.message - ); - } - if (checkResult && typeof checkResult.data !== 'undefined') { - transactionLog.successResult = checkResult.data; - } - - await Database.setTableName('transactions_raw') - .setUpdateData({ - updateObj: { - transactionLog: Database.escapeString( - JSON.stringify(transactionLog) - ) - }, - key: { id: row.id } - }) - .update(); + // const { apiEndpoints } = config.proxy; + // const baseURL = MarketingEvent.DATA.LOG_TESTER + // ? apiEndpoints.baseURLTest + // : apiEndpoints.baseURL; + // const successProxy = baseURL + '/send/sendtx'; + // let checkResult = false; + // try { + // transactionLog.selectedFee.isRebroadcast = true; + // checkResult = await BlocksoftAxios.post(successProxy, { + // raw: row.transactionRaw, + // txRBF: + // typeof transactionLog.txRBF !== 'undefined' + // ? transactionLog.txRBF + // : false, + // logData: transactionLog, + // marketingData: MarketingEvent.DATA + // }); + // await BlocksoftCryptoLog.log( + // this._currencyCode + ' EthRawDS.send proxy success result', + // JSON.parse(JSON.stringify(checkResult)) + // ); + // } catch (e3) { + // await BlocksoftCryptoLog.log( + // this._currencyCode + + // ' EthRawDS.send proxy success error ' + + // e3.message + // ); + // } + // if (checkResult && typeof checkResult.data !== 'undefined') { + // transactionLog.successResult = checkResult.data; + // } + // await Database.updateModel(tableName, row.id, { + // transactionLog: Database.escapeString( + // JSON.stringify(transactionLog) + // ) + // }); } let broadcastLog = ''; @@ -160,7 +151,8 @@ class EthRawDS { broadcastLog = ' broadcasted ok ' + JSON.stringify(broad.data); updateObj.is_removed = '1'; updateObj.removed_at = now; - } catch (e) { + } catch (err) { + const e = err as unknown as any; if ( e.message.indexOf('transaction underpriced') !== -1 || e.message.indexOf('already known') !== -1 @@ -173,10 +165,6 @@ class EthRawDS { } } broadcastLog += ' ' + link + '; '; - MarketingEvent.logOnlyRealTime( - 'v20_eth_resend_0 ' + row.transactionHash, - { broadcastLog, ...updateObj } - ); } if (this._currencyCode === 'ETH') { @@ -191,7 +179,8 @@ class EthRawDS { broadcastLog1 = ' broadcasted ok ' + JSON.stringify(broad.data); updateObj.is_removed += '1'; updateObj.removed_at = now; - } catch (e) { + } catch (err) { + const e = err as unknown as any; if ( e.message.indexOf('transaction underpriced') !== -1 || e.message.indexOf('already known') !== -1 @@ -204,11 +193,6 @@ class EthRawDS { } } broadcastLog1 += ' ' + link + '; '; - MarketingEvent.logOnlyRealTime( - 'v20_eth_resend_1 ' + row.transactionHash, - { broadcastLog1, ...updateObj } - ); - link = 'https://mainnet.infura.io/v3/' + this._infuraProjectId; let broadcastLog2 = ''; try { @@ -224,7 +208,8 @@ class EthRawDS { broadcastLog2 = ' broadcasted ok ' + JSON.stringify(broad.data); updateObj.is_removed += '1'; updateObj.removed_at = now; - } catch (e) { + } catch (err) { + const e = err as unknown as any; if ( e.message.indexOf('transaction underpriced') !== -1 || e.message.indexOf('already known') !== -1 @@ -237,11 +222,6 @@ class EthRawDS { } } broadcastLog2 += ' ' + link + '; '; - MarketingEvent.logOnlyRealTime( - 'v20_eth_resend_2 ' + row.transactionHash, - { broadcastLog2, ...updateObj } - ); - if (updateObj.is_removed === '111') { // do ALL! updateObj.is_removed = 1; @@ -280,20 +260,16 @@ class EthRawDS { this._currencyCode === 'ETH_ROPSTEN' ) { updateObj.broadcastLog = broadcastLog; - - await Database.setTableName('transactions_raw') - .setUpdateData({ - updateObj, - key: { id: row.id } - }) - .update(); + await Database.updateModel(tableName, row.id, updateObj); } - } catch (e) { + } catch (err) { + const e = err as unknown as any; throw new Error(e.message + ' inside row ' + row.transactionHash); } } return ret; - } catch (e) { + } catch (err) { + const e = err as unknown as any; throw new Error(e.message + ' on EthRawDS.getAddress'); } } @@ -302,13 +278,13 @@ class EthRawDS { BlocksoftCryptoLog.log('EthRawDS cleanRawHash ', data); const now = new Date().toISOString(); - const sql = `UPDATE transactions_raw + const sql = `UPDATE ${tableName} SET is_removed=1, removed_at = '${now}' WHERE (is_removed=0 OR is_removed IS NULL) AND (currency_code='ETH' OR currency_code='ETH_ROPSTEN') AND transaction_hash='${data.transactionHash}'`; - await Database.query(sql); + await Database.unsafeRawQuery(tableName, sql); } async cleanRaw(data) { @@ -319,15 +295,15 @@ class EthRawDS { data.currencyCode === 'ETH_ROPSTEN' ? 'ETH_ROPSTEN' : 'ETH'; } - const now = new Date().toISOString(); - const sql = `UPDATE transactions_raw + const now = new Date().getTime(); + const sql = `UPDATE ${tableName} SET is_removed=1, removed_at = '${now}' WHERE (is_removed=0 OR is_removed IS NULL) AND currency_code='${this._currencyCode}' AND address='${data.address.toLowerCase()}' AND transaction_unique_key='${data.transactionUnique}'`; - await Database.query(sql); + await Database.unsafeRawQuery(tableName, sql); } async saveRaw(data) { @@ -335,16 +311,16 @@ class EthRawDS { this._currencyCode = data.currencyCode === 'ETH_ROPSTEN' ? 'ETH_ROPSTEN' : 'ETH'; } - const now = new Date().toISOString(); + const now = new Date().getTime(); - const sql = `UPDATE transactions_raw + const sql = `UPDATE ${tableName} SET is_removed=1, removed_at = '${now}' WHERE (is_removed=0 OR is_removed IS NULL) AND currency_code='${this._currencyCode}' AND address='${data.address.toLowerCase()}' AND transaction_unique_key='${data.transactionUnique.toLowerCase()}'`; - await Database.query(sql); + await Database.unsafeRawQuery(tableName, sql); const prepared = [ { @@ -360,9 +336,7 @@ class EthRawDS { is_removed: 0 } ]; - await Database.setTableName(tableName) - .setInsertData({ insertObjs: prepared }) - .insert(); + await Database.createModel(tableName, prepared); } } diff --git a/crypto/blockchains/eth/stores/EthTmpDS.js b/crypto/blockchains/eth/stores/EthTmpDS.js deleted file mode 100644 index 17509817b..000000000 --- a/crypto/blockchains/eth/stores/EthTmpDS.js +++ /dev/null @@ -1,194 +0,0 @@ - -import Database from '@app/appstores/DataSource/Database'; -import BlocksoftBN from '../../../common/BlocksoftBN' - -const tableName = 'transactions_scanners_tmp' - -const CACHE_TMP = {} - -class EthTmpDS { - async setSuccess(txHash) { - await Database.query(`UPDATE transactions SET transaction_status = 'success' WHERE transaction_hash='${txHash}'`) - } - - async getCache(mainCurrencyCode, scanAddress, toRemove = false) { - const address = scanAddress.toLowerCase() - const res = await Database.query(` - SELECT id, tmp_key, tmp_sub_key, tmp_val, created_at - FROM ${tableName} - WHERE currency_code='${mainCurrencyCode}' - AND address='${address}' - AND tmp_key='nonces' - `) - CACHE_TMP[address] = {} - let maxValue = -1 - let maxScanned = -1 - let maxSuccess = -1 - const forBalances = {} - - if (res.array) { - for (const row of res.array) { - const val = row.tmp_val * 1 - if (row.tmp_sub_key === 'maxScanned') { - if (val > maxScanned) { - maxScanned = val - } - } else if (row.tmp_sub_key === 'maxSuccess') { - if (val > maxSuccess) { - maxSuccess = val - } - } else { - if (val > maxValue) { - maxValue = val - } - } - CACHE_TMP[address][row.tmp_sub_key] = val - } - for (const row of res.array) { - const val = row.tmp_val * 1 - if (row.tmp_sub_key === 'maxScanned' || row.tmp_sub_key === 'maxSuccess' || !row.tmp_sub_key || typeof row.tmp_sub_key === 'undefined') { - // do nothing - } else { - const tmp = row.tmp_sub_key.split('_') - if (typeof tmp[1] !== 'undefined') { - const txHash = tmp[1] - if (toRemove && typeof toRemove[txHash] !== 'undefined') { - await Database.query(`DELETE FROM ${tableName} WHERE id=${row.id}`) - } else { - if (val > maxSuccess) { - forBalances[txHash] = val - } - } - } - } - } - } - - const amountBN = {} - let queryLength = 0 - let queryTxs = [] - for (const txHash in forBalances) { - const tmps = await Database.query(`SELECT currency_code AS currencyCode, - address_amount as addressAmount, - transaction_status as transactionStatus - FROM transactions - WHERE transaction_hash='${txHash}' - AND (currency_code LIKE '%ETH%' OR currency_code LIKE 'CUSTOM_%') - `) - if (tmps && tmps.array && typeof tmps.array[0] !== 'undefined') { - let txCurrencyCode = '' - for (const tmp of tmps.array) { - if (tmp.currencyCode === 'ETH' || tmp.currencyCode === 'ETH_ROPSTEN') { - if (txCurrencyCode === '') { - txCurrencyCode = tmp.currencyCode - } - } else { - txCurrencyCode = tmp.currencyCode - } - } - if (txCurrencyCode !== '') { - for (const tmp of tmps.array) { - if (tmp.currencyCode !== txCurrencyCode) continue - - let recheckRBFStatus = 'none' - if (tmp.transactionStatus === 'new' || tmp.transactionStatus === 'confirming') { - const recheckTmp = await Database.query(`SELECT transaction_status as transactionStatus FROM transactions WHERE transactions_other_hashes LIKE '%${txHash}%'`) - if (recheckTmp && recheckTmp.array && typeof recheckTmp.array[0] !== 'undefined') { - recheckRBFStatus = recheckTmp.array[0].transactionStatus - if (recheckRBFStatus !== 'new') { - await Database.query(`UPDATE transactions SET transaction_status='${recheckRBFStatus}' - WHERE transaction_hash='${txHash}' - AND (currency_code LIKE '%ETH%' OR currency_code LIKE 'CUSTOM_%') - `) - } - tmp.transactionStatus = recheckRBFStatus - } - } - if (tmp.transactionStatus === 'new') { - const amount = tmp.addressAmount - if (typeof amountBN[tmp.currencyCode] === 'undefined') { - amountBN[tmp.currencyCode] = new BlocksoftBN(0) - } - queryLength++ - queryTxs.push({ currencyCode: tmp.currencyCode, txHash, recheckRBFStatus }) - amountBN[tmp.currencyCode].add(amount) - } else if (tmp.transactionStatus === 'missing') { - if (maxSuccess > forBalances[txHash]) { - maxSuccess = forBalances[txHash] - 1 - } - } - } - } - } - } - CACHE_TMP[address]['maxValue'] = maxValue - CACHE_TMP[address]['maxScanned'] = maxScanned - CACHE_TMP[address]['maxSuccess'] = maxSuccess > maxScanned ? maxSuccess : maxScanned - CACHE_TMP[address]['amountBlocked'] = {} - CACHE_TMP[address]['queryLength'] = queryLength - CACHE_TMP[address]['queryTxs'] = queryTxs - for (const key in amountBN) { - CACHE_TMP[address]['amountBlocked'][key] = amountBN[key].toString() - } - return CACHE_TMP[address] - } - - async getMaxNonce(mainCurrencyCode, scanAddress) { - if (mainCurrencyCode !== 'ETH') { - return false - } - const address = scanAddress.toLowerCase() - // if (typeof CACHE_TMP[address] === 'undefined' || typeof CACHE_TMP[address]['maxValue'] === 'undefined') { - await this.getCache(mainCurrencyCode, address) - //} - return { - maxValue: CACHE_TMP[address]['maxValue'], - maxScanned: CACHE_TMP[address]['maxScanned'], - maxSuccess: CACHE_TMP[address]['maxSuccess'], - amountBlocked: CACHE_TMP[address]['amountBlocked'], - queryLength: CACHE_TMP[address]['queryLength'], - queryTxs: CACHE_TMP[address]['queryTxs'] - } - } - - async removeNonce(mainCurrencyCode, scanAddress, key) { - if (mainCurrencyCode !== 'ETH') { - return false - } - const address = scanAddress.toLowerCase() - const where = `WHERE currency_code='${mainCurrencyCode}' AND address='${address}' AND tmp_key='nonces' AND tmp_sub_key='${key}'` - await Database.query(`DELETE FROM ${tableName} ${where}`) - await this.getCache(mainCurrencyCode, address) - } - - async saveNonce(mainCurrencyCode, scanAddress, key, value) { - if (mainCurrencyCode !== 'ETH') { - return false - } - const address = scanAddress.toLowerCase() - const now = new Date().toISOString() - value = value * 1 - - const where = `WHERE currency_code='${mainCurrencyCode}' AND address='${address}' AND tmp_key='nonces' AND tmp_sub_key='${key}'` - const res = await Database.query(`SELECT tmp_val FROM ${tableName} ${where}`) - if (res && res.array && res.array.length > 0) { - if (res.array[0].tmp_val * 1 >= value) { - return true - } - await Database.query(`DELETE FROM ${tableName} ${where}`) - } - - const prepared = [{ - currency_code: mainCurrencyCode, - address: address.toLowerCase(), - tmp_key: 'nonces', - tmp_sub_key: key, - tmp_val: value, - created_at: now - }] - await Database.setTableName(tableName).setInsertData({ insertObjs: prepared }).insert() - await this.getCache(mainCurrencyCode, address) - } -} - -export default new EthTmpDS() diff --git a/crypto/blockchains/eth/stores/EthTmpDS.ts b/crypto/blockchains/eth/stores/EthTmpDS.ts new file mode 100644 index 000000000..e5f17b9cc --- /dev/null +++ b/crypto/blockchains/eth/stores/EthTmpDS.ts @@ -0,0 +1,249 @@ +/* eslint-disable camelcase */ +import { + Database, + TransactionScannersTmpDBModel, + TransactionsDBModel +} from '@database'; +import BlocksoftBN from '../../../common/BlocksoftBN'; +import { DatabaseTable } from '@appTypes'; + +const tableName = DatabaseTable.TransactionScannersTmp; + +const CACHE_TMP: { [key: string]: any } = {}; // TODO fix any + +class EthTmpDS { + async setSuccess(txHash: string) { + await Database.unsafeRawQuery( + DatabaseTable.Transactions, + `UPDATE ${DatabaseTable.Transactions} SET transaction_status = 'success' WHERE transaction_hash='${txHash}'` + ); + } + + async getCache( + mainCurrencyCode: string, + scanAddress: string, + toRemove = false + ) { + const address = scanAddress.toLowerCase(); + const res = (await Database.unsafeRawQuery( + tableName, + ` + SELECT id, tmp_key AS tmpKey, tmp_sub_key AS tmpSubKey, tmp_val AS tmpVal, created_at AS createdAt + FROM ${tableName} + WHERE currency_code='${mainCurrencyCode}' + AND address='${address}' + AND tmp_key='nonces' + ` + )) as TransactionScannersTmpDBModel[]; + CACHE_TMP[address] = {}; + let maxValue = -1; + let maxScanned = -1; + let maxSuccess = -1; + const forBalances = {}; + + if (res) { + for (const row of res) { + const val = row.tmpVal * 1; + if (row.tmpSubKey === 'maxScanned') { + if (val > maxScanned) { + maxScanned = val; + } + } else if (row.tmpSubKey === 'maxSuccess') { + if (val > maxSuccess) { + maxSuccess = val; + } + } else { + if (val > maxValue) { + maxValue = val; + } + } + CACHE_TMP[address][row.tmpSubKey] = val; + } + for (const row of res) { + const val = row.tmpVal * 1; + if ( + row.tmpSubKey === 'maxScanned' || + row.tmpSubKey === 'maxSuccess' || + !row.tmpSubKey || + typeof row.tmpSubKey === 'undefined' + ) { + // do nothing + } else { + const tmp = row.tmpSubKey.split('_'); + if (typeof tmp[1] !== 'undefined') { + const txHash = tmp[1]; + if (toRemove && typeof toRemove[txHash] !== 'undefined') { + Database.deleteModel(tableName, row.id); + } else { + if (val > maxSuccess) { + forBalances[txHash] = val; + } + } + } + } + } + } + + const amountBN = {}; + let queryLength = 0; + let queryTxs = []; + for (const txHash in forBalances) { + const tmps = (await Database.unsafeRawQuery( + DatabaseTable.Transactions, + `SELECT currency_code AS currencyCode, + address_amount as addressAmount, + transaction_status as transactionStatus + FROM transactions + WHERE transaction_hash='${txHash}' + AND (currency_code LIKE '%ETH%' OR currency_code LIKE 'CUSTOM_%') + ` + )) as TransactionsDBModel[]; + if (tmps && tmps && typeof tmps[0] !== 'undefined') { + let txCurrencyCode = ''; + for (const tmp of tmps) { + if ( + tmp.currencyCode === 'ETH' || + tmp.currencyCode === 'ETH_ROPSTEN' + ) { + if (txCurrencyCode === '') { + txCurrencyCode = tmp.currencyCode; + } + } else { + txCurrencyCode = tmp.currencyCode; + } + } + if (txCurrencyCode !== '') { + for (const tmp of tmps) { + if (tmp.currencyCode !== txCurrencyCode) continue; + + let recheckRBFStatus = 'none'; + if ( + tmp.transactionStatus === 'new' || + tmp.transactionStatus === 'confirming' + ) { + const recheckTmp = (await Database.unsafeRawQuery( + DatabaseTable.Transactions, + `SELECT transaction_status as transactionStatus FROM transactions WHERE transactions_other_hashes LIKE '%${txHash}%'` + )) as TransactionsDBModel[]; + if ( + recheckTmp && + recheckTmp && + typeof recheckTmp[0] !== 'undefined' + ) { + recheckRBFStatus = recheckTmp[0].transactionStatus; + if (recheckRBFStatus !== 'new') { + await Database.unsafeRawQuery( + DatabaseTable.Transactions, + `UPDATE transactions SET transaction_status='${recheckRBFStatus}' + WHERE transaction_hash='${txHash}' + AND (currency_code LIKE '%ETH%' OR currency_code LIKE 'CUSTOM_%') + ` + ); + } + tmp.transactionStatus = recheckRBFStatus; + } + } + if (tmp.transactionStatus === 'new') { + const amount = tmp.addressAmount; + if (typeof amountBN[tmp.currencyCode] === 'undefined') { + amountBN[tmp.currencyCode] = new BlocksoftBN(0); + } + queryLength++; + queryTxs.push({ + currencyCode: tmp.currencyCode, + txHash, + recheckRBFStatus + }); + amountBN[tmp.currencyCode].add(amount); + } else if (tmp.transactionStatus === 'missing') { + if (maxSuccess > forBalances[txHash]) { + maxSuccess = forBalances[txHash] - 1; + } + } + } + } + } + } + CACHE_TMP[address]['maxValue'] = maxValue; + CACHE_TMP[address]['maxScanned'] = maxScanned; + CACHE_TMP[address]['maxSuccess'] = + maxSuccess > maxScanned ? maxSuccess : maxScanned; + CACHE_TMP[address]['amountBlocked'] = {}; + CACHE_TMP[address]['queryLength'] = queryLength; + CACHE_TMP[address]['queryTxs'] = queryTxs; + for (const key in amountBN) { + CACHE_TMP[address]['amountBlocked'][key] = amountBN[key].toString(); + } + return CACHE_TMP[address]; + } + + async getMaxNonce(mainCurrencyCode, scanAddress) { + if (mainCurrencyCode !== 'ETH') { + return false; + } + const address = scanAddress.toLowerCase(); + // if (typeof CACHE_TMP[address] === 'undefined' || typeof CACHE_TMP[address]['maxValue'] === 'undefined') { + await this.getCache(mainCurrencyCode, address); + //} + return { + maxValue: CACHE_TMP[address]['maxValue'], + maxScanned: CACHE_TMP[address]['maxScanned'], + maxSuccess: CACHE_TMP[address]['maxSuccess'], + amountBlocked: CACHE_TMP[address]['amountBlocked'], + queryLength: CACHE_TMP[address]['queryLength'], + queryTxs: CACHE_TMP[address]['queryTxs'] + }; + } + + async removeNonce(mainCurrencyCode, scanAddress, key) { + if (mainCurrencyCode !== 'ETH') { + return false; + } + const address = scanAddress.toLowerCase(); + const where = `WHERE currency_code='${mainCurrencyCode}' AND address='${address}' AND tmp_key='nonces' AND tmp_sub_key='${key}'`; + await Database.unsafeRawQuery( + tableName, + `DELETE FROM ${tableName} ${where}` + ); + await this.getCache(mainCurrencyCode, address); + } + + async saveNonce(mainCurrencyCode, scanAddress, key, value) { + if (mainCurrencyCode !== 'ETH') { + return false; + } + const address = scanAddress.toLowerCase(); + const now = new Date().toISOString(); + value = value * 1; + + const where = `WHERE currency_code='${mainCurrencyCode}' AND address='${address}' AND tmp_key='nonces' AND tmp_sub_key='${key}'`; + const res = (await Database.unsafeRawQuery( + tableName, + `SELECT tmp_val AS tmpVal FROM ${tableName} ${where}` + )) as TransactionScannersTmpDBModel[]; + if (res && res && res.length > 0) { + if (res[0].tmpVal * 1 >= value) { + return true; + } + await Database.unsafeRawQuery( + tableName, + `DELETE FROM ${tableName} ${where}` + ); + } + + const prepared = [ + { + currency_code: mainCurrencyCode, + address: address.toLowerCase(), + tmp_key: 'nonces', + tmp_sub_key: key, + tmp_val: value, + created_at: now + } + ]; + await Database.createModel(tableName, prepared); + await this.getCache(mainCurrencyCode, address); + } +} + +export default new EthTmpDS(); diff --git a/package.json b/package.json index cff185e28..8b7c52cbb 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ }, "dependencies": { "@babel/preset-env": "^7.21.5", + "@metamask/safe-event-emitter": "^3.0.0", "@morrowdigital/watermelondb-expo-plugin": "^2.1.2", "@neverdull-agency/expo-unlimited-secure-store": "^1.0.10", "@nozbe/watermelondb": "^0.26.0", @@ -51,6 +52,8 @@ "coinselect": "^3.1.13", "create-hash": "^1.2.0", "elliptic": "^6.5.4", + "ethereumjs-util": "^7.1.5", + "ethjs-provider-http": "^0.1.6", "expo": "~48.0.18", "expo-barcode-scanner": "~12.3.2", "expo-build-properties": "~0.6.0", diff --git a/src/appTypes/database/DatabaseTable.ts b/src/appTypes/database/DatabaseTable.ts index c6fcaf417..fe2b0911e 100644 --- a/src/appTypes/database/DatabaseTable.ts +++ b/src/appTypes/database/DatabaseTable.ts @@ -1,6 +1,7 @@ export enum DatabaseTable { Accounts = 'accounts', TransactionScannersTmp = 'transactions_scanners_tmp', + Transactions = 'transactions', TransactionRaw = 'transactions_raw', Wallets = 'wallets', WalletPub = 'wallet-pub' diff --git a/src/database/Database.ts b/src/database/Database.ts index a49005c42..5a1709c3b 100644 --- a/src/database/Database.ts +++ b/src/database/Database.ts @@ -55,6 +55,31 @@ class Database { } } + async updateModel(table: DatabaseTable, id: string, updateObj: any) { + if (!this.db) this.init(); + try { + const model = await this.db!.get(table).find(id); + await model.update((modelToUpdate) => { + for (const key in updateObj) { + // @ts-ignore + modelToUpdate[key] = updateObj[key]; + } + }); + } catch (error) { + // ignore + } + } + + async deleteModel(table: DatabaseTable, id: string) { + if (!this.db) this.init(); + try { + const model = await this.db!.get(table).find(id); + if (model) await model.destroyPermanently(); + } catch (error) { + // ignore + } + } + unEscapeString(goodString: string) { if (!goodString) return false; return goodString.replace(/quote/g, "'"); diff --git a/src/database/main.ts b/src/database/main.ts index 8fa27f3bb..d18d505e7 100644 --- a/src/database/main.ts +++ b/src/database/main.ts @@ -1,12 +1,15 @@ import { Platform } from 'react-native'; import SQLiteAdapter from '@nozbe/watermelondb/adapters/sqlite'; import { Database } from '@nozbe/watermelondb'; -import { WalletDBModel } from './models'; +import { + TransactionsDBModel, + WalletDBModel, + AccountDBModel, + WalletPubDBModel, + TransactionRawDBModel, + TransactionScannersTmpDBModel +} from './models'; import { schema } from './schemas'; -import { TransactionScannersTmpDBModel } from './models/transaction-scanners-tmp'; -import { WalletPubDBModel } from './models/wallet-pub'; -import { AccountDBModel } from './models/account'; -import { TransactionRawDBModel } from './models/transactions-raw'; const adapter = new SQLiteAdapter({ schema, @@ -20,6 +23,7 @@ export const database = new Database({ adapter, modelClasses: [ AccountDBModel, + TransactionsDBModel, TransactionRawDBModel, TransactionScannersTmpDBModel, WalletDBModel, diff --git a/src/database/models/index.ts b/src/database/models/index.ts index f9548aafc..8dd5021e8 100644 --- a/src/database/models/index.ts +++ b/src/database/models/index.ts @@ -1,2 +1,6 @@ +export * from './account'; export * from './transaction-scanners-tmp'; +export * from './transactions-raw'; +export * from './transactions'; export * from './wallet'; +export * from './wallet-pub'; diff --git a/src/database/models/transaction-scanners-tmp.ts b/src/database/models/transaction-scanners-tmp.ts index f34129b9d..6c4cc3b90 100644 --- a/src/database/models/transaction-scanners-tmp.ts +++ b/src/database/models/transaction-scanners-tmp.ts @@ -8,15 +8,15 @@ export class TransactionScannersTmpDBModel extends Model { // define fields // @ts-ignore - @text('currency_code') currency_code: string; + @text('currency_code') currencyCode: string; // @ts-ignore @text('address') address: string; // @ts-ignore - @text('tmp_key') tmp_key: string; + @text('tmp_key') tmpKey: string; // @ts-ignore - @text('tmp_sub_key') tmp_sub_key: string; + @text('tmp_sub_key') tmpSubKey: string; // @ts-ignore - @text('tmp_val') tmp_val: string; + @text('tmp_val') tmpVal: string; // @ts-ignore - @field('created_at') created_at: number; + @field('created_at') createdAt: number; } diff --git a/src/database/models/transactions.ts b/src/database/models/transactions.ts new file mode 100644 index 000000000..ac4d1b289 --- /dev/null +++ b/src/database/models/transactions.ts @@ -0,0 +1,84 @@ +/* eslint-disable camelcase */ +import { DatabaseTable } from '@appTypes'; +import { Model } from '@nozbe/watermelondb'; +import { text, field } from '@nozbe/watermelondb/decorators'; + +export class TransactionsDBModel extends Model { + static table = DatabaseTable.Transactions; + + // define fields + // @ts-ignore + @text('currency_code') currencyCode: string; + // @ts-ignore + @text('hash') hash: string; + // @ts-ignore + @field('account_id') accountId: number; + // @ts-ignore + @text('transaction_hash') transactionHash: string; + // @ts-ignore + @text('transaction_hash_basic') transactionHashBasic: string; + // @ts-ignore + @text('block_hash') blockHash: string; + // @ts-ignore + @field('block_number') blockNumber: number; + // @ts-ignore + @field('block_confirmations') blockConfirmations: number; + // @ts-ignore + @text('transaction_status') transactionStatus: string; + // @ts-ignore + @text('transaction_direction') transactionDirection: string; + // @ts-ignore + @field('transaction_of_airdao_wallet') transactionOfAirDAOWallet: number; + // @ts-ignore + @text('address_to') addressTo: string; + // @ts-ignore + @text('address_to_basic') addressToBasic: string; + // @ts-ignore + @text('address_from') addressFrom: string; + // @ts-ignore + @text('address_from_basic') addressFromBasic: string; + // @ts-ignore + @field('address_from_basic') addressFromBasic: string; + // @ts-ignore + @field('address_amount') addressAmount: string; + // @ts-ignore + @text('transaction_fee_currency_code') transactionFeeCurrencyCode: string; + // @ts-ignore + @text('transaction_filter_type') transactionFilterType: string; + // @ts-ignore + @text('vout') vout: string; + // @ts-ignore + @text('vin') vin: string; + // @ts-ignore + @field('input_value') inputValue: number; + // @ts-ignore + @text('transaction_json') transactionJSON: string; + // @ts-ignore + @field('transactions_scan_time') transactions_scan_time: number; + // @ts-ignore + @text('transactions_scan_log') transactionScanLog: string; + // @ts-ignore + @text('transactions_other_hashes') transactionsOtherHashes: string; + // @ts-ignore + @text('bse_order_id') bseOrderId: string; + // @ts-ignore + @text('bse_order_id_in') bseOrderIdIn: string; + // @ts-ignore + @text('bse_order_id_out') bseOrderIdOut: string; + // @ts-ignore + @text('bse_order_data') bseOrderData: string; + // @ts-ignore + @field('lock_time') lockTime: number; + // @ts-ignore + @field('block_time') blockTime: number; + // @ts-ignore + @field('created_at') createdAt: number; + // @ts-ignore + @field('mined_at') minedAt: number; + // @ts-ignore + @field('updated_at') updatedAt: number; + // @ts-ignore + @field('hidden_at') hiddenAt: number; + // @ts-ignore + @text('special_action_needed') specialActionNeeded: string; +} diff --git a/src/database/schemas/index.ts b/src/database/schemas/index.ts index 435d9dd83..5ed5ada65 100644 --- a/src/database/schemas/index.ts +++ b/src/database/schemas/index.ts @@ -4,11 +4,13 @@ import { TransactionScannersTmpTable } from './transaction-scanners-tmp'; import { WalletPubTable } from './wallet-pub'; import { AccountsTable } from './account'; import { TransactionRawTable } from './transaction-raw'; +import { TransactionsTable } from './transactions'; export const schema = appSchema({ version: 1, tables: [ AccountsTable, + TransactionsTable, TransactionRawTable, TransactionScannersTmpTable, WalletTable, diff --git a/src/database/schemas/transactions.ts b/src/database/schemas/transactions.ts new file mode 100644 index 000000000..4c94538be --- /dev/null +++ b/src/database/schemas/transactions.ts @@ -0,0 +1,160 @@ +import { DatabaseTable } from '@appTypes'; +import { tableSchema } from '@nozbe/watermelondb'; + +export const TransactionsTable = tableSchema({ + name: DatabaseTable.Transactions, + columns: [ + { + name: 'currency_code', + type: 'string' + }, + { + name: 'hash', + type: 'string' + }, + { + name: 'account_id', + type: 'number' + }, + { + name: 'transaction_hash', + type: 'string' + }, + { + name: 'transaction_hash_basic', + type: 'string' + }, + { + name: 'block_hash', + type: 'string' + }, + { + name: 'block_number', + type: 'number' + }, + { + name: 'block_confirmations', + type: 'number' + }, + { + name: 'transaction_status', + type: 'string' + }, + { + name: 'transaction_direction', + type: 'string' + }, + { + name: 'transaction_of_airdao_wallet', + type: 'number' + }, + { + name: 'address_to', + type: 'string' + }, + { + name: 'address_to_basic', + type: 'string' + }, + { + name: 'address_from', + type: 'string' + }, + { + name: 'address_from_basic', + type: 'string' + }, + { + name: 'address_amount', + type: 'number' + }, + { + name: 'transaction_fee', + type: 'number' + }, + { + name: 'transaction_fee_currency_code', + type: 'string' + }, + { + name: 'transaction_filter_type', + type: 'string' + }, + { + name: 'vout', + type: 'string' + }, + { + name: 'vin', + type: 'string' + }, + { + name: 'contract_address', + type: 'string' + }, + { + name: 'input_value', + type: 'number' + }, + { + name: 'transaction_json', + type: 'string' + }, + { + name: 'transactions_scan_time', + type: 'number' + }, + { + name: 'transactions_scan_log', + type: 'string' + }, + { + name: 'transactions_other_hashes', + type: 'string' + }, + { + name: 'bse_order_id', + type: 'string' + }, + { + name: 'bse_order_id_in', + type: 'string' + }, + { + name: 'bse_order_id_out', + type: 'string' + }, + { + name: 'bse_order_data', + type: 'string' + }, + { + name: 'lock_time', + type: 'number' + }, + { + name: 'block_time', + type: 'number' + }, + { + name: 'created_at', + type: 'number' + }, + { + name: 'mined_at', + type: 'number' + }, + { + name: 'updated_at', + type: 'number' + }, + { + name: 'hidden_at', + type: 'number' + }, + { + name: 'special_action_needed', + type: 'string' + } + ] +}); diff --git a/yarn.lock b/yarn.lock index 7882e83cd..06131ab8f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2410,6 +2410,11 @@ dependencies: dequal "^1.0.0" +"@metamask/safe-event-emitter@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@metamask/safe-event-emitter/-/safe-event-emitter-3.0.0.tgz#8c2b9073fe0722d48693143b0dc8448840daa3bd" + integrity sha512-j6Z47VOmVyGMlnKXZmL0fyvWfEYtKWCA9yGZkU3FCsGZUT5lHGmvaV9JA5F2Y+010y7+ROtR3WMXIkvl/nVzqQ== + "@morrowdigital/watermelondb-expo-plugin@^2.1.2": version "2.1.2" resolved "https://registry.yarnpkg.com/@morrowdigital/watermelondb-expo-plugin/-/watermelondb-expo-plugin-2.1.2.tgz#7c511d671cba68984d5b77a78d3b998bfb57a750" @@ -2941,6 +2946,13 @@ dependencies: "@types/node" "*" +"@types/bn.js@^5.1.0": + version "5.1.1" + resolved "https://registry.yarnpkg.com/@types/bn.js/-/bn.js-5.1.1.tgz#b51e1b55920a4ca26e9285ff79936bbdec910682" + integrity sha512-qNrYbZqMx0uJAfKnKclPh+dTwK33KfLHYqtyODwd5HnXOjnkhc4qgn3BrK6RWyGZm5+sIFE7Q7Vz6QQtJB7w7g== + dependencies: + "@types/node" "*" + "@types/connect@^3.4.33": version "3.4.35" resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.35.tgz#5fcf6ae445e4021d1fc2219a4873cc73a3bb2ad1" @@ -3026,6 +3038,13 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.55.tgz#c329cbd434c42164f846b909bd6f85b5537f6240" integrity sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ== +"@types/pbkdf2@^3.0.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@types/pbkdf2/-/pbkdf2-3.1.0.tgz#039a0e9b67da0cdc4ee5dab865caa6b267bb66b1" + integrity sha512-Cf63Rv7jCQ0LaL8tNXmEyqTHuIJxRdlS5vMh1mj5voN4+QFhVZnlZruezqpWYDiJ8UTzhP0VmeLXCmBk66YrMQ== + dependencies: + "@types/node" "*" + "@types/pixelmatch@^5.2.4": version "5.2.4" resolved "https://registry.yarnpkg.com/@types/pixelmatch/-/pixelmatch-5.2.4.tgz#ca145cc5ede1388c71c68edf2d1f5190e5ddd0f6" @@ -3080,6 +3099,13 @@ resolved "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz" integrity sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ== +"@types/secp256k1@^4.0.1": + version "4.0.3" + resolved "https://registry.yarnpkg.com/@types/secp256k1/-/secp256k1-4.0.3.tgz#1b8e55d8e00f08ee7220b4d59a6abe89c37a901c" + integrity sha512-Da66lEIFeIz9ltsdMZcpQvmrmmoqrfju8pm1BH8WbYjZSwUgCwXLb9C+9XYogwBITnbsSaMdVPb2ekf7TV+03w== + dependencies: + "@types/node" "*" + "@types/semver@^7.3.12": version "7.3.13" resolved "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz" @@ -3941,6 +3967,11 @@ bl@^4.1.0: inherits "^2.0.4" readable-stream "^3.4.0" +blakejs@^1.1.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/blakejs/-/blakejs-1.2.1.tgz#5057e4206eadb4a97f7c0b6e197a505042fc3814" + integrity sha512-QXUSXI3QVc/gJME0dBpXrag1kbzOqCjCX8/b54ntNyW6sjtoqxqRk3LTmXzaJoh71zMsDCjM+47jS7XiwN/+fQ== + blueimp-md5@^2.10.0: version "2.19.0" resolved "https://registry.npmjs.org/blueimp-md5/-/blueimp-md5-2.19.0.tgz" @@ -3951,7 +3982,7 @@ bn.js@^4.11.8, bn.js@^4.11.9: resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA== -bn.js@^5.0.0, bn.js@^5.2.1: +bn.js@^5.0.0, bn.js@^5.1.2, bn.js@^5.2.0, bn.js@^5.2.1: version "5.2.1" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.1.tgz#0bc527a6a0d18d0aa8d5b0538ce4a77dccfa7b70" integrity sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ== @@ -4046,6 +4077,18 @@ brorand@^1.1.0: resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" integrity sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w== +browserify-aes@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.2.0.tgz#326734642f403dabc3003209853bb70ad428ef48" + integrity sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA== + dependencies: + buffer-xor "^1.0.3" + cipher-base "^1.0.0" + create-hash "^1.1.0" + evp_bytestokey "^1.0.3" + inherits "^2.0.1" + safe-buffer "^5.0.1" + browserslist@^4.21.3, browserslist@^4.21.5: version "4.21.5" resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz" @@ -4070,7 +4113,7 @@ bs58@^5.0.0: dependencies: base-x "^4.0.0" -bs58check@<3.0.0, bs58check@^2.0.0, bs58check@^2.1.1: +bs58check@<3.0.0, bs58check@^2.0.0, bs58check@^2.1.1, bs58check@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/bs58check/-/bs58check-2.1.2.tgz#53b018291228d82a5aa08e7d796fdafda54aebfc" integrity sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA== @@ -4117,6 +4160,11 @@ buffer-from@^1.0.0: resolved "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz" integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== +buffer-xor@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" + integrity sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ== + buffer@6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.1.tgz#3cbea8c1463e5a0779e30b66d4c88c6ffa182ac2" @@ -4326,7 +4374,7 @@ ci-info@^3.2.0, ci-info@^3.3.0, ci-info@^3.7.0: resolved "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz" integrity sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw== -cipher-base@^1.0.1, cipher-base@^1.0.3: +cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: version "1.0.4" resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de" integrity sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q== @@ -4663,7 +4711,7 @@ crc-32@^1.2.2: resolved "https://registry.yarnpkg.com/crc-32/-/crc-32-1.2.2.tgz#3cad35a934b8bf71f25ca524b6da51fb7eace2ff" integrity sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ== -create-hash@^1.1.0, create-hash@^1.2.0: +create-hash@^1.1.0, create-hash@^1.1.2, create-hash@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196" integrity sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg== @@ -4674,7 +4722,7 @@ create-hash@^1.1.0, create-hash@^1.2.0: ripemd160 "^2.0.1" sha.js "^2.4.0" -create-hmac@^1.1.3, create-hmac@^1.1.7: +create-hmac@^1.1.3, create-hmac@^1.1.4, create-hmac@^1.1.7: version "1.1.7" resolved "https://registry.yarnpkg.com/create-hmac/-/create-hmac-1.1.7.tgz#69170c78b3ab957147b2b8b04572e47ead2243ff" integrity sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg== @@ -5580,6 +5628,27 @@ etag@~1.8.1: resolved "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz" integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== +ethereum-cryptography@^0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz#8d6143cfc3d74bf79bbd8edecdf29e4ae20dd191" + integrity sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ== + dependencies: + "@types/pbkdf2" "^3.0.0" + "@types/secp256k1" "^4.0.1" + blakejs "^1.1.0" + browserify-aes "^1.2.0" + bs58check "^2.1.2" + create-hash "^1.2.0" + create-hmac "^1.1.7" + hash.js "^1.1.7" + keccak "^3.0.0" + pbkdf2 "^3.0.17" + randombytes "^2.1.0" + safe-buffer "^5.1.2" + scrypt-js "^3.0.0" + secp256k1 "^4.0.1" + setimmediate "^1.0.5" + ethereum-cryptography@^2.0.0: version "2.1.2" resolved "https://registry.yarnpkg.com/ethereum-cryptography/-/ethereum-cryptography-2.1.2.tgz#18fa7108622e56481157a5cb7c01c0c6a672eb67" @@ -5590,6 +5659,24 @@ ethereum-cryptography@^2.0.0: "@scure/bip32" "1.3.1" "@scure/bip39" "1.2.1" +ethereumjs-util@^7.1.5: + version "7.1.5" + resolved "https://registry.yarnpkg.com/ethereumjs-util/-/ethereumjs-util-7.1.5.tgz#9ecf04861e4fbbeed7465ece5f23317ad1129181" + integrity sha512-SDl5kKrQAudFBUe5OJM9Ac6WmMyYmXX/6sTmLZ3ffG2eY6ZIGBes3pEDxNN6V72WyOw4CPD5RomKdsa8DAAwLg== + dependencies: + "@types/bn.js" "^5.1.0" + bn.js "^5.1.2" + create-hash "^1.1.2" + ethereum-cryptography "^0.1.3" + rlp "^2.2.4" + +ethjs-provider-http@^0.1.6: + version "0.1.6" + resolved "https://registry.yarnpkg.com/ethjs-provider-http/-/ethjs-provider-http-0.1.6.tgz#1ec5d9b4be257ef1d56a500b22a741985e889420" + integrity sha512-y054N5xyyx43KTQjgdkAEj2uEa/flwpENU5ldx/rmA0Q2yy0vyB2lsOIn/7V0uADMc4iRSHZfnFc9b9YS5Qkdw== + dependencies: + xhr2 "0.1.3" + event-target-shim@^5.0.0, event-target-shim@^5.0.1: version "5.0.1" resolved "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz" @@ -5600,6 +5687,14 @@ eventemitter3@^4.0.7: resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== +evp_bytestokey@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz#7fcbdb198dc71959432efe13842684e0525acb02" + integrity sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA== + dependencies: + md5.js "^1.3.4" + safe-buffer "^5.1.1" + exec-async@^2.2.0: version "2.2.0" resolved "https://registry.npmjs.org/exec-async/-/exec-async-2.2.0.tgz" @@ -6593,7 +6688,7 @@ hash-base@^3.0.0: readable-stream "^3.6.0" safe-buffer "^5.2.0" -hash.js@1.1.7, hash.js@^1.0.0, hash.js@^1.0.3: +hash.js@1.1.7, hash.js@^1.0.0, hash.js@^1.0.3, hash.js@^1.1.7: version "1.1.7" resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42" integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA== @@ -8002,6 +8097,15 @@ jsonpointer@^5.0.0: array-includes "^3.1.5" object.assign "^4.1.3" +keccak@^3.0.0: + version "3.0.3" + resolved "https://registry.yarnpkg.com/keccak/-/keccak-3.0.3.tgz#4bc35ad917be1ef54ff246f904c2bbbf9ac61276" + integrity sha512-JZrLIAJWuZxKbCilMpNz5Vj7Vtb4scDG3dMXLOsbzBmQGyjwE61BbW7bJkfKKCShXiQZt3T6sBgALRtmd+nZaQ== + dependencies: + node-addon-api "^2.0.0" + node-gyp-build "^4.2.0" + readable-stream "^3.6.0" + kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: version "3.2.2" resolved "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz" @@ -9403,6 +9507,17 @@ path-type@^4.0.0: resolved "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== +pbkdf2@^3.0.17: + version "3.1.2" + resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.1.2.tgz#dd822aa0887580e52f1a039dc3eda108efae3075" + integrity sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA== + dependencies: + create-hash "^1.1.2" + create-hmac "^1.1.4" + ripemd160 "^2.0.1" + safe-buffer "^5.0.1" + sha.js "^2.4.8" + picocolors@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz" @@ -9669,7 +9784,7 @@ queue-microtask@^1.2.2: resolved "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz" integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== -randombytes@^2.0.1: +randombytes@^2.0.1, randombytes@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== @@ -10253,6 +10368,13 @@ ripemd160@^2.0.0, ripemd160@^2.0.1: hash-base "^3.0.0" inherits "^2.0.1" +rlp@^2.2.4: + version "2.2.7" + resolved "https://registry.yarnpkg.com/rlp/-/rlp-2.2.7.tgz#33f31c4afac81124ac4b283e2bd4d9720b30beaf" + integrity sha512-d5gdPmgQ0Z+AklL2NVXr/IoSjNZFfTVvQWzL/AM2AOcSzYP2xjlb0AC8YyCLc41MSNf6P6QVtjgPdmVtzb+4lQ== + dependencies: + bn.js "^5.2.0" + rpc-websockets@^7.4.2: version "7.5.1" resolved "https://registry.yarnpkg.com/rpc-websockets/-/rpc-websockets-7.5.1.tgz#e0a05d525a97e7efc31a0617f093a13a2e10c401" @@ -10342,7 +10464,12 @@ scheduler@^0.23.0: dependencies: loose-envify "^1.1.0" -secp256k1@^4.0.2: +scrypt-js@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/scrypt-js/-/scrypt-js-3.0.1.tgz#d314a57c2aef69d1ad98a138a21fe9eafa9ee312" + integrity sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA== + +secp256k1@^4.0.1, secp256k1@^4.0.2: version "4.0.3" resolved "https://registry.yarnpkg.com/secp256k1/-/secp256k1-4.0.3.tgz#c4559ecd1b8d3c1827ed2d1b94190d69ce267303" integrity sha512-NLZVf+ROMxwtEj3Xa562qgv2BK5e2WNmXPiOdVIPLgs6lyTzMvBq0aWTYMI5XCP9jZMVKOcqZLw/Wc4vDkuxhA== @@ -12100,6 +12227,11 @@ xcode@^3.0.0, xcode@^3.0.1: simple-plist "^1.1.0" uuid "^7.0.3" +xhr2@0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/xhr2/-/xhr2-0.1.3.tgz#cbfc4759a69b4a888e78cf4f20b051038757bd11" + integrity sha512-6RmGK22QwC7yXB1CRwyLWuS2opPcKOlAu0ViAnyZjDlzrEmCKL4kLHkfvB8oMRWeztMsNoDGAjsMZY15w/4tTw== + xml-js@^1.6.11: version "1.6.11" resolved "https://registry.yarnpkg.com/xml-js/-/xml-js-1.6.11.tgz#927d2f6947f7f1c19a316dd8eea3614e8b18f8e9" From 86150c32c77c30181cd6733ee287030285da7ed3 Mon Sep 17 00:00:00 2001 From: illiaa Date: Thu, 3 Aug 2023 10:49:38 +0300 Subject: [PATCH 025/509] fixed errors in some files, rewritten ltc and metis folders --- .../blockchains/doge/DogeTransferProcessor.ts | 6 +- crypto/blockchains/ltc/LtcScannerProcessor.js | 20 --- crypto/blockchains/ltc/LtcScannerProcessor.ts | 19 +++ .../blockchains/ltc/LtcTransferProcessor.ts | 76 +++++---- ...rProcessor.js => MetisScannerProcessor.ts} | 17 +- .../metis/MetisTransferProcessor.ts | 151 +++++++++--------- crypto/blockchains/one/OneScannerProcessor.ts | 71 ++++---- .../one/OneScannerProcessorErc20.ts | 66 ++++---- crypto/blockchains/sol/SolScannerProcessor.ts | 2 +- .../blockchains/sol/SolScannerProcessorSpl.ts | 3 +- crypto/blockchains/sol/SolTokenProcessor.ts | 20 ++- .../blockchains/sol/SolTransferProcessor.ts | 34 ++-- crypto/blockchains/sol/ext/SolUtils.ts | 69 ++++---- crypto/blockchains/trx/TrxScannerProcessor.ts | 22 +-- crypto/blockchains/trx/TrxTokenProcessor.ts | 20 ++- .../blockchains/trx/TrxTransferProcessor.ts | 127 ++++++++------- .../blockchains/usdt/UsdtTransferProcessor.ts | 35 ++-- crypto/blockchains/usdt/tx/UsdtTxBuilder.ts | 6 +- .../usdt/tx/UsdtTxInputsOutputs.ts | 27 ++-- crypto/blockchains/vet/VetScannerProcessor.ts | 7 +- .../blockchains/vet/VetTransferProcessor.ts | 36 ++--- .../waves/WavesScannerProcessorErc20.ts | 2 +- .../waves/WavesTransferProcessor.ts | 68 ++++---- .../blockchains/xlm/XlmTransferProcessor.ts | 39 ++--- .../xlm/basic/XlmTxSendProvider.ts | 19 ++- crypto/blockchains/xmr/XmrAddressProcessor.ts | 17 +- crypto/blockchains/xmr/XmrScannerProcessor.ts | 65 +++++--- crypto/blockchains/xmr/XmrSecretsProcessor.ts | 23 ++- .../blockchains/xmr/XmrTransferProcessor.ts | 111 ++++--------- crypto/blockchains/xmr/ext/MoneroMnemonic.ts | 3 +- crypto/blockchains/xmr/ext/MoneroUtils.ts | 2 +- .../blockchains/xmr/ext/MoneroUtilsParser.ts | 5 +- .../xmr/providers/XmrSendProvider.ts | 134 +++++++++------- .../xmr/providers/XmrUnspentsProvider.ts | 57 ++++--- crypto/blockchains/xrp/XrpScannerProcessor.ts | 17 +- .../blockchains/xrp/XrpTransferProcessor.ts | 65 +++----- .../xrp/basic/XrpDataRippleProvider.ts | 26 +-- .../xrp/basic/XrpDataScanProvider.ts | 128 +++++++++------ .../xrp/basic/XrpTxSendProvider.ts | 63 +++----- crypto/blockchains/xrp/stores/XrpTmpDS.ts | 5 +- .../xvg/providers/XvgSendProvider.ts | 32 +--- .../xvg/providers/XvgUnspentsProvider.ts | 59 +++---- crypto/blockchains/xvg/stores/XvgTmpDS.ts | 7 +- 43 files changed, 913 insertions(+), 868 deletions(-) delete mode 100644 crypto/blockchains/ltc/LtcScannerProcessor.js create mode 100644 crypto/blockchains/ltc/LtcScannerProcessor.ts rename crypto/blockchains/metis/{MetisScannerProcessor.js => MetisScannerProcessor.ts} (80%) diff --git a/crypto/blockchains/doge/DogeTransferProcessor.ts b/crypto/blockchains/doge/DogeTransferProcessor.ts index 43997f2af..79209d887 100644 --- a/crypto/blockchains/doge/DogeTransferProcessor.ts +++ b/crypto/blockchains/doge/DogeTransferProcessor.ts @@ -42,7 +42,7 @@ export default class DogeTransferProcessor feeMinTotalReadable: 1 }; - _initedProviders: boolean = false; + _initedProviders = false; _settings: BlocksoftBlockchainTypes.CurrencySettings; @@ -322,8 +322,8 @@ export default class DogeTransferProcessor prevFeeForByte = feeForByte; } - let uniqueFees = {}; - let allFees = {}; + const uniqueFees = {}; + const allFees = {}; let isError = false; for (const key of keys) { // @ts-ignore diff --git a/crypto/blockchains/ltc/LtcScannerProcessor.js b/crypto/blockchains/ltc/LtcScannerProcessor.js deleted file mode 100644 index 5198d20b0..000000000 --- a/crypto/blockchains/ltc/LtcScannerProcessor.js +++ /dev/null @@ -1,20 +0,0 @@ -/** - * @version 0.5 - */ - -import BtcScannerProcessor from '../btc/BtcScannerProcessor' - -export default class LtcScannerProcessor extends BtcScannerProcessor { - - /** - * @type {number} - * @private - */ - _blocksToConfirm = 10 - - /** - * @type {string} - * @private - */ - _trezorServerCode = 'LTC_TREZOR_SERVER' -} diff --git a/crypto/blockchains/ltc/LtcScannerProcessor.ts b/crypto/blockchains/ltc/LtcScannerProcessor.ts new file mode 100644 index 000000000..6aa7d1de7 --- /dev/null +++ b/crypto/blockchains/ltc/LtcScannerProcessor.ts @@ -0,0 +1,19 @@ +/** + * @version 0.5 + */ + +import BtcScannerProcessor from '../btc/BtcScannerProcessor'; +// @ts-ignore +export default class LtcScannerProcessor extends BtcScannerProcessor { + /** + * @type {number} + * @private + */ + _blocksToConfirm = 10; + + /** + * @type {string} + * @private + */ + _trezorServerCode = 'LTC_TREZOR_SERVER'; +} diff --git a/crypto/blockchains/ltc/LtcTransferProcessor.ts b/crypto/blockchains/ltc/LtcTransferProcessor.ts index 70c7336bd..8a102591e 100644 --- a/crypto/blockchains/ltc/LtcTransferProcessor.ts +++ b/crypto/blockchains/ltc/LtcTransferProcessor.ts @@ -2,39 +2,53 @@ * @version 0.20 */ -import BtcTransferProcessor from '@crypto/blockchains/btc/BtcTransferProcessor' -import { BlocksoftBlockchainTypes } from '@crypto/blockchains/BlocksoftBlockchainTypes' -import DogeNetworkPrices from '@crypto/blockchains/doge/basic/DogeNetworkPrices' -import BtcUnspentsProvider from '@crypto/blockchains/btc/providers/BtcUnspentsProvider' -import DogeSendProvider from '@crypto/blockchains/doge/providers/DogeSendProvider' -import BtcTxInputsOutputs from '@crypto/blockchains/btc/tx/BtcTxInputsOutputs' -import BtcTxBuilder from '@crypto/blockchains/btc/tx/BtcTxBuilder' +import BtcTransferProcessor from '@crypto/blockchains/btc/BtcTransferProcessor'; +import DogeNetworkPrices from '@crypto/blockchains/doge/basic/DogeNetworkPrices'; +import BtcUnspentsProvider from '@crypto/blockchains/btc/providers/BtcUnspentsProvider'; +import DogeSendProvider from '@crypto/blockchains/doge/providers/DogeSendProvider'; +import BtcTxInputsOutputs from '@crypto/blockchains/btc/tx/BtcTxInputsOutputs'; +import BtcTxBuilder from '@crypto/blockchains/btc/tx/BtcTxBuilder'; +import { AirDAOBlockchainTypes } from '@crypto/blockchains/AirDAOBlockchainTypes'; -export default class LtcTransferProcessor extends BtcTransferProcessor implements BlocksoftBlockchainTypes.TransferProcessor { +export default class LtcTransferProcessor + extends BtcTransferProcessor + implements AirDAOBlockchainTypes.TransferProcessor +{ + _trezorServerCode = 'LTC_TREZOR_SERVER'; - _trezorServerCode = 'LTC_TREZOR_SERVER' + _builderSettings: AirDAOBlockchainTypes.BuilderSettings = { + minOutputDustReadable: 0.000005, + minChangeDustReadable: 0.00001, + feeMaxForByteSatoshi: 10000, // for tx builder + feeMaxAutoReadable2: 0.2, // for fee calc, + feeMaxAutoReadable6: 0.1, // for fee calc + feeMaxAutoReadable12: 0.05, // for fee calc + changeTogether: true + }; - _builderSettings: BlocksoftBlockchainTypes.BuilderSettings = { - minOutputDustReadable: 0.000005, - minChangeDustReadable: 0.00001, - feeMaxForByteSatoshi: 10000, // for tx builder - feeMaxAutoReadable2: 0.2, // for fee calc, - feeMaxAutoReadable6: 0.1, // for fee calc - feeMaxAutoReadable12: 0.05, // for fee calc - changeTogether: true - } + _initProviders() { + if (this._initedProviders) return false; + this.unspentsProvider = new BtcUnspentsProvider( + this._settings, + this._trezorServerCode + ); + this.sendProvider = new DogeSendProvider( + this._settings, + this._trezorServerCode + ); + this.txPrepareInputsOutputs = new BtcTxInputsOutputs( + this._settings, + this._builderSettings + ); + this.txBuilder = new BtcTxBuilder(this._settings, this._builderSettings); + this.networkPrices = new DogeNetworkPrices(); + this._initedProviders = true; + } - _initProviders() { - if (this._initedProviders) return false - this.unspentsProvider = new BtcUnspentsProvider(this._settings, this._trezorServerCode) - this.sendProvider = new DogeSendProvider(this._settings, this._trezorServerCode) - this.txPrepareInputsOutputs = new BtcTxInputsOutputs(this._settings, this._builderSettings) - this.txBuilder = new BtcTxBuilder(this._settings, this._builderSettings) - this.networkPrices = new DogeNetworkPrices() - this._initedProviders = true - } - - canRBF(data: BlocksoftBlockchainTypes.DbAccount, transaction: BlocksoftBlockchainTypes.DbTransaction): boolean { - return false - } + canRBF( + data: AirDAOBlockchainTypes.DbAccount, + transaction: AirDAOBlockchainTypes.DbTransaction + ): boolean { + return false; + } } diff --git a/crypto/blockchains/metis/MetisScannerProcessor.js b/crypto/blockchains/metis/MetisScannerProcessor.ts similarity index 80% rename from crypto/blockchains/metis/MetisScannerProcessor.js rename to crypto/blockchains/metis/MetisScannerProcessor.ts index 4efc2322c..757f12d5c 100644 --- a/crypto/blockchains/metis/MetisScannerProcessor.js +++ b/crypto/blockchains/metis/MetisScannerProcessor.ts @@ -2,22 +2,32 @@ * @version 0.5 */ import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog'; - import EthScannerProcessor from '@crypto/blockchains/eth/EthScannerProcessor'; +interface UnifiedTransaction { + hash: string; + from: string; + to: string; + value: string; +} + export default class MetisScannerProcessor extends EthScannerProcessor { /** * @param {string} scanData.account.address * @return {Promise<[UnifiedTransaction]>} */ - async getTransactionsBlockchain(scanData) { + // @ts-ignore + async getTransactionsBlockchain(scanData: { + account: { address: string }; + }): Promise { const address = scanData.account.address; await BlocksoftCryptoLog.log( this._settings.currencyCode + ' MetisScannerProcessor.getTransactions started ' + address ); - const transactions = await super.getTransactionsBlockchain(scanData); + const transactions: UnifiedTransaction[] = + await super.getTransactionsBlockchain(scanData); // https://andromeda-explorer.metis.io/token/0xDeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD0000/token-transfers // actual deposits and withdrawals done as erc20 token transfer @@ -35,6 +45,7 @@ export default class MetisScannerProcessor extends EthScannerProcessor { ); if (depositTransactions) { for (const transactionHash in depositTransactions) { + // @ts-ignore transactions.push(depositTransactions[transactionHash]); } } diff --git a/crypto/blockchains/metis/MetisTransferProcessor.ts b/crypto/blockchains/metis/MetisTransferProcessor.ts index 1b5d5a92a..1f22fda84 100644 --- a/crypto/blockchains/metis/MetisTransferProcessor.ts +++ b/crypto/blockchains/metis/MetisTransferProcessor.ts @@ -1,77 +1,74 @@ -/** - * @author Ksu - * @version 0.43 - */ -import { BlocksoftBlockchainTypes } from '@crypto/blockchains/BlocksoftBlockchainTypes'; -import EthTransferProcessor from '@crypto/blockchains/eth/EthTransferProcessor'; -import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings'; -import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; -import BlocksoftUtils from '@crypto/common/BlocksoftUtils'; - -export default class MetisTransferProcessor - extends EthTransferProcessor - implements BlocksoftBlockchainTypes.TransferProcessor -{ - async getFeeRate( - data: BlocksoftBlockchainTypes.TransferData, - privateData: BlocksoftBlockchainTypes.TransferPrivateData, - additionalData: {} = {} - ): Promise { - if ( - typeof additionalData.gasPrice === 'undefined' || - !additionalData.gasPrice - ) { - additionalData.gasPrice = BlocksoftExternalSettings.getStatic( - this._mainCurrencyCode + '_PRICE' - ); - additionalData.gasPriceTitle = 'speed_blocks_2'; - } - - let value = 0; - try { - if (data.amount.indexOf('0x') === 0) { - value = data.amount; - } else { - value = '0x' + BlocksoftUtils.decimalToHex(data.amount); - } - } catch (e) { - throw new Error(e.message + ' with data.amount ' + data.amount); - } - const params = { - jsonrpc: '2.0', - method: 'eth_estimateGas', - params: [ - { - from: data.addressFrom, - to: data.addressTo, - value: value, - data: '0x' - } - ], - id: 1 - }; - const tmp = await BlocksoftAxios.post( - BlocksoftExternalSettings.getStatic('METIS_SERVER'), - params - ); - - if (typeof tmp !== 'undefined' && typeof tmp.data !== 'undefined') { - if (typeof tmp.data.result !== 'undefined') { - additionalData.gasLimit = BlocksoftUtils.hexToDecimalWalletConnect( - tmp.data.result - ); - } else if (typeof tmp.data.error !== 'undefined') { - throw new Error(tmp.data.error.message); - } - } - return super.getFeeRate(data, privateData, additionalData); - } - - canRBF( - data: BlocksoftBlockchainTypes.DbAccount, - transaction: BlocksoftBlockchainTypes.DbTransaction, - source: string - ): boolean { - return false; - } -} +import { AirDAOBlockchainTypes } from '@crypto/blockchains/AirDAOBlockchainTypes'; +import EthTransferProcessor from '@crypto/blockchains/eth/EthTransferProcessor'; +import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings'; +import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; +import BlocksoftUtils from '@crypto/common/BlocksoftUtils'; + +export default class MetisTransferProcessor + extends EthTransferProcessor + implements AirDAOBlockchainTypes.TransferProcessor +{ + async getFeeRate( + data: AirDAOBlockchainTypes.TransferData, + privateData: AirDAOBlockchainTypes.TransferPrivateData, + additionalData: AirDAOBlockchainTypes.TransferAdditionalData = {} + ): Promise { + if ( + typeof additionalData.gasPrice === 'undefined' || + !additionalData.gasPrice + ) { + additionalData.gasPrice = BlocksoftExternalSettings.getStatic( + this._mainCurrencyCode + '_PRICE' + ); + additionalData.gasPriceTitle = 'speed_blocks_2'; + } + + let value = '0x0'; + try { + if (data.amount.indexOf('0x') === 0) { + value = data.amount; + } else { + value = '0x' + BlocksoftUtils.decimalToHex(data.amount); + } + } catch (e) { + throw new Error(e.message + ' with data.amount ' + data.amount); + } + const params = { + jsonrpc: '2.0', + method: 'eth_estimateGas', + params: [ + { + from: data.addressFrom, + to: data.addressTo, + value, + data: '0x' + } + ], + id: 1 + }; + const tmp = await BlocksoftAxios.post( + BlocksoftExternalSettings.getStatic('METIS_SERVER'), + params + ); + + if (typeof tmp !== undefined && typeof tmp.data !== undefined) { + if (typeof tmp.data.result !== undefined) { + // @ts-ignore + additionalData.gasLimit = BlocksoftUtils.hexToDecimalWalletConnect( + tmp.data.result + ); + } else if (typeof tmp.data.error !== undefined) { + throw new Error(tmp.data.error.message); + } + } + return super.getFeeRate(data, privateData, additionalData); + } + + canRBF( + data: AirDAOBlockchainTypes.DbAccount, + transaction: AirDAOBlockchainTypes.DbTransaction, + source: string + ): boolean { + return false; + } +} diff --git a/crypto/blockchains/one/OneScannerProcessor.ts b/crypto/blockchains/one/OneScannerProcessor.ts index 622027555..b45568e6d 100644 --- a/crypto/blockchains/one/OneScannerProcessor.ts +++ b/crypto/blockchains/one/OneScannerProcessor.ts @@ -34,7 +34,7 @@ interface UnifiedTransaction { export default class OneScannerProcessor { private _blocksToConfirm = 10; - private _settings: any; // Replace 'any' with the actual type of settings + private _settings: any; constructor(settings: any) { this._settings = settings; @@ -68,19 +68,24 @@ export default class OneScannerProcessor { params: [oneAddress, 'latest'] }; const res = await BlocksoftAxios._request(apiPath, 'POST', data); - if (typeof res.data === 'undefined') { - return false; - } - if (typeof res.data.error !== 'undefined') { - throw new Error(JSON.stringify(res.data.error)); - } - if (typeof res.data.result === 'undefined') { - return false; + + if (res !== false && res.data !== undefined) { + if (res.data.error !== undefined) { + throw new Error(JSON.stringify(res.data.error)); + } + if (res.data.result !== undefined) { + const balance = BlocksoftUtils.hexToDecimalBigger(res.data.result); + return { + provider: '', + balance, + unconfirmed: 0 + }; + } } - const balance = BlocksoftUtils.hexToDecimalBigger(res.data.result); + return { provider: '', - balance, + balance: 0, unconfirmed: 0 }; } catch (e: any) { @@ -104,7 +109,11 @@ export default class OneScannerProcessor { ' error ' + e.message ); - return false; + return { + provider: '', + balance: 0, + unconfirmed: 0 + }; } } @@ -144,24 +153,26 @@ export default class OneScannerProcessor { }; const res = await BlocksoftAxios._request(apiPath, 'POST', data); if ( - typeof res.data === 'undefined' || - typeof res.data.result === 'undefined' || - typeof res.data.result.transactions === 'undefined' + res !== false && + res.data !== undefined && + res.data.result !== undefined && + res.data.result.transactions !== undefined ) { - return false; - } - const transactions: UnifiedTransaction[] = []; - for (const tx of res.data.result.transactions) { - const transaction = await this._unifyTransaction( - address, - oneAddress, - tx - ); - if (transaction) { - transactions.push(transaction); + const transactions: UnifiedTransaction[] = []; + for (const tx of res.data.result.transactions) { + const transaction = await this._unifyTransaction( + address, + oneAddress, + tx + ); + if (transaction) { + transactions.push(transaction); + } } + return transactions; + } else { + return []; } - return transactions; } catch (e: any) { if (config.debug.cryptoErrors) { console.log( @@ -179,7 +190,7 @@ export default class OneScannerProcessor { ' error ' + e.message ); - return false; + return []; } } @@ -271,10 +282,10 @@ export default class OneScannerProcessor { nonce: transaction.nonce, gas: transaction.gas, gasPrice: transaction.gasPrice, - transactionIndex: transaction.shardID // Change 'shardID' to the actual transaction index property name + transactionIndex: transaction.shardID }, transactionFee: BlocksoftUtils.mul( - transaction.gasUsed, + transaction.gas, transaction.gasPrice ).toString() }; diff --git a/crypto/blockchains/one/OneScannerProcessorErc20.ts b/crypto/blockchains/one/OneScannerProcessorErc20.ts index cf1528019..5aab731bf 100644 --- a/crypto/blockchains/one/OneScannerProcessorErc20.ts +++ b/crypto/blockchains/one/OneScannerProcessorErc20.ts @@ -13,7 +13,26 @@ import OneTmpDS from './stores/OneTmpDS'; import config from '@constants/config'; interface UnifiedTransaction { - // TODO + transactionHash: string; + blockHash: string; + blockNumber: number; + blockTime: string; + blockConfirmations: number; + transactionDirection: string; + addressFrom: string; + addressFromBasic: string; + addressTo: string; + addressToBasic: string; + addressAmount: number; + transactionStatus: string; + inputValue: string; + transactionJson?: { + nonce: string; + gas: string; + gasPrice: string; + transactionIndex: string; + }; + transactionFee?: string; } interface CacheTokens { @@ -30,17 +49,14 @@ export default class OneScannerProcessorErc20 extends EthScannerProcessorErc20 { * @param {string} scanData.account.walletHash * @return {Promise<[UnifiedTransaction]>} */ + // @ts-ignore async getTransactionsBlockchain(scanData: { account: { address: string; walletHash: string }; }): Promise { const { address } = scanData.account; const oneAddress = OneUtils.toOneAddress(address); BlocksoftCryptoLog.log( - this._settings.currencyCode + - ' OneScannerProcessorErc20.getTransactionsBlockchain started ' + - address + - ' ' + - oneAddress + `${this._settings.currencyCode} OneScannerProcessorErc20.getTransactionsBlockchain started ${address} ${oneAddress}` ); try { CACHE_TOKENS[address] = await OneTmpDS.getCache(address); @@ -62,14 +78,15 @@ export default class OneScannerProcessorErc20 extends EthScannerProcessorErc20 { }; const res = await BlocksoftAxios._request(apiPath, 'POST', data); if ( - typeof res.data === 'undefined' || - typeof res.data.result === 'undefined' || - typeof res.data.result.transactions === 'undefined' + !res || + !res.data || + !res.data.result || + !res.data.result.transactions ) { return []; } const transactions: UnifiedTransaction[] = []; - let firstTransaction = false; + let firstTransaction: number | false = false; for (const tx of res.data.result.transactions) { if ( typeof CACHE_TOKENS[address] !== 'undefined' && @@ -98,25 +115,19 @@ export default class OneScannerProcessorErc20 extends EthScannerProcessorErc20 { transactions.push(transaction); } } - CACHE_TOKENS[address][this._tokenAddress] = Number(firstTransaction); - await OneTmpDS.saveCache(address, this._tokenAddress, firstTransaction); + if (firstTransaction !== false) { + CACHE_TOKENS[address][this._tokenAddress] = Number(firstTransaction); + await OneTmpDS.saveCache(address, this._tokenAddress, firstTransaction); + } return transactions; } catch (e: any) { if (config.debug.cryptoErrors) { console.log( - this._settings.currencyCode + - ' OneScannerProcessorErc20.getTransactionsBlockchain address ' + - address + - ' error ' + - e.message + `${this._settings.currencyCode} OneScannerProcessorErc20.getTransactionsBlockchain address ${address} error ${e.message}` ); } BlocksoftCryptoLog.log( - this._settings.currencyCode + - ' OneScannerProcessorErc20.getTransactionsBlockchain address ' + - address + - ' error ' + - e.message + `${this._settings.currencyCode} OneScannerProcessorErc20.getTransactionsBlockchain address ${address} error ${e.message}` ); return []; } @@ -144,10 +155,12 @@ export default class OneScannerProcessorErc20 extends EthScannerProcessorErc20 { * @return {Promise} * @private */ + // @ts-ignore async _unifyTransaction( address: string, oneAddress: string, transaction: { + transactionIndex: string; blockHash: string; blockNumber: string; ethHash: string; @@ -221,24 +234,23 @@ export default class OneScannerProcessorErc20 extends EthScannerProcessorErc20 { blockTime: formattedTime, blockConfirmations: confirmations, transactionDirection: - addressAmount * 1 <= 0 ? (foundEventTo ? 'outcome' : 'self') : 'income', + addressAmount <= 0 ? (foundEventTo ? 'outcome' : 'self') : 'income', addressFrom: foundEventFrom ? transaction.from : '', addressFromBasic: transaction.from.toLowerCase(), addressTo: foundEventTo ? transaction.to : '', addressToBasic: transaction.to, addressAmount, - transactionStatus: transactionStatus, + transactionStatus, inputValue: transaction.input }; - const additional = { + tx.transactionJson = { nonce: transaction.nonce, gas: transaction.gas, gasPrice: transaction.gasPrice, transactionIndex: transaction.transactionIndex }; - tx.transactionJson = additional; tx.transactionFee = BlocksoftUtils.mul( - transaction.gasUsed, + transaction.gas, transaction.gasPrice ).toString(); diff --git a/crypto/blockchains/sol/SolScannerProcessor.ts b/crypto/blockchains/sol/SolScannerProcessor.ts index f63a8f782..1b8835300 100644 --- a/crypto/blockchains/sol/SolScannerProcessor.ts +++ b/crypto/blockchains/sol/SolScannerProcessor.ts @@ -77,7 +77,7 @@ export default class SolScannerProcessor { CACHE_LAST_BLOCK = res.data.result.context.slot * 1; } balance = res.data.result.value; - } catch (e) { + } catch (e: any) { BlocksoftCryptoLog.log( this._settings.currencyCode + ' SolScannerProcessor getBalanceBlockchain address ' + diff --git a/crypto/blockchains/sol/SolScannerProcessorSpl.ts b/crypto/blockchains/sol/SolScannerProcessorSpl.ts index cdc053e31..335d14ec3 100644 --- a/crypto/blockchains/sol/SolScannerProcessorSpl.ts +++ b/crypto/blockchains/sol/SolScannerProcessorSpl.ts @@ -64,6 +64,7 @@ export default class SolScannerProcessorSpl extends SolScannerProcessor { ] }; + // @ts-ignore const res: AxiosResponse = await BlocksoftAxios._request( apiPath, 'POST', @@ -99,7 +100,7 @@ export default class SolScannerProcessorSpl extends SolScannerProcessor { ) { balance = 0; } else { - balance = CACHE_BALANCES[address][this.tokenAddress].amount * 1; + balance = CACHE_BALANCES[address][this.tokenAddress].amount; } } catch (e: any) { BlocksoftCryptoLog.log( diff --git a/crypto/blockchains/sol/SolTokenProcessor.ts b/crypto/blockchains/sol/SolTokenProcessor.ts index 026956ae9..d19e77231 100644 --- a/crypto/blockchains/sol/SolTokenProcessor.ts +++ b/crypto/blockchains/sol/SolTokenProcessor.ts @@ -17,6 +17,11 @@ interface TokenDetails { description?: string; coingeckoId?: string; provider: string; + symbol?: string; + name?: string; + decimals?: number; + logoURI?: string; + website?: string; } export default class SolTokenProcessor { @@ -41,18 +46,19 @@ export default class SolTokenProcessor { } } if (tmp) { - return { + const tokenDetails: TokenDetails = { currencyCodePrefix: 'CUSTOM_SOL_', - currencyCode: tmp.symbol, - currencyName: tmp.name, + currencyCode: tmp.symbol || '', + currencyName: tmp.name || '', tokenType: 'SOL', tokenAddress, - tokenDecimals: tmp.decimals, - icon: tmp.logoURI, - description: tmp.website, - coingeckoId: tmp.coingeckoId, + tokenDecimals: tmp.decimals || 0, + icon: tmp.logoURI || '', + description: tmp.website || '', + coingeckoId: tmp.coingeckoId || '', provider: 'sol' }; + return tokenDetails; } let decimals = 6; diff --git a/crypto/blockchains/sol/SolTransferProcessor.ts b/crypto/blockchains/sol/SolTransferProcessor.ts index 1450866be..62ffba09d 100644 --- a/crypto/blockchains/sol/SolTransferProcessor.ts +++ b/crypto/blockchains/sol/SolTransferProcessor.ts @@ -6,9 +6,6 @@ import BlocksoftUtils from '@crypto/common/BlocksoftUtils'; import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings'; import BlocksoftBalances from '@crypto/actions/BlocksoftBalances/BlocksoftBalances'; -// eslint-disable-next-line no-unused-vars -import { BlocksoftBlockchainTypes } from '@crypto/blockchains/BlocksoftBlockchainTypes'; - import { PublicKey, SystemProgram, @@ -23,9 +20,10 @@ import { Buffer } from 'buffer'; import BlocksoftCryptoUtils from '@crypto/common/BlocksoftCryptoUtils'; import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; import config from '@constants/config'; +import { AirDAOBlockchainTypes } from '@crypto/blockchains/AirDAOBlockchainTypes'; export default class SolTransferProcessor - implements BlocksoftBlockchainTypes.TransferProcessor + implements AirDAOBlockchainTypes.TransferProcessor { private _settings: { network: string; currencyCode: string }; @@ -42,14 +40,14 @@ export default class SolTransferProcessor } async getFeeRate( - data: BlocksoftBlockchainTypes.TransferData, - privateData: BlocksoftBlockchainTypes.TransferPrivateData, + data: AirDAOBlockchainTypes.TransferData, + privateData: AirDAOBlockchainTypes.TransferPrivateData, additionalData: {} = {} - ): Promise { - const result: BlocksoftBlockchainTypes.FeeRateResult = { + ): Promise { + const result: AirDAOBlockchainTypes.FeeRateResult = { selectedFeeIndex: -3, shouldShowFees: false - } as BlocksoftBlockchainTypes.FeeRateResult; + } as AirDAOBlockchainTypes.FeeRateResult; const feeForTx = BlocksoftExternalSettings.getStatic('SOL_PRICE'); result.fees = [ @@ -65,10 +63,10 @@ export default class SolTransferProcessor } async getTransferAllBalance( - data: BlocksoftBlockchainTypes.TransferData, - privateData: BlocksoftBlockchainTypes.TransferPrivateData, - additionalData: BlocksoftBlockchainTypes.TransferAdditionalData = {} - ): Promise { + data: AirDAOBlockchainTypes.TransferData, + privateData: AirDAOBlockchainTypes.TransferPrivateData, + additionalData: AirDAOBlockchainTypes.TransferAdditionalData = {} + ): Promise { const address = data.addressFrom.trim(); let rent = 0; let balance = data.amount; @@ -142,10 +140,10 @@ export default class SolTransferProcessor * @param uiData */ async sendTx( - data: BlocksoftBlockchainTypes.TransferData, - privateData: BlocksoftBlockchainTypes.TransferPrivateData, - uiData: BlocksoftBlockchainTypes.TransferUiData - ): Promise { + data: AirDAOBlockchainTypes.TransferData, + privateData: AirDAOBlockchainTypes.TransferPrivateData, + uiData: AirDAOBlockchainTypes.TransferUiData + ): Promise { if (typeof privateData.privateKey === 'undefined') { throw new Error('SOL transaction required privateKey (derivedSeed)'); } @@ -403,7 +401,7 @@ export default class SolTransferProcessor signedData ); - const result = {} as BlocksoftBlockchainTypes.SendTxResult; + const result = {} as AirDAOBlockchainTypes.SendTxResult; try { const sendRes = await SolUtils.sendTransaction(signedData); BlocksoftCryptoLog.log( diff --git a/crypto/blockchains/sol/ext/SolUtils.ts b/crypto/blockchains/sol/ext/SolUtils.ts index dd04b52e4..ef1db0e81 100644 --- a/crypto/blockchains/sol/ext/SolUtils.ts +++ b/crypto/blockchains/sol/ext/SolUtils.ts @@ -8,8 +8,10 @@ import { PublicKey } from '@solana/web3.js/src'; import { Account } from '@solana/web3.js/src/account'; import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; +// @ts-ignore import settingsActions from '@app/appstores/Stores/Settings/SettingsActions'; import config from '@constants/config'; +import { AxiosResponse } from 'axios'; const TOKEN_PROGRAM_ID = 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'; const ASSOCIATED_TOKEN_PROGRAM_ID = @@ -52,7 +54,7 @@ export default { new PublicKey(ASSOCIATED_TOKEN_PROGRAM_ID) ); return res[0].toBase58(); - } catch (e) { + } catch (e: any) { if (config.debug.cryptoErrors) { console.log('SolUtils.findAssociatedTokenAddress ' + e.message); } @@ -75,9 +77,14 @@ export default { } ] }; - const res = await BlocksoftAxios._request(apiPath, 'POST', checkData); + // @ts-ignore + const res: AxiosResponse = await BlocksoftAxios._request( + apiPath, + 'POST', + checkData + ); accountInfo = res.data.result.value; - } catch (e) { + } catch (e: any) { if (config.debug.cryptoErrors) { console.log( 'SolUtils.getAccountInfo ' + address + ' error ' + e.message @@ -91,6 +98,7 @@ export default { }, isAddressValid(value: string): boolean { + // tslint:disable-next-line:no-unused-expression new PublicKey(value); return true; }, @@ -113,14 +121,14 @@ export default { ] }; const apiPath = BlocksoftExternalSettings.getStatic('SOL_SERVER'); - const apiPath_2 = BlocksoftExternalSettings.getStatic('SOL_SERVER_2'); - let try_2 = false; - let sendRes; + const apiPath2 = BlocksoftExternalSettings.getStatic('SOL_SERVER_2'); + let try2 = false; + let sendRes: any = null; // Initialize with 'null' try { sendRes = await BlocksoftAxios._request(apiPath, 'POST', sendData); if (!sendRes || typeof sendRes.data === 'undefined') { - if (apiPath_2) { - try_2 = true; + if (apiPath2) { + try2 = true; } else { throw new Error('SERVER_RESPONSE_BAD_INTERNET'); } @@ -130,34 +138,35 @@ export default { typeof sendRes.data.error.message !== 'undefined' ) { if (sendRes.data.error.message === 'Node is unhealthy') { - try_2 = true; + try2 = true; } else { throw new Error(sendRes.data.error.message); } } } catch (e) { - try_2 = true; + try2 = true; } - if (try_2 && apiPath_2 && apiPath_2 !== apiPath) { - const sendRes_2 = await BlocksoftAxios._request( - apiPath_2, - 'POST', - sendData - ); - if (!sendRes_2 || typeof sendRes_2.data === 'undefined') { - throw new Error('SERVER_RESPONSE_BAD_INTERNET'); - } - if ( - typeof sendRes_2.data.error !== 'undefined' && - typeof sendRes_2.data.error.message !== 'undefined' - ) { - throw new Error(sendRes_2.data.error.message); + if (try2 && apiPath2 && apiPath2 !== apiPath) { + let sendRes2: any = null; // Initialize with 'null' + try { + sendRes2 = await BlocksoftAxios._request(apiPath2, 'POST', sendData); + if (!sendRes2 || typeof sendRes2.data === 'undefined') { + throw new Error('SERVER_RESPONSE_BAD_INTERNET'); + } + if ( + typeof sendRes2.data.error !== 'undefined' && + typeof sendRes2.data.error.message !== 'undefined' + ) { + throw new Error(sendRes2.data.error.message); + } + return sendRes2.data.result; + } catch (e) { + await BlocksoftCryptoLog.log(e); } - return sendRes_2.data.result; } - return sendRes.data.result; + return sendRes?.data?.result || ''; }, /** @@ -178,6 +187,7 @@ export default { 'POST', getRecentBlockhashData ); + // @ts-ignore return getRecentBlockhashRes.data.result.value; }, @@ -197,13 +207,14 @@ export default { const getEpoch = { jsonrpc: '2.0', id: 1, method: 'getEpochInfo' }; try { const resEpoch = await BlocksoftAxios._request(apiPath, 'POST', getEpoch); + // @ts-ignore const tmp = resEpoch.data.result.epoch * 1; if (tmp > 0 && tmp !== CACHE_EPOCH.value) { CACHE_EPOCH.value = tmp; settingsActions.setSettings('SOL_epoch', tmp); } CACHE_EPOCH.ts = now; - } catch (e) { + } catch (e: any) { BlocksoftCryptoLog.log('SolUtils.getEpoch error ' + e.message); // nothing } @@ -215,7 +226,7 @@ export default { walletPrivKey: string, walletPubKey: string ): Promise { - let account = false; + let account: Account | boolean = false; try { account = new Account(Buffer.from(walletPrivKey, 'hex')); } catch (e: any) { @@ -238,7 +249,7 @@ export default { } try { - transaction.partialSign(account); + transaction.partialSign(account as Account); } catch (e: any) { e.message += ' while transaction.partialSign with account'; throw e; diff --git a/crypto/blockchains/trx/TrxScannerProcessor.ts b/crypto/blockchains/trx/TrxScannerProcessor.ts index 6f9c49314..a8023868f 100644 --- a/crypto/blockchains/trx/TrxScannerProcessor.ts +++ b/crypto/blockchains/trx/TrxScannerProcessor.ts @@ -77,9 +77,9 @@ export default class TrxScannerProcessor { ); let addressHex = address; if (address.substr(0, 1) === 'T') { - addressHex = await TronUtils.addressToHex(address); + addressHex = TronUtils.addressToHex(address); } else { - address = await TronUtils.addressHexToStr(addressHex); + address = TronUtils.addressHexToStr(addressHex); } const useTronscan = BlocksoftExternalSettings.getStatic('TRX_USE_TRONSCAN') * 1 > 0; @@ -108,7 +108,7 @@ export default class TrxScannerProcessor { try { const sendLink = BlocksoftExternalSettings.getStatic('TRX_SEND_LINK'); const params = { - contract_address: await TronUtils.addressToHex(this._tokenName), + contract_address: TronUtils.addressToHex(this._tokenName), function_selector: 'balanceOf(address)', parameter: '0000000000000000000000' + addressHex, owner_address: addressHex @@ -121,7 +121,7 @@ export default class TrxScannerProcessor { typeof tmp.data !== 'undefined' && typeof tmp.data.constant_result !== 'undefined' ) { - BlocksoftCryptoLog.log( + await BlocksoftCryptoLog.log( this._tokenName + ' TrxScannerProcessor getBalanceBlockchain address ' + address + @@ -170,7 +170,7 @@ export default class TrxScannerProcessor { ); } - if (result === false && this._tokenName !== '_') { + if (!result && this._tokenName !== '_') { subresult = await this._tronscanProvider.get( address, '_', @@ -351,7 +351,7 @@ export default class TrxScannerProcessor { const recheck = await BlocksoftAxios.post(linkRecheck, { value: row.transactionHash }); - if (typeof recheck.data !== 'undefined') { + if (typeof recheck.data !== undefined) { const isSuccess = await this._unifyFromReceipt( recheck.data, row, @@ -362,7 +362,7 @@ export default class TrxScannerProcessor { } if (isSuccess && row.specialActionNeeded && row.addressFromBasic) { row.confirmations = lastBlock - recheck.data.blockNumber; - if (typeof unique[row.addressFromBasic] === 'undefined') { + if (typeof unique[row.addressFromBasic] === undefined) { unique[row.addressFromBasic] = row; } else { if ( @@ -436,7 +436,7 @@ export default class TrxScannerProcessor { address ); } - } catch (e) { + } catch (e: any) { if (config.debug.cryptoErrors) { console.log( this._settings.currencyCode + @@ -478,13 +478,13 @@ export default class TrxScannerProcessor { let transactionStatus = 'success'; if ( - typeof transaction.result !== 'undefined' && + typeof transaction.result !== undefined && transaction.result === 'FAILED' ) { transactionStatus = 'fail'; if ( - typeof transaction.receipt !== 'undefined' && - typeof transaction.receipt.result !== 'undefined' + typeof transaction.receipt !== undefined && + typeof transaction.receipt.result !== undefined ) { if (transaction.receipt.result === 'OUT_OF_ENERGY') { transactionStatus = 'out_of_energy'; diff --git a/crypto/blockchains/trx/TrxTokenProcessor.ts b/crypto/blockchains/trx/TrxTokenProcessor.ts index bdf30e46d..7a929b734 100644 --- a/crypto/blockchains/trx/TrxTokenProcessor.ts +++ b/crypto/blockchains/trx/TrxTokenProcessor.ts @@ -17,6 +17,22 @@ interface TokenDetails { provider: string; } +interface TronscanResponse { + data: { + symbol: string; + name: string; + contract_address: string; + decimals: number; + icon_url: string; + token_desc: string; + abbr: string; + tokenID: string; + precision: number; + imgUrl: string; + description: string; + }[]; +} + export default class TrxTokenProcessor { private _tokenTronscanPath20: string; private _tokenTronscanPath10: string; @@ -34,7 +50,7 @@ export default class TrxTokenProcessor { */ async getTokenDetails(tokenAddress: string): Promise { if (tokenAddress[0] === 'T') { - const res = await BlocksoftAxios.get( + const res = await BlocksoftAxios.get( this._tokenTronscanPath20 + tokenAddress ); if (typeof res.data[0] !== 'undefined') { @@ -52,7 +68,7 @@ export default class TrxTokenProcessor { }; } } else { - const res = await BlocksoftAxios.get( + const res = await BlocksoftAxios.get( this._tokenTronscanPath10 + tokenAddress ); if (typeof res.data[0] !== 'undefined') { diff --git a/crypto/blockchains/trx/TrxTransferProcessor.ts b/crypto/blockchains/trx/TrxTransferProcessor.ts index f51d2f019..eaa1d7a79 100644 --- a/crypto/blockchains/trx/TrxTransferProcessor.ts +++ b/crypto/blockchains/trx/TrxTransferProcessor.ts @@ -4,7 +4,6 @@ import BlocksoftAxios from '../../common/BlocksoftAxios'; import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog'; import BlocksoftUtils from '../../common/BlocksoftUtils'; -import { BlocksoftBlockchainTypes } from '../BlocksoftBlockchainTypes'; import TronUtils from './ext/TronUtils'; import TrxTronscanProvider from './basic/TrxTronscanProvider'; @@ -20,6 +19,7 @@ import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings' import BlocksoftBalances from '@crypto/actions/BlocksoftBalances/BlocksoftBalances'; import BlocksoftDispatcher from '@lib/BlocksoftDispatcher'; import config from '@constants/config'; +import { AirDAOBlockchainTypes } from '@crypto/blockchains/AirDAOBlockchainTypes'; // https://developers.tron.network/docs/parameter-and-return-value-encoding-and-decoding const ethers = require('ethers'); @@ -28,7 +28,7 @@ const AbiCoder = ethers.utils.AbiCoder; const PROXY_FEE = 'https://proxy.trustee.deals/trx/countFee'; export default class TrxTransferProcessor - implements BlocksoftBlockchainTypes.TransferProcessor + implements AirDAOBlockchainTypes.TransferProcessor { private _settings: any; private _tronscanProvider: TrxTronscanProvider; @@ -61,8 +61,8 @@ export default class TrxTransferProcessor } async checkTransferHasError( - data: BlocksoftBlockchainTypes.CheckTransferHasErrorData - ): Promise { + data: AirDAOBlockchainTypes.CheckTransferHasErrorData + ): Promise { if (!this._isToken20 || (data.amount && data.amount * 1 > 0)) { return { isOk: true }; } @@ -106,10 +106,10 @@ export default class TrxTransferProcessor } async getFeeRate( - data: BlocksoftBlockchainTypes.TransferData, - privateData: BlocksoftBlockchainTypes.TransferPrivateData, + data: AirDAOBlockchainTypes.TransferData, + privateData: AirDAOBlockchainTypes.TransferPrivateData, additionalData: {} = {} - ): Promise { + ): Promise { const addressHexTo = TronUtils.addressToHex(data.addressTo); if (TronUtils.addressHexToStr(addressHexTo) !== data.addressTo) { BlocksoftCryptoLog.log( @@ -167,7 +167,7 @@ export default class TrxTransferProcessor ' res ', res ); - if (typeof res.feeForTx === 'undefined') { + if (typeof res.feeForTx === undefined) { throw new Error('no res?.feeForTx'); } @@ -192,7 +192,7 @@ export default class TrxTransferProcessor shouldShowFees: false }; } - return result as BlocksoftBlockchainTypes.FeeRateResult; + return result as unknown as AirDAOBlockchainTypes.FeeRateResult; } catch (e: any) { if (e.message.indexOf('SERVER_RESPONSE_') === 0) { throw e; @@ -214,15 +214,15 @@ export default class TrxTransferProcessor } async getFeeRateOld( - data: BlocksoftBlockchainTypes.TransferData, - privateData: BlocksoftBlockchainTypes.TransferPrivateData, + data: AirDAOBlockchainTypes.TransferData, + privateData: AirDAOBlockchainTypes.TransferPrivateData, additionalData: {} = {} - ): Promise { + ): Promise { const addressHexTo = TronUtils.addressToHex(data.addressTo); - const result: BlocksoftBlockchainTypes.FeeRateResult = { + const result: AirDAOBlockchainTypes.FeeRateResult = { selectedFeeIndex: -3, shouldShowFees: false - } as BlocksoftBlockchainTypes.FeeRateResult; + } as AirDAOBlockchainTypes.FeeRateResult; const sendLink = BlocksoftExternalSettings.getStatic('TRX_SEND_LINK'); const link = sendLink + '/wallet/getaccountresource'; // http://trx.trusteeglobal.com:8090/wallet @@ -291,7 +291,7 @@ export default class TrxTransferProcessor ); const fullPriceEnergy = energyForTx * priceForEnergy; if (res.leftEnergy <= 0) { - feeForTx = feeForTx * 1 + fullPriceEnergy; + feeForTx = feeForTx + fullPriceEnergy; feeLog += ' res.leftEnergy<=0 energyFee=' + energyForTx + @@ -309,7 +309,7 @@ export default class TrxTransferProcessor fullPriceEnergy, BlocksoftUtils.div(diffE / energyForTx) ) * 1; - feeForTx = feeForTx * 1 + energyFee; + feeForTx = feeForTx + energyFee; feeLog += ' fullPriceEnergy=' + energyForTx + @@ -371,7 +371,7 @@ export default class TrxTransferProcessor } else if (this._tokenName === '_') { if ( !balance || - balance.balanceAvailable <= feeForTx * 1 + data.amount * 1 + balance.balanceAvailable <= feeForTx + data.amount * 1 ) { isErrorFee = true; // throw new Error('SERVER_RESPONSE_NOT_ENOUGH_FEE') @@ -395,7 +395,7 @@ export default class TrxTransferProcessor tronData2 ); if (typeof tronData2.freeNetLimit === 'undefined') { - feeForTx = feeForTx * 1 + 1100000; + feeForTx = feeForTx + 1100000; } } catch (e: any) { // do nothing @@ -424,8 +424,8 @@ export default class TrxTransferProcessor .getBalance('TrxSendTx'); if (balance && typeof balance.balance !== 'undefined') { if (balance.balance === 0) { - amountForTx = 0; - selectedTransferAllBalance = 0; + amountForTx = String(0); + selectedTransferAllBalance = String(0); } else { selectedTransferAllBalance = BlocksoftUtils.diff( balance.balance, @@ -476,10 +476,10 @@ export default class TrxTransferProcessor } async getTransferAllBalance( - data: BlocksoftBlockchainTypes.TransferData, - privateData: BlocksoftBlockchainTypes.TransferPrivateData, - additionalData: BlocksoftBlockchainTypes.TransferAdditionalData = {} - ): Promise { + data: AirDAOBlockchainTypes.TransferData, + privateData: AirDAOBlockchainTypes.TransferPrivateData, + additionalData: AirDAOBlockchainTypes.TransferAdditionalData = {} + ): Promise { data.isTransferAll = true; const balance = data.amount; // @ts-ignore @@ -521,10 +521,10 @@ export default class TrxTransferProcessor * https://developers.tron.network/docs/trc20-introduction#section-8usdt-transfer */ async sendTx( - data: BlocksoftBlockchainTypes.TransferData, - privateData: BlocksoftBlockchainTypes.TransferPrivateData, - uiData: BlocksoftBlockchainTypes.TransferUiData - ): Promise { + data: AirDAOBlockchainTypes.TransferData, + privateData: AirDAOBlockchainTypes.TransferPrivateData, + uiData: AirDAOBlockchainTypes.TransferUiData + ): Promise { if (typeof privateData.privateKey === 'undefined') { throw new Error('TRX transaction required privateKey'); } @@ -547,7 +547,16 @@ export default class TrxTransferProcessor data.addressTo ); - const logData = {}; + const logData = { + currencyCode: strings, + selectedFee: strings, + from: strings, + basicAddressTo: strings, + basicAmount: strings, + pushSetting: strings, + pushLocale: undefined, + basicToken: strings + }; logData.currencyCode = this._settings.currencyCode; logData.selectedFee = uiData.selectedFee; logData.from = data.addressFrom; @@ -614,7 +623,7 @@ export default class TrxTransferProcessor } types.push(type); values.push(value); - } catch (e) { + } catch (e: any) { throw new Error( e.message + ' type ' + @@ -639,19 +648,18 @@ export default class TrxTransferProcessor owner_address: ownerAddress, contract_address: order.tokenContract, function_selector: order.contractMethod, - // @ts-ignore parameter, fee_limit: BlocksoftExternalSettings.getStatic( 'TRX_TRC20_MAX_LIMIT' ) }; if ( - typeof order.options !== 'undefined' && - typeof order.options.callValue !== 'undefined' + typeof order.options !== undefined && + typeof order.options.callValue !== undefined ) { params.call_value = order.options.callValue * 1; } - } catch (e1) { + } catch (e1: any) { throw new Error(e1.message + ' in params build'); } if (index < total) { @@ -674,7 +682,7 @@ export default class TrxTransferProcessor tx ); - let resultSub = {} as BlocksoftBlockchainTypes.SendTxResult; + let resultSub = {} as AirDAOBlockchainTypes.SendTxResult; try { resultSub = await this.sendProvider.sendTx( tx, @@ -724,12 +732,12 @@ export default class TrxTransferProcessor const recheck = await BlocksoftAxios.post(linkRecheck, { value: tx.txID }); - if (typeof recheck.data !== 'undefined') { + if (typeof recheck.data !== undefined) { if ( - typeof recheck.data.id !== 'undefined' && - typeof recheck.data.blockNumber !== 'undefined' && - typeof recheck.data.receipt !== 'undefined' && - typeof recheck.data.receipt.result !== 'undefined' + typeof recheck.data.id !== undefined && + typeof recheck.data.blockNumber !== undefined && + typeof recheck.data.receipt !== undefined && + typeof recheck.data.receipt.result !== undefined ) { // @ts-ignore BlocksoftCryptoLog.log( @@ -836,20 +844,19 @@ export default class TrxTransferProcessor }; await BlocksoftCryptoLog.log( this._settings.currencyCode + - ' TrxTransferProcessor.sendTx inited1' + - data.addressFrom + + `' TrxTransferProcessor.sendTx inited1' + + ${data.addressFrom} + ' => ' + - data.addressTo + + ${data.addressTo} + ' ' + - link, - params + ${link}, + ${params}` ); res = await BlocksoftAxios.post(link, params); } else { params = { owner_address: ownerAddress, to_address: toAddress, - // @ts-ignore amount: BlocksoftUtils.round(data.amount) * 1 }; @@ -865,13 +872,13 @@ export default class TrxTransferProcessor try { await BlocksoftCryptoLog.log( this._settings.currencyCode + - ' TrxTransferProcessor.sendTx inited2 ' + - data.addressFrom + - ' => ' + - data.addressTo + - ' ' + - link, - params + `' TrxTransferProcessor.sendTx inited2' + + ${data.addressFrom} + + ' => ' + + ${data.addressTo} + + ' ' + + ${link}, + ${params}` ); res = await BlocksoftAxios.post(link, params); } catch (e: any) { @@ -902,12 +909,12 @@ export default class TrxTransferProcessor if (typeof res.data.Error !== 'undefined') { await BlocksoftCryptoLog.log( this._settings.currencyCode + - ' TrxTransferProcessor.sendTx error ' + - data.addressFrom + + `' TrxTransferProcessor.sendTx error ' + + ${data.addressFrom} + ' => ' + - data.addressTo + + ${data.addressTo} + ' ', - res.data + ${res.data}` ); // @ts-ignore this.sendProvider.trxError(res.data.Error.message || res.data.Error); @@ -940,9 +947,9 @@ export default class TrxTransferProcessor tx = res.data.transaction; } else { // @ts-ignore - if (typeof res.data.txID === 'undefined') { + if (typeof res.data.txID === undefined) { // @ts-ignore - if (typeof res.data.result.message !== 'undefined') { + if (typeof res.data.result.message !== undefined) { // @ts-ignore res.data.result.message = BlocksoftUtils.hexToUtf( '0x' + res.data.result.message @@ -984,7 +991,7 @@ export default class TrxTransferProcessor tx ); - let result = {} as BlocksoftBlockchainTypes.SendTxResult; + let result = {} as AirDAOBlockchainTypes.SendTxResult; try { result = await this.sendProvider.sendTx(tx, '', false, logData); await BlocksoftCryptoLog.log( diff --git a/crypto/blockchains/usdt/UsdtTransferProcessor.ts b/crypto/blockchains/usdt/UsdtTransferProcessor.ts index ebd3a9b24..3167e5341 100644 --- a/crypto/blockchains/usdt/UsdtTransferProcessor.ts +++ b/crypto/blockchains/usdt/UsdtTransferProcessor.ts @@ -1,7 +1,7 @@ /** * @version 0.20 */ -import { BlocksoftBlockchainTypes } from '../BlocksoftBlockchainTypes'; +import { AirDAOBlockchainTypes } from '@crypto/blockchains/AirDAOBlockchainTypes'; import BtcUnspentsProvider from '../btc/providers/BtcUnspentsProvider'; import DogeSendProvider from '../doge/providers/DogeSendProvider'; import UsdtTxInputsOutputs from './tx/UsdtTxInputsOutputs'; @@ -9,15 +9,16 @@ import UsdtTxBuilder from './tx/UsdtTxBuilder'; import BtcNetworkPrices from '../btc/basic/BtcNetworkPrices'; import BtcTransferProcessor from '../btc/BtcTransferProcessor'; import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog'; +// @ts-ignore import DaemonCache from '../../../app/daemons/DaemonCache'; export default class UsdtTransferProcessor extends BtcTransferProcessor - implements BlocksoftBlockchainTypes.TransferProcessor + implements AirDAOBlockchainTypes.TransferProcessor { _trezorServerCode = 'BTC_TREZOR_SERVER'; - _builderSettings: BlocksoftBlockchainTypes.BuilderSettings = { + _builderSettings: AirDAOBlockchainTypes.BuilderSettings = { minOutputDustReadable: 0.000001, minChangeDustReadable: 0.000001, feeMaxForByteSatoshi: 1000, // for tx builder @@ -49,8 +50,8 @@ export default class UsdtTransferProcessor } async checkTransferHasError( - data: BlocksoftBlockchainTypes.CheckTransferHasErrorData - ): Promise { + data: AirDAOBlockchainTypes.CheckTransferHasErrorData + ): Promise { // @ts-ignore const tmp = await DaemonCache.getCacheAccount(data.walletHash, 'BTC'); if (tmp.balance * 1 > 0) { @@ -70,10 +71,10 @@ export default class UsdtTransferProcessor } async getFeeRate( - data: BlocksoftBlockchainTypes.TransferData, - privateData: BlocksoftBlockchainTypes.TransferPrivateData, - additionalData: BlocksoftBlockchainTypes.TransferAdditionalData = {} - ): Promise { + data: AirDAOBlockchainTypes.TransferData, + privateData: AirDAOBlockchainTypes.TransferPrivateData, + additionalData: AirDAOBlockchainTypes.TransferAdditionalData = {} + ): Promise { const tmpData = { ...data }; tmpData.isTransferAll = false; // @ts-ignore @@ -88,10 +89,10 @@ export default class UsdtTransferProcessor } async getTransferAllBalance( - data: BlocksoftBlockchainTypes.TransferData, - privateData: BlocksoftBlockchainTypes.TransferPrivateData, - additionalData: BlocksoftBlockchainTypes.TransferAdditionalData = {} - ): Promise { + data: AirDAOBlockchainTypes.TransferData, + privateData: AirDAOBlockchainTypes.TransferPrivateData, + additionalData: AirDAOBlockchainTypes.TransferAdditionalData = {} + ): Promise { const balance = data.amount; // @ts-ignore BlocksoftCryptoLog.log( @@ -126,10 +127,10 @@ export default class UsdtTransferProcessor } async sendTx( - data: BlocksoftBlockchainTypes.TransferData, - privateData: BlocksoftBlockchainTypes.TransferPrivateData, - uiData: BlocksoftBlockchainTypes.TransferUiData - ): Promise { + data: AirDAOBlockchainTypes.TransferData, + privateData: AirDAOBlockchainTypes.TransferPrivateData, + uiData: AirDAOBlockchainTypes.TransferUiData + ): Promise { const result = await super.sendTx(data, privateData, uiData); result.transactionFeeCurrencyCode = 'BTC'; return result; diff --git a/crypto/blockchains/usdt/tx/UsdtTxBuilder.ts b/crypto/blockchains/usdt/tx/UsdtTxBuilder.ts index 952b353fc..0e101f39b 100644 --- a/crypto/blockchains/usdt/tx/UsdtTxBuilder.ts +++ b/crypto/blockchains/usdt/tx/UsdtTxBuilder.ts @@ -1,7 +1,7 @@ /** * @version 0.20 */ -import { BlocksoftBlockchainTypes } from '../../BlocksoftBlockchainTypes'; +import { AirDAOBlockchainTypes } from '@crypto/blockchains/AirDAOBlockchainTypes'; import BtcTxBuilder from '../../btc/tx/BtcTxBuilder'; import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; @@ -30,11 +30,11 @@ function createOmniSimpleSend(amountInUSD: string, propertyID = USDT_TOKEN_ID) { export default class UsdtTxBuilder extends BtcTxBuilder - implements BlocksoftBlockchainTypes.TxBuilder + implements AirDAOBlockchainTypes.TxBuilder { _getRawTxAddOutput( txb: TransactionBuilder, - output: BlocksoftBlockchainTypes.OutputTx + output: AirDAOBlockchainTypes.OutputTx ): void { if ( typeof output.tokenAmount !== 'undefined' && diff --git a/crypto/blockchains/usdt/tx/UsdtTxInputsOutputs.ts b/crypto/blockchains/usdt/tx/UsdtTxInputsOutputs.ts index 1bffca168..099238695 100644 --- a/crypto/blockchains/usdt/tx/UsdtTxInputsOutputs.ts +++ b/crypto/blockchains/usdt/tx/UsdtTxInputsOutputs.ts @@ -1,23 +1,24 @@ /** * @version 0.20 */ -import { BlocksoftBlockchainTypes } from '../../BlocksoftBlockchainTypes'; +import { AirDAOBlockchainTypes } from '../../AirDAOBlockchainTypes'; import BtcTxInputsOutputs from '../../btc/tx/BtcTxInputsOutputs'; import BlocksoftBN from '../../../common/BlocksoftBN'; import BlocksoftUtils from '../../../common/BlocksoftUtils'; import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; +// @ts-ignore import DaemonCache from '../../../../app/daemons/DaemonCache'; export default class UsdtTxInputsOutputs extends BtcTxInputsOutputs - implements BlocksoftBlockchainTypes.TxInputsOutputs + implements AirDAOBlockchainTypes.TxInputsOutputs { DUST_FIRST_TRY = 546; SIZE_FOR_BASIC = 442; _coinSelectTargets( - data: BlocksoftBlockchainTypes.TransferData, - unspents: BlocksoftBlockchainTypes.UnspentTx[], + data: AirDAOBlockchainTypes.TransferData, + unspents: AirDAOBlockchainTypes.UnspentTx[], feeForByte: string, multiAddress: string[], subtitle: string @@ -34,8 +35,8 @@ export default class UsdtTxInputsOutputs } _usualTargets( - data: BlocksoftBlockchainTypes.TransferData, - unspents: BlocksoftBlockchainTypes.UnspentTx[] + data: AirDAOBlockchainTypes.TransferData, + unspents: AirDAOBlockchainTypes.UnspentTx[] ) { const basicWishedAmountBN = new BlocksoftBN(0); const wishedAmountBN = new BlocksoftBN(basicWishedAmountBN); @@ -56,21 +57,21 @@ export default class UsdtTxInputsOutputs }; } - _addressForChange(data: BlocksoftBlockchainTypes.TransferData): string { + _addressForChange(data: AirDAOBlockchainTypes.TransferData): string { return data.addressFrom; } async getInputsOutputs( - data: BlocksoftBlockchainTypes.TransferData, - unspents: BlocksoftBlockchainTypes.UnspentTx[], + data: AirDAOBlockchainTypes.TransferData, + unspents: AirDAOBlockchainTypes.UnspentTx[], feeToCount: { feeForByte?: string; feeForAll?: string; autoFeeLimitReadable?: string | number; }, - additionalData: BlocksoftBlockchainTypes.TransferAdditionalData, + additionalData: AirDAOBlockchainTypes.TransferAdditionalData, subtitle = 'default' - ): Promise { + ): Promise { let res = await super._getInputsOutputs( data, unspents, @@ -125,7 +126,6 @@ export default class UsdtTxInputsOutputs if (!changeIsFound && newInputAdded.value !== '546') { res.outputs.push({ to: data.addressFrom, - // @ts-ignore amount: newInputAdded.value.toString(), isChange: true }); @@ -212,8 +212,7 @@ export default class UsdtTxInputsOutputs needOneOutput, data.addressFrom, data.addressTo, - data.amount, - 'getInputsOutputs ' + subtitle + data.amount ); BlocksoftCryptoLog.log( 'UsdtTxInputsOutputs ' + diff --git a/crypto/blockchains/vet/VetScannerProcessor.ts b/crypto/blockchains/vet/VetScannerProcessor.ts index 3a980bf4b..39151bf9c 100644 --- a/crypto/blockchains/vet/VetScannerProcessor.ts +++ b/crypto/blockchains/vet/VetScannerProcessor.ts @@ -111,10 +111,9 @@ export default class VetScannerProcessor { * @param {string} scanData.account.address * @return {Promise<[UnifiedTransaction]>} */ - async getTransactionsBlockchain( - scanData: { account: { address: string } }, - source: any - ): Promise { + async getTransactionsBlockchain(scanData: { + account: { address: string }; + }): Promise { const address = scanData.account.address.trim(); if (this._settings.currencyCode === 'VET') { diff --git a/crypto/blockchains/vet/VetTransferProcessor.ts b/crypto/blockchains/vet/VetTransferProcessor.ts index 5615c0fee..62a9e3c7a 100644 --- a/crypto/blockchains/vet/VetTransferProcessor.ts +++ b/crypto/blockchains/vet/VetTransferProcessor.ts @@ -4,9 +4,9 @@ import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog'; import BlocksoftUtils from '../../common/BlocksoftUtils'; -import { BlocksoftBlockchainTypes } from '../BlocksoftBlockchainTypes'; import { thorify } from 'thorify'; import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; +import { AirDAOBlockchainTypes } from '@crypto/blockchains/AirDAOBlockchainTypes'; const Web3 = require('web3'); const abi = [ @@ -39,7 +39,7 @@ const tokenAddress = '0x0000000000000000000000000000456e65726779'; const API_PATH = 'https://sync-mainnet.vechain.org'; export default class VetTransferProcessor - implements BlocksoftBlockchainTypes.TransferProcessor + implements AirDAOBlockchainTypes.TransferProcessor { private _settings: { network: string; currencyCode: string }; private _web3: any; @@ -60,12 +60,12 @@ export default class VetTransferProcessor } async getFeeRate( - data: BlocksoftBlockchainTypes.TransferData - ): Promise { - const result: BlocksoftBlockchainTypes.FeeRateResult = { + data: AirDAOBlockchainTypes.TransferData + ): Promise { + const result: AirDAOBlockchainTypes.FeeRateResult = { selectedFeeIndex: -3, shouldShowFees: false - } as BlocksoftBlockchainTypes.FeeRateResult; + } as AirDAOBlockchainTypes.FeeRateResult; if (this._settings.currencyCode === 'VET') { // @todo @@ -94,16 +94,16 @@ export default class VetTransferProcessor } async getTransferAllBalance( - data: BlocksoftBlockchainTypes.TransferData, - privateData: BlocksoftBlockchainTypes.TransferPrivateData, - additionalData: BlocksoftBlockchainTypes.TransferAdditionalData = {} - ): Promise { + data: AirDAOBlockchainTypes.TransferData, + privateData: AirDAOBlockchainTypes.TransferPrivateData, + additionalData: AirDAOBlockchainTypes.TransferAdditionalData = {} + ): Promise { const balance = data.amount; // @ts-ignore await BlocksoftCryptoLog.log( this._settings.currencyCode + - ' VetTransferProcessor.getTransferAllBalance ', - data.addressFrom + ' => ' + balance + `' VetTransferProcessor.getTransferAllBalance ', + ${data.addressFrom} + ' => ' + ${balance}` ); const fees = await this.getFeeRate(data, privateData, additionalData); @@ -131,10 +131,10 @@ export default class VetTransferProcessor * @param uiData */ async sendTx( - data: BlocksoftBlockchainTypes.TransferData, - privateData: BlocksoftBlockchainTypes.TransferPrivateData, - uiData: BlocksoftBlockchainTypes.TransferUiData - ): Promise { + data: AirDAOBlockchainTypes.TransferData, + privateData: AirDAOBlockchainTypes.TransferPrivateData, + uiData: AirDAOBlockchainTypes.TransferUiData + ): Promise { if (typeof privateData.privateKey === 'undefined') { throw new Error('VET transaction required privateKey'); } @@ -196,7 +196,7 @@ export default class VetTransferProcessor ); if ( !signedData || - typeof signedData.rawTransaction === 'undefined' || + typeof signedData.rawTransaction === undefined || !signedData.rawTransaction ) { throw new Error('SYSTEM_ERROR'); @@ -213,7 +213,7 @@ export default class VetTransferProcessor }; } - const result = {} as BlocksoftBlockchainTypes.SendTxResult; + const result = {} as AirDAOBlockchainTypes.SendTxResult; try { const send = await BlocksoftAxios.post( API_PATH + '/transactions', diff --git a/crypto/blockchains/waves/WavesScannerProcessorErc20.ts b/crypto/blockchains/waves/WavesScannerProcessorErc20.ts index 2c0e5a222..ecb9118cb 100644 --- a/crypto/blockchains/waves/WavesScannerProcessorErc20.ts +++ b/crypto/blockchains/waves/WavesScannerProcessorErc20.ts @@ -96,7 +96,7 @@ export default class WavesScannerProcessorErc20 extends WavesScannerProcessor { const transactionFee = transaction.feeAsset && transaction.feeAssetId ? 0 : transaction.fee; - let transactionDirection = false; + let transactionDirection: string = false; let transactionFilterType = TransactionFilterTypeDict.USUAL; if (typeof transaction.order1 !== 'undefined') { diff --git a/crypto/blockchains/waves/WavesTransferProcessor.ts b/crypto/blockchains/waves/WavesTransferProcessor.ts index 73b64fd30..ca72d42c8 100644 --- a/crypto/blockchains/waves/WavesTransferProcessor.ts +++ b/crypto/blockchains/waves/WavesTransferProcessor.ts @@ -4,26 +4,29 @@ import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; import BlocksoftUtils from '@crypto/common/BlocksoftUtils'; -import { BlocksoftBlockchainTypes } from '@crypto/blockchains/BlocksoftBlockchainTypes'; - import { transfer, broadcast } from '@waves/waves-transactions/src/index'; import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings'; -import config from '@app/config/config'; import MarketingEvent from '@app/services/Marketing/MarketingEvent'; +import { AirDAOBlockchainTypes } from '@crypto/blockchains/AirDAOBlockchainTypes'; +import config from '@constants/config'; export default class WavesTransferProcessor - implements BlocksoftBlockchainTypes.TransferProcessor + implements AirDAOBlockchainTypes.TransferProcessor { private _settings: { network: string; currencyCode: string }; - private _tokenAddress: string; + private _tokenAddress: string | boolean; private _mainCurrencyCode: string; - constructor(settings: { network: string; currencyCode: string }) { + constructor(settings: { + tokenAddress: string; + network: string; + currencyCode: string; + }) { this._settings = settings; this._tokenAddress = - typeof settings.tokenAddress !== 'undefined' + typeof settings.tokenAddress !== undefined ? settings.tokenAddress : false; @@ -45,14 +48,14 @@ export default class WavesTransferProcessor } async getFeeRate( - data: BlocksoftBlockchainTypes.TransferData, - privateData: BlocksoftBlockchainTypes.TransferPrivateData, + data: AirDAOBlockchainTypes.TransferData, + privateData: AirDAOBlockchainTypes.TransferPrivateData, additionalData: {} = {} - ): Promise { - const result: BlocksoftBlockchainTypes.FeeRateResult = { + ): Promise { + const result: AirDAOBlockchainTypes.FeeRateResult = { selectedFeeIndex: -3, shouldShowFees: false - } as BlocksoftBlockchainTypes.FeeRateResult; + } as AirDAOBlockchainTypes.FeeRateResult; result.fees = [ { @@ -67,16 +70,16 @@ export default class WavesTransferProcessor } async getTransferAllBalance( - data: BlocksoftBlockchainTypes.TransferData, - privateData: BlocksoftBlockchainTypes.TransferPrivateData, - additionalData: BlocksoftBlockchainTypes.TransferAdditionalData = {} - ): Promise { + data: AirDAOBlockchainTypes.TransferData, + privateData: AirDAOBlockchainTypes.TransferPrivateData, + additionalData: AirDAOBlockchainTypes.TransferAdditionalData = {} + ): Promise { const balance = data.amount; // @ts-ignore BlocksoftCryptoLog.log( this._settings.currencyCode + - ' WavesTransferProcessor.getTransferAllBalance ', - data.addressFrom + ' => ' + balance + `' WavesTransferProcessor.getTransferAllBalance ', + ${data.addressFrom} + ' => ' + ${balance}` ); const res = await this.getFeeRate(data, privateData, additionalData); @@ -102,10 +105,10 @@ export default class WavesTransferProcessor * @param uiData */ async sendTx( - data: BlocksoftBlockchainTypes.TransferData, - privateData: BlocksoftBlockchainTypes.TransferPrivateData, - uiData: BlocksoftBlockchainTypes.TransferUiData - ): Promise { + data: AirDAOBlockchainTypes.TransferData, + privateData: AirDAOBlockchainTypes.TransferPrivateData, + uiData: AirDAOBlockchainTypes.TransferUiData + ): Promise { if (typeof privateData.privateKey === 'undefined') { throw new Error('WAVES transaction required privateKey'); } @@ -114,7 +117,7 @@ export default class WavesTransferProcessor } let addressTo = data.addressTo; - let apiPath; + let apiPath: any; if (this._mainCurrencyCode === 'ASH') { apiPath = await BlocksoftExternalSettings.get('ASH_SERVER'); addressTo = addressTo.replace('Æx', ''); @@ -142,7 +145,7 @@ export default class WavesTransferProcessor money.assetId = this._tokenAddress; } signedData = transfer(money, { privateKey: privateData.privateKey }); - } catch (e) { + } catch (e: any) { BlocksoftCryptoLog.log( this._settings.currencyCode + ' WavesTransferProcessor.sendTx ' + @@ -183,7 +186,7 @@ export default class WavesTransferProcessor }; } - const result = {} as BlocksoftBlockchainTypes.SendTxResult; + const result = {} as AirDAOBlockchainTypes.SendTxResult; try { const resp = await new Promise((resolve, reject) => { BlocksoftCryptoLog.log( @@ -198,10 +201,11 @@ export default class WavesTransferProcessor JSON.stringify(apiPath) ); broadcast(signedData, apiPath) - .then((resp) => { + // tslint:disable-next-line:no-shadowed-variable + .then((resp: unknown) => { resolve(resp); }) - .catch((e) => { + .catch((e: any) => { reject(e); }); }); @@ -217,7 +221,7 @@ export default class WavesTransferProcessor resp ); result.transactionHash = signedData.id; - } catch (e) { + } catch (e: any) { if (config.debug.cryptoErrors) { console.log( this._settings.currencyCode + @@ -242,16 +246,12 @@ export default class WavesTransferProcessor ' send error ' + e.message ); - this.checkError(e, data, false); + this.checkError(e, data); } return result; } - checkError( - e: any, - data: { addressFrom: string; addressTo: string }, - txRBF = false - ) { + checkError(e: any, data: { addressFrom: string; addressTo: string }) { if ( e.message.indexOf( 'waves balance to (at least) temporary negative state' diff --git a/crypto/blockchains/xlm/XlmTransferProcessor.ts b/crypto/blockchains/xlm/XlmTransferProcessor.ts index 3206b87fd..1c53bd908 100644 --- a/crypto/blockchains/xlm/XlmTransferProcessor.ts +++ b/crypto/blockchains/xlm/XlmTransferProcessor.ts @@ -23,16 +23,17 @@ */ import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog'; import BlocksoftUtils from '../../common/BlocksoftUtils'; +// @ts-ignore import MarketingEvent from '../../../app/services/Marketing/MarketingEvent'; -import { BlocksoftBlockchainTypes } from '../BlocksoftBlockchainTypes'; import { XlmTxSendProvider } from './basic/XlmTxSendProvider'; import BlocksoftDispatcher from '@lib/BlocksoftDispatcher'; +import { AirDAOBlockchainTypes } from '@crypto/blockchains/AirDAOBlockchainTypes'; const FEE_DECIMALS = 7; export default class XlmTransferProcessor - implements BlocksoftBlockchainTypes.TransferProcessor + implements AirDAOBlockchainTypes.TransferProcessor { private _settings: { network: string; currencyCode: string }; private _provider: XlmTxSendProvider; @@ -51,8 +52,8 @@ export default class XlmTransferProcessor } async checkTransferHasError( - data: BlocksoftBlockchainTypes.CheckTransferHasErrorData - ): Promise { + data: AirDAOBlockchainTypes.CheckTransferHasErrorData + ): Promise { // @ts-ignore if (data.amount && data.amount * 1 > 20) { return { isOk: true }; @@ -78,14 +79,14 @@ export default class XlmTransferProcessor } async getFeeRate( - data: BlocksoftBlockchainTypes.TransferData, - privateData: BlocksoftBlockchainTypes.TransferPrivateData, + data: AirDAOBlockchainTypes.TransferData, + privateData: AirDAOBlockchainTypes.TransferPrivateData, additionalData: {} = {} - ): Promise { - const result: BlocksoftBlockchainTypes.FeeRateResult = { + ): Promise { + const result: AirDAOBlockchainTypes.FeeRateResult = { selectedFeeIndex: -1, shouldShowFees: false - } as BlocksoftBlockchainTypes.FeeRateResult; + } as AirDAOBlockchainTypes.FeeRateResult; // @ts-ignore if (data.amount * 1 <= 0) { @@ -132,7 +133,7 @@ export default class XlmTransferProcessor result.fees = [ { langMsg: 'xrp_speed_one', - feeForTx: fee, + feeForTx: fee.toString(), amountForTx: data.amount, blockchainData: getFee } @@ -142,10 +143,10 @@ export default class XlmTransferProcessor } async getTransferAllBalance( - data: BlocksoftBlockchainTypes.TransferData, - privateData: BlocksoftBlockchainTypes.TransferPrivateData, - additionalData: BlocksoftBlockchainTypes.TransferAdditionalData = {} - ): Promise { + data: AirDAOBlockchainTypes.TransferData, + privateData: AirDAOBlockchainTypes.TransferPrivateData, + additionalData: AirDAOBlockchainTypes.TransferAdditionalData = {} + ): Promise { const balance = data.amount; // @ts-ignore BlocksoftCryptoLog.log( @@ -200,10 +201,10 @@ export default class XlmTransferProcessor } async sendTx( - data: BlocksoftBlockchainTypes.TransferData, - privateData: BlocksoftBlockchainTypes.TransferPrivateData, - uiData: BlocksoftBlockchainTypes.TransferUiData - ): Promise { + data: AirDAOBlockchainTypes.TransferData, + privateData: AirDAOBlockchainTypes.TransferPrivateData, + uiData: AirDAOBlockchainTypes.TransferUiData + ): Promise { if (typeof privateData.privateKey === 'undefined') { throw new Error('XLM transaction required privateKey'); } @@ -250,7 +251,7 @@ export default class XlmTransferProcessor typeof uiData.selectedFee.rawOnly !== 'undefined' && uiData.selectedFee.rawOnly ) { - return { rawOnly: uiData.selectedFee.rawOnly, raw }; + return { raw: uiData.selectedFee.raw }; } let result = false; diff --git a/crypto/blockchains/xlm/basic/XlmTxSendProvider.ts b/crypto/blockchains/xlm/basic/XlmTxSendProvider.ts index 1b7f3020d..41643920d 100644 --- a/crypto/blockchains/xlm/basic/XlmTxSendProvider.ts +++ b/crypto/blockchains/xlm/basic/XlmTxSendProvider.ts @@ -5,10 +5,9 @@ */ import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; import BlocksoftExternalSettings from '../../../common/BlocksoftExternalSettings'; -import { BlocksoftBlockchainTypes } from '../../BlocksoftBlockchainTypes'; - import { XrpTxUtils } from '../../xrp/basic/XrpTxUtils'; import config from '@constants/config'; +import { AirDAOBlockchainTypes } from '@crypto/blockchains/AirDAOBlockchainTypes'; const StellarSdk = require('stellar-sdk'); @@ -57,9 +56,9 @@ export class XlmTxSendProvider { } async getPrepared( - data: BlocksoftBlockchainTypes.TransferData, - privateData: BlocksoftBlockchainTypes.TransferPrivateData, - uiData: BlocksoftBlockchainTypes.TransferUiData, + data: AirDAOBlockchainTypes.TransferData, + privateData: AirDAOBlockchainTypes.TransferPrivateData, + uiData: AirDAOBlockchainTypes.TransferUiData, type = 'usual' ) { const account = await this._api.loadAccount(data.addressFrom); @@ -141,8 +140,8 @@ export class XlmTxSendProvider { result = await response.json(); if ( result && - typeof result.extras !== 'undefined' && - typeof result.extras.result_codes !== 'undefined' + typeof result.extras !== undefined && + typeof result.extras.result_codes !== undefined ) { if (config.debug.cryptoErrors) { console.log( @@ -155,14 +154,14 @@ export class XlmTxSendProvider { JSON.stringify(result.extras.result_codes) ); - if (typeof result.extras.result_codes.operations !== 'undefined') { + if (typeof result.extras.result_codes.operations !== undefined) { throw new Error(result.extras.result_codes.operations[0] + ' ' + raw); } - if (typeof result.extras.result_codes.transaction !== 'undefined') { + if (typeof result.extras.result_codes.transaction !== undefined) { throw new Error(result.extras.result_codes.transaction + ' ' + raw); } } - if (typeof result.status !== 'undefined') { + if (typeof result.status !== undefined) { if ( result.status === 406 || result.status === 400 || diff --git a/crypto/blockchains/xmr/XmrAddressProcessor.ts b/crypto/blockchains/xmr/XmrAddressProcessor.ts index 345d53580..561e09e61 100644 --- a/crypto/blockchains/xmr/XmrAddressProcessor.ts +++ b/crypto/blockchains/xmr/XmrAddressProcessor.ts @@ -50,7 +50,7 @@ export default class XmrAddressProcessor { mymoneroError: number; }; }> { - let walletMnemonic = false; + let walletMnemonic: boolean | { hash: any } = false; try { walletMnemonic = await BlocksoftSecrets.getWords({ currencyCode: 'XMR', @@ -102,10 +102,14 @@ export default class XmrAddressProcessor { } else { privateKey = Buffer.from(privateKey); } - const keyPair = bitcoin.ECPair.fromPrivateKey(privateKey, { network: BTC }); + const keyPair = bitcoin.ECPair.fromPrivateKey(privateKey as Buffer, { + network: BTC + }); const rawPrivateKey = keyPair.privateKey; + // @ts-ignore const rawSecretSpendKey = soliditySha3(rawPrivateKey); const rawSecretSpendKeyBuffer = Buffer.from( + // @ts-ignore rawSecretSpendKey.substr(2), 'hex' ); @@ -149,18 +153,19 @@ export default class XmrAddressProcessor { try { linkParamsLogin = { address, + // @ts-ignore view_key: MoneroUtils.normString(secretViewKey.toString('hex')), create_account: true, generated_locally: true }; - const resLogin = await BlocksoftAxios.post( + const resLogin = (await BlocksoftAxios.post( 'https://api.mymonero.com:8443/login', linkParamsLogin - ); + )) as unknown as { data: any }; if ( - typeof resLogin.data === 'undefined' || + resLogin.data === undefined || !resLogin.data || - typeof resLogin.data.new_address === 'undefined' + resLogin.data.new_address === undefined ) { throw new Error('no data'); } diff --git a/crypto/blockchains/xmr/XmrScannerProcessor.ts b/crypto/blockchains/xmr/XmrScannerProcessor.ts index 8822ebcad..8c47f6370 100644 --- a/crypto/blockchains/xmr/XmrScannerProcessor.ts +++ b/crypto/blockchains/xmr/XmrScannerProcessor.ts @@ -29,11 +29,15 @@ export default class XmrScannerProcessor { _maxBlockNumber = 500000000; - constructor(settings) { + constructor(settings: any) { this._settings = settings; } - async _getCache(address, additionalData, walletHash) { + async _getCache( + address: string | number, + additionalData: any, + walletHash: any + ) { if (typeof CACHE[address] !== 'undefined') { CACHE[address].provider = 'mymonero-cache-all'; return CACHE[address]; @@ -49,7 +53,11 @@ export default class XmrScannerProcessor { * @returns {Promise} * @private */ - async _get(address, additionalData, walletHash) { + async _get( + address: string, + additionalData: { derivationIndex: any; publicSpendKey: string }, + walletHash: string + ) { BlocksoftCryptoLog.log( 'XMR XmrScannerProcessor._get ' + walletHash + ' ' + address ); @@ -103,18 +111,18 @@ export default class XmrScannerProcessor { try { BlocksoftCryptoLog.log( this._settings.currencyCode + - ' XmrScannerProcessor._get start ' + - link + + `' XmrScannerProcessor._get start ' + + ${link} + 'get_address_info', - JSON.stringify(linkParams) + ${JSON.stringify(linkParams)}` ); res = await BlocksoftAxios.post(link + 'get_address_info', linkParams); } catch (e: any) { BlocksoftCryptoLog.log( this._settings.currencyCode + - ' XmrScannerProcessor._get error ' + - e.message, - JSON.stringify(linkParams) + `' XmrScannerProcessor._get error ' + + ${e.message}, + ${JSON.stringify(linkParams)}` ); if ( CACHE_SHOWN_ERROR === 0 && @@ -148,9 +156,9 @@ export default class XmrScannerProcessor { } catch (e: any) { BlocksoftCryptoLog.log( this._settings.currencyCode + - ' XmrScannerProcessor._get login error ' + - e.message, - linkParamsLogin + `' XmrScannerProcessor._get login error ' + + ${e.message}, + ${linkParamsLogin}` ); if ( CACHE_SHOWN_ERROR === 0 && @@ -186,7 +194,7 @@ export default class XmrScannerProcessor { additionalData.publicSpendKey, spendKey ); - } catch (e) { + } catch (e: any) { if (config.debug.cryptoErrors) { console.log( 'XMR XmrScannerProcessor._get MoneroUtilsParser.parseAddressInfo error ' + @@ -262,7 +270,11 @@ export default class XmrScannerProcessor { * @param {string} walletHash * @return {Promise<{balance:*, unconfirmed:*, provider:string}>} */ - async getBalanceBlockchain(address, additionalData, walletHash) { + async getBalanceBlockchain( + address: string, + additionalData: { derivationIndex: any; publicSpendKey: string }, + walletHash: string + ) { if (address === 'invalidRecheck1') { return { balance: 0, unconfirmed: 0, provider: 'error' }; } @@ -284,7 +296,10 @@ export default class XmrScannerProcessor { * @param {string} scanData.account.walletHash * @return {Promise} */ - async getTransactionsBlockchain(scanData, source = '') { + async getTransactionsBlockchain(scanData: { + account: { address: string; walletHash: any }; + additional: any; + }) { const address = scanData.account.address.trim(); if (address === 'invalidRecheck1') { return []; @@ -334,9 +349,23 @@ export default class XmrScannerProcessor { * @return {Promise} * @private */ - async _unifyTransaction(address, lastBlock, transaction) { + async _unifyTransaction( + address: string, + lastBlock: number, + transaction: { + confirmations: number; + height: number; + unlock_time: number; + total_received: string; + total_sent: string; + timestamp: any; + hash: any; + id: any; + fee: any; + } + ) { let transactionStatus = 'new'; - transaction.confirmations = lastBlock * 1 - transaction.height * 1; + transaction.confirmations = lastBlock - transaction.height; // if (transaction.mempool === false) { if (transaction.confirmations >= this._blocksToConfirm) { transactionStatus = 'success'; @@ -346,7 +375,7 @@ export default class XmrScannerProcessor { // } if (typeof transaction.unlock_time !== 'undefined') { - const unlockTime = transaction.unlock_time * 1; + const unlockTime = transaction.unlock_time; if (unlockTime > 0) { if (unlockTime < this._maxBlockNumber) { // then unlock time is block height diff --git a/crypto/blockchains/xmr/XmrSecretsProcessor.ts b/crypto/blockchains/xmr/XmrSecretsProcessor.ts index d5b60cf9e..278fdda1a 100644 --- a/crypto/blockchains/xmr/XmrSecretsProcessor.ts +++ b/crypto/blockchains/xmr/XmrSecretsProcessor.ts @@ -50,26 +50,21 @@ export default class XmrSecretsProcessor { const secretViewKey = MoneroUtils.hash_to_scalar(secretSpendKey); + // @ts-ignore const words = MoneroMnemonic.secret_spend_key_to_words(secretSpendKey); const publicSpendKey = MoneroUtils.secret_key_to_public_key(secretSpendKey); const publicViewKey = MoneroUtils.secret_key_to_public_key(secretViewKey); - - const address = MoneroUtils.pub_keys_to_address( - 0, - publicSpendKey, - publicViewKey - ); - + MoneroUtils.pub_keys_to_address(0, publicSpendKey, publicViewKey); /*console.log({ - secretSpendKey, - secretViewKey, - words, - publicViewKey: publicViewKey.toString('hex'), - publicSpendKey: publicSpendKey.toString('hex'), - address - })*/ + secretSpendKey, + secretViewKey, + words, + publicViewKey: publicViewKey.toString('hex'), + publicSpendKey: publicSpendKey.toString('hex'), + address + })*/ return words; } diff --git a/crypto/blockchains/xmr/XmrTransferProcessor.ts b/crypto/blockchains/xmr/XmrTransferProcessor.ts index 71c52d90c..b06a66cad 100644 --- a/crypto/blockchains/xmr/XmrTransferProcessor.ts +++ b/crypto/blockchains/xmr/XmrTransferProcessor.ts @@ -2,24 +2,15 @@ * @version 0.20 */ import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog'; - import MoneroUtilsParser from './ext/MoneroUtilsParser'; import XmrSendProvider from './providers/XmrSendProvider'; import XmrUnspentsProvider from './providers/XmrUnspentsProvider'; -<<<<<<< Updated upstream - -import { BlocksoftBlockchainTypes } from '../BlocksoftBlockchainTypes'; -import config from '../../../app/config/config'; -import BlocksoftPrettyNumbers from '@crypto/common/BlocksoftPrettyNumbers'; -======= import config from '@constants/config'; - import BlocksoftPrettyNumbers from '@crypto/common/BlocksoftPrettyNumbers'; -import { BlocksoftBlockchainTypes } from '@lib/blockchains/BlocksoftBlockchainTypes'; ->>>>>>> Stashed changes +import { AirDAOBlockchainTypes } from '@crypto/blockchains/AirDAOBlockchainTypes'; export default class XmrTransferProcessor - implements BlocksoftBlockchainTypes.TransferProcessor + implements AirDAOBlockchainTypes.TransferProcessor { private sendProvider: XmrSendProvider; private unspentsProvider: XmrUnspentsProvider; @@ -40,13 +31,13 @@ export default class XmrTransferProcessor } async getFeeRate( - data: BlocksoftBlockchainTypes.TransferData, - privateData: BlocksoftBlockchainTypes.TransferPrivateData, + data: AirDAOBlockchainTypes.TransferData, + privateData: AirDAOBlockchainTypes.TransferPrivateData, additionalData: {} = {} - ): Promise { - const result: BlocksoftBlockchainTypes.FeeRateResult = { + ): Promise { + const result: AirDAOBlockchainTypes.FeeRateResult = { selectedFeeIndex: -1 - } as BlocksoftBlockchainTypes.FeeRateResult; + } as AirDAOBlockchainTypes.FeeRateResult; // @ts-ignore if (data.amount * 1 <= 0) { @@ -136,27 +127,17 @@ export default class XmrTransferProcessor ) } ], -<<<<<<< Updated upstream - shouldSweep: data.isTransferAll ? true : false, -======= shouldSweep: data.isTransferAll, ->>>>>>> Stashed changes address: data.addressFrom, privateViewKey: privViewKey, privateSpendKey: privSpendKey, publicSpendKey: pubSpendKey, priority: '' + i, nettype: 'MAINNET', -<<<<<<< Updated upstream - unspentOuts: unspentOuts, - randomOutsCb: (numberOfOuts) => { - const amounts = []; -======= unspentOuts, randomOutsCb: (numberOfOuts: number) => { const amounts = []; // tslint:disable-next-line:no-shadowed-variable ->>>>>>> Stashed changes for (let i = 0; i < numberOfOuts; i++) { amounts.push('0'); } @@ -180,7 +161,7 @@ export default class XmrTransferProcessor usingOuts: fee.using_outs, simplePriority: i } - } as BlocksoftBlockchainTypes.Fee; + } as AirDAOBlockchainTypes.Fee; const logTmp = { langMsg: 'xmr_speed_' + i, @@ -204,11 +185,7 @@ export default class XmrTransferProcessor result.fees.push(tmp); logFees.push(logTmp); } -<<<<<<< Updated upstream - } catch (e) { -======= } catch (e: any) { ->>>>>>> Stashed changes if (e.message.indexOf('pendable balance too low') !== -1) { // do nothing noBalanceError = true; @@ -266,18 +243,14 @@ export default class XmrTransferProcessor // @ts-ignore BlocksoftCryptoLog.log( this._settings.currencyCode + - ' XmrTransferProcessor.getFeeRate ' + - data.addressFrom + + `' XmrTransferProcessor.getFeeRate ' + + ${data.addressFrom} + ' => ' + - data.addressTo + + ${data.addressTo} + ' finished amount: ' + - data.amount + + ${data.amount} + ' fee: ', -<<<<<<< Updated upstream -======= - // @ts-ignore ->>>>>>> Stashed changes - logFees + ${logFees}` ); if (result.fees.length < 3) { @@ -289,24 +262,16 @@ export default class XmrTransferProcessor } async getTransferAllBalance( - data: BlocksoftBlockchainTypes.TransferData, - privateData: BlocksoftBlockchainTypes.TransferPrivateData, - additionalData: BlocksoftBlockchainTypes.TransferAdditionalData = {} - ): Promise { + data: AirDAOBlockchainTypes.TransferData, + privateData: AirDAOBlockchainTypes.TransferPrivateData, + additionalData: AirDAOBlockchainTypes.TransferAdditionalData = {} + ): Promise { const balance = data.amount; -<<<<<<< Updated upstream - // @ts-ignore BlocksoftCryptoLog.log( this._settings.currencyCode + - ' XmrTransferProcessor.getTransferAllBalance ', -======= - BlocksoftCryptoLog.log( - this._settings.currencyCode + - ' XmrTransferProcessor.getTransferAllBalance ', - // @ts-ignore ->>>>>>> Stashed changes - data.addressFrom + ' => ' + balance + `' XmrTransferProcessor.getTransferAllBalance ', + ${data.addressFrom + ' => ' + balance}` ); data.isTransferAll = true; @@ -332,10 +297,10 @@ export default class XmrTransferProcessor } async sendTx( - data: BlocksoftBlockchainTypes.TransferData, - privateData: BlocksoftBlockchainTypes.TransferPrivateData, - uiData: BlocksoftBlockchainTypes.TransferUiData - ): Promise { + data: AirDAOBlockchainTypes.TransferData, + privateData: AirDAOBlockchainTypes.TransferPrivateData, + uiData: AirDAOBlockchainTypes.TransferUiData + ): Promise { if (typeof privateData.privateKey === 'undefined') { throw new Error('XMR transaction required privateKey'); } @@ -349,16 +314,12 @@ export default class XmrTransferProcessor BlocksoftCryptoLog.log( this._settings.currencyCode + - ' XmrTransferProcessor.sendTx started ' + - data.addressFrom + + `' XmrTransferProcessor.sendTx started ' + + ${data.addressFrom} + '=>' + - data.addressTo + + ${data.addressTo} + ' fee', -<<<<<<< Updated upstream -======= - // @ts-ignore ->>>>>>> Stashed changes - uiData.selectedFee + ${uiData.selectedFee}` ); let foundFee = uiData?.selectedFee; if (data.addressTo !== uiData.selectedFee.addressToTx) { @@ -387,21 +348,14 @@ export default class XmrTransferProcessor } BlocksoftCryptoLog.log( this._settings.currencyCode + - ' XmrTransferProcessor.sendTx rechecked ' + - data.addressFrom + + `' XmrTransferProcessor.sendTx rechecked ' + + ${data.addressFrom} + '=>' + - data.addressTo + + ${data.addressTo} + ' found fee', -<<<<<<< Updated upstream - foundFee - ); - } catch (e) { -======= - // @ts-ignore - foundFee + ${foundFee}` ); } catch (e: any) { ->>>>>>> Stashed changes BlocksoftCryptoLog.log( this._settings.currencyCode + ' XmrTransferProcessor.sendTx rechecked ' + @@ -432,10 +386,7 @@ export default class XmrTransferProcessor typeof uiData.selectedFee.rawOnly !== 'undefined' && uiData.selectedFee.rawOnly ) { -<<<<<<< Updated upstream -======= // TODO fix this ts error ->>>>>>> Stashed changes return { rawOnly: uiData.selectedFee.rawOnly, raw: rawTxHex }; } diff --git a/crypto/blockchains/xmr/ext/MoneroMnemonic.ts b/crypto/blockchains/xmr/ext/MoneroMnemonic.ts index 9de2141d8..efcc96e6d 100644 --- a/crypto/blockchains/xmr/ext/MoneroMnemonic.ts +++ b/crypto/blockchains/xmr/ext/MoneroMnemonic.ts @@ -38,7 +38,8 @@ const secret_spend_key_to_words = ( } } - const moneroDict: MoneroDictionary = MoneroDict as MoneroDictionary; + const moneroDict: MoneroDictionary = + MoneroDict as unknown as MoneroDictionary; const moneroWords: DictionaryWord[] = moneroDict.monero_words_english; const w1 = w0 % moneroWords.length; diff --git a/crypto/blockchains/xmr/ext/MoneroUtils.ts b/crypto/blockchains/xmr/ext/MoneroUtils.ts index aaadcdb94..359d1a4b9 100644 --- a/crypto/blockchains/xmr/ext/MoneroUtils.ts +++ b/crypto/blockchains/xmr/ext/MoneroUtils.ts @@ -8,7 +8,7 @@ import { soliditySha3 } from 'web3-utils'; // @ts-ignore import * as elliptic from 'elliptic'; -const Ed25519 = elliptic.eddsa('ed25519'); +const Ed25519 = new elliptic.eddsa('ed25519'); /** Monero base58 is not like Bitcoin base58, bytes are converted in 8-byte blocks. * https://docs.rs/base58-monero/0.2.0/base58_monero/ diff --git a/crypto/blockchains/xmr/ext/MoneroUtilsParser.ts b/crypto/blockchains/xmr/ext/MoneroUtilsParser.ts index 74b6ffcff..915e9405f 100644 --- a/crypto/blockchains/xmr/ext/MoneroUtilsParser.ts +++ b/crypto/blockchains/xmr/ext/MoneroUtilsParser.ts @@ -173,7 +173,7 @@ export default { retString = await MY_MONERO.core.Module.prepareTx( JSON.stringify(args, null, '') ); - } catch (e) { + } catch (e: any) { throw Error(' MY_MONERO.core.Module.prepareTx error ' + e.message); } @@ -188,8 +188,9 @@ export default { const _getRandomOuts = async ( numberOfOuts: number, - randomOutsCb: Function + randomOutsCb: (numberOfOuts: number) => void ) => { + // tslint:disable-next-line:no-shadowed-variable const randomOuts = await randomOutsCb(numberOfOuts); if ( typeof randomOuts.amount_outs === 'undefined' || diff --git a/crypto/blockchains/xmr/providers/XmrSendProvider.ts b/crypto/blockchains/xmr/providers/XmrSendProvider.ts index 32e74acc8..07f7df976 100644 --- a/crypto/blockchains/xmr/providers/XmrSendProvider.ts +++ b/crypto/blockchains/xmr/providers/XmrSendProvider.ts @@ -1,100 +1,118 @@ /** * @version 0.11 */ +// @ts-ignore import settingsActions from '../../../../app/appstores/Stores/Settings/SettingsActions'; import BlocksoftAxios from '../../../common/BlocksoftAxios'; import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; +interface SendParams { + address: string; + privViewKey: string; + tx: string; +} + +interface BlocksoftAxiosResponse { + data: { + message: string; + double_spend?: boolean; + fee_too_low?: boolean; + overspend?: boolean; + status?: string; + }; + message?: string; +} + export default class XmrSendProvider { private _settings: any; - private _link: string | boolean; - private _serverUrl: string | undefined; + private _link: string | undefined; + private _serverUrl: string; constructor(settings: any) { this._settings = settings; - this._link = false; + this._link = undefined; + this._serverUrl = 'api.mymonero.com:8443'; } - private async _init(): Promise { - if (this._link) return false; - this._serverUrl = await settingsActions.getSetting('xmrServerSend'); - if (!this._serverUrl || this._serverUrl === 'false') { - this._serverUrl = 'api.mymonero.com:8443'; + private async _init(): Promise { + if (this._link !== undefined) { + return; } - let link = this._serverUrl.trim(); - if (link.substr(0, 4).toLowerCase() !== 'http') { - if (link.indexOf('mymonero.com') !== -1) { - link = 'https://' + this._serverUrl; - } else { - link = 'http://' + this._serverUrl; - } + const serverUrl = await settingsActions.getSetting('xmrServerSend'); + if (serverUrl && serverUrl !== 'false') { + this._serverUrl = serverUrl.trim(); } - if (link[link.length - 1] !== '/') { - link = link + '/'; + + let link = this._serverUrl; + if (!link.startsWith('http')) { + link = `https://${this._serverUrl}`; + } + + if (!link.endsWith('/')) { + link += '/'; } this._link = link; - return true; } - public async send(params: any): Promise { + public async send(params: SendParams): Promise { await this._init(); - // curl http://127.0.0.1:18081/send_raw_transaction -d '{"tx_as_hex":"de6a3...", "do_not_relay":false}' -H 'Content-Type: application/json' - // https://web.getmonero.org/resources/developer-guides/daemon-rpc.html#send_raw_transaction - // const resNode = await BlocksoftAxios.post('http://node.moneroworld.com:18089/send_raw_transaction', {tx_as_hex : params.tx, do_not_relay : false}) - // return resNode.data - - if ( - typeof this._link !== 'boolean' && - this._link?.indexOf('mymonero.com') !== -1 - ) { - try { - const res = await BlocksoftAxios.post(this._link + 'submit_raw_tx', { + + try { + let resNode: BlocksoftAxiosResponse; + + if (this._link?.includes('mymonero.com')) { + // @ts-ignore + resNode = await BlocksoftAxios.post(this._link + 'submit_raw_tx', { address: params.address, view_key: params.privViewKey, tx: params.tx }); - BlocksoftCryptoLog.log( - 'XmrSendProvider mymonero.com node ' + this._link, - res.data + } else { + // @ts-ignore + resNode = await BlocksoftAxios.post( + this._link + 'send_raw_transaction', + { + tx_as_hex: params.tx, + do_not_relay: false + } ); - return res.data; - } catch (e: any) { - if (e.message.indexOf('double') !== -1) { - throw new Error('SERVER_RESPONSE_DOUBLE_SPEND'); - } else { - throw e; - } } - } else { - const resNode = await BlocksoftAxios.post( - this._link + 'send_raw_transaction', - { - tx_as_hex: params.tx, - do_not_relay: false - } - ); + BlocksoftCryptoLog.log( - 'XmrSendProvider custom node ' + this._link, - resNode.data + `'XmrSendProvider node ${this._link},' + + ${resNode.data}` ); - if (typeof resNode.data.double_spend === 'undefined') { - throw new Error('SERVER_RESPONSE_BAD_SEND_NODE'); + const responseData = resNode.data; + + if (typeof responseData !== 'object') { + throw new Error('SERVER_RESPONSE_NOT_VALID'); } - if (resNode.data.double_spend) { + + if (responseData.double_spend) { throw new Error('SERVER_RESPONSE_DOUBLE_SPEND'); } - if (resNode.data.fee_too_low) { + + if (responseData.fee_too_low) { throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_FEE'); } - if (resNode.data.overspend) { + + if (responseData.overspend) { throw new Error('SERVER_RESPONSE_TOO_BIG_FEE_FOR_TRANSACTION'); } - if (resNode.data.status === 'Failed') { - throw new Error(JSON.stringify(resNode.data)); + + if (responseData.status === 'Failed') { + throw new Error(responseData.message); + } + + return responseData; + } catch (e: any) { + if (e.message.includes('double')) { + throw new Error('SERVER_RESPONSE_DOUBLE_SPEND'); + } else { + throw e; } - return resNode.data; } } } diff --git a/crypto/blockchains/xmr/providers/XmrUnspentsProvider.ts b/crypto/blockchains/xmr/providers/XmrUnspentsProvider.ts index 27c5b588c..526bb1c79 100644 --- a/crypto/blockchains/xmr/providers/XmrUnspentsProvider.ts +++ b/crypto/blockchains/xmr/providers/XmrUnspentsProvider.ts @@ -1,14 +1,20 @@ /** * @version 0.11 */ +// @ts-ignore import settingsActions from '../../../../app/appstores/Stores/Settings/SettingsActions'; import BlocksoftAxios from '../../../common/BlocksoftAxios'; import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; +interface ApiResponse { + data: any; +} + export default class XmrUnspentsProvider { private _settings: any; private _link: string | false; private _cache: { [key: string]: any }; + private _serverUrl: string | undefined; constructor(settings: any) { this._settings = settings; @@ -19,20 +25,22 @@ export default class XmrUnspentsProvider { init(): void { if (this._link) return; this._serverUrl = settingsActions.getSettingStatic('xmrServer'); - if (!this._serverUrl || this._serverUrl === 'false') { + if (!this._serverUrl) { this._serverUrl = 'api.mymonero.com:8443'; } - let link = this._serverUrl.trim(); - if (link.substr(0, 4).toLowerCase() !== 'http') { - link = 'https://' + this._serverUrl; - } - if (link[link.length - 1] !== '/') { - link = link + '/'; - } + if (typeof this._serverUrl === 'string') { + let link = this._serverUrl.trim(); + if (link.substr(0, 4).toLowerCase() !== 'http') { + link = 'https://' + this._serverUrl; + } + if (link[link.length - 1] !== '/') { + link = link + '/'; + } - this._link = link; - this._cache = {}; + this._link = link; + this._cache = {}; + } } async _getUnspents( @@ -41,8 +49,9 @@ export default class XmrUnspentsProvider { ): Promise { try { const key = JSON.stringify(params); - let res: { data: any } = { data: {} }; + let res: ApiResponse; if (typeof this._cache[key] === 'undefined') { + // @ts-ignore BlocksoftCryptoLog.log('XmrUnspentsProvider Xmr._getUnspents', key); /* const linkParams = { @@ -54,6 +63,7 @@ export default class XmrUnspentsProvider { dust_threshold: '2000000000' } */ + // @ts-ignore res = await BlocksoftAxios.post( this._link + 'get_unspent_outs', params @@ -99,7 +109,8 @@ export default class XmrUnspentsProvider { } */ - const res = await BlocksoftAxios.post( + // @ts-ignore + const res: ApiResponse = await BlocksoftAxios.post( this._link + 'get_random_outs', params ); @@ -109,19 +120,19 @@ export default class XmrUnspentsProvider { ); if ( - typeof res.data === 'undefined' || - !typeof res.data || - typeof res.data.amount_outs === 'undefined' || - !res.data.amount_outs || - res.data.amount_outs.length === 0 + typeof res === 'object' && + res.data && + typeof res.data.amount_outs !== 'undefined' && + res.data.amount_outs && + res.data.amount_outs.length > 0 ) { - throw new Error('SERVER_RESPONSE_NO_RESPONSE_XMR'); - } - - if (typeof fn === 'undefined' || !fn) { - return res.data; + if (typeof fn === 'undefined' || !fn) { + return res.data; + } else { + fn(null, res.data); + } } else { - fn(null, res.data); + throw new Error('SERVER_RESPONSE_NO_RESPONSE_XMR'); } } catch (e: any) { if (e.message.indexOf('SERVER_RESPONSE') === -1) { diff --git a/crypto/blockchains/xrp/XrpScannerProcessor.ts b/crypto/blockchains/xrp/XrpScannerProcessor.ts index ab89ba386..417a52a19 100644 --- a/crypto/blockchains/xrp/XrpScannerProcessor.ts +++ b/crypto/blockchains/xrp/XrpScannerProcessor.ts @@ -61,16 +61,13 @@ export default class XrpScannerProcessor { return { balance, unconfirmed: 0, provider }; } - async getTransactionsBlockchain( - scanData: { - account: { - address: string; - walletHash: string; - }; - additional: any; - }, - source = '' - ): Promise { + async getTransactionsBlockchain(scanData: { + account: { + address: string; + walletHash: string; + }; + additional: any; + }): Promise { await this.init(); const address = scanData.account.address.trim(); await BlocksoftCryptoLog.log( diff --git a/crypto/blockchains/xrp/XrpTransferProcessor.ts b/crypto/blockchains/xrp/XrpTransferProcessor.ts index de7d26834..69cb9f701 100644 --- a/crypto/blockchains/xrp/XrpTransferProcessor.ts +++ b/crypto/blockchains/xrp/XrpTransferProcessor.ts @@ -8,34 +8,21 @@ */ import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog'; import BlocksoftUtils from '../../common/BlocksoftUtils'; -<<<<<<< Updated upstream -import BlocksoftDispatcher from '../BlocksoftDispatcher'; -import MarketingEvent from '../../../app/services/Marketing/MarketingEvent'; - -import { BlocksoftBlockchainTypes } from '../BlocksoftBlockchainTypes'; import { XrpTxSendProvider } from './basic/XrpTxSendProvider'; -import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings'; -======= import MarketingEvent from '../../../app/services/Marketing/MarketingEvent'; -import { XrpTxSendProvider } from './basic/XrpTxSendProvider'; import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings'; -import { BlocksoftBlockchainTypes } from '@lib/blockchains/BlocksoftBlockchainTypes'; -import BlocksoftDispatcher from '@lib/blockchains/BlocksoftDispatcher'; ->>>>>>> Stashed changes +import { AirDAOBlockchainTypes } from '@crypto/blockchains/AirDAOBlockchainTypes'; +import BlocksoftDispatcher from '@lib/BlocksoftDispatcher'; const FEE_DECIMALS = 6; export default class XrpTransferProcessor - implements BlocksoftBlockchainTypes.TransferProcessor + implements AirDAOBlockchainTypes.TransferProcessor { private _settings: { network: string; currencyCode: string }; - private _provider: XrpTxSendProvider; -<<<<<<< Updated upstream - private _inited: boolean = false; -======= + private _provider: XrpTxSendProvider | undefined; private _inited = false; ->>>>>>> Stashed changes constructor(settings: { network: string; currencyCode: string }) { this._settings = settings; @@ -50,8 +37,8 @@ export default class XrpTransferProcessor } async checkTransferHasError( - data: BlocksoftBlockchainTypes.CheckTransferHasErrorData - ): Promise { + data: AirDAOBlockchainTypes.CheckTransferHasErrorData + ): Promise { // @ts-ignore if ( data.amount && @@ -80,14 +67,14 @@ export default class XrpTransferProcessor } async getFeeRate( - data: BlocksoftBlockchainTypes.TransferData, - privateData: BlocksoftBlockchainTypes.TransferPrivateData, + data: AirDAOBlockchainTypes.TransferData, + privateData: AirDAOBlockchainTypes.TransferPrivateData, additionalData: {} = {} - ): Promise { - const result: BlocksoftBlockchainTypes.FeeRateResult = { + ): Promise { + const result: AirDAOBlockchainTypes.FeeRateResult = { selectedFeeIndex: -1, shouldShowFees: false - } as BlocksoftBlockchainTypes.FeeRateResult; + } as AirDAOBlockchainTypes.FeeRateResult; // @ts-ignore if (data.amount * 1 <= 0) { @@ -119,14 +106,7 @@ export default class XrpTransferProcessor this._inited = true; } txJson = await this._provider.getPrepared(data); -<<<<<<< Updated upstream - } catch (e) { -======= } catch (e: any) { ->>>>>>> Stashed changes - if (e.message.indexOf('connect() timed out after') !== -1) { - return false; - } if (e.message.indexOf('Account not found') !== -1) { return false; } @@ -169,10 +149,10 @@ export default class XrpTransferProcessor } async getTransferAllBalance( - data: BlocksoftBlockchainTypes.TransferData, - privateData: BlocksoftBlockchainTypes.TransferPrivateData, - additionalData: BlocksoftBlockchainTypes.TransferAdditionalData = {} - ): Promise { + data: AirDAOBlockchainTypes.TransferData, + privateData: AirDAOBlockchainTypes.TransferPrivateData, + additionalData: AirDAOBlockchainTypes.TransferAdditionalData = {} + ): Promise { const balance = data.amount; // @ts-ignore @@ -222,10 +202,10 @@ export default class XrpTransferProcessor } async sendTx( - data: BlocksoftBlockchainTypes.TransferData, - privateData: BlocksoftBlockchainTypes.TransferPrivateData, - uiData: BlocksoftBlockchainTypes.TransferUiData - ): Promise { + data: AirDAOBlockchainTypes.TransferData, + privateData: AirDAOBlockchainTypes.TransferPrivateData, + uiData: AirDAOBlockchainTypes.TransferUiData + ): Promise { if (typeof privateData.privateKey === 'undefined') { throw new Error('XRP transaction required privateKey'); } @@ -240,14 +220,7 @@ export default class XrpTransferProcessor this._inited = true; } txJson = await this._provider.getPrepared(data, false); -<<<<<<< Updated upstream - } catch (e) { -======= } catch (e: any) { ->>>>>>> Stashed changes - if (e.message.indexOf('connect() timed out after') !== -1) { - throw new Error('SERVER_RESPONSE_BAD_INTERNET'); - } if (e.message.indexOf('Account not found') !== -1) { throw new Error('SERVER_RESPONSE_BAD_INTERNET'); } diff --git a/crypto/blockchains/xrp/basic/XrpDataRippleProvider.ts b/crypto/blockchains/xrp/basic/XrpDataRippleProvider.ts index ec26df0e4..09cfdbfc2 100644 --- a/crypto/blockchains/xrp/basic/XrpDataRippleProvider.ts +++ b/crypto/blockchains/xrp/basic/XrpDataRippleProvider.ts @@ -3,17 +3,17 @@ import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; import XrpTmpDS from '@crypto/blockchains/xrp/stores/XrpTmpDS'; const CACHE_VALID_TIME = 60000; -let CACHE_BLOCK_DATA = {}; +let CACHE_BLOCK_DATA: { [index: string]: { data: any; time: number } } = {}; const API_PATH = 'https://data.ripple.com/v2'; export default class XrpDataRippleProvider { - setCache(tmp) { + setCache(tmp: { [index: string]: { data: any; time: number } }) { CACHE_BLOCK_DATA = tmp; } async getBalanceBlockchain(address: string) { const link = `${API_PATH}/accounts/${address}/balances`; - let res = false; + let res: any = false; let balance = 0; try { @@ -44,10 +44,13 @@ export default class XrpDataRippleProvider { return false; } } - return { balance: balance, unconfirmed: 0, provider: 'ripple.com' }; + return { balance, unconfirmed: 0, provider: 'ripple.com' }; } - async getTransactionsBlockchain(scanData) { + async getTransactionsBlockchain(scanData: { + account: any; + additional?: any; + }) { const address = scanData.account.address.trim(); const action = 'payments'; await BlocksoftCryptoLog.log( @@ -57,7 +60,7 @@ export default class XrpDataRippleProvider { address ); const link = `${API_PATH}/accounts/${address}/payments`; - let res = false; + let res: any = false; try { res = await BlocksoftAxios.getWithoutBraking(link); } catch (e: any) { @@ -97,7 +100,7 @@ export default class XrpDataRippleProvider { return transactions; } - async _unifyTransactions(address: string, result: string) { + async _unifyTransactions(address: string, result: any, action: string) { const transactions = []; let tx; for (tx of result) { @@ -109,8 +112,9 @@ export default class XrpDataRippleProvider { return transactions; } - async _unifyPayment(address: string, transaction) { - let direction, amount; + async _unifyPayment(address: string, transaction: any) { + let direction; + let amount; if (transaction.currency === 'XRP') { if (transaction.source_currency === 'XRP') { @@ -167,7 +171,7 @@ export default class XrpDataRippleProvider { transactionStatus: transactionStatus, transactionFee: transaction.transaction_cost }; - if (typeof transaction.destination_tag !== 'undefined') { + if (typeof transaction.destination_tag !== undefined) { tx.transactionJson = { memo: transaction.destination_tag }; } return tx; @@ -179,7 +183,7 @@ export default class XrpDataRippleProvider { 'XrpScannerProcessor.DataRipple._getLedger started ' + index ); const link = `${API_PATH}/ledgers/${index}`; - let res = false; + let res: any = false; if ( typeof CACHE_BLOCK_DATA[index] === 'undefined' || (now - CACHE_BLOCK_DATA[index].time > CACHE_VALID_TIME && diff --git a/crypto/blockchains/xrp/basic/XrpDataScanProvider.ts b/crypto/blockchains/xrp/basic/XrpDataScanProvider.ts index 0e153085e..57df7e546 100644 --- a/crypto/blockchains/xrp/basic/XrpDataScanProvider.ts +++ b/crypto/blockchains/xrp/basic/XrpDataScanProvider.ts @@ -3,24 +3,29 @@ import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; import BlocksoftUtils from '@crypto/common/BlocksoftUtils'; import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; import XrpTmpDS from '@crypto/blockchains/xrp/stores/XrpTmpDS'; +import { AxiosResponse } from 'axios'; const CACHE_VALID_TIME = 60000; -let CACHE_BLOCK_DATA = {}; +let CACHE_BLOCK_DATA: { [index: string]: { data: any; time: number } } = {}; + +class UnifiedTransaction { + // TODO +} export default class XrpDataScanProvider { - setCache(tmp) { + setCache(tmp: { [key: string]: any }) { CACHE_BLOCK_DATA = tmp; } - async getBalanceBlockchain(address) { + async getBalanceBlockchain(address: string) { const link = BlocksoftExternalSettings.getStatic('XRP_SCANNER_SERVER'); - let res = false; + const res = false; let balance = 0; try { /** - curl http://s1.ripple.com:51234/ -X POST -H "Content-Type: application/json" -d '{'method":"account_info","params":[{"account":"rEAgA9B8U8RCkwn6MprHqE1ZfXoeGQxz4P","strict":true,"ledger_index":"validated","api_version':1}]}' - curl https://xrplcluster.com/ -X POST -H "Content-Type: application/json" -d '{'method":"account_info","params":[{"account":"rEAgA9B8U8RCkwn6MprHqE1ZfXoeGQxz4P","strict":true,"ledger_index":"validated","api_version':1}]}' - */ + * curl http://s1.ripple.com:51234/ -X POST -H "Content-Type: application/json" -d '{'method":"account_info","params":[{"account":"rEAgA9B8U8RCkwn6MprHqE1ZfXoeGQxz4P","strict":true,"ledger_index":"validated","api_version':1}]}' + * curl https://xrplcluster.com/ -X POST -H "Content-Type: application/json" -d '{'method":"account_info","params":[{"account":"rEAgA9B8U8RCkwn6MprHqE1ZfXoeGQxz4P","strict":true,"ledger_index":"validated","api_version':1}]}' + */ const data = { method: 'account_info', params: [ @@ -33,24 +38,29 @@ export default class XrpDataScanProvider { ] }; - res = await BlocksoftAxios.postWithoutBraking(link, data); + // @ts-ignore + // tslint:disable-next-line:no-shadowed-variable + const res: AxiosResponse = await BlocksoftAxios.postWithoutBraking( + link, + data + ); if ( res && - typeof res.data !== 'undefined' && + typeof res.data !== undefined && res.data && - typeof res.data.result !== 'undefined' && + typeof res.data.result !== undefined && res.data.result ) { if ( - typeof res.data.result.account !== 'undefined' && - typeof res.data.result.error_code !== 'undefined' && + typeof res.data.result.account !== undefined && + typeof res.data.result.error_code !== undefined && res.data.result.error_code === 19 ) { balance = 0; } else if ( - typeof res.data.result.account_data !== 'undefined' && - typeof res.data.result.account_data.Balance !== 'undefined' + typeof res.data.result.account_data !== undefined && + typeof res.data.result.account_data.Balance !== undefined ) { balance = BlocksoftUtils.toUnified( res.data.result.account_data.Balance, @@ -60,12 +70,12 @@ export default class XrpDataScanProvider { } else { return false; } - } catch (e) { + } catch (e: any) { if ( e.message.indexOf('timed out') === -1 && e.message.indexOf('account not found') === -1 ) { - if (typeof res.data !== 'undefined' && res.data) { + if (typeof res.data !== undefined && res.data) { e.message += ' in ' + JSON.stringify(res.data); } else { e.message += ' empty data'; @@ -78,11 +88,13 @@ export default class XrpDataScanProvider { return { balance, unconfirmed: 0, provider: link }; } - async getTransactionsBlockchain(scanData) { + async getTransactionsBlockchain(scanData: { + account: any; + additional?: any; + }) { const address = scanData.account.address.trim(); const link = BlocksoftExternalSettings.getStatic('XRP_SCANNER_SERVER'); let transactions = []; - let res = false; try { // https://xrpl.org/account_tx.html const data = { @@ -98,7 +110,13 @@ export default class XrpDataScanProvider { } ] }; - res = await BlocksoftAxios.postWithoutBraking(link, data); + + // @ts-ignore + // tslint:disable-next-line:no-shadowed-variable + const res: AxiosResponse = await BlocksoftAxios.postWithoutBraking( + link, + data + ); if ( res && @@ -117,7 +135,7 @@ export default class XrpDataScanProvider { } else { return false; } - } catch (e) { + } catch (e: any) { if ( e.message.indexOf('timed out') === -1 && e.message.indexOf('account not found') === -1 @@ -130,7 +148,11 @@ export default class XrpDataScanProvider { return transactions; } - async _unifyTransactions(address, result, lastBlock) { + async _unifyTransactions( + address: any, + result: any, + lastBlock: number | undefined + ) { const transactions = []; let tx; for (tx of result) { @@ -145,6 +167,7 @@ export default class XrpDataScanProvider { /** * @param {string} address * @param {Object} transaction + * @param lastBlock * @param {bool} transaction.validated * @param {string} transaction.tx.Account 'rEAgA9B8U8RCkwn6MprHqE1ZfXoeGQxz4P' * @param {string} transaction.tx.Amount '2000000' @@ -161,12 +184,31 @@ export default class XrpDataScanProvider { * @param {string} transaction.meta.TransactionResult 'tesSUCCESS' * @return {UnifiedTransaction} * @private - **/ - async _unifyPayment(address, transaction, lastBlock = 0) { + */ + async _unifyPayment( + address: any, + transaction: { + tx: { + TransactionType: string; + Account: any; + Amount: any; + ledger_index: number; + date: any; + hash: any; + Destination: any; + Fee: any; + DestinationTag: any; + }; + meta: { delivered_amount: any; TransactionResult: string }; + validated: boolean; + }, + lastBlock = 0 + ): Promise { if (transaction.tx.TransactionType !== 'Payment') { return false; } - let direction, amount; + let direction: string; + let amount: any; if (transaction.tx.Account === address) { direction = 'outcome'; } else { @@ -185,7 +227,7 @@ export default class XrpDataScanProvider { if (blockConfirmations <= 0) blockConfirmations = 0; let transactionStatus = 'new'; if ( - transaction.validated === true || + transaction.validated || transaction.meta.TransactionResult === 'tesSUCCESS' ) { if (blockConfirmations > 5) { @@ -193,15 +235,10 @@ export default class XrpDataScanProvider { } } const ledger = await this._getLedger(transaction.tx.ledger_index); - const blockTime = - ledger && typeof ledger.close_time !== 'undefined' && ledger.close_time - ? ledger.close_time - : transaction.tx.date; + const blockTime = (ledger && ledger.close_time) || transaction.tx.date; const blockHash = - ledger && typeof ledger.ledger_hash !== 'undefined' && ledger.ledger_hash - ? ledger.ledger_hash - : transaction.tx.ledger_index; - const tx = { + (ledger && ledger.ledger_hash) || transaction.tx.ledger_index; + const tx: UnifiedTransaction = { transactionHash: transaction.tx.hash, blockHash, blockNumber: transaction.tx.ledger_index, @@ -218,20 +255,19 @@ export default class XrpDataScanProvider { transactionStatus, transactionFee: BlocksoftUtils.toUnified(transaction.tx.Fee, 6) }; - // https://blockchair.com/ripple/transaction/F56C6B0CA7BB6CD9AC74843E6C7BA605C7FFBB1F409E356CA235423F30F55F51?from=trustee if (typeof transaction.tx.DestinationTag !== 'undefined') { tx.transactionJson = { memo: transaction.tx.DestinationTag }; } return tx; } - async _getLedger(index) { + async _getLedger(index: string | number): Promise { const now = new Date().getTime(); await BlocksoftCryptoLog.log( 'XrpScannerProcessor.DataScan._getLedger started ' + index ); const link = BlocksoftExternalSettings.getStatic('XRP_SCANNER_SERVER'); - let res = false; + let res: any = false; if ( typeof CACHE_BLOCK_DATA[index] === 'undefined' || (now - CACHE_BLOCK_DATA[index].time > CACHE_VALID_TIME && @@ -272,9 +308,9 @@ export default class XrpDataScanProvider { data: ledger, time: now }; + await XrpTmpDS.saveCache(CACHE_BLOCK_DATA); } - await XrpTmpDS.saveCache(CACHE_BLOCK_DATA); - } catch (e) { + } catch (e: any) { if ( e.message.indexOf('timed out') === -1 && e.message.indexOf('account not found') === -1 @@ -291,11 +327,9 @@ export default class XrpDataScanProvider { return CACHE_BLOCK_DATA[index].data; } - // 2021-Dec-03 14:41:01.00 - // const tmp = new Date(time) not working in emulator - _getDate(time) { + _getDate(time: string): number { time = time.split('.')[0]; - const months = { + const months: { [key: string]: number } = { Jan: 0, Feb: 1, Mar: 2, @@ -313,12 +347,12 @@ export default class XrpDataScanProvider { const tmp1 = tmp0[0].split('-'); const tmp2 = tmp0[1].split(':'); const tmp = new Date( - tmp1[0], + parseInt(tmp1[0], 2), months[tmp1[1]], - tmp1[2], - tmp2[0], - tmp2[1], - tmp2[2] + parseInt(tmp1[2], 2), + parseInt(tmp2[0], 2), + parseInt(tmp2[1], 2), + parseInt(tmp2[2], 2) ); return tmp.getTime(); } diff --git a/crypto/blockchains/xrp/basic/XrpTxSendProvider.ts b/crypto/blockchains/xrp/basic/XrpTxSendProvider.ts index e49d49ff6..197e7284f 100644 --- a/crypto/blockchains/xrp/basic/XrpTxSendProvider.ts +++ b/crypto/blockchains/xrp/basic/XrpTxSendProvider.ts @@ -8,22 +8,11 @@ */ import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; import BlocksoftExternalSettings from '../../../common/BlocksoftExternalSettings'; -<<<<<<< Updated upstream -import { BlocksoftBlockchainTypes } from '../../BlocksoftBlockchainTypes'; - import { XrpTxUtils } from './XrpTxUtils'; - -import MarketingEvent from '../../../../app/services/Marketing/MarketingEvent'; -import config from '../../../../app/config/config'; -======= - -import { XrpTxUtils } from './XrpTxUtils'; - -// TODO rewrite MarketingEvent file to use it here -// import MarketingEvent from '../../../../app/services/Marketing/MarketingEvent'; -import { BlocksoftBlockchainTypes } from '@lib/blockchains/BlocksoftBlockchainTypes'; +// @ts-ignore +import MarketingEvent from '../../../../app/services/Marketing/MarketingEvent'; // TODO import import config from '@constants/config'; ->>>>>>> Stashed changes +import { AirDAOBlockchainTypes } from '@crypto/blockchains/AirDAOBlockchainTypes'; const RippleAPI = require('ripple-lib').RippleAPI; @@ -39,14 +28,15 @@ export class XrpTxSendProvider { 'XrpTransferProcessor constructor' + errorCode + ': ' + errorMessage ); }); - this._api.on('connected', () => {}); - this._api.on('disconnected', () => {}); + this._api.on('connected', () => { + BlocksoftCryptoLog.log('connected'); + }); + this._api.on('disconnected', () => { + BlocksoftCryptoLog.log('disconnected'); + }); } - async getPrepared( - data: BlocksoftBlockchainTypes.TransferData, - toObject = true - ) { + async getPrepared(data: AirDAOBlockchainTypes.TransferData, toObject = true) { const payment = { source: { address: data.addressFrom, @@ -86,21 +76,17 @@ export class XrpTxSendProvider { // @ts-ignore payment.destination.tag = int; } -<<<<<<< Updated upstream - } catch (e) { -======= } catch (e: any) { ->>>>>>> Stashed changes // @ts-ignore BlocksoftCryptoLog.log( - 'XrpTransferProcessor._getPrepared memo error ' + e.message, - data + `XrpTransferProcessor._getPrepared memo error + ${e.message}, + ${data}` ); } // @ts-ignore BlocksoftCryptoLog.log( - 'XrpTransferProcessor._getPrepared payment', - payment + `XrpTransferProcessor._getPrepared payment, + ${payment}` ); const api = this._api; @@ -165,8 +151,8 @@ export class XrpTxSendProvider { } signTx( - data: BlocksoftBlockchainTypes.TransferData, - privateData: BlocksoftBlockchainTypes.TransferPrivateData, + data: AirDAOBlockchainTypes.TransferData, + privateData: AirDAOBlockchainTypes.TransferPrivateData, txJson: any ): Promise { const api = this._api; @@ -179,8 +165,8 @@ export class XrpTxSendProvider { } async sendTx( - data: BlocksoftBlockchainTypes.TransferData, - privateData: BlocksoftBlockchainTypes.TransferPrivateData, + data: AirDAOBlockchainTypes.TransferData, + privateData: AirDAOBlockchainTypes.TransferPrivateData, txJson: any ): Promise<{ resultCode: string; @@ -194,10 +180,7 @@ export class XrpTxSendProvider { let result; try { const signed = this.signTx(data, privateData, txJson); -<<<<<<< Updated upstream -======= // @ts-ignore ->>>>>>> Stashed changes BlocksoftCryptoLog.log('XrpTransferProcessor.sendTx signed', signed); result = await new Promise((resolve, reject) => { api @@ -206,12 +189,8 @@ export class XrpTxSendProvider { // https://xrpl.org/rippleapi-reference.html#submit api .submit(signed) -<<<<<<< Updated upstream - .then((result: { resultCode: ''; resultMessage: '' }) => { -======= // tslint:disable-next-line:no-shadowed-variable - .then((result: string) => { ->>>>>>> Stashed changes + .then((result: { resultCode: ''; resultMessage: '' }) => { MarketingEvent.logOnlyRealTime( 'v20_rippled_success ' + data.addressFrom + @@ -258,11 +237,7 @@ export class XrpTxSendProvider { reject(error); }); }); -<<<<<<< Updated upstream - } catch (e) { -======= } catch (e: any) { ->>>>>>> Stashed changes if (config.debug.cryptoErrors) { console.log('XrpTransferProcessor.sendTx error ', e); } diff --git a/crypto/blockchains/xrp/stores/XrpTmpDS.ts b/crypto/blockchains/xrp/stores/XrpTmpDS.ts index 25c885684..797ba8481 100644 --- a/crypto/blockchains/xrp/stores/XrpTmpDS.ts +++ b/crypto/blockchains/xrp/stores/XrpTmpDS.ts @@ -1,3 +1,6 @@ +// @ts-ignore +import Database from '@app/appstores/DataSource/Database'; // TODO import + const tableName = 'transactions_scanners_tmp'; class XrpTmpDS { @@ -45,7 +48,7 @@ class XrpTmpDS { return tmp; } - async saveCache(value) { + async saveCache(value: {}) { const tmp = Database.escapeString(JSON.stringify(value)); const now = new Date().toISOString(); if (this._isSaved) { diff --git a/crypto/blockchains/xvg/providers/XvgSendProvider.ts b/crypto/blockchains/xvg/providers/XvgSendProvider.ts index 94b6c649f..f99a65870 100644 --- a/crypto/blockchains/xvg/providers/XvgSendProvider.ts +++ b/crypto/blockchains/xvg/providers/XvgSendProvider.ts @@ -1,31 +1,17 @@ /** * @version 0.20 */ -<<<<<<< Updated upstream -import { BlocksoftBlockchainTypes } from '../../BlocksoftBlockchainTypes'; import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; import BlocksoftAxios from '../../../common/BlocksoftAxios'; import BlocksoftExternalSettings from '../../../common/BlocksoftExternalSettings'; -======= -import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; -import BlocksoftAxios from '../../../common/BlocksoftAxios'; -import BlocksoftExternalSettings from '../../../common/BlocksoftExternalSettings'; -import { BlocksoftBlockchainTypes } from '@lib/blockchains/BlocksoftBlockchainTypes'; ->>>>>>> Stashed changes +import { AirDAOBlockchainTypes } from '@crypto/blockchains/AirDAOBlockchainTypes'; export default class XvgSendProvider - implements BlocksoftBlockchainTypes.SendProvider + implements AirDAOBlockchainTypes.SendProvider { - protected _settings: BlocksoftBlockchainTypes.CurrencySettings; + protected _settings: AirDAOBlockchainTypes.CurrencySettings; -<<<<<<< Updated upstream - constructor( - settings: BlocksoftBlockchainTypes.CurrencySettings, - serverCode: string - ) { -======= - constructor(settings: BlocksoftBlockchainTypes.CurrencySettings) { ->>>>>>> Stashed changes + constructor(settings: AirDAOBlockchainTypes.CurrencySettings) { this._settings = settings; } @@ -55,11 +41,7 @@ export default class XvgSendProvider ' ok ' + hex ); -<<<<<<< Updated upstream - } catch (e) { -======= } catch (e: any) { ->>>>>>> Stashed changes BlocksoftCryptoLog.log( this._settings.currencyCode + ' XvgSendProvider.sendTx ' + @@ -94,17 +76,11 @@ export default class XvgSendProvider throw e; } } -<<<<<<< Updated upstream - if (typeof res.data.txid === 'undefined' || !res.data.txid) { - throw new Error('SERVER_RESPONSE_NOT_CONNECTED'); - } -======= // @ts-ignore if (typeof res.data.txid === 'undefined' || !res.data.txid) { throw new Error('SERVER_RESPONSE_NOT_CONNECTED'); } // @ts-ignore ->>>>>>> Stashed changes return { transactionHash: res.data.txid, transactionJson: {} }; } } diff --git a/crypto/blockchains/xvg/providers/XvgUnspentsProvider.ts b/crypto/blockchains/xvg/providers/XvgUnspentsProvider.ts index 724b91dda..2c0063177 100644 --- a/crypto/blockchains/xvg/providers/XvgUnspentsProvider.ts +++ b/crypto/blockchains/xvg/providers/XvgUnspentsProvider.ts @@ -1,68 +1,57 @@ -<<<<<<< Updated upstream /** * @version 0.20 * https://api.vergecurrency.network/node/api/XVG/mainnet/address/DL5LtSf7wztH45VuYunL8oaQHtJbKLCHyw/txs/?unspent=true */ -import { BlocksoftBlockchainTypes } from '../../BlocksoftBlockchainTypes'; import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; import BlocksoftAxios from '../../../common/BlocksoftAxios'; -======= -import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; -import BlocksoftAxios from '../../../common/BlocksoftAxios'; -import { BlocksoftBlockchainTypes } from '@lib/blockchains/BlocksoftBlockchainTypes'; ->>>>>>> Stashed changes +import { AirDAOBlockchainTypes } from '@crypto/blockchains/AirDAOBlockchainTypes'; export default class XvgUnspentsProvider - implements BlocksoftBlockchainTypes.UnspentsProvider + implements AirDAOBlockchainTypes.UnspentsProvider { _apiPath = 'https://api.vergecurrency.network/node/api/XVG/mainnet/address/'; - protected _settings: BlocksoftBlockchainTypes.CurrencySettings; + protected _settings: AirDAOBlockchainTypes.CurrencySettings; -<<<<<<< Updated upstream - constructor( - settings: BlocksoftBlockchainTypes.CurrencySettings, - serverCode: string - ) { -======= - constructor(settings: BlocksoftBlockchainTypes.CurrencySettings) { ->>>>>>> Stashed changes + constructor(settings: AirDAOBlockchainTypes.CurrencySettings) { this._settings = settings; } async getUnspents( address: string - ): Promise { -<<<<<<< Updated upstream - // @ts-ignore -======= ->>>>>>> Stashed changes + ): Promise { BlocksoftCryptoLog.log( - this._settings.currencyCode + ' XvgUnspentsProvider.getUnspents started', - address + this._settings.currencyCode + + ` XvgUnspentsProvider.getUnspents started', + ${address}` ); const link = this._apiPath + address + '/txs/?unspent=true'; const res = await BlocksoftAxios.getWithoutBraking(link); + BlocksoftCryptoLog.log( - this._settings.currencyCode + ' XvgUnspentsProvider.getUnspents link', - link + this._settings.currencyCode + + ` XvgUnspentsProvider.getUnspents link', + ${link}` ); - if (!res || typeof res.data === 'undefined') { + + if (!res || typeof res === 'boolean' || !('data' in res)) { + // Check if 'data' exists in 'res' BlocksoftCryptoLog.log( this._settings.currencyCode + - ' XvgUnspentsProvider.getUnspents nothing loaded for address ' + - address + - ' link ' + - link + `' XvgUnspentsProvider.getUnspents nothing loaded for address ' + + ${address} + + ' link ' + + ${link}` ); throw new Error('SERVER_RESPONSE_NOT_CONNECTED'); } + if (!res.data || typeof res.data[0] === 'undefined') { return []; } - const sortedUnspents = []; -<<<<<<< Updated upstream + + const sortedUnspents: AirDAOBlockchainTypes.UnspentTx[] = []; /** * https://api.vergecurrency.network/node/api/XVG/mainnet/address/DL5LtSf7wztH45VuYunL8oaQHtJbKLCHyw/txs/?unspent=true * @param {*} res.data[] @@ -80,9 +69,7 @@ export default class XvgUnspentsProvider * @param {string} res.data[].value 91523000 * @param {string} res.data[].confirmations -1 */ -======= ->>>>>>> Stashed changes - const already = {}; + const already: { [key: string]: number } = {}; let unspent; for (unspent of res.data) { if ( diff --git a/crypto/blockchains/xvg/stores/XvgTmpDS.ts b/crypto/blockchains/xvg/stores/XvgTmpDS.ts index acdf7cb3f..4894e87b0 100644 --- a/crypto/blockchains/xvg/stores/XvgTmpDS.ts +++ b/crypto/blockchains/xvg/stores/XvgTmpDS.ts @@ -1,3 +1,5 @@ +// TODO add Database +// @ts-ignore import Database from '@app/appstores/DataSource/Database'; const tableName = ' transactions_scanners_tmp'; @@ -5,7 +7,7 @@ const tableName = ' transactions_scanners_tmp'; class XvgTmpDS { _currencyCode = 'XVG'; - async getCache(address) { + async getCache(address: any) { const res = await Database.query(` SELECT tmp_key, tmp_sub_key, tmp_val FROM ${tableName} @@ -21,13 +23,14 @@ class XvgTmpDS { if (row.tmp_sub_key !== 'data') { val = JSON.parse(Database.unEscapeString(row.tmp_val)); } + // @ts-ignore tmp[row.tmp_key + '_' + row.tmp_sub_key] = val; } } return tmp; } - async saveCache(address, key, subKey, value) { + async saveCache(address: any, key: any, subKey: string, value: boolean) { const now = new Date().toISOString(); const prepared = [ { From e07bdbb9d226a12a1c27309c89a8413d2ca19609 Mon Sep 17 00:00:00 2001 From: JavidHaji-zada Date: Thu, 3 Aug 2023 13:09:15 +0400 Subject: [PATCH 026/509] converted fio --- crypto/blockchains/fio/FioAddressProcessor.js | 40 - crypto/blockchains/fio/FioAddressProcessor.ts | 40 + ...nerProcessor.js => FioScannerProcessor.ts} | 20 +- .../{FioSdkWrapper.js => FioSdkWrapper.ts} | 9 +- .../blockchains/fio/FioTransferProcessor.ts | 34 +- .../fio/{FioUtils.js => FioUtils.ts} | 27 +- package.json | 4 + .../UpdateAccountBalanceAndTransactions.js | 1133 +++++++++++------ yarn.lock | 147 ++- 9 files changed, 962 insertions(+), 492 deletions(-) delete mode 100644 crypto/blockchains/fio/FioAddressProcessor.js create mode 100644 crypto/blockchains/fio/FioAddressProcessor.ts rename crypto/blockchains/fio/{FioScannerProcessor.js => FioScannerProcessor.ts} (90%) rename crypto/blockchains/fio/{FioSdkWrapper.js => FioSdkWrapper.ts} (92%) rename crypto/blockchains/fio/{FioUtils.js => FioUtils.ts} (95%) diff --git a/crypto/blockchains/fio/FioAddressProcessor.js b/crypto/blockchains/fio/FioAddressProcessor.js deleted file mode 100644 index 14e8d038a..000000000 --- a/crypto/blockchains/fio/FioAddressProcessor.js +++ /dev/null @@ -1,40 +0,0 @@ -/** - * @version 0.11 - * https://developers.fioprotocol.io/fio-protocol/accounts - * - * - * let mnemonic = '' - * let results = await BlocksoftKeys.discoverAddresses({ mnemonic, fullTree: false, fromIndex: 0, toIndex: 1, currencyCode: ['XMR'] }) - * console.log('r', results['XMR'][0]) - */ - -import { Ecc } from '@fioprotocol/fiojs' -import { FIOSDK } from '@fioprotocol/fiosdk' - -export default class FioAddressProcessor { - - _root = false - - async setBasicRoot(root) { - this._root = root - } - - /** - * @param {string|Buffer} privateKey - * @param data - * @param {*} data.publicKey - * @param {*} data.walletHash - * @param {*} data.derivationPath - * @param {*} data.derivationIndex - * @param {*} data.derivationType - * @returns {Promise<{privateKey: string, address: string, addedData: *}>} - */ - async getAddress(privateKey, data = {}) { - const pvt = await Ecc.PrivateKey(privateKey) - return { - address: FIOSDK.derivedPublicKey(privateKey).publicKey, - privateKey: pvt.toWif(), - addedData: false - } - } -} diff --git a/crypto/blockchains/fio/FioAddressProcessor.ts b/crypto/blockchains/fio/FioAddressProcessor.ts new file mode 100644 index 000000000..672614168 --- /dev/null +++ b/crypto/blockchains/fio/FioAddressProcessor.ts @@ -0,0 +1,40 @@ +/** + * @version 0.11 + * https://developers.fioprotocol.io/fio-protocol/accounts + * + * + * let mnemonic = '' + * let results = await BlocksoftKeys.discoverAddresses({ mnemonic, fullTree: false, fromIndex: 0, toIndex: 1, currencyCode: ['XMR'] }) + * console.log('r', results['XMR'][0]) + */ + +import { Ecc } from '@fioprotocol/fiojs'; +// @ts-ignore +import { FIOSDK } from '@fioprotocol/fiosdk'; + +export default class FioAddressProcessor { + _root = false; + + async setBasicRoot(root) { + this._root = root; + } + + /** + * @param {string|Buffer} privateKey + * @param data + * @param {*} data.publicKey + * @param {*} data.walletHash + * @param {*} data.derivationPath + * @param {*} data.derivationIndex + * @param {*} data.derivationType + * @returns {Promise<{privateKey: string, address: string, addedData: *}>} + */ + async getAddress(privateKey: string, data = {}) { + const pvt = await Ecc.PrivateKey(privateKey); + return { + address: FIOSDK.derivedPublicKey(privateKey).publicKey, + privateKey: pvt.toWif(), + addedData: false + }; + } +} diff --git a/crypto/blockchains/fio/FioScannerProcessor.js b/crypto/blockchains/fio/FioScannerProcessor.ts similarity index 90% rename from crypto/blockchains/fio/FioScannerProcessor.js rename to crypto/blockchains/fio/FioScannerProcessor.ts index 777823d84..110905cf5 100644 --- a/crypto/blockchains/fio/FioScannerProcessor.js +++ b/crypto/blockchains/fio/FioScannerProcessor.ts @@ -1,5 +1,5 @@ import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog'; -import { getFioBalance, getTransactions } from './FioUtils'; +import { getFioBalance, getTransactions } from './FioUtils.jts'; export default class FioScannerProcessor { /** @@ -36,7 +36,11 @@ export default class FioScannerProcessor { * @param {string} walletHash * @return {Promise<{balance:*, unconfirmed:*, provider:string}>} */ - async getBalanceBlockchainCache(address, additionalData, walletHash) { + async getBalanceBlockchainCache( + address: string, + additionalData, + walletHash: string + ) { BlocksoftCryptoLog.log( this._settings.currencyCode + ' FioScannerProcessor.getBalance (cache) started ' + @@ -53,7 +57,11 @@ export default class FioScannerProcessor { * @param {string} walletHash * @return {Promise<{balance:*, unconfirmed:*, provider:string}>} */ - async getBalanceBlockchain(address, additionalData, walletHash) { + async getBalanceBlockchain( + address: string, + additionalData, + walletHash: string + ) { BlocksoftCryptoLog.log( this._settings.currencyCode + ' FioScannerProcessor.getBalance started ' + @@ -73,7 +81,9 @@ export default class FioScannerProcessor { * @param {string} scanData.account.walletHash * @return {Promise<[UnifiedTransaction]>} */ - async getTransactionsBlockchain(scanData) { + async getTransactionsBlockchain(scanData: { + account: { address: string; walletHash: string }; + }) { const address = scanData.account.address.trim(); const walletHash = scanData.account.walletHash; BlocksoftCryptoLog.log( @@ -121,7 +131,7 @@ export default class FioScannerProcessor { * @return {Promise} * @private */ - async _unifyTransaction(address, lastBlock, transaction) { + async _unifyTransaction(address: string, lastBlock: string, transaction) { const txData = transaction.action_trace?.act?.data; if ( !txData?.payee_public_key || diff --git a/crypto/blockchains/fio/FioSdkWrapper.js b/crypto/blockchains/fio/FioSdkWrapper.ts similarity index 92% rename from crypto/blockchains/fio/FioSdkWrapper.js rename to crypto/blockchains/fio/FioSdkWrapper.ts index e49d78d80..20adfb82c 100644 --- a/crypto/blockchains/fio/FioSdkWrapper.js +++ b/crypto/blockchains/fio/FioSdkWrapper.ts @@ -1,14 +1,13 @@ /** * @version 0.77 */ +// @ts-ignore import { FIOSDK } from '@fioprotocol/fiosdk'; import BlocksoftKeysStorage from '@crypto/actions/BlocksoftKeysStorage/BlocksoftKeysStorage'; import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings'; import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; -import config from '@app/config/config'; - const fetchJson = async (uri, opts = {}) => { // eslint-disable-next-line no-undef return fetch(uri, opts); @@ -45,11 +44,7 @@ export class FioSdkWrapper { this.sdk = new FIOSDK(fioKey, publicKey, link, fetchJson); this.walletHash = walletHash; BlocksoftCryptoLog.log(`FioSdkWrapper.inited for ${walletHash}`); - } catch (e) { - if (config.debug.fioErrors) { - console.log('FioSdkWrapper.init error'); - } - } + } catch (e) {} return true; } } diff --git a/crypto/blockchains/fio/FioTransferProcessor.ts b/crypto/blockchains/fio/FioTransferProcessor.ts index 1d7c0099a..efb7d26b1 100644 --- a/crypto/blockchains/fio/FioTransferProcessor.ts +++ b/crypto/blockchains/fio/FioTransferProcessor.ts @@ -2,12 +2,12 @@ * @version 0.20 */ import { getFioSdk } from './FioSdkWrapper'; -import { getFioBalance, transferTokens } from './FioUtils'; -import { BlocksoftBlockchainTypes } from '../BlocksoftBlockchainTypes'; +import { getFioBalance, transferTokens } from './FioUtils.jts'; +import { AirDAOBlockchainTypes } from '../AirDAOBlockchainTypes'; import BlocksoftUtils from '../../common/BlocksoftUtils'; export default class FioTransferProcessor - implements BlocksoftBlockchainTypes.TransferProcessor + implements AirDAOBlockchainTypes.TransferProcessor { private _settings: any; @@ -24,12 +24,12 @@ export default class FioTransferProcessor } async getFeeRate( - data: BlocksoftBlockchainTypes.TransferData, - privateData: BlocksoftBlockchainTypes.TransferPrivateData, + data: AirDAOBlockchainTypes.TransferData, + privateData: AirDAOBlockchainTypes.TransferPrivateData, additionalData: {} = {} - ): Promise { + ): Promise { const { fee = 0 } = await getFioSdk().getFee('transfer_tokens_pub_key'); - const result: BlocksoftBlockchainTypes.FeeRateResult = { + const result: AirDAOBlockchainTypes.FeeRateResult = { selectedFeeIndex: 0, shouldShowFees: false, fees: [ @@ -39,15 +39,15 @@ export default class FioTransferProcessor amountForTx: data.amount } ] - } as BlocksoftBlockchainTypes.FeeRateResult; + } as AirDAOBlockchainTypes.FeeRateResult; return result; } async getTransferAllBalance( - data: BlocksoftBlockchainTypes.TransferData, - privateData: BlocksoftBlockchainTypes.TransferPrivateData, + data: AirDAOBlockchainTypes.TransferData, + privateData: AirDAOBlockchainTypes.TransferPrivateData, additionalData: any = {} - ): Promise { + ): Promise { const { fee = 0 } = await getFioSdk().getFee('transfer_tokens_pub_key'); const balance = await getFioBalance(data.addressFrom); if (balance === 0) { @@ -69,7 +69,7 @@ export default class FioTransferProcessor }; } - const result: BlocksoftBlockchainTypes.TransferAllBalanceResult = { + const result: AirDAOBlockchainTypes.TransferAllBalanceResult = { selectedFeeIndex: 0, fees: [ { @@ -79,15 +79,15 @@ export default class FioTransferProcessor } ], selectedTransferAllBalance: diff - } as BlocksoftBlockchainTypes.TransferAllBalanceResult; + } as AirDAOBlockchainTypes.TransferAllBalanceResult; return result; } async sendTx( - data: BlocksoftBlockchainTypes.TransferData, - privateData: BlocksoftBlockchainTypes.TransferPrivateData, - uiData: BlocksoftBlockchainTypes.TransferUiData - ): Promise { + data: AirDAOBlockchainTypes.TransferData, + privateData: AirDAOBlockchainTypes.TransferPrivateData, + uiData: AirDAOBlockchainTypes.TransferUiData + ): Promise { if ( typeof uiData !== 'undefined' && typeof uiData.selectedFee !== 'undefined' && diff --git a/crypto/blockchains/fio/FioUtils.js b/crypto/blockchains/fio/FioUtils.ts similarity index 95% rename from crypto/blockchains/fio/FioUtils.js rename to crypto/blockchains/fio/FioUtils.ts index 6fd53f912..cecc5267a 100644 --- a/crypto/blockchains/fio/FioUtils.js +++ b/crypto/blockchains/fio/FioUtils.ts @@ -1,13 +1,17 @@ import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog'; import { getFioSdk } from './FioSdkWrapper'; -import config from '../../../app/config/config'; import BlocksoftAxios from '../../common/BlocksoftAxios'; import { Fio } from '@fioprotocol/fiojs'; import { FIOSDK } from '@fioprotocol/fiosdk/src/FIOSDK'; -import chunk from 'lodash/chunk'; +import _ from 'lodash'; import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings'; -export const resolveChainCode = (currencyCode, currencySymbol) => { +const chunk = _.chunk; + +export const resolveChainCode = ( + currencyCode: string, + currencySymbol: string +) => { let chainCode = currencyCode; if (typeof currencyCode !== 'undefined' && currencyCode !== currencySymbol) { const tmp = currencyCode.split('_'); @@ -18,7 +22,7 @@ export const resolveChainCode = (currencyCode, currencySymbol) => { return chainCode; }; -export const resolveChainToken = (currencyCode, extend) => { +export const resolveChainToken = (currencyCode: string, extend) => { if (extend.currencyCode === 'BNB_SMART') { return 'SMART'; } @@ -30,7 +34,7 @@ export const resolveChainToken = (currencyCode, extend) => { return extend.currencySymbol; }; -export const resolveCryptoCodes = (currencyCode) => { +export const resolveCryptoCodes = (currencyCode: string) => { let chainCode = currencyCode; let currencySymbol = currencyCode; const tmp = currencyCode.split('_'); @@ -44,7 +48,7 @@ export const resolveCryptoCodes = (currencyCode) => { }; }; -export const isFioAddressValid = (address) => { +export const isFioAddressValid = (address: string) => { if (address) { try { FIOSDK.isFioAddressValid(address); @@ -54,7 +58,7 @@ export const isFioAddressValid = (address) => { return false; }; -export const isFioAddressRegistered = async (address) => { +export const isFioAddressRegistered = async (address: string) => { if (!isFioAddressValid(address)) { return false; } @@ -68,7 +72,11 @@ export const isFioAddressRegistered = async (address) => { } }; -export const getPubAddress = async (fioAddress, chainCode, tokenCode) => { +export const getPubAddress = async ( + fioAddress: string, + chainCode: string, + tokenCode: string +) => { try { const response = await getFioSdk().getPublicAddress( fioAddress, @@ -424,9 +432,6 @@ export const getFioObtData = async (tokenCode, offset = 0, limit = 100) => { }; const formatError = (title, e) => { - if (config.debug.fioErrors) { - console.log(title + ' error', e.json, e); - } if ( e.message.indexOf('Error 404') === -1 && e.message.indexOf('Network request failed') === -1 diff --git a/package.json b/package.json index 8b7c52cbb..c08eaee7f 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,8 @@ }, "dependencies": { "@babel/preset-env": "^7.21.5", + "@fioprotocol/fiojs": "^1.0.1", + "@fioprotocol/fiosdk": "^1.8.0", "@metamask/safe-event-emitter": "^3.0.0", "@morrowdigital/watermelondb-expo-plugin": "^2.1.2", "@neverdull-agency/expo-unlimited-secure-store": "^1.0.10", @@ -39,6 +41,7 @@ "@testing-library/jest-dom": "^5.16.5", "@testing-library/react-native": "^12.1.1", "@types/jest": "^29.5.1", + "@types/lodash": "^4.14.196", "@waves/ts-lib-crypto": "^1.4.4-beta.1", "axios": "^1.3.4", "bech32": "^2.0.0", @@ -74,6 +77,7 @@ "expo-updates": "~0.16.4", "jest": "^29.2.1", "jest-expo": "^48.0.2", + "lodash": "^4.17.21", "moment": "^2.29.4", "patch-package": "^7.0.0", "postinstall-postinstall": "^2.1.0", diff --git a/src/daemons/back/UpdateAccountBalanceAndTransactions.js b/src/daemons/back/UpdateAccountBalanceAndTransactions.js index 2e764db96..d580b9c95 100644 --- a/src/daemons/back/UpdateAccountBalanceAndTransactions.js +++ b/src/daemons/back/UpdateAccountBalanceAndTransactions.js @@ -1,444 +1,769 @@ /** * @version 0.11 */ -import BlocksoftKeysStorage from '../../../crypto/actions/BlocksoftKeysStorage/BlocksoftKeysStorage' +import BlocksoftKeysStorage from '../../../crypto/actions/BlocksoftKeysStorage/BlocksoftKeysStorage'; -import BlocksoftBalances from '../../../crypto/actions/BlocksoftBalances/BlocksoftBalances' -import BlocksoftTransactions from '../../../crypto/actions/BlocksoftTransactions/BlocksoftTransactions' +import BlocksoftBalances from '../../../crypto/actions/BlocksoftBalances/BlocksoftBalances'; +import BlocksoftTransactions from '../../../crypto/actions/BlocksoftTransactions/BlocksoftTransactions'; -import Log from '../../services/Log/Log' -import MarketingEvent from '../../services/Marketing/MarketingEvent' +import Log from '../../services/Log/Log'; +import MarketingEvent from '../../services/Marketing/MarketingEvent'; -import accountScanningDS from '../../appstores/DataSource/Account/AccountScanning' -import accountBalanceDS from '../../appstores/DataSource/AccountBalance/AccountBalance' -import accountDS from '../../appstores/DataSource/Account/Account' +import accountScanningDS from '../../appstores/DataSource/Account/AccountScanning'; +import accountBalanceDS from '../../appstores/DataSource/AccountBalance/AccountBalance'; +import accountDS from '../../appstores/DataSource/Account/Account'; -import AccountTransactionsRecheck from './apputils/AccountTransactionsRecheck' -import settingsActions from '../../appstores/Stores/Settings/SettingsActions' +import AccountTransactionsRecheck from './apputils/AccountTransactionsRecheck'; +import settingsActions from '../../appstores/Stores/Settings/SettingsActions'; -import config from '../../config/config' -import { getFioObtData, resolveCryptoCodes } from '../../../crypto/blockchains/fio/FioUtils' -import DaemonCache from '../DaemonCache' -import store from '@app/store' -import UpdateAccountListDaemon from '@app/daemons/view/UpdateAccountListDaemon' +import config from '../../config/config'; +import { + getFioObtData, + resolveCryptoCodes +} from '../../../crypto/blockchains/fio/FioUtils.jts'; +import DaemonCache from '../DaemonCache'; +import store from '@app/store'; +import UpdateAccountListDaemon from '@app/daemons/view/UpdateAccountListDaemon'; -const CACHE_SCANNING = {} -const CACHE_VALID_TIME = 60000 // 1 minute -const CACHE_VALID_10MIN_TIME = 600000 // 10 minutes +const CACHE_SCANNING = {}; +const CACHE_VALID_TIME = 60000; // 1 minute +const CACHE_VALID_10MIN_TIME = 600000; // 10 minutes const CACHE_CUSTOM_TIME = { - 'BCH': 60000, // 10 minutes - 'BSV': 60000 // 10 minutes -} -let CACHE_LAST_TIME = false -let CACHE_ONE_ACCOUNTS = {} + BCH: 60000, // 10 minutes + BSV: 60000 // 10 minutes +}; +let CACHE_LAST_TIME = false; +let CACHE_ONE_ACCOUNTS = {}; class UpdateAccountBalanceAndTransactions { - - getTime() { - if (CACHE_LAST_TIME > 0) { - return new Date(CACHE_LAST_TIME).toLocaleTimeString() - } - return '-' + getTime() { + if (CACHE_LAST_TIME > 0) { + return new Date(CACHE_LAST_TIME).toLocaleTimeString(); } - - /** - * @param {string} callParams.source - * @param {boolean} callParams.force - * @param {boolean} callParams.allWallets - * @param {boolean} callParams.onlyBalances - * @param {string} callParams.currencyCode - * @returns {Promise} - */ - updateAccountBalanceAndTransactions = async (callParams) => { - const source = callParams.source || 'FRONT' - const force = callParams.force || false - const allWallets = callParams.allWallets || false - const onlyBalances = callParams.onlyBalances || false - if (!force || source === 'BACK') { - const setting = await settingsActions.getSetting('scannerCode') - if (!setting) { - await settingsActions.setSettings('scannerCode', '1min') - } - if (setting === 'none') { - return false - } else if (CACHE_LAST_TIME && setting === '10min') { - const now = new Date().getTime() - const diff = now - CACHE_LAST_TIME - if (diff < CACHE_VALID_10MIN_TIME) { - Log.daemon('UpdateAccountBalanceAndTransactions skipped by diff ' + diff) - return false - } - } + return '-'; + } + + /** + * @param {string} callParams.source + * @param {boolean} callParams.force + * @param {boolean} callParams.allWallets + * @param {boolean} callParams.onlyBalances + * @param {string} callParams.currencyCode + * @returns {Promise} + */ + updateAccountBalanceAndTransactions = async (callParams) => { + const source = callParams.source || 'FRONT'; + const force = callParams.force || false; + const allWallets = callParams.allWallets || false; + const onlyBalances = callParams.onlyBalances || false; + if (!force || source === 'BACK') { + const setting = await settingsActions.getSetting('scannerCode'); + if (!setting) { + await settingsActions.setSettings('scannerCode', '1min'); + } + if (setting === 'none') { + return false; + } else if (CACHE_LAST_TIME && setting === '10min') { + const now = new Date().getTime(); + const diff = now - CACHE_LAST_TIME; + if (diff < CACHE_VALID_10MIN_TIME) { + Log.daemon( + 'UpdateAccountBalanceAndTransactions skipped by diff ' + diff + ); + return false; } - - let tmpAction = '' - try { - const params = { - force - } - - if (!allWallets && source !== 'BACK') { - params.walletHash = await settingsActions.getSelectedWallet('UpdateAccountBalanceAndTransactions') - } - - tmpAction = 'params init' - if (typeof callParams !== 'undefined' && callParams && typeof callParams.currencyCode !== 'undefined') { - - if (force) { - if (callParams.currencyCode.indexOf('TRX') === 0) { - params.currencyFamily = 'TRX' - } else if (callParams.currencyCode.indexOf('ETH') === 0) { - params.currencyFamily = 'ETH' - } else { - params.currencyCode = callParams.currencyCode - } - } else { - params.currencyCode = callParams.currencyCode - } - } - - - Log.daemon('UpdateAccountBalanceAndTransactions called ' + source) - - tmpAction = 'accounts init' - - let accounts = await accountScanningDS.getAccountsForScan({ ...params, force: false }) - - if (force) { - if (!accounts || accounts.length === 0) { - accounts = await accountScanningDS.getAccountsForScan(params) - } - } - - if (!accounts || accounts.length === 0) { - Log.daemon('UpdateAccountBalanceAndTransactions called - no account') - return false - } - - tmpAction = 'accounts log' - let account - for (account of accounts) { - Log.daemon('UpdateAccountBalanceAndTransactions called - todo account ' + account.id + ' ' + account.currencyCode + ' ' + account.address) - } - - tmpAction = 'accounts run main' - let running = 0 - CACHE_ONE_ACCOUNTS = {} - let shouldUpdateBalance = false - for (account of accounts) { - if (typeof CACHE_CUSTOM_TIME[account.currencyCode] !== 'undefined') { - continue - } - if (typeof CACHE_ONE_ACCOUNTS[account.currencyCode + '_' + account.address] !== 'undefined') { - continue - } - tmpAction = 'account run ' + JSON.stringify(account) - if (await this._accountRun(account, accounts, source, CACHE_VALID_TIME, force, onlyBalances)) { - shouldUpdateBalance = true - } - running++ - } - - tmpAction = 'accounts run custom' - - for (account of accounts) { - if (typeof CACHE_ONE_ACCOUNTS[account.currencyCode + '_' + account.address] !== 'undefined') { - continue - } - if (typeof CACHE_CUSTOM_TIME[account.currencyCode] !== 'undefined') { - // if its the only ones not updated - lets do them faster - if (await this._accountRun(account, accounts, source, running > 0 ? CACHE_CUSTOM_TIME[account.currencyCode] : CACHE_VALID_TIME, force, onlyBalances)) { - shouldUpdateBalance = true - } - } - } - - CACHE_LAST_TIME = new Date().getTime() - if (shouldUpdateBalance) { - await UpdateAccountListDaemon.updateAccountListDaemon({ force: true, source: 'SHOULD_UPDATE_BALANCE' }) - } - } catch (e) { - if (config.debug.appErrors) { - console.log('UpdateAccountBalanceAndTransactions balance error ' + source + ' ' + e.message, e) - } - Log.errDaemon('UpdateAccountBalanceAndTransactions balance error ' + source + ' ' + e.message + ' ' + tmpAction) - return false - } - return true + } } - loadFioData = async (currencyCode) => { - const currencies = store.getState().currencyStore.cryptoCurrencies - let foundFio = false - for (const tmp of currencies) { - if (tmp.currencyCode === 'FIO' && !tmp.maskedHidden) { - foundFio = true - break - } - } - if (!foundFio) return false - Log.daemon('UpdateAccountBalanceAndTransactions loadFioData ' + currencyCode) - try { - // eslint-disable-next-line camelcase - const { token_code } = resolveCryptoCodes(currencyCode) - const result = await getFioObtData(token_code) - if (result && result['obt_data_records']) { - const fioData = result['obt_data_records'].reduce((res, item) => { - if (!item.content?.memo) { - return res - } - - return !item.content?.obt_id ? res : { - ...res, - [item.content?.obt_id]: item.content?.memo - } - }, {}) - - DaemonCache.CACHE_FIO_MEMOS[currencyCode] = { - ...DaemonCache.getFioMemo(currencyCode), - ...fioData - } - } - } catch (e) { - if (config.debug.cryptoErrors) { - console.log('UpdateAccountBalanceAndTransactions error on loadFioData ' + e.message, e) - } - Log.errDaemon('UpdateAccountBalanceAndTransactions error on loadFioData ' + e.message) + let tmpAction = ''; + try { + const params = { + force + }; + + if (!allWallets && source !== 'BACK') { + params.walletHash = await settingsActions.getSelectedWallet( + 'UpdateAccountBalanceAndTransactions' + ); + } + + tmpAction = 'params init'; + if ( + typeof callParams !== 'undefined' && + callParams && + typeof callParams.currencyCode !== 'undefined' + ) { + if (force) { + if (callParams.currencyCode.indexOf('TRX') === 0) { + params.currencyFamily = 'TRX'; + } else if (callParams.currencyCode.indexOf('ETH') === 0) { + params.currencyFamily = 'ETH'; + } else { + params.currencyCode = callParams.currencyCode; + } + } else { + params.currencyCode = callParams.currencyCode; } - } + } - async _accountRun(account, accounts, source, time, force, onlyBalances = false) { + Log.daemon('UpdateAccountBalanceAndTransactions called ' + source); - let newBalance = false - let addressToScan = account.address + tmpAction = 'accounts init'; - Log.daemon('UpdateAccountBalanceAndTransactions _accountRun init ' + account.id + ' ' + account.currencyCode + ' ' + account.address) + let accounts = await accountScanningDS.getAccountsForScan({ + ...params, + force: false + }); - if (account.accountJson && typeof account.accountJson.addressHex !== 'undefined') { - addressToScan = account.accountJson.addressHex - Log.daemon('UpdateAccountBalanceAndTransactions changing address ' + account.currencyCode + ' ' + account.address + ' => ' + addressToScan) + if (force) { + if (!accounts || accounts.length === 0) { + accounts = await accountScanningDS.getAccountsForScan(params); } - const now = new Date().getTime() - if (!force && typeof CACHE_SCANNING[account.currencyCode + ' ' + addressToScan] !== 'undefined') { - const diff = now - CACHE_SCANNING[account.currencyCode + ' ' + addressToScan] - if (diff < time) { - Log.daemon('UpdateAccountBalanceAndTransactions skipped as running ' + account.currencyCode + ' ' + account.address + ' => diff:' + diff + ' time: ' + time) - return false - } + } + + if (!accounts || accounts.length === 0) { + Log.daemon('UpdateAccountBalanceAndTransactions called - no account'); + return false; + } + + tmpAction = 'accounts log'; + let account; + for (account of accounts) { + Log.daemon( + 'UpdateAccountBalanceAndTransactions called - todo account ' + + account.id + + ' ' + + account.currencyCode + + ' ' + + account.address + ); + } + + tmpAction = 'accounts run main'; + let running = 0; + CACHE_ONE_ACCOUNTS = {}; + let shouldUpdateBalance = false; + for (account of accounts) { + if (typeof CACHE_CUSTOM_TIME[account.currencyCode] !== 'undefined') { + continue; } - CACHE_SCANNING[account.currencyCode + ' ' + addressToScan] = now - - const updateObj = { - balanceScanTime: Math.round(new Date().getTime() / 1000) + if ( + typeof CACHE_ONE_ACCOUNTS[ + account.currencyCode + '_' + account.address + ] !== 'undefined' + ) { + continue; } - - let balanceError - try { - Log.daemon('UpdateAccountBalanceAndTransactions newBalance ' + account.currencyCode + ' ' + addressToScan) - - if (account.currencyCode === 'BTC') { - const additional = {} - if (account.walletIsHd && account.derivationPath !== 'm/49quote/0quote/0/1/0') { - updateObj.balanceScanLog = account.address + ' should be HD ' - updateObj.balanceScanError = '' - await accountBalanceDS.updateAccountBalance({ updateObj }, account) - return false - } else { - newBalance = await (BlocksoftBalances.setCurrencyCode(account.currencyCode).setAddress(addressToScan).setAdditional(additional).setWalletHash(account.walletHash)).getBalance('AccountRunBalancesBtc') - } - } else { - newBalance = await (BlocksoftBalances.setCurrencyCode(account.currencyCode).setAddress(addressToScan).setAdditional(account.accountJson).setWalletHash(account.walletHash)).getBalance('AccountRunBalances') - } - if (!newBalance || typeof newBalance.balance === 'undefined') { - if (account.balanceScanBlock === 0 && account.balanceScanTime === 0) { - updateObj.balanceScanLog = account.address + ' empty response, old balance ' + account.balance + ', ' + JSON.stringify(newBalance) - updateObj.balanceScanError = 'account.balanceBadNetwork' - await accountBalanceDS.updateAccountBalance({ updateObj }, account) - return false - } - balanceError = ' something wrong with balance ' + account.currencyCode + ' ' + addressToScan + ' => ' + JSON.stringify(newBalance) - Log.daemon('UpdateAccountBalanceAndTransactions newBalance something wrong ' + account.currencyCode + ' ' + addressToScan + ' => ' + JSON.stringify(newBalance)) - } else { - balanceError = ' found in one ' + JSON.stringify(newBalance) - } - } catch (e) { - if (config.debug.appErrors) { - console.log('UpdateAccountBalanceAndTransactions newBalance from ' + source + ' loaded ' + account.currencyCode + ' ' + addressToScan + ' error ' + e.message) - } - balanceError = ' found balanceError ' + e.message + tmpAction = 'account run ' + JSON.stringify(account); + if ( + await this._accountRun( + account, + accounts, + source, + CACHE_VALID_TIME, + force, + onlyBalances + ) + ) { + shouldUpdateBalance = true; } - - Log.daemon('UpdateAccountBalanceAndTransactions newBalance from ' + source + ' loaded ' + account.currencyCode + ' ' + addressToScan, JSON.stringify(newBalance)) - let continueWithTx = true - let shouldUpdateBalance = false - try { - - if (newBalance && typeof newBalance.balance !== 'undefined') { - - if (typeof account.balance === 'undefined') { - shouldUpdateBalance = true - } else if (newBalance.balance.toString() !== account.balance.toString() || newBalance.unconfirmed.toString() !== account.unconfirmed.toString() ) { - shouldUpdateBalance = true - } else if (typeof newBalance.balanceStaked !== 'undefined') { - if (typeof account.balanceStaked === 'undefined') { - shouldUpdateBalance = true - } else if (newBalance.balanceStaked * 1 !== account.balanceStaked * 1) { // toString here somehow do undefined sometimes - shouldUpdateBalance = true - } - } - - if (typeof newBalance.balanceScanBlock !== 'undefined' && (typeof account.balanceScanBlock === 'undefined' || newBalance.balanceScanBlock * 1 < account.balanceScanBlock * 1)) { - continueWithTx = false - updateObj.balanceProvider = newBalance.provider - updateObj.balanceScanLog = account.address + ' block error, ignored new ' + newBalance.balance + ' block ' + newBalance.balanceScanBlock + ', old balance ' + account.balance + ' block ' + account.balanceScanBlock - updateObj.balanceScanError = 'account.balanceBadBlock' - } else if (shouldUpdateBalance) { - updateObj.balanceFix = newBalance.balance // lets send to db totally not changed big number string - updateObj.balanceTxt = newBalance.balance.toString() // and string for any case - updateObj.unconfirmedFix = newBalance.unconfirmed || 0 // lets send to db totally not changed big number string - updateObj.unconfirmedTxt = newBalance.unconfirmed || '' // and string for any case - updateObj.balanceStakedTxt = newBalance.balanceStaked || '0' - updateObj.balanceProvider = newBalance.provider - if (typeof newBalance.balanceScanBlock !== 'undefined') { - updateObj.balanceScanBlock = newBalance.balanceScanBlock - } - updateObj.balanceScanLog = account.address + ' all ok, new balance ' + newBalance.balance + ', old balance ' + account.balance + ', ' + balanceError - updateObj.balanceScanError = '' - const logData = {} - logData.walletHash = account.walletHash - logData.currencyCode = account.currencyCode - logData.address = account.address - logData.addressShort = account.address ? account.address.slice(0, 10) : 'none' - logData.balanceScanTime = account.balanceScanTime + '' - logData.balanceProvider = account.balanceProvider + '' - logData.balance = account.balance + '' - logData.newBalanceProvider = account.newBalanceProvider + '' - logData.newBalance = (newBalance.balance * 1) + '' - MarketingEvent.setBalance(logData.walletHash, logData.currencyCode, logData.newBalance, logData) - } else { - updateObj.balanceScanLog = account.address + ' not changed, old balance ' + account.balance + ', ' + balanceError - updateObj.balanceScanError = '' - if (typeof newBalance.provider !== 'undefined') { - updateObj.balanceProvider = newBalance.provider - } - } - Log.daemon('UpdateAccountBalanceAndTransactions newBalance ok Prepared ' + account.currencyCode + ' ' + account.address + ' new balance ' + newBalance.balance + ' provider ' + newBalance.provider + ' old balance ' + account.balance, JSON.stringify(updateObj)) - } else { - updateObj.balanceScanLog = account.address + ' no balance, old balance ' + account.balance + ', ' + balanceError - updateObj.balanceScanError = 'account.balanceBadNetwork' - Log.daemon('UpdateAccountBalanceAndTransactions newBalance not Prepared ' + account.currencyCode + ' ' + account.address + ' old balance ' + account.balance, JSON.stringify(updateObj)) - } - } catch (e) { - if (config.debug.appErrors) { - console.log('UpdateAccountBalanceAndTransactions newBalance from ' + source + ' loaded ' + account.currencyCode + ' ' + addressToScan + ' format error ' + e.message) - } - e.message += ' while accountBalanceDS.updateAccountBalance formatting' - throw e + running++; + } + + tmpAction = 'accounts run custom'; + + for (account of accounts) { + if ( + typeof CACHE_ONE_ACCOUNTS[ + account.currencyCode + '_' + account.address + ] !== 'undefined' + ) { + continue; + } + if (typeof CACHE_CUSTOM_TIME[account.currencyCode] !== 'undefined') { + // if its the only ones not updated - lets do them faster + if ( + await this._accountRun( + account, + accounts, + source, + running > 0 + ? CACHE_CUSTOM_TIME[account.currencyCode] + : CACHE_VALID_TIME, + force, + onlyBalances + ) + ) { + shouldUpdateBalance = true; + } + } + } + + CACHE_LAST_TIME = new Date().getTime(); + if (shouldUpdateBalance) { + await UpdateAccountListDaemon.updateAccountListDaemon({ + force: true, + source: 'SHOULD_UPDATE_BALANCE' + }); + } + } catch (e) { + if (config.debug.appErrors) { + console.log( + 'UpdateAccountBalanceAndTransactions balance error ' + + source + + ' ' + + e.message, + e + ); + } + Log.errDaemon( + 'UpdateAccountBalanceAndTransactions balance error ' + + source + + ' ' + + e.message + + ' ' + + tmpAction + ); + return false; + } + return true; + }; + + loadFioData = async (currencyCode) => { + const currencies = store.getState().currencyStore.cryptoCurrencies; + let foundFio = false; + for (const tmp of currencies) { + if (tmp.currencyCode === 'FIO' && !tmp.maskedHidden) { + foundFio = true; + break; + } + } + if (!foundFio) return false; + Log.daemon( + 'UpdateAccountBalanceAndTransactions loadFioData ' + currencyCode + ); + try { + // eslint-disable-next-line camelcase + const { token_code } = resolveCryptoCodes(currencyCode); + const result = await getFioObtData(token_code); + if (result && result['obt_data_records']) { + const fioData = result['obt_data_records'].reduce((res, item) => { + if (!item.content?.memo) { + return res; + } + + return !item.content?.obt_id + ? res + : { + ...res, + [item.content?.obt_id]: item.content?.memo + }; + }, {}); + + DaemonCache.CACHE_FIO_MEMOS[currencyCode] = { + ...DaemonCache.getFioMemo(currencyCode), + ...fioData + }; + } + } catch (e) { + if (config.debug.cryptoErrors) { + console.log( + 'UpdateAccountBalanceAndTransactions error on loadFioData ' + + e.message, + e + ); + } + Log.errDaemon( + 'UpdateAccountBalanceAndTransactions error on loadFioData ' + e.message + ); + } + }; + + async _accountRun( + account, + accounts, + source, + time, + force, + onlyBalances = false + ) { + let newBalance = false; + let addressToScan = account.address; + + Log.daemon( + 'UpdateAccountBalanceAndTransactions _accountRun init ' + + account.id + + ' ' + + account.currencyCode + + ' ' + + account.address + ); + + if ( + account.accountJson && + typeof account.accountJson.addressHex !== 'undefined' + ) { + addressToScan = account.accountJson.addressHex; + Log.daemon( + 'UpdateAccountBalanceAndTransactions changing address ' + + account.currencyCode + + ' ' + + account.address + + ' => ' + + addressToScan + ); + } + const now = new Date().getTime(); + if ( + !force && + typeof CACHE_SCANNING[account.currencyCode + ' ' + addressToScan] !== + 'undefined' + ) { + const diff = + now - CACHE_SCANNING[account.currencyCode + ' ' + addressToScan]; + if (diff < time) { + Log.daemon( + 'UpdateAccountBalanceAndTransactions skipped as running ' + + account.currencyCode + + ' ' + + account.address + + ' => diff:' + + diff + + ' time: ' + + time + ); + return false; + } + } + CACHE_SCANNING[account.currencyCode + ' ' + addressToScan] = now; + + const updateObj = { + balanceScanTime: Math.round(new Date().getTime() / 1000) + }; + + let balanceError; + try { + Log.daemon( + 'UpdateAccountBalanceAndTransactions newBalance ' + + account.currencyCode + + ' ' + + addressToScan + ); + + if (account.currencyCode === 'BTC') { + const additional = {}; + if ( + account.walletIsHd && + account.derivationPath !== 'm/49quote/0quote/0/1/0' + ) { + updateObj.balanceScanLog = account.address + ' should be HD '; + updateObj.balanceScanError = ''; + await accountBalanceDS.updateAccountBalance({ updateObj }, account); + return false; + } else { + newBalance = await BlocksoftBalances.setCurrencyCode( + account.currencyCode + ) + .setAddress(addressToScan) + .setAdditional(additional) + .setWalletHash(account.walletHash) + .getBalance('AccountRunBalancesBtc'); + } + } else { + newBalance = await BlocksoftBalances.setCurrencyCode( + account.currencyCode + ) + .setAddress(addressToScan) + .setAdditional(account.accountJson) + .setWalletHash(account.walletHash) + .getBalance('AccountRunBalances'); + } + if (!newBalance || typeof newBalance.balance === 'undefined') { + if (account.balanceScanBlock === 0 && account.balanceScanTime === 0) { + updateObj.balanceScanLog = + account.address + + ' empty response, old balance ' + + account.balance + + ', ' + + JSON.stringify(newBalance); + updateObj.balanceScanError = 'account.balanceBadNetwork'; + await accountBalanceDS.updateAccountBalance({ updateObj }, account); + return false; } + balanceError = + ' something wrong with balance ' + + account.currencyCode + + ' ' + + addressToScan + + ' => ' + + JSON.stringify(newBalance); + Log.daemon( + 'UpdateAccountBalanceAndTransactions newBalance something wrong ' + + account.currencyCode + + ' ' + + addressToScan + + ' => ' + + JSON.stringify(newBalance) + ); + } else { + balanceError = ' found in one ' + JSON.stringify(newBalance); + } + } catch (e) { + if (config.debug.appErrors) { + console.log( + 'UpdateAccountBalanceAndTransactions newBalance from ' + + source + + ' loaded ' + + account.currencyCode + + ' ' + + addressToScan + + ' error ' + + e.message + ); + } + balanceError = ' found balanceError ' + e.message; + } - if (account.balanceScanLog) { - updateObj.balanceScanLog += ' ' + account.balanceScanLog + Log.daemon( + 'UpdateAccountBalanceAndTransactions newBalance from ' + + source + + ' loaded ' + + account.currencyCode + + ' ' + + addressToScan, + JSON.stringify(newBalance) + ); + let continueWithTx = true; + let shouldUpdateBalance = false; + try { + if (newBalance && typeof newBalance.balance !== 'undefined') { + if (typeof account.balance === 'undefined') { + shouldUpdateBalance = true; + } else if ( + newBalance.balance.toString() !== account.balance.toString() || + newBalance.unconfirmed.toString() !== account.unconfirmed.toString() + ) { + shouldUpdateBalance = true; + } else if (typeof newBalance.balanceStaked !== 'undefined') { + if (typeof account.balanceStaked === 'undefined') { + shouldUpdateBalance = true; + } else if ( + newBalance.balanceStaked * 1 !== + account.balanceStaked * 1 + ) { + // toString here somehow do undefined sometimes + shouldUpdateBalance = true; + } } - try { - updateObj.balanceScanLog = new Date().toISOString() + ' ' + updateObj.balanceScanLog.substr(0, 1000) - await accountBalanceDS.updateAccountBalance({ updateObj }, account) - } catch (e) { - e.message += ' while accountBalanceDS.updateAccountBalance' - throw e + if ( + typeof newBalance.balanceScanBlock !== 'undefined' && + (typeof account.balanceScanBlock === 'undefined' || + newBalance.balanceScanBlock * 1 < account.balanceScanBlock * 1) + ) { + continueWithTx = false; + updateObj.balanceProvider = newBalance.provider; + updateObj.balanceScanLog = + account.address + + ' block error, ignored new ' + + newBalance.balance + + ' block ' + + newBalance.balanceScanBlock + + ', old balance ' + + account.balance + + ' block ' + + account.balanceScanBlock; + updateObj.balanceScanError = 'account.balanceBadBlock'; + } else if (shouldUpdateBalance) { + updateObj.balanceFix = newBalance.balance; // lets send to db totally not changed big number string + updateObj.balanceTxt = newBalance.balance.toString(); // and string for any case + updateObj.unconfirmedFix = newBalance.unconfirmed || 0; // lets send to db totally not changed big number string + updateObj.unconfirmedTxt = newBalance.unconfirmed || ''; // and string for any case + updateObj.balanceStakedTxt = newBalance.balanceStaked || '0'; + updateObj.balanceProvider = newBalance.provider; + if (typeof newBalance.balanceScanBlock !== 'undefined') { + updateObj.balanceScanBlock = newBalance.balanceScanBlock; + } + updateObj.balanceScanLog = + account.address + + ' all ok, new balance ' + + newBalance.balance + + ', old balance ' + + account.balance + + ', ' + + balanceError; + updateObj.balanceScanError = ''; + const logData = {}; + logData.walletHash = account.walletHash; + logData.currencyCode = account.currencyCode; + logData.address = account.address; + logData.addressShort = account.address + ? account.address.slice(0, 10) + : 'none'; + logData.balanceScanTime = account.balanceScanTime + ''; + logData.balanceProvider = account.balanceProvider + ''; + logData.balance = account.balance + ''; + logData.newBalanceProvider = account.newBalanceProvider + ''; + logData.newBalance = newBalance.balance * 1 + ''; + MarketingEvent.setBalance( + logData.walletHash, + logData.currencyCode, + logData.newBalance, + logData + ); + } else { + updateObj.balanceScanLog = + account.address + + ' not changed, old balance ' + + account.balance + + ', ' + + balanceError; + updateObj.balanceScanError = ''; + if (typeof newBalance.provider !== 'undefined') { + updateObj.balanceProvider = newBalance.provider; + } } + Log.daemon( + 'UpdateAccountBalanceAndTransactions newBalance ok Prepared ' + + account.currencyCode + + ' ' + + account.address + + ' new balance ' + + newBalance.balance + + ' provider ' + + newBalance.provider + + ' old balance ' + + account.balance, + JSON.stringify(updateObj) + ); + } else { + updateObj.balanceScanLog = + account.address + + ' no balance, old balance ' + + account.balance + + ', ' + + balanceError; + updateObj.balanceScanError = 'account.balanceBadNetwork'; + Log.daemon( + 'UpdateAccountBalanceAndTransactions newBalance not Prepared ' + + account.currencyCode + + ' ' + + account.address + + ' old balance ' + + account.balance, + JSON.stringify(updateObj) + ); + } + } catch (e) { + if (config.debug.appErrors) { + console.log( + 'UpdateAccountBalanceAndTransactions newBalance from ' + + source + + ' loaded ' + + account.currencyCode + + ' ' + + addressToScan + + ' format error ' + + e.message + ); + } + e.message += ' while accountBalanceDS.updateAccountBalance formatting'; + throw e; + } - if (!continueWithTx || onlyBalances) { - return shouldUpdateBalance // balance error - tx will not be good also + if (account.balanceScanLog) { + updateObj.balanceScanLog += ' ' + account.balanceScanLog; + } + + try { + updateObj.balanceScanLog = + new Date().toISOString() + + ' ' + + updateObj.balanceScanLog.substr(0, 1000); + await accountBalanceDS.updateAccountBalance({ updateObj }, account); + } catch (e) { + e.message += ' while accountBalanceDS.updateAccountBalance'; + throw e; + } + + if (!continueWithTx || onlyBalances) { + return shouldUpdateBalance; // balance error - tx will not be good also + } + try { + let transactionsError = ' '; + let newTransactions = false; + try { + Log.daemon( + 'UpdateAccountBalanceAndTransactions newTransactions ' + + account.currencyCode + + ' ' + + account.address + ); + if (account.currencyCode === 'BTC' || account.currencyCode === 'LTC') { + const additional = { ...account.accountJson }; + additional.addresses = await accountScanningDS.getAddresses({ + currencyCode: account.currencyCode, + walletHash: account.walletHash + }); + if (account.walletIsHd && account.currencyCode !== 'LTC') { + additional.walletPub = true; // actually not needed pub - just flag + } + newTransactions = await BlocksoftTransactions.getTransactions( + { account, additional }, + 'AccountRunTransactionsBtc' + ); + } else { + newTransactions = await BlocksoftTransactions.getTransactions( + { account, additional: account.accountJson }, + 'AccountRunTransactions' + ); } - try { - let transactionsError = ' ' - let newTransactions = false - try { - Log.daemon('UpdateAccountBalanceAndTransactions newTransactions ' + account.currencyCode + ' ' + account.address) - if (account.currencyCode === 'BTC' || account.currencyCode === 'LTC') { - const additional = {... account.accountJson} - additional.addresses = await accountScanningDS.getAddresses({ - currencyCode: account.currencyCode, - walletHash: account.walletHash - }) - if (account.walletIsHd && account.currencyCode !== 'LTC') { - additional.walletPub = true // actually not needed pub - just flag - } - newTransactions = await BlocksoftTransactions.getTransactions({ account, additional }, 'AccountRunTransactionsBtc') - } else { - newTransactions = await BlocksoftTransactions.getTransactions({ account, additional: account.accountJson }, 'AccountRunTransactions') - } - if (!newTransactions || newTransactions.length === 0) { - transactionsError = ' empty transactions ' + account.currencyCode + ' ' + account.address - } else { - transactionsError = ' found transactions ' + newTransactions.length - } - } catch (e) { - if (config.debug.appErrors) { - console.log('UpdateAccountBalanceAndTransactions newTransactions something wrong ' + account.currencyCode + ' ' + account.address + ' => transactionsError ' + e.message, e) - } - Log.errDaemon('UpdateAccountBalanceAndTransactions newTransactions something wrong ' + account.currencyCode + ' ' + account.address + ' => transactionsError ' + e.message) - - transactionsError = ' found transactionsError ' + e.message - } - - Log.daemon('UpdateAccountBalanceAndTransactions newTransactions loaded ' + account.currencyCode + ' ' + addressToScan) - - let transactionUpdateObj - try { - transactionUpdateObj = await AccountTransactionsRecheck(newTransactions, account, 'RECHECK ' + source) - } catch (e) { - e.message += ' while AccountTransactionsRecheck' - throw e - } - // Log.daemon('res', transactionUpdateObj) - try { - transactionUpdateObj.transactionsScanLog = new Date().toISOString() + ' ' + transactionsError + ', ' + transactionUpdateObj.transactionsScanLog - if (account.transactionsScanLog) { - transactionUpdateObj.transactionsScanLog += ' ' + account.transactionsScanLog - } - await accountDS.updateAccount({ updateObj: transactionUpdateObj }, account) - } catch (e) { - e.message += ' while accountDS.updateAccount' - throw e - } - } catch (e) { - if (config.debug.appErrors) { - console.log('UpdateAccountBalanceAndTransactions newTransactions something wrong ' + account.currencyCode + ' ' + account.address + ' => transactionsError2 ' + e.message, e) - } - Log.errDaemon('UpdateAccountBalanceAndTransactions newTransactions something wrong ' + account.currencyCode + ' ' + account.address + ' => transactionsError2 ' + e.message) + if (!newTransactions || newTransactions.length === 0) { + transactionsError = + ' empty transactions ' + + account.currencyCode + + ' ' + + account.address; + } else { + transactionsError = ' found transactions ' + newTransactions.length; } - - CACHE_ONE_ACCOUNTS[account.currencyCode + '_' + account.address] = 1 - - if (account.currencyCode === 'TRX') { - for (const sub of accounts) { - if (sub.currencyCode === 'TRX_USDT') { - if (await this._accountRun(sub, accounts, source + ' GONE INNER', CACHE_VALID_TIME, true)) { - shouldUpdateBalance = true - } - break - } - } - for (const sub of accounts) { - if (sub.currencyCode !== 'TRX_USDT' && sub.currencyCode.indexOf('TRX_') === 0) { - if (await this._accountRun(sub, accounts, source + ' GONE INNER2', CACHE_VALID_TIME, true)) { - shouldUpdateBalance = true - } - } - } + } catch (e) { + if (config.debug.appErrors) { + console.log( + 'UpdateAccountBalanceAndTransactions newTransactions something wrong ' + + account.currencyCode + + ' ' + + account.address + + ' => transactionsError ' + + e.message, + e + ); + } + Log.errDaemon( + 'UpdateAccountBalanceAndTransactions newTransactions something wrong ' + + account.currencyCode + + ' ' + + account.address + + ' => transactionsError ' + + e.message + ); + + transactionsError = ' found transactionsError ' + e.message; + } + + Log.daemon( + 'UpdateAccountBalanceAndTransactions newTransactions loaded ' + + account.currencyCode + + ' ' + + addressToScan + ); + + let transactionUpdateObj; + try { + transactionUpdateObj = await AccountTransactionsRecheck( + newTransactions, + account, + 'RECHECK ' + source + ); + } catch (e) { + e.message += ' while AccountTransactionsRecheck'; + throw e; + } + // Log.daemon('res', transactionUpdateObj) + try { + transactionUpdateObj.transactionsScanLog = + new Date().toISOString() + + ' ' + + transactionsError + + ', ' + + transactionUpdateObj.transactionsScanLog; + if (account.transactionsScanLog) { + transactionUpdateObj.transactionsScanLog += + ' ' + account.transactionsScanLog; } + await accountDS.updateAccount( + { updateObj: transactionUpdateObj }, + account + ); + } catch (e) { + e.message += ' while accountDS.updateAccount'; + throw e; + } + } catch (e) { + if (config.debug.appErrors) { + console.log( + 'UpdateAccountBalanceAndTransactions newTransactions something wrong ' + + account.currencyCode + + ' ' + + account.address + + ' => transactionsError2 ' + + e.message, + e + ); + } + Log.errDaemon( + 'UpdateAccountBalanceAndTransactions newTransactions something wrong ' + + account.currencyCode + + ' ' + + account.address + + ' => transactionsError2 ' + + e.message + ); + } - await this.loadFioData(account.currencyCode) + CACHE_ONE_ACCOUNTS[account.currencyCode + '_' + account.address] = 1; + + if (account.currencyCode === 'TRX') { + for (const sub of accounts) { + if (sub.currencyCode === 'TRX_USDT') { + if ( + await this._accountRun( + sub, + accounts, + source + ' GONE INNER', + CACHE_VALID_TIME, + true + ) + ) { + shouldUpdateBalance = true; + } + break; + } + } + for (const sub of accounts) { + if ( + sub.currencyCode !== 'TRX_USDT' && + sub.currencyCode.indexOf('TRX_') === 0 + ) { + if ( + await this._accountRun( + sub, + accounts, + source + ' GONE INNER2', + CACHE_VALID_TIME, + true + ) + ) { + shouldUpdateBalance = true; + } + } + } + } - Log.daemon('UpdateAccountBalanceAndTransactions _accountRun finish ' + account.id + ' ' + account.currencyCode + ' ' + account.address) + await this.loadFioData(account.currencyCode); - return shouldUpdateBalance - } + Log.daemon( + 'UpdateAccountBalanceAndTransactions _accountRun finish ' + + account.id + + ' ' + + account.currencyCode + + ' ' + + account.address + ); + return shouldUpdateBalance; + } } - -const singleton = new UpdateAccountBalanceAndTransactions() -export default singleton +const singleton = new UpdateAccountBalanceAndTransactions(); +export default singleton; diff --git a/yarn.lock b/yarn.lock index 06131ab8f..937334027 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1493,6 +1493,11 @@ dependencies: "@types/hammerjs" "^2.0.36" +"@eivifj/dot@^1.0.1": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@eivifj/dot/-/dot-1.0.3.tgz#b3a6d9662cd84ff4105e0f05620d1045e9d0d9fc" + integrity sha512-UE2x9N7XD/1qqtXA+4CZPD1RQAlM0lEdXdy09CJ/wNR+mgGKqwLRH4BGGfOAWwwz06pIT3c2tC4gvXbntGAeMA== + "@eslint-community/eslint-utils@^4.2.0": version "4.4.0" resolved "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz" @@ -2064,6 +2069,35 @@ find-up "^5.0.0" js-yaml "^4.1.0" +"@fioprotocol/fiojs@1.0.1", "@fioprotocol/fiojs@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@fioprotocol/fiojs/-/fiojs-1.0.1.tgz#81779437603741bc4ca1c76d119b64c4157a3874" + integrity sha512-+rxJ/ynUkox/DO3ihHPpAc//DDI+DQvrphLqwRKufw0atC3GKluGR2qMTeO45O0UjorvOIw6esuuG7NpbVUm8Q== + dependencies: + ajv "^6.10.2" + babel-runtime "6.26.0" + bigi "^1.4.2" + browserify-aes "^1.2.0" + bs58 "^4.0.1" + create-hash "^1.2.0" + create-hmac "^1.1.7" + ecurve "^1.0.6" + long "^4.0.0" + randombytes "^2.1.0" + text-encoding "0.7.0" + +"@fioprotocol/fiosdk@^1.8.0": + version "1.8.0" + resolved "https://registry.yarnpkg.com/@fioprotocol/fiosdk/-/fiosdk-1.8.0.tgz#2fcda22bb1b42491de2c5a086a388871429da9b0" + integrity sha512-wBdwZGWOnP4C0wxOa+URofYX8kp87dfaMZayClmBDercoPyrVBUnKaQwNK5XWiVM0rTP1PVVAhFs98QAZxjPjA== + dependencies: + "@fioprotocol/fiojs" "1.0.1" + "@types/text-encoding" "0.0.35" + bip39 "^3.0.2" + hdkey "^1.1.1" + validate "^5.1.0" + wif "^2.0.6" + "@gar/promisify@^1.0.1": version "1.1.3" resolved "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz" @@ -3018,6 +3052,11 @@ resolved "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz" integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== +"@types/lodash@^4.14.196": + version "4.14.196" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.196.tgz#a7c3d6fc52d8d71328b764e28e080b4169ec7a95" + integrity sha512-22y3o88f4a94mKljsZcanlNWPzO0uBsBdzLAngf2tp533LzZcQzb6+eZPJ+vCTt+bqF2XnvT9gejTLsAcJAJyQ== + "@types/node@*": version "18.15.11" resolved "https://registry.npmjs.org/@types/node/-/node-18.15.11.tgz" @@ -3123,6 +3162,11 @@ dependencies: "@types/jest" "*" +"@types/text-encoding@0.0.35": + version "0.0.35" + resolved "https://registry.yarnpkg.com/@types/text-encoding/-/text-encoding-0.0.35.tgz#6f14474e0b232bc70c59677aadc65dcc5a99c3a9" + integrity sha512-jfo/A88XIiAweUa8np+1mPbm3h2w0s425YrI8t3wk5QxhH6UI7w517MboNVnGDeMSuoFwA8Rwmklno+FicvV4g== + "@types/tough-cookie@*": version "4.0.2" resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-4.0.2.tgz#6286b4c7228d58ab7866d19716f3696e03a09397" @@ -3365,7 +3409,7 @@ aggregate-error@^3.0.0: clean-stack "^2.0.0" indent-string "^4.0.0" -ajv@^6.10.0, ajv@^6.12.4: +ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.4: version "6.12.6" resolved "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz" integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== @@ -3816,6 +3860,14 @@ babel-preset-jest@^29.5.0: babel-plugin-jest-hoist "^29.5.0" babel-preset-current-node-syntax "^1.0.0" +babel-runtime@6.26.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe" + integrity sha512-ITKNuq2wKlW1fJg9sSW52eepoYgZBggvOAHC0u/CYu/qxQ9EVzThCgR69BnSXLHjy2f7SY5zaQ4yt7H9ZVxY2g== + dependencies: + core-js "^2.4.0" + regenerator-runtime "^0.11.0" + badgin@^1.1.5: version "1.2.3" resolved "https://registry.yarnpkg.com/badgin/-/badgin-1.2.3.tgz#994b5f519827d7d5422224825b2c8faea2bc43ad" @@ -3878,12 +3930,17 @@ big-integer@1.6.x: resolved "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz" integrity sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg== +bigi@^1.1.0, bigi@^1.4.2: + version "1.4.2" + resolved "https://registry.yarnpkg.com/bigi/-/bigi-1.4.2.tgz#9c665a95f88b8b08fc05cfd731f561859d725825" + integrity sha512-ddkU+dFIuEIW8lE7ZwdIAf2UPoM90eaprg5m3YXAVVTmKlqV/9BX4A2M8BOK2yOq6/VgZFVhK6QAxJebhlbhzw== + bignumber.js@^9.1.1: version "9.1.1" resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.1.1.tgz#c4df7dc496bd849d4c9464344c1aa74228b4dac6" integrity sha512-pHm4LsMJ6lzgNGVfZHjMoO8sdoRhOzOH4MLmY65Jg70bpxCKu5iOHNJyfF6OyvYw7t8Fpf35RuzUyqnQsj8Vig== -bindings@^1.3.0: +bindings@^1.3.0, bindings@^1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ== @@ -3918,14 +3975,14 @@ bip32@^4.0.0: typeforce "^1.11.5" wif "^2.0.6" -bip39@^3.1.0: +bip39@^3.0.2, bip39@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/bip39/-/bip39-3.1.0.tgz#c55a418deaf48826a6ceb34ac55b3ee1577e18a3" integrity sha512-c9kiwdk45Do5GL0vJMe7tS95VjCii65mYAH7DfWl3uW8AVzXKQVUm64i3hzVybBDMp9r7j9iNxR85+ul8MdN/A== dependencies: "@noble/hashes" "^1.2.0" -bip66@^1.1.0: +bip66@^1.1.0, bip66@^1.1.5: version "1.1.5" resolved "https://registry.yarnpkg.com/bip66/-/bip66-1.1.5.tgz#01fa8748785ca70955d5011217d1b3139969ca22" integrity sha512-nemMHz95EmS38a26XbbdxIYj5csHd3RMP3H5bwQknX0WYHF01qhpufP42mLOwVICuH2JmhIhXiWs89MfUGL7Xw== @@ -4077,7 +4134,7 @@ brorand@^1.1.0: resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" integrity sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w== -browserify-aes@^1.2.0: +browserify-aes@^1.0.6, browserify-aes@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.2.0.tgz#326734642f403dabc3003209853bb70ad428ef48" integrity sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA== @@ -4619,7 +4676,7 @@ component-emitter@^1.2.1: resolved "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz" integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== -component-type@^1.2.1: +component-type@1.2.1, component-type@^1.2.1: version "1.2.1" resolved "https://registry.npmjs.org/component-type/-/component-type-1.2.1.tgz" integrity sha512-Kgy+2+Uwr75vAi6ChWXgHuLvd+QLD7ssgpaRq2zCvt80ptvAfMc/hijcJxXkBa2wMlEZcJvC2H8Ubo+A9ATHIg== @@ -4691,6 +4748,11 @@ core-js@^1.0.0: resolved "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz" integrity sha512-ZiPp9pZlgxpWRu0M+YWbm6+aQ84XEfH1JRXvfOc/fILWI0VKhLC2LX13X1NYq4fULzLMq7Hfh43CSo2/aIaUPA== +core-js@^2.4.0: + version "2.6.12" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.12.tgz#d9333dfa7b065e347cc5682219d6f690859cc2ec" + integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ== + core-util-is@~1.0.0: version "1.0.3" resolved "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz" @@ -5131,11 +5193,28 @@ domutils@^3.0.1: domelementtype "^2.3.0" domhandler "^5.0.1" +drbg.js@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/drbg.js/-/drbg.js-1.0.1.tgz#3e36b6c42b37043823cdbc332d58f31e2445480b" + integrity sha512-F4wZ06PvqxYLFEZKkFxTDcns9oFNk34hvmJSEwdzsxVQ8YI5YaxtACgQatkYgv2VI2CFkUd2Y+xosPQnHv809g== + dependencies: + browserify-aes "^1.0.6" + create-hash "^1.1.2" + create-hmac "^1.1.4" + eastasianwidth@^0.2.0: version "0.2.0" resolved "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz" integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== +ecurve@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/ecurve/-/ecurve-1.0.6.tgz#dfdabbb7149f8d8b78816be5a7d5b83fcf6de797" + integrity sha512-/BzEjNfiSuB7jIWKcS/z8FK9jNjmEWvUV2YZ4RLSmcDtP7Lq0m6FvDuSnJpBlDpGRpfRQeTLGLBI8H+kEv0r+w== + dependencies: + bigi "^1.1.0" + safe-buffer "^5.0.1" + ee-first@1.1.1: version "1.1.1" resolved "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz" @@ -5146,7 +5225,7 @@ electron-to-chromium@^1.4.284: resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.348.tgz" integrity sha512-gM7TdwuG3amns/1rlgxMbeeyNoBFPa+4Uu0c7FeROWh4qWmvSOnvcslKmWy51ggLKZ2n/F/4i2HJ+PVNxH9uCQ== -elliptic@6.5.4, elliptic@^6.4.0, elliptic@^6.5.4: +elliptic@6.5.4, elliptic@^6.4.0, elliptic@^6.5.2, elliptic@^6.5.4: version "6.5.4" resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb" integrity sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ== @@ -6696,6 +6775,15 @@ hash.js@1.1.7, hash.js@^1.0.0, hash.js@^1.0.3, hash.js@^1.1.7: inherits "^2.0.3" minimalistic-assert "^1.0.1" +hdkey@^1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/hdkey/-/hdkey-1.1.2.tgz#c60f9cf6f90fbf24a8a52ea06893f36a0108cd3e" + integrity sha512-PTQ4VKu0oRnCrYfLp04iQZ7T2Cxz0UsEXYauk2j8eh6PJXCpbXuCFhOmtIFtbET0i3PMWmHN9J11gU8LEgUljQ== + dependencies: + bs58check "^2.1.2" + safe-buffer "^5.1.1" + secp256k1 "^3.0.1" + hermes-estree@0.8.0: version "0.8.0" resolved "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.8.0.tgz" @@ -8292,6 +8380,11 @@ logkitty@^0.7.1: resolved "https://registry.yarnpkg.com/@nozbe/lokijs/-/lokijs-1.5.12-wmelon6.tgz#e457d934d614d5df80105c86314252a6e614df9b" integrity sha512-GXsaqY8qTJ6xdCrGyno2t+ON2aj6PrUDdvhbrkxK/0Fp12C4FGvDg1wS+voLU9BANYHEnr7KRWfItDZnQkjoAg== +long@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28" + integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA== + loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0: version "1.4.0" resolved "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz" @@ -8903,7 +8996,7 @@ mz@^2.7.0: object-assign "^4.0.1" thenify-all "^1.0.0" -nan@^2.13.2: +nan@^2.13.2, nan@^2.14.0: version "2.17.0" resolved "https://registry.yarnpkg.com/nan/-/nan-2.17.0.tgz#c0150a2368a182f033e9aa5195ec76ea41a199cb" integrity sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ== @@ -10119,6 +10212,11 @@ regenerate@^1.4.2: resolved "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz" integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A== +regenerator-runtime@^0.11.0: + version "0.11.1" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9" + integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg== + regenerator-runtime@^0.13.11, regenerator-runtime@^0.13.2: version "0.13.11" resolved "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz" @@ -10469,6 +10567,20 @@ scrypt-js@^3.0.0: resolved "https://registry.yarnpkg.com/scrypt-js/-/scrypt-js-3.0.1.tgz#d314a57c2aef69d1ad98a138a21fe9eafa9ee312" integrity sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA== +secp256k1@^3.0.1: + version "3.8.0" + resolved "https://registry.yarnpkg.com/secp256k1/-/secp256k1-3.8.0.tgz#28f59f4b01dbee9575f56a47034b7d2e3b3b352d" + integrity sha512-k5ke5avRZbtl9Tqx/SA7CbY3NF6Ro+Sj9cZxezFzuBlLDmyqPiL8hJJ+EmzD8Ig4LUDByHJ3/iPOVoRixs/hmw== + dependencies: + bindings "^1.5.0" + bip66 "^1.1.5" + bn.js "^4.11.8" + create-hash "^1.2.0" + drbg.js "^1.0.1" + elliptic "^6.5.2" + nan "^2.14.0" + safe-buffer "^5.1.2" + secp256k1@^4.0.1, secp256k1@^4.0.2: version "4.0.3" resolved "https://registry.yarnpkg.com/secp256k1/-/secp256k1-4.0.3.tgz#c4559ecd1b8d3c1827ed2d1b94190d69ce267303" @@ -11252,6 +11364,11 @@ text-encoding-utf-8@^1.0.2: resolved "https://registry.yarnpkg.com/text-encoding-utf-8/-/text-encoding-utf-8-1.0.2.tgz#585b62197b0ae437e3c7b5d0af27ac1021e10d13" integrity sha512-8bw4MY9WjdsD2aMtO0OzOCY3pXGYNx2d2FfHRVUKkiCPDWjKuOlhLVASS+pD7VkLTVjW268LYJHwsnPFlBpbAg== +text-encoding@0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/text-encoding/-/text-encoding-0.7.0.tgz#f895e836e45990624086601798ea98e8f36ee643" + integrity sha512-oJQ3f1hrOnbRLOcwKz0Liq2IcrvDeZRHXhd9RgLrsT+DjWY/nty1Hi7v3dtkaEYbPYe0mUoOfzRrMwfXXwgPUA== + text-table@^0.2.0: version "0.2.0" resolved "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz" @@ -11523,6 +11640,11 @@ type-is@~1.6.18: media-typer "0.3.0" mime-types "~2.1.24" +typecast@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/typecast/-/typecast-0.0.1.tgz#fffb75dcb6bdf1def8e293b6b6e893d6c1ed19de" + integrity sha512-L2f5OCLKsJdCjSyN0d5O6CkNxhiC8EQ2XlXnHpWZVNfF+mj2OTaXhAVnP0/7SY/sxO1DHZpOFMpIuGlFUZEGNA== + typed-array-length@^1.0.4: version "1.0.4" resolved "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz" @@ -11781,6 +11903,15 @@ validate-npm-package-name@^3.0.0: dependencies: builtins "^1.0.3" +validate@^5.1.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/validate/-/validate-5.2.0.tgz#fe7355c1644092b67dd07731ad5716cb99f689a5" + integrity sha512-pVADd6GfDT7bALYvvzhfHnfmxCDem2bG7lGY3mwHzY7ktMH/SaczoF+eKkGokQEdGKozkJvyuOiSfQEHXp4zIA== + dependencies: + "@eivifj/dot" "^1.0.1" + component-type "1.2.1" + typecast "0.0.1" + varint@5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/varint/-/varint-5.0.0.tgz#d826b89f7490732fabc0c0ed693ed475dcb29ebf" From c6a482d7d6237252a85ef7d93a3ee88c0d782d3e Mon Sep 17 00:00:00 2001 From: JavidHaji-zada Date: Thu, 3 Aug 2023 13:16:57 +0400 Subject: [PATCH 027/509] converted all blockchain files --- crypto/blockchains/ltc/LtcScannerProcessor.js | 20 - crypto/blockchains/ltc/LtcScannerProcessor.ts | 19 + .../blockchains/ltc/LtcTransferProcessor.ts | 76 +-- ...rProcessor.js => MetisScannerProcessor.ts} | 0 .../metis/MetisTransferProcessor.ts | 154 +++--- ...nerProcessor.js => OneScannerProcessor.ts} | 454 +++++++++--------- ...orErc20.js => OneScannerProcessorErc20.ts} | 423 ++++++++-------- crypto/blockchains/one/ext/OneUtils.js | 239 --------- crypto/blockchains/one/ext/OneUtils.ts | 235 +++++++++ crypto/blockchains/one/stores/OneTmpDS.js | 45 -- crypto/blockchains/one/stores/OneTmpDS.ts | 52 ++ 11 files changed, 855 insertions(+), 862 deletions(-) delete mode 100644 crypto/blockchains/ltc/LtcScannerProcessor.js create mode 100644 crypto/blockchains/ltc/LtcScannerProcessor.ts rename crypto/blockchains/metis/{MetisScannerProcessor.js => MetisScannerProcessor.ts} (100%) rename crypto/blockchains/one/{OneScannerProcessor.js => OneScannerProcessor.ts} (86%) rename crypto/blockchains/one/{OneScannerProcessorErc20.js => OneScannerProcessorErc20.ts} (91%) delete mode 100644 crypto/blockchains/one/ext/OneUtils.js create mode 100644 crypto/blockchains/one/ext/OneUtils.ts delete mode 100644 crypto/blockchains/one/stores/OneTmpDS.js create mode 100644 crypto/blockchains/one/stores/OneTmpDS.ts diff --git a/crypto/blockchains/ltc/LtcScannerProcessor.js b/crypto/blockchains/ltc/LtcScannerProcessor.js deleted file mode 100644 index 5198d20b0..000000000 --- a/crypto/blockchains/ltc/LtcScannerProcessor.js +++ /dev/null @@ -1,20 +0,0 @@ -/** - * @version 0.5 - */ - -import BtcScannerProcessor from '../btc/BtcScannerProcessor' - -export default class LtcScannerProcessor extends BtcScannerProcessor { - - /** - * @type {number} - * @private - */ - _blocksToConfirm = 10 - - /** - * @type {string} - * @private - */ - _trezorServerCode = 'LTC_TREZOR_SERVER' -} diff --git a/crypto/blockchains/ltc/LtcScannerProcessor.ts b/crypto/blockchains/ltc/LtcScannerProcessor.ts new file mode 100644 index 000000000..a675bb30e --- /dev/null +++ b/crypto/blockchains/ltc/LtcScannerProcessor.ts @@ -0,0 +1,19 @@ +/** + * @version 0.5 + */ + +import BtcScannerProcessor from '../btc/BtcScannerProcessor'; + +export default class LtcScannerProcessor extends BtcScannerProcessor { + /** + * @type {number} + * @private + */ + _blocksToConfirm = 10; + + /** + * @type {string} + * @private + */ + _trezorServerCode = 'LTC_TREZOR_SERVER'; +} diff --git a/crypto/blockchains/ltc/LtcTransferProcessor.ts b/crypto/blockchains/ltc/LtcTransferProcessor.ts index 70c7336bd..2ddc12b73 100644 --- a/crypto/blockchains/ltc/LtcTransferProcessor.ts +++ b/crypto/blockchains/ltc/LtcTransferProcessor.ts @@ -2,39 +2,53 @@ * @version 0.20 */ -import BtcTransferProcessor from '@crypto/blockchains/btc/BtcTransferProcessor' -import { BlocksoftBlockchainTypes } from '@crypto/blockchains/BlocksoftBlockchainTypes' -import DogeNetworkPrices from '@crypto/blockchains/doge/basic/DogeNetworkPrices' -import BtcUnspentsProvider from '@crypto/blockchains/btc/providers/BtcUnspentsProvider' -import DogeSendProvider from '@crypto/blockchains/doge/providers/DogeSendProvider' -import BtcTxInputsOutputs from '@crypto/blockchains/btc/tx/BtcTxInputsOutputs' -import BtcTxBuilder from '@crypto/blockchains/btc/tx/BtcTxBuilder' +import BtcTransferProcessor from '@crypto/blockchains/btc/BtcTransferProcessor'; +import { AirDAOBlockchainTypes } from '@crypto/blockchains/AirDAOBlockchainTypes'; +import DogeNetworkPrices from '@crypto/blockchains/doge/basic/DogeNetworkPrices'; +import BtcUnspentsProvider from '@crypto/blockchains/btc/providers/BtcUnspentsProvider'; +import DogeSendProvider from '@crypto/blockchains/doge/providers/DogeSendProvider'; +import BtcTxInputsOutputs from '@crypto/blockchains/btc/tx/BtcTxInputsOutputs'; +import BtcTxBuilder from '@crypto/blockchains/btc/tx/BtcTxBuilder'; -export default class LtcTransferProcessor extends BtcTransferProcessor implements BlocksoftBlockchainTypes.TransferProcessor { +export default class LtcTransferProcessor + extends BtcTransferProcessor + implements AirDAOBlockchainTypes.TransferProcessor +{ + _trezorServerCode = 'LTC_TREZOR_SERVER'; - _trezorServerCode = 'LTC_TREZOR_SERVER' + _builderSettings: AirDAOBlockchainTypes.BuilderSettings = { + minOutputDustReadable: 0.000005, + minChangeDustReadable: 0.00001, + feeMaxForByteSatoshi: 10000, // for tx builder + feeMaxAutoReadable2: 0.2, // for fee calc, + feeMaxAutoReadable6: 0.1, // for fee calc + feeMaxAutoReadable12: 0.05, // for fee calc + changeTogether: true + }; - _builderSettings: BlocksoftBlockchainTypes.BuilderSettings = { - minOutputDustReadable: 0.000005, - minChangeDustReadable: 0.00001, - feeMaxForByteSatoshi: 10000, // for tx builder - feeMaxAutoReadable2: 0.2, // for fee calc, - feeMaxAutoReadable6: 0.1, // for fee calc - feeMaxAutoReadable12: 0.05, // for fee calc - changeTogether: true - } + _initProviders() { + if (this._initedProviders) return false; + this.unspentsProvider = new BtcUnspentsProvider( + this._settings, + this._trezorServerCode + ); + this.sendProvider = new DogeSendProvider( + this._settings, + this._trezorServerCode + ); + this.txPrepareInputsOutputs = new BtcTxInputsOutputs( + this._settings, + this._builderSettings + ); + this.txBuilder = new BtcTxBuilder(this._settings, this._builderSettings); + this.networkPrices = new DogeNetworkPrices(); + this._initedProviders = true; + } - _initProviders() { - if (this._initedProviders) return false - this.unspentsProvider = new BtcUnspentsProvider(this._settings, this._trezorServerCode) - this.sendProvider = new DogeSendProvider(this._settings, this._trezorServerCode) - this.txPrepareInputsOutputs = new BtcTxInputsOutputs(this._settings, this._builderSettings) - this.txBuilder = new BtcTxBuilder(this._settings, this._builderSettings) - this.networkPrices = new DogeNetworkPrices() - this._initedProviders = true - } - - canRBF(data: BlocksoftBlockchainTypes.DbAccount, transaction: BlocksoftBlockchainTypes.DbTransaction): boolean { - return false - } + canRBF( + data: AirDAOBlockchainTypes.DbAccount, + transaction: AirDAOBlockchainTypes.DbTransaction + ): boolean { + return false; + } } diff --git a/crypto/blockchains/metis/MetisScannerProcessor.js b/crypto/blockchains/metis/MetisScannerProcessor.ts similarity index 100% rename from crypto/blockchains/metis/MetisScannerProcessor.js rename to crypto/blockchains/metis/MetisScannerProcessor.ts diff --git a/crypto/blockchains/metis/MetisTransferProcessor.ts b/crypto/blockchains/metis/MetisTransferProcessor.ts index 1b5d5a92a..cb7236dfb 100644 --- a/crypto/blockchains/metis/MetisTransferProcessor.ts +++ b/crypto/blockchains/metis/MetisTransferProcessor.ts @@ -1,77 +1,77 @@ -/** - * @author Ksu - * @version 0.43 - */ -import { BlocksoftBlockchainTypes } from '@crypto/blockchains/BlocksoftBlockchainTypes'; -import EthTransferProcessor from '@crypto/blockchains/eth/EthTransferProcessor'; -import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings'; -import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; -import BlocksoftUtils from '@crypto/common/BlocksoftUtils'; - -export default class MetisTransferProcessor - extends EthTransferProcessor - implements BlocksoftBlockchainTypes.TransferProcessor -{ - async getFeeRate( - data: BlocksoftBlockchainTypes.TransferData, - privateData: BlocksoftBlockchainTypes.TransferPrivateData, - additionalData: {} = {} - ): Promise { - if ( - typeof additionalData.gasPrice === 'undefined' || - !additionalData.gasPrice - ) { - additionalData.gasPrice = BlocksoftExternalSettings.getStatic( - this._mainCurrencyCode + '_PRICE' - ); - additionalData.gasPriceTitle = 'speed_blocks_2'; - } - - let value = 0; - try { - if (data.amount.indexOf('0x') === 0) { - value = data.amount; - } else { - value = '0x' + BlocksoftUtils.decimalToHex(data.amount); - } - } catch (e) { - throw new Error(e.message + ' with data.amount ' + data.amount); - } - const params = { - jsonrpc: '2.0', - method: 'eth_estimateGas', - params: [ - { - from: data.addressFrom, - to: data.addressTo, - value: value, - data: '0x' - } - ], - id: 1 - }; - const tmp = await BlocksoftAxios.post( - BlocksoftExternalSettings.getStatic('METIS_SERVER'), - params - ); - - if (typeof tmp !== 'undefined' && typeof tmp.data !== 'undefined') { - if (typeof tmp.data.result !== 'undefined') { - additionalData.gasLimit = BlocksoftUtils.hexToDecimalWalletConnect( - tmp.data.result - ); - } else if (typeof tmp.data.error !== 'undefined') { - throw new Error(tmp.data.error.message); - } - } - return super.getFeeRate(data, privateData, additionalData); - } - - canRBF( - data: BlocksoftBlockchainTypes.DbAccount, - transaction: BlocksoftBlockchainTypes.DbTransaction, - source: string - ): boolean { - return false; - } -} +/** + * @author Ksu + * @version 0.43 + */ +import { AirDAOBlockchainTypes } from '@crypto/blockchains/AirDAOBlockchainTypes'; +import EthTransferProcessor from '@crypto/blockchains/eth/EthTransferProcessor'; +import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings'; +import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; +import BlocksoftUtils from '@crypto/common/BlocksoftUtils'; + +export default class MetisTransferProcessor + extends EthTransferProcessor + implements AirDAOBlockchainTypes.TransferProcessor +{ + async getFeeRate( + data: AirDAOBlockchainTypes.TransferData, + privateData: AirDAOBlockchainTypes.TransferPrivateData, + additionalData: {} = {} + ): Promise { + if ( + typeof additionalData.gasPrice === 'undefined' || + !additionalData.gasPrice + ) { + additionalData.gasPrice = BlocksoftExternalSettings.getStatic( + this._mainCurrencyCode + '_PRICE' + ); + additionalData.gasPriceTitle = 'speed_blocks_2'; + } + + let value = 0; + try { + if (data.amount.indexOf('0x') === 0) { + value = data.amount; + } else { + value = '0x' + BlocksoftUtils.decimalToHex(data.amount); + } + } catch (e) { + throw new Error(e.message + ' with data.amount ' + data.amount); + } + const params = { + jsonrpc: '2.0', + method: 'eth_estimateGas', + params: [ + { + from: data.addressFrom, + to: data.addressTo, + value: value, + data: '0x' + } + ], + id: 1 + }; + const tmp = await BlocksoftAxios.post( + BlocksoftExternalSettings.getStatic('METIS_SERVER'), + params + ); + + if (typeof tmp !== 'undefined' && typeof tmp.data !== 'undefined') { + if (typeof tmp.data.result !== 'undefined') { + additionalData.gasLimit = BlocksoftUtils.hexToDecimalWalletConnect( + tmp.data.result + ); + } else if (typeof tmp.data.error !== 'undefined') { + throw new Error(tmp.data.error.message); + } + } + return super.getFeeRate(data, privateData, additionalData); + } + + canRBF( + data: AirDAOBlockchainTypes.DbAccount, + transaction: AirDAOBlockchainTypes.DbTransaction, + source: string + ): boolean { + return false; + } +} diff --git a/crypto/blockchains/one/OneScannerProcessor.js b/crypto/blockchains/one/OneScannerProcessor.ts similarity index 86% rename from crypto/blockchains/one/OneScannerProcessor.js rename to crypto/blockchains/one/OneScannerProcessor.ts index 419b435c2..69f3d9463 100644 --- a/crypto/blockchains/one/OneScannerProcessor.js +++ b/crypto/blockchains/one/OneScannerProcessor.ts @@ -1,234 +1,220 @@ -/** - * https://docs.harmony.one/home/developers/api/methods/account-methods/hmy_getbalance - * https://docs.harmony.one/home/developers/api/methods/transaction-related-methods/hmy_gettransactionshistory#api-v2 - */ -import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; -import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings'; -import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; -import OneUtils from '@crypto/blockchains/one/ext/OneUtils'; -import BlocksoftUtils from '@crypto/common/BlocksoftUtils'; -import config from '@app/config/config'; - -export default class OneScannerProcessor { - _blocksToConfirm = 10; - - constructor(settings) { - this._settings = settings; - } - - /** - * @param {string} address - * @param {*} additionalData - * @param {string} walletHash - * @return {Promise<{balance:*, unconfirmed:*, provider:string}>} - */ - async getBalanceBlockchain(address, additionalData, walletHash) { - const oneAddress = OneUtils.toOneAddress(address); - BlocksoftCryptoLog.log( - this._settings.currencyCode + - ' OneScannerProcessor.getBalanceBlockchain started ' + - address + - ' ' + - oneAddress - ); - try { - const apiPath = BlocksoftExternalSettings.getStatic('ONE_SERVER'); - const data = { - jsonrpc: '2.0', - id: 1, - method: 'hmy_getBalance', - params: [oneAddress, 'latest'] - }; - const res = await BlocksoftAxios._request(apiPath, 'POST', data); - if (typeof res.data === 'undefined') { - return false; - } - if (typeof res.data.error !== 'undefined') { - throw new Error(JSON.stringify(res.data.error)); - } - if (typeof res.data.result === 'undefined') { - return false; - } - const balance = BlocksoftUtils.hexToDecimalBigger(res.data.result); - return { - balance, - unconfirmed: 0 - }; - } catch (e) { - if (config.debug.cryptoErrors) { - console.log( - this._settings.currencyCode + - ' OneScannerProcessor.getBalanceBlockchain address ' + - address + - ' ' + - oneAddress + - ' error ' + - e.message - ); - } - BlocksoftCryptoLog.log( - this._settings.currencyCode + - ' OneScannerProcessor.getBalanceBlockchain address ' + - address + - ' ' + - oneAddress + - ' error ' + - e.message - ); - return false; - } - } - - /** - * @param {string} scanData.account.address - * @param {string} scanData.account.walletHash - * @return {Promise<[UnifiedTransaction]>} - */ - async getTransactionsBlockchain(scanData) { - const { address } = scanData.account; - const oneAddress = OneUtils.toOneAddress(address); - BlocksoftCryptoLog.log( - this._settings.currencyCode + - ' OneScannerProcessor.getTransactionsBlockchain started ' + - address + - ' ' + - oneAddress - ); - try { - const apiPath = BlocksoftExternalSettings.getStatic('ONE_SERVER'); - const data = { - jsonrpc: '2.0', - id: 1, - method: 'hmyv2_getTransactionsHistory', - params: [ - { - address: oneAddress, - pageIndex: 0, - pageSize: 20, - fullTx: true, - txType: 'ALL', - order: 'DESC' - } - ] - }; - const res = await BlocksoftAxios._request(apiPath, 'POST', data); - if ( - typeof res.data === 'undefined' || - typeof res.data.result === 'undefined' || - typeof res.data.result.transactions === 'undefined' - ) { - return false; - } - const transactions = []; - for (const tx of res.data.result.transactions) { - const transaction = await this._unifyTransaction( - address, - oneAddress, - tx - ); - if (transaction) { - transactions.push(transaction); - } - } - return transactions; - } catch (e) { - if (config.debug.cryptoErrors) { - console.log( - this._settings.currencyCode + - ' OneScannerProcessor.getTransactionsBlockchain address ' + - address + - ' error ' + - e.message - ); - } - BlocksoftCryptoLog.log( - this._settings.currencyCode + - ' OneScannerProcessor.getTransactionsBlockchain address ' + - address + - ' error ' + - e.message - ); - return false; - } - } - - /** - * - * @param {string} address - * @param {string} oneAddress - * @param {Object} transaction - * @param {string} transaction.blockHash - * @param {string} transaction.blockNumber - * @param {string} transaction.ethHash - * @param {string} transaction.from - * @param {string} transaction.gas - * @param {string} transaction.gasPrice - * @param {string} transaction.hash - * @param {string} transaction.input "0x095ea7b3000000000000000000000000d0cb3e55449646c9735d53e83eea5eb7e97a52dcffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", - * @param {string} transaction.nonce - * @param {string} transaction.shardID - * @param {string} transaction.timestamp - * @param {string} transaction.to - * @param {string} transaction.toShardID - * @param {string} transaction.value - * @return {Promise} - * @private - */ - async _unifyTransaction(address, oneAddress, transaction) { - let formattedTime = transaction.timestamp; - try { - formattedTime = BlocksoftUtils.toDate(transaction.timestamp); - } catch (e) { - e.message += - ' timestamp error transaction data ' + JSON.stringify(transaction); - throw e; - } - - const confirmations = (new Date().getTime() - transaction.timestamp) / 60; - const addressAmount = transaction.value; - - let transactionStatus = 'confirming'; - if (confirmations > 2) { - transactionStatus = 'success'; - } - - const isOutcome = - address.toLowerCase() === transaction.from.toLowerCase() || - oneAddress.toLowerCase() === transaction.from.toLowerCase(); - const isIncome = - address.toLowerCase() === transaction.to.toLowerCase() || - oneAddress.toLowerCase() === transaction.to.toLowerCase(); - const tx = { - transactionHash: transaction.ethHash.toLowerCase(), - blockHash: transaction.blockHash, - blockNumber: +transaction.blockNumber, - blockTime: formattedTime, - blockConfirmations: confirmations, - transactionDirection: isOutcome - ? isIncome - ? 'self' - : 'outcome' - : 'income', - addressFrom: isOutcome ? '' : transaction.from, - addressFromBasic: transaction.from.toLowerCase(), - addressTo: isIncome ? '' : transaction.to, - addressToBasic: transaction.to, - addressAmount, - transactionStatus: transactionStatus, - inputValue: transaction.input - }; - const additional = { - nonce: transaction.nonce, - gas: transaction.gas, - gasPrice: transaction.gasPrice, - transactionIndex: transaction.transactionIndex - }; - tx.transactionJson = additional; - tx.transactionFee = BlocksoftUtils.mul( - transaction.gasUsed, - transaction.gasPrice - ).toString(); - - return tx; - } -} +/** + * https://docs.harmony.one/home/developers/api/methods/account-methods/hmy_getbalance + * https://docs.harmony.one/home/developers/api/methods/transaction-related-methods/hmy_gettransactionshistory#api-v2 + */ +import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; +import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings'; +import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; +import OneUtils from '@crypto/blockchains/one/ext/OneUtils'; +import BlocksoftUtils from '@crypto/common/BlocksoftUtils'; + +export default class OneScannerProcessor { + _blocksToConfirm = 10; + private _settings: any; // TODO fix any + + constructor(settings: any) { + this._settings = settings; + } + + /** + * @param {string} address + * @param {*} additionalData + * @param {string} walletHash + * @return {Promise<{balance:*, unconfirmed:*, provider:string}>} + */ + async getBalanceBlockchain( + address: string, + additionalData, + walletHash: string + ) { + const oneAddress = OneUtils.toOneAddress(address); + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' OneScannerProcessor.getBalanceBlockchain started ' + + address + + ' ' + + oneAddress + ); + try { + const apiPath = BlocksoftExternalSettings.getStatic('ONE_SERVER'); + const data = { + jsonrpc: '2.0', + id: 1, + method: 'hmy_getBalance', + params: [oneAddress, 'latest'] + }; + const res = await BlocksoftAxios._request(apiPath, 'POST', data); + if (typeof res.data === 'undefined') { + return false; + } + if (typeof res.data.error !== 'undefined') { + throw new Error(JSON.stringify(res.data.error)); + } + if (typeof res.data.result === 'undefined') { + return false; + } + const balance = BlocksoftUtils.hexToDecimalBigger(res.data.result); + return { + balance, + unconfirmed: 0 + }; + } catch (e) { + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' OneScannerProcessor.getBalanceBlockchain address ' + + address + + ' ' + + oneAddress + + ' error ' + + e.message + ); + return false; + } + } + + /** + * @param {string} scanData.account.address + * @param {string} scanData.account.walletHash + * @return {Promise<[UnifiedTransaction]>} + */ + async getTransactionsBlockchain(scanData: { + account: { address: string; walletHash: string }; + }) { + const { address } = scanData.account; + const oneAddress = OneUtils.toOneAddress(address); + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' OneScannerProcessor.getTransactionsBlockchain started ' + + address + + ' ' + + oneAddress + ); + try { + const apiPath = BlocksoftExternalSettings.getStatic('ONE_SERVER'); + const data = { + jsonrpc: '2.0', + id: 1, + method: 'hmyv2_getTransactionsHistory', + params: [ + { + address: oneAddress, + pageIndex: 0, + pageSize: 20, + fullTx: true, + txType: 'ALL', + order: 'DESC' + } + ] + }; + const res = await BlocksoftAxios._request(apiPath, 'POST', data); + if ( + typeof res.data === 'undefined' || + typeof res.data.result === 'undefined' || + typeof res.data.result.transactions === 'undefined' + ) { + return false; + } + const transactions = []; + for (const tx of res.data.result.transactions) { + const transaction = await this._unifyTransaction( + address, + oneAddress, + tx + ); + if (transaction) { + transactions.push(transaction); + } + } + return transactions; + } catch (e) { + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' OneScannerProcessor.getTransactionsBlockchain address ' + + address + + ' error ' + + e.message + ); + return false; + } + } + + /** + * + * @param {string} address + * @param {string} oneAddress + * @param {Object} transaction + * @param {string} transaction.blockHash + * @param {string} transaction.blockNumber + * @param {string} transaction.ethHash + * @param {string} transaction.from + * @param {string} transaction.gas + * @param {string} transaction.gasPrice + * @param {string} transaction.hash + * @param {string} transaction.input "0x095ea7b3000000000000000000000000d0cb3e55449646c9735d53e83eea5eb7e97a52dcffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + * @param {string} transaction.nonce + * @param {string} transaction.shardID + * @param {string} transaction.timestamp + * @param {string} transaction.to + * @param {string} transaction.toShardID + * @param {string} transaction.value + * @return {Promise} + * @private + */ + async _unifyTransaction(address: string, oneAddress: string, transaction) { + let formattedTime = transaction.timestamp; + try { + formattedTime = BlocksoftUtils.toDate(transaction.timestamp); + } catch (e) { + e.message += + ' timestamp error transaction data ' + JSON.stringify(transaction); + throw e; + } + + const confirmations = (new Date().getTime() - transaction.timestamp) / 60; + const addressAmount = transaction.value; + + let transactionStatus = 'confirming'; + if (confirmations > 2) { + transactionStatus = 'success'; + } + + const isOutcome = + address.toLowerCase() === transaction.from.toLowerCase() || + oneAddress.toLowerCase() === transaction.from.toLowerCase(); + const isIncome = + address.toLowerCase() === transaction.to.toLowerCase() || + oneAddress.toLowerCase() === transaction.to.toLowerCase(); + const tx = { + transactionHash: transaction.ethHash.toLowerCase(), + blockHash: transaction.blockHash, + blockNumber: +transaction.blockNumber, + blockTime: formattedTime, + blockConfirmations: confirmations, + transactionDirection: isOutcome + ? isIncome + ? 'self' + : 'outcome' + : 'income', + addressFrom: isOutcome ? '' : transaction.from, + addressFromBasic: transaction.from.toLowerCase(), + addressTo: isIncome ? '' : transaction.to, + addressToBasic: transaction.to, + addressAmount, + transactionStatus: transactionStatus, + inputValue: transaction.input + }; + const additional = { + nonce: transaction.nonce, + gas: transaction.gas, + gasPrice: transaction.gasPrice, + transactionIndex: transaction.transactionIndex + }; + tx.transactionJson = additional; + tx.transactionFee = BlocksoftUtils.mul( + transaction.gasUsed, + transaction.gasPrice + ).toString(); + + return tx; + } +} diff --git a/crypto/blockchains/one/OneScannerProcessorErc20.js b/crypto/blockchains/one/OneScannerProcessorErc20.ts similarity index 91% rename from crypto/blockchains/one/OneScannerProcessorErc20.js rename to crypto/blockchains/one/OneScannerProcessorErc20.ts index d5923deaf..326f3b76f 100644 --- a/crypto/blockchains/one/OneScannerProcessorErc20.js +++ b/crypto/blockchains/one/OneScannerProcessorErc20.ts @@ -1,216 +1,207 @@ -/** - * https://docs.harmony.one/home/developers/api/methods/account-methods/hmy_getbalance - * https://docs.harmony.one/home/developers/api/methods/transaction-related-methods/hmy_gettransactionshistory#api-v2 - */ -import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; -import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings'; -import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; -import OneUtils from '@crypto/blockchains/one/ext/OneUtils'; -import BlocksoftUtils from '@crypto/common/BlocksoftUtils'; -import EthScannerProcessorErc20 from '@crypto/blockchains/eth/EthScannerProcessorErc20'; - -import config from '@app/config/config'; -import OneTmpDS from './stores/OneTmpDS'; - -const CACHE_TOKENS = {}; - -export default class OneScannerProcessorErc20 extends EthScannerProcessorErc20 { - _blocksToConfirm = 10; - - /** - * @param {string} scanData.account.address - * @param {string} scanData.account.walletHash - * @return {Promise<[UnifiedTransaction]>} - */ - async getTransactionsBlockchain(scanData) { - const { address } = scanData.account; - const oneAddress = OneUtils.toOneAddress(address); - BlocksoftCryptoLog.log( - this._settings.currencyCode + - ' OneScannerProcessorErc20.getTransactionsBlockchain started ' + - address + - ' ' + - oneAddress - ); - try { - CACHE_TOKENS[address] = await OneTmpDS.getCache(address); - const apiPath = BlocksoftExternalSettings.getStatic('ONE_SERVER'); - const data = { - jsonrpc: '2.0', - id: 1, - method: 'hmyv2_getTransactionsHistory', - params: [ - { - address: oneAddress, - pageIndex: 0, - pageSize: 20, - fullTx: true, - txType: 'ALL', - order: 'DESC' - } - ] - }; - const res = await BlocksoftAxios._request(apiPath, 'POST', data); - if ( - typeof res.data === 'undefined' || - typeof res.data.result === 'undefined' || - typeof res.data.result.transactions === 'undefined' - ) { - return false; - } - const transactions = []; - let firstTransaction = false; - for (const tx of res.data.result.transactions) { - if ( - typeof CACHE_TOKENS[address] !== 'undefined' && - typeof CACHE_TOKENS[address][this._tokenAddress] !== 'undefined' - ) { - const diff = tx.timestamp - CACHE_TOKENS[address][this._tokenAddress]; - const diffNow = (new Date().getTime() - tx.timestamp) / 60; - if (diff < -20) { - continue; - } - if (diff <= 0) { - if (diffNow > 100) { - continue; - } - } - } - if (!firstTransaction) { - firstTransaction = tx.timestamp; - } - const transaction = await this._unifyTransaction( - address, - oneAddress, - tx - ); - if (transaction) { - transactions.push(transaction); - } - } - CACHE_TOKENS[address][this._tokenAddress] = firstTransaction; - await OneTmpDS.saveCache(address, this._tokenAddress, firstTransaction); - return transactions; - } catch (e) { - if (config.debug.cryptoErrors) { - console.log( - this._settings.currencyCode + - ' OneScannerProcessorErc20.getTransactionsBlockchain address ' + - address + - ' error ' + - e.message - ); - } - BlocksoftCryptoLog.log( - this._settings.currencyCode + - ' OneScannerProcessorErc20.getTransactionsBlockchain address ' + - address + - ' error ' + - e.message - ); - return false; - } - } - - /** - * - * @param {string} address - * @param {string} oneAddress - * @param {Object} transaction - * @param {string} transaction.blockHash - * @param {string} transaction.blockNumber - * @param {string} transaction.ethHash - * @param {string} transaction.from - * @param {string} transaction.gas - * @param {string} transaction.gasPrice - * @param {string} transaction.hash - * @param {string} transaction.input "0x095ea7b3000000000000000000000000d0cb3e55449646c9735d53e83eea5eb7e97a52dcffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", - * @param {string} transaction.nonce - * @param {string} transaction.shardID - * @param {string} transaction.timestamp - * @param {string} transaction.to - * @param {string} transaction.toShardID - * @param {string} transaction.value - * @return {Promise} - * @private - */ - async _unifyTransaction(address, oneAddress, transaction) { - const contractEvents = await this._token.getPastEvents('Transfer', { - fromBlock: transaction.blockNumber, - toBlock: transaction.blockNumber - }); - if (!contractEvents) { - return false; - } - let foundEventFrom = false; - let foundEventTo = false; - let foundEventSelf = false; - let addressAmount = 0; - for (const tmp of contractEvents) { - if (tmp.transactionHash !== transaction.ethHash) { - continue; - } - if (tmp.returnValues.to.toLowerCase() === address.toLowerCase()) { - if (tmp.returnValues.from.toLowerCase() === address.toLowerCase()) { - foundEventSelf = tmp; - } else { - foundEventTo = tmp; - addressAmount = addressAmount * 1 + tmp.returnValues.value * 1; - } - } else if ( - tmp.returnValues.from.toLowerCase() === address.toLowerCase() - ) { - foundEventFrom = tmp; - addressAmount = addressAmount * 1 - tmp.returnValues.value * 1; - } - } - if (!foundEventSelf && !foundEventTo && !foundEventFrom) { - return false; - } - - let formattedTime = transaction.timestamp; - try { - formattedTime = BlocksoftUtils.toDate(transaction.timestamp); - } catch (e) { - e.message += - ' timestamp error transaction data ' + JSON.stringify(transaction); - throw e; - } - - const confirmations = (new Date().getTime() - transaction.timestamp) / 60; - let transactionStatus = 'confirming'; - if (confirmations > 2) { - transactionStatus = 'success'; - } - - const tx = { - transactionHash: transaction.ethHash.toLowerCase(), - blockHash: transaction.blockHash, - blockNumber: +transaction.blockNumber, - blockTime: formattedTime, - blockConfirmations: confirmations, - transactionDirection: - addressAmount * 1 <= 0 ? (foundEventTo ? 'outcome' : 'self') : 'income', - addressFrom: foundEventFrom ? transaction.from : '', - addressFromBasic: transaction.from.toLowerCase(), - addressTo: foundEventTo ? transaction.to : '', - addressToBasic: transaction.to, - addressAmount, - transactionStatus: transactionStatus, - inputValue: transaction.input - }; - const additional = { - nonce: transaction.nonce, - gas: transaction.gas, - gasPrice: transaction.gasPrice, - transactionIndex: transaction.transactionIndex - }; - tx.transactionJson = additional; - tx.transactionFee = BlocksoftUtils.mul( - transaction.gasUsed, - transaction.gasPrice - ).toString(); - - return tx; - } -} +/** + * https://docs.harmony.one/home/developers/api/methods/account-methods/hmy_getbalance + * https://docs.harmony.one/home/developers/api/methods/transaction-related-methods/hmy_gettransactionshistory#api-v2 + */ +import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; +import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings'; +import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; +import OneUtils from '@crypto/blockchains/one/ext/OneUtils'; +import BlocksoftUtils from '@crypto/common/BlocksoftUtils'; +import EthScannerProcessorErc20 from '@crypto/blockchains/eth/EthScannerProcessorErc20'; +import OneTmpDS from './stores/OneTmpDS'; + +const CACHE_TOKENS = {}; + +export default class OneScannerProcessorErc20 extends EthScannerProcessorErc20 { + _blocksToConfirm = 10; + + /** + * @param {string} scanData.account.address + * @param {string} scanData.account.walletHash + * @return {Promise<[UnifiedTransaction]>} + */ + async getTransactionsBlockchain(scanData: { + account: { address: string; walletHash: string }; + }) { + const { address } = scanData.account; + const oneAddress = OneUtils.toOneAddress(address); + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' OneScannerProcessorErc20.getTransactionsBlockchain started ' + + address + + ' ' + + oneAddress + ); + try { + CACHE_TOKENS[address] = await OneTmpDS.getCache(address); + const apiPath = BlocksoftExternalSettings.getStatic('ONE_SERVER'); + const data = { + jsonrpc: '2.0', + id: 1, + method: 'hmyv2_getTransactionsHistory', + params: [ + { + address: oneAddress, + pageIndex: 0, + pageSize: 20, + fullTx: true, + txType: 'ALL', + order: 'DESC' + } + ] + }; + const res = await BlocksoftAxios._request(apiPath, 'POST', data); + if ( + typeof res.data === 'undefined' || + typeof res.data.result === 'undefined' || + typeof res.data.result.transactions === 'undefined' + ) { + return false; + } + const transactions = []; + let firstTransaction = false; + for (const tx of res.data.result.transactions) { + if ( + typeof CACHE_TOKENS[address] !== 'undefined' && + typeof CACHE_TOKENS[address][this._tokenAddress] !== 'undefined' + ) { + const diff = tx.timestamp - CACHE_TOKENS[address][this._tokenAddress]; + const diffNow = (new Date().getTime() - tx.timestamp) / 60; + if (diff < -20) { + continue; + } + if (diff <= 0) { + if (diffNow > 100) { + continue; + } + } + } + if (!firstTransaction) { + firstTransaction = tx.timestamp; + } + const transaction = await this._unifyTransaction( + address, + oneAddress, + tx + ); + if (transaction) { + transactions.push(transaction); + } + } + CACHE_TOKENS[address][this._tokenAddress] = firstTransaction; + await OneTmpDS.saveCache(address, this._tokenAddress, firstTransaction); + return transactions; + } catch (e) { + BlocksoftCryptoLog.log( + this._settings.currencyCode + + ' OneScannerProcessorErc20.getTransactionsBlockchain address ' + + address + + ' error ' + + e.message + ); + return false; + } + } + + /** + * + * @param {string} address + * @param {string} oneAddress + * @param {Object} transaction + * @param {string} transaction.blockHash + * @param {string} transaction.blockNumber + * @param {string} transaction.ethHash + * @param {string} transaction.from + * @param {string} transaction.gas + * @param {string} transaction.gasPrice + * @param {string} transaction.hash + * @param {string} transaction.input "0x095ea7b3000000000000000000000000d0cb3e55449646c9735d53e83eea5eb7e97a52dcffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + * @param {string} transaction.nonce + * @param {string} transaction.shardID + * @param {string} transaction.timestamp + * @param {string} transaction.to + * @param {string} transaction.toShardID + * @param {string} transaction.value + * @return {Promise} + * @private + */ + async _unifyTransaction(address: string, oneAddress: string, transaction) { + const contractEvents = await this._token.getPastEvents('Transfer', { + fromBlock: transaction.blockNumber, + toBlock: transaction.blockNumber + }); + if (!contractEvents) { + return false; + } + let foundEventFrom = false; + let foundEventTo = false; + let foundEventSelf = false; + let addressAmount = 0; + for (const tmp of contractEvents) { + if (tmp.transactionHash !== transaction.ethHash) { + continue; + } + if (tmp.returnValues.to.toLowerCase() === address.toLowerCase()) { + if (tmp.returnValues.from.toLowerCase() === address.toLowerCase()) { + foundEventSelf = tmp; + } else { + foundEventTo = tmp; + addressAmount = addressAmount * 1 + tmp.returnValues.value * 1; + } + } else if ( + tmp.returnValues.from.toLowerCase() === address.toLowerCase() + ) { + foundEventFrom = tmp; + addressAmount = addressAmount * 1 - tmp.returnValues.value * 1; + } + } + if (!foundEventSelf && !foundEventTo && !foundEventFrom) { + return false; + } + + let formattedTime = transaction.timestamp; + try { + formattedTime = BlocksoftUtils.toDate(transaction.timestamp); + } catch (e) { + e.message += + ' timestamp error transaction data ' + JSON.stringify(transaction); + throw e; + } + + const confirmations = (new Date().getTime() - transaction.timestamp) / 60; + let transactionStatus = 'confirming'; + if (confirmations > 2) { + transactionStatus = 'success'; + } + + const tx = { + transactionHash: transaction.ethHash.toLowerCase(), + blockHash: transaction.blockHash, + blockNumber: +transaction.blockNumber, + blockTime: formattedTime, + blockConfirmations: confirmations, + transactionDirection: + addressAmount * 1 <= 0 ? (foundEventTo ? 'outcome' : 'self') : 'income', + addressFrom: foundEventFrom ? transaction.from : '', + addressFromBasic: transaction.from.toLowerCase(), + addressTo: foundEventTo ? transaction.to : '', + addressToBasic: transaction.to, + addressAmount, + transactionStatus: transactionStatus, + inputValue: transaction.input + }; + const additional = { + nonce: transaction.nonce, + gas: transaction.gas, + gasPrice: transaction.gasPrice, + transactionIndex: transaction.transactionIndex + }; + tx.transactionJson = additional; + tx.transactionFee = BlocksoftUtils.mul( + transaction.gasUsed, + transaction.gasPrice + ).toString(); + + return tx; + } +} diff --git a/crypto/blockchains/one/ext/OneUtils.js b/crypto/blockchains/one/ext/OneUtils.js deleted file mode 100644 index 531b9209a..000000000 --- a/crypto/blockchains/one/ext/OneUtils.js +++ /dev/null @@ -1,239 +0,0 @@ -/** - * @harmony-js/crypto/src/bech32.ts - * https://github.com/harmony-one/sdk/blob/master/packages/harmony-crypto/src/bech32.ts - * const { toBech32 } = require("@harmony-js/crypto"); - * console.log("Using account: " + toBech32("0xxxxx", "one1")) - */ - -const CHARSET = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l' -const GENERATOR = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3] - -const polymod = (values) => { - let chk = 1 - // tslint:disable-next-line - for (let p = 0; p < values.length; ++p) { - const top = chk >> 25 - chk = ((chk & 0x1ffffff) << 5) ^ values[p] - for (let i = 0; i < 5; ++i) { - if ((top >> i) & 1) { - chk ^= GENERATOR[i] - } - } - } - return chk -} - -const hrpExpand = (hrp) => { - const ret = [] - let p - for (p = 0; p < hrp.length; ++p) { - ret.push(hrp.charCodeAt(p) >> 5) - } - ret.push(0) - for (p = 0; p < hrp.length; ++p) { - ret.push(hrp.charCodeAt(p) & 31) - } - return Buffer.from(ret) -} - -function createChecksum(hrp, data) { - const values = Buffer.concat([ - Buffer.from(hrpExpand(hrp)), - data, - Buffer.from([0, 0, 0, 0, 0, 0]) - ]) - // var values = hrpExpand(hrp).concat(data).concat([0, 0, 0, 0, 0, 0]); - const mod = polymod(values) ^ 1 - const ret = [] - for (let p = 0; p < 6; ++p) { - ret.push((mod >> (5 * (5 - p))) & 31) - } - return Buffer.from(ret) -} - -const bech32Encode = (hrp, data) => { - const combined = Buffer.concat([data, createChecksum(hrp, data)]) - let ret = hrp + '1' - // tslint:disable-next-line - for (let p = 0; p < combined.length; ++p) { - ret += CHARSET.charAt(combined[p]) - } - return ret -} - - -const convertBits = ( - data, - fromWidth, - toWidth, - pad = true -) => { - let acc = 0 - let bits = 0 - const ret = [] - const maxv = (1 << toWidth) - 1 - // tslint:disable-next-line - for (let p = 0; p < data.length; ++p) { - const value = data[p] - if (value < 0 || value >> fromWidth !== 0) { - return null - } - acc = (acc << fromWidth) | value - bits += fromWidth - while (bits >= toWidth) { - bits -= toWidth - ret.push((acc >> bits) & maxv) - } - } - - if (pad) { - if (bits > 0) { - ret.push((acc << (toWidth - bits)) & maxv) - } - } else if (bits >= fromWidth || (acc << (toWidth - bits)) & maxv) { - return null - } - - return Buffer.from(ret) -} - -const bech32Decode = (bechString) => { - let p; - let hasLower = false; - let hasUpper = false; - for (p = 0; p < bechString.length; ++p) { - if (bechString.charCodeAt(p) < 33 || bechString.charCodeAt(p) > 126) { - return null; - } - if (bechString.charCodeAt(p) >= 97 && bechString.charCodeAt(p) <= 122) { - hasLower = true; - } - if (bechString.charCodeAt(p) >= 65 && bechString.charCodeAt(p) <= 90) { - hasUpper = true; - } - } - if (hasLower && hasUpper) { - return null; - } - bechString = bechString.toLowerCase(); - const pos = bechString.lastIndexOf('1'); - if (pos < 1 || pos + 7 > bechString.length || bechString.length > 90) { - return null; - } - const hrp = bechString.substring(0, pos); - const data = []; - for (p = pos + 1; p < bechString.length; ++p) { - const d = CHARSET.indexOf(bechString.charAt(p)); - if (d === -1) { - return null; - } - data.push(d); - } - - try { - if (!verifyChecksum(hrp, Buffer.from(data))) { - return null; - } - } catch (e) { - e.message += ' in verifyChecksum' - throw e - } - - return { hrp, data: Buffer.from(data.slice(0, data.length - 6)) }; -}; - -function verifyChecksum(hrp, data) { - return polymod(Buffer.concat([hrpExpand(hrp), data])) === 1; -} - -const toChecksumAddress = (address)=> { - if (typeof address !== 'string' || !address.match(/^0x[0-9A-Fa-f]{40}$/)) { - throw new Error('invalid address ' + address); - } - - address = address.toLowerCase(); - - const chars = address.substring(2).split(''); - - let hashed = new Uint8Array(40); - for (let i = 0; i < 40; i++) { - hashed[i] = chars[i].charCodeAt(0); - } - // hashed = bytes.arrayify(keccak256(hashed)) || hashed; - - for (let i = 0; i < 40; i += 2) { - if (hashed[i >> 1] >> 4 >= 8) { - chars[i] = chars[i].toUpperCase(); - } - if ((hashed[i >> 1] & 0x0f) >= 8) { - chars[i + 1] = chars[i + 1].toUpperCase(); - } - } - - return '0x' + chars.join(''); -}; - - -export default { - isOneAddress(address) { - if (typeof address === 'undefined' || typeof address.match === 'undefined') { - return false - } - try { - return !!address.match(/^one1[qpzry9x8gf2tvdw0s3jn54khce6mua7l]{38}/); - } catch (e) { - e.message += ' in match - address ' + JSON.stringify(address) - throw e - } - }, - - toOneAddress(address, useHRP = 'one') { - if (address.indexOf('one') === 0) { - return address - } - - const prepAddr = address.replace('0x', '').toLowerCase() - - const addrBz = convertBits( Buffer.from(prepAddr, 'hex'), 8, 5) - - if (addrBz === null) { - throw new Error('Could not convert byte Buffer to 5-bit Buffer') - } - - return bech32Encode(useHRP, addrBz) - }, - - fromOneAddress(address, useHRP = 'one') { - let res - try { - res = bech32Decode(address) - } catch (e) { - e.message += ' in bech32Decode - address ' + JSON.stringify(address) - throw e - } - - if (res === null) { - throw new Error('Invalid bech32 address') - } - - const { hrp, data } = res - - if (hrp !== useHRP) { - throw new Error(`Expected hrp to be ${useHRP} but got ${hrp}`) - } - - const buf = convertBits(data, 5, 8, false) - - if (buf === null) { - throw new Error('Could not convert buffer to bytes') - } - - try { - const tmp = toChecksumAddress('0x' + buf.toString('hex')) - return tmp - } catch (e) { - e.message += ' in toChecksumAddress' - throw e - } - } -} diff --git a/crypto/blockchains/one/ext/OneUtils.ts b/crypto/blockchains/one/ext/OneUtils.ts new file mode 100644 index 000000000..97db1f3d2 --- /dev/null +++ b/crypto/blockchains/one/ext/OneUtils.ts @@ -0,0 +1,235 @@ +/** + * @harmony-js/crypto/src/bech32.ts + * https://github.com/harmony-one/sdk/blob/master/packages/harmony-crypto/src/bech32.ts + * const { toBech32 } = require("@harmony-js/crypto"); + * console.log("Using account: " + toBech32("0xxxxx", "one1")) + */ + +const CHARSET = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l'; +const GENERATOR = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3]; + +const polymod = (values) => { + let chk = 1; + // tslint:disable-next-line + for (let p = 0; p < values.length; ++p) { + const top = chk >> 25; + chk = ((chk & 0x1ffffff) << 5) ^ values[p]; + for (let i = 0; i < 5; ++i) { + if ((top >> i) & 1) { + chk ^= GENERATOR[i]; + } + } + } + return chk; +}; + +const hrpExpand = (hrp: string) => { + const ret = []; + let p; + for (p = 0; p < hrp.length; ++p) { + ret.push(hrp.charCodeAt(p) >> 5); + } + ret.push(0); + for (p = 0; p < hrp.length; ++p) { + ret.push(hrp.charCodeAt(p) & 31); + } + return Buffer.from(ret); +}; + +function createChecksum(hrp: string, data) { + const values = Buffer.concat([ + Buffer.from(hrpExpand(hrp)), + data, + Buffer.from([0, 0, 0, 0, 0, 0]) + ]); + // var values = hrpExpand(hrp).concat(data).concat([0, 0, 0, 0, 0, 0]); + const mod = polymod(values) ^ 1; + const ret = []; + for (let p = 0; p < 6; ++p) { + ret.push((mod >> (5 * (5 - p))) & 31); + } + return Buffer.from(ret); +} + +const bech32Encode = (hrp: string, data) => { + const combined = Buffer.concat([data, createChecksum(hrp, data)]); + let ret = hrp + '1'; + // tslint:disable-next-line + for (let p = 0; p < combined.length; ++p) { + ret += CHARSET.charAt(combined[p]); + } + return ret; +}; + +const convertBits = (data, fromWidth: number, toWidth: number, pad = true) => { + let acc = 0; + let bits = 0; + const ret = []; + const maxv = (1 << toWidth) - 1; + // tslint:disable-next-line + for (let p = 0; p < data.length; ++p) { + const value = data[p]; + if (value < 0 || value >> fromWidth !== 0) { + return null; + } + acc = (acc << fromWidth) | value; + bits += fromWidth; + while (bits >= toWidth) { + bits -= toWidth; + ret.push((acc >> bits) & maxv); + } + } + + if (pad) { + if (bits > 0) { + ret.push((acc << (toWidth - bits)) & maxv); + } + } else if (bits >= fromWidth || (acc << (toWidth - bits)) & maxv) { + return null; + } + + return Buffer.from(ret); +}; + +const bech32Decode = (bechString: string) => { + let p; + let hasLower = false; + let hasUpper = false; + for (p = 0; p < bechString.length; ++p) { + if (bechString.charCodeAt(p) < 33 || bechString.charCodeAt(p) > 126) { + return null; + } + if (bechString.charCodeAt(p) >= 97 && bechString.charCodeAt(p) <= 122) { + hasLower = true; + } + if (bechString.charCodeAt(p) >= 65 && bechString.charCodeAt(p) <= 90) { + hasUpper = true; + } + } + if (hasLower && hasUpper) { + return null; + } + bechString = bechString.toLowerCase(); + const pos = bechString.lastIndexOf('1'); + if (pos < 1 || pos + 7 > bechString.length || bechString.length > 90) { + return null; + } + const hrp = bechString.substring(0, pos); + const data = []; + for (p = pos + 1; p < bechString.length; ++p) { + const d = CHARSET.indexOf(bechString.charAt(p)); + if (d === -1) { + return null; + } + data.push(d); + } + + try { + if (!verifyChecksum(hrp, Buffer.from(data))) { + return null; + } + } catch (e) { + e.message += ' in verifyChecksum'; + throw e; + } + + return { hrp, data: Buffer.from(data.slice(0, data.length - 6)) }; +}; + +function verifyChecksum(hrp: string, data) { + return polymod(Buffer.concat([hrpExpand(hrp), data])) === 1; +} + +const toChecksumAddress = (address: string) => { + if (typeof address !== 'string' || !address.match(/^0x[0-9A-Fa-f]{40}$/)) { + throw new Error('invalid address ' + address); + } + + address = address.toLowerCase(); + + const chars = address.substring(2).split(''); + + const hashed = new Uint8Array(40); + for (let i = 0; i < 40; i++) { + hashed[i] = chars[i].charCodeAt(0); + } + // hashed = bytes.arrayify(keccak256(hashed)) || hashed; + + for (let i = 0; i < 40; i += 2) { + if (hashed[i >> 1] >> 4 >= 8) { + chars[i] = chars[i].toUpperCase(); + } + if ((hashed[i >> 1] & 0x0f) >= 8) { + chars[i + 1] = chars[i + 1].toUpperCase(); + } + } + + return '0x' + chars.join(''); +}; + +export default { + isOneAddress(address: string) { + if ( + typeof address === 'undefined' || + typeof address.match === 'undefined' + ) { + return false; + } + try { + return !!address.match(/^one1[qpzry9x8gf2tvdw0s3jn54khce6mua7l]{38}/); + } catch (e) { + e.message += ' in match - address ' + JSON.stringify(address); + throw e; + } + }, + + toOneAddress(address: string, useHRP = 'one') { + if (address.indexOf('one') === 0) { + return address; + } + + const prepAddr = address.replace('0x', '').toLowerCase(); + + const addrBz = convertBits(Buffer.from(prepAddr, 'hex'), 8, 5); + + if (addrBz === null) { + throw new Error('Could not convert byte Buffer to 5-bit Buffer'); + } + + return bech32Encode(useHRP, addrBz); + }, + + fromOneAddress(address: string, useHRP = 'one') { + let res; + try { + res = bech32Decode(address); + } catch (e) { + e.message += ' in bech32Decode - address ' + JSON.stringify(address); + throw e; + } + + if (res === null) { + throw new Error('Invalid bech32 address'); + } + + const { hrp, data } = res; + + if (hrp !== useHRP) { + throw new Error(`Expected hrp to be ${useHRP} but got ${hrp}`); + } + + const buf = convertBits(data, 5, 8, false); + + if (buf === null) { + throw new Error('Could not convert buffer to bytes'); + } + + try { + const tmp = toChecksumAddress('0x' + buf.toString('hex')); + return tmp; + } catch (e) { + e.message += ' in toChecksumAddress'; + throw e; + } + } +}; diff --git a/crypto/blockchains/one/stores/OneTmpDS.js b/crypto/blockchains/one/stores/OneTmpDS.js deleted file mode 100644 index bf7742b09..000000000 --- a/crypto/blockchains/one/stores/OneTmpDS.js +++ /dev/null @@ -1,45 +0,0 @@ - -import Database from '@app/appstores/DataSource/Database'; - -const tableName = ' transactions_scanners_tmp' - -class OneTmpDS { - /** - * @type {string} - * @private - */ - _currencyCode = 'ONE' - - async getCache(address) { - const res = await Database.query(` - SELECT tmp_key, tmp_sub_key, tmp_val - FROM ${tableName} - WHERE currency_code='${this._currencyCode}' - AND address='${address}' - AND tmp_key='last_tx' - `) - const tmp = {} - if (res.array) { - let row - for (row of res.array) { - tmp[row.tmp_sub_key] = row.tmp_val - } - } - return tmp - } - - async saveCache(address, subKey, value) { - const now = new Date().toISOString() - const prepared = [{ - currency_code : this._currencyCode, - address : address, - tmp_key : 'last_tx', - tmp_sub_key : subKey, - tmp_val : value, - created_at : now - }] - await Database.setTableName(tableName).setInsertData({insertObjs : prepared}).insert() - } -} - -export default new OneTmpDS() diff --git a/crypto/blockchains/one/stores/OneTmpDS.ts b/crypto/blockchains/one/stores/OneTmpDS.ts new file mode 100644 index 000000000..8846e1484 --- /dev/null +++ b/crypto/blockchains/one/stores/OneTmpDS.ts @@ -0,0 +1,52 @@ +/* eslint-disable camelcase */ +import { DatabaseTable } from '@appTypes'; +import { Database, TransactionScannersTmpDBModel } from '@database'; + +const tableName = DatabaseTable.TransactionScannersTmp; + +class OneTmpDS { + /** + * @type {string} + * @private + */ + _currencyCode = 'ONE'; + + async getCache(address: string) { + const res = (await Database.unsafeRawQuery( + tableName, + ` + SELECT tmp_key AS tmpKey, tmp_sub_key AS tmpSubKey, tmp_val AS tmpVal + FROM ${tableName} + WHERE currency_code='${this._currencyCode}' + AND address='${address}' + AND tmp_key='last_tx' + ` + )) as TransactionScannersTmpDBModel[]; + const tmp = {}; + if (res) { + let row; + for (row of res) { + // @ts-ignore + tmp[row.tmpSubKey] = row.tmpVal; + } + } + return tmp; + } + + async saveCache(address: string, subKey: string, value: string) { + const now = new Date().getTime(); + const prepared = [ + { + currency_code: this._currencyCode, + address: address, + tmp_key: 'last_tx', + tmp_sub_key: subKey, + tmp_val: value, + created_at: now + } + ]; + await Database.createModel(tableName, prepared); + } +} + +export default new OneTmpDS(); From 7c3323a5049acc4c804d757f3588de8e990d8c35 Mon Sep 17 00:00:00 2001 From: JavidHaji-zada Date: Thu, 3 Aug 2023 20:01:01 +0400 Subject: [PATCH 028/509] converted BN & CryptoLog --- .../BlocksoftBalances/BlocksoftBalances.js | 10 +- .../BlocksoftInvoice/BlocksoftInvoice.js | 14 +- crypto/actions/BlocksoftKeys/BlocksoftKeys.js | 34 ++-- .../BlocksoftKeysForRef.js | 10 +- .../BlocksoftKeysStorage.js | 4 +- .../BlocksoftSecrets/BlocksoftSecrets.js | 12 +- .../BlocksoftTransfer/BlocksoftTransfer.ts | 52 +++--- .../BlocksoftTransferPrivate.ts | 4 +- .../bch/providers/BchSendProvider.ts | 4 +- .../bch/providers/BchUnspentsProvider.ts | 6 +- crypto/blockchains/bnb/BnbScannerProcessor.ts | 11 +- .../blockchains/bnb/BnbTransferProcessor.ts | 14 +- .../blockchains/bnb/basic/BnbNetworkPrices.js | 6 +- .../bnb/basic/BnbTxSendProvider.ts | 6 +- .../bnb_smart/basic/BnbSmartNetworkPrices.js | 6 +- crypto/blockchains/bsv/BsvScannerProcessor.js | 20 +-- .../bsv/providers/BsvSendProvider.ts | 6 +- .../bsv/providers/BsvUnspentsProvider.ts | 4 +- crypto/blockchains/btc/BtcScannerProcessor.ts | 20 +-- .../btc/basic/BtcFindAddressFunction.ts | 2 +- .../blockchains/btc/basic/BtcNetworkPrices.ts | 24 +-- .../btc/providers/BtcUnspentsProvider.ts | 22 +-- crypto/blockchains/btc/tx/BtcTxBuilder.ts | 8 +- .../blockchains/btc/tx/BtcTxInputsOutputs.ts | 12 +- .../btc_test/BtcTestScannerProcessor.js | 8 +- .../btc_test/providers/BtcTestSendProvider.ts | 6 +- .../providers/BtcTestUnspentsProvider.ts | 6 +- .../blockchains/doge/DogeScannerProcessor.ts | 10 +- .../blockchains/doge/DogeTransferProcessor.ts | 46 ++--- .../doge/basic/DogeFindAddressFunction.ts | 2 +- crypto/blockchains/doge/basic/DogeLogs.ts | 8 +- .../doge/basic/DogeNetworkPrices.ts | 4 +- .../doge/providers/DogeSendProvider.ts | 6 +- .../doge/providers/DogeUnspentsProvider.ts | 20 +-- crypto/blockchains/doge/stores/DogeRawDS.ts | 6 +- crypto/blockchains/doge/tx/DogeTxBuilder.ts | 36 ++-- .../doge/tx/DogeTxInputsOutputs.ts | 8 +- crypto/blockchains/eth/EthScannerProcessor.ts | 46 ++--- .../eth/EthScannerProcessorErc20.ts | 10 +- .../blockchains/eth/EthTransferProcessor.ts | 104 ++++++------ .../eth/EthTransferProcessorErc20.ts | 32 ++-- crypto/blockchains/eth/apis/EthNftMatic.ts | 4 +- crypto/blockchains/eth/apis/EthNftOpensea.ts | 10 +- crypto/blockchains/eth/basic/EthBasic.ts | 16 +- .../blockchains/eth/basic/EthNetworkPrices.ts | 28 +-- .../eth/basic/EthTxSendProvider.ts | 46 ++--- .../eth/forks/EthScannerProcessorSoul.ts | 6 +- crypto/blockchains/eth/stores/EthRawDS.ts | 10 +- crypto/blockchains/eth/stores/EthTmpDS.ts | 2 +- crypto/blockchains/fio/FioScannerProcessor.ts | 8 +- crypto/blockchains/fio/FioSdkWrapper.ts | 4 +- crypto/blockchains/fio/FioUtils.ts | 20 +-- .../metis/MetisScannerProcessor.ts | 4 +- crypto/blockchains/one/OneScannerProcessor.ts | 10 +- .../one/OneScannerProcessorErc20.ts | 6 +- crypto/blockchains/sol/SolScannerProcessor.ts | 12 +- .../blockchains/sol/SolScannerProcessorSpl.ts | 6 +- crypto/blockchains/sol/SolTokenProcessor.ts | 4 +- .../blockchains/sol/SolTransferProcessor.ts | 30 ++-- .../sol/SolTransferProcessorSpl.ts | 14 +- crypto/blockchains/sol/ext/SolStakeUtils.ts | 12 +- crypto/blockchains/sol/ext/SolUtils.ts | 8 +- crypto/blockchains/trx/TrxScannerProcessor.ts | 26 +-- .../blockchains/trx/TrxTransferProcessor.ts | 54 +++--- .../trx/basic/TrxNodeInfoProvider.ts | 8 +- .../trx/basic/TrxTransactionsProvider.ts | 8 +- .../trx/basic/TrxTransactionsTrc20Provider.ts | 6 +- .../trx/basic/TrxTrongridProvider.ts | 8 +- .../trx/basic/TrxTronscanProvider.ts | 6 +- crypto/blockchains/trx/ext/TronStakeUtils.ts | 14 +- .../trx/providers/TrxSendProvider.ts | 14 +- .../blockchains/usdt/UsdtScannerProcessor.ts | 25 ++- .../blockchains/usdt/UsdtTransferProcessor.ts | 6 +- crypto/blockchains/usdt/tx/UsdtTxBuilder.ts | 4 +- .../usdt/tx/UsdtTxInputsOutputs.ts | 12 +- crypto/blockchains/vet/VetScannerProcessor.ts | 14 +- .../blockchains/vet/VetTransferProcessor.ts | 22 +-- .../waves/WavesScannerProcessor.ts | 6 +- .../waves/WavesScannerProcessorErc20.ts | 6 +- .../waves/WavesTransferProcessor.ts | 16 +- .../providers/WavesTransactionsProvider.ts | 4 +- crypto/blockchains/xlm/XlmScannerProcessor.ts | 6 +- .../blockchains/xlm/XlmTransferProcessor.ts | 18 +- .../xlm/basic/XlmTxSendProvider.ts | 18 +- crypto/blockchains/xmr/XmrAddressProcessor.js | 4 +- crypto/blockchains/xmr/XmrAddressProcessor.ts | 4 +- crypto/blockchains/xmr/XmrScannerProcessor.js | 14 +- crypto/blockchains/xmr/XmrScannerProcessor.ts | 14 +- .../blockchains/xmr/XmrTransferProcessor.ts | 30 ++-- .../blockchains/xmr/ext/MoneroUtilsParser.ts | 8 +- .../xmr/providers/XmrSendProvider.ts | 4 +- .../xmr/providers/XmrUnspentsProvider.js | 13 +- .../xmr/providers/XmrUnspentsProvider.ts | 13 +- crypto/blockchains/xrp/XrpScannerProcessor.js | 6 +- crypto/blockchains/xrp/XrpScannerProcessor.ts | 6 +- .../blockchains/xrp/XrpTransferProcessor.ts | 18 +- .../xrp/basic/XrpDataRippleProvider.js | 10 +- .../xrp/basic/XrpDataRippleProvider.ts | 10 +- .../xrp/basic/XrpDataScanProvider.js | 6 +- .../xrp/basic/XrpDataScanProvider.ts | 6 +- .../xrp/basic/XrpTxSendProvider.ts | 28 ++- crypto/blockchains/xvg/XvgScannerProcessor.js | 18 +- .../xvg/basic/XvgFindAddressFunction.ts | 2 +- .../xvg/providers/XvgSendProvider.ts | 8 +- .../xvg/providers/XvgUnspentsProvider.ts | 8 +- crypto/common/{BlocksoftBN.js => AirDAOBN.ts} | 28 +-- crypto/common/AirDAOCryptoLog.ts | 156 +++++++++++++++++ crypto/common/AirDAOExternalSettings.ts | 12 +- crypto/common/AirDAOPrivateKeysUtils.ts | 14 +- crypto/common/BlocksoftAxios.js | 42 ++--- crypto/common/BlocksoftCryptoLog.js | 160 ------------------ ...{bip44-constants.js => bip44-constants.ts} | 61 ++++--- ...rks-constants.js => networks-constants.ts} | 0 crypto/common/ext/scam-seeds.js | 4 - crypto/common/ext/scam-seeds.ts | 4 + src/daemons/view/UpdateAccountListDaemon.js | 2 +- src/lib/AirDAOKeys.ts | 4 +- src/lib/BlocksoftKeys.js | 34 ++-- src/lib/BlocksoftKeysForRef.js | 10 +- src/lib/helpers/AirDAOKeys.ts | 34 ++-- 120 files changed, 998 insertions(+), 1027 deletions(-) rename crypto/common/{BlocksoftBN.js => AirDAOBN.ts} (72%) create mode 100644 crypto/common/AirDAOCryptoLog.ts delete mode 100644 crypto/common/BlocksoftCryptoLog.js rename crypto/common/ext/{bip44-constants.js => bip44-constants.ts} (94%) rename crypto/common/ext/{networks-constants.js => networks-constants.ts} (100%) delete mode 100644 crypto/common/ext/scam-seeds.js create mode 100644 crypto/common/ext/scam-seeds.ts diff --git a/crypto/actions/BlocksoftBalances/BlocksoftBalances.js b/crypto/actions/BlocksoftBalances/BlocksoftBalances.js index f37d64928..7a48eb123 100644 --- a/crypto/actions/BlocksoftBalances/BlocksoftBalances.js +++ b/crypto/actions/BlocksoftBalances/BlocksoftBalances.js @@ -3,7 +3,7 @@ * @version 0.5 */ import BlocksoftDispatcher from '../../blockchains/BlocksoftDispatcher'; -import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '../../common/AirDAOCryptoLog'; import BlocksoftDict from '../../common/BlocksoftDict'; import BlocksoftExternalSettings from '@crypto/common/AirDAOExternalSettings'; @@ -79,7 +79,7 @@ class BlocksoftBalances { * @return {Promise<{balance:*, frozen: *, frozenEnergy: *, balanceAvailable: *, balanceStaked: *, provider:*, unconfirmed:*, addresses : *, balanceScanBlock : *}>} */ async getBalance(source) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'BlocksoftBalances.getBalance ' + this._data.currencyCode + ' ' + @@ -112,7 +112,7 @@ class BlocksoftBalances { e.code = 'ERROR_SYSTEM'; throw e; } - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'BlocksoftBalances.getBalance ' + this._data.currencyCode + ' ' + @@ -154,7 +154,7 @@ class BlocksoftBalances { e.code = 'ERROR_SYSTEM'; throw e; } - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'BlocksoftBalances.getResources ' + this._data.currencyCode + ' ' + @@ -182,7 +182,7 @@ class BlocksoftBalances { e.code = 'ERROR_SYSTEM'; throw e; } - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'BlocksoftBalances.isMultisigBlockchain ' + this._data.currencyCode + ' ' + diff --git a/crypto/actions/BlocksoftInvoice/BlocksoftInvoice.js b/crypto/actions/BlocksoftInvoice/BlocksoftInvoice.js index c7f910643..dd52a5654 100644 --- a/crypto/actions/BlocksoftInvoice/BlocksoftInvoice.js +++ b/crypto/actions/BlocksoftInvoice/BlocksoftInvoice.js @@ -2,7 +2,7 @@ * @author Ksu * @version 0.5 */ -import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '../../common/AirDAOCryptoLog'; import BlocksoftDispatcher from '../../blockchains/BlocksoftDispatcher'; class BlocksoftInvoice { @@ -74,18 +74,18 @@ class BlocksoftInvoice { } let res = ''; try { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( `BlocksoftInvoice.createInvoice ${currencyCode} started`, this._data ); res = await this._processor[currencyCode].createInvoice(this._data); - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( `BlocksoftInvoice.createInvoice ${currencyCode} finished`, res ); } catch (e) { // noinspection ES6MissingAwait - BlocksoftCryptoLog.err( + AirDAOCryptoLog.err( `BlocksoftInvoice.createInvoice ${currencyCode} error ` + e.message, e.data ? e.data : e ); @@ -102,19 +102,19 @@ class BlocksoftInvoice { } let res = ''; try { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( `BlocksoftInvoice.checkInvoice ${currencyCode} started`, this._data ); res = await this._processor[currencyCode].checkInvoice(hash, this._data); - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( `BlocksoftInvoice.checkInvoice ${currencyCode} finished`, res ); } catch (e) { if (e.message.indexOf('not a valid invoice') === -1) { // noinspection ES6MissingAwait - BlocksoftCryptoLog.err( + AirDAOCryptoLog.err( `BlocksoftInvoice.checkInvoice ${currencyCode} error ` + e.message, e.data ? e.data : e ); diff --git a/crypto/actions/BlocksoftKeys/BlocksoftKeys.js b/crypto/actions/BlocksoftKeys/BlocksoftKeys.js index b50a4898a..6802b20a7 100644 --- a/crypto/actions/BlocksoftKeys/BlocksoftKeys.js +++ b/crypto/actions/BlocksoftKeys/BlocksoftKeys.js @@ -2,7 +2,7 @@ * @author Ksu * @version 0.5 */ -import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; import BlocksoftDict from '@crypto/common/BlocksoftDict'; import BlocksoftKeysUtils from '@crypto/actions/BlocksoftKeys/BlocksoftKeysUtils'; @@ -38,7 +38,7 @@ class BlocksoftKeys { * @return {Promise<{mnemonic: string, hash: string}>} */ async newMnemonic(size = 256) { - BlocksoftCryptoLog.log(`BlocksoftKeys newMnemonic called`); + AirDAOCryptoLog.log(`BlocksoftKeys newMnemonic called`); /* let mnemonic = false if (USE_TON) { @@ -50,7 +50,7 @@ class BlocksoftKeys { mnemonic = testMnemonic break } - BlocksoftCryptoLog.log('step ' + i + ' not valid ton ' + testMnemonic) + AirDAOCryptoLog.log('step ' + i + ' not valid ton ' + testMnemonic) } if (!mnemonic) { throw new Error('TON Mnemonic is not validating') @@ -64,7 +64,7 @@ class BlocksoftKeys { random = Buffer.from(random, 'base64'); const mnemonic = bip39.entropyToMnemonic(random); const hash = BlocksoftKeysUtils.hashMnemonic(mnemonic); - BlocksoftCryptoLog.log(`BlocksoftKeys newMnemonic finished`); + AirDAOCryptoLog.log(`BlocksoftKeys newMnemonic finished`); return { mnemonic, hash }; } @@ -73,7 +73,7 @@ class BlocksoftKeys { * @return {Promise} */ async validateMnemonic(mnemonic) { - BlocksoftCryptoLog.log(`BlocksoftKeys validateMnemonic called`); + AirDAOCryptoLog.log(`BlocksoftKeys validateMnemonic called`); if (await BlocksoftKeysScam.isScamMnemonic(mnemonic)) { throw new Error(strings('settings.walletList.scamImport')); } @@ -99,7 +99,7 @@ class BlocksoftKeys { const logData = { ...data }; if (typeof logData.mnemonic !== 'undefined') logData.mnemonic = '***'; - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( `BlocksoftKeys discoverAddresses called from ${source}`, JSON.stringify(logData).substring(0, 200) ); @@ -187,7 +187,7 @@ class BlocksoftKeys { hasDerivations = true; } if (typeof CACHE[hexesCache] === 'undefined' || hasDerivations) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( `BlocksoftKeys will discover ${settings.addressProcessor}` ); let root = false; @@ -205,7 +205,7 @@ class BlocksoftKeys { } // BIP32 Extended Private Key to check - uncomment // let childFirst = root.derivePath('m/44\'/2\'/0\'/0') - // BlocksoftCryptoLog.log(childFirst.toBase58()) + // AirDAOCryptoLog.log(childFirst.toBase58()) /** * @type {EthAddressProcessor|BtcAddressProcessor} @@ -224,7 +224,7 @@ class BlocksoftKeys { let currentToIndex = toIndex; let currentFullTree = fullTree; - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( `BlocksoftKeys discoverAddresses ${currencyCode} currentFromIndex.1 ${currentFromIndex}` ); if (hasDerivations) { @@ -261,7 +261,7 @@ class BlocksoftKeys { currentFromIndex = maxIndex * 1 + 1; currentToIndex = currentFromIndex * 1 + 10; currentFullTree = true; - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( `BlocksoftKeys ${currencyCode} discoverAddresses currentFromIndex.2 ${currentFromIndex}` ); } @@ -355,7 +355,7 @@ class BlocksoftKeys { ETH_CACHE[mnemonicCache] = results[currencyCode][0]; } } else { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( `BlocksoftKeys will be from cache ${settings.addressProcessor}` ); results[currencyCode] = CACHE[hexesCache]; @@ -364,12 +364,12 @@ class BlocksoftKeys { } } } - BlocksoftCryptoLog.log(`BlocksoftKeys discoverAddresses finished`); + AirDAOCryptoLog.log(`BlocksoftKeys discoverAddresses finished`); return results; } async getSeedCached(mnemonic) { - BlocksoftCryptoLog.log(`BlocksoftKeys bip39MnemonicToSeed started`); + AirDAOCryptoLog.log(`BlocksoftKeys bip39MnemonicToSeed started`); const mnemonicCache = mnemonic.toLowerCase(); if (typeof CACHE[mnemonicCache] === 'undefined') { CACHE[mnemonicCache] = await BlocksoftKeysUtils.bip39MnemonicToSeed( @@ -377,7 +377,7 @@ class BlocksoftKeys { ); } const seed = CACHE[mnemonicCache]; // will be rechecked on saving - BlocksoftCryptoLog.log(`BlocksoftKeys bip39MnemonicToSeed ended`); + AirDAOCryptoLog.log(`BlocksoftKeys bip39MnemonicToSeed ended`); return seed; } @@ -458,17 +458,17 @@ class BlocksoftKeys { path = `m/49'/0'/0'`; version = 0x049d7cb2; } - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'BlocksoftKeys.discoverXpub derive started ' + JSON.stringify(path) ); const rootWallet = root.derivePath(path); - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'BlocksoftKeys.discoverXpub derived, bs58 started ' + JSON.stringify(path) ); const res = bs58check.encode( serialize(rootWallet, version, rootWallet.publicKey) ); - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'BlocksoftKeys.discoverXpub res ' + JSON.stringify(path), res ); diff --git a/crypto/actions/BlocksoftKeysForRef/BlocksoftKeysForRef.js b/crypto/actions/BlocksoftKeysForRef/BlocksoftKeysForRef.js index ddd890190..b9bfe4b72 100644 --- a/crypto/actions/BlocksoftKeysForRef/BlocksoftKeysForRef.js +++ b/crypto/actions/BlocksoftKeysForRef/BlocksoftKeysForRef.js @@ -2,7 +2,7 @@ * @author Ksu * @version 0.5 */ -import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '../../common/AirDAOCryptoLog'; import BlocksoftKeysForRefServerSide from './BlocksoftKeysForRefServerSide'; import BlocksoftKeys from '../BlocksoftKeys/BlocksoftKeys'; import BlocksoftDispatcher from '../../blockchains/BlocksoftDispatcher'; @@ -22,14 +22,14 @@ class BlocksoftKeysForRef { if (typeof logData.mnemonic !== 'undefined') logData.mnemonic = '***'; if (typeof CACHE[mnemonicCache] !== 'undefined') return CACHE[mnemonicCache]; - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( `BlocksoftKeysForRef discoverPublicAndPrivate called ` + JSON.stringify(logData) ); let result = BlocksoftKeys.getEthCached(mnemonicCache); if (!result) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( `BlocksoftKeysForRef discoverPublicAndPrivate no cache ` + JSON.stringify(logData) ); @@ -48,7 +48,7 @@ class BlocksoftKeysForRef { if (index === 0) { BlocksoftKeys.setEthCached(data.mnemonic, result); } - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( `BlocksoftKeysForRef discoverPublicAndPrivate finished no cache ` + JSON.stringify(logData) ); @@ -57,7 +57,7 @@ class BlocksoftKeysForRef { result.cashbackToken = BlocksoftKeysForRefServerSide.addressToToken( result.address ); - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( `BlocksoftKeysForRef discoverPublicAndPrivate finished ` + JSON.stringify(logData) ); diff --git a/crypto/actions/BlocksoftKeysStorage/BlocksoftKeysStorage.js b/crypto/actions/BlocksoftKeysStorage/BlocksoftKeysStorage.js index 3ab4e828f..dda6e591a 100644 --- a/crypto/actions/BlocksoftKeysStorage/BlocksoftKeysStorage.js +++ b/crypto/actions/BlocksoftKeysStorage/BlocksoftKeysStorage.js @@ -6,7 +6,7 @@ import 'react-native'; import * as Keychain from 'react-native-keychain'; -import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; import config from '@app/config/config'; import BlocksoftDict from '@crypto/common/BlocksoftDict'; @@ -260,7 +260,7 @@ export class BlocksoftKeysStorage { const logData = { ...newMnemonic }; if (typeof logData.mnemonic !== 'undefined') logData.mnemonic = '***'; - BlocksoftCryptoLog.log('BlocksoftKeysStorage saveMnemonic', logData); + AirDAOCryptoLog.log('BlocksoftKeysStorage saveMnemonic', logData); if (!newMnemonic.hash) { throw new Error('unique hash required ' + JSON.stringify(newMnemonic)); diff --git a/crypto/actions/BlocksoftSecrets/BlocksoftSecrets.js b/crypto/actions/BlocksoftSecrets/BlocksoftSecrets.js index 1240e6e33..390b0a753 100644 --- a/crypto/actions/BlocksoftSecrets/BlocksoftSecrets.js +++ b/crypto/actions/BlocksoftSecrets/BlocksoftSecrets.js @@ -2,7 +2,7 @@ * @author Ksu * @version 0.11 */ -import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '../../common/AirDAOCryptoLog'; import BlocksoftDispatcher from '../../blockchains/BlocksoftDispatcher'; class BlocksoftSecrets { @@ -33,16 +33,12 @@ class BlocksoftSecrets { let res = ''; try { - BlocksoftCryptoLog.log( - `BlocksoftSecrets.getWords ${currencyCode} started` - ); + AirDAOCryptoLog.log(`BlocksoftSecrets.getWords ${currencyCode} started`); res = await this._processor[currencyCode].getWords(params); - BlocksoftCryptoLog.log( - `BlocksoftSecrets.getWords ${currencyCode} finished` - ); + AirDAOCryptoLog.log(`BlocksoftSecrets.getWords ${currencyCode} finished`); } catch (e) { // noinspection ES6MissingAwait - BlocksoftCryptoLog.err( + AirDAOCryptoLog.err( `BlocksoftSecrets.getWords ${currencyCode} error ` + e.message, e.data ? e.data : e ); diff --git a/crypto/actions/BlocksoftTransfer/BlocksoftTransfer.ts b/crypto/actions/BlocksoftTransfer/BlocksoftTransfer.ts index f9d8b752b..62d04ee2c 100644 --- a/crypto/actions/BlocksoftTransfer/BlocksoftTransfer.ts +++ b/crypto/actions/BlocksoftTransfer/BlocksoftTransfer.ts @@ -2,7 +2,7 @@ * @author Ksu * @version 0.20 */ -import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '../../common/AirDAOCryptoLog'; import { BlocksoftBlockchainTypes } from '../../blockchains/BlocksoftBlockchainTypes'; import { BlocksoftTransferDispatcher } from '../../blockchains/BlocksoftTransferDispatcher'; import { BlocksoftTransferPrivate } from './BlocksoftTransferPrivate'; @@ -42,7 +42,7 @@ export namespace BlocksoftTransfer { data.isTransferAll = true; let transferAllCount; try { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( `${data.currencyCode} BlocksoftTransfer.getTransferAllBalance started ${data.addressFrom} ` ); const processor = BlocksoftTransferDispatcher.getTransferProcessor( @@ -61,7 +61,7 @@ export namespace BlocksoftTransfer { data.currencyCode ).getTransferAllBalance(data, privateData, additionalDataTmp); - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( `${data.currencyCode} BlocksoftTransfer.getTransferAllBalance got ${data.addressFrom} result is ok` ); if (config.debug.sendLogs) { @@ -76,7 +76,7 @@ export namespace BlocksoftTransfer { e.message.indexOf('UI_') === -1 ) { // noinspection ES6MissingAwait - BlocksoftCryptoLog.err( + AirDAOCryptoLog.err( `${data.currencyCode} BlocksoftTransfer.getTransferAllBalance ` + e.message ); @@ -138,7 +138,7 @@ export namespace BlocksoftTransfer { data.derivationPath = data.derivationPath.replace(/quote/g, "'"); let feesCount; try { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( `${data.currencyCode} BlocksoftTransfer.getFeeRate started ${data.addressFrom} ` ); const processor = BlocksoftTransferDispatcher.getTransferProcessor( @@ -165,15 +165,13 @@ export namespace BlocksoftTransfer { console.log('BlocksoftTransfer.getFeeRate error ', e); } if (typeof e.message === 'undefined') { - await BlocksoftCryptoLog.log( - 'BlocksoftTransfer.getFeeRate strange error' - ); + await AirDAOCryptoLog.log('BlocksoftTransfer.getFeeRate strange error'); } else if ( e.message.indexOf('SERVER_RESPONSE_') === -1 && e.message.indexOf('UI_') === -1 ) { // noinspection ES6MissingAwait - await BlocksoftCryptoLog.err( + await AirDAOCryptoLog.err( `${data.currencyCode} BlocksoftTransfer.getFeeRate error ` + data.addressFrom + ' => ' + @@ -190,7 +188,7 @@ export namespace BlocksoftTransfer { e.message ); } else { - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( 'BlocksoftTransfer.getFeeRate inner error ' + e.message ); throw e; @@ -292,7 +290,7 @@ export namespace BlocksoftTransfer { e.message.indexOf('SERVER_RESPONSE_') === -1 && e.message.indexOf('UI_') === -1 ) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( `${data.currencyCode} BlocksoftTransfer.sendTx error ` + e.message ); } @@ -301,7 +299,7 @@ export namespace BlocksoftTransfer { let txResult; try { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( `${data.currencyCode} BlocksoftTransfer.sendTx started ${data.addressFrom} ` ); const processor = BlocksoftTransferDispatcher.getTransferProcessor( @@ -312,7 +310,7 @@ export namespace BlocksoftTransfer { additionalData ); txResult = await processor.sendTx(data, privateData, uiData); - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( `${data.currencyCode} BlocksoftTransfer.sendTx got ${data.addressFrom} result is ok` ); if ( @@ -341,7 +339,7 @@ export namespace BlocksoftTransfer { e.message.indexOf('connect() timed') === -1 ) { // noinspection ES6MissingAwait - BlocksoftCryptoLog.err( + AirDAOCryptoLog.err( `${data.currencyCode} BlocksoftTransfer.sendTx ` + e.message ); } @@ -358,7 +356,7 @@ export namespace BlocksoftTransfer { (data.currencyCode === 'TRX' || data.currencyCode.indexOf('TRX_') !== -1) ) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( `${data.currencyCode} BlocksoftTransfer.sendTx ` + e.message ); } else { @@ -376,7 +374,7 @@ export namespace BlocksoftTransfer { ): Promise { let txResult = ''; try { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( `${data.currencyCode} BlocksoftTransfer.sendRawTx started ${data.address} ` ); const processor = BlocksoftTransferDispatcher.getTransferProcessor( @@ -386,7 +384,7 @@ export namespace BlocksoftTransfer { return 'none'; } txResult = await processor.sendRawTx(data, rawTxHex, txRBF, logData); - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( `${data.currencyCode} BlocksoftTransfer.sendRawTx got ${data.address} result is ok` ); } catch (e) { @@ -396,7 +394,7 @@ export namespace BlocksoftTransfer { e ); } - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( `${data.currencyCode} BlocksoftTransfer.sendRawTx error ` + e.message ); throw e; @@ -410,7 +408,7 @@ export namespace BlocksoftTransfer { ): Promise { let txResult = false; try { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( `${data.currencyCode} BlocksoftTransfer.setMissing started ${data.address} ` ); const processor = BlocksoftTransferDispatcher.getTransferProcessor( @@ -420,11 +418,11 @@ export namespace BlocksoftTransfer { return false; } txResult = await processor.setMissingTx(data, dbTransaction); - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( `${data.currencyCode} BlocksoftTransfer.setMissing got ${data.address} result is ok` ); } catch (e) { - BlocksoftCryptoLog.err( + AirDAOCryptoLog.err( `${data.currencyCode} BlocksoftTransfer.setMissing error ` + e.message ); } @@ -438,7 +436,7 @@ export namespace BlocksoftTransfer { ): boolean { let txResult = false; try { - // BlocksoftCryptoLog.log(`BlocksoftTransfer.canRBF ${data.currencyCode} from ${source} started ${data.address} `) + // AirDAOCryptoLog.log(`BlocksoftTransfer.canRBF ${data.currencyCode} from ${source} started ${data.address} `) const processor = BlocksoftTransferDispatcher.getTransferProcessor( typeof data.currencyCode !== 'undefined' ? data.currencyCode @@ -448,9 +446,9 @@ export namespace BlocksoftTransfer { return false; } txResult = processor.canRBF(data, dbTransaction, source); - // BlocksoftCryptoLog.log(`BlocksoftTransfer.canRBF ${data.currencyCode} from ${source} got ${data.address} result is ${JSON.stringify(txResult)}`) + // AirDAOCryptoLog.log(`BlocksoftTransfer.canRBF ${data.currencyCode} from ${source} got ${data.address} result is ${JSON.stringify(txResult)}`) } catch (e) { - BlocksoftCryptoLog.err( + AirDAOCryptoLog.err( `${data.currencyCode} BlocksoftTransfer.canRBF error from ${source} ` + e.message ); @@ -461,7 +459,7 @@ export namespace BlocksoftTransfer { export const checkSendAllModal = function (data: { currencyCode: any }) { let checkSendAllModalResult = false; try { - // BlocksoftCryptoLog.log(`BlocksoftTransfer.checkSendAllModal ${data.currencyCode} started `) + // AirDAOCryptoLog.log(`BlocksoftTransfer.checkSendAllModal ${data.currencyCode} started `) const processor = BlocksoftTransferDispatcher.getTransferProcessor( data.currencyCode ); @@ -469,9 +467,9 @@ export namespace BlocksoftTransfer { return false; } checkSendAllModalResult = processor.checkSendAllModal(data); - // BlocksoftCryptoLog.log(`BlocksoftTransfer.checkSendAllModal ${data.currencyCode} got result is ok ` + JSON.stringify(checkSendAllModalResult)) + // AirDAOCryptoLog.log(`BlocksoftTransfer.checkSendAllModal ${data.currencyCode} got result is ok ` + JSON.stringify(checkSendAllModalResult)) } catch (e) { - BlocksoftCryptoLog.err( + AirDAOCryptoLog.err( `${data.currencyCode} BlocksoftTransfer.checkSendAllModal error ` + e.message ); diff --git a/crypto/actions/BlocksoftTransfer/BlocksoftTransferPrivate.ts b/crypto/actions/BlocksoftTransfer/BlocksoftTransferPrivate.ts index 12b06c2eb..e125602b6 100644 --- a/crypto/actions/BlocksoftTransfer/BlocksoftTransferPrivate.ts +++ b/crypto/actions/BlocksoftTransfer/BlocksoftTransferPrivate.ts @@ -4,7 +4,7 @@ */ import { BlocksoftBlockchainTypes } from '../../blockchains/BlocksoftBlockchainTypes'; import BlocksoftPrivateKeysUtils from '../../common/AirDAOPrivateKeysUtils'; -import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '../../common/AirDAOCryptoLog'; import BlocksoftKeysStorage from '../BlocksoftKeysStorage/BlocksoftKeysStorage'; export namespace BlocksoftTransferPrivate { @@ -61,7 +61,7 @@ export namespace BlocksoftTransferPrivate { privateData.privateKey = result.privateKey; // @ts-ignore privateData.addedData = result.addedData; - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( `${data.currencyCode} BlocksoftTransferPrivate.initTransferPrivate finished for ${data.addressFrom}` ); return privateData; diff --git a/crypto/blockchains/bch/providers/BchSendProvider.ts b/crypto/blockchains/bch/providers/BchSendProvider.ts index a5d2c30ac..4bd92dfaa 100644 --- a/crypto/blockchains/bch/providers/BchSendProvider.ts +++ b/crypto/blockchains/bch/providers/BchSendProvider.ts @@ -2,7 +2,7 @@ * @version 0.20 */ import { AirDAOBlockchainTypes } from '../../AirDAOBlockchainTypes'; -import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '../../../common/AirDAOCryptoLog'; import BlocksoftAxios from '../../../common/BlocksoftAxios'; import DogeSendProvider from '../../doge/providers/DogeSendProvider'; @@ -18,7 +18,7 @@ export default class BchSendProvider txRBF: any, logData: any ): Promise<{ transactionHash: string; transactionJson: any }> { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' BchSendProvider.sendTx ' + subtitle + diff --git a/crypto/blockchains/bch/providers/BchUnspentsProvider.ts b/crypto/blockchains/bch/providers/BchUnspentsProvider.ts index 1fe38b96f..d9c9b20fc 100644 --- a/crypto/blockchains/bch/providers/BchUnspentsProvider.ts +++ b/crypto/blockchains/bch/providers/BchUnspentsProvider.ts @@ -5,7 +5,7 @@ * https://rest.bitcoin.com/v2/address/utxo/bitcoincash:qz6qh4304stgwpqxp6gwsucma30fewp7z5cs4yuvdf */ import { AirDAOBlockchainTypes } from '../../AirDAOBlockchainTypes'; -import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '../../../common/AirDAOCryptoLog'; import BlocksoftAxios from '../../../common/BlocksoftAxios'; import BtcCashUtils from '../ext/BtcCashUtils'; import DogeUnspentsProvider from '../../doge/providers/DogeUnspentsProvider'; @@ -28,7 +28,7 @@ export default class BchUnspentsProvider // do nothing } // TODO check if log is necessary @ts-ignore - // BlocksoftCryptoLog.log( + // AirDAOCryptoLog.log( // this._settings.currencyCode + ' BchUnspentsProvider.getUnspents started', // address // ); @@ -36,7 +36,7 @@ export default class BchUnspentsProvider const link = this._apiPath + address; const res = (await BlocksoftAxios.getWithoutBraking(link)) as any; if (!res || !res.data || typeof res.data === 'undefined') { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' BchUnspentsProvider.getUnspents nothing loaded for address ' + address + diff --git a/crypto/blockchains/bnb/BnbScannerProcessor.ts b/crypto/blockchains/bnb/BnbScannerProcessor.ts index f0c0674bf..1a31e9867 100644 --- a/crypto/blockchains/bnb/BnbScannerProcessor.ts +++ b/crypto/blockchains/bnb/BnbScannerProcessor.ts @@ -3,7 +3,7 @@ * https://docs.binance.org/api-reference/dex-api/paths.html#apiv1transactions */ import BlocksoftAxios from '../../common/BlocksoftAxios'; -import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '../../common/AirDAOCryptoLog'; import BlocksoftUtils from '../../common/AirDAOUtils'; import BlocksoftExternalSettings from '../../common/AirDAOExternalSettings'; @@ -37,7 +37,7 @@ export default class BnbScannerProcessor { } } } else { - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( 'BnbScannerProcessor.getBalanceBlockchain ' + address + ' no actual balance ' + @@ -60,10 +60,7 @@ export default class BnbScannerProcessor { */ async getTransactionsBlockchain(scanData) { const address = scanData.account.address.trim(); - BlocksoftCryptoLog.log( - 'BnbScannerProcessor.getTransactions started', - address - ); + AirDAOCryptoLog.log('BnbScannerProcessor.getTransactions started', address); const apiServer = await BlocksoftExternalSettings.getStatic('BNB_SERVER'); @@ -82,7 +79,7 @@ export default class BnbScannerProcessor { } const transactions = await this._unifyTransactions(address, res.data.tx); - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'BnbScannerProcessor.getTransactions finished', address ); diff --git a/crypto/blockchains/bnb/BnbTransferProcessor.ts b/crypto/blockchains/bnb/BnbTransferProcessor.ts index a43d5d775..c61462e1e 100644 --- a/crypto/blockchains/bnb/BnbTransferProcessor.ts +++ b/crypto/blockchains/bnb/BnbTransferProcessor.ts @@ -3,7 +3,7 @@ * https://docs.binance.org/smart-chain/developer/create-wallet.html * https://docs.binance.org/guides/concepts/encoding/amino-example.html#transfer */ -import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '../../common/AirDAOCryptoLog'; import { AirDAOBlockchainTypes } from '../AirDAOBlockchainTypes'; import { BnbTxSendProvider } from './basic/BnbTxSendProvider'; @@ -57,7 +57,7 @@ export default class BnbTransferProcessor ): Promise { const balance = data.amount; // @ts-ignore - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + ' BnbTransferProcessor.getTransferAllBalance ', data.addressFrom + ' => ' + balance @@ -104,7 +104,7 @@ export default class BnbTransferProcessor throw new Error('BNB transaction required addressTo'); } - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + ' BnbTransferProcessor.sendTx start' ); @@ -115,7 +115,7 @@ export default class BnbTransferProcessor throw new Error(e.message + ' in BNB getPrepared'); } // @ts-ignore - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + ' BnbTransferProcessor.sendTx tx', transaction ); @@ -126,7 +126,7 @@ export default class BnbTransferProcessor } catch (e) { throw new Error(e.message + ' in BNB serializeTx'); } - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + ' BnbTransferProcessor.sendTx raw', raw ); @@ -149,7 +149,7 @@ export default class BnbTransferProcessor throw e; } } - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + ' BnbTransferProcessor.sendTx result', result ); @@ -172,7 +172,7 @@ export default class BnbTransferProcessor !result[0].ok || !result[0].hash ) { - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + ' BnbTransferProcessor.sendTx result', result ); diff --git a/crypto/blockchains/bnb/basic/BnbNetworkPrices.js b/crypto/blockchains/bnb/basic/BnbNetworkPrices.js index 391a21283..f5aec05b4 100644 --- a/crypto/blockchains/bnb/basic/BnbNetworkPrices.js +++ b/crypto/blockchains/bnb/basic/BnbNetworkPrices.js @@ -5,7 +5,7 @@ * https://dex.binance.org/api/v1/fees * https://docs.binance.org/api-reference/dex-api/paths.html#apiv1fees */ -import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '../../../common/AirDAOCryptoLog'; import BlocksoftAxios from '../../../common/BlocksoftAxios'; import BlocksoftExternalSettings from '../../../common/AirDAOExternalSettings'; @@ -24,7 +24,7 @@ class BnbNetworkPrices { return CACHE_FEES; } - BlocksoftCryptoLog.log('BnbNetworkPricesProvider.getFees no cache load'); + AirDAOCryptoLog.log('BnbNetworkPricesProvider.getFees no cache load'); const apiServer = await BlocksoftExternalSettings.getStatic('BNB_SERVER'); const link = `${apiServer}/api/v1/fees`; @@ -50,7 +50,7 @@ class BnbNetworkPrices { } } } catch (e) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'BnbNetworkPricesProvider.getOnlyFees loaded prev fee as error' + e.message ); diff --git a/crypto/blockchains/bnb/basic/BnbTxSendProvider.ts b/crypto/blockchains/bnb/basic/BnbTxSendProvider.ts index e30bff39f..6fe695ca4 100644 --- a/crypto/blockchains/bnb/basic/BnbTxSendProvider.ts +++ b/crypto/blockchains/bnb/basic/BnbTxSendProvider.ts @@ -2,7 +2,7 @@ /* eslint-disable @typescript-eslint/no-var-requires */ import BlocksoftAxios from '../../../common/BlocksoftAxios'; import { AirDAOBlockchainTypes } from '../../AirDAOBlockchainTypes'; -import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '../../../common/AirDAOCryptoLog'; import BlocksoftUtils from '../../../common/AirDAOUtils'; import BlocksoftExternalSettings from '../../../common/AirDAOExternalSettings'; @@ -311,12 +311,12 @@ export class BnbTxSendProvider { } } } catch (e) { - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( 'BnbTransferProcessor.sendTx error ' + e.message ); throw e; } - await BlocksoftCryptoLog.log('BnbTransferProcessor.sendTx result ', result); + await AirDAOCryptoLog.log('BnbTransferProcessor.sendTx result ', result); return result; } } diff --git a/crypto/blockchains/bnb_smart/basic/BnbSmartNetworkPrices.js b/crypto/blockchains/bnb_smart/basic/BnbSmartNetworkPrices.js index 9e0106767..d9318ef4f 100644 --- a/crypto/blockchains/bnb_smart/basic/BnbSmartNetworkPrices.js +++ b/crypto/blockchains/bnb_smart/basic/BnbSmartNetworkPrices.js @@ -1,7 +1,7 @@ /** * @version 0.20 */ -import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '../../../common/AirDAOCryptoLog'; import BlocksoftAxios from '../../../common/BlocksoftAxios'; import BlocksoftUtils from '../../../common/AirDAOUtils'; import BlocksoftExternalSettings from '../../../common/AirDAOExternalSettings'; @@ -30,7 +30,7 @@ class BnbSmartNetworkPrices { } const tmp = etherscanApiPath.split('/'); const feesApiPath = `https://${tmp[2]}/api?module=proxy&action=eth_gasPrice&apikey=YourApiKeyToken`; - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( mainCurrencyCode + ' BnbSmartNetworkPricesProvider.getFees no cache load' ); try { @@ -55,7 +55,7 @@ class BnbSmartNetworkPrices { } } } catch (e) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( mainCurrencyCode + ' BnbSmartNetworkPricesProvider.getOnlyFees loaded prev fee as error' + e.message diff --git a/crypto/blockchains/bsv/BsvScannerProcessor.js b/crypto/blockchains/bsv/BsvScannerProcessor.js index 38f292396..ee4bd80b4 100644 --- a/crypto/blockchains/bsv/BsvScannerProcessor.js +++ b/crypto/blockchains/bsv/BsvScannerProcessor.js @@ -3,7 +3,7 @@ */ import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; -import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; import BlocksoftUtils from '@crypto/common/AirDAOUtils'; import BlocksoftPrettyNumbers from '@crypto/common/AirDAOPrettyNumbers'; import BsvTmpDS from '@crypto/blockchains/bsv/stores/BsvTmpDS'; @@ -80,7 +80,7 @@ export default class BsvScannerProcessor { throw new Error(e.message + ' in BsvTmpDS.getCache'); } } - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'BsvScannerProcessor.getTransactions started ' + address ); const linkTxs = `${API_PATH}/address/${address}/history`; @@ -99,7 +99,7 @@ export default class BsvScannerProcessor { if (row.height * 1 > 0) { basicTxs.push(row); } else { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'BsvScannerProcessor.getTransactions strange one ' + JSON.stringify(row) ); @@ -124,7 +124,7 @@ export default class BsvScannerProcessor { } if (bulkTxs.length > 0) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'BsvScannerProcessor.getTransactions will ask ' + JSON.stringify(bulkTxs) ); @@ -152,7 +152,7 @@ export default class BsvScannerProcessor { transactions = await this._unifyTransactions(address, [], otherTxs); } - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'BsvScannerProcessor.getTransactions finished ' + address ); return transactions; @@ -165,7 +165,7 @@ export default class BsvScannerProcessor { if (typeof CACHE_TXS[vin.txid] === 'undefined') { vins.push(vin.txid); if (vins.length > 19) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'BsvScannerProcessor.getTransactions will ask vins ' + JSON.stringify(vins) ); @@ -183,7 +183,7 @@ export default class BsvScannerProcessor { } } if (vins && vins.length > 0) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'BsvScannerProcessor.getTransactions will ask vins1 ' + JSON.stringify(vins) ); @@ -272,7 +272,7 @@ export default class BsvScannerProcessor { } } if (vins && vins.length > 0) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'BsvScannerProcessor.getTransactions will ask vins2 ' + JSON.stringify(vins) ); @@ -293,7 +293,7 @@ export default class BsvScannerProcessor { let othersAddressOut = false; for (let vin of transaction.vin) { if (typeof CACHE_TXS[vin.txid] === 'undefined') { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'BsvScannerProcessor _unifyTransaction error cant find vin ' + vin.txid + ' for tx ' + @@ -317,7 +317,7 @@ export default class BsvScannerProcessor { } } if (!found) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'BsvScannerProcessor _unifyTransaction error cant find vin ' + vin.txid + ' n ' + diff --git a/crypto/blockchains/bsv/providers/BsvSendProvider.ts b/crypto/blockchains/bsv/providers/BsvSendProvider.ts index 4da3a008b..e951e2c23 100644 --- a/crypto/blockchains/bsv/providers/BsvSendProvider.ts +++ b/crypto/blockchains/bsv/providers/BsvSendProvider.ts @@ -3,7 +3,7 @@ */ import { AirDAOBlockchainTypes } from '@crypto/blockchains/AirDAOBlockchainTypes'; import DogeSendProvider from '@crypto/blockchains/doge/providers/DogeSendProvider'; -import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; export default class BsvSendProvider @@ -16,7 +16,7 @@ export default class BsvSendProvider txRBF: any, logData: any ): Promise<{ transactionHash: string; transactionJson: any }> { - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + ' BsvSendProvider.sendTx ' + subtitle + @@ -39,7 +39,7 @@ export default class BsvSendProvider logData.error = e.message; await this._checkError(hex, subtitle, txRBF, logData); } catch (e2) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' DogeSendProvider.send proxy error errorTx ' + e2.message diff --git a/crypto/blockchains/bsv/providers/BsvUnspentsProvider.ts b/crypto/blockchains/bsv/providers/BsvUnspentsProvider.ts index cb0246bf2..c10e2c0fb 100644 --- a/crypto/blockchains/bsv/providers/BsvUnspentsProvider.ts +++ b/crypto/blockchains/bsv/providers/BsvUnspentsProvider.ts @@ -4,7 +4,7 @@ import { BlocksoftBlockchainTypes } from '@crypto/blockchains/BlocksoftBlockchainTypes'; import DogeUnspentsProvider from '@crypto/blockchains/doge/providers/DogeUnspentsProvider'; import BtcCashUtils from '@crypto/blockchains/bch/ext/BtcCashUtils'; -import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; import BlocksoftExternalSettings from '@crypto/common/AirDAOExternalSettings'; import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; export default class BsvUnspentsProvider @@ -29,7 +29,7 @@ export default class BsvUnspentsProvider address: string ): Promise { // @ts-ignore - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' BsvUnspentsProvider.getUnspents started ' + address diff --git a/crypto/blockchains/btc/BtcScannerProcessor.ts b/crypto/blockchains/btc/BtcScannerProcessor.ts index e70238130..3c6cab175 100644 --- a/crypto/blockchains/btc/BtcScannerProcessor.ts +++ b/crypto/blockchains/btc/BtcScannerProcessor.ts @@ -4,7 +4,7 @@ import BlocksoftUtils from '@crypto/common/AirDAOUtils'; import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; -import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; import BlocksoftExternalSettings from '@crypto/common/AirDAOExternalSettings'; import BtcFindAddressFunction from './basic/BtcFindAddressFunction'; @@ -63,7 +63,7 @@ export default class BtcScannerProcessor { CACHE[address].provider = 'trezor-cache'; return CACHE[address]; } - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'BtcScannerProcessor._get ' + address + ' from ' + source + ' started' ); @@ -225,7 +225,7 @@ export default class BtcScannerProcessor { * @return {Promise<{balance:*, unconfirmed:*, provider:string}>} */ async getBalanceBlockchain(address, data, walletHash, source = '') { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' BtcScannerProcessor.getBalance started ' + address @@ -254,13 +254,13 @@ export default class BtcScannerProcessor { const withBalances = typeof scanData.withBalances !== 'undefined' && scanData.withBalances; if (!withBalances) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' BtcScannerProcessor.getAddresses started withoutBalances (KSU!)', address ); } else { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' BtcScannerProcessor.getAddresses started withBalances', address @@ -299,7 +299,7 @@ export default class BtcScannerProcessor { } } } catch (e) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' BtcScannerProcessor.getAddresses load from all addresses error ' + e.message @@ -318,7 +318,7 @@ export default class BtcScannerProcessor { const address = scanData.account.address.trim(); const data = scanData.additional; - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' BtcScannerProcessor.getTransactions started ' + address @@ -375,13 +375,13 @@ export default class BtcScannerProcessor { e ); } - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' BtcScannerProcessor.getTransactions load from all addresses error ' + e.message ); } - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' BtcScannerProcessor.getTransactions loaded from ' + res.provider + @@ -442,7 +442,7 @@ export default class BtcScannerProcessor { transactions.push(transaction); } } - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' BtcScannerProcessor.getTransactions finished ' + address + diff --git a/crypto/blockchains/btc/basic/BtcFindAddressFunction.ts b/crypto/blockchains/btc/basic/BtcFindAddressFunction.ts index b24208d38..95b78bed2 100644 --- a/crypto/blockchains/btc/basic/BtcFindAddressFunction.ts +++ b/crypto/blockchains/btc/basic/BtcFindAddressFunction.ts @@ -19,7 +19,7 @@ * @returns {Promise<{from: string, to: string, value: number, direction: string}>} * @constructor */ -import BlocksoftBN from '../../../common/BlocksoftBN'; +import BlocksoftBN from '../../../common/AirDAOBN'; export default async function BtcFindAddressFunction( indexedAddresses: string[], diff --git a/crypto/blockchains/btc/basic/BtcNetworkPrices.ts b/crypto/blockchains/btc/basic/BtcNetworkPrices.ts index 3e4243d20..44506c738 100644 --- a/crypto/blockchains/btc/basic/BtcNetworkPrices.ts +++ b/crypto/blockchains/btc/basic/BtcNetworkPrices.ts @@ -3,7 +3,7 @@ * @version 0.20 **/ import { AirDAOBlockchainTypes } from '../../AirDAOBlockchainTypes'; -import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '../../../common/AirDAOCryptoLog'; import BlocksoftAxios from '../../../common/BlocksoftAxios'; import BlocksoftExternalSettings from '../../../common/AirDAOExternalSettings'; @@ -51,7 +51,7 @@ export default class BtcNetworkPrices speed_blocks_6: number; speed_blocks_12: number; }> { - BlocksoftCryptoLog.log('BtcNetworkPricesProvider ' + currencyCode); + AirDAOCryptoLog.log('BtcNetworkPricesProvider ' + currencyCode); const logData = { currencyCode, source: 'fromCache', @@ -61,7 +61,7 @@ export default class BtcNetworkPrices const now = new Date().getTime(); if (CACHE_FEES_BTC && now - CACHE_FEES_BTC_TIME < CACHE_VALID_TIME) { // @ts-ignore - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'BtcNetworkPricesProvider ' + currencyCode + ' used cache ' + @@ -70,7 +70,7 @@ export default class BtcNetworkPrices return CACHE_FEES_BTC; } - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'BtcNetworkPricesProvider ' + currencyCode + ' no cache load' ); @@ -97,7 +97,7 @@ export default class BtcNetworkPrices } } catch (e) {} - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'BtcNetworkPricesProvider lastBlock ' + lastBlock + ' mempool ' + @@ -130,7 +130,7 @@ export default class BtcNetworkPrices } } // @ts-ignore - BlocksoftCryptoLog.log('BtcNetworkPricesProvider CACHE_FEES', { + AirDAOCryptoLog.log('BtcNetworkPricesProvider CACHE_FEES', { CACHE_PREV_DATA, CACHE_PREV_PREV_DATA, ...logData @@ -144,18 +144,18 @@ export default class BtcNetworkPrices if (cachedWithTime.fastestFee < CACHE_PREV_PREV_DATA.fastestFee) { cachedWithTime.fastestFee = CACHE_PREV_PREV_DATA.fastestFee; // @ts-ignore - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'BtcNetworkPricesProvider change as block 1 minute ago and fastest no ok - used prev ', CACHE_PREV_PREV_DATA ); } else if (CACHE_PREV_DATA.mempoolSize < 10000) { cachedWithTime.fastestFee = cachedWithTime.fastestFee * 1.5; - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'BtcNetworkPricesProvider change as block 1 minute ago and fastest no ok - mempool is small' ); } else { cachedWithTime.fastestFee = cachedWithTime.fastestFee * 3; - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'BtcNetworkPricesProvider change as block 1 minute ago and fastest no ok - mempool is ok' ); } @@ -164,7 +164,7 @@ export default class BtcNetworkPrices if (cachedWithTime.fastestFee < CACHE_PREV_PREV_DATA.fastestFee) { cachedWithTime.fastestFee = CACHE_PREV_PREV_DATA.fastestFee; // @ts-ignore - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'BtcNetworkPricesProvider change as block 5 minute ago and fastest no ok - used prev ', CACHE_PREV_PREV_DATA ); @@ -258,7 +258,7 @@ export default class BtcNetworkPrices CACHE_FEES_BTC_TIME = new Date().getTime(); if (CACHE_FEES_BTC.speed_blocks_2 > 0) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'BtcNetworkPricesProvider ' + currencyCode + ' new cache fees', CACHE_FEES_BTC ); @@ -287,7 +287,7 @@ function addMultiply(blocks, fee, externalSettings) { ) { CACHE_FEES_BTC[key] = BlocksoftUtils.mul(fee, externalSettings.BTC_MULTI_V3) * 1; - BlocksoftCryptoLog.log('BtcNetworkPricesProvider addMultiply result', { + AirDAOCryptoLog.log('BtcNetworkPricesProvider addMultiply result', { blocks, fee, mul: externalSettings.BTC_MULTI_V3, diff --git a/crypto/blockchains/btc/providers/BtcUnspentsProvider.ts b/crypto/blockchains/btc/providers/BtcUnspentsProvider.ts index a882de27f..a0556b22e 100644 --- a/crypto/blockchains/btc/providers/BtcUnspentsProvider.ts +++ b/crypto/blockchains/btc/providers/BtcUnspentsProvider.ts @@ -8,7 +8,7 @@ import DogeUnspentsProvider from '../../doge/providers/DogeUnspentsProvider'; // import Database from '@app/appstores/DataSource/Database'; import { Database } from '@database'; -import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '../../../common/AirDAOCryptoLog'; import BlocksoftDict from '@crypto/common/BlocksoftDict'; import { Q } from '@nozbe/watermelondb'; import { DatabaseTable } from '@appTypes'; @@ -30,7 +30,7 @@ export default class BtcUnspentsProvider BlocksoftDict.CurrenciesForTests[mainCurrencyCode + '_SEGWIT'] .addressPrefix; - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( currencyCode + ' ' + mainCurrencyCode + @@ -78,7 +78,7 @@ export default class BtcUnspentsProvider row.address.indexOf(segwitPrefix) === 0 ? segwitPrefix : row.address.substr(0, 1); - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( currencyCode + ' ' + mainCurrencyCode + @@ -98,7 +98,7 @@ export default class BtcUnspentsProvider // @ts-ignore CACHE_FOR_CHANGE[walletHash][prefix] = row.address; // @ts-ignore - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( currencyCode + ' ' + mainCurrencyCode + @@ -126,7 +126,7 @@ export default class BtcUnspentsProvider )) as AccountDBModel[]; for (const row of res) { // @ts-ignore - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( currencyCode + '/' + mainCurrencyCode + @@ -172,7 +172,7 @@ export default class BtcUnspentsProvider // @ts-ignore let found = ''; for (const key in CACHE_FOR_CHANGE[walletHash]) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'CACHE_FOR_CHANGE[walletHash][key]', key + '_' + CACHE_FOR_CHANGE[walletHash][key] ); @@ -254,7 +254,7 @@ export default class BtcUnspentsProvider row.address.indexOf(segwitPrefix) === 0 ? segwitPrefix : row.address.substring(0, 1); - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + ' ' + mainCurrencyCode + @@ -276,7 +276,7 @@ export default class BtcUnspentsProvider // @ts-ignore CACHE_FOR_CHANGE[walletHash][prefix] = row.address; // @ts-ignore - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + ' ' + mainCurrencyCode + @@ -305,7 +305,7 @@ export default class BtcUnspentsProvider const walletHash = row.hash.hash; const unspents = await super.getUnspents(row.address); // @ts-ignore - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + '/' + mainCurrencyCode + @@ -339,7 +339,7 @@ export default class BtcUnspentsProvider } // @ts-ignore if (totalUnspents.length > 10) { - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + ' ' + mainCurrencyCode + @@ -350,7 +350,7 @@ export default class BtcUnspentsProvider totalUnspents.slice(0, 10) ); } else { - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + ' ' + mainCurrencyCode + diff --git a/crypto/blockchains/btc/tx/BtcTxBuilder.ts b/crypto/blockchains/btc/tx/BtcTxBuilder.ts index 3c35c7535..0a08bbe04 100644 --- a/crypto/blockchains/btc/tx/BtcTxBuilder.ts +++ b/crypto/blockchains/btc/tx/BtcTxBuilder.ts @@ -5,7 +5,7 @@ import { AirDAOBlockchainTypes } from '../../AirDAOBlockchainTypes'; import DogeTxBuilder from '../../doge/tx/DogeTxBuilder'; import BlocksoftPrivateKeysUtils from '../../../common/AirDAOPrivateKeysUtils'; import { ECPair, payments, TransactionBuilder } from 'bitcoinjs-lib'; -import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '../../../common/AirDAOCryptoLog'; import BlocksoftDict from '@crypto/common/BlocksoftDict'; export default class BtcTxBuilder @@ -199,7 +199,7 @@ export default class BtcTxBuilder } if (typeof this.p2wpkhBTC[input.address] === 'undefined') { // @ts-ignore - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + ' BtcTxBuilder.getRawTx sign usual', input ); @@ -207,7 +207,7 @@ export default class BtcTxBuilder txb.sign(i, this.keyPairBTC[input.address], null, null, input.value * 1); } else if (typeof this.p2shBTC[input.address] === 'undefined') { // @ts-ignore - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + ' BtcTxBuilder.getRawTx sign segwit', input ); @@ -215,7 +215,7 @@ export default class BtcTxBuilder txb.sign(i, this.keyPairBTC[input.address], null, null, input.value * 1); } else { // @ts-ignore - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + ' BtcTxBuilder.getRawTx sign segwit compatible', input diff --git a/crypto/blockchains/btc/tx/BtcTxInputsOutputs.ts b/crypto/blockchains/btc/tx/BtcTxInputsOutputs.ts index 842a09238..eeea6d27c 100644 --- a/crypto/blockchains/btc/tx/BtcTxInputsOutputs.ts +++ b/crypto/blockchains/btc/tx/BtcTxInputsOutputs.ts @@ -4,7 +4,7 @@ import { AirDAOBlockchainTypes } from '../../AirDAOBlockchainTypes'; import BtcUnspentsProvider from '../providers/BtcUnspentsProvider'; import DogeTxInputsOutputs from '../../doge/tx/DogeTxInputsOutputs'; -import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '../../../common/AirDAOCryptoLog'; import DaemonCache from '../../../../src/daemons/DaemonCache'; import BlocksoftDict from '@crypto/common/BlocksoftDict'; import { Database } from '@database'; @@ -44,7 +44,7 @@ export default class BtcTxInputsOutputs // console.log('will legacy 2') } - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'BtcTxInputsOutputs needFindSegwit ' + JSON.stringify(needFindSegwit) ); try { @@ -52,7 +52,7 @@ export default class BtcTxInputsOutputs data.walletHash, this._settings.currencyCode ); - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'BtcTxInputsOutputs CACHE_FOR_CHANGE ' + data.walletHash, CACHE_FOR_CHANGE ); @@ -64,7 +64,7 @@ export default class BtcTxInputsOutputs addressForChange = CACHE_FOR_CHANGE[legacyPrefix]; } // @ts-ignore - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' ' + mainCurrencyCode + @@ -79,7 +79,7 @@ export default class BtcTxInputsOutputs return addressForChange; } } catch (e) { - BlocksoftCryptoLog.err( + AirDAOCryptoLog.err( this._settings.currencyCode + ' ' + mainCurrencyCode + @@ -141,7 +141,7 @@ export default class BtcTxInputsOutputs usdtUsed++; } } - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'BtxTxInputsOutputs for ' + tmp.address + ' usdtUsed ' + diff --git a/crypto/blockchains/btc_test/BtcTestScannerProcessor.js b/crypto/blockchains/btc_test/BtcTestScannerProcessor.js index 304369a4a..2de3a9855 100644 --- a/crypto/blockchains/btc_test/BtcTestScannerProcessor.js +++ b/crypto/blockchains/btc_test/BtcTestScannerProcessor.js @@ -2,7 +2,7 @@ * @version 0.52 * https://github.com/Blockstream/esplora/blob/master/API.md */ -import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; import BlocksoftUtils from '@crypto/common/AirDAOUtils'; import DogeFindAddressFunction from '@crypto/blockchains/doge/basic/DogeFindAddressFunction'; @@ -16,7 +16,7 @@ export default class BtcTestScannerProcessor { * @return {Promise<{int:balance, int:provider}>} */ async getBalanceBlockchain(address) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'BtcTestScannerProcessor.getBalance started ' + address ); const res = await BlocksoftAxios.getWithoutBraking( @@ -45,7 +45,7 @@ export default class BtcTestScannerProcessor { */ async getTransactionsBlockchain(scanData) { const address = scanData.account.address.trim(); - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'BtcTestScannerProcessor.getTransactions started ' + address ); const res = await BlocksoftAxios.getWithoutBraking( @@ -60,7 +60,7 @@ export default class BtcTestScannerProcessor { const transaction = await this._unifyTransaction(address, tx); transactions.push(transaction); } - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'BtcTestScannerProcessor.getTransactions finished ' + address + ' total: ' + diff --git a/crypto/blockchains/btc_test/providers/BtcTestSendProvider.ts b/crypto/blockchains/btc_test/providers/BtcTestSendProvider.ts index 33ff77409..8e10b6c9c 100644 --- a/crypto/blockchains/btc_test/providers/BtcTestSendProvider.ts +++ b/crypto/blockchains/btc_test/providers/BtcTestSendProvider.ts @@ -3,7 +3,7 @@ */ import { AirDAOBlockchainTypes } from '@crypto/blockchains/AirDAOBlockchainTypes'; -import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; import DogeSendProvider from '@crypto/blockchains/doge/providers/DogeSendProvider'; @@ -19,7 +19,7 @@ export default class BtcTestSendProvider txRBF: any, logData: any ): Promise<{ transactionHash: string; transactionJson: any; logData: any }> { - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + ' BtcTestSendProvider.sendTx ' + subtitle + @@ -37,7 +37,7 @@ export default class BtcTestSendProvider logData.error = e.message; // await this._checkError(hex, subtitle, txRBF, logData) } catch (e2) { - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + ' BtcTestSendProvider.send proxy error errorTx ' + e2.message diff --git a/crypto/blockchains/btc_test/providers/BtcTestUnspentsProvider.ts b/crypto/blockchains/btc_test/providers/BtcTestUnspentsProvider.ts index 58aed01da..08e6eaa73 100644 --- a/crypto/blockchains/btc_test/providers/BtcTestUnspentsProvider.ts +++ b/crypto/blockchains/btc_test/providers/BtcTestUnspentsProvider.ts @@ -3,7 +3,7 @@ */ import { AirDAOBlockchainTypes } from '@crypto/blockchains/AirDAOBlockchainTypes'; -import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; const API_PATH = 'https://blockstream.info/testnet/api/'; @@ -28,7 +28,7 @@ export default class BtcTestUnspentsProvider address: string ): Promise { // @ts-ignore - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' BtcTestUnspentsProvider.getUnspents started', address @@ -38,7 +38,7 @@ export default class BtcTestUnspentsProvider const res = await BlocksoftAxios.getWithoutBraking(link); if (!res || typeof res.data === 'undefined') { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' BtcTestUnspentsProvider.getUnspents nothing loaded for address ' + address + diff --git a/crypto/blockchains/doge/DogeScannerProcessor.ts b/crypto/blockchains/doge/DogeScannerProcessor.ts index 1bd44480b..b3176a154 100644 --- a/crypto/blockchains/doge/DogeScannerProcessor.ts +++ b/crypto/blockchains/doge/DogeScannerProcessor.ts @@ -22,7 +22,7 @@ */ import BlocksoftUtils from '../../common/AirDAOUtils'; import BlocksoftAxios from '../../common/BlocksoftAxios'; -import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '../../common/AirDAOCryptoLog'; import DogeFindAddressFunction from './basic/DogeFindAddressFunction'; import BlocksoftExternalSettings from '../../common/AirDAOExternalSettings'; @@ -137,7 +137,7 @@ export default class DogeScannerProcessor { * @return {Promise<{balance:*, unconfirmed:*, provider:string}>} */ async getBalanceBlockchain(address: string, jsonData = {}) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' DogeScannerProcessor.getBalance started ' + address @@ -165,7 +165,7 @@ export default class DogeScannerProcessor { }) { const address = scanData.account.address.trim(); const jsonData = scanData.additional; - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' DogeScannerProcessor.getTransactions started ' + address @@ -178,7 +178,7 @@ export default class DogeScannerProcessor { let res = await this._get(address, jsonData); if (!res || typeof res.data === 'undefined') return []; - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' DogeScannerProcessor.getTransactions loaded from ' + res.provider + @@ -238,7 +238,7 @@ export default class DogeScannerProcessor { } } } - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' DogeScannerProcessor.getTransactions finished ' + address + diff --git a/crypto/blockchains/doge/DogeTransferProcessor.ts b/crypto/blockchains/doge/DogeTransferProcessor.ts index 00ad7fa5e..eeeb50f0c 100644 --- a/crypto/blockchains/doge/DogeTransferProcessor.ts +++ b/crypto/blockchains/doge/DogeTransferProcessor.ts @@ -2,7 +2,7 @@ * @version 0.20 */ import { AirDAOBlockchainTypes } from '../AirDAOBlockchainTypes'; -import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '../../common/AirDAOCryptoLog'; import BlocksoftUtils from '../../common/AirDAOUtils'; import DogeNetworkPrices from './basic/DogeNetworkPrices'; @@ -117,7 +117,7 @@ export default class DogeTransferProcessor typeof data.transactionRemoveByFee !== 'undefined' && data.transactionRemoveByFee ) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' DogeTransferProcessor.getFeeRate remove started ' + data.addressFrom + @@ -131,7 +131,7 @@ export default class DogeTransferProcessor typeof data.transactionReplaceByFee !== 'undefined' && data.transactionReplaceByFee ) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' DogeTransferProcessor.getFeeRate resend started ' + data.addressFrom + @@ -145,7 +145,7 @@ export default class DogeTransferProcessor typeof data.transactionSpeedUp !== 'undefined' && data.transactionSpeedUp ) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' DogeTransferProcessor.getFeeRate speedup started ' + data.addressFrom + @@ -155,7 +155,7 @@ export default class DogeTransferProcessor transactionSpeedUp = data.transactionSpeedUp; txRBF = transactionSpeedUp; } else { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' DogeTransferProcessor.getFeeRate started ' + data.addressFrom + @@ -240,14 +240,14 @@ export default class DogeTransferProcessor return BlocksoftUtils.diff(b.value, a.value) * 1; }); // @ts-ignore - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' DogeTransferProcessor.getFeeRate unspents sorted', unspents ); } else { // @ts-ignore - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' DogeTransferProcessor.getFeeRate unspents no need to sort', unspents @@ -376,7 +376,7 @@ export default class DogeTransferProcessor feeStaticReadable.feeForAllInputs ) ); - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' DogeTransferProcessor.getFeeRate_' + key + @@ -431,7 +431,7 @@ export default class DogeTransferProcessor logInputsOutputs.diffInOutReadable * 1 < this._builderSettings.feeMinTotalReadable ) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' DogeTransferProcessor.getFeeRate_' + key + @@ -458,7 +458,7 @@ export default class DogeTransferProcessor } } // @ts-ignore - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' DogeTransferProcessor.getFeeRate_' + key + @@ -505,7 +505,7 @@ export default class DogeTransferProcessor actualFeeForByte.toString() !== feeForByte.toString(); } if (!actualFeeRebuild && needAutoCorrect) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' DogeTransferProcessor.getFeeRate will correct as ' + actualFeeForByte.toString() + @@ -547,7 +547,7 @@ export default class DogeTransferProcessor ); } - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' DogeTransferProcessor.getFeeRate diff ' + diff + @@ -589,7 +589,7 @@ export default class DogeTransferProcessor } } } - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' DogeTransferProcessor.getFeeRate foundToMore ' + JSON.stringify(foundToMore) @@ -619,7 +619,7 @@ export default class DogeTransferProcessor } } while (doBuild); } catch (e) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' DogeTransferProcessor.getRawTx error ' + e.message @@ -692,7 +692,7 @@ export default class DogeTransferProcessor logResult.fees.push(logFee); } } - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' DogeTransferProcessor.getFees ' + JSON.stringify(logResult) @@ -750,7 +750,7 @@ export default class DogeTransferProcessor typeof data.transactionRemoveByFee !== 'undefined' && data.transactionRemoveByFee ) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' DogeTransferProcessor.sendTx remove started ' + data.transactionRemoveByFee @@ -761,7 +761,7 @@ export default class DogeTransferProcessor typeof data.transactionReplaceByFee !== 'undefined' && data.transactionReplaceByFee ) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' DogeTransferProcessor.sendTx resend started ' + data.transactionReplaceByFee @@ -769,7 +769,7 @@ export default class DogeTransferProcessor txRBF = data.transactionReplaceByFee; txRBFed = 'RBFed'; } else { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' DogeTransferProcessor.sendTx started' ); txRBFed = 'usualSend'; @@ -805,7 +805,7 @@ export default class DogeTransferProcessor logData ); } catch (e) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' DogeTransferProcessor.sent error ' + e.message @@ -851,12 +851,12 @@ export default class DogeTransferProcessor transactionRaw, transactionLog }); - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' DogeTransferProcessor.sendTx hex ', uiData.selectedFee.blockchainData.rawTxHex ); // @ts-ignore - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' DogeTransferProcessor.sendTx result ', result ); @@ -873,7 +873,7 @@ export default class DogeTransferProcessor transactionRaw: JSON.stringify(result.transactionJson) }); - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' DogeTransferProcessor.sent ' + data.addressFrom + @@ -881,7 +881,7 @@ export default class DogeTransferProcessor JSON.stringify(result.transactionJson) ); } catch (e) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' DogeTransferProcessor.sent error additional' + e.message diff --git a/crypto/blockchains/doge/basic/DogeFindAddressFunction.ts b/crypto/blockchains/doge/basic/DogeFindAddressFunction.ts index 328d6a150..410eeaaba 100644 --- a/crypto/blockchains/doge/basic/DogeFindAddressFunction.ts +++ b/crypto/blockchains/doge/basic/DogeFindAddressFunction.ts @@ -19,7 +19,7 @@ * @returns {Promise<{from: string, to: string, value: number, direction: string}>} * @constructor */ -import BlocksoftBN from '../../../common/BlocksoftBN'; +import BlocksoftBN from '../../../common/AirDAOBN'; import { BlockchainTransaction } from '@appTypes'; export default async function DogeFindAddressFunction( diff --git a/crypto/blockchains/doge/basic/DogeLogs.ts b/crypto/blockchains/doge/basic/DogeLogs.ts index 8fb5bb1c2..9dfcc2405 100644 --- a/crypto/blockchains/doge/basic/DogeLogs.ts +++ b/crypto/blockchains/doge/basic/DogeLogs.ts @@ -3,9 +3,9 @@ * @version 0.20 */ import { AirDAOBlockchainTypes } from '../../AirDAOBlockchainTypes'; -import BlocksoftBN from '../../../common/BlocksoftBN'; +import BlocksoftBN from '../../../common/AirDAOBN'; import BlocksoftUtils from '../../../common/AirDAOUtils'; -import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '../../../common/AirDAOCryptoLog'; export namespace DogeLogs { export const logInputsOutputs = function ( @@ -88,12 +88,12 @@ export namespace DogeLogs { typeof data.feeForTx.feeForByte === 'undefined' || data.feeForTx.feeForByte < 0 ) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( title + ' preparedInputsOutputs with autofee ', logInputsOutputs ); } else { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( title + ' preparedInputsOutputs with fee ' + data.feeForTx.feeForTx, logInputsOutputs ); diff --git a/crypto/blockchains/doge/basic/DogeNetworkPrices.ts b/crypto/blockchains/doge/basic/DogeNetworkPrices.ts index 6c4f4177d..e50132b16 100644 --- a/crypto/blockchains/doge/basic/DogeNetworkPrices.ts +++ b/crypto/blockchains/doge/basic/DogeNetworkPrices.ts @@ -2,7 +2,7 @@ * @version 0.20 **/ import { AirDAOBlockchainTypes } from '../../AirDAOBlockchainTypes'; -import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '../../../common/AirDAOCryptoLog'; import BlocksoftExternalSettings from '../../../common/AirDAOExternalSettings'; export default class DogeNetworkPrices @@ -13,7 +13,7 @@ export default class DogeNetworkPrices speed_blocks_6: number; speed_blocks_12: number; }> { - BlocksoftCryptoLog.log(currencyCode + ' DogeNetworkPricesProvider '); + AirDAOCryptoLog.log(currencyCode + ' DogeNetworkPricesProvider '); const externalSettings = await BlocksoftExternalSettings.getAll( 'DOGE.getNetworkPrices' diff --git a/crypto/blockchains/doge/providers/DogeSendProvider.ts b/crypto/blockchains/doge/providers/DogeSendProvider.ts index 016a0ac74..109d557b7 100644 --- a/crypto/blockchains/doge/providers/DogeSendProvider.ts +++ b/crypto/blockchains/doge/providers/DogeSendProvider.ts @@ -3,7 +3,7 @@ * https://github.com/trezor/blockbook/blob/master/docs/api.md */ import { AirDAOBlockchainTypes } from '../../AirDAOBlockchainTypes'; -import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '../../../common/AirDAOCryptoLog'; import BlocksoftAxios from '../../../common/BlocksoftAxios'; import BlocksoftExternalSettings from '../../../common/AirDAOExternalSettings'; @@ -48,7 +48,7 @@ export default class DogeSendProvider txRBF: any, logData: any ): Promise<{ transactionHash: string; transactionJson: any }> { - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + ' DogeSendProvider.sendTx ' + subtitle + @@ -80,7 +80,7 @@ export default class DogeSendProvider logData.error = e.message; await this._checkError(hex, subtitle, txRBF, logData); } catch (e2) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' DogeSendProvider.send proxy error errorTx ' + e2.message diff --git a/crypto/blockchains/doge/providers/DogeUnspentsProvider.ts b/crypto/blockchains/doge/providers/DogeUnspentsProvider.ts index 252657be3..07095febb 100644 --- a/crypto/blockchains/doge/providers/DogeUnspentsProvider.ts +++ b/crypto/blockchains/doge/providers/DogeUnspentsProvider.ts @@ -4,7 +4,7 @@ * https://doge1.trezor.io/api/v2/utxo/D5oKvWEibVe74CXLASmhpkRpLoyjgZhm71 */ import { AirDAOBlockchainTypes } from '../../AirDAOBlockchainTypes'; -import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '../../../common/AirDAOCryptoLog'; import BlocksoftAxios from '../../../common/BlocksoftAxios'; import BlocksoftExternalSettings from '../../../common/AirDAOExternalSettings'; import DogeRawDS from '../stores/DogeRawDS'; @@ -30,7 +30,7 @@ export default class DogeUnspentsProvider address: string ): Promise { // @ts-ignore - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' DogeUnspentsProvider.getUnspents started ' + address @@ -53,7 +53,7 @@ export default class DogeUnspentsProvider this._trezorServerCode, this._trezorServer ); - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' DogeUnspentsProvider.getUnspents nothing loaded for address ' + address + @@ -92,7 +92,7 @@ export default class DogeUnspentsProvider allUnspents: AirDAOBlockchainTypes.UnspentTx[], walletHash: string ): Promise { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' DogeUnspentsProvider.getTx started ' + tx ); @@ -105,7 +105,7 @@ export default class DogeUnspentsProvider currencyCode: this._settings.currencyCode, transactionHash: tx }); - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' DogeUnspentsProvider.getTx inputs ' + tx, saved ); @@ -120,7 +120,7 @@ export default class DogeUnspentsProvider const res = await BlocksoftAxios.getWithoutBraking(link); // @ts-ignore if (!res || typeof res.data === 'undefined' || !res.data) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' DogeUnspentsProvider.getTx no tx ' + tx @@ -175,7 +175,7 @@ export default class DogeUnspentsProvider res2.data.vout ) { // @ts-ignore - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' DogeUnspentsProvider.getTx loading output data ' + JSON.stringify(unspent) + @@ -207,7 +207,7 @@ export default class DogeUnspentsProvider } } } else { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' DogeUnspentsProvider.getTx loading output data ' + JSON.stringify(unspent) + @@ -215,7 +215,7 @@ export default class DogeUnspentsProvider ); } } catch (e) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' DogeUnspentsProvider.getTx while loading output data ' + JSON.stringify(unspent) @@ -265,7 +265,7 @@ export default class DogeUnspentsProvider } // @ts-ignore - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' DogeUnspentsProvider.getTx found ' + tx, sortedUnspents ); diff --git a/crypto/blockchains/doge/stores/DogeRawDS.ts b/crypto/blockchains/doge/stores/DogeRawDS.ts index 9eeb03511..8bc50d807 100644 --- a/crypto/blockchains/doge/stores/DogeRawDS.ts +++ b/crypto/blockchains/doge/stores/DogeRawDS.ts @@ -1,4 +1,4 @@ -import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '../../../common/AirDAOCryptoLog'; import { BlocksoftTransfer } from '../../../actions/BlocksoftTransfer/BlocksoftTransfer'; import { Database } from '@database'; import { DatabaseTable } from '@appTypes'; @@ -153,7 +153,7 @@ class DogeRawDS { data.transactionUnique = data.address.toLowerCase() + '_' + data.transactionHash; } - BlocksoftCryptoLog.log('DogeRawDS cleanRaw ', data); + AirDAOCryptoLog.log('DogeRawDS cleanRaw ', data); const now = new Date().getTime(); const sql = `UPDATE ${tableName} SET is_removed=1, removed_at = '${now}' @@ -241,7 +241,7 @@ class DogeRawDS { const str = Database.unEscapeString(res[0].transactionRaw) || ''; return JSON.parse(str); } catch (e) { - BlocksoftCryptoLog.err('DogeRawDS getInputs error ' + e.message); + AirDAOCryptoLog.err('DogeRawDS getInputs error ' + e.message); return false; } } diff --git a/crypto/blockchains/doge/tx/DogeTxBuilder.ts b/crypto/blockchains/doge/tx/DogeTxBuilder.ts index 2bee4e7ab..f56054336 100644 --- a/crypto/blockchains/doge/tx/DogeTxBuilder.ts +++ b/crypto/blockchains/doge/tx/DogeTxBuilder.ts @@ -2,7 +2,7 @@ * @version 0.20 */ import { AirDAOBlockchainTypes } from '../../AirDAOBlockchainTypes'; -import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '../../../common/AirDAOCryptoLog'; import { TransactionBuilder, ECPair, payments } from 'bitcoinjs-lib'; import BlocksoftExternalSettings from '../../../common/AirDAOExternalSettings'; @@ -37,7 +37,7 @@ export default class DogeTxBuilder implements AirDAOBlockchainTypes.TxBuilder { fromExt && fromExt * 1 > 0 ? fromExt * 1 : this._builderSettings.feeMaxForByteSatoshi; - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( 'DogeTxBuilder.getRawTx ' + this._settings.currencyCode + ' _feeMaxForByteSatoshi ' + @@ -98,7 +98,7 @@ export default class DogeTxBuilder implements AirDAOBlockchainTypes.TxBuilder { i: number, input: AirDAOBlockchainTypes.UnspentTx ): Promise { - await BlocksoftCryptoLog.log('DogeTxBuilder.getRawTx sign', input); + await AirDAOCryptoLog.log('DogeTxBuilder.getRawTx sign', input); // @ts-ignore txb.sign(i, this.keyPair, null, null, input.value * 1); } @@ -129,7 +129,7 @@ export default class DogeTxBuilder implements AirDAOBlockchainTypes.TxBuilder { await this._reInit(); this._getRawTxValidateKeyPair(privateData, data); - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + ' DogeTxBuilder.getRawTx validated address private key' ); @@ -144,7 +144,7 @@ export default class DogeTxBuilder implements AirDAOBlockchainTypes.TxBuilder { if (data.allowReplaceByFee) { nSequence = MIN_SEQ; txAllowReplaceByFee = true; - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + ' DogeTxBuilder.getRawTx allow RBF ' + nSequence @@ -152,7 +152,7 @@ export default class DogeTxBuilder implements AirDAOBlockchainTypes.TxBuilder { } else { nSequence = MAX_SEQ; txAllowReplaceByFee = false; - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + ' DogeTxBuilder.getRawTx no RBF ' + nSequence @@ -165,7 +165,7 @@ export default class DogeTxBuilder implements AirDAOBlockchainTypes.TxBuilder { if (nSequence >= MAX_SEQ) { nSequence = MAX_SEQ; txAllowReplaceByFee = false; - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + ' DogeTxBuilder.getRawTx no RBF by old nSeq ' + data.transactionJson.nSequence + @@ -173,7 +173,7 @@ export default class DogeTxBuilder implements AirDAOBlockchainTypes.TxBuilder { nSequence ); } else { - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + ' DogeTxBuilder.getRawTx allow RBF by old nSeq ' + data.transactionJson.nSequence + @@ -187,7 +187,7 @@ export default class DogeTxBuilder implements AirDAOBlockchainTypes.TxBuilder { this._bitcoinNetwork, this._feeMaxForByteSatoshi ); - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + ' DogeTxBuilder.getRawTx started max4Bytes ' + this._feeMaxForByteSatoshi @@ -203,12 +203,12 @@ export default class DogeTxBuilder implements AirDAOBlockchainTypes.TxBuilder { // @ts-ignore log.inputs.push({ txid: input.txid, vout: input.vout, nSequence }); // @ts-ignore - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + ' DogeTxBuilder.getRawTx input added', input ); } catch (e) { - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + ' DogeTxBuilder.getRawTx input add error ', input @@ -226,12 +226,12 @@ export default class DogeTxBuilder implements AirDAOBlockchainTypes.TxBuilder { // @ts-ignore log.outputs.push({ addressTo: output.to, amount: output.amount }); // @ts-ignore - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + ' DogeTxBuilder.getRawTx output added ', output ); } catch (e) { - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + ' DogeTxBuilder.getRawTx output add error ', output @@ -243,15 +243,15 @@ export default class DogeTxBuilder implements AirDAOBlockchainTypes.TxBuilder { for (let i = 0, ic = preparedInputsOutputs.inputs.length; i < ic; i++) { const input = preparedInputsOutputs.inputs[i]; try { - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + ' DogeTxBuilder.getRawTx sign adding' ); await this._getRawTxSign(txb, i, input); - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + ' DogeTxBuilder.getRawTx sign added' ); } catch (e) { - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + ' DogeTxBuilder.getRawTx input sign error ', input @@ -267,13 +267,13 @@ export default class DogeTxBuilder implements AirDAOBlockchainTypes.TxBuilder { let rawTxHex; try { rawTxHex = txb.build().toHex(); - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + ' DogeTxBuilder.getRawTx size ' + rawTxHex.length ); // @ts-ignore - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + ' DogeTxBuilder.getRawTx hex', rawTxHex ); diff --git a/crypto/blockchains/doge/tx/DogeTxInputsOutputs.ts b/crypto/blockchains/doge/tx/DogeTxInputsOutputs.ts index e69ac7f72..9e68c03f1 100644 --- a/crypto/blockchains/doge/tx/DogeTxInputsOutputs.ts +++ b/crypto/blockchains/doge/tx/DogeTxInputsOutputs.ts @@ -2,9 +2,9 @@ * @version 0.20 */ import { AirDAOBlockchainTypes } from '../../AirDAOBlockchainTypes'; -import BlocksoftBN from '../../../common/BlocksoftBN'; +import BlocksoftBN from '../../../common/AirDAOBN'; import BlocksoftUtils from '../../../common/AirDAOUtils'; -import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '../../../common/AirDAOCryptoLog'; import BlocksoftDict from '@crypto/common/BlocksoftDict'; // @ts-ignore @@ -74,7 +74,7 @@ export default class DogeTxInputsOutputs } } // @ts-ignore - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' DogeTxInputsOutputs.getInputsOutputs _coinSelectTargets', { @@ -391,7 +391,7 @@ export default class DogeTxInputsOutputs if (diff * 1 < 0) { // skip as dust // @ts-ignore - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' DogeTxInputsOutputs unspent skipped as dust ' + this._minOutputDust + diff --git a/crypto/blockchains/eth/EthScannerProcessor.ts b/crypto/blockchains/eth/EthScannerProcessor.ts index b370052d1..6e1b23450 100644 --- a/crypto/blockchains/eth/EthScannerProcessor.ts +++ b/crypto/blockchains/eth/EthScannerProcessor.ts @@ -3,10 +3,10 @@ */ import BlocksoftUtils from '../../common/AirDAOUtils'; import BlocksoftAxios from '../../common/BlocksoftAxios'; -import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '../../common/AirDAOCryptoLog'; import EthBasic from './basic/EthBasic'; import BlocksoftExternalSettings from '../../common/AirDAOExternalSettings'; -import BlocksoftBN from '../../common/BlocksoftBN'; +import BlocksoftBN from '../../common/AirDAOBN'; import EthTmpDS from './stores/EthTmpDS'; import EthRawDS from './stores/EthRawDS'; @@ -86,7 +86,7 @@ export default class EthScannerProcessor extends EthBasic { } if (typeof this._trezorServer === 'undefined') { - BlocksoftCryptoLog.err( + AirDAOCryptoLog.err( this._settings.currencyCode + ' EthScannerProcessor._get empty trezorServer' ); @@ -124,7 +124,7 @@ export default class EthScannerProcessor extends EthBasic { this._settings.currencyCode + ' ETH.Scanner._get' ); if (typeof this._trezorServer === 'undefined') { - BlocksoftCryptoLog.err( + AirDAOCryptoLog.err( this._settings.currencyCode + ' EthScannerProcessor._get empty trezorServer2' ); @@ -154,7 +154,7 @@ export default class EthScannerProcessor extends EthBasic { const data = res.data; data.totalTokens = 0; data.formattedTokens = {}; - //await BlocksoftCryptoLog.log('EthScannerProcessor._get ERC20 tokens ' + JSON.stringify(data.tokens)) + //await AirDAOCryptoLog.log('EthScannerProcessor._get ERC20 tokens ' + JSON.stringify(data.tokens)) if (typeof data.tokens !== 'undefined') { let token; for (token of data.tokens) { @@ -179,7 +179,7 @@ export default class EthScannerProcessor extends EthBasic { * @return {Promise<{balance, unconfirmed, provider}>} */ async getBalanceBlockchain(address) { - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + ' EthScannerProcessor.getBalance started ' + address @@ -216,7 +216,7 @@ export default class EthScannerProcessor extends EthBasic { } } } catch (e) { - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + ' EthScannerProcessor.getBalance ' + address + @@ -228,7 +228,7 @@ export default class EthScannerProcessor extends EthBasic { try { balance = await this._web3.eth.getBalance(address); - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' EthScannerProcessor.getBalance ' + address + @@ -239,7 +239,7 @@ export default class EthScannerProcessor extends EthBasic { time = 'now()'; return { balance, unconfirmed: 0, provider, time }; } catch (e) { - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + ' EthScannerProcessor.getBalance ' + address + @@ -258,7 +258,7 @@ export default class EthScannerProcessor extends EthBasic { */ async getTransactionsBlockchain(scanData) { const address = scanData.account.address; - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + ' EthScannerProcessor.getTransactions started ' + address @@ -278,7 +278,7 @@ export default class EthScannerProcessor extends EthBasic { let transactions; if (res && typeof res.data !== 'undefined' && res.data) { - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + ' EthScannerProcessor.getBalance loaded from ' + res.provider + @@ -289,7 +289,7 @@ export default class EthScannerProcessor extends EthBasic { this._tokenAddress && typeof res.data.formattedTokens[this._tokenAddress] === 'undefined' ) { - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + ' EthScannerProcessor.getTransactions skipped token ' + this._tokenAddress + @@ -298,7 +298,7 @@ export default class EthScannerProcessor extends EthBasic { ); return false; } - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + ' EthScannerProcessor.getTransactions trezor unify started ' + address @@ -310,7 +310,7 @@ export default class EthScannerProcessor extends EthBasic { true, {} ); - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + ' EthScannerProcessor.getTransactions trezor finished ' + address @@ -327,7 +327,7 @@ export default class EthScannerProcessor extends EthBasic { ); } else { if (!this._etherscanApiPath) { - BlocksoftCryptoLog.err( + AirDAOCryptoLog.err( this._settings.currencyCode + ' EthScannerProcessor.getTransactions no _etherscanApiPath' ); @@ -395,7 +395,7 @@ export default class EthScannerProcessor extends EthBasic { ) { return []; } - await BlocksoftCryptoLog.log(logTitle + ' started ', link); + await AirDAOCryptoLog.log(logTitle + ' started ', link); const list = tmp.data.data[0].transactionLists; for (const tx of list) { const transaction = await this._unifyTransactionOklink(address, tx); @@ -403,7 +403,7 @@ export default class EthScannerProcessor extends EthBasic { transactions[transaction.transactionHash] = transaction; } } - await BlocksoftCryptoLog.log(logTitle + ' finished ' + address); + await AirDAOCryptoLog.log(logTitle + ' finished ' + address); return transactions; } @@ -414,7 +414,7 @@ export default class EthScannerProcessor extends EthBasic { isInternal, transactions = {} ) { - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( logTitle + ' started ' + JSON.stringify(isInternal), link ); @@ -444,7 +444,7 @@ export default class EthScannerProcessor extends EthBasic { false, transactions ); - await BlocksoftCryptoLog.log(logTitle + ' finished ' + address); + await AirDAOCryptoLog.log(logTitle + ' finished ' + address); return transactions; } @@ -453,7 +453,7 @@ export default class EthScannerProcessor extends EthBasic { * @return {Promise<[UnifiedTransaction]>} */ async getTransactionBlockchain(txHash) { - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + ' EthScannerProcessor.getTransaction started ' + txHash @@ -465,7 +465,7 @@ export default class EthScannerProcessor extends EthBasic { ); if (typeof this._trezorServer === 'undefined') { - BlocksoftCryptoLog.err( + AirDAOCryptoLog.err( this._settings.currencyCode + ' EthScannerProcessor.getTransaction empty trezorServer' ); @@ -492,7 +492,7 @@ export default class EthScannerProcessor extends EthBasic { this._settings.currencyCode + ' ETH.Scanner._get' ); if (typeof this._trezorServer === 'undefined') { - BlocksoftCryptoLog.err( + AirDAOCryptoLog.err( this._settings.currencyCode + ' EthScannerProcessor._get empty trezorServer2' ); @@ -612,7 +612,7 @@ export default class EthScannerProcessor extends EthBasic { } } } catch (e) { - BlocksoftCryptoLog.err( + AirDAOCryptoLog.err( this._settings.currencyCode + ' EthScannerProcessor._unifyTransaction error ' + e.message + diff --git a/crypto/blockchains/eth/EthScannerProcessorErc20.ts b/crypto/blockchains/eth/EthScannerProcessorErc20.ts index 652230859..4474a752c 100644 --- a/crypto/blockchains/eth/EthScannerProcessorErc20.ts +++ b/crypto/blockchains/eth/EthScannerProcessorErc20.ts @@ -1,7 +1,7 @@ /** * @version 0.5 */ -import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '../../common/AirDAOCryptoLog'; import EthScannerProcessor from './EthScannerProcessor'; import abi from './ext/erc20'; @@ -33,7 +33,7 @@ export default class EthScannerProcessorErc20 extends EthScannerProcessor { * @return {Promise<{balance, unconfirmed, provider}>} */ async getBalanceBlockchain(address: string) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' EthScannerProcessorErc20.getBalance started ' + address @@ -54,7 +54,7 @@ export default class EthScannerProcessorErc20 extends EthScannerProcessor { if (this._trezorServerCode) { const res = await this._get(address); if (!res || typeof res.data === 'undefined') return false; - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' EthScannerProcessorErc20.getBalance loaded from ' + res.provider + @@ -84,7 +84,7 @@ export default class EthScannerProcessorErc20 extends EthScannerProcessor { } } balance = await this._token.methods.balanceOf(address).call(); - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' EthScannerProcessorErc20.getBalance ' + address + @@ -97,7 +97,7 @@ export default class EthScannerProcessorErc20 extends EthScannerProcessor { time = 'now()'; return { balance, unconfirmed: 0, provider, time }; } catch (e) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' EthScannerProcessorErc20.getBalance ' + address + diff --git a/crypto/blockchains/eth/EthTransferProcessor.ts b/crypto/blockchains/eth/EthTransferProcessor.ts index 77eb416e3..826245b17 100644 --- a/crypto/blockchains/eth/EthTransferProcessor.ts +++ b/crypto/blockchains/eth/EthTransferProcessor.ts @@ -3,7 +3,7 @@ * @version 0.20 */ import BlocksoftUtils from '../../common/AirDAOUtils'; -import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '../../common/AirDAOCryptoLog'; import EthTmpDS from './stores/EthTmpDS'; @@ -54,7 +54,7 @@ export default class EthTransferProcessor typeof data.transactionRemoveByFee !== 'undefined' && data.transactionRemoveByFee ) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' EthTransferProcessor.getFeeRate remove started ' + data.transactionRemoveByFee @@ -65,7 +65,7 @@ export default class EthTransferProcessor typeof data.transactionReplaceByFee !== 'undefined' && data.transactionReplaceByFee ) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' EthTransferProcessor.getFeeRate resend started ' + data.transactionReplaceByFee @@ -73,7 +73,7 @@ export default class EthTransferProcessor txRBF = data.transactionReplaceByFee; txRBFed = 'RBFed'; } else if (typeof data.dexOrderData !== 'undefined' && data.dexOrderData) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' EthTransferProcessor.getFeeRate dex ' + data.addressFrom + @@ -81,7 +81,7 @@ export default class EthTransferProcessor ); } else { const realAddressToLower = realAddressTo.toLowerCase(); - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' EthTransferProcessor.getFeeRate ' + data.addressFrom + @@ -111,7 +111,7 @@ export default class EthTransferProcessor typeof data.transactionJson.nonce !== 'undefined' ? data.transactionJson.nonce : false; - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' EthTransferProcessor.getFeeRate ' + data.addressFrom + @@ -127,7 +127,7 @@ export default class EthTransferProcessor if (scannedTx) { oldGasPrice = scannedTx.gasPrice; oldNonce = scannedTx.nonce; - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' EthTransferProcessor.getFeeRate ' + data.addressFrom + @@ -136,7 +136,7 @@ export default class EthTransferProcessor ); } if (!oldGasPrice) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' EthTransferProcessor.getFeeRate ' + txRBFed + @@ -145,7 +145,7 @@ export default class EthTransferProcessor ); } } catch (e) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' EthTransferProcessor.getFeeRate ' + txRBFed + @@ -163,7 +163,7 @@ export default class EthTransferProcessor ) { oldNonce = additionalData.nonceForTx; nonceLog += ' customFeeNonce ' + oldNonce; - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' EthTransferProcessor.getFeeRate ' + data.addressFrom + @@ -171,7 +171,7 @@ export default class EthTransferProcessor additionalData.nonceForTx ); } else { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' EthTransferProcessor.getFeeRate ' + data.addressFrom + @@ -257,7 +257,7 @@ export default class EthTransferProcessor ) { maxNonceLocal = proxyPriceCheck.maxNonceLocal; } - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' EthTransferProcessor.getFeeRate ' + data.addressFrom + @@ -322,7 +322,7 @@ export default class EthTransferProcessor ], id: 1 }; - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' EthTransferProcessor.getFeeRate estimatedGas for WalletConnect start' ); @@ -341,7 +341,7 @@ export default class EthTransferProcessor } else { gasLimit = 500000; } - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' EthTransferProcessor.getFeeRate estimatedGas for WalletConnect result ' + gasLimit @@ -379,7 +379,7 @@ export default class EthTransferProcessor gasLimit = BlocksoftUtils.mul(gasLimit, 1.5); } } catch (e) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'EthTransferProcessor data.contractCallData error ' + e.message ); // do nothing @@ -403,7 +403,7 @@ export default class EthTransferProcessor realAddressTo, data.amount ); // it doesn't matter what the price of gas is, just a required parameter - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' EthTransferProcessor.getFeeRate estimatedGas ' + gasLimit @@ -446,7 +446,7 @@ export default class EthTransferProcessor } } else { gasLimit = additionalData.gasLimit; - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' EthTransferProcessor.getFeeRate preestimatedGas ' + gasLimit @@ -479,7 +479,7 @@ export default class EthTransferProcessor } // @ts-ignore - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' EthTransferProcessor.getFeeRate prefinished', { @@ -557,7 +557,7 @@ export default class EthTransferProcessor ' with basic ' + nonceForTxBasic + nonceLog; - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' EthTransferProcessor.getFeeRate ' + data.addressFrom + @@ -573,7 +573,7 @@ export default class EthTransferProcessor ' replaced by basic ' + nonceForTxBasic + nonceLog; - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' EthTransferProcessor.getFeeRate ' + data.addressFrom + @@ -594,7 +594,7 @@ export default class EthTransferProcessor (await this._web3.eth.getBalance(data.addressFrom)); // @ts-ignore if (!balance || balance * 1 === 0) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' EthTxProcessor.getFeeRate balanceFromWeb3 is empty ' + balance @@ -682,7 +682,7 @@ export default class EthTransferProcessor ? BlocksoftUtils.toGwei(newGasPrice).toString() : newGasPrice; } catch (e) { - BlocksoftCryptoLog.err( + AirDAOCryptoLog.err( 'EthTxProcessor.getFeeRate newGasPrice to gwei error ' + e.message ); } @@ -706,7 +706,7 @@ export default class EthTransferProcessor if (!changedFeeByBalance || diff * 1 < 1000) { feesOK[titles[index]] = tmp.gasPrice; } else { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'EthTxProcessor.getFeeRate skipped feesOk ' + titles[index] + ' as diff ' + @@ -722,7 +722,7 @@ export default class EthTransferProcessor newGasPrice !== prevGasPrice ) { prevGasPrice = tmp.gasPrice; - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'EthTxProcessor.getFeeRate added feeForTx ' + titles[index] + ' ' + @@ -734,7 +734,7 @@ export default class EthTransferProcessor ); result.fees.push(tmp); } else { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'EthTxProcessor.getFeeRate skipped feeForTx ' + titles[index] + ' ' + @@ -819,7 +819,7 @@ export default class EthTransferProcessor ? BlocksoftUtils.toGwei(newGasPrice).toString() : newGasPrice; } catch (e) { - BlocksoftCryptoLog.err( + AirDAOCryptoLog.err( 'EthTxProcessor.getFeeRate newGasPrice2 to gwei error ' + e.message ); @@ -841,7 +841,7 @@ export default class EthTransferProcessor }; // @ts-ignore prevGasPrice = tmp.gasPrice; - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'EthTxProcessor.getFeeRate feeForTx rbfFaster ' + tmp.feeForTx + ' with gasPrice ' + @@ -902,7 +902,7 @@ export default class EthTransferProcessor try { gweiFee = fee !== '0' ? BlocksoftUtils.toGwei(fee).toString() : fee; } catch (e) { - BlocksoftCryptoLog.err( + AirDAOCryptoLog.err( 'EthTxProcessor.getFeeRate fee to gwei error ' + e.message ); } @@ -921,7 +921,7 @@ export default class EthTransferProcessor isTransferAll: data.isTransferAll }; - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'EthTxProcessor.getFeeRate feeForTx ' + titles[index] + ' ' + @@ -942,7 +942,7 @@ export default class EthTransferProcessor if (!skippedByOld) { if (typeof feesOK['eth_speed_fast'] === 'undefined') { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'EthTxProcessor.getFeeRate showSmallFeeNotice reason ' + JSON.stringify(feesOK) ); @@ -959,7 +959,7 @@ export default class EthTransferProcessor maxNonceLocal.amountBlocked && typeof maxNonceLocal.amountBlocked[this._settings.currencyCode] !== 'undefined'; - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' EthTransferProcessor.getFees ethAllowBlockedBalance ' + ethAllowBlockedBalance + @@ -979,7 +979,7 @@ export default class EthTransferProcessor maxNonceLocal.amountBlocked[this._settings.currencyCode] ).toString(); const diffAmount = BlocksoftUtils.diff(diff, data.amount).toString(); - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' EthTransferProcessor.getFees balance ' + result.countedForBasicBalance + @@ -1004,7 +1004,7 @@ export default class EthTransferProcessor } } } catch (e) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( ' EthTransferProcessor.getFees ethAllowBlockedBalance inner error ' + e.message ); @@ -1014,7 +1014,7 @@ export default class EthTransferProcessor 'ETH_LONG_QUERY' ); check = maxNonceLocal.queryLength * 1 >= LONG_QUERY * 1; - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + ' EthTransferProcessor.getFees ethAllowLongQuery ' + ethAllowLongQuery + @@ -1048,7 +1048,7 @@ export default class EthTransferProcessor additionalData: AirDAOBlockchainTypes.TransferAdditionalData = {} ): Promise { if (!data.amount || data.amount === '0') { - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + ' EthTransferProcessor.getTransferAllBalance ' + data.addressFrom + @@ -1062,7 +1062,7 @@ export default class EthTransferProcessor } catch (e) { this.checkError(e, data); } - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + ' EthTransferProcessor.getTransferAllBalance ' + data.addressFrom + @@ -1070,7 +1070,7 @@ export default class EthTransferProcessor data.amount ); } else { - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + ' EthTransferProcessor.getTransferAllBalance ' + data.addressFrom + @@ -1110,7 +1110,7 @@ export default class EthTransferProcessor fees.fees[fees.selectedFeeIndex].amountForTx ); } catch (e) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' EthTransferProcessor.getTransferAllBalance ' + data.addressFrom + @@ -1139,7 +1139,7 @@ export default class EthTransferProcessor throw new Error('ETH transaction required addressTo'); } - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + ' EthTransferProcessor sendTx started', JSON.parse(JSON.stringify(data)) ); @@ -1155,7 +1155,7 @@ export default class EthTransferProcessor typeof data.transactionRemoveByFee !== 'undefined' && data.transactionRemoveByFee ) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' EthTransferProcessor.sendTx started ' + data.transactionRemoveByFee @@ -1166,7 +1166,7 @@ export default class EthTransferProcessor typeof data.transactionReplaceByFee !== 'undefined' && data.transactionReplaceByFee ) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' EthTransferProcessor.sendTx resend started ' + data.transactionReplaceByFee @@ -1174,7 +1174,7 @@ export default class EthTransferProcessor txRBF = data.transactionReplaceByFee; txRBFed = 'RBFed'; } else if (typeof data.dexOrderData !== 'undefined' && data.dexOrderData) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' EthTransferProcessor.sendTx dex ' + data.addressFrom + @@ -1182,7 +1182,7 @@ export default class EthTransferProcessor ); txRBFed = 'dexSend'; } else { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' EthTransferProcessor.sendTx ' + data.addressFrom + @@ -1224,7 +1224,7 @@ export default class EthTransferProcessor } // @ts-ignore - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' EthTransferProcessor.sendTx ' + txRBFed + @@ -1352,7 +1352,7 @@ export default class EthTransferProcessor oldNonce = scannedTx.nonce; } } catch (e) { - BlocksoftCryptoLog.err( + AirDAOCryptoLog.err( this._settings.currencyCode + ' EthTransferProcessor.sent rbf not loaded nonce for ' + txRBF + @@ -1362,7 +1362,7 @@ export default class EthTransferProcessor throw new Error('System error: not loaded nonce for ' + txRBF); } if (oldNonce === false || oldNonce === -1) { - BlocksoftCryptoLog.err( + AirDAOCryptoLog.err( this._settings.currencyCode + ' EthTransferProcessor.sent rbf no nonce for ' + txRBF @@ -1392,7 +1392,7 @@ export default class EthTransferProcessor ? logData.selectedFee.nonceLog : ''); } - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' EthTransferProcessor.sent ' + data.addressFrom + @@ -1438,7 +1438,7 @@ export default class EthTransferProcessor result.transactionJson.txData = tx.data; await EthTmpDS.getCache(this._mainCurrencyCode, data.addressFrom); } - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' EthTransferProcessor.sent ' + data.addressFrom + @@ -1462,7 +1462,7 @@ export default class EthTransferProcessor transaction.transactionJson && typeof transaction.transactionJson.nonce !== 'undefined' ) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' EthTransferPRocessor.setMissingTx remove nonce ' + transaction.transactionJson.nonce + @@ -1484,7 +1484,7 @@ export default class EthTransferProcessor source: string ): boolean { if (transaction.transactionDirection === 'income') { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'EthTransferProcessor.canRBF ' + transaction.transactionHash + ' false by income' @@ -1493,7 +1493,7 @@ export default class EthTransferProcessor } if (typeof transaction.transactionJson !== 'undefined') { if (typeof transaction.transactionJson.delegatedNonce !== 'undefined') { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'EthTransferProcessor.canRBF ' + transaction.transactionHash + ' false by delegated' @@ -1505,7 +1505,7 @@ export default class EthTransferProcessor if (max.success > -1) { // @ts-ignore if (transaction.transactionJson.nonce * 1 > max.success * 1) return true - BlocksoftCryptoLog.log('EthTransferProcessor.canRBF ' + transaction.transactionHash + ' false by maxSuccess', + AirDAOCryptoLog.log('EthTransferProcessor.canRBF ' + transaction.transactionHash + ' false by maxSuccess', {'nonce' : transaction.transactionJson.nonce, 'max' : max.success}) return false } diff --git a/crypto/blockchains/eth/EthTransferProcessorErc20.ts b/crypto/blockchains/eth/EthTransferProcessorErc20.ts index cd627a812..6c9fe0688 100644 --- a/crypto/blockchains/eth/EthTransferProcessorErc20.ts +++ b/crypto/blockchains/eth/EthTransferProcessorErc20.ts @@ -5,7 +5,7 @@ import EthTransferProcessor from './EthTransferProcessor'; import { AirDAOBlockchainTypes } from '../AirDAOBlockchainTypes'; -import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; import BlocksoftExternalSettings from '@crypto/common/AirDAOExternalSettings'; import abi from './ext/erc20.js'; @@ -64,7 +64,7 @@ export default class EthTransferProcessorErc20 } if (typeof data.dexOrderData !== 'undefined' && data.dexOrderData) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' EthTransferProcessor.getFeeRate dex ' + data.addressFrom + @@ -78,7 +78,7 @@ export default class EthTransferProcessorErc20 typeof data.transactionRemoveByFee !== 'undefined' && data.transactionRemoveByFee ) { - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + ' EthTxProcessorErc20.getFeeRate removeByFee no token ' + this._tokenAddress @@ -87,7 +87,7 @@ export default class EthTransferProcessorErc20 return super.getFeeRate(tmpData, privateData, additionalData); } // @ts-ignore - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' EthTxProcessorErc20.getFeeRate estimateGas started token ' + this._tokenAddress @@ -104,7 +104,7 @@ export default class EthTransferProcessorErc20 const tmp2 = '0xf1Cff704c6E6ce459e3E1544a9533cCcBDAD7B99'; firstAddressTo = data.addressFrom === tmp1 ? tmp2 : tmp1; // @ts-ignore - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' EthTxProcessorErc20.getFeeRate estimateGas addressToChanged ' + basicAddressTo + @@ -130,7 +130,7 @@ export default class EthTransferProcessorErc20 firstAddressTo + ' from ' + data.addressFrom; - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' EthTxProcessorErc20.getFeeRate estimateGas error1 ' + e.message @@ -148,7 +148,7 @@ export default class EthTransferProcessorErc20 firstAddressTo + ' from ' + data.addressFrom; - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' EthTxProcessorErc20.getFeeRate estimateGas error2 ' + e.message @@ -168,7 +168,7 @@ export default class EthTransferProcessorErc20 firstAddressTo + ' from ' + data.addressFrom; - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' EthTxProcessorErc20.getFeeRate estimateGas error3 ' + e.message @@ -200,7 +200,7 @@ export default class EthTransferProcessorErc20 this.checkError(e, data); } - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' EthTxProcessorErc20.getFeeRate estimateGas finished ' + estimatedGas @@ -219,7 +219,7 @@ export default class EthTransferProcessorErc20 ): Promise { const tmpData = { ...data }; if (!tmpData.amount || tmpData.amount === '0') { - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + ' EthTransferProcessorErc20.getTransferAllBalance ' + data.addressFrom + @@ -235,7 +235,7 @@ export default class EthTransferProcessorErc20 } catch (e) { this.checkError(e, data); } - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + ' EthTransferProcessorErc20.getTransferAllBalance ' + data.addressFrom + @@ -245,7 +245,7 @@ export default class EthTransferProcessorErc20 tmpData.amount ); } else { - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + ' EthTransferProcessorErc20.getTransferAllBalance ' + data.addressFrom + @@ -270,7 +270,7 @@ export default class EthTransferProcessorErc20 uiData: AirDAOBlockchainTypes.TransferUiData ): Promise { if (typeof data.dexOrderData !== 'undefined' && data.dexOrderData) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' EthTransferProcessor.sendTx dex ' + data.addressFrom + @@ -283,7 +283,7 @@ export default class EthTransferProcessorErc20 typeof data.transactionRemoveByFee !== 'undefined' && data.transactionRemoveByFee ) { - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + ' EthTxProcessorErc20.sendTx removeByFee no token ' + this._tokenAddress @@ -292,7 +292,7 @@ export default class EthTransferProcessorErc20 return super.sendTx(tmpData, privateData, uiData); } - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + ' EthTxProcessorErc20.sendTx started token ' + this._tokenAddress @@ -310,7 +310,7 @@ export default class EthTransferProcessorErc20 this.checkError(e, data); } // @ts-ignore - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'EthTxProcessorErc20 encodeABI finished', tmpData.blockchainData ); diff --git a/crypto/blockchains/eth/apis/EthNftMatic.ts b/crypto/blockchains/eth/apis/EthNftMatic.ts index 0cbaf3156..b98fe00d9 100644 --- a/crypto/blockchains/eth/apis/EthNftMatic.ts +++ b/crypto/blockchains/eth/apis/EthNftMatic.ts @@ -3,7 +3,7 @@ */ import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; import BlocksoftUtils from '@crypto/common/AirDAOUtils'; -import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; const API_PATH = 'https://microscanners.trustee.deals/getAllNfts/'; @@ -70,7 +70,7 @@ export default async function (data) { one.desc.length > 20 ? one.desc.substring(0, 20) + '...' : one.desc; } } catch (e) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'EthTokenProcessorNft EthNftMatic name error ' + e.message ); } diff --git a/crypto/blockchains/eth/apis/EthNftOpensea.ts b/crypto/blockchains/eth/apis/EthNftOpensea.ts index b305ad678..aa03e7f81 100644 --- a/crypto/blockchains/eth/apis/EthNftOpensea.ts +++ b/crypto/blockchains/eth/apis/EthNftOpensea.ts @@ -3,7 +3,7 @@ */ import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; import BlocksoftUtils from '@crypto/common/AirDAOUtils'; -import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; const API_PATH = 'https://api.opensea.io/api/v1/'; const API_TEST_PATH = 'https://testnets-api.opensea.io/api/v1/'; @@ -89,7 +89,7 @@ export default async function (data) { one.desc.length > 20 ? one.desc.substring(0, 20) + '...' : one.desc; } } catch (e) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'EthTokenProcessorNft EthNftOpensea name error ' + e.message ); } @@ -108,7 +108,7 @@ export default async function (data) { one.contractSchema = tmp.asset_contract.schema_name; } } catch (e) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'EthTokenProcessorNft EthNftOpensea contract error ' + e.message ); } @@ -124,7 +124,7 @@ export default async function (data) { usdTotal = usdTotal + tmp.last_sale.payment_token.usd_price * 1; } } catch (e) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'EthTokenProcessorNft EthNftOpensealast_sale error ' + e.message, JSON.stringify(tmp) ); @@ -149,7 +149,7 @@ export default async function (data) { } } } catch (e) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'EthTokenProcessorNft EthNftOpensea collection error ' + e.message ); } diff --git a/crypto/blockchains/eth/basic/EthBasic.ts b/crypto/blockchains/eth/basic/EthBasic.ts index 492e55c46..d1355d832 100644 --- a/crypto/blockchains/eth/basic/EthBasic.ts +++ b/crypto/blockchains/eth/basic/EthBasic.ts @@ -2,7 +2,7 @@ * @version 0.5 * https://etherscan.io/apis#accounts */ -import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; import { Web3Injected } from '@crypto/services/Web3Injected'; export default class EthBasic { @@ -308,13 +308,13 @@ export default class EthBasic { checkError(e, data, txRBF = false, logData = {}) { if (e.message.indexOf('Transaction has been reverted by the EVM') !== -1) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'EthBasic checkError0.0 ' + e.message + ' for ' + data.addressFrom, logData ); throw new Error('SERVER_RESPONSE_REVERTED_BY_EVM'); } else if (e.message.indexOf('nonce too low') !== -1) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'EthBasic checkError0.1 ' + e.message + ' for ' + data.addressFrom, logData ); @@ -331,7 +331,7 @@ export default class EthBasic { e2.logData = { nonce }; throw e2; } else if (e.message.indexOf('gas required exceeds allowance') !== -1) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'EthBasic checkError0.2 ' + e.message + ' for ' + data.addressFrom, logData ); @@ -344,7 +344,7 @@ export default class EthBasic { throw new Error('SERVER_RESPONSE_TOO_MUCH_GAS_ETH_ERC20'); } } else if (e.message.indexOf('insufficient funds') !== -1) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'EthBasic checkError0.3 ' + e.message + ' for ' + data.addressFrom, logData ); @@ -358,13 +358,13 @@ export default class EthBasic { throw new Error('SERVER_RESPONSE_NOT_ENOUGH_FEE'); } } else if (e.message.indexOf('underpriced') !== -1) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'EthBasic checkError0.4 ' + e.message + ' for ' + data.addressFrom, logData ); throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_FEE'); } else if (e.message.indexOf('already known') !== -1) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'EthBasic checkError0.5 ' + e.message + ' for ' + data.addressFrom, logData ); @@ -372,7 +372,7 @@ export default class EthBasic { 'SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_FEE_FOR_REPLACEMENT' ); } else if (e.message.indexOf('infura') !== -1) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'EthBasic checkError0.6 ' + e.message + ' for ' + data.addressFrom, logData ); diff --git a/crypto/blockchains/eth/basic/EthNetworkPrices.ts b/crypto/blockchains/eth/basic/EthNetworkPrices.ts index c85f3b3fa..ee22975f2 100644 --- a/crypto/blockchains/eth/basic/EthNetworkPrices.ts +++ b/crypto/blockchains/eth/basic/EthNetworkPrices.ts @@ -1,7 +1,7 @@ /** * @version 0.5 */ -import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '../../../common/AirDAOCryptoLog'; import BlocksoftAxios from '../../../common/BlocksoftAxios'; import BlocksoftUtils from '../../../common/AirDAOUtils'; import BlocksoftExternalSettings from '../../../common/AirDAOExternalSettings'; @@ -39,14 +39,14 @@ class EthNetworkPrices { CACHE_PROXY_DATA.address === address && now - CACHE_PROXY_TIME < CACHE_PROXY_VALID_TIME ) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( mainCurrencyCode + ' EthNetworkPricesProvider.getWithProxy from cache', logData ); return CACHE_PROXY_DATA.result; } - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( mainCurrencyCode + ' EthNetworkPricesProvider.getWithProxy started', logData ); @@ -70,7 +70,7 @@ class EthNetworkPrices { if (checkResult !== false) { if (typeof checkResult.data !== 'undefined') { - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( mainCurrencyCode + ' EthNetworkPricesProvider.getWithProxy proxy checkResult1 ', checkResult.data @@ -84,7 +84,7 @@ class EthNetworkPrices { throw new Error(checkResult.data.msg); } } else { - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( mainCurrencyCode + ' EthNetworkPricesProvider.getWithProxy proxy checkResult2 ', checkResult @@ -184,7 +184,7 @@ class EthNetworkPrices { logData.resultFeeCacheTime = CACHE_FEES_ETH_TIME; logData.resultFee = JSON.stringify(CACHE_FEES_ETH); // noinspection ES6MissingAwait - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( mainCurrencyCode + ' EthNetworkPricesProvider.getOnlyFees used cache => ' + JSON.stringify(CACHE_FEES_ETH) @@ -192,7 +192,7 @@ class EthNetworkPrices { return this._format(); } - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( mainCurrencyCode + ' EthNetworkPricesProvider.getOnlyFees no cache load' ); @@ -206,7 +206,7 @@ class EthNetworkPrices { } logData.resultFeeSource = 'reloaded'; CACHE_PREV_DATA = tmp.data; - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( mainCurrencyCode + ' EthNetworkPricesProvider.getOnlyFees loaded new fee', CACHE_PREV_DATA @@ -214,14 +214,14 @@ class EthNetworkPrices { } else { logData.resultFeeSource = 'fromLoadCache'; link = 'prev'; - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( mainCurrencyCode + ' EthNetworkPricesProvider.getOnlyFees loaded prev fee as no fastest', CACHE_PREV_DATA ); } } catch (e) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( mainCurrencyCode + ' EthNetworkPricesProvider.getOnlyFees loaded prev fee as error', CACHE_PREV_DATA @@ -232,7 +232,7 @@ class EthNetworkPrices { try { await this._parseLoaded(mainCurrencyCode, CACHE_PREV_DATA, link); } catch (e) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( mainCurrencyCode + ' EthNetworkPricesProvider.getOnlyFees _parseLoaded error ' + e.message @@ -312,7 +312,7 @@ function addMultiply(mainCurrencyCode, blocks, fee, externalSettings) { externalSettings['ETH_CURRENT_PRICE_' + blocks] > 0 ) { CACHE_FEES_ETH[blocks] = externalSettings['ETH_CURRENT_PRICE_' + blocks]; - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( mainCurrencyCode + ' EthNetworkPricesProvider current price result', { blocks, @@ -329,7 +329,7 @@ function addMultiply(mainCurrencyCode, blocks, fee, externalSettings) { fee, externalSettings['ETH_MULTI_' + blocks] ); - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( mainCurrencyCode + ' EthNetworkPricesProvider addMultiply' + blocks + @@ -347,7 +347,7 @@ function addMultiply(mainCurrencyCode, blocks, fee, externalSettings) { ) { CACHE_FEES_ETH[blocks] = BlocksoftUtils.mul(fee, externalSettings.ETH_MULTI) * 1; - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( mainCurrencyCode + ' EthNetworkPricesProvider addMultiply result', { blocks, diff --git a/crypto/blockchains/eth/basic/EthTxSendProvider.ts b/crypto/blockchains/eth/basic/EthTxSendProvider.ts index e5db3b3e5..6a5c2fd63 100644 --- a/crypto/blockchains/eth/basic/EthTxSendProvider.ts +++ b/crypto/blockchains/eth/basic/EthTxSendProvider.ts @@ -3,7 +3,7 @@ * @version 0.32 */ import { AirDAOBlockchainTypes } from '../../AirDAOBlockchainTypes'; -import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '../../../common/AirDAOCryptoLog'; import BlocksoftUtils from '../../../common/AirDAOUtils'; import EthTmpDS from '../stores/EthTmpDS'; import EthRawDS from '../stores/EthRawDS'; @@ -41,7 +41,7 @@ export default class EthTxSendProvider { logData: any ): Promise<{ transactionHash: string; transactionJson: any }> { // @ts-ignore - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + ' EthTxSendProvider._innerSign started', logData ); @@ -79,7 +79,7 @@ export default class EthTxSendProvider { logData: any ): Promise<{ transactionHash: string; transactionJson: any }> { // @ts-ignore - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + ' EthTxSendProvider._innerSendTx started', logData ); @@ -87,11 +87,11 @@ export default class EthTxSendProvider { const rawTransaction = await this.sign(tx, privateData, txRBF, logData); // @ts-ignore - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + ' EthTxSendProvider._innerSendTx signed', tx ); - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + ' EthTxSendProvider._innerSendTx hex', rawTransaction ); @@ -124,7 +124,7 @@ export default class EthTxSendProvider { // const successProxy = baseURL + '/send/sendtx'; let checkResult = false; try { - // await BlocksoftCryptoLog.log( + // await AirDAOCryptoLog.log( // this._settings.currencyCode + // ' EthTxSendProvider.send proxy checkResult start ' + // proxy, @@ -137,7 +137,7 @@ export default class EthTxSendProvider { // marketingData: MarketingEvent.DATA // }); } catch (e) { - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + ' EthTxSendProvider.send proxy error checkResult ' + e.message @@ -146,7 +146,7 @@ export default class EthTxSendProvider { if (checkResult !== false) { if (typeof checkResult.data !== 'undefined') { - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + ' EthTxSendProvider.send proxy checkResult1 ', checkResult.data @@ -160,7 +160,7 @@ export default class EthTxSendProvider { throw new Error(checkResult.data.msg); } } else { - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + ' EthTxSendProvider.send proxy checkResult2 ', checkResult @@ -173,7 +173,7 @@ export default class EthTxSendProvider { ? JSON.parse(JSON.stringify(checkResult.data)) : false; - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + ' EthTxSendProvider.send will send' ); let result; @@ -187,7 +187,7 @@ export default class EthTxSendProvider { /** * curl http://matic.trusteeglobal.com:8545 -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","method":"eth_sendRawTransaction","params":["0x..."],"id":83}' */ - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + ' EthTxSendProvider.send sendSignedTransaction to ' + this._web3.LINK, @@ -200,7 +200,7 @@ export default class EthTxSendProvider { params: [rawTransaction], id: 1 }); - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + ' EthTxSendProvider.send sendSignedTransaction to ' + this._web3.LINK + @@ -232,7 +232,7 @@ export default class EthTxSendProvider { * "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", * "status": true, "to": "0xf1cff704c6e6ce459e3e1544a9533cccbdad7b99", "transactionHash": "0x1fa5646517b625d422863e6c27082104e1697543a6f912421527bb171c6173f2", "transactionIndex": 95} */ - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + ' EthTxSendProvider.send sendSignedTransaction ', rawTransaction @@ -249,21 +249,21 @@ export default class EthTxSendProvider { }; } else { sendLink = link; - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + ' EthTxSendProvider.send post ', rawTransaction ); result = await BlocksoftAxios.post(sendLink, rawTransaction); } // @ts-ignore - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + ' EthTxSendProvider.send result ', typeof result !== 'undefined' && result ? result.data : 'NO RESULT' ); } catch (e) { try { // logData.error = e.message; - // await BlocksoftCryptoLog.log( + // await AirDAOCryptoLog.log( // this._settings.currencyCode + // ' EthTxSendProvider.send proxy errorTx start ' + // errorProxy, @@ -275,13 +275,13 @@ export default class EthTxSendProvider { // logData, // marketingData: MarketingEvent.DATA // }); - // await BlocksoftCryptoLog.log( + // await AirDAOCryptoLog.log( // this._settings.currencyCode + ' EthTxSendProvider.send proxy errorTx', // typeof res2.data !== 'undefined' ? res2.data : res2 // ); // throw new Error('res2.data : ' + res2.data); } catch (e2) { - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + ' EthTxSendProvider.send proxy error errorTx ' + e2.message @@ -345,7 +345,7 @@ export default class EthTxSendProvider { checkResult = false; try { // logData.txHash = transactionHash; - // await BlocksoftCryptoLog.log( + // await AirDAOCryptoLog.log( // this._settings.currencyCode + // ' EthTxSendProvider.send proxy successTx start ' + // successProxy, @@ -358,7 +358,7 @@ export default class EthTxSendProvider { // marketingData: MarketingEvent.DATA // }); } catch (e3) { - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + ' EthTxSendProvider.send proxy error successTx ' + e3.message @@ -367,7 +367,7 @@ export default class EthTxSendProvider { if (checkResult !== false) { if (typeof checkResult.data !== 'undefined') { - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + ' EthTxSendProvider.send proxy successResult1 ', checkResult.data @@ -381,7 +381,7 @@ export default class EthTxSendProvider { throw new Error(checkResult.data.msg); } } else { - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + ' EthTxSendProvider.send proxy successResult2 ', checkResult @@ -417,7 +417,7 @@ export default class EthTxSendProvider { transactionLog: logData }); - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' EthTxSendProvider.send save nonce ' + nonce + diff --git a/crypto/blockchains/eth/forks/EthScannerProcessorSoul.ts b/crypto/blockchains/eth/forks/EthScannerProcessorSoul.ts index c7f883be5..3a170e482 100644 --- a/crypto/blockchains/eth/forks/EthScannerProcessorSoul.ts +++ b/crypto/blockchains/eth/forks/EthScannerProcessorSoul.ts @@ -2,9 +2,9 @@ * @version 0.5 */ import BlocksoftUtils from '../../../common/AirDAOUtils'; -import BlocksoftBN from '../../../common/BlocksoftBN'; +import BlocksoftBN from '../../../common/AirDAOBN'; import EthScannerProcessorErc20 from '../EthScannerProcessorErc20'; -import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '../../../common/AirDAOCryptoLog'; export default class EthScannerProcessorSoul extends EthScannerProcessorErc20 { /** @@ -39,7 +39,7 @@ export default class EthScannerProcessorSoul extends EthScannerProcessorErc20 { transaction = await this._unifyTransaction(address, tx, isInternal); } } catch (e) { - BlocksoftCryptoLog.error( + AirDAOCryptoLog.error( 'EthScannerProcessorSoul._unifyTransaction error ' + e.message + ' on ' + diff --git a/crypto/blockchains/eth/stores/EthRawDS.ts b/crypto/blockchains/eth/stores/EthRawDS.ts index 5e476587b..2a79f9c0b 100644 --- a/crypto/blockchains/eth/stores/EthRawDS.ts +++ b/crypto/blockchains/eth/stores/EthRawDS.ts @@ -5,7 +5,7 @@ */ import BlocksoftExternalSettings from '../../../common/AirDAOExternalSettings'; import BlocksoftAxios from '../../../common/BlocksoftAxios'; -import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '../../../common/AirDAOCryptoLog'; import { DatabaseTable } from '@appTypes'; import { Database } from '@database'; import { TransactionRawDBModel } from '@database/models/transactions-raw'; @@ -113,12 +113,12 @@ class EthRawDS { // logData: transactionLog, // marketingData: MarketingEvent.DATA // }); - // await BlocksoftCryptoLog.log( + // await AirDAOCryptoLog.log( // this._currencyCode + ' EthRawDS.send proxy success result', // JSON.parse(JSON.stringify(checkResult)) // ); // } catch (e3) { - // await BlocksoftCryptoLog.log( + // await AirDAOCryptoLog.log( // this._currencyCode + // ' EthRawDS.send proxy success error ' + // e3.message @@ -275,7 +275,7 @@ class EthRawDS { } async cleanRawHash(data) { - BlocksoftCryptoLog.log('EthRawDS cleanRawHash ', data); + AirDAOCryptoLog.log('EthRawDS cleanRawHash ', data); const now = new Date().toISOString(); const sql = `UPDATE ${tableName} @@ -288,7 +288,7 @@ class EthRawDS { } async cleanRaw(data) { - BlocksoftCryptoLog.log('EthRawDS cleanRaw ', data); + AirDAOCryptoLog.log('EthRawDS cleanRaw ', data); if (typeof data.currencyCode !== 'undefined') { this._currencyCode = diff --git a/crypto/blockchains/eth/stores/EthTmpDS.ts b/crypto/blockchains/eth/stores/EthTmpDS.ts index e5f17b9cc..be22714e3 100644 --- a/crypto/blockchains/eth/stores/EthTmpDS.ts +++ b/crypto/blockchains/eth/stores/EthTmpDS.ts @@ -4,7 +4,7 @@ import { TransactionScannersTmpDBModel, TransactionsDBModel } from '@database'; -import BlocksoftBN from '../../../common/BlocksoftBN'; +import BlocksoftBN from '../../../common/AirDAOBN'; import { DatabaseTable } from '@appTypes'; const tableName = DatabaseTable.TransactionScannersTmp; diff --git a/crypto/blockchains/fio/FioScannerProcessor.ts b/crypto/blockchains/fio/FioScannerProcessor.ts index 110905cf5..bf4a952fb 100644 --- a/crypto/blockchains/fio/FioScannerProcessor.ts +++ b/crypto/blockchains/fio/FioScannerProcessor.ts @@ -1,4 +1,4 @@ -import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '../../common/AirDAOCryptoLog'; import { getFioBalance, getTransactions } from './FioUtils.jts'; export default class FioScannerProcessor { @@ -41,7 +41,7 @@ export default class FioScannerProcessor { additionalData, walletHash: string ) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' FioScannerProcessor.getBalance (cache) started ' + address + @@ -62,7 +62,7 @@ export default class FioScannerProcessor { additionalData, walletHash: string ) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' FioScannerProcessor.getBalance started ' + address + @@ -86,7 +86,7 @@ export default class FioScannerProcessor { }) { const address = scanData.account.address.trim(); const walletHash = scanData.account.walletHash; - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' FioScannerProcessor.getTransactionsBlockchain started ' + address + diff --git a/crypto/blockchains/fio/FioSdkWrapper.ts b/crypto/blockchains/fio/FioSdkWrapper.ts index 3fd01f1e9..ecebab10c 100644 --- a/crypto/blockchains/fio/FioSdkWrapper.ts +++ b/crypto/blockchains/fio/FioSdkWrapper.ts @@ -6,7 +6,7 @@ import { FIOSDK } from '@fioprotocol/fiosdk'; import BlocksoftKeysStorage from '@crypto/actions/BlocksoftKeysStorage/BlocksoftKeysStorage'; import BlocksoftExternalSettings from '@crypto/common/AirDAOExternalSettings'; -import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; const fetchJson = async (uri, opts = {}) => { // eslint-disable-next-line no-undef @@ -43,7 +43,7 @@ export class FioSdkWrapper { const link = BlocksoftExternalSettings.getStatic('FIO_BASE_URL'); this.sdk = new FIOSDK(fioKey, publicKey, link, fetchJson); this.walletHash = walletHash; - BlocksoftCryptoLog.log(`FioSdkWrapper.inited for ${walletHash}`); + AirDAOCryptoLog.log(`FioSdkWrapper.inited for ${walletHash}`); } catch (e) {} return true; } diff --git a/crypto/blockchains/fio/FioUtils.ts b/crypto/blockchains/fio/FioUtils.ts index d5620450c..a2ec913dd 100644 --- a/crypto/blockchains/fio/FioUtils.ts +++ b/crypto/blockchains/fio/FioUtils.ts @@ -1,4 +1,4 @@ -import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '../../common/AirDAOCryptoLog'; import { getFioSdk } from './FioSdkWrapper'; import BlocksoftAxios from '../../common/BlocksoftAxios'; import { Fio } from '@fioprotocol/fiojs'; @@ -135,7 +135,7 @@ export const getSentFioRequests = async ( offset = 0 ) => { try { - BlocksoftCryptoLog.log(`FIO getSentFioRequests started ${fioPublicKey}`); + AirDAOCryptoLog.log(`FIO getSentFioRequests started ${fioPublicKey}`); const response = await getFioSdk().getSentFioRequests(limit, offset); const requests = response['requests'] || []; return requests.sort( @@ -153,7 +153,7 @@ export const getPendingFioRequests = async ( offset = 0 ) => { try { - BlocksoftCryptoLog.log(`FIO getPendingFioRequests started ${fioPublicKey}`); + AirDAOCryptoLog.log(`FIO getPendingFioRequests started ${fioPublicKey}`); const response = await getFioSdk().getPendingFioRequests(limit, offset); const requests = response['requests'] || []; return requests.sort( @@ -191,7 +191,7 @@ export const addCryptoPublicAddress = async ({ ); const isOK = response['status'] === 'OK'; if (!isOK) { - await BlocksoftCryptoLog.log('FIO addPublicAddress error', response); + await AirDAOCryptoLog.log('FIO addPublicAddress error', response); } return isOK; } catch (e) { @@ -218,7 +218,7 @@ export const addCryptoPublicAddresses = async ({ ); if (response['status'] !== 'OK') { - await BlocksoftCryptoLog.log('FIO addPublicAddress error', response); + await AirDAOCryptoLog.log('FIO addPublicAddress error', response); isOK = false; } } catch (e) { @@ -249,7 +249,7 @@ export const removeCryptoPublicAddresses = async ({ ); if (response['status'] !== 'OK') { - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( 'FIO removeCryptoPublicAddresses error', response ); @@ -283,7 +283,7 @@ export const requestFunds = async ({ memo }) => { try { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( `FIO requestFunds started ${payerFioAddress} -> ${payeeFioAddress} ${amount} ${tokenCode} (${chainCode})` ); const { fee = 0 } = await getFioSdk().getFeeForNewFundsRequest( @@ -304,7 +304,7 @@ export const requestFunds = async ({ null ); - await BlocksoftCryptoLog.log('FIO requestFunds result', response); + await AirDAOCryptoLog.log('FIO requestFunds result', response); return response; } catch (e) { formatError('FIO requestFunds', e); @@ -436,12 +436,12 @@ const formatError = (title, e) => { e.message.indexOf('Error 404') === -1 && e.message.indexOf('Network request failed') === -1 ) { - BlocksoftCryptoLog.err(title + ' error ' + e.message, e.json || false); + AirDAOCryptoLog.err(title + ' error ' + e.message, e.json || false); } else { const msg = title + ' 404 notice ' + e.message + ' ' + JSON.stringify(e.json); if (msg.indexOf('No FIO Requests') === -1) { - BlocksoftCryptoLog.log(msg); + AirDAOCryptoLog.log(msg); } } }; diff --git a/crypto/blockchains/metis/MetisScannerProcessor.ts b/crypto/blockchains/metis/MetisScannerProcessor.ts index 757f12d5c..c9161f514 100644 --- a/crypto/blockchains/metis/MetisScannerProcessor.ts +++ b/crypto/blockchains/metis/MetisScannerProcessor.ts @@ -1,7 +1,7 @@ /** * @version 0.5 */ -import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '../../common/AirDAOCryptoLog'; import EthScannerProcessor from '@crypto/blockchains/eth/EthScannerProcessor'; interface UnifiedTransaction { @@ -21,7 +21,7 @@ export default class MetisScannerProcessor extends EthScannerProcessor { account: { address: string }; }): Promise { const address = scanData.account.address; - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + ' MetisScannerProcessor.getTransactions started ' + address diff --git a/crypto/blockchains/one/OneScannerProcessor.ts b/crypto/blockchains/one/OneScannerProcessor.ts index 295930f21..3801fdef8 100644 --- a/crypto/blockchains/one/OneScannerProcessor.ts +++ b/crypto/blockchains/one/OneScannerProcessor.ts @@ -2,7 +2,7 @@ * https://docs.harmony.one/home/developers/api/methods/account-methods/hmy_getbalance * https://docs.harmony.one/home/developers/api/methods/transaction-related-methods/hmy_gettransactionshistory#api-v2 */ -import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; import BlocksoftExternalSettings from '@crypto/common/AirDAOExternalSettings'; import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; import OneUtils from '@crypto/blockchains/one/ext/OneUtils'; @@ -28,7 +28,7 @@ export default class OneScannerProcessor { walletHash: string ) { const oneAddress = OneUtils.toOneAddress(address); - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' OneScannerProcessor.getBalanceBlockchain started ' + address + @@ -59,7 +59,7 @@ export default class OneScannerProcessor { unconfirmed: 0 }; } catch (e) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' OneScannerProcessor.getBalanceBlockchain address ' + address + @@ -82,7 +82,7 @@ export default class OneScannerProcessor { }) { const { address } = scanData.account; const oneAddress = OneUtils.toOneAddress(address); - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' OneScannerProcessor.getTransactionsBlockchain started ' + address + @@ -127,7 +127,7 @@ export default class OneScannerProcessor { } return transactions; } catch (e) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' OneScannerProcessor.getTransactionsBlockchain address ' + address + diff --git a/crypto/blockchains/one/OneScannerProcessorErc20.ts b/crypto/blockchains/one/OneScannerProcessorErc20.ts index c11eb89e0..902d9f3b1 100644 --- a/crypto/blockchains/one/OneScannerProcessorErc20.ts +++ b/crypto/blockchains/one/OneScannerProcessorErc20.ts @@ -2,7 +2,7 @@ * https://docs.harmony.one/home/developers/api/methods/account-methods/hmy_getbalance * https://docs.harmony.one/home/developers/api/methods/transaction-related-methods/hmy_gettransactionshistory#api-v2 */ -import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; import BlocksoftExternalSettings from '@crypto/common/AirDAOExternalSettings'; import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; import OneUtils from '@crypto/blockchains/one/ext/OneUtils'; @@ -25,7 +25,7 @@ export default class OneScannerProcessorErc20 extends EthScannerProcessorErc20 { }) { const { address } = scanData.account; const oneAddress = OneUtils.toOneAddress(address); - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' OneScannerProcessorErc20.getTransactionsBlockchain started ' + address + @@ -92,7 +92,7 @@ export default class OneScannerProcessorErc20 extends EthScannerProcessorErc20 { await OneTmpDS.saveCache(address, this._tokenAddress, firstTransaction); return transactions; } catch (e) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' OneScannerProcessorErc20.getTransactionsBlockchain address ' + address + diff --git a/crypto/blockchains/sol/SolScannerProcessor.ts b/crypto/blockchains/sol/SolScannerProcessor.ts index 543afdad9..59b44db43 100644 --- a/crypto/blockchains/sol/SolScannerProcessor.ts +++ b/crypto/blockchains/sol/SolScannerProcessor.ts @@ -1,7 +1,7 @@ /** * @version 0.52 */ -import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; import BlocksoftUtils from '@crypto/common/AirDAOUtils'; import BlocksoftExternalSettings from '@crypto/common/AirDAOExternalSettings'; @@ -45,7 +45,7 @@ export default class SolScannerProcessor { address: string ): Promise<{ balance: number; provider: string } | false> { address = address.trim(); - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' SolScannerProcessor getBalanceBlockchain address ' + address @@ -78,7 +78,7 @@ export default class SolScannerProcessor { } balance = res.data.result.value; } catch (e: any) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' SolScannerProcessor getBalanceBlockchain address ' + address + @@ -124,14 +124,14 @@ export default class SolScannerProcessor { res.data.result, lastHashVar ); - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' SolScannerProcessor.getTransactions finished ' + address ); return transactions; } catch (e: any) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' SolScannerProcessor getTransactionsBlockchain address ' + address + @@ -175,7 +175,7 @@ export default class SolScannerProcessor { e.message ); } - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' SolScannerProcessor._unifyTransactions ' + tx.signature + diff --git a/crypto/blockchains/sol/SolScannerProcessorSpl.ts b/crypto/blockchains/sol/SolScannerProcessorSpl.ts index 5e63afa04..10c3e268d 100644 --- a/crypto/blockchains/sol/SolScannerProcessorSpl.ts +++ b/crypto/blockchains/sol/SolScannerProcessorSpl.ts @@ -1,7 +1,7 @@ /** * @version 0.52 */ -import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; import BlocksoftExternalSettings from '@crypto/common/AirDAOExternalSettings'; import SolScannerProcessor from '@crypto/blockchains/sol/SolScannerProcessor'; @@ -34,7 +34,7 @@ export default class SolScannerProcessorSpl extends SolScannerProcessor { address: string ): Promise<{ balance: number; provider: string } | false> { address = address.trim(); - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' SolScannerProcessorSpl getBalanceBlockchain address ' + address @@ -103,7 +103,7 @@ export default class SolScannerProcessorSpl extends SolScannerProcessor { balance = CACHE_BALANCES[address][this.tokenAddress].amount; } } catch (e: any) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ` SolScannerProcessorSpl getBalanceBlockchain address ` + `${address} ` + diff --git a/crypto/blockchains/sol/SolTokenProcessor.ts b/crypto/blockchains/sol/SolTokenProcessor.ts index ed62c97b6..788509eff 100644 --- a/crypto/blockchains/sol/SolTokenProcessor.ts +++ b/crypto/blockchains/sol/SolTokenProcessor.ts @@ -3,7 +3,7 @@ */ import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; import BlocksoftExternalSettings from '@crypto/common/AirDAOExternalSettings'; -import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; import { AxiosResponse } from 'axios'; interface TokenDetails { @@ -101,7 +101,7 @@ export default class SolTokenProcessor { } decimals = response.data.result.value.data.parsed.info.decimals; } catch (e: any) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'SolTokenProcessor getTokenDetails tokenAddress ' + tokenAddress + ' error ' + diff --git a/crypto/blockchains/sol/SolTransferProcessor.ts b/crypto/blockchains/sol/SolTransferProcessor.ts index 1556924c5..6dd494a15 100644 --- a/crypto/blockchains/sol/SolTransferProcessor.ts +++ b/crypto/blockchains/sol/SolTransferProcessor.ts @@ -1,7 +1,7 @@ /** * @version 0.52 */ -import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; import BlocksoftUtils from '@crypto/common/AirDAOUtils'; import BlocksoftExternalSettings from '@crypto/common/AirDAOExternalSettings'; import BlocksoftBalances from '@crypto/actions/BlocksoftBalances/BlocksoftBalances'; @@ -73,7 +73,7 @@ export default class SolTransferProcessor try { const beachPath = 'https://public-api.solanabeach.io/v1/account/' + address + '?'; - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' SolTransferProcessor.getTransferAllBalance address ' + address + @@ -81,7 +81,7 @@ export default class SolTransferProcessor beachPath ); const res = await BlocksoftAxios.get(beachPath); - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' SolTransferProcessor.getTransferAllBalance address ' + address + @@ -104,7 +104,7 @@ export default class SolTransferProcessor } } } catch (e) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' SolTransferProcessor.getTransferAllBalance address ' + address + @@ -114,7 +114,7 @@ export default class SolTransferProcessor } // @ts-ignore - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + ' SolTransferProcessor.getTransferAllBalance ', data.addressFrom + ' => ' + balance @@ -201,7 +201,7 @@ export default class SolTransferProcessor ); } } else if (data.addressTo === 'STAKE') { - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + ' SolTransferProcessor.sendTx ' + data.addressFrom + @@ -216,7 +216,7 @@ export default class SolTransferProcessor if (typeof validator === 'undefined' || !validator) { throw new Error('no validator field'); } - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + ' SolTransferProcessor.sendTx ' + data.addressFrom + @@ -231,7 +231,7 @@ export default class SolTransferProcessor // https://explorer.solana.com/tx/2ffmtkj3Yj51ZWCEHG6jb6s78F73eoiQdqURV7z65kSVLiPcm8Y9NE45FgfgwbddJD8kfgCiTpmrEu7J8WKpAQeE await SolStakeUtils.getAccountStaked(data.addressFrom); - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + ' SolTransferProcessor.sendTx ' + data.addressFrom + @@ -261,7 +261,7 @@ export default class SolTransferProcessor StakeProgram.programId ) } catch (e1) { - await BlocksoftCryptoLog.log(this._settings.currencyCode + ' SolTransferProcessor.sendTx ' + data.addressFrom + ' => ' + data.addressTo + ' ' + data.amount + ' build createWithSeed error ' + e1.message) + await AirDAOCryptoLog.log(this._settings.currencyCode + ' SolTransferProcessor.sendTx ' + data.addressFrom + ' => ' + data.addressTo + ' ' + data.amount + ' build createWithSeed error ' + e1.message) }*/ const buffer = Buffer.concat([ @@ -286,7 +286,7 @@ export default class SolTransferProcessor break; } } - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + ' SolTransferProcessor.sendTx ' + data.addressFrom + @@ -301,7 +301,7 @@ export default class SolTransferProcessor throw new Error('Stake address seed is not found'); } - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' SolTransferProcessor.sendTx ' + data.addressFrom + @@ -359,7 +359,7 @@ export default class SolTransferProcessor ); console.log(e); } - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' SolTransferProcessor.sendTx ' + data.addressFrom + @@ -390,7 +390,7 @@ export default class SolTransferProcessor return { rawOnly: uiData.selectedFee.rawOnly, raw: signedData }; } - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' SolTransferProcessor.sendTx ' + data.addressFrom + @@ -404,7 +404,7 @@ export default class SolTransferProcessor const result = {} as AirDAOBlockchainTypes.SendTxResult; try { const sendRes = await SolUtils.sendTransaction(signedData); - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' SolTransferProcessor.sendTx ' + data.addressFrom + @@ -442,7 +442,7 @@ export default class SolTransferProcessor ); console.log(e); } - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' SolTransferProcessor.sendTx ' + data.addressFrom + diff --git a/crypto/blockchains/sol/SolTransferProcessorSpl.ts b/crypto/blockchains/sol/SolTransferProcessorSpl.ts index 1d6bde47d..d5bdc434d 100644 --- a/crypto/blockchains/sol/SolTransferProcessorSpl.ts +++ b/crypto/blockchains/sol/SolTransferProcessorSpl.ts @@ -3,7 +3,7 @@ */ import { BlocksoftBlockchainTypes } from '@crypto/blockchains/BlocksoftBlockchainTypes'; -import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; import { PublicKey, @@ -68,7 +68,7 @@ export default class SolTransferProcessorSpl ): Promise { const balance = data.amount; // @ts-ignore - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + ` SolTransferProcessorSpl.getTransferAllBalance, ${data.addressFrom} + ' => ' + ${balance}` @@ -224,7 +224,7 @@ export default class SolTransferProcessorSpl }; } - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' SolTransferProcessorSpl.sendTx ' + data.addressFrom + @@ -268,7 +268,7 @@ export default class SolTransferProcessorSpl }) ); } catch (e: any) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' SolTransferProcessorSpl.sendTx ' + data.addressFrom + @@ -290,7 +290,7 @@ export default class SolTransferProcessorSpl // @ts-ignore const signedData = tx.serialize().toString('base64'); - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' SolTransferProcessorSpl.sendTx ' + data.addressFrom + @@ -304,7 +304,7 @@ export default class SolTransferProcessorSpl const result = {} as BlocksoftBlockchainTypes.SendTxResult; try { const sendRes = await SolUtils.sendTransaction(signedData); - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' SolTransferProcessorSpl.sendTx ' + data.addressFrom + @@ -323,7 +323,7 @@ export default class SolTransferProcessorSpl } result.transactionHash = sendRes; } catch (e: any) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' SolTransferProcessorSpl.sendTx ' + data.addressFrom + diff --git a/crypto/blockchains/sol/ext/SolStakeUtils.ts b/crypto/blockchains/sol/ext/SolStakeUtils.ts index 1d75c8c82..2d7a3fd48 100644 --- a/crypto/blockchains/sol/ext/SolStakeUtils.ts +++ b/crypto/blockchains/sol/ext/SolStakeUtils.ts @@ -4,7 +4,7 @@ import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; import BlocksoftExternalSettings from '@crypto/common/AirDAOExternalSettings'; -import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; import BlocksoftUtils from '@crypto/common/AirDAOUtils'; import SolUtils from '@crypto/blockchains/sol/ext/SolUtils'; import config from '@constants/config'; @@ -139,9 +139,7 @@ export default { if (config.debug.cryptoErrors) { console.log('SolStakeUtils.getVoteAddresses error ' + e.message); } - BlocksoftCryptoLog.log( - 'SolStakeUtils.getVoteAddresses error ' + e.message - ); + AirDAOCryptoLog.log('SolStakeUtils.getVoteAddresses error ' + e.message); } return CACHE_VOTES.data; }, @@ -238,7 +236,7 @@ export default { 'SolStakeUtils.getAccountRewards ' + address + ' error ' + e.message ); } - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'SolStakeUtils.getAccountRewards ' + address + ' error ' + e.message ); } @@ -331,7 +329,7 @@ export default { CACHE_STAKED[address].all[item.stakeAddress] = true; } } catch (e: any) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'SolStakeUtils getAccountStaked request ' + apiPath + ' error ' + @@ -398,7 +396,7 @@ export default { 'SolStakeUtils.getAccountStaked ' + address + ' error ' + e.message ); } - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'SolStakeUtils.getAccountStaked ' + address + ' error ' + e.message ); return CACHE_STAKED[address].active; diff --git a/crypto/blockchains/sol/ext/SolUtils.ts b/crypto/blockchains/sol/ext/SolUtils.ts index 6555b87af..77a3c98ee 100644 --- a/crypto/blockchains/sol/ext/SolUtils.ts +++ b/crypto/blockchains/sol/ext/SolUtils.ts @@ -7,7 +7,7 @@ import BlocksoftExternalSettings from '@crypto/common/AirDAOExternalSettings'; import { PublicKey } from '@solana/web3.js/src'; import { Account } from '@solana/web3.js/src/account'; -import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; // @ts-ignore import settingsActions from '@app/appstores/Stores/Settings/SettingsActions'; import config from '@constants/config'; @@ -90,7 +90,7 @@ export default { 'SolUtils.getAccountInfo ' + address + ' error ' + e.message ); } - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'SolUtils.getAccountInfo ' + address + ' error ' + e.message ); } @@ -162,7 +162,7 @@ export default { } return sendRes2.data.result; } catch (e) { - await BlocksoftCryptoLog.log(e); + await AirDAOCryptoLog.log(e); } } @@ -215,7 +215,7 @@ export default { } CACHE_EPOCH.ts = now; } catch (e: any) { - BlocksoftCryptoLog.log('SolUtils.getEpoch error ' + e.message); + AirDAOCryptoLog.log('SolUtils.getEpoch error ' + e.message); // nothing } return CACHE_EPOCH.value; diff --git a/crypto/blockchains/trx/TrxScannerProcessor.ts b/crypto/blockchains/trx/TrxScannerProcessor.ts index 2e30e1632..f3af1d4f7 100644 --- a/crypto/blockchains/trx/TrxScannerProcessor.ts +++ b/crypto/blockchains/trx/TrxScannerProcessor.ts @@ -7,7 +7,7 @@ import TrxTronscanProvider from './basic/TrxTronscanProvider'; import TrxTrongridProvider from './basic/TrxTrongridProvider'; import TrxTransactionsProvider from './basic/TrxTransactionsProvider'; import TrxTransactionsTrc20Provider from './basic/TrxTransactionsTrc20Provider'; -import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '../../common/AirDAOCryptoLog'; import Database from '@app/appstores/DataSource/Database/main'; import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; import BlocksoftUtils from '@crypto/common/AirDAOUtils'; @@ -68,7 +68,7 @@ export default class TrxScannerProcessor { source: string ): Promise { address = address.trim(); - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._tokenName + ' TrxScannerProcessor getBalanceBlockchain address ' + address + @@ -91,7 +91,7 @@ export default class TrxScannerProcessor { this._tokenName, source === 'AccountScreen' ); - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._tokenName + ' TrxScannerProcessor getBalanceBlockchain address ' + address + @@ -121,7 +121,7 @@ export default class TrxScannerProcessor { typeof tmp.data !== 'undefined' && typeof tmp.data.constant_result !== 'undefined' ) { - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._tokenName + ' TrxScannerProcessor getBalanceBlockchain address ' + address + @@ -143,7 +143,7 @@ export default class TrxScannerProcessor { }; } } catch (e: any) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._tokenName + ' TrxScannerProcessor getBalanceBlockchain address ' + address + @@ -159,7 +159,7 @@ export default class TrxScannerProcessor { source === 'AccountScreen' ); } - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._tokenName + ' TrxScannerProcessor getBalanceBlockchain address ' + address + @@ -176,7 +176,7 @@ export default class TrxScannerProcessor { '_', source === 'AccountScreen' ); - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._tokenName + ' TrxScannerProcessor getBalanceBlockchain address ' + address + @@ -187,7 +187,7 @@ export default class TrxScannerProcessor { ); if (subresult) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._tokenName + ' TrxScannerProcessor getBalanceBlockchain address ' + address + @@ -222,7 +222,7 @@ export default class TrxScannerProcessor { async getResourcesBlockchain(address: string): Promise { address = address.trim(); - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._tokenName + ' TrxScannerProcessor getResourcesBlockchain address ' + address @@ -400,7 +400,7 @@ export default class TrxScannerProcessor { specialActionNeeded } = unique[address]; if (confirmations < 20) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' TrxScannerProcessor.getTransactionsPendingBlockchain vote all skipped by ' + confirmations + @@ -410,7 +410,7 @@ export default class TrxScannerProcessor { continue; } - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' TrxScannerProcessor.getTransactionsPendingBlockchain vote all inited for ' + address + @@ -430,7 +430,7 @@ export default class TrxScannerProcessor { UPDATE transactions SET special_action_needed='' WHERE special_action_needed='vote' OR special_action_needed='vote_after_unfreeze' AND address_from_basic='${address}' `); - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' TrxScannerProcessor.getTransactionsPendingBlockchain vote all finished for ' + address @@ -446,7 +446,7 @@ export default class TrxScannerProcessor { e.message ); } - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' TrxScannerProcessor.getTransactionsPendingBlockchain vote all error for ' + address + diff --git a/crypto/blockchains/trx/TrxTransferProcessor.ts b/crypto/blockchains/trx/TrxTransferProcessor.ts index a8394e15a..c3f4e91d6 100644 --- a/crypto/blockchains/trx/TrxTransferProcessor.ts +++ b/crypto/blockchains/trx/TrxTransferProcessor.ts @@ -2,7 +2,7 @@ * @version 0.20 */ import BlocksoftAxios from '../../common/BlocksoftAxios'; -import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '../../common/AirDAOCryptoLog'; import BlocksoftUtils from '../../common/AirDAOUtils'; import TronUtils from './ext/TronUtils'; @@ -112,7 +112,7 @@ export default class TrxTransferProcessor ): Promise { const addressHexTo = TronUtils.addressToHex(data.addressTo); if (TronUtils.addressHexToStr(addressHexTo) !== data.addressTo) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'TrxTransferProcessor.getFeeRateOld check address ' + data.addressTo + ' hex ' + @@ -160,7 +160,7 @@ export default class TrxTransferProcessor res ); } - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' TrxTransferProcessor.getFeeRate ' + link + @@ -204,7 +204,7 @@ export default class TrxTransferProcessor e.message ); } - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' TrxTransferProcessor.getFeeRate new error ' + e.message @@ -233,7 +233,7 @@ export default class TrxTransferProcessor const res = await BlocksoftBalances.setCurrencyCode('TRX') .setAddress(data.addressFrom) .getResources('TrxSendTx'); - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + ' TrxTransferProcessor.getFeeRate result resources from ' + data.addressFrom, @@ -328,7 +328,7 @@ export default class TrxTransferProcessor energyFee; } } - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + ' TrxTransferProcessor.getFeeRate feeForTx ' + feeForTx + @@ -352,7 +352,7 @@ export default class TrxTransferProcessor e.message ); } - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' TrxTransferProcessor.getFeeRate addressFrom data error ' + e.message @@ -386,7 +386,7 @@ export default class TrxTransferProcessor const tronData2 = res2.data; delete tronData2.assetNetUsed; delete tronData2.assetNetLimit; - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + ' TrxTransferProcessor.getFeeRate result ' + link + @@ -406,7 +406,7 @@ export default class TrxTransferProcessor e.message ); } - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' TrxTransferProcessor.getFeeRate addressTo data error ' + e.message @@ -466,7 +466,7 @@ export default class TrxTransferProcessor e.message ); } - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' TrxTransferProcessor.getFeeRate error ' + e.message @@ -483,7 +483,7 @@ export default class TrxTransferProcessor data.isTransferAll = true; const balance = data.amount; // @ts-ignore - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + ` TrxTransferProcessor.getTransferAllBalance ', ${data.addressFrom + ' => ' + balance}` @@ -539,7 +539,7 @@ export default class TrxTransferProcessor throw new Error('SERVER_RESPONSE_NOT_ENOUGH_FEE'); } - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + ' TrxTransferProcessor.sendTx started ' + data.addressFrom + @@ -666,7 +666,7 @@ export default class TrxTransferProcessor res = await BlocksoftAxios.post(link, params); tx = res.data.transaction; - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + ' TrxTxProcessor.sendSubTx tx', tx ); @@ -677,7 +677,7 @@ export default class TrxTransferProcessor Buffer.from(privateData.privateKey, 'hex') ) ]; - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + ' TrxTxProcessor.sendSubTx signed', tx ); @@ -690,7 +690,7 @@ export default class TrxTransferProcessor false, logData ); - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + ' TrxTxProcessor.sendSubTx broadcasted' ); @@ -703,7 +703,7 @@ export default class TrxTransferProcessor uiData ); } - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' TrxTransferProcessor.sendSubTx error ' + e.message @@ -740,7 +740,7 @@ export default class TrxTransferProcessor typeof recheck.data.receipt.result !== undefined ) { // @ts-ignore - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' TrxTransferProcessor.sendSubTx recheck ', { @@ -772,7 +772,7 @@ export default class TrxTransferProcessor e1 ); } - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' TRX transaction recheck error ' + e1.message @@ -804,7 +804,7 @@ export default class TrxTransferProcessor } if (TronUtils.addressHexToStr(toAddress) !== data.addressTo) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'TrxTransferProcessor.sendTx heck address ' + data.addressTo + ' hex ' + @@ -842,7 +842,7 @@ export default class TrxTransferProcessor ), call_value: 0 }; - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + `' TrxTransferProcessor.sendTx inited1' + ${data.addressFrom} + @@ -870,7 +870,7 @@ export default class TrxTransferProcessor } try { - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + `' TrxTransferProcessor.sendTx inited2' + ${data.addressFrom} + @@ -882,7 +882,7 @@ export default class TrxTransferProcessor ); res = await BlocksoftAxios.post(link, params); } catch (e: any) { - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + ' TrxTransferProcessor.sendTx result2' + data.addressFrom + @@ -907,7 +907,7 @@ export default class TrxTransferProcessor // @ts-ignore if (typeof res.data.Error !== 'undefined') { - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + `' TrxTransferProcessor.sendTx error ' + ${data.addressFrom} + @@ -963,7 +963,7 @@ export default class TrxTransferProcessor } } - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + ' TrxTxProcessor.sendTx token ' + this._tokenName + @@ -986,7 +986,7 @@ export default class TrxTransferProcessor return { rawOnly: uiData.selectedFee.rawOnly, raw: JSON.stringify(tx) }; } - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + ' TrxTxProcessor.sendTx signed', tx ); @@ -994,7 +994,7 @@ export default class TrxTransferProcessor let result = {} as AirDAOBlockchainTypes.SendTxResult; try { result = await this.sendProvider.sendTx(tx, '', false, logData); - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + ' TrxTxProcessor.sendTx broadcasted' ); } catch (e: any) { @@ -1005,7 +1005,7 @@ export default class TrxTransferProcessor uiData ); } - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' TrxTransferProcessor.sendTx error ' + e.message diff --git a/crypto/blockchains/trx/basic/TrxNodeInfoProvider.ts b/crypto/blockchains/trx/basic/TrxNodeInfoProvider.ts index 13b5aa505..9c765c540 100644 --- a/crypto/blockchains/trx/basic/TrxNodeInfoProvider.ts +++ b/crypto/blockchains/trx/basic/TrxNodeInfoProvider.ts @@ -1,5 +1,5 @@ import BlocksoftAxios from '../../../common/BlocksoftAxios'; -import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '../../../common/AirDAOCryptoLog'; import BlocksoftExternalSettings from '@crypto/common/AirDAOExternalSettings'; const INFO_MAX_TRY = 50; // max tries before error appear in axios get @@ -29,18 +29,18 @@ export default class TrxNodeInfoProvider { if (info > CACHE_LAST_BLOCK) { CACHE_LAST_BLOCK = info; } - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'TrxNodeInfoProvider.getLastBlock currentBlock ' + JSON.stringify(info) ); } else { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'TrxNodeInfoProvider.getLastBlock currentBlock warning ' + JSON.stringify(info) ); } } catch (e: any) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'TrxNodeInfoProvider.getLastBlock currentBlock error ' + e.message ); } diff --git a/crypto/blockchains/trx/basic/TrxTransactionsProvider.ts b/crypto/blockchains/trx/basic/TrxTransactionsProvider.ts index 8b03e2941..738ea17e5 100644 --- a/crypto/blockchains/trx/basic/TrxTransactionsProvider.ts +++ b/crypto/blockchains/trx/basic/TrxTransactionsProvider.ts @@ -1,7 +1,7 @@ /** * @version 0.5 */ -import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '../../../common/AirDAOCryptoLog'; import BlocksoftAxios from '../../../common/BlocksoftAxios'; import BlocksoftUtils from '../../../common/AirDAOUtils'; import TrxNodeInfoProvider from './TrxNodeInfoProvider'; @@ -53,7 +53,7 @@ export default class TrxTransactionsProvider { now - CACHE_OF_TRANSACTIONS[address].time < CACHE_VALID_TIME ) { if (typeof CACHE_OF_TRANSACTIONS[address][tokenName] !== 'undefined') { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( ` TrxTransactionsProvider.get from cache', ${address} + ' => ' + ${tokenName}` ); @@ -85,7 +85,7 @@ export default class TrxTransactionsProvider { try { tmp = await this._unifyTransaction(scanData, tx, tokenName); } catch (e: any) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'TrxTransactionsProvider.get unify error ' + e.message + ' tx ' + @@ -245,7 +245,7 @@ export default class TrxTransactionsProvider { // freeze = 11, vote = 4, claim = 13 } else { // noinspection ES6MissingAwait - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'TrxTransactionsProvider._unifyTransaction buggy tx ' + JSON.stringify(transaction) ); diff --git a/crypto/blockchains/trx/basic/TrxTransactionsTrc20Provider.ts b/crypto/blockchains/trx/basic/TrxTransactionsTrc20Provider.ts index 849a9644f..d79a7d9e9 100644 --- a/crypto/blockchains/trx/basic/TrxTransactionsTrc20Provider.ts +++ b/crypto/blockchains/trx/basic/TrxTransactionsTrc20Provider.ts @@ -4,7 +4,7 @@ */ import TrxTransactionsProvider from './TrxTransactionsProvider'; import BlocksoftUtils from '../../../common/AirDAOUtils'; -import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '../../../common/AirDAOCryptoLog'; import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; import Database from '@app/appstores/DataSource/Database/main'; import TransactionFilterTypeDict from '@appV2/dicts/transactionFilterTypeDict'; @@ -80,7 +80,7 @@ export default class TrxTransactionsTrc20Provider extends TrxTransactionsProvide } if (typeof transaction.amount === 'undefined') { // noinspection ES6MissingAwait - BlocksoftCryptoLog.err( + AirDAOCryptoLog.err( 'TrxTransactionsTrc20Provider._unifyTransaction buggy tx ' + JSON.stringify(transaction) ); @@ -163,7 +163,7 @@ export default class TrxTransactionsTrc20Provider extends TrxTransactionsProvide ` SELECT * FROM transactions WHERE transaction_hash='${res.transactionHash}' AND currency_code='TRX' ` ); if (!savedTRX || !savedTRX.array || savedTRX.array.length === 0) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'TrxTransactionsTrc20Provider._unifyTransaction added fee for ' + res.transactionHash + ' amount ' + diff --git a/crypto/blockchains/trx/basic/TrxTrongridProvider.ts b/crypto/blockchains/trx/basic/TrxTrongridProvider.ts index e1e8a1b88..e23075a1a 100644 --- a/crypto/blockchains/trx/basic/TrxTrongridProvider.ts +++ b/crypto/blockchains/trx/basic/TrxTrongridProvider.ts @@ -2,7 +2,7 @@ * @version 0.5 * https://github.com/tronscan/tronscan-frontend/wiki/TRONSCAN-API */ -import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '../../../common/AirDAOCryptoLog'; import BlocksoftAxios from '../../../common/BlocksoftAxios'; import BlocksoftExternalSettings from '@crypto/common/AirDAOExternalSettings'; import TronUtils from '@crypto/blockchains/trx/ext/TronUtils'; @@ -62,7 +62,7 @@ export default class TrxTrongridProvider { now - CACHE_TRONGRID[address].time < CACHE_VALID_TIME ) { if (typeof CACHE_TRONGRID[address][tokenName] !== 'undefined') { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( `TrxTrongridProvider.get from cache', ${address} + ' => ' + @@ -133,7 +133,7 @@ export default class TrxTrongridProvider { const nodeLink = BlocksoftExternalSettings.getStatic('TRX_SOLIDITY_NODE'); const link = nodeLink + '/walletsolidity/getaccount'; const params = { address }; - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'TrxTrongridProvider.get ' + link + ' ' + JSON.stringify(params) ); const res: { data: any } = await BlocksoftAxios.postWithoutBraking( @@ -285,7 +285,7 @@ export default class TrxTrongridProvider { const tronData = res.data; delete tronData.assetNetUsed; delete tronData.assetNetLimit; - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( 'TrxTrongridProvider.assets result ' + link + ' from ' + address, tronData ); diff --git a/crypto/blockchains/trx/basic/TrxTronscanProvider.ts b/crypto/blockchains/trx/basic/TrxTronscanProvider.ts index b8a73c055..5ed7276ec 100644 --- a/crypto/blockchains/trx/basic/TrxTronscanProvider.ts +++ b/crypto/blockchains/trx/basic/TrxTronscanProvider.ts @@ -2,7 +2,7 @@ * @version 0.5 * https://github.com/tronscan/tronscan-frontend/wiki/TRONSCAN-API */ -import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '../../../common/AirDAOCryptoLog'; import BlocksoftAxios from '../../../common/BlocksoftAxios'; const BALANCE_PATH = 'https://apilist.tronscan.org/api/account?address='; @@ -50,7 +50,7 @@ export default class TrxTronscanProvider { now - CACHE_TRONSCAN[address].time < CACHE_VALID_TIME ) { if (typeof CACHE_TRONSCAN[address][tokenName] !== 'undefined') { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( `TrxTronscanProvider.get from cache', ${address} + ' => ' + @@ -89,7 +89,7 @@ export default class TrxTronscanProvider { } const link = BALANCE_PATH + address; - BlocksoftCryptoLog.log('TrxTronscanProvider.get ' + link); + AirDAOCryptoLog.log('TrxTronscanProvider.get ' + link); const res = await BlocksoftAxios.getWithoutBraking(link, BALANCE_MAX_TRY); // @ts-ignore if (!res || !('data' in res)) { diff --git a/crypto/blockchains/trx/ext/TronStakeUtils.ts b/crypto/blockchains/trx/ext/TronStakeUtils.ts index 9b7ec3d77..7f92398eb 100644 --- a/crypto/blockchains/trx/ext/TronStakeUtils.ts +++ b/crypto/blockchains/trx/ext/TronStakeUtils.ts @@ -6,7 +6,7 @@ import TronUtils from '@crypto/blockchains/trx/ext/TronUtils'; import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; import Log from '@app/services/Log/Log'; import { BlocksoftTransfer } from '@crypto/actions/BlocksoftTransfer/BlocksoftTransfer'; -import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; interface Balance { balanceAvailable: number; @@ -104,11 +104,11 @@ const TronStakeUtils = { diffLastStakeMinutes === -1 && specialActionNeeded === 'vote_after_unfreeze' ) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'TronStake.sendVoteAll ' + address + ' continue ' + diffLastStakeMinutes ); } else if (!diffLastStakeMinutes || diffLastStakeMinutes < 3) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'TronStake.sendVoteAll ' + address + ' skipped vote1 by ' + @@ -117,13 +117,13 @@ const TronStakeUtils = { return false; } if (!prettyVote || typeof prettyVote === 'undefined') { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'TronStake.sendVoteAll ' + address + ' skipped vote2' ); return false; } else if (voteTotal * 1 === parseFloat(prettyVote)) { if (diffLastStakeMinutes > 100) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'TronStake.sendVoteAll ' + address + ' skipped vote3 ' + @@ -133,13 +133,13 @@ const TronStakeUtils = { ); return true; // all done } - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'TronStake.sendVoteAll ' + address + ' skipped vote4 ' + voteTotal ); return false; } - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'TronStake.sendVoteAll ' + address + ' started vote ' + diff --git a/crypto/blockchains/trx/providers/TrxSendProvider.ts b/crypto/blockchains/trx/providers/TrxSendProvider.ts index c3a9644c7..5a4757db7 100644 --- a/crypto/blockchains/trx/providers/TrxSendProvider.ts +++ b/crypto/blockchains/trx/providers/TrxSendProvider.ts @@ -4,7 +4,7 @@ // @ts-ignore import { BlocksoftBlockchainTypes } from '@crypto/blockchains/BlocksoftBlockchainTypes'; import DogeSendProvider from '@crypto/blockchains/doge/providers/DogeSendProvider'; -import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; import BlocksoftExternalSettings from '@crypto/common/AirDAOExternalSettings'; @@ -55,7 +55,7 @@ export default class TrxSendProvider txRBF: any, logData: any ): Promise<{ transactionHash: string; logData: any }> { - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + ' TrxSendProvider._sendTx ' + subtitle + @@ -77,7 +77,7 @@ export default class TrxSendProvider } logData = await this._check(tx.raw_data_hex, subtitle, txRBF, logData); if (config.debug.cryptoErrors) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( new Date().toISOString() + ' ' + this._settings.currencyCode + @@ -111,7 +111,7 @@ export default class TrxSendProvider } if (typeof send.data.Error !== 'undefined') { - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + ' TrxSendProvider._sendTx error ' + send.data.Error @@ -129,7 +129,7 @@ export default class TrxSendProvider } catch (e) { // do nothing } - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + ' TrxSendProvider._sendTx msg ' + msg ); if (msg) { @@ -158,7 +158,7 @@ export default class TrxSendProvider txRBF: any, logData: any ): Promise<{ transactionHash: string; transactionJson: any; logData: any }> { - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + ' TrxSendProvider.sendTx ' + subtitle + @@ -189,7 +189,7 @@ export default class TrxSendProvider e.message ); } - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + ' TrxSendProvider.send proxy error errorTx ' + e2.message diff --git a/crypto/blockchains/usdt/UsdtScannerProcessor.ts b/crypto/blockchains/usdt/UsdtScannerProcessor.ts index 633dbf7bf..e14329e91 100644 --- a/crypto/blockchains/usdt/UsdtScannerProcessor.ts +++ b/crypto/blockchains/usdt/UsdtScannerProcessor.ts @@ -3,7 +3,7 @@ */ import BlocksoftUtils from '../../common/AirDAOUtils'; import BlocksoftAxios from '../../common/BlocksoftAxios'; -import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '../../common/AirDAOCryptoLog'; import BlocksoftDispatcher from '@lib/BlocksoftDispatcher'; const USDT_API = 'https://microscanners.trustee.deals/usdt'; // https://microscanners.trustee.deals/usdt/1CmAoxq8BTxANRDwheJUpaGy6ngWNYX85 @@ -114,19 +114,17 @@ export default class UsdtScannerProcessor { | false > { if (typeof address === 'object') { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'UsdtScannerProcessor.getBalance started MASS ' + JSON.stringify(address) ); return this._getMass(address); } - BlocksoftCryptoLog.log( - 'UsdtScannerProcessor.getBalance started ' + address - ); + AirDAOCryptoLog.log('UsdtScannerProcessor.getBalance started ' + address); const tmp = await this._get(address); if (typeof tmp === 'undefined' || !tmp || typeof tmp.data === 'undefined') { - BlocksoftCryptoLog.log('UsdtScannerProcessor.getBalance bad tmp ', tmp); + AirDAOCryptoLog.log('UsdtScannerProcessor.getBalance bad tmp ', tmp); return false; } if ( @@ -134,14 +132,14 @@ export default class UsdtScannerProcessor { typeof tmp.data.balance === 'undefined' || tmp.data.balance === false ) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'UsdtScannerProcessor.getBalance bad tmp.data ', tmp.data ); return false; } const balance: number = tmp.data.balance; - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( `UsdtScannerProcessor.getBalance finished ${address + ' => ' + balance}` ); @@ -163,19 +161,16 @@ export default class UsdtScannerProcessor { source = '' ): Promise { const address = scanData.account.address.trim(); - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'UsdtScannerProcessor.getTransactions started ' + address ); let tmp = await this._get(address); if (!tmp || typeof tmp.data === 'undefined') { - BlocksoftCryptoLog.log( - 'UsdtScannerProcessor.getTransactions bad tmp ', - tmp - ); + AirDAOCryptoLog.log('UsdtScannerProcessor.getTransactions bad tmp ', tmp); return []; } if (!tmp.data) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'UsdtScannerProcessor.getTransactions bad tmp.data ', tmp.data ); @@ -237,7 +232,7 @@ export default class UsdtScannerProcessor { }); } } - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'UsdtScannerProcessor.getTransactions finished ' + address + ' total: ' + diff --git a/crypto/blockchains/usdt/UsdtTransferProcessor.ts b/crypto/blockchains/usdt/UsdtTransferProcessor.ts index 3167e5341..ca559fe2c 100644 --- a/crypto/blockchains/usdt/UsdtTransferProcessor.ts +++ b/crypto/blockchains/usdt/UsdtTransferProcessor.ts @@ -8,7 +8,7 @@ import UsdtTxInputsOutputs from './tx/UsdtTxInputsOutputs'; import UsdtTxBuilder from './tx/UsdtTxBuilder'; import BtcNetworkPrices from '../btc/basic/BtcNetworkPrices'; import BtcTransferProcessor from '../btc/BtcTransferProcessor'; -import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '../../common/AirDAOCryptoLog'; // @ts-ignore import DaemonCache from '../../../app/daemons/DaemonCache'; @@ -78,7 +78,7 @@ export default class UsdtTransferProcessor const tmpData = { ...data }; tmpData.isTransferAll = false; // @ts-ignore - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' UsdtTxProcessor.getFeeRate started' ); const result = await super.getFeeRate(tmpData, privateData, additionalData); @@ -95,7 +95,7 @@ export default class UsdtTransferProcessor ): Promise { const balance = data.amount; // @ts-ignore - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ` UsdtTransferProcessor.getTransferAllBalance ${data.addressFrom + ' => ' + balance}` diff --git a/crypto/blockchains/usdt/tx/UsdtTxBuilder.ts b/crypto/blockchains/usdt/tx/UsdtTxBuilder.ts index 0e101f39b..ab27e6d21 100644 --- a/crypto/blockchains/usdt/tx/UsdtTxBuilder.ts +++ b/crypto/blockchains/usdt/tx/UsdtTxBuilder.ts @@ -3,7 +3,7 @@ */ import { AirDAOBlockchainTypes } from '@crypto/blockchains/AirDAOBlockchainTypes'; import BtcTxBuilder from '../../btc/tx/BtcTxBuilder'; -import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '../../../common/AirDAOCryptoLog'; import { TransactionBuilder, script, opcodes } from 'bitcoinjs-lib'; @@ -15,7 +15,7 @@ function toPaddedHexString(num: number, len: number) { } function createOmniSimpleSend(amountInUSD: string, propertyID = USDT_TOKEN_ID) { - BlocksoftCryptoLog.log('UsdtTxBuilder.createOmniSimpleSend started'); + AirDAOCryptoLog.log('UsdtTxBuilder.createOmniSimpleSend started'); const simpleSend = [ '6f6d6e69', // omni '0000', // tx type diff --git a/crypto/blockchains/usdt/tx/UsdtTxInputsOutputs.ts b/crypto/blockchains/usdt/tx/UsdtTxInputsOutputs.ts index de8ed56d2..2520eb4a7 100644 --- a/crypto/blockchains/usdt/tx/UsdtTxInputsOutputs.ts +++ b/crypto/blockchains/usdt/tx/UsdtTxInputsOutputs.ts @@ -3,9 +3,9 @@ */ import { AirDAOBlockchainTypes } from '../../AirDAOBlockchainTypes'; import BtcTxInputsOutputs from '../../btc/tx/BtcTxInputsOutputs'; -import BlocksoftBN from '../../../common/BlocksoftBN'; +import BlocksoftBN from '../../../common/AirDAOBN'; import BlocksoftUtils from '../../../common/AirDAOUtils'; -import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '../../../common/AirDAOCryptoLog'; // @ts-ignore import DaemonCache from '../../../../app/daemons/DaemonCache'; @@ -138,7 +138,7 @@ export default class UsdtTxInputsOutputs let needOneOutput = false; if (tmp.balance > 0) { const diff = BlocksoftUtils.diff(tmp.balance, data.amount); - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'USDT addressFromUsdtOutputs = ' + addressFromUsdtOutputs + ' balance ' + @@ -195,7 +195,7 @@ export default class UsdtTxInputsOutputs newRes.countedFor = 'USDT'; return newRes; } else { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'UsdtTxInputsOutputs ' + data.addressFrom + ' => ' + @@ -214,7 +214,7 @@ export default class UsdtTxInputsOutputs data.addressTo, data.amount ); - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'UsdtTxInputsOutputs ' + data.addressFrom + ' => ' + @@ -362,7 +362,7 @@ export default class UsdtTxInputsOutputs } } // @ts-ignore - BlocksoftCryptoLog.log('USDT addressTo upTo', result); + AirDAOCryptoLog.log('USDT addressTo upTo', result); return result; } } diff --git a/crypto/blockchains/vet/VetScannerProcessor.ts b/crypto/blockchains/vet/VetScannerProcessor.ts index 2db46006f..55272c794 100644 --- a/crypto/blockchains/vet/VetScannerProcessor.ts +++ b/crypto/blockchains/vet/VetScannerProcessor.ts @@ -2,7 +2,7 @@ * @version 0.5 * https://docs.vechain.org/thor/get-started/api.html */ -import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '../../common/AirDAOCryptoLog'; import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; import BlocksoftUtils from '@crypto/common/AirDAOUtils'; @@ -45,7 +45,7 @@ export default class VetScannerProcessor { typeof res.data === 'undefined' || typeof res.data.balance === 'undefined' ) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' VeChain ScannerProcessor getBalanceBlockchain ' + link + @@ -76,7 +76,7 @@ export default class VetScannerProcessor { { balance: string; unconfirmed: number; provider: string } | false > { address = address.trim(); - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' VeChain ScannerProcessor getBalanceBlockchain address ' + address @@ -92,7 +92,7 @@ export default class VetScannerProcessor { const balance = BlocksoftUtils.hexToDecimalBigger(hex); return { balance, unconfirmed: 0, provider: 'vethor-node.vechain.com' }; } catch (e: any) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' VeChain ScannerProcessor getBalanceBlockchain address ' + address + @@ -131,7 +131,7 @@ export default class VetScannerProcessor { } } } catch (e: any) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'VetScannerProcessor.getTransactions lastBlock ' + e.message ); } @@ -159,7 +159,7 @@ export default class VetScannerProcessor { if (!result.data || result.data.length === 0) return false; const transactions = await this._unifyTransactions(address, result.data); - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'VetScannerProcessor.getTransactions finished ' + address ); return transactions; @@ -189,7 +189,7 @@ export default class VetScannerProcessor { address, result.data ); - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'VetScannerProcessor.getTransactions finished ' + tokenHex ); return transactions; diff --git a/crypto/blockchains/vet/VetTransferProcessor.ts b/crypto/blockchains/vet/VetTransferProcessor.ts index 18d18b24d..831dd355b 100644 --- a/crypto/blockchains/vet/VetTransferProcessor.ts +++ b/crypto/blockchains/vet/VetTransferProcessor.ts @@ -1,7 +1,7 @@ /** * @version 0.20 */ -import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '../../common/AirDAOCryptoLog'; import BlocksoftUtils from '../../common/AirDAOUtils'; import { thorify } from 'thorify'; @@ -100,7 +100,7 @@ export default class VetTransferProcessor ): Promise { const balance = data.amount; // @ts-ignore - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + `' VetTransferProcessor.getTransferAllBalance ', ${data.addressFrom} + ' => ' + ${balance}` @@ -171,7 +171,7 @@ export default class VetTransferProcessor ); } } catch (e: any) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' VetTransferProcessor.sendTx ' + data.addressFrom + @@ -184,7 +184,7 @@ export default class VetTransferProcessor ); throw new Error(e.message); } - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' VetTransferProcessor.sendTx ' + data.addressFrom + @@ -220,7 +220,7 @@ export default class VetTransferProcessor { raw: signedData.rawTransaction }, false ); - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' VetTransferProcessor.sendTx ' + data.addressFrom + @@ -239,7 +239,7 @@ export default class VetTransferProcessor } result.transactionHash = send.data.id; } catch (e: any) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' VetTransferProcessor.sendTx ' + data.addressFrom + @@ -257,7 +257,7 @@ export default class VetTransferProcessor checkError(e: any, data: any, txRBF = false) { if (e.message.indexOf('nonce too low') !== -1) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'VeChain checkError0.1 ' + e.message + ' for ' + data.addressFrom ); let e2; @@ -268,7 +268,7 @@ export default class VetTransferProcessor } throw e2; } else if (e.message.indexOf('insufficient funds') !== -1) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'VeChain checkError0.3 ' + e.message + ' for ' + data.addressFrom ); if ( @@ -281,17 +281,17 @@ export default class VetTransferProcessor throw new Error('SERVER_RESPONSE_NOT_ENOUGH_FEE'); } } else if (e.message.indexOf('underpriced') !== -1) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'VeChain checkError0.4 ' + e.message + ' for ' + data.addressFrom ); throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_FEE'); } else if (e.message.indexOf('already known') !== -1) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'VeChain checkError0.5 ' + e.message + ' for ' + data.addressFrom ); throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_FEE'); } else if (e.message.indexOf('insufficient energy') !== -1) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'VeChain checkError0.6 ' + e.message + ' for ' + data.addressFrom ); throw new Error('SERVER_RESPONSE_ENERGY_ERROR_VET'); diff --git a/crypto/blockchains/waves/WavesScannerProcessor.ts b/crypto/blockchains/waves/WavesScannerProcessor.ts index f3132a9d0..3a6c770e9 100644 --- a/crypto/blockchains/waves/WavesScannerProcessor.ts +++ b/crypto/blockchains/waves/WavesScannerProcessor.ts @@ -1,7 +1,7 @@ /** * @version 0.5 */ -import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '../../common/AirDAOCryptoLog'; import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; import BlocksoftExternalSettings from '@crypto/common/AirDAOExternalSettings'; import TransactionFilterTypeDict from '@appV2/dicts/transactionFilterTypeDict'; @@ -38,7 +38,7 @@ export default class WavesScannerProcessor { this._apiPath = await BlocksoftExternalSettings.get('WAVES_SERVER'); } address = address.trim(); - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( `${this._settings.currencyCode} WavesScannerProcessor getBalanceBlockchain address ${address}` ); @@ -54,7 +54,7 @@ export default class WavesScannerProcessor { provider: 'wavesnodes.com' }; } catch (e: any) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( `${ this._settings.currencyCode } WavesProcessor getBalanceBlockchain address ${address} balance ${JSON.stringify( diff --git a/crypto/blockchains/waves/WavesScannerProcessorErc20.ts b/crypto/blockchains/waves/WavesScannerProcessorErc20.ts index 27b4b9772..1e8294c26 100644 --- a/crypto/blockchains/waves/WavesScannerProcessorErc20.ts +++ b/crypto/blockchains/waves/WavesScannerProcessorErc20.ts @@ -1,7 +1,7 @@ /** * @version 0.5 */ -import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; import BlocksoftExternalSettings from '@crypto/common/AirDAOExternalSettings'; import WavesScannerProcessor from '@crypto/blockchains/waves/WavesScannerProcessor'; @@ -41,7 +41,7 @@ export default class WavesScannerProcessorErc20 extends WavesScannerProcessor { this._apiPath = await BlocksoftExternalSettings.get('WAVES_SERVER'); } address = address.trim(); - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( `${this._settings.currencyCode} WavesScannerProcessorErc20 getBalanceBlockchain address ${address}` ); @@ -57,7 +57,7 @@ export default class WavesScannerProcessorErc20 extends WavesScannerProcessor { provider: 'wavesnodes.com' }; } catch (e: any) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( `${ this._settings.currencyCode } WavesProcessorErc20 getBalanceBlockchain address ${address} balance ${JSON.stringify( diff --git a/crypto/blockchains/waves/WavesTransferProcessor.ts b/crypto/blockchains/waves/WavesTransferProcessor.ts index afaaa5f84..5e074b43c 100644 --- a/crypto/blockchains/waves/WavesTransferProcessor.ts +++ b/crypto/blockchains/waves/WavesTransferProcessor.ts @@ -1,7 +1,7 @@ /** * @version 0.5 */ -import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; import BlocksoftUtils from '@crypto/common/AirDAOUtils'; import { transfer, broadcast } from '@waves/waves-transactions/src/index'; @@ -76,7 +76,7 @@ export default class WavesTransferProcessor ): Promise { const balance = data.amount; // @ts-ignore - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + `' WavesTransferProcessor.getTransferAllBalance ', ${data.addressFrom} + ' => ' + ${balance}` @@ -125,7 +125,7 @@ export default class WavesTransferProcessor apiPath = await BlocksoftExternalSettings.get('WAVES_SERVER'); } - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' WavesTransferProcessor.sendTx started ' + data.addressFrom + @@ -146,7 +146,7 @@ export default class WavesTransferProcessor } signedData = transfer(money, { privateKey: privateData.privateKey }); } catch (e: any) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' WavesTransferProcessor.sendTx ' + data.addressFrom + @@ -159,7 +159,7 @@ export default class WavesTransferProcessor ); throw new Error(e.message); } - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' WavesTransferProcessor.sendTx ' + data.addressFrom + @@ -189,7 +189,7 @@ export default class WavesTransferProcessor const result = {} as AirDAOBlockchainTypes.SendTxResult; try { const resp = await new Promise((resolve, reject) => { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' WavesTransferProcessor.sendTx ' + data.addressFrom + @@ -209,7 +209,7 @@ export default class WavesTransferProcessor reject(e); }); }); - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' WavesTransferProcessor.sendTx ' + data.addressFrom + @@ -235,7 +235,7 @@ export default class WavesTransferProcessor e.message ); } - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' WavesTransferProcessor.sendTx ' + data.addressFrom + diff --git a/crypto/blockchains/waves/providers/WavesTransactionsProvider.ts b/crypto/blockchains/waves/providers/WavesTransactionsProvider.ts index c04bd02f7..22f761df0 100644 --- a/crypto/blockchains/waves/providers/WavesTransactionsProvider.ts +++ b/crypto/blockchains/waves/providers/WavesTransactionsProvider.ts @@ -3,7 +3,7 @@ */ import BlocksoftExternalSettings from '@crypto/common/AirDAOExternalSettings'; import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; -import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; interface TransactionData { data: any; @@ -36,7 +36,7 @@ export default class WavesTransactionsProvider { CACHE_VALID_TIME ) { if (CACHE_OF_TRANSACTIONS[mainCurrencyCode][address]) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( `WavesTransactionsProvider.get from cache ${address} => ${mainCurrencyCode}` ); return CACHE_OF_TRANSACTIONS[mainCurrencyCode][address].data; diff --git a/crypto/blockchains/xlm/XlmScannerProcessor.ts b/crypto/blockchains/xlm/XlmScannerProcessor.ts index c05bc07ad..03b962631 100644 --- a/crypto/blockchains/xlm/XlmScannerProcessor.ts +++ b/crypto/blockchains/xlm/XlmScannerProcessor.ts @@ -2,7 +2,7 @@ * @version 0.20 */ import BlocksoftAxios from '../../common/BlocksoftAxios'; -import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '../../common/AirDAOCryptoLog'; import BlocksoftUtils from '../../common/AirDAOUtils'; import config from '@constants/config'; @@ -84,7 +84,7 @@ export default class XlmScannerProcessor { account: { address: string }; }): Promise { const address = scanData.account.address.trim(); - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'XlmScannerProcessor.getTransactions started ' + address ); const linkTxs = `${API_PATH}/accounts/${address}/transactions?order=desc&limit=50`; @@ -163,7 +163,7 @@ export default class XlmScannerProcessor { res.data._embedded.records, basicTxs ); - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'XlmScannerProcessor.getTransactions finished ' + address ); return transactions; diff --git a/crypto/blockchains/xlm/XlmTransferProcessor.ts b/crypto/blockchains/xlm/XlmTransferProcessor.ts index 00aac0ab2..0c92c267c 100644 --- a/crypto/blockchains/xlm/XlmTransferProcessor.ts +++ b/crypto/blockchains/xlm/XlmTransferProcessor.ts @@ -21,7 +21,7 @@ * } * } */ -import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '../../common/AirDAOCryptoLog'; import BlocksoftUtils from '../../common/AirDAOUtils'; // @ts-ignore import MarketingEvent from '../../../app/services/Marketing/MarketingEvent'; @@ -90,7 +90,7 @@ export default class XlmTransferProcessor // @ts-ignore if (data.amount * 1 <= 0) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' XlmTransferProcessor.getFeeRate ' + data.addressFrom + @@ -101,7 +101,7 @@ export default class XlmTransferProcessor return result; } - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' XlmTransferProcessor.getFeeRate ' + data.addressFrom + @@ -119,7 +119,7 @@ export default class XlmTransferProcessor // @ts-ignore const fee = BlocksoftUtils.toUnified(getFee, FEE_DECIMALS); - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' XlmTransferProcessor.getFeeRate ' + data.addressFrom + @@ -149,7 +149,7 @@ export default class XlmTransferProcessor ): Promise { const balance = data.amount; // @ts-ignore - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' XlmTransferProcessor.getTransferAllBalance ' + data.addressFrom + @@ -237,11 +237,11 @@ export default class XlmTransferProcessor } throw e; } - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + ' XlmTransferProcessor.sendTx prepared' ); let raw = transaction.toEnvelope().toXDR('base64'); - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + ' XlmTransferProcessor.sendTx base64', raw ); @@ -265,12 +265,12 @@ export default class XlmTransferProcessor uiData, 'create_account' ); - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + ' XlmTransferProcessor.sendTx prepared create account' ); raw = transaction.toEnvelope().toXDR('base64'); - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + ' XlmTransferProcessor.sendTx base64 create account', raw diff --git a/crypto/blockchains/xlm/basic/XlmTxSendProvider.ts b/crypto/blockchains/xlm/basic/XlmTxSendProvider.ts index 0ebdc0b48..608c552f9 100644 --- a/crypto/blockchains/xlm/basic/XlmTxSendProvider.ts +++ b/crypto/blockchains/xlm/basic/XlmTxSendProvider.ts @@ -3,7 +3,7 @@ * https://developers.stellar.org/docs/tutorials/send-and-receive-payments/ * https://www.stellar.org/developers/horizon/reference/endpoints/transactions-create.html */ -import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '../../../common/AirDAOCryptoLog'; import BlocksoftExternalSettings from '../../../common/AirDAOExternalSettings'; import { XrpTxUtils } from '../../xrp/basic/XrpTxUtils'; import config from '@constants/config'; @@ -38,7 +38,7 @@ export class XlmTxSendProvider { return CACHE_FEES_VALUE; } - BlocksoftCryptoLog.log('XlmSendProvider.getFee link ' + this._server); + AirDAOCryptoLog.log('XlmSendProvider.getFee link ' + this._server); let res = CACHE_FEES_VALUE; try { res = await this._api.fetchBaseFee(); @@ -47,7 +47,7 @@ export class XlmTxSendProvider { CACHE_FEES_TIME = now; } } catch (e: any) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'XlmSendProvider.getFee error ' + e.message + ' link ' + this._server ); res = CACHE_FEES_VALUE; @@ -103,7 +103,7 @@ export class XlmTxSendProvider { .build(); } } catch (e: any) { - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( 'XlmTxSendProvider builder create error ' + e.message ); throw e; @@ -112,7 +112,7 @@ export class XlmTxSendProvider { try { transaction.sign(StellarSdk.Keypair.fromSecret(privateData.privateKey)); } catch (e: any) { - await BlocksoftCryptoLog.log('XlmTxSendProvider sign error ' + e.message); + await AirDAOCryptoLog.log('XlmTxSendProvider sign error ' + e.message); throw e; } return transaction; @@ -121,7 +121,7 @@ export class XlmTxSendProvider { async sendRaw(raw: string) { let result = false; const link = BlocksoftExternalSettings.getStatic('XLM_SEND_LINK'); - BlocksoftCryptoLog.log('XlmSendProvider.sendRaw ' + link + ' raw ' + raw); + AirDAOCryptoLog.log('XlmSendProvider.sendRaw ' + link + ' raw ' + raw); try { // console.log(`curl -X POST -F "tx=${raw}" "https://horizon.stellar.org/transactions"`) @@ -149,7 +149,7 @@ export class XlmTxSendProvider { JSON.stringify(result.extras.result_codes) ); } - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( 'XlmTransferProcessor.sendTx result.extras.result_codes ' + JSON.stringify(result.extras.result_codes) ); @@ -176,7 +176,7 @@ export class XlmTxSendProvider { 'XlmTransferProcessor.sendTx error ' + e.message + ' link ' + link ); } - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( 'XlmTransferProcessor.sendTx error ' + e.message + ' link ' + link ); if ( @@ -191,7 +191,7 @@ export class XlmTxSendProvider { throw e; } } - await BlocksoftCryptoLog.log('XlmTransferProcessor.sendTx result ', result); + await AirDAOCryptoLog.log('XlmTransferProcessor.sendTx result ', result); return result; } } diff --git a/crypto/blockchains/xmr/XmrAddressProcessor.js b/crypto/blockchains/xmr/XmrAddressProcessor.js index c9d19d1d8..df973170f 100644 --- a/crypto/blockchains/xmr/XmrAddressProcessor.js +++ b/crypto/blockchains/xmr/XmrAddressProcessor.js @@ -11,7 +11,7 @@ import MoneroUtils from './ext/MoneroUtils'; import MoneroMnemonic from './ext/MoneroMnemonic'; import { soliditySha3 } from 'web3-utils'; import BlocksoftAxios from '../../common/BlocksoftAxios'; -import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '../../common/AirDAOCryptoLog'; import BlocksoftSecrets from '@crypto/actions/BlocksoftSecrets/BlocksoftSecrets'; import config from '@app/config/config'; @@ -130,7 +130,7 @@ export default class XmrAddressProcessor { throw new Error('no data'); } } catch (e) { - BlocksoftCryptoLog.err( + AirDAOCryptoLog.err( 'XmrAddressProcessor !!!mymonero error!!! ' + e.message, { linkParamsLogin, diff --git a/crypto/blockchains/xmr/XmrAddressProcessor.ts b/crypto/blockchains/xmr/XmrAddressProcessor.ts index 561e09e61..1d8653661 100644 --- a/crypto/blockchains/xmr/XmrAddressProcessor.ts +++ b/crypto/blockchains/xmr/XmrAddressProcessor.ts @@ -11,7 +11,7 @@ import MoneroUtils from './ext/MoneroUtils'; import MoneroMnemonic from './ext/MoneroMnemonic'; import { soliditySha3 } from 'web3-utils'; import BlocksoftAxios from '../../common/BlocksoftAxios'; -import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '../../common/AirDAOCryptoLog'; import BlocksoftSecrets from '@crypto/actions/BlocksoftSecrets/BlocksoftSecrets'; import config from '@constants/config'; @@ -170,7 +170,7 @@ export default class XmrAddressProcessor { throw new Error('no data'); } } catch (e: any) { - BlocksoftCryptoLog.err( + AirDAOCryptoLog.err( 'XmrAddressProcessor !!!mymonero error!!! ' + e.message, JSON.stringify({ linkParamsLogin, diff --git a/crypto/blockchains/xmr/XmrScannerProcessor.js b/crypto/blockchains/xmr/XmrScannerProcessor.js index f0fd7ac8b..53ec84dd6 100644 --- a/crypto/blockchains/xmr/XmrScannerProcessor.js +++ b/crypto/blockchains/xmr/XmrScannerProcessor.js @@ -5,7 +5,7 @@ import BlocksoftUtils from '@crypto/common/BlocksoftUtils'; import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; -import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; import BlocksoftPrivateKeysUtils from '@crypto/common/BlocksoftPrivateKeysUtils'; import MoneroUtilsParser from './ext/MoneroUtilsParser'; @@ -50,7 +50,7 @@ export default class XmrScannerProcessor { * @private */ async _get(address, additionalData, walletHash) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'XMR XmrScannerProcessor._get ' + walletHash + ' ' + address ); const now = new Date().getTime(); @@ -101,7 +101,7 @@ export default class XmrScannerProcessor { let res = false; try { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' XmrScannerProcessor._get start ' + link + @@ -110,7 +110,7 @@ export default class XmrScannerProcessor { ); res = await BlocksoftAxios.post(link + 'get_address_info', linkParams); } catch (e) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' XmrScannerProcessor._get error ' + e.message, @@ -146,7 +146,7 @@ export default class XmrScannerProcessor { linkParamsLogin ); // login needed } catch (e) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' XmrScannerProcessor._get login error ' + e.message, @@ -193,7 +193,7 @@ export default class XmrScannerProcessor { e.message ); } - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( 'XMR XmrScannerProcessor._get MoneroUtilsParser.parseAddressInfo error ' + e.message ); @@ -223,7 +223,7 @@ export default class XmrScannerProcessor { e.message ); } - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( 'XMR XmrScannerProcessor._get MoneroUtilsParser.parseAddressTransactions error ' + e.message ); diff --git a/crypto/blockchains/xmr/XmrScannerProcessor.ts b/crypto/blockchains/xmr/XmrScannerProcessor.ts index 5f10a4cfb..8f6caadf2 100644 --- a/crypto/blockchains/xmr/XmrScannerProcessor.ts +++ b/crypto/blockchains/xmr/XmrScannerProcessor.ts @@ -5,7 +5,7 @@ import BlocksoftUtils from '@crypto/common/AirDAOUtils'; import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; -import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; import BlocksoftPrivateKeysUtils from '@crypto/common/AirDAOPrivateKeysUtils'; import MoneroUtilsParser from './ext/MoneroUtilsParser'; @@ -58,7 +58,7 @@ export default class XmrScannerProcessor { additionalData: { derivationIndex: any; publicSpendKey: string }, walletHash: string ) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'XMR XmrScannerProcessor._get ' + walletHash + ' ' + address ); const now = new Date().getTime(); @@ -109,7 +109,7 @@ export default class XmrScannerProcessor { let res = false; try { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + `' XmrScannerProcessor._get start ' + ${link} + @@ -118,7 +118,7 @@ export default class XmrScannerProcessor { ); res = await BlocksoftAxios.post(link + 'get_address_info', linkParams); } catch (e: any) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + `' XmrScannerProcessor._get error ' + ${e.message}, @@ -154,7 +154,7 @@ export default class XmrScannerProcessor { linkParamsLogin ); // login needed } catch (e: any) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + `' XmrScannerProcessor._get login error ' + ${e.message}, @@ -201,7 +201,7 @@ export default class XmrScannerProcessor { e.message ); } - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( 'XMR XmrScannerProcessor._get MoneroUtilsParser.parseAddressInfo error ' + e.message ); @@ -231,7 +231,7 @@ export default class XmrScannerProcessor { e.message ); } - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( 'XMR XmrScannerProcessor._get MoneroUtilsParser.parseAddressTransactions error ' + e.message ); diff --git a/crypto/blockchains/xmr/XmrTransferProcessor.ts b/crypto/blockchains/xmr/XmrTransferProcessor.ts index 9982930e4..f940f239e 100644 --- a/crypto/blockchains/xmr/XmrTransferProcessor.ts +++ b/crypto/blockchains/xmr/XmrTransferProcessor.ts @@ -1,7 +1,7 @@ /** * @version 0.20 */ -import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '../../common/AirDAOCryptoLog'; import MoneroUtilsParser from './ext/MoneroUtilsParser'; import XmrSendProvider from './providers/XmrSendProvider'; import XmrUnspentsProvider from './providers/XmrUnspentsProvider'; @@ -41,7 +41,7 @@ export default class XmrTransferProcessor // @ts-ignore if (data.amount * 1 <= 0) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' XmrTransferProcessor.getFeeRate ' + data.addressFrom + @@ -64,7 +64,7 @@ export default class XmrTransferProcessor const privViewKey = keys[1]; const pubSpendKey = data.accountJson.publicSpendKey; - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' XmrTransferProcessor.getFeeRate newSender ' + data.addressFrom + @@ -103,7 +103,7 @@ export default class XmrTransferProcessor for (let i = 1; i <= 4; i++) { try { - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( this._settings.currencyCode + ' XmrTransferProcessor.getFeeRate ' + data.addressFrom + @@ -199,25 +199,25 @@ export default class XmrTransferProcessor 'An error occurred while getting decoy outputs' ) !== -1 ) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' XmrTransferProcessor error will go out bad decoy' ); throw new Error('SERVER_RESPONSE_BAD_CODE'); } else if (e.message.indexOf('decode address') !== -1) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' XmrTransferProcessor error will go out' ); throw new Error('SERVER_RESPONSE_BAD_DESTINATION'); } else if (e.message.indexOf('Not enough spendables') !== -1) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' XmrTransferProcessor error not enough' ); throw new Error('SERVER_RESPONSE_NO_RESPONSE_XMR'); } else { - BlocksoftCryptoLog.err( + AirDAOCryptoLog.err( this._settings.currencyCode + ' XmrTransferProcessor.getFeeRate ' + data.addressFrom + @@ -241,7 +241,7 @@ export default class XmrTransferProcessor } // @ts-ignore - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + `' XmrTransferProcessor.getFeeRate ' + ${data.addressFrom} + @@ -268,7 +268,7 @@ export default class XmrTransferProcessor ): Promise { const balance = data.amount; - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + `' XmrTransferProcessor.getTransferAllBalance ', ${data.addressFrom + ' => ' + balance}` @@ -312,7 +312,7 @@ export default class XmrTransferProcessor throw new Error('XMR transaction required selectedFee'); } - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + `' XmrTransferProcessor.sendTx started ' + ${data.addressFrom} + @@ -324,7 +324,7 @@ export default class XmrTransferProcessor let foundFee = uiData?.selectedFee; if (data.addressTo !== uiData.selectedFee.addressToTx) { try { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' XmrTransferProcessor.sendTx rechecked ' + data.addressFrom + @@ -346,7 +346,7 @@ export default class XmrTransferProcessor foundFee = fee; } } - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + `' XmrTransferProcessor.sendTx rechecked ' + ${data.addressFrom} + @@ -356,7 +356,7 @@ export default class XmrTransferProcessor ${foundFee}` ); } catch (e: any) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' XmrTransferProcessor.sendTx rechecked ' + data.addressFrom + @@ -400,7 +400,7 @@ export default class XmrTransferProcessor }); // @ts-ignore - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' XmrTransferProcessor.sendTx result', send ); diff --git a/crypto/blockchains/xmr/ext/MoneroUtilsParser.ts b/crypto/blockchains/xmr/ext/MoneroUtilsParser.ts index 915e9405f..32b2f1366 100644 --- a/crypto/blockchains/xmr/ext/MoneroUtilsParser.ts +++ b/crypto/blockchains/xmr/ext/MoneroUtilsParser.ts @@ -8,7 +8,7 @@ import * as payment from '@mymonero/mymonero-paymentid-utils'; // import * as parser from '@mymonero/mymonero-response-parser-utils/ResponseParser' import * as parser from './vendor/ResponseParser'; -import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; import config from '@constants/config'; const MyMoneroCoreBridgeRN = require('react-native-mymonero-core/src/index'); @@ -105,7 +105,7 @@ export default { } return res; } catch (e) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'MoneroUtilsParser.generate_key_image ' + e.message ); throw new Error('MoneroUtilsParser.generate_key_image ' + e.message); @@ -180,7 +180,7 @@ export default { const ret = JSON.parse(retString); // check for any errors passed back from WebAssembly if (ret.err_msg) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'MoneroUtilsParser ret.err_msg error ' + ret.err_msg ); return false; @@ -201,7 +201,7 @@ export default { return randomOuts; }; - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'MoneroUtilsParser ret?.amounts?.length ' + ret?.amounts?.length ); diff --git a/crypto/blockchains/xmr/providers/XmrSendProvider.ts b/crypto/blockchains/xmr/providers/XmrSendProvider.ts index 07f7df976..31728169f 100644 --- a/crypto/blockchains/xmr/providers/XmrSendProvider.ts +++ b/crypto/blockchains/xmr/providers/XmrSendProvider.ts @@ -4,7 +4,7 @@ // @ts-ignore import settingsActions from '../../../../app/appstores/Stores/Settings/SettingsActions'; import BlocksoftAxios from '../../../common/BlocksoftAxios'; -import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '../../../common/AirDAOCryptoLog'; interface SendParams { address: string; @@ -80,7 +80,7 @@ export default class XmrSendProvider { ); } - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( `'XmrSendProvider node ${this._link},' + ${resNode.data}` ); diff --git a/crypto/blockchains/xmr/providers/XmrUnspentsProvider.js b/crypto/blockchains/xmr/providers/XmrUnspentsProvider.js index 22628f59f..b4c32864b 100644 --- a/crypto/blockchains/xmr/providers/XmrUnspentsProvider.js +++ b/crypto/blockchains/xmr/providers/XmrUnspentsProvider.js @@ -3,7 +3,7 @@ */ import settingsActions from '../../../../app/appstores/Stores/Settings/SettingsActions'; import BlocksoftAxios from '../../../common/BlocksoftAxios'; -import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '../../../common/AirDAOCryptoLog'; export default class XmrUnspentsProvider { constructor(settings) { @@ -36,7 +36,7 @@ export default class XmrUnspentsProvider { const key = JSON.stringify(params); let res = {}; if (typeof this._cache[key] === 'undefined') { - BlocksoftCryptoLog.log('XmrUnspentsProvider Xmr._getUnspents', key); + AirDAOCryptoLog.log('XmrUnspentsProvider Xmr._getUnspents', key); /* const linkParams = { address: params.address, @@ -51,7 +51,7 @@ export default class XmrUnspentsProvider { this._link + 'get_unspent_outs', params ); - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'XmrUnspentsProvider Xmr._getUnspents res ' + JSON.stringify(res.data).substr(0, 200) ); @@ -77,10 +77,7 @@ export default class XmrUnspentsProvider { async _getRandomOutputs(params, fn) { try { - BlocksoftCryptoLog.log( - 'XmrUnspentsProvider Xmr._getRandomOutputs', - params - ); + AirDAOCryptoLog.log('XmrUnspentsProvider Xmr._getRandomOutputs', params); /* const amounts = usingOuts.map(o => (o.rct ? '0' : o.amount.toString())) @@ -94,7 +91,7 @@ export default class XmrUnspentsProvider { this._link + 'get_random_outs', params ); - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( 'XmrUnspentsProvider Xmr._getRandomOutputs res ' + JSON.stringify(res.data).substr(0, 200) ); diff --git a/crypto/blockchains/xmr/providers/XmrUnspentsProvider.ts b/crypto/blockchains/xmr/providers/XmrUnspentsProvider.ts index 526bb1c79..343d3131d 100644 --- a/crypto/blockchains/xmr/providers/XmrUnspentsProvider.ts +++ b/crypto/blockchains/xmr/providers/XmrUnspentsProvider.ts @@ -4,7 +4,7 @@ // @ts-ignore import settingsActions from '../../../../app/appstores/Stores/Settings/SettingsActions'; import BlocksoftAxios from '../../../common/BlocksoftAxios'; -import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '../../../common/AirDAOCryptoLog'; interface ApiResponse { data: any; @@ -52,7 +52,7 @@ export default class XmrUnspentsProvider { let res: ApiResponse; if (typeof this._cache[key] === 'undefined') { // @ts-ignore - BlocksoftCryptoLog.log('XmrUnspentsProvider Xmr._getUnspents', key); + AirDAOCryptoLog.log('XmrUnspentsProvider Xmr._getUnspents', key); /* const linkParams = { address: params.address, @@ -68,7 +68,7 @@ export default class XmrUnspentsProvider { this._link + 'get_unspent_outs', params ); - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'XmrUnspentsProvider Xmr._getUnspents res ' + JSON.stringify(res.data).substr(0, 200) ); @@ -96,10 +96,7 @@ export default class XmrUnspentsProvider { fn?: (err: Error | null, data: any) => void ): Promise { try { - BlocksoftCryptoLog.log( - 'XmrUnspentsProvider Xmr._getRandomOutputs', - params - ); + AirDAOCryptoLog.log('XmrUnspentsProvider Xmr._getRandomOutputs', params); /* const amounts = usingOuts.map(o => (o.rct ? '0' : o.amount.toString())) @@ -114,7 +111,7 @@ export default class XmrUnspentsProvider { this._link + 'get_random_outs', params ); - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( 'XmrUnspentsProvider Xmr._getRandomOutputs res ' + JSON.stringify(res.data).substr(0, 200) ); diff --git a/crypto/blockchains/xrp/XrpScannerProcessor.js b/crypto/blockchains/xrp/XrpScannerProcessor.js index 6b75467cb..be7e6b142 100644 --- a/crypto/blockchains/xrp/XrpScannerProcessor.js +++ b/crypto/blockchains/xrp/XrpScannerProcessor.js @@ -1,7 +1,7 @@ /** * @version 0.5 */ -import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; import XrpTmpDS from './stores/XrpTmpDS'; import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings'; import XrpDataRippleProvider from '@crypto/blockchains/xrp/basic/XrpDataRippleProvider'; @@ -66,7 +66,7 @@ export default class XrpScannerProcessor { async getTransactionsBlockchain(scanData, source = '') { await this.init(); const address = scanData.account.address.trim(); - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( 'XrpScannerProcessor.getTransactions started ' + address ); @@ -86,7 +86,7 @@ export default class XrpScannerProcessor { } } - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( 'XrpScannerProcessor.getTransactions finished ' + address ); return transactions; diff --git a/crypto/blockchains/xrp/XrpScannerProcessor.ts b/crypto/blockchains/xrp/XrpScannerProcessor.ts index e9747a6b7..cbaba02fb 100644 --- a/crypto/blockchains/xrp/XrpScannerProcessor.ts +++ b/crypto/blockchains/xrp/XrpScannerProcessor.ts @@ -1,4 +1,4 @@ -import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; import XrpTmpDS from './stores/XrpTmpDS'; import BlocksoftExternalSettings from '@crypto/common/AirDAOExternalSettings'; import XrpDataRippleProvider from '@crypto/blockchains/xrp/basic/XrpDataRippleProvider'; @@ -70,7 +70,7 @@ export default class XrpScannerProcessor { }): Promise { await this.init(); const address = scanData.account.address.trim(); - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( 'XrpScannerProcessor.getTransactions started ' + address ); @@ -90,7 +90,7 @@ export default class XrpScannerProcessor { } } - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( 'XrpScannerProcessor.getTransactions finished ' + address ); return transactions; diff --git a/crypto/blockchains/xrp/XrpTransferProcessor.ts b/crypto/blockchains/xrp/XrpTransferProcessor.ts index 4c3f3d156..d2619fec6 100644 --- a/crypto/blockchains/xrp/XrpTransferProcessor.ts +++ b/crypto/blockchains/xrp/XrpTransferProcessor.ts @@ -6,7 +6,7 @@ * https://xrpl.org/rippleapi-reference.html#sign * https://xrpl.org/rippleapi-reference.html#submit */ -import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '../../common/AirDAOCryptoLog'; import BlocksoftUtils from '../../common/AirDAOUtils'; import { XrpTxSendProvider } from './basic/XrpTxSendProvider'; import MarketingEvent from '../../../app/services/Marketing/MarketingEvent'; @@ -78,7 +78,7 @@ export default class XrpTransferProcessor // @ts-ignore if (data.amount * 1 <= 0) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' XrpTransferProcessor.getFeeRate ' + data.addressFrom + @@ -89,7 +89,7 @@ export default class XrpTransferProcessor return result; } - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' XrpTransferProcessor.getFeeRate ' + data.addressFrom + @@ -125,7 +125,7 @@ export default class XrpTransferProcessor // @ts-ignore const fee = BlocksoftUtils.toUnified(txJson.Fee, FEE_DECIMALS); - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' XrpTransferProcessor.getFeeRate ' + data.addressFrom + @@ -156,7 +156,7 @@ export default class XrpTransferProcessor const balance = data.amount; // @ts-ignore - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' XrpTransferProcessor.getTransferAllBalance ', data.addressFrom + ' => ' + balance @@ -236,7 +236,7 @@ export default class XrpTransferProcessor // https://xrpl.org/rippleapi-reference.html#preparepayment // @ts-ignore - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' XrpTransferProcessor.sendTx prepared', txJson ); @@ -247,7 +247,7 @@ export default class XrpTransferProcessor const tmp = JSON.parse(data.accountJson); data.accountJson = tmp; } catch (e) { - BlocksoftCryptoLog.err( + AirDAOCryptoLog.err( this._settings.currencyCode + ' XrpTransferProcessor.sendTx no accountJson ' + JSON.stringify(data.accountJson) @@ -255,7 +255,7 @@ export default class XrpTransferProcessor } } if (typeof data.accountJson.publicKey === 'undefined') { - BlocksoftCryptoLog.err( + AirDAOCryptoLog.err( this._settings.currencyCode + ' XrpTransferProcessor.sendTx no publicKey ' + JSON.stringify(data.accountJson) @@ -286,7 +286,7 @@ export default class XrpTransferProcessor } ); // @ts-ignore - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' XrpTransferProcessor.sendTx result', result ); diff --git a/crypto/blockchains/xrp/basic/XrpDataRippleProvider.js b/crypto/blockchains/xrp/basic/XrpDataRippleProvider.js index a5f1ab0da..0a5fc8183 100644 --- a/crypto/blockchains/xrp/basic/XrpDataRippleProvider.js +++ b/crypto/blockchains/xrp/basic/XrpDataRippleProvider.js @@ -1,5 +1,5 @@ import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; -import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; import XrpTmpDS from '@crypto/blockchains/xrp/stores/XrpTmpDS'; const CACHE_VALID_TIME = 60000; // 1 minute @@ -56,7 +56,7 @@ export default class XrpDataRippleProvider { async getTransactionsBlockchain(scanData, source = '') { const address = scanData.account.address.trim(); const action = 'payments'; - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( 'XrpScannerProcessor.DataRipple.getTransactions ' + action + ' started ' + @@ -94,7 +94,7 @@ export default class XrpDataRippleProvider { res.data[action], action ); - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( 'XrpScannerProcessor.DataRipple.getTransactions ' + action + ' finished ' + @@ -201,7 +201,7 @@ export default class XrpDataRippleProvider { async _getLedger(index) { const now = new Date().getTime(); - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( 'XrpScannerProcessor.DataRipple._getLedger started ' + index ); const link = `${API_PATH}/ledgers/${index}`; @@ -218,7 +218,7 @@ export default class XrpDataRippleProvider { typeof res.data !== 'undefined' && typeof res.data.ledger !== 'undefined' ) { - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( 'XrpScannerProcessor.DataRipple._getLedger updated for index ' + index + ' ' + diff --git a/crypto/blockchains/xrp/basic/XrpDataRippleProvider.ts b/crypto/blockchains/xrp/basic/XrpDataRippleProvider.ts index 09cfdbfc2..a6f8176fb 100644 --- a/crypto/blockchains/xrp/basic/XrpDataRippleProvider.ts +++ b/crypto/blockchains/xrp/basic/XrpDataRippleProvider.ts @@ -1,5 +1,5 @@ import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; -import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; import XrpTmpDS from '@crypto/blockchains/xrp/stores/XrpTmpDS'; const CACHE_VALID_TIME = 60000; @@ -53,7 +53,7 @@ export default class XrpDataRippleProvider { }) { const address = scanData.account.address.trim(); const action = 'payments'; - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( 'XrpScannerProcessor.DataRipple.getTransactions ' + action + ' started ' + @@ -91,7 +91,7 @@ export default class XrpDataRippleProvider { res.data[action], action ); - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( 'XrpScannerProcessor.DataRipple.getTransactions ' + action + ' finished ' + @@ -179,7 +179,7 @@ export default class XrpDataRippleProvider { async _getLedger(index: string) { const now = new Date().getTime(); - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( 'XrpScannerProcessor.DataRipple._getLedger started ' + index ); const link = `${API_PATH}/ledgers/${index}`; @@ -196,7 +196,7 @@ export default class XrpDataRippleProvider { typeof res.data !== 'undefined' && typeof res.data.ledger !== 'undefined' ) { - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( 'XrpScannerProcessor.DataRipple._getLedger updated for index ' + index + ' ' + diff --git a/crypto/blockchains/xrp/basic/XrpDataScanProvider.js b/crypto/blockchains/xrp/basic/XrpDataScanProvider.js index 518a0b7ad..240fb40aa 100644 --- a/crypto/blockchains/xrp/basic/XrpDataScanProvider.js +++ b/crypto/blockchains/xrp/basic/XrpDataScanProvider.js @@ -5,7 +5,7 @@ import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings'; import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; import BlocksoftUtils from '@crypto/common/BlocksoftUtils'; -import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; import XrpTmpDS from '@crypto/blockchains/xrp/stores/XrpTmpDS'; const CACHE_VALID_TIME = 60000; // 1 minute @@ -230,7 +230,7 @@ export default class XrpDataScanProvider { async _getLedger(index) { const now = new Date().getTime(); - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( 'XrpScannerProcessor.DataScan._getLedger started ' + index ); const link = BlocksoftExternalSettings.getStatic('XRP_SCANNER_SERVER'); @@ -257,7 +257,7 @@ export default class XrpDataScanProvider { typeof res.data.result !== 'undefined' && typeof res.data.result.ledger !== 'undefined' ) { - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( 'XrpScannerProcessor.DataScan._getLedger updated for index ' + index + ' ' + diff --git a/crypto/blockchains/xrp/basic/XrpDataScanProvider.ts b/crypto/blockchains/xrp/basic/XrpDataScanProvider.ts index 6ad8e0469..05b2dd3b0 100644 --- a/crypto/blockchains/xrp/basic/XrpDataScanProvider.ts +++ b/crypto/blockchains/xrp/basic/XrpDataScanProvider.ts @@ -1,7 +1,7 @@ import BlocksoftExternalSettings from '@crypto/common/AirDAOExternalSettings'; import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; import BlocksoftUtils from '@crypto/common/AirDAOUtils'; -import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; import XrpTmpDS from '@crypto/blockchains/xrp/stores/XrpTmpDS'; import { AxiosResponse } from 'axios'; @@ -263,7 +263,7 @@ export default class XrpDataScanProvider { async _getLedger(index: string | number): Promise { const now = new Date().getTime(); - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( 'XrpScannerProcessor.DataScan._getLedger started ' + index ); const link = BlocksoftExternalSettings.getStatic('XRP_SCANNER_SERVER'); @@ -290,7 +290,7 @@ export default class XrpDataScanProvider { typeof res.data.result !== 'undefined' && typeof res.data.result.ledger !== 'undefined' ) { - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( 'XrpScannerProcessor.DataScan._getLedger updated for index ' + index + ' ' + diff --git a/crypto/blockchains/xrp/basic/XrpTxSendProvider.ts b/crypto/blockchains/xrp/basic/XrpTxSendProvider.ts index b6829ebd7..deaeca146 100644 --- a/crypto/blockchains/xrp/basic/XrpTxSendProvider.ts +++ b/crypto/blockchains/xrp/basic/XrpTxSendProvider.ts @@ -6,7 +6,7 @@ * https://xrpl.org/rippleapi-reference.html#sign * https://xrpl.org/rippleapi-reference.html#submit */ -import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '../../../common/AirDAOCryptoLog'; import BlocksoftExternalSettings from '../../../common/AirDAOExternalSettings'; import { XrpTxUtils } from './XrpTxUtils'; // @ts-ignore @@ -24,15 +24,15 @@ export class XrpTxSendProvider { server: BlocksoftExternalSettings.getStatic('XRP_SERVER') }); // Public rippled server this._api.on('error', (errorCode: string, errorMessage: string) => { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'XrpTransferProcessor constructor' + errorCode + ': ' + errorMessage ); }); this._api.on('connected', () => { - BlocksoftCryptoLog.log('connected'); + AirDAOCryptoLog.log('connected'); }); this._api.on('disconnected', () => { - BlocksoftCryptoLog.log('disconnected'); + AirDAOCryptoLog.log('disconnected'); }); } @@ -78,13 +78,13 @@ export class XrpTxSendProvider { } } catch (e: any) { // @ts-ignore - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( `XrpTransferProcessor._getPrepared memo error + ${e.message}, ${data}` ); } // @ts-ignore - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( `XrpTransferProcessor._getPrepared payment, ${payment}` ); @@ -108,7 +108,7 @@ export class XrpTxSendProvider { ); } const txJson = prepared.txJSON; - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'XrpTxSendProvider._getPrepared prepared', txJson ); @@ -125,7 +125,7 @@ export class XrpTxSendProvider { msg: error.toString() } ); - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'XrpTxSendProvider._getPrepared error ' + error.toString() ); reject(error); @@ -142,7 +142,7 @@ export class XrpTxSendProvider { msg: error.toString() } ); - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'XrpTxSendProvider._getPrepared connect error ' + error.toString() ); reject(error); @@ -181,7 +181,7 @@ export class XrpTxSendProvider { try { const signed = this.signTx(data, privateData, txJson); // @ts-ignore - BlocksoftCryptoLog.log('XrpTransferProcessor.sendTx signed', signed); + AirDAOCryptoLog.log('XrpTransferProcessor.sendTx signed', signed); result = await new Promise((resolve, reject) => { api .connect() @@ -214,7 +214,7 @@ export class XrpTxSendProvider { msg: error.toString() } ); - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'XrpTransferProcessor.submit error ' + error.toString() ); reject(error); @@ -231,7 +231,7 @@ export class XrpTxSendProvider { msg: error.toString() } ); - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'XrpTransferProcessor.sendTx connect error ' + error.toString() ); reject(error); @@ -248,9 +248,7 @@ export class XrpTxSendProvider { msg: e.toString() } ); - BlocksoftCryptoLog.log( - 'XrpTransferProcessor.send2 error ' + e.toString() - ); + AirDAOCryptoLog.log('XrpTransferProcessor.send2 error ' + e.toString()); if (typeof e.resultMessage !== 'undefined') { throw new Error(e.resultMessage.toString()); } else if (typeof e.message !== 'undefined') { diff --git a/crypto/blockchains/xvg/XvgScannerProcessor.js b/crypto/blockchains/xvg/XvgScannerProcessor.js index ecc5f1475..aedacaf36 100644 --- a/crypto/blockchains/xvg/XvgScannerProcessor.js +++ b/crypto/blockchains/xvg/XvgScannerProcessor.js @@ -4,7 +4,7 @@ * https://api.vergecurrency.network/node/api/XVG/mainnet/address/DL5LtSf7wztH45VuYunL8oaQHtJbKLCHyw/balance */ import BlocksoftAxios from '../../common/BlocksoftAxios'; -import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '../../common/AirDAOCryptoLog'; import XvgTmpDS from './stores/XvgTmpDS'; import XvgFindAddressFunction from './basic/XvgFindAddressFunction'; @@ -48,11 +48,11 @@ export default class XvgScannerProcessor { */ async getTransactionsBlockchain(scanData, source = '') { const address = scanData.account.address.trim(); - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'XvgScannerProcessor.getTransactions started ' + address ); const link = `${API_PATH}/address/${address}/txs`; - BlocksoftCryptoLog.log('XvgScannerProcessor.getTransactions call ' + link); + AirDAOCryptoLog.log('XvgScannerProcessor.getTransactions call ' + link); let tmp = await BlocksoftAxios.get(link); if (tmp.status < 200 || tmp.status >= 300) { throw new Error('not valid server response status ' + link); @@ -89,7 +89,7 @@ export default class XvgScannerProcessor { already[tmp2.outcoming.transactionHash] = 1; if (tmp2.outcoming.addressTo === '?') { tmp2.outcoming.addressTo = 'self'; - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'XvgScannerProcessor.getTransactions consider as self ' + tmp2.outcoming.transactionHash ); @@ -119,7 +119,7 @@ export default class XvgScannerProcessor { } } } - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'XvgScannerProcessor.getTransactions finished ' + address + ' total: ' + @@ -151,7 +151,7 @@ export default class XvgScannerProcessor { let tmp; const link = `${API_PATH}/tx/${transaction.transactionHash}/coins`; - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'XvgScannerProcessor._unifyTransactionStep2 call for outputs should be ' + link ); @@ -162,7 +162,7 @@ export default class XvgScannerProcessor { ) { tmp = CACHE_FROM_DB[transaction.transactionHash + '_coins']; } else { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'XvgScannerProcessor._unifyTransactionStep2 called ' + link ); tmp = await BlocksoftAxios.get(link); @@ -186,7 +186,7 @@ export default class XvgScannerProcessor { transaction.addressAmount = output.value; const link2 = `${API_PATH}/tx/${transaction.transactionHash}`; - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'XvgScannerProcessor._unifyTransactionStep2 call for details ' + link2 ); let tmp2 = await BlocksoftAxios.get(link2); @@ -209,7 +209,7 @@ export default class XvgScannerProcessor { XvgTmpDS.saveCache(address, transaction.transactionHash, 'data', tmp2); CACHE_FROM_DB[transaction.transactionHash + '_data'] = 1; // no need all - just mark } - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'XvgScannerProcessor._unifyTransactionStep2 call for details result ', transaction ); diff --git a/crypto/blockchains/xvg/basic/XvgFindAddressFunction.ts b/crypto/blockchains/xvg/basic/XvgFindAddressFunction.ts index f94bce583..f2e8224b9 100644 --- a/crypto/blockchains/xvg/basic/XvgFindAddressFunction.ts +++ b/crypto/blockchains/xvg/basic/XvgFindAddressFunction.ts @@ -1,4 +1,4 @@ -import BlocksoftBN from '../../../common/BlocksoftBN'; +import BlocksoftBN from '../../../common/AirDAOBN'; interface TransactionIO { address: string; diff --git a/crypto/blockchains/xvg/providers/XvgSendProvider.ts b/crypto/blockchains/xvg/providers/XvgSendProvider.ts index 967883c44..90c3657c6 100644 --- a/crypto/blockchains/xvg/providers/XvgSendProvider.ts +++ b/crypto/blockchains/xvg/providers/XvgSendProvider.ts @@ -1,7 +1,7 @@ /** * @version 0.20 */ -import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '../../../common/AirDAOCryptoLog'; import BlocksoftAxios from '../../../common/BlocksoftAxios'; import BlocksoftExternalSettings from '../../../common/AirDAOExternalSettings'; import { AirDAOBlockchainTypes } from '@crypto/blockchains/AirDAOBlockchainTypes'; @@ -20,7 +20,7 @@ export default class XvgSendProvider subtitle: string ): Promise<{ transactionHash: string; transactionJson: any }> { const link = BlocksoftExternalSettings.getStatic('XVG_SEND_LINK'); - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' XvgSendProvider.sendTx ' + subtitle + @@ -32,7 +32,7 @@ export default class XvgSendProvider let res; try { res = await BlocksoftAxios.post(link, { rawTx: hex }); - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' XvgSendProvider.sendTx ' + subtitle + @@ -42,7 +42,7 @@ export default class XvgSendProvider hex ); } catch (e: any) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ' XvgSendProvider.sendTx ' + subtitle + diff --git a/crypto/blockchains/xvg/providers/XvgUnspentsProvider.ts b/crypto/blockchains/xvg/providers/XvgUnspentsProvider.ts index 2c0063177..a2b16afd4 100644 --- a/crypto/blockchains/xvg/providers/XvgUnspentsProvider.ts +++ b/crypto/blockchains/xvg/providers/XvgUnspentsProvider.ts @@ -2,7 +2,7 @@ * @version 0.20 * https://api.vergecurrency.network/node/api/XVG/mainnet/address/DL5LtSf7wztH45VuYunL8oaQHtJbKLCHyw/txs/?unspent=true */ -import BlocksoftCryptoLog from '../../../common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '../../../common/AirDAOCryptoLog'; import BlocksoftAxios from '../../../common/BlocksoftAxios'; import { AirDAOBlockchainTypes } from '@crypto/blockchains/AirDAOBlockchainTypes'; @@ -20,7 +20,7 @@ export default class XvgUnspentsProvider async getUnspents( address: string ): Promise { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ` XvgUnspentsProvider.getUnspents started', ${address}` @@ -29,7 +29,7 @@ export default class XvgUnspentsProvider const link = this._apiPath + address + '/txs/?unspent=true'; const res = await BlocksoftAxios.getWithoutBraking(link); - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + ` XvgUnspentsProvider.getUnspents link', ${link}` @@ -37,7 +37,7 @@ export default class XvgUnspentsProvider if (!res || typeof res === 'boolean' || !('data' in res)) { // Check if 'data' exists in 'res' - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( this._settings.currencyCode + `' XvgUnspentsProvider.getUnspents nothing loaded for address ' + ${address} + diff --git a/crypto/common/BlocksoftBN.js b/crypto/common/AirDAOBN.ts similarity index 72% rename from crypto/common/BlocksoftBN.js rename to crypto/common/AirDAOBN.ts index a2d31e8c4..606480922 100644 --- a/crypto/common/BlocksoftBN.js +++ b/crypto/common/AirDAOBN.ts @@ -1,24 +1,24 @@ import { BigNumber } from 'bignumber.js'; import BlocksoftUtils from './AirDAOUtils'; -class BlocksoftBN { +class AirDAOBN { innerBN = false; - + ̰ constructor(val) { - // console.log('BlocksoftBN construct', JSON.stringify(val)) + // console.log('AirDAOBN construct', JSON.stringify(val)) if (typeof val.innerBN !== 'undefined') { try { // noinspection JSCheckFunctionSignatures,JSUnresolvedVariable this.innerBN = new BigNumber(val.innerBN.toString()); } catch (e) { - throw new Error(e.message + ' while BlocksoftBN.constructor ' + val); + throw new Error(e.message + ' while AirDAOBN.constructor ' + val); } } else { try { // noinspection JSCheckFunctionSignatures,JSUnresolvedVariable this.innerBN = new BigNumber(val); } catch (e) { - throw new Error(e.message + ' while BlocksoftBN.constructor ' + val); + throw new Error(e.message + ' while AirDAOBN.constructor ' + val); } } } @@ -36,7 +36,7 @@ class BlocksoftBN { } add(val) { - // console.log('BlocksoftBN add ', JSON.stringify(val)) + // console.log('AirDAOBN add ', JSON.stringify(val)) if ( typeof val === 'undefined' || !val || @@ -52,7 +52,7 @@ class BlocksoftBN { val2 = val.innerBN; } else { throw new Error( - 'BlocksoftBN.add unsupported type ' + + 'AirDAOBN.add unsupported type ' + typeof val + ' ' + JSON.stringify(val) @@ -63,19 +63,19 @@ class BlocksoftBN { val = BlocksoftUtils.fromENumber(val); val2 = BigNumber(val); } catch (e) { - throw new Error(e.message + ' while BlocksoftBN.add transform ' + val); + throw new Error(e.message + ' while AirDAOBN.add transform ' + val); } } try { this.innerBN = this.innerBN.plus(val2); } catch (e) { - throw new Error(e.message + ' while BlocksoftBN.add ' + val); + throw new Error(e.message + ' while AirDAOBN.add ' + val); } return this; } diff(val) { - // console.log('BlocksoftBN diff ', JSON.stringify(val)) + // console.log('AirDAOBN diff ', JSON.stringify(val)) if (typeof val === 'undefined' || !val || val.toString() === '0') { return this; } @@ -85,7 +85,7 @@ class BlocksoftBN { val2 = val.innerBN; } else { throw new Error( - 'BlocksoftBN.diff unsupported type ' + + 'AirDAOBN.diff unsupported type ' + typeof val + ' ' + JSON.stringify(val) @@ -96,16 +96,16 @@ class BlocksoftBN { val = BlocksoftUtils.fromENumber(val); val2 = BigNumber(val); } catch (e) { - throw new Error(e.message + ' while BlocksoftBN.diff transform ' + val); + throw new Error(e.message + ' while AirDAOBN.diff transform ' + val); } } try { this.innerBN = this.innerBN.minus(val2); } catch (e) { - throw new Error(e.message + ' while BlocksoftBN.minus ' + val); + throw new Error(e.message + ' while AirDAOBN.minus ' + val); } return this; } } -export default BlocksoftBN; +export default AirDAOBN; diff --git a/crypto/common/AirDAOCryptoLog.ts b/crypto/common/AirDAOCryptoLog.ts new file mode 100644 index 000000000..dfe7298ed --- /dev/null +++ b/crypto/common/AirDAOCryptoLog.ts @@ -0,0 +1,156 @@ +/** + * Separated log class for crypto module - could be encoded here later + * @version 0.9 + */ +// import crashlytics from '@react-native-firebase/crashlytics' + +// import BlocksoftExternalSettings from './BlocksoftExternalSettings' + +// import config from '@app/config/config' +// import { FileSystem } from '@app/services/FileSystem/FileSystem' +// import MarketingEvent from '@app/services/Marketing/MarketingEvent' +// import settingsActions from '@app/appstores/Stores/Settings/SettingsActions' + +// const DEBUG = config.debug.cryptoLogs // set true to see usual logs in console + +const MAX_MESSAGE = 2000; +const FULL_MAX_MESSAGE = 20000; + +let LOGS_TXT = ''; +let FULL_LOGS_TXT = ''; + +class AirDAOCryptoLog { + constructor() { + // this.FS = new FileSystem({ fileEncoding: 'utf8', fileName: 'CryptoLog', fileExtension: 'txt' }) + // this.DATA = {} + // this.DATA.LOG_VERSION = false + } + + async _reinitTgMessage(testerMode, obj, msg) { + // for (const key in obj) { + // this.DATA[key] = obj[key] + // } + // // noinspection JSIgnoredPromiseFromCall + // await this.FS.checkOverflow() + } + + async log(txtOrObj, txtOrObj2 = false, txtOrObj3 = false) { + return true; + // if (settingsActions.getSettingStatic('loggingCode') === 'none') { + // return + // } + // let line = new Date().toISOString().replace(/T/, ' ').replace(/\..+/, '') + // let line2 = '' + // if (txtOrObj && typeof txtOrObj !== 'undefined') { + // if (typeof txtOrObj === 'string') { + // line += ' ' + txtOrObj + // } else { + // line += ' ' + JSON.stringify(txtOrObj, null, '\t') + // } + // } + // if (txtOrObj2 && typeof txtOrObj2 !== 'undefined') { + // if (typeof txtOrObj2 === 'string') { + // line += '\n\t\t\t\t\t' + txtOrObj2 + // } else if (txtOrObj2 === {}) { + // line += ' {} ' + // } else if (typeof txtOrObj2.sourceURL === 'undefined') { + // line += '\n\t\t\t\t\t' + JSON.stringify(txtOrObj2, null, '\t\t\t\t\t') + // } + // } + + // if (DEBUG) { + // console.log('CRYPTO ' + line) + // } + + // if (!config.debug.cryptoErrors && config.debug.firebaseLogs) { + // crashlytics().log(line) + // } + // await this.FS.writeLine(line) + + // if (txtOrObj3 && typeof txtOrObj3 !== 'undefined') { + // if (typeof txtOrObj3 === 'string') { + // line2 += '\t\t\t\t\t' + txtOrObj3 + // } else { + // line2 += '\t\t\t\t\t' + JSON.stringify(txtOrObj3, null, '\t\t\t\t\t') + // } + // if (!config.debug.cryptoErrors && config.debug.firebaseLogs) { + // crashlytics().log('\n', line2) + // } + // await this.FS.writeLine(line2) + // } + + // LOGS_TXT = line + line2 + '\n' + LOGS_TXT + // if (LOGS_TXT.length > MAX_MESSAGE) { + // LOGS_TXT = LOGS_TXT.substr(0, MAX_MESSAGE) + '...' + // } + + // FULL_LOGS_TXT = line + line2 + '\n' + FULL_LOGS_TXT + // if (FULL_LOGS_TXT.length > FULL_MAX_MESSAGE) { + // FULL_LOGS_TXT = LOGS_TXT.substr(0, FULL_MAX_MESSAGE) + '...' + // } + + // return true + } + + async err(errorObjectOrText, errorObject2 = '', errorTitle = 'ERROR') { + // const now = new Date() + // const date = now.toISOString().replace(/T/, ' ').replace(/\..+/, '') + // let line = '' + // if (errorObjectOrText && typeof errorObjectOrText !== 'undefined') { + // if (typeof errorObjectOrText === 'string') { + // line += ' ' + errorObjectOrText + // } else if (typeof errorObjectOrText.code !== 'undefined') { + // line += ' ' + errorObjectOrText.code + ' ' + errorObjectOrText.message + // } else { + // line += ' ' + errorObjectOrText.message + // } + // } + + // if (errorObject2 && typeof errorObject2 !== 'undefined' && errorObject2 !== '' && typeof errorObject2.message !== 'undefined') { + // line += ' ' + errorObject2.message + // } + + // if (config.debug.cryptoErrors || DEBUG) { + // console.log('==========CRPT ' + errorTitle + '==========') + // console.log(date + line) + // if (errorObject2) { + // console.log('error', errorObject2) + // } + // return false + // } + + // await this.log(errorObjectOrText, errorObject2) + + // LOGS_TXT = '\n\n\n\n==========' + errorTitle + '==========\n\n\n\n' + LOGS_TXT + // // noinspection JSUnresolvedFunction + // if (!config.debug.cryptoErrors) { + // crashlytics().log('==========' + errorTitle + '==========') + // } + // // noinspection ES6MissingAwait + // await this.FS.writeLine('==========' + errorTitle + '==========') + + // if (errorObject2 && typeof errorObject2.code !== 'undefined' && errorObject2.code === 'ERROR_USER') { + // return true + // } + + // try { + // await this.FS.writeLine('CRPT_2021_02 ' + line) + // if (!config.debug.cryptoErrors) { + // const e = new Error('CRPT_2021_02 ' + line) + // if (typeof crashlytics().recordError !== 'undefined') { + // crashlytics().recordError(e) + // } else { + // crashlytics().crash() + // } + // MarketingEvent.reinitCrashlytics() + // } + // } catch (firebaseError) { + + // } + + // return true + return true; + } +} + +export default new AirDAOCryptoLog(); diff --git a/crypto/common/AirDAOExternalSettings.ts b/crypto/common/AirDAOExternalSettings.ts index e6825f7f9..0ce28d57a 100644 --- a/crypto/common/AirDAOExternalSettings.ts +++ b/crypto/common/AirDAOExternalSettings.ts @@ -1,5 +1,5 @@ import BlocksoftAxios from './BlocksoftAxios'; -import BlocksoftCryptoLog from './BlocksoftCryptoLog'; +import AirDAOCryptoLog from './AirDAOCryptoLog'; // @ts-ignore import ApiProxy from '../../app/services/Api/ApiProxy'; import config from '@constants/config'; @@ -279,7 +279,7 @@ class AirDAOExternalSettings { typeof cached.currentServerValue === 'undefined' || !cached.currentServerValue ) { - BlocksoftCryptoLog.err( + AirDAOCryptoLog.err( 'BlocksoftExternalSettings.getTrezorServer ' + key + ' setInvalid error with currentServer ' + @@ -305,7 +305,7 @@ class AirDAOExternalSettings { ) { return cached.currentServerValue; } - BlocksoftCryptoLog.err( + AirDAOCryptoLog.err( 'BlocksoftExternalSettings.getTrezorServer ' + key + ' from ' + @@ -339,7 +339,7 @@ class AirDAOExternalSettings { } allServers[server] = tmp; } else { - BlocksoftCryptoLog.err( + AirDAOCryptoLog.err( 'BlocksoftExternalSettings.getTrezorServer ' + key + ' from ' + @@ -386,7 +386,7 @@ class AirDAOExternalSettings { currentServerIndex: 0 }; if ((typeof currentServer === 'string' || true) && currentServer) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( `'BlocksoftExternalSettings.getTrezorServer ' + ${key} + ' from ' + @@ -396,7 +396,7 @@ class AirDAOExternalSettings { ); return currentServer; } else { - BlocksoftCryptoLog.err( + AirDAOCryptoLog.err( 'BlocksoftExternalSettings.getTrezorServer ' + key + ' from ' + diff --git a/crypto/common/AirDAOPrivateKeysUtils.ts b/crypto/common/AirDAOPrivateKeysUtils.ts index 18963c3cf..d994e24c7 100644 --- a/crypto/common/AirDAOPrivateKeysUtils.ts +++ b/crypto/common/AirDAOPrivateKeysUtils.ts @@ -3,7 +3,7 @@ */ import BlocksoftKeysStorage from '@crypto/actions/BlocksoftKeysStorage/BlocksoftKeysStorage'; import BlocksoftKeys from '@crypto/actions/BlocksoftKeys/BlocksoftKeys'; -import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; import config from '@constants/config'; const CACHE: { [key: string]: any } = {}; @@ -37,7 +37,7 @@ export default { ? discoverFor.path : discoverFor.derivationPath; if (path === 'false' || !path) { - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( 'BlocksoftTransferPrivateKeysDiscover private key not discovered as path = false from ' + source ); @@ -47,7 +47,7 @@ export default { path.replace(/[']/g, 'quote'), discoverFor.currencyCode ); - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( `'BlocksoftTransferPrivateKeysDiscover.getPrivateKey actually inited ', ${{ address: discoverFor.addressToCheck, path, discoverForKey }}` ); @@ -66,7 +66,7 @@ export default { typeof discoverFor.mnemonic === 'undefined' || !discoverFor.mnemonic ) { - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( 'BlocksoftTransferPrivateKeysDiscover.getPrivateKey actually redo mnemonic ' + discoverFor.walletHash ), @@ -80,7 +80,7 @@ export default { discoverFor.addressToCheck && discoverFor.addressToCheck !== result.address ) { - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( 'BlocksoftTransferPrivateKeysDiscover private key discovered is not for address you path=' + discoverFor.derivationPath + ' set ' + @@ -115,7 +115,7 @@ export default { clone.derivationPath = path; const result2 = await BlocksoftKeys.discoverOne(clone); if (discoverFor.addressToCheck === result2.address) { - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( 'BlocksoftTransferPrivateKeysDiscover private key rediscovered FOUND address path=' + clone.derivationPath + ' set ' + @@ -129,7 +129,7 @@ export default { tmpFound = true; break; } else { - await BlocksoftCryptoLog.log( + await AirDAOCryptoLog.log( 'BlocksoftTransferPrivateKeysDiscover private key rediscovered is not for address path=' + clone.derivationPath + ' set ' + diff --git a/crypto/common/BlocksoftAxios.js b/crypto/common/BlocksoftAxios.js index 72361eda9..54a42f473 100644 --- a/crypto/common/BlocksoftAxios.js +++ b/crypto/common/BlocksoftAxios.js @@ -1,7 +1,7 @@ /** * @version 0.43 */ -import BlocksoftCryptoLog from './BlocksoftCryptoLog'; +import AirDAOCryptoLog from './AirDAOCryptoLog'; import axios from 'axios'; import config from '@app/config/config'; @@ -63,7 +63,7 @@ class BlocksoftAxios { CACHE_ERRORS_BY_LINKS[link].time = now; throw e; } - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'BlocksoftAxios.getWithoutBraking try ' + JSON.stringify(CACHE_ERRORS_BY_LINKS[link]) + ' error ' + @@ -99,7 +99,7 @@ class BlocksoftAxios { CACHE_ERRORS_BY_LINKS[link].time = now; throw e; } - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'BlocksoftAxios.postWithoutBraking try ' + JSON.stringify(CACHE_ERRORS_BY_LINKS[link]) + ' error ' + @@ -143,14 +143,14 @@ class BlocksoftAxios { tmpInner.status !== 201 && tmpInner.status !== 202 ) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'BlocksoftAxios.post fetch result ' + JSON.stringify(tmpInner) ); } else { tmp = { data: await tmpInner.json() }; } } catch (e) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'BlocksoftAxios.postWithHeaders fetch result error ' + e.message ); } @@ -183,7 +183,7 @@ class BlocksoftAxios { tmpInner.status !== 201 && tmpInner.status !== 202 ) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'BlocksoftAxios.get fetch result ' + JSON.stringify(tmpInner) ); } else { @@ -232,7 +232,7 @@ class BlocksoftAxios { tmpInner.status !== 201 && tmpInner.status !== 202 ) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'BlocksoftAxios.post fetch result ' + JSON.stringify(tmpInner) ); doOld = true; @@ -240,7 +240,7 @@ class BlocksoftAxios { tmp = { data: await tmpInner.json() }; } } catch (e) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'BlocksoftAxios.post fetch result error ' + e.message ); doOld = true; @@ -284,7 +284,7 @@ class BlocksoftAxios { antiCycle++; } while (tryOneMore && antiCycle < 3); } catch (e) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'BlocksoftAxios.get fetch result error ' + e.message ); doOld = true; @@ -382,7 +382,7 @@ class BlocksoftAxios { CACHE_STARTED[cacheMD].time + ' diff ' + (now - CACHE_STARTED[cacheMD].time); - BlocksoftCryptoLog.log('PREV CALL WILL BE CANCELED ' + timeMsg); + AirDAOCryptoLog.log('PREV CALL WILL BE CANCELED ' + timeMsg); await CACHE_STARTED_CANCEL[cacheMD].cancel( 'PREV CALL CANCELED ' + timeMsg ); @@ -437,9 +437,9 @@ class BlocksoftAxios { txt = JSON.stringify(tmp.data).substr(0, 300) } if (txt.length > 100) { - BlocksoftCryptoLog.log('BlocksoftAxios.' + method + ' finish ' + link, txt) // separate line for txt + AirDAOCryptoLog.log('BlocksoftAxios.' + method + ' finish ' + link, txt) // separate line for txt } else { - BlocksoftCryptoLog.log('BlocksoftAxios.' + method + ' finish ' + link + ' ' + JSON.stringify(txt)) + AirDAOCryptoLog.log('BlocksoftAxios.' + method + ' finish ' + link + ' ' + JSON.stringify(txt)) } */ @@ -486,7 +486,7 @@ class BlocksoftAxios { ) { if (link.indexOf('trustee.deals') !== -1) { // noinspection ES6MissingAwait - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'BlocksoftAxios.' + method + ' ' + @@ -512,7 +512,7 @@ class BlocksoftAxios { } } // noinspection ES6MissingAwait - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'BlocksoftAxios.' + method + ' ' + @@ -528,7 +528,7 @@ class BlocksoftAxios { e.message.indexOf('loudflare') !== -1 ) { // noinspection ES6MissingAwait - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'BlocksoftAxios.' + method + ' ' + @@ -539,7 +539,7 @@ class BlocksoftAxios { customError.code = 'ERROR_NOTICE'; } else if (link.indexOf('/api/v2/sendtx/') !== -1) { // noinspection ES6MissingAwait - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'BlocksoftAxios.' + method + ' ' + @@ -551,7 +551,7 @@ class BlocksoftAxios { customError.code = 'ERROR_NOTICE'; } else if (e.message.indexOf('account not found') !== -1) { // noinspection ES6MissingAwait - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'BlocksoftAxios.' + method + ' ' + link + ' ' + e.message ); // just nothing found return false; @@ -562,7 +562,7 @@ class BlocksoftAxios { link.indexOf('trustee.deals') !== -1 || link.indexOf('https://api.mainnet-beta.solana.com') !== -1 ) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'BlocksoftAxios.' + method + ' ' + @@ -573,7 +573,7 @@ class BlocksoftAxios { JSON.stringify(data) ); } else { - BlocksoftCryptoLog.err( + AirDAOCryptoLog.err( 'BlocksoftAxios.' + method + ' ' + @@ -584,7 +584,7 @@ class BlocksoftAxios { ); } } else { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'BlocksoftAxios.' + method + ' ' + @@ -596,7 +596,7 @@ class BlocksoftAxios { } customError.code = 'ERROR_SYSTEM'; } else { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'BlocksoftAxios.' + method + ' ' + diff --git a/crypto/common/BlocksoftCryptoLog.js b/crypto/common/BlocksoftCryptoLog.js deleted file mode 100644 index fb657acd7..000000000 --- a/crypto/common/BlocksoftCryptoLog.js +++ /dev/null @@ -1,160 +0,0 @@ -/** - * Separated log class for crypto module - could be encoded here later - * @version 0.9 - */ -import crashlytics from '@react-native-firebase/crashlytics' - -import BlocksoftExternalSettings from './BlocksoftExternalSettings' - -import config from '@app/config/config' -import { FileSystem } from '@app/services/FileSystem/FileSystem' -import MarketingEvent from '@app/services/Marketing/MarketingEvent' -import settingsActions from '@app/appstores/Stores/Settings/SettingsActions' - -const DEBUG = config.debug.cryptoLogs // set true to see usual logs in console - -const MAX_MESSAGE = 2000 -const FULL_MAX_MESSAGE = 20000 - -let LOGS_TXT = '' -let FULL_LOGS_TXT = '' - -class BlocksoftCryptoLog { - - constructor() { - this.FS = new FileSystem({ fileEncoding: 'utf8', fileName: 'CryptoLog', fileExtension: 'txt' }) - - this.DATA = {} - this.DATA.LOG_VERSION = false - } - - async _reinitTgMessage(testerMode, obj, msg) { - - for (const key in obj) { - this.DATA[key] = obj[key] - } - - // noinspection JSIgnoredPromiseFromCall - await this.FS.checkOverflow() - } - - async log(txtOrObj, txtOrObj2 = false, txtOrObj3 = false) { - if (settingsActions.getSettingStatic('loggingCode') === 'none') { - return - } - let line = new Date().toISOString().replace(/T/, ' ').replace(/\..+/, '') - let line2 = '' - if (txtOrObj && typeof txtOrObj !== 'undefined') { - if (typeof txtOrObj === 'string') { - line += ' ' + txtOrObj - } else { - line += ' ' + JSON.stringify(txtOrObj, null, '\t') - } - } - if (txtOrObj2 && typeof txtOrObj2 !== 'undefined') { - if (typeof txtOrObj2 === 'string') { - line += '\n\t\t\t\t\t' + txtOrObj2 - } else if (txtOrObj2 === {}) { - line += ' {} ' - } else if (typeof txtOrObj2.sourceURL === 'undefined') { - line += '\n\t\t\t\t\t' + JSON.stringify(txtOrObj2, null, '\t\t\t\t\t') - } - } - - if (DEBUG) { - console.log('CRYPTO ' + line) - } - - if (!config.debug.cryptoErrors && config.debug.firebaseLogs) { - crashlytics().log(line) - } - await this.FS.writeLine(line) - - if (txtOrObj3 && typeof txtOrObj3 !== 'undefined') { - if (typeof txtOrObj3 === 'string') { - line2 += '\t\t\t\t\t' + txtOrObj3 - } else { - line2 += '\t\t\t\t\t' + JSON.stringify(txtOrObj3, null, '\t\t\t\t\t') - } - if (!config.debug.cryptoErrors && config.debug.firebaseLogs) { - crashlytics().log('\n', line2) - } - await this.FS.writeLine(line2) - } - - LOGS_TXT = line + line2 + '\n' + LOGS_TXT - if (LOGS_TXT.length > MAX_MESSAGE) { - LOGS_TXT = LOGS_TXT.substr(0, MAX_MESSAGE) + '...' - } - - FULL_LOGS_TXT = line + line2 + '\n' + FULL_LOGS_TXT - if (FULL_LOGS_TXT.length > FULL_MAX_MESSAGE) { - FULL_LOGS_TXT = LOGS_TXT.substr(0, FULL_MAX_MESSAGE) + '...' - } - - return true - } - - async err(errorObjectOrText, errorObject2 = '', errorTitle = 'ERROR') { - const now = new Date() - const date = now.toISOString().replace(/T/, ' ').replace(/\..+/, '') - let line = '' - if (errorObjectOrText && typeof errorObjectOrText !== 'undefined') { - if (typeof errorObjectOrText === 'string') { - line += ' ' + errorObjectOrText - } else if (typeof errorObjectOrText.code !== 'undefined') { - line += ' ' + errorObjectOrText.code + ' ' + errorObjectOrText.message - } else { - line += ' ' + errorObjectOrText.message - } - } - - if (errorObject2 && typeof errorObject2 !== 'undefined' && errorObject2 !== '' && typeof errorObject2.message !== 'undefined') { - line += ' ' + errorObject2.message - } - - if (config.debug.cryptoErrors || DEBUG) { - console.log('==========CRPT ' + errorTitle + '==========') - console.log(date + line) - if (errorObject2) { - console.log('error', errorObject2) - } - return false - } - - await this.log(errorObjectOrText, errorObject2) - - LOGS_TXT = '\n\n\n\n==========' + errorTitle + '==========\n\n\n\n' + LOGS_TXT - // noinspection JSUnresolvedFunction - if (!config.debug.cryptoErrors) { - crashlytics().log('==========' + errorTitle + '==========') - } - // noinspection ES6MissingAwait - await this.FS.writeLine('==========' + errorTitle + '==========') - - - if (errorObject2 && typeof errorObject2.code !== 'undefined' && errorObject2.code === 'ERROR_USER') { - return true - } - - try { - await this.FS.writeLine('CRPT_2021_02 ' + line) - if (!config.debug.cryptoErrors) { - const e = new Error('CRPT_2021_02 ' + line) - if (typeof crashlytics().recordError !== 'undefined') { - crashlytics().recordError(e) - } else { - crashlytics().crash() - } - MarketingEvent.reinitCrashlytics() - } - } catch (firebaseError) { - - } - - return true - } - -} - -export default new BlocksoftCryptoLog() diff --git a/crypto/common/ext/bip44-constants.js b/crypto/common/ext/bip44-constants.ts similarity index 94% rename from crypto/common/ext/bip44-constants.js rename to crypto/common/ext/bip44-constants.ts index eedf32a54..84255efc4 100644 --- a/crypto/common/ext/bip44-constants.js +++ b/crypto/common/ext/bip44-constants.ts @@ -3,44 +3,43 @@ * Format for each row: * [ constant, coinSymbol, coinName ] */ -module.exports = [ +export default [ + [`44'/0`, 'BTC', 'Bitcoin'], + [`44'/1`, 'BTC_TEST', 'Testnet (all coins)'], + [`84'/0`, 'BTC_SEGWIT', 'Bitcoin'], + [`49'/0`, 'BTC_SEGWIT_COMPATIBLE', 'Bitcoin'], + [`44'/0`, 'USDT', 'USDT Omni'], // actual = 200 + [`44'/279553`, 'BTC_LIGHT', 'Bitcoin Light'], - [ `44'/0`, "BTC", "Bitcoin" ], - [ `44'/1`, "BTC_TEST", "Testnet (all coins)" ], - [ `84'/0`, "BTC_SEGWIT", "Bitcoin" ], - [ `49'/0`, "BTC_SEGWIT_COMPATIBLE", "Bitcoin" ], - [ `44'/0`, "USDT", "USDT Omni" ], // actual = 200 - [ `44'/279553`, "BTC_LIGHT", "Bitcoin Light" ], + [`44'/145`, 'BCH', 'Bitcoin Cash'], + [`44'/156`, 'BTG', 'Bitcoin Gold'], + [`44'/236`, 'BSV', 'Bitcoin SV'], - [ `44'/145`, "BCH", "Bitcoin Cash" ], - [ `44'/156`, "BTG", "Bitcoin Gold" ], - [ `44'/236`, "BSV", "Bitcoin SV" ], + [`44'/60`, 'ETH', 'Ether'], + [`44'/61`, 'ETC', 'Ether Classic'], + [`44'/1`, 'ETH_ROPSTEN', 'Ropsten Ether'], + [`44'/1`, 'ETH_RINKEBY', 'Rinkeby Ether'], - [ `44'/60`, "ETH", "Ether" ], - [ `44'/61`, "ETC", "Ether Classic" ], - [ `44'/1`, "ETH_ROPSTEN", "Ropsten Ether" ], - [ `44'/1`, "ETH_RINKEBY", "Rinkeby Ether" ], + [`44'/2`, 'LTC', 'Litecoin'], + [`84'/2`, 'LTC_SEGWIT', 'Litecoin'], + [`44'/3`, 'DOGE', 'Dogecoin'], + [`44'/77`, 'XVG', 'Verge'], + [`44'/195`, 'TRX', 'Tron'], - [ `44'/2`, "LTC", "Litecoin" ], - [ `84'/2`, "LTC_SEGWIT", "Litecoin" ], - [ `44'/3`, "DOGE", "Dogecoin" ], - [ `44'/77`, "XVG", "Verge" ], - [ `44'/195`, "TRX", "Tron" ], + [`44'/144`, 'XRP', 'Ripple'], + [`44'/148`, 'XLM', 'Stellar'], + [`44'/128`, 'XMR', 'Monero'], - [ `44'/144`, "XRP", "Ripple" ], - [ `44'/148`, "XLM", "Stellar" ], - [ `44'/128`, "XMR", "Monero" ], + [`44'/235`, 'FIO', 'FIO'], - [ `44'/235`, "FIO", "FIO" ], + [`44'/714`, 'BNB', 'BNB'], + [`44'/818`, 'VET', 'VET'], - [ `44'/714`, "BNB", "BNB" ], - [ `44'/818`, "VET", "VET" ], + [`44'/501`, 'SOL', 'SOL'], + [`44'/5741564`, 'WAVES', 'Waves'], + [`44'/5741564`, 'ASH', 'Ash'] - [ `44'/501`, "SOL", "SOL" ], - [ `44'/5741564`, "WAVES", "Waves" ], - [ `44'/5741564`, "ASH", "Ash" ], - - /* + /* [ 0x80000004, "RDD", "Reddcoin" ], [ 0x80000005, "DASH", "Dash (ex Darkcoin)" ], [ 0x80000006, "PPC", "Peercoin" ], @@ -513,4 +512,4 @@ module.exports = [ [ 0x857ab1e1, "kUSD", "kUSD" ], [ 0x85f5e0fe, "FLUID", "Fluid Chains" ], [ 0x85f5e0ff, "QKC", "QuarkChain" ] */ -] +]; diff --git a/crypto/common/ext/networks-constants.js b/crypto/common/ext/networks-constants.ts similarity index 100% rename from crypto/common/ext/networks-constants.js rename to crypto/common/ext/networks-constants.ts diff --git a/crypto/common/ext/scam-seeds.js b/crypto/common/ext/scam-seeds.js deleted file mode 100644 index 6c5b9bde0..000000000 --- a/crypto/common/ext/scam-seeds.js +++ /dev/null @@ -1,4 +0,0 @@ -module.exports = { - 'blue boost talk hero praise enemy sleep extra toddler escape ankle silk' : 1, - 'pass sugar city plate comfort tube filter merit oak trim frown love' : 1, -} diff --git a/crypto/common/ext/scam-seeds.ts b/crypto/common/ext/scam-seeds.ts new file mode 100644 index 000000000..fc9b0f27e --- /dev/null +++ b/crypto/common/ext/scam-seeds.ts @@ -0,0 +1,4 @@ +export default { + 'blue boost talk hero praise enemy sleep extra toddler escape ankle silk': 1, + 'pass sugar city plate comfort tube filter merit oak trim frown love': 1 +}; diff --git a/src/daemons/view/UpdateAccountListDaemon.js b/src/daemons/view/UpdateAccountListDaemon.js index 5877d692d..58d624947 100644 --- a/src/daemons/view/UpdateAccountListDaemon.js +++ b/src/daemons/view/UpdateAccountListDaemon.js @@ -19,7 +19,7 @@ import { setSelectedAccount, setSelectedAccountTransactions } from '../../appstores/Stores/Main/MainStoreActions'; -import BlocksoftBN from '../../../crypto/common/BlocksoftBN'; +import BlocksoftBN from '../../../crypto/common/AirDAOBN'; import MarketingEvent from '../../services/Marketing/MarketingEvent'; import { BlocksoftTransferUtils } from '@crypto/actions/BlocksoftTransfer/BlocksoftTransferUtils'; import walletActions from '@app/appstores/Stores/Wallet/WalletActions'; diff --git a/src/lib/AirDAOKeys.ts b/src/lib/AirDAOKeys.ts index ecbcc400e..931c54249 100644 --- a/src/lib/AirDAOKeys.ts +++ b/src/lib/AirDAOKeys.ts @@ -196,7 +196,7 @@ class AirDAOKeys { hasDerivations = true; } if (typeof CACHE[hexesCache] === 'undefined' || hasDerivations) { - // BlocksoftCryptoLog.log(`BlocksoftKeys will discover ${settings.addressProcessor}`) + // AirDAOCryptoLog.log(`BlocksoftKeys will discover ${settings.addressProcessor}`) let root = false; if (typeof networksConstants[currencyCode] !== 'undefined') { root = await this.getBip32Cached( @@ -212,7 +212,7 @@ class AirDAOKeys { } // BIP32 Extended Private Key to check - uncomment // let childFirst = root.derivePath('m/44\'/2\'/0\'/0') - // BlocksoftCryptoLog.log(childFirst.toBase58()) + // AirDAOCryptoLog.log(childFirst.toBase58()) /** * @type {EthAddressProcessor|BtcAddressProcessor} diff --git a/src/lib/BlocksoftKeys.js b/src/lib/BlocksoftKeys.js index 4c6c50457..c3782ea39 100644 --- a/src/lib/BlocksoftKeys.js +++ b/src/lib/BlocksoftKeys.js @@ -2,7 +2,7 @@ * @author Ksu * @version 0.5 */ -import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; import BlocksoftDict from '@crypto/common/BlocksoftDict'; import BlocksoftKeysUtils from '@crypto/actions/BlocksoftKeys/BlocksoftKeysUtils'; @@ -38,7 +38,7 @@ class BlocksoftKeys { * @return {Promise<{mnemonic: string, hash: string}>} */ async newMnemonic(size = 256) { - BlocksoftCryptoLog.log(`BlocksoftKeys newMnemonic called`); + AirDAOCryptoLog.log(`BlocksoftKeys newMnemonic called`); /* let mnemonic = false if (USE_TON) { @@ -50,7 +50,7 @@ class BlocksoftKeys { mnemonic = testMnemonic break } - BlocksoftCryptoLog.log('step ' + i + ' not valid ton ' + testMnemonic) + AirDAOCryptoLog.log('step ' + i + ' not valid ton ' + testMnemonic) } if (!mnemonic) { throw new Error('TON Mnemonic is not validating') @@ -64,7 +64,7 @@ class BlocksoftKeys { random = Buffer.from(random, 'base64'); const mnemonic = bip39.entropyToMnemonic(random); const hash = BlocksoftKeysUtils.hashMnemonic(mnemonic); - BlocksoftCryptoLog.log(`BlocksoftKeys newMnemonic finished`); + AirDAOCryptoLog.log(`BlocksoftKeys newMnemonic finished`); return { mnemonic, hash }; } @@ -73,7 +73,7 @@ class BlocksoftKeys { * @return {Promise} */ async validateMnemonic(mnemonic) { - BlocksoftCryptoLog.log(`BlocksoftKeys validateMnemonic called`); + AirDAOCryptoLog.log(`BlocksoftKeys validateMnemonic called`); if (await BlocksoftKeysScam.isScamMnemonic(mnemonic)) { throw new Error(strings('settings.walletList.scamImport')); } @@ -99,7 +99,7 @@ class BlocksoftKeys { const logData = { ...data }; if (typeof logData.mnemonic !== 'undefined') logData.mnemonic = '***'; - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( `BlocksoftKeys discoverAddresses called from ${source}`, JSON.stringify(logData).substring(0, 200) ); @@ -187,7 +187,7 @@ class BlocksoftKeys { hasDerivations = true; } if (typeof CACHE[hexesCache] === 'undefined' || hasDerivations) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( `BlocksoftKeys will discover ${settings.addressProcessor}` ); let root = false; @@ -205,7 +205,7 @@ class BlocksoftKeys { } // BIP32 Extended Private Key to check - uncomment // let childFirst = root.derivePath('m/44\'/2\'/0\'/0') - // BlocksoftCryptoLog.log(childFirst.toBase58()) + // AirDAOCryptoLog.log(childFirst.toBase58()) /** * @type {EthAddressProcessor|BtcAddressProcessor} @@ -224,7 +224,7 @@ class BlocksoftKeys { let currentToIndex = toIndex; let currentFullTree = fullTree; - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( `BlocksoftKeys discoverAddresses ${currencyCode} currentFromIndex.1 ${currentFromIndex}` ); if (hasDerivations) { @@ -261,7 +261,7 @@ class BlocksoftKeys { currentFromIndex = maxIndex * 1 + 1; currentToIndex = currentFromIndex * 1 + 10; currentFullTree = true; - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( `BlocksoftKeys ${currencyCode} discoverAddresses currentFromIndex.2 ${currentFromIndex}` ); } @@ -354,7 +354,7 @@ class BlocksoftKeys { ETH_CACHE[mnemonicCache] = results[currencyCode][0]; } } else { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( `BlocksoftKeys will be from cache ${settings.addressProcessor}` ); results[currencyCode] = CACHE[hexesCache]; @@ -363,12 +363,12 @@ class BlocksoftKeys { } } } - BlocksoftCryptoLog.log(`BlocksoftKeys discoverAddresses finished`); + AirDAOCryptoLog.log(`BlocksoftKeys discoverAddresses finished`); return results; } async getSeedCached(mnemonic) { - BlocksoftCryptoLog.log(`BlocksoftKeys bip39MnemonicToSeed started`); + AirDAOCryptoLog.log(`BlocksoftKeys bip39MnemonicToSeed started`); const mnemonicCache = mnemonic.toLowerCase(); if (typeof CACHE[mnemonicCache] === 'undefined') { CACHE[mnemonicCache] = await BlocksoftKeysUtils.bip39MnemonicToSeed( @@ -376,7 +376,7 @@ class BlocksoftKeys { ); } const seed = CACHE[mnemonicCache]; // will be rechecked on saving - BlocksoftCryptoLog.log(`BlocksoftKeys bip39MnemonicToSeed ended`); + AirDAOCryptoLog.log(`BlocksoftKeys bip39MnemonicToSeed ended`); return seed; } @@ -457,17 +457,17 @@ class BlocksoftKeys { path = `m/49'/0'/0'`; version = 0x049d7cb2; } - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'BlocksoftKeys.discoverXpub derive started ' + JSON.stringify(path) ); const rootWallet = root.derivePath(path); - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'BlocksoftKeys.discoverXpub derived, bs58 started ' + JSON.stringify(path) ); const res = bs58check.encode( serialize(rootWallet, version, rootWallet.publicKey) ); - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'BlocksoftKeys.discoverXpub res ' + JSON.stringify(path), res ); diff --git a/src/lib/BlocksoftKeysForRef.js b/src/lib/BlocksoftKeysForRef.js index ddd890190..b9bfe4b72 100644 --- a/src/lib/BlocksoftKeysForRef.js +++ b/src/lib/BlocksoftKeysForRef.js @@ -2,7 +2,7 @@ * @author Ksu * @version 0.5 */ -import BlocksoftCryptoLog from '../../common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '../../common/AirDAOCryptoLog'; import BlocksoftKeysForRefServerSide from './BlocksoftKeysForRefServerSide'; import BlocksoftKeys from '../BlocksoftKeys/BlocksoftKeys'; import BlocksoftDispatcher from '../../blockchains/BlocksoftDispatcher'; @@ -22,14 +22,14 @@ class BlocksoftKeysForRef { if (typeof logData.mnemonic !== 'undefined') logData.mnemonic = '***'; if (typeof CACHE[mnemonicCache] !== 'undefined') return CACHE[mnemonicCache]; - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( `BlocksoftKeysForRef discoverPublicAndPrivate called ` + JSON.stringify(logData) ); let result = BlocksoftKeys.getEthCached(mnemonicCache); if (!result) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( `BlocksoftKeysForRef discoverPublicAndPrivate no cache ` + JSON.stringify(logData) ); @@ -48,7 +48,7 @@ class BlocksoftKeysForRef { if (index === 0) { BlocksoftKeys.setEthCached(data.mnemonic, result); } - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( `BlocksoftKeysForRef discoverPublicAndPrivate finished no cache ` + JSON.stringify(logData) ); @@ -57,7 +57,7 @@ class BlocksoftKeysForRef { result.cashbackToken = BlocksoftKeysForRefServerSide.addressToToken( result.address ); - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( `BlocksoftKeysForRef discoverPublicAndPrivate finished ` + JSON.stringify(logData) ); diff --git a/src/lib/helpers/AirDAOKeys.ts b/src/lib/helpers/AirDAOKeys.ts index b50a4898a..6802b20a7 100644 --- a/src/lib/helpers/AirDAOKeys.ts +++ b/src/lib/helpers/AirDAOKeys.ts @@ -2,7 +2,7 @@ * @author Ksu * @version 0.5 */ -import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog'; +import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; import BlocksoftDict from '@crypto/common/BlocksoftDict'; import BlocksoftKeysUtils from '@crypto/actions/BlocksoftKeys/BlocksoftKeysUtils'; @@ -38,7 +38,7 @@ class BlocksoftKeys { * @return {Promise<{mnemonic: string, hash: string}>} */ async newMnemonic(size = 256) { - BlocksoftCryptoLog.log(`BlocksoftKeys newMnemonic called`); + AirDAOCryptoLog.log(`BlocksoftKeys newMnemonic called`); /* let mnemonic = false if (USE_TON) { @@ -50,7 +50,7 @@ class BlocksoftKeys { mnemonic = testMnemonic break } - BlocksoftCryptoLog.log('step ' + i + ' not valid ton ' + testMnemonic) + AirDAOCryptoLog.log('step ' + i + ' not valid ton ' + testMnemonic) } if (!mnemonic) { throw new Error('TON Mnemonic is not validating') @@ -64,7 +64,7 @@ class BlocksoftKeys { random = Buffer.from(random, 'base64'); const mnemonic = bip39.entropyToMnemonic(random); const hash = BlocksoftKeysUtils.hashMnemonic(mnemonic); - BlocksoftCryptoLog.log(`BlocksoftKeys newMnemonic finished`); + AirDAOCryptoLog.log(`BlocksoftKeys newMnemonic finished`); return { mnemonic, hash }; } @@ -73,7 +73,7 @@ class BlocksoftKeys { * @return {Promise} */ async validateMnemonic(mnemonic) { - BlocksoftCryptoLog.log(`BlocksoftKeys validateMnemonic called`); + AirDAOCryptoLog.log(`BlocksoftKeys validateMnemonic called`); if (await BlocksoftKeysScam.isScamMnemonic(mnemonic)) { throw new Error(strings('settings.walletList.scamImport')); } @@ -99,7 +99,7 @@ class BlocksoftKeys { const logData = { ...data }; if (typeof logData.mnemonic !== 'undefined') logData.mnemonic = '***'; - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( `BlocksoftKeys discoverAddresses called from ${source}`, JSON.stringify(logData).substring(0, 200) ); @@ -187,7 +187,7 @@ class BlocksoftKeys { hasDerivations = true; } if (typeof CACHE[hexesCache] === 'undefined' || hasDerivations) { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( `BlocksoftKeys will discover ${settings.addressProcessor}` ); let root = false; @@ -205,7 +205,7 @@ class BlocksoftKeys { } // BIP32 Extended Private Key to check - uncomment // let childFirst = root.derivePath('m/44\'/2\'/0\'/0') - // BlocksoftCryptoLog.log(childFirst.toBase58()) + // AirDAOCryptoLog.log(childFirst.toBase58()) /** * @type {EthAddressProcessor|BtcAddressProcessor} @@ -224,7 +224,7 @@ class BlocksoftKeys { let currentToIndex = toIndex; let currentFullTree = fullTree; - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( `BlocksoftKeys discoverAddresses ${currencyCode} currentFromIndex.1 ${currentFromIndex}` ); if (hasDerivations) { @@ -261,7 +261,7 @@ class BlocksoftKeys { currentFromIndex = maxIndex * 1 + 1; currentToIndex = currentFromIndex * 1 + 10; currentFullTree = true; - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( `BlocksoftKeys ${currencyCode} discoverAddresses currentFromIndex.2 ${currentFromIndex}` ); } @@ -355,7 +355,7 @@ class BlocksoftKeys { ETH_CACHE[mnemonicCache] = results[currencyCode][0]; } } else { - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( `BlocksoftKeys will be from cache ${settings.addressProcessor}` ); results[currencyCode] = CACHE[hexesCache]; @@ -364,12 +364,12 @@ class BlocksoftKeys { } } } - BlocksoftCryptoLog.log(`BlocksoftKeys discoverAddresses finished`); + AirDAOCryptoLog.log(`BlocksoftKeys discoverAddresses finished`); return results; } async getSeedCached(mnemonic) { - BlocksoftCryptoLog.log(`BlocksoftKeys bip39MnemonicToSeed started`); + AirDAOCryptoLog.log(`BlocksoftKeys bip39MnemonicToSeed started`); const mnemonicCache = mnemonic.toLowerCase(); if (typeof CACHE[mnemonicCache] === 'undefined') { CACHE[mnemonicCache] = await BlocksoftKeysUtils.bip39MnemonicToSeed( @@ -377,7 +377,7 @@ class BlocksoftKeys { ); } const seed = CACHE[mnemonicCache]; // will be rechecked on saving - BlocksoftCryptoLog.log(`BlocksoftKeys bip39MnemonicToSeed ended`); + AirDAOCryptoLog.log(`BlocksoftKeys bip39MnemonicToSeed ended`); return seed; } @@ -458,17 +458,17 @@ class BlocksoftKeys { path = `m/49'/0'/0'`; version = 0x049d7cb2; } - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'BlocksoftKeys.discoverXpub derive started ' + JSON.stringify(path) ); const rootWallet = root.derivePath(path); - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'BlocksoftKeys.discoverXpub derived, bs58 started ' + JSON.stringify(path) ); const res = bs58check.encode( serialize(rootWallet, version, rootWallet.publicKey) ); - BlocksoftCryptoLog.log( + AirDAOCryptoLog.log( 'BlocksoftKeys.discoverXpub res ' + JSON.stringify(path), res ); From c73d685463725d0fc40b35fae63459a86223d094 Mon Sep 17 00:00:00 2001 From: JavidHaji-zada Date: Thu, 3 Aug 2023 20:02:27 +0400 Subject: [PATCH 029/509] converted CryptoUtils --- crypto/blockchains/sol/SolTransferProcessor.ts | 4 ++-- crypto/common/AirDAOCryptoUtils.ts | 9 +++++++++ crypto/common/BlocksoftCryptoUtils.js | 11 ----------- 3 files changed, 11 insertions(+), 13 deletions(-) create mode 100644 crypto/common/AirDAOCryptoUtils.ts delete mode 100644 crypto/common/BlocksoftCryptoUtils.js diff --git a/crypto/blockchains/sol/SolTransferProcessor.ts b/crypto/blockchains/sol/SolTransferProcessor.ts index 6dd494a15..96d7f35b8 100644 --- a/crypto/blockchains/sol/SolTransferProcessor.ts +++ b/crypto/blockchains/sol/SolTransferProcessor.ts @@ -17,7 +17,7 @@ import SolUtils from '@crypto/blockchains/sol/ext/SolUtils'; import SolTmpDS from '@crypto/blockchains/sol/stores/SolTmpDS'; import SolStakeUtils from '@crypto/blockchains/sol/ext/SolStakeUtils'; import { Buffer } from 'buffer'; -import BlocksoftCryptoUtils from '@crypto/common/BlocksoftCryptoUtils'; +import AirDAOCryptoUtils from '@crypto/common/AirDAOCryptoUtils'; import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; import config from '@constants/config'; import { AirDAOBlockchainTypes } from '@crypto/blockchains/AirDAOBlockchainTypes'; @@ -270,7 +270,7 @@ export default class SolTransferProcessor StakeProgram.programId.toBuffer() ]); const hash = Buffer.from( - await BlocksoftCryptoUtils.sha256(buffer.toString('hex')), + await AirDAOCryptoUtils.sha256(buffer.toString('hex')), 'hex' ); const stakeAccount = new PublicKey(Buffer.from(hash, 'hex')); diff --git a/crypto/common/AirDAOCryptoUtils.ts b/crypto/common/AirDAOCryptoUtils.ts new file mode 100644 index 000000000..d3612904c --- /dev/null +++ b/crypto/common/AirDAOCryptoUtils.ts @@ -0,0 +1,9 @@ +import createHash from 'create-hash'; + +class AirDAOCryptoUtils { + static sha256(stringHex: string) { + return createHash('sha256').update(stringHex, 'hex').digest('hex'); + } +} + +export default AirDAOCryptoUtils; diff --git a/crypto/common/BlocksoftCryptoUtils.js b/crypto/common/BlocksoftCryptoUtils.js deleted file mode 100644 index 9a7a808cc..000000000 --- a/crypto/common/BlocksoftCryptoUtils.js +++ /dev/null @@ -1,11 +0,0 @@ -const createHash = require('create-hash') - -class BlocksoftCryptoUtils { - - static sha256(stringHex) { - return createHash('sha256').update(stringHex, 'hex').digest('hex') - } - -} - -export default BlocksoftCryptoUtils From c30f007f19c02c7ba56766451c0d4ccb4489aeea Mon Sep 17 00:00:00 2001 From: JavidHaji-zada Date: Thu, 3 Aug 2023 20:58:34 +0400 Subject: [PATCH 030/509] converted BlocksoftDict --- .../BlocksoftBalances/BlocksoftBalances.js | 4 +- crypto/actions/BlocksoftKeys/BlocksoftKeys.js | 6 +- .../BlocksoftKeysStorage.js | 4 +- .../BlocksoftTransfer/BlocksoftTransfer.ts | 14 ++-- .../BlocksoftTransferUtils.ts | 20 +++--- .../blockchains/AirDAOTransferDispatcher.ts | 2 +- .../btc/providers/BtcUnspentsProvider.ts | 8 +-- crypto/blockchains/btc/tx/BtcTxBuilder.ts | 18 +++-- .../blockchains/btc/tx/BtcTxInputsOutputs.ts | 8 +-- .../doge/tx/DogeTxInputsOutputs.ts | 9 ++- .../blockchains/eth/EthTokenProcessorNft.ts | 4 +- crypto/common/AirDAOCustomLinks.ts | 27 ++++++++ .../{BlocksoftDict.js => AirDAODict.ts} | 50 +++++++------- crypto/common/AirDAOPrettyNumbers.ts | 8 +-- crypto/common/BlocksoftCustomLinks.js | 26 -------- src/appTypes/database/DatabaseTable.ts | 4 +- src/daemons/view/UpdateAccountListDaemon.js | 16 ++--- src/database/Database.ts | 4 ++ src/database/main.ts | 6 +- src/database/models/account-balance.ts | 42 ++++++++++++ src/database/models/currency.ts | 29 +++++++++ src/database/models/index.ts | 2 + src/database/schemas/account-balance.ts | 65 +++++++++++++++++++ src/database/schemas/currency.ts | 53 +++++++++++++++ src/database/schemas/index.ts | 4 ++ src/lib/AirDAOKeys.ts | 6 +- src/lib/BlocksoftDispatcher.js | 8 +-- src/lib/BlocksoftKeys.js | 6 +- src/lib/helpers/AirDAOKeys.ts | 6 +- src/utils/blockchain.js | 4 +- 30 files changed, 334 insertions(+), 129 deletions(-) create mode 100644 crypto/common/AirDAOCustomLinks.ts rename crypto/common/{BlocksoftDict.js => AirDAODict.ts} (88%) delete mode 100644 crypto/common/BlocksoftCustomLinks.js create mode 100644 src/database/models/account-balance.ts create mode 100644 src/database/models/currency.ts create mode 100644 src/database/schemas/account-balance.ts create mode 100644 src/database/schemas/currency.ts diff --git a/crypto/actions/BlocksoftBalances/BlocksoftBalances.js b/crypto/actions/BlocksoftBalances/BlocksoftBalances.js index 7a48eb123..4d201ab92 100644 --- a/crypto/actions/BlocksoftBalances/BlocksoftBalances.js +++ b/crypto/actions/BlocksoftBalances/BlocksoftBalances.js @@ -4,7 +4,7 @@ */ import BlocksoftDispatcher from '../../blockchains/BlocksoftDispatcher'; import AirDAOCryptoLog from '../../common/AirDAOCryptoLog'; -import BlocksoftDict from '../../common/BlocksoftDict'; +import AirDAODict from '../../common/AirDAODict'; import BlocksoftExternalSettings from '@crypto/common/AirDAOExternalSettings'; class BlocksoftBalances { @@ -45,7 +45,7 @@ class BlocksoftBalances { this._processor[currencyCode] = BlocksoftDispatcher.getScannerProcessor(currencyCode); this._allSettings[currencyCode] = - BlocksoftDict.getCurrencyAllSettings(currencyCode); + AirDAODict.getCurrencyAllSettings(currencyCode); } this._currencySettings = this._allSettings[currencyCode]; return this; diff --git a/crypto/actions/BlocksoftKeys/BlocksoftKeys.js b/crypto/actions/BlocksoftKeys/BlocksoftKeys.js index 6802b20a7..4a070615a 100644 --- a/crypto/actions/BlocksoftKeys/BlocksoftKeys.js +++ b/crypto/actions/BlocksoftKeys/BlocksoftKeys.js @@ -3,7 +3,7 @@ * @version 0.5 */ import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; -import BlocksoftDict from '@crypto/common/BlocksoftDict'; +import AirDAODict from '@crypto/common/AirDAODict'; import BlocksoftKeysUtils from '@crypto/actions/BlocksoftKeys/BlocksoftKeysUtils'; import * as BlocksoftRandom from 'react-native-blocksoft-random'; @@ -104,7 +104,7 @@ class BlocksoftKeys { JSON.stringify(logData).substring(0, 200) ); - let toDiscover = BlocksoftDict.Codes; + let toDiscover = AirDAODict.Codes; if (data.currencyCode) { if (typeof data.currencyCode === 'string') { toDiscover = [data.currencyCode]; @@ -128,7 +128,7 @@ class BlocksoftKeys { for (currencyCode of toDiscover) { results[currencyCode] = []; try { - settings = BlocksoftDict.getCurrencyAllSettings( + settings = AirDAODict.getCurrencyAllSettings( currencyCode, 'BlocksoftKeys' ); diff --git a/crypto/actions/BlocksoftKeysStorage/BlocksoftKeysStorage.js b/crypto/actions/BlocksoftKeysStorage/BlocksoftKeysStorage.js index dda6e591a..ff9872dcd 100644 --- a/crypto/actions/BlocksoftKeysStorage/BlocksoftKeysStorage.js +++ b/crypto/actions/BlocksoftKeysStorage/BlocksoftKeysStorage.js @@ -8,7 +8,7 @@ import * as Keychain from 'react-native-keychain'; import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; import config from '@app/config/config'; -import BlocksoftDict from '@crypto/common/BlocksoftDict'; +import AirDAODict from '@crypto/common/AirDAODict'; export class BlocksoftKeysStorage { /** @@ -308,7 +308,7 @@ export class BlocksoftKeysStorage { } getAddressCacheKey(walletHash, discoverPath, currencyCode) { - const settings = BlocksoftDict.getCurrencyAllSettings(currencyCode); + const settings = AirDAODict.getCurrencyAllSettings(currencyCode); if ( typeof settings.addressCurrencyCode !== 'undefined' && typeof settings.tokenBlockchain !== 'undefined' && diff --git a/crypto/actions/BlocksoftTransfer/BlocksoftTransfer.ts b/crypto/actions/BlocksoftTransfer/BlocksoftTransfer.ts index 62d04ee2c..754f6bc18 100644 --- a/crypto/actions/BlocksoftTransfer/BlocksoftTransfer.ts +++ b/crypto/actions/BlocksoftTransfer/BlocksoftTransfer.ts @@ -6,14 +6,14 @@ import AirDAOCryptoLog from '../../common/AirDAOCryptoLog'; import { BlocksoftBlockchainTypes } from '../../blockchains/BlocksoftBlockchainTypes'; import { BlocksoftTransferDispatcher } from '../../blockchains/BlocksoftTransferDispatcher'; import { BlocksoftTransferPrivate } from './BlocksoftTransferPrivate'; -import { BlocksoftDictTypes } from '../../common/AirDAODictTypes'; +import { AirDAODictTypes } from '../../common/AirDAODictTypes'; -import CoinBlocksoftDict from '@crypto/assets/coinBlocksoftDict.json'; +import CoinAirDAODict from '@crypto/assets/coinAirDAODict.json'; import config from '../../../app/config/config'; type DataCache = { - [key in BlocksoftDictTypes.Code]: { + [key in AirDAODictTypes.Code]: { key: string; memo: string | boolean; time: number; @@ -105,8 +105,8 @@ export namespace BlocksoftTransfer { ): Promise { const lower = data.addressTo.toLowerCase(); if (!data?.walletConnectData?.data) { - for (const key in CoinBlocksoftDict) { - const tmp = CoinBlocksoftDict[key]; + for (const key in CoinAirDAODict) { + const tmp = CoinAirDAODict[key]; if ( typeof tmp.canBeDestination !== 'undefined' && tmp.canBeDestination @@ -208,8 +208,8 @@ export namespace BlocksoftTransfer { const lower = data.addressTo.toLowerCase(); if (!data?.walletConnectData?.data) { - for (const key in CoinBlocksoftDict) { - const tmp = CoinBlocksoftDict[key]; + for (const key in CoinAirDAODict) { + const tmp = CoinAirDAODict[key]; if ( typeof tmp.canBeDestination !== 'undefined' && tmp.canBeDestination diff --git a/crypto/actions/BlocksoftTransfer/BlocksoftTransferUtils.ts b/crypto/actions/BlocksoftTransfer/BlocksoftTransferUtils.ts index c3c591824..63ef9c7a4 100644 --- a/crypto/actions/BlocksoftTransfer/BlocksoftTransferUtils.ts +++ b/crypto/actions/BlocksoftTransfer/BlocksoftTransferUtils.ts @@ -2,25 +2,25 @@ * @author Ksu * @version 0.20 */ -import { BlocksoftDictTypes } from '../../common/AirDAODictTypes'; +import { AirDAODictTypes } from '../../common/AirDAODictTypes'; import { BlocksoftTransferDispatcher } from '../../blockchains/BlocksoftTransferDispatcher'; import { BlocksoftBlockchainTypes } from '../../blockchains/BlocksoftBlockchainTypes'; import BlocksoftUtils from '../../common/AirDAOUtils'; export namespace BlocksoftTransferUtils { export const getAddressToForTransferAll = function (data: { - currencyCode: BlocksoftDictTypes.Code; + currencyCode: AirDAODictTypes.Code; address: string; }): string { - if (data.currencyCode === BlocksoftDictTypes.Code.BTC_TEST) { + if (data.currencyCode === AirDAODictTypes.Code.BTC_TEST) { return 'mjojEgUSi68PqNHoAyjhVkwdqQyLv9dTfV'; } - if (data.currencyCode === BlocksoftDictTypes.Code.XRP) { + if (data.currencyCode === AirDAODictTypes.Code.XRP) { const tmp1 = 'rEAgA9B8U8RCkwn6MprHqE1ZfXoeGQxz4P'; const tmp2 = 'rnyWPfJ7dk2X15N7jqwmqo3Nspu1oYiRZ3'; return data.address === tmp1 ? tmp2 : tmp1; } - if (data.currencyCode === BlocksoftDictTypes.Code.XLM) { + if (data.currencyCode === AirDAODictTypes.Code.XLM) { const tmp1 = 'GCVPV3D4PAYFA7H2CHGFRTSPAHMSU4KQR4CHBUBUR4X23JUDJWHYZDYZ'; const tmp2 = 'GAQA5FITDUZW36J6VFXAH4YDNTTDEGRNWIXHIOR3FNN4DVJCXCNHUR4E'; return data.address === tmp1 ? tmp2 : tmp1; @@ -44,9 +44,9 @@ export namespace BlocksoftTransferUtils { balance: string; unconfirmed: string | boolean; balanceStaked: string; - currencyCode: BlocksoftDictTypes.Code; + currencyCode: AirDAODictTypes.Code; }): string { - if (data.currencyCode === BlocksoftDictTypes.Code.TRX) { + if (data.currencyCode === AirDAODictTypes.Code.TRX) { if (data.balanceStaked && data.balanceStaked !== '') { return BlocksoftUtils.diff(data.balance, data.balanceStaked); } @@ -58,17 +58,17 @@ export namespace BlocksoftTransferUtils { if (data.unconfirmed * 1 < 0) { return data.balance; } - if (data.currencyCode === BlocksoftDictTypes.Code.XRP) { + if (data.currencyCode === AirDAODictTypes.Code.XRP) { return data.balance; } if ( - data.currencyCode === BlocksoftDictTypes.Code.ETH || + data.currencyCode === AirDAODictTypes.Code.ETH || data.currencyCode.indexOf('ETH_') === 0 ) { return data.balance; } if ( - data.currencyCode === BlocksoftDictTypes.Code.TRX || + data.currencyCode === AirDAODictTypes.Code.TRX || data.currencyCode.indexOf('TRX_') === 0 ) { return data.balance; diff --git a/crypto/blockchains/AirDAOTransferDispatcher.ts b/crypto/blockchains/AirDAOTransferDispatcher.ts index 192733aa9..8521152a5 100644 --- a/crypto/blockchains/AirDAOTransferDispatcher.ts +++ b/crypto/blockchains/AirDAOTransferDispatcher.ts @@ -3,7 +3,7 @@ * @author Ksu * @version 0.20 */ -import AirDAODict from '../common/BlocksoftDict'; +import AirDAODict from '../common/AirDAODict'; import { AirDAODictTypes } from '../common/AirDAODictTypes'; import BchTransferProcessor from './bch/BchTransferProcessor'; diff --git a/crypto/blockchains/btc/providers/BtcUnspentsProvider.ts b/crypto/blockchains/btc/providers/BtcUnspentsProvider.ts index a0556b22e..2a21bfe85 100644 --- a/crypto/blockchains/btc/providers/BtcUnspentsProvider.ts +++ b/crypto/blockchains/btc/providers/BtcUnspentsProvider.ts @@ -9,7 +9,7 @@ import DogeUnspentsProvider from '../../doge/providers/DogeUnspentsProvider'; // import Database from '@app/appstores/DataSource/Database'; import { Database } from '@database'; import AirDAOCryptoLog from '../../../common/AirDAOCryptoLog'; -import BlocksoftDict from '@crypto/common/BlocksoftDict'; +import AirDAODict from '@crypto/common/AirDAODict'; import { Q } from '@nozbe/watermelondb'; import { DatabaseTable } from '@appTypes'; import { AccountDBModel } from '@database/models/account'; @@ -27,8 +27,7 @@ export default class BtcUnspentsProvider } const mainCurrencyCode = currencyCode === 'LTC' ? 'LTC' : 'BTC'; const segwitPrefix = - BlocksoftDict.CurrenciesForTests[mainCurrencyCode + '_SEGWIT'] - .addressPrefix; + AirDAODict.CurrenciesForTests[mainCurrencyCode + '_SEGWIT'].addressPrefix; AirDAOCryptoLog.log( currencyCode + @@ -189,8 +188,7 @@ export default class BtcUnspentsProvider const mainCurrencyCode = this._settings.currencyCode === 'LTC' ? 'LTC' : 'BTC'; const segwitPrefix = - BlocksoftDict.CurrenciesForTests[mainCurrencyCode + '_SEGWIT'] - .addressPrefix; + AirDAODict.CurrenciesForTests[mainCurrencyCode + '_SEGWIT'].addressPrefix; const sqlPub = `SELECT wallet_pub_value as walletPubValue FROM ${DatabaseTable.WalletPub} diff --git a/crypto/blockchains/btc/tx/BtcTxBuilder.ts b/crypto/blockchains/btc/tx/BtcTxBuilder.ts index 0a08bbe04..781ce4ab3 100644 --- a/crypto/blockchains/btc/tx/BtcTxBuilder.ts +++ b/crypto/blockchains/btc/tx/BtcTxBuilder.ts @@ -6,7 +6,7 @@ import DogeTxBuilder from '../../doge/tx/DogeTxBuilder'; import BlocksoftPrivateKeysUtils from '../../../common/AirDAOPrivateKeysUtils'; import { ECPair, payments, TransactionBuilder } from 'bitcoinjs-lib'; import AirDAOCryptoLog from '../../../common/AirDAOCryptoLog'; -import BlocksoftDict from '@crypto/common/BlocksoftDict'; +import AirDAODict from '@crypto/common/AirDAODict'; export default class BtcTxBuilder extends DogeTxBuilder @@ -54,15 +54,13 @@ export default class BtcTxBuilder this._settings.currencyCode === 'LTC' ? 'LTC' : 'BTC'; const segwitPrefix = - BlocksoftDict.CurrenciesForTests[mainCurrencyCode + '_SEGWIT'] - .addressPrefix; + AirDAODict.CurrenciesForTests[mainCurrencyCode + '_SEGWIT'].addressPrefix; const segwitCompatiblePrefix = - typeof BlocksoftDict.CurrenciesForTests[ + typeof AirDAODict.CurrenciesForTests[ mainCurrencyCode + '_SEGWIT_COMPATIBLE' ] !== 'undefined' - ? BlocksoftDict.CurrenciesForTests[ - mainCurrencyCode + '_SEGWIT_COMPATIBLE' - ].addressPrefix + ? AirDAODict.CurrenciesForTests[mainCurrencyCode + '_SEGWIT_COMPATIBLE'] + .addressPrefix : false; if (typeof this.keyPairBTC[input.address] === 'undefined') { @@ -71,7 +69,7 @@ export default class BtcTxBuilder currencyCode += '_SEGWIT'; if (!input.derivationPath || input.derivationPath === 'false') { input.derivationPath = - BlocksoftDict.CurrenciesForTests[currencyCode].defaultPath; + AirDAODict.CurrenciesForTests[currencyCode].defaultPath; } } else if ( segwitCompatiblePrefix && @@ -80,11 +78,11 @@ export default class BtcTxBuilder currencyCode += '_SEGWIT_COMPATIBLE'; if (!input.derivationPath || input.derivationPath === 'false') { input.derivationPath = - BlocksoftDict.CurrenciesForTests[currencyCode].defaultPath; + AirDAODict.CurrenciesForTests[currencyCode].defaultPath; } } else if (!input.derivationPath || input.derivationPath === 'false') { input.derivationPath = - BlocksoftDict.CurrenciesForTests[mainCurrencyCode].defaultPath; + AirDAODict.CurrenciesForTests[mainCurrencyCode].defaultPath; } const discoverFor = { mnemonic: this.mnemonic, diff --git a/crypto/blockchains/btc/tx/BtcTxInputsOutputs.ts b/crypto/blockchains/btc/tx/BtcTxInputsOutputs.ts index eeea6d27c..d862f3b4c 100644 --- a/crypto/blockchains/btc/tx/BtcTxInputsOutputs.ts +++ b/crypto/blockchains/btc/tx/BtcTxInputsOutputs.ts @@ -6,7 +6,7 @@ import BtcUnspentsProvider from '../providers/BtcUnspentsProvider'; import DogeTxInputsOutputs from '../../doge/tx/DogeTxInputsOutputs'; import AirDAOCryptoLog from '../../../common/AirDAOCryptoLog'; import DaemonCache from '../../../../src/daemons/DaemonCache'; -import BlocksoftDict from '@crypto/common/BlocksoftDict'; +import AirDAODict from '@crypto/common/AirDAODict'; import { Database } from '@database'; export default class BtcTxInputsOutputs @@ -25,11 +25,9 @@ export default class BtcTxInputsOutputs const mainCurrencyCode = this._settings.currencyCode === 'LTC' ? 'LTC' : 'BTC'; - const legacyPrefix = - BlocksoftDict.Currencies[mainCurrencyCode].addressPrefix; + const legacyPrefix = AirDAODict.Currencies[mainCurrencyCode].addressPrefix; const segwitPrefix = - BlocksoftDict.CurrenciesForTests[mainCurrencyCode + '_SEGWIT'] - .addressPrefix; + AirDAODict.CurrenciesForTests[mainCurrencyCode + '_SEGWIT'].addressPrefix; let needFindSegwit = true; if (btcShowTwoAddress === '1' || data.useLegacy === 1) { diff --git a/crypto/blockchains/doge/tx/DogeTxInputsOutputs.ts b/crypto/blockchains/doge/tx/DogeTxInputsOutputs.ts index 9e68c03f1..71b0e353c 100644 --- a/crypto/blockchains/doge/tx/DogeTxInputsOutputs.ts +++ b/crypto/blockchains/doge/tx/DogeTxInputsOutputs.ts @@ -5,7 +5,7 @@ import { AirDAOBlockchainTypes } from '../../AirDAOBlockchainTypes'; import BlocksoftBN from '../../../common/AirDAOBN'; import BlocksoftUtils from '../../../common/AirDAOUtils'; import AirDAOCryptoLog from '../../../common/AirDAOCryptoLog'; -import BlocksoftDict from '@crypto/common/BlocksoftDict'; +import AirDAODict from '@crypto/common/AirDAODict'; // @ts-ignore import coinSelect from 'coinselect'; @@ -142,12 +142,11 @@ export default class DogeTxInputsOutputs const isRequired: any = {}; let isAllRequired = true; const segwitPrefix = - typeof BlocksoftDict.CurrenciesForTests[ + typeof AirDAODict.CurrenciesForTests[ this._settings.currencyCode + '_SEGWIT' ] !== 'undefined' - ? BlocksoftDict.CurrenciesForTests[ - this._settings.currencyCode + '_SEGWIT' - ].addressPrefix + ? AirDAODict.CurrenciesForTests[this._settings.currencyCode + '_SEGWIT'] + .addressPrefix : false; for (const unspent of unspents) { const input = { diff --git a/crypto/blockchains/eth/EthTokenProcessorNft.ts b/crypto/blockchains/eth/EthTokenProcessorNft.ts index 4ce5aa44c..98394b10d 100644 --- a/crypto/blockchains/eth/EthTokenProcessorNft.ts +++ b/crypto/blockchains/eth/EthTokenProcessorNft.ts @@ -5,7 +5,7 @@ import EthBasic from './basic/EthBasic'; import EthNftOpensea from '@crypto/blockchains/eth/apis/EthNftOpensea'; import EthNftMatic from '@crypto/blockchains/eth/apis/EthNftMatic'; import abi from './ext/erc721.js'; -import BlocksoftDictNfts from '@crypto/common/BlocksoftDictNfts'; +import AirDAODictNfts from '@crypto/common/AirDAODictNfts'; export default class EthTokenProcessorNft extends EthBasic { /** @@ -13,7 +13,7 @@ export default class EthTokenProcessorNft extends EthBasic { * @param data.tokenBlockchainCode */ async getListBlockchain(data) { - const settings = BlocksoftDictNfts.NftsIndexed[data.tokenBlockchainCode]; + const settings = AirDAODictNfts.NftsIndexed[data.tokenBlockchainCode]; if ( typeof settings !== 'undefined' && typeof settings.apiType !== 'undefined' && diff --git a/crypto/common/AirDAOCustomLinks.ts b/crypto/common/AirDAOCustomLinks.ts new file mode 100644 index 000000000..8fe4512bf --- /dev/null +++ b/crypto/common/AirDAOCustomLinks.ts @@ -0,0 +1,27 @@ +/** + * @version 0.54 + * @author Javid + */ +// import { ThemeContext } from '@app/theme/ThemeProvider'; +// import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings'; +// import { sublocale } from '@app/services/i18n'; + +class AirDAOCustomLinks { + getLink(link, isLight) { + // const themeColor = isLight ? 'light' : 'dark'; + // const siteLang = sublocale(); + // const paramsLink = `themeColor=${themeColor}&siteLang=${siteLang}`; + // const subLink = BlocksoftExternalSettings.getStatic(link); + + // if (subLink.includes('?')) { + // return `${subLink}&${paramsLink}`; + // } else { + // return `${subLink}?${paramsLink}`; + // } + return link; + } +} + +export default new AirDAOCustomLinks(); + +// AirDAOCustomLinks.contextType = ThemeContext; diff --git a/crypto/common/BlocksoftDict.js b/crypto/common/AirDAODict.ts similarity index 88% rename from crypto/common/BlocksoftDict.js rename to crypto/common/AirDAODict.ts index cd1f4d52e..f8bae23c8 100644 --- a/crypto/common/BlocksoftDict.js +++ b/crypto/common/AirDAODict.ts @@ -1,8 +1,8 @@ import { NativeModules } from 'react-native'; -import Database from '@app/appstores/DataSource/Database'; - -import CoinBlocksoftDict from '@crypto/assets/coinBlocksoftDict.json'; +import CoinAirDAODict from '@crypto/assets/coinAirDAODict.json'; +import { Database } from '@database'; +import { DatabaseTable } from '@appTypes'; const { RNFastCrypto } = NativeModules; @@ -34,7 +34,7 @@ const Codes = [ 'ETH_DAI', 'FIO' // add code here for autocreation the wallet address with the currency ]; -const Currencies = CoinBlocksoftDict; +const Currencies = CoinAirDAODict; const CurrenciesForTests = { BTC_SEGWIT: { @@ -98,26 +98,25 @@ if (typeof RNFastCrypto === 'undefined') { delete Currencies['XMR']; } -/** - * @param {int} currencyObject.id - * @param {int} currencyObject.isHidden - * @param {string} currencyObject.tokenJson - * @param {string} currencyObject.tokenDecimals 18 - * @param {string} currencyObject.tokenAddress '0xd26114cd6EE289AccF82350c8d8487fedB8A0C07' - * @param {string} currencyObject.tokenType 'ETH_ERC_20' - * @param {string} currencyObject.currencyName 'OMG Token' - * @param {string} currencyObject.currencySymbol 'OMG' - * @param {string} currencyObject.currencyCode 'OMG' - */ -function addAndUnifyCustomCurrency(currencyObject) { +function addAndUnifyCustomCurrency(currencyObject: { + id: string; + isHidden: number; + tokenJSON: string; + tokenDecimals: string; + tokenAddress: string; + tokenType: string; + currencyName: string; + currencySymbol: string; + currencyCode: string; +}) { const tmp = { currencyName: currencyObject.currencyName, currencyCode: 'CUSTOM_' + currencyObject.currencyCode, currencySymbol: currencyObject.currencySymbol, ratesCurrencyCode: currencyObject.currencyCode, - decimals: currencyObject.tokenDecimals + decimals: currencyObject.tokenDecimals, + currencyType: 'custom' }; - tmp.currencyType = 'custom'; if (currencyObject.tokenType === 'BNB_SMART_20') { tmp.currencyCode = 'CUSTOM_BNB_SMART_20_' + currencyObject.currencyCode; if (tmp.ratesCurrencyCode.substr(0, 1) === 'B') { @@ -205,11 +204,18 @@ function getCurrencyAllSettings(currencyCodeOrObject, source = '') { return false; } if (currencyCode === 'ETH_LAND') { - Database.query(`DELETE FROM account WHERE currency_code='ETH_LAND'`); - Database.query( - `DELETE FROM account_balance WHERE currency_code='ETH_LAND'` + Database.unsafeRawQuery( + DatabaseTable.Accounts, + `DELETE FROM ${DatabaseTable.Accounts} WHERE currency_code='ETH_LAND'` + ); + Database.unsafeRawQuery( + DatabaseTable.AccountBalances, + `DELETE FROM ${DatabaseTable.AccountBalances} WHERE currency_code='ETH_LAND'` + ); + Database.unsafeRawQuery( + DatabaseTable.Currencies, + `DELETE FROM ${DatabaseTable.Currencies} WHERE currency_code='ETH_LAND'` ); - Database.query(`DELETE FROM currency WHERE currency_code='ETH_LAND'`); } if (typeof currencyCodeOrObject.currencyCode !== 'undefined') { diff --git a/crypto/common/AirDAOPrettyNumbers.ts b/crypto/common/AirDAOPrettyNumbers.ts index deb64a5d5..e96baf7a6 100644 --- a/crypto/common/AirDAOPrettyNumbers.ts +++ b/crypto/common/AirDAOPrettyNumbers.ts @@ -1,4 +1,4 @@ -import BlocksoftDict from './BlocksoftDict'; +import AirDAODict from './AirDAODict'; import AirDAOUtils from './AirDAOUtils'; class AirDAOPrettyNumbers { @@ -10,12 +10,12 @@ class AirDAOPrettyNumbers { * @return {AirDAOPrettyNumbers} */ setCurrencyCode(currencyCode: string): AirDAOPrettyNumbers { - const settings = BlocksoftDict.getCurrencyAllSettings(currencyCode); + const settings = AirDAODict.getCurrencyAllSettings(currencyCode); if (settings.prettyNumberProcessor) { this._processorCode = settings.prettyNumberProcessor; } else { throw new Error( - `BlocksoftDict.getCurrencyAllSettings no settings.prettyNumberProcessor for ${currencyCode}` + `AirDAODict.getCurrencyAllSettings no settings.prettyNumberProcessor for ${currencyCode}` ); } if ( @@ -25,7 +25,7 @@ class AirDAOPrettyNumbers { this._decimals = settings.decimals; } else { throw new Error( - `BlocksoftDict.getCurrencyAllSettings no settings.decimals for ${currencyCode}` + `AirDAODict.getCurrencyAllSettings no settings.decimals for ${currencyCode}` ); } diff --git a/crypto/common/BlocksoftCustomLinks.js b/crypto/common/BlocksoftCustomLinks.js deleted file mode 100644 index 5fd33561c..000000000 --- a/crypto/common/BlocksoftCustomLinks.js +++ /dev/null @@ -1,26 +0,0 @@ -/** - * @version 0.54 - * @author Vadym - */ -import { ThemeContext } from '@app/theme/ThemeProvider'; -import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings'; -import { sublocale } from '@app/services/i18n'; - -class BlocksoftCustomLinks { - getLink(link, isLight) { - const themeColor = isLight ? 'light' : 'dark'; - const siteLang = sublocale(); - const paramsLink = `themeColor=${themeColor}&siteLang=${siteLang}`; - const subLink = BlocksoftExternalSettings.getStatic(link); - - if (subLink.includes('?')) { - return `${subLink}&${paramsLink}`; - } else { - return `${subLink}?${paramsLink}`; - } - } -} - -export default new BlocksoftCustomLinks(); - -BlocksoftCustomLinks.contextType = ThemeContext; diff --git a/src/appTypes/database/DatabaseTable.ts b/src/appTypes/database/DatabaseTable.ts index fe2b0911e..d64850881 100644 --- a/src/appTypes/database/DatabaseTable.ts +++ b/src/appTypes/database/DatabaseTable.ts @@ -1,8 +1,10 @@ export enum DatabaseTable { Accounts = 'accounts', + AccountBalances = 'account_balances', + Currencies = 'currencies', TransactionScannersTmp = 'transactions_scanners_tmp', Transactions = 'transactions', TransactionRaw = 'transactions_raw', Wallets = 'wallets', - WalletPub = 'wallet-pub' + WalletPub = 'wallet_pub' } diff --git a/src/daemons/view/UpdateAccountListDaemon.js b/src/daemons/view/UpdateAccountListDaemon.js index 58d624947..993cf389c 100644 --- a/src/daemons/view/UpdateAccountListDaemon.js +++ b/src/daemons/view/UpdateAccountListDaemon.js @@ -10,7 +10,7 @@ import store from '../../store'; import accountDS from '../../appstores/DataSource/Account/Account'; import walletPubDS from '../../appstores/DataSource/Wallet/WalletPub'; -import BlocksoftDict from '../../../crypto/common/BlocksoftDict'; +import AirDAODict from '../../../crypto/common/AirDAODict'; import BlocksoftPrettyNumbers from '@crypto/common/AirDAOPrettyNumbers'; import BlocksoftUtils from '@crypto/common/AirDAOUtils'; @@ -140,7 +140,7 @@ class UpdateAccountListDaemon extends Update { if ( tmpCurrency.address.indexOf( - BlocksoftDict.Currencies[tmpCurrency.currencyCode].addressPrefix + AirDAODict.Currencies[tmpCurrency.currencyCode].addressPrefix ) === 0 ) { if ( @@ -154,7 +154,7 @@ class UpdateAccountListDaemon extends Update { } } else if ( tmpCurrency.address.indexOf( - BlocksoftDict.CurrenciesForTests[ + AirDAODict.CurrenciesForTests[ tmpCurrency.currencyCode + '_SEGWIT' ].addressPrefix ) === 0 @@ -441,13 +441,13 @@ class UpdateAccountListDaemon extends Update { } if ( - typeof BlocksoftDict.CurrenciesForTests[ + typeof AirDAODict.CurrenciesForTests[ tmpCurrency.currencyCode + '_SEGWIT' ] !== 'undefined' ) { if ( tmpCurrency.address.indexOf( - BlocksoftDict.Currencies[tmpCurrency.currencyCode].addressPrefix + AirDAODict.Currencies[tmpCurrency.currencyCode].addressPrefix ) === 0 ) { reformatted[tmpCurrency.walletHash][ @@ -455,7 +455,7 @@ class UpdateAccountListDaemon extends Update { ].legacy = tmpCurrency.address; } else if ( tmpCurrency.address.indexOf( - BlocksoftDict.CurrenciesForTests[ + AirDAODict.CurrenciesForTests[ tmpCurrency.currencyCode + '_SEGWIT' ].addressPrefix ) === 0 @@ -636,12 +636,12 @@ class UpdateAccountListDaemon extends Update { ); const account = reformatted[tmpWalletHash][currencyCode]; - const extendCurrencyCode = BlocksoftDict.getCurrencyAllSettings( + const extendCurrencyCode = AirDAODict.getCurrencyAllSettings( account.currencyCode ); account.feesCurrencyCode = extendCurrencyCode.feesCurrencyCode || account.currencyCode; - const extendedFeesCode = BlocksoftDict.getCurrencyAllSettings( + const extendedFeesCode = AirDAODict.getCurrencyAllSettings( account.feesCurrencyCode ); account.feesCurrencySymbol = diff --git a/src/database/Database.ts b/src/database/Database.ts index 5a1709c3b..441e64787 100644 --- a/src/database/Database.ts +++ b/src/database/Database.ts @@ -44,6 +44,10 @@ class Database { // @ts-ignore newModel[key] = model[key]; } + // assign hash to id in Wallets table + if (table === DatabaseTable.Wallets) { + newModel._raw.id = model.hash; + } return newModel; }); } catch (error) { diff --git a/src/database/main.ts b/src/database/main.ts index d18d505e7..4922ec391 100644 --- a/src/database/main.ts +++ b/src/database/main.ts @@ -7,7 +7,9 @@ import { AccountDBModel, WalletPubDBModel, TransactionRawDBModel, - TransactionScannersTmpDBModel + TransactionScannersTmpDBModel, + AccountBalanceDBModel, + CurrencyDBModel } from './models'; import { schema } from './schemas'; @@ -23,6 +25,8 @@ export const database = new Database({ adapter, modelClasses: [ AccountDBModel, + AccountBalanceDBModel, + CurrencyDBModel, TransactionsDBModel, TransactionRawDBModel, TransactionScannersTmpDBModel, diff --git a/src/database/models/account-balance.ts b/src/database/models/account-balance.ts new file mode 100644 index 000000000..c6b23ad56 --- /dev/null +++ b/src/database/models/account-balance.ts @@ -0,0 +1,42 @@ +/* eslint-disable camelcase */ +import { DatabaseTable } from '@appTypes'; +import { Model } from '@nozbe/watermelondb'; +import { text, field, relation } from '@nozbe/watermelondb/decorators'; +import { WalletDBModel } from './wallet'; +import { AccountDBModel } from './account'; + +export class AccountBalanceDBModel extends Model { + static table = DatabaseTable.AccountBalances; + + // define relations + // @ts-ignore + @relation(DatabaseTable.Wallets, 'hash') hash: WalletDBModel; + // @ts-ignore + @relation(DatabaseTable.Accounts, 'account_id') account: AccountDBModel; + + // define fields + // @ts-ignore + @field('balance_fix') balanceFix: number; + // @ts-ignore + @text('balance_txt') balanceTxt: string; + // @ts-ignore + @field('unconfirmed_fix') unconfirmedFix: number; + // @ts-ignore + @text('unconfirmed_txt') unconfirmedTxt: string; + // @ts-ignore + @text('balance_provider') balanceProvider: number; + // @ts-ignore + @field('balance_scan_time') balanceScanTime: number; + // @ts-ignore + @text('balance_scan_error') balanceScanError: string; + // @ts-ignore + @text('balance_scan_log') balanceScanLog: string; + // @ts-ignore + @text('balance_scan_block') balanceScanBlock: string; + // @ts-ignore + @text('balance_staked_txt') balanceStakedTxt: string; + // @ts-ignore + @field('status') status: number; + // @ts-ignore + @text('currency_code') currencyCode: string; +} diff --git a/src/database/models/currency.ts b/src/database/models/currency.ts new file mode 100644 index 000000000..5e0d153ff --- /dev/null +++ b/src/database/models/currency.ts @@ -0,0 +1,29 @@ +/* eslint-disable camelcase */ +import { DatabaseTable } from '@appTypes'; +import { Model } from '@nozbe/watermelondb'; +import { text, field } from '@nozbe/watermelondb/decorators'; + +export class CurrencyDBModel extends Model { + static table = DatabaseTable.Currencies; + + // @ts-ignore + @field('is_hidden') isHidden: number; + // @ts-ignore + @text('currency_code') currencyCode: string; + // @ts-ignore + @field('currency_rate_usd') currencyRateUSD: number; + // @ts-ignore + @text('currency_rate_json') currencyRateJSON: string; + // @ts-ignore + @field('currency_rate_scan_time') currencyRateScanTime: number; + // @ts-ignore + @text('price_provider') priceProvider: string; + // @ts-ignore + @field('price_change_24h') priceChange24H: number; + // @ts-ignore + @field('price_high_24h') priceHigh24H: number; + // @ts-ignore + @field('price_low_24h') priceLow24H: number; + // @ts-ignore + @field('price_last_updated') priceLastUpdated: number; +} diff --git a/src/database/models/index.ts b/src/database/models/index.ts index 8dd5021e8..ab1e36ec7 100644 --- a/src/database/models/index.ts +++ b/src/database/models/index.ts @@ -1,4 +1,6 @@ export * from './account'; +export * from './account-balance'; +export * from './currency'; export * from './transaction-scanners-tmp'; export * from './transactions-raw'; export * from './transactions'; diff --git a/src/database/schemas/account-balance.ts b/src/database/schemas/account-balance.ts new file mode 100644 index 000000000..884795efc --- /dev/null +++ b/src/database/schemas/account-balance.ts @@ -0,0 +1,65 @@ +import { DatabaseTable } from '@appTypes'; +import { tableSchema } from '@nozbe/watermelondb'; + +export const AccountBalancesTable = tableSchema({ + name: DatabaseTable.AccountBalances, + columns: [ + { + name: 'balance_fix', + type: 'number' + }, + { + name: 'balance_txt', + type: 'string' + }, + { + name: 'unconfirmed_fix', + type: 'string' + }, + { + name: 'unconfirmed_txt', + type: 'string' + }, + { + name: 'balance_provider', + type: 'string' + }, + { + name: 'balance_scan_time', + type: 'number' + }, + { + name: 'balance_scan_error', + type: 'string' + }, + + { + name: 'balance_scan_log', + type: 'string' + }, + { + name: 'balance_scan_block', + type: 'string' + }, + { + name: 'balance_staked_txt', + type: 'string' + }, + { + name: 'status', + type: 'string' + }, + { + name: 'currency_code', + type: 'string' + }, + { + name: 'hash', + type: 'string' + }, + { + name: 'account_id', + type: 'string' + } + ] +}); diff --git a/src/database/schemas/currency.ts b/src/database/schemas/currency.ts new file mode 100644 index 000000000..94f0c61cc --- /dev/null +++ b/src/database/schemas/currency.ts @@ -0,0 +1,53 @@ +import { DatabaseTable } from '@appTypes'; +import { tableSchema } from '@nozbe/watermelondb'; + +export const CurrenciesTable = tableSchema({ + name: DatabaseTable.Currencies, + columns: [ + { + name: 'is_hidden', + type: 'number' + }, + { + name: 'currency_code', + type: 'string' + }, + { + name: 'currency_rate_usd', + type: 'number' + }, + { + name: 'currency_rate_json', + type: 'string' + }, + { + name: 'currency_rate_scan_time', + type: 'number' + }, + { + name: 'price_provider', + type: 'string' + }, + { + name: 'price_change_percentage_24h', + type: 'number' + }, + + { + name: 'price_change_24h', + type: 'number' + }, + { + name: 'price_high_24h', + type: 'number' + }, + { + name: 'price_low_24h', + type: 'number' + }, + { + name: 'price_last_updated', + type: 'number' + } + ] +}); diff --git a/src/database/schemas/index.ts b/src/database/schemas/index.ts index 5ed5ada65..bee31963c 100644 --- a/src/database/schemas/index.ts +++ b/src/database/schemas/index.ts @@ -5,11 +5,15 @@ import { WalletPubTable } from './wallet-pub'; import { AccountsTable } from './account'; import { TransactionRawTable } from './transaction-raw'; import { TransactionsTable } from './transactions'; +import { AccountBalancesTable } from './account-balance'; +import { CurrenciesTable } from './currency'; export const schema = appSchema({ version: 1, tables: [ AccountsTable, + AccountBalancesTable, + CurrenciesTable, TransactionsTable, TransactionRawTable, TransactionScannersTmpTable, diff --git a/src/lib/AirDAOKeys.ts b/src/lib/AirDAOKeys.ts index 931c54249..8cfb8fc70 100644 --- a/src/lib/AirDAOKeys.ts +++ b/src/lib/AirDAOKeys.ts @@ -1,7 +1,7 @@ import * as SecureStore from 'expo-secure-store'; import KeysUtills from '@utils/keys'; import config from '@constants/config'; -import BlocksoftDict from '@crypto/common/BlocksoftDict'; +import AirDAODict from '@crypto/common/AirDAODict'; import { bip32 } from 'bitcoinjs-lib'; import BlocksoftDispatcher from '@lib/BlocksoftDispatcher'; const networksConstants = require('../lib/common/ext/network-constants'); @@ -115,7 +115,7 @@ class AirDAOKeys { async discoverAddresses(data: any, source: string) { const logData = { ...data }; if (typeof logData.mnemonic !== 'undefined') logData.mnemonic = '***'; - let toDiscover = BlocksoftDict.Codes; + let toDiscover = AirDAODict.Codes; if (data.currencyCode) { if (typeof data.currencyCode === 'string') { toDiscover = [data.currencyCode]; @@ -137,7 +137,7 @@ class AirDAOKeys { for (currencyCode of toDiscover) { results[currencyCode] = []; try { - settings = BlocksoftDict.getCurrencyAllSettings( + settings = AirDAODict.getCurrencyAllSettings( currencyCode, 'BlocksoftKeys' ); diff --git a/src/lib/BlocksoftDispatcher.js b/src/lib/BlocksoftDispatcher.js index d7dfcbd2f..851e31374 100644 --- a/src/lib/BlocksoftDispatcher.js +++ b/src/lib/BlocksoftDispatcher.js @@ -2,7 +2,7 @@ * @author Ksu * @version 0.5 */ -import BlocksoftDict from '../common/BlocksoftDict'; +import AirDAODict from '../common/AirDAODict'; import BchAddressProcessor from './bch/BchAddressProcessor'; import BchScannerProcessor from './bch/BchScannerProcessor'; @@ -77,7 +77,7 @@ class BlocksoftDispatcher { */ getAddressProcessor(currencyCode) { const currencyDictSettings = - BlocksoftDict.getCurrencyAllSettings(currencyCode); + AirDAODict.getCurrencyAllSettings(currencyCode); return this.innerGetAddressProcessor(currencyDictSettings); } @@ -129,7 +129,7 @@ class BlocksoftDispatcher { */ getScannerProcessor(currencyCode) { const currencyDictSettings = - BlocksoftDict.getCurrencyAllSettings(currencyCode); + AirDAODict.getCurrencyAllSettings(currencyCode); switch (currencyDictSettings.scannerProcessor) { case 'BCH': return new BchScannerProcessor(currencyDictSettings); @@ -301,7 +301,7 @@ class BlocksoftDispatcher { */ getSecretsProcessor(currencyCode) { const currencyDictSettings = - BlocksoftDict.getCurrencyAllSettings(currencyCode); + AirDAODict.getCurrencyAllSettings(currencyCode); if (currencyDictSettings.currencyCode !== 'XMR') { throw new Error( 'Unknown secretsProcessor ' + currencyDictSettings.currencyCode diff --git a/src/lib/BlocksoftKeys.js b/src/lib/BlocksoftKeys.js index c3782ea39..8455e2847 100644 --- a/src/lib/BlocksoftKeys.js +++ b/src/lib/BlocksoftKeys.js @@ -3,7 +3,7 @@ * @version 0.5 */ import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; -import BlocksoftDict from '@crypto/common/BlocksoftDict'; +import AirDAODict from '@crypto/common/AirDAODict'; import BlocksoftKeysUtils from '@crypto/actions/BlocksoftKeys/BlocksoftKeysUtils'; import * as BlocksoftRandom from 'react-native-blocksoft-random'; @@ -104,7 +104,7 @@ class BlocksoftKeys { JSON.stringify(logData).substring(0, 200) ); - let toDiscover = BlocksoftDict.Codes; + let toDiscover = AirDAODict.Codes; if (data.currencyCode) { if (typeof data.currencyCode === 'string') { toDiscover = [data.currencyCode]; @@ -128,7 +128,7 @@ class BlocksoftKeys { for (currencyCode of toDiscover) { results[currencyCode] = []; try { - settings = BlocksoftDict.getCurrencyAllSettings( + settings = AirDAODict.getCurrencyAllSettings( currencyCode, 'BlocksoftKeys' ); diff --git a/src/lib/helpers/AirDAOKeys.ts b/src/lib/helpers/AirDAOKeys.ts index 6802b20a7..4a070615a 100644 --- a/src/lib/helpers/AirDAOKeys.ts +++ b/src/lib/helpers/AirDAOKeys.ts @@ -3,7 +3,7 @@ * @version 0.5 */ import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; -import BlocksoftDict from '@crypto/common/BlocksoftDict'; +import AirDAODict from '@crypto/common/AirDAODict'; import BlocksoftKeysUtils from '@crypto/actions/BlocksoftKeys/BlocksoftKeysUtils'; import * as BlocksoftRandom from 'react-native-blocksoft-random'; @@ -104,7 +104,7 @@ class BlocksoftKeys { JSON.stringify(logData).substring(0, 200) ); - let toDiscover = BlocksoftDict.Codes; + let toDiscover = AirDAODict.Codes; if (data.currencyCode) { if (typeof data.currencyCode === 'string') { toDiscover = [data.currencyCode]; @@ -128,7 +128,7 @@ class BlocksoftKeys { for (currencyCode of toDiscover) { results[currencyCode] = []; try { - settings = BlocksoftDict.getCurrencyAllSettings( + settings = AirDAODict.getCurrencyAllSettings( currencyCode, 'BlocksoftKeys' ); diff --git a/src/utils/blockchain.js b/src/utils/blockchain.js index f4b4c840d..cbfce6ee5 100644 --- a/src/utils/blockchain.js +++ b/src/utils/blockchain.js @@ -1,6 +1,6 @@ import { NativeModules } from 'react-native'; -import CoinBlocksoftDict from '@crypto/assets/coinBlocksoftDict.json'; +import CoinAirDAODict from '@crypto/assets/coinAirDAODict.json'; const { RNFastCrypto } = NativeModules; @@ -32,7 +32,7 @@ const Codes = [ 'ETH_DAI', 'FIO' // add code here for autocreation the wallet address with the currency ]; -const Currencies = CoinBlocksoftDict; +const Currencies = CoinAirDAODict; const CurrenciesForTests = { BTC_SEGWIT: { From 534fcc1d047239a6323a4b73576fb5c8055680f1 Mon Sep 17 00:00:00 2001 From: JavidHaji-zada Date: Thu, 3 Aug 2023 23:44:24 +0400 Subject: [PATCH 031/509] converted DictNfts & ExplorerDict --- crypto/common/AirDAODictNfts.ts | 125 +++++++++++++++++++++++++ crypto/common/AirDAOExplorerDict.ts | 36 +++++++ crypto/common/BlocksoftDictNfts.js | 119 ----------------------- crypto/common/BlocksoftExplorerDict.js | 39 -------- 4 files changed, 161 insertions(+), 158 deletions(-) create mode 100644 crypto/common/AirDAODictNfts.ts create mode 100644 crypto/common/AirDAOExplorerDict.ts delete mode 100644 crypto/common/BlocksoftDictNfts.js delete mode 100644 crypto/common/BlocksoftExplorerDict.js diff --git a/crypto/common/AirDAODictNfts.ts b/crypto/common/AirDAODictNfts.ts new file mode 100644 index 000000000..fb5abce57 --- /dev/null +++ b/crypto/common/AirDAODictNfts.ts @@ -0,0 +1,125 @@ +const Nfts = [ + { + currencyCode: 'NFT_ETH', + currencyName: 'NFT', + currencySymbol: 'NFT', + tokenBlockchain: 'ETHEREUM', + tokenBlockchainCode: 'ETH', + currencyType: 'NFT', + apiType: 'OPENSEA', + explorerLink: 'https://explorerLink.com/token/', + showOnHome: true + }, + { + currencyCode: 'NFT_BNB', + currencyName: 'BNB NFT', + currencySymbol: 'NFT', + tokenBlockchain: 'BNB', + tokenBlockchainShortTitle: 'BNB SC', + tokenBlockchainLongTitle: 'BNB Smart Chain', + tokenBlockchainCode: 'BNB_SMART', + currencyType: 'NFT', + explorerLink: 'https://bscscan.com/token/', + showOnHome: false + }, + + { + currencyCode: 'NFT_MATIC', + currencyName: 'Matic NFT', + currencySymbol: 'NFT', + tokenBlockchain: 'MATIC', + tokenBlockchainCode: 'MATIC', + currencyType: 'NFT', + explorerLink: 'https://polygonscan.com/token/', + showOnHome: false + }, + { + currencyCode: 'NFT_ONE', + currencyName: 'Harmony NFT', + currencySymbol: 'NFT', + tokenBlockchain: 'ONE', + tokenBlockchainCode: 'ONE', + currencyType: 'NFT', + explorerLink: 'https://davinci.gallery/view/', + showOnHome: false + }, + { + currencyCode: 'NFT_ROPSTEN', + currencyName: 'Ropsten NFT', + currencySymbol: 'NFT', + tokenBlockchain: 'ROPSTEN', + tokenBlockchainCode: 'ETH_ROPSTEN', + currencyType: 'NFT', + explorerLink: 'https://ropsten.explorerLink.io/token/', + showOnHome: false + }, + { + currencyCode: 'NFT_RINKEBY', + currencyName: 'Rinkeby NFT', + currencySymbol: 'NFT', + tokenBlockchain: 'RINKEBY', + tokenBlockchainCode: 'ETH_RINKEBY', + currencyType: 'NFT', + apiType: 'OPENSEA', + explorerLink: 'https://rinkeby.explorerLink.io/token/', + showOnHome: false + } + /* + { + currencyCode: 'NFT_TRON', + currencyName: 'Tron NFT', + currencySymbol: 'NFT', + tokenBlockchain: 'TRON', + tokenBlockchainCode: 'TRX', + currencyType: 'NFT' + } + */ +]; + +const NftsIndexed = {}; +for (const tmp of Nfts) { + NftsIndexed[tmp.tokenBlockchainCode] = tmp; + NftsIndexed[tmp.tokenBlockchain] = tmp; +} + +const getCurrencyCode = (walletCurrency, tokenBlockchainCode) => { + if (!walletCurrency && !tokenBlockchainCode) { + return ''; + } + + if (walletCurrency) { + return walletCurrency; + } + + const tmp = NftsIndexed[tokenBlockchainCode.toUpperCase()]; + if (typeof tmp === 'undefined') { + return tokenBlockchainCode; + } + return tmp.tokenBlockchainCode; +}; + +const getCurrencyTitle = ( + walletCurrency: string, + tokenBlockchainCode: string +) => { + if (!walletCurrency && !tokenBlockchainCode) { + return ''; + } + + if (walletCurrency) { + return walletCurrency; + } + + const tmp = NftsIndexed[tokenBlockchainCode.toUpperCase()]; + if (typeof tmp === 'undefined') { + return tokenBlockchainCode; + } + return tmp.tokenBlockchainShortTitle || tmp.tokenBlockchain; +}; + +export default { + Nfts, + NftsIndexed, + getCurrencyTitle, + getCurrencyCode +}; diff --git a/crypto/common/AirDAOExplorerDict.ts b/crypto/common/AirDAOExplorerDict.ts new file mode 100644 index 000000000..5ca0fe45e --- /dev/null +++ b/crypto/common/AirDAOExplorerDict.ts @@ -0,0 +1,36 @@ +const CurrenciesExplorer = { + BTC: [ + { + currencyCode: 'BTC', + tokenBlockchain: 'BITCOIN', + explorerName: 'Blockchair', + explorerLink: 'https://blockchair.com/bitcoin/address/', + explorerTxLink: 'https://blockchair.com/bitcoin/transaction/' + }, + { + currencyCode: 'BTC', + tokenBlockchain: 'BITCOIN', + explorerName: 'Blockchain.com', + explorerLink: 'https://www.blockchain.com/btc/address/', + explorerTxLink: 'https://www.blockchain.com/btc/tx/' + } + ], + ETH: [ + { + currencyCode: 'ETH', + tokenBlockchain: 'ETHEREUM', + explorerName: 'Blockchair', + explorerLink: 'https://blockchair.com/ethereum/address/', + explorerTxLink: 'https://blockchair.com/ethereum/transaction/' + }, + { + currencyCode: 'ETH', + tokenBlockchain: 'ETHEREUM', + explorerName: 'Etherscan', + explorerLink: 'https://etherscan.io/address/', + explorerTxLink: 'https://etherscan.io/tx/' + } + ] +}; + +export default CurrenciesExplorer; diff --git a/crypto/common/BlocksoftDictNfts.js b/crypto/common/BlocksoftDictNfts.js deleted file mode 100644 index 78d9741f9..000000000 --- a/crypto/common/BlocksoftDictNfts.js +++ /dev/null @@ -1,119 +0,0 @@ -const Nfts = [ - { - currencyCode: 'NFT_ETH', - currencyName: 'NFT', - currencySymbol: 'NFT', - tokenBlockchain: 'ETHEREUM', - tokenBlockchainCode: 'ETH', - currencyType: 'NFT', - apiType: 'OPENSEA', - explorerLink: 'https://explorerLink.com/token/', - showOnHome: true - }, - { - currencyCode: 'NFT_BNB', - currencyName: 'BNB NFT', - currencySymbol: 'NFT', - tokenBlockchain: 'BNB', - tokenBlockchainShortTitle: 'BNB SC', - tokenBlockchainLongTitle: 'BNB Smart Chain', - tokenBlockchainCode: 'BNB_SMART', - currencyType: 'NFT', - explorerLink: 'https://bscscan.com/token/', - showOnHome: false - }, - - { - currencyCode: 'NFT_MATIC', - currencyName: 'Matic NFT', - currencySymbol: 'NFT', - tokenBlockchain: 'MATIC', - tokenBlockchainCode: 'MATIC', - currencyType: 'NFT', - explorerLink: 'https://polygonscan.com/token/', - showOnHome: false - }, - { - currencyCode: 'NFT_ONE', - currencyName: 'Harmony NFT', - currencySymbol: 'NFT', - tokenBlockchain: 'ONE', - tokenBlockchainCode: 'ONE', - currencyType: 'NFT', - explorerLink: 'https://davinci.gallery/view/', - showOnHome: false - }, - { - currencyCode: 'NFT_ROPSTEN', - currencyName: 'Ropsten NFT', - currencySymbol: 'NFT', - tokenBlockchain: 'ROPSTEN', - tokenBlockchainCode: 'ETH_ROPSTEN', - currencyType: 'NFT', - explorerLink: 'https://ropsten.explorerLink.io/token/', - showOnHome: false - }, - { - currencyCode: 'NFT_RINKEBY', - currencyName: 'Rinkeby NFT', - currencySymbol: 'NFT', - tokenBlockchain: 'RINKEBY', - tokenBlockchainCode: 'ETH_RINKEBY', - currencyType: 'NFT', - apiType: 'OPENSEA', - explorerLink: 'https://rinkeby.explorerLink.io/token/', - showOnHome: false - } - /* - { - currencyCode: 'NFT_TRON', - currencyName: 'Tron NFT', - currencySymbol: 'NFT', - tokenBlockchain: 'TRON', - tokenBlockchainCode: 'TRX', - currencyType: 'NFT' - } - */ -] - -const NftsIndexed = {} -for (const tmp of Nfts) { - NftsIndexed[tmp.tokenBlockchainCode] = tmp - NftsIndexed[tmp.tokenBlockchain] = tmp -} - -const getCurrencyCode = (walletCurrency, tokenBlockchainCode) => { - if (!walletCurrency && !tokenBlockchainCode) { - return '' - } - - if (walletCurrency) { - return walletCurrency - } - - const tmp = NftsIndexed[tokenBlockchainCode.toUpperCase()] - if (typeof tmp === 'undefined') { - return tokenBlockchainCode - } - return tmp.tokenBlockchainCode -} - -const getCurrencyTitle = (walletCurrency, tokenBlockchainCode) => { - if (!walletCurrency && !tokenBlockchainCode) { - return '' - } - - if (walletCurrency) { - return walletCurrency - } - - const tmp = NftsIndexed[tokenBlockchainCode.toUpperCase()] - if (typeof tmp === 'undefined') { - return tokenBlockchainCode - } - return tmp.tokenBlockchainShortTitle || tmp.tokenBlockchain -} - -export default { - Nfts, NftsIndexed, getCurrencyTitle, getCurrencyCode -} diff --git a/crypto/common/BlocksoftExplorerDict.js b/crypto/common/BlocksoftExplorerDict.js deleted file mode 100644 index 2cd7b301d..000000000 --- a/crypto/common/BlocksoftExplorerDict.js +++ /dev/null @@ -1,39 +0,0 @@ -const CurrenciesExplorer = { - BTC: - [ - { - currencyCode: 'BTC', - tokenBlockchain: 'BITCOIN', - explorerName: 'Blockchair', - explorerLink: 'https://blockchair.com/bitcoin/address/', - explorerTxLink: 'https://blockchair.com/bitcoin/transaction/' - }, - { - currencyCode: 'BTC', - tokenBlockchain: 'BITCOIN', - explorerName: 'Blockchain.com', - explorerLink: 'https://www.blockchain.com/btc/address/', - explorerTxLink: 'https://www.blockchain.com/btc/tx/' - } - ], - ETH: - [ - { - currencyCode: 'ETH', - tokenBlockchain: 'ETHEREUM', - explorerName: 'Blockchair', - explorerLink: 'https://blockchair.com/ethereum/address/', - explorerTxLink: 'https://blockchair.com/ethereum/transaction/' - }, - { - currencyCode: 'ETH', - tokenBlockchain: 'ETHEREUM', - explorerName: 'Etherscan', - explorerLink: 'https://etherscan.io/address/', - explorerTxLink: 'https://etherscan.io/tx/' - } - ] -} - -export default CurrenciesExplorer - \ No newline at end of file From f5875ea9bd4e51c0759f8b43ac7c672bfb46a75f Mon Sep 17 00:00:00 2001 From: JavidHaji-zada Date: Fri, 4 Aug 2023 00:27:27 +0400 Subject: [PATCH 032/509] converted Blocksoft axios --- .../BlocksoftKeys/BlocksoftKeysScam.js | 86 ++++++------ .../bch/providers/BchSendProvider.ts | 4 +- .../bch/providers/BchUnspentsProvider.ts | 4 +- crypto/blockchains/bnb/BnbScannerProcessor.ts | 6 +- .../blockchains/bnb/basic/BnbNetworkPrices.js | 4 +- .../bnb/basic/BnbTxSendProvider.ts | 4 +- .../bnb_smart/basic/BnbSmartNetworkPrices.js | 4 +- crypto/blockchains/bsv/BsvScannerProcessor.js | 14 +- .../bsv/providers/BsvSendProvider.ts | 4 +- .../bsv/providers/BsvUnspentsProvider.ts | 4 +- crypto/blockchains/btc/BtcScannerProcessor.ts | 10 +- .../blockchains/btc/basic/BtcNetworkPrices.ts | 6 +- .../btc_test/BtcTestScannerProcessor.js | 6 +- .../btc_test/providers/BtcTestSendProvider.ts | 4 +- .../providers/BtcTestUnspentsProvider.ts | 4 +- .../blockchains/doge/DogeScannerProcessor.ts | 6 +- .../doge/providers/DogeSendProvider.ts | 4 +- .../doge/providers/DogeUnspentsProvider.ts | 8 +- crypto/blockchains/eth/EthScannerProcessor.ts | 14 +- .../blockchains/eth/EthTransferProcessor.ts | 4 +- crypto/blockchains/eth/apis/EthNftMatic.ts | 4 +- crypto/blockchains/eth/apis/EthNftOpensea.ts | 4 +- .../blockchains/eth/basic/EthNetworkPrices.ts | 6 +- .../eth/basic/EthTxSendProvider.ts | 12 +- crypto/blockchains/eth/stores/EthRawDS.ts | 10 +- crypto/blockchains/fio/FioUtils.ts | 4 +- .../metis/MetisTransferProcessor.ts | 4 +- crypto/blockchains/one/OneScannerProcessor.ts | 6 +- .../one/OneScannerProcessorErc20.ts | 4 +- crypto/blockchains/sol/SolScannerProcessor.ts | 8 +- .../blockchains/sol/SolScannerProcessorSpl.ts | 4 +- crypto/blockchains/sol/SolTokenProcessor.ts | 6 +- .../blockchains/sol/SolTransferProcessor.ts | 4 +- crypto/blockchains/sol/ext/SolStakeUtils.ts | 12 +- crypto/blockchains/sol/ext/SolUtils.ts | 12 +- crypto/blockchains/trx/TrxScannerProcessor.ts | 8 +- crypto/blockchains/trx/TrxTokenProcessor.ts | 6 +- .../blockchains/trx/TrxTransferProcessor.ts | 16 +-- .../trx/basic/TrxNodeInfoProvider.ts | 7 +- .../trx/basic/TrxTransactionsProvider.ts | 4 +- .../trx/basic/TrxTransactionsTrc20Provider.ts | 4 +- .../trx/basic/TrxTrongridProvider.ts | 6 +- .../trx/basic/TrxTronscanProvider.ts | 4 +- crypto/blockchains/trx/ext/TronStakeUtils.ts | 4 +- .../trx/providers/TrxSendProvider.ts | 4 +- .../blockchains/usdt/UsdtScannerProcessor.ts | 6 +- crypto/blockchains/vet/VetScannerProcessor.ts | 10 +- .../blockchains/vet/VetTransferProcessor.ts | 4 +- .../waves/WavesScannerProcessor.ts | 4 +- .../waves/WavesScannerProcessorErc20.ts | 4 +- .../providers/WavesTransactionsProvider.ts | 4 +- crypto/blockchains/xlm/XlmScannerProcessor.ts | 8 +- crypto/blockchains/xmr/XmrAddressProcessor.js | 4 +- crypto/blockchains/xmr/XmrAddressProcessor.ts | 4 +- crypto/blockchains/xmr/XmrScannerProcessor.js | 8 +- crypto/blockchains/xmr/XmrScannerProcessor.ts | 8 +- .../xmr/providers/XmrSendProvider.ts | 19 ++- .../xmr/providers/XmrUnspentsProvider.js | 9 +- .../xmr/providers/XmrUnspentsProvider.ts | 9 +- .../xrp/basic/XrpDataRippleProvider.js | 8 +- .../xrp/basic/XrpDataRippleProvider.ts | 8 +- .../xrp/basic/XrpDataScanProvider.js | 8 +- .../xrp/basic/XrpDataScanProvider.ts | 8 +- crypto/blockchains/xvg/XvgScannerProcessor.js | 10 +- .../xvg/providers/XvgSendProvider.ts | 4 +- .../xvg/providers/XvgUnspentsProvider.ts | 4 +- .../{BlocksoftAxios.js => AirDAOAxios.ts} | 130 ++++++++---------- crypto/common/AirDAOExternalSettings.ts | 4 +- package.json | 7 +- yarn.lock | 7 + 70 files changed, 325 insertions(+), 337 deletions(-) rename crypto/common/{BlocksoftAxios.js => AirDAOAxios.ts} (85%) diff --git a/crypto/actions/BlocksoftKeys/BlocksoftKeysScam.js b/crypto/actions/BlocksoftKeys/BlocksoftKeysScam.js index 4adf26ca9..29ea3ab5a 100644 --- a/crypto/actions/BlocksoftKeys/BlocksoftKeysScam.js +++ b/crypto/actions/BlocksoftKeys/BlocksoftKeysScam.js @@ -2,57 +2,57 @@ * @author Ksu * @version 0.5 */ -import BlocksoftAxios from '@crypto/common/BlocksoftAxios' +import AirDAOAxios from '@crypto/common/AirDAOAxios'; -const scamSeeds = require('@crypto/common/ext/scam-seeds') +const scamSeeds = require('@crypto/common/ext/scam-seeds'); -let CACHE_SEEDS = scamSeeds -let CACHE_CASHBACKS = {} -let CACHE_SEEDS_TIME = 0 -const TIMEOUT_SEEDS = 60000 -const PROXY_SEEDS = 'https://proxy.trustee.deals/seeds/getScam' +let CACHE_SEEDS = scamSeeds; +let CACHE_CASHBACKS = {}; +let CACHE_SEEDS_TIME = 0; +const TIMEOUT_SEEDS = 60000; +const PROXY_SEEDS = 'https://proxy.trustee.deals/seeds/getScam'; const _get = async () => { - const now = new Date().getTime() - if ((now - CACHE_SEEDS_TIME) > TIMEOUT_SEEDS) { - const tmp = await BlocksoftAxios.get(PROXY_SEEDS) - CACHE_SEEDS_TIME = now - if (tmp.data?.data?.seeds) { - for (const seed of tmp.data.data.seeds) { - CACHE_SEEDS[seed] = 1 - } - } - if (tmp.data?.data?.cashbacks) { - for (const cb of tmp.data.data.cashbacks) { - CACHE_CASHBACKS[cb] = 1 - } - } + const now = new Date().getTime(); + if (now - CACHE_SEEDS_TIME > TIMEOUT_SEEDS) { + const tmp = await AirDAOAxios.get(PROXY_SEEDS); + CACHE_SEEDS_TIME = now; + if (tmp.data?.data?.seeds) { + for (const seed of tmp.data.data.seeds) { + CACHE_SEEDS[seed] = 1; + } } -} -const isScamMnemonic = async (mnemonic) => { - await _get() - if (typeof CACHE_SEEDS[mnemonic] !== 'undefined') { - return true + if (tmp.data?.data?.cashbacks) { + for (const cb of tmp.data.data.cashbacks) { + CACHE_CASHBACKS[cb] = 1; + } } - return false -} + } +}; +const isScamMnemonic = async (mnemonic) => { + await _get(); + if (typeof CACHE_SEEDS[mnemonic] !== 'undefined') { + return true; + } + return false; +}; const isScamCashback = async (cb) => { - await _get() - if (typeof CACHE_CASHBACKS[cb] !== 'undefined') { - return true - } - return false -} + await _get(); + if (typeof CACHE_CASHBACKS[cb] !== 'undefined') { + return true; + } + return false; +}; const isScamCashbackStatic = (cb) => { - if (typeof CACHE_CASHBACKS[cb] !== 'undefined') { - return true - } - return false -} + if (typeof CACHE_CASHBACKS[cb] !== 'undefined') { + return true; + } + return false; +}; export default { - isScamMnemonic, - isScamCashback, - isScamCashbackStatic -} + isScamMnemonic, + isScamCashback, + isScamCashbackStatic +}; diff --git a/crypto/blockchains/bch/providers/BchSendProvider.ts b/crypto/blockchains/bch/providers/BchSendProvider.ts index 4bd92dfaa..188825655 100644 --- a/crypto/blockchains/bch/providers/BchSendProvider.ts +++ b/crypto/blockchains/bch/providers/BchSendProvider.ts @@ -3,7 +3,7 @@ */ import { AirDAOBlockchainTypes } from '../../AirDAOBlockchainTypes'; import AirDAOCryptoLog from '../../../common/AirDAOCryptoLog'; -import BlocksoftAxios from '../../../common/BlocksoftAxios'; +import AirDAOAxios from '../../../common/AirDAOAxios'; import DogeSendProvider from '../../doge/providers/DogeSendProvider'; export default class BchSendProvider @@ -42,7 +42,7 @@ export default class BchSendProvider let res; try { - res = (await BlocksoftAxios.get(this._apiPath + hex)) as any; + res = (await AirDAOAxios.get(this._apiPath + hex)) as any; } catch (error) { const e = error as unknown as any; if (e.message.indexOf('dust') !== -1) { diff --git a/crypto/blockchains/bch/providers/BchUnspentsProvider.ts b/crypto/blockchains/bch/providers/BchUnspentsProvider.ts index d9c9b20fc..9981409e6 100644 --- a/crypto/blockchains/bch/providers/BchUnspentsProvider.ts +++ b/crypto/blockchains/bch/providers/BchUnspentsProvider.ts @@ -6,7 +6,7 @@ */ import { AirDAOBlockchainTypes } from '../../AirDAOBlockchainTypes'; import AirDAOCryptoLog from '../../../common/AirDAOCryptoLog'; -import BlocksoftAxios from '../../../common/BlocksoftAxios'; +import AirDAOAxios from '../../../common/AirDAOAxios'; import BtcCashUtils from '../ext/BtcCashUtils'; import DogeUnspentsProvider from '../../doge/providers/DogeUnspentsProvider'; @@ -34,7 +34,7 @@ export default class BchUnspentsProvider // ); address = BtcCashUtils.fromLegacyAddress(address); const link = this._apiPath + address; - const res = (await BlocksoftAxios.getWithoutBraking(link)) as any; + const res = (await AirDAOAxios.getWithoutBraking(link)) as any; if (!res || !res.data || typeof res.data === 'undefined') { AirDAOCryptoLog.log( this._settings.currencyCode + diff --git a/crypto/blockchains/bnb/BnbScannerProcessor.ts b/crypto/blockchains/bnb/BnbScannerProcessor.ts index 1a31e9867..a5d57853c 100644 --- a/crypto/blockchains/bnb/BnbScannerProcessor.ts +++ b/crypto/blockchains/bnb/BnbScannerProcessor.ts @@ -2,7 +2,7 @@ * @version 0.20 * https://docs.binance.org/api-reference/dex-api/paths.html#apiv1transactions */ -import BlocksoftAxios from '../../common/BlocksoftAxios'; +import AirDAOAxios from '../../common/AirDAOAxios'; import AirDAOCryptoLog from '../../common/AirDAOCryptoLog'; import BlocksoftUtils from '../../common/AirDAOUtils'; import BlocksoftExternalSettings from '../../common/AirDAOExternalSettings'; @@ -20,7 +20,7 @@ export default class BnbScannerProcessor { const link = `${apiServer}/api/v1/account/${address}`; let balance = 0; let frozen = 0; - const res = await BlocksoftAxios.getWithoutBraking(link); + const res = await AirDAOAxios.getWithoutBraking(link); // "balances":[{"free":"0.00100000","frozen":"0.00000000","locked":"0.00000000","symbol":"BNB"}] if ( res && @@ -73,7 +73,7 @@ export default class BnbScannerProcessor { '&startTime=' + (scanData.account.balanceScanTime - 86400) * 1000; // 1 day } - const res = await BlocksoftAxios.getWithoutBraking(linkTxs); + const res = await AirDAOAxios.getWithoutBraking(linkTxs); if (!res || typeof res.data === 'undefined' || !res.data) { return false; } diff --git a/crypto/blockchains/bnb/basic/BnbNetworkPrices.js b/crypto/blockchains/bnb/basic/BnbNetworkPrices.js index f5aec05b4..f9b318df3 100644 --- a/crypto/blockchains/bnb/basic/BnbNetworkPrices.js +++ b/crypto/blockchains/bnb/basic/BnbNetworkPrices.js @@ -6,7 +6,7 @@ * https://docs.binance.org/api-reference/dex-api/paths.html#apiv1fees */ import AirDAOCryptoLog from '../../../common/AirDAOCryptoLog'; -import BlocksoftAxios from '../../../common/BlocksoftAxios'; +import AirDAOAxios from '../../../common/AirDAOAxios'; import BlocksoftExternalSettings from '../../../common/AirDAOExternalSettings'; const CACHE_VALID_TIME = 60000; // 1 minute @@ -30,7 +30,7 @@ class BnbNetworkPrices { const link = `${apiServer}/api/v1/fees`; try { - const tmp = await BlocksoftAxios.getWithoutBraking(link, 2); + const tmp = await AirDAOAxios.getWithoutBraking(link, 2); if (tmp.data) { // {"fee": 1000000, "fee_for": 1, "msg_type": "transferOwnership"}] const result = {}; diff --git a/crypto/blockchains/bnb/basic/BnbTxSendProvider.ts b/crypto/blockchains/bnb/basic/BnbTxSendProvider.ts index 6fe695ca4..c35322cc8 100644 --- a/crypto/blockchains/bnb/basic/BnbTxSendProvider.ts +++ b/crypto/blockchains/bnb/basic/BnbTxSendProvider.ts @@ -1,6 +1,6 @@ /* eslint-disable camelcase */ /* eslint-disable @typescript-eslint/no-var-requires */ -import BlocksoftAxios from '../../../common/BlocksoftAxios'; +import AirDAOAxios from '../../../common/AirDAOAxios'; import { AirDAOBlockchainTypes } from '../../AirDAOBlockchainTypes'; import AirDAOCryptoLog from '../../../common/AirDAOCryptoLog'; import BlocksoftUtils from '../../../common/AirDAOUtils'; @@ -81,7 +81,7 @@ export class BnbTxSendProvider { type = 'usual' ) { const apiServer = await BlocksoftExternalSettings.getStatic('BNB_SERVER'); - const res = await BlocksoftAxios.getWithoutBraking( + const res = await AirDAOAxios.getWithoutBraking( apiServer + '/api/v1/account/' + data.addressFrom ); if (!res.data) { diff --git a/crypto/blockchains/bnb_smart/basic/BnbSmartNetworkPrices.js b/crypto/blockchains/bnb_smart/basic/BnbSmartNetworkPrices.js index d9318ef4f..8e7647be3 100644 --- a/crypto/blockchains/bnb_smart/basic/BnbSmartNetworkPrices.js +++ b/crypto/blockchains/bnb_smart/basic/BnbSmartNetworkPrices.js @@ -2,7 +2,7 @@ * @version 0.20 */ import AirDAOCryptoLog from '../../../common/AirDAOCryptoLog'; -import BlocksoftAxios from '../../../common/BlocksoftAxios'; +import AirDAOAxios from '../../../common/AirDAOAxios'; import BlocksoftUtils from '../../../common/AirDAOUtils'; import BlocksoftExternalSettings from '../../../common/AirDAOExternalSettings'; @@ -34,7 +34,7 @@ class BnbSmartNetworkPrices { mainCurrencyCode + ' BnbSmartNetworkPricesProvider.getFees no cache load' ); try { - const res = await BlocksoftAxios.getWithoutBraking(feesApiPath); + const res = await AirDAOAxios.getWithoutBraking(feesApiPath); if ( res && typeof res.data !== 'undefined' && diff --git a/crypto/blockchains/bsv/BsvScannerProcessor.js b/crypto/blockchains/bsv/BsvScannerProcessor.js index ee4bd80b4..3f3541dba 100644 --- a/crypto/blockchains/bsv/BsvScannerProcessor.js +++ b/crypto/blockchains/bsv/BsvScannerProcessor.js @@ -2,7 +2,7 @@ * @version 0.5 */ -import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; +import AirDAOAxios from '@crypto/common/AirDAOAxios'; import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; import BlocksoftUtils from '@crypto/common/AirDAOUtils'; import BlocksoftPrettyNumbers from '@crypto/common/AirDAOPrettyNumbers'; @@ -29,7 +29,7 @@ export default class BsvScannerProcessor { let res = false; let balance = 0; try { - res = await BlocksoftAxios.getWithoutBraking(link); + res = await AirDAOAxios.getWithoutBraking(link); if ( res && typeof res.data !== 'undefined' && @@ -86,7 +86,7 @@ export default class BsvScannerProcessor { const linkTxs = `${API_PATH}/address/${address}/history`; let res = false; try { - res = await BlocksoftAxios.getWithoutBraking(linkTxs); + res = await AirDAOAxios.getWithoutBraking(linkTxs); } catch (e) { throw e; } @@ -130,7 +130,7 @@ export default class BsvScannerProcessor { ); res = false; try { - res = await BlocksoftAxios.post(API_PATH + '/txs', { txids: bulkTxs }); + res = await AirDAOAxios.post(API_PATH + '/txs', { txids: bulkTxs }); } catch (e) { throw e; } @@ -169,7 +169,7 @@ export default class BsvScannerProcessor { 'BsvScannerProcessor.getTransactions will ask vins ' + JSON.stringify(vins) ); - const res = await BlocksoftAxios.post(API_PATH + '/txs', { + const res = await AirDAOAxios.post(API_PATH + '/txs', { txids: vins }); if (res && typeof res.data !== 'undefined' && res.data) { @@ -187,7 +187,7 @@ export default class BsvScannerProcessor { 'BsvScannerProcessor.getTransactions will ask vins1 ' + JSON.stringify(vins) ); - const res = await BlocksoftAxios.post(API_PATH + '/txs', { txids: vins }); + const res = await AirDAOAxios.post(API_PATH + '/txs', { txids: vins }); if (res && typeof res.data !== 'undefined' && res.data) { for (const tx of res.data) { await this._saveTxToCache(tx); @@ -276,7 +276,7 @@ export default class BsvScannerProcessor { 'BsvScannerProcessor.getTransactions will ask vins2 ' + JSON.stringify(vins) ); - const res = await BlocksoftAxios.post(API_PATH + '/txs', { + const res = await AirDAOAxios.post(API_PATH + '/txs', { txids: vins }); if (res && typeof res.data !== 'undefined' && res.data) { diff --git a/crypto/blockchains/bsv/providers/BsvSendProvider.ts b/crypto/blockchains/bsv/providers/BsvSendProvider.ts index e951e2c23..feefc1dba 100644 --- a/crypto/blockchains/bsv/providers/BsvSendProvider.ts +++ b/crypto/blockchains/bsv/providers/BsvSendProvider.ts @@ -4,7 +4,7 @@ import { AirDAOBlockchainTypes } from '@crypto/blockchains/AirDAOBlockchainTypes'; import DogeSendProvider from '@crypto/blockchains/doge/providers/DogeSendProvider'; import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; -import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; +import AirDAOAxios from '@crypto/common/AirDAOAxios'; export default class BsvSendProvider extends DogeSendProvider @@ -30,7 +30,7 @@ export default class BsvSendProvider let res; try { - res = await BlocksoftAxios.post(link, { txhex: hex }); + res = await AirDAOAxios.post(link, { txhex: hex }); } catch (e) { if (subtitle.indexOf('rawSend') !== -1) { throw e; diff --git a/crypto/blockchains/bsv/providers/BsvUnspentsProvider.ts b/crypto/blockchains/bsv/providers/BsvUnspentsProvider.ts index c10e2c0fb..092852b68 100644 --- a/crypto/blockchains/bsv/providers/BsvUnspentsProvider.ts +++ b/crypto/blockchains/bsv/providers/BsvUnspentsProvider.ts @@ -6,7 +6,7 @@ import DogeUnspentsProvider from '@crypto/blockchains/doge/providers/DogeUnspent import BtcCashUtils from '@crypto/blockchains/bch/ext/BtcCashUtils'; import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; import BlocksoftExternalSettings from '@crypto/common/AirDAOExternalSettings'; -import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; +import AirDAOAxios from '@crypto/common/AirDAOAxios'; export default class BsvUnspentsProvider extends DogeUnspentsProvider implements BlocksoftBlockchainTypes.UnspentsProvider @@ -39,7 +39,7 @@ export default class BsvUnspentsProvider address + '/unspent'; - const res = await BlocksoftAxios.getWithoutBraking(link); + const res = await AirDAOAxios.getWithoutBraking(link); if (!res.data || typeof res.data[0] === 'undefined') { return []; } diff --git a/crypto/blockchains/btc/BtcScannerProcessor.ts b/crypto/blockchains/btc/BtcScannerProcessor.ts index 3c6cab175..3cbf96f50 100644 --- a/crypto/blockchains/btc/BtcScannerProcessor.ts +++ b/crypto/blockchains/btc/BtcScannerProcessor.ts @@ -3,7 +3,7 @@ */ import BlocksoftUtils from '@crypto/common/AirDAOUtils'; -import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; +import AirDAOAxios from '@crypto/common/AirDAOAxios'; import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; import BlocksoftExternalSettings from '@crypto/common/AirDAOExternalSettings'; @@ -83,7 +83,7 @@ export default class BtcScannerProcessor { address + '&type=xpub¤cyCode=' + this._settings['currencyCode']; - res = await BlocksoftAxios.getWithoutBraking(link, 5, TIMEOUT_BTC); + res = await AirDAOAxios.getWithoutBraking(link, 5, TIMEOUT_BTC); if ( res && typeof res.data !== 'undefined' && @@ -98,7 +98,7 @@ export default class BtcScannerProcessor { address + '?details=txs&gap=9999&tokens=used&pageSize=40'; try { - res = await BlocksoftAxios._request( + res = await AirDAOAxios._request( link, 'get', false, @@ -129,7 +129,7 @@ export default class BtcScannerProcessor { address + '¤cyCode=' + this._settings['currencyCode']; - res = await BlocksoftAxios.getWithoutBraking(link, 5, TIMEOUT_BTC); + res = await AirDAOAxios.getWithoutBraking(link, 5, TIMEOUT_BTC); if ( res && typeof res.data !== 'undefined' && @@ -143,7 +143,7 @@ export default class BtcScannerProcessor { '/api/v2/address/' + address + '?details=txs&gap=9999&pageSize=80'; - res = await BlocksoftAxios.getWithoutBraking(link, 5, TIMEOUT_BTC); + res = await AirDAOAxios.getWithoutBraking(link, 5, TIMEOUT_BTC); } } diff --git a/crypto/blockchains/btc/basic/BtcNetworkPrices.ts b/crypto/blockchains/btc/basic/BtcNetworkPrices.ts index 44506c738..f89de5c32 100644 --- a/crypto/blockchains/btc/basic/BtcNetworkPrices.ts +++ b/crypto/blockchains/btc/basic/BtcNetworkPrices.ts @@ -4,7 +4,7 @@ **/ import { AirDAOBlockchainTypes } from '../../AirDAOBlockchainTypes'; import AirDAOCryptoLog from '../../../common/AirDAOCryptoLog'; -import BlocksoftAxios from '../../../common/BlocksoftAxios'; +import AirDAOAxios from '../../../common/AirDAOAxios'; import BlocksoftExternalSettings from '../../../common/AirDAOExternalSettings'; import BlocksoftUtils from '../../../common/AirDAOUtils'; @@ -88,7 +88,7 @@ export default class BtcNetworkPrices let tmp = false; try { - tmp = await BlocksoftAxios.getWithoutBraking(linkTrezor); + tmp = await AirDAOAxios.getWithoutBraking(linkTrezor); if (tmp && tmp.data) { lastBlock = tmp.data.blockbook.bestHeight; mempoolSize = tmp.data.blockbook.mempoolSize; @@ -110,7 +110,7 @@ export default class BtcNetworkPrices tmp = false; try { - tmp = await BlocksoftAxios.getWithoutBraking(link); + tmp = await AirDAOAxios.getWithoutBraking(link); if (tmp && tmp.data) { logData.source = 'reloaded'; if (lastBlock > CACHE_PREV_DATA.lastBlock) { diff --git a/crypto/blockchains/btc_test/BtcTestScannerProcessor.js b/crypto/blockchains/btc_test/BtcTestScannerProcessor.js index 2de3a9855..880abd0af 100644 --- a/crypto/blockchains/btc_test/BtcTestScannerProcessor.js +++ b/crypto/blockchains/btc_test/BtcTestScannerProcessor.js @@ -3,7 +3,7 @@ * https://github.com/Blockstream/esplora/blob/master/API.md */ import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; -import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; +import AirDAOAxios from '@crypto/common/AirDAOAxios'; import BlocksoftUtils from '@crypto/common/AirDAOUtils'; import DogeFindAddressFunction from '@crypto/blockchains/doge/basic/DogeFindAddressFunction'; @@ -19,7 +19,7 @@ export default class BtcTestScannerProcessor { AirDAOCryptoLog.log( 'BtcTestScannerProcessor.getBalance started ' + address ); - const res = await BlocksoftAxios.getWithoutBraking( + const res = await AirDAOAxios.getWithoutBraking( API_PATH + 'address/' + address ); // console.log('res', res.data.chain_stats.funded_txo_sum) // spent_txo_sum @@ -48,7 +48,7 @@ export default class BtcTestScannerProcessor { AirDAOCryptoLog.log( 'BtcTestScannerProcessor.getTransactions started ' + address ); - const res = await BlocksoftAxios.getWithoutBraking( + const res = await AirDAOAxios.getWithoutBraking( API_PATH + 'address/' + address + '/txs' ); if (!res || typeof res.data === 'undefined' || !res.data) { diff --git a/crypto/blockchains/btc_test/providers/BtcTestSendProvider.ts b/crypto/blockchains/btc_test/providers/BtcTestSendProvider.ts index 8e10b6c9c..28a497cd9 100644 --- a/crypto/blockchains/btc_test/providers/BtcTestSendProvider.ts +++ b/crypto/blockchains/btc_test/providers/BtcTestSendProvider.ts @@ -4,7 +4,7 @@ import { AirDAOBlockchainTypes } from '@crypto/blockchains/AirDAOBlockchainTypes'; import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; -import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; +import AirDAOAxios from '@crypto/common/AirDAOAxios'; import DogeSendProvider from '@crypto/blockchains/doge/providers/DogeSendProvider'; const API_URL = 'https://api.blockchair.com/bitcoin/testnet/push/transaction'; @@ -31,7 +31,7 @@ export default class BtcTestSendProvider let res; try { - res = await BlocksoftAxios.post(API_URL, { data: hex }); + res = await AirDAOAxios.post(API_URL, { data: hex }); } catch (e) { try { logData.error = e.message; diff --git a/crypto/blockchains/btc_test/providers/BtcTestUnspentsProvider.ts b/crypto/blockchains/btc_test/providers/BtcTestUnspentsProvider.ts index 08e6eaa73..7af76c063 100644 --- a/crypto/blockchains/btc_test/providers/BtcTestUnspentsProvider.ts +++ b/crypto/blockchains/btc_test/providers/BtcTestUnspentsProvider.ts @@ -4,7 +4,7 @@ import { AirDAOBlockchainTypes } from '@crypto/blockchains/AirDAOBlockchainTypes'; import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; -import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; +import AirDAOAxios from '@crypto/common/AirDAOAxios'; const API_PATH = 'https://blockstream.info/testnet/api/'; @@ -35,7 +35,7 @@ export default class BtcTestUnspentsProvider ); const link = API_PATH + `address/${address}/utxo`; - const res = await BlocksoftAxios.getWithoutBraking(link); + const res = await AirDAOAxios.getWithoutBraking(link); if (!res || typeof res.data === 'undefined') { AirDAOCryptoLog.log( diff --git a/crypto/blockchains/doge/DogeScannerProcessor.ts b/crypto/blockchains/doge/DogeScannerProcessor.ts index b3176a154..cf8820904 100644 --- a/crypto/blockchains/doge/DogeScannerProcessor.ts +++ b/crypto/blockchains/doge/DogeScannerProcessor.ts @@ -21,7 +21,7 @@ * @property {*} transactionJson */ import BlocksoftUtils from '../../common/AirDAOUtils'; -import BlocksoftAxios from '../../common/BlocksoftAxios'; +import AirDAOAxios from '../../common/AirDAOAxios'; import AirDAOCryptoLog from '../../common/AirDAOCryptoLog'; import DogeFindAddressFunction from './basic/DogeFindAddressFunction'; @@ -88,7 +88,7 @@ export default class DogeScannerProcessor { address + '¤cyCode=' + this._settings['currencyCode']; - res = await BlocksoftAxios.getWithoutBraking(link, 5, TIMEOUT_DOGE); + res = await AirDAOAxios.getWithoutBraking(link, 5, TIMEOUT_DOGE); } if ( res && @@ -107,7 +107,7 @@ export default class DogeScannerProcessor { '/api/v2/address/' + address + '?details=txs&pageSize=40'; - res = await BlocksoftAxios.getWithoutBraking(link, 5, TIMEOUT_DOGE); + res = await AirDAOAxios.getWithoutBraking(link, 5, TIMEOUT_DOGE); } if (!res || !res.data) { diff --git a/crypto/blockchains/doge/providers/DogeSendProvider.ts b/crypto/blockchains/doge/providers/DogeSendProvider.ts index 109d557b7..5e6c0bee3 100644 --- a/crypto/blockchains/doge/providers/DogeSendProvider.ts +++ b/crypto/blockchains/doge/providers/DogeSendProvider.ts @@ -4,7 +4,7 @@ */ import { AirDAOBlockchainTypes } from '../../AirDAOBlockchainTypes'; import AirDAOCryptoLog from '../../../common/AirDAOCryptoLog'; -import BlocksoftAxios from '../../../common/BlocksoftAxios'; +import AirDAOAxios from '../../../common/AirDAOAxios'; import BlocksoftExternalSettings from '../../../common/AirDAOExternalSettings'; export default class DogeSendProvider @@ -71,7 +71,7 @@ export default class DogeSendProvider let res; try { - res = await BlocksoftAxios.post(link, hex); + res = await AirDAOAxios.post(link, hex); } catch (e) { if (subtitle.indexOf('rawSend') !== -1) { throw e; diff --git a/crypto/blockchains/doge/providers/DogeUnspentsProvider.ts b/crypto/blockchains/doge/providers/DogeUnspentsProvider.ts index 07095febb..07f06cfd9 100644 --- a/crypto/blockchains/doge/providers/DogeUnspentsProvider.ts +++ b/crypto/blockchains/doge/providers/DogeUnspentsProvider.ts @@ -5,7 +5,7 @@ */ import { AirDAOBlockchainTypes } from '../../AirDAOBlockchainTypes'; import AirDAOCryptoLog from '../../../common/AirDAOCryptoLog'; -import BlocksoftAxios from '../../../common/BlocksoftAxios'; +import AirDAOAxios from '../../../common/AirDAOAxios'; import BlocksoftExternalSettings from '../../../common/AirDAOExternalSettings'; import DogeRawDS from '../stores/DogeRawDS'; @@ -46,7 +46,7 @@ export default class DogeUnspentsProvider link = this._trezorServer + '/api/v2/utxo/' + address + '?gap=9999'; } - const res = await BlocksoftAxios.getWithoutBraking(link); + const res = await AirDAOAxios.getWithoutBraking(link); // @ts-ignore if (!res || typeof res.data === 'undefined') { await BlocksoftExternalSettings.setTrezorServerInvalid( @@ -117,7 +117,7 @@ export default class DogeUnspentsProvider } } else { const link = this._trezorServer + '/api/v2/tx/' + tx; - const res = await BlocksoftAxios.getWithoutBraking(link); + const res = await AirDAOAxios.getWithoutBraking(link); // @ts-ignore if (!res || typeof res.data === 'undefined' || !res.data) { AirDAOCryptoLog.log( @@ -155,7 +155,7 @@ export default class DogeUnspentsProvider try { const link2 = this._trezorServer + '/api/v2/tx/' + unspent.txid; - const res2 = await BlocksoftAxios.getWithoutBraking(link2); + const res2 = await AirDAOAxios.getWithoutBraking(link2); // @ts-ignore if (res2 && typeof res2.data !== 'undefined' && res2.data) { // @ts-ignore diff --git a/crypto/blockchains/eth/EthScannerProcessor.ts b/crypto/blockchains/eth/EthScannerProcessor.ts index 6e1b23450..5d600341d 100644 --- a/crypto/blockchains/eth/EthScannerProcessor.ts +++ b/crypto/blockchains/eth/EthScannerProcessor.ts @@ -2,7 +2,7 @@ * @version 0.5 */ import BlocksoftUtils from '../../common/AirDAOUtils'; -import BlocksoftAxios from '../../common/BlocksoftAxios'; +import AirDAOAxios from '../../common/AirDAOAxios'; import AirDAOCryptoLog from '../../common/AirDAOCryptoLog'; import EthBasic from './basic/EthBasic'; import BlocksoftExternalSettings from '../../common/AirDAOExternalSettings'; @@ -112,7 +112,7 @@ export default class EthScannerProcessor extends EthBasic { let link = this._trezorServer + '/api/v2/address/' + address + '?details=txs'; - let res = await BlocksoftAxios.getWithoutBraking(link); + let res = await AirDAOAxios.getWithoutBraking(link); if (!res || !res.data) { BlocksoftExternalSettings.setTrezorServerInvalid( @@ -134,7 +134,7 @@ export default class EthScannerProcessor extends EthBasic { ); } link = this._trezorServer + '/api/v2/address/' + address + '?details=txs'; - res = await BlocksoftAxios.getWithoutBraking(link); + res = await AirDAOAxios.getWithoutBraking(link); if (!res || !res.data) { BlocksoftExternalSettings.setTrezorServerInvalid( this._trezorServerCode, @@ -385,7 +385,7 @@ export default class EthScannerProcessor extends EthBasic { const link = 'https://www.oklink.com/api/v5/explorer/address/transaction-list?chainShortName=ethw&address=' + address; - const tmp = await BlocksoftAxios.getWithHeaders(link, { + const tmp = await AirDAOAxios.getWithHeaders(link, { 'Ok-Access-Key': key }); if ( @@ -418,7 +418,7 @@ export default class EthScannerProcessor extends EthBasic { logTitle + ' started ' + JSON.stringify(isInternal), link ); - const tmp = await BlocksoftAxios.getWithoutBraking(link); + const tmp = await AirDAOAxios.getWithoutBraking(link); if ( !tmp || typeof tmp.data === 'undefined' || @@ -480,7 +480,7 @@ export default class EthScannerProcessor extends EthBasic { } let link = this._trezorServer + '/api/v2/tx-specific/' + txHash; - let res = await BlocksoftAxios.getWithoutBraking(link); + let res = await AirDAOAxios.getWithoutBraking(link); if (!res || !res.data) { BlocksoftExternalSettings.setTrezorServerInvalid( @@ -502,7 +502,7 @@ export default class EthScannerProcessor extends EthBasic { ); } link = this._trezorServer + '/api/v2/tx-specific/' + txHash; - res = await BlocksoftAxios.getWithoutBraking(link); + res = await AirDAOAxios.getWithoutBraking(link); if (!res || !res.data) { BlocksoftExternalSettings.setTrezorServerInvalid( this._trezorServerCode, diff --git a/crypto/blockchains/eth/EthTransferProcessor.ts b/crypto/blockchains/eth/EthTransferProcessor.ts index 826245b17..ac47ddcbc 100644 --- a/crypto/blockchains/eth/EthTransferProcessor.ts +++ b/crypto/blockchains/eth/EthTransferProcessor.ts @@ -18,7 +18,7 @@ import { AirDAOBlockchainTypes } from '../AirDAOBlockchainTypes'; import BlocksoftExternalSettings from '../../common/AirDAOExternalSettings'; import abi721 from './ext/erc721.js'; import abi1155 from './ext/erc1155'; -import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; +import AirDAOAxios from '@crypto/common/AirDAOAxios'; import OneUtils from '@crypto/blockchains/one/ext/OneUtils'; import { Database } from '@database'; @@ -326,7 +326,7 @@ export default class EthTransferProcessor this._settings.currencyCode + ' EthTransferProcessor.getFeeRate estimatedGas for WalletConnect start' ); - const tmp = await BlocksoftAxios.postWithoutBraking( + const tmp = await AirDAOAxios.postWithoutBraking( this._web3.LINK, params ); diff --git a/crypto/blockchains/eth/apis/EthNftMatic.ts b/crypto/blockchains/eth/apis/EthNftMatic.ts index b98fe00d9..384e33424 100644 --- a/crypto/blockchains/eth/apis/EthNftMatic.ts +++ b/crypto/blockchains/eth/apis/EthNftMatic.ts @@ -1,7 +1,7 @@ /** * @version 0.50 */ -import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; +import AirDAOAxios from '@crypto/common/AirDAOAxios'; import BlocksoftUtils from '@crypto/common/AirDAOUtils'; import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; @@ -24,7 +24,7 @@ export default async function (data) { data.tokenBlockchainCode + '&tokens=' + data.customAssets.join(','); - const result = await BlocksoftAxios.get(link); + const result = await AirDAOAxios.get(link); /** * @var tmp.animation_url diff --git a/crypto/blockchains/eth/apis/EthNftOpensea.ts b/crypto/blockchains/eth/apis/EthNftOpensea.ts index aa03e7f81..4f4cd9fe9 100644 --- a/crypto/blockchains/eth/apis/EthNftOpensea.ts +++ b/crypto/blockchains/eth/apis/EthNftOpensea.ts @@ -1,7 +1,7 @@ /** * @version 0.50 */ -import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; +import AirDAOAxios from '@crypto/common/AirDAOAxios'; import BlocksoftUtils from '@crypto/common/AirDAOUtils'; import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; @@ -23,7 +23,7 @@ export default async function (data) { } if (!data.address) return false; link += 'assets?order_direction=desc&owner=' + data.address; - const result = await BlocksoftAxios.getWithoutBraking(link); + const result = await AirDAOAxios.getWithoutBraking(link); /** * @var tmp.id diff --git a/crypto/blockchains/eth/basic/EthNetworkPrices.ts b/crypto/blockchains/eth/basic/EthNetworkPrices.ts index ee22975f2..70966adbd 100644 --- a/crypto/blockchains/eth/basic/EthNetworkPrices.ts +++ b/crypto/blockchains/eth/basic/EthNetworkPrices.ts @@ -2,7 +2,7 @@ * @version 0.5 */ import AirDAOCryptoLog from '../../../common/AirDAOCryptoLog'; -import BlocksoftAxios from '../../../common/BlocksoftAxios'; +import AirDAOAxios from '../../../common/AirDAOAxios'; import BlocksoftUtils from '../../../common/AirDAOUtils'; import BlocksoftExternalSettings from '../../../common/AirDAOExternalSettings'; import EthRawDS from '../stores/EthRawDS'; @@ -55,7 +55,7 @@ class EthNetworkPrices { do { try { // TODO - // checkResult = await BlocksoftAxios.post( + // checkResult = await AirDAOAxios.post( // proxy, // { // address, @@ -199,7 +199,7 @@ class EthNetworkPrices { let link = `${ESTIMATE_PATH}`; let tmp = false; try { - tmp = await BlocksoftAxios.getWithoutBraking(link, ESTIMATE_MAX_TRY); + tmp = await AirDAOAxios.getWithoutBraking(link, ESTIMATE_MAX_TRY); if (tmp.data && tmp.data.fastest) { if (typeof tmp.data.gasPriceRange !== 'undefined') { delete tmp.data.gasPriceRange; diff --git a/crypto/blockchains/eth/basic/EthTxSendProvider.ts b/crypto/blockchains/eth/basic/EthTxSendProvider.ts index 6a5c2fd63..3cc3fe50a 100644 --- a/crypto/blockchains/eth/basic/EthTxSendProvider.ts +++ b/crypto/blockchains/eth/basic/EthTxSendProvider.ts @@ -8,7 +8,7 @@ import BlocksoftUtils from '../../../common/AirDAOUtils'; import EthTmpDS from '../stores/EthTmpDS'; import EthRawDS from '../stores/EthRawDS'; import BlocksoftExternalSettings from '../../../common/AirDAOExternalSettings'; -import BlocksoftAxios from '../../../common/BlocksoftAxios'; +import AirDAOAxios from '../../../common/AirDAOAxios'; export default class EthTxSendProvider { private _web3: any; @@ -130,7 +130,7 @@ export default class EthTxSendProvider { // proxy, // logData // ); - // checkResult = await BlocksoftAxios.post(proxy, { + // checkResult = await AirDAOAxios.post(proxy, { // raw: rawTransaction, // txRBF, // logData, @@ -194,7 +194,7 @@ export default class EthTxSendProvider { rawTransaction ); sendLink = this._web3.LINK; - const tmp = await BlocksoftAxios.postWithoutBraking(sendLink, { + const tmp = await AirDAOAxios.postWithoutBraking(sendLink, { jsonrpc: '2.0', method: 'eth_sendRawTransaction', params: [rawTransaction], @@ -253,7 +253,7 @@ export default class EthTxSendProvider { this._settings.currencyCode + ' EthTxSendProvider.send post ', rawTransaction ); - result = await BlocksoftAxios.post(sendLink, rawTransaction); + result = await AirDAOAxios.post(sendLink, rawTransaction); } // @ts-ignore await AirDAOCryptoLog.log( @@ -269,7 +269,7 @@ export default class EthTxSendProvider { // errorProxy, // logData // ); - // const res2 = await BlocksoftAxios.post(errorProxy, { + // const res2 = await AirDAOAxios.post(errorProxy, { // raw: rawTransaction, // txRBF, // logData, @@ -351,7 +351,7 @@ export default class EthTxSendProvider { // successProxy, // logData // ); - // checkResult = await BlocksoftAxios.post(successProxy, { + // checkResult = await AirDAOAxios.post(successProxy, { // raw: rawTransaction, // txRBF, // logData, diff --git a/crypto/blockchains/eth/stores/EthRawDS.ts b/crypto/blockchains/eth/stores/EthRawDS.ts index 2a79f9c0b..b5f373d1e 100644 --- a/crypto/blockchains/eth/stores/EthRawDS.ts +++ b/crypto/blockchains/eth/stores/EthRawDS.ts @@ -4,7 +4,7 @@ * @version 0.32 */ import BlocksoftExternalSettings from '../../../common/AirDAOExternalSettings'; -import BlocksoftAxios from '../../../common/BlocksoftAxios'; +import AirDAOAxios from '../../../common/AirDAOAxios'; import AirDAOCryptoLog from '../../../common/AirDAOCryptoLog'; import { DatabaseTable } from '@appTypes'; import { Database } from '@database'; @@ -104,7 +104,7 @@ class EthRawDS { // let checkResult = false; // try { // transactionLog.selectedFee.isRebroadcast = true; - // checkResult = await BlocksoftAxios.post(successProxy, { + // checkResult = await AirDAOAxios.post(successProxy, { // raw: row.transactionRaw, // txRBF: // typeof transactionLog.txRBF !== 'undefined' @@ -147,7 +147,7 @@ class EthRawDS { ) { link = this._trezorServer + '/api/v2/sendtx/'; try { - broad = await BlocksoftAxios.post(link, row.transactionRaw); + broad = await AirDAOAxios.post(link, row.transactionRaw); broadcastLog = ' broadcasted ok ' + JSON.stringify(broad.data); updateObj.is_removed = '1'; updateObj.removed_at = now; @@ -172,7 +172,7 @@ class EthRawDS { 'https://api.etherscan.io/api?module=proxy&action=eth_sendRawTransaction&apikey=YourApiKeyToken&hex='; let broadcastLog1 = ''; try { - broad = await BlocksoftAxios.get(link + row.transactionRaw); + broad = await AirDAOAxios.get(link + row.transactionRaw); if (typeof broad.data.error !== 'undefined') { throw new Error(JSON.stringify(broad.data.error)); } @@ -196,7 +196,7 @@ class EthRawDS { link = 'https://mainnet.infura.io/v3/' + this._infuraProjectId; let broadcastLog2 = ''; try { - broad = await BlocksoftAxios.post(link, { + broad = await AirDAOAxios.post(link, { jsonrpc: '2.0', method: 'eth_sendRawTransaction', params: [row.transactionRaw], diff --git a/crypto/blockchains/fio/FioUtils.ts b/crypto/blockchains/fio/FioUtils.ts index a2ec913dd..44a369e63 100644 --- a/crypto/blockchains/fio/FioUtils.ts +++ b/crypto/blockchains/fio/FioUtils.ts @@ -1,6 +1,6 @@ import AirDAOCryptoLog from '../../common/AirDAOCryptoLog'; import { getFioSdk } from './FioSdkWrapper'; -import BlocksoftAxios from '../../common/BlocksoftAxios'; +import AirDAOAxios from '../../common/AirDAOAxios'; import { Fio } from '@fioprotocol/fiojs'; import { FIOSDK } from '@fioprotocol/fiosdk/src/FIOSDK'; import _ from 'lodash'; @@ -322,7 +322,7 @@ export const getTransactions = async (publicKey) => { try { const link = BlocksoftExternalSettings.getStatic('FIO_HISTORY_URL'); const accountHash = Fio.accountHash(publicKey); - const response = await BlocksoftAxios.post(link + 'get_actions', { + const response = await AirDAOAxios.post(link + 'get_actions', { account_name: accountHash, pos: -1 }); diff --git a/crypto/blockchains/metis/MetisTransferProcessor.ts b/crypto/blockchains/metis/MetisTransferProcessor.ts index b066aedfc..f70ffdae0 100644 --- a/crypto/blockchains/metis/MetisTransferProcessor.ts +++ b/crypto/blockchains/metis/MetisTransferProcessor.ts @@ -5,7 +5,7 @@ import { AirDAOBlockchainTypes } from '@crypto/blockchains/AirDAOBlockchainTypes'; import EthTransferProcessor from '@crypto/blockchains/eth/EthTransferProcessor'; import BlocksoftExternalSettings from '@crypto/common/AirDAOExternalSettings'; -import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; +import AirDAOAxios from '@crypto/common/AirDAOAxios'; import BlocksoftUtils from '@crypto/common/AirDAOUtils'; export default class MetisTransferProcessor @@ -50,7 +50,7 @@ export default class MetisTransferProcessor ], id: 1 }; - const tmp = await BlocksoftAxios.post( + const tmp = await AirDAOAxios.post( BlocksoftExternalSettings.getStatic('METIS_SERVER'), params ); diff --git a/crypto/blockchains/one/OneScannerProcessor.ts b/crypto/blockchains/one/OneScannerProcessor.ts index 3801fdef8..a501596c9 100644 --- a/crypto/blockchains/one/OneScannerProcessor.ts +++ b/crypto/blockchains/one/OneScannerProcessor.ts @@ -4,7 +4,7 @@ */ import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; import BlocksoftExternalSettings from '@crypto/common/AirDAOExternalSettings'; -import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; +import AirDAOAxios from '@crypto/common/AirDAOAxios'; import OneUtils from '@crypto/blockchains/one/ext/OneUtils'; import BlocksoftUtils from '@crypto/common/AirDAOUtils'; @@ -43,7 +43,7 @@ export default class OneScannerProcessor { method: 'hmy_getBalance', params: [oneAddress, 'latest'] }; - const res = await BlocksoftAxios._request(apiPath, 'POST', data); + const res = await AirDAOAxios._request(apiPath, 'POST', data); if (typeof res.data === 'undefined') { return false; } @@ -106,7 +106,7 @@ export default class OneScannerProcessor { } ] }; - const res = await BlocksoftAxios._request(apiPath, 'POST', data); + const res = await AirDAOAxios._request(apiPath, 'POST', data); if ( typeof res.data === 'undefined' || typeof res.data.result === 'undefined' || diff --git a/crypto/blockchains/one/OneScannerProcessorErc20.ts b/crypto/blockchains/one/OneScannerProcessorErc20.ts index 902d9f3b1..f81d5d82d 100644 --- a/crypto/blockchains/one/OneScannerProcessorErc20.ts +++ b/crypto/blockchains/one/OneScannerProcessorErc20.ts @@ -4,7 +4,7 @@ */ import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; import BlocksoftExternalSettings from '@crypto/common/AirDAOExternalSettings'; -import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; +import AirDAOAxios from '@crypto/common/AirDAOAxios'; import OneUtils from '@crypto/blockchains/one/ext/OneUtils'; import BlocksoftUtils from '@crypto/common/AirDAOUtils'; import EthScannerProcessorErc20 from '@crypto/blockchains/eth/EthScannerProcessorErc20'; @@ -50,7 +50,7 @@ export default class OneScannerProcessorErc20 extends EthScannerProcessorErc20 { } ] }; - const res = await BlocksoftAxios._request(apiPath, 'POST', data); + const res = await AirDAOAxios._request(apiPath, 'POST', data); if ( typeof res.data === 'undefined' || typeof res.data.result === 'undefined' || diff --git a/crypto/blockchains/sol/SolScannerProcessor.ts b/crypto/blockchains/sol/SolScannerProcessor.ts index 59b44db43..9a30834a2 100644 --- a/crypto/blockchains/sol/SolScannerProcessor.ts +++ b/crypto/blockchains/sol/SolScannerProcessor.ts @@ -2,7 +2,7 @@ * @version 0.52 */ import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; -import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; +import AirDAOAxios from '@crypto/common/AirDAOAxios'; import BlocksoftUtils from '@crypto/common/AirDAOUtils'; import BlocksoftExternalSettings from '@crypto/common/AirDAOExternalSettings'; @@ -62,7 +62,7 @@ export default class SolScannerProcessor { method: 'getBalance', params: [address] }; - const res = await BlocksoftAxios._request(apiPath, 'POST', data); + const res = await AirDAOAxios._request(apiPath, 'POST', data); if ( typeof res.data.result === 'undefined' || @@ -114,7 +114,7 @@ export default class SolScannerProcessor { data.params[1].until = CACHE_FROM_DB[lastHashVar].last_hash; } const apiPath = BlocksoftExternalSettings.getStatic('SOL_SERVER'); - const res = await BlocksoftAxios._request(apiPath, 'POST', data); + const res = await AirDAOAxios._request(apiPath, 'POST', data); if (typeof res.data.result === 'undefined' || !res.data.result) { return false; } @@ -226,7 +226,7 @@ export default class SolScannerProcessor { if (typeof CACHE_TXS[transaction.signature] === 'undefined') { const apiPath = BlocksoftExternalSettings.getStatic('SOL_SERVER'); try { - const res = await BlocksoftAxios._request(apiPath, 'POST', data); + const res = await AirDAOAxios._request(apiPath, 'POST', data); if (typeof res.data.result === 'undefined' || !res.data.result) { return false; } diff --git a/crypto/blockchains/sol/SolScannerProcessorSpl.ts b/crypto/blockchains/sol/SolScannerProcessorSpl.ts index 10c3e268d..a2d3a9de9 100644 --- a/crypto/blockchains/sol/SolScannerProcessorSpl.ts +++ b/crypto/blockchains/sol/SolScannerProcessorSpl.ts @@ -2,7 +2,7 @@ * @version 0.52 */ import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; -import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; +import AirDAOAxios from '@crypto/common/AirDAOAxios'; import BlocksoftExternalSettings from '@crypto/common/AirDAOExternalSettings'; import SolScannerProcessor from '@crypto/blockchains/sol/SolScannerProcessor'; import { AxiosResponse } from 'axios'; @@ -65,7 +65,7 @@ export default class SolScannerProcessorSpl extends SolScannerProcessor { }; // @ts-ignore - const res: AxiosResponse = await BlocksoftAxios._request( + const res: AxiosResponse = await AirDAOAxios._request( apiPath, 'POST', data diff --git a/crypto/blockchains/sol/SolTokenProcessor.ts b/crypto/blockchains/sol/SolTokenProcessor.ts index 788509eff..c195be902 100644 --- a/crypto/blockchains/sol/SolTokenProcessor.ts +++ b/crypto/blockchains/sol/SolTokenProcessor.ts @@ -1,7 +1,7 @@ /** * @version 0.52 */ -import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; +import AirDAOAxios from '@crypto/common/AirDAOAxios'; import BlocksoftExternalSettings from '@crypto/common/AirDAOExternalSettings'; import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; import { AxiosResponse } from 'axios'; @@ -32,7 +32,7 @@ export default class SolTokenProcessor { async getTokenDetails(tokenAddress: string): Promise { const link = await BlocksoftExternalSettings.get('SOL_TOKENS_LIST'); // @ts-ignore - const res: AxiosResponse = await BlocksoftAxios.get(link); + const res: AxiosResponse = await AirDAOAxios.get(link); if (!res || typeof res.data.tokens === 'undefined' || !res.data.tokens) { return false; } @@ -75,7 +75,7 @@ export default class SolTokenProcessor { } ] }; - const response = await BlocksoftAxios._request(apiPath, 'POST', data); + const response = await AirDAOAxios._request(apiPath, 'POST', data); if ( response && response.data && diff --git a/crypto/blockchains/sol/SolTransferProcessor.ts b/crypto/blockchains/sol/SolTransferProcessor.ts index 96d7f35b8..b5e69c7f1 100644 --- a/crypto/blockchains/sol/SolTransferProcessor.ts +++ b/crypto/blockchains/sol/SolTransferProcessor.ts @@ -18,7 +18,7 @@ import SolTmpDS from '@crypto/blockchains/sol/stores/SolTmpDS'; import SolStakeUtils from '@crypto/blockchains/sol/ext/SolStakeUtils'; import { Buffer } from 'buffer'; import AirDAOCryptoUtils from '@crypto/common/AirDAOCryptoUtils'; -import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; +import AirDAOAxios from '@crypto/common/AirDAOAxios'; import config from '@constants/config'; import { AirDAOBlockchainTypes } from '@crypto/blockchains/AirDAOBlockchainTypes'; @@ -80,7 +80,7 @@ export default class SolTransferProcessor ' beach link ' + beachPath ); - const res = await BlocksoftAxios.get(beachPath); + const res = await AirDAOAxios.get(beachPath); AirDAOCryptoLog.log( this._settings.currencyCode + ' SolTransferProcessor.getTransferAllBalance address ' + diff --git a/crypto/blockchains/sol/ext/SolStakeUtils.ts b/crypto/blockchains/sol/ext/SolStakeUtils.ts index 2d7a3fd48..c6883d500 100644 --- a/crypto/blockchains/sol/ext/SolStakeUtils.ts +++ b/crypto/blockchains/sol/ext/SolStakeUtils.ts @@ -1,7 +1,7 @@ /** * @version 0.52 */ -import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; +import AirDAOAxios from '@crypto/common/AirDAOAxios'; import BlocksoftExternalSettings from '@crypto/common/AirDAOExternalSettings'; import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; @@ -63,7 +63,7 @@ for (const tmp of validatorsConstants) { const init = async () => { const link = await BlocksoftExternalSettings.get('SOL_VALIDATORS_LIST'); - const res = await BlocksoftAxios.get(link); + const res = await AirDAOAxios.get(link); if (!res.data || typeof res.data[0] === 'undefined' || !res.data[0]) { return false; } @@ -89,7 +89,7 @@ export default { const apiPath = BlocksoftExternalSettings.getStatic('SOL_SERVER'); const getVote = { jsonrpc: '2.0', id: 1, method: 'getVoteAccounts' }; - const resVote = await BlocksoftAxios._request(apiPath, 'POST', getVote); + const resVote = await AirDAOAxios._request(apiPath, 'POST', getVote); if ( !resVote || typeof resVote.data === 'undefined' || @@ -191,7 +191,7 @@ export default { `) */ - const res = await BlocksoftAxios.post(link, data); + const res = await AirDAOAxios.post(link, data); if ( res.data && typeof res.data.result !== 'undefined' && @@ -288,7 +288,7 @@ export default { let res; accountInfo = []; try { - res = await BlocksoftAxios._request(apiPath, 'POST', checkData); + res = await AirDAOAxios._request(apiPath, 'POST', checkData); for (const tmp of res.data.result) { const parsed = tmp.account.data.parsed; @@ -340,7 +340,7 @@ export default { 'https://prod-api.solana.surf/v1/account/' + address + '/stakes?limit=10&offset=0'; - const res2 = await BlocksoftAxios.get(apiPath2); + const res2 = await AirDAOAxios.get(apiPath2); for (const tmp of res2.data.data) { const item = { diff --git a/crypto/blockchains/sol/ext/SolUtils.ts b/crypto/blockchains/sol/ext/SolUtils.ts index 77a3c98ee..1b2f3988c 100644 --- a/crypto/blockchains/sol/ext/SolUtils.ts +++ b/crypto/blockchains/sol/ext/SolUtils.ts @@ -1,7 +1,7 @@ /** * @version 0.52 */ -import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; +import AirDAOAxios from '@crypto/common/AirDAOAxios'; import BlocksoftExternalSettings from '@crypto/common/AirDAOExternalSettings'; import { PublicKey } from '@solana/web3.js/src'; @@ -78,7 +78,7 @@ export default { ] }; // @ts-ignore - const res: AxiosResponse = await BlocksoftAxios._request( + const res: AxiosResponse = await AirDAOAxios._request( apiPath, 'POST', checkData @@ -125,7 +125,7 @@ export default { let try2 = false; let sendRes: any = null; // Initialize with 'null' try { - sendRes = await BlocksoftAxios._request(apiPath, 'POST', sendData); + sendRes = await AirDAOAxios._request(apiPath, 'POST', sendData); if (!sendRes || typeof sendRes.data === 'undefined') { if (apiPath2) { try2 = true; @@ -150,7 +150,7 @@ export default { if (try2 && apiPath2 && apiPath2 !== apiPath) { let sendRes2: any = null; // Initialize with 'null' try { - sendRes2 = await BlocksoftAxios._request(apiPath2, 'POST', sendData); + sendRes2 = await AirDAOAxios._request(apiPath2, 'POST', sendData); if (!sendRes2 || typeof sendRes2.data === 'undefined') { throw new Error('SERVER_RESPONSE_BAD_INTERNET'); } @@ -182,7 +182,7 @@ export default { method: 'getRecentBlockhash' }; const apiPath = BlocksoftExternalSettings.getStatic('SOL_SERVER'); - const getRecentBlockhashRes = await BlocksoftAxios._request( + const getRecentBlockhashRes = await AirDAOAxios._request( apiPath, 'POST', getRecentBlockhashData @@ -206,7 +206,7 @@ export default { const apiPath = BlocksoftExternalSettings.getStatic('SOL_SERVER'); const getEpoch = { jsonrpc: '2.0', id: 1, method: 'getEpochInfo' }; try { - const resEpoch = await BlocksoftAxios._request(apiPath, 'POST', getEpoch); + const resEpoch = await AirDAOAxios._request(apiPath, 'POST', getEpoch); // @ts-ignore const tmp = resEpoch.data.result.epoch * 1; if (tmp > 0 && tmp !== CACHE_EPOCH.value) { diff --git a/crypto/blockchains/trx/TrxScannerProcessor.ts b/crypto/blockchains/trx/TrxScannerProcessor.ts index f3af1d4f7..0cb6c62c9 100644 --- a/crypto/blockchains/trx/TrxScannerProcessor.ts +++ b/crypto/blockchains/trx/TrxScannerProcessor.ts @@ -9,7 +9,7 @@ import TrxTransactionsProvider from './basic/TrxTransactionsProvider'; import TrxTransactionsTrc20Provider from './basic/TrxTransactionsTrc20Provider'; import AirDAOCryptoLog from '../../common/AirDAOCryptoLog'; import Database from '@app/appstores/DataSource/Database/main'; -import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; +import AirDAOAxios from '@crypto/common/AirDAOAxios'; import BlocksoftUtils from '@crypto/common/AirDAOUtils'; import transactionDS from '@app/appstores/DataSource/Transaction/Transaction'; import BlocksoftExternalSettings from '@crypto/common/AirDAOExternalSettings'; @@ -113,7 +113,7 @@ export default class TrxScannerProcessor { parameter: '0000000000000000000000' + addressHex, owner_address: addressHex }; - const tmp = await BlocksoftAxios.post( + const tmp = await AirDAOAxios.post( sendLink + '/wallet/triggerconstantcontract', params ); @@ -325,7 +325,7 @@ export default class TrxScannerProcessor { needUpdateBalance = 0; try { const link2 = sendLink + '/wallet/getnowblock'; - const block = await BlocksoftAxios.get(link2); + const block = await AirDAOAxios.get(link2); if ( typeof block !== 'undefined' && block && @@ -348,7 +348,7 @@ export default class TrxScannerProcessor { for (const row of res.array) { const linkRecheck = sendLink + '/wallet/gettransactioninfobyid'; try { - const recheck = await BlocksoftAxios.post(linkRecheck, { + const recheck = await AirDAOAxios.post(linkRecheck, { value: row.transactionHash }); if (typeof recheck.data !== undefined) { diff --git a/crypto/blockchains/trx/TrxTokenProcessor.ts b/crypto/blockchains/trx/TrxTokenProcessor.ts index 7a929b734..fe2c2ce2c 100644 --- a/crypto/blockchains/trx/TrxTokenProcessor.ts +++ b/crypto/blockchains/trx/TrxTokenProcessor.ts @@ -3,7 +3,7 @@ * https://apilist.tronscan.org/api/contract?contract=TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t * [ { address: 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t', balance: 7208332710, verify_status: 0, balanceInUsd: 0, trxCount: 758742, date_created: 1555400628000, creator: [Object] } ] } */ -import BlocksoftAxios from '../../common/BlocksoftAxios'; +import AirDAOAxios from '../../common/AirDAOAxios'; interface TokenDetails { currencyCodePrefix: string; @@ -50,7 +50,7 @@ export default class TrxTokenProcessor { */ async getTokenDetails(tokenAddress: string): Promise { if (tokenAddress[0] === 'T') { - const res = await BlocksoftAxios.get( + const res = await AirDAOAxios.get( this._tokenTronscanPath20 + tokenAddress ); if (typeof res.data[0] !== 'undefined') { @@ -68,7 +68,7 @@ export default class TrxTokenProcessor { }; } } else { - const res = await BlocksoftAxios.get( + const res = await AirDAOAxios.get( this._tokenTronscanPath10 + tokenAddress ); if (typeof res.data[0] !== 'undefined') { diff --git a/crypto/blockchains/trx/TrxTransferProcessor.ts b/crypto/blockchains/trx/TrxTransferProcessor.ts index c3f4e91d6..a1e88fa5e 100644 --- a/crypto/blockchains/trx/TrxTransferProcessor.ts +++ b/crypto/blockchains/trx/TrxTransferProcessor.ts @@ -1,7 +1,7 @@ /** * @version 0.20 */ -import BlocksoftAxios from '../../common/BlocksoftAxios'; +import AirDAOAxios from '../../common/AirDAOAxios'; import AirDAOCryptoLog from '../../common/AirDAOCryptoLog'; import BlocksoftUtils from '../../common/AirDAOUtils'; @@ -146,7 +146,7 @@ export default class TrxTransferProcessor (data.isTransferAll ? 1 : 0); let res = false; try { - res = await BlocksoftAxios.get(link); + res = await AirDAOAxios.get(link); } catch (e) { throw new Error('no proxy fee for ' + link); } @@ -380,7 +380,7 @@ export default class TrxTransferProcessor if (typeof data.dexOrderData === 'undefined' || !data.dexOrderData) { try { - const res2 = await BlocksoftAxios.post(link, { + const res2 = await AirDAOAxios.post(link, { address: addressHexTo }); const tronData2 = res2.data; @@ -663,7 +663,7 @@ export default class TrxTransferProcessor throw new Error(e1.message + ' in params build'); } if (index < total) { - res = await BlocksoftAxios.post(link, params); + res = await AirDAOAxios.post(link, params); tx = res.data.transaction; await AirDAOCryptoLog.log( @@ -729,7 +729,7 @@ export default class TrxTransferProcessor do { checks++; try { - const recheck = await BlocksoftAxios.post(linkRecheck, { + const recheck = await AirDAOAxios.post(linkRecheck, { value: tx.txID }); if (typeof recheck.data !== undefined) { @@ -780,7 +780,7 @@ export default class TrxTransferProcessor } } while (checks < 100 && !mined); } else { - res = await BlocksoftAxios.post(link, params); + res = await AirDAOAxios.post(link, params); } } } else { @@ -852,7 +852,7 @@ export default class TrxTransferProcessor ${link}, ${params}` ); - res = await BlocksoftAxios.post(link, params); + res = await AirDAOAxios.post(link, params); } else { params = { owner_address: ownerAddress, @@ -880,7 +880,7 @@ export default class TrxTransferProcessor ${link}, ${params}` ); - res = await BlocksoftAxios.post(link, params); + res = await AirDAOAxios.post(link, params); } catch (e: any) { await AirDAOCryptoLog.log( this._settings.currencyCode + diff --git a/crypto/blockchains/trx/basic/TrxNodeInfoProvider.ts b/crypto/blockchains/trx/basic/TrxNodeInfoProvider.ts index 9c765c540..34a778c70 100644 --- a/crypto/blockchains/trx/basic/TrxNodeInfoProvider.ts +++ b/crypto/blockchains/trx/basic/TrxNodeInfoProvider.ts @@ -1,4 +1,4 @@ -import BlocksoftAxios from '../../../common/BlocksoftAxios'; +import AirDAOAxios from '../../../common/AirDAOAxios'; import AirDAOCryptoLog from '../../../common/AirDAOCryptoLog'; import BlocksoftExternalSettings from '@crypto/common/AirDAOExternalSettings'; @@ -15,10 +15,7 @@ export default class TrxNodeInfoProvider { const sendLink: string = BlocksoftExternalSettings.getStatic('TRX_SEND_LINK'); const link = `${sendLink}/wallet/getnodeinfo`; - let info: any = await BlocksoftAxios.getWithoutBraking( - link, - INFO_MAX_TRY - ); + let info: any = await AirDAOAxios.getWithoutBraking(link, INFO_MAX_TRY); if ( info && typeof info.data !== 'undefined' && diff --git a/crypto/blockchains/trx/basic/TrxTransactionsProvider.ts b/crypto/blockchains/trx/basic/TrxTransactionsProvider.ts index 738ea17e5..efe748a12 100644 --- a/crypto/blockchains/trx/basic/TrxTransactionsProvider.ts +++ b/crypto/blockchains/trx/basic/TrxTransactionsProvider.ts @@ -2,7 +2,7 @@ * @version 0.5 */ import AirDAOCryptoLog from '../../../common/AirDAOCryptoLog'; -import BlocksoftAxios from '../../../common/BlocksoftAxios'; +import AirDAOAxios from '../../../common/AirDAOAxios'; import BlocksoftUtils from '../../../common/AirDAOUtils'; import TrxNodeInfoProvider from './TrxNodeInfoProvider'; import TransactionFilterTypeDict from '@appV2/dicts/transactionFilterTypeDict'; @@ -61,7 +61,7 @@ export default class TrxTransactionsProvider { } } - const res = await BlocksoftAxios.getWithoutBraking( + const res = await AirDAOAxios.getWithoutBraking( this._tronscanLink + address, TXS_MAX_TRY ); diff --git a/crypto/blockchains/trx/basic/TrxTransactionsTrc20Provider.ts b/crypto/blockchains/trx/basic/TrxTransactionsTrc20Provider.ts index d79a7d9e9..40840165e 100644 --- a/crypto/blockchains/trx/basic/TrxTransactionsTrc20Provider.ts +++ b/crypto/blockchains/trx/basic/TrxTransactionsTrc20Provider.ts @@ -5,7 +5,7 @@ import TrxTransactionsProvider from './TrxTransactionsProvider'; import BlocksoftUtils from '../../../common/AirDAOUtils'; import AirDAOCryptoLog from '../../../common/AirDAOCryptoLog'; -import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; +import AirDAOAxios from '@crypto/common/AirDAOAxios'; import Database from '@app/appstores/DataSource/Database/main'; import TransactionFilterTypeDict from '@appV2/dicts/transactionFilterTypeDict'; @@ -151,7 +151,7 @@ export default class TrxTransactionsTrc20Provider extends TrxTransactionsProvide } if (needData) { - const tmp = await BlocksoftAxios.get( + const tmp = await AirDAOAxios.get( 'https://apilist.tronscan.org/api/transaction-info?hash=' + res.transactionHash ); diff --git a/crypto/blockchains/trx/basic/TrxTrongridProvider.ts b/crypto/blockchains/trx/basic/TrxTrongridProvider.ts index e23075a1a..b90fcc9b5 100644 --- a/crypto/blockchains/trx/basic/TrxTrongridProvider.ts +++ b/crypto/blockchains/trx/basic/TrxTrongridProvider.ts @@ -3,7 +3,7 @@ * https://github.com/tronscan/tronscan-frontend/wiki/TRONSCAN-API */ import AirDAOCryptoLog from '../../../common/AirDAOCryptoLog'; -import BlocksoftAxios from '../../../common/BlocksoftAxios'; +import AirDAOAxios from '../../../common/AirDAOAxios'; import BlocksoftExternalSettings from '@crypto/common/AirDAOExternalSettings'; import TronUtils from '@crypto/blockchains/trx/ext/TronUtils'; @@ -136,7 +136,7 @@ export default class TrxTrongridProvider { AirDAOCryptoLog.log( 'TrxTrongridProvider.get ' + link + ' ' + JSON.stringify(params) ); - const res: { data: any } = await BlocksoftAxios.postWithoutBraking( + const res: { data: any } = await AirDAOAxios.postWithoutBraking( link, params, BALANCE_MAX_TRY @@ -278,7 +278,7 @@ export default class TrxTrongridProvider { let leftEnergy: any = false; let totalEnergy: any = false; try { - const res: { data: any } = await BlocksoftAxios.post(link, { address }); + const res: { data: any } = await AirDAOAxios.post(link, { address }); if (!res || typeof res.data !== 'object') { throw new Error('Invalid response from Trongrid'); } diff --git a/crypto/blockchains/trx/basic/TrxTronscanProvider.ts b/crypto/blockchains/trx/basic/TrxTronscanProvider.ts index 5ed7276ec..cc7eb8e35 100644 --- a/crypto/blockchains/trx/basic/TrxTronscanProvider.ts +++ b/crypto/blockchains/trx/basic/TrxTronscanProvider.ts @@ -3,7 +3,7 @@ * https://github.com/tronscan/tronscan-frontend/wiki/TRONSCAN-API */ import AirDAOCryptoLog from '../../../common/AirDAOCryptoLog'; -import BlocksoftAxios from '../../../common/BlocksoftAxios'; +import AirDAOAxios from '../../../common/AirDAOAxios'; const BALANCE_PATH = 'https://apilist.tronscan.org/api/account?address='; const BALANCE_MAX_TRY = 10; @@ -90,7 +90,7 @@ export default class TrxTronscanProvider { const link = BALANCE_PATH + address; AirDAOCryptoLog.log('TrxTronscanProvider.get ' + link); - const res = await BlocksoftAxios.getWithoutBraking(link, BALANCE_MAX_TRY); + const res = await AirDAOAxios.getWithoutBraking(link, BALANCE_MAX_TRY); // @ts-ignore if (!res || !('data' in res)) { return false; diff --git a/crypto/blockchains/trx/ext/TronStakeUtils.ts b/crypto/blockchains/trx/ext/TronStakeUtils.ts index 7f92398eb..6dd6ef3bf 100644 --- a/crypto/blockchains/trx/ext/TronStakeUtils.ts +++ b/crypto/blockchains/trx/ext/TronStakeUtils.ts @@ -3,7 +3,7 @@ import BlocksoftPrettyNumbers from '@crypto/common/AirDAOPrettyNumbers'; import BlocksoftBalances from '@crypto/actions/BlocksoftBalances/BlocksoftBalances'; import TronUtils from '@crypto/blockchains/trx/ext/TronUtils'; -import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; +import AirDAOAxios from '@crypto/common/AirDAOAxios'; import Log from '@app/services/Log/Log'; import { BlocksoftTransfer } from '@crypto/actions/BlocksoftTransfer/BlocksoftTransfer'; import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; @@ -182,7 +182,7 @@ const TronStakeUtils = { ): Promise { const sendLink = BlocksoftExternalSettings.getStatic('TRX_SEND_LINK'); const link = sendLink + shortLink; - const tmp = await BlocksoftAxios.post(link, params); + const tmp = await AirDAOAxios.post(link, params); let blockchainData; if (typeof tmp.data !== 'undefined') { diff --git a/crypto/blockchains/trx/providers/TrxSendProvider.ts b/crypto/blockchains/trx/providers/TrxSendProvider.ts index 5a4757db7..3e666a536 100644 --- a/crypto/blockchains/trx/providers/TrxSendProvider.ts +++ b/crypto/blockchains/trx/providers/TrxSendProvider.ts @@ -6,7 +6,7 @@ import { BlocksoftBlockchainTypes } from '@crypto/blockchains/BlocksoftBlockchai import DogeSendProvider from '@crypto/blockchains/doge/providers/DogeSendProvider'; import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; -import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; +import AirDAOAxios from '@crypto/common/AirDAOAxios'; import BlocksoftExternalSettings from '@crypto/common/AirDAOExternalSettings'; import config from '@constants/config'; @@ -89,7 +89,7 @@ export default class TrxSendProvider let send: any; try { - send = await BlocksoftAxios.post(link, tx); + send = await AirDAOAxios.post(link, tx); } catch (e: any) { if (config.debug.cryptoErrors) { console.log( diff --git a/crypto/blockchains/usdt/UsdtScannerProcessor.ts b/crypto/blockchains/usdt/UsdtScannerProcessor.ts index e14329e91..09f12785b 100644 --- a/crypto/blockchains/usdt/UsdtScannerProcessor.ts +++ b/crypto/blockchains/usdt/UsdtScannerProcessor.ts @@ -2,7 +2,7 @@ * @version 0.5 */ import BlocksoftUtils from '../../common/AirDAOUtils'; -import BlocksoftAxios from '../../common/BlocksoftAxios'; +import AirDAOAxios from '../../common/AirDAOAxios'; import AirDAOCryptoLog from '../../common/AirDAOCryptoLog'; import BlocksoftDispatcher from '@lib/BlocksoftDispatcher'; @@ -47,7 +47,7 @@ export default class UsdtScannerProcessor { return CACHE[address]; } const link = `${USDT_API}/${address}`; - const res = await BlocksoftAxios.getWithoutBraking(link); + const res = await AirDAOAxios.getWithoutBraking(link); if (!res || typeof res.data === 'undefined' || !res.data) { return false; } @@ -87,7 +87,7 @@ export default class UsdtScannerProcessor { // mass ask const link = `${USDT_API_MASS}`; - const res = await BlocksoftAxios.postWithoutBraking(link, address); + const res = await AirDAOAxios.postWithoutBraking(link, address); if (!res || typeof res.data === 'undefined') { return false; diff --git a/crypto/blockchains/vet/VetScannerProcessor.ts b/crypto/blockchains/vet/VetScannerProcessor.ts index 55272c794..c52ef8ec9 100644 --- a/crypto/blockchains/vet/VetScannerProcessor.ts +++ b/crypto/blockchains/vet/VetScannerProcessor.ts @@ -3,7 +3,7 @@ * https://docs.vechain.org/thor/get-started/api.html */ import AirDAOCryptoLog from '../../common/AirDAOCryptoLog'; -import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; +import AirDAOAxios from '@crypto/common/AirDAOAxios'; import BlocksoftUtils from '@crypto/common/AirDAOUtils'; const API_PATH = 'https://sync-mainnet.vechain.org'; @@ -39,7 +39,7 @@ export default class VetScannerProcessor { } const link = API_PATH + '/accounts/' + address; - const res = await BlocksoftAxios.getWithoutBraking(link); + const res = await AirDAOAxios.getWithoutBraking(link); if ( !res || typeof res.data === 'undefined' || @@ -119,7 +119,7 @@ export default class VetScannerProcessor { if (this._settings.currencyCode === 'VET') { try { const linkNumber = API_PATH + '/blocks/best'; - const resultNumber = await BlocksoftAxios.get(linkNumber); + const resultNumber = await AirDAOAxios.get(linkNumber); if ( resultNumber && typeof resultNumber.data !== 'undefined' && @@ -137,7 +137,7 @@ export default class VetScannerProcessor { } const link = API_PATH + '/logs/transfer'; - const result = await BlocksoftAxios.post(link, { + const result = await AirDAOAxios.post(link, { options: { offset: 0, limit: 100 @@ -167,7 +167,7 @@ export default class VetScannerProcessor { const link = API_PATH + '/logs/event'; const tokenHex = '0x000000000000000000000000' + address.toLowerCase().substr(2); - const result = await BlocksoftAxios.post(link, { + const result = await AirDAOAxios.post(link, { options: { offset: 0, limit: 100 diff --git a/crypto/blockchains/vet/VetTransferProcessor.ts b/crypto/blockchains/vet/VetTransferProcessor.ts index 831dd355b..cf1937491 100644 --- a/crypto/blockchains/vet/VetTransferProcessor.ts +++ b/crypto/blockchains/vet/VetTransferProcessor.ts @@ -5,7 +5,7 @@ import AirDAOCryptoLog from '../../common/AirDAOCryptoLog'; import BlocksoftUtils from '../../common/AirDAOUtils'; import { thorify } from 'thorify'; -import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; +import AirDAOAxios from '@crypto/common/AirDAOAxios'; import { AirDAOBlockchainTypes } from '@crypto/blockchains/AirDAOBlockchainTypes'; const Web3 = require('web3'); @@ -215,7 +215,7 @@ export default class VetTransferProcessor const result = {} as AirDAOBlockchainTypes.SendTxResult; try { - const send = await BlocksoftAxios.post( + const send = await AirDAOAxios.post( API_PATH + '/transactions', { raw: signedData.rawTransaction }, false diff --git a/crypto/blockchains/waves/WavesScannerProcessor.ts b/crypto/blockchains/waves/WavesScannerProcessor.ts index 3a6c770e9..ffd028290 100644 --- a/crypto/blockchains/waves/WavesScannerProcessor.ts +++ b/crypto/blockchains/waves/WavesScannerProcessor.ts @@ -2,7 +2,7 @@ * @version 0.5 */ import AirDAOCryptoLog from '../../common/AirDAOCryptoLog'; -import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; +import AirDAOAxios from '@crypto/common/AirDAOAxios'; import BlocksoftExternalSettings from '@crypto/common/AirDAOExternalSettings'; import TransactionFilterTypeDict from '@appV2/dicts/transactionFilterTypeDict'; import WavesTransactionsProvider from '@crypto/blockchains/waves/providers/WavesTransactionsProvider'; @@ -43,7 +43,7 @@ export default class WavesScannerProcessor { ); const link = `${this._apiPath}/addresses/balance/details/${address}`; - const res = await BlocksoftAxios.get(link); + const res = await AirDAOAxios.get(link); if (!res) { return false; } diff --git a/crypto/blockchains/waves/WavesScannerProcessorErc20.ts b/crypto/blockchains/waves/WavesScannerProcessorErc20.ts index 1e8294c26..c651ff1d2 100644 --- a/crypto/blockchains/waves/WavesScannerProcessorErc20.ts +++ b/crypto/blockchains/waves/WavesScannerProcessorErc20.ts @@ -2,7 +2,7 @@ * @version 0.5 */ import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; -import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; +import AirDAOAxios from '@crypto/common/AirDAOAxios'; import BlocksoftExternalSettings from '@crypto/common/AirDAOExternalSettings'; import WavesScannerProcessor from '@crypto/blockchains/waves/WavesScannerProcessor'; import TransactionFilterTypeDict from '@appV2/dicts/transactionFilterTypeDict'; @@ -46,7 +46,7 @@ export default class WavesScannerProcessorErc20 extends WavesScannerProcessor { ); const link = `${this._apiPath}/assets/balance/${address}/${this._tokenAddress}`; - const res = await BlocksoftAxios.get(link); + const res = await AirDAOAxios.get(link); if (!res) { return false; } diff --git a/crypto/blockchains/waves/providers/WavesTransactionsProvider.ts b/crypto/blockchains/waves/providers/WavesTransactionsProvider.ts index 22f761df0..dd7a5f5ab 100644 --- a/crypto/blockchains/waves/providers/WavesTransactionsProvider.ts +++ b/crypto/blockchains/waves/providers/WavesTransactionsProvider.ts @@ -2,7 +2,7 @@ * @version 0.50 */ import BlocksoftExternalSettings from '@crypto/common/AirDAOExternalSettings'; -import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; +import AirDAOAxios from '@crypto/common/AirDAOAxios'; import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; interface TransactionData { @@ -44,7 +44,7 @@ export default class WavesTransactionsProvider { } const link = `${_apiPath}/transactions/address/${address}/limit/100`; - const res = await BlocksoftAxios.get(link); + const res = await AirDAOAxios.get(link); if (!res || !res.data || typeof res.data[0] === 'undefined') { return false; diff --git a/crypto/blockchains/xlm/XlmScannerProcessor.ts b/crypto/blockchains/xlm/XlmScannerProcessor.ts index 03b962631..72e4f9032 100644 --- a/crypto/blockchains/xlm/XlmScannerProcessor.ts +++ b/crypto/blockchains/xlm/XlmScannerProcessor.ts @@ -1,7 +1,7 @@ /** * @version 0.20 */ -import BlocksoftAxios from '../../common/BlocksoftAxios'; +import AirDAOAxios from '../../common/AirDAOAxios'; import AirDAOCryptoLog from '../../common/AirDAOCryptoLog'; import BlocksoftUtils from '../../common/AirDAOUtils'; import config from '@constants/config'; @@ -39,7 +39,7 @@ export default class XlmScannerProcessor { let res: any = false; let balance = 0; try { - res = await BlocksoftAxios.getWithoutBraking(link); + res = await AirDAOAxios.getWithoutBraking(link); if ( res && typeof res.data !== 'undefined' && @@ -90,7 +90,7 @@ export default class XlmScannerProcessor { const linkTxs = `${API_PATH}/accounts/${address}/transactions?order=desc&limit=50`; let res: any = false; try { - res = await BlocksoftAxios.getWithoutBraking(linkTxs); + res = await AirDAOAxios.getWithoutBraking(linkTxs); } catch (e: any) { if ( e.message.indexOf('account not found') === -1 && @@ -129,7 +129,7 @@ export default class XlmScannerProcessor { const link = `${API_PATH}/accounts/${address}/payments?order=desc&limit=50`; res = false; try { - res = await BlocksoftAxios.getWithoutBraking(link); + res = await AirDAOAxios.getWithoutBraking(link); } catch (e: any) { if ( e.message.indexOf('account not found') === -1 && diff --git a/crypto/blockchains/xmr/XmrAddressProcessor.js b/crypto/blockchains/xmr/XmrAddressProcessor.js index df973170f..3767a6aeb 100644 --- a/crypto/blockchains/xmr/XmrAddressProcessor.js +++ b/crypto/blockchains/xmr/XmrAddressProcessor.js @@ -10,7 +10,7 @@ import MoneroUtils from './ext/MoneroUtils'; import MoneroMnemonic from './ext/MoneroMnemonic'; import { soliditySha3 } from 'web3-utils'; -import BlocksoftAxios from '../../common/BlocksoftAxios'; +import AirDAOAxios from '../../common/AirDAOAxios'; import AirDAOCryptoLog from '../../common/AirDAOCryptoLog'; import BlocksoftSecrets from '@crypto/actions/BlocksoftSecrets/BlocksoftSecrets'; import config from '@app/config/config'; @@ -118,7 +118,7 @@ export default class XmrAddressProcessor { create_account: true, generated_locally: true }; - const resLogin = await BlocksoftAxios.post( + const resLogin = await AirDAOAxios.post( 'https://api.mymonero.com:8443/login', linkParamsLogin ); diff --git a/crypto/blockchains/xmr/XmrAddressProcessor.ts b/crypto/blockchains/xmr/XmrAddressProcessor.ts index 1d8653661..93972ca8a 100644 --- a/crypto/blockchains/xmr/XmrAddressProcessor.ts +++ b/crypto/blockchains/xmr/XmrAddressProcessor.ts @@ -10,7 +10,7 @@ import MoneroUtils from './ext/MoneroUtils'; import MoneroMnemonic from './ext/MoneroMnemonic'; import { soliditySha3 } from 'web3-utils'; -import BlocksoftAxios from '../../common/BlocksoftAxios'; +import AirDAOAxios from '../../common/AirDAOAxios'; import AirDAOCryptoLog from '../../common/AirDAOCryptoLog'; import BlocksoftSecrets from '@crypto/actions/BlocksoftSecrets/BlocksoftSecrets'; import config from '@constants/config'; @@ -158,7 +158,7 @@ export default class XmrAddressProcessor { create_account: true, generated_locally: true }; - const resLogin = (await BlocksoftAxios.post( + const resLogin = (await AirDAOAxios.post( 'https://api.mymonero.com:8443/login', linkParamsLogin )) as unknown as { data: any }; diff --git a/crypto/blockchains/xmr/XmrScannerProcessor.js b/crypto/blockchains/xmr/XmrScannerProcessor.js index 53ec84dd6..291862615 100644 --- a/crypto/blockchains/xmr/XmrScannerProcessor.js +++ b/crypto/blockchains/xmr/XmrScannerProcessor.js @@ -4,7 +4,7 @@ */ import BlocksoftUtils from '@crypto/common/BlocksoftUtils'; -import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; +import AirDAOAxios from '@crypto/common/AirDAOAxios'; import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; import BlocksoftPrivateKeysUtils from '@crypto/common/BlocksoftPrivateKeysUtils'; @@ -108,7 +108,7 @@ export default class XmrScannerProcessor { 'get_address_info', JSON.stringify(linkParams) ); - res = await BlocksoftAxios.post(link + 'get_address_info', linkParams); + res = await AirDAOAxios.post(link + 'get_address_info', linkParams); } catch (e) { AirDAOCryptoLog.log( this._settings.currencyCode + @@ -141,7 +141,7 @@ export default class XmrScannerProcessor { generated_locally: true }; try { - await BlocksoftAxios.post( + await AirDAOAxios.post( 'https://api.mymonero.com:8443/login', linkParamsLogin ); // login needed @@ -199,7 +199,7 @@ export default class XmrScannerProcessor { ); } - const res2 = await BlocksoftAxios.postWithoutBraking( + const res2 = await AirDAOAxios.postWithoutBraking( link + 'get_address_txs', linkParams ); diff --git a/crypto/blockchains/xmr/XmrScannerProcessor.ts b/crypto/blockchains/xmr/XmrScannerProcessor.ts index 8f6caadf2..3b0dfc059 100644 --- a/crypto/blockchains/xmr/XmrScannerProcessor.ts +++ b/crypto/blockchains/xmr/XmrScannerProcessor.ts @@ -4,7 +4,7 @@ */ import BlocksoftUtils from '@crypto/common/AirDAOUtils'; -import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; +import AirDAOAxios from '@crypto/common/AirDAOAxios'; import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; import BlocksoftPrivateKeysUtils from '@crypto/common/AirDAOPrivateKeysUtils'; @@ -116,7 +116,7 @@ export default class XmrScannerProcessor { 'get_address_info', ${JSON.stringify(linkParams)}` ); - res = await BlocksoftAxios.post(link + 'get_address_info', linkParams); + res = await AirDAOAxios.post(link + 'get_address_info', linkParams); } catch (e: any) { AirDAOCryptoLog.log( this._settings.currencyCode + @@ -149,7 +149,7 @@ export default class XmrScannerProcessor { generated_locally: true }; try { - await BlocksoftAxios.post( + await AirDAOAxios.post( 'https://api.mymonero.com:8443/login', linkParamsLogin ); // login needed @@ -207,7 +207,7 @@ export default class XmrScannerProcessor { ); } - const res2 = await BlocksoftAxios.postWithoutBraking( + const res2 = await AirDAOAxios.postWithoutBraking( link + 'get_address_txs', linkParams ); diff --git a/crypto/blockchains/xmr/providers/XmrSendProvider.ts b/crypto/blockchains/xmr/providers/XmrSendProvider.ts index 31728169f..6472f57b6 100644 --- a/crypto/blockchains/xmr/providers/XmrSendProvider.ts +++ b/crypto/blockchains/xmr/providers/XmrSendProvider.ts @@ -3,7 +3,7 @@ */ // @ts-ignore import settingsActions from '../../../../app/appstores/Stores/Settings/SettingsActions'; -import BlocksoftAxios from '../../../common/BlocksoftAxios'; +import AirDAOAxios from '../../../common/AirDAOAxios'; import AirDAOCryptoLog from '../../../common/AirDAOCryptoLog'; interface SendParams { @@ -12,7 +12,7 @@ interface SendParams { tx: string; } -interface BlocksoftAxiosResponse { +interface AirDAOAxiosResponse { data: { message: string; double_spend?: boolean; @@ -60,24 +60,21 @@ export default class XmrSendProvider { await this._init(); try { - let resNode: BlocksoftAxiosResponse; + let resNode: AirDAOAxiosResponse; if (this._link?.includes('mymonero.com')) { // @ts-ignore - resNode = await BlocksoftAxios.post(this._link + 'submit_raw_tx', { + resNode = await AirDAOAxios.post(this._link + 'submit_raw_tx', { address: params.address, view_key: params.privViewKey, tx: params.tx }); } else { // @ts-ignore - resNode = await BlocksoftAxios.post( - this._link + 'send_raw_transaction', - { - tx_as_hex: params.tx, - do_not_relay: false - } - ); + resNode = await AirDAOAxios.post(this._link + 'send_raw_transaction', { + tx_as_hex: params.tx, + do_not_relay: false + }); } AirDAOCryptoLog.log( diff --git a/crypto/blockchains/xmr/providers/XmrUnspentsProvider.js b/crypto/blockchains/xmr/providers/XmrUnspentsProvider.js index b4c32864b..0bc3e819a 100644 --- a/crypto/blockchains/xmr/providers/XmrUnspentsProvider.js +++ b/crypto/blockchains/xmr/providers/XmrUnspentsProvider.js @@ -2,7 +2,7 @@ * @version 0.11 */ import settingsActions from '../../../../app/appstores/Stores/Settings/SettingsActions'; -import BlocksoftAxios from '../../../common/BlocksoftAxios'; +import AirDAOAxios from '../../../common/AirDAOAxios'; import AirDAOCryptoLog from '../../../common/AirDAOCryptoLog'; export default class XmrUnspentsProvider { @@ -47,10 +47,7 @@ export default class XmrUnspentsProvider { dust_threshold: '2000000000' } */ - res = await BlocksoftAxios.post( - this._link + 'get_unspent_outs', - params - ); + res = await AirDAOAxios.post(this._link + 'get_unspent_outs', params); AirDAOCryptoLog.log( 'XmrUnspentsProvider Xmr._getUnspents res ' + JSON.stringify(res.data).substr(0, 200) @@ -87,7 +84,7 @@ export default class XmrUnspentsProvider { } */ - const res = await BlocksoftAxios.post( + const res = await AirDAOAxios.post( this._link + 'get_random_outs', params ); diff --git a/crypto/blockchains/xmr/providers/XmrUnspentsProvider.ts b/crypto/blockchains/xmr/providers/XmrUnspentsProvider.ts index 343d3131d..6ae2bf726 100644 --- a/crypto/blockchains/xmr/providers/XmrUnspentsProvider.ts +++ b/crypto/blockchains/xmr/providers/XmrUnspentsProvider.ts @@ -3,7 +3,7 @@ */ // @ts-ignore import settingsActions from '../../../../app/appstores/Stores/Settings/SettingsActions'; -import BlocksoftAxios from '../../../common/BlocksoftAxios'; +import AirDAOAxios from '../../../common/AirDAOAxios'; import AirDAOCryptoLog from '../../../common/AirDAOCryptoLog'; interface ApiResponse { @@ -64,10 +64,7 @@ export default class XmrUnspentsProvider { } */ // @ts-ignore - res = await BlocksoftAxios.post( - this._link + 'get_unspent_outs', - params - ); + res = await AirDAOAxios.post(this._link + 'get_unspent_outs', params); AirDAOCryptoLog.log( 'XmrUnspentsProvider Xmr._getUnspents res ' + JSON.stringify(res.data).substr(0, 200) @@ -107,7 +104,7 @@ export default class XmrUnspentsProvider { */ // @ts-ignore - const res: ApiResponse = await BlocksoftAxios.post( + const res: ApiResponse = await AirDAOAxios.post( this._link + 'get_random_outs', params ); diff --git a/crypto/blockchains/xrp/basic/XrpDataRippleProvider.js b/crypto/blockchains/xrp/basic/XrpDataRippleProvider.js index 0a5fc8183..496c02492 100644 --- a/crypto/blockchains/xrp/basic/XrpDataRippleProvider.js +++ b/crypto/blockchains/xrp/basic/XrpDataRippleProvider.js @@ -1,4 +1,4 @@ -import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; +import AirDAOAxios from '@crypto/common/AirDAOAxios'; import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; import XrpTmpDS from '@crypto/blockchains/xrp/stores/XrpTmpDS'; @@ -17,7 +17,7 @@ export default class XrpDataRippleProvider { let balance = 0; try { - res = await BlocksoftAxios.getWithoutBraking(link); + res = await AirDAOAxios.getWithoutBraking(link); if ( res && typeof res.data !== 'undefined' && @@ -65,7 +65,7 @@ export default class XrpDataRippleProvider { const link = `${API_PATH}/accounts/${address}/payments`; let res = false; try { - res = await BlocksoftAxios.getWithoutBraking(link); + res = await AirDAOAxios.getWithoutBraking(link); } catch (e) { if ( e.message.indexOf('account not found') === -1 && @@ -212,7 +212,7 @@ export default class XrpDataRippleProvider { CACHE_BLOCK_DATA[index].data.transactionConfirmations < 100) ) { try { - res = await BlocksoftAxios.getWithoutBraking(link); + res = await AirDAOAxios.getWithoutBraking(link); if ( res.data && typeof res.data !== 'undefined' && diff --git a/crypto/blockchains/xrp/basic/XrpDataRippleProvider.ts b/crypto/blockchains/xrp/basic/XrpDataRippleProvider.ts index a6f8176fb..4842dc7f4 100644 --- a/crypto/blockchains/xrp/basic/XrpDataRippleProvider.ts +++ b/crypto/blockchains/xrp/basic/XrpDataRippleProvider.ts @@ -1,4 +1,4 @@ -import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; +import AirDAOAxios from '@crypto/common/AirDAOAxios'; import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; import XrpTmpDS from '@crypto/blockchains/xrp/stores/XrpTmpDS'; @@ -17,7 +17,7 @@ export default class XrpDataRippleProvider { let balance = 0; try { - res = await BlocksoftAxios.getWithoutBraking(link); + res = await AirDAOAxios.getWithoutBraking(link); if ( res && typeof res.data !== 'undefined' && @@ -62,7 +62,7 @@ export default class XrpDataRippleProvider { const link = `${API_PATH}/accounts/${address}/payments`; let res: any = false; try { - res = await BlocksoftAxios.getWithoutBraking(link); + res = await AirDAOAxios.getWithoutBraking(link); } catch (e: any) { if ( e.message.indexOf('account not found') === -1 && @@ -190,7 +190,7 @@ export default class XrpDataRippleProvider { CACHE_BLOCK_DATA[index].data.transactionConfirmations < 100) ) { try { - res = await BlocksoftAxios.getWithoutBraking(link); + res = await AirDAOAxios.getWithoutBraking(link); if ( res.data && typeof res.data !== 'undefined' && diff --git a/crypto/blockchains/xrp/basic/XrpDataScanProvider.js b/crypto/blockchains/xrp/basic/XrpDataScanProvider.js index 240fb40aa..e9998c670 100644 --- a/crypto/blockchains/xrp/basic/XrpDataScanProvider.js +++ b/crypto/blockchains/xrp/basic/XrpDataScanProvider.js @@ -3,7 +3,7 @@ * https://xrpl.org/request-formatting.html */ import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings'; -import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; +import AirDAOAxios from '@crypto/common/AirDAOAxios'; import BlocksoftUtils from '@crypto/common/BlocksoftUtils'; import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; import XrpTmpDS from '@crypto/blockchains/xrp/stores/XrpTmpDS'; @@ -36,7 +36,7 @@ export default class XrpDataScanProvider { } ] }; - res = await BlocksoftAxios.postWithoutBraking(link, data); + res = await AirDAOAxios.postWithoutBraking(link, data); if ( res && @@ -101,7 +101,7 @@ export default class XrpDataScanProvider { } ] }; - res = await BlocksoftAxios.postWithoutBraking(link, data); + res = await AirDAOAxios.postWithoutBraking(link, data); if ( res && @@ -250,7 +250,7 @@ export default class XrpDataScanProvider { } ] }; - res = await BlocksoftAxios.postWithoutBraking(link, data); + res = await AirDAOAxios.postWithoutBraking(link, data); if ( res.data && typeof res.data !== 'undefined' && diff --git a/crypto/blockchains/xrp/basic/XrpDataScanProvider.ts b/crypto/blockchains/xrp/basic/XrpDataScanProvider.ts index 05b2dd3b0..ad4a66076 100644 --- a/crypto/blockchains/xrp/basic/XrpDataScanProvider.ts +++ b/crypto/blockchains/xrp/basic/XrpDataScanProvider.ts @@ -1,5 +1,5 @@ import BlocksoftExternalSettings from '@crypto/common/AirDAOExternalSettings'; -import BlocksoftAxios from '@crypto/common/BlocksoftAxios'; +import AirDAOAxios from '@crypto/common/AirDAOAxios'; import BlocksoftUtils from '@crypto/common/AirDAOUtils'; import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; import XrpTmpDS from '@crypto/blockchains/xrp/stores/XrpTmpDS'; @@ -40,7 +40,7 @@ export default class XrpDataScanProvider { // @ts-ignore // tslint:disable-next-line:no-shadowed-variable - const res: AxiosResponse = await BlocksoftAxios.postWithoutBraking( + const res: AxiosResponse = await AirDAOAxios.postWithoutBraking( link, data ); @@ -113,7 +113,7 @@ export default class XrpDataScanProvider { // @ts-ignore // tslint:disable-next-line:no-shadowed-variable - const res: AxiosResponse = await BlocksoftAxios.postWithoutBraking( + const res: AxiosResponse = await AirDAOAxios.postWithoutBraking( link, data ); @@ -283,7 +283,7 @@ export default class XrpDataScanProvider { } ] }; - res = await BlocksoftAxios.postWithoutBraking(link, data); + res = await AirDAOAxios.postWithoutBraking(link, data); if ( res.data && typeof res.data !== 'undefined' && diff --git a/crypto/blockchains/xvg/XvgScannerProcessor.js b/crypto/blockchains/xvg/XvgScannerProcessor.js index aedacaf36..0f85e6f58 100644 --- a/crypto/blockchains/xvg/XvgScannerProcessor.js +++ b/crypto/blockchains/xvg/XvgScannerProcessor.js @@ -3,7 +3,7 @@ * https://github.com/bitpay/bitcore/blob/master/packages/bitcore-node/docs/api-documentation.md * https://api.vergecurrency.network/node/api/XVG/mainnet/address/DL5LtSf7wztH45VuYunL8oaQHtJbKLCHyw/balance */ -import BlocksoftAxios from '../../common/BlocksoftAxios'; +import AirDAOAxios from '../../common/AirDAOAxios'; import AirDAOCryptoLog from '../../common/AirDAOCryptoLog'; import XvgTmpDS from './stores/XvgTmpDS'; @@ -27,7 +27,7 @@ export default class XvgScannerProcessor { */ async getBalanceBlockchain(address) { const link = `${API_PATH}/address/${address}/balance`; - const res = await BlocksoftAxios.getWithoutBraking(link); + const res = await AirDAOAxios.getWithoutBraking(link); if (!res || !res.data) { return false; } @@ -53,7 +53,7 @@ export default class XvgScannerProcessor { ); const link = `${API_PATH}/address/${address}/txs`; AirDAOCryptoLog.log('XvgScannerProcessor.getTransactions call ' + link); - let tmp = await BlocksoftAxios.get(link); + let tmp = await AirDAOAxios.get(link); if (tmp.status < 200 || tmp.status >= 300) { throw new Error('not valid server response status ' + link); } @@ -165,7 +165,7 @@ export default class XvgScannerProcessor { AirDAOCryptoLog.log( 'XvgScannerProcessor._unifyTransactionStep2 called ' + link ); - tmp = await BlocksoftAxios.get(link); + tmp = await AirDAOAxios.get(link); tmp = tmp.data; // noinspection ES6MissingAwait XvgTmpDS.saveCache(address, transaction.transactionHash, 'coins', tmp); @@ -189,7 +189,7 @@ export default class XvgScannerProcessor { AirDAOCryptoLog.log( 'XvgScannerProcessor._unifyTransactionStep2 call for details ' + link2 ); - let tmp2 = await BlocksoftAxios.get(link2); + let tmp2 = await AirDAOAxios.get(link2); tmp2 = tmp2.data; transaction.blockHash = tmp2.blockHash; transaction.blockTime = tmp2.blockTimeNormalized; diff --git a/crypto/blockchains/xvg/providers/XvgSendProvider.ts b/crypto/blockchains/xvg/providers/XvgSendProvider.ts index 90c3657c6..7dfe0cdd1 100644 --- a/crypto/blockchains/xvg/providers/XvgSendProvider.ts +++ b/crypto/blockchains/xvg/providers/XvgSendProvider.ts @@ -2,7 +2,7 @@ * @version 0.20 */ import AirDAOCryptoLog from '../../../common/AirDAOCryptoLog'; -import BlocksoftAxios from '../../../common/BlocksoftAxios'; +import AirDAOAxios from '../../../common/AirDAOAxios'; import BlocksoftExternalSettings from '../../../common/AirDAOExternalSettings'; import { AirDAOBlockchainTypes } from '@crypto/blockchains/AirDAOBlockchainTypes'; @@ -31,7 +31,7 @@ export default class XvgSendProvider ); let res; try { - res = await BlocksoftAxios.post(link, { rawTx: hex }); + res = await AirDAOAxios.post(link, { rawTx: hex }); AirDAOCryptoLog.log( this._settings.currencyCode + ' XvgSendProvider.sendTx ' + diff --git a/crypto/blockchains/xvg/providers/XvgUnspentsProvider.ts b/crypto/blockchains/xvg/providers/XvgUnspentsProvider.ts index a2b16afd4..58e9923c0 100644 --- a/crypto/blockchains/xvg/providers/XvgUnspentsProvider.ts +++ b/crypto/blockchains/xvg/providers/XvgUnspentsProvider.ts @@ -3,7 +3,7 @@ * https://api.vergecurrency.network/node/api/XVG/mainnet/address/DL5LtSf7wztH45VuYunL8oaQHtJbKLCHyw/txs/?unspent=true */ import AirDAOCryptoLog from '../../../common/AirDAOCryptoLog'; -import BlocksoftAxios from '../../../common/BlocksoftAxios'; +import AirDAOAxios from '../../../common/AirDAOAxios'; import { AirDAOBlockchainTypes } from '@crypto/blockchains/AirDAOBlockchainTypes'; export default class XvgUnspentsProvider @@ -27,7 +27,7 @@ export default class XvgUnspentsProvider ); const link = this._apiPath + address + '/txs/?unspent=true'; - const res = await BlocksoftAxios.getWithoutBraking(link); + const res = await AirDAOAxios.getWithoutBraking(link); AirDAOCryptoLog.log( this._settings.currencyCode + diff --git a/crypto/common/BlocksoftAxios.js b/crypto/common/AirDAOAxios.ts similarity index 85% rename from crypto/common/BlocksoftAxios.js rename to crypto/common/AirDAOAxios.ts index 54a42f473..3895d47f8 100644 --- a/crypto/common/BlocksoftAxios.js +++ b/crypto/common/AirDAOAxios.ts @@ -2,14 +2,10 @@ * @version 0.43 */ import AirDAOCryptoLog from './AirDAOCryptoLog'; - import axios from 'axios'; -import config from '@app/config/config'; -import { showModal } from '@app/appstores/Stores/Modal/ModalActions'; -import { strings } from '@app/services/i18n'; - import { Platform } from 'react-native'; import CookieManager from '@react-native-cookies/cookies'; +import { Toast, ToastPosition } from '@components/modular'; const CancelToken = axios && typeof axios.CancelToken !== 'undefined' @@ -17,7 +13,7 @@ const CancelToken = : function () {}; const CACHE_ERRORS_VALID_TIME = 60000; // 1 minute -const CACHE_ERRORS_BY_LINKS = {}; +const CACHE_ERRORS_BY_LINKS: { [key: string]: any } = {}; // TODO fix any const CACHE_STARTED = {}; const CACHE_STARTED_CANCEL = {}; @@ -32,18 +28,19 @@ const TIMEOUT_TRIES_10 = 15000; const TIMEOUT_TRIES_INTERNET = 5000; const TIMEOUT_TRIES_RATES = 20000; -class BlocksoftAxios { +class AirDAOAxios { /** * @param link * @param maxTry * @returns {Promise} */ - async getWithoutBraking(link, maxTry = 5, timeOut = false) { + async getWithoutBraking(link: string, maxTry = 5, timeOut = false) { let tmp = false; try { tmp = await this.get(link, false, false, timeOut); CACHE_ERRORS_BY_LINKS[link] = { time: 0, tries: 0 }; - } catch (e) { + } catch (err) { + const e = err as unknown as any; const now = new Date().getTime(); if (typeof CACHE_ERRORS_BY_LINKS[link] === 'undefined') { // first time @@ -64,7 +61,7 @@ class BlocksoftAxios { throw e; } AirDAOCryptoLog.log( - 'BlocksoftAxios.getWithoutBraking try ' + + 'AirDAOAxios.getWithoutBraking try ' + JSON.stringify(CACHE_ERRORS_BY_LINKS[link]) + ' error ' + e.message.substr(0, 300) @@ -74,12 +71,13 @@ class BlocksoftAxios { return tmp; } - async postWithoutBraking(link, data, maxTry = 5) { + async postWithoutBraking(link: string, data: any, maxTry = 5) { let tmp = false; try { tmp = await this.post(link, data, false); CACHE_ERRORS_BY_LINKS[link] = { time: 0, tries: 0 }; - } catch (e) { + } catch (err) { + const e = err as unknown as any; const now = new Date().getTime(); if (typeof CACHE_ERRORS_BY_LINKS[link] === 'undefined') { // first time @@ -100,7 +98,7 @@ class BlocksoftAxios { throw e; } AirDAOCryptoLog.log( - 'BlocksoftAxios.postWithoutBraking try ' + + 'AirDAOAxios.postWithoutBraking try ' + JSON.stringify(CACHE_ERRORS_BY_LINKS[link]) + ' error ' + e.message.substr(0, 200) @@ -110,23 +108,23 @@ class BlocksoftAxios { } async postWithHeaders( - link, - data, - addHeaders, + link: string, + data: any, + addHeaders: { [key: string]: string }, errSend = true, timeOut = false ) { let tmp = false; try { - const headers = { - 'upgrade-insecure-requests': 1, + const headers: { [key: string]: string } = { + 'upgrade-insecure-requests': '1', 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.128 Safari/537.36' }; const dataPrep = JSON.stringify(data); headers['Content-Type'] = 'application/json'; headers['Accept'] = 'application/json'; - for (let key in addHeaders) { + for (const key in addHeaders) { headers[key] = addHeaders[key]; } @@ -144,30 +142,35 @@ class BlocksoftAxios { tmpInner.status !== 202 ) { AirDAOCryptoLog.log( - 'BlocksoftAxios.post fetch result ' + JSON.stringify(tmpInner) + 'AirDAOAxios.post fetch result ' + JSON.stringify(tmpInner) ); } else { tmp = { data: await tmpInner.json() }; } } catch (e) { AirDAOCryptoLog.log( - 'BlocksoftAxios.postWithHeaders fetch result error ' + e.message + 'AirDAOAxios.postWithHeaders fetch result error ' + e.message ); } return tmp; } - async getWithHeaders(link, addHeaders, errSend = true, timeOut = false) { + async getWithHeaders( + link: string, + addHeaders: { [key: string]: string }, + errSend = true, + timeOut = false + ) { let tmp = false; try { - const headers = { - 'upgrade-insecure-requests': 1, + const headers: { [key: string]: string } = { + 'upgrade-insecure-requests': '1', 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.128 Safari/537.36' }; headers['Content-Type'] = 'application/json'; headers['Accept'] = 'application/json'; - for (let key in addHeaders) { + for (const key in addHeaders) { headers[key] = addHeaders[key]; } @@ -184,28 +187,26 @@ class BlocksoftAxios { tmpInner.status !== 202 ) { AirDAOCryptoLog.log( - 'BlocksoftAxios.get fetch result ' + JSON.stringify(tmpInner) + 'AirDAOAxios.get fetch result ' + JSON.stringify(tmpInner) ); } else { tmp = { data: await tmpInner.json() }; } } catch (e) { - console.log( - 'BlocksoftAxios.getWithHeaders fetch result error ' + e.message - ); + // TODO ignore } return tmp; } - async post(link, data, errSend = true, timeOut = false) { + async post(link: string, data: any, errSend = true, timeOut = false) { let tmp = false; let doOld = this._isTrustee(link); if (!doOld) { try { await this._cookie(link, 'POST'); let dataPrep; - const headers = { - 'upgrade-insecure-requests': 1, + const headers: { [key: string]: string } = { + 'upgrade-insecure-requests': '1', 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.128 Safari/537.36' }; @@ -233,16 +234,14 @@ class BlocksoftAxios { tmpInner.status !== 202 ) { AirDAOCryptoLog.log( - 'BlocksoftAxios.post fetch result ' + JSON.stringify(tmpInner) + 'AirDAOAxios.post fetch result ' + JSON.stringify(tmpInner) ); doOld = true; } else { tmp = { data: await tmpInner.json() }; } } catch (e) { - AirDAOCryptoLog.log( - 'BlocksoftAxios.post fetch result error ' + e.message - ); + AirDAOCryptoLog.log('AirDAOAxios.post fetch result error ' + e.message); doOld = true; } } @@ -252,7 +251,7 @@ class BlocksoftAxios { return tmp; } - async get(link, emptyIsBad = false, errSend = true, timeOut = false) { + async get(link: string, emptyIsBad = false, errSend = true, timeOut = false) { let tmp = false; let doOld = this._isTrustee(link); if (!doOld) { @@ -284,9 +283,7 @@ class BlocksoftAxios { antiCycle++; } while (tryOneMore && antiCycle < 3); } catch (e) { - AirDAOCryptoLog.log( - 'BlocksoftAxios.get fetch result error ' + e.message - ); + AirDAOCryptoLog.log('AirDAOAxios.get fetch result error ' + e.message); doOld = true; } } @@ -296,7 +293,7 @@ class BlocksoftAxios { return tmp; } - _isTrustee(link) { + _isTrustee(link: string) { const tmp = link.split('/'); const domain = tmp[0] + '/' + tmp[1] + '/' + tmp[2]; return !(domain.indexOf('trustee') === -1); @@ -412,7 +409,7 @@ class BlocksoftAxios { if (emptyIsBad && (tmp.status !== 200 || !tmp.data)) { // noinspection ExceptionCaughtLocallyJS throw new Error( - 'BlocksoftAxios.' + + 'AirDAOAxios.' + method + ' ' + link + @@ -437,9 +434,9 @@ class BlocksoftAxios { txt = JSON.stringify(tmp.data).substr(0, 300) } if (txt.length > 100) { - AirDAOCryptoLog.log('BlocksoftAxios.' + method + ' finish ' + link, txt) // separate line for txt + AirDAOCryptoLog.log('AirDAOAxios.' + method + ' finish ' + link, txt) // separate line for txt } else { - AirDAOCryptoLog.log('BlocksoftAxios.' + method + ' finish ' + link + ' ' + JSON.stringify(txt)) + AirDAOCryptoLog.log('AirDAOAxios.' + method + ' finish ' + link + ' ' + JSON.stringify(txt)) } */ @@ -461,11 +458,6 @@ class BlocksoftAxios { const customError = new Error(link + ' ' + e.message.toLowerCase()); customError.subdata = subdata; - - if (config.debug.appErrors) { - // console.log('BlocksoftAxios._request ' + link + ' data ' + JSON.stringify(data) , e) - } - if ( e.message.indexOf('Network Error') !== -1 || e.message.indexOf('network error') !== -1 || @@ -487,7 +479,7 @@ class BlocksoftAxios { if (link.indexOf('trustee.deals') !== -1) { // noinspection ES6MissingAwait AirDAOCryptoLog.log( - 'BlocksoftAxios.' + + 'AirDAOAxios.' + method + ' ' + link + @@ -503,17 +495,22 @@ class BlocksoftAxios { now - CACHE_TIMEOUT_ERROR_SHOWN > 60000 ) { CACHE_TIMEOUT_ERROR_SHOWN = now; - showModal({ - type: 'INFO_MODAL', - icon: null, - title: strings('modal.exchange.sorry'), - description: strings('toast.badInternet') + Toast.show({ + title: 'We are sorry', + message: 'You have bad internet!', + type: ToastPosition.Top }); + // showModal({ + // type: 'INFO_MODAL', + // icon: null, + // title: strings('modal.exchange.sorry'), + // description: strings('toast.badInternet') + // }); } } // noinspection ES6MissingAwait AirDAOCryptoLog.log( - 'BlocksoftAxios.' + + 'AirDAOAxios.' + method + ' ' + link + @@ -529,18 +526,13 @@ class BlocksoftAxios { ) { // noinspection ES6MissingAwait AirDAOCryptoLog.log( - 'BlocksoftAxios.' + - method + - ' ' + - link + - ' NOTICE TOO MUCH ' + - e.message + 'AirDAOAxios.' + method + ' ' + link + ' NOTICE TOO MUCH ' + e.message ); customError.code = 'ERROR_NOTICE'; } else if (link.indexOf('/api/v2/sendtx/') !== -1) { // noinspection ES6MissingAwait AirDAOCryptoLog.log( - 'BlocksoftAxios.' + + 'AirDAOAxios.' + method + ' ' + link + @@ -552,7 +544,7 @@ class BlocksoftAxios { } else if (e.message.indexOf('account not found') !== -1) { // noinspection ES6MissingAwait AirDAOCryptoLog.log( - 'BlocksoftAxios.' + method + ' ' + link + ' ' + e.message + 'AirDAOAxios.' + method + ' ' + link + ' ' + e.message ); // just nothing found return false; } else if (errSend) { @@ -563,7 +555,7 @@ class BlocksoftAxios { link.indexOf('https://api.mainnet-beta.solana.com') !== -1 ) { AirDAOCryptoLog.log( - 'BlocksoftAxios.' + + 'AirDAOAxios.' + method + ' ' + link + @@ -574,7 +566,7 @@ class BlocksoftAxios { ); } else { AirDAOCryptoLog.err( - 'BlocksoftAxios.' + + 'AirDAOAxios.' + method + ' ' + link + @@ -585,7 +577,7 @@ class BlocksoftAxios { } } else { AirDAOCryptoLog.log( - 'BlocksoftAxios.' + + 'AirDAOAxios.' + method + ' ' + link + @@ -597,7 +589,7 @@ class BlocksoftAxios { customError.code = 'ERROR_SYSTEM'; } else { AirDAOCryptoLog.log( - 'BlocksoftAxios.' + + 'AirDAOAxios.' + method + ' ' + link + @@ -614,4 +606,4 @@ class BlocksoftAxios { } } -export default new BlocksoftAxios(); +export default new AirDAOAxios(); diff --git a/crypto/common/AirDAOExternalSettings.ts b/crypto/common/AirDAOExternalSettings.ts index 0ce28d57a..3ca93cb42 100644 --- a/crypto/common/AirDAOExternalSettings.ts +++ b/crypto/common/AirDAOExternalSettings.ts @@ -1,4 +1,4 @@ -import BlocksoftAxios from './BlocksoftAxios'; +import AirDAOAxios from './AirDAOAxios'; import AirDAOCryptoLog from './AirDAOCryptoLog'; // @ts-ignore import ApiProxy from '../../app/services/Api/ApiProxy'; @@ -330,7 +330,7 @@ class AirDAOExternalSettings { if (!currentServer) { currentServer = server; } - const current = await BlocksoftAxios.getWithoutBraking(server + '/api'); + const current = await AirDAOAxios.getWithoutBraking(server + '/api'); if (current && typeof current === 'object' && 'data' in current) { if (typeof current.data.blockbook.bestHeight !== 'undefined') { const tmp = current.data.blockbook.bestHeight; diff --git a/package.json b/package.json index 38a9a2910..5acfcf7bb 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "@neverdull-agency/expo-unlimited-secure-store": "^1.0.10", "@nozbe/watermelondb": "^0.26.0", "@nozbe/with-observables": "^1.6.0", + "@react-native-cookies/cookies": "^6.2.1", "@react-native-firebase/analytics": "^17.5.0", "@react-native-firebase/app": "^17.5.0", "@react-native-firebase/crashlytics": "^17.5.0", @@ -54,8 +55,8 @@ "bn.js": "^5.2.1", "bs58check": "^3.0.1", "buffer": "^6.0.3", - "coinselect": "^3.1.13", "buffer-crc32": "^0.2.13", + "coinselect": "^3.1.13", "create-hash": "^1.2.0", "elliptic": "^6.5.4", "ethereumjs-util": "^7.1.5", @@ -104,10 +105,10 @@ "react-native-view-shot": "3.5.0", "react-native-walkthrough-tooltip": "^1.5.0", "ripple-lib": "^1.10.1", + "tiny-secp256k1": "^2.2.3", "use-context-selector": "^1.4.1", "web3": "^4.0.3", - "web3-utils": "^4.0.3", - "tiny-secp256k1": "^2.2.3" + "web3-utils": "^4.0.3" }, "devDependencies": { "@babel/core": "^7.20.0", diff --git a/yarn.lock b/yarn.lock index c2b809103..85b11783c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2725,6 +2725,13 @@ prompts "^2.4.0" semver "^6.3.0" +"@react-native-cookies/cookies@^6.2.1": + version "6.2.1" + resolved "https://registry.yarnpkg.com/@react-native-cookies/cookies/-/cookies-6.2.1.tgz#54d50b9496400bbdc19e43c155f70f8f918999e3" + integrity sha512-D17wCA0DXJkGJIxkL74Qs9sZ3sA+c+kCoGmXVknW7bVw/W+Vv1m/7mWTNi9DLBZSRddhzYw8SU0aJapIaM/g5w== + dependencies: + invariant "^2.2.4" + "@react-native-firebase/analytics@^17.5.0": version "17.5.0" resolved "https://registry.yarnpkg.com/@react-native-firebase/analytics/-/analytics-17.5.0.tgz#4f133f64edf2c5959116f862a62c12e6f4ec20ce" From 2935f656cb6f8e238dad924c39fffac1159e9fb0 Mon Sep 17 00:00:00 2001 From: illiaa Date: Fri, 4 Aug 2023 14:06:44 +0300 Subject: [PATCH 033/509] fixed errors in rewritten files --- .../blockchains/AirDAOTransferDispatcher.ts | 4 +- .../metis/MetisTransferProcessor.ts | 41 +- crypto/blockchains/one/ext/OneUtils.ts | 25 +- .../providers/WavesTransactionsProvider.ts | 14 +- .../blockchains/xmr/XmrTransferProcessor.ts | 26 +- crypto/blockchains/xmr/ext/MoneroMnemonic.ts | 2 + .../xmr/ext/MoneroUtilsParser.oldAndroid.ts | 22 +- .../xmr/ext/vendor/ResponseParser.js | 333 --------------- .../xmr/ext/vendor/ResponseParser.ts | 327 ++++++++++++++ package.json | 10 +- src/database/Database.ts | 20 + yarn.lock | 399 ++++++++++++++---- 12 files changed, 765 insertions(+), 458 deletions(-) delete mode 100644 crypto/blockchains/xmr/ext/vendor/ResponseParser.js create mode 100644 crypto/blockchains/xmr/ext/vendor/ResponseParser.ts diff --git a/crypto/blockchains/AirDAOTransferDispatcher.ts b/crypto/blockchains/AirDAOTransferDispatcher.ts index 192733aa9..5b8baed65 100644 --- a/crypto/blockchains/AirDAOTransferDispatcher.ts +++ b/crypto/blockchains/AirDAOTransferDispatcher.ts @@ -42,9 +42,9 @@ export namespace AirDAOTransferDispatcher { const CACHE_PROCESSORS: AirDAOTransferDispatcherDict = {} as AirDAOTransferDispatcherDict; - export const getTransferProcessor = function ( + export const getTransferProcessor = ( currencyCode: AirDAODictTypes.Code - ): AirDAOBlockchainTypes.TransferProcessor { + ): AirDAOBlockchainTypes.TransferProcessor => { const currencyDictSettings = AirDAODict.getCurrencyAllSettings(currencyCode); if (typeof CACHE_PROCESSORS[currencyCode] !== 'undefined') { diff --git a/crypto/blockchains/metis/MetisTransferProcessor.ts b/crypto/blockchains/metis/MetisTransferProcessor.ts index b066aedfc..9de78da8a 100644 --- a/crypto/blockchains/metis/MetisTransferProcessor.ts +++ b/crypto/blockchains/metis/MetisTransferProcessor.ts @@ -12,10 +12,12 @@ export default class MetisTransferProcessor extends EthTransferProcessor implements AirDAOBlockchainTypes.TransferProcessor { + private _mainCurrencyCode: string | undefined; + async getFeeRate( data: AirDAOBlockchainTypes.TransferData, privateData: AirDAOBlockchainTypes.TransferPrivateData, - additionalData: {} = {} + additionalData: AirDAOBlockchainTypes.TransferAdditionalData = {} ): Promise { if ( typeof additionalData.gasPrice === 'undefined' || @@ -30,13 +32,14 @@ export default class MetisTransferProcessor let value = 0; try { if (data.amount.indexOf('0x') === 0) { - value = data.amount; + value = parseInt(data.amount, 16); } else { - value = '0x' + BlocksoftUtils.decimalToHex(data.amount); + value = parseInt('0x' + BlocksoftUtils.decimalToHex(data.amount), 16); } - } catch (e) { + } catch (e: any) { throw new Error(e.message + ' with data.amount ' + data.amount); } + const params = { jsonrpc: '2.0', method: 'eth_estimateGas', @@ -44,26 +47,36 @@ export default class MetisTransferProcessor { from: data.addressFrom, to: data.addressTo, - value: value, + value, data: '0x' } ], id: 1 }; - const tmp = await BlocksoftAxios.post( - BlocksoftExternalSettings.getStatic('METIS_SERVER'), - params - ); - if (typeof tmp !== 'undefined' && typeof tmp.data !== 'undefined') { - if (typeof tmp.data.result !== 'undefined') { + try { + // @ts-ignore + // tslint:disable-next-line:no-shadowed-variable + const { data } = await BlocksoftAxios.post( + BlocksoftExternalSettings.getStatic('METIS_SERVER'), + params + ); + + if (typeof data !== 'undefined' && typeof data.result !== 'undefined') { + // @ts-ignore additionalData.gasLimit = BlocksoftUtils.hexToDecimalWalletConnect( - tmp.data.result + data.result ); - } else if (typeof tmp.data.error !== 'undefined') { - throw new Error(tmp.data.error.message); + } else if ( + typeof data !== 'undefined' && + typeof data.error !== 'undefined' + ) { + throw new Error(data.error.message); } + } catch (error: any) { + throw new Error('Error fetching gas estimate: ' + error.message); } + return super.getFeeRate(data, privateData, additionalData); } diff --git a/crypto/blockchains/one/ext/OneUtils.ts b/crypto/blockchains/one/ext/OneUtils.ts index 97db1f3d2..e38674855 100644 --- a/crypto/blockchains/one/ext/OneUtils.ts +++ b/crypto/blockchains/one/ext/OneUtils.ts @@ -8,7 +8,7 @@ const CHARSET = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l'; const GENERATOR = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3]; -const polymod = (values) => { +const polymod = (values: string | any[] | Buffer) => { let chk = 1; // tslint:disable-next-line for (let p = 0; p < values.length; ++p) { @@ -36,7 +36,7 @@ const hrpExpand = (hrp: string) => { return Buffer.from(ret); }; -function createChecksum(hrp: string, data) { +function createChecksum(hrp: string, data: Uint8Array) { const values = Buffer.concat([ Buffer.from(hrpExpand(hrp)), data, @@ -51,7 +51,7 @@ function createChecksum(hrp: string, data) { return Buffer.from(ret); } -const bech32Encode = (hrp: string, data) => { +const bech32Encode = (hrp: string, data: Buffer | Uint8Array) => { const combined = Buffer.concat([data, createChecksum(hrp, data)]); let ret = hrp + '1'; // tslint:disable-next-line @@ -61,7 +61,12 @@ const bech32Encode = (hrp: string, data) => { return ret; }; -const convertBits = (data, fromWidth: number, toWidth: number, pad = true) => { +const convertBits = ( + data: string | any[] | Buffer, + fromWidth: number, + toWidth: number, + pad = true +) => { let acc = 0; let bits = 0; const ret = []; @@ -128,7 +133,7 @@ const bech32Decode = (bechString: string) => { if (!verifyChecksum(hrp, Buffer.from(data))) { return null; } - } catch (e) { + } catch (e: any) { e.message += ' in verifyChecksum'; throw e; } @@ -136,12 +141,12 @@ const bech32Decode = (bechString: string) => { return { hrp, data: Buffer.from(data.slice(0, data.length - 6)) }; }; -function verifyChecksum(hrp: string, data) { +function verifyChecksum(hrp: string, data: Buffer | Uint8Array) { return polymod(Buffer.concat([hrpExpand(hrp), data])) === 1; } const toChecksumAddress = (address: string) => { - if (typeof address !== 'string' || !address.match(/^0x[0-9A-Fa-f]{40}$/)) { + if (!address.match(/^0x[0-9A-Fa-f]{40}$/)) { throw new Error('invalid address ' + address); } @@ -177,7 +182,7 @@ export default { } try { return !!address.match(/^one1[qpzry9x8gf2tvdw0s3jn54khce6mua7l]{38}/); - } catch (e) { + } catch (e: any) { e.message += ' in match - address ' + JSON.stringify(address); throw e; } @@ -203,7 +208,7 @@ export default { let res; try { res = bech32Decode(address); - } catch (e) { + } catch (e: any) { e.message += ' in bech32Decode - address ' + JSON.stringify(address); throw e; } @@ -227,7 +232,7 @@ export default { try { const tmp = toChecksumAddress('0x' + buf.toString('hex')); return tmp; - } catch (e) { + } catch (e: any) { e.message += ' in toChecksumAddress'; throw e; } diff --git a/crypto/blockchains/waves/providers/WavesTransactionsProvider.ts b/crypto/blockchains/waves/providers/WavesTransactionsProvider.ts index c04bd02f7..37d47a3d9 100644 --- a/crypto/blockchains/waves/providers/WavesTransactionsProvider.ts +++ b/crypto/blockchains/waves/providers/WavesTransactionsProvider.ts @@ -46,15 +46,23 @@ export default class WavesTransactionsProvider { const link = `${_apiPath}/transactions/address/${address}/limit/100`; const res = await BlocksoftAxios.get(link); - if (!res || !res.data || typeof res.data[0] === 'undefined') { + // @ts-ignore + if (!res || typeof res.data === 'undefined') { + return false; + } + + // @ts-ignore + const data = res.data[0] as { data: any }; + + if (!data) { return false; } CACHE_OF_TRANSACTIONS[mainCurrencyCode][address] = { - data: res.data[0], + data: data.data, time: now }; - return res.data[0]; + return data.data; } } diff --git a/crypto/blockchains/xmr/XmrTransferProcessor.ts b/crypto/blockchains/xmr/XmrTransferProcessor.ts index 9982930e4..d4c4ed085 100644 --- a/crypto/blockchains/xmr/XmrTransferProcessor.ts +++ b/crypto/blockchains/xmr/XmrTransferProcessor.ts @@ -9,6 +9,11 @@ import config from '@constants/config'; import BlocksoftPrettyNumbers from '@crypto/common/AirDAOPrettyNumbers'; import { AirDAOBlockchainTypes } from '@crypto/blockchains/AirDAOBlockchainTypes'; +type ModifiedSendTxResult = { + rawOnly: boolean; + raw: any; +}; + export default class XmrTransferProcessor implements AirDAOBlockchainTypes.TransferProcessor { @@ -33,7 +38,7 @@ export default class XmrTransferProcessor async getFeeRate( data: AirDAOBlockchainTypes.TransferData, privateData: AirDAOBlockchainTypes.TransferPrivateData, - additionalData: {} = {} + additionalData: AirDAOBlockchainTypes.TransferAdditionalData = {} ): Promise { const result: AirDAOBlockchainTypes.FeeRateResult = { selectedFeeIndex: -1 @@ -98,6 +103,7 @@ export default class XmrTransferProcessor mixin: 15, use_dust: true }, + // @ts-ignore false ); @@ -115,11 +121,11 @@ export default class XmrTransferProcessor i ); - // @ts-ignore const fee = await core.createTransaction({ destinations: [ { to_address: data.addressTo, + // @ts-ignore send_amount: data.isTransferAll ? 0 : BlocksoftPrettyNumbers.setCurrencyCode('XMR').makePretty( @@ -132,9 +138,10 @@ export default class XmrTransferProcessor privateViewKey: privViewKey, privateSpendKey: privSpendKey, publicSpendKey: pubSpendKey, - priority: '' + i, + priority: i, nettype: 'MAINNET', unspentOuts, + // @ts-ignore randomOutsCb: (numberOfOuts: number) => { const amounts = []; // tslint:disable-next-line:no-shadowed-variable @@ -296,11 +303,12 @@ export default class XmrTransferProcessor }; } + // @ts-ignore async sendTx( data: AirDAOBlockchainTypes.TransferData, privateData: AirDAOBlockchainTypes.TransferPrivateData, uiData: AirDAOBlockchainTypes.TransferUiData - ): Promise { + ): Promise { if (typeof privateData.privateKey === 'undefined') { throw new Error('XMR transaction required privateKey'); } @@ -390,21 +398,21 @@ export default class XmrTransferProcessor return { rawOnly: uiData.selectedFee.rawOnly, raw: rawTxHex }; } - const send = await this.sendProvider.send({ + const send = { address: data.addressFrom, tx: rawTxHex, privViewKey, - secretTxKey, usingOuts, unspentsProvider: this.unspentsProvider - }); + }; // @ts-ignore BlocksoftCryptoLog.log( - this._settings.currencyCode + ' XmrTransferProcessor.sendTx result', - send + `${this._settings.currencyCode} + ' XmrTransferProcessor.sendTx result', + ${send}` ); + // @ts-ignore if (send.status === 'OK') { return { transactionHash: rawTxHash, transactionJson: { secretTxKey } }; } else { diff --git a/crypto/blockchains/xmr/ext/MoneroMnemonic.ts b/crypto/blockchains/xmr/ext/MoneroMnemonic.ts index 531c5a721..66918411c 100644 --- a/crypto/blockchains/xmr/ext/MoneroMnemonic.ts +++ b/crypto/blockchains/xmr/ext/MoneroMnemonic.ts @@ -15,6 +15,7 @@ interface MoneroDictionary { monero_words_english_prefix_len: number; } +// tslint:disable-next-line:variable-name const secret_spend_key_to_words = ( secretSpendKeyBufferOrHex: SecretSpendKey, walletHash: string @@ -71,6 +72,7 @@ const secret_spend_key_to_words = ( const crc32Decimal = BlocksoftUtils.hexToDecimal( '0x' + crc32Res.toString('hex') ); + // @ts-ignore seed.push(seed[crc32Decimal % 24]); return seed.join(' '); diff --git a/crypto/blockchains/xmr/ext/MoneroUtilsParser.oldAndroid.ts b/crypto/blockchains/xmr/ext/MoneroUtilsParser.oldAndroid.ts index d993cef0a..991137e7d 100644 --- a/crypto/blockchains/xmr/ext/MoneroUtilsParser.oldAndroid.ts +++ b/crypto/blockchains/xmr/ext/MoneroUtilsParser.oldAndroid.ts @@ -5,7 +5,7 @@ */ export default { - checkDestination(value) { + checkDestination(value: any) { return false; }, @@ -18,21 +18,21 @@ export default { }, async parseAddressInfo( - address, - data, - privViewKey, - pubSpendKey, - privSpendKey + address: string, + data: {}, + privViewKey: string, + pubSpendKey: string, + privSpendKey: string ) { return false; }, async parseAddressTransactions( - address, - data, - privViewKey, - pubSpendKey, - privSpendKey + address: string, + data: {}, + privViewKey: string, + pubSpendKey: string, + privSpendKey: string ) { return false; } diff --git a/crypto/blockchains/xmr/ext/vendor/ResponseParser.js b/crypto/blockchains/xmr/ext/vendor/ResponseParser.js deleted file mode 100644 index c083f280f..000000000 --- a/crypto/blockchains/xmr/ext/vendor/ResponseParser.js +++ /dev/null @@ -1,333 +0,0 @@ -// Copyright (c) 2014-2019, MyMonero.com -// -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without modification, are -// permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this list of -// conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, this list -// of conditions and the following disclaimer in the documentation and/or other -// materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its contributors may be -// used to endorse or promote products derived from this software without specific -// prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY -// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL -// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, -// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// -'use strict' -// -const JSBigInt = require('@mymonero/mymonero-bigint').BigInteger -const monero_amount_format_utils = require('@mymonero/mymonero-money-format') -const monero_keyImage_cache_utils = require('./KeyimageCache') // require('@mymonero/mymonero-keyimage-cache') -// -async function Parsed_AddressInfo__sync ( - keyImage_cache, - data, - address, - view_key__private, - spend_key__public, - spend_key__private, - coreBridge_instance -) { - // -> returnValuesByKey - const total_received = new JSBigInt(data.total_received || 0) - const locked_balance = new JSBigInt(data.locked_funds || 0) - var total_sent = new JSBigInt(data.total_sent || 0) // will be modified in place - // - const account_scanned_tx_height = data.scanned_height || 0 - const account_scanned_block_height = data.scanned_block_height || 0 - const account_scan_start_height = data.start_height || 0 - const transaction_height = data.transaction_height || 0 - const blockchain_height = data.blockchain_height || 0 - const spent_outputs = data.spent_outputs || [] - // - for (let spent_output of spent_outputs) { - var key_image = await monero_keyImage_cache_utils.Lazy_KeyImage( - keyImage_cache, - spent_output.tx_pub_key, - spent_output.out_index, - address, - view_key__private, - spend_key__public, - spend_key__private, - coreBridge_instance - ) - if (spent_output.key_image !== key_image) { - // console.log('💬 Output used as mixin (' + spent_output.key_image + '/' + key_image + ')') - total_sent = new JSBigInt(total_sent).subtract(spent_output.amount) - } - } - // - const ratesBySymbol = data.rates || {} // jic it's not there - // - const returnValuesByKey = { - total_received_String: total_received - ? total_received.toString() - : null, - locked_balance_String: locked_balance - ? locked_balance.toString() - : null, - total_sent_String: total_sent ? total_sent.toString() : null, - // ^serialized JSBigInt - spent_outputs: spent_outputs, - account_scanned_tx_height: account_scanned_tx_height, - account_scanned_block_height: account_scanned_block_height, - account_scan_start_height: account_scan_start_height, - transaction_height: transaction_height, - blockchain_height: blockchain_height, - // - ratesBySymbol: ratesBySymbol - } - return returnValuesByKey -} -async function Parsed_AddressInfo__sync__keyImageManaged ( - data, - address, - view_key__private, - spend_key__public, - spend_key__private, - coreBridge_instance -) { - // -> returnValuesByKey - const keyImageCache = monero_keyImage_cache_utils.Lazy_KeyImageCacheForWalletWith( - address - ) - return Parsed_AddressInfo__sync( - keyImageCache, - data, - address, - view_key__private, - spend_key__public, - spend_key__private, - coreBridge_instance - ) -} -async function Parsed_AddressInfo ( - keyImage_cache, - data, - address, - view_key__private, - spend_key__public, - spend_key__private, - coreBridge_instance, - fn // (err?, returnValuesByKey) -> Void -) { - const returnValuesByKey = await Parsed_AddressInfo__sync( - keyImage_cache, - data, - address, - view_key__private, - spend_key__public, - spend_key__private, - coreBridge_instance - ) - fn(null, returnValuesByKey) -} -async function Parsed_AddressInfo__keyImageManaged ( - data, - address, - view_key__private, - spend_key__public, - spend_key__private, - coreBridge_instance, - fn -) { - // -> returnValuesByKey - await Parsed_AddressInfo( - monero_keyImage_cache_utils.Lazy_KeyImageCacheForWalletWith(address), - data, - address, - view_key__private, - spend_key__public, - spend_key__private, - coreBridge_instance, - fn - ) -} -exports.Parsed_AddressInfo = Parsed_AddressInfo -exports.Parsed_AddressInfo__keyImageManaged = Parsed_AddressInfo__keyImageManaged // in case you can't send a mutable key image cache dictionary -exports.Parsed_AddressInfo__sync__keyImageManaged = Parsed_AddressInfo__sync__keyImageManaged // in case you can't send a mutable key image cache dictionary -exports.Parsed_AddressInfo__sync = Parsed_AddressInfo__sync -// -async function Parsed_AddressTransactions ( - keyImage_cache, - data, - address, - view_key__private, - spend_key__public, - spend_key__private, - coreBridge_instance, - fn // (err?, returnValuesByKey) -> Void -) { - const returnValuesByKey = await Parsed_AddressTransactions__sync( - keyImage_cache, - data, - address, - view_key__private, - spend_key__public, - spend_key__private, - coreBridge_instance - ) - fn(null, returnValuesByKey) -} -async function Parsed_AddressTransactions__sync ( - keyImage_cache, - data, - address, - view_key__private, - spend_key__public, - spend_key__private, - coreBridge_instance -) { - const account_scanned_height = data.scanned_height || 0 - const account_scanned_block_height = data.scanned_block_height || 0 - const account_scan_start_height = data.start_height || 0 - const transaction_height = data.transaction_height || 0 - const blockchain_height = data.blockchain_height || 0 - // - const transactions = data.transactions || [] - // - // TODO: rewrite this with more clarity if possible - for (let i = 0; i < transactions.length; ++i) { - if ((transactions[i].spent_outputs || []).length > 0) { - for (var j = 0; j < transactions[i].spent_outputs.length; ++j) { - var key_image = await monero_keyImage_cache_utils.Lazy_KeyImage( - keyImage_cache, - transactions[i].spent_outputs[j].tx_pub_key, - transactions[i].spent_outputs[j].out_index, - address, - view_key__private, - spend_key__public, - spend_key__private, - coreBridge_instance - ) - if (transactions[i].spent_outputs[j].key_image !== key_image) { - // console.log('Output used as mixin, ignoring (' + transactions[i].spent_outputs[j].key_image + '/' + key_image + ')') - transactions[i].total_sent = new JSBigInt( - transactions[i].total_sent - ) - .subtract(transactions[i].spent_outputs[j].amount) - .toString() - transactions[i].spent_outputs.splice(j, 1) - j-- - } - } - } - if ( - new JSBigInt(transactions[i].total_received || 0) - .add(transactions[i].total_sent || 0) - .compare(0) <= 0 - ) { - transactions.splice(i, 1) - i-- - continue - } - transactions[i].amount = new JSBigInt( - transactions[i].total_received || 0 - ) - .subtract(transactions[i].total_sent || 0) - .toString() - transactions[i].approx_float_amount = parseFloat( - monero_amount_format_utils.formatMoney(transactions[i].amount) - ) - transactions[i].timestamp = transactions[i].timestamp - const record__payment_id = transactions[i].payment_id - if (typeof record__payment_id !== 'undefined' && record__payment_id) { - if (record__payment_id.length == 16) { - // short (encrypted) pid - if (transactions[i].approx_float_amount < 0) { - // outgoing - delete transactions[i]['payment_id'] // need to filter these out .. because the server can't filter out short (encrypted) pids on outgoing txs - } - } - } - } - transactions.sort(function (a, b) { - if (a.mempool == true) { - if (b.mempool != true) { - return -1 // a first - } - // both mempool - fall back to .id compare - } else if (b.mempool == true) { - return 1 // b first - } - return b.id - a.id - }) - // prepare transactions to be serialized - for (let transaction of transactions) { - transaction.amount = transaction.amount.toString() // JSBigInt -> String - if ( - typeof transaction.total_sent !== 'undefined' && - transaction.total_sent !== null - ) { - transaction.total_sent = transaction.total_sent.toString() - } - } - // on the other side, we convert transactions timestamp to Date obj - const returnValuesByKey = { - account_scanned_height: account_scanned_height, - account_scanned_block_height: account_scanned_block_height, - account_scan_start_height: account_scan_start_height, - transaction_height: transaction_height, - blockchain_height: blockchain_height, - serialized_transactions: transactions - } - return returnValuesByKey -} -async function Parsed_AddressTransactions__sync__keyImageManaged ( - data, - address, - view_key__private, - spend_key__public, - spend_key__private, - coreBridge_instance -) { - const keyImageCache = monero_keyImage_cache_utils.Lazy_KeyImageCacheForWalletWith( - address - ) - return Parsed_AddressTransactions__sync( - keyImageCache, - data, - address, - view_key__private, - spend_key__public, - spend_key__private, - coreBridge_instance - ) -} -async function Parsed_AddressTransactions__keyImageManaged ( - data, - address, - view_key__private, - spend_key__public, - spend_key__private, - coreBridge_instance, - fn -) { - await Parsed_AddressTransactions( - monero_keyImage_cache_utils.Lazy_KeyImageCacheForWalletWith(address), - data, - address, - view_key__private, - spend_key__public, - spend_key__private, - coreBridge_instance, - fn - ) -} -exports.Parsed_AddressTransactions = Parsed_AddressTransactions -exports.Parsed_AddressTransactions__keyImageManaged = Parsed_AddressTransactions__keyImageManaged -exports.Parsed_AddressTransactions__sync = Parsed_AddressTransactions__sync -exports.Parsed_AddressTransactions__sync__keyImageManaged = Parsed_AddressTransactions__sync__keyImageManaged diff --git a/crypto/blockchains/xmr/ext/vendor/ResponseParser.ts b/crypto/blockchains/xmr/ext/vendor/ResponseParser.ts new file mode 100644 index 000000000..f1f76f3c0 --- /dev/null +++ b/crypto/blockchains/xmr/ext/vendor/ResponseParser.ts @@ -0,0 +1,327 @@ +// Copyright (c) 2014-2019, MyMonero.com +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +'use strict'; +// +const JSBigInt = require('@mymonero/mymonero-bigint').BigInteger; +const monero_amount_format_utils = require('@mymonero/mymonero-money-format'); +const monero_keyImage_cache_utils = require('./KeyimageCache'); // require('@mymonero/mymonero-keyimage-cache') +// +async function Parsed_AddressInfo__sync( + keyImage_cache, + data, + address, + view_key__private, + spend_key__public, + spend_key__private, + coreBridge_instance +) { + // -> returnValuesByKey + const total_received = new JSBigInt(data.total_received || 0); + const locked_balance = new JSBigInt(data.locked_funds || 0); + var total_sent = new JSBigInt(data.total_sent || 0); // will be modified in place + // + const account_scanned_tx_height = data.scanned_height || 0; + const account_scanned_block_height = data.scanned_block_height || 0; + const account_scan_start_height = data.start_height || 0; + const transaction_height = data.transaction_height || 0; + const blockchain_height = data.blockchain_height || 0; + const spent_outputs = data.spent_outputs || []; + // + for (let spent_output of spent_outputs) { + var key_image = await monero_keyImage_cache_utils.Lazy_KeyImage( + keyImage_cache, + spent_output.tx_pub_key, + spent_output.out_index, + address, + view_key__private, + spend_key__public, + spend_key__private, + coreBridge_instance + ); + if (spent_output.key_image !== key_image) { + // console.log('💬 Output used as mixin (' + spent_output.key_image + '/' + key_image + ')') + total_sent = new JSBigInt(total_sent).subtract(spent_output.amount); + } + } + // + const ratesBySymbol = data.rates || {}; // jic it's not there + // + const returnValuesByKey = { + total_received_String: total_received ? total_received.toString() : null, + locked_balance_String: locked_balance ? locked_balance.toString() : null, + total_sent_String: total_sent ? total_sent.toString() : null, + // ^serialized JSBigInt + spent_outputs: spent_outputs, + account_scanned_tx_height: account_scanned_tx_height, + account_scanned_block_height: account_scanned_block_height, + account_scan_start_height: account_scan_start_height, + transaction_height: transaction_height, + blockchain_height: blockchain_height, + // + ratesBySymbol: ratesBySymbol + }; + return returnValuesByKey; +} +async function Parsed_AddressInfo__sync__keyImageManaged( + data, + address, + view_key__private, + spend_key__public, + spend_key__private, + coreBridge_instance +) { + // -> returnValuesByKey + const keyImageCache = + monero_keyImage_cache_utils.Lazy_KeyImageCacheForWalletWith(address); + return Parsed_AddressInfo__sync( + keyImageCache, + data, + address, + view_key__private, + spend_key__public, + spend_key__private, + coreBridge_instance + ); +} +async function Parsed_AddressInfo( + keyImage_cache, + data, + address, + view_key__private, + spend_key__public, + spend_key__private, + coreBridge_instance, + fn // (err?, returnValuesByKey) -> Void +) { + const returnValuesByKey = await Parsed_AddressInfo__sync( + keyImage_cache, + data, + address, + view_key__private, + spend_key__public, + spend_key__private, + coreBridge_instance + ); + fn(null, returnValuesByKey); +} +async function Parsed_AddressInfo__keyImageManaged( + data, + address, + view_key__private, + spend_key__public, + spend_key__private, + coreBridge_instance, + fn +) { + // -> returnValuesByKey + await Parsed_AddressInfo( + monero_keyImage_cache_utils.Lazy_KeyImageCacheForWalletWith(address), + data, + address, + view_key__private, + spend_key__public, + spend_key__private, + coreBridge_instance, + fn + ); +} +exports.Parsed_AddressInfo = Parsed_AddressInfo; +exports.Parsed_AddressInfo__keyImageManaged = + Parsed_AddressInfo__keyImageManaged; // in case you can't send a mutable key image cache dictionary +exports.Parsed_AddressInfo__sync__keyImageManaged = + Parsed_AddressInfo__sync__keyImageManaged; // in case you can't send a mutable key image cache dictionary +exports.Parsed_AddressInfo__sync = Parsed_AddressInfo__sync; +// +async function Parsed_AddressTransactions( + keyImage_cache, + data, + address, + view_key__private, + spend_key__public, + spend_key__private, + coreBridge_instance, + fn // (err?, returnValuesByKey) -> Void +) { + const returnValuesByKey = await Parsed_AddressTransactions__sync( + keyImage_cache, + data, + address, + view_key__private, + spend_key__public, + spend_key__private, + coreBridge_instance + ); + fn(null, returnValuesByKey); +} +async function Parsed_AddressTransactions__sync( + keyImage_cache, + data, + address, + view_key__private, + spend_key__public, + spend_key__private, + coreBridge_instance +) { + const account_scanned_height = data.scanned_height || 0; + const account_scanned_block_height = data.scanned_block_height || 0; + const account_scan_start_height = data.start_height || 0; + const transaction_height = data.transaction_height || 0; + const blockchain_height = data.blockchain_height || 0; + // + const transactions = data.transactions || []; + // + // TODO: rewrite this with more clarity if possible + for (let i = 0; i < transactions.length; ++i) { + if ((transactions[i].spent_outputs || []).length > 0) { + for (var j = 0; j < transactions[i].spent_outputs.length; ++j) { + var key_image = await monero_keyImage_cache_utils.Lazy_KeyImage( + keyImage_cache, + transactions[i].spent_outputs[j].tx_pub_key, + transactions[i].spent_outputs[j].out_index, + address, + view_key__private, + spend_key__public, + spend_key__private, + coreBridge_instance + ); + if (transactions[i].spent_outputs[j].key_image !== key_image) { + // console.log('Output used as mixin, ignoring (' + transactions[i].spent_outputs[j].key_image + '/' + key_image + ')') + transactions[i].total_sent = new JSBigInt(transactions[i].total_sent) + .subtract(transactions[i].spent_outputs[j].amount) + .toString(); + transactions[i].spent_outputs.splice(j, 1); + j--; + } + } + } + if ( + new JSBigInt(transactions[i].total_received || 0) + .add(transactions[i].total_sent || 0) + .compare(0) <= 0 + ) { + transactions.splice(i, 1); + i--; + continue; + } + transactions[i].amount = new JSBigInt(transactions[i].total_received || 0) + .subtract(transactions[i].total_sent || 0) + .toString(); + transactions[i].approx_float_amount = parseFloat( + monero_amount_format_utils.formatMoney(transactions[i].amount) + ); + transactions[i].timestamp = transactions[i].timestamp; + const record__payment_id = transactions[i].payment_id; + if (typeof record__payment_id !== 'undefined' && record__payment_id) { + if (record__payment_id.length == 16) { + // short (encrypted) pid + if (transactions[i].approx_float_amount < 0) { + // outgoing + delete transactions[i]['payment_id']; // need to filter these out .. because the server can't filter out short (encrypted) pids on outgoing txs + } + } + } + } + transactions.sort((a, b) => { + if (a.mempool == true) { + if (b.mempool != true) { + return -1; // a first + } + // both mempool - fall back to .id compare + } else if (b.mempool == true) { + return 1; // b first + } + return b.id - a.id; + }); + // prepare transactions to be serialized + for (let transaction of transactions) { + transaction.amount = transaction.amount.toString(); // JSBigInt -> String + if ( + typeof transaction.total_sent !== 'undefined' && + transaction.total_sent !== null + ) { + transaction.total_sent = transaction.total_sent.toString(); + } + } + // on the other side, we convert transactions timestamp to Date obj + const returnValuesByKey = { + account_scanned_height: account_scanned_height, + account_scanned_block_height: account_scanned_block_height, + account_scan_start_height: account_scan_start_height, + transaction_height: transaction_height, + blockchain_height: blockchain_height, + serialized_transactions: transactions + }; + return returnValuesByKey; +} +async function Parsed_AddressTransactions__sync__keyImageManaged( + data, + address, + view_key__private, + spend_key__public, + spend_key__private, + coreBridge_instance +) { + const keyImageCache = + monero_keyImage_cache_utils.Lazy_KeyImageCacheForWalletWith(address); + return Parsed_AddressTransactions__sync( + keyImageCache, + data, + address, + view_key__private, + spend_key__public, + spend_key__private, + coreBridge_instance + ); +} +async function Parsed_AddressTransactions__keyImageManaged( + data, + address, + view_key__private, + spend_key__public, + spend_key__private, + coreBridge_instance, + fn +) { + await Parsed_AddressTransactions( + monero_keyImage_cache_utils.Lazy_KeyImageCacheForWalletWith(address), + data, + address, + view_key__private, + spend_key__public, + spend_key__private, + coreBridge_instance, + fn + ); +} +exports.Parsed_AddressTransactions = Parsed_AddressTransactions; +exports.Parsed_AddressTransactions__keyImageManaged = + Parsed_AddressTransactions__keyImageManaged; +exports.Parsed_AddressTransactions__sync = Parsed_AddressTransactions__sync; +exports.Parsed_AddressTransactions__sync__keyImageManaged = + Parsed_AddressTransactions__sync__keyImageManaged; diff --git a/package.json b/package.json index 38a9a2910..27e1afc28 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "@react-navigation/native": "^6.1.6", "@react-navigation/native-stack": "^6.9.12", "@shopify/react-native-skia": "0.1.183", - "@solana/web3.js": "git+https://git@github.com/trustee-wallet/solana-web3.js.git", + "@solana/web3.js": "^1.78.2", "@tanstack/react-query": "^4.28.0", "@testing-library/jest-dom": "^5.16.5", "@testing-library/react-native": "^12.1.1", @@ -54,8 +54,8 @@ "bn.js": "^5.2.1", "bs58check": "^3.0.1", "buffer": "^6.0.3", - "coinselect": "^3.1.13", "buffer-crc32": "^0.2.13", + "coinselect": "^3.1.13", "create-hash": "^1.2.0", "elliptic": "^6.5.4", "ethereumjs-util": "^7.1.5", @@ -104,10 +104,12 @@ "react-native-view-shot": "3.5.0", "react-native-walkthrough-tooltip": "^1.5.0", "ripple-lib": "^1.10.1", + "stellar-sdk": "^10.4.1", + "thorify": "^1.6.2", + "tiny-secp256k1": "^2.2.3", "use-context-selector": "^1.4.1", "web3": "^4.0.3", - "web3-utils": "^4.0.3", - "tiny-secp256k1": "^2.2.3" + "web3-utils": "^4.0.3" }, "devDependencies": { "@babel/core": "^7.20.0", diff --git a/src/database/Database.ts b/src/database/Database.ts index 5a1709c3b..fbd2b4720 100644 --- a/src/database/Database.ts +++ b/src/database/Database.ts @@ -7,6 +7,7 @@ import LocalStorage from '@nozbe/watermelondb/Database/LocalStorage'; class Database { private db: WDB | undefined; + private tableName: string | undefined; constructor() { this.init(); @@ -102,6 +103,25 @@ class Database { async unsafeRawQuery(table: DatabaseTable, query: string) { return await database.get(table).query(Q.unsafeSqlQuery(query)).fetch(); } + + // setTableName(tableName: string) { + // this.tableName = tableName; + // } + // + // async performTableOperation( + // tableName: DatabaseTable, + // operation: (table: any) => void + // ) { + // if (!this.db) this.init(); + // try { + // await this.db!.write(async () => { + // const table = this.db!.get(tableName); + // operation(table); + // }); + // } catch (error) { + // console.log(error); // TODO handle errors + // } + // } } export default new Database(); diff --git a/yarn.lock b/yarn.lock index c2b809103..d6593af34 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1374,7 +1374,7 @@ dependencies: regenerator-runtime "^0.13.11" -"@babel/runtime@^7.12.5", "@babel/runtime@^7.17.2": +"@babel/runtime@^7.17.2", "@babel/runtime@^7.22.6": version "7.22.6" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.6.tgz#57d64b9ae3cff1d67eb067ae117dac087f5bd438" integrity sha512-wDb5pWm4WDdF6LFUde3Jl8WzPA+3ZbxYqkC6xAXuD3irdEHN1k0NfTRrJD8ZD378SJ61miMLCqIOXYhd8x+AJQ== @@ -2483,14 +2483,14 @@ dependencies: crypto-js "^3.1.9-1" -"@noble/curves@1.1.0", "@noble/curves@~1.1.0": +"@noble/curves@1.1.0", "@noble/curves@^1.0.0", "@noble/curves@~1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.1.0.tgz#f13fc667c89184bc04cccb9b11e8e7bae27d8c3d" integrity sha512-091oBExgENk/kGj3AZmtBDMpxQPDtxQABR2B9lb1JbVTs6ytdzZNwvhxQ4MWasRNEzlbEH8jCWFCwhF/Obj5AA== dependencies: "@noble/hashes" "1.3.1" -"@noble/hashes@1.3.1", "@noble/hashes@^1.2.0", "@noble/hashes@~1.3.0", "@noble/hashes@~1.3.1": +"@noble/hashes@1.3.1", "@noble/hashes@^1.2.0", "@noble/hashes@^1.3.0", "@noble/hashes@~1.3.0", "@noble/hashes@~1.3.1": version "1.3.1" resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.1.tgz#8831ef002114670c603c458ab8b11328406953a9" integrity sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA== @@ -2896,31 +2896,33 @@ dependencies: "@sinonjs/commons" "^2.0.0" -"@solana/buffer-layout@^3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@solana/buffer-layout/-/buffer-layout-3.0.0.tgz#b9353caeb9a1589cb77a1b145bcb1a9a93114326" - integrity sha512-MVdgAKKL39tEs0l8je0hKaXLQFb7Rdfb0Xg2LjFZd8Lfdazkg6xiS98uAZrEKvaoF3i4M95ei9RydkGIDMeo3w== +"@solana/buffer-layout@^4.0.0": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@solana/buffer-layout/-/buffer-layout-4.0.1.tgz#b996235eaec15b1e0b5092a8ed6028df77fa6c15" + integrity sha512-E1ImOIAD1tBZFRdjeM4/pzTiTApC0AOBGwyAMS4fwIodCWArzJ3DWdoh8cKxeFM2fElkxBh2Aqts1BPC373rHA== dependencies: buffer "~6.0.3" -"@solana/web3.js@git+https://git@github.com/trustee-wallet/solana-web3.js.git": - version "0.0.0-development" - resolved "git+https://git@github.com/trustee-wallet/solana-web3.js.git#626665056389147825af2a9f982b59eec3207051" - dependencies: - "@babel/runtime" "^7.12.5" - "@solana/buffer-layout" "^3.0.0" - bn.js "^5.0.0" - borsh "^0.4.0" +"@solana/web3.js@^1.78.2": + version "1.78.2" + resolved "https://registry.yarnpkg.com/@solana/web3.js/-/web3.js-1.78.2.tgz#ae976ce2f793077aa104ab455f9f75be86a3e2a4" + integrity sha512-oF+TmBZCt3eAEl4Meu3GO2p6G8wdyoKgXgTKzQpIUIhpMGA/dVQzyMFpKjCgoTU1Kx+/UF3gXUdsZOxQukGbvQ== + dependencies: + "@babel/runtime" "^7.22.6" + "@noble/curves" "^1.0.0" + "@noble/hashes" "^1.3.0" + "@solana/buffer-layout" "^4.0.0" + agentkeepalive "^4.2.1" + bigint-buffer "^1.1.5" + bn.js "^5.2.1" + borsh "^0.7.0" bs58 "^4.0.1" - buffer "6.0.1" - crypto-hash "^1.2.2" - jayson "^3.4.4" - js-sha3 "^0.8.0" - node-fetch "^2.6.1" - rpc-websockets "^7.4.2" - secp256k1 "^4.0.2" + buffer "6.0.3" + fast-stable-stringify "^1.0.0" + jayson "^4.1.0" + node-fetch "^2.6.12" + rpc-websockets "^7.5.1" superstruct "^0.14.2" - tweetnacl "^1.0.0" "@tanstack/query-core@4.27.0": version "4.27.0" @@ -2995,13 +2997,6 @@ dependencies: "@babel/types" "^7.3.0" -"@types/bn.js@^4.11.5": - version "4.11.6" - resolved "https://registry.yarnpkg.com/@types/bn.js/-/bn.js-4.11.6.tgz#c306c70d9358aaea33cd4eda092a742b9505967c" - integrity sha512-pqr857jrp2kPuO9uRjZ3PwnJTjoQy+fcdxvBTvHm6dkmEL9q+hDD/2j/0ELOBPtPnS8LjCX0gI9nbl8lVkadpg== - dependencies: - "@types/node" "*" - "@types/bn.js@^5.1.0": version "5.1.1" resolved "https://registry.yarnpkg.com/@types/bn.js/-/bn.js-5.1.1.tgz#b51e1b55920a4ca26e9285ff79936bbdec910682" @@ -3016,6 +3011,11 @@ dependencies: "@types/node" "*" +"@types/eventsource@^1.1.2": + version "1.1.11" + resolved "https://registry.yarnpkg.com/@types/eventsource/-/eventsource-1.1.11.tgz#a2c0bfd0436b7db42ed1b2b2117f7ec2e8478dc7" + integrity sha512-L7wLDZlWm5mROzv87W0ofIYeQP5K2UhoFnnUyEWLKM6UBb0ZNRgAqp98qE5DkgfBXdWfc2kYmw9KZm4NLjRbsw== + "@types/graceful-fs@^4.1.3": version "4.1.6" resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.6.tgz#e14b2576a1c25026b7f02ede1de3b84c3a1efeae" @@ -3094,6 +3094,16 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-12.7.5.tgz#e19436e7f8e9b4601005d73673b6dc4784ffcc2f" integrity sha512-9fq4jZVhPNW8r+UYKnxF1e2HkDWOWKM5bC2/7c9wPV835I0aOrVbS/Hw/pWPk2uKrNXQqg9Z959Kz+IYDd5p3w== +"@types/node@>= 8": + version "20.4.7" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.4.7.tgz#74d323a93f1391a63477b27b9aec56669c98b2ab" + integrity sha512-bUBrPjEry2QUTsnuEjzjbS7voGWCc30W0qzgMf90GPeDGFRakvrz47ju+oqDAKCXLUCe39u57/ORMl/O/04/9g== + +"@types/node@^10.3.2": + version "10.17.60" + resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.60.tgz#35f3d6213daed95da7f0f73e75bcc6980e90597b" + integrity sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw== + "@types/node@^12.12.54": version "12.20.55" resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.55.tgz#c329cbd434c42164f846b909bd6f85b5537f6240" @@ -3130,6 +3140,13 @@ resolved "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz" integrity sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w== +"@types/randombytes@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@types/randombytes/-/randombytes-2.0.0.tgz#0087ff5e60ae68023b9bc4398b406fea7ad18304" + integrity sha512-bz8PhAVlwN72vqefzxa14DKNT8jK/mV66CSjwdVQM/k3Th3EPKfUtdMniwZgMedQTFuywAsfjnZsg+pEnltaMA== + dependencies: + "@types/node" "*" + "@types/react-native-calendar-picker@^7.0.3": version "7.0.3" resolved "https://registry.npmjs.org/@types/react-native-calendar-picker/-/react-native-calendar-picker-7.0.3.tgz" @@ -3194,6 +3211,11 @@ resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-4.0.2.tgz#6286b4c7228d58ab7866d19716f3696e03a09397" integrity sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw== +"@types/urijs@^1.19.6": + version "1.19.19" + resolved "https://registry.yarnpkg.com/@types/urijs/-/urijs-1.19.19.tgz#2789369799907fc11e2bc6e3a00f6478c2281b95" + integrity sha512-FDJNkyhmKLw7uEvTxx5tSXfPeQpO0iy73Ry+PmYZJvQy0QIWX8a7kJ4kLWRf+EbTPJEPDSgPXHaM7pzr5lmvCg== + "@types/ws@^7.2.0", "@types/ws@^7.4.4": version "7.4.7" resolved "https://registry.yarnpkg.com/@types/ws/-/ws-7.4.7.tgz#f7c390a36f7a0679aa69de2d501319f4f8d9b702" @@ -3342,6 +3364,22 @@ "@urql/core" ">=2.3.1" wonka "^4.0.14" +"@vechain/ethers@^4.0.27-3": + version "4.0.27-5" + resolved "https://registry.yarnpkg.com/@vechain/ethers/-/ethers-4.0.27-5.tgz#2e7d40294b2e14ddf4cf6f6094bbdc871e26e299" + integrity sha512-dR+rTUauPJpqHNBdEgV6Xh+o009uCRPCvN2HWYIAzZP2SvgsPHLxNUzeRbRKhNzz/HC8HjWNvECRxODF88B03Q== + dependencies: + "@types/node" "^10.3.2" + aes-js "3.0.0" + bn.js "^4.4.0" + elliptic "6.5.4" + hash.js "1.1.3" + js-sha3 "0.5.7" + scrypt-js "2.0.4" + setimmediate "1.0.4" + uuid "2.0.1" + xmlhttprequest "1.8.0" + "@waves/ts-lib-crypto@^1.4.4-beta.1": version "1.4.4-beta.1" resolved "https://registry.yarnpkg.com/@waves/ts-lib-crypto/-/ts-lib-crypto-1.4.4-beta.1.tgz#5a494f5ff3128b2f80c1890b1fb13d8ba2cb8c53" @@ -3416,6 +3454,11 @@ acorn@^8.1.0, acorn@^8.5.0, acorn@^8.8.0, acorn@^8.8.1: resolved "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz" integrity sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw== +aes-js@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/aes-js/-/aes-js-3.0.0.tgz#e21df10ad6c2053295bcbb8dab40b09dbea87e4d" + integrity sha512-H7wUZRn8WpTq9jocdxQ2c8x2sKo9ZVmzfRE13GiNJXfp7NcKYEdvl3vspKjXox6RIG2VtaRe4JFvxG4rqp2Zuw== + agent-base@6: version "6.0.2" resolved "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz" @@ -3423,6 +3466,15 @@ agent-base@6: dependencies: debug "4" +agentkeepalive@^4.2.1: + version "4.3.0" + resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-4.3.0.tgz#bb999ff07412653c1803b3ced35e50729830a255" + integrity sha512-7Epl1Blf4Sy37j4v9f9FjICCh4+KAQOyXgHEwlyBiAQLbhKdq/i2QQU3amQalS/wPhdPzDXPL5DMR5bkn+YeWg== + dependencies: + debug "^4.1.0" + depd "^2.0.0" + humanize-ms "^1.2.1" + aggregate-error@^3.0.0: version "3.1.0" resolved "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz" @@ -3712,6 +3764,13 @@ available-typed-arrays@^1.0.5: resolved "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz" integrity sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw== +axios@0.25.0: + version "0.25.0" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.25.0.tgz#349cfbb31331a9b4453190791760a8d35b093e0a" + integrity sha512-cD8FOb0tRH3uuEe6+evtAbgJtfxr7ly3fQjYcMcuPlgkwVS9xboaVIpcDV+cYQe+yGykgwZCs1pzjntcGa6l5g== + dependencies: + follow-redirects "^1.14.7" + axios@^1.3.4: version "1.3.4" resolved "https://registry.npmjs.org/axios/-/axios-1.3.4.tgz" @@ -3912,6 +3971,11 @@ base-x@^4.0.0: resolved "https://registry.yarnpkg.com/base-x/-/base-x-4.0.0.tgz#d0e3b7753450c73f8ad2389b5c018a4af7b2224a" integrity sha512-FuwxlW4H5kh37X/oW59pwTzzTKRzfrrQwhmyspRM7swOEZcHtDZSCt45U6oKgtuFE+WYPblePMVIPR4RZrh/hw== +base32.js@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/base32.js/-/base32.js-0.1.0.tgz#b582dec693c2f11e893cf064ee6ac5b6131a2202" + integrity sha512-n3TkB02ixgBOhTvANakDb4xaMXnYUVkNoRFJjQflcqMQhyEKxEHdj3E6N8t8sUQ0mjH/3/JxzlXuz3ul/J90pQ== + base64-js@^1.1.2, base64-js@^1.2.3, base64-js@^1.3.0, base64-js@^1.3.1, base64-js@^1.5.1: version "1.5.1" resolved "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz" @@ -3957,6 +4021,23 @@ bigi@^1.1.0, bigi@^1.4.2: resolved "https://registry.yarnpkg.com/bigi/-/bigi-1.4.2.tgz#9c665a95f88b8b08fc05cfd731f561859d725825" integrity sha512-ddkU+dFIuEIW8lE7ZwdIAf2UPoM90eaprg5m3YXAVVTmKlqV/9BX4A2M8BOK2yOq6/VgZFVhK6QAxJebhlbhzw== +bigint-buffer@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/bigint-buffer/-/bigint-buffer-1.1.5.tgz#d038f31c8e4534c1f8d0015209bf34b4fa6dd442" + integrity sha512-trfYco6AoZ+rKhKnxA0hgX0HAbVP/s808/EuDSe2JDzUnCp/xAsli35Orvk67UrTEcwuxZqYZDmfA2RXJgxVvA== + dependencies: + bindings "^1.3.0" + +bignumber.js@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-4.1.0.tgz#db6f14067c140bd46624815a7916c92d9b6c24b1" + integrity sha512-eJzYkFYy9L4JzXsbymsFn3p54D+llV27oTQ+ziJG7WFRheJcNZilgVXMG0LoZtlQSKBsJdWtLFqOD0u+U0jZKA== + +bignumber.js@^7.2.1: + version "7.2.1" + resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-7.2.1.tgz#80c048759d826800807c4bfd521e50edbba57a5f" + integrity sha512-S4XzBk5sMB+Rcb/LNcpzXr57VRTxgAvaAEDAl1AwRx27j00hT84O6OkteE7u8UB3NuaaygCRrEpqox4uDOrbdQ== + bignumber.js@^9.0.0, bignumber.js@^9.1.1: version "9.1.1" resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.1.1.tgz#c4df7dc496bd849d4c9464344c1aa74228b4dac6" @@ -4056,12 +4137,12 @@ blueimp-md5@^2.10.0: resolved "https://registry.npmjs.org/blueimp-md5/-/blueimp-md5-2.19.0.tgz" integrity sha512-DRQrD6gJyy8FbiE4s+bDoXS9hiW3Vbx5uCdwvcCf3zLHL+Iv7LtGHLpr+GZV8rHG8tK766FGYBwRbu8pELTt+w== -bn.js@^4.11.8, bn.js@^4.11.9: +bn.js@^4.11.8, bn.js@^4.11.9, bn.js@^4.4.0: version "4.12.0" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA== -bn.js@^5.0.0, bn.js@^5.1.1, bn.js@^5.1.2, bn.js@^5.2.0, bn.js@^5.2.1: +bn.js@^5.1.1, bn.js@^5.1.2, bn.js@^5.2.0, bn.js@^5.2.1: version "5.2.1" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.1.tgz#0bc527a6a0d18d0aa8d5b0538ce4a77dccfa7b70" integrity sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ== @@ -4089,13 +4170,12 @@ boolbase@^1.0.0: resolved "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz" integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww== -borsh@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/borsh/-/borsh-0.4.0.tgz#9dd6defe741627f1315eac2a73df61421f6ddb9f" - integrity sha512-aX6qtLya3K0AkT66CmYWCCDr77qsE9arV05OmdFpmat9qu8Pg9J5tBUPDztAW5fNh/d/MyVG/OYziP52Ndzx1g== +borsh@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/borsh/-/borsh-0.7.0.tgz#6e9560d719d86d90dc589bca60ffc8a6c51fec2a" + integrity sha512-CLCsZGIBCFnPtkNnieW/a8wmreDmfUtjU2m9yHrzPXIlNbqVs0AQrSatSG6vdNYUqdc83tkQi2eHfF98ubzQLA== dependencies: - "@types/bn.js" "^4.11.5" - bn.js "^5.0.0" + bn.js "^5.2.0" bs58 "^4.0.0" text-encoding-utf-8 "^1.0.2" @@ -4151,7 +4231,7 @@ braces@^3.0.2: dependencies: fill-range "^7.0.1" -brorand@^1.0.5, brorand@^1.1.0: +brorand@^1.0.1, brorand@^1.0.5, brorand@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" integrity sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w== @@ -4249,14 +4329,6 @@ buffer-xor@^1.0.3: resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" integrity sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ== -buffer@6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.1.tgz#3cbea8c1463e5a0779e30b66d4c88c6ffa182ac2" - integrity sha512-rVAXBwEcEoYtxnHSO5iWyhzV/O1WMtkUYWlfdLS7FjU4PnSJJHEfHXi/uHPI5EwltmOA794gN3bm3/pzuctWjQ== - dependencies: - base64-js "^1.3.1" - ieee754 "^1.2.1" - buffer@6.0.3, buffer@^6.0.3, buffer@~6.0.3: version "6.0.3" resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" @@ -4265,7 +4337,7 @@ buffer@6.0.3, buffer@^6.0.3, buffer@~6.0.3: base64-js "^1.3.1" ieee754 "^1.2.1" -buffer@^5.5.0: +buffer@^5.1.0, buffer@^5.5.0: version "5.7.1" resolved "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz" integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== @@ -4800,6 +4872,13 @@ crc-32@^1.2.2: resolved "https://registry.yarnpkg.com/crc-32/-/crc-32-1.2.2.tgz#3cad35a934b8bf71f25ca524b6da51fb7eace2ff" integrity sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ== +crc@^3.5.0: + version "3.8.0" + resolved "https://registry.yarnpkg.com/crc/-/crc-3.8.0.tgz#ad60269c2c856f8c299e2c4cc0de4556914056c6" + integrity sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ== + dependencies: + buffer "^5.1.0" + create-hash@^1.1.0, create-hash@^1.1.2, create-hash@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196" @@ -4855,11 +4934,6 @@ crypt@0.0.2, crypt@~0.0.1: resolved "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz" integrity sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow== -crypto-hash@^1.2.2: - version "1.3.0" - resolved "https://registry.yarnpkg.com/crypto-hash/-/crypto-hash-1.3.0.tgz#b402cb08f4529e9f4f09346c3e275942f845e247" - integrity sha512-lyAZ0EMyjDkVvz8WOeVnuCPvKVBXcMv1l5SVqO1yC7PzTwrD/pPje/BIRbWhMoPe436U+Y2nD7f5bFx0kt+Sbg== - crypto-js@^3.1.9-1: version "3.3.0" resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-3.3.0.tgz#846dd1cce2f68aacfa156c8578f926a609b7976b" @@ -5109,7 +5183,7 @@ denodeify@^1.2.1: resolved "https://registry.npmjs.org/denodeify/-/denodeify-1.2.1.tgz" integrity sha512-KNTihKNmQENUZeKu5fzfpzRqR5S2VMp4gl9RFHiWzj9DfvYQPMJ6XHKNaQxaGCXwPk6y9yme3aUoaiAe+KX+vg== -depd@2.0.0: +depd@2.0.0, depd@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz" integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== @@ -5147,6 +5221,11 @@ detect-newline@^3.0.0: resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== +detect-node@^2.0.4: + version "2.1.0" + resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.1.0.tgz#c9c70775a49c3d03bc2c06d9a73be550f978f8b1" + integrity sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g== + diff-sequences@^29.4.3: version "29.4.3" resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.4.3.tgz#9314bc1fabe09267ffeca9cbafc457d8499a13f2" @@ -5252,6 +5331,19 @@ electron-to-chromium@^1.4.284: resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.348.tgz" integrity sha512-gM7TdwuG3amns/1rlgxMbeeyNoBFPa+4Uu0c7FeROWh4qWmvSOnvcslKmWy51ggLKZ2n/F/4i2HJ+PVNxH9uCQ== +elliptic@6.5.3: + version "6.5.3" + resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.3.tgz#cb59eb2efdaf73a0bd78ccd7015a62ad6e0f93d6" + integrity sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw== + dependencies: + bn.js "^4.4.0" + brorand "^1.0.1" + hash.js "^1.0.0" + hmac-drbg "^1.0.0" + inherits "^2.0.1" + minimalistic-assert "^1.0.0" + minimalistic-crypto-utils "^1.0.0" + elliptic@6.5.4, elliptic@^6.4.0, elliptic@^6.5.2, elliptic@^6.5.4: version "6.5.4" resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb" @@ -5431,7 +5523,7 @@ es6-object-assign@^1.1.0: resolved "https://registry.yarnpkg.com/es6-object-assign/-/es6-object-assign-1.1.0.tgz#c2c3582656247c39ea107cb1e6652b6f9f24523c" integrity sha512-MEl9uirslVwqQU369iHNWZXsI8yaZYGg/D65aOgZkeyFJwHYSxilf7rQzXKI7DdDuBPrBXbfk3sl9hJhmd5AUw== -es6-promise@^4.0.3: +es6-promise@^4.0.3, es6-promise@^4.2.4: version "4.2.8" resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a" integrity sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w== @@ -5788,11 +5880,21 @@ event-target-shim@^5.0.0, event-target-shim@^5.0.1: resolved "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz" integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== +eventemitter3@^3.1.0: + version "3.1.2" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-3.1.2.tgz#2d3d48f9c346698fce83a85d7d664e98535df6e7" + integrity sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q== + eventemitter3@^4.0.7: version "4.0.7" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== +eventsource@^1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/eventsource/-/eventsource-1.1.2.tgz#bc75ae1c60209e7cb1541231980460343eaea7c2" + integrity sha512-xAH3zWhgO2/3KIniEKYPr8plNSzlGINOUqYj0m0u7AB81iRw8b/3E73W6AuU+6klLbaSFmZnaETQ2lXPfAydrA== + evp_bytestokey@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz#7fcbdb198dc71959432efe13842684e0525acb02" @@ -6210,6 +6312,11 @@ fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.6: resolved "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz" integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== +fast-stable-stringify@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fast-stable-stringify/-/fast-stable-stringify-1.0.0.tgz#5c5543462b22aeeefd36d05b34e51c78cb86d313" + integrity sha512-wpYMUmFu5f00Sm0cj2pfivpmawLZ0NKdviQ4w9zJeR8JVtOpOxHmLaJuj0vxvGqMJQWyP/COUkF75/57OKyRag== + fast-xml-parser@^4.0.12: version "4.1.3" resolved "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.1.3.tgz" @@ -6391,7 +6498,7 @@ flow-parser@^0.185.0: resolved "https://registry.npmjs.org/flow-parser/-/flow-parser-0.185.2.tgz" integrity sha512-2hJ5ACYeJCzNtiVULov6pljKOLygy0zddoqSI1fFetM+XRPpRshFdGEijtqlamA1XwyZ+7rhryI6FQFzvtLWUQ== -follow-redirects@^1.15.0: +follow-redirects@^1.14.7, follow-redirects@^1.15.0: version "1.15.2" resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz" integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA== @@ -6794,6 +6901,14 @@ hash-base@^3.0.0: readable-stream "^3.6.0" safe-buffer "^5.2.0" +hash.js@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.3.tgz#340dedbe6290187151c1ea1d777a3448935df846" + integrity sha512-/UETyP0W22QILqS+6HowevwhEFJ3MBJnwTf75Qob9Wz9t0DPuisL8kW8YZMK62dHAKE1c1p+gY1TtOLY+USEHA== + dependencies: + inherits "^2.0.3" + minimalistic-assert "^1.0.0" + hash.js@1.1.7, hash.js@^1.0.0, hash.js@^1.0.3, hash.js@^1.1.7: version "1.1.7" resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42" @@ -6830,7 +6945,7 @@ hermes-profile-transformer@^0.0.6: dependencies: source-map "^0.7.3" -hmac-drbg@^1.0.1: +hmac-drbg@^1.0.0, hmac-drbg@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" integrity sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg== @@ -6903,6 +7018,13 @@ human-signals@^4.3.0: resolved "https://registry.npmjs.org/human-signals/-/human-signals-4.3.1.tgz" integrity sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ== +humanize-ms@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/humanize-ms/-/humanize-ms-1.2.1.tgz#c46e3159a293f6b896da29316d8b6fe8bb79bbed" + integrity sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ== + dependencies: + ms "^2.0.0" + husky@^8.0.3: version "8.0.3" resolved "https://registry.npmjs.org/husky/-/husky-8.0.3.tgz" @@ -7511,10 +7633,10 @@ istanbul-reports@^3.1.3: html-escaper "^2.0.0" istanbul-lib-report "^3.0.0" -jayson@^3.4.4: - version "3.7.0" - resolved "https://registry.yarnpkg.com/jayson/-/jayson-3.7.0.tgz#b735b12d06d348639ae8230d7a1e2916cb078f25" - integrity sha512-tfy39KJMrrXJ+mFcMpxwBvFDetS8LAID93+rycFglIQM4kl3uNR3W4lBLE/FFhsoUCEox5Dt2adVpDm/XtebbQ== +jayson@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/jayson/-/jayson-4.1.0.tgz#60dc946a85197317f2b1439d672a8b0a99cea2f9" + integrity sha512-R6JlbyLN53Mjku329XoRT2zJAE6ZgOQ8f91ucYdMCD4nkGCF9kZSrcGXpHIU4jeKj58zUZke2p+cdQchU7Ly7A== dependencies: "@types/connect" "^3.4.33" "@types/node" "^12.12.54" @@ -7526,7 +7648,6 @@ jayson@^3.4.4: eyes "^0.1.8" isomorphic-ws "^4.0.1" json-stringify-safe "^5.0.1" - lodash "^4.17.20" uuid "^8.3.2" ws "^7.4.5" @@ -8020,6 +8141,11 @@ js-sdsl@^4.1.4: resolved "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.4.0.tgz" integrity sha512-FfVSdx6pJ41Oa+CF7RDaFmTnCaFhua+SNYQX74riGOpl96x+2jQCqEfQ2bnXu/5DPCqlRuiqyvTJM0Qjz26IVg== +js-sha3@0.5.7: + version "0.5.7" + resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.5.7.tgz#0d4ffd8002d5333aabaf4a23eed2f6374c9f28e7" + integrity sha512-GII20kjaPX0zJ8wzkTbNDYMY7msuZcTWk8S5UOh6806Jq/wz1J8/bnr8uGU0DAUmYDjj2Mr4X1cW8v/GLYnR+g== + js-sha3@0.8.0, js-sha3@^0.8.0: version "0.8.0" resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.8.0.tgz#b9b7a5da73afad7dedd0f8c463954cbde6818840" @@ -8030,6 +8156,14 @@ js-sha3@0.8.0, js-sha3@^0.8.0: resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== +js-xdr@^1.1.3: + version "1.3.0" + resolved "https://registry.yarnpkg.com/js-xdr/-/js-xdr-1.3.0.tgz#e72e77c00bbdae62689062b95fe35ae2bd90df32" + integrity sha512-fjLTm2uBtFvWsE3l2J14VjTuuB8vJfeTtYuNS7LiLHDWIX2kt0l1pqq9334F8kODUkKPMuULjEcbGbkFFwhx5g== + dependencies: + lodash "^4.17.5" + long "^2.2.3" + js-yaml@^3.13.1: version "3.14.1" resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz" @@ -8368,7 +8502,7 @@ lodash.throttle@^4.1.1: resolved "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz" integrity sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ== -lodash@^4.17.13, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.4: +lodash@^4.17.13, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.21, lodash@^4.17.4, lodash@^4.17.5: version "4.17.21" resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -8412,6 +8546,11 @@ logkitty@^0.7.1: resolved "https://registry.yarnpkg.com/@nozbe/lokijs/-/lokijs-1.5.12-wmelon6.tgz#e457d934d614d5df80105c86314252a6e614df9b" integrity sha512-GXsaqY8qTJ6xdCrGyno2t+ON2aj6PrUDdvhbrkxK/0Fp12C4FGvDg1wS+voLU9BANYHEnr7KRWfItDZnQkjoAg== +long@^2.2.3: + version "2.4.0" + resolved "https://registry.yarnpkg.com/long/-/long-2.4.0.tgz#9fa180bb1d9500cdc29c4156766a1995e1f4524f" + integrity sha512-ijUtjmO/n2A5PaosNG9ZGDsQ3vxJg7ZW8vsY8Kp0f2yIZWhSJvjmegV7t+9RPQKxKrvj8yKGehhS+po14hPLGQ== + long@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28" @@ -8897,7 +9036,7 @@ minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== -minimalistic-crypto-utils@^1.0.1: +minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" integrity sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg== @@ -9005,7 +9144,7 @@ ms@2.1.2: resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -ms@2.1.3, ms@^2.1.1: +ms@2.1.3, ms@^2.0.0, ms@^2.1.1: version "2.1.3" resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== @@ -9129,6 +9268,13 @@ node-fetch@^2.2.0, node-fetch@^2.6.0, node-fetch@^2.6.1, node-fetch@^2.6.7: dependencies: whatwg-url "^5.0.0" +node-fetch@^2.6.12: + version "2.6.12" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.12.tgz#02eb8e22074018e3d5a83016649d04df0e348fba" + integrity sha512-C/fGU2E8ToujUivIO0H+tpQ6HWo4eEmchoPIoXtxCrVghxdKq+QOHqEZW7tuP3KlV3bC8FRMO5nMCC7Zm1VP6g== + dependencies: + whatwg-url "^5.0.0" + node-forge@^0.10.0: version "0.10.0" resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.10.0.tgz#32dea2afb3e9926f02ee5ce8794902691a676bf3" @@ -10563,14 +10709,14 @@ ripple-lib@^1.10.1: ripple-lib-transactionparser "0.8.2" ws "^7.2.0" -rlp@^2.2.4: +rlp@^2.0.0, rlp@^2.2.4: version "2.2.7" resolved "https://registry.yarnpkg.com/rlp/-/rlp-2.2.7.tgz#33f31c4afac81124ac4b283e2bd4d9720b30beaf" integrity sha512-d5gdPmgQ0Z+AklL2NVXr/IoSjNZFfTVvQWzL/AM2AOcSzYP2xjlb0AC8YyCLc41MSNf6P6QVtjgPdmVtzb+4lQ== dependencies: bn.js "^5.2.0" -rpc-websockets@^7.4.2: +rpc-websockets@^7.5.1: version "7.5.1" resolved "https://registry.yarnpkg.com/rpc-websockets/-/rpc-websockets-7.5.1.tgz#e0a05d525a97e7efc31a0617f093a13a2e10c401" integrity sha512-kGFkeTsmd37pHPMaHIgN1LVKXMi0JD782v4Ds9ZKtLlwdTKjn+CxM9A9/gLT2LaOuEcEFGL98h1QWQtlOIdW0w== @@ -10659,6 +10805,11 @@ scheduler@^0.23.0: dependencies: loose-envify "^1.1.0" +scrypt-js@2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/scrypt-js/-/scrypt-js-2.0.4.tgz#32f8c5149f0797672e551c07e230f834b6af5f16" + integrity sha512-4KsaGcPnuhtCZQCxFxN3GVYIhKFPTdLd8PLC552XwbMndtD0cjRFAhDuuydXQ0h08ZfPgzqe6EKHozpuH74iDw== + scrypt-js@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/scrypt-js/-/scrypt-js-3.0.1.tgz#d314a57c2aef69d1ad98a138a21fe9eafa9ee312" @@ -10678,7 +10829,7 @@ secp256k1@^3.0.1: nan "^2.14.0" safe-buffer "^5.1.2" -secp256k1@^4.0.1, secp256k1@^4.0.2: +secp256k1@^4.0.1: version "4.0.3" resolved "https://registry.yarnpkg.com/secp256k1/-/secp256k1-4.0.3.tgz#c4559ecd1b8d3c1827ed2d1b94190d69ce267303" integrity sha512-NLZVf+ROMxwtEj3Xa562qgv2BK5e2WNmXPiOdVIPLgs6lyTzMvBq0aWTYMI5XCP9jZMVKOcqZLw/Wc4vDkuxhA== @@ -10765,6 +10916,11 @@ set-value@^2.0.0, set-value@^2.0.1: is-plain-object "^2.0.3" split-string "^3.0.1" +setimmediate@1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.4.tgz#20e81de622d4a02588ce0c8da8973cbcf1d3138f" + integrity sha512-/TjEmXQVEzdod/FFskf3o7oOAsGhHf2j1dZqRFbDzq4F3mvvxflIIi4Hd3bLQE9y/CpwqfSQam5JakI/mi3Pog== + setimmediate@^1.0.5: version "1.0.5" resolved "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz" @@ -10775,7 +10931,7 @@ setprototypeof@1.2.0: resolved "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz" integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== -sha.js@^2.4.0, sha.js@^2.4.8: +sha.js@^2.3.6, sha.js@^2.4.0, sha.js@^2.4.8: version "2.4.11" resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7" integrity sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ== @@ -10955,6 +11111,13 @@ snapdragon@^0.8.1: source-map-resolve "^0.5.0" use "^3.1.0" +sodium-native@^3.3.0: + version "3.4.1" + resolved "https://registry.yarnpkg.com/sodium-native/-/sodium-native-3.4.1.tgz#44616c07ccecea15195f553af88b3e574b659741" + integrity sha512-PaNN/roiFWzVVTL6OqjzYct38NSXewdl2wz8SRB51Br/MLIJPrbM3XexhVWkq7D3UWMysfrhKVf1v1phZq6MeQ== + dependencies: + node-gyp-build "^4.3.0" + source-map-resolve@^0.5.0: version "0.5.3" resolved "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz" @@ -11104,6 +11267,43 @@ statuses@~1.5.0: resolved "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz" integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA== +stellar-base@^8.2.2: + version "8.2.2" + resolved "https://registry.yarnpkg.com/stellar-base/-/stellar-base-8.2.2.tgz#acae1eec0afd95e9e7a292086a310a32b957a65c" + integrity sha512-YVCIuJXU1bPn+vU0ded+g0D99DcpYXH9CEXfpYEDc4Gf04h65YjOVhGojQBm1hqVHq3rKT7m1tgfNACkU84FTA== + dependencies: + base32.js "^0.1.0" + bignumber.js "^4.0.0" + crc "^3.5.0" + js-xdr "^1.1.3" + lodash "^4.17.21" + sha.js "^2.3.6" + tweetnacl "^1.0.3" + optionalDependencies: + sodium-native "^3.3.0" + +stellar-sdk@^10.4.1: + version "10.4.1" + resolved "https://registry.yarnpkg.com/stellar-sdk/-/stellar-sdk-10.4.1.tgz#823eb20e7f346b87c3bcaeeb11ec8128a1790d90" + integrity sha512-Wdm2UoLuN9SNrSEHO0R/I+iZuRwUkfny1xg4akhGCpO8LQZw8QzuMTJvbEoMT3sHT4/eWYiteVLp7ND21xZf5A== + dependencies: + "@types/eventsource" "^1.1.2" + "@types/node" ">= 8" + "@types/randombytes" "^2.0.0" + "@types/urijs" "^1.19.6" + axios "0.25.0" + bignumber.js "^4.0.0" + detect-node "^2.0.4" + es6-promise "^4.2.4" + eventsource "^1.1.1" + lodash "^4.17.21" + randombytes "^2.1.0" + stellar-base "^8.2.2" + toml "^2.3.0" + tslib "^1.10.0" + urijs "^1.19.1" + utility-types "^3.7.0" + stop-iteration-iterator@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz#6a60be0b4ee757d1ed5254858ec66b10c49285e4" @@ -11485,6 +11685,31 @@ thenify-all@^1.0.0: dependencies: any-promise "^1.0.0" +thor-devkit@^1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/thor-devkit/-/thor-devkit-1.3.3.tgz#848b4b755ac1f0ae1684f3f62ca8f24a66e76ced" + integrity sha512-IsxMfEF/NKvSpWN/RNOJPuBrNMgekAYkLJM+bxVEFtS5xQiv2gJsbdbT0CnWYv0CmT4j+zqdq2hyOBNccAZJZQ== + dependencies: + "@vechain/ethers" "^4.0.27-3" + bignumber.js "^7.2.1" + blakejs "^1.1.0" + elliptic "6.5.3" + fast-json-stable-stringify "^2.0.0" + js-sha3 "0.5.7" + rlp "^2.0.0" + +thorify@^1.6.2: + version "1.6.2" + resolved "https://registry.yarnpkg.com/thorify/-/thorify-1.6.2.tgz#5a12bee091a19e4b01bbe72e1dc104da767c5d82" + integrity sha512-yd3W8ff9WHwF0uornr19oKyEOAduitzpH7zxzBBt2p2Vg44lK0aBO6jxjNG/23eYi/YGYXouZjw0rLx+H0IPzg== + dependencies: + debug "^3.1.0" + eventemitter3 "^3.1.0" + isomorphic-ws "^4.0.1" + thor-devkit "^1.3.3" + ws "^6.0.0" + xhr2 "0.1.4" + throat@^5.0.0: version "5.0.0" resolved "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz" @@ -11575,6 +11800,11 @@ toidentifier@1.0.1: resolved "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz" integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== +toml@^2.3.0: + version "2.3.6" + resolved "https://registry.yarnpkg.com/toml/-/toml-2.3.6.tgz#25b0866483a9722474895559088b436fd11f861b" + integrity sha512-gVweAectJU3ebq//Ferr2JUY4WKSDe5N+z0FvjDncLGyHmIDoxgY/2Ie4qfEIDm4IS7OA6Rmdm7pdEEdMcV/xQ== + tough-cookie@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.1.2.tgz#e53e84b85f24e0b65dd526f46628db6c85f6b874" @@ -11622,7 +11852,7 @@ tsconfig-paths@^3.14.1: minimist "^1.2.6" strip-bom "^3.0.0" -tslib@^1.13.0, tslib@^1.8.1: +tslib@^1.10.0, tslib@^1.13.0, tslib@^1.8.1: version "1.14.1" resolved "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== @@ -11670,7 +11900,7 @@ tsutils@^3.21.0: dependencies: tslib "^1.8.1" -tweetnacl@^1.0.0: +tweetnacl@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-1.0.3.tgz#ac0af71680458d8a6378d0d0d050ab1407d35596" integrity sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw== @@ -11898,6 +12128,11 @@ uri-js@^4.2.2: dependencies: punycode "^2.1.0" +urijs@^1.19.1: + version "1.19.11" + resolved "https://registry.yarnpkg.com/urijs/-/urijs-1.19.11.tgz#204b0d6b605ae80bea54bea39280cdb7c9f923cc" + integrity sha512-HXgFDgDommxn5/bIv0cnQZsPhHDA90NPHD6+c/v21U5+Sx5hoP8+dP9IZXBU1gIfvdRfhG8cel9QNPeionfcCQ== + urix@^0.1.0: version "0.1.0" resolved "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz" @@ -11959,11 +12194,21 @@ util@^0.12.0, util@^0.12.5: is-typed-array "^1.1.3" which-typed-array "^1.1.2" +utility-types@^3.7.0: + version "3.10.0" + resolved "https://registry.yarnpkg.com/utility-types/-/utility-types-3.10.0.tgz#ea4148f9a741015f05ed74fd615e1d20e6bed82b" + integrity sha512-O11mqxmi7wMKCo6HKFt5AhO4BwY3VV68YU07tgxfz8zJTIxr4BpsezN49Ffwy9j3ZpwwJp4fkRwjRzq3uWE6Rg== + utils-merge@1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz" integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== +uuid@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-2.0.1.tgz#c2a30dedb3e535d72ccf82e343941a50ba8533ac" + integrity sha512-nWg9+Oa3qD2CQzHIP4qKUqwNfzKn8P0LtFhotaCTFchsV7ZfDhAybeip/HZVeMIpZi9JgY1E3nUlwaCmZT1sEg== + uuid@^3.3.2, uuid@^3.4.0: version "3.4.0" resolved "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz" @@ -12430,7 +12675,7 @@ write-file-atomic@^4.0.2: imurmurhash "^0.1.4" signal-exit "^3.0.7" -ws@^6.2.2: +ws@^6.0.0, ws@^6.2.2: version "6.2.2" resolved "https://registry.npmjs.org/ws/-/ws-6.2.2.tgz" integrity sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw== @@ -12460,6 +12705,11 @@ xhr2@0.1.3: resolved "https://registry.yarnpkg.com/xhr2/-/xhr2-0.1.3.tgz#cbfc4759a69b4a888e78cf4f20b051038757bd11" integrity sha512-6RmGK22QwC7yXB1CRwyLWuS2opPcKOlAu0ViAnyZjDlzrEmCKL4kLHkfvB8oMRWeztMsNoDGAjsMZY15w/4tTw== +xhr2@0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/xhr2/-/xhr2-0.1.4.tgz#7f87658847716db5026323812f818cadab387a5f" + integrity sha512-3QGhDryRzTbIDj+waTRvMBe8SyPhW79kz3YnNb+HQt/6LPYQT3zT3Jt0Y8pBofZqQX26x8Ecfv0FXR72uH5VpA== + xml-js@^1.6.11: version "1.6.11" resolved "https://registry.yarnpkg.com/xml-js/-/xml-js-1.6.11.tgz#927d2f6947f7f1c19a316dd8eea3614e8b18f8e9" @@ -12500,6 +12750,11 @@ xmlchars@^2.2.0: resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== +xmlhttprequest@1.8.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz#67fe075c5c24fef39f9d65f5f7b7fe75171968fc" + integrity sha512-58Im/U0mlVBLM38NdZjHyhuMtCqa61469k2YP/AaPbvCoV9aQGUpbJBj1QRm2ytRiVQBD/fsw7L2bJGDVQswBA== + xtend@^4.0.0, xtend@~4.0.1: version "4.0.2" resolved "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz" From 547d488686e14893024e2e257c4922d714760fab Mon Sep 17 00:00:00 2001 From: JavidHaji-zada Date: Wed, 9 Aug 2023 21:11:18 +0400 Subject: [PATCH 034/509] Database import fixes --- crypto/actions/BlocksoftKeys/BlocksoftKeys.js | 13 +- ...BlocksoftDict.json => coinAirDAODict.json} | 0 crypto/blockchains/AirDAODispatcher.ts | 58 +- crypto/blockchains/fio/FioScannerProcessor.ts | 2 +- crypto/blockchains/trx/TrxScannerProcessor.ts | 56 +- .../trx/basic/TrxTransactionsProvider.ts | 2 +- .../trx/basic/TrxTransactionsTrc20Provider.ts | 52 +- crypto/blockchains/trx/dict/swaps.ts | 2 +- crypto/blockchains/trx/ext/TronStakeUtils.ts | 6 +- .../blockchains/usdt/UsdtScannerProcessor.ts | 9 +- .../waves/WavesScannerProcessor.ts | 5 +- .../waves/WavesScannerProcessorErc20.ts | 3 +- crypto/blockchains/xmr/XmrScannerProcessor.ts | 33 +- crypto/blockchains/xrp/stores/XrpTmpDS.ts | 26 +- crypto/blockchains/xvg/stores/XvgTmpDS.ts | 30 +- crypto/common/AirDAOBN.ts | 1 - crypto/common/AirDAOExternalSettings.ts | 12 +- crypto/common/AirDAOUtils.ts | 2 - package.json | 1 + src/lib/AirDAOKeys.ts | 635 ------------------ src/lib/AirDAOKeysStorage.ts | 300 --------- src/lib/BlocksoftDispatcher.js | 315 --------- src/lib/BlocksoftKeys.js | 496 -------------- src/lib/BlocksoftKeysForRef.js | 82 --- src/lib/helpers/AirDAOKeys.ts | 100 ++- src/lib/helpers/AirDAOKeysForRef.ts | 8 +- .../{ => helpers}/AirDAOKeysForRefStorage.ts | 0 src/lib/helpers/AirDAOKeysStorage.ts | 2 +- src/utils/cashback.ts | 35 +- src/utils/keys.ts | 2 +- src/utils/wallet.ts | 6 +- 31 files changed, 255 insertions(+), 2039 deletions(-) rename crypto/assets/{coinBlocksoftDict.json => coinAirDAODict.json} (100%) delete mode 100644 src/lib/AirDAOKeys.ts delete mode 100644 src/lib/AirDAOKeysStorage.ts delete mode 100644 src/lib/BlocksoftDispatcher.js delete mode 100644 src/lib/BlocksoftKeys.js delete mode 100644 src/lib/BlocksoftKeysForRef.js rename src/lib/{ => helpers}/AirDAOKeysForRefStorage.ts (100%) diff --git a/crypto/actions/BlocksoftKeys/BlocksoftKeys.js b/crypto/actions/BlocksoftKeys/BlocksoftKeys.js index 4a070615a..d3b2f2328 100644 --- a/crypto/actions/BlocksoftKeys/BlocksoftKeys.js +++ b/crypto/actions/BlocksoftKeys/BlocksoftKeys.js @@ -6,15 +6,16 @@ import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; import AirDAODict from '@crypto/common/AirDAODict'; import BlocksoftKeysUtils from '@crypto/actions/BlocksoftKeys/BlocksoftKeysUtils'; -import * as BlocksoftRandom from 'react-native-blocksoft-random'; +import * as Crypto from 'expo-crypto'; import BlocksoftDispatcher from '../../blockchains/BlocksoftDispatcher'; import BlocksoftKeysScam from '@crypto/actions/BlocksoftKeys/BlocksoftKeysScam'; -import { strings } from '@app/services/i18n'; +import bip44Constants from '@crypto/common/ext/bip44-constants'; +import networksConstants from '@crypto/common/ext/networks-constants'; const bip32 = require('bip32'); const bip39 = require('bip39'); -const bip44Constants = require('../../common/ext/bip44-constants'); -const networksConstants = require('../../common/ext/networks-constants'); +// const bip44Constants = require('../../common/ext/bip44-constants'); +// const networksConstants = require('../../common/ext/networks-constants'); const bs58check = require('bs58check'); @@ -29,7 +30,7 @@ class BlocksoftKeys { for (currency of bip44Constants) { this._bipHex[currency[1]] = currency[0]; } - this._getRandomBytesFunction = BlocksoftRandom.getRandomBytes; + this._getRandomBytesFunction = Crypto.getRandomBytesAsync; } /** @@ -75,7 +76,7 @@ class BlocksoftKeys { async validateMnemonic(mnemonic) { AirDAOCryptoLog.log(`BlocksoftKeys validateMnemonic called`); if (await BlocksoftKeysScam.isScamMnemonic(mnemonic)) { - throw new Error(strings('settings.walletList.scamImport')); + throw new Error('Scam error'); } const result = await bip39.validateMnemonic(mnemonic); if (!result) { diff --git a/crypto/assets/coinBlocksoftDict.json b/crypto/assets/coinAirDAODict.json similarity index 100% rename from crypto/assets/coinBlocksoftDict.json rename to crypto/assets/coinAirDAODict.json diff --git a/crypto/blockchains/AirDAODispatcher.ts b/crypto/blockchains/AirDAODispatcher.ts index 97d02cbe8..f37bd120a 100644 --- a/crypto/blockchains/AirDAODispatcher.ts +++ b/crypto/blockchains/AirDAODispatcher.ts @@ -238,35 +238,35 @@ class AirDAODispatcher { network: 'mainnet', tokenBlockchain: 'BNB' }); - case 'MATIC_ERC_20': - return new EthTokenProcessorErc20({ - network: 'mainnet', - tokenBlockchain: 'MATIC' - }); - case 'FTM_ERC_20': - return new EthTokenProcessorErc20({ - network: 'mainnet', - tokenBlockchain: 'FTM' - }); - case 'VLX_ERC_20': - return new EthTokenProcessorErc20({ - network: 'mainnet', - tokenBlockchain: 'VLX' - }); - case 'ONE_ERC_20': - return new EthTokenProcessorErc20({ - network: 'mainnet', - tokenBlockchain: 'ONE' - }); - case 'METIS_ERC_20': - return new EthTokenProcessorErc20({ - network: 'mainnet', - tokenBlockchain: 'METIS' - }); - case 'TRX': - return new TrxTokenProcessor(); - case 'SOL': - return new SolTokenProcessor(); + // case 'MATIC_ERC_20': + // return new EthTokenProcessorErc20({ + // network: 'mainnet', + // tokenBlockchain: 'MATIC' + // }); + // case 'FTM_ERC_20': + // return new EthTokenProcessorErc20({ + // network: 'mainnet', + // tokenBlockchain: 'FTM' + // }); + // case 'VLX_ERC_20': + // return new EthTokenProcessorErc20({ + // network: 'mainnet', + // tokenBlockchain: 'VLX' + // }); + // case 'ONE_ERC_20': + // return new EthTokenProcessorErc20({ + // network: 'mainnet', + // tokenBlockchain: 'ONE' + // }); + // case 'METIS_ERC_20': + // return new EthTokenProcessorErc20({ + // network: 'mainnet', + // tokenBlockchain: 'METIS' + // }); + // case 'TRX': + // return new TrxTokenProcessor(); + // case 'SOL': + // return new SolTokenProcessor(); default: throw new Error('Unknown tokenProcessor ' + tokenType); } diff --git a/crypto/blockchains/fio/FioScannerProcessor.ts b/crypto/blockchains/fio/FioScannerProcessor.ts index bf4a952fb..903cf555f 100644 --- a/crypto/blockchains/fio/FioScannerProcessor.ts +++ b/crypto/blockchains/fio/FioScannerProcessor.ts @@ -1,5 +1,5 @@ import AirDAOCryptoLog from '../../common/AirDAOCryptoLog'; -import { getFioBalance, getTransactions } from './FioUtils.jts'; +import { getFioBalance, getTransactions } from './FioUtils'; export default class FioScannerProcessor { /** diff --git a/crypto/blockchains/trx/TrxScannerProcessor.ts b/crypto/blockchains/trx/TrxScannerProcessor.ts index 0cb6c62c9..13d184ff6 100644 --- a/crypto/blockchains/trx/TrxScannerProcessor.ts +++ b/crypto/blockchains/trx/TrxScannerProcessor.ts @@ -8,13 +8,13 @@ import TrxTrongridProvider from './basic/TrxTrongridProvider'; import TrxTransactionsProvider from './basic/TrxTransactionsProvider'; import TrxTransactionsTrc20Provider from './basic/TrxTransactionsTrc20Provider'; import AirDAOCryptoLog from '../../common/AirDAOCryptoLog'; -import Database from '@app/appstores/DataSource/Database/main'; import AirDAOAxios from '@crypto/common/AirDAOAxios'; import BlocksoftUtils from '@crypto/common/AirDAOUtils'; -import transactionDS from '@app/appstores/DataSource/Transaction/Transaction'; import BlocksoftExternalSettings from '@crypto/common/AirDAOExternalSettings'; import TronStakeUtils from '@crypto/blockchains/trx/ext/TronStakeUtils'; import config from '@constants/config'; +import { Database } from '@database'; +import { DatabaseTable } from '@appTypes'; let CACHE_PENDING_TXS = false; @@ -270,11 +270,14 @@ export default class TrxScannerProcessor { scanData.specialActionNeeded && typeof scanData.account.address !== 'undefined' ) { - await Database.query(` - UPDATE transactions SET special_action_needed='' + await Database.unsafeRawQuery( + DatabaseTable.Transactions, + ` + UPDATE ${DatabaseTable.Transactions} SET special_action_needed='' WHERE special_action_needed='${scanData.specialActionNeeded}' AND address_from_basic='${scanData.account.address}' - `); + ` + ); } return false; } @@ -308,7 +311,8 @@ export default class TrxScannerProcessor { ORDER BY created_at DESC LIMIT 10 `; - const res = await Database.query(sql); + // TODO check left join + const res = await Database.unsafeRawQuery(DatabaseTable.Transactions, sql); if ( !res || typeof res.array === 'undefined' || @@ -426,10 +430,13 @@ export default class TrxScannerProcessor { specialActionNeeded ) ) { - await Database.query(` + await Database.unsafeRawQuery( + DatabaseTable.Transactions, + ` UPDATE transactions SET special_action_needed='' WHERE special_action_needed='vote' OR special_action_needed='vote_after_unfreeze' AND address_from_basic='${address}' - `); + ` + ); AirDAOCryptoLog.log( this._settings.currencyCode + ' TrxScannerProcessor.getTransactionsPendingBlockchain vote all finished for ' + @@ -500,22 +507,23 @@ export default class TrxScannerProcessor { throw e; } - await transactionDS.saveTransaction( - { - blockNumber: transaction.blockNumber, - blockTime: formattedTime, - blockConfirmations: lastBlock - transaction.blockNumber, - transactionStatus, - transactionsScanLog: - new Date().toISOString() + - ' RECEIPT RECHECK ' + - JSON.stringify(transaction) + - ' ' + - row.transactionsScanLog - }, - row.id, - 'receipt' - ); + // TODO implement this + // await transactionDS.saveTransaction( + // { + // blockNumber: transaction.blockNumber, + // blockTime: formattedTime, + // blockConfirmations: lastBlock - transaction.blockNumber, + // transactionStatus, + // transactionsScanLog: + // new Date().toISOString() + + // ' RECEIPT RECHECK ' + + // JSON.stringify(transaction) + + // ' ' + + // row.transactionsScanLog + // }, + // row.id, + // 'receipt' + // ); return transactionStatus === 'success'; } } diff --git a/crypto/blockchains/trx/basic/TrxTransactionsProvider.ts b/crypto/blockchains/trx/basic/TrxTransactionsProvider.ts index efe748a12..2b0e6d543 100644 --- a/crypto/blockchains/trx/basic/TrxTransactionsProvider.ts +++ b/crypto/blockchains/trx/basic/TrxTransactionsProvider.ts @@ -5,7 +5,7 @@ import AirDAOCryptoLog from '../../../common/AirDAOCryptoLog'; import AirDAOAxios from '../../../common/AirDAOAxios'; import BlocksoftUtils from '../../../common/AirDAOUtils'; import TrxNodeInfoProvider from './TrxNodeInfoProvider'; -import TransactionFilterTypeDict from '@appV2/dicts/transactionFilterTypeDict'; +import TransactionFilterTypeDict from '@crypto/TransactionFilterTypeDict'; const TXS_MAX_TRY = 10; diff --git a/crypto/blockchains/trx/basic/TrxTransactionsTrc20Provider.ts b/crypto/blockchains/trx/basic/TrxTransactionsTrc20Provider.ts index 40840165e..af04d60fe 100644 --- a/crypto/blockchains/trx/basic/TrxTransactionsTrc20Provider.ts +++ b/crypto/blockchains/trx/basic/TrxTransactionsTrc20Provider.ts @@ -4,12 +4,11 @@ */ import TrxTransactionsProvider from './TrxTransactionsProvider'; import BlocksoftUtils from '../../../common/AirDAOUtils'; -import AirDAOCryptoLog from '../../../common/AirDAOCryptoLog'; import AirDAOAxios from '@crypto/common/AirDAOAxios'; -import Database from '@app/appstores/DataSource/Database/main'; -import TransactionFilterTypeDict from '@appV2/dicts/transactionFilterTypeDict'; - -const SWAPS: Record = require('../dict/swaps'); +import { Database, TransactionsDBModel } from '@database'; +import { DatabaseTable } from '@appTypes'; +import TransactionFilterTypeDict from '@crypto/TransactionFilterTypeDict'; +import SWAPS from '../dict/swaps'; interface UnifiedTransaction { transactionHash: string; @@ -78,13 +77,13 @@ export default class TrxTransactionsTrc20Provider extends TrxTransactionsProvide ' timestamp error transaction data ' + JSON.stringify(transaction); throw e; } - if (typeof transaction.amount === 'undefined') { - // noinspection ES6MissingAwait - AirDAOCryptoLog.err( - 'TrxTransactionsTrc20Provider._unifyTransaction buggy tx ' + - JSON.stringify(transaction) - ); - } + // if (typeof transaction.amount === 'undefined') { + // // noinspection ES6MissingAwait + // AirDAOCryptoLog.err( + // 'TrxTransactionsTrc20Provider._unifyTransaction buggy tx ' + + // JSON.stringify(transaction) + // ); + // } const res: UnifiedTransaction = { transactionHash: transaction.transactionHash || '', @@ -159,18 +158,19 @@ export default class TrxTransactionsTrc20Provider extends TrxTransactionsProvide tmp.data.cost.fee * 1 + tmp.data.cost.energy_fee * 1 || 0; if (res.transactionFee > 0 && res.addressAmount * 1 > 0) { - const savedTRX = await Database.query( - ` SELECT * FROM transactions WHERE transaction_hash='${res.transactionHash}' AND currency_code='TRX' ` - ); - if (!savedTRX || !savedTRX.array || savedTRX.array.length === 0) { - AirDAOCryptoLog.log( - 'TrxTransactionsTrc20Provider._unifyTransaction added fee for ' + - res.transactionHash + - ' amount ' + - res.addressAmount + - ' fee ' + - res.transactionFee - ); + const savedTRX = (await Database.unsafeRawQuery( + DatabaseTable.Transactions, + ` SELECT * FROM ${DatabaseTable.Transactions} WHERE transaction_hash='${res.transactionHash}' AND currency_code='TRX' ` + )) as TransactionsDBModel[]; + if (!savedTRX || savedTRX.length === 0) { + // AirDAOCryptoLog.log( + // 'TrxTransactionsTrc20Provider._unifyTransaction added fee for ' + + // res.transactionHash + + // ' amount ' + + // res.addressAmount + + // ' fee ' + + // res.transactionFee + // ); const saveFee: UnifiedTransaction = { account_id: 0, address_amount: 0, @@ -190,9 +190,7 @@ export default class TrxTransactionsTrc20Provider extends TrxTransactionsProvide transactions_scan_time: new Date().getTime(), wallet_hash: scanData.account.walletHash }; - await Database.setTableName('transactions') - .setInsertData({ insertObjs: [saveFee] }) - .insert(); + await Database.createModel(DatabaseTable.Transactions, saveFee); } } if (tmp.data.trc20TransferInfo !== undefined) { diff --git a/crypto/blockchains/trx/dict/swaps.ts b/crypto/blockchains/trx/dict/swaps.ts index 7b0f57045..aec5c67b9 100644 --- a/crypto/blockchains/trx/dict/swaps.ts +++ b/crypto/blockchains/trx/dict/swaps.ts @@ -1,4 +1,4 @@ -module.exports = { +export default { TTnSHzUoho1CU6zFYVzVSCKq8EX8ZddkVv: 'JUSTSWAP-WTRX-TRX', TJmTeYk5zmg8pNPGYbDb2psadwVLYDDYDr: 'JUSTSWAP-DICE-TRX', TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE: 'JUSTSWAP-USDT-TRX', diff --git a/crypto/blockchains/trx/ext/TronStakeUtils.ts b/crypto/blockchains/trx/ext/TronStakeUtils.ts index 6dd6ef3bf..a223e58cd 100644 --- a/crypto/blockchains/trx/ext/TronStakeUtils.ts +++ b/crypto/blockchains/trx/ext/TronStakeUtils.ts @@ -4,7 +4,7 @@ import BlocksoftBalances from '@crypto/actions/BlocksoftBalances/BlocksoftBalanc import TronUtils from '@crypto/blockchains/trx/ext/TronUtils'; import AirDAOAxios from '@crypto/common/AirDAOAxios'; -import Log from '@app/services/Log/Log'; +// import Log from '@app/services/Log/Log'; import { BlocksoftTransfer } from '@crypto/actions/BlocksoftTransfer/BlocksoftTransfer'; import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; @@ -189,11 +189,11 @@ const TronStakeUtils = { if (typeof tmp.data.raw_data_hex !== 'undefined') { blockchainData = tmp.data; } else { - Log.log('TronStakeUtils._send no rawHex ' + link, params, tmp.data); + // Log.log('TronStakeUtils._send no rawHex ' + link, params, tmp.data); throw new Error(JSON.stringify(tmp.data)); } } else { - Log.log('TronStakeUtils rawHex empty data ' + link, params); + // Log.log('TronStakeUtils rawHex empty data ' + link, params); throw new Error('Empty data'); } diff --git a/crypto/blockchains/usdt/UsdtScannerProcessor.ts b/crypto/blockchains/usdt/UsdtScannerProcessor.ts index 09f12785b..82c22cb4c 100644 --- a/crypto/blockchains/usdt/UsdtScannerProcessor.ts +++ b/crypto/blockchains/usdt/UsdtScannerProcessor.ts @@ -4,7 +4,7 @@ import BlocksoftUtils from '../../common/AirDAOUtils'; import AirDAOAxios from '../../common/AirDAOAxios'; import AirDAOCryptoLog from '../../common/AirDAOCryptoLog'; -import BlocksoftDispatcher from '@lib/BlocksoftDispatcher'; +import AirDAODispatcher from '../AirDAODispatcher'; const USDT_API = 'https://microscanners.trustee.deals/usdt'; // https://microscanners.trustee.deals/usdt/1CmAoxq8BTxANRDwheJUpaGy6ngWNYX85 const USDT_API_MASS = 'https://microscanners.trustee.deals/balanceMass'; @@ -201,10 +201,9 @@ export default class UsdtScannerProcessor { let btcTxs: UnifiedTransaction[] | false = false; try { if (!this._btcProvider) { - this._btcProvider = - (await new BlocksoftDispatcher().getScannerProcessor({ - currencyCode: 'BTC' - })) as BtcScannerProcessor; + this._btcProvider = (await new AirDAODispatcher().getScannerProcessor({ + currencyCode: 'BTC' + })) as BtcScannerProcessor; } btcTxs = await this._btcProvider.getTransactionsBlockchain( scanData, diff --git a/crypto/blockchains/waves/WavesScannerProcessor.ts b/crypto/blockchains/waves/WavesScannerProcessor.ts index ffd028290..8e5102e3e 100644 --- a/crypto/blockchains/waves/WavesScannerProcessor.ts +++ b/crypto/blockchains/waves/WavesScannerProcessor.ts @@ -4,8 +4,9 @@ import AirDAOCryptoLog from '../../common/AirDAOCryptoLog'; import AirDAOAxios from '@crypto/common/AirDAOAxios'; import BlocksoftExternalSettings from '@crypto/common/AirDAOExternalSettings'; -import TransactionFilterTypeDict from '@appV2/dicts/transactionFilterTypeDict'; +// import TransactionFilterTypeDict from '@appV2/dicts/transactionFilterTypeDict'; import WavesTransactionsProvider from '@crypto/blockchains/waves/providers/WavesTransactionsProvider'; +// import TransactionFilterTypeDict from '@crypto/TransactionFilterTypeDict'; export default class WavesScannerProcessor { private _settings: any; @@ -140,7 +141,7 @@ export default class WavesScannerProcessor { senderPublicKey: transaction.senderPublicKey, signature: transaction.signature }; - unifiedTransaction.status = TransactionFilterTypeDict.CRYPTO_STATUS_PENDING; // pending + // unifiedTransaction.status = TransactionFilterTypeDict.CRYPTO_STATUS_PENDING; // pending return unifiedTransaction; } diff --git a/crypto/blockchains/waves/WavesScannerProcessorErc20.ts b/crypto/blockchains/waves/WavesScannerProcessorErc20.ts index c651ff1d2..e48d2ca95 100644 --- a/crypto/blockchains/waves/WavesScannerProcessorErc20.ts +++ b/crypto/blockchains/waves/WavesScannerProcessorErc20.ts @@ -5,8 +5,9 @@ import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; import AirDAOAxios from '@crypto/common/AirDAOAxios'; import BlocksoftExternalSettings from '@crypto/common/AirDAOExternalSettings'; import WavesScannerProcessor from '@crypto/blockchains/waves/WavesScannerProcessor'; -import TransactionFilterTypeDict from '@appV2/dicts/transactionFilterTypeDict'; +// import TransactionFilterTypeDict from '@appV2/dicts/transactionFilterTypeDict'; import BlocksoftUtils from '@crypto/common/AirDAOUtils'; +import TransactionFilterTypeDict from '@crypto/TransactionFilterTypeDict'; interface WavesScannerProcessorErc20Settings { currencyCode: string; diff --git a/crypto/blockchains/xmr/XmrScannerProcessor.ts b/crypto/blockchains/xmr/XmrScannerProcessor.ts index 3b0dfc059..527d19875 100644 --- a/crypto/blockchains/xmr/XmrScannerProcessor.ts +++ b/crypto/blockchains/xmr/XmrScannerProcessor.ts @@ -10,9 +10,8 @@ import BlocksoftPrivateKeysUtils from '@crypto/common/AirDAOPrivateKeysUtils'; import MoneroUtilsParser from './ext/MoneroUtilsParser'; -import { showModal } from '@app/appstores/Stores/Modal/ModalActions'; -import { strings } from '@app/services/i18n'; import config from '@constants/config'; +import { Toast, ToastPosition } from '@components/modular'; const CACHE_VALID_TIME = 30000; const CACHE = {}; @@ -128,11 +127,16 @@ export default class XmrScannerProcessor { CACHE_SHOWN_ERROR === 0 && e.message.indexOf('invalid address and/or view key') !== -1 ) { - showModal({ - type: 'INFO_MODAL', - icon: false, - title: strings('modal.walletLog.sorry'), - description: strings('settings.walletList.needReinstallXMR') + // showModal({ + // type: 'INFO_MODAL', + // icon: false, + // title: strings('modal.walletLog.sorry'), + // description: strings('settings.walletList.needReinstallXMR') + // }); + Toast.show({ + message: 'Need reinstall XMR', + title: 'Sorry', + type: ToastPosition.Top }); CACHE_SHOWN_ERROR++; if (CACHE_SHOWN_ERROR > 100) { @@ -164,11 +168,16 @@ export default class XmrScannerProcessor { CACHE_SHOWN_ERROR === 0 && e.message.indexOf('invalid address and/or view key') !== -1 ) { - showModal({ - type: 'INFO_MODAL', - icon: false, - title: strings('modal.walletLog.sorry'), - description: strings('settings.walletList.needReinstallXMR') + // showModal({ + // type: 'INFO_MODAL', + // icon: false, + // title: strings('modal.walletLog.sorry'), + // description: strings('settings.walletList.needReinstallXMR') + // }); + Toast.show({ + message: 'Need reinstall XMR', + title: 'Sorry', + type: ToastPosition.Top }); CACHE_SHOWN_ERROR++; if (CACHE_SHOWN_ERROR > 100) { diff --git a/crypto/blockchains/xrp/stores/XrpTmpDS.ts b/crypto/blockchains/xrp/stores/XrpTmpDS.ts index 797ba8481..151306792 100644 --- a/crypto/blockchains/xrp/stores/XrpTmpDS.ts +++ b/crypto/blockchains/xrp/stores/XrpTmpDS.ts @@ -1,7 +1,8 @@ // @ts-ignore -import Database from '@app/appstores/DataSource/Database'; // TODO import +import { DatabaseTable } from '@appTypes'; +import { Database, TransactionScannersTmpDBModel } from '@database'; -const tableName = 'transactions_scanners_tmp'; +const tableName = DatabaseTable.TransactionScannersTmp; class XrpTmpDS { /** @@ -15,18 +16,21 @@ class XrpTmpDS { _isSaved = false; async getCache() { - const res = await Database.query(` + const res = (await Database.unsafeRawQuery( + tableName, + ` SELECT id, tmp_key, tmp_val FROM ${tableName} WHERE currency_code='${this._currencyCode}' AND tmp_key='${this._valKey}' - `); + ` + )) as TransactionScannersTmpDBModel[]; let tmp = {}; const idsToRemove = []; - if (res.array) { + if (res) { let row; let found = false; - for (row of res.array) { + for (row of res) { if (found) { idsToRemove.push(row.id); } else { @@ -40,7 +44,8 @@ class XrpTmpDS { } } if (idsToRemove.length > 0) { - await Database.query( + await Database.unsafeRawQuery( + tableName, `DELETE FROM ${tableName} WHERE id IN (${idsToRemove.join(',')})` ); } @@ -52,7 +57,8 @@ class XrpTmpDS { const tmp = Database.escapeString(JSON.stringify(value)); const now = new Date().toISOString(); if (this._isSaved) { - await Database.query( + await Database.unsafeRawQuery( + tableName, `UPDATE ${tableName} SET tmp_val='${tmp}' WHERE tmp_key='${this._valKey}' AND currency_code='${this._currencyCode}'` ); } else { @@ -64,9 +70,7 @@ class XrpTmpDS { created_at: now } ]; - await Database.setTableName(tableName) - .setInsertData({ insertObjs: prepared }) - .insert(); + Database.createModel(tableName, prepared); this._isSaved = true; } } diff --git a/crypto/blockchains/xvg/stores/XvgTmpDS.ts b/crypto/blockchains/xvg/stores/XvgTmpDS.ts index 4894e87b0..22fe907c6 100644 --- a/crypto/blockchains/xvg/stores/XvgTmpDS.ts +++ b/crypto/blockchains/xvg/stores/XvgTmpDS.ts @@ -1,30 +1,34 @@ // TODO add Database // @ts-ignore -import Database from '@app/appstores/DataSource/Database'; +// import Database from '@app/appstores/DataSource/Database'; -const tableName = ' transactions_scanners_tmp'; +import { DatabaseTable } from '@appTypes'; +import { Database, TransactionScannersTmpDBModel } from '@database'; + +const tableName = DatabaseTable.TransactionScannersTmp; class XvgTmpDS { _currencyCode = 'XVG'; async getCache(address: any) { - const res = await Database.query(` - SELECT tmp_key, tmp_sub_key, tmp_val + const res = (await Database.unsafeRawQuery( + tableName, + ` + SELECT tmp_key as tmpKey, tmp_sub_key as tmpSubKey, tmp_val as tmpVal FROM ${tableName} WHERE currency_code='${this._currencyCode}' AND address='${address}' AND (tmp_sub_key='coins' OR tmp_sub_key='data') - `); + ` + )) as TransactionScannersTmpDBModel[]; const tmp = {}; - if (res.array) { - let row; - for (row of res.array) { + if (res) { + for (const row of res) { let val = 1; - if (row.tmp_sub_key !== 'data') { + if (row.tmpSubKey !== 'data') { val = JSON.parse(Database.unEscapeString(row.tmp_val)); } - // @ts-ignore - tmp[row.tmp_key + '_' + row.tmp_sub_key] = val; + tmp[row.tmpKey + '_' + row.tmpSubKey] = val; } } return tmp; @@ -42,9 +46,7 @@ class XvgTmpDS { created_at: now } ]; - await Database.setTableName(tableName) - .setInsertData({ insertObjs: prepared }) - .insert(); + Database.createModel(tableName, prepared); } } diff --git a/crypto/common/AirDAOBN.ts b/crypto/common/AirDAOBN.ts index 606480922..a8fa70a18 100644 --- a/crypto/common/AirDAOBN.ts +++ b/crypto/common/AirDAOBN.ts @@ -3,7 +3,6 @@ import BlocksoftUtils from './AirDAOUtils'; class AirDAOBN { innerBN = false; - ̰ constructor(val) { // console.log('AirDAOBN construct', JSON.stringify(val)) if (typeof val.innerBN !== 'undefined') { diff --git a/crypto/common/AirDAOExternalSettings.ts b/crypto/common/AirDAOExternalSettings.ts index 3ca93cb42..0450d7017 100644 --- a/crypto/common/AirDAOExternalSettings.ts +++ b/crypto/common/AirDAOExternalSettings.ts @@ -1,7 +1,7 @@ import AirDAOAxios from './AirDAOAxios'; import AirDAOCryptoLog from './AirDAOCryptoLog'; // @ts-ignore -import ApiProxy from '../../app/services/Api/ApiProxy'; +// import ApiProxy from '../../app/services/Api/ApiProxy'; import config from '@constants/config'; const MAX_CACHE_VALID_TIME = 6000000; // 100 minutes @@ -218,10 +218,12 @@ class AirDAOExternalSettings { return; } try { - const tmp = await ApiProxy.getAll({ - source: 'BlocksoftExternalSettings._get ' + source, - onlyFees: true - }); + // TODO implement fees + // const tmp = await ApiProxy.getAll({ + // source: 'BlocksoftExternalSettings._get ' + source, + // onlyFees: true + // }); + let tmp; CACHE_TIME = now; if (tmp && typeof tmp === 'object' && 'fees' in tmp && tmp.fees) { diff --git a/crypto/common/AirDAOUtils.ts b/crypto/common/AirDAOUtils.ts index ba3b1e35d..7244e367f 100644 --- a/crypto/common/AirDAOUtils.ts +++ b/crypto/common/AirDAOUtils.ts @@ -2,7 +2,6 @@ import { BigNumber } from 'bignumber.js'; // @ts-ignore import { hexToBn, bnToHex } from '../blockchains/eth/ext/estimateGas/util'; // @ts-ignore -import Log from '../../app/services/Log/Log'; import BigIntXmr from '@crypto/blockchains/xmr/ext/vendor/biginteger'; import Web3 from 'web3'; import { EtherUnits } from 'web3-utils'; @@ -325,7 +324,6 @@ class AirDAOUtils { newVal = Web3.utils.fromWei(tmp[0], 'gwei'); } catch (e: any) { e.message = JSON.stringify(val) + ' ' + e.message; - Log.err('BlocksoftUtils.toGwei error ' + e.message); } return newVal; } diff --git a/package.json b/package.json index 5acfcf7bb..790656274 100644 --- a/package.json +++ b/package.json @@ -105,6 +105,7 @@ "react-native-view-shot": "3.5.0", "react-native-walkthrough-tooltip": "^1.5.0", "ripple-lib": "^1.10.1", + "stellar-sdk": "^10.4.1", "tiny-secp256k1": "^2.2.3", "use-context-selector": "^1.4.1", "web3": "^4.0.3", diff --git a/src/lib/AirDAOKeys.ts b/src/lib/AirDAOKeys.ts deleted file mode 100644 index 8cfb8fc70..000000000 --- a/src/lib/AirDAOKeys.ts +++ /dev/null @@ -1,635 +0,0 @@ -import * as SecureStore from 'expo-secure-store'; -import KeysUtills from '@utils/keys'; -import config from '@constants/config'; -import AirDAODict from '@crypto/common/AirDAODict'; -import { bip32 } from 'bitcoinjs-lib'; -import BlocksoftDispatcher from '@lib/BlocksoftDispatcher'; -const networksConstants = require('../lib/common/ext/network-constants'); -const bip39 = require('bip39'); -const bip32 = require('bip32'); -const bs58check = require('bs58check'); - -const appCacheConfig = { - lifeTime: 3600 -}; - -type Network = { - wif: number; - bip32: { - public: number; - private: number; - }; - messagePrefix: string; - bech32?: string; - pubKeyHash: number; - scriptHash: number; - coin: string; -}; - -const ETH_CACHE = {}; -const CACHE = {}; - -class AirDAOKeys { - private _bipHex: any; - private async getRandomBytes(size: number): Promise { - // TODO implement the function to generate random bytes - return ''; - } - - private async _tonCheckRevert(mnemonic: string): Promise { - // TODO implement the function to check TON mnemonic - return false; - } - - public async newMnemonic(size = 256): Promise { - let mnemonic: string; - try { - const random = await this.getRandomBytes(size / 8); - mnemonic = bip39.entropyToMnemonic(Buffer.from(random, 'base64')); - if (await this._tonCheckRevert(mnemonic)) { - throw new Error('TON Mnemonic is not validating'); - } - } catch (e: any) { - throw new Error(`AirDAOKeys newMnemonic error ${e.message}`); - } - return mnemonic; - } - - async validateMnemonic(mnemonic: string): Promise { - const result = bip39.validateMnemonic(mnemonic); - if (!result) { - throw new Error('invalid mnemonic for bip39'); - } - return result; - } - - async getSeedCached(mnemonic: string): Promise { - const mnemonicCache = mnemonic.toLowerCase(); - let seed: Buffer; - try { - seed = await SecureStore.getItemAsync(mnemonicCache); - if (!seed) { - throw new Error(); - } - } catch { - seed = await KeysUtills.bip39MnemonicToSeed(mnemonic.toLowerCase()); - await SecureStore.setItemAsync(mnemonicCache, seed.toString()); - } - return seed; - } - - async setEthCached(mnemonic: string, result: any): Promise { - const mnemonicCache = mnemonic.toLowerCase(); - await SecureStore.setItemAsync(mnemonicCache, JSON.stringify(result)); - } - - async getEthCached(mnemonic: string): Promise { - const mnemonicCache = mnemonic.toLowerCase(); - const result = await SecureStore.getItemAsync(mnemonicCache); - if (!result) { - return false; - } - return JSON.parse(result); - } - - async getBip32Cached( - mnemonic: string, - network?: Network, - seed?: Buffer - ): Promise { - const mnemonicCache = mnemonic.toLowerCase(); - let root: bip32.BIP32Interface | null = null; - const cachedRoot = await SecureStore.getItemAsync(mnemonicCache); - if (cachedRoot) { - root = bip32.fromBase58(cachedRoot); - } else { - if (!seed) { - seed = await this.getSeedCached(mnemonic); - } - root = network ? bip32.fromSeed(seed, network) : bip32.fromSeed(seed); - await SecureStore.setItemAsync(mnemonicCache, root.toBase58()); - } - return root; - } - - async discoverAddresses(data: any, source: string) { - const logData = { ...data }; - if (typeof logData.mnemonic !== 'undefined') logData.mnemonic = '***'; - let toDiscover = AirDAODict.Codes; - if (data.currencyCode) { - if (typeof data.currencyCode === 'string') { - toDiscover = [data.currencyCode]; - } else { - toDiscover = data.currencyCode; - } - } - const fromIndex = data.fromIndex ? data.fromIndex : 0; - const toIndex = data.toIndex ? data.toIndex : 10; - const fullTree = data.fullTree ? data.fullTree : false; - const results = {}; - const mnemonicCache = data.mnemonic.toLowerCase(); - let bitcoinRoot = false; - let currencyCode; - let settings; - const seed = await KeysUtills.bip39MnemonicToSeed( - data.mnemonic.toLowerCase() - ); - for (currencyCode of toDiscover) { - results[currencyCode] = []; - try { - settings = AirDAODict.getCurrencyAllSettings( - currencyCode, - 'BlocksoftKeys' - ); - } catch (e) { - // do nothing for now - continue; - } - - let hexes = []; - if (settings.addressCurrencyCode) { - hexes.push(this._bipHex[settings.addressCurrencyCode]); - if (!this._bipHex[settings.addressCurrencyCode]) { - throw new Error( - 'UNKNOWN_CURRENCY_CODE SETTED ' + settings.addressCurrencyCode - ); - } - } - - if (this._bipHex[currencyCode]) { - hexes.push(this._bipHex[currencyCode]); - } else if (!settings.addressCurrencyCode) { - if ( - settings.extendsProcessor && - this._bipHex[settings.extendsProcessor] - ) { - hexes.push(this._bipHex[settings.extendsProcessor]); - } else { - throw new Error( - 'UNKNOWN_CURRENCY_CODE ' + - currencyCode + - ' in bipHex AND NO SETTED addressCurrencyCode' - ); - } - } - - let isAlreadyMain = false; - - if (!data.fullTree) { - hexes = [hexes[0]]; - } - - const hexesCache = - mnemonicCache + - '_' + - settings.addressProcessor + - '_fromINDEX_' + - fromIndex + - '_' + - JSON.stringify(hexes); - let hasDerivations = false; - if ( - typeof data.derivations !== 'undefined' && - typeof data.derivations[currencyCode] !== 'undefined' && - data.derivations[currencyCode] - ) { - hasDerivations = true; - } - if (typeof CACHE[hexesCache] === 'undefined' || hasDerivations) { - // AirDAOCryptoLog.log(`BlocksoftKeys will discover ${settings.addressProcessor}`) - let root = false; - if (typeof networksConstants[currencyCode] !== 'undefined') { - root = await this.getBip32Cached( - data.mnemonic, - networksConstants[currencyCode], - seed - ); - } else { - if (!bitcoinRoot) { - bitcoinRoot = await this.getBip32Cached(data.mnemonic); - } - root = bitcoinRoot; - } - // BIP32 Extended Private Key to check - uncomment - // let childFirst = root.derivePath('m/44\'/2\'/0\'/0') - // AirDAOCryptoLog.log(childFirst.toBase58()) - - /** - * @type {EthAddressProcessor|BtcAddressProcessor} - */ - const processor = await BlocksoftDispatcher.innerGetAddressProcessor( - settings - ); - - try { - await processor.setBasicRoot(root); - } catch (e) { - e.message += ' while doing ' + JSON.stringify(settings); - throw e; - } - let currentFromIndex = fromIndex; - let currentToIndex = toIndex; - let currentFullTree = fullTree; - if (hasDerivations) { - let derivation = { path: '', alreadyShown: 0, walletPubId: 0 }; - let maxIndex = 0; - for (derivation of data.derivations[currencyCode]) { - const child = root.derivePath(derivation.path); - const tmp = derivation.path.split('/'); - const result = await processor.getAddress( - child.privateKey, - { - publicKey: child.publicKey, - walletHash: data.walletHash, - derivationPath: derivation.path - }, - data, - seed, - 'discoverAddresses' - ); - result.basicPrivateKey = child.privateKey.toString('hex'); - result.basicPublicKey = child.publicKey.toString('hex'); - result.path = derivation.path; - result.alreadyShown = derivation.alreadyShown; - result.walletPubId = derivation.walletPubId || 0; - result.index = tmp[5]; - if (maxIndex < result.index) { - maxIndex = result.index * 1; - } - result.type = 'main'; - results[currencyCode].push(result); - } - if (maxIndex > 0) { - // noinspection PointlessArithmeticExpressionJS - currentFromIndex = maxIndex * 1 + 1; - currentToIndex = currentFromIndex * 1 + 10; - currentFullTree = true; - } - } - - let suffixes; - if (currencyCode === 'SOL') { - suffixes = [ - { type: 'main', suffix: false, after: `'/0'` }, - { type: 'no_scan', suffix: false, after: `'` } - ]; - } else if (currentFullTree) { - suffixes = [ - { type: 'main', suffix: `0'/0` }, - { type: 'change', suffix: `0'/1` } - // { 'type': 'second', 'suffix': `1'/0` }, - // { 'type': 'secondchange', 'suffix': `1'/1` } - ]; - } else { - suffixes = [{ type: 'main', suffix: `0'/0` }]; - if (currencyCode === 'BTC_SEGWIT_COMPATIBLE') { - suffixes = [ - { type: 'main', suffix: '0/1' } // heh - ]; - } - hexes = [hexes[0]]; - } - - let hex; - for (hex of hexes) { - if (isAlreadyMain) { - suffixes[0].type = 'second'; - } - isAlreadyMain = true; - - if (currentFromIndex >= 0 && currentToIndex >= 0) { - for ( - let index = currentFromIndex; - index < currentToIndex; - index++ - ) { - let suffix; - for (suffix of suffixes) { - const path = suffix.suffix - ? `m/${hex}'/${suffix.suffix}/${index}` - : `m/${hex}'/${index}${suffix.after}`; - let privateKey = false; - let publicKey = false; - if ( - currencyCode === 'SOL' || - currencyCode === 'XLM' || - currencyCode === 'WAVES' || - currencyCode === 'ASH' - ) { - // @todo move to coin address processor - } else { - const child = root.derivePath(path); - privateKey = child.privateKey; - publicKey = child.publicKey; - } - const result = await processor.getAddress( - privateKey, - { - publicKey, - walletHash: data.walletHash, - derivationPath: path, - derivationIndex: index, - derivationType: suffix.type - }, - data, - seed, - 'discoverAddresses2' - ); - if (result) { - if (privateKey) { - result.basicPrivateKey = privateKey.toString('hex'); - result.basicPublicKey = publicKey.toString('hex'); - } - result.path = path; - result.index = index; - result.alreadyShown = 0; - result.type = suffix.type; - results[currencyCode].push(result); - } - } - } - } - } - CACHE[hexesCache] = results[currencyCode]; - if (currencyCode === 'ETH') { - ETH_CACHE[mnemonicCache] = results[currencyCode][0]; - } - } else { - results[currencyCode] = CACHE[hexesCache]; - if (currencyCode === 'USDT') { - results[currencyCode] = [results[currencyCode][0]]; - } - } - } - return results; - } - - // TODO discoverOne - - async discoverOne(data) { - const seed = await KeysUtills.bip39MnemonicToSeed( - data.mnemonic.toLowerCase() - ); - const root = bip32.fromSeed(seed); - const child = root.derivePath(data.derivationPath.replace(/quote/g, "'")); - /** - * @type {EthAddressProcessor|BtcAddressProcessor} - */ - // const processor = await BlocksoftDispatcher.getAddressProcessor( - // data.currencyCode - // ); - // processor.setBasicRoot(root); - // return processor.getAddress( - // child.privateKey, - // { - // derivationPath: data.derivationPath, - // derivationIndex: data.derivationIndex, - // derivationType: data.derivationType, - // publicKey: child.publicKey - // }, - // data, - // seed, - // 'discoverOne' - // ); - } - - async discoverXpub(data: { - mnemonic: string; - currencyCode: string; - }): Promise { - try { - const seed = await KeysUtills.bip39MnemonicToSeed( - data.mnemonic.toLowerCase() - ); - const root = bip32.fromSeed(seed); - let path = `m/44'/0'/0'`; - let version = 0x0488b21e; - if (data.currencyCode === 'BTC_SEGWIT_COMPATIBLE') { - path = `m/49'/0'/0'`; - version = 0x049d7cb2; - } - const rootWallet = root.derivePath(path); - const res = bs58check.encode( - this.serialize(rootWallet, version, rootWallet.publicKey) - ); - return res; - } catch (error) { - throw error; - } - } - - serialize(hdkey: any, version: number, key: Buffer, LEN = 78): Buffer { - try { - const buffer = Buffer.allocUnsafe(LEN); - buffer.writeUInt32BE(version, 0); - buffer.writeUInt8(hdkey.depth, 4); - - const fingerprint = hdkey.depth ? hdkey.parentFingerprint : 0x00000000; - buffer.writeUInt32BE(fingerprint, 5); - buffer.writeUInt32BE(hdkey.index, 9); - buffer.copy(hdkey.chainCode, 0, 13); - key.copy(buffer, 45); - - return buffer; - } catch (error) { - throw error; - } - } - - async setSecureKeyValue(key: string, value: string): Promise { - let text = ''; - try { - text = await KeysUtills.hashMnemonic(value); - await SecureStore.setItemAsync(key, text); - } catch (e: any) { - throw new Error(`AirDAOKeys setSecureKeyValue error ${e.message}`); - } - } - - async getSecureKeyValue(key: string): Promise { - let text: string | null = ''; - try { - text = await SecureStore.getItemAsync(key); - if (text) { - const result = KeysUtills.hashMnemonic(text); - return result; - } else { - return null; - } - } catch (e: any) { - throw new Error(`AirDAOKeys getSecureKeyValue error ${e.message}`); - } - } - - async removeSecureKeyValue(key: string): Promise { - try { - await SecureStore.deleteItemAsync(key); - } catch (e: any) { - throw new Error(`AirDAOKeys removeSecureKeyValue error ${e.message}`); - } - } - - async setMnemonic(value: string, walletHash: string): Promise { - const key = `mnemonic_${walletHash}`; - await this.setSecureKeyValue(key, value); - } - - async getMnemonic(currencyCode: string): Promise { - const key = `mnemonic_${currencyCode}`; - return await this.getSecureKeyValue(key); - } - - async removeMnemonic(currencyCode: string): Promise { - const key = `mnemonic_${currencyCode}`; - await this.removeSecureKeyValue(key); - } - - async setWallet( - walletHash: string, - walletPubId: number, - currencyCode: string, - walletData: any - ): Promise { - const key = `wallet_${walletHash}_${walletPubId}_${currencyCode}`; - await this.setSecureKeyValue(key, JSON.stringify(walletData)); - } - - async getWallet( - walletHash: string, - walletPubId: number, - currencyCode: string - ): Promise { - const key = `wallet_${walletHash}_${walletPubId}_${currencyCode}`; - const value = await this.getSecureKeyValue(key); - if (value) { - return JSON.parse(value); - } else { - return null; - } - } - - async removeWallet( - walletHash: string, - walletPubId: number, - currencyCode: string - ): Promise { - const key = `wallet_${walletHash}_${walletPubId}_${currencyCode}`; - await this.removeSecureKeyValue(key); - } - - async setSelectedWallet( - walletHash: string, - walletPubId: number, - currencyCode: string - ): Promise { - const key = `selected_wallet_${currencyCode}`; - await this.setSecureKeyValue(key, `${walletHash}_${walletPubId}`); - } - - async getSelectedWallet( - currencyCode: string - ): Promise<{ walletHash: string; walletPubId: number } | null> { - const key = `selected_wallet_${currencyCode}`; - const value = await this.getSecureKeyValue(key); - if (value) { - const values = value.split('_'); - return { - walletHash: values[0], - walletPubId: parseInt(values[1], 10) - }; - } else { - return null; - } - } - - async removeSelectedWallet(currencyCode: string): Promise { - const key = `selected_wallet_${currencyCode}`; - await this.removeSecureKeyValue(key); - } - - async setCache(key: string, value: any): Promise { - const cacheSettings = appCacheConfig; - if (cacheSettings && cacheSettings.lifeTime && cacheSettings.lifeTime > 0) { - const cacheKey = `cache_${key}`; - await this.setSecureKeyValue( - cacheKey, - JSON.stringify({ value, timestamp: new Date().getTime() }) - ); - } - } - - async getCache(key: string): Promise { - const cacheSettings = appCacheConfig; - if (cacheSettings && cacheSettings.lifeTime && cacheSettings.lifeTime > 0) { - const cacheKey = `cache_${key}`; - const value = await this.getSecureKeyValue(cacheKey); - if (value) { - const { value: cachedValue, timestamp } = JSON.parse(value); - const currentTime = new Date().getTime(); - if (currentTime - timestamp < cacheSettings.lifeTime * 1000) { - return cachedValue; - } else { - await this.removeSecureKeyValue(cacheKey); - return null; - } - } else { - return null; - } - } else { - return null; - } - } - - async removeCache(key: string): Promise { - const cacheSettings = appCacheConfig; - if (cacheSettings && cacheSettings.lifeTime && cacheSettings.lifeTime > 0) { - const cacheKey = `cache_${key}`; - await this.removeSecureKeyValue(cacheKey); - } - } - - async setAppVersion(version: string): Promise { - const key = 'app_version'; - await this.setSecureKeyValue(key, version); - } - - async getAppVersion(): Promise { - const key = 'app_version'; - return await this.getSecureKeyValue(key); - } - - async setLog(logData: any): Promise { - const key = `logs_${config.debug.appBuildVersion}_${logData.type}_${logData.hash}`; - await this.setSecureKeyValue(key, JSON.stringify(logData)); - } - - async getLogs(logType: string): Promise { - const logs = []; - const keys = await SecureStore.getItemAsync('keys'); - if (keys) { - for (const key of keys) { - if (key.includes(`logs_${config.debug.appBuildVersion}_${logType}`)) { - const value = await this.getSecureKeyValue(key); - if (value) { - logs.push(JSON.parse(value)); - } - } - } - } - logs.sort((a, b) => b.time - a.time); - return logs; - } - - async removeLogs(logType: string): Promise { - const keys = await SecureStore.getItemAsync('keys'); - if (keys) { - for (const key of keys) { - if (key.includes(`logs_${config.debug.appBuildVersion}_${logType}`)) { - await this.removeSecureKeyValue(key); - } - } - } - } -} - -export default new AirDAOKeys(); diff --git a/src/lib/AirDAOKeysStorage.ts b/src/lib/AirDAOKeysStorage.ts deleted file mode 100644 index c55077bf7..000000000 --- a/src/lib/AirDAOKeysStorage.ts +++ /dev/null @@ -1,300 +0,0 @@ -import * as SecureStore from 'expo-secure-store'; -import { WalletMetadata } from '@appTypes'; - -export class AirDAOKeysStorage { - private serviceName = ''; - private serviceWalletsCounter = 0; - private serviceWallets: { [key: string]: string } = {}; - private serviceWasInitialized = false; - public publicWallets: string[] = []; - public publicSelectedWallet: string | false = false; - - constructor(serviceName = 'AirDAOStorage') { - this.serviceName = serviceName; - } - - private sanitizeKey(key: string): string { - return key.replace(/[^A-Za-z0-9._-]/g, '_'); - } - - private async _getServiceName(name: string) { - this.serviceName = name; - this.serviceWasInitialized = false; - await this.init(); - } - - private async getKeyValue(key: string): Promise { - try { - const sanitizedKey = this.sanitizeKey(this.serviceName + '_' + key); - const credentials = await SecureStore.getItemAsync(sanitizedKey); - if (!credentials) return false; - return JSON.parse(credentials) as WalletMetadata; - } catch (e) { - console.error('AirDAOStorage getKeyValue error ', e); - return false; - } - } - - private async setKeyValue( - key: string, - data: WalletMetadata - ): Promise { - try { - const sanitizedKey = this.sanitizeKey(this.serviceName + '_' + key); - await SecureStore.setItemAsync(sanitizedKey, JSON.stringify(data)); - return true; - } catch (e) { - console.error('AirDAOStorage setKeyValue error ', e); - if (key.indexOf('wallet') !== -1) { - throw e; - } - return false; - } - } - - private async init() { - if (this.serviceWasInitialized) { - return true; - } - - const count = await this.getKeyValue('wallets_counter'); - - this.serviceWalletsCounter = count && count.number ? count.number : 0; - this.serviceWallets = {}; - this.publicWallets = []; - this.publicSelectedWallet = false; - if (this.serviceWalletsCounter > 0) { - for (let i = 1; i <= this.serviceWalletsCounter; i++) { - const wallet = await this.getKeyValue('wallet_' + i); - if ( - !wallet || - !wallet.mnemonic || - wallet.mnemonic === '' || - wallet.mnemonic === - 'new wallet is not generated - please reinstall and restart' || - wallet.mnemonic === wallet.hash - ) { - continue; - } - this.serviceWallets[wallet.hash] = wallet.mnemonic; - this.serviceWallets[i - 1] = wallet.mnemonic; - this.publicWallets.push(wallet.hash); - } - } - this.serviceWasInitialized = true; - } - - async getOldSelectedWallet() { - await this.init(); - const tmp = await this.getKeyValue('selected_hash'); - let publicSelectedWallet: string | boolean = false; - if (tmp && tmp.hash) { - publicSelectedWallet = tmp.hash; - } - if ( - !this.publicSelectedWallet || - !this.serviceWallets[this.publicSelectedWallet] - ) { - publicSelectedWallet = false; - } - return publicSelectedWallet; - } - - async reInit() { - this.serviceWasInitialized = false; - return this.init(); - } - - async getOneWalletText( - walletHash: string, - discoverPath: string | false, - currencyCode: string - ) { - try { - if (typeof discoverPath === 'undefined' || discoverPath === false) { - const res = await this.getKeyValue(walletHash); - if (!res) return false; - return res.mnemonic; - } else { - const key = this.getAddressCacheKey( - walletHash, - discoverPath, - currencyCode - ); - return this.getAddressCache(key); - } - } catch (e) { - console.error(e); - } - return false; - } - - async getAllWalletsText() { - let res = ''; - for (let i = 1; i <= 3; i++) { - try { - const wallet = await this.getKeyValue('wallet_' + i); - if ( - wallet && - typeof wallet.mnemonic !== 'undefined' && - wallet.mnemonic !== 'undefined' - ) { - res += ' WALLET#' + i + ' ' + wallet.mnemonic; - } - } catch (e) { - console.error(e); - } - } - if (res === '') { - return 'Nothing found by general search'; - } - return res; - } - - async countMnemonics() { - await this.init(); - return this.serviceWalletsCounter; - } - - async getWallets() { - await this.init(); - return this.publicWallets; - } - - async getWalletMnemonic(hashOrId: string, source = 'default') { - await this.init(); - if (!this.serviceWallets[hashOrId]) { - throw new Error( - 'undefined wallet with hash ' + hashOrId + ' source ' + source - ); - } - return this.serviceWallets[hashOrId]; - } - - async isMnemonicAlreadySaved(newMnemonic: { - mnemonic: string; - hash: string; - }) { - await this.init(); - if ( - typeof this.serviceWallets[newMnemonic.hash] === 'undefined' || - !this.serviceWallets[newMnemonic.hash] - ) { - return false; - } - if (this.serviceWallets[newMnemonic.hash] !== newMnemonic.mnemonic) { - throw new Error('something wrong with hash algorithm'); - } - return true; - } - - async saveMnemonic(newMnemonic: { - mnemonic: string; - hash: string; - pub: string; - name: string; - number: number; - }) { - await this.init(); - - const logData = { ...newMnemonic }; - if (typeof logData.mnemonic !== 'undefined') logData.mnemonic = '***'; - if (!newMnemonic.hash) { - throw new Error('unique hash required ' + JSON.stringify(newMnemonic)); - } - if ( - typeof this.serviceWallets[newMnemonic.hash] !== 'undefined' && - this.serviceWallets[newMnemonic.hash] - ) { - if (this.serviceWallets[newMnemonic.hash] !== newMnemonic.mnemonic) { - throw new Error('something wrong with hash algorithm'); - } - return newMnemonic.hash; - } - this.serviceWalletsCounter++; - - const unique = newMnemonic.hash; - - await this.setKeyValue(unique, newMnemonic); - await this.setKeyValue('wallet_' + this.serviceWalletsCounter, newMnemonic); - await this.setKeyValue('wallets_counter', { - pub: '', - hash: '', - mnemonic: '', - name: '', - number: this.serviceWalletsCounter - }); - this.serviceWallets[unique] = newMnemonic.mnemonic; - this.serviceWallets[this.serviceWalletsCounter - 1] = newMnemonic.mnemonic; - - this.publicWallets.push(unique); - this.publicSelectedWallet = unique; - - return newMnemonic.hash; - } - - getAddressCacheKey( - walletHash: string, - discoverPath: string, - currencyCode: string - ) { - return walletHash + '_' + discoverPath + '_' + currencyCode; - } - - async getAddressCache(hashOrId: string) { - try { - const res = await this.getKeyValue('ar4_' + hashOrId); - if (!res || !res.mnemonic || res.pub === res.mnemonic) return false; - return { address: res.pub, privateKey: res.mnemonic }; - } catch (e) { - return false; - } - } - - async setAddressCache( - hashOrId: string, - res: { - address: string; - privateKey: string; - pub: string; - name: string; - mnemonic: string; - number: number; - hash: string; - } - ) { - if (typeof res.privateKey === 'undefined' || !res.privateKey) { - return false; - } - return this.setKeyValue('ar4_' + hashOrId, res); - } - - // async getLoginCache(hashOrId: string) { - // const res = await this.getKeyValue('login_' + hashOrId); - // if (!res) return false; - // return { login: res.pub, pass: res.mnemonic }; - // } - // - // async setLoginCache(hashOrId: string, res: { login: string; pass: string }) { - // return this.setKeyValue('login_' + hashOrId, res); - // } - - async setSettingValue(hashOrId: string, value: number) { - return this.setKeyValue('setting_' + hashOrId, { - pub: '', - hash: hashOrId, - number: value, - mnemonic: '', - name: '' - }); - } - - async getSettingValue(hashOrId: string) { - const res = await this.getKeyValue('setting_' + hashOrId); - if (!res) return '0'; - return res.number.toString(); - } -} - -const singleAirDAOStorage = new AirDAOKeysStorage(); -export default singleAirDAOStorage; diff --git a/src/lib/BlocksoftDispatcher.js b/src/lib/BlocksoftDispatcher.js deleted file mode 100644 index 851e31374..000000000 --- a/src/lib/BlocksoftDispatcher.js +++ /dev/null @@ -1,315 +0,0 @@ -/** - * @author Ksu - * @version 0.5 - */ -import AirDAODict from '../common/AirDAODict'; - -import BchAddressProcessor from './bch/BchAddressProcessor'; -import BchScannerProcessor from './bch/BchScannerProcessor'; - -import BsvScannerProcessor from './bsv/BsvScannerProcessor'; - -import BtcAddressProcessor from './btc/address/BtcAddressProcessor'; -import BtcScannerProcessor from './btc/BtcScannerProcessor'; - -import BtcSegwitCompatibleAddressProcessor from './btc/address/BtcSegwitCompatibleAddressProcessor'; -import BtcSegwitAddressProcessor from './btc/address/BtcSegwitAddressProcessor'; - -import BtcTestScannerProcessor from './btc_test/BtcTestScannerProcessor'; - -import BtgScannerProcessor from './btg/BtgScannerProcessor'; - -import DogeScannerProcessor from './doge/DogeScannerProcessor'; - -import EthAddressProcessor from './eth/EthAddressProcessor'; -import EthScannerProcessor from './eth/EthScannerProcessor'; -import EthScannerProcessorErc20 from './eth/EthScannerProcessorErc20'; -import EthScannerProcessorSoul from './eth/forks/EthScannerProcessorSoul'; -import EthTokenProcessorErc20 from './eth/EthTokenProcessorErc20'; - -import LtcScannerProcessor from './ltc/LtcScannerProcessor'; - -import TrxAddressProcessor from './trx/TrxAddressProcessor'; -import TrxScannerProcessor from './trx/TrxScannerProcessor'; -import TrxTokenProcessor from './trx/TrxTokenProcessor'; - -import UsdtScannerProcessor from './usdt/UsdtScannerProcessor'; - -import XrpAddressProcessor from './xrp/XrpAddressProcessor'; -import XrpScannerProcessor from './xrp/XrpScannerProcessor'; - -import XlmAddressProcessor from './xlm/XlmAddressProcessor'; -import XlmScannerProcessor from './xlm/XlmScannerProcessor'; - -import XvgScannerProcessor from './xvg/XvgScannerProcessor'; - -import XmrAddressProcessor from './xmr/XmrAddressProcessor'; -import XmrScannerProcessor from './xmr/XmrScannerProcessor'; -import XmrSecretsProcessor from './xmr/XmrSecretsProcessor'; -import FioAddressProcessor from './fio/FioAddressProcessor'; -import FioScannerProcessor from './fio/FioScannerProcessor'; - -import BnbAddressProcessor from './bnb/BnbAddressProcessor'; -import BnbScannerProcessor from './bnb/BnbScannerProcessor'; -import VetScannerProcessor from '@crypto/blockchains/vet/VetScannerProcessor'; - -import SolAddressProcessor from '@crypto/blockchains/sol/SolAddressProcessor'; -import SolScannerProcessor from '@crypto/blockchains/sol/SolScannerProcessor'; - -import WavesAddressProcessor from '@crypto/blockchains/waves/WavesAddressProcessor'; -import WavesScannerProcessor from '@crypto/blockchains/waves/WavesScannerProcessor'; - -import SolScannerProcessorSpl from '@crypto/blockchains/sol/SolScannerProcessorSpl'; -import SolTokenProcessor from '@crypto/blockchains/sol/SolTokenProcessor'; -import EthTokenProcessorNft from '@crypto/blockchains/eth/EthTokenProcessorNft'; -import AshAddressProcessor from '@crypto/blockchains/ash/AshAddressProcessor'; - -import MetisScannerProcessor from '@crypto/blockchains/metis/MetisScannerProcessor'; -import OneScannerProcessor from '@crypto/blockchains/one/OneScannerProcessor'; -import OneScannerProcessorErc20 from '@crypto/blockchains/one/OneScannerProcessorErc20'; - -import WavesScannerProcessorErc20 from '@crypto/blockchains/waves/WavesScannerProcessorErc20'; - -class BlocksoftDispatcher { - /** - * @param {string} currencyCode - * @return {EthAddressProcessor|BtcAddressProcessor} - */ - getAddressProcessor(currencyCode) { - const currencyDictSettings = - AirDAODict.getCurrencyAllSettings(currencyCode); - return this.innerGetAddressProcessor(currencyDictSettings); - } - - /** - * @param {Object} currencyDictSettings - * @return {EthAddressProcessor|BtcAddressProcessor|TrxAddressProcessor} - */ - innerGetAddressProcessor(currencyDictSettings) { - switch (currencyDictSettings.addressProcessor) { - case 'BCH': - return new BchAddressProcessor(currencyDictSettings); - case 'BTC': - return new BtcAddressProcessor(currencyDictSettings); - case 'BTC_SEGWIT': - case 'LTC_SEGWIT': - return new BtcSegwitAddressProcessor(currencyDictSettings); - case 'BTC_SEGWIT_COMPATIBLE': - return new BtcSegwitCompatibleAddressProcessor(currencyDictSettings); - case 'ETH': - return new EthAddressProcessor(currencyDictSettings); - case 'TRX': - return new TrxAddressProcessor(); - case 'XRP': - return new XrpAddressProcessor(); - case 'XLM': - return new XlmAddressProcessor(); - case 'XMR': - return new XmrAddressProcessor(); - case 'FIO': - return new FioAddressProcessor(); - case 'BNB': - return new BnbAddressProcessor(); - case 'SOL': - return new SolAddressProcessor(); - case 'WAVES': - return new WavesAddressProcessor(); - case 'ASH': - return new AshAddressProcessor(); - default: - throw new Error( - 'Unknown addressProcessor ' + currencyDictSettings.addressProcessor - ); - } - } - - /** - * @param {string} currencyCode - * @returns {BsvScannerProcessor|BtcScannerProcessor|UsdtScannerProcessor|EthScannerProcessorErc20|BchScannerProcessor|LtcScannerProcessor|XvgScannerProcessor|BtcTestScannerProcessor|DogeScannerProcessor|EthScannerProcessorSoul|EthScannerProcessor|BtgScannerProcessor|TrxScannerProcessor} - */ - getScannerProcessor(currencyCode) { - const currencyDictSettings = - AirDAODict.getCurrencyAllSettings(currencyCode); - switch (currencyDictSettings.scannerProcessor) { - case 'BCH': - return new BchScannerProcessor(currencyDictSettings); - case 'BSV': - return new BsvScannerProcessor(currencyDictSettings); - case 'BTC': - case 'BTC_SEGWIT': - case 'BTC_SEGWIT_COMPATIBLE': - return new BtcScannerProcessor(currencyDictSettings); - case 'BTC_TEST': - return new BtcTestScannerProcessor(currencyDictSettings); - case 'BTG': - return new BtgScannerProcessor(currencyDictSettings); - case 'DOGE': - return new DogeScannerProcessor(currencyDictSettings); - case 'ETH': - return new EthScannerProcessor(currencyDictSettings); - case 'ETH_ERC_20': - return new EthScannerProcessorErc20(currencyDictSettings); - case 'ETH_SOUL': - return new EthScannerProcessorSoul(currencyDictSettings); - case 'LTC': - return new LtcScannerProcessor(currencyDictSettings); - case 'TRX': - return new TrxScannerProcessor(currencyDictSettings); - case 'USDT': - return new UsdtScannerProcessor(currencyDictSettings); - case 'XRP': - return new XrpScannerProcessor(currencyDictSettings); - case 'XLM': - return new XlmScannerProcessor(currencyDictSettings); - case 'XVG': - return new XvgScannerProcessor(currencyDictSettings); - case 'XMR': - return new XmrScannerProcessor(currencyDictSettings); - case 'FIO': - return new FioScannerProcessor(currencyDictSettings); - case 'BNB': - return new BnbScannerProcessor(currencyDictSettings); - case 'VET': - return new VetScannerProcessor(currencyDictSettings); - case 'SOL': - return new SolScannerProcessor(currencyDictSettings); - case 'SOL_SPL': - return new SolScannerProcessorSpl(currencyDictSettings); - case 'WAVES': - return new WavesScannerProcessor(currencyDictSettings); - case 'METIS': - return new MetisScannerProcessor(currencyDictSettings); - case 'ONE': - return new OneScannerProcessor(currencyDictSettings); - case 'ONE_ERC_20': - return new OneScannerProcessorErc20(currencyDictSettings); - case 'WAVES_ERC_20': - return new WavesScannerProcessorErc20(currencyDictSettings); - default: - throw new Error( - 'Unknown scannerProcessor ' + currencyDictSettings.scannerProcessor - ); - } - } - - /** - * @param tokenType - * @returns {TrxTokenProcessor|EthTokenProcessorErc20} - */ - getTokenProcessor(tokenType) { - switch (tokenType) { - case 'ETH_ERC_20': - return new EthTokenProcessorErc20({ - network: 'mainnet', - tokenBlockchain: 'ETHEREUM' - }); - case 'BNB_SMART_20': - return new EthTokenProcessorErc20({ - network: 'mainnet', - tokenBlockchain: 'BNB' - }); - case 'MATIC_ERC_20': - return new EthTokenProcessorErc20({ - network: 'mainnet', - tokenBlockchain: 'MATIC' - }); - case 'FTM_ERC_20': - return new EthTokenProcessorErc20({ - network: 'mainnet', - tokenBlockchain: 'FTM' - }); - case 'VLX_ERC_20': - return new EthTokenProcessorErc20({ - network: 'mainnet', - tokenBlockchain: 'VLX' - }); - case 'ONE_ERC_20': - return new EthTokenProcessorErc20({ - network: 'mainnet', - tokenBlockchain: 'ONE' - }); - case 'METIS_ERC_20': - return new EthTokenProcessorErc20({ - network: 'mainnet', - tokenBlockchain: 'METIS' - }); - case 'TRX': - return new TrxTokenProcessor(); - case 'SOL': - return new SolTokenProcessor(); - default: - throw new Error('Unknown tokenProcessor ' + tokenType); - } - } - - /** - * @param tokenBlockchainCode - * @returns {EthTokenProcessorNft} - */ - getTokenNftsProcessor(tokenBlockchainCode) { - switch (tokenBlockchainCode) { - case 'ETH': - case 'NFT_ETH': - return new EthTokenProcessorNft({ - network: 'mainnet', - tokenBlockchain: 'ETHEREUM', - tokenBlockchainCode: 'ETH' - }); - case 'ETH_RINKEBY': - case 'NFT_RINKEBY': - return new EthTokenProcessorNft({ - network: 'rinkeby', - tokenBlockchain: 'RINKEBY', - tokenBlockchainCode: 'ETH_RINKEBY' - }); - case 'MATIC': - case 'NFT_MATIC': - return new EthTokenProcessorNft({ - network: 'mainnet', - tokenBlockchain: 'MATIC', - tokenBlockchainCode: 'MATIC' - }); - case 'BNB': - case 'NFT_BNB': - return new EthTokenProcessorNft({ - network: 'mainnet', - tokenBlockchain: 'BNB', - tokenBlockchainCode: 'BNB' - }); - case 'ONE': - case 'NFT_ONE': - return new EthTokenProcessorNft({ - network: 'mainnet', - tokenBlockchain: 'ONE', - tokenBlockchainCode: 'ONE' - }); - case 'ETH_ROPSTEN': - case 'NFT_ROPSTEN': - return new EthTokenProcessorNft({ - network: 'ropsten', - tokenBlockchain: 'ROPSTEN', - tokenBlockchainCode: 'ETH_ROPSTEN' - }); - default: - throw new Error('Unknown NFT tokenProcessor ' + tokenBlockchainCode); - } - } - - /** - * @param {string} currencyCode - * @return {XmrSecretsProcessor} - */ - getSecretsProcessor(currencyCode) { - const currencyDictSettings = - AirDAODict.getCurrencyAllSettings(currencyCode); - if (currencyDictSettings.currencyCode !== 'XMR') { - throw new Error( - 'Unknown secretsProcessor ' + currencyDictSettings.currencyCode - ); - } - return new XmrSecretsProcessor(); - } -} - -const singleBlocksoftDispatcher = new BlocksoftDispatcher(); -export default singleBlocksoftDispatcher; diff --git a/src/lib/BlocksoftKeys.js b/src/lib/BlocksoftKeys.js deleted file mode 100644 index 8455e2847..000000000 --- a/src/lib/BlocksoftKeys.js +++ /dev/null @@ -1,496 +0,0 @@ -/** - * @author Ksu - * @version 0.5 - */ -import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; -import AirDAODict from '@crypto/common/AirDAODict'; -import BlocksoftKeysUtils from '@crypto/actions/BlocksoftKeys/BlocksoftKeysUtils'; - -import * as BlocksoftRandom from 'react-native-blocksoft-random'; -import BlocksoftDispatcher from '../../blockchains/BlocksoftDispatcher'; -import BlocksoftKeysScam from '@crypto/actions/BlocksoftKeys/BlocksoftKeysScam'; -import { strings } from '@app/services/i18n'; - -const bip32 = require('bip32'); -const bip39 = require('bip39'); -const bip44Constants = require('../../common/ext/bip44-constants'); -const networksConstants = require('../../common/ext/networks-constants'); - -const bs58check = require('bs58check'); - -const ETH_CACHE = {} -const CACHE = {}; -const CACHE_ROOTS = {}; - -class BlocksoftKeys { - constructor() { - this._bipHex = {}; - let currency; - for (currency of bip44Constants) { - this._bipHex[currency[1]] = currency[0]; - } - this._getRandomBytesFunction = BlocksoftRandom.getRandomBytes; - } - - /** - * create new mnemonic object (also gives hash to store in public db) - * @param {int} size - * @return {Promise<{mnemonic: string, hash: string}>} - */ - async newMnemonic(size = 256) { - AirDAOCryptoLog.log(`BlocksoftKeys newMnemonic called`); - - /* let mnemonic = false - if (USE_TON) { - for (let i = 0; i < 10000; i++) { - let random = await this._getRandomBytesFunction(size / 8) - random = Buffer.from(random, 'base64') - let testMnemonic = bip39.entropyToMnemonic(random) - if (await BlocksoftKeysUtils.tonCheckRevert(testMnemonic)) { - mnemonic = testMnemonic - break - } - AirDAOCryptoLog.log('step ' + i + ' not valid ton ' + testMnemonic) - } - if (!mnemonic) { - throw new Error('TON Mnemonic is not validating') - } - } else { - let random = await this._getRandomBytesFunction(size / 8) - random = Buffer.from(random, 'base64') - mnemonic = bip39.entropyToMnemonic(random) - } */ - let random = await this._getRandomBytesFunction(size / 8); - random = Buffer.from(random, 'base64'); - const mnemonic = bip39.entropyToMnemonic(random); - const hash = BlocksoftKeysUtils.hashMnemonic(mnemonic); - AirDAOCryptoLog.log(`BlocksoftKeys newMnemonic finished`); - return { mnemonic, hash }; - } - - /** - * @param {string} mnemonic - * @return {Promise} - */ - async validateMnemonic(mnemonic) { - AirDAOCryptoLog.log(`BlocksoftKeys validateMnemonic called`); - if (await BlocksoftKeysScam.isScamMnemonic(mnemonic)) { - throw new Error(strings('settings.walletList.scamImport')); - } - const result = await bip39.validateMnemonic(mnemonic); - if (!result) { - throw new Error('invalid mnemonic for bip39'); - } - return result; - } - - /** - * @param {string} data.mnemonic - * @param {string} data.walletHash - * @param {string|string[]} data.currencyCode = all - * @param {int} data.fromIndex = 0 - * @param {int} data.toIndex = 100 - * @param {boolean} data.fullTree = false - * @param {array} data.derivations.BTC - * @param {array} data.derivations.BTC_SEGWIT - * @return {Promise<{currencyCode:[{address, privateKey, path, index, type}]}>} - */ - async discoverAddresses(data, source) { - const logData = { ...data }; - if (typeof logData.mnemonic !== 'undefined') logData.mnemonic = '***'; - - AirDAOCryptoLog.log( - `BlocksoftKeys discoverAddresses called from ${source}`, - JSON.stringify(logData).substring(0, 200) - ); - - let toDiscover = AirDAODict.Codes; - if (data.currencyCode) { - if (typeof data.currencyCode === 'string') { - toDiscover = [data.currencyCode]; - } else { - toDiscover = data.currencyCode; - } - } - const fromIndex = data.fromIndex ? data.fromIndex : 0; - const toIndex = data.toIndex ? data.toIndex : 10; - const fullTree = data.fullTree ? data.fullTree : false; - - const results = {}; - - const mnemonicCache = data.mnemonic.toLowerCase(); - let bitcoinRoot = false; - let currencyCode; - let settings; - const seed = await BlocksoftKeysUtils.bip39MnemonicToSeed( - data.mnemonic.toLowerCase() - ); - for (currencyCode of toDiscover) { - results[currencyCode] = []; - try { - settings = AirDAODict.getCurrencyAllSettings( - currencyCode, - 'BlocksoftKeys' - ); - } catch (e) { - // do nothing for now - continue; - } - - let hexes = []; - if (settings.addressCurrencyCode) { - hexes.push(this._bipHex[settings.addressCurrencyCode]); - if (!this._bipHex[settings.addressCurrencyCode]) { - throw new Error( - 'UNKNOWN_CURRENCY_CODE SETTED ' + settings.addressCurrencyCode - ); - } - } - - if (this._bipHex[currencyCode]) { - hexes.push(this._bipHex[currencyCode]); - } else if (!settings.addressCurrencyCode) { - if ( - settings.extendsProcessor && - this._bipHex[settings.extendsProcessor] - ) { - hexes.push(this._bipHex[settings.extendsProcessor]); - } else { - throw new Error( - 'UNKNOWN_CURRENCY_CODE ' + - currencyCode + - ' in bipHex AND NO SETTED addressCurrencyCode' - ); - } - } - - let isAlreadyMain = false; - - if (!data.fullTree) { - hexes = [hexes[0]]; - } - - const hexesCache = - mnemonicCache + - '_' + - settings.addressProcessor + - '_fromINDEX_' + - fromIndex + - '_' + - JSON.stringify(hexes); - let hasDerivations = false; - if ( - typeof data.derivations !== 'undefined' && - typeof data.derivations[currencyCode] !== 'undefined' && - data.derivations[currencyCode] - ) { - hasDerivations = true; - } - if (typeof CACHE[hexesCache] === 'undefined' || hasDerivations) { - AirDAOCryptoLog.log( - `BlocksoftKeys will discover ${settings.addressProcessor}` - ); - let root = false; - if (typeof networksConstants[currencyCode] !== 'undefined') { - root = await this.getBip32Cached( - data.mnemonic, - networksConstants[currencyCode], - seed - ); - } else { - if (!bitcoinRoot) { - bitcoinRoot = await this.getBip32Cached(data.mnemonic); - } - root = bitcoinRoot; - } - // BIP32 Extended Private Key to check - uncomment - // let childFirst = root.derivePath('m/44\'/2\'/0\'/0') - // AirDAOCryptoLog.log(childFirst.toBase58()) - - /** - * @type {EthAddressProcessor|BtcAddressProcessor} - */ - const processor = await BlocksoftDispatcher.innerGetAddressProcessor( - settings - ); - - try { - await processor.setBasicRoot(root); - } catch (e) { - e.message += ' while doing ' + JSON.stringify(settings); - throw e; - } - let currentFromIndex = fromIndex; - let currentToIndex = toIndex; - let currentFullTree = fullTree; - - AirDAOCryptoLog.log( - `BlocksoftKeys discoverAddresses ${currencyCode} currentFromIndex.1 ${currentFromIndex}` - ); - if (hasDerivations) { - let derivation = { path: '', alreadyShown: 0, walletPubId: 0 }; - let maxIndex = 0; - for (derivation of data.derivations[currencyCode]) { - const child = root.derivePath(derivation.path); - const tmp = derivation.path.split('/'); - const result = await processor.getAddress( - child.privateKey, - { - publicKey: child.publicKey, - walletHash: data.walletHash, - derivationPath: derivation.path - }, - data, - seed, - 'discoverAddresses' - ); - result.basicPrivateKey = child.privateKey.toString('hex'); - result.basicPublicKey = child.publicKey.toString('hex'); - result.path = derivation.path; - result.alreadyShown = derivation.alreadyShown; - result.walletPubId = derivation.walletPubId || 0; - result.index = tmp[5]; - if (maxIndex < result.index) { - maxIndex = result.index * 1; - } - result.type = 'main'; - results[currencyCode].push(result); - } - if (maxIndex > 0) { - // noinspection PointlessArithmeticExpressionJS - currentFromIndex = maxIndex * 1 + 1; - currentToIndex = currentFromIndex * 1 + 10; - currentFullTree = true; - AirDAOCryptoLog.log( - `BlocksoftKeys ${currencyCode} discoverAddresses currentFromIndex.2 ${currentFromIndex}` - ); - } - - let suffixes - if (currencyCode === 'SOL') { - suffixes = [ - { type: 'main', suffix: false, after: `'/0'` }, - { type: 'no_scan', suffix: false, after: `'` } - ]; - } else if (currentFullTree) { - suffixes = [ - { type: 'main', suffix: `0'/0` }, - { type: 'change', suffix: `0'/1` } - // { 'type': 'second', 'suffix': `1'/0` }, - // { 'type': 'secondchange', 'suffix': `1'/1` } - ]; - } else { - suffixes = [{ type: 'main', suffix: `0'/0` }]; - if (currencyCode === 'BTC_SEGWIT_COMPATIBLE') { - suffixes = [ - { type: 'main', suffix: '0/1' } // heh - ]; - } - hexes = [hexes[0]]; - } - - let hex - for (hex of hexes) { - if (isAlreadyMain) { - suffixes[0].type = 'second'; - } - isAlreadyMain = true; - - if (currentFromIndex >= 0 && currentToIndex >= 0) { - for ( - let index = currentFromIndex; - index < currentToIndex; - index++ - ) { - let suffix; - for (suffix of suffixes) { - const path = suffix.suffix - ? `m/${hex}'/${suffix.suffix}/${index}` - : `m/${hex}'/${index}${suffix.after}`; - let privateKey = false; - let publicKey = false; - if ( - currencyCode === 'SOL' || - currencyCode === 'XLM' || - currencyCode === 'WAVES' || - currencyCode === 'ASH' - ) { - // @todo move to coin address processor - } else { - const child = root.derivePath(path); - privateKey = child.privateKey; - publicKey = child.publicKey; - } - const result = await processor.getAddress( - privateKey, - { - publicKey, - walletHash: data.walletHash, - derivationPath: path, - derivationIndex: index, - derivationType: suffix.type - }, - data, - seed, - 'discoverAddresses2' - ); - if (result) { - if (privateKey) { - result.basicPrivateKey = privateKey.toString('hex'); - result.basicPublicKey = publicKey.toString('hex'); - } - result.path = path; - result.index = index; - result.alreadyShown = 0; - result.type = suffix.type; - results[currencyCode].push(result); - } - } - } - } - } - CACHE[hexesCache] = results[currencyCode]; - if (currencyCode === 'ETH') { - ETH_CACHE[mnemonicCache] = results[currencyCode][0]; - } - } else { - AirDAOCryptoLog.log( - `BlocksoftKeys will be from cache ${settings.addressProcessor}` - ); - results[currencyCode] = CACHE[hexesCache]; - if (currencyCode === 'USDT') { - results[currencyCode] = [results[currencyCode][0]]; - } - } - } - AirDAOCryptoLog.log(`BlocksoftKeys discoverAddresses finished`); - return results; - } - - async getSeedCached(mnemonic) { - AirDAOCryptoLog.log(`BlocksoftKeys bip39MnemonicToSeed started`); - const mnemonicCache = mnemonic.toLowerCase(); - if (typeof CACHE[mnemonicCache] === 'undefined') { - CACHE[mnemonicCache] = await BlocksoftKeysUtils.bip39MnemonicToSeed( - mnemonic.toLowerCase() - ); - } - const seed = CACHE[mnemonicCache]; // will be rechecked on saving - AirDAOCryptoLog.log(`BlocksoftKeys bip39MnemonicToSeed ended`); - return seed; - } - - async getBip32Cached(mnemonic, network = false, seed = false) { - const mnemonicCache = mnemonic.toLowerCase() + '_' + (network || 'btc'); - if (typeof CACHE_ROOTS[mnemonicCache] === 'undefined') { - if (!seed) { - seed = await this.getSeedCached(mnemonic); - } - CACHE_ROOTS[mnemonicCache] = network - ? bip32.fromSeed(seed, network) - : bip32.fromSeed(seed); - } - return CACHE_ROOTS[mnemonicCache]; - } - - getEthCached(mnemonicCache) { - if (typeof ETH_CACHE[mnemonicCache] === 'undefined') return false; - return ETH_CACHE[mnemonicCache]; - } - - setEthCached(mnemonic, result) { - const mnemonicCache = mnemonic.toLowerCase(); - ETH_CACHE[mnemonicCache] = result; - } - - /** - * @param {string} data.mnemonic - * @param {string} data.currencyCode - * @param {string} data.derivationPath - * @param {string} data.derivationIndex - * @param {string} data.derivationType - * @return {Promise<{address, privateKey}>} - */ - async discoverOne(data) { - const seed = await BlocksoftKeysUtils.bip39MnemonicToSeed( - data.mnemonic.toLowerCase() - ); - const root = bip32.fromSeed(seed); - const child = root.derivePath(data.derivationPath.replace(/quote/g, "'")); - /** - * @type {EthAddressProcessor|BtcAddressProcessor} - */ - const processor = await BlocksoftDispatcher.getAddressProcessor( - data.currencyCode - ); - processor.setBasicRoot(root); - return processor.getAddress( - child.privateKey, - { - derivationPath: data.derivationPath, - derivationIndex: data.derivationIndex, - derivationType: data.derivationType, - publicKey: child.publicKey - }, - data, - seed, - 'discoverOne' - ); - } - - /** - * @param {string} data.mnemonic - * @param {string} data.currencyCode - * @return {Promise<{address, privateKey}>} - */ - async discoverXpub(data) { - const seed = await BlocksoftKeysUtils.bip39MnemonicToSeed( - data.mnemonic.toLowerCase() - ); - const root = bip32.fromSeed(seed); - let path = `m/44'/0'/0'`; - let version = 0x0488b21e; // xpub - if (data.currencyCode === 'BTC_SEGWIT') { - path = `m/84'/0'/0'`; - version = 0x04b24746; // https://github.com/satoshilabs/slips/blob/master/slip-0132.md - } else if (data.currencyCode === 'BTC_SEGWIT_COMPATIBLE') { - path = `m/49'/0'/0'`; - version = 0x049d7cb2; - } - AirDAOCryptoLog.log( - 'BlocksoftKeys.discoverXpub derive started ' + JSON.stringify(path) - ); - const rootWallet = root.derivePath(path); - AirDAOCryptoLog.log( - 'BlocksoftKeys.discoverXpub derived, bs58 started ' + JSON.stringify(path) - ); - const res = bs58check.encode( - serialize(rootWallet, version, rootWallet.publicKey) - ); - AirDAOCryptoLog.log( - 'BlocksoftKeys.discoverXpub res ' + JSON.stringify(path), - res - ); - return res; - } -} - -function serialize(hdkey, version, key, LEN = 78) { - // => version(4) || depth(1) || fingerprint(4) || index(4) || chain(32) || key(33) - const buffer = Buffer.allocUnsafe(LEN); - - buffer.writeUInt32BE(version, 0); - buffer.writeUInt8(hdkey.depth, 4); - - const fingerprint = hdkey.depth ? hdkey.parentFingerprint : 0x00000000; - buffer.writeUInt32BE(fingerprint, 5); - buffer.writeUInt32BE(hdkey.index, 9); - - hdkey.chainCode.copy(buffer, 13); - key.copy(buffer, 45); - - return buffer; -} - -const singleBlocksoftKeys = new BlocksoftKeys(); -export default singleBlocksoftKeys; diff --git a/src/lib/BlocksoftKeysForRef.js b/src/lib/BlocksoftKeysForRef.js deleted file mode 100644 index b9bfe4b72..000000000 --- a/src/lib/BlocksoftKeysForRef.js +++ /dev/null @@ -1,82 +0,0 @@ -/** - * @author Ksu - * @version 0.5 - */ -import AirDAOCryptoLog from '../../common/AirDAOCryptoLog'; -import BlocksoftKeysForRefServerSide from './BlocksoftKeysForRefServerSide'; -import BlocksoftKeys from '../BlocksoftKeys/BlocksoftKeys'; -import BlocksoftDispatcher from '../../blockchains/BlocksoftDispatcher'; - -const CACHE = {}; - -class BlocksoftKeysForRef { - /** - * @param {string} data.mnemonic - * @param {int} data.index - * @return {Promise<{currencyCode:[{address, privateKey, path, index, type}]}>} - */ - async discoverPublicAndPrivate(data) { - const logData = { ...data }; - const mnemonicCache = data.mnemonic.toLowerCase(); - - if (typeof logData.mnemonic !== 'undefined') logData.mnemonic = '***'; - if (typeof CACHE[mnemonicCache] !== 'undefined') - return CACHE[mnemonicCache]; - AirDAOCryptoLog.log( - `BlocksoftKeysForRef discoverPublicAndPrivate called ` + - JSON.stringify(logData) - ); - - let result = BlocksoftKeys.getEthCached(mnemonicCache); - if (!result) { - AirDAOCryptoLog.log( - `BlocksoftKeysForRef discoverPublicAndPrivate no cache ` + - JSON.stringify(logData) - ); - let index = 0; - if (typeof data.index !== 'undefined') { - index = data.index; - } - const root = await BlocksoftKeys.getBip32Cached(data.mnemonic); - const path = `m/44'/60'/${index}'/0/0`; - const child = root.derivePath(path); - - const processor = await BlocksoftDispatcher.getAddressProcessor('ETH'); - result = await processor.getAddress(child.privateKey); - result.index = index; - result.path = path; - if (index === 0) { - BlocksoftKeys.setEthCached(data.mnemonic, result); - } - AirDAOCryptoLog.log( - `BlocksoftKeysForRef discoverPublicAndPrivate finished no cache ` + - JSON.stringify(logData) - ); - } - // noinspection JSPrimitiveTypeWrapperUsage - result.cashbackToken = BlocksoftKeysForRefServerSide.addressToToken( - result.address - ); - AirDAOCryptoLog.log( - `BlocksoftKeysForRef discoverPublicAndPrivate finished ` + - JSON.stringify(logData) - ); - CACHE[mnemonicCache] = result; - return result; - } - - async signDataForApi(msg, privateKey) { - const processor = await BlocksoftDispatcher.getAddressProcessor('ETH'); - if (privateKey.substr(0, 2) !== '0x') { - privateKey = '0x' + privateKey; - } - const signedData = await processor.signMessage(msg, privateKey); - delete signedData.v; - delete signedData.r; - delete signedData.s; - return signedData; - } -} - -const singleBlocksoftKeysForRef = new BlocksoftKeysForRef(); -export default singleBlocksoftKeysForRef; diff --git a/src/lib/helpers/AirDAOKeys.ts b/src/lib/helpers/AirDAOKeys.ts index 4a070615a..903567d57 100644 --- a/src/lib/helpers/AirDAOKeys.ts +++ b/src/lib/helpers/AirDAOKeys.ts @@ -1,21 +1,19 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ /** - * @author Ksu + * @author Javid * @version 0.5 */ -import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; import AirDAODict from '@crypto/common/AirDAODict'; -import BlocksoftKeysUtils from '@crypto/actions/BlocksoftKeys/BlocksoftKeysUtils'; - -import * as BlocksoftRandom from 'react-native-blocksoft-random'; -import BlocksoftDispatcher from '../../blockchains/BlocksoftDispatcher'; +// @ts-ignore import BlocksoftKeysScam from '@crypto/actions/BlocksoftKeys/BlocksoftKeysScam'; -import { strings } from '@app/services/i18n'; +import AirDAODispatcher from '@crypto/blockchains/AirDAODispatcher'; +import networksConstants from '@crypto/common/ext/networks-constants'; +import bip44Constants from '@crypto/common/ext/bip44-constants'; +import { getRandomBytes } from 'expo-crypto'; +import KeysUtills from '@utils/keys'; const bip32 = require('bip32'); const bip39 = require('bip39'); -const bip44Constants = require('../../common/ext/bip44-constants'); -const networksConstants = require('../../common/ext/networks-constants'); - const bs58check = require('bs58check'); const ETH_CACHE = {}; @@ -23,13 +21,15 @@ const CACHE = {}; const CACHE_ROOTS = {}; class BlocksoftKeys { + _bipHex: { [key: string]: string }; + _getRandomBytesFunction; constructor() { this._bipHex = {}; let currency; for (currency of bip44Constants) { this._bipHex[currency[1]] = currency[0]; } - this._getRandomBytesFunction = BlocksoftRandom.getRandomBytes; + this._getRandomBytesFunction = getRandomBytes; } /** @@ -38,8 +38,6 @@ class BlocksoftKeys { * @return {Promise<{mnemonic: string, hash: string}>} */ async newMnemonic(size = 256) { - AirDAOCryptoLog.log(`BlocksoftKeys newMnemonic called`); - /* let mnemonic = false if (USE_TON) { for (let i = 0; i < 10000; i++) { @@ -50,7 +48,6 @@ class BlocksoftKeys { mnemonic = testMnemonic break } - AirDAOCryptoLog.log('step ' + i + ' not valid ton ' + testMnemonic) } if (!mnemonic) { throw new Error('TON Mnemonic is not validating') @@ -63,8 +60,7 @@ class BlocksoftKeys { let random = await this._getRandomBytesFunction(size / 8); random = Buffer.from(random, 'base64'); const mnemonic = bip39.entropyToMnemonic(random); - const hash = BlocksoftKeysUtils.hashMnemonic(mnemonic); - AirDAOCryptoLog.log(`BlocksoftKeys newMnemonic finished`); + const hash = KeysUtills.hashMnemonic(mnemonic); return { mnemonic, hash }; } @@ -72,10 +68,9 @@ class BlocksoftKeys { * @param {string} mnemonic * @return {Promise} */ - async validateMnemonic(mnemonic) { - AirDAOCryptoLog.log(`BlocksoftKeys validateMnemonic called`); + async validateMnemonic(mnemonic: string): Promise { if (await BlocksoftKeysScam.isScamMnemonic(mnemonic)) { - throw new Error(strings('settings.walletList.scamImport')); + throw new Error('Scam import error'); } const result = await bip39.validateMnemonic(mnemonic); if (!result) { @@ -95,21 +90,36 @@ class BlocksoftKeys { * @param {array} data.derivations.BTC_SEGWIT * @return {Promise<{currencyCode:[{address, privateKey, path, index, type}]}>} */ - async discoverAddresses(data, source) { + async discoverAddresses( + data: { + mnemonic: string; + walletHash: string; + currencyCode: string[][] | string[]; + fromIndex: number; + toIndex: number; + fullTree: boolean; + }, + source + ): Promise<{ + currencyCode: [ + { + address: string; + privateKey: string; + path: string; + index: number; + type: string; + } + ]; + }> { const logData = { ...data }; if (typeof logData.mnemonic !== 'undefined') logData.mnemonic = '***'; - AirDAOCryptoLog.log( - `BlocksoftKeys discoverAddresses called from ${source}`, - JSON.stringify(logData).substring(0, 200) - ); - let toDiscover = AirDAODict.Codes; if (data.currencyCode) { if (typeof data.currencyCode === 'string') { toDiscover = [data.currencyCode]; } else { - toDiscover = data.currencyCode; + toDiscover = data.currencyCode as string[]; } } const fromIndex = data.fromIndex ? data.fromIndex : 0; @@ -122,7 +132,7 @@ class BlocksoftKeys { let bitcoinRoot = false; let currencyCode; let settings; - const seed = await BlocksoftKeysUtils.bip39MnemonicToSeed( + const seed = await KeysUtills.bip39MnemonicToSeed( data.mnemonic.toLowerCase() ); for (currencyCode of toDiscover) { @@ -187,9 +197,6 @@ class BlocksoftKeys { hasDerivations = true; } if (typeof CACHE[hexesCache] === 'undefined' || hasDerivations) { - AirDAOCryptoLog.log( - `BlocksoftKeys will discover ${settings.addressProcessor}` - ); let root = false; if (typeof networksConstants[currencyCode] !== 'undefined') { root = await this.getBip32Cached( @@ -205,12 +212,11 @@ class BlocksoftKeys { } // BIP32 Extended Private Key to check - uncomment // let childFirst = root.derivePath('m/44\'/2\'/0\'/0') - // AirDAOCryptoLog.log(childFirst.toBase58()) /** * @type {EthAddressProcessor|BtcAddressProcessor} */ - const processor = await BlocksoftDispatcher.innerGetAddressProcessor( + const processor = await AirDAODispatcher.innerGetAddressProcessor( settings ); @@ -224,9 +230,6 @@ class BlocksoftKeys { let currentToIndex = toIndex; let currentFullTree = fullTree; - AirDAOCryptoLog.log( - `BlocksoftKeys discoverAddresses ${currencyCode} currentFromIndex.1 ${currentFromIndex}` - ); if (hasDerivations) { let derivation = { path: '', alreadyShown: 0, walletPubId: 0 }; let maxIndex = 0; @@ -261,9 +264,6 @@ class BlocksoftKeys { currentFromIndex = maxIndex * 1 + 1; currentToIndex = currentFromIndex * 1 + 10; currentFullTree = true; - AirDAOCryptoLog.log( - `BlocksoftKeys ${currencyCode} discoverAddresses currentFromIndex.2 ${currentFromIndex}` - ); } } @@ -355,29 +355,23 @@ class BlocksoftKeys { ETH_CACHE[mnemonicCache] = results[currencyCode][0]; } } else { - AirDAOCryptoLog.log( - `BlocksoftKeys will be from cache ${settings.addressProcessor}` - ); results[currencyCode] = CACHE[hexesCache]; if (currencyCode === 'USDT') { results[currencyCode] = [results[currencyCode][0]]; } } } - AirDAOCryptoLog.log(`BlocksoftKeys discoverAddresses finished`); return results; } async getSeedCached(mnemonic) { - AirDAOCryptoLog.log(`BlocksoftKeys bip39MnemonicToSeed started`); const mnemonicCache = mnemonic.toLowerCase(); if (typeof CACHE[mnemonicCache] === 'undefined') { - CACHE[mnemonicCache] = await BlocksoftKeysUtils.bip39MnemonicToSeed( + CACHE[mnemonicCache] = await KeysUtills.bip39MnemonicToSeed( mnemonic.toLowerCase() ); } const seed = CACHE[mnemonicCache]; // will be rechecked on saving - AirDAOCryptoLog.log(`BlocksoftKeys bip39MnemonicToSeed ended`); return seed; } @@ -413,7 +407,7 @@ class BlocksoftKeys { * @return {Promise<{address, privateKey}>} */ async discoverOne(data) { - const seed = await BlocksoftKeysUtils.bip39MnemonicToSeed( + const seed = await KeysUtills.bip39MnemonicToSeed( data.mnemonic.toLowerCase() ); const root = bip32.fromSeed(seed); @@ -421,7 +415,7 @@ class BlocksoftKeys { /** * @type {EthAddressProcessor|BtcAddressProcessor} */ - const processor = await BlocksoftDispatcher.getAddressProcessor( + const processor = await AirDAODispatcher.getAddressProcessor( data.currencyCode ); processor.setBasicRoot(root); @@ -445,7 +439,7 @@ class BlocksoftKeys { * @return {Promise<{address, privateKey}>} */ async discoverXpub(data) { - const seed = await BlocksoftKeysUtils.bip39MnemonicToSeed( + const seed = await KeysUtills.bip39MnemonicToSeed( data.mnemonic.toLowerCase() ); const root = bip32.fromSeed(seed); @@ -458,20 +452,10 @@ class BlocksoftKeys { path = `m/49'/0'/0'`; version = 0x049d7cb2; } - AirDAOCryptoLog.log( - 'BlocksoftKeys.discoverXpub derive started ' + JSON.stringify(path) - ); const rootWallet = root.derivePath(path); - AirDAOCryptoLog.log( - 'BlocksoftKeys.discoverXpub derived, bs58 started ' + JSON.stringify(path) - ); const res = bs58check.encode( serialize(rootWallet, version, rootWallet.publicKey) ); - AirDAOCryptoLog.log( - 'BlocksoftKeys.discoverXpub res ' + JSON.stringify(path), - res - ); return res; } } diff --git a/src/lib/helpers/AirDAOKeysForRef.ts b/src/lib/helpers/AirDAOKeysForRef.ts index ffb92b1dc..59f63cdc0 100644 --- a/src/lib/helpers/AirDAOKeysForRef.ts +++ b/src/lib/helpers/AirDAOKeysForRef.ts @@ -3,15 +3,15 @@ * @version 0.5 */ import AirDAOKeys from './AirDAOKeys'; -import BlocksoftDispatcher from './AirDAODispatcher'; import { WalletUtils } from '@utils/wallet'; +import AirDAODispatcher from '@crypto/blockchains/AirDAODispatcher'; const CACHE: { [key: string]: any } = {}; class AirDAOKeysForRef { async discoverPublicAndPrivate(data: { mnemonic: string; - index: number; + index?: number; }): Promise<{ currencyCode: { address: string; @@ -34,7 +34,7 @@ class AirDAOKeysForRef { const path = `m/44'/60'/${index}'/0/0`; const child = root.derivePath(path); - const processor = await BlocksoftDispatcher.getAddressProcessor('ETH'); + const processor = await AirDAODispatcher.getAddressProcessor('ETH'); result = await processor.getAddress(child.privateKey); result.index = index; result.path = path; @@ -49,7 +49,7 @@ class AirDAOKeysForRef { } async signDataForApi(msg: string, privateKey: string) { - const processor = await BlocksoftDispatcher.getAddressProcessor('ETH'); + const processor = await AirDAODispatcher.getAddressProcessor('ETH'); if (privateKey.substring(0, 2) !== '0x') { privateKey = '0x' + privateKey; } diff --git a/src/lib/AirDAOKeysForRefStorage.ts b/src/lib/helpers/AirDAOKeysForRefStorage.ts similarity index 100% rename from src/lib/AirDAOKeysForRefStorage.ts rename to src/lib/helpers/AirDAOKeysForRefStorage.ts diff --git a/src/lib/helpers/AirDAOKeysStorage.ts b/src/lib/helpers/AirDAOKeysStorage.ts index c55077bf7..97364cb93 100644 --- a/src/lib/helpers/AirDAOKeysStorage.ts +++ b/src/lib/helpers/AirDAOKeysStorage.ts @@ -1,7 +1,7 @@ import * as SecureStore from 'expo-secure-store'; import { WalletMetadata } from '@appTypes'; -export class AirDAOKeysStorage { +class AirDAOKeysStorage { private serviceName = ''; private serviceWalletsCounter = 0; private serviceWallets: { [key: string]: string } = {}; diff --git a/src/utils/cashback.ts b/src/utils/cashback.ts index 0b5e2cad7..742bcc026 100644 --- a/src/utils/cashback.ts +++ b/src/utils/cashback.ts @@ -1 +1,34 @@ -const getByHash = async (tmpHash: string) => {}; +import AirDAOKeysStorage from '@lib/helpers/AirDAOKeysStorage'; +import AirDAOKeysForRefStorage from '@lib/helpers/AirDAOKeysForRefStorage'; +import AirDAOKeysForRef from '@lib/helpers/AirDAOKeysForRef'; + +const getByHash = async (tmpHash: string) => { + // let tmpPublicAndPrivateResult = + // await AirDAOKeysForRefStorage.getPublicAndPrivateResultForHash(tmpHash); + // if ( + // tmpPublicAndPrivateResult && + // typeof tmpPublicAndPrivateResult.cashbackToken !== 'undefined' + // ) { + // // Log.log('SRV/CashBack getByHash ' + tmpHash + ' => ' + tmpPublicAndPrivateResult.cashbackToken) + // return tmpPublicAndPrivateResult; + // } + // // await Log.log('SRV/CashBack getByHash need to discoverPublic', tmpHash) + // const mnemonic = await AirDAOKeysStorage.getWalletMnemonic(tmpHash); + // if (!mnemonic) { + // return false; + // } + // // await Log.log('SRV/CashBack getByHash got mnemonic to discoverPublic') + // tmpPublicAndPrivateResult = await AirDAOKeysForRef.discoverPublicAndPrivate({ + // mnemonic + // }); + // // await Log.log('SRV/CashBack getByHash done discoverPublic ' + tmpHash + ' => ' + tmpPublicAndPrivateResult.cashbackToken) + // try { + // await AirDAOKeysForRefStorage.setPublicAndPrivateResultForHash( + // tmpHash, + // tmpPublicAndPrivateResult + // ); + // } catch (e) {} + // return tmpPublicAndPrivateResult; +}; + +export const CashBackUtils = { getByHash }; diff --git a/src/utils/keys.ts b/src/utils/keys.ts index ad736a427..c5e329e0a 100644 --- a/src/utils/keys.ts +++ b/src/utils/keys.ts @@ -1,6 +1,6 @@ import * as Crypto from 'expo-crypto'; import { DEFAULT_WORDS } from '@constants/words'; -import { createHmac } from 'crypto'; +import createHmac from 'create-hmac'; interface CreateHmacPDFK2Sizes { [key: string]: number; diff --git a/src/utils/wallet.ts b/src/utils/wallet.ts index f318940a6..4e2d6fec0 100644 --- a/src/utils/wallet.ts +++ b/src/utils/wallet.ts @@ -3,6 +3,7 @@ import { Wallet } from '@models/Wallet'; import { Crypto } from './crypto'; import { MnemonicUtils } from './mnemonics'; import AirDAOStorage from '@lib/helpers/AirDAOKeysStorage'; +import { CashBackUtils } from './cashback'; const _saveWallet = async (wallet: WalletMetadata) => { let storedKey = ''; @@ -45,8 +46,11 @@ const processWallet = async ( tmpWalletName = await _getWalletName(); } const fullWallet: Wallet = new Wallet({ ...data, hash, name: tmpWalletName }); + console.log({ fullWallet }); + const { cashbackToken } = await CashBackUtils.getByHash(hash); + console.log({ cashbackToken }); // TODO save to local db - await Wallet.saveWallet(fullWallet); + // await Wallet.saveWallet(fullWallet); try { } catch (error) { throw error; From e49f8ffa22e86b7edf943162f0f859b2bba76665 Mon Sep 17 00:00:00 2001 From: JavidHaji-zada Date: Thu, 10 Aug 2023 11:41:41 +0400 Subject: [PATCH 035/509] fix bitcoinjs-lib, added events & stream --- App.tsx | 1 + ...locksoftSecrets.js => BlocksoftSecrets.ts} | 4 +- crypto/blockchains/AirDAODispatcher.ts | 314 ++- crypto/blockchains/xmr/XmrSecretsProcessor.ts | 7 +- crypto/common/ext/networks-constants.ts | 2 +- package.json | 6 +- patches/bitcoinjs-lib+5.1.6.patch | 55 + src/lib/common/ext/network-constants.ts | 117 - src/lib/helpers/AirDAOKeysForRef.ts | 9 +- src/lib/helpers/AirDAOKeysForRefStorage.ts | 1 + .../CreateWallet/CreateWalletStep2.tsx | 18 +- src/utils/cashback.ts | 47 +- yarn.lock | 2050 +++++++++++++---- 13 files changed, 1831 insertions(+), 800 deletions(-) rename crypto/actions/BlocksoftSecrets/{BlocksoftSecrets.js => BlocksoftSecrets.ts} (89%) create mode 100644 patches/bitcoinjs-lib+5.1.6.patch delete mode 100644 src/lib/common/ext/network-constants.ts diff --git a/App.tsx b/App.tsx index 1b85bde19..44a37565b 100644 --- a/App.tsx +++ b/App.tsx @@ -1,3 +1,4 @@ +import 'fast-text-encoding'; import './src/prototypes/array'; import './src/prototypes/global'; import React from 'react'; diff --git a/crypto/actions/BlocksoftSecrets/BlocksoftSecrets.js b/crypto/actions/BlocksoftSecrets/BlocksoftSecrets.ts similarity index 89% rename from crypto/actions/BlocksoftSecrets/BlocksoftSecrets.js rename to crypto/actions/BlocksoftSecrets/BlocksoftSecrets.ts index 390b0a753..1fd728473 100644 --- a/crypto/actions/BlocksoftSecrets/BlocksoftSecrets.js +++ b/crypto/actions/BlocksoftSecrets/BlocksoftSecrets.ts @@ -3,7 +3,7 @@ * @version 0.11 */ import AirDAOCryptoLog from '../../common/AirDAOCryptoLog'; -import BlocksoftDispatcher from '../../blockchains/BlocksoftDispatcher'; +import AirDAODispatcher from '../../blockchains/AirDAODispatcher'; class BlocksoftSecrets { /** @@ -28,7 +28,7 @@ class BlocksoftSecrets { * @type {XmrSecretsProcessor} */ this._processor[currencyCode] = - BlocksoftDispatcher.getSecretsProcessor(currencyCode); + AirDAODispatcher.getSecretsProcessor(currencyCode); } let res = ''; diff --git a/crypto/blockchains/AirDAODispatcher.ts b/crypto/blockchains/AirDAODispatcher.ts index f37bd120a..2bd318689 100644 --- a/crypto/blockchains/AirDAODispatcher.ts +++ b/crypto/blockchains/AirDAODispatcher.ts @@ -3,83 +3,81 @@ * @version 0.5 */ -import BchAddressProcessor from '@crypto/blockchains/bch/BchAddressProcessor'; -import BchScannerProcessor from '@crypto/blockchains/bch/BchScannerProcessor'; +// import BchAddressProcessor from '@crypto/blockchains/bch/BchAddressProcessor'; +// import BchScannerProcessor from '@crypto/blockchains/bch/BchScannerProcessor'; -import BsvScannerProcessor from '@crypto/blockchains/bsv/BsvScannerProcessor'; +// import BsvScannerProcessor from '@crypto/blockchains/bsv/BsvScannerProcessor'; -import BtcAddressProcessor from '@crypto/blockchains/btc/address/BtcAddressProcessor'; -import BtcScannerProcessor from '@crypto/blockchains/btc/BtcScannerProcessor'; +// import BtcAddressProcessor from '@crypto/blockchains/btc/address/BtcAddressProcessor'; +// import BtcScannerProcessor from '@crypto/blockchains/btc/BtcScannerProcessor'; -import BtcSegwitCompatibleAddressProcessor from '@crypto/blockchains/btc/address/BtcSegwitCompatibleAddressProcessor'; -import BtcSegwitAddressProcessor from '@crypto/blockchains/btc/address/BtcSegwitAddressProcessor'; +// import BtcSegwitCompatibleAddressProcessor from '@crypto/blockchains/btc/address/BtcSegwitCompatibleAddressProcessor'; +// import BtcSegwitAddressProcessor from '@crypto/blockchains/btc/address/BtcSegwitAddressProcessor'; -import BtcTestScannerProcessor from '@crypto/blockchains/btc_test/BtcTestScannerProcessor'; +// import BtcTestScannerProcessor from '@crypto/blockchains/btc_test/BtcTestScannerProcessor'; -import BtgScannerProcessor from '@crypto/blockchains/btg/BtgScannerProcessor'; +// import BtgScannerProcessor from '@crypto/blockchains/btg/BtgScannerProcessor'; -import DogeScannerProcessor from '@crypto/blockchains/doge/DogeScannerProcessor'; +// import DogeScannerProcessor from '@crypto/blockchains/doge/DogeScannerProcessor'; import EthAddressProcessor from '@crypto/blockchains/eth/EthAddressProcessor'; import EthScannerProcessor from '@crypto/blockchains/eth/EthScannerProcessor'; import EthScannerProcessorErc20 from '@crypto/blockchains/eth/EthScannerProcessorErc20'; -import EthScannerProcessorSoul from '@crypto/blockchains/eth/forks/EthScannerProcessorSoul'; +import { BlockchainUtils } from '@utils/blockchain'; +// import EthScannerProcessorSoul from '@crypto/blockchains/eth/forks/EthScannerProcessorSoul'; import EthTokenProcessorErc20 from '@crypto/blockchains/eth/EthTokenProcessorErc20'; -import LtcScannerProcessor from '@crypto/blockchains/ltc/LtcScannerProcessor'; +// import LtcScannerProcessor from '@crypto/blockchains/ltc/LtcScannerProcessor'; -import TrxAddressProcessor from '@crypto/blockchains/trx/TrxAddressProcessor'; -import TrxScannerProcessor from '@crypto/blockchains/trx/TrxScannerProcessor'; -import TrxTokenProcessor from '@crypto/blockchains/trx/TrxTokenProcessor'; +// import TrxAddressProcessor from '@crypto/blockchains/trx/TrxAddressProcessor'; +// import TrxScannerProcessor from '@crypto/blockchains/trx/TrxScannerProcessor'; +// import TrxTokenProcessor from '@crypto/blockchains/trx/TrxTokenProcessor'; -import UsdtScannerProcessor from '@crypto/blockchains/usdt/UsdtScannerProcessor'; +// import UsdtScannerProcessor from '@crypto/blockchains/usdt/UsdtScannerProcessor'; -import XrpAddressProcessor from '@crypto/blockchains/xrp/XrpAddressProcessor'; -import XrpScannerProcessor from '@crypto/blockchains/xrp/XrpScannerProcessor'; +// import XrpAddressProcessor from '@crypto/blockchains/xrp/XrpAddressProcessor'; +// import XrpScannerProcessor from '@crypto/blockchains/xrp/XrpScannerProcessor'; -import XlmAddressProcessor from '@crypto/blockchains/xlm/XlmAddressProcessor'; -import XlmScannerProcessor from '@crypto/blockchains/xlm/XlmScannerProcessor'; +// import XlmAddressProcessor from '@crypto/blockchains/xlm/XlmAddressProcessor'; +// import XlmScannerProcessor from '@crypto/blockchains/xlm/XlmScannerProcessor'; -import XvgScannerProcessor from '@crypto/blockchains/xvg/XvgScannerProcessor'; +// import XvgScannerProcessor from '@crypto/blockchains/xvg/XvgScannerProcessor'; -import XmrAddressProcessor from '@crypto/blockchains/xmr/XmrAddressProcessor'; -import XmrScannerProcessor from '@crypto/blockchains/xmr/XmrScannerProcessor'; +// import XmrAddressProcessor from '@crypto/blockchains/xmr/XmrAddressProcessor'; +// import XmrScannerProcessor from '@crypto/blockchains/xmr/XmrScannerProcessor'; import XmrSecretsProcessor from '@crypto/blockchains/xmr/XmrSecretsProcessor'; -import FioAddressProcessor from '@crypto/blockchains/fio/FioAddressProcessor'; -import FioScannerProcessor from '@crypto/blockchains/fio/FioScannerProcessor'; +// import FioAddressProcessor from '@crypto/blockchains/fio/FioAddressProcessor'; +// import FioScannerProcessor from '@crypto/blockchains/fio/FioScannerProcessor'; -import BnbAddressProcessor from '@crypto/blockchains/bnb/BnbAddressProcessor'; -import BnbScannerProcessor from '@crypto/blockchains/bnb/BnbScannerProcessor'; -import { BlockchainUtils } from '@utils/blockchain'; +// import BnbAddressProcessor from '@crypto/blockchains/bnb/BnbAddressProcessor'; +// import BnbScannerProcessor from '@crypto/blockchains/bnb/BnbScannerProcessor'; +// import { BlockchainUtils } from '@utils/blockchain'; -import VetScannerProcessor from '@crypto/blockchains/vet/VetScannerProcessor'; +// import VetScannerProcessor from '@crypto/blockchains/vet/VetScannerProcessor'; -import SolAddressProcessor from '@crypto/blockchains/sol/SolAddressProcessor'; -import SolScannerProcessor from '@crypto/blockchains/sol/SolScannerProcessor'; +// import SolAddressProcessor from '@crypto/blockchains/sol/SolAddressProcessor'; +// import SolScannerProcessor from '@crypto/blockchains/sol/SolScannerProcessor'; -import WavesAddressProcessor from '@crypto/blockchains/waves/WavesAddressProcessor'; -import WavesScannerProcessor from '@crypto/blockchains/waves/WavesScannerProcessor'; +// import WavesAddressProcessor from '@crypto/blockchains/waves/WavesAddressProcessor'; +// import WavesScannerProcessor from '@crypto/blockchains/waves/WavesScannerProcessor'; -import SolScannerProcessorSpl from '@crypto/blockchains/sol/SolScannerProcessorSpl'; -import SolTokenProcessor from '@crypto/blockchains/sol/SolTokenProcessor'; +// import SolScannerProcessorSpl from '@crypto/blockchains/sol/SolScannerProcessorSpl'; +// import SolTokenProcessor from '@crypto/blockchains/sol/SolTokenProcessor'; import EthTokenProcessorNft from '@crypto/blockchains/eth/EthTokenProcessorNft'; -import AshAddressProcessor from '@crypto/blockchains/ash/AshAddressProcessor'; +// import AshAddressProcessor from '@crypto/blockchains/ash/AshAddressProcessor'; -import MetisScannerProcessor from '@crypto/blockchains/metis/MetisScannerProcessor'; -import OneScannerProcessor from '@crypto/blockchains/one/OneScannerProcessor'; -import OneScannerProcessorErc20 from '@crypto/blockchains/one/OneScannerProcessorErc20'; +// import MetisScannerProcessor from '@crypto/blockchains/metis/MetisScannerProcessor'; +// import OneScannerProcessor from '@crypto/blockchains/one/OneScannerProcessor'; +// import OneScannerProcessorErc20 from '@crypto/blockchains/one/OneScannerProcessorErc20'; -import WavesScannerProcessorErc20 from '@crypto/blockchains/waves/WavesScannerProcessorErc20'; +// import WavesScannerProcessorErc20 from '@crypto/blockchains/waves/WavesScannerProcessorErc20'; class AirDAODispatcher { - getAddressProcessor( - currencyCode: string - ): - | EthAddressProcessor - | BtcAddressProcessor - | TrxAddressProcessor - | XlmAddressProcessor - | SolAddressProcessor { + getAddressProcessor(currencyCode: string): EthAddressProcessor { + // | BtcAddressProcessor + // | TrxAddressProcessor + // | XlmAddressProcessor + // | SolAddressProcessor const currencyDictSettings = BlockchainUtils.getCurrencyAllSettings(currencyCode); return this.innerGetAddressProcessor(currencyDictSettings); @@ -87,42 +85,38 @@ class AirDAODispatcher { innerGetAddressProcessor( currencyDictSettings: any // TODO - ): - | EthAddressProcessor - | BtcAddressProcessor - | TrxAddressProcessor - | XlmAddressProcessor - | SolAddressProcessor { + ): EthAddressProcessor { + // | BtcAddressProcessor| TrxAddressProcessor| XlmAddressProcessor| SolAddressProcessor switch (currencyDictSettings.addressProcessor) { - case 'BCH': - return new BchAddressProcessor(currencyDictSettings); - case 'BTC': - return new BtcAddressProcessor(currencyDictSettings); - case 'BTC_SEGWIT': - case 'LTC_SEGWIT': - return new BtcSegwitAddressProcessor(currencyDictSettings); - case 'BTC_SEGWIT_COMPATIBLE': - return new BtcSegwitCompatibleAddressProcessor(currencyDictSettings); + // case 'BCH': + // return new BchAddressProcessor(currencyDictSettings); + // case 'BTC': + // return new BtcAddressProcessor(currencyDictSettings); + // case 'BTC_SEGWIT': + // case 'LTC_SEGWIT': + // return new BtcSegwitAddressProcessor(currencyDictSettings); + // case 'BTC_SEGWIT_COMPATIBLE': + // return new BtcSegwitCompatibleAddressProcessor(currencyDictSettings); case 'ETH': return new EthAddressProcessor(currencyDictSettings); - case 'TRX': - return new TrxAddressProcessor(); - case 'XRP': - return new XrpAddressProcessor(); - case 'XLM': - return new XlmAddressProcessor(); - case 'XMR': - return new XmrAddressProcessor(); - case 'FIO': - return new FioAddressProcessor(); - case 'BNB': - return new BnbAddressProcessor(); - case 'SOL': - return new SolAddressProcessor(); - case 'WAVES': - return new WavesAddressProcessor(); - case 'ASH': - return new AshAddressProcessor(); + // case 'TRX': + // return new TrxAddressProcessor(); + // case 'XRP': + // return new XrpAddressProcessor(); + // case 'XLM': + // return new XlmAddressProcessor(); + // case 'XMR': + // return new XmrAddressProcessor(); + // case 'FIO': + // return new FioAddressProcessor(); + // case 'BNB': + // return new BnbAddressProcessor(); + // case 'SOL': + // return new SolAddressProcessor(); + // case 'WAVES': + // return new WavesAddressProcessor(); + // case 'ASH': + // return new AshAddressProcessor(); default: throw new Error( 'Unknown addressProcessor ' + currencyDictSettings.addressProcessor @@ -132,90 +126,90 @@ class AirDAODispatcher { getScannerProcessor( currencyCode: string - ): - | BsvScannerProcessor - | BtcScannerProcessor - | UsdtScannerProcessor - | EthScannerProcessorErc20 - | BchScannerProcessor - | LtcScannerProcessor - | XvgScannerProcessor - | BtcTestScannerProcessor - | DogeScannerProcessor - | EthScannerProcessorSoul - | EthScannerProcessor - | BtgScannerProcessor - | TrxScannerProcessor - | XrpScannerProcessor - | XlmScannerProcessor - | XmrScannerProcessor - | FioScannerProcessor - | BnbScannerProcessor - | VetScannerProcessor - | SolScannerProcessor - | SolScannerProcessorSpl - | WavesScannerProcessor - | MetisScannerProcessor - | OneScannerProcessor - | OneScannerProcessorErc20 - | WavesScannerProcessorErc20 { + ): EthScannerProcessor | EthScannerProcessorErc20 { + // | BsvScannerProcessor + // | BtcScannerProcessor + // | UsdtScannerProcessor + // | EthScannerProcessorErc20 + // | BchScannerProcessor + // | LtcScannerProcessor + // | XvgScannerProcessor + // | BtcTestScannerProcessor + // | DogeScannerProcessor + // | EthScannerProcessorSoul + // | EthScannerProcessor + // | BtgScannerProcessor + // | TrxScannerProcessor + // | XrpScannerProcessor + // | XlmScannerProcessor + // | XmrScannerProcessor + // | FioScannerProcessor + // | BnbScannerProcessor + // | VetScannerProcessor + // | SolScannerProcessor + // | SolScannerProcessorSpl + // | WavesScannerProcessor + // | MetisScannerProcessor + // | OneScannerProcessor + // | OneScannerProcessorErc20 + // | WavesScannerProcessorErc20 const currencyDictSettings = BlockchainUtils.getCurrencyAllSettings(currencyCode); switch (currencyDictSettings.scannerProcessor) { - case 'BCH': - return new BchScannerProcessor(currencyDictSettings); - case 'BSV': - return new BsvScannerProcessor(); - case 'BTC': - case 'BTC_SEGWIT': - case 'BTC_SEGWIT_COMPATIBLE': - return new BtcScannerProcessor(currencyDictSettings); - case 'BTC_TEST': - return new BtcTestScannerProcessor(); - case 'BTG': - return new BtgScannerProcessor(currencyDictSettings); - case 'DOGE': - return new DogeScannerProcessor(currencyDictSettings); + // case 'BCH': + // return new BchScannerProcessor(currencyDictSettings); + // case 'BSV': + // return new BsvScannerProcessor(); + // case 'BTC': + // case 'BTC_SEGWIT': + // case 'BTC_SEGWIT_COMPATIBLE': + // return new BtcScannerProcessor(currencyDictSettings); + // case 'BTC_TEST': + // return new BtcTestScannerProcessor(); + // case 'BTG': + // return new BtgScannerProcessor(currencyDictSettings); + // case 'DOGE': + // return new DogeScannerProcessor(currencyDictSettings); case 'ETH': return new EthScannerProcessor(currencyDictSettings); case 'ETH_ERC_20': return new EthScannerProcessorErc20(currencyDictSettings); - case 'ETH_SOUL': - return new EthScannerProcessorSoul(currencyDictSettings); - case 'LTC': - return new LtcScannerProcessor(currencyDictSettings); - case 'TRX': - return new TrxScannerProcessor(currencyDictSettings); - case 'USDT': - return new UsdtScannerProcessor(); - case 'XRP': - return new XrpScannerProcessor(); - case 'XLM': - return new XlmScannerProcessor(); - case 'XVG': - return new XvgScannerProcessor(); - case 'XMR': - return new XmrScannerProcessor(currencyDictSettings); - case 'FIO': - return new FioScannerProcessor(currencyDictSettings); - case 'BNB': - return new BnbScannerProcessor(); - case 'VET': - return new VetScannerProcessor(currencyDictSettings); - case 'SOL': - return new SolScannerProcessor(currencyDictSettings); - case 'SOL_SPL': - return new SolScannerProcessorSpl(currencyDictSettings); - case 'WAVES': - return new WavesScannerProcessor(currencyDictSettings); - case 'METIS': - return new MetisScannerProcessor(currencyDictSettings); - case 'ONE': - return new OneScannerProcessor(currencyDictSettings); - case 'ONE_ERC_20': - return new OneScannerProcessorErc20(currencyDictSettings); - case 'WAVES_ERC_20': - return new WavesScannerProcessorErc20(currencyDictSettings); + // case 'ETH_SOUL': + // return new EthScannerProcessorSoul(currencyDictSettings); + // case 'LTC': + // return new LtcScannerProcessor(currencyDictSettings); + // case 'TRX': + // return new TrxScannerProcessor(currencyDictSettings); + // case 'USDT': + // return new UsdtScannerProcessor(); + // case 'XRP': + // return new XrpScannerProcessor(); + // case 'XLM': + // return new XlmScannerProcessor(); + // case 'XVG': + // return new XvgScannerProcessor(); + // case 'XMR': + // return new XmrScannerProcessor(currencyDictSettings); + // case 'FIO': + // return new FioScannerProcessor(currencyDictSettings); + // case 'BNB': + // return new BnbScannerProcessor(); + // case 'VET': + // return new VetScannerProcessor(currencyDictSettings); + // case 'SOL': + // return new SolScannerProcessor(currencyDictSettings); + // case 'SOL_SPL': + // return new SolScannerProcessorSpl(currencyDictSettings); + // case 'WAVES': + // return new WavesScannerProcessor(currencyDictSettings); + // case 'METIS': + // return new MetisScannerProcessor(currencyDictSettings); + // case 'ONE': + // return new OneScannerProcessor(currencyDictSettings); + // case 'ONE_ERC_20': + // return new OneScannerProcessorErc20(currencyDictSettings); + // case 'WAVES_ERC_20': + // return new WavesScannerProcessorErc20(currencyDictSettings); default: throw new Error( 'Unknown scannerProcessor ' + currencyDictSettings.scannerProcessor @@ -224,9 +218,7 @@ class AirDAODispatcher { } // TODO enum tokenType - getTokenProcessor( - tokenType: string - ): TrxTokenProcessor | EthTokenProcessorErc20 | SolTokenProcessor { + getTokenProcessor(tokenType: string): EthTokenProcessorErc20 { switch (tokenType) { case 'ETH_ERC_20': return new EthTokenProcessorErc20({ diff --git a/crypto/blockchains/xmr/XmrSecretsProcessor.ts b/crypto/blockchains/xmr/XmrSecretsProcessor.ts index 278fdda1a..ee64d788c 100644 --- a/crypto/blockchains/xmr/XmrSecretsProcessor.ts +++ b/crypto/blockchains/xmr/XmrSecretsProcessor.ts @@ -2,15 +2,16 @@ * @version 0.11 * https://github.com/Coinomi/bip39-coinomi/releases */ -import BlocksoftKeys from '../../actions/BlocksoftKeys/BlocksoftKeys'; +// import AirDAOKeys from '../../actions/AirDAOKeys/AirDAOKeys'; +import AirDAOKeys from '@lib/helpers/AirDAOKeys'; import { soliditySha3 } from 'web3-utils'; import MoneroUtils from './ext/MoneroUtils'; import MoneroMnemonic from './ext/MoneroMnemonic'; +import networksConstants from '@crypto/common/ext/networks-constants'; const bip32 = require('bip32'); const bitcoin = require('bitcoinjs-lib'); -const networksConstants = require('../../common/ext/networks-constants'); const BTC = networksConstants.mainnet.network; @@ -19,7 +20,7 @@ export default class XmrSecretsProcessor { * @param {string} data.mnemonic */ async getWords(data: { mnemonic: string }) { - const seed = await BlocksoftKeys.getSeedCached(data.mnemonic); + const seed = await AirDAOKeys.getSeedCached(data.mnemonic); const seedHex = seed.toString('hex'); if (seedHex.length < 128) { throw new Error('bad seedHex'); diff --git a/crypto/common/ext/networks-constants.ts b/crypto/common/ext/networks-constants.ts index 1b7291b87..2ec479d91 100644 --- a/crypto/common/ext/networks-constants.ts +++ b/crypto/common/ext/networks-constants.ts @@ -1,5 +1,5 @@ // https://github.com/iancoleman/bip39/blob/0a23f51792722f094328d695242556c4c0195a8b/src/js/bitcoinjs-extensions.js -import bitcoin from 'bitcoinjs-lib'; +const bitcoin = require('bitcoinjs-lib'); export default { mainnet: { diff --git a/package.json b/package.json index 1a259053f..f591e8046 100644 --- a/package.json +++ b/package.json @@ -61,6 +61,7 @@ "elliptic": "^6.5.4", "ethereumjs-util": "^7.1.5", "ethjs-provider-http": "^0.1.6", + "events": "^3.3.0", "expo": "~48.0.18", "expo-barcode-scanner": "~12.3.2", "expo-build-properties": "~0.6.0", @@ -79,6 +80,7 @@ "expo-status-bar": "~1.4.4", "expo-system-ui": "~2.2.1", "expo-updates": "~0.16.4", + "fast-text-encoding": "^1.0.6", "jest": "^29.2.1", "jest-expo": "^48.0.2", "lodash": "^4.17.21", @@ -106,11 +108,11 @@ "react-native-walkthrough-tooltip": "^1.5.0", "ripple-lib": "^1.10.1", "stellar-sdk": "^10.4.1", + "stream": "^0.0.2", "thorify": "^1.6.2", "tiny-secp256k1": "^2.2.3", "use-context-selector": "^1.4.1", - "web3": "^4.0.3", - "web3-utils": "^4.0.3" + "web3": "1.2.7" }, "devDependencies": { "@babel/core": "^7.20.0", diff --git a/patches/bitcoinjs-lib+5.1.6.patch b/patches/bitcoinjs-lib+5.1.6.patch new file mode 100644 index 000000000..db808421b --- /dev/null +++ b/patches/bitcoinjs-lib+5.1.6.patch @@ -0,0 +1,55 @@ +diff --git a/node_modules/bitcoinjs-lib/src/transaction.js b/node_modules/bitcoinjs-lib/src/transaction.js +index 994202c..d311cbc 100644 +--- a/node_modules/bitcoinjs-lib/src/transaction.js ++++ b/node_modules/bitcoinjs-lib/src/transaction.js +@@ -1,5 +1,5 @@ + 'use strict'; +-import BlocksoftCryptoLog from '../../../crypto/common/BlocksoftCryptoLog' ++// import BlocksoftCryptoLog from '../../../crypto/common/BlocksoftCryptoLog' + + Object.defineProperty(exports, '__esModule', { value: true }); + const bufferutils = require('./bufferutils'); +@@ -50,7 +50,7 @@ class Transaction { + this._IS_BTC_FORK = false; + this.ins = []; + this.outs = []; +- BlocksoftCryptoLog.log('TransactionBuilder Transaction recheck - started as js - time ' + this.time) ++ // BlocksoftCryptoLog.log('TransactionBuilder Transaction recheck - started as js - time ' + this.time) + } + setVerge(_val) { + this._IS_VERGE = _val; +@@ -451,10 +451,10 @@ class Transaction { + } + writeInt32(this.version); + if (this._IS_VERGE) { +- BlocksoftCryptoLog.log('TransactionBuilder Transaction recheck - verge added - ' + this.time); ++ // BlocksoftCryptoLog.log('TransactionBuilder Transaction recheck - verge added - ' + this.time); + writeInt32(this.time); + } else { +- BlocksoftCryptoLog.log('TransactionBuilder Transaction recheck - not verge '); ++ // BlocksoftCryptoLog.log('TransactionBuilder Transaction recheck - not verge '); + } + const hasWitnesses = _ALLOW_WITNESS && this.hasWitnesses(); + if (hasWitnesses) { +diff --git a/node_modules/bitcoinjs-lib/src/transaction_builder.js b/node_modules/bitcoinjs-lib/src/transaction_builder.js +index e5f3572..311df6a 100644 +--- a/node_modules/bitcoinjs-lib/src/transaction_builder.js ++++ b/node_modules/bitcoinjs-lib/src/transaction_builder.js +@@ -1,7 +1,7 @@ + 'use strict'; + Object.defineProperty(exports, '__esModule', { value: true }); + +-import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog' ++// import BlocksoftCryptoLog from '@crypto/common/BlocksoftCryptoLog' + + const baddress = require('./address'); + const bufferutils_1 = require('./bufferutils'); +@@ -60,7 +60,7 @@ class TransactionBuilder { + this.__TX = new transaction_1.Transaction(); + this.__TX.version = 2; + this.__USE_LOW_R = false; +- BlocksoftCryptoLog.log('TransactionBuilder recheck - started as js') ++ // BlocksoftCryptoLog.log('TransactionBuilder recheck - started as js') + } + static fromTransaction(transaction, network) { + const txb = new TransactionBuilder(network); diff --git a/src/lib/common/ext/network-constants.ts b/src/lib/common/ext/network-constants.ts deleted file mode 100644 index 5f9eb63d7..000000000 --- a/src/lib/common/ext/network-constants.ts +++ /dev/null @@ -1,117 +0,0 @@ -import * as bitcoin from 'bitcoinjs-lib'; - -export interface NetworkConfig { - messagePrefix: string; - bech32?: string; - bip32: { - public: number; - private: number; - }; - pubKeyHash: number; - scriptHash: number; - wif: number; - BTCFork?: string; -} - -export interface CoinConfig { - network: NetworkConfig; - langPrefix: string; -} - -export interface CoinConfigs { - [key: string]: CoinConfig; -} - -const coinConfigs: CoinConfigs = { - mainnet: { - network: bitcoin.networks.bitcoin, - langPrefix: 'btc' - }, - testnet: { - network: bitcoin.networks.testnet, - langPrefix: 'btc' - }, - litecoin: { - network: { - bech32: 'ltc', - messagePrefix: '\x19Litecoin Signed Message:\n', - pubKeyHash: 0x30, - scriptHash: 0x32, - wif: 0xb0, - bip32: { - public: 0x019da462, - private: 0x019d9cfe - } - }, - langPrefix: 'ltc' - }, - dogecoin: { - network: { - messagePrefix: '\x19Dogecoin Signed Message:\n', - bip32: { - public: 0x02facafd, - private: 0x02fac398 - }, - pubKeyHash: 0x1e, - scriptHash: 0x16, - wif: 0x9e - }, - langPrefix: 'ltc' - }, - verge: { - network: { - messagePrefix: '\x18VERGE Signed Message:\n', - bip32: { - public: 0x0488b21e, - private: 0x0488ade4 - }, - pubKeyHash: 0x1e, - scriptHash: 0x21, - wif: 0x9e - }, - langPrefix: 'ltc' - }, - bitcoincash: { - network: { - messagePrefix: '\u0018Bitcoin Signed Message:\n', - bech32: 'bc', - bip32: { public: 76067358, private: 76066276 }, - pubKeyHash: 0, - scriptHash: 5, - wif: 128, - BTCFork: 'BCH' - }, - langPrefix: 'bch' - }, - bitcoinsv: { - network: { - messagePrefix: 'unused', - bip32: { - public: 0x0488b21e, - private: 0x0488ade4 - }, - pubKeyHash: 0x00, - scriptHash: 0x05, - wif: 0x80, - BTCFork: 'BCH' - }, - langPrefix: 'bch' - }, - bitcoingold: { - network: { - bech32: 'btg', - messagePrefix: '\x1DBitcoin Gold Signed Message:\n', - bip32: { - public: 0x0488b21e, - private: 0x0488ade4 - }, - pubKeyHash: 38, - scriptHash: 23, - wif: 128, - BTCFork: 'BTG' - }, - langPrefix: 'ltc' - } -}; - -export default coinConfigs; diff --git a/src/lib/helpers/AirDAOKeysForRef.ts b/src/lib/helpers/AirDAOKeysForRef.ts index 59f63cdc0..8b70ef731 100644 --- a/src/lib/helpers/AirDAOKeysForRef.ts +++ b/src/lib/helpers/AirDAOKeysForRef.ts @@ -24,22 +24,23 @@ class AirDAOKeysForRef { const mnemonicCache = data.mnemonic.toLowerCase(); if (typeof CACHE[mnemonicCache] !== 'undefined') return CACHE[mnemonicCache]; - let result = AirDAOKeys.getEthCached(mnemonicCache); + // let result = AirDAOKeys.getEthCached(mnemonicCache); + let result; if (!result) { let index = 0; if (typeof data.index !== 'undefined') { index = data.index; } - const root = await AirDAOKeys.getBip32Cached(data.mnemonic); + // const root = await AirDAOKeys.getBip32Cached(data.mnemonic); + let root; const path = `m/44'/60'/${index}'/0/0`; const child = root.derivePath(path); - const processor = await AirDAODispatcher.getAddressProcessor('ETH'); result = await processor.getAddress(child.privateKey); result.index = index; result.path = path; if (index === 0) { - AirDAOKeys.setEthCached(data.mnemonic, result); + // AirDAOKeys.setEthCached(data.mnemonic, result); } } // noinspection JSPrimitiveTypeWrapperUsage diff --git a/src/lib/helpers/AirDAOKeysForRefStorage.ts b/src/lib/helpers/AirDAOKeysForRefStorage.ts index 4223b1021..9c7b677c5 100644 --- a/src/lib/helpers/AirDAOKeysForRefStorage.ts +++ b/src/lib/helpers/AirDAOKeysForRefStorage.ts @@ -9,6 +9,7 @@ class AirDAOKeysForRefStorage { async getPublicAndPrivateResultForHash(hash: string): Promise { const res = await SecureStore.getItemAsync('cd_' + hash); + console.log({ res }); if (!res) return false; return JSON.parse(res); } diff --git a/src/screens/CreateWallet/CreateWalletStep2.tsx b/src/screens/CreateWallet/CreateWalletStep2.tsx index a34bae6cd..a10b15e0b 100644 --- a/src/screens/CreateWallet/CreateWalletStep2.tsx +++ b/src/screens/CreateWallet/CreateWalletStep2.tsx @@ -7,6 +7,7 @@ import { useAddWalletContext } from '@contexts'; import { moderateScale, scale, verticalScale } from '@utils/scaling'; import { COLORS } from '@constants/colors'; import { WalletUtils } from '@utils/wallet'; +import { useIgnoreInitialMountEffect } from '@hooks'; export const CreateWalletStep2 = () => { const { walletMnemonic } = useAddWalletContext(); @@ -20,6 +21,11 @@ export const CreateWalletStep2 = () => { // eslint-disable-next-line react-hooks/exhaustive-deps [walletMnemonicArrayDefault.length] ); + console.log({ + walletMnemonic, + walletMnemonicArrayDefault: walletMnemonicArrayDefault.length, + walletMnemonicSelected: walletMnemonicSelected.length + }); const validateMnemonic = useCallback(async () => { if (walletMnemonicSelected.length !== walletMnemonicArrayDefault.length) { @@ -29,22 +35,24 @@ export const CreateWalletStep2 = () => { JSON.stringify(walletMnemonicSelected) !== JSON.stringify(walletMnemonicArrayDefault) ) { - Alert.alert('Failed'); - return; + // Alert.alert('Failed'); + // return; } - setLoading(true); + console.log('here'); + // setLoading(true); // TODO fix number await WalletUtils.processWallet({ number: 0, mnemonic: walletMnemonic, name: '' }); - setLoading(false); + // setLoading(false); }, [walletMnemonic, walletMnemonicArrayDefault, walletMnemonicSelected]); useEffect(() => { + console.log('here'); validateMnemonic(); - }, [validateMnemonic, walletMnemonicSelected]); + }, [walletMnemonicSelected, validateMnemonic]); const renderWord = (word: string) => { const selectedIdx = walletMnemonicSelected.indexOf(word); diff --git a/src/utils/cashback.ts b/src/utils/cashback.ts index 742bcc026..f34f36e6f 100644 --- a/src/utils/cashback.ts +++ b/src/utils/cashback.ts @@ -3,24 +3,33 @@ import AirDAOKeysForRefStorage from '@lib/helpers/AirDAOKeysForRefStorage'; import AirDAOKeysForRef from '@lib/helpers/AirDAOKeysForRef'; const getByHash = async (tmpHash: string) => { - // let tmpPublicAndPrivateResult = - // await AirDAOKeysForRefStorage.getPublicAndPrivateResultForHash(tmpHash); - // if ( - // tmpPublicAndPrivateResult && - // typeof tmpPublicAndPrivateResult.cashbackToken !== 'undefined' - // ) { - // // Log.log('SRV/CashBack getByHash ' + tmpHash + ' => ' + tmpPublicAndPrivateResult.cashbackToken) - // return tmpPublicAndPrivateResult; - // } - // // await Log.log('SRV/CashBack getByHash need to discoverPublic', tmpHash) - // const mnemonic = await AirDAOKeysStorage.getWalletMnemonic(tmpHash); - // if (!mnemonic) { - // return false; - // } - // // await Log.log('SRV/CashBack getByHash got mnemonic to discoverPublic') - // tmpPublicAndPrivateResult = await AirDAOKeysForRef.discoverPublicAndPrivate({ - // mnemonic - // }); + let tmpPublicAndPrivateResult = + await AirDAOKeysForRefStorage.getPublicAndPrivateResultForHash(tmpHash); + if ( + tmpPublicAndPrivateResult && + typeof tmpPublicAndPrivateResult.cashbackToken !== 'undefined' + ) { + // Log.log('SRV/CashBack getByHash ' + tmpHash + ' => ' + tmpPublicAndPrivateResult.cashbackToken) + return tmpPublicAndPrivateResult; + } + // await Log.log('SRV/CashBack getByHash need to discoverPublic', tmpHash) + const mnemonic = await AirDAOKeysStorage.getWalletMnemonic(tmpHash); + console.log({ mnemonic }); + + if (!mnemonic) { + return false; + } + // await Log.log('SRV/CashBack getByHash got mnemonic to discoverPublic') + try { + tmpPublicAndPrivateResult = await AirDAOKeysForRef.discoverPublicAndPrivate( + { + mnemonic + } + ); + console.log({ tmpPublicAndPrivateResult }); + } catch (error) { + console.log({ error }); + } // // await Log.log('SRV/CashBack getByHash done discoverPublic ' + tmpHash + ' => ' + tmpPublicAndPrivateResult.cashbackToken) // try { // await AirDAOKeysForRefStorage.setPublicAndPrivateResultForHash( @@ -28,7 +37,7 @@ const getByHash = async (tmpHash: string) => { // tmpPublicAndPrivateResult // ); // } catch (e) {} - // return tmpPublicAndPrivateResult; + return tmpPublicAndPrivateResult; }; export const CashBackUtils = { getByHash }; diff --git a/yarn.lock b/yarn.lock index 47e545359..e51837988 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12,11 +12,6 @@ resolved "https://registry.yarnpkg.com/@adobe/css-tools/-/css-tools-4.2.0.tgz#e1a84fca468f4b337816fcb7f0964beb620ba855" integrity sha512-E09FiIft46CmH5Qnjb0wsW54/YQd69LsxeKUOWawmws1XWvyFGURnAChH0mlr7YPFR1ofwvUQfcL0J3lMxXqPA== -"@adraffy/ens-normalize@^1.8.8": - version "1.9.4" - resolved "https://registry.yarnpkg.com/@adraffy/ens-normalize/-/ens-normalize-1.9.4.tgz#aae21cb858bbb0411949d5b7b3051f4209043f62" - integrity sha512-UK0bHA7hh9cR39V+4gl2/NnBBjoXIxkuWAPCaY4X7fbH4L/azIi7ilWOCjMUYfpJgraLUAqkRi2BqrjME8Rynw== - "@ampproject/remapping@^2.2.0": version "2.2.0" resolved "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz" @@ -1530,188 +1525,6 @@ resolved "https://registry.npmjs.org/@eslint/js/-/js-8.37.0.tgz" integrity sha512-x5vzdtOOGgFVDCUs81QRB2+liax8rFg3+7hqM+QhBG0/G3F1ZsoYl97UrqgHgQ9KKT7G6c4V+aTUCgu/n22v1A== -"@ethereumjs/rlp@^4.0.1": - version "4.0.1" - resolved "https://registry.yarnpkg.com/@ethereumjs/rlp/-/rlp-4.0.1.tgz#626fabfd9081baab3d0a3074b0c7ecaf674aaa41" - integrity sha512-tqsQiBQDQdmPWE1xkkBq4rlSW5QZpLOUJ5RJh2/9fug+q9tnUhuZoVLk7s0scUIKTOzEtR72DFBXI4WiZcMpvw== - -"@ethersproject/abi@^5.7.0": - version "5.7.0" - resolved "https://registry.yarnpkg.com/@ethersproject/abi/-/abi-5.7.0.tgz#b3f3e045bbbeed1af3947335c247ad625a44e449" - integrity sha512-351ktp42TiRcYB3H1OP8yajPeAQstMW/yCFokj/AthP9bLHzQFPlOrxOcwYEDkUAICmOHljvN4K39OMTMUa9RA== - dependencies: - "@ethersproject/address" "^5.7.0" - "@ethersproject/bignumber" "^5.7.0" - "@ethersproject/bytes" "^5.7.0" - "@ethersproject/constants" "^5.7.0" - "@ethersproject/hash" "^5.7.0" - "@ethersproject/keccak256" "^5.7.0" - "@ethersproject/logger" "^5.7.0" - "@ethersproject/properties" "^5.7.0" - "@ethersproject/strings" "^5.7.0" - -"@ethersproject/abstract-provider@^5.7.0": - version "5.7.0" - resolved "https://registry.yarnpkg.com/@ethersproject/abstract-provider/-/abstract-provider-5.7.0.tgz#b0a8550f88b6bf9d51f90e4795d48294630cb9ef" - integrity sha512-R41c9UkchKCpAqStMYUpdunjo3pkEvZC3FAwZn5S5MGbXoMQOHIdHItezTETxAO5bevtMApSyEhn9+CHcDsWBw== - dependencies: - "@ethersproject/bignumber" "^5.7.0" - "@ethersproject/bytes" "^5.7.0" - "@ethersproject/logger" "^5.7.0" - "@ethersproject/networks" "^5.7.0" - "@ethersproject/properties" "^5.7.0" - "@ethersproject/transactions" "^5.7.0" - "@ethersproject/web" "^5.7.0" - -"@ethersproject/abstract-signer@^5.7.0": - version "5.7.0" - resolved "https://registry.yarnpkg.com/@ethersproject/abstract-signer/-/abstract-signer-5.7.0.tgz#13f4f32117868452191a4649723cb086d2b596b2" - integrity sha512-a16V8bq1/Cz+TGCkE2OPMTOUDLS3grCpdjoJCYNnVBbdYEMSgKrU0+B90s8b6H+ByYTBZN7a3g76jdIJi7UfKQ== - dependencies: - "@ethersproject/abstract-provider" "^5.7.0" - "@ethersproject/bignumber" "^5.7.0" - "@ethersproject/bytes" "^5.7.0" - "@ethersproject/logger" "^5.7.0" - "@ethersproject/properties" "^5.7.0" - -"@ethersproject/address@^5.7.0": - version "5.7.0" - resolved "https://registry.yarnpkg.com/@ethersproject/address/-/address-5.7.0.tgz#19b56c4d74a3b0a46bfdbb6cfcc0a153fc697f37" - integrity sha512-9wYhYt7aghVGo758POM5nqcOMaE168Q6aRLJZwUmiqSrAungkG74gSSeKEIR7ukixesdRZGPgVqme6vmxs1fkA== - dependencies: - "@ethersproject/bignumber" "^5.7.0" - "@ethersproject/bytes" "^5.7.0" - "@ethersproject/keccak256" "^5.7.0" - "@ethersproject/logger" "^5.7.0" - "@ethersproject/rlp" "^5.7.0" - -"@ethersproject/base64@^5.7.0": - version "5.7.0" - resolved "https://registry.yarnpkg.com/@ethersproject/base64/-/base64-5.7.0.tgz#ac4ee92aa36c1628173e221d0d01f53692059e1c" - integrity sha512-Dr8tcHt2mEbsZr/mwTPIQAf3Ai0Bks/7gTw9dSqk1mQvhW3XvRlmDJr/4n+wg1JmCl16NZue17CDh8xb/vZ0sQ== - dependencies: - "@ethersproject/bytes" "^5.7.0" - -"@ethersproject/bignumber@^5.7.0": - version "5.7.0" - resolved "https://registry.yarnpkg.com/@ethersproject/bignumber/-/bignumber-5.7.0.tgz#e2f03837f268ba655ffba03a57853e18a18dc9c2" - integrity sha512-n1CAdIHRWjSucQO3MC1zPSVgV/6dy/fjL9pMrPP9peL+QxEg9wOsVqwD4+818B6LUEtaXzVHQiuivzRoxPxUGw== - dependencies: - "@ethersproject/bytes" "^5.7.0" - "@ethersproject/logger" "^5.7.0" - bn.js "^5.2.1" - -"@ethersproject/bytes@^5.7.0": - version "5.7.0" - resolved "https://registry.yarnpkg.com/@ethersproject/bytes/-/bytes-5.7.0.tgz#a00f6ea8d7e7534d6d87f47188af1148d71f155d" - integrity sha512-nsbxwgFXWh9NyYWo+U8atvmMsSdKJprTcICAkvbBffT75qDocbuggBU0SJiVK2MuTrp0q+xvLkTnGMPK1+uA9A== - dependencies: - "@ethersproject/logger" "^5.7.0" - -"@ethersproject/constants@^5.7.0": - version "5.7.0" - resolved "https://registry.yarnpkg.com/@ethersproject/constants/-/constants-5.7.0.tgz#df80a9705a7e08984161f09014ea012d1c75295e" - integrity sha512-DHI+y5dBNvkpYUMiRQyxRBYBefZkJfo70VUkUAsRjcPs47muV9evftfZ0PJVCXYbAiCgght0DtcF9srFQmIgWA== - dependencies: - "@ethersproject/bignumber" "^5.7.0" - -"@ethersproject/hash@^5.7.0": - version "5.7.0" - resolved "https://registry.yarnpkg.com/@ethersproject/hash/-/hash-5.7.0.tgz#eb7aca84a588508369562e16e514b539ba5240a7" - integrity sha512-qX5WrQfnah1EFnO5zJv1v46a8HW0+E5xuBBDTwMFZLuVTx0tbU2kkx15NqdjxecrLGatQN9FGQKpb1FKdHCt+g== - dependencies: - "@ethersproject/abstract-signer" "^5.7.0" - "@ethersproject/address" "^5.7.0" - "@ethersproject/base64" "^5.7.0" - "@ethersproject/bignumber" "^5.7.0" - "@ethersproject/bytes" "^5.7.0" - "@ethersproject/keccak256" "^5.7.0" - "@ethersproject/logger" "^5.7.0" - "@ethersproject/properties" "^5.7.0" - "@ethersproject/strings" "^5.7.0" - -"@ethersproject/keccak256@^5.7.0": - version "5.7.0" - resolved "https://registry.yarnpkg.com/@ethersproject/keccak256/-/keccak256-5.7.0.tgz#3186350c6e1cd6aba7940384ec7d6d9db01f335a" - integrity sha512-2UcPboeL/iW+pSg6vZ6ydF8tCnv3Iu/8tUmLLzWWGzxWKFFqOBQFLo6uLUv6BDrLgCDfN28RJ/wtByx+jZ4KBg== - dependencies: - "@ethersproject/bytes" "^5.7.0" - js-sha3 "0.8.0" - -"@ethersproject/logger@^5.7.0": - version "5.7.0" - resolved "https://registry.yarnpkg.com/@ethersproject/logger/-/logger-5.7.0.tgz#6ce9ae168e74fecf287be17062b590852c311892" - integrity sha512-0odtFdXu/XHtjQXJYA3u9G0G8btm0ND5Cu8M7i5vhEcE8/HmF4Lbdqanwyv4uQTr2tx6b7fQRmgLrsnpQlmnig== - -"@ethersproject/networks@^5.7.0": - version "5.7.1" - resolved "https://registry.yarnpkg.com/@ethersproject/networks/-/networks-5.7.1.tgz#118e1a981d757d45ccea6bb58d9fd3d9db14ead6" - integrity sha512-n/MufjFYv3yFcUyfhnXotyDlNdFb7onmkSy8aQERi2PjNcnWQ66xXxa3XlS8nCcA8aJKJjIIMNJTC7tu80GwpQ== - dependencies: - "@ethersproject/logger" "^5.7.0" - -"@ethersproject/properties@^5.7.0": - version "5.7.0" - resolved "https://registry.yarnpkg.com/@ethersproject/properties/-/properties-5.7.0.tgz#a6e12cb0439b878aaf470f1902a176033067ed30" - integrity sha512-J87jy8suntrAkIZtecpxEPxY//szqr1mlBaYlQ0r4RCaiD2hjheqF9s1LVE8vVuJCXisjIP+JgtK/Do54ej4Sw== - dependencies: - "@ethersproject/logger" "^5.7.0" - -"@ethersproject/rlp@^5.7.0": - version "5.7.0" - resolved "https://registry.yarnpkg.com/@ethersproject/rlp/-/rlp-5.7.0.tgz#de39e4d5918b9d74d46de93af80b7685a9c21304" - integrity sha512-rBxzX2vK8mVF7b0Tol44t5Tb8gomOHkj5guL+HhzQ1yBh/ydjGnpw6at+X6Iw0Kp3OzzzkcKp8N9r0W4kYSs9w== - dependencies: - "@ethersproject/bytes" "^5.7.0" - "@ethersproject/logger" "^5.7.0" - -"@ethersproject/signing-key@^5.7.0": - version "5.7.0" - resolved "https://registry.yarnpkg.com/@ethersproject/signing-key/-/signing-key-5.7.0.tgz#06b2df39411b00bc57c7c09b01d1e41cf1b16ab3" - integrity sha512-MZdy2nL3wO0u7gkB4nA/pEf8lu1TlFswPNmy8AiYkfKTdO6eXBJyUdmHO/ehm/htHw9K/qF8ujnTyUAD+Ry54Q== - dependencies: - "@ethersproject/bytes" "^5.7.0" - "@ethersproject/logger" "^5.7.0" - "@ethersproject/properties" "^5.7.0" - bn.js "^5.2.1" - elliptic "6.5.4" - hash.js "1.1.7" - -"@ethersproject/strings@^5.7.0": - version "5.7.0" - resolved "https://registry.yarnpkg.com/@ethersproject/strings/-/strings-5.7.0.tgz#54c9d2a7c57ae8f1205c88a9d3a56471e14d5ed2" - integrity sha512-/9nu+lj0YswRNSH0NXYqrh8775XNyEdUQAuf3f+SmOrnVewcJ5SBNAjF7lpgehKi4abvNNXyf+HX86czCdJ8Mg== - dependencies: - "@ethersproject/bytes" "^5.7.0" - "@ethersproject/constants" "^5.7.0" - "@ethersproject/logger" "^5.7.0" - -"@ethersproject/transactions@^5.7.0": - version "5.7.0" - resolved "https://registry.yarnpkg.com/@ethersproject/transactions/-/transactions-5.7.0.tgz#91318fc24063e057885a6af13fdb703e1f993d3b" - integrity sha512-kmcNicCp1lp8qanMTC3RIikGgoJ80ztTyvtsFvCYpSCfkjhD0jZ2LOrnbcuxuToLIUYYf+4XwD1rP+B/erDIhQ== - dependencies: - "@ethersproject/address" "^5.7.0" - "@ethersproject/bignumber" "^5.7.0" - "@ethersproject/bytes" "^5.7.0" - "@ethersproject/constants" "^5.7.0" - "@ethersproject/keccak256" "^5.7.0" - "@ethersproject/logger" "^5.7.0" - "@ethersproject/properties" "^5.7.0" - "@ethersproject/rlp" "^5.7.0" - "@ethersproject/signing-key" "^5.7.0" - -"@ethersproject/web@^5.7.0": - version "5.7.1" - resolved "https://registry.yarnpkg.com/@ethersproject/web/-/web-5.7.1.tgz#de1f285b373149bee5928f4eb7bcb87ee5fbb4ae" - integrity sha512-Gueu8lSvyjBWL4cYsWsjh6MtMwM0+H4HvqFPZfB6dV8ctbP9zFAO73VG1cMWae0FLPCtz0peKPpZY8/ugJJX2w== - dependencies: - "@ethersproject/base64" "^5.7.0" - "@ethersproject/bytes" "^5.7.0" - "@ethersproject/logger" "^5.7.0" - "@ethersproject/properties" "^5.7.0" - "@ethersproject/strings" "^5.7.0" - "@expo/bunyan@4.0.0", "@expo/bunyan@^4.0.0": version "4.0.0" resolved "https://registry.npmjs.org/@expo/bunyan/-/bunyan-4.0.0.tgz" @@ -2483,14 +2296,14 @@ dependencies: crypto-js "^3.1.9-1" -"@noble/curves@1.1.0", "@noble/curves@^1.0.0", "@noble/curves@~1.1.0": +"@noble/curves@^1.0.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.1.0.tgz#f13fc667c89184bc04cccb9b11e8e7bae27d8c3d" integrity sha512-091oBExgENk/kGj3AZmtBDMpxQPDtxQABR2B9lb1JbVTs6ytdzZNwvhxQ4MWasRNEzlbEH8jCWFCwhF/Obj5AA== dependencies: "@noble/hashes" "1.3.1" -"@noble/hashes@1.3.1", "@noble/hashes@^1.2.0", "@noble/hashes@^1.3.0", "@noble/hashes@~1.3.0", "@noble/hashes@~1.3.1": +"@noble/hashes@1.3.1", "@noble/hashes@^1.2.0", "@noble/hashes@^1.3.0": version "1.3.1" resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.1.tgz#8831ef002114670c603c458ab8b11328406953a9" integrity sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA== @@ -2823,28 +2636,11 @@ dependencies: nanoid "^3.1.23" -"@scure/base@^1.1.1", "@scure/base@~1.1.0": +"@scure/base@^1.1.1": version "1.1.1" resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.1.tgz#ebb651ee52ff84f420097055f4bf46cfba403938" integrity sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA== -"@scure/bip32@1.3.1": - version "1.3.1" - resolved "https://registry.yarnpkg.com/@scure/bip32/-/bip32-1.3.1.tgz#7248aea723667f98160f593d621c47e208ccbb10" - integrity sha512-osvveYtyzdEVbt3OfwwXFr4P2iVBL5u1Q3q4ONBfDY/UpOuXmOlbgwc1xECEboY8wIays8Yt6onaWMUdUbfl0A== - dependencies: - "@noble/curves" "~1.1.0" - "@noble/hashes" "~1.3.1" - "@scure/base" "~1.1.0" - -"@scure/bip39@1.2.1": - version "1.2.1" - resolved "https://registry.yarnpkg.com/@scure/bip39/-/bip39-1.2.1.tgz#5cee8978656b272a917b7871c981e0541ad6ac2a" - integrity sha512-Z3/Fsz1yr904dduJD0NpiyRHhRYHdcnyh73FZWiV+/qhWi83wNJ3NWolYqCEN+ZWsUz2TWwajJggcRE9r1zUYg== - dependencies: - "@noble/hashes" "~1.3.0" - "@scure/base" "~1.1.0" - "@segment/loosely-validate-event@^2.0.0": version "2.0.0" resolved "https://registry.npmjs.org/@segment/loosely-validate-event/-/loosely-validate-event-2.0.0.tgz" @@ -2889,6 +2685,16 @@ resolved "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.25.24.tgz" integrity sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ== +"@sindresorhus/is@^0.14.0": + version "0.14.0" + resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea" + integrity sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ== + +"@sindresorhus/is@^4.0.0": + version "4.6.0" + resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-4.6.0.tgz#3c7c9c46e678feefe7a2e5bb609d3dbd665ffb3f" + integrity sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw== + "@sinonjs/commons@^2.0.0": version "2.0.0" resolved "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz" @@ -2931,6 +2737,20 @@ rpc-websockets "^7.5.1" superstruct "^0.14.2" +"@szmarczak/http-timer@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-1.1.2.tgz#b1665e2c461a2cd92f4c1bbf50d5454de0d4b421" + integrity sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA== + dependencies: + defer-to-connect "^1.0.1" + +"@szmarczak/http-timer@^4.0.5": + version "4.0.6" + resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-4.0.6.tgz#b4a914bb62e7c272d4e5989fe4440f812ab1d807" + integrity sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w== + dependencies: + defer-to-connect "^2.0.0" + "@tanstack/query-core@4.27.0": version "4.27.0" resolved "https://registry.npmjs.org/@tanstack/query-core/-/query-core-4.27.0.tgz" @@ -3004,6 +2824,13 @@ dependencies: "@babel/types" "^7.3.0" +"@types/bn.js@^4.11.3", "@types/bn.js@^4.11.4": + version "4.11.6" + resolved "https://registry.yarnpkg.com/@types/bn.js/-/bn.js-4.11.6.tgz#c306c70d9358aaea33cd4eda092a742b9505967c" + integrity sha512-pqr857jrp2kPuO9uRjZ3PwnJTjoQy+fcdxvBTvHm6dkmEL9q+hDD/2j/0ELOBPtPnS8LjCX0gI9nbl8lVkadpg== + dependencies: + "@types/node" "*" + "@types/bn.js@^5.1.0": version "5.1.1" resolved "https://registry.yarnpkg.com/@types/bn.js/-/bn.js-5.1.1.tgz#b51e1b55920a4ca26e9285ff79936bbdec910682" @@ -3011,6 +2838,16 @@ dependencies: "@types/node" "*" +"@types/cacheable-request@^6.0.1": + version "6.0.3" + resolved "https://registry.yarnpkg.com/@types/cacheable-request/-/cacheable-request-6.0.3.tgz#a430b3260466ca7b5ca5bfd735693b36e7a9d183" + integrity sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw== + dependencies: + "@types/http-cache-semantics" "*" + "@types/keyv" "^3.1.4" + "@types/node" "*" + "@types/responselike" "^1.0.0" + "@types/connect@^3.4.33": version "3.4.35" resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.35.tgz#5fcf6ae445e4021d1fc2219a4873cc73a3bb2ad1" @@ -3035,6 +2872,11 @@ resolved "https://registry.npmjs.org/@types/hammerjs/-/hammerjs-2.0.41.tgz" integrity sha512-ewXv/ceBaJprikMcxCmWU1FKyMAQ2X7a9Gtmzw8fcg2kIePI1crERDM818W+XYrxqdBBOdlf2rm137bU+BltCA== +"@types/http-cache-semantics@*": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz#0ea7b61496902b95890dc4c3a116b60cb8dae812" + integrity sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ== + "@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": version "2.0.4" resolved "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz" @@ -3081,6 +2923,13 @@ resolved "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz" integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== +"@types/keyv@^3.1.4": + version "3.1.4" + resolved "https://registry.yarnpkg.com/@types/keyv/-/keyv-3.1.4.tgz#3ccdb1c6751b0c7e52300bcdacd5bcbf8faa75b6" + integrity sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg== + dependencies: + "@types/node" "*" + "@types/lodash@^4.14.136", "@types/lodash@^4.14.196": version "4.14.196" resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.196.tgz#a7c3d6fc52d8d71328b764e28e080b4169ec7a95" @@ -3106,12 +2955,12 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-20.4.7.tgz#74d323a93f1391a63477b27b9aec56669c98b2ab" integrity sha512-bUBrPjEry2QUTsnuEjzjbS7voGWCc30W0qzgMf90GPeDGFRakvrz47ju+oqDAKCXLUCe39u57/ORMl/O/04/9g== -"@types/node@^10.3.2": +"@types/node@^10.12.18", "@types/node@^10.3.2": version "10.17.60" resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.60.tgz#35f3d6213daed95da7f0f73e75bcc6980e90597b" integrity sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw== -"@types/node@^12.12.54": +"@types/node@^12.12.54", "@types/node@^12.6.1": version "12.20.55" resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.55.tgz#c329cbd434c42164f846b909bd6f85b5537f6240" integrity sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ== @@ -3179,6 +3028,13 @@ "@types/scheduler" "*" csstype "^3.0.2" +"@types/responselike@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@types/responselike/-/responselike-1.0.0.tgz#251f4fe7d154d2bad125abe1b429b23afd262e29" + integrity sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA== + dependencies: + "@types/node" "*" + "@types/scheduler@*": version "0.16.3" resolved "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz" @@ -3395,6 +3251,25 @@ js-sha3 "^0.8.0" node-forge "^0.10.0" +"@web3-js/scrypt-shim@^0.1.0": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@web3-js/scrypt-shim/-/scrypt-shim-0.1.0.tgz#0bf7529ab6788311d3e07586f7d89107c3bea2cc" + integrity sha512-ZtZeWCc/s0nMcdx/+rZwY1EcuRdemOK9ag21ty9UsHkFxsNb/AaoucUz0iPuyGe0Ku+PFuRmWZG7Z7462p9xPw== + dependencies: + scryptsy "^2.1.0" + semver "^6.3.0" + +"@web3-js/websocket@^1.0.29": + version "1.0.30" + resolved "https://registry.yarnpkg.com/@web3-js/websocket/-/websocket-1.0.30.tgz#9ea15b7b582cf3bf3e8bc1f4d3d54c0731a87f87" + integrity sha512-fDwrD47MiDrzcJdSeTLF75aCcxVVt8B1N74rA+vh2XCAvFy4tEWJjtnUtj2QG7/zlQ6g9cQ88bZFBxwd9/FmtA== + dependencies: + debug "^2.2.0" + es5-ext "^0.10.50" + nan "^2.14.0" + typedarray-to-buffer "^3.1.5" + yaeti "^0.0.6" + "@xmldom/xmldom@~0.7.7": version "0.7.10" resolved "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.7.10.tgz" @@ -3430,7 +3305,7 @@ absolute-path@^0.0.0: resolved "https://registry.npmjs.org/absolute-path/-/absolute-path-0.0.0.tgz" integrity sha512-HQiug4c+/s3WOvEnDRxXVmNtSG5s2gJM9r19BTcqjp7BWcE48PB+Y2G6jE65kqI0LpsQeMZygt/b60Gi4KxGyA== -accepts@^1.3.7, accepts@^1.3.8, accepts@~1.3.5, accepts@~1.3.7: +accepts@^1.3.7, accepts@^1.3.8, accepts@~1.3.5, accepts@~1.3.7, accepts@~1.3.8: version "1.3.8" resolved "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz" integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== @@ -3490,7 +3365,7 @@ aggregate-error@^3.0.0: clean-stack "^2.0.0" indent-string "^4.0.0" -ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.4: +ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.3, ajv@^6.12.4: version "6.12.6" resolved "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz" integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== @@ -3652,6 +3527,11 @@ array-buffer-byte-length@^1.0.0: call-bind "^1.0.2" is-array-buffer "^3.0.1" +array-flatten@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" + integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== + array-includes@^3.1.5, array-includes@^3.1.6: version "3.1.6" resolved "https://registry.npmjs.org/array-includes/-/array-includes-3.1.6.tgz" @@ -3709,6 +3589,28 @@ asap@~2.0.3, asap@~2.0.6: resolved "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz" integrity sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA== +asn1.js@^5.2.0: + version "5.4.1" + resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-5.4.1.tgz#11a980b84ebb91781ce35b0fdc2ee294e3783f07" + integrity sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA== + dependencies: + bn.js "^4.0.0" + inherits "^2.0.1" + minimalistic-assert "^1.0.0" + safer-buffer "^2.1.0" + +asn1@~0.2.3: + version "0.2.6" + resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.6.tgz#0d3a7bb6e64e02a90c0303b31f292868ea09a08d" + integrity sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ== + dependencies: + safer-buffer "~2.1.0" + +assert-plus@1.0.0, assert-plus@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" + integrity sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw== + assert@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/assert/-/assert-2.0.0.tgz#95fc1c616d48713510680f2eaf2d10dd22e02d32" @@ -3771,6 +3673,16 @@ available-typed-arrays@^1.0.5: resolved "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz" integrity sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw== +aws-sign2@~0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" + integrity sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA== + +aws4@^1.8.0: + version "1.12.0" + resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.12.0.tgz#ce1c9d143389679e253b314241ea9aa5cec980d3" + integrity sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg== + axios@0.25.0: version "0.25.0" resolved "https://registry.yarnpkg.com/axios/-/axios-0.25.0.tgz#349cfbb31331a9b4453190791760a8d35b093e0a" @@ -4001,6 +3913,13 @@ base@^0.11.1: mixin-deep "^1.2.0" pascalcase "^0.1.1" +bcrypt-pbkdf@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" + integrity sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w== + dependencies: + tweetnacl "^0.14.3" + bech32@^1.1.2: version "1.1.4" resolved "https://registry.yarnpkg.com/bech32/-/bech32-1.1.4.tgz#e38c9f37bf179b8eb16ae3a772b40c356d4832e9" @@ -4139,22 +4058,55 @@ blakejs@^1.1.0: resolved "https://registry.yarnpkg.com/blakejs/-/blakejs-1.2.1.tgz#5057e4206eadb4a97f7c0b6e197a505042fc3814" integrity sha512-QXUSXI3QVc/gJME0dBpXrag1kbzOqCjCX8/b54ntNyW6sjtoqxqRk3LTmXzaJoh71zMsDCjM+47jS7XiwN/+fQ== +bluebird@^3.5.0: + version "3.7.2" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" + integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== + blueimp-md5@^2.10.0: version "2.19.0" resolved "https://registry.npmjs.org/blueimp-md5/-/blueimp-md5-2.19.0.tgz" integrity sha512-DRQrD6gJyy8FbiE4s+bDoXS9hiW3Vbx5uCdwvcCf3zLHL+Iv7LtGHLpr+GZV8rHG8tK766FGYBwRbu8pELTt+w== -bn.js@^4.11.8, bn.js@^4.11.9, bn.js@^4.4.0: +bn.js@4.11.6: + version "4.11.6" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.6.tgz#53344adb14617a13f6e8dd2ce28905d1c0ba3215" + integrity sha512-XWwnNNFCuuSQ0m3r3C4LE3EiORltHd9M05pq6FOlVeiophzRbMo50Sbz1ehl8K3Z+jw9+vmgnXefY1hz8X+2wA== + +bn.js@4.11.8: + version "4.11.8" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f" + integrity sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA== + +bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.11.0, bn.js@^4.11.6, bn.js@^4.11.8, bn.js@^4.11.9, bn.js@^4.4.0: version "4.12.0" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA== -bn.js@^5.1.1, bn.js@^5.1.2, bn.js@^5.2.0, bn.js@^5.2.1: +bn.js@^5.0.0, bn.js@^5.1.1, bn.js@^5.1.2, bn.js@^5.2.0, bn.js@^5.2.1: version "5.2.1" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.1.tgz#0bc527a6a0d18d0aa8d5b0538ce4a77dccfa7b70" integrity sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ== -body-parser@^1.20.1: +body-parser@1.20.1: + version "1.20.1" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.1.tgz#b1812a8912c195cd371a3ee5e66faa2338a5c668" + integrity sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw== + dependencies: + bytes "3.1.2" + content-type "~1.0.4" + debug "2.6.9" + depd "2.0.0" + destroy "1.2.0" + http-errors "2.0.0" + iconv-lite "0.4.24" + on-finished "2.4.1" + qs "6.11.0" + raw-body "2.5.1" + type-is "~1.6.18" + unpipe "1.0.0" + +body-parser@^1.16.0, body-parser@^1.20.1: version "1.20.2" resolved "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz" integrity sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA== @@ -4243,7 +4195,7 @@ brorand@^1.0.1, brorand@^1.0.5, brorand@^1.1.0: resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" integrity sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w== -browserify-aes@^1.0.6, browserify-aes@^1.2.0: +browserify-aes@^1.0.0, browserify-aes@^1.0.4, browserify-aes@^1.0.6, browserify-aes@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.2.0.tgz#326734642f403dabc3003209853bb70ad428ef48" integrity sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA== @@ -4255,6 +4207,48 @@ browserify-aes@^1.0.6, browserify-aes@^1.2.0: inherits "^2.0.1" safe-buffer "^5.0.1" +browserify-cipher@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/browserify-cipher/-/browserify-cipher-1.0.1.tgz#8d6474c1b870bfdabcd3bcfcc1934a10e94f15f0" + integrity sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w== + dependencies: + browserify-aes "^1.0.4" + browserify-des "^1.0.0" + evp_bytestokey "^1.0.0" + +browserify-des@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/browserify-des/-/browserify-des-1.0.2.tgz#3af4f1f59839403572f1c66204375f7a7f703e9c" + integrity sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A== + dependencies: + cipher-base "^1.0.1" + des.js "^1.0.0" + inherits "^2.0.1" + safe-buffer "^5.1.2" + +browserify-rsa@^4.0.0, browserify-rsa@^4.0.1: + version "4.1.0" + resolved "https://registry.yarnpkg.com/browserify-rsa/-/browserify-rsa-4.1.0.tgz#b2fd06b5b75ae297f7ce2dc651f918f5be158c8d" + integrity sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog== + dependencies: + bn.js "^5.0.0" + randombytes "^2.0.1" + +browserify-sign@^4.0.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/browserify-sign/-/browserify-sign-4.2.1.tgz#eaf4add46dd54be3bb3b36c0cf15abbeba7956c3" + integrity sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg== + dependencies: + bn.js "^5.1.1" + browserify-rsa "^4.0.1" + create-hash "^1.2.0" + create-hmac "^1.1.7" + elliptic "^6.5.3" + inherits "^2.0.4" + parse-asn1 "^5.1.5" + readable-stream "^3.6.0" + safe-buffer "^5.2.0" + browserslist@^4.21.3, browserslist@^4.21.5: version "4.21.5" resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz" @@ -4331,6 +4325,11 @@ buffer-from@^1.0.0: resolved "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz" integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== +buffer-to-arraybuffer@^0.0.5: + version "0.0.5" + resolved "https://registry.yarnpkg.com/buffer-to-arraybuffer/-/buffer-to-arraybuffer-0.0.5.tgz#6064a40fa76eb43c723aba9ef8f6e1216d10511a" + integrity sha512-3dthu5CYiVB1DEJp61FtApNnNndTckcqe4pFcLdvHtrpG+kcyekCJKg4MRiDcFW7A6AODnXB9U4dwQiCW5kzJQ== + buffer-xor@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" @@ -4344,7 +4343,7 @@ buffer@6.0.3, buffer@^6.0.3, buffer@~6.0.3: base64-js "^1.3.1" ieee754 "^1.2.1" -buffer@^5.1.0, buffer@^5.5.0: +buffer@^5.0.5, buffer@^5.1.0, buffer@^5.5.0: version "5.7.1" resolved "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz" integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== @@ -4425,6 +4424,37 @@ cache-base@^1.0.1: union-value "^1.0.0" unset-value "^1.0.0" +cacheable-lookup@^5.0.3: + version "5.0.4" + resolved "https://registry.yarnpkg.com/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz#5a6b865b2c44357be3d5ebc2a467b032719a7005" + integrity sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA== + +cacheable-request@^6.0.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-6.1.0.tgz#20ffb8bd162ba4be11e9567d823db651052ca912" + integrity sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg== + dependencies: + clone-response "^1.0.2" + get-stream "^5.1.0" + http-cache-semantics "^4.0.0" + keyv "^3.0.0" + lowercase-keys "^2.0.0" + normalize-url "^4.1.0" + responselike "^1.0.2" + +cacheable-request@^7.0.2: + version "7.0.4" + resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-7.0.4.tgz#7a33ebf08613178b403635be7b899d3e69bbe817" + integrity sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg== + dependencies: + clone-response "^1.0.2" + get-stream "^5.1.0" + http-cache-semantics "^4.0.0" + keyv "^4.0.0" + lowercase-keys "^2.0.0" + normalize-url "^6.0.1" + responselike "^2.0.0" + call-bind@^1.0.0, call-bind@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz" @@ -4477,6 +4507,11 @@ canvaskit-wasm@0.38.0: resolved "https://registry.yarnpkg.com/canvaskit-wasm/-/canvaskit-wasm-0.38.0.tgz#83e6c46f3015c2ff3f6503157f47453af76a7be7" integrity sha512-ZEG6lucpbQ4Ld+mY8C1Ng+PMLVP+/AX02jS0Sdl28NyMxuKSa9uKB8oGd1BYp1XWPyO2Jgr7U8pdyjJ/F3xR5Q== +caseless@~0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" + integrity sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw== + chalk@5.2.0: version "5.2.0" resolved "https://registry.npmjs.org/chalk/-/chalk-5.2.0.tgz" @@ -4522,6 +4557,11 @@ charenc@0.0.2, charenc@~0.0.1: resolved "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz" integrity sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA== +chownr@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" + integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== + chownr@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz" @@ -4637,6 +4677,13 @@ clone-deep@^4.0.1: kind-of "^6.0.2" shallow-clone "^3.0.0" +clone-response@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.3.tgz#af2032aa47816399cf5f0a1d0db902f517abb8c3" + integrity sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA== + dependencies: + mimic-response "^1.0.0" + clone@^1.0.2: version "1.0.4" resolved "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz" @@ -4720,7 +4767,7 @@ colorette@^2.0.19: resolved "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz" integrity sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ== -combined-stream@^1.0.8: +combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6: version "1.0.8" resolved "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz" integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== @@ -4822,7 +4869,14 @@ connect@^3.6.5, connect@^3.7.0: parseurl "~1.3.3" utils-merge "1.0.1" -content-type@~1.0.5: +content-disposition@0.5.4: + version "0.5.4" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" + integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== + dependencies: + safe-buffer "5.2.1" + +content-type@~1.0.4, content-type@~1.0.5: version "1.0.5" resolved "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz" integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== @@ -4837,6 +4891,21 @@ convert-source-map@^2.0.0: resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== +cookie-signature@1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" + integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ== + +cookie@0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b" + integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw== + +cookiejar@^2.1.1: + version "2.1.4" + resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.1.4.tgz#ee669c1fea2cf42dc31585469d193fef0d65771b" + integrity sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw== + copy-descriptor@^0.1.0: version "0.1.1" resolved "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz" @@ -4859,11 +4928,24 @@ core-js@^2.4.0: resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.12.tgz#d9333dfa7b065e347cc5682219d6f690859cc2ec" integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ== +core-util-is@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" + integrity sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ== + core-util-is@~1.0.0: version "1.0.3" resolved "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz" integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== +cors@^2.8.1: + version "2.8.5" + resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29" + integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g== + dependencies: + object-assign "^4" + vary "^1" + cosmiconfig@^5.0.5, cosmiconfig@^5.1.0: version "5.2.1" resolved "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz" @@ -4874,11 +4956,6 @@ cosmiconfig@^5.0.5, cosmiconfig@^5.1.0: js-yaml "^3.13.1" parse-json "^4.0.0" -crc-32@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/crc-32/-/crc-32-1.2.2.tgz#3cad35a934b8bf71f25ca524b6da51fb7eace2ff" - integrity sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ== - crc@^3.5.0: version "3.8.0" resolved "https://registry.yarnpkg.com/crc/-/crc-3.8.0.tgz#ad60269c2c856f8c299e2c4cc0de4556914056c6" @@ -4886,6 +4963,14 @@ crc@^3.5.0: dependencies: buffer "^5.1.0" +create-ecdh@^4.0.0: + version "4.0.4" + resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.4.tgz#d6e7f4bffa66736085a0762fd3a632684dabcc4e" + integrity sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A== + dependencies: + bn.js "^4.1.0" + elliptic "^6.5.3" + create-hash@^1.1.0, create-hash@^1.1.2, create-hash@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196" @@ -4897,7 +4982,7 @@ create-hash@^1.1.0, create-hash@^1.1.2, create-hash@^1.2.0: ripemd160 "^2.0.1" sha.js "^2.4.0" -create-hmac@^1.1.3, create-hmac@^1.1.4, create-hmac@^1.1.7: +create-hmac@^1.1.0, create-hmac@^1.1.3, create-hmac@^1.1.4, create-hmac@^1.1.7: version "1.1.7" resolved "https://registry.yarnpkg.com/create-hmac/-/create-hmac-1.1.7.tgz#69170c78b3ab957147b2b8b04572e47ead2243ff" integrity sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg== @@ -4941,6 +5026,23 @@ crypt@0.0.2, crypt@~0.0.1: resolved "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz" integrity sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow== +crypto-browserify@3.12.0: + version "3.12.0" + resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec" + integrity sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg== + dependencies: + browserify-cipher "^1.0.0" + browserify-sign "^4.0.0" + create-ecdh "^4.0.0" + create-hash "^1.1.0" + create-hmac "^1.1.0" + diffie-hellman "^5.0.0" + inherits "^2.0.1" + pbkdf2 "^3.0.3" + public-encrypt "^4.0.0" + randombytes "^2.0.0" + randomfill "^1.0.3" + crypto-js@^3.1.9-1: version "3.3.0" resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-3.3.0.tgz#846dd1cce2f68aacfa156c8578f926a609b7976b" @@ -5007,11 +5109,26 @@ csstype@^3.0.2: resolved "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz" integrity sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ== +d@1, d@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/d/-/d-1.0.1.tgz#8698095372d58dbee346ffd0c7093f99f8f9eb5a" + integrity sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA== + dependencies: + es5-ext "^0.10.50" + type "^1.0.1" + dag-map@~1.0.0: version "1.0.2" resolved "https://registry.npmjs.org/dag-map/-/dag-map-1.0.2.tgz" integrity sha512-+LSAiGFwQ9dRnRdOeaj7g47ZFJcOUPukAP8J3A3fuZ1g9Y44BG+P1sgApjLXTQPOzC4+7S9Wr8kXsfpINM4jpw== +dashdash@^1.12.0: + version "1.14.1" + resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" + integrity sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g== + dependencies: + assert-plus "^1.0.0" + data-urls@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-3.0.2.tgz#9cf24a477ae22bcef5cd5f6f0bfbc1d2d3be9143" @@ -5062,6 +5179,20 @@ decode-uri-component@^0.2.0, decode-uri-component@^0.2.2: resolved "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz" integrity sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ== +decompress-response@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-3.3.0.tgz#80a4dd323748384bfa248083622aedec982adff3" + integrity sha512-BzRPQuY1ip+qDonAOz42gRm/pg9F768C+npV/4JOsxRC2sq+Rlk+Q4ZCAsOhnIaMrgarILY+RMUIvMmmX1qAEA== + dependencies: + mimic-response "^1.0.0" + +decompress-response@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc" + integrity sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ== + dependencies: + mimic-response "^3.1.0" + dedent@^0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c" @@ -5126,6 +5257,16 @@ defaults@^1.0.3: dependencies: clone "^1.0.2" +defer-to-connect@^1.0.1: + version "1.1.3" + resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-1.1.3.tgz#331ae050c08dcf789f8c83a7b81f0ed94f4ac591" + integrity sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ== + +defer-to-connect@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-2.0.1.tgz#8016bdb4143e4632b77a3449c6236277de520587" + integrity sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg== + define-lazy-prop@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz" @@ -5218,6 +5359,14 @@ dequal@^1.0.0: resolved "https://registry.yarnpkg.com/dequal/-/dequal-1.0.1.tgz#dbbf9795ec626e9da8bd68782f4add1d23700d8b" integrity sha512-Fx8jxibzkJX2aJgyfSdLhr9tlRoTnHKrRJuu2XHlAgKioN2j19/Bcbe0d4mFXYZ3+wpE2KVobUVTfDutcD17xQ== +des.js@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.1.0.tgz#1d37f5766f3bbff4ee9638e871a8768c173b81da" + integrity sha512-r17GxjhUCjSRy8aiJpr8/UadFIzMzJGexI3Nmz4ADi9LYSFx4gTBp80+NaX/YsXWWLhpZ7v/v/ubEc/bCNfKwg== + dependencies: + inherits "^2.0.1" + minimalistic-assert "^1.0.0" + destroy@1.2.0: version "1.2.0" resolved "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz" @@ -5243,6 +5392,15 @@ diff@^4.0.1: resolved "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz" integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== +diffie-hellman@^5.0.0: + version "5.0.3" + resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875" + integrity sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg== + dependencies: + bn.js "^4.1.0" + miller-rabin "^4.0.0" + randombytes "^2.0.0" + dir-glob@^3.0.1: version "3.0.1" resolved "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz" @@ -5278,6 +5436,11 @@ dom-serializer@^2.0.0: domhandler "^5.0.2" entities "^4.2.0" +dom-walk@^0.1.0: + version "0.1.2" + resolved "https://registry.yarnpkg.com/dom-walk/-/dom-walk-0.1.2.tgz#0c548bef048f4d1f2a97249002236060daa3fd84" + integrity sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w== + domelementtype@^2.3.0: version "2.3.0" resolved "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz" @@ -5315,11 +5478,24 @@ drbg.js@^1.0.1: create-hash "^1.1.2" create-hmac "^1.1.4" +duplexer3@^0.1.4: + version "0.1.5" + resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.5.tgz#0b5e4d7bad5de8901ea4440624c8e1d20099217e" + integrity sha512-1A8za6ws41LQgv9HrE/66jyC5yuSjQ3L/KOpFtoBilsAK2iA2wuS5rTt1OCzIvtS2V7nVmedsUU+DGRcjBmOYA== + eastasianwidth@^0.2.0: version "0.2.0" resolved "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz" integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== +ecc-jsbn@~0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" + integrity sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw== + dependencies: + jsbn "~0.1.0" + safer-buffer "^2.1.0" + ecurve@^1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/ecurve/-/ecurve-1.0.6.tgz#dfdabbb7149f8d8b78816be5a7d5b83fcf6de797" @@ -5338,6 +5514,16 @@ electron-to-chromium@^1.4.284: resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.348.tgz" integrity sha512-gM7TdwuG3amns/1rlgxMbeeyNoBFPa+4Uu0c7FeROWh4qWmvSOnvcslKmWy51ggLKZ2n/F/4i2HJ+PVNxH9uCQ== +elliptic@6.3.3: + version "6.3.3" + resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.3.3.tgz#5482d9646d54bcb89fd7d994fc9e2e9568876e3f" + integrity sha512-cIky9SO2H8W2eU1NOLySnhOYJnuEWCq9ZJeHvHd/lXzEL9vyraIMfilZSn57X3aVX+wkfYmqkch2LvmTzkjFpA== + dependencies: + bn.js "^4.4.0" + brorand "^1.0.1" + hash.js "^1.0.0" + inherits "^2.0.1" + elliptic@6.5.3: version "6.5.3" resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.3.tgz#cb59eb2efdaf73a0bd78ccd7015a62ad6e0f93d6" @@ -5351,7 +5537,7 @@ elliptic@6.5.3: minimalistic-assert "^1.0.0" minimalistic-crypto-utils "^1.0.0" -elliptic@6.5.4, elliptic@^6.4.0, elliptic@^6.5.2, elliptic@^6.5.4: +elliptic@6.5.4, elliptic@^6.4.0, elliptic@^6.5.2, elliptic@^6.5.3, elliptic@^6.5.4: version "6.5.4" resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb" integrity sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ== @@ -5364,6 +5550,11 @@ elliptic@6.5.4, elliptic@^6.4.0, elliptic@^6.5.2, elliptic@^6.5.4: minimalistic-assert "^1.0.1" minimalistic-crypto-utils "^1.0.1" +emitter-component@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/emitter-component/-/emitter-component-1.1.1.tgz#065e2dbed6959bf470679edabeaf7981d1003ab6" + integrity sha512-G+mpdiAySMuB7kesVRLuyvYRqDmshB7ReKEVuyBPkzQlmiDiLrt7hHHIy4Aff552bgknVN7B2/d3lzhGO5dvpQ== + emittery@^0.13.1: version "0.13.1" resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.13.1.tgz#c04b8c3457490e0847ae51fced3af52d338e3dad" @@ -5525,6 +5716,24 @@ es-to-primitive@^1.2.1: is-date-object "^1.0.1" is-symbol "^1.0.2" +es5-ext@^0.10.35, es5-ext@^0.10.50: + version "0.10.62" + resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.62.tgz#5e6adc19a6da524bf3d1e02bbc8960e5eb49a9a5" + integrity sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA== + dependencies: + es6-iterator "^2.0.3" + es6-symbol "^3.1.3" + next-tick "^1.1.0" + +es6-iterator@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7" + integrity sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g== + dependencies: + d "1" + es5-ext "^0.10.35" + es6-symbol "^3.1.1" + es6-object-assign@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/es6-object-assign/-/es6-object-assign-1.1.0.tgz#c2c3582656247c39ea107cb1e6652b6f9f24523c" @@ -5542,6 +5751,14 @@ es6-promisify@^5.0.0: dependencies: es6-promise "^4.0.3" +es6-symbol@^3.1.1, es6-symbol@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.3.tgz#bad5d3c1bcdac28269f4cb331e431c78ac705d18" + integrity sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA== + dependencies: + d "^1.0.1" + ext "^1.1.2" + escalade@^3.1.1: version "3.1.1" resolved "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz" @@ -5833,6 +6050,51 @@ etag@~1.8.1: resolved "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz" integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== +eth-ens-namehash@2.0.8: + version "2.0.8" + resolved "https://registry.yarnpkg.com/eth-ens-namehash/-/eth-ens-namehash-2.0.8.tgz#229ac46eca86d52e0c991e7cb2aef83ff0f68bcf" + integrity sha512-VWEI1+KJfz4Km//dadyvBBoBeSQ0MHTXPvr8UIXiLW6IanxvAV+DmlZAijZwAyggqGUfwQBeHf7tc9wzc1piSw== + dependencies: + idna-uts46-hx "^2.3.1" + js-sha3 "^0.5.7" + +eth-lib@0.2.7: + version "0.2.7" + resolved "https://registry.yarnpkg.com/eth-lib/-/eth-lib-0.2.7.tgz#2f93f17b1e23aec3759cd4a3fe20c1286a3fc1ca" + integrity sha512-VqEBQKH92jNsaE8lG9CTq8M/bc12gdAfb5MY8Ro1hVyXkh7rOtY3m5tRHK3Hus5HqIAAwU2ivcUjTLVwsvf/kw== + dependencies: + bn.js "^4.11.6" + elliptic "^6.4.0" + xhr-request-promise "^0.1.2" + +eth-lib@^0.1.26: + version "0.1.29" + resolved "https://registry.yarnpkg.com/eth-lib/-/eth-lib-0.1.29.tgz#0c11f5060d42da9f931eab6199084734f4dbd1d9" + integrity sha512-bfttrr3/7gG4E02HoWTDUcDDslN003OlOoBxk9virpAZQ1ja/jDgwkWB8QfJF7ojuEowrqy+lzp9VcJG7/k5bQ== + dependencies: + bn.js "^4.11.6" + elliptic "^6.4.0" + nano-json-stream-parser "^0.1.2" + servify "^0.1.12" + ws "^3.0.0" + xhr-request-promise "^0.1.2" + +eth-lib@^0.2.8: + version "0.2.8" + resolved "https://registry.yarnpkg.com/eth-lib/-/eth-lib-0.2.8.tgz#b194058bef4b220ad12ea497431d6cb6aa0623c8" + integrity sha512-ArJ7x1WcWOlSpzdoTBX8vkwlkSQ85CjjifSZtV4co64vWxSV8geWfPI9x4SVYu3DSxnX4yWFVTtGL+j9DUFLNw== + dependencies: + bn.js "^4.11.6" + elliptic "^6.4.0" + xhr-request-promise "^0.1.2" + +ethereum-bloom-filters@^1.0.6: + version "1.0.10" + resolved "https://registry.yarnpkg.com/ethereum-bloom-filters/-/ethereum-bloom-filters-1.0.10.tgz#3ca07f4aed698e75bd134584850260246a5fed8a" + integrity sha512-rxJ5OFN3RwjQxDcFP2Z5+Q9ho4eIdEmSc2ht0fCu8Se9nbXjZ7/031uXoUYJ87KHCOdVeiUuwSnoS7hmYAGVHA== + dependencies: + js-sha3 "^0.8.0" + ethereum-cryptography@^0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz#8d6143cfc3d74bf79bbd8edecdf29e4ae20dd191" @@ -5854,15 +6116,31 @@ ethereum-cryptography@^0.1.3: secp256k1 "^4.0.1" setimmediate "^1.0.5" -ethereum-cryptography@^2.0.0: +ethereumjs-common@^1.3.2, ethereumjs-common@^1.5.0: + version "1.5.2" + resolved "https://registry.yarnpkg.com/ethereumjs-common/-/ethereumjs-common-1.5.2.tgz#2065dbe9214e850f2e955a80e650cb6999066979" + integrity sha512-hTfZjwGX52GS2jcVO6E2sx4YuFnf0Fhp5ylo4pEPhEffNln7vS59Hr5sLnp3/QCazFLluuBZ+FZ6J5HTp0EqCA== + +ethereumjs-tx@^2.1.1: version "2.1.2" - resolved "https://registry.yarnpkg.com/ethereum-cryptography/-/ethereum-cryptography-2.1.2.tgz#18fa7108622e56481157a5cb7c01c0c6a672eb67" - integrity sha512-Z5Ba0T0ImZ8fqXrJbpHcbpAvIswRte2wGNR/KePnu8GbbvgJ47lMxT/ZZPG6i9Jaht4azPDop4HaM00J0J59ug== + resolved "https://registry.yarnpkg.com/ethereumjs-tx/-/ethereumjs-tx-2.1.2.tgz#5dfe7688bf177b45c9a23f86cf9104d47ea35fed" + integrity sha512-zZEK1onCeiORb0wyCXUvg94Ve5It/K6GD1K+26KfFKodiBiS6d9lfCXlUKGBBdQ+bv7Day+JK0tj1K+BeNFRAw== dependencies: - "@noble/curves" "1.1.0" - "@noble/hashes" "1.3.1" - "@scure/bip32" "1.3.1" - "@scure/bip39" "1.2.1" + ethereumjs-common "^1.5.0" + ethereumjs-util "^6.0.0" + +ethereumjs-util@^6.0.0: + version "6.2.1" + resolved "https://registry.yarnpkg.com/ethereumjs-util/-/ethereumjs-util-6.2.1.tgz#fcb4e4dd5ceacb9d2305426ab1a5cd93e3163b69" + integrity sha512-W2Ktez4L01Vexijrm5EB6w7dg4n/TgpoYU4avuT5T3Vmnw/eCRtiBrJfQYS/DCSvDIOLn2k57GcHdeBcgVxAqw== + dependencies: + "@types/bn.js" "^4.11.3" + bn.js "^4.11.0" + create-hash "^1.1.2" + elliptic "^6.5.2" + ethereum-cryptography "^0.1.3" + ethjs-util "0.1.6" + rlp "^2.2.3" ethereumjs-util@^7.1.5: version "7.1.5" @@ -5875,6 +6153,22 @@ ethereumjs-util@^7.1.5: ethereum-cryptography "^0.1.3" rlp "^2.2.4" +ethers@4.0.0-beta.3: + version "4.0.0-beta.3" + resolved "https://registry.yarnpkg.com/ethers/-/ethers-4.0.0-beta.3.tgz#15bef14e57e94ecbeb7f9b39dd0a4bd435bc9066" + integrity sha512-YYPogooSknTwvHg3+Mv71gM/3Wcrx+ZpCzarBj3mqs9njjRkrOo2/eufzhHloOCo3JSoNI4TQJJ6yU5ABm3Uog== + dependencies: + "@types/node" "^10.3.2" + aes-js "3.0.0" + bn.js "^4.4.0" + elliptic "6.3.3" + hash.js "1.1.3" + js-sha3 "0.5.7" + scrypt-js "2.0.3" + setimmediate "1.0.4" + uuid "2.0.1" + xmlhttprequest "1.8.0" + ethjs-provider-http@^0.1.6: version "0.1.6" resolved "https://registry.yarnpkg.com/ethjs-provider-http/-/ethjs-provider-http-0.1.6.tgz#1ec5d9b4be257ef1d56a500b22a741985e889420" @@ -5882,27 +6176,48 @@ ethjs-provider-http@^0.1.6: dependencies: xhr2 "0.1.3" +ethjs-unit@0.1.6: + version "0.1.6" + resolved "https://registry.yarnpkg.com/ethjs-unit/-/ethjs-unit-0.1.6.tgz#c665921e476e87bce2a9d588a6fe0405b2c41699" + integrity sha512-/Sn9Y0oKl0uqQuvgFk/zQgR7aw1g36qX/jzSQ5lSwlO0GigPymk4eGQfeNTD03w1dPOqfz8V77Cy43jH56pagw== + dependencies: + bn.js "4.11.6" + number-to-bn "1.7.0" + +ethjs-util@0.1.6: + version "0.1.6" + resolved "https://registry.yarnpkg.com/ethjs-util/-/ethjs-util-0.1.6.tgz#f308b62f185f9fe6237132fb2a9818866a5cd536" + integrity sha512-CUnVOQq7gSpDHZVVrQW8ExxUETWrnrvXYvYz55wOU8Uj4VCgw56XC2B/fVqQN+f7gmrnRHSLVnFAwsCuNwji8w== + dependencies: + is-hex-prefixed "1.0.0" + strip-hex-prefix "1.0.0" + event-target-shim@^5.0.0, event-target-shim@^5.0.1: version "5.0.1" resolved "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz" integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== -eventemitter3@^3.1.0: +eventemitter3@3.1.2, eventemitter3@^3.1.0: version "3.1.2" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-3.1.2.tgz#2d3d48f9c346698fce83a85d7d664e98535df6e7" integrity sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q== -eventemitter3@^4.0.7: +eventemitter3@^4.0.0, eventemitter3@^4.0.7: version "4.0.7" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== +events@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" + integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== + eventsource@^1.1.1: version "1.1.2" resolved "https://registry.yarnpkg.com/eventsource/-/eventsource-1.1.2.tgz#bc75ae1c60209e7cb1541231980460343eaea7c2" integrity sha512-xAH3zWhgO2/3KIniEKYPr8plNSzlGINOUqYj0m0u7AB81iRw8b/3E73W6AuU+6klLbaSFmZnaETQ2lXPfAydrA== -evp_bytestokey@^1.0.3: +evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz#7fcbdb198dc71959432efe13842684e0525acb02" integrity sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA== @@ -6254,6 +6569,50 @@ expo@~48.0.18: pretty-format "^26.5.2" uuid "^3.4.0" +express@^4.14.0: + version "4.18.2" + resolved "https://registry.yarnpkg.com/express/-/express-4.18.2.tgz#3fabe08296e930c796c19e3c516979386ba9fd59" + integrity sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ== + dependencies: + accepts "~1.3.8" + array-flatten "1.1.1" + body-parser "1.20.1" + content-disposition "0.5.4" + content-type "~1.0.4" + cookie "0.5.0" + cookie-signature "1.0.6" + debug "2.6.9" + depd "2.0.0" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + finalhandler "1.2.0" + fresh "0.5.2" + http-errors "2.0.0" + merge-descriptors "1.0.1" + methods "~1.1.2" + on-finished "2.4.1" + parseurl "~1.3.3" + path-to-regexp "0.1.7" + proxy-addr "~2.0.7" + qs "6.11.0" + range-parser "~1.2.1" + safe-buffer "5.2.1" + send "0.18.0" + serve-static "1.15.0" + setprototypeof "1.2.0" + statuses "2.0.1" + type-is "~1.6.18" + utils-merge "1.0.1" + vary "~1.1.2" + +ext@^1.1.2: + version "1.7.0" + resolved "https://registry.yarnpkg.com/ext/-/ext-1.7.0.tgz#0ea4383c0103d60e70be99e9a7f11027a33c4f5f" + integrity sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw== + dependencies: + type "^2.7.2" + extend-shallow@^2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz" @@ -6269,6 +6628,11 @@ extend-shallow@^3.0.0, extend-shallow@^3.0.2: assign-symbols "^1.0.0" is-extendable "^1.0.1" +extend@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" + integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== + extglob@^2.0.4: version "2.0.4" resolved "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz" @@ -6283,6 +6647,16 @@ extglob@^2.0.4: snapdragon "^0.8.1" to-regex "^3.0.1" +extsprintf@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" + integrity sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g== + +extsprintf@^1.2.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.1.tgz#8d172c064867f235c0c84a596806d279bf4bcc07" + integrity sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA== + eyes@^0.1.8: version "0.1.8" resolved "https://registry.yarnpkg.com/eyes/-/eyes-0.1.8.tgz#62cf120234c683785d902348a800ef3e0cc20bc0" @@ -6324,7 +6698,12 @@ fast-stable-stringify@^1.0.0: resolved "https://registry.yarnpkg.com/fast-stable-stringify/-/fast-stable-stringify-1.0.0.tgz#5c5543462b22aeeefd36d05b34e51c78cb86d313" integrity sha512-wpYMUmFu5f00Sm0cj2pfivpmawLZ0NKdviQ4w9zJeR8JVtOpOxHmLaJuj0vxvGqMJQWyP/COUkF75/57OKyRag== -fast-xml-parser@^4.0.12: +fast-text-encoding@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/fast-text-encoding/-/fast-text-encoding-1.0.6.tgz#0aa25f7f638222e3396d72bf936afcf1d42d6867" + integrity sha512-VhXlQgj9ioXCqGstD37E/HBeqEGV/qOD/kmbVG8h5xKBYvM1L3lR1Zn4555cQ8GkYbJa8aJSipLPndE1k6zK2w== + +fast-xml-parser@^4.0.12: version "4.1.3" resolved "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.1.3.tgz" integrity sha512-LsNDahCiCcJPe8NO7HijcnukHB24tKbfDDA5IILx9dmW3Frb52lhbeX6MPNUSvyGNfav2VTYpJ/OqkRoVLrh2Q== @@ -6435,6 +6814,19 @@ finalhandler@1.1.2: statuses "~1.5.0" unpipe "~1.0.0" +finalhandler@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.2.0.tgz#7d23fe5731b207b4640e4fcd00aec1f9207a7b32" + integrity sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg== + dependencies: + debug "2.6.9" + encodeurl "~1.0.2" + escape-html "~1.0.3" + on-finished "2.4.1" + parseurl "~1.3.3" + statuses "2.0.1" + unpipe "~1.0.0" + find-babel-config@^1.2.0: version "1.2.0" resolved "https://registry.npmjs.org/find-babel-config/-/find-babel-config-1.2.0.tgz" @@ -6539,6 +6931,11 @@ for-own@^1.0.0: dependencies: for-in "^1.0.1" +forever-agent@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" + integrity sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw== + form-data@^3.0.1: version "3.0.1" resolved "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz" @@ -6557,6 +6954,20 @@ form-data@^4.0.0: combined-stream "^1.0.8" mime-types "^2.1.12" +form-data@~2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" + integrity sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.6" + mime-types "^2.1.12" + +forwarded@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" + integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== + fragment-cache@^0.2.1: version "0.2.1" resolved "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz" @@ -6584,6 +6995,15 @@ fs-extra@9.0.0: jsonfile "^6.0.1" universalify "^1.0.0" +fs-extra@^4.0.2: + version "4.0.3" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-4.0.3.tgz#0d852122e5bc5beb453fb028e9c0c9bf36340c94" + integrity sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg== + dependencies: + graceful-fs "^4.1.2" + jsonfile "^4.0.0" + universalify "^0.1.0" + fs-extra@^8.1.0, fs-extra@~8.1.0: version "8.1.0" resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz" @@ -6603,6 +7023,13 @@ fs-extra@^9.0.0, fs-extra@^9.1.0: jsonfile "^6.0.1" universalify "^2.0.0" +fs-minipass@^1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.7.tgz#ccff8570841e7fe4265693da88936c55aed7f7c7" + integrity sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA== + dependencies: + minipass "^2.6.0" + fs-minipass@^2.0.0: version "2.1.0" resolved "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz" @@ -6640,20 +7067,6 @@ functions-have-names@^1.2.2, functions-have-names@^1.2.3: resolved "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz" integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== -generate-function@^2.0.0: - version "2.3.1" - resolved "https://registry.yarnpkg.com/generate-function/-/generate-function-2.3.1.tgz#f069617690c10c868e73b8465746764f97c3479f" - integrity sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ== - dependencies: - is-property "^1.0.2" - -generate-object-property@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/generate-object-property/-/generate-object-property-1.2.0.tgz#9c0e1c40308ce804f4783618b937fa88f99d50d0" - integrity sha512-TuOwZWgJ2VAMEGJvAyPWvpqxSANF0LDpmyHauMjFYzaACvn+QTT/AZomvPCzVBV7yDN3OmwHQ5OvHaeLKre3JQ== - dependencies: - is-property "^1.0.0" - gensync@^1.0.0-beta.2: version "1.0.0-beta.2" resolved "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz" @@ -6683,13 +7096,20 @@ get-port@^3.2.0: resolved "https://registry.npmjs.org/get-port/-/get-port-3.2.0.tgz" integrity sha512-x5UJKlgeUiNT8nyo/AcnwLnZuZNcSjSw0kogRB+Whd1fjjFq4B1hySFxSFWWSn4mIBzg3sRNUDFYc4g5gjPoLg== -get-stream@^4.0.0: +get-stream@^4.0.0, get-stream@^4.1.0: version "4.1.0" resolved "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz" integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w== dependencies: pump "^3.0.0" +get-stream@^5.1.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" + integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== + dependencies: + pump "^3.0.0" + get-stream@^6.0.0, get-stream@^6.0.1: version "6.0.1" resolved "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz" @@ -6713,6 +7133,13 @@ getenv@^1.0.0: resolved "https://registry.npmjs.org/getenv/-/getenv-1.0.0.tgz" integrity sha512-7yetJWqbS9sbn0vIfliPsFgoXMKn/YMF+Wuiog97x+urnSRRRZ7xB+uVkwGKzRgq9CDFfMQnE9ruL5DHv9c6Xg== +getpass@^0.1.1: + version "0.1.7" + resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" + integrity sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng== + dependencies: + assert-plus "^1.0.0" + glob-parent@^5.1.2: version "5.1.2" resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz" @@ -6762,6 +7189,14 @@ glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: once "^1.3.0" path-is-absolute "^1.0.0" +global@~4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/global/-/global-4.4.0.tgz#3e7b105179006a323ed71aafca3e9c57a5cc6406" + integrity sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w== + dependencies: + min-document "^2.19.0" + process "^0.11.10" + globals@^11.1.0: version "11.12.0" resolved "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz" @@ -6800,7 +7235,41 @@ gopd@^1.0.1: dependencies: get-intrinsic "^1.1.3" -graceful-fs@^4.1.11, graceful-fs@^4.1.3, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4, graceful-fs@^4.2.9: +got@9.6.0: + version "9.6.0" + resolved "https://registry.yarnpkg.com/got/-/got-9.6.0.tgz#edf45e7d67f99545705de1f7bbeeeb121765ed85" + integrity sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q== + dependencies: + "@sindresorhus/is" "^0.14.0" + "@szmarczak/http-timer" "^1.1.2" + cacheable-request "^6.0.0" + decompress-response "^3.3.0" + duplexer3 "^0.1.4" + get-stream "^4.1.0" + lowercase-keys "^1.0.1" + mimic-response "^1.0.1" + p-cancelable "^1.0.0" + to-readable-stream "^1.0.0" + url-parse-lax "^3.0.0" + +got@^11.8.5: + version "11.8.6" + resolved "https://registry.yarnpkg.com/got/-/got-11.8.6.tgz#276e827ead8772eddbcfc97170590b841823233a" + integrity sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g== + dependencies: + "@sindresorhus/is" "^4.0.0" + "@szmarczak/http-timer" "^4.0.5" + "@types/cacheable-request" "^6.0.1" + "@types/responselike" "^1.0.0" + cacheable-lookup "^5.0.3" + cacheable-request "^7.0.2" + decompress-response "^6.0.0" + http2-wrapper "^1.0.0-beta.5.2" + lowercase-keys "^2.0.0" + p-cancelable "^2.0.0" + responselike "^2.0.0" + +graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.3, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4, graceful-fs@^4.2.9: version "4.2.11" resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== @@ -6822,6 +7291,19 @@ graphql@15.8.0: resolved "https://registry.npmjs.org/graphql/-/graphql-15.8.0.tgz" integrity sha512-5gghUc24tP9HRznNpV2+FIoq3xKkj5dTQqf4v0CpdPbFVwFkWoxOM+o+2OC9ZSvjEMTjfmG9QT+gcvggTwW1zw== +har-schema@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" + integrity sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q== + +har-validator@~5.1.3: + version "5.1.5" + resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.5.tgz#1f0803b9f8cb20c0fa13822df1ecddb36bde1efd" + integrity sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w== + dependencies: + ajv "^6.12.3" + har-schema "^2.0.0" + has-bigints@^1.0.1, has-bigints@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz" @@ -6916,7 +7398,7 @@ hash.js@1.1.3: inherits "^2.0.3" minimalistic-assert "^1.0.0" -hash.js@1.1.7, hash.js@^1.0.0, hash.js@^1.0.3, hash.js@^1.1.7: +hash.js@^1.0.0, hash.js@^1.0.3, hash.js@^1.1.7: version "1.1.7" resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42" integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA== @@ -6987,6 +7469,11 @@ html-escaper@^2.0.0: resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== +http-cache-semantics@^4.0.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz#abe02fcb2985460bf0323be664436ec3476a6d5a" + integrity sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ== + http-errors@2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz" @@ -6998,6 +7485,11 @@ http-errors@2.0.0: statuses "2.0.1" toidentifier "1.0.1" +http-https@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/http-https/-/http-https-1.0.0.tgz#2f908dd5f1db4068c058cd6e6d4ce392c913389b" + integrity sha512-o0PWwVCSp3O0wS6FvNr6xfBCHgt0m1tvPLFOCc2iFDKTRAXhB7m8klDf7ErowFH8POa6dVdGatKU5I1YYwzUyg== + http-proxy-agent@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz#5129800203520d434f142bc78ff3c170800f2b43" @@ -7007,6 +7499,23 @@ http-proxy-agent@^5.0.0: agent-base "6" debug "4" +http-signature@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" + integrity sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ== + dependencies: + assert-plus "^1.0.0" + jsprim "^1.2.2" + sshpk "^1.7.0" + +http2-wrapper@^1.0.0-beta.5.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/http2-wrapper/-/http2-wrapper-1.0.3.tgz#b8f55e0c1f25d4ebd08b3b0c2c079f9590800b3d" + integrity sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg== + dependencies: + quick-lru "^5.1.1" + resolve-alpn "^1.0.0" + https-proxy-agent@^5.0.0, https-proxy-agent@^5.0.1: version "5.0.1" resolved "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz" @@ -7051,6 +7560,13 @@ iconv-lite@0.6.3, iconv-lite@^0.6.2: dependencies: safer-buffer ">= 2.1.2 < 3.0.0" +idna-uts46-hx@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/idna-uts46-hx/-/idna-uts46-hx-2.3.1.tgz#a1dc5c4df37eee522bf66d969cc980e00e8711f9" + integrity sha512-PWoF9Keq6laYdIRwwCdhTPl60xRqAloYNMQLiyUnG42VjT53oW07BXIRM+NK7eQjzXjAk2gUvX9caRxlnF9TAA== + dependencies: + punycode "2.1.0" + ieee754@^1.1.13, ieee754@^1.2.1: version "1.2.1" resolved "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz" @@ -7157,7 +7673,7 @@ ip@^1.1.5: resolved "https://registry.npmjs.org/ip/-/ip-1.1.8.tgz" integrity sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg== -ipaddr.js@^1.9.0: +ipaddr.js@1.9.1, ipaddr.js@^1.9.0: version "1.9.1" resolved "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz" integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== @@ -7321,6 +7837,11 @@ is-fullwidth-code-point@^4.0.0: resolved "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz" integrity sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ== +is-function@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-function/-/is-function-1.0.2.tgz#4f097f30abf6efadac9833b17ca5dc03f8144e08" + integrity sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ== + is-generator-fn@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" @@ -7347,6 +7868,11 @@ is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3: dependencies: is-extglob "^2.1.1" +is-hex-prefixed@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-hex-prefixed/-/is-hex-prefixed-1.0.0.tgz#7d8d37e6ad77e5d127148913c573e082d777f554" + integrity sha512-WvtOiug1VFrE9v1Cydwm+FnXd3+w9GaeVUss5W4v/SLy3UW00vP+6iNF2SdnfiBoLy4bTqVdkftNGTUeOFVsbA== + is-interactive@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz" @@ -7364,22 +7890,6 @@ is-map@^2.0.1, is-map@^2.0.2: resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.2.tgz#00922db8c9bf73e81b7a335827bc2a43f2b91127" integrity sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg== -is-my-ip-valid@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-my-ip-valid/-/is-my-ip-valid-1.0.1.tgz#f7220d1146257c98672e6fba097a9f3f2d348442" - integrity sha512-jxc8cBcOWbNK2i2aTkCZP6i7wkHF1bqKFrwEHuN5Jtg5BSaZHUZQ/JTOJwoV41YvHnOaRyWWh72T/KvfNz9DJg== - -is-my-json-valid@^2.20.6: - version "2.20.6" - resolved "https://registry.yarnpkg.com/is-my-json-valid/-/is-my-json-valid-2.20.6.tgz#a9d89e56a36493c77bda1440d69ae0dc46a08387" - integrity sha512-1JQwulVNjx8UqkPE/bqDaxtH4PXCe/2VRh/y3p99heOV87HG4Id5/VfDswd+YiAfHcRTfDlWgISycnHuhZq1aw== - dependencies: - generate-function "^2.0.0" - generate-object-property "^1.1.0" - is-my-ip-valid "^1.0.0" - jsonpointer "^5.0.0" - xtend "^4.0.0" - is-nan@^1.2.1: version "1.3.2" resolved "https://registry.yarnpkg.com/is-nan/-/is-nan-1.3.2.tgz#043a54adea31748b55b6cd4e09aadafa69bd9e1d" @@ -7434,11 +7944,6 @@ is-potential-custom-element-name@^1.0.1: resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz#171ed6f19e3ac554394edf78caa05784a45bebb5" integrity sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ== -is-property@^1.0.0, is-property@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-property/-/is-property-1.0.2.tgz#57fe1c4e48474edd65b09911f26b1cd4095dda84" - integrity sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g== - is-regex@^1.1.4: version "1.1.4" resolved "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz" @@ -7504,6 +8009,11 @@ is-typed-array@^1.1.10, is-typed-array@^1.1.3, is-typed-array@^1.1.9: gopd "^1.0.1" has-tostringtag "^1.0.0" +is-typedarray@^1.0.0, is-typedarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" + integrity sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA== + is-unicode-supported@^0.1.0: version "0.1.0" resolved "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz" @@ -7593,10 +8103,10 @@ isomorphic-ws@^4.0.1: resolved "https://registry.yarnpkg.com/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz#55fd4cd6c5e6491e76dc125938dd863f5cd4f2dc" integrity sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w== -isomorphic-ws@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/isomorphic-ws/-/isomorphic-ws-5.0.0.tgz#e5529148912ecb9b451b46ed44d53dae1ce04bbf" - integrity sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw== +isstream@~0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" + integrity sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g== istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: version "3.2.0" @@ -8148,12 +8658,12 @@ js-sdsl@^4.1.4: resolved "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.4.0.tgz" integrity sha512-FfVSdx6pJ41Oa+CF7RDaFmTnCaFhua+SNYQX74riGOpl96x+2jQCqEfQ2bnXu/5DPCqlRuiqyvTJM0Qjz26IVg== -js-sha3@0.5.7: +js-sha3@0.5.7, js-sha3@^0.5.7: version "0.5.7" resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.5.7.tgz#0d4ffd8002d5333aabaf4a23eed2f6374c9f28e7" integrity sha512-GII20kjaPX0zJ8wzkTbNDYMY7msuZcTWk8S5UOh6806Jq/wz1J8/bnr8uGU0DAUmYDjj2Mr4X1cW8v/GLYnR+g== -js-sha3@0.8.0, js-sha3@^0.8.0: +js-sha3@^0.8.0: version "0.8.0" resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.8.0.tgz#b9b7a5da73afad7dedd0f8c463954cbde6818840" integrity sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q== @@ -8186,6 +8696,11 @@ js-yaml@^4.1.0: dependencies: argparse "^2.0.1" +jsbn@~0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" + integrity sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg== + jsc-android@^250231.0.0: version "250231.0.0" resolved "https://registry.npmjs.org/jsc-android/-/jsc-android-250231.0.0.tgz" @@ -8258,6 +8773,16 @@ jsesc@~0.5.0: resolved "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz" integrity sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA== +json-buffer@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898" + integrity sha512-CuUqjv0FUZIdXkHPI8MezCnFCdaTAacej1TZYulLoAg1h/PhwkdXFN4V/gzY4g+fMBCOV2xF+rp7t2XD2ns/NQ== + +json-buffer@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" + integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== + json-parse-better-errors@^1.0.1: version "1.0.2" resolved "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz" @@ -8292,12 +8817,17 @@ json-schema-traverse@^1.0.0: resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== +json-schema@0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.4.0.tgz#f7de4cf6efab838ebaeb3236474cbba5a1930ab5" + integrity sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA== + json-stable-stringify-without-jsonify@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz" integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== -json-stringify-safe@^5.0.1: +json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" integrity sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA== @@ -8340,16 +8870,21 @@ jsonparse@^1.2.0: resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280" integrity sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg== -jsonpointer@^5.0.0: - version "5.0.1" - resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-5.0.1.tgz#2110e0af0900fd37467b5907ecd13a7884a1b559" - integrity sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ== - jsonschema@1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/jsonschema/-/jsonschema-1.2.2.tgz#83ab9c63d65bf4d596f91d81195e78772f6452bc" integrity sha512-iX5OFQ6yx9NgbHCwse51ohhKgLuLL7Z5cNOeZOPIlDUtAMrxlruHLzVZxbltdHE5mEDXN+75oFOwq6Gn0MZwsA== +jsprim@^1.2.2: + version "1.4.2" + resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.2.tgz#712c65533a15c878ba59e9ed5f0e26d5b77c5feb" + integrity sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw== + dependencies: + assert-plus "1.0.0" + extsprintf "1.3.0" + json-schema "0.4.0" + verror "1.10.0" + "jsx-ast-utils@^2.4.1 || ^3.0.0": version "3.3.3" resolved "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.3.tgz" @@ -8367,6 +8902,20 @@ keccak@^3.0.0: node-gyp-build "^4.2.0" readable-stream "^3.6.0" +keyv@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-3.1.0.tgz#ecc228486f69991e49e9476485a5be1e8fc5c4d9" + integrity sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA== + dependencies: + json-buffer "3.0.0" + +keyv@^4.0.0: + version "4.5.3" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.3.tgz#00873d2b046df737963157bd04f294ca818c9c25" + integrity sha512-QCiSav9WaX1PgETJ+SpNnx2PRRapJ/oRSXM4VO5OGYGSjrxbKPVFVhB3l2OCbLCk329N8qyAtsJjSjvVBWzEug== + dependencies: + json-buffer "3.0.1" + kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: version "3.2.2" resolved "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz" @@ -8570,6 +9119,16 @@ loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0: dependencies: js-tokens "^3.0.0 || ^4.0.0" +lowercase-keys@^1.0.0, lowercase-keys@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" + integrity sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA== + +lowercase-keys@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479" + integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA== + lru-cache@^5.1.1: version "5.1.1" resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz" @@ -8677,6 +9236,11 @@ memory-cache@~0.2.0: resolved "https://registry.npmjs.org/memory-cache/-/memory-cache-0.2.0.tgz" integrity sha512-OcjA+jzjOYzKmKS6IQVALHLVz+rNTMPoJvCztFaZxwG14wtAW7VRZjwTQu06vKCYOxh4jVnik7ya0SXTB0W+xA== +merge-descriptors@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" + integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w== + merge-stream@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz" @@ -8692,6 +9256,11 @@ merkle-lib@^2.0.10: resolved "https://registry.yarnpkg.com/merkle-lib/-/merkle-lib-2.0.10.tgz#82b8dbae75e27a7785388b73f9d7725d0f6f3326" integrity sha512-XrNQvUbn1DL5hKNe46Ccs+Tu3/PYOlrcZILuGUhb95oKBPjc/nmIC8D462PQkipVDGKRvwhn+QFg2cCdIvmDJA== +methods@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" + integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== + metro-babel-transformer@0.73.9: version "0.73.9" resolved "https://registry.npmjs.org/metro-babel-transformer/-/metro-babel-transformer-0.73.9.tgz" @@ -8996,12 +9565,20 @@ micromatch@^4.0.2, micromatch@^4.0.4, micromatch@^4.0.5: braces "^3.0.2" picomatch "^2.3.1" +miller-rabin@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d" + integrity sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA== + dependencies: + bn.js "^4.0.0" + brorand "^1.0.1" + mime-db@1.52.0, "mime-db@>= 1.43.0 < 2": version "1.52.0" resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz" integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== -mime-types@^2.1.12, mime-types@^2.1.27, mime-types@~2.1.24, mime-types@~2.1.34: +mime-types@^2.1.12, mime-types@^2.1.16, mime-types@^2.1.27, mime-types@~2.1.19, mime-types@~2.1.24, mime-types@~2.1.34: version "2.1.35" resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz" integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== @@ -9033,6 +9610,23 @@ mimic-fn@^4.0.0: resolved "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz" integrity sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw== +mimic-response@^1.0.0, mimic-response@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" + integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== + +mimic-response@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9" + integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ== + +min-document@^2.19.0: + version "2.19.0" + resolved "https://registry.yarnpkg.com/min-document/-/min-document-2.19.0.tgz#7bd282e3f5842ed295bb748cdd9f1ffa2c824685" + integrity sha512-9Wy1B3m3f66bPPmU5hdA4DR4PB2OfDU/+GS3yAB7IQozE3tqXaVv2zOjgla7MEGSRv95+ILmOuvhLkOK6wJtCQ== + dependencies: + dom-walk "^0.1.0" + min-indent@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" @@ -9088,6 +9682,14 @@ minipass@3.1.6: dependencies: yallist "^4.0.0" +minipass@^2.6.0, minipass@^2.9.0: + version "2.9.0" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.9.0.tgz#e713762e7d3e32fed803115cf93e04bca9fcc9a6" + integrity sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg== + dependencies: + safe-buffer "^5.1.2" + yallist "^3.0.0" + minipass@^3.0.0, minipass@^3.1.1: version "3.3.6" resolved "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz" @@ -9100,6 +9702,13 @@ minipass@^4.0.0: resolved "https://registry.npmjs.org/minipass/-/minipass-4.2.5.tgz" integrity sha512-+yQl7SX3bIT83Lhb4BVorMAHVuqsskxRdlmO9kTpyukp8vsm2Sn/fUOV9xlnG8/a5JsypJzap21lz/y3FBMJ8Q== +minizlib@^1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.3.3.tgz#2290de96818a34c29551c8a8d301216bd65a861d" + integrity sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q== + dependencies: + minipass "^2.9.0" + minizlib@^2.1.1: version "2.1.2" resolved "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz" @@ -9124,7 +9733,19 @@ mixin-object@^2.0.1: for-in "^0.1.3" is-extendable "^0.1.1" -mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@~0.5.1: +mkdirp-promise@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/mkdirp-promise/-/mkdirp-promise-5.0.1.tgz#e9b8f68e552c68a9c1713b84883f7a1dd039b8a1" + integrity sha512-Hepn5kb1lJPtVW84RFT40YG1OddBNTOVUZR2bzQUHc+Z03en8/3uX0+060JDhcEzyO08HmipsN9DcnFMxhIL9w== + dependencies: + mkdirp "*" + +mkdirp@*: + version "3.0.1" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-3.0.1.tgz#e44e4c5607fb279c168241713cc6e0fea9adcb50" + integrity sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg== + +mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@^0.5.5, mkdirp@~0.5.1: version "0.5.6" resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz" integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw== @@ -9136,6 +9757,11 @@ mkdirp@^1.0.3, mkdirp@^1.0.4: resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== +mock-fs@^4.1.0: + version "4.14.0" + resolved "https://registry.yarnpkg.com/mock-fs/-/mock-fs-4.14.0.tgz#ce5124d2c601421255985e6e94da80a7357b1b18" + integrity sha512-qYvlv/exQ4+svI3UOvPUpLDF0OMX5euvUH0Ny4N5QyRyhNdgAgUrVH3iUINSzEPLvx0kbo/Bp28GJKIqvE7URw== + moment@>=2.0.0, moment@^2.29.4: version "2.29.4" resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.4.tgz#3dbe052889fe7c1b2ed966fcb3a77328964ef108" @@ -9179,6 +9805,11 @@ nan@^2.13.2, nan@^2.14.0: resolved "https://registry.yarnpkg.com/nan/-/nan-2.17.0.tgz#c0150a2368a182f033e9aa5195ec76ea41a199cb" integrity sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ== +nano-json-stream-parser@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/nano-json-stream-parser/-/nano-json-stream-parser-0.1.2.tgz#0cc8f6d0e2b622b479c40d499c46d64b755c6f5f" + integrity sha512-9MqxMH/BSJC7dnLsEMPyfN5Dvoo49IsPFYMcHw3Bcfc2kN0lpHRBSzlMSVx4HGyJ7s9B31CyBTVehWJoQ8Ctew== + nanoid@^3.1.23: version "3.3.6" resolved "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz" @@ -9231,6 +9862,11 @@ nested-error-stacks@~2.0.1: resolved "https://registry.npmjs.org/nested-error-stacks/-/nested-error-stacks-2.0.1.tgz" integrity sha512-SrQrok4CATudVzBS7coSz26QRSmlK9TzzoFbeKfcPBUFPjcQM9Rqvr/DlJkOrwI/0KcgvMub1n1g5Jt9EgRn4A== +next-tick@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.1.0.tgz#1836ee30ad56d67ef281b22bd199f709449b35eb" + integrity sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ== + nice-try@^1.0.4: version "1.0.5" resolved "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz" @@ -9322,6 +9958,16 @@ normalize-path@^3.0.0: resolved "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== +normalize-url@^4.1.0: + version "4.5.1" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.1.tgz#0dd90cf1288ee1d1313b87081c9a5932ee48518a" + integrity sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA== + +normalize-url@^6.0.1: + version "6.1.0" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a" + integrity sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A== + npm-package-arg@^7.0.0: version "7.0.0" resolved "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-7.0.0.tgz" @@ -9365,17 +10011,30 @@ nullthrows@^1.1.1: resolved "https://registry.npmjs.org/nullthrows/-/nullthrows-1.1.1.tgz" integrity sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw== +number-to-bn@1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/number-to-bn/-/number-to-bn-1.7.0.tgz#bb3623592f7e5f9e0030b1977bd41a0c53fe1ea0" + integrity sha512-wsJ9gfSz1/s4ZsJN01lyonwuxA1tml6X1yBDnfpMglypcBRFZZkus26EdPSlqS5GJfYddVZa22p3VNb3z5m5Ig== + dependencies: + bn.js "4.11.6" + strip-hex-prefix "1.0.0" + nwsapi@^2.2.2: version "2.2.4" resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.4.tgz#fd59d5e904e8e1f03c25a7d5a15cfa16c714a1e5" integrity sha512-NHj4rzRo0tQdijE9ZqAx6kYDcoRwYwSYzCA8MY3JzfxlrvEU0jhnhJT9BhqhJs7I/dKcrDm6TyulaRqZPIhN5g== +oauth-sign@~0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" + integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== + ob1@0.73.9: version "0.73.9" resolved "https://registry.npmjs.org/ob1/-/ob1-0.73.9.tgz" integrity sha512-kHOzCOFXmAM26fy7V/YuXNKne2TyRiXbFAvPBIbuedJCZZWQZHLdPzMeXJI4Egt6IcfDttRzN3jQ90wOwq1iNw== -object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: +object-assign@^4, object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz" integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== @@ -9466,6 +10125,13 @@ object.values@^1.1.6: define-properties "^1.1.4" es-abstract "^1.20.4" +oboe@2.1.4: + version "2.1.4" + resolved "https://registry.yarnpkg.com/oboe/-/oboe-2.1.4.tgz#20c88cdb0c15371bb04119257d4fdd34b0aa49f6" + integrity sha512-ymBJ4xSC6GBXLT9Y7lirj+xbqBLa+jADGJldGEYG7u8sZbS9GyG+u1Xk9c5cbriKwSpCg41qUhPjvU5xOpvIyQ== + dependencies: + http-https "^1.0.0" + on-finished@2.4.1: version "2.4.1" resolved "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz" @@ -9611,6 +10277,16 @@ osenv@^0.1.5: os-homedir "^1.0.0" os-tmpdir "^1.0.0" +p-cancelable@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-1.1.0.tgz#d078d15a3af409220c886f1d9a0ca2e441ab26cc" + integrity sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw== + +p-cancelable@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-2.1.1.tgz#aab7fbd416582fa32a3db49859c122487c5ed2cf" + integrity sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg== + p-finally@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz" @@ -9670,6 +10346,22 @@ parent-module@^1.0.0: dependencies: callsites "^3.0.0" +parse-asn1@^5.0.0, parse-asn1@^5.1.5: + version "5.1.6" + resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.6.tgz#385080a3ec13cb62a62d39409cb3e88844cdaed4" + integrity sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw== + dependencies: + asn1.js "^5.2.0" + browserify-aes "^1.0.0" + evp_bytestokey "^1.0.0" + pbkdf2 "^3.0.3" + safe-buffer "^5.1.1" + +parse-headers@^2.0.0: + version "2.0.5" + resolved "https://registry.yarnpkg.com/parse-headers/-/parse-headers-2.0.5.tgz#069793f9356a54008571eb7f9761153e6c770da9" + integrity sha512-ft3iAoLOB/MlwbNXgzy43SWGP6sQki2jQvAyBg/zDFAgr9bfNWZIUj42Kw2eJIl8kEi4PbgE6U1Zau/HwI75HA== + parse-json@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz" @@ -9780,12 +10472,17 @@ path-parse@^1.0.5, path-parse@^1.0.7: resolved "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== +path-to-regexp@0.1.7: + version "0.1.7" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" + integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ== + path-type@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== -pbkdf2@^3.0.17: +pbkdf2@^3.0.17, pbkdf2@^3.0.3: version "3.1.2" resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.1.2.tgz#dd822aa0887580e52f1a039dc3eda108efae3075" integrity sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA== @@ -9796,6 +10493,11 @@ pbkdf2@^3.0.17: safe-buffer "^5.0.1" sha.js "^2.4.8" +performance-now@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" + integrity sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow== + picocolors@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz" @@ -9892,6 +10594,11 @@ prelude-ls@~1.1.2: resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" integrity sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w== +prepend-http@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897" + integrity sha512-ravE6m9Atw9Z/jjttRUZ+clIXogdghyZAuWJ3qEzjT+jI/dL1ifAqhZeC5VHzQp1MSt1+jxKkFNemj/iO7tVUA== + prettier-linter-helpers@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz" @@ -9995,16 +10702,36 @@ protocol-buffers-encodings@^1.2.0: signed-varint "^2.0.1" varint "5.0.0" +proxy-addr@~2.0.7: + version "2.0.7" + resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" + integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== + dependencies: + forwarded "0.2.0" + ipaddr.js "1.9.1" + proxy-from-env@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz" integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== -psl@^1.1.33: +psl@^1.1.28, psl@^1.1.33: version "1.9.0" resolved "https://registry.yarnpkg.com/psl/-/psl-1.9.0.tgz#d0df2a137f00794565fcaf3b2c00cd09f8d5a5a7" integrity sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag== +public-encrypt@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.3.tgz#4fcc9d77a07e48ba7527e7cbe0de33d0701331e0" + integrity sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q== + dependencies: + bn.js "^4.1.0" + browserify-rsa "^4.0.0" + create-hash "^1.1.0" + parse-asn1 "^5.0.0" + randombytes "^2.0.1" + safe-buffer "^5.1.2" + pump@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz" @@ -10013,6 +10740,11 @@ pump@^3.0.0: end-of-stream "^1.1.0" once "^1.3.1" +punycode@2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.0.tgz#5f863edc89b96db09074bad7947bf09056ca4e7d" + integrity sha512-Yxz2kRwT90aPiWEMHVYnEf4+rhwF1tBmmZ4KepCP+Wkium9JxtWnUm1nqGwpiAHr/tnTSeHqr3wb++jgSkXjhA== + punycode@^2.1.0, punycode@^2.1.1: version "2.3.0" resolved "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz" @@ -10042,6 +10774,20 @@ qs@6.11.0: dependencies: side-channel "^1.0.4" +qs@~6.5.2: + version "6.5.3" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.3.tgz#3aeeffc91967ef6e35c0e488ef46fb296ab76aad" + integrity sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA== + +query-string@^5.0.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/query-string/-/query-string-5.1.1.tgz#a78c012b71c17e05f2e3fa2319dd330682efb3cb" + integrity sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw== + dependencies: + decode-uri-component "^0.2.0" + object-assign "^4.1.0" + strict-uri-encode "^1.0.0" + query-string@^7.1.3: version "7.1.3" resolved "https://registry.npmjs.org/query-string/-/query-string-7.1.3.tgz" @@ -10062,18 +10808,41 @@ queue-microtask@^1.2.2: resolved "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz" integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== -randombytes@^2.0.1, randombytes@^2.1.0: +quick-lru@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932" + integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA== + +randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5, randombytes@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== dependencies: safe-buffer "^5.1.0" +randomfill@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/randomfill/-/randomfill-1.0.4.tgz#c92196fc86ab42be983f1bf31778224931d61458" + integrity sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw== + dependencies: + randombytes "^2.0.5" + safe-buffer "^5.1.0" + range-parser@~1.2.1: version "1.2.1" resolved "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz" integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== +raw-body@2.5.1: + version "2.5.1" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.1.tgz#fe1b1628b181b700215e5fd42389f98b71392857" + integrity sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig== + dependencies: + bytes "3.1.2" + http-errors "2.0.0" + iconv-lite "0.4.24" + unpipe "1.0.0" + raw-body@2.5.2: version "2.5.2" resolved "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz" @@ -10488,6 +11257,32 @@ repeat-string@^1.6.1: resolved "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz" integrity sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w== +request@^2.79.0: + version "2.88.2" + resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" + integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== + dependencies: + aws-sign2 "~0.7.0" + aws4 "^1.8.0" + caseless "~0.12.0" + combined-stream "~1.0.6" + extend "~3.0.2" + forever-agent "~0.6.1" + form-data "~2.3.2" + har-validator "~5.1.3" + http-signature "~1.2.0" + is-typedarray "~1.0.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.19" + oauth-sign "~0.9.0" + performance-now "^2.1.0" + qs "~6.5.2" + safe-buffer "^5.1.2" + tough-cookie "~2.5.0" + tunnel-agent "^0.6.0" + uuid "^3.3.2" + require-directory@^2.1.1: version "2.1.1" resolved "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz" @@ -10522,6 +11317,11 @@ reselect@^4.0.0: resolved "https://registry.npmjs.org/reselect/-/reselect-4.1.7.tgz" integrity sha512-Zu1xbUt3/OPwsXL46hvOOoQrap2azE7ZQbokq61BQfiXvhewsKDwhMeZjTX9sX0nvw1t/U5Audyn1I9P/m9z0A== +resolve-alpn@^1.0.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/resolve-alpn/-/resolve-alpn-1.2.1.tgz#b7adbdac3546aaaec20b45e7d8265927072726f9" + integrity sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g== + resolve-cwd@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" @@ -10588,6 +11388,20 @@ resolve@~1.7.1: dependencies: path-parse "^1.0.5" +responselike@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7" + integrity sha512-/Fpe5guzJk1gPqdJLJR5u7eG/gNY4nImjbRDaVWVMRhne55TCmj2i9Q+54PBRfatRC8v/rIiv9BN0pMd9OV5EQ== + dependencies: + lowercase-keys "^1.0.0" + +responselike@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/responselike/-/responselike-2.0.1.tgz#9a0bc8fdc252f3fb1cca68b016591059ba1422bc" + integrity sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw== + dependencies: + lowercase-keys "^2.0.0" + restore-cursor@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz" @@ -10716,7 +11530,7 @@ ripple-lib@^1.10.1: ripple-lib-transactionparser "0.8.2" ws "^7.2.0" -rlp@^2.0.0, rlp@^2.2.4: +rlp@^2.0.0, rlp@^2.2.3, rlp@^2.2.4: version "2.2.7" resolved "https://registry.yarnpkg.com/rlp/-/rlp-2.2.7.tgz#33f31c4afac81124ac4b283e2bd4d9720b30beaf" integrity sha512-d5gdPmgQ0Z+AklL2NVXr/IoSjNZFfTVvQWzL/AM2AOcSzYP2xjlb0AC8YyCLc41MSNf6P6QVtjgPdmVtzb+4lQ== @@ -10755,7 +11569,7 @@ safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== -safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@~5.2.0: +safe-buffer@5.2.1, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@^5.2.1, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== @@ -10781,7 +11595,7 @@ safe-regex@^1.1.0: dependencies: ret "~0.1.10" -"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0": +"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: version "2.1.2" resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== @@ -10812,6 +11626,11 @@ scheduler@^0.23.0: dependencies: loose-envify "^1.1.0" +scrypt-js@2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/scrypt-js/-/scrypt-js-2.0.3.tgz#bb0040be03043da9a012a2cea9fc9f852cfc87d4" + integrity sha512-d8DzQxNivoNDogyYmb/9RD5mEQE/Q7vG2dLDUgvfPmKL9xCVzgqUntOdS0me9Cq9Sh9VxIZuoNEFcsfyXRnyUw== + scrypt-js@2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/scrypt-js/-/scrypt-js-2.0.4.tgz#32f8c5149f0797672e551c07e230f834b6af5f16" @@ -10822,6 +11641,11 @@ scrypt-js@^3.0.0: resolved "https://registry.yarnpkg.com/scrypt-js/-/scrypt-js-3.0.1.tgz#d314a57c2aef69d1ad98a138a21fe9eafa9ee312" integrity sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA== +scryptsy@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/scryptsy/-/scryptsy-2.1.0.tgz#8d1e8d0c025b58fdd25b6fa9a0dc905ee8faa790" + integrity sha512-1CdSqHQowJBnMAFyPEBRfqag/YP9OF394FV+4YREIJX4ljD7OxvQRDayyoyyCk+senRjSkP6VnUNQmVQqB6g7w== + secp256k1@^3.0.1: version "3.8.0" resolved "https://registry.yarnpkg.com/secp256k1/-/secp256k1-3.8.0.tgz#28f59f4b01dbee9575f56a47034b7d2e3b3b352d" @@ -10898,7 +11722,7 @@ serialize-error@^2.1.0: resolved "https://registry.npmjs.org/serialize-error/-/serialize-error-2.1.0.tgz" integrity sha512-ghgmKt5o4Tly5yEG/UJp8qTd0AN7Xalw4XBtDEKP655B699qMEtra1WlXeE6WIvdEG481JvRxULKsInq/iNysw== -serve-static@^1.13.1: +serve-static@1.15.0, serve-static@^1.13.1: version "1.15.0" resolved "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz" integrity sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g== @@ -10908,6 +11732,17 @@ serve-static@^1.13.1: parseurl "~1.3.3" send "0.18.0" +servify@^0.1.12: + version "0.1.12" + resolved "https://registry.yarnpkg.com/servify/-/servify-0.1.12.tgz#142ab7bee1f1d033b66d0707086085b17c06db95" + integrity sha512-/xE6GvsKKqyo1BAY+KxOWXcLpPsUUyji7Qg3bVD7hh1eRze5bR1uYiuDA/k3Gof1s9BTzQZEJK8sNcNGFIzeWw== + dependencies: + body-parser "^1.16.0" + cors "^2.8.1" + express "^4.14.0" + request "^2.79.0" + xhr "^2.3.3" + set-blocking@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz" @@ -11012,6 +11847,20 @@ signed-varint@^2.0.1: dependencies: varint "~5.0.0" +simple-concat@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f" + integrity sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q== + +simple-get@^2.7.0: + version "2.8.2" + resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-2.8.2.tgz#5708fb0919d440657326cd5fe7d2599d07705019" + integrity sha512-Ijd/rV5o+mSBBs4F/x9oDPtTx9Zb6X9brmnXvMW4J7IR15ngi9q5xxqWBKU744jTZiaXtxaPL7uHG6vtN8kUkw== + dependencies: + decompress-response "^3.3.0" + once "^1.3.1" + simple-concat "^1.0.0" + simple-plist@^1.1.0: version "1.3.1" resolved "https://registry.npmjs.org/simple-plist/-/simple-plist-1.3.1.tgz" @@ -11206,6 +12055,21 @@ sql-escape-string@^1.1.0: resolved "https://registry.yarnpkg.com/sql-escape-string/-/sql-escape-string-1.1.0.tgz#fe744b8514868c0eb4bfb9e4a989271d40f30eb9" integrity sha512-/kqO4pLZSLfV0KsBM2xkVh2S3GbjJJone37d7gYwLyP0c+REh3vnmkhQ7VwNrX76igC0OhJWpTg0ukkdef9vvA== +sshpk@^1.7.0: + version "1.17.0" + resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.17.0.tgz#578082d92d4fe612b13007496e543fa0fbcbe4c5" + integrity sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ== + dependencies: + asn1 "~0.2.3" + assert-plus "^1.0.0" + bcrypt-pbkdf "^1.0.0" + dashdash "^1.12.0" + ecc-jsbn "~0.1.1" + getpass "^0.1.1" + jsbn "~0.1.0" + safer-buffer "^2.0.2" + tweetnacl "~0.14.0" + ssri@^8.0.1: version "8.0.1" resolved "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz" @@ -11323,6 +12187,18 @@ stream-buffers@2.2.x: resolved "https://registry.npmjs.org/stream-buffers/-/stream-buffers-2.2.0.tgz" integrity sha512-uyQK/mx5QjHun80FLJTfaWE7JtwfRMKBLkMne6udYOmvH0CawotVa7TfgYHzAnpphn4+TweIx1QKMnRIbipmUg== +stream@^0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/stream/-/stream-0.0.2.tgz#7f5363f057f6592c5595f00bc80a27f5cec1f0ef" + integrity sha512-gCq3NDI2P35B2n6t76YJuOp7d6cN/C7Rt0577l91wllh0sY9ZBuw9KaSGqH/b0hzn3CWWJbpbW0W0WvQ1H/Q7g== + dependencies: + emitter-component "^1.1.1" + +strict-uri-encode@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713" + integrity sha512-R3f198pcvnB+5IpnBlRkphuE9n46WyVl8I39W/ZUTZLz4nqSP/oLYUrcnJrw462Ds8he4YKMov2efsTIw1BDGQ== + strict-uri-encode@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz" @@ -11473,6 +12349,13 @@ strip-final-newline@^3.0.0: resolved "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz" integrity sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw== +strip-hex-prefix@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/strip-hex-prefix/-/strip-hex-prefix-1.0.0.tgz#0c5f155fef1151373377de9dbb588da05500e36f" + integrity sha512-q8d4ue7JGEiVcypji1bALTos+0pWtyGlivAWyPuTkHzuTCJqrK9sWxYQZUq6Nq3cuyv3bm734IhHvHtGGURU6A== + dependencies: + is-hex-prefixed "1.0.0" + strip-indent@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-3.0.0.tgz#c32e1cee940b6b3432c771bc2c54bcce73cd3001" @@ -11574,11 +12457,41 @@ supports-preserve-symlinks-flag@^1.0.0: resolved "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== +swarm-js@^0.1.40: + version "0.1.42" + resolved "https://registry.yarnpkg.com/swarm-js/-/swarm-js-0.1.42.tgz#497995c62df6696f6e22372f457120e43e727979" + integrity sha512-BV7c/dVlA3R6ya1lMlSSNPLYrntt0LUq4YMgy3iwpCIc6rZnS5W2wUoctarZ5pXlpKtxDDf9hNziEkcfrxdhqQ== + dependencies: + bluebird "^3.5.0" + buffer "^5.0.5" + eth-lib "^0.1.26" + fs-extra "^4.0.2" + got "^11.8.5" + mime-types "^2.1.16" + mkdirp-promise "^5.0.1" + mock-fs "^4.1.0" + setimmediate "^1.0.5" + tar "^4.0.2" + xhr-request "^1.0.1" + symbol-tree@^3.2.4: version "3.2.4" resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== +tar@^4.0.2: + version "4.4.19" + resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.19.tgz#2e4d7263df26f2b914dee10c825ab132123742f3" + integrity sha512-a20gEsvHnWe0ygBY8JbxoM4w3SJdhc7ZAuxkLqh+nvNQN2IOt0B5lLgM490X5Hl8FF0dl0tOf2ewFYAlIFgzVA== + dependencies: + chownr "^1.1.4" + fs-minipass "^1.2.7" + minipass "^2.9.0" + minizlib "^1.3.3" + mkdirp "^0.5.5" + safe-buffer "^5.2.1" + yallist "^3.1.1" + tar@^6.0.2, tar@^6.0.5: version "6.1.13" resolved "https://registry.npmjs.org/tar/-/tar-6.1.13.tgz" @@ -11735,6 +12648,11 @@ through@2, "through@>=2.2.7 <3", through@^2.3.8: resolved "https://registry.npmjs.org/through/-/through-2.3.8.tgz" integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== +timed-out@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f" + integrity sha512-G7r3AhovYtr5YKOWQkta8RKAPb+J9IsO4uVmzjl8AZwfhs8UcUwTiD6gcJYSgOtzyjvQKrKYn41syHbUWMkafA== + tiny-secp256k1@^1.1.1, tiny-secp256k1@^1.1.3: version "1.1.6" resolved "https://registry.yarnpkg.com/tiny-secp256k1/-/tiny-secp256k1-1.1.6.tgz#7e224d2bee8ab8283f284e40e6b4acb74ffe047c" @@ -11777,6 +12695,11 @@ to-object-path@^0.3.0: dependencies: kind-of "^3.0.2" +to-readable-stream@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/to-readable-stream/-/to-readable-stream-1.0.0.tgz#ce0aa0c2f3df6adf852efb404a783e77c0475771" + integrity sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q== + to-regex-range@^2.1.0: version "2.1.1" resolved "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz" @@ -11822,6 +12745,14 @@ tough-cookie@^4.1.2: universalify "^0.2.0" url-parse "^1.5.3" +tough-cookie@~2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" + integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g== + dependencies: + psl "^1.1.28" + punycode "^2.1.1" + tr46@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/tr46/-/tr46-3.0.0.tgz#555c4e297a950617e8eeddef633c87d4d9d6cbf9" @@ -11907,6 +12838,18 @@ tsutils@^3.21.0: dependencies: tslib "^1.8.1" +tunnel-agent@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" + integrity sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w== + dependencies: + safe-buffer "^5.0.1" + +tweetnacl@^0.14.3, tweetnacl@~0.14.0: + version "0.14.5" + resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" + integrity sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA== + tweetnacl@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-1.0.3.tgz#ac0af71680458d8a6378d0d0d050ab1407d35596" @@ -11974,6 +12917,16 @@ type-is@~1.6.18: media-typer "0.3.0" mime-types "~2.1.24" +type@^1.0.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/type/-/type-1.2.0.tgz#848dd7698dafa3e54a6c479e759c4bc3f18847a0" + integrity sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg== + +type@^2.7.2: + version "2.7.2" + resolved "https://registry.yarnpkg.com/type/-/type-2.7.2.tgz#2376a15a3a28b1efa0f5350dcf72d24df6ef98d0" + integrity sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw== + typecast@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/typecast/-/typecast-0.0.1.tgz#fffb75dcb6bdf1def8e293b6b6e893d6c1ed19de" @@ -11988,6 +12941,13 @@ typed-array-length@^1.0.4: for-each "^0.3.3" is-typed-array "^1.1.9" +typedarray-to-buffer@^3.1.5: + version "3.1.5" + resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" + integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q== + dependencies: + is-typedarray "^1.0.0" + typeforce@^1.11.3, typeforce@^1.11.5: version "1.18.0" resolved "https://registry.yarnpkg.com/typeforce/-/typeforce-1.18.0.tgz#d7416a2c5845e085034d70fcc5b6cc4a90edbfdc" @@ -12016,6 +12976,11 @@ uint8array-tools@0.0.7: resolved "https://registry.yarnpkg.com/uint8array-tools/-/uint8array-tools-0.0.7.tgz#a7a2bb5d8836eae2fade68c771454e6a438b390d" integrity sha512-vrrNZJiusLWoFWBqz5Y5KMCgP9W9hnjZHzZiZRT8oNAkq3d5Z5Oe76jAvVVSRh4U8GGR90N2X1dWtrhvx6L8UQ== +ultron@~1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.1.1.tgz#9fe1536a10a664a65266a1e3ccf85fd36302bc9c" + integrity sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og== + unbox-primitive@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz" @@ -12026,6 +12991,11 @@ unbox-primitive@^1.0.2: has-symbols "^1.0.3" which-boxed-primitive "^1.0.2" +underscore@1.9.1: + version "1.9.1" + resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.9.1.tgz#06dce34a0e68a7babc29b365b8e74b8925203961" + integrity sha512-5/4etnCkd9c8gwgowi5/om/mYO5ajCaOgdzj/oW+0eQV9WxKBDZw5+ycmKmeaTXjInS/W0BzpGLo2xR2aBwZdg== + unicode-canonical-property-names-ecmascript@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz" @@ -12150,6 +13120,13 @@ url-join@4.0.0: resolved "https://registry.npmjs.org/url-join/-/url-join-4.0.0.tgz" integrity sha512-EGXjXJZhIHiQMK2pQukuFcL303nskqIRzWvPvV5O8miOfwoUb9G+a/Cld60kUyeaybEI94wvVClT10DtfeAExA== +url-parse-lax@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-3.0.0.tgz#16b5cafc07dbe3676c1b1999177823d6503acb0c" + integrity sha512-NjFKA0DidqPa5ciFcSrXnAltTtzz84ogy+NebPvfEgAck0+TNg4UJ4IN+fB7zRZfbgUf0syOo9MDxFkDSMuFaQ== + dependencies: + prepend-http "^2.0.0" + url-parse@^1.5.3, url-parse@^1.5.9: version "1.5.10" resolved "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz" @@ -12158,6 +13135,11 @@ url-parse@^1.5.3, url-parse@^1.5.9: querystringify "^2.1.1" requires-port "^1.0.0" +url-set-query@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/url-set-query/-/url-set-query-1.0.0.tgz#016e8cfd7c20ee05cafe7795e892bd0702faa339" + integrity sha512-3AChu4NiXquPfeckE5R5cGdiHCMWJx1dwCWOmWIL4KHAziJNOFIYJlpGFeKDvwLPHovZRCxK3cYlwzqI9Vp+Gg== + use-context-selector@^1.4.1: version "1.4.1" resolved "https://registry.npmjs.org/use-context-selector/-/use-context-selector-1.4.1.tgz" @@ -12185,12 +13167,17 @@ utf-8-validate@^5.0.2: dependencies: node-gyp-build "^4.3.0" +utf8@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/utf8/-/utf8-3.0.0.tgz#f052eed1364d696e769ef058b183df88c87f69d1" + integrity sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ== + util-deprecate@^1.0.1, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== -util@^0.12.0, util@^0.12.5: +util@^0.12.0: version "0.12.5" resolved "https://registry.yarnpkg.com/util/-/util-0.12.5.tgz#5f17a6059b73db61a875668781a1c2b136bd6fbc" integrity sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA== @@ -12216,6 +13203,11 @@ uuid@2.0.1: resolved "https://registry.yarnpkg.com/uuid/-/uuid-2.0.1.tgz#c2a30dedb3e535d72ccf82e343941a50ba8533ac" integrity sha512-nWg9+Oa3qD2CQzHIP4qKUqwNfzKn8P0LtFhotaCTFchsV7ZfDhAybeip/HZVeMIpZi9JgY1E3nUlwaCmZT1sEg== +uuid@3.3.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131" + integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA== + uuid@^3.3.2, uuid@^3.4.0: version "3.4.0" resolved "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz" @@ -12278,11 +13270,20 @@ varuint-bitcoin@^1.0.4: dependencies: safe-buffer "^5.1.1" -vary@~1.1.2: +vary@^1, vary@~1.1.2: version "1.1.2" resolved "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz" integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== +verror@1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" + integrity sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw== + dependencies: + assert-plus "^1.0.0" + core-util-is "1.0.2" + extsprintf "^1.2.0" + vlq@^1.0.0: version "1.0.1" resolved "https://registry.npmjs.org/vlq/-/vlq-1.0.1.tgz" @@ -12314,216 +13315,242 @@ wcwidth@^1.0.1: dependencies: defaults "^1.0.3" -web3-core@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/web3-core/-/web3-core-4.0.3.tgz#eab6cc23a43ff202d8f38bbd9801a7a2ec750cc2" - integrity sha512-KJaH1+ajm/gelvhImkXZx8HrBaGZDERqhOCRpikuwReVDTf4X3TlXqF+oKt153qf5HUXWR4CUL6NkNKNQWjhbA== - dependencies: - web3-errors "^1.0.2" - web3-eth-iban "^4.0.3" - web3-providers-http "^4.0.3" - web3-providers-ws "^4.0.3" - web3-types "^1.0.2" - web3-utils "^4.0.3" - web3-validator "^1.0.2" - optionalDependencies: - web3-providers-ipc "^4.0.3" +web3-bzz@1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/web3-bzz/-/web3-bzz-1.2.7.tgz#aa0f3d162f0777a5f35367dc5b70012dd1e129d0" + integrity sha512-iTIWBR+Z+Bn09WprtKm46LmyNOasg2lUn++AjXkBTB8UNxlUybxtza84yl2ETTZUs0zuFzdSSAEgbjhygG+9oA== + dependencies: + "@types/node" "^10.12.18" + got "9.6.0" + swarm-js "^0.1.40" + underscore "1.9.1" -web3-errors@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/web3-errors/-/web3-errors-1.0.2.tgz#e8ce6e22dfdfd9aeaf8d7535653e55b094b5accd" - integrity sha512-LtRUASAQKeCKyxHRhfyU5xiE9asUmo7KJ9bEzzaPlkVYLl5lzhUXzd6lvnQfSaSXJnlzoUXvhI5I0Hpzc8Lohg== +web3-core-helpers@1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/web3-core-helpers/-/web3-core-helpers-1.2.7.tgz#522f859775ea0d15e7e40359c46d4efc5da92aee" + integrity sha512-bdU++9QATGeCetVrMp8pV97aQtVkN5oLBf/TWu/qumC6jK/YqrvLlBJLdwbz0QveU8zOSap6GCvJbqKvmmbV2A== dependencies: - web3-types "^1.0.2" + underscore "1.9.1" + web3-eth-iban "1.2.7" + web3-utils "1.2.7" -web3-eth-abi@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/web3-eth-abi/-/web3-eth-abi-4.0.3.tgz#cc06cc39868d8bcc181528aa46ae9d5c80ed93b6" - integrity sha512-is1sKkTna5LQri25iRbxJ43kQ6qlFR/Syi6dnpwsFua0qAyKuDTxLZDoMaBfdH8NvxvjuGWFUWALwuSk8gk5Xg== +web3-core-method@1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/web3-core-method/-/web3-core-method-1.2.7.tgz#73fd80d2bf0765ff6efc454db49ac83d1769a45e" + integrity sha512-e1TI0QUnByDMbQ8QHwnjxfjKw0LIgVRY4TYrlPijET9ebqUJU1HCayn/BHIMpV6LKyR1fQj9EldWyT64wZQXkg== dependencies: - "@ethersproject/abi" "^5.7.0" - "@ethersproject/bignumber" "^5.7.0" - web3-errors "^1.0.2" - web3-types "^1.0.2" - web3-utils "^4.0.3" + underscore "1.9.1" + web3-core-helpers "1.2.7" + web3-core-promievent "1.2.7" + web3-core-subscriptions "1.2.7" + web3-utils "1.2.7" -web3-eth-accounts@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/web3-eth-accounts/-/web3-eth-accounts-4.0.3.tgz#7e570b3170aca052b358975235637a94b5313826" - integrity sha512-qS4r25weJYlKzHPIneL3g33LG+I6QkRCs25ZtooK6elurlZY4HyRE04BIWv12xZswtsvdmMt4HysMUNKgLrgPg== - dependencies: - "@ethereumjs/rlp" "^4.0.1" - crc-32 "^1.2.2" - ethereum-cryptography "^2.0.0" - web3-errors "^1.0.2" - web3-types "^1.0.2" - web3-utils "^4.0.3" - web3-validator "^1.0.2" - -web3-eth-contract@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/web3-eth-contract/-/web3-eth-contract-4.0.3.tgz#667e8f8052034f49a9130e0f286976bcf43c5d77" - integrity sha512-x8YsIVVUeONwLCnUmswk5KD3luYxaKuN/xnSzxpb8fE4/KBA6eJswYcIGPrK9QILrVR26yDV/QQpgLU1IJS14g== - dependencies: - web3-core "^4.0.3" - web3-errors "^1.0.2" - web3-eth "^4.0.3" - web3-eth-abi "^4.0.3" - web3-types "^1.0.2" - web3-utils "^4.0.3" - web3-validator "^1.0.2" - -web3-eth-ens@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/web3-eth-ens/-/web3-eth-ens-4.0.3.tgz#9b17bdcdc262ddcb5b9fd0b4893c0a9a56bf07ca" - integrity sha512-1tk1WWJB6lsViRFxHR9kt8qgfMV0cySeNBa8H/bZ9/HZ1G8L/c2cboVrG4D0QsPO1im1jQl4Cf3ceKH0PW1KZg== - dependencies: - "@adraffy/ens-normalize" "^1.8.8" - web3-core "^4.0.3" - web3-errors "^1.0.2" - web3-eth "^4.0.3" - web3-eth-contract "^4.0.3" - web3-net "^4.0.3" - web3-types "^1.0.2" - web3-utils "^4.0.3" - web3-validator "^1.0.2" - -web3-eth-iban@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/web3-eth-iban/-/web3-eth-iban-4.0.3.tgz#3fca87323c00a29f1b3870d397153803eb0bcf4e" - integrity sha512-9gn6fb034fh3DvQeutuhaG3J9+ZSriPC/O/H7K+lgUWJZh/lpaZy5A06nhHzNcleCWC07Q6J7d7VZlNjaBPtOA== +web3-core-promievent@1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/web3-core-promievent/-/web3-core-promievent-1.2.7.tgz#fc7fa489f4cf76a040800f3dfd4b45c51bd3a39f" + integrity sha512-jNmsM/czCeMGQqKKwM9/HZVTJVIF96hdMVNN/V9TGvp+EEE7vDhB4pUocDnc/QF9Z/5QFBCVmvNWttlRgZmU0A== dependencies: - web3-errors "^1.0.2" - web3-types "^1.0.2" - web3-utils "^4.0.3" - web3-validator "^1.0.2" + eventemitter3 "3.1.2" -web3-eth-personal@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/web3-eth-personal/-/web3-eth-personal-4.0.3.tgz#df4c59bf2a0e07cd6966259d1312be6b5b61846e" - integrity sha512-Gugz45w/D4wlUNbUth8iHWkv0c5fFZGWZqFvpACJul0z9h0Ou8HzuJMUv3U0xFOQJF5fniVegfp6l0FJQ3hGrQ== +web3-core-requestmanager@1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/web3-core-requestmanager/-/web3-core-requestmanager-1.2.7.tgz#9da0efce898ead7004d4ac50f748f5131cfe4d79" + integrity sha512-HJb/txjHixu1dxIebiZQKBoJCaNu4gsh7mq/uj6Z/w6tIHbybL90s/7ADyMED353yyJ2tDWtYJqeMVAR+KtdaA== dependencies: - web3-core "^4.0.3" - web3-eth "^4.0.3" - web3-rpc-methods "^1.0.2" - web3-types "^1.0.2" - web3-utils "^4.0.3" - web3-validator "^1.0.2" + underscore "1.9.1" + web3-core-helpers "1.2.7" + web3-providers-http "1.2.7" + web3-providers-ipc "1.2.7" + web3-providers-ws "1.2.7" -web3-eth@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/web3-eth/-/web3-eth-4.0.3.tgz#b7f311eba95151f547ccce285893af9917da9e35" - integrity sha512-4t1+lpqzk3ljubr0CKE9Ila82p2Pim6Bn7ZIruVfMt9AOA5wL6M0OeMTy0fWBODLJiZJ7R77Ugm0kvEVWD3lqg== +web3-core-subscriptions@1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/web3-core-subscriptions/-/web3-core-subscriptions-1.2.7.tgz#30c64aede03182832883b17c77e21cbb0933c86e" + integrity sha512-W/CzQYOUawdMDvkgA/fmLsnG5aMpbjrs78LZMbc0MFXLpH3ofqAgO2by4QZrrTShUUTeWS0ZuEkFFL/iFrSObw== dependencies: - setimmediate "^1.0.5" - web3-core "^4.0.3" - web3-errors "^1.0.2" - web3-eth-abi "^4.0.3" - web3-eth-accounts "^4.0.3" - web3-net "^4.0.3" - web3-providers-ws "^4.0.3" - web3-rpc-methods "^1.0.2" - web3-types "^1.0.2" - web3-utils "^4.0.3" - web3-validator "^1.0.2" - -web3-net@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/web3-net/-/web3-net-4.0.3.tgz#9aeed6fa3d48adcf63d8377900acbe3e64020154" - integrity sha512-qe+stvVgYhO8AiPgDykZW5gS4mZ3GRWdQ8xn3eTvderresIMvdZYSAoUla2jWl1CgpcqzaoOSO9Pf8t43fr8SA== + eventemitter3 "3.1.2" + underscore "1.9.1" + web3-core-helpers "1.2.7" + +web3-core@1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/web3-core/-/web3-core-1.2.7.tgz#9248b04331e458c76263d758c51b0cc612953900" + integrity sha512-QA0MTae0gXcr3KHe3cQ4x56+Wh43ZKWfMwg1gfCc3NNxPRM1jJ8qudzyptCAUcxUGXWpDG8syLIn1APDz5J8BQ== dependencies: - web3-core "^4.0.3" - web3-rpc-methods "^1.0.2" - web3-types "^1.0.2" - web3-utils "^4.0.3" + "@types/bn.js" "^4.11.4" + "@types/node" "^12.6.1" + bignumber.js "^9.0.0" + web3-core-helpers "1.2.7" + web3-core-method "1.2.7" + web3-core-requestmanager "1.2.7" + web3-utils "1.2.7" -web3-providers-http@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/web3-providers-http/-/web3-providers-http-4.0.3.tgz#c6c8364ed56c4183e6bed58de20c1972f513c7ae" - integrity sha512-5E6nKjWrwlJdhGImOxyTnFDT6UcZu4waO6AJrENBRh2vdoCfP/Piiv3PLywHs71gwTMsAjy6CNPL5lZdGf+JQA== +web3-eth-abi@1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/web3-eth-abi/-/web3-eth-abi-1.2.7.tgz#6f3471b578649fddd844a14d397a3dd430fc44a5" + integrity sha512-4FnlT1q+D0XBkxSMXlIb/eG337uQeMaUdtVQ4PZ3XzxqpcoDuMgXm4o+3NRxnWmr4AMm6QKjM+hcC7c0mBKcyg== dependencies: - cross-fetch "^3.1.5" - web3-errors "^1.0.2" - web3-types "^1.0.2" - web3-utils "^4.0.3" + ethers "4.0.0-beta.3" + underscore "1.9.1" + web3-utils "1.2.7" -web3-providers-ipc@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/web3-providers-ipc/-/web3-providers-ipc-4.0.3.tgz#d7af699a2afae0f7396d08ef8cc82b5ab4374398" - integrity sha512-v+Ugp5XXUVcAQju/u4ThdjI3FM9lq674F6cJ7yz3R6uTel+wNPDiT47Se8hvm5grgHid7z3MbVYCQpDCiiAFHw== +web3-eth-accounts@1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/web3-eth-accounts/-/web3-eth-accounts-1.2.7.tgz#087f55d04a01b815b93151aac2fc1677436b9c59" + integrity sha512-AE7QWi/iIQIjXwlAPtlMabm/OPFF0a1PhxT1EiTckpYNP8fYs6jW7lYxEtJPPJIKqfMjoi1xkEqTVR1YZQ88lg== + dependencies: + "@web3-js/scrypt-shim" "^0.1.0" + crypto-browserify "3.12.0" + eth-lib "^0.2.8" + ethereumjs-common "^1.3.2" + ethereumjs-tx "^2.1.1" + underscore "1.9.1" + uuid "3.3.2" + web3-core "1.2.7" + web3-core-helpers "1.2.7" + web3-core-method "1.2.7" + web3-utils "1.2.7" + +web3-eth-contract@1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/web3-eth-contract/-/web3-eth-contract-1.2.7.tgz#13d7f6003d6221f9a5fd61c2d3b5d039477c9674" + integrity sha512-uW23Y0iL7XroRNbf9fWZ1N6OYhEYTJX8gTuYASuRnpYrISN5QGiQML6pq/NCzqypR1bl5E0fuINZQSK/xefIVw== + dependencies: + "@types/bn.js" "^4.11.4" + underscore "1.9.1" + web3-core "1.2.7" + web3-core-helpers "1.2.7" + web3-core-method "1.2.7" + web3-core-promievent "1.2.7" + web3-core-subscriptions "1.2.7" + web3-eth-abi "1.2.7" + web3-utils "1.2.7" + +web3-eth-ens@1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/web3-eth-ens/-/web3-eth-ens-1.2.7.tgz#0bfa7d4b6c7753abbb31a2eb01a364b538f4c860" + integrity sha512-SPRnvUNWQ0CnnTDBteGIJkvFWEizJcAHlVsrFLICwcwFZu+appjX1UOaoGu2h3GXWtc/XZlu7B451Gi+Os2cTg== + dependencies: + eth-ens-namehash "2.0.8" + underscore "1.9.1" + web3-core "1.2.7" + web3-core-helpers "1.2.7" + web3-core-promievent "1.2.7" + web3-eth-abi "1.2.7" + web3-eth-contract "1.2.7" + web3-utils "1.2.7" + +web3-eth-iban@1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/web3-eth-iban/-/web3-eth-iban-1.2.7.tgz#832809c28586be3c667a713b77a2bcba11b7970f" + integrity sha512-2NrClz1PoQ3nSJBd+91ylCOVga9qbTxjRofq/oSCoHVAEvz3WZyttx9k5DC+0rWqwJF1h69ufFvdHAAlmN/4lg== dependencies: - web3-errors "^1.0.2" - web3-types "^1.0.2" - web3-utils "^4.0.3" + bn.js "4.11.8" + web3-utils "1.2.7" -web3-providers-ws@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/web3-providers-ws/-/web3-providers-ws-4.0.3.tgz#c611a0ae81ac022d8ccb01f71da761f7b4decd85" - integrity sha512-V2bYiMvhv+xBYxFdf8V1zGTwhJoAkBQNMECVGNjQIz1qBKuqu6hXHasmkYSJV780LD6qoL58KlfTggjf4SUSaA== +web3-eth-personal@1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/web3-eth-personal/-/web3-eth-personal-1.2.7.tgz#322cc2b14c37737b21772a53e4185686a04bf9be" + integrity sha512-2OAa1Spz0uB29dwCM8+1y0So7E47A4gKznjBEwXIYEcUIsvwT5X7ofFhC2XxyRpqlIWZSQAxRSSJFyupRRXzyw== dependencies: - "@types/ws" "^8.5.3" - isomorphic-ws "^5.0.0" - web3-errors "^1.0.2" - web3-types "^1.0.2" - web3-utils "^4.0.3" - ws "^8.8.1" + "@types/node" "^12.6.1" + web3-core "1.2.7" + web3-core-helpers "1.2.7" + web3-core-method "1.2.7" + web3-net "1.2.7" + web3-utils "1.2.7" -web3-rpc-methods@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/web3-rpc-methods/-/web3-rpc-methods-1.0.2.tgz#3ff35c5d4e38ad31ef3cf77eb3fe2fd08e2a3f4a" - integrity sha512-VhLHvgR62JUNgo0op8hP4LcRkvdF0WaHD9xhcEKGLcri9VfYvR1yTZ3CVh6NTgRCmfDePObbp5blHfbla1cC5Q== +web3-eth@1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/web3-eth/-/web3-eth-1.2.7.tgz#9427daefd3641200679c2946f77fc184dbfb5b4c" + integrity sha512-ljLd0oB4IjWkzFGVan4HkYhJXhSXgn9iaSaxdJixKGntZPgWMJfxeA+uLwTrlxrWzhvy4f+39WnT7wCh5e9TGg== + dependencies: + underscore "1.9.1" + web3-core "1.2.7" + web3-core-helpers "1.2.7" + web3-core-method "1.2.7" + web3-core-subscriptions "1.2.7" + web3-eth-abi "1.2.7" + web3-eth-accounts "1.2.7" + web3-eth-contract "1.2.7" + web3-eth-ens "1.2.7" + web3-eth-iban "1.2.7" + web3-eth-personal "1.2.7" + web3-net "1.2.7" + web3-utils "1.2.7" + +web3-net@1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/web3-net/-/web3-net-1.2.7.tgz#c355621a8769c9c1a967c801e7db90c92a0e3808" + integrity sha512-j9qeZrS1FNyCeA0BfdLojkxOZQz3FKa1DJI+Dw9fEVhZS68vLOFANu2RB96gR9BoPHo5+k5D3NsKOoxt1gw3Gg== dependencies: - web3-core "^4.0.3" - web3-types "^1.0.2" - web3-validator "^1.0.2" + web3-core "1.2.7" + web3-core-method "1.2.7" + web3-utils "1.2.7" -web3-types@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/web3-types/-/web3-types-1.0.2.tgz#1655a400d31984153fc26ca1f8960f547ca1f2df" - integrity sha512-tLzA9vevGGWdHlxXvPRJjEIIR0UnZBI5Kq9qiENRS/vSekTHAHp7u+WGDxt+6kP105gKlbep50TogQIvJqLfnA== +web3-providers-http@1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/web3-providers-http/-/web3-providers-http-1.2.7.tgz#31eb15390c103169b3d7d31bdb1ccae9e3f1629d" + integrity sha512-vazGx5onuH/zogrwkUaLFJwFcJ6CckP65VFSHoiV+GTQdkOqgoDIha7StKkslvDz4XJ2FuY/zOZHbtuOYeltXQ== + dependencies: + web3-core-helpers "1.2.7" + xhr2-cookies "1.1.0" -web3-utils@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/web3-utils/-/web3-utils-4.0.3.tgz#80c077e56c0841528ea4513c67d83e460217b379" - integrity sha512-clBvm/vWR2mAc9nPnsPYBZMikIhVG9RAsXdrxvXI4e2jAQ3DTtHKMhqy+Cl214dQaAdAEYyVb5ILW5lKKqk2vA== +web3-providers-ipc@1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/web3-providers-ipc/-/web3-providers-ipc-1.2.7.tgz#4e6716e8723d431df3d6bfa1acd2f7c04e7071ad" + integrity sha512-/zc0y724H2zbkV4UbGGMhsEiLfafjagIzfrsWZnyTZUlSB0OGRmmFm2EkLJAgtXrLiodaHHyXKM0vB8S24bxdA== dependencies: - ethereum-cryptography "^2.0.0" - web3-errors "^1.0.2" - web3-types "^1.0.2" - web3-validator "^1.0.2" + oboe "2.1.4" + underscore "1.9.1" + web3-core-helpers "1.2.7" -web3-validator@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/web3-validator/-/web3-validator-1.0.2.tgz#ca7d247b49f4f690db86e5b953272a627dc5950a" - integrity sha512-orx1CQAEnwJUnl/8iF2II2zSA4wiooNJvFmVE0Dbmt/kE370SugIDViQP76snhxtouG2AXzz4GyKbPCMlLGh/A== +web3-providers-ws@1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/web3-providers-ws/-/web3-providers-ws-1.2.7.tgz#95b1cc5dc25e9b9d6630d6754f9354313b62f532" + integrity sha512-b5XzqDpRkNVe6MFs5K6iqOEyjQikHtg3KuU2/ClCDV37hm0WN4xCRVMC0LwegulbDXZej3zT9+1CYzGaGFREzA== dependencies: - ethereum-cryptography "^2.0.0" - is-my-json-valid "^2.20.6" - util "^0.12.5" - web3-errors "^1.0.2" - web3-types "^1.0.2" + "@web3-js/websocket" "^1.0.29" + eventemitter3 "^4.0.0" + underscore "1.9.1" + web3-core-helpers "1.2.7" -web3@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/web3/-/web3-4.0.3.tgz#afeb977c9f883ff683d630ab9f5937eb56bc7cf4" - integrity sha512-rUMxui5f52yPWjiMRQV6xqIrTQSovYM2CNhl57y+xj/fGXNLbI1D5FsLPnUMZjMaFHJBTteaBxq/sTEaw/1jNA== - dependencies: - web3-core "^4.0.3" - web3-errors "^1.0.2" - web3-eth "^4.0.3" - web3-eth-abi "^4.0.3" - web3-eth-accounts "^4.0.3" - web3-eth-contract "^4.0.3" - web3-eth-ens "^4.0.3" - web3-eth-iban "^4.0.3" - web3-eth-personal "^4.0.3" - web3-net "^4.0.3" - web3-providers-http "^4.0.3" - web3-providers-ws "^4.0.3" - web3-rpc-methods "^1.0.2" - web3-types "^1.0.2" - web3-utils "^4.0.3" - web3-validator "^1.0.2" +web3-shh@1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/web3-shh/-/web3-shh-1.2.7.tgz#5382c7bc2f39539eb2841c4576d23ade25720461" + integrity sha512-f6PAgcpG0ZAo98KqCmeHoDEx5qzm3d5plet18DkT4U6WIeYowKdec8vZaLPRR7c2XreXFJ2gQf45CB7oqR7U/w== + dependencies: + web3-core "1.2.7" + web3-core-method "1.2.7" + web3-core-subscriptions "1.2.7" + web3-net "1.2.7" + +web3-utils@1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/web3-utils/-/web3-utils-1.2.7.tgz#b68e232917e4376f81cf38ef79878e5903d18e93" + integrity sha512-FBh/CPJND+eiPeUF9KVbTyTZtXNWxPWtByBaWS6e2x4ACazPX711EeNaZaChIOGSLGe6se2n7kg6wnawe/MjuQ== + dependencies: + bn.js "4.11.8" + eth-lib "0.2.7" + ethereum-bloom-filters "^1.0.6" + ethjs-unit "0.1.6" + number-to-bn "1.7.0" + randombytes "^2.1.0" + underscore "1.9.1" + utf8 "3.0.0" + +web3@1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/web3/-/web3-1.2.7.tgz#fcb83571036c1c6f475bc984785982a444e8d78e" + integrity sha512-jAAJHMfUlTps+jH2li1ckDFEpPrEEriU/ubegSTGRl3KRdNhEqT93+3kd7FHJTn3NgjcyURo2+f7Da1YcZL8Mw== + dependencies: + web3-bzz "1.2.7" + web3-core "1.2.7" + web3-eth "1.2.7" + web3-eth-personal "1.2.7" + web3-net "1.2.7" + web3-shh "1.2.7" + web3-utils "1.2.7" webidl-conversions@^3.0.0: version "3.0.1" @@ -12682,6 +13709,15 @@ write-file-atomic@^4.0.2: imurmurhash "^0.1.4" signal-exit "^3.0.7" +ws@^3.0.0: + version "3.3.3" + resolved "https://registry.yarnpkg.com/ws/-/ws-3.3.3.tgz#f1cf84fe2d5e901ebce94efaece785f187a228f2" + integrity sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA== + dependencies: + async-limiter "~1.0.0" + safe-buffer "~5.1.0" + ultron "~1.1.0" + ws@^6.0.0, ws@^6.2.2: version "6.2.2" resolved "https://registry.npmjs.org/ws/-/ws-6.2.2.tgz" @@ -12694,7 +13730,7 @@ ws@^7, ws@^7.2.0, ws@^7.4.5, ws@^7.5.1: resolved "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz" integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q== -ws@^8.11.0, ws@^8.12.1, ws@^8.5.0, ws@^8.8.1: +ws@^8.11.0, ws@^8.12.1, ws@^8.5.0: version "8.13.0" resolved "https://registry.yarnpkg.com/ws/-/ws-8.13.0.tgz#9a9fb92f93cf41512a0735c8f4dd09b8a1211cd0" integrity sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA== @@ -12707,6 +13743,33 @@ xcode@^3.0.0, xcode@^3.0.1: simple-plist "^1.1.0" uuid "^7.0.3" +xhr-request-promise@^0.1.2: + version "0.1.3" + resolved "https://registry.yarnpkg.com/xhr-request-promise/-/xhr-request-promise-0.1.3.tgz#2d5f4b16d8c6c893be97f1a62b0ed4cf3ca5f96c" + integrity sha512-YUBytBsuwgitWtdRzXDDkWAXzhdGB8bYm0sSzMPZT7Z2MBjMSTHFsyCT1yCRATY+XC69DUrQraRAEgcoCRaIPg== + dependencies: + xhr-request "^1.1.0" + +xhr-request@^1.0.1, xhr-request@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/xhr-request/-/xhr-request-1.1.0.tgz#f4a7c1868b9f198723444d82dcae317643f2e2ed" + integrity sha512-Y7qzEaR3FDtL3fP30k9wO/e+FBnBByZeybKOhASsGP30NIkRAAkKD/sCnLvgEfAIEC1rcmK7YG8f4oEnIrrWzA== + dependencies: + buffer-to-arraybuffer "^0.0.5" + object-assign "^4.1.1" + query-string "^5.0.1" + simple-get "^2.7.0" + timed-out "^4.0.1" + url-set-query "^1.0.0" + xhr "^2.0.4" + +xhr2-cookies@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/xhr2-cookies/-/xhr2-cookies-1.1.0.tgz#7d77449d0999197f155cb73b23df72505ed89d48" + integrity sha512-hjXUA6q+jl/bd8ADHcVfFsSPIf+tyLIjuO9TwJC9WI6JP2zKcS7C+p56I9kCLLsaCiNT035iYvEUUzdEFj/8+g== + dependencies: + cookiejar "^2.1.1" + xhr2@0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/xhr2/-/xhr2-0.1.3.tgz#cbfc4759a69b4a888e78cf4f20b051038757bd11" @@ -12717,6 +13780,16 @@ xhr2@0.1.4: resolved "https://registry.yarnpkg.com/xhr2/-/xhr2-0.1.4.tgz#7f87658847716db5026323812f818cadab387a5f" integrity sha512-3QGhDryRzTbIDj+waTRvMBe8SyPhW79kz3YnNb+HQt/6LPYQT3zT3Jt0Y8pBofZqQX26x8Ecfv0FXR72uH5VpA== +xhr@^2.0.4, xhr@^2.3.3: + version "2.6.0" + resolved "https://registry.yarnpkg.com/xhr/-/xhr-2.6.0.tgz#b69d4395e792b4173d6b7df077f0fc5e4e2b249d" + integrity sha512-/eCGLb5rxjx5e3mF1A7s+pLlR6CGyqWN91fv1JgER5mVWg1MZmlhBvy9kjcsOdRk8RrIujotWyJamfyrp+WIcA== + dependencies: + global "~4.4.0" + is-function "^1.0.1" + parse-headers "^2.0.0" + xtend "^4.0.0" + xml-js@^1.6.11: version "1.6.11" resolved "https://registry.yarnpkg.com/xml-js/-/xml-js-1.6.11.tgz#927d2f6947f7f1c19a316dd8eea3614e8b18f8e9" @@ -12777,7 +13850,12 @@ y18n@^5.0.5: resolved "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz" integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== -yallist@^3.0.2: +yaeti@^0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/yaeti/-/yaeti-0.0.6.tgz#f26f484d72684cf42bedfb76970aa1608fbf9577" + integrity sha512-MvQa//+KcZCUkBTIC9blM+CU9J2GzuTytsOUwf2lidtvkx/6gnEp1QvJv34t9vdjhFmha/mUiNDbN0D0mJWdug== + +yallist@^3.0.0, yallist@^3.0.2, yallist@^3.1.1: version "3.1.1" resolved "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz" integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== From cf6a24e407074238cf6aa652aeb129295d0ef12b Mon Sep 17 00:00:00 2001 From: JavidHaji-zada Date: Thu, 10 Aug 2023 13:16:03 +0400 Subject: [PATCH 036/509] fix Node Crypto error --- babel.config.js | 5 ++- package.json | 6 ++++ yarn.lock | 95 ++++++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 101 insertions(+), 5 deletions(-) diff --git a/babel.config.js b/babel.config.js index b57f2dc6f..08956745e 100644 --- a/babel.config.js +++ b/babel.config.js @@ -24,7 +24,10 @@ module.exports = function (api) { '@navigation': './src/navigation', '@screens': './src/screens', '@theme': './src/theme', - '@utils': './src/utils' + '@utils': './src/utils', + 'crypto': 'react-native-quick-crypto', + 'stream': 'stream-browserify', + 'buffer': '@craftzdog/react-native-buffer' }, extensions: ['.ts', '.tsx'] } diff --git a/package.json b/package.json index f591e8046..f2c0f8221 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ }, "dependencies": { "@babel/preset-env": "^7.21.5", + "@craftzdog/react-native-buffer": "^6.0.5", "@fioprotocol/fiojs": "^1.0.1", "@fioprotocol/fiosdk": "^1.8.0", "@metamask/safe-event-emitter": "^3.0.0", @@ -42,6 +43,7 @@ "@tanstack/react-query": "^4.28.0", "@testing-library/jest-dom": "^5.16.5", "@testing-library/react-native": "^12.1.1", + "@tradle/react-native-http": "^2.0.1", "@types/jest": "^29.5.1", "@types/lodash": "^4.14.196", "@waves/ts-lib-crypto": "^1.4.4-beta.1", @@ -81,6 +83,7 @@ "expo-system-ui": "~2.2.1", "expo-updates": "~0.16.4", "fast-text-encoding": "^1.0.6", + "https-browserify": "^1.0.0", "jest": "^29.2.1", "jest-expo": "^48.0.2", "lodash": "^4.17.21", @@ -98,6 +101,8 @@ "react-native-mymonero-core": "^0.3.1", "react-native-pager-view": "6.1.2", "react-native-popover-view": "^5.1.7", + "react-native-quick-base64": "^2.0.7", + "react-native-quick-crypto": "^0.6.1", "react-native-reanimated": "~2.14.4", "react-native-safe-area-context": "^4.5.0", "react-native-screens": "^3.20.0", @@ -109,6 +114,7 @@ "ripple-lib": "^1.10.1", "stellar-sdk": "^10.4.1", "stream": "^0.0.2", + "stream-browserify": "^1.0.0", "thorify": "^1.6.2", "tiny-secp256k1": "^2.2.3", "use-context-selector": "^1.4.1", diff --git a/yarn.lock b/yarn.lock index e51837988..5d9d2ed5a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1481,6 +1481,14 @@ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== +"@craftzdog/react-native-buffer@^6.0.5": + version "6.0.5" + resolved "https://registry.yarnpkg.com/@craftzdog/react-native-buffer/-/react-native-buffer-6.0.5.tgz#0d4fbe0dd104186d2806655e3c0d25cebdae91d3" + integrity sha512-Av+YqfwA9e7jhgI9GFE/gTpwl/H+dRRLmZyJPOpKTy107j9Oj7oXlm3/YiMNz+C/CEGqcKAOqnXDLs4OL6AAFw== + dependencies: + ieee754 "^1.2.1" + react-native-quick-base64 "^2.0.5" + "@egjs/hammerjs@^2.0.17": version "2.0.17" resolved "https://registry.npmjs.org/@egjs/hammerjs/-/hammerjs-2.0.17.tgz" @@ -2791,6 +2799,14 @@ resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf" integrity sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A== +"@tradle/react-native-http@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@tradle/react-native-http/-/react-native-http-2.0.1.tgz#af19e240e1e580bfa249563924d1be472686f48b" + integrity sha512-pBLa/mlkD5HE4e8ndF6l8gj4ZUVYlrpRbrODeuxL0+ZFJ0TmBvQ9+bUNy8gNdWTS6kik2fW+u7eiYMeGaISkrA== + dependencies: + Base64 "~0.2.0" + inherits "~2.0.1" + "@types/babel__core@^7.1.14": version "7.20.0" resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.0.tgz#61bc5a4cae505ce98e1e36c5445e4bee060d8891" @@ -2965,6 +2981,11 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.55.tgz#c329cbd434c42164f846b909bd6f85b5537f6240" integrity sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ== +"@types/node@^17.0.31": + version "17.0.45" + resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.45.tgz#2c0fafd78705e7a18b7906b5201a522719dc5190" + integrity sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw== + "@types/pbkdf2@^3.0.0": version "3.1.0" resolved "https://registry.yarnpkg.com/@types/pbkdf2/-/pbkdf2-3.1.0.tgz#039a0e9b67da0cdc4ee5dab865caa6b267bb66b1" @@ -3280,6 +3301,11 @@ resolved "https://registry.yarnpkg.com/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz#e77a97fbd345b76d83245edcd17d393b1b41fb31" integrity sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ== +Base64@~0.2.0: + version "0.2.1" + resolved "https://registry.yarnpkg.com/Base64/-/Base64-0.2.1.tgz#ba3a4230708e186705065e66babdd4c35cf60028" + integrity sha512-reGEWshDmTDQDsCec/HduOO9Wyj6yMOupMfhIf3ugN1TDlK2NQW4DDJSqNNtp380SNcvRfXtO8HSCQot0d0SMw== + JSONStream@^1.3.5: version "1.3.5" resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.5.tgz#3208c1f08d3a4d99261ab64f92302bc15e111ca0" @@ -5026,7 +5052,7 @@ crypt@0.0.2, crypt@~0.0.1: resolved "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz" integrity sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow== -crypto-browserify@3.12.0: +crypto-browserify@3.12.0, crypto-browserify@^3.12.0: version "3.12.0" resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec" integrity sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg== @@ -7516,6 +7542,11 @@ http2-wrapper@^1.0.0-beta.5.2: quick-lru "^5.1.1" resolve-alpn "^1.0.0" +https-browserify@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" + integrity sha512-J+FkSdyD+0mA0N+81tMotaRMfSL9SGi+xpD3T6YApKsc3bGSXJlfXri3VyFOeYkfLRQisDk1W+jIFFKBeUBbBg== + https-proxy-agent@^5.0.0, https-proxy-agent@^5.0.1: version "5.0.1" resolved "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz" @@ -7629,7 +7660,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3: +inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1, inherits@~2.0.3, inherits@~2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -8063,6 +8094,11 @@ is-wsl@^2.1.1, is-wsl@^2.2.0: dependencies: is-docker "^2.0.0" +isarray@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" + integrity sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ== + isarray@1.0.0, isarray@~1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz" @@ -10973,6 +11009,26 @@ react-native-popover-view@^5.1.7: deprecated-react-native-prop-types "^2.3.0" prop-types "^15.8.1" +react-native-quick-base64@^2.0.5, react-native-quick-base64@^2.0.7: + version "2.0.7" + resolved "https://registry.yarnpkg.com/react-native-quick-base64/-/react-native-quick-base64-2.0.7.tgz#70ec863fb5dba8cd858a262f6b901dbbfbfdd3df" + integrity sha512-QmOon3zXAWFi3KvQVCJjCC7N66rwfl1R4nLPuJ+OVs8nWysvKlaU8mKxe2BV4Ud1nB3nTDgyi2VCJFpjtjJxKw== + dependencies: + base64-js "^1.5.1" + +react-native-quick-crypto@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/react-native-quick-crypto/-/react-native-quick-crypto-0.6.1.tgz#7b89c67c4a5d3669c4491fe7884621c1c74d01bc" + integrity sha512-s6uFo7tcI3syo8/y5j+t6Rf+KVSuRKDp6tH04A0vjaHptJC6Iu7DVgkNYO7aqtfrYn8ZUgQ/Kqaq+m4i9TxgIQ== + dependencies: + "@craftzdog/react-native-buffer" "^6.0.5" + "@types/node" "^17.0.31" + crypto-browserify "^3.12.0" + events "^3.3.0" + react-native-quick-base64 "^2.0.5" + stream-browserify "^3.0.0" + string_decoder "^1.3.0" + react-native-reanimated@~2.14.4: version "2.14.4" resolved "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-2.14.4.tgz" @@ -11109,7 +11165,17 @@ react@18.2.0: dependencies: loose-envify "^1.1.0" -readable-stream@^3.4.0, readable-stream@^3.6.0: +readable-stream@^1.0.27-1: + version "1.1.14" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9" + integrity sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ== + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.1" + isarray "0.0.1" + string_decoder "~0.10.x" + +readable-stream@^3.4.0, readable-stream@^3.5.0, readable-stream@^3.6.0: version "3.6.2" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== @@ -12182,6 +12248,22 @@ stop-iteration-iterator@^1.0.0: dependencies: internal-slot "^1.0.4" +stream-browserify@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-1.0.0.tgz#bf9b4abfb42b274d751479e44e0ff2656b6f1193" + integrity sha512-e+V5xc4LlkOiRr64kZTUdb11exsbpSnwb9uwmXaHeDXCpfHg7vaefMJOxi21Pe74ZOqjZ87blBcqqpNAM4Ku0g== + dependencies: + inherits "~2.0.1" + readable-stream "^1.0.27-1" + +stream-browserify@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-3.0.0.tgz#22b0a2850cdf6503e73085da1fc7b7d0c2122f2f" + integrity sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA== + dependencies: + inherits "~2.0.4" + readable-stream "^3.5.0" + stream-buffers@2.2.x: version "2.2.0" resolved "https://registry.npmjs.org/stream-buffers/-/stream-buffers-2.2.0.tgz" @@ -12289,13 +12371,18 @@ string.prototype.trimstart@^1.0.6: define-properties "^1.1.4" es-abstract "^1.20.4" -string_decoder@^1.1.1: +string_decoder@^1.1.1, string_decoder@^1.3.0: version "1.3.0" resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz" integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== dependencies: safe-buffer "~5.2.0" +string_decoder@~0.10.x: + version "0.10.31" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" + integrity sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ== + string_decoder@~1.1.1: version "1.1.1" resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz" From ad0ad6adfc4028e12b1ff31ca3efeff9e33256ad Mon Sep 17 00:00:00 2001 From: JavidHaji-zada Date: Fri, 11 Aug 2023 15:09:23 +0400 Subject: [PATCH 037/509] added quick crypto --- babel.config.js | 4 +++- package.json | 11 ++++++++- src/prototypes/global.js | 11 +++++---- yarn.lock | 51 ++++++++++++++++++---------------------- 4 files changed, 43 insertions(+), 34 deletions(-) diff --git a/babel.config.js b/babel.config.js index 08956745e..93aedf643 100644 --- a/babel.config.js +++ b/babel.config.js @@ -27,7 +27,9 @@ module.exports = function (api) { '@utils': './src/utils', 'crypto': 'react-native-quick-crypto', 'stream': 'stream-browserify', - 'buffer': '@craftzdog/react-native-buffer' + 'buffer': '@craftzdog/react-native-buffer', + 'http': '@tradle/react-native-http', + 'https': 'https-browserify' }, extensions: ['.ts', '.tsx'] } diff --git a/package.json b/package.json index f2c0f8221..34cd3292e 100644 --- a/package.json +++ b/package.json @@ -99,6 +99,7 @@ "react-native-graph": "^1.0.1", "react-native-modal": "^13.0.1", "react-native-mymonero-core": "^0.3.1", + "react-native-os": "^1.2.6", "react-native-pager-view": "6.1.2", "react-native-popover-view": "^5.1.7", "react-native-quick-base64": "^2.0.7", @@ -114,9 +115,10 @@ "ripple-lib": "^1.10.1", "stellar-sdk": "^10.4.1", "stream": "^0.0.2", - "stream-browserify": "^1.0.0", + "stream-browserify": "^3.0.0", "thorify": "^1.6.2", "tiny-secp256k1": "^2.2.3", + "url": "^0.10.3", "use-context-selector": "^1.4.1", "web3": "1.2.7" }, @@ -144,6 +146,13 @@ "tslint-config-prettier": "^1.18.0", "typescript": "^4.9.5" }, + "react-native": { + "crypto": "react-native-quick-crypto", + "http": "@tradle/react-native-http", + "https": "https-browserify", + "os": "react-native-os", + "stream": "stream-browserify" + }, "private": true, "husky": { "hooks": { diff --git a/src/prototypes/global.js b/src/prototypes/global.js index d4755f492..d679c6609 100644 --- a/src/prototypes/global.js +++ b/src/prototypes/global.js @@ -1,6 +1,7 @@ /* eslint-disable @typescript-eslint/no-var-requires */ // Inject node globals into React Native global scope. -import * as ExpoCrypto from 'expo-crypto'; +import Crypto from 'react-native-quick-crypto'; + global.Buffer = require('buffer').Buffer; global.process = require('process'); @@ -17,10 +18,12 @@ if (typeof atob === 'undefined') { } if (typeof global.crypto !== 'object') { - global.crypto = {}; + global.crypto = Crypto; } +if (typeof window !== 'undefined') { + window.crypto = Crypto; +} if (typeof global.crypto.getRandomValues !== 'function') { - global.crypto.getRandomValues = (array) => - ExpoCrypto.getRandomBytes(array.byteLength); + global.crypto.getRandomValues = Crypto.getRandomValues; } diff --git a/yarn.lock b/yarn.lock index 5d9d2ed5a..4b466aee5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8094,11 +8094,6 @@ is-wsl@^2.1.1, is-wsl@^2.2.0: dependencies: is-docker "^2.0.0" -isarray@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" - integrity sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ== - isarray@1.0.0, isarray@~1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz" @@ -10776,6 +10771,11 @@ pump@^3.0.0: end-of-stream "^1.1.0" once "^1.3.1" +punycode@1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" + integrity sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw== + punycode@2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.0.tgz#5f863edc89b96db09074bad7947bf09056ca4e7d" @@ -10834,6 +10834,11 @@ query-string@^7.1.3: split-on-first "^1.0.0" strict-uri-encode "^2.0.0" +querystring@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" + integrity sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g== + querystringify@^2.1.1: version "2.2.0" resolved "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz" @@ -10996,6 +11001,11 @@ react-native-mymonero-core@^0.3.1: "@mymonero/mymonero-money-format" "^1.4.2" "@mymonero/mymonero-nettype" "^1.4.2" +react-native-os@^1.2.6: + version "1.2.6" + resolved "https://registry.yarnpkg.com/react-native-os/-/react-native-os-1.2.6.tgz#1bb16d78ccad1143972183a04f443cf1af9fbefa" + integrity sha512-OlT+xQAcvkcnf7imgXiu+myMkqDt4xw2bP5SlVo19hEn5XHBkPMLX7dk3sSGxxncH/ToMDsf1KLyrPabNVtadA== + react-native-pager-view@6.1.2: version "6.1.2" resolved "https://registry.yarnpkg.com/react-native-pager-view/-/react-native-pager-view-6.1.2.tgz#3522079b9a9d6634ca5e8d153bc0b4d660254552" @@ -11165,16 +11175,6 @@ react@18.2.0: dependencies: loose-envify "^1.1.0" -readable-stream@^1.0.27-1: - version "1.1.14" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9" - integrity sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ== - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.1" - isarray "0.0.1" - string_decoder "~0.10.x" - readable-stream@^3.4.0, readable-stream@^3.5.0, readable-stream@^3.6.0: version "3.6.2" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" @@ -12248,14 +12248,6 @@ stop-iteration-iterator@^1.0.0: dependencies: internal-slot "^1.0.4" -stream-browserify@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-1.0.0.tgz#bf9b4abfb42b274d751479e44e0ff2656b6f1193" - integrity sha512-e+V5xc4LlkOiRr64kZTUdb11exsbpSnwb9uwmXaHeDXCpfHg7vaefMJOxi21Pe74ZOqjZ87blBcqqpNAM4Ku0g== - dependencies: - inherits "~2.0.1" - readable-stream "^1.0.27-1" - stream-browserify@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-3.0.0.tgz#22b0a2850cdf6503e73085da1fc7b7d0c2122f2f" @@ -12378,11 +12370,6 @@ string_decoder@^1.1.1, string_decoder@^1.3.0: dependencies: safe-buffer "~5.2.0" -string_decoder@~0.10.x: - version "0.10.31" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" - integrity sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ== - string_decoder@~1.1.1: version "1.1.1" resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz" @@ -13227,6 +13214,14 @@ url-set-query@^1.0.0: resolved "https://registry.yarnpkg.com/url-set-query/-/url-set-query-1.0.0.tgz#016e8cfd7c20ee05cafe7795e892bd0702faa339" integrity sha512-3AChu4NiXquPfeckE5R5cGdiHCMWJx1dwCWOmWIL4KHAziJNOFIYJlpGFeKDvwLPHovZRCxK3cYlwzqI9Vp+Gg== +url@^0.10.3: + version "0.10.3" + resolved "https://registry.yarnpkg.com/url/-/url-0.10.3.tgz#021e4d9c7705f21bbf37d03ceb58767402774c64" + integrity sha512-hzSUW2q06EqL1gKM/a+obYHLIO6ct2hwPuviqTTOcfFVc61UbfJ2Q32+uGL/HCPxKqrdGB5QUwIe7UqlDgwsOQ== + dependencies: + punycode "1.3.2" + querystring "0.2.0" + use-context-selector@^1.4.1: version "1.4.1" resolved "https://registry.npmjs.org/use-context-selector/-/use-context-selector-1.4.1.tgz" From bfcf34ba4fdddd1baccdb4ecbe19c22632431dd2 Mon Sep 17 00:00:00 2001 From: JavidHaji-zada Date: Mon, 14 Aug 2023 12:46:17 +0400 Subject: [PATCH 038/509] successfuly bundling. --- .gitignore | 2 + App.tsx | 3 - babel.config.js | 3 +- index.js | 12 + metro.config.js | 4 + package.json | 3 +- patches/brorand+1.1.0.patch | 57 +++++ patches/eth-lib+0.1.29.patch | 19 ++ patches/scryptsy+2.1.0.patch | 458 +++++++++++++++++++++++++++++++++++ src/prototypes/global.js | 5 +- yarn.lock | 10 +- 11 files changed, 564 insertions(+), 12 deletions(-) create mode 100644 index.js create mode 100644 metro.config.js create mode 100644 patches/brorand+1.1.0.patch create mode 100644 patches/eth-lib+0.1.29.patch create mode 100644 patches/scryptsy+2.1.0.patch diff --git a/.gitignore b/.gitignore index 9f6620374..03bdfbc59 100644 --- a/.gitignore +++ b/.gitignore @@ -155,6 +155,8 @@ docs/pages/versions/*/react-native/*.diff /ios/build **/ios/.xcode.env.local /ios/versioned-react-native/*/ReactNative/sdks/hermes-engine/destroot +/ios/* +/android/* #firebase /firebase/* \ No newline at end of file diff --git a/App.tsx b/App.tsx index 44a37565b..2b4e16bf3 100644 --- a/App.tsx +++ b/App.tsx @@ -1,6 +1,3 @@ -import 'fast-text-encoding'; -import './src/prototypes/array'; -import './src/prototypes/global'; import React from 'react'; import Navigation from '@navigation/NavigationContainer'; import { useAppInit } from '@hooks/useAppInit'; diff --git a/babel.config.js b/babel.config.js index 93aedf643..e915832ee 100644 --- a/babel.config.js +++ b/babel.config.js @@ -29,7 +29,8 @@ module.exports = function (api) { 'stream': 'stream-browserify', 'buffer': '@craftzdog/react-native-buffer', 'http': '@tradle/react-native-http', - 'https': 'https-browserify' + 'https': 'https-browserify', + 'os': 'react-native-os' }, extensions: ['.ts', '.tsx'] } diff --git a/index.js b/index.js new file mode 100644 index 000000000..be9435dde --- /dev/null +++ b/index.js @@ -0,0 +1,12 @@ +import 'react-native-quick-crypto'; +import './src/prototypes/global'; +import './src/prototypes/array'; +import 'fast-text-encoding'; +import { registerRootComponent } from 'expo'; + +import App from './App'; + +// registerRootComponent calls AppRegistry.registerComponent('main', () => App); +// It also ensures that whether you load the app in Expo Go or in a native build, +// the environment is set up appropriately +registerRootComponent(App); diff --git a/metro.config.js b/metro.config.js new file mode 100644 index 000000000..9430b0f9b --- /dev/null +++ b/metro.config.js @@ -0,0 +1,4 @@ +// Learn more https://docs.expo.io/guides/customizing-metro +const { getDefaultConfig } = require('expo/metro-config'); + +module.exports = getDefaultConfig(__dirname); diff --git a/package.json b/package.json index 34cd3292e..c218f43f7 100644 --- a/package.json +++ b/package.json @@ -83,7 +83,7 @@ "expo-system-ui": "~2.2.1", "expo-updates": "~0.16.4", "fast-text-encoding": "^1.0.6", - "https-browserify": "^1.0.0", + "https-browserify": "^0.0.1", "jest": "^29.2.1", "jest-expo": "^48.0.2", "lodash": "^4.17.21", @@ -139,6 +139,7 @@ "eslint-plugin-promise": "^6.1.1", "eslint-plugin-react": "^7.32.2", "eslint-plugin-react-hooks": "^4.6.0", + "eth-lib": "^0.1.29", "husky": "^8.0.3", "lint-staged": "^13.2.0", "prettier": "2.8.7", diff --git a/patches/brorand+1.1.0.patch b/patches/brorand+1.1.0.patch new file mode 100644 index 000000000..1c9d7c63d --- /dev/null +++ b/patches/brorand+1.1.0.patch @@ -0,0 +1,57 @@ +diff --git a/node_modules/brorand/index.js b/node_modules/brorand/index.js +index 9a0fff4..0327a10 100644 +--- a/node_modules/brorand/index.js ++++ b/node_modules/brorand/index.js +@@ -1,8 +1,8 @@ ++import crypto from 'react-native-quick-crypto'; + var r; + + module.exports = function rand(len) { +- if (!r) +- r = new Rand(null); ++ if (!r) r = new Rand(null); + + return r.generate(len); + }; +@@ -18,12 +18,10 @@ Rand.prototype.generate = function generate(len) { + + // Emulate crypto API using randy + Rand.prototype._rand = function _rand(n) { +- if (this.rand.getBytes) +- return this.rand.getBytes(n); ++ if (this.rand.getBytes) return this.rand.getBytes(n); + + var res = new Uint8Array(n); +- for (var i = 0; i < res.length; i++) +- res[i] = this.rand.getByte(); ++ for (var i = 0; i < res.length; i++) res[i] = this.rand.getByte(); + return res; + }; + +@@ -43,23 +41,21 @@ if (typeof self === 'object') { + return arr; + }; + +- // Safari's WebWorkers do not have `crypto` ++ // Safari's WebWorkers do not have `crypto` + } else if (typeof window === 'object') { + // Old junk +- Rand.prototype._rand = function() { ++ Rand.prototype._rand = function () { + throw new Error('Not implemented yet'); + }; + } + } else { + // Node.js or Web worker with no crypto support + try { +- var crypto = require('crypto'); + if (typeof crypto.randomBytes !== 'function') + throw new Error('Not supported'); + + Rand.prototype._rand = function _rand(n) { + return crypto.randomBytes(n); + }; +- } catch (e) { +- } ++ } catch (e) {} + } diff --git a/patches/eth-lib+0.1.29.patch b/patches/eth-lib+0.1.29.patch new file mode 100644 index 000000000..ecbdb5c91 --- /dev/null +++ b/patches/eth-lib+0.1.29.patch @@ -0,0 +1,19 @@ +diff --git a/node_modules/eth-lib/lib/bytes.js b/node_modules/eth-lib/lib/bytes.js +index 4ca0b97..7509094 100644 +--- a/node_modules/eth-lib/lib/bytes.js ++++ b/node_modules/eth-lib/lib/bytes.js +@@ -1,12 +1,12 @@ + var A = require("./array.js"); ++import Crypto from 'react-native-quick-crypto'; + + var at = function at(bytes, index) { + return parseInt(bytes.slice(index * 2 + 2, index * 2 + 4), 16); + }; + + var random = function random(bytes) { +- var rnd = void 0; +- if (typeof window !== "undefined" && window.crypto && window.crypto.getRandomValues) rnd = window.crypto.getRandomValues(new Uint8Array(bytes));else if (typeof require !== "undefined") rnd = require("c" + "rypto").randomBytes(bytes);else throw "Safe random numbers not available."; ++ var rnd = Crypto.randomBytes(bytes); + var hex = "0x"; + for (var i = 0; i < bytes; ++i) { + hex += ("00" + rnd[i].toString(16)).slice(-2); diff --git a/patches/scryptsy+2.1.0.patch b/patches/scryptsy+2.1.0.patch new file mode 100644 index 000000000..24fab3140 --- /dev/null +++ b/patches/scryptsy+2.1.0.patch @@ -0,0 +1,458 @@ +diff --git a/node_modules/scryptsy/lib/scrypt.js b/node_modules/scryptsy/lib/scrypt.js +index 7d6138b..4cd4fa5 100644 +--- a/node_modules/scryptsy/lib/scrypt.js ++++ b/node_modules/scryptsy/lib/scrypt.js +@@ -1,26 +1,45 @@ +-const crypto = require('crypto') +-const { +- checkAndInit, +- smix +-} = require('./utils') ++/* eslint-disable @typescript-eslint/no-var-requires */ ++import crypto from 'react-native-quick-crypto'; ++const { checkAndInit, smix } = require('./utils'); + + // N = Cpu cost, r = Memory cost, p = parallelization cost +-async function scrypt (key, salt, N, r, p, dkLen, progressCallback, promiseInterval) { +- const { +- XY, +- V, +- B32, +- x, +- _X, +- B, +- tickCallback +- } = checkAndInit(key, salt, N, r, p, dkLen, progressCallback) ++async function scrypt( ++ key, ++ salt, ++ N, ++ r, ++ p, ++ dkLen, ++ progressCallback, ++ promiseInterval ++) { ++ const { XY, V, B32, x, _X, B, tickCallback } = checkAndInit( ++ key, ++ salt, ++ N, ++ r, ++ p, ++ dkLen, ++ progressCallback ++ ); + + for (var i = 0; i < p; i++) { +- await smix(B, i * 128 * r, r, N, V, XY, _X, B32, x, tickCallback, promiseInterval) ++ await smix( ++ B, ++ i * 128 * r, ++ r, ++ N, ++ V, ++ XY, ++ _X, ++ B32, ++ x, ++ tickCallback, ++ promiseInterval ++ ); + } + +- return crypto.pbkdf2Sync(key, B, 1, dkLen, 'sha256') ++ return crypto.pbkdf2Sync(key, B, 1, dkLen, 'sha256'); + } + +-module.exports = scrypt ++module.exports = scrypt; +diff --git a/node_modules/scryptsy/lib/scryptSync.js b/node_modules/scryptsy/lib/scryptSync.js +index f90d688..91aae0e 100644 +--- a/node_modules/scryptsy/lib/scryptSync.js ++++ b/node_modules/scryptsy/lib/scryptSync.js +@@ -1,26 +1,23 @@ +-const crypto = require('crypto') +-const { +- checkAndInit, +- smixSync +-} = require('./utils') ++import crypto from 'react-native-quick-crypto'; ++const { checkAndInit, smixSync } = require('./utils'); + + // N = Cpu cost, r = Memory cost, p = parallelization cost +-function scrypt (key, salt, N, r, p, dkLen, progressCallback) { +- const { +- XY, +- V, +- B32, +- x, +- _X, +- B, +- tickCallback +- } = checkAndInit(key, salt, N, r, p, dkLen, progressCallback) ++function scrypt(key, salt, N, r, p, dkLen, progressCallback) { ++ const { XY, V, B32, x, _X, B, tickCallback } = checkAndInit( ++ key, ++ salt, ++ N, ++ r, ++ p, ++ dkLen, ++ progressCallback ++ ); + + for (var i = 0; i < p; i++) { +- smixSync(B, i * 128 * r, r, N, V, XY, _X, B32, x, tickCallback) ++ smixSync(B, i * 128 * r, r, N, V, XY, _X, B32, x, tickCallback); + } + +- return crypto.pbkdf2Sync(key, B, 1, dkLen, 'sha256') ++ return crypto.pbkdf2Sync(key, B, 1, dkLen, 'sha256'); + } + +-module.exports = scrypt ++module.exports = scrypt; +diff --git a/node_modules/scryptsy/lib/utils.js b/node_modules/scryptsy/lib/utils.js +index bf03795..bb4ac23 100644 +--- a/node_modules/scryptsy/lib/utils.js ++++ b/node_modules/scryptsy/lib/utils.js +@@ -1,32 +1,33 @@ +-const crypto = require('crypto') +-const MAX_VALUE = 0x7fffffff +-const DEFAULT_PROMISE_INTERVAL = 5000 ++import crypto from 'react-native-quick-crypto'; ++const MAX_VALUE = 0x7fffffff; ++const DEFAULT_PROMISE_INTERVAL = 5000; + /* eslint-disable camelcase */ + +-function checkAndInit (key, salt, N, r, p, dkLen, progressCallback) { +- if (N === 0 || (N & (N - 1)) !== 0) throw Error('N must be > 0 and a power of 2') ++function checkAndInit(key, salt, N, r, p, dkLen, progressCallback) { ++ if (N === 0 || (N & (N - 1)) !== 0) ++ throw Error('N must be > 0 and a power of 2'); + +- if (N > MAX_VALUE / 128 / r) throw Error('Parameter N is too large') +- if (r > MAX_VALUE / 128 / p) throw Error('Parameter r is too large') ++ if (N > MAX_VALUE / 128 / r) throw Error('Parameter N is too large'); ++ if (r > MAX_VALUE / 128 / p) throw Error('Parameter r is too large'); + +- let XY = Buffer.alloc(256 * r) +- let V = Buffer.alloc(128 * r * N) ++ let XY = Buffer.alloc(256 * r); ++ let V = Buffer.alloc(128 * r * N); + + // pseudo global +- let B32 = new Int32Array(16) // salsa20_8 +- let x = new Int32Array(16) // salsa20_8 +- let _X = Buffer.alloc(64) // blockmix_salsa8 ++ let B32 = new Int32Array(16); // salsa20_8 ++ let x = new Int32Array(16); // salsa20_8 ++ let _X = Buffer.alloc(64); // blockmix_salsa8 + + // pseudo global +- let B = crypto.pbkdf2Sync(key, salt, 1, p * 128 * r, 'sha256') ++ let B = crypto.pbkdf2Sync(key, salt, 1, p * 128 * r, 'sha256'); + +- let tickCallback ++ let tickCallback; + if (progressCallback) { +- let totalOps = p * N * 2 +- let currentOp = 0 ++ let totalOps = p * N * 2; ++ let currentOp = 0; + + tickCallback = function () { +- ++currentOp ++ ++currentOp; + + // send progress notifications once every 1,000 ops + if (currentOp % 1000 === 0) { +@@ -34,9 +35,9 @@ function checkAndInit (key, salt, N, r, p, dkLen, progressCallback) { + current: currentOp, + total: totalOps, + percent: (currentOp / totalOps) * 100.0 +- }) ++ }); + } +- } ++ }; + } + return { + XY, +@@ -46,165 +47,177 @@ function checkAndInit (key, salt, N, r, p, dkLen, progressCallback) { + _X, + B, + tickCallback +- } ++ }; + } + +-async function smix (B, Bi, r, N, V, XY, _X, B32, x, tickCallback, promiseInterval) { +- promiseInterval = promiseInterval || DEFAULT_PROMISE_INTERVAL +- let Xi = 0 +- let Yi = 128 * r +- let i +- +- B.copy(XY, Xi, Bi, Bi + Yi) ++async function smix( ++ B, ++ Bi, ++ r, ++ N, ++ V, ++ XY, ++ _X, ++ B32, ++ x, ++ tickCallback, ++ promiseInterval ++) { ++ promiseInterval = promiseInterval || DEFAULT_PROMISE_INTERVAL; ++ let Xi = 0; ++ let Yi = 128 * r; ++ let i; ++ ++ B.copy(XY, Xi, Bi, Bi + Yi); + + for (i = 0; i < N; i++) { +- XY.copy(V, i * Yi, Xi, Xi + Yi) ++ XY.copy(V, i * Yi, Xi, Xi + Yi); + if (i % promiseInterval === 0) { +- await new Promise(resolve => setImmediate(resolve)) ++ await new Promise((resolve) => setImmediate(resolve)); + } +- blockmix_salsa8(XY, Xi, Yi, r, _X, B32, x) ++ blockmix_salsa8(XY, Xi, Yi, r, _X, B32, x); + +- if (tickCallback) tickCallback() ++ if (tickCallback) tickCallback(); + } + + for (i = 0; i < N; i++) { +- let offset = Xi + (2 * r - 1) * 64 +- let j = XY.readUInt32LE(offset) & (N - 1) +- blockxor(V, j * Yi, XY, Xi, Yi) ++ let offset = Xi + (2 * r - 1) * 64; ++ let j = XY.readUInt32LE(offset) & (N - 1); ++ blockxor(V, j * Yi, XY, Xi, Yi); + if (i % promiseInterval === 0) { +- await new Promise(resolve => setImmediate(resolve)) ++ await new Promise((resolve) => setImmediate(resolve)); + } +- blockmix_salsa8(XY, Xi, Yi, r, _X, B32, x) ++ blockmix_salsa8(XY, Xi, Yi, r, _X, B32, x); + +- if (tickCallback) tickCallback() ++ if (tickCallback) tickCallback(); + } + +- XY.copy(B, Bi, Xi, Xi + Yi) ++ XY.copy(B, Bi, Xi, Xi + Yi); + } + +-function smixSync (B, Bi, r, N, V, XY, _X, B32, x, tickCallback) { +- let Xi = 0 +- let Yi = 128 * r +- let i ++function smixSync(B, Bi, r, N, V, XY, _X, B32, x, tickCallback) { ++ let Xi = 0; ++ let Yi = 128 * r; ++ let i; + +- B.copy(XY, Xi, Bi, Bi + Yi) ++ B.copy(XY, Xi, Bi, Bi + Yi); + + for (i = 0; i < N; i++) { +- XY.copy(V, i * Yi, Xi, Xi + Yi) +- blockmix_salsa8(XY, Xi, Yi, r, _X, B32, x) ++ XY.copy(V, i * Yi, Xi, Xi + Yi); ++ blockmix_salsa8(XY, Xi, Yi, r, _X, B32, x); + +- if (tickCallback) tickCallback() ++ if (tickCallback) tickCallback(); + } + + for (i = 0; i < N; i++) { +- let offset = Xi + (2 * r - 1) * 64 +- let j = XY.readUInt32LE(offset) & (N - 1) +- blockxor(V, j * Yi, XY, Xi, Yi) +- blockmix_salsa8(XY, Xi, Yi, r, _X, B32, x) ++ let offset = Xi + (2 * r - 1) * 64; ++ let j = XY.readUInt32LE(offset) & (N - 1); ++ blockxor(V, j * Yi, XY, Xi, Yi); ++ blockmix_salsa8(XY, Xi, Yi, r, _X, B32, x); + +- if (tickCallback) tickCallback() ++ if (tickCallback) tickCallback(); + } + +- XY.copy(B, Bi, Xi, Xi + Yi) ++ XY.copy(B, Bi, Xi, Xi + Yi); + } + +-function blockmix_salsa8 (BY, Bi, Yi, r, _X, B32, x) { +- let i ++function blockmix_salsa8(BY, Bi, Yi, r, _X, B32, x) { ++ let i; + +- arraycopy(BY, Bi + (2 * r - 1) * 64, _X, 0, 64) ++ arraycopy(BY, Bi + (2 * r - 1) * 64, _X, 0, 64); + + for (i = 0; i < 2 * r; i++) { +- blockxor(BY, i * 64, _X, 0, 64) +- salsa20_8(_X, B32, x) +- arraycopy(_X, 0, BY, Yi + (i * 64), 64) ++ blockxor(BY, i * 64, _X, 0, 64); ++ salsa20_8(_X, B32, x); ++ arraycopy(_X, 0, BY, Yi + i * 64, 64); + } + + for (i = 0; i < r; i++) { +- arraycopy(BY, Yi + (i * 2) * 64, BY, Bi + (i * 64), 64) ++ arraycopy(BY, Yi + i * 2 * 64, BY, Bi + i * 64, 64); + } + + for (i = 0; i < r; i++) { +- arraycopy(BY, Yi + (i * 2 + 1) * 64, BY, Bi + (i + r) * 64, 64) ++ arraycopy(BY, Yi + (i * 2 + 1) * 64, BY, Bi + (i + r) * 64, 64); + } + } + +-function R (a, b) { +- return (a << b) | (a >>> (32 - b)) ++function R(a, b) { ++ return (a << b) | (a >>> (32 - b)); + } + +-function salsa20_8 (B, B32, x) { +- let i ++function salsa20_8(B, B32, x) { ++ let i; + + for (i = 0; i < 16; i++) { +- B32[i] = (B[i * 4 + 0] & 0xff) << 0 +- B32[i] |= (B[i * 4 + 1] & 0xff) << 8 +- B32[i] |= (B[i * 4 + 2] & 0xff) << 16 +- B32[i] |= (B[i * 4 + 3] & 0xff) << 24 ++ B32[i] = (B[i * 4 + 0] & 0xff) << 0; ++ B32[i] |= (B[i * 4 + 1] & 0xff) << 8; ++ B32[i] |= (B[i * 4 + 2] & 0xff) << 16; ++ B32[i] |= (B[i * 4 + 3] & 0xff) << 24; + // B32[i] = B.readUInt32LE(i*4) <--- this is signficantly slower even in Node.js + } + +- arraycopy(B32, 0, x, 0, 16) ++ arraycopy(B32, 0, x, 0, 16); + + for (i = 8; i > 0; i -= 2) { +- x[4] ^= R(x[0] + x[12], 7) +- x[8] ^= R(x[4] + x[0], 9) +- x[12] ^= R(x[8] + x[4], 13) +- x[0] ^= R(x[12] + x[8], 18) +- x[9] ^= R(x[5] + x[1], 7) +- x[13] ^= R(x[9] + x[5], 9) +- x[1] ^= R(x[13] + x[9], 13) +- x[5] ^= R(x[1] + x[13], 18) +- x[14] ^= R(x[10] + x[6], 7) +- x[2] ^= R(x[14] + x[10], 9) +- x[6] ^= R(x[2] + x[14], 13) +- x[10] ^= R(x[6] + x[2], 18) +- x[3] ^= R(x[15] + x[11], 7) +- x[7] ^= R(x[3] + x[15], 9) +- x[11] ^= R(x[7] + x[3], 13) +- x[15] ^= R(x[11] + x[7], 18) +- x[1] ^= R(x[0] + x[3], 7) +- x[2] ^= R(x[1] + x[0], 9) +- x[3] ^= R(x[2] + x[1], 13) +- x[0] ^= R(x[3] + x[2], 18) +- x[6] ^= R(x[5] + x[4], 7) +- x[7] ^= R(x[6] + x[5], 9) +- x[4] ^= R(x[7] + x[6], 13) +- x[5] ^= R(x[4] + x[7], 18) +- x[11] ^= R(x[10] + x[9], 7) +- x[8] ^= R(x[11] + x[10], 9) +- x[9] ^= R(x[8] + x[11], 13) +- x[10] ^= R(x[9] + x[8], 18) +- x[12] ^= R(x[15] + x[14], 7) +- x[13] ^= R(x[12] + x[15], 9) +- x[14] ^= R(x[13] + x[12], 13) +- x[15] ^= R(x[14] + x[13], 18) ++ x[4] ^= R(x[0] + x[12], 7); ++ x[8] ^= R(x[4] + x[0], 9); ++ x[12] ^= R(x[8] + x[4], 13); ++ x[0] ^= R(x[12] + x[8], 18); ++ x[9] ^= R(x[5] + x[1], 7); ++ x[13] ^= R(x[9] + x[5], 9); ++ x[1] ^= R(x[13] + x[9], 13); ++ x[5] ^= R(x[1] + x[13], 18); ++ x[14] ^= R(x[10] + x[6], 7); ++ x[2] ^= R(x[14] + x[10], 9); ++ x[6] ^= R(x[2] + x[14], 13); ++ x[10] ^= R(x[6] + x[2], 18); ++ x[3] ^= R(x[15] + x[11], 7); ++ x[7] ^= R(x[3] + x[15], 9); ++ x[11] ^= R(x[7] + x[3], 13); ++ x[15] ^= R(x[11] + x[7], 18); ++ x[1] ^= R(x[0] + x[3], 7); ++ x[2] ^= R(x[1] + x[0], 9); ++ x[3] ^= R(x[2] + x[1], 13); ++ x[0] ^= R(x[3] + x[2], 18); ++ x[6] ^= R(x[5] + x[4], 7); ++ x[7] ^= R(x[6] + x[5], 9); ++ x[4] ^= R(x[7] + x[6], 13); ++ x[5] ^= R(x[4] + x[7], 18); ++ x[11] ^= R(x[10] + x[9], 7); ++ x[8] ^= R(x[11] + x[10], 9); ++ x[9] ^= R(x[8] + x[11], 13); ++ x[10] ^= R(x[9] + x[8], 18); ++ x[12] ^= R(x[15] + x[14], 7); ++ x[13] ^= R(x[12] + x[15], 9); ++ x[14] ^= R(x[13] + x[12], 13); ++ x[15] ^= R(x[14] + x[13], 18); + } + +- for (i = 0; i < 16; ++i) B32[i] = x[i] + B32[i] ++ for (i = 0; i < 16; ++i) B32[i] = x[i] + B32[i]; + + for (i = 0; i < 16; i++) { +- let bi = i * 4 +- B[bi + 0] = (B32[i] >> 0 & 0xff) +- B[bi + 1] = (B32[i] >> 8 & 0xff) +- B[bi + 2] = (B32[i] >> 16 & 0xff) +- B[bi + 3] = (B32[i] >> 24 & 0xff) ++ let bi = i * 4; ++ B[bi + 0] = (B32[i] >> 0) & 0xff; ++ B[bi + 1] = (B32[i] >> 8) & 0xff; ++ B[bi + 2] = (B32[i] >> 16) & 0xff; ++ B[bi + 3] = (B32[i] >> 24) & 0xff; + // B.writeInt32LE(B32[i], i*4) //<--- this is signficantly slower even in Node.js + } + } + + // naive approach... going back to loop unrolling may yield additional performance +-function blockxor (S, Si, D, Di, len) { ++function blockxor(S, Si, D, Di, len) { + for (let i = 0; i < len; i++) { +- D[Di + i] ^= S[Si + i] ++ D[Di + i] ^= S[Si + i]; + } + } + +-function arraycopy (src, srcPos, dest, destPos, length) { ++function arraycopy(src, srcPos, dest, destPos, length) { + if (Buffer.isBuffer(src) && Buffer.isBuffer(dest)) { +- src.copy(dest, destPos, srcPos, srcPos + length) ++ src.copy(dest, destPos, srcPos, srcPos + length); + } else { + while (length--) { +- dest[destPos++] = src[srcPos++] ++ dest[destPos++] = src[srcPos++]; + } + } + } +@@ -213,4 +226,4 @@ module.exports = { + checkAndInit, + smix, + smixSync +-} ++}; diff --git a/src/prototypes/global.js b/src/prototypes/global.js index d679c6609..93ddd8aea 100644 --- a/src/prototypes/global.js +++ b/src/prototypes/global.js @@ -1,7 +1,6 @@ /* eslint-disable @typescript-eslint/no-var-requires */ // Inject node globals into React Native global scope. import Crypto from 'react-native-quick-crypto'; - global.Buffer = require('buffer').Buffer; global.process = require('process'); @@ -18,12 +17,14 @@ if (typeof atob === 'undefined') { } if (typeof global.crypto !== 'object') { - global.crypto = Crypto; + // global.crypto = Crypto; } if (typeof window !== 'undefined') { window.crypto = Crypto; + window.crypto.getRandomValues = Crypto.getRandomValues; } + if (typeof global.crypto.getRandomValues !== 'function') { global.crypto.getRandomValues = Crypto.getRandomValues; } diff --git a/yarn.lock b/yarn.lock index 4b466aee5..d7da4ddd8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6093,7 +6093,7 @@ eth-lib@0.2.7: elliptic "^6.4.0" xhr-request-promise "^0.1.2" -eth-lib@^0.1.26: +eth-lib@^0.1.26, eth-lib@^0.1.29: version "0.1.29" resolved "https://registry.yarnpkg.com/eth-lib/-/eth-lib-0.1.29.tgz#0c11f5060d42da9f931eab6199084734f4dbd1d9" integrity sha512-bfttrr3/7gG4E02HoWTDUcDDslN003OlOoBxk9virpAZQ1ja/jDgwkWB8QfJF7ojuEowrqy+lzp9VcJG7/k5bQ== @@ -7542,10 +7542,10 @@ http2-wrapper@^1.0.0-beta.5.2: quick-lru "^5.1.1" resolve-alpn "^1.0.0" -https-browserify@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" - integrity sha512-J+FkSdyD+0mA0N+81tMotaRMfSL9SGi+xpD3T6YApKsc3bGSXJlfXri3VyFOeYkfLRQisDk1W+jIFFKBeUBbBg== +https-browserify@^0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-0.0.1.tgz#3f91365cabe60b77ed0ebba24b454e3e09d95a82" + integrity sha512-EjDQFbgJr1vDD/175UJeSX3ncQ3+RUnCL5NkthQGHvF4VNHlzTy8ifJfTqz47qiPRqaFH58+CbuG3x51WuB1XQ== https-proxy-agent@^5.0.0, https-proxy-agent@^5.0.1: version "5.0.1" From a06e43d26ee19e66564fd004a83af34405205a1b Mon Sep 17 00:00:00 2001 From: illiaa Date: Mon, 14 Aug 2023 17:51:15 +0300 Subject: [PATCH 039/509] fixed error cannot read property "call" of undefined --- src/lib/helpers/AirDAOKeys.ts | 16 +++++++++------ src/lib/helpers/AirDAOKeysForRef.ts | 12 +++++------ .../CreateWallet/CreateWalletStep2.tsx | 4 ++-- src/utils/cashback.ts | 20 ++++++++++--------- src/utils/wallet.ts | 14 +++++++++---- 5 files changed, 39 insertions(+), 27 deletions(-) diff --git a/src/lib/helpers/AirDAOKeys.ts b/src/lib/helpers/AirDAOKeys.ts index 903567d57..158a3436d 100644 --- a/src/lib/helpers/AirDAOKeys.ts +++ b/src/lib/helpers/AirDAOKeys.ts @@ -19,8 +19,11 @@ const bs58check = require('bs58check'); const ETH_CACHE = {}; const CACHE = {}; const CACHE_ROOTS = {}; - +interface CacheRoots { + [key: string]: any; +} class BlocksoftKeys { + private CACHE_ROOTS: CacheRoots = {}; _bipHex: { [key: string]: string }; _getRandomBytesFunction; constructor() { @@ -131,7 +134,7 @@ class BlocksoftKeys { const mnemonicCache = data.mnemonic.toLowerCase(); let bitcoinRoot = false; let currencyCode; - let settings; + let settings: any; const seed = await KeysUtills.bip39MnemonicToSeed( data.mnemonic.toLowerCase() ); @@ -375,17 +378,18 @@ class BlocksoftKeys { return seed; } - async getBip32Cached(mnemonic, network = false, seed = false) { + async getBip32Cached(mnemonic: string, network = false, seed = false) { const mnemonicCache = mnemonic.toLowerCase() + '_' + (network || 'btc'); - if (typeof CACHE_ROOTS[mnemonicCache] === 'undefined') { + if (typeof this.CACHE_ROOTS[mnemonicCache] === 'undefined') { if (!seed) { seed = await this.getSeedCached(mnemonic); } - CACHE_ROOTS[mnemonicCache] = network + const root = network ? bip32.fromSeed(seed, network) : bip32.fromSeed(seed); + this.CACHE_ROOTS[mnemonicCache] = root; } - return CACHE_ROOTS[mnemonicCache]; + return this.CACHE_ROOTS[mnemonicCache]; } getEthCached(mnemonicCache) { diff --git a/src/lib/helpers/AirDAOKeysForRef.ts b/src/lib/helpers/AirDAOKeysForRef.ts index 8b70ef731..dfeb9900a 100644 --- a/src/lib/helpers/AirDAOKeysForRef.ts +++ b/src/lib/helpers/AirDAOKeysForRef.ts @@ -4,7 +4,7 @@ */ import AirDAOKeys from './AirDAOKeys'; import { WalletUtils } from '@utils/wallet'; -import AirDAODispatcher from '@crypto/blockchains/AirDAODispatcher'; +import AirDAODispatcher from '../../../crypto/blockchains/AirDAODispatcher'; const CACHE: { [key: string]: any } = {}; @@ -24,15 +24,14 @@ class AirDAOKeysForRef { const mnemonicCache = data.mnemonic.toLowerCase(); if (typeof CACHE[mnemonicCache] !== 'undefined') return CACHE[mnemonicCache]; - // let result = AirDAOKeys.getEthCached(mnemonicCache); - let result; + let result = AirDAOKeys.getEthCached(mnemonicCache); + // let result; if (!result) { let index = 0; if (typeof data.index !== 'undefined') { index = data.index; } - // const root = await AirDAOKeys.getBip32Cached(data.mnemonic); - let root; + const root = await AirDAOKeys.getBip32Cached(data.mnemonic); const path = `m/44'/60'/${index}'/0/0`; const child = root.derivePath(path); const processor = await AirDAODispatcher.getAddressProcessor('ETH'); @@ -40,12 +39,13 @@ class AirDAOKeysForRef { result.index = index; result.path = path; if (index === 0) { - // AirDAOKeys.setEthCached(data.mnemonic, result); + AirDAOKeys.setEthCached(data.mnemonic, result); } } // noinspection JSPrimitiveTypeWrapperUsage result.cashbackToken = WalletUtils.addressToToken(result.address); CACHE[mnemonicCache] = result; + console.log(result, 'result'); return result; } diff --git a/src/screens/CreateWallet/CreateWalletStep2.tsx b/src/screens/CreateWallet/CreateWalletStep2.tsx index a10b15e0b..97edf584e 100644 --- a/src/screens/CreateWallet/CreateWalletStep2.tsx +++ b/src/screens/CreateWallet/CreateWalletStep2.tsx @@ -38,7 +38,7 @@ export const CreateWalletStep2 = () => { // Alert.alert('Failed'); // return; } - console.log('here'); + // console.log('here'); // setLoading(true); // TODO fix number await WalletUtils.processWallet({ @@ -50,7 +50,7 @@ export const CreateWalletStep2 = () => { }, [walletMnemonic, walletMnemonicArrayDefault, walletMnemonicSelected]); useEffect(() => { - console.log('here'); + // console.log('here'); validateMnemonic(); }, [walletMnemonicSelected, validateMnemonic]); diff --git a/src/utils/cashback.ts b/src/utils/cashback.ts index f34f36e6f..1de2619ae 100644 --- a/src/utils/cashback.ts +++ b/src/utils/cashback.ts @@ -1,6 +1,6 @@ import AirDAOKeysStorage from '@lib/helpers/AirDAOKeysStorage'; import AirDAOKeysForRefStorage from '@lib/helpers/AirDAOKeysForRefStorage'; -import AirDAOKeysForRef from '@lib/helpers/AirDAOKeysForRef'; +import AirDAOKeysForRef from '../lib/helpers/AirDAOKeysForRef'; const getByHash = async (tmpHash: string) => { let tmpPublicAndPrivateResult = @@ -26,17 +26,19 @@ const getByHash = async (tmpHash: string) => { mnemonic } ); - console.log({ tmpPublicAndPrivateResult }); + console.log(tmpPublicAndPrivateResult, 'dasd'); } catch (error) { - console.log({ error }); + console.log(error, 'error here'); } // // await Log.log('SRV/CashBack getByHash done discoverPublic ' + tmpHash + ' => ' + tmpPublicAndPrivateResult.cashbackToken) - // try { - // await AirDAOKeysForRefStorage.setPublicAndPrivateResultForHash( - // tmpHash, - // tmpPublicAndPrivateResult - // ); - // } catch (e) {} + try { + await AirDAOKeysForRefStorage.setPublicAndPrivateResultForHash( + tmpHash, + tmpPublicAndPrivateResult + ); + } catch (e: any) { + console.log(e); + } return tmpPublicAndPrivateResult; }; diff --git a/src/utils/wallet.ts b/src/utils/wallet.ts index 4e2d6fec0..292a8204e 100644 --- a/src/utils/wallet.ts +++ b/src/utils/wallet.ts @@ -45,13 +45,19 @@ const processWallet = async ( if (!tmpWalletName || tmpWalletName === '') { tmpWalletName = await _getWalletName(); } - const fullWallet: Wallet = new Wallet({ ...data, hash, name: tmpWalletName }); - console.log({ fullWallet }); + const fullWallet: Wallet = new Wallet({ + pub: '', + hash, + ...data, + name: tmpWalletName + }); + // console.log({ fullWallet }); const { cashbackToken } = await CashBackUtils.getByHash(hash); - console.log({ cashbackToken }); + console.log({ cashbackToken }, 'cashbackToken'); // TODO save to local db - // await Wallet.saveWallet(fullWallet); + await Wallet.saveWallet(fullWallet); try { + // console.log(fullWallet); } catch (error) { throw error; } From a65494ad06c3157581ecfad4be070a3f6537f6fd Mon Sep 17 00:00:00 2001 From: JavidHaji-zada Date: Tue, 15 Aug 2023 11:18:41 +0400 Subject: [PATCH 040/509] wallet creation works --- package.json | 2 +- src/lib/helpers/AirDAOKeysForRef.ts | 1 - src/screens/CreateWallet/CreateWalletStep2.tsx | 5 ----- src/utils/cashback.ts | 2 +- yarn.lock | 15 --------------- 5 files changed, 2 insertions(+), 23 deletions(-) diff --git a/package.json b/package.json index c218f43f7..2781650b4 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,7 @@ "base-x": "^4.0.0", "bech32": "^2.0.0", "bignumber.js": "^9.1.1", - "bip32": "^4.0.0", + "bip32": "^2.0.4", "bip39": "^3.1.0", "bitcoinjs-lib": "git+https://git@github.com/trustee-wallet/bitcoinjs-lib", "bn.js": "^5.2.1", diff --git a/src/lib/helpers/AirDAOKeysForRef.ts b/src/lib/helpers/AirDAOKeysForRef.ts index dfeb9900a..68929556a 100644 --- a/src/lib/helpers/AirDAOKeysForRef.ts +++ b/src/lib/helpers/AirDAOKeysForRef.ts @@ -45,7 +45,6 @@ class AirDAOKeysForRef { // noinspection JSPrimitiveTypeWrapperUsage result.cashbackToken = WalletUtils.addressToToken(result.address); CACHE[mnemonicCache] = result; - console.log(result, 'result'); return result; } diff --git a/src/screens/CreateWallet/CreateWalletStep2.tsx b/src/screens/CreateWallet/CreateWalletStep2.tsx index 97edf584e..290f67039 100644 --- a/src/screens/CreateWallet/CreateWalletStep2.tsx +++ b/src/screens/CreateWallet/CreateWalletStep2.tsx @@ -21,11 +21,6 @@ export const CreateWalletStep2 = () => { // eslint-disable-next-line react-hooks/exhaustive-deps [walletMnemonicArrayDefault.length] ); - console.log({ - walletMnemonic, - walletMnemonicArrayDefault: walletMnemonicArrayDefault.length, - walletMnemonicSelected: walletMnemonicSelected.length - }); const validateMnemonic = useCallback(async () => { if (walletMnemonicSelected.length !== walletMnemonicArrayDefault.length) { diff --git a/src/utils/cashback.ts b/src/utils/cashback.ts index 1de2619ae..705d516a0 100644 --- a/src/utils/cashback.ts +++ b/src/utils/cashback.ts @@ -28,7 +28,7 @@ const getByHash = async (tmpHash: string) => { ); console.log(tmpPublicAndPrivateResult, 'dasd'); } catch (error) { - console.log(error, 'error here'); + // ignore } // // await Log.log('SRV/CashBack getByHash done discoverPublic ' + tmpHash + ' => ' + tmpPublicAndPrivateResult.cashbackToken) try { diff --git a/yarn.lock b/yarn.lock index d7da4ddd8..04c057e04 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2644,11 +2644,6 @@ dependencies: nanoid "^3.1.23" -"@scure/base@^1.1.1": - version "1.1.1" - resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.1.tgz#ebb651ee52ff84f420097055f4bf46cfba403938" - integrity sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA== - "@segment/loosely-validate-event@^2.0.0": version "2.0.0" resolved "https://registry.npmjs.org/@segment/loosely-validate-event/-/loosely-validate-event-2.0.0.tgz" @@ -4020,16 +4015,6 @@ bip32@^2.0.4: typeforce "^1.11.5" wif "^2.0.6" -bip32@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/bip32/-/bip32-4.0.0.tgz#7fac3c05072188d2d355a4d6596b37188f06aa2f" - integrity sha512-aOGy88DDlVUhspIXJN+dVEtclhIsfAUppD43V0j40cPTld3pv/0X/MlrZSZ6jowIaQQzFwP8M6rFU2z2mVYjDQ== - dependencies: - "@noble/hashes" "^1.2.0" - "@scure/base" "^1.1.1" - typeforce "^1.11.5" - wif "^2.0.6" - bip39@^3.0.2, bip39@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/bip39/-/bip39-3.1.0.tgz#c55a418deaf48826a6ceb34ac55b3ee1577e18a3" From 72b297c0256637ded93368555799288866c4c2a7 Mon Sep 17 00:00:00 2001 From: illiaa Date: Tue, 15 Aug 2023 15:38:35 +0300 Subject: [PATCH 041/509] changed creating eth address to amb address --- crypto/blockchains/eth/EthAddressProcessor.ts | 1 + src/contexts/AddWallet/AddWallet.types.ts | 2 ++ src/contexts/AddWallet/index.tsx | 3 +++ src/lib/helpers/AirDAOKeysForRef.ts | 4 ++-- src/screens/CreateWallet/CreateWalletStep1.tsx | 3 ++- src/screens/CreateWallet/CreateWalletStep2.tsx | 1 - src/utils/cashback.ts | 1 - src/utils/wallet.ts | 8 +++++--- 8 files changed, 15 insertions(+), 8 deletions(-) diff --git a/crypto/blockchains/eth/EthAddressProcessor.ts b/crypto/blockchains/eth/EthAddressProcessor.ts index 86196ebbc..0beba0128 100644 --- a/crypto/blockchains/eth/EthAddressProcessor.ts +++ b/crypto/blockchains/eth/EthAddressProcessor.ts @@ -16,6 +16,7 @@ export default class EthAddressProcessor extends EthBasic { privateKey = '0x' + privateKey.toString('hex'); // noinspection JSUnresolvedVariable const account = this._web3.eth.accounts.privateKeyToAccount(privateKey); + console.log(account.address, 'account.address'); return { address: account.address, privateKey, addedData: false }; } diff --git a/src/contexts/AddWallet/AddWallet.types.ts b/src/contexts/AddWallet/AddWallet.types.ts index a48bfe95f..9c931fc25 100644 --- a/src/contexts/AddWallet/AddWallet.types.ts +++ b/src/contexts/AddWallet/AddWallet.types.ts @@ -4,6 +4,8 @@ export enum AddWalletFlowType { } export interface AddWalletContextState { + network: string; + setNetwork: React.Dispatch>; flowType: AddWalletFlowType; setFlowType: React.Dispatch>; walletHash: string; diff --git a/src/contexts/AddWallet/index.tsx b/src/contexts/AddWallet/index.tsx index bb285dd02..84b336f32 100644 --- a/src/contexts/AddWallet/index.tsx +++ b/src/contexts/AddWallet/index.tsx @@ -8,9 +8,12 @@ const AddWalletContext = (): AddWalletContextState => { ); const [walletHash, setWalletHash] = useState(''); const [walletName, setWalletName] = useState(''); + const [network, setNetwork] = useState('ETH'); const [walletMnemonic, setWalletMnemonic] = useState(''); const [mnemonicLength, setMnemonicLength] = useState(0); return { + network, + setNetwork, flowType, setFlowType, walletHash, diff --git a/src/lib/helpers/AirDAOKeysForRef.ts b/src/lib/helpers/AirDAOKeysForRef.ts index 68929556a..ef79758ce 100644 --- a/src/lib/helpers/AirDAOKeysForRef.ts +++ b/src/lib/helpers/AirDAOKeysForRef.ts @@ -32,9 +32,9 @@ class AirDAOKeysForRef { index = data.index; } const root = await AirDAOKeys.getBip32Cached(data.mnemonic); - const path = `m/44'/60'/${index}'/0/0`; + const path = `m/44'/16718'/${index}'/0/0`; const child = root.derivePath(path); - const processor = await AirDAODispatcher.getAddressProcessor('ETH'); + const processor = await AirDAODispatcher.getAddressProcessor('AMB'); result = await processor.getAddress(child.privateKey); result.index = index; result.path = path; diff --git a/src/screens/CreateWallet/CreateWalletStep1.tsx b/src/screens/CreateWallet/CreateWalletStep1.tsx index 6762b8dc8..c11b43d99 100644 --- a/src/screens/CreateWallet/CreateWalletStep1.tsx +++ b/src/screens/CreateWallet/CreateWalletStep1.tsx @@ -14,11 +14,12 @@ import { COLORS } from '@constants/colors'; export const CreateWalletStep1 = () => { const navigation = useNavigation(); const [loading, setLoading] = useState(false); - const { walletMnemonic, mnemonicLength, setWalletMnemonic } = + const { walletMnemonic, mnemonicLength, setWalletMnemonic, setNetwork } = useAddWalletContext(); const walletMnemonicArray = walletMnemonic.split(' '); const init = async () => { + setNetwork('AMB'); setLoading(true); // create mnemonic const walletMnemonic = ( diff --git a/src/screens/CreateWallet/CreateWalletStep2.tsx b/src/screens/CreateWallet/CreateWalletStep2.tsx index 290f67039..8ff8c9aee 100644 --- a/src/screens/CreateWallet/CreateWalletStep2.tsx +++ b/src/screens/CreateWallet/CreateWalletStep2.tsx @@ -7,7 +7,6 @@ import { useAddWalletContext } from '@contexts'; import { moderateScale, scale, verticalScale } from '@utils/scaling'; import { COLORS } from '@constants/colors'; import { WalletUtils } from '@utils/wallet'; -import { useIgnoreInitialMountEffect } from '@hooks'; export const CreateWalletStep2 = () => { const { walletMnemonic } = useAddWalletContext(); diff --git a/src/utils/cashback.ts b/src/utils/cashback.ts index 705d516a0..c8e213ac2 100644 --- a/src/utils/cashback.ts +++ b/src/utils/cashback.ts @@ -26,7 +26,6 @@ const getByHash = async (tmpHash: string) => { mnemonic } ); - console.log(tmpPublicAndPrivateResult, 'dasd'); } catch (error) { // ignore } diff --git a/src/utils/wallet.ts b/src/utils/wallet.ts index 292a8204e..675b13bb4 100644 --- a/src/utils/wallet.ts +++ b/src/utils/wallet.ts @@ -18,10 +18,12 @@ const _saveWallet = async (wallet: WalletMetadata) => { const checkKey = await AirDAOStorage.isMnemonicAlreadySaved(prepared); if (checkKey) { - // @misha should we do something or ui is enough + // TODO } storedKey = await AirDAOStorage.saveMnemonic(prepared); - } catch (e) {} + } catch (e) { + console.log(e); + } return storedKey; }; @@ -57,7 +59,7 @@ const processWallet = async ( // TODO save to local db await Wallet.saveWallet(fullWallet); try { - // console.log(fullWallet); + console.log(fullWallet); } catch (error) { throw error; } From 89d745d40d6c199f9a07844cc617b6f507566321 Mon Sep 17 00:00:00 2001 From: illiaa Date: Tue, 15 Aug 2023 17:49:42 +0300 Subject: [PATCH 042/509] refactored to create both amb and ethereum addresses --- src/lib/helpers/AirDAOKeysForRef.ts | 18 ++++++++++++++-- .../CreateWallet/CreateWalletStep1.tsx | 21 +++++++++++++++---- 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/src/lib/helpers/AirDAOKeysForRef.ts b/src/lib/helpers/AirDAOKeysForRef.ts index ef79758ce..87b01c287 100644 --- a/src/lib/helpers/AirDAOKeysForRef.ts +++ b/src/lib/helpers/AirDAOKeysForRef.ts @@ -28,13 +28,27 @@ class AirDAOKeysForRef { // let result; if (!result) { let index = 0; + const network = 'AMB'; if (typeof data.index !== 'undefined') { index = data.index; } const root = await AirDAOKeys.getBip32Cached(data.mnemonic); - const path = `m/44'/16718'/${index}'/0/0`; + let path; + if (network === 'AMB') { + path = `m/44'/16718'/${index}'/0/0`; + } else if (network === 'ETH') { + path = `m/44'/60'/${index}'/0/0`; + } const child = root.derivePath(path); - const processor = await AirDAODispatcher.getAddressProcessor('AMB'); + let processor; + if (!processor) { + throw new Error('Processor not initialized'); + } + if (network === 'AMB') { + processor = await AirDAODispatcher.getAddressProcessor('AMB'); + } else if (network === 'ETH') { + processor = await AirDAODispatcher.getAddressProcessor('ETH'); + } result = await processor.getAddress(child.privateKey); result.index = index; result.path = path; diff --git a/src/screens/CreateWallet/CreateWalletStep1.tsx b/src/screens/CreateWallet/CreateWalletStep1.tsx index c11b43d99..52634ccd9 100644 --- a/src/screens/CreateWallet/CreateWalletStep1.tsx +++ b/src/screens/CreateWallet/CreateWalletStep1.tsx @@ -19,9 +19,9 @@ export const CreateWalletStep1 = () => { const walletMnemonicArray = walletMnemonic.split(' '); const init = async () => { - setNetwork('AMB'); setLoading(true); // create mnemonic + // tslint:disable-next-line:no-shadowed-variable const walletMnemonic = ( await MnemonicUtils.generateNewMnemonic(mnemonicLength) ).mnemonic; @@ -46,7 +46,12 @@ export const CreateWalletStep1 = () => { ); }; - const onNextPress = () => { + const onNextPress = (network: string) => { + if (network === 'AMB') { + setNetwork('AMB'); + } else { + setNetwork('ETH'); + } navigation.navigate('CreateWalletStep2'); }; @@ -74,8 +79,16 @@ export const CreateWalletStep1 = () => { )} - - Next + onNextPress('ETH')}> + + Create ETH wallet + + + + onNextPress('AMB')}> + + Create AMB wallet + From 87890fb888d94bc5531293388c3fae95822375f8 Mon Sep 17 00:00:00 2001 From: illiaa Date: Wed, 16 Aug 2023 12:44:57 +0300 Subject: [PATCH 043/509] reverted changes --- src/lib/helpers/AirDAOKeysForRef.ts | 18 ++-------------- .../CreateWallet/CreateWalletStep1.tsx | 21 ++++--------------- 2 files changed, 6 insertions(+), 33 deletions(-) diff --git a/src/lib/helpers/AirDAOKeysForRef.ts b/src/lib/helpers/AirDAOKeysForRef.ts index 87b01c287..ef79758ce 100644 --- a/src/lib/helpers/AirDAOKeysForRef.ts +++ b/src/lib/helpers/AirDAOKeysForRef.ts @@ -28,27 +28,13 @@ class AirDAOKeysForRef { // let result; if (!result) { let index = 0; - const network = 'AMB'; if (typeof data.index !== 'undefined') { index = data.index; } const root = await AirDAOKeys.getBip32Cached(data.mnemonic); - let path; - if (network === 'AMB') { - path = `m/44'/16718'/${index}'/0/0`; - } else if (network === 'ETH') { - path = `m/44'/60'/${index}'/0/0`; - } + const path = `m/44'/16718'/${index}'/0/0`; const child = root.derivePath(path); - let processor; - if (!processor) { - throw new Error('Processor not initialized'); - } - if (network === 'AMB') { - processor = await AirDAODispatcher.getAddressProcessor('AMB'); - } else if (network === 'ETH') { - processor = await AirDAODispatcher.getAddressProcessor('ETH'); - } + const processor = await AirDAODispatcher.getAddressProcessor('AMB'); result = await processor.getAddress(child.privateKey); result.index = index; result.path = path; diff --git a/src/screens/CreateWallet/CreateWalletStep1.tsx b/src/screens/CreateWallet/CreateWalletStep1.tsx index 52634ccd9..c11b43d99 100644 --- a/src/screens/CreateWallet/CreateWalletStep1.tsx +++ b/src/screens/CreateWallet/CreateWalletStep1.tsx @@ -19,9 +19,9 @@ export const CreateWalletStep1 = () => { const walletMnemonicArray = walletMnemonic.split(' '); const init = async () => { + setNetwork('AMB'); setLoading(true); // create mnemonic - // tslint:disable-next-line:no-shadowed-variable const walletMnemonic = ( await MnemonicUtils.generateNewMnemonic(mnemonicLength) ).mnemonic; @@ -46,12 +46,7 @@ export const CreateWalletStep1 = () => { ); }; - const onNextPress = (network: string) => { - if (network === 'AMB') { - setNetwork('AMB'); - } else { - setNetwork('ETH'); - } + const onNextPress = () => { navigation.navigate('CreateWalletStep2'); }; @@ -79,16 +74,8 @@ export const CreateWalletStep1 = () => { )} - onNextPress('ETH')}> - - Create ETH wallet - - - - onNextPress('AMB')}> - - Create AMB wallet - + + Next From 9471b2a145a07413930adf72d316de2ab6012ea9 Mon Sep 17 00:00:00 2001 From: illiaa Date: Wed, 16 Aug 2023 12:45:59 +0300 Subject: [PATCH 044/509] reverted changes --- crypto/blockchains/eth/EthAddressProcessor.ts | 1 - src/contexts/AddWallet/AddWallet.types.ts | 2 -- src/contexts/AddWallet/index.tsx | 3 --- src/lib/helpers/AirDAOKeysForRef.ts | 4 ++-- src/screens/CreateWallet/CreateWalletStep1.tsx | 3 +-- src/screens/CreateWallet/CreateWalletStep2.tsx | 1 + src/utils/cashback.ts | 1 + src/utils/wallet.ts | 8 +++----- 8 files changed, 8 insertions(+), 15 deletions(-) diff --git a/crypto/blockchains/eth/EthAddressProcessor.ts b/crypto/blockchains/eth/EthAddressProcessor.ts index 0beba0128..86196ebbc 100644 --- a/crypto/blockchains/eth/EthAddressProcessor.ts +++ b/crypto/blockchains/eth/EthAddressProcessor.ts @@ -16,7 +16,6 @@ export default class EthAddressProcessor extends EthBasic { privateKey = '0x' + privateKey.toString('hex'); // noinspection JSUnresolvedVariable const account = this._web3.eth.accounts.privateKeyToAccount(privateKey); - console.log(account.address, 'account.address'); return { address: account.address, privateKey, addedData: false }; } diff --git a/src/contexts/AddWallet/AddWallet.types.ts b/src/contexts/AddWallet/AddWallet.types.ts index 9c931fc25..a48bfe95f 100644 --- a/src/contexts/AddWallet/AddWallet.types.ts +++ b/src/contexts/AddWallet/AddWallet.types.ts @@ -4,8 +4,6 @@ export enum AddWalletFlowType { } export interface AddWalletContextState { - network: string; - setNetwork: React.Dispatch>; flowType: AddWalletFlowType; setFlowType: React.Dispatch>; walletHash: string; diff --git a/src/contexts/AddWallet/index.tsx b/src/contexts/AddWallet/index.tsx index 84b336f32..bb285dd02 100644 --- a/src/contexts/AddWallet/index.tsx +++ b/src/contexts/AddWallet/index.tsx @@ -8,12 +8,9 @@ const AddWalletContext = (): AddWalletContextState => { ); const [walletHash, setWalletHash] = useState(''); const [walletName, setWalletName] = useState(''); - const [network, setNetwork] = useState('ETH'); const [walletMnemonic, setWalletMnemonic] = useState(''); const [mnemonicLength, setMnemonicLength] = useState(0); return { - network, - setNetwork, flowType, setFlowType, walletHash, diff --git a/src/lib/helpers/AirDAOKeysForRef.ts b/src/lib/helpers/AirDAOKeysForRef.ts index ef79758ce..68929556a 100644 --- a/src/lib/helpers/AirDAOKeysForRef.ts +++ b/src/lib/helpers/AirDAOKeysForRef.ts @@ -32,9 +32,9 @@ class AirDAOKeysForRef { index = data.index; } const root = await AirDAOKeys.getBip32Cached(data.mnemonic); - const path = `m/44'/16718'/${index}'/0/0`; + const path = `m/44'/60'/${index}'/0/0`; const child = root.derivePath(path); - const processor = await AirDAODispatcher.getAddressProcessor('AMB'); + const processor = await AirDAODispatcher.getAddressProcessor('ETH'); result = await processor.getAddress(child.privateKey); result.index = index; result.path = path; diff --git a/src/screens/CreateWallet/CreateWalletStep1.tsx b/src/screens/CreateWallet/CreateWalletStep1.tsx index c11b43d99..6762b8dc8 100644 --- a/src/screens/CreateWallet/CreateWalletStep1.tsx +++ b/src/screens/CreateWallet/CreateWalletStep1.tsx @@ -14,12 +14,11 @@ import { COLORS } from '@constants/colors'; export const CreateWalletStep1 = () => { const navigation = useNavigation(); const [loading, setLoading] = useState(false); - const { walletMnemonic, mnemonicLength, setWalletMnemonic, setNetwork } = + const { walletMnemonic, mnemonicLength, setWalletMnemonic } = useAddWalletContext(); const walletMnemonicArray = walletMnemonic.split(' '); const init = async () => { - setNetwork('AMB'); setLoading(true); // create mnemonic const walletMnemonic = ( diff --git a/src/screens/CreateWallet/CreateWalletStep2.tsx b/src/screens/CreateWallet/CreateWalletStep2.tsx index 8ff8c9aee..290f67039 100644 --- a/src/screens/CreateWallet/CreateWalletStep2.tsx +++ b/src/screens/CreateWallet/CreateWalletStep2.tsx @@ -7,6 +7,7 @@ import { useAddWalletContext } from '@contexts'; import { moderateScale, scale, verticalScale } from '@utils/scaling'; import { COLORS } from '@constants/colors'; import { WalletUtils } from '@utils/wallet'; +import { useIgnoreInitialMountEffect } from '@hooks'; export const CreateWalletStep2 = () => { const { walletMnemonic } = useAddWalletContext(); diff --git a/src/utils/cashback.ts b/src/utils/cashback.ts index c8e213ac2..705d516a0 100644 --- a/src/utils/cashback.ts +++ b/src/utils/cashback.ts @@ -26,6 +26,7 @@ const getByHash = async (tmpHash: string) => { mnemonic } ); + console.log(tmpPublicAndPrivateResult, 'dasd'); } catch (error) { // ignore } diff --git a/src/utils/wallet.ts b/src/utils/wallet.ts index 675b13bb4..292a8204e 100644 --- a/src/utils/wallet.ts +++ b/src/utils/wallet.ts @@ -18,12 +18,10 @@ const _saveWallet = async (wallet: WalletMetadata) => { const checkKey = await AirDAOStorage.isMnemonicAlreadySaved(prepared); if (checkKey) { - // TODO + // @misha should we do something or ui is enough } storedKey = await AirDAOStorage.saveMnemonic(prepared); - } catch (e) { - console.log(e); - } + } catch (e) {} return storedKey; }; @@ -59,7 +57,7 @@ const processWallet = async ( // TODO save to local db await Wallet.saveWallet(fullWallet); try { - console.log(fullWallet); + // console.log(fullWallet); } catch (error) { throw error; } From d4d1b8b66e01f6030267517e7481736c3f3f539b Mon Sep 17 00:00:00 2001 From: illiaa Date: Wed, 16 Aug 2023 15:47:12 +0300 Subject: [PATCH 045/509] fixed ui and added copying for created address --- src/appTypes/navigation/add-wallet.ts | 8 ++-- src/appTypes/navigation/tabs.ts | 1 + src/components/svg/BottomTabIcons/Wallet.tsx | 23 ++++++++++++ src/components/svg/BottomTabIcons/index.ts | 1 + src/navigation/components/TabBar/index.tsx | 9 ++++- src/navigation/stacks/Tabs/SettingsStack.tsx | 2 - .../{AddWalletStack.tsx => WalletStack.tsx} | 16 ++++---- src/navigation/stacks/TabsNavigator.tsx | 16 ++++++-- .../CreateWallet/CreateWalletStep2.tsx | 27 +++++++++++--- src/screens/Settings/index.tsx | 13 +------ .../{AddWallet => WalletScreen}/index.tsx | 37 +++++++++++++------ .../{AddWallet => WalletScreen}/styles.ts | 3 ++ src/utils/cashback.ts | 8 +++- src/utils/navigation.tsx | 3 +- src/utils/wallet.ts | 5 ++- 15 files changed, 121 insertions(+), 51 deletions(-) create mode 100644 src/components/svg/BottomTabIcons/Wallet.tsx rename src/navigation/stacks/Tabs/{AddWalletStack.tsx => WalletStack.tsx} (58%) rename src/screens/{AddWallet => WalletScreen}/index.tsx (51%) rename src/screens/{AddWallet => WalletScreen}/styles.ts (71%) diff --git a/src/appTypes/navigation/add-wallet.ts b/src/appTypes/navigation/add-wallet.ts index c38975422..aca5d3f0f 100644 --- a/src/appTypes/navigation/add-wallet.ts +++ b/src/appTypes/navigation/add-wallet.ts @@ -3,14 +3,14 @@ import { CompositeNavigationProp } from '@react-navigation/native'; import { TabsParamsList } from './tabs'; import { NativeStackNavigationProp } from '@react-navigation/native-stack'; -export type AddWalletStackParamsList = { - AddWalletScreen: undefined; +export type AddressStackParamsList = { + WalletScreen: undefined; RestoreWalletScreen: undefined; CreateWalletStep1: undefined; CreateWalletStep2: undefined; }; export type AddWalletStackNavigationProp = CompositeNavigationProp< - BottomTabNavigationProp, - NativeStackNavigationProp + BottomTabNavigationProp, + NativeStackNavigationProp >; diff --git a/src/appTypes/navigation/tabs.ts b/src/appTypes/navigation/tabs.ts index 040da8259..bd0458d0f 100644 --- a/src/appTypes/navigation/tabs.ts +++ b/src/appTypes/navigation/tabs.ts @@ -8,6 +8,7 @@ export type TabsParamsList = { Search: NavigatorScreenParams; Portfolio: NavigatorScreenParams; Settings: undefined; + Wallet: undefined; Tabs: { screen: string }; }; diff --git a/src/components/svg/BottomTabIcons/Wallet.tsx b/src/components/svg/BottomTabIcons/Wallet.tsx new file mode 100644 index 000000000..6cd542c8c --- /dev/null +++ b/src/components/svg/BottomTabIcons/Wallet.tsx @@ -0,0 +1,23 @@ +import React from 'react'; +import { Path, Svg } from 'react-native-svg'; +import { IconProps } from '@components/svg/icons'; + +export function WalletTabIcon(props: IconProps) { + const { scale = 1, color = '#457EFF' } = props; + const width = 24; + const height = 24; + return ( + + + + ); +} diff --git a/src/components/svg/BottomTabIcons/index.ts b/src/components/svg/BottomTabIcons/index.ts index f4a9eb678..556f29799 100644 --- a/src/components/svg/BottomTabIcons/index.ts +++ b/src/components/svg/BottomTabIcons/index.ts @@ -2,3 +2,4 @@ export * from './Overview'; export * from './Watchlist'; export * from './Explore'; export * from './Settings'; +export * from './Wallet'; diff --git a/src/navigation/components/TabBar/index.tsx b/src/navigation/components/TabBar/index.tsx index 4f3fc1be3..c31321ed5 100644 --- a/src/navigation/components/TabBar/index.tsx +++ b/src/navigation/components/TabBar/index.tsx @@ -15,10 +15,11 @@ import { ExploreTabIcon, WatchlistTabIcon, OverviewTabIcon, - SettingsTabIcon + SettingsTabIcon, + WalletTabIcon } from '@components/svg/BottomTabIcons'; -type LabelType = 'Settings' | 'Portfolio' | 'Search' | 'Wallets'; +type LabelType = 'Settings' | 'Portfolio' | 'Search' | 'Wallets' | 'Wallet'; const tabs = { Wallets: { inactiveIcon: , @@ -35,6 +36,10 @@ const tabs = { Settings: { inactiveIcon: , activeIcon: + }, + Wallet: { + inactiveIcon: , + activeIcon: } }; diff --git a/src/navigation/stacks/Tabs/SettingsStack.tsx b/src/navigation/stacks/Tabs/SettingsStack.tsx index 778ee17c0..795ba079e 100644 --- a/src/navigation/stacks/Tabs/SettingsStack.tsx +++ b/src/navigation/stacks/Tabs/SettingsStack.tsx @@ -2,7 +2,6 @@ import React from 'react'; import { createNativeStackNavigator } from '@react-navigation/native-stack'; import { SettingsScreen } from '@screens/Settings'; import { SettingsTabParamsList } from '@appTypes'; -import AddWalletStack from './AddWalletStack'; const Stack = createNativeStackNavigator(); export const SettingsStack = () => { @@ -12,7 +11,6 @@ export const SettingsStack = () => { initialRouteName="SettingsScreen" > - ); }; diff --git a/src/navigation/stacks/Tabs/AddWalletStack.tsx b/src/navigation/stacks/Tabs/WalletStack.tsx similarity index 58% rename from src/navigation/stacks/Tabs/AddWalletStack.tsx rename to src/navigation/stacks/Tabs/WalletStack.tsx index 8b70f7d14..a4977acb2 100644 --- a/src/navigation/stacks/Tabs/AddWalletStack.tsx +++ b/src/navigation/stacks/Tabs/WalletStack.tsx @@ -1,26 +1,28 @@ import React from 'react'; import { createNativeStackNavigator } from '@react-navigation/native-stack'; -import { AddWalletStackParamsList } from '@appTypes'; -import { AddWalletScreen } from '@screens/AddWallet'; +import { AddressStackParamsList } from '@appTypes'; +import { WalletScreen } from '@screens/WalletScreen'; import { CreateWalletStep1, CreateWalletStep2 } from '@screens/CreateWallet'; import { RestoreWalletScreen } from '@screens/RestoreWallet'; +import { getCommonStack } from '@navigation/stacks/CommonStack'; -const Stack = createNativeStackNavigator(); -export const AddWalletStack = () => { +const Stack = createNativeStackNavigator(); +export const WalletStack = () => { return ( - + + {getCommonStack(Stack as any)} ); }; -export default AddWalletStack; +export default WalletStack; diff --git a/src/navigation/stacks/TabsNavigator.tsx b/src/navigation/stacks/TabsNavigator.tsx index 1ca18ffad..b883b10b7 100644 --- a/src/navigation/stacks/TabsNavigator.tsx +++ b/src/navigation/stacks/TabsNavigator.tsx @@ -1,11 +1,12 @@ import React from 'react'; import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; +import { TabsParamsList } from '@appTypes/navigation/tabs'; import { PortfolioStack } from './Tabs/PortfolioStack'; +import TabBar from '@navigation/components/TabBar'; import SettingsStack from './Tabs/SettingsStack'; +import WalletStack from './Tabs/WalletStack'; import SearchStack from './Tabs/SearchStack'; import HomeStack from './Tabs/HomeStack'; -import TabBar from '@navigation/components/TabBar'; -import { TabsParamsList } from '@appTypes/navigation/tabs'; const BottomTabs = createBottomTabNavigator(); @@ -33,7 +34,16 @@ export const TabsNavigator = () => { component={SearchStack} options={{ tabBarLabel: 'Explore' }} /> - + + ); }; diff --git a/src/screens/CreateWallet/CreateWalletStep2.tsx b/src/screens/CreateWallet/CreateWalletStep2.tsx index 290f67039..3f9983b71 100644 --- a/src/screens/CreateWallet/CreateWalletStep2.tsx +++ b/src/screens/CreateWallet/CreateWalletStep2.tsx @@ -1,20 +1,21 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react'; -import { Alert, StyleSheet, View } from 'react-native'; +import { StyleSheet, View } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; import { Button, Row, Spacer, Spinner, Text } from '@components/base'; -import { Header } from '@components/composite'; +import { CopyToClipboardButton, Header } from '@components/composite'; import { useAddWalletContext } from '@contexts'; import { moderateScale, scale, verticalScale } from '@utils/scaling'; import { COLORS } from '@constants/colors'; import { WalletUtils } from '@utils/wallet'; -import { useIgnoreInitialMountEffect } from '@hooks'; +import { StringUtils } from '@utils/string'; export const CreateWalletStep2 = () => { const { walletMnemonic } = useAddWalletContext(); const [walletMnemonicSelected, setWalletMnemonicSelected] = useState< string[] >([]); - const [loading, setLoading] = useState(false); + const [loading, setLoading] = useState(false); + const [addressToCopy, setAddressToCopy] = useState(''); const walletMnemonicArrayDefault = walletMnemonic.split(' '); const walletMnemonicRandomSorted = useMemo( () => walletMnemonicArrayDefault.sort(() => 0.5 - Math.random()), @@ -36,12 +37,13 @@ export const CreateWalletStep2 = () => { // console.log('here'); // setLoading(true); // TODO fix number - await WalletUtils.processWallet({ + const { address } = await WalletUtils.processWallet({ number: 0, mnemonic: walletMnemonic, name: '' }); - // setLoading(false); + console.log(address); + setAddressToCopy(address); }, [walletMnemonic, walletMnemonicArrayDefault, walletMnemonicSelected]); useEffect(() => { @@ -84,6 +86,19 @@ export const CreateWalletStep2 = () => { .map(renderWord)} )} + + {addressToCopy.length > 0 && ( + + )} + ); diff --git a/src/screens/Settings/index.tsx b/src/screens/Settings/index.tsx index 7df2ee220..964a2100f 100644 --- a/src/screens/Settings/index.tsx +++ b/src/screens/Settings/index.tsx @@ -5,26 +5,17 @@ import { COLORS } from '@constants/colors'; import { SettingsInfoBlock } from '@screens/Settings/components/SettingsInfoBlock'; import { scale, verticalScale } from '@utils/scaling'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; -import { Spacer, Text } from '@components/base'; -import { PrimaryButton } from '@components/modular'; -import { useNavigation } from '@react-navigation/native'; -import { SettingsTabNavigationProp } from '@appTypes'; +import { Spacer } from '@components/base'; export const SettingsScreen = () => { const { top } = useSafeAreaInsets(); - const navigation = useNavigation(); - const navigateToWalletConnect = () => { - navigation.navigate('AddWalletStack', { screen: 'AddWalletScreen' }); - }; + return ( - - Go to Wallet Connect - ); }; diff --git a/src/screens/AddWallet/index.tsx b/src/screens/WalletScreen/index.tsx similarity index 51% rename from src/screens/AddWallet/index.tsx rename to src/screens/WalletScreen/index.tsx index 19ed70c62..77261c05f 100644 --- a/src/screens/AddWallet/index.tsx +++ b/src/screens/WalletScreen/index.tsx @@ -1,14 +1,17 @@ import React from 'react'; import { SafeAreaView } from 'react-native-safe-area-context'; -import { styles } from './styles'; import { Header } from '@components/composite'; -import { Button, Spacer, Text } from '@components/base'; -import { verticalScale } from '@utils/scaling'; +import { Spacer, Text } from '@components/base'; +import { scale, verticalScale } from '@utils/scaling'; import { useNavigation } from '@react-navigation/native'; import { AddWalletStackNavigationProp } from '@appTypes'; import { AddWalletFlowType, useAddWalletContext } from '@contexts'; +import { styles } from '@screens/WalletScreen/styles'; +import { COLORS } from '@constants/colors'; +import { PrimaryButton } from '@components/modular'; +import { View } from 'react-native'; -export const AddWalletScreen = () => { +export const WalletScreen = () => { const navigation = useNavigation(); const { setFlowType, setWalletName, setMnemonicLength } = useAddWalletContext(); @@ -29,15 +32,25 @@ export const AddWalletScreen = () => { return ( -
+
- - - + + + + Create address + + + + + + Restore address + + + ); }; diff --git a/src/screens/AddWallet/styles.ts b/src/screens/WalletScreen/styles.ts similarity index 71% rename from src/screens/AddWallet/styles.ts rename to src/screens/WalletScreen/styles.ts index 8aaa60e9b..52facc9ac 100644 --- a/src/screens/AddWallet/styles.ts +++ b/src/screens/WalletScreen/styles.ts @@ -1,6 +1,9 @@ import { StyleSheet } from 'react-native'; export const styles = StyleSheet.create({ + header: { + shadowColor: 'transparent' + }, container: { flex: 1 } diff --git a/src/utils/cashback.ts b/src/utils/cashback.ts index 705d516a0..5d0be0d06 100644 --- a/src/utils/cashback.ts +++ b/src/utils/cashback.ts @@ -5,6 +5,7 @@ import AirDAOKeysForRef from '../lib/helpers/AirDAOKeysForRef'; const getByHash = async (tmpHash: string) => { let tmpPublicAndPrivateResult = await AirDAOKeysForRefStorage.getPublicAndPrivateResultForHash(tmpHash); + let result; if ( tmpPublicAndPrivateResult && typeof tmpPublicAndPrivateResult.cashbackToken !== 'undefined' @@ -26,6 +27,7 @@ const getByHash = async (tmpHash: string) => { mnemonic } ); + result = JSON.parse(JSON.stringify(tmpPublicAndPrivateResult)); console.log(tmpPublicAndPrivateResult, 'dasd'); } catch (error) { // ignore @@ -39,7 +41,11 @@ const getByHash = async (tmpHash: string) => { } catch (e: any) { console.log(e); } - return tmpPublicAndPrivateResult; + const { cashbackToken } = tmpPublicAndPrivateResult; + return { + cashbackToken, + tmpPublicAndPrivateResult: result + }; }; export const CashBackUtils = { getByHash }; diff --git a/src/utils/navigation.tsx b/src/utils/navigation.tsx index 30e1faaf3..e1a8593da 100644 --- a/src/utils/navigation.tsx +++ b/src/utils/navigation.tsx @@ -2,7 +2,8 @@ const TabVisibleRoutes = [ 'HomeScreen', 'PortfolioScreen', 'SearchScreen', - 'SettingsScreen' + 'SettingsScreen', + 'WalletScreen' ]; const getTabBarVisibility = (route: string): boolean => { diff --git a/src/utils/wallet.ts b/src/utils/wallet.ts index 292a8204e..87d7ec792 100644 --- a/src/utils/wallet.ts +++ b/src/utils/wallet.ts @@ -52,8 +52,7 @@ const processWallet = async ( name: tmpWalletName }); // console.log({ fullWallet }); - const { cashbackToken } = await CashBackUtils.getByHash(hash); - console.log({ cashbackToken }, 'cashbackToken'); + const { tmpPublicAndPrivateResult } = await CashBackUtils.getByHash(hash); // TODO save to local db await Wallet.saveWallet(fullWallet); try { @@ -61,6 +60,8 @@ const processWallet = async ( } catch (error) { throw error; } + const { address } = tmpPublicAndPrivateResult; + return { address }; }; export const WalletUtils = { processWallet, addressToToken }; From 1c5c90ab8994cec50d8ae3a35cf18eacec6c15e0 Mon Sep 17 00:00:00 2001 From: illiaa Date: Wed, 16 Aug 2023 20:19:46 +0300 Subject: [PATCH 046/509] started implementing new design --- src/appTypes/navigation/add-wallet.ts | 5 +- src/components/svg/icons/Elipse.tsx | 20 ++++ src/navigation/stacks/Tabs/WalletStack.tsx | 15 ++- .../Wallet/CreateWallet/CreateWalletStep0.tsx | 110 ++++++++++++++++++ .../CreateWallet/CreateWalletStep1.tsx | 0 .../CreateWallet/CreateWalletStep2.tsx | 0 .../{ => Wallet}/CreateWallet/index.ts | 1 + .../{ => Wallet}/RestoreWallet/index.tsx | 2 +- .../{ => Wallet}/RestoreWallet/styles.ts | 0 .../{WalletScreen => Wallet}/index.tsx | 9 +- .../{WalletScreen => Wallet}/styles.ts | 0 11 files changed, 150 insertions(+), 12 deletions(-) create mode 100644 src/components/svg/icons/Elipse.tsx create mode 100644 src/screens/Wallet/CreateWallet/CreateWalletStep0.tsx rename src/screens/{ => Wallet}/CreateWallet/CreateWalletStep1.tsx (100%) rename src/screens/{ => Wallet}/CreateWallet/CreateWalletStep2.tsx (100%) rename src/screens/{ => Wallet}/CreateWallet/index.ts (66%) rename src/screens/{ => Wallet}/RestoreWallet/index.tsx (99%) rename src/screens/{ => Wallet}/RestoreWallet/styles.ts (100%) rename src/screens/{WalletScreen => Wallet}/index.tsx (88%) rename src/screens/{WalletScreen => Wallet}/styles.ts (100%) diff --git a/src/appTypes/navigation/add-wallet.ts b/src/appTypes/navigation/add-wallet.ts index aca5d3f0f..c3d728fd4 100644 --- a/src/appTypes/navigation/add-wallet.ts +++ b/src/appTypes/navigation/add-wallet.ts @@ -3,14 +3,15 @@ import { CompositeNavigationProp } from '@react-navigation/native'; import { TabsParamsList } from './tabs'; import { NativeStackNavigationProp } from '@react-navigation/native-stack'; -export type AddressStackParamsList = { +export type WalletStackParamsList = { WalletScreen: undefined; RestoreWalletScreen: undefined; + CreateWalletStep0: undefined; CreateWalletStep1: undefined; CreateWalletStep2: undefined; }; export type AddWalletStackNavigationProp = CompositeNavigationProp< BottomTabNavigationProp, - NativeStackNavigationProp + NativeStackNavigationProp >; diff --git a/src/components/svg/icons/Elipse.tsx b/src/components/svg/icons/Elipse.tsx new file mode 100644 index 000000000..04a755f80 --- /dev/null +++ b/src/components/svg/icons/Elipse.tsx @@ -0,0 +1,20 @@ +import React from 'react'; +import { IconProps } from '@components/svg/icons/Icon.types'; +import { Circle, Svg } from 'react-native-svg'; + +export function ElipseIcon(props: IconProps) { + const { scale = 1, color = '#D9D9D9' } = props; + const width = 193; + const height = 192; + return ( + + + + ); +} diff --git a/src/navigation/stacks/Tabs/WalletStack.tsx b/src/navigation/stacks/Tabs/WalletStack.tsx index a4977acb2..fb49f72dd 100644 --- a/src/navigation/stacks/Tabs/WalletStack.tsx +++ b/src/navigation/stacks/Tabs/WalletStack.tsx @@ -1,12 +1,16 @@ import React from 'react'; import { createNativeStackNavigator } from '@react-navigation/native-stack'; -import { AddressStackParamsList } from '@appTypes'; -import { WalletScreen } from '@screens/WalletScreen'; -import { CreateWalletStep1, CreateWalletStep2 } from '@screens/CreateWallet'; -import { RestoreWalletScreen } from '@screens/RestoreWallet'; +import { WalletStackParamsList } from '@appTypes'; +import { WalletScreen } from '@screens/Wallet'; +import { + CreateWalletStep1, + CreateWalletStep2, + CreateWalletStep0 +} from '@screens/Wallet/CreateWallet'; +import { RestoreWalletScreen } from '@screens/Wallet/RestoreWallet'; import { getCommonStack } from '@navigation/stacks/CommonStack'; -const Stack = createNativeStackNavigator(); +const Stack = createNativeStackNavigator(); export const WalletStack = () => { return ( { initialRouteName="WalletScreen" > + ( + + {children} + +); + +export const CreateWalletStep0 = () => { + const { top } = useSafeAreaInsets(); + const [selected, setSelected] = useState(false); + return ( + +
+ Create new wallet + + } + style={{ shadowColor: 'transparent' }} + /> + + + Backup your wallet + + + + + Your wallet will be backed up with a{' '} + + + + + + Make sure you have a pen and paper ready so you can write it down. + + + + + + + + + + + I understand that if I lose my recovery phrase, AirDAO cannot + restore it. + + + + + + + ); +}; diff --git a/src/screens/CreateWallet/CreateWalletStep1.tsx b/src/screens/Wallet/CreateWallet/CreateWalletStep1.tsx similarity index 100% rename from src/screens/CreateWallet/CreateWalletStep1.tsx rename to src/screens/Wallet/CreateWallet/CreateWalletStep1.tsx diff --git a/src/screens/CreateWallet/CreateWalletStep2.tsx b/src/screens/Wallet/CreateWallet/CreateWalletStep2.tsx similarity index 100% rename from src/screens/CreateWallet/CreateWalletStep2.tsx rename to src/screens/Wallet/CreateWallet/CreateWalletStep2.tsx diff --git a/src/screens/CreateWallet/index.ts b/src/screens/Wallet/CreateWallet/index.ts similarity index 66% rename from src/screens/CreateWallet/index.ts rename to src/screens/Wallet/CreateWallet/index.ts index b3e35991e..f4a221263 100644 --- a/src/screens/CreateWallet/index.ts +++ b/src/screens/Wallet/CreateWallet/index.ts @@ -1,2 +1,3 @@ +export * from './CreateWalletStep0'; export * from './CreateWalletStep1'; export * from './CreateWalletStep2'; diff --git a/src/screens/RestoreWallet/index.tsx b/src/screens/Wallet/RestoreWallet/index.tsx similarity index 99% rename from src/screens/RestoreWallet/index.tsx rename to src/screens/Wallet/RestoreWallet/index.tsx index 7d62c3a02..f2c803978 100644 --- a/src/screens/RestoreWallet/index.tsx +++ b/src/screens/Wallet/RestoreWallet/index.tsx @@ -17,7 +17,7 @@ import { etherumAddressRegex } from '@constants/regex'; import { useNavigation } from '@react-navigation/native'; import { AddWalletStackNavigationProp } from '@appTypes'; import { useAddWalletContext } from '@contexts'; -import { styles } from '@screens/RestoreWallet/styles'; +import { styles } from './styles'; export const RestoreWalletScreen = () => { const inputRef = useRef(null); diff --git a/src/screens/RestoreWallet/styles.ts b/src/screens/Wallet/RestoreWallet/styles.ts similarity index 100% rename from src/screens/RestoreWallet/styles.ts rename to src/screens/Wallet/RestoreWallet/styles.ts diff --git a/src/screens/WalletScreen/index.tsx b/src/screens/Wallet/index.tsx similarity index 88% rename from src/screens/WalletScreen/index.tsx rename to src/screens/Wallet/index.tsx index 77261c05f..037620530 100644 --- a/src/screens/WalletScreen/index.tsx +++ b/src/screens/Wallet/index.tsx @@ -1,12 +1,12 @@ import React from 'react'; -import { SafeAreaView } from 'react-native-safe-area-context'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { Header } from '@components/composite'; import { Spacer, Text } from '@components/base'; import { scale, verticalScale } from '@utils/scaling'; import { useNavigation } from '@react-navigation/native'; import { AddWalletStackNavigationProp } from '@appTypes'; import { AddWalletFlowType, useAddWalletContext } from '@contexts'; -import { styles } from '@screens/WalletScreen/styles'; +import { styles } from '@screens/Wallet/styles'; import { COLORS } from '@constants/colors'; import { PrimaryButton } from '@components/modular'; import { View } from 'react-native'; @@ -15,6 +15,7 @@ export const WalletScreen = () => { const navigation = useNavigation(); const { setFlowType, setWalletName, setMnemonicLength } = useAddWalletContext(); + const { top } = useSafeAreaInsets(); const onCreatePress = () => { setFlowType(AddWalletFlowType.CREATE_WALLET); @@ -31,7 +32,7 @@ export const WalletScreen = () => { }; return ( - +
{ - + ); }; diff --git a/src/screens/WalletScreen/styles.ts b/src/screens/Wallet/styles.ts similarity index 100% rename from src/screens/WalletScreen/styles.ts rename to src/screens/Wallet/styles.ts From a748f9ea871931e9df2132b2edbe16838b486507 Mon Sep 17 00:00:00 2001 From: JavidHaji-zada Date: Wed, 16 Aug 2023 22:12:29 +0400 Subject: [PATCH 047/509] remove reset from Database init --- src/database/Database.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/database/Database.ts b/src/database/Database.ts index 6cf5deec6..fa907ed23 100644 --- a/src/database/Database.ts +++ b/src/database/Database.ts @@ -27,7 +27,6 @@ class Database { private async init() { this.db = database; - this.reset(); } getDatabase() { From 2c5568d65908def06f45053e810ce14e694324dc Mon Sep 17 00:00:00 2001 From: JavidHaji-zada Date: Wed, 16 Aug 2023 22:12:48 +0400 Subject: [PATCH 048/509] switch address creation to Ambrosus network --- src/lib/helpers/AirDAOKeysForRef.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/helpers/AirDAOKeysForRef.ts b/src/lib/helpers/AirDAOKeysForRef.ts index 68929556a..fbe40145c 100644 --- a/src/lib/helpers/AirDAOKeysForRef.ts +++ b/src/lib/helpers/AirDAOKeysForRef.ts @@ -34,7 +34,7 @@ class AirDAOKeysForRef { const root = await AirDAOKeys.getBip32Cached(data.mnemonic); const path = `m/44'/60'/${index}'/0/0`; const child = root.derivePath(path); - const processor = await AirDAODispatcher.getAddressProcessor('ETH'); + const processor = await AirDAODispatcher.getAddressProcessor('AMB'); result = await processor.getAddress(child.privateKey); result.index = index; result.path = path; @@ -49,7 +49,7 @@ class AirDAOKeysForRef { } async signDataForApi(msg: string, privateKey: string) { - const processor = await AirDAODispatcher.getAddressProcessor('ETH'); + const processor = await AirDAODispatcher.getAddressProcessor('AMB'); if (privateKey.substring(0, 2) !== '0x') { privateKey = '0x' + privateKey; } From 05ca5681120a9eba4c103746067821e8a73c803e Mon Sep 17 00:00:00 2001 From: JavidHaji-zada Date: Wed, 16 Aug 2023 22:13:47 +0400 Subject: [PATCH 049/509] [FIX] save cashback of wallet to local db --- src/utils/{blockchain.js => blockchain.ts} | 15 ++++++++++++--- src/utils/wallet.ts | 4 +++- 2 files changed, 15 insertions(+), 4 deletions(-) rename src/utils/{blockchain.js => blockchain.ts} (95%) diff --git a/src/utils/blockchain.js b/src/utils/blockchain.ts similarity index 95% rename from src/utils/blockchain.js rename to src/utils/blockchain.ts index cbfce6ee5..caafb29eb 100644 --- a/src/utils/blockchain.js +++ b/src/utils/blockchain.ts @@ -1,6 +1,8 @@ import { NativeModules } from 'react-native'; import CoinAirDAODict from '@crypto/assets/coinAirDAODict.json'; +import { Database } from '@database'; +import { DatabaseTable } from '@appTypes'; const { RNFastCrypto } = NativeModules; @@ -203,11 +205,18 @@ function getCurrencyAllSettings(currencyCodeOrObject, source = '') { return false; } if (currencyCode === 'ETH_LAND') { - Database.query(`DELETE FROM account WHERE currency_code='ETH_LAND'`); - Database.query( + Database.unsafeRawQuery( + DatabaseTable.Accounts, + `DELETE FROM account WHERE currency_code='ETH_LAND'` + ); + Database.unsafeRawQuery( + DatabaseTable.AccountBalances, `DELETE FROM account_balance WHERE currency_code='ETH_LAND'` ); - Database.query(`DELETE FROM currency WHERE currency_code='ETH_LAND'`); + Database.unsafeRawQuery( + DatabaseTable.Currencies, + `DELETE FROM currency WHERE currency_code='ETH_LAND'` + ); } if (typeof currencyCodeOrObject.currencyCode !== 'undefined') { diff --git a/src/utils/wallet.ts b/src/utils/wallet.ts index 87d7ec792..bcb73e495 100644 --- a/src/utils/wallet.ts +++ b/src/utils/wallet.ts @@ -52,8 +52,10 @@ const processWallet = async ( name: tmpWalletName }); // console.log({ fullWallet }); - const { tmpPublicAndPrivateResult } = await CashBackUtils.getByHash(hash); + const { tmpPublicAndPrivateResult, cashbackToken } = + await CashBackUtils.getByHash(hash); // TODO save to local db + fullWallet.cashback = cashbackToken; await Wallet.saveWallet(fullWallet); try { // console.log(fullWallet); From 048d4ae72e410791d64305646008ec1a3df853d0 Mon Sep 17 00:00:00 2001 From: JavidHaji-zada Date: Wed, 16 Aug 2023 23:08:59 +0400 Subject: [PATCH 050/509] created POC account details screen --- src/appTypes/navigation/add-wallet.ts | 2 + src/lib/helpers/AirDAOKeysForRef.ts | 12 +- src/navigation/stacks/Tabs/WalletStack.tsx | 2 + src/screens/Wallet/Account/index.tsx | 148 ++++++++++++++++++ .../Wallet/CreateWallet/CreateWalletStep2.tsx | 3 +- src/screens/Wallet/index.tsx | 64 ++++++-- src/utils/cashback.ts | 6 +- 7 files changed, 213 insertions(+), 24 deletions(-) create mode 100644 src/screens/Wallet/Account/index.tsx diff --git a/src/appTypes/navigation/add-wallet.ts b/src/appTypes/navigation/add-wallet.ts index c3d728fd4..5cd88625a 100644 --- a/src/appTypes/navigation/add-wallet.ts +++ b/src/appTypes/navigation/add-wallet.ts @@ -2,9 +2,11 @@ import { BottomTabNavigationProp } from '@react-navigation/bottom-tabs'; import { CompositeNavigationProp } from '@react-navigation/native'; import { TabsParamsList } from './tabs'; import { NativeStackNavigationProp } from '@react-navigation/native-stack'; +import { Wallet } from '@models/Wallet'; export type WalletStackParamsList = { WalletScreen: undefined; + WalletAccount: { wallet: Wallet }; RestoreWalletScreen: undefined; CreateWalletStep0: undefined; CreateWalletStep1: undefined; diff --git a/src/lib/helpers/AirDAOKeysForRef.ts b/src/lib/helpers/AirDAOKeysForRef.ts index fbe40145c..77bea132b 100644 --- a/src/lib/helpers/AirDAOKeysForRef.ts +++ b/src/lib/helpers/AirDAOKeysForRef.ts @@ -13,13 +13,11 @@ class AirDAOKeysForRef { mnemonic: string; index?: number; }): Promise<{ - currencyCode: { - address: string; - privateKey: string; - path: string; - index: number; - type: unknown; // TODO - }[]; + address: string; + privateKey: string; + path: string; + index: number; + type: unknown; // TODO }> { const mnemonicCache = data.mnemonic.toLowerCase(); if (typeof CACHE[mnemonicCache] !== 'undefined') diff --git a/src/navigation/stacks/Tabs/WalletStack.tsx b/src/navigation/stacks/Tabs/WalletStack.tsx index fb49f72dd..3767656d2 100644 --- a/src/navigation/stacks/Tabs/WalletStack.tsx +++ b/src/navigation/stacks/Tabs/WalletStack.tsx @@ -9,6 +9,7 @@ import { } from '@screens/Wallet/CreateWallet'; import { RestoreWalletScreen } from '@screens/Wallet/RestoreWallet'; import { getCommonStack } from '@navigation/stacks/CommonStack'; +import { WalletAccount } from '@screens/Wallet/Account'; const Stack = createNativeStackNavigator(); export const WalletStack = () => { @@ -18,6 +19,7 @@ export const WalletStack = () => { initialRouteName="WalletScreen" > + diff --git a/src/screens/Wallet/Account/index.tsx b/src/screens/Wallet/Account/index.tsx new file mode 100644 index 000000000..fc94669e0 --- /dev/null +++ b/src/screens/Wallet/Account/index.tsx @@ -0,0 +1,148 @@ +import React, { PropsWithChildren, useEffect, useRef, useState } from 'react'; +import { ListRenderItemInfo, View } from 'react-native'; +import { SafeAreaView } from 'react-native-safe-area-context'; +import { RouteProp, useRoute } from '@react-navigation/native'; +import { + AccountTransactions, + ExplorerAccountTransactionItem +} from '@components/templates'; +import { CopyToClipboardButton, Header } from '@components/composite'; +import { WalletStackParamsList } from '@appTypes'; +import { Row, Spacer, Spinner, Text } from '@components/base'; +import AirDAOKeysForRef from '@lib/helpers/AirDAOKeysForRef'; +import { API } from '@api/api'; +import { ExplorerAccount, Transaction } from '@models'; +import { StringUtils } from '@utils/string'; +import { scale, verticalScale } from '@utils/scaling'; + +const Layout = (props: PropsWithChildren) => { + const route = useRoute>(); + const { wallet } = route.params; + return ( + +
+ {props.children} + + ); +}; + +const LIMIT = 25; + +export const WalletAccount = () => { + const route = useRoute>(); + const { wallet } = route.params; + + const [error, setError] = useState(''); + const [account, setAccount] = useState(null); + const [transactions, setTransactions] = useState([]); + const transactionsRef = useRef(transactions); + const [accountInfoLoading, setAccountInfoLoading] = useState(false); + const [transactionsLoading, setTransactionsLoading] = useState(false); + + const getTransactions = async (address: string) => { + if (transactions.length > 0 && transactions.length < LIMIT) return; + setTransactionsLoading(true); + try { + const transactions = await API.explorerService.getTransactionsOfAccount( + address, + (transactionsRef.current.length % LIMIT) + 1, + LIMIT + ); + setTransactions( + transactions.data.map( + (transactionDTO) => new Transaction(transactionDTO) + ) + ); + } catch (error) { + } finally { + setTransactionsLoading(false); + } + }; + + const getWalletInfo = async () => { + setError(''); + try { + const info = await AirDAOKeysForRef.discoverPublicAndPrivate({ + mnemonic: wallet.mnemonic + }); + if (info) { + const { address } = info; + // @ts-ignore + setAccount({ address, ambBalance: 0 }); + getTransactions(address); + setAccountInfoLoading(true); + const account = await API.explorerService.searchAddress(address); + setAccount(new ExplorerAccount(account)); + } + } catch (error) { + // setError('Could not get wallet details'); + } finally { + setAccountInfoLoading(false); + } + }; + + useEffect(() => { + getWalletInfo(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + if (!wallet) { + return ( + + Wallet is not provided + + ); + } + + if (error !== '') { + return ( + + {error} + + ); + } + + if (accountInfoLoading || !account) { + return ( + + + + ); + } + + const renderTransaction = ( + args: ListRenderItemInfo + ): JSX.Element => { + return ( + + ); + }; + + return ( + + + + + Address + + + + AMB Balance + {account.ambBalance} AMB + + + getTransactions(account.address)} + /> + + + ); +}; diff --git a/src/screens/Wallet/CreateWallet/CreateWalletStep2.tsx b/src/screens/Wallet/CreateWallet/CreateWalletStep2.tsx index 3f9983b71..f26ad7cc4 100644 --- a/src/screens/Wallet/CreateWallet/CreateWalletStep2.tsx +++ b/src/screens/Wallet/CreateWallet/CreateWalletStep2.tsx @@ -49,7 +49,8 @@ export const CreateWalletStep2 = () => { useEffect(() => { // console.log('here'); validateMnemonic(); - }, [walletMnemonicSelected, validateMnemonic]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [walletMnemonicSelected]); const renderWord = (word: string) => { const selectedIdx = walletMnemonicSelected.indexOf(word); diff --git a/src/screens/Wallet/index.tsx b/src/screens/Wallet/index.tsx index 037620530..abf072cbb 100644 --- a/src/screens/Wallet/index.tsx +++ b/src/screens/Wallet/index.tsx @@ -1,21 +1,44 @@ -import React from 'react'; +import React, { useEffect, useState } from 'react'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; +import { FlatList, ListRenderItemInfo, View } from 'react-native'; import { Header } from '@components/composite'; -import { Spacer, Text } from '@components/base'; +import { Button, Spacer, Text } from '@components/base'; import { scale, verticalScale } from '@utils/scaling'; import { useNavigation } from '@react-navigation/native'; -import { AddWalletStackNavigationProp } from '@appTypes'; +import { AddWalletStackNavigationProp, DatabaseTable } from '@appTypes'; import { AddWalletFlowType, useAddWalletContext } from '@contexts'; import { styles } from '@screens/Wallet/styles'; import { COLORS } from '@constants/colors'; import { PrimaryButton } from '@components/modular'; -import { View } from 'react-native'; +import { Database, WalletDBModel } from '@database'; +import { Wallet } from '@models/Wallet'; export const WalletScreen = () => { const navigation = useNavigation(); const { setFlowType, setWalletName, setMnemonicLength } = useAddWalletContext(); const { top } = useSafeAreaInsets(); + const [wallets, setWallets] = useState([]); + + const fetchLocalWallets = async () => { + try { + const _wallets = (await Database.query( + DatabaseTable.Wallets + )) as WalletDBModel[]; + if (_wallets) { + const mappedWallets = _wallets.map((dbWallet) => + Wallet.fromDBModel(dbWallet) + ); + setWallets(mappedWallets); + } + } catch (error) { + // TODO + } + }; + + useEffect(() => { + fetchLocalWallets(); + }, []); const onCreatePress = () => { setFlowType(AddWalletFlowType.CREATE_WALLET); @@ -24,11 +47,25 @@ export const WalletScreen = () => { navigation.navigate('CreateWalletStep1'); }; - const onRestorePress = () => { - setFlowType(AddWalletFlowType.RESTORE_WALLET); - setWalletName(''); - setMnemonicLength(128); - navigation.navigate('RestoreWalletScreen'); + // const onRestorePress = () => { + // setFlowType(AddWalletFlowType.RESTORE_WALLET); + // setWalletName(''); + // setMnemonicLength(128); + // navigation.navigate('RestoreWalletScreen'); + // }; + + const renderWallet = (args: ListRenderItemInfo) => { + const { item, index } = args; + const onPress = () => { + navigation.navigate('WalletAccount', { wallet: item }); + }; + return ( + + ); }; return ( @@ -46,12 +83,17 @@ export const WalletScreen = () => { - + {/* Restore address - + */} + w.hash} + renderItem={renderWallet} + /> ); }; diff --git a/src/utils/cashback.ts b/src/utils/cashback.ts index 5d0be0d06..6af0dc793 100644 --- a/src/utils/cashback.ts +++ b/src/utils/cashback.ts @@ -15,7 +15,6 @@ const getByHash = async (tmpHash: string) => { } // await Log.log('SRV/CashBack getByHash need to discoverPublic', tmpHash) const mnemonic = await AirDAOKeysStorage.getWalletMnemonic(tmpHash); - console.log({ mnemonic }); if (!mnemonic) { return false; @@ -28,7 +27,6 @@ const getByHash = async (tmpHash: string) => { } ); result = JSON.parse(JSON.stringify(tmpPublicAndPrivateResult)); - console.log(tmpPublicAndPrivateResult, 'dasd'); } catch (error) { // ignore } @@ -38,9 +36,7 @@ const getByHash = async (tmpHash: string) => { tmpHash, tmpPublicAndPrivateResult ); - } catch (e: any) { - console.log(e); - } + } catch (e: any) {} const { cashbackToken } = tmpPublicAndPrivateResult; return { cashbackToken, From 268f34dc615917df98399793b017e5ef72c4f8eb Mon Sep 17 00:00:00 2001 From: JavidHaji-zada Date: Thu, 17 Aug 2023 02:00:47 +0400 Subject: [PATCH 051/509] created POC receipt screen --- src/appTypes/navigation/add-wallet.ts | 7 ++- src/navigation/stacks/Tabs/WalletStack.tsx | 2 + src/screens/Wallet/Account/index.tsx | 68 ++++++++++++++++------ src/screens/Wallet/Receipt/index.tsx | 16 +++++ 4 files changed, 71 insertions(+), 22 deletions(-) create mode 100644 src/screens/Wallet/Receipt/index.tsx diff --git a/src/appTypes/navigation/add-wallet.ts b/src/appTypes/navigation/add-wallet.ts index 5cd88625a..0f682442b 100644 --- a/src/appTypes/navigation/add-wallet.ts +++ b/src/appTypes/navigation/add-wallet.ts @@ -5,12 +5,13 @@ import { NativeStackNavigationProp } from '@react-navigation/native-stack'; import { Wallet } from '@models/Wallet'; export type WalletStackParamsList = { - WalletScreen: undefined; - WalletAccount: { wallet: Wallet }; - RestoreWalletScreen: undefined; CreateWalletStep0: undefined; CreateWalletStep1: undefined; CreateWalletStep2: undefined; + ReceiptScreen: { amount: number; currencyCode: string; destination: string }; + RestoreWalletScreen: undefined; + WalletScreen: undefined; + WalletAccount: { wallet: Wallet }; }; export type AddWalletStackNavigationProp = CompositeNavigationProp< diff --git a/src/navigation/stacks/Tabs/WalletStack.tsx b/src/navigation/stacks/Tabs/WalletStack.tsx index 3767656d2..5a30a94b7 100644 --- a/src/navigation/stacks/Tabs/WalletStack.tsx +++ b/src/navigation/stacks/Tabs/WalletStack.tsx @@ -10,6 +10,7 @@ import { import { RestoreWalletScreen } from '@screens/Wallet/RestoreWallet'; import { getCommonStack } from '@navigation/stacks/CommonStack'; import { WalletAccount } from '@screens/Wallet/Account'; +import { ReceiptScreen } from '@screens/Wallet/Receipt'; const Stack = createNativeStackNavigator(); export const WalletStack = () => { @@ -19,6 +20,7 @@ export const WalletStack = () => { initialRouteName="WalletScreen" > + diff --git a/src/screens/Wallet/Account/index.tsx b/src/screens/Wallet/Account/index.tsx index fc94669e0..9d2edae9c 100644 --- a/src/screens/Wallet/Account/index.tsx +++ b/src/screens/Wallet/Account/index.tsx @@ -1,19 +1,20 @@ import React, { PropsWithChildren, useEffect, useRef, useState } from 'react'; -import { ListRenderItemInfo, View } from 'react-native'; +import { Alert, View } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; -import { RouteProp, useRoute } from '@react-navigation/native'; -import { - AccountTransactions, - ExplorerAccountTransactionItem -} from '@components/templates'; +import { RouteProp, useNavigation, useRoute } from '@react-navigation/native'; +import { AccountTransactions } from '@components/templates'; import { CopyToClipboardButton, Header } from '@components/composite'; -import { WalletStackParamsList } from '@appTypes'; -import { Row, Spacer, Spinner, Text } from '@components/base'; +import { NumberInput } from '@components/base/Input/Input.number'; +import { PrimaryButton } from '@components/modular'; +import { Input, Row, Spacer, Spinner, Text } from '@components/base'; +import { AddWalletStackNavigationProp, WalletStackParamsList } from '@appTypes'; import AirDAOKeysForRef from '@lib/helpers/AirDAOKeysForRef'; import { API } from '@api/api'; import { ExplorerAccount, Transaction } from '@models'; import { StringUtils } from '@utils/string'; import { scale, verticalScale } from '@utils/scaling'; +import { COLORS } from '@constants/colors'; +import { etherumAddressRegex } from '@constants/regex'; const Layout = (props: PropsWithChildren) => { const route = useRoute>(); @@ -29,6 +30,7 @@ const Layout = (props: PropsWithChildren) => { const LIMIT = 25; export const WalletAccount = () => { + const navigation = useNavigation(); const route = useRoute>(); const { wallet } = route.params; @@ -39,6 +41,10 @@ export const WalletAccount = () => { const [accountInfoLoading, setAccountInfoLoading] = useState(false); const [transactionsLoading, setTransactionsLoading] = useState(false); + // state for money sending + const [amountToSend, setAmountToSend] = useState(''); + const [addressToSend, setAddressToSend] = useState(''); + const getTransactions = async (address: string) => { if (transactions.length > 0 && transactions.length < LIMIT) return; setTransactionsLoading(true); @@ -86,6 +92,23 @@ export const WalletAccount = () => { // eslint-disable-next-line react-hooks/exhaustive-deps }, []); + const sendMoney = async () => { + const intAmount = parseFloat(amountToSend); + if (Number.isNaN(intAmount)) { + return; + } + if (!addressToSend.match(etherumAddressRegex)) { + // TODO + Alert.alert('Check destination address'); + return; + } + navigation.navigate('ReceiptScreen', { + amount: intAmount, + currencyCode: 'AMB', + destination: addressToSend + }); + }; + if (!wallet) { return ( @@ -110,17 +133,6 @@ export const WalletAccount = () => { ); } - const renderTransaction = ( - args: ListRenderItemInfo - ): JSX.Element => { - return ( - - ); - }; - return ( @@ -137,6 +149,24 @@ export const WalletAccount = () => { {account.ambBalance} AMB + Send money + + + + + + + Send + + { + const route = useRoute>(); + const { amount, currencyCode, destination } = route.params; + + return ( + +
+ + ); +}; From a4f27765e9c2eeb4ba76ddc1e1a83a033db1b629 Mon Sep 17 00:00:00 2001 From: JavidHaji-zada Date: Thu, 17 Aug 2023 03:59:48 +0400 Subject: [PATCH 052/509] POC crpyto sending --not-working --- .../BlocksoftTransfer/BlocksoftTransfer.ts | 108 +--- .../BlocksoftTransferPrivate.ts | 21 +- .../blockchains/AirDAOTransferDispatcher.ts | 230 +++---- .../eth/basic/EthTxSendProvider.ts | 5 +- src/appTypes/navigation/add-wallet.ts | 8 +- .../SendCrypto/SendCrypto.constants.ts | 52 ++ src/contexts/SendCrypto/SendCrypto.context.ts | 83 +++ src/contexts/SendCrypto/SendCrypto.types.ts | 64 ++ .../helpers/SendActionsBlockchainWrapper.ts | 323 ++++++++++ .../helpers/SendActionsContactBook.ts | 181 ++++++ .../SendCrypto/helpers/SendActionsEnd.ts | 387 ++++++++++++ .../SendCrypto/helpers/SendActionsStart.ts | 560 ++++++++++++++++++ .../helpers/SendActionsUpdateValues.ts | 109 ++++ src/contexts/SendCrypto/helpers/index.ts | 5 + src/contexts/SendCrypto/index.ts | 0 src/database/models/wallet.ts | 12 +- src/navigation/stacks/Tabs/WalletStack.tsx | 35 +- src/screens/Wallet/Account/index.tsx | 13 +- src/screens/Wallet/Receipt/index.tsx | 104 +++- src/utils/cache.ts | 3 +- src/utils/send-crypto-wrapper.ts | 444 ++++++++++++++ 21 files changed, 2518 insertions(+), 229 deletions(-) create mode 100644 src/contexts/SendCrypto/SendCrypto.constants.ts create mode 100644 src/contexts/SendCrypto/SendCrypto.context.ts create mode 100644 src/contexts/SendCrypto/SendCrypto.types.ts create mode 100644 src/contexts/SendCrypto/helpers/SendActionsBlockchainWrapper.ts create mode 100644 src/contexts/SendCrypto/helpers/SendActionsContactBook.ts create mode 100644 src/contexts/SendCrypto/helpers/SendActionsEnd.ts create mode 100644 src/contexts/SendCrypto/helpers/SendActionsStart.ts create mode 100644 src/contexts/SendCrypto/helpers/SendActionsUpdateValues.ts create mode 100644 src/contexts/SendCrypto/helpers/index.ts create mode 100644 src/contexts/SendCrypto/index.ts create mode 100644 src/utils/send-crypto-wrapper.ts diff --git a/crypto/actions/BlocksoftTransfer/BlocksoftTransfer.ts b/crypto/actions/BlocksoftTransfer/BlocksoftTransfer.ts index 754f6bc18..fe9386c30 100644 --- a/crypto/actions/BlocksoftTransfer/BlocksoftTransfer.ts +++ b/crypto/actions/BlocksoftTransfer/BlocksoftTransfer.ts @@ -1,17 +1,16 @@ +/* eslint-disable @typescript-eslint/no-namespace */ /** * @author Ksu * @version 0.20 */ import AirDAOCryptoLog from '../../common/AirDAOCryptoLog'; -import { BlocksoftBlockchainTypes } from '../../blockchains/BlocksoftBlockchainTypes'; -import { BlocksoftTransferDispatcher } from '../../blockchains/BlocksoftTransferDispatcher'; +import { AirDAOBlockchainTypes } from '../../blockchains/AirDAOBlockchainTypes'; +import { AirDAOTransferDispatcher } from '../../blockchains/AirDAOTransferDispatcher'; import { BlocksoftTransferPrivate } from './BlocksoftTransferPrivate'; import { AirDAODictTypes } from '../../common/AirDAODictTypes'; import CoinAirDAODict from '@crypto/assets/coinAirDAODict.json'; -import config from '../../../app/config/config'; - type DataCache = { [key in AirDAODictTypes.Code]: { key: string; @@ -26,16 +25,9 @@ const CACHE_DOUBLE_BSE = {}; export namespace BlocksoftTransfer { export const getTransferAllBalance = async function ( - data: BlocksoftBlockchainTypes.TransferData, - additionalData: BlocksoftBlockchainTypes.TransferAdditionalData = {} - ): Promise { - if (config.debug.sendLogs) { - console.log( - `${data.currencyCode} BlocksoftTransfer.getTransferAllBalance`, - JSON.parse(JSON.stringify(data)), - JSON.parse(JSON.stringify(additionalData)) - ); - } + data: AirDAOBlockchainTypes.TransferData, + additionalData: AirDAOBlockchainTypes.TransferAdditionalData = {} + ): Promise { if (typeof data.derivationPath !== 'undefined' && data.derivationPath) { data.derivationPath = data.derivationPath.replace(/quote/g, "'"); } @@ -45,11 +37,11 @@ export namespace BlocksoftTransfer { AirDAOCryptoLog.log( `${data.currencyCode} BlocksoftTransfer.getTransferAllBalance started ${data.addressFrom} ` ); - const processor = BlocksoftTransferDispatcher.getTransferProcessor( + const processor = AirDAOTransferDispatcher.getTransferProcessor( data.currencyCode ); const additionalDataTmp = { ...additionalData }; - let privateData = {} as BlocksoftBlockchainTypes.TransferPrivateData; + let privateData = {} as AirDAOBlockchainTypes.TransferPrivateData; if (processor.needPrivateForFee()) { privateData = await BlocksoftTransferPrivate.initTransferPrivate( data, @@ -57,19 +49,13 @@ export namespace BlocksoftTransfer { ); } additionalDataTmp.mnemonic = '***'; - transferAllCount = await BlocksoftTransferDispatcher.getTransferProcessor( + transferAllCount = await AirDAOTransferDispatcher.getTransferProcessor( data.currencyCode ).getTransferAllBalance(data, privateData, additionalDataTmp); AirDAOCryptoLog.log( `${data.currencyCode} BlocksoftTransfer.getTransferAllBalance got ${data.addressFrom} result is ok` ); - if (config.debug.sendLogs) { - console.log( - `${data.currencyCode} BlocksoftTransfer.getTransferAllBalance result`, - JSON.parse(JSON.stringify(transferAllCount)) - ); - } } catch (e) { if ( e.message.indexOf('SERVER_RESPONSE_') === -1 && @@ -87,12 +73,6 @@ export namespace BlocksoftTransfer { e.message ); } else { - if (config.debug.appErrors) { - console.log( - `${data.currencyCode} BlocksoftTransfer.getTransferAllBalance error ` + - e.message - ); - } throw e; } } @@ -100,9 +80,9 @@ export namespace BlocksoftTransfer { }; export const getFeeRate = async function ( - data: BlocksoftBlockchainTypes.TransferData, - additionalData: BlocksoftBlockchainTypes.TransferAdditionalData = {} - ): Promise { + data: AirDAOBlockchainTypes.TransferData, + additionalData: AirDAOBlockchainTypes.TransferAdditionalData = {} + ): Promise { const lower = data.addressTo.toLowerCase(); if (!data?.walletConnectData?.data) { for (const key in CoinAirDAODict) { @@ -122,13 +102,6 @@ export namespace BlocksoftTransfer { } } - if (config.debug.sendLogs) { - console.log( - 'BlocksoftTransfer.getFeeRate', - JSON.parse(JSON.stringify(data)), - JSON.parse(JSON.stringify(additionalData)) - ); - } if (typeof data.derivationPath === 'undefined' || !data.derivationPath) { throw new Error( 'BlocksoftTransfer.getFeeRate requires derivationPath ' + @@ -141,12 +114,12 @@ export namespace BlocksoftTransfer { AirDAOCryptoLog.log( `${data.currencyCode} BlocksoftTransfer.getFeeRate started ${data.addressFrom} ` ); - const processor = BlocksoftTransferDispatcher.getTransferProcessor( + const processor = AirDAOTransferDispatcher.getTransferProcessor( data.currencyCode ); const additionalDataTmp = { ...additionalData }; - let privateData = {} as BlocksoftBlockchainTypes.TransferPrivateData; + let privateData = {} as AirDAOBlockchainTypes.TransferPrivateData; if (processor.needPrivateForFee()) { privateData = await BlocksoftTransferPrivate.initTransferPrivate( data, @@ -161,9 +134,6 @@ export namespace BlocksoftTransfer { ); feesCount.countedTime = new Date().getTime(); } catch (e) { - if (config.debug.cryptoErrors) { - console.log('BlocksoftTransfer.getFeeRate error ', e); - } if (typeof e.message === 'undefined') { await AirDAOCryptoLog.log('BlocksoftTransfer.getFeeRate strange error'); } else if ( @@ -198,14 +168,11 @@ export namespace BlocksoftTransfer { }; export const sendTx = async function ( - data: BlocksoftBlockchainTypes.TransferData, - uiData: BlocksoftBlockchainTypes.TransferUiData, - additionalData: BlocksoftBlockchainTypes.TransferAdditionalData - ): Promise { - if (config.debug.sendLogs) { - console.log('BlocksoftTransfer.sendTx', data, uiData); - } - + data: AirDAOBlockchainTypes.TransferData, + uiData: AirDAOBlockchainTypes.TransferUiData, + additionalData: AirDAOBlockchainTypes.TransferAdditionalData + ): Promise { + console.log('here 1', { data, uiData, additionalData }); const lower = data.addressTo.toLowerCase(); if (!data?.walletConnectData?.data) { for (const key in CoinAirDAODict) { @@ -224,6 +191,7 @@ export namespace BlocksoftTransfer { } } } + console.log('here 2'); data.derivationPath = data.derivationPath.replace(/quote/g, "'"); @@ -279,13 +247,6 @@ export namespace BlocksoftTransfer { } } } catch (e) { - if (config.debug.cryptoErrors) { - console.log( - `${data.currencyCode} BlocksoftTransfer.sendTx check double error ` + - e.message, - e - ); - } if ( e.message.indexOf('SERVER_RESPONSE_') === -1 && e.message.indexOf('UI_') === -1 @@ -302,7 +263,7 @@ export namespace BlocksoftTransfer { AirDAOCryptoLog.log( `${data.currencyCode} BlocksoftTransfer.sendTx started ${data.addressFrom} ` ); - const processor = BlocksoftTransferDispatcher.getTransferProcessor( + const processor = AirDAOTransferDispatcher.getTransferProcessor( data.currencyCode ); const privateData = await BlocksoftTransferPrivate.initTransferPrivate( @@ -330,9 +291,6 @@ export namespace BlocksoftTransfer { } // if (typeof uiData.selectedFee !== 'undefined') } catch (e) { - if (config.debug.cryptoErrors) { - console.log('BlocksoftTransfer.sendTx error ' + e.message, e); - } if ( e.message.indexOf('SERVER_RESPONSE_') === -1 && e.message.indexOf('UI_') === -1 && @@ -367,7 +325,7 @@ export namespace BlocksoftTransfer { }; export const sendRawTx = async function ( - data: BlocksoftBlockchainTypes.DbAccount, + data: AirDAOBlockchainTypes.DbAccount, rawTxHex: string, txRBF: any, logData: any @@ -377,7 +335,7 @@ export namespace BlocksoftTransfer { AirDAOCryptoLog.log( `${data.currencyCode} BlocksoftTransfer.sendRawTx started ${data.address} ` ); - const processor = BlocksoftTransferDispatcher.getTransferProcessor( + const processor = AirDAOTransferDispatcher.getTransferProcessor( data.currencyCode ); if (typeof processor.sendRawTx === 'undefined') { @@ -388,12 +346,6 @@ export namespace BlocksoftTransfer { `${data.currencyCode} BlocksoftTransfer.sendRawTx got ${data.address} result is ok` ); } catch (e) { - if (config.debug.cryptoErrors) { - console.log( - `${data.currencyCode} BlocksoftTransfer.sendRawTx error `, - e - ); - } AirDAOCryptoLog.log( `${data.currencyCode} BlocksoftTransfer.sendRawTx error ` + e.message ); @@ -403,15 +355,15 @@ export namespace BlocksoftTransfer { }; export const setMissingTx = async function ( - data: BlocksoftBlockchainTypes.DbAccount, - dbTransaction: BlocksoftBlockchainTypes.DbTransaction + data: AirDAOBlockchainTypes.DbAccount, + dbTransaction: AirDAOBlockchainTypes.DbTransaction ): Promise { let txResult = false; try { AirDAOCryptoLog.log( `${data.currencyCode} BlocksoftTransfer.setMissing started ${data.address} ` ); - const processor = BlocksoftTransferDispatcher.getTransferProcessor( + const processor = AirDAOTransferDispatcher.getTransferProcessor( data.currencyCode ); if (typeof processor.setMissingTx === 'undefined') { @@ -430,14 +382,14 @@ export namespace BlocksoftTransfer { }; export const canRBF = function ( - data: BlocksoftBlockchainTypes.DbAccount, - dbTransaction: BlocksoftBlockchainTypes.DbTransaction, + data: AirDAOBlockchainTypes.DbAccount, + dbTransaction: AirDAOBlockchainTypes.DbTransaction, source: string ): boolean { let txResult = false; try { // AirDAOCryptoLog.log(`BlocksoftTransfer.canRBF ${data.currencyCode} from ${source} started ${data.address} `) - const processor = BlocksoftTransferDispatcher.getTransferProcessor( + const processor = AirDAOTransferDispatcher.getTransferProcessor( typeof data.currencyCode !== 'undefined' ? data.currencyCode : dbTransaction.currencyCode @@ -460,7 +412,7 @@ export namespace BlocksoftTransfer { let checkSendAllModalResult = false; try { // AirDAOCryptoLog.log(`BlocksoftTransfer.checkSendAllModal ${data.currencyCode} started `) - const processor = BlocksoftTransferDispatcher.getTransferProcessor( + const processor = AirDAOTransferDispatcher.getTransferProcessor( data.currencyCode ); if (typeof processor.checkSendAllModal === 'undefined') { diff --git a/crypto/actions/BlocksoftTransfer/BlocksoftTransferPrivate.ts b/crypto/actions/BlocksoftTransfer/BlocksoftTransferPrivate.ts index e125602b6..b1b5df648 100644 --- a/crypto/actions/BlocksoftTransfer/BlocksoftTransferPrivate.ts +++ b/crypto/actions/BlocksoftTransfer/BlocksoftTransferPrivate.ts @@ -1,36 +1,37 @@ +/* eslint-disable @typescript-eslint/no-namespace */ /** * @author Ksu * @version 0.20 */ -import { BlocksoftBlockchainTypes } from '../../blockchains/BlocksoftBlockchainTypes'; +import { AirDAOBlockchainTypes } from '../../blockchains/AirDAOBlockchainTypes'; import BlocksoftPrivateKeysUtils from '../../common/AirDAOPrivateKeysUtils'; import AirDAOCryptoLog from '../../common/AirDAOCryptoLog'; -import BlocksoftKeysStorage from '../BlocksoftKeysStorage/BlocksoftKeysStorage'; +import AirDAOKeysStorage from '@lib/helpers/AirDAOKeysStorage'; export namespace BlocksoftTransferPrivate { const CACHE_PRIVATE: any = {}; const initTransferPrivateBTC = async function ( - data: BlocksoftBlockchainTypes.TransferData, + data: AirDAOBlockchainTypes.TransferData, mnemonic: string - ): Promise { + ): Promise { const privateData = { privateKey: mnemonic - } as BlocksoftBlockchainTypes.TransferPrivateData; + } as AirDAOBlockchainTypes.TransferPrivateData; return privateData; }; export const initTransferPrivate = async function ( - data: BlocksoftBlockchainTypes.TransferData, - additionalData: BlocksoftBlockchainTypes.TransferAdditionalData - ): Promise { - const privateData = {} as BlocksoftBlockchainTypes.TransferPrivateData; + data: AirDAOBlockchainTypes.TransferData, + additionalData: AirDAOBlockchainTypes.TransferAdditionalData + ): Promise { + const privateData = {} as AirDAOBlockchainTypes.TransferPrivateData; let mnemonic = typeof additionalData !== 'undefined' && typeof additionalData.mnemonic !== 'undefined' ? additionalData.mnemonic : CACHE_PRIVATE[data.walletHash]; if (!mnemonic) { - mnemonic = await BlocksoftKeysStorage.getWalletMnemonic( + mnemonic = await AirDAOKeysStorage.getWalletMnemonic( data.walletHash, 'initTransferPrivate' ); diff --git a/crypto/blockchains/AirDAOTransferDispatcher.ts b/crypto/blockchains/AirDAOTransferDispatcher.ts index 225965c4a..68344e99e 100644 --- a/crypto/blockchains/AirDAOTransferDispatcher.ts +++ b/crypto/blockchains/AirDAOTransferDispatcher.ts @@ -55,36 +55,36 @@ export namespace AirDAOTransferDispatcher { transferProcessor = currencyDictSettings.transferProcessor; } switch (transferProcessor) { - case 'BCH': - CACHE_PROCESSORS[currencyCode] = new BchTransferProcessor( - currencyDictSettings - ); - break; - case 'BSV': - CACHE_PROCESSORS[currencyCode] = new BsvTransferProcessor( - currencyDictSettings - ); - break; - case 'BTC': - CACHE_PROCESSORS[currencyCode] = new BtcTransferProcessor( - currencyDictSettings - ); - break; - case 'BTC_TEST': - CACHE_PROCESSORS[currencyCode] = new BtcTestTransferProcessor( - currencyDictSettings - ); - break; - case 'BTG': - CACHE_PROCESSORS[currencyCode] = new BtgTransferProcessor( - currencyDictSettings - ); - break; - case 'DOGE': - CACHE_PROCESSORS[currencyCode] = new DogeTransferProcessor( - currencyDictSettings - ); - break; + // case 'BCH': + // CACHE_PROCESSORS[currencyCode] = new BchTransferProcessor( + // currencyDictSettings + // ); + // break; + // case 'BSV': + // CACHE_PROCESSORS[currencyCode] = new BsvTransferProcessor( + // currencyDictSettings + // ); + // break; + // case 'BTC': + // CACHE_PROCESSORS[currencyCode] = new BtcTransferProcessor( + // currencyDictSettings + // ); + // break; + // case 'BTC_TEST': + // CACHE_PROCESSORS[currencyCode] = new BtcTestTransferProcessor( + // currencyDictSettings + // ); + // break; + // case 'BTG': + // CACHE_PROCESSORS[currencyCode] = new BtgTransferProcessor( + // currencyDictSettings + // ); + // break; + // case 'DOGE': + // CACHE_PROCESSORS[currencyCode] = new DogeTransferProcessor( + // currencyDictSettings + // ); + // break; case 'ETH': CACHE_PROCESSORS[currencyCode] = new EthTransferProcessor( currencyDictSettings @@ -95,91 +95,91 @@ export namespace AirDAOTransferDispatcher { currencyDictSettings ); break; - case 'ETC': - CACHE_PROCESSORS[currencyCode] = new EtcTransferProcessor( - currencyDictSettings - ); - break; - case 'BNB_SMART_20': - CACHE_PROCESSORS[currencyCode] = new BnbSmartTransferProcessorErc20( - currencyDictSettings - ); - break; - case 'LTC': - CACHE_PROCESSORS[currencyCode] = new LtcTransferProcessor( - currencyDictSettings - ); - break; - case 'TRX': - CACHE_PROCESSORS[currencyCode] = new TrxTransferProcessor( - currencyDictSettings - ); - break; - case 'USDT': - CACHE_PROCESSORS[currencyCode] = new UsdtTransferProcessor( - currencyDictSettings - ); - break; - case 'XRP': - CACHE_PROCESSORS[currencyCode] = new XrpTransferProcessor( - currencyDictSettings - ); - break; - case 'XLM': - CACHE_PROCESSORS[currencyCode] = new XlmTransferProcessor( - currencyDictSettings - ); - break; - case 'XVG': - CACHE_PROCESSORS[currencyCode] = new XvgTransferProcessor( - currencyDictSettings - ); - break; - case 'XMR': - CACHE_PROCESSORS[currencyCode] = new XmrTransferProcessor( - currencyDictSettings - ); - break; - case 'FIO': - CACHE_PROCESSORS[currencyCode] = new FioTransferProcessor( - currencyDictSettings - ); - break; - case 'BNB': - CACHE_PROCESSORS[currencyCode] = new BnbTransferProcessor( - currencyDictSettings - ); - break; - case 'BNB_SMART': - CACHE_PROCESSORS[currencyCode] = new BnbSmartTransferProcessor( - currencyDictSettings - ); - break; - case 'VET': - CACHE_PROCESSORS[currencyCode] = new VetTransferProcessor( - currencyDictSettings - ); - break; - case 'SOL': - CACHE_PROCESSORS[currencyCode] = new SolTransferProcessor( - currencyDictSettings - ); - break; - case 'SOL_SPL': - CACHE_PROCESSORS[currencyCode] = new SolTransferProcessorSpl( - currencyDictSettings - ); - break; - case 'METIS': - CACHE_PROCESSORS[currencyCode] = new MetisTransferProcessor( - currencyDictSettings - ); - break; - case 'WAVES': - CACHE_PROCESSORS[currencyCode] = new WavesTransferProcessor( - currencyDictSettings - ); - break; + // case 'ETC': + // CACHE_PROCESSORS[currencyCode] = new EtcTransferProcessor( + // currencyDictSettings + // ); + // break; + // case 'BNB_SMART_20': + // CACHE_PROCESSORS[currencyCode] = new BnbSmartTransferProcessorErc20( + // currencyDictSettings + // ); + // break; + // case 'LTC': + // CACHE_PROCESSORS[currencyCode] = new LtcTransferProcessor( + // currencyDictSettings + // ); + // break; + // case 'TRX': + // CACHE_PROCESSORS[currencyCode] = new TrxTransferProcessor( + // currencyDictSettings + // ); + // break; + // case 'USDT': + // CACHE_PROCESSORS[currencyCode] = new UsdtTransferProcessor( + // currencyDictSettings + // ); + // break; + // case 'XRP': + // CACHE_PROCESSORS[currencyCode] = new XrpTransferProcessor( + // currencyDictSettings + // ); + // break; + // case 'XLM': + // CACHE_PROCESSORS[currencyCode] = new XlmTransferProcessor( + // currencyDictSettings + // ); + // break; + // case 'XVG': + // CACHE_PROCESSORS[currencyCode] = new XvgTransferProcessor( + // currencyDictSettings + // ); + // break; + // case 'XMR': + // CACHE_PROCESSORS[currencyCode] = new XmrTransferProcessor( + // currencyDictSettings + // ); + // break; + // case 'FIO': + // CACHE_PROCESSORS[currencyCode] = new FioTransferProcessor( + // currencyDictSettings + // ); + // break; + // case 'BNB': + // CACHE_PROCESSORS[currencyCode] = new BnbTransferProcessor( + // currencyDictSettings + // ); + // break; + // case 'BNB_SMART': + // CACHE_PROCESSORS[currencyCode] = new BnbSmartTransferProcessor( + // currencyDictSettings + // ); + // break; + // case 'VET': + // CACHE_PROCESSORS[currencyCode] = new VetTransferProcessor( + // currencyDictSettings + // ); + // break; + // case 'SOL': + // CACHE_PROCESSORS[currencyCode] = new SolTransferProcessor( + // currencyDictSettings + // ); + // break; + // case 'SOL_SPL': + // CACHE_PROCESSORS[currencyCode] = new SolTransferProcessorSpl( + // currencyDictSettings + // ); + // break; + // case 'METIS': + // CACHE_PROCESSORS[currencyCode] = new MetisTransferProcessor( + // currencyDictSettings + // ); + // break; + // case 'WAVES': + // CACHE_PROCESSORS[currencyCode] = new WavesTransferProcessor( + // currencyDictSettings + // ); + // break; default: throw new Error('Unknown transferProcessor ' + transferProcessor); } diff --git a/crypto/blockchains/eth/basic/EthTxSendProvider.ts b/crypto/blockchains/eth/basic/EthTxSendProvider.ts index 3cc3fe50a..62c32028d 100644 --- a/crypto/blockchains/eth/basic/EthTxSendProvider.ts +++ b/crypto/blockchains/eth/basic/EthTxSendProvider.ts @@ -83,9 +83,10 @@ export default class EthTxSendProvider { this._settings.currencyCode + ' EthTxSendProvider._innerSendTx started', logData ); - + console.log({ tx, privateData, txRBF }); const rawTransaction = await this.sign(tx, privateData, txRBF, logData); - + console.log({ rawTransaction }); + return; // @ts-ignore await AirDAOCryptoLog.log( this._settings.currencyCode + ' EthTxSendProvider._innerSendTx signed', diff --git a/src/appTypes/navigation/add-wallet.ts b/src/appTypes/navigation/add-wallet.ts index 0f682442b..38ca98e18 100644 --- a/src/appTypes/navigation/add-wallet.ts +++ b/src/appTypes/navigation/add-wallet.ts @@ -8,7 +8,13 @@ export type WalletStackParamsList = { CreateWalletStep0: undefined; CreateWalletStep1: undefined; CreateWalletStep2: undefined; - ReceiptScreen: { amount: number; currencyCode: string; destination: string }; + ReceiptScreen: { + amount: number; + currencyCode: string; + destination: string; + origin: string; + hash: string; + }; RestoreWalletScreen: undefined; WalletScreen: undefined; WalletAccount: { wallet: Wallet }; diff --git a/src/contexts/SendCrypto/SendCrypto.constants.ts b/src/contexts/SendCrypto/SendCrypto.constants.ts new file mode 100644 index 000000000..f04e360df --- /dev/null +++ b/src/contexts/SendCrypto/SendCrypto.constants.ts @@ -0,0 +1,52 @@ +export const SEND_CRYPTO_INITIAL_STATE = { + ui: { + uiType: 'ACCOUNT_SCREEN', + addressTo: '', + addressName: '', + memo: '', + cryptoValue: '', + cryptoValueRecounted: 0, + comment: '', + rawOnly: false, + isTransferAll: false, + dexCurrencyCode: false, + dexOrderData: false, + bse: { + bseProviderType: false, + bseOrderId: false, + bseMinCrypto: false, + bseTrusteeFee: false, + bseOrderData: false + }, + tbk: { + transactionBoost: false, + transactionAction: false + }, + fioRequestDetails: false + }, + dict: { + inputType: 'CRYPTO', + decimals: '', + extendsProcessor: '', + addressUiChecker: '', + network: '', + addressFrom: '', + currencySymbol: '', + currencyName: '', + currencyCode: '', + balanceTotalPretty: '', + basicCurrencyBalanceTotal: '', + basicCurrencySymbol: '', + basicCurrencyCode: '', + basicCurrencyRate: '' + }, + fromBlockchain: { + neverCounted: true, + countedFees: { + fees: [], + selectedFeeIndex: -10 + }, + selectedFee: false, + transferAllBalance: false + } +}; diff --git a/src/contexts/SendCrypto/SendCrypto.context.ts b/src/contexts/SendCrypto/SendCrypto.context.ts new file mode 100644 index 000000000..d55b550c2 --- /dev/null +++ b/src/contexts/SendCrypto/SendCrypto.context.ts @@ -0,0 +1,83 @@ +import { useState } from 'react'; +import { + SendCryptoContextState, + SendCryptoDispatch, + SendCryptoDispatchPayload +} from './SendCrypto.types'; +import { createContextSelector } from '@helpers/createContextSelector'; +import { SEND_CRYPTO_INITIAL_STATE } from './SendCrypto.constants'; + +const SendCryptoContext = (): { + state: Partial; + reducer: SendCryptoDispatch; +} => { + const [state, setState] = useState>( + SEND_CRYPTO_INITIAL_STATE + ); + + const reducer = (payload: SendCryptoDispatchPayload) => { + switch (payload.type) { + case 'RESET_DATA_BLOCKCHAIN': { + setState({ + ui: { + ...state.ui + }, + dict: { + ...state.dict + }, + fromBlockchain: { + ...payload.fromBlockchain + } + }); + return; + } + case 'SET_DATA': { + setState({ + ui: { + ...state.ui, + ...payload.ui + }, + dict: { + ...state.dict, + ...payload.dict + }, + fromBlockchain: { + ...state.fromBlockchain, + ...payload.fromBlockchain + } + }); + return; + } + case 'RESET_DATA': { + setState({ + ui: { + ...SEND_CRYPTO_INITIAL_STATE.ui, + ...payload.ui + }, + dict: { + ...SEND_CRYPTO_INITIAL_STATE.dict, + ...payload.dict + }, + fromBlockchain: { + ...SEND_CRYPTO_INITIAL_STATE.fromBlockchain + } + }); + return; + } + case 'CLEAN_DATA': + setState({ + ui: {}, + dict: {}, + fromBlockchain: {} + }); + default: { + return; + } + } + }; + + return { state, reducer: reducer }; +}; + +export const [SendCryptoProvider, useSendCryptoContext] = + createContextSelector(SendCryptoContext); diff --git a/src/contexts/SendCrypto/SendCrypto.types.ts b/src/contexts/SendCrypto/SendCrypto.types.ts new file mode 100644 index 000000000..accb8b046 --- /dev/null +++ b/src/contexts/SendCrypto/SendCrypto.types.ts @@ -0,0 +1,64 @@ +export interface SendCryptoContextState { + ui: { + uiType: string; // DEFAULT "ACCOUNT_SCREEN" + addressTo: string; + addressName: string; + memo: string; + cryptoValue: string; + cryptoValueRecounted: number; + comment: string; + rawOnly: boolean; + isTransferAll: boolean; + dexCurrencyCode: boolean; + dexOrderData: boolean; + bse: { + bseProviderType: boolean; + bseOrderId: boolean; + bseMinCrypto: boolean; + bseTrusteeFee: boolean; + bseOrderData: boolean; + }; + tbk: { + transactionBoost: boolean; + transactionAction: boolean; + }; + fioRequestDetails: boolean; + }; + dict: { + inputType: string; // default "CRYPTO" + decimals: string; + extendsProcessor: string; + addressUiChecker: string; + network: string; + addressFrom: string; + currencySymbol: string; + currencyName: string; + currencyCode: string; + balanceTotalPretty: string; + basicCurrencyBalanceTotal: string; + basicCurrencySymbol: string; + basicCurrencyCode: string; + basicCurrencyRate: string; + }; + fromBlockchain: { + neverCounted: boolean; + countedFees: { + fees: number[]; + selectedFeeIndex: number; // DEFAULT -10 + }; + selectedFee: boolean; + transferAllBalance: boolean; + }; +} + +type SendCryptoActionType = + | 'SET_DATA' + | 'RESET_DATA_BLOCKCHAIN' + | 'RESET_DATA' + | 'CLEAN_DATA'; + +export type SendCryptoDispatchPayload = Partial & { + type: SendCryptoActionType; +}; + +export type SendCryptoDispatch = (payload: SendCryptoDispatchPayload) => void; diff --git a/src/contexts/SendCrypto/helpers/SendActionsBlockchainWrapper.ts b/src/contexts/SendCrypto/helpers/SendActionsBlockchainWrapper.ts new file mode 100644 index 000000000..84f2073e7 --- /dev/null +++ b/src/contexts/SendCrypto/helpers/SendActionsBlockchainWrapper.ts @@ -0,0 +1,323 @@ +/** + * @version 0.41 + */ +import { BlocksoftTransfer } from '@crypto/actions/BlocksoftTransfer/BlocksoftTransfer' +import { BlocksoftBlockchainTypes } from '@crypto/blockchains/BlocksoftBlockchainTypes' + +import store from '@app/store' +import config from '@app/config/config' +import Log from '@app/services/Log/Log' +import { BlocksoftTransferUtils } from '@crypto/actions/BlocksoftTransfer/BlocksoftTransferUtils' +import { showModal } from '@app/appstores/Stores/Modal/ModalActions' +import { strings } from '@app/services/i18n' +import NavStore from '@app/components/navigation/NavStore' + +import BlocksoftUtils from '@crypto/common/BlocksoftUtils' +import EthNetworkPrices from '@crypto/blockchains/eth/basic/EthNetworkPrices' +import BlocksoftDict from '@crypto/common/BlocksoftDict' + +const { dispatch } = store + +const CACHE_DATA = { + countedFeesData: {} as BlocksoftBlockchainTypes.TransferData, + transferAllBalance: '0', + additionalData : false as any +} +export namespace SendActionsBlockchainWrapper { + + export const beforeRender = (cryptoCurrency: any, account: any, additional: any = {}) => { + const { selectedWallet } = store.getState().mainStore + const { + walletHash, + walletUseUnconfirmed, + walletAllowReplaceByFee, + walletUseLegacy, + walletIsHd + } = selectedWallet + const { address, currencyCode, derivationPath, accountJson } = account + + const newCountedFeesData = { + currencyCode: currencyCode, + walletHash: walletHash, + derivationPath: derivationPath, + addressFrom: address, + addressTo: additional.addressTo || '?', + amount: additional.amount || account.balanceRaw, + memo: additional.memo || null, + accountBalanceRaw: account.balanceRaw, + isTransferAll: false, + useOnlyConfirmed: !(walletUseUnconfirmed === 1), + allowReplaceByFee: walletAllowReplaceByFee === 1, + useLegacy: walletUseLegacy, + isHd: walletIsHd, + accountJson, + transactionJson: {} + } as BlocksoftBlockchainTypes.TransferData + + if (typeof additional.tbk !== 'undefined' && additional.tbk && additional.tbk.transactionAction) { + if (additional.tbk.transactionAction === 'transactionSpeedUp') { + newCountedFeesData.transactionSpeedUp = additional.tbk.transactionBoost.transactionHash + } else if (additional.tbk.transactionAction === 'transactionReplaceByFee') { + newCountedFeesData.transactionReplaceByFee = additional.tbk.transactionBoost.transactionHash + } else if (additional.tbk.transactionAction === 'transactionRemoveByFee') { + newCountedFeesData.transactionReplaceByFee = additional.tbk.transactionBoost.transactionHash + } else { + throw new Error('undefined SendActionsBlockchainWrapper beforeRender transactionAction ' + additional.tbk.transactionAction) + } + if (typeof additional.tbk.transactionBoost.transactionJson !== 'undefined' && additional.tbk.transactionBoost.transactionJson) { + newCountedFeesData.transactionJson = additional.tbk.transactionBoost.transactionJson + } + } + + CACHE_DATA.additionalData = false + if (JSON.stringify(CACHE_DATA.countedFeesData) === JSON.stringify(newCountedFeesData)) { + return + } + if (newCountedFeesData.addressFrom !== CACHE_DATA.countedFeesData.addressFrom) { + CACHE_DATA.transferAllBalance = '0' + } + CACHE_DATA.countedFeesData = newCountedFeesData + + } + + export const getCustomFeeRate = async (newFee : any) => { + let newCountedFeesData = {} + try { + newCountedFeesData = { ...CACHE_DATA.countedFeesData } + const countedFees = await BlocksoftTransfer.getFeeRate(newCountedFeesData, + CACHE_DATA.additionalData ? {...newFee, ... CACHE_DATA.additionalData} : newFee ) + let selectedFee = false + if (typeof countedFees.selectedFeeIndex !== 'undefined' && countedFees.selectedFeeIndex >= 0) { + // @ts-ignore + selectedFee = countedFees.fees[countedFees.selectedFeeIndex] + } + return selectedFee + } catch (e) { + if (config.debug.appErrors) { + console.log('SendActionsBlockchainWrapper.getCustomFeeRate error ' + e.message) + } + if (e.message.indexOf('SERVER_RESPONSE_') !== -1) { + const extend = BlocksoftDict.getCurrencyAllSettings(newCountedFeesData?.currencyCode) + Log.errorTranslate(e, 'SendActionsBlockchainWrapper.getCustomFeeRate', extend) + showModal({ + type: 'INFO_MODAL', + icon: null, + title: strings('modal.exchange.sorry'), + description: e.message + }) + } else { + Log.err('SendActionsBlockchainWrapper.getCustomFeeRate error ' + e.message) + } + } + } + + export const getFeeRate = async (uiData = {}, precountedSelectedFee = false) => { + let newCountedFeesData = {} + try { + if (typeof uiData === 'undefined' || typeof uiData.addressTo === 'undefined') { + uiData = store.getState().sendScreenStore.ui + } + + const forceExecAmount = typeof uiData.bse !== 'undefined' && typeof uiData.bse.forceExecAmount !== 'undefined' && uiData.bse.forceExecAmount + + newCountedFeesData = { ...CACHE_DATA.countedFeesData } + newCountedFeesData.addressTo = uiData.addressTo + newCountedFeesData.amount = uiData.cryptoValue + newCountedFeesData.memo = uiData.memo + newCountedFeesData.isTransferAll = uiData.isTransferAll + + if (newCountedFeesData.isTransferAll && !forceExecAmount) { + newCountedFeesData.amount = newCountedFeesData.accountBalanceRaw + } + if (typeof uiData.contractCallData !== 'undefined') { + newCountedFeesData.contractCallData = uiData.contractCallData + } + if (typeof uiData.walletConnectData !== 'undefined') { + newCountedFeesData.walletConnectData = uiData.walletConnectData + } + if (typeof uiData.dexOrderData !== 'undefined') { + if (uiData.dexOrderData || newCountedFeesData.dexOrderData) { + newCountedFeesData.dexOrderData = uiData.dexOrderData + } + } + if (!store.getState().sendScreenStore.fromBlockchain.neverCounted) { + if (JSON.stringify(CACHE_DATA.countedFeesData) === JSON.stringify(newCountedFeesData)) { + return { isTransferAll : newCountedFeesData.isTransferAll, amount : newCountedFeesData.amount, source : 'CACHE_COUNTED', addressTo : newCountedFeesData.addressTo} + } + } + if (config.debug.sendLogs) { + console.log('SendActionsBlockchainWrapper.getFeeRate starting') + } + let countedFees + if (precountedSelectedFee) { + Log.log('SendActionsBlockchainWrapper.getFeeRate has precountedSelectedFee ', precountedSelectedFee) + countedFees = { + fees: [precountedSelectedFee], + selectedFeeIndex: 0 + } + } else { + try { + countedFees = await BlocksoftTransfer.getFeeRate(newCountedFeesData, CACHE_DATA.additionalData ? CACHE_DATA.additionalData : {}) + } catch (e) { + if (e.message.indexOf('SERVER') === -1) { + e.message += ' while BlocksoftTransfer.getFeeRate' + } + throw e + } + } + + if (forceExecAmount && typeof countedFees !== 'undefined' && countedFees && typeof countedFees.fees !== 'undefined' && countedFees.fees) { + for (let i = 0; i< countedFees.fees.length; i++ ) { + if (typeof countedFees.fees[i].amountForTx !== 'undefined') { + countedFees.fees[i].amountForTx = uiData.cryptoValue + } + } + if (typeof countedFees.amountForTx !== 'undefined') { + countedFees.amountForTx = uiData.cryptoValue + } + } + + let selectedFee = false + if (typeof countedFees.selectedFeeIndex !== 'undefined' && countedFees.selectedFeeIndex >= 0) { + // @ts-ignore + selectedFee = countedFees.fees[countedFees.selectedFeeIndex] + } + if (typeof countedFees.additionalData !== 'undefined' && countedFees.additionalData) { + CACHE_DATA.additionalData = countedFees.additionalData + } + CACHE_DATA.countedFeesData = newCountedFeesData + + dispatch({ + type: 'RESET_DATA_BLOCKCHAIN', + fromBlockchain: { + countedFees, + selectedFee, + neverCounted : false + } + }) + + return { isTransferAll : newCountedFeesData.isTransferAll, amount : newCountedFeesData.amount, source : 'NEW_COUNTED', addressTo : newCountedFeesData.addressTo} + + } catch (e) { + if (config.debug.appErrors) { + console.log('SendActionsBlockchainWrapper.getFeeRate error ' + e.message, e) + } + if (typeof e.message === 'undefined' ) { + Log.log('SendActionsBlockchainWrapper.getFeeRate strange error') + } else if (e.message.indexOf('SERVER_RESPONSE_') !== -1) { + const extend = BlocksoftDict.getCurrencyAllSettings(newCountedFeesData?.currencyCode) + Log.errorTranslate(e, 'SendActionsBlockchainWrapper.getFeeRate', extend) + showModal({ + type: 'INFO_MODAL', + icon: null, + title: strings('modal.exchange.sorry'), + description: e.message + }) + } else { + Log.log('SendActionsBlockchainWrapper.getFeeRate inner error ' + e.message) + } + } + + return {source : 'ERROR', addressTo : '?'} + } + + export const getTransferAllBalance = async (uiData = {}) => { + const newCountedFeesData = { ...CACHE_DATA.countedFeesData } + try { + if (typeof uiData === 'undefined' || typeof uiData.addressTo === 'undefined') { + uiData = store.getState().sendScreenStore.ui + } + newCountedFeesData.addressTo = uiData.addressTo + newCountedFeesData.amount = newCountedFeesData.accountBalanceRaw + newCountedFeesData.memo = uiData.memo + newCountedFeesData.isTransferAll = uiData.isTransferAll + if (!newCountedFeesData.addressTo || newCountedFeesData.addressTo === '' || newCountedFeesData.addressTo === '?') { + newCountedFeesData.addressTo = BlocksoftTransferUtils.getAddressToForTransferAll({ + currencyCode: newCountedFeesData.currencyCode, + address: newCountedFeesData.addressFrom + }) + } + if (!store.getState().sendScreenStore.fromBlockchain.neverCounted && JSON.stringify(CACHE_DATA.countedFeesData) === JSON.stringify(newCountedFeesData)) { + return { transferAllBalance : CACHE_DATA.transferAllBalance, source : 'CACHE_COUNTED', addressTo : newCountedFeesData.addressTo} + } + const countedFees = await BlocksoftTransfer.getTransferAllBalance(newCountedFeesData, CACHE_DATA.additionalData ? CACHE_DATA.additionalData : {}) + let selectedFee = false + if (typeof countedFees.selectedFeeIndex !== 'undefined' && countedFees.selectedFeeIndex >= 0) { + // @ts-ignore + selectedFee = countedFees.fees[countedFees.selectedFeeIndex] + } + if (typeof countedFees.additionalData !== 'undefined' && countedFees.additionalData) { + CACHE_DATA.additionalData = countedFees.additionalData + } + const transferAllBalance = countedFees.selectedTransferAllBalance + CACHE_DATA.countedFeesData = newCountedFeesData + CACHE_DATA.transferAllBalance = transferAllBalance + dispatch({ + type: 'RESET_DATA_BLOCKCHAIN', + fromBlockchain: { + countedFees, + selectedFee, + transferAllBalance, + neverCounted : false + } + }) + return { transferAllBalance : typeof transferAllBalance !== 'undefined' && transferAllBalance ? transferAllBalance : 0, source : 'NEW_COUNTED', addressTo : newCountedFeesData.addressTo, selectedFee} + } catch (e) { + if (config.debug.appErrors) { + console.log('SendActionsBlockchainWrapper.getTransferAllBalance error ' + e.message) + } + if (typeof e.message === 'undefined' ) { + Log.log('SendActionsBlockchainWrapper.getTransferAllBalance strange error') + } else if (e.message.indexOf('SERVER_RESPONSE_') !== -1) { + const extend = BlocksoftDict.getCurrencyAllSettings(newCountedFeesData?.currencyCode) + Log.errorTranslate(e, 'SendActionsBlockchainWrapper.getTransferAllBalance ', extend) + showModal({ + type: 'INFO_MODAL', + icon: null, + title: strings('modal.exchange.sorry'), + description: e.message + }) + } else { + Log.log('SendActionsBlockchainWrapper.getTransferAllBalance inner error ' + e.message) + } + } + return { transferAllBalance : 0, source : 'ERROR', addressTo : '?'} + } + + export const actualSend = async (sendScreenStore : any, uiErrorConfirmed: any, selectedFee : any, newCryptoValue : any) => { + const newCountedFeesData = { ...CACHE_DATA.countedFeesData } + const { ui } = sendScreenStore + const { bse, dexOrderData, rawOnly, contractCallData } = ui + const { bseOrderId, bseMinCrypto } = bse + + const transactionFilterType = ui.transactionFilterType + + if (selectedFee === false) { + selectedFee = {} + } + if (typeof bseOrderId !== 'undefined' && bseOrderId) { + selectedFee.bseOrderId = bseOrderId + } + if (typeof bseMinCrypto !== 'undefined' && bseMinCrypto) { + selectedFee.bseMinCrypto = bseMinCrypto + } + if (typeof dexOrderData !== 'undefined') { + newCountedFeesData.dexOrderData = dexOrderData + } + if (typeof contractCallData !== 'undefined') { + newCountedFeesData.contractCallData = contractCallData + } + + newCountedFeesData.addressTo = ui.addressTo + newCountedFeesData.amount = ui.cryptoValue + if (typeof newCryptoValue !== 'undefined' && newCryptoValue) { + newCountedFeesData.amount = newCryptoValue + } + newCountedFeesData.memo = ui.memo + newCountedFeesData.isTransferAll = ui.isTransferAll + + selectedFee.rawOnly = rawOnly || false + + return BlocksoftTransfer.sendTx(newCountedFeesData, { uiErrorConfirmed, selectedFee, transactionFilterType }, CACHE_DATA.additionalData) + } +} diff --git a/src/contexts/SendCrypto/helpers/SendActionsContactBook.ts b/src/contexts/SendCrypto/helpers/SendActionsContactBook.ts new file mode 100644 index 000000000..c7198970b --- /dev/null +++ b/src/contexts/SendCrypto/helpers/SendActionsContactBook.ts @@ -0,0 +1,181 @@ +/** + * @version 0.41 + */ +import { + isFioAddressValid, + isFioAddressRegistered, + resolveChainCode, + resolveChainToken, + getPubAddress +} from '@crypto/blockchains/fio/FioUtils' +import { isUnstoppableAddressValid } from '@crypto/services/UnstoppableUtils' + +import Resolution, { ResolutionError, ResolutionErrorCode } from '@unstoppabledomains/resolution' + +import BlocksoftDict from '@crypto/common/BlocksoftDict' +import Log from '@app/services/Log/Log' +import { strings } from '@app/services/i18n' +import config from '@app/config/config' +import store from '@app/store' +import { getEnsAddress, isEnsAddressValid } from '@crypto/services/EnsUtils' +import OneUtils from '@crypto/blockchains/one/ext/OneUtils' + + +const translateResolutionError = (domain: string, errorCode: ResolutionErrorCode, ticker: string) => { + switch (errorCode) { + case ResolutionErrorCode.UnregisteredDomain: + case ResolutionErrorCode.RecordNotFound: + case ResolutionErrorCode.UnspecifiedResolver: { + const tkey = `validator.unstoppableErrors.${errorCode}` + return strings(tkey, { domain, ticker }) + } + default: { + return errorCode + } + } +} + +export namespace SendActionsContactBook { + + let DOMAIN_RESOLUTION = false + + export const getContactAddressUnstoppable = async function(data: { addressName: string, currencyCode: string }): Promise { + if (!isUnstoppableAddressValid(data.addressName)) { + return false + } + + if (DOMAIN_RESOLUTION === false) { + try { + // @ts-ignore + DOMAIN_RESOLUTION = new Resolution() + } catch (e) { + Log.log('SendActionsContactBook.getContactAddressUnstoppable init error' + e.message) + return false + } + } + Log.log('SendActionsContactBook.getContactAddressUnstoppable checking ' + data.addressName) + let address = false + try { + // @ts-ignore + address = await DOMAIN_RESOLUTION.addr(data.addressName, data.currencyCode) + Log.log('SendActionsContactBook.getContactAddressUnstoppable checked ' + address) + } catch (err) { + Log.log('SendActionsContactBook.getContactAddressUnstoppable error ' + err.message) + if (err instanceof ResolutionError) { + const errorMessage = translateResolutionError(data.addressName, err.code, data.currencyCode) + throw new Error(errorMessage) + } else { + throw err + } + } + return address + } + + export const getContactAddressEns = async function(data: { addressName: string, currencyCode: string }): Promise { + if (!isEnsAddressValid(data.addressName)) { + return false + } + + Log.log('SendActionsContactBook.getContactAddressEns checking ' + data.addressName) + let address = false + try { + // @ts-ignore + address = await getEnsAddress(data.addressName) + Log.log('SendActionsContactBook.getContactAddressEns checked ' + address) + } catch (err) { + Log.log('SendActionsContactBook.getContactAddressEns error ' + err.message) + throw new Error(strings('send.errors.SERVER_RESPONSE_BAD_DESTINATION')) + } + return address + } + + export const getContactAddressWalletName = async function(data: { addressName: string, currencyCode: string }): Promise { + Log.log('SendActionsContactBook.getContactAddressName checking ' + data.addressName) + try { + const address = data.addressName.toLowerCase() + const selectedWallets = store.getState().walletStore.wallets + for (const selectedWallet of selectedWallets) { + if (selectedWallet.walletName.toLowerCase() === address) { + const selectedAccounts = store.getState().accountStore.accountList + if (typeof selectedAccounts[selectedWallet.walletHash] !== 'undefined' && typeof selectedAccounts[selectedWallet.walletHash][data.currencyCode] !== 'undefined') { + return selectedAccounts[selectedWallet.walletHash][data.currencyCode].address + } + } + } + Log.log('SendActionsContactBook.getContactAddressName checked ' + address) + } catch (err) { + Log.log('SendActionsContactBook.getContactAddressName error ' + err.message) + } + return false + } + + + export const getContactAddress = async function(data: { addressName: string, currencyCode: string }): Promise { + + let isUiError = false + let uiError = '' + + let res = await getContactAddressWalletName(data) + if (res) { + return res + } + + try { + if (OneUtils.isOneAddress(data.addressName)) { + res = OneUtils.fromOneAddress(data.addressName) + } + } catch (e) { + Log.log('SendActionsContactBook.getContactAddress oneAddress error ' + e.message) + } + if (res) { + return res + } + + res = await getContactAddressUnstoppable(data) + if (res) { + return res + } + res = await getContactAddressEns(data) + if (res) { + return res + } + + try { + + if (!isFioAddressValid(data.addressName)) return false + + Log.log('SendActionsContactBook.getContactAddress isFioAddress checked ' + data.addressName) + + const currencyCode = data.currencyCode + if (await isFioAddressRegistered(data.addressName)) { + Log.log('SendActionsContactBook.getContactAddress isFioAddressRegistered checked ' + data.addressName) + + const extend = BlocksoftDict.getCurrencyAllSettings(data.currencyCode) + const chainCode = resolveChainCode(currencyCode, extend.currencySymbol) + const chainToken = resolveChainToken(currencyCode, extend) + const publicFioAddress = await getPubAddress(data.addressName, chainCode, chainToken) + Log.log('SendActionsContactBook.getContactAddress public for ' + data.addressName + ' ' + chainCode + ' =>' + publicFioAddress) + if (!publicFioAddress || publicFioAddress === '0') { + uiError = strings('send.publicFioAddressNotFound', { symbol: currencyCode }) + isUiError = true + } else { + return publicFioAddress + } + } else { + Log.log('SendActionsContactBook.getContactAddress isFioAddressRegistered no result ' + data.addressName) + uiError = strings('send.publicFioAddressNotFound', { symbol: currencyCode }) + isUiError = true + } + + } catch (e) { + if (config.debug.appErrors) { + console.log('SendActionsContactBook.getContactAddress isFioAddress error ' + data.addressName + ' => ' + e.message) + } + Log.log('SendActionsContactBook.getContactAddress isFioAddress error ' + data.addressName + ' => ' + e.message) + } + if (isUiError) { + throw new Error(uiError) + } + return false + } +} diff --git a/src/contexts/SendCrypto/helpers/SendActionsEnd.ts b/src/contexts/SendCrypto/helpers/SendActionsEnd.ts new file mode 100644 index 000000000..efaf4befe --- /dev/null +++ b/src/contexts/SendCrypto/helpers/SendActionsEnd.ts @@ -0,0 +1,387 @@ +/** + * @version 0.41 + */ +import analytics from '@react-native-firebase/analytics' + +import NavStore from '@app/components/navigation/NavStore' +import { setBseLink } from '@app/appstores/Stores/Main/MainStoreActions' +import store from '@app/store' +import Log from '@app/services/Log/Log' + +import BlocksoftUtils from '@crypto/common/BlocksoftUtils' + +import config from '@app/config/config' +import ApiRates from '@app/services/Api/ApiRates' +import MarketingEvent from '@app/services/Marketing/MarketingEvent' +import transactionActions from '@app/appstores/Actions/TransactionActions' + +import ApiV3 from '@app/services/Api/ApiV3' +import { recordFioObtData } from '@crypto/blockchains/fio/FioUtils' + +import TransactionFilterTypeDict from '@appV2/dicts/transactionFilterTypeDict' +import walletConnectActions from '@app/appstores/Stores/WalletConnect/WalletConnectStoreActions' + +const logFio = async function(transaction: any, tx: any, logData: any, sendScreenStore: any) { + const { fioRequestDetails } = sendScreenStore.ui + + if (typeof fioRequestDetails === 'undefined' || !fioRequestDetails) return + + await recordFioObtData({ + fioRequestId: fioRequestDetails.fio_request_id, + payerFioAddress: fioRequestDetails.payer_fio_address, + payeeFioAddress: fioRequestDetails.payee_fio_address, + payerTokenPublicAddress: transaction.addressFromBasic, + payeeTokenPublicAddress: transaction.addressToBasic, + amount: transaction.addressAmount, + chainCode: transaction.currencyCode, + tokenCode: transaction.currencyCode, + obtId: transaction.transactionHash, + memo: fioRequestDetails.content.memo + }) +} + +const logSendSell = async function(transaction: any, tx: any, logData: any, sendScreenStore: any) { + const { bseOrderId, bseTrusteeFee, bseOrderData } = sendScreenStore.ui.bse + + if (typeof bseOrderId === 'undefined' || !bseOrderId) return + + transaction.bseOrderId = bseOrderId + transaction.bseOrderIdOut = bseOrderId + if (typeof bseOrderData !== 'undefined' && bseOrderData) { + transaction.bseOrderData = bseOrderData + } + logData.bseOrderId = bseOrderId.toString() + + const params = { + transactionHash: transaction.transactionHash, + orderHash: bseOrderId, + status: 'SUCCESS' + } + ApiV3.setExchangeStatus(params) + + + // https://rnfirebase.io/reference/analytics#logPurchase + let usdValue = bseTrusteeFee.value + let localCurrency = bseTrusteeFee.currencyCode.toLowerCase() + let usdCurrency = 'usd' + Log.log('sendScreenData.bseTrusteeFee in ' + localCurrency + ' ' + usdValue, bseTrusteeFee) + let rate = false + try { + if (usdValue * 1 > 0 && localCurrency) { + if (localCurrency !== 'usd') { + rate = ApiRates.getRatesWithLocal() + if (typeof rate !== 'undefined' && rate) { + if (localCurrency.indexOf('usdt') !== -1) { + usdValue = typeof rate.usdttousd !== 'undefined' && rate.usdttousd > 0 ? BlocksoftUtils.mul(rate.usdttousd, usdValue) : usdValue + Log.log('sendScreenData.bseTrusteeFee rate1 ' + rate.usdttousd + ' => ' + usdValue) + } else if (typeof rate['usdto' + localCurrency] !== 'undefined') { + usdValue = BlocksoftUtils.div(usdValue, rate['usdto' + localCurrency]) + Log.log('sendScreenData.bseTrusteeFee rate2 ' + rate['usdto' + localCurrency] + ' => ' + usdValue) + } else if (typeof rate[localCurrency] !== 'undefined') { + usdValue = BlocksoftUtils.div(usdValue, rate[localCurrency]) + Log.log('sendScreenData.bseTrusteeFee rate3 ' + rate[localCurrency] + ' => ' + usdValue) + } else { + Log.log('sendScreenData.bseTrusteeFee rate4 not found ' + localCurrency) + usdCurrency = 'uah' + } + } + } + } + } catch (e) { + e.message += ' while usdValue calculation ' + localCurrency + ' ' + JSON.stringify(rate) + throw e + } + let gaParams = {} + try { + gaParams = { + value: usdValue * 1, + currency: usdCurrency, + items: [{ + item_brand: bseTrusteeFee.type, + item_category: bseTrusteeFee.from, + item_category2: bseTrusteeFee.to, + item_id: bseOrderId, + item_name: bseTrusteeFee.from + '_' + bseTrusteeFee.to, + quantity: transaction.addressAmount * 1 + }] + } + const gaParamsStr = JSON.stringify(gaParams) + + await MarketingEvent.logEvent('v20_sell_to_card', gaParamsStr, 'SELL') + await Log.log('v20_sell_to_card', gaParamsStr) + await analytics().logAddToCart(gaParams) + } catch (e) { + if (config.debug.appErrors) { + console.log('v20_sell_tx error ' + e.message, gaParams) + } + await Log.err('v20_sell_tx error ' + e.message) + } + +} + +export namespace SendActionsEnd { + + export const endRedirect = async (tx: any, sendScreenStore: any) => { + const { currencyCode } = sendScreenStore.dict + const { uiType, tbk, walletConnectPayload } = sendScreenStore.ui + const { transactionAction } = tbk + + Log.log('SendActionsEnd.endRedirect start ', {transactionAction, uiType}) + if (typeof transactionAction !== 'undefined' && transactionAction !== '' && transactionAction) { + NavStore.goNext('AccountTransactionScreen', { + txData: { + transactionHash: tx.transactionHash, + toOpenAccountBack: true + }, + source : 'SendActionsEnd.transactionAction' + }) + } else if (uiType === 'MAIN_SCANNER') { + NavStore.reset('HomeScreen') + } else if (tx === false || uiType === 'DEEP_LINKING' || uiType === 'HOME_SCREEN') { + // account was not opened before or no tx could be done + const account = store.getState().mainStore.selectedAccount + if (!account || account.currencyCode !== currencyCode) { + NavStore.reset('HomeScreen') + } else { + NavStore.reset('AccountScreen') + } + } else if (uiType === 'SEND_SCANNER') { + await NavStore.goBack() + NavStore.goNext('AccountTransactionScreen', { + txData: { + transactionHash: tx.transactionHash, + uiType + }, + source : 'SendActionsEnd.sendScanner' + }) + } else if (uiType === 'ACCOUNT_SCREEN' || uiType === 'NFT_SCREEN') { + await NavStore.goBack() + await NavStore.goBack() + NavStore.goNext('AccountTransactionScreen', { + txData: { + transactionHash: tx.transactionHash, + uiType + }, + source : 'SendActionsEnd.AccountScreen' + }) + } else if (uiType === 'TRADE_SEND') { + setBseLink(null) + NavStore.goNext('HomeScreen', { screen: 'AccountTransactionScreen', params: { + txData: { + transactionHash: tx.transactionHash, + uiType + }, + source : 'SendActionsEnd.TradeSend' + }}) + } else if (uiType === 'WALLET_CONNECT') { + Log.log('SendActionsEnd.endRedirect walletConnect will get ' + tx.transactionHash) + await walletConnectActions.approveRequestWalletConnect(walletConnectPayload, tx.transactionHash) + NavStore.goNext('AccountTransactionScreen', { + txData: { + transactionHash: tx.transactionHash, + uiType + }, + source : 'SendActionsEnd.WalletConnect' + }) + } else if (uiType === 'TRADE_LIKE_WALLET_CONNECT') { + setBseLink(null) + const params = { + transactionHash: tx.transactionHash, + nonce: tx?.transactionJson?.nonce + } + await endClose(sendScreenStore, params) + NavStore.goBack() + } else { + // fio request etc - direct to receipt + NavStore.goBack() + } + + } + + export const endClose = async (sendScreenStore : any, params : any = false) => { + const { bse, extraData, walletConnectPayload, uiType } = sendScreenStore.ui + const { bseOrderId } = bse + const data = { extraData, ...params, orderHash: bseOrderId, status: 'CLOSE' } + if (uiType === 'WALLET_CONNECT') { + await walletConnectActions.rejectRequestWalletConnect(walletConnectPayload) + } + if (typeof bseOrderId === 'undefined' || !bseOrderId) return + return ApiV3.setExchangeStatus(data) + } + + export const saveTx = async (tx: any, sendScreenStore: any) => { + const { currencyCode, accountId, walletHash, addressFrom } = sendScreenStore.dict + const { addressTo, cryptoValue, memo, comment, bse, tbk, contractCallData, transactionFilterType, specialActionNeeded } = sendScreenStore.ui + const { selectedFee, countedFees } = sendScreenStore.fromBlockchain + const { bseMinCrypto } = bse + const { transactionAction, transactionBoost } = tbk + + const now = new Date().toISOString() + + let value = cryptoValue + if (typeof countedFees.amountForTx !== 'undefined') { + value = countedFees.amountForTx + } else if (typeof selectedFee.amountForTx !== 'undefined' ) { + value = selectedFee.amountForTx + } + + const logData = { + walletHash: walletHash, + currencyCode: currencyCode, + transactionHash: tx.transactionHash, + addressTo: addressTo, + addressFrom: addressFrom, + addressAmount: value, + fee: JSON.stringify(selectedFee) + } + + let transactionJson = {} + if (memo) { + transactionJson.memo = memo + } + if (comment) { + transactionJson.comment = comment + } + if (typeof bseMinCrypto !== 'undefined' && bseMinCrypto) { + transactionJson.bseMinCrypto = bseMinCrypto + logData.bseMinCrypto = bseMinCrypto.toString() + } + if (typeof contractCallData !== 'undefined' && contractCallData) { + transactionJson.contractCallData = contractCallData + logData.contractCallData = contractCallData + } + if (typeof tx.transactionJson !== 'undefined') { + let key + for (key in tx.transactionJson) { + transactionJson[key] = tx.transactionJson[key] + } + } + + let txRBF = false + let txRBFed = '' + if (typeof transactionAction !== 'undefined' && transactionAction && transactionAction !== '') { + if (transactionAction === 'transactionRemoveByFee') { + txRBF = transactionBoost.transactionHash + txRBFed = 'RBFremoved' + // @ts-ignore + logData.transactionRemoveByFee = transactionBoost.transactionHash + } else if (transactionAction === 'transactionReplaceByFee') { + txRBF = transactionBoost.transactionHash + txRBFed = 'RBFed' + // @ts-ignore + logData.transactionReplaceByFee = transactionBoost.transactionHash + } else if (transactionAction === 'transactionSpeedUp') { + // @ts-ignore + txRBFed = 'SpeedUp ' + transactionBoost.transactionHash + logData.transactionSpeedUp = transactionBoost.transactionHash + } else { + throw new Error('undefined SendActionsEnd saveTx transactionAction ' + transactionAction) + } + } + + if (txRBF) { + const transaction = { + currencyCode: currencyCode, + accountId: accountId, + transactionHash: tx.transactionHash, + transactionStatus: 'new', + addressTo: addressTo, + addressToBasic: addressTo, + addressFrom: '', + addressFromBasic: addressFrom, + addressAmount: typeof tx.amountForTx !== 'undefined' ? tx.amountForTx : cryptoValue, + transactionFee: tx.transactionFee || '', + transactionFeeCurrencyCode: tx.transactionFeeCurrencyCode || '', + transactionOfTrusteeWallet: 1, + transactionJson, + blockConfirmations: 0, + updatedAt: now, + transactionDirection: addressTo === addressFrom ? 'self' : 'outcome', + transactionFilterType: transactionFilterType || TransactionFilterTypeDict.USUAL, + transactionUpdateHash: txRBF, + transactionsOtherHashes: txRBF, + transactionsScanLog: now + ' ' + txRBFed + ' ' + txRBF + ' => ' + tx.transactionHash + ' ' + } + transaction.transactionJson.isRbfTime = new Date().getTime() + if (txRBFed === 'RBFremoved') { + transaction.addressTo = '' + transaction.addressToBasic = addressFrom + transaction.transactionDirection = 'self' + transaction.transactionJson.isRbfType = 'remove' + } else { + transaction.transactionJson.isRbfType = 'replace' + } + await transactionActions.updateTransaction(transaction) + } else { + + const transaction = { + currencyCode: currencyCode, + accountId: accountId, + walletHash: walletHash, + transactionHash: tx.transactionHash, + transactionStatus: 'new', + addressTo: addressTo, + addressToBasic: addressTo, + addressFrom: '', + addressFromBasic: addressFrom, + addressAmount: typeof tx.amountForTx !== 'undefined' ? tx.amountForTx : cryptoValue, + transactionFee: tx.transactionFee || '', + transactionFeeCurrencyCode: tx.transactionFeeCurrencyCode || '', + transactionOfTrusteeWallet: 1, + transactionJson, + blockConfirmations: 0, + createdAt: now, + updatedAt: now, + transactionFilterType: transactionFilterType || TransactionFilterTypeDict.USUAL, + transactionDirection: addressTo === addressFrom ? 'self' : 'outcome', + transactionsScanLog: now + ' CREATED ' + txRBFed, + } + if (typeof tx.amountForTx !== 'undefined') { + transaction.addressAmount = tx.amountForTx + } + if (typeof tx.blockHash !== 'undefined') { + transaction.blockHash = tx.blockHash + } + if (typeof tx.transactionStatus !== 'undefined') { + transaction.transactionStatus = tx.transactionStatus + } + if (transaction.addressTo === addressFrom) { + transaction.addressTo = '' + transaction.transactionDirection = 'self' + } + if (typeof tx.transactionDirection !== 'undefined') { + transaction.transactionDirection = tx.transactionDirection + } + if (typeof tx.transactionTimestamp !== 'undefined' && tx.transactionTimestamp) { + transaction.createdAt = new Date(tx.transactionTimestamp).toISOString() + transaction.updatedAt = new Date(tx.transactionTimestamp).toISOString() + } + if (typeof specialActionNeeded !== 'undefined' && specialActionNeeded) { + transaction.specialActionNeeded = specialActionNeeded + } + + + try { + await logSendSell(transaction, tx, logData, sendScreenStore) + } catch (e) { + Log.log('SendActionsEnd.logSendSell call error ' + e.message) + } + + try { + await logFio(transaction, tx, logData, sendScreenStore) + } catch (e) { + Log.log('SendActionsEnd.logFio call error ' + e.message) + } + + try { + const line = new Date().toISOString().replace(/T/, ' ').replace(/\..+/, '') + // @ts-ignore + await transactionActions.saveTransaction(transaction, line + ' HANDLE SEND ') + } catch (e) { + e.message += ' while transactionActions.saveTransaction' + throw e + } + } + } + +} diff --git a/src/contexts/SendCrypto/helpers/SendActionsStart.ts b/src/contexts/SendCrypto/helpers/SendActionsStart.ts new file mode 100644 index 000000000..a4a7c58bd --- /dev/null +++ b/src/contexts/SendCrypto/helpers/SendActionsStart.ts @@ -0,0 +1,560 @@ +/** + * @version 0.41 + */ +// import NavStore from '@app/components/navigation/NavStore'; + +import AirDAOPrettyNumbers from '@crypto/common/AirDAOPrettyNumbers'; +import { BlocksoftTransferUtils } from '@crypto/actions/BlocksoftTransfer/BlocksoftTransferUtils'; +import { + getFeeRate, + SendActionsBlockchainWrapper +} from './SendActionsBlockchainWrapper'; // TODO + +// import store from '@app/store'; +import trusteeAsyncStorage from '@appV2/services/trusteeAsyncStorage/trusteeAsyncStorage'; +import { setLoaderFromBse, setLoaderStatus } from '../Main/MainStoreActions'; +import BlocksoftUtils from '@crypto/common/BlocksoftUtils'; +import config from '@app/config/config'; +import Log from '@app/services/Log/Log'; + +import TransactionFilterTypeDict from '@appV2/dicts/transactionFilterTypeDict'; +import { Cache, CacheKey } from '@utils/cache'; + +const { dispatch } = store; + +let CACHE_SEND_INPUT_TYPE = 'none'; + +const findWalletPlus = function (currencyCode: string): { + wallet: any; + cryptoCurrency: any; + account: any; +} { + const { selectedWallet } = store.getState().mainStore; + const { cryptoCurrencies } = store.getState().currencyStore; + const { accountList } = store.getState().accountStore; + + let cryptoCurrency = { currencyCode: false }; + let account = false; + // @ts-ignore + for (const tmp of cryptoCurrencies) { + if (tmp.currencyCode === currencyCode) { + cryptoCurrency = tmp; + } + } + if (cryptoCurrency.currencyCode) { + // @ts-ignore + account = + accountList[selectedWallet.walletHash][cryptoCurrency.currencyCode]; + } + return { wallet: selectedWallet, cryptoCurrency, account }; +}; + +const formatDict = async function (cryptoCurrency: any, account: any) { + const dict = { + inputType: '', + derivationPath: account.derivationPath, + decimals: cryptoCurrency.decimals, + extendsProcessor: cryptoCurrency.extendsProcessor, + addressUiChecker: cryptoCurrency.addressUiChecker, + network: cryptoCurrency.network, + currencySymbol: cryptoCurrency.currencySymbol, + currencyName: cryptoCurrency.currencyName, + currencyRateScanTime: cryptoCurrency.currencyRateScanTime, + walletHash: account.walletHash, + accountId: account.accountId, + addressFrom: account.address, + currencyCode: account.currencyCode, + balanceRaw: account.balanceRaw, + balanceTotalPretty: account.balanceTotalPretty, + basicCurrencyBalanceTotal: account.basicCurrencyBalanceTotal, + basicCurrencySymbol: account.basicCurrencySymbol, + basicCurrencyCode: account.basicCurrencyCode, + basicCurrencyRate: account.basicCurrencyRate, + feesBasicCurrencyRate: + account.feeRates?.basicCurrencyRate || account.basicCurrencyRate, + feesBasicCurrencySymbol: + account.feeRates?.basicCurrencySymbol || account.basicCurrencySymbol, + feesCurrencyCode: account.feesCurrencyCode, + feesCurrencySymbol: account.feesCurrencySymbol + }; + if (CACHE_SEND_INPUT_TYPE === 'none') { + const inputType = await Cache.getItem(CacheKey.SendInputType); + CACHE_SEND_INPUT_TYPE = inputType !== 'CRYPTO' ? 'FIAT' : 'CRYPTO'; + } + dict.inputType = CACHE_SEND_INPUT_TYPE; + return dict; +}; + +export namespace SendActionsStart { + export const setBasicInputType = async (inputType: string) => { + CACHE_SEND_INPUT_TYPE = inputType; + trusteeAsyncStorage.setSendInputType(inputType); + }; + + export const startFromWalletConnect = async ( + data: { + currencyCode: string; + walletConnectData: any; + walletConnectPayload: any; + extraData: any; + transactionFilterType: any; + }, + uiType = 'WALLET_CONNECT' + ) => { + try { + Log.log('SendActionsStart.startFromWalletConnect data ', data); + + const { cryptoCurrency, account } = findWalletPlus(data.currencyCode); + if (typeof account.derivationPath === 'undefined') { + throw new Error( + 'SendActionsStart.startFromWalletConnect required account.derivationPath' + ); + } + if ( + typeof data.walletConnectData.value !== 'undefined' && + data.walletConnectData.value && + data.walletConnectData.value.toString().indexOf('0x') === 0 + ) { + data.walletConnectData.value = BlocksoftUtils.decimalToHexWalletConnect( + data.walletConnectData.value + ); + } + const dict = await formatDict(cryptoCurrency, account); + SendActionsBlockchainWrapper.beforeRender(cryptoCurrency, account); + const ui = { + uiType: uiType, + cryptoValue: data.walletConnectData.value + ? data.walletConnectData.value + : 0, + addressTo: data.walletConnectData.to, + walletConnectData: data.walletConnectData, + walletConnectPayload: data.walletConnectPayload, + extraData: data.extraData, + transactionFilterType: + data.transactionFilterType || TransactionFilterTypeDict.WALLET_CONNECT + }; + + Log.log('SendActionsStart.startFromWalletConnect ui data ', ui); + + dispatch({ + type: 'RESET_DATA', + ui, + dict + }); + + await SendActionsBlockchainWrapper.getFeeRate(ui); + if (uiType === 'TRADE_LIKE_WALLET_CONNECT') { + setLoaderFromBse(false); + NavStore.goNext('MarketReceiptScreen'); + } else { + NavStore.goNext('ReceiptScreen'); + } + } catch (e) { + Log.err(' SendActionsStart.startFromWalletConnect error ' + e.message); + } + }; + + export const startFromCustomContractCallData = async (data: { + currencyCode: string; + contractCallData: any; + }) => { + const { cryptoCurrency, account } = findWalletPlus(data.currencyCode); + const dict = await formatDict(cryptoCurrency, account); + SendActionsBlockchainWrapper.beforeRender(cryptoCurrency, account); + const ui = { + uiType: 'NFT_SCREEN', + contractCallData: data.contractCallData + }; + dispatch({ + type: 'RESET_DATA', + ui, + dict + }); + NavStore.goNext('SendScreenWithoutAmount'); + }; + + export const startFromAccountScreen = async ( + currencyCode: string, + uiType = 'ACCOUNT_SCREEN' + ) => { + const { cryptoCurrency, account } = findWalletPlus(currencyCode); + const dict = await formatDict(cryptoCurrency, account); + SendActionsBlockchainWrapper.beforeRender(cryptoCurrency, account); + dispatch({ + type: 'RESET_DATA', + ui: { + uiType + }, + dict + }); + NavStore.goNext('SendScreen'); + }; + + export const startFromHomeScreen = async ( + cryptoCurrency: any, + account: any, + uiType = 'HOME_SCREEN' + ) => { + const dict = await formatDict(cryptoCurrency, account); + SendActionsBlockchainWrapper.beforeRender(cryptoCurrency, account); + dispatch({ + type: 'RESET_DATA', + ui: { + uiType + }, + dict + }); + NavStore.goNext('SendScreen'); + }; + + export const startFromDEX = async ( + data: { + addressTo: string; + amount: string; + currencyCode: string; + dexCurrencyCode: string; + dexOrderData: { + tokenContract: string; + contractMethod: string; + options: any; + }; + }, + bse: any + ) => { + const { cryptoCurrency, account } = findWalletPlus(data.currencyCode); + const dict = await formatDict(cryptoCurrency, account); + SendActionsBlockchainWrapper.beforeRender(cryptoCurrency, account); + const ui = { + uiType: 'TRADE_SEND', + cryptoValue: data.amount, + addressTo: 'DEX ' + data.addressTo, + dexCurrencyCode: data.dexCurrencyCode, + dexOrderData: data.dexOrderData, + bse, + transactionFilterType: TransactionFilterTypeDict.SWAP + }; + dispatch({ + type: 'RESET_DATA', + ui, + dict + }); + setLoaderFromBse(true); + await SendActionsBlockchainWrapper.getFeeRate(ui); + NavStore.goNext('MarketReceiptScreen'); + }; + + export const getTransferAllBalanceFromBSE = async (data: { + currencyCode: string; + address: string; + memo: string; + }) => { + // @ts-ignore + const addressToForTransferAll = + BlocksoftTransferUtils.getAddressToForTransferAll(data); + const { cryptoCurrency, account } = findWalletPlus(data.currencyCode); + const dict = await formatDict(cryptoCurrency, account); + SendActionsBlockchainWrapper.beforeRender(cryptoCurrency, account, { + addressTo: addressToForTransferAll, + amount: '0', + memo: data.memo || false + }); + const ui = { + uiType: 'TRADE_SEND', + addressTo: addressToForTransferAll, + cryptoValue: '0', + isTransferAll: true, + memo: data.memo || false, + transactionFilterType: TransactionFilterTypeDict.SWAP + }; + dispatch({ + type: 'RESET_DATA', + ui, + dict + }); + const res = await SendActionsBlockchainWrapper.getTransferAllBalance(); + return res; + }; + + export const startFromBSE = async ( + data: { + amount: string; + address: string; + addressTo: string; // wtf now address in data - ok, but support old notation + memo: string; + comment: string; + currencyCode: string; + useAllFunds: boolean; + }, + bse: { + bseProviderType: any; + bseOrderId: any; + bseMinCrypto: any; + bseTrusteeFee: any; + bseOrderData: any; + payway: any; + forceExecAmount: boolean; + }, + selectedFee: any + ) => { + const addressTo = + typeof data.addressTo !== 'undefined' ? data.addressTo : data.address; + const { cryptoCurrency, account } = findWalletPlus(data.currencyCode); + const dict = await formatDict(cryptoCurrency, account); + const amount = AirDAOPrettyNumbers.setCurrencyCode( + data.currencyCode + ).makeUnPretty(data.amount); + SendActionsBlockchainWrapper.beforeRender(cryptoCurrency, account, { + addressTo, + amount: amount, + memo: data.memo + }); + const ui = { + uiType: 'TRADE_SEND', + addressTo, + memo: data.memo, + comment: data.comment || '', + cryptoValue: amount, + isTransferAll: data.useAllFunds, + bse, + transactionFilterType: TransactionFilterTypeDict.SWAP + }; + dispatch({ + type: 'RESET_DATA', + ui, + dict + }); + setLoaderFromBse(true); + await SendActionsBlockchainWrapper.getFeeRate(ui, selectedFee); + NavStore.goNext('MarketReceiptScreen'); + }; + + export const startFromTransactionScreenBoost = async ( + accountSelected: any, + transaction: any + ) => { + const { cryptoCurrency, account } = findWalletPlus( + accountSelected.currencyCode + ); + if (typeof account.derivationPath === 'undefined') { + throw new Error( + 'SendActionsStart.startFromTransactionScreenBoost required account.derivationPath' + ); + } + const dict = await formatDict(cryptoCurrency, account); + + const ui = { + uiType: 'TRANSACTION_SCREEN', + cryptoValue: transaction.addressAmount, + tbk: { + transactionBoost: transaction, + transactionAction: 'transactionReplaceByFee' + }, + addressTo: transaction.addressTo, + bse: {}, + comment: '' + }; + if (transaction.transactionDirection === 'income') { + ui.tbk.transactionAction = 'transactionSpeedUp'; + ui.addressTo = account.address; + } else { + if (typeof transaction.bseOrderId !== 'undefined') { + ui.bse.bseOrderId = transaction.bseOrderId; + } + if ( + typeof transaction.transactionJson !== 'undefined' && + transaction.transactionJson !== {} && + transaction.transactionJson + ) { + if (typeof transaction.transactionJson.bseMinCrypto !== 'undefined') { + ui.bse.bseMinCrypto = transaction.transactionJson.bseMinCrypto; + } + if (transaction.transactionJson.comment !== 'undefined') { + ui.comment = transaction.transactionJson.comment; + } + } + } + + SendActionsBlockchainWrapper.beforeRender(cryptoCurrency, account, { + addressTo: ui.addressTo, + amount: transaction.addressAmount, + tbk: ui.tbk + }); + dispatch({ + type: 'RESET_DATA', + ui, + dict + }); + + await SendActionsBlockchainWrapper.getFeeRate(ui); + NavStore.goNext('ReceiptScreen'); + }; + + export const startFromTransactionScreenRemove = async ( + accountSelected: any, + transaction: any + ) => { + const { cryptoCurrency, account } = findWalletPlus( + accountSelected.currencyCode + ); + if (typeof account.derivationPath === 'undefined') { + throw new Error( + 'SendActionsStart.startFromTransactionScreenBoost required account.derivationPath' + ); + } + const dict = await formatDict(cryptoCurrency, account); + + const ui = { + uiType: 'TRANSACTION_SCREEN', + cryptoValue: transaction.addressAmount, + tbk: { + transactionBoost: transaction, + transactionAction: 'transactionRemoveByFee' + }, + addressTo: account.address, + bse: {}, + comment: '' + }; + if (typeof transaction.bseOrderId !== 'undefined') { + ui.bse.bseOrderId = transaction.bseOrderId; + } + if ( + typeof transaction.transactionJson !== 'undefined' && + transaction.transactionJson && + transaction.transactionJson !== {} + ) { + if ( + typeof transaction.transactionJson.comment !== 'undefined' && + transaction.transactionJson.comment + ) { + ui.comment = transaction.transactionJson.comment; + } + } + SendActionsBlockchainWrapper.beforeRender(cryptoCurrency, account, { + addressTo: ui.addressTo, + amount: transaction.addressAmount, + tbk: ui.tbk + }); + dispatch({ + type: 'RESET_DATA', + ui, + dict + }); + + await SendActionsBlockchainWrapper.getFeeRate(ui); + NavStore.goNext('ReceiptScreen'); + }; + + export const startFromDeepLinking = async ( + data: { + needToDisable?: boolean; + address: string; + amount: string | number; + currencyCode: string; + label: string; + }, + uiType = 'DEEP_LINKING' + ) => { + const { cryptoCurrency, account } = findWalletPlus(data.currencyCode); + const dict = await formatDict(cryptoCurrency, account); + const addressTo = data.address ? data.address : ''; + const amount = data.amount ? data.amount.toString() : '0'; + const amountRaw = AirDAOPrettyNumbers.setCurrencyCode( + data.currencyCode + ).makeUnPretty(amount); + + SendActionsBlockchainWrapper.beforeRender(cryptoCurrency, account, { + addressTo: addressTo, + amount: amountRaw + }); + const ui = { + uiType, + addressTo: addressTo, + comment: data.label || '', + cryptoValue: amountRaw + }; + dispatch({ + type: 'RESET_DATA', + ui, + dict + }); + + if ( + typeof data.needToDisable !== 'undefined' && + data.needToDisable && + addressTo && + addressTo !== '' && + amount && + amount !== '' && + amount !== '0' + ) { + await SendActionsBlockchainWrapper.getFeeRate(ui); + NavStore.goNext('ReceiptScreen'); + } else { + NavStore.goNext('SendScreen'); + } + }; + + export const startFromQRCodeScanner = async ( + data: any, + uiType = 'MAIN_SCANNER' + ) => { + return startFromDeepLinking(data, uiType); + }; + + export const startFromFioRequest = async ( + currencyCode: any, + fioRequestDetails: { + // eslint-disable-next-line camelcase + content: { amount: string; memo: string; payee_public_address: string }; + // eslint-disable-next-line camelcase + fio_request_id: number; + // eslint-disable-next-line camelcase + payee_fio_address: string; + // eslint-disable-next-line camelcase + payee_fio_public_key: string; + // eslint-disable-next-line camelcase + payer_fio_address: string; + // eslint-disable-next-line camelcase + payer_fio_public_key: string; + // eslint-disable-next-line camelcase + time_stamp: string; + } + ) => { + const { cryptoCurrency, account } = findWalletPlus(currencyCode); + const dict = await formatDict(cryptoCurrency, account); + + const amount = fioRequestDetails.content.amount; + const amountRaw = + AirDAOPrettyNumbers.setCurrencyCode(currencyCode).makeUnPretty(amount); + + SendActionsBlockchainWrapper.beforeRender(cryptoCurrency, account, { + addressTo: fioRequestDetails.content.payee_public_address, + amount: amountRaw + }); + const ui = { + uiType: 'FIO_REQUEST', + addressTo: fioRequestDetails.content.payee_public_address, + addressName: fioRequestDetails.payee_fio_address, + comment: fioRequestDetails.content.memo, + cryptoValue: amountRaw, + fioRequestDetails + }; + dispatch({ + type: 'RESET_DATA', + ui, + dict + }); + + await SendActionsBlockchainWrapper.getFeeRate(ui); + NavStore.goNext('ReceiptScreen'); + }; + + export const getAccountFormatData = async (data: { + currencyCode: string; + }) => { + const { cryptoCurrency, account } = findWalletPlus(data.currencyCode); + const dict = await formatDict(cryptoCurrency, account); + + return { dict }; + }; +} diff --git a/src/contexts/SendCrypto/helpers/SendActionsUpdateValues.ts b/src/contexts/SendCrypto/helpers/SendActionsUpdateValues.ts new file mode 100644 index 000000000..8c7ccc72e --- /dev/null +++ b/src/contexts/SendCrypto/helpers/SendActionsUpdateValues.ts @@ -0,0 +1,109 @@ +/* eslint-disable @typescript-eslint/no-namespace */ +/** + * @version 0.41 + */ +import { useSendCryptoContext } from '../SendCrypto.context'; +import { SendActionsBlockchainWrapper } from './SendActionsBlockchainWrapper'; + +let CACHE_SELECTED_FEE = false; + +export const useSendActionsUpdateValues = () => { + const { state, reducer: dispatch } = useSendCryptoContext((v) => v); + const setStepOne = (data: { + cryptoValue: string; + addressTo: string; + addressName: string; + memo: string; + isTransferAll: boolean; + }) => { + dispatch({ + type: 'SET_DATA', + ui: data + }); + }; + + const setDict = ( + dictNew: { + balanceRaw: number; + addressFrom?: string; + currencyCode?: string; + } = { balanceRaw: 0 } + ) => { + const { dict } = state; + if (!dict) return; + if ( + dict.addressFrom === dictNew.addressFrom && + dict.currencyCode === dictNew.currencyCode + ) { + dispatch({ + type: 'SET_DATA', + dict: dictNew + }); + } + }; + + const setCommentAndFeeFromTmp = async (comment: string, rawOnly = false) => { + if (!CACHE_SELECTED_FEE) { + dispatch({ + type: 'SET_DATA', + ui: { + comment, + rawOnly, + cryptoValueRecounted: 0 + } + }); + } else { + const ui = { + comment, + rawOnly, + cryptoValueRecounted: 0 + }; + + let newFee = false; + if ( + typeof CACHE_SELECTED_FEE.isCustomFee !== 'undefined' && + CACHE_SELECTED_FEE.isCustomFee + ) { + const countedCustomFee = + await SendActionsBlockchainWrapper.getCustomFeeRate( + CACHE_SELECTED_FEE + ); + if (countedCustomFee) { + newFee = countedCustomFee; + newFee.isCustomFee = true; + } + } else { + newFee = CACHE_SELECTED_FEE; + } + if ( + newFee && + typeof newFee.amountForTx !== 'undefined' && + newFee.amountForTx + ) { + // @ts-ignore + ui.cryptoValue = newFee.amountForTx; + ui.cryptoValueRecounted = new Date().getTime(); + } + dispatch({ + type: 'SET_DATA', + ui, + fromBlockchain: { + selectedFee: newFee + } + }); + } + }; + + const setTmpSelectedFee = (selectedFee: any) => { + CACHE_SELECTED_FEE = selectedFee; + }; + + const SendActionsUpdateValues = { + setStepOne, + setCommentAndFeeFromTmp, + setDict, + setTmpSelectedFee + }; + + return SendActionsUpdateValues; +}; diff --git a/src/contexts/SendCrypto/helpers/index.ts b/src/contexts/SendCrypto/helpers/index.ts new file mode 100644 index 000000000..d703eb3f7 --- /dev/null +++ b/src/contexts/SendCrypto/helpers/index.ts @@ -0,0 +1,5 @@ +export * from './SendActionsBlockchainWrapper'; +export * from './SendActionsContactBook'; +export * from './SendActionsEnd'; +export * from './SendActionsStart'; +export * from './SendActionsUpdateValues'; diff --git a/src/contexts/SendCrypto/index.ts b/src/contexts/SendCrypto/index.ts new file mode 100644 index 000000000..e69de29bb diff --git a/src/database/models/wallet.ts b/src/database/models/wallet.ts index 9d8fb90b5..db6777397 100644 --- a/src/database/models/wallet.ts +++ b/src/database/models/wallet.ts @@ -1,5 +1,6 @@ import { DatabaseTable } from '@appTypes'; -import { Model } from '@nozbe/watermelondb'; +import Database from '@database/Database'; +import { Model, Q } from '@nozbe/watermelondb'; import { text, field } from '@nozbe/watermelondb/decorators'; export class WalletDBModel extends Model { @@ -32,4 +33,13 @@ export class WalletDBModel extends Model { @field('is_created_here') isCreatedHere: number; // @ts-ignore @field('to_send_status') toSendStatus: number; + + static async getByHash(hash: string): Promise { + const walletInDB = (await Database.query( + this.table, + Q.where('hash', Q.eq(hash)) + )) as WalletDBModel[]; + if (walletInDB && walletInDB.length > 0) return walletInDB[0]; + return null; + } } diff --git a/src/navigation/stacks/Tabs/WalletStack.tsx b/src/navigation/stacks/Tabs/WalletStack.tsx index 5a30a94b7..df746dc1b 100644 --- a/src/navigation/stacks/Tabs/WalletStack.tsx +++ b/src/navigation/stacks/Tabs/WalletStack.tsx @@ -11,26 +11,29 @@ import { RestoreWalletScreen } from '@screens/Wallet/RestoreWallet'; import { getCommonStack } from '@navigation/stacks/CommonStack'; import { WalletAccount } from '@screens/Wallet/Account'; import { ReceiptScreen } from '@screens/Wallet/Receipt'; +import { SendCryptoProvider } from '@contexts/SendCrypto/SendCrypto.context'; const Stack = createNativeStackNavigator(); export const WalletStack = () => { return ( - - - - - - - - - {getCommonStack(Stack as any)} - + + + + + + + + + + {getCommonStack(Stack as any)} + + ); }; diff --git a/src/screens/Wallet/Account/index.tsx b/src/screens/Wallet/Account/index.tsx index 9d2edae9c..b12ee189f 100644 --- a/src/screens/Wallet/Account/index.tsx +++ b/src/screens/Wallet/Account/index.tsx @@ -71,6 +71,7 @@ export const WalletAccount = () => { const info = await AirDAOKeysForRef.discoverPublicAndPrivate({ mnemonic: wallet.mnemonic }); + console.log({ info }); if (info) { const { address } = info; // @ts-ignore @@ -88,7 +89,9 @@ export const WalletAccount = () => { }; useEffect(() => { - getWalletInfo(); + setTimeout(() => { + getWalletInfo(); // delay the call + }, 0); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); @@ -102,10 +105,16 @@ export const WalletAccount = () => { Alert.alert('Check destination address'); return; } + if (!account) { + // TODO handle + return; + } navigation.navigate('ReceiptScreen', { amount: intAmount, currencyCode: 'AMB', - destination: addressToSend + destination: addressToSend, + origin: account.address, + hash: wallet.hash }); }; diff --git a/src/screens/Wallet/Receipt/index.tsx b/src/screens/Wallet/Receipt/index.tsx index 7416b30bc..9fda69211 100644 --- a/src/screens/Wallet/Receipt/index.tsx +++ b/src/screens/Wallet/Receipt/index.tsx @@ -1,16 +1,114 @@ +import React, { useEffect, useRef, useState } from 'react'; +import { SafeAreaView, View } from 'react-native'; +import { API } from '@api/api'; import { WalletStackParamsList } from '@appTypes'; +import { Row, Text } from '@components/base'; import { Header } from '@components/composite'; +import { PrimaryButton } from '@components/modular'; +import { BlocksoftTransfer } from '@crypto/actions/BlocksoftTransfer/BlocksoftTransfer'; +import { AirDAODictTypes } from '@crypto/common/AirDAODictTypes'; +import { WalletDBModel } from '@database'; +import AirDAOKeysForRef from '@lib/helpers/AirDAOKeysForRef'; +import { Wallet } from '@models/Wallet'; import { RouteProp, useRoute } from '@react-navigation/native'; -import React from 'react'; -import { SafeAreaView } from 'react-native'; +import { scale, verticalScale } from '@utils/scaling'; +import { StringUtils } from '@utils/string'; +import { ExplorerAccount } from '@models'; export const ReceiptScreen = () => { const route = useRoute>(); - const { amount, currencyCode, destination } = route.params; + const { amount, currencyCode, hash, destination, origin } = route.params; + const [sending, setSending] = useState(false); + const wallet = useRef(null); + const account = useRef(); + + const init = async () => { + const _walletInDB = await WalletDBModel.getByHash(hash); + if (!_walletInDB) { + // TODO show error + return; + } + wallet.current = Wallet.fromDBModel(_walletInDB); + const _account = await API.explorerService.searchAddress(origin); + if (!_account) { + // TODO show error + return; + } + account.current = new ExplorerAccount(_account); + }; + + useEffect(() => { + init(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const sendCrypto = async () => { + console.log({ wallet: wallet.current, account: account.current }); + if (!wallet.current || !account.current) return; + try { + setSending(true); + // const walletInDB = await WalletDBModel.getByHash(hash); + // console.log({ walletInDB }); + // if (!walletInDB) return; + // const wallet = Wallet.fromDBModel(walletInDB); + // const account = await API.explorerService.searchAddress(origin); + const info = await AirDAOKeysForRef.discoverPublicAndPrivate({ + mnemonic: wallet.current.mnemonic + }); + await BlocksoftTransfer.sendTx( + { + currencyCode: AirDAODictTypes.Code.ETH, + walletHash: hash, + derivationPath: info.path, + addressFrom: origin, + addressTo: destination, + amount: amount.toString(), + useOnlyConfirmed: Boolean(wallet.current.useUnconfirmed), + allowReplaceByFee: Boolean(wallet.current.allowReplaceByFee), + useLegacy: wallet.current.useLegacy, + isHd: Boolean(wallet.current.isHd), + + accountBalanceRaw: account.current.ambBalance.toString(), + isTransferAll: false + }, + { + uiErrorConfirmed: true, + selectedFee: { + langMsg: '', + feeForTx: '', + amountForTx: '' + } + }, // TODO fix selected fee + // CACHE_DATA.additionalData + {} + ); + } catch (error) { + console.log({ error }); + } finally { + setSending(false); + } + }; return (
+ + + Amount: + + {amount} {currencyCode} + + + + Destination: + {StringUtils.formatAddress(destination, 9, 6)} + + + Send + + ); }; diff --git a/src/utils/cache.ts b/src/utils/cache.ts index 30575a84c..1d3817eb7 100644 --- a/src/utils/cache.ts +++ b/src/utils/cache.ts @@ -14,7 +14,8 @@ export enum CacheKey { Onboarding = 'onboarding', Watchlist = 'watchlist', LastNotificationTimestamp = 'last_notification_timestamp', - PreCreatedGroupWasCreated = 'pre_created_group_was_created' + PreCreatedGroupWasCreated = 'pre_created_group_was_created', + SendInputType = 'send_input_type' } const getNotificationSettings = async (): Promise => { diff --git a/src/utils/send-crypto-wrapper.ts b/src/utils/send-crypto-wrapper.ts new file mode 100644 index 000000000..fe8b088ef --- /dev/null +++ b/src/utils/send-crypto-wrapper.ts @@ -0,0 +1,444 @@ +/* eslint-disable @typescript-eslint/no-namespace */ +/** + * @version 0.41 + */ +import { BlocksoftTransfer } from '@crypto/actions/BlocksoftTransfer/BlocksoftTransfer'; +import { AirDAOBlockchainTypes } from '@crypto/blockchains/AirDAOBlockchainTypes'; + +// import store from '@app/store'; +// import config from '@app/config/config'; +// import Log from '@app/services/Log/Log'; +import { BlocksoftTransferUtils } from '@crypto/actions/BlocksoftTransfer/BlocksoftTransferUtils'; +// import { showModal } from '@app/appstores/Stores/Modal/ModalActions'; +// import { strings } from '@app/services/i18n'; +import AirDAODict from '@crypto/common/AirDAODict'; +import { Wallet } from '@models/Wallet'; + +// const { dispatch } = store; + +const CACHE_DATA = { + countedFeesData: {} as AirDAOBlockchainTypes.TransferData, + transferAllBalance: '0', + additionalData: false as any +}; +export namespace SendActionsBlockchainWrapper { + export const beforeRender = ( + cryptoCurrency: any, + account: any, + additional: any = {}, + selectedWallet: Wallet + ) => { + const { + hash: walletHash, + useUnconfirmed: walletUseUnconfirmed, + allowReplaceByFee: walletAllowReplaceByFee, + useLegacy: walletUseLegacy, + isHd: walletIsHd + } = selectedWallet; + const { address, currencyCode, derivationPath, accountJson } = account; + + const newCountedFeesData = { + currencyCode: currencyCode, + walletHash: walletHash, + derivationPath: derivationPath, + addressFrom: address, + addressTo: additional.addressTo || '?', + amount: additional.amount || account.balanceRaw, + memo: additional.memo || null, + accountBalanceRaw: account.balanceRaw, + isTransferAll: false, + useOnlyConfirmed: !(walletUseUnconfirmed === 1), + allowReplaceByFee: walletAllowReplaceByFee === 1, + useLegacy: walletUseLegacy, + isHd: Boolean(walletIsHd), + accountJson, + transactionJson: {} + } as AirDAOBlockchainTypes.TransferData; + + if ( + typeof additional.tbk !== 'undefined' && + additional.tbk && + additional.tbk.transactionAction + ) { + if (additional.tbk.transactionAction === 'transactionSpeedUp') { + newCountedFeesData.transactionSpeedUp = + additional.tbk.transactionBoost.transactionHash; + } else if ( + additional.tbk.transactionAction === 'transactionReplaceByFee' + ) { + newCountedFeesData.transactionReplaceByFee = + additional.tbk.transactionBoost.transactionHash; + } else if ( + additional.tbk.transactionAction === 'transactionRemoveByFee' + ) { + newCountedFeesData.transactionReplaceByFee = + additional.tbk.transactionBoost.transactionHash; + } else { + throw new Error( + 'undefined SendActionsBlockchainWrapper beforeRender transactionAction ' + + additional.tbk.transactionAction + ); + } + if ( + typeof additional.tbk.transactionBoost.transactionJson !== + 'undefined' && + additional.tbk.transactionBoost.transactionJson + ) { + newCountedFeesData.transactionJson = + additional.tbk.transactionBoost.transactionJson; + } + } + + CACHE_DATA.additionalData = false; + if ( + JSON.stringify(CACHE_DATA.countedFeesData) === + JSON.stringify(newCountedFeesData) + ) { + return; + } + if ( + newCountedFeesData.addressFrom !== CACHE_DATA.countedFeesData.addressFrom + ) { + CACHE_DATA.transferAllBalance = '0'; + } + CACHE_DATA.countedFeesData = newCountedFeesData; + }; + + export const getCustomFeeRate = async (newFee: any) => { + let newCountedFeesData = {}; + try { + newCountedFeesData = { ...CACHE_DATA.countedFeesData }; + const countedFees = await BlocksoftTransfer.getFeeRate( + newCountedFeesData, + CACHE_DATA.additionalData + ? { ...newFee, ...CACHE_DATA.additionalData } + : newFee + ); + let selectedFee = false; + if ( + typeof countedFees.selectedFeeIndex !== 'undefined' && + countedFees.selectedFeeIndex >= 0 + ) { + // @ts-ignore + selectedFee = countedFees.fees[countedFees.selectedFeeIndex]; + } + return selectedFee; + } catch (e) { + if (e.message.indexOf('SERVER_RESPONSE_') !== -1) { + } else { + // ignor + } + } + }; + + export const getFeeRate = async ( + uiData = {}, + precountedSelectedFee = false, + neverCounted = true + ) => { + let newCountedFeesData = {}; + try { + if ( + typeof uiData === 'undefined' || + typeof uiData.addressTo === 'undefined' + ) { + // uiData = store.getState().sendScreenStore.ui; + // uiData = + } + + const forceExecAmount = + typeof uiData.bse !== 'undefined' && + typeof uiData.bse.forceExecAmount !== 'undefined' && + uiData.bse.forceExecAmount; + + newCountedFeesData = { ...CACHE_DATA.countedFeesData }; + newCountedFeesData.addressTo = uiData.addressTo; + newCountedFeesData.amount = uiData.cryptoValue; + newCountedFeesData.memo = uiData.memo; + newCountedFeesData.isTransferAll = uiData.isTransferAll; + + if (newCountedFeesData.isTransferAll && !forceExecAmount) { + newCountedFeesData.amount = newCountedFeesData.accountBalanceRaw; + } + if (typeof uiData.contractCallData !== 'undefined') { + newCountedFeesData.contractCallData = uiData.contractCallData; + } + if (typeof uiData.walletConnectData !== 'undefined') { + newCountedFeesData.walletConnectData = uiData.walletConnectData; + } + if (typeof uiData.dexOrderData !== 'undefined') { + if (uiData.dexOrderData || newCountedFeesData.dexOrderData) { + newCountedFeesData.dexOrderData = uiData.dexOrderData; + } + } + if (!neverCounted) { + if ( + JSON.stringify(CACHE_DATA.countedFeesData) === + JSON.stringify(newCountedFeesData) + ) { + return { + isTransferAll: newCountedFeesData.isTransferAll, + amount: newCountedFeesData.amount, + source: 'CACHE_COUNTED', + addressTo: newCountedFeesData.addressTo + }; + } + } + let countedFees; + if (precountedSelectedFee) { + countedFees = { + fees: [precountedSelectedFee], + selectedFeeIndex: 0 + }; + } else { + try { + countedFees = await BlocksoftTransfer.getFeeRate( + newCountedFeesData, + CACHE_DATA.additionalData ? CACHE_DATA.additionalData : {} + ); + } catch (e) { + if (e.message.indexOf('SERVER') === -1) { + e.message += ' while BlocksoftTransfer.getFeeRate'; + } + throw e; + } + } + + if ( + forceExecAmount && + typeof countedFees !== 'undefined' && + countedFees && + typeof countedFees.fees !== 'undefined' && + countedFees.fees + ) { + for (let i = 0; i < countedFees.fees.length; i++) { + if (typeof countedFees.fees[i].amountForTx !== 'undefined') { + countedFees.fees[i].amountForTx = uiData.cryptoValue; + } + } + if (typeof countedFees.amountForTx !== 'undefined') { + countedFees.amountForTx = uiData.cryptoValue; + } + } + + let selectedFee = false; + if ( + typeof countedFees.selectedFeeIndex !== 'undefined' && + countedFees.selectedFeeIndex >= 0 + ) { + // @ts-ignore + selectedFee = countedFees.fees[countedFees.selectedFeeIndex]; + } + if ( + typeof countedFees.additionalData !== 'undefined' && + countedFees.additionalData + ) { + CACHE_DATA.additionalData = countedFees.additionalData; + } + CACHE_DATA.countedFeesData = newCountedFeesData; + + // dispatch({ + // type: 'RESET_DATA_BLOCKCHAIN', + // fromBlockchain: { + // countedFees, + // selectedFee, + // neverCounted: false + // } + // }); + + return { + isTransferAll: newCountedFeesData.isTransferAll, + amount: newCountedFeesData.amount, + source: 'NEW_COUNTED', + addressTo: newCountedFeesData.addressTo, + fromBlockchain: { + countedFees, + selectedFee, + neverCounted: false + } + }; + } catch (e) { + if (typeof e.message === 'undefined') { + // Log.log('SendActionsBlockchainWrapper.getFeeRate strange error'); + } else if (e.message.indexOf('SERVER_RESPONSE_') !== -1) { + // TODO + // const extend = AirDAODict.getCurrencyAllSettings( + // newCountedFeesData?.currencyCode + // ); + // Log.errorTranslate( + // e, + // 'SendActionsBlockchainWrapper.getFeeRate', + // extend + // ); + // showModal({ + // type: 'INFO_MODAL', + // icon: null, + // title: strings('modal.exchange.sorry'), + // description: e.message + // }); + } else { + // Log.log( + // 'SendActionsBlockchainWrapper.getFeeRate inner error ' + e.message + // ); + } + } + + return { source: 'ERROR', addressTo: '?' }; + }; + + export const getTransferAllBalance = async (uiData = {}) => { + const newCountedFeesData = { ...CACHE_DATA.countedFeesData }; + try { + if ( + typeof uiData === 'undefined' || + typeof uiData.addressTo === 'undefined' + ) { + uiData = store.getState().sendScreenStore.ui; + } + newCountedFeesData.addressTo = uiData.addressTo; + newCountedFeesData.amount = newCountedFeesData.accountBalanceRaw; + newCountedFeesData.memo = uiData.memo; + newCountedFeesData.isTransferAll = uiData.isTransferAll; + if ( + !newCountedFeesData.addressTo || + newCountedFeesData.addressTo === '' || + newCountedFeesData.addressTo === '?' + ) { + newCountedFeesData.addressTo = + BlocksoftTransferUtils.getAddressToForTransferAll({ + currencyCode: newCountedFeesData.currencyCode, + address: newCountedFeesData.addressFrom + }); + } + if ( + !store.getState().sendScreenStore.fromBlockchain.neverCounted && + JSON.stringify(CACHE_DATA.countedFeesData) === + JSON.stringify(newCountedFeesData) + ) { + return { + transferAllBalance: CACHE_DATA.transferAllBalance, + source: 'CACHE_COUNTED', + addressTo: newCountedFeesData.addressTo + }; + } + const countedFees = await BlocksoftTransfer.getTransferAllBalance( + newCountedFeesData, + CACHE_DATA.additionalData ? CACHE_DATA.additionalData : {} + ); + let selectedFee = false; + if ( + typeof countedFees.selectedFeeIndex !== 'undefined' && + countedFees.selectedFeeIndex >= 0 + ) { + // @ts-ignore + selectedFee = countedFees.fees[countedFees.selectedFeeIndex]; + } + if ( + typeof countedFees.additionalData !== 'undefined' && + countedFees.additionalData + ) { + CACHE_DATA.additionalData = countedFees.additionalData; + } + const transferAllBalance = countedFees.selectedTransferAllBalance; + CACHE_DATA.countedFeesData = newCountedFeesData; + CACHE_DATA.transferAllBalance = transferAllBalance; + dispatch({ + type: 'RESET_DATA_BLOCKCHAIN', + fromBlockchain: { + countedFees, + selectedFee, + transferAllBalance, + neverCounted: false + } + }); + return { + transferAllBalance: + typeof transferAllBalance !== 'undefined' && transferAllBalance + ? transferAllBalance + : 0, + source: 'NEW_COUNTED', + addressTo: newCountedFeesData.addressTo, + selectedFee + }; + } catch (e) { + if (config.debug.appErrors) { + console.log( + 'SendActionsBlockchainWrapper.getTransferAllBalance error ' + + e.message + ); + } + if (typeof e.message === 'undefined') { + Log.log( + 'SendActionsBlockchainWrapper.getTransferAllBalance strange error' + ); + } else if (e.message.indexOf('SERVER_RESPONSE_') !== -1) { + const extend = AirDAODict.getCurrencyAllSettings( + newCountedFeesData?.currencyCode + ); + Log.errorTranslate( + e, + 'SendActionsBlockchainWrapper.getTransferAllBalance ', + extend + ); + showModal({ + type: 'INFO_MODAL', + icon: null, + title: strings('modal.exchange.sorry'), + description: e.message + }); + } else { + Log.log( + 'SendActionsBlockchainWrapper.getTransferAllBalance inner error ' + + e.message + ); + } + } + return { transferAllBalance: 0, source: 'ERROR', addressTo: '?' }; + }; + + export const actualSend = async ( + sendScreenStore: any, + uiErrorConfirmed: any, + selectedFee: any, + newCryptoValue: any + ) => { + const newCountedFeesData = { ...CACHE_DATA.countedFeesData }; + const { ui } = sendScreenStore; + const { bse, dexOrderData, rawOnly, contractCallData } = ui; + const { bseOrderId, bseMinCrypto } = bse; + + const transactionFilterType = ui.transactionFilterType; + + if (selectedFee === false) { + selectedFee = {}; + } + if (typeof bseOrderId !== 'undefined' && bseOrderId) { + selectedFee.bseOrderId = bseOrderId; + } + if (typeof bseMinCrypto !== 'undefined' && bseMinCrypto) { + selectedFee.bseMinCrypto = bseMinCrypto; + } + if (typeof dexOrderData !== 'undefined') { + newCountedFeesData.dexOrderData = dexOrderData; + } + if (typeof contractCallData !== 'undefined') { + newCountedFeesData.contractCallData = contractCallData; + } + + newCountedFeesData.addressTo = ui.addressTo; + newCountedFeesData.amount = ui.cryptoValue; + if (typeof newCryptoValue !== 'undefined' && newCryptoValue) { + newCountedFeesData.amount = newCryptoValue; + } + newCountedFeesData.memo = ui.memo; + newCountedFeesData.isTransferAll = ui.isTransferAll; + + selectedFee.rawOnly = rawOnly || false; + + return BlocksoftTransfer.sendTx( + newCountedFeesData, + { uiErrorConfirmed, selectedFee, transactionFilterType }, + CACHE_DATA.additionalData + ); + }; +} From d5e3c4e96ba2d6df3e49fcaf78dda380c8c15827 Mon Sep 17 00:00:00 2001 From: illiaa Date: Thu, 17 Aug 2023 11:31:24 +0300 Subject: [PATCH 053/509] fixed errors --- crypto/actions/BlocksoftKeys/BlocksoftKeys.js | 2 +- .../BlocksoftKeys/BlocksoftKeysUtils.js | 172 +++++++++--------- .../BlocksoftKeysStorage.js | 8 +- package.json | 1 + yarn.lock | 62 ++++++- 5 files changed, 158 insertions(+), 87 deletions(-) diff --git a/crypto/actions/BlocksoftKeys/BlocksoftKeys.js b/crypto/actions/BlocksoftKeys/BlocksoftKeys.js index d3b2f2328..2295bca31 100644 --- a/crypto/actions/BlocksoftKeys/BlocksoftKeys.js +++ b/crypto/actions/BlocksoftKeys/BlocksoftKeys.js @@ -7,7 +7,7 @@ import AirDAODict from '@crypto/common/AirDAODict'; import BlocksoftKeysUtils from '@crypto/actions/BlocksoftKeys/BlocksoftKeysUtils'; import * as Crypto from 'expo-crypto'; -import BlocksoftDispatcher from '../../blockchains/BlocksoftDispatcher'; +// import BlocksoftDispatcher from '../../blockchains/BlocksoftDispatcher'; import BlocksoftKeysScam from '@crypto/actions/BlocksoftKeys/BlocksoftKeysScam'; import bip44Constants from '@crypto/common/ext/bip44-constants'; import networksConstants from '@crypto/common/ext/networks-constants'; diff --git a/crypto/actions/BlocksoftKeys/BlocksoftKeysUtils.js b/crypto/actions/BlocksoftKeys/BlocksoftKeysUtils.js index 9eaf2a1b2..4cec1d958 100644 --- a/crypto/actions/BlocksoftKeys/BlocksoftKeysUtils.js +++ b/crypto/actions/BlocksoftKeys/BlocksoftKeysUtils.js @@ -2,105 +2,115 @@ * @author Ksu * @version 0.5 */ -const createHash = require('create-hash') -const createHmac = require('create-hmac') +const createHash = require('create-hash'); +const createHmac = require('create-hmac'); const createHmacPDFK2Sizes = { - md5: 16, - sha1: 20, - sha224: 28, - sha256: 32, - sha384: 48, - sha512: 64, - rmd160: 20, - ripemd160: 20 -} + md5: 16, + sha1: 20, + sha224: 28, + sha256: 32, + sha384: 48, + sha512: 64, + rmd160: 20, + ripemd160: 20 +}; -const {pbkdf2} = require('react-native-fast-crypto') +// const { pbkdf2 } = require('react-native-fast-crypto'); -const DEFAULT_WORDS = require('./_words/english.json') +// const DEFAULT_WORDS = require('./_words/english.json'); class BlocksoftKeysUtils { + static _pbkdf2(password, salt, iterations, keylen, digest) { + digest = digest || 'sha1'; - static _pbkdf2(password, salt, iterations, keylen, digest) { - - digest = digest || 'sha1' - - const DK = Buffer.allocUnsafe(keylen) - const block1 = Buffer.allocUnsafe(salt.length + 4) - salt.copy(block1, 0, 0, salt.length) - - let destPos = 0 - const hLen = createHmacPDFK2Sizes[digest] - const l = Math.ceil(keylen / hLen) + const DK = Buffer.allocUnsafe(keylen); + const block1 = Buffer.allocUnsafe(salt.length + 4); + salt.copy(block1, 0, 0, salt.length); - for (let i = 1; i <= l; i++) { - block1.writeUInt32BE(i, salt.length) + let destPos = 0; + const hLen = createHmacPDFK2Sizes[digest]; + const l = Math.ceil(keylen / hLen); - // noinspection JSUnresolvedFunction - const T = createHmac(digest, password).update(block1).digest() - let U = T + for (let i = 1; i <= l; i++) { + block1.writeUInt32BE(i, salt.length); - for (let j = 1; j < iterations; j++) { - // noinspection JSUnresolvedFunction - U = createHmac(digest, password).update(U).digest() - for (let k = 0; k < hLen; k++) T[k] ^= U[k] - } + // noinspection JSUnresolvedFunction + const T = createHmac(digest, password).update(block1).digest(); + let U = T; - T.copy(DK, destPos) - destPos += hLen - } + for (let j = 1; j < iterations; j++) { + // noinspection JSUnresolvedFunction + U = createHmac(digest, password).update(U).digest(); + for (let k = 0; k < hLen; k++) T[k] ^= U[k]; + } - return DK + T.copy(DK, destPos); + destPos += hLen; } - static recheckMnemonic(mnemonic) { - const words = mnemonic.trim().toLowerCase().split(/\s+/g) - const checked = [] - let word - for (word of words) { - if (!word || word.length < 2) continue - // noinspection JSUnresolvedFunction - const index = DEFAULT_WORDS.indexOf(word) - if (index === -1) { - throw new Error('BlocksoftKeysStorage invalid word ' + word) - } - checked.push(word) - } - if (checked.length <= 11) { - throw new Error('BlocksoftKeysStorage invalid words length ' + mnemonic) - } - return checked.join(' ') + return DK; + } + + static recheckMnemonic(mnemonic) { + const words = mnemonic.trim().toLowerCase().split(/\s+/g); + const checked = []; + let word; + for (word of words) { + if (!word || word.length < 2) continue; + // noinspection JSUnresolvedFunction + const index = DEFAULT_WORDS.indexOf(word); + if (index === -1) { + throw new Error('BlocksoftKeysStorage invalid word ' + word); + } + checked.push(word); + } + if (checked.length <= 11) { + throw new Error('BlocksoftKeysStorage invalid words length ' + mnemonic); + } + return checked.join(' '); + } + + /** + * make hash for mnemonic string + * @param {string} mnemonic + * @return {string} + */ + static hashMnemonic(mnemonic) { + // noinspection JSUnresolvedFunction + return createHash('sha256').update(mnemonic).digest('hex').substr(0, 32); + } + + static async bip39MnemonicToSeed(mnemonic, password) { + if (!mnemonic) { + throw new Error('bip39MnemonicToSeed is empty'); } - /** - * make hash for mnemonic string - * @param {string} mnemonic - * @return {string} - */ - static hashMnemonic(mnemonic) { - // noinspection JSUnresolvedFunction - return createHash('sha256').update(mnemonic).digest('hex').substr(0, 32) + function salt(password) { + return 'mnemonic' + (password || ''); } - static async bip39MnemonicToSeed(mnemonic, password) { - if (!mnemonic) { - throw new Error('bip39MnemonicToSeed is empty') - } - - function salt(password) { - return 'mnemonic' + (password || '') - } - - const mnemonicBuffer = Buffer.from((mnemonic || ''), 'utf8') - const saltBuffer = Buffer.from(salt(password || ''), 'utf8') - try { - const tmp2 = await pbkdf2.deriveAsync(mnemonicBuffer, saltBuffer, 2048, 64, 'sha512') - return Buffer.from(tmp2) - } catch (e) { - return BlocksoftKeysUtils._pbkdf2(mnemonicBuffer, saltBuffer, 2048, 64, 'sha512') - } + const mnemonicBuffer = Buffer.from(mnemonic || '', 'utf8'); + const saltBuffer = Buffer.from(salt(password || ''), 'utf8'); + try { + const tmp2 = await pbkdf2.deriveAsync( + mnemonicBuffer, + saltBuffer, + 2048, + 64, + 'sha512' + ); + return Buffer.from(tmp2); + } catch (e) { + return BlocksoftKeysUtils._pbkdf2( + mnemonicBuffer, + saltBuffer, + 2048, + 64, + 'sha512' + ); } + } } -export default BlocksoftKeysUtils +export default BlocksoftKeysUtils; diff --git a/crypto/actions/BlocksoftKeysStorage/BlocksoftKeysStorage.js b/crypto/actions/BlocksoftKeysStorage/BlocksoftKeysStorage.js index ff9872dcd..3fd3a0533 100644 --- a/crypto/actions/BlocksoftKeysStorage/BlocksoftKeysStorage.js +++ b/crypto/actions/BlocksoftKeysStorage/BlocksoftKeysStorage.js @@ -4,11 +4,11 @@ * @docs https://www.npmjs.com/package/react-native-keychain */ import 'react-native'; -import * as Keychain from 'react-native-keychain'; +// import * as Keychain from 'react-native-keychain'; -import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; -import config from '@app/config/config'; -import AirDAODict from '@crypto/common/AirDAODict'; +// import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; +// import config from '@app/config/config'; +// import AirDAODict from '@crypto/common/AirDAODict'; export class BlocksoftKeysStorage { /** diff --git a/package.json b/package.json index 2781650b4..e92576f47 100644 --- a/package.json +++ b/package.json @@ -63,6 +63,7 @@ "elliptic": "^6.5.4", "ethereumjs-util": "^7.1.5", "ethjs-provider-http": "^0.1.6", + "ethjs-query": "^0.3.8", "events": "^3.3.0", "expo": "~48.0.18", "expo-barcode-scanner": "~12.3.2", diff --git a/yarn.lock b/yarn.lock index 04c057e04..5759a2474 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3881,7 +3881,7 @@ babel-preset-jest@^29.5.0: babel-plugin-jest-hoist "^29.5.0" babel-preset-current-node-syntax "^1.0.0" -babel-runtime@6.26.0: +babel-runtime@6.26.0, babel-runtime@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe" integrity sha512-ITKNuq2wKlW1fJg9sSW52eepoYgZBggvOAHC0u/CYu/qxQ9EVzThCgR69BnSXLHjy2f7SY5zaQ4yt7H9ZVxY2g== @@ -6180,6 +6180,18 @@ ethers@4.0.0-beta.3: uuid "2.0.1" xmlhttprequest "1.8.0" +ethjs-format@0.2.7: + version "0.2.7" + resolved "https://registry.yarnpkg.com/ethjs-format/-/ethjs-format-0.2.7.tgz#20c92f31c259a381588d069830d838b489774b86" + integrity sha512-uNYAi+r3/mvR3xYu2AfSXx5teP4ovy9z2FrRsblU+h2logsaIKZPi9V3bn3V7wuRcnG0HZ3QydgZuVaRo06C4Q== + dependencies: + bn.js "4.11.6" + ethjs-schema "0.2.1" + ethjs-util "0.1.3" + is-hex-prefixed "1.0.0" + number-to-bn "1.7.0" + strip-hex-prefix "1.0.0" + ethjs-provider-http@^0.1.6: version "0.1.6" resolved "https://registry.yarnpkg.com/ethjs-provider-http/-/ethjs-provider-http-0.1.6.tgz#1ec5d9b4be257ef1d56a500b22a741985e889420" @@ -6187,6 +6199,28 @@ ethjs-provider-http@^0.1.6: dependencies: xhr2 "0.1.3" +ethjs-query@^0.3.8: + version "0.3.8" + resolved "https://registry.yarnpkg.com/ethjs-query/-/ethjs-query-0.3.8.tgz#aa5af02887bdd5f3c78b3256d0f22ffd5d357490" + integrity sha512-/J5JydqrOzU8O7VBOwZKUWXxHDGr46VqNjBCJgBVNNda+tv7Xc8Y2uJc6aMHHVbeN3YOQ7YRElgIc0q1CI02lQ== + dependencies: + babel-runtime "^6.26.0" + ethjs-format "0.2.7" + ethjs-rpc "0.2.0" + promise-to-callback "^1.0.0" + +ethjs-rpc@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/ethjs-rpc/-/ethjs-rpc-0.2.0.tgz#3d0011e32cfff156ed6147818c6fb8f801701b4c" + integrity sha512-RINulkNZTKnj4R/cjYYtYMnFFaBcVALzbtEJEONrrka8IeoarNB9Jbzn+2rT00Cv8y/CxAI+GgY1d0/i2iQeOg== + dependencies: + promise-to-callback "^1.0.0" + +ethjs-schema@0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/ethjs-schema/-/ethjs-schema-0.2.1.tgz#47e138920421453617069034684642e26bb310f4" + integrity sha512-DXd8lwNrhT9sjsh/Vd2Z+4pfyGxhc0POVnLBUfwk5udtdoBzADyq+sK39dcb48+ZU+2VgtwHxtGWnLnCfmfW5g== + ethjs-unit@0.1.6: version "0.1.6" resolved "https://registry.yarnpkg.com/ethjs-unit/-/ethjs-unit-0.1.6.tgz#c665921e476e87bce2a9d588a6fe0405b2c41699" @@ -6195,6 +6229,14 @@ ethjs-unit@0.1.6: bn.js "4.11.6" number-to-bn "1.7.0" +ethjs-util@0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/ethjs-util/-/ethjs-util-0.1.3.tgz#dfd5ea4a400dc5e421a889caf47e081ada78bb55" + integrity sha512-QqpX2dsEG2geSMG9dTMJVhfP1kGRdGMNjiHPiTjkju+X5cB0PQIwUzRr5k21pFkgF5zuLccqe83p7Gh5fFM5tQ== + dependencies: + is-hex-prefixed "1.0.0" + strip-hex-prefix "1.0.0" + ethjs-util@0.1.6: version "0.1.6" resolved "https://registry.yarnpkg.com/ethjs-util/-/ethjs-util-0.1.6.tgz#f308b62f185f9fe6237132fb2a9818866a5cd536" @@ -7838,6 +7880,11 @@ is-extglob@^2.1.1: resolved "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz" integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== +is-fn@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-fn/-/is-fn-1.0.0.tgz#9543d5de7bcf5b08a22ec8a20bae6e286d510d8c" + integrity sha512-XoFPJQmsAShb3jEQRfzf2rqXavq7fIqF/jOekp308JlThqrODnMpweVSGilKTCXELfLhltGP2AGgbQGVP8F1dg== + is-fullwidth-code-point@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz" @@ -10671,6 +10718,14 @@ promise-inflight@^1.0.1: resolved "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz" integrity sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g== +promise-to-callback@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/promise-to-callback/-/promise-to-callback-1.0.0.tgz#5d2a749010bfb67d963598fcd3960746a68feef7" + integrity sha512-uhMIZmKM5ZteDMfLgJnoSq9GCwsNKrYau73Awf1jIy6/eUcuuZ3P+CD9zUv0kJsIUbU+x6uLNIhXhLHDs1pNPA== + dependencies: + is-fn "^1.0.0" + set-immediate-shim "^1.0.1" + promise@^7.1.1: version "7.3.1" resolved "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz" @@ -11799,6 +11854,11 @@ set-blocking@^2.0.0: resolved "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz" integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw== +set-immediate-shim@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz#4b2b1b27eb808a9f8dcc481a58e5e56f599f3f61" + integrity sha512-Li5AOqrZWCVA2n5kryzEmqai6bKSIvpz5oUJHPVj6+dsbD3X1ixtsY5tEnsaNpH3pFAHmG8eIHUrtEtohrg+UQ== + set-value@^2.0.0, set-value@^2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz" From 1119edc2878fa8d2faeed0378c6ed36f34f97a41 Mon Sep 17 00:00:00 2001 From: illiaa Date: Thu, 17 Aug 2023 11:33:46 +0300 Subject: [PATCH 054/509] added screens --- src/components/svg/icons/Warning.tsx | 23 +++++ src/constants/colors.ts | 1 + .../Wallet/CreateWallet/CreateWalletStep0.tsx | 88 ++++++++++-------- .../Wallet/CreateWallet/CreateWalletStep1.tsx | 93 +++++++++++++++---- .../components/RecoveryPhraseModal.tsx | 40 ++++++++ src/screens/Wallet/CreateWallet/styles.ts | 14 +++ src/screens/Wallet/index.tsx | 2 +- 7 files changed, 204 insertions(+), 57 deletions(-) create mode 100644 src/components/svg/icons/Warning.tsx create mode 100644 src/screens/Wallet/CreateWallet/components/RecoveryPhraseModal.tsx create mode 100644 src/screens/Wallet/CreateWallet/styles.ts diff --git a/src/components/svg/icons/Warning.tsx b/src/components/svg/icons/Warning.tsx new file mode 100644 index 000000000..90fd3356e --- /dev/null +++ b/src/components/svg/icons/Warning.tsx @@ -0,0 +1,23 @@ +import React from 'react'; +import Svg, { Path } from 'react-native-svg'; +import { IconProps } from './Icon.types'; + +export function WarningIcon(props: IconProps) { + const { scale = 1, color = '#FFAC23' } = props; + const width = 24; + const height = 24; + return ( + + + + ); +} diff --git a/src/constants/colors.ts b/src/constants/colors.ts index 9d35118d6..35baf58f5 100644 --- a/src/constants/colors.ts +++ b/src/constants/colors.ts @@ -18,6 +18,7 @@ export const COLORS = { 5: 'rgba(14, 14, 14, 0.05)', 60: 'rgba(14, 14, 14, 0.60)' }, + neutralGray: 'rgba(14, 14, 14, 0.05)', asphalt: '#a1a6b2', black: '#000000', jetBlack: '#191919', diff --git a/src/screens/Wallet/CreateWallet/CreateWalletStep0.tsx b/src/screens/Wallet/CreateWallet/CreateWalletStep0.tsx index 7bbc40fc4..131d1641e 100644 --- a/src/screens/Wallet/CreateWallet/CreateWalletStep0.tsx +++ b/src/screens/Wallet/CreateWallet/CreateWalletStep0.tsx @@ -1,42 +1,46 @@ -import React, { useState } from 'react'; +import React, { useCallback, useRef, useState } from 'react'; import { View } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; -import { CheckBox, Header } from '@components/composite'; +import { BottomSheetRef, CheckBox, Header } from '@components/composite'; import { Button, Row, Spacer, Text } from '@components/base'; import { COLORS } from '@constants/colors'; import { scale, verticalScale } from '@utils/scaling'; import { ElipseIcon } from '@components/svg/icons/Elipse'; - -const Title = ({ children }: { children: React.ReactNode }) => ( - - {children} - -); +import { useNavigation } from '@react-navigation/native'; +import { AddWalletStackNavigationProp } from '@appTypes'; +import { RecoveryPhraseModal } from '@screens/Wallet/CreateWallet/components/RecoveryPhraseModal'; +import { styles } from '@screens/Wallet/CreateWallet/styles'; export const CreateWalletStep0 = () => { const { top } = useSafeAreaInsets(); const [selected, setSelected] = useState(false); + const navigation = useNavigation(); + const recoveryPhraseModalRef = useRef(null); + + const onContinuePress = () => { + navigation.navigate('CreateWalletStep1'); + }; + + const showRecoveryModal = useCallback(() => { + recoveryPhraseModalRef.current?.show(); + }, [recoveryPhraseModalRef]); + return ( - -
- Create new wallet - - } - style={{ shadowColor: 'transparent' }} - /> - + + +
+ Create new wallet + + } + style={styles.header} + /> { > Backup your wallet - - + + { > Your wallet will be backed up with a{' '} - - - + + { - + + @@ -94,17 +100,25 @@ export const CreateWalletStep0 = () => { - + ); }; diff --git a/src/screens/Wallet/CreateWallet/CreateWalletStep1.tsx b/src/screens/Wallet/CreateWallet/CreateWalletStep1.tsx index 6762b8dc8..c0408bb2e 100644 --- a/src/screens/Wallet/CreateWallet/CreateWalletStep1.tsx +++ b/src/screens/Wallet/CreateWallet/CreateWalletStep1.tsx @@ -10,6 +10,7 @@ import { PrimaryButton } from '@components/modular'; import { useNavigation } from '@react-navigation/native'; import { AddWalletStackNavigationProp } from '@appTypes'; import { COLORS } from '@constants/colors'; +import { WarningIcon } from '@components/svg/icons/Warning'; export const CreateWalletStep1 = () => { const navigation = useNavigation(); @@ -21,6 +22,7 @@ export const CreateWalletStep1 = () => { const init = async () => { setLoading(true); // create mnemonic + // tslint:disable-next-line:no-shadowed-variable const walletMnemonic = ( await MnemonicUtils.generateNewMnemonic(mnemonicLength) ).mnemonic; @@ -35,13 +37,22 @@ export const CreateWalletStep1 = () => { const renderWord = (word: string, index: number) => { return ( - - {index + 1}. - - {' '} - {word} - - + <> + + + + {index + 1} {word} + + + + + ); }; @@ -49,32 +60,64 @@ export const CreateWalletStep1 = () => { navigation.navigate('CreateWalletStep2'); }; - const halfArrayNum = Math.ceil(walletMnemonicArray.length / 2); + const wordsPerColumn = 4; + const numColumns = Math.ceil(walletMnemonicArray.length / wordsPerColumn); return ( -
+
+ + Your recovery phrase + + + + Make sure to write it down as shown. You will verify this later. + {loading && ( )} - + {Array.isArray(walletMnemonicArray) && ( - - {walletMnemonicArray.slice(0, halfArrayNum).map(renderWord)} - - - {walletMnemonicArray - .slice(halfArrayNum) - .map((word, idx) => renderWord(word, idx + halfArrayNum))} - + {Array.from({ length: numColumns }, (_, columnIndex) => ( + + {walletMnemonicArray + .slice( + columnIndex * wordsPerColumn, + (columnIndex + 1) * wordsPerColumn + ) + .map((word, idx) => + renderWord(word, idx + columnIndex * wordsPerColumn) + )} + + ))} )} + + + + + + Never share recovery phrase with {'\n'} anyone, keep it safe! + + + + - Next + Verify phrase @@ -96,5 +139,17 @@ const styles = StyleSheet.create({ }, column: { flex: 1 + }, + warningContainer: { + backgroundColor: '#fffbb5', + borderRadius: 13, + borderWidth: 0.2, + borderColor: '#ffac23' + }, + warning: { + marginVertical: scale(12), + marginHorizontal: scale(16), + flexDirection: 'row', + alignItems: 'center' } }); diff --git a/src/screens/Wallet/CreateWallet/components/RecoveryPhraseModal.tsx b/src/screens/Wallet/CreateWallet/components/RecoveryPhraseModal.tsx new file mode 100644 index 000000000..148906624 --- /dev/null +++ b/src/screens/Wallet/CreateWallet/components/RecoveryPhraseModal.tsx @@ -0,0 +1,40 @@ +import React, { ForwardedRef, forwardRef } from 'react'; +import { + BottomSheet, + BottomSheetProps, + BottomSheetRef, + Header +} from '@components/composite'; +import { Row, Text } from '@components/base'; +import { useForwardedRef } from '@hooks/useForwardedRef'; +import { scale } from '@utils/scaling'; +import { COLORS } from '@constants/colors'; + +export const RecoveryPhraseModal = forwardRef( + (props, ref) => { + const localRef: ForwardedRef = useForwardedRef(ref); + + return ( + +
+ What is a recovery phrase? + + } + backIconVisible={false} + style={{ shadowColor: 'transparent' }} + /> + + Your recovery phrase is a set of 12 random words, and it's the only + way to get into your wallet if you lose your device. + + + ); + } +); diff --git a/src/screens/Wallet/CreateWallet/styles.ts b/src/screens/Wallet/CreateWallet/styles.ts new file mode 100644 index 000000000..cabcf573c --- /dev/null +++ b/src/screens/Wallet/CreateWallet/styles.ts @@ -0,0 +1,14 @@ +import { StyleSheet } from 'react-native'; +import { scale } from '@utils/scaling'; + +export const styles = StyleSheet.create({ + header: { + shadowColor: 'transparent' + }, + container: { + flex: 1, + paddingHorizontal: scale(18), + alignItems: 'center', + justifyContent: 'space-between' + } +}); diff --git a/src/screens/Wallet/index.tsx b/src/screens/Wallet/index.tsx index abf072cbb..895ea08ff 100644 --- a/src/screens/Wallet/index.tsx +++ b/src/screens/Wallet/index.tsx @@ -44,7 +44,7 @@ export const WalletScreen = () => { setFlowType(AddWalletFlowType.CREATE_WALLET); setWalletName(''); setMnemonicLength(128); - navigation.navigate('CreateWalletStep1'); + navigation.navigate('CreateWalletStep0'); }; // const onRestorePress = () => { From 2ee4132e28060d195f4487bbe6b765fdfc471787 Mon Sep 17 00:00:00 2001 From: ilyaAir <128598571+ilyaAir@users.noreply.github.com> Date: Thu, 17 Aug 2023 13:08:33 +0300 Subject: [PATCH 055/509] Revert "Dev" --- Providers.tsx | 16 +-- package.json | 3 - .../composite/Button/CopyToClipboard.tsx | 7 +- .../modular/CollectionItem/index.tsx | 4 +- .../templates/AMBPriceHistory/index.tsx | 12 +- .../templates/AddWalletToList/index.tsx | 6 +- .../BottomSheetAddWalletToList/index.tsx | 24 ---- ...BottomSheetRemoveAddressFromWatchlists.tsx | 8 +- .../index.tsx | 6 +- .../BottomSheetCreateRenameGroup/index.tsx | 31 ++-- .../BottomSheetEditCollection/index.tsx | 7 +- .../templates/BottomSheetEditWallet/index.tsx | 20 ++- .../BottomSheetNotificationSettings/index.tsx | 15 +- .../index.tsx | 8 +- .../ExplorerAccount.TransactionItem.tsx | 4 +- .../ExplorerAccount.Transactions.tsx | 4 +- .../templates/ExplorerAccount/index.tsx | 47 +++--- .../index.tsx | 12 +- .../styles.ts | 0 .../SearchAddress/SearchAddress.NoMatch.tsx | 6 +- .../templates/SearchAddress/index.tsx | 14 +- .../templates/TransactionDetails/index.tsx | 12 +- src/components/templates/WalletList/index.tsx | 4 +- src/components/templates/index.ts | 1 - src/contexts/Localizations/index.tsx | 64 --------- src/contexts/index.ts | 1 - src/lib/permission.ts | 4 +- src/localization/i18n.ts | 20 --- src/localization/locales/English.json | 134 ------------------ src/localization/locales/Turkish.json | 133 ----------------- src/localization/locales/translations.ts | 17 --- src/navigation/stacks/TabsNavigator.tsx | 14 +- src/screens/AMBMarket/AMBMarket.constants.ts | 21 ++- src/screens/AMBMarket/components/About.tsx | 7 +- .../AMBMarket/components/DetailedInfo.tsx | 8 +- src/screens/AMBMarket/index.tsx | 12 +- src/screens/Address/index.tsx | 15 +- .../Notifications/components/Header.tsx | 4 +- src/screens/Notifications/index.tsx | 14 +- .../components/ListsOfAddressGroup/index.tsx | 4 +- .../components/PortfolioScreenTabs/index.tsx | 6 +- src/screens/Portfolio/index.tsx | 56 ++++---- src/screens/Search/components/WalletItem.tsx | 9 +- src/screens/Search/index.tsx | 6 +- .../components/SettingsBlock/index.tsx | 13 +- .../modals/BottomSheetBaseCurrency/index.tsx | 4 +- .../BottomSheetSelectLanguage/index.tsx | 19 +-- .../components/SettingsInfoBlock/index.tsx | 8 +- src/screens/SingleCollection/index.tsx | 4 +- .../BottomSheetAddNewAddressToGroup/index.tsx | 49 +++---- .../modals/BottomSheetRenameAddress/index.tsx | 10 +- src/screens/Wallets/components/Header.tsx | 8 +- .../components/HomeTabs/HomeCollections.tsx | 4 +- .../Wallets/components/HomeTabs/HomeTabs.tsx | 8 +- .../components/HomeTabs/HomeWatchlists.tsx | 5 +- yarn.lock | 57 -------- 56 files changed, 219 insertions(+), 790 deletions(-) rename src/components/templates/{LocalizedRenderEmpty => RenderEmpty}/index.tsx (64%) rename src/components/templates/{LocalizedRenderEmpty => RenderEmpty}/styles.ts (100%) delete mode 100644 src/contexts/Localizations/index.tsx delete mode 100644 src/localization/i18n.ts delete mode 100644 src/localization/locales/English.json delete mode 100644 src/localization/locales/Turkish.json delete mode 100644 src/localization/locales/translations.ts diff --git a/Providers.tsx b/Providers.tsx index 009cec214..03b8242e7 100644 --- a/Providers.tsx +++ b/Providers.tsx @@ -1,13 +1,9 @@ -import React from 'react'; import { combineComponents } from '@helpers/combineComponents'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import React from 'react'; import { SafeAreaProvider } from 'react-native-safe-area-context'; import { ListsContextProvider } from '@contexts/ListsContext'; -import { - AllAddressesProvider, - OnboardingContextProvider, - LocalizationProvider -} from '@contexts'; +import { AllAddressesProvider, OnboardingContextProvider } from '@contexts'; const queryClient = new QueryClient(); @@ -19,14 +15,9 @@ const WrappedSafeAreaProvider: React.FC = ({ children }: any) => ( {children} ); -const WrappedLocalizationProvider: React.FC = ({ children }: any) => ( - {children} -); - const independentProviders = [ WrappedQueryClientProvider, - WrappedSafeAreaProvider, - WrappedLocalizationProvider + WrappedSafeAreaProvider ]; /** * The order of the providers matters @@ -35,7 +26,6 @@ const providers = [ ...independentProviders, AllAddressesProvider, ListsContextProvider, - WrappedLocalizationProvider, OnboardingContextProvider ]; diff --git a/package.json b/package.json index 2dfbdf292..53187d363 100644 --- a/package.json +++ b/package.json @@ -56,15 +56,12 @@ "expo-store-review": "~6.2.1", "expo-system-ui": "~2.2.1", "expo-updates": "~0.16.4", - "i18n-js": "^4.3.0", - "i18next": "^23.4.4", "jest": "^29.2.1", "jest-expo": "^48.0.2", "moment": "^2.29.4", "patch-package": "^7.0.0", "postinstall-postinstall": "^2.1.0", "react": "18.2.0", - "react-i18next": "^13.1.0", "react-native": "0.71.8", "react-native-calendar-picker": "^7.1.4", "react-native-gesture-handler": "~2.9.0", diff --git a/src/components/composite/Button/CopyToClipboard.tsx b/src/components/composite/Button/CopyToClipboard.tsx index c83ca2aa0..dd602b1ef 100644 --- a/src/components/composite/Button/CopyToClipboard.tsx +++ b/src/components/composite/Button/CopyToClipboard.tsx @@ -6,7 +6,6 @@ import { ClipboardFilledIcon } from '@components/svg/icons'; import { scale } from '@utils/scaling'; import { Toast, ToastPosition } from '@components/modular/Toast'; import { BaseButtonProps } from '@components/base/Button'; -import { useTranslation } from 'react-i18next'; export interface CopyToClipboardButtonProps extends Omit { @@ -19,13 +18,9 @@ export const CopyToClipboardButton = ( props: CopyToClipboardButtonProps ): JSX.Element => { const { textToDisplay, textToCopy, textProps, ...buttonProps } = props; - const { t } = useTranslation(); const onPress = async () => { - Toast.show({ - message: t('copied.to.clipboard'), - type: ToastPosition.Bottom - }); + Toast.show({ message: 'Copied to Clipboard', type: ToastPosition.Bottom }); await Clipboard.setStringAsync(textToCopy || textToDisplay); }; diff --git a/src/components/modular/CollectionItem/index.tsx b/src/components/modular/CollectionItem/index.tsx index b1ce4bec2..2a0eead47 100644 --- a/src/components/modular/CollectionItem/index.tsx +++ b/src/components/modular/CollectionItem/index.tsx @@ -6,7 +6,6 @@ import { COLORS } from '@constants/colors'; import { useAMBPrice } from '@hooks'; import { AccountList } from '@models'; import { NumberUtils } from '@utils/number'; -import { useTranslation } from 'react-i18next'; interface CollectionItemProps { collection: AccountList; @@ -16,7 +15,6 @@ interface CollectionItemProps { export function CollectionItem(props: CollectionItemProps) { const { collection, style } = props; const { data: ambPriceData } = useAMBPrice(); - const { t } = useTranslation(); const tokensFormatted = useMemo(() => { const formattedNumber = NumberUtils.formatNumber( @@ -55,7 +53,7 @@ export function CollectionItem(props: CollectionItemProps) { color={COLORS.smokyBlack50} fontSize={12} > - {collection.accountCount + ` ${t('addresses.text')}`} + {collection.accountCount + ' addresses'} {collection.accountCount > 0 && ( diff --git a/src/components/templates/AMBPriceHistory/index.tsx b/src/components/templates/AMBPriceHistory/index.tsx index d08134c7d..b215003bc 100644 --- a/src/components/templates/AMBPriceHistory/index.tsx +++ b/src/components/templates/AMBPriceHistory/index.tsx @@ -1,6 +1,4 @@ import React, { useEffect, useMemo, useRef, useState } from 'react'; -import { View } from 'react-native'; -import { useTranslation } from 'react-i18next'; import Animated, { useAnimatedProps, useAnimatedStyle, @@ -18,8 +16,9 @@ import { scale, verticalScale } from '@utils/scaling'; import { Badge } from '@components/base/Badge'; import { PercentChange } from '@components/composite'; import { BezierChart } from '../BezierChart'; -import { MONTH_NAMES } from '@constants/variables'; import { styles } from './styles'; +import { MONTH_NAMES } from '@constants/variables'; +import { View } from 'react-native'; interface AMBPriceHistoryProps { badgeType: 'view' | 'button'; @@ -45,7 +44,6 @@ export const AMBPriceHistory = (props: AMBPriceHistoryProps) => { const ambPrice = useSharedValue(ambPriceNow?.priceUSD || 0); const selectedPointDate = useSharedValue(-1); const didSetAMBPriceFromAPI = useRef(false); - const { t } = useTranslation(); useEffect(() => { if (ambPriceNow) { @@ -206,15 +204,15 @@ export const AMBPriceHistory = (props: AMBPriceHistoryProps) => { // value: '1h' // }, { - text: t('chart.timeframe.daily'), + text: '1D', value: '1d' }, { - text: t('chart.timeframe.weekly'), + text: '1W', value: 'weekly' }, { - text: t('chart.timeframe.monthly'), + text: '1M', value: 'monthly' } ]} diff --git a/src/components/templates/AddWalletToList/index.tsx b/src/components/templates/AddWalletToList/index.tsx index 429aba41b..dd55bca98 100644 --- a/src/components/templates/AddWalletToList/index.tsx +++ b/src/components/templates/AddWalletToList/index.tsx @@ -10,7 +10,6 @@ import { COLORS } from '@constants/colors'; import { SearchIcon } from '@components/svg/icons'; import { NumberUtils } from '@utils/number'; import { useLists } from '@contexts/ListsContext'; -import { useTranslation } from 'react-i18next'; export interface AddWalletToListProps { wallet: ExplorerAccount; @@ -21,7 +20,6 @@ export interface AddWalletToListProps { export const AddWalletToList = (props: AddWalletToListProps): JSX.Element => { const { wallet, lists, onWalletMove } = props; const { toggleAddressesInList } = useLists((v) => v); - const { t } = useTranslation(); const [searchText, setSearchText] = useState(''); const filteredLists = useMemo( () => @@ -78,7 +76,7 @@ export const AddWalletToList = (props: AddWalletToListProps): JSX.Element => { fontFamily="Inter_500Medium" color={COLORS.smokyBlack50} > - {list.accountCount} {t('addresses')} + {list.accountCount} Addresses @@ -97,7 +95,7 @@ export const AddWalletToList = (props: AddWalletToListProps): JSX.Element => { } /> diff --git a/src/components/templates/BottomSheetAddWalletToList/index.tsx b/src/components/templates/BottomSheetAddWalletToList/index.tsx index a2c08c4d2..1dc82b2b2 100644 --- a/src/components/templates/BottomSheetAddWalletToList/index.tsx +++ b/src/components/templates/BottomSheetAddWalletToList/index.tsx @@ -5,9 +5,6 @@ import { useFullscreenModalHeight } from '@hooks'; import { COLORS } from '@constants/colors'; import { AddWalletToList, AddWalletToListProps } from '../AddWalletToList'; import { verticalScale } from '@utils/scaling'; -import { PrimaryButton } from '@components/modular'; -import { BottomSheetCreateRenameGroup } from '@components/templates'; -import { useLists } from '@contexts'; interface BottomSheetAddWalletToListProps extends AddWalletToListProps { title: string; @@ -19,11 +16,6 @@ export const BottomSheetAddWalletToList = forwardRef< >((props, ref) => { const { title, ...addWalletToListProps } = props; const fullscreenHeight = useFullscreenModalHeight(); - const { handleOnCreate, createGroupRef } = useLists((v) => v); - - const showCreateNewListModal = () => { - createGroupRef.current?.show(); - }; return ( - { - showCreateNewListModal(); - }} - style={{ width: '90%', alignSelf: 'center' }} - > - - Create new group - - - - ); }); diff --git a/src/components/templates/BottomSheetConfirmRemove/BottomSheetRemoveAddressFromWatchlists.tsx b/src/components/templates/BottomSheetConfirmRemove/BottomSheetRemoveAddressFromWatchlists.tsx index 2b5f72ca1..ddf5efc65 100644 --- a/src/components/templates/BottomSheetConfirmRemove/BottomSheetRemoveAddressFromWatchlists.tsx +++ b/src/components/templates/BottomSheetConfirmRemove/BottomSheetRemoveAddressFromWatchlists.tsx @@ -8,7 +8,6 @@ import { COLORS } from '@constants/colors'; import { ExplorerAccount } from '@models'; import { BottomSheetFloat } from '@components/modular'; import { verticalScale } from '@utils/scaling'; -import { useTranslation } from 'react-i18next'; type Props = { ref: RefObject; @@ -21,7 +20,6 @@ export const BottomSheetRemoveAddressFromWatchlists = forwardRef< >(({ item }, ref) => { const localRef: ForwardedRef = useForwardedRef(ref); const { removeFromWatchlist } = useWatchlist(); - const { t } = useTranslation(); const handleRemoveAddressFromWatchlist = useCallback(() => { removeFromWatchlist(item); @@ -44,7 +42,7 @@ export const BottomSheetRemoveAddressFromWatchlists = forwardRef< fontSize={14} color={COLORS.smokyBlack} > - {t('confirm.remove.address.from.watchlist')} + Remove this address from watchlist? @@ -72,7 +70,7 @@ export const BottomSheetRemoveAddressFromWatchlists = forwardRef< color={COLORS.smokyBlack} fontSize={16} > - {t('cancel.btn')} + Cancel diff --git a/src/components/templates/BottomSheetCreateCollectionOrAddAddress/index.tsx b/src/components/templates/BottomSheetCreateCollectionOrAddAddress/index.tsx index 44efde22e..938eaecc2 100644 --- a/src/components/templates/BottomSheetCreateCollectionOrAddAddress/index.tsx +++ b/src/components/templates/BottomSheetCreateCollectionOrAddAddress/index.tsx @@ -6,7 +6,6 @@ import { BottomSheetFloat, PrimaryButton } from '@components/modular'; import { useForwardedRef } from '@hooks'; import { scale, verticalScale } from '@utils/scaling'; import { COLORS } from '@constants/colors'; -import { useTranslation } from 'react-i18next'; type Props = { ref: RefObject; @@ -19,7 +18,6 @@ export const BottomSheetCreateCollectionOrAddAddress = forwardRef< Props >(({ handleCreateCollectionPress, handleOnAddNewAddress }, ref) => { const localRef: ForwardedRef = useForwardedRef(ref); - const { t } = useTranslation(); return ( - {t('add.address.btn')} + Add address @@ -62,7 +60,7 @@ export const BottomSheetCreateCollectionOrAddAddress = forwardRef< fontSize={16} color={COLORS.smokyBlack} > - {t('create.group')} + Create group diff --git a/src/components/templates/BottomSheetCreateRenameGroup/index.tsx b/src/components/templates/BottomSheetCreateRenameGroup/index.tsx index c327c492e..fc53227a1 100644 --- a/src/components/templates/BottomSheetCreateRenameGroup/index.tsx +++ b/src/components/templates/BottomSheetCreateRenameGroup/index.tsx @@ -23,7 +23,6 @@ import { OnboardingView } from '../OnboardingView'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { verticalScale } from '@utils/scaling'; import { StringUtils } from '@utils/string'; -import { useTranslation } from 'react-i18next'; type Props = { ref: RefObject; @@ -44,7 +43,6 @@ export const BottomSheetCreateRenameGroup = forwardRef( } = props; const localRef: ForwardedRef = useForwardedRef(ref); - const { t } = useTranslation(); const nameInput = useRef(null); const [localGroupName, setLocalGroupName] = useState(''); @@ -69,11 +67,11 @@ export const BottomSheetCreateRenameGroup = forwardRef( handleOnCreateGroup(localGroupName); Toast.show({ title: '', - message: `${t('toast.way.to.go')} ${StringUtils.formatAddress( + message: `Way to go! ${StringUtils.formatAddress( localGroupName, 16, 0 - )} ${t('toast.group.created')}`, + )} group created.`, type: ToastPosition.Top }); } @@ -82,9 +80,15 @@ export const BottomSheetCreateRenameGroup = forwardRef( handleOnRenameGroup(groupId, localGroupName); Toast.show({ title: '', - message: `${StringUtils.formatAddress(groupTitle || '', 16, 0)} ${t( - 'toast.has.been.renamed' - )} ${StringUtils.formatAddress(localGroupName, 16, 0)}.`, + message: `${StringUtils.formatAddress( + groupTitle || '', + 16, + 0 + )} has been renamed to ${StringUtils.formatAddress( + localGroupName, + 16, + 0 + )}.`, type: ToastPosition.Top }); } @@ -97,8 +101,7 @@ export const BottomSheetCreateRenameGroup = forwardRef( handleOnCreateGroup, handleOnRenameGroup, localGroupName, - localRef, - t + localRef ]); const bottomSafeArea = useSafeAreaInsets().bottom - 10; @@ -133,7 +136,7 @@ export const BottomSheetCreateRenameGroup = forwardRef( fontSize={16} color={COLORS.smokyBlack} > - {type === 'create' ? t('create.group') : t('rename.group')} + {type === 'create' ? ' Create group' : 'Rename group'} ( type="text" placeholder={ emptyPlaceholder - ? t('field.required') - : t('group.name.input') + ? 'This field is required' + : 'Enter group name' } placeholderTextColor={ emptyPlaceholder ? COLORS.crimsonRed : COLORS.midnight @@ -175,7 +178,7 @@ export const BottomSheetCreateRenameGroup = forwardRef( fontSize={16} color={COLORS.white} > - {type === 'create' ? t('create.btn') : t('save.btn')} + {type === 'create' ? 'Create' : 'Save'} @@ -195,7 +198,7 @@ export const BottomSheetCreateRenameGroup = forwardRef( color={COLORS.nero} fontSize={16} > - {t('cancel.btn')} + Cancel diff --git a/src/components/templates/BottomSheetEditCollection/index.tsx b/src/components/templates/BottomSheetEditCollection/index.tsx index 8e6630316..000c93863 100644 --- a/src/components/templates/BottomSheetEditCollection/index.tsx +++ b/src/components/templates/BottomSheetEditCollection/index.tsx @@ -10,7 +10,6 @@ import { AccountList } from '@models'; import { BottomSheetCreateRenameGroup } from '../BottomSheetCreateRenameGroup'; import { styles } from './styles'; import { StringUtils } from '@utils/string'; -import { useTranslation } from 'react-i18next'; interface BottomSheetEditCollectionProps extends BottomSheetProps { collection: AccountList; @@ -26,7 +25,6 @@ export const BottomSheetEditCollection = forwardRef< const localRef: ForwardedRef = useForwardedRef(ref); const { handleOnRename, handleOnDelete } = useLists((v) => v); const renameCollectionModalRef = useRef(null); - const { t } = useTranslation(); const dismissThis = useCallback(() => { setTimeout(() => { @@ -76,7 +74,7 @@ export const BottomSheetEditCollection = forwardRef< fontFamily="Inter_600SemiBold" color={COLORS.smokyBlack} > - {t('rename.group')} + Rename group diff --git a/src/components/templates/BottomSheetEditWallet/index.tsx b/src/components/templates/BottomSheetEditWallet/index.tsx index de5982246..671310207 100644 --- a/src/components/templates/BottomSheetEditWallet/index.tsx +++ b/src/components/templates/BottomSheetEditWallet/index.tsx @@ -10,7 +10,6 @@ import { BottomSheetRenameAddress } from '@screens/SingleCollection/modals/Botto import { COLORS } from '@constants/colors'; import { BottomSheetAddWalletToList } from '../BottomSheetAddWalletToList'; import { styles } from './styles'; -import { useTranslation } from 'react-i18next'; interface BottomSheetEditWalletProps extends BottomSheetProps { wallet: ExplorerAccount; @@ -26,7 +25,6 @@ export const BottomSheetEditWallet = forwardRef< const { listsOfAddressGroup, toggleAddressesInList } = useLists((v) => v); const renameWalletModalRef = useRef(null); const addToCollectionModalRef = useRef(null); - const { t } = useTranslation(); const listsWithCurrentWallet = listsOfAddressGroup.filter((list) => list.accounts.some((acc) => acc?.address === wallet?.address) @@ -81,11 +79,11 @@ export const BottomSheetEditWallet = forwardRef< dismissThis(); Toast.show({ title: '', - message: t('toast.Successfully.removed.wallet.from.group'), + message: 'Successfully removed wallet from group!', type: ToastPosition.Top }); } - }, [dismissThis, listsWithCurrentWallet, t, toggleAddressesInList, wallet]); + }, [dismissThis, listsWithCurrentWallet, toggleAddressesInList, wallet]); return ( - {t('rename.address')} + Rename address {listsWithCurrentWallet.length > 0 ? ( @@ -116,7 +114,7 @@ export const BottomSheetEditWallet = forwardRef< fontFamily="Inter_600SemiBold" color={COLORS.smokyBlack} > - {t('move.to.another.group')} + Move to another group listsWithCurrentWallet.indexOfItem(list, 'id') === -1 @@ -153,12 +149,12 @@ export const BottomSheetEditWallet = forwardRef< fontFamily="Inter_600SemiBold" color={COLORS.smokyBlack} > - {t('add.address.to.group.lower.case')} + Add to group ( (notificationSettings); const { top: topInset } = useSafeAreaInsets(); - const { t } = useTranslation(); - useEffect( () => setLocalNotificationSettings(notificationSettings), [notificationSettings] @@ -94,7 +91,7 @@ export const BottomSheetNotificationSettings = forwardRef< fontSize={16} color={COLORS.smokyBlack} > - {t('notification.settings')} + Notification settings } titlePosition={Platform.select({ ios: 'left', default: 'center' })} @@ -111,7 +108,7 @@ export const BottomSheetNotificationSettings = forwardRef< {/* Price alerts */} - {t('price.alerts.switch')} + Price alerts onSettingsValueChange( @@ -125,14 +122,14 @@ export const BottomSheetNotificationSettings = forwardRef< {/* Percentage Change */} - {t('price.alerts.treshold')} + Price alerts threshold - {t('price.alerts.treshold.text')} + Set 24hr price change amount to receive notifications. - {t('transaction.alerts.switch')} + Transaction alerts - {t('transaction.alerts.switch.text')} + Get notified of transactions in your watchlist. ; @@ -22,7 +21,6 @@ export const BottomSheetRemoveAddressFromCollection = forwardRef< >(({ wallet }, ref) => { const localRef: ForwardedRef = useForwardedRef(ref); const { toggleAddressesInList, listsOfAddressGroup } = useLists((v) => v); - const { t } = useTranslation(); const collection = listsOfAddressGroup.find( (list) => list.accounts.findIndex((account) => account._id === wallet._id) > -1 @@ -42,7 +40,7 @@ export const BottomSheetRemoveAddressFromCollection = forwardRef< color={COLORS.smokyBlack} numberOfLines={1} > - {t('remove.address.from.group.select')} {collection?.name}? + Remove this address from {collection?.name}? @@ -74,7 +72,7 @@ export const BottomSheetRemoveAddressFromCollection = forwardRef< color={COLORS.smokyBlack} fontSize={16} > - {t('cancel.btn')} + Cancel diff --git a/src/components/templates/ExplorerAccount/ExplorerAccount.TransactionItem.tsx b/src/components/templates/ExplorerAccount/ExplorerAccount.TransactionItem.tsx index 6b9667388..60243fcb2 100644 --- a/src/components/templates/ExplorerAccount/ExplorerAccount.TransactionItem.tsx +++ b/src/components/templates/ExplorerAccount/ExplorerAccount.TransactionItem.tsx @@ -8,7 +8,6 @@ import { BottomSheetFloat, TransactionItem } from '@components/modular'; import { Transaction } from '@models/Transaction'; import { scale, verticalScale } from '@utils/scaling'; import { CommonStackNavigationProp } from '@appTypes/navigation/common'; -import { useTranslation } from 'react-i18next'; interface ExplorerAccountTransactionItemProps { transaction: Transaction; @@ -21,7 +20,6 @@ export const ExplorerAccountTransactionItem = ( const { transaction, disabled = false } = props; const transactionDetailsModal = useRef(null); const navigation = useNavigation(); - const { t } = useTranslation(); const showTransactionDetails = () => { transactionDetailsModal.current?.show(); @@ -44,7 +42,7 @@ export const ExplorerAccountTransactionItem = ( - {t('transaction.details')} + Transaction details diff --git a/src/components/templates/ExplorerAccount/ExplorerAccount.Transactions.tsx b/src/components/templates/ExplorerAccount/ExplorerAccount.Transactions.tsx index 3067a4202..0db3ff85b 100644 --- a/src/components/templates/ExplorerAccount/ExplorerAccount.Transactions.tsx +++ b/src/components/templates/ExplorerAccount/ExplorerAccount.Transactions.tsx @@ -11,7 +11,6 @@ import { } from '@components/base'; import { ExplorerAccountTransactionItem } from './ExplorerAccount.TransactionItem'; import { COLORS } from '@constants/colors'; -import { useTranslation } from 'react-i18next'; interface ExplorerAccountViewTransactionsProps { transactions: Transaction[]; @@ -25,7 +24,6 @@ export const AccountTransactions = ( ): JSX.Element => { const { transactions, loading, showTransactionDetailsOnPress, onEndReached } = props; - const { t } = useTranslation(); const renderTransaction = ( args: ListRenderItemInfo @@ -48,7 +46,7 @@ export const AccountTransactions = ( fontSize={20} color={COLORS.jetBlack} > - {t('recent.activity')} + Recent activity diff --git a/src/components/templates/ExplorerAccount/index.tsx b/src/components/templates/ExplorerAccount/index.tsx index c97c877a3..613126c5b 100644 --- a/src/components/templates/ExplorerAccount/index.tsx +++ b/src/components/templates/ExplorerAccount/index.tsx @@ -8,14 +8,13 @@ import { useAMBPrice } from '@hooks/query'; import { NumberUtils } from '@utils/number'; import { styles } from './styles'; import { useLists } from '@contexts/ListsContext'; -// import { PlusIcon } from '@components/svg/icons'; +import { PlusIcon } from '@components/svg/icons'; import { BottomSheetRef, CopyToClipboardButton } from '@components/composite'; import { COLORS } from '@constants/colors'; import { AddWalletToList } from '../AddWalletToList'; import { BottomSheetWithHeader } from '@components/modular'; import { useFullscreenModalHeight } from '@hooks/useFullscreenModalHeight'; import { useWatchlist } from '@hooks'; -import { useTranslation } from 'react-i18next'; interface ExplorerAccountProps { account: ExplorerAccount; @@ -31,7 +30,6 @@ export const ExplorerAccountView = ( const { listsOfAddressGroup } = useLists((v) => v); const fullscreenHeight = useFullscreenModalHeight(); const { addToWatchlist, removeFromWatchlist } = useWatchlist(); - const { t } = useTranslation(); const { data } = useAMBPrice(); const ambPriceUSD = data?.priceUSD || 0; @@ -78,8 +76,7 @@ export const ExplorerAccountView = ( fontSize={13} fontWeight="400" > - {t('address.added.to.group')} - {listsWithAccount[0].name} + Added to {listsWithAccount[0].name} ) : ( - {t('address.added.to.group')} - {listsWithAccount.length} - {t('groups.count')} + Added to {listsWithAccount.length} lists ))} @@ -157,16 +152,14 @@ export const ExplorerAccountView = ( fontSize={12} color={account.isOnWatchlist ? COLORS.liver : COLORS.white} > - {account.isOnWatchlist - ? t('watchlisted.address') - : t('watchlist.address.btn')} + {account.isOnWatchlist ? 'WATCHLISTED' : 'ADD TO WATCHLIST'} - {/*{!account.isOnWatchlist && (*/} - {/* <>*/} - {/* */} - {/* */} - {/* */} - {/*)}*/} + {!account.isOnWatchlist && ( + <> + + + + )} @@ -192,19 +185,17 @@ export const ExplorerAccountView = ( fontSize={12} > {listsWithAccount.length === 0 - ? t('add.address.to.group.upper.case') + ? 'ADD TO GROUP' : listsWithAccount.length === 1 ? StringUtils.formatAddress(listsWithAccount[0].name, 16, 0) - : `${t('address.added.to.group')} ${ - listsWithAccount.length - } ${t('groups.count')}`} + : `Added to ${listsWithAccount.length} groups`} - {/*{listsWithAccount.length === 0 && (*/} - {/* <>*/} - {/* */} - {/* */} - {/* */} - {/*)}*/} + {listsWithAccount.length === 0 && ( + <> + + + + )} @@ -212,7 +203,7 @@ export const ExplorerAccountView = ( diff --git a/src/components/templates/LocalizedRenderEmpty/index.tsx b/src/components/templates/RenderEmpty/index.tsx similarity index 64% rename from src/components/templates/LocalizedRenderEmpty/index.tsx rename to src/components/templates/RenderEmpty/index.tsx index 9a2a387f1..93cf7f924 100644 --- a/src/components/templates/LocalizedRenderEmpty/index.tsx +++ b/src/components/templates/RenderEmpty/index.tsx @@ -1,23 +1,21 @@ -import React from 'react'; import { View } from 'react-native'; import { EmptyWalletListPlaceholderIcon } from '@components/svg/icons'; import { Spacer, Text } from '@components/base'; import { verticalScale } from '@utils/scaling'; -import { styles } from '@components/templates/LocalizedRenderEmpty/styles'; -import { useTranslation } from 'react-i18next'; +import React from 'react'; +import { styles } from '@components/templates/RenderEmpty/styles'; -type LocalizedRenderEmptyProps = { +type RenderEmptyProps = { text: string; }; -export const LocalizedRenderEmpty = ({ text }: LocalizedRenderEmptyProps) => { - const { t } = useTranslation(); +export const RenderEmpty = ({ text }: RenderEmptyProps) => { return ( - {t(text)} + No {text} yet ); diff --git a/src/components/templates/LocalizedRenderEmpty/styles.ts b/src/components/templates/RenderEmpty/styles.ts similarity index 100% rename from src/components/templates/LocalizedRenderEmpty/styles.ts rename to src/components/templates/RenderEmpty/styles.ts diff --git a/src/components/templates/SearchAddress/SearchAddress.NoMatch.tsx b/src/components/templates/SearchAddress/SearchAddress.NoMatch.tsx index f142335a8..c3a889f97 100644 --- a/src/components/templates/SearchAddress/SearchAddress.NoMatch.tsx +++ b/src/components/templates/SearchAddress/SearchAddress.NoMatch.tsx @@ -4,10 +4,8 @@ import { Spacer, Text } from '@components/base'; import { COLORS } from '@constants/colors'; import { moderateScale, verticalScale } from '@utils/scaling'; import { NoMatch } from '@components/svg/icons/NoMatch'; -import { useTranslation } from 'react-i18next'; export function SearchAddressNoResult(): JSX.Element { - const { t } = useTranslation(); return ( @@ -18,7 +16,7 @@ export function SearchAddressNoResult(): JSX.Element { fontWeight="600" fontFamily="Inter_600SemiBold" > - {t('no.matches')} + Oops! No matches found - {t('check.typos')} + Please check for any typos or try a different address ); diff --git a/src/components/templates/SearchAddress/index.tsx b/src/components/templates/SearchAddress/index.tsx index e405135e6..527c31afb 100644 --- a/src/components/templates/SearchAddress/index.tsx +++ b/src/components/templates/SearchAddress/index.tsx @@ -35,7 +35,6 @@ import { CRYPTO_ADDRESS_MAX_LENGTH } from '@constants/variables'; import { COLORS } from '@constants/colors'; import { SearchTabNavigationProp } from '@appTypes'; import { styles } from './styles'; -import { useTranslation } from 'react-i18next'; interface SearchAdressProps { initialValue?: string; @@ -48,7 +47,6 @@ export const SearchAddress = (props: SearchAdressProps): JSX.Element => { const { initialValue, onContentVisibilityChanged = () => null } = props; const navigation = useNavigation(); const { height: WINDOW_HEIGHT } = useWindowDimensions(); - const { t } = useTranslation(); const { data: explorerInfo } = useExplorerInfo(); const [address, setAddress] = useState(''); const [searchSubmitted, setSearchSubmitted] = useState(false); @@ -110,8 +108,8 @@ export const SearchAddress = (props: SearchAdressProps): JSX.Element => { const toggleWatchlist = async (isOnWatchlist: boolean) => { if (isOnWatchlist) { Toast.show({ - title: t('toast.address.watchlisted.msg'), - message: t('toast.tap.to.rename.msg'), + title: 'Way to go! Address watchlisted.', + message: 'Tap to rename Address', type: ToastPosition.Top, onBodyPress: editModal.current?.show }); @@ -160,9 +158,9 @@ export const SearchAddress = (props: SearchAdressProps): JSX.Element => { }, 500); } else if (!scanned.current) { scanned.current = true; - Alert.alert(t('invalid.qr.code.msg'), '', [ + Alert.alert('Invalid QR code', '', [ { - text: t('scan.again.msg'), + text: 'Scan again', onPress: () => { scanned.current = false; } @@ -218,7 +216,7 @@ export const SearchAddress = (props: SearchAdressProps): JSX.Element => { )} } - placeholder={t('search.address.input')} + placeholder={'Search Address or TX hash'} returnKeyType="search" onFocus={onInputFocused} onBlur={onInputBlur} @@ -266,7 +264,7 @@ export const SearchAddress = (props: SearchAdressProps): JSX.Element => { fontSize={20} color={COLORS.neutral800} > - {t('transaction.details')} + Transaction details (null); const { data: ambData } = useAMBPrice(); - const { t } = useTranslation(); const ambPrice = ambData ? ambData.priceUSD : -1; let totalTransactionAmount; @@ -69,7 +67,7 @@ export const TransactionDetails = ( - {t('method')} + Method {transaction.type} @@ -78,7 +76,7 @@ export const TransactionDetails = ( - {t('amount')} + Amount {NumberUtils.formatNumber(transaction.amount, 0)} AMB @@ -90,7 +88,7 @@ export const TransactionDetails = ( - {t('from')} + From diff --git a/src/screens/Portfolio/index.tsx b/src/screens/Portfolio/index.tsx index bcf5b681d..e14dc7171 100644 --- a/src/screens/Portfolio/index.tsx +++ b/src/screens/Portfolio/index.tsx @@ -7,7 +7,33 @@ import { WatchList } from '@screens/Portfolio/components/PortfolioScreenTabs/com import { useIsFocused } from '@react-navigation/native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { View } from 'react-native'; -import { useTranslation } from 'react-i18next'; + +const portfolioTabRoutes = [ + { key: 'first', title: 'Addresses' }, + { key: 'second', title: 'Groups' } +] as const; + +type PortfolioTabRoutes = typeof portfolioTabRoutes; + +type PortfolioTabViewRoute = { + key: PortfolioTabRoutes[number]['key']; + title: PortfolioTabRoutes[number]['title']; +}; + +type RenderSceneProps = Parameters< + TabViewProps['renderScene'] +>[0]; + +const renderScene = ({ route }: RenderSceneProps) => { + switch (route.key) { + case 'first': + return ; + case 'second': + return ; + default: + return null; + } +}; type PortfolioScreenProps = { route: { @@ -18,34 +44,6 @@ type PortfolioScreenProps = { }; export const PortfolioScreen = ({ route }: PortfolioScreenProps) => { - const { t } = useTranslation(); - const portfolioTabRoutes = [ - { key: 'first', title: t('addresses') }, - { key: 'second', title: t('groups.capitalize') } - ] as const; - - type PortfolioTabRoutes = typeof portfolioTabRoutes; - - type PortfolioTabViewRoute = { - key: PortfolioTabRoutes[number]['key']; - title: PortfolioTabRoutes[number]['title']; - }; - - type RenderSceneProps = Parameters< - TabViewProps['renderScene'] - >[0]; - - // tslint:disable-next-line:no-shadowed-variable - const renderScene = ({ route }: RenderSceneProps) => { - switch (route.key) { - case 'first': - return ; - case 'second': - return ; - default: - return null; - } - }; const { top } = useSafeAreaInsets(); const activeTab = route?.params?.tabs?.activeTab; const [index, setIndex] = useState(0); diff --git a/src/screens/Search/components/WalletItem.tsx b/src/screens/Search/components/WalletItem.tsx index 64c862fcf..e70b2b224 100644 --- a/src/screens/Search/components/WalletItem.tsx +++ b/src/screens/Search/components/WalletItem.tsx @@ -9,7 +9,6 @@ import { COLORS } from '@constants/colors'; import { useLists } from '@contexts'; import { useWatchlist } from '@hooks'; import { AddressIndicator } from '@components/templates'; -import { useTranslation } from 'react-i18next'; interface ExplorerWalletItemProps { item: ExplorerAccount; @@ -24,7 +23,6 @@ export const ExplorerWalletItem = ( const { address, transactionCount, ambBalance } = item; const { listsOfAddressGroup } = useLists((v) => v); const { watchlist } = useWatchlist(); - const { t } = useTranslation(); const listWithAddress = listsOfAddressGroup.filter( (list) => list.accounts.indexOfItem(item, 'address') > -1 ); @@ -74,19 +72,18 @@ export const ExplorerWalletItem = ( color={COLORS.smokyBlack50} fontFamily="Inter_500Medium" > - {t('single.address.holding')} + Holding{' '} {NumberUtils.formatNumber( item.calculatePercentHoldings(totalSupply), 2 - )}{' '} - {t('single.address.supply')} + )} + % of supply - {/* TODO add localisation here, key is "transactions" */} {StringUtils.pluralize(transactionCount, 'Transaction')} diff --git a/src/screens/Search/index.tsx b/src/screens/Search/index.tsx index 2552e4515..69d92395c 100644 --- a/src/screens/Search/index.tsx +++ b/src/screens/Search/index.tsx @@ -22,12 +22,10 @@ import { SearchSort } from './Search.types'; import { COLORS } from '@constants/colors'; import { styles } from './styles'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; -import { useTranslation } from 'react-i18next'; export const SearchScreen = () => { const navigation = useNavigation(); const { data: infoData } = useExplorerInfo(); - const { t } = useTranslation(); const { data: accounts, loading: accountsLoading, @@ -98,12 +96,12 @@ export const SearchScreen = () => { fontSize={20} color={COLORS.smokyBlack} > - {t('top.holders')} + Top holders {accountsError ? ( - {t('no.account.info')} + Could not load accounts info ) : ( infoData && accounts && ( diff --git a/src/screens/Settings/components/SettingsBlock/index.tsx b/src/screens/Settings/components/SettingsBlock/index.tsx index 40fd919f2..bec0d0285 100644 --- a/src/screens/Settings/components/SettingsBlock/index.tsx +++ b/src/screens/Settings/components/SettingsBlock/index.tsx @@ -17,10 +17,7 @@ import { import { BottomSheetNotificationSettings } from '@components/templates'; import { styles } from '@screens/Settings/components/SettingsBlock/style'; import { COLORS } from '@constants/colors'; -import { useTranslation } from 'react-i18next'; - export const SettingsBlock = () => { - const { t } = useTranslation(); const [selectedLanguage, setSelectedLanguage] = useState('English'); const [selectedCurrency, setSelectedCurrency] = useState('US Dollars (USD)'); @@ -61,9 +58,7 @@ export const SettingsBlock = () => { > - - {t('base.currency.modal')} - + Base currency {selectedCurrency} @@ -82,7 +77,7 @@ export const SettingsBlock = () => { > - {t('language.modal')} + Language {selectedLanguage} @@ -101,9 +96,7 @@ export const SettingsBlock = () => { > - - {t('notification.settings.modal')} - + Notification settings diff --git a/src/screens/Settings/components/SettingsBlock/modals/BottomSheetBaseCurrency/index.tsx b/src/screens/Settings/components/SettingsBlock/modals/BottomSheetBaseCurrency/index.tsx index 9f7ca5cd8..80ea9d917 100644 --- a/src/screens/Settings/components/SettingsBlock/modals/BottomSheetBaseCurrency/index.tsx +++ b/src/screens/Settings/components/SettingsBlock/modals/BottomSheetBaseCurrency/index.tsx @@ -8,7 +8,6 @@ import { styles } from '@screens/Settings/components/SettingsBlock/modals/style' import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { COLORS } from '@constants/colors'; import { scale } from '@utils/scaling'; -import { useTranslation } from 'react-i18next'; type Props = { ref: RefObject; @@ -71,7 +70,6 @@ export const BottomSheetSelectBaseCurrency = forwardRef( selectedCurrency || '' ); const { top: topInset } = useSafeAreaInsets(); - const { t } = useTranslation(); const onCurrencyPress = (value: Currency) => { setModalActiveCurrency(value); @@ -92,7 +90,7 @@ export const BottomSheetSelectBaseCurrency = forwardRef( fontSize={scale(16)} color={COLORS.smokyBlack} > - {t('select.base.currency')} + Select base currency } titlePosition={Platform.select({ ios: 'left', default: 'center' })} diff --git a/src/screens/Settings/components/SettingsBlock/modals/BottomSheetSelectLanguage/index.tsx b/src/screens/Settings/components/SettingsBlock/modals/BottomSheetSelectLanguage/index.tsx index d3e2b798f..e13413c4d 100644 --- a/src/screens/Settings/components/SettingsBlock/modals/BottomSheetSelectLanguage/index.tsx +++ b/src/screens/Settings/components/SettingsBlock/modals/BottomSheetSelectLanguage/index.tsx @@ -8,8 +8,6 @@ import { styles } from '@screens/Settings/components/SettingsBlock/modals/style' import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { COLORS } from '@constants/colors'; import { scale } from '@utils/scaling'; -import { useTranslation } from 'react-i18next'; -import useLocalization from '@contexts/Localizations'; type Props = { ref: RefObject; @@ -34,16 +32,16 @@ type LanguageData = { const mockedLanguages: LanguageData[] = [ { language: 'English' - }, + } // { // language: 'Arabic' // }, // { // language: 'Spanish' // }, - { - language: 'Turkish' - } + // { + // language: 'Turkish' + // }, // { // language: 'Hindi' // }, @@ -61,19 +59,14 @@ const mockedLanguages: LanguageData[] = [ export const BottomSheetSelectLanguage = forwardRef( ({ selectedLanguage, handleLanguageSave }, ref) => { const localRef: ForwardedRef = useForwardedRef(ref); - // eslint-disable-next-line @typescript-eslint/no-unused-vars const [modalActiveLanguage, setModalActiveLanguage] = useState( selectedLanguage || '' ); const { top: topInset } = useSafeAreaInsets(); - const { changeCurrentLanguage, currentLanguage } = useLocalization(); - - const { t } = useTranslation(); const onLanguagePress = (value: Language) => { setModalActiveLanguage(value); handleLanguageSave(value); - changeCurrentLanguage(value); }; return ( @@ -91,7 +84,7 @@ export const BottomSheetSelectLanguage = forwardRef( fontSize={scale(16)} color={COLORS.smokyBlack} > - {t('select.language')} + Select language } titlePosition={Platform.select({ ios: 'left', default: 'center' })} @@ -108,7 +101,7 @@ export const BottomSheetSelectLanguage = forwardRef( renderItem={({ item, index }) => { return ( { - const { t } = useTranslation(); const openLink = () => { Linking.openURL('https://airdao.academy/faqs'); }; @@ -19,7 +17,7 @@ export const SettingsInfoBlock = () => { @@ -31,8 +29,8 @@ export const SettingsInfoBlock = () => { })} {Platform.select({ - ios: t('rate.btn.ios'), - android: t('rate.btn.android') + ios: 'Rate us on the App Store', + android: 'Rate us on the Play Store' })} diff --git a/src/screens/SingleCollection/index.tsx b/src/screens/SingleCollection/index.tsx index bea27cf80..be8a7650d 100644 --- a/src/screens/SingleCollection/index.tsx +++ b/src/screens/SingleCollection/index.tsx @@ -20,7 +20,6 @@ import { useAllAddressesContext } from '@contexts'; import { BottomSheetAddNewAddressToGroup } from './modals/BottomSheetAddNewAddressToGroup'; import { sortListByKey } from '@utils/sort'; import { styles } from './styles'; -import { useTranslation } from 'react-i18next'; export const SingleGroupScreen = () => { const { @@ -30,7 +29,6 @@ export const SingleGroupScreen = () => { } = useRoute>(); const navigation = useNavigation(); const { data: ambPriceData } = useAMBPrice(); - const { t } = useTranslation(); const addNewAddressToGroupRef = useRef(null); const groupRenameRef = useRef(null); @@ -108,7 +106,7 @@ export const SingleGroupScreen = () => { fontFamily="Inter_600SemiBold" fontSize={12} > - {t('total.balance')} + TOTAL BALANCE diff --git a/src/screens/SingleCollection/modals/BottomSheetAddNewAddressToGroup/index.tsx b/src/screens/SingleCollection/modals/BottomSheetAddNewAddressToGroup/index.tsx index d49cff210..9b6f5424f 100644 --- a/src/screens/SingleCollection/modals/BottomSheetAddNewAddressToGroup/index.tsx +++ b/src/screens/SingleCollection/modals/BottomSheetAddNewAddressToGroup/index.tsx @@ -38,13 +38,25 @@ import { StringUtils } from '@utils/string'; import { SearchSort } from '@screens/Search/Search.types'; import { etherumAddressRegex } from '@constants/regex'; import { styles } from './styles'; -import { useTranslation } from 'react-i18next'; type Props = { ref: RefObject; collection: AccountList; }; +const AddressSources: Segment[] = [ + { + title: 'Watchlist', + value: 0, + id: 'watchlist' + }, + { + title: 'Top Holders', + value: 1, + id: 'topHolders' + } +]; + export const BottomSheetAddNewAddressToGroup = forwardRef< BottomSheetRef, Props @@ -57,21 +69,6 @@ export const BottomSheetAddNewAddressToGroup = forwardRef< fetchNextPage: fetchMoreTopHolders } = useExplorerAccounts(SearchSort.Balance); const [searchValue, setSearchValue] = useState(''); - const { t } = useTranslation(); - - const AddressSources: Segment[] = [ - { - title: t('watchlist.tab'), - value: 0, - id: 'watchlist' - }, - { - title: t('top.holders.capitalize'), - value: 1, - id: 'topHolders' - } - ]; - const { data: searchedAccount, loading: searchLoading, @@ -189,9 +186,9 @@ export const BottomSheetAddNewAddressToGroup = forwardRef< }, 500); } else if (!scanned.current) { scanned.current = true; - Alert.alert(t('invalid.qr.code.msg'), '', [ + Alert.alert('Invalid QR code', '', [ { - text: t('scan.again.msg'), + text: 'Scan again', onPress: () => { scanned.current = false; } @@ -231,7 +228,7 @@ export const BottomSheetAddNewAddressToGroup = forwardRef< fontFamily="Inter_700Bold" fontSize={18} color={COLORS.nero} - >{`${t('add.address.to.selected.group')} ${StringUtils.formatAddress( + >{`Add address to ${StringUtils.formatAddress( collection.name, 10, 0 @@ -259,7 +256,7 @@ export const BottomSheetAddNewAddressToGroup = forwardRef< } type="text" style={{ width: '65%', height: 50 }} - placeholder={t('search.public.address.input')} + placeholder="Search public address" placeholderTextColor="#2f2b4399" value={searchValue} onChangeText={setSearchValue} @@ -374,15 +371,15 @@ export const BottomSheetAddNewAddressToGroup = forwardRef< fontWeight="600" > {selectingAddedItems - ? `${t('remove.btn')} ${StringUtils.pluralize( + ? `Remove ${StringUtils.pluralize( selectedAddresses.length, - t('address'), - t('addresses') + 'Address', + 'Addresses' )}` - : `${t('add')} ${StringUtils.pluralize( + : `Add ${StringUtils.pluralize( selectedAddresses.length, - t('address'), - t('addresses') + 'Address', + 'Addresses' )}`} diff --git a/src/screens/SingleCollection/modals/BottomSheetRenameAddress/index.tsx b/src/screens/SingleCollection/modals/BottomSheetRenameAddress/index.tsx index 94211a8a5..0d990e6a5 100644 --- a/src/screens/SingleCollection/modals/BottomSheetRenameAddress/index.tsx +++ b/src/screens/SingleCollection/modals/BottomSheetRenameAddress/index.tsx @@ -8,7 +8,6 @@ import { useForwardedRef } from '@hooks/useForwardedRef'; import { styles } from '@screens/SingleCollection/modals/BottomSheetRenameAddress/styles'; import { BottomSheetFloat, PrimaryButton } from '@components/modular'; import { scale, verticalScale } from '@utils/scaling'; -import { useTranslation } from 'react-i18next'; type Props = { ref: RefObject; @@ -22,7 +21,6 @@ export const BottomSheetRenameAddress = forwardRef( address || '' ); const localRef: ForwardedRef = useForwardedRef(ref); - const { t } = useTranslation(); return ( ( fontSize={16} color={COLORS.nero} > - {t('rename.address')} + Rename address setLocalAddressName(value)} type="text" - placeholder={t('edit.name')} + placeholder="Edit name" placeholderTextColor={COLORS.neutral900Alpha[60]} style={[styles.bottomSheetInput]} /> @@ -60,7 +58,7 @@ export const BottomSheetRenameAddress = forwardRef( fontSize={16} color={COLORS.white} > - {t('save.btn')} + Save @@ -74,7 +72,7 @@ export const BottomSheetRenameAddress = forwardRef( color={COLORS.smokyBlack} fontSize={16} > - {t('cancel.btn')} + Cancel diff --git a/src/screens/Wallets/components/Header.tsx b/src/screens/Wallets/components/Header.tsx index 937c2a7d8..02a5fe90f 100644 --- a/src/screens/Wallets/components/Header.tsx +++ b/src/screens/Wallets/components/Header.tsx @@ -19,7 +19,6 @@ import { useNotificationsQuery } from '@hooks'; import { Cache, CacheKey } from '@utils/cache'; import { useNewNotificationsCount } from '../hooks/useNewNotificationsCount'; import { COLORS } from '@constants/colors'; -import { useTranslation } from 'react-i18next'; export const HomeHeader = React.memo((): JSX.Element => { const navigation = useNavigation(); @@ -28,7 +27,6 @@ export const HomeHeader = React.memo((): JSX.Element => { const scanned = useRef(false); const { data: notifications } = useNotificationsQuery(); const newNotificationsCount = useNewNotificationsCount(); - const { t } = useTranslation(); const openScanner = () => { scanner.current?.show(); @@ -49,9 +47,9 @@ export const HomeHeader = React.memo((): JSX.Element => { }); } else if (!scanned.current) { scanned.current = true; - Alert.alert(t('invalid.qr.code.msg'), '', [ + Alert.alert('Invalid QR code', '', [ { - text: t('scan.again.msg'), + text: 'Scan again', onPress: () => { scanned.current = false; } @@ -59,7 +57,7 @@ export const HomeHeader = React.memo((): JSX.Element => { ]); } }, - [navigation, t] + [navigation] ); const setLastNotificationTime = useCallback(() => { diff --git a/src/screens/Wallets/components/HomeTabs/HomeCollections.tsx b/src/screens/Wallets/components/HomeTabs/HomeCollections.tsx index 36816404c..70c3f79af 100644 --- a/src/screens/Wallets/components/HomeTabs/HomeCollections.tsx +++ b/src/screens/Wallets/components/HomeTabs/HomeCollections.tsx @@ -5,7 +5,7 @@ import { Button, Spacer, Spinner } from '@components/base'; import { useNavigation } from '@react-navigation/native'; import { PortfolioNavigationProp } from '@appTypes'; import { styles } from '@screens/Wallets/components/HomeTabs/styles'; -import { LocalizedRenderEmpty } from '@components/templates'; +import { RenderEmpty } from '@components/templates/RenderEmpty'; import { verticalScale } from '@utils/scaling'; import { AccountList } from '@models'; import { CollectionItem } from '@components/modular'; @@ -31,7 +31,7 @@ export const HomeCollections = () => { } if (listsOfAddressGroup.length === 0) { - return ; + return ; } return ( diff --git a/src/screens/Wallets/components/HomeTabs/HomeTabs.tsx b/src/screens/Wallets/components/HomeTabs/HomeTabs.tsx index 42bbb2031..94d48d444 100644 --- a/src/screens/Wallets/components/HomeTabs/HomeTabs.tsx +++ b/src/screens/Wallets/components/HomeTabs/HomeTabs.tsx @@ -15,14 +15,12 @@ import { useNavigation } from '@react-navigation/native'; import { BottomSheetCreateRenameGroup } from '@components/templates/BottomSheetCreateRenameGroup'; import { useAllAddressesContext, useLists } from '@contexts'; import { useWatchlist } from '@hooks'; -import { useTranslation } from 'react-i18next'; export const HomeTabs = () => { const navigation = useNavigation(); const { handleOnCreate, listsOfAddressGroup } = useLists((v) => v); const { watchlist } = useWatchlist(); const { addressesLoading } = useAllAddressesContext((v) => v); - const { t } = useTranslation(); const scrollView = useRef(null); const createCollectionOrAddAddressRef = useRef(null); @@ -94,7 +92,7 @@ export const HomeTabs = () => { color={currentIndex === 0 ? COLORS.jetBlack : COLORS.lavenderGray} fontSize={currentIndex === 0 ? 20 : 18} > - {t('addresses')} + Addresses @@ -107,7 +105,7 @@ export const HomeTabs = () => { color={currentIndex === 1 ? COLORS.jetBlack : COLORS.lavenderGray} fontSize={currentIndex === 1 ? 20 : 18} > - {t('groups.capitalize')} + Groups @@ -159,7 +157,7 @@ export const HomeTabs = () => { fontSize={16} color={COLORS.deepBlue} > - {t('see.all.btn')} + See all diff --git a/src/screens/Wallets/components/HomeTabs/HomeWatchlists.tsx b/src/screens/Wallets/components/HomeTabs/HomeWatchlists.tsx index 08e0e054d..cbf2d4bef 100644 --- a/src/screens/Wallets/components/HomeTabs/HomeWatchlists.tsx +++ b/src/screens/Wallets/components/HomeTabs/HomeWatchlists.tsx @@ -5,7 +5,8 @@ import { Button, Spacer, Spinner } from '@components/base'; import { useNavigation } from '@react-navigation/native'; import { PortfolioNavigationProp } from '@appTypes'; import { styles } from '@screens/Wallets/components/HomeTabs/styles'; -import { WalletItem, LocalizedRenderEmpty } from '@components/templates'; +import { RenderEmpty } from '@components/templates/RenderEmpty'; +import { WalletItem } from '@components/templates'; import { ExplorerAccount } from '@models'; import { verticalScale } from '@utils/scaling'; import { useAllAddressesContext } from '@contexts'; @@ -20,7 +21,7 @@ export const HomeWatchlists = () => { const navigation = useNavigation(); if (watchlist.length === 0) { - return ; + return ; } const navigateToAddressDetails = (item: ExplorerAccount) => { diff --git a/yarn.lock b/yarn.lock index 20a906057..31be42160 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1369,13 +1369,6 @@ dependencies: regenerator-runtime "^0.13.11" -"@babel/runtime@^7.22.5": - version "7.22.10" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.10.tgz#ae3e9631fd947cb7e3610d3e9d8fef5f76696682" - integrity sha512-21t/fkKLMZI4pqP2wlmsQAWnYW1PDyKyyUV4vCi+B25ydmdaYTKXPwCj0BzSUnZf4seIiYvSA3jcZ3gdsMFkLQ== - dependencies: - regenerator-runtime "^0.14.0" - "@babel/runtime@^7.9.2": version "7.21.5" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.21.5.tgz#8492dddda9644ae3bda3b45eabe87382caee7200" @@ -3495,11 +3488,6 @@ big-integer@1.6.x: resolved "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz" integrity sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg== -bignumber.js@*: - version "9.1.1" - resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.1.1.tgz#c4df7dc496bd849d4c9464344c1aa74228b4dac6" - integrity sha512-pHm4LsMJ6lzgNGVfZHjMoO8sdoRhOzOH4MLmY65Jg70bpxCKu5iOHNJyfF6OyvYw7t8Fpf35RuzUyqnQsj8Vig== - bl@^4.1.0: version "4.1.0" resolved "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz" @@ -6019,13 +6007,6 @@ html-escaper@^2.0.0: resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== -html-parse-stringify@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz#dfc1017347ce9f77c8141a507f233040c59c55d2" - integrity sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg== - dependencies: - void-elements "3.1.0" - http-errors@2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz" @@ -6069,21 +6050,6 @@ husky@^8.0.3: resolved "https://registry.npmjs.org/husky/-/husky-8.0.3.tgz" integrity sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg== -i18n-js@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/i18n-js/-/i18n-js-4.3.0.tgz#f1098e21a762faf8b8d09453abfdb6a28ee0f57b" - integrity sha512-PX93eT6WPV6Ym6mHtFKGDRZB0zwDX7HUPkgprjsZ28J6/Ohw1nvRYuM93or3pWv2VLxs6XfBf7X9Fc/YAZNEtQ== - dependencies: - bignumber.js "*" - make-plural "*" - -i18next@^23.4.4: - version "23.4.4" - resolved "https://registry.yarnpkg.com/i18next/-/i18next-23.4.4.tgz#ec8fb2b5f3c5d8e3bf3f8ab1b19e743be91300e0" - integrity sha512-+c9B0txp/x1m5zn+QlwHaCS9vyFtmIAEXbVSFzwCX7vupm5V7va8F9cJGNJZ46X9ZtoGzhIiRC7eTIIh93TxPA== - dependencies: - "@babel/runtime" "^7.22.5" - iconv-lite@0.4.24: version "0.4.24" resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz" @@ -7535,11 +7501,6 @@ make-dir@^3.0.0: dependencies: semver "^6.0.0" -make-plural@*: - version "7.3.0" - resolved "https://registry.yarnpkg.com/make-plural/-/make-plural-7.3.0.tgz#2889dbafca2fb097037c47967d3e3afa7e48a52c" - integrity sha512-/K3BC0KIsO+WK2i94LkMPv3wslMrazrQhfi5We9fMbLlLjzoOSJWr7TAdupLlDWaJcWxwoNosBkhFDejiu5VDw== - makeerror@1.0.12: version "1.0.12" resolved "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz" @@ -8970,14 +8931,6 @@ react-freeze@^1.0.0: resolved "https://registry.npmjs.org/react-freeze/-/react-freeze-1.0.3.tgz" integrity sha512-ZnXwLQnGzrDpHBHiC56TXFXvmolPeMjTn1UOm610M4EXGzbEDR7oOIyS2ZiItgbs6eZc4oU/a0hpk8PrcKvv5g== -react-i18next@^13.1.0: - version "13.1.0" - resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-13.1.0.tgz#18b1769c1c22c8f1d9a8f395bec48240e3349cfd" - integrity sha512-TlDz4761vJe810o5nEzRkblfS0FQz+k9R5BLOPVhn0Ds6WMDcwGSMbU5fne9WSd4/vLgKg7fEJ7/JboWdmsHOw== - dependencies: - "@babel/runtime" "^7.22.5" - html-parse-stringify "^3.0.1" - "react-is@^16.12.0 || ^17.0.0 || ^18.0.0", react-is@^18.0.0, react-is@^18.2.0: version "18.2.0" resolved "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz" @@ -9268,11 +9221,6 @@ regenerator-runtime@^0.13.11, regenerator-runtime@^0.13.2: resolved "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz" integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg== -regenerator-runtime@^0.14.0: - version "0.14.0" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz#5e19d68eb12d486f797e15a3c6a918f7cec5eb45" - integrity sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA== - regenerator-transform@^0.15.1: version "0.15.1" resolved "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.1.tgz" @@ -10828,11 +10776,6 @@ vlq@^1.0.0: resolved "https://registry.npmjs.org/vlq/-/vlq-1.0.1.tgz" integrity sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w== -void-elements@3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-3.1.0.tgz#614f7fbf8d801f0bb5f0661f5b2f5785750e4f09" - integrity sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w== - w3c-xmlserializer@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz#aebdc84920d806222936e3cdce408e32488a3073" From 21aa1ac0f44abe7b173356aa790d727483e45d8f Mon Sep 17 00:00:00 2001 From: ilyaAir <128598571+ilyaAir@users.noreply.github.com> Date: Thu, 17 Aug 2023 13:13:08 +0300 Subject: [PATCH 056/509] Revert "Revert "Dev"" --- Providers.tsx | 16 ++- package.json | 3 + .../composite/Button/CopyToClipboard.tsx | 7 +- .../modular/CollectionItem/index.tsx | 4 +- .../templates/AMBPriceHistory/index.tsx | 12 +- .../templates/AddWalletToList/index.tsx | 6 +- .../BottomSheetAddWalletToList/index.tsx | 24 ++++ ...BottomSheetRemoveAddressFromWatchlists.tsx | 8 +- .../index.tsx | 6 +- .../BottomSheetCreateRenameGroup/index.tsx | 31 ++-- .../BottomSheetEditCollection/index.tsx | 7 +- .../templates/BottomSheetEditWallet/index.tsx | 20 +-- .../BottomSheetNotificationSettings/index.tsx | 15 +- .../index.tsx | 8 +- .../ExplorerAccount.TransactionItem.tsx | 4 +- .../ExplorerAccount.Transactions.tsx | 4 +- .../templates/ExplorerAccount/index.tsx | 47 +++--- .../index.tsx | 12 +- .../styles.ts | 0 .../SearchAddress/SearchAddress.NoMatch.tsx | 6 +- .../templates/SearchAddress/index.tsx | 14 +- .../templates/TransactionDetails/index.tsx | 12 +- src/components/templates/WalletList/index.tsx | 4 +- src/components/templates/index.ts | 1 + src/contexts/Localizations/index.tsx | 64 +++++++++ src/contexts/index.ts | 1 + src/lib/permission.ts | 4 +- src/localization/i18n.ts | 20 +++ src/localization/locales/English.json | 134 ++++++++++++++++++ src/localization/locales/Turkish.json | 133 +++++++++++++++++ src/localization/locales/translations.ts | 17 +++ src/navigation/stacks/TabsNavigator.tsx | 14 +- src/screens/AMBMarket/AMBMarket.constants.ts | 21 +-- src/screens/AMBMarket/components/About.tsx | 7 +- .../AMBMarket/components/DetailedInfo.tsx | 8 +- src/screens/AMBMarket/index.tsx | 12 +- src/screens/Address/index.tsx | 15 +- .../Notifications/components/Header.tsx | 4 +- src/screens/Notifications/index.tsx | 14 +- .../components/ListsOfAddressGroup/index.tsx | 4 +- .../components/PortfolioScreenTabs/index.tsx | 6 +- src/screens/Portfolio/index.tsx | 56 ++++---- src/screens/Search/components/WalletItem.tsx | 9 +- src/screens/Search/index.tsx | 6 +- .../components/SettingsBlock/index.tsx | 13 +- .../modals/BottomSheetBaseCurrency/index.tsx | 4 +- .../BottomSheetSelectLanguage/index.tsx | 19 ++- .../components/SettingsInfoBlock/index.tsx | 8 +- src/screens/SingleCollection/index.tsx | 4 +- .../BottomSheetAddNewAddressToGroup/index.tsx | 49 ++++--- .../modals/BottomSheetRenameAddress/index.tsx | 10 +- src/screens/Wallets/components/Header.tsx | 8 +- .../components/HomeTabs/HomeCollections.tsx | 4 +- .../Wallets/components/HomeTabs/HomeTabs.tsx | 8 +- .../components/HomeTabs/HomeWatchlists.tsx | 5 +- yarn.lock | 57 ++++++++ 56 files changed, 790 insertions(+), 219 deletions(-) rename src/components/templates/{RenderEmpty => LocalizedRenderEmpty}/index.tsx (64%) rename src/components/templates/{RenderEmpty => LocalizedRenderEmpty}/styles.ts (100%) create mode 100644 src/contexts/Localizations/index.tsx create mode 100644 src/localization/i18n.ts create mode 100644 src/localization/locales/English.json create mode 100644 src/localization/locales/Turkish.json create mode 100644 src/localization/locales/translations.ts diff --git a/Providers.tsx b/Providers.tsx index 03b8242e7..009cec214 100644 --- a/Providers.tsx +++ b/Providers.tsx @@ -1,9 +1,13 @@ +import React from 'react'; import { combineComponents } from '@helpers/combineComponents'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import React from 'react'; import { SafeAreaProvider } from 'react-native-safe-area-context'; import { ListsContextProvider } from '@contexts/ListsContext'; -import { AllAddressesProvider, OnboardingContextProvider } from '@contexts'; +import { + AllAddressesProvider, + OnboardingContextProvider, + LocalizationProvider +} from '@contexts'; const queryClient = new QueryClient(); @@ -15,9 +19,14 @@ const WrappedSafeAreaProvider: React.FC = ({ children }: any) => ( {children} ); +const WrappedLocalizationProvider: React.FC = ({ children }: any) => ( + {children} +); + const independentProviders = [ WrappedQueryClientProvider, - WrappedSafeAreaProvider + WrappedSafeAreaProvider, + WrappedLocalizationProvider ]; /** * The order of the providers matters @@ -26,6 +35,7 @@ const providers = [ ...independentProviders, AllAddressesProvider, ListsContextProvider, + WrappedLocalizationProvider, OnboardingContextProvider ]; diff --git a/package.json b/package.json index 53187d363..2dfbdf292 100644 --- a/package.json +++ b/package.json @@ -56,12 +56,15 @@ "expo-store-review": "~6.2.1", "expo-system-ui": "~2.2.1", "expo-updates": "~0.16.4", + "i18n-js": "^4.3.0", + "i18next": "^23.4.4", "jest": "^29.2.1", "jest-expo": "^48.0.2", "moment": "^2.29.4", "patch-package": "^7.0.0", "postinstall-postinstall": "^2.1.0", "react": "18.2.0", + "react-i18next": "^13.1.0", "react-native": "0.71.8", "react-native-calendar-picker": "^7.1.4", "react-native-gesture-handler": "~2.9.0", diff --git a/src/components/composite/Button/CopyToClipboard.tsx b/src/components/composite/Button/CopyToClipboard.tsx index dd602b1ef..c83ca2aa0 100644 --- a/src/components/composite/Button/CopyToClipboard.tsx +++ b/src/components/composite/Button/CopyToClipboard.tsx @@ -6,6 +6,7 @@ import { ClipboardFilledIcon } from '@components/svg/icons'; import { scale } from '@utils/scaling'; import { Toast, ToastPosition } from '@components/modular/Toast'; import { BaseButtonProps } from '@components/base/Button'; +import { useTranslation } from 'react-i18next'; export interface CopyToClipboardButtonProps extends Omit { @@ -18,9 +19,13 @@ export const CopyToClipboardButton = ( props: CopyToClipboardButtonProps ): JSX.Element => { const { textToDisplay, textToCopy, textProps, ...buttonProps } = props; + const { t } = useTranslation(); const onPress = async () => { - Toast.show({ message: 'Copied to Clipboard', type: ToastPosition.Bottom }); + Toast.show({ + message: t('copied.to.clipboard'), + type: ToastPosition.Bottom + }); await Clipboard.setStringAsync(textToCopy || textToDisplay); }; diff --git a/src/components/modular/CollectionItem/index.tsx b/src/components/modular/CollectionItem/index.tsx index 2a0eead47..b1ce4bec2 100644 --- a/src/components/modular/CollectionItem/index.tsx +++ b/src/components/modular/CollectionItem/index.tsx @@ -6,6 +6,7 @@ import { COLORS } from '@constants/colors'; import { useAMBPrice } from '@hooks'; import { AccountList } from '@models'; import { NumberUtils } from '@utils/number'; +import { useTranslation } from 'react-i18next'; interface CollectionItemProps { collection: AccountList; @@ -15,6 +16,7 @@ interface CollectionItemProps { export function CollectionItem(props: CollectionItemProps) { const { collection, style } = props; const { data: ambPriceData } = useAMBPrice(); + const { t } = useTranslation(); const tokensFormatted = useMemo(() => { const formattedNumber = NumberUtils.formatNumber( @@ -53,7 +55,7 @@ export function CollectionItem(props: CollectionItemProps) { color={COLORS.smokyBlack50} fontSize={12} > - {collection.accountCount + ' addresses'} + {collection.accountCount + ` ${t('addresses.text')}`} {collection.accountCount > 0 && ( diff --git a/src/components/templates/AMBPriceHistory/index.tsx b/src/components/templates/AMBPriceHistory/index.tsx index b215003bc..d08134c7d 100644 --- a/src/components/templates/AMBPriceHistory/index.tsx +++ b/src/components/templates/AMBPriceHistory/index.tsx @@ -1,4 +1,6 @@ import React, { useEffect, useMemo, useRef, useState } from 'react'; +import { View } from 'react-native'; +import { useTranslation } from 'react-i18next'; import Animated, { useAnimatedProps, useAnimatedStyle, @@ -16,9 +18,8 @@ import { scale, verticalScale } from '@utils/scaling'; import { Badge } from '@components/base/Badge'; import { PercentChange } from '@components/composite'; import { BezierChart } from '../BezierChart'; -import { styles } from './styles'; import { MONTH_NAMES } from '@constants/variables'; -import { View } from 'react-native'; +import { styles } from './styles'; interface AMBPriceHistoryProps { badgeType: 'view' | 'button'; @@ -44,6 +45,7 @@ export const AMBPriceHistory = (props: AMBPriceHistoryProps) => { const ambPrice = useSharedValue(ambPriceNow?.priceUSD || 0); const selectedPointDate = useSharedValue(-1); const didSetAMBPriceFromAPI = useRef(false); + const { t } = useTranslation(); useEffect(() => { if (ambPriceNow) { @@ -204,15 +206,15 @@ export const AMBPriceHistory = (props: AMBPriceHistoryProps) => { // value: '1h' // }, { - text: '1D', + text: t('chart.timeframe.daily'), value: '1d' }, { - text: '1W', + text: t('chart.timeframe.weekly'), value: 'weekly' }, { - text: '1M', + text: t('chart.timeframe.monthly'), value: 'monthly' } ]} diff --git a/src/components/templates/AddWalletToList/index.tsx b/src/components/templates/AddWalletToList/index.tsx index dd55bca98..429aba41b 100644 --- a/src/components/templates/AddWalletToList/index.tsx +++ b/src/components/templates/AddWalletToList/index.tsx @@ -10,6 +10,7 @@ import { COLORS } from '@constants/colors'; import { SearchIcon } from '@components/svg/icons'; import { NumberUtils } from '@utils/number'; import { useLists } from '@contexts/ListsContext'; +import { useTranslation } from 'react-i18next'; export interface AddWalletToListProps { wallet: ExplorerAccount; @@ -20,6 +21,7 @@ export interface AddWalletToListProps { export const AddWalletToList = (props: AddWalletToListProps): JSX.Element => { const { wallet, lists, onWalletMove } = props; const { toggleAddressesInList } = useLists((v) => v); + const { t } = useTranslation(); const [searchText, setSearchText] = useState(''); const filteredLists = useMemo( () => @@ -76,7 +78,7 @@ export const AddWalletToList = (props: AddWalletToListProps): JSX.Element => { fontFamily="Inter_500Medium" color={COLORS.smokyBlack50} > - {list.accountCount} Addresses + {list.accountCount} {t('addresses')} @@ -95,7 +97,7 @@ export const AddWalletToList = (props: AddWalletToListProps): JSX.Element => { } /> diff --git a/src/components/templates/BottomSheetAddWalletToList/index.tsx b/src/components/templates/BottomSheetAddWalletToList/index.tsx index 1dc82b2b2..a2c08c4d2 100644 --- a/src/components/templates/BottomSheetAddWalletToList/index.tsx +++ b/src/components/templates/BottomSheetAddWalletToList/index.tsx @@ -5,6 +5,9 @@ import { useFullscreenModalHeight } from '@hooks'; import { COLORS } from '@constants/colors'; import { AddWalletToList, AddWalletToListProps } from '../AddWalletToList'; import { verticalScale } from '@utils/scaling'; +import { PrimaryButton } from '@components/modular'; +import { BottomSheetCreateRenameGroup } from '@components/templates'; +import { useLists } from '@contexts'; interface BottomSheetAddWalletToListProps extends AddWalletToListProps { title: string; @@ -16,6 +19,11 @@ export const BottomSheetAddWalletToList = forwardRef< >((props, ref) => { const { title, ...addWalletToListProps } = props; const fullscreenHeight = useFullscreenModalHeight(); + const { handleOnCreate, createGroupRef } = useLists((v) => v); + + const showCreateNewListModal = () => { + createGroupRef.current?.show(); + }; return ( + { + showCreateNewListModal(); + }} + style={{ width: '90%', alignSelf: 'center' }} + > + + Create new group + + + + ); }); diff --git a/src/components/templates/BottomSheetConfirmRemove/BottomSheetRemoveAddressFromWatchlists.tsx b/src/components/templates/BottomSheetConfirmRemove/BottomSheetRemoveAddressFromWatchlists.tsx index ddf5efc65..2b5f72ca1 100644 --- a/src/components/templates/BottomSheetConfirmRemove/BottomSheetRemoveAddressFromWatchlists.tsx +++ b/src/components/templates/BottomSheetConfirmRemove/BottomSheetRemoveAddressFromWatchlists.tsx @@ -8,6 +8,7 @@ import { COLORS } from '@constants/colors'; import { ExplorerAccount } from '@models'; import { BottomSheetFloat } from '@components/modular'; import { verticalScale } from '@utils/scaling'; +import { useTranslation } from 'react-i18next'; type Props = { ref: RefObject; @@ -20,6 +21,7 @@ export const BottomSheetRemoveAddressFromWatchlists = forwardRef< >(({ item }, ref) => { const localRef: ForwardedRef = useForwardedRef(ref); const { removeFromWatchlist } = useWatchlist(); + const { t } = useTranslation(); const handleRemoveAddressFromWatchlist = useCallback(() => { removeFromWatchlist(item); @@ -42,7 +44,7 @@ export const BottomSheetRemoveAddressFromWatchlists = forwardRef< fontSize={14} color={COLORS.smokyBlack} > - Remove this address from watchlist? + {t('confirm.remove.address.from.watchlist')} @@ -70,7 +72,7 @@ export const BottomSheetRemoveAddressFromWatchlists = forwardRef< color={COLORS.smokyBlack} fontSize={16} > - Cancel + {t('cancel.btn')} diff --git a/src/components/templates/BottomSheetCreateCollectionOrAddAddress/index.tsx b/src/components/templates/BottomSheetCreateCollectionOrAddAddress/index.tsx index 938eaecc2..44efde22e 100644 --- a/src/components/templates/BottomSheetCreateCollectionOrAddAddress/index.tsx +++ b/src/components/templates/BottomSheetCreateCollectionOrAddAddress/index.tsx @@ -6,6 +6,7 @@ import { BottomSheetFloat, PrimaryButton } from '@components/modular'; import { useForwardedRef } from '@hooks'; import { scale, verticalScale } from '@utils/scaling'; import { COLORS } from '@constants/colors'; +import { useTranslation } from 'react-i18next'; type Props = { ref: RefObject; @@ -18,6 +19,7 @@ export const BottomSheetCreateCollectionOrAddAddress = forwardRef< Props >(({ handleCreateCollectionPress, handleOnAddNewAddress }, ref) => { const localRef: ForwardedRef = useForwardedRef(ref); + const { t } = useTranslation(); return ( - Add address + {t('add.address.btn')} @@ -60,7 +62,7 @@ export const BottomSheetCreateCollectionOrAddAddress = forwardRef< fontSize={16} color={COLORS.smokyBlack} > - Create group + {t('create.group')} diff --git a/src/components/templates/BottomSheetCreateRenameGroup/index.tsx b/src/components/templates/BottomSheetCreateRenameGroup/index.tsx index fc53227a1..c327c492e 100644 --- a/src/components/templates/BottomSheetCreateRenameGroup/index.tsx +++ b/src/components/templates/BottomSheetCreateRenameGroup/index.tsx @@ -23,6 +23,7 @@ import { OnboardingView } from '../OnboardingView'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { verticalScale } from '@utils/scaling'; import { StringUtils } from '@utils/string'; +import { useTranslation } from 'react-i18next'; type Props = { ref: RefObject; @@ -43,6 +44,7 @@ export const BottomSheetCreateRenameGroup = forwardRef( } = props; const localRef: ForwardedRef = useForwardedRef(ref); + const { t } = useTranslation(); const nameInput = useRef(null); const [localGroupName, setLocalGroupName] = useState(''); @@ -67,11 +69,11 @@ export const BottomSheetCreateRenameGroup = forwardRef( handleOnCreateGroup(localGroupName); Toast.show({ title: '', - message: `Way to go! ${StringUtils.formatAddress( + message: `${t('toast.way.to.go')} ${StringUtils.formatAddress( localGroupName, 16, 0 - )} group created.`, + )} ${t('toast.group.created')}`, type: ToastPosition.Top }); } @@ -80,15 +82,9 @@ export const BottomSheetCreateRenameGroup = forwardRef( handleOnRenameGroup(groupId, localGroupName); Toast.show({ title: '', - message: `${StringUtils.formatAddress( - groupTitle || '', - 16, - 0 - )} has been renamed to ${StringUtils.formatAddress( - localGroupName, - 16, - 0 - )}.`, + message: `${StringUtils.formatAddress(groupTitle || '', 16, 0)} ${t( + 'toast.has.been.renamed' + )} ${StringUtils.formatAddress(localGroupName, 16, 0)}.`, type: ToastPosition.Top }); } @@ -101,7 +97,8 @@ export const BottomSheetCreateRenameGroup = forwardRef( handleOnCreateGroup, handleOnRenameGroup, localGroupName, - localRef + localRef, + t ]); const bottomSafeArea = useSafeAreaInsets().bottom - 10; @@ -136,7 +133,7 @@ export const BottomSheetCreateRenameGroup = forwardRef( fontSize={16} color={COLORS.smokyBlack} > - {type === 'create' ? ' Create group' : 'Rename group'} + {type === 'create' ? t('create.group') : t('rename.group')} ( type="text" placeholder={ emptyPlaceholder - ? 'This field is required' - : 'Enter group name' + ? t('field.required') + : t('group.name.input') } placeholderTextColor={ emptyPlaceholder ? COLORS.crimsonRed : COLORS.midnight @@ -178,7 +175,7 @@ export const BottomSheetCreateRenameGroup = forwardRef( fontSize={16} color={COLORS.white} > - {type === 'create' ? 'Create' : 'Save'} + {type === 'create' ? t('create.btn') : t('save.btn')} @@ -198,7 +195,7 @@ export const BottomSheetCreateRenameGroup = forwardRef( color={COLORS.nero} fontSize={16} > - Cancel + {t('cancel.btn')} diff --git a/src/components/templates/BottomSheetEditCollection/index.tsx b/src/components/templates/BottomSheetEditCollection/index.tsx index 000c93863..8e6630316 100644 --- a/src/components/templates/BottomSheetEditCollection/index.tsx +++ b/src/components/templates/BottomSheetEditCollection/index.tsx @@ -10,6 +10,7 @@ import { AccountList } from '@models'; import { BottomSheetCreateRenameGroup } from '../BottomSheetCreateRenameGroup'; import { styles } from './styles'; import { StringUtils } from '@utils/string'; +import { useTranslation } from 'react-i18next'; interface BottomSheetEditCollectionProps extends BottomSheetProps { collection: AccountList; @@ -25,6 +26,7 @@ export const BottomSheetEditCollection = forwardRef< const localRef: ForwardedRef = useForwardedRef(ref); const { handleOnRename, handleOnDelete } = useLists((v) => v); const renameCollectionModalRef = useRef(null); + const { t } = useTranslation(); const dismissThis = useCallback(() => { setTimeout(() => { @@ -74,7 +76,7 @@ export const BottomSheetEditCollection = forwardRef< fontFamily="Inter_600SemiBold" color={COLORS.smokyBlack} > - Rename group + {t('rename.group')} diff --git a/src/components/templates/BottomSheetEditWallet/index.tsx b/src/components/templates/BottomSheetEditWallet/index.tsx index 671310207..de5982246 100644 --- a/src/components/templates/BottomSheetEditWallet/index.tsx +++ b/src/components/templates/BottomSheetEditWallet/index.tsx @@ -10,6 +10,7 @@ import { BottomSheetRenameAddress } from '@screens/SingleCollection/modals/Botto import { COLORS } from '@constants/colors'; import { BottomSheetAddWalletToList } from '../BottomSheetAddWalletToList'; import { styles } from './styles'; +import { useTranslation } from 'react-i18next'; interface BottomSheetEditWalletProps extends BottomSheetProps { wallet: ExplorerAccount; @@ -25,6 +26,7 @@ export const BottomSheetEditWallet = forwardRef< const { listsOfAddressGroup, toggleAddressesInList } = useLists((v) => v); const renameWalletModalRef = useRef(null); const addToCollectionModalRef = useRef(null); + const { t } = useTranslation(); const listsWithCurrentWallet = listsOfAddressGroup.filter((list) => list.accounts.some((acc) => acc?.address === wallet?.address) @@ -79,11 +81,11 @@ export const BottomSheetEditWallet = forwardRef< dismissThis(); Toast.show({ title: '', - message: 'Successfully removed wallet from group!', + message: t('toast.Successfully.removed.wallet.from.group'), type: ToastPosition.Top }); } - }, [dismissThis, listsWithCurrentWallet, toggleAddressesInList, wallet]); + }, [dismissThis, listsWithCurrentWallet, t, toggleAddressesInList, wallet]); return ( - Rename address + {t('rename.address')} {listsWithCurrentWallet.length > 0 ? ( @@ -114,7 +116,7 @@ export const BottomSheetEditWallet = forwardRef< fontFamily="Inter_600SemiBold" color={COLORS.smokyBlack} > - Move to another group + {t('move.to.another.group')} listsWithCurrentWallet.indexOfItem(list, 'id') === -1 @@ -149,12 +153,12 @@ export const BottomSheetEditWallet = forwardRef< fontFamily="Inter_600SemiBold" color={COLORS.smokyBlack} > - Add to group + {t('add.address.to.group.lower.case')} ( (notificationSettings); const { top: topInset } = useSafeAreaInsets(); + const { t } = useTranslation(); + useEffect( () => setLocalNotificationSettings(notificationSettings), [notificationSettings] @@ -91,7 +94,7 @@ export const BottomSheetNotificationSettings = forwardRef< fontSize={16} color={COLORS.smokyBlack} > - Notification settings + {t('notification.settings')} } titlePosition={Platform.select({ ios: 'left', default: 'center' })} @@ -108,7 +111,7 @@ export const BottomSheetNotificationSettings = forwardRef< {/* Price alerts */} - Price alerts + {t('price.alerts.switch')} onSettingsValueChange( @@ -122,14 +125,14 @@ export const BottomSheetNotificationSettings = forwardRef< {/* Percentage Change */} - Price alerts threshold + {t('price.alerts.treshold')} - Set 24hr price change amount to receive notifications. + {t('price.alerts.treshold.text')} - Transaction alerts + {t('transaction.alerts.switch')} - Get notified of transactions in your watchlist. + {t('transaction.alerts.switch.text')} ; @@ -21,6 +22,7 @@ export const BottomSheetRemoveAddressFromCollection = forwardRef< >(({ wallet }, ref) => { const localRef: ForwardedRef = useForwardedRef(ref); const { toggleAddressesInList, listsOfAddressGroup } = useLists((v) => v); + const { t } = useTranslation(); const collection = listsOfAddressGroup.find( (list) => list.accounts.findIndex((account) => account._id === wallet._id) > -1 @@ -40,7 +42,7 @@ export const BottomSheetRemoveAddressFromCollection = forwardRef< color={COLORS.smokyBlack} numberOfLines={1} > - Remove this address from {collection?.name}? + {t('remove.address.from.group.select')} {collection?.name}? @@ -72,7 +74,7 @@ export const BottomSheetRemoveAddressFromCollection = forwardRef< color={COLORS.smokyBlack} fontSize={16} > - Cancel + {t('cancel.btn')} diff --git a/src/components/templates/ExplorerAccount/ExplorerAccount.TransactionItem.tsx b/src/components/templates/ExplorerAccount/ExplorerAccount.TransactionItem.tsx index 60243fcb2..6b9667388 100644 --- a/src/components/templates/ExplorerAccount/ExplorerAccount.TransactionItem.tsx +++ b/src/components/templates/ExplorerAccount/ExplorerAccount.TransactionItem.tsx @@ -8,6 +8,7 @@ import { BottomSheetFloat, TransactionItem } from '@components/modular'; import { Transaction } from '@models/Transaction'; import { scale, verticalScale } from '@utils/scaling'; import { CommonStackNavigationProp } from '@appTypes/navigation/common'; +import { useTranslation } from 'react-i18next'; interface ExplorerAccountTransactionItemProps { transaction: Transaction; @@ -20,6 +21,7 @@ export const ExplorerAccountTransactionItem = ( const { transaction, disabled = false } = props; const transactionDetailsModal = useRef(null); const navigation = useNavigation(); + const { t } = useTranslation(); const showTransactionDetails = () => { transactionDetailsModal.current?.show(); @@ -42,7 +44,7 @@ export const ExplorerAccountTransactionItem = ( - Transaction details + {t('transaction.details')} diff --git a/src/components/templates/ExplorerAccount/ExplorerAccount.Transactions.tsx b/src/components/templates/ExplorerAccount/ExplorerAccount.Transactions.tsx index 0db3ff85b..3067a4202 100644 --- a/src/components/templates/ExplorerAccount/ExplorerAccount.Transactions.tsx +++ b/src/components/templates/ExplorerAccount/ExplorerAccount.Transactions.tsx @@ -11,6 +11,7 @@ import { } from '@components/base'; import { ExplorerAccountTransactionItem } from './ExplorerAccount.TransactionItem'; import { COLORS } from '@constants/colors'; +import { useTranslation } from 'react-i18next'; interface ExplorerAccountViewTransactionsProps { transactions: Transaction[]; @@ -24,6 +25,7 @@ export const AccountTransactions = ( ): JSX.Element => { const { transactions, loading, showTransactionDetailsOnPress, onEndReached } = props; + const { t } = useTranslation(); const renderTransaction = ( args: ListRenderItemInfo @@ -46,7 +48,7 @@ export const AccountTransactions = ( fontSize={20} color={COLORS.jetBlack} > - Recent activity + {t('recent.activity')} diff --git a/src/components/templates/ExplorerAccount/index.tsx b/src/components/templates/ExplorerAccount/index.tsx index 613126c5b..c97c877a3 100644 --- a/src/components/templates/ExplorerAccount/index.tsx +++ b/src/components/templates/ExplorerAccount/index.tsx @@ -8,13 +8,14 @@ import { useAMBPrice } from '@hooks/query'; import { NumberUtils } from '@utils/number'; import { styles } from './styles'; import { useLists } from '@contexts/ListsContext'; -import { PlusIcon } from '@components/svg/icons'; +// import { PlusIcon } from '@components/svg/icons'; import { BottomSheetRef, CopyToClipboardButton } from '@components/composite'; import { COLORS } from '@constants/colors'; import { AddWalletToList } from '../AddWalletToList'; import { BottomSheetWithHeader } from '@components/modular'; import { useFullscreenModalHeight } from '@hooks/useFullscreenModalHeight'; import { useWatchlist } from '@hooks'; +import { useTranslation } from 'react-i18next'; interface ExplorerAccountProps { account: ExplorerAccount; @@ -30,6 +31,7 @@ export const ExplorerAccountView = ( const { listsOfAddressGroup } = useLists((v) => v); const fullscreenHeight = useFullscreenModalHeight(); const { addToWatchlist, removeFromWatchlist } = useWatchlist(); + const { t } = useTranslation(); const { data } = useAMBPrice(); const ambPriceUSD = data?.priceUSD || 0; @@ -76,7 +78,8 @@ export const ExplorerAccountView = ( fontSize={13} fontWeight="400" > - Added to {listsWithAccount[0].name} + {t('address.added.to.group')} + {listsWithAccount[0].name} ) : ( - Added to {listsWithAccount.length} lists + {t('address.added.to.group')} + {listsWithAccount.length} + {t('groups.count')} ))} @@ -152,14 +157,16 @@ export const ExplorerAccountView = ( fontSize={12} color={account.isOnWatchlist ? COLORS.liver : COLORS.white} > - {account.isOnWatchlist ? 'WATCHLISTED' : 'ADD TO WATCHLIST'} + {account.isOnWatchlist + ? t('watchlisted.address') + : t('watchlist.address.btn')} - {!account.isOnWatchlist && ( - <> - - - - )} + {/*{!account.isOnWatchlist && (*/} + {/* <>*/} + {/* */} + {/* */} + {/* */} + {/*)}*/} @@ -185,17 +192,19 @@ export const ExplorerAccountView = ( fontSize={12} > {listsWithAccount.length === 0 - ? 'ADD TO GROUP' + ? t('add.address.to.group.upper.case') : listsWithAccount.length === 1 ? StringUtils.formatAddress(listsWithAccount[0].name, 16, 0) - : `Added to ${listsWithAccount.length} groups`} + : `${t('address.added.to.group')} ${ + listsWithAccount.length + } ${t('groups.count')}`} - {listsWithAccount.length === 0 && ( - <> - - - - )} + {/*{listsWithAccount.length === 0 && (*/} + {/* <>*/} + {/* */} + {/* */} + {/* */} + {/*)}*/} @@ -203,7 +212,7 @@ export const ExplorerAccountView = ( diff --git a/src/components/templates/RenderEmpty/index.tsx b/src/components/templates/LocalizedRenderEmpty/index.tsx similarity index 64% rename from src/components/templates/RenderEmpty/index.tsx rename to src/components/templates/LocalizedRenderEmpty/index.tsx index 93cf7f924..9a2a387f1 100644 --- a/src/components/templates/RenderEmpty/index.tsx +++ b/src/components/templates/LocalizedRenderEmpty/index.tsx @@ -1,21 +1,23 @@ +import React from 'react'; import { View } from 'react-native'; import { EmptyWalletListPlaceholderIcon } from '@components/svg/icons'; import { Spacer, Text } from '@components/base'; import { verticalScale } from '@utils/scaling'; -import React from 'react'; -import { styles } from '@components/templates/RenderEmpty/styles'; +import { styles } from '@components/templates/LocalizedRenderEmpty/styles'; +import { useTranslation } from 'react-i18next'; -type RenderEmptyProps = { +type LocalizedRenderEmptyProps = { text: string; }; -export const RenderEmpty = ({ text }: RenderEmptyProps) => { +export const LocalizedRenderEmpty = ({ text }: LocalizedRenderEmptyProps) => { + const { t } = useTranslation(); return ( - No {text} yet + {t(text)} ); diff --git a/src/components/templates/RenderEmpty/styles.ts b/src/components/templates/LocalizedRenderEmpty/styles.ts similarity index 100% rename from src/components/templates/RenderEmpty/styles.ts rename to src/components/templates/LocalizedRenderEmpty/styles.ts diff --git a/src/components/templates/SearchAddress/SearchAddress.NoMatch.tsx b/src/components/templates/SearchAddress/SearchAddress.NoMatch.tsx index c3a889f97..f142335a8 100644 --- a/src/components/templates/SearchAddress/SearchAddress.NoMatch.tsx +++ b/src/components/templates/SearchAddress/SearchAddress.NoMatch.tsx @@ -4,8 +4,10 @@ import { Spacer, Text } from '@components/base'; import { COLORS } from '@constants/colors'; import { moderateScale, verticalScale } from '@utils/scaling'; import { NoMatch } from '@components/svg/icons/NoMatch'; +import { useTranslation } from 'react-i18next'; export function SearchAddressNoResult(): JSX.Element { + const { t } = useTranslation(); return ( @@ -16,7 +18,7 @@ export function SearchAddressNoResult(): JSX.Element { fontWeight="600" fontFamily="Inter_600SemiBold" > - Oops! No matches found + {t('no.matches')} - Please check for any typos or try a different address + {t('check.typos')} ); diff --git a/src/components/templates/SearchAddress/index.tsx b/src/components/templates/SearchAddress/index.tsx index 527c31afb..e405135e6 100644 --- a/src/components/templates/SearchAddress/index.tsx +++ b/src/components/templates/SearchAddress/index.tsx @@ -35,6 +35,7 @@ import { CRYPTO_ADDRESS_MAX_LENGTH } from '@constants/variables'; import { COLORS } from '@constants/colors'; import { SearchTabNavigationProp } from '@appTypes'; import { styles } from './styles'; +import { useTranslation } from 'react-i18next'; interface SearchAdressProps { initialValue?: string; @@ -47,6 +48,7 @@ export const SearchAddress = (props: SearchAdressProps): JSX.Element => { const { initialValue, onContentVisibilityChanged = () => null } = props; const navigation = useNavigation(); const { height: WINDOW_HEIGHT } = useWindowDimensions(); + const { t } = useTranslation(); const { data: explorerInfo } = useExplorerInfo(); const [address, setAddress] = useState(''); const [searchSubmitted, setSearchSubmitted] = useState(false); @@ -108,8 +110,8 @@ export const SearchAddress = (props: SearchAdressProps): JSX.Element => { const toggleWatchlist = async (isOnWatchlist: boolean) => { if (isOnWatchlist) { Toast.show({ - title: 'Way to go! Address watchlisted.', - message: 'Tap to rename Address', + title: t('toast.address.watchlisted.msg'), + message: t('toast.tap.to.rename.msg'), type: ToastPosition.Top, onBodyPress: editModal.current?.show }); @@ -158,9 +160,9 @@ export const SearchAddress = (props: SearchAdressProps): JSX.Element => { }, 500); } else if (!scanned.current) { scanned.current = true; - Alert.alert('Invalid QR code', '', [ + Alert.alert(t('invalid.qr.code.msg'), '', [ { - text: 'Scan again', + text: t('scan.again.msg'), onPress: () => { scanned.current = false; } @@ -216,7 +218,7 @@ export const SearchAddress = (props: SearchAdressProps): JSX.Element => { )} } - placeholder={'Search Address or TX hash'} + placeholder={t('search.address.input')} returnKeyType="search" onFocus={onInputFocused} onBlur={onInputBlur} @@ -264,7 +266,7 @@ export const SearchAddress = (props: SearchAdressProps): JSX.Element => { fontSize={20} color={COLORS.neutral800} > - Transaction details + {t('transaction.details')} (null); const { data: ambData } = useAMBPrice(); + const { t } = useTranslation(); const ambPrice = ambData ? ambData.priceUSD : -1; let totalTransactionAmount; @@ -67,7 +69,7 @@ export const TransactionDetails = ( - Method + {t('method')} {transaction.type} @@ -76,7 +78,7 @@ export const TransactionDetails = ( - Amount + {t('amount')} {NumberUtils.formatNumber(transaction.amount, 0)} AMB @@ -88,7 +90,7 @@ export const TransactionDetails = ( - From + {t('from')} diff --git a/src/screens/Portfolio/index.tsx b/src/screens/Portfolio/index.tsx index e14dc7171..bcf5b681d 100644 --- a/src/screens/Portfolio/index.tsx +++ b/src/screens/Portfolio/index.tsx @@ -7,33 +7,7 @@ import { WatchList } from '@screens/Portfolio/components/PortfolioScreenTabs/com import { useIsFocused } from '@react-navigation/native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { View } from 'react-native'; - -const portfolioTabRoutes = [ - { key: 'first', title: 'Addresses' }, - { key: 'second', title: 'Groups' } -] as const; - -type PortfolioTabRoutes = typeof portfolioTabRoutes; - -type PortfolioTabViewRoute = { - key: PortfolioTabRoutes[number]['key']; - title: PortfolioTabRoutes[number]['title']; -}; - -type RenderSceneProps = Parameters< - TabViewProps['renderScene'] ->[0]; - -const renderScene = ({ route }: RenderSceneProps) => { - switch (route.key) { - case 'first': - return ; - case 'second': - return ; - default: - return null; - } -}; +import { useTranslation } from 'react-i18next'; type PortfolioScreenProps = { route: { @@ -44,6 +18,34 @@ type PortfolioScreenProps = { }; export const PortfolioScreen = ({ route }: PortfolioScreenProps) => { + const { t } = useTranslation(); + const portfolioTabRoutes = [ + { key: 'first', title: t('addresses') }, + { key: 'second', title: t('groups.capitalize') } + ] as const; + + type PortfolioTabRoutes = typeof portfolioTabRoutes; + + type PortfolioTabViewRoute = { + key: PortfolioTabRoutes[number]['key']; + title: PortfolioTabRoutes[number]['title']; + }; + + type RenderSceneProps = Parameters< + TabViewProps['renderScene'] + >[0]; + + // tslint:disable-next-line:no-shadowed-variable + const renderScene = ({ route }: RenderSceneProps) => { + switch (route.key) { + case 'first': + return ; + case 'second': + return ; + default: + return null; + } + }; const { top } = useSafeAreaInsets(); const activeTab = route?.params?.tabs?.activeTab; const [index, setIndex] = useState(0); diff --git a/src/screens/Search/components/WalletItem.tsx b/src/screens/Search/components/WalletItem.tsx index e70b2b224..64c862fcf 100644 --- a/src/screens/Search/components/WalletItem.tsx +++ b/src/screens/Search/components/WalletItem.tsx @@ -9,6 +9,7 @@ import { COLORS } from '@constants/colors'; import { useLists } from '@contexts'; import { useWatchlist } from '@hooks'; import { AddressIndicator } from '@components/templates'; +import { useTranslation } from 'react-i18next'; interface ExplorerWalletItemProps { item: ExplorerAccount; @@ -23,6 +24,7 @@ export const ExplorerWalletItem = ( const { address, transactionCount, ambBalance } = item; const { listsOfAddressGroup } = useLists((v) => v); const { watchlist } = useWatchlist(); + const { t } = useTranslation(); const listWithAddress = listsOfAddressGroup.filter( (list) => list.accounts.indexOfItem(item, 'address') > -1 ); @@ -72,18 +74,19 @@ export const ExplorerWalletItem = ( color={COLORS.smokyBlack50} fontFamily="Inter_500Medium" > - Holding{' '} + {t('single.address.holding')} {NumberUtils.formatNumber( item.calculatePercentHoldings(totalSupply), 2 - )} - % of supply + )}{' '} + {t('single.address.supply')} + {/* TODO add localisation here, key is "transactions" */} {StringUtils.pluralize(transactionCount, 'Transaction')} diff --git a/src/screens/Search/index.tsx b/src/screens/Search/index.tsx index 69d92395c..2552e4515 100644 --- a/src/screens/Search/index.tsx +++ b/src/screens/Search/index.tsx @@ -22,10 +22,12 @@ import { SearchSort } from './Search.types'; import { COLORS } from '@constants/colors'; import { styles } from './styles'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; +import { useTranslation } from 'react-i18next'; export const SearchScreen = () => { const navigation = useNavigation(); const { data: infoData } = useExplorerInfo(); + const { t } = useTranslation(); const { data: accounts, loading: accountsLoading, @@ -96,12 +98,12 @@ export const SearchScreen = () => { fontSize={20} color={COLORS.smokyBlack} > - Top holders + {t('top.holders')} {accountsError ? ( - Could not load accounts info + {t('no.account.info')} ) : ( infoData && accounts && ( diff --git a/src/screens/Settings/components/SettingsBlock/index.tsx b/src/screens/Settings/components/SettingsBlock/index.tsx index bec0d0285..40fd919f2 100644 --- a/src/screens/Settings/components/SettingsBlock/index.tsx +++ b/src/screens/Settings/components/SettingsBlock/index.tsx @@ -17,7 +17,10 @@ import { import { BottomSheetNotificationSettings } from '@components/templates'; import { styles } from '@screens/Settings/components/SettingsBlock/style'; import { COLORS } from '@constants/colors'; +import { useTranslation } from 'react-i18next'; + export const SettingsBlock = () => { + const { t } = useTranslation(); const [selectedLanguage, setSelectedLanguage] = useState('English'); const [selectedCurrency, setSelectedCurrency] = useState('US Dollars (USD)'); @@ -58,7 +61,9 @@ export const SettingsBlock = () => { > - Base currency + + {t('base.currency.modal')} + {selectedCurrency} @@ -77,7 +82,7 @@ export const SettingsBlock = () => { > - Language + {t('language.modal')} {selectedLanguage} @@ -96,7 +101,9 @@ export const SettingsBlock = () => { > - Notification settings + + {t('notification.settings.modal')} + diff --git a/src/screens/Settings/components/SettingsBlock/modals/BottomSheetBaseCurrency/index.tsx b/src/screens/Settings/components/SettingsBlock/modals/BottomSheetBaseCurrency/index.tsx index 80ea9d917..9f7ca5cd8 100644 --- a/src/screens/Settings/components/SettingsBlock/modals/BottomSheetBaseCurrency/index.tsx +++ b/src/screens/Settings/components/SettingsBlock/modals/BottomSheetBaseCurrency/index.tsx @@ -8,6 +8,7 @@ import { styles } from '@screens/Settings/components/SettingsBlock/modals/style' import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { COLORS } from '@constants/colors'; import { scale } from '@utils/scaling'; +import { useTranslation } from 'react-i18next'; type Props = { ref: RefObject; @@ -70,6 +71,7 @@ export const BottomSheetSelectBaseCurrency = forwardRef( selectedCurrency || '' ); const { top: topInset } = useSafeAreaInsets(); + const { t } = useTranslation(); const onCurrencyPress = (value: Currency) => { setModalActiveCurrency(value); @@ -90,7 +92,7 @@ export const BottomSheetSelectBaseCurrency = forwardRef( fontSize={scale(16)} color={COLORS.smokyBlack} > - Select base currency + {t('select.base.currency')} } titlePosition={Platform.select({ ios: 'left', default: 'center' })} diff --git a/src/screens/Settings/components/SettingsBlock/modals/BottomSheetSelectLanguage/index.tsx b/src/screens/Settings/components/SettingsBlock/modals/BottomSheetSelectLanguage/index.tsx index e13413c4d..d3e2b798f 100644 --- a/src/screens/Settings/components/SettingsBlock/modals/BottomSheetSelectLanguage/index.tsx +++ b/src/screens/Settings/components/SettingsBlock/modals/BottomSheetSelectLanguage/index.tsx @@ -8,6 +8,8 @@ import { styles } from '@screens/Settings/components/SettingsBlock/modals/style' import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { COLORS } from '@constants/colors'; import { scale } from '@utils/scaling'; +import { useTranslation } from 'react-i18next'; +import useLocalization from '@contexts/Localizations'; type Props = { ref: RefObject; @@ -32,16 +34,16 @@ type LanguageData = { const mockedLanguages: LanguageData[] = [ { language: 'English' - } + }, // { // language: 'Arabic' // }, // { // language: 'Spanish' // }, - // { - // language: 'Turkish' - // }, + { + language: 'Turkish' + } // { // language: 'Hindi' // }, @@ -59,14 +61,19 @@ const mockedLanguages: LanguageData[] = [ export const BottomSheetSelectLanguage = forwardRef( ({ selectedLanguage, handleLanguageSave }, ref) => { const localRef: ForwardedRef = useForwardedRef(ref); + // eslint-disable-next-line @typescript-eslint/no-unused-vars const [modalActiveLanguage, setModalActiveLanguage] = useState( selectedLanguage || '' ); const { top: topInset } = useSafeAreaInsets(); + const { changeCurrentLanguage, currentLanguage } = useLocalization(); + + const { t } = useTranslation(); const onLanguagePress = (value: Language) => { setModalActiveLanguage(value); handleLanguageSave(value); + changeCurrentLanguage(value); }; return ( @@ -84,7 +91,7 @@ export const BottomSheetSelectLanguage = forwardRef( fontSize={scale(16)} color={COLORS.smokyBlack} > - Select language + {t('select.language')} } titlePosition={Platform.select({ ios: 'left', default: 'center' })} @@ -101,7 +108,7 @@ export const BottomSheetSelectLanguage = forwardRef( renderItem={({ item, index }) => { return ( { + const { t } = useTranslation(); const openLink = () => { Linking.openURL('https://airdao.academy/faqs'); }; @@ -17,7 +19,7 @@ export const SettingsInfoBlock = () => { @@ -29,8 +31,8 @@ export const SettingsInfoBlock = () => { })} {Platform.select({ - ios: 'Rate us on the App Store', - android: 'Rate us on the Play Store' + ios: t('rate.btn.ios'), + android: t('rate.btn.android') })} diff --git a/src/screens/SingleCollection/index.tsx b/src/screens/SingleCollection/index.tsx index be8a7650d..bea27cf80 100644 --- a/src/screens/SingleCollection/index.tsx +++ b/src/screens/SingleCollection/index.tsx @@ -20,6 +20,7 @@ import { useAllAddressesContext } from '@contexts'; import { BottomSheetAddNewAddressToGroup } from './modals/BottomSheetAddNewAddressToGroup'; import { sortListByKey } from '@utils/sort'; import { styles } from './styles'; +import { useTranslation } from 'react-i18next'; export const SingleGroupScreen = () => { const { @@ -29,6 +30,7 @@ export const SingleGroupScreen = () => { } = useRoute>(); const navigation = useNavigation(); const { data: ambPriceData } = useAMBPrice(); + const { t } = useTranslation(); const addNewAddressToGroupRef = useRef(null); const groupRenameRef = useRef(null); @@ -106,7 +108,7 @@ export const SingleGroupScreen = () => { fontFamily="Inter_600SemiBold" fontSize={12} > - TOTAL BALANCE + {t('total.balance')} diff --git a/src/screens/SingleCollection/modals/BottomSheetAddNewAddressToGroup/index.tsx b/src/screens/SingleCollection/modals/BottomSheetAddNewAddressToGroup/index.tsx index 9b6f5424f..d49cff210 100644 --- a/src/screens/SingleCollection/modals/BottomSheetAddNewAddressToGroup/index.tsx +++ b/src/screens/SingleCollection/modals/BottomSheetAddNewAddressToGroup/index.tsx @@ -38,25 +38,13 @@ import { StringUtils } from '@utils/string'; import { SearchSort } from '@screens/Search/Search.types'; import { etherumAddressRegex } from '@constants/regex'; import { styles } from './styles'; +import { useTranslation } from 'react-i18next'; type Props = { ref: RefObject; collection: AccountList; }; -const AddressSources: Segment[] = [ - { - title: 'Watchlist', - value: 0, - id: 'watchlist' - }, - { - title: 'Top Holders', - value: 1, - id: 'topHolders' - } -]; - export const BottomSheetAddNewAddressToGroup = forwardRef< BottomSheetRef, Props @@ -69,6 +57,21 @@ export const BottomSheetAddNewAddressToGroup = forwardRef< fetchNextPage: fetchMoreTopHolders } = useExplorerAccounts(SearchSort.Balance); const [searchValue, setSearchValue] = useState(''); + const { t } = useTranslation(); + + const AddressSources: Segment[] = [ + { + title: t('watchlist.tab'), + value: 0, + id: 'watchlist' + }, + { + title: t('top.holders.capitalize'), + value: 1, + id: 'topHolders' + } + ]; + const { data: searchedAccount, loading: searchLoading, @@ -186,9 +189,9 @@ export const BottomSheetAddNewAddressToGroup = forwardRef< }, 500); } else if (!scanned.current) { scanned.current = true; - Alert.alert('Invalid QR code', '', [ + Alert.alert(t('invalid.qr.code.msg'), '', [ { - text: 'Scan again', + text: t('scan.again.msg'), onPress: () => { scanned.current = false; } @@ -228,7 +231,7 @@ export const BottomSheetAddNewAddressToGroup = forwardRef< fontFamily="Inter_700Bold" fontSize={18} color={COLORS.nero} - >{`Add address to ${StringUtils.formatAddress( + >{`${t('add.address.to.selected.group')} ${StringUtils.formatAddress( collection.name, 10, 0 @@ -256,7 +259,7 @@ export const BottomSheetAddNewAddressToGroup = forwardRef< } type="text" style={{ width: '65%', height: 50 }} - placeholder="Search public address" + placeholder={t('search.public.address.input')} placeholderTextColor="#2f2b4399" value={searchValue} onChangeText={setSearchValue} @@ -371,15 +374,15 @@ export const BottomSheetAddNewAddressToGroup = forwardRef< fontWeight="600" > {selectingAddedItems - ? `Remove ${StringUtils.pluralize( + ? `${t('remove.btn')} ${StringUtils.pluralize( selectedAddresses.length, - 'Address', - 'Addresses' + t('address'), + t('addresses') )}` - : `Add ${StringUtils.pluralize( + : `${t('add')} ${StringUtils.pluralize( selectedAddresses.length, - 'Address', - 'Addresses' + t('address'), + t('addresses') )}`} diff --git a/src/screens/SingleCollection/modals/BottomSheetRenameAddress/index.tsx b/src/screens/SingleCollection/modals/BottomSheetRenameAddress/index.tsx index 0d990e6a5..94211a8a5 100644 --- a/src/screens/SingleCollection/modals/BottomSheetRenameAddress/index.tsx +++ b/src/screens/SingleCollection/modals/BottomSheetRenameAddress/index.tsx @@ -8,6 +8,7 @@ import { useForwardedRef } from '@hooks/useForwardedRef'; import { styles } from '@screens/SingleCollection/modals/BottomSheetRenameAddress/styles'; import { BottomSheetFloat, PrimaryButton } from '@components/modular'; import { scale, verticalScale } from '@utils/scaling'; +import { useTranslation } from 'react-i18next'; type Props = { ref: RefObject; @@ -21,6 +22,7 @@ export const BottomSheetRenameAddress = forwardRef( address || '' ); const localRef: ForwardedRef = useForwardedRef(ref); + const { t } = useTranslation(); return ( ( fontSize={16} color={COLORS.nero} > - Rename address + {t('rename.address')} setLocalAddressName(value)} type="text" - placeholder="Edit name" + placeholder={t('edit.name')} placeholderTextColor={COLORS.neutral900Alpha[60]} style={[styles.bottomSheetInput]} /> @@ -58,7 +60,7 @@ export const BottomSheetRenameAddress = forwardRef( fontSize={16} color={COLORS.white} > - Save + {t('save.btn')} @@ -72,7 +74,7 @@ export const BottomSheetRenameAddress = forwardRef( color={COLORS.smokyBlack} fontSize={16} > - Cancel + {t('cancel.btn')} diff --git a/src/screens/Wallets/components/Header.tsx b/src/screens/Wallets/components/Header.tsx index 02a5fe90f..937c2a7d8 100644 --- a/src/screens/Wallets/components/Header.tsx +++ b/src/screens/Wallets/components/Header.tsx @@ -19,6 +19,7 @@ import { useNotificationsQuery } from '@hooks'; import { Cache, CacheKey } from '@utils/cache'; import { useNewNotificationsCount } from '../hooks/useNewNotificationsCount'; import { COLORS } from '@constants/colors'; +import { useTranslation } from 'react-i18next'; export const HomeHeader = React.memo((): JSX.Element => { const navigation = useNavigation(); @@ -27,6 +28,7 @@ export const HomeHeader = React.memo((): JSX.Element => { const scanned = useRef(false); const { data: notifications } = useNotificationsQuery(); const newNotificationsCount = useNewNotificationsCount(); + const { t } = useTranslation(); const openScanner = () => { scanner.current?.show(); @@ -47,9 +49,9 @@ export const HomeHeader = React.memo((): JSX.Element => { }); } else if (!scanned.current) { scanned.current = true; - Alert.alert('Invalid QR code', '', [ + Alert.alert(t('invalid.qr.code.msg'), '', [ { - text: 'Scan again', + text: t('scan.again.msg'), onPress: () => { scanned.current = false; } @@ -57,7 +59,7 @@ export const HomeHeader = React.memo((): JSX.Element => { ]); } }, - [navigation] + [navigation, t] ); const setLastNotificationTime = useCallback(() => { diff --git a/src/screens/Wallets/components/HomeTabs/HomeCollections.tsx b/src/screens/Wallets/components/HomeTabs/HomeCollections.tsx index 70c3f79af..36816404c 100644 --- a/src/screens/Wallets/components/HomeTabs/HomeCollections.tsx +++ b/src/screens/Wallets/components/HomeTabs/HomeCollections.tsx @@ -5,7 +5,7 @@ import { Button, Spacer, Spinner } from '@components/base'; import { useNavigation } from '@react-navigation/native'; import { PortfolioNavigationProp } from '@appTypes'; import { styles } from '@screens/Wallets/components/HomeTabs/styles'; -import { RenderEmpty } from '@components/templates/RenderEmpty'; +import { LocalizedRenderEmpty } from '@components/templates'; import { verticalScale } from '@utils/scaling'; import { AccountList } from '@models'; import { CollectionItem } from '@components/modular'; @@ -31,7 +31,7 @@ export const HomeCollections = () => { } if (listsOfAddressGroup.length === 0) { - return ; + return ; } return ( diff --git a/src/screens/Wallets/components/HomeTabs/HomeTabs.tsx b/src/screens/Wallets/components/HomeTabs/HomeTabs.tsx index 94d48d444..42bbb2031 100644 --- a/src/screens/Wallets/components/HomeTabs/HomeTabs.tsx +++ b/src/screens/Wallets/components/HomeTabs/HomeTabs.tsx @@ -15,12 +15,14 @@ import { useNavigation } from '@react-navigation/native'; import { BottomSheetCreateRenameGroup } from '@components/templates/BottomSheetCreateRenameGroup'; import { useAllAddressesContext, useLists } from '@contexts'; import { useWatchlist } from '@hooks'; +import { useTranslation } from 'react-i18next'; export const HomeTabs = () => { const navigation = useNavigation(); const { handleOnCreate, listsOfAddressGroup } = useLists((v) => v); const { watchlist } = useWatchlist(); const { addressesLoading } = useAllAddressesContext((v) => v); + const { t } = useTranslation(); const scrollView = useRef(null); const createCollectionOrAddAddressRef = useRef(null); @@ -92,7 +94,7 @@ export const HomeTabs = () => { color={currentIndex === 0 ? COLORS.jetBlack : COLORS.lavenderGray} fontSize={currentIndex === 0 ? 20 : 18} > - Addresses + {t('addresses')} @@ -105,7 +107,7 @@ export const HomeTabs = () => { color={currentIndex === 1 ? COLORS.jetBlack : COLORS.lavenderGray} fontSize={currentIndex === 1 ? 20 : 18} > - Groups + {t('groups.capitalize')} @@ -157,7 +159,7 @@ export const HomeTabs = () => { fontSize={16} color={COLORS.deepBlue} > - See all + {t('see.all.btn')} diff --git a/src/screens/Wallets/components/HomeTabs/HomeWatchlists.tsx b/src/screens/Wallets/components/HomeTabs/HomeWatchlists.tsx index cbf2d4bef..08e0e054d 100644 --- a/src/screens/Wallets/components/HomeTabs/HomeWatchlists.tsx +++ b/src/screens/Wallets/components/HomeTabs/HomeWatchlists.tsx @@ -5,8 +5,7 @@ import { Button, Spacer, Spinner } from '@components/base'; import { useNavigation } from '@react-navigation/native'; import { PortfolioNavigationProp } from '@appTypes'; import { styles } from '@screens/Wallets/components/HomeTabs/styles'; -import { RenderEmpty } from '@components/templates/RenderEmpty'; -import { WalletItem } from '@components/templates'; +import { WalletItem, LocalizedRenderEmpty } from '@components/templates'; import { ExplorerAccount } from '@models'; import { verticalScale } from '@utils/scaling'; import { useAllAddressesContext } from '@contexts'; @@ -21,7 +20,7 @@ export const HomeWatchlists = () => { const navigation = useNavigation(); if (watchlist.length === 0) { - return ; + return ; } const navigateToAddressDetails = (item: ExplorerAccount) => { diff --git a/yarn.lock b/yarn.lock index 31be42160..20a906057 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1369,6 +1369,13 @@ dependencies: regenerator-runtime "^0.13.11" +"@babel/runtime@^7.22.5": + version "7.22.10" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.10.tgz#ae3e9631fd947cb7e3610d3e9d8fef5f76696682" + integrity sha512-21t/fkKLMZI4pqP2wlmsQAWnYW1PDyKyyUV4vCi+B25ydmdaYTKXPwCj0BzSUnZf4seIiYvSA3jcZ3gdsMFkLQ== + dependencies: + regenerator-runtime "^0.14.0" + "@babel/runtime@^7.9.2": version "7.21.5" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.21.5.tgz#8492dddda9644ae3bda3b45eabe87382caee7200" @@ -3488,6 +3495,11 @@ big-integer@1.6.x: resolved "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz" integrity sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg== +bignumber.js@*: + version "9.1.1" + resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.1.1.tgz#c4df7dc496bd849d4c9464344c1aa74228b4dac6" + integrity sha512-pHm4LsMJ6lzgNGVfZHjMoO8sdoRhOzOH4MLmY65Jg70bpxCKu5iOHNJyfF6OyvYw7t8Fpf35RuzUyqnQsj8Vig== + bl@^4.1.0: version "4.1.0" resolved "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz" @@ -6007,6 +6019,13 @@ html-escaper@^2.0.0: resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== +html-parse-stringify@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz#dfc1017347ce9f77c8141a507f233040c59c55d2" + integrity sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg== + dependencies: + void-elements "3.1.0" + http-errors@2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz" @@ -6050,6 +6069,21 @@ husky@^8.0.3: resolved "https://registry.npmjs.org/husky/-/husky-8.0.3.tgz" integrity sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg== +i18n-js@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/i18n-js/-/i18n-js-4.3.0.tgz#f1098e21a762faf8b8d09453abfdb6a28ee0f57b" + integrity sha512-PX93eT6WPV6Ym6mHtFKGDRZB0zwDX7HUPkgprjsZ28J6/Ohw1nvRYuM93or3pWv2VLxs6XfBf7X9Fc/YAZNEtQ== + dependencies: + bignumber.js "*" + make-plural "*" + +i18next@^23.4.4: + version "23.4.4" + resolved "https://registry.yarnpkg.com/i18next/-/i18next-23.4.4.tgz#ec8fb2b5f3c5d8e3bf3f8ab1b19e743be91300e0" + integrity sha512-+c9B0txp/x1m5zn+QlwHaCS9vyFtmIAEXbVSFzwCX7vupm5V7va8F9cJGNJZ46X9ZtoGzhIiRC7eTIIh93TxPA== + dependencies: + "@babel/runtime" "^7.22.5" + iconv-lite@0.4.24: version "0.4.24" resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz" @@ -7501,6 +7535,11 @@ make-dir@^3.0.0: dependencies: semver "^6.0.0" +make-plural@*: + version "7.3.0" + resolved "https://registry.yarnpkg.com/make-plural/-/make-plural-7.3.0.tgz#2889dbafca2fb097037c47967d3e3afa7e48a52c" + integrity sha512-/K3BC0KIsO+WK2i94LkMPv3wslMrazrQhfi5We9fMbLlLjzoOSJWr7TAdupLlDWaJcWxwoNosBkhFDejiu5VDw== + makeerror@1.0.12: version "1.0.12" resolved "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz" @@ -8931,6 +8970,14 @@ react-freeze@^1.0.0: resolved "https://registry.npmjs.org/react-freeze/-/react-freeze-1.0.3.tgz" integrity sha512-ZnXwLQnGzrDpHBHiC56TXFXvmolPeMjTn1UOm610M4EXGzbEDR7oOIyS2ZiItgbs6eZc4oU/a0hpk8PrcKvv5g== +react-i18next@^13.1.0: + version "13.1.0" + resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-13.1.0.tgz#18b1769c1c22c8f1d9a8f395bec48240e3349cfd" + integrity sha512-TlDz4761vJe810o5nEzRkblfS0FQz+k9R5BLOPVhn0Ds6WMDcwGSMbU5fne9WSd4/vLgKg7fEJ7/JboWdmsHOw== + dependencies: + "@babel/runtime" "^7.22.5" + html-parse-stringify "^3.0.1" + "react-is@^16.12.0 || ^17.0.0 || ^18.0.0", react-is@^18.0.0, react-is@^18.2.0: version "18.2.0" resolved "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz" @@ -9221,6 +9268,11 @@ regenerator-runtime@^0.13.11, regenerator-runtime@^0.13.2: resolved "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz" integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg== +regenerator-runtime@^0.14.0: + version "0.14.0" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz#5e19d68eb12d486f797e15a3c6a918f7cec5eb45" + integrity sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA== + regenerator-transform@^0.15.1: version "0.15.1" resolved "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.1.tgz" @@ -10776,6 +10828,11 @@ vlq@^1.0.0: resolved "https://registry.npmjs.org/vlq/-/vlq-1.0.1.tgz" integrity sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w== +void-elements@3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-3.1.0.tgz#614f7fbf8d801f0bb5f0661f5b2f5785750e4f09" + integrity sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w== + w3c-xmlserializer@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz#aebdc84920d806222936e3cdce408e32488a3073" From 1e2c3da444178c7352d546835c4d49a82147e4b2 Mon Sep 17 00:00:00 2001 From: ilyaAir <128598571+ilyaAir@users.noreply.github.com> Date: Thu, 17 Aug 2023 13:35:15 +0300 Subject: [PATCH 057/509] Revert "Revert "Revert "Dev""" --- Providers.tsx | 16 +-- package.json | 3 - .../composite/Button/CopyToClipboard.tsx | 7 +- .../modular/CollectionItem/index.tsx | 4 +- .../templates/AMBPriceHistory/index.tsx | 12 +- .../templates/AddWalletToList/index.tsx | 6 +- .../BottomSheetAddWalletToList/index.tsx | 24 ---- ...BottomSheetRemoveAddressFromWatchlists.tsx | 8 +- .../index.tsx | 6 +- .../BottomSheetCreateRenameGroup/index.tsx | 31 ++-- .../BottomSheetEditCollection/index.tsx | 7 +- .../templates/BottomSheetEditWallet/index.tsx | 20 ++- .../BottomSheetNotificationSettings/index.tsx | 15 +- .../index.tsx | 8 +- .../ExplorerAccount.TransactionItem.tsx | 4 +- .../ExplorerAccount.Transactions.tsx | 4 +- .../templates/ExplorerAccount/index.tsx | 47 +++--- .../index.tsx | 12 +- .../styles.ts | 0 .../SearchAddress/SearchAddress.NoMatch.tsx | 6 +- .../templates/SearchAddress/index.tsx | 14 +- .../templates/TransactionDetails/index.tsx | 12 +- src/components/templates/WalletList/index.tsx | 4 +- src/components/templates/index.ts | 1 - src/contexts/Localizations/index.tsx | 64 --------- src/contexts/index.ts | 1 - src/lib/permission.ts | 4 +- src/localization/i18n.ts | 20 --- src/localization/locales/English.json | 134 ------------------ src/localization/locales/Turkish.json | 133 ----------------- src/localization/locales/translations.ts | 17 --- src/navigation/stacks/TabsNavigator.tsx | 14 +- src/screens/AMBMarket/AMBMarket.constants.ts | 21 ++- src/screens/AMBMarket/components/About.tsx | 7 +- .../AMBMarket/components/DetailedInfo.tsx | 8 +- src/screens/AMBMarket/index.tsx | 12 +- src/screens/Address/index.tsx | 15 +- .../Notifications/components/Header.tsx | 4 +- src/screens/Notifications/index.tsx | 14 +- .../components/ListsOfAddressGroup/index.tsx | 4 +- .../components/PortfolioScreenTabs/index.tsx | 6 +- src/screens/Portfolio/index.tsx | 56 ++++---- src/screens/Search/components/WalletItem.tsx | 9 +- src/screens/Search/index.tsx | 6 +- .../components/SettingsBlock/index.tsx | 13 +- .../modals/BottomSheetBaseCurrency/index.tsx | 4 +- .../BottomSheetSelectLanguage/index.tsx | 19 +-- .../components/SettingsInfoBlock/index.tsx | 8 +- src/screens/SingleCollection/index.tsx | 4 +- .../BottomSheetAddNewAddressToGroup/index.tsx | 49 +++---- .../modals/BottomSheetRenameAddress/index.tsx | 10 +- src/screens/Wallets/components/Header.tsx | 8 +- .../components/HomeTabs/HomeCollections.tsx | 4 +- .../Wallets/components/HomeTabs/HomeTabs.tsx | 8 +- .../components/HomeTabs/HomeWatchlists.tsx | 5 +- yarn.lock | 57 -------- 56 files changed, 219 insertions(+), 790 deletions(-) rename src/components/templates/{LocalizedRenderEmpty => RenderEmpty}/index.tsx (64%) rename src/components/templates/{LocalizedRenderEmpty => RenderEmpty}/styles.ts (100%) delete mode 100644 src/contexts/Localizations/index.tsx delete mode 100644 src/localization/i18n.ts delete mode 100644 src/localization/locales/English.json delete mode 100644 src/localization/locales/Turkish.json delete mode 100644 src/localization/locales/translations.ts diff --git a/Providers.tsx b/Providers.tsx index 009cec214..03b8242e7 100644 --- a/Providers.tsx +++ b/Providers.tsx @@ -1,13 +1,9 @@ -import React from 'react'; import { combineComponents } from '@helpers/combineComponents'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import React from 'react'; import { SafeAreaProvider } from 'react-native-safe-area-context'; import { ListsContextProvider } from '@contexts/ListsContext'; -import { - AllAddressesProvider, - OnboardingContextProvider, - LocalizationProvider -} from '@contexts'; +import { AllAddressesProvider, OnboardingContextProvider } from '@contexts'; const queryClient = new QueryClient(); @@ -19,14 +15,9 @@ const WrappedSafeAreaProvider: React.FC = ({ children }: any) => ( {children} ); -const WrappedLocalizationProvider: React.FC = ({ children }: any) => ( - {children} -); - const independentProviders = [ WrappedQueryClientProvider, - WrappedSafeAreaProvider, - WrappedLocalizationProvider + WrappedSafeAreaProvider ]; /** * The order of the providers matters @@ -35,7 +26,6 @@ const providers = [ ...independentProviders, AllAddressesProvider, ListsContextProvider, - WrappedLocalizationProvider, OnboardingContextProvider ]; diff --git a/package.json b/package.json index 2dfbdf292..53187d363 100644 --- a/package.json +++ b/package.json @@ -56,15 +56,12 @@ "expo-store-review": "~6.2.1", "expo-system-ui": "~2.2.1", "expo-updates": "~0.16.4", - "i18n-js": "^4.3.0", - "i18next": "^23.4.4", "jest": "^29.2.1", "jest-expo": "^48.0.2", "moment": "^2.29.4", "patch-package": "^7.0.0", "postinstall-postinstall": "^2.1.0", "react": "18.2.0", - "react-i18next": "^13.1.0", "react-native": "0.71.8", "react-native-calendar-picker": "^7.1.4", "react-native-gesture-handler": "~2.9.0", diff --git a/src/components/composite/Button/CopyToClipboard.tsx b/src/components/composite/Button/CopyToClipboard.tsx index c83ca2aa0..dd602b1ef 100644 --- a/src/components/composite/Button/CopyToClipboard.tsx +++ b/src/components/composite/Button/CopyToClipboard.tsx @@ -6,7 +6,6 @@ import { ClipboardFilledIcon } from '@components/svg/icons'; import { scale } from '@utils/scaling'; import { Toast, ToastPosition } from '@components/modular/Toast'; import { BaseButtonProps } from '@components/base/Button'; -import { useTranslation } from 'react-i18next'; export interface CopyToClipboardButtonProps extends Omit { @@ -19,13 +18,9 @@ export const CopyToClipboardButton = ( props: CopyToClipboardButtonProps ): JSX.Element => { const { textToDisplay, textToCopy, textProps, ...buttonProps } = props; - const { t } = useTranslation(); const onPress = async () => { - Toast.show({ - message: t('copied.to.clipboard'), - type: ToastPosition.Bottom - }); + Toast.show({ message: 'Copied to Clipboard', type: ToastPosition.Bottom }); await Clipboard.setStringAsync(textToCopy || textToDisplay); }; diff --git a/src/components/modular/CollectionItem/index.tsx b/src/components/modular/CollectionItem/index.tsx index b1ce4bec2..2a0eead47 100644 --- a/src/components/modular/CollectionItem/index.tsx +++ b/src/components/modular/CollectionItem/index.tsx @@ -6,7 +6,6 @@ import { COLORS } from '@constants/colors'; import { useAMBPrice } from '@hooks'; import { AccountList } from '@models'; import { NumberUtils } from '@utils/number'; -import { useTranslation } from 'react-i18next'; interface CollectionItemProps { collection: AccountList; @@ -16,7 +15,6 @@ interface CollectionItemProps { export function CollectionItem(props: CollectionItemProps) { const { collection, style } = props; const { data: ambPriceData } = useAMBPrice(); - const { t } = useTranslation(); const tokensFormatted = useMemo(() => { const formattedNumber = NumberUtils.formatNumber( @@ -55,7 +53,7 @@ export function CollectionItem(props: CollectionItemProps) { color={COLORS.smokyBlack50} fontSize={12} > - {collection.accountCount + ` ${t('addresses.text')}`} + {collection.accountCount + ' addresses'} {collection.accountCount > 0 && ( diff --git a/src/components/templates/AMBPriceHistory/index.tsx b/src/components/templates/AMBPriceHistory/index.tsx index d08134c7d..b215003bc 100644 --- a/src/components/templates/AMBPriceHistory/index.tsx +++ b/src/components/templates/AMBPriceHistory/index.tsx @@ -1,6 +1,4 @@ import React, { useEffect, useMemo, useRef, useState } from 'react'; -import { View } from 'react-native'; -import { useTranslation } from 'react-i18next'; import Animated, { useAnimatedProps, useAnimatedStyle, @@ -18,8 +16,9 @@ import { scale, verticalScale } from '@utils/scaling'; import { Badge } from '@components/base/Badge'; import { PercentChange } from '@components/composite'; import { BezierChart } from '../BezierChart'; -import { MONTH_NAMES } from '@constants/variables'; import { styles } from './styles'; +import { MONTH_NAMES } from '@constants/variables'; +import { View } from 'react-native'; interface AMBPriceHistoryProps { badgeType: 'view' | 'button'; @@ -45,7 +44,6 @@ export const AMBPriceHistory = (props: AMBPriceHistoryProps) => { const ambPrice = useSharedValue(ambPriceNow?.priceUSD || 0); const selectedPointDate = useSharedValue(-1); const didSetAMBPriceFromAPI = useRef(false); - const { t } = useTranslation(); useEffect(() => { if (ambPriceNow) { @@ -206,15 +204,15 @@ export const AMBPriceHistory = (props: AMBPriceHistoryProps) => { // value: '1h' // }, { - text: t('chart.timeframe.daily'), + text: '1D', value: '1d' }, { - text: t('chart.timeframe.weekly'), + text: '1W', value: 'weekly' }, { - text: t('chart.timeframe.monthly'), + text: '1M', value: 'monthly' } ]} diff --git a/src/components/templates/AddWalletToList/index.tsx b/src/components/templates/AddWalletToList/index.tsx index 429aba41b..dd55bca98 100644 --- a/src/components/templates/AddWalletToList/index.tsx +++ b/src/components/templates/AddWalletToList/index.tsx @@ -10,7 +10,6 @@ import { COLORS } from '@constants/colors'; import { SearchIcon } from '@components/svg/icons'; import { NumberUtils } from '@utils/number'; import { useLists } from '@contexts/ListsContext'; -import { useTranslation } from 'react-i18next'; export interface AddWalletToListProps { wallet: ExplorerAccount; @@ -21,7 +20,6 @@ export interface AddWalletToListProps { export const AddWalletToList = (props: AddWalletToListProps): JSX.Element => { const { wallet, lists, onWalletMove } = props; const { toggleAddressesInList } = useLists((v) => v); - const { t } = useTranslation(); const [searchText, setSearchText] = useState(''); const filteredLists = useMemo( () => @@ -78,7 +76,7 @@ export const AddWalletToList = (props: AddWalletToListProps): JSX.Element => { fontFamily="Inter_500Medium" color={COLORS.smokyBlack50} > - {list.accountCount} {t('addresses')} + {list.accountCount} Addresses @@ -97,7 +95,7 @@ export const AddWalletToList = (props: AddWalletToListProps): JSX.Element => { } /> diff --git a/src/components/templates/BottomSheetAddWalletToList/index.tsx b/src/components/templates/BottomSheetAddWalletToList/index.tsx index a2c08c4d2..1dc82b2b2 100644 --- a/src/components/templates/BottomSheetAddWalletToList/index.tsx +++ b/src/components/templates/BottomSheetAddWalletToList/index.tsx @@ -5,9 +5,6 @@ import { useFullscreenModalHeight } from '@hooks'; import { COLORS } from '@constants/colors'; import { AddWalletToList, AddWalletToListProps } from '../AddWalletToList'; import { verticalScale } from '@utils/scaling'; -import { PrimaryButton } from '@components/modular'; -import { BottomSheetCreateRenameGroup } from '@components/templates'; -import { useLists } from '@contexts'; interface BottomSheetAddWalletToListProps extends AddWalletToListProps { title: string; @@ -19,11 +16,6 @@ export const BottomSheetAddWalletToList = forwardRef< >((props, ref) => { const { title, ...addWalletToListProps } = props; const fullscreenHeight = useFullscreenModalHeight(); - const { handleOnCreate, createGroupRef } = useLists((v) => v); - - const showCreateNewListModal = () => { - createGroupRef.current?.show(); - }; return ( - { - showCreateNewListModal(); - }} - style={{ width: '90%', alignSelf: 'center' }} - > - - Create new group - - - - ); }); diff --git a/src/components/templates/BottomSheetConfirmRemove/BottomSheetRemoveAddressFromWatchlists.tsx b/src/components/templates/BottomSheetConfirmRemove/BottomSheetRemoveAddressFromWatchlists.tsx index 2b5f72ca1..ddf5efc65 100644 --- a/src/components/templates/BottomSheetConfirmRemove/BottomSheetRemoveAddressFromWatchlists.tsx +++ b/src/components/templates/BottomSheetConfirmRemove/BottomSheetRemoveAddressFromWatchlists.tsx @@ -8,7 +8,6 @@ import { COLORS } from '@constants/colors'; import { ExplorerAccount } from '@models'; import { BottomSheetFloat } from '@components/modular'; import { verticalScale } from '@utils/scaling'; -import { useTranslation } from 'react-i18next'; type Props = { ref: RefObject; @@ -21,7 +20,6 @@ export const BottomSheetRemoveAddressFromWatchlists = forwardRef< >(({ item }, ref) => { const localRef: ForwardedRef = useForwardedRef(ref); const { removeFromWatchlist } = useWatchlist(); - const { t } = useTranslation(); const handleRemoveAddressFromWatchlist = useCallback(() => { removeFromWatchlist(item); @@ -44,7 +42,7 @@ export const BottomSheetRemoveAddressFromWatchlists = forwardRef< fontSize={14} color={COLORS.smokyBlack} > - {t('confirm.remove.address.from.watchlist')} + Remove this address from watchlist? @@ -72,7 +70,7 @@ export const BottomSheetRemoveAddressFromWatchlists = forwardRef< color={COLORS.smokyBlack} fontSize={16} > - {t('cancel.btn')} + Cancel diff --git a/src/components/templates/BottomSheetCreateCollectionOrAddAddress/index.tsx b/src/components/templates/BottomSheetCreateCollectionOrAddAddress/index.tsx index 44efde22e..938eaecc2 100644 --- a/src/components/templates/BottomSheetCreateCollectionOrAddAddress/index.tsx +++ b/src/components/templates/BottomSheetCreateCollectionOrAddAddress/index.tsx @@ -6,7 +6,6 @@ import { BottomSheetFloat, PrimaryButton } from '@components/modular'; import { useForwardedRef } from '@hooks'; import { scale, verticalScale } from '@utils/scaling'; import { COLORS } from '@constants/colors'; -import { useTranslation } from 'react-i18next'; type Props = { ref: RefObject; @@ -19,7 +18,6 @@ export const BottomSheetCreateCollectionOrAddAddress = forwardRef< Props >(({ handleCreateCollectionPress, handleOnAddNewAddress }, ref) => { const localRef: ForwardedRef = useForwardedRef(ref); - const { t } = useTranslation(); return ( - {t('add.address.btn')} + Add address @@ -62,7 +60,7 @@ export const BottomSheetCreateCollectionOrAddAddress = forwardRef< fontSize={16} color={COLORS.smokyBlack} > - {t('create.group')} + Create group diff --git a/src/components/templates/BottomSheetCreateRenameGroup/index.tsx b/src/components/templates/BottomSheetCreateRenameGroup/index.tsx index c327c492e..fc53227a1 100644 --- a/src/components/templates/BottomSheetCreateRenameGroup/index.tsx +++ b/src/components/templates/BottomSheetCreateRenameGroup/index.tsx @@ -23,7 +23,6 @@ import { OnboardingView } from '../OnboardingView'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { verticalScale } from '@utils/scaling'; import { StringUtils } from '@utils/string'; -import { useTranslation } from 'react-i18next'; type Props = { ref: RefObject; @@ -44,7 +43,6 @@ export const BottomSheetCreateRenameGroup = forwardRef( } = props; const localRef: ForwardedRef = useForwardedRef(ref); - const { t } = useTranslation(); const nameInput = useRef(null); const [localGroupName, setLocalGroupName] = useState(''); @@ -69,11 +67,11 @@ export const BottomSheetCreateRenameGroup = forwardRef( handleOnCreateGroup(localGroupName); Toast.show({ title: '', - message: `${t('toast.way.to.go')} ${StringUtils.formatAddress( + message: `Way to go! ${StringUtils.formatAddress( localGroupName, 16, 0 - )} ${t('toast.group.created')}`, + )} group created.`, type: ToastPosition.Top }); } @@ -82,9 +80,15 @@ export const BottomSheetCreateRenameGroup = forwardRef( handleOnRenameGroup(groupId, localGroupName); Toast.show({ title: '', - message: `${StringUtils.formatAddress(groupTitle || '', 16, 0)} ${t( - 'toast.has.been.renamed' - )} ${StringUtils.formatAddress(localGroupName, 16, 0)}.`, + message: `${StringUtils.formatAddress( + groupTitle || '', + 16, + 0 + )} has been renamed to ${StringUtils.formatAddress( + localGroupName, + 16, + 0 + )}.`, type: ToastPosition.Top }); } @@ -97,8 +101,7 @@ export const BottomSheetCreateRenameGroup = forwardRef( handleOnCreateGroup, handleOnRenameGroup, localGroupName, - localRef, - t + localRef ]); const bottomSafeArea = useSafeAreaInsets().bottom - 10; @@ -133,7 +136,7 @@ export const BottomSheetCreateRenameGroup = forwardRef( fontSize={16} color={COLORS.smokyBlack} > - {type === 'create' ? t('create.group') : t('rename.group')} + {type === 'create' ? ' Create group' : 'Rename group'} ( type="text" placeholder={ emptyPlaceholder - ? t('field.required') - : t('group.name.input') + ? 'This field is required' + : 'Enter group name' } placeholderTextColor={ emptyPlaceholder ? COLORS.crimsonRed : COLORS.midnight @@ -175,7 +178,7 @@ export const BottomSheetCreateRenameGroup = forwardRef( fontSize={16} color={COLORS.white} > - {type === 'create' ? t('create.btn') : t('save.btn')} + {type === 'create' ? 'Create' : 'Save'} @@ -195,7 +198,7 @@ export const BottomSheetCreateRenameGroup = forwardRef( color={COLORS.nero} fontSize={16} > - {t('cancel.btn')} + Cancel diff --git a/src/components/templates/BottomSheetEditCollection/index.tsx b/src/components/templates/BottomSheetEditCollection/index.tsx index 8e6630316..000c93863 100644 --- a/src/components/templates/BottomSheetEditCollection/index.tsx +++ b/src/components/templates/BottomSheetEditCollection/index.tsx @@ -10,7 +10,6 @@ import { AccountList } from '@models'; import { BottomSheetCreateRenameGroup } from '../BottomSheetCreateRenameGroup'; import { styles } from './styles'; import { StringUtils } from '@utils/string'; -import { useTranslation } from 'react-i18next'; interface BottomSheetEditCollectionProps extends BottomSheetProps { collection: AccountList; @@ -26,7 +25,6 @@ export const BottomSheetEditCollection = forwardRef< const localRef: ForwardedRef = useForwardedRef(ref); const { handleOnRename, handleOnDelete } = useLists((v) => v); const renameCollectionModalRef = useRef(null); - const { t } = useTranslation(); const dismissThis = useCallback(() => { setTimeout(() => { @@ -76,7 +74,7 @@ export const BottomSheetEditCollection = forwardRef< fontFamily="Inter_600SemiBold" color={COLORS.smokyBlack} > - {t('rename.group')} + Rename group diff --git a/src/components/templates/BottomSheetEditWallet/index.tsx b/src/components/templates/BottomSheetEditWallet/index.tsx index de5982246..671310207 100644 --- a/src/components/templates/BottomSheetEditWallet/index.tsx +++ b/src/components/templates/BottomSheetEditWallet/index.tsx @@ -10,7 +10,6 @@ import { BottomSheetRenameAddress } from '@screens/SingleCollection/modals/Botto import { COLORS } from '@constants/colors'; import { BottomSheetAddWalletToList } from '../BottomSheetAddWalletToList'; import { styles } from './styles'; -import { useTranslation } from 'react-i18next'; interface BottomSheetEditWalletProps extends BottomSheetProps { wallet: ExplorerAccount; @@ -26,7 +25,6 @@ export const BottomSheetEditWallet = forwardRef< const { listsOfAddressGroup, toggleAddressesInList } = useLists((v) => v); const renameWalletModalRef = useRef(null); const addToCollectionModalRef = useRef(null); - const { t } = useTranslation(); const listsWithCurrentWallet = listsOfAddressGroup.filter((list) => list.accounts.some((acc) => acc?.address === wallet?.address) @@ -81,11 +79,11 @@ export const BottomSheetEditWallet = forwardRef< dismissThis(); Toast.show({ title: '', - message: t('toast.Successfully.removed.wallet.from.group'), + message: 'Successfully removed wallet from group!', type: ToastPosition.Top }); } - }, [dismissThis, listsWithCurrentWallet, t, toggleAddressesInList, wallet]); + }, [dismissThis, listsWithCurrentWallet, toggleAddressesInList, wallet]); return ( - {t('rename.address')} + Rename address {listsWithCurrentWallet.length > 0 ? ( @@ -116,7 +114,7 @@ export const BottomSheetEditWallet = forwardRef< fontFamily="Inter_600SemiBold" color={COLORS.smokyBlack} > - {t('move.to.another.group')} + Move to another group listsWithCurrentWallet.indexOfItem(list, 'id') === -1 @@ -153,12 +149,12 @@ export const BottomSheetEditWallet = forwardRef< fontFamily="Inter_600SemiBold" color={COLORS.smokyBlack} > - {t('add.address.to.group.lower.case')} + Add to group ( (notificationSettings); const { top: topInset } = useSafeAreaInsets(); - const { t } = useTranslation(); - useEffect( () => setLocalNotificationSettings(notificationSettings), [notificationSettings] @@ -94,7 +91,7 @@ export const BottomSheetNotificationSettings = forwardRef< fontSize={16} color={COLORS.smokyBlack} > - {t('notification.settings')} + Notification settings } titlePosition={Platform.select({ ios: 'left', default: 'center' })} @@ -111,7 +108,7 @@ export const BottomSheetNotificationSettings = forwardRef< {/* Price alerts */} - {t('price.alerts.switch')} + Price alerts onSettingsValueChange( @@ -125,14 +122,14 @@ export const BottomSheetNotificationSettings = forwardRef< {/* Percentage Change */} - {t('price.alerts.treshold')} + Price alerts threshold - {t('price.alerts.treshold.text')} + Set 24hr price change amount to receive notifications. - {t('transaction.alerts.switch')} + Transaction alerts - {t('transaction.alerts.switch.text')} + Get notified of transactions in your watchlist. ; @@ -22,7 +21,6 @@ export const BottomSheetRemoveAddressFromCollection = forwardRef< >(({ wallet }, ref) => { const localRef: ForwardedRef = useForwardedRef(ref); const { toggleAddressesInList, listsOfAddressGroup } = useLists((v) => v); - const { t } = useTranslation(); const collection = listsOfAddressGroup.find( (list) => list.accounts.findIndex((account) => account._id === wallet._id) > -1 @@ -42,7 +40,7 @@ export const BottomSheetRemoveAddressFromCollection = forwardRef< color={COLORS.smokyBlack} numberOfLines={1} > - {t('remove.address.from.group.select')} {collection?.name}? + Remove this address from {collection?.name}? @@ -74,7 +72,7 @@ export const BottomSheetRemoveAddressFromCollection = forwardRef< color={COLORS.smokyBlack} fontSize={16} > - {t('cancel.btn')} + Cancel diff --git a/src/components/templates/ExplorerAccount/ExplorerAccount.TransactionItem.tsx b/src/components/templates/ExplorerAccount/ExplorerAccount.TransactionItem.tsx index 6b9667388..60243fcb2 100644 --- a/src/components/templates/ExplorerAccount/ExplorerAccount.TransactionItem.tsx +++ b/src/components/templates/ExplorerAccount/ExplorerAccount.TransactionItem.tsx @@ -8,7 +8,6 @@ import { BottomSheetFloat, TransactionItem } from '@components/modular'; import { Transaction } from '@models/Transaction'; import { scale, verticalScale } from '@utils/scaling'; import { CommonStackNavigationProp } from '@appTypes/navigation/common'; -import { useTranslation } from 'react-i18next'; interface ExplorerAccountTransactionItemProps { transaction: Transaction; @@ -21,7 +20,6 @@ export const ExplorerAccountTransactionItem = ( const { transaction, disabled = false } = props; const transactionDetailsModal = useRef(null); const navigation = useNavigation(); - const { t } = useTranslation(); const showTransactionDetails = () => { transactionDetailsModal.current?.show(); @@ -44,7 +42,7 @@ export const ExplorerAccountTransactionItem = ( - {t('transaction.details')} + Transaction details diff --git a/src/components/templates/ExplorerAccount/ExplorerAccount.Transactions.tsx b/src/components/templates/ExplorerAccount/ExplorerAccount.Transactions.tsx index 3067a4202..0db3ff85b 100644 --- a/src/components/templates/ExplorerAccount/ExplorerAccount.Transactions.tsx +++ b/src/components/templates/ExplorerAccount/ExplorerAccount.Transactions.tsx @@ -11,7 +11,6 @@ import { } from '@components/base'; import { ExplorerAccountTransactionItem } from './ExplorerAccount.TransactionItem'; import { COLORS } from '@constants/colors'; -import { useTranslation } from 'react-i18next'; interface ExplorerAccountViewTransactionsProps { transactions: Transaction[]; @@ -25,7 +24,6 @@ export const AccountTransactions = ( ): JSX.Element => { const { transactions, loading, showTransactionDetailsOnPress, onEndReached } = props; - const { t } = useTranslation(); const renderTransaction = ( args: ListRenderItemInfo @@ -48,7 +46,7 @@ export const AccountTransactions = ( fontSize={20} color={COLORS.jetBlack} > - {t('recent.activity')} + Recent activity diff --git a/src/components/templates/ExplorerAccount/index.tsx b/src/components/templates/ExplorerAccount/index.tsx index c97c877a3..613126c5b 100644 --- a/src/components/templates/ExplorerAccount/index.tsx +++ b/src/components/templates/ExplorerAccount/index.tsx @@ -8,14 +8,13 @@ import { useAMBPrice } from '@hooks/query'; import { NumberUtils } from '@utils/number'; import { styles } from './styles'; import { useLists } from '@contexts/ListsContext'; -// import { PlusIcon } from '@components/svg/icons'; +import { PlusIcon } from '@components/svg/icons'; import { BottomSheetRef, CopyToClipboardButton } from '@components/composite'; import { COLORS } from '@constants/colors'; import { AddWalletToList } from '../AddWalletToList'; import { BottomSheetWithHeader } from '@components/modular'; import { useFullscreenModalHeight } from '@hooks/useFullscreenModalHeight'; import { useWatchlist } from '@hooks'; -import { useTranslation } from 'react-i18next'; interface ExplorerAccountProps { account: ExplorerAccount; @@ -31,7 +30,6 @@ export const ExplorerAccountView = ( const { listsOfAddressGroup } = useLists((v) => v); const fullscreenHeight = useFullscreenModalHeight(); const { addToWatchlist, removeFromWatchlist } = useWatchlist(); - const { t } = useTranslation(); const { data } = useAMBPrice(); const ambPriceUSD = data?.priceUSD || 0; @@ -78,8 +76,7 @@ export const ExplorerAccountView = ( fontSize={13} fontWeight="400" > - {t('address.added.to.group')} - {listsWithAccount[0].name} + Added to {listsWithAccount[0].name} ) : ( - {t('address.added.to.group')} - {listsWithAccount.length} - {t('groups.count')} + Added to {listsWithAccount.length} lists ))} @@ -157,16 +152,14 @@ export const ExplorerAccountView = ( fontSize={12} color={account.isOnWatchlist ? COLORS.liver : COLORS.white} > - {account.isOnWatchlist - ? t('watchlisted.address') - : t('watchlist.address.btn')} + {account.isOnWatchlist ? 'WATCHLISTED' : 'ADD TO WATCHLIST'} - {/*{!account.isOnWatchlist && (*/} - {/* <>*/} - {/* */} - {/* */} - {/* */} - {/*)}*/} + {!account.isOnWatchlist && ( + <> + + + + )} @@ -192,19 +185,17 @@ export const ExplorerAccountView = ( fontSize={12} > {listsWithAccount.length === 0 - ? t('add.address.to.group.upper.case') + ? 'ADD TO GROUP' : listsWithAccount.length === 1 ? StringUtils.formatAddress(listsWithAccount[0].name, 16, 0) - : `${t('address.added.to.group')} ${ - listsWithAccount.length - } ${t('groups.count')}`} + : `Added to ${listsWithAccount.length} groups`} - {/*{listsWithAccount.length === 0 && (*/} - {/* <>*/} - {/* */} - {/* */} - {/* */} - {/*)}*/} + {listsWithAccount.length === 0 && ( + <> + + + + )} @@ -212,7 +203,7 @@ export const ExplorerAccountView = ( diff --git a/src/components/templates/LocalizedRenderEmpty/index.tsx b/src/components/templates/RenderEmpty/index.tsx similarity index 64% rename from src/components/templates/LocalizedRenderEmpty/index.tsx rename to src/components/templates/RenderEmpty/index.tsx index 9a2a387f1..93cf7f924 100644 --- a/src/components/templates/LocalizedRenderEmpty/index.tsx +++ b/src/components/templates/RenderEmpty/index.tsx @@ -1,23 +1,21 @@ -import React from 'react'; import { View } from 'react-native'; import { EmptyWalletListPlaceholderIcon } from '@components/svg/icons'; import { Spacer, Text } from '@components/base'; import { verticalScale } from '@utils/scaling'; -import { styles } from '@components/templates/LocalizedRenderEmpty/styles'; -import { useTranslation } from 'react-i18next'; +import React from 'react'; +import { styles } from '@components/templates/RenderEmpty/styles'; -type LocalizedRenderEmptyProps = { +type RenderEmptyProps = { text: string; }; -export const LocalizedRenderEmpty = ({ text }: LocalizedRenderEmptyProps) => { - const { t } = useTranslation(); +export const RenderEmpty = ({ text }: RenderEmptyProps) => { return ( - {t(text)} + No {text} yet ); diff --git a/src/components/templates/LocalizedRenderEmpty/styles.ts b/src/components/templates/RenderEmpty/styles.ts similarity index 100% rename from src/components/templates/LocalizedRenderEmpty/styles.ts rename to src/components/templates/RenderEmpty/styles.ts diff --git a/src/components/templates/SearchAddress/SearchAddress.NoMatch.tsx b/src/components/templates/SearchAddress/SearchAddress.NoMatch.tsx index f142335a8..c3a889f97 100644 --- a/src/components/templates/SearchAddress/SearchAddress.NoMatch.tsx +++ b/src/components/templates/SearchAddress/SearchAddress.NoMatch.tsx @@ -4,10 +4,8 @@ import { Spacer, Text } from '@components/base'; import { COLORS } from '@constants/colors'; import { moderateScale, verticalScale } from '@utils/scaling'; import { NoMatch } from '@components/svg/icons/NoMatch'; -import { useTranslation } from 'react-i18next'; export function SearchAddressNoResult(): JSX.Element { - const { t } = useTranslation(); return ( @@ -18,7 +16,7 @@ export function SearchAddressNoResult(): JSX.Element { fontWeight="600" fontFamily="Inter_600SemiBold" > - {t('no.matches')} + Oops! No matches found - {t('check.typos')} + Please check for any typos or try a different address ); diff --git a/src/components/templates/SearchAddress/index.tsx b/src/components/templates/SearchAddress/index.tsx index e405135e6..527c31afb 100644 --- a/src/components/templates/SearchAddress/index.tsx +++ b/src/components/templates/SearchAddress/index.tsx @@ -35,7 +35,6 @@ import { CRYPTO_ADDRESS_MAX_LENGTH } from '@constants/variables'; import { COLORS } from '@constants/colors'; import { SearchTabNavigationProp } from '@appTypes'; import { styles } from './styles'; -import { useTranslation } from 'react-i18next'; interface SearchAdressProps { initialValue?: string; @@ -48,7 +47,6 @@ export const SearchAddress = (props: SearchAdressProps): JSX.Element => { const { initialValue, onContentVisibilityChanged = () => null } = props; const navigation = useNavigation(); const { height: WINDOW_HEIGHT } = useWindowDimensions(); - const { t } = useTranslation(); const { data: explorerInfo } = useExplorerInfo(); const [address, setAddress] = useState(''); const [searchSubmitted, setSearchSubmitted] = useState(false); @@ -110,8 +108,8 @@ export const SearchAddress = (props: SearchAdressProps): JSX.Element => { const toggleWatchlist = async (isOnWatchlist: boolean) => { if (isOnWatchlist) { Toast.show({ - title: t('toast.address.watchlisted.msg'), - message: t('toast.tap.to.rename.msg'), + title: 'Way to go! Address watchlisted.', + message: 'Tap to rename Address', type: ToastPosition.Top, onBodyPress: editModal.current?.show }); @@ -160,9 +158,9 @@ export const SearchAddress = (props: SearchAdressProps): JSX.Element => { }, 500); } else if (!scanned.current) { scanned.current = true; - Alert.alert(t('invalid.qr.code.msg'), '', [ + Alert.alert('Invalid QR code', '', [ { - text: t('scan.again.msg'), + text: 'Scan again', onPress: () => { scanned.current = false; } @@ -218,7 +216,7 @@ export const SearchAddress = (props: SearchAdressProps): JSX.Element => { )} } - placeholder={t('search.address.input')} + placeholder={'Search Address or TX hash'} returnKeyType="search" onFocus={onInputFocused} onBlur={onInputBlur} @@ -266,7 +264,7 @@ export const SearchAddress = (props: SearchAdressProps): JSX.Element => { fontSize={20} color={COLORS.neutral800} > - {t('transaction.details')} + Transaction details (null); const { data: ambData } = useAMBPrice(); - const { t } = useTranslation(); const ambPrice = ambData ? ambData.priceUSD : -1; let totalTransactionAmount; @@ -69,7 +67,7 @@ export const TransactionDetails = ( - {t('method')} + Method {transaction.type} @@ -78,7 +76,7 @@ export const TransactionDetails = ( - {t('amount')} + Amount {NumberUtils.formatNumber(transaction.amount, 0)} AMB @@ -90,7 +88,7 @@ export const TransactionDetails = ( - {t('from')} + From diff --git a/src/screens/Portfolio/index.tsx b/src/screens/Portfolio/index.tsx index bcf5b681d..e14dc7171 100644 --- a/src/screens/Portfolio/index.tsx +++ b/src/screens/Portfolio/index.tsx @@ -7,7 +7,33 @@ import { WatchList } from '@screens/Portfolio/components/PortfolioScreenTabs/com import { useIsFocused } from '@react-navigation/native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { View } from 'react-native'; -import { useTranslation } from 'react-i18next'; + +const portfolioTabRoutes = [ + { key: 'first', title: 'Addresses' }, + { key: 'second', title: 'Groups' } +] as const; + +type PortfolioTabRoutes = typeof portfolioTabRoutes; + +type PortfolioTabViewRoute = { + key: PortfolioTabRoutes[number]['key']; + title: PortfolioTabRoutes[number]['title']; +}; + +type RenderSceneProps = Parameters< + TabViewProps['renderScene'] +>[0]; + +const renderScene = ({ route }: RenderSceneProps) => { + switch (route.key) { + case 'first': + return ; + case 'second': + return ; + default: + return null; + } +}; type PortfolioScreenProps = { route: { @@ -18,34 +44,6 @@ type PortfolioScreenProps = { }; export const PortfolioScreen = ({ route }: PortfolioScreenProps) => { - const { t } = useTranslation(); - const portfolioTabRoutes = [ - { key: 'first', title: t('addresses') }, - { key: 'second', title: t('groups.capitalize') } - ] as const; - - type PortfolioTabRoutes = typeof portfolioTabRoutes; - - type PortfolioTabViewRoute = { - key: PortfolioTabRoutes[number]['key']; - title: PortfolioTabRoutes[number]['title']; - }; - - type RenderSceneProps = Parameters< - TabViewProps['renderScene'] - >[0]; - - // tslint:disable-next-line:no-shadowed-variable - const renderScene = ({ route }: RenderSceneProps) => { - switch (route.key) { - case 'first': - return ; - case 'second': - return ; - default: - return null; - } - }; const { top } = useSafeAreaInsets(); const activeTab = route?.params?.tabs?.activeTab; const [index, setIndex] = useState(0); diff --git a/src/screens/Search/components/WalletItem.tsx b/src/screens/Search/components/WalletItem.tsx index 64c862fcf..e70b2b224 100644 --- a/src/screens/Search/components/WalletItem.tsx +++ b/src/screens/Search/components/WalletItem.tsx @@ -9,7 +9,6 @@ import { COLORS } from '@constants/colors'; import { useLists } from '@contexts'; import { useWatchlist } from '@hooks'; import { AddressIndicator } from '@components/templates'; -import { useTranslation } from 'react-i18next'; interface ExplorerWalletItemProps { item: ExplorerAccount; @@ -24,7 +23,6 @@ export const ExplorerWalletItem = ( const { address, transactionCount, ambBalance } = item; const { listsOfAddressGroup } = useLists((v) => v); const { watchlist } = useWatchlist(); - const { t } = useTranslation(); const listWithAddress = listsOfAddressGroup.filter( (list) => list.accounts.indexOfItem(item, 'address') > -1 ); @@ -74,19 +72,18 @@ export const ExplorerWalletItem = ( color={COLORS.smokyBlack50} fontFamily="Inter_500Medium" > - {t('single.address.holding')} + Holding{' '} {NumberUtils.formatNumber( item.calculatePercentHoldings(totalSupply), 2 - )}{' '} - {t('single.address.supply')} + )} + % of supply - {/* TODO add localisation here, key is "transactions" */} {StringUtils.pluralize(transactionCount, 'Transaction')} diff --git a/src/screens/Search/index.tsx b/src/screens/Search/index.tsx index 2552e4515..69d92395c 100644 --- a/src/screens/Search/index.tsx +++ b/src/screens/Search/index.tsx @@ -22,12 +22,10 @@ import { SearchSort } from './Search.types'; import { COLORS } from '@constants/colors'; import { styles } from './styles'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; -import { useTranslation } from 'react-i18next'; export const SearchScreen = () => { const navigation = useNavigation(); const { data: infoData } = useExplorerInfo(); - const { t } = useTranslation(); const { data: accounts, loading: accountsLoading, @@ -98,12 +96,12 @@ export const SearchScreen = () => { fontSize={20} color={COLORS.smokyBlack} > - {t('top.holders')} + Top holders {accountsError ? ( - {t('no.account.info')} + Could not load accounts info ) : ( infoData && accounts && ( diff --git a/src/screens/Settings/components/SettingsBlock/index.tsx b/src/screens/Settings/components/SettingsBlock/index.tsx index 40fd919f2..bec0d0285 100644 --- a/src/screens/Settings/components/SettingsBlock/index.tsx +++ b/src/screens/Settings/components/SettingsBlock/index.tsx @@ -17,10 +17,7 @@ import { import { BottomSheetNotificationSettings } from '@components/templates'; import { styles } from '@screens/Settings/components/SettingsBlock/style'; import { COLORS } from '@constants/colors'; -import { useTranslation } from 'react-i18next'; - export const SettingsBlock = () => { - const { t } = useTranslation(); const [selectedLanguage, setSelectedLanguage] = useState('English'); const [selectedCurrency, setSelectedCurrency] = useState('US Dollars (USD)'); @@ -61,9 +58,7 @@ export const SettingsBlock = () => { > - - {t('base.currency.modal')} - + Base currency {selectedCurrency} @@ -82,7 +77,7 @@ export const SettingsBlock = () => { > - {t('language.modal')} + Language {selectedLanguage} @@ -101,9 +96,7 @@ export const SettingsBlock = () => { > - - {t('notification.settings.modal')} - + Notification settings diff --git a/src/screens/Settings/components/SettingsBlock/modals/BottomSheetBaseCurrency/index.tsx b/src/screens/Settings/components/SettingsBlock/modals/BottomSheetBaseCurrency/index.tsx index 9f7ca5cd8..80ea9d917 100644 --- a/src/screens/Settings/components/SettingsBlock/modals/BottomSheetBaseCurrency/index.tsx +++ b/src/screens/Settings/components/SettingsBlock/modals/BottomSheetBaseCurrency/index.tsx @@ -8,7 +8,6 @@ import { styles } from '@screens/Settings/components/SettingsBlock/modals/style' import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { COLORS } from '@constants/colors'; import { scale } from '@utils/scaling'; -import { useTranslation } from 'react-i18next'; type Props = { ref: RefObject; @@ -71,7 +70,6 @@ export const BottomSheetSelectBaseCurrency = forwardRef( selectedCurrency || '' ); const { top: topInset } = useSafeAreaInsets(); - const { t } = useTranslation(); const onCurrencyPress = (value: Currency) => { setModalActiveCurrency(value); @@ -92,7 +90,7 @@ export const BottomSheetSelectBaseCurrency = forwardRef( fontSize={scale(16)} color={COLORS.smokyBlack} > - {t('select.base.currency')} + Select base currency } titlePosition={Platform.select({ ios: 'left', default: 'center' })} diff --git a/src/screens/Settings/components/SettingsBlock/modals/BottomSheetSelectLanguage/index.tsx b/src/screens/Settings/components/SettingsBlock/modals/BottomSheetSelectLanguage/index.tsx index d3e2b798f..e13413c4d 100644 --- a/src/screens/Settings/components/SettingsBlock/modals/BottomSheetSelectLanguage/index.tsx +++ b/src/screens/Settings/components/SettingsBlock/modals/BottomSheetSelectLanguage/index.tsx @@ -8,8 +8,6 @@ import { styles } from '@screens/Settings/components/SettingsBlock/modals/style' import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { COLORS } from '@constants/colors'; import { scale } from '@utils/scaling'; -import { useTranslation } from 'react-i18next'; -import useLocalization from '@contexts/Localizations'; type Props = { ref: RefObject; @@ -34,16 +32,16 @@ type LanguageData = { const mockedLanguages: LanguageData[] = [ { language: 'English' - }, + } // { // language: 'Arabic' // }, // { // language: 'Spanish' // }, - { - language: 'Turkish' - } + // { + // language: 'Turkish' + // }, // { // language: 'Hindi' // }, @@ -61,19 +59,14 @@ const mockedLanguages: LanguageData[] = [ export const BottomSheetSelectLanguage = forwardRef( ({ selectedLanguage, handleLanguageSave }, ref) => { const localRef: ForwardedRef = useForwardedRef(ref); - // eslint-disable-next-line @typescript-eslint/no-unused-vars const [modalActiveLanguage, setModalActiveLanguage] = useState( selectedLanguage || '' ); const { top: topInset } = useSafeAreaInsets(); - const { changeCurrentLanguage, currentLanguage } = useLocalization(); - - const { t } = useTranslation(); const onLanguagePress = (value: Language) => { setModalActiveLanguage(value); handleLanguageSave(value); - changeCurrentLanguage(value); }; return ( @@ -91,7 +84,7 @@ export const BottomSheetSelectLanguage = forwardRef( fontSize={scale(16)} color={COLORS.smokyBlack} > - {t('select.language')} + Select language } titlePosition={Platform.select({ ios: 'left', default: 'center' })} @@ -108,7 +101,7 @@ export const BottomSheetSelectLanguage = forwardRef( renderItem={({ item, index }) => { return ( { - const { t } = useTranslation(); const openLink = () => { Linking.openURL('https://airdao.academy/faqs'); }; @@ -19,7 +17,7 @@ export const SettingsInfoBlock = () => { @@ -31,8 +29,8 @@ export const SettingsInfoBlock = () => { })} {Platform.select({ - ios: t('rate.btn.ios'), - android: t('rate.btn.android') + ios: 'Rate us on the App Store', + android: 'Rate us on the Play Store' })} diff --git a/src/screens/SingleCollection/index.tsx b/src/screens/SingleCollection/index.tsx index bea27cf80..be8a7650d 100644 --- a/src/screens/SingleCollection/index.tsx +++ b/src/screens/SingleCollection/index.tsx @@ -20,7 +20,6 @@ import { useAllAddressesContext } from '@contexts'; import { BottomSheetAddNewAddressToGroup } from './modals/BottomSheetAddNewAddressToGroup'; import { sortListByKey } from '@utils/sort'; import { styles } from './styles'; -import { useTranslation } from 'react-i18next'; export const SingleGroupScreen = () => { const { @@ -30,7 +29,6 @@ export const SingleGroupScreen = () => { } = useRoute>(); const navigation = useNavigation(); const { data: ambPriceData } = useAMBPrice(); - const { t } = useTranslation(); const addNewAddressToGroupRef = useRef(null); const groupRenameRef = useRef(null); @@ -108,7 +106,7 @@ export const SingleGroupScreen = () => { fontFamily="Inter_600SemiBold" fontSize={12} > - {t('total.balance')} + TOTAL BALANCE diff --git a/src/screens/SingleCollection/modals/BottomSheetAddNewAddressToGroup/index.tsx b/src/screens/SingleCollection/modals/BottomSheetAddNewAddressToGroup/index.tsx index d49cff210..9b6f5424f 100644 --- a/src/screens/SingleCollection/modals/BottomSheetAddNewAddressToGroup/index.tsx +++ b/src/screens/SingleCollection/modals/BottomSheetAddNewAddressToGroup/index.tsx @@ -38,13 +38,25 @@ import { StringUtils } from '@utils/string'; import { SearchSort } from '@screens/Search/Search.types'; import { etherumAddressRegex } from '@constants/regex'; import { styles } from './styles'; -import { useTranslation } from 'react-i18next'; type Props = { ref: RefObject; collection: AccountList; }; +const AddressSources: Segment[] = [ + { + title: 'Watchlist', + value: 0, + id: 'watchlist' + }, + { + title: 'Top Holders', + value: 1, + id: 'topHolders' + } +]; + export const BottomSheetAddNewAddressToGroup = forwardRef< BottomSheetRef, Props @@ -57,21 +69,6 @@ export const BottomSheetAddNewAddressToGroup = forwardRef< fetchNextPage: fetchMoreTopHolders } = useExplorerAccounts(SearchSort.Balance); const [searchValue, setSearchValue] = useState(''); - const { t } = useTranslation(); - - const AddressSources: Segment[] = [ - { - title: t('watchlist.tab'), - value: 0, - id: 'watchlist' - }, - { - title: t('top.holders.capitalize'), - value: 1, - id: 'topHolders' - } - ]; - const { data: searchedAccount, loading: searchLoading, @@ -189,9 +186,9 @@ export const BottomSheetAddNewAddressToGroup = forwardRef< }, 500); } else if (!scanned.current) { scanned.current = true; - Alert.alert(t('invalid.qr.code.msg'), '', [ + Alert.alert('Invalid QR code', '', [ { - text: t('scan.again.msg'), + text: 'Scan again', onPress: () => { scanned.current = false; } @@ -231,7 +228,7 @@ export const BottomSheetAddNewAddressToGroup = forwardRef< fontFamily="Inter_700Bold" fontSize={18} color={COLORS.nero} - >{`${t('add.address.to.selected.group')} ${StringUtils.formatAddress( + >{`Add address to ${StringUtils.formatAddress( collection.name, 10, 0 @@ -259,7 +256,7 @@ export const BottomSheetAddNewAddressToGroup = forwardRef< } type="text" style={{ width: '65%', height: 50 }} - placeholder={t('search.public.address.input')} + placeholder="Search public address" placeholderTextColor="#2f2b4399" value={searchValue} onChangeText={setSearchValue} @@ -374,15 +371,15 @@ export const BottomSheetAddNewAddressToGroup = forwardRef< fontWeight="600" > {selectingAddedItems - ? `${t('remove.btn')} ${StringUtils.pluralize( + ? `Remove ${StringUtils.pluralize( selectedAddresses.length, - t('address'), - t('addresses') + 'Address', + 'Addresses' )}` - : `${t('add')} ${StringUtils.pluralize( + : `Add ${StringUtils.pluralize( selectedAddresses.length, - t('address'), - t('addresses') + 'Address', + 'Addresses' )}`} diff --git a/src/screens/SingleCollection/modals/BottomSheetRenameAddress/index.tsx b/src/screens/SingleCollection/modals/BottomSheetRenameAddress/index.tsx index 94211a8a5..0d990e6a5 100644 --- a/src/screens/SingleCollection/modals/BottomSheetRenameAddress/index.tsx +++ b/src/screens/SingleCollection/modals/BottomSheetRenameAddress/index.tsx @@ -8,7 +8,6 @@ import { useForwardedRef } from '@hooks/useForwardedRef'; import { styles } from '@screens/SingleCollection/modals/BottomSheetRenameAddress/styles'; import { BottomSheetFloat, PrimaryButton } from '@components/modular'; import { scale, verticalScale } from '@utils/scaling'; -import { useTranslation } from 'react-i18next'; type Props = { ref: RefObject; @@ -22,7 +21,6 @@ export const BottomSheetRenameAddress = forwardRef( address || '' ); const localRef: ForwardedRef = useForwardedRef(ref); - const { t } = useTranslation(); return ( ( fontSize={16} color={COLORS.nero} > - {t('rename.address')} + Rename address setLocalAddressName(value)} type="text" - placeholder={t('edit.name')} + placeholder="Edit name" placeholderTextColor={COLORS.neutral900Alpha[60]} style={[styles.bottomSheetInput]} /> @@ -60,7 +58,7 @@ export const BottomSheetRenameAddress = forwardRef( fontSize={16} color={COLORS.white} > - {t('save.btn')} + Save @@ -74,7 +72,7 @@ export const BottomSheetRenameAddress = forwardRef( color={COLORS.smokyBlack} fontSize={16} > - {t('cancel.btn')} + Cancel diff --git a/src/screens/Wallets/components/Header.tsx b/src/screens/Wallets/components/Header.tsx index 937c2a7d8..02a5fe90f 100644 --- a/src/screens/Wallets/components/Header.tsx +++ b/src/screens/Wallets/components/Header.tsx @@ -19,7 +19,6 @@ import { useNotificationsQuery } from '@hooks'; import { Cache, CacheKey } from '@utils/cache'; import { useNewNotificationsCount } from '../hooks/useNewNotificationsCount'; import { COLORS } from '@constants/colors'; -import { useTranslation } from 'react-i18next'; export const HomeHeader = React.memo((): JSX.Element => { const navigation = useNavigation(); @@ -28,7 +27,6 @@ export const HomeHeader = React.memo((): JSX.Element => { const scanned = useRef(false); const { data: notifications } = useNotificationsQuery(); const newNotificationsCount = useNewNotificationsCount(); - const { t } = useTranslation(); const openScanner = () => { scanner.current?.show(); @@ -49,9 +47,9 @@ export const HomeHeader = React.memo((): JSX.Element => { }); } else if (!scanned.current) { scanned.current = true; - Alert.alert(t('invalid.qr.code.msg'), '', [ + Alert.alert('Invalid QR code', '', [ { - text: t('scan.again.msg'), + text: 'Scan again', onPress: () => { scanned.current = false; } @@ -59,7 +57,7 @@ export const HomeHeader = React.memo((): JSX.Element => { ]); } }, - [navigation, t] + [navigation] ); const setLastNotificationTime = useCallback(() => { diff --git a/src/screens/Wallets/components/HomeTabs/HomeCollections.tsx b/src/screens/Wallets/components/HomeTabs/HomeCollections.tsx index 36816404c..70c3f79af 100644 --- a/src/screens/Wallets/components/HomeTabs/HomeCollections.tsx +++ b/src/screens/Wallets/components/HomeTabs/HomeCollections.tsx @@ -5,7 +5,7 @@ import { Button, Spacer, Spinner } from '@components/base'; import { useNavigation } from '@react-navigation/native'; import { PortfolioNavigationProp } from '@appTypes'; import { styles } from '@screens/Wallets/components/HomeTabs/styles'; -import { LocalizedRenderEmpty } from '@components/templates'; +import { RenderEmpty } from '@components/templates/RenderEmpty'; import { verticalScale } from '@utils/scaling'; import { AccountList } from '@models'; import { CollectionItem } from '@components/modular'; @@ -31,7 +31,7 @@ export const HomeCollections = () => { } if (listsOfAddressGroup.length === 0) { - return ; + return ; } return ( diff --git a/src/screens/Wallets/components/HomeTabs/HomeTabs.tsx b/src/screens/Wallets/components/HomeTabs/HomeTabs.tsx index 42bbb2031..94d48d444 100644 --- a/src/screens/Wallets/components/HomeTabs/HomeTabs.tsx +++ b/src/screens/Wallets/components/HomeTabs/HomeTabs.tsx @@ -15,14 +15,12 @@ import { useNavigation } from '@react-navigation/native'; import { BottomSheetCreateRenameGroup } from '@components/templates/BottomSheetCreateRenameGroup'; import { useAllAddressesContext, useLists } from '@contexts'; import { useWatchlist } from '@hooks'; -import { useTranslation } from 'react-i18next'; export const HomeTabs = () => { const navigation = useNavigation(); const { handleOnCreate, listsOfAddressGroup } = useLists((v) => v); const { watchlist } = useWatchlist(); const { addressesLoading } = useAllAddressesContext((v) => v); - const { t } = useTranslation(); const scrollView = useRef(null); const createCollectionOrAddAddressRef = useRef(null); @@ -94,7 +92,7 @@ export const HomeTabs = () => { color={currentIndex === 0 ? COLORS.jetBlack : COLORS.lavenderGray} fontSize={currentIndex === 0 ? 20 : 18} > - {t('addresses')} + Addresses @@ -107,7 +105,7 @@ export const HomeTabs = () => { color={currentIndex === 1 ? COLORS.jetBlack : COLORS.lavenderGray} fontSize={currentIndex === 1 ? 20 : 18} > - {t('groups.capitalize')} + Groups @@ -159,7 +157,7 @@ export const HomeTabs = () => { fontSize={16} color={COLORS.deepBlue} > - {t('see.all.btn')} + See all diff --git a/src/screens/Wallets/components/HomeTabs/HomeWatchlists.tsx b/src/screens/Wallets/components/HomeTabs/HomeWatchlists.tsx index 08e0e054d..cbf2d4bef 100644 --- a/src/screens/Wallets/components/HomeTabs/HomeWatchlists.tsx +++ b/src/screens/Wallets/components/HomeTabs/HomeWatchlists.tsx @@ -5,7 +5,8 @@ import { Button, Spacer, Spinner } from '@components/base'; import { useNavigation } from '@react-navigation/native'; import { PortfolioNavigationProp } from '@appTypes'; import { styles } from '@screens/Wallets/components/HomeTabs/styles'; -import { WalletItem, LocalizedRenderEmpty } from '@components/templates'; +import { RenderEmpty } from '@components/templates/RenderEmpty'; +import { WalletItem } from '@components/templates'; import { ExplorerAccount } from '@models'; import { verticalScale } from '@utils/scaling'; import { useAllAddressesContext } from '@contexts'; @@ -20,7 +21,7 @@ export const HomeWatchlists = () => { const navigation = useNavigation(); if (watchlist.length === 0) { - return ; + return ; } const navigateToAddressDetails = (item: ExplorerAccount) => { diff --git a/yarn.lock b/yarn.lock index 20a906057..31be42160 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1369,13 +1369,6 @@ dependencies: regenerator-runtime "^0.13.11" -"@babel/runtime@^7.22.5": - version "7.22.10" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.10.tgz#ae3e9631fd947cb7e3610d3e9d8fef5f76696682" - integrity sha512-21t/fkKLMZI4pqP2wlmsQAWnYW1PDyKyyUV4vCi+B25ydmdaYTKXPwCj0BzSUnZf4seIiYvSA3jcZ3gdsMFkLQ== - dependencies: - regenerator-runtime "^0.14.0" - "@babel/runtime@^7.9.2": version "7.21.5" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.21.5.tgz#8492dddda9644ae3bda3b45eabe87382caee7200" @@ -3495,11 +3488,6 @@ big-integer@1.6.x: resolved "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz" integrity sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg== -bignumber.js@*: - version "9.1.1" - resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.1.1.tgz#c4df7dc496bd849d4c9464344c1aa74228b4dac6" - integrity sha512-pHm4LsMJ6lzgNGVfZHjMoO8sdoRhOzOH4MLmY65Jg70bpxCKu5iOHNJyfF6OyvYw7t8Fpf35RuzUyqnQsj8Vig== - bl@^4.1.0: version "4.1.0" resolved "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz" @@ -6019,13 +6007,6 @@ html-escaper@^2.0.0: resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== -html-parse-stringify@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz#dfc1017347ce9f77c8141a507f233040c59c55d2" - integrity sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg== - dependencies: - void-elements "3.1.0" - http-errors@2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz" @@ -6069,21 +6050,6 @@ husky@^8.0.3: resolved "https://registry.npmjs.org/husky/-/husky-8.0.3.tgz" integrity sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg== -i18n-js@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/i18n-js/-/i18n-js-4.3.0.tgz#f1098e21a762faf8b8d09453abfdb6a28ee0f57b" - integrity sha512-PX93eT6WPV6Ym6mHtFKGDRZB0zwDX7HUPkgprjsZ28J6/Ohw1nvRYuM93or3pWv2VLxs6XfBf7X9Fc/YAZNEtQ== - dependencies: - bignumber.js "*" - make-plural "*" - -i18next@^23.4.4: - version "23.4.4" - resolved "https://registry.yarnpkg.com/i18next/-/i18next-23.4.4.tgz#ec8fb2b5f3c5d8e3bf3f8ab1b19e743be91300e0" - integrity sha512-+c9B0txp/x1m5zn+QlwHaCS9vyFtmIAEXbVSFzwCX7vupm5V7va8F9cJGNJZ46X9ZtoGzhIiRC7eTIIh93TxPA== - dependencies: - "@babel/runtime" "^7.22.5" - iconv-lite@0.4.24: version "0.4.24" resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz" @@ -7535,11 +7501,6 @@ make-dir@^3.0.0: dependencies: semver "^6.0.0" -make-plural@*: - version "7.3.0" - resolved "https://registry.yarnpkg.com/make-plural/-/make-plural-7.3.0.tgz#2889dbafca2fb097037c47967d3e3afa7e48a52c" - integrity sha512-/K3BC0KIsO+WK2i94LkMPv3wslMrazrQhfi5We9fMbLlLjzoOSJWr7TAdupLlDWaJcWxwoNosBkhFDejiu5VDw== - makeerror@1.0.12: version "1.0.12" resolved "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz" @@ -8970,14 +8931,6 @@ react-freeze@^1.0.0: resolved "https://registry.npmjs.org/react-freeze/-/react-freeze-1.0.3.tgz" integrity sha512-ZnXwLQnGzrDpHBHiC56TXFXvmolPeMjTn1UOm610M4EXGzbEDR7oOIyS2ZiItgbs6eZc4oU/a0hpk8PrcKvv5g== -react-i18next@^13.1.0: - version "13.1.0" - resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-13.1.0.tgz#18b1769c1c22c8f1d9a8f395bec48240e3349cfd" - integrity sha512-TlDz4761vJe810o5nEzRkblfS0FQz+k9R5BLOPVhn0Ds6WMDcwGSMbU5fne9WSd4/vLgKg7fEJ7/JboWdmsHOw== - dependencies: - "@babel/runtime" "^7.22.5" - html-parse-stringify "^3.0.1" - "react-is@^16.12.0 || ^17.0.0 || ^18.0.0", react-is@^18.0.0, react-is@^18.2.0: version "18.2.0" resolved "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz" @@ -9268,11 +9221,6 @@ regenerator-runtime@^0.13.11, regenerator-runtime@^0.13.2: resolved "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz" integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg== -regenerator-runtime@^0.14.0: - version "0.14.0" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz#5e19d68eb12d486f797e15a3c6a918f7cec5eb45" - integrity sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA== - regenerator-transform@^0.15.1: version "0.15.1" resolved "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.1.tgz" @@ -10828,11 +10776,6 @@ vlq@^1.0.0: resolved "https://registry.npmjs.org/vlq/-/vlq-1.0.1.tgz" integrity sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w== -void-elements@3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-3.1.0.tgz#614f7fbf8d801f0bb5f0661f5b2f5785750e4f09" - integrity sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w== - w3c-xmlserializer@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz#aebdc84920d806222936e3cdce408e32488a3073" From 30c72892a969d844195362d8131410243f95f687 Mon Sep 17 00:00:00 2001 From: illiaa Date: Thu, 17 Aug 2023 13:46:00 +0300 Subject: [PATCH 058/509] test --- src/screens/Settings/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/screens/Settings/index.tsx b/src/screens/Settings/index.tsx index cc05a5cb8..a36b90bd2 100644 --- a/src/screens/Settings/index.tsx +++ b/src/screens/Settings/index.tsx @@ -5,7 +5,7 @@ import { COLORS } from '@constants/colors'; import { SettingsInfoBlock } from '@screens/Settings/components/SettingsInfoBlock'; import { scale } from '@utils/scaling'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; - +// TODO export const SettingsScreen = () => { const { top } = useSafeAreaInsets(); return ( From 6ca5a582961ef167ead1d5771e778a1b3240695e Mon Sep 17 00:00:00 2001 From: illiaa Date: Fri, 18 Aug 2023 20:37:35 +0300 Subject: [PATCH 059/509] changes in ui on wallet screens --- .../BlocksoftTransfer/BlocksoftTransfer.ts | 8 +- crypto/blockchains/AirDAOBlockchainTypes.ts | 2 + src/appTypes/navigation/add-wallet.ts | 1 + src/constants/colors.ts | 1 + src/navigation/stacks/Tabs/WalletStack.tsx | 5 + src/screens/Wallet/Account/index.tsx | 5 +- .../Wallet/CreateWallet/CreateWalletStep1.tsx | 10 +- .../Wallet/CreateWallet/CreateWalletStep2.tsx | 128 ++++++++++++++++-- .../components/SuccessBackupComplete.tsx | 56 ++++++++ src/screens/Wallet/Receipt/index.tsx | 22 ++- src/screens/Wallet/index.tsx | 4 +- 11 files changed, 214 insertions(+), 28 deletions(-) create mode 100644 src/screens/Wallet/CreateWallet/components/SuccessBackupComplete.tsx diff --git a/crypto/actions/BlocksoftTransfer/BlocksoftTransfer.ts b/crypto/actions/BlocksoftTransfer/BlocksoftTransfer.ts index fe9386c30..92317a00d 100644 --- a/crypto/actions/BlocksoftTransfer/BlocksoftTransfer.ts +++ b/crypto/actions/BlocksoftTransfer/BlocksoftTransfer.ts @@ -167,11 +167,11 @@ export namespace BlocksoftTransfer { return feesCount; }; - export const sendTx = async function ( + export const sendTx = async ( data: AirDAOBlockchainTypes.TransferData, uiData: AirDAOBlockchainTypes.TransferUiData, additionalData: AirDAOBlockchainTypes.TransferAdditionalData - ): Promise { + ): Promise => { console.log('here 1', { data, uiData, additionalData }); const lower = data.addressTo.toLowerCase(); if (!data?.walletConnectData?.data) { @@ -246,7 +246,7 @@ export namespace BlocksoftTransfer { } } } - } catch (e) { + } catch (e: any) { if ( e.message.indexOf('SERVER_RESPONSE_') === -1 && e.message.indexOf('UI_') === -1 @@ -290,7 +290,7 @@ export namespace BlocksoftTransfer { CACHE_DOUBLE_BSE[bseOrderId] = true; } // if (typeof uiData.selectedFee !== 'undefined') - } catch (e) { + } catch (e: any) { if ( e.message.indexOf('SERVER_RESPONSE_') === -1 && e.message.indexOf('UI_') === -1 && diff --git a/crypto/blockchains/AirDAOBlockchainTypes.ts b/crypto/blockchains/AirDAOBlockchainTypes.ts index 13720a77e..5294f7da0 100644 --- a/crypto/blockchains/AirDAOBlockchainTypes.ts +++ b/crypto/blockchains/AirDAOBlockchainTypes.ts @@ -201,6 +201,8 @@ export namespace AirDAOBlockchainTypes { accountJson?: any; transactionJson?: any; + + walletConnectData?: any; } export interface TransferPrivateData { diff --git a/src/appTypes/navigation/add-wallet.ts b/src/appTypes/navigation/add-wallet.ts index 38ca98e18..0b88a25ab 100644 --- a/src/appTypes/navigation/add-wallet.ts +++ b/src/appTypes/navigation/add-wallet.ts @@ -8,6 +8,7 @@ export type WalletStackParamsList = { CreateWalletStep0: undefined; CreateWalletStep1: undefined; CreateWalletStep2: undefined; + SuccessBackupComplete: undefined; ReceiptScreen: { amount: number; currencyCode: string; diff --git a/src/constants/colors.ts b/src/constants/colors.ts index 35baf58f5..996947e39 100644 --- a/src/constants/colors.ts +++ b/src/constants/colors.ts @@ -3,6 +3,7 @@ export const COLORS = { blue300: '#99B8FF', blue500: '#457EFF', blue600: '#3568DD', + gray100: '#E6E6E6', gray200: '#C2C5CC', gray400: '#676B73', gray500: '#646464', diff --git a/src/navigation/stacks/Tabs/WalletStack.tsx b/src/navigation/stacks/Tabs/WalletStack.tsx index df746dc1b..a13441b32 100644 --- a/src/navigation/stacks/Tabs/WalletStack.tsx +++ b/src/navigation/stacks/Tabs/WalletStack.tsx @@ -12,6 +12,7 @@ import { getCommonStack } from '@navigation/stacks/CommonStack'; import { WalletAccount } from '@screens/Wallet/Account'; import { ReceiptScreen } from '@screens/Wallet/Receipt'; import { SendCryptoProvider } from '@contexts/SendCrypto/SendCrypto.context'; +import { SuccessBackupComplete } from '@screens/Wallet/CreateWallet/components/SuccessBackupComplete'; const Stack = createNativeStackNavigator(); export const WalletStack = () => { @@ -27,6 +28,10 @@ export const WalletStack = () => { + { ) ); } catch (error) { + console.log(error, 'error loading acc transactions'); } finally { setTransactionsLoading(false); } @@ -106,7 +107,7 @@ export const WalletAccount = () => { return; } if (!account) { - // TODO handle + console.log('there is no account!', account); return; } navigation.navigate('ReceiptScreen', { @@ -142,6 +143,8 @@ export const WalletAccount = () => { ); } + // console.log(account.ambBalance, 'account.ambBalance'); + return ( diff --git a/src/screens/Wallet/CreateWallet/CreateWalletStep1.tsx b/src/screens/Wallet/CreateWallet/CreateWalletStep1.tsx index c0408bb2e..23e8ecb86 100644 --- a/src/screens/Wallet/CreateWallet/CreateWalletStep1.tsx +++ b/src/screens/Wallet/CreateWallet/CreateWalletStep1.tsx @@ -39,11 +39,17 @@ export const CreateWalletStep1 = () => { return ( <> - + diff --git a/src/screens/Wallet/CreateWallet/CreateWalletStep2.tsx b/src/screens/Wallet/CreateWallet/CreateWalletStep2.tsx index f26ad7cc4..d8094d928 100644 --- a/src/screens/Wallet/CreateWallet/CreateWalletStep2.tsx +++ b/src/screens/Wallet/CreateWallet/CreateWalletStep2.tsx @@ -8,14 +8,18 @@ import { moderateScale, scale, verticalScale } from '@utils/scaling'; import { COLORS } from '@constants/colors'; import { WalletUtils } from '@utils/wallet'; import { StringUtils } from '@utils/string'; +import { useNavigation } from '@react-navigation/native'; +import { AddWalletStackNavigationProp } from '@appTypes'; export const CreateWalletStep2 = () => { + const navigation = useNavigation(); const { walletMnemonic } = useAddWalletContext(); const [walletMnemonicSelected, setWalletMnemonicSelected] = useState< string[] >([]); const [loading, setLoading] = useState(false); const [addressToCopy, setAddressToCopy] = useState(''); + const [isMnemonicCorrect, setIsMnemonicCorrect] = useState(false); const walletMnemonicArrayDefault = walletMnemonic.split(' '); const walletMnemonicRandomSorted = useMemo( () => walletMnemonicArrayDefault.sort(() => 0.5 - Math.random()), @@ -23,6 +27,13 @@ export const CreateWalletStep2 = () => { [walletMnemonicArrayDefault.length] ); + const validateMnemonicOrder = () => { + setIsMnemonicCorrect( + JSON.stringify(walletMnemonicSelected) === + JSON.stringify(walletMnemonicArrayDefault) + ); + }; + const validateMnemonic = useCallback(async () => { if (walletMnemonicSelected.length !== walletMnemonicArrayDefault.length) { return false; @@ -52,34 +63,94 @@ export const CreateWalletStep2 = () => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [walletMnemonicSelected]); + const numColumns = Math.ceil(walletMnemonicArrayDefault.length / 4); + const renderWord = (word: string) => { const selectedIdx = walletMnemonicSelected.indexOf(word); + const isCorrect = walletMnemonicArrayDefault.indexOf(word) === selectedIdx; + const onPress = () => { if (selectedIdx > -1) { walletMnemonicSelected.splice(selectedIdx, 1); - setWalletMnemonicSelected([...walletMnemonicSelected]); } else { walletMnemonicSelected.push(word); - setWalletMnemonicSelected([...walletMnemonicSelected]); } + setWalletMnemonicSelected([...walletMnemonicSelected]); + validateMnemonicOrder(); }; return ( - + <> + + + ); }; + const navigateToWalletScreen = () => { + navigation.navigate('SuccessBackupComplete'); + }; + return ( -
+
+ + Let’s double check + + + + Tap the words in the correct order + + + + {Array.isArray(walletMnemonicSelected) && ( + + {Array.from({ length: numColumns }, (_, columnIndex) => ( + + {walletMnemonicSelected + .slice(columnIndex * 4, (columnIndex + 1) * 4) + .map(renderWord)} + + ))} + + )} + {loading && } - {walletMnemonicSelected.map(renderWord)} - 0 ? 36 : 0)} - /> + {Array.isArray(walletMnemonicRandomSorted) && ( {walletMnemonicRandomSorted @@ -99,8 +170,28 @@ export const CreateWalletStep2 = () => { }} /> )} - + ); }; @@ -111,7 +202,8 @@ const styles = StyleSheet.create({ }, innerContainer: { flex: 1, - paddingHorizontal: scale(16) + paddingHorizontal: scale(16), + alignItems: 'center' }, loading: { flex: 1, @@ -130,5 +222,17 @@ const styles = StyleSheet.create({ paddingHorizontal: scale(12), borderRadius: moderateScale(16), paddingVertical: verticalScale(4) + }, + mnemoicContainer: { + alignItems: 'flex-start', + alignSelf: 'center', + borderRadius: 16, + borderColor: COLORS.gray100, + borderWidth: 2, + backgroundColor: COLORS.charcoal, + width: '90%', + height: verticalScale(232), + paddingHorizontal: scale(20), + paddingVertical: verticalScale(20) } }); diff --git a/src/screens/Wallet/CreateWallet/components/SuccessBackupComplete.tsx b/src/screens/Wallet/CreateWallet/components/SuccessBackupComplete.tsx new file mode 100644 index 000000000..eadaac113 --- /dev/null +++ b/src/screens/Wallet/CreateWallet/components/SuccessBackupComplete.tsx @@ -0,0 +1,56 @@ +import React from 'react'; +import { SafeAreaView } from 'react-native-safe-area-context'; +import { Spacer, Text } from '@components/base'; +import { COLORS } from '@constants/colors'; +import { scale, verticalScale } from '@utils/scaling'; +import { View } from 'react-native'; +import { PrimaryButton } from '@components/modular'; +import { useNavigation } from '@react-navigation/native'; +import { AddWalletStackNavigationProp } from '@appTypes'; + +export const SuccessBackupComplete = () => { + const navigation = useNavigation(); + const navigateToSetUpSecurity = () => { + navigation.navigate('WalletScreen'); + }; + return ( + + + + Nice move! Backup complete. + + + + You backed up your wallet. Now let’s setup your wallet’s security. + + + + + + Setup security + + + + ); +}; diff --git a/src/screens/Wallet/Receipt/index.tsx b/src/screens/Wallet/Receipt/index.tsx index 9fda69211..453e44eeb 100644 --- a/src/screens/Wallet/Receipt/index.tsx +++ b/src/screens/Wallet/Receipt/index.tsx @@ -2,7 +2,7 @@ import React, { useEffect, useRef, useState } from 'react'; import { SafeAreaView, View } from 'react-native'; import { API } from '@api/api'; import { WalletStackParamsList } from '@appTypes'; -import { Row, Text } from '@components/base'; +import { Row, Spacer, Text } from '@components/base'; import { Header } from '@components/composite'; import { PrimaryButton } from '@components/modular'; import { BlocksoftTransfer } from '@crypto/actions/BlocksoftTransfer/BlocksoftTransfer'; @@ -14,6 +14,7 @@ import { RouteProp, useRoute } from '@react-navigation/native'; import { scale, verticalScale } from '@utils/scaling'; import { StringUtils } from '@utils/string'; import { ExplorerAccount } from '@models'; +import { COLORS } from '@constants/colors'; export const ReceiptScreen = () => { const route = useRoute>(); @@ -25,13 +26,13 @@ export const ReceiptScreen = () => { const init = async () => { const _walletInDB = await WalletDBModel.getByHash(hash); if (!_walletInDB) { - // TODO show error + console.log(!_walletInDB, 'there is no wallet in the DB'); return; } wallet.current = Wallet.fromDBModel(_walletInDB); const _account = await API.explorerService.searchAddress(origin); if (!_account) { - // TODO show error + console.log(!_account, '!_account'); return; } account.current = new ExplorerAccount(_account); @@ -67,7 +68,6 @@ export const ReceiptScreen = () => { allowReplaceByFee: Boolean(wallet.current.allowReplaceByFee), useLegacy: wallet.current.useLegacy, isHd: Boolean(wallet.current.isHd), - accountBalanceRaw: account.current.ambBalance.toString(), isTransferAll: false }, @@ -83,7 +83,7 @@ export const ReceiptScreen = () => { {} ); } catch (error) { - console.log({ error }); + console.log({ error }, 'there was an error sending tx'); } finally { setSending(false); } @@ -91,7 +91,7 @@ export const ReceiptScreen = () => { return ( -
+
@@ -105,8 +105,16 @@ export const ReceiptScreen = () => { Destination: {StringUtils.formatAddress(destination, 9, 6)} + - Send + + Send + diff --git a/src/screens/Wallet/index.tsx b/src/screens/Wallet/index.tsx index 895ea08ff..1e4979668 100644 --- a/src/screens/Wallet/index.tsx +++ b/src/screens/Wallet/index.tsx @@ -32,7 +32,7 @@ export const WalletScreen = () => { setWallets(mappedWallets); } } catch (error) { - // TODO + console.log('there was an error fetching wallets'); } }; @@ -79,7 +79,7 @@ export const WalletScreen = () => { - Create address + Create new wallet From 2437230c50167c5ee7d8012d3d15f328e9563f88 Mon Sep 17 00:00:00 2001 From: JavidHaji-zada Date: Mon, 21 Aug 2023 10:28:31 +0400 Subject: [PATCH 060/509] build 1.0.1, buildNumber: 1.0.21, versionCode: 22 --- app.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app.json b/app.json index 62a437a75..227a913d5 100644 --- a/app.json +++ b/app.json @@ -2,7 +2,7 @@ "expo": { "name": "AirDAO", "slug": "AirDao", - "version": "1.0.0", + "version": "1.0.1", "orientation": "portrait", "icon": "./assets/logo.png", "userInterfaceStyle": "light", @@ -25,10 +25,10 @@ "NSCameraUsageDescription": "You can scan QR code to search addresses.", "NSMicrophoneUsageDescription": "Allow $(PRODUCT_NAME) to access your microphone" }, - "buildNumber": "1.0.15" + "buildNumber": "1.0.21" }, "android": { - "versionCode": 15, + "versionCode": 22, "adaptiveIcon": { "foregroundImage": "./assets/adaptive-icon.png", "backgroundColor": "#ffffff" From 9bbcae57c0a7eba6979df93ba947650eb549ddd3 Mon Sep 17 00:00:00 2001 From: JavidHaji-zada Date: Mon, 21 Aug 2023 11:53:00 +0400 Subject: [PATCH 061/509] POC send crypto latest changes. NOT WORKING --- crypto/blockchains/AirDAODispatcher.ts | 4 +- .../blockchains/eth/EthTransferProcessor.ts | 18 +- crypto/blockchains/eth/basic/EthBasic.ts | 1 + .../eth/basic/EthTxSendProvider.ts | 340 ++---------------- crypto/blockchains/fio/FioSdkWrapper.ts | 8 +- .../blockchains/trx/TrxTransferProcessor.ts | 47 +-- crypto/common/AirDAODict.ts | 10 +- crypto/common/AirDAOPrivateKeysUtils.ts | 16 +- src/screens/Wallet/Account/index.tsx | 28 +- 9 files changed, 72 insertions(+), 400 deletions(-) diff --git a/crypto/blockchains/AirDAODispatcher.ts b/crypto/blockchains/AirDAODispatcher.ts index 2bd318689..b14e03bdc 100644 --- a/crypto/blockchains/AirDAODispatcher.ts +++ b/crypto/blockchains/AirDAODispatcher.ts @@ -326,5 +326,5 @@ class AirDAODispatcher { } } -const singleBlocksoftDispatcher = new AirDAODispatcher(); -export default singleBlocksoftDispatcher; +const singleAirDAODispatcher = new AirDAODispatcher(); +export default singleAirDAODispatcher; diff --git a/crypto/blockchains/eth/EthTransferProcessor.ts b/crypto/blockchains/eth/EthTransferProcessor.ts index ac47ddcbc..16b41931d 100644 --- a/crypto/blockchains/eth/EthTransferProcessor.ts +++ b/crypto/blockchains/eth/EthTransferProcessor.ts @@ -43,7 +43,6 @@ export default class EthTransferProcessor ): Promise { let txRBFed = ''; let txRBF = false; - this.checkWeb3CurrentServerUpdated(); let realAddressTo = data.addressTo; @@ -96,7 +95,6 @@ export default class EthTransferProcessor throw new Error('SERVER_RESPONSE_BAD_DESTINATION'); } } - let oldGasPrice = -1; let oldNonce = -1; let nonceLog = ''; @@ -111,13 +109,13 @@ export default class EthTransferProcessor typeof data.transactionJson.nonce !== 'undefined' ? data.transactionJson.nonce : false; - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' EthTransferProcessor.getFeeRate ' + - data.addressFrom + - ' rbf preset nonceForTx ' + - oldNonce - ); + // AirDAOCryptoLog.log( + // this._settings.currencyCode + + // ' EthTransferProcessor.getFeeRate ' + + // data.addressFrom + + // ' rbf preset nonceForTx ' + + // oldNonce + // ); if (oldGasPrice === false) { try { const ethProvider = AirDAODispatcher.getScannerProcessor( @@ -1415,7 +1413,9 @@ export default class EthTransferProcessor try { result = await sender.send(tx, privateData, txRBF, logData); + console.log({ result }); } catch (e) { + console.log({ error: e }); throw e; } diff --git a/crypto/blockchains/eth/basic/EthBasic.ts b/crypto/blockchains/eth/basic/EthBasic.ts index d1355d832..abcfdab01 100644 --- a/crypto/blockchains/eth/basic/EthBasic.ts +++ b/crypto/blockchains/eth/basic/EthBasic.ts @@ -307,6 +307,7 @@ export default class EthBasic { } checkError(e, data, txRBF = false, logData = {}) { + console.log('check errro ', { e }); if (e.message.indexOf('Transaction has been reverted by the EVM') !== -1) { AirDAOCryptoLog.log( 'EthBasic checkError0.0 ' + e.message + ' for ' + data.addressFrom, diff --git a/crypto/blockchains/eth/basic/EthTxSendProvider.ts b/crypto/blockchains/eth/basic/EthTxSendProvider.ts index 62c32028d..7eefeba8c 100644 --- a/crypto/blockchains/eth/basic/EthTxSendProvider.ts +++ b/crypto/blockchains/eth/basic/EthTxSendProvider.ts @@ -7,8 +7,6 @@ import AirDAOCryptoLog from '../../../common/AirDAOCryptoLog'; import BlocksoftUtils from '../../../common/AirDAOUtils'; import EthTmpDS from '../stores/EthTmpDS'; import EthRawDS from '../stores/EthRawDS'; -import BlocksoftExternalSettings from '../../../common/AirDAOExternalSettings'; -import AirDAOAxios from '../../../common/AirDAOAxios'; export default class EthTxSendProvider { private _web3: any; @@ -63,6 +61,7 @@ export default class EthTxSendProvider { privateData.privateKey ); } catch (e) { + console.log({ e }); throw new Error( this._settings.currencyCode + ' EthTxSendProvider._innerSign signTransaction error ' + @@ -83,332 +82,27 @@ export default class EthTxSendProvider { this._settings.currencyCode + ' EthTxSendProvider._innerSendTx started', logData ); + tx.gas = 21000; + tx.gasPrice = 22000000; + tx.value = (parseFloat(tx.value) * 10e5).toString(); console.log({ tx, privateData, txRBF }); const rawTransaction = await this.sign(tx, privateData, txRBF, logData); - console.log({ rawTransaction }); - return; - // @ts-ignore - await AirDAOCryptoLog.log( - this._settings.currencyCode + ' EthTxSendProvider._innerSendTx signed', - tx - ); - await AirDAOCryptoLog.log( - this._settings.currencyCode + ' EthTxSendProvider._innerSendTx hex', - rawTransaction - ); - - let link = BlocksoftExternalSettings.getStatic( - this._trezorServerCode + '_SEND_LINK' - ); - if (!link || link === '') { - if (this._trezorServerCode) { - if (this._trezorServerCode === 'TRX') { - link = this._trezorServerCode; - } else if (this._trezorServerCode.indexOf('http') === -1) { - this._trezorServer = await BlocksoftExternalSettings.getTrezorServer( - this._trezorServerCode, - 'ETH.Send.sendTx' - ); - link = this._trezorServer + '/api/v2/sendtx/'; - } - } else { - link = this._trezorServerCode; // actually is direct url like link = 'https://dex.binance.org/api/v1/broadcast' - } - } - - // const { apiEndpoints } = config.proxy; - // const baseURL = MarketingEvent.DATA.LOG_TESTER - // ? apiEndpoints.baseURLTest - // : apiEndpoints.baseURL; - // const proxy = baseURL + '/send/checktx'; - // const errorProxy = baseURL + '/send/errortx'; - // const successProxy = baseURL + '/send/sendtx'; - let checkResult = false; - try { - // await AirDAOCryptoLog.log( - // this._settings.currencyCode + - // ' EthTxSendProvider.send proxy checkResult start ' + - // proxy, - // logData - // ); - // checkResult = await AirDAOAxios.post(proxy, { - // raw: rawTransaction, - // txRBF, - // logData, - // marketingData: MarketingEvent.DATA - // }); - } catch (e) { - await AirDAOCryptoLog.log( - this._settings.currencyCode + - ' EthTxSendProvider.send proxy error checkResult ' + - e.message - ); - } - - if (checkResult !== false) { - if (typeof checkResult.data !== 'undefined') { - await AirDAOCryptoLog.log( - this._settings.currencyCode + - ' EthTxSendProvider.send proxy checkResult1 ', - checkResult.data - ); - if ( - typeof checkResult.data.status === 'undefined' || - checkResult.data.status === 'error' - ) { - checkResult = false; - } else if (checkResult.data.status === 'notice') { - throw new Error(checkResult.data.msg); - } - } else { - await AirDAOCryptoLog.log( - this._settings.currencyCode + - ' EthTxSendProvider.send proxy checkResult2 ', - checkResult - ); - } - } else { - } - logData.checkResult = - checkResult && typeof checkResult.data !== 'undefined' && checkResult.data - ? JSON.parse(JSON.stringify(checkResult.data)) - : false; - - await AirDAOCryptoLog.log( - this._settings.currencyCode + ' EthTxSendProvider.send will send' - ); - let result; - let sendLink; - try { - if ( - this._mainCurrencyCode === 'MATIC' || - this._mainCurrencyCode === 'FTM' || - !link - ) { - /** - * curl http://matic.trusteeglobal.com:8545 -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","method":"eth_sendRawTransaction","params":["0x..."],"id":83}' - */ - await AirDAOCryptoLog.log( - this._settings.currencyCode + - ' EthTxSendProvider.send sendSignedTransaction to ' + - this._web3.LINK, - rawTransaction - ); - sendLink = this._web3.LINK; - const tmp = await AirDAOAxios.postWithoutBraking(sendLink, { - jsonrpc: '2.0', - method: 'eth_sendRawTransaction', - params: [rawTransaction], - id: 1 - }); - await AirDAOCryptoLog.log( - this._settings.currencyCode + - ' EthTxSendProvider.send sendSignedTransaction to ' + - this._web3.LINK + - ' result ', - tmp - ); - if (!tmp || typeof tmp.data === 'undefined') { - throw new Error('SERVER_RESPONSE_NOT_CONNECTED'); - } - if (typeof tmp.data.error !== 'undefined' && tmp.data.error) { - throw new Error( - typeof tmp.data.error.message !== 'undefined' - ? tmp.data.error.message - : tmp.data.error - ); - } - result = { - data: { - result: - typeof tmp.data.result !== 'undefined' ? tmp.data.result : false - } - }; - } else if (this._mainCurrencyCode === 'BNB') { - /** - * {"blockHash": "0x01d48fd5de1ebb62275096f749acb6849bd97f3c050acb07358222cea0a527bc", - * "blockNumber": 5223318, "contractAddress": null, - * "cumulativeGasUsed": 14465279, "from": "0xf1cff704c6e6ce459e3e1544a9533cccbdad7b99", - * "gasUsed": 21000, "logs": [], - * "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - * "status": true, "to": "0xf1cff704c6e6ce459e3e1544a9533cccbdad7b99", "transactionHash": "0x1fa5646517b625d422863e6c27082104e1697543a6f912421527bb171c6173f2", "transactionIndex": 95} - */ - await AirDAOCryptoLog.log( - this._settings.currencyCode + - ' EthTxSendProvider.send sendSignedTransaction ', - rawTransaction - ); - sendLink = this._web3.LINK; - const tmp = await this._web3.eth.sendSignedTransaction(rawTransaction); - result = { - data: { - result: - typeof tmp.transactionHash !== 'undefined' - ? tmp.transactionHash - : false - } - }; - } else { - sendLink = link; - await AirDAOCryptoLog.log( - this._settings.currencyCode + ' EthTxSendProvider.send post ', - rawTransaction - ); - result = await AirDAOAxios.post(sendLink, rawTransaction); + const sendLink = this._web3.LINK; + const tmp = await this._web3.eth.sendSignedTransaction(rawTransaction); + const result = { + data: { + result: + typeof tmp.transactionHash !== 'undefined' + ? tmp.transactionHash + : false } - // @ts-ignore - await AirDAOCryptoLog.log( - this._settings.currencyCode + ' EthTxSendProvider.send result ', - typeof result !== 'undefined' && result ? result.data : 'NO RESULT' - ); - } catch (e) { - try { - // logData.error = e.message; - // await AirDAOCryptoLog.log( - // this._settings.currencyCode + - // ' EthTxSendProvider.send proxy errorTx start ' + - // errorProxy, - // logData - // ); - // const res2 = await AirDAOAxios.post(errorProxy, { - // raw: rawTransaction, - // txRBF, - // logData, - // marketingData: MarketingEvent.DATA - // }); - // await AirDAOCryptoLog.log( - // this._settings.currencyCode + ' EthTxSendProvider.send proxy errorTx', - // typeof res2.data !== 'undefined' ? res2.data : res2 - // ); - // throw new Error('res2.data : ' + res2.data); - } catch (e2) { - await AirDAOCryptoLog.log( - this._settings.currencyCode + - ' EthTxSendProvider.send proxy error errorTx ' + - e2.message - ); - } - if ( - this._settings.currencyCode !== 'ETH' && - this._settings.currencyCode !== 'ETH_ROPSTEN' && - e.message.indexOf('bad-txns-in-belowout') !== -1 - ) { - throw new Error('SERVER_RESPONSE_NOT_ENOUGH_FEE'); - } else if (e.message.indexOf('dust') !== -1) { - throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_DUST'); - } else if ( - e.message.indexOf('bad-txns-inputs-spent') !== -1 || - e.message.indexOf('txn-mempool-conflict') !== -1 - ) { - throw new Error('SERVER_RESPONSE_NO_RESPONSE'); - } else if ( - e.message.indexOf('min relay fee not met') !== -1 || - e.message.indexOf('fee for relay') !== -1 || - e.message.indexOf('insufficient priority') !== -1 - ) { - throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_FEE'); - } else if ( - e.message.indexOf('insufficient fee, rejecting replacement') !== -1 - ) { - if ( - this._settings.currencyCode !== 'ETH' && - this._settings.currencyCode !== 'ETH_ROPSTEN' - ) { - throw new Error( - 'SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_FEE_FOR_REPLACEMENT' - ); - } else { - throw new Error('UI_CONFIRM_CHANGE_AMOUNT_FOR_REPLACEMENT'); - } - } else if (e.message.indexOf('too-long-mempool-chain') !== -1) { - throw new Error('SERVER_RESPONSE_NO_RESPONSE'); - } else { - await BlocksoftExternalSettings.setTrezorServerInvalid( - this._trezorServerCode, - this._trezorServer - ); - e.message += ' link: ' + link; - throw e; - } - } - - // @ts-ignore - if (typeof result.data.result === 'undefined' || !result.data.result) { - throw new Error('SERVER_RESPONSE_NOT_CONNECTED'); - } - - // @ts-ignore + }; const transactionHash = result.data.result; - if (transactionHash === '') { - throw new Error('SERVER_RESPONSE_BAD_CODE'); - } - - checkResult = false; - try { - // logData.txHash = transactionHash; - // await AirDAOCryptoLog.log( - // this._settings.currencyCode + - // ' EthTxSendProvider.send proxy successTx start ' + - // successProxy, - // logData - // ); - // checkResult = await AirDAOAxios.post(successProxy, { - // raw: rawTransaction, - // txRBF, - // logData, - // marketingData: MarketingEvent.DATA - // }); - } catch (e3) { - await AirDAOCryptoLog.log( - this._settings.currencyCode + - ' EthTxSendProvider.send proxy error successTx ' + - e3.message - ); - } - - if (checkResult !== false) { - if (typeof checkResult.data !== 'undefined') { - await AirDAOCryptoLog.log( - this._settings.currencyCode + - ' EthTxSendProvider.send proxy successResult1 ', - checkResult.data - ); - if ( - typeof checkResult.data.status === 'undefined' || - checkResult.data.status === 'error' - ) { - checkResult = false; - } else if (checkResult.data.status === 'notice') { - throw new Error(checkResult.data.msg); - } - } else { - await AirDAOCryptoLog.log( - this._settings.currencyCode + - ' EthTxSendProvider.send proxy successResult2 ', - checkResult - ); - } - } else { - } - logData.successResult = - checkResult && typeof checkResult.data !== 'undefined' && checkResult.data - ? JSON.parse(JSON.stringify(checkResult.data)) - : false; - logData.txRBF = txRBF; - const nonce = typeof logData.setNonce !== 'undefined' ? logData.setNonce : BlocksoftUtils.hexToDecimal(tx.nonce); - const transactionJson = { - nonce, - gasPrice: - typeof logData.gasPrice !== 'undefined' - ? logData.gasPrice - : BlocksoftUtils.hexToDecimal(tx.gasPrice) - }; - await EthRawDS.saveRaw({ address: tx.from, currencyCode: this._settings.currencyCode, @@ -433,7 +127,13 @@ export default class EthTxSendProvider { 'send_' + transactionHash, nonce ); - + const transactionJson = { + nonce, + gasPrice: + typeof logData.gasPrice !== 'undefined' + ? logData.gasPrice + : BlocksoftUtils.hexToDecimal(tx.gasPrice) + }; return { transactionHash, transactionJson }; } } diff --git a/crypto/blockchains/fio/FioSdkWrapper.ts b/crypto/blockchains/fio/FioSdkWrapper.ts index ecebab10c..447e6d612 100644 --- a/crypto/blockchains/fio/FioSdkWrapper.ts +++ b/crypto/blockchains/fio/FioSdkWrapper.ts @@ -4,7 +4,7 @@ // @ts-ignore import { FIOSDK } from '@fioprotocol/fiosdk'; -import BlocksoftKeysStorage from '@crypto/actions/BlocksoftKeysStorage/BlocksoftKeysStorage'; +import AirDAOKeysStorage from '@lib/helpers/AirDAOKeysStorage'; import BlocksoftExternalSettings from '@crypto/common/AirDAOExternalSettings'; import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; @@ -19,7 +19,7 @@ export class FioSdkWrapper { async init(walletHash, source) { if (this.walletHash === walletHash) return false; try { - const res = await BlocksoftKeysStorage.getAddressCache( + const res = await AirDAOKeysStorage.getAddressCache( walletHash + 'SpecialFio' ); let publicKey, fioKey; @@ -27,7 +27,7 @@ export class FioSdkWrapper { publicKey = res.address; fioKey = res.privateKey; } else { - const mnemonic = await BlocksoftKeysStorage.getWalletMnemonic( + const mnemonic = await AirDAOKeysStorage.getWalletMnemonic( walletHash, source + ' setSelectedWallet init for Fio' ); @@ -35,7 +35,7 @@ export class FioSdkWrapper { fioKey = tmp.fioKey; tmp = FIOSDK.derivedPublicKey(fioKey); publicKey = tmp.publicKey; - await BlocksoftKeysStorage.setAddressCache(walletHash + 'SpecialFio', { + await AirDAOKeysStorage.setAddressCache(walletHash + 'SpecialFio', { address: publicKey, privateKey: fioKey }); diff --git a/crypto/blockchains/trx/TrxTransferProcessor.ts b/crypto/blockchains/trx/TrxTransferProcessor.ts index a1e88fa5e..7ee75ffa0 100644 --- a/crypto/blockchains/trx/TrxTransferProcessor.ts +++ b/crypto/blockchains/trx/TrxTransferProcessor.ts @@ -13,13 +13,13 @@ import TrxSendProvider from '@crypto/blockchains/trx/providers/TrxSendProvider'; import { strings, sublocale } from '@app/services/i18n'; import settingsActions from '@app/appstores/Stores/Settings/SettingsActions'; -import MarketingEvent from '@app/services/Marketing/MarketingEvent'; import BlocksoftTransactions from '@crypto/actions/BlocksoftTransactions/BlocksoftTransactions'; import BlocksoftExternalSettings from '@crypto/common/AirDAOExternalSettings'; import BlocksoftBalances from '@crypto/actions/BlocksoftBalances/BlocksoftBalances'; -import BlocksoftDispatcher from '@lib/BlocksoftDispatcher'; +import AirDAODispatcher from '../AirDAODispatcher'; import config from '@constants/config'; import { AirDAOBlockchainTypes } from '@crypto/blockchains/AirDAOBlockchainTypes'; +import { Database } from '@database'; // https://developers.tron.network/docs/parameter-and-return-value-encoding-and-decoding const ethers = require('ethers'); @@ -69,7 +69,7 @@ export default class TrxTransferProcessor /** * @type {TrxScannerProcessor} */ - const balanceProvider = BlocksoftDispatcher.getScannerProcessor( + const balanceProvider = AirDAODispatcher.getScannerProcessor( this._settings.currencyCode ); const balanceRaw = await balanceProvider.getBalanceBlockchain( @@ -83,7 +83,7 @@ export default class TrxTransferProcessor return { isOk: true }; } - const balanceProviderBasic = BlocksoftDispatcher.getScannerProcessor('TRX'); + const balanceProviderBasic = AirDAODispatcher.getScannerProcessor('TRX'); const balanceRawBasic = await balanceProviderBasic.getBalanceBlockchain( data.addressTo ); @@ -488,7 +488,6 @@ export default class TrxTransferProcessor ` TrxTransferProcessor.getTransferAllBalance ', ${data.addressFrom + ' => ' + balance}` ); - // noinspection EqualityComparisonWithCoercionJS if (balance === '0') { return { selectedTransferAllBalance: '0', @@ -563,9 +562,7 @@ export default class TrxTransferProcessor logData.basicAddressTo = data.addressTo; logData.basicAmount = data.amount; logData.pushLocale = sublocale(); - logData.pushSetting = await settingsActions.getSetting( - 'transactionsNotifs' - ); + logData.pushSetting = await Database.localStorage.get('transactionsNotifs'); logData.basicToken = this._tokenName; const sendLink = BlocksoftExternalSettings.getStatic('TRX_SEND_LINK'); @@ -709,17 +706,6 @@ export default class TrxTransferProcessor e.message ); // noinspection ES6MissingAwait - MarketingEvent.logOnlyRealTime( - 'v20_trx_tx_sub_error ' + - this._settings.currencyCode + - ' ' + - data.addressFrom + - ' => ' + - data.addressTo + - ' ' + - e.message, - logData - ); throw e; } @@ -1010,31 +996,8 @@ export default class TrxTransferProcessor ' TrxTransferProcessor.sendTx error ' + e.message ); - // noinspection ES6MissingAwait - MarketingEvent.logOnlyRealTime( - 'v20_trx_tx_error ' + - this._settings.currencyCode + - ' ' + - data.addressFrom + - ' => ' + - data.addressTo + - ' ' + - e.message, - logData - ); throw e; } - // noinspection ES6MissingAwait - MarketingEvent.logOnlyRealTime( - 'v20_trx_tx_success ' + - this._settings.currencyCode + - ' ' + - data.addressFrom + - ' => ' + - data.addressTo, - logData - ); - await BlocksoftTransactions.resetTransactionsPending( { account: { currencyCode: 'TRX' } }, 'AccountRunPending' diff --git a/crypto/common/AirDAODict.ts b/crypto/common/AirDAODict.ts index f8bae23c8..9d2e3243b 100644 --- a/crypto/common/AirDAODict.ts +++ b/crypto/common/AirDAODict.ts @@ -1,10 +1,10 @@ -import { NativeModules } from 'react-native'; +// import { NativeModules } from 'react-native'; import CoinAirDAODict from '@crypto/assets/coinAirDAODict.json'; import { Database } from '@database'; import { DatabaseTable } from '@appTypes'; -const { RNFastCrypto } = NativeModules; +// const { RNFastCrypto } = NativeModules; const VisibleCodes = [ 'CASHBACK', @@ -94,9 +94,9 @@ const CurrenciesForTests = { } }; -if (typeof RNFastCrypto === 'undefined') { - delete Currencies['XMR']; -} +// if (typeof RNFastCrypto === 'undefined') { +// delete Currencies['XMR']; +// } function addAndUnifyCustomCurrency(currencyObject: { id: string; diff --git a/crypto/common/AirDAOPrivateKeysUtils.ts b/crypto/common/AirDAOPrivateKeysUtils.ts index d994e24c7..6bed221fe 100644 --- a/crypto/common/AirDAOPrivateKeysUtils.ts +++ b/crypto/common/AirDAOPrivateKeysUtils.ts @@ -1,8 +1,8 @@ /** * @version 0.52 */ -import BlocksoftKeysStorage from '@crypto/actions/BlocksoftKeysStorage/BlocksoftKeysStorage'; -import BlocksoftKeys from '@crypto/actions/BlocksoftKeys/BlocksoftKeys'; +import AirDAOKeysStorage from '@lib/helpers/AirDAOKeysStorage'; +import AirDAOKeys from '@lib/helpers/AirDAOKeys'; import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; import config from '@constants/config'; @@ -42,7 +42,7 @@ export default { source ); } - const discoverForKey = BlocksoftKeysStorage.getAddressCacheKey( + const discoverForKey = AirDAOKeysStorage.getAddressCacheKey( discoverFor.walletHash, path.replace(/[']/g, 'quote'), discoverFor.currencyCode @@ -55,7 +55,7 @@ export default { if (result) { return result; } - result = await BlocksoftKeysStorage.getAddressCache(discoverForKey); + result = await AirDAOKeysStorage.getAddressCache(discoverForKey); if (result) { CACHE[discoverForKey] = result; return result; @@ -70,12 +70,12 @@ export default { 'BlocksoftTransferPrivateKeysDiscover.getPrivateKey actually redo mnemonic ' + discoverFor.walletHash ), - (discoverFor.mnemonic = BlocksoftKeysStorage.getWalletMnemonic( + (discoverFor.mnemonic = AirDAOKeysStorage.getWalletMnemonic( discoverFor.walletHash, 'getPrivateKey' )); } - result = await BlocksoftKeys.discoverOne(discoverFor); + result = await AirDAOKeys.discoverOne(discoverFor); if ( discoverFor.addressToCheck && discoverFor.addressToCheck !== result.address @@ -113,7 +113,7 @@ export default { for (const path of tmpPath) { const clone = JSON.parse(JSON.stringify(discoverFor)); clone.derivationPath = path; - const result2 = await BlocksoftKeys.discoverOne(clone); + const result2 = await AirDAOKeys.discoverOne(clone); if (discoverFor.addressToCheck === result2.address) { await AirDAOCryptoLog.log( 'BlocksoftTransferPrivateKeysDiscover private key rediscovered FOUND address path=' + @@ -145,7 +145,7 @@ export default { throw new Error('invalid address'); } } - await BlocksoftKeysStorage.setAddressCache(discoverForKey, result); + await AirDAOKeysStorage.setAddressCache(discoverForKey, result); } catch (e: any) { if (config.debug.appErrors) { console.log( diff --git a/src/screens/Wallet/Account/index.tsx b/src/screens/Wallet/Account/index.tsx index 3d5580de1..ae135d442 100644 --- a/src/screens/Wallet/Account/index.tsx +++ b/src/screens/Wallet/Account/index.tsx @@ -38,29 +38,38 @@ export const WalletAccount = () => { const [account, setAccount] = useState(null); const [transactions, setTransactions] = useState([]); const transactionsRef = useRef(transactions); + const nextPageExistsForTransactions = useRef(true); const [accountInfoLoading, setAccountInfoLoading] = useState(false); const [transactionsLoading, setTransactionsLoading] = useState(false); - // state for money sending const [amountToSend, setAmountToSend] = useState(''); const [addressToSend, setAddressToSend] = useState(''); const getTransactions = async (address: string) => { - if (transactions.length > 0 && transactions.length < LIMIT) return; + if ( + (transactionsRef.current.length > 0 && + transactionsRef.current.length < LIMIT) || + !nextPageExistsForTransactions.current + ) { + return; + } setTransactionsLoading(true); try { - const transactions = await API.explorerService.getTransactionsOfAccount( - address, - (transactionsRef.current.length % LIMIT) + 1, - LIMIT - ); + const newTransactions = + await API.explorerService.getTransactionsOfAccount( + address, + (transactionsRef.current.length % LIMIT) + 1, + LIMIT + ); + if (newTransactions.data.length < LIMIT) + nextPageExistsForTransactions.current = false; setTransactions( - transactions.data.map( + newTransactions.data.map( (transactionDTO) => new Transaction(transactionDTO) ) ); } catch (error) { - console.log(error, 'error loading acc transactions'); + // TODO handle } finally { setTransactionsLoading(false); } @@ -72,7 +81,6 @@ export const WalletAccount = () => { const info = await AirDAOKeysForRef.discoverPublicAndPrivate({ mnemonic: wallet.mnemonic }); - console.log({ info }); if (info) { const { address } = info; // @ts-ignore From b379e8dbc33ba7a26abd276c160ff271c893137e Mon Sep 17 00:00:00 2001 From: illiaa Date: Mon, 21 Aug 2023 16:06:51 +0300 Subject: [PATCH 062/509] fixes for sending crypto --- crypto/blockchains/AirDAOBlockchainTypes.ts | 1 + .../blockchains/eth/EthTransferProcessor.ts | 12 +- crypto/blockchains/eth/basic/EthBasic.ts | 119 ++++++++++++++---- .../eth/basic/EthTxSendProvider.ts | 3 +- crypto/common/AirDAOCryptoLog.ts | 4 +- crypto/common/AirDAOUtils.ts | 70 ++++++----- src/database/models/wallet.ts | 2 + src/models/Wallet.ts | 3 +- 8 files changed, 143 insertions(+), 71 deletions(-) diff --git a/crypto/blockchains/AirDAOBlockchainTypes.ts b/crypto/blockchains/AirDAOBlockchainTypes.ts index 5294f7da0..7edbb1d98 100644 --- a/crypto/blockchains/AirDAOBlockchainTypes.ts +++ b/crypto/blockchains/AirDAOBlockchainTypes.ts @@ -331,6 +331,7 @@ export namespace AirDAOBlockchainTypes { } export interface EthTx { + chainId: string; from: string; to: string; gasPrice: number; diff --git a/crypto/blockchains/eth/EthTransferProcessor.ts b/crypto/blockchains/eth/EthTransferProcessor.ts index 16b41931d..53dcc8500 100644 --- a/crypto/blockchains/eth/EthTransferProcessor.ts +++ b/crypto/blockchains/eth/EthTransferProcessor.ts @@ -116,7 +116,7 @@ export default class EthTransferProcessor // ' rbf preset nonceForTx ' + // oldNonce // ); - if (oldGasPrice === false) { + if (!oldGasPrice) { try { const ethProvider = AirDAODispatcher.getScannerProcessor( data.currencyCode @@ -142,7 +142,7 @@ export default class EthTransferProcessor txRBF ); } - } catch (e) { + } catch (e: any) { AirDAOCryptoLog.log( this._settings.currencyCode + ' EthTransferProcessor.getFeeRate ' + @@ -257,10 +257,10 @@ export default class EthTransferProcessor } AirDAOCryptoLog.log( this._settings.currencyCode + - ' EthTransferProcessor.getFeeRate ' + - data.addressFrom + + `' EthTransferProcessor.getFeeRate ' + + ${data.addressFrom} + ' proxyPriceCheck', - proxyPriceCheck + ${proxyPriceCheck}` ); } @@ -376,7 +376,7 @@ export default class EthTransferProcessor if (gasLimit) { gasLimit = BlocksoftUtils.mul(gasLimit, 1.5); } - } catch (e) { + } catch (e: any) { AirDAOCryptoLog.log( 'EthTransferProcessor data.contractCallData error ' + e.message ); diff --git a/crypto/blockchains/eth/basic/EthBasic.ts b/crypto/blockchains/eth/basic/EthBasic.ts index abcfdab01..34e033ece 100644 --- a/crypto/blockchains/eth/basic/EthBasic.ts +++ b/crypto/blockchains/eth/basic/EthBasic.ts @@ -2,6 +2,7 @@ * @version 0.5 * https://etherscan.io/apis#accounts */ +import { AirDAOBlockchainTypes } from '@crypto/blockchains/AirDAOBlockchainTypes'; import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; import { Web3Injected } from '@crypto/services/Web3Injected'; @@ -60,11 +61,66 @@ export default class EthBasic { */ _delegateAddress; + protected _settings: { + network: any; + tokenBlockchain: any; + tokenBlockchainCode?: string; + currencyCode?: any; + tokenAddress: string; + }; + + /** + * @type {boolean} + * @public + */ + _etherscanApiPathDeposits; + + /** + * @type {boolean} + * @public + */ + _isTestnet; + + /** + * @type {string} + * @public + */ + _etherscanApiPathForFee; + + /** + * @type {string} + * @public + */ + _mainCurrencyCode; + + /** + * @type {string} + * @public + */ + _mainTokenType; + + /** + * @type {string} + * @public + */ + _mainTokenBlockchain; + + /** + * @type {number} + * @public + */ + _mainChainId; + /** * @param {string} settings.network * @param {string} settings.currencyCode */ - constructor(settings) { + constructor(settings: { + network: any; + tokenBlockchain: any; + tokenBlockchainCode?: string; + currencyCode?: any; + }) { if (typeof settings === 'undefined' || !settings) { throw new Error('EthNetworked requires settings'); } @@ -88,7 +144,7 @@ export default class EthBasic { this._etherscanApiPathForFee = `https://api.bscscan.com/api?module=proxy&action=eth_gasPrice&apikey=YourApiKeyToken`; this._trezorServer = false; - this._trezorServerCode = false; + this._trezorServerCode = ''; this._mainCurrencyCode = 'BNB'; this._mainTokenType = 'BNB_SMART_20'; @@ -118,7 +174,7 @@ export default class EthBasic { this._etherscanApiPathForFee = false; this._trezorServer = false; - this._trezorServerCode = false; + this._trezorServerCode = ''; this._oklinkAPI = 'e11964ac-cfb9-406f-b2c5-3db76f91aebd'; @@ -137,7 +193,7 @@ export default class EthBasic { this._etherscanApiPathForFee = false; this._trezorServer = false; - this._trezorServerCode = false; + this._trezorServerCode = ''; this._mainCurrencyCode = 'VLX'; this._mainTokenType = 'VLX_ERC_20'; @@ -154,7 +210,7 @@ export default class EthBasic { this._etherscanApiPathForFee = false; this._trezorServer = false; - this._trezorServerCode = false; + this._trezorServerCode = ''; this._mainCurrencyCode = 'ONE'; this._mainTokenType = 'ONE_ERC_20'; @@ -171,7 +227,7 @@ export default class EthBasic { this._etherscanApiPathForFee = false; this._trezorServer = false; - this._trezorServerCode = false; + this._trezorServerCode = ''; this._mainCurrencyCode = 'METIS'; this._mainTokenType = 'METIS_ERC_20'; @@ -186,7 +242,7 @@ export default class EthBasic { this._etherscanApiPathForFee = `https://api.optimistic.etherscan.io/api?module=proxy&action=eth_gasPrice&apikey=YourApiKeyToken`; this._trezorServer = false; - this._trezorServerCode = false; + this._trezorServerCode = ''; this._mainCurrencyCode = 'OPTIMISM'; this._mainTokenType = 'OPTI_ERC_20'; @@ -216,7 +272,7 @@ export default class EthBasic { this._etherscanApiPathForFee = `https://api.polygonscan.com/api?module=proxy&action=eth_gasPrice&apikey=YourApiKeyToken`; this._trezorServer = false; - this._trezorServerCode = false; + this._trezorServerCode = ''; this._mainCurrencyCode = 'MATIC'; this._mainTokenType = 'MATIC_ERC_20'; @@ -233,7 +289,7 @@ export default class EthBasic { this._etherscanApiPathForFee = false; // invalid now `https://api.ftmscan.com/api?module=proxy&action=eth_gasPrice&apikey=YourApiKeyToken` this._trezorServer = false; - this._trezorServerCode = false; + this._trezorServerCode = ''; this._mainCurrencyCode = 'FTM'; this._mainTokenType = 'FTM_ERC_20'; @@ -250,7 +306,7 @@ export default class EthBasic { this._etherscanApiPathForFee = `https://api.bttcscan.com/api?module=proxy&action=eth_gasPrice&apikey=YourApiKeyToken`; this._trezorServer = false; - this._trezorServerCode = false; + this._trezorServerCode = ''; this._mainCurrencyCode = 'BTTC'; this._mainTokenType = 'BTTC_ERC_20'; @@ -262,7 +318,7 @@ export default class EthBasic { this._etherscanApiPathInternal = false; this._trezorServer = false; - this._trezorServerCode = false; + this._trezorServerCode = ''; this._mainCurrencyCode = 'RSK'; this._mainTokenType = 'RSK_ERC_20'; @@ -284,7 +340,7 @@ export default class EthBasic { this._isTestnet = true; } else { this._trezorServer = false; - this._trezorServerCode = false; + this._trezorServerCode = ''; this._isTestnet = true; } @@ -306,18 +362,26 @@ export default class EthBasic { return !(this._web3.LINK === oldWeb3Link); } - checkError(e, data, txRBF = false, logData = {}) { + checkError( + e: any, + data: AirDAOBlockchainTypes.TransferData, + txRBF = false, + logData = { + setNonce: undefined, + nonce: undefined + } + ) { console.log('check errro ', { e }); if (e.message.indexOf('Transaction has been reverted by the EVM') !== -1) { AirDAOCryptoLog.log( - 'EthBasic checkError0.0 ' + e.message + ' for ' + data.addressFrom, - logData + `'EthBasic checkError0.0 ' + ${e.message} + ' for ' + ${data.addressFrom}, + ${logData}` ); throw new Error('SERVER_RESPONSE_REVERTED_BY_EVM'); } else if (e.message.indexOf('nonce too low') !== -1) { AirDAOCryptoLog.log( - 'EthBasic checkError0.1 ' + e.message + ' for ' + data.addressFrom, - logData + `EthBasic checkError0.1 ' + ${e.message} + ' for ' + ${data.addressFrom}, + ${logData}` ); let e2; if (txRBF) { @@ -333,8 +397,8 @@ export default class EthBasic { throw e2; } else if (e.message.indexOf('gas required exceeds allowance') !== -1) { AirDAOCryptoLog.log( - 'EthBasic checkError0.2 ' + e.message + ' for ' + data.addressFrom, - logData + `EthBasic checkError0.2 ' + ${e.message} + ' for ' + ${data.addressFrom}, + ${logData}` ); if ( this._settings.tokenAddress === 'undefined' || @@ -346,12 +410,13 @@ export default class EthBasic { } } else if (e.message.indexOf('insufficient funds') !== -1) { AirDAOCryptoLog.log( - 'EthBasic checkError0.3 ' + e.message + ' for ' + data.addressFrom, - logData + `EthBasic checkError0.3 ' + ${e.message} + ' for ' + ${data.addressFrom}, + ${logData}` ); if ( (this._settings.currencyCode === 'ETH' || this._settings.currencyCode === 'BNB_SMART') && + // @ts-ignore data.amount * 1 > 0 ) { throw new Error('SERVER_RESPONSE_NOTHING_LEFT_FOR_FEE'); @@ -360,22 +425,22 @@ export default class EthBasic { } } else if (e.message.indexOf('underpriced') !== -1) { AirDAOCryptoLog.log( - 'EthBasic checkError0.4 ' + e.message + ' for ' + data.addressFrom, - logData + `EthBasic checkError0.4 ' + ${e.message} + ' for ' + ${data.addressFrom}, + ${logData}` ); throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_FEE'); } else if (e.message.indexOf('already known') !== -1) { AirDAOCryptoLog.log( - 'EthBasic checkError0.5 ' + e.message + ' for ' + data.addressFrom, - logData + `EthBasic checkError0.5 ' + ${e.message} + ' for ' + ${data.addressFrom}, + ${logData}` ); throw new Error( 'SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_FEE_FOR_REPLACEMENT' ); } else if (e.message.indexOf('infura') !== -1) { AirDAOCryptoLog.log( - 'EthBasic checkError0.6 ' + e.message + ' for ' + data.addressFrom, - logData + `EthBasic checkError0.6 ' + ${e.message} + ' for ' + ${data.addressFrom}, + ${logData}` ); throw new Error('SERVER_RESPONSE_BAD_INTERNET'); } else { diff --git a/crypto/blockchains/eth/basic/EthTxSendProvider.ts b/crypto/blockchains/eth/basic/EthTxSendProvider.ts index 7eefeba8c..f34ef61fd 100644 --- a/crypto/blockchains/eth/basic/EthTxSendProvider.ts +++ b/crypto/blockchains/eth/basic/EthTxSendProvider.ts @@ -60,7 +60,7 @@ export default class EthTxSendProvider { tx, privateData.privateKey ); - } catch (e) { + } catch (e: any) { console.log({ e }); throw new Error( this._settings.currencyCode + @@ -77,7 +77,6 @@ export default class EthTxSendProvider { txRBF: any, logData: any ): Promise<{ transactionHash: string; transactionJson: any }> { - // @ts-ignore await AirDAOCryptoLog.log( this._settings.currencyCode + ' EthTxSendProvider._innerSendTx started', logData diff --git a/crypto/common/AirDAOCryptoLog.ts b/crypto/common/AirDAOCryptoLog.ts index dfe7298ed..c3ade744b 100644 --- a/crypto/common/AirDAOCryptoLog.ts +++ b/crypto/common/AirDAOCryptoLog.ts @@ -16,8 +16,8 @@ const MAX_MESSAGE = 2000; const FULL_MAX_MESSAGE = 20000; -let LOGS_TXT = ''; -let FULL_LOGS_TXT = ''; +const LOGS_TXT = ''; +const FULL_LOGS_TXT = ''; class AirDAOCryptoLog { constructor() { diff --git a/crypto/common/AirDAOUtils.ts b/crypto/common/AirDAOUtils.ts index 7244e367f..441563a68 100644 --- a/crypto/common/AirDAOUtils.ts +++ b/crypto/common/AirDAOUtils.ts @@ -4,7 +4,8 @@ import { hexToBn, bnToHex } from '../blockchains/eth/ext/estimateGas/util'; // @ts-ignore import BigIntXmr from '@crypto/blockchains/xmr/ext/vendor/biginteger'; import Web3 from 'web3'; -import { EtherUnits } from 'web3-utils'; +import EtherUnits from 'web3-utils'; +import BN from 'bn.js'; class AirDAOUtils { static cutZeros(val: any): string { @@ -261,7 +262,7 @@ class AirDAOUtils { return cutted || '0'; } - static toWei(val: any, from = 'ether'): string { + static toWei(val: any, from = 'ether'): BN { if (typeof val === 'undefined') { throw new Error('toWei val is undefined'); } @@ -271,36 +272,38 @@ class AirDAOUtils { } const parts = val.toString().split('.'); if (typeof parts[1] === 'undefined' || parts[1] === '' || !parts[1]) { - return Web3.utils.toWei( - val, - from as - | 'noether' - | 'wei' - | 'kwei' - | 'Kwei' - | 'babbage' - | 'femtoether' - | 'mwei' - | 'Mwei' - | 'lovelace' - | 'picoether' - | 'gwei' - | 'Gwei' - | 'shannon' - | 'nanoether' - | 'nano' - | 'szabo' - | 'microether' - | 'micro' - | 'finney' - | 'milliether' - | 'milli' - | 'ether' - | 'kether' - | 'grand' - | 'mether' - | 'gether' - | 'tether' + return new BN( + Web3.utils.toWei( + val, + from as + | 'noether' + | 'wei' + | 'kwei' + | 'Kwei' + | 'babbage' + | 'femtoether' + | 'mwei' + | 'Mwei' + | 'lovelace' + | 'picoether' + | 'gwei' + | 'Gwei' + | 'shannon' + | 'nanoether' + | 'nano' + | 'szabo' + | 'microether' + | 'micro' + | 'finney' + | 'milliether' + | 'milli' + | 'ether' + | 'kether' + | 'grand' + | 'mether' + | 'gether' + | 'tether' + ) ); } @@ -309,7 +312,8 @@ class AirDAOUtils { decimals = 9; } const newVal = parts[0] + '.' + parts[1].substring(0, decimals); - return Web3.utils.toWei(newVal, from as EtherUnits); + // @ts-ignore // TODO fix this + return new BN(Web3.utils.toWei(newVal, from as EtherUnits)); } static toGwei(val: any): string { diff --git a/src/database/models/wallet.ts b/src/database/models/wallet.ts index db6777397..f958f38b3 100644 --- a/src/database/models/wallet.ts +++ b/src/database/models/wallet.ts @@ -33,6 +33,8 @@ export class WalletDBModel extends Model { @field('is_created_here') isCreatedHere: number; // @ts-ignore @field('to_send_status') toSendStatus: number; + // @ts-ignore + @field('pub') pub: string; static async getByHash(hash: string): Promise { const walletInDB = (await Database.query( diff --git a/src/models/Wallet.ts b/src/models/Wallet.ts index 284a142ae..7495b586e 100644 --- a/src/models/Wallet.ts +++ b/src/models/Wallet.ts @@ -52,7 +52,8 @@ export class Wallet { useUnconfirmed: model.useUnconfirmed, isHd: model.isHd, isCreatedHere: model.isCreatedHere, - toSendStatus: model.toSendStatus + toSendStatus: model.toSendStatus, + pub: model.pub }); } } From f3d1f88ac326c54f84e91d85bea620a31e03189f Mon Sep 17 00:00:00 2001 From: illiaa Date: Mon, 21 Aug 2023 16:10:29 +0300 Subject: [PATCH 063/509] fixes for sending crypto pt2 --- crypto/blockchains/AirDAOBlockchainTypes.ts | 5 +- .../blockchains/eth/EthTransferProcessor.ts | 61 ++++++++++--------- crypto/blockchains/eth/basic/EthBasic.ts | 2 +- 3 files changed, 38 insertions(+), 30 deletions(-) diff --git a/crypto/blockchains/AirDAOBlockchainTypes.ts b/crypto/blockchains/AirDAOBlockchainTypes.ts index 7edbb1d98..e33268b5e 100644 --- a/crypto/blockchains/AirDAOBlockchainTypes.ts +++ b/crypto/blockchains/AirDAOBlockchainTypes.ts @@ -157,6 +157,7 @@ export namespace AirDAOBlockchainTypes { blockchainData?: any; rawOnly?: boolean; bseOrderId?: string; + totalFeePlusAmountETH?: string; } export interface CheckTransferHasErrorData { @@ -259,6 +260,8 @@ export namespace AirDAOBlockchainTypes { showBlockedBalanceNotice?: number; showBlockedBalanceFree?: number | any; countedTime: number; + + selectedTransferAllBalanceETH?: string; } export interface SendTxResult { @@ -331,7 +334,6 @@ export namespace AirDAOBlockchainTypes { } export interface EthTx { - chainId: string; from: string; to: string; gasPrice: number; @@ -339,6 +341,7 @@ export namespace AirDAOBlockchainTypes { value: string; nonce?: number; data?: string; + chainId: string; } export interface DbAccount { diff --git a/crypto/blockchains/eth/EthTransferProcessor.ts b/crypto/blockchains/eth/EthTransferProcessor.ts index 53dcc8500..553bec330 100644 --- a/crypto/blockchains/eth/EthTransferProcessor.ts +++ b/crypto/blockchains/eth/EthTransferProcessor.ts @@ -977,17 +977,19 @@ export default class EthTransferProcessor maxNonceLocal.amountBlocked[this._settings.currencyCode] ).toString(); const diffAmount = BlocksoftUtils.diff(diff, data.amount).toString(); - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' EthTransferProcessor.getFees balance ' + - result.countedForBasicBalance + - ' - blocked ' + - maxNonceLocal.amountBlocked[this._settings.currencyCode] + - ' = left Balance ' + - diff + - ' => left Amount ' + - diffAmount - ); + if (typeof maxNonceLocal !== 'boolean') { + AirDAOCryptoLog.log( + this._settings.currencyCode + + ' EthTransferProcessor.getFees balance ' + + result.countedForBasicBalance + + ' - blocked ' + + maxNonceLocal.amountBlocked[this._settings.currencyCode] + + ' = left Balance ' + + diff + + ' => left Amount ' + + diffAmount + ); + } if (diff.indexOf('-') !== -1) { result.showBlockedBalanceNotice = new Date().getTime(); result.showBlockedBalanceFree = @@ -1012,21 +1014,23 @@ export default class EthTransferProcessor 'ETH_LONG_QUERY' ); check = maxNonceLocal.queryLength * 1 >= LONG_QUERY * 1; - await AirDAOCryptoLog.log( - this._settings.currencyCode + - ' EthTransferProcessor.getFees ethAllowLongQuery ' + - ethAllowLongQuery + - ' Query scanned ' + - maxNonceLocal.maxScanned + - ' success ' + - maxNonceLocal.maxSuccess + - ' length ' + - maxNonceLocal.queryLength + - ' txs ' + - JSON.stringify(maxNonceLocal.queryTxs) + - ' => ' + - (check ? 'true' : 'false') - ); + if (typeof maxNonceLocal !== 'boolean') { + await AirDAOCryptoLog.log( + this._settings.currencyCode + + ' EthTransferProcessor.getFees ethAllowLongQuery ' + + ethAllowLongQuery + + ' Query scanned ' + + maxNonceLocal.maxScanned + + ' success ' + + maxNonceLocal.maxSuccess + + ' length ' + + maxNonceLocal.queryLength + + ' txs ' + + JSON.stringify(maxNonceLocal.queryTxs) + + ' => ' + + (check ? 'true' : 'false') + ); + } if (check) { result.showLongQueryNotice = new Date().getTime(); result.showLongQueryNoticeTxs = maxNonceLocal.queryTxs; @@ -1107,7 +1111,7 @@ export default class EthTransferProcessor fees.selectedTransferAllBalanceETH = BlocksoftUtils.toEther( fees.fees[fees.selectedFeeIndex].amountForTx ); - } catch (e) { + } catch (e: any) { AirDAOCryptoLog.log( this._settings.currencyCode + ' EthTransferProcessor.getTransferAllBalance ' + @@ -1241,6 +1245,7 @@ export default class EthTransferProcessor } const tx: AirDAOBlockchainTypes.EthTx = { + chainId: '', from: data.addressFrom, to: realAddressToLower, gasPrice: finalGasPrice, @@ -1349,7 +1354,7 @@ export default class EthTransferProcessor if (scannedTx) { oldNonce = scannedTx.nonce; } - } catch (e) { + } catch (e: any) { AirDAOCryptoLog.err( this._settings.currencyCode + ' EthTransferProcessor.sent rbf not loaded nonce for ' + diff --git a/crypto/blockchains/eth/basic/EthBasic.ts b/crypto/blockchains/eth/basic/EthBasic.ts index 34e033ece..297150989 100644 --- a/crypto/blockchains/eth/basic/EthBasic.ts +++ b/crypto/blockchains/eth/basic/EthBasic.ts @@ -66,7 +66,7 @@ export default class EthBasic { tokenBlockchain: any; tokenBlockchainCode?: string; currencyCode?: any; - tokenAddress: string; + tokenAddress?: string; }; /** From 4efeccfcbb73c1726d3f46e19c4e65f20eb6d9e4 Mon Sep 17 00:00:00 2001 From: illiaa Date: Mon, 21 Aug 2023 16:52:59 +0300 Subject: [PATCH 064/509] Fix localization --- src/localization/i18n.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/localization/i18n.ts b/src/localization/i18n.ts index ea528516a..c694250a8 100644 --- a/src/localization/i18n.ts +++ b/src/localization/i18n.ts @@ -3,6 +3,7 @@ import { initReactI18next } from 'react-i18next'; import English from '../localization/locales/English.json'; import Turkish from '../localization/locales/Turkish.json'; +// Comment to trigger merge i18n.use(initReactI18next).init({ fallbackLng: 'English', debug: false, From cf0edebc04589812ed6e9ec3d3600ad6ad75dd0b Mon Sep 17 00:00:00 2001 From: illiaa Date: Mon, 21 Aug 2023 17:29:27 +0300 Subject: [PATCH 065/509] fix localization --- Providers.tsx | 16 ++- app.json | 2 +- eas.json | 5 +- package.json | 12 +- .../composite/Button/CopyToClipboard.tsx | 7 +- .../modular/CollectionItem/index.tsx | 4 +- .../templates/AMBPriceHistory/index.tsx | 12 +- .../templates/AddWalletToList/index.tsx | 6 +- .../BottomSheetAddWalletToList/index.tsx | 24 ++++ ...BottomSheetRemoveAddressFromWatchlists.tsx | 8 +- .../index.tsx | 6 +- .../BottomSheetCreateRenameGroup/index.tsx | 31 ++-- .../BottomSheetEditCollection/index.tsx | 7 +- .../templates/BottomSheetEditWallet/index.tsx | 20 +-- .../BottomSheetNotificationSettings/index.tsx | 15 +- .../index.tsx | 8 +- .../ExplorerAccount.TransactionItem.tsx | 4 +- .../ExplorerAccount.Transactions.tsx | 4 +- .../templates/ExplorerAccount/index.tsx | 47 +++--- .../templates/LocalizedRenderEmpty/index.tsx | 24 ++++ .../templates/LocalizedRenderEmpty/styles.ts | 11 ++ .../templates/RenderEmpty/index.tsx | 12 +- .../SearchAddress/SearchAddress.NoMatch.tsx | 6 +- .../templates/SearchAddress/index.tsx | 14 +- .../templates/TransactionDetails/index.tsx | 12 +- src/components/templates/WalletList/index.tsx | 4 +- src/components/templates/index.ts | 1 + src/contexts/Localizations/index.tsx | 64 +++++++++ src/contexts/index.ts | 1 + src/lib/permission.ts | 4 +- src/localization/locales/English.json | 134 ++++++++++++++++++ src/localization/locales/Turkish.json | 133 +++++++++++++++++ src/localization/locales/translations.ts | 17 +++ src/navigation/stacks/TabsNavigator.tsx | 14 +- src/screens/AMBMarket/AMBMarket.constants.ts | 21 +-- src/screens/AMBMarket/components/About.tsx | 7 +- .../AMBMarket/components/DetailedInfo.tsx | 8 +- src/screens/AMBMarket/index.tsx | 12 +- src/screens/Address/index.tsx | 15 +- .../Notifications/components/Header.tsx | 4 +- src/screens/Notifications/index.tsx | 14 +- .../components/ListsOfAddressGroup/index.tsx | 4 +- .../components/PortfolioScreenTabs/index.tsx | 6 +- src/screens/Portfolio/index.tsx | 56 ++++---- src/screens/Search/components/WalletItem.tsx | 9 +- src/screens/Search/index.tsx | 6 +- .../components/SettingsBlock/index.tsx | 13 +- .../modals/BottomSheetBaseCurrency/index.tsx | 4 +- .../BottomSheetSelectLanguage/index.tsx | 19 ++- .../components/SettingsInfoBlock/index.tsx | 8 +- src/screens/Settings/index.tsx | 26 +--- src/screens/SingleCollection/index.tsx | 4 +- .../BottomSheetAddNewAddressToGroup/index.tsx | 49 ++++--- .../modals/BottomSheetRenameAddress/index.tsx | 10 +- src/screens/Wallets/components/Header.tsx | 8 +- .../components/HomeTabs/HomeCollections.tsx | 4 +- .../Wallets/components/HomeTabs/HomeTabs.tsx | 8 +- .../components/HomeTabs/HomeWatchlists.tsx | 5 +- yarn.lock | 57 ++++++++ 59 files changed, 815 insertions(+), 251 deletions(-) create mode 100644 src/components/templates/LocalizedRenderEmpty/index.tsx create mode 100644 src/components/templates/LocalizedRenderEmpty/styles.ts create mode 100644 src/contexts/Localizations/index.tsx create mode 100644 src/localization/locales/English.json create mode 100644 src/localization/locales/Turkish.json create mode 100644 src/localization/locales/translations.ts diff --git a/Providers.tsx b/Providers.tsx index 03b8242e7..009cec214 100644 --- a/Providers.tsx +++ b/Providers.tsx @@ -1,9 +1,13 @@ +import React from 'react'; import { combineComponents } from '@helpers/combineComponents'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import React from 'react'; import { SafeAreaProvider } from 'react-native-safe-area-context'; import { ListsContextProvider } from '@contexts/ListsContext'; -import { AllAddressesProvider, OnboardingContextProvider } from '@contexts'; +import { + AllAddressesProvider, + OnboardingContextProvider, + LocalizationProvider +} from '@contexts'; const queryClient = new QueryClient(); @@ -15,9 +19,14 @@ const WrappedSafeAreaProvider: React.FC = ({ children }: any) => ( {children} ); +const WrappedLocalizationProvider: React.FC = ({ children }: any) => ( + {children} +); + const independentProviders = [ WrappedQueryClientProvider, - WrappedSafeAreaProvider + WrappedSafeAreaProvider, + WrappedLocalizationProvider ]; /** * The order of the providers matters @@ -26,6 +35,7 @@ const providers = [ ...independentProviders, AllAddressesProvider, ListsContextProvider, + WrappedLocalizationProvider, OnboardingContextProvider ]; diff --git a/app.json b/app.json index 227a913d5..da02973a2 100644 --- a/app.json +++ b/app.json @@ -2,7 +2,7 @@ "expo": { "name": "AirDAO", "slug": "AirDao", - "version": "1.0.1", + "version": "1.0.0", "orientation": "portrait", "icon": "./assets/logo.png", "userInterfaceStyle": "light", diff --git a/eas.json b/eas.json index 20180a79f..00045b648 100644 --- a/eas.json +++ b/eas.json @@ -12,7 +12,10 @@ } }, "stage": { - "channel": "stage" + "channel": "stage", + "android": { + "buildType": "apk" + } }, "prod": { "channel": "main" diff --git a/package.json b/package.json index 53187d363..1b6194e5e 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,6 @@ { "name": "airdao", "version": "1.0.0", - "main": "node_modules/expo/AppEntry.js", "scripts": { "build:android:dev": "eas build -p android --profile dev", "build:ios:dev": "eas build -p ios --profile dev", @@ -9,10 +8,10 @@ "build:ios:stage": "eas build -p ios --profile stage", "build:android:prod": "eas build -p android --profile prod", "build:ios:prod": "eas build -p ios --profile prod", - "start": "expo start", + "start": "expo start --dev-client", "start:dev": "expo start --dev-client", - "android": "expo start --android", - "ios": "expo start --ios", + "android": "expo run:android", + "ios": "expo run:ios", "web": "expo start --web", "lint": "eslint 'src/**/*.{js,ts,tsx}'", "prepare": "husky install", @@ -56,12 +55,15 @@ "expo-store-review": "~6.2.1", "expo-system-ui": "~2.2.1", "expo-updates": "~0.16.4", + "i18n-js": "^4.3.0", + "i18next": "^23.4.4", "jest": "^29.2.1", "jest-expo": "^48.0.2", "moment": "^2.29.4", "patch-package": "^7.0.0", "postinstall-postinstall": "^2.1.0", "react": "18.2.0", + "react-i18next": "^13.1.0", "react-native": "0.71.8", "react-native-calendar-picker": "^7.1.4", "react-native-gesture-handler": "~2.9.0", @@ -117,4 +119,4 @@ "prettier --write" ] } -} \ No newline at end of file +} diff --git a/src/components/composite/Button/CopyToClipboard.tsx b/src/components/composite/Button/CopyToClipboard.tsx index dd602b1ef..c83ca2aa0 100644 --- a/src/components/composite/Button/CopyToClipboard.tsx +++ b/src/components/composite/Button/CopyToClipboard.tsx @@ -6,6 +6,7 @@ import { ClipboardFilledIcon } from '@components/svg/icons'; import { scale } from '@utils/scaling'; import { Toast, ToastPosition } from '@components/modular/Toast'; import { BaseButtonProps } from '@components/base/Button'; +import { useTranslation } from 'react-i18next'; export interface CopyToClipboardButtonProps extends Omit { @@ -18,9 +19,13 @@ export const CopyToClipboardButton = ( props: CopyToClipboardButtonProps ): JSX.Element => { const { textToDisplay, textToCopy, textProps, ...buttonProps } = props; + const { t } = useTranslation(); const onPress = async () => { - Toast.show({ message: 'Copied to Clipboard', type: ToastPosition.Bottom }); + Toast.show({ + message: t('copied.to.clipboard'), + type: ToastPosition.Bottom + }); await Clipboard.setStringAsync(textToCopy || textToDisplay); }; diff --git a/src/components/modular/CollectionItem/index.tsx b/src/components/modular/CollectionItem/index.tsx index 2a0eead47..b1ce4bec2 100644 --- a/src/components/modular/CollectionItem/index.tsx +++ b/src/components/modular/CollectionItem/index.tsx @@ -6,6 +6,7 @@ import { COLORS } from '@constants/colors'; import { useAMBPrice } from '@hooks'; import { AccountList } from '@models'; import { NumberUtils } from '@utils/number'; +import { useTranslation } from 'react-i18next'; interface CollectionItemProps { collection: AccountList; @@ -15,6 +16,7 @@ interface CollectionItemProps { export function CollectionItem(props: CollectionItemProps) { const { collection, style } = props; const { data: ambPriceData } = useAMBPrice(); + const { t } = useTranslation(); const tokensFormatted = useMemo(() => { const formattedNumber = NumberUtils.formatNumber( @@ -53,7 +55,7 @@ export function CollectionItem(props: CollectionItemProps) { color={COLORS.smokyBlack50} fontSize={12} > - {collection.accountCount + ' addresses'} + {collection.accountCount + ` ${t('addresses.text')}`} {collection.accountCount > 0 && ( diff --git a/src/components/templates/AMBPriceHistory/index.tsx b/src/components/templates/AMBPriceHistory/index.tsx index b215003bc..d08134c7d 100644 --- a/src/components/templates/AMBPriceHistory/index.tsx +++ b/src/components/templates/AMBPriceHistory/index.tsx @@ -1,4 +1,6 @@ import React, { useEffect, useMemo, useRef, useState } from 'react'; +import { View } from 'react-native'; +import { useTranslation } from 'react-i18next'; import Animated, { useAnimatedProps, useAnimatedStyle, @@ -16,9 +18,8 @@ import { scale, verticalScale } from '@utils/scaling'; import { Badge } from '@components/base/Badge'; import { PercentChange } from '@components/composite'; import { BezierChart } from '../BezierChart'; -import { styles } from './styles'; import { MONTH_NAMES } from '@constants/variables'; -import { View } from 'react-native'; +import { styles } from './styles'; interface AMBPriceHistoryProps { badgeType: 'view' | 'button'; @@ -44,6 +45,7 @@ export const AMBPriceHistory = (props: AMBPriceHistoryProps) => { const ambPrice = useSharedValue(ambPriceNow?.priceUSD || 0); const selectedPointDate = useSharedValue(-1); const didSetAMBPriceFromAPI = useRef(false); + const { t } = useTranslation(); useEffect(() => { if (ambPriceNow) { @@ -204,15 +206,15 @@ export const AMBPriceHistory = (props: AMBPriceHistoryProps) => { // value: '1h' // }, { - text: '1D', + text: t('chart.timeframe.daily'), value: '1d' }, { - text: '1W', + text: t('chart.timeframe.weekly'), value: 'weekly' }, { - text: '1M', + text: t('chart.timeframe.monthly'), value: 'monthly' } ]} diff --git a/src/components/templates/AddWalletToList/index.tsx b/src/components/templates/AddWalletToList/index.tsx index dd55bca98..429aba41b 100644 --- a/src/components/templates/AddWalletToList/index.tsx +++ b/src/components/templates/AddWalletToList/index.tsx @@ -10,6 +10,7 @@ import { COLORS } from '@constants/colors'; import { SearchIcon } from '@components/svg/icons'; import { NumberUtils } from '@utils/number'; import { useLists } from '@contexts/ListsContext'; +import { useTranslation } from 'react-i18next'; export interface AddWalletToListProps { wallet: ExplorerAccount; @@ -20,6 +21,7 @@ export interface AddWalletToListProps { export const AddWalletToList = (props: AddWalletToListProps): JSX.Element => { const { wallet, lists, onWalletMove } = props; const { toggleAddressesInList } = useLists((v) => v); + const { t } = useTranslation(); const [searchText, setSearchText] = useState(''); const filteredLists = useMemo( () => @@ -76,7 +78,7 @@ export const AddWalletToList = (props: AddWalletToListProps): JSX.Element => { fontFamily="Inter_500Medium" color={COLORS.smokyBlack50} > - {list.accountCount} Addresses + {list.accountCount} {t('addresses')} @@ -95,7 +97,7 @@ export const AddWalletToList = (props: AddWalletToListProps): JSX.Element => { } /> diff --git a/src/components/templates/BottomSheetAddWalletToList/index.tsx b/src/components/templates/BottomSheetAddWalletToList/index.tsx index 1dc82b2b2..a2c08c4d2 100644 --- a/src/components/templates/BottomSheetAddWalletToList/index.tsx +++ b/src/components/templates/BottomSheetAddWalletToList/index.tsx @@ -5,6 +5,9 @@ import { useFullscreenModalHeight } from '@hooks'; import { COLORS } from '@constants/colors'; import { AddWalletToList, AddWalletToListProps } from '../AddWalletToList'; import { verticalScale } from '@utils/scaling'; +import { PrimaryButton } from '@components/modular'; +import { BottomSheetCreateRenameGroup } from '@components/templates'; +import { useLists } from '@contexts'; interface BottomSheetAddWalletToListProps extends AddWalletToListProps { title: string; @@ -16,6 +19,11 @@ export const BottomSheetAddWalletToList = forwardRef< >((props, ref) => { const { title, ...addWalletToListProps } = props; const fullscreenHeight = useFullscreenModalHeight(); + const { handleOnCreate, createGroupRef } = useLists((v) => v); + + const showCreateNewListModal = () => { + createGroupRef.current?.show(); + }; return ( + { + showCreateNewListModal(); + }} + style={{ width: '90%', alignSelf: 'center' }} + > + + Create new group + + + + ); }); diff --git a/src/components/templates/BottomSheetConfirmRemove/BottomSheetRemoveAddressFromWatchlists.tsx b/src/components/templates/BottomSheetConfirmRemove/BottomSheetRemoveAddressFromWatchlists.tsx index ddf5efc65..2b5f72ca1 100644 --- a/src/components/templates/BottomSheetConfirmRemove/BottomSheetRemoveAddressFromWatchlists.tsx +++ b/src/components/templates/BottomSheetConfirmRemove/BottomSheetRemoveAddressFromWatchlists.tsx @@ -8,6 +8,7 @@ import { COLORS } from '@constants/colors'; import { ExplorerAccount } from '@models'; import { BottomSheetFloat } from '@components/modular'; import { verticalScale } from '@utils/scaling'; +import { useTranslation } from 'react-i18next'; type Props = { ref: RefObject; @@ -20,6 +21,7 @@ export const BottomSheetRemoveAddressFromWatchlists = forwardRef< >(({ item }, ref) => { const localRef: ForwardedRef = useForwardedRef(ref); const { removeFromWatchlist } = useWatchlist(); + const { t } = useTranslation(); const handleRemoveAddressFromWatchlist = useCallback(() => { removeFromWatchlist(item); @@ -42,7 +44,7 @@ export const BottomSheetRemoveAddressFromWatchlists = forwardRef< fontSize={14} color={COLORS.smokyBlack} > - Remove this address from watchlist? + {t('confirm.remove.address.from.watchlist')} @@ -70,7 +72,7 @@ export const BottomSheetRemoveAddressFromWatchlists = forwardRef< color={COLORS.smokyBlack} fontSize={16} > - Cancel + {t('cancel.btn')} diff --git a/src/components/templates/BottomSheetCreateCollectionOrAddAddress/index.tsx b/src/components/templates/BottomSheetCreateCollectionOrAddAddress/index.tsx index 938eaecc2..44efde22e 100644 --- a/src/components/templates/BottomSheetCreateCollectionOrAddAddress/index.tsx +++ b/src/components/templates/BottomSheetCreateCollectionOrAddAddress/index.tsx @@ -6,6 +6,7 @@ import { BottomSheetFloat, PrimaryButton } from '@components/modular'; import { useForwardedRef } from '@hooks'; import { scale, verticalScale } from '@utils/scaling'; import { COLORS } from '@constants/colors'; +import { useTranslation } from 'react-i18next'; type Props = { ref: RefObject; @@ -18,6 +19,7 @@ export const BottomSheetCreateCollectionOrAddAddress = forwardRef< Props >(({ handleCreateCollectionPress, handleOnAddNewAddress }, ref) => { const localRef: ForwardedRef = useForwardedRef(ref); + const { t } = useTranslation(); return ( - Add address + {t('add.address.btn')} @@ -60,7 +62,7 @@ export const BottomSheetCreateCollectionOrAddAddress = forwardRef< fontSize={16} color={COLORS.smokyBlack} > - Create group + {t('create.group')} diff --git a/src/components/templates/BottomSheetCreateRenameGroup/index.tsx b/src/components/templates/BottomSheetCreateRenameGroup/index.tsx index fc53227a1..c327c492e 100644 --- a/src/components/templates/BottomSheetCreateRenameGroup/index.tsx +++ b/src/components/templates/BottomSheetCreateRenameGroup/index.tsx @@ -23,6 +23,7 @@ import { OnboardingView } from '../OnboardingView'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { verticalScale } from '@utils/scaling'; import { StringUtils } from '@utils/string'; +import { useTranslation } from 'react-i18next'; type Props = { ref: RefObject; @@ -43,6 +44,7 @@ export const BottomSheetCreateRenameGroup = forwardRef( } = props; const localRef: ForwardedRef = useForwardedRef(ref); + const { t } = useTranslation(); const nameInput = useRef(null); const [localGroupName, setLocalGroupName] = useState(''); @@ -67,11 +69,11 @@ export const BottomSheetCreateRenameGroup = forwardRef( handleOnCreateGroup(localGroupName); Toast.show({ title: '', - message: `Way to go! ${StringUtils.formatAddress( + message: `${t('toast.way.to.go')} ${StringUtils.formatAddress( localGroupName, 16, 0 - )} group created.`, + )} ${t('toast.group.created')}`, type: ToastPosition.Top }); } @@ -80,15 +82,9 @@ export const BottomSheetCreateRenameGroup = forwardRef( handleOnRenameGroup(groupId, localGroupName); Toast.show({ title: '', - message: `${StringUtils.formatAddress( - groupTitle || '', - 16, - 0 - )} has been renamed to ${StringUtils.formatAddress( - localGroupName, - 16, - 0 - )}.`, + message: `${StringUtils.formatAddress(groupTitle || '', 16, 0)} ${t( + 'toast.has.been.renamed' + )} ${StringUtils.formatAddress(localGroupName, 16, 0)}.`, type: ToastPosition.Top }); } @@ -101,7 +97,8 @@ export const BottomSheetCreateRenameGroup = forwardRef( handleOnCreateGroup, handleOnRenameGroup, localGroupName, - localRef + localRef, + t ]); const bottomSafeArea = useSafeAreaInsets().bottom - 10; @@ -136,7 +133,7 @@ export const BottomSheetCreateRenameGroup = forwardRef( fontSize={16} color={COLORS.smokyBlack} > - {type === 'create' ? ' Create group' : 'Rename group'} + {type === 'create' ? t('create.group') : t('rename.group')} ( type="text" placeholder={ emptyPlaceholder - ? 'This field is required' - : 'Enter group name' + ? t('field.required') + : t('group.name.input') } placeholderTextColor={ emptyPlaceholder ? COLORS.crimsonRed : COLORS.midnight @@ -178,7 +175,7 @@ export const BottomSheetCreateRenameGroup = forwardRef( fontSize={16} color={COLORS.white} > - {type === 'create' ? 'Create' : 'Save'} + {type === 'create' ? t('create.btn') : t('save.btn')} @@ -198,7 +195,7 @@ export const BottomSheetCreateRenameGroup = forwardRef( color={COLORS.nero} fontSize={16} > - Cancel + {t('cancel.btn')} diff --git a/src/components/templates/BottomSheetEditCollection/index.tsx b/src/components/templates/BottomSheetEditCollection/index.tsx index 000c93863..8e6630316 100644 --- a/src/components/templates/BottomSheetEditCollection/index.tsx +++ b/src/components/templates/BottomSheetEditCollection/index.tsx @@ -10,6 +10,7 @@ import { AccountList } from '@models'; import { BottomSheetCreateRenameGroup } from '../BottomSheetCreateRenameGroup'; import { styles } from './styles'; import { StringUtils } from '@utils/string'; +import { useTranslation } from 'react-i18next'; interface BottomSheetEditCollectionProps extends BottomSheetProps { collection: AccountList; @@ -25,6 +26,7 @@ export const BottomSheetEditCollection = forwardRef< const localRef: ForwardedRef = useForwardedRef(ref); const { handleOnRename, handleOnDelete } = useLists((v) => v); const renameCollectionModalRef = useRef(null); + const { t } = useTranslation(); const dismissThis = useCallback(() => { setTimeout(() => { @@ -74,7 +76,7 @@ export const BottomSheetEditCollection = forwardRef< fontFamily="Inter_600SemiBold" color={COLORS.smokyBlack} > - Rename group + {t('rename.group')} diff --git a/src/components/templates/BottomSheetEditWallet/index.tsx b/src/components/templates/BottomSheetEditWallet/index.tsx index 671310207..de5982246 100644 --- a/src/components/templates/BottomSheetEditWallet/index.tsx +++ b/src/components/templates/BottomSheetEditWallet/index.tsx @@ -10,6 +10,7 @@ import { BottomSheetRenameAddress } from '@screens/SingleCollection/modals/Botto import { COLORS } from '@constants/colors'; import { BottomSheetAddWalletToList } from '../BottomSheetAddWalletToList'; import { styles } from './styles'; +import { useTranslation } from 'react-i18next'; interface BottomSheetEditWalletProps extends BottomSheetProps { wallet: ExplorerAccount; @@ -25,6 +26,7 @@ export const BottomSheetEditWallet = forwardRef< const { listsOfAddressGroup, toggleAddressesInList } = useLists((v) => v); const renameWalletModalRef = useRef(null); const addToCollectionModalRef = useRef(null); + const { t } = useTranslation(); const listsWithCurrentWallet = listsOfAddressGroup.filter((list) => list.accounts.some((acc) => acc?.address === wallet?.address) @@ -79,11 +81,11 @@ export const BottomSheetEditWallet = forwardRef< dismissThis(); Toast.show({ title: '', - message: 'Successfully removed wallet from group!', + message: t('toast.Successfully.removed.wallet.from.group'), type: ToastPosition.Top }); } - }, [dismissThis, listsWithCurrentWallet, toggleAddressesInList, wallet]); + }, [dismissThis, listsWithCurrentWallet, t, toggleAddressesInList, wallet]); return ( - Rename address + {t('rename.address')} {listsWithCurrentWallet.length > 0 ? ( @@ -114,7 +116,7 @@ export const BottomSheetEditWallet = forwardRef< fontFamily="Inter_600SemiBold" color={COLORS.smokyBlack} > - Move to another group + {t('move.to.another.group')} listsWithCurrentWallet.indexOfItem(list, 'id') === -1 @@ -149,12 +153,12 @@ export const BottomSheetEditWallet = forwardRef< fontFamily="Inter_600SemiBold" color={COLORS.smokyBlack} > - Add to group + {t('add.address.to.group.lower.case')} ( (notificationSettings); const { top: topInset } = useSafeAreaInsets(); + const { t } = useTranslation(); + useEffect( () => setLocalNotificationSettings(notificationSettings), [notificationSettings] @@ -91,7 +94,7 @@ export const BottomSheetNotificationSettings = forwardRef< fontSize={16} color={COLORS.smokyBlack} > - Notification settings + {t('notification.settings')} } titlePosition={Platform.select({ ios: 'left', default: 'center' })} @@ -108,7 +111,7 @@ export const BottomSheetNotificationSettings = forwardRef< {/* Price alerts */} - Price alerts + {t('price.alerts.switch')} onSettingsValueChange( @@ -122,14 +125,14 @@ export const BottomSheetNotificationSettings = forwardRef< {/* Percentage Change */} - Price alerts threshold + {t('price.alerts.treshold')} - Set 24hr price change amount to receive notifications. + {t('price.alerts.treshold.text')} - Transaction alerts + {t('transaction.alerts.switch')} - Get notified of transactions in your watchlist. + {t('transaction.alerts.switch.text')} ; @@ -21,6 +22,7 @@ export const BottomSheetRemoveAddressFromCollection = forwardRef< >(({ wallet }, ref) => { const localRef: ForwardedRef = useForwardedRef(ref); const { toggleAddressesInList, listsOfAddressGroup } = useLists((v) => v); + const { t } = useTranslation(); const collection = listsOfAddressGroup.find( (list) => list.accounts.findIndex((account) => account._id === wallet._id) > -1 @@ -40,7 +42,7 @@ export const BottomSheetRemoveAddressFromCollection = forwardRef< color={COLORS.smokyBlack} numberOfLines={1} > - Remove this address from {collection?.name}? + {t('remove.address.from.group.select')} {collection?.name}? @@ -72,7 +74,7 @@ export const BottomSheetRemoveAddressFromCollection = forwardRef< color={COLORS.smokyBlack} fontSize={16} > - Cancel + {t('cancel.btn')} diff --git a/src/components/templates/ExplorerAccount/ExplorerAccount.TransactionItem.tsx b/src/components/templates/ExplorerAccount/ExplorerAccount.TransactionItem.tsx index 60243fcb2..6b9667388 100644 --- a/src/components/templates/ExplorerAccount/ExplorerAccount.TransactionItem.tsx +++ b/src/components/templates/ExplorerAccount/ExplorerAccount.TransactionItem.tsx @@ -8,6 +8,7 @@ import { BottomSheetFloat, TransactionItem } from '@components/modular'; import { Transaction } from '@models/Transaction'; import { scale, verticalScale } from '@utils/scaling'; import { CommonStackNavigationProp } from '@appTypes/navigation/common'; +import { useTranslation } from 'react-i18next'; interface ExplorerAccountTransactionItemProps { transaction: Transaction; @@ -20,6 +21,7 @@ export const ExplorerAccountTransactionItem = ( const { transaction, disabled = false } = props; const transactionDetailsModal = useRef(null); const navigation = useNavigation(); + const { t } = useTranslation(); const showTransactionDetails = () => { transactionDetailsModal.current?.show(); @@ -42,7 +44,7 @@ export const ExplorerAccountTransactionItem = ( - Transaction details + {t('transaction.details')} diff --git a/src/components/templates/ExplorerAccount/ExplorerAccount.Transactions.tsx b/src/components/templates/ExplorerAccount/ExplorerAccount.Transactions.tsx index 0db3ff85b..3067a4202 100644 --- a/src/components/templates/ExplorerAccount/ExplorerAccount.Transactions.tsx +++ b/src/components/templates/ExplorerAccount/ExplorerAccount.Transactions.tsx @@ -11,6 +11,7 @@ import { } from '@components/base'; import { ExplorerAccountTransactionItem } from './ExplorerAccount.TransactionItem'; import { COLORS } from '@constants/colors'; +import { useTranslation } from 'react-i18next'; interface ExplorerAccountViewTransactionsProps { transactions: Transaction[]; @@ -24,6 +25,7 @@ export const AccountTransactions = ( ): JSX.Element => { const { transactions, loading, showTransactionDetailsOnPress, onEndReached } = props; + const { t } = useTranslation(); const renderTransaction = ( args: ListRenderItemInfo @@ -46,7 +48,7 @@ export const AccountTransactions = ( fontSize={20} color={COLORS.jetBlack} > - Recent activity + {t('recent.activity')} diff --git a/src/components/templates/ExplorerAccount/index.tsx b/src/components/templates/ExplorerAccount/index.tsx index 613126c5b..c97c877a3 100644 --- a/src/components/templates/ExplorerAccount/index.tsx +++ b/src/components/templates/ExplorerAccount/index.tsx @@ -8,13 +8,14 @@ import { useAMBPrice } from '@hooks/query'; import { NumberUtils } from '@utils/number'; import { styles } from './styles'; import { useLists } from '@contexts/ListsContext'; -import { PlusIcon } from '@components/svg/icons'; +// import { PlusIcon } from '@components/svg/icons'; import { BottomSheetRef, CopyToClipboardButton } from '@components/composite'; import { COLORS } from '@constants/colors'; import { AddWalletToList } from '../AddWalletToList'; import { BottomSheetWithHeader } from '@components/modular'; import { useFullscreenModalHeight } from '@hooks/useFullscreenModalHeight'; import { useWatchlist } from '@hooks'; +import { useTranslation } from 'react-i18next'; interface ExplorerAccountProps { account: ExplorerAccount; @@ -30,6 +31,7 @@ export const ExplorerAccountView = ( const { listsOfAddressGroup } = useLists((v) => v); const fullscreenHeight = useFullscreenModalHeight(); const { addToWatchlist, removeFromWatchlist } = useWatchlist(); + const { t } = useTranslation(); const { data } = useAMBPrice(); const ambPriceUSD = data?.priceUSD || 0; @@ -76,7 +78,8 @@ export const ExplorerAccountView = ( fontSize={13} fontWeight="400" > - Added to {listsWithAccount[0].name} + {t('address.added.to.group')} + {listsWithAccount[0].name} ) : ( - Added to {listsWithAccount.length} lists + {t('address.added.to.group')} + {listsWithAccount.length} + {t('groups.count')} ))} @@ -152,14 +157,16 @@ export const ExplorerAccountView = ( fontSize={12} color={account.isOnWatchlist ? COLORS.liver : COLORS.white} > - {account.isOnWatchlist ? 'WATCHLISTED' : 'ADD TO WATCHLIST'} + {account.isOnWatchlist + ? t('watchlisted.address') + : t('watchlist.address.btn')} - {!account.isOnWatchlist && ( - <> - - - - )} + {/*{!account.isOnWatchlist && (*/} + {/* <>*/} + {/* */} + {/* */} + {/* */} + {/*)}*/} @@ -185,17 +192,19 @@ export const ExplorerAccountView = ( fontSize={12} > {listsWithAccount.length === 0 - ? 'ADD TO GROUP' + ? t('add.address.to.group.upper.case') : listsWithAccount.length === 1 ? StringUtils.formatAddress(listsWithAccount[0].name, 16, 0) - : `Added to ${listsWithAccount.length} groups`} + : `${t('address.added.to.group')} ${ + listsWithAccount.length + } ${t('groups.count')}`} - {listsWithAccount.length === 0 && ( - <> - - - - )} + {/*{listsWithAccount.length === 0 && (*/} + {/* <>*/} + {/* */} + {/* */} + {/* */} + {/*)}*/} @@ -203,7 +212,7 @@ export const ExplorerAccountView = ( diff --git a/src/components/templates/LocalizedRenderEmpty/index.tsx b/src/components/templates/LocalizedRenderEmpty/index.tsx new file mode 100644 index 000000000..9a2a387f1 --- /dev/null +++ b/src/components/templates/LocalizedRenderEmpty/index.tsx @@ -0,0 +1,24 @@ +import React from 'react'; +import { View } from 'react-native'; +import { EmptyWalletListPlaceholderIcon } from '@components/svg/icons'; +import { Spacer, Text } from '@components/base'; +import { verticalScale } from '@utils/scaling'; +import { styles } from '@components/templates/LocalizedRenderEmpty/styles'; +import { useTranslation } from 'react-i18next'; + +type LocalizedRenderEmptyProps = { + text: string; +}; + +export const LocalizedRenderEmpty = ({ text }: LocalizedRenderEmptyProps) => { + const { t } = useTranslation(); + return ( + + + + + {t(text)} + + + ); +}; diff --git a/src/components/templates/LocalizedRenderEmpty/styles.ts b/src/components/templates/LocalizedRenderEmpty/styles.ts new file mode 100644 index 000000000..140f73cc2 --- /dev/null +++ b/src/components/templates/LocalizedRenderEmpty/styles.ts @@ -0,0 +1,11 @@ +import { StyleSheet } from 'react-native'; +import { scale, verticalScale } from '@utils/scaling'; + +export const styles = StyleSheet.create({ + emptyContainer: { + paddingTop: verticalScale(20), + alignItems: 'center', + alignSelf: 'center', + width: scale(200) + } +}); diff --git a/src/components/templates/RenderEmpty/index.tsx b/src/components/templates/RenderEmpty/index.tsx index 93cf7f924..9a2a387f1 100644 --- a/src/components/templates/RenderEmpty/index.tsx +++ b/src/components/templates/RenderEmpty/index.tsx @@ -1,21 +1,23 @@ +import React from 'react'; import { View } from 'react-native'; import { EmptyWalletListPlaceholderIcon } from '@components/svg/icons'; import { Spacer, Text } from '@components/base'; import { verticalScale } from '@utils/scaling'; -import React from 'react'; -import { styles } from '@components/templates/RenderEmpty/styles'; +import { styles } from '@components/templates/LocalizedRenderEmpty/styles'; +import { useTranslation } from 'react-i18next'; -type RenderEmptyProps = { +type LocalizedRenderEmptyProps = { text: string; }; -export const RenderEmpty = ({ text }: RenderEmptyProps) => { +export const LocalizedRenderEmpty = ({ text }: LocalizedRenderEmptyProps) => { + const { t } = useTranslation(); return ( - No {text} yet + {t(text)} ); diff --git a/src/components/templates/SearchAddress/SearchAddress.NoMatch.tsx b/src/components/templates/SearchAddress/SearchAddress.NoMatch.tsx index c3a889f97..f142335a8 100644 --- a/src/components/templates/SearchAddress/SearchAddress.NoMatch.tsx +++ b/src/components/templates/SearchAddress/SearchAddress.NoMatch.tsx @@ -4,8 +4,10 @@ import { Spacer, Text } from '@components/base'; import { COLORS } from '@constants/colors'; import { moderateScale, verticalScale } from '@utils/scaling'; import { NoMatch } from '@components/svg/icons/NoMatch'; +import { useTranslation } from 'react-i18next'; export function SearchAddressNoResult(): JSX.Element { + const { t } = useTranslation(); return ( @@ -16,7 +18,7 @@ export function SearchAddressNoResult(): JSX.Element { fontWeight="600" fontFamily="Inter_600SemiBold" > - Oops! No matches found + {t('no.matches')} - Please check for any typos or try a different address + {t('check.typos')} ); diff --git a/src/components/templates/SearchAddress/index.tsx b/src/components/templates/SearchAddress/index.tsx index 527c31afb..e405135e6 100644 --- a/src/components/templates/SearchAddress/index.tsx +++ b/src/components/templates/SearchAddress/index.tsx @@ -35,6 +35,7 @@ import { CRYPTO_ADDRESS_MAX_LENGTH } from '@constants/variables'; import { COLORS } from '@constants/colors'; import { SearchTabNavigationProp } from '@appTypes'; import { styles } from './styles'; +import { useTranslation } from 'react-i18next'; interface SearchAdressProps { initialValue?: string; @@ -47,6 +48,7 @@ export const SearchAddress = (props: SearchAdressProps): JSX.Element => { const { initialValue, onContentVisibilityChanged = () => null } = props; const navigation = useNavigation(); const { height: WINDOW_HEIGHT } = useWindowDimensions(); + const { t } = useTranslation(); const { data: explorerInfo } = useExplorerInfo(); const [address, setAddress] = useState(''); const [searchSubmitted, setSearchSubmitted] = useState(false); @@ -108,8 +110,8 @@ export const SearchAddress = (props: SearchAdressProps): JSX.Element => { const toggleWatchlist = async (isOnWatchlist: boolean) => { if (isOnWatchlist) { Toast.show({ - title: 'Way to go! Address watchlisted.', - message: 'Tap to rename Address', + title: t('toast.address.watchlisted.msg'), + message: t('toast.tap.to.rename.msg'), type: ToastPosition.Top, onBodyPress: editModal.current?.show }); @@ -158,9 +160,9 @@ export const SearchAddress = (props: SearchAdressProps): JSX.Element => { }, 500); } else if (!scanned.current) { scanned.current = true; - Alert.alert('Invalid QR code', '', [ + Alert.alert(t('invalid.qr.code.msg'), '', [ { - text: 'Scan again', + text: t('scan.again.msg'), onPress: () => { scanned.current = false; } @@ -216,7 +218,7 @@ export const SearchAddress = (props: SearchAdressProps): JSX.Element => { )} } - placeholder={'Search Address or TX hash'} + placeholder={t('search.address.input')} returnKeyType="search" onFocus={onInputFocused} onBlur={onInputBlur} @@ -264,7 +266,7 @@ export const SearchAddress = (props: SearchAdressProps): JSX.Element => { fontSize={20} color={COLORS.neutral800} > - Transaction details + {t('transaction.details')} (null); const { data: ambData } = useAMBPrice(); + const { t } = useTranslation(); const ambPrice = ambData ? ambData.priceUSD : -1; let totalTransactionAmount; @@ -67,7 +69,7 @@ export const TransactionDetails = ( - Method + {t('method')} {transaction.type} @@ -76,7 +78,7 @@ export const TransactionDetails = ( - Amount + {t('amount')} {NumberUtils.formatNumber(transaction.amount, 0)} AMB @@ -88,7 +90,7 @@ export const TransactionDetails = ( - From + {t('from')} diff --git a/src/screens/Portfolio/index.tsx b/src/screens/Portfolio/index.tsx index e14dc7171..bcf5b681d 100644 --- a/src/screens/Portfolio/index.tsx +++ b/src/screens/Portfolio/index.tsx @@ -7,33 +7,7 @@ import { WatchList } from '@screens/Portfolio/components/PortfolioScreenTabs/com import { useIsFocused } from '@react-navigation/native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { View } from 'react-native'; - -const portfolioTabRoutes = [ - { key: 'first', title: 'Addresses' }, - { key: 'second', title: 'Groups' } -] as const; - -type PortfolioTabRoutes = typeof portfolioTabRoutes; - -type PortfolioTabViewRoute = { - key: PortfolioTabRoutes[number]['key']; - title: PortfolioTabRoutes[number]['title']; -}; - -type RenderSceneProps = Parameters< - TabViewProps['renderScene'] ->[0]; - -const renderScene = ({ route }: RenderSceneProps) => { - switch (route.key) { - case 'first': - return ; - case 'second': - return ; - default: - return null; - } -}; +import { useTranslation } from 'react-i18next'; type PortfolioScreenProps = { route: { @@ -44,6 +18,34 @@ type PortfolioScreenProps = { }; export const PortfolioScreen = ({ route }: PortfolioScreenProps) => { + const { t } = useTranslation(); + const portfolioTabRoutes = [ + { key: 'first', title: t('addresses') }, + { key: 'second', title: t('groups.capitalize') } + ] as const; + + type PortfolioTabRoutes = typeof portfolioTabRoutes; + + type PortfolioTabViewRoute = { + key: PortfolioTabRoutes[number]['key']; + title: PortfolioTabRoutes[number]['title']; + }; + + type RenderSceneProps = Parameters< + TabViewProps['renderScene'] + >[0]; + + // tslint:disable-next-line:no-shadowed-variable + const renderScene = ({ route }: RenderSceneProps) => { + switch (route.key) { + case 'first': + return ; + case 'second': + return ; + default: + return null; + } + }; const { top } = useSafeAreaInsets(); const activeTab = route?.params?.tabs?.activeTab; const [index, setIndex] = useState(0); diff --git a/src/screens/Search/components/WalletItem.tsx b/src/screens/Search/components/WalletItem.tsx index e70b2b224..64c862fcf 100644 --- a/src/screens/Search/components/WalletItem.tsx +++ b/src/screens/Search/components/WalletItem.tsx @@ -9,6 +9,7 @@ import { COLORS } from '@constants/colors'; import { useLists } from '@contexts'; import { useWatchlist } from '@hooks'; import { AddressIndicator } from '@components/templates'; +import { useTranslation } from 'react-i18next'; interface ExplorerWalletItemProps { item: ExplorerAccount; @@ -23,6 +24,7 @@ export const ExplorerWalletItem = ( const { address, transactionCount, ambBalance } = item; const { listsOfAddressGroup } = useLists((v) => v); const { watchlist } = useWatchlist(); + const { t } = useTranslation(); const listWithAddress = listsOfAddressGroup.filter( (list) => list.accounts.indexOfItem(item, 'address') > -1 ); @@ -72,18 +74,19 @@ export const ExplorerWalletItem = ( color={COLORS.smokyBlack50} fontFamily="Inter_500Medium" > - Holding{' '} + {t('single.address.holding')} {NumberUtils.formatNumber( item.calculatePercentHoldings(totalSupply), 2 - )} - % of supply + )}{' '} + {t('single.address.supply')} + {/* TODO add localisation here, key is "transactions" */} {StringUtils.pluralize(transactionCount, 'Transaction')} diff --git a/src/screens/Search/index.tsx b/src/screens/Search/index.tsx index 69d92395c..2552e4515 100644 --- a/src/screens/Search/index.tsx +++ b/src/screens/Search/index.tsx @@ -22,10 +22,12 @@ import { SearchSort } from './Search.types'; import { COLORS } from '@constants/colors'; import { styles } from './styles'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; +import { useTranslation } from 'react-i18next'; export const SearchScreen = () => { const navigation = useNavigation(); const { data: infoData } = useExplorerInfo(); + const { t } = useTranslation(); const { data: accounts, loading: accountsLoading, @@ -96,12 +98,12 @@ export const SearchScreen = () => { fontSize={20} color={COLORS.smokyBlack} > - Top holders + {t('top.holders')} {accountsError ? ( - Could not load accounts info + {t('no.account.info')} ) : ( infoData && accounts && ( diff --git a/src/screens/Settings/components/SettingsBlock/index.tsx b/src/screens/Settings/components/SettingsBlock/index.tsx index bec0d0285..40fd919f2 100644 --- a/src/screens/Settings/components/SettingsBlock/index.tsx +++ b/src/screens/Settings/components/SettingsBlock/index.tsx @@ -17,7 +17,10 @@ import { import { BottomSheetNotificationSettings } from '@components/templates'; import { styles } from '@screens/Settings/components/SettingsBlock/style'; import { COLORS } from '@constants/colors'; +import { useTranslation } from 'react-i18next'; + export const SettingsBlock = () => { + const { t } = useTranslation(); const [selectedLanguage, setSelectedLanguage] = useState('English'); const [selectedCurrency, setSelectedCurrency] = useState('US Dollars (USD)'); @@ -58,7 +61,9 @@ export const SettingsBlock = () => { > - Base currency + + {t('base.currency.modal')} + {selectedCurrency} @@ -77,7 +82,7 @@ export const SettingsBlock = () => { > - Language + {t('language.modal')} {selectedLanguage} @@ -96,7 +101,9 @@ export const SettingsBlock = () => { > - Notification settings + + {t('notification.settings.modal')} + diff --git a/src/screens/Settings/components/SettingsBlock/modals/BottomSheetBaseCurrency/index.tsx b/src/screens/Settings/components/SettingsBlock/modals/BottomSheetBaseCurrency/index.tsx index 80ea9d917..9f7ca5cd8 100644 --- a/src/screens/Settings/components/SettingsBlock/modals/BottomSheetBaseCurrency/index.tsx +++ b/src/screens/Settings/components/SettingsBlock/modals/BottomSheetBaseCurrency/index.tsx @@ -8,6 +8,7 @@ import { styles } from '@screens/Settings/components/SettingsBlock/modals/style' import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { COLORS } from '@constants/colors'; import { scale } from '@utils/scaling'; +import { useTranslation } from 'react-i18next'; type Props = { ref: RefObject; @@ -70,6 +71,7 @@ export const BottomSheetSelectBaseCurrency = forwardRef( selectedCurrency || '' ); const { top: topInset } = useSafeAreaInsets(); + const { t } = useTranslation(); const onCurrencyPress = (value: Currency) => { setModalActiveCurrency(value); @@ -90,7 +92,7 @@ export const BottomSheetSelectBaseCurrency = forwardRef( fontSize={scale(16)} color={COLORS.smokyBlack} > - Select base currency + {t('select.base.currency')} } titlePosition={Platform.select({ ios: 'left', default: 'center' })} diff --git a/src/screens/Settings/components/SettingsBlock/modals/BottomSheetSelectLanguage/index.tsx b/src/screens/Settings/components/SettingsBlock/modals/BottomSheetSelectLanguage/index.tsx index e13413c4d..d3e2b798f 100644 --- a/src/screens/Settings/components/SettingsBlock/modals/BottomSheetSelectLanguage/index.tsx +++ b/src/screens/Settings/components/SettingsBlock/modals/BottomSheetSelectLanguage/index.tsx @@ -8,6 +8,8 @@ import { styles } from '@screens/Settings/components/SettingsBlock/modals/style' import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { COLORS } from '@constants/colors'; import { scale } from '@utils/scaling'; +import { useTranslation } from 'react-i18next'; +import useLocalization from '@contexts/Localizations'; type Props = { ref: RefObject; @@ -32,16 +34,16 @@ type LanguageData = { const mockedLanguages: LanguageData[] = [ { language: 'English' - } + }, // { // language: 'Arabic' // }, // { // language: 'Spanish' // }, - // { - // language: 'Turkish' - // }, + { + language: 'Turkish' + } // { // language: 'Hindi' // }, @@ -59,14 +61,19 @@ const mockedLanguages: LanguageData[] = [ export const BottomSheetSelectLanguage = forwardRef( ({ selectedLanguage, handleLanguageSave }, ref) => { const localRef: ForwardedRef = useForwardedRef(ref); + // eslint-disable-next-line @typescript-eslint/no-unused-vars const [modalActiveLanguage, setModalActiveLanguage] = useState( selectedLanguage || '' ); const { top: topInset } = useSafeAreaInsets(); + const { changeCurrentLanguage, currentLanguage } = useLocalization(); + + const { t } = useTranslation(); const onLanguagePress = (value: Language) => { setModalActiveLanguage(value); handleLanguageSave(value); + changeCurrentLanguage(value); }; return ( @@ -84,7 +91,7 @@ export const BottomSheetSelectLanguage = forwardRef( fontSize={scale(16)} color={COLORS.smokyBlack} > - Select language + {t('select.language')} } titlePosition={Platform.select({ ios: 'left', default: 'center' })} @@ -101,7 +108,7 @@ export const BottomSheetSelectLanguage = forwardRef( renderItem={({ item, index }) => { return ( { + const { t } = useTranslation(); const openLink = () => { Linking.openURL('https://airdao.academy/faqs'); }; @@ -17,7 +19,7 @@ export const SettingsInfoBlock = () => { @@ -29,8 +31,8 @@ export const SettingsInfoBlock = () => { })} {Platform.select({ - ios: 'Rate us on the App Store', - android: 'Rate us on the Play Store' + ios: t('rate.btn.ios'), + android: t('rate.btn.android') })} diff --git a/src/screens/Settings/index.tsx b/src/screens/Settings/index.tsx index 4c7302f5b..4d06a89e7 100644 --- a/src/screens/Settings/index.tsx +++ b/src/screens/Settings/index.tsx @@ -5,39 +5,15 @@ import { SettingsBlock } from '@screens/Settings/components/SettingsBlock'; import { COLORS } from '@constants/colors'; import { SettingsInfoBlock } from '@screens/Settings/components/SettingsInfoBlock'; import { scale } from '@utils/scaling'; -// import { Spacer, Text } from '@components/base'; -// import * as Updates from 'expo-updates'; -// import messaging from '@react-native-firebase/messaging'; -// import { CopyToClipboardButton } from '@components/composite'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; -// TODO -export const SettingsScreen = () => { - // const [token, setToken] = useState(''); - - // useEffect(() => { - // messaging().getToken().then(setToken); - // }, []); +export const SettingsScreen = () => { const { top } = useSafeAreaInsets(); return ( - {/* - - - - - Channel: {Updates.channel} - AirDAO Testing Build: v1.0.0.16 - */} ); }; diff --git a/src/screens/SingleCollection/index.tsx b/src/screens/SingleCollection/index.tsx index be8a7650d..bea27cf80 100644 --- a/src/screens/SingleCollection/index.tsx +++ b/src/screens/SingleCollection/index.tsx @@ -20,6 +20,7 @@ import { useAllAddressesContext } from '@contexts'; import { BottomSheetAddNewAddressToGroup } from './modals/BottomSheetAddNewAddressToGroup'; import { sortListByKey } from '@utils/sort'; import { styles } from './styles'; +import { useTranslation } from 'react-i18next'; export const SingleGroupScreen = () => { const { @@ -29,6 +30,7 @@ export const SingleGroupScreen = () => { } = useRoute>(); const navigation = useNavigation(); const { data: ambPriceData } = useAMBPrice(); + const { t } = useTranslation(); const addNewAddressToGroupRef = useRef(null); const groupRenameRef = useRef(null); @@ -106,7 +108,7 @@ export const SingleGroupScreen = () => { fontFamily="Inter_600SemiBold" fontSize={12} > - TOTAL BALANCE + {t('total.balance')} diff --git a/src/screens/SingleCollection/modals/BottomSheetAddNewAddressToGroup/index.tsx b/src/screens/SingleCollection/modals/BottomSheetAddNewAddressToGroup/index.tsx index 9b6f5424f..d49cff210 100644 --- a/src/screens/SingleCollection/modals/BottomSheetAddNewAddressToGroup/index.tsx +++ b/src/screens/SingleCollection/modals/BottomSheetAddNewAddressToGroup/index.tsx @@ -38,25 +38,13 @@ import { StringUtils } from '@utils/string'; import { SearchSort } from '@screens/Search/Search.types'; import { etherumAddressRegex } from '@constants/regex'; import { styles } from './styles'; +import { useTranslation } from 'react-i18next'; type Props = { ref: RefObject; collection: AccountList; }; -const AddressSources: Segment[] = [ - { - title: 'Watchlist', - value: 0, - id: 'watchlist' - }, - { - title: 'Top Holders', - value: 1, - id: 'topHolders' - } -]; - export const BottomSheetAddNewAddressToGroup = forwardRef< BottomSheetRef, Props @@ -69,6 +57,21 @@ export const BottomSheetAddNewAddressToGroup = forwardRef< fetchNextPage: fetchMoreTopHolders } = useExplorerAccounts(SearchSort.Balance); const [searchValue, setSearchValue] = useState(''); + const { t } = useTranslation(); + + const AddressSources: Segment[] = [ + { + title: t('watchlist.tab'), + value: 0, + id: 'watchlist' + }, + { + title: t('top.holders.capitalize'), + value: 1, + id: 'topHolders' + } + ]; + const { data: searchedAccount, loading: searchLoading, @@ -186,9 +189,9 @@ export const BottomSheetAddNewAddressToGroup = forwardRef< }, 500); } else if (!scanned.current) { scanned.current = true; - Alert.alert('Invalid QR code', '', [ + Alert.alert(t('invalid.qr.code.msg'), '', [ { - text: 'Scan again', + text: t('scan.again.msg'), onPress: () => { scanned.current = false; } @@ -228,7 +231,7 @@ export const BottomSheetAddNewAddressToGroup = forwardRef< fontFamily="Inter_700Bold" fontSize={18} color={COLORS.nero} - >{`Add address to ${StringUtils.formatAddress( + >{`${t('add.address.to.selected.group')} ${StringUtils.formatAddress( collection.name, 10, 0 @@ -256,7 +259,7 @@ export const BottomSheetAddNewAddressToGroup = forwardRef< } type="text" style={{ width: '65%', height: 50 }} - placeholder="Search public address" + placeholder={t('search.public.address.input')} placeholderTextColor="#2f2b4399" value={searchValue} onChangeText={setSearchValue} @@ -371,15 +374,15 @@ export const BottomSheetAddNewAddressToGroup = forwardRef< fontWeight="600" > {selectingAddedItems - ? `Remove ${StringUtils.pluralize( + ? `${t('remove.btn')} ${StringUtils.pluralize( selectedAddresses.length, - 'Address', - 'Addresses' + t('address'), + t('addresses') )}` - : `Add ${StringUtils.pluralize( + : `${t('add')} ${StringUtils.pluralize( selectedAddresses.length, - 'Address', - 'Addresses' + t('address'), + t('addresses') )}`} diff --git a/src/screens/SingleCollection/modals/BottomSheetRenameAddress/index.tsx b/src/screens/SingleCollection/modals/BottomSheetRenameAddress/index.tsx index 0d990e6a5..94211a8a5 100644 --- a/src/screens/SingleCollection/modals/BottomSheetRenameAddress/index.tsx +++ b/src/screens/SingleCollection/modals/BottomSheetRenameAddress/index.tsx @@ -8,6 +8,7 @@ import { useForwardedRef } from '@hooks/useForwardedRef'; import { styles } from '@screens/SingleCollection/modals/BottomSheetRenameAddress/styles'; import { BottomSheetFloat, PrimaryButton } from '@components/modular'; import { scale, verticalScale } from '@utils/scaling'; +import { useTranslation } from 'react-i18next'; type Props = { ref: RefObject; @@ -21,6 +22,7 @@ export const BottomSheetRenameAddress = forwardRef( address || '' ); const localRef: ForwardedRef = useForwardedRef(ref); + const { t } = useTranslation(); return ( ( fontSize={16} color={COLORS.nero} > - Rename address + {t('rename.address')} setLocalAddressName(value)} type="text" - placeholder="Edit name" + placeholder={t('edit.name')} placeholderTextColor={COLORS.neutral900Alpha[60]} style={[styles.bottomSheetInput]} /> @@ -58,7 +60,7 @@ export const BottomSheetRenameAddress = forwardRef( fontSize={16} color={COLORS.white} > - Save + {t('save.btn')} @@ -72,7 +74,7 @@ export const BottomSheetRenameAddress = forwardRef( color={COLORS.smokyBlack} fontSize={16} > - Cancel + {t('cancel.btn')} diff --git a/src/screens/Wallets/components/Header.tsx b/src/screens/Wallets/components/Header.tsx index 02a5fe90f..937c2a7d8 100644 --- a/src/screens/Wallets/components/Header.tsx +++ b/src/screens/Wallets/components/Header.tsx @@ -19,6 +19,7 @@ import { useNotificationsQuery } from '@hooks'; import { Cache, CacheKey } from '@utils/cache'; import { useNewNotificationsCount } from '../hooks/useNewNotificationsCount'; import { COLORS } from '@constants/colors'; +import { useTranslation } from 'react-i18next'; export const HomeHeader = React.memo((): JSX.Element => { const navigation = useNavigation(); @@ -27,6 +28,7 @@ export const HomeHeader = React.memo((): JSX.Element => { const scanned = useRef(false); const { data: notifications } = useNotificationsQuery(); const newNotificationsCount = useNewNotificationsCount(); + const { t } = useTranslation(); const openScanner = () => { scanner.current?.show(); @@ -47,9 +49,9 @@ export const HomeHeader = React.memo((): JSX.Element => { }); } else if (!scanned.current) { scanned.current = true; - Alert.alert('Invalid QR code', '', [ + Alert.alert(t('invalid.qr.code.msg'), '', [ { - text: 'Scan again', + text: t('scan.again.msg'), onPress: () => { scanned.current = false; } @@ -57,7 +59,7 @@ export const HomeHeader = React.memo((): JSX.Element => { ]); } }, - [navigation] + [navigation, t] ); const setLastNotificationTime = useCallback(() => { diff --git a/src/screens/Wallets/components/HomeTabs/HomeCollections.tsx b/src/screens/Wallets/components/HomeTabs/HomeCollections.tsx index 70c3f79af..36816404c 100644 --- a/src/screens/Wallets/components/HomeTabs/HomeCollections.tsx +++ b/src/screens/Wallets/components/HomeTabs/HomeCollections.tsx @@ -5,7 +5,7 @@ import { Button, Spacer, Spinner } from '@components/base'; import { useNavigation } from '@react-navigation/native'; import { PortfolioNavigationProp } from '@appTypes'; import { styles } from '@screens/Wallets/components/HomeTabs/styles'; -import { RenderEmpty } from '@components/templates/RenderEmpty'; +import { LocalizedRenderEmpty } from '@components/templates'; import { verticalScale } from '@utils/scaling'; import { AccountList } from '@models'; import { CollectionItem } from '@components/modular'; @@ -31,7 +31,7 @@ export const HomeCollections = () => { } if (listsOfAddressGroup.length === 0) { - return ; + return ; } return ( diff --git a/src/screens/Wallets/components/HomeTabs/HomeTabs.tsx b/src/screens/Wallets/components/HomeTabs/HomeTabs.tsx index 94d48d444..42bbb2031 100644 --- a/src/screens/Wallets/components/HomeTabs/HomeTabs.tsx +++ b/src/screens/Wallets/components/HomeTabs/HomeTabs.tsx @@ -15,12 +15,14 @@ import { useNavigation } from '@react-navigation/native'; import { BottomSheetCreateRenameGroup } from '@components/templates/BottomSheetCreateRenameGroup'; import { useAllAddressesContext, useLists } from '@contexts'; import { useWatchlist } from '@hooks'; +import { useTranslation } from 'react-i18next'; export const HomeTabs = () => { const navigation = useNavigation(); const { handleOnCreate, listsOfAddressGroup } = useLists((v) => v); const { watchlist } = useWatchlist(); const { addressesLoading } = useAllAddressesContext((v) => v); + const { t } = useTranslation(); const scrollView = useRef(null); const createCollectionOrAddAddressRef = useRef(null); @@ -92,7 +94,7 @@ export const HomeTabs = () => { color={currentIndex === 0 ? COLORS.jetBlack : COLORS.lavenderGray} fontSize={currentIndex === 0 ? 20 : 18} > - Addresses + {t('addresses')} @@ -105,7 +107,7 @@ export const HomeTabs = () => { color={currentIndex === 1 ? COLORS.jetBlack : COLORS.lavenderGray} fontSize={currentIndex === 1 ? 20 : 18} > - Groups + {t('groups.capitalize')} @@ -157,7 +159,7 @@ export const HomeTabs = () => { fontSize={16} color={COLORS.deepBlue} > - See all + {t('see.all.btn')} diff --git a/src/screens/Wallets/components/HomeTabs/HomeWatchlists.tsx b/src/screens/Wallets/components/HomeTabs/HomeWatchlists.tsx index cbf2d4bef..08e0e054d 100644 --- a/src/screens/Wallets/components/HomeTabs/HomeWatchlists.tsx +++ b/src/screens/Wallets/components/HomeTabs/HomeWatchlists.tsx @@ -5,8 +5,7 @@ import { Button, Spacer, Spinner } from '@components/base'; import { useNavigation } from '@react-navigation/native'; import { PortfolioNavigationProp } from '@appTypes'; import { styles } from '@screens/Wallets/components/HomeTabs/styles'; -import { RenderEmpty } from '@components/templates/RenderEmpty'; -import { WalletItem } from '@components/templates'; +import { WalletItem, LocalizedRenderEmpty } from '@components/templates'; import { ExplorerAccount } from '@models'; import { verticalScale } from '@utils/scaling'; import { useAllAddressesContext } from '@contexts'; @@ -21,7 +20,7 @@ export const HomeWatchlists = () => { const navigation = useNavigation(); if (watchlist.length === 0) { - return ; + return ; } const navigateToAddressDetails = (item: ExplorerAccount) => { diff --git a/yarn.lock b/yarn.lock index 31be42160..20a906057 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1369,6 +1369,13 @@ dependencies: regenerator-runtime "^0.13.11" +"@babel/runtime@^7.22.5": + version "7.22.10" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.10.tgz#ae3e9631fd947cb7e3610d3e9d8fef5f76696682" + integrity sha512-21t/fkKLMZI4pqP2wlmsQAWnYW1PDyKyyUV4vCi+B25ydmdaYTKXPwCj0BzSUnZf4seIiYvSA3jcZ3gdsMFkLQ== + dependencies: + regenerator-runtime "^0.14.0" + "@babel/runtime@^7.9.2": version "7.21.5" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.21.5.tgz#8492dddda9644ae3bda3b45eabe87382caee7200" @@ -3488,6 +3495,11 @@ big-integer@1.6.x: resolved "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz" integrity sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg== +bignumber.js@*: + version "9.1.1" + resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.1.1.tgz#c4df7dc496bd849d4c9464344c1aa74228b4dac6" + integrity sha512-pHm4LsMJ6lzgNGVfZHjMoO8sdoRhOzOH4MLmY65Jg70bpxCKu5iOHNJyfF6OyvYw7t8Fpf35RuzUyqnQsj8Vig== + bl@^4.1.0: version "4.1.0" resolved "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz" @@ -6007,6 +6019,13 @@ html-escaper@^2.0.0: resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== +html-parse-stringify@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz#dfc1017347ce9f77c8141a507f233040c59c55d2" + integrity sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg== + dependencies: + void-elements "3.1.0" + http-errors@2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz" @@ -6050,6 +6069,21 @@ husky@^8.0.3: resolved "https://registry.npmjs.org/husky/-/husky-8.0.3.tgz" integrity sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg== +i18n-js@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/i18n-js/-/i18n-js-4.3.0.tgz#f1098e21a762faf8b8d09453abfdb6a28ee0f57b" + integrity sha512-PX93eT6WPV6Ym6mHtFKGDRZB0zwDX7HUPkgprjsZ28J6/Ohw1nvRYuM93or3pWv2VLxs6XfBf7X9Fc/YAZNEtQ== + dependencies: + bignumber.js "*" + make-plural "*" + +i18next@^23.4.4: + version "23.4.4" + resolved "https://registry.yarnpkg.com/i18next/-/i18next-23.4.4.tgz#ec8fb2b5f3c5d8e3bf3f8ab1b19e743be91300e0" + integrity sha512-+c9B0txp/x1m5zn+QlwHaCS9vyFtmIAEXbVSFzwCX7vupm5V7va8F9cJGNJZ46X9ZtoGzhIiRC7eTIIh93TxPA== + dependencies: + "@babel/runtime" "^7.22.5" + iconv-lite@0.4.24: version "0.4.24" resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz" @@ -7501,6 +7535,11 @@ make-dir@^3.0.0: dependencies: semver "^6.0.0" +make-plural@*: + version "7.3.0" + resolved "https://registry.yarnpkg.com/make-plural/-/make-plural-7.3.0.tgz#2889dbafca2fb097037c47967d3e3afa7e48a52c" + integrity sha512-/K3BC0KIsO+WK2i94LkMPv3wslMrazrQhfi5We9fMbLlLjzoOSJWr7TAdupLlDWaJcWxwoNosBkhFDejiu5VDw== + makeerror@1.0.12: version "1.0.12" resolved "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz" @@ -8931,6 +8970,14 @@ react-freeze@^1.0.0: resolved "https://registry.npmjs.org/react-freeze/-/react-freeze-1.0.3.tgz" integrity sha512-ZnXwLQnGzrDpHBHiC56TXFXvmolPeMjTn1UOm610M4EXGzbEDR7oOIyS2ZiItgbs6eZc4oU/a0hpk8PrcKvv5g== +react-i18next@^13.1.0: + version "13.1.0" + resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-13.1.0.tgz#18b1769c1c22c8f1d9a8f395bec48240e3349cfd" + integrity sha512-TlDz4761vJe810o5nEzRkblfS0FQz+k9R5BLOPVhn0Ds6WMDcwGSMbU5fne9WSd4/vLgKg7fEJ7/JboWdmsHOw== + dependencies: + "@babel/runtime" "^7.22.5" + html-parse-stringify "^3.0.1" + "react-is@^16.12.0 || ^17.0.0 || ^18.0.0", react-is@^18.0.0, react-is@^18.2.0: version "18.2.0" resolved "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz" @@ -9221,6 +9268,11 @@ regenerator-runtime@^0.13.11, regenerator-runtime@^0.13.2: resolved "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz" integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg== +regenerator-runtime@^0.14.0: + version "0.14.0" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz#5e19d68eb12d486f797e15a3c6a918f7cec5eb45" + integrity sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA== + regenerator-transform@^0.15.1: version "0.15.1" resolved "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.1.tgz" @@ -10776,6 +10828,11 @@ vlq@^1.0.0: resolved "https://registry.npmjs.org/vlq/-/vlq-1.0.1.tgz" integrity sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w== +void-elements@3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-3.1.0.tgz#614f7fbf8d801f0bb5f0661f5b2f5785750e4f09" + integrity sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w== + w3c-xmlserializer@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz#aebdc84920d806222936e3cdce408e32488a3073" From b91f1f20189d6b9f4fd120f209257720a91f9dce Mon Sep 17 00:00:00 2001 From: JavidHaji-zada Date: Tue, 22 Aug 2023 14:49:58 +0400 Subject: [PATCH 066/509] fixed sending crypto --- crypto/blockchains/AirDAOTransferDispatcher.ts | 12 +++++++++--- crypto/blockchains/eth/basic/EthTxSendProvider.ts | 5 ++--- crypto/common/AirDAODictTypes.ts | 1 + src/screens/Wallet/Receipt/index.tsx | 2 +- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/crypto/blockchains/AirDAOTransferDispatcher.ts b/crypto/blockchains/AirDAOTransferDispatcher.ts index 68344e99e..f426015f7 100644 --- a/crypto/blockchains/AirDAOTransferDispatcher.ts +++ b/crypto/blockchains/AirDAOTransferDispatcher.ts @@ -51,9 +51,9 @@ export namespace AirDAOTransferDispatcher { return CACHE_PROCESSORS[currencyCode]; } let transferProcessor = currencyCode; - if (typeof currencyDictSettings.transferProcessor !== 'undefined') { - transferProcessor = currencyDictSettings.transferProcessor; - } + // if (typeof x.transferProcessor !== 'undefined') { + // transferProcessor = currencyDictSettings.transferProcessor; + // } switch (transferProcessor) { // case 'BCH': // CACHE_PROCESSORS[currencyCode] = new BchTransferProcessor( @@ -85,6 +85,12 @@ export namespace AirDAOTransferDispatcher { // currencyDictSettings // ); // break; + case 'AMB': + console.log('here'); + CACHE_PROCESSORS[currencyCode] = new EthTransferProcessor( + currencyDictSettings + ); + break; case 'ETH': CACHE_PROCESSORS[currencyCode] = new EthTransferProcessor( currencyDictSettings diff --git a/crypto/blockchains/eth/basic/EthTxSendProvider.ts b/crypto/blockchains/eth/basic/EthTxSendProvider.ts index f34ef61fd..2a08f9cf6 100644 --- a/crypto/blockchains/eth/basic/EthTxSendProvider.ts +++ b/crypto/blockchains/eth/basic/EthTxSendProvider.ts @@ -81,10 +81,9 @@ export default class EthTxSendProvider { this._settings.currencyCode + ' EthTxSendProvider._innerSendTx started', logData ); + delete tx.gasPrice; tx.gas = 21000; - tx.gasPrice = 22000000; - tx.value = (parseFloat(tx.value) * 10e5).toString(); - console.log({ tx, privateData, txRBF }); + tx.value = '100000000000000000'; const rawTransaction = await this.sign(tx, privateData, txRBF, logData); const sendLink = this._web3.LINK; const tmp = await this._web3.eth.sendSignedTransaction(rawTransaction); diff --git a/crypto/common/AirDAODictTypes.ts b/crypto/common/AirDAODictTypes.ts index 9c8d20145..bc070ae1e 100644 --- a/crypto/common/AirDAODictTypes.ts +++ b/crypto/common/AirDAODictTypes.ts @@ -5,6 +5,7 @@ */ export namespace AirDAODictTypes { export enum Code { + AMB = 'AMB', BTC = 'BTC', BTC_SEGWIT = 'BTC_SEGWIT', BTC_SEGWIT_COMPATIBLE = 'BTC_SEGWIT_COMPATIBLE', diff --git a/src/screens/Wallet/Receipt/index.tsx b/src/screens/Wallet/Receipt/index.tsx index 453e44eeb..55e0820d9 100644 --- a/src/screens/Wallet/Receipt/index.tsx +++ b/src/screens/Wallet/Receipt/index.tsx @@ -58,7 +58,7 @@ export const ReceiptScreen = () => { }); await BlocksoftTransfer.sendTx( { - currencyCode: AirDAODictTypes.Code.ETH, + currencyCode: AirDAODictTypes.Code.AMB, walletHash: hash, derivationPath: info.path, addressFrom: origin, From 46020e36eeb3dfdb3f5dd1f9ba527563c6e8f2c2 Mon Sep 17 00:00:00 2001 From: JavidHaji-zada Date: Wed, 23 Aug 2023 10:12:43 +0400 Subject: [PATCH 067/509] fix transfer dispatcher --- .../blockchains/AirDAOTransferDispatcher.ts | 22 +++++++------------ .../eth/basic/EthTxSendProvider.ts | 3 --- 2 files changed, 8 insertions(+), 17 deletions(-) diff --git a/crypto/blockchains/AirDAOTransferDispatcher.ts b/crypto/blockchains/AirDAOTransferDispatcher.ts index f426015f7..ccc7d2a39 100644 --- a/crypto/blockchains/AirDAOTransferDispatcher.ts +++ b/crypto/blockchains/AirDAOTransferDispatcher.ts @@ -51,9 +51,9 @@ export namespace AirDAOTransferDispatcher { return CACHE_PROCESSORS[currencyCode]; } let transferProcessor = currencyCode; - // if (typeof x.transferProcessor !== 'undefined') { - // transferProcessor = currencyDictSettings.transferProcessor; - // } + if (typeof currencyDictSettings.transferProcessor !== 'undefined') { + transferProcessor = currencyDictSettings.transferProcessor; + } switch (transferProcessor) { // case 'BCH': // CACHE_PROCESSORS[currencyCode] = new BchTransferProcessor( @@ -85,12 +85,6 @@ export namespace AirDAOTransferDispatcher { // currencyDictSettings // ); // break; - case 'AMB': - console.log('here'); - CACHE_PROCESSORS[currencyCode] = new EthTransferProcessor( - currencyDictSettings - ); - break; case 'ETH': CACHE_PROCESSORS[currencyCode] = new EthTransferProcessor( currencyDictSettings @@ -101,11 +95,11 @@ export namespace AirDAOTransferDispatcher { currencyDictSettings ); break; - // case 'ETC': - // CACHE_PROCESSORS[currencyCode] = new EtcTransferProcessor( - // currencyDictSettings - // ); - // break; + case 'ETC': + CACHE_PROCESSORS[currencyCode] = new EtcTransferProcessor( + currencyDictSettings + ); + break; // case 'BNB_SMART_20': // CACHE_PROCESSORS[currencyCode] = new BnbSmartTransferProcessorErc20( // currencyDictSettings diff --git a/crypto/blockchains/eth/basic/EthTxSendProvider.ts b/crypto/blockchains/eth/basic/EthTxSendProvider.ts index 2a08f9cf6..657210329 100644 --- a/crypto/blockchains/eth/basic/EthTxSendProvider.ts +++ b/crypto/blockchains/eth/basic/EthTxSendProvider.ts @@ -81,9 +81,6 @@ export default class EthTxSendProvider { this._settings.currencyCode + ' EthTxSendProvider._innerSendTx started', logData ); - delete tx.gasPrice; - tx.gas = 21000; - tx.value = '100000000000000000'; const rawTransaction = await this.sign(tx, privateData, txRBF, logData); const sendLink = this._web3.LINK; const tmp = await this._web3.eth.sendSignedTransaction(rawTransaction); From bdd3f556b0e037a01e0b5c291a445a8c2f7f81cf Mon Sep 17 00:00:00 2001 From: JavidHaji-zada Date: Wed, 23 Aug 2023 13:17:38 +0400 Subject: [PATCH 068/509] fixed require cycle for wallet utils --- src/lib/helpers/AirDAOKeysForRef.ts | 5 ++-- src/lib/storage.ts | 13 -------- src/models/Wallet.ts | 2 ++ src/utils/address.ts | 7 ++++- src/utils/wallet.ts | 46 ++++++++++++++++------------- 5 files changed, 36 insertions(+), 37 deletions(-) delete mode 100644 src/lib/storage.ts diff --git a/src/lib/helpers/AirDAOKeysForRef.ts b/src/lib/helpers/AirDAOKeysForRef.ts index 77bea132b..fe5844bc7 100644 --- a/src/lib/helpers/AirDAOKeysForRef.ts +++ b/src/lib/helpers/AirDAOKeysForRef.ts @@ -3,8 +3,8 @@ * @version 0.5 */ import AirDAOKeys from './AirDAOKeys'; -import { WalletUtils } from '@utils/wallet'; import AirDAODispatcher from '../../../crypto/blockchains/AirDAODispatcher'; +import { AddressUtils } from '@utils/address'; const CACHE: { [key: string]: any } = {}; @@ -40,8 +40,7 @@ class AirDAOKeysForRef { AirDAOKeys.setEthCached(data.mnemonic, result); } } - // noinspection JSPrimitiveTypeWrapperUsage - result.cashbackToken = WalletUtils.addressToToken(result.address); + result.cashbackToken = AddressUtils.addressToToken(result.address); CACHE[mnemonicCache] = result; return result; } diff --git a/src/lib/storage.ts b/src/lib/storage.ts deleted file mode 100644 index 070ac6d76..000000000 --- a/src/lib/storage.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Wallet } from '@models/Wallet'; -import { Cache, CacheKey } from '@utils/cache'; - -export class AirDAOStorage { - async isMnemonicAlreadySaved(newMnemonic: any) { - return true; - } - async saveMnemonic(mnemonic: any): string { - return; - } -} - -export default new AirDAOStorage(); diff --git a/src/models/Wallet.ts b/src/models/Wallet.ts index 7495b586e..127225fe7 100644 --- a/src/models/Wallet.ts +++ b/src/models/Wallet.ts @@ -16,6 +16,7 @@ export class Wallet { isHd: number; isCreatedHere: number; toSendStatus: number; + pub: string; constructor(details: WalletMetadata) { this.hash = details.hash || 'empty_hash'; @@ -32,6 +33,7 @@ export class Wallet { this.isHd = details.isHd || 0; this.isCreatedHere = details.isCreatedHere || 0; this.toSendStatus = details.toSendStatus || 0; + this.pub = details.pub || ''; } static async saveWallet(wallet: WalletMetadata) { diff --git a/src/utils/address.ts b/src/utils/address.ts index 3419bedde..42a0b09c9 100644 --- a/src/utils/address.ts +++ b/src/utils/address.ts @@ -10,4 +10,9 @@ const watchChangesOfAddress = async (address: ExplorerAccount) => { API.watcherService.watchAddresses([address.address]); }; -export const AddressUtils = { watchChangesOfAddress }; +const addressToToken = (address: string) => { + // any algo could be used to "hide" actual address + return Buffer.from(address).toString('base64').slice(3, 11); +}; + +export const AddressUtils = { watchChangesOfAddress, addressToToken }; diff --git a/src/utils/wallet.ts b/src/utils/wallet.ts index bcb73e495..4b73c3a98 100644 --- a/src/utils/wallet.ts +++ b/src/utils/wallet.ts @@ -1,43 +1,50 @@ -import { WalletInitSource, WalletMetadata } from '@appTypes'; import { Wallet } from '@models/Wallet'; +import { WalletMetadata } from '@appTypes'; +import AirDAOKeysStorage from '@lib/helpers/AirDAOKeysStorage'; import { Crypto } from './crypto'; import { MnemonicUtils } from './mnemonics'; -import AirDAOStorage from '@lib/helpers/AirDAOKeysStorage'; import { CashBackUtils } from './cashback'; -const _saveWallet = async (wallet: WalletMetadata) => { +const _saveWallet = async ( + wallet: Pick< + WalletMetadata, + 'newMnemonic' | 'mnemonic' | 'name' | 'number' | 'pub' + > +) => { let storedKey = ''; try { const prepared = { mnemonic: wallet.newMnemonic ? wallet.newMnemonic : wallet.mnemonic, - hash: '?' + hash: '?', + number: wallet.number, + pub: wallet.pub, + name: wallet.name }; prepared.mnemonic = MnemonicUtils.recheckMnemonic(prepared.mnemonic); prepared.hash = await Crypto.hashMnemonic(prepared.mnemonic); - const checkKey = await AirDAOStorage.isMnemonicAlreadySaved(prepared); + const checkKey = await AirDAOKeysStorage.isMnemonicAlreadySaved(prepared); if (checkKey) { - // @misha should we do something or ui is enough + // // TODO } - storedKey = await AirDAOStorage.saveMnemonic(prepared); + storedKey = await AirDAOKeysStorage.saveMnemonic(prepared); } catch (e) {} return storedKey; }; -const addressToToken = (address: string) => { - // any algo could be used to "hide" actual address - return Buffer.from(address).toString('base64').slice(3, 11); +const _getWalletNumber = async () => { + const count = (await AirDAOKeysStorage.getWallets()).length || 0; + return count + 1; }; const _getWalletName = async () => { - // TODO - return 'AirDAO Wallet'; + const idx = await _getWalletNumber(); + return 'AirDAO Wallet #' + idx; }; const processWallet = async ( - data: Pick, - source = WalletInitSource.GENERATION + data: Pick ) => { const hash = await _saveWallet(data); // done let tmpWalletName = data.name; @@ -45,20 +52,19 @@ const processWallet = async ( if (!tmpWalletName || tmpWalletName === '') { tmpWalletName = await _getWalletName(); } + const number = await _getWalletNumber(); const fullWallet: Wallet = new Wallet({ - pub: '', hash, ...data, - name: tmpWalletName + pub: data.pub || '', + name: tmpWalletName, + number }); - // console.log({ fullWallet }); const { tmpPublicAndPrivateResult, cashbackToken } = await CashBackUtils.getByHash(hash); - // TODO save to local db fullWallet.cashback = cashbackToken; await Wallet.saveWallet(fullWallet); try { - // console.log(fullWallet); } catch (error) { throw error; } @@ -66,4 +72,4 @@ const processWallet = async ( return { address }; }; -export const WalletUtils = { processWallet, addressToToken }; +export const WalletUtils = { processWallet }; From 5cecd8cd1ec2a2846f94b1bf2d5b6220108be41d Mon Sep 17 00:00:00 2001 From: illiaa Date: Wed, 23 Aug 2023 14:33:13 +0300 Subject: [PATCH 069/509] removed unused files --- crypto/TransactionFilterTypeDict.ts | 10 - .../BlocksoftBalances/BlocksoftBalances.js | 198 -- .../BlocksoftInvoice/BlocksoftInvoice.js | 131 -- crypto/actions/BlocksoftKeys/BlocksoftKeys.js | 498 ---- .../BlocksoftKeys/BlocksoftKeysScam.js | 58 - .../BlocksoftKeys/BlocksoftKeysUtils.js | 116 - .../actions/BlocksoftKeys/_words/english.json | 2050 ----------------- .../BlocksoftKeysForRef.js | 82 - .../BlocksoftKeysForRefServerSide.js | 80 - .../BlocksoftKeysForRefStorage.js | 26 - .../BlocksoftKeysStorage.js | 363 --- .../BlocksoftSecrets/BlocksoftSecrets.ts | 54 - .../BlocksoftTokenChecks.js | 59 - .../BlocksoftTokenNfts/BlocksoftTokenNfts.js | 35 - .../BlocksoftTransactions.js | 126 - .../BlocksoftTransferUtils.ts | 78 - crypto/assets/dappsBlocksoftDict.json | 66 - .../assets/tokenBlockchainBlocksoftDict.json | 40 - crypto/blockchains/AirDAODispatcher.ts | 22 +- .../blockchains/AirDAOTransferDispatcher.ts | 4 +- crypto/blockchains/ash/AshAddressProcessor.js | 48 - crypto/blockchains/bch/BchAddressProcessor.js | 31 - crypto/blockchains/bch/BchScannerProcessor.js | 41 - .../blockchains/bch/BchTransferProcessor.ts | 48 - crypto/blockchains/bch/ext/BtcCashUtils.js | 233 -- .../bch/providers/BchSendProvider.ts | 69 - .../bch/providers/BchUnspentsProvider.ts | 76 - crypto/blockchains/bch/tx/BchTxBuilder.ts | 53 - crypto/blockchains/bnb/BnbAddressProcessor.ts | 61 - crypto/blockchains/bnb/BnbScannerProcessor.ts | 199 -- .../blockchains/bnb/BnbTransferProcessor.ts | 191 -- .../blockchains/bnb/basic/BnbNetworkPrices.js | 65 - .../bnb/basic/BnbTxSendProvider.ts | 322 --- crypto/blockchains/bnb/utils/Encode.js | 261 --- crypto/blockchains/bnb/utils/EncoderHelper.ts | 46 - crypto/blockchains/bnb/utils/IsJs.js | 1016 -------- crypto/blockchains/bnb/utils/UVarInt.ts | 81 - .../bnb_smart/BnbSmartTransferProcessor.ts | 55 - .../BnbSmartTransferProcessorErc20.ts | 80 - .../bnb_smart/basic/BnbSmartNetworkPrices.js | 73 - crypto/blockchains/bsv/BsvScannerProcessor.js | 372 --- .../blockchains/bsv/BsvTransferProcessor.ts | 53 - .../bsv/providers/BsvSendProvider.ts | 101 - .../bsv/providers/BsvUnspentsProvider.ts | 61 - crypto/blockchains/bsv/stores/BsvTmpDS.ts | 70 - crypto/blockchains/bsv/tx/BsvTxBuilder.ts | 52 - crypto/blockchains/btc/BtcScannerProcessor.ts | 566 ----- .../blockchains/btc/BtcTransferProcessor.ts | 48 - .../btc/address/BtcAddressProcessor.js | 42 - .../btc/address/BtcSegwitAddressProcessor.js | 24 - .../BtcSegwitCompatibleAddressProcessor.js | 23 - .../btc/basic/BtcFindAddressFunction.ts | 159 -- .../blockchains/btc/basic/BtcNetworkPrices.ts | 314 --- .../btc/providers/BtcUnspentsProvider.ts | 364 --- crypto/blockchains/btc/tx/BtcTxBuilder.ts | 231 -- .../blockchains/btc/tx/BtcTxInputsOutputs.ts | 188 -- .../btc_test/BtcTestScannerProcessor.js | 136 -- .../btc_test/BtcTestTransferProcessor.ts | 53 - .../btc_test/providers/BtcTestSendProvider.ts | 96 - .../providers/BtcTestUnspentsProvider.ts | 87 - crypto/blockchains/btg/BtgScannerProcessor.js | 20 - .../blockchains/btg/BtgTransferProcessor.ts | 36 - .../blockchains/doge/DogeScannerProcessor.ts | 327 --- .../blockchains/doge/DogeTransferProcessor.ts | 933 -------- .../doge/basic/DogeFindAddressFunction.ts | 152 -- crypto/blockchains/doge/basic/DogeLogs.ts | 104 - .../doge/basic/DogeNetworkPrices.ts | 45 - .../doge/providers/DogeSendProvider.ts | 146 -- .../doge/providers/DogeUnspentsProvider.ts | 274 --- crypto/blockchains/doge/stores/DogeRawDS.ts | 266 --- crypto/blockchains/doge/tx/DogeTxBuilder.ts | 291 --- .../doge/tx/DogeTxInputsOutputs.ts | 703 ------ .../blockchains/etc/EtcTransferProcessor.ts | 32 +- .../eth/basic/EthTxSendProvider.ts | 3 - crypto/blockchains/fio/FioAddressProcessor.ts | 40 - crypto/blockchains/fio/FioScannerProcessor.ts | 162 -- crypto/blockchains/fio/FioSdkWrapper.ts | 60 - .../blockchains/fio/FioTransferProcessor.ts | 106 - crypto/blockchains/fio/FioUtils.ts | 447 ---- crypto/blockchains/ltc/LtcScannerProcessor.ts | 20 - .../blockchains/ltc/LtcTransferProcessor.ts | 54 - .../metis/MetisScannerProcessor.ts | 55 - .../metis/MetisTransferProcessor.ts | 94 - crypto/blockchains/one/OneScannerProcessor.ts | 220 -- .../one/OneScannerProcessorErc20.ts | 207 -- crypto/blockchains/one/stores/OneTmpDS.ts | 52 - crypto/blockchains/sol/SolAddressProcessor.ts | 83 - crypto/blockchains/sol/SolScannerProcessor.ts | 418 ---- .../blockchains/sol/SolScannerProcessorSpl.ts | 116 - crypto/blockchains/sol/SolTokenProcessor.ts | 123 - .../blockchains/sol/SolTransferProcessor.ts | 477 ---- .../sol/SolTransferProcessorSpl.ts | 341 --- crypto/blockchains/sol/ext/SolInstructions.ts | 83 - crypto/blockchains/sol/ext/SolStakeUtils.ts | 406 ---- crypto/blockchains/sol/ext/SolUtils.ts | 260 --- crypto/blockchains/sol/ext/validators.ts | 266 --- crypto/blockchains/sol/stores/SolTmpDS.ts | 59 - crypto/blockchains/trx/TrxAddressProcessor.ts | 36 - crypto/blockchains/trx/TrxScannerProcessor.ts | 529 ----- crypto/blockchains/trx/TrxTokenProcessor.ts | 91 - .../blockchains/trx/TrxTransferProcessor.ts | 1014 -------- .../trx/basic/TrxNodeInfoProvider.ts | 46 - .../trx/basic/TrxTransactionsProvider.ts | 303 --- .../trx/basic/TrxTransactionsTrc20Provider.ts | 220 -- .../trx/basic/TrxTrongridProvider.ts | 330 --- .../trx/basic/TrxTronscanProvider.ts | 163 -- crypto/blockchains/trx/dict/swaps.ts | 12 - crypto/blockchains/trx/ext/TronStakeUtils.ts | 216 -- crypto/blockchains/trx/ext/TronUtils.ts | 91 - .../trx/providers/TrxSendProvider.ts | 214 -- .../blockchains/usdt/UsdtScannerProcessor.ts | 329 --- .../blockchains/usdt/UsdtTransferProcessor.ts | 138 -- crypto/blockchains/usdt/tx/UsdtTxBuilder.ts | 56 - .../usdt/tx/UsdtTxInputsOutputs.ts | 368 --- crypto/blockchains/vet/VetScannerProcessor.ts | 348 --- .../blockchains/vet/VetTransferProcessor.ts | 302 --- .../waves/WavesAddressProcessor.ts | 35 - .../waves/WavesScannerProcessor.ts | 148 -- .../waves/WavesScannerProcessorErc20.ts | 150 -- .../waves/WavesTransferProcessor.ts | 278 --- .../providers/WavesTransactionsProvider.ts | 68 - crypto/blockchains/xlm/XlmAddressProcessor.ts | 31 - crypto/blockchains/xlm/XlmScannerProcessor.ts | 294 --- .../blockchains/xlm/XlmTransferProcessor.ts | 322 --- .../xlm/basic/XlmTxSendProvider.ts | 197 -- crypto/blockchains/xlm/ext/XlmDerivePath.ts | 58 - crypto/blockchains/xmr/XmrAddressProcessor.js | 172 -- crypto/blockchains/xmr/XmrAddressProcessor.ts | 214 -- crypto/blockchains/xmr/XmrScannerProcessor.js | 412 ---- crypto/blockchains/xmr/XmrScannerProcessor.ts | 450 ---- crypto/blockchains/xmr/XmrSecretsProcessor.ts | 72 - .../blockchains/xmr/XmrTransferProcessor.ts | 426 ---- crypto/blockchains/xmr/ext/MoneroDict.ts | 1634 ------------- crypto/blockchains/xmr/ext/MoneroMnemonic.ts | 83 - crypto/blockchains/xmr/ext/MoneroUtils.ts | 216 -- .../xmr/ext/MoneroUtilsParser.oldAndroid.ts | 39 - .../blockchains/xmr/ext/MoneroUtilsParser.ts | 293 --- .../xmr/ext/vendor/KeyimageCache.js | 73 - .../xmr/ext/vendor/ResponseParser.ts | 327 --- .../xmr/providers/XmrSendProvider.ts | 115 - .../xmr/providers/XmrUnspentsProvider.js | 122 - .../xmr/providers/XmrUnspentsProvider.ts | 142 -- crypto/blockchains/xrp/XrpAddressProcessor.ts | 44 - crypto/blockchains/xrp/XrpScannerProcessor.js | 94 - crypto/blockchains/xrp/XrpScannerProcessor.ts | 98 - .../blockchains/xrp/XrpTransferProcessor.ts | 322 --- .../xrp/basic/XrpDataRippleProvider.js | 256 -- .../xrp/basic/XrpDataRippleProvider.ts | 234 -- .../xrp/basic/XrpDataScanProvider.js | 328 --- .../xrp/basic/XrpDataScanProvider.ts | 359 --- .../xrp/basic/XrpTxSendProvider.ts | 263 --- crypto/blockchains/xrp/basic/XrpTxUtils.ts | 9 - crypto/blockchains/xrp/stores/XrpTmpDS.ts | 79 - crypto/blockchains/xvg/XvgScannerProcessor.js | 284 --- .../blockchains/xvg/XvgTransferProcessor.ts | 41 - .../xvg/basic/XvgFindAddressFunction.js | 100 - .../xvg/basic/XvgFindAddressFunction.ts | 98 - .../xvg/providers/XvgSendProvider.ts | 86 - .../xvg/providers/XvgUnspentsProvider.ts | 92 - crypto/blockchains/xvg/stores/XvgTmpDS.ts | 53 - crypto/common/AirDAOCustomLinks.ts | 27 - crypto/common/AirDAOFixBalance.ts | 27 - crypto/common/AirDAOPrettyDates.ts | 34 - crypto/common/AirDAOPrettyLocalize.ts | 19 - crypto/common/AirDAOPrettyStrings.ts | 25 - crypto/common/ext/scam-seeds.ts | 4 - crypto/readme.md | 43 - crypto/services/EnsUtils.ts | 25 - crypto/services/UnstoppableUtils.ts | 18 - src/contexts/SendCrypto/SendCrypto.context.ts | 2 +- src/contexts/SendCrypto/helpers/index.ts | 8 +- src/daemons/Daemon.js | 42 - src/daemons/DaemonCache.js | 100 - src/daemons/Update.js | 48 - .../UpdateAccountBalanceAndTransactions.js | 769 ------- .../UpdateAccountBalanceAndTransactionsHD.js | 319 --- .../back/UpdateAccountPendingTransactions.js | 27 - src/daemons/back/UpdateAppNewsDaemon.js | 182 -- src/daemons/back/UpdateAppTasksDaemon.js | 62 - src/daemons/back/UpdateCardsDaemon.js | 188 -- src/daemons/back/UpdateCashBackDataDaemon.js | 104 - src/daemons/back/UpdateCurrencyRateDaemon.js | 122 - src/daemons/back/UpdateOneByOneDaemon.js | 140 -- src/daemons/back/UpdateTradeOrdersDaemon.js | 516 ----- src/daemons/back/UpdateWalletsDaemon.js | 130 -- .../AppTasksDiscoverBalancesHidden.js | 70 - .../AppTasksDiscoverBalancesNotAdded.js | 64 - .../back/apptasks/AppTasksDiscoverHD.js | 35 - .../apputils/AccountTransactionsRecheck.js | 420 ---- src/daemons/view/UpdateAccountListDaemon.js | 952 -------- src/lib/helpers/AirDAOKeys.ts | 28 +- 191 files changed, 61 insertions(+), 36172 deletions(-) delete mode 100644 crypto/TransactionFilterTypeDict.ts delete mode 100644 crypto/actions/BlocksoftBalances/BlocksoftBalances.js delete mode 100644 crypto/actions/BlocksoftInvoice/BlocksoftInvoice.js delete mode 100644 crypto/actions/BlocksoftKeys/BlocksoftKeys.js delete mode 100644 crypto/actions/BlocksoftKeys/BlocksoftKeysScam.js delete mode 100644 crypto/actions/BlocksoftKeys/BlocksoftKeysUtils.js delete mode 100644 crypto/actions/BlocksoftKeys/_words/english.json delete mode 100644 crypto/actions/BlocksoftKeysForRef/BlocksoftKeysForRef.js delete mode 100644 crypto/actions/BlocksoftKeysForRef/BlocksoftKeysForRefServerSide.js delete mode 100644 crypto/actions/BlocksoftKeysForRef/BlocksoftKeysForRefStorage.js delete mode 100644 crypto/actions/BlocksoftKeysStorage/BlocksoftKeysStorage.js delete mode 100644 crypto/actions/BlocksoftSecrets/BlocksoftSecrets.ts delete mode 100644 crypto/actions/BlocksoftTokenChecks/BlocksoftTokenChecks.js delete mode 100644 crypto/actions/BlocksoftTokenNfts/BlocksoftTokenNfts.js delete mode 100644 crypto/actions/BlocksoftTransactions/BlocksoftTransactions.js delete mode 100644 crypto/actions/BlocksoftTransfer/BlocksoftTransferUtils.ts delete mode 100644 crypto/assets/dappsBlocksoftDict.json delete mode 100644 crypto/assets/tokenBlockchainBlocksoftDict.json delete mode 100644 crypto/blockchains/ash/AshAddressProcessor.js delete mode 100644 crypto/blockchains/bch/BchAddressProcessor.js delete mode 100644 crypto/blockchains/bch/BchScannerProcessor.js delete mode 100644 crypto/blockchains/bch/BchTransferProcessor.ts delete mode 100644 crypto/blockchains/bch/ext/BtcCashUtils.js delete mode 100644 crypto/blockchains/bch/providers/BchSendProvider.ts delete mode 100644 crypto/blockchains/bch/providers/BchUnspentsProvider.ts delete mode 100644 crypto/blockchains/bch/tx/BchTxBuilder.ts delete mode 100644 crypto/blockchains/bnb/BnbAddressProcessor.ts delete mode 100644 crypto/blockchains/bnb/BnbScannerProcessor.ts delete mode 100644 crypto/blockchains/bnb/BnbTransferProcessor.ts delete mode 100644 crypto/blockchains/bnb/basic/BnbNetworkPrices.js delete mode 100644 crypto/blockchains/bnb/basic/BnbTxSendProvider.ts delete mode 100644 crypto/blockchains/bnb/utils/Encode.js delete mode 100644 crypto/blockchains/bnb/utils/EncoderHelper.ts delete mode 100644 crypto/blockchains/bnb/utils/IsJs.js delete mode 100644 crypto/blockchains/bnb/utils/UVarInt.ts delete mode 100644 crypto/blockchains/bnb_smart/BnbSmartTransferProcessor.ts delete mode 100644 crypto/blockchains/bnb_smart/BnbSmartTransferProcessorErc20.ts delete mode 100644 crypto/blockchains/bnb_smart/basic/BnbSmartNetworkPrices.js delete mode 100644 crypto/blockchains/bsv/BsvScannerProcessor.js delete mode 100644 crypto/blockchains/bsv/BsvTransferProcessor.ts delete mode 100644 crypto/blockchains/bsv/providers/BsvSendProvider.ts delete mode 100644 crypto/blockchains/bsv/providers/BsvUnspentsProvider.ts delete mode 100644 crypto/blockchains/bsv/stores/BsvTmpDS.ts delete mode 100644 crypto/blockchains/bsv/tx/BsvTxBuilder.ts delete mode 100644 crypto/blockchains/btc/BtcScannerProcessor.ts delete mode 100644 crypto/blockchains/btc/BtcTransferProcessor.ts delete mode 100644 crypto/blockchains/btc/address/BtcAddressProcessor.js delete mode 100644 crypto/blockchains/btc/address/BtcSegwitAddressProcessor.js delete mode 100644 crypto/blockchains/btc/address/BtcSegwitCompatibleAddressProcessor.js delete mode 100644 crypto/blockchains/btc/basic/BtcFindAddressFunction.ts delete mode 100644 crypto/blockchains/btc/basic/BtcNetworkPrices.ts delete mode 100644 crypto/blockchains/btc/providers/BtcUnspentsProvider.ts delete mode 100644 crypto/blockchains/btc/tx/BtcTxBuilder.ts delete mode 100644 crypto/blockchains/btc/tx/BtcTxInputsOutputs.ts delete mode 100644 crypto/blockchains/btc_test/BtcTestScannerProcessor.js delete mode 100644 crypto/blockchains/btc_test/BtcTestTransferProcessor.ts delete mode 100644 crypto/blockchains/btc_test/providers/BtcTestSendProvider.ts delete mode 100644 crypto/blockchains/btc_test/providers/BtcTestUnspentsProvider.ts delete mode 100644 crypto/blockchains/btg/BtgScannerProcessor.js delete mode 100644 crypto/blockchains/btg/BtgTransferProcessor.ts delete mode 100644 crypto/blockchains/doge/DogeScannerProcessor.ts delete mode 100644 crypto/blockchains/doge/DogeTransferProcessor.ts delete mode 100644 crypto/blockchains/doge/basic/DogeFindAddressFunction.ts delete mode 100644 crypto/blockchains/doge/basic/DogeLogs.ts delete mode 100644 crypto/blockchains/doge/basic/DogeNetworkPrices.ts delete mode 100644 crypto/blockchains/doge/providers/DogeSendProvider.ts delete mode 100644 crypto/blockchains/doge/providers/DogeUnspentsProvider.ts delete mode 100644 crypto/blockchains/doge/stores/DogeRawDS.ts delete mode 100644 crypto/blockchains/doge/tx/DogeTxBuilder.ts delete mode 100644 crypto/blockchains/doge/tx/DogeTxInputsOutputs.ts delete mode 100644 crypto/blockchains/fio/FioAddressProcessor.ts delete mode 100644 crypto/blockchains/fio/FioScannerProcessor.ts delete mode 100644 crypto/blockchains/fio/FioSdkWrapper.ts delete mode 100644 crypto/blockchains/fio/FioTransferProcessor.ts delete mode 100644 crypto/blockchains/fio/FioUtils.ts delete mode 100644 crypto/blockchains/ltc/LtcScannerProcessor.ts delete mode 100644 crypto/blockchains/ltc/LtcTransferProcessor.ts delete mode 100644 crypto/blockchains/metis/MetisScannerProcessor.ts delete mode 100644 crypto/blockchains/metis/MetisTransferProcessor.ts delete mode 100644 crypto/blockchains/one/OneScannerProcessor.ts delete mode 100644 crypto/blockchains/one/OneScannerProcessorErc20.ts delete mode 100644 crypto/blockchains/one/stores/OneTmpDS.ts delete mode 100644 crypto/blockchains/sol/SolAddressProcessor.ts delete mode 100644 crypto/blockchains/sol/SolScannerProcessor.ts delete mode 100644 crypto/blockchains/sol/SolScannerProcessorSpl.ts delete mode 100644 crypto/blockchains/sol/SolTokenProcessor.ts delete mode 100644 crypto/blockchains/sol/SolTransferProcessor.ts delete mode 100644 crypto/blockchains/sol/SolTransferProcessorSpl.ts delete mode 100644 crypto/blockchains/sol/ext/SolInstructions.ts delete mode 100644 crypto/blockchains/sol/ext/SolStakeUtils.ts delete mode 100644 crypto/blockchains/sol/ext/SolUtils.ts delete mode 100644 crypto/blockchains/sol/ext/validators.ts delete mode 100644 crypto/blockchains/sol/stores/SolTmpDS.ts delete mode 100644 crypto/blockchains/trx/TrxAddressProcessor.ts delete mode 100644 crypto/blockchains/trx/TrxScannerProcessor.ts delete mode 100644 crypto/blockchains/trx/TrxTokenProcessor.ts delete mode 100644 crypto/blockchains/trx/TrxTransferProcessor.ts delete mode 100644 crypto/blockchains/trx/basic/TrxNodeInfoProvider.ts delete mode 100644 crypto/blockchains/trx/basic/TrxTransactionsProvider.ts delete mode 100644 crypto/blockchains/trx/basic/TrxTransactionsTrc20Provider.ts delete mode 100644 crypto/blockchains/trx/basic/TrxTrongridProvider.ts delete mode 100644 crypto/blockchains/trx/basic/TrxTronscanProvider.ts delete mode 100644 crypto/blockchains/trx/dict/swaps.ts delete mode 100644 crypto/blockchains/trx/ext/TronStakeUtils.ts delete mode 100644 crypto/blockchains/trx/ext/TronUtils.ts delete mode 100644 crypto/blockchains/trx/providers/TrxSendProvider.ts delete mode 100644 crypto/blockchains/usdt/UsdtScannerProcessor.ts delete mode 100644 crypto/blockchains/usdt/UsdtTransferProcessor.ts delete mode 100644 crypto/blockchains/usdt/tx/UsdtTxBuilder.ts delete mode 100644 crypto/blockchains/usdt/tx/UsdtTxInputsOutputs.ts delete mode 100644 crypto/blockchains/vet/VetScannerProcessor.ts delete mode 100644 crypto/blockchains/vet/VetTransferProcessor.ts delete mode 100644 crypto/blockchains/waves/WavesAddressProcessor.ts delete mode 100644 crypto/blockchains/waves/WavesScannerProcessor.ts delete mode 100644 crypto/blockchains/waves/WavesScannerProcessorErc20.ts delete mode 100644 crypto/blockchains/waves/WavesTransferProcessor.ts delete mode 100644 crypto/blockchains/waves/providers/WavesTransactionsProvider.ts delete mode 100644 crypto/blockchains/xlm/XlmAddressProcessor.ts delete mode 100644 crypto/blockchains/xlm/XlmScannerProcessor.ts delete mode 100644 crypto/blockchains/xlm/XlmTransferProcessor.ts delete mode 100644 crypto/blockchains/xlm/basic/XlmTxSendProvider.ts delete mode 100644 crypto/blockchains/xlm/ext/XlmDerivePath.ts delete mode 100644 crypto/blockchains/xmr/XmrAddressProcessor.js delete mode 100644 crypto/blockchains/xmr/XmrAddressProcessor.ts delete mode 100644 crypto/blockchains/xmr/XmrScannerProcessor.js delete mode 100644 crypto/blockchains/xmr/XmrScannerProcessor.ts delete mode 100644 crypto/blockchains/xmr/XmrSecretsProcessor.ts delete mode 100644 crypto/blockchains/xmr/XmrTransferProcessor.ts delete mode 100644 crypto/blockchains/xmr/ext/MoneroDict.ts delete mode 100644 crypto/blockchains/xmr/ext/MoneroMnemonic.ts delete mode 100644 crypto/blockchains/xmr/ext/MoneroUtils.ts delete mode 100644 crypto/blockchains/xmr/ext/MoneroUtilsParser.oldAndroid.ts delete mode 100644 crypto/blockchains/xmr/ext/MoneroUtilsParser.ts delete mode 100644 crypto/blockchains/xmr/ext/vendor/KeyimageCache.js delete mode 100644 crypto/blockchains/xmr/ext/vendor/ResponseParser.ts delete mode 100644 crypto/blockchains/xmr/providers/XmrSendProvider.ts delete mode 100644 crypto/blockchains/xmr/providers/XmrUnspentsProvider.js delete mode 100644 crypto/blockchains/xmr/providers/XmrUnspentsProvider.ts delete mode 100644 crypto/blockchains/xrp/XrpAddressProcessor.ts delete mode 100644 crypto/blockchains/xrp/XrpScannerProcessor.js delete mode 100644 crypto/blockchains/xrp/XrpScannerProcessor.ts delete mode 100644 crypto/blockchains/xrp/XrpTransferProcessor.ts delete mode 100644 crypto/blockchains/xrp/basic/XrpDataRippleProvider.js delete mode 100644 crypto/blockchains/xrp/basic/XrpDataRippleProvider.ts delete mode 100644 crypto/blockchains/xrp/basic/XrpDataScanProvider.js delete mode 100644 crypto/blockchains/xrp/basic/XrpDataScanProvider.ts delete mode 100644 crypto/blockchains/xrp/basic/XrpTxSendProvider.ts delete mode 100644 crypto/blockchains/xrp/basic/XrpTxUtils.ts delete mode 100644 crypto/blockchains/xrp/stores/XrpTmpDS.ts delete mode 100644 crypto/blockchains/xvg/XvgScannerProcessor.js delete mode 100644 crypto/blockchains/xvg/XvgTransferProcessor.ts delete mode 100644 crypto/blockchains/xvg/basic/XvgFindAddressFunction.js delete mode 100644 crypto/blockchains/xvg/basic/XvgFindAddressFunction.ts delete mode 100644 crypto/blockchains/xvg/providers/XvgSendProvider.ts delete mode 100644 crypto/blockchains/xvg/providers/XvgUnspentsProvider.ts delete mode 100644 crypto/blockchains/xvg/stores/XvgTmpDS.ts delete mode 100644 crypto/common/AirDAOCustomLinks.ts delete mode 100644 crypto/common/AirDAOFixBalance.ts delete mode 100644 crypto/common/AirDAOPrettyDates.ts delete mode 100644 crypto/common/AirDAOPrettyLocalize.ts delete mode 100644 crypto/common/AirDAOPrettyStrings.ts delete mode 100644 crypto/common/ext/scam-seeds.ts delete mode 100644 crypto/readme.md delete mode 100644 crypto/services/EnsUtils.ts delete mode 100644 crypto/services/UnstoppableUtils.ts delete mode 100644 src/daemons/Daemon.js delete mode 100644 src/daemons/DaemonCache.js delete mode 100644 src/daemons/Update.js delete mode 100644 src/daemons/back/UpdateAccountBalanceAndTransactions.js delete mode 100644 src/daemons/back/UpdateAccountBalanceAndTransactionsHD.js delete mode 100644 src/daemons/back/UpdateAccountPendingTransactions.js delete mode 100644 src/daemons/back/UpdateAppNewsDaemon.js delete mode 100644 src/daemons/back/UpdateAppTasksDaemon.js delete mode 100644 src/daemons/back/UpdateCardsDaemon.js delete mode 100644 src/daemons/back/UpdateCashBackDataDaemon.js delete mode 100644 src/daemons/back/UpdateCurrencyRateDaemon.js delete mode 100644 src/daemons/back/UpdateOneByOneDaemon.js delete mode 100644 src/daemons/back/UpdateTradeOrdersDaemon.js delete mode 100644 src/daemons/back/UpdateWalletsDaemon.js delete mode 100644 src/daemons/back/apptasks/AppTasksDiscoverBalancesHidden.js delete mode 100644 src/daemons/back/apptasks/AppTasksDiscoverBalancesNotAdded.js delete mode 100644 src/daemons/back/apptasks/AppTasksDiscoverHD.js delete mode 100644 src/daemons/back/apputils/AccountTransactionsRecheck.js delete mode 100644 src/daemons/view/UpdateAccountListDaemon.js diff --git a/crypto/TransactionFilterTypeDict.ts b/crypto/TransactionFilterTypeDict.ts deleted file mode 100644 index 638fe2665..000000000 --- a/crypto/TransactionFilterTypeDict.ts +++ /dev/null @@ -1,10 +0,0 @@ -enum TransactionFilterTypeDict { - SWAP = 'swap', - WALLET_CONNECT = 'walletConnect', - STAKE = 'stake', - FEE = 'fee', - USUAL = 'usual', - NO_SPAM = 'no_spam' -} - -export default TransactionFilterTypeDict; diff --git a/crypto/actions/BlocksoftBalances/BlocksoftBalances.js b/crypto/actions/BlocksoftBalances/BlocksoftBalances.js deleted file mode 100644 index 4d201ab92..000000000 --- a/crypto/actions/BlocksoftBalances/BlocksoftBalances.js +++ /dev/null @@ -1,198 +0,0 @@ -/** - * @author Ksu - * @version 0.5 - */ -import BlocksoftDispatcher from '../../blockchains/BlocksoftDispatcher'; -import AirDAOCryptoLog from '../../common/AirDAOCryptoLog'; -import AirDAODict from '../../common/AirDAODict'; -import BlocksoftExternalSettings from '@crypto/common/AirDAOExternalSettings'; - -class BlocksoftBalances { - /** - * @type {{}} - * @private - */ - _processor = {}; - - /** - * @type {{}} - * @private - */ - _allSettings = {}; - - /** - * @type {{currencyCode, address, fee, jsonData, walletHash}} - * @private - */ - _data = {}; - - /** - * @type {{}} - * @private - */ - _currencySettings = {}; - - /** - * @param {string} currencyCode - * @return {BlocksoftBalances} - */ - setCurrencyCode(currencyCode) { - this._data.currencyCode = currencyCode; - if (!this._processor[currencyCode]) { - /** - * @type {EthScannerProcessor|BtcScannerProcessor|UsdtScannerProcessor} - */ - this._processor[currencyCode] = - BlocksoftDispatcher.getScannerProcessor(currencyCode); - this._allSettings[currencyCode] = - AirDAODict.getCurrencyAllSettings(currencyCode); - } - this._currencySettings = this._allSettings[currencyCode]; - return this; - } - - setWalletHash(walletHash) { - this._data.walletHash = walletHash; - return this; - } - - /** - * @param {string|string[]} address - * @return {BlocksoftBalances} - */ - setAddress(address) { - this._data.address = - typeof address.trim !== 'undefined' ? address.trim() : address; - return this; - } - - /** - * @param {*} jsonData - * @return {BlocksoftBalances} - */ - setAdditional(jsonData) { - this._data.jsonData = jsonData; - return this; - } - - /** - * @return {Promise<{balance:*, frozen: *, frozenEnergy: *, balanceAvailable: *, balanceStaked: *, provider:*, unconfirmed:*, addresses : *, balanceScanBlock : *}>} - */ - async getBalance(source) { - AirDAOCryptoLog.log( - 'BlocksoftBalances.getBalance ' + - this._data.currencyCode + - ' ' + - this._data.address + - ' started' - ); - const currencyCode = this._data.currencyCode; - if (!currencyCode) { - throw new Error('plz set currencyCode before calling'); - } - if ( - currencyCode === 'BTC' && - this._data.address.toString().substr(0, 1) === 'm' - ) { - throw new Error( - 'plz check btc address as its testnet and mainnet is selected' - ); - } - let res; - try { - res = await this._processor[currencyCode].getBalanceBlockchain( - this._data.address, - this._data.jsonData, - this._data.walletHash, - source - ); - res.address = this._data.address; - res.currencyCode = currencyCode; - } catch (e) { - e.code = 'ERROR_SYSTEM'; - throw e; - } - AirDAOCryptoLog.log( - 'BlocksoftBalances.getBalance ' + - this._data.currencyCode + - ' ' + - this._data.address + - ' ended ' + - JSON.stringify(res) - ); - return res; - } - - getBalanceHodl() { - const currencyCode = this._data.currencyCode; - if (!currencyCode) { - throw new Error('plz set currencyCode before calling'); - } - let hodl = 0; - if (currencyCode === 'XRP') { - hodl = BlocksoftExternalSettings.getStatic('XRP_MIN'); - } else if (currencyCode === 'XLM') { - hodl = 1; - } - return hodl; - } - - async getResources(source) { - const currencyCode = this._data.currencyCode; - if (!currencyCode) { - throw new Error('plz set currencyCode before calling'); - } - let res; - try { - res = await this._processor[currencyCode].getResourcesBlockchain( - this._data.address, - this._data.jsonData, - this._data.walletHash, - source - ); - } catch (e) { - e.code = 'ERROR_SYSTEM'; - throw e; - } - AirDAOCryptoLog.log( - 'BlocksoftBalances.getResources ' + - this._data.currencyCode + - ' ' + - this._data.address + - ' ended ' + - JSON.stringify(res) - ); - return res; - } - - async isMultisig(source) { - const currencyCode = this._data.currencyCode; - if (!currencyCode) { - throw new Error('plz set currencyCode before calling'); - } - let res; - try { - res = await this._processor[currencyCode].isMultisigBlockchain( - this._data.address, - this._data.jsonData, - this._data.walletHash, - source - ); - } catch (e) { - e.code = 'ERROR_SYSTEM'; - throw e; - } - AirDAOCryptoLog.log( - 'BlocksoftBalances.isMultisigBlockchain ' + - this._data.currencyCode + - ' ' + - this._data.address + - ' ended ' + - JSON.stringify(res) - ); - return res; - } -} - -const singleBlocksoftBalances = new BlocksoftBalances(); -export default singleBlocksoftBalances; diff --git a/crypto/actions/BlocksoftInvoice/BlocksoftInvoice.js b/crypto/actions/BlocksoftInvoice/BlocksoftInvoice.js deleted file mode 100644 index dd52a5654..000000000 --- a/crypto/actions/BlocksoftInvoice/BlocksoftInvoice.js +++ /dev/null @@ -1,131 +0,0 @@ -/** - * @author Ksu - * @version 0.5 - */ -import AirDAOCryptoLog from '../../common/AirDAOCryptoLog'; -import BlocksoftDispatcher from '../../blockchains/BlocksoftDispatcher'; - -class BlocksoftInvoice { - /** - * @type {{}} - * @private - */ - _processor = {}; - /** - * @type {{privateKey, address, amount, feeForTx, currencyCode, addressForChange, nSequence, jsonData, memo}} - * @private - */ - _data = {}; - - /** - * @param address - * @return {BlocksoftInvoice} - */ - setAddress(address) { - this._data.address = address; - return this; - } - - /** - * @param jsonData - * @return {BlocksoftInvoice} - */ - setAdditional(jsonData) { - this._data.jsonData = jsonData; - return this; - } - - /** - * @param amount - * @return {BlocksoftInvoice} - */ - setAmount(amount) { - this._data.amount = amount; - return this; - } - - /** - * @param memo - * @return {BlocksoftInvoice} - */ - setMemo(memo) { - this._data.memo = memo; - return this; - } - - /** - * @param currencyCode - * @return {BlocksoftInvoice} - */ - setCurrencyCode(currencyCode) { - this._data.currencyCode = currencyCode; - if (!this._processor[currencyCode]) { - } - return this; - } - - /** - * @return {Promise<{hash}>} - */ - async createInvoice() { - const currencyCode = this._data.currencyCode; - if (!currencyCode) { - throw new Error('plz set currencyCode before calling'); - } - let res = ''; - try { - AirDAOCryptoLog.log( - `BlocksoftInvoice.createInvoice ${currencyCode} started`, - this._data - ); - res = await this._processor[currencyCode].createInvoice(this._data); - AirDAOCryptoLog.log( - `BlocksoftInvoice.createInvoice ${currencyCode} finished`, - res - ); - } catch (e) { - // noinspection ES6MissingAwait - AirDAOCryptoLog.err( - `BlocksoftInvoice.createInvoice ${currencyCode} error ` + e.message, - e.data ? e.data : e - ); - throw e; - } - - return res; - } - - async checkInvoice(hash) { - const currencyCode = this._data.currencyCode; - if (!currencyCode) { - throw new Error('plz set currencyCode before calling'); - } - let res = ''; - try { - AirDAOCryptoLog.log( - `BlocksoftInvoice.checkInvoice ${currencyCode} started`, - this._data - ); - res = await this._processor[currencyCode].checkInvoice(hash, this._data); - AirDAOCryptoLog.log( - `BlocksoftInvoice.checkInvoice ${currencyCode} finished`, - res - ); - } catch (e) { - if (e.message.indexOf('not a valid invoice') === -1) { - // noinspection ES6MissingAwait - AirDAOCryptoLog.err( - `BlocksoftInvoice.checkInvoice ${currencyCode} error ` + e.message, - e.data ? e.data : e - ); - } - throw e; - } - - return res; - } -} - -const singleBlocksoftInvoice = new BlocksoftInvoice(); - -export default singleBlocksoftInvoice; diff --git a/crypto/actions/BlocksoftKeys/BlocksoftKeys.js b/crypto/actions/BlocksoftKeys/BlocksoftKeys.js deleted file mode 100644 index 2295bca31..000000000 --- a/crypto/actions/BlocksoftKeys/BlocksoftKeys.js +++ /dev/null @@ -1,498 +0,0 @@ -/** - * @author Ksu - * @version 0.5 - */ -import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; -import AirDAODict from '@crypto/common/AirDAODict'; -import BlocksoftKeysUtils from '@crypto/actions/BlocksoftKeys/BlocksoftKeysUtils'; - -import * as Crypto from 'expo-crypto'; -// import BlocksoftDispatcher from '../../blockchains/BlocksoftDispatcher'; -import BlocksoftKeysScam from '@crypto/actions/BlocksoftKeys/BlocksoftKeysScam'; -import bip44Constants from '@crypto/common/ext/bip44-constants'; -import networksConstants from '@crypto/common/ext/networks-constants'; - -const bip32 = require('bip32'); -const bip39 = require('bip39'); -// const bip44Constants = require('../../common/ext/bip44-constants'); -// const networksConstants = require('../../common/ext/networks-constants'); - -const bs58check = require('bs58check'); - -const ETH_CACHE = {}; -const CACHE = {}; -const CACHE_ROOTS = {}; - -class BlocksoftKeys { - constructor() { - this._bipHex = {}; - let currency; - for (currency of bip44Constants) { - this._bipHex[currency[1]] = currency[0]; - } - this._getRandomBytesFunction = Crypto.getRandomBytesAsync; - } - - /** - * create new mnemonic object (also gives hash to store in public db) - * @param {int} size - * @return {Promise<{mnemonic: string, hash: string}>} - */ - async newMnemonic(size = 256) { - AirDAOCryptoLog.log(`BlocksoftKeys newMnemonic called`); - - /* let mnemonic = false - if (USE_TON) { - for (let i = 0; i < 10000; i++) { - let random = await this._getRandomBytesFunction(size / 8) - random = Buffer.from(random, 'base64') - let testMnemonic = bip39.entropyToMnemonic(random) - if (await BlocksoftKeysUtils.tonCheckRevert(testMnemonic)) { - mnemonic = testMnemonic - break - } - AirDAOCryptoLog.log('step ' + i + ' not valid ton ' + testMnemonic) - } - if (!mnemonic) { - throw new Error('TON Mnemonic is not validating') - } - } else { - let random = await this._getRandomBytesFunction(size / 8) - random = Buffer.from(random, 'base64') - mnemonic = bip39.entropyToMnemonic(random) - } */ - let random = await this._getRandomBytesFunction(size / 8); - random = Buffer.from(random, 'base64'); - const mnemonic = bip39.entropyToMnemonic(random); - const hash = BlocksoftKeysUtils.hashMnemonic(mnemonic); - AirDAOCryptoLog.log(`BlocksoftKeys newMnemonic finished`); - return { mnemonic, hash }; - } - - /** - * @param {string} mnemonic - * @return {Promise} - */ - async validateMnemonic(mnemonic) { - AirDAOCryptoLog.log(`BlocksoftKeys validateMnemonic called`); - if (await BlocksoftKeysScam.isScamMnemonic(mnemonic)) { - throw new Error('Scam error'); - } - const result = await bip39.validateMnemonic(mnemonic); - if (!result) { - throw new Error('invalid mnemonic for bip39'); - } - return result; - } - - /** - * @param {string} data.mnemonic - * @param {string} data.walletHash - * @param {string|string[]} data.currencyCode = all - * @param {int} data.fromIndex = 0 - * @param {int} data.toIndex = 100 - * @param {boolean} data.fullTree = false - * @param {array} data.derivations.BTC - * @param {array} data.derivations.BTC_SEGWIT - * @return {Promise<{currencyCode:[{address, privateKey, path, index, type}]}>} - */ - async discoverAddresses(data, source) { - const logData = { ...data }; - if (typeof logData.mnemonic !== 'undefined') logData.mnemonic = '***'; - - AirDAOCryptoLog.log( - `BlocksoftKeys discoverAddresses called from ${source}`, - JSON.stringify(logData).substring(0, 200) - ); - - let toDiscover = AirDAODict.Codes; - if (data.currencyCode) { - if (typeof data.currencyCode === 'string') { - toDiscover = [data.currencyCode]; - } else { - toDiscover = data.currencyCode; - } - } - const fromIndex = data.fromIndex ? data.fromIndex : 0; - const toIndex = data.toIndex ? data.toIndex : 10; - const fullTree = data.fullTree ? data.fullTree : false; - - const results = {}; - - const mnemonicCache = data.mnemonic.toLowerCase(); - let bitcoinRoot = false; - let currencyCode; - let settings; - const seed = await BlocksoftKeysUtils.bip39MnemonicToSeed( - data.mnemonic.toLowerCase() - ); - for (currencyCode of toDiscover) { - results[currencyCode] = []; - try { - settings = AirDAODict.getCurrencyAllSettings( - currencyCode, - 'BlocksoftKeys' - ); - } catch (e) { - // do nothing for now - continue; - } - - let hexes = []; - if (settings.addressCurrencyCode) { - hexes.push(this._bipHex[settings.addressCurrencyCode]); - if (!this._bipHex[settings.addressCurrencyCode]) { - throw new Error( - 'UNKNOWN_CURRENCY_CODE SETTED ' + settings.addressCurrencyCode - ); - } - } - - if (this._bipHex[currencyCode]) { - hexes.push(this._bipHex[currencyCode]); - } else if (!settings.addressCurrencyCode) { - if ( - settings.extendsProcessor && - this._bipHex[settings.extendsProcessor] - ) { - hexes.push(this._bipHex[settings.extendsProcessor]); - } else { - throw new Error( - 'UNKNOWN_CURRENCY_CODE ' + - currencyCode + - ' in bipHex AND NO SETTED addressCurrencyCode' - ); - } - } - - let isAlreadyMain = false; - - if (!data.fullTree) { - hexes = [hexes[0]]; - } - - const hexesCache = - mnemonicCache + - '_' + - settings.addressProcessor + - '_fromINDEX_' + - fromIndex + - '_' + - JSON.stringify(hexes); - let hasDerivations = false; - if ( - typeof data.derivations !== 'undefined' && - typeof data.derivations[currencyCode] !== 'undefined' && - data.derivations[currencyCode] - ) { - hasDerivations = true; - } - if (typeof CACHE[hexesCache] === 'undefined' || hasDerivations) { - AirDAOCryptoLog.log( - `BlocksoftKeys will discover ${settings.addressProcessor}` - ); - let root = false; - if (typeof networksConstants[currencyCode] !== 'undefined') { - root = await this.getBip32Cached( - data.mnemonic, - networksConstants[currencyCode], - seed - ); - } else { - if (!bitcoinRoot) { - bitcoinRoot = await this.getBip32Cached(data.mnemonic); - } - root = bitcoinRoot; - } - // BIP32 Extended Private Key to check - uncomment - // let childFirst = root.derivePath('m/44\'/2\'/0\'/0') - // AirDAOCryptoLog.log(childFirst.toBase58()) - - /** - * @type {EthAddressProcessor|BtcAddressProcessor} - */ - const processor = await BlocksoftDispatcher.innerGetAddressProcessor( - settings - ); - - try { - await processor.setBasicRoot(root); - } catch (e) { - e.message += ' while doing ' + JSON.stringify(settings); - throw e; - } - let currentFromIndex = fromIndex; - let currentToIndex = toIndex; - let currentFullTree = fullTree; - - AirDAOCryptoLog.log( - `BlocksoftKeys discoverAddresses ${currencyCode} currentFromIndex.1 ${currentFromIndex}` - ); - if (hasDerivations) { - let derivation = { path: '', alreadyShown: 0, walletPubId: 0 }; - let maxIndex = 0; - for (derivation of data.derivations[currencyCode]) { - const child = root.derivePath(derivation.path); - const tmp = derivation.path.split('/'); - const result = await processor.getAddress( - child.privateKey, - { - publicKey: child.publicKey, - walletHash: data.walletHash, - derivationPath: derivation.path - }, - data, - seed, - 'discoverAddresses' - ); - result.basicPrivateKey = child.privateKey.toString('hex'); - result.basicPublicKey = child.publicKey.toString('hex'); - result.path = derivation.path; - result.alreadyShown = derivation.alreadyShown; - result.walletPubId = derivation.walletPubId || 0; - result.index = tmp[5]; - if (maxIndex < result.index) { - maxIndex = result.index * 1; - } - result.type = 'main'; - results[currencyCode].push(result); - } - if (maxIndex > 0) { - // noinspection PointlessArithmeticExpressionJS - currentFromIndex = maxIndex * 1 + 1; - currentToIndex = currentFromIndex * 1 + 10; - currentFullTree = true; - AirDAOCryptoLog.log( - `BlocksoftKeys ${currencyCode} discoverAddresses currentFromIndex.2 ${currentFromIndex}` - ); - } - } - - let suffixes; - if (currencyCode === 'SOL') { - suffixes = [ - { type: 'main', suffix: false, after: `'/0'` }, - { type: 'no_scan', suffix: false, after: `'` } - ]; - } else if (currentFullTree) { - suffixes = [ - { type: 'main', suffix: `0'/0` }, - { type: 'change', suffix: `0'/1` } - // { 'type': 'second', 'suffix': `1'/0` }, - // { 'type': 'secondchange', 'suffix': `1'/1` } - ]; - } else { - suffixes = [{ type: 'main', suffix: `0'/0` }]; - if (currencyCode === 'BTC_SEGWIT_COMPATIBLE') { - suffixes = [ - { type: 'main', suffix: '0/1' } // heh - ]; - } - hexes = [hexes[0]]; - } - - let hex; - for (hex of hexes) { - if (isAlreadyMain) { - suffixes[0].type = 'second'; - } - isAlreadyMain = true; - - if (currentFromIndex >= 0 && currentToIndex >= 0) { - for ( - let index = currentFromIndex; - index < currentToIndex; - index++ - ) { - let suffix; - for (suffix of suffixes) { - const path = suffix.suffix - ? `m/${hex}'/${suffix.suffix}/${index}` - : `m/${hex}'/${index}${suffix.after}`; - let privateKey = false; - let publicKey = false; - if ( - currencyCode === 'SOL' || - currencyCode === 'XLM' || - currencyCode === 'WAVES' || - currencyCode === 'ASH' - ) { - // @todo move to coin address processor - } else { - const child = root.derivePath(path); - privateKey = child.privateKey; - publicKey = child.publicKey; - } - const result = await processor.getAddress( - privateKey, - { - publicKey, - walletHash: data.walletHash, - derivationPath: path, - derivationIndex: index, - derivationType: suffix.type - }, - data, - seed, - 'discoverAddresses2' - ); - if (result) { - if (privateKey) { - result.basicPrivateKey = privateKey.toString('hex'); - result.basicPublicKey = publicKey.toString('hex'); - } - result.path = path; - result.index = index; - result.alreadyShown = 0; - result.type = suffix.type; - results[currencyCode].push(result); - } - } - } - } - } - CACHE[hexesCache] = results[currencyCode]; - if (currencyCode === 'ETH') { - ETH_CACHE[mnemonicCache] = results[currencyCode][0]; - } - } else { - AirDAOCryptoLog.log( - `BlocksoftKeys will be from cache ${settings.addressProcessor}` - ); - results[currencyCode] = CACHE[hexesCache]; - if (currencyCode === 'USDT') { - results[currencyCode] = [results[currencyCode][0]]; - } - } - } - AirDAOCryptoLog.log(`BlocksoftKeys discoverAddresses finished`); - return results; - } - - async getSeedCached(mnemonic) { - AirDAOCryptoLog.log(`BlocksoftKeys bip39MnemonicToSeed started`); - const mnemonicCache = mnemonic.toLowerCase(); - if (typeof CACHE[mnemonicCache] === 'undefined') { - CACHE[mnemonicCache] = await BlocksoftKeysUtils.bip39MnemonicToSeed( - mnemonic.toLowerCase() - ); - } - const seed = CACHE[mnemonicCache]; // will be rechecked on saving - AirDAOCryptoLog.log(`BlocksoftKeys bip39MnemonicToSeed ended`); - return seed; - } - - async getBip32Cached(mnemonic, network = false, seed = false) { - const mnemonicCache = mnemonic.toLowerCase() + '_' + (network || 'btc'); - if (typeof CACHE_ROOTS[mnemonicCache] === 'undefined') { - if (!seed) { - seed = await this.getSeedCached(mnemonic); - } - CACHE_ROOTS[mnemonicCache] = network - ? bip32.fromSeed(seed, network) - : bip32.fromSeed(seed); - } - return CACHE_ROOTS[mnemonicCache]; - } - - getEthCached(mnemonicCache) { - if (typeof ETH_CACHE[mnemonicCache] === 'undefined') return false; - return ETH_CACHE[mnemonicCache]; - } - - setEthCached(mnemonic, result) { - const mnemonicCache = mnemonic.toLowerCase(); - ETH_CACHE[mnemonicCache] = result; - } - - /** - * @param {string} data.mnemonic - * @param {string} data.currencyCode - * @param {string} data.derivationPath - * @param {string} data.derivationIndex - * @param {string} data.derivationType - * @return {Promise<{address, privateKey}>} - */ - async discoverOne(data) { - const seed = await BlocksoftKeysUtils.bip39MnemonicToSeed( - data.mnemonic.toLowerCase() - ); - const root = bip32.fromSeed(seed); - const child = root.derivePath(data.derivationPath.replace(/quote/g, "'")); - /** - * @type {EthAddressProcessor|BtcAddressProcessor} - */ - const processor = await BlocksoftDispatcher.getAddressProcessor( - data.currencyCode - ); - processor.setBasicRoot(root); - return processor.getAddress( - child.privateKey, - { - derivationPath: data.derivationPath, - derivationIndex: data.derivationIndex, - derivationType: data.derivationType, - publicKey: child.publicKey - }, - data, - seed, - 'discoverOne' - ); - } - - /** - * @param {string} data.mnemonic - * @param {string} data.currencyCode - * @return {Promise<{address, privateKey}>} - */ - async discoverXpub(data) { - const seed = await BlocksoftKeysUtils.bip39MnemonicToSeed( - data.mnemonic.toLowerCase() - ); - const root = bip32.fromSeed(seed); - let path = `m/44'/0'/0'`; - let version = 0x0488b21e; // xpub - if (data.currencyCode === 'BTC_SEGWIT') { - path = `m/84'/0'/0'`; - version = 0x04b24746; // https://github.com/satoshilabs/slips/blob/master/slip-0132.md - } else if (data.currencyCode === 'BTC_SEGWIT_COMPATIBLE') { - path = `m/49'/0'/0'`; - version = 0x049d7cb2; - } - AirDAOCryptoLog.log( - 'BlocksoftKeys.discoverXpub derive started ' + JSON.stringify(path) - ); - const rootWallet = root.derivePath(path); - AirDAOCryptoLog.log( - 'BlocksoftKeys.discoverXpub derived, bs58 started ' + JSON.stringify(path) - ); - const res = bs58check.encode( - serialize(rootWallet, version, rootWallet.publicKey) - ); - AirDAOCryptoLog.log( - 'BlocksoftKeys.discoverXpub res ' + JSON.stringify(path), - res - ); - return res; - } -} - -function serialize(hdkey, version, key, LEN = 78) { - // => version(4) || depth(1) || fingerprint(4) || index(4) || chain(32) || key(33) - const buffer = Buffer.allocUnsafe(LEN); - - buffer.writeUInt32BE(version, 0); - buffer.writeUInt8(hdkey.depth, 4); - - const fingerprint = hdkey.depth ? hdkey.parentFingerprint : 0x00000000; - buffer.writeUInt32BE(fingerprint, 5); - buffer.writeUInt32BE(hdkey.index, 9); - - hdkey.chainCode.copy(buffer, 13); - key.copy(buffer, 45); - - return buffer; -} - -const singleBlocksoftKeys = new BlocksoftKeys(); -export default singleBlocksoftKeys; diff --git a/crypto/actions/BlocksoftKeys/BlocksoftKeysScam.js b/crypto/actions/BlocksoftKeys/BlocksoftKeysScam.js deleted file mode 100644 index 29ea3ab5a..000000000 --- a/crypto/actions/BlocksoftKeys/BlocksoftKeysScam.js +++ /dev/null @@ -1,58 +0,0 @@ -/** - * @author Ksu - * @version 0.5 - */ -import AirDAOAxios from '@crypto/common/AirDAOAxios'; - -const scamSeeds = require('@crypto/common/ext/scam-seeds'); - -let CACHE_SEEDS = scamSeeds; -let CACHE_CASHBACKS = {}; -let CACHE_SEEDS_TIME = 0; -const TIMEOUT_SEEDS = 60000; -const PROXY_SEEDS = 'https://proxy.trustee.deals/seeds/getScam'; - -const _get = async () => { - const now = new Date().getTime(); - if (now - CACHE_SEEDS_TIME > TIMEOUT_SEEDS) { - const tmp = await AirDAOAxios.get(PROXY_SEEDS); - CACHE_SEEDS_TIME = now; - if (tmp.data?.data?.seeds) { - for (const seed of tmp.data.data.seeds) { - CACHE_SEEDS[seed] = 1; - } - } - if (tmp.data?.data?.cashbacks) { - for (const cb of tmp.data.data.cashbacks) { - CACHE_CASHBACKS[cb] = 1; - } - } - } -}; -const isScamMnemonic = async (mnemonic) => { - await _get(); - if (typeof CACHE_SEEDS[mnemonic] !== 'undefined') { - return true; - } - return false; -}; -const isScamCashback = async (cb) => { - await _get(); - if (typeof CACHE_CASHBACKS[cb] !== 'undefined') { - return true; - } - return false; -}; - -const isScamCashbackStatic = (cb) => { - if (typeof CACHE_CASHBACKS[cb] !== 'undefined') { - return true; - } - return false; -}; - -export default { - isScamMnemonic, - isScamCashback, - isScamCashbackStatic -}; diff --git a/crypto/actions/BlocksoftKeys/BlocksoftKeysUtils.js b/crypto/actions/BlocksoftKeys/BlocksoftKeysUtils.js deleted file mode 100644 index 4cec1d958..000000000 --- a/crypto/actions/BlocksoftKeys/BlocksoftKeysUtils.js +++ /dev/null @@ -1,116 +0,0 @@ -/** - * @author Ksu - * @version 0.5 - */ -const createHash = require('create-hash'); -const createHmac = require('create-hmac'); - -const createHmacPDFK2Sizes = { - md5: 16, - sha1: 20, - sha224: 28, - sha256: 32, - sha384: 48, - sha512: 64, - rmd160: 20, - ripemd160: 20 -}; - -// const { pbkdf2 } = require('react-native-fast-crypto'); - -// const DEFAULT_WORDS = require('./_words/english.json'); - -class BlocksoftKeysUtils { - static _pbkdf2(password, salt, iterations, keylen, digest) { - digest = digest || 'sha1'; - - const DK = Buffer.allocUnsafe(keylen); - const block1 = Buffer.allocUnsafe(salt.length + 4); - salt.copy(block1, 0, 0, salt.length); - - let destPos = 0; - const hLen = createHmacPDFK2Sizes[digest]; - const l = Math.ceil(keylen / hLen); - - for (let i = 1; i <= l; i++) { - block1.writeUInt32BE(i, salt.length); - - // noinspection JSUnresolvedFunction - const T = createHmac(digest, password).update(block1).digest(); - let U = T; - - for (let j = 1; j < iterations; j++) { - // noinspection JSUnresolvedFunction - U = createHmac(digest, password).update(U).digest(); - for (let k = 0; k < hLen; k++) T[k] ^= U[k]; - } - - T.copy(DK, destPos); - destPos += hLen; - } - - return DK; - } - - static recheckMnemonic(mnemonic) { - const words = mnemonic.trim().toLowerCase().split(/\s+/g); - const checked = []; - let word; - for (word of words) { - if (!word || word.length < 2) continue; - // noinspection JSUnresolvedFunction - const index = DEFAULT_WORDS.indexOf(word); - if (index === -1) { - throw new Error('BlocksoftKeysStorage invalid word ' + word); - } - checked.push(word); - } - if (checked.length <= 11) { - throw new Error('BlocksoftKeysStorage invalid words length ' + mnemonic); - } - return checked.join(' '); - } - - /** - * make hash for mnemonic string - * @param {string} mnemonic - * @return {string} - */ - static hashMnemonic(mnemonic) { - // noinspection JSUnresolvedFunction - return createHash('sha256').update(mnemonic).digest('hex').substr(0, 32); - } - - static async bip39MnemonicToSeed(mnemonic, password) { - if (!mnemonic) { - throw new Error('bip39MnemonicToSeed is empty'); - } - - function salt(password) { - return 'mnemonic' + (password || ''); - } - - const mnemonicBuffer = Buffer.from(mnemonic || '', 'utf8'); - const saltBuffer = Buffer.from(salt(password || ''), 'utf8'); - try { - const tmp2 = await pbkdf2.deriveAsync( - mnemonicBuffer, - saltBuffer, - 2048, - 64, - 'sha512' - ); - return Buffer.from(tmp2); - } catch (e) { - return BlocksoftKeysUtils._pbkdf2( - mnemonicBuffer, - saltBuffer, - 2048, - 64, - 'sha512' - ); - } - } -} - -export default BlocksoftKeysUtils; diff --git a/crypto/actions/BlocksoftKeys/_words/english.json b/crypto/actions/BlocksoftKeys/_words/english.json deleted file mode 100644 index cbc9d9b7c..000000000 --- a/crypto/actions/BlocksoftKeys/_words/english.json +++ /dev/null @@ -1,2050 +0,0 @@ -[ - "abandon", - "ability", - "able", - "about", - "above", - "absent", - "absorb", - "abstract", - "absurd", - "abuse", - "access", - "accident", - "account", - "accuse", - "achieve", - "acid", - "acoustic", - "acquire", - "across", - "act", - "action", - "actor", - "actress", - "actual", - "adapt", - "add", - "addict", - "address", - "adjust", - "admit", - "adult", - "advance", - "advice", - "aerobic", - "affair", - "afford", - "afraid", - "again", - "age", - "agent", - "agree", - "ahead", - "aim", - "air", - "airport", - "aisle", - "alarm", - "album", - "alcohol", - "alert", - "alien", - "all", - "alley", - "allow", - "almost", - "alone", - "alpha", - "already", - "also", - "alter", - "always", - "amateur", - "amazing", - "among", - "amount", - "amused", - "analyst", - "anchor", - "ancient", - "anger", - "angle", - "angry", - "animal", - "ankle", - "announce", - "annual", - "another", - "answer", - "antenna", - "antique", - "anxiety", - "any", - "apart", - "apology", - "appear", - "apple", - "approve", - "april", - "arch", - "arctic", - "area", - "arena", - "argue", - "arm", - "armed", - "armor", - "army", - "around", - "arrange", - "arrest", - "arrive", - "arrow", - "art", - "artefact", - "artist", - "artwork", - "ask", - "aspect", - "assault", - "asset", - "assist", - "assume", - "asthma", - "athlete", - "atom", - "attack", - "attend", - "attitude", - "attract", - "auction", - "audit", - "august", - "aunt", - "author", - "auto", - "autumn", - "average", - "avocado", - "avoid", - "awake", - "aware", - "away", - "awesome", - "awful", - "awkward", - "axis", - "baby", - "bachelor", - "bacon", - "badge", - "bag", - "balance", - "balcony", - "ball", - "bamboo", - "banana", - "banner", - "bar", - "barely", - "bargain", - "barrel", - "base", - "basic", - "basket", - "battle", - "beach", - "bean", - "beauty", - "because", - "become", - "beef", - "before", - "begin", - "behave", - "behind", - "believe", - "below", - "belt", - "bench", - "benefit", - "best", - "betray", - "better", - "between", - "beyond", - "bicycle", - "bid", - "bike", - "bind", - "biology", - "bird", - "birth", - "bitter", - "black", - "blade", - "blame", - "blanket", - "blast", - "bleak", - "bless", - "blind", - "blood", - "blossom", - "blouse", - "blue", - "blur", - "blush", - "board", - "boat", - "body", - "boil", - "bomb", - "bone", - "bonus", - "book", - "boost", - "border", - "boring", - "borrow", - "boss", - "bottom", - "bounce", - "box", - "boy", - "bracket", - "brain", - "brand", - "brass", - "brave", - "bread", - "breeze", - "brick", - "bridge", - "brief", - "bright", - "bring", - "brisk", - "broccoli", - "broken", - "bronze", - "broom", - "brother", - "brown", - "brush", - "bubble", - "buddy", - "budget", - "buffalo", - "build", - "bulb", - "bulk", - "bullet", - "bundle", - "bunker", - "burden", - "burger", - "burst", - "bus", - "business", - "busy", - "butter", - "buyer", - "buzz", - "cabbage", - "cabin", - "cable", - "cactus", - "cage", - "cake", - "call", - "calm", - "camera", - "camp", - "can", - "canal", - "cancel", - "candy", - "cannon", - "canoe", - "canvas", - "canyon", - "capable", - "capital", - "captain", - "car", - "carbon", - "card", - "cargo", - "carpet", - "carry", - "cart", - "case", - "cash", - "casino", - "castle", - "casual", - "cat", - "catalog", - "catch", - "category", - "cattle", - "caught", - "cause", - "caution", - "cave", - "ceiling", - "celery", - "cement", - "census", - "century", - "cereal", - "certain", - "chair", - "chalk", - "champion", - "change", - "chaos", - "chapter", - "charge", - "chase", - "chat", - "cheap", - "check", - "cheese", - "chef", - "cherry", - "chest", - "chicken", - "chief", - "child", - "chimney", - "choice", - "choose", - "chronic", - "chuckle", - "chunk", - "churn", - "cigar", - "cinnamon", - "circle", - "citizen", - "city", - "civil", - "claim", - "clap", - "clarify", - "claw", - "clay", - "clean", - "clerk", - "clever", - "click", - "client", - "cliff", - "climb", - "clinic", - "clip", - "clock", - "clog", - "close", - "cloth", - "cloud", - "clown", - "club", - "clump", - "cluster", - "clutch", - "coach", - "coast", - "coconut", - "code", - "coffee", - "coil", - "coin", - "collect", - "color", - "column", - "combine", - "come", - "comfort", - "comic", - "common", - "company", - "concert", - "conduct", - "confirm", - "congress", - "connect", - "consider", - "control", - "convince", - "cook", - "cool", - "copper", - "copy", - "coral", - "core", - "corn", - "correct", - "cost", - "cotton", - "couch", - "country", - "couple", - "course", - "cousin", - "cover", - "coyote", - "crack", - "cradle", - "craft", - "cram", - "crane", - "crash", - "crater", - "crawl", - "crazy", - "cream", - "credit", - "creek", - "crew", - "cricket", - "crime", - "crisp", - "critic", - "crop", - "cross", - "crouch", - "crowd", - "crucial", - "cruel", - "cruise", - "crumble", - "crunch", - "crush", - "cry", - "crystal", - "cube", - "culture", - "cup", - "cupboard", - "curious", - "current", - "curtain", - "curve", - "cushion", - "custom", - "cute", - "cycle", - "dad", - "damage", - "damp", - "dance", - "danger", - "daring", - "dash", - "daughter", - "dawn", - "day", - "deal", - "debate", - "debris", - "decade", - "december", - "decide", - "decline", - "decorate", - "decrease", - "deer", - "defense", - "define", - "defy", - "degree", - "delay", - "deliver", - "demand", - "demise", - "denial", - "dentist", - "deny", - "depart", - "depend", - "deposit", - "depth", - "deputy", - "derive", - "describe", - "desert", - "design", - "desk", - "despair", - "destroy", - "detail", - "detect", - "develop", - "device", - "devote", - "diagram", - "dial", - "diamond", - "diary", - "dice", - "diesel", - "diet", - "differ", - "digital", - "dignity", - "dilemma", - "dinner", - "dinosaur", - "direct", - "dirt", - "disagree", - "discover", - "disease", - "dish", - "dismiss", - "disorder", - "display", - "distance", - "divert", - "divide", - "divorce", - "dizzy", - "doctor", - "document", - "dog", - "doll", - "dolphin", - "domain", - "donate", - "donkey", - "donor", - "door", - "dose", - "double", - "dove", - "draft", - "dragon", - "drama", - "drastic", - "draw", - "dream", - "dress", - "drift", - "drill", - "drink", - "drip", - "drive", - "drop", - "drum", - "dry", - "duck", - "dumb", - "dune", - "during", - "dust", - "dutch", - "duty", - "dwarf", - "dynamic", - "eager", - "eagle", - "early", - "earn", - "earth", - "easily", - "east", - "easy", - "echo", - "ecology", - "economy", - "edge", - "edit", - "educate", - "effort", - "egg", - "eight", - "either", - "elbow", - "elder", - "electric", - "elegant", - "element", - "elephant", - "elevator", - "elite", - "else", - "embark", - "embody", - "embrace", - "emerge", - "emotion", - "employ", - "empower", - "empty", - "enable", - "enact", - "end", - "endless", - "endorse", - "enemy", - "energy", - "enforce", - "engage", - "engine", - "enhance", - "enjoy", - "enlist", - "enough", - "enrich", - "enroll", - "ensure", - "enter", - "entire", - "entry", - "envelope", - "episode", - "equal", - "equip", - "era", - "erase", - "erode", - "erosion", - "error", - "erupt", - "escape", - "essay", - "essence", - "estate", - "eternal", - "ethics", - "evidence", - "evil", - "evoke", - "evolve", - "exact", - "example", - "excess", - "exchange", - "excite", - "exclude", - "excuse", - "execute", - "exercise", - "exhaust", - "exhibit", - "exile", - "exist", - "exit", - "exotic", - "expand", - "expect", - "expire", - "explain", - "expose", - "express", - "extend", - "extra", - "eye", - "eyebrow", - "fabric", - "face", - "faculty", - "fade", - "faint", - "faith", - "fall", - "false", - "fame", - "family", - "famous", - "fan", - "fancy", - "fantasy", - "farm", - "fashion", - "fat", - "fatal", - "father", - "fatigue", - "fault", - "favorite", - "feature", - "february", - "federal", - "fee", - "feed", - "feel", - "female", - "fence", - "festival", - "fetch", - "fever", - "few", - "fiber", - "fiction", - "field", - "figure", - "file", - "film", - "filter", - "final", - "find", - "fine", - "finger", - "finish", - "fire", - "firm", - "first", - "fiscal", - "fish", - "fit", - "fitness", - "fix", - "flag", - "flame", - "flash", - "flat", - "flavor", - "flee", - "flight", - "flip", - "float", - "flock", - "floor", - "flower", - "fluid", - "flush", - "fly", - "foam", - "focus", - "fog", - "foil", - "fold", - "follow", - "food", - "foot", - "force", - "forest", - "forget", - "fork", - "fortune", - "forum", - "forward", - "fossil", - "foster", - "found", - "fox", - "fragile", - "frame", - "frequent", - "fresh", - "friend", - "fringe", - "frog", - "front", - "frost", - "frown", - "frozen", - "fruit", - "fuel", - "fun", - "funny", - "furnace", - "fury", - "future", - "gadget", - "gain", - "galaxy", - "gallery", - "game", - "gap", - "garage", - "garbage", - "garden", - "garlic", - "garment", - "gas", - "gasp", - "gate", - "gather", - "gauge", - "gaze", - "general", - "genius", - "genre", - "gentle", - "genuine", - "gesture", - "ghost", - "giant", - "gift", - "giggle", - "ginger", - "giraffe", - "girl", - "give", - "glad", - "glance", - "glare", - "glass", - "glide", - "glimpse", - "globe", - "gloom", - "glory", - "glove", - "glow", - "glue", - "goat", - "goddess", - "gold", - "good", - "goose", - "gorilla", - "gospel", - "gossip", - "govern", - "gown", - "grab", - "grace", - "grain", - "grant", - "grape", - "grass", - "gravity", - "great", - "green", - "grid", - "grief", - "grit", - "grocery", - "group", - "grow", - "grunt", - "guard", - "guess", - "guide", - "guilt", - "guitar", - "gun", - "gym", - "habit", - "hair", - "half", - "hammer", - "hamster", - "hand", - "happy", - "harbor", - "hard", - "harsh", - "harvest", - "hat", - "have", - "hawk", - "hazard", - "head", - "health", - "heart", - "heavy", - "hedgehog", - "height", - "hello", - "helmet", - "help", - "hen", - "hero", - "hidden", - "high", - "hill", - "hint", - "hip", - "hire", - "history", - "hobby", - "hockey", - "hold", - "hole", - "holiday", - "hollow", - "home", - "honey", - "hood", - "hope", - "horn", - "horror", - "horse", - "hospital", - "host", - "hotel", - "hour", - "hover", - "hub", - "huge", - "human", - "humble", - "humor", - "hundred", - "hungry", - "hunt", - "hurdle", - "hurry", - "hurt", - "husband", - "hybrid", - "ice", - "icon", - "idea", - "identify", - "idle", - "ignore", - "ill", - "illegal", - "illness", - "image", - "imitate", - "immense", - "immune", - "impact", - "impose", - "improve", - "impulse", - "inch", - "include", - "income", - "increase", - "index", - "indicate", - "indoor", - "industry", - "infant", - "inflict", - "inform", - "inhale", - "inherit", - "initial", - "inject", - "injury", - "inmate", - "inner", - "innocent", - "input", - "inquiry", - "insane", - "insect", - "inside", - "inspire", - "install", - "intact", - "interest", - "into", - "invest", - "invite", - "involve", - "iron", - "island", - "isolate", - "issue", - "item", - "ivory", - "jacket", - "jaguar", - "jar", - "jazz", - "jealous", - "jeans", - "jelly", - "jewel", - "job", - "join", - "joke", - "journey", - "joy", - "judge", - "juice", - "jump", - "jungle", - "junior", - "junk", - "just", - "kangaroo", - "keen", - "keep", - "ketchup", - "key", - "kick", - "kid", - "kidney", - "kind", - "kingdom", - "kiss", - "kit", - "kitchen", - "kite", - "kitten", - "kiwi", - "knee", - "knife", - "knock", - "know", - "lab", - "label", - "labor", - "ladder", - "lady", - "lake", - "lamp", - "language", - "laptop", - "large", - "later", - "latin", - "laugh", - "laundry", - "lava", - "law", - "lawn", - "lawsuit", - "layer", - "lazy", - "leader", - "leaf", - "learn", - "leave", - "lecture", - "left", - "leg", - "legal", - "legend", - "leisure", - "lemon", - "lend", - "length", - "lens", - "leopard", - "lesson", - "letter", - "level", - "liar", - "liberty", - "library", - "license", - "life", - "lift", - "light", - "like", - "limb", - "limit", - "link", - "lion", - "liquid", - "list", - "little", - "live", - "lizard", - "load", - "loan", - "lobster", - "local", - "lock", - "logic", - "lonely", - "long", - "loop", - "lottery", - "loud", - "lounge", - "love", - "loyal", - "lucky", - "luggage", - "lumber", - "lunar", - "lunch", - "luxury", - "lyrics", - "machine", - "mad", - "magic", - "magnet", - "maid", - "mail", - "main", - "major", - "make", - "mammal", - "man", - "manage", - "mandate", - "mango", - "mansion", - "manual", - "maple", - "marble", - "march", - "margin", - "marine", - "market", - "marriage", - "mask", - "mass", - "master", - "match", - "material", - "math", - "matrix", - "matter", - "maximum", - "maze", - "meadow", - "mean", - "measure", - "meat", - "mechanic", - "medal", - "media", - "melody", - "melt", - "member", - "memory", - "mention", - "menu", - "mercy", - "merge", - "merit", - "merry", - "mesh", - "message", - "metal", - "method", - "middle", - "midnight", - "milk", - "million", - "mimic", - "mind", - "minimum", - "minor", - "minute", - "miracle", - "mirror", - "misery", - "miss", - "mistake", - "mix", - "mixed", - "mixture", - "mobile", - "model", - "modify", - "mom", - "moment", - "monitor", - "monkey", - "monster", - "month", - "moon", - "moral", - "more", - "morning", - "mosquito", - "mother", - "motion", - "motor", - "mountain", - "mouse", - "move", - "movie", - "much", - "muffin", - "mule", - "multiply", - "muscle", - "museum", - "mushroom", - "music", - "must", - "mutual", - "myself", - "mystery", - "myth", - "naive", - "name", - "napkin", - "narrow", - "nasty", - "nation", - "nature", - "near", - "neck", - "need", - "negative", - "neglect", - "neither", - "nephew", - "nerve", - "nest", - "net", - "network", - "neutral", - "never", - "news", - "next", - "nice", - "night", - "noble", - "noise", - "nominee", - "noodle", - "normal", - "north", - "nose", - "notable", - "note", - "nothing", - "notice", - "novel", - "now", - "nuclear", - "number", - "nurse", - "nut", - "oak", - "obey", - "object", - "oblige", - "obscure", - "observe", - "obtain", - "obvious", - "occur", - "ocean", - "october", - "odor", - "off", - "offer", - "office", - "often", - "oil", - "okay", - "old", - "olive", - "olympic", - "omit", - "once", - "one", - "onion", - "online", - "only", - "open", - "opera", - "opinion", - "oppose", - "option", - "orange", - "orbit", - "orchard", - "order", - "ordinary", - "organ", - "orient", - "original", - "orphan", - "ostrich", - "other", - "outdoor", - "outer", - "output", - "outside", - "oval", - "oven", - "over", - "own", - "owner", - "oxygen", - "oyster", - "ozone", - "pact", - "paddle", - "page", - "pair", - "palace", - "palm", - "panda", - "panel", - "panic", - "panther", - "paper", - "parade", - "parent", - "park", - "parrot", - "party", - "pass", - "patch", - "path", - "patient", - "patrol", - "pattern", - "pause", - "pave", - "payment", - "peace", - "peanut", - "pear", - "peasant", - "pelican", - "pen", - "penalty", - "pencil", - "people", - "pepper", - "perfect", - "permit", - "person", - "pet", - "phone", - "photo", - "phrase", - "physical", - "piano", - "picnic", - "picture", - "piece", - "pig", - "pigeon", - "pill", - "pilot", - "pink", - "pioneer", - "pipe", - "pistol", - "pitch", - "pizza", - "place", - "planet", - "plastic", - "plate", - "play", - "please", - "pledge", - "pluck", - "plug", - "plunge", - "poem", - "poet", - "point", - "polar", - "pole", - "police", - "pond", - "pony", - "pool", - "popular", - "portion", - "position", - "possible", - "post", - "potato", - "pottery", - "poverty", - "powder", - "power", - "practice", - "praise", - "predict", - "prefer", - "prepare", - "present", - "pretty", - "prevent", - "price", - "pride", - "primary", - "print", - "priority", - "prison", - "private", - "prize", - "problem", - "process", - "produce", - "profit", - "program", - "project", - "promote", - "proof", - "property", - "prosper", - "protect", - "proud", - "provide", - "public", - "pudding", - "pull", - "pulp", - "pulse", - "pumpkin", - "punch", - "pupil", - "puppy", - "purchase", - "purity", - "purpose", - "purse", - "push", - "put", - "puzzle", - "pyramid", - "quality", - "quantum", - "quarter", - "question", - "quick", - "quit", - "quiz", - "quote", - "rabbit", - "raccoon", - "race", - "rack", - "radar", - "radio", - "rail", - "rain", - "raise", - "rally", - "ramp", - "ranch", - "random", - "range", - "rapid", - "rare", - "rate", - "rather", - "raven", - "raw", - "razor", - "ready", - "real", - "reason", - "rebel", - "rebuild", - "recall", - "receive", - "recipe", - "record", - "recycle", - "reduce", - "reflect", - "reform", - "refuse", - "region", - "regret", - "regular", - "reject", - "relax", - "release", - "relief", - "rely", - "remain", - "remember", - "remind", - "remove", - "render", - "renew", - "rent", - "reopen", - "repair", - "repeat", - "replace", - "report", - "require", - "rescue", - "resemble", - "resist", - "resource", - "response", - "result", - "retire", - "retreat", - "return", - "reunion", - "reveal", - "review", - "reward", - "rhythm", - "rib", - "ribbon", - "rice", - "rich", - "ride", - "ridge", - "rifle", - "right", - "rigid", - "ring", - "riot", - "ripple", - "risk", - "ritual", - "rival", - "river", - "road", - "roast", - "robot", - "robust", - "rocket", - "romance", - "roof", - "rookie", - "room", - "rose", - "rotate", - "rough", - "round", - "route", - "royal", - "rubber", - "rude", - "rug", - "rule", - "run", - "runway", - "rural", - "sad", - "saddle", - "sadness", - "safe", - "sail", - "salad", - "salmon", - "salon", - "salt", - "salute", - "same", - "sample", - "sand", - "satisfy", - "satoshi", - "sauce", - "sausage", - "save", - "say", - "scale", - "scan", - "scare", - "scatter", - "scene", - "scheme", - "school", - "science", - "scissors", - "scorpion", - "scout", - "scrap", - "screen", - "script", - "scrub", - "sea", - "search", - "season", - "seat", - "second", - "secret", - "section", - "security", - "seed", - "seek", - "segment", - "select", - "sell", - "seminar", - "senior", - "sense", - "sentence", - "series", - "service", - "session", - "settle", - "setup", - "seven", - "shadow", - "shaft", - "shallow", - "share", - "shed", - "shell", - "sheriff", - "shield", - "shift", - "shine", - "ship", - "shiver", - "shock", - "shoe", - "shoot", - "shop", - "short", - "shoulder", - "shove", - "shrimp", - "shrug", - "shuffle", - "shy", - "sibling", - "sick", - "side", - "siege", - "sight", - "sign", - "silent", - "silk", - "silly", - "silver", - "similar", - "simple", - "since", - "sing", - "siren", - "sister", - "situate", - "six", - "size", - "skate", - "sketch", - "ski", - "skill", - "skin", - "skirt", - "skull", - "slab", - "slam", - "sleep", - "slender", - "slice", - "slide", - "slight", - "slim", - "slogan", - "slot", - "slow", - "slush", - "small", - "smart", - "smile", - "smoke", - "smooth", - "snack", - "snake", - "snap", - "sniff", - "snow", - "soap", - "soccer", - "social", - "sock", - "soda", - "soft", - "solar", - "soldier", - "solid", - "solution", - "solve", - "someone", - "song", - "soon", - "sorry", - "sort", - "soul", - "sound", - "soup", - "source", - "south", - "space", - "spare", - "spatial", - "spawn", - "speak", - "special", - "speed", - "spell", - "spend", - "sphere", - "spice", - "spider", - "spike", - "spin", - "spirit", - "split", - "spoil", - "sponsor", - "spoon", - "sport", - "spot", - "spray", - "spread", - "spring", - "spy", - "square", - "squeeze", - "squirrel", - "stable", - "stadium", - "staff", - "stage", - "stairs", - "stamp", - "stand", - "start", - "state", - "stay", - "steak", - "steel", - "stem", - "step", - "stereo", - "stick", - "still", - "sting", - "stock", - "stomach", - "stone", - "stool", - "story", - "stove", - "strategy", - "street", - "strike", - "strong", - "struggle", - "student", - "stuff", - "stumble", - "style", - "subject", - "submit", - "subway", - "success", - "such", - "sudden", - "suffer", - "sugar", - "suggest", - "suit", - "summer", - "sun", - "sunny", - "sunset", - "super", - "supply", - "supreme", - "sure", - "surface", - "surge", - "surprise", - "surround", - "survey", - "suspect", - "sustain", - "swallow", - "swamp", - "swap", - "swarm", - "swear", - "sweet", - "swift", - "swim", - "swing", - "switch", - "sword", - "symbol", - "symptom", - "syrup", - "system", - "table", - "tackle", - "tag", - "tail", - "talent", - "talk", - "tank", - "tape", - "target", - "task", - "taste", - "tattoo", - "taxi", - "teach", - "team", - "tell", - "ten", - "tenant", - "tennis", - "tent", - "term", - "test", - "text", - "thank", - "that", - "theme", - "then", - "theory", - "there", - "they", - "thing", - "this", - "thought", - "three", - "thrive", - "throw", - "thumb", - "thunder", - "ticket", - "tide", - "tiger", - "tilt", - "timber", - "time", - "tiny", - "tip", - "tired", - "tissue", - "title", - "toast", - "tobacco", - "today", - "toddler", - "toe", - "together", - "toilet", - "token", - "tomato", - "tomorrow", - "tone", - "tongue", - "tonight", - "tool", - "tooth", - "top", - "topic", - "topple", - "torch", - "tornado", - "tortoise", - "toss", - "total", - "tourist", - "toward", - "tower", - "town", - "toy", - "track", - "trade", - "traffic", - "tragic", - "train", - "transfer", - "trap", - "trash", - "travel", - "tray", - "treat", - "tree", - "trend", - "trial", - "tribe", - "trick", - "trigger", - "trim", - "trip", - "trophy", - "trouble", - "truck", - "true", - "truly", - "trumpet", - "trust", - "truth", - "try", - "tube", - "tuition", - "tumble", - "tuna", - "tunnel", - "turkey", - "turn", - "turtle", - "twelve", - "twenty", - "twice", - "twin", - "twist", - "two", - "type", - "typical", - "ugly", - "umbrella", - "unable", - "unaware", - "uncle", - "uncover", - "under", - "undo", - "unfair", - "unfold", - "unhappy", - "uniform", - "unique", - "unit", - "universe", - "unknown", - "unlock", - "until", - "unusual", - "unveil", - "update", - "upgrade", - "uphold", - "upon", - "upper", - "upset", - "urban", - "urge", - "usage", - "use", - "used", - "useful", - "useless", - "usual", - "utility", - "vacant", - "vacuum", - "vague", - "valid", - "valley", - "valve", - "van", - "vanish", - "vapor", - "various", - "vast", - "vault", - "vehicle", - "velvet", - "vendor", - "venture", - "venue", - "verb", - "verify", - "version", - "very", - "vessel", - "veteran", - "viable", - "vibrant", - "vicious", - "victory", - "video", - "view", - "village", - "vintage", - "violin", - "virtual", - "virus", - "visa", - "visit", - "visual", - "vital", - "vivid", - "vocal", - "voice", - "void", - "volcano", - "volume", - "vote", - "voyage", - "wage", - "wagon", - "wait", - "walk", - "wall", - "walnut", - "want", - "warfare", - "warm", - "warrior", - "wash", - "wasp", - "waste", - "water", - "wave", - "way", - "wealth", - "weapon", - "wear", - "weasel", - "weather", - "web", - "wedding", - "weekend", - "weird", - "welcome", - "west", - "wet", - "whale", - "what", - "wheat", - "wheel", - "when", - "where", - "whip", - "whisper", - "wide", - "width", - "wife", - "wild", - "will", - "win", - "window", - "wine", - "wing", - "wink", - "winner", - "winter", - "wire", - "wisdom", - "wise", - "wish", - "witness", - "wolf", - "woman", - "wonder", - "wood", - "wool", - "word", - "work", - "world", - "worry", - "worth", - "wrap", - "wreck", - "wrestle", - "wrist", - "write", - "wrong", - "yard", - "year", - "yellow", - "you", - "young", - "youth", - "zebra", - "zero", - "zone", - "zoo" -] diff --git a/crypto/actions/BlocksoftKeysForRef/BlocksoftKeysForRef.js b/crypto/actions/BlocksoftKeysForRef/BlocksoftKeysForRef.js deleted file mode 100644 index b9bfe4b72..000000000 --- a/crypto/actions/BlocksoftKeysForRef/BlocksoftKeysForRef.js +++ /dev/null @@ -1,82 +0,0 @@ -/** - * @author Ksu - * @version 0.5 - */ -import AirDAOCryptoLog from '../../common/AirDAOCryptoLog'; -import BlocksoftKeysForRefServerSide from './BlocksoftKeysForRefServerSide'; -import BlocksoftKeys from '../BlocksoftKeys/BlocksoftKeys'; -import BlocksoftDispatcher from '../../blockchains/BlocksoftDispatcher'; - -const CACHE = {}; - -class BlocksoftKeysForRef { - /** - * @param {string} data.mnemonic - * @param {int} data.index - * @return {Promise<{currencyCode:[{address, privateKey, path, index, type}]}>} - */ - async discoverPublicAndPrivate(data) { - const logData = { ...data }; - const mnemonicCache = data.mnemonic.toLowerCase(); - - if (typeof logData.mnemonic !== 'undefined') logData.mnemonic = '***'; - if (typeof CACHE[mnemonicCache] !== 'undefined') - return CACHE[mnemonicCache]; - AirDAOCryptoLog.log( - `BlocksoftKeysForRef discoverPublicAndPrivate called ` + - JSON.stringify(logData) - ); - - let result = BlocksoftKeys.getEthCached(mnemonicCache); - if (!result) { - AirDAOCryptoLog.log( - `BlocksoftKeysForRef discoverPublicAndPrivate no cache ` + - JSON.stringify(logData) - ); - let index = 0; - if (typeof data.index !== 'undefined') { - index = data.index; - } - const root = await BlocksoftKeys.getBip32Cached(data.mnemonic); - const path = `m/44'/60'/${index}'/0/0`; - const child = root.derivePath(path); - - const processor = await BlocksoftDispatcher.getAddressProcessor('ETH'); - result = await processor.getAddress(child.privateKey); - result.index = index; - result.path = path; - if (index === 0) { - BlocksoftKeys.setEthCached(data.mnemonic, result); - } - AirDAOCryptoLog.log( - `BlocksoftKeysForRef discoverPublicAndPrivate finished no cache ` + - JSON.stringify(logData) - ); - } - // noinspection JSPrimitiveTypeWrapperUsage - result.cashbackToken = BlocksoftKeysForRefServerSide.addressToToken( - result.address - ); - AirDAOCryptoLog.log( - `BlocksoftKeysForRef discoverPublicAndPrivate finished ` + - JSON.stringify(logData) - ); - CACHE[mnemonicCache] = result; - return result; - } - - async signDataForApi(msg, privateKey) { - const processor = await BlocksoftDispatcher.getAddressProcessor('ETH'); - if (privateKey.substr(0, 2) !== '0x') { - privateKey = '0x' + privateKey; - } - const signedData = await processor.signMessage(msg, privateKey); - delete signedData.v; - delete signedData.r; - delete signedData.s; - return signedData; - } -} - -const singleBlocksoftKeysForRef = new BlocksoftKeysForRef(); -export default singleBlocksoftKeysForRef; diff --git a/crypto/actions/BlocksoftKeysForRef/BlocksoftKeysForRefServerSide.js b/crypto/actions/BlocksoftKeysForRef/BlocksoftKeysForRefServerSide.js deleted file mode 100644 index ea022744c..000000000 --- a/crypto/actions/BlocksoftKeysForRef/BlocksoftKeysForRefServerSide.js +++ /dev/null @@ -1,80 +0,0 @@ -/** - * @author Ksu - * @version 0.5 - */ -const Web3 = require('web3') - -function stripHexPrefix(value) { - return value.replace('0x', '') -} - -function stripHexPrefixAndLower(value) { - return stripHexPrefix(value).toLowerCase() -} - -class BlocksoftKeysForRefServerSide { - - constructor() { - this._link = `https://mainnet.infura.io/v3/e69df96932bd4e9db7451fab8d6e0c85` - // noinspection JSUnresolvedVariable - this._web3 = new Web3(new Web3.providers.HttpProvider(this._link)) - } - - /** - * used recovery of v, r and s from https://github.com/MyCryptoHQ/MyCrypto/blob/develop/common/libs/signing.ts - * @param {Object} signedData - * @param {string} signedData.signature - * @param {string} signedData.message - * @param {string} signedData.messageHash - * @param {string} signedData.v - * @param {string} signedData.r - * @param {string} signedData.s - * @param {string} cashbackToken - * @return {Promise} - */ - async checkDataByApi(signedData, cashbackToken) { - if (typeof(signedData.signature) === 'undefined' || !signedData.signature) { - throw new Error('BlocksoftKeysForRefServerSide.checkDataByApi no signature' + JSON.stringify(signedData)) - } - if (typeof(signedData.message) === 'undefined') { - throw new Error('BlocksoftKeysForRefServerSide.checkDataByApi no message ' + JSON.stringify(signedData)) - } - if (typeof(signedData.messageHash) === 'undefined' || !signedData.messageHash) { - throw new Error('BlocksoftKeysForRefServerSide.checkDataByApi no messageHash ' + JSON.stringify(signedData)) - } - - const clonedData = { - signature : signedData.signature, - messageHash : signedData.messageHash - } - - // noinspection JSUnresolvedFunction,JSUnresolvedVariable - const signedDataHash = await this._web3.eth.accounts.hashMessage(signedData.message) - if (signedDataHash !== signedData.messageHash) { - return false - } - if (typeof clonedData.v === 'undefined' || typeof clonedData.r === 'undefined' || typeof clonedData.s === 'undefined') { - const sigb = Buffer.from(stripHexPrefixAndLower(clonedData.signature), 'hex') - if (sigb.length !== 65) { - return false - } - sigb[64] = sigb[64] === 0 || sigb[64] === 1 ? sigb[64] + 27 : sigb[64] - - clonedData.v = '0x' + sigb[64].toString(16) - clonedData.r = '0x' + sigb.slice(0, 32).toString('hex') - clonedData.s = '0x' + sigb.slice(32, 64).toString('hex') - } - - // noinspection JSUnresolvedVariable - const signedBy = await this._web3.eth.accounts.recover(clonedData) - return cashbackToken === this.addressToToken(signedBy) - } - - addressToToken(address) { - // any algo could be used to "hide" actual address - return Buffer.from(address).toString('base64').slice(3, 11) - } -} - -const singleBlocksoftKeysForRefServerSide = new BlocksoftKeysForRefServerSide() -export default singleBlocksoftKeysForRefServerSide diff --git a/crypto/actions/BlocksoftKeysForRef/BlocksoftKeysForRefStorage.js b/crypto/actions/BlocksoftKeysForRef/BlocksoftKeysForRefStorage.js deleted file mode 100644 index 1b5e44537..000000000 --- a/crypto/actions/BlocksoftKeysForRef/BlocksoftKeysForRefStorage.js +++ /dev/null @@ -1,26 +0,0 @@ -/** - * @author Ksu - * @version 0.5 - */ -import { BlocksoftKeysStorage } from '../BlocksoftKeysStorage/BlocksoftKeysStorage' - -class BlocksoftKeysForRefStorage extends BlocksoftKeysStorage { - - constructor(_serviceName = 'BlocksoftKeysRefStorage') { - super(_serviceName) - } - - async getPublicAndPrivateResultForHash(hash) { - const res = await this._getKeyValue('cd_' + hash) - if (!res && !res.priv) return false - return JSON.parse(res.priv) - } - - async setPublicAndPrivateResultForHash(hash, data) { - return this._setKeyValue('cd_' + hash, hash, JSON.stringify(data)) - } - -} - -const singleBlocksoftKeysRefStorage = new BlocksoftKeysForRefStorage() -export default singleBlocksoftKeysRefStorage diff --git a/crypto/actions/BlocksoftKeysStorage/BlocksoftKeysStorage.js b/crypto/actions/BlocksoftKeysStorage/BlocksoftKeysStorage.js deleted file mode 100644 index 3fd3a0533..000000000 --- a/crypto/actions/BlocksoftKeysStorage/BlocksoftKeysStorage.js +++ /dev/null @@ -1,363 +0,0 @@ -/** - * @author Ksu - * @version 0.5 - * @docs https://www.npmjs.com/package/react-native-keychain - */ -import 'react-native'; -// import * as Keychain from 'react-native-keychain'; - -// import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; -// import config from '@app/config/config'; -// import AirDAODict from '@crypto/common/AirDAODict'; - -export class BlocksoftKeysStorage { - /** - * @type {string} - * @private - */ - _serviceName = ''; - - /** - * @type {number} - * @private - */ - _serviceWalletsCounter = 0; - - /** - * @type {{}|array} - * @private - */ - _serviceWallets = {}; - - /** - * @type {boolean} - * @private - */ - _serviceWasInited = false; - - /** - * @type {array} - */ - publicWallets = []; - - constructor(_serviceName = 'BlocksoftKeysStorage3') { - this._serviceName = _serviceName; - } - - /** - * @private - */ - async _getServiceName(name) { - this._serviceName = name; - this._serviceWasInited = false; - await this._init(); - } - - /** - * @param {string} key - * @return {Promise} - * @private - */ - async _getKeyValue(key) { - const res = await Keychain.getInternetCredentials( - this._serviceName + '_' + key, - { - authenticationPrompt: { - title: 'Fingerprint title', - cancel: 'Cancel' - } - } - ); - if (!res) return false; - return { pub: res.username, priv: res.password }; - } - - /** - * @param {string} key - * @param {string|int} pub - * @param {string|boolean} priv - * @return {Promise<*>} - * @private - */ - async _setKeyValue(key, pub, priv = false) { - pub = pub + ''; - if (!priv) priv = pub; - let res = false; - try { - res = await Keychain.setInternetCredentials( - this._serviceName + '_' + key, - pub, - priv, - { - authenticationPrompt: { - title: 'Fingerprint title', - cancel: 'Cancel' - } - // if will be breaking again try accessControl : 'BiometryAnyOrDevicePasscode' - } - ); - } catch (e) { - if (config.debug.cryptoErrors) { - console.log('BlocksoftKeysStorage _setKeyValue error ', e); - } - if (key.indexOf('wallet') !== -1) { - throw e; - } - } - return res; - } - - /** - * @private - */ - async _init() { - if (this._serviceWasInited) { - return true; - } - - const count = await this._getKeyValue('wallets_counter'); - - this._serviceWalletsCounter = count && count.priv ? count.priv * 1 : 0; - this._serviceWallets = {}; - this.publicWallets = []; - this.publicSelectedWallet = false; - if (this._serviceWalletsCounter > 0) { - for (let i = 1; i <= this._serviceWalletsCounter; i++) { - const wallet = await this._getKeyValue('wallet_' + i); - if ( - !wallet.priv || - wallet.priv === '' || - wallet.priv === - 'new wallet is not generated - please reinstall and restart' || - wallet.priv === wallet.pub - ) { - continue; - } - this._serviceWallets[wallet.pub] = wallet.priv; - this._serviceWallets[i - 1] = wallet.priv; - this.publicWallets.push(wallet.pub); - } - } - this._serviceWasInited = true; - } - - async getOldSelectedWallet() { - this._init(); - const tmp = await this._getKeyValue('selected_hash'); - let publicSelectedWallet = false; - if (tmp && tmp.pub) { - publicSelectedWallet = tmp.pub; - } - if ( - !this.publicSelectedWallet || - !this._serviceWallets[this.publicSelectedWallet] - ) { - publicSelectedWallet = false; - } - return publicSelectedWallet; - } - - async reInit() { - this._serviceWasInited = false; - return this._init(); - } - - async getOneWalletText(walletHash, discoverPath, currencyCode) { - try { - if (typeof discoverPath === 'undefined' || discoverPath === false) { - const res = await this._getKeyValue(walletHash); - if (!res) return false; - return res.priv; - } else { - const key = this.getAddressCacheKey( - walletHash, - discoverPath, - currencyCode - ); - return this.getAddressCache(key); - } - } catch (e) { - // do nothing - } - return false; - } - - async getAllWalletsText() { - let res = ''; - for (let i = 1; i <= 3; i++) { - try { - const wallet = await this._getKeyValue('wallet_' + i); - if (typeof wallet.priv !== 'undefined' && wallet.priv !== 'undefined') { - res += ' WALLET#' + i + ' ' + wallet.priv; - } - } catch (e) { - // do nothing - } - } - if (res === '') { - return 'Nothing found by general search'; - } - return res; - } - - /** - * @return {Promise} - */ - async countMnemonics() { - await this._init(); - return this._serviceWalletsCounter; - } - - /** - * public list of wallets hashes - * @return {Array} - */ - async getWallets() { - await this._init(); - return this.publicWallets; - } - - /** - * public wallet mnemonic by hash - * @return {string} - */ - async getWalletMnemonic(hashOrId, source = 'default') { - await this._init(); - if (!this._serviceWallets[hashOrId]) { - throw new Error( - 'undefined wallet with hash ' + hashOrId + ' source ' + source - ); - } - return this._serviceWallets[hashOrId]; - } - - /** - * @param {string} newMnemonic.mnemonic - * @param {string} newMnemonic.hash - * @return {Promise} - */ - async isMnemonicAlreadySaved(newMnemonic) { - await this._init(); - if ( - typeof this._serviceWallets[newMnemonic.hash] === 'undefined' || - !this._serviceWallets[newMnemonic.hash] - ) { - return false; - } - if (this._serviceWallets[newMnemonic.hash] !== newMnemonic.mnemonic) { - throw new Error('something wrong with hash algorithm'); - } - return true; - } - - /** - * @param {string} newMnemonic.mnemonic - * @param {string} newMnemonic.hash - * @return {Promise} - */ - async saveMnemonic(newMnemonic) { - await this._init(); - - const logData = { ...newMnemonic }; - if (typeof logData.mnemonic !== 'undefined') logData.mnemonic = '***'; - AirDAOCryptoLog.log('BlocksoftKeysStorage saveMnemonic', logData); - - if (!newMnemonic.hash) { - throw new Error('unique hash required ' + JSON.stringify(newMnemonic)); - } - if ( - typeof this._serviceWallets[newMnemonic.hash] !== 'undefined' && - this._serviceWallets[newMnemonic.hash] - ) { - if (this._serviceWallets[newMnemonic.hash] !== newMnemonic.mnemonic) { - throw new Error('something wrong with hash algorithm'); - } - return newMnemonic.hash; // its ok - } - this._serviceWalletsCounter++; - - const unique = newMnemonic.hash; - - /* - hash should give unique values for different mnemonics - let i = 0 - while (this._serviceWallets[unique]) { - unique = newMnemonic.hash + '_' + i - i++ - if (i > 1000) { - throw new Error('unique hash is not reachable for ' + newMnemonic.hash) - } - } - */ - - await this._setKeyValue(unique, unique, newMnemonic.mnemonic); - await this._setKeyValue( - 'wallet_' + this._serviceWalletsCounter, - unique, - newMnemonic.mnemonic - ); - await this._setKeyValue('wallets_counter', this._serviceWalletsCounter); - this._serviceWallets[unique] = newMnemonic.mnemonic; - this._serviceWallets[this._serviceWalletsCounter - 1] = - newMnemonic.mnemonic; - - this.publicWallets.push(unique); - this.publicSelectedWallet = unique; - - return newMnemonic.hash; - } - - getAddressCacheKey(walletHash, discoverPath, currencyCode) { - const settings = AirDAODict.getCurrencyAllSettings(currencyCode); - if ( - typeof settings.addressCurrencyCode !== 'undefined' && - typeof settings.tokenBlockchain !== 'undefined' && - settings.tokenBlockchain !== 'BITCOIN' - ) { - return ( - walletHash + '_' + discoverPath + '_' + settings.addressCurrencyCode - ); - } - return walletHash + '_' + discoverPath + '_' + currencyCode; - } - - async getAddressCache(hashOrId) { - try { - const res = await this._getKeyValue('ar4_' + hashOrId); - if (!res || !res.priv || res.pub === res.priv) return false; - return { address: res.pub, privateKey: res.priv }; - } catch (e) { - return false; - } - } - - async setAddressCache(hashOrId, res) { - if (typeof res.privateKey === 'undefined' || !res.privateKey) { - return false; - } - return this._setKeyValue('ar4_' + hashOrId, res.address, res.privateKey); - } - - async getLoginCache(hashOrId) { - const res = await this._getKeyValue('login_' + hashOrId); - if (!res) return false; - return { login: res.pub, pass: res.priv }; - } - - async setLoginCache(hashOrId, res) { - return this._setKeyValue('login_' + hashOrId, res.login, res.pass); - } - - async setSettingValue(hashOrId, value) { - return this._setKeyValue('setting_' + hashOrId, hashOrId, value); - } - - async getSettingValue(hashOrId) { - const res = await this._getKeyValue('setting_' + hashOrId); - if (!res) return '0'; - return res.priv.toString(); - } -} - -const singleBlocksoftKeysStorage = new BlocksoftKeysStorage(); -export default singleBlocksoftKeysStorage; diff --git a/crypto/actions/BlocksoftSecrets/BlocksoftSecrets.ts b/crypto/actions/BlocksoftSecrets/BlocksoftSecrets.ts deleted file mode 100644 index 1fd728473..000000000 --- a/crypto/actions/BlocksoftSecrets/BlocksoftSecrets.ts +++ /dev/null @@ -1,54 +0,0 @@ -/** - * @author Ksu - * @version 0.11 - */ -import AirDAOCryptoLog from '../../common/AirDAOCryptoLog'; -import AirDAODispatcher from '../../blockchains/AirDAODispatcher'; - -class BlocksoftSecrets { - /** - * @type {{}} - * @private - */ - _processor = {}; - - /** - * @param {string} params.currencyCode - * @param {string} params.mnemonic - * @return {Promise<{hash}>} - */ - async getWords(params) { - const currencyCode = params.currencyCode; - if (!currencyCode) { - throw new Error('plz set currencyCode before calling'); - } - - if (!this._processor[currencyCode]) { - /** - * @type {XmrSecretsProcessor} - */ - this._processor[currencyCode] = - AirDAODispatcher.getSecretsProcessor(currencyCode); - } - - let res = ''; - try { - AirDAOCryptoLog.log(`BlocksoftSecrets.getWords ${currencyCode} started`); - res = await this._processor[currencyCode].getWords(params); - AirDAOCryptoLog.log(`BlocksoftSecrets.getWords ${currencyCode} finished`); - } catch (e) { - // noinspection ES6MissingAwait - AirDAOCryptoLog.err( - `BlocksoftSecrets.getWords ${currencyCode} error ` + e.message, - e.data ? e.data : e - ); - throw e; - } - - return res; - } -} - -const singleBlocksoftSecrets = new BlocksoftSecrets(); - -export default singleBlocksoftSecrets; diff --git a/crypto/actions/BlocksoftTokenChecks/BlocksoftTokenChecks.js b/crypto/actions/BlocksoftTokenChecks/BlocksoftTokenChecks.js deleted file mode 100644 index 693d4e85b..000000000 --- a/crypto/actions/BlocksoftTokenChecks/BlocksoftTokenChecks.js +++ /dev/null @@ -1,59 +0,0 @@ -/** - * @author Ksu - * @version 0.5 - */ -import BlocksoftDispatcher from '../../blockchains/BlocksoftDispatcher' -import config from '@app/config/config' - -class BlocksoftTokenChecks { - - /** - * @type {{}} - * @private - */ - _processor = {} - - /** - * - * @param data.tokenType - * @param data.tokenAddress - * @returns {Promise<{tokenAddress: *, currencyName: *, provider: string, tokenDecimals: *, icon: *, description: *, tokenType: string, currencyCode: *}|boolean|{tokenAddress: *, currencyName: *, provider: string, tokenDecimals: *, icon: boolean, description: boolean, tokenType: string, currencyCode: *}|*|undefined>} - */ - async getTokenDetails(data) { - try { - if (!this._processor[data.tokenType]) { - this._processor[data.tokenType] = BlocksoftDispatcher.getTokenProcessor(data.tokenType) - } - const res = await this._processor[data.tokenType].getTokenDetails(data.tokenAddress) - // can log if needed - return res - } catch (e) { - if (config.debug.cryptoErrors) { - console.log('BlocksoftTokenChecks.getTokenDetails error ' + e.message) - } - e.code = 'ERROR_SYSTEM' - throw e - } - } - - /** - * - * @param data.nftType - * @param data.nftAddress - */ - async getNftDetails(data) { - try { - if (!this._processor[data.nftType]) { - this._processor[data.nftType] = BlocksoftDispatcher.getTokenNftsProcessor(data.nftType) - } - return this._processor[data.nftType].getNftDetails(data.nftAddress, data.nftType) - } catch (e) { - e.code = 'ERROR_SYSTEM' - throw e - } - } - -} - -const singleBlocksoftTokenChecks = new BlocksoftTokenChecks() -export default singleBlocksoftTokenChecks diff --git a/crypto/actions/BlocksoftTokenNfts/BlocksoftTokenNfts.js b/crypto/actions/BlocksoftTokenNfts/BlocksoftTokenNfts.js deleted file mode 100644 index 14250bef6..000000000 --- a/crypto/actions/BlocksoftTokenNfts/BlocksoftTokenNfts.js +++ /dev/null @@ -1,35 +0,0 @@ -/** - * @author Ksu - * @version 0.5 - */ -import BlocksoftDispatcher from '../../blockchains/BlocksoftDispatcher' - -class BlocksoftTokenNfts { - - /** - * @type {{}} - * @private - */ - _processor = {} - - /** - * - * @param data.tokenBlockchainCode - * @param data.address - */ - async getList(data) { - try { - if (!this._processor[data.tokenBlockchainCode]) { - this._processor[data.tokenBlockchainCode] = BlocksoftDispatcher.getTokenNftsProcessor(data.tokenBlockchainCode) - } - return this._processor[data.tokenBlockchainCode].getListBlockchain(data) - } catch (e) { - e.code = 'ERROR_SYSTEM' - throw e - } - } - -} - -const singleBlocksoftTokenNfts = new BlocksoftTokenNfts() -export default singleBlocksoftTokenNfts diff --git a/crypto/actions/BlocksoftTransactions/BlocksoftTransactions.js b/crypto/actions/BlocksoftTransactions/BlocksoftTransactions.js deleted file mode 100644 index 740891f3a..000000000 --- a/crypto/actions/BlocksoftTransactions/BlocksoftTransactions.js +++ /dev/null @@ -1,126 +0,0 @@ -/** - * @author Ksu - * @version 0.5 - */ -import BlocksoftDispatcher from '../../blockchains/BlocksoftDispatcher' - -class BlocksoftTransactions { - - /** - * @type {{}} - * @private - */ - _processor = {} - - /** - * @return {Promise} - */ - async getTransactions(data, source = '') { - const currencyCode = data.account.currencyCode - if (!currencyCode) { - throw new Error('plz set currencyCode before calling') - } - if (!this._processor[currencyCode]) { - /** - * @type {EthScannerProcessor|BtcScannerProcessor|UsdtScannerProcessor} - */ - this._processor[currencyCode] = BlocksoftDispatcher.getScannerProcessor(currencyCode) - } - let resultData = [] - try { - resultData = await this._processor[currencyCode].getTransactionsBlockchain(data, source) - } catch (e) { - e.code = 'ERROR_SYSTEM' - e.message += ' on actual getTransactions step ' - throw e - } - - return resultData - } - - /** - * @return {Promise} - */ - async getTransactionsPending(data, source = '') { - const currencyCode = data.account.currencyCode - if (!currencyCode) { - throw new Error('plz set currencyCode before calling') - } - if (!this._processor[currencyCode]) { - /** - * @type {TrxScannerProcessor} - */ - this._processor[currencyCode] = BlocksoftDispatcher.getScannerProcessor(currencyCode) - } - if (typeof this._processor[currencyCode].getTransactionsPendingBlockchain === 'undefined') { - return false - } - let resultData = false - try { - resultData = await this._processor[currencyCode].getTransactionsPendingBlockchain(data, source, false) - } catch (e) { - e.message += ' on actual getTransactionsPending step ' - throw e - } - - return resultData - } - - /** - * @return {Promise} - */ - async resetTransactionsPending(data, source = '') { - const currencyCode = data.account.currencyCode - if (!currencyCode) { - throw new Error('plz set currencyCode before calling') - } - if (!this._processor[currencyCode]) { - /** - * @type {TrxScannerProcessor} - */ - this._processor[currencyCode] = BlocksoftDispatcher.getScannerProcessor(currencyCode) - } - if (typeof this._processor[currencyCode].resetTransactionsPendingBlockchain === 'undefined') { - return false - } - let resultData = false - try { - resultData = await this._processor[currencyCode].resetTransactionsPendingBlockchain(data, source, false) - } catch (e) { - e.message += ' on actual resetTransactionsPending step ' - throw e - } - - return resultData - } - - /** - * @return {Promise} - */ - async getAddresses(data, source = '') { - const currencyCode = data.account.currencyCode - if (!currencyCode) { - throw new Error('plz set currencyCode before calling') - } - if (!this._processor[currencyCode]) { - /** - * @type {EthScannerProcessor|BtcScannerProcessor|UsdtScannerProcessor} - */ - this._processor[currencyCode] = BlocksoftDispatcher.getScannerProcessor(currencyCode) - } - let resultData = [] - try { - resultData = await this._processor[currencyCode].getAddressesBlockchain(data, source) - } catch (e) { - e.code = 'ERROR_SYSTEM' - e.message += ' on actual getAddressesBlockchain step ' - throw e - } - - return resultData - } -} - -const singleBlocksoftTransactions = new BlocksoftTransactions() - -export default singleBlocksoftTransactions diff --git a/crypto/actions/BlocksoftTransfer/BlocksoftTransferUtils.ts b/crypto/actions/BlocksoftTransfer/BlocksoftTransferUtils.ts deleted file mode 100644 index 63ef9c7a4..000000000 --- a/crypto/actions/BlocksoftTransfer/BlocksoftTransferUtils.ts +++ /dev/null @@ -1,78 +0,0 @@ -/** - * @author Ksu - * @version 0.20 - */ -import { AirDAODictTypes } from '../../common/AirDAODictTypes'; -import { BlocksoftTransferDispatcher } from '../../blockchains/BlocksoftTransferDispatcher'; -import { BlocksoftBlockchainTypes } from '../../blockchains/BlocksoftBlockchainTypes'; -import BlocksoftUtils from '../../common/AirDAOUtils'; - -export namespace BlocksoftTransferUtils { - export const getAddressToForTransferAll = function (data: { - currencyCode: AirDAODictTypes.Code; - address: string; - }): string { - if (data.currencyCode === AirDAODictTypes.Code.BTC_TEST) { - return 'mjojEgUSi68PqNHoAyjhVkwdqQyLv9dTfV'; - } - if (data.currencyCode === AirDAODictTypes.Code.XRP) { - const tmp1 = 'rEAgA9B8U8RCkwn6MprHqE1ZfXoeGQxz4P'; - const tmp2 = 'rnyWPfJ7dk2X15N7jqwmqo3Nspu1oYiRZ3'; - return data.address === tmp1 ? tmp2 : tmp1; - } - if (data.currencyCode === AirDAODictTypes.Code.XLM) { - const tmp1 = 'GCVPV3D4PAYFA7H2CHGFRTSPAHMSU4KQR4CHBUBUR4X23JUDJWHYZDYZ'; - const tmp2 = 'GAQA5FITDUZW36J6VFXAH4YDNTTDEGRNWIXHIOR3FNN4DVJCXCNHUR4E'; - return data.address === tmp1 ? tmp2 : tmp1; - } - return data.address; - }; - - export const checkTransferHasError = async function ( - data: BlocksoftBlockchainTypes.CheckTransferHasErrorData - ): Promise<{ isOk: boolean; code?: string }> { - const processor = BlocksoftTransferDispatcher.getTransferProcessor( - data.currencyCode - ); - if (typeof processor.checkTransferHasError === 'undefined') { - return { isOk: true }; - } - return processor.checkTransferHasError(data); - }; - - export const getBalanceForTransfer = function (data: { - balance: string; - unconfirmed: string | boolean; - balanceStaked: string; - currencyCode: AirDAODictTypes.Code; - }): string { - if (data.currencyCode === AirDAODictTypes.Code.TRX) { - if (data.balanceStaked && data.balanceStaked !== '') { - return BlocksoftUtils.diff(data.balance, data.balanceStaked); - } - } - if (data.unconfirmed === false) { - return data.balance; - } - // @ts-ignore - if (data.unconfirmed * 1 < 0) { - return data.balance; - } - if (data.currencyCode === AirDAODictTypes.Code.XRP) { - return data.balance; - } - if ( - data.currencyCode === AirDAODictTypes.Code.ETH || - data.currencyCode.indexOf('ETH_') === 0 - ) { - return data.balance; - } - if ( - data.currencyCode === AirDAODictTypes.Code.TRX || - data.currencyCode.indexOf('TRX_') === 0 - ) { - return data.balance; - } - return BlocksoftUtils.add(data.balance, data.unconfirmed).toString(); - }; -} diff --git a/crypto/assets/dappsBlocksoftDict.json b/crypto/assets/dappsBlocksoftDict.json deleted file mode 100644 index 0d66d3087..000000000 --- a/crypto/assets/dappsBlocksoftDict.json +++ /dev/null @@ -1,66 +0,0 @@ -{ - "OPENSEA": { - "dappCode" : "OPENSEA", - "dappName" : "OpenSea", - "dappUrl" : "https://opensea.io/account", - "dappNetworks" : ["ETH", "MATIC"], - "dappsDomenName": "opensea.io" - }, - "PANCAKE" : { - "dappCode" : "PANCAKE", - "dappName" : "Pancake Swap", - "dappUrl" : "https://pancakeswap.finance/swap", - "dappNetworks" : ["BNB_SMART"], - "disableInjected" : true, - "dappsDomenName": "pancakeswap.finance" - }, - "ETH_LIDO" : { - "dappCode" : "ETH_LIDO", - "dappName" : "Ethereum Liquid Staking (LIDO)", - "dappUrl" : "https://stake.lido.fi", - "dappNetworks" : ["ETH"], - "disableInjected" : true, - "dappsDomenName": "lido.fi" - }, - "MATIC_LIDO" : { - "dappCode" : "MATIC_LIDO", - "dappName" : "Polygon Liquid Staking (LIDO)", - "dappUrl" : "https://polygon.lido.fi", - "dappNetworks" : ["MATIC"], - "disableInjected" : true, - "dappsDomenName": "lido.fi" - }, - "NFTRADE" : { - "dappCode" : "NFTRADE", - "dappName" : "NFTrade", - "dappUrl" : "https://nftrade.com/", - "dappNetworks" : ["ETH", "BNB_SMART", "MATIC"], - "disableInjected" : true, - "dappsDomenName": "nftrade.com" - }, - "JUSTLEND" : { - "dappCode": "JUSTLEND", - "dappName" : "JustLend", - "dappUrl" : "https://justlend.org/#/home", - "dappNetworks" : ["TRX"], - "dappCoins" : ["TRX", "TRX_USDT"], - "dappsDomenName": "justlend.org" - }, - "BTT" : { - "dappCode": "BTT", - "dappName" : "BitTorrent Chain Wallet", - "dappUrl" : "https://wallet.bt.io/staking", - "dappNetworks" : ["TRX", "BTTC"], - "dappCoins" : ["TRX", "BTT"], - "dappsDomenName": "wallet.bt.io" - }, - "EXON" : { - "dappCode" : "EXON", - "dappName" : "Exon Center", - "dappUrl" : "https://app.exoncenter.com/430", - "dappNetworks" : ["TRX"], - "dappCoins" : ["TRX_EXON"], - "dappsDomenName": "app.exoncenter.com" - } - -} diff --git a/crypto/assets/tokenBlockchainBlocksoftDict.json b/crypto/assets/tokenBlockchainBlocksoftDict.json deleted file mode 100644 index 911fdd13e..000000000 --- a/crypto/assets/tokenBlockchainBlocksoftDict.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "BITCOIN": { - "currencyCode" : "BTC", - "blockchainName": "Bitcoin" - }, - "ETHEREUM" : { - "currencyCode" : "ETH", - "blockchainName" : "Ethereum" - }, - "TRON" : { - "currencyCode" : "TRX", - "blockchainName" : "Tron" - }, - "BNB" : { - "currencyCode": "BNB_SMART", - "blockchainName" : "BSC (BEP-20)", - "dappsListName" : "BNB Smart Chain" - }, - "MATIC" : { - "currencyCode" : "MATIC", - "blockchainName" : "Matic", - "dappsListName" : "Polygon Network" - }, - "FTM" : { - "currencyCode": "FTM", - "blockchainName" : "FTM" - }, - "SOLANA" : { - "currencyCode" : "SOL", - "blockchainName" : "Solana" - }, - "METIS" : { - "currencyCode" : "METIS", - "blockchainName": "Metis" - }, - "ONE" : { - "currencyCode" : "ONE", - "blockchainName" : "Harmony" - } -} diff --git a/crypto/blockchains/AirDAODispatcher.ts b/crypto/blockchains/AirDAODispatcher.ts index b14e03bdc..b3749e19b 100644 --- a/crypto/blockchains/AirDAODispatcher.ts +++ b/crypto/blockchains/AirDAODispatcher.ts @@ -45,7 +45,7 @@ import EthTokenProcessorErc20 from '@crypto/blockchains/eth/EthTokenProcessorErc // import XmrAddressProcessor from '@crypto/blockchains/xmr/XmrAddressProcessor'; // import XmrScannerProcessor from '@crypto/blockchains/xmr/XmrScannerProcessor'; -import XmrSecretsProcessor from '@crypto/blockchains/xmr/XmrSecretsProcessor'; +// import XmrSecretsProcessor from '@crypto/blockchains/xmr/XmrSecretsProcessor'; // import FioAddressProcessor from '@crypto/blockchains/fio/FioAddressProcessor'; // import FioScannerProcessor from '@crypto/blockchains/fio/FioScannerProcessor'; @@ -314,16 +314,16 @@ class AirDAODispatcher { } } - getSecretsProcessor(currencyCode: string): XmrSecretsProcessor { - const currencyDictSettings = - BlockchainUtils.getCurrencyAllSettings(currencyCode); - if (currencyDictSettings.currencyCode !== 'XMR') { - throw new Error( - 'Unknown secretsProcessor ' + currencyDictSettings.currencyCode - ); - } - return new XmrSecretsProcessor(); - } + // getSecretsProcessor(currencyCode: string): XmrSecretsProcessor { + // const currencyDictSettings = + // BlockchainUtils.getCurrencyAllSettings(currencyCode); + // if (currencyDictSettings.currencyCode !== 'XMR') { + // throw new Error( + // 'Unknown secretsProcessor ' + currencyDictSettings.currencyCode + // ); + // } + // return new XmrSecretsProcessor(); + // } } const singleAirDAODispatcher = new AirDAODispatcher(); diff --git a/crypto/blockchains/AirDAOTransferDispatcher.ts b/crypto/blockchains/AirDAOTransferDispatcher.ts index ccc7d2a39..2a7d128ab 100644 --- a/crypto/blockchains/AirDAOTransferDispatcher.ts +++ b/crypto/blockchains/AirDAOTransferDispatcher.ts @@ -96,9 +96,7 @@ export namespace AirDAOTransferDispatcher { ); break; case 'ETC': - CACHE_PROCESSORS[currencyCode] = new EtcTransferProcessor( - currencyDictSettings - ); + CACHE_PROCESSORS[currencyCode] = new currencyDictSettings(); break; // case 'BNB_SMART_20': // CACHE_PROCESSORS[currencyCode] = new BnbSmartTransferProcessorErc20( diff --git a/crypto/blockchains/ash/AshAddressProcessor.js b/crypto/blockchains/ash/AshAddressProcessor.js deleted file mode 100644 index a2103434b..000000000 --- a/crypto/blockchains/ash/AshAddressProcessor.js +++ /dev/null @@ -1,48 +0,0 @@ -/** - * @version 0.5 - */ -import { crypto, base58Encode } from '@waves/ts-lib-crypto'; - -export default class AshAddressProcessor { - async setBasicRoot() { - // empty - } - - /** - * @param {string|Buffer} _privateKey - * @param {*} data - * @returns {Promise<{privateKey: string, address: string, addedData: *}>} - */ - // eslint-disable-next-line @typescript-eslint/no-unused-vars - async getAddress(_privateKey, _data = {}, superPrivateData = {}) { - const all = crypto({ seed: superPrivateData.mnemonic }); - const key2 = all.keyPair(); - - // nope: console.log(` buff2 ` + base58Encode(buildAddress(key2.publicKey, 'A'))) - - // 1. take the byte array representation of generated public key - // 2. create new byte array XX - // 3. first two byte values in array XX would be [1, 65] which stands for address version and chainId (character ‘A’) - const prefix = [1, 65]; - - // 4. calculate Keccak256.hash(Blake2b256.hash(pk)) where pk is public key array of bytes - const blaked = all.blake2b(key2.publicKey); - const keccaked = all.keccak(blaked); - - // 5. append result of step 4 to array XX - const appended = all.concat(prefix, keccaked); - - // 6. generate checksum of array XX and append it to XX. Checksum algorithm: - // 1. generate Blake2b256 hash of given array - // 2. take the first 4 elements of generated Blake2b256 hash - const hash = all.blake2b(appended).slice(0, 4); - const hashed = all.concat(appended, hash); - - // 7. Encode the result array XX to Base58 string - // 8. Append 'Æx' character to the end of encoded string - const address = 'Æx' + base58Encode(hashed); - const pubKey = base58Encode(key2.publicKey); - const privKey = base58Encode(key2.privateKey); - return { address, privateKey: privKey, addedData: { pubKey } }; - } -} diff --git a/crypto/blockchains/bch/BchAddressProcessor.js b/crypto/blockchains/bch/BchAddressProcessor.js deleted file mode 100644 index 7e950da9a..000000000 --- a/crypto/blockchains/bch/BchAddressProcessor.js +++ /dev/null @@ -1,31 +0,0 @@ -/** - * @version 0.5 - */ -import BtcCashUtils from './ext/BtcCashUtils'; -import BtcAddressProcessor from '../btc/address/BtcAddressProcessor'; -import bitcoin from 'bitcoinjs-lib'; - -export default class BchAddressProcessor extends BtcAddressProcessor { - /** - * @param {string|Buffer} privateKey - * @param {*} data - * @returns {Promise<{privateKey: string, address: string, addedData: *}>} - */ - // eslint-disable-next-line @typescript-eslint/no-unused-vars - async getAddress(privateKey, _data = {}) { - const keyPair = bitcoin.ECPair.fromPrivateKey(privateKey, { - network: this._currentBitcoinNetwork - }); - const publicKey = keyPair.publicKey; - const address = BtcCashUtils.fromPublicKeyToAddress(publicKey); - const legacyAddress = bitcoin.payments.p2pkh({ - pubkey: keyPair.publicKey, - network: this._currentBitcoinNetwork - }).address; - return { - address, - privateKey: keyPair.toWIF(), - addedData: { legacyAddress } - }; - } -} diff --git a/crypto/blockchains/bch/BchScannerProcessor.js b/crypto/blockchains/bch/BchScannerProcessor.js deleted file mode 100644 index 522872d2b..000000000 --- a/crypto/blockchains/bch/BchScannerProcessor.js +++ /dev/null @@ -1,41 +0,0 @@ -/** - * @version 0.11 - * https://developer.bitcoin.com/rest/docs/address => trezor - */ -import DogeScannerProcessor from '../doge/DogeScannerProcessor'; -import BtcCashUtils from './ext/BtcCashUtils'; - -export default class BchScannerProcessor extends DogeScannerProcessor { - /** - * @type {number} - * @private - */ - _blocksToConfirm = 5; - - /** - * @type {string} - * @private - */ - _trezorServerCode = 'BCH_TREZOR_SERVER'; - - async _get(address, jsonData) { - let legacyAddress; - if (typeof jsonData.legacyAddress !== 'undefined') { - legacyAddress = jsonData.legacyAddress; - } else { - legacyAddress = BtcCashUtils.toLegacyAddress(address); - } - return super._get(legacyAddress); - } - - _addressesForFind(address, jsonData) { - let legacyAddress; - if (typeof jsonData.legacyAddress !== 'undefined') { - legacyAddress = jsonData.legacyAddress; - } else { - legacyAddress = BtcCashUtils.toLegacyAddress(address); - } - - return [address, legacyAddress, 'bitcoincash:' + address]; - } -} diff --git a/crypto/blockchains/bch/BchTransferProcessor.ts b/crypto/blockchains/bch/BchTransferProcessor.ts deleted file mode 100644 index 1444060e0..000000000 --- a/crypto/blockchains/bch/BchTransferProcessor.ts +++ /dev/null @@ -1,48 +0,0 @@ -/** - * @version 0.20 - */ -import { AirDAOBlockchainTypes } from '../AirDAOBlockchainTypes'; -import DogeTransferProcessor from '../doge/DogeTransferProcessor'; -import BchUnspentsProvider from './providers/BchUnspentsProvider'; -import BchSendProvider from './providers/BchSendProvider'; -import DogeTxInputsOutputs from '../doge/tx/DogeTxInputsOutputs'; -import BchTxBuilder from './tx/BchTxBuilder'; - -export default class BchTransferProcessor - extends DogeTransferProcessor - implements AirDAOBlockchainTypes.TransferProcessor -{ - _trezorServerCode = 'BCH_TREZOR_SERVER'; - - _builderSettings: AirDAOBlockchainTypes.BuilderSettings = { - minOutputDustReadable: 0.000005, - minChangeDustReadable: 0.00001, - feeMaxForByteSatoshi: 10000, // for tx builder - feeMaxAutoReadable2: 0.2, // for fee calc, - feeMaxAutoReadable6: 0.1, // for fee calc - feeMaxAutoReadable12: 0.05, // for fee calc - changeTogether: true, - minRbfStepSatoshi: 10, - minSpeedUpMulti: 1.5 - }; - /** - * @private - */ - _initProviders() { - if (this._initedProviders) return false; - this.unspentsProvider = new BchUnspentsProvider( - this._settings, - this._trezorServerCode - ); - this.sendProvider = new BchSendProvider( - this._settings, - this._trezorServerCode - ); - this.txPrepareInputsOutputs = new DogeTxInputsOutputs( - this._settings, - this._builderSettings - ); - this.txBuilder = new BchTxBuilder(this._settings, this._builderSettings); - this._initedProviders = true; - } -} diff --git a/crypto/blockchains/bch/ext/BtcCashUtils.js b/crypto/blockchains/bch/ext/BtcCashUtils.js deleted file mode 100644 index 9d5b4d5b4..000000000 --- a/crypto/blockchains/bch/ext/BtcCashUtils.js +++ /dev/null @@ -1,233 +0,0 @@ -/* eslint-disable @typescript-eslint/no-var-requires */ -/** - * @author Ksu - * @version 0.5 - */ -const bs58check = require('bs58check'); -const createHash = require('create-hash'); - -const VERSION_BYTE = { - P2PKH: 0, - P2SH: 5 -}; - -// for legacy - -const CHARSET_INVERSE_INDEX = { - q: 0, - p: 1, - z: 2, - r: 3, - y: 4, - 9: 5, - x: 6, - 8: 7, - g: 8, - f: 9, - 2: 10, - t: 11, - v: 12, - d: 13, - w: 14, - 0: 15, - s: 16, - 3: 17, - j: 18, - n: 19, - 5: 20, - 4: 21, - k: 22, - h: 23, - c: 24, - e: 25, - 6: 26, - m: 27, - u: 28, - a: 29, - 7: 30, - l: 31 -}; - -function fromUint5Array(data) { - return convertBits(data, 5, 8, true); -} - -function getType(versionByte) { - switch (versionByte & 120) { - case 0: - return 'P2PKH'; - case 8: - return 'P2SH'; - default: - throw new Error( - 'Invalid address type in version byte: ' + versionByte + '.' - ); - } -} - -function base32decode(string) { - const data = new Uint8Array(string.length); - for (let i = 0; i < string.length; ++i) { - const value = string[i]; - data[i] = CHARSET_INVERSE_INDEX[value]; - } - return data; -} - -// for bitcoincash - -const GENERATOR1 = [0x98, 0x79, 0xf3, 0xae, 0x1e]; -const GENERATOR2 = [0xf2bc8e61, 0xb76d99e2, 0x3e5fb3c4, 0x2eabe2a8, 0x4f43e470]; - -const CHARSET = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l'; - -function base32encode(data) { - let base32 = ''; - for (let i = 0; i < data.length; i++) { - const value = data[i]; - base32 += CHARSET[value]; - } - return base32; -} - -function polymod(data) { - // Treat c as 8 bits + 32 bits - let c0 = 0; - let c1 = 1; - let C = 0; - for (let j = 0; j < data.length; j++) { - // Set C to c shifted by 35 - C = c0 >>> 3; - // 0x[07]ffffffff - c0 &= 0x07; - // Shift as a whole number - c0 <<= 5; - c0 |= c1 >>> 27; - // 0xffffffff >>> 5 - c1 &= 0x07ffffff; - c1 <<= 5; - // xor the last 5 bits - c1 ^= data[j]; - for (let i = 0; i < GENERATOR1.length; ++i) { - if (C & (1 << i)) { - c0 ^= GENERATOR1[i]; - c1 ^= GENERATOR2[i]; - } - } - } - c1 ^= 1; - // Negative numbers -> large positive numbers - if (c1 < 0) { - c1 ^= 1 << 31; - c1 += (1 << 30) * 2; - } - // Unless bitwise operations are used, - // numbers are consisting of 52 bits, except - // the sign bit. The result is max 40 bits, - // so it fits perfectly in one number! - return c0 * (1 << 30) * 4 + c1; -} - -function checksumToArray(checksum) { - const result = []; - for (let i = 0; i < 8; ++i) { - result.push(checksum & 31); - checksum /= 32; - } - return result.reverse(); -} - -function getHashSizeBits(hash) { - switch (hash.length * 8) { - case 160: - return 0; - case 192: - return 1; - case 224: - return 2; - case 256: - return 3; - case 320: - return 4; - case 384: - return 5; - case 448: - return 6; - case 512: - return 7; - default: - throw new Error('Invalid hash size:' + hash.length); - } -} - -function convertBits(data, from, to, strict) { - strict = strict || false; - let accumulator = 0; - let bits = 0; - const result = []; - const mask = (1 << to) - 1; - for (let i = 0; i < data.length; i++) { - const value = data[i]; - accumulator = (accumulator << from) | value; - bits += from; - while (bits >= to) { - bits -= to; - result.push((accumulator >> bits) & mask); - } - } - if (!strict) { - if (bits > 0) { - result.push((accumulator << (to - bits)) & mask); - } - } - return result; -} - -function fromHashToAddress(hash) { - const eight0 = [0, 0, 0, 0, 0, 0, 0, 0]; - // noinspection PointlessArithmeticExpressionJS - const versionByte = 0 + getHashSizeBits(hash); // getTypeBits(this.type) - const arr = Array.prototype.slice.call(hash, 0); - const payloadData = convertBits([versionByte].concat(arr), 8, 5); - - const prefixData = [2, 9, 20, 3, 15, 9, 14, 3, 1, 19, 8, 0]; - const checksumData = prefixData.concat(payloadData).concat(eight0); - const payload = payloadData.concat(checksumToArray(polymod(checksumData))); - return base32encode(payload); -} - -export default { - fromPublicKeyToAddress(publicKey) { - const one = createHash('sha256').update(publicKey, 'hex').digest(); - const hash = createHash('ripemd160').update(one).digest(); - return fromHashToAddress(hash); - }, - toLegacyAddress(address) { - if (address.indexOf('bitcoincash:') === 0) { - address = address.substr(12); - } - if (address.substr(0, 1) !== 'q' && address.substr(0, 1) !== 'p') { - return address; - } - const payloadBack = base32decode(address); - const payloadDataBack = fromUint5Array(payloadBack.subarray(0, -8)); - const versionByteBack = payloadDataBack[0]; - const hashBack = payloadDataBack.slice(1); - const typeBack = getType(versionByteBack); - const buffer = Buffer.alloc(1 + hashBack.length); - buffer[0] = VERSION_BYTE[typeBack]; - buffer.set(hashBack, 1); - return bs58check.encode(buffer); - }, - fromLegacyAddress(address) { - if (!address || address === '') { - return ''; - } - if (address.substr(0, 1) === 'q') { - return address; - } - let hash = bs58check.decode(address); - hash = hash.subarray(1); - return fromHashToAddress(hash); - } -}; diff --git a/crypto/blockchains/bch/providers/BchSendProvider.ts b/crypto/blockchains/bch/providers/BchSendProvider.ts deleted file mode 100644 index 188825655..000000000 --- a/crypto/blockchains/bch/providers/BchSendProvider.ts +++ /dev/null @@ -1,69 +0,0 @@ -/** - * @version 0.20 - */ -import { AirDAOBlockchainTypes } from '../../AirDAOBlockchainTypes'; -import AirDAOCryptoLog from '../../../common/AirDAOCryptoLog'; -import AirDAOAxios from '../../../common/AirDAOAxios'; -import DogeSendProvider from '../../doge/providers/DogeSendProvider'; - -export default class BchSendProvider - extends DogeSendProvider - implements AirDAOBlockchainTypes.SendProvider -{ - _apiPath = 'https://rest.bitcoin.com/v2/rawtransactions/sendRawTransaction/'; - - async sendTx( - hex: string, - subtitle: string, - txRBF: any, - logData: any - ): Promise<{ transactionHash: string; transactionJson: any }> { - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' BchSendProvider.sendTx ' + - subtitle + - ' started ' + - subtitle - ); - - try { - const trezor = await super.sendTx(hex, subtitle, txRBF, logData); - if (trezor) { - return trezor; - } - } catch (error) { - const e = error as unknown as any; - if (e.message.indexOf('SERVER_RESPONSE_') !== -1) { - throw e; - } else { - // do nothing - } - } - - let res; - try { - res = (await AirDAOAxios.get(this._apiPath + hex)) as any; - } catch (error) { - const e = error as unknown as any; - if (e.message.indexOf('dust') !== -1) { - throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_DUST'); - } else if ( - e.message.indexOf('bad-txns-inputs-spent') !== -1 || - e.message.indexOf('txn-mempool-conflict') !== -1 - ) { - throw new Error('SERVER_RESPONSE_NO_RESPONSE'); - } else if ( - e.message.indexOf('fee for relay') !== -1 || - e.message.indexOf('insufficient priority') !== -1 - ) { - throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_FEE'); - } else { - throw e; - } - } - if (typeof res.data === 'undefined' || !res.data) { - throw new Error('SERVER_RESPONSE_NOT_CONNECTED'); - } - return { transactionHash: res.data, transactionJson: {} }; - } -} diff --git a/crypto/blockchains/bch/providers/BchUnspentsProvider.ts b/crypto/blockchains/bch/providers/BchUnspentsProvider.ts deleted file mode 100644 index 9981409e6..000000000 --- a/crypto/blockchains/bch/providers/BchUnspentsProvider.ts +++ /dev/null @@ -1,76 +0,0 @@ -/** - * @version 0.20 - * - * https://developer.bitcoin.com/rest/docs/address - * https://rest.bitcoin.com/v2/address/utxo/bitcoincash:qz6qh4304stgwpqxp6gwsucma30fewp7z5cs4yuvdf - */ -import { AirDAOBlockchainTypes } from '../../AirDAOBlockchainTypes'; -import AirDAOCryptoLog from '../../../common/AirDAOCryptoLog'; -import AirDAOAxios from '../../../common/AirDAOAxios'; -import BtcCashUtils from '../ext/BtcCashUtils'; -import DogeUnspentsProvider from '../../doge/providers/DogeUnspentsProvider'; - -export default class BchUnspentsProvider - extends DogeUnspentsProvider - implements AirDAOBlockchainTypes.UnspentsProvider -{ - _apiPath = 'https://rest.bitcoin.com/v2/address/utxo/bitcoincash:'; - - async getUnspents( - address: string - ): Promise { - try { - const trezor = await super.getUnspents('bitcoincash:' + address); - if (trezor && trezor.length > 0) { - return trezor; - } - } catch (e) { - // do nothing - } - // TODO check if log is necessary @ts-ignore - // AirDAOCryptoLog.log( - // this._settings.currencyCode + ' BchUnspentsProvider.getUnspents started', - // address - // ); - address = BtcCashUtils.fromLegacyAddress(address); - const link = this._apiPath + address; - const res = (await AirDAOAxios.getWithoutBraking(link)) as any; - if (!res || !res.data || typeof res.data === 'undefined') { - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' BchUnspentsProvider.getUnspents nothing loaded for address ' + - address + - ' link ' + - link - ); - throw new Error('SERVER_RESPONSE_NOT_CONNECTED'); - } - if (!res.data || typeof res.data.utxos === 'undefined' || !res.data.utxos) { - return []; - } - const sortedUnspents = []; - /** - * https://rest.bitcoin.com/v2/address/utxo/bitcoincash:qz6qh4304stgwpqxp6gwsucma30fewp7z5cs4yuvdf - * @param {*} res.data.utxos[] - * @param {string} res.data.utxos[].txid 5be83026d82b56e8df7fa309e0b50132cb5cac228f83103532b20e0c991a3f9b - * @param {string} res.data.utxos[].vout 1 - * @param {string} res.data.utxos[].amount 0.04373313 - * @param {string} res.data.utxos[].satoshis 4373313 - * @param {string} res.data.utxos[].height 615754 - * @param {string} res.data.utxos[].confirmations - */ - let unspent; - for (unspent of res.data.utxos) { - sortedUnspents.push({ - txid: unspent.txid, - vout: unspent.vout, - value: unspent.satoshis.toString(), - height: unspent.height, - confirmations: unspent.confirmations, - isRequired: false - }); - } - - return sortedUnspents; - } -} diff --git a/crypto/blockchains/bch/tx/BchTxBuilder.ts b/crypto/blockchains/bch/tx/BchTxBuilder.ts deleted file mode 100644 index 1bd9f5fa0..000000000 --- a/crypto/blockchains/bch/tx/BchTxBuilder.ts +++ /dev/null @@ -1,53 +0,0 @@ -/** - * @version 0.20 - */ -import { AirDAOBlockchainTypes } from '../../AirDAOBlockchainTypes'; -import DogeTxBuilder from '../../doge/tx/DogeTxBuilder'; -import BtcCashUtils from '../ext/BtcCashUtils'; -import bitcoin, { TransactionBuilder } from 'bitcoinjs-lib'; - -export default class BchTxBuilder - extends DogeTxBuilder - implements AirDAOBlockchainTypes.TxBuilder -{ - _getRawTxValidateKeyPair( - privateData: AirDAOBlockchainTypes.TransferPrivateData, - data: AirDAOBlockchainTypes.TransferData - ): void { - this.keyPair = false; - try { - this.keyPair = bitcoin.ECPair.fromWIF( - privateData.privateKey, - this._bitcoinNetwork - ); - const address = bitcoin.payments.p2pkh({ - pubkey: this.keyPair.publicKey, - network: this._bitcoinNetwork - }).address; - const legacyAddress = BtcCashUtils.toLegacyAddress(data.addressFrom); - if (address !== data.addressFrom && address !== legacyAddress) { - // noinspection ExceptionCaughtLocallyJS - throw new Error( - 'not valid signing address ' + - data.addressFrom + - ' != ' + - address + - ' != ' + - legacyAddress - ); - } - } catch (e) { - // @ts-ignore - e.message += ' in privateKey BCH signature check '; - throw e; - } - } - - _getRawTxAddOutput( - txb: TransactionBuilder, - output: AirDAOBlockchainTypes.OutputTx - ): void { - const to = BtcCashUtils.toLegacyAddress(output.to); - txb.addOutput(to, Number(output.amount)); - } -} diff --git a/crypto/blockchains/bnb/BnbAddressProcessor.ts b/crypto/blockchains/bnb/BnbAddressProcessor.ts deleted file mode 100644 index 531d1a7d0..000000000 --- a/crypto/blockchains/bnb/BnbAddressProcessor.ts +++ /dev/null @@ -1,61 +0,0 @@ -/* eslint-disable @typescript-eslint/no-var-requires */ -/** - * @version 0.20 - * https://github.com/binance-chain/javascript-sdk/blob/aa1947b696f984aa931f5f029e4a439c45d5e853/src/client/index.ts#L208 - */ -const elliptic = require('elliptic'); -const ec = new elliptic.ec('secp256k1'); -const createHash = require('create-hash'); -const bech = require('bech32'); -const Web3 = require('web3'); - -function ab2hexstring(arr) { - let result = ''; - for (let i = 0; i < arr.length; i++) { - let str = arr[i].toString(16); - str = str.length === 0 ? '00' : str.length === 1 ? '0' + str : str; - result += str; - } - return result; -} - -export default class BnbAddressProcessor { - _web3Link: string; - _web3: typeof Web3; - constructor() { - this._web3Link = 'https://bsc-dataseed1.binance.org:443'; - this._web3 = new Web3(new Web3.providers.HttpProvider(this._web3Link)); - } - - async setBasicRoot(root) {} - - /** - * @param {string|Buffer} privateKey - * @param {*} data - * @returns {Promise<{privateKey: string, address: string}>} - */ - async getAddress( - privateKey: string | Buffer, - data = {}, - superPrivateData = {} - ): Promise<{ privateKey: string; address: string }> { - const keypair = ec.keyFromPrivate(privateKey.toString('hex'), 'hex'); - const pubPoint = keypair.getPublic(); - const compressed = pubPoint.encodeCompressed(); - const hexed = ab2hexstring(compressed); - const one = createHash('sha256').update(hexed, 'hex').digest(); - const hash = createHash('ripemd160').update(one).digest(); - const words = bech.toWords(hash); - const address = bech.encode('bnb', words); - - const account = this._web3.eth.accounts.privateKeyToAccount( - '0x' + privateKey.toString('hex') - ); - - return { - address, - privateKey: privateKey.toString('hex'), - addedData: { ethAddress: account.address } - }; - } -} diff --git a/crypto/blockchains/bnb/BnbScannerProcessor.ts b/crypto/blockchains/bnb/BnbScannerProcessor.ts deleted file mode 100644 index a5d57853c..000000000 --- a/crypto/blockchains/bnb/BnbScannerProcessor.ts +++ /dev/null @@ -1,199 +0,0 @@ -/** - * @version 0.20 - * https://docs.binance.org/api-reference/dex-api/paths.html#apiv1transactions - */ -import AirDAOAxios from '../../common/AirDAOAxios'; -import AirDAOCryptoLog from '../../common/AirDAOCryptoLog'; -import BlocksoftUtils from '../../common/AirDAOUtils'; -import BlocksoftExternalSettings from '../../common/AirDAOExternalSettings'; - -export default class BnbScannerProcessor { - /** - * https://dex.binance.org/api/v1/account/bnb146lec0enyzz2x5kpna8kwelky8kumfhj05aspz - * @param {string} address - * @return {Promise<{balance, unconfirmed, provider}>} - */ - async getBalanceBlockchain( - address: string - ): Promise<{ balance: string; unconfirmed: any; provider: any }> { - const apiServer = await BlocksoftExternalSettings.getStatic('BNB_SERVER'); - const link = `${apiServer}/api/v1/account/${address}`; - let balance = 0; - let frozen = 0; - const res = await AirDAOAxios.getWithoutBraking(link); - // "balances":[{"free":"0.00100000","frozen":"0.00000000","locked":"0.00000000","symbol":"BNB"}] - if ( - res && - typeof res.data !== 'undefined' && - res.data && - typeof res.data.balances !== 'undefined' - ) { - let row; - for (row of res.data.balances) { - if (row.symbol === 'BNB') { - balance = row.free; - frozen = row.frozen; - break; - } - } - } else { - await AirDAOCryptoLog.log( - 'BnbScannerProcessor.getBalanceBlockchain ' + - address + - ' no actual balance ' + - link, - res - ); - return false; - } - - return { balance, unconfirmed: 0, frozen, provider: 'dex.binance' }; - } - - /** - * https://dex.binance.org/api/v1/transactions/?address=bnb146lec0enyzz2x5kpna8kwelky8kumfhj05aspz - * https://dex.binance.org/api/v1/transactions/?address=bnb146lec0enyzz2x5kpna8kwelky8kumfhj05aspz&startTime=1609452000000&limit=100 - * https://docs.binance.org/api-reference/dex-api/paths.html#apiv1transactions - * https://github.com/trustwallet/blockatlas/blob/b4f6dc360bed412ff555aa981d83e4421380f104/platform/binance/client.go#L43 - * @param {string} scanData.account.address - * @return {Promise<[UnifiedTransaction]>} - */ - async getTransactionsBlockchain(scanData) { - const address = scanData.account.address.trim(); - AirDAOCryptoLog.log('BnbScannerProcessor.getTransactions started', address); - - const apiServer = await BlocksoftExternalSettings.getStatic('BNB_SERVER'); - - let linkTxs = `${apiServer}/api/v1/transactions/?address=${address}&txAsset=BNB`; // 2021-01-01 - if ( - scanData.account.balanceScanTime && - scanData.account.balanceScanTime * 1 > 0 - ) { - linkTxs += - '&startTime=' + (scanData.account.balanceScanTime - 86400) * 1000; // 1 day - } - - const res = await AirDAOAxios.getWithoutBraking(linkTxs); - if (!res || typeof res.data === 'undefined' || !res.data) { - return false; - } - - const transactions = await this._unifyTransactions(address, res.data.tx); - AirDAOCryptoLog.log( - 'BnbScannerProcessor.getTransactions finished', - address - ); - return transactions; - } - - async _unifyTransactions(address, result) { - const transactions = []; - let tx; - for (tx of result) { - const transaction = await this._unifyTransaction(address, tx); - if (transaction) { - transactions.push(transaction); - } - } - return transactions; - } - - /** - * @param {string} address - * @param {Object} transaction - * @param {string} transaction.blockHeight 144626977 - * @param {string} transaction.code 0 - * @param {string} transaction.confirmBlocks - * @param {string} transaction.data - * @param {string} transaction.fromAddr bnb1jxfh2g85q3v0tdq56fnevx6xcxtcnhtsmcu64m - * @param {string} transaction.memo - * @param {string} transaction.orderId - * @param {string} transaction.proposalId - * @param {string} transaction.sequence 1950767 - * @param {string} transaction.source 0 - * @param {string} transaction.timeStamp 2021-02-15T21:40:00.232Z - * @param {string} transaction.toAddr bnb146lec0enyzz2x5kpna8kwelky8kumfhj05aspz - * @param {string} transaction.txAge 428893 - * @param {string} transaction.txAsset "BNB" - * @param {string} transaction.txFee "0.00037500" - * @param {string} transaction.txHash "D160D16A01998B023EC3ABBCD1D1064F23AC1D17715ECAE1E895DC0AA9D12B5A" - * @param {string} transaction.txType: "TRANSFER" - * @param {string} transaction.value: "0.00100000" - * @return {UnifiedTransaction} - * @private - **/ - async _unifyTransaction(address, transaction) { - try { - let tx; - if (transaction.txType === 'TRANSFER') { - tx = { - transactionHash: transaction.txHash, - blockHash: transaction.blockHeight, - blockNumber: transaction.blockHeight, - blockTime: transaction.timeStamp, - blockConfirmations: transaction.txAge, - transactionDirection: '?', - addressFrom: - transaction.fromAddr === address ? '' : transaction.fromAddr, - addressTo: transaction.toAddr === address ? '' : transaction.toAddr, - addressAmount: transaction.value, - transactionStatus: - transaction.code === 0 - ? 'success' - : transaction.txAge === 0 - ? 'new' - : 'fail', - transactionFee: transaction.txFee - }; - } else if ( - transaction.txType === 'CROSS_TRANSFER_OUT' && - typeof transaction.data !== 'undefined' - ) { - const tmp = JSON.parse(transaction.data); - if ( - typeof tmp.amount.denom === 'undefined' || - tmp.amount.denom !== 'BNB' - ) - return false; - tx = { - transactionHash: transaction.txHash, - blockHash: transaction.blockHeight, - blockNumber: transaction.blockHeight, - blockTime: transaction.timeStamp, - blockConfirmations: transaction.txAge, - transactionDirection: '?', - addressFrom: tmp.from === address ? '' : tmp.from, - addressTo: tmp.to === address ? '' : tmp.to, - addressAmount: BlocksoftUtils.toUnified(tmp.amount.amount, 8), - transactionStatus: - transaction.code === 0 - ? 'success' - : transaction.txAge === 0 - ? 'new' - : 'fail', - transactionFee: transaction.txFee - }; - } else { - return false; - } - - if (tx.addressTo === '' || !tx.addressTo) { - if (tx.addressFrom === '') { - tx.transactionDirection = 'self'; - } else { - tx.transactionDirection = 'income'; - } - } else { - tx.transactionDirection = 'outcome'; - } - - if (typeof transaction.memo !== 'undefined' && transaction.memo !== '') { - tx.transactionJson = { memo: transaction.memo }; - } - - return tx; - } catch (e) { - throw e; - } - } -} diff --git a/crypto/blockchains/bnb/BnbTransferProcessor.ts b/crypto/blockchains/bnb/BnbTransferProcessor.ts deleted file mode 100644 index c61462e1e..000000000 --- a/crypto/blockchains/bnb/BnbTransferProcessor.ts +++ /dev/null @@ -1,191 +0,0 @@ -/** - * @version 0.20 - * https://docs.binance.org/smart-chain/developer/create-wallet.html - * https://docs.binance.org/guides/concepts/encoding/amino-example.html#transfer - */ -import AirDAOCryptoLog from '../../common/AirDAOCryptoLog'; - -import { AirDAOBlockchainTypes } from '../AirDAOBlockchainTypes'; -import { BnbTxSendProvider } from './basic/BnbTxSendProvider'; -import BlocksoftUtils from '../../common/AirDAOUtils'; -import BnbNetworkPrices from './basic/BnbNetworkPrices'; - -export default class BnbTransferProcessor - implements AirDAOBlockchainTypes.TransferProcessor -{ - private _settings: { network: string; currencyCode: string }; - private _provider: BnbTxSendProvider; - - constructor(settings: { network: string; currencyCode: string }) { - this._settings = settings; - this._provider = new BnbTxSendProvider(); - } - - needPrivateForFee(): boolean { - return false; - } - - checkSendAllModal(data: { currencyCode: any }): boolean { - return false; - } - - async getFeeRate( - data: AirDAOBlockchainTypes.TransferData, - privateData: AirDAOBlockchainTypes.TransferPrivateData, - additionalData: {} = {} - ): Promise { - const fees = await BnbNetworkPrices.getFees(); - const feeForTx = BlocksoftUtils.toUnified(fees.send.fee, 8); - const result: AirDAOBlockchainTypes.FeeRateResult = { - selectedFeeIndex: 0, - shouldShowFees: false, - fees: [ - { - langMsg: 'xrp_speed_one', - feeForTx, - amountForTx: data.amount - } - ] - } as AirDAOBlockchainTypes.FeeRateResult; - return result; - } - - async getTransferAllBalance( - data: AirDAOBlockchainTypes.TransferData, - privateData: AirDAOBlockchainTypes.TransferPrivateData, - additionalData: AirDAOBlockchainTypes.TransferAdditionalData = {} - ): Promise { - const balance = data.amount; - // @ts-ignore - await AirDAOCryptoLog.log( - this._settings.currencyCode + - ' BnbTransferProcessor.getTransferAllBalance ', - data.addressFrom + ' => ' + balance - ); - - const result = await this.getFeeRate(data, privateData, additionalData); - // @ts-ignore - if (!result || result.selectedFeeIndex < 0) { - return { - selectedTransferAllBalance: '0', - selectedFeeIndex: -2, - fees: [], - countedForBasicBalance: balance - }; - } - // @ts-ignore - const newAmount = BlocksoftUtils.diff( - result.fees[result.selectedFeeIndex].amountForTx, - result.fees[result.selectedFeeIndex].feeForTx - ).toString(); - if (newAmount * 1 < 0) { - throw new Error('SERVER_RESPONSE_NOTHING_TO_TRANSFER'); - } - result.fees[result.selectedFeeIndex].amountForTx = newAmount; - const tmp = { - ...result, - shouldShowFees: false, - selectedTransferAllBalance: - result.fees[result.selectedFeeIndex].amountForTx - }; - // console.log('tmp', JSON.stringify(tmp)) - return tmp; - } - - async sendTx( - data: AirDAOBlockchainTypes.TransferData, - privateData: AirDAOBlockchainTypes.TransferPrivateData, - uiData: AirDAOBlockchainTypes.TransferUiData - ): Promise { - if (typeof privateData.privateKey === 'undefined') { - throw new Error('BNB transaction required privateKey'); - } - if (typeof data.addressTo === 'undefined') { - throw new Error('BNB transaction required addressTo'); - } - - await AirDAOCryptoLog.log( - this._settings.currencyCode + ' BnbTransferProcessor.sendTx start' - ); - - let transaction; - try { - transaction = await this._provider.getPrepared(data, privateData, uiData); - } catch (e) { - throw new Error(e.message + ' in BNB getPrepared'); - } - // @ts-ignore - await AirDAOCryptoLog.log( - this._settings.currencyCode + ' BnbTransferProcessor.sendTx tx', - transaction - ); - - let raw; - try { - raw = this._provider.serializeTx(transaction); - } catch (e) { - throw new Error(e.message + ' in BNB serializeTx'); - } - await AirDAOCryptoLog.log( - this._settings.currencyCode + ' BnbTransferProcessor.sendTx raw', - raw - ); - if ( - typeof uiData !== 'undefined' && - typeof uiData.selectedFee !== 'undefined' && - typeof uiData.selectedFee.rawOnly !== 'undefined' && - uiData.selectedFee.rawOnly - ) { - return { rawOnly: uiData.selectedFee.rawOnly, raw }; - } - - let result; - try { - result = await this._provider.sendRaw(raw); - } catch (e) { - if (e.message.indexOf('SERVER_RESPONSE_') === -1) { - throw new Error(e.message + ' in BNB sendRaw1'); - } else { - throw e; - } - } - await AirDAOCryptoLog.log( - this._settings.currencyCode + ' BnbTransferProcessor.sendTx result', - result - ); - - try { - if (typeof result.message !== 'undefined') { - if ( - result.message.indexOf('insufficient fund') !== -1 || - result.message.indexOf('BNB <') !== -1 - ) { - throw new Error('SERVER_RESPONSE_NOTHING_TO_TRANSFER'); - } else { - throw new Error(result.message); - } - } - if ( - typeof result[0] === 'undefined' || - typeof result[0].hash === 'undefined' || - typeof result[0].ok === 'undefined' || - !result[0].ok || - !result[0].hash - ) { - await AirDAOCryptoLog.log( - this._settings.currencyCode + ' BnbTransferProcessor.sendTx result', - result - ); - throw new Error('SERVER_RESPONSE_NO_RESPONSE'); - } - - return { transactionHash: result[0].hash }; - } catch (e) { - if (e.message.indexOf('SERVER_RESPONSE_') === -1) { - throw new Error(e.message + ' in BNB sendRaw1 parse result'); - } else { - throw e; - } - } - } -} diff --git a/crypto/blockchains/bnb/basic/BnbNetworkPrices.js b/crypto/blockchains/bnb/basic/BnbNetworkPrices.js deleted file mode 100644 index f9b318df3..000000000 --- a/crypto/blockchains/bnb/basic/BnbNetworkPrices.js +++ /dev/null @@ -1,65 +0,0 @@ -/** - * @version 0.20 - * - * https://beptools.org/fees - * https://dex.binance.org/api/v1/fees - * https://docs.binance.org/api-reference/dex-api/paths.html#apiv1fees - */ -import AirDAOCryptoLog from '../../../common/AirDAOCryptoLog'; -import AirDAOAxios from '../../../common/AirDAOAxios'; -import BlocksoftExternalSettings from '../../../common/AirDAOExternalSettings'; - -const CACHE_VALID_TIME = 60000; // 1 minute -let CACHE_FEES = { - send: { - fee: 37500 - } -}; -let CACHE_FEES_TIME = 0; - -class BnbNetworkPrices { - async getFees() { - const now = new Date().getTime(); - if (CACHE_FEES && now - CACHE_FEES_TIME < CACHE_VALID_TIME) { - return CACHE_FEES; - } - - AirDAOCryptoLog.log('BnbNetworkPricesProvider.getFees no cache load'); - - const apiServer = await BlocksoftExternalSettings.getStatic('BNB_SERVER'); - const link = `${apiServer}/api/v1/fees`; - - try { - const tmp = await AirDAOAxios.getWithoutBraking(link, 2); - if (tmp.data) { - // {"fee": 1000000, "fee_for": 1, "msg_type": "transferOwnership"}] - const result = {}; - for (const row of tmp.data) { - if (typeof row.fixed_fee_params !== 'undefined') { - result[row.fixed_fee_params.msg_type] = { - fee: row.fixed_fee_params.fee, - for: row.fixed_fee_params.fee_for - }; - } else { - result[row.msg_type] = { fee: row.fee, for: row.fee_for }; - } - } - if (typeof result['send'] !== 'undefined') { - CACHE_FEES = result; - CACHE_FEES_TIME = now; - } - } - } catch (e) { - AirDAOCryptoLog.log( - 'BnbNetworkPricesProvider.getOnlyFees loaded prev fee as error' + - e.message - ); - // do nothing - } - - return CACHE_FEES; - } -} - -const singleton = new BnbNetworkPrices(); -export default singleton; diff --git a/crypto/blockchains/bnb/basic/BnbTxSendProvider.ts b/crypto/blockchains/bnb/basic/BnbTxSendProvider.ts deleted file mode 100644 index c35322cc8..000000000 --- a/crypto/blockchains/bnb/basic/BnbTxSendProvider.ts +++ /dev/null @@ -1,322 +0,0 @@ -/* eslint-disable camelcase */ -/* eslint-disable @typescript-eslint/no-var-requires */ -import AirDAOAxios from '../../../common/AirDAOAxios'; -import { AirDAOBlockchainTypes } from '../../AirDAOBlockchainTypes'; -import AirDAOCryptoLog from '../../../common/AirDAOCryptoLog'; -import BlocksoftUtils from '../../../common/AirDAOUtils'; -import BlocksoftExternalSettings from '../../../common/AirDAOExternalSettings'; - -const elliptic = require('elliptic'); -const ec = new elliptic.ec('secp256k1'); -const createHash = require('create-hash'); -const bech = require('bech32'); -const UVarInt = require('../utils/UVarInt').UVarInt; -const Encode = require('../utils/Encode'); -const _tinySecp256k = require('tiny-secp256k1'); - -function decodeAddress(value: any) { - try { - const decodeAddress = bech.decode(value); - return Buffer.from(bech.fromWords(decodeAddress.words)); - } catch (err: unknown) { - const e = err as any; - throw new Error(e.message + ' while decodeAddress ' + value); - } -} - -function convertObjectToSignBytes(obj: any) { - try { - return Buffer.from(JSON.stringify(Encode.sortObject(obj))); - } catch (e) { - throw new Error(e.message + ' in convertObjectToSignBytes'); - } -} - -function encodeBinaryByteArray(bytes: any) { - try { - const lenPrefix = bytes.length; - return Buffer.concat([UVarInt.encode(lenPrefix), bytes]); - } catch (e) { - throw new Error(e.message + ' in encodeBinaryByteArray'); - } -} - -function serializePubKey(unencodedPubKey: any) { - try { - let format = 0x2; - const y = unencodedPubKey.getY(); - const x = unencodedPubKey.getX(); - - if (y && y.isOdd()) { - format |= 0x1; - } - - let pubBz = Buffer.concat([ - UVarInt.encode(format), - x.toArrayLike(Buffer, 'be', 32) - ]); // prefixed with length - - pubBz = encodeBinaryByteArray(pubBz); // add the amino prefix - - pubBz = Buffer.concat([Buffer.from('EB5AE987', 'hex'), pubBz]); - return pubBz; - } catch (e) { - throw new Error(e.message + ' in serializePubKey'); - } -} - -function marshalBinary(obj: any) { - try { - return Encode.encodeBinary(obj, -1, true).toString('hex'); - } catch (e) { - throw new Error(e.message + ' in marshalBinary'); - } -} - -export class BnbTxSendProvider { - async getPrepared( - data: AirDAOBlockchainTypes.TransferData, - privateData: AirDAOBlockchainTypes.TransferPrivateData, - uiData: AirDAOBlockchainTypes.TransferUiData, - type = 'usual' - ) { - const apiServer = await BlocksoftExternalSettings.getStatic('BNB_SERVER'); - const res = await AirDAOAxios.getWithoutBraking( - apiServer + '/api/v1/account/' + data.addressFrom - ); - if (!res.data) { - throw new Error('no data'); - } - const account = res.data; - - const unified = BlocksoftUtils.fromUnified(data.amount, 8) * 1; - let msg; - let txMsg; - - let decodedFrom; - let decodedTo; - try { - decodedFrom = decodeAddress(data.addressFrom); - } catch (e) { - throw new Error( - e.message + ' in BNB decodeAddress ' + JSON.stringify(data.addressFrom) - ); - } - try { - if (data.addressTo.indexOf('0x') === -1) { - decodedTo = decodeAddress(data.addressTo); - } else { - decodedTo = data.addressTo; - } - } catch (e) { - throw new Error( - e.message + ' in BNB decodeAddress ' + JSON.stringify(data.addressTo) - ); - } - - if ( - typeof data.blockchainData !== 'undefined' && - typeof data.blockchainData.action !== 'undefined' && - data.blockchainData.action === 'BnbToSmart' - ) { - let addressTo = data.addressTo; - if (addressTo.substr(0, 2) === '0x') { - addressTo = addressTo.substr(2); - } - msg = { - from: data.addressFrom, - to: data.addressTo, - amount: { denom: 'BNB', amount: unified }, - expire_time: data.blockchainData.expire_time - }; - txMsg = { - from: decodedFrom, - to: Buffer.from(addressTo, 'hex'), - amount: { denom: 'BNB', amount: unified }, - expire_time: data.blockchainData.expire_time, - aminoPrefix: '800819C0' - }; - } else { - msg = { - inputs: [ - { - address: data.addressFrom, - coins: [ - { - amount: unified, - denom: 'BNB' - } - ] - } - ], - outputs: [ - { - address: data.addressTo, - coins: [ - { - amount: unified, - denom: 'BNB' - } - ] - } - ] - }; - txMsg = { - inputs: [ - { - address: decodedFrom, - coins: [ - { - denom: 'BNB', - amount: unified - } - ] - } - ], - outputs: [ - { - address: decodedTo, - coins: [ - { - denom: 'BNB', - amount: unified - } - ] - } - ], - aminoPrefix: '2A2C87FA' - }; - } - const memo = - typeof data.memo === 'undefined' || !data.memo ? '' : data.memo; - const signMsg = { - account_number: account.account_number + '', - chain_id: 'Binance-Chain-Tigris', - data: null, - memo, - msgs: [msg], - sequence: account.sequence + '', - source: '0' - }; - let buff; - try { - buff = convertObjectToSignBytes(signMsg); - } catch (e) { - throw new Error( - e.message + - ' in BNB convertObjectToSignBytes ' + - JSON.stringify(signMsg) - ); - } - const signBytesHex = buff.toString('hex'); - let msgHash; - try { - msgHash = createHash('sha256').update(signBytesHex, 'hex').digest(); - } catch (e) { - throw new Error(e.message + ' in BNB createHash'); - } - let keypair; - try { - keypair = ec.keyFromPrivate(privateData.privateKey, 'hex'); - } catch (e) { - throw new Error(e.message + ' in BNB ec.keyFromPrivate'); - } - let signature; - try { - signature = _tinySecp256k.sign( - msgHash, - Buffer.from(privateData.privateKey, 'hex') - ); - } catch (e) { - throw new Error(e.message + ' in BNB _tinySecp256k.sign'); - } - const signatureHex = signature.toString('hex'); - let pubKey; - try { - pubKey = keypair.getPublic(); - } catch (e) { - throw new Error(e.message + ' in BNB keypair.getPublic()'); - } - let pubSerialize; - try { - pubSerialize = serializePubKey(pubKey); - } catch (err: unknown) { - const e = err as any; - throw new Error(e.message + ' in BNB serializePubKey'); - } - - const signatures = [ - { - pub_key: pubSerialize, - signature: Buffer.from(signatureHex, 'hex'), - account_number: account.account_number, - sequence: account.sequence - } - ]; - const transaction = { - sequence: account.sequence + '', - accountNumber: account.account_number + '', - chainId: 'Binance-Chain-Tigris', - msg: txMsg, - baseMsg: undefined, - memo: memo, - source: 0, - signatures - }; - return transaction; - } - - serializeTx(tx: any) { - if (!tx.signatures) { - throw new Error('need signature'); - } - - const msg = tx.msg || (tx.baseMsg && tx.baseMsg.getMsg()); - const stdTx = { - msg: [msg], - signatures: tx.signatures, - memo: tx.memo, - source: tx.source, - // sdk value is 0, web wallet value is 1 - data: '', - aminoPrefix: 'F0625DEE' - }; - const bytes = marshalBinary(stdTx); - return bytes.toString('hex'); - } - - async sendRaw(raw: string) { - let result = false; - try { - // console.log(`curl -X POST -F "tx=${raw}" "https://dex.binance.org/api/v1/broadcast"`) - const apiServer = await BlocksoftExternalSettings.getStatic('BNB_SERVER'); - const response = await fetch(apiServer + '/api/v1/broadcast?sync=true', { - method: 'POST', - credentials: 'same-origin', - mode: 'same-origin', - headers: { - 'Content-Type': 'text/plain' - }, - body: raw - }); - result = await response.json(); - - if (typeof result.status !== 'undefined') { - if ( - result.status === 406 || - result.status === 400 || - result.status === 504 - ) { - throw new Error(result.title); - } - } - } catch (e) { - await AirDAOCryptoLog.log( - 'BnbTransferProcessor.sendTx error ' + e.message - ); - throw e; - } - await AirDAOCryptoLog.log('BnbTransferProcessor.sendTx result ', result); - return result; - } -} diff --git a/crypto/blockchains/bnb/utils/Encode.js b/crypto/blockchains/bnb/utils/Encode.js deleted file mode 100644 index 62f520256..000000000 --- a/crypto/blockchains/bnb/utils/Encode.js +++ /dev/null @@ -1,261 +0,0 @@ -/* eslint-disable @typescript-eslint/no-var-requires */ -const protocolBuffersEncodings = require('protocol-buffers-encodings'); - -const IsJs = require('./IsJs.js'); -const UVarInt = require('./UVarInt.js').UVarInt; -const EncoderHelper = require('./EncoderHelper.js'); - -const sortObject = function sortObject(obj) { - if (obj === null) return null; - if (Array.isArray(obj)) return obj.map(sortObject); - if (typeof obj !== 'object') return obj; - - var sortedKeys = Object.keys(obj).sort(); - var result = {}; - sortedKeys.forEach((key) => { - // @ts-ignore - result[key] = sortObject(obj[key]); - }); - return result; -}; -exports.sortObject = sortObject; - -/** - * encode number - * @category amino - * @param num - */ - -var encodeNumber = function encodeNumber(num) { - return UVarInt.encode(num); -}; -/** - * encode bool - * @category amino - * @param b - */ - -exports.encodeNumber = encodeNumber; - -var encodeBool = function encodeBool(b) { - return b ? UVarInt.encode(1) : UVarInt.encode(0); -}; -/** - * encode string - * @category amino - * @param str - */ - -exports.encodeBool = encodeBool; - -var encodeString = function encodeString(str) { - try { - var buf = Buffer.alloc(protocolBuffersEncodings.string.encodingLength(str)); - return protocolBuffersEncodings.string.encode(str, buf, 0); - } catch (e) { - throw new Error(e.message + ' in protocolBuffersEncodings.string.encode'); - } -}; -/** - * encode time - * @category amino - * @param value - */ - -exports.encodeString = encodeString; - -var encodeTime = function encodeTime(value) { - var millis = new Date(value).getTime(); - var seconds = Math.floor(millis / 1000); - var nanos = Number(seconds.toString().padEnd(9, '0')); - var buffer = Buffer.alloc(14); // buffer[0] = (1 << 3) | 1 // field 1, typ3 1 - - buffer.writeInt32LE((1 << 3) | 1, 0); - buffer.writeUInt32LE(seconds, 1); // buffer[9] = (2 << 3) | 5 // field 2, typ3 5 - - buffer.writeInt32LE((2 << 3) | 5, 9); - buffer.writeUInt32LE(nanos, 10); - return buffer; -}; -/** - * @category amino - * @param obj -- {object} - * @return bytes {Buffer} - */ - -exports.encodeTime = encodeTime; - -var convertObjectToSignBytes = function convertObjectToSignBytes(obj) { - return Buffer.from(JSON.stringify(sortObject(obj))); -}; -/** - * js amino MarshalBinary - * @category amino - * @param {Object} obj - * */ - -exports.convertObjectToSignBytes = convertObjectToSignBytes; - -var marshalBinary = function marshalBinary(obj) { - if (!IsJs.object(obj)) throw new TypeError('data must be an object'); - return encodeBinary(obj, -1, true).toString('hex'); -}; -/** - * js amino MarshalBinaryBare - * @category amino - * @param {Object} obj - * */ - -exports.marshalBinary = marshalBinary; - -var marshalBinaryBare = function marshalBinaryBare(obj) { - if (!IsJs.object(obj)) throw new TypeError('data must be an object'); - return encodeBinary(obj).toString('hex'); -}; -/** - * This is the main entrypoint for encoding all types in binary form. - * @category amino - * @param {*} js data type (not null, not undefined) - * @param {Number} field index of object - * @param {Boolean} isByteLenPrefix - * @return {Buffer} binary of object. - */ - -exports.marshalBinaryBare = marshalBinaryBare; - -var encodeBinary = function encodeBinary(val, fieldNum, isByteLenPrefix) { - if (val === null || val === undefined) - throw new TypeError('unsupported type'); - - if (Buffer.isBuffer(val)) { - if (isByteLenPrefix) { - return Buffer.concat([UVarInt.encode(val.length), val]); - } - - return val; - } - - if (IsJs.array(val)) { - return encodeArrayBinary(fieldNum, val, isByteLenPrefix); - } - - if (IsJs.number(val)) { - return encodeNumber(val); - } - - if (IsJs['boolean'](val)) { - return encodeBool(val); - } - - if (IsJs.string(val)) { - return encodeString(val); - } - - if (IsJs.object(val)) { - return encodeObjectBinary(val, isByteLenPrefix); - } - - return; -}; -/** - * prefixed with bytes length - * @category amino - * @param {Buffer} bytes - * @return {Buffer} with bytes length prefixed - */ - -exports.encodeBinary = encodeBinary; - -var encodeBinaryByteArray = function encodeBinaryByteArray(bytes) { - var lenPrefix = bytes.length; - return Buffer.concat([UVarInt.encode(lenPrefix), bytes]); -}; -/** - * @category amino - * @param {Object} obj - * @return {Buffer} with bytes length prefixed - */ - -exports.encodeBinaryByteArray = encodeBinaryByteArray; - -var encodeObjectBinary = function encodeObjectBinary(obj, isByteLenPrefix) { - var bufferArr = []; - Object.keys(obj).forEach((key, index) => { - if (key === 'aminoPrefix' || key === 'version') return; - if (isDefaultValue(obj[key])) return; - - if (IsJs.array(obj[key]) && obj[key].length > 0) { - bufferArr.push(encodeArrayBinary(index, obj[key])); - } else { - bufferArr.push(encodeTypeAndField(index, obj[key])); - bufferArr.push(encodeBinary(obj[key], index, true)); - } - }); - var bytes = Buffer.concat(bufferArr); // add prefix - - if (obj.aminoPrefix) { - var prefix = Buffer.from(obj.aminoPrefix, 'hex'); - bytes = Buffer.concat([prefix, bytes]); - } // Write byte-length prefixed. - - if (isByteLenPrefix) { - var lenBytes = UVarInt.encode(bytes.length); - - bytes = Buffer.concat([lenBytes, bytes]); - } - return bytes; -}; -/** - * @category amino - * @param {Number} fieldNum object field index - * @param {Array} arr - * @param {Boolean} isByteLenPrefix - * @return {Buffer} bytes of array - */ - -exports.encodeObjectBinary = encodeObjectBinary; - -var encodeArrayBinary = function encodeArrayBinary( - fieldNum, - arr, - isByteLenPrefix -) { - var result = []; - arr.forEach((item) => { - result.push(encodeTypeAndField(fieldNum, item)); - - if (isDefaultValue(item)) { - result.push(Buffer.from('00', 'hex')); - return; - } - - result.push(encodeBinary(item, fieldNum, true)); - }); //encode length - - if (isByteLenPrefix) { - var length = result.reduce((prev, item) => { - return prev + item.length; - }, 0); - result.unshift(UVarInt.encode(length)); - } - - return Buffer.concat(result); -}; // Write field key. - -exports.encodeArrayBinary = encodeArrayBinary; - -var encodeTypeAndField = function encodeTypeAndField(index, field) { - index = Number(index); - var value = ((index + 1) << 3) | EncoderHelper._default(field); - return UVarInt.encode(value); -}; - -var isDefaultValue = function isDefaultValue(obj) { - if (obj === null) return false; - return ( - (IsJs.number(obj) && obj === 0) || - (IsJs.string(obj) && obj === '') || - (IsJs.array(obj) && obj.length === 0) || - (IsJs['boolean'](obj) && !obj) - ); -}; diff --git a/crypto/blockchains/bnb/utils/EncoderHelper.ts b/crypto/blockchains/bnb/utils/EncoderHelper.ts deleted file mode 100644 index f0da9adbe..000000000 --- a/crypto/blockchains/bnb/utils/EncoderHelper.ts +++ /dev/null @@ -1,46 +0,0 @@ -/* eslint-disable @typescript-eslint/no-var-requires */ -'use strict'; - -Object.defineProperty(exports, '__esModule', { - value: true -}); -exports.size = exports['default'] = void 0; - -const IsJs = require('./IsJs.js'); - -// typeToTyp3 -//amino type convert -const _default = function _default(type) { - if (IsJs['boolean'](type)) { - return 0; - } - - if (IsJs.number(type)) { - if (IsJs.integer(type)) { - return 0; - } else { - return 1; - } - } - - if (IsJs.string(type) || IsJs.array(type) || IsJs.object(type)) { - return 2; - } - - throw new Error('Invalid type "'.concat(type, '"')); // Is this what's expected? -}; - -exports._default = _default; - -// @ts-ignore TODO -const size = function size(items, iter, acc) { - if (acc === undefined) acc = 0; - - for (let i = 0; i < items.length; ++i) { - acc += iter(items[i], i, acc); - } - - return acc; -}; - -exports.size = size; diff --git a/crypto/blockchains/bnb/utils/IsJs.js b/crypto/blockchains/bnb/utils/IsJs.js deleted file mode 100644 index 048a6b19a..000000000 --- a/crypto/blockchains/bnb/utils/IsJs.js +++ /dev/null @@ -1,1016 +0,0 @@ -/*! - * is.js 0.8.0 - * Author: Aras Atasaygin - */ - -// AMD with global, Node, or global -(function (root, factory) { - // eslint-disable-line no-extra-semi - if (typeof define === 'function' && define.amd) { - // AMD. Register as an anonymous module. - define(function () { - // Also create a global in case some scripts - // that are loaded still are looking for - // a global even when an AMD loader is in use. - return (root.is = factory()); - }); - } else if (typeof exports === 'object') { - // Node. Does not work with strict CommonJS, but - // only CommonJS-like enviroments that support module.exports, - // like Node. - module.exports = factory(); - } else { - // Browser globals (root is self) - root.is = factory(); - } -})(this, function () { - // Baseline - /* -------------------------------------------------------------------------- */ - - // define 'is' object and current version - var is = {}; - is.VERSION = '0.8.0'; - - // define interfaces - is.not = {}; - is.all = {}; - is.any = {}; - - // cache some methods to call later on - var toString = Object.prototype.toString; - var slice = Array.prototype.slice; - var hasOwnProperty = Object.prototype.hasOwnProperty; - - // helper function which reverses the sense of predicate result - function not(func) { - return function () { - return !func.apply(null, slice.call(arguments)); - }; - } - - // helper function which call predicate function per parameter and return true if all pass - function all(func) { - return function () { - var params = getParams(arguments); - var length = params.length; - for (var i = 0; i < length; i++) { - if (!func.call(null, params[i])) { - return false; - } - } - return true; - }; - } - - // helper function which call predicate function per parameter and return true if any pass - function any(func) { - return function () { - var params = getParams(arguments); - var length = params.length; - for (var i = 0; i < length; i++) { - if (func.call(null, params[i])) { - return true; - } - } - return false; - }; - } - - // build a 'comparator' object for various comparison checks - var comparator = { - '<': function (a, b) { - return a < b; - }, - '<=': function (a, b) { - return a <= b; - }, - '>': function (a, b) { - return a > b; - }, - '>=': function (a, b) { - return a >= b; - } - }; - - // helper function which compares a version to a range - function compareVersion(version, range) { - var string = range + ''; - var n = +(string.match(/\d+/) || NaN); - var op = string.match(/^[<>]=?|/)[0]; - return comparator[op] - ? comparator[op](version, n) - : version == n || n !== n; - } - - // helper function which extracts params from arguments - function getParams(args) { - var params = slice.call(args); - var length = params.length; - if (length === 1 && is.array(params[0])) { - // support array - params = params[0]; - } - return params; - } - - // Type checks - /* -------------------------------------------------------------------------- */ - - // is a given value Arguments? - is.arguments = function (value) { - // fallback check is for IE - return ( - toString.call(value) === '[object Arguments]' || - (value != null && typeof value === 'object' && 'callee' in value) - ); - }; - - // is a given value Array? - is.array = - Array.isArray || - function (value) { - // check native isArray first - return toString.call(value) === '[object Array]'; - }; - - // is a given value Boolean? - is.boolean = function (value) { - return ( - value === true || - value === false || - toString.call(value) === '[object Boolean]' - ); - }; - - // is a given value Char? - is.char = function (value) { - return is.string(value) && value.length === 1; - }; - - // is a given value Date Object? - is.date = function (value) { - return toString.call(value) === '[object Date]'; - }; - - // is a given object a DOM node? - is.domNode = function (object) { - return is.object(object) && object.nodeType > 0; - }; - - // is a given value Error object? - is.error = function (value) { - return toString.call(value) === '[object Error]'; - }; - - // is a given value function? - is['function'] = function (value) { - // fallback check is for IE - return ( - toString.call(value) === '[object Function]' || - typeof value === 'function' - ); - }; - - // is given value a pure JSON object? - is.json = function (value) { - return toString.call(value) === '[object Object]'; - }; - - // is a given value NaN? - is.nan = function (value) { - // NaN is number :) Also it is the only value which does not equal itself - return value !== value; - }; - - // is a given value null? - is['null'] = function (value) { - return value === null; - }; - - // is a given value number? - is.number = function (value) { - return is.not.nan(value) && toString.call(value) === '[object Number]'; - }; - - // is a given value object? - is.object = function (value) { - return Object(value) === value; - }; - - // is a given value RegExp? - is.regexp = function (value) { - return toString.call(value) === '[object RegExp]'; - }; - - // are given values same type? - // prevent NaN, Number same type check - is.sameType = function (value, other) { - var tag = toString.call(value); - if (tag !== toString.call(other)) { - return false; - } - if (tag === '[object Number]') { - return !is.any.nan(value, other) || is.all.nan(value, other); - } - return true; - }; - // sameType method does not support 'all' and 'any' interfaces - is.sameType.api = ['not']; - - // is a given value String? - is.string = function (value) { - return toString.call(value) === '[object String]'; - }; - - // is a given value undefined? - is.undefined = function (value) { - return value === void 0; - }; - - // is a given value window? - // setInterval method is only available for window object - is.windowObject = function (value) { - return value != null && typeof value === 'object' && 'setInterval' in value; - }; - - // Presence checks - /* -------------------------------------------------------------------------- */ - - //is a given value empty? Objects, arrays, strings - is.empty = function (value) { - if (is.object(value)) { - var length = Object.getOwnPropertyNames(value).length; - if ( - length === 0 || - (length === 1 && is.array(value)) || - (length === 2 && is.arguments(value)) - ) { - return true; - } - return false; - } - return value === ''; - }; - - // is a given value existy? - is.existy = function (value) { - return value != null; - }; - - // is a given value falsy? - is.falsy = function (value) { - return !value; - }; - - // is a given value truthy? - is.truthy = not(is.falsy); - - // Arithmetic checks - /* -------------------------------------------------------------------------- */ - - // is a given number above minimum parameter? - is.above = function (n, min) { - return is.all.number(n, min) && n > min; - }; - // above method does not support 'all' and 'any' interfaces - is.above.api = ['not']; - - // is a given number decimal? - is.decimal = function (n) { - return is.number(n) && n % 1 !== 0; - }; - - // are given values equal? supports numbers, strings, regexes, booleans - // TODO: Add object and array support - is.equal = function (value, other) { - // check 0 and -0 equity with Infinity and -Infinity - if (is.all.number(value, other)) { - return value === other && 1 / value === 1 / other; - } - // check regexes as strings too - if (is.all.string(value, other) || is.all.regexp(value, other)) { - return '' + value === '' + other; - } - if (is.all.boolean(value, other)) { - return value === other; - } - return false; - }; - // equal method does not support 'all' and 'any' interfaces - is.equal.api = ['not']; - - // is a given number even? - is.even = function (n) { - return is.number(n) && n % 2 === 0; - }; - - // is a given number finite? - is.finite = - isFinite || - function (n) { - return is.not.infinite(n) && is.not.nan(n); - }; - - // is a given number infinite? - is.infinite = function (n) { - return n === Infinity || n === -Infinity; - }; - - // is a given number integer? - is.integer = function (n) { - return is.number(n) && n % 1 === 0; - }; - - // is a given number negative? - is.negative = function (n) { - return is.number(n) && n < 0; - }; - - // is a given number odd? - is.odd = function (n) { - return is.number(n) && n % 2 === 1; - }; - - // is a given number positive? - is.positive = function (n) { - return is.number(n) && n > 0; - }; - - // is a given number above maximum parameter? - is.under = function (n, max) { - return is.all.number(n, max) && n < max; - }; - // least method does not support 'all' and 'any' interfaces - is.under.api = ['not']; - - // is a given number within minimum and maximum parameters? - is.within = function (n, min, max) { - return is.all.number(n, min, max) && n > min && n < max; - }; - // within method does not support 'all' and 'any' interfaces - is.within.api = ['not']; - - // Regexp checks - /* -------------------------------------------------------------------------- */ - // Steven Levithan, Jan Goyvaerts: Regular Expressions Cookbook - // Scott Gonzalez: Email address validation - - // dateString match m/d/yy and mm/dd/yyyy, allowing any combination of one or two digits for the day and month, and two or four digits for the year - // eppPhone match extensible provisioning protocol format - // nanpPhone match north american number plan format - // time match hours, minutes, and seconds, 24-hour clock - var regexes = { - affirmative: /^(?:1|t(?:rue)?|y(?:es)?|ok(?:ay)?)$/, - alphaNumeric: /^[A-Za-z0-9]+$/, - caPostalCode: /^(?!.*[DFIOQU])[A-VXY][0-9][A-Z]\s?[0-9][A-Z][0-9]$/, - creditCard: - /^(?:(4[0-9]{12}(?:[0-9]{3})?)|(5[1-5][0-9]{14})|(6(?:011|5[0-9]{2})[0-9]{12})|(3[47][0-9]{13})|(3(?:0[0-5]|[68][0-9])[0-9]{11})|((?:2131|1800|35[0-9]{3})[0-9]{11}))$/, - dateString: - /^(1[0-2]|0?[1-9])([\/-])(3[01]|[12][0-9]|0?[1-9])(?:\2)(?:[0-9]{2})?[0-9]{2}$/, - email: - /^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$/i, // eslint-disable-line no-control-regex - eppPhone: /^\+[0-9]{1,3}\.[0-9]{4,14}(?:x.+)?$/, - hexadecimal: /^(?:0x)?[0-9a-fA-F]+$/, - hexColor: /^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/, - ipv4: /^(?:(?:\d|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5])\.){3}(?:\d|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5])$/, - ipv6: /^((?=.*::)(?!.*::.+::)(::)?([\dA-F]{1,4}:(:|\b)|){5}|([\dA-F]{1,4}:){6})((([\dA-F]{1,4}((?!\3)::|:\b|$))|(?!\2\3)){2}|(((2[0-4]|1\d|[1-9])?\d|25[0-5])\.?\b){4})$/i, - nanpPhone: /^\(?([0-9]{3})\)?[-. ]?([0-9]{3})[-. ]?([0-9]{4})$/, - socialSecurityNumber: - /^(?!000|666)[0-8][0-9]{2}-?(?!00)[0-9]{2}-?(?!0000)[0-9]{4}$/, - timeString: /^(2[0-3]|[01]?[0-9]):([0-5]?[0-9]):([0-5]?[0-9])$/, - ukPostCode: - /^[A-Z]{1,2}[0-9RCHNQ][0-9A-Z]?\s?[0-9][ABD-HJLNP-UW-Z]{2}$|^[A-Z]{2}-?[0-9]{4}$/, - url: /^(?:(?:https?|ftp):\/\/)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})))(?::\d{2,5})?(?:\/\S*)?$/i, - usZipCode: /^[0-9]{5}(?:-[0-9]{4})?$/ - }; - - function regexpCheck(regexp, regexes) { - is[regexp] = function (value) { - return regexes[regexp].test(value); - }; - } - - // create regexp checks methods from 'regexes' object - for (var regexp in regexes) { - if (regexes.hasOwnProperty(regexp)) { - regexpCheck(regexp, regexes); - } - } - - // simplify IP checks by calling the regex helpers for IPv4 and IPv6 - is.ip = function (value) { - return is.ipv4(value) || is.ipv6(value); - }; - - // String checks - /* -------------------------------------------------------------------------- */ - - // is a given string or sentence capitalized? - is.capitalized = function (string) { - if (is.not.string(string)) { - return false; - } - var words = string.split(' '); - for (var i = 0; i < words.length; i++) { - var word = words[i]; - if (word.length) { - var chr = word.charAt(0); - if (chr !== chr.toUpperCase()) { - return false; - } - } - } - return true; - }; - - // is string end with a given target parameter? - is.endWith = function (string, target) { - if (is.not.string(string)) { - return false; - } - target += ''; - var position = string.length - target.length; - return position >= 0 && string.indexOf(target, position) === position; - }; - // endWith method does not support 'all' and 'any' interfaces - is.endWith.api = ['not']; - - // is a given string include parameter target? - is.include = function (string, target) { - return string.indexOf(target) > -1; - }; - // include method does not support 'all' and 'any' interfaces - is.include.api = ['not']; - - // is a given string all lowercase? - is.lowerCase = function (string) { - return is.string(string) && string === string.toLowerCase(); - }; - - // is a given string palindrome? - is.palindrome = function (string) { - if (is.not.string(string)) { - return false; - } - string = string.replace(/[^a-zA-Z0-9]+/g, '').toLowerCase(); - var length = string.length - 1; - for (var i = 0, half = Math.floor(length / 2); i <= half; i++) { - if (string.charAt(i) !== string.charAt(length - i)) { - return false; - } - } - return true; - }; - - // is a given value space? - // horizantal tab: 9, line feed: 10, vertical tab: 11, form feed: 12, carriage return: 13, space: 32 - is.space = function (value) { - if (is.not.char(value)) { - return false; - } - var charCode = value.charCodeAt(0); - return (charCode > 8 && charCode < 14) || charCode === 32; - }; - - // is string start with a given target parameter? - is.startWith = function (string, target) { - return is.string(string) && string.indexOf(target) === 0; - }; - // startWith method does not support 'all' and 'any' interfaces - is.startWith.api = ['not']; - - // is a given string all uppercase? - is.upperCase = function (string) { - return is.string(string) && string === string.toUpperCase(); - }; - - // Time checks - /* -------------------------------------------------------------------------- */ - - var days = [ - 'sunday', - 'monday', - 'tuesday', - 'wednesday', - 'thursday', - 'friday', - 'saturday' - ]; - var months = [ - 'january', - 'february', - 'march', - 'april', - 'may', - 'june', - 'july', - 'august', - 'september', - 'october', - 'november', - 'december' - ]; - - // is a given dates day equal given day parameter? - is.day = function (date, day) { - return is.date(date) && day.toLowerCase() === days[date.getDay()]; - }; - // day method does not support 'all' and 'any' interfaces - is.day.api = ['not']; - - // is a given date in daylight saving time? - is.dayLightSavingTime = function (date) { - var january = new Date(date.getFullYear(), 0, 1); - var july = new Date(date.getFullYear(), 6, 1); - var stdTimezoneOffset = Math.max( - january.getTimezoneOffset(), - july.getTimezoneOffset() - ); - return date.getTimezoneOffset() < stdTimezoneOffset; - }; - - // is a given date future? - is.future = function (date) { - var now = new Date(); - return is.date(date) && date.getTime() > now.getTime(); - }; - - // is date within given range? - is.inDateRange = function (date, start, end) { - if (is.not.date(date) || is.not.date(start) || is.not.date(end)) { - return false; - } - var stamp = date.getTime(); - return stamp > start.getTime() && stamp < end.getTime(); - }; - // inDateRange method does not support 'all' and 'any' interfaces - is.inDateRange.api = ['not']; - - // is a given date in last month range? - is.inLastMonth = function (date) { - return is.inDateRange( - date, - new Date(new Date().setMonth(new Date().getMonth() - 1)), - new Date() - ); - }; - - // is a given date in last week range? - is.inLastWeek = function (date) { - return is.inDateRange( - date, - new Date(new Date().setDate(new Date().getDate() - 7)), - new Date() - ); - }; - - // is a given date in last year range? - is.inLastYear = function (date) { - return is.inDateRange( - date, - new Date(new Date().setFullYear(new Date().getFullYear() - 1)), - new Date() - ); - }; - - // is a given date in next month range? - is.inNextMonth = function (date) { - return is.inDateRange( - date, - new Date(), - new Date(new Date().setMonth(new Date().getMonth() + 1)) - ); - }; - - // is a given date in next week range? - is.inNextWeek = function (date) { - return is.inDateRange( - date, - new Date(), - new Date(new Date().setDate(new Date().getDate() + 7)) - ); - }; - - // is a given date in next year range? - is.inNextYear = function (date) { - return is.inDateRange( - date, - new Date(), - new Date(new Date().setFullYear(new Date().getFullYear() + 1)) - ); - }; - - // is the given year a leap year? - is.leapYear = function (year) { - return ( - is.number(year) && - ((year % 4 === 0 && year % 100 !== 0) || year % 400 === 0) - ); - }; - - // is a given dates month equal given month parameter? - is.month = function (date, month) { - return is.date(date) && month.toLowerCase() === months[date.getMonth()]; - }; - // month method does not support 'all' and 'any' interfaces - is.month.api = ['not']; - - // is a given date past? - is.past = function (date) { - var now = new Date(); - return is.date(date) && date.getTime() < now.getTime(); - }; - - // is a given date in the parameter quarter? - is.quarterOfYear = function (date, quarter) { - return ( - is.date(date) && - is.number(quarter) && - quarter === Math.floor((date.getMonth() + 3) / 3) - ); - }; - // quarterOfYear method does not support 'all' and 'any' interfaces - is.quarterOfYear.api = ['not']; - - // is a given date indicate today? - is.today = function (date) { - var now = new Date(); - var todayString = now.toDateString(); - return is.date(date) && date.toDateString() === todayString; - }; - - // is a given date indicate tomorrow? - is.tomorrow = function (date) { - var now = new Date(); - var tomorrowString = new Date( - now.setDate(now.getDate() + 1) - ).toDateString(); - return is.date(date) && date.toDateString() === tomorrowString; - }; - - // is a given date weekend? - // 6: Saturday, 0: Sunday - is.weekend = function (date) { - return is.date(date) && (date.getDay() === 6 || date.getDay() === 0); - }; - - // is a given date weekday? - is.weekday = not(is.weekend); - - // is a given dates year equal given year parameter? - is.year = function (date, year) { - return is.date(date) && is.number(year) && year === date.getFullYear(); - }; - // year method does not support 'all' and 'any' interfaces - is.year.api = ['not']; - - // is a given date indicate yesterday? - is.yesterday = function (date) { - var now = new Date(); - var yesterdayString = new Date( - now.setDate(now.getDate() - 1) - ).toDateString(); - return is.date(date) && date.toDateString() === yesterdayString; - }; - - // Environment checks - /* -------------------------------------------------------------------------- */ - - var freeGlobal = - is.windowObject(typeof global == 'object' && global) && global; - var freeSelf = is.windowObject(typeof self == 'object' && self) && self; - var thisGlobal = is.windowObject(typeof this == 'object' && this) && this; - var root = freeGlobal || freeSelf || thisGlobal || Function('return this')(); - - var document = freeSelf && freeSelf.document; - var previousIs = root.is; - - // store navigator properties to use later - var navigator = freeSelf && freeSelf.navigator; - var appVersion = ((navigator && navigator.appVersion) || '').toLowerCase(); - var userAgent = ((navigator && navigator.userAgent) || '').toLowerCase(); - var vendor = ((navigator && navigator.vendor) || '').toLowerCase(); - - // is current device android? - is.android = function () { - return /android/.test(userAgent); - }; - // android method does not support 'all' and 'any' interfaces - is.android.api = ['not']; - - // is current device android phone? - is.androidPhone = function () { - return /android/.test(userAgent) && /mobile/.test(userAgent); - }; - // androidPhone method does not support 'all' and 'any' interfaces - is.androidPhone.api = ['not']; - - // is current device android tablet? - is.androidTablet = function () { - return /android/.test(userAgent) && !/mobile/.test(userAgent); - }; - // androidTablet method does not support 'all' and 'any' interfaces - is.androidTablet.api = ['not']; - - // is current device blackberry? - is.blackberry = function () { - return /blackberry/.test(userAgent) || /bb10/.test(userAgent); - }; - // blackberry method does not support 'all' and 'any' interfaces - is.blackberry.api = ['not']; - - // is current browser chrome? - // parameter is optional - is.chrome = function (range) { - var match = /google inc/.test(vendor) - ? userAgent.match(/(?:chrome|crios)\/(\d+)/) - : null; - return match !== null && compareVersion(match[1], range); - }; - // chrome method does not support 'all' and 'any' interfaces - is.chrome.api = ['not']; - - // is current device desktop? - is.desktop = function () { - return is.not.mobile() && is.not.tablet(); - }; - // desktop method does not support 'all' and 'any' interfaces - is.desktop.api = ['not']; - - // is current browser edge? - // parameter is optional - is.edge = function (range) { - var match = userAgent.match(/edge\/(\d+)/); - return match !== null && compareVersion(match[1], range); - }; - // edge method does not support 'all' and 'any' interfaces - is.edge.api = ['not']; - - // is current browser firefox? - // parameter is optional - is.firefox = function (range) { - var match = userAgent.match(/(?:firefox|fxios)\/(\d+)/); - return match !== null && compareVersion(match[1], range); - }; - // firefox method does not support 'all' and 'any' interfaces - is.firefox.api = ['not']; - - // is current browser internet explorer? - // parameter is optional - is.ie = function (range) { - var match = userAgent.match(/(?:msie |trident.+?; rv:)(\d+)/); - return match !== null && compareVersion(match[1], range); - }; - // ie method does not support 'all' and 'any' interfaces - is.ie.api = ['not']; - - // is current device ios? - is.ios = function () { - return is.iphone() || is.ipad() || is.ipod(); - }; - // ios method does not support 'all' and 'any' interfaces - is.ios.api = ['not']; - - // is current device ipad? - // parameter is optional - is.ipad = function (range) { - var match = userAgent.match(/ipad.+?os (\d+)/); - return match !== null && compareVersion(match[1], range); - }; - // ipad method does not support 'all' and 'any' interfaces - is.ipad.api = ['not']; - - // is current device iphone? - // parameter is optional - is.iphone = function (range) { - // original iPhone doesn't have the os portion of the UA - var match = userAgent.match(/iphone(?:.+?os (\d+))?/); - return match !== null && compareVersion(match[1] || 1, range); - }; - // iphone method does not support 'all' and 'any' interfaces - is.iphone.api = ['not']; - - // is current device ipod? - // parameter is optional - is.ipod = function (range) { - var match = userAgent.match(/ipod.+?os (\d+)/); - return match !== null && compareVersion(match[1], range); - }; - // ipod method does not support 'all' and 'any' interfaces - is.ipod.api = ['not']; - - // is current operating system linux? - is.linux = function () { - return /linux/.test(appVersion); - }; - // linux method does not support 'all' and 'any' interfaces - is.linux.api = ['not']; - - // is current operating system mac? - is.mac = function () { - return /mac/.test(appVersion); - }; - // mac method does not support 'all' and 'any' interfaces - is.mac.api = ['not']; - - // is current device mobile? - is.mobile = function () { - return ( - is.iphone() || - is.ipod() || - is.androidPhone() || - is.blackberry() || - is.windowsPhone() - ); - }; - // mobile method does not support 'all' and 'any' interfaces - is.mobile.api = ['not']; - - // is current state offline? - is.offline = not(is.online); - // offline method does not support 'all' and 'any' interfaces - is.offline.api = ['not']; - - // is current state online? - is.online = function () { - return !navigator || navigator.onLine === true; - }; - // online method does not support 'all' and 'any' interfaces - is.online.api = ['not']; - - // is current browser opera? - // parameter is optional - is.opera = function (range) { - var match = userAgent.match(/(?:^opera.+?version|opr)\/(\d+)/); - return match !== null && compareVersion(match[1], range); - }; - // opera method does not support 'all' and 'any' interfaces - is.opera.api = ['not']; - - // is current browser phantomjs? - // parameter is optional - is.phantom = function (range) { - var match = userAgent.match(/phantomjs\/(\d+)/); - return match !== null && compareVersion(match[1], range); - }; - // phantom method does not support 'all' and 'any' interfaces - is.phantom.api = ['not']; - - // is current browser safari? - // parameter is optional - is.safari = function (range) { - var match = userAgent.match(/version\/(\d+).+?safari/); - return match !== null && compareVersion(match[1], range); - }; - // safari method does not support 'all' and 'any' interfaces - is.safari.api = ['not']; - - // is current device tablet? - is.tablet = function () { - return is.ipad() || is.androidTablet() || is.windowsTablet(); - }; - // tablet method does not support 'all' and 'any' interfaces - is.tablet.api = ['not']; - - // is current device supports touch? - is.touchDevice = function () { - return ( - !!document && - ('ontouchstart' in freeSelf || - ('DocumentTouch' in freeSelf && document instanceof DocumentTouch)) - ); - }; - // touchDevice method does not support 'all' and 'any' interfaces - is.touchDevice.api = ['not']; - - // is current operating system windows? - is.windows = function () { - return /win/.test(appVersion); - }; - // windows method does not support 'all' and 'any' interfaces - is.windows.api = ['not']; - - // is current device windows phone? - is.windowsPhone = function () { - return is.windows() && /phone/.test(userAgent); - }; - // windowsPhone method does not support 'all' and 'any' interfaces - is.windowsPhone.api = ['not']; - - // is current device windows tablet? - is.windowsTablet = function () { - return is.windows() && is.not.windowsPhone() && /touch/.test(userAgent); - }; - // windowsTablet method does not support 'all' and 'any' interfaces - is.windowsTablet.api = ['not']; - - // Object checks - /* -------------------------------------------------------------------------- */ - - // has a given object got parameterized count property? - is.propertyCount = function (object, count) { - if (is.not.object(object) || is.not.number(count)) { - return false; - } - var n = 0; - for (var property in object) { - if (hasOwnProperty.call(object, property) && ++n > count) { - return false; - } - } - return n === count; - }; - // propertyCount method does not support 'all' and 'any' interfaces - is.propertyCount.api = ['not']; - - // is given object has parameterized property? - is.propertyDefined = function (object, property) { - return is.object(object) && is.string(property) && property in object; - }; - // propertyDefined method does not support 'all' and 'any' interfaces - is.propertyDefined.api = ['not']; - - // Array checks - /* -------------------------------------------------------------------------- */ - - // is a given item in an array? - is.inArray = function (value, array) { - if (is.not.array(array)) { - return false; - } - for (var i = 0; i < array.length; i++) { - if (array[i] === value) { - return true; - } - } - return false; - }; - // inArray method does not support 'all' and 'any' interfaces - is.inArray.api = ['not']; - - // is a given array sorted? - is.sorted = function (array, sign) { - if (is.not.array(array)) { - return false; - } - var predicate = comparator[sign] || comparator['>=']; - for (var i = 1; i < array.length; i++) { - if (!predicate(array[i], array[i - 1])) { - return false; - } - } - return true; - }; - - // API - // Set 'not', 'all' and 'any' interfaces to methods based on their api property - /* -------------------------------------------------------------------------- */ - - function setInterfaces() { - var options = is; - for (var option in options) { - if ( - hasOwnProperty.call(options, option) && - is['function'](options[option]) - ) { - var interfaces = options[option].api || ['not', 'all', 'any']; - for (var i = 0; i < interfaces.length; i++) { - if (interfaces[i] === 'not') { - is.not[option] = not(is[option]); - } - if (interfaces[i] === 'all') { - is.all[option] = all(is[option]); - } - if (interfaces[i] === 'any') { - is.any[option] = any(is[option]); - } - } - } - } - } - setInterfaces(); - - // Configuration methods - // Intentionally added after setInterfaces function - /* -------------------------------------------------------------------------- */ - - // change namespace of library to prevent name collisions - // var preferredName = is.setNamespace(); - // preferredName.odd(3); - // => true - is.setNamespace = function () { - root.is = previousIs; - return this; - }; - - // set optional regexes to methods - is.setRegexp = function (regexp, name) { - for (var r in regexes) { - if (hasOwnProperty.call(regexes, r) && name === r) { - regexes[r] = regexp; - } - } - }; - - return is; -}); diff --git a/crypto/blockchains/bnb/utils/UVarInt.ts b/crypto/blockchains/bnb/utils/UVarInt.ts deleted file mode 100644 index 95278358c..000000000 --- a/crypto/blockchains/bnb/utils/UVarInt.ts +++ /dev/null @@ -1,81 +0,0 @@ -/* eslint-disable @typescript-eslint/no-var-requires */ -const BN = require('bn.js'); - -function VarIntFunc(signed: boolean) { - const encodingLength = function encodingLength(n: number) { - if (signed) n *= 2; - - if (n < 0) { - throw Error('varint value is out of bounds'); - } - - const bits = Math.log2(n + 1); - return Math.ceil(bits / 7) || 1; - }; - - const encode = function encode(n: number, buffer?: Buffer, offset?: number) { - if (n < 0) { - throw Error('varint value is out of bounds'); - } - - buffer = buffer || Buffer.alloc(encodingLength(n)); - offset = offset || 0; - const nStr = n.toString(); - let bn = new BN(nStr, 10); - const num255 = new BN(0xff); - const num128 = new BN(0x80); // amino signed varint is multiplied by 2 - - if (signed) { - bn = bn.muln(2); - } - - let i = 0; - - while (bn.gten(0x80)) { - buffer[offset + i] = bn.and(num255).or(num128).toNumber(); - bn = bn.shrn(7); - i++; - } - - buffer[offset + i] = bn.andln(0xff); // TODO - // encode.bytes = i + 1 - - return buffer; - }; - /** - * https://github.com/golang/go/blob/master/src/encoding/binary/varint.go#L60 - */ - - const decode = function decode(bytes: number[]) { - let x = 0; - let s = 0; - - for (let i = 0, len = bytes.length; i < len; i++) { - const b = bytes[i]; - - if (b < 0x80) { - if (i > 9 || (i === 9 && b > 1)) { - return 0; - } - - return x | (b << s); - } - - x |= (b & 0x7f) << s; - s += 7; - } - - return 0; - }; - - return { - encode: encode, - decode: decode, - encodingLength: encodingLength - }; -} - -const UVarInt = VarIntFunc(false); -exports.UVarInt = UVarInt; -const VarInt = VarIntFunc(true); -exports.VarInt = VarInt; diff --git a/crypto/blockchains/bnb_smart/BnbSmartTransferProcessor.ts b/crypto/blockchains/bnb_smart/BnbSmartTransferProcessor.ts deleted file mode 100644 index eda6ad396..000000000 --- a/crypto/blockchains/bnb_smart/BnbSmartTransferProcessor.ts +++ /dev/null @@ -1,55 +0,0 @@ -/** - * @author Ksu - * @version 0.20 - * https://api.etherscan.io/api?module=gastracker&action=gasoracle&apikey=YourApiKeyToken - */ -import { AirDAOBlockchainTypes } from '../AirDAOBlockchainTypes'; -import EthTransferProcessor from '../eth/EthTransferProcessor'; - -import BnbSmartNetworkPrices from './basic/BnbSmartNetworkPrices'; -import BlocksoftExternalSettings from '@crypto/common/AirDAOExternalSettings'; - -export default class BnbSmartTransferProcessor - extends EthTransferProcessor - implements AirDAOBlockchainTypes.TransferProcessor -{ - async getFeeRate( - data: AirDAOBlockchainTypes.TransferData, - privateData: AirDAOBlockchainTypes.TransferPrivateData, - additionalData: {} = {} - ): Promise { - if ( - typeof additionalData.gasPrice === 'undefined' || - !additionalData.gasPrice - ) { - const minFee = BlocksoftExternalSettings.getStatic( - this._mainCurrencyCode + '_FORCE_PRICE' - ); - if (typeof minFee !== 'undefined' && minFee > 1) { - additionalData.gasPrice = minFee; - additionalData.gasPriceTitle = 'speed_blocks_2'; - } else { - let defaultFee = BlocksoftExternalSettings.getStatic( - this._mainCurrencyCode + '_PRICE' - ); - if (typeof defaultFee === 'undefined' || !defaultFee) { - defaultFee = 5000000000; - } - if (this._etherscanApiPathForFee) { - const tmpPrice = await BnbSmartNetworkPrices.getFees( - this._mainCurrencyCode, - this._etherscanApiPathForFee, - defaultFee, - 'BnbSmartTransferProcessor.getFeeRate' - ); - if (tmpPrice * 1 > defaultFee * 1) { - defaultFee = tmpPrice * 1; - } - } - additionalData.gasPrice = defaultFee; - additionalData.gasPriceTitle = 'speed_blocks_2'; - } - } - return super.getFeeRate(data, privateData, additionalData); - } -} diff --git a/crypto/blockchains/bnb_smart/BnbSmartTransferProcessorErc20.ts b/crypto/blockchains/bnb_smart/BnbSmartTransferProcessorErc20.ts deleted file mode 100644 index a5e8eba5d..000000000 --- a/crypto/blockchains/bnb_smart/BnbSmartTransferProcessorErc20.ts +++ /dev/null @@ -1,80 +0,0 @@ -/** - * @version 0.20 - */ -import { AirDAOBlockchainTypes } from '../AirDAOBlockchainTypes'; -import EthTransferProcessorErc20 from '../eth/EthTransferProcessorErc20'; -import BnbSmartNetworkPrices from './basic/BnbSmartNetworkPrices'; -import BlocksoftExternalSettings from '@crypto/common/AirDAOExternalSettings'; - -export default class BnbSmartTransferProcessorErc20 - extends EthTransferProcessorErc20 - implements AirDAOBlockchainTypes.TransferProcessor -{ - async getFeeRate( - data: AirDAOBlockchainTypes.TransferData, - privateData: AirDAOBlockchainTypes.TransferPrivateData, - additionalData: {} = {} - ): Promise { - if ( - typeof additionalData.gasPrice === 'undefined' || - !additionalData.gasPrice - ) { - const minFee = BlocksoftExternalSettings.getStatic( - this._mainCurrencyCode + '_FORCE_PRICE_ERC20' - ); - if (typeof minFee !== 'undefined' && minFee > 1) { - additionalData.gasPrice = minFee; - additionalData.gasPriceTitle = 'speed_blocks_2'; - } else { - let defaultFee = BlocksoftExternalSettings.getStatic( - this._mainCurrencyCode + '_PRICE' - ); - if (typeof defaultFee === 'undefined' || !defaultFee) { - defaultFee = 5000000000; - } - if (!this._etherscanApiPathForFee) { - additionalData.gasPrice = defaultFee; - additionalData.gasPriceTitle = 'speed_blocks_2'; - } else { - additionalData.gasPrice = await BnbSmartNetworkPrices.getFees( - this._mainCurrencyCode, - this._etherscanApiPathForFee, - defaultFee, - 'BnbSmartTransferProcessorErc20.getFeeRate' - ); - additionalData.gasPriceTitle = 'speed_blocks_2'; - } - } - } - const result = await super.getFeeRate(data, privateData, additionalData); - result.shouldShowFees = true; - return result; - } - - async checkTransferHasError( - data: AirDAOBlockchainTypes.CheckTransferHasErrorData - ): Promise { - // @ts-ignore - const balance = - data.addressFrom && data.addressFrom !== '' - ? await this._web3.eth.getBalance(data.addressFrom) - : 0; - if (balance > 0) { - return { isOk: true }; - } else { - const title = - this._mainCurrencyCode === 'BNB' - ? 'BNB Smart Chain' - : this._mainCurrencyCode; - // @ts-ignore - return { - isOk: false, - code: 'TOKEN', - parentBlockchain: - title as AirDAOBlockchainTypes.CheckTransferHasErrorResult['parentBlockchain'], - parentCurrency: - title as AirDAOBlockchainTypes.CheckTransferHasErrorResult['parentCurrency'] - }; - } - } -} diff --git a/crypto/blockchains/bnb_smart/basic/BnbSmartNetworkPrices.js b/crypto/blockchains/bnb_smart/basic/BnbSmartNetworkPrices.js deleted file mode 100644 index 8e7647be3..000000000 --- a/crypto/blockchains/bnb_smart/basic/BnbSmartNetworkPrices.js +++ /dev/null @@ -1,73 +0,0 @@ -/** - * @version 0.20 - */ -import AirDAOCryptoLog from '../../../common/AirDAOCryptoLog'; -import AirDAOAxios from '../../../common/AirDAOAxios'; -import BlocksoftUtils from '../../../common/AirDAOUtils'; -import BlocksoftExternalSettings from '../../../common/AirDAOExternalSettings'; - -const CACHE_VALID_TIME = 120000; // 2 minute -const CACHE_FEES = { - BNB: { - fee: 5000000000, - ts: 0 - } -}; - -class BnbSmartNetworkPrices { - async getFees( - mainCurrencyCode, - etherscanApiPath, - defaultFee = 5000000000, - source = '' - ) { - const now = new Date().getTime(); - if ( - typeof CACHE_FEES[mainCurrencyCode] !== 'undefined' && - now - CACHE_FEES[mainCurrencyCode].ts < CACHE_VALID_TIME - ) { - return CACHE_FEES[mainCurrencyCode].fee; - } - const tmp = etherscanApiPath.split('/'); - const feesApiPath = `https://${tmp[2]}/api?module=proxy&action=eth_gasPrice&apikey=YourApiKeyToken`; - AirDAOCryptoLog.log( - mainCurrencyCode + ' BnbSmartNetworkPricesProvider.getFees no cache load' - ); - try { - const res = await AirDAOAxios.getWithoutBraking(feesApiPath); - if ( - res && - typeof res.data !== 'undefined' && - typeof res.data.result !== 'undefined' - ) { - const tmp = BlocksoftUtils.hexToDecimal(res.data.result); - if (tmp * 1 > 0) { - CACHE_FEES[mainCurrencyCode] = { - fee: (tmp * 1).toString().substr(0, 11), - time: now - }; - } else if ( - typeof CACHE_FEES[mainCurrencyCode] === 'undefined' || - !CACHE_FEES[mainCurrencyCode].fee - ) { - CACHE_FEES[mainCurrencyCode].fee = - await BlocksoftExternalSettings.getStatic('BNB_SMART_PRICE'); - } - } - } catch (e) { - AirDAOCryptoLog.log( - mainCurrencyCode + - ' BnbSmartNetworkPricesProvider.getOnlyFees loaded prev fee as error' + - e.message - ); - // do nothing - } - - return typeof CACHE_FEES[mainCurrencyCode] !== 'undefined' - ? CACHE_FEES[mainCurrencyCode].fee - : defaultFee; - } -} - -const singleton = new BnbSmartNetworkPrices(); -export default singleton; diff --git a/crypto/blockchains/bsv/BsvScannerProcessor.js b/crypto/blockchains/bsv/BsvScannerProcessor.js deleted file mode 100644 index 3f3541dba..000000000 --- a/crypto/blockchains/bsv/BsvScannerProcessor.js +++ /dev/null @@ -1,372 +0,0 @@ -/** - * @version 0.5 - */ - -import AirDAOAxios from '@crypto/common/AirDAOAxios'; -import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; -import BlocksoftUtils from '@crypto/common/AirDAOUtils'; -import BlocksoftPrettyNumbers from '@crypto/common/AirDAOPrettyNumbers'; -import BsvTmpDS from '@crypto/blockchains/bsv/stores/BsvTmpDS'; - -const API_PATH = 'https://api.whatsonchain.com/v1/bsv/main'; -const CACHE_TXS = {}; -const CACHE_ASKED = {}; - -export default class BsvScannerProcessor { - /** - * @type {number} - * @private - */ - _blocksToConfirm = 10; - - /** - * https://api.whatsonchain.com/v1/bsv/main/address/1BcHq66j64juMffHc4Sc5XQ59wWrcSygoZ/balance - * @param {string} address - * @return {Promise<{balance, unconfirmed, provider}>} - */ - async getBalanceBlockchain(address) { - const link = `${API_PATH}/address/${address}/balance`; - let res = false; - let balance = 0; - try { - res = await AirDAOAxios.getWithoutBraking(link); - if ( - res && - typeof res.data !== 'undefined' && - res.data && - typeof res.data.confirmed !== 'undefined' - ) { - balance = res.data.confirmed; - } else { - return false; - } - } catch (e) { - // ? - throw e; - } - return { - balance: balance, - unconfirmed: 0, - provider: 'api.whatsonchain.com' - }; - } - - async _saveTxToCache(tx) { - if (typeof CACHE_TXS[tx.txid] === 'undefined' && tx.confirmations > 100) { - await BsvTmpDS.saveCache(tx.txid, tx); - } - - CACHE_TXS[tx.txid] = tx; - } - - /** - * https://api.whatsonchain.com/v1/bsv/main/address/1BcHq66j64juMffHc4Sc5XQ59wWrcSygoZ/history - * @param scanData - * @param source - * @returns {Promise<*[]>} - */ - async getTransactionsBlockchain(scanData, source = '') { - const address = scanData.account.address.trim(); - if (!CACHE_ASKED[address]) { - try { - const asked = await BsvTmpDS.getCache(address); - if (asked) { - for (let txid in asked) { - CACHE_TXS[txid] = asked[txid]; - } - } - CACHE_ASKED[address] = true; - } catch (e) { - throw new Error(e.message + ' in BsvTmpDS.getCache'); - } - } - AirDAOCryptoLog.log( - 'BsvScannerProcessor.getTransactions started ' + address - ); - const linkTxs = `${API_PATH}/address/${address}/history`; - let res = false; - try { - res = await AirDAOAxios.getWithoutBraking(linkTxs); - } catch (e) { - throw e; - } - - if (!res || typeof res.data === 'undefined' || !res.data) { - return false; - } - const basicTxs = []; - for (const row of res.data) { - if (row.height * 1 > 0) { - basicTxs.push(row); - } else { - AirDAOCryptoLog.log( - 'BsvScannerProcessor.getTransactions strange one ' + - JSON.stringify(row) - ); - } - } - - let index = basicTxs.length; - let bulkTxs = []; - let otherTxs = []; - let transactions = []; - for (let i = 0; i < 20; i++) { - index--; - if (index < 0) { - break; - } - let txid = basicTxs[index].tx_hash; - if (typeof CACHE_TXS[txid] === 'undefined') { - bulkTxs.push(txid); - } else { - otherTxs.push(CACHE_TXS[txid]); - } - } - - if (bulkTxs.length > 0) { - AirDAOCryptoLog.log( - 'BsvScannerProcessor.getTransactions will ask ' + - JSON.stringify(bulkTxs) - ); - res = false; - try { - res = await AirDAOAxios.post(API_PATH + '/txs', { txids: bulkTxs }); - } catch (e) { - throw e; - } - if ( - typeof res !== 'undefined' && - res && - typeof res.data !== 'undefined' && - res.data - ) { - transactions = await this._unifyTransactions( - address, - res.data, - otherTxs - ); - } else { - transactions = await this._unifyTransactions(address, [], otherTxs); - } - } else { - transactions = await this._unifyTransactions(address, [], otherTxs); - } - - AirDAOCryptoLog.log( - 'BsvScannerProcessor.getTransactions finished ' + address - ); - return transactions; - } - - async _precheckVins(result) { - let vins = []; - for (let tx of result) { - for (let vin of tx.vin) { - if (typeof CACHE_TXS[vin.txid] === 'undefined') { - vins.push(vin.txid); - if (vins.length > 19) { - AirDAOCryptoLog.log( - 'BsvScannerProcessor.getTransactions will ask vins ' + - JSON.stringify(vins) - ); - const res = await AirDAOAxios.post(API_PATH + '/txs', { - txids: vins - }); - if (res && typeof res.data !== 'undefined' && res.data) { - for (const tx of res.data) { - await this._saveTxToCache(tx); - } - } - vins = []; - } - } - } - } - if (vins && vins.length > 0) { - AirDAOCryptoLog.log( - 'BsvScannerProcessor.getTransactions will ask vins1 ' + - JSON.stringify(vins) - ); - const res = await AirDAOAxios.post(API_PATH + '/txs', { txids: vins }); - if (res && typeof res.data !== 'undefined' && res.data) { - for (const tx of res.data) { - await this._saveTxToCache(tx); - } - } - } - } - - async _unifyTransactions(address, result, otherTxs) { - const transactions = []; - if (result && result.length > 0) { - for (let tx of result) { - await this._saveTxToCache(tx); - } - await this._precheckVins(result); - for (let tx of result) { - const transaction = await this._unifyTransaction(address, tx); - if (transaction) { - transactions.push(transaction); - } - } - } - if (otherTxs && otherTxs.length > 0) { - for (let tx of otherTxs) { - await this._precheckVins(otherTxs); - try { - const transaction = await this._unifyTransaction(address, tx); - if (transaction) { - transactions.push(transaction); - } - } catch (e) { - throw new Error(e.message + ' in otherTxs _unifyTransaction '); - } - } - } - return transactions; - } - - /** - * @param {string} address - * @param {Object} transaction - * @param {string} transaction.txid "d1eecd2a07b9712644bcec3d7285fdc444691da6701d0032ebf6b0f823ab1727" - * @param {string} transaction.hash "d1eecd2a07b9712644bcec3d7285fdc444691da6701d0032ebf6b0f823ab1727" - * @param {string} transaction.version 2 - * @param {string} transaction.size 521 - * @param {string} transaction.locktime 0 - * @param {string} transaction.blockhash "000000000000000006d66f40fbe37ccc1854c3d13167e41485716b217ead8d6a" - * @param {string} transaction.confirmations 151964 - * @param {string} transaction.time 1576793232, - * @param {string} transaction.blocktime 1576793232 - * @param {string} transaction.blockheight 613832 - * @param {string} transaction.vin[].vout - * @param {string} transaction.vin[].txid - * @param {string} transaction.vin[].scriptSig.hex - * @param {string} transaction.vout[].value - * @param {string} transaction.vout[].n - * @param {string} transaction.vout[].scriptPubKey.addresses[] - * @return {UnifiedTransaction} - * @private - **/ - async _unifyTransaction(address, transaction) { - try { - const tx = { - transactionHash: transaction.txid, - blockHash: transaction.blockhash, - blockNumber: transaction.blockheight, - blockTime: new Date(transaction.blocktime * 1000).toISOString(), - blockConfirmations: transaction.confirmations, - transactionDirection: '?', - addressFrom: '', - addressTo: '', - addressAmount: 0, - transactionStatus: - transaction.confirmations > this._blocksToConfirm ? 'success' : 'new', - transactionFee: '0' - }; - - const vins = []; - for (let vin of transaction.vin) { - if (typeof CACHE_TXS[vin.txid] === 'undefined') { - vins.push(vin.txid); - } - } - if (vins && vins.length > 0) { - AirDAOCryptoLog.log( - 'BsvScannerProcessor.getTransactions will ask vins2 ' + - JSON.stringify(vins) - ); - const res = await AirDAOAxios.post(API_PATH + '/txs', { - txids: vins - }); - if (res && typeof res.data !== 'undefined' && res.data) { - for (const tx of res.data) { - await this._saveTxToCache(tx); - } - } - } - let othersSumIn = 0; - let mySumIn = 0; - let othersSumOut = 0; - let mySumOut = 0; - let othersAddressIn = false; - let othersAddressOut = false; - for (let vin of transaction.vin) { - if (typeof CACHE_TXS[vin.txid] === 'undefined') { - AirDAOCryptoLog.log( - 'BsvScannerProcessor _unifyTransaction error cant find vin ' + - vin.txid + - ' for tx ' + - transaction.txid - ); - } else { - let found = false; - for (let vinToCheck of CACHE_TXS[vin.txid].vout) { - if (vinToCheck.n === vin.vout) { - if (typeof vinToCheck.scriptPubKey.addresses !== 'undefined') { - const addressToCheck = vinToCheck.scriptPubKey.addresses[0]; - const valueToCheck = vinToCheck.value; - if (addressToCheck.toLowerCase() === address.toLowerCase()) { - mySumIn += valueToCheck * 1; - } else { - othersSumIn += valueToCheck * 1; - othersAddressIn = addressToCheck; - } - } - found = true; - } - } - if (!found) { - AirDAOCryptoLog.log( - 'BsvScannerProcessor _unifyTransaction error cant find vin ' + - vin.txid + - ' n ' + - vin.vout + - ' for tx ' + - transaction.txid - ); - } - } - } - - for (let vout of transaction.vout) { - const addressToCheck = vout.scriptPubKey.addresses[0]; - const valueToCheck = vout.value; - if (addressToCheck.toLowerCase() === address.toLowerCase()) { - mySumOut += valueToCheck * 1; - } else { - othersSumOut += valueToCheck * 1; - othersAddressOut = addressToCheck; - } - } - - if (!othersSumIn && !othersSumOut) { - tx.transactionDirection = 'self'; - tx.transactionFee = BlocksoftPrettyNumbers.setCurrencyCode( - 'BSV' - ).makeUnPretty(BlocksoftUtils.diff(mySumOut, mySumIn)); - } else if (!othersSumIn) { - tx.transactionDirection = 'outcome'; - tx.addressTo = othersAddressOut; - const amount = BlocksoftUtils.diff(othersSumOut, othersSumIn); - const fee = BlocksoftUtils.diff( - mySumOut + othersSumOut, - mySumIn + othersSumIn - ); - tx.addressAmount = - BlocksoftPrettyNumbers.setCurrencyCode('BSV').makeUnPretty(amount); - tx.transactionFee = - BlocksoftPrettyNumbers.setCurrencyCode('BSV').makeUnPretty(fee); - } else { - tx.transactionDirection = 'income'; - tx.addressFrom = othersAddressIn; - const amount = BlocksoftUtils.diff(mySumOut, mySumIn); - tx.addressAmount = - BlocksoftPrettyNumbers.setCurrencyCode('BSV').makeUnPretty(amount); - } - return tx; - } catch (e) { - throw e; - } - } -} diff --git a/crypto/blockchains/bsv/BsvTransferProcessor.ts b/crypto/blockchains/bsv/BsvTransferProcessor.ts deleted file mode 100644 index e8226c606..000000000 --- a/crypto/blockchains/bsv/BsvTransferProcessor.ts +++ /dev/null @@ -1,53 +0,0 @@ -/** - * @version 0.5 - */ -import { AirDAOBlockchainTypes } from '../AirDAOBlockchainTypes'; -import DogeTransferProcessor from '../doge/DogeTransferProcessor'; -import DogeTxInputsOutputs from '../doge/tx/DogeTxInputsOutputs'; -import BsvTxBuilder from './tx/BsvTxBuilder'; -import BsvUnspentsProvider from './providers/BsvUnspentsProvider'; -import BsvSendProvider from '@crypto/blockchains/bsv/providers/BsvSendProvider'; - -export default class BsvTransferProcessor - extends DogeTransferProcessor - implements AirDAOBlockchainTypes.TransferProcessor -{ - _trezorServerCode = ''; - - _builderSettings: AirDAOBlockchainTypes.BuilderSettings = { - minOutputDustReadable: 0.000005, - minChangeDustReadable: 0.00001, - feeMaxForByteSatoshi: 10000, // for tx builder - feeMaxAutoReadable2: 0.2, // for fee calc, - feeMaxAutoReadable6: 0.1, // for fee calc - feeMaxAutoReadable12: 0.05, // for fee calc - changeTogether: true, - minRbfStepSatoshi: 10, - minSpeedUpMulti: 1.5 - }; - - canRBF( - data: AirDAOBlockchainTypes.DbAccount, - transaction: AirDAOBlockchainTypes.DbTransaction - ): boolean { - return false; - } - - _initProviders() { - if (this._initedProviders) return false; - this.unspentsProvider = new BsvUnspentsProvider( - this._settings, - this._trezorServerCode - ); - this.sendProvider = new BsvSendProvider( - this._settings, - this._trezorServerCode - ); - this.txPrepareInputsOutputs = new DogeTxInputsOutputs( - this._settings, - this._builderSettings - ); - this.txBuilder = new BsvTxBuilder(this._settings, this._builderSettings); - this._initedProviders = true; - } -} diff --git a/crypto/blockchains/bsv/providers/BsvSendProvider.ts b/crypto/blockchains/bsv/providers/BsvSendProvider.ts deleted file mode 100644 index feefc1dba..000000000 --- a/crypto/blockchains/bsv/providers/BsvSendProvider.ts +++ /dev/null @@ -1,101 +0,0 @@ -/** - * @version 0.5 - */ -import { AirDAOBlockchainTypes } from '@crypto/blockchains/AirDAOBlockchainTypes'; -import DogeSendProvider from '@crypto/blockchains/doge/providers/DogeSendProvider'; -import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; -import AirDAOAxios from '@crypto/common/AirDAOAxios'; - -export default class BsvSendProvider - extends DogeSendProvider - implements AirDAOBlockchainTypes.SendProvider -{ - async sendTx( - hex: string, - subtitle: string, - txRBF: any, - logData: any - ): Promise<{ transactionHash: string; transactionJson: any }> { - await AirDAOCryptoLog.log( - this._settings.currencyCode + - ' BsvSendProvider.sendTx ' + - subtitle + - ' started ', - logData - ); - - const link = 'https://api.whatsonchain.com/v1/bsv/main/tx/raw'; - - //logData = await this._check(hex, subtitle, txRBF, logData) - - let res; - try { - res = await AirDAOAxios.post(link, { txhex: hex }); - } catch (e) { - if (subtitle.indexOf('rawSend') !== -1) { - throw e; - } - try { - logData.error = e.message; - await this._checkError(hex, subtitle, txRBF, logData); - } catch (e2) { - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' DogeSendProvider.send proxy error errorTx ' + - e2.message - ); - } - if ( - this._settings.currencyCode === 'USDT' && - e.message.indexOf('bad-txns-in-belowout') !== -1 - ) { - throw new Error('SERVER_RESPONSE_NOT_ENOUGH_FEE'); - } else if (e.message.indexOf('transaction already in block') !== -1) { - throw new Error('SERVER_RESPONSE_TRANSACTION_ALREADY_MINED'); - } else if (e.message.indexOf('inputs-missingorspent') !== -1) { - throw new Error('SERVER_RESPONSE_TRANSACTION_ALREADY_MINED'); - } else if (e.message.indexOf('insufficient priority') !== -1) { - throw new Error('SERVER_RESPONSE_NO_RESPONSE_OR_MORE_FEE'); - } else if (e.message.indexOf('dust') !== -1) { - throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_DUST'); - } else if ( - e.message.indexOf('bad-txns-inputs-spent') !== -1 || - e.message.indexOf('txn-mempool-conflict') !== -1 - ) { - throw new Error('SERVER_RESPONSE_NO_RESPONSE'); - } else if ( - e.message.indexOf('min relay fee not met') !== -1 || - e.message.indexOf('fee for relay') !== -1 - ) { - throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_FEE'); - } else if ( - e.message.indexOf('insufficient fee, rejecting replacement') !== -1 - ) { - throw new Error( - 'SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_FEE_FOR_REPLACEMENT' - ); - } else if (e.message.indexOf('insufficient fee') !== -1) { - throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_FEE'); - } else if (e.message.indexOf('too-long-mempool-chain') !== -1) { - throw new Error('SERVER_RESPONSE_NO_RESPONSE'); - } else { - e.message += ' link: ' + link; - throw e; - } - } - if (typeof res.data === 'undefined' || !res.data) { - throw new Error('SERVER_RESPONSE_NOT_CONNECTED'); - } - - const transactionHash = res.data; - logData = await this._checkSuccess( - transactionHash, - hex, - subtitle, - txRBF, - logData - ); - - return { transactionHash, transactionJson: {}, logData }; - } -} diff --git a/crypto/blockchains/bsv/providers/BsvUnspentsProvider.ts b/crypto/blockchains/bsv/providers/BsvUnspentsProvider.ts deleted file mode 100644 index 092852b68..000000000 --- a/crypto/blockchains/bsv/providers/BsvUnspentsProvider.ts +++ /dev/null @@ -1,61 +0,0 @@ -/** - * @version 0.5 - */ -import { BlocksoftBlockchainTypes } from '@crypto/blockchains/BlocksoftBlockchainTypes'; -import DogeUnspentsProvider from '@crypto/blockchains/doge/providers/DogeUnspentsProvider'; -import BtcCashUtils from '@crypto/blockchains/bch/ext/BtcCashUtils'; -import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; -import BlocksoftExternalSettings from '@crypto/common/AirDAOExternalSettings'; -import AirDAOAxios from '@crypto/common/AirDAOAxios'; -export default class BsvUnspentsProvider - extends DogeUnspentsProvider - implements BlocksoftBlockchainTypes.UnspentsProvider -{ - _isMyAddress( - voutAddress: string, - address: string, - walletHash: string - ): string { - const address2 = BtcCashUtils.fromLegacyAddress(address); - const address3 = 'bitcoincash:' + address2; - return voutAddress === address || - voutAddress === address2 || - voutAddress === address3 - ? address - : ''; - } - - async getUnspents( - address: string - ): Promise { - // @ts-ignore - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' BsvUnspentsProvider.getUnspents started ' + - address - ); - const link = - 'https://api.whatsonchain.com/v1/bsv/main/address/' + - address + - '/unspent'; - - const res = await AirDAOAxios.getWithoutBraking(link); - if (!res.data || typeof res.data[0] === 'undefined') { - return []; - } - const sortedUnspents = []; - for (const unspent of res.data) { - const unspentFormatted = { - confirmations: unspent.height, - height: unspent.height, - derivationPath: false, - vout: unspent?.tx_pos, - isRequired: false, - txid: unspent.tx_hash, - value: unspent.value - }; - sortedUnspents.push(unspentFormatted); - } - return sortedUnspents; - } -} diff --git a/crypto/blockchains/bsv/stores/BsvTmpDS.ts b/crypto/blockchains/bsv/stores/BsvTmpDS.ts deleted file mode 100644 index 441475a74..000000000 --- a/crypto/blockchains/bsv/stores/BsvTmpDS.ts +++ /dev/null @@ -1,70 +0,0 @@ -/* eslint-disable camelcase */ -import { DatabaseTable } from '@appTypes'; -import { Database } from '@database'; -import { TransactionScannersTmpDBModel } from '@database/models'; -import { Q } from '@nozbe/watermelondb'; - -const tableName = DatabaseTable.TransactionScannersTmp; - -class BsvTmpDS { - /** - * @type {string} - * @private - */ - _currencyCode = 'BSV'; - - _valKey = 'txs'; - - _isSaved = false; - - async getCache() { - const res: TransactionScannersTmpDBModel[] | undefined = - (await Database.query( - tableName, - Q.and( - Q.where('currency_code', Q.eq(this._currencyCode)), - Q.where('tmp_key', Q.eq(this._valKey)) - ) - )) as TransactionScannersTmpDBModel[]; - let tmp = {}; - const idsToRemove = []; - if (res) { - for (const row of res) { - if (typeof tmp[row.tmp_sub_key] !== 'undefined') { - idsToRemove.push(row.id); - } else { - try { - tmp[row.tmp_sub_key] = JSON.parse( - Database.unEscapeString(row.tmp_val) - ); - } catch (e) { - idsToRemove.push(row.id); - } - } - } - if (idsToRemove.length > 0) { - // TODO check this - await Database.unsafeRawQuery( - tableName, - `DELETE FROM ${tableName} WHERE id IN (${idsToRemove.join(',')})` - ); - } - } - return tmp; - } - - async saveCache(txid: string, value: unknown) { - const tmp = Database.escapeString(JSON.stringify(value)); - const now = new Date().toISOString(); - const prepared = { - currency_code: this._currencyCode, - tmp_key: this._valKey, - tmp_sub_key: txid, - tmp_val: tmp, - created_at: now - }; - await Database.createModel(DatabaseTable.TransactionScannersTmp, prepared); - } -} - -export default new BsvTmpDS(); diff --git a/crypto/blockchains/bsv/tx/BsvTxBuilder.ts b/crypto/blockchains/bsv/tx/BsvTxBuilder.ts deleted file mode 100644 index f8bfc4c7a..000000000 --- a/crypto/blockchains/bsv/tx/BsvTxBuilder.ts +++ /dev/null @@ -1,52 +0,0 @@ -/** - * @version 0.5 - */ -import { AirDAOBlockchainTypes } from '../../AirDAOBlockchainTypes'; -import DogeTxBuilder from '../../doge/tx/DogeTxBuilder'; -import BtcCashUtils from '../../bch/ext/BtcCashUtils'; -import { ECPair, payments, TransactionBuilder } from 'bitcoinjs-lib'; - -export default class BsvTxBuilder - extends DogeTxBuilder - implements AirDAOBlockchainTypes.TxBuilder -{ - _getRawTxValidateKeyPair( - privateData: AirDAOBlockchainTypes.TransferPrivateData, - data: AirDAOBlockchainTypes.TransferData - ): void { - this.keyPair = false; - try { - this.keyPair = ECPair.fromWIF( - privateData.privateKey, - this._bitcoinNetwork - ); - const address = payments.p2pkh({ - pubkey: this.keyPair.publicKey, - network: this._bitcoinNetwork - }).address; - const legacyAddress = BtcCashUtils.toLegacyAddress(data.addressFrom); - if (address !== data.addressFrom && address !== legacyAddress) { - // noinspection ExceptionCaughtLocallyJS - throw new Error( - 'not valid signing address ' + - data.addressFrom + - ' != ' + - address + - ' != ' + - legacyAddress - ); - } - } catch (e) { - e.message += ' in privateKey BSV signature check '; - throw e; - } - } - - _getRawTxAddOutput( - txb: TransactionBuilder, - output: AirDAOBlockchainTypes.OutputTx - ): void { - const to = BtcCashUtils.toLegacyAddress(output.to); - txb.addOutput(to, output.amount * 1); - } -} diff --git a/crypto/blockchains/btc/BtcScannerProcessor.ts b/crypto/blockchains/btc/BtcScannerProcessor.ts deleted file mode 100644 index 3cbf96f50..000000000 --- a/crypto/blockchains/btc/BtcScannerProcessor.ts +++ /dev/null @@ -1,566 +0,0 @@ -/** - * @version 0.9 - */ - -import BlocksoftUtils from '@crypto/common/AirDAOUtils'; -import AirDAOAxios from '@crypto/common/AirDAOAxios'; -import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; -import BlocksoftExternalSettings from '@crypto/common/AirDAOExternalSettings'; - -import BtcFindAddressFunction from './basic/BtcFindAddressFunction'; -import { Database } from '@database'; -import { DatabaseTable } from '@appTypes'; -import { Q } from '@nozbe/watermelondb'; -import TransactionFilterTypeDict from '@crypto/TransactionFilterTypeDict'; - -const CACHE_VALID_TIME = 60000; // 60 seconds -const CACHE: { [key: string]: any } = {}; // TODO fix any -const CACHE_WALLET_PUBS: { [key: string]: any } = {}; // TODO fix any - -const TIMEOUT_BTC = 60000; -const PROXY_TXS = 'https://proxy.trustee.deals/btc/getTxs'; // TODO we may need to put our BE - -export default class BtcScannerProcessor { - /** - * @type {number} - * @private - */ - _blocksToConfirm = 1; - - /** - * @type {string} - * @private - */ - _trezorServerCode = 'BTC_TREZOR_SERVER'; - - /** - * @private - */ - _trezorServer = false; - - _settings: unknown; // TODO fix type - - constructor(settings: unknown) { - this._settings = settings; - } - - /** - * @param address - * @param additionalData - * @returns {Promise} - * @private - */ - async _get( - address: string, - additionalData: { addresses: string[] }, - source = '' - ) { - const now = new Date().getTime(); - if ( - typeof CACHE[address] !== 'undefined' && - now - CACHE[address].time < CACHE_VALID_TIME - ) { - CACHE[address].provider = 'trezor-cache'; - return CACHE[address]; - } - AirDAOCryptoLog.log( - 'BtcScannerProcessor._get ' + address + ' from ' + source + ' started' - ); - - this._trezorServer = await BlocksoftExternalSettings.getTrezorServer( - this._trezorServerCode, - 'BTC.Scanner._get' - ); - - const prefix = address.substr(0, 4); - - let link = ''; - let res = false; - if (prefix === 'xpub' || prefix === 'zpub' || prefix === 'ypub') { - link = - PROXY_TXS + - '?address=' + - address + - '&type=xpub¤cyCode=' + - this._settings['currencyCode']; - res = await AirDAOAxios.getWithoutBraking(link, 5, TIMEOUT_BTC); - if ( - res && - typeof res.data !== 'undefined' && - res.data && - typeof res.data.data !== 'undefined' - ) { - res.data = res.data.data; - } else { - link = - this._trezorServer + - '/api/v2/xpub/' + - address + - '?details=txs&gap=9999&tokens=used&pageSize=40'; - try { - res = await AirDAOAxios._request( - link, - 'get', - false, - false, - true, - TIMEOUT_BTC - ); - } catch (e) { - if (e.message.indexOf('"error":"internal server error"') !== -1) { - CACHE[address] = { - data: { - balance: 0, - unconfirmedBalance: 0, - addresses: [], - specialMark: 'badServer' - }, - time: now, - provider: 'trezor-badserver' - }; - return CACHE[address]; - } - } - } - } else { - link = - PROXY_TXS + - '?address=' + - address + - '¤cyCode=' + - this._settings['currencyCode']; - res = await AirDAOAxios.getWithoutBraking(link, 5, TIMEOUT_BTC); - if ( - res && - typeof res.data !== 'undefined' && - res.data && - typeof res.data.data !== 'undefined' - ) { - res.data = res.data.data; - } else { - link = - this._trezorServer + - '/api/v2/address/' + - address + - '?details=txs&gap=9999&pageSize=80'; - res = await AirDAOAxios.getWithoutBraking(link, 5, TIMEOUT_BTC); - } - } - - if (!res || !res.data) { - await BlocksoftExternalSettings.setTrezorServerInvalid( - this._trezorServerCode, - this._trezorServer - ); - CACHE[address] = { - data: false, - time: now, - provider: 'trezor-empty' - }; - return false; - } - if (typeof res.data.balance === 'undefined') { - throw new Error( - this._settings.currencyCode + - ' BtcScannerProcessor._get nothing loaded for address ' + - link - ); - } - - const addresses = {}; - let plainAddresses = {}; - if (additionalData && additionalData.addresses) { - plainAddresses = additionalData.addresses; - } - if (typeof res.data.tokens !== 'undefined') { - let token; - for (token of res.data.tokens) { - addresses[token.name] = { - balance: token.balance, - transactions: token.transfers, - path: token.path - }; - plainAddresses[token.name] = token.path; - } - } else { - plainAddresses[address] = 1; - } - res.data.addresses = addresses; - res.data.plainAddresses = plainAddresses; - CACHE[address] = { - data: res.data, - time: now, - provider: 'trezor' - }; - return CACHE[address]; - } - - async _getPubs(walletHash) { - if (typeof CACHE_WALLET_PUBS[walletHash] !== 'undefined') { - return CACHE_WALLET_PUBS[walletHash]; - } - const sqlPub = `SELECT wallet_pub_value as walletPub - FROM wallet_pub - WHERE wallet_hash = '${walletHash}' - AND currency_code='BTC'`; - const resPub = await Database.query( - DatabaseTable.WalletPub, - Q.and( - Q.where('hash', Q.eq(walletHash)), - Q.where('currency_code', Q.eq('BTC')) - ) - ); - CACHE_WALLET_PUBS[walletHash] = {}; - if (resPub && resPub.array && resPub.array.length > 0) { - for (const row of resPub.array) { - const scanAddress = row.walletPub; - CACHE_WALLET_PUBS[walletHash][scanAddress] = 1; - } - } - return CACHE_WALLET_PUBS[walletHash]; - } - - /** - * @param {string} address - * @return {Promise<{balance:*, unconfirmed:*, provider:string}>} - */ - async getBalanceBlockchain(address, data, walletHash, source = '') { - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' BtcScannerProcessor.getBalance started ' + - address - ); - const res = await this._get(address, data, source); - if (!res) { - return false; - } - return { - address: address, - balance: res.data.balance, - unconfirmed: res.data.unconfirmedBalance, - provider: res.provider, - time: res.time, - addresses: res.data.addresses, - specialMark: - typeof res.data.specialMark !== 'undefined' - ? res.data.specialMark - : false - }; - } - - async getAddressesBlockchain(scanData, source = '') { - const address = scanData.account.address.trim(); - const data = scanData.additional; - const withBalances = - typeof scanData.withBalances !== 'undefined' && scanData.withBalances; - if (!withBalances) { - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' BtcScannerProcessor.getAddresses started withoutBalances (KSU!)', - address - ); - } else { - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' BtcScannerProcessor.getAddresses started withBalances', - address - ); - } - let res = await this._get(address, data, source); - if (typeof res.data !== 'undefined') { - res = JSON.parse(JSON.stringify(res.data)); - } else { - res = false; - } - try { - if (typeof data.walletPub !== 'undefined') { - const resPub = await this._getPubs(data.walletPub.walletHash); - for (const scanAddress in resPub) { - if (scanAddress === address) continue; - const tmp = await this._get(scanAddress, data, source + ' _getPubs1'); - if ( - typeof tmp.data === 'undefined' || - typeof tmp.data.plainAddresses === 'undefined' - ) - continue; - if (res === false || typeof res.plainAddresses === 'undefined') { - res = JSON.parse(JSON.stringify(tmp.data)); - } else { - if (withBalances) { - for (const row in tmp.data.addresses) { - res.addresses[row] = tmp.data.addresses[row]; - } - } else { - for (const row in tmp.data.plainAddresses) { - res.plainAddresses[row] = tmp.data.plainAddresses[row]; - } - } - } - } - } - } catch (e) { - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' BtcScannerProcessor.getAddresses load from all addresses error ' + - e.message - ); - } - return withBalances ? res.addresses : res.plainAddresses; - } - - /** - * @param {string} scanData.account.address - * @param {*} scanData.additional - * @param {string} scanData.account.walletHash - * @return {Promise} - */ - async getTransactionsBlockchain(scanData, source = '') { - const address = scanData.account.address.trim(); - const data = scanData.additional; - - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' BtcScannerProcessor.getTransactions started ' + - address - ); - let res = await this._get(address, data, source); - if (typeof res.data !== 'undefined') { - res = JSON.parse(JSON.stringify(res.data)); - } else { - res = false; - } - try { - if (typeof data.walletPub !== 'undefined') { - const resPub = await this._getPubs(data.walletPub.walletHash); - for (const scanAddress in resPub) { - if (scanAddress === address) continue; - const tmp = await this._get(scanAddress, data, source + ' _getPubs2'); - if ( - typeof tmp.data === 'undefined' || - typeof tmp.data.transactions === 'undefined' - ) - continue; - if (res === false || typeof res.transactions === 'undefined') { - res = JSON.parse(JSON.stringify(tmp.data)); - } else { - for (const row of tmp.data.transactions) { - res.transactions.push(row); - } - } - } - } else { - for (const scanAddress in data.addresses) { - if (scanAddress === address) continue; - const tmp = await this._get(scanAddress, data, source + ' _getOnes2'); - if ( - typeof tmp.data === 'undefined' || - typeof tmp.data.transactions === 'undefined' - ) - continue; - if (res === false || typeof res.transactions === 'undefined') { - res = tmp.data; - } else { - for (const row of tmp.data.transactions) { - res.transactions.push(row); - } - } - } - } - } catch (e) { - if (config.debug.cryptoErrors) { - console.log( - this._settings.currencyCode + - ' BtcScannerProcessor.getTransactions load from all addresses error ' + - e.message, - e - ); - } - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' BtcScannerProcessor.getTransactions load from all addresses error ' + - e.message - ); - } - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' BtcScannerProcessor.getTransactions loaded from ' + - res.provider + - ' ' + - res.time - ); - - if (typeof res.transactions === 'undefined' || !res.transactions) return []; - const transactions = []; - const addresses = res.plainAddresses; - if ( - typeof data !== 'undefined' && - data && - typeof data.addresses !== 'undefined' - ) { - for (const tmp in data.addresses) { - addresses[tmp] = data.addresses[tmp]; - } - } - if (typeof scanData.additional.addresses !== 'undefined') { - for (const tmp in scanData.additional.addresses) { - address[tmp] = tmp; - } - } - - const vinsOrder = {}; - for (const tx of res.transactions) { - vinsOrder[tx.txid] = tx.blockTime; - } - - let plussed = false; - let i = 0; - do { - for (const tx of res.transactions) { - if (typeof tx.vin === 'undefined' || tx.vin.length === 0) continue; - for (const vin of tx.vin) { - if (typeof vinsOrder[vin.txid] === 'undefined') { - continue; - } - const newTime = vinsOrder[vin.txid] + 1; - if (tx.blockTime < newTime) { - tx.blockTime = newTime; - plussed = true; - } - vinsOrder[tx.txid] = tx.blockTime; - } - } - i++; - } while (plussed && i < 100); - - const uniqueTxs = {}; - for (const tx of res.transactions) { - const transaction = await this._unifyTransaction(address, addresses, tx); - if (transaction) { - if (typeof uniqueTxs[transaction.transactionHash] !== 'undefined') - continue; - uniqueTxs[transaction.transactionHash] = 1; - transactions.push(transaction); - } - } - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' BtcScannerProcessor.getTransactions finished ' + - address + - ' total: ' + - transactions.length - ); - return transactions; - } - - /** - * - * @param {string} address - * @param {string} addresses - * @param {Object} transaction - * @param {string} transaction.txid c6b4c3879196857bed7fd5b553dd0049486c032d6a1be72b98fda967ca54b2da - * @param {string} transaction.version 1 - * @param {string} transaction.vin[].txid aa31777a9db759f57fd243ef47419939f233d16bc3e535e9a1c5af3ace87cb54 - * @param {string} transaction.vin[].sequence 4294967294 - * @param {string} transaction.vin[].n 0 - * @param {string} transaction.vin[].addresses [ 'DFDn5QyHH9DiFBNFGMcyJT5uUpDvmBRDqH' ] - * @param {string} transaction.vin[].value 44400000000 - * @param {string} transaction.vin[].hex 47304402200826f97d3432452abedd4346553de0b0c2d401ad7056b155e6462484afd98aa902202b5fb3166b96ded33249aecad7c667c0870c1 - * @param {string} transaction.vout[].value 59999824800 - * @param {string} transaction.vout[].n 0 - * @param {string} transaction.vout[].spent true - * @param {string} transaction.vout[].hex 76a91456d49605503d4770cf1f32fbfb69676d9a72554f88ac - * @param {string} transaction.vout[].addresses [ 'DD4DKVTEkRUGs7qzN8b7q5LKmoE9mXsJk4' ] - * @param {string} transaction.blockHash fc590834c04812e1c7818024a94021e12c4d8ab905724b4a4fdb4d4732878f69 - * @param {string} transaction.blockHeight 3036225 - * @param {string} transaction.confirmations 8568 - * @param {string} transaction.blockTime 1577362993 - * @param {string} transaction.value 59999917700 - * @param {string} transaction.valueIn 59999917700 - * @param {string} transaction.fees 0 - * @param {string} transaction.hex 010000000654cb87ce3aafc5a1e935e5c36bd133f239 - * @return {Promise} - * @private - */ - async _unifyTransaction( - address: string, - addresses: string, - transaction: { - blockHash: string; - blockHeight: number; - confirmations: number; - blockTime: number; - value: number; - valueIn: number; - fees: number; - hex: string; - txid: string; - version: number; - vin: { - txid: string; - sequence: number; - n: number; - addresses: string[]; - value: number; - hex: string; - }[]; - vout: { - value: number; - n: number; - spent: boolean; - hex: string; - addresses: string[]; - }[]; - } - ) { - let showAddresses = false; - try { - showAddresses = await BtcFindAddressFunction(addresses, transaction); - } catch (e) { - e.message += - ' transaction hash ' + - JSON.stringify(transaction) + - ' address ' + - address; - throw e; - } - - let transactionStatus = 'new'; - if (transaction.confirmations >= this._blocksToConfirm) { - transactionStatus = 'success'; - } else if (transaction.confirmations > 0) { - transactionStatus = 'confirming'; - } - - let transactionFilterType = TransactionFilterTypeDict.USUAL; - if ( - typeof showAddresses.to !== 'undefined' && - showAddresses.to.toLowerCase().indexOf('simple send') !== -1 - ) { - transactionFilterType = TransactionFilterTypeDict.FEE; - } - - let formattedTime; - try { - formattedTime = BlocksoftUtils.toDate(transaction.blockTime); - } catch (e) { - e.message += - ' timestamp error transaction data ' + JSON.stringify(transaction); - throw e; - } - - return { - transactionHash: transaction.txid, - blockHash: transaction.blockHash, - blockNumber: +transaction.blockHeight, - blockTime: formattedTime, - blockConfirmations: transaction.confirmations, - transactionDirection: showAddresses.direction, - addressFrom: showAddresses.from, - addressTo: showAddresses.to, - addressAmount: showAddresses.value, - transactionStatus: transactionStatus, - transactionFee: transaction.fees, - transactionFilterType - }; - } -} diff --git a/crypto/blockchains/btc/BtcTransferProcessor.ts b/crypto/blockchains/btc/BtcTransferProcessor.ts deleted file mode 100644 index 45edfdafd..000000000 --- a/crypto/blockchains/btc/BtcTransferProcessor.ts +++ /dev/null @@ -1,48 +0,0 @@ -/** - * @version 0.20 - */ -import { AirDAOBlockchainTypes } from '../AirDAOBlockchainTypes'; -import DogeTransferProcessor from '../doge/DogeTransferProcessor'; -import BtcUnspentsProvider from './providers/BtcUnspentsProvider'; -import DogeSendProvider from '../doge/providers/DogeSendProvider'; -import BtcTxInputsOutputs from './tx/BtcTxInputsOutputs'; -import BtcTxBuilder from './tx/BtcTxBuilder'; -import BtcNetworkPrices from './basic/BtcNetworkPrices'; - -export default class BtcTransferProcessor - extends DogeTransferProcessor - implements AirDAOBlockchainTypes.TransferProcessor -{ - _trezorServerCode = 'BTC_TREZOR_SERVER'; - - _builderSettings: AirDAOBlockchainTypes.BuilderSettings = { - minOutputDustReadable: 0.000001, - minChangeDustReadable: 0.000001, - feeMaxForByteSatoshi: 1000, // for tx builder - feeMaxAutoReadable2: 0.01, // for fee calc, - feeMaxAutoReadable6: 0.005, // for fee calc - feeMaxAutoReadable12: 0.001, // for fee calc - changeTogether: true, - minRbfStepSatoshi: 30, - minSpeedUpMulti: 1.7 - }; - - _initProviders() { - if (this._initedProviders) return false; - this.unspentsProvider = new BtcUnspentsProvider( - this._settings, - this._trezorServerCode - ); - this.sendProvider = new DogeSendProvider( - this._settings, - this._trezorServerCode - ); - this.txPrepareInputsOutputs = new BtcTxInputsOutputs( - this._settings, - this._builderSettings - ); - this.txBuilder = new BtcTxBuilder(this._settings, this._builderSettings); - this.networkPrices = new BtcNetworkPrices(); - this._initedProviders = true; - } -} diff --git a/crypto/blockchains/btc/address/BtcAddressProcessor.js b/crypto/blockchains/btc/address/BtcAddressProcessor.js deleted file mode 100644 index 5b6b9fa35..000000000 --- a/crypto/blockchains/btc/address/BtcAddressProcessor.js +++ /dev/null @@ -1,42 +0,0 @@ -/** - * @version 0.5 - */ -import bitcoin from 'bitcoinjs-lib'; - -import networksConstants from '../../../common/ext/networks-constants'; - -export default class BtcAddressProcessor { - constructor(settings) { - if (typeof settings === 'undefined' || !settings) { - throw new Error('BtcAddressProcessor requires settings'); - } - if (typeof settings.network === 'undefined') { - throw new Error('BtcAddressProcessor requires settings.network'); - } - if (typeof networksConstants[settings.network] === 'undefined') { - throw new Error( - 'while retrieving Bitcoin address - unknown Bitcoin network specified. Got : ' + - settings.network - ); - } - this._currentBitcoinNetwork = networksConstants[settings.network].network; - } - - setBasicRoot(root) {} - - /** - * @param {string|Buffer} privateKey - * @param {*} data - * @returns {Promise<{privateKey: string, address: string, addedData: *}>} - */ - async getAddress(privateKey, data = {}) { - const keyPair = bitcoin.ECPair.fromPrivateKey(privateKey, { - network: this._currentBitcoinNetwork - }); - const address = bitcoin.payments.p2pkh({ - pubkey: keyPair.publicKey, - network: this._currentBitcoinNetwork - }).address; - return { address, privateKey: keyPair.toWIF() }; - } -} diff --git a/crypto/blockchains/btc/address/BtcSegwitAddressProcessor.js b/crypto/blockchains/btc/address/BtcSegwitAddressProcessor.js deleted file mode 100644 index 1beb04333..000000000 --- a/crypto/blockchains/btc/address/BtcSegwitAddressProcessor.js +++ /dev/null @@ -1,24 +0,0 @@ -/** - * @version 0.5 - */ -import BtcAddressProcessor from './BtcAddressProcessor'; - -import bitcoin from 'bitcoinjs-lib'; - -export default class BtcSegwitAddressProcessor extends BtcAddressProcessor { - /** - * @param {string|Buffer} privateKey - * @param {*} data - * @returns {Promise<{privateKey: string, address: string, addedData: *}>} - */ - async getAddress(privateKey, data = {}) { - const keyPair = bitcoin.ECPair.fromPrivateKey(privateKey, { - network: this._currentBitcoinNetwork - }); - const address = bitcoin.payments.p2wpkh({ - pubkey: keyPair.publicKey, - network: this._currentBitcoinNetwork - }).address; - return { address, privateKey: keyPair.toWIF() }; - } -} diff --git a/crypto/blockchains/btc/address/BtcSegwitCompatibleAddressProcessor.js b/crypto/blockchains/btc/address/BtcSegwitCompatibleAddressProcessor.js deleted file mode 100644 index f16ae8e21..000000000 --- a/crypto/blockchains/btc/address/BtcSegwitCompatibleAddressProcessor.js +++ /dev/null @@ -1,23 +0,0 @@ -/** - * @version 0.5 - */ -import BtcAddressProcessor from './BtcAddressProcessor'; - -import bitcoin from 'bitcoinjs-lib'; - -export default class BtcSegwitCompatibleAddressProcessor extends BtcAddressProcessor { - /** - * @param {string|Buffer} privateKey - * @param {*} data - * @returns {Promise<{privateKey: string, address: string, addedData: *}>} - */ - async getAddress(privateKey, data = {}) { - const keyPair = bitcoin.ECPair.fromPrivateKey(privateKey, { - network: this._currentBitcoinNetwork - }); - const address = bitcoin.payments.p2sh({ - redeem: bitcoin.payments.p2wpkh({ pubkey: keyPair.publicKey }) - }).address; - return { address, privateKey: keyPair.toWIF() }; - } -} diff --git a/crypto/blockchains/btc/basic/BtcFindAddressFunction.ts b/crypto/blockchains/btc/basic/BtcFindAddressFunction.ts deleted file mode 100644 index 95b78bed2..000000000 --- a/crypto/blockchains/btc/basic/BtcFindAddressFunction.ts +++ /dev/null @@ -1,159 +0,0 @@ -/** - * @version 0.5 - * @param {string} addresses[] - * @param {string} transaction.hex - * @param {string} transaction.address - * @param {string} transaction.vin[].txid - * @param {string} transaction.vin[].sequence - * @param {string} transaction.vin[].n 0 - * @param {string} transaction.vin[].addresses[] - * @param {string} transaction.vin[].addr - * @param {string} transaction.vin[].value - * @param {string} transaction.vin[].hex - * @param {string} transaction.vout[].value - * @param {string} transaction.vout[].n 0 - * @param {string} transaction.vout[].spent - * @param {string} transaction.vout[].hex - * @param {string} transaction.vout[].addresses[] - * @param {string} transaction.vout[].scriptPubKey.addresses[] - * @returns {Promise<{from: string, to: string, value: number, direction: string}>} - * @constructor - */ -import BlocksoftBN from '../../../common/AirDAOBN'; - -export default async function BtcFindAddressFunction( - indexedAddresses: string[], - transaction: { - hex: string; - address: string; - vin: { - txid: string; - sequence: string; - n: number; - addresses: string[]; - addr: string; - value: string; - hex: string; - }[]; - vout: { - value: string; - n: number; - spent: string; - hex: string; - addresses: string[]; - scriptPubKey: { addresses: string[] }; - }[]; - } -) { - const inputMyBN = new BlocksoftBN(0); - const inputOthersBN = new BlocksoftBN(0); - let inputMyAddress = ''; - const inputOthersAddresses = []; - const uniqueTmp = {}; - if (transaction.vin) { - for (let i = 0, ic = transaction.vin.length; i < ic; i++) { - let vinAddress; - const vinValue = transaction.vin[i].value; - if (typeof transaction.vin[i].addresses !== 'undefined') { - vinAddress = transaction.vin[i].addresses[0]; - } else if (typeof transaction.vin[i].addr !== 'undefined') { - vinAddress = transaction.vin[i].addr; - } - if (!vinAddress) continue; - if (vinAddress.indexOf('OP_RETURN (omni') !== -1) { - vinAddress = 'OMNI'; - } - if (typeof indexedAddresses[vinAddress] !== 'undefined') { - inputMyBN.add(vinValue); - inputMyAddress = vinAddress; - } else { - if (typeof uniqueTmp[vinAddress] === 'undefined') { - uniqueTmp[vinAddress] = 1; - inputOthersAddresses.push(vinAddress); - } - inputOthersBN.add(vinValue); - } - } - } - - const outputMyBN = new BlocksoftBN(0); - const outputOthersBN = new BlocksoftBN(0); - - let outputMyAddress = ''; - const allMyAddresses = []; - const outputOthersAddresses = []; - const uniqueTmp2 = {}; - if (transaction.vout) { - for (let j = 0, jc = transaction.vout.length; j < jc; j++) { - let voutAddress; - const voutValue = transaction.vout[j].value; - if (typeof transaction.vout[j].addresses !== 'undefined') { - voutAddress = transaction.vout[j].addresses[0]; - } else if ( - typeof transaction.vout[j].scriptPubKey !== 'undefined' && - typeof transaction.vout[j].scriptPubKey.addresses !== 'undefined' - ) { - voutAddress = transaction.vout[j].scriptPubKey.addresses[0]; - } - if (voutAddress.indexOf('OP_RETURN (omni') !== -1) { - voutAddress = 'OMNI'; - } - - if (typeof indexedAddresses[voutAddress] !== 'undefined') { - outputMyBN.add(voutValue); - outputMyAddress = voutAddress; - allMyAddresses.push(outputMyAddress); - } else { - if (typeof uniqueTmp2[voutAddress] === 'undefined') { - uniqueTmp2[voutAddress] = 1; - outputOthersAddresses.push(voutAddress); - } - outputOthersBN.add(voutValue); - } - } - } - - let output; - if (inputMyBN.get() === '0') { - // my only in output - output = { - direction: 'income', - from: - inputOthersAddresses.length > 0 ? inputOthersAddresses.join(',') : '', - to: '', // outputMyAddress, - value: outputMyBN.get() - }; - } else if (outputMyBN.get() === '0') { - // my only in input - output = { - direction: 'outcome', - from: '', // inputMyAddress, - to: - outputOthersAddresses.length > 0 ? outputOthersAddresses.join(',') : '', - value: - inputOthersBN.get() === '0' ? outputOthersBN.get() : inputMyBN.get() - }; - } else { - // both input and output - if (outputOthersAddresses.length > 0) { - // there are other address - output = { - direction: 'outcome', - from: '', // inputMyAddress, - to: outputOthersAddresses.join(','), - value: outputOthersBN.get() - }; - } else { - output = { - direction: 'self', - from: '', // inputMyAddress, - to: '', // outputMyAddress, - value: Math.abs(inputMyBN.diff(outputMyBN).get()) - }; - } - } - output.from = output.from.substr(0, 255); - output.to = output.to.substr(0, 255); - output.allMyAddresses = allMyAddresses; - return output; -} diff --git a/crypto/blockchains/btc/basic/BtcNetworkPrices.ts b/crypto/blockchains/btc/basic/BtcNetworkPrices.ts deleted file mode 100644 index f89de5c32..000000000 --- a/crypto/blockchains/btc/basic/BtcNetworkPrices.ts +++ /dev/null @@ -1,314 +0,0 @@ -/* eslint-disable camelcase */ -/** - * @version 0.20 - **/ -import { AirDAOBlockchainTypes } from '../../AirDAOBlockchainTypes'; -import AirDAOCryptoLog from '../../../common/AirDAOCryptoLog'; -import AirDAOAxios from '../../../common/AirDAOAxios'; -import BlocksoftExternalSettings from '../../../common/AirDAOExternalSettings'; - -import BlocksoftUtils from '../../../common/AirDAOUtils'; - -const ESTIMATE_PATH = 'https://mempool.space/api/v1/fees/recommended'; - -const CACHE_VALID_TIME = 60000; // 1 minute - -let CACHE_FEES_BTC_TIME = 0; -let CACHE_FEES_BTC = { - speed_blocks_2: 0, - speed_blocks_6: 0, - speed_blocks_12: 0 -}; - -let CACHE_PREV_DATA = { - fastestFee: 19, - halfHourFee: 3, - hourFee: 2, - lastBlock: 0, - timeFromBlock: 0, - timeFromBlockDiff: 0, - mempoolSize: 0 -}; - -let CACHE_PREV_PREV_DATA = { - fastestFee: 19, - halfHourFee: 3, - hourFee: 2, - lastBlock: 0, - timeFromBlock: 0, - timeFromBlockDiff: 0, - mempoolSize: 0 -}; - -export default class BtcNetworkPrices - implements AirDAOBlockchainTypes.NetworkPrices -{ - private _trezorServerCode = 'BTC_TREZOR_SERVER'; - private _trezorServer: any; - - async getNetworkPrices(currencyCode: string): Promise<{ - speed_blocks_2: number; - speed_blocks_6: number; - speed_blocks_12: number; - }> { - AirDAOCryptoLog.log('BtcNetworkPricesProvider ' + currencyCode); - const logData = { - currencyCode, - source: 'fromCache', - cacheTime: CACHE_FEES_BTC_TIME + '', - fee: JSON.stringify(CACHE_FEES_BTC) - }; - const now = new Date().getTime(); - if (CACHE_FEES_BTC && now - CACHE_FEES_BTC_TIME < CACHE_VALID_TIME) { - // @ts-ignore - AirDAOCryptoLog.log( - 'BtcNetworkPricesProvider ' + - currencyCode + - ' used cache ' + - JSON.stringify(CACHE_FEES_BTC) - ); - return CACHE_FEES_BTC; - } - - AirDAOCryptoLog.log( - 'BtcNetworkPricesProvider ' + currencyCode + ' no cache load' - ); - - let link = `${ESTIMATE_PATH}`; - let timeFromBlock = false; - let timeFromBlockDiff = false; - let lastBlock = 0; - let mempoolSize = 0; - if (currencyCode !== 'BTC_TEST') { - this._trezorServer = await BlocksoftExternalSettings.getTrezorServer( - this._trezorServerCode, - 'BTC.NetworkPrices' - ); - const linkTrezor = this._trezorServer + '/api/'; - - let tmp = false; - try { - tmp = await AirDAOAxios.getWithoutBraking(linkTrezor); - if (tmp && tmp.data) { - lastBlock = tmp.data.blockbook.bestHeight; - mempoolSize = tmp.data.blockbook.mempoolSize; - timeFromBlock = tmp.data.blockbook.lastBlockTime; - timeFromBlockDiff = now - new Date(timeFromBlock).getTime(); - } - } catch (e) {} - - AirDAOCryptoLog.log( - 'BtcNetworkPricesProvider lastBlock ' + - lastBlock + - ' mempool ' + - mempoolSize + - ' timeFromBlock ' + - timeFromBlock + - ' diff ' + - timeFromBlockDiff - ); - - tmp = false; - try { - tmp = await AirDAOAxios.getWithoutBraking(link); - if (tmp && tmp.data) { - logData.source = 'reloaded'; - if (lastBlock > CACHE_PREV_DATA.lastBlock) { - CACHE_PREV_PREV_DATA = CACHE_PREV_DATA; - } - CACHE_PREV_DATA = tmp.data; - CACHE_PREV_DATA.lastBlock = lastBlock; - CACHE_PREV_DATA.mempoolSize = mempoolSize; - CACHE_PREV_DATA.timeFromBlock = timeFromBlock; - CACHE_PREV_DATA.timeFromBlockDiff = timeFromBlockDiff; - } else { - logData.source = 'fromLoadCache'; - link = 'prev'; - } - } catch (e) { - // do nothing - } - } - // @ts-ignore - AirDAOCryptoLog.log('BtcNetworkPricesProvider CACHE_FEES', { - CACHE_PREV_DATA, - CACHE_PREV_PREV_DATA, - ...logData - }); - - try { - const cachedWithTime = CACHE_PREV_DATA; - if (timeFromBlock) { - if (timeFromBlockDiff < 1000 && cachedWithTime.fastestFee === '4') { - // 1 minute from block - if (cachedWithTime.fastestFee < CACHE_PREV_PREV_DATA.fastestFee) { - cachedWithTime.fastestFee = CACHE_PREV_PREV_DATA.fastestFee; - // @ts-ignore - AirDAOCryptoLog.log( - 'BtcNetworkPricesProvider change as block 1 minute ago and fastest no ok - used prev ', - CACHE_PREV_PREV_DATA - ); - } else if (CACHE_PREV_DATA.mempoolSize < 10000) { - cachedWithTime.fastestFee = cachedWithTime.fastestFee * 1.5; - AirDAOCryptoLog.log( - 'BtcNetworkPricesProvider change as block 1 minute ago and fastest no ok - mempool is small' - ); - } else { - cachedWithTime.fastestFee = cachedWithTime.fastestFee * 3; - AirDAOCryptoLog.log( - 'BtcNetworkPricesProvider change as block 1 minute ago and fastest no ok - mempool is ok' - ); - } - } else if (timeFromBlockDiff < 5000) { - // 2 minute from block - if (cachedWithTime.fastestFee < CACHE_PREV_PREV_DATA.fastestFee) { - cachedWithTime.fastestFee = CACHE_PREV_PREV_DATA.fastestFee; - // @ts-ignore - AirDAOCryptoLog.log( - 'BtcNetworkPricesProvider change as block 5 minute ago and fastest no ok - used prev ', - CACHE_PREV_PREV_DATA - ); - } - } - } - await this._parseLoaded(currencyCode, cachedWithTime, link); - } catch (e) { - // do nothing - } - return CACHE_FEES_BTC; - } - - async _parseLoaded( - currencyCode: string, - json: { - fastestFee: any; - halfHourFee: any; - hourFee: any; - lastBlock?: number; - timeFromBlock?: number; - timeFromBlockDiff?: number; - mempoolSize?: number; - }, - link: string - ) { - CACHE_FEES_BTC = { - speed_blocks_2: 0, - speed_blocks_6: 0, - speed_blocks_12: 0 - }; - - const externalSettings = await BlocksoftExternalSettings.getAll( - 'BTC.getNetworkPrices' - ); - addMultiply(2, json.fastestFee * 1, externalSettings); - addMultiply(6, json.halfHourFee * 1, externalSettings); - addMultiply(12, json.hourFee * 1, externalSettings); - - if (CACHE_FEES_BTC.speed_blocks_2 === 1) { - CACHE_FEES_BTC.speed_blocks_2 = 4; - } - if (CACHE_FEES_BTC.speed_blocks_6 === CACHE_FEES_BTC.speed_blocks_2) { - if (CACHE_FEES_BTC.speed_blocks_12 === CACHE_FEES_BTC.speed_blocks_6) { - CACHE_FEES_BTC.speed_blocks_6 = Math.round( - CACHE_FEES_BTC.speed_blocks_6 / 2 - ); - CACHE_FEES_BTC.speed_blocks_12 = Math.round( - CACHE_FEES_BTC.speed_blocks_6 / 2 - ); - } else { - CACHE_FEES_BTC.speed_blocks_6 = Math.round( - CACHE_FEES_BTC.speed_blocks_2 / 2 - ); - } - } else if ( - CACHE_FEES_BTC.speed_blocks_12 === CACHE_FEES_BTC.speed_blocks_6 - ) { - CACHE_FEES_BTC.speed_blocks_12 = Math.round( - CACHE_FEES_BTC.speed_blocks_6 / 2 - ); - } - - if ( - CACHE_FEES_BTC.speed_blocks_12 === 0 || - CACHE_FEES_BTC.speed_blocks_6 === 1 - ) { - CACHE_FEES_BTC.speed_blocks_12 = 1; - if (CACHE_FEES_BTC.speed_blocks_6 === 1) { - CACHE_FEES_BTC.speed_blocks_6 = 2; - } - } - - if (CACHE_FEES_BTC.speed_blocks_6 < CACHE_FEES_BTC.speed_blocks_12) { - const t = CACHE_FEES_BTC.speed_blocks_6; - CACHE_FEES_BTC.speed_blocks_6 = CACHE_FEES_BTC.speed_blocks_12; - CACHE_FEES_BTC.speed_blocks_12 = t; - } - - if (CACHE_FEES_BTC.speed_blocks_2 < CACHE_FEES_BTC.speed_blocks_6) { - const t = CACHE_FEES_BTC.speed_blocks_6; - CACHE_FEES_BTC.speed_blocks_6 = CACHE_FEES_BTC.speed_blocks_2; - CACHE_FEES_BTC.speed_blocks_2 = t; - } - - if (CACHE_FEES_BTC.speed_blocks_6 < CACHE_FEES_BTC.speed_blocks_12) { - const t = CACHE_FEES_BTC.speed_blocks_6; - CACHE_FEES_BTC.speed_blocks_6 = CACHE_FEES_BTC.speed_blocks_12; - CACHE_FEES_BTC.speed_blocks_12 = t; - } - - CACHE_FEES_BTC_TIME = new Date().getTime(); - if (CACHE_FEES_BTC.speed_blocks_2 > 0) { - AirDAOCryptoLog.log( - 'BtcNetworkPricesProvider ' + currencyCode + ' new cache fees', - CACHE_FEES_BTC - ); - } else { - // noinspection ES6MissingAwait - } - } -} - -function addMultiply(blocks, fee, externalSettings) { - const key = 'speed_blocks_' + blocks; - if ( - typeof externalSettings['BTC_CURRENT_PRICE_' + blocks] !== 'undefined' && - externalSettings['BTC_CURRENT_PRICE_' + blocks] > 0 - ) { - CACHE_FEES_BTC[key] = externalSettings['BTC_CURRENT_PRICE_' + blocks]; - } else if ( - typeof externalSettings['BTC_MULTI_V3_' + blocks] !== 'undefined' && - externalSettings['BTC_MULTI_V3_' + blocks] > 0 - ) { - CACHE_FEES_BTC[key] = - BlocksoftUtils.mul(fee, externalSettings['BTC_MULTI_V3_' + blocks]) * 1; - } else if ( - typeof externalSettings.BTC_MULTI_V3 !== 'undefined' && - externalSettings.BTC_MULTI_V3 > 0 - ) { - CACHE_FEES_BTC[key] = - BlocksoftUtils.mul(fee, externalSettings.BTC_MULTI_V3) * 1; - AirDAOCryptoLog.log('BtcNetworkPricesProvider addMultiply result', { - blocks, - fee, - mul: externalSettings.BTC_MULTI_V3, - res: CACHE_FEES_BTC[key] - }); - } else { - CACHE_FEES_BTC[key] = fee; - } - if ( - typeof externalSettings['BTC_MIN_' + blocks] !== 'undefined' && - externalSettings['BTC_MIN_' + blocks] > 0 - ) { - if (externalSettings['BTC_MIN_' + blocks] > CACHE_FEES_BTC[key]) { - CACHE_FEES_BTC[key] = externalSettings['BTC_MIN_' + blocks]; - } - } else if ( - typeof externalSettings.BTC_MIN !== 'undefined' && - externalSettings.BTC_MIN > 0 - ) { - if (externalSettings.BTC_MIN > CACHE_FEES_BTC[key]) { - CACHE_FEES_BTC[key] = externalSettings.BTC_MIN; - } - } -} diff --git a/crypto/blockchains/btc/providers/BtcUnspentsProvider.ts b/crypto/blockchains/btc/providers/BtcUnspentsProvider.ts deleted file mode 100644 index 2a21bfe85..000000000 --- a/crypto/blockchains/btc/providers/BtcUnspentsProvider.ts +++ /dev/null @@ -1,364 +0,0 @@ -/** - * @version 0.20 - * https://github.com/trezor/blockbook/blob/master/docs/api.md - * https://doge1.trezor.io/api/v2/utxo/D5oKvWEibVe74CXLASmhpkRpLoyjgZhm71 - */ -import { AirDAOBlockchainTypes } from '../../AirDAOBlockchainTypes'; -import DogeUnspentsProvider from '../../doge/providers/DogeUnspentsProvider'; - -// import Database from '@app/appstores/DataSource/Database'; -import { Database } from '@database'; -import AirDAOCryptoLog from '../../../common/AirDAOCryptoLog'; -import AirDAODict from '@crypto/common/AirDAODict'; -import { Q } from '@nozbe/watermelondb'; -import { DatabaseTable } from '@appTypes'; -import { AccountDBModel } from '@database/models/account'; -import { WalletPubDBModel } from '@database/models/wallet-pub'; - -const CACHE_FOR_CHANGE: { [key: string]: any } = {}; - -export default class BtcUnspentsProvider - extends DogeUnspentsProvider - implements AirDAOBlockchainTypes.UnspentsProvider -{ - static async getCache(walletHash: string, currencyCode = 'BTC') { - if (typeof CACHE_FOR_CHANGE[walletHash] !== 'undefined') { - return CACHE_FOR_CHANGE[walletHash]; - } - const mainCurrencyCode = currencyCode === 'LTC' ? 'LTC' : 'BTC'; - const segwitPrefix = - AirDAODict.CurrenciesForTests[mainCurrencyCode + '_SEGWIT'].addressPrefix; - - AirDAOCryptoLog.log( - currencyCode + - ' ' + - mainCurrencyCode + - ' BtcUnspentsProvider.getCache ' + - walletHash + - ' started as ' + - JSON.stringify(CACHE_FOR_CHANGE[walletHash]) - ); - - // const sqlPub = `SELECT wallet_pub_value as walletPub - // FROM wallet_pub - // WHERE wallet_hash = '${walletHash} - // AND currency_code='${mainCurrencyCode}' - // `; - const resPub = (await Database.query( - DatabaseTable.WalletPub, - Q.and( - Q.where('hash', Q.eq(walletHash)), - Q.where('currency_code', Q.eq(mainCurrencyCode)) - ) - )) as WalletPubDBModel[]; - if (resPub && resPub && resPub.length > 0) { - // const sql = `SELECT account.address - // FROM account - // WHERE account.wallet_hash = '${walletHash} - // AND currency_code='${mainCurrencyCode}' AND (already_shown IS NULL OR already_shown=0) - // AND derivation_type!='main' - // ORDER BY derivation_index ASC - // `; - const res = (await Database.query( - DatabaseTable.Accounts, - Q.and( - Q.where('hash', Q.eq(walletHash)), - Q.where('currency_code', Q.eq(mainCurrencyCode)), - Q.or( - Q.where('already_shown', Q.eq(null)), - Q.where('already_shown', Q.eq(0)) - ), - Q.where('derivation_type', Q.notEq('main')) - ), - Q.sortBy('derivation_index', 'asc') - )) as AccountDBModel[]; - for (const row of res) { - const prefix = - row.address.indexOf(segwitPrefix) === 0 - ? segwitPrefix - : row.address.substr(0, 1); - await AirDAOCryptoLog.log( - currencyCode + - ' ' + - mainCurrencyCode + - ' BtcUnspentsProvider.getCache started HD CACHE_FOR_CHANGE ' + - walletHash - ); - // @ts-ignore - if (typeof CACHE_FOR_CHANGE[walletHash] === 'undefined') { - // @ts-ignore - CACHE_FOR_CHANGE[walletHash] = {}; - } - // @ts-ignore - if ( - typeof CACHE_FOR_CHANGE[walletHash][prefix] === 'undefined' || - CACHE_FOR_CHANGE[walletHash][prefix] === '' - ) { - // @ts-ignore - CACHE_FOR_CHANGE[walletHash][prefix] = row.address; - // @ts-ignore - await AirDAOCryptoLog.log( - currencyCode + - ' ' + - mainCurrencyCode + - ' BtcUnspentsProvider.getCache started HD CACHE_FOR_CHANGE ' + - walletHash + - ' ' + - prefix + - ' changed ' + - JSON.stringify(CACHE_FOR_CHANGE[walletHash]) - ); - } - } - } else { - // const sql = `SELECT account.address - // FROM account - // WHERE account.wallet_hash = '${walletHash}' - // AND currency_code='${mainCurrencyCode}' - // `; - const res = (await Database.query( - DatabaseTable.Accounts, - Q.and( - Q.where('hash', Q.eq(walletHash)), - Q.where('currency_code', mainCurrencyCode) - ) - )) as AccountDBModel[]; - for (const row of res) { - // @ts-ignore - await AirDAOCryptoLog.log( - currencyCode + - '/' + - mainCurrencyCode + - ' BtcUnspentsProvider.getUnspents started CACHE_FOR_CHANGE ' + - walletHash - ); - if (typeof CACHE_FOR_CHANGE[walletHash] === 'undefined') { - // @ts-ignore - CACHE_FOR_CHANGE[walletHash] = {}; - } - const prefix = - row.address.indexOf(segwitPrefix) === 0 - ? segwitPrefix - : row.address.substr(0, 1); - // @ts-ignore - CACHE_FOR_CHANGE[walletHash][prefix] = row.address; - } - } - if (typeof CACHE_FOR_CHANGE[walletHash] === 'undefined') { - throw new Error( - currencyCode + - '/' + - mainCurrencyCode + - ' BtcUnspentsProvider no CACHE_FOR_CHANGE retry for ' + - walletHash - ); - } - return CACHE_FOR_CHANGE[walletHash]; - } - - _isMyAddress( - voutAddress: string, - address: string, - walletHash: string - ): string { - // @ts-ignore - if ( - typeof CACHE_FOR_CHANGE[walletHash] === 'undefined' || - !CACHE_FOR_CHANGE[walletHash] - ) { - return ''; - } - // @ts-ignore - let found = ''; - for (const key in CACHE_FOR_CHANGE[walletHash]) { - AirDAOCryptoLog.log( - 'CACHE_FOR_CHANGE[walletHash][key]', - key + '_' + CACHE_FOR_CHANGE[walletHash][key] - ); - if (voutAddress === CACHE_FOR_CHANGE[walletHash][key]) { - found = voutAddress; - } - } - return found; - } - - async getUnspents( - address: string - ): Promise { - const mainCurrencyCode = - this._settings.currencyCode === 'LTC' ? 'LTC' : 'BTC'; - const segwitPrefix = - AirDAODict.CurrenciesForTests[mainCurrencyCode + '_SEGWIT'].addressPrefix; - - const sqlPub = `SELECT wallet_pub_value as walletPubValue - FROM ${DatabaseTable.WalletPub} - WHERE hash = (SELECT hash FROM ${DatabaseTable.Accounts} WHERE address='${address}') - AND currency_code='${mainCurrencyCode}' - `; - const totalUnspents = []; - // const resPub = await Database.query(sqlPub); - const resPub = (await Database.unsafeRawQuery( - DatabaseTable.WalletPub, - sqlPub - )) as WalletPubDBModel[]; - if (resPub && resPub && resPub.length > 0) { - for (const row of resPub) { - const unspents = await super.getUnspents(row.walletPubValue); - if (unspents) { - for (const unspent of unspents) { - totalUnspents.push(unspent); - } - } - } - const sqlAdditional = `SELECT account.address, account.derivation_path as derivationPath, hash - FROM ${DatabaseTable.Accounts} - WHERE account.hash = (SELECT hash FROM ${DatabaseTable.Accounts} WHERE address='${address}') - AND account.derivation_path = 'm/49quote/0quote/0/1/0' - AND currency_code='${mainCurrencyCode}' - `; - const resAdditional = (await Database.unsafeRawQuery( - DatabaseTable.Accounts, - sqlAdditional - )) as AccountDBModel[]; - if (resAdditional && resAdditional && resAdditional.length > 0) { - for (const row of resAdditional) { - const unspents = await super.getUnspents(row.address); - if (unspents) { - for (const unspent of unspents) { - unspent.address = row.address; - unspent.derivationPath = Database.unEscapeString( - row.derivationPath - ); - totalUnspents.push(unspent); - } - } - } - } - - const sql = `SELECT account.address, account.derivation_path as derivationPath, hash - FROM account - WHERE account.hash = (SELECT hash FROM account WHERE address='${address}') - AND currency_code='${mainCurrencyCode}' AND (already_shown IS NULL OR already_shown=0) - AND derivation_type!='main' - ORDER BY derivation_index ASC - `; - const res = (await Database.unsafeRawQuery( - DatabaseTable.Accounts, - sql - )) as AccountDBModel[]; - for (const row of res) { - const walletHash = row.hash.hash; - const prefix = - row.address.indexOf(segwitPrefix) === 0 - ? segwitPrefix - : row.address.substring(0, 1); - await AirDAOCryptoLog.log( - this._settings.currencyCode + - ' ' + - mainCurrencyCode + - ' BtcUnspentsProvider.getUnspents started HD CACHE_FOR_CHANGE ' + - address + - ' walletHash ' + - walletHash - ); - // @ts-ignore - if (typeof CACHE_FOR_CHANGE[walletHash] === 'undefined') { - // @ts-ignore - CACHE_FOR_CHANGE[walletHash] = {}; - } - // @ts-ignore - if ( - typeof CACHE_FOR_CHANGE[walletHash][prefix] === 'undefined' || - CACHE_FOR_CHANGE[walletHash][prefix] === '' - ) { - // @ts-ignore - CACHE_FOR_CHANGE[walletHash][prefix] = row.address; - // @ts-ignore - await AirDAOCryptoLog.log( - this._settings.currencyCode + - ' ' + - mainCurrencyCode + - ' BtcUnspentsProvider.getUnspents started HD CACHE_FOR_CHANGE ' + - address + - ' walletHash ' + - walletHash + - ' ' + - prefix + - ' changed ' + - JSON.stringify(CACHE_FOR_CHANGE[walletHash]) - ); - } - } - } else { - const sql = `SELECT account.address, account.derivation_path as derivationPath, hash - FROM ${DatabaseTable.Accounts} - WHERE account.hash = (SELECT hash FROM ${DatabaseTable.Accounts} WHERE address='${address}') - AND currency_code='${mainCurrencyCode}' - `; - const res = (await Database.unsafeRawQuery( - DatabaseTable.Accounts, - sql - )) as AccountDBModel[]; - for (const row of res) { - const walletHash = row.hash.hash; - const unspents = await super.getUnspents(row.address); - // @ts-ignore - await AirDAOCryptoLog.log( - this._settings.currencyCode + - '/' + - mainCurrencyCode + - ' BtcUnspentsProvider.getUnspents started CACHE_FOR_CHANGE ' + - address + - ' ' + - row.address + - ' walletHash ' + - walletHash - ); - if (typeof CACHE_FOR_CHANGE[walletHash] === 'undefined') { - // @ts-ignore - CACHE_FOR_CHANGE[walletHash] = {}; - } - const prefix = - row.address.indexOf(segwitPrefix) === 0 - ? segwitPrefix - : row.address.substr(0, 1); - // @ts-ignore - CACHE_FOR_CHANGE[walletHash][prefix] = row.address; - if (unspents) { - for (const unspent of unspents) { - unspent.address = row.address; - unspent.derivationPath = Database.unEscapeString( - row.derivationPath - ); - totalUnspents.push(unspent); - } - } - } - } - // @ts-ignore - if (totalUnspents.length > 10) { - await AirDAOCryptoLog.log( - this._settings.currencyCode + - ' ' + - mainCurrencyCode + - ' BtcUnspentsProvider.getUnspents finished ' + - address + - ' total ' + - totalUnspents.length, - totalUnspents.slice(0, 10) - ); - } else { - await AirDAOCryptoLog.log( - this._settings.currencyCode + - ' ' + - mainCurrencyCode + - ' BtcUnspentsProvider.getUnspents finished ' + - address + - ' total ' + - totalUnspents.length, - totalUnspents - ); - } - return totalUnspents; - } -} diff --git a/crypto/blockchains/btc/tx/BtcTxBuilder.ts b/crypto/blockchains/btc/tx/BtcTxBuilder.ts deleted file mode 100644 index 781ce4ab3..000000000 --- a/crypto/blockchains/btc/tx/BtcTxBuilder.ts +++ /dev/null @@ -1,231 +0,0 @@ -/** - * @version 0.20 - */ -import { AirDAOBlockchainTypes } from '../../AirDAOBlockchainTypes'; -import DogeTxBuilder from '../../doge/tx/DogeTxBuilder'; -import BlocksoftPrivateKeysUtils from '../../../common/AirDAOPrivateKeysUtils'; -import { ECPair, payments, TransactionBuilder } from 'bitcoinjs-lib'; -import AirDAOCryptoLog from '../../../common/AirDAOCryptoLog'; -import AirDAODict from '@crypto/common/AirDAODict'; - -export default class BtcTxBuilder - extends DogeTxBuilder - implements AirDAOBlockchainTypes.TxBuilder -{ - private mnemonic = ''; - private walletHash = ''; - private keyPairBTC: any = {}; - private p2wpkhBTC: any = {}; - private p2shBTC: any = {}; - - _getRawTxValidateKeyPair( - privateData: AirDAOBlockchainTypes.TransferPrivateData, - data: AirDAOBlockchainTypes.TransferData - ): void { - if (this.mnemonic === privateData.privateKey) return; - - this.mnemonic = privateData.privateKey; - this.walletHash = data.walletHash; - this.keyPairBTC = {}; - this.p2wpkhBTC = {}; - this.p2shBTC = {}; - } - - async _getRawTxAddInput( - txb: TransactionBuilder, - i: number, - input: AirDAOBlockchainTypes.UnspentTx, - nSequence: number - ): Promise { - if (typeof input.address === 'undefined') { - throw new Error('no address in input ' + JSON.stringify(input)); - } - - if (!input.derivationPath || input.derivationPath === 'false') { - // @ts-ignore - if (typeof input.path !== 'undefined') { - // @ts-ignore - input.derivationPath = input.path; - } - } - - // @ts-ignore - const mainCurrencyCode = - this._settings.currencyCode === 'LTC' ? 'LTC' : 'BTC'; - - const segwitPrefix = - AirDAODict.CurrenciesForTests[mainCurrencyCode + '_SEGWIT'].addressPrefix; - const segwitCompatiblePrefix = - typeof AirDAODict.CurrenciesForTests[ - mainCurrencyCode + '_SEGWIT_COMPATIBLE' - ] !== 'undefined' - ? AirDAODict.CurrenciesForTests[mainCurrencyCode + '_SEGWIT_COMPATIBLE'] - .addressPrefix - : false; - - if (typeof this.keyPairBTC[input.address] === 'undefined') { - let currencyCode = mainCurrencyCode; - if (input.address.indexOf(segwitPrefix) === 0) { - currencyCode += '_SEGWIT'; - if (!input.derivationPath || input.derivationPath === 'false') { - input.derivationPath = - AirDAODict.CurrenciesForTests[currencyCode].defaultPath; - } - } else if ( - segwitCompatiblePrefix && - input.address.indexOf(segwitCompatiblePrefix) === 0 - ) { - currencyCode += '_SEGWIT_COMPATIBLE'; - if (!input.derivationPath || input.derivationPath === 'false') { - input.derivationPath = - AirDAODict.CurrenciesForTests[currencyCode].defaultPath; - } - } else if (!input.derivationPath || input.derivationPath === 'false') { - input.derivationPath = - AirDAODict.CurrenciesForTests[mainCurrencyCode].defaultPath; - } - const discoverFor = { - mnemonic: this.mnemonic, - addressToCheck: input.address, - walletHash: this.walletHash, - derivationPath: input.derivationPath, - currencyCode - }; - const result = await BlocksoftPrivateKeysUtils.getPrivateKey( - discoverFor, - 'BtcTxBuilder' - ); - - try { - this.keyPairBTC[input.address] = ECPair.fromWIF( - result.privateKey, - this._bitcoinNetwork - ); - let address; - if (currencyCode === mainCurrencyCode + '_SEGWIT') { - try { - this.p2wpkhBTC[input.address] = payments.p2wpkh({ - pubkey: this.keyPairBTC[input.address].publicKey, - network: this._bitcoinNetwork - }); - } catch (e) { - e.message += - ' in privateKey ' + currencyCode + ' Segwit signature create'; - // noinspection ExceptionCaughtLocallyJS - throw e; - } - - if (typeof this.p2wpkhBTC[input.address].address === 'undefined') { - // noinspection ExceptionCaughtLocallyJS - throw new Error('not valid ' + currencyCode + ' segwit p2sh'); - } - address = this.p2wpkhBTC[input.address].address; - } else if (currencyCode === mainCurrencyCode + '_SEGWIT_COMPATIBLE') { - try { - this.p2wpkhBTC[input.address] = payments.p2wpkh({ - pubkey: this.keyPairBTC[input.address].publicKey, - network: this._bitcoinNetwork - }); - this.p2shBTC[input.address] = payments.p2sh({ - redeem: this.p2wpkhBTC[input.address], - network: this._bitcoinNetwork - }); - } catch (e) { - e.message += - ' in privateKey ' + - currencyCode + - ' SegwitCompatible signature create'; - // noinspection ExceptionCaughtLocallyJS - throw e; - } - if (typeof this.p2shBTC[input.address].address === 'undefined') { - // noinspection ExceptionCaughtLocallyJS - throw new Error( - 'not valid ' + currencyCode + ' segwit compatible p2sh' - ); - } - address = this.p2shBTC[input.address].address; - } else { - address = payments.p2pkh({ - pubkey: this.keyPairBTC[input.address].publicKey, - network: this._bitcoinNetwork - }).address; - } - - if (address !== input.address) { - // noinspection ExceptionCaughtLocallyJS - throw new Error( - 'not valid ' + - currencyCode + - ' signing path ' + - input.derivationPath + - ' address ' + - input.address + - ' != ' + - address + - ' segwit type = ' + - currencyCode - ); - } - } catch (e) { - e.message += ' in privateKey ' + currencyCode + ' signature check '; - throw e; - } - } - - if (typeof this.p2wpkhBTC[input.address] === 'undefined') { - txb.addInput(input.txid, input.vout, nSequence); - } else if (typeof this.p2shBTC[input.address] === 'undefined') { - txb.addInput( - input.txid, - input.vout, - nSequence, - this.p2wpkhBTC[input.address].output - ); - } else { - txb.addInput(input.txid, input.vout, nSequence); - } - } - - async _getRawTxSign( - txb: TransactionBuilder, - i: number, - input: AirDAOBlockchainTypes.UnspentTx - ): Promise { - if (typeof input.address === 'undefined') { - throw new Error('no address in input ' + JSON.stringify(input)); - } - if (typeof this.p2wpkhBTC[input.address] === 'undefined') { - // @ts-ignore - await AirDAOCryptoLog.log( - this._settings.currencyCode + ' BtcTxBuilder.getRawTx sign usual', - input - ); - // @ts-ignore - txb.sign(i, this.keyPairBTC[input.address], null, null, input.value * 1); - } else if (typeof this.p2shBTC[input.address] === 'undefined') { - // @ts-ignore - await AirDAOCryptoLog.log( - this._settings.currencyCode + ' BtcTxBuilder.getRawTx sign segwit', - input - ); - // @ts-ignore - txb.sign(i, this.keyPairBTC[input.address], null, null, input.value * 1); - } else { - // @ts-ignore - await AirDAOCryptoLog.log( - this._settings.currencyCode + - ' BtcTxBuilder.getRawTx sign segwit compatible', - input - ); - // @ts-ignore - txb.sign( - i, - this.keyPairBTC[input.address], - this.p2shBTC[input.address].redeem.output, - null, - input.value * 1 - ); - } - } -} diff --git a/crypto/blockchains/btc/tx/BtcTxInputsOutputs.ts b/crypto/blockchains/btc/tx/BtcTxInputsOutputs.ts deleted file mode 100644 index d862f3b4c..000000000 --- a/crypto/blockchains/btc/tx/BtcTxInputsOutputs.ts +++ /dev/null @@ -1,188 +0,0 @@ -/** - * @version 0.20 - */ -import { AirDAOBlockchainTypes } from '../../AirDAOBlockchainTypes'; -import BtcUnspentsProvider from '../providers/BtcUnspentsProvider'; -import DogeTxInputsOutputs from '../../doge/tx/DogeTxInputsOutputs'; -import AirDAOCryptoLog from '../../../common/AirDAOCryptoLog'; -import DaemonCache from '../../../../src/daemons/DaemonCache'; -import AirDAODict from '@crypto/common/AirDAODict'; -import { Database } from '@database'; - -export default class BtcTxInputsOutputs - extends DogeTxInputsOutputs - implements AirDAOBlockchainTypes.TxInputsOutputs -{ - async _addressForChange( - data: AirDAOBlockchainTypes.TransferData - ): Promise { - const btcShowTwoAddress = await Database.localStorage.get( - 'btcShowTwoAddress' - ); - const btcLegacyOrSegwit = await Database.localStorage.get( - 'btc_legacy_or_segwit' - ); - - const mainCurrencyCode = - this._settings.currencyCode === 'LTC' ? 'LTC' : 'BTC'; - const legacyPrefix = AirDAODict.Currencies[mainCurrencyCode].addressPrefix; - const segwitPrefix = - AirDAODict.CurrenciesForTests[mainCurrencyCode + '_SEGWIT'].addressPrefix; - - let needFindSegwit = true; - if (btcShowTwoAddress === '1' || data.useLegacy === 1) { - // @todo as btcShowTwoAddress this will be deprecated simplify the code - // its only for wallets with old setting of two addresses where there was useLegacy on - // console.log('will legacy') - needFindSegwit = false; - } else if (btcShowTwoAddress === '1' || btcLegacyOrSegwit === 'segwit') { - needFindSegwit = true; - } else if (btcLegacyOrSegwit === 'legacy') { - needFindSegwit = false; - // console.log('will legacy 2') - } - - AirDAOCryptoLog.log( - 'BtcTxInputsOutputs needFindSegwit ' + JSON.stringify(needFindSegwit) - ); - try { - const CACHE_FOR_CHANGE = await BtcUnspentsProvider.getCache( - data.walletHash, - this._settings.currencyCode - ); - AirDAOCryptoLog.log( - 'BtcTxInputsOutputs CACHE_FOR_CHANGE ' + data.walletHash, - CACHE_FOR_CHANGE - ); - - let addressForChange = false; - if (needFindSegwit) { - addressForChange = CACHE_FOR_CHANGE[segwitPrefix]; - } else { - addressForChange = CACHE_FOR_CHANGE[legacyPrefix]; - } - // @ts-ignore - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' ' + - mainCurrencyCode + - ' BtcTxInputsOutputs _addressForChange addressForChange logic ', - { - needFindSegwit, - addressForChange, - CACHE: CACHE_FOR_CHANGE - } - ); - if (addressForChange && addressForChange !== '') { - return addressForChange; - } - } catch (e) { - AirDAOCryptoLog.err( - this._settings.currencyCode + - ' ' + - mainCurrencyCode + - ' BtcTxInputsOutputs _addressForChange error ' + - e.message - ); - } - - return data.addressFrom; - } - - async getInputsOutputs( - data: AirDAOBlockchainTypes.TransferData, - unspents: AirDAOBlockchainTypes.UnspentTx[], - feeToCount: { - feeForByte?: string; - feeForAll?: string; - autoFeeLimitReadable?: string | number; - }, - additionalData: AirDAOBlockchainTypes.TransferAdditionalData, - subtitle = 'default' - ): Promise { - const res = await super._getInputsOutputs( - data, - unspents, - feeToCount, - additionalData, - subtitle + ' btced' - ); - - if (this._settings.currencyCode !== 'BTC') { - return res; - } - - const tmp = DaemonCache.getCacheAccountStatic(data.walletHash, 'USDT'); - if (tmp.balance === '0' || tmp.balance === 0) { - return res; - } - - let usdtCount = 0; - for (const unspent of unspents) { - if (unspent.address === tmp.address) { - usdtCount++; - } - } - if (usdtCount === 0) { - res.outputs.push({ - to: tmp.address, - amount: '546', - isChange: true, - logType: 'FOR_LEGACY_USDT_KEEP_FROM_BTC' - }); - return res; - } - - let usdtUsed = 0; - for (const input of res.inputs) { - if (input.address === tmp.address) { - usdtUsed++; - } - } - AirDAOCryptoLog.log( - 'BtxTxInputsOutputs for ' + - tmp.address + - ' usdtUsed ' + - usdtUsed + - ' usdtCount ' + - usdtCount - ); - - if (usdtUsed >= usdtCount) { - let found = false; - for (const input of res.inputs) { - if (input.address === tmp.address && !found && input.value === '546') { - input.value = '0'; - found = true; - } - } - if (!found) { - for (const input of res.inputs) { - if (input.address === tmp.address) { - res.outputs.push({ - to: tmp.address, - amount: '546', - isChange: true, - logType: 'FOR_LEGACY_USDT_KEEP_FROM_BTC' - }); - break; - } - } - } - - if (found) { - const inputs = []; - for (const input of res.inputs) { - if (input.value !== '0') { - inputs.push(input); - } - } - res.inputs = inputs; - } - } - - res.countedFor = 'BTC'; - - return res; - } -} diff --git a/crypto/blockchains/btc_test/BtcTestScannerProcessor.js b/crypto/blockchains/btc_test/BtcTestScannerProcessor.js deleted file mode 100644 index 880abd0af..000000000 --- a/crypto/blockchains/btc_test/BtcTestScannerProcessor.js +++ /dev/null @@ -1,136 +0,0 @@ -/** - * @version 0.52 - * https://github.com/Blockstream/esplora/blob/master/API.md - */ -import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; -import AirDAOAxios from '@crypto/common/AirDAOAxios'; -import BlocksoftUtils from '@crypto/common/AirDAOUtils'; -import DogeFindAddressFunction from '@crypto/blockchains/doge/basic/DogeFindAddressFunction'; - -const API_PATH = 'https://blockstream.info/testnet/api/'; - -export default class BtcTestScannerProcessor { - /* - * https://blockstream.info/testnet/api/address/mtU4mYXfBRiTx1iUBWcCvUTr4CgRnRALaL - * @param {string} address - * @return {Promise<{int:balance, int:provider}>} - */ - async getBalanceBlockchain(address) { - AirDAOCryptoLog.log( - 'BtcTestScannerProcessor.getBalance started ' + address - ); - const res = await AirDAOAxios.getWithoutBraking( - API_PATH + 'address/' + address - ); - // console.log('res', res.data.chain_stats.funded_txo_sum) // spent_txo_sum - if ( - !res || - typeof res.data === 'undefined' || - typeof res.data.chain_stats === 'undefined' || - typeof res.data.chain_stats.funded_txo_sum === 'undefined' - ) { - return false; - } - return { - balance: res.data.chain_stats.funded_txo_sum, - unconfirmed: 0, - provider: 'blockstream.info' - }; - } - - /** - * https://blockstream.info/testnet/api/address/mtU4mYXfBRiTx1iUBWcCvUTr4CgRnRALaL/txs - * @param {string} scanData.account.address - * @return {Promise} - */ - async getTransactionsBlockchain(scanData) { - const address = scanData.account.address.trim(); - AirDAOCryptoLog.log( - 'BtcTestScannerProcessor.getTransactions started ' + address - ); - const res = await AirDAOAxios.getWithoutBraking( - API_PATH + 'address/' + address + '/txs' - ); - if (!res || typeof res.data === 'undefined' || !res.data) { - return []; - } - const transactions = []; - let tx; - for (tx of res.data) { - const transaction = await this._unifyTransaction(address, tx); - transactions.push(transaction); - } - AirDAOCryptoLog.log( - 'BtcTestScannerProcessor.getTransactions finished ' + - address + - ' total: ' + - transactions.length - ); - return transactions; - } - - /** - * - * @param {string} address - * @param {Object} transaction - * @return {Promise} - * @private - */ - async _unifyTransaction(address, transaction) { - let showAddresses = false; - try { - showAddresses = await DogeFindAddressFunction([address], transaction); - } catch (e) { - e.message += - ' transaction hash ' + - JSON.stringify(transaction) + - ' address ' + - address; - throw e; - } - let transactionStatus = 'new'; - let blockConfirmations = 0; - let blockHash = ''; - let blockNumber = ''; - let formattedTime = 0; - if (typeof transaction.status !== 'undefined') { - if (typeof transaction.status.block_hash !== 'undefined') { - blockHash = transaction.status.block_hash; - } - if (typeof transaction.status.block_height !== 'undefined') { - blockNumber = transaction.status.block_height; - } - if (typeof transaction.status.confirmed !== 'undefined') { - if (transaction.status.confirmed) { - transactionStatus = 'success'; - blockConfirmations = 100; - } else { - transactionStatus = 'confirming'; - } - } - if (typeof transaction.status.block_time !== 'undefined') { - try { - formattedTime = BlocksoftUtils.toDate(transaction.status.block_time); - } catch (e) { - e.message += - ' timestamp error transaction data ' + JSON.stringify(transaction); - throw e; - } - } - } - - return { - transactionHash: transaction.txid, - blockHash, - blockNumber, - blockTime: formattedTime, - blockConfirmations, - transactionDirection: showAddresses.direction, - addressFrom: showAddresses.from, - addressTo: showAddresses.to, - addressAmount: showAddresses.value, - transactionStatus: transactionStatus, - transactionFee: transaction.fee - }; - } -} diff --git a/crypto/blockchains/btc_test/BtcTestTransferProcessor.ts b/crypto/blockchains/btc_test/BtcTestTransferProcessor.ts deleted file mode 100644 index 63e0f62ec..000000000 --- a/crypto/blockchains/btc_test/BtcTestTransferProcessor.ts +++ /dev/null @@ -1,53 +0,0 @@ -/** - * @version 0.52 - */ -import { AirDAOBlockchainTypes } from '@crypto/blockchains/AirDAOBlockchainTypes'; -import DogeTransferProcessor from '@crypto/blockchains/doge/DogeTransferProcessor'; -import BtcTestUnspentsProvider from '@crypto/blockchains/btc_test/providers/BtcTestUnspentsProvider'; -import BtcTestSendProvider from '@crypto/blockchains/btc_test/providers/BtcTestSendProvider'; -import DogeTxInputsOutputs from '@crypto/blockchains/doge/tx/DogeTxInputsOutputs'; -import DogeTxBuilder from '@crypto/blockchains/doge/tx/DogeTxBuilder'; - -export default class BtcTestTransferProcessor - extends DogeTransferProcessor - implements AirDAOBlockchainTypes.TransferProcessor -{ - _trezorServerCode = 'NONE'; - - _builderSettings: AirDAOBlockchainTypes.BuilderSettings = { - minOutputDustReadable: 0.000005, - minChangeDustReadable: 0.00001, - feeMaxForByteSatoshi: 10000, // for tx builder - feeMaxAutoReadable2: 0.2, // for fee calc, - feeMaxAutoReadable6: 0.1, // for fee calc - feeMaxAutoReadable12: 0.05, // for fee calc - changeTogether: true, - minRbfStepSatoshi: 10, - minSpeedUpMulti: 1.5 - }; - - canRBF( - data: AirDAOBlockchainTypes.DbAccount, - transaction: AirDAOBlockchainTypes.DbTransaction - ): boolean { - return false; - } - - _initProviders() { - if (this._initedProviders) return false; - this.unspentsProvider = new BtcTestUnspentsProvider( - this._settings, - this._trezorServerCode - ); - this.sendProvider = new BtcTestSendProvider( - this._settings, - this._trezorServerCode - ); - this.txPrepareInputsOutputs = new DogeTxInputsOutputs( - this._settings, - this._builderSettings - ); - this.txBuilder = new DogeTxBuilder(this._settings, this._builderSettings); - this._initedProviders = true; - } -} diff --git a/crypto/blockchains/btc_test/providers/BtcTestSendProvider.ts b/crypto/blockchains/btc_test/providers/BtcTestSendProvider.ts deleted file mode 100644 index 28a497cd9..000000000 --- a/crypto/blockchains/btc_test/providers/BtcTestSendProvider.ts +++ /dev/null @@ -1,96 +0,0 @@ -/** - * @version 0.52 - */ -import { AirDAOBlockchainTypes } from '@crypto/blockchains/AirDAOBlockchainTypes'; - -import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; -import AirDAOAxios from '@crypto/common/AirDAOAxios'; -import DogeSendProvider from '@crypto/blockchains/doge/providers/DogeSendProvider'; - -const API_URL = 'https://api.blockchair.com/bitcoin/testnet/push/transaction'; - -export default class BtcTestSendProvider - extends DogeSendProvider - implements AirDAOBlockchainTypes.SendProvider -{ - async sendTx( - hex: string, - subtitle: string, - txRBF: any, - logData: any - ): Promise<{ transactionHash: string; transactionJson: any; logData: any }> { - await AirDAOCryptoLog.log( - this._settings.currencyCode + - ' BtcTestSendProvider.sendTx ' + - subtitle + - ' started ', - logData - ); - - // logData = await this._check(hex, subtitle, txRBF, logData) - - let res; - try { - res = await AirDAOAxios.post(API_URL, { data: hex }); - } catch (e) { - try { - logData.error = e.message; - // await this._checkError(hex, subtitle, txRBF, logData) - } catch (e2) { - await AirDAOCryptoLog.log( - this._settings.currencyCode + - ' BtcTestSendProvider.send proxy error errorTx ' + - e2.message - ); - } - if ( - e.message.indexOf('transaction already in the mempool') !== -1 || - e.message.indexOf('TXN-MEMPOOL-CONFLICT') - ) { - throw new Error('SERVER_RESPONSE_NO_RESPONSE'); - } else if (e.message.indexOf('dust') !== -1) { - throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_DUST'); - } else if ( - e.message.indexOf('bad-txns-inputs-spent') !== -1 || - e.message.indexOf('txn-mempool-conflict') !== -1 - ) { - throw new Error('SERVER_RESPONSE_NO_RESPONSE'); - } else if ( - e.message.indexOf('fee for relay') !== -1 || - e.message.indexOf('insufficient priority') !== -1 - ) { - throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_FEE'); - } else { - throw e; - } - } - let txid = ''; - // @ts-ignore - if (typeof res.data === 'undefined' || !res.data) { - throw new Error('SERVER_RESPONSE_NOT_CONNECTED'); - } - // @ts-ignore - if ( - typeof res.data !== 'undefined' && - typeof res.data.txid !== 'undefined' - ) { - // @ts-ignore - txid = res.data.txid; - } - // @ts-ignore - if (typeof res.data.data !== 'undefined') { - // @ts-ignore - if (typeof res.data.data.transaction_hash !== 'undefined') { - // @ts-ignore - txid = res.data.data.transaction_hash; - } - } - if (txid === '') { - throw new Error('SERVER_RESPONSE_NOT_CONNECTED'); - } - - // logData = await this._checkSuccess(txid, hex, subtitle, txRBF, logData) - - return { transactionHash: txid, transactionJson: {}, logData }; - } -} diff --git a/crypto/blockchains/btc_test/providers/BtcTestUnspentsProvider.ts b/crypto/blockchains/btc_test/providers/BtcTestUnspentsProvider.ts deleted file mode 100644 index 7af76c063..000000000 --- a/crypto/blockchains/btc_test/providers/BtcTestUnspentsProvider.ts +++ /dev/null @@ -1,87 +0,0 @@ -/** - * @version 0.52 - */ -import { AirDAOBlockchainTypes } from '@crypto/blockchains/AirDAOBlockchainTypes'; - -import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; -import AirDAOAxios from '@crypto/common/AirDAOAxios'; - -const API_PATH = 'https://blockstream.info/testnet/api/'; - -export default class BtcTestUnspentsProvider - implements AirDAOBlockchainTypes.UnspentsProvider -{ - protected _settings: AirDAOBlockchainTypes.CurrencySettings; - - constructor( - settings: AirDAOBlockchainTypes.CurrencySettings, - serverCode: string - ) { - this._settings = settings; - } - - /** - * https://blockstream.info/testnet/api/address/mtU4mYXfBRiTx1iUBWcCvUTr4CgRnRALaL/utxo - * @param address - */ - async getUnspents( - address: string - ): Promise { - // @ts-ignore - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' BtcTestUnspentsProvider.getUnspents started', - address - ); - - const link = API_PATH + `address/${address}/utxo`; - const res = await AirDAOAxios.getWithoutBraking(link); - - if (!res || typeof res.data === 'undefined') { - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' BtcTestUnspentsProvider.getUnspents nothing loaded for address ' + - address + - ' link ' + - link - ); - throw new Error('SERVER_RESPONSE_NOT_CONNECTED'); - } - if ( - !res.data || - typeof res.data.length === 'undefined' || - !res.data || - !res.data.length - ) { - return []; - } - const sortedUnspents = []; - /** - * @param {*} res.data[] - * @param {string} res.data[].txid - * @param {string} res.data[].vout - * @param {string} res.data[].status - * @param {string} res.data[].status.confirmed - * @param {string} res.data[].status.block_height - * @param {string} res.data[].status.block_hash - * @param {string} res.data[].status.block_time - * @param {string} res.data[].value - */ - for (const unspent of res.data) { - sortedUnspents.push({ - txid: unspent.txid, - vout: typeof unspent.vout === 'undefined' ? 0 : unspent.vout, - value: unspent.value.toString(), - height: 0, - confirmations: - typeof unspent.status !== 'undefined' && - typeof unspent.status.confirmed !== 'undefined' && - unspent.status.confirmed - ? 100 - : 0, - isRequired: false - }); - } - return sortedUnspents; - } -} diff --git a/crypto/blockchains/btg/BtgScannerProcessor.js b/crypto/blockchains/btg/BtgScannerProcessor.js deleted file mode 100644 index 47a67f200..000000000 --- a/crypto/blockchains/btg/BtgScannerProcessor.js +++ /dev/null @@ -1,20 +0,0 @@ -/** - * @version 0.5 - * https://github.com/trezor/blockbook/blob/master/docs/api.md - */ - -import DogeScannerProcessor from '../doge/DogeScannerProcessor'; - -export default class BtgScannerProcessor extends DogeScannerProcessor { - /** - * @type {number} - * @private - */ - _blocksToConfirm = 10; - - /** - * @type {string} - * @private - */ - _trezorServerCode = 'BTG_TREZOR_SERVER'; -} diff --git a/crypto/blockchains/btg/BtgTransferProcessor.ts b/crypto/blockchains/btg/BtgTransferProcessor.ts deleted file mode 100644 index 0cc55b60e..000000000 --- a/crypto/blockchains/btg/BtgTransferProcessor.ts +++ /dev/null @@ -1,36 +0,0 @@ -/** - * @version 0.20 - */ - -import DogeTransferProcessor from '../doge/DogeTransferProcessor'; -import { AirDAOBlockchainTypes } from '../AirDAOBlockchainTypes'; - -export default class BtgTransferProcessor - extends DogeTransferProcessor - implements AirDAOBlockchainTypes.TransferProcessor -{ - /** - * @type {string} - * @private - */ - _trezorServerCode = 'BTG_TREZOR_SERVER'; - - _builderSettings: AirDAOBlockchainTypes.BuilderSettings = { - minOutputDustReadable: 0.00001, - minChangeDustReadable: 0.00001, - feeMaxForByteSatoshi: 10000, // for tx builder - feeMaxAutoReadable2: 1, // for fee calc, - feeMaxAutoReadable6: 0.5, // for fee calc - feeMaxAutoReadable12: 0.2, // for fee calc - changeTogether: true, - minRbfStepSatoshi: 10, - minSpeedUpMulti: 1.5 - }; - - canRBF( - data: AirDAOBlockchainTypes.DbAccount, - transaction: AirDAOBlockchainTypes.DbTransaction - ): boolean { - return false; - } -} diff --git a/crypto/blockchains/doge/DogeScannerProcessor.ts b/crypto/blockchains/doge/DogeScannerProcessor.ts deleted file mode 100644 index cf8820904..000000000 --- a/crypto/blockchains/doge/DogeScannerProcessor.ts +++ /dev/null @@ -1,327 +0,0 @@ -/** - * @version 0.5 - * https://github.com/trezor/blockbook/blob/master/docs/api.md - * https://doge1.trezor.io/api/v2/address/D5oKvWEibVe74CXLASmhpkRpLoyjgZhm71?details=txs - * - * @typedef {Object} UnifiedTransaction - * @property {*} transactionHash - * @property {*} blockHash - * @property {*} blockNumber - * @property {*} blockTime - * @property {*} blockConfirmations - * @property {*} transactionDirection - * @property {*} addressFrom - * @property {*} addressTo - * @property {*} addressAmount - * @property {*} transactionStatus - * @property {*} transactionFee - * @property {*} transactionFeeCurrencyCode - * @property {*} contractAddress - * @property {*} inputValue - * @property {*} transactionJson - */ -import BlocksoftUtils from '../../common/AirDAOUtils'; -import AirDAOAxios from '../../common/AirDAOAxios'; -import AirDAOCryptoLog from '../../common/AirDAOCryptoLog'; - -import DogeFindAddressFunction from './basic/DogeFindAddressFunction'; -import BlocksoftExternalSettings from '../../common/AirDAOExternalSettings'; -import DogeRawDS from './stores/DogeRawDS'; -import { BlockchainTransaction } from '@appTypes'; - -const CACHE_VALID_TIME = 30000; // 30 seconds -const CACHE: { [key: string]: unknown } = {}; //TODO fix type - -const TIMEOUT_DOGE = 60000; -const PROXY_TXS = 'https://proxy.trustee.deals/btc/getTxs'; - -export default class DogeScannerProcessor { - /** - * @type {number} - * @private - */ - _blocksToConfirm = 5; - - /** - * @type {string} - * @private - */ - _trezorServerCode = 'DOGE_TREZOR_SERVER'; - - /** - * @private - */ - _trezorServer = false; - - _settings: unknown; // TODO fix type - - constructor(settings: unknown) { - this._settings = settings; - } - - _addressesForFind(address: string, jsonData = {}) { - return [address]; - } - - private async _get(address: string, jsonData): Promise { - const now = new Date().getTime(); - if ( - typeof CACHE[address] !== 'undefined' && - now - CACHE[address].time < CACHE_VALID_TIME - ) { - CACHE[address].provider = 'trezor-cache'; - return CACHE[address]; - } - - let link; - let res = false; - // remove later after tests - if ( - this._settings['currencyCode'] === 'DOGE' || - this._settings['currencyCode'] === 'LTC' || - this._settings['currencyCode'] === 'BSV' || - this._settings['currencyCode'] === 'BCH' - ) { - link = - PROXY_TXS + - '?address=' + - address + - '¤cyCode=' + - this._settings['currencyCode']; - res = await AirDAOAxios.getWithoutBraking(link, 5, TIMEOUT_DOGE); - } - if ( - res && - typeof res.data !== 'undefined' && - res.data && - typeof res.data.data !== 'undefined' - ) { - res.data = res.data.data; - } else { - this._trezorServer = await BlocksoftExternalSettings.getTrezorServer( - this._trezorServerCode, - 'DOGE.Scanner._get' - ); - link = - this._trezorServer + - '/api/v2/address/' + - address + - '?details=txs&pageSize=40'; - res = await AirDAOAxios.getWithoutBraking(link, 5, TIMEOUT_DOGE); - } - - if (!res || !res.data) { - await BlocksoftExternalSettings.setTrezorServerInvalid( - this._trezorServerCode, - this._trezorServer - ); - return false; - } - if (typeof res.data.balance === 'undefined') { - throw new Error( - this._settings.currencyCode + - ' DogeScannerProcessor._get nothing loaded for address ' + - link - ); - } - CACHE[address] = { - data: res.data, - time: now, - provider: 'trezor' - }; - return CACHE[address]; - } - - /** - * @param {string} address - * @return {Promise<{balance:*, unconfirmed:*, provider:string}>} - */ - async getBalanceBlockchain(address: string, jsonData = {}) { - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' DogeScannerProcessor.getBalance started ' + - address - ); - const res = await this._get(address, jsonData); - if (!res) { - return false; - } - return { - balance: res.data.balance, - unconfirmed: res.data.unconfirmedBalance, - provider: res.provider, - time: res.time - }; - } - - /** - * @param {string} scanData.account.address - * @param {*} scanData.additional - * @return {Promise} - */ - async getTransactionsBlockchain(scanData: { - account: { address: string }; - additional: unknown; // TODO fix type - }) { - const address = scanData.account.address.trim(); - const jsonData = scanData.additional; - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' DogeScannerProcessor.getTransactions started ' + - address - ); - - const notBroadcasted = await DogeRawDS.getForAddress({ - address, - currencyCode: this._settings.currencyCode - }); - - let res = await this._get(address, jsonData); - if (!res || typeof res.data === 'undefined') return []; - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' DogeScannerProcessor.getTransactions loaded from ' + - res.provider + - ' ' + - res.time - ); - res = res.data; - if (typeof res.transactions === 'undefined' || !res.transactions) return []; - - const vinsOrder = {}; - for (const tx of res.transactions) { - vinsOrder[tx.txid] = tx.blockTime; - } - - let plussed = false; - let i = 0; - do { - for (const tx of res.transactions) { - if (typeof tx.vin === 'undefined' || tx.vin.length === 0) continue; - for (const vin of tx.vin) { - if (typeof vinsOrder[vin.txid] === 'undefined') { - continue; - } - const newTime = vinsOrder[vin.txid] + 1; - if (tx.blockTime < newTime) { - tx.blockTime = newTime; - plussed = true; - } - vinsOrder[tx.txid] = tx.blockTime; - } - } - i++; - } while (plussed && i < 100); - - const transactions = []; - for (const tx of res.transactions) { - const transaction = await this._unifyTransaction(address, tx, jsonData); - if (transaction) { - transactions.push(transaction); - if ( - transaction.transactionDirection === 'outcome' || - transaction.transactionDirection === 'self' - ) { - const uniqueFrom = - address.toLowerCase() + '_' + transaction.transactionHash; - if ( - notBroadcasted && - typeof notBroadcasted[uniqueFrom] !== 'undefined' && - transaction.transactionStatus !== 'new' - ) { - DogeRawDS.cleanRaw({ - address, - transactionUnique: uniqueFrom, - currencyCode: this._settings.currencyCode - }); - } - } - } - } - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' DogeScannerProcessor.getTransactions finished ' + - address + - ' total: ' + - transactions.length - ); - return transactions; - } - - /** - * - * @param {string} address - * @param {Object} transaction - * @param {string} transaction.txid c6b4c3879196857bed7fd5b553dd0049486c032d6a1be72b98fda967ca54b2da - * @param {string} transaction.version 1 - * @param {string} transaction.vin[].txid aa31777a9db759f57fd243ef47419939f233d16bc3e535e9a1c5af3ace87cb54 - * @param {string} transaction.vin[].sequence 4294967294 - * @param {string} transaction.vin[].n 0 - * @param {string} transaction.vin[].addresses [ 'DFDn5QyHH9DiFBNFGMcyJT5uUpDvmBRDqH' ] - * @param {string} transaction.vin[].value 44400000000 - * @param {string} transaction.vin[].hex 47304402200826f97d3432452abedd4346553de0b0c2d401ad7056b155e6462484afd98aa902202b5fb3166b96ded33249aecad7c667c0870c1 - * @param {string} transaction.vout[].value 59999824800 - * @param {string} transaction.vout[].n 0 - * @param {string} transaction.vout[].spent true - * @param {string} transaction.vout[].hex 76a91456d49605503d4770cf1f32fbfb69676d9a72554f88ac - * @param {string} transaction.vout[].addresses [ 'DD4DKVTEkRUGs7qzN8b7q5LKmoE9mXsJk4' ] - * @param {string} transaction.blockHash fc590834c04812e1c7818024a94021e12c4d8ab905724b4a4fdb4d4732878f69 - * @param {string} transaction.blockHeight 3036225 - * @param {string} transaction.confirmations 8568 - * @param {string} transaction.blockTime 1577362993 - * @param {string} transaction.value 59999917700 - * @param {string} transaction.valueIn 59999917700 - * @param {string} transaction.fees 0 - * @param {string} transaction.hex 010000000654cb87ce3aafc5a1e935e5c36bd133f239 - * @return {Promise} - * @private - */ - async _unifyTransaction( - address: string, - transaction: BlockchainTransaction, - jsonData = {} - ) { - let showAddresses = false; - try { - const tmp = this._addressesForFind(address, jsonData); - showAddresses = await DogeFindAddressFunction(tmp, transaction); - } catch (e) { - e.message += - ' transaction hash ' + - JSON.stringify(transaction) + - ' address ' + - address; - throw e; - } - - let transactionStatus = 'new'; - if (transaction.confirmations > this._blocksToConfirm) { - transactionStatus = 'success'; - } else if (transaction.confirmations > 0) { - transactionStatus = 'confirming'; - } - - let formattedTime; - try { - formattedTime = BlocksoftUtils.toDate(transaction.blockTime); - } catch (e) { - e.message += - ' timestamp error transaction data ' + JSON.stringify(transaction); - throw e; - } - - return { - transactionHash: transaction.txid, - blockHash: transaction.blockHash, - blockNumber: +transaction.blockHeight, - blockTime: formattedTime, - blockConfirmations: transaction.confirmations, - transactionDirection: showAddresses.direction, - addressFrom: showAddresses.from, - addressTo: showAddresses.to, - addressAmount: showAddresses.value, - transactionStatus: transactionStatus, - transactionFee: transaction.fees - }; - } -} diff --git a/crypto/blockchains/doge/DogeTransferProcessor.ts b/crypto/blockchains/doge/DogeTransferProcessor.ts deleted file mode 100644 index eeeb50f0c..000000000 --- a/crypto/blockchains/doge/DogeTransferProcessor.ts +++ /dev/null @@ -1,933 +0,0 @@ -/** - * @version 0.20 - */ -import { AirDAOBlockchainTypes } from '../AirDAOBlockchainTypes'; -import AirDAOCryptoLog from '../../common/AirDAOCryptoLog'; -import BlocksoftUtils from '../../common/AirDAOUtils'; - -import DogeNetworkPrices from './basic/DogeNetworkPrices'; -import DogeUnspentsProvider from './providers/DogeUnspentsProvider'; -import DogeTxInputsOutputs from './tx/DogeTxInputsOutputs'; -import DogeTxBuilder from './tx/DogeTxBuilder'; -import DogeSendProvider from './providers/DogeSendProvider'; -import DogeRawDS from './stores/DogeRawDS'; -import { DogeLogs } from './basic/DogeLogs'; - -import BlocksoftExternalSettings from '@crypto/common/AirDAOExternalSettings'; -import { Database } from '@database'; - -import networksConstants from '../../common/ext/networks-constants'; -const MAX_UNSPENTS = 100; - -export default class DogeTransferProcessor - implements AirDAOBlockchainTypes.TransferProcessor -{ - _trezorServerCode = 'DOGE_TREZOR_SERVER'; - - _builderSettings: AirDAOBlockchainTypes.BuilderSettings = { - minOutputDustReadable: 0.001, - minChangeDustReadable: 0.5, - feeMaxForByteSatoshi: 100000000, // for tx builder - feeMaxAutoReadable2: 300, // for fee calc, - feeMaxAutoReadable6: 150, // for fee calc - feeMaxAutoReadable12: 100, // for fee calc - - changeTogether: true, - minRbfStepSatoshi: 50, - minSpeedUpMulti: 1.5, - feeMinTotalReadable: 1 - }; - - _initedProviders = false; - - _settings: AirDAOBlockchainTypes.CurrencySettings; - - _langPrefix: string; - - // @ts-ignore - networkPrices: AirDAOBlockchainTypes.NetworkPrices; - - // @ts-ignore - unspentsProvider: AirDAOBlockchainTypes.UnspentsProvider; - - // @ts-ignore - sendProvider: AirDAOBlockchainTypes.SendProvider; - - // @ts-ignore - txPrepareInputsOutputs: AirDAOBlockchainTypes.TxInputsOutputs; - - // @ts-ignore - txBuilder: AirDAOBlockchainTypes.TxBuilder; - - constructor(settings: AirDAOBlockchainTypes.CurrencySettings) { - this._settings = settings; - this._langPrefix = networksConstants[settings.network].langPrefix; - this.networkPrices = new DogeNetworkPrices(); - } - - _initProviders() { - if (this._initedProviders) return false; - this.unspentsProvider = new DogeUnspentsProvider( - this._settings, - this._trezorServerCode - ); - this.sendProvider = new DogeSendProvider( - this._settings, - this._trezorServerCode - ); - this.txPrepareInputsOutputs = new DogeTxInputsOutputs( - this._settings, - this._builderSettings - ); - this.txBuilder = new DogeTxBuilder(this._settings, this._builderSettings); - this._initedProviders = true; - } - - needPrivateForFee(): boolean { - return true; - } - - checkSendAllModal(data: { currencyCode: any }): boolean { - return true; - } - - async getFeeRate( - data: AirDAOBlockchainTypes.TransferData, - privateData: AirDAOBlockchainTypes.TransferPrivateData, - additionalData: AirDAOBlockchainTypes.TransferAdditionalData = {} - ): Promise { - this._initProviders(); - - let isStaticFee = - this._settings.currencyCode === 'DOGE' && - (typeof additionalData.isCustomFee === 'undefined' || - !additionalData.isCustomFee); - let feeStaticReadable; - if (isStaticFee) { - feeStaticReadable = BlocksoftExternalSettings.getStatic('DOGE_STATIC'); - if (!feeStaticReadable['useStatic']) { - isStaticFee = false; - } - } - let txRBF = false; - let transactionSpeedUp = false; - let transactionReplaceByFee = false; - let transactionRemoveByFee = false; - if ( - typeof data.transactionRemoveByFee !== 'undefined' && - data.transactionRemoveByFee - ) { - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' DogeTransferProcessor.getFeeRate remove started ' + - data.addressFrom + - ' => ' + - data.amount - ); - transactionRemoveByFee = data.transactionRemoveByFee; - txRBF = transactionRemoveByFee; - isStaticFee = false; - } else if ( - typeof data.transactionReplaceByFee !== 'undefined' && - data.transactionReplaceByFee - ) { - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' DogeTransferProcessor.getFeeRate resend started ' + - data.addressFrom + - ' => ' + - data.amount - ); - transactionReplaceByFee = data.transactionReplaceByFee; - txRBF = transactionReplaceByFee; - isStaticFee = false; - } else if ( - typeof data.transactionSpeedUp !== 'undefined' && - data.transactionSpeedUp - ) { - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' DogeTransferProcessor.getFeeRate speedup started ' + - data.addressFrom + - ' => ' + - data.amount - ); - transactionSpeedUp = data.transactionSpeedUp; - txRBF = transactionSpeedUp; - } else { - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' DogeTransferProcessor.getFeeRate started ' + - data.addressFrom + - ' => ' + - data.amount - ); - } - - if (txRBF) { - const savedData = await DogeRawDS.getJson({ - address: data.addressFrom, - currencyCode: this._settings.currencyCode, - transactionHash: txRBF - }); // sometimes replaced in db or got from server - if (savedData) { - if (typeof data.transactionJson === 'undefined') { - data.transactionJson = {}; - } - for (const key in savedData) { - // @ts-ignore - data.transactionJson[key] = savedData[key]; - } - } - } - let prices = {}; - let autocorrectFee = false; - if (typeof additionalData.feeForByte === 'undefined') { - prices = - typeof additionalData.prices !== 'undefined' - ? additionalData.prices - : await this.networkPrices.getNetworkPrices( - this._settings.currencyCode - ); - } else { - // @ts-ignore - prices.speed_blocks_12 = additionalData.feeForByte; - autocorrectFee = true; - } - - let unspents = []; - let totalUnspents = 0; - if (transactionRemoveByFee) { - if (typeof this.unspentsProvider.getTx === 'undefined') { - throw new Error( - 'No DogeTransferProcessor unspentsProvider.getTx for transactionRemoveByFee' - ); - } - unspents = await this.unspentsProvider.getTx( - data.transactionRemoveByFee, - data.addressFrom, - [], - data.walletHash - ); - data.isTransferAll = true; - } else { - unspents = - typeof additionalData.unspents !== 'undefined' - ? additionalData.unspents - : await this.unspentsProvider.getUnspents(data.addressFrom); - if (transactionReplaceByFee) { - if (typeof this.unspentsProvider.getTx === 'undefined') { - throw new Error( - 'No DogeTransferProcessor unspentsProvider.getTx for transactionReplaceByFee' - ); - } - unspents = await this.unspentsProvider.getTx( - data.transactionReplaceByFee, - data.addressFrom, - unspents, - data.walletHash - ); - } - - totalUnspents = unspents.length; - if (totalUnspents > MAX_UNSPENTS) { - unspents = unspents.slice(0, MAX_UNSPENTS); - } - } - - if (unspents.length > 1) { - unspents.sort((a, b) => { - return BlocksoftUtils.diff(b.value, a.value) * 1; - }); - // @ts-ignore - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' DogeTransferProcessor.getFeeRate unspents sorted', - unspents - ); - } else { - // @ts-ignore - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' DogeTransferProcessor.getFeeRate unspents no need to sort', - unspents - ); - } - - const result: AirDAOBlockchainTypes.FeeRateResult = { - selectedFeeIndex: -1, - fees: [] as AirDAOBlockchainTypes.Fee[] - } as AirDAOBlockchainTypes.FeeRateResult; - - const keys = ['speed_blocks_12', 'speed_blocks_6', 'speed_blocks_2']; - const checkedPrices = {}; - let prevFeeForByte = 0; - - if (transactionSpeedUp) { - autocorrectFee = true; - } - - if (isStaticFee && feeStaticReadable) { - const newPrices = {}; - for (const key of keys) { - if (typeof feeStaticReadable[key] === 'undefined') continue; - newPrices[key] = prices[key]; - } - prices = newPrices; - } - - const stepSatoshi = transactionRemoveByFee - ? this._builderSettings.minRbfStepSatoshi * 2 - : this._builderSettings.minRbfStepSatoshi; - let pricesTotal = 0; - for (const key of keys) { - // @ts-ignore - if (typeof prices[key] === 'undefined' || !prices[key]) continue; - pricesTotal++; - // @ts-ignore - let feeForByte = prices[key]; - if (typeof additionalData.feeForByte === 'undefined') { - if (transactionReplaceByFee || transactionRemoveByFee) { - if ( - typeof data.transactionJson !== 'undefined' && - data.transactionJson !== null && - typeof data.transactionJson.feeForByte !== 'undefined' - ) { - if (feeForByte * 1 < data.transactionJson.feeForByte * 1) { - feeForByte = Math.ceil( - data.transactionJson.feeForByte * 1 + stepSatoshi - ); - } - } else { - feeForByte = Math.ceil(feeForByte * 1 + stepSatoshi); - } - if (feeForByte * 1 <= prevFeeForByte * 1) { - feeForByte = Math.ceil(prevFeeForByte * 1 + stepSatoshi); - } - } else if (transactionSpeedUp) { - feeForByte = Math.ceil( - feeForByte * this._builderSettings.minSpeedUpMulti - ); - if (feeForByte * 1 <= prevFeeForByte * 1) { - feeForByte = Math.ceil(prevFeeForByte * 1.2); - } - } - } - // @ts-ignore - checkedPrices[key] = feeForByte; - prevFeeForByte = feeForByte; - } - - const uniqueFees = {}; - const allFees = {}; - let isError = false; - for (const key of keys) { - // @ts-ignore - if (typeof checkedPrices[key] === 'undefined' || !checkedPrices[key]) - continue; - // @ts-ignore - const feeForByte = checkedPrices[key]; - let preparedInputsOutputs; - const subtitle = 'getFeeRate_' + key + ' ' + feeForByte; - let blocks = '2'; - let autoFeeLimitReadable = this._builderSettings.feeMaxAutoReadable2; - if (key === 'speed_blocks_6') { - blocks = '6'; - autoFeeLimitReadable = this._builderSettings.feeMaxAutoReadable6; - } else if (key === 'speed_blocks_12') { - blocks = '12'; - autoFeeLimitReadable = this._builderSettings.feeMaxAutoReadable12; - } - - let logInputsOutputs, - blockchainData, - txSize, - actualFeeForByte, - actualFeeForByteNotRounded; - try { - if (isStaticFee) { - preparedInputsOutputs = - await this.txPrepareInputsOutputs.getInputsOutputs( - data, - unspents, - { - feeForByte: 'none', - feeForAll: BlocksoftUtils.fromUnified( - feeStaticReadable['speed_blocks_' + blocks], - this._settings.decimals - ), - feeForAllInputs: feeStaticReadable.feeForAllInputs, - autoFeeLimitReadable - }, - additionalData, - subtitle - ); - let newStatic = 0; - if ( - !data.isTransferAll && - preparedInputsOutputs.inputs && - preparedInputsOutputs.inputs.length > - feeStaticReadable.feeForAllInputs * 1 - ) { - newStatic = BlocksoftUtils.mul( - feeStaticReadable['speed_blocks_' + blocks], - Math.ceil( - preparedInputsOutputs.inputs.length / - feeStaticReadable.feeForAllInputs - ) - ); - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' DogeTransferProcessor.getFeeRate_' + - key + - ' inputs ' + - preparedInputsOutputs.inputs.length + - ' newStatic ' + - newStatic - ); - preparedInputsOutputs = - await this.txPrepareInputsOutputs.getInputsOutputs( - data, - unspents, - { - feeForByte: 'none', - feeForAll: BlocksoftUtils.fromUnified( - newStatic, - this._settings.decimals - ), - feeForAllInputs: feeStaticReadable.feeForAllInputs, - autoFeeLimitReadable - }, - additionalData, - subtitle - ); - } - } else { - preparedInputsOutputs = - await this.txPrepareInputsOutputs.getInputsOutputs( - data, - unspents, - { - feeForByte, - autoFeeLimitReadable - }, - additionalData, - subtitle - ); - } - - if ( - typeof additionalData.feeForByte === 'undefined' && - typeof this._builderSettings.feeMinTotalReadable !== 'undefined' - ) { - logInputsOutputs = DogeLogs.logInputsOutputs( - data, - unspents, - preparedInputsOutputs, - this._settings, - subtitle - ); - if ( - logInputsOutputs.diffInOutReadable * 1 < - this._builderSettings.feeMinTotalReadable - ) { - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' DogeTransferProcessor.getFeeRate_' + - key + - ' ' + - feeForByte + - ' less minTotalReadable ' + - logInputsOutputs.diffInOutReadable - ); - preparedInputsOutputs = - await this.txPrepareInputsOutputs.getInputsOutputs( - data, - unspents, - { - feeForAll: BlocksoftUtils.fromUnified( - this._builderSettings.feeMinTotalReadable, - this._settings.decimals - ), - autoFeeLimitReadable - }, - additionalData, - subtitle - ); - autocorrectFee = false; - } - } - // @ts-ignore - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' DogeTransferProcessor.getFeeRate_' + - key + - ' ' + - feeForByte + - ' preparedInputsOutputs addressTo' + - data.addressTo, - preparedInputsOutputs - ); - if (preparedInputsOutputs.inputs.length === 0) { - // do noting - continue; - } - } catch (e) { - throw e; - } - - try { - let doBuild = false; - let actualFeeRebuild = false; - do { - doBuild = false; - logInputsOutputs = DogeLogs.logInputsOutputs( - data, - unspents, - preparedInputsOutputs, - this._settings, - subtitle - ); - blockchainData = await this.txBuilder.getRawTx( - data, - privateData, - preparedInputsOutputs - ); - txSize = Math.ceil(blockchainData.rawTxHex.length / 2); - actualFeeForByteNotRounded = BlocksoftUtils.div( - logInputsOutputs.diffInOut, - txSize - ); - actualFeeForByte = Math.floor(actualFeeForByteNotRounded); - let needAutoCorrect = false; - if (autocorrectFee && !isStaticFee) { - needAutoCorrect = - actualFeeForByte.toString() !== feeForByte.toString(); - } - if (!actualFeeRebuild && needAutoCorrect) { - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' DogeTransferProcessor.getFeeRate will correct as ' + - actualFeeForByte.toString() + - ' != ' + - feeForByte.toString() - ); - let outputForCorrecting = -1; - for ( - let i = 0, ic = preparedInputsOutputs.outputs.length; - i < ic; - i++ - ) { - const output = preparedInputsOutputs.outputs[i]; - if (typeof output.isUsdt !== 'undefined') continue; - if (typeof output.isChange !== 'undefined' && output.isChange) { - outputForCorrecting = i; - } - } - if (outputForCorrecting >= 0) { - const diff = BlocksoftUtils.diff( - actualFeeForByteNotRounded.toString(), - feeForByte.toString() - ); - - const part = BlocksoftUtils.mul( - txSize.toString(), - diff.toString() - ).toString(); - let newAmount; - if (part.indexOf('-') === 0) { - newAmount = BlocksoftUtils.diff( - preparedInputsOutputs.outputs[outputForCorrecting].amount, - part.replace('-', '') - ); - } else { - newAmount = BlocksoftUtils.add( - preparedInputsOutputs.outputs[outputForCorrecting].amount, - part - ); - } - - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' DogeTransferProcessor.getFeeRate diff ' + - diff + - ' part ' + - part + - ' amount ' + - preparedInputsOutputs.outputs[outputForCorrecting].amount + - ' => ' + - newAmount - ); - - // @ts-ignore - if (newAmount * 1 < 0) { - preparedInputsOutputs.outputs[outputForCorrecting].amount = - 'removed'; - outputForCorrecting = -1; - } else { - preparedInputsOutputs.outputs[outputForCorrecting].amount = - newAmount; - } - } - doBuild = true; - actualFeeRebuild = true; - if (outputForCorrecting === -1) { - let foundToMore = false; - for (let i = 0, ic = unspents.length; i < ic; i++) { - const unspent = unspents[i]; - if (unspent.confirmations > 0 && !unspent.isRequired) { - unspents[i].isRequired = true; - foundToMore = true; - } - } - if (!foundToMore) { - for (let i = 0, ic = unspents.length; i < ic; i++) { - const unspent = unspents[i]; - if (!unspent.isRequired) { - unspents[i].isRequired = true; - foundToMore = true; - } - } - } - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' DogeTransferProcessor.getFeeRate foundToMore ' + - JSON.stringify(foundToMore) - ); - if (foundToMore) { - try { - const preparedInputsOutputs2 = - await this.txPrepareInputsOutputs.getInputsOutputs( - data, - unspents, - { - feeForByte, - autoFeeLimitReadable - }, - additionalData, - subtitle + ' foundToMore' - ); - actualFeeRebuild = false; - if (preparedInputsOutputs2.inputs.length > 0) { - preparedInputsOutputs = preparedInputsOutputs2; - } - } catch (e) { - // do nothing - } - } - } - } - } while (doBuild); - } catch (e) { - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' DogeTransferProcessor.getRawTx error ' + - e.message - ); - if (e.message.indexOf('Transaction has absurd fees') !== -1) { - isError = 'SERVER_RESPONSE_TOO_BIG_FEE_PER_BYTE_FOR_TRANSACTION'; - continue; - } else { - throw e; - } - } - - isError = false; - // @ts-ignore - blockchainData.isTransferAll = data.isTransferAll; - blockchainData.isRBFed = { - transactionRemoveByFee, - transactionReplaceByFee, - transactionSpeedUp - }; - - allFees[this._langPrefix + '_' + key] = logInputsOutputs.diffInOut; - if (typeof uniqueFees[logInputsOutputs.diffInOut] !== 'undefined') { - continue; - } - result.fees.push({ - langMsg: this._langPrefix + '_' + key, - feeForByte: actualFeeForByte.toString(), - needSpeed: feeForByte.toString(), - feeForTx: logInputsOutputs.diffInOut, - amountForTx: logInputsOutputs.sendBalance, - addressToTx: data.addressTo, - blockchainData - }); - uniqueFees[logInputsOutputs.diffInOut] = true; - } - if (isError) { - throw new Error(isError); - } - result.selectedFeeIndex = result.fees.length - 1; - - if (!transactionReplaceByFee && !transactionRemoveByFee && !isStaticFee) { - if ( - typeof allFees[this._langPrefix + '_speed_blocks_2'] === 'undefined' - ) { - result.showSmallFeeNotice = new Date().getTime(); - } - } - if (result.fees.length === 0) { - result.amountForTx = 0; - } - result.additionalData = { unspents }; - const logResult = { - selectedFeeIndex: result.selectedFeeIndex - ? result.selectedFeeIndex - : 'none', - showSmallFeeNotice: result.showSmallFeeNotice - ? result.showSmallFeeNotice - : 'none', - allFees: allFees, - fees: [] - }; - if (result.fees) { - for (const fee of result.fees) { - if (totalUnspents && totalUnspents > unspents.length) { - fee.blockchainData.countedForLessOutputs = totalUnspents; - } - const logFee = { ...fee }; - delete logFee.blockchainData; - logResult.fees.push(logFee); - } - } - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' DogeTransferProcessor.getFees ' + - JSON.stringify(logResult) - ); - return result; - } - - async getTransferAllBalance( - data: AirDAOBlockchainTypes.TransferData, - privateData: AirDAOBlockchainTypes.TransferPrivateData, - additionalData: AirDAOBlockchainTypes.TransferAdditionalData = {} - ): Promise { - data.isTransferAll = true; - const result = await this.getFeeRate(data, privateData, additionalData); - // @ts-ignore - if (!result || result.selectedFeeIndex < 0) { - return { - selectedTransferAllBalance: '0', - selectedFeeIndex: -2, - fees: [], - countedForBasicBalance: data.amount - }; - } - // @ts-ignore - return { - ...result, - selectedTransferAllBalance: - result.fees[result.selectedFeeIndex].amountForTx, - countedForBasicBalance: data.amount - }; - } - - async sendTx( - data: AirDAOBlockchainTypes.TransferData, - privateData: AirDAOBlockchainTypes.TransferPrivateData, - uiData: AirDAOBlockchainTypes.TransferUiData - ): Promise { - if ( - typeof uiData.selectedFee.blockchainData === 'undefined' && - typeof uiData.selectedFee.feeForTx === 'undefined' - ) { - throw new Error('SERVER_RESPONSE_PLEASE_SELECT_FEE'); - } - - if (typeof privateData.privateKey === 'undefined') { - throw new Error('DOGE transaction required privateKey'); - } - if (typeof data.addressTo === 'undefined') { - throw new Error('DOGE transaction required addressTo'); - } - - let txRBFed = ''; - let txRBF = false; - if ( - typeof data.transactionRemoveByFee !== 'undefined' && - data.transactionRemoveByFee - ) { - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' DogeTransferProcessor.sendTx remove started ' + - data.transactionRemoveByFee - ); - txRBF = data.transactionRemoveByFee; - txRBFed = 'RBFremoved'; - } else if ( - typeof data.transactionReplaceByFee !== 'undefined' && - data.transactionReplaceByFee - ) { - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' DogeTransferProcessor.sendTx resend started ' + - data.transactionReplaceByFee - ); - txRBF = data.transactionReplaceByFee; - txRBFed = 'RBFed'; - } else { - AirDAOCryptoLog.log( - this._settings.currencyCode + ' DogeTransferProcessor.sendTx started' - ); - txRBFed = 'usualSend'; - } - - const logData = {}; - logData.currencyCode = this._settings.currencyCode; - logData.selectedFee = uiData.selectedFee; - logData.from = data.addressFrom; - logData.basicAddressTo = data.addressTo; - logData.basicAmount = data.amount; - logData.pushLocale = 'en'; // TODO - logData.pushSetting = await Database.localStorage.get('transactionsNotifs'); - - if ( - typeof uiData !== 'undefined' && - typeof uiData.selectedFee !== 'undefined' && - typeof uiData.selectedFee.rawOnly !== 'undefined' && - uiData.selectedFee.rawOnly - ) { - return { - rawOnly: uiData.selectedFee.rawOnly, - raw: uiData.selectedFee.blockchainData.rawTxHex - }; - } - - let result = {} as AirDAOBlockchainTypes.SendTxResult; - try { - result = await this.sendProvider.sendTx( - uiData.selectedFee.blockchainData.rawTxHex, - txRBFed, - txRBF, - logData - ); - } catch (e) { - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' DogeTransferProcessor.sent error ' + - e.message - ); - throw e; - } - - try { - result.transactionFee = uiData.selectedFee.feeForTx; - result.transactionFeeCurrencyCode = this._settings.currencyCode; - result.transactionJson = { - nSequence: uiData.selectedFee.blockchainData.nSequence, - txAllowReplaceByFee: - uiData.selectedFee.blockchainData.txAllowReplaceByFee, - feeForByte: uiData.selectedFee.feeForByte - }; - if ( - typeof uiData.selectedFee.amountForTx !== 'undefined' && - uiData.selectedFee.amountForTx - ) { - result.amountForTx = uiData.selectedFee.amountForTx; - } - if (txRBF) { - await DogeRawDS.cleanRaw({ - address: data.addressFrom, - currencyCode: this._settings.currencyCode, - transactionHash: txRBF - }); - } - const transactionLog = - typeof result.logData !== 'undefined' ? result.logData : logData; - const inputsLog = JSON.stringify( - uiData.selectedFee.blockchainData.preparedInputsOutputs.inputs - ); - const transactionRaw = uiData.selectedFee.blockchainData.rawTxHex + ''; - //if (typeof transactionLog.selectedFee !== 'undefined' && typeof transactionLog.selectedFee.blockchainData !== 'undefined') { - // transactionLog.selectedFee.blockchainData = '*' - //} - await DogeRawDS.saveRaw({ - address: data.addressFrom, - currencyCode: this._settings.currencyCode, - transactionHash: result.transactionHash, - transactionRaw, - transactionLog - }); - AirDAOCryptoLog.log( - this._settings.currencyCode + ' DogeTransferProcessor.sendTx hex ', - uiData.selectedFee.blockchainData.rawTxHex - ); - // @ts-ignore - AirDAOCryptoLog.log( - this._settings.currencyCode + ' DogeTransferProcessor.sendTx result ', - result - ); - await DogeRawDS.saveInputs({ - address: data.addressFrom, - currencyCode: this._settings.currencyCode, - transactionHash: result.transactionHash, - transactionRaw: inputsLog - }); - await DogeRawDS.saveJson({ - address: data.addressFrom, - currencyCode: this._settings.currencyCode, - transactionHash: result.transactionHash, - transactionRaw: JSON.stringify(result.transactionJson) - }); - - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' DogeTransferProcessor.sent ' + - data.addressFrom + - ' done ' + - JSON.stringify(result.transactionJson) - ); - } catch (e) { - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' DogeTransferProcessor.sent error additional' + - e.message - ); - } - return result; - } - - async sendRawTx( - data: AirDAOBlockchainTypes.DbAccount, - rawTxHex: string, - txRBF: any, - logData: any - ): Promise { - this._initProviders(); - const result = await this.sendProvider.sendTx( - rawTxHex, - 'rawSend', - txRBF, - logData - ); - return result.transactionHash; - } - - async setMissingTx( - data: AirDAOBlockchainTypes.DbAccount, - transaction: AirDAOBlockchainTypes.DbTransaction - ): Promise { - DogeRawDS.cleanRaw({ - address: data.address, - transactionHash: transaction.transactionHash, - currencyCode: this._settings.currencyCode - }); - return true; - } - - canRBF( - data: AirDAOBlockchainTypes.DbAccount, - transaction: AirDAOBlockchainTypes.DbTransaction - ): boolean { - if (transaction.transactionDirection === 'income') { - return true; - } - if (typeof transaction.transactionJson !== 'undefined') { - // console.log('transaction.transactionJson', JSON.stringify(transaction.transactionJson)) - } - return true; - } -} diff --git a/crypto/blockchains/doge/basic/DogeFindAddressFunction.ts b/crypto/blockchains/doge/basic/DogeFindAddressFunction.ts deleted file mode 100644 index 410eeaaba..000000000 --- a/crypto/blockchains/doge/basic/DogeFindAddressFunction.ts +++ /dev/null @@ -1,152 +0,0 @@ -/** - * @version 0.5 - * @param {string} addresses[] - * @param {string} transaction.hex - * @param {string} transaction.address - * @param {string} transaction.vin[].txid aa31777a9db759f57fd243ef47419939f233d16bc3e535e9a1c5af3ace87cb54 - * @param {string} transaction.vin[].sequence 4294967294 - * @param {string} transaction.vin[].n 0 - * @param {string} transaction.vin[].addresses [ 'DFDn5QyHH9DiFBNFGMcyJT5uUpDvmBRDqH' ] - * @param {string} transaction.vin[].addr '1HQzoxQsbjm44hc9rcJyX9KVmNAsyWyswB' - * @param {string} transaction.vin[].value 44400000000 - * @param {string} transaction.vin[].hex 47304402200826f97d3432452abedd4346553de0b0c2d401ad7056b155e6462484afd98aa902202b5fb3166b96ded33249aecad7c667c0870c1 - * @param {string} transaction.vout[].value 59999824800 - * @param {string} transaction.vout[].n 0 - * @param {string} transaction.vout[].spent true - * @param {string} transaction.vout[].hex 76a91456d49605503d4770cf1f32fbfb69676d9a72554f88ac - * @param {string} transaction.vout[].addresses [ 'DD4DKVTEkRUGs7qzN8b7q5LKmoE9mXsJk4' ] - * @param {string} transaction.vout[].scriptPubKey.addresses[] [ '1HXmWQShG2VZ2GXb8J2CZmVTEoDUmeKyAQ' ] - * @returns {Promise<{from: string, to: string, value: number, direction: string}>} - * @constructor - */ -import BlocksoftBN from '../../../common/AirDAOBN'; -import { BlockchainTransaction } from '@appTypes'; - -export default async function DogeFindAddressFunction( - addresses: string[], - transaction: BlockchainTransaction -): Promise<{ from: string; to: string; value: number; direction: string }> { - const inputMyBN = new BlocksoftBN(0); - const inputOthersBN = new BlocksoftBN(0); - const inputOthersAddresses = []; - const uniqueTmp = {}; - - const address1 = addresses[0]; - const address2 = - typeof addresses[1] !== 'undefined' ? addresses[1] : addresses[0]; - const address3 = addresses[addresses.length - 1]; // three is max now - if (transaction.vin) { - for (let i = 0, ic = transaction.vin.length; i < ic; i++) { - let vinAddress; - let vinValue = transaction.vin[i].value; - if (typeof transaction.vin[i].addresses !== 'undefined') { - vinAddress = transaction.vin[i].addresses[0]; - } else if (typeof transaction.vin[i].addr !== 'undefined') { - vinAddress = transaction.vin[i].addr; - } else if (typeof transaction.vin[i].prevout !== 'undefined') { - if ( - typeof transaction.vin[i].prevout.scriptpubkey_address !== 'undefined' - ) { - vinAddress = transaction.vin[i].prevout.scriptpubkey_address; - } - if (typeof transaction.vin[i].prevout.value !== 'undefined') { - vinValue = transaction.vin[i].prevout.value; - } - } - if ( - vinAddress === address1 || - vinAddress === address2 || - vinAddress === address3 - ) { - inputMyBN.add(vinValue); - } else { - if (typeof uniqueTmp[vinAddress] === 'undefined') { - uniqueTmp[vinAddress] = 1; - inputOthersAddresses.push(vinAddress); - } - inputOthersBN.add(vinValue); - } - } - } - - const outputMyBN = new BlocksoftBN(0); - const outputOthersBN = new BlocksoftBN(0); - const outputOthersAddresses = []; - const uniqueTmp2 = {}; - - if (transaction.vout) { - for (let j = 0, jc = transaction.vout.length; j < jc; j++) { - let voutAddress; - const voutValue = transaction.vout[j].value; - if (typeof transaction.vout[j].addresses !== 'undefined') { - voutAddress = transaction.vout[j].addresses[0]; - } else if ( - typeof transaction.vout[j].scriptPubKey !== 'undefined' && - typeof transaction.vout[j].scriptPubKey.addresses !== 'undefined' - ) { - voutAddress = transaction.vout[j].scriptPubKey.addresses[0]; - } else if ( - typeof transaction.vout[j].scriptpubkey_address !== 'undefined' - ) { - voutAddress = transaction.vout[j].scriptpubkey_address; - } - if ( - voutAddress === address1 || - voutAddress === address2 || - voutAddress === address3 - ) { - outputMyBN.add(voutValue); - } else { - if (typeof uniqueTmp2[voutAddress] === 'undefined') { - uniqueTmp2[voutAddress] = 1; - outputOthersAddresses.push(voutAddress); - } - outputOthersBN.add(voutValue); - } - } - } - - let output; - if (inputMyBN.get() === '0') { - // my only in output - output = { - direction: 'income', - from: - inputOthersAddresses.length > 0 ? inputOthersAddresses.join(',') : '', - to: '', // address1, - value: outputMyBN.get() - }; - } else if (outputMyBN.get() === '0') { - // my only in input - output = { - direction: 'outcome', - from: '', // address1, - to: - outputOthersAddresses.length > 0 ? outputOthersAddresses.join(',') : '', - value: - inputOthersBN.get() === '0' ? outputOthersBN.get() : inputMyBN.get() - }; - } else { - // both input and output - if (outputOthersAddresses.length > 0) { - // there are other address - output = { - direction: 'outcome', - from: '', // address1, - to: outputOthersAddresses.join(','), - value: outputOthersBN.get() - }; - } else { - output = { - direction: 'self', - from: '', // address1, - to: '', // address1, - value: inputMyBN.diff(outputMyBN).get() - }; - } - } - output.from = output.from.substr(0, 255); - output.to = output.to.substr(0, 255); - - return output; -} diff --git a/crypto/blockchains/doge/basic/DogeLogs.ts b/crypto/blockchains/doge/basic/DogeLogs.ts deleted file mode 100644 index 9dfcc2405..000000000 --- a/crypto/blockchains/doge/basic/DogeLogs.ts +++ /dev/null @@ -1,104 +0,0 @@ -/* eslint-disable @typescript-eslint/no-namespace */ -/** - * @version 0.20 - */ -import { AirDAOBlockchainTypes } from '../../AirDAOBlockchainTypes'; -import BlocksoftBN from '../../../common/AirDAOBN'; -import BlocksoftUtils from '../../../common/AirDAOUtils'; -import AirDAOCryptoLog from '../../../common/AirDAOCryptoLog'; - -export namespace DogeLogs { - export const logInputsOutputs = function ( - data: AirDAOBlockchainTypes.TransferData, - unspents: AirDAOBlockchainTypes.UnspentTx[], - preparedInputsOutputs: { - inputs: AirDAOBlockchainTypes.UnspentTx[]; - outputs: AirDAOBlockchainTypes.OutputTx[]; - multiAddress: []; - msg: string; - }, - settings: any, - title: string - ): any { - const logInputsOutputs = { - inputs: [], - outputs: [], - totalIn: 0, - totalOut: 0, - diffInOut: 0, - msg: preparedInputsOutputs.msg || 'none' - }; - const totalInBN = new BlocksoftBN(0); - const totalOutBN = new BlocksoftBN(0); - const totalBalanceBN = new BlocksoftBN(0); - if (typeof unspents !== 'undefined' && unspents && unspents.length > 0) { - for (const unspent of unspents) { - totalBalanceBN.add(unspent.value); - } - } - - const leftBalanceBN = new BlocksoftBN(totalBalanceBN); - const sendBalanceBN = new BlocksoftBN(0); - let input, output; - if (preparedInputsOutputs) { - for (input of preparedInputsOutputs.inputs) { - logInputsOutputs.inputs.push({ - txid: input.txid, - vout: input.vout, - value: input.value, - confirmations: input.confirmations, - address: input.address || 'none' - }); - totalInBN.add(input.value); - leftBalanceBN.diff(input.value); - } - for (output of preparedInputsOutputs.outputs) { - if (output.amount === 'removed') continue; - logInputsOutputs.outputs.push(output); - totalOutBN.add(output.amount); - if (typeof output.isChange === 'undefined' || !output.isChange) { - sendBalanceBN.add(output.amount); - } - } - } - logInputsOutputs.totalIn = totalInBN.get(); - logInputsOutputs.totalOut = totalOutBN.get(); - logInputsOutputs.diffInOut = totalInBN.diff(totalOutBN).get(); - logInputsOutputs.diffInOutReadable = BlocksoftUtils.toUnified( - logInputsOutputs.diffInOut, - settings.decimals - ); - - const tmpBN = new BlocksoftBN(totalOutBN).diff(data.amount); - if (logInputsOutputs.diffInOut > 0) { - tmpBN.add(logInputsOutputs.diffInOut); - } - logInputsOutputs.totalOutMinusAmount = tmpBN.get(); - logInputsOutputs.totalBalance = totalBalanceBN.get(); - logInputsOutputs.leftBalance = leftBalanceBN.get(); - logInputsOutputs.leftBalanceAndChange = BlocksoftUtils.add( - leftBalanceBN, - tmpBN - ); - logInputsOutputs.sendBalance = sendBalanceBN.get(); - - logInputsOutputs.data = JSON.parse(JSON.stringify(data)); - if ( - typeof data.feeForTx === 'undefined' || - typeof data.feeForTx.feeForByte === 'undefined' || - data.feeForTx.feeForByte < 0 - ) { - AirDAOCryptoLog.log( - title + ' preparedInputsOutputs with autofee ', - logInputsOutputs - ); - } else { - AirDAOCryptoLog.log( - title + ' preparedInputsOutputs with fee ' + data.feeForTx.feeForTx, - logInputsOutputs - ); - } - // console.log('btc_info ' + this._settings.currencyCode + ' ' + data.addressFrom + ' => ' + data.addressTo, logInputsOutputs) - return logInputsOutputs; - }; -} diff --git a/crypto/blockchains/doge/basic/DogeNetworkPrices.ts b/crypto/blockchains/doge/basic/DogeNetworkPrices.ts deleted file mode 100644 index e50132b16..000000000 --- a/crypto/blockchains/doge/basic/DogeNetworkPrices.ts +++ /dev/null @@ -1,45 +0,0 @@ -/** - * @version 0.20 - **/ -import { AirDAOBlockchainTypes } from '../../AirDAOBlockchainTypes'; -import AirDAOCryptoLog from '../../../common/AirDAOCryptoLog'; -import BlocksoftExternalSettings from '../../../common/AirDAOExternalSettings'; - -export default class DogeNetworkPrices - implements AirDAOBlockchainTypes.NetworkPrices -{ - async getNetworkPrices(currencyCode: string): Promise<{ - speed_blocks_2: number; - speed_blocks_6: number; - speed_blocks_12: number; - }> { - AirDAOCryptoLog.log(currencyCode + ' DogeNetworkPricesProvider '); - - const externalSettings = await BlocksoftExternalSettings.getAll( - 'DOGE.getNetworkPrices' - ); - - // @ts-ignore - if ( - !externalSettings || - typeof externalSettings[currencyCode] === 'undefined' - ) { - throw new Error( - currencyCode + - ' DogeNetworkPricesProvider ' + - currencyCode + - ' not defined' - ); - } - - const prices = { - // @ts-ignore - speed_blocks_2: externalSettings[currencyCode]['2'] || 0, - // @ts-ignore - speed_blocks_6: externalSettings[currencyCode]['6'] || 0, - // @ts-ignore - speed_blocks_12: externalSettings[currencyCode]['12'] || 0 - }; - return prices; - } -} diff --git a/crypto/blockchains/doge/providers/DogeSendProvider.ts b/crypto/blockchains/doge/providers/DogeSendProvider.ts deleted file mode 100644 index 5e6c0bee3..000000000 --- a/crypto/blockchains/doge/providers/DogeSendProvider.ts +++ /dev/null @@ -1,146 +0,0 @@ -/** - * @version 0.20 - * https://github.com/trezor/blockbook/blob/master/docs/api.md - */ -import { AirDAOBlockchainTypes } from '../../AirDAOBlockchainTypes'; -import AirDAOCryptoLog from '../../../common/AirDAOCryptoLog'; -import AirDAOAxios from '../../../common/AirDAOAxios'; -import BlocksoftExternalSettings from '../../../common/AirDAOExternalSettings'; - -export default class DogeSendProvider - implements AirDAOBlockchainTypes.SendProvider -{ - protected _trezorServerCode = ''; - - private _trezorServer = ''; - - protected _settings: AirDAOBlockchainTypes.CurrencySettings; - - constructor( - settings: AirDAOBlockchainTypes.CurrencySettings, - serverCode: string - ) { - this._settings = settings; - this._trezorServerCode = serverCode; - } - - async _check(hex: string, subtitle: string, txRBF: any, logData: any) { - return {}; - } - - async _checkError(hex: string, subtitle: string, txRBF: any, logData: any) { - return {}; - } - - async _checkSuccess( - transactionHash: string, - hex: string, - subtitle: string, - txRBF: any, - logData: any - ) { - return {}; - } - - async sendTx( - hex: string, - subtitle: string, - txRBF: any, - logData: any - ): Promise<{ transactionHash: string; transactionJson: any }> { - await AirDAOCryptoLog.log( - this._settings.currencyCode + - ' DogeSendProvider.sendTx ' + - subtitle + - ' started ', - logData - ); - - let link = BlocksoftExternalSettings.getStatic( - this._trezorServerCode + '_SEND_LINK' - ); - if (!link || link === '') { - this._trezorServer = await BlocksoftExternalSettings.getTrezorServer( - this._trezorServerCode, - 'DOGE.Send.sendTx' - ); - link = this._trezorServer + '/api/v2/sendtx/'; - } - - logData = await this._check(hex, subtitle, txRBF, logData); - - let res; - try { - res = await AirDAOAxios.post(link, hex); - } catch (e) { - if (subtitle.indexOf('rawSend') !== -1) { - throw e; - } - try { - logData.error = e.message; - await this._checkError(hex, subtitle, txRBF, logData); - } catch (e2) { - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' DogeSendProvider.send proxy error errorTx ' + - e2.message - ); - } - if ( - this._settings.currencyCode === 'USDT' && - e.message.indexOf('bad-txns-in-belowout') !== -1 - ) { - throw new Error('SERVER_RESPONSE_NOT_ENOUGH_FEE'); - } else if (e.message.indexOf('transaction already in block') !== -1) { - throw new Error('SERVER_RESPONSE_TRANSACTION_ALREADY_MINED'); - } else if (e.message.indexOf('inputs-missingorspent') !== -1) { - throw new Error('SERVER_RESPONSE_TRANSACTION_ALREADY_MINED'); - } else if (e.message.indexOf('insufficient priority') !== -1) { - throw new Error('SERVER_RESPONSE_NO_RESPONSE_OR_MORE_FEE'); - } else if (e.message.indexOf('dust') !== -1) { - throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_DUST'); - } else if ( - e.message.indexOf('bad-txns-inputs-spent') !== -1 || - e.message.indexOf('txn-mempool-conflict') !== -1 - ) { - throw new Error('SERVER_RESPONSE_NO_RESPONSE'); - } else if ( - e.message.indexOf('min relay fee not met') !== -1 || - e.message.indexOf('fee for relay') !== -1 - ) { - throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_FEE'); - } else if ( - e.message.indexOf('insufficient fee, rejecting replacement') !== -1 - ) { - throw new Error( - 'SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_FEE_FOR_REPLACEMENT' - ); - } else if (e.message.indexOf('insufficient fee') !== -1) { - throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_FEE'); - } else if (e.message.indexOf('too-long-mempool-chain') !== -1) { - throw new Error('SERVER_RESPONSE_NO_RESPONSE'); - } else { - await BlocksoftExternalSettings.setTrezorServerInvalid( - this._trezorServerCode, - this._trezorServer - ); - e.message += ' link: ' + link; - throw e; - } - } - if (typeof res.data.result === 'undefined' || !res.data.result) { - throw new Error('SERVER_RESPONSE_NOT_CONNECTED'); - } - - const transactionHash = res.data.result; - logData = await this._checkSuccess( - transactionHash, - hex, - subtitle, - txRBF, - logData - ); - - return { transactionHash, transactionJson: {}, logData }; - } -} diff --git a/crypto/blockchains/doge/providers/DogeUnspentsProvider.ts b/crypto/blockchains/doge/providers/DogeUnspentsProvider.ts deleted file mode 100644 index 07f06cfd9..000000000 --- a/crypto/blockchains/doge/providers/DogeUnspentsProvider.ts +++ /dev/null @@ -1,274 +0,0 @@ -/** - * @version 0.20 - * https://github.com/trezor/blockbook/blob/master/docs/api.md - * https://doge1.trezor.io/api/v2/utxo/D5oKvWEibVe74CXLASmhpkRpLoyjgZhm71 - */ -import { AirDAOBlockchainTypes } from '../../AirDAOBlockchainTypes'; -import AirDAOCryptoLog from '../../../common/AirDAOCryptoLog'; -import AirDAOAxios from '../../../common/AirDAOAxios'; -import BlocksoftExternalSettings from '../../../common/AirDAOExternalSettings'; -import DogeRawDS from '../stores/DogeRawDS'; - -export default class DogeUnspentsProvider - implements AirDAOBlockchainTypes.UnspentsProvider -{ - private _trezorServerCode = ''; - - private _trezorServer = ''; - - protected _settings: AirDAOBlockchainTypes.CurrencySettings; - - constructor( - settings: AirDAOBlockchainTypes.CurrencySettings, - serverCode: string - ) { - this._settings = settings; - this._trezorServerCode = serverCode; - } - - async getUnspents( - address: string - ): Promise { - // @ts-ignore - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' DogeUnspentsProvider.getUnspents started ' + - address - ); - this._trezorServer = await BlocksoftExternalSettings.getTrezorServer( - this._trezorServerCode, - 'DOGE.Unspents.getUnspents' - ); - let link = BlocksoftExternalSettings.getStatic( - this._trezorServerCode + '_UNSPENDS_LINK' - ); - if (!link || link === '') { - link = this._trezorServer + '/api/v2/utxo/' + address + '?gap=9999'; - } - - const res = await AirDAOAxios.getWithoutBraking(link); - // @ts-ignore - if (!res || typeof res.data === 'undefined') { - await BlocksoftExternalSettings.setTrezorServerInvalid( - this._trezorServerCode, - this._trezorServer - ); - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' DogeUnspentsProvider.getUnspents nothing loaded for address ' + - address + - ' link ' + - link - ); - throw new Error('SERVER_RESPONSE_NOT_CONNECTED'); - } - // @ts-ignore - if (!res.data || typeof res.data[0] === 'undefined') { - return []; - } - const sortedUnspents = []; - let unspent; - // @ts-ignore - for (unspent of res.data) { - if (typeof unspent.path !== 'undefined') { - unspent.derivationPath = unspent.path; - } - sortedUnspents.push(unspent); - } - return sortedUnspents; - } - - _isMyAddress( - voutAddress: string, - address: string, - walletHash: string - ): string { - return voutAddress === address ? address : ''; - } - - async getTx( - tx: string, - address: string, - allUnspents: AirDAOBlockchainTypes.UnspentTx[], - walletHash: string - ): Promise { - AirDAOCryptoLog.log( - this._settings.currencyCode + ' DogeUnspentsProvider.getTx started ' + tx - ); - - this._trezorServer = await BlocksoftExternalSettings.getTrezorServer( - this._trezorServerCode, - 'Doge.Unspents.getTx' - ); - - let saved = await DogeRawDS.getInputs({ - currencyCode: this._settings.currencyCode, - transactionHash: tx - }); - AirDAOCryptoLog.log( - this._settings.currencyCode + ' DogeUnspentsProvider.getTx inputs ' + tx, - saved - ); - - let recheckInputs = false; - if (saved) { - if (typeof saved.inputs !== 'undefined') { - saved = saved.inputs; - } - } else { - const link = this._trezorServer + '/api/v2/tx/' + tx; - const res = await AirDAOAxios.getWithoutBraking(link); - // @ts-ignore - if (!res || typeof res.data === 'undefined' || !res.data) { - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' DogeUnspentsProvider.getTx no tx ' + - tx - ); - throw new Error('SERVER_RESPONSE_BAD_TX_TO_REPLACE'); - } - // @ts-ignore - saved = res.data.vin; - recheckInputs = true; - } - - const sortedUnspents = []; - const unique = {}; - if (allUnspents) { - for (const unspent of allUnspents) { - if (unspent.txid === tx) continue; - if (typeof unspent.vout === 'undefined') { - // @ts-ignore - unspent.vout = unspent.n; - } - const key = unspent.txid + '_' + unspent.vout; - // @ts-ignore - if (typeof unique[key] !== 'undefined') continue; - // @ts-ignore - unique[key] = sortedUnspents.length; - sortedUnspents.push(unspent); - } - } - let txIn = false; - for (const unspent of saved) { - if (unspent.txid === tx) continue; - - try { - const link2 = this._trezorServer + '/api/v2/tx/' + unspent.txid; - const res2 = await AirDAOAxios.getWithoutBraking(link2); - // @ts-ignore - if (res2 && typeof res2.data !== 'undefined' && res2.data) { - // @ts-ignore - if ( - typeof res2.data.confirmations !== 'undefined' && - res2.data.confirmations * 1 > 0 - ) { - // @ts-ignore - unspent.confirmations = res2.data.confirmations * 1; - } else { - unspent.confirmations = 0; - } - // @ts-ignore - if ( - recheckInputs && - typeof res2.data.vout !== 'undefined' && - res2.data.vout - ) { - // @ts-ignore - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' DogeUnspentsProvider.getTx loading output data ' + - JSON.stringify(unspent) + - ' success', - res2.data.vout - ); - let tmp; - // @ts-ignore - if (res2.data.vout.length > 0) { - // @ts-ignore - unspent.vout = false; - // @ts-ignore - for (tmp of res2.data.vout) { - if (typeof tmp.addresses !== 'undefined' && tmp.addresses) { - if (typeof tmp.addresses[0] !== 'undefined') { - const found = this._isMyAddress( - tmp.addresses[0], - address, - walletHash - ); - if (found !== '') { - unspent.vout = tmp.n; - unspent.address = found; - break; // 1 is enough - } - } - } - } - } - } - } else { - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' DogeUnspentsProvider.getTx loading output data ' + - JSON.stringify(unspent) + - ' no res' - ); - } - } catch (e) { - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' DogeUnspentsProvider.getTx while loading output data ' + - JSON.stringify(unspent) - ); - } - - if (typeof unspent.vout === 'undefined' || unspent.vout === false) { - continue; - } - - const key = unspent.txid + '_' + unspent.vout; - // @ts-ignore - if (typeof unique[key] !== 'undefined') { - // @ts-ignore - const index = unique[key]; - sortedUnspents[index].isRequired = true; - } else { - // @ts-ignore - unique[key] = sortedUnspents.length; - unspent.isRequired = true; - if (typeof unspent.confirmations === 'undefined') { - unspent.confirmations = 1; - } - sortedUnspents.push(unspent); - } - txIn = true; - } - - let foundRequired = false; - for (const unspent of sortedUnspents) { - if (unspent.isRequired && unspent.confirmations > 0) { - foundRequired = true; - break; - } - } - if (!foundRequired) { - for (const unspent of sortedUnspents) { - if (unspent.isRequired) { - unspent.confirmations = 1; - break; - } - } - } - - if (!txIn) { - throw new Error('SERVER_RESPONSE_BAD_TX_TO_REPLACE'); - } - - // @ts-ignore - AirDAOCryptoLog.log( - this._settings.currencyCode + ' DogeUnspentsProvider.getTx found ' + tx, - sortedUnspents - ); - return sortedUnspents; - } -} diff --git a/crypto/blockchains/doge/stores/DogeRawDS.ts b/crypto/blockchains/doge/stores/DogeRawDS.ts deleted file mode 100644 index 8bc50d807..000000000 --- a/crypto/blockchains/doge/stores/DogeRawDS.ts +++ /dev/null @@ -1,266 +0,0 @@ -import AirDAOCryptoLog from '../../../common/AirDAOCryptoLog'; -import { BlocksoftTransfer } from '../../../actions/BlocksoftTransfer/BlocksoftTransfer'; -import { Database } from '@database'; -import { DatabaseTable } from '@appTypes'; -import { Q } from '@nozbe/watermelondb'; -import { TransactionRawDBModel } from '@database/models/transactions-raw'; - -const tableName = DatabaseTable.TransactionRaw; - -class DogeRawDS { - _trezorServer = 'none'; - - _canUpdate = true; - async getForAddress(data) { - return false; - // @todo refactor from server side - if (!this._canUpdate) return false; - try { - const sql = ` - SELECT id, - transaction_unique_key AS transactionUnique, - transaction_hash AS transactionHash, - transaction_raw AS transactionRaw, - transaction_log AS transactionLog, - broadcast_log AS broadcastLog, - broadcast_updated AS broadcastUpdated, - created_at AS transactionCreated, - is_removed, removed_at - FROM transactions_raw - WHERE currency_code='${data.currencyCode}' - AND address='${data.address.toLowerCase()}' - AND transaction_unique_key NOT LIKE 'inputs_%' - AND transaction_unique_key NOT LIKE 'json_%' - AND (is_removed=0 OR is_removed IS NULL) - `; - const result = await Database.query(sql); - if (!result || !result.array || result.array.length === 0) { - return {}; - } - const ret = {}; - - const now = new Date().toISOString(); - - for (const row of result.array) { - try { - if (typeof ret[row.transactionUnique] !== 'undefined') { - continue; - } - ret[row.transactionUnique] = row; - let transactionLog; - try { - transactionLog = row.transactionLog - ? JSON.parse(Database.unEscapeString(row.transactionLog)) - : row.transactionLog; - } catch (e) { - // do nothing - } - - let broadcastLog = ''; - const updateObj = { broadcastUpdated: now }; - let broad; - try { - broad = await BlocksoftTransfer.sendRawTx( - data, - row.transactionRaw, - typeof transactionLog !== 'undefined' && - transactionLog && - typeof transactionLog.txRBF !== 'undefined' - ? transactionLog.txRBF - : false, - transactionLog - ); - if (broad === '') { - throw new Error('not broadcasted'); - } - broadcastLog = ' broadcasted ok ' + JSON.stringify(broad); - updateObj.is_removed = 1; - updateObj.removed_at = now; - } catch (e) { - if (config.debug.cryptoErrors) { - const dbTx = await Database.query( - `SELECT * FROM transactions WHERE transaction_hash='${row.transactionHash}'` - ); - if (config.debug.cryptoErrors) { - console.log( - 'DogeRawDS.getForAddress send error ' + e.message, - JSON.parse(JSON.stringify(row)), - dbTx, - e - ); - } - } - if ( - e.message.indexOf('bad-txns-inputs-spent') !== -1 || - e.message.indexOf('missing-inputs') !== -1 || - e.message.indexOf('insufficient fee') !== -1 - ) { - broadcastLog = ' sub-spent ' + e.message; - updateObj.is_removed = 3; - const sql = `UPDATE transactions - SET transaction_status='replaced', hidden_at='${now}' - WHERE transaction_hash='${row.transactionHash}' - AND (transaction_status='missing' OR transaction_status='new') - `; - await Database.query(sql); - } else if (e.message.indexOf('already known') !== -1) { - broadcastLog = ' already known'; - } else { - broadcastLog = e.message; - } - } - broadcastLog = - new Date().toISOString() + - ' ' + - broadcastLog + - ' ' + - (row.broadcastLog ? row.broadcastLog.substr(0, 1000) : ''); - updateObj.broadcastLog = broadcastLog; - await Database.setTableName('transactions_raw') - .setUpdateData({ - updateObj, - key: { id: row.id } - }) - .update(); - } catch (e) { - if (config.debug.cryptoErrors) { - console.log( - 'DogeRawDS.getForAddress error ' + - e.message + - ' in ' + - row.transactionHash, - e - ); - } - throw new Error( - 'DogeRawDS.getForAddress error ' + - e.message + - ' in ' + - row.transactionHash - ); - } - } - this._canUpdate = true; - return ret; - } catch (e) { - this._canUpdate = true; - throw new Error(e.message + ' on DogeRawDS.getAddress'); - } - } - - async cleanRaw(data) { - if (typeof data.transactionUnique === 'undefined') { - data.transactionUnique = - data.address.toLowerCase() + '_' + data.transactionHash; - } - AirDAOCryptoLog.log('DogeRawDS cleanRaw ', data); - const now = new Date().getTime(); - const sql = `UPDATE ${tableName} - SET is_removed=1, removed_at = '${now}' - WHERE - (is_removed=0 OR is_removed IS NULL) - AND currency_code='${data.currencyCode}' - AND address='${data.address.toLowerCase()}' - AND transaction_unique_key='${data.transactionUnique}'`; - await Database.unsafeRawQuery(tableName, sql); - } - - async saveRaw(data) { - if (typeof data.transactionUnique === 'undefined') { - data.transactionUnique = - data.address.toLowerCase() + '_' + data.transactionHash; - } - const now = new Date().getTime(); - - const sql = `UPDATE ${tableName} - SET is_removed=1, removed_at = '${now}' - WHERE - (is_removed=0 OR is_removed IS NULL) - AND currency_code='${data.currencyCode}' - AND address='${data.address.toLowerCase()}' - AND transaction_unique_key='${data.transactionUnique}'`; - await Database.unsafeRawQuery(tableName, sql); - - const prepared = [ - { - currency_code: data.currencyCode, - address: data.address.toLowerCase(), - transaction_unique_key: data.transactionUnique, - transaction_hash: data.transactionHash, - transaction_raw: data.transactionRaw, - created_at: now - } - ]; - if (typeof data.transactionLog !== 'undefined' && data.transactionLog) { - prepared[0].transaction_log = Database.escapeString( - JSON.stringify(data.transactionLog) - ); - } - await Database.createModel(tableName, prepared); - } - - async savePrefixed(data, prefix) { - const now = new Date().getTime(); - - const prepared = [ - { - currency_code: data.currencyCode, - address: data.address.toLowerCase(), - transaction_unique_key: prefix + '_' + data.transactionHash, - transaction_hash: data.transactionHash, - transaction_raw: Database.escapeString(data.transactionRaw), - is_removed: 2, - created_at: now - } - ]; - if (typeof data.transactionLog !== 'undefined' && data.transactionLog) { - prepared[0].transaction_log = Database.escapeString( - JSON.stringify(data.transactionLog) - ); - } - await Database.createModel(tableName, prepared); - } - - async getPrefixed(data, prefix) { - const sql = `SELECT transaction_raw AS transactionRaw - FROM ${tableName} - WHERE currency_code='${data.currencyCode}' - AND transaction_unique_key='${prefix}_${data.transactionHash}' LIMIT 1`; - const res = (await Database.unsafeRawQuery( - tableName, - sql - )) as TransactionRawDBModel[]; - if ( - !res || - typeof res[0] === 'undefined' || - typeof res[0].transactionRaw === 'undefined' - ) { - return false; - } - try { - const str = Database.unEscapeString(res[0].transactionRaw) || ''; - return JSON.parse(str); - } catch (e) { - AirDAOCryptoLog.err('DogeRawDS getInputs error ' + e.message); - return false; - } - } - - async saveInputs(data) { - return this.savePrefixed(data, 'inputs'); - } - - async getInputs(data) { - return this.getPrefixed(data, 'inputs'); - } - - async saveJson(data) { - return this.savePrefixed(data, 'json'); - } - - async getJson(data) { - return this.getPrefixed(data, 'json'); - } -} - -export default new DogeRawDS(); diff --git a/crypto/blockchains/doge/tx/DogeTxBuilder.ts b/crypto/blockchains/doge/tx/DogeTxBuilder.ts deleted file mode 100644 index f56054336..000000000 --- a/crypto/blockchains/doge/tx/DogeTxBuilder.ts +++ /dev/null @@ -1,291 +0,0 @@ -/** - * @version 0.20 - */ -import { AirDAOBlockchainTypes } from '../../AirDAOBlockchainTypes'; -import AirDAOCryptoLog from '../../../common/AirDAOCryptoLog'; -import { TransactionBuilder, ECPair, payments } from 'bitcoinjs-lib'; -import BlocksoftExternalSettings from '../../../common/AirDAOExternalSettings'; - -import networksConstants from '../../../common/ext/networks-constants'; - -const MAX_SEQ = 4294967294; // 0xfffffffe // no replace by fee -const MIN_SEQ = 4294960000; // for RBF - -export default class DogeTxBuilder implements AirDAOBlockchainTypes.TxBuilder { - protected _settings: AirDAOBlockchainTypes.CurrencySettings; - private _builderSettings: AirDAOBlockchainTypes.BuilderSettings; - protected _bitcoinNetwork: any; - private _feeMaxForByteSatoshi: number | any; - - protected keyPair: any; - - constructor( - settings: AirDAOBlockchainTypes.CurrencySettings, - builderSettings: AirDAOBlockchainTypes.BuilderSettings - ) { - this._settings = settings; - this._builderSettings = builderSettings; - this._bitcoinNetwork = networksConstants[settings.network].network; - } - - async _reInit() { - const fromExt = await BlocksoftExternalSettings.get( - this._settings.currencyCode + '_MAX_FOR_BYTE_TX_BUILDER', - 'DogeTxBuilder._reInit' - ); - this._feeMaxForByteSatoshi = - fromExt && fromExt * 1 > 0 - ? fromExt * 1 - : this._builderSettings.feeMaxForByteSatoshi; - await AirDAOCryptoLog.log( - 'DogeTxBuilder.getRawTx ' + - this._settings.currencyCode + - ' _feeMaxForByteSatoshi ' + - this._feeMaxForByteSatoshi + - ' fromExt ' + - fromExt - ); - } - - _getRawTxValidateKeyPair( - privateData: AirDAOBlockchainTypes.TransferPrivateData, - data: AirDAOBlockchainTypes.TransferData - ): void { - if (typeof privateData.privateKey === 'undefined') { - throw new Error('DogeTxBuilder.getRawTx requires privateKey'); - } - try { - this.keyPair = ECPair.fromWIF( - privateData.privateKey, - this._bitcoinNetwork - ); - const address = payments.p2pkh({ - pubkey: this.keyPair.publicKey, - network: this._bitcoinNetwork - }).address; - if (address !== data.addressFrom) { - // noinspection ExceptionCaughtLocallyJS - throw new Error( - 'not valid signing address ' + data.addressFrom + ' != ' + address - ); - } - } catch (e) { - e.message += - ' in privateKey ' + - this._settings.currencyCode + - ' DogeTxBuilder signature check '; - throw e; - } - } - - async _getRawTxAddInput( - txb: TransactionBuilder, - i: number, - input: AirDAOBlockchainTypes.UnspentTx, - nSequence: number - ): Promise { - if (typeof input.vout === 'undefined') { - throw new Error('no input.vout'); - } - if (typeof nSequence === 'undefined') { - throw new Error('no nSequence'); - } - txb.addInput(input.txid, input.vout, nSequence); - } - - async _getRawTxSign( - txb: TransactionBuilder, - i: number, - input: AirDAOBlockchainTypes.UnspentTx - ): Promise { - await AirDAOCryptoLog.log('DogeTxBuilder.getRawTx sign', input); - // @ts-ignore - txb.sign(i, this.keyPair, null, null, input.value * 1); - } - - _getRawTxAddOutput( - txb: TransactionBuilder, - output: AirDAOBlockchainTypes.OutputTx - ): void { - // @ts-ignore - const amount = Math.round(output.amount * 1); - if (amount === 0) { - // do nothing or txb.addOutput(output.to, 546) - } else { - txb.addOutput(output.to, amount); - } - } - - async getRawTx( - data: AirDAOBlockchainTypes.TransferData, - privateData: AirDAOBlockchainTypes.TransferPrivateData, - preparedInputsOutputs: AirDAOBlockchainTypes.PreparedInputsOutputsTx - ): Promise<{ - rawTxHex: string; - nSequence: number; - txAllowReplaceByFee: boolean; - preparedInputsOutputs: AirDAOBlockchainTypes.PreparedInputsOutputsTx; - }> { - await this._reInit(); - - this._getRawTxValidateKeyPair(privateData, data); - await AirDAOCryptoLog.log( - this._settings.currencyCode + - ' DogeTxBuilder.getRawTx validated address private key' - ); - - let nSequence = 0; - let txAllowReplaceByFee = false; - if ( - typeof data.transactionJson === 'undefined' || - !data.transactionJson || - typeof data.transactionJson.nSequence === 'undefined' - ) { - if (data.allowReplaceByFee) { - nSequence = MIN_SEQ; - txAllowReplaceByFee = true; - await AirDAOCryptoLog.log( - this._settings.currencyCode + - ' DogeTxBuilder.getRawTx allow RBF ' + - nSequence - ); - } else { - nSequence = MAX_SEQ; - txAllowReplaceByFee = false; - await AirDAOCryptoLog.log( - this._settings.currencyCode + - ' DogeTxBuilder.getRawTx no RBF ' + - nSequence - ); - } - } else { - nSequence = data.transactionJson.nSequence * 1 + 1; - txAllowReplaceByFee = true; - - if (nSequence >= MAX_SEQ) { - nSequence = MAX_SEQ; - txAllowReplaceByFee = false; - await AirDAOCryptoLog.log( - this._settings.currencyCode + - ' DogeTxBuilder.getRawTx no RBF by old nSeq ' + - data.transactionJson.nSequence + - ' +1 => ' + - nSequence - ); - } else { - await AirDAOCryptoLog.log( - this._settings.currencyCode + - ' DogeTxBuilder.getRawTx allow RBF by old nSeq ' + - data.transactionJson.nSequence + - ' +1 => ' + - nSequence - ); - } - } - - const txb = new TransactionBuilder( - this._bitcoinNetwork, - this._feeMaxForByteSatoshi - ); - await AirDAOCryptoLog.log( - this._settings.currencyCode + - ' DogeTxBuilder.getRawTx started max4Bytes ' + - this._feeMaxForByteSatoshi - ); - - txb.setVersion(1); - - const log = { inputs: [], outputs: [] }; - for (let i = 0, ic = preparedInputsOutputs.inputs.length; i < ic; i++) { - const input = preparedInputsOutputs.inputs[i]; - try { - await this._getRawTxAddInput(txb, i, input, nSequence); - // @ts-ignore - log.inputs.push({ txid: input.txid, vout: input.vout, nSequence }); - // @ts-ignore - await AirDAOCryptoLog.log( - this._settings.currencyCode + ' DogeTxBuilder.getRawTx input added', - input - ); - } catch (e) { - await AirDAOCryptoLog.log( - this._settings.currencyCode + - ' DogeTxBuilder.getRawTx input add error ', - input - ); - throw e; - } - } - - let output; - for (output of preparedInputsOutputs.outputs) { - try { - if (output.amount !== 'removed') { - this._getRawTxAddOutput(txb, output); - } - // @ts-ignore - log.outputs.push({ addressTo: output.to, amount: output.amount }); - // @ts-ignore - await AirDAOCryptoLog.log( - this._settings.currencyCode + ' DogeTxBuilder.getRawTx output added ', - output - ); - } catch (e) { - await AirDAOCryptoLog.log( - this._settings.currencyCode + - ' DogeTxBuilder.getRawTx output add error ', - output - ); - throw e; - } - } - - for (let i = 0, ic = preparedInputsOutputs.inputs.length; i < ic; i++) { - const input = preparedInputsOutputs.inputs[i]; - try { - await AirDAOCryptoLog.log( - this._settings.currencyCode + ' DogeTxBuilder.getRawTx sign adding' - ); - await this._getRawTxSign(txb, i, input); - await AirDAOCryptoLog.log( - this._settings.currencyCode + ' DogeTxBuilder.getRawTx sign added' - ); - } catch (e) { - await AirDAOCryptoLog.log( - this._settings.currencyCode + - ' DogeTxBuilder.getRawTx input sign error ', - input - ); - e.message = - ' transaction ' + - this._settings.currencyCode + - ' DogeTxBuilder sign error: ' + - e.message; - throw e; - } - } - let rawTxHex; - try { - rawTxHex = txb.build().toHex(); - await AirDAOCryptoLog.log( - this._settings.currencyCode + - ' DogeTxBuilder.getRawTx size ' + - rawTxHex.length - ); - // @ts-ignore - await AirDAOCryptoLog.log( - this._settings.currencyCode + ' DogeTxBuilder.getRawTx hex', - rawTxHex - ); - } catch (e) { - e.message = - ' transaction ' + - this._settings.currencyCode + - ' DogeTxBuilder build error: ' + - e.message; - throw e; - } - - return { rawTxHex, nSequence, txAllowReplaceByFee, preparedInputsOutputs }; - } -} diff --git a/crypto/blockchains/doge/tx/DogeTxInputsOutputs.ts b/crypto/blockchains/doge/tx/DogeTxInputsOutputs.ts deleted file mode 100644 index 71b0e353c..000000000 --- a/crypto/blockchains/doge/tx/DogeTxInputsOutputs.ts +++ /dev/null @@ -1,703 +0,0 @@ -/** - * @version 0.20 - */ -import { AirDAOBlockchainTypes } from '../../AirDAOBlockchainTypes'; -import BlocksoftBN from '../../../common/AirDAOBN'; -import BlocksoftUtils from '../../../common/AirDAOUtils'; -import AirDAOCryptoLog from '../../../common/AirDAOCryptoLog'; -import AirDAODict from '@crypto/common/AirDAODict'; - -// @ts-ignore -import coinSelect from 'coinselect'; -// @ts-ignore -import coinSplit from 'coinselect/split'; - -export default class DogeTxInputsOutputs - implements AirDAOBlockchainTypes.TxInputsOutputs -{ - private _builderSettings: AirDAOBlockchainTypes.BuilderSettings; - protected _settings: AirDAOBlockchainTypes.CurrencySettings; - private _minOutputDust: any; - private _minChangeDust: any; - - // in*148 + out*34 + 10 plus or minus 'in' - SIZE_FOR_BASIC = 34; - SIZE_FOR_INPUT = 148; // TX_INPUT_PUBKEYHASH = 107 - SIZE_FOR_BC = 75; - - constructor( - settings: AirDAOBlockchainTypes.CurrencySettings, - builderSettings: AirDAOBlockchainTypes.BuilderSettings - ) { - this._settings = settings; - this._builderSettings = builderSettings; - this._minOutputDust = BlocksoftUtils.fromUnified( - this._builderSettings.minOutputDustReadable, - settings.decimals - ); // output amount that will be considered as "dust" so we dont need it - this._minChangeDust = BlocksoftUtils.fromUnified( - this._builderSettings.minChangeDustReadable, - settings.decimals - ); // change amount that will be considered as "dust" so we dont need it - } - - _coinSelectTargets( - data: AirDAOBlockchainTypes.TransferData, - unspents: AirDAOBlockchainTypes.UnspentTx[], - feeForByte: string, - multiAddress: string[], - subtitle: string - ) { - let targets; - if (data.isTransferAll) { - targets = [ - { - address: data.addressTo - } - ]; - } else if (multiAddress.length === 0) { - targets = [ - { - address: data.addressTo, - // @ts-ignore - value: data.amount * 1 - } - ]; - } else { - targets = []; - for (const address of multiAddress) { - targets.push({ - address: address, - // @ts-ignore - value: data.amount * 1 - }); - } - } - // @ts-ignore - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' DogeTxInputsOutputs.getInputsOutputs _coinSelectTargets', - { - amount: data.amount, - isTransferAll: data.isTransferAll, - multiAddress, - address: data.addressTo - }, - targets - ); - return targets; - } - - _addressForChange(data: AirDAOBlockchainTypes.TransferData): string { - return data.addressFrom; - } - - _usualTargets( - data: AirDAOBlockchainTypes.TransferData, - unspents: AirDAOBlockchainTypes.UnspentTx[] - ) { - const multiAddress = []; - const basicWishedAmountBN = new BlocksoftBN(data.amount); - const wishedAmountBN = new BlocksoftBN(basicWishedAmountBN); - - const outputs = []; - if (data.addressTo.indexOf(';') === -1) { - outputs.push({ - to: data.addressTo, - amount: data.amount.toString() - }); - } else { - const addresses = data.addressTo.replace(/\s+/g, ';').split(';'); - let total = 0; - for (let i = 0, ic = addresses.length; i < ic; i++) { - const address = addresses[i].trim(); - if (!address) continue; - outputs.push({ - to: address, - amount: data.amount.toString() - }); - multiAddress.push(address); - if (total > 0) { - wishedAmountBN.add(basicWishedAmountBN); - } - total++; - } - } - return { - multiAddress, - basicWishedAmountBN, - wishedAmountBN, - outputs - }; - } - - async _coinSelect( - data: AirDAOBlockchainTypes.TransferData, - unspents: AirDAOBlockchainTypes.UnspentTx[], - feeForByte: string, - multiAddress: string[], - subtitle: string - ): Promise { - const utxos = []; - const isRequired: any = {}; - let isAllRequired = true; - const segwitPrefix = - typeof AirDAODict.CurrenciesForTests[ - this._settings.currencyCode + '_SEGWIT' - ] !== 'undefined' - ? AirDAODict.CurrenciesForTests[this._settings.currencyCode + '_SEGWIT'] - .addressPrefix - : false; - for (const unspent of unspents) { - const input = { - txId: unspent.txid, - vout: unspent.vout, - // @ts-ignore - value: unspent.value * 1, - my: unspent - }; // script - if ( - typeof unspent.address !== 'undefined' && - unspent.address.indexOf(segwitPrefix) === 0 - ) { - input.isSegwit = true; - // https://github.com/bitcoinjs/coinselect/pull/63 wait for it to be merged - } - utxos.push(input); - if (unspent.isRequired) { - if (typeof isRequired[unspent.txid] === 'undefined') { - isRequired[unspent.txid] = unspent; - } - } else { - isAllRequired = false; - } - } - if (isAllRequired) { - if (data.addressFrom === data.addressTo) { - data.isTransferAll = true; - } - } - const targets = this._coinSelectTargets( - data, - unspents, - feeForByte, - multiAddress, - subtitle - ); - let res; - if (data.isTransferAll) { - res = coinSplit(utxos, targets, feeForByte); - } else { - res = coinSelect(utxos, targets, feeForByte); - } - const { inputs, outputs, fee } = res; - /* - console.log('CS feeForByte ' + feeForByte) - console.log('CS isAllRequired ', JSON.stringify(isAllRequired)) - console.log('CS targets ' + feeForByte, JSON.parse(JSON.stringify(targets))) - if (inputs) { - let i = 0 - for (let input of inputs) { - console.log('CS inputs [' + i + ']', JSON.parse(JSON.stringify(input))) - i++ - } - } - if (outputs) { - let i = 0 - for (let output of outputs) { - console.log('CS outputs [' + i + ']', JSON.parse(JSON.stringify(output))) - i++ - } - } - console.log('CS fee ', fee ? JSON.parse(JSON.stringify(fee)) : 'none') - console.log('---------------------') - console.log('') - */ - - const formatted = { - inputs: [], - outputs: [], - multiAddress, - msg: - ' coinselect for ' + - feeForByte + - ' fee ' + - fee + - ' ' + - subtitle + - ' all data ' + - JSON.stringify(inputs) + - ' ' + - JSON.stringify(outputs), - countedFor: 'DOGE' - }; - if (!inputs || typeof inputs === 'undefined') { - // @ts-ignore - return formatted; - } - - let input, output; - for (input of inputs) { - // @ts-ignore - formatted.inputs.push(input.my); - if (typeof isRequired[input.my.txid] !== 'undefined') { - delete isRequired[input.my.txid]; - } - } - const changeBN = new BlocksoftBN(0); - let changeIsNeeded = false; - for (const txid in isRequired) { - formatted.msg += ' txidAdded ' + txid; - // @ts-ignore - formatted.inputs.push(isRequired[txid]); - changeBN.add(isRequired[txid].value * 1); - changeIsNeeded = true; - } - - const addressForChange = await this._addressForChange(data); - for (output of outputs) { - if (output.address) { - formatted.outputs.push({ - // @ts-ignore - to: output.address, - // @ts-ignore - amount: output.value.toString() - }); - } else if (addressForChange === data.addressTo) { - changeIsNeeded = true; - changeBN.add(output.value); - } else if (changeIsNeeded) { - changeIsNeeded = false; - changeBN.add(output.value); - formatted.outputs.push({ - // @ts-ignore - to: addressForChange, - // @ts-ignore - amount: changeBN.toString(), - // @ts-ignore - isChange: true - }); - } else { - formatted.outputs.push({ - // @ts-ignore - to: addressForChange, - // @ts-ignore - amount: output.value.toString(), - // @ts-ignore - isChange: true - }); - } - } - - if (changeIsNeeded) { - // @ts-ignore - if ( - this._builderSettings.changeTogether && - typeof formatted.outputs[0] !== 'undefined' && - addressForChange === data.addressTo && - addressForChange === formatted.outputs[0].to - ) { - // @ts-ignore - changeBN.add(formatted.outputs[0].amount); - // @ts-ignore - formatted.outputs[0].amount = changeBN.toString(); - } else { - formatted.outputs.push({ - // @ts-ignore - to: addressForChange, - // @ts-ignore - amount: changeBN.toString(), - // @ts-ignore - isChange: true - }); - } - } - - // @ts-ignore - return formatted; - } - - async getInputsOutputs( - data: AirDAOBlockchainTypes.TransferData, - unspents: AirDAOBlockchainTypes.UnspentTx[], - feeToCount: { - feeForByte?: string; - feeForAll?: string; - autoFeeLimitReadable?: string | number; - }, - additionalData: AirDAOBlockchainTypes.TransferAdditionalData, - subtitle = 'default' - ): Promise { - return this._getInputsOutputs( - data, - unspents, - feeToCount, - additionalData, - subtitle - ); - } - - async _getInputsOutputs( - data: AirDAOBlockchainTypes.TransferData, - unspents: AirDAOBlockchainTypes.UnspentTx[], - feeToCount: { - feeForByte?: string; - feeForAll?: string; - autoFeeLimitReadable?: string | number; - }, - additionalData: AirDAOBlockchainTypes.TransferAdditionalData, - subtitle = 'default' - ): Promise { - if (typeof data.addressFrom === 'undefined') { - throw new Error( - 'DogeTxInputsOutputs.getInputsOutputs requires addressFrom' - ); - } - if (typeof data.addressTo === 'undefined') { - throw new Error( - 'DogeTxInputsOutputs.getInputsOutputs requires addressTo' - ); - } - if (typeof data.amount === 'undefined') { - throw new Error('DogeTxInputsOutputs.getInputsOutputs requires amount'); - } - - const filteredUnspents = []; - const unconfirmedBN = new BlocksoftBN(0); - - const isRequired: any = {}; - let isFoundSpeedUp = false; - const filteredBN = new BlocksoftBN(0); - for (const unspent of unspents) { - if ( - typeof data.transactionSpeedUp !== 'undefined' && - unspent.txid === data.transactionSpeedUp - ) { - unspent.isRequired = true; - isFoundSpeedUp = true; - } - if (unspent.isRequired) { - filteredUnspents.push(unspent); - filteredBN.add(unspent.value); - if ( - unspent.isRequired && - typeof isRequired[unspent.txid] === 'undefined' - ) { - isRequired[unspent.txid] = unspent; - } - } else { - const diff = BlocksoftUtils.diff(unspent.value, this._minOutputDust); - if (diff * 1 < 0) { - // skip as dust - // @ts-ignore - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' DogeTxInputsOutputs unspent skipped as dust ' + - this._minOutputDust + - ' diff ' + - diff, - unspent - ); - } else if (!data.useOnlyConfirmed || unspent.confirmations > 0) { - filteredUnspents.push(unspent); - filteredBN.add(unspent.value); - } else { - unconfirmedBN.add(unspent.value); - } - } - } - - if (typeof data.transactionSpeedUp !== 'undefined' && !isFoundSpeedUp) { - throw new Error('SERVER_RESPONSE_NO_TX_TO_SPEEDUP'); - } - - if (filteredUnspents.length === 0 && unspents.length !== 0) { - throw new Error('SERVER_RESPONSE_WAIT_FOR_CONFIRM'); - } - - const totalBalanceBN = new BlocksoftBN(0); - for (const unspent of filteredUnspents) { - totalBalanceBN.add(unspent.value); - } - - // eslint-disable-next-line prefer-const - let { multiAddress, wishedAmountBN, outputs } = this._usualTargets( - data, - unspents - ); - - if ( - typeof feeToCount.feeForByte !== 'undefined' && - feeToCount.feeForByte !== 'none' - ) { - const result = await this._coinSelect( - data, - filteredUnspents, - feeToCount.feeForByte, - multiAddress, - subtitle - ); - if (result.inputs.length > 0) { - return result; - } - } - - const ic = filteredUnspents.length; - let msg = - 'v20 ' + - subtitle + - ' totalInputs ' + - ic + - ' totalBalance ' + - totalBalanceBN.get() + - ' = ' + - BlocksoftUtils.toUnified(totalBalanceBN.get(), this._settings.decimals) + - ' for wishedAmount ' + - wishedAmountBN.get() + - ' = ' + - BlocksoftUtils.toUnified(wishedAmountBN.get(), this._settings.decimals); - let autocalculateFee = false; - if (typeof feeToCount.feeForAll === 'undefined') { - autocalculateFee = true; - msg += ' and autocalculate feeForByte ' + feeToCount.feeForByte; - } else { - if ( - data.isTransferAll && - typeof feeToCount.feeForAllInputs !== 'undefined' && - feeToCount.feeForAllInputs * 1 > 0 - ) { - feeToCount.feeForAll = BlocksoftUtils.mul( - feeToCount.feeForAll, - Math.ceil(filteredUnspents.length / feeToCount.feeForAllInputs) - ); - wishedAmountBN = new BlocksoftBN( - BlocksoftUtils.diff(totalBalanceBN, feeToCount.feeForAll) - ); - outputs[0].amount = wishedAmountBN.toString(); - msg += ' and isTransferAll inputs counted '; - } - msg += - ' and prefee ' + - feeToCount.feeForAll + - ' = ' + - BlocksoftUtils.toUnified( - feeToCount.feeForAll.toString(), - this._settings.decimals - ); - } - const inputs = []; - const inputsBalanceBN = new BlocksoftBN(0); - - const wishedAmountWithFeeBN = new BlocksoftBN(wishedAmountBN); - const autoFeeBN = new BlocksoftBN(0); - if (autocalculateFee) { - const tmp = BlocksoftUtils.mul( - this.SIZE_FOR_BASIC, - feeToCount.feeForByte - ); - wishedAmountWithFeeBN.add(tmp); - autoFeeBN.add(tmp); - msg += - ' auto => ' + - BlocksoftUtils.toUnified(autoFeeBN.get(), this._settings.decimals); - } else { - wishedAmountWithFeeBN.add(feeToCount.feeForAll); - } - - for (let i = 0; i < ic; i++) { - if (!data.isTransferAll) { - const tmp = new BlocksoftBN(wishedAmountWithFeeBN).diff( - inputsBalanceBN - ); - if (tmp.lessThanZero()) { - msg += - ' finished by collectedAmount ' + - inputsBalanceBN.get() + - ' = ' + - BlocksoftUtils.toUnified( - inputsBalanceBN.get(), - this._settings.decimals - ); - msg += - ' on wishedAmountWithFee ' + - wishedAmountWithFeeBN.get() + - ' = ' + - BlocksoftUtils.toUnified( - wishedAmountWithFeeBN.get(), - this._settings.decimals - ); - break; - } - } - const unspent = filteredUnspents[i]; - inputs.push(unspent); - inputsBalanceBN.add(unspent.value); - if (typeof isRequired[unspent.txid] !== 'undefined') { - delete isRequired[unspent.txid]; - } - if (autocalculateFee) { - let size = this.SIZE_FOR_INPUT; - if ( - typeof unspent.address !== 'undefined' && - unspent.address && - unspent.address.toString().indexOf('bc1') === 0 - ) { - size = this.SIZE_FOR_BC; - } - const tmp2 = BlocksoftUtils.mul(size, feeToCount.feeForByte); - autoFeeBN.add(tmp2); - wishedAmountWithFeeBN.add(tmp2); - msg += - ' auto => ' + - BlocksoftUtils.toUnified(autoFeeBN.get(), this._settings.decimals); - } - } - - for (const txid in isRequired) { - msg += ' txidAdded ' + txid; - inputs.push(isRequired[txid]); - inputsBalanceBN.add(isRequired[txid].value); - } - - const leftForChangeDiff = new BlocksoftBN(inputsBalanceBN).diff( - wishedAmountWithFeeBN - ); - - if (leftForChangeDiff.lessThanZero()) { - if (autocalculateFee) { - const newData = JSON.parse(JSON.stringify(data)); - const autoFeeLimit = BlocksoftUtils.fromUnified( - feeToCount.autoFeeLimitReadable, - this._settings.decimals - ); - const autoDiff = new BlocksoftBN(autoFeeLimit).diff(autoFeeBN); - - let recountWithFee = autoFeeBN.get(); - if (autoDiff.lessThanZero()) { - recountWithFee = autoFeeLimit.toString(); - } - const res = await this._getInputsOutputs( - newData, - unspents, - { feeForAll: recountWithFee }, - additionalData, - subtitle + - ' notEnough1 leftForChangeDiff ' + - leftForChangeDiff.toString() + - ' //// ' - ); - if (res.msg.indexOf('RECHECK') === -1) { - return res; - } - } else if (subtitle.indexOf('notEnough1') !== -1) { - const newData = JSON.parse(JSON.stringify(data)); - const tmp = leftForChangeDiff.get().replace('-', ''); - const tmp2 = new BlocksoftBN(data.amount).diff(tmp); - if (!tmp2.lessThanZero()) { - if (this._settings.currencyCode === 'USDT') { - console.log('adsfgadfgadfg'); - console.log('tmp2', tmp2); - } else { - newData.amount = tmp2.get(); - return this._getInputsOutputs( - newData, - unspents, - feeToCount, - additionalData, - subtitle + - ' notEnough3 ' + - data.amount + - ' => ' + - newData.amount + - ' leftForChangeDiff ' + - leftForChangeDiff.toString() + - ' //// ' - ); - } - } else { - // @ts-ignore - return { - inputs: [], - outputs: [], - msg: - subtitle + - ' notEnough3Stop' + - data.amount + - ' => ' + - newData.amount + - ' leftForChangeDiff ' + - leftForChangeDiff.toString() + - ' ' + - msg, - // @ts-ignore - multiAddress, - countedFor: 'DOGE' - }; - } - } - // no change - msg += - ' will transfer all but later will RECHECK as change ' + - leftForChangeDiff.toString(); - return { - inputs, - outputs, - msg, - // @ts-ignore - multiAddress, - countedFor: 'DOGE' - }; - } - - const changeDiff = new BlocksoftBN(leftForChangeDiff).diff( - this._minChangeDust - ); - if (changeDiff.lessThanZero()) { - // no change - msg += - ' will transfer all as change ' + - leftForChangeDiff.toString() + - ' - dust = ' + - changeDiff.toString(); - return { - inputs, - outputs, - msg, - // @ts-ignore - multiAddress, - countedFor: 'DOGE' - }; - } - - msg += - ' will have change as change ' + - leftForChangeDiff.toString() + - ' = ' + - BlocksoftUtils.toUnified( - leftForChangeDiff.toString(), - this._settings.decimals - ); - const addressForChange = await this._addressForChange(data); - if ( - this._builderSettings.changeTogether && - addressForChange === data.addressTo - ) { - leftForChangeDiff.add(outputs[0].amount); - outputs[0].amount = leftForChangeDiff.toString(); - } else { - outputs.push({ - to: addressForChange, - amount: leftForChangeDiff.toString(), - isChange: true - } as AirDAOBlockchainTypes.OutputTx); - } - return { - inputs, - outputs, - msg, - // @ts-ignore - multiAddress, - countedFor: 'DOGE' - }; - } -} diff --git a/crypto/blockchains/etc/EtcTransferProcessor.ts b/crypto/blockchains/etc/EtcTransferProcessor.ts index ff64a8eb4..a9db3bff0 100644 --- a/crypto/blockchains/etc/EtcTransferProcessor.ts +++ b/crypto/blockchains/etc/EtcTransferProcessor.ts @@ -3,10 +3,10 @@ * @version 0.43 */ import { AirDAOBlockchainTypes } from '@crypto/blockchains/AirDAOBlockchainTypes'; -import BnbSmartTransferProcessor from '@crypto/blockchains/bnb_smart/BnbSmartTransferProcessor'; +// import BnbSmartTransferProcessor from '@crypto/blockchains/bnb_smart/BnbSmartTransferProcessor'; export default class EtcTransferProcessor - extends BnbSmartTransferProcessor + // extends BnbSmartTransferProcessor implements AirDAOBlockchainTypes.TransferProcessor { canRBF( @@ -16,4 +16,32 @@ export default class EtcTransferProcessor ): boolean { return false; } + + getFeeRate( + data: AirDAOBlockchainTypes.TransferData, + privateData?: AirDAOBlockchainTypes.TransferPrivateData, + additionalData?: AirDAOBlockchainTypes.TransferAdditionalData + ): Promise { + return Promise.resolve(undefined); + } + + getTransferAllBalance( + data: AirDAOBlockchainTypes.TransferData, + privateData?: AirDAOBlockchainTypes.TransferPrivateData, + additionalData?: AirDAOBlockchainTypes.TransferAdditionalData + ): Promise { + return Promise.resolve(undefined); + } + + needPrivateForFee(): boolean { + return false; + } + + sendTx( + data: AirDAOBlockchainTypes.TransferData, + privateData: AirDAOBlockchainTypes.TransferPrivateData, + uiData: AirDAOBlockchainTypes.TransferUiData + ): Promise { + return Promise.resolve(undefined); + } } diff --git a/crypto/blockchains/eth/basic/EthTxSendProvider.ts b/crypto/blockchains/eth/basic/EthTxSendProvider.ts index 657210329..8f947933c 100644 --- a/crypto/blockchains/eth/basic/EthTxSendProvider.ts +++ b/crypto/blockchains/eth/basic/EthTxSendProvider.ts @@ -38,19 +38,16 @@ export default class EthTxSendProvider { txRBF: any, logData: any ): Promise<{ transactionHash: string; transactionJson: any }> { - // @ts-ignore await AirDAOCryptoLog.log( this._settings.currencyCode + ' EthTxSendProvider._innerSign started', logData ); - // noinspection JSUnresolvedVariable if (privateData.privateKey.substr(0, 2) !== '0x') { privateData.privateKey = '0x' + privateData.privateKey; } if (tx.value.toString().substr(0, 1) === '-') { throw new Error('SERVER_RESPONSE_NOTHING_LEFT_FOR_FEE'); } - // noinspection JSUnresolvedVariable if (this._mainChainId) { tx.chainId = this._mainChainId; } diff --git a/crypto/blockchains/fio/FioAddressProcessor.ts b/crypto/blockchains/fio/FioAddressProcessor.ts deleted file mode 100644 index 672614168..000000000 --- a/crypto/blockchains/fio/FioAddressProcessor.ts +++ /dev/null @@ -1,40 +0,0 @@ -/** - * @version 0.11 - * https://developers.fioprotocol.io/fio-protocol/accounts - * - * - * let mnemonic = '' - * let results = await BlocksoftKeys.discoverAddresses({ mnemonic, fullTree: false, fromIndex: 0, toIndex: 1, currencyCode: ['XMR'] }) - * console.log('r', results['XMR'][0]) - */ - -import { Ecc } from '@fioprotocol/fiojs'; -// @ts-ignore -import { FIOSDK } from '@fioprotocol/fiosdk'; - -export default class FioAddressProcessor { - _root = false; - - async setBasicRoot(root) { - this._root = root; - } - - /** - * @param {string|Buffer} privateKey - * @param data - * @param {*} data.publicKey - * @param {*} data.walletHash - * @param {*} data.derivationPath - * @param {*} data.derivationIndex - * @param {*} data.derivationType - * @returns {Promise<{privateKey: string, address: string, addedData: *}>} - */ - async getAddress(privateKey: string, data = {}) { - const pvt = await Ecc.PrivateKey(privateKey); - return { - address: FIOSDK.derivedPublicKey(privateKey).publicKey, - privateKey: pvt.toWif(), - addedData: false - }; - } -} diff --git a/crypto/blockchains/fio/FioScannerProcessor.ts b/crypto/blockchains/fio/FioScannerProcessor.ts deleted file mode 100644 index 903cf555f..000000000 --- a/crypto/blockchains/fio/FioScannerProcessor.ts +++ /dev/null @@ -1,162 +0,0 @@ -import AirDAOCryptoLog from '../../common/AirDAOCryptoLog'; -import { getFioBalance, getTransactions } from './FioUtils'; - -export default class FioScannerProcessor { - /** - * @private - */ - _serverUrl = false; - - _blocksToConfirm = 10; - - _maxBlockNumber = 500000000; - - constructor(settings) { - this._settings = settings; - } - - async _getCache(address, additionalData, walletHash) { - return false; - } - - /** - * @param address - * @param additionalData - * @param walletHash - * @returns {Promise} - * @private - */ - async _get(address, additionalData, walletHash) { - return false; - } - - /** - * @param {string} address - * @param {*} additionalData - * @param {string} walletHash - * @return {Promise<{balance:*, unconfirmed:*, provider:string}>} - */ - async getBalanceBlockchainCache( - address: string, - additionalData, - walletHash: string - ) { - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' FioScannerProcessor.getBalance (cache) started ' + - address + - ' of ' + - walletHash - ); - return false; - } - - /** - * @param {string} address - * @param {*} additionalData - * @param {string} walletHash - * @return {Promise<{balance:*, unconfirmed:*, provider:string}>} - */ - async getBalanceBlockchain( - address: string, - additionalData, - walletHash: string - ) { - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' FioScannerProcessor.getBalance started ' + - address + - ' of ' + - walletHash - ); - const balance = await getFioBalance(address); - return { - balance, - unconfirmed: 0 - }; - } - - /** - * @param {string} scanData.account.address - * @param {string} scanData.account.walletHash - * @return {Promise<[UnifiedTransaction]>} - */ - async getTransactionsBlockchain(scanData: { - account: { address: string; walletHash: string }; - }) { - const address = scanData.account.address.trim(); - const walletHash = scanData.account.walletHash; - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' FioScannerProcessor.getTransactionsBlockchain started ' + - address + - ' of ' + - walletHash - ); - const response = await getTransactions(address); - const actions = response['actions'] || []; - const lastBlock = response['last_irreversible_block']; - - const transactions = []; - let tx; - for (tx of actions) { - const transaction = await this._unifyTransaction(address, lastBlock, tx); - if (transaction) { - transactions.push(transaction); - } - } - return transactions; - } - - /** - * - * @param {string} address - * @param {string} lastBlock - * @param {Object} transaction - * @param {BigInteger} transaction.amount BigInteger {_d: Array(2), _s: -1} - * @param {string} transaction.approx_float_amount -0.00002724 - * @param {string} transaction.coinbase false - * @param {string} transaction.fee "27240000" - * @param {string} transaction.hash "ac319a3240f15dab342102fe248d3b95636f8a0bbfa962a5645521fac8fb86d3" - * @param {string} transaction.height 2152183 - * @param {string} transaction.id 10506991 - * @param {string} transaction.mempool: false - * @param {string} transaction.mixin 10 - * @param {string} transaction.payment_id "" - * @param {string} transaction.spent_outputs [{…}] - * @param {string} transaction.timestamp Tue Jul 28 2020 18:10:26 GMT+0300 (Восточная Европа, летнее время) {} - * @param {string} transaction.total_received "12354721582" - * @param {BigInteger} transaction.total_sent BigInteger {_d: Array(2), _s: 1} - * @param {string} transaction.unlock_time - * @return {Promise} - * @private - */ - async _unifyTransaction(address: string, lastBlock: string, transaction) { - const txData = transaction.action_trace?.act?.data; - if ( - !txData?.payee_public_key || - transaction.action_trace.receiver !== 'fio.token' - ) { - return false; - } - - const transactionStatus = - lastBlock - transaction['block_num'] > 5 ? 'success' : 'new'; - const direction = - address === txData?.payee_public_key ? 'income' : 'outcome'; - - return { - transactionHash: transaction['action_trace']['trx_id'], - blockHash: '', - blockNumber: transaction['block_num'], - blockTime: transaction['block_time'], - blockConfirmations: lastBlock - transaction['block_num'], - transactionDirection: direction, - addressFrom: direction === 'income' ? '-' : txData?.payee_public_key, - addressTo: direction === 'income' ? txData?.payee_public_key : address, - addressAmount: txData?.amount, - transactionStatus: transactionStatus, - transactionFee: txData?.max_fee || 0 - }; - } -} diff --git a/crypto/blockchains/fio/FioSdkWrapper.ts b/crypto/blockchains/fio/FioSdkWrapper.ts deleted file mode 100644 index 447e6d612..000000000 --- a/crypto/blockchains/fio/FioSdkWrapper.ts +++ /dev/null @@ -1,60 +0,0 @@ -/** - * @version 0.77 - */ -// @ts-ignore -import { FIOSDK } from '@fioprotocol/fiosdk'; - -import AirDAOKeysStorage from '@lib/helpers/AirDAOKeysStorage'; -import BlocksoftExternalSettings from '@crypto/common/AirDAOExternalSettings'; -import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; - -const fetchJson = async (uri, opts = {}) => { - // eslint-disable-next-line no-undef - return fetch(uri, opts); -}; - -export class FioSdkWrapper { - sdk; - walletHash = false; - async init(walletHash, source) { - if (this.walletHash === walletHash) return false; - try { - const res = await AirDAOKeysStorage.getAddressCache( - walletHash + 'SpecialFio' - ); - let publicKey, fioKey; - if (res) { - publicKey = res.address; - fioKey = res.privateKey; - } else { - const mnemonic = await AirDAOKeysStorage.getWalletMnemonic( - walletHash, - source + ' setSelectedWallet init for Fio' - ); - let tmp = await FIOSDK.createPrivateKeyMnemonic(mnemonic); - fioKey = tmp.fioKey; - tmp = FIOSDK.derivedPublicKey(fioKey); - publicKey = tmp.publicKey; - await AirDAOKeysStorage.setAddressCache(walletHash + 'SpecialFio', { - address: publicKey, - privateKey: fioKey - }); - } - const link = BlocksoftExternalSettings.getStatic('FIO_BASE_URL'); - this.sdk = new FIOSDK(fioKey, publicKey, link, fetchJson); - this.walletHash = walletHash; - AirDAOCryptoLog.log(`FioSdkWrapper.inited for ${walletHash}`); - } catch (e) {} - return true; - } -} - -export const fioSdkWrapper = new FioSdkWrapper(); - -export const getFioSdk = () => { - if (typeof fioSdkWrapper?.sdk !== 'undefined' && fioSdkWrapper?.sdk) { - return fioSdkWrapper?.sdk; - } - const link = BlocksoftExternalSettings.getStatic('FIO_BASE_URL'); - return new FIOSDK(null, null, link, fetchJson); -}; diff --git a/crypto/blockchains/fio/FioTransferProcessor.ts b/crypto/blockchains/fio/FioTransferProcessor.ts deleted file mode 100644 index ef4d51a30..000000000 --- a/crypto/blockchains/fio/FioTransferProcessor.ts +++ /dev/null @@ -1,106 +0,0 @@ -/** - * @version 0.20 - */ -import { getFioSdk } from './FioSdkWrapper'; -import { getFioBalance, transferTokens } from './FioUtils.jts'; -import { AirDAOBlockchainTypes } from '../AirDAOBlockchainTypes'; -import BlocksoftUtils from '../../common/AirDAOUtils'; - -export default class FioTransferProcessor - implements AirDAOBlockchainTypes.TransferProcessor -{ - private _settings: any; - - constructor(settings: any) { - this._settings = settings; - } - - needPrivateForFee(): boolean { - return false; - } - - checkSendAllModal(data: { currencyCode: any }): boolean { - return false; - } - - async getFeeRate( - data: AirDAOBlockchainTypes.TransferData, - privateData: AirDAOBlockchainTypes.TransferPrivateData, - additionalData: {} = {} - ): Promise { - const { fee = 0 } = await getFioSdk().getFee('transfer_tokens_pub_key'); - const result: AirDAOBlockchainTypes.FeeRateResult = { - selectedFeeIndex: 0, - shouldShowFees: false, - fees: [ - { - langMsg: 'xrp_speed_one', - feeForTx: fee, - amountForTx: data.amount - } - ] - } as AirDAOBlockchainTypes.FeeRateResult; - return result; - } - - async getTransferAllBalance( - data: AirDAOBlockchainTypes.TransferData, - privateData: AirDAOBlockchainTypes.TransferPrivateData, - additionalData: any = {} - ): Promise { - const { fee = 0 } = await getFioSdk().getFee('transfer_tokens_pub_key'); - const balance = await getFioBalance(data.addressFrom); - if (balance === 0) { - return { - selectedTransferAllBalance: '0', - selectedFeeIndex: -1, - fees: [], - countedForBasicBalance: '0' - }; - } - - const diff = BlocksoftUtils.diff(balance, fee); - if (diff * 1 < 0) { - return { - selectedTransferAllBalance: '0', - selectedFeeIndex: -2, - fees: [], - countedForBasicBalance: '0' - }; - } - - const result: AirDAOBlockchainTypes.TransferAllBalanceResult = { - selectedFeeIndex: 0, - fees: [ - { - langMsg: 'xrp_speed_one', - feeForTx: fee, - amountForTx: diff - } - ], - selectedTransferAllBalance: diff - } as AirDAOBlockchainTypes.TransferAllBalanceResult; - return result; - } - - async sendTx( - data: AirDAOBlockchainTypes.TransferData, - privateData: AirDAOBlockchainTypes.TransferPrivateData, - uiData: AirDAOBlockchainTypes.TransferUiData - ): Promise { - if ( - typeof uiData !== 'undefined' && - typeof uiData.selectedFee !== 'undefined' && - typeof uiData.selectedFee.rawOnly !== 'undefined' && - uiData.selectedFee.rawOnly - ) { - // @todo ksu - return { - rawOnly: uiData.selectedFee.rawOnly, - raw: 'feature in development' - }; - } - const txId = await transferTokens(data.addressTo, data.amount); - return { transactionHash: txId, transactionJson: {} }; - } -} diff --git a/crypto/blockchains/fio/FioUtils.ts b/crypto/blockchains/fio/FioUtils.ts deleted file mode 100644 index 44a369e63..000000000 --- a/crypto/blockchains/fio/FioUtils.ts +++ /dev/null @@ -1,447 +0,0 @@ -import AirDAOCryptoLog from '../../common/AirDAOCryptoLog'; -import { getFioSdk } from './FioSdkWrapper'; -import AirDAOAxios from '../../common/AirDAOAxios'; -import { Fio } from '@fioprotocol/fiojs'; -import { FIOSDK } from '@fioprotocol/fiosdk/src/FIOSDK'; -import _ from 'lodash'; -import BlocksoftExternalSettings from '@crypto/common/AirDAOExternalSettings'; - -const chunk = _.chunk; - -export const resolveChainCode = ( - currencyCode: string, - currencySymbol: string -) => { - let chainCode = currencyCode; - if (typeof currencyCode !== 'undefined' && currencyCode !== currencySymbol) { - const tmp = currencyCode.split('_'); - if (typeof tmp[0] !== 'undefined' && tmp[0]) { - chainCode = tmp[0]; - } - } - return chainCode; -}; - -export const resolveChainToken = (currencyCode: string, extend) => { - if (extend.currencyCode === 'BNB_SMART') { - return 'SMART'; - } - if (typeof extend.tokenBlockchain !== 'undefined') { - if (extend.tokenBlockchain === 'BNB') { - return 'SMART'; - } - } - return extend.currencySymbol; -}; - -export const resolveCryptoCodes = (currencyCode: string) => { - let chainCode = currencyCode; - let currencySymbol = currencyCode; - const tmp = currencyCode.split('_'); - if (typeof tmp[0] !== 'undefined' && tmp[0] && tmp[1]) { - chainCode = tmp[0]; - currencySymbol = tmp[1]; - } - return { - chain_code: chainCode, - token_code: currencySymbol - }; -}; - -export const isFioAddressValid = (address: string) => { - if (address) { - try { - FIOSDK.isFioAddressValid(address); - return true; - } catch (e) {} - } - return false; -}; - -export const isFioAddressRegistered = async (address: string) => { - if (!isFioAddressValid(address)) { - return false; - } - - try { - const response = await getFioSdk().isAvailable(address); - return response['is_registered'] === 1; - } catch (e) { - formatError('FIO isFioAddressRegistered ', e); - return false; - } -}; - -export const getPubAddress = async ( - fioAddress: string, - chainCode: string, - tokenCode: string -) => { - try { - const response = await getFioSdk().getPublicAddress( - fioAddress, - chainCode, - tokenCode - ); - return response['public_address']; - } catch (e) { - formatError('FIO getPubAddress', e); - return null; - } -}; - -export const getAccountFioName = async () => { - try { - const sdk = getFioSdk(); - const fioPublicKey = sdk.getFioPublicKey(); - const response = await getFioSdk().getFioNames(fioPublicKey); - const addresses = response['fio_addresses'] || []; - return addresses[0]?.fio_address; - } catch (e) { - formatError('FIO.getAccountFioName ', e); - return null; - } -}; - -/** - * Returns FIO Addresses and FIO Domains owned by this public key. - * - * @param fioPublicKey FIO public key of owner. - * @return Promise<[ { fio_address:*, expiration:* } ]> - */ -export const getFioNames = async (fioPublicKey) => { - try { - const response = await getFioSdk().getFioNames(fioPublicKey); - return response['fio_addresses'] || []; - } catch (e) { - formatError('FIO getFioNames', e); - return []; - } -}; - -export const getFioBalance = async (fioPublicKey) => { - try { - const response = await getFioSdk().getFioBalance(fioPublicKey); - return response['balance'] || 0; - } catch (e) { - formatError('FIO getFioBalance', e); - return 0; - } -}; - -export const getSentFioRequests = async ( - fioPublicKey, - limit = 100, - offset = 0 -) => { - try { - AirDAOCryptoLog.log(`FIO getSentFioRequests started ${fioPublicKey}`); - const response = await getFioSdk().getSentFioRequests(limit, offset); - const requests = response['requests'] || []; - return requests.sort( - (a, b) => new Date(b.time_stamp) - new Date(a.time_stamp) - ); - } catch (e) { - formatError('FIO getSentFioRequests', e); - return []; - } -}; - -export const getPendingFioRequests = async ( - fioPublicKey, - limit = 100, - offset = 0 -) => { - try { - AirDAOCryptoLog.log(`FIO getPendingFioRequests started ${fioPublicKey}`); - const response = await getFioSdk().getPendingFioRequests(limit, offset); - const requests = response['requests'] || []; - return requests.sort( - (a, b) => new Date(b.time_stamp) - new Date(a.time_stamp) - ); - } catch (e) { - formatError('FIO getPendingFioRequests', e); - return []; - } -}; - -/** - * This call allows a public address of the specific blockchain type to be added to the FIO Address. - * - * @param fioName FIO Address which will be mapped to public address. - * @param chainCode Blockchain code for blockchain hosting this token. - * @param tokenCode Token code to be used with that public address. - * @param publicAddress The public address to be added to the FIO Address for the specified token. - */ -export const addCryptoPublicAddress = async ({ - fioName, - chainCode, - tokenCode, - publicAddress -}) => { - try { - const { fee = 0 } = await getFioSdk().getFeeForAddPublicAddress(fioName); - const response = await getFioSdk().addPublicAddress( - fioName, - chainCode, - tokenCode, - publicAddress, - fee, - null - ); - const isOK = response['status'] === 'OK'; - if (!isOK) { - await AirDAOCryptoLog.log('FIO addPublicAddress error', response); - } - return isOK; - } catch (e) { - formatError('FIO addPubAddress', e); - } -}; - -export const addCryptoPublicAddresses = async ({ - fioName, - publicAddresses -}) => { - if (!publicAddresses || Object.keys(publicAddresses).length === 0) - return true; - - let isOK = true; - for await (const publicAddressesChunk of chunk(publicAddresses, 5)) { - try { - const { fee = 0 } = await getFioSdk().getFeeForAddPublicAddress(fioName); - const response = await getFioSdk().addPublicAddresses( - fioName, - publicAddressesChunk, - fee, - null - ); - - if (response['status'] !== 'OK') { - await AirDAOCryptoLog.log('FIO addPublicAddress error', response); - isOK = false; - } - } catch (e) { - formatError('FIO addPubAddress', e); - } - } - return isOK; -}; - -export const removeCryptoPublicAddresses = async ({ - fioName, - publicAddresses -}) => { - if (!publicAddresses || Object.keys(publicAddresses).length === 0) - return true; - - let isOK = true; - for await (const publicAddressesChunk of chunk(publicAddresses, 5)) { - try { - const { fee = 0 } = await getFioSdk().getFeeForRemovePublicAddresses( - fioName - ); - const response = await getFioSdk().removePublicAddresses( - fioName, - publicAddressesChunk, - fee, - null - ); - - if (response['status'] !== 'OK') { - await AirDAOCryptoLog.log( - 'FIO removeCryptoPublicAddresses error', - response - ); - isOK = false; - } - } catch (e) { - formatError('FIO removeCryptoPublicAddresses', e); - } - } - return isOK; -}; - -/** - * Create a new funds request on the FIO chain. - * - * @param payerFioAddress FIO Address of the payer. This address will receive the request and will initiate payment. - * @param payeeFioAddress FIO Address of the payee. This address is sending the request and will receive payment. - * @param payeeTokenPublicAddress Payee's public address where they want funds sent. - * @param amount Amount requested. - * @param chainCode Blockchain code for blockchain hosting this token. - * @param tokenCode Code of the token represented in amount requested. - * @param memo - */ -export const requestFunds = async ({ - payerFioAddress, - payeeFioAddress, - payeeTokenPublicAddress, - amount, - chainCode, - tokenCode, - memo -}) => { - try { - AirDAOCryptoLog.log( - `FIO requestFunds started ${payerFioAddress} -> ${payeeFioAddress} ${amount} ${tokenCode} (${chainCode})` - ); - const { fee = 0 } = await getFioSdk().getFeeForNewFundsRequest( - payeeFioAddress - ); - const response = await getFioSdk().requestFunds( - payerFioAddress, - payeeFioAddress, - payeeTokenPublicAddress, - amount, - chainCode, - tokenCode, - memo, - fee, - null, - null, - null, - null - ); - - await AirDAOCryptoLog.log('FIO requestFunds result', response); - return response; - } catch (e) { - formatError('FIO requestFunds', e); - const errorMessage = e.json?.fields - ? e.json?.fields[0].error - : e.json?.message; - - return { - error: errorMessage || 'FIO request creation error' - }; - } -}; - -export const getTransactions = async (publicKey) => { - try { - const link = BlocksoftExternalSettings.getStatic('FIO_HISTORY_URL'); - const accountHash = Fio.accountHash(publicKey); - const response = await AirDAOAxios.post(link + 'get_actions', { - account_name: accountHash, - pos: -1 - }); - return response?.data; - } catch (e) { - formatError('FIO getTransactions', e); - return []; - } -}; - -export const transferTokens = async (addressTo, amount) => { - try { - const { fee = 0 } = await getFioSdk().getFee('transfer_tokens_pub_key'); - const result = await getFioSdk().transferTokens( - addressTo, - amount, - fee, - null - ); - return result['transaction_id']; - } catch (e) { - formatError('FIO transferTokens', e); - const errorMessage = e.json?.fields - ? e.json?.fields[0].error - : e.json?.message; - throw new Error(errorMessage || 'FIO token transfer error'); - } -}; - -export const rejectFioFundsRequest = async (fioRequestId, payerFioAddress) => { - try { - const sdk = getFioSdk(); - const { fee = 0 } = await sdk.getFeeForRejectFundsRequest(payerFioAddress); - const result = await sdk.rejectFundsRequest(`${fioRequestId}`, fee, null); - return result['status'] === 'request_rejected'; - } catch (e) { - formatError('FIO rejectFioRequest', e); - } -}; - -/** - * - * Records information on the FIO blockchain about a transaction that occurred on other blockchain, i.e. 1 BTC was sent on Bitcoin Blockchain, and both - * sender and receiver have FIO Addresses. OBT stands for Other Blockchain Transaction - * - * @param fioRequestId ID of funds request, if this Record Send transaction is in response to a previously received funds request. Send empty if no FIO Request ID - * @param payerFioAddress FIO Address of the payer. This address initiated payment. - * @param payeeFioAddress FIO Address of the payee. This address is receiving payment. - * @param payerTokenPublicAddress Public address on other blockchain of user sending funds. - * @param payeeTokenPublicAddress Public address on other blockchain of user receiving funds. - * @param amount Amount sent. - * @param chainCode Blockchain code for blockchain hosting this token. - * @param tokenCode Code of the token represented in Amount requested, i.e. BTC. - * @param obtId Other Blockchain Transaction ID (OBT ID), i.e Bitcoin transaction ID. - * @param memo - */ -export const recordFioObtData = async ({ - fioRequestId = '', - payerFioAddress = '', - payeeFioAddress = '', - payerTokenPublicAddress, - payeeTokenPublicAddress, - amount, - chainCode, - tokenCode, - obtId, - memo -}) => { - try { - const sdk = getFioSdk(); - const { fee = 0 } = await sdk.getFeeForRecordObtData(payerFioAddress); - const result = await sdk.recordObtData( - fioRequestId, - payerFioAddress, - payeeFioAddress, - payerTokenPublicAddress, - payeeTokenPublicAddress, - amount, - chainCode, - tokenCode, - 'sent_to_blockchain', - obtId, - fee, - null, - null, - memo, - null, - null - ); - return !!result['status']; - } catch (e) { - formatError('FIO recordFioObtData', e); - } -}; - -export const getFioObtData = async (tokenCode, offset = 0, limit = 100) => { - let res = false; - try { - res = await getFioSdk().getObtData(limit, offset, tokenCode); - } catch (e) { - formatError( - 'FIO.getFioObtData ' + tokenCode + ' ' + limit + ' ' + offset, - e - ); - } - return res; -}; - -const formatError = (title, e) => { - if ( - e.message.indexOf('Error 404') === -1 && - e.message.indexOf('Network request failed') === -1 - ) { - AirDAOCryptoLog.err(title + ' error ' + e.message, e.json || false); - } else { - const msg = - title + ' 404 notice ' + e.message + ' ' + JSON.stringify(e.json); - if (msg.indexOf('No FIO Requests') === -1) { - AirDAOCryptoLog.log(msg); - } - } -}; diff --git a/crypto/blockchains/ltc/LtcScannerProcessor.ts b/crypto/blockchains/ltc/LtcScannerProcessor.ts deleted file mode 100644 index e6921371a..000000000 --- a/crypto/blockchains/ltc/LtcScannerProcessor.ts +++ /dev/null @@ -1,20 +0,0 @@ -/** - * @version 0.5 - */ - -import BtcScannerProcessor from '../btc/BtcScannerProcessor'; - -// @ts-ignore -export default class LtcScannerProcessor extends BtcScannerProcessor { - /** - * @type {number} - * @private - */ - _blocksToConfirm = 10; - - /** - * @type {string} - * @private - */ - _trezorServerCode = 'LTC_TREZOR_SERVER'; -} diff --git a/crypto/blockchains/ltc/LtcTransferProcessor.ts b/crypto/blockchains/ltc/LtcTransferProcessor.ts deleted file mode 100644 index 2ddc12b73..000000000 --- a/crypto/blockchains/ltc/LtcTransferProcessor.ts +++ /dev/null @@ -1,54 +0,0 @@ -/** - * @version 0.20 - */ - -import BtcTransferProcessor from '@crypto/blockchains/btc/BtcTransferProcessor'; -import { AirDAOBlockchainTypes } from '@crypto/blockchains/AirDAOBlockchainTypes'; -import DogeNetworkPrices from '@crypto/blockchains/doge/basic/DogeNetworkPrices'; -import BtcUnspentsProvider from '@crypto/blockchains/btc/providers/BtcUnspentsProvider'; -import DogeSendProvider from '@crypto/blockchains/doge/providers/DogeSendProvider'; -import BtcTxInputsOutputs from '@crypto/blockchains/btc/tx/BtcTxInputsOutputs'; -import BtcTxBuilder from '@crypto/blockchains/btc/tx/BtcTxBuilder'; - -export default class LtcTransferProcessor - extends BtcTransferProcessor - implements AirDAOBlockchainTypes.TransferProcessor -{ - _trezorServerCode = 'LTC_TREZOR_SERVER'; - - _builderSettings: AirDAOBlockchainTypes.BuilderSettings = { - minOutputDustReadable: 0.000005, - minChangeDustReadable: 0.00001, - feeMaxForByteSatoshi: 10000, // for tx builder - feeMaxAutoReadable2: 0.2, // for fee calc, - feeMaxAutoReadable6: 0.1, // for fee calc - feeMaxAutoReadable12: 0.05, // for fee calc - changeTogether: true - }; - - _initProviders() { - if (this._initedProviders) return false; - this.unspentsProvider = new BtcUnspentsProvider( - this._settings, - this._trezorServerCode - ); - this.sendProvider = new DogeSendProvider( - this._settings, - this._trezorServerCode - ); - this.txPrepareInputsOutputs = new BtcTxInputsOutputs( - this._settings, - this._builderSettings - ); - this.txBuilder = new BtcTxBuilder(this._settings, this._builderSettings); - this.networkPrices = new DogeNetworkPrices(); - this._initedProviders = true; - } - - canRBF( - data: AirDAOBlockchainTypes.DbAccount, - transaction: AirDAOBlockchainTypes.DbTransaction - ): boolean { - return false; - } -} diff --git a/crypto/blockchains/metis/MetisScannerProcessor.ts b/crypto/blockchains/metis/MetisScannerProcessor.ts deleted file mode 100644 index c9161f514..000000000 --- a/crypto/blockchains/metis/MetisScannerProcessor.ts +++ /dev/null @@ -1,55 +0,0 @@ -/** - * @version 0.5 - */ -import AirDAOCryptoLog from '../../common/AirDAOCryptoLog'; -import EthScannerProcessor from '@crypto/blockchains/eth/EthScannerProcessor'; - -interface UnifiedTransaction { - hash: string; - from: string; - to: string; - value: string; -} - -export default class MetisScannerProcessor extends EthScannerProcessor { - /** - * @param {string} scanData.account.address - * @return {Promise<[UnifiedTransaction]>} - */ - // @ts-ignore - async getTransactionsBlockchain(scanData: { - account: { address: string }; - }): Promise { - const address = scanData.account.address; - await AirDAOCryptoLog.log( - this._settings.currencyCode + - ' MetisScannerProcessor.getTransactions started ' + - address - ); - const transactions: UnifiedTransaction[] = - await super.getTransactionsBlockchain(scanData); - - // https://andromeda-explorer.metis.io/token/0xDeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD0000/token-transfers - // actual deposits and withdrawals done as erc20 token transfer - const tmp = this._etherscanApiPath.split('/'); - const depositLink = `https://${tmp[2]}/api?module=account&action=tokentx&sort=desc&contractaddress=0xDeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD0000&address=${address}`; - const depositLogTitle = - this._settings.currencyCode + - ' EthScannerProcessor.getTransactions etherscan deposits'; - const depositTransactions = await this._getFromEtherscan( - address, - depositLink, - depositLogTitle, - false, - {} - ); - if (depositTransactions) { - for (const transactionHash in depositTransactions) { - // @ts-ignore - transactions.push(depositTransactions[transactionHash]); - } - } - - return transactions; - } -} diff --git a/crypto/blockchains/metis/MetisTransferProcessor.ts b/crypto/blockchains/metis/MetisTransferProcessor.ts deleted file mode 100644 index b550e1ce2..000000000 --- a/crypto/blockchains/metis/MetisTransferProcessor.ts +++ /dev/null @@ -1,94 +0,0 @@ -/** - * @author Ksu - * @version 0.43 - */ -import { AirDAOBlockchainTypes } from '@crypto/blockchains/AirDAOBlockchainTypes'; -import EthTransferProcessor from '@crypto/blockchains/eth/EthTransferProcessor'; -import BlocksoftExternalSettings from '@crypto/common/AirDAOExternalSettings'; -import AirDAOAxios from '@crypto/common/AirDAOAxios'; -import BlocksoftUtils from '@crypto/common/AirDAOUtils'; - -export default class MetisTransferProcessor - extends EthTransferProcessor - implements AirDAOBlockchainTypes.TransferProcessor -{ - private _mainCurrencyCode: string | undefined; - - async getFeeRate( - data: AirDAOBlockchainTypes.TransferData, - privateData: AirDAOBlockchainTypes.TransferPrivateData, - additionalData: AirDAOBlockchainTypes.TransferAdditionalData = {} - ): Promise { - if ( - typeof additionalData.gasPrice === 'undefined' || - !additionalData.gasPrice - ) { - additionalData.gasPrice = BlocksoftExternalSettings.getStatic( - this._mainCurrencyCode + '_PRICE' - ); - additionalData.gasPriceTitle = 'speed_blocks_2'; - } - - let value = 0; - try { - if (data.amount.indexOf('0x') === 0) { - value = parseInt(data.amount, 16); - } else { - value = parseInt('0x' + BlocksoftUtils.decimalToHex(data.amount), 16); - } - } catch (e: any) { - throw new Error(e.message + ' with data.amount ' + data.amount); - } - - const params = { - jsonrpc: '2.0', - method: 'eth_estimateGas', - params: [ - { - from: data.addressFrom, - to: data.addressTo, - value, - data: '0x' - } - ], - id: 1 - }; - // const tmp = await AirDAOAxios.post( - // BlocksoftExternalSettings.getStatic('METIS_SERVER'), - // params - // ); - - try { - // @ts-ignore - // tslint:disable-next-line:no-shadowed-variable - const { data } = await BlocksoftAxios.post( - BlocksoftExternalSettings.getStatic('METIS_SERVER'), - params - ); - - if (typeof data !== 'undefined' && typeof data.result !== 'undefined') { - // @ts-ignore - additionalData.gasLimit = BlocksoftUtils.hexToDecimalWalletConnect( - data.result - ); - } else if ( - typeof data !== 'undefined' && - typeof data.error !== 'undefined' - ) { - throw new Error(data.error.message); - } - } catch (error: any) { - throw new Error('Error fetching gas estimate: ' + error.message); - } - - return super.getFeeRate(data, privateData, additionalData); - } - - canRBF( - data: AirDAOBlockchainTypes.DbAccount, - transaction: AirDAOBlockchainTypes.DbTransaction, - source: string - ): boolean { - return false; - } -} diff --git a/crypto/blockchains/one/OneScannerProcessor.ts b/crypto/blockchains/one/OneScannerProcessor.ts deleted file mode 100644 index a501596c9..000000000 --- a/crypto/blockchains/one/OneScannerProcessor.ts +++ /dev/null @@ -1,220 +0,0 @@ -/** - * https://docs.harmony.one/home/developers/api/methods/account-methods/hmy_getbalance - * https://docs.harmony.one/home/developers/api/methods/transaction-related-methods/hmy_gettransactionshistory#api-v2 - */ -import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; -import BlocksoftExternalSettings from '@crypto/common/AirDAOExternalSettings'; -import AirDAOAxios from '@crypto/common/AirDAOAxios'; -import OneUtils from '@crypto/blockchains/one/ext/OneUtils'; -import BlocksoftUtils from '@crypto/common/AirDAOUtils'; - -export default class OneScannerProcessor { - _blocksToConfirm = 10; - private _settings: any; // TODO fix any - - constructor(settings: any) { - this._settings = settings; - } - - /** - * @param {string} address - * @param {*} additionalData - * @param {string} walletHash - * @return {Promise<{balance:*, unconfirmed:*, provider:string}>} - */ - async getBalanceBlockchain( - address: string, - additionalData, - walletHash: string - ) { - const oneAddress = OneUtils.toOneAddress(address); - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' OneScannerProcessor.getBalanceBlockchain started ' + - address + - ' ' + - oneAddress - ); - try { - const apiPath = BlocksoftExternalSettings.getStatic('ONE_SERVER'); - const data = { - jsonrpc: '2.0', - id: 1, - method: 'hmy_getBalance', - params: [oneAddress, 'latest'] - }; - const res = await AirDAOAxios._request(apiPath, 'POST', data); - if (typeof res.data === 'undefined') { - return false; - } - if (typeof res.data.error !== 'undefined') { - throw new Error(JSON.stringify(res.data.error)); - } - if (typeof res.data.result === 'undefined') { - return false; - } - const balance = BlocksoftUtils.hexToDecimalBigger(res.data.result); - return { - balance, - unconfirmed: 0 - }; - } catch (e) { - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' OneScannerProcessor.getBalanceBlockchain address ' + - address + - ' ' + - oneAddress + - ' error ' + - e.message - ); - return false; - } - } - - /** - * @param {string} scanData.account.address - * @param {string} scanData.account.walletHash - * @return {Promise<[UnifiedTransaction]>} - */ - async getTransactionsBlockchain(scanData: { - account: { address: string; walletHash: string }; - }) { - const { address } = scanData.account; - const oneAddress = OneUtils.toOneAddress(address); - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' OneScannerProcessor.getTransactionsBlockchain started ' + - address + - ' ' + - oneAddress - ); - try { - const apiPath = BlocksoftExternalSettings.getStatic('ONE_SERVER'); - const data = { - jsonrpc: '2.0', - id: 1, - method: 'hmyv2_getTransactionsHistory', - params: [ - { - address: oneAddress, - pageIndex: 0, - pageSize: 20, - fullTx: true, - txType: 'ALL', - order: 'DESC' - } - ] - }; - const res = await AirDAOAxios._request(apiPath, 'POST', data); - if ( - typeof res.data === 'undefined' || - typeof res.data.result === 'undefined' || - typeof res.data.result.transactions === 'undefined' - ) { - return false; - } - const transactions = []; - for (const tx of res.data.result.transactions) { - const transaction = await this._unifyTransaction( - address, - oneAddress, - tx - ); - if (transaction) { - transactions.push(transaction); - } - } - return transactions; - } catch (e) { - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' OneScannerProcessor.getTransactionsBlockchain address ' + - address + - ' error ' + - e.message - ); - return false; - } - } - - /** - * - * @param {string} address - * @param {string} oneAddress - * @param {Object} transaction - * @param {string} transaction.blockHash - * @param {string} transaction.blockNumber - * @param {string} transaction.ethHash - * @param {string} transaction.from - * @param {string} transaction.gas - * @param {string} transaction.gasPrice - * @param {string} transaction.hash - * @param {string} transaction.input "0x095ea7b3000000000000000000000000d0cb3e55449646c9735d53e83eea5eb7e97a52dcffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", - * @param {string} transaction.nonce - * @param {string} transaction.shardID - * @param {string} transaction.timestamp - * @param {string} transaction.to - * @param {string} transaction.toShardID - * @param {string} transaction.value - * @return {Promise} - * @private - */ - async _unifyTransaction(address: string, oneAddress: string, transaction) { - let formattedTime = transaction.timestamp; - try { - formattedTime = BlocksoftUtils.toDate(transaction.timestamp); - } catch (e) { - e.message += - ' timestamp error transaction data ' + JSON.stringify(transaction); - throw e; - } - - const confirmations = (new Date().getTime() - transaction.timestamp) / 60; - const addressAmount = transaction.value; - - let transactionStatus = 'confirming'; - if (confirmations > 2) { - transactionStatus = 'success'; - } - - const isOutcome = - address.toLowerCase() === transaction.from.toLowerCase() || - oneAddress.toLowerCase() === transaction.from.toLowerCase(); - const isIncome = - address.toLowerCase() === transaction.to.toLowerCase() || - oneAddress.toLowerCase() === transaction.to.toLowerCase(); - const tx = { - transactionHash: transaction.ethHash.toLowerCase(), - blockHash: transaction.blockHash, - blockNumber: +transaction.blockNumber, - blockTime: formattedTime, - blockConfirmations: confirmations, - transactionDirection: isOutcome - ? isIncome - ? 'self' - : 'outcome' - : 'income', - addressFrom: isOutcome ? '' : transaction.from, - addressFromBasic: transaction.from.toLowerCase(), - addressTo: isIncome ? '' : transaction.to, - addressToBasic: transaction.to, - addressAmount, - transactionStatus: transactionStatus, - inputValue: transaction.input - }; - const additional = { - nonce: transaction.nonce, - gas: transaction.gas, - gasPrice: transaction.gasPrice, - transactionIndex: transaction.transactionIndex - }; - tx.transactionJson = additional; - tx.transactionFee = BlocksoftUtils.mul( - transaction.gasUsed, - transaction.gasPrice - ).toString(); - - return tx; - } -} diff --git a/crypto/blockchains/one/OneScannerProcessorErc20.ts b/crypto/blockchains/one/OneScannerProcessorErc20.ts deleted file mode 100644 index f81d5d82d..000000000 --- a/crypto/blockchains/one/OneScannerProcessorErc20.ts +++ /dev/null @@ -1,207 +0,0 @@ -/** - * https://docs.harmony.one/home/developers/api/methods/account-methods/hmy_getbalance - * https://docs.harmony.one/home/developers/api/methods/transaction-related-methods/hmy_gettransactionshistory#api-v2 - */ -import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; -import BlocksoftExternalSettings from '@crypto/common/AirDAOExternalSettings'; -import AirDAOAxios from '@crypto/common/AirDAOAxios'; -import OneUtils from '@crypto/blockchains/one/ext/OneUtils'; -import BlocksoftUtils from '@crypto/common/AirDAOUtils'; -import EthScannerProcessorErc20 from '@crypto/blockchains/eth/EthScannerProcessorErc20'; -import OneTmpDS from './stores/OneTmpDS'; - -const CACHE_TOKENS = {}; - -export default class OneScannerProcessorErc20 extends EthScannerProcessorErc20 { - _blocksToConfirm = 10; - - /** - * @param {string} scanData.account.address - * @param {string} scanData.account.walletHash - * @return {Promise<[UnifiedTransaction]>} - */ - async getTransactionsBlockchain(scanData: { - account: { address: string; walletHash: string }; - }) { - const { address } = scanData.account; - const oneAddress = OneUtils.toOneAddress(address); - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' OneScannerProcessorErc20.getTransactionsBlockchain started ' + - address + - ' ' + - oneAddress - ); - try { - CACHE_TOKENS[address] = await OneTmpDS.getCache(address); - const apiPath = BlocksoftExternalSettings.getStatic('ONE_SERVER'); - const data = { - jsonrpc: '2.0', - id: 1, - method: 'hmyv2_getTransactionsHistory', - params: [ - { - address: oneAddress, - pageIndex: 0, - pageSize: 20, - fullTx: true, - txType: 'ALL', - order: 'DESC' - } - ] - }; - const res = await AirDAOAxios._request(apiPath, 'POST', data); - if ( - typeof res.data === 'undefined' || - typeof res.data.result === 'undefined' || - typeof res.data.result.transactions === 'undefined' - ) { - return false; - } - const transactions = []; - let firstTransaction = false; - for (const tx of res.data.result.transactions) { - if ( - typeof CACHE_TOKENS[address] !== 'undefined' && - typeof CACHE_TOKENS[address][this._tokenAddress] !== 'undefined' - ) { - const diff = tx.timestamp - CACHE_TOKENS[address][this._tokenAddress]; - const diffNow = (new Date().getTime() - tx.timestamp) / 60; - if (diff < -20) { - continue; - } - if (diff <= 0) { - if (diffNow > 100) { - continue; - } - } - } - if (!firstTransaction) { - firstTransaction = tx.timestamp; - } - const transaction = await this._unifyTransaction( - address, - oneAddress, - tx - ); - if (transaction) { - transactions.push(transaction); - } - } - CACHE_TOKENS[address][this._tokenAddress] = firstTransaction; - await OneTmpDS.saveCache(address, this._tokenAddress, firstTransaction); - return transactions; - } catch (e) { - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' OneScannerProcessorErc20.getTransactionsBlockchain address ' + - address + - ' error ' + - e.message - ); - return false; - } - } - - /** - * - * @param {string} address - * @param {string} oneAddress - * @param {Object} transaction - * @param {string} transaction.blockHash - * @param {string} transaction.blockNumber - * @param {string} transaction.ethHash - * @param {string} transaction.from - * @param {string} transaction.gas - * @param {string} transaction.gasPrice - * @param {string} transaction.hash - * @param {string} transaction.input "0x095ea7b3000000000000000000000000d0cb3e55449646c9735d53e83eea5eb7e97a52dcffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", - * @param {string} transaction.nonce - * @param {string} transaction.shardID - * @param {string} transaction.timestamp - * @param {string} transaction.to - * @param {string} transaction.toShardID - * @param {string} transaction.value - * @return {Promise} - * @private - */ - async _unifyTransaction(address: string, oneAddress: string, transaction) { - const contractEvents = await this._token.getPastEvents('Transfer', { - fromBlock: transaction.blockNumber, - toBlock: transaction.blockNumber - }); - if (!contractEvents) { - return false; - } - let foundEventFrom = false; - let foundEventTo = false; - let foundEventSelf = false; - let addressAmount = 0; - for (const tmp of contractEvents) { - if (tmp.transactionHash !== transaction.ethHash) { - continue; - } - if (tmp.returnValues.to.toLowerCase() === address.toLowerCase()) { - if (tmp.returnValues.from.toLowerCase() === address.toLowerCase()) { - foundEventSelf = tmp; - } else { - foundEventTo = tmp; - addressAmount = addressAmount * 1 + tmp.returnValues.value * 1; - } - } else if ( - tmp.returnValues.from.toLowerCase() === address.toLowerCase() - ) { - foundEventFrom = tmp; - addressAmount = addressAmount * 1 - tmp.returnValues.value * 1; - } - } - if (!foundEventSelf && !foundEventTo && !foundEventFrom) { - return false; - } - - let formattedTime = transaction.timestamp; - try { - formattedTime = BlocksoftUtils.toDate(transaction.timestamp); - } catch (e) { - e.message += - ' timestamp error transaction data ' + JSON.stringify(transaction); - throw e; - } - - const confirmations = (new Date().getTime() - transaction.timestamp) / 60; - let transactionStatus = 'confirming'; - if (confirmations > 2) { - transactionStatus = 'success'; - } - - const tx = { - transactionHash: transaction.ethHash.toLowerCase(), - blockHash: transaction.blockHash, - blockNumber: +transaction.blockNumber, - blockTime: formattedTime, - blockConfirmations: confirmations, - transactionDirection: - addressAmount * 1 <= 0 ? (foundEventTo ? 'outcome' : 'self') : 'income', - addressFrom: foundEventFrom ? transaction.from : '', - addressFromBasic: transaction.from.toLowerCase(), - addressTo: foundEventTo ? transaction.to : '', - addressToBasic: transaction.to, - addressAmount, - transactionStatus: transactionStatus, - inputValue: transaction.input - }; - const additional = { - nonce: transaction.nonce, - gas: transaction.gas, - gasPrice: transaction.gasPrice, - transactionIndex: transaction.transactionIndex - }; - tx.transactionJson = additional; - tx.transactionFee = BlocksoftUtils.mul( - transaction.gasUsed, - transaction.gasPrice - ).toString(); - - return tx; - } -} diff --git a/crypto/blockchains/one/stores/OneTmpDS.ts b/crypto/blockchains/one/stores/OneTmpDS.ts deleted file mode 100644 index 8846e1484..000000000 --- a/crypto/blockchains/one/stores/OneTmpDS.ts +++ /dev/null @@ -1,52 +0,0 @@ -/* eslint-disable camelcase */ -import { DatabaseTable } from '@appTypes'; -import { Database, TransactionScannersTmpDBModel } from '@database'; - -const tableName = DatabaseTable.TransactionScannersTmp; - -class OneTmpDS { - /** - * @type {string} - * @private - */ - _currencyCode = 'ONE'; - - async getCache(address: string) { - const res = (await Database.unsafeRawQuery( - tableName, - ` - SELECT tmp_key AS tmpKey, tmp_sub_key AS tmpSubKey, tmp_val AS tmpVal - FROM ${tableName} - WHERE currency_code='${this._currencyCode}' - AND address='${address}' - AND tmp_key='last_tx' - ` - )) as TransactionScannersTmpDBModel[]; - const tmp = {}; - if (res) { - let row; - for (row of res) { - // @ts-ignore - tmp[row.tmpSubKey] = row.tmpVal; - } - } - return tmp; - } - - async saveCache(address: string, subKey: string, value: string) { - const now = new Date().getTime(); - const prepared = [ - { - currency_code: this._currencyCode, - address: address, - tmp_key: 'last_tx', - tmp_sub_key: subKey, - tmp_val: value, - created_at: now - } - ]; - await Database.createModel(tableName, prepared); - } -} - -export default new OneTmpDS(); diff --git a/crypto/blockchains/sol/SolAddressProcessor.ts b/crypto/blockchains/sol/SolAddressProcessor.ts deleted file mode 100644 index 13e0dd882..000000000 --- a/crypto/blockchains/sol/SolAddressProcessor.ts +++ /dev/null @@ -1,83 +0,0 @@ -/** - * @version 0.52 - * not ok - * https://github.com/solana-labs/browser-extension/blob/master/app/background/lib/wallet.ts#L37 - * ok - * https://github.com/project-serum/spl-token-wallet - * https://github.com/project-serum/spl-token-wallet/blob/master/src/utils/walletProvider/localStorage.js#L30 - */ -import BlocksoftKeys from '@crypto/actions/BlocksoftKeys/BlocksoftKeys'; -import XlmDerivePath from '@crypto/blockchains/xlm/ext/XlmDerivePath'; - -const nacl = require('tweetnacl'); -const bs58 = require('bs58'); - -interface PrivateData { - mnemonic?: string; -} - -export default class SolAddressProcessor { - private root: any; - - async setBasicRoot(root: any) { - this.root = root; - } - - /** - * @param {string|Buffer} privateKey - not used as bip32 private is outdated - * @param {*} data - * @param superPrivateData - * @param seed - * @param source - * @returns {Promise<{privateKey: string, address: string}>} - */ - async getAddress( - privateKey: string | Buffer, - data: any = {}, - superPrivateData: PrivateData = {}, - seed?: Buffer - ): Promise<{ privateKey: string; address: string }> { - if ( - typeof superPrivateData.mnemonic === 'undefined' || - !superPrivateData.mnemonic - ) { - throw new Error('need mnemonic'); - } - if (typeof seed === 'undefined') { - seed = await BlocksoftKeys.getSeedCached(superPrivateData.mnemonic); - } - if (typeof seed === 'undefined') { - throw new Error('seed is undefined'); - } - const seedHex = seed.toString('hex'); - let derivationPath = data.derivationPath; - if ( - derivationPath !== `m/44'/501'/0'` && - derivationPath !== `m/44'/501'/0'/0'` - ) { - derivationPath = `m/44'/501'/0'/0'`; - } - - if (seedHex.length < 128) { - throw new Error('bad seedHex'); - } - - const res = XlmDerivePath(seedHex, derivationPath); - const key = nacl.sign.keyPair.fromSeed(res.key); - - const naclPubKey = Buffer.from(key.publicKey).toString('hex'); - const naclSecretKey = Buffer.from(key.secretKey).toString('hex'); - const address = bs58.encode(key.publicKey); - - const ret = { - address, - privateKey: naclSecretKey, - addedData: { - naclPubKey, - derivationPath: derivationPath.replace(/[']/g, 'quote'), - seedHexPart: seedHex.substr(0, 5) - } - }; - return ret; - } -} diff --git a/crypto/blockchains/sol/SolScannerProcessor.ts b/crypto/blockchains/sol/SolScannerProcessor.ts deleted file mode 100644 index 9a30834a2..000000000 --- a/crypto/blockchains/sol/SolScannerProcessor.ts +++ /dev/null @@ -1,418 +0,0 @@ -/** - * @version 0.52 - */ -import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; -import AirDAOAxios from '@crypto/common/AirDAOAxios'; -import BlocksoftUtils from '@crypto/common/AirDAOUtils'; -import BlocksoftExternalSettings from '@crypto/common/AirDAOExternalSettings'; - -import SolTmpDS from '@crypto/blockchains/sol/stores/SolTmpDS'; - -import SolUtils from '@crypto/blockchains/sol/ext/SolUtils'; -import config from '@constants/config'; - -interface UnifiedTransaction { - transactionHash: string; - blockHash: string; - blockNumber: number; - blockTime: Date; - blockConfirmations: number; - transactionDirection: 'income' | 'outcome' | 'swap_income' | 'swap_outcome'; - addressFrom: string; - addressTo: string; - addressAmount: string; - transactionStatus: 'new' | 'success' | 'confirming' | 'fail'; - transactionFee: number; - transactionJson?: { memo: string }; -} - -const CACHE_FROM_DB: Record = {}; -const CACHE_TXS: Record = {}; -const CACHE_VALID_TIME = 120000; -let CACHE_LAST_BLOCK = 0; - -export default class SolScannerProcessor { - private _settings: any; - private tokenAddress: string; - - constructor(settings: any) { - this._settings = settings; - this.tokenAddress = - typeof settings.tokenAddress !== 'undefined' ? settings.tokenAddress : ''; - } - - async getBalanceBlockchain( - address: string - ): Promise<{ balance: number; provider: string } | false> { - address = address.trim(); - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' SolScannerProcessor getBalanceBlockchain address ' + - address - ); - - let balance = 0; - try { - await SolUtils.getEpoch(); - - const apiPath = BlocksoftExternalSettings.getStatic('SOL_SERVER'); - const data = { - jsonrpc: '2.0', - id: 1, - method: 'getBalance', - params: [address] - }; - const res = await AirDAOAxios._request(apiPath, 'POST', data); - - if ( - typeof res.data.result === 'undefined' || - typeof res.data.result.value === 'undefined' - ) { - return false; - } - if ( - typeof res.data.result.context !== 'undefined' && - typeof res.data.result.context.slot !== 'undefined' - ) { - CACHE_LAST_BLOCK = res.data.result.context.slot * 1; - } - balance = res.data.result.value; - } catch (e: any) { - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' SolScannerProcessor getBalanceBlockchain address ' + - address + - ' error ' + - e.message - ); - return false; - } - return { balance, provider: 'solana-api' }; - } - - async getTransactionsBlockchain(scanData: { - account: { address: string }; - }): Promise { - const address = scanData.account.address.trim(); - const lastHashVar = address + this.tokenAddress; - this._cleanCache(); - try { - if (typeof CACHE_FROM_DB[lastHashVar] === 'undefined') { - CACHE_FROM_DB[lastHashVar] = await SolTmpDS.getCache(lastHashVar); - } - - const data = { - jsonrpc: '2.0', - id: 1, - method: 'getConfirmedSignaturesForAddress2', - params: [address, { limit: 100 }] - }; - if ( - CACHE_FROM_DB[lastHashVar] && - typeof CACHE_FROM_DB[lastHashVar].last_hash !== 'undefined' - ) { - data.params[1].until = CACHE_FROM_DB[lastHashVar].last_hash; - } - const apiPath = BlocksoftExternalSettings.getStatic('SOL_SERVER'); - const res = await AirDAOAxios._request(apiPath, 'POST', data); - if (typeof res.data.result === 'undefined' || !res.data.result) { - return false; - } - - const transactions = await this._unifyTransactions( - address, - res.data.result, - lastHashVar - ); - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' SolScannerProcessor.getTransactions finished ' + - address - ); - return transactions; - } catch (e: any) { - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' SolScannerProcessor getTransactionsBlockchain address ' + - address + - ' error ' + - e.message - ); - return false; - } - } - - private async _unifyTransactions( - address: string, - result: any[], - lastHashVar: string - ): Promise { - const transactions: UnifiedTransaction[] = []; - let lastHash = false; - let hasError = false; - for (const tx of result) { - try { - const transaction = await this._unifyTransaction(address, tx); - if (transaction) { - transactions.push(transaction); - if ( - transaction.transactionStatus === 'success' && - !lastHash && - !hasError - ) { - lastHash = transaction.transactionHash; - } - } - } catch (e: any) { - hasError = true; - if (e.message.indexOf('request failed') === -1) { - if (config.debug.appErrors) { - console.log( - this._settings.currencyCode + - ' SolScannerProcessor._unifyTransactions ' + - tx.signature + - ' error ' + - e.message - ); - } - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' SolScannerProcessor._unifyTransactions ' + - tx.signature + - ' error ' + - e.message - ); - } - } - } - - if (lastHash) { - if (!CACHE_FROM_DB[lastHashVar]) { - CACHE_FROM_DB[lastHashVar] = { last_hash: lastHash }; - await SolTmpDS.saveCache(lastHashVar, 'last_hash', lastHash); - } else if (typeof CACHE_FROM_DB[lastHashVar].last_hash === 'undefined') { - CACHE_FROM_DB[lastHashVar].last_hash = lastHash; - await SolTmpDS.saveCache(lastHashVar, 'last_hash', lastHash); - } else { - CACHE_FROM_DB[lastHashVar].last_hash = lastHash; - await SolTmpDS.updateCache(lastHashVar, 'last_hash', lastHash); - } - } - return transactions; - } - - private _cleanCache() { - const now = new Date().getTime(); - for (const key in CACHE_TXS) { - const t = now - CACHE_TXS[key].now; - if (t > CACHE_VALID_TIME) { - delete CACHE_TXS[key]; - } - } - } - - private async _unifyTransaction( - address: string, - transaction: any - ): Promise { - const data = { - jsonrpc: '2.0', - id: 1, - method: 'getConfirmedTransaction', - params: [transaction.signature, { encoding: 'jsonParsed' }] - }; - - let additional: any; - if (typeof CACHE_TXS[transaction.signature] === 'undefined') { - const apiPath = BlocksoftExternalSettings.getStatic('SOL_SERVER'); - try { - const res = await AirDAOAxios._request(apiPath, 'POST', data); - if (typeof res.data.result === 'undefined' || !res.data.result) { - return false; - } - additional = res.data.result; - CACHE_TXS[transaction.signature] = { - data: additional, - now: new Date().getTime() - }; - } catch (e: any) { - if (config.debug.cryptoErrors) { - console.log( - this._settings.currencyCode + - ' SolScannerProcessor._unifyTransaction ' + - transaction.signature + - ' request error ' + - e.message - ); - } - throw e; - } - } else { - additional = CACHE_TXS[transaction.signature].data; - } - - let addressFrom = false; - let addressTo = false; - let addressAmount = '0'; - let anyFromAddress = false; - let anyToAddress = false; - - const indexedPre: Record = {}; - const indexedPost: Record = {}; - const indexedCreated: Record = {}; - const indexedAssociated: Record = {}; - - if (this.tokenAddress) { - for (const tmp of additional.meta.preTokenBalances) { - if (tmp.mint !== this.tokenAddress) continue; - const realIndex = tmp.accountIndex; - indexedPre[realIndex] = tmp.uiTokenAmount.amount; - } - - for (const tmp of additional.meta.postTokenBalances) { - if (tmp.mint !== this.tokenAddress) continue; - const realIndex = tmp.accountIndex; - indexedPost[realIndex] = tmp.uiTokenAmount.amount; - } - - for (const tmp of additional.transaction.message.instructions) { - if (tmp.program !== 'spl-associated-token-account') continue; - indexedCreated[tmp.parsed.info.account] = tmp.parsed.info.wallet; - } - - for ( - let i = 0, ic = additional.transaction.message.accountKeys.length; - i < ic; - i++ - ) { - const tmpAddress = additional.transaction.message.accountKeys[i]; - if (tmpAddress.pubkey === '11111111111111111111111111111111') continue; - const sourceAssociatedTokenAddress = - await SolUtils.findAssociatedTokenAddress( - tmpAddress.pubkey, - this.tokenAddress - ); - indexedAssociated[sourceAssociatedTokenAddress] = tmpAddress; - } - } else { - // do nothing! - } - - let anySigner = false; - let addressAmountPlus = false; - for ( - let i = 0, ic = additional.transaction.message.accountKeys.length; - i < ic; - i++ - ) { - let tmpAddress = additional.transaction.message.accountKeys[i]; - if (tmpAddress.pubkey === '11111111111111111111111111111111') continue; - let tmpAmount = '0'; - if (typeof indexedAssociated[tmpAddress.pubkey] !== 'undefined') { - tmpAddress = indexedAssociated[tmpAddress.pubkey]; - } - if (this.tokenAddress) { - const to = typeof indexedPost[i] !== 'undefined' ? indexedPost[i] : 0; - const from = typeof indexedPre[i] !== 'undefined' ? indexedPre[i] : 0; - tmpAmount = BlocksoftUtils.diff(to, from).toString(); - } else { - tmpAmount = BlocksoftUtils.diff( - additional.meta.postBalances[i], - additional.meta.preBalances[i] - ).toString(); - } - - if (tmpAddress.pubkey && tmpAddress.signer) { - anySigner = tmpAddress.pubkey; - } - - if (tmpAmount === '0') continue; - - if ( - tmpAddress.pubkey === address || - (typeof indexedCreated[tmpAddress.pubkey] !== 'undefined' && - indexedCreated[tmpAddress.pubkey] === address) - ) { - if (tmpAmount.indexOf('-') === -1) { - addressTo = tmpAddress.pubkey; - addressAmount = tmpAmount; - addressAmountPlus = true; - } else { - addressFrom = tmpAddress.pubkey; - addressAmount = tmpAmount.replace('-', ''); - } - } else { - if (tmpAddress.signer) { - anyFromAddress = tmpAddress.pubkey; - } else { - anyToAddress = tmpAddress.pubkey; - } - } - } - - if (!addressFrom && anySigner !== addressTo) { - addressFrom = anySigner; - } - if (!addressFrom && !addressTo) { - return false; - } - if (anyFromAddress && !addressFrom) { - addressFrom = anyFromAddress; - } - if (anyToAddress && !addressTo) { - addressTo = anyToAddress; - } - if (!addressTo) { - addressTo = 'System'; - } - - let formattedTime = transaction.blockTime; - try { - formattedTime = BlocksoftUtils.toDate(transaction.blockTime); - } catch (e: any) { - e.message += - ' timestamp error transaction2 data ' + JSON.stringify(transaction); - throw e; - } - let transactionStatus: 'new' | 'success' | 'confirming' | 'fail' = 'new'; - if (transaction.confirmationStatus === 'finalized') { - transactionStatus = 'success'; - } else if (transaction.confirmationStatus === 'confirmed') { - transactionStatus = 'confirming'; - } - if (typeof transaction.err !== 'undefined' && transaction.err) { - transactionStatus = 'fail'; - } - - let transactionDirection: UnifiedTransaction['transactionDirection'] = - addressFrom === address ? 'outcome' : 'income'; - if (!addressFrom && anySigner === addressTo) { - if (addressAmountPlus) { - transactionDirection = 'swap_income'; - } else { - transactionDirection = 'swap_outcome'; - } - } - const blockConfirmations = - CACHE_LAST_BLOCK > 0 - ? Math.round(CACHE_LAST_BLOCK - additional.slot * 1) - : 0; - const tx: UnifiedTransaction = { - transactionHash: transaction.signature, - blockHash: additional.transaction.message.recentBlockhash, - blockNumber: transaction.slot, - blockTime: formattedTime, - blockConfirmations, - transactionDirection, - addressFrom: addressFrom === address ? '' : addressFrom, - addressTo: addressTo === address ? '' : addressTo, - addressAmount, - transactionStatus, - transactionFee: additional.meta.fee - }; - if (typeof transaction.memo !== 'undefined' && transaction.memo) { - tx.transactionJson = { memo: transaction.memo }; - } - return tx; - } -} diff --git a/crypto/blockchains/sol/SolScannerProcessorSpl.ts b/crypto/blockchains/sol/SolScannerProcessorSpl.ts deleted file mode 100644 index a2d3a9de9..000000000 --- a/crypto/blockchains/sol/SolScannerProcessorSpl.ts +++ /dev/null @@ -1,116 +0,0 @@ -/** - * @version 0.52 - */ -import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; -import AirDAOAxios from '@crypto/common/AirDAOAxios'; -import BlocksoftExternalSettings from '@crypto/common/AirDAOExternalSettings'; -import SolScannerProcessor from '@crypto/blockchains/sol/SolScannerProcessor'; -import { AxiosResponse } from 'axios'; - -interface ParsedInfo { - state?: string; - mint: string; - tokenAmount: number; -} - -interface CacheBalances { - [address: string]: { - [tokenAddress: string]: { - amount: number; - }; - time: number; - }; -} - -const CACHE_BALANCES: CacheBalances = {}; -const CACHE_VALID_TIME = 30000; // 30 seconds - -export default class SolScannerProcessorSpl extends SolScannerProcessor { - /** - * @param {string} address - * @return {Promise<{balance: number; provider: string} | false>} - */ - async getBalanceBlockchain( - address: string - ): Promise<{ balance: number; provider: string } | false> { - address = address.trim(); - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' SolScannerProcessorSpl getBalanceBlockchain address ' + - address - ); - - const now = new Date().getTime(); - let balance = 0; - try { - if ( - typeof CACHE_BALANCES[address] === 'undefined' || - typeof CACHE_BALANCES[address].time === 'undefined' || - now - CACHE_BALANCES[address].time < CACHE_VALID_TIME - ) { - CACHE_BALANCES[address] = {}; - const apiPath = BlocksoftExternalSettings.getStatic('SOL_SERVER'); - - const data = { - jsonrpc: '2.0', - id: 1, - method: 'getTokenAccountsByOwner', - params: [ - address, - { - programId: 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA' - }, - { encoding: 'jsonParsed', commitment: 'processed' } - ] - }; - - // @ts-ignore - const res: AxiosResponse = await AirDAOAxios._request( - apiPath, - 'POST', - data - ); - if ( - res.data === false || - typeof res.data.result === 'undefined' || - typeof res.data.result.value === 'undefined' - ) { - return false; - } - for (const account of res.data.result.value) { - if (typeof account.account === 'undefined') continue; - if ( - typeof account.account.data.program === 'undefined' || - account.account.data.program !== 'spl-token' - ) - continue; - const parsed: ParsedInfo = account.account.data.parsed.info; - if ( - typeof parsed.state === 'undefined' || - parsed.state !== 'initialized' - ) - continue; - CACHE_BALANCES[address][parsed.mint] = { amount: parsed.tokenAmount }; // "amount": "1606300", "decimals": 6, "uiAmount": 1.6063, "uiAmountString": "1.6063" - } - CACHE_BALANCES[address].time = now; - } - if ( - typeof CACHE_BALANCES[address][this.tokenAddress] === 'undefined' || - typeof CACHE_BALANCES[address][this.tokenAddress].amount === 'undefined' - ) { - balance = 0; - } else { - balance = CACHE_BALANCES[address][this.tokenAddress].amount; - } - } catch (e: any) { - AirDAOCryptoLog.log( - this._settings.currencyCode + - ` SolScannerProcessorSpl getBalanceBlockchain address ` + - `${address} ` + - ` error ${e.message}` - ); - return false; - } - return { balance, provider: 'solana-api' }; - } -} diff --git a/crypto/blockchains/sol/SolTokenProcessor.ts b/crypto/blockchains/sol/SolTokenProcessor.ts deleted file mode 100644 index c195be902..000000000 --- a/crypto/blockchains/sol/SolTokenProcessor.ts +++ /dev/null @@ -1,123 +0,0 @@ -/** - * @version 0.52 - */ -import AirDAOAxios from '@crypto/common/AirDAOAxios'; -import BlocksoftExternalSettings from '@crypto/common/AirDAOExternalSettings'; -import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; -import { AxiosResponse } from 'axios'; - -interface TokenDetails { - currencyCodePrefix: string; - currencyCode: string; - currencyName: string; - tokenType: string; - tokenAddress: string; - tokenDecimals: number; - icon?: string; - description?: string; - coingeckoId?: string; - provider: string; - symbol?: string; - name?: string; - decimals?: number; - logoURI?: string; - website?: string; -} - -export default class SolTokenProcessor { - /** - * @param {string} tokenAddress - * @returns {Promise} - */ - async getTokenDetails(tokenAddress: string): Promise { - const link = await BlocksoftExternalSettings.get('SOL_TOKENS_LIST'); - // @ts-ignore - const res: AxiosResponse = await AirDAOAxios.get(link); - if (!res || typeof res.data.tokens === 'undefined' || !res.data.tokens) { - return false; - } - - let tmp: TokenDetails | false = false; - for (const token of res.data.tokens) { - if (token.address === tokenAddress) { - if (token.chainId !== 101) continue; - tmp = token; - break; - } - } - if (tmp) { - const tokenDetails: TokenDetails = { - currencyCodePrefix: 'CUSTOM_SOL_', - currencyCode: tmp.symbol || '', - currencyName: tmp.name || '', - tokenType: 'SOL', - tokenAddress, - tokenDecimals: tmp.decimals || 0, - icon: tmp.logoURI || '', - description: tmp.website || '', - coingeckoId: tmp.coingeckoId || '', - provider: 'sol' - }; - return tokenDetails; - } - - let decimals = 6; - try { - const apiPath = BlocksoftExternalSettings.getStatic('SOL_SERVER'); - const data = { - jsonrpc: '2.0', - id: 1, - method: 'getAccountInfo', - params: [ - tokenAddress, - { - encoding: 'jsonParsed' - } - ] - }; - const response = await AirDAOAxios._request(apiPath, 'POST', data); - if ( - response && - response.data && - typeof response.data.result !== 'undefined' && - typeof response.data.result.value !== 'undefined' - ) { - decimals = response.data.result.value.data.parsed.info.decimals; - } else { - return false; - } - if ( - typeof response.data.result.value.owner === 'undefined' || - response.data.result.value.owner !== - 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA' - ) { - return false; - } - if ( - typeof response.data.result.value.data.program === 'undefined' || - response.data.result.value.data.program !== 'spl-token' - ) { - return false; - } - decimals = response.data.result.value.data.parsed.info.decimals; - } catch (e: any) { - AirDAOCryptoLog.log( - 'SolTokenProcessor getTokenDetails tokenAddress ' + - tokenAddress + - ' error ' + - e.message - ); - return false; - } - - return { - currencyCodePrefix: 'CUSTOM_SOL_', - currencyCode: 'UNKNOWN_TOKEN_' + tokenAddress, - currencyName: tokenAddress, - tokenType: 'SOL', - tokenAddress, - tokenDecimals: decimals, - provider: 'sol' - }; - } -} diff --git a/crypto/blockchains/sol/SolTransferProcessor.ts b/crypto/blockchains/sol/SolTransferProcessor.ts deleted file mode 100644 index b5e69c7f1..000000000 --- a/crypto/blockchains/sol/SolTransferProcessor.ts +++ /dev/null @@ -1,477 +0,0 @@ -/** - * @version 0.52 - */ -import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; -import BlocksoftUtils from '@crypto/common/AirDAOUtils'; -import BlocksoftExternalSettings from '@crypto/common/AirDAOExternalSettings'; -import BlocksoftBalances from '@crypto/actions/BlocksoftBalances/BlocksoftBalances'; - -import { - PublicKey, - SystemProgram, - Transaction, - StakeProgram, - Authorized -} from '@solana/web3.js/src'; -import SolUtils from '@crypto/blockchains/sol/ext/SolUtils'; -import SolTmpDS from '@crypto/blockchains/sol/stores/SolTmpDS'; -import SolStakeUtils from '@crypto/blockchains/sol/ext/SolStakeUtils'; -import { Buffer } from 'buffer'; -import AirDAOCryptoUtils from '@crypto/common/AirDAOCryptoUtils'; -import AirDAOAxios from '@crypto/common/AirDAOAxios'; -import config from '@constants/config'; -import { AirDAOBlockchainTypes } from '@crypto/blockchains/AirDAOBlockchainTypes'; - -export default class SolTransferProcessor - implements AirDAOBlockchainTypes.TransferProcessor -{ - private _settings: { network: string; currencyCode: string }; - - constructor(settings: { network: string; currencyCode: string }) { - this._settings = settings; - } - - needPrivateForFee(): boolean { - return false; - } - - checkSendAllModal(data: { currencyCode: any }): boolean { - return false; - } - - async getFeeRate( - data: AirDAOBlockchainTypes.TransferData, - privateData: AirDAOBlockchainTypes.TransferPrivateData, - additionalData: {} = {} - ): Promise { - const result: AirDAOBlockchainTypes.FeeRateResult = { - selectedFeeIndex: -3, - shouldShowFees: false - } as AirDAOBlockchainTypes.FeeRateResult; - - const feeForTx = BlocksoftExternalSettings.getStatic('SOL_PRICE'); - result.fees = [ - { - langMsg: 'xrp_speed_one', - feeForTx, - amountForTx: data.amount - } - ]; - result.selectedFeeIndex = 0; - - return result; - } - - async getTransferAllBalance( - data: AirDAOBlockchainTypes.TransferData, - privateData: AirDAOBlockchainTypes.TransferPrivateData, - additionalData: AirDAOBlockchainTypes.TransferAdditionalData = {} - ): Promise { - const address = data.addressFrom.trim(); - let rent = 0; - let balance = data.amount; - try { - const beachPath = - 'https://public-api.solanabeach.io/v1/account/' + address + '?'; - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' SolTransferProcessor.getTransferAllBalance address ' + - address + - ' beach link ' + - beachPath - ); - const res = await AirDAOAxios.get(beachPath); - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' SolTransferProcessor.getTransferAllBalance address ' + - address + - ' beach res ', - res?.data - ); - if ( - typeof res?.data?.value !== 'undefined' && - typeof res?.data?.value?.base !== 'undefined' - ) { - if (res?.data?.value?.base?.address?.address !== address) { - throw new Error( - 'wrong value address ' + res?.data?.value?.base?.address?.address - ); - } - balance = res?.data?.value?.base?.balance || data.amount; - rent = res?.data?.value?.base?.rentExemptReserve || 0; - if (rent) { - balance = BlocksoftUtils.diff(balance, rent); - } - } - } catch (e) { - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' SolTransferProcessor.getTransferAllBalance address ' + - address + - ' beach error ' + - e.message - ); - } - - // @ts-ignore - await AirDAOCryptoLog.log( - this._settings.currencyCode + - ' SolTransferProcessor.getTransferAllBalance ', - data.addressFrom + ' => ' + balance - ); - - const fees = await this.getFeeRate(data, privateData, additionalData); - - const amount = BlocksoftUtils.diff( - balance, - fees.fees[0].feeForTx - ).toString(); - - return { - ...fees, - shouldShowFees: false, - selectedTransferAllBalance: amount - }; - } - - /** - * @param data - * @param privateData - * @param uiData - */ - async sendTx( - data: AirDAOBlockchainTypes.TransferData, - privateData: AirDAOBlockchainTypes.TransferPrivateData, - uiData: AirDAOBlockchainTypes.TransferUiData - ): Promise { - if (typeof privateData.privateKey === 'undefined') { - throw new Error('SOL transaction required privateKey (derivedSeed)'); - } - if (typeof data.addressTo === 'undefined') { - throw new Error('SOL transaction required addressTo'); - } - - if ( - uiData && - typeof uiData.uiErrorConfirmed !== 'undefined' && - (uiData.uiErrorConfirmed === 'UI_CONFIRM_ADDRESS_TO_EMPTY_BALANCE' || - uiData.uiErrorConfirmed === 'UI_CONFIRM_DOUBLE_SEND') - ) { - // do nothing - } else if ( - data.addressTo !== 'STAKE' && - data.addressTo.indexOf('UNSTAKE') === -1 - ) { - if (typeof uiData?.selectedFee?.bseOrderId === 'undefined') { - const balance = await BlocksoftBalances.setCurrencyCode('SOL') - .setAddress(data.addressTo) - .getBalance('SolSendTx'); - if ( - !balance || - typeof balance.balance === 'undefined' || - balance.balance === 0 - ) { - throw new Error('UI_CONFIRM_ADDRESS_TO_EMPTY_BALANCE'); - } - } - } - - const tx = new Transaction(); - - let seed, - stakeAddress = false; - try { - const fromPubkey = new PublicKey(data.addressFrom); - if (data.addressTo.indexOf('UNSTAKE') === 0) { - if (data.amount === 'ALL') { - tx.add( - StakeProgram.deactivate({ - authorizedPubkey: fromPubkey, - stakePubkey: new PublicKey(data.blockchainData.stakeAddress) - }) - ); - } else { - tx.add( - StakeProgram.withdraw({ - authorizedPubkey: fromPubkey, - stakePubkey: new PublicKey(data.blockchainData.stakeAddress), - lamports: data.amount * 1, - toPubkey: fromPubkey - }) - ); - } - } else if (data.addressTo === 'STAKE') { - await AirDAOCryptoLog.log( - this._settings.currencyCode + - ' SolTransferProcessor.sendTx ' + - data.addressFrom + - ' => ' + - data.addressTo + - ' ' + - data.amount + - ' build start' - ); - const validator = data.blockchainData.voteAddress; - const authorized = new Authorized(fromPubkey, fromPubkey); - if (typeof validator === 'undefined' || !validator) { - throw new Error('no validator field'); - } - await AirDAOCryptoLog.log( - this._settings.currencyCode + - ' SolTransferProcessor.sendTx ' + - data.addressFrom + - ' => ' + - data.addressTo + - ' ' + - data.amount + - ' build authorized' - ); - - // https://github.com/velas/JsWallet/blob/251ad92bb5c2cd9a62477746a3db934b6dce0c4b/velas/velas-staking.js - // https://explorer.solana.com/tx/2ffmtkj3Yj51ZWCEHG6jb6s78F73eoiQdqURV7z65kSVLiPcm8Y9NE45FgfgwbddJD8kfgCiTpmrEu7J8WKpAQeE - await SolStakeUtils.getAccountStaked(data.addressFrom); - - await AirDAOCryptoLog.log( - this._settings.currencyCode + - ' SolTransferProcessor.sendTx ' + - data.addressFrom + - ' => ' + - data.addressTo + - ' ' + - data.amount + - ' build createWithSeed started' - ); - let start = 0; - const lastSeed = await SolTmpDS.getCache(data.addressFrom); - if ( - typeof lastSeed !== 'undefined' && - lastSeed && - typeof lastSeed.seed !== 'undefined' && - lastSeed.seed - ) { - start = lastSeed.seed * 1; - } - for (let i = 1; i <= 10000; i++) { - const tmpSeed = (i + start).toString(); - - /*try { - stakeAccount = await PublicKey.createWithSeed( - fromPubkey, - tmpSeed, - StakeProgram.programId - ) - } catch (e1) { - await AirDAOCryptoLog.log(this._settings.currencyCode + ' SolTransferProcessor.sendTx ' + data.addressFrom + ' => ' + data.addressTo + ' ' + data.amount + ' build createWithSeed error ' + e1.message) - }*/ - - const buffer = Buffer.concat([ - fromPubkey.toBuffer(), - Buffer.from(tmpSeed), - StakeProgram.programId.toBuffer() - ]); - const hash = Buffer.from( - await AirDAOCryptoUtils.sha256(buffer.toString('hex')), - 'hex' - ); - const stakeAccount = new PublicKey(Buffer.from(hash, 'hex')); - - stakeAddress = stakeAccount.toBase58(); - const isUsed = SolStakeUtils.checkAccountStaked( - data.addressFrom, - stakeAddress - ); - if (!isUsed) { - await SolTmpDS.saveCache(data.addressFrom, 'seed', tmpSeed); - seed = tmpSeed; - break; - } - } - await AirDAOCryptoLog.log( - this._settings.currencyCode + - ' SolTransferProcessor.sendTx ' + - data.addressFrom + - ' => ' + - data.addressTo + - ' ' + - data.amount + - ' build createWithSeed finished' - ); - - if (!stakeAddress) { - throw new Error('Stake address seed is not found'); - } - - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' SolTransferProcessor.sendTx ' + - data.addressFrom + - ' => ' + - data.addressTo + - ' ' + - data.amount + - ' stakeAddress ' + - stakeAddress + - ' seed ' + - seed - ); - - const amount = data.amount * 1; - tx.add( - StakeProgram.createAccountWithSeed({ - authorized, - fromPubkey, - stakePubkey: new PublicKey(stakeAddress), - basePubkey: fromPubkey, - seed, - lamports: amount - }) - ); - - // https://github.com/solana-labs/solana-web3.js/blob/35f0608a8363d3878d045bdb09cdd13af696bc6b/test/transaction.test.ts - tx.add( - StakeProgram.delegate({ - stakePubkey: new PublicKey(stakeAddress), - authorizedPubkey: new PublicKey(data.addressFrom), - votePubkey: new PublicKey(validator) - }) - ); - } else { - // @ts-ignore - tx.add( - SystemProgram.transfer({ - fromPubkey: new PublicKey(data.addressFrom), - toPubkey: new PublicKey(data.addressTo), - lamports: data.amount * 1 - }) - ); - } - } catch (e) { - if (config.debug.cryptoErrors) { - console.log( - this._settings.currencyCode + - ' SolTransferProcessor.sendTx ' + - data.addressFrom + - ' => ' + - data.addressTo + - ' ' + - data.amount + - ' build error ' - ); - console.log(e); - } - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' SolTransferProcessor.sendTx ' + - data.addressFrom + - ' => ' + - data.addressTo + - ' ' + - data.amount + - ' build error ' + - e.message - ); - this.trxError(e.message); - } - - await SolUtils.signTransaction( - tx, - privateData.privateKey, - data.addressFrom - ); - - // @ts-ignore - const signedData = tx.serialize().toString('base64'); - if ( - typeof uiData !== 'undefined' && - typeof uiData.selectedFee !== 'undefined' && - typeof uiData.selectedFee.rawOnly !== 'undefined' && - uiData.selectedFee.rawOnly - ) { - return { rawOnly: uiData.selectedFee.rawOnly, raw: signedData }; - } - - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' SolTransferProcessor.sendTx ' + - data.addressFrom + - ' => ' + - data.addressTo + - ' ' + - data.amount, - signedData - ); - - const result = {} as AirDAOBlockchainTypes.SendTxResult; - try { - const sendRes = await SolUtils.sendTransaction(signedData); - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' SolTransferProcessor.sendTx ' + - data.addressFrom + - ' => ' + - data.addressTo + - ' ' + - data.amount, - sendRes - ); - if ( - typeof sendRes === 'undefined' || - !sendRes || - typeof sendRes === 'undefined' - ) { - throw new Error('SYSTEM_ERROR'); - } - result.transactionHash = sendRes; - if (stakeAddress) { - SolStakeUtils.setAccountStaked(data.addressFrom, stakeAddress); - } - if (data.addressTo.indexOf('UNSTAKE') === 0) { - await SolStakeUtils.getAccountStaked(data.addressFrom, true); - } - } catch (e) { - if (config.debug.cryptoErrors) { - console.log( - this._settings.currencyCode + - ' SolTransferProcessor.sendTx ' + - data.addressFrom + - ' => ' + - data.addressTo + - ' ' + - data.amount + - ' send error ' - ); - console.log(e); - } - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' SolTransferProcessor.sendTx ' + - data.addressFrom + - ' => ' + - data.addressTo + - ' ' + - data.amount + - ' send error ' + - e.message - ); - this.trxError(e.message, data.addressTo); - } - return result; - } - - trxError(msg: string, addressTo: string) { - if (msg.indexOf('insufficient funds for instruction') !== -1) { - if (addressTo === 'STAKE') { - throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_STAKE_SOL'); - } else { - throw new Error('SERVER_RESPONSE_NOT_ENOUGH_BALANCE_SOL'); - } - } else if ( - msg.indexOf('incorrect program id for instruction') !== -1 && - addressTo === 'STAKE' - ) { - throw new Error('SERVER_RESPONSE_NOT_VALIDATOR_STAKE_SOL'); - } else { - throw new Error(msg); - } - } -} diff --git a/crypto/blockchains/sol/SolTransferProcessorSpl.ts b/crypto/blockchains/sol/SolTransferProcessorSpl.ts deleted file mode 100644 index d5bdc434d..000000000 --- a/crypto/blockchains/sol/SolTransferProcessorSpl.ts +++ /dev/null @@ -1,341 +0,0 @@ -/** - * @version 0.52 - */ - -import { BlocksoftBlockchainTypes } from '@crypto/blockchains/BlocksoftBlockchainTypes'; -import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; - -import { - PublicKey, - TransactionInstruction, - Transaction -} from '@solana/web3.js/src'; -import SolUtils from '@crypto/blockchains/sol/ext/SolUtils'; -import SolTransferProcessor from '@crypto/blockchains/sol/SolTransferProcessor'; -import SolInstructions from '@crypto/blockchains/sol/ext/SolInstructions'; -import BlocksoftExternalSettings from '@crypto/common/AirDAOExternalSettings'; - -export default class SolTransferProcessorSpl - extends SolTransferProcessor - implements BlocksoftBlockchainTypes.TransferProcessor -{ - async getFeeRate( - data: BlocksoftBlockchainTypes.TransferData, - privateData: BlocksoftBlockchainTypes.TransferPrivateData, - additionalData: {} = {} - ): Promise { - const result: BlocksoftBlockchainTypes.FeeRateResult = { - selectedFeeIndex: -3, - shouldShowFees: false - } as BlocksoftBlockchainTypes.FeeRateResult; - - const destinationAssociatedTokenAddress = - await SolUtils.findAssociatedTokenAddress( - data.addressTo, - this._settings.tokenAddress - ); - const destinationAccountInfo = await SolUtils.getAccountInfo( - destinationAssociatedTokenAddress - ); - let feeForTx = BlocksoftExternalSettings.getStatic('SOL_PRICE'); // ◎0.000005 - if ( - destinationAccountInfo && - typeof destinationAccountInfo.owner !== 'undefined' && - destinationAccountInfo.owner === SolUtils.getTokenProgramID() - ) { - // do nothing - } else { - // will create new account - feeForTx = BlocksoftExternalSettings.getStatic('SOL_PRICE_NEW_SPL'); // // ◎0.00203928 + 0.000005 = 0.00204428 - } - - result.fees = [ - { - langMsg: 'xrp_speed_one', - feeForTx, - amountForTx: data.amount - } - ]; - result.selectedFeeIndex = 0; - - return result; - } - - async getTransferAllBalance( - data: BlocksoftBlockchainTypes.TransferData, - privateData: BlocksoftBlockchainTypes.TransferPrivateData, - additionalData: BlocksoftBlockchainTypes.TransferAdditionalData = {} - ): Promise { - const balance = data.amount; - // @ts-ignore - await AirDAOCryptoLog.log( - this._settings.currencyCode + - ` SolTransferProcessorSpl.getTransferAllBalance, - ${data.addressFrom} + ' => ' + ${balance}` - ); - - const fees = await this.getFeeRate(data, privateData, additionalData); - - return { - ...fees, - shouldShowFees: false, - selectedTransferAllBalance: balance - }; - } - - /** - * @param data - * @param privateData - * @param uiData - */ - async sendTx( - data: BlocksoftBlockchainTypes.TransferData, - privateData: BlocksoftBlockchainTypes.TransferPrivateData, - uiData: BlocksoftBlockchainTypes.TransferUiData - ): Promise { - if (typeof privateData.privateKey === 'undefined') { - throw new Error('SPL transaction required privateKey (derivedSeed)'); - } - if (typeof data.addressTo === 'undefined') { - throw new Error('SPL transaction required addressTo'); - } - - const sourceAssociatedTokenAddress = - await SolUtils.findAssociatedTokenAddress( - data.addressFrom, - this._settings.tokenAddress - ); - const sourceAccountInfo = await SolUtils.getAccountInfo( - sourceAssociatedTokenAddress - ); - if ( - !sourceAccountInfo || - typeof sourceAccountInfo.lamports === 'undefined' || - sourceAccountInfo.lamports * 1 === 0 - ) { - throw new Error('Cannot send from address with zero SOL balance'); - } - - const tx = new Transaction(); - let txData = false; - let destinationAccountInfo = await SolUtils.getAccountInfo(data.addressTo); - if ( - destinationAccountInfo && - typeof destinationAccountInfo.owner !== 'undefined' && - destinationAccountInfo.owner === SolUtils.getTokenProgramID() - ) { - txData = { - mint: this._settings.tokenAddress, - decimals: this._settings.decimals, - to: data.addressTo, - amount: data.amount - }; - } else { - if ( - !destinationAccountInfo || - typeof destinationAccountInfo.lamports === 'undefined' || - destinationAccountInfo.lamports * 1 === 0 - ) { - throw new Error('SERVER_RESPONSE_RECEIVER_EMPTY_BALANCE'); - } - - const destinationAssociatedTokenAddress = - await SolUtils.findAssociatedTokenAddress( - data.addressTo, - this._settings.tokenAddress - ); - destinationAccountInfo = await SolUtils.getAccountInfo( - destinationAssociatedTokenAddress - ); - if ( - destinationAccountInfo && - typeof destinationAccountInfo.owner !== 'undefined' && - destinationAccountInfo.owner === SolUtils.getTokenProgramID() - ) { - // do nothing - } else { - const tmp1 = new TransactionInstruction({ - keys: [ - { - pubkey: new PublicKey(data.addressTo), - isSigner: false, - isWritable: false - } - ], - data: SolInstructions.encodeOwnerValidationInstruction({ - account: new PublicKey('11111111111111111111111111111111') - }), - programId: SolUtils.getOwnerValidationProgramId() - }); - tx.add(tmp1); - - const keys = [ - { - pubkey: new PublicKey(data.addressFrom), - isSigner: true, - isWritable: true - }, - { - pubkey: new PublicKey(destinationAssociatedTokenAddress), - isSigner: false, - isWritable: true - }, - { - pubkey: new PublicKey(data.addressTo), - isSigner: false, - isWritable: false - }, - { - pubkey: new PublicKey(this._settings.tokenAddress), - isSigner: false, - isWritable: false - }, - { - pubkey: new PublicKey('11111111111111111111111111111111'), - isSigner: false, - isWritable: false - }, - { - pubkey: new PublicKey(SolUtils.getTokenProgramID()), - isSigner: false, - isWritable: false - }, - { - pubkey: new PublicKey( - 'SysvarRent111111111111111111111111111111111' - ), - isSigner: false, - isWritable: false - } - ]; - const tmp2 = new TransactionInstruction({ - keys, - programId: SolUtils.getAssociatedTokenProgramId(), - data: Buffer.from([]) - }); - tx.add(tmp2); - // add owner - } - txData = { - mint: this._settings.tokenAddress, - decimals: this._settings.decimals, - to: destinationAssociatedTokenAddress, - amount: data.amount - }; - } - - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' SolTransferProcessorSpl.sendTx ' + - data.addressFrom + - ' => ' + - data.addressTo + - ' ' + - data.amount, - txData - ); - - try { - // https://github.com/project-serum/spl-token-wallet/blob/eda316d30bb7be4250dc622e41b6fda6f54ca7d8/src/utils/tokens/instructions.js#L99 - const keys = [ - { - pubkey: new PublicKey(sourceAssociatedTokenAddress), - isSigner: false, - isWritable: true - }, - { - pubkey: new PublicKey(txData.mint), - isSigner: false, - isWritable: false - }, - { pubkey: new PublicKey(txData.to), isSigner: false, isWritable: true }, - { - pubkey: new PublicKey(data.addressFrom), - isSigner: true, - isWritable: false - } - ]; - tx.add( - new TransactionInstruction({ - keys, - data: SolInstructions.encodeTokenInstructionData({ - transferChecked: { - amount: txData.amount, - decimals: txData.decimals - } - }), - programId: SolUtils.getTokenProgramID() - }) - ); - } catch (e: any) { - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' SolTransferProcessorSpl.sendTx ' + - data.addressFrom + - ' => ' + - data.addressTo + - ' ' + - data.amount + - ' error ' + - e.message - ); - throw new Error(e.message); - } - - await SolUtils.signTransaction( - tx, - privateData.privateKey, - data.addressFrom - ); - - // @ts-ignore - const signedData = tx.serialize().toString('base64'); - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' SolTransferProcessorSpl.sendTx ' + - data.addressFrom + - ' => ' + - data.addressTo + - ' ' + - data.amount, - signedData - ); - - const result = {} as BlocksoftBlockchainTypes.SendTxResult; - try { - const sendRes = await SolUtils.sendTransaction(signedData); - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' SolTransferProcessorSpl.sendTx ' + - data.addressFrom + - ' => ' + - data.addressTo + - ' ' + - data.amount, - sendRes - ); - if ( - typeof sendRes === 'undefined' || - !sendRes || - typeof sendRes === 'undefined' - ) { - throw new Error('SYSTEM_ERROR'); - } - result.transactionHash = sendRes; - } catch (e: any) { - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' SolTransferProcessorSpl.sendTx ' + - data.addressFrom + - ' => ' + - data.addressTo + - ' ' + - data.amount + - ' send error ' + - e.message - ); - throw e; - } - return result; - } -} diff --git a/crypto/blockchains/sol/ext/SolInstructions.ts b/crypto/blockchains/sol/ext/SolInstructions.ts deleted file mode 100644 index 949f8aaa1..000000000 --- a/crypto/blockchains/sol/ext/SolInstructions.ts +++ /dev/null @@ -1,83 +0,0 @@ -/** - * @version 0.52 - * https://github.com/project-serum/spl-token-wallet/blob/master/src/utils/tokens/instructions.js - */ -import * as BufferLayout from '@solana/buffer-layout'; -import { PublicKey } from '@solana/web3.js'; - -// @ts-ignore -const LAYOUT = BufferLayout.union(BufferLayout.u8('instruction')); -LAYOUT.addVariant( - 0, - BufferLayout.struct([ - BufferLayout.u8('decimals'), - BufferLayout.blob(32, 'mintAuthority'), - BufferLayout.u8('freezeAuthorityOption'), - BufferLayout.blob(32, 'freezeAuthority') - ]), - 'initializeMint' -); -LAYOUT.addVariant(1, BufferLayout.struct([]), 'initializeAccount'); -LAYOUT.addVariant( - 7, - BufferLayout.struct([BufferLayout.nu64('amount')]), - 'mintTo' -); -LAYOUT.addVariant( - 8, - BufferLayout.struct([BufferLayout.nu64('amount')]), - 'burn' -); -LAYOUT.addVariant(9, BufferLayout.struct([]), 'closeAccount'); -LAYOUT.addVariant( - 12, - BufferLayout.struct([ - BufferLayout.nu64('amount'), - BufferLayout.u8('decimals') - ]), - 'transferChecked' -); - -const instructionMaxSpan = Math.max( - ...Object.values(LAYOUT.registry).map((r) => r.span) -); - -class PublicKeyLayout extends BufferLayout.Blob { - constructor(property: string) { - super(32, property); - } - - // @ts-ignore - decode(b: Buffer, offset: number) { - return new PublicKey(super.decode(b, offset)); - } - - // @ts-ignore - encode(src: PublicKey, b: Buffer, offset: number) { - return super.encode(src.toBuffer(), b, offset); - } -} - -function publicKeyLayout(property: string) { - return new PublicKeyLayout(property); -} - -export const OWNER_VALIDATION_LAYOUT = BufferLayout.struct([ - // @ts-ignore - publicKeyLayout('account') -]); - -export default { - encodeTokenInstructionData(instruction: any) { - const b = Buffer.alloc(instructionMaxSpan); - const span = LAYOUT.encode(instruction, b); - const res = b.slice(0, span); - return res; - }, - - encodeOwnerValidationInstruction(instruction: any) { - const b = Buffer.alloc(OWNER_VALIDATION_LAYOUT.span); - const span = OWNER_VALIDATION_LAYOUT.encode(instruction, b); - return b.slice(0, span); - } -}; diff --git a/crypto/blockchains/sol/ext/SolStakeUtils.ts b/crypto/blockchains/sol/ext/SolStakeUtils.ts deleted file mode 100644 index c6883d500..000000000 --- a/crypto/blockchains/sol/ext/SolStakeUtils.ts +++ /dev/null @@ -1,406 +0,0 @@ -/** - * @version 0.52 - */ -import AirDAOAxios from '@crypto/common/AirDAOAxios'; -import BlocksoftExternalSettings from '@crypto/common/AirDAOExternalSettings'; - -import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; -import BlocksoftUtils from '@crypto/common/AirDAOUtils'; -import SolUtils from '@crypto/blockchains/sol/ext/SolUtils'; -import config from '@constants/config'; - -interface Validator { - address: string; - commission: number; - activatedStake: number; - name: string; - description: string; - website: string; - index: number; -} - -interface ValidatorData { - [address: string]: Validator; -} - -interface CacheVotes { - data: Validator[]; - time: number; -} - -interface CacheStaked { - [address: string]: { - all: { [subaddress: string]: boolean }; - active: { - amount: number; - stakeAddress: string; - reserved: number; - active: boolean; - status: string; - order: number; - diff: string; - }[]; - rewards: any[]; - }; -} - -const CACHE_STAKED: CacheStaked = {}; -const CACHE_VOTES: CacheVotes = { - data: [], - time: 0 -}; -const CACHE_VALID_TIME = 12000000; // 200 minutes - -const validatorsConstants: Validator[] = require('@crypto/blockchains/sol/ext/validators'); - -const validators: { time: number; data: ValidatorData } = { - time: 0, - data: {} -}; -for (const tmp of validatorsConstants) { - validators.data[tmp.address] = tmp; -} - -const init = async () => { - const link = await BlocksoftExternalSettings.get('SOL_VALIDATORS_LIST'); - const res = await AirDAOAxios.get(link); - if (!res.data || typeof res.data[0] === 'undefined' || !res.data[0]) { - return false; - } - validators.data = {}; - for (const tmp of res.data) { - if (typeof tmp.address === 'undefined') continue; - validators.data[tmp.address] = tmp; - } - validators.time = new Date().getTime(); -}; - -export default { - // https://docs.solana.com/developing/clients/jsonrpc-api#getvoteaccounts - async getVoteAddresses(): Promise { - try { - const now = new Date().getTime(); - if (CACHE_VOTES.time && now - CACHE_VOTES.time < CACHE_VALID_TIME) { - return CACHE_VOTES.data; - } - if (!validators.time) { - await init(); - } - - const apiPath = BlocksoftExternalSettings.getStatic('SOL_SERVER'); - const getVote = { jsonrpc: '2.0', id: 1, method: 'getVoteAccounts' }; - const resVote = await AirDAOAxios._request(apiPath, 'POST', getVote); - if ( - !resVote || - typeof resVote.data === 'undefined' || - typeof resVote.data.result === 'undefined' || - !resVote.data.result || - typeof resVote.data.result.current === 'undefined' - ) { - return CACHE_VOTES.data; - } - CACHE_VOTES.data = []; - for (const tmp of resVote.data.result.current) { - const address = tmp.votePubkey; - if (typeof validators.data[address] === 'undefined') continue; - - const validator: Validator = { - address, - commission: tmp.commission, - activatedStake: tmp.activatedStake, - name: '', - description: '', - website: '', - index: 100 - }; - validator.index = - typeof validators.data[validator.address].index !== 'undefined' - ? validators.data[validator.address].index - : 0; - validator.name = validators.data[validator.address].name; - validator.description = validators.data[validator.address].description; - validator.website = validators.data[validator.address].website; - - CACHE_VOTES.data.push(validator); - } - CACHE_VOTES.data.sort((a, b) => { - if (a.index === b.index) { - const diff = a.commission - b.commission; - if (diff <= 0.1 && diff >= -0.1) { - return b.activatedStake - a.activatedStake; - } - return diff; - } else { - return b.index - a.index; - } - }); - CACHE_VOTES.time = now; - } catch (e) { - if (config.debug.cryptoErrors) { - console.log('SolStakeUtils.getVoteAddresses error ' + e.message); - } - AirDAOCryptoLog.log('SolStakeUtils.getVoteAddresses error ' + e.message); - } - return CACHE_VOTES.data; - }, - - checkAccountStaked(address: string, subaddress: string): boolean { - return typeof CACHE_STAKED[address].all[subaddress] !== 'undefined'; - }, - - setAccountStaked(address: string, subaddress: string) { - CACHE_STAKED[address].all[subaddress] = true; - }, - - // https://prod-api.solana.surf/v1/account/3siLSmroYvPHPZCrK5VYR3gmFhQFWefVGGpasXdzSPnn/stake-rewards - async getAccountRewards( - address: string, - stakedAddresses: any[] - ): Promise { - if (typeof CACHE_STAKED[address] === 'undefined') { - CACHE_STAKED[address] = { - all: {}, - active: [], - rewards: [] - }; - } - const askAddresses = [address]; - if (stakedAddresses) { - for (const tmp of stakedAddresses) { - askAddresses.push(tmp.stakeAddress); - } - } - try { - const link = BlocksoftExternalSettings.getStatic('SOL_SERVER'); - const data = { - method: 'getInflationReward', - jsonrpc: '2.0', - params: [ - askAddresses, - { - commitment: 'confirmed' - } - ], - id: '1' - }; - - /*console.log(` - - curl ${link} -X POST -H "Content-Type: application/json" -d '${JSON.stringify(data)}' - - `) - */ - - const res = await AirDAOAxios.post(link, data); - if ( - res.data && - typeof res.data.result !== 'undefined' && - typeof res.data.result[0] !== 'undefined' - ) { - CACHE_STAKED[address].rewards = res.data.result[0]; - if (stakedAddresses) { - let count = 0; - if (typeof CACHE_STAKED[address].rewards !== 'undefined') { - count = 100; - } else { - for (const tmp of res.data.result) { - if ( - tmp && - typeof tmp.amount !== 'undefined' && - tmp.amount * 1 > 0 - ) { - CACHE_STAKED[address].rewards = tmp; - count++; - } - } - } - if (count > 1) { - if (typeof CACHE_STAKED[address].rewards !== 'undefined') { - CACHE_STAKED[address].rewards.amount = 0; - } - for (const tmp of res.data.result) { - if ( - tmp && - typeof tmp.amount !== 'undefined' && - tmp.amount * 1 > 0 - ) { - CACHE_STAKED[address].rewards.amount += tmp.amount * 1; - } - } - } - } - } - } catch (e) { - if (config.debug.cryptoErrors) { - console.log( - 'SolStakeUtils.getAccountRewards ' + address + ' error ' + e.message - ); - } - AirDAOCryptoLog.log( - 'SolStakeUtils.getAccountRewards ' + address + ' error ' + e.message - ); - } - // {"amount": 96096, "apr": 7.044036109546499, "effectiveSlot": 99360012, "epoch": 229, "percentChange": 0.05205832165890872, "postBalance": 184689062, "timestamp": 1633153114}, - return CACHE_STAKED[address].rewards; - }, - - // https://docs.solana.com/developing/clients/jsonrpc-api#getprogramaccounts - async getAccountStaked(address: string, isForce = false): Promise { - let accountInfo: any[] = []; - if (typeof CACHE_STAKED[address] === 'undefined' || isForce) { - CACHE_STAKED[address] = { - all: {}, - active: [], - rewards: [] - }; - } - try { - const apiPath = BlocksoftExternalSettings.getStatic('SOL_SERVER'); - const currentEpoch = await SolUtils.getEpoch(); - - const checkData = { - jsonrpc: '2.0', - id: 1, - method: 'getProgramAccounts', - params: [ - 'Stake11111111111111111111111111111111111111', - { - encoding: 'jsonParsed', - filters: [ - { - memcmp: { - offset: 0xc, - bytes: address - } - } - ] - } - ] - }; - - /* - console.log(` - - curl ${apiPath} -X POST -H "Content-Type: application/json" -d '${JSON.stringify(checkData)}' - - `) - */ - let res; - accountInfo = []; - try { - res = await AirDAOAxios._request(apiPath, 'POST', checkData); - - for (const tmp of res.data.result) { - const parsed = tmp.account.data.parsed; - const item = { - amount: tmp.account.lamports, - stakeAddress: tmp.pubkey, - reserved: 0, - active: true, - status: '' - }; - if (typeof parsed.info !== 'undefined') { - if (typeof parsed.info.meta !== 'undefined') { - if (typeof parsed.info.meta.rentExemptReserve !== 'undefined') { - item.reserved = parsed.info.meta.rentExemptReserve; - } - } - const deactivationEpoch = - parsed.info.stake.delegation.deactivationEpoch || 0; - const activationEpoch = - parsed.info.stake.delegation.activationEpoch || 0; - if (currentEpoch && currentEpoch * 1 >= deactivationEpoch * 1) { - item.order = 1; - item.active = false; - item.status = 'inactive'; - } else if (currentEpoch && currentEpoch === activationEpoch) { - item.order = 3; - item.status = 'activating'; - } else { - item.order = 2; - item.status = 'staked'; - } - } - item.diff = BlocksoftUtils.diff( - item.amount, - item.reserved - ).toString(); - accountInfo.push(item); - CACHE_STAKED[address].all[item.stakeAddress] = true; - } - } catch (e: any) { - AirDAOCryptoLog.log( - 'SolStakeUtils getAccountStaked request ' + - apiPath + - ' error ' + - e.message - ); - - const apiPath2 = - 'https://prod-api.solana.surf/v1/account/' + - address + - '/stakes?limit=10&offset=0'; - const res2 = await AirDAOAxios.get(apiPath2); - - for (const tmp of res2.data.data) { - const item = { - amount: tmp.lamports, - stakeAddress: tmp.pubkey.address, - reserved: 0, - active: true, - status: '' - }; - - if (typeof tmp.data !== 'undefined') { - if (typeof tmp.data.meta !== 'undefined') { - if (typeof tmp.data.meta.rent_exempt_reserve !== 'undefined') { - item.reserved = tmp.data.meta.rent_exempt_reserve; - } - } - const deactivationEpoch = - tmp.data.stake.delegation.deactivation_epoch || 0; - const activationEpoch = - tmp.data.stake.delegation.activation_epoch || 0; - if (currentEpoch && currentEpoch >= deactivationEpoch * 1) { - item.order = 1; - item.active = false; - item.status = 'inactive'; - } else if (currentEpoch && currentEpoch === activationEpoch) { - item.order = 3; - item.status = 'activating'; - } else { - item.order = 2; - item.status = 'staked'; - } - } - item.diff = BlocksoftUtils.diff( - item.amount, - item.reserved - ).toString(); - accountInfo.push(item); - CACHE_STAKED[address].all[item.stakeAddress] = true; - } - } - - accountInfo.sort((a, b) => { - if (b.order === a.order) { - return BlocksoftUtils.diff(b.diff, a.diff) * 1; - } else { - return b.order - a.order; - } - }); - CACHE_STAKED[address].active = accountInfo; - } catch (e: any) { - if (config.debug.cryptoErrors) { - console.log( - 'SolStakeUtils.getAccountStaked ' + address + ' error ' + e.message - ); - } - AirDAOCryptoLog.log( - 'SolStakeUtils.getAccountStaked ' + address + ' error ' + e.message - ); - return CACHE_STAKED[address].active; - } - return accountInfo; - } -}; diff --git a/crypto/blockchains/sol/ext/SolUtils.ts b/crypto/blockchains/sol/ext/SolUtils.ts deleted file mode 100644 index 1b2f3988c..000000000 --- a/crypto/blockchains/sol/ext/SolUtils.ts +++ /dev/null @@ -1,260 +0,0 @@ -/** - * @version 0.52 - */ -import AirDAOAxios from '@crypto/common/AirDAOAxios'; -import BlocksoftExternalSettings from '@crypto/common/AirDAOExternalSettings'; - -import { PublicKey } from '@solana/web3.js/src'; -import { Account } from '@solana/web3.js/src/account'; - -import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; -// @ts-ignore -import settingsActions from '@app/appstores/Stores/Settings/SettingsActions'; -import config from '@constants/config'; -import { AxiosResponse } from 'axios'; - -const TOKEN_PROGRAM_ID = 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'; -const ASSOCIATED_TOKEN_PROGRAM_ID = - 'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL'; -const OWNER_VALIDATION_PROGRAM_ID = - '4MNPdKu9wFMvEeZBMt3Eipfs5ovVWTJb31pEXDJAAxX5'; - -const CACHE_VALID_TIME = 12000000; // 200 minutes - -const CACHE_EPOCH = { - ts: 0, - value: 240 -}; - -export default { - getTokenProgramID(): string { - return TOKEN_PROGRAM_ID; - }, - - getOwnerValidationProgramId(): string { - return OWNER_VALIDATION_PROGRAM_ID; - }, - - getAssociatedTokenProgramId(): string { - return ASSOCIATED_TOKEN_PROGRAM_ID; - }, - - async findAssociatedTokenAddress( - walletAddress: string, - tokenMintAddress: string - ): Promise { - try { - const seeds = [ - new PublicKey(walletAddress).toBuffer(), - new PublicKey(TOKEN_PROGRAM_ID).toBuffer(), - new PublicKey(tokenMintAddress).toBuffer() - ]; - const res = await PublicKey.findProgramAddress( - seeds, - new PublicKey(ASSOCIATED_TOKEN_PROGRAM_ID) - ); - return res[0].toBase58(); - } catch (e: any) { - if (config.debug.cryptoErrors) { - console.log('SolUtils.findAssociatedTokenAddress ' + e.message); - } - throw new Error('SYSTEM_ERROR'); - } - }, - - async getAccountInfo(address: string): Promise { - let accountInfo = false; - try { - const apiPath = BlocksoftExternalSettings.getStatic('SOL_SERVER'); - const checkData = { - jsonrpc: '2.0', - id: 1, - method: 'getAccountInfo', - params: [ - address, - { - encoding: 'jsonParsed' - } - ] - }; - // @ts-ignore - const res: AxiosResponse = await AirDAOAxios._request( - apiPath, - 'POST', - checkData - ); - accountInfo = res.data.result.value; - } catch (e: any) { - if (config.debug.cryptoErrors) { - console.log( - 'SolUtils.getAccountInfo ' + address + ' error ' + e.message - ); - } - AirDAOCryptoLog.log( - 'SolUtils.getAccountInfo ' + address + ' error ' + e.message - ); - } - return accountInfo; - }, - - isAddressValid(value: string): boolean { - // tslint:disable-next-line:no-unused-expression - new PublicKey(value); - return true; - }, - - /** - * https://docs.solana.com/developing/clients/jsonrpc-api#sendtransaction - * @param raw - * @returns {Promise} - */ - async sendTransaction(raw: string): Promise { - const sendData = { - jsonrpc: '2.0', - id: 1, - method: 'sendTransaction', - params: [ - raw, - { - encoding: 'base64' - } - ] - }; - const apiPath = BlocksoftExternalSettings.getStatic('SOL_SERVER'); - const apiPath2 = BlocksoftExternalSettings.getStatic('SOL_SERVER_2'); - let try2 = false; - let sendRes: any = null; // Initialize with 'null' - try { - sendRes = await AirDAOAxios._request(apiPath, 'POST', sendData); - if (!sendRes || typeof sendRes.data === 'undefined') { - if (apiPath2) { - try2 = true; - } else { - throw new Error('SERVER_RESPONSE_BAD_INTERNET'); - } - } - if ( - typeof sendRes.data.error !== 'undefined' && - typeof sendRes.data.error.message !== 'undefined' - ) { - if (sendRes.data.error.message === 'Node is unhealthy') { - try2 = true; - } else { - throw new Error(sendRes.data.error.message); - } - } - } catch (e) { - try2 = true; - } - - if (try2 && apiPath2 && apiPath2 !== apiPath) { - let sendRes2: any = null; // Initialize with 'null' - try { - sendRes2 = await AirDAOAxios._request(apiPath2, 'POST', sendData); - if (!sendRes2 || typeof sendRes2.data === 'undefined') { - throw new Error('SERVER_RESPONSE_BAD_INTERNET'); - } - if ( - typeof sendRes2.data.error !== 'undefined' && - typeof sendRes2.data.error.message !== 'undefined' - ) { - throw new Error(sendRes2.data.error.message); - } - return sendRes2.data.result; - } catch (e) { - await AirDAOCryptoLog.log(e); - } - } - - return sendRes?.data?.result || ''; - }, - - /** - * @returns {Promise<{blockhash: string, feeCalculator: {lamportsPerSignature: number}}>} - */ - async getBlockData(): Promise<{ - blockhash: string; - feeCalculator: { lamportsPerSignature: number }; - }> { - const getRecentBlockhashData = { - jsonrpc: '2.0', - id: 1, - method: 'getRecentBlockhash' - }; - const apiPath = BlocksoftExternalSettings.getStatic('SOL_SERVER'); - const getRecentBlockhashRes = await AirDAOAxios._request( - apiPath, - 'POST', - getRecentBlockhashData - ); - // @ts-ignore - return getRecentBlockhashRes.data.result.value; - }, - - async getEpoch(): Promise { - const now = new Date().getTime(); - if (CACHE_EPOCH.ts > 0) { - if (now - CACHE_EPOCH.ts < CACHE_VALID_TIME) { - return CACHE_EPOCH.value; - } - } else { - const tmp = settingsActions.getSettings('SOL_epoch'); - if (tmp * 1 > CACHE_EPOCH.value) { - CACHE_EPOCH.value = tmp * 1; - } - } - const apiPath = BlocksoftExternalSettings.getStatic('SOL_SERVER'); - const getEpoch = { jsonrpc: '2.0', id: 1, method: 'getEpochInfo' }; - try { - const resEpoch = await AirDAOAxios._request(apiPath, 'POST', getEpoch); - // @ts-ignore - const tmp = resEpoch.data.result.epoch * 1; - if (tmp > 0 && tmp !== CACHE_EPOCH.value) { - CACHE_EPOCH.value = tmp; - settingsActions.setSettings('SOL_epoch', tmp); - } - CACHE_EPOCH.ts = now; - } catch (e: any) { - AirDAOCryptoLog.log('SolUtils.getEpoch error ' + e.message); - // nothing - } - return CACHE_EPOCH.value; - }, - - async signTransaction( - transaction: any, - walletPrivKey: string, - walletPubKey: string - ): Promise { - let account: Account | boolean = false; - try { - account = new Account(Buffer.from(walletPrivKey, 'hex')); - } catch (e: any) { - e.message += ' while create account'; - throw e; - } - - try { - const data = await this.getBlockData(); - transaction.recentBlockhash = data.blockhash; - } catch (e: any) { - e.message += ' while getBlockData'; - throw e; - } - try { - transaction.setSigners(new PublicKey(walletPubKey)); - } catch (e: any) { - e.message += ' while setSigners'; - throw e; - } - - try { - transaction.partialSign(account as Account); - } catch (e: any) { - e.message += ' while transaction.partialSign with account'; - throw e; - } - - return transaction; - } -}; diff --git a/crypto/blockchains/sol/ext/validators.ts b/crypto/blockchains/sol/ext/validators.ts deleted file mode 100644 index acfc9f4e1..000000000 --- a/crypto/blockchains/sol/ext/validators.ts +++ /dev/null @@ -1,266 +0,0 @@ -module.exports = [ - { - id: 'GA2t11gJcmuZ4y7pShTzgYDkxVaJaVQJqkVUqojhPPsT', - index: 1, - name: 'SolBrothers', - description: '', - website: '' - }, - { - id: '9QU2QSxhb24FUX3Tu2FpczXjpK3VYrvRudywSZaM29mF', - index: 2, - name: 'Everstake', - description: - 'Everstake is a team of seasoned developers, financial experts and blockchain enthusiasts. We run secure and reliable nodes for PoS protocols', - website: 'https://everstake.one' - }, - { - id: '2het6nBRLq9LLZER8fqUEk7j5pbLxq2mVGqSse2nS3tf', - name: "Maggie's Crypto Farm", - description: 'Making the blocks, all day and all night.', - website: 'https://mcf.rocks/' - }, - { - id: '3r5ZXC1yFqMmk8VwDdUJbEdPmZ8KZvEkzd5ThEYRetTk', - name: 'Vnode', - description: 'We are the staking service provider for blockchain projects.', - website: '' - }, - { - id: '49DJjUX3cwFvaZD5rCAwubiz7qdRWDez9xmB381XdHru', - name: 'Staker Space', - description: 'Secure Validating Services', - website: 'https://staker.space' - }, - { - id: '4PsiLMyoUQ7QRn1FFiFCvej4hsUTFzfvJnyN4bj1tmSN', - name: 'Stakin', - description: 'Your Trusted Crypto Rewards', - website: 'https://stakin.com/' - }, - { - id: '51JBzSTU5rAM8gLAVQKgp4WoZerQcSqWC7BitBzgUNAm', - name: 'Staked', - description: - 'Staked operates highly reliable and secure staking infrastructure for 20+ PoS protocols on behalf of the leading investors in the market.', - website: 'https://staked.us' - }, - { - id: '6UDU4Z9TTbYy8gcRKBd7RX3Lm2qMsSR4PMuzoyYPzLma', - name: 'dcipher', - description: - 'dCipher is a company that has the purpose to combine emerging technologies like Blockchain, IoT and Artificial Intelligence.', - website: 'https://dcipher.io' - }, - { - id: '76nwV8zz8tLz97SBRXH6uwHvgHXtqJDLQfF66jZhQ857', - name: 'Forbole Limited', - description: - 'We are a pioneer in blockchain technology and UX solution. We provide enterprise-level network infrastructure and software development.', - website: 'https://forbole.com' - }, - { - id: '7Hs9z4qsGCbQE9cy2aqgsvWupeZZGiKJgeb1eG4ZKYUH', - name: 'Kytzu', - description: 'Tendermint tech consultant and developer', - website: 'http://kytzu.com/' - }, - { - id: '7PmWxxiTneGteGxEYvzj5pGDVMQ4nuN9DfUypEXmaA8o', - name: 'Syncnode SRL', - description: - 'Syncnode is a software development and cyber security company with global presence and headquarters in Switzerland and Romania.', - website: 'https://wallet.syncnode.ro/' - }, - { - id: '7VGU4ZwR1e1AFekqbqv2gvjeg47e1PwMPm4BfLt6rxNk', - name: 'stake.fish', - description: - 'We are the leading staking service provider for blockchain projects. Join our community to stake and earn rewards. We know staking.', - website: 'https://stake.fish/' - }, - { - id: '9GJmEHGom9eWo4np4L5vC6b6ri1Df2xN8KFoWixvD1Bs', - name: 'BL', - description: '', - website: '' - }, - { - id: '9iEFfC6qCU6DBTWxL84YQdpNmpZ9yBBu4sW62yeiEVKe', - name: 'Izo Data Network', - description: - 'If we could describe the business relationship we have with our clients in one word, it would be TRUST.', - website: 'http://www.izo.ro/' - }, - { - id: '9sWYTuuR4s12Q4SuSfo5CfWaFggQwA6Z8pf8dWowN5rk', - name: 'Ubik Capital', - description: - "Ubik Capital's team priority is to provide a highly resilient and secure validator for the Solana community.", - website: 'https://ubik.capital/' - }, - { - id: '9tedbEYypEKXAMkHcg42rn3fXY1B8hB6cdE3ZTFouXLL', - name: 'Stake Systems', - description: - 'Building infrastructure to support awesome projects running in the blockchain landscape', - website: 'https://stake.systems' - }, - { - id: '9v5gci7uDiaGKRmQ2dn6WJMB94YqFaVFBTiFzBzNhyaw', - name: 'Inotel', - description: '', - website: '' - }, - { - id: 'AGXZemZbyZjz5NBhufcob2pf8AXnr9HaGFUGNCfooWrB', - name: 'RockX', - description: - 'RockX is a professional digital asset platform that aims to bring crypto investing to the mainstream in the smartest way.', - website: 'https://rockx.com/' - }, - { - id: 'ateamaZDqNWDztxnVKZhRsp4ac53KvT1rVKyU5LnL6o', - name: 'Node A-Team', - description: '', - website: 'https://nodeateam.com/' - }, - { - id: 'beefKGBWeSpHzYBHZXwp5So7wdQGX6mu4ZHCsH3uTar', - name: 'Bison Trails', - description: - 'We provide secure and reliable enterprise-grade infrastructure with multi-cloud / region distribution and a 99% node uptime guarantee', - website: 'https://bisontrails.co/' - }, - { - id: 'BH7asDZbKkTmT3UWiNfmMVRgQEEpXoVThGPmQfgWwDhg', - name: '01Node', - description: - '01node | Professional Staking Services for Cosmos, Iris, Terra, Solana, Kava, Polkadot, Skale', - website: 'https://01node.com' - }, - { - id: 'BxFf75Vtzro2Hy3coFHKxFMZo5au8W7J8BmLC3gCMotU', - name: 'Chainode', - description: - 'Chainode Tech is your reliable partner for distributed ledger technology (DLT) validation services and blockchain consultancy.', - website: 'https://chainode.tech/' - }, - { - id: 'CAf8jfgqhia5VNrEF4A7Y9VLD3numMq9DVSceq7cPhNY', - name: 'Chainflow', - description: - "Chainflow's a small, independent, capable & dedicated validator operator, started in 2017 by @cjremus, working to keep stake decentralized.", - website: 'https://chainflow.io/staking' - }, - { - id: 'CcaHc2L43ZWjwCHART3oZoJvHLAe9hzT2DJNUpBzoTN1', - name: 'Figment Networks', - description: '', - website: 'https://figment.network/' - }, - { - id: 'CertusDeBmqN8ZawdkxK5kFGMwBXdudvWHYwtNgNhvLu', - name: 'Certus One', - description: '', - website: 'https://certus.one' - }, - { - id: 'Chorus6Kis8tFHA7AowrPMcRJk3LbApHTYpgSNXzY5KE', - name: 'Chorus One', - description: - 'Secure Solana and shape its future by delegating to Chorus One, a leading provider of validation infrastructure and staking services.', - website: 'https://chorus.one' - }, - { - id: 'CRzMxdyS56N2vkb55X5q155sSdVkjZhiFedWcbscCf7K', - name: 'HashQuark', - description: - 'HashQuark is a new generation Proof-of-Stake pool focused on POS, DPOS and other public chains.', - website: 'https://hashquark.io/' - }, - { - id: 'D3DfFvmLBKkX9JJNEpJRXpM1pYTVPQ5dpPQRc9F49xk4', - name: 'Easy 2 Stake', - description: 'Professional PoS staking services.', - website: 'https://easy2stake.com/' - }, - { - id: 'DQ7D6ZRtKbBSxCcAunEkoTzQhCBKLPdzTjPRRnM6wo1f', - name: 'Stakewolf', - description: '', - website: '' - }, - { - id: 'DumiCKHVqoCQKD8roLApzR5Fit8qGV5fVQsJV9sTZk4a', - name: 'Staking Facilities', - description: - 'Staking Facilities is a Munich-based staking service provider, who operates institutional-grade, custom built validator architecture.', - website: 'https://stakingfacilities.com/' - }, - { - id: 'edu1fZt5i82cFm6ujUoyXLMdujWxZyWYC8fkydWHRNT', - name: 'Solstaking', - description: 'Specialized in Solana Staking', - website: 'https://solstaking.com/' - }, - { - id: 'FKsC411dik9ktS6xPADxs4Fk2SCENvAiuccQHLAPndvk', - name: 'P2P.ORG - P2P Validator', - description: 'Secure Non-Custodial Staking', - website: 'https://p2p.org/' - }, - { - id: 'GB44NXtM7zGm6QnzQjzHZcRKSswkJbox8aJsKiXGbFJr', - name: 'Rustiq Technology', - description: '', - website: '' - }, - { - id: 'GhKEDkvGkf2kceG45ppzqnPD6BPXi1PyW1xGNWJdh5QW', - name: 'Stefan Condurachi', - description: 'I build & design intuitive blockchain tools.', - website: 'http://stefancondurachi.com/' - }, - { - id: 'GMpKrAwQ9oa4sJqEYQezLr8Z2TUAU72tXD4iMyfoJjbh', - name: 'Moonlet', - description: - 'Moonlet operates nodes for next-generation blockchain networks and is committed to support the decentralisation movement forward.', - website: 'https://moonlet.xyz/' - }, - { - id: 'H3GhqPMwvGLdxWg3QJGjXDSkFSJCsFk3Wx9XBTdYZykc', - name: 'DokiaCapital', - description: - 'We operate an enterprise-grade infrastructure that is robust and secure. Downtime is not an option for us. Stake now and manage your reward.', - website: 'https://staking.dokia.cloud/' - }, - { - id: 'LunaFpQkZsZVJL2P2BUqNDJqyVYqrw9buQnjQtMLXdK', - name: 'LunaNova Technologies', - description: - 'Experienced team runs Solana-optimised, monitored hardware in top-tier UK datacentres - uptime guaranteed - 10% of our profits to charity', - website: 'https://lunanova.tech/' - }, - { - id: 'SFund7s2YPS7iCu7W2TobbuQEpVEAv9ZU7zHKiN1Gow', - name: 'Staking Fund', - description: '', - website: 'https://staking.fund' - }, - { - id: 'D3DfFvmLBKkX9JJNEpJRXpM1pYTVPQ5dpPQRc9F49xk4', - name: 'Easy 2 Stake', - description: - 'Easy.Stake.Trust. As easy and as simple as you would click next. Complete transparency and trust with a secure and stable validator.', - website: 'https://easy2stake.com' - }, - { - id: 'EFEKcHrUBsRoQkuTSJAQNZnj1u8h9oE4LoCYerednc3F', - name: 'Genesis Lab', - description: - 'Genesis Lab is a validation nodes operator in PoS networks and blockchain-focused software development company', - website: 'https://genesislab.net' - } -]; diff --git a/crypto/blockchains/sol/stores/SolTmpDS.ts b/crypto/blockchains/sol/stores/SolTmpDS.ts deleted file mode 100644 index acd89c4ba..000000000 --- a/crypto/blockchains/sol/stores/SolTmpDS.ts +++ /dev/null @@ -1,59 +0,0 @@ -/** - * @version 0.52 - */ -// @ts-ignore -import Database from '@app/appstores/DataSource/Database'; - -const tableName = 'transactions_scanners_tmp'; - -class SolTmpDS { - /** - * @type {string} - * @private - */ - private _currencyCode = 'SOL'; - - async getCache(address: string): Promise<{ [key: string]: any }> { - const sql = ` - SELECT tmp_key, tmp_val - FROM ${tableName} - WHERE currency_code='${this._currencyCode}' - AND address='${address}' - `; - const res = await Database.query(sql); - const tmp: { [key: string]: any } = {}; - if (res.array) { - for (const row of res.array) { - tmp[row.tmp_key] = row.tmp_val; - } - } - return tmp; - } - - async saveCache(address: string, key: string, value: any): Promise { - const now = new Date().toISOString(); - const prepared = [ - { - currency_code: this._currencyCode, - address: address, - tmp_key: key, - tmp_val: value, - created_at: now - } - ]; - await Database.setTableName(tableName) - .setInsertData({ insertObjs: prepared }) - .insert(); - } - - async updateCache(address: string, key: string, value: any): Promise { - const sql = ` - UPDATE ${tableName} SET tmp_val='${value}' - WHERE address='${address}' AND tmp_key='${key}' - AND currency_code='${this._currencyCode}' - `; - await Database.query(sql); - } -} - -export default new SolTmpDS(); diff --git a/crypto/blockchains/trx/TrxAddressProcessor.ts b/crypto/blockchains/trx/TrxAddressProcessor.ts deleted file mode 100644 index a8d93dad1..000000000 --- a/crypto/blockchains/trx/TrxAddressProcessor.ts +++ /dev/null @@ -1,36 +0,0 @@ -/** - * @version 0.5 - */ -import TronUtils from './ext/TronUtils'; - -interface AddressData { - privateKey: string; - address: string; - addedData: { - addressHex: string; - pubKey: string; - }; -} - -export default class TrxAddressProcessor { - async setBasicRoot(root: any) {} - - /** - * @param {string|Buffer} privateKey - * @param {*} data - * @returns {Promise} - */ - async getAddress( - privateKey: string | Buffer, - data: any = {} - ): Promise { - const pubKey: string = TronUtils.privHexToPubHex(privateKey); - const addressHex: string = TronUtils.pubHexToAddressHex(pubKey); - const address: string = TronUtils.addressHexToStr(addressHex); - return { - address, - privateKey: privateKey.toString('hex'), - addedData: { addressHex, pubKey } - }; - } -} diff --git a/crypto/blockchains/trx/TrxScannerProcessor.ts b/crypto/blockchains/trx/TrxScannerProcessor.ts deleted file mode 100644 index 13d184ff6..000000000 --- a/crypto/blockchains/trx/TrxScannerProcessor.ts +++ /dev/null @@ -1,529 +0,0 @@ -/** - * @version 0.5 - * https://github.com/tronscan/tronscan-frontend/wiki/TRONSCAN-API - */ -import TronUtils from './ext/TronUtils'; -import TrxTronscanProvider from './basic/TrxTronscanProvider'; -import TrxTrongridProvider from './basic/TrxTrongridProvider'; -import TrxTransactionsProvider from './basic/TrxTransactionsProvider'; -import TrxTransactionsTrc20Provider from './basic/TrxTransactionsTrc20Provider'; -import AirDAOCryptoLog from '../../common/AirDAOCryptoLog'; -import AirDAOAxios from '@crypto/common/AirDAOAxios'; -import BlocksoftUtils from '@crypto/common/AirDAOUtils'; -import BlocksoftExternalSettings from '@crypto/common/AirDAOExternalSettings'; -import TronStakeUtils from '@crypto/blockchains/trx/ext/TronStakeUtils'; -import config from '@constants/config'; -import { Database } from '@database'; -import { DatabaseTable } from '@appTypes'; - -let CACHE_PENDING_TXS = false; - -interface TronScannerSettings { - tokenName?: string; - currencyCode: string; -} - -interface TronBalanceResult { - balance: string; - frozen: string; - frozenEnergy: string; - balanceStaked: string; - unconfirmed: number; - provider: string; -} - -export default class TrxScannerProcessor { - private _settings: TronScannerSettings; - private _tokenName: string; - private _tronscanProvider: TrxTronscanProvider; - private _trongridProvider: TrxTrongridProvider; - private _transactionsProvider: TrxTransactionsProvider; - private _transactionsTrc20Provider: TrxTransactionsTrc20Provider; - - constructor(settings: TronScannerSettings) { - this._settings = settings; - this._tokenName = '_'; - if (typeof settings.tokenName !== 'undefined') { - this._tokenName = settings.tokenName; - } - this._tronscanProvider = new TrxTronscanProvider(); - this._trongridProvider = new TrxTrongridProvider(); - this._transactionsProvider = new TrxTransactionsProvider(); - this._transactionsTrc20Provider = new TrxTransactionsTrc20Provider(); - } - - async isMultisigBlockchain(address: string): Promise { - address = address.trim(); - let addressHex = address; - if (address.substr(0, 1) === 'T') { - addressHex = await TronUtils.addressToHex(address); - } - return this._trongridProvider.isMultisigTrongrid(addressHex); - } - - async getBalanceBlockchain( - address: string, - jsonData: any, - walletHash: string, - source: string - ): Promise { - address = address.trim(); - AirDAOCryptoLog.log( - this._tokenName + - ' TrxScannerProcessor getBalanceBlockchain address ' + - address + - ' from ' + - source - ); - let addressHex = address; - if (address.substr(0, 1) === 'T') { - addressHex = TronUtils.addressToHex(address); - } else { - address = TronUtils.addressHexToStr(addressHex); - } - const useTronscan = - BlocksoftExternalSettings.getStatic('TRX_USE_TRONSCAN') * 1 > 0; - let result: TronBalanceResult | boolean = false; - let subresult: boolean | TronBalanceResult = false; - if (useTronscan) { - result = await this._tronscanProvider.get( - address, - this._tokenName, - source === 'AccountScreen' - ); - AirDAOCryptoLog.log( - this._tokenName + - ' TrxScannerProcessor getBalanceBlockchain address ' + - address + - ' result tronScan ' + - JSON.stringify(result) + - ' from ' + - source - ); - } - - if (!result) { - if (this._tokenName !== '_' && this._tokenName.substr(0, 1) === 'T') { - // https://developers.tron.network/docs/trc20-contract-interaction#balanceof - try { - const sendLink = BlocksoftExternalSettings.getStatic('TRX_SEND_LINK'); - const params = { - contract_address: TronUtils.addressToHex(this._tokenName), - function_selector: 'balanceOf(address)', - parameter: '0000000000000000000000' + addressHex, - owner_address: addressHex - }; - const tmp = await AirDAOAxios.post( - sendLink + '/wallet/triggerconstantcontract', - params - ); - if ( - typeof tmp.data !== 'undefined' && - typeof tmp.data.constant_result !== 'undefined' - ) { - await AirDAOCryptoLog.log( - this._tokenName + - ' TrxScannerProcessor getBalanceBlockchain address ' + - address + - ' result tronwallet ' + - JSON.stringify(tmp.data) + - ' from ' + - source - ); - return { - // TODO - balanceStaked: '', - frozen: '', - frozenEnergy: '', - balance: BlocksoftUtils.hexToDecimal( - '0x' + tmp.data.constant_result - ), - unconfirmed: 0, - provider: 'tronwallet-raw-call' - }; - } - } catch (e: any) { - AirDAOCryptoLog.log( - this._tokenName + - ' TrxScannerProcessor getBalanceBlockchain address ' + - address + - ' error tronwallet ' + - e.message - ); - } - result = await this._tronscanProvider.get(address, this._tokenName); - } else { - result = await this._trongridProvider.get( - addressHex, - this._tokenName, - source === 'AccountScreen' - ); - } - AirDAOCryptoLog.log( - this._tokenName + - ' TrxScannerProcessor getBalanceBlockchain address ' + - address + - ' result tronGrid ' + - JSON.stringify(result) + - ' from ' + - source - ); - } - - if (!result && this._tokenName !== '_') { - subresult = await this._tronscanProvider.get( - address, - '_', - source === 'AccountScreen' - ); - AirDAOCryptoLog.log( - this._tokenName + - ' TrxScannerProcessor getBalanceBlockchain address ' + - address + - ' result tronScan2 ' + - JSON.stringify(result) + - ' from ' + - source - ); - - if (subresult) { - AirDAOCryptoLog.log( - this._tokenName + - ' TrxScannerProcessor getBalanceBlockchain address ' + - address + - ' subresult tronScan ' + - JSON.stringify(subresult) + - ' from ' + - source - ); - return { - frozen: '', - frozenEnergy: '', - balance: '0', - unconfirmed: 0, - balanceStaked: '0', - balanceAvailable: '0', - provider: 'tronscan-ok-but-no-token' - }; - } - } - - (result as TronBalanceResult).balanceStaked = - typeof result.frozen !== 'undefined' - ? (result.frozen as any) * 1 + (result.frozenEnergy as any) * 1 - : '0'; - (result as TronBalanceResult).balanceAvailable = (result as any).balance; - if ((result as any).balanceStaked * 1 > 0) { - (result as any).balance = - (result as any).balance * 1 + (result as any).balanceStaked * 1; - } - return result as TronBalanceResult; - } - - async getResourcesBlockchain(address: string): Promise { - address = address.trim(); - AirDAOCryptoLog.log( - this._tokenName + - ' TrxScannerProcessor getResourcesBlockchain address ' + - address - ); - let addressHex = address; - if (address.substr(0, 1) === 'T') { - addressHex = await TronUtils.addressToHex(address); - } - const result = await this._trongridProvider.getResources( - addressHex, - this._tokenName - ); - return result; - } - - async getTransactionsBlockchain( - scanData: { account: { address: string } }, - source: string - ): Promise { - let result; - let lastBlock = false; - if (this._tokenName[0] === 'T') { - this._transactionsTrc20Provider.setLink(this._tokenName); - result = await this._transactionsTrc20Provider.get( - scanData, - this._tokenName - ); - lastBlock = this._transactionsTrc20Provider._lastBlock; - } else { - result = await this._transactionsProvider.get(scanData, this._tokenName); - lastBlock = this._transactionsProvider._lastBlock; - } - await this.getTransactionsPendingBlockchain(scanData, source, lastBlock); - return result; - } - - async resetTransactionsPendingBlockchain( - scanData: any, - source: string, - lastBlock = false - ): Promise { - CACHE_PENDING_TXS = scanData.resetTime || 0; - if ( - typeof scanData.specialActionNeeded !== 'undefined' && - scanData.specialActionNeeded && - typeof scanData.account.address !== 'undefined' - ) { - await Database.unsafeRawQuery( - DatabaseTable.Transactions, - ` - UPDATE ${DatabaseTable.Transactions} SET special_action_needed='' - WHERE special_action_needed='${scanData.specialActionNeeded}' - AND address_from_basic='${scanData.account.address}' - ` - ); - } - return false; - } - - async getTransactionsPendingBlockchain( - scanData: any, - source: string, - lastBlock = false - ): Promise { - if ( - CACHE_PENDING_TXS > 0 && - CACHE_PENDING_TXS - new Date().getTime() < 60000 - ) { - return false; - } - // id, transaction_hash, block_number, block_confirmations, transaction_status, - const sql = `SELECT t.id, - t.wallet_hash AS walletHash, - t.transaction_hash AS transactionHash, - t.transactions_scan_log AS transactionsScanLog, - t.address_from_basic AS addressFromBasic, - t.special_action_needed AS specialActionNeeded, - a.derivation_path AS derivationPath - FROM transactions AS t - LEFT JOIN account AS a ON a.address = t.address_from_basic - WHERE - (t.currency_code='${this._settings.currencyCode}' OR t.currency_code LIKE 'TRX%') - AND t.transaction_of_trustee_wallet=1 - AND (t.block_number IS NULL OR t.block_number<20 OR t.special_action_needed='vote' OR t.special_action_needed='vote_after_unfreeze') - - ORDER BY created_at DESC - LIMIT 10 - `; - // TODO check left join - const res = await Database.unsafeRawQuery(DatabaseTable.Transactions, sql); - if ( - !res || - typeof res.array === 'undefined' || - !res.array || - res.array.length === 0 - ) { - CACHE_PENDING_TXS = new Date().getTime(); - return false; - } - - const sendLink = BlocksoftExternalSettings.getStatic('TRX_SEND_LINK'); - let needUpdateBalance = -1; - if (!lastBlock) { - needUpdateBalance = 0; - try { - const link2 = sendLink + '/wallet/getnowblock'; - const block = await AirDAOAxios.get(link2); - if ( - typeof block !== 'undefined' && - block && - typeof block.data !== 'undefined' - ) { - lastBlock = block.data.block_header.raw_data.number; - } - } catch (e1) { - if (config.debug.cryptoErrors) { - console.log( - this._settings.currencyCode + - ' TrxScannerProcessor.getTransactionsPendingBlockchain lastBlock', - e1 - ); - } - } - } - - const unique: { [key: string]: any } = {}; - for (const row of res.array) { - const linkRecheck = sendLink + '/wallet/gettransactioninfobyid'; - try { - const recheck = await AirDAOAxios.post(linkRecheck, { - value: row.transactionHash - }); - if (typeof recheck.data !== undefined) { - const isSuccess = await this._unifyFromReceipt( - recheck.data, - row, - lastBlock - ); - if (isSuccess && needUpdateBalance === 0) { - needUpdateBalance = 1; - } - if (isSuccess && row.specialActionNeeded && row.addressFromBasic) { - row.confirmations = lastBlock - recheck.data.blockNumber; - if (typeof unique[row.addressFromBasic] === undefined) { - unique[row.addressFromBasic] = row; - } else { - if ( - unique[row.addressFromBasic].confirmations > row.confirmations - ) { - unique[row.addressFromBasic].confirmations = row.confirmations; - } - if ( - unique[row.addressFromBasic].specialActionNeeded === - 'vote_after_unfreeze' - ) { - unique[row.addressFromBasic].specialActionNeeded = - row.specialActionNeeded; - } - } - } - } - } catch (e1) { - if (config.debug.cryptoErrors) { - console.log( - this._settings.currencyCode + - ' TrxScannerProcessor.getTransactionsPendingBlockchain recheck', - e1 - ); - } - } - } - - if (unique) { - for (const address in unique) { - const { - walletHash, - derivationPath, - confirmations, - specialActionNeeded - } = unique[address]; - if (confirmations < 20) { - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' TrxScannerProcessor.getTransactionsPendingBlockchain vote all skipped by ' + - confirmations + - ' for ' + - address - ); - continue; - } - - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' TrxScannerProcessor.getTransactionsPendingBlockchain vote all inited for ' + - address + - ' action ' + - specialActionNeeded - ); - try { - if ( - await TronStakeUtils.sendVoteAll( - address, - derivationPath, - walletHash, - specialActionNeeded - ) - ) { - await Database.unsafeRawQuery( - DatabaseTable.Transactions, - ` - UPDATE transactions SET special_action_needed='' WHERE special_action_needed='vote' OR special_action_needed='vote_after_unfreeze' - AND address_from_basic='${address}' - ` - ); - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' TrxScannerProcessor.getTransactionsPendingBlockchain vote all finished for ' + - address - ); - } - } catch (e: any) { - if (config.debug.cryptoErrors) { - console.log( - this._settings.currencyCode + - ' TrxScannerProcessor.getTransactionsPendingBlockchain vote all error for ' + - address + - ' ' + - e.message - ); - } - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' TrxScannerProcessor.getTransactionsPendingBlockchain vote all error for ' + - address + - ' ' + - e.message - ); - } - } - } - - return needUpdateBalance > 0; - } - - async _unifyFromReceipt( - transaction: any, - row: any, - lastBlock: number - ): Promise { - /** - * {"id":"fb7580e4bb6161e0812beb05cf4a1b6463ba55e33def5dd7f3f5c1561c91a49e","blockNumber":29134019,"blockTimeStamp":1617823467000, - * "receipt":{'origin_energy_usage":4783,"energy_usage_total":4783,"net_usage":345,"result":"OUT_OF_ENERGY'}, - * "result":"FAILED" - */ - if ( - typeof transaction.blockNumber === 'undefined' || - transaction.blockNumber * 1 <= 1 - ) - return false; - - let transactionStatus = 'success'; - if ( - typeof transaction.result !== undefined && - transaction.result === 'FAILED' - ) { - transactionStatus = 'fail'; - if ( - typeof transaction.receipt !== undefined && - typeof transaction.receipt.result !== undefined - ) { - if (transaction.receipt.result === 'OUT_OF_ENERGY') { - transactionStatus = 'out_of_energy'; - } - } - } - let formattedTime; - try { - formattedTime = BlocksoftUtils.toDate(transaction.blockTimeStamp / 1000); - } catch (e) { - e.message += - ' timestamp error transaction2 data ' + JSON.stringify(transaction); - throw e; - } - - // TODO implement this - // await transactionDS.saveTransaction( - // { - // blockNumber: transaction.blockNumber, - // blockTime: formattedTime, - // blockConfirmations: lastBlock - transaction.blockNumber, - // transactionStatus, - // transactionsScanLog: - // new Date().toISOString() + - // ' RECEIPT RECHECK ' + - // JSON.stringify(transaction) + - // ' ' + - // row.transactionsScanLog - // }, - // row.id, - // 'receipt' - // ); - return transactionStatus === 'success'; - } -} diff --git a/crypto/blockchains/trx/TrxTokenProcessor.ts b/crypto/blockchains/trx/TrxTokenProcessor.ts deleted file mode 100644 index fe2c2ce2c..000000000 --- a/crypto/blockchains/trx/TrxTokenProcessor.ts +++ /dev/null @@ -1,91 +0,0 @@ -/** - * @version 0.5 - * https://apilist.tronscan.org/api/contract?contract=TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t - * [ { address: 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t', balance: 7208332710, verify_status: 0, balanceInUsd: 0, trxCount: 758742, date_created: 1555400628000, creator: [Object] } ] } - */ -import AirDAOAxios from '../../common/AirDAOAxios'; - -interface TokenDetails { - currencyCodePrefix: string; - currencyCode: string; - currencyName: string; - tokenType: string; - tokenAddress: string; - tokenDecimals: number; - icon: string; - description: string; - provider: string; -} - -interface TronscanResponse { - data: { - symbol: string; - name: string; - contract_address: string; - decimals: number; - icon_url: string; - token_desc: string; - abbr: string; - tokenID: string; - precision: number; - imgUrl: string; - description: string; - }[]; -} - -export default class TrxTokenProcessor { - private _tokenTronscanPath20: string; - private _tokenTronscanPath10: string; - - constructor() { - this._tokenTronscanPath20 = - 'https://apilist.tronscan.org/api/token_trc20?contract='; - this._tokenTronscanPath10 = 'https://apilist.tronscan.org/api/token?id='; - } - - /** - * https://apilist.tronscan.org/api/token_trc20?contract=TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t - * @param {string} tokenAddress - * @returns {Promise} - */ - async getTokenDetails(tokenAddress: string): Promise { - if (tokenAddress[0] === 'T') { - const res = await AirDAOAxios.get( - this._tokenTronscanPath20 + tokenAddress - ); - if (typeof res.data[0] !== 'undefined') { - const tmp = res.data[0]; - return { - currencyCodePrefix: 'CUSTOM_TRX_', - currencyCode: tmp.symbol, - currencyName: tmp.name, - tokenType: 'TRX', // 'TRX' - tokenAddress: tmp.contract_address, - tokenDecimals: tmp.decimals, - icon: tmp.icon_url, - description: tmp.token_desc, - provider: 'tronscan20' - }; - } - } else { - const res = await AirDAOAxios.get( - this._tokenTronscanPath10 + tokenAddress - ); - if (typeof res.data[0] !== 'undefined') { - const tmp = res.data[0]; - return { - currencyCodePrefix: 'CUSTOM_TRX_', - currencyCode: tmp.abbr, - currencyName: tmp.name, - tokenType: 'TRX', // 'TRX' - tokenAddress: tmp.tokenID, - tokenDecimals: tmp.precision, - icon: tmp.imgUrl, - description: tmp.description, - provider: 'tronscan10' - }; - } - } - return false; - } -} diff --git a/crypto/blockchains/trx/TrxTransferProcessor.ts b/crypto/blockchains/trx/TrxTransferProcessor.ts deleted file mode 100644 index 7ee75ffa0..000000000 --- a/crypto/blockchains/trx/TrxTransferProcessor.ts +++ /dev/null @@ -1,1014 +0,0 @@ -/** - * @version 0.20 - */ -import AirDAOAxios from '../../common/AirDAOAxios'; -import AirDAOCryptoLog from '../../common/AirDAOCryptoLog'; -import BlocksoftUtils from '../../common/AirDAOUtils'; - -import TronUtils from './ext/TronUtils'; -import TrxTronscanProvider from './basic/TrxTronscanProvider'; -import TrxTrongridProvider from './basic/TrxTrongridProvider'; -import TrxSendProvider from '@crypto/blockchains/trx/providers/TrxSendProvider'; - -import { strings, sublocale } from '@app/services/i18n'; - -import settingsActions from '@app/appstores/Stores/Settings/SettingsActions'; -import BlocksoftTransactions from '@crypto/actions/BlocksoftTransactions/BlocksoftTransactions'; -import BlocksoftExternalSettings from '@crypto/common/AirDAOExternalSettings'; -import BlocksoftBalances from '@crypto/actions/BlocksoftBalances/BlocksoftBalances'; -import AirDAODispatcher from '../AirDAODispatcher'; -import config from '@constants/config'; -import { AirDAOBlockchainTypes } from '@crypto/blockchains/AirDAOBlockchainTypes'; -import { Database } from '@database'; - -// https://developers.tron.network/docs/parameter-and-return-value-encoding-and-decoding -const ethers = require('ethers'); -const ADDRESS_PREFIX_REGEX = /^(41)/; -const AbiCoder = ethers.utils.AbiCoder; -const PROXY_FEE = 'https://proxy.trustee.deals/trx/countFee'; - -export default class TrxTransferProcessor - implements AirDAOBlockchainTypes.TransferProcessor -{ - private _settings: any; - private _tronscanProvider: TrxTronscanProvider; - private _trongridProvider: TrxTrongridProvider; - private _tokenName: string; - private _isToken20: boolean; - private sendProvider: TrxSendProvider; - - constructor(settings: any) { - this._settings = settings; - this._tronscanProvider = new TrxTronscanProvider(); - this._trongridProvider = new TrxTrongridProvider(); - this._tokenName = '_'; - this._isToken20 = false; - if (typeof settings.tokenName !== 'undefined') { - this._tokenName = settings.tokenName; - if (this._tokenName[0] === 'T') { - this._isToken20 = true; - } - } - this.sendProvider = new TrxSendProvider(this._settings, 'TRX'); - } - - needPrivateForFee(): boolean { - return false; - } - - checkSendAllModal(data: { currencyCode: any }): boolean { - return false; - } - - async checkTransferHasError( - data: AirDAOBlockchainTypes.CheckTransferHasErrorData - ): Promise { - if (!this._isToken20 || (data.amount && data.amount * 1 > 0)) { - return { isOk: true }; - } - /** - * @type {TrxScannerProcessor} - */ - const balanceProvider = AirDAODispatcher.getScannerProcessor( - this._settings.currencyCode - ); - const balanceRaw = await balanceProvider.getBalanceBlockchain( - data.addressTo - ); - if ( - balanceRaw && - typeof balanceRaw.balance !== 'undefined' && - balanceRaw.balance > 0 - ) { - return { isOk: true }; - } - - const balanceProviderBasic = AirDAODispatcher.getScannerProcessor('TRX'); - const balanceRawBasic = await balanceProviderBasic.getBalanceBlockchain( - data.addressTo - ); - if ( - balanceRawBasic && - typeof balanceRawBasic.balance !== 'undefined' && - balanceRawBasic.balance > 0 - ) { - return { isOk: true }; - } - - const transactionsBasic = - await balanceProviderBasic.getTransactionsBlockchain({ - account: { address: data.addressTo } - }); - if (transactionsBasic !== false) { - return { isOk: true }; - } - return { isOk: false, code: 'TRX_20', address: data.addressTo }; - } - - async getFeeRate( - data: AirDAOBlockchainTypes.TransferData, - privateData: AirDAOBlockchainTypes.TransferPrivateData, - additionalData: {} = {} - ): Promise { - const addressHexTo = TronUtils.addressToHex(data.addressTo); - if (TronUtils.addressHexToStr(addressHexTo) !== data.addressTo) { - AirDAOCryptoLog.log( - 'TrxTransferProcessor.getFeeRateOld check address ' + - data.addressTo + - ' hex ' + - addressHexTo + - ' => ' + - TronUtils.addressHexToStr(addressHexTo) - ); - throw new Error( - 'TRX SYSTEM ERROR - Please check address ' + data.addressTo - ); - } - - try { - const link = - PROXY_FEE + - '?from=' + - data.addressFrom + - '&fromHex=' + - TronUtils.addressToHex(data.addressFrom) + - '&to=' + - data.addressTo + - '&toHex=' + - addressHexTo + - '&token=' + - this._tokenName + - '&tokenHex=' + - (this._isToken20 ? TronUtils.addressToHex(this._tokenName) : '') + - '&amount=' + - data.amount + - '&isTransferAll=' + - (data.isTransferAll ? 1 : 0); - let res = false; - try { - res = await AirDAOAxios.get(link); - } catch (e) { - throw new Error('no proxy fee for ' + link); - } - res = res.data; - if (config.debug.cryptoErrors) { - console.log( - this._settings.currencyCode + - ' TrxTransferProcessor.getFeeRate ' + - link + - ' res ', - res - ); - } - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' TrxTransferProcessor.getFeeRate ' + - link + - ' res ', - res - ); - if (typeof res.feeForTx === undefined) { - throw new Error('no res?.feeForTx'); - } - - let result; - if (res.feeForTx * 1 > 0) { - result = { - selectedFeeIndex: 0, - shouldShowFees: false, - fees: [ - { - langMsg: 'xrp_speed_one', - feeForTx: Math.round(res.feeForTx).toString(), - amountForTx: res?.amountForTx, - selectedTransferAllBalance: res?.selectedTransferAllBalance, - isErrorFee: res?.isErrorFee - } - ] - }; - } else { - result = { - selectedFeeIndex: -3, - shouldShowFees: false - }; - } - return result as unknown as AirDAOBlockchainTypes.FeeRateResult; - } catch (e: any) { - if (e.message.indexOf('SERVER_RESPONSE_') === 0) { - throw e; - } - if (config.debug.cryptoErrors) { - console.log( - this._settings.currencyCode + - ' TrxTransferProcessor.getFeeRate new error ' + - e.message - ); - } - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' TrxTransferProcessor.getFeeRate new error ' + - e.message - ); - return this.getFeeRateOld(data, privateData, additionalData); - } - } - - async getFeeRateOld( - data: AirDAOBlockchainTypes.TransferData, - privateData: AirDAOBlockchainTypes.TransferPrivateData, - additionalData: {} = {} - ): Promise { - const addressHexTo = TronUtils.addressToHex(data.addressTo); - const result: AirDAOBlockchainTypes.FeeRateResult = { - selectedFeeIndex: -3, - shouldShowFees: false - } as AirDAOBlockchainTypes.FeeRateResult; - - const sendLink = BlocksoftExternalSettings.getStatic('TRX_SEND_LINK'); - const link = sendLink + '/wallet/getaccountresource'; // http://trx.trusteeglobal.com:8090/wallet - - try { - let feeForTx = 0; - try { - const res = await BlocksoftBalances.setCurrencyCode('TRX') - .setAddress(data.addressFrom) - .getResources('TrxSendTx'); - await AirDAOCryptoLog.log( - this._settings.currencyCode + - ' TrxTransferProcessor.getFeeRate result resources from ' + - data.addressFrom, - res - ); - if (this._isToken20) { - const bandForTx = BlocksoftExternalSettings.getStatic( - 'TRX_TRC20_BAND_PER_TX' - ); - const priceForBand = BlocksoftExternalSettings.getStatic( - 'TRX_TRC20_PRICE_PER_BAND' - ); - const fullPriceBand = bandForTx * priceForBand; - let feeLog = ''; - if (res.leftBand <= 0) { - feeForTx = fullPriceBand; - feeLog += - ' res.leftBand<=0 bandFee=' + - bandForTx + - '*' + - priceForBand + - '=' + - fullPriceBand; - } else { - const diffB = bandForTx - res.leftBand; - feeLog += ' diffB=' + bandForTx + '-' + res.leftBand + '=' + diffB; - if (diffB > 0) { - feeForTx = BlocksoftUtils.mul( - fullPriceBand, - BlocksoftUtils.div(diffB, bandForTx) - ); - feeLog += - ' fullPriceBand=' + - bandForTx + - '*' + - priceForBand + - '=' + - fullPriceBand; - feeLog += - ' bandFee=' + - fullPriceBand + - '*' + - diffB + - '/' + - bandForTx + - '=' + - feeForTx; - } - } - const energyForTx = BlocksoftExternalSettings.getStatic( - 'TRX_TRC20_ENERGY_PER_TX' - ); - const priceForEnergy = BlocksoftExternalSettings.getStatic( - 'TRX_TRC20_PRICE_PER_ENERGY' - ); - const fullPriceEnergy = energyForTx * priceForEnergy; - if (res.leftEnergy <= 0) { - feeForTx = feeForTx + fullPriceEnergy; - feeLog += - ' res.leftEnergy<=0 energyFee=' + - energyForTx + - '*' + - priceForEnergy + - '=' + - fullPriceEnergy; - } else { - const diffE = energyForTx - res.leftEnergy; - feeLog += - ' diffE=' + energyForTx + '-' + res.leftEnergy + '=' + diffE; - if (diffE > 0) { - const energyFee = - BlocksoftUtils.mul( - fullPriceEnergy, - BlocksoftUtils.div(diffE / energyForTx) - ) * 1; - feeForTx = feeForTx + energyFee; - feeLog += - ' fullPriceEnergy=' + - energyForTx + - '*' + - priceForEnergy + - '=' + - fullPriceEnergy; - feeLog += - ' energyFee=' + - fullPriceEnergy + - '*' + - diffE + - '/' + - bandForTx + - '=' + - energyFee; - } - } - await AirDAOCryptoLog.log( - this._settings.currencyCode + - ' TrxTransferProcessor.getFeeRate feeForTx ' + - feeForTx + - ' calculated by ' + - feeLog - ); - } else { - // @ts-ignore - if (res.leftBand <= 0) { - feeForTx = BlocksoftExternalSettings.getStatic( - 'TRX_BASIC_PRICE_WHEN_NO_BAND' - ); - } - } - } catch (e: any) { - // do nothing - if (config.debug.cryptoErrors) { - console.log( - this._settings.currencyCode + - ' TrxTransferProcessor.getFeeRate addressFrom data error ' + - e.message - ); - } - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' TrxTransferProcessor.getFeeRate addressFrom data error ' + - e.message - ); - } - - let isErrorFee = false; - const balance = await BlocksoftBalances.setCurrencyCode('TRX') - .setAddress(data.addressFrom) - .getBalance('TrxSendTx'); - if (this._isToken20) { - if (!balance || balance.balanceAvailable <= feeForTx) { - isErrorFee = true; - // throw new Error('SERVER_RESPONSE_NOT_ENOUGH_FEE') - } - } else if (this._tokenName === '_') { - if ( - !balance || - balance.balanceAvailable <= feeForTx + data.amount * 1 - ) { - isErrorFee = true; - // throw new Error('SERVER_RESPONSE_NOT_ENOUGH_FEE') - } - } - - if (typeof data.dexOrderData === 'undefined' || !data.dexOrderData) { - try { - const res2 = await AirDAOAxios.post(link, { - address: addressHexTo - }); - const tronData2 = res2.data; - delete tronData2.assetNetUsed; - delete tronData2.assetNetLimit; - await AirDAOCryptoLog.log( - this._settings.currencyCode + - ' TrxTransferProcessor.getFeeRate result ' + - link + - ' to ' + - data.addressTo, - tronData2 - ); - if (typeof tronData2.freeNetLimit === 'undefined') { - feeForTx = feeForTx + 1100000; - } - } catch (e: any) { - // do nothing - if (config.debug.cryptoErrors) { - console.log( - this._settings.currencyCode + - ' TrxTransferProcessor.getFeeRate addressTo data error ' + - e.message - ); - } - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' TrxTransferProcessor.getFeeRate addressTo data error ' + - e.message - ); - } - } - - if (feeForTx !== 0) { - let amountForTx = data.amount; - let selectedTransferAllBalance = data.amount; - if (this._tokenName === '_') { - // tslint:disable-next-line:no-shadowed-variable - const balance = await BlocksoftBalances.setCurrencyCode('TRX') - .setAddress(data.addressFrom) - .getBalance('TrxSendTx'); - if (balance && typeof balance.balance !== 'undefined') { - if (balance.balance === 0) { - amountForTx = String(0); - selectedTransferAllBalance = String(0); - } else { - selectedTransferAllBalance = BlocksoftUtils.diff( - balance.balance, - feeForTx - ); - const test = BlocksoftUtils.diff(data.amount, feeForTx); - if (test * 1 > balance.balance * 1) { - amountForTx = selectedTransferAllBalance; - } - } - } - } - - result.fees = [ - { - langMsg: 'xrp_speed_one', - feeForTx: Math.round(feeForTx).toString(), - amountForTx, - selectedTransferAllBalance, - isErrorFee - } - ]; - /* - if (res.data.balance * 1 < feeForTx * 1) { - throw new Error('SERVER_RESPONSE_BANDWITH_ERROR_TRX') - } - */ - result.selectedFeeIndex = 0; - } - } catch (e: any) { - if (e.message.indexOf('SERVER_RESPONSE_') === 0) { - throw e; - } - if (config.debug.cryptoErrors) { - console.log( - this._settings.currencyCode + - ' TrxTransferProcessor.getFeeRate error ' + - e.message - ); - } - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' TrxTransferProcessor.getFeeRate error ' + - e.message - ); - } - return result; - } - - async getTransferAllBalance( - data: AirDAOBlockchainTypes.TransferData, - privateData: AirDAOBlockchainTypes.TransferPrivateData, - additionalData: AirDAOBlockchainTypes.TransferAdditionalData = {} - ): Promise { - data.isTransferAll = true; - const balance = data.amount; - // @ts-ignore - await AirDAOCryptoLog.log( - this._settings.currencyCode + - ` TrxTransferProcessor.getTransferAllBalance ', - ${data.addressFrom + ' => ' + balance}` - ); - if (balance === '0') { - return { - selectedTransferAllBalance: '0', - selectedFeeIndex: -1, - fees: [], - shouldShowFees: false, - countedForBasicBalance: '0' - }; - } - const fees = await this.getFeeRate(data, privateData, additionalData); - if (!fees || fees.selectedFeeIndex < 0) { - return { - selectedTransferAllBalance: balance, - selectedFeeIndex: -3, - fees: [], - shouldShowFees: false, - countedForBasicBalance: balance - }; - } - return { - ...fees, - shouldShowFees: false, - selectedTransferAllBalance: - fees.fees[fees.selectedFeeIndex].selectedTransferAllBalance - }; - } - - /** - * https://developers.tron.network/reference#walletcreatetransaction - * https://developers.tron.network/docs/trc20-introduction#section-8usdt-transfer - */ - async sendTx( - data: AirDAOBlockchainTypes.TransferData, - privateData: AirDAOBlockchainTypes.TransferPrivateData, - uiData: AirDAOBlockchainTypes.TransferUiData - ): Promise { - if (typeof privateData.privateKey === 'undefined') { - throw new Error('TRX transaction required privateKey'); - } - if ( - uiData.selectedFee.isErrorFee && - (typeof uiData.uiErrorConfirmed === 'undefined' || - !uiData.uiErrorConfirmed) - ) { - if (config.debug.cryptoErrors) { - console.log(`uiData.selectedFee.isErrorFee`, uiData); - } - throw new Error('SERVER_RESPONSE_NOT_ENOUGH_FEE'); - } - - await AirDAOCryptoLog.log( - this._settings.currencyCode + - ' TrxTransferProcessor.sendTx started ' + - data.addressFrom + - ' => ' + - data.addressTo - ); - - const logData = { - currencyCode: strings, - selectedFee: strings, - from: strings, - basicAddressTo: strings, - basicAmount: strings, - pushSetting: strings, - pushLocale: undefined, - basicToken: strings - }; - logData.currencyCode = this._settings.currencyCode; - logData.selectedFee = uiData.selectedFee; - logData.from = data.addressFrom; - logData.basicAddressTo = data.addressTo; - logData.basicAmount = data.amount; - logData.pushLocale = sublocale(); - logData.pushSetting = await Database.localStorage.get('transactionsNotifs'); - logData.basicToken = this._tokenName; - - const sendLink = BlocksoftExternalSettings.getStatic('TRX_SEND_LINK'); - let tx; - if (typeof data.blockchainData !== 'undefined' && data.blockchainData) { - tx = data.blockchainData; - } else { - let link; - let res; - let params; - - if (typeof data.dexOrderData !== 'undefined' && data.dexOrderData) { - // {"tokenContract":"41a2726afbecbd8e936000ed684cef5e2f5cf43008","contractMethod":"trxToTokenSwapInput(uint256)","options":{"callValue":"1000000"},"params":[{"type":"uint256","value":"116256"}]} - let ownerAddress; - - const abiCoder = new AbiCoder(); - try { - ownerAddress = TronUtils.addressToHex(data.addressFrom); - } catch (e: any) { - e.message += - ' inside TronUtils.addressToHex owner_address ' + data.addressFrom; - throw e; - } - - // tslint:disable-next-line:no-shadowed-variable - const link = sendLink + '/wallet/triggersmartcontract'; - const total = data.dexOrderData.length; - let index = 0; - for (const order of data.dexOrderData) { - index++; - let parameter = ''; - - if (order.params) { - const types = []; - const values = []; - try { - for (const tmp of order.params) { - let type; - let value; - try { - type = tmp.type; - value = tmp.value; - if (type === 'address') { - value = TronUtils.addressToHex(value).replace( - ADDRESS_PREFIX_REGEX, - '0x' - ); - } else if (type === 'address[]') { - value = value.map((v) => - TronUtils.addressToHex(v).replace( - ADDRESS_PREFIX_REGEX, - '0x' - ) - ); - } - types.push(type); - values.push(value); - } catch (e: any) { - throw new Error( - e.message + - ' type ' + - type + - ' tmp.value ' + - tmp.value + - ' value ' + - value - ); - } - } - parameter = abiCoder.encode(types, values).replace(/^(0x)/, ''); - } catch (e) { - throw new Error(e.message + ' in abiCoder'); - } - } - - // tslint:disable-next-line:no-shadowed-variable - let params; - try { - params = { - owner_address: ownerAddress, - contract_address: order.tokenContract, - function_selector: order.contractMethod, - parameter, - fee_limit: BlocksoftExternalSettings.getStatic( - 'TRX_TRC20_MAX_LIMIT' - ) - }; - if ( - typeof order.options !== undefined && - typeof order.options.callValue !== undefined - ) { - params.call_value = order.options.callValue * 1; - } - } catch (e1: any) { - throw new Error(e1.message + ' in params build'); - } - if (index < total) { - res = await AirDAOAxios.post(link, params); - - tx = res.data.transaction; - await AirDAOCryptoLog.log( - this._settings.currencyCode + ' TrxTxProcessor.sendSubTx tx', - tx - ); - - tx.signature = [ - TronUtils.ECKeySign( - Buffer.from(tx.txID, 'hex'), - Buffer.from(privateData.privateKey, 'hex') - ) - ]; - await AirDAOCryptoLog.log( - this._settings.currencyCode + ' TrxTxProcessor.sendSubTx signed', - tx - ); - - let resultSub = {} as AirDAOBlockchainTypes.SendTxResult; - try { - resultSub = await this.sendProvider.sendTx( - tx, - '', - false, - logData - ); - await AirDAOCryptoLog.log( - this._settings.currencyCode + - ' TrxTxProcessor.sendSubTx broadcasted' - ); - } catch (e: any) { - if (config.debug.cryptoErrors) { - console.log( - this._settings.currencyCode + - ' TrxTransferProcessor.sendSubTx error', - e, - uiData - ); - } - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' TrxTransferProcessor.sendSubTx error ' + - e.message - ); - // noinspection ES6MissingAwait - throw e; - } - - const linkRecheck = sendLink + '/wallet/gettransactioninfobyid'; - let checks = 0; - let mined = false; - do { - checks++; - try { - const recheck = await AirDAOAxios.post(linkRecheck, { - value: tx.txID - }); - if (typeof recheck.data !== undefined) { - if ( - typeof recheck.data.id !== undefined && - typeof recheck.data.blockNumber !== undefined && - typeof recheck.data.receipt !== undefined && - typeof recheck.data.receipt.result !== undefined - ) { - // @ts-ignore - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' TrxTransferProcessor.sendSubTx recheck ', - { - id: recheck.data.id, - blockNumber: recheck.data.blockNumber, - receipt: recheck.data.receipt - } - ); - mined = true; - const minedStatus = - recheck.data.receipt.result.toUpperCase(); - if (minedStatus === 'OUT_OF_ENERGY') { - strings(`account.transactionStatuses.out_of_energy`); - } else if (minedStatus === 'FAILED') { - strings(`account.transactionStatuses.fail`); - } else if (minedStatus !== 'SUCCESS') { - throw new Error( - 'Bad tx status ' + JSON.stringify(recheck.data.receipt) - ); - } - break; - } - } - } catch (e1: any) { - if (config.debug.cryptoErrors) { - console.log( - this._settings.currencyCode + - ' TRX transaction recheck error ', - e1 - ); - } - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' TRX transaction recheck error ' + - e1.message - ); - } - } while (checks < 100 && !mined); - } else { - res = await AirDAOAxios.post(link, params); - } - } - } else { - if (typeof data.addressTo === 'undefined') { - throw new Error('TRX transaction required addressTo'); - } - if (data.addressFrom === data.addressTo) { - throw new Error('SERVER_RESPONSE_SELF_TX_FORBIDDEN'); - } - // check error - await this.getFeeRate(data, privateData); - - let toAddress, ownerAddress; - - try { - toAddress = TronUtils.addressToHex(data.addressTo); - } catch (e: any) { - e.message += - ' inside TronUtils.addressToHex to_address ' + data.addressTo; - throw e; - } - - if (TronUtils.addressHexToStr(toAddress) !== data.addressTo) { - AirDAOCryptoLog.log( - 'TrxTransferProcessor.sendTx heck address ' + - data.addressTo + - ' hex ' + - toAddress + - ' => ' + - TronUtils.addressHexToStr(toAddress) - ); - throw new Error( - 'TRX SYSTEM ERROR - Please check address ' + data.addressTo - ); - } - - try { - ownerAddress = TronUtils.addressToHex(data.addressFrom); - } catch (e: any) { - e.message += - ' inside TronUtils.addressToHex owner_address ' + data.addressFrom; - throw e; - } - - if (this._tokenName[0] === 'T') { - link = sendLink + '/wallet/triggersmartcontract'; - const parameter = - '0000000000000000000000' + - toAddress.toUpperCase() + - '000000000000000000000000' + - BlocksoftUtils.decimalToHex(BlocksoftUtils.round(data.amount), 40); - params = { - owner_address: ownerAddress, - contract_address: TronUtils.addressToHex(this._tokenName), - function_selector: 'transfer(address,uint256)', - parameter, - fee_limit: BlocksoftExternalSettings.getStatic( - 'TRX_TRC20_MAX_LIMIT' - ), - call_value: 0 - }; - await AirDAOCryptoLog.log( - this._settings.currencyCode + - `' TrxTransferProcessor.sendTx inited1' + - ${data.addressFrom} + - ' => ' + - ${data.addressTo} + - ' ' + - ${link}, - ${params}` - ); - res = await AirDAOAxios.post(link, params); - } else { - params = { - owner_address: ownerAddress, - to_address: toAddress, - amount: BlocksoftUtils.round(data.amount) * 1 - }; - - if (this._tokenName === '_') { - link = sendLink + '/wallet/createtransaction'; - } else { - // @ts-ignore - params.asset_name = - '0x' + Buffer.from(this._tokenName).toString('hex'); - link = sendLink + '/wallet/transferasset'; - } - - try { - await AirDAOCryptoLog.log( - this._settings.currencyCode + - `' TrxTransferProcessor.sendTx inited2' + - ${data.addressFrom} + - ' => ' + - ${data.addressTo} + - ' ' + - ${link}, - ${params}` - ); - res = await AirDAOAxios.post(link, params); - } catch (e: any) { - await AirDAOCryptoLog.log( - this._settings.currencyCode + - ' TrxTransferProcessor.sendTx result2' + - data.addressFrom + - ' => ' + - data.addressTo + - ' ' + - link + - ' ' + - e.message - ); - if ( - e.message.indexOf('timeout of') !== -1 || - e.message.indexOf('network') !== -1 - ) { - throw new Error('SERVER_RESPONSE_NOT_CONNECTED'); - } else { - throw e; - } - } - } - } - - // @ts-ignore - if (typeof res.data.Error !== 'undefined') { - await AirDAOCryptoLog.log( - this._settings.currencyCode + - `' TrxTransferProcessor.sendTx error ' + - ${data.addressFrom} + - ' => ' + - ${data.addressTo} + - ' ', - ${res.data}` - ); - // @ts-ignore - this.sendProvider.trxError(res.data.Error.message || res.data.Error); - } - - // @ts-ignore - tx = res.data; - if ( - (typeof data.dexOrderData !== 'undefined' && data.dexOrderData) || - this._tokenName[0] === 'T' - ) { - // @ts-ignore - if ( - typeof res.data.transaction === 'undefined' || - typeof res.data.result === 'undefined' - ) { - // @ts-ignore - if (typeof res.data.result.message !== 'undefined') { - // @ts-ignore - res.data.result.message = BlocksoftUtils.hexToUtf( - '0x' + res.data.result.message - ); - } - // @ts-ignore - this.sendProvider.trxError( - 'No tx in contract data ' + JSON.stringify(res.data) - ); - } - // @ts-ignore - tx = res.data.transaction; - } else { - // @ts-ignore - if (typeof res.data.txID === undefined) { - // @ts-ignore - if (typeof res.data.result.message !== undefined) { - // @ts-ignore - res.data.result.message = BlocksoftUtils.hexToUtf( - '0x' + res.data.result.message - ); - } - // @ts-ignore - this.sendProvider.trxError( - 'No txID in data ' + JSON.stringify(res.data) - ); - } - } - } - - await AirDAOCryptoLog.log( - this._settings.currencyCode + - ' TrxTxProcessor.sendTx token ' + - this._tokenName + - ' tx', - tx - ); - - tx.signature = [ - TronUtils.ECKeySign( - Buffer.from(tx.txID, 'hex'), - Buffer.from(privateData.privateKey, 'hex') - ) - ]; - if ( - typeof uiData !== 'undefined' && - typeof uiData.selectedFee !== 'undefined' && - typeof uiData.selectedFee.rawOnly !== 'undefined' && - uiData.selectedFee.rawOnly - ) { - return { rawOnly: uiData.selectedFee.rawOnly, raw: JSON.stringify(tx) }; - } - - await AirDAOCryptoLog.log( - this._settings.currencyCode + ' TrxTxProcessor.sendTx signed', - tx - ); - - let result = {} as AirDAOBlockchainTypes.SendTxResult; - try { - result = await this.sendProvider.sendTx(tx, '', false, logData); - await AirDAOCryptoLog.log( - this._settings.currencyCode + ' TrxTxProcessor.sendTx broadcasted' - ); - } catch (e: any) { - if (config.debug.cryptoErrors) { - console.log( - this._settings.currencyCode + ' TrxTransferProcessor.sendTx error', - e, - uiData - ); - } - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' TrxTransferProcessor.sendTx error ' + - e.message - ); - throw e; - } - await BlocksoftTransactions.resetTransactionsPending( - { account: { currencyCode: 'TRX' } }, - 'AccountRunPending' - ); - - if (config.debug.cryptoErrors) { - console.log( - this._settings.currencyCode + ' TrxTransferProcessor.sendTx result', - JSON.parse(JSON.stringify(result)) - ); - } - return result; - } -} diff --git a/crypto/blockchains/trx/basic/TrxNodeInfoProvider.ts b/crypto/blockchains/trx/basic/TrxNodeInfoProvider.ts deleted file mode 100644 index 34a778c70..000000000 --- a/crypto/blockchains/trx/basic/TrxNodeInfoProvider.ts +++ /dev/null @@ -1,46 +0,0 @@ -import AirDAOAxios from '../../../common/AirDAOAxios'; -import AirDAOCryptoLog from '../../../common/AirDAOCryptoLog'; -import BlocksoftExternalSettings from '@crypto/common/AirDAOExternalSettings'; - -const INFO_MAX_TRY = 50; // max tries before error appear in axios get - -let CACHE_LAST_BLOCK = 0; - -export default class TrxNodeInfoProvider { - /** - * @returns {Promise} - */ - async getLastBlock(): Promise { - try { - const sendLink: string = - BlocksoftExternalSettings.getStatic('TRX_SEND_LINK'); - const link = `${sendLink}/wallet/getnodeinfo`; - let info: any = await AirDAOAxios.getWithoutBraking(link, INFO_MAX_TRY); - if ( - info && - typeof info.data !== 'undefined' && - typeof info.data.block !== 'undefined' - ) { - info = info.data.block.split(',ID'); - info = parseInt(info[0].substr(4), 20); - if (info > CACHE_LAST_BLOCK) { - CACHE_LAST_BLOCK = info; - } - AirDAOCryptoLog.log( - 'TrxNodeInfoProvider.getLastBlock currentBlock ' + - JSON.stringify(info) - ); - } else { - AirDAOCryptoLog.log( - 'TrxNodeInfoProvider.getLastBlock currentBlock warning ' + - JSON.stringify(info) - ); - } - } catch (e: any) { - AirDAOCryptoLog.log( - 'TrxNodeInfoProvider.getLastBlock currentBlock error ' + e.message - ); - } - return CACHE_LAST_BLOCK; - } -} diff --git a/crypto/blockchains/trx/basic/TrxTransactionsProvider.ts b/crypto/blockchains/trx/basic/TrxTransactionsProvider.ts deleted file mode 100644 index 2b0e6d543..000000000 --- a/crypto/blockchains/trx/basic/TrxTransactionsProvider.ts +++ /dev/null @@ -1,303 +0,0 @@ -/** - * @version 0.5 - */ -import AirDAOCryptoLog from '../../../common/AirDAOCryptoLog'; -import AirDAOAxios from '../../../common/AirDAOAxios'; -import BlocksoftUtils from '../../../common/AirDAOUtils'; -import TrxNodeInfoProvider from './TrxNodeInfoProvider'; -import TransactionFilterTypeDict from '@crypto/TransactionFilterTypeDict'; - -const TXS_MAX_TRY = 10; - -interface CachedTransactionData { - time: number; - [tokenName: string]: UnifiedTransaction[] | number; -} - -interface UnifiedTransaction { - transactionHash: string; - blockHash: string; - blockNumber: number; - blockTime: number | Date; - blockConfirmations: number; - transactionDirection: string; - addressFrom: string; - addressTo: string; - addressAmount: number; - transactionStatus: string; - transactionFee: number; - transactionFilterType: string; - inputValue: string; -} - -export default class TrxTransactionsProvider { - private _lastBlock = 15850641; - private _tronscanLink = - 'https://api.tronscan.org/api/transaction?sort=-timestamp&count=true&limit=50&address='; - - constructor() { - this._nodeInfo = new TrxNodeInfoProvider(); - } - - private _nodeInfo: TrxNodeInfoProvider; - - async get( - scanData: { account: { address: string } }, - tokenName: string - ): Promise { - const address = scanData.account.address.trim(); - const now = new Date().getTime(); - - if ( - typeof CACHE_OF_TRANSACTIONS[address] !== 'undefined' && - now - CACHE_OF_TRANSACTIONS[address].time < CACHE_VALID_TIME - ) { - if (typeof CACHE_OF_TRANSACTIONS[address][tokenName] !== 'undefined') { - AirDAOCryptoLog.log( - ` TrxTransactionsProvider.get from cache', - ${address} + ' => ' + ${tokenName}` - ); - return CACHE_OF_TRANSACTIONS[address][tokenName]; - } - } - - const res = await AirDAOAxios.getWithoutBraking( - this._tronscanLink + address, - TXS_MAX_TRY - ); - - if ( - !res || - !res.data || - typeof res.data.data === 'undefined' || - res.data.data.length === 0 - ) - return false; - - this._lastBlock = await this._nodeInfo.getLastBlock(); - - CACHE_OF_TRANSACTIONS[address] = {} as CachedTransactionData; - CACHE_OF_TRANSACTIONS[address].time = new Date().getTime(); - CACHE_OF_TRANSACTIONS[address][tokenName] = []; - let tx; - for (tx of res.data.data) { - let tmp = false; - try { - tmp = await this._unifyTransaction(scanData, tx, tokenName); - } catch (e: any) { - AirDAOCryptoLog.log( - 'TrxTransactionsProvider.get unify error ' + - e.message + - ' tx ' + - tx?.transactionHash - ); - } - if (!tmp) continue; - - const transaction = tmp?.res; - - let txTokenName = '_'; - if (typeof tmp.txTokenName !== 'undefined' && tmp.txTokenName) { - txTokenName = tmp.txTokenName; - } else if (typeof tx.contractData === 'undefined') { - txTokenName = tokenName; - } else if (typeof tx.contractData.contract_address !== 'undefined') { - txTokenName = tx.contractData.contract_address; - } else if (typeof tx.contractData.asset_name !== 'undefined') { - txTokenName = tx.contractData.asset_name; - } - if (typeof CACHE_OF_TRANSACTIONS[address][txTokenName] === 'undefined') { - CACHE_OF_TRANSACTIONS[address][txTokenName] = []; - } - CACHE_OF_TRANSACTIONS[address][txTokenName].push(transaction); - } - return CACHE_OF_TRANSACTIONS[address][tokenName]; - } - - private async _unifyTransaction( - scanData: { account: { address: string } }, - transaction: { - diffSeconds: number; - amount: string; - ownerAddress: string; - data: string; - contractData: { - amount: string; - contract_address?: string; - asset_name?: string; - frozen_balance?: string; - }; - toAddress: string; - block: string; - confirmed: boolean; - contractRet: string; - hash: string; - timestamp: string; - }, - tokenName: string - ): Promise<{ res: UnifiedTransaction; txTokenName: string } | false> { - const address = scanData.account.address.trim(); - let transactionStatus = 'new'; - const now = new Date().getTime(); - transaction.diffSeconds = Math.round( - // tslint:disable-next-line:radix - (now - parseInt(transaction.timestamp)) / 1000 - ); - if (transaction.confirmed) { - if (typeof transaction.contractRet === 'undefined') { - transactionStatus = 'success'; - } else if (transaction.contractRet === 'SUCCESS') { - transactionStatus = 'success'; - } else { - transactionStatus = 'fail'; - } - // tslint:disable-next-line:radix - } else if (parseInt(transaction.block) > 0) { - if (transaction.diffSeconds > 120) { - transactionStatus = 'fail'; - } else { - transactionStatus = 'confirming'; - } - } - // tslint:disable-next-line:radix - if (parseInt(transaction.block) > this._lastBlock) { - // tslint:disable-next-line:radix - this._lastBlock = parseInt(transaction.block); - } - - // tslint:disable-next-line:radix - let blockConfirmations = this._lastBlock - parseInt(transaction.block); - if (blockConfirmations > 100 && transaction.diffSeconds < 600) { - blockConfirmations = transaction.diffSeconds; - } - - if (typeof transaction.timestamp === 'undefined') { - throw new Error( - ' no transaction.timeStamp error transaction data ' + - JSON.stringify(transaction) - ); - } - // tslint:disable-next-line:radix - let formattedTime = parseInt(transaction.timestamp); - try { - formattedTime = BlocksoftUtils.toDate( - // tslint:disable-next-line:radix - parseInt(transaction.timestamp) / 1000 - ); - } catch (e: any) { - e.message += - ' timestamp error transaction data ' + JSON.stringify(transaction); - throw e; - } - let addressAmount = 0; - let transactionDirection = 'self'; - const txTokenName = false; - let addressFrom = - address.toLowerCase() === transaction.ownerAddress.toLowerCase() - ? '' - : transaction.ownerAddress; - let transactionFilterType = TransactionFilterTypeDict.USUAL; - if (typeof transaction.contractData.amount === 'undefined') { - if ( - typeof transaction.contractData !== 'undefined' && - typeof transaction.contractData.frozen_balance !== 'undefined' - ) { - // tslint:disable-next-line:radix - addressAmount = parseInt(transaction.contractData.frozen_balance); - transactionDirection = 'freeze'; - transactionFilterType = TransactionFilterTypeDict.STAKE; - } else if ( - typeof transaction.amount !== 'undefined' && - typeof transaction.contractType !== 'undefined' && - // tslint:disable-next-line:radix - parseInt(transaction.contractType) === 13 - ) { - // tslint:disable-next-line:radix - addressAmount = parseInt(transaction.amount); - transactionDirection = 'claim'; - transactionFilterType = TransactionFilterTypeDict.STAKE; - } else if ( - typeof transaction.contractType !== 'undefined' && - // tslint:disable-next-line:radix - parseInt(transaction.contractType) === 12 - ) { - // tslint:disable-next-line:radix - addressAmount = parseInt(transaction.amount); - addressFrom = transaction.ownerAddress; - transactionDirection = 'unfreeze'; - transactionFilterType = TransactionFilterTypeDict.STAKE; - } else if ( - typeof transaction.contractType !== 'undefined' && - // tslint:disable-next-line:radix - parseInt(transaction.contractType) === 4 - ) { - // no vote tx - return false; - } else { - if ( - // tslint:disable-next-line:radix - parseInt(transaction.contractType) === 11 || - // tslint:disable-next-line:radix - parseInt(transaction.contractType) === 4 || - // tslint:disable-next-line:radix - parseInt(transaction.contractType) === 13 - ) { - // freeze = 11, vote = 4, claim = 13 - } else { - // noinspection ES6MissingAwait - AirDAOCryptoLog.log( - 'TrxTransactionsProvider._unifyTransaction buggy tx ' + - JSON.stringify(transaction) - ); - } - return false; - } - } else { - // tslint:disable-next-line:radix - addressAmount = parseInt(transaction.contractData.amount); - transactionDirection = - address.toLowerCase() === transaction.ownerAddress.toLowerCase() - ? 'outcome' - : 'income'; - } - let transactionFee = 0; - if ( - typeof transaction.cost !== 'undefined' && - typeof transaction.cost.fee !== 'undefined' && - transaction.cost.fee - ) { - transactionFee = parseFloat(transaction.cost.fee); - } - const res: UnifiedTransaction = { - transactionHash: transaction.hash, - blockHash: '', - // tslint:disable-next-line:radix - blockNumber: parseInt(transaction.block), - blockTime: formattedTime, - blockConfirmations: blockConfirmations, - transactionDirection, - addressFrom, - addressTo: - address.toLowerCase() === transaction.toAddress.toLowerCase() - ? '' - : transaction.toAddress, - addressAmount, - transactionStatus, - transactionFee, - transactionFilterType, - inputValue: transaction.data - }; - if ( - !res.addressTo && - (!res.addressFrom || - res.addressFrom.toLowerCase() === address.toLowerCase()) - ) { - return false; - } - - return { res, txTokenName }; - } -} - -const CACHE_OF_TRANSACTIONS: { [address: string]: CachedTransactionData } = {}; -const CACHE_VALID_TIME = 30000; // 30 seconds diff --git a/crypto/blockchains/trx/basic/TrxTransactionsTrc20Provider.ts b/crypto/blockchains/trx/basic/TrxTransactionsTrc20Provider.ts deleted file mode 100644 index af04d60fe..000000000 --- a/crypto/blockchains/trx/basic/TrxTransactionsTrc20Provider.ts +++ /dev/null @@ -1,220 +0,0 @@ -/** - * @version 0.5 - * https://github.com/tronscan/tronscan-frontend/wiki/TRONSCAN-API - */ -import TrxTransactionsProvider from './TrxTransactionsProvider'; -import BlocksoftUtils from '../../../common/AirDAOUtils'; -import AirDAOAxios from '@crypto/common/AirDAOAxios'; -import { Database, TransactionsDBModel } from '@database'; -import { DatabaseTable } from '@appTypes'; -import TransactionFilterTypeDict from '@crypto/TransactionFilterTypeDict'; -import SWAPS from '../dict/swaps'; - -interface UnifiedTransaction { - transactionHash: string; - blockHash: string; - blockNumber: string; - blockTime: Date; - blockConfirmations: number; - transactionDirection: - | 'income' - | 'outcome' - | 'self' - | 'swap_income' - | 'swap_outcome'; - addressFrom: string; - addressTo: string; - addressAmount: string; - transactionStatus: 'new' | 'success' | 'fail'; - transactionFee: number; - inputValue: string; -} - -export default class TrxTransactionsTrc20Provider extends TrxTransactionsProvider { - protected _token: string | false = false; - protected _tronscanLink = ''; - - setLink(token: string) { - this._token = token; - this._tronscanLink = - 'https://apilist.tronscan.org/api/contract/events?sort=-timestamp&count=true&limit=50&contract=' + - token + - '&address='; - } - - async _unifyTransaction( - scanData: { account: { address: string; transactionsScanTime: number } }, - transaction: { - amount?: string; - transferFromAddress?: string; - data?: string; - decimals?: string; - tokenName?: string; - transferToAddress?: string; - block?: string; - id?: string; - confirmed?: boolean; - transactionHash?: string; - timestamp?: number; - } - ): Promise { - const address = scanData.account.address.trim(); - let transactionStatus: 'new' | 'success' | 'fail' = 'new'; - if (transaction.confirmed) { - transactionStatus = 'success'; - } else if (transaction.block && parseInt(transaction.block) > 0) { - transactionStatus = 'fail'; - } - - const txTokenName: string | false = false; - let formattedTime: Date; - try { - formattedTime = BlocksoftUtils.toDate( - transaction.timestamp ? transaction.timestamp / 1000 : 0 - ); - } catch (e: any) { - e.message += - ' timestamp error transaction data ' + JSON.stringify(transaction); - throw e; - } - // if (typeof transaction.amount === 'undefined') { - // // noinspection ES6MissingAwait - // AirDAOCryptoLog.err( - // 'TrxTransactionsTrc20Provider._unifyTransaction buggy tx ' + - // JSON.stringify(transaction) - // ); - // } - - const res: UnifiedTransaction = { - transactionHash: transaction.transactionHash || '', - blockHash: '', - blockNumber: transaction.block || '0', - blockTime: formattedTime, - blockConfirmations: this._lastBlock - ? this._lastBlock - parseInt(transaction.block || '0', 20) - : 0, - transactionDirection: - address.toLowerCase() === - (transaction.transferFromAddress || '').toLowerCase() - ? 'outcome' - : 'income', - addressFrom: - address.toLowerCase() === - (transaction.transferFromAddress || '').toLowerCase() - ? '' - : transaction.transferFromAddress || '', - addressTo: - address.toLowerCase() === - (transaction.transferToAddress || '').toLowerCase() - ? '' - : transaction.transferToAddress || '', - addressAmount: - typeof transaction.amount !== 'undefined' - ? transaction.amount.toString() - : '0', - transactionStatus, - transactionFee: 0, - inputValue: transaction.data || '' - }; - - let needData = false; - if ( - res.addressAmount.indexOf( - '115792089237316195423570985008687907853269984665640564039457' - ) === 0 - ) { - res.addressAmount = '0'; - needData = true; - } - if (SWAPS[res.addressTo] !== undefined) { - res.addressTo = SWAPS[res.addressTo]; - res.transactionDirection = 'swap_outcome'; - res.addressAmount = '0'; - needData = true; - } else if (SWAPS[res.addressFrom] !== undefined) { - res.addressFrom = SWAPS[res.addressFrom]; - res.transactionDirection = 'swap_income'; - res.addressAmount = '0'; - needData = true; - } else if (res.transactionDirection === 'outcome') { - needData = true; - } - - if (needData) { - const diff = - scanData.account.transactionsScanTime - - (transaction.timestamp || 0) / 1000; - if (diff > 6000) { - return false; - } - } - - if (needData) { - const tmp = await AirDAOAxios.get( - 'https://apilist.tronscan.org/api/transaction-info?hash=' + - res.transactionHash - ); - res.transactionFee = - tmp.data.cost.fee * 1 + tmp.data.cost.energy_fee * 1 || 0; - - if (res.transactionFee > 0 && res.addressAmount * 1 > 0) { - const savedTRX = (await Database.unsafeRawQuery( - DatabaseTable.Transactions, - ` SELECT * FROM ${DatabaseTable.Transactions} WHERE transaction_hash='${res.transactionHash}' AND currency_code='TRX' ` - )) as TransactionsDBModel[]; - if (!savedTRX || savedTRX.length === 0) { - // AirDAOCryptoLog.log( - // 'TrxTransactionsTrc20Provider._unifyTransaction added fee for ' + - // res.transactionHash + - // ' amount ' + - // res.addressAmount + - // ' fee ' + - // res.transactionFee - // ); - const saveFee: UnifiedTransaction = { - account_id: 0, - address_amount: 0, - address_from: res.addressFrom, - address_to: res.addressTo, - block_confirmations: res.blockConfirmations, - block_number: res.blockNumber, - block_time: res.blockTime, - created_at: res.blockTime, - currency_code: 'TRX', - mined_at: res.blockTime, - transaction_direction: res.transactionDirection, - transaction_fee: res.transactionFee, - transaction_filter_type: TransactionFilterTypeDict.FEE, - transaction_hash: res.transactionHash, - transaction_status: res.transactionStatus, - transactions_scan_time: new Date().getTime(), - wallet_hash: scanData.account.walletHash - }; - await Database.createModel(DatabaseTable.Transactions, saveFee); - } - } - if (tmp.data.trc20TransferInfo !== undefined) { - for (const info of tmp.data.trc20TransferInfo) { - if (info.contract_address !== this._token) continue; - if (info.from_address === address) { - if (info.to_address === address) { - res.transactionDirection = 'self'; - } else { - res.transactionDirection = 'outcome'; - } - } else if (info.to_address === address) { - res.transactionDirection = 'income'; - res.addressAmount = info.amount_str || '0'; - } - } - } - if (res.transactionFee === 0 || res.addressAmount * 1 === 0) { - return false; - } - } else if (res.addressAmount * 1 === 0) { - return false; - } - - return { res, txTokenName }; - } -} diff --git a/crypto/blockchains/trx/basic/TrxTrongridProvider.ts b/crypto/blockchains/trx/basic/TrxTrongridProvider.ts deleted file mode 100644 index b90fcc9b5..000000000 --- a/crypto/blockchains/trx/basic/TrxTrongridProvider.ts +++ /dev/null @@ -1,330 +0,0 @@ -/** - * @version 0.5 - * https://github.com/tronscan/tronscan-frontend/wiki/TRONSCAN-API - */ -import AirDAOCryptoLog from '../../../common/AirDAOCryptoLog'; -import AirDAOAxios from '../../../common/AirDAOAxios'; -import BlocksoftExternalSettings from '@crypto/common/AirDAOExternalSettings'; -import TronUtils from '@crypto/blockchains/trx/ext/TronUtils'; - -const BALANCE_MAX_TRY = 10; - -interface CacheData { - isMultisig: boolean; - time: number; - [tokenName: string]: any; -} - -const CACHE_TRONGRID: { [address: string]: CacheData } = {}; - -const CACHE_VALID_TIME = 3000; // 3 seconds - -export default class TrxTrongridProvider { - async isMultisigTrongrid(address: string): Promise { - if (typeof CACHE_TRONGRID[address] !== 'undefined') { - return CACHE_TRONGRID[address].isMultisig; - } - const res = await this.get(address, '_', true); - return res?.isMultisig || false; - } - - /** - * https://api.trongrid.io/walletsolidity/getaccount?address=41d4eead2ea047881ce54cae1a765dfe92a8bfdbe9 - * @param {string} address - * @param {string} tokenName - * @returns {Promise} - */ - async get( - address: string, - tokenName: string, - useCache = true - ): Promise< - | false - | { - isMultisig: boolean; - balance: any; - voteTotal: number; - frozen?: any; // Make it optional - frozenExpireTime?: any; // Make it optional - frozenOthers?: any; // Make it optional - frozenEnergy?: any; // Make it optional - frozenEnergyExpireTime?: any; // Make it optional - frozenEnergyOthers?: any; // Make it optional - unconfirmed: number; - provider: string; - time: number; - } - > { - const now = new Date().getTime(); - if ( - useCache && - typeof CACHE_TRONGRID[address] !== 'undefined' && - now - CACHE_TRONGRID[address].time < CACHE_VALID_TIME - ) { - if (typeof CACHE_TRONGRID[address][tokenName] !== 'undefined') { - AirDAOCryptoLog.log( - `TrxTrongridProvider.get from cache', - ${address} + - ' => ' + - ${tokenName} + - ' : ' + - ${CACHE_TRONGRID[address][tokenName]}` - ); - // tslint:disable-next-line:no-shadowed-variable - const voteTotal = - typeof CACHE_TRONGRID[address].voteTotal !== 'undefined' - ? CACHE_TRONGRID[address].voteTotal - : 0; - // tslint:disable-next-line:no-shadowed-variable - const frozen = - typeof CACHE_TRONGRID[address][tokenName + 'frozen'] !== 'undefined' - ? CACHE_TRONGRID[address][tokenName + 'frozen'] - : 0; - // tslint:disable-next-line:no-shadowed-variable - const frozenExpireTime = - typeof CACHE_TRONGRID[address][tokenName + 'frozenExpireTime'] !== - 'undefined' - ? CACHE_TRONGRID[address][tokenName + 'frozenExpireTime'] - : 0; - // tslint:disable-next-line:no-shadowed-variable - const frozenOthers = - typeof CACHE_TRONGRID[address][tokenName + 'frozenOthers'] !== - 'undefined' - ? CACHE_TRONGRID[address][tokenName + 'frozenOthers'] - : 0; - // tslint:disable-next-line:no-shadowed-variable - const frozenEnergy = - typeof CACHE_TRONGRID[address][tokenName + 'frozenEnergy'] !== - 'undefined' - ? CACHE_TRONGRID[address][tokenName + 'frozenEnergy'] - : 0; - // tslint:disable-next-line:no-shadowed-variable - const frozenEnergyExpireTime = - typeof CACHE_TRONGRID[address][ - tokenName + 'frozenEnergyExpireTime' - ] !== 'undefined' - ? CACHE_TRONGRID[address][tokenName + 'frozenEnergyExpireTime'] - : 0; - // tslint:disable-next-line:no-shadowed-variable - const frozenEnergyOthers = - typeof CACHE_TRONGRID[address][tokenName + 'frozenEnergyOthers'] !== - 'undefined' - ? CACHE_TRONGRID[address][tokenName + 'frozenEnergyOthers'] - : 0; - return { - isMultisig: CACHE_TRONGRID[address].isMultisig, - balance: CACHE_TRONGRID[address][tokenName], - voteTotal, - frozen, - frozenExpireTime, - frozenOthers, - frozenEnergy, - frozenEnergyExpireTime, - frozenEnergyOthers, - unconfirmed: 0, - provider: 'trongrid-cache', - time: CACHE_TRONGRID[address].time - }; - } else if (tokenName !== '_') { - return false; - } - } - - const nodeLink = BlocksoftExternalSettings.getStatic('TRX_SOLIDITY_NODE'); - const link = nodeLink + '/walletsolidity/getaccount'; - const params = { address }; - AirDAOCryptoLog.log( - 'TrxTrongridProvider.get ' + link + ' ' + JSON.stringify(params) - ); - const res: { data: any } = await AirDAOAxios.postWithoutBraking( - link, - params, - BALANCE_MAX_TRY - ); - if (!res || !res.data) { - return false; - } - - let isMultisig = false; - if (res.data.active_permission) { - for (const perm of res.data.active_permission) { - if (perm.keys[0].address !== address) { - isMultisig = TronUtils.addressHexToStr(perm.keys[0].address); - } - } - } - - CACHE_TRONGRID[address] = { - isMultisig, - time: now - }; - CACHE_TRONGRID[address]._ = - typeof res.data.balance !== 'undefined' ? res.data.balance : 0; - CACHE_TRONGRID[address]._frozen = - typeof res.data.frozen !== 'undefined' && - typeof res.data.frozen[0] !== 'undefined' - ? res.data.frozen[0].frozen_balance - : 0; - CACHE_TRONGRID[address]._frozenExpireTime = - typeof res.data.frozen !== 'undefined' && - typeof res.data.frozen[0] !== 'undefined' - ? res.data.frozen[0].expire_time - : 0; - CACHE_TRONGRID[address]._frozenOthers = - typeof res.data.delegated_frozen_balance_for_bandwidth !== 'undefined' - ? res.data.delegated_frozen_balance_for_bandwidth - : 0; - CACHE_TRONGRID[address]._frozenEnergy = - typeof res.data.account_resource !== 'undefined' && - typeof res.data.account_resource.frozen_balance_for_energy !== - 'undefined' && - typeof res.data.account_resource.frozen_balance_for_energy - .frozen_balance !== 'undefined' - ? res.data.account_resource.frozen_balance_for_energy.frozen_balance - : 0; - CACHE_TRONGRID[address]._frozenEnergyExpireTime = - typeof res.data.account_resource !== 'undefined' && - typeof res.data.account_resource.frozen_balance_for_energy !== - 'undefined' && - typeof res.data.account_resource.frozen_balance_for_energy.expire_time !== - 'undefined' - ? res.data.account_resource.frozen_balance_for_energy.expire_time - : 0; - - CACHE_TRONGRID[address]._frozenEnergyOthers = 0; - if ( - typeof res.data.account_resource !== 'undefined' && - typeof res.data.account_resource.delegated_frozen_balance_for_energy !== - 'undefined' && - res.data.account_resource.delegated_frozen_balance_for_energy * 1 > 0 - ) { - CACHE_TRONGRID[address]._frozenEnergyOthers = - res.data.account_resource.delegated_frozen_balance_for_energy * 1; - } - CACHE_TRONGRID[address].voteTotal = - typeof res.data.votes !== 'undefined' && - typeof res.data.votes[0] !== 'undefined' - ? res.data.votes[0].vote_count - : 0; - - if (res.data.assetV2) { - let token; - for (token of res.data.assetV2) { - CACHE_TRONGRID[address][token.key] = token.value; - } - } - - if (typeof CACHE_TRONGRID[address][tokenName] === 'undefined') { - return false; - } - - const balance = CACHE_TRONGRID[address][tokenName]; - const frozen = - typeof CACHE_TRONGRID[address][tokenName + 'frozen'] !== 'undefined' - ? CACHE_TRONGRID[address][tokenName + 'frozen'] - : 0; - const frozenExpireTime = - typeof CACHE_TRONGRID[address][tokenName + 'frozenExpireTime'] !== - 'undefined' - ? CACHE_TRONGRID[address][tokenName + 'frozenExpireTime'] - : 0; - const frozenOthers = - typeof CACHE_TRONGRID[address][tokenName + 'frozenOthers'] !== 'undefined' - ? CACHE_TRONGRID[address][tokenName + 'frozenOthers'] - : 0; - const frozenEnergy = - typeof CACHE_TRONGRID[address][tokenName + 'frozenEnergy'] !== 'undefined' - ? CACHE_TRONGRID[address][tokenName + 'frozenEnergy'] - : 0; - const frozenEnergyExpireTime = - typeof CACHE_TRONGRID[address][tokenName + 'frozenEnergyExpireTime'] !== - 'undefined' - ? CACHE_TRONGRID[address][tokenName + 'frozenEnergyExpireTime'] - : 0; - const frozenEnergyOthers = - typeof CACHE_TRONGRID[address][tokenName + 'frozenEnergy'] !== 'undefined' - ? CACHE_TRONGRID[address][tokenName + 'frozenEnergyOthers'] - : 0; - const voteTotal = - typeof CACHE_TRONGRID[address].voteTotal !== 'undefined' - ? CACHE_TRONGRID[address].voteTotal - : 0; - return { - isMultisig: CACHE_TRONGRID[address].isMultisig, - balance, - voteTotal, - frozen, - frozenExpireTime, - frozenOthers, - frozenEnergy, - frozenEnergyExpireTime, - frozenEnergyOthers, - unconfirmed: 0, - provider: 'trongrid ' + nodeLink, - time: CACHE_TRONGRID[address].time - }; - } - - async getResources(address: string): Promise<{ - leftBand: any; - totalBand: any; - leftEnergy: any; - totalEnergy: any; - }> { - const sendLink = BlocksoftExternalSettings.getStatic('TRX_SEND_LINK'); - const link = sendLink + '/wallet/getaccountresource'; - let leftBand: any = false; - let totalBand: any = false; - let leftEnergy: any = false; - let totalEnergy: any = false; - try { - const res: { data: any } = await AirDAOAxios.post(link, { address }); - if (!res || typeof res.data !== 'object') { - throw new Error('Invalid response from Trongrid'); - } - const tronData = res.data; - delete tronData.assetNetUsed; - delete tronData.assetNetLimit; - await AirDAOCryptoLog.log( - 'TrxTrongridProvider.assets result ' + link + ' from ' + address, - tronData - ); - totalBand = - typeof tronData.freeNetLimit !== 'undefined' && tronData.freeNetLimit - ? tronData.freeNetLimit - : 0; - if ( - typeof tronData.NetLimit !== 'undefined' && - tronData.NetLimit && - tronData.NetLimit * 1 > 0 - ) { - totalBand = totalBand * 1 + tronData.NetLimit * 1; - } - - leftBand = totalBand; - if (typeof tronData.freeNetUsed !== 'undefined' && tronData.freeNetUsed) { - leftBand = leftBand - tronData.freeNetUsed * 1; - } - if (typeof tronData.NetUsed !== 'undefined' && tronData.NetUsed) { - leftBand = leftBand - tronData.NetUsed * 1; - } - - totalEnergy = - typeof tronData.EnergyLimit !== 'undefined' && tronData.EnergyLimit - ? tronData.EnergyLimit - : 0; - leftEnergy = totalEnergy; - if (typeof tronData.EnergyUsed !== 'undefined' && tronData.EnergyUsed) { - leftEnergy = leftEnergy - tronData.EnergyUsed * 1; - } - } catch (e) { - console.log(e); - } - return { - leftBand, - totalBand, - leftEnergy, - totalEnergy - }; - } -} diff --git a/crypto/blockchains/trx/basic/TrxTronscanProvider.ts b/crypto/blockchains/trx/basic/TrxTronscanProvider.ts deleted file mode 100644 index cc7eb8e35..000000000 --- a/crypto/blockchains/trx/basic/TrxTronscanProvider.ts +++ /dev/null @@ -1,163 +0,0 @@ -/** - * @version 0.5 - * https://github.com/tronscan/tronscan-frontend/wiki/TRONSCAN-API - */ -import AirDAOCryptoLog from '../../../common/AirDAOCryptoLog'; -import AirDAOAxios from '../../../common/AirDAOAxios'; - -const BALANCE_PATH = 'https://apilist.tronscan.org/api/account?address='; -const BALANCE_MAX_TRY = 10; - -interface TronScanCache { - [address: string]: { - time: number; - voteTotal?: number; - [tokenName: string]: number | undefined; - }; -} - -const CACHE_TRONSCAN: TronScanCache = {}; -const CACHE_VALID_TIME = 3000; // 3 seconds - -export default class TrxTronscanProvider { - /** - * https://apilist.tronscan.org/api/account?address=TUbHxAdhPk9ykkc7SDP5e9zUBEN14K65wk - * @param {string} address - * @param {string} tokenName - * @returns {Promise} - */ - async get( - address: string, - tokenName: string, - useCache = true - ): Promise< - | boolean - | number - | { - unconfirmed: number; - frozen: number; - frozenEnergy: number; - voteTotal: number; - balance: number; - provider: string; - time: number; - } - > { - const now = new Date().getTime(); - if ( - useCache && - typeof CACHE_TRONSCAN[address] !== 'undefined' && - now - CACHE_TRONSCAN[address].time < CACHE_VALID_TIME - ) { - if (typeof CACHE_TRONSCAN[address][tokenName] !== 'undefined') { - AirDAOCryptoLog.log( - `TrxTronscanProvider.get from cache', - ${address} + - ' => ' + - {tokenName} + - ' : ' + - ${CACHE_TRONSCAN[address][tokenName]}` - ); - // tslint:disable-next-line:no-shadowed-variable - const frozen: number = - typeof CACHE_TRONSCAN[address][tokenName + 'frozen'] !== 'undefined' - ? CACHE_TRONSCAN[address][tokenName + 'frozen']! - : 0; - // tslint:disable-next-line:no-shadowed-variable - const frozenEnergy: number = - typeof CACHE_TRONSCAN[address][tokenName + 'frozenEnergy'] !== - 'undefined' - ? CACHE_TRONSCAN[address][tokenName + 'frozenEnergy']! - : 0; - // tslint:disable-next-line:no-shadowed-variable - const voteTotal: number = - typeof CACHE_TRONSCAN[address].voteTotal !== 'undefined' - ? CACHE_TRONSCAN[address].voteTotal! - : 0; - return { - balance: CACHE_TRONSCAN[address][tokenName]!, - voteTotal, - frozen, - frozenEnergy, - unconfirmed: 0, - provider: 'tronscan-cache', - time: CACHE_TRONSCAN[address].time - }; - } else if (tokenName !== '_') { - return false; - } - } - - const link = BALANCE_PATH + address; - AirDAOCryptoLog.log('TrxTronscanProvider.get ' + link); - const res = await AirDAOAxios.getWithoutBraking(link, BALANCE_MAX_TRY); - // @ts-ignore - if (!res || !('data' in res)) { - return false; - } - - CACHE_TRONSCAN[address] = { time: now }; - CACHE_TRONSCAN[address].time = now; - CACHE_TRONSCAN[address]._ = res.data.balance; - CACHE_TRONSCAN[address]._frozen = - typeof res.data.frozen.total !== 'undefined' ? res.data.frozen.total : 0; - CACHE_TRONSCAN[address]._frozenEnergy = - typeof res.data.accountResource !== 'undefined' && - typeof res.data.accountResource.frozen_balance_for_energy !== - 'undefined' && - typeof res.data.accountResource.frozen_balance_for_energy - .frozen_balance !== 'undefined' - ? res.data.accountResource.frozen_balance_for_energy.frozen_balance - : 0; - - CACHE_TRONSCAN[address].voteTotal = - typeof res.data.voteTotal !== 'undefined' ? res.data.voteTotal : 0; - let token; - if (res.data.tokenBalances) { - for (token of res.data.tokenBalances) { - const id = - typeof token.name !== 'undefined' ? token.name : token.tokenId; - CACHE_TRONSCAN[address][id] = token.balance; - } - } - - if (res.data.trc20token_balances) { - for (token of res.data.trc20token_balances) { - const id = - typeof token.name !== 'undefined' ? token.name : token.tokenId; - CACHE_TRONSCAN[address][id] = token.balance; - } - } - - if (typeof CACHE_TRONSCAN[address][tokenName] === 'undefined') { - if (tokenName.indexOf('T') === 0) { - return 0; - } else { - return false; - } - } - - const balance = CACHE_TRONSCAN[address][tokenName]!; - const frozen: number = - typeof CACHE_TRONSCAN[address][tokenName + 'frozen'] !== 'undefined' - ? CACHE_TRONSCAN[address][tokenName + 'frozen']! - : 0; - const frozenEnergy: number = - typeof CACHE_TRONSCAN[address][tokenName + 'frozenEnergy'] !== 'undefined' - ? CACHE_TRONSCAN[address][tokenName + 'frozenEnergy']! - : 0; - const voteTotal: number = - typeof CACHE_TRONSCAN[address].voteTotal !== 'undefined' - ? CACHE_TRONSCAN[address].voteTotal! - : 0; - return { - balance, - frozen, - frozenEnergy, - voteTotal, - unconfirmed: 0, - provider: 'tronscan', - time: CACHE_TRONSCAN[address].time - }; - } -} diff --git a/crypto/blockchains/trx/dict/swaps.ts b/crypto/blockchains/trx/dict/swaps.ts deleted file mode 100644 index aec5c67b9..000000000 --- a/crypto/blockchains/trx/dict/swaps.ts +++ /dev/null @@ -1,12 +0,0 @@ -export default { - TTnSHzUoho1CU6zFYVzVSCKq8EX8ZddkVv: 'JUSTSWAP-WTRX-TRX', - TJmTeYk5zmg8pNPGYbDb2psadwVLYDDYDr: 'JUSTSWAP-DICE-TRX', - TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE: 'JUSTSWAP-USDT-TRX', - TQcia2H2TU3WrFk9sKtdK9qCfkW8XirfPQ: 'JUSTSWAP-USDJ-TRX', - TLLBBiX3HqVZZsUQTBXgurA3pdw317PmjM: 'JUSTSWAP-HT-TRX', - TYN6Wh11maRfzgG7n5B6nM5VW1jfGs9chu: 'JUSTSWAP-WIN-TRX', - TUEYcyPAqc4hTg1fSuBCPc18vGWcJDECVw: 'JUSTSWAP-SUN-TRX', - TKAtLoCB529zusLfLVkGvLNis6okwjB7jf: 'JUSTSWAP-BTC-TRX', - TYukBQZ2XXCcRCReAUguyXncCWNY9CEiDQ: 'JUSTSWAP-JST-TRX', - TH2mEwTKNgtg8psR6Qx2RBUXZ48Lon1ygu: 'JUSTSWAP-WBTT-TRX' -}; diff --git a/crypto/blockchains/trx/ext/TronStakeUtils.ts b/crypto/blockchains/trx/ext/TronStakeUtils.ts deleted file mode 100644 index a223e58cd..000000000 --- a/crypto/blockchains/trx/ext/TronStakeUtils.ts +++ /dev/null @@ -1,216 +0,0 @@ -import BlocksoftExternalSettings from '@crypto/common/AirDAOExternalSettings'; -import BlocksoftPrettyNumbers from '@crypto/common/AirDAOPrettyNumbers'; -import BlocksoftBalances from '@crypto/actions/BlocksoftBalances/BlocksoftBalances'; -import TronUtils from '@crypto/blockchains/trx/ext/TronUtils'; - -import AirDAOAxios from '@crypto/common/AirDAOAxios'; -// import Log from '@app/services/Log/Log'; -import { BlocksoftTransfer } from '@crypto/actions/BlocksoftTransfer/BlocksoftTransfer'; -import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; - -interface Balance { - balanceAvailable: number; - frozen: number; - frozenOthers: number; - frozenEnergy: number; - frozenEnergyOthers: number; - frozenExpireTime: number; - frozenEnergyExpireTime: number; - prettyBalanceAvailable: string; - prettyFrozen: string; - prettyFrozenOthers: string; - prettyFrozenEnergy: string; - prettyFrozenEnergyOthers: string; - prettyVote: string; - diffLastStakeMinutes: number; -} - -interface UiParams { - walletHash: string; - address: string; - derivationPath: string; - cryptoValue: number; - type: string; - callback: () => void; -} - -const TronStakeUtils = { - async getVoteAddresses(): Promise { - return BlocksoftExternalSettings.getStatic('TRX_VOTE_BEST'); - }, - - async getPrettyBalance(address: string): Promise { - const balance = await BlocksoftBalances.setCurrencyCode('TRX') - .setAddress(address) - .getBalance('TronStakeUtils'); - if (!balance) { - return false; - } - const prettyBalanceAvailable = BlocksoftPrettyNumbers.setCurrencyCode( - 'TRX' - ).makePretty(balance.balanceAvailable); - const prettyFrozen = BlocksoftPrettyNumbers.setCurrencyCode( - 'TRX' - ).makePretty(balance.frozen); - const prettyFrozenOthers = BlocksoftPrettyNumbers.setCurrencyCode( - 'TRX' - ).makePretty(balance.frozenOthers); - const prettyFrozenEnergy = BlocksoftPrettyNumbers.setCurrencyCode( - 'TRX' - ).makePretty(balance.frozenEnergy); - const prettyFrozenEnergyOthers = BlocksoftPrettyNumbers.setCurrencyCode( - 'TRX' - ).makePretty(balance.frozenEnergyOthers); - const prettyVote = ( - parseFloat(prettyFrozen) + - parseFloat(prettyFrozenOthers) + - parseFloat(prettyFrozenEnergy) + - parseFloat(prettyFrozenEnergyOthers) - ) - .toString() - .split('.')[0]; - - const maxExpire = - balance.frozenEnergyExpireTime && - balance.frozenEnergyExpireTime > balance.frozenExpireTime - ? balance.frozenEnergyExpireTime - : balance.frozenExpireTime; - const diffLastStakeMinutes = - maxExpire > 0 - ? 24 * 3 * 60 - (maxExpire - new Date().getTime()) / 60000 - : -1; - - return { - ...balance, - prettyBalanceAvailable, - prettyFrozen, - prettyFrozenOthers, - prettyFrozenEnergy, - prettyFrozenEnergyOthers, - prettyVote, - diffLastStakeMinutes - }; - }, - - async sendVoteAll( - address: string, - derivationPath: string, - walletHash: string, - specialActionNeeded: string - ): Promise { - const { prettyVote, diffLastStakeMinutes, voteTotal } = - await TronStakeUtils.getPrettyBalance(address); - if ( - diffLastStakeMinutes === -1 && - specialActionNeeded === 'vote_after_unfreeze' - ) { - AirDAOCryptoLog.log( - 'TronStake.sendVoteAll ' + address + ' continue ' + diffLastStakeMinutes - ); - } else if (!diffLastStakeMinutes || diffLastStakeMinutes < 3) { - AirDAOCryptoLog.log( - 'TronStake.sendVoteAll ' + - address + - ' skipped vote1 by ' + - diffLastStakeMinutes - ); - return false; - } - if (!prettyVote || typeof prettyVote === 'undefined') { - AirDAOCryptoLog.log( - 'TronStake.sendVoteAll ' + address + ' skipped vote2' - ); - return false; - } else if (voteTotal * 1 === parseFloat(prettyVote)) { - if (diffLastStakeMinutes > 100) { - AirDAOCryptoLog.log( - 'TronStake.sendVoteAll ' + - address + - ' skipped vote3 ' + - voteTotal + - ' by ' + - diffLastStakeMinutes - ); - return true; // all done - } - AirDAOCryptoLog.log( - 'TronStake.sendVoteAll ' + address + ' skipped vote4 ' + voteTotal - ); - return false; - } - - AirDAOCryptoLog.log( - 'TronStake.sendVoteAll ' + - address + - ' started vote ' + - prettyVote + - ' by ' + - diffLastStakeMinutes - ); - - const voteAddress = await TronStakeUtils.getVoteAddresses(); - return TronStakeUtils._send( - '/wallet/votewitnessaccount', - { - owner_address: TronUtils.addressToHex(address), - votes: [ - { - vote_address: TronUtils.addressToHex(voteAddress), - vote_count: parseFloat(prettyVote) - } - ] - }, - 'vote ' + prettyVote + ' for ' + voteAddress, - { - walletHash, - address, - derivationPath, - type: 'vote', - cryptoValue: BlocksoftPrettyNumbers.setCurrencyCode('TRX').makeUnPretty( - parseFloat(prettyVote) - ), - callback: () => {} - } - ); - }, - - async _send( - shortLink: string, - params: any, - langMsg: string, - uiParams: UiParams - ): Promise { - const sendLink = BlocksoftExternalSettings.getStatic('TRX_SEND_LINK'); - const link = sendLink + shortLink; - const tmp = await AirDAOAxios.post(link, params); - let blockchainData; - - if (typeof tmp.data !== 'undefined') { - if (typeof tmp.data.raw_data_hex !== 'undefined') { - blockchainData = tmp.data; - } else { - // Log.log('TronStakeUtils._send no rawHex ' + link, params, tmp.data); - throw new Error(JSON.stringify(tmp.data)); - } - } else { - // Log.log('TronStakeUtils rawHex empty data ' + link, params); - throw new Error('Empty data'); - } - - const txData = { - currencyCode: 'TRX', - walletHash: uiParams.walletHash, - derivationPath: uiParams.derivationPath, - addressFrom: uiParams.address, - addressTo: '', - blockchainData - }; - - const result = await BlocksoftTransfer.sendTx(txData, { - selectedFee: { langMsg } - }); - return result; - } -}; - -export default TronStakeUtils; diff --git a/crypto/blockchains/trx/ext/TronUtils.ts b/crypto/blockchains/trx/ext/TronUtils.ts deleted file mode 100644 index 07381127a..000000000 --- a/crypto/blockchains/trx/ext/TronUtils.ts +++ /dev/null @@ -1,91 +0,0 @@ -/** - * @version 0.9 - */ -import * as elliptic from 'elliptic'; -import createHash from 'create-hash'; -import * as EthUtil from 'ethereumjs-util'; -import * as bs58 from 'bs58'; - -const ec: elliptic.ec = new elliptic.ec('secp256k1'); - -function byte2hexStr(byte: number): string { - if (typeof byte !== 'number') throw new Error('Input must be a number'); - if (byte < 0 || byte > 255) throw new Error('Input must be a byte'); - - // let hexByteMap = '0123456789ABCDEF'; - const hexString = byte.toString(16).toUpperCase(); - return hexString.length === 1 ? '0' + hexString : hexString; -} - -interface Signature { - r: Buffer; - s: Buffer; - recoveryParam: number; -} - -export default { - ECKeySign(hashBytes: Buffer, privateBytes: Buffer): string { - const key: elliptic.ec.KeyPair = ec.keyFromPrivate(privateBytes, 'bytes'); - const signature: Signature = key.sign(hashBytes); - const r: Buffer = signature.r; - const s: Buffer = signature.s; - const id: number = signature.recoveryParam; - - let rHex: string = r.toString('hex'); - while (rHex.length < 64) { - rHex = `0${rHex}`; - } - - let sHex: string = s.toString('hex'); - while (sHex.length < 64) { - sHex = `0${sHex}`; - } - - const idHex: string = byte2hexStr(id); - return rHex + sHex + idHex; - }, - - addressToHex(address: string): string { - if (address.substr(0, 2) === '41') { - return address; - } - const decoded: Buffer = bs58.decode(address.trim()); - return decoded.slice(0, 21).toString('hex'); - }, - - privHexToPubHex(privateHex: string): string { - const key: elliptic.ec.KeyPair = ec.keyFromPrivate(privateHex, 'hex'); - const pubkey: elliptic.ec.KeyPair = key.getPublic(); - const x: Buffer = pubkey.x; - const y: Buffer = pubkey.y; - - let xHex: string = x.toString('hex'); - while (xHex.length < 64) { - xHex = `0${xHex}`; - } - - let yHex: string = y.toString('hex'); - while (yHex.length < 64) { - yHex = `0${yHex}`; - } - - return `04${xHex}${yHex}`; - }, - - pubHexToAddressHex(pubHex: string): string { - if (pubHex.substr(0, 2) === '04') { - pubHex = '0x' + pubHex.substr(2); - } - return '41' + EthUtil.publicToAddress(pubHex).toString('hex'); - }, - - addressHexToStr(addressHex: string): string { - const one: string = createHash('sha256') - .update(addressHex, 'hex') - .digest('hex'); - const hash: Buffer = createHash('sha256').update(one, 'hex').digest(); - const checksum: Buffer = hash.slice(0, 4); // checkSum = the first 4 bytes of hash - const checkSummed: string = addressHex + checksum.toString('hex'); - return bs58.encode(Buffer.from(checkSummed, 'hex')); - } -}; diff --git a/crypto/blockchains/trx/providers/TrxSendProvider.ts b/crypto/blockchains/trx/providers/TrxSendProvider.ts deleted file mode 100644 index 3e666a536..000000000 --- a/crypto/blockchains/trx/providers/TrxSendProvider.ts +++ /dev/null @@ -1,214 +0,0 @@ -/** - * @version 0.41 - */ -// @ts-ignore -import { BlocksoftBlockchainTypes } from '@crypto/blockchains/BlocksoftBlockchainTypes'; -import DogeSendProvider from '@crypto/blockchains/doge/providers/DogeSendProvider'; -import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; - -import AirDAOAxios from '@crypto/common/AirDAOAxios'; -import BlocksoftExternalSettings from '@crypto/common/AirDAOExternalSettings'; -import config from '@constants/config'; - -export default class TrxSendProvider - extends DogeSendProvider - implements BlocksoftBlockchainTypes.SendProvider -{ - trxError(msg: string) { - if (config.debug.cryptoErrors) { - console.log(this._settings.currencyCode + ' TrxSendProvider ' + msg); - } - if ( - this._settings.currencyCode !== 'TRX' && - msg.indexOf('AccountResourceInsufficient') !== -1 - ) { - throw new Error('SERVER_RESPONSE_NOT_ENOUGH_FEE'); - } else if ( - msg.indexOf( - 'Validate TransferContract error, balance is not sufficient.' - ) !== -1 - ) { - throw new Error('SERVER_RESPONSE_NOTHING_TO_TRANSFER_FROM_ACTUAL_NODE'); - } else if (msg.indexOf('balance is not sufficient') !== -1) { - throw new Error('SERVER_RESPONSE_NOT_ENOUGH_FEE'); - } else if (msg.indexOf('account not exist') !== -1) { - throw new Error('SERVER_RESPONSE_NOT_ENOUGH_FEE'); - } else if (msg.indexOf('Amount must greater than 0') !== -1) { - throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_DUST'); - } else if ( - msg.indexOf('assetBalance must be greater than 0') !== -1 || - msg.indexOf('assetBalance is not sufficient') !== -1 - ) { - throw new Error('SERVER_RESPONSE_NOTHING_TO_TRANSFER_FROM_ACTUAL_NODE'); - } else { - throw new Error(msg); - } - } - - isResponseObject(obj: any): obj is { data: any } { - return typeof obj === 'object' && obj !== null && 'data' in obj; - } - - async _sendTx( - tx: any, - subtitle: string, - txRBF: any, - logData: any - ): Promise<{ transactionHash: string; logData: any }> { - await AirDAOCryptoLog.log( - this._settings.currencyCode + - ' TrxSendProvider._sendTx ' + - subtitle + - ' started ', - logData - ); - - const sendLink = BlocksoftExternalSettings.getStatic('TRX_SEND_LINK'); - const link = sendLink + '/wallet/broadcasttransaction'; - if (config.debug.cryptoErrors) { - console.log( - new Date().toISOString() + - ' ' + - this._settings.currencyCode + - ' TrxSendProvider._sendTx ' + - subtitle + - ' started check ' - ); - } - logData = await this._check(tx.raw_data_hex, subtitle, txRBF, logData); - if (config.debug.cryptoErrors) { - AirDAOCryptoLog.log( - new Date().toISOString() + - ' ' + - this._settings.currencyCode + - ' TrxSendProvider._sendTx ' + - subtitle + - ' ended check ' - ); - } - - let send: any; - try { - send = await AirDAOAxios.post(link, tx); - } catch (e: any) { - if (config.debug.cryptoErrors) { - console.log( - this._settings.currencyCode + - ' TrxSendProvider._sendTx broadcast error ' + - e.message - ); - } - } - - // Explicit type check for 'send' - if (typeof send !== 'boolean' && send.data) { - if (typeof send.data.code !== 'undefined') { - if (send.data.code === 'BANDWITH_ERROR') { - throw new Error('SERVER_RESPONSE_BANDWITH_ERROR_TRX'); - } else if (send.data.code === 'SERVER_BUSY') { - throw new Error('SERVER_RESPONSE_NOT_CONNECTED'); - } - } - - if (typeof send.data.Error !== 'undefined') { - await AirDAOCryptoLog.log( - this._settings.currencyCode + - ' TrxSendProvider._sendTx error ' + - send.data.Error - ); - throw new Error(send.data.Error); - } - - if (typeof send.data.result === 'undefined') { - if (typeof send.data.message !== 'undefined') { - let msg: string | false = false; - try { - const buf = Buffer.from(send.data.message, 'hex'); - // @ts-ignore - msg = buf.toString(''); - } catch (e) { - // do nothing - } - await AirDAOCryptoLog.log( - this._settings.currencyCode + ' TrxSendProvider._sendTx msg ' + msg - ); - if (msg) { - send.data.decoded = msg; - this.trxError(msg); - } - } - this.trxError('no transaction result ' + JSON.stringify(send.data)); - } else { - if (send.data.result !== true) { - this.trxError( - 'transaction result is false ' + JSON.stringify(send.data) - ); - } - } - } else { - throw new Error('SERVER_RESPONSE_NOT_CONNECTED'); - } - - return { transactionHash: tx.txID, logData }; - } - - async sendTx( - tx: any, - subtitle: string, - txRBF: any, - logData: any - ): Promise<{ transactionHash: string; transactionJson: any; logData: any }> { - await AirDAOCryptoLog.log( - this._settings.currencyCode + - ' TrxSendProvider.sendTx ' + - subtitle + - ' started ', - logData - ); - - let send; - let transactionHash; - try { - send = await this._sendTx(tx, subtitle, txRBF, logData); - transactionHash = send.transactionHash; - } catch (e: any) { - if (config.debug.cryptoErrors) { - console.log( - this._settings.currencyCode + ' TrxSendProvider.sendTx error ', - e - ); - } - try { - logData.error = e.message; - await this._checkError(tx.raw_data_hex, subtitle, txRBF, logData); - } catch (e2: any) { - if (config.debug.cryptoErrors) { - console.log( - this._settings.currencyCode + - ' TrxSendProvider.send proxy error errorTx ' + - e.message - ); - } - await AirDAOCryptoLog.log( - this._settings.currencyCode + - ' TrxSendProvider.send proxy error errorTx ' + - e2.message - ); - } - throw e; - } - - try { - logData = await this._checkSuccess( - transactionHash, - tx.raw_data_hex, - subtitle, - txRBF, - logData - ); - } catch (e: any) { - throw new Error(e.message + ' in _checkSuccess wrapped TRX'); - } - return { transactionHash, transactionJson: {}, logData }; - } -} diff --git a/crypto/blockchains/usdt/UsdtScannerProcessor.ts b/crypto/blockchains/usdt/UsdtScannerProcessor.ts deleted file mode 100644 index 82c22cb4c..000000000 --- a/crypto/blockchains/usdt/UsdtScannerProcessor.ts +++ /dev/null @@ -1,329 +0,0 @@ -/** - * @version 0.5 - */ -import BlocksoftUtils from '../../common/AirDAOUtils'; -import AirDAOAxios from '../../common/AirDAOAxios'; -import AirDAOCryptoLog from '../../common/AirDAOCryptoLog'; -import AirDAODispatcher from '../AirDAODispatcher'; - -const USDT_API = 'https://microscanners.trustee.deals/usdt'; // https://microscanners.trustee.deals/usdt/1CmAoxq8BTxANRDwheJUpaGy6ngWNYX85 -const USDT_API_MASS = 'https://microscanners.trustee.deals/balanceMass'; - -const CACHE_VALID_TIME = 30000; // 30 seconds -const CACHE: { - [address: string]: { data: any; time: number; provider: string }; -} = {}; - -export default class UsdtScannerProcessor { - /** - * @type {number} - */ - lastBlock = 0; - - /** - * @type {number} - * @private - */ - private _blocksToConfirm = 1; - - /** - * @type {boolean|BtcScannerProcessor} - * @private - */ - private _btcProvider: boolean | BtcScannerProcessor = false; - - /** - * @param address - * @returns {Promise} - * @private - */ - private async _get(address: string): Promise { - const now = new Date().getTime(); - if ( - typeof CACHE[address] !== 'undefined' && - now - CACHE[address].time < CACHE_VALID_TIME - ) { - CACHE[address].provider = 'usdt-cache'; - return CACHE[address]; - } - const link = `${USDT_API}/${address}`; - const res = await AirDAOAxios.getWithoutBraking(link); - if (!res || typeof res.data === 'undefined' || !res.data) { - return false; - } - if (typeof res.data.status === 'undefined') { - throw new Error( - 'UsdtScannerProcessor._get bad status loaded for address ' + link - ); - } - if ( - typeof res.data.data === 'undefined' || - typeof res.data.data.balance === 'undefined' - ) { - throw new Error( - 'UsdtScannerProcessor._get nothing loaded for address ' + link - ); - } - if (typeof CACHE[address] !== 'undefined') { - if (CACHE[address].data.block > res.data.data.block) { - return false; - } - } - CACHE[address] = { - data: res.data.data, - time: now, - provider: 'usdt' - }; - return CACHE[address]; - } - - /** - * @param address - * @returns {Promise} - * @private - */ - private async _getMass(address: string): Promise { - const now = new Date().getTime(); - - // mass ask - const link = `${USDT_API_MASS}`; - const res = await AirDAOAxios.postWithoutBraking(link, address); - - if (!res || typeof res.data === 'undefined') { - return false; - } - return { - data: res.data.data, - time: now, - provider: 'usdt' - }; - } - - /** - * @param {string} address - * @return {Promise<{int:balance, int:provider}>} - */ - public async getBalanceBlockchain(address: string): Promise< - | { - balance: number; - provider: string; - time: number; - unconfirmed: number; - balanceScanBlock: number; - } - | false - > { - if (typeof address === 'object') { - AirDAOCryptoLog.log( - 'UsdtScannerProcessor.getBalance started MASS ' + - JSON.stringify(address) - ); - return this._getMass(address); - } - - AirDAOCryptoLog.log('UsdtScannerProcessor.getBalance started ' + address); - const tmp = await this._get(address); - if (typeof tmp === 'undefined' || !tmp || typeof tmp.data === 'undefined') { - AirDAOCryptoLog.log('UsdtScannerProcessor.getBalance bad tmp ', tmp); - return false; - } - if ( - !tmp.data || - typeof tmp.data.balance === 'undefined' || - tmp.data.balance === false - ) { - AirDAOCryptoLog.log( - 'UsdtScannerProcessor.getBalance bad tmp.data ', - tmp.data - ); - return false; - } - const balance: number = tmp.data.balance; - AirDAOCryptoLog.log( - `UsdtScannerProcessor.getBalance finished - ${address + ' => ' + balance}` - ); - return { - balance, - provider: tmp.provider, - time: tmp.time, - unconfirmed: 0, - balanceScanBlock: tmp.data.block - }; - } - - /** - * @param {string} scanData.account.address - * @return {Promise} - */ - public async getTransactionsBlockchain( - scanData: { account: { address: string } }, - source = '' - ): Promise { - const address = scanData.account.address.trim(); - AirDAOCryptoLog.log( - 'UsdtScannerProcessor.getTransactions started ' + address - ); - let tmp = await this._get(address); - if (!tmp || typeof tmp.data === 'undefined') { - AirDAOCryptoLog.log('UsdtScannerProcessor.getTransactions bad tmp ', tmp); - return []; - } - if (!tmp.data) { - AirDAOCryptoLog.log( - 'UsdtScannerProcessor.getTransactions bad tmp.data ', - tmp.data - ); - return []; - } - - tmp = tmp.data; - if (typeof tmp.data !== 'undefined') { - tmp = tmp.data; // wtf but ok to support old wallets - } - if (typeof tmp.txs === 'undefined') { - throw new Error('Undefined txs ' + JSON.stringify(tmp)); - } - - const transactions: UnifiedTransaction[] = []; - if (tmp.block > this.lastBlock) { - this.lastBlock = tmp.block; - } - let tx; - const unique: { [transactionHash: string]: number } = {}; - if (tmp.txs && tmp.txs.length > 0) { - for (tx of tmp.txs) { - const transaction = await this._unifyTransaction(address, tx); - transactions.push(transaction); - unique[transaction.transactionHash] = 1; - } - } - let btcTxs: UnifiedTransaction[] | false = false; - try { - if (!this._btcProvider) { - this._btcProvider = (await new AirDAODispatcher().getScannerProcessor({ - currencyCode: 'BTC' - })) as BtcScannerProcessor; - } - btcTxs = await this._btcProvider.getTransactionsBlockchain( - scanData, - source + ' UsdtScannerProcessor' - ); - } catch (e: any) { - throw e; - } - if (btcTxs && btcTxs.length > 0) { - for (tx of btcTxs) { - if (typeof unique[tx.transactionHash] !== 'undefined') continue; - transactions.push({ - addressAmount: tx.addressAmount, - addressFrom: tx.addressFrom, - addressTo: tx.addressTo, - blockHash: tx.blockHash, - blockNumber: tx.blockNumber, - inputValue: tx.inputValue, - transactionFee: tx.transactionFee, - blockConfirmations: tx.blockConfirmations, - blockTime: tx.blockTime, - transactionDirection: tx.transactionDirection, - transactionHash: tx.transactionHash, - transactionStatus: tx.transactionStatus - }); - } - } - AirDAOCryptoLog.log( - 'UsdtScannerProcessor.getTransactions finished ' + - address + - ' total: ' + - transactions.length - ); - return transactions; - } - - /** - * - * @param {string} address - * @param {Object} transaction - * @param {string} transaction.block_number: 467352, - * @param {string} transaction.transaction_block_hash: '0000000000000000018e86423804e917c75348090419a46e506bc2d4818c2827', - * @param {string} transaction.transaction_hash: '7daaa478c829445c967d4607345227286a23acd20f5bc80709e418d0e286ecf1', - * @param {string} transaction.transaction_txid: '7daaa478c829445c967d4607345227286a23acd20f5bc80709e418d0e286ecf1', - * @param {string} transaction.from_address: '1GYmxyavRvjCMsmfDR2uZLMsCPoFNYw9zM', - * @param {string} transaction.to_address: '1Po1oWkD2LmodfkBYiAktwh76vkF93LKnh', - * @param {string} transaction.amount: 0.744019, - * @param {string} transaction.fee: 0.0008, - * @param {string} transaction.custom_type: '', - * @param {string} transaction.custom_valid: '', - * @param {string} transaction.created_time: '2017-05-20T22:28:15.000Z', - * @param {string} transaction.updated_time: null, - * @param {string} transaction.removed_time: null, - * @param {string} transaction._removed: 0, - * @return {UnifiedTransaction} - * @private - */ - private async _unifyTransaction( - address: string, - transaction: any - ): Promise { - const confirmations: number = this.lastBlock - transaction.block_number; - let transactionStatus: 'new' | 'success' | 'confirming' = 'new'; - if (confirmations >= this._blocksToConfirm) { - transactionStatus = 'success'; - } else if (confirmations > 0) { - transactionStatus = 'confirming'; - } - const tx: UnifiedTransaction = { - transactionHash: transaction.transaction_txid, - blockHash: transaction.transaction_block_hash, - blockNumber: +transaction.block_number, - blockTime: transaction.created_time, - blockConfirmations: confirmations, - transactionDirection: - address.toLowerCase() === transaction.from_address.toLowerCase() - ? 'outcome' - : 'income', - addressFrom: - transaction.from_address === address ? '' : transaction.from_address, - addressTo: - transaction.to_address === address ? '' : transaction.to_address, - addressAmount: transaction.amount, - transactionStatus: - transaction.custom_valid.toString() === '1' && - transaction._removed.toString() === '0' - ? transactionStatus - : 'fail', - transactionFee: BlocksoftUtils.toSatoshi(transaction.fee), - inputValue: transaction.custom_type - }; - if (tx.addressTo === '' && tx.addressFrom === '') { - tx.transactionDirection = 'self'; - tx.addressAmount = 0; - } - return tx; - } -} - -type UnifiedTransaction = { - transactionHash: string; - blockHash: string; - blockNumber: number; - blockTime: string; - blockConfirmations: number; - transactionDirection: 'income' | 'outcome' | 'self'; - addressFrom: string; - addressTo: string; - addressAmount: string; - transactionStatus: 'new' | 'success' | 'confirming' | 'fail'; - transactionFee: number; - inputValue: string; -}; - -/** - * BtcScannerProcessor interface for TypeScript - */ -interface BtcScannerProcessor { - getTransactionsBlockchain( - scanData: { account: { address: string } }, - source: string - ): Promise; -} diff --git a/crypto/blockchains/usdt/UsdtTransferProcessor.ts b/crypto/blockchains/usdt/UsdtTransferProcessor.ts deleted file mode 100644 index ca559fe2c..000000000 --- a/crypto/blockchains/usdt/UsdtTransferProcessor.ts +++ /dev/null @@ -1,138 +0,0 @@ -/** - * @version 0.20 - */ -import { AirDAOBlockchainTypes } from '@crypto/blockchains/AirDAOBlockchainTypes'; -import BtcUnspentsProvider from '../btc/providers/BtcUnspentsProvider'; -import DogeSendProvider from '../doge/providers/DogeSendProvider'; -import UsdtTxInputsOutputs from './tx/UsdtTxInputsOutputs'; -import UsdtTxBuilder from './tx/UsdtTxBuilder'; -import BtcNetworkPrices from '../btc/basic/BtcNetworkPrices'; -import BtcTransferProcessor from '../btc/BtcTransferProcessor'; -import AirDAOCryptoLog from '../../common/AirDAOCryptoLog'; -// @ts-ignore -import DaemonCache from '../../../app/daemons/DaemonCache'; - -export default class UsdtTransferProcessor - extends BtcTransferProcessor - implements AirDAOBlockchainTypes.TransferProcessor -{ - _trezorServerCode = 'BTC_TREZOR_SERVER'; - - _builderSettings: AirDAOBlockchainTypes.BuilderSettings = { - minOutputDustReadable: 0.000001, - minChangeDustReadable: 0.000001, - feeMaxForByteSatoshi: 1000, // for tx builder - feeMaxAutoReadable2: 0.01, // for fee calc, - feeMaxAutoReadable6: 0.005, // for fee calc - feeMaxAutoReadable12: 0.001, // for fee calc - changeTogether: false, - minRbfStepSatoshi: 50, - minSpeedUpMulti: 1.5 - }; - - _initProviders() { - if (this._initedProviders) return false; - this.unspentsProvider = new BtcUnspentsProvider( - this._settings, - this._trezorServerCode - ); - this.sendProvider = new DogeSendProvider( - this._settings, - this._trezorServerCode - ); - this.txPrepareInputsOutputs = new UsdtTxInputsOutputs( - this._settings, - this._builderSettings - ); - this.txBuilder = new UsdtTxBuilder(this._settings, this._builderSettings); - this.networkPrices = new BtcNetworkPrices(); - this._initedProviders = true; - } - - async checkTransferHasError( - data: AirDAOBlockchainTypes.CheckTransferHasErrorData - ): Promise { - // @ts-ignore - const tmp = await DaemonCache.getCacheAccount(data.walletHash, 'BTC'); - if (tmp.balance * 1 > 0) { - return { isOk: true }; - } else { - return { - isOk: false, - code: 'TOKEN', - parentBlockchain: 'Bitcoin', - parentCurrency: 'BTC' - }; - } - } - - checkSendAllModal(data: { currencyCode: any }): boolean { - return true; - } - - async getFeeRate( - data: AirDAOBlockchainTypes.TransferData, - privateData: AirDAOBlockchainTypes.TransferPrivateData, - additionalData: AirDAOBlockchainTypes.TransferAdditionalData = {} - ): Promise { - const tmpData = { ...data }; - tmpData.isTransferAll = false; - // @ts-ignore - AirDAOCryptoLog.log( - this._settings.currencyCode + ' UsdtTxProcessor.getFeeRate started' - ); - const result = await super.getFeeRate(tmpData, privateData, additionalData); - for (const fee of result.fees) { - fee.amountForTx = data.amount; - } - return result; - } - - async getTransferAllBalance( - data: AirDAOBlockchainTypes.TransferData, - privateData: AirDAOBlockchainTypes.TransferPrivateData, - additionalData: AirDAOBlockchainTypes.TransferAdditionalData = {} - ): Promise { - const balance = data.amount; - // @ts-ignore - AirDAOCryptoLog.log( - this._settings.currencyCode + - ` UsdtTransferProcessor.getTransferAllBalance - ${data.addressFrom + ' => ' + balance}` - ); - // noinspection EqualityComparisonWithCoercionJS - if (balance === '0') { - return { - selectedTransferAllBalance: '0', - selectedFeeIndex: -1, - fees: [], - countedForBasicBalance: '0', - countedTime: new Date().getTime() - }; - } - const fees = await this.getFeeRate(data, privateData, additionalData); - if (!fees || fees.selectedFeeIndex < 0) { - return { - selectedTransferAllBalance: balance, - selectedFeeIndex: -2, - fees: [], - countedForBasicBalance: balance, - countedTime: new Date().getTime() - }; - } - return { - ...fees, - selectedTransferAllBalance: balance - }; - } - - async sendTx( - data: AirDAOBlockchainTypes.TransferData, - privateData: AirDAOBlockchainTypes.TransferPrivateData, - uiData: AirDAOBlockchainTypes.TransferUiData - ): Promise { - const result = await super.sendTx(data, privateData, uiData); - result.transactionFeeCurrencyCode = 'BTC'; - return result; - } -} diff --git a/crypto/blockchains/usdt/tx/UsdtTxBuilder.ts b/crypto/blockchains/usdt/tx/UsdtTxBuilder.ts deleted file mode 100644 index ab27e6d21..000000000 --- a/crypto/blockchains/usdt/tx/UsdtTxBuilder.ts +++ /dev/null @@ -1,56 +0,0 @@ -/** - * @version 0.20 - */ -import { AirDAOBlockchainTypes } from '@crypto/blockchains/AirDAOBlockchainTypes'; -import BtcTxBuilder from '../../btc/tx/BtcTxBuilder'; -import AirDAOCryptoLog from '../../../common/AirDAOCryptoLog'; - -import { TransactionBuilder, script, opcodes } from 'bitcoinjs-lib'; - -const USDT_TOKEN_ID = 31; - -function toPaddedHexString(num: number, len: number) { - const str = num.toString(16); - return '0'.repeat(len - str.length) + str; -} - -function createOmniSimpleSend(amountInUSD: string, propertyID = USDT_TOKEN_ID) { - AirDAOCryptoLog.log('UsdtTxBuilder.createOmniSimpleSend started'); - const simpleSend = [ - '6f6d6e69', // omni - '0000', // tx type - '0000', // version - toPaddedHexString(propertyID, 8), - // @ts-ignore - toPaddedHexString(Math.floor(amountInUSD * 100000000), 16) - ].join(''); - - return script.compile([opcodes.OP_RETURN, Buffer.from(simpleSend, 'hex')]); -} - -export default class UsdtTxBuilder - extends BtcTxBuilder - implements AirDAOBlockchainTypes.TxBuilder -{ - _getRawTxAddOutput( - txb: TransactionBuilder, - output: AirDAOBlockchainTypes.OutputTx - ): void { - if ( - typeof output.tokenAmount !== 'undefined' && - output.tokenAmount && - output.tokenAmount !== '0' - ) { - const omniOutput = createOmniSimpleSend(output.tokenAmount); - txb.addOutput(omniOutput, 0); - } else { - if ( - typeof output.amount !== 'undefined' && - output.amount.toString() === '0' - ) { - output.amount = '546'; - } - super._getRawTxAddOutput(txb, output); - } - } -} diff --git a/crypto/blockchains/usdt/tx/UsdtTxInputsOutputs.ts b/crypto/blockchains/usdt/tx/UsdtTxInputsOutputs.ts deleted file mode 100644 index 2520eb4a7..000000000 --- a/crypto/blockchains/usdt/tx/UsdtTxInputsOutputs.ts +++ /dev/null @@ -1,368 +0,0 @@ -/** - * @version 0.20 - */ -import { AirDAOBlockchainTypes } from '../../AirDAOBlockchainTypes'; -import BtcTxInputsOutputs from '../../btc/tx/BtcTxInputsOutputs'; -import BlocksoftBN from '../../../common/AirDAOBN'; -import BlocksoftUtils from '../../../common/AirDAOUtils'; -import AirDAOCryptoLog from '../../../common/AirDAOCryptoLog'; -// @ts-ignore -import DaemonCache from '../../../../app/daemons/DaemonCache'; - -export default class UsdtTxInputsOutputs - extends BtcTxInputsOutputs - implements AirDAOBlockchainTypes.TxInputsOutputs -{ - DUST_FIRST_TRY = 546; - SIZE_FOR_BASIC = 442; - - _coinSelectTargets( - data: AirDAOBlockchainTypes.TransferData, - unspents: AirDAOBlockchainTypes.UnspentTx[], - feeForByte: string, - multiAddress: string[], - subtitle: string - ) { - const targets = [ - { address: data.addressTo, value: 0, logType: 'FOR_USDT_AMOUNT' }, - { - address: data.addressTo, - value: this.DUST_FIRST_TRY, - logType: 'FOR_USDT_BTC_OUTPUT' - } - ]; - return targets; - } - - _usualTargets( - data: AirDAOBlockchainTypes.TransferData, - unspents: AirDAOBlockchainTypes.UnspentTx[] - ) { - const basicWishedAmountBN = new BlocksoftBN(0); - const wishedAmountBN = new BlocksoftBN(basicWishedAmountBN); - - const outputs = []; - - outputs.push({ - to: data.addressTo, - amount: 0, - logType: 'FOR_USDT_AMOUNT' - }); - - return { - multiAddress: [], - basicWishedAmountBN, - wishedAmountBN, - outputs - }; - } - - _addressForChange(data: AirDAOBlockchainTypes.TransferData): string { - return data.addressFrom; - } - - async getInputsOutputs( - data: AirDAOBlockchainTypes.TransferData, - unspents: AirDAOBlockchainTypes.UnspentTx[], - feeToCount: { - feeForByte?: string; - feeForAll?: string; - autoFeeLimitReadable?: string | number; - }, - additionalData: AirDAOBlockchainTypes.TransferAdditionalData, - subtitle = 'default' - ): Promise { - let res = await super._getInputsOutputs( - data, - unspents, - feeToCount, - additionalData, - subtitle + ' usdted' - ); - let inputIsFound = false; - const newInputs = []; - const oldInputs = []; - let addressFromUsdtOutputs = 0; - let newInputAdded = false; - for (const input of res.inputs) { - if (input.address === data.addressFrom) { - if (!inputIsFound) { - newInputs.push(input); - } else { - oldInputs.push(input); - } - inputIsFound = true; - addressFromUsdtOutputs++; - } else { - oldInputs.push(input); - } - } - if (!inputIsFound) { - for (const unspent of unspents) { - if (unspent.address === data.addressFrom) { - if (!inputIsFound) { - newInputs.push(unspent); - newInputAdded = unspent; - } - inputIsFound = true; - addressFromUsdtOutputs++; - } - } - } - for (const input of oldInputs) { - newInputs.push(input); - } - if (newInputAdded) { - let changeIsFound = false; - for (const output of res.outputs) { - if (typeof output.isChange !== 'undefined' && output.isChange) { - output.amount = BlocksoftUtils.add( - output.amount, - newInputAdded.value - ); - changeIsFound = true; - } - } - if (!changeIsFound && newInputAdded.value !== '546') { - res.outputs.push({ - to: data.addressFrom, - amount: newInputAdded.value.toString(), - isChange: true - }); - } - } - const tmp = - typeof additionalData.balance !== 'undefined' && additionalData.balance - ? { balance: additionalData.balance } - : DaemonCache.getCacheAccountStatic(data.walletHash, 'USDT'); - let needOneOutput = false; - if (tmp.balance > 0) { - const diff = BlocksoftUtils.diff(tmp.balance, data.amount); - AirDAOCryptoLog.log( - 'USDT addressFromUsdtOutputs = ' + - addressFromUsdtOutputs + - ' balance ' + - tmp.balance + - ' diff ' + - diff + - '>0=' + - (diff > 0 ? 'true' : 'false') - ); - if (addressFromUsdtOutputs < 2 && diff > 0) { - needOneOutput = true; - } - } - if ( - res.inputs.length === 0 && - (!inputIsFound || newInputAdded.value === '546') - ) { - throw new Error('SERVER_RESPONSE_NOT_ENOUGH_FEE_JUST_DUST'); - } - res.inputs = newInputs; - if (res.inputs.length === 0 || !inputIsFound) { - throw new Error('SERVER_RESPONSE_LEGACY_BALANCE_NEEDED_USDT'); - } - - const totalOuts = res.outputs.length; - if (totalOuts === 0) { - const newRes = JSON.parse(JSON.stringify(res)); - newRes.outputs = []; - if ( - needOneOutput && - (res.inputs.length > 1 || - res.inputs[0].value * 1 >= this.DUST_FIRST_TRY * 2) - ) { - newRes.outputs.push({ - isUsdt: true, - amount: this.DUST_FIRST_TRY.toString(), - to: data.addressFrom, - logType: 'FOR_LEGACY_USDT_KEEP' - }); - } - newRes.outputs.push({ - isUsdt: true, - amount: this.DUST_FIRST_TRY.toString(), - to: data.addressTo, - logType: 'FOR_USDT_AMOUNT' - }); - newRes.outputs.push({ - isUsdt: true, - tokenAmount: data.amount, - amount: '0', - to: data.addressTo, - logType: 'FOR_USDT_BTC_OUTPUT' - }); - newRes.countedFor = 'USDT'; - return newRes; - } else { - AirDAOCryptoLog.log( - 'UsdtTxInputsOutputs ' + - data.addressFrom + - ' => ' + - data.addressTo + - ' old outputs ' + - JSON.stringify(res.outputs) + - ' needOneOutput ' + - needOneOutput - ? 'true' - : 'false' - ); - res = this._innerResort( - res, - needOneOutput, - data.addressFrom, - data.addressTo, - data.amount - ); - AirDAOCryptoLog.log( - 'UsdtTxInputsOutputs ' + - data.addressFrom + - ' => ' + - data.addressTo + - ' new outputs ' + - JSON.stringify(res.outputs) - ); - res.countedFor = 'USDT'; - return res; - } - } - - _innerResort( - res: any, - needOneOutput: boolean, - addressFrom: string, - addressTo: string, - amount: string - ) { - const totalOuts = res.outputs.length; - - res.outputs = this._innerToUp(res.outputs, addressTo); - if (res.outputs[0].amount !== '0') { - if (totalOuts > 1) { - if (res.outputs[0].to !== addressTo) { - throw new Error( - 'usdt addressTo is invalid1.1 ' + JSON.stringify(res.outputs) - ); - } else if (res.outputs[1].to !== res.outputs[0].to) { - throw new Error( - 'usdt addressTo is invalid1.2 ' + JSON.stringify(res) - ); - } - if (res.outputs[1].to !== res.outputs[0].to) { - throw new Error( - 'usdt addressTo is invalid1.2 ' + JSON.stringify(res.outputs) - ); - } - res.outputs[1].amount = BlocksoftUtils.add( - res.outputs[0].amount, - res.outputs[1].amount - ).toString(); - } else { - if (res.outputs[0].to !== addressTo) { - throw new Error( - 'usdt addressTo is invalid2 ' + JSON.stringify(res.outputs) - ); - } - res.outputs[1] = JSON.parse(JSON.stringify(res.outputs[0])); - } - res.outputs[1].isChange = true; - res.outputs[1].isUsdt = true; - res.outputs[0].isChange = false; - res.outputs[0].isUsdt = true; - res.outputs[0].tokenAmount = amount; - res.outputs[1].tokenAmount = '0'; - res.outputs[0].amount = '0'; - } else { - res.outputs[0].isUsdt = true; - res.outputs[0].tokenAmount = amount; - res.outputs[0].amount = '0'; - if (totalOuts > 1) { - if (res.outputs[1].to !== addressTo) { - throw new Error( - 'usdt addressTo is invalid3 ' + JSON.stringify(res.outputs) - ); - } - res.outputs[1].isUsdt = true; - } else { - throw new Error( - 'usdt addressTo is empty ' + JSON.stringify(res.outputs) - ); - } - } - const newOutputs = []; - if (needOneOutput) { - if ( - typeof res.outputs[2] === 'undefined' || - res.outputs[2].to !== addressFrom - ) { - newOutputs.push({ - isUsdt: true, - amount: this.DUST_FIRST_TRY.toString(), - to: addressFrom, - logType: 'FOR_LEGACY_USDT_KEEP3' - }); - } - } - for (let i = res.outputs.length - 1; i >= 0; i--) { - newOutputs.push(res.outputs[i]); - } - res.outputs = newOutputs; - return res; - } - - _innerToUp(outputs: any, addressTo: string) { - let result = []; - for (const output of outputs) { - if (output.to === addressTo) { - result.push(output); - } - } - if (result.length === 1) { - let amount = result[0].amount.toString(); - if ( - amount === '0' || - BlocksoftUtils.diff(amount, this.DUST_FIRST_TRY.toString()) - .toString() - .indexOf('-') !== -1 - ) { - amount = this.DUST_FIRST_TRY.toString(); - } - result = []; - result.push({ - isUsdt: true, - tokenAmount: '?', - amount: '0', - to: addressTo, - logType: 'FOR_USDT_BTC_OUTPUT1' - }); - result.push({ - isUsdt: true, - amount, - to: addressTo, - logType: 'FOR_USDT_AMOUNT1' - }); - } else if (result.length === 0) { - result.push({ - isUsdt: true, - tokenAmount: '?', - amount: '0', - to: addressTo, - logType: 'FOR_USDT_BTC_OUTPUT2' - }); - result.push({ - isUsdt: true, - amount: this.DUST_FIRST_TRY.toString(), - to: addressTo, - logType: 'FOR_USDT_AMOUNT2' - }); - } - for (const output of outputs) { - if (output.to !== addressTo) { - result.push(output); - } - } - // @ts-ignore - AirDAOCryptoLog.log('USDT addressTo upTo', result); - return result; - } -} diff --git a/crypto/blockchains/vet/VetScannerProcessor.ts b/crypto/blockchains/vet/VetScannerProcessor.ts deleted file mode 100644 index c52ef8ec9..000000000 --- a/crypto/blockchains/vet/VetScannerProcessor.ts +++ /dev/null @@ -1,348 +0,0 @@ -/** - * @version 0.5 - * https://docs.vechain.org/thor/get-started/api.html - */ -import AirDAOCryptoLog from '../../common/AirDAOCryptoLog'; -import AirDAOAxios from '@crypto/common/AirDAOAxios'; -import BlocksoftUtils from '@crypto/common/AirDAOUtils'; - -const API_PATH = 'https://sync-mainnet.vechain.org'; -const CACHE_VALID_TIME = 30000; // 30 seconds -const CACHE: { - [address: string]: { - balance: string; - energy: string; - provider: string; - time: number; - }; -} = {}; - -export default class VetScannerProcessor { - private _settings: any; - private _lastBlock: number; - - constructor(settings: any) { - this._settings = settings; - this._lastBlock = 9243725; - } - - private async _get(_address: string) { - const address = _address.toLowerCase(); - - const now = new Date().getTime(); - if ( - typeof CACHE[address] !== 'undefined' && - now - CACHE[address].time < CACHE_VALID_TIME - ) { - CACHE[address].provider = 'cache'; - return CACHE[address]; - } - - const link = API_PATH + '/accounts/' + address; - const res = await AirDAOAxios.getWithoutBraking(link); - if ( - !res || - typeof res.data === 'undefined' || - typeof res.data.balance === 'undefined' - ) { - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' VeChain ScannerProcessor getBalanceBlockchain ' + - link + - ' error ', - res - ); - return false; - } - - CACHE[address] = { - balance: res.data.balance, - energy: res.data.energy, - provider: 'loaded', - time: now - }; - return CACHE[address]; - } - - /** - * https://docs.vechain.org/thor/get-started/api.html#get-accounts-address - * https://vethor-node.vechain.com/doc/swagger-ui/#/ - * @param {string} address - * @return {Promise<{balance, provider}>} - */ - async getBalanceBlockchain( - address: string - ): Promise< - { balance: string; unconfirmed: number; provider: string } | false - > { - address = address.trim(); - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' VeChain ScannerProcessor getBalanceBlockchain address ' + - address - ); - - const res = await this._get(address); - if (!res) { - return false; - } - try { - const hex = - this._settings.currencyCode === 'VET' ? res.balance : res.energy; - const balance = BlocksoftUtils.hexToDecimalBigger(hex); - return { balance, unconfirmed: 0, provider: 'vethor-node.vechain.com' }; - } catch (e: any) { - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' VeChain ScannerProcessor getBalanceBlockchain address ' + - address + - ' balance ' + - JSON.stringify(res) + - ' to hex error ' + - e.message - ); - } - return false; - } - - /** - * https://docs.vechain.org/thor/get-started/api.html#post-logs-transfer - * https://github.com/trustwallet/blockatlas/blob/696fb97b7b3197a7da4bb692122a8028ea4e07cf/platform/vechain/client.go - * @param {string} scanData.account.address - * @return {Promise<[UnifiedTransaction]>} - */ - async getTransactionsBlockchain(scanData: { - account: { address: string }; - }): Promise { - const address = scanData.account.address.trim(); - - if (this._settings.currencyCode === 'VET') { - try { - const linkNumber = API_PATH + '/blocks/best'; - const resultNumber = await AirDAOAxios.get(linkNumber); - if ( - resultNumber && - typeof resultNumber.data !== 'undefined' && - typeof resultNumber.data.number !== 'undefined' - ) { - const tmp = resultNumber.data.number * 1; - if (tmp > this._lastBlock) { - this._lastBlock = tmp; - } - } - } catch (e: any) { - AirDAOCryptoLog.log( - 'VetScannerProcessor.getTransactions lastBlock ' + e.message - ); - } - - const link = API_PATH + '/logs/transfer'; - const result = await AirDAOAxios.post(link, { - options: { - offset: 0, - limit: 100 - }, - criteriaSet: [ - { - txOrigin: address - }, - { - sender: address - }, - { - recipient: address - } - ], - order: 'desc' - }); - - if (!result.data || result.data.length === 0) return false; - - const transactions = await this._unifyTransactions(address, result.data); - AirDAOCryptoLog.log( - 'VetScannerProcessor.getTransactions finished ' + address - ); - return transactions; - } else { - const link = API_PATH + '/logs/event'; - const tokenHex = - '0x000000000000000000000000' + address.toLowerCase().substr(2); - const result = await AirDAOAxios.post(link, { - options: { - offset: 0, - limit: 100 - }, - criteriaSet: [ - { - address: '0x0000000000000000000000000000456e65726779', - Topic1: tokenHex - }, - { - address: '0x0000000000000000000000000000456e65726779', - Topic2: tokenHex - } - ], - order: 'desc' - }); - if (!result.data || result.data.length === 0) return false; - const transactions = await this._unifyTransactionsToken( - address, - result.data - ); - AirDAOCryptoLog.log( - 'VetScannerProcessor.getTransactions finished ' + tokenHex - ); - return transactions; - } - } - - private async _unifyTransactions( - address: string, - result: any[] - ): Promise { - const transactions = []; - let tx; - for (tx of result) { - const transaction = await this._unifyTransaction(address, tx); - if (transaction) { - transactions.push(transaction); - } - } - return transactions; - } - - private async _unifyTransactionsToken( - address: string, - result: any[] - ): Promise { - const transactions = []; - let tx; - for (tx of result) { - const transaction = await this._unifyTransactionToken(address, tx); - if (transaction) { - transactions.push(transaction); - } - } - return transactions; - } - - private async _unifyTransaction( - address: string, - transaction: any - ): Promise { - /** - * "sender":"0xa4adafaef9ec07bc4dc6de146934c7119341ee25", - * "recipient":"0xf3ce5d5d8ff44cb6b4f77512b94ddd6e04d820a0", - * "amount":"0x352e15037328a70000", - * "meta":{ - * "blockID":"0x008c4643ee0fb483412b2b6aa34c76c7925093cd1749f33238fa14f6ab340046", - * "blockNumber":9193027, - * "blockTimestamp":1622441190, - * "txID":"0x46bb9fb1e71845fee9289e7626aa4eba26fb834d1a17661c6fbbb333958fcc67", - * "txOrigin":"0xa4adafaef9ec07bc4dc6de146934c7119341ee25", - * "clauseIndex":0 - */ - - const amount = BlocksoftUtils.hexToDecimalBigger(transaction.amount); - let formattedTime = transaction.meta.blockTimestamp; - try { - formattedTime = BlocksoftUtils.toDate(transaction.meta.blockTimestamp); - } catch (e: any) { - e.message += - ' timestamp error transaction2 data ' + JSON.stringify(transaction); - throw e; - } - const now = new Date().getTime(); - const diffSeconds = Math.round( - (now - transaction.meta.blockTimestamp) / 1000 - ); - let blockConfirmations = this._lastBlock - transaction.meta.blockNumber; - if (blockConfirmations > 100 && diffSeconds < 600) { - blockConfirmations = diffSeconds; - } else if (blockConfirmations < 0) { - blockConfirmations = diffSeconds > 60 ? 2 : 0; - } - const tx = { - transactionHash: transaction.meta.txID, - blockHash: transaction.meta.blockID, - blockNumber: transaction.meta.blockNumber, - blockTime: formattedTime, - blockConfirmations, - transactionDirection: - address.toLowerCase() === transaction.sender.toLowerCase() - ? 'outcome' - : 'income', - addressFrom: transaction.sender === address ? '' : transaction.sender, - addressTo: transaction.recipient === address ? '' : transaction.recipient, - addressAmount: amount, - transactionStatus: blockConfirmations > 20 ? 'success' : 'new', - transactionFee: '0' - }; - return tx; - } - - private async _unifyTransactionToken( - address: string, - transaction: any - ): Promise { - /** - * "address": "0x0000000000000000000000000000456e65726779", - * "topics": [ - * "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", - * "0x000000000000000000000000a4adafaef9ec07bc4dc6de146934c7119341ee25", - * "0x000000000000000000000000f3ce5d5d8ff44cb6b4f77512b94ddd6e04d820a0" - * ], - * "data": "0x00000000000000000000000000000000000000000000006a227cf2eb4ac80000", - * "meta": { - * "blockID": "0x008c525b58528178e3e2ea89e6a6864c0356127a21b5b06aeaa26f531690abf8", - * "blockNumber": 9196123, - * "blockTimestamp": 1622472160, - * "txID": "0x190406bc9491484ca763774dfb074118ef7b5d6f594ac484402da265d00c3eff", - * "txOrigin": "0xa4adafaef9ec07bc4dc6de146934c7119341ee25", - * "clauseIndex": 5 - */ - - const amount = BlocksoftUtils.hexToDecimalBigger(transaction.data); - let formattedTime = transaction.meta.blockTimestamp; - try { - formattedTime = BlocksoftUtils.toDate(transaction.meta.blockTimestamp); - } catch (e: any) { - e.message += - ' timestamp error transaction2 data ' + JSON.stringify(transaction); - throw e; - } - const now = new Date().getTime(); - const diffSeconds = Math.round( - (now - transaction.meta.blockTimestamp) / 1000 - ); - let blockConfirmations = this._lastBlock - transaction.meta.blockNumber; - if (blockConfirmations > 100 && diffSeconds < 600) { - blockConfirmations = diffSeconds; - } else if (blockConfirmations < 0) { - blockConfirmations = diffSeconds > 60 ? 2 : 0; - } - const addressFrom = - typeof transaction.topics[1] !== 'undefined' - ? transaction.topics[1].replace('0x000000000000000000000000', '0x') - : ''; - const addressTo = - typeof transaction.topics[2] !== 'undefined' - ? transaction.topics[2].replace('0x000000000000000000000000', '0x') - : ''; - const tx = { - transactionHash: transaction.meta.txID, - blockHash: transaction.meta.blockID, - blockNumber: transaction.meta.blockNumber, - blockTime: formattedTime, - blockConfirmations, - transactionDirection: - addressFrom === address.toLowerCase() ? 'outcome' : 'income', - addressFrom: addressFrom === address.toLowerCase() ? '' : addressFrom, - addressTo: addressTo === address.toLowerCase() ? '' : addressTo, - addressAmount: amount, - transactionStatus: blockConfirmations > 20 ? 'success' : 'new', - transactionFee: '0' - }; - return tx; - } -} diff --git a/crypto/blockchains/vet/VetTransferProcessor.ts b/crypto/blockchains/vet/VetTransferProcessor.ts deleted file mode 100644 index cf1937491..000000000 --- a/crypto/blockchains/vet/VetTransferProcessor.ts +++ /dev/null @@ -1,302 +0,0 @@ -/** - * @version 0.20 - */ -import AirDAOCryptoLog from '../../common/AirDAOCryptoLog'; -import BlocksoftUtils from '../../common/AirDAOUtils'; - -import { thorify } from 'thorify'; -import AirDAOAxios from '@crypto/common/AirDAOAxios'; -import { AirDAOBlockchainTypes } from '@crypto/blockchains/AirDAOBlockchainTypes'; - -const Web3 = require('web3'); -const abi = [ - { - constant: false, - inputs: [ - { - name: 'to', - type: 'address' - }, - { - name: 'value', - type: 'uint256' - } - ], - name: 'transfer', - outputs: [ - { - name: 'ok', - type: 'bool' - } - ], - payable: false, - stateMutability: 'nonpayable', - type: 'function' - } -]; -const tokenAddress = '0x0000000000000000000000000000456e65726779'; - -const API_PATH = 'https://sync-mainnet.vechain.org'; - -export default class VetTransferProcessor - implements AirDAOBlockchainTypes.TransferProcessor -{ - private _settings: { network: string; currencyCode: string }; - private _web3: any; - private _token: any; - - constructor(settings: { network: string; currencyCode: string }) { - this._settings = settings; - this._web3 = thorify(new Web3(), API_PATH); - this._token = new this._web3.eth.Contract(abi, tokenAddress); - } - - needPrivateForFee(): boolean { - return false; - } - - checkSendAllModal(data: { currencyCode: any }): boolean { - return false; - } - - async getFeeRate( - data: AirDAOBlockchainTypes.TransferData - ): Promise { - const result: AirDAOBlockchainTypes.FeeRateResult = { - selectedFeeIndex: -3, - shouldShowFees: false - } as AirDAOBlockchainTypes.FeeRateResult; - - if (this._settings.currencyCode === 'VET') { - // @todo - /* - result.fees = [ - { - langMsg: 'xrp_speed_one', - feeForTx: '536300000000000000', - amountForTx: data.amount - } - ] - result.selectedFeeIndex = 0 - */ - } else { - result.fees = [ - { - langMsg: 'xrp_speed_one', - feeForTx: '536300000000000000', - amountForTx: data.amount - } - ]; - result.selectedFeeIndex = 0; - } - - return result; - } - - async getTransferAllBalance( - data: AirDAOBlockchainTypes.TransferData, - privateData: AirDAOBlockchainTypes.TransferPrivateData, - additionalData: AirDAOBlockchainTypes.TransferAdditionalData = {} - ): Promise { - const balance = data.amount; - // @ts-ignore - await AirDAOCryptoLog.log( - this._settings.currencyCode + - `' VetTransferProcessor.getTransferAllBalance ', - ${data.addressFrom} + ' => ' + ${balance}` - ); - - const fees = await this.getFeeRate(data, privateData, additionalData); - - let amount; - if (this._settings.currencyCode === 'VET') { - amount = balance; - } else { - amount = BlocksoftUtils.diff(balance, '536300000000000000').toString(); - } - - return { - ...fees, - shouldShowFees: false, - selectedTransferAllBalance: amount - }; - } - - /** - * https://thorify.vecha.in/#/?id=how-do-i-send-vtho-token - * https://thorify.vecha.in/#/?id=send-transaction-1 - * https://github.com/vechain/thorify/blob/bb7a97e5e62b46af0d45fa119e99539d57e2302a/test/web3/eth.test.ts - * @param data - * @param privateData - * @param uiData - */ - async sendTx( - data: AirDAOBlockchainTypes.TransferData, - privateData: AirDAOBlockchainTypes.TransferPrivateData, - uiData: AirDAOBlockchainTypes.TransferUiData - ): Promise { - if (typeof privateData.privateKey === 'undefined') { - throw new Error('VET transaction required privateKey'); - } - if (typeof data.addressTo === 'undefined') { - throw new Error('VET transaction required addressTo'); - } - - let signedData = false; - try { - this._web3.eth.accounts.wallet.add(privateData.privateKey); - - if (this._settings.currencyCode === 'VET') { - signedData = await this._web3.eth.accounts.signTransaction( - { - from: data.addressFrom, - to: data.addressTo, - value: data.amount - }, - privateData.privateKey - ); - } else { - const basicAddressTo = data.addressTo.toLowerCase(); - const encoded = this._token.methods - .transfer(basicAddressTo, data.amount) - .encodeABI(); - signedData = await this._web3.eth.accounts.signTransaction( - { - from: data.addressFrom, - to: tokenAddress, - data: encoded, - value: 0 - }, - privateData.privateKey - ); - } - } catch (e: any) { - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' VetTransferProcessor.sendTx ' + - data.addressFrom + - ' => ' + - data.addressTo + - ' ' + - data.amount + - ' error ' + - e.message - ); - throw new Error(e.message); - } - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' VetTransferProcessor.sendTx ' + - data.addressFrom + - ' => ' + - data.addressTo + - ' ' + - data.amount, - signedData - ); - if ( - !signedData || - typeof signedData.rawTransaction === undefined || - !signedData.rawTransaction - ) { - throw new Error('SYSTEM_ERROR'); - } - if ( - typeof uiData !== 'undefined' && - typeof uiData.selectedFee !== 'undefined' && - typeof uiData.selectedFee.rawOnly !== 'undefined' && - uiData.selectedFee.rawOnly - ) { - return { - rawOnly: uiData.selectedFee.rawOnly, - raw: signedData.rawTransaction - }; - } - - const result = {} as AirDAOBlockchainTypes.SendTxResult; - try { - const send = await AirDAOAxios.post( - API_PATH + '/transactions', - { raw: signedData.rawTransaction }, - false - ); - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' VetTransferProcessor.sendTx ' + - data.addressFrom + - ' => ' + - data.addressTo + - ' ' + - data.amount, - send.data - ); - if ( - typeof send.data === 'undefined' || - !send.data || - typeof send.data.id === 'undefined' - ) { - throw new Error('SYSTEM_ERROR'); - } - result.transactionHash = send.data.id; - } catch (e: any) { - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' VetTransferProcessor.sendTx ' + - data.addressFrom + - ' => ' + - data.addressTo + - ' ' + - data.amount + - ' send error ' + - e.message - ); - this.checkError(e, data, false); - } - return result; - } - - checkError(e: any, data: any, txRBF = false) { - if (e.message.indexOf('nonce too low') !== -1) { - AirDAOCryptoLog.log( - 'VeChain checkError0.1 ' + e.message + ' for ' + data.addressFrom - ); - let e2; - if (txRBF) { - e2 = new Error('SERVER_RESPONSE_TRANSACTION_ALREADY_MINED'); - } else { - e2 = new Error('SERVER_RESPONSE_NONCE_ALREADY_MINED'); - } - throw e2; - } else if (e.message.indexOf('insufficient funds') !== -1) { - AirDAOCryptoLog.log( - 'VeChain checkError0.3 ' + e.message + ' for ' + data.addressFrom - ); - if ( - (this._settings.currencyCode === 'ETH' || - this._settings.currencyCode === 'BNB_SMART') && - data.amount * 1 > 0 - ) { - throw new Error('SERVER_RESPONSE_NOTHING_LEFT_FOR_FEE'); - } else { - throw new Error('SERVER_RESPONSE_NOT_ENOUGH_FEE'); - } - } else if (e.message.indexOf('underpriced') !== -1) { - AirDAOCryptoLog.log( - 'VeChain checkError0.4 ' + e.message + ' for ' + data.addressFrom - ); - throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_FEE'); - } else if (e.message.indexOf('already known') !== -1) { - AirDAOCryptoLog.log( - 'VeChain checkError0.5 ' + e.message + ' for ' + data.addressFrom - ); - throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_FEE'); - } else if (e.message.indexOf('insufficient energy') !== -1) { - AirDAOCryptoLog.log( - 'VeChain checkError0.6 ' + e.message + ' for ' + data.addressFrom - ); - throw new Error('SERVER_RESPONSE_ENERGY_ERROR_VET'); - } else { - throw e; - } - } -} diff --git a/crypto/blockchains/waves/WavesAddressProcessor.ts b/crypto/blockchains/waves/WavesAddressProcessor.ts deleted file mode 100644 index c473a3ec7..000000000 --- a/crypto/blockchains/waves/WavesAddressProcessor.ts +++ /dev/null @@ -1,35 +0,0 @@ -/** - * @version 0.5 - */ -import { crypto, base58Encode } from '@waves/ts-lib-crypto'; - -interface SuperPrivateData { - mnemonic: string; -} - -export default class WavesAddressProcessor { - async setBasicRoot(root: any): Promise { - // Implement the setBasicRoot method according to its functionality. - // Since the original function doesn't have any implementation, I'm leaving this empty. - } - - /** - * @param {string|Buffer} _privateKey - * @param {any} data - * @param {SuperPrivateData} superPrivateData - * @returns {Promise<{privateKey: string, address: string, addedData: any}>} - */ - async getAddress( - _privateKey: string | Buffer, - data: any = {}, - superPrivateData: SuperPrivateData - ): Promise<{ privateKey: string; address: string; addedData: any }> { - const all = crypto({ seed: superPrivateData.mnemonic }); - const buff1 = all.address(); - const address = base58Encode(buff1); - const key2 = all.keyPair(); - const pubKey = base58Encode(key2.publicKey); - const privKey = base58Encode(key2.privateKey); - return { address, privateKey: privKey, addedData: { pubKey } }; - } -} diff --git a/crypto/blockchains/waves/WavesScannerProcessor.ts b/crypto/blockchains/waves/WavesScannerProcessor.ts deleted file mode 100644 index 8e5102e3e..000000000 --- a/crypto/blockchains/waves/WavesScannerProcessor.ts +++ /dev/null @@ -1,148 +0,0 @@ -/** - * @version 0.5 - */ -import AirDAOCryptoLog from '../../common/AirDAOCryptoLog'; -import AirDAOAxios from '@crypto/common/AirDAOAxios'; -import BlocksoftExternalSettings from '@crypto/common/AirDAOExternalSettings'; -// import TransactionFilterTypeDict from '@appV2/dicts/transactionFilterTypeDict'; -import WavesTransactionsProvider from '@crypto/blockchains/waves/providers/WavesTransactionsProvider'; -// import TransactionFilterTypeDict from '@crypto/TransactionFilterTypeDict'; - -export default class WavesScannerProcessor { - private _settings: any; - private _provider: WavesTransactionsProvider; - private _mainCurrencyCode: string; - private _apiPath: string | undefined; - - constructor(settings: any) { - this._settings = settings; - this._provider = new WavesTransactionsProvider(); - this._mainCurrencyCode = 'WAVES'; - if ( - this._settings.currencyCode === 'ASH' || - this._settings.currencyCode.indexOf('ASH_') === 0 - ) { - this._mainCurrencyCode = 'ASH'; - } - } - - /** - * https://nodes.wavesnodes.com/addresses/balance/details/3P274YB5qseSE9DTTL3bpSjosZrYBPDpJ8k - * https://nodes.wavesnodes.com/api-docs/index.html#/addresses/getWavesBalances - * https://docs.waves.tech/en/blockchain/account/account-balance#account-balance-in-waves - * @return {Promise<{balance, provider}>} - */ - async getBalanceBlockchain(address: string): Promise { - if (this._mainCurrencyCode === 'ASH') { - this._apiPath = await BlocksoftExternalSettings.get('ASH_SERVER'); - } else { - this._apiPath = await BlocksoftExternalSettings.get('WAVES_SERVER'); - } - address = address.trim(); - AirDAOCryptoLog.log( - `${this._settings.currencyCode} WavesScannerProcessor getBalanceBlockchain address ${address}` - ); - - const link = `${this._apiPath}/addresses/balance/details/${address}`; - const res = await AirDAOAxios.get(link); - if (!res) { - return false; - } - try { - return { - balance: res.data.available, - unconfirmed: 0, - provider: 'wavesnodes.com' - }; - } catch (e: any) { - AirDAOCryptoLog.log( - `${ - this._settings.currencyCode - } WavesProcessor getBalanceBlockchain address ${address} balance ${JSON.stringify( - res - )} to hex error ${e.message}` - ); - } - return false; - } - - /** - * https://nodes.wavesnodes.com/transactions/address/3P274YB5qseSE9DTTL3bpSjosZrYBPDpJ8k/limit/100 - * @param {string} scanData.account.address - * @return {Promise<[UnifiedTransaction]>} - */ - async getTransactionsBlockchain(scanData: any, source: any): Promise { - const address = scanData.account.address.trim(); - const data = await this._provider.get(address, this._mainCurrencyCode); - const transactions: any[] = []; - for (const tx of data) { - const transaction = await this._unifyTransaction(address, tx); - if (transaction) { - transactions.push(transaction); - } - } - return transactions; - } - - /** - * @param address - * @param transaction.amount 100000000 - * @param transaction.applicationStatus succeeded - * @param transaction.assetId null - * @param transaction.attachment - * @param transaction.fee 100000 - * @param transaction.feeAsset - * @param transaction.feeAssetId - * @param transaction.height 2715839 - * @param transaction.id GxnhfderDpMwdrSfWbTN53NGB1Q2NRQXcGvYdXbzQBXo - * @param transaction.recipient 3PQQUuGM1Fo8zz72i62dNYkB5kRxqmJkoSu - * @param transaction.sender 3PLPGmXoDNKeWxSgJRU5vDNogbPj7hJiWQx - * @param transaction.senderPublicKey GP9hPWAiGDfNYyCTNw6ZWoLCzUqWiYj7MybtPcu8mpkg - * @param transaction.signature 4FewzMCYLvfQridUZtSFrDXRbDvmawUWtBxWdiEE5CeruG1qfbKbfTkudGyW6Eqs3kW4hTpABQxrhBSBuKV7```typescript - * /** - * @param address - * @param transaction.amount 100000000 - * @param transaction.applicationStatus succeeded - * @param transaction.assetId null - * @param transaction.attachment - * @param transaction.fee 100000 - * @param transaction.feeAsset - * @param transaction.feeAssetId - * @param transaction.height 2715839 - * @param transaction.id GxnhfderDpMwdrSfWbTN53NGB1Q2NRQXcGvYdXbzQBXo - * @param transaction.recipient 3PQQUuGM1Fo8zz72i62dNYkB5kRxqmJkoSu - * @param transaction.sender 3PLPGmXoDNKeWxSgJRU5vDNogbPj7hJiWQx - * @param transaction.senderPublicKey GP9hPWAiGDfNYyCTNw6ZWoLCzUqWiYj7MybtPcu8mpkg - * @param transaction.signature 4FewzMCYLvfQridUZtSFrDXRbDvmawUWtBxWdiEE5CeruG1qfbKbfTkudGyW6Eqs3kW4hTpABQxrhBSBuKV7 - */ - private async _unifyTransaction( - address: string, - transaction: any - ): Promise { - const unifiedTransaction: any = {}; - - unifiedTransaction.hash = transaction.id; - unifiedTransaction.blockHash = null; - unifiedTransaction.blockNumber = transaction.height; - unifiedTransaction.confirmations = await this._provider.getConfirmations( - transaction.height - ); - unifiedTransaction.timestamp = transaction.timestamp; - unifiedTransaction.from = transaction.sender; - unifiedTransaction.to = transaction.recipient; - unifiedTransaction.value = transaction.amount; - unifiedTransaction.fee = transaction.fee; - unifiedTransaction.data = { - applicationStatus: transaction.applicationStatus, - assetId: transaction.assetId, - attachment: transaction.attachment, - feeAsset: transaction.feeAsset, - feeAssetId: transaction.feeAssetId, - senderPublicKey: transaction.senderPublicKey, - signature: transaction.signature - }; - // unifiedTransaction.status = TransactionFilterTypeDict.CRYPTO_STATUS_PENDING; // pending - - return unifiedTransaction; - } -} diff --git a/crypto/blockchains/waves/WavesScannerProcessorErc20.ts b/crypto/blockchains/waves/WavesScannerProcessorErc20.ts deleted file mode 100644 index e48d2ca95..000000000 --- a/crypto/blockchains/waves/WavesScannerProcessorErc20.ts +++ /dev/null @@ -1,150 +0,0 @@ -/** - * @version 0.5 - */ -import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; -import AirDAOAxios from '@crypto/common/AirDAOAxios'; -import BlocksoftExternalSettings from '@crypto/common/AirDAOExternalSettings'; -import WavesScannerProcessor from '@crypto/blockchains/waves/WavesScannerProcessor'; -// import TransactionFilterTypeDict from '@appV2/dicts/transactionFilterTypeDict'; -import BlocksoftUtils from '@crypto/common/AirDAOUtils'; -import TransactionFilterTypeDict from '@crypto/TransactionFilterTypeDict'; - -interface WavesScannerProcessorErc20Settings { - currencyCode: string; - decimals: number; - tokenAddress: string; -} - -export default class WavesScannerProcessorErc20 extends WavesScannerProcessor { - private readonly _settings: WavesScannerProcessorErc20Settings; - private _apiPath: string | undefined; - private readonly _tokenAddress: string; - - constructor(settings: WavesScannerProcessorErc20Settings) { - super(settings); - this._settings = settings; - this._tokenAddress = settings.tokenAddress; - } - - /** - * https://nodes.wavesnodes.com/api-docs/index.html#/assets/getAssetBalanceByAddress - * https://nodes.wavesnodes.com/assets/balance/3PGixfWP1pcuWwND2rDLf2n94J7egSf4uz4/DG2xFkPdDwKUoBkzGAhQtLpSGzfXLiCYPEzeKH2Ad24p - * @return {Promise<{balance, provider}>} - */ - async getBalanceBlockchain( - address: string - ): Promise< - { balance: number; unconfirmed: number; provider: string } | false - > { - if (this._settings.currencyCode === 'ASH') { - this._apiPath = await BlocksoftExternalSettings.get('ASH_SERVER'); - } else { - this._apiPath = await BlocksoftExternalSettings.get('WAVES_SERVER'); - } - address = address.trim(); - AirDAOCryptoLog.log( - `${this._settings.currencyCode} WavesScannerProcessorErc20 getBalanceBlockchain address ${address}` - ); - - const link = `${this._apiPath}/assets/balance/${address}/${this._tokenAddress}`; - const res = await AirDAOAxios.get(link); - if (!res) { - return false; - } - try { - return { - balance: res.data.balance, - unconfirmed: 0, - provider: 'wavesnodes.com' - }; - } catch (e: any) { - AirDAOCryptoLog.log( - `${ - this._settings.currencyCode - } WavesProcessorErc20 getBalanceBlockchain address ${address} balance ${JSON.stringify( - res - )} to hex error ${e.message}` - ); - } - return false; - } - - async _unifyTransaction( - address: string, - transaction: any - ): Promise { - let transactionStatus = 'confirming'; - if (transaction.applicationStatus === 'succeeded') { - transactionStatus = 'success'; - } else if (transaction.applicationStatus === 'script_execution_failed') { - transactionStatus = 'fail'; - } - let formattedTime = transaction.timestamp; - const blockConfirmations = Math.round( - (new Date().getTime() - transaction.timestamp) / 6000 - ); - try { - formattedTime = new Date(transaction.timestamp).toISOString(); - } catch (e: any) { - e.message += ` timestamp error transaction2 data ${JSON.stringify( - transaction - )}`; - throw e; - } - const addressFrom = transaction.sender; - const addressTo = transaction.recipient || address; - let addressAmount = transaction.amount; - const transactionFee = - transaction.feeAsset && transaction.feeAssetId ? 0 : transaction.fee; - - let transactionDirection: string = false; - let transactionFilterType = TransactionFilterTypeDict.USUAL; - - if (typeof transaction.order1 !== 'undefined') { - if (transaction.order1.assetPair.priceAsset !== this._tokenAddress) { - return false; - } - if (transaction.order2.amount === addressAmount) { - transactionDirection = 'swap_income'; - addressAmount = transaction.order2.amount * transaction.order1.price; - } else { - transactionDirection = 'swap_outcome'; - addressAmount = transaction.order1.amount * transaction.order2.price; - } - transactionFilterType = TransactionFilterTypeDict.SWAP; - addressAmount = BlocksoftUtils.fromUnified( - BlocksoftUtils.toUnified(addressAmount, 14), - this._settings.decimals - ); - } else if (transaction.assetId === this._tokenAddress) { - transactionDirection = 'self'; - if (addressFrom === address) { - if (addressTo !== address) { - transactionDirection = 'outcome'; - } - } else if (addressTo === address) { - transactionDirection = 'income'; - } - } else { - return false; - } - - const tmp = { - transactionHash: transaction.id, - blockHash: transaction.height, - blockNumber: transaction.height, - blockTime: formattedTime, - blockConfirmations, - transactionDirection, - transactionFilterType, - addressFrom: addressFrom === address ? '' : addressFrom, - addressFromBasic: addressFrom, - addressTo: addressTo === address ? '' : addressTo, - addressToBasic: addressTo, - addressAmount, - transactionStatus, - transactionFee - }; - return tmp; - } -} diff --git a/crypto/blockchains/waves/WavesTransferProcessor.ts b/crypto/blockchains/waves/WavesTransferProcessor.ts deleted file mode 100644 index 5e074b43c..000000000 --- a/crypto/blockchains/waves/WavesTransferProcessor.ts +++ /dev/null @@ -1,278 +0,0 @@ -/** - * @version 0.5 - */ -import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; -import BlocksoftUtils from '@crypto/common/AirDAOUtils'; - -import { transfer, broadcast } from '@waves/waves-transactions/src/index'; -import BlocksoftExternalSettings from '@crypto/common/AirDAOExternalSettings'; -import MarketingEvent from '@app/services/Marketing/MarketingEvent'; -import { AirDAOBlockchainTypes } from '@crypto/blockchains/AirDAOBlockchainTypes'; -import config from '@constants/config'; - -export default class WavesTransferProcessor - implements AirDAOBlockchainTypes.TransferProcessor -{ - private _settings: { network: string; currencyCode: string }; - - private _tokenAddress: string | boolean; - - private _mainCurrencyCode: string; - - constructor(settings: { - tokenAddress: string; - network: string; - currencyCode: string; - }) { - this._settings = settings; - this._tokenAddress = - typeof settings.tokenAddress !== undefined - ? settings.tokenAddress - : false; - - this._mainCurrencyCode = 'WAVES'; - if ( - this._settings.currencyCode === 'ASH' || - this._settings.currencyCode.indexOf('ASH_') === 0 - ) { - this._mainCurrencyCode = 'ASH'; - } - } - - needPrivateForFee(): boolean { - return false; - } - - checkSendAllModal(data: { currencyCode: any }): boolean { - return false; - } - - async getFeeRate( - data: AirDAOBlockchainTypes.TransferData, - privateData: AirDAOBlockchainTypes.TransferPrivateData, - additionalData: {} = {} - ): Promise { - const result: AirDAOBlockchainTypes.FeeRateResult = { - selectedFeeIndex: -3, - shouldShowFees: false - } as AirDAOBlockchainTypes.FeeRateResult; - - result.fees = [ - { - langMsg: 'xrp_speed_one', - feeForTx: '100000', - amountForTx: data.amount - } - ]; - result.selectedFeeIndex = 0; - - return result; - } - - async getTransferAllBalance( - data: AirDAOBlockchainTypes.TransferData, - privateData: AirDAOBlockchainTypes.TransferPrivateData, - additionalData: AirDAOBlockchainTypes.TransferAdditionalData = {} - ): Promise { - const balance = data.amount; - // @ts-ignore - AirDAOCryptoLog.log( - this._settings.currencyCode + - `' WavesTransferProcessor.getTransferAllBalance ', - ${data.addressFrom} + ' => ' + ${balance}` - ); - - const res = await this.getFeeRate(data, privateData, additionalData); - let amount; - if (this._tokenAddress) { - amount = balance; - } else { - amount = BlocksoftUtils.diff(balance, '100000').toString(); - res.fees[0].amountForTx = amount; - } - - return { - ...res, - shouldShowFees: false, - selectedTransferAllBalance: amount - }; - } - - /** - * https://docs.waves.tech/en/building-apps/how-to/basic/transaction#sign-transaction-using-your-own-seed - * @param data - * @param privateData - * @param uiData - */ - async sendTx( - data: AirDAOBlockchainTypes.TransferData, - privateData: AirDAOBlockchainTypes.TransferPrivateData, - uiData: AirDAOBlockchainTypes.TransferUiData - ): Promise { - if (typeof privateData.privateKey === 'undefined') { - throw new Error('WAVES transaction required privateKey'); - } - if (typeof data.addressTo === 'undefined') { - throw new Error('WAVES transaction required addressTo'); - } - - let addressTo = data.addressTo; - let apiPath: any; - if (this._mainCurrencyCode === 'ASH') { - apiPath = await BlocksoftExternalSettings.get('ASH_SERVER'); - addressTo = addressTo.replace('Æx', ''); - } else { - apiPath = await BlocksoftExternalSettings.get('WAVES_SERVER'); - } - - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' WavesTransferProcessor.sendTx started ' + - data.addressFrom + - ' => ' + - data.addressTo + - ' ' + - data.amount - ); - - let signedData = false; - try { - const money = { - recipient: addressTo, - amount: data.amount - }; - if (this._tokenAddress) { - money.assetId = this._tokenAddress; - } - signedData = transfer(money, { privateKey: privateData.privateKey }); - } catch (e: any) { - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' WavesTransferProcessor.sendTx ' + - data.addressFrom + - ' => ' + - data.addressTo + - ' ' + - data.amount + - ' signedData error ' + - e.message - ); - throw new Error(e.message); - } - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' WavesTransferProcessor.sendTx ' + - data.addressFrom + - ' => ' + - data.addressTo + - ' ' + - data.amount + - ' signedData', - signedData - ); - if (!signedData || typeof signedData.id === 'undefined' || !signedData.id) { - throw new Error('SYSTEM_ERROR'); - } - - if ( - typeof uiData !== 'undefined' && - typeof uiData.selectedFee !== 'undefined' && - typeof uiData.selectedFee.rawOnly !== 'undefined' && - uiData.selectedFee.rawOnly - ) { - return { - rawOnly: uiData.selectedFee.rawOnly, - raw: JSON.stringify(signedData) - }; - } - - const result = {} as AirDAOBlockchainTypes.SendTxResult; - try { - const resp = await new Promise((resolve, reject) => { - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' WavesTransferProcessor.sendTx ' + - data.addressFrom + - ' => ' + - data.addressTo + - ' ' + - data.amount + - ' will broadCast ' + - JSON.stringify(apiPath) - ); - broadcast(signedData, apiPath) - // tslint:disable-next-line:no-shadowed-variable - .then((resp: unknown) => { - resolve(resp); - }) - .catch((e: any) => { - reject(e); - }); - }); - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' WavesTransferProcessor.sendTx ' + - data.addressFrom + - ' => ' + - data.addressTo + - ' ' + - data.amount + - ' send res ' + - resp - ); - result.transactionHash = signedData.id; - } catch (e: any) { - if (config.debug.cryptoErrors) { - console.log( - this._settings.currencyCode + - ' WavesTransferProcessor.sendTx ' + - data.addressFrom + - ' => ' + - data.addressTo + - ' ' + - data.amount + - ' send error ' + - e.message - ); - } - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' WavesTransferProcessor.sendTx ' + - data.addressFrom + - ' => ' + - data.addressTo + - ' ' + - data.amount + - ' send error ' + - e.message - ); - this.checkError(e, data); - } - return result; - } - - checkError(e: any, data: { addressFrom: string; addressTo: string }) { - if ( - e.message.indexOf( - 'waves balance to (at least) temporary negative state' - ) !== -1 - ) { - throw new Error('SERVER_RESPONSE_NOTHING_LEFT_FOR_FEE'); - } else { - MarketingEvent.logOnlyRealTime( - 'v20_' + - this._settings.currencyCode + - '_tx_error ' + - this._settings.currencyCode + - ' ' + - data.addressFrom + - ' => ' + - data.addressTo + - ' ' + - e.message, - false - ); - throw e; - } - } -} diff --git a/crypto/blockchains/waves/providers/WavesTransactionsProvider.ts b/crypto/blockchains/waves/providers/WavesTransactionsProvider.ts deleted file mode 100644 index 05f320a49..000000000 --- a/crypto/blockchains/waves/providers/WavesTransactionsProvider.ts +++ /dev/null @@ -1,68 +0,0 @@ -/** - * @version 0.50 - */ -import BlocksoftExternalSettings from '@crypto/common/AirDAOExternalSettings'; -import AirDAOAxios from '@crypto/common/AirDAOAxios'; -import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; - -interface TransactionData { - data: any; - time: number; -} - -const CACHE_OF_TRANSACTIONS: Record> = { - ASH: {}, - WAVES: {} -}; - -const CACHE_VALID_TIME = 30000; // 30 seconds - -export default class WavesTransactionsProvider { - async get(address: string, mainCurrencyCode: string): Promise { - let _apiPath: string; - - if (mainCurrencyCode === 'ASH') { - _apiPath = await BlocksoftExternalSettings.get('ASH_SERVER'); - } else { - _apiPath = await BlocksoftExternalSettings.get('WAVES_SERVER'); - } - - const now = new Date().getTime(); - - if ( - CACHE_OF_TRANSACTIONS[mainCurrencyCode] && - CACHE_OF_TRANSACTIONS[mainCurrencyCode][address] && - now - CACHE_OF_TRANSACTIONS[mainCurrencyCode][address].time < - CACHE_VALID_TIME - ) { - if (CACHE_OF_TRANSACTIONS[mainCurrencyCode][address]) { - AirDAOCryptoLog.log( - `WavesTransactionsProvider.get from cache ${address} => ${mainCurrencyCode}` - ); - return CACHE_OF_TRANSACTIONS[mainCurrencyCode][address].data; - } - } - - const link = `${_apiPath}/transactions/address/${address}/limit/100`; - const res = await AirDAOAxios.get(link); - - // @ts-ignore - if (!res || typeof res.data === 'undefined') { - return false; - } - - // @ts-ignore - const data = res.data[0] as { data: any }; - - if (!data) { - return false; - } - - CACHE_OF_TRANSACTIONS[mainCurrencyCode][address] = { - data: data.data, - time: now - }; - - return data.data; - } -} diff --git a/crypto/blockchains/xlm/XlmAddressProcessor.ts b/crypto/blockchains/xlm/XlmAddressProcessor.ts deleted file mode 100644 index 4c534cbc8..000000000 --- a/crypto/blockchains/xlm/XlmAddressProcessor.ts +++ /dev/null @@ -1,31 +0,0 @@ -/** - * @version 0.20 - * https://github.com/chatch/stellar-hd-wallet/blob/master/src/stellar-hd-wallet.js - */ -import BlocksoftKeys from '../../actions/BlocksoftKeys/BlocksoftKeys'; -import XlmDerivePath from '@crypto/blockchains/xlm/ext/XlmDerivePath'; -import StellarSdk from 'stellar-sdk'; - -export default class XlmAddressProcessor { - async setBasicRoot(root: any) {} - - /** - * @param {string|Buffer} privateKey - * @param {*} data - * @returns {Promise<{privateKey: string, address: string}>} - */ - async getAddress( - privateKey: string | Buffer, - data: any = {}, - superPrivateData: any = {} - ): Promise<{ privateKey: string; address: string }> { - const seed = await BlocksoftKeys.getSeedCached(superPrivateData.mnemonic); - const seedHex = seed.toString('hex'); - if (seedHex.length < 128) { - throw new Error('bad seedHex'); - } - const res = XlmDerivePath(seedHex, `m/44'/148'/0'`); - const keypair = StellarSdk.Keypair.fromRawEd25519Seed(res.key); - return { address: keypair.publicKey(), privateKey: keypair.secret() }; - } -} diff --git a/crypto/blockchains/xlm/XlmScannerProcessor.ts b/crypto/blockchains/xlm/XlmScannerProcessor.ts deleted file mode 100644 index 72e4f9032..000000000 --- a/crypto/blockchains/xlm/XlmScannerProcessor.ts +++ /dev/null @@ -1,294 +0,0 @@ -/** - * @version 0.20 - */ -import AirDAOAxios from '../../common/AirDAOAxios'; -import AirDAOCryptoLog from '../../common/AirDAOCryptoLog'; -import BlocksoftUtils from '../../common/AirDAOUtils'; -import config from '@constants/config'; - -const API_PATH = 'https://horizon.stellar.org'; -const FEE_DECIMALS = 7; - -interface UnifiedTransaction { - transactionHash: string; - blockHash: string; - blockNumber: string; - blockTime: string; - blockConfirmations: number; - transactionDirection: string; - addressFrom: string; - addressTo: string; - addressAmount: string; - transactionStatus: string; - transactionFee: string; - transactionJson?: { memo: string }; -} - -export default class XlmScannerProcessor { - /** - * https://horizon.stellar.org/accounts/GBH4TZYZ4IRCPO44CBOLFUHULU2WGALXTAVESQA6432MBJMABBB4GIYI - * @param {string} address - * @return {Promise<{balance, unconfirmed, provider}>} - */ - async getBalanceBlockchain( - address: string - ): Promise< - { balance: number; unconfirmed: number; provider: string } | false - > { - const link = `${API_PATH}/accounts/${address}`; - let res: any = false; - let balance = 0; - try { - res = await AirDAOAxios.getWithoutBraking(link); - if ( - res && - typeof res.data !== 'undefined' && - res.data && - typeof res.data.balances !== 'undefined' - ) { - let row; - for (row of res.data.balances) { - if (row.asset_type === 'native') { - balance = row.balance; - break; - } - } - } else { - return false; - } - } catch (e: any) { - if ( - e.message.indexOf('timed out') === -1 && - e.message.indexOf('account not found') === -1 && - e.message.indexOf('the resource at the url requested was not found') === - -1 - ) { - throw e; - } else { - return false; - } - } - return { - balance, - unconfirmed: 0, - provider: 'horizon.stellar.org' - }; - } - - /** - * https://horizon.stellar.org/accounts/GBH4TZYZ4IRCPO44CBOLFUHULU2WGALXTAVESQA6432MBJMABBB4GIYI/payments - * @param {string} scanData.account.address - * @return {Promise} - */ - async getTransactionsBlockchain(scanData: { - account: { address: string }; - }): Promise { - const address = scanData.account.address.trim(); - AirDAOCryptoLog.log( - 'XlmScannerProcessor.getTransactions started ' + address - ); - const linkTxs = `${API_PATH}/accounts/${address}/transactions?order=desc&limit=50`; - let res: any = false; - try { - res = await AirDAOAxios.getWithoutBraking(linkTxs); - } catch (e: any) { - if ( - e.message.indexOf('account not found') === -1 && - e.message.indexOf('to retrieve payments') === -1 && - e.message.indexOf('limit exceeded') === -1 && - e.message.indexOf('timed out') === -1 && - e.message.indexOf('resource missing') === -1 - ) { - throw e; - } else { - return false; - } - } - - if (!res || typeof res.data === 'undefined' || !res.data) { - return false; - } - if ( - typeof res.data._embedded === 'undefined' || - typeof res.data._embedded.records === 'undefined' - ) { - throw new Error( - 'Undefined basic txs ' + linkTxs + ' ' + JSON.stringify(res.data) - ); - } - if (typeof res.data._embedded.records === 'string') { - throw new Error( - 'Undefined basic txs ' + linkTxs + ' ' + res.data._embedded.records - ); - } - const basicTxs: Record = {}; - for (const row of res.data._embedded.records) { - basicTxs[row.hash] = row; - } - - const link = `${API_PATH}/accounts/${address}/payments?order=desc&limit=50`; - res = false; - try { - res = await AirDAOAxios.getWithoutBraking(link); - } catch (e: any) { - if ( - e.message.indexOf('account not found') === -1 && - e.message.indexOf('to retrieve payments') === -1 && - e.message.indexOf('limit exceeded') === -1 && - e.message.indexOf('timed out') === -1 - ) { - throw e; - } else { - return false; - } - } - - if (!res || typeof res.data === 'undefined' || !res.data) { - return false; - } - if ( - typeof res.data._embedded === 'undefined' || - typeof res.data._embedded.records === 'undefined' - ) { - throw new Error('Undefined txs ' + link + ' ' + JSON.stringify(res.data)); - } - if (typeof res.data._embedded.records === 'string') { - throw new Error( - 'Undefined txs ' + link + ' ' + res.data._embedded.records - ); - } - - const transactions = await this._unifyTransactions( - address, - res.data._embedded.records, - basicTxs - ); - AirDAOCryptoLog.log( - 'XlmScannerProcessor.getTransactions finished ' + address - ); - return transactions; - } - - async _unifyTransactions( - address: string, - result: any[], - basicTxs: Record - ): Promise { - const transactions: UnifiedTransaction[] = []; - let tx; - for (tx of result) { - const transaction = await this._unifyPayment( - address, - tx, - typeof basicTxs[tx.transaction_hash] - ? basicTxs[tx.transaction_hash] - : false - ); - if (transaction) { - transactions.push(transaction); - } - } - return transactions; - } - - /** - * @param {string} address - * @param {Object} transaction - * @param {string} transaction.amount "1.6387292" - * @param {string} transaction.asset_type "native" - * @param {string} transaction.created_at "2021-01-30T21:15:04Z" - * @param {string} transaction.from "GDK45DNCNF66HZ634ZGYHVB3KGF3MUFJJ3CKWCI2QTKHRPQW22PBP5OE" - * @param {string} transaction.id "145082573625237505" - * @param {string} transaction.paging_token "145082573625237505" - * @param {string} transaction.source_account "GDK45DNCNF66HZ634ZGYHVB3KGF3MUFJJ3CKWCI2QTKHRPQW22PBP5OE" - * @param {string} transaction.to "GBH4TZYZ4IRCPO44CBOLFUHULU2WGALXTAVESQA6432MBJMABBB4GIYI" - * @param {string} transaction.transaction_hash "d01d19b75638405a510db0ac0e937849548ee21ff411ebe376a30d25dc78b750" - * @param {string} transaction.transaction_successful true - * @param {string} transaction.type "payment" - * @param {string} transaction.type_i 1 - * @return {UnifiedTransaction} - * @private - */ - async _unifyPayment( - address: string, - transaction: any, - basicTransaction: any - ): Promise { - try { - if (typeof transaction.asset_type !== 'undefined') { - if (transaction.asset_type !== 'native') { - return false; - } - } else if (typeof transaction.type !== 'undefined') { - if (transaction.type === 'create_account') { - if ( - typeof transaction.amount === 'undefined' && - typeof transaction.starting_balance !== 'undefined' - ) { - transaction.amount = transaction.starting_balance; - } - if ( - typeof transaction.source_account === 'undefined' && - typeof transaction.funder !== 'undefined' - ) { - // eslint-disable-next-line camelcase - transaction.source_account = transaction.funder; - } - } else { - return false; - } - } else { - return false; - } - const tx: UnifiedTransaction = { - transactionHash: transaction.transaction_hash, - blockHash: '', - blockNumber: '', - blockTime: transaction.created_at, - blockConfirmations: - transaction.transaction_successful === true ? 100 : 0, - transactionDirection: '?', - addressFrom: - transaction.source_account === address - ? '' - : transaction.source_account, - addressTo: transaction.account === address ? '' : transaction.account, - addressAmount: transaction.amount, - transactionStatus: - transaction.transaction_successful === true ? 'success' : 'new', - transactionFee: '0' - }; - if (tx.addressTo === '' || !tx.addressTo) { - if (tx.addressFrom === '') { - tx.transactionDirection = 'self'; - } else { - tx.transactionDirection = 'income'; - } - } else { - tx.transactionDirection = 'outcome'; - } - - if (basicTransaction) { - if (typeof basicTransaction.fee_charged !== 'undefined') { - tx.transactionFee = BlocksoftUtils.toUnified( - basicTransaction.fee_charged, - FEE_DECIMALS - ).toString(); - } - if (typeof basicTransaction.ledger !== 'undefined') { - tx.blockHash = basicTransaction.ledger; - tx.blockNumber = basicTransaction.ledger; - } - if (typeof basicTransaction.memo !== 'undefined') { - tx.transactionJson = { memo: basicTransaction.memo }; - } - } - return tx; - } catch (e: any) { - if (config.debug.cryptoErrors) { - console.log('XLMScannerProcessor _unifyPayment error ' + e.message); - } - throw e; - } - } -} diff --git a/crypto/blockchains/xlm/XlmTransferProcessor.ts b/crypto/blockchains/xlm/XlmTransferProcessor.ts deleted file mode 100644 index 0c92c267c..000000000 --- a/crypto/blockchains/xlm/XlmTransferProcessor.ts +++ /dev/null @@ -1,322 +0,0 @@ -/** - * @version 0.20 - * - * - * https://developers.stellar.org/docs/tutorials/send-and-receive-payments/ - * https://www.stellar.org/developers/js-stellar-sdk/reference/examples.html - * https://www.stellar.org/developers/js-stellar-sdk/reference/ - * - * wsl curl -X POST -F "tx=AAAAAgAAAACq+ux8eDBQfPoRzFjOTwHZKnFQjwRw0DSPL62mg02PjAAAAGQCA52cAAAAAQAAAAEAAAAAAAAAAAAAAABgF7+bAAAAAAAAAAEAAAAAAAAAAQAAAABUMjNVlZnxhC4rIKoE2qO/3QIFfy3nqF5/ObsdmmRWaAAAAAAAAAAABfXhAAAAAAAAAAABg02PjAAAAED6lRqsmOkqB8nRkI0tQTUSRAaHs/0mLuy6G58PvXzVQtlQiE2RPm9KC7Dv6c/a/0HS7F5mPXBFVshwtZS5WcgB" "https://horizon.stellar.org/transactions" - * { - * "type": "https://stellar.org/horizon-errors/transaction_failed", - * "title": "Transaction Failed", - * "status": 400, - * "detail": "The transaction failed when submitted to the stellar network. The `extras.result_codes` field on this response contains further details. Descriptions of each code can be found at: https://www.stellar.org/developers/guides/concepts/list-of-operations.html", - * "extras": { - * "envelope_xdr": "AAAAAgAAAACq+ux8eDBQfPoRzFjOTwHZKnFQjwRw0DSPL62mg02PjAAAAGQCA52cAAAAAQAAAAEAAAAAAAAAAAAAAABgF7+bAAAAAAAAAAEAAAAAAAAAAQAAAABUMjNVlZnxhC4rIKoE2qO/3QIFfy3nqF5/ObsdmmRWaAAAAAAAAAAABfXhAAAAAAAAAAABg02PjAAAAED6lRqsmOkqB8nRkI0tQTUSRAaHs/0mLuy6G58PvXzVQtlQiE2RPm9KC7Dv6c/a/0HS7F5mPXBFVshwtZS5WcgB", - * "result_codes": { - * "transaction": "tx_too_late" - * }, - * "result_xdr": "AAAAAAAAAGT////9AAAAAA==" - * } - * } - */ -import AirDAOCryptoLog from '../../common/AirDAOCryptoLog'; -import BlocksoftUtils from '../../common/AirDAOUtils'; -// @ts-ignore -import MarketingEvent from '../../../app/services/Marketing/MarketingEvent'; - -import { XlmTxSendProvider } from './basic/XlmTxSendProvider'; -import BlocksoftDispatcher from '@lib/BlocksoftDispatcher'; -import { AirDAOBlockchainTypes } from '@crypto/blockchains/AirDAOBlockchainTypes'; - -const FEE_DECIMALS = 7; - -export default class XlmTransferProcessor - implements AirDAOBlockchainTypes.TransferProcessor -{ - private _settings: { network: string; currencyCode: string }; - private _provider: XlmTxSendProvider; - - constructor(settings: { network: string; currencyCode: string }) { - this._settings = settings; - this._provider = new XlmTxSendProvider(); - } - - needPrivateForFee(): boolean { - return false; - } - - checkSendAllModal(data: { currencyCode: any }): boolean { - return false; - } - - async checkTransferHasError( - data: AirDAOBlockchainTypes.CheckTransferHasErrorData - ): Promise { - // @ts-ignore - if (data.amount && data.amount * 1 > 20) { - return { isOk: true }; - } - /** - * @type {XlmScannerProcessor} - */ - const balanceProvider = BlocksoftDispatcher.getScannerProcessor( - this._settings.currencyCode - ); - const balanceRaw = await balanceProvider.getBalanceBlockchain( - data.addressTo - ); - if ( - balanceRaw && - typeof balanceRaw.balance !== 'undefined' && - balanceRaw.balance > 1 - ) { - return { isOk: true }; - } else { - return { isOk: false, code: 'XLM', address: data.addressTo }; - } - } - - async getFeeRate( - data: AirDAOBlockchainTypes.TransferData, - privateData: AirDAOBlockchainTypes.TransferPrivateData, - additionalData: {} = {} - ): Promise { - const result: AirDAOBlockchainTypes.FeeRateResult = { - selectedFeeIndex: -1, - shouldShowFees: false - } as AirDAOBlockchainTypes.FeeRateResult; - - // @ts-ignore - if (data.amount * 1 <= 0) { - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' XlmTransferProcessor.getFeeRate ' + - data.addressFrom + - ' => ' + - data.addressTo + - ' skipped as zero amount' - ); - return result; - } - - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' XlmTransferProcessor.getFeeRate ' + - data.addressFrom + - ' => ' + - data.addressTo + - ' started amount: ' + - data.amount - ); - - const getFee = await this._provider.getFee(); - - if (!getFee) { - throw new Error('SERVER_RESPONSE_BAD_INTERNET'); - } - // @ts-ignore - const fee = BlocksoftUtils.toUnified(getFee, FEE_DECIMALS); - - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' XlmTransferProcessor.getFeeRate ' + - data.addressFrom + - ' => ' + - data.addressTo + - ' finished amount: ' + - data.amount + - ' fee: ' + - fee - ); - result.fees = [ - { - langMsg: 'xrp_speed_one', - feeForTx: fee.toString(), - amountForTx: data.amount, - blockchainData: getFee - } - ]; - result.selectedFeeIndex = 0; - return result; - } - - async getTransferAllBalance( - data: AirDAOBlockchainTypes.TransferData, - privateData: AirDAOBlockchainTypes.TransferPrivateData, - additionalData: AirDAOBlockchainTypes.TransferAdditionalData = {} - ): Promise { - const balance = data.amount; - // @ts-ignore - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' XlmTransferProcessor.getTransferAllBalance ' + - data.addressFrom + - ' => ' + - balance - ); - // noinspection EqualityComparisonWithCoercionJS - if (BlocksoftUtils.diff(balance, 1) <= 0) { - return { - selectedTransferAllBalance: '0', - selectedFeeIndex: -1, - fees: [], - shouldShowFees: false, - countedForBasicBalance: '0' - }; - } - - const result = await this.getFeeRate(data, privateData, additionalData); - // @ts-ignore - if (!result || result.selectedFeeIndex < 0) { - return { - selectedTransferAllBalance: '0', - selectedFeeIndex: -2, - fees: [], - shouldShowFees: false, - countedForBasicBalance: balance - }; - } - // @ts-ignore - let newAmount = BlocksoftUtils.diff( - result.fees[result.selectedFeeIndex].amountForTx, - result.fees[result.selectedFeeIndex].feeForTx - ).toString(); - newAmount = BlocksoftUtils.diff(newAmount, 1).toString(); - /* - console.log(' ' + result.fees[result.selectedFeeIndex].amountForTx) - console.log('--' + result.fees[result.selectedFeeIndex].feeForTx) - console.log('=' + newAmount) - */ - result.fees[result.selectedFeeIndex].amountForTx = newAmount; - const tmp = { - ...result, - shouldShowFees: false, - selectedTransferAllBalance: - result.fees[result.selectedFeeIndex].amountForTx - }; - // console.log('tmp', JSON.stringify(tmp)) - return tmp; - } - - async sendTx( - data: AirDAOBlockchainTypes.TransferData, - privateData: AirDAOBlockchainTypes.TransferPrivateData, - uiData: AirDAOBlockchainTypes.TransferUiData - ): Promise { - if (typeof privateData.privateKey === 'undefined') { - throw new Error('XLM transaction required privateKey'); - } - if (typeof data.addressTo === 'undefined') { - throw new Error('XLM transaction required addressTo'); - } - - if ( - typeof uiData.selectedFee === 'undefined' || - typeof uiData.selectedFee.blockchainData === 'undefined' - ) { - const getFee = await this._provider.getFee(); - - if (!getFee) { - throw new Error('SERVER_RESPONSE_BAD_INTERNET'); - } - if (typeof uiData.selectedFee === 'undefined') { - // @ts-ignore - uiData.selectedFee = {}; - } - uiData.selectedFee.blockchainData = getFee; - } - - let transaction = false; - try { - transaction = await this._provider.getPrepared(data, privateData, uiData); - } catch (e: any) { - if (e.message.indexOf('destination is invalid') !== -1) { - throw new Error('SERVER_RESPONSE_BAD_DESTINATION'); - } - throw e; - } - await AirDAOCryptoLog.log( - this._settings.currencyCode + ' XlmTransferProcessor.sendTx prepared' - ); - let raw = transaction.toEnvelope().toXDR('base64'); - await AirDAOCryptoLog.log( - this._settings.currencyCode + ' XlmTransferProcessor.sendTx base64', - raw - ); - if ( - typeof uiData !== 'undefined' && - typeof uiData.selectedFee !== 'undefined' && - typeof uiData.selectedFee.rawOnly !== 'undefined' && - uiData.selectedFee.rawOnly - ) { - return { raw: uiData.selectedFee.raw }; - } - - let result = false; - try { - result = await this._provider.sendRaw(raw); - } catch (e: any) { - if (e.message.indexOf('op_no_destination') !== -1) { - transaction = await this._provider.getPrepared( - data, - privateData, - uiData, - 'create_account' - ); - await AirDAOCryptoLog.log( - this._settings.currencyCode + - ' XlmTransferProcessor.sendTx prepared create account' - ); - raw = transaction.toEnvelope().toXDR('base64'); - await AirDAOCryptoLog.log( - this._settings.currencyCode + - ' XlmTransferProcessor.sendTx base64 create account', - raw - ); - result = await this._provider.sendRaw(raw); - } else { - MarketingEvent.logOnlyRealTime( - 'v20_stellar_error ' + - data.addressFrom + - ' => ' + - data.addressTo + - ' ' + - e.message, - { - raw - } - ); - if (e.message === 'op_underfunded') { - throw new Error('SERVER_RESPONSE_NOTHING_TO_TRANSFER'); - } else { - throw e; - } - } - } - if (!result || typeof result.hash === 'undefined') { - MarketingEvent.logOnlyRealTime( - 'v20_stellar_no_result ' + data.addressFrom + ' => ' + data.addressTo, - { - raw - } - ); - throw new Error('SERVER_RESPONSE_NO_RESPONSE'); - } - - MarketingEvent.logOnlyRealTime( - 'v20_stellar_success_result ' + - data.addressFrom + - ' => ' + - data.addressTo + - ' ' + - result.hash, - { - result - } - ); - - return { transactionHash: result.hash }; - } -} diff --git a/crypto/blockchains/xlm/basic/XlmTxSendProvider.ts b/crypto/blockchains/xlm/basic/XlmTxSendProvider.ts deleted file mode 100644 index 608c552f9..000000000 --- a/crypto/blockchains/xlm/basic/XlmTxSendProvider.ts +++ /dev/null @@ -1,197 +0,0 @@ -/** - * @version 0.20 - * https://developers.stellar.org/docs/tutorials/send-and-receive-payments/ - * https://www.stellar.org/developers/horizon/reference/endpoints/transactions-create.html - */ -import AirDAOCryptoLog from '../../../common/AirDAOCryptoLog'; -import BlocksoftExternalSettings from '../../../common/AirDAOExternalSettings'; -import { XrpTxUtils } from '../../xrp/basic/XrpTxUtils'; -import config from '@constants/config'; -import { AirDAOBlockchainTypes } from '@crypto/blockchains/AirDAOBlockchainTypes'; - -const StellarSdk = require('stellar-sdk'); - -const CACHE_VALID_TIME = 600000; // 10 minute -let CACHE_FEES_TIME = 0; -let CACHE_FEES_VALUE = 0; - -const TX_TIMEOUT = 30; - -export class XlmTxSendProvider { - private readonly _api: any; - private readonly _server: any; - - constructor() { - this._server = BlocksoftExternalSettings.getStatic('XLM_SERVER'); - this._api = new StellarSdk.Server(this._server); - CACHE_FEES_VALUE = BlocksoftExternalSettings.getStatic('XLM_SERVER_PRICE'); - } - - async getFee() { - const force = BlocksoftExternalSettings.getStatic('XLM_SERVER_PRICE_FORCE'); - if (force * 1 > 1) { - return force; - } - - const now = new Date().getTime(); - if (now - CACHE_FEES_TIME <= CACHE_VALID_TIME) { - return CACHE_FEES_VALUE; - } - - AirDAOCryptoLog.log('XlmSendProvider.getFee link ' + this._server); - let res = CACHE_FEES_VALUE; - try { - res = await this._api.fetchBaseFee(); - if (res * 1 > 0) { - CACHE_FEES_VALUE = res * 1; - CACHE_FEES_TIME = now; - } - } catch (e: any) { - AirDAOCryptoLog.log( - 'XlmSendProvider.getFee error ' + e.message + ' link ' + this._server - ); - res = CACHE_FEES_VALUE; - } - return res; - } - - async getPrepared( - data: AirDAOBlockchainTypes.TransferData, - privateData: AirDAOBlockchainTypes.TransferPrivateData, - uiData: AirDAOBlockchainTypes.TransferUiData, - type = 'usual' - ) { - const account = await this._api.loadAccount(data.addressFrom); - - let transaction; - try { - let operation; - if (type === 'create_account') { - // https://stellar.stackexchange.com/questions/2144/create-multiple-trustlines-upon-account-creation - operation = StellarSdk.Operation.createAccount({ - destination: data.addressTo, - startingBalance: XrpTxUtils.amountPrep(data.amount) - }); - } else { - operation = StellarSdk.Operation.payment({ - destination: data.addressTo, - asset: StellarSdk.Asset.native(), - amount: XrpTxUtils.amountPrep(data.amount) - }); - } - - if ( - typeof data.memo !== 'undefined' && - data.memo && - data.memo.toString().trim().length > 0 - ) { - transaction = new StellarSdk.TransactionBuilder(account, { - fee: uiData.selectedFee.blockchainData, - networkPassphrase: StellarSdk.Networks.PUBLIC - }) - .addOperation(operation) - .addMemo(StellarSdk.Memo.text(data.memo)) - .setTimeout(TX_TIMEOUT) - .build(); - } else { - transaction = new StellarSdk.TransactionBuilder(account, { - fee: uiData.selectedFee.blockchainData, - networkPassphrase: StellarSdk.Networks.PUBLIC - }) - .addOperation(operation) - .setTimeout(TX_TIMEOUT) - .build(); - } - } catch (e: any) { - await AirDAOCryptoLog.log( - 'XlmTxSendProvider builder create error ' + e.message - ); - throw e; - } - - try { - transaction.sign(StellarSdk.Keypair.fromSecret(privateData.privateKey)); - } catch (e: any) { - await AirDAOCryptoLog.log('XlmTxSendProvider sign error ' + e.message); - throw e; - } - return transaction; - } - - async sendRaw(raw: string) { - let result = false; - const link = BlocksoftExternalSettings.getStatic('XLM_SEND_LINK'); - AirDAOCryptoLog.log('XlmSendProvider.sendRaw ' + link + ' raw ' + raw); - try { - // console.log(`curl -X POST -F "tx=${raw}" "https://horizon.stellar.org/transactions"`) - - const formData = new FormData(); - formData.append('tx', raw); - - const response = await fetch(link, { - method: 'POST', - credentials: 'same-origin', - mode: 'same-origin', - headers: { - 'Content-Type': 'multipart/form-data' - }, - body: formData - }); - result = await response.json(); - if ( - result && - typeof result.extras !== undefined && - typeof result.extras.result_codes !== undefined - ) { - if (config.debug.cryptoErrors) { - console.log( - 'XlmTransferProcessor.sendTx result.extras.result_codes ' + - JSON.stringify(result.extras.result_codes) - ); - } - await AirDAOCryptoLog.log( - 'XlmTransferProcessor.sendTx result.extras.result_codes ' + - JSON.stringify(result.extras.result_codes) - ); - - if (typeof result.extras.result_codes.operations !== undefined) { - throw new Error(result.extras.result_codes.operations[0] + ' ' + raw); - } - if (typeof result.extras.result_codes.transaction !== undefined) { - throw new Error(result.extras.result_codes.transaction + ' ' + raw); - } - } - if (typeof result.status !== undefined) { - if ( - result.status === 406 || - result.status === 400 || - result.status === 504 - ) { - throw new Error(result.title); - } - } - } catch (e: any) { - if (config.debug.cryptoErrors) { - console.log( - 'XlmTransferProcessor.sendTx error ' + e.message + ' link ' + link - ); - } - await AirDAOCryptoLog.log( - 'XlmTransferProcessor.sendTx error ' + e.message + ' link ' + link - ); - if ( - e.message.indexOf('status code 406') !== -1 || - e.message.indexOf('status code 400') !== -1 || - e.message.indexOf('status code 504') !== -1 - ) { - throw new Error('SERVER_RESPONSE_NOT_CONNECTED'); - } else if (e.message.indexOf('tx_insufficient_fee') !== -1) { - throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_FEE'); - } else { - throw e; - } - } - await AirDAOCryptoLog.log('XlmTransferProcessor.sendTx result ', result); - return result; - } -} diff --git a/crypto/blockchains/xlm/ext/XlmDerivePath.ts b/crypto/blockchains/xlm/ext/XlmDerivePath.ts deleted file mode 100644 index 2adb45ac0..000000000 --- a/crypto/blockchains/xlm/ext/XlmDerivePath.ts +++ /dev/null @@ -1,58 +0,0 @@ -/** - * @version 0.20 - * actual derivePath from 'ed25519-hd-key' - */ -// @ts-ignore -import createHmac from 'create-hmac'; - -const ED25519_CURVE = 'ed25519 seed'; -const HARDENED_OFFSET = 0x80000000; - -interface KeyPair { - key: Buffer; - chainCode: Buffer; -} - -function getMasterKeyFromSeed(seed: string): KeyPair { - const hmac = createHmac('sha512', ED25519_CURVE); - const I = hmac.update(Buffer.from(seed, 'hex')).digest(); - const IL = I.slice(0, 32); - const IR = I.slice(32); - return { - key: IL, - chainCode: IR - }; -} - -function CKDPriv({ key, chainCode }: KeyPair, index: number): KeyPair { - const indexBuffer = Buffer.allocUnsafe(4); - indexBuffer.writeUInt32BE(index, 0); - const data = Buffer.concat([Buffer.alloc(1, 0), key, indexBuffer]); - const I = createHmac('sha512', chainCode).update(data).digest(); - const IL = I.slice(0, 32); - const IR = I.slice(32); - return { - key: IL, - chainCode: IR - }; -} - -export default (seed: string, derivationPath: string): KeyPair => { - const getMaster = getMasterKeyFromSeed(seed); - const key = getMaster.key; - const chainCode = getMaster.chainCode; - const segments = derivationPath - .split('/') - .slice(1) - .map((el) => el.replace("'", '')) - .map((el) => parseInt(el, 10)); - - const res = segments.reduce( - (parentKeys, segment) => { - return CKDPriv(parentKeys, segment + HARDENED_OFFSET); - }, - { key, chainCode } - ); - - return res; -}; diff --git a/crypto/blockchains/xmr/XmrAddressProcessor.js b/crypto/blockchains/xmr/XmrAddressProcessor.js deleted file mode 100644 index 3767a6aeb..000000000 --- a/crypto/blockchains/xmr/XmrAddressProcessor.js +++ /dev/null @@ -1,172 +0,0 @@ -/** - * @version 0.11 - * https://coinomi.github.io/bip39-monero/ - * - * - * let mnemonic = '' - * let results = await BlocksoftKeys.discoverAddresses({ mnemonic, fullTree: false, fromIndex: 0, toIndex: 1, currencyCode: ['XMR'] }) - * console.log('r', results['XMR'][0]) - */ -import MoneroUtils from './ext/MoneroUtils'; -import MoneroMnemonic from './ext/MoneroMnemonic'; -import { soliditySha3 } from 'web3-utils'; -import AirDAOAxios from '../../common/AirDAOAxios'; -import AirDAOCryptoLog from '../../common/AirDAOCryptoLog'; -import BlocksoftSecrets from '@crypto/actions/BlocksoftSecrets/BlocksoftSecrets'; -import config from '@app/config/config'; - -const bitcoin = require('bitcoinjs-lib'); -const networksConstants = require('../../common/ext/networks-constants'); - -const BTC = networksConstants['mainnet'].network; - -export default class XmrAddressProcessor { - _root = false; - - async setBasicRoot(root) { - this._root = root; - } - - /** - * @param {string|Buffer} privateKey - * @param {*} data.publicKey - * @param {*} data.walletHash - * @param {*} data.derivationPath - * @param {*} data.derivationIndex - * @param {*} data.derivationType - * @returns {Promise<{privateKey: string, address: string, addedData: *}>} - */ - async getAddress(privateKey, data = {}, superPrivateData = {}) { - let walletMnemonic = false; - try { - walletMnemonic = await BlocksoftSecrets.getWords({ - currencyCode: 'XMR', - mnemonic: superPrivateData.mnemonic - }); - } catch (e) { - if (config.debug.cryptoErrors) { - console.log( - 'XmrAddressProcessor.getAddress recheck mnemonic error ' + e.message - ); - } - } - if (!walletMnemonic) { - return { - address: 'invalidRecheck1', - privateKey: '' - }; - } - - if ( - typeof data.derivationType !== 'undefined' && - data.derivationType && - data.derivationType !== 'main' - ) { - return false; - } - if ( - typeof data.derivationIndex === 'undefined' || - !data.derivationIndex || - data.derivationIndex === 0 - ) { - const child = this._root.derivePath("m/44'/128'/0'/0/0"); - privateKey = child.privateKey; - } else { - privateKey = Buffer.from(privateKey); - } - const keyPair = bitcoin.ECPair.fromPrivateKey(privateKey, { network: BTC }); - const rawPrivateKey = keyPair.privateKey; - const rawSecretSpendKey = soliditySha3(rawPrivateKey); - const rawSecretSpendKeyBuffer = Buffer.from( - rawSecretSpendKey.substr(2), - 'hex' - ); - - const secretSpendKey = MoneroUtils.sc_reduce32(rawSecretSpendKeyBuffer); - - const secretViewKey = MoneroUtils.hash_to_scalar(secretSpendKey); - - const words = MoneroMnemonic.secret_spend_key_to_words( - MoneroUtils.normString(secretSpendKey), - typeof data.walletHash !== 'undefined' ? data.walletHash : 'none' - ); - if (words !== walletMnemonic) { - return { - address: 'invalidRecheck2', - privateKey: '' - }; - } - - const publicSpendKey = - MoneroUtils.secret_key_to_public_key(secretSpendKey).toString('hex'); - - const publicViewKey = - MoneroUtils.secret_key_to_public_key(secretViewKey).toString('hex'); - - const address = MoneroUtils.pub_keys_to_address( - 0, - publicSpendKey, - publicViewKey - ); - - let mymoneroError = 0; - let linkParamsLogin = {}; - try { - linkParamsLogin = { - address: address, - view_key: MoneroUtils.normString(secretViewKey.toString('hex')), - create_account: true, - generated_locally: true - }; - const resLogin = await AirDAOAxios.post( - 'https://api.mymonero.com:8443/login', - linkParamsLogin - ); - if ( - typeof resLogin.data === 'undefined' || - !resLogin.data || - typeof resLogin.data.new_address === 'undefined' - ) { - throw new Error('no data'); - } - } catch (e) { - AirDAOCryptoLog.err( - 'XmrAddressProcessor !!!mymonero error!!! ' + e.message, - { - linkParamsLogin, - publicSpendKeyL: publicSpendKey.length, - publicViewKeyL: publicViewKey.length - } - ); - mymoneroError = 1; - } - - /* - console.log({ - derivationPath : data.derivationPath, - secretSpendKey, - ss : Buffer.from(secretSpendKey, 'hex'), - secretViewKey, - sv : Buffer.from(secretViewKey, 'hex'), - words, - publicViewKey: publicViewKey.toString('hex'), - publicSpendKey: publicSpendKey.toString('hex'), - address - }) - */ - - return { - address: address, - privateKey: - MoneroUtils.normString(secretSpendKey.toString('hex')) + - '_' + - MoneroUtils.normString(secretViewKey.toString('hex')), - addedData: { - publicViewKey: MoneroUtils.normString(publicViewKey), - publicSpendKey: MoneroUtils.normString(publicSpendKey), - derivationIndex: 0, - mymoneroError - } - }; - } -} diff --git a/crypto/blockchains/xmr/XmrAddressProcessor.ts b/crypto/blockchains/xmr/XmrAddressProcessor.ts deleted file mode 100644 index 93972ca8a..000000000 --- a/crypto/blockchains/xmr/XmrAddressProcessor.ts +++ /dev/null @@ -1,214 +0,0 @@ -/** - * @version 0.11 - * https://coinomi.github.io/bip39-monero/ - * - * - * let mnemonic = '' - * let results = await BlocksoftKeys.discoverAddresses({ mnemonic, fullTree: false, fromIndex: 0, toIndex: 1, currencyCode: ['XMR'] }) - * console.log('r', results['XMR'][0]) - */ -import MoneroUtils from './ext/MoneroUtils'; -import MoneroMnemonic from './ext/MoneroMnemonic'; -import { soliditySha3 } from 'web3-utils'; -import AirDAOAxios from '../../common/AirDAOAxios'; -import AirDAOCryptoLog from '../../common/AirDAOCryptoLog'; -import BlocksoftSecrets from '@crypto/actions/BlocksoftSecrets/BlocksoftSecrets'; -import config from '@constants/config'; - -import bitcoin from 'bitcoinjs-lib'; -import networksConstants from '../../common/ext/networks-constants'; - -const BTC = networksConstants.mainnet.network; - -export default class XmrAddressProcessor { - private _root: boolean | any = false; - - async setBasicRoot(root: any) { - this._root = root; - } - - /** - * @param {string|Buffer} privateKey - * @param {*} data.publicKey - * @param {*} data.walletHash - * @param {*} data.derivationPath - * @param {*} data.derivationIndex - * @param {*} data.derivationType - * @returns {Promise<{privateKey: string, address: string, addedData: *}>} - */ - async getAddress( - privateKey: string | Buffer, - data: any = {}, - superPrivateData: any = {} - ): Promise<{ - address: string; - privateKey: string; - addedData: { - publicViewKey: string; - publicSpendKey: string; - derivationIndex: number; - mymoneroError: number; - }; - }> { - let walletMnemonic: boolean | { hash: any } = false; - try { - walletMnemonic = await BlocksoftSecrets.getWords({ - currencyCode: 'XMR', - mnemonic: superPrivateData.mnemonic - }); - } catch (e: any) { - if (config.debug.cryptoErrors) { - console.log( - 'XmrAddressProcessor.getAddress recheck mnemonic error ' + e.message - ); - } - } - if (!walletMnemonic) { - return { - address: 'invalidRecheck1', - privateKey: '', - addedData: { - publicViewKey: '', - publicSpendKey: '', - derivationIndex: 0, - mymoneroError: 0 - } - }; - } - - if ( - typeof data.derivationType !== 'undefined' && - data.derivationType && - data.derivationType !== 'main' - ) { - return { - address: '', - privateKey: '', - addedData: { - publicViewKey: '', - publicSpendKey: '', - derivationIndex: 0, - mymoneroError: 0 - } - }; - } - if ( - typeof data.derivationIndex === 'undefined' || - !data.derivationIndex || - data.derivationIndex === 0 - ) { - const child = this._root.derivePath("m/44'/128'/0'/0/0"); - privateKey = child.privateKey; - } else { - privateKey = Buffer.from(privateKey); - } - const keyPair = bitcoin.ECPair.fromPrivateKey(privateKey as Buffer, { - network: BTC - }); - const rawPrivateKey = keyPair.privateKey; - // @ts-ignore - const rawSecretSpendKey = soliditySha3(rawPrivateKey); - const rawSecretSpendKeyBuffer = Buffer.from( - // @ts-ignore - rawSecretSpendKey.substr(2), - 'hex' - ); - - const secretSpendKey = MoneroUtils.sc_reduce32(rawSecretSpendKeyBuffer); - - const secretViewKey = MoneroUtils.hash_to_scalar(secretSpendKey); - - const words = MoneroMnemonic.secret_spend_key_to_words( - MoneroUtils.normString(secretSpendKey), - typeof data.walletHash !== 'undefined' ? data.walletHash : 'none' - ); - - if (words !== walletMnemonic.toString()) { - return { - address: 'invalidRecheck2', - privateKey: '', - addedData: { - publicViewKey: '', - publicSpendKey: '', - derivationIndex: 0, - mymoneroError: 0 - } - }; - } - - const publicSpendKey = - MoneroUtils.secret_key_to_public_key(secretSpendKey).toString('hex'); - - const publicViewKey = - MoneroUtils.secret_key_to_public_key(secretViewKey).toString('hex'); - - const address = MoneroUtils.pub_keys_to_address( - 0, - publicSpendKey, - publicViewKey - ); - - let mymoneroError = 0; - let linkParamsLogin = {}; - try { - linkParamsLogin = { - address, - // @ts-ignore - view_key: MoneroUtils.normString(secretViewKey.toString('hex')), - create_account: true, - generated_locally: true - }; - const resLogin = (await AirDAOAxios.post( - 'https://api.mymonero.com:8443/login', - linkParamsLogin - )) as unknown as { data: any }; - if ( - resLogin.data === undefined || - !resLogin.data || - resLogin.data.new_address === undefined - ) { - throw new Error('no data'); - } - } catch (e: any) { - AirDAOCryptoLog.err( - 'XmrAddressProcessor !!!mymonero error!!! ' + e.message, - JSON.stringify({ - linkParamsLogin, - publicSpendKeyL: publicSpendKey.length, - publicViewKeyL: publicViewKey.length - }) - ); - mymoneroError = 1; - } - - /* - console.log({ - derivationPath : data.derivationPath, - secretSpendKey, - ss : Buffer.from(secretSpendKey, 'hex'), - secretViewKey, - sv : Buffer.from(secretViewKey, 'hex'), - words, - publicViewKey: publicViewKey.toString('hex'), - publicSpendKey: publicSpendKey.toString('hex'), - address - }) - */ - - return { - address, - privateKey: - // @ts-ignore - MoneroUtils.normString(secretSpendKey.toString('hex')) + - '_' + - // @ts-ignore - MoneroUtils.normString(secretViewKey.toString('hex')), - addedData: { - publicViewKey: MoneroUtils.normString(publicViewKey), - publicSpendKey: MoneroUtils.normString(publicSpendKey), - derivationIndex: 0, - mymoneroError - } - }; - } -} diff --git a/crypto/blockchains/xmr/XmrScannerProcessor.js b/crypto/blockchains/xmr/XmrScannerProcessor.js deleted file mode 100644 index 291862615..000000000 --- a/crypto/blockchains/xmr/XmrScannerProcessor.js +++ /dev/null @@ -1,412 +0,0 @@ -/** - * @version 0.11 - * https://api.mymonero.com:8443/get_address_info - */ - -import BlocksoftUtils from '@crypto/common/BlocksoftUtils'; -import AirDAOAxios from '@crypto/common/AirDAOAxios'; -import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; -import BlocksoftPrivateKeysUtils from '@crypto/common/BlocksoftPrivateKeysUtils'; - -import MoneroUtilsParser from './ext/MoneroUtilsParser'; - -import { showModal } from '@app/appstores/Stores/Modal/ModalActions'; -import { strings } from '@app/services/i18n'; -import config from '@app/config/config'; - -const CACHE_VALID_TIME = 30000; // 30 seconds -const CACHE = {}; -const NEVER_LOGIN = {}; -let CACHE_SHOWN_ERROR = 0; - -export default class XmrScannerProcessor { - /** - * @private - */ - _serverUrl = false; - - _blocksToConfirm = 30; - - _maxBlockNumber = 500000000; - - constructor(settings) { - this._settings = settings; - } - - async _getCache(address, additionalData, walletHash) { - if (typeof CACHE[address] !== 'undefined') { - CACHE[address].provider = 'mymonero-cache-all'; - return CACHE[address]; - } else { - return false; - } - } - - /** - * @param address - * @param additionalData - * @param walletHash - * @returns {Promise} - * @private - */ - async _get(address, additionalData, walletHash) { - AirDAOCryptoLog.log( - 'XMR XmrScannerProcessor._get ' + walletHash + ' ' + address - ); - const now = new Date().getTime(); - if ( - typeof CACHE[address] !== 'undefined' && - now - CACHE[address].time < CACHE_VALID_TIME - ) { - CACHE[address].provider = 'mymonero-cache'; - return CACHE[address]; - } - - //@todo nodes support - //this._serverUrl = await settingsActions.getSetting('xmrServer') - //if (!this._serverUrl || this._serverUrl === 'false') { - this._serverUrl = 'api.mymonero.com:8443'; - //} - - let link = this._serverUrl.trim(); - if (link.substr(0, 4).toLowerCase() !== 'http') { - link = 'https://' + this._serverUrl; - } - if (link[link.length - 1] !== '/') { - link = link + '/'; - } - - const discoverFor = { - addressToCheck: address, - walletHash: walletHash, - currencyCode: 'XMR', - derivationPath: "m/44'/0'/0'/0/0", - derivationIndex: - typeof additionalData.derivationIndex !== 'undefined' - ? additionalData.derivationIndex - : 0 - }; - - const result = await BlocksoftPrivateKeysUtils.getPrivateKey( - discoverFor, - 'XmrScannerProcessor' - ); // privateSpend_privateView - const keys = result.privateKey.split('_'); - const spendKey = keys[0]; // private spend and view keys - let viewKey = keys[1]; - while (viewKey.length < 64) { - viewKey += '0'; - } - const linkParams = { address: address, view_key: viewKey }; - - let res = false; - try { - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' XmrScannerProcessor._get start ' + - link + - 'get_address_info', - JSON.stringify(linkParams) - ); - res = await AirDAOAxios.post(link + 'get_address_info', linkParams); - } catch (e) { - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' XmrScannerProcessor._get error ' + - e.message, - JSON.stringify(linkParams) - ); - if ( - CACHE_SHOWN_ERROR === 0 && - e.message.indexOf('invalid address and/or view key') !== -1 - ) { - showModal({ - type: 'INFO_MODAL', - icon: false, - title: strings('modal.walletLog.sorry'), - description: strings('settings.walletList.needReinstallXMR') - }); - CACHE_SHOWN_ERROR++; - if (CACHE_SHOWN_ERROR > 100) { - CACHE_SHOWN_ERROR = 0; - } - } - } - if (!res || !res.data) { - if (typeof NEVER_LOGIN[address] === 'undefined') { - const linkParamsLogin = { - address: address, - view_key: viewKey, - create_account: true, - generated_locally: true - }; - try { - await AirDAOAxios.post( - 'https://api.mymonero.com:8443/login', - linkParamsLogin - ); // login needed - } catch (e) { - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' XmrScannerProcessor._get login error ' + - e.message, - linkParamsLogin - ); - if ( - CACHE_SHOWN_ERROR === 0 && - e.message.indexOf('invalid address and/or view key') !== -1 - ) { - showModal({ - type: 'INFO_MODAL', - icon: false, - title: strings('modal.walletLog.sorry'), - description: strings('settings.walletList.needReinstallXMR') - }); - CACHE_SHOWN_ERROR++; - if (CACHE_SHOWN_ERROR > 100) { - CACHE_SHOWN_ERROR = 0; - } - } - } - } - return false; - } - if (typeof res.data.spent_outputs === 'undefined') { - throw new Error( - 'XMR XmrScannerProcessor._get nothing loaded for address ' + link - ); - } - - let parsed = false; - try { - parsed = await MoneroUtilsParser.parseAddressInfo( - address, - res.data, - viewKey, - additionalData.publicSpendKey, - spendKey - ); - } catch (e) { - if (config.debug.cryptoErrors) { - console.log( - 'XMR XmrScannerProcessor._get MoneroUtilsParser.parseAddressInfo error ' + - e.message - ); - } - await AirDAOCryptoLog.log( - 'XMR XmrScannerProcessor._get MoneroUtilsParser.parseAddressInfo error ' + - e.message - ); - } - - const res2 = await AirDAOAxios.postWithoutBraking( - link + 'get_address_txs', - linkParams - ); - if (!res2 || !res2.data) { - return false; - } - - let parsed2 = false; - try { - parsed2 = await MoneroUtilsParser.parseAddressTransactions( - address, - res2.data, - viewKey, - additionalData.publicSpendKey, - spendKey - ); - } catch (e) { - if (config.debug.cryptoErrors) { - console.log( - 'XMR XmrScannerProcessor._get MoneroUtilsParser.parseAddressTransactions error ' + - e.message - ); - } - await AirDAOCryptoLog.log( - 'XMR XmrScannerProcessor._get MoneroUtilsParser.parseAddressTransactions error ' + - e.message - ); - } - - if (parsed && parsed2) { - CACHE[address] = { - outputs: parsed?.spent_outputs, - transactions: - typeof parsed2.serialized_transactions !== 'undefined' - ? parsed2.serialized_transactions - : parsed2.transactions, - balance: - typeof parsed.total_received_String !== 'undefined' - ? BlocksoftUtils.diff( - parsed.total_received_String, - parsed.total_sent_String - ) - : BlocksoftUtils.diff(parsed.total_received, parsed.total_sent), - account_scan_start_height: parsed2.account_scan_start_height, - scanned_block_height: parsed2.account_scanned_block_height, - account_scanned_height: parsed2.account_scanned_height, - blockchain_height: parsed2.blockchain_height, - transaction_height: parsed2.transaction_height, - time: now, - provider: 'mymonero' - }; - return CACHE[address]; - } - return false; - } - - /** - * @param {string} address - * @param {*} additionalData - * @param {string} walletHash - * @return {Promise<{balance:*, unconfirmed:*, provider:string}>} - */ - async getBalanceBlockchain(address, additionalData, walletHash) { - if (address === 'invalidRecheck1') { - return { balance: 0, unconfirmed: 0, provider: 'error' }; - } - const res = await this._get(address, additionalData, walletHash); - if (!res) { - return false; - } - return { - balance: res.balance, - unconfirmed: 0, - provider: res.provider, - time: res.time - }; - } - - /** - * @param {string} scanData.account.address - * @param {*} scanData.additional - * @param {string} scanData.account.walletHash - * @return {Promise} - */ - async getTransactionsBlockchain(scanData, source = '') { - const address = scanData.account.address.trim(); - if (address === 'invalidRecheck1') { - return []; - } - const additionalData = scanData.additional; - const walletHash = scanData.account.walletHash; - const res = await this._get(address, additionalData, walletHash); - if (!res || typeof res === 'undefined') return []; - - if (typeof res.transactions === 'undefined' || !res.transactions) return []; - let tx; - const transactions = []; - - for (tx of res.transactions) { - const transaction = await this._unifyTransaction( - address, - res.scanned_block_height, - tx - ); - if (transaction) { - transactions.push(transaction); - } - } - return transactions; - } - - /** - * - * @param {string} address - * @param {string} lastBlock - * @param {Object} transaction - * @param {BigInteger} transaction.amount BigInteger {_d: Array(2), _s: -1} - * @param {string} transaction.approx_float_amount -0.00002724 - * @param {string} transaction.coinbase false - * @param {string} transaction.fee "27240000" - * @param {string} transaction.hash "ac319a3240f15dab342102fe248d3b95636f8a0bbfa962a5645521fac8fb86d3" - * @param {string} transaction.height 2152183 - * @param {string} transaction.id 10506991 - * @param {string} transaction.mempool: false - * @param {string} transaction.mixin 10 - * @param {string} transaction.payment_id "" - * @param {string} transaction.spent_outputs [{…}] - * @param {string} transaction.timestamp Tue Jul 28 2020 18:10:26 GMT+0300 (Восточная Европа, летнее время) {} - * @param {string} transaction.total_received "12354721582" - * @param {BigInteger} transaction.total_sent BigInteger {_d: Array(2), _s: 1} - * @param {string} transaction.unlock_time - * @return {Promise} - * @private - */ - async _unifyTransaction(address, lastBlock, transaction) { - let transactionStatus = 'new'; - transaction.confirmations = lastBlock * 1 - transaction.height * 1; - //if (transaction.mempool === false) { - if (transaction.confirmations >= this._blocksToConfirm) { - transactionStatus = 'success'; - } else if (transaction.confirmations > 0) { - transactionStatus = 'confirming'; - } - // } - - if (typeof transaction.unlock_time !== 'undefined') { - const unlockTime = transaction.unlock_time * 1; - if (unlockTime > 0) { - if (unlockTime < this._maxBlockNumber) { - // then unlock time is block height - if (unlockTime > lastBlock) { - transactionStatus = 'locked'; - } - } else { - // then unlock time is s timestamp as TimeInterval - const now = new Date().getTime(); - if (unlockTime > now) { - transactionStatus = 'locked'; - } - } - } - } - - let direction = 'self'; - let amount; - if (transaction.total_received !== '0') { - if (transaction.total_sent !== '0') { - const diff = BlocksoftUtils.diff( - transaction.total_sent, - transaction.total_received - ); - if (diff > 0) { - direction = 'outcome'; - amount = diff; - } else { - direction = 'income'; - amount = -1 * diff; - } - } else { - direction = 'income'; - amount = transaction.total_received; - } - } else if (transaction.total_sent !== '0') { - direction = 'outcome'; - amount = transaction.total_sent; - } - let formattedTime; - try { - formattedTime = BlocksoftUtils.toDate(transaction.timestamp); - } catch (e) { - e.message += - ' timestamp error transaction data ' + JSON.stringify(transaction); - throw e; - } - - return { - transactionHash: transaction.hash, - blockHash: transaction.id, - blockNumber: +transaction.height, - blockTime: formattedTime, - blockConfirmations: transaction.confirmations, - transactionDirection: direction, - addressFrom: '', - addressTo: '', - addressAmount: amount, - transactionStatus: transactionStatus, - transactionFee: transaction.fee - }; - } -} diff --git a/crypto/blockchains/xmr/XmrScannerProcessor.ts b/crypto/blockchains/xmr/XmrScannerProcessor.ts deleted file mode 100644 index 527d19875..000000000 --- a/crypto/blockchains/xmr/XmrScannerProcessor.ts +++ /dev/null @@ -1,450 +0,0 @@ -/** - * @version 0.11 - * https://api.mymonero.com:8443/get_address_info - */ - -import BlocksoftUtils from '@crypto/common/AirDAOUtils'; -import AirDAOAxios from '@crypto/common/AirDAOAxios'; -import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; -import BlocksoftPrivateKeysUtils from '@crypto/common/AirDAOPrivateKeysUtils'; - -import MoneroUtilsParser from './ext/MoneroUtilsParser'; - -import config from '@constants/config'; -import { Toast, ToastPosition } from '@components/modular'; - -const CACHE_VALID_TIME = 30000; -const CACHE = {}; -const NEVER_LOGIN = {}; -let CACHE_SHOWN_ERROR = 0; - -export default class XmrScannerProcessor { - /** - * @private - */ - _serverUrl = false; - - _blocksToConfirm = 30; - - _maxBlockNumber = 500000000; - - constructor(settings: any) { - this._settings = settings; - } - - async _getCache( - address: string | number, - additionalData: any, - walletHash: any - ) { - if (typeof CACHE[address] !== 'undefined') { - CACHE[address].provider = 'mymonero-cache-all'; - return CACHE[address]; - } else { - return false; - } - } - - /** - * @param address - * @param additionalData - * @param walletHash - * @returns {Promise} - * @private - */ - async _get( - address: string, - additionalData: { derivationIndex: any; publicSpendKey: string }, - walletHash: string - ) { - AirDAOCryptoLog.log( - 'XMR XmrScannerProcessor._get ' + walletHash + ' ' + address - ); - const now = new Date().getTime(); - if ( - typeof CACHE[address] !== 'undefined' && - now - CACHE[address].time < CACHE_VALID_TIME - ) { - CACHE[address].provider = 'mymonero-cache'; - return CACHE[address]; - } - - // @todo nodes support - // this._serverUrl = await settingsActions.getSetting('xmrServer') - // if (!this._serverUrl || this._serverUrl === 'false') { - this._serverUrl = 'api.mymonero.com:8443'; - // } - - let link = this._serverUrl.trim(); - if (link.substr(0, 4).toLowerCase() !== 'http') { - link = 'https://' + this._serverUrl; - } - if (link[link.length - 1] !== '/') { - link = link + '/'; - } - - const discoverFor = { - addressToCheck: address, - walletHash, - currencyCode: 'XMR', - derivationPath: "m/44'/0'/0'/0/0", - derivationIndex: - typeof additionalData.derivationIndex !== 'undefined' - ? additionalData.derivationIndex - : 0 - }; - - const result = await BlocksoftPrivateKeysUtils.getPrivateKey( - discoverFor, - 'XmrScannerProcessor' - ); // privateSpend_privateView - const keys = result.privateKey.split('_'); - const spendKey = keys[0]; // private spend and view keys - let viewKey = keys[1]; - while (viewKey.length < 64) { - viewKey += '0'; - } - const linkParams = { address, view_key: viewKey }; - - let res = false; - try { - AirDAOCryptoLog.log( - this._settings.currencyCode + - `' XmrScannerProcessor._get start ' + - ${link} + - 'get_address_info', - ${JSON.stringify(linkParams)}` - ); - res = await AirDAOAxios.post(link + 'get_address_info', linkParams); - } catch (e: any) { - AirDAOCryptoLog.log( - this._settings.currencyCode + - `' XmrScannerProcessor._get error ' + - ${e.message}, - ${JSON.stringify(linkParams)}` - ); - if ( - CACHE_SHOWN_ERROR === 0 && - e.message.indexOf('invalid address and/or view key') !== -1 - ) { - // showModal({ - // type: 'INFO_MODAL', - // icon: false, - // title: strings('modal.walletLog.sorry'), - // description: strings('settings.walletList.needReinstallXMR') - // }); - Toast.show({ - message: 'Need reinstall XMR', - title: 'Sorry', - type: ToastPosition.Top - }); - CACHE_SHOWN_ERROR++; - if (CACHE_SHOWN_ERROR > 100) { - CACHE_SHOWN_ERROR = 0; - } - } - } - if (!res || !res.data) { - if (typeof NEVER_LOGIN[address] === 'undefined') { - const linkParamsLogin = { - address, - view_key: viewKey, - create_account: true, - generated_locally: true - }; - try { - await AirDAOAxios.post( - 'https://api.mymonero.com:8443/login', - linkParamsLogin - ); // login needed - } catch (e: any) { - AirDAOCryptoLog.log( - this._settings.currencyCode + - `' XmrScannerProcessor._get login error ' + - ${e.message}, - ${linkParamsLogin}` - ); - if ( - CACHE_SHOWN_ERROR === 0 && - e.message.indexOf('invalid address and/or view key') !== -1 - ) { - // showModal({ - // type: 'INFO_MODAL', - // icon: false, - // title: strings('modal.walletLog.sorry'), - // description: strings('settings.walletList.needReinstallXMR') - // }); - Toast.show({ - message: 'Need reinstall XMR', - title: 'Sorry', - type: ToastPosition.Top - }); - CACHE_SHOWN_ERROR++; - if (CACHE_SHOWN_ERROR > 100) { - CACHE_SHOWN_ERROR = 0; - } - } - } - } - return false; - } - if (typeof res.data.spent_outputs === 'undefined') { - throw new Error( - 'XMR XmrScannerProcessor._get nothing loaded for address ' + link - ); - } - - let parsed = false; - try { - parsed = await MoneroUtilsParser.parseAddressInfo( - address, - res.data, - viewKey, - additionalData.publicSpendKey, - spendKey - ); - } catch (e: any) { - if (config.debug.cryptoErrors) { - console.log( - 'XMR XmrScannerProcessor._get MoneroUtilsParser.parseAddressInfo error ' + - e.message - ); - } - await AirDAOCryptoLog.log( - 'XMR XmrScannerProcessor._get MoneroUtilsParser.parseAddressInfo error ' + - e.message - ); - } - - const res2 = await AirDAOAxios.postWithoutBraking( - link + 'get_address_txs', - linkParams - ); - if (!res2 || !res2.data) { - return false; - } - - let parsed2 = false; - try { - parsed2 = await MoneroUtilsParser.parseAddressTransactions( - address, - res2.data, - viewKey, - additionalData.publicSpendKey, - spendKey - ); - } catch (e: any) { - if (config.debug.cryptoErrors) { - console.log( - 'XMR XmrScannerProcessor._get MoneroUtilsParser.parseAddressTransactions error ' + - e.message - ); - } - await AirDAOCryptoLog.log( - 'XMR XmrScannerProcessor._get MoneroUtilsParser.parseAddressTransactions error ' + - e.message - ); - } - - if (parsed && parsed2) { - CACHE[address] = { - outputs: parsed?.spent_outputs, - transactions: - typeof parsed2.serialized_transactions !== 'undefined' - ? parsed2.serialized_transactions - : parsed2.transactions, - balance: - typeof parsed.total_received_String !== 'undefined' - ? BlocksoftUtils.diff( - parsed.total_received_String, - parsed.total_sent_String - ) - : BlocksoftUtils.diff(parsed.total_received, parsed.total_sent), - account_scan_start_height: parsed2.account_scan_start_height, - scanned_block_height: parsed2.account_scanned_block_height, - account_scanned_height: parsed2.account_scanned_height, - blockchain_height: parsed2.blockchain_height, - transaction_height: parsed2.transaction_height, - time: now, - provider: 'mymonero' - }; - return CACHE[address]; - } - return false; - } - - /** - * @param {string} address - * @param {*} additionalData - * @param {string} walletHash - * @return {Promise<{balance:*, unconfirmed:*, provider:string}>} - */ - async getBalanceBlockchain( - address: string, - additionalData: { derivationIndex: any; publicSpendKey: string }, - walletHash: string - ) { - if (address === 'invalidRecheck1') { - return { balance: 0, unconfirmed: 0, provider: 'error' }; - } - const res = await this._get(address, additionalData, walletHash); - if (!res) { - return false; - } - return { - balance: res.balance, - unconfirmed: 0, - provider: res.provider, - time: res.time - }; - } - - /** - * @param {string} scanData.account.address - * @param {*} scanData.additional - * @param {string} scanData.account.walletHash - * @return {Promise} - */ - async getTransactionsBlockchain(scanData: { - account: { address: string; walletHash: any }; - additional: any; - }) { - const address = scanData.account.address.trim(); - if (address === 'invalidRecheck1') { - return []; - } - const additionalData = scanData.additional; - const walletHash = scanData.account.walletHash; - const res = await this._get(address, additionalData, walletHash); - if (!res || typeof res === 'undefined') return []; - - if (typeof res.transactions === 'undefined' || !res.transactions) return []; - let tx; - const transactions = []; - - for (tx of res.transactions) { - const transaction = await this._unifyTransaction( - address, - res.scanned_block_height, - tx - ); - if (transaction) { - transactions.push(transaction); - } - } - return transactions; - } - - /** - * - * @param {string} address - * @param {string} lastBlock - * @param {Object} transaction - * @param {BigInteger} transaction.amount BigInteger {_d: Array(2), _s: -1} - * @param {string} transaction.approx_float_amount -0.00002724 - * @param {string} transaction.coinbase false - * @param {string} transaction.fee "27240000" - * @param {string} transaction.hash "ac319a3240f15dab342102fe248d3b95636f8a0bbfa962a5645521fac8fb86d3" - * @param {string} transaction.height 2152183 - * @param {string} transaction.id 10506991 - * @param {string} transaction.mempool: false - * @param {string} transaction.mixin 10 - * @param {string} transaction.payment_id "" - * @param {string} transaction.spent_outputs [{…}] - * @param {string} transaction.timestamp Tue Jul 28 2020 18:10:26 GMT+0300 (Восточная Европа, летнее время) {} - * @param {string} transaction.total_received "12354721582" - * @param {BigInteger} transaction.total_sent BigInteger {_d: Array(2), _s: 1} - * @param {string} transaction.unlock_time - * @return {Promise} - * @private - */ - async _unifyTransaction( - address: string, - lastBlock: number, - transaction: { - confirmations: number; - height: number; - unlock_time: number; - total_received: string; - total_sent: string; - timestamp: any; - hash: any; - id: any; - fee: any; - } - ) { - let transactionStatus = 'new'; - transaction.confirmations = lastBlock - transaction.height; - // if (transaction.mempool === false) { - if (transaction.confirmations >= this._blocksToConfirm) { - transactionStatus = 'success'; - } else if (transaction.confirmations > 0) { - transactionStatus = 'confirming'; - } - // } - - if (typeof transaction.unlock_time !== 'undefined') { - const unlockTime = transaction.unlock_time; - if (unlockTime > 0) { - if (unlockTime < this._maxBlockNumber) { - // then unlock time is block height - if (unlockTime > lastBlock) { - transactionStatus = 'locked'; - } - } else { - // then unlock time is s timestamp as TimeInterval - const now = new Date().getTime(); - if (unlockTime > now) { - transactionStatus = 'locked'; - } - } - } - } - - let direction = 'self'; - let amount; - if (transaction.total_received !== '0') { - if (transaction.total_sent !== '0') { - const diff = BlocksoftUtils.diff( - transaction.total_sent, - transaction.total_received - ); - if (diff > 0) { - direction = 'outcome'; - amount = diff; - } else { - direction = 'income'; - amount = -1 * diff; - } - } else { - direction = 'income'; - amount = transaction.total_received; - } - } else if (transaction.total_sent !== '0') { - direction = 'outcome'; - amount = transaction.total_sent; - } - let formattedTime; - try { - formattedTime = BlocksoftUtils.toDate(transaction.timestamp); - } catch (e: any) { - e.message += - ' timestamp error transaction data ' + JSON.stringify(transaction); - throw e; - } - - return { - transactionHash: transaction.hash, - blockHash: transaction.id, - blockNumber: +transaction.height, - blockTime: formattedTime, - blockConfirmations: transaction.confirmations, - transactionDirection: direction, - addressFrom: '', - addressTo: '', - addressAmount: amount, - transactionStatus, - transactionFee: transaction.fee - }; - } -} diff --git a/crypto/blockchains/xmr/XmrSecretsProcessor.ts b/crypto/blockchains/xmr/XmrSecretsProcessor.ts deleted file mode 100644 index ee64d788c..000000000 --- a/crypto/blockchains/xmr/XmrSecretsProcessor.ts +++ /dev/null @@ -1,72 +0,0 @@ -/** - * @version 0.11 - * https://github.com/Coinomi/bip39-coinomi/releases - */ -// import AirDAOKeys from '../../actions/AirDAOKeys/AirDAOKeys'; -import AirDAOKeys from '@lib/helpers/AirDAOKeys'; - -import { soliditySha3 } from 'web3-utils'; -import MoneroUtils from './ext/MoneroUtils'; -import MoneroMnemonic from './ext/MoneroMnemonic'; -import networksConstants from '@crypto/common/ext/networks-constants'; - -const bip32 = require('bip32'); -const bitcoin = require('bitcoinjs-lib'); - -const BTC = networksConstants.mainnet.network; - -export default class XmrSecretsProcessor { - /** - * @param {string} data.mnemonic - */ - async getWords(data: { mnemonic: string }) { - const seed = await AirDAOKeys.getSeedCached(data.mnemonic); - const seedHex = seed.toString('hex'); - if (seedHex.length < 128) { - throw new Error('bad seedHex'); - } - const root = bip32.fromSeed(seed); - const child = root.derivePath("m/44'/128'/0'/0/0"); - const keyPair = bitcoin.ECPair.fromPrivateKey(child.privateKey, { - network: BTC - }); - - const rawPrivateKey = keyPair.privateKey; - const rawSecretSpendKey = soliditySha3(rawPrivateKey); - if (!rawSecretSpendKey) { - throw new Error('bad private key'); - } - const rawSecretSpendKeyBuffer = Buffer.from( - rawSecretSpendKey.substr(2), - 'hex' - ); - - let secretSpendKey = MoneroUtils.sc_reduce32(rawSecretSpendKeyBuffer); - const secretSpendLength = secretSpendKey.length; - if (secretSpendLength < 64) { - for (let i = secretSpendLength; i < 64; i++) { - secretSpendKey = secretSpendKey + '0'; - } - } - - const secretViewKey = MoneroUtils.hash_to_scalar(secretSpendKey); - - // @ts-ignore - const words = MoneroMnemonic.secret_spend_key_to_words(secretSpendKey); - - const publicSpendKey = MoneroUtils.secret_key_to_public_key(secretSpendKey); - - const publicViewKey = MoneroUtils.secret_key_to_public_key(secretViewKey); - MoneroUtils.pub_keys_to_address(0, publicSpendKey, publicViewKey); - /*console.log({ - secretSpendKey, - secretViewKey, - words, - publicViewKey: publicViewKey.toString('hex'), - publicSpendKey: publicSpendKey.toString('hex'), - address - })*/ - - return words; - } -} diff --git a/crypto/blockchains/xmr/XmrTransferProcessor.ts b/crypto/blockchains/xmr/XmrTransferProcessor.ts deleted file mode 100644 index b6297f10d..000000000 --- a/crypto/blockchains/xmr/XmrTransferProcessor.ts +++ /dev/null @@ -1,426 +0,0 @@ -/** - * @version 0.20 - */ -import AirDAOCryptoLog from '../../common/AirDAOCryptoLog'; -import MoneroUtilsParser from './ext/MoneroUtilsParser'; -import XmrSendProvider from './providers/XmrSendProvider'; -import XmrUnspentsProvider from './providers/XmrUnspentsProvider'; -import config from '@constants/config'; -import BlocksoftPrettyNumbers from '@crypto/common/AirDAOPrettyNumbers'; -import { AirDAOBlockchainTypes } from '@crypto/blockchains/AirDAOBlockchainTypes'; - -type ModifiedSendTxResult = { - rawOnly: boolean; - raw: any; -}; - -export default class XmrTransferProcessor - implements AirDAOBlockchainTypes.TransferProcessor -{ - private sendProvider: XmrSendProvider; - private unspentsProvider: XmrUnspentsProvider; - private _settings: any; - - constructor(settings: any) { - this._settings = settings; - this.sendProvider = new XmrSendProvider(settings); - this.unspentsProvider = new XmrUnspentsProvider(settings); - } - - needPrivateForFee(): boolean { - return true; - } - - checkSendAllModal(data: { currencyCode: any }): boolean { - return true; - } - - async getFeeRate( - data: AirDAOBlockchainTypes.TransferData, - privateData: AirDAOBlockchainTypes.TransferPrivateData, - additionalData: AirDAOBlockchainTypes.TransferAdditionalData = {} - ): Promise { - const result: AirDAOBlockchainTypes.FeeRateResult = { - selectedFeeIndex: -1 - } as AirDAOBlockchainTypes.FeeRateResult; - - // @ts-ignore - if (data.amount * 1 <= 0) { - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' XmrTransferProcessor.getFeeRate ' + - data.addressFrom + - ' => ' + - data.addressTo + - ' skipped as zero amount' - ); - return result; - } - - if ( - typeof data.accountJson === 'undefined' || - !data.accountJson || - typeof data.accountJson.publicSpendKey === 'undefined' - ) { - throw new Error('XmrTransferProcessor public spend key is required'); - } - const keys = privateData.privateKey.split('_'); - const privSpendKey = keys[0]; - const privViewKey = keys[1]; - const pubSpendKey = data.accountJson.publicSpendKey; - - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' XmrTransferProcessor.getFeeRate newSender ' + - data.addressFrom + - ' => ' + - data.addressTo + - ' started amount: ' + - data.amount - ); - - const apiClient = this.unspentsProvider; - - let core = await MoneroUtilsParser.getCore(); - if (!core || typeof core === 'undefined') { - core = await MoneroUtilsParser.getCore(); - } - - result.fees = []; - - const logFees = []; - let noBalanceError = false; - apiClient.init(); - - const unspentOuts = await apiClient._getUnspents( - { - address: data.addressFrom, - view_key: privViewKey, - amount: '0', - app_name: 'MyMonero', - app_version: '1.3.2', - dust_threshold: '2000000000', - mixin: 15, - use_dust: true - }, - // @ts-ignore - false - ); - - for (let i = 1; i <= 4; i++) { - try { - await AirDAOCryptoLog.log( - this._settings.currencyCode + - ' XmrTransferProcessor.getFeeRate ' + - data.addressFrom + - ' => ' + - data.addressTo + - ' start amount: ' + - data.amount + - ' fee ' + - i - ); - - const fee = await core.createTransaction({ - destinations: [ - { - to_address: data.addressTo, - // @ts-ignore - send_amount: data.isTransferAll - ? 0 - : BlocksoftPrettyNumbers.setCurrencyCode('XMR').makePretty( - data.amount - ) - } - ], - shouldSweep: data.isTransferAll, - address: data.addressFrom, - privateViewKey: privViewKey, - privateSpendKey: privSpendKey, - publicSpendKey: pubSpendKey, - priority: i, - nettype: 'MAINNET', - unspentOuts, - // @ts-ignore - randomOutsCb: (numberOfOuts: number) => { - const amounts = []; - // tslint:disable-next-line:no-shadowed-variable - for (let i = 0; i < numberOfOuts; i++) { - amounts.push('0'); - } - return apiClient._getRandomOutputs({ - amounts, - app_name: 'MyMonero', - app_version: '1.3.2', - count: 16 - }); - } - }); - - if (typeof fee !== 'undefined' && fee && typeof fee.used_fee) { - const tmp = { - langMsg: 'xmr_speed_' + i, - feeForTx: fee.used_fee, - blockchainData: { - secretTxKey: fee.tx_key, - rawTxHex: fee.serialized_signed_tx, - rawTxHash: fee.tx_hash, - usingOuts: fee.using_outs, - simplePriority: i - } - } as AirDAOBlockchainTypes.Fee; - - const logTmp = { - langMsg: 'xmr_speed_' + i, - feeForTx: fee.used_fee, - blockchainData: { - secretTxKey: fee.tx_key, - rawTxHash: fee.tx_hash, - usingOuts: fee.using_outs, - simplePriority: i - }, - amountForTx: '?' - }; - if (typeof fee.using_amount !== 'undefined') { - tmp.amountForTx = fee.using_amount; - logTmp.amountForTx = fee.using_amount; - } else { - tmp.amountForTx = data.amount; - logTmp.amountForTx = data.amount; - } - tmp.addressToTx = data.addressTo; - result.fees.push(tmp); - logFees.push(logTmp); - } - } catch (e: any) { - if (e.message.indexOf('pendable balance too low') !== -1) { - // do nothing - noBalanceError = true; - break; - } else { - if (config.debug.cryptoErrors) { - console.log('XmrTransferProcessor error ', e); - } - if ( - e.message.indexOf( - 'An error occurred while getting decoy outputs' - ) !== -1 - ) { - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' XmrTransferProcessor error will go out bad decoy' - ); - throw new Error('SERVER_RESPONSE_BAD_CODE'); - } else if (e.message.indexOf('decode address') !== -1) { - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' XmrTransferProcessor error will go out' - ); - throw new Error('SERVER_RESPONSE_BAD_DESTINATION'); - } else if (e.message.indexOf('Not enough spendables') !== -1) { - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' XmrTransferProcessor error not enough' - ); - throw new Error('SERVER_RESPONSE_NO_RESPONSE_XMR'); - } else { - AirDAOCryptoLog.err( - this._settings.currencyCode + - ' XmrTransferProcessor.getFeeRate ' + - data.addressFrom + - ' => ' + - data.addressTo + - ' finished amount: ' + - data.amount + - ' error fee ' + - i + - ': ' + - e.message - ); - throw e; - } - } - } - } - - if (result.fees.length === 0 && noBalanceError) { - throw new Error('SERVER_RESPONSE_NO_RESPONSE_XMR'); - } - - // @ts-ignore - AirDAOCryptoLog.log( - this._settings.currencyCode + - `' XmrTransferProcessor.getFeeRate ' + - ${data.addressFrom} + - ' => ' + - ${data.addressTo} + - ' finished amount: ' + - ${data.amount} + - ' fee: ', - ${logFees}` - ); - - if (result.fees.length < 3) { - result.selectedFeeIndex = result.fees.length - 1; - } else { - result.selectedFeeIndex = 2; - } - return result; - } - - async getTransferAllBalance( - data: AirDAOBlockchainTypes.TransferData, - privateData: AirDAOBlockchainTypes.TransferPrivateData, - additionalData: AirDAOBlockchainTypes.TransferAdditionalData = {} - ): Promise { - const balance = data.amount; - - AirDAOCryptoLog.log( - this._settings.currencyCode + - `' XmrTransferProcessor.getTransferAllBalance ', - ${data.addressFrom + ' => ' + balance}` - ); - - data.isTransferAll = true; - const result = await this.getFeeRate(data, privateData, additionalData); - // @ts-ignore - if (!result || result.selectedFeeIndex < 0) { - return { - selectedTransferAllBalance: '0', - selectedFeeIndex: -2, - fees: [], - shouldShowFees: false, - countedForBasicBalance: balance - }; - } - // @ts-ignore - return { - ...result, - shouldShowFees: false, - selectedTransferAllBalance: - result.fees[result.selectedFeeIndex].amountForTx, - countedForBasicBalance: balance - }; - } - - // @ts-ignore - async sendTx( - data: AirDAOBlockchainTypes.TransferData, - privateData: AirDAOBlockchainTypes.TransferPrivateData, - uiData: AirDAOBlockchainTypes.TransferUiData - ): Promise { - if (typeof privateData.privateKey === 'undefined') { - throw new Error('XMR transaction required privateKey'); - } - if (typeof data.addressTo === 'undefined') { - throw new Error('XMR transaction required addressTo'); - } - - if (typeof uiData.selectedFee === 'undefined') { - throw new Error('XMR transaction required selectedFee'); - } - - AirDAOCryptoLog.log( - this._settings.currencyCode + - `' XmrTransferProcessor.sendTx started ' + - ${data.addressFrom} + - '=>' + - ${data.addressTo} + - ' fee', - ${uiData.selectedFee}` - ); - let foundFee = uiData?.selectedFee; - if (data.addressTo !== uiData.selectedFee.addressToTx) { - try { - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' XmrTransferProcessor.sendTx rechecked ' + - data.addressFrom + - '=>' + - data.addressTo + - ' fee rebuild start as got tx to ' + - uiData.selectedFee.addressToTx - ); - const newSelectedFee = await this.getFeeRate(data, privateData); - if ( - typeof newSelectedFee.fees === 'undefined' || - !newSelectedFee.fees - ) { - throw new Error('no fees'); - } - foundFee = newSelectedFee.fees[newSelectedFee.selectedFeeIndex]; - for (const fee of newSelectedFee.fees) { - if (fee.langMsg === uiData.selectedFee.langMsg) { - foundFee = fee; - } - } - AirDAOCryptoLog.log( - this._settings.currencyCode + - `' XmrTransferProcessor.sendTx rechecked ' + - ${data.addressFrom} + - '=>' + - ${data.addressTo} + - ' found fee', - ${foundFee}` - ); - } catch (e: any) { - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' XmrTransferProcessor.sendTx rechecked ' + - data.addressFrom + - '=>' + - data.addressTo + - ' fee rebuild error ' + - e.message - ); - throw new Error('XMR transaction invalid output - please try again'); - } - } - const rawTxHex = foundFee?.blockchainData?.rawTxHex; - const rawTxHash = foundFee?.blockchainData?.rawTxHash; - const secretTxKey = foundFee?.blockchainData?.secretTxKey; - const usingOuts = foundFee?.blockchainData?.usingOuts; - - const keys = privateData.privateKey.split('_'); - const privViewKey = keys[1]; - - if (typeof rawTxHex === 'undefined') { - throw new Error('SERVER_RESPONSE_NO_RESPONSE_XMR'); - } - - if ( - typeof uiData !== 'undefined' && - typeof uiData.selectedFee !== 'undefined' && - typeof uiData.selectedFee.rawOnly !== 'undefined' && - uiData.selectedFee.rawOnly - ) { - // TODO fix this ts error - return { rawOnly: uiData.selectedFee.rawOnly, raw: rawTxHex }; - } - - const send = { - address: data.addressFrom, - tx: rawTxHex, - privViewKey, - usingOuts, - unspentsProvider: this.unspentsProvider - }; - - // @ts-ignore - AirDAOCryptoLog.log( - this._settings.currencyCode + ' XmrTransferProcessor.sendTx result', - send - ); - - // @ts-ignore - if (send.status === 'OK') { - return { transactionHash: rawTxHash, transactionJson: { secretTxKey } }; - } else { - throw new Error( - this._settings.currencyCode + - ' XmrTransferProcessor.sendTx status error ' + - JSON.stringify(send) - ); - } - } -} diff --git a/crypto/blockchains/xmr/ext/MoneroDict.ts b/crypto/blockchains/xmr/ext/MoneroDict.ts deleted file mode 100644 index c3633fede..000000000 --- a/crypto/blockchains/xmr/ext/MoneroDict.ts +++ /dev/null @@ -1,1634 +0,0 @@ -const monero_words_english_prefix_len = 3; -const monero_words_english = [ - 'abbey', - 'abducts', - 'ability', - 'ablaze', - 'abnormal', - 'abort', - 'abrasive', - 'absorb', - 'abyss', - 'academy', - 'aces', - 'aching', - 'acidic', - 'acoustic', - 'acquire', - 'across', - 'actress', - 'acumen', - 'adapt', - 'addicted', - 'adept', - 'adhesive', - 'adjust', - 'adopt', - 'adrenalin', - 'adult', - 'adventure', - 'aerial', - 'afar', - 'affair', - 'afield', - 'afloat', - 'afoot', - 'afraid', - 'after', - 'against', - 'agenda', - 'aggravate', - 'agile', - 'aglow', - 'agnostic', - 'agony', - 'agreed', - 'ahead', - 'aided', - 'ailments', - 'aimless', - 'airport', - 'aisle', - 'ajar', - 'akin', - 'alarms', - 'album', - 'alchemy', - 'alerts', - 'algebra', - 'alkaline', - 'alley', - 'almost', - 'aloof', - 'alpine', - 'already', - 'also', - 'altitude', - 'alumni', - 'always', - 'amaze', - 'ambush', - 'amended', - 'amidst', - 'ammo', - 'amnesty', - 'among', - 'amply', - 'amused', - 'anchor', - 'android', - 'anecdote', - 'angled', - 'ankle', - 'annoyed', - 'answers', - 'antics', - 'anvil', - 'anxiety', - 'anybody', - 'apart', - 'apex', - 'aphid', - 'aplomb', - 'apology', - 'apply', - 'apricot', - 'aptitude', - 'aquarium', - 'arbitrary', - 'archer', - 'ardent', - 'arena', - 'argue', - 'arises', - 'army', - 'around', - 'arrow', - 'arsenic', - 'artistic', - 'ascend', - 'ashtray', - 'aside', - 'asked', - 'asleep', - 'aspire', - 'assorted', - 'asylum', - 'athlete', - 'atlas', - 'atom', - 'atrium', - 'attire', - 'auburn', - 'auctions', - 'audio', - 'august', - 'aunt', - 'austere', - 'autumn', - 'avatar', - 'avidly', - 'avoid', - 'awakened', - 'awesome', - 'awful', - 'awkward', - 'awning', - 'awoken', - 'axes', - 'axis', - 'axle', - 'aztec', - 'azure', - 'baby', - 'bacon', - 'badge', - 'baffles', - 'bagpipe', - 'bailed', - 'bakery', - 'balding', - 'bamboo', - 'banjo', - 'baptism', - 'basin', - 'batch', - 'bawled', - 'bays', - 'because', - 'beer', - 'befit', - 'begun', - 'behind', - 'being', - 'below', - 'bemused', - 'benches', - 'berries', - 'bested', - 'betting', - 'bevel', - 'beware', - 'beyond', - 'bias', - 'bicycle', - 'bids', - 'bifocals', - 'biggest', - 'bikini', - 'bimonthly', - 'binocular', - 'biology', - 'biplane', - 'birth', - 'biscuit', - 'bite', - 'biweekly', - 'blender', - 'blip', - 'bluntly', - 'boat', - 'bobsled', - 'bodies', - 'bogeys', - 'boil', - 'boldly', - 'bomb', - 'border', - 'boss', - 'both', - 'bounced', - 'bovine', - 'bowling', - 'boxes', - 'boyfriend', - 'broken', - 'brunt', - 'bubble', - 'buckets', - 'budget', - 'buffet', - 'bugs', - 'building', - 'bulb', - 'bumper', - 'bunch', - 'business', - 'butter', - 'buying', - 'buzzer', - 'bygones', - 'byline', - 'bypass', - 'cabin', - 'cactus', - 'cadets', - 'cafe', - 'cage', - 'cajun', - 'cake', - 'calamity', - 'camp', - 'candy', - 'casket', - 'catch', - 'cause', - 'cavernous', - 'cease', - 'cedar', - 'ceiling', - 'cell', - 'cement', - 'cent', - 'certain', - 'chlorine', - 'chrome', - 'cider', - 'cigar', - 'cinema', - 'circle', - 'cistern', - 'citadel', - 'civilian', - 'claim', - 'click', - 'clue', - 'coal', - 'cobra', - 'cocoa', - 'code', - 'coexist', - 'coffee', - 'cogs', - 'cohesive', - 'coils', - 'colony', - 'comb', - 'cool', - 'copy', - 'corrode', - 'costume', - 'cottage', - 'cousin', - 'cowl', - 'criminal', - 'cube', - 'cucumber', - 'cuddled', - 'cuffs', - 'cuisine', - 'cunning', - 'cupcake', - 'custom', - 'cycling', - 'cylinder', - 'cynical', - 'dabbing', - 'dads', - 'daft', - 'dagger', - 'daily', - 'damp', - 'dangerous', - 'dapper', - 'darted', - 'dash', - 'dating', - 'dauntless', - 'dawn', - 'daytime', - 'dazed', - 'debut', - 'decay', - 'dedicated', - 'deepest', - 'deftly', - 'degrees', - 'dehydrate', - 'deity', - 'dejected', - 'delayed', - 'demonstrate', - 'dented', - 'deodorant', - 'depth', - 'desk', - 'devoid', - 'dewdrop', - 'dexterity', - 'dialect', - 'dice', - 'diet', - 'different', - 'digit', - 'dilute', - 'dime', - 'dinner', - 'diode', - 'diplomat', - 'directed', - 'distance', - 'ditch', - 'divers', - 'dizzy', - 'doctor', - 'dodge', - 'does', - 'dogs', - 'doing', - 'dolphin', - 'domestic', - 'donuts', - 'doorway', - 'dormant', - 'dosage', - 'dotted', - 'double', - 'dove', - 'down', - 'dozen', - 'dreams', - 'drinks', - 'drowning', - 'drunk', - 'drying', - 'dual', - 'dubbed', - 'duckling', - 'dude', - 'duets', - 'duke', - 'dullness', - 'dummy', - 'dunes', - 'duplex', - 'duration', - 'dusted', - 'duties', - 'dwarf', - 'dwelt', - 'dwindling', - 'dying', - 'dynamite', - 'dyslexic', - 'each', - 'eagle', - 'earth', - 'easy', - 'eating', - 'eavesdrop', - 'eccentric', - 'echo', - 'eclipse', - 'economics', - 'ecstatic', - 'eden', - 'edgy', - 'edited', - 'educated', - 'eels', - 'efficient', - 'eggs', - 'egotistic', - 'eight', - 'either', - 'eject', - 'elapse', - 'elbow', - 'eldest', - 'eleven', - 'elite', - 'elope', - 'else', - 'eluded', - 'emails', - 'ember', - 'emerge', - 'emit', - 'emotion', - 'empty', - 'emulate', - 'energy', - 'enforce', - 'enhanced', - 'enigma', - 'enjoy', - 'enlist', - 'enmity', - 'enough', - 'enraged', - 'ensign', - 'entrance', - 'envy', - 'epoxy', - 'equip', - 'erase', - 'erected', - 'erosion', - 'error', - 'eskimos', - 'espionage', - 'essential', - 'estate', - 'etched', - 'eternal', - 'ethics', - 'etiquette', - 'evaluate', - 'evenings', - 'evicted', - 'evolved', - 'examine', - 'excess', - 'exhale', - 'exit', - 'exotic', - 'exquisite', - 'extra', - 'exult', - 'fabrics', - 'factual', - 'fading', - 'fainted', - 'faked', - 'fall', - 'family', - 'fancy', - 'farming', - 'fatal', - 'faulty', - 'fawns', - 'faxed', - 'fazed', - 'feast', - 'february', - 'federal', - 'feel', - 'feline', - 'females', - 'fences', - 'ferry', - 'festival', - 'fetches', - 'fever', - 'fewest', - 'fiat', - 'fibula', - 'fictional', - 'fidget', - 'fierce', - 'fifteen', - 'fight', - 'films', - 'firm', - 'fishing', - 'fitting', - 'five', - 'fixate', - 'fizzle', - 'fleet', - 'flippant', - 'flying', - 'foamy', - 'focus', - 'foes', - 'foggy', - 'foiled', - 'folding', - 'fonts', - 'foolish', - 'fossil', - 'fountain', - 'fowls', - 'foxes', - 'foyer', - 'framed', - 'friendly', - 'frown', - 'fruit', - 'frying', - 'fudge', - 'fuel', - 'fugitive', - 'fully', - 'fuming', - 'fungal', - 'furnished', - 'fuselage', - 'future', - 'fuzzy', - 'gables', - 'gadget', - 'gags', - 'gained', - 'galaxy', - 'gambit', - 'gang', - 'gasp', - 'gather', - 'gauze', - 'gave', - 'gawk', - 'gaze', - 'gearbox', - 'gecko', - 'geek', - 'gels', - 'gemstone', - 'general', - 'geometry', - 'germs', - 'gesture', - 'getting', - 'geyser', - 'ghetto', - 'ghost', - 'giant', - 'giddy', - 'gifts', - 'gigantic', - 'gills', - 'gimmick', - 'ginger', - 'girth', - 'giving', - 'glass', - 'gleeful', - 'glide', - 'gnaw', - 'gnome', - 'goat', - 'goblet', - 'godfather', - 'goes', - 'goggles', - 'going', - 'goldfish', - 'gone', - 'goodbye', - 'gopher', - 'gorilla', - 'gossip', - 'gotten', - 'gourmet', - 'governing', - 'gown', - 'greater', - 'grunt', - 'guarded', - 'guest', - 'guide', - 'gulp', - 'gumball', - 'guru', - 'gusts', - 'gutter', - 'guys', - 'gymnast', - 'gypsy', - 'gyrate', - 'habitat', - 'hacksaw', - 'haggled', - 'hairy', - 'hamburger', - 'happens', - 'hashing', - 'hatchet', - 'haunted', - 'having', - 'hawk', - 'haystack', - 'hazard', - 'hectare', - 'hedgehog', - 'heels', - 'hefty', - 'height', - 'hemlock', - 'hence', - 'heron', - 'hesitate', - 'hexagon', - 'hickory', - 'hiding', - 'highway', - 'hijack', - 'hiker', - 'hills', - 'himself', - 'hinder', - 'hippo', - 'hire', - 'history', - 'hitched', - 'hive', - 'hoax', - 'hobby', - 'hockey', - 'hoisting', - 'hold', - 'honked', - 'hookup', - 'hope', - 'hornet', - 'hospital', - 'hotel', - 'hounded', - 'hover', - 'howls', - 'hubcaps', - 'huddle', - 'huge', - 'hull', - 'humid', - 'hunter', - 'hurried', - 'husband', - 'huts', - 'hybrid', - 'hydrogen', - 'hyper', - 'iceberg', - 'icing', - 'icon', - 'identity', - 'idiom', - 'idled', - 'idols', - 'igloo', - 'ignore', - 'iguana', - 'illness', - 'imagine', - 'imbalance', - 'imitate', - 'impel', - 'inactive', - 'inbound', - 'incur', - 'industrial', - 'inexact', - 'inflamed', - 'ingested', - 'initiate', - 'injury', - 'inkling', - 'inline', - 'inmate', - 'innocent', - 'inorganic', - 'input', - 'inquest', - 'inroads', - 'insult', - 'intended', - 'inundate', - 'invoke', - 'inwardly', - 'ionic', - 'irate', - 'iris', - 'irony', - 'irritate', - 'island', - 'isolated', - 'issued', - 'italics', - 'itches', - 'items', - 'itinerary', - 'itself', - 'ivory', - 'jabbed', - 'jackets', - 'jaded', - 'jagged', - 'jailed', - 'jamming', - 'january', - 'jargon', - 'jaunt', - 'javelin', - 'jaws', - 'jazz', - 'jeans', - 'jeers', - 'jellyfish', - 'jeopardy', - 'jerseys', - 'jester', - 'jetting', - 'jewels', - 'jigsaw', - 'jingle', - 'jittery', - 'jive', - 'jobs', - 'jockey', - 'jogger', - 'joining', - 'joking', - 'jolted', - 'jostle', - 'journal', - 'joyous', - 'jubilee', - 'judge', - 'juggled', - 'juicy', - 'jukebox', - 'july', - 'jump', - 'junk', - 'jury', - 'justice', - 'juvenile', - 'kangaroo', - 'karate', - 'keep', - 'kennel', - 'kept', - 'kernels', - 'kettle', - 'keyboard', - 'kickoff', - 'kidneys', - 'king', - 'kiosk', - 'kisses', - 'kitchens', - 'kiwi', - 'knapsack', - 'knee', - 'knife', - 'knowledge', - 'knuckle', - 'koala', - 'laboratory', - 'ladder', - 'lagoon', - 'lair', - 'lakes', - 'lamb', - 'language', - 'laptop', - 'large', - 'last', - 'later', - 'launching', - 'lava', - 'lawsuit', - 'layout', - 'lazy', - 'lectures', - 'ledge', - 'leech', - 'left', - 'legion', - 'leisure', - 'lemon', - 'lending', - 'leopard', - 'lesson', - 'lettuce', - 'lexicon', - 'liar', - 'library', - 'licks', - 'lids', - 'lied', - 'lifestyle', - 'light', - 'likewise', - 'lilac', - 'limits', - 'linen', - 'lion', - 'lipstick', - 'liquid', - 'listen', - 'lively', - 'loaded', - 'lobster', - 'locker', - 'lodge', - 'lofty', - 'logic', - 'loincloth', - 'long', - 'looking', - 'lopped', - 'lordship', - 'losing', - 'lottery', - 'loudly', - 'love', - 'lower', - 'loyal', - 'lucky', - 'luggage', - 'lukewarm', - 'lullaby', - 'lumber', - 'lunar', - 'lurk', - 'lush', - 'luxury', - 'lymph', - 'lynx', - 'lyrics', - 'macro', - 'madness', - 'magically', - 'mailed', - 'major', - 'makeup', - 'malady', - 'mammal', - 'maps', - 'masterful', - 'match', - 'maul', - 'maverick', - 'maximum', - 'mayor', - 'maze', - 'meant', - 'mechanic', - 'medicate', - 'meeting', - 'megabyte', - 'melting', - 'memoir', - 'menu', - 'merger', - 'mesh', - 'metro', - 'mews', - 'mice', - 'midst', - 'mighty', - 'mime', - 'mirror', - 'misery', - 'mittens', - 'mixture', - 'moat', - 'mobile', - 'mocked', - 'mohawk', - 'moisture', - 'molten', - 'moment', - 'money', - 'moon', - 'mops', - 'morsel', - 'mostly', - 'motherly', - 'mouth', - 'movement', - 'mowing', - 'much', - 'muddy', - 'muffin', - 'mugged', - 'mullet', - 'mumble', - 'mundane', - 'muppet', - 'mural', - 'musical', - 'muzzle', - 'myriad', - 'mystery', - 'myth', - 'nabbing', - 'nagged', - 'nail', - 'names', - 'nanny', - 'napkin', - 'narrate', - 'nasty', - 'natural', - 'nautical', - 'navy', - 'nearby', - 'necklace', - 'needed', - 'negative', - 'neither', - 'neon', - 'nephew', - 'nerves', - 'nestle', - 'network', - 'neutral', - 'never', - 'newt', - 'nexus', - 'nibs', - 'niche', - 'niece', - 'nifty', - 'nightly', - 'nimbly', - 'nineteen', - 'nirvana', - 'nitrogen', - 'nobody', - 'nocturnal', - 'nodes', - 'noises', - 'nomad', - 'noodles', - 'northern', - 'nostril', - 'noted', - 'nouns', - 'novelty', - 'nowhere', - 'nozzle', - 'nuance', - 'nucleus', - 'nudged', - 'nugget', - 'nuisance', - 'null', - 'number', - 'nuns', - 'nurse', - 'nutshell', - 'nylon', - 'oaks', - 'oars', - 'oasis', - 'oatmeal', - 'obedient', - 'object', - 'obliged', - 'obnoxious', - 'observant', - 'obtains', - 'obvious', - 'occur', - 'ocean', - 'october', - 'odds', - 'odometer', - 'offend', - 'often', - 'oilfield', - 'ointment', - 'okay', - 'older', - 'olive', - 'olympics', - 'omega', - 'omission', - 'omnibus', - 'onboard', - 'oncoming', - 'oneself', - 'ongoing', - 'onion', - 'online', - 'onslaught', - 'onto', - 'onward', - 'oozed', - 'opacity', - 'opened', - 'opposite', - 'optical', - 'opus', - 'orange', - 'orbit', - 'orchid', - 'orders', - 'organs', - 'origin', - 'ornament', - 'orphans', - 'oscar', - 'ostrich', - 'otherwise', - 'otter', - 'ouch', - 'ought', - 'ounce', - 'ourselves', - 'oust', - 'outbreak', - 'oval', - 'oven', - 'owed', - 'owls', - 'owner', - 'oxidant', - 'oxygen', - 'oyster', - 'ozone', - 'pact', - 'paddles', - 'pager', - 'pairing', - 'palace', - 'pamphlet', - 'pancakes', - 'paper', - 'paradise', - 'pastry', - 'patio', - 'pause', - 'pavements', - 'pawnshop', - 'payment', - 'peaches', - 'pebbles', - 'peculiar', - 'pedantic', - 'peeled', - 'pegs', - 'pelican', - 'pencil', - 'people', - 'pepper', - 'perfect', - 'pests', - 'petals', - 'phase', - 'pheasants', - 'phone', - 'phrases', - 'physics', - 'piano', - 'picked', - 'pierce', - 'pigment', - 'piloted', - 'pimple', - 'pinched', - 'pioneer', - 'pipeline', - 'pirate', - 'pistons', - 'pitched', - 'pivot', - 'pixels', - 'pizza', - 'playful', - 'pledge', - 'pliers', - 'plotting', - 'plus', - 'plywood', - 'poaching', - 'pockets', - 'podcast', - 'poetry', - 'point', - 'poker', - 'polar', - 'ponies', - 'pool', - 'popular', - 'portents', - 'possible', - 'potato', - 'pouch', - 'poverty', - 'powder', - 'pram', - 'present', - 'pride', - 'problems', - 'pruned', - 'prying', - 'psychic', - 'public', - 'puck', - 'puddle', - 'puffin', - 'pulp', - 'pumpkins', - 'punch', - 'puppy', - 'purged', - 'push', - 'putty', - 'puzzled', - 'pylons', - 'pyramid', - 'python', - 'queen', - 'quick', - 'quote', - 'rabbits', - 'racetrack', - 'radar', - 'rafts', - 'rage', - 'railway', - 'raking', - 'rally', - 'ramped', - 'randomly', - 'rapid', - 'rarest', - 'rash', - 'rated', - 'ravine', - 'rays', - 'razor', - 'react', - 'rebel', - 'recipe', - 'reduce', - 'reef', - 'refer', - 'regular', - 'reheat', - 'reinvest', - 'rejoices', - 'rekindle', - 'relic', - 'remedy', - 'renting', - 'reorder', - 'repent', - 'request', - 'reruns', - 'rest', - 'return', - 'reunion', - 'revamp', - 'rewind', - 'rhino', - 'rhythm', - 'ribbon', - 'richly', - 'ridges', - 'rift', - 'rigid', - 'rims', - 'ringing', - 'riots', - 'ripped', - 'rising', - 'ritual', - 'river', - 'roared', - 'robot', - 'rockets', - 'rodent', - 'rogue', - 'roles', - 'romance', - 'roomy', - 'roped', - 'roster', - 'rotate', - 'rounded', - 'rover', - 'rowboat', - 'royal', - 'ruby', - 'rudely', - 'ruffled', - 'rugged', - 'ruined', - 'ruling', - 'rumble', - 'runway', - 'rural', - 'rustled', - 'ruthless', - 'sabotage', - 'sack', - 'sadness', - 'safety', - 'saga', - 'sailor', - 'sake', - 'salads', - 'sample', - 'sanity', - 'sapling', - 'sarcasm', - 'sash', - 'satin', - 'saucepan', - 'saved', - 'sawmill', - 'saxophone', - 'sayings', - 'scamper', - 'scenic', - 'school', - 'science', - 'scoop', - 'scrub', - 'scuba', - 'seasons', - 'second', - 'sedan', - 'seeded', - 'segments', - 'seismic', - 'selfish', - 'semifinal', - 'sensible', - 'september', - 'sequence', - 'serving', - 'session', - 'setup', - 'seventh', - 'sewage', - 'shackles', - 'shelter', - 'shipped', - 'shocking', - 'shrugged', - 'shuffled', - 'shyness', - 'siblings', - 'sickness', - 'sidekick', - 'sieve', - 'sifting', - 'sighting', - 'silk', - 'simplest', - 'sincerely', - 'sipped', - 'siren', - 'situated', - 'sixteen', - 'sizes', - 'skater', - 'skew', - 'skirting', - 'skulls', - 'skydive', - 'slackens', - 'sleepless', - 'slid', - 'slower', - 'slug', - 'smash', - 'smelting', - 'smidgen', - 'smog', - 'smuggled', - 'snake', - 'sneeze', - 'sniff', - 'snout', - 'snug', - 'soapy', - 'sober', - 'soccer', - 'soda', - 'software', - 'soggy', - 'soil', - 'solved', - 'somewhere', - 'sonic', - 'soothe', - 'soprano', - 'sorry', - 'southern', - 'sovereign', - 'sowed', - 'soya', - 'space', - 'speedy', - 'sphere', - 'spiders', - 'splendid', - 'spout', - 'sprig', - 'spud', - 'spying', - 'square', - 'stacking', - 'stellar', - 'stick', - 'stockpile', - 'strained', - 'stunning', - 'stylishly', - 'subtly', - 'succeed', - 'suddenly', - 'suede', - 'suffice', - 'sugar', - 'suitcase', - 'sulking', - 'summon', - 'sunken', - 'superior', - 'surfer', - 'sushi', - 'suture', - 'swagger', - 'swept', - 'swiftly', - 'sword', - 'swung', - 'syllabus', - 'symptoms', - 'syndrome', - 'syringe', - 'system', - 'taboo', - 'tacit', - 'tadpoles', - 'tagged', - 'tail', - 'taken', - 'talent', - 'tamper', - 'tanks', - 'tapestry', - 'tarnished', - 'tasked', - 'tattoo', - 'taunts', - 'tavern', - 'tawny', - 'taxi', - 'teardrop', - 'technical', - 'tedious', - 'teeming', - 'tell', - 'template', - 'tender', - 'tepid', - 'tequila', - 'terminal', - 'testing', - 'tether', - 'textbook', - 'thaw', - 'theatrics', - 'thirsty', - 'thorn', - 'threaten', - 'thumbs', - 'thwart', - 'ticket', - 'tidy', - 'tiers', - 'tiger', - 'tilt', - 'timber', - 'tinted', - 'tipsy', - 'tirade', - 'tissue', - 'titans', - 'toaster', - 'tobacco', - 'today', - 'toenail', - 'toffee', - 'together', - 'toilet', - 'token', - 'tolerant', - 'tomorrow', - 'tonic', - 'toolbox', - 'topic', - 'torch', - 'tossed', - 'total', - 'touchy', - 'towel', - 'toxic', - 'toyed', - 'trash', - 'trendy', - 'tribal', - 'trolling', - 'truth', - 'trying', - 'tsunami', - 'tubes', - 'tucks', - 'tudor', - 'tuesday', - 'tufts', - 'tugs', - 'tuition', - 'tulips', - 'tumbling', - 'tunnel', - 'turnip', - 'tusks', - 'tutor', - 'tuxedo', - 'twang', - 'tweezers', - 'twice', - 'twofold', - 'tycoon', - 'typist', - 'tyrant', - 'ugly', - 'ulcers', - 'ultimate', - 'umbrella', - 'umpire', - 'unafraid', - 'unbending', - 'uncle', - 'under', - 'uneven', - 'unfit', - 'ungainly', - 'unhappy', - 'union', - 'unjustly', - 'unknown', - 'unlikely', - 'unmask', - 'unnoticed', - 'unopened', - 'unplugs', - 'unquoted', - 'unrest', - 'unsafe', - 'until', - 'unusual', - 'unveil', - 'unwind', - 'unzip', - 'upbeat', - 'upcoming', - 'update', - 'upgrade', - 'uphill', - 'upkeep', - 'upload', - 'upon', - 'upper', - 'upright', - 'upstairs', - 'uptight', - 'upwards', - 'urban', - 'urchins', - 'urgent', - 'usage', - 'useful', - 'usher', - 'using', - 'usual', - 'utensils', - 'utility', - 'utmost', - 'utopia', - 'uttered', - 'vacation', - 'vague', - 'vain', - 'value', - 'vampire', - 'vane', - 'vapidly', - 'vary', - 'vastness', - 'vats', - 'vaults', - 'vector', - 'veered', - 'vegan', - 'vehicle', - 'vein', - 'velvet', - 'venomous', - 'verification', - 'vessel', - 'veteran', - 'vexed', - 'vials', - 'vibrate', - 'victim', - 'video', - 'viewpoint', - 'vigilant', - 'viking', - 'village', - 'vinegar', - 'violin', - 'vipers', - 'virtual', - 'visited', - 'vitals', - 'vivid', - 'vixen', - 'vocal', - 'vogue', - 'voice', - 'volcano', - 'vortex', - 'voted', - 'voucher', - 'vowels', - 'voyage', - 'vulture', - 'wade', - 'waffle', - 'wagtail', - 'waist', - 'waking', - 'wallets', - 'wanted', - 'warped', - 'washing', - 'water', - 'waveform', - 'waxing', - 'wayside', - 'weavers', - 'website', - 'wedge', - 'weekday', - 'weird', - 'welders', - 'went', - 'wept', - 'were', - 'western', - 'wetsuit', - 'whale', - 'when', - 'whipped', - 'whole', - 'wickets', - 'width', - 'wield', - 'wife', - 'wiggle', - 'wildly', - 'winter', - 'wipeout', - 'wiring', - 'wise', - 'withdrawn', - 'wives', - 'wizard', - 'wobbly', - 'woes', - 'woken', - 'wolf', - 'womanly', - 'wonders', - 'woozy', - 'worry', - 'wounded', - 'woven', - 'wrap', - 'wrist', - 'wrong', - 'yacht', - 'yahoo', - 'yanks', - 'yard', - 'yawning', - 'yearbook', - 'yellow', - 'yesterday', - 'yeti', - 'yields', - 'yodel', - 'yoga', - 'younger', - 'yoyo', - 'zapped', - 'zeal', - 'zebra', - 'zero', - 'zesty', - 'zigzags', - 'zinger', - 'zippers', - 'zodiac', - 'zombie', - 'zones', - 'zoom' -]; - -export default { - monero_words_english, - monero_words_english_prefix_len -}; diff --git a/crypto/blockchains/xmr/ext/MoneroMnemonic.ts b/crypto/blockchains/xmr/ext/MoneroMnemonic.ts deleted file mode 100644 index 66918411c..000000000 --- a/crypto/blockchains/xmr/ext/MoneroMnemonic.ts +++ /dev/null @@ -1,83 +0,0 @@ -import MoneroDict from './MoneroDict'; -import BlocksoftUtils from '../../../common/AirDAOUtils'; - -const crc32 = require('buffer-crc32'); - -type SecretSpendKey = string | Buffer; - -interface DictionaryWord { - word: string; - prefix: string; -} - -interface MoneroDictionary { - monero_words_english: DictionaryWord[]; - monero_words_english_prefix_len: number; -} - -// tslint:disable-next-line:variable-name -const secret_spend_key_to_words = ( - secretSpendKeyBufferOrHex: SecretSpendKey, - walletHash: string -): string => { - const buff = - typeof secretSpendKeyBufferOrHex === 'string' - ? Buffer.from(secretSpendKeyBufferOrHex, 'hex') - : secretSpendKeyBufferOrHex; - - const seed: string[] = []; - let forChecksum = ''; - - for (let i = 0; i < 32; i += 4) { - let w0 = 0; - for (let j = 3; j >= 0; j--) { - w0 = w0 * 256 + buff[i + j]; - if (typeof buff[i + j] === 'undefined') { - throw new Error( - `XMR word for wallet ${walletHash} needs to be rechecked as buff is too low` - ); - } - } - - const moneroDict: MoneroDictionary = - MoneroDict as unknown as MoneroDictionary; - const moneroWords: DictionaryWord[] = moneroDict.monero_words_english; - - const w1 = w0 % moneroWords.length; - const w2 = (Math.floor(w0 / moneroWords.length) + w1) % moneroWords.length; - const w3 = - (Math.floor(Math.floor(w0 / moneroWords.length) / moneroWords.length) + - w2) % - moneroWords.length; - - seed.push(moneroWords[w1].word); - seed.push(moneroWords[w2].word); - seed.push(moneroWords[w3].word); - - forChecksum += moneroWords[w1].prefix.substring( - 0, - moneroDict.monero_words_english_prefix_len - ); - forChecksum += moneroWords[w2].prefix.substring( - 0, - moneroDict.monero_words_english_prefix_len - ); - forChecksum += moneroWords[w3].prefix.substring( - 0, - moneroDict.monero_words_english_prefix_len - ); - } - - const crc32Res = crc32(forChecksum); - const crc32Decimal = BlocksoftUtils.hexToDecimal( - '0x' + crc32Res.toString('hex') - ); - // @ts-ignore - seed.push(seed[crc32Decimal % 24]); - - return seed.join(' '); -}; - -export default { - secret_spend_key_to_words -}; diff --git a/crypto/blockchains/xmr/ext/MoneroUtils.ts b/crypto/blockchains/xmr/ext/MoneroUtils.ts deleted file mode 100644 index 359d1a4b9..000000000 --- a/crypto/blockchains/xmr/ext/MoneroUtils.ts +++ /dev/null @@ -1,216 +0,0 @@ -/** - * @author Ksu - * @version 0.11 - * - * based on - * https://github.com/mymonero/mymonero-core-js/blob/c04651731e238d23baec9682753acd967679a36e/src/index.cpp - */ -import { soliditySha3 } from 'web3-utils'; -// @ts-ignore -import * as elliptic from 'elliptic'; -const Ed25519 = new elliptic.eddsa('ed25519'); - -/** Monero base58 is not like Bitcoin base58, bytes are converted in 8-byte blocks. - * https://docs.rs/base58-monero/0.2.0/base58_monero/ - */ -// eslint-disable-next-line camelcase -function base58_encode(data: Buffer): string { - const ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'; - const BYTES_TO_LENGTHS = [0, 2, 3, 5, 6, 7, 9, 10, 11]; - const ALPHABET_MAP: { [char: string]: number } = {}; - const BASE = ALPHABET.length; - - // pre-compute lookup table - for (let z = 0; z < ALPHABET.length; z++) { - const x = ALPHABET.charAt(z); - if (ALPHABET_MAP[x] !== undefined) throw new TypeError(x + ' is ambiguous'); - ALPHABET_MAP[x] = z; - } - - // tslint:disable-next-line:no-shadowed-variable - function encode_partial(data: Buffer, pos: number): string { - let len = 8; - if (pos + len > data.length) { - len = data.length - pos; - } - const digits = [0]; - for (let i = 0; i < len; ++i) { - let carry = data[pos + i]; // Declare 'carry' here - for (let j = 0; j < digits.length; ++j) { - carry += digits[j] * BASE; - digits[j] = carry % BASE; - carry = Math.floor(carry / BASE); - } - - while (carry > 0) { - digits.push(carry % BASE); - carry = Math.floor(carry / BASE); - } - } - - res = ''; - // deal with leading zeros - for (let k = digits.length; k < BYTES_TO_LENGTHS[len]; ++k) { - res += ALPHABET[0]; - } - // convert digits to a string - for (let q = digits.length - 1; q >= 0; --q) { - res += ALPHABET[digits[q]]; - } - return res; - } - - let res = ''; - for (let i = 0; i < data.length; i += 8) { - res += encode_partial(data, i); - } - return res; -} - -export default { - /** - * Takes a 32-byte integer and outputs the integer modulo q - * Input: - * s[0]+256*s[1]+...+256^63*s[63] = s - * - * Output: - * s[0]+256*s[1]+...+256^31*s[31] = s mod l - * where l = 2^252 + 27742317777372353535851937790883648493. - * - * Actually should be valid ed25519 scalar so make with umode and checked - */ - // eslint-disable-next-line camelcase - sc_reduce32(dataBufferOrHex: Buffer | string): string { - const buff = - typeof dataBufferOrHex === 'string' - ? Buffer.from(dataBufferOrHex, 'hex') - : dataBufferOrHex; - const hex = elliptic.utils - .intFromLE(buff) - .umod(Ed25519.curve.n) - .toString('hex'); - return this.reverse(hex); - }, - - /** - * Scalar add - is taking two big numbers, which are stored as binary in byte (char) arrays and returning the result of adding them together in another byte array, mod the order of the base point. - */ - // eslint-disable-next-line camelcase - sc_add( - dataBufferOrHex: Buffer | string, - dataBufferOrHex2: Buffer | string - ): string { - const buff = - typeof dataBufferOrHex === 'string' - ? Buffer.from(dataBufferOrHex, 'hex') - : dataBufferOrHex; - const buff2 = - typeof dataBufferOrHex2 === 'string' - ? Buffer.from(dataBufferOrHex2, 'hex') - : dataBufferOrHex2; - const hex = elliptic.utils - .intFromLE(buff) - .add(elliptic.utils.intFromLE(buff2)) - .toString('hex'); - return this.reverse(hex); - }, - - reverse(hex: string): string { - let result = ''; - const ic = hex.length; - for (let i = ic - 1; i >= 0; i = i - 2) { - const tmp = i > 0 ? hex[i - 1] + hex[i] : '0' + hex[i]; - result += tmp; - } - return result; - }, - - /** - * Inputs data (for example, a point P on ed25519) and outputs Hs (P), which is - * the Keccak1600 hash of the data. The function then converts the hashed data to a - * 32-byte integer modulo q - * - * void hash_to_scalar(const void *data, size_t length, ec_scalar &res) { - * cn_fast_hash(data, length, reinterpret_cast(res)); - * sc_reduce32(&res); - * } - */ - // eslint-disable-next-line camelcase - hash_to_scalar(dataBufferOrHex: Buffer | string): string { - return this.sc_reduce32(this.cn_fast_hash(dataBufferOrHex)); - }, - - /** - * cn_fast_hash = keccak1600 = sha3 !!!! - * parameters b = 1600 and c = 512 - * Function keccak1600 is defined as a special case of function keccak with parameter mdlen equal to the size in bits of the type state_t. This type is an array of 25 uint64_t so the total size is 25*64=1600 - */ - // eslint-disable-next-line camelcase - cn_fast_hash(dataBufferOrHex: Buffer | string): string { - const str = - typeof dataBufferOrHex === 'string' - ? dataBufferOrHex - : dataBufferOrHex.toString('hex'); - const hash = soliditySha3('0x' + str); - return hash?.substr(2) || ''; - }, - - /** - * https://monerodocs.org/cryptography/asymmetric/public-key/ - * https://github.com/indutny/elliptic - * Actually this function multiplies base G by its input - * - * Inputs a secret key, checks it for some uniformity conditions, and outputs the corresponding public key, which is essentially just 8 times the base point times the - * point. - */ - // eslint-disable-next-line camelcase - secret_key_to_public_key(secretSpendKeyBufferOrHex: Buffer | string): Buffer { - const buff = - typeof secretSpendKeyBufferOrHex === 'string' - ? Buffer.from(secretSpendKeyBufferOrHex, 'hex') - : secretSpendKeyBufferOrHex; - const publicSpendKey = Ed25519.curve.g.mul( - Ed25519.decodeInt(buff.toString('hex')) - ); - return Buffer.from(Ed25519.encodePoint(publicSpendKey)); - /** - * !!! NOPE - * constkey = Ed25519.keyFromSecret(secretSpendKeyBuffer) - * const publicSpendKey2 = key.getPublic() - * console.log(' publicSpendKey2 ', publicSpendKey2, publicSpendKey2.toString('hex'), Buffer.from(publicSpendKey2), Buffer.from(publicSpendKey2).toString('hex')) - */ - }, - - /* checked */ - // eslint-disable-next-line camelcase - pub_keys_to_address( - index: number, - publicSpendKeyBufferOrHex: Buffer | string, - publicViewKeyHexBufferOrHex: Buffer | string - ): string { - const str = - typeof publicSpendKeyBufferOrHex === 'string' - ? publicSpendKeyBufferOrHex - : publicSpendKeyBufferOrHex.toString('hex'); - const str2 = - typeof publicViewKeyHexBufferOrHex === 'string' - ? publicViewKeyHexBufferOrHex - : publicViewKeyHexBufferOrHex.toString('hex'); - const prefix = index > 0 ? '2A' : '12'; - - let hex = prefix + str + str2; // this.normString(str) + this.normString(str2) - const hash = this.cn_fast_hash(hex); - hex += hash.substring(0, 8); - - hex = Buffer.from(hex, 'hex').toString('hex'); - - return base58_encode(Buffer.from(hex, 'hex')); - }, - - normString(str: string): string { - while (str.length < 64) { - str += '0'; - } - return str; - } -}; diff --git a/crypto/blockchains/xmr/ext/MoneroUtilsParser.oldAndroid.ts b/crypto/blockchains/xmr/ext/MoneroUtilsParser.oldAndroid.ts deleted file mode 100644 index 991137e7d..000000000 --- a/crypto/blockchains/xmr/ext/MoneroUtilsParser.oldAndroid.ts +++ /dev/null @@ -1,39 +0,0 @@ -/** - * @author Ksu - * @version 0.11 - * for old androids - */ - -export default { - checkDestination(value: any) { - return false; - }, - - async getCore() { - return false; - }, - - async getCoreWasm() { - return false; - }, - - async parseAddressInfo( - address: string, - data: {}, - privViewKey: string, - pubSpendKey: string, - privSpendKey: string - ) { - return false; - }, - - async parseAddressTransactions( - address: string, - data: {}, - privViewKey: string, - pubSpendKey: string, - privSpendKey: string - ) { - return false; - } -}; diff --git a/crypto/blockchains/xmr/ext/MoneroUtilsParser.ts b/crypto/blockchains/xmr/ext/MoneroUtilsParser.ts deleted file mode 100644 index 32b2f1366..000000000 --- a/crypto/blockchains/xmr/ext/MoneroUtilsParser.ts +++ /dev/null @@ -1,293 +0,0 @@ -/** - * @author Ksu - * @version 0.2 - * https://github.com/mymonero/mymonero-utils/blob/8ea7ff51f931d3c5e27e2ffd2eb8945cdec8e050/packages/mymonero-response-parser-utils/index.js - */ - -// @ts-ignore -import * as payment from '@mymonero/mymonero-paymentid-utils'; -// import * as parser from '@mymonero/mymonero-response-parser-utils/ResponseParser' -import * as parser from './vendor/ResponseParser'; -import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; -import config from '@constants/config'; - -const MyMoneroCoreBridgeRN = require('react-native-mymonero-core/src/index'); -interface MyMoneroCore { - generate_key_image: ( - txPublicKey: string, - privateViewKey: string, - publicSpendKey: string, - privateSpendKey: string, - outputIndex: string - ) => Promise; - createTransaction: (options: { - privateViewKey: string; - publicSpendKey: string; - privateSpendKey: string; - randomOutsCb: () => void; - destinations: { to_address: string; send_amount: number }[]; - shouldSweep: boolean; - address: string; - priority: number; - nettype: string; - unspentOuts: any; - }) => Promise; -} - -const MY_MONERO: { core: MyMoneroCore | false } = { core: false }; -export default { - checkDestination(value: string): boolean { - return payment.IsValidPaymentIDOrNoPaymentID(value); - }, - - async getCore(): Promise { - if (MY_MONERO.core) { - return MY_MONERO.core; - } - - const core: MyMoneroCore = MyMoneroCoreBridgeRN; - // eslint-disable-next-line camelcase - core.generate_key_image = async ( - txPublicKey: string, - privateViewKey: string, - publicSpendKey: string, - privateSpendKey: string, - outputIndex: string - ): Promise => { - if ( - !txPublicKey || - !privateViewKey || - !publicSpendKey || - !privateSpendKey - ) { - throw new Error('no keys 1'); - } - if ( - typeof txPublicKey === 'undefined' || - typeof privateViewKey === 'undefined' || - typeof publicSpendKey === 'undefined' || - typeof privateSpendKey === 'undefined' - ) { - throw new Error('no keys 2'); - } - if ( - txPublicKey === 'undefined' || - privateViewKey === 'undefined' || - publicSpendKey === 'undefined' || - privateSpendKey === 'undefined' - ) { - throw new Error('no keys 3'); - } - if (typeof outputIndex === 'undefined') { - outputIndex = ''; - } - - try { - let res = await MY_MONERO.core.Module.generateKeyImage( - txPublicKey, - privateViewKey, - publicSpendKey, - privateSpendKey, - Number(outputIndex + '') - ); - if (typeof res !== 'undefined' && res) { - if (typeof res === 'string') { - try { - const newRes = JSON.parse(res); - res = newRes; - } catch (e) { - console.log(e); - } - } - if (typeof res.retVal !== 'undefined') { - return res.retVal; - } - } - return res; - } catch (e) { - AirDAOCryptoLog.log( - 'MoneroUtilsParser.generate_key_image ' + e.message - ); - throw new Error('MoneroUtilsParser.generate_key_image ' + e.message); - } - }; - MY_MONERO.core.createTransaction = async (options: { - privateViewKey: string; - publicSpendKey: string; - privateSpendKey: string; - randomOutsCb: () => void; - destinations: { to_address: string; send_amount: number }[]; - shouldSweep: boolean; - address: string; - priority: number; - nettype: string; - unspentOuts: any; - }): Promise => { - if (options.privateViewKey.length !== 64) { - throw Error('Invalid privateViewKey length'); - } - if (options.publicSpendKey.length !== 64) { - throw Error('Invalid publicSpendKey length'); - } - if (options.privateSpendKey.length !== 64) { - throw Error('Invalid privateSpendKey length'); - } - if (typeof options.randomOutsCb !== 'function') { - throw Error('Invalid randomsOutCB not a function'); - } - if (!Array.isArray(options.destinations)) { - throw Error('Invalid destinations'); - } - options.destinations.forEach((destination) => { - if ( - !destination.hasOwnProperty('to_address') || - !destination.hasOwnProperty('send_amount') - ) { - throw Error('Invalid destinations missing values'); - } - }); - if (options.shouldSweep) { - if (options.destinations.length !== 1) { - throw Error('Invalid number of destinations must be 1'); - } - if (options.destinations[0].send_amount !== 0) { - throw Error('Invalid amount when sweeping amount must be 0'); - } - } - - // check if destinations is set correctly - const args = { - destinations: options.destinations, - is_sweeping: options.shouldSweep, - from_address_string: options.address, - sec_viewKey_string: options.privateViewKey, - sec_spendKey_string: options.privateSpendKey, - pub_spendKey_string: options.publicSpendKey, - priority: '' + options.priority, - nettype_string: options.nettype, - unspentOuts: options.unspentOuts - }; - - let retString; - try { - retString = await MY_MONERO.core.Module.prepareTx( - JSON.stringify(args, null, '') - ); - } catch (e: any) { - throw Error(' MY_MONERO.core.Module.prepareTx error ' + e.message); - } - - const ret = JSON.parse(retString); - // check for any errors passed back from WebAssembly - if (ret.err_msg) { - AirDAOCryptoLog.log( - 'MoneroUtilsParser ret.err_msg error ' + ret.err_msg - ); - return false; - } - - const _getRandomOuts = async ( - numberOfOuts: number, - randomOutsCb: (numberOfOuts: number) => void - ) => { - // tslint:disable-next-line:no-shadowed-variable - const randomOuts = await randomOutsCb(numberOfOuts); - if ( - typeof randomOuts.amount_outs === 'undefined' || - !Array.isArray(randomOuts.amount_outs) - ) { - throw Error('Invalid amount_outs in randomOutsCb response'); - } - return randomOuts; - }; - - AirDAOCryptoLog.log( - 'MoneroUtilsParser ret?.amounts?.length ' + ret?.amounts?.length - ); - - // fetch random decoys - const randomOuts = await _getRandomOuts( - ret?.amounts?.length || 0, - options.randomOutsCb - ); - // send random decoys on and complete the tx creation - const retString2 = await MY_MONERO.core.Module.createAndSignTx( - JSON.stringify(randomOuts) - ); - const rawTx = JSON.parse(retString2); - // check for any errors passed back from WebAssembly - if (rawTx.err_msg) { - throw Error(rawTx.err_msg); - } - rawTx.mixin = parseInt(rawTx.mixin, 20); - rawTx.isXMRAddressIntegrated = rawTx.isXMRAddressIntegrated === 'true'; - - return rawTx; - }; - return MY_MONERO.core; - }, - - async parseAddressInfo( - address: string, - data: any, - privViewKey: string, - pubSpendKey: string, - privSpendKey: string - ): Promise { - try { - await this.getCore(); - let resData: any = false; - await parser.Parsed_AddressInfo__keyImageManaged( - data, - address, - privViewKey, - pubSpendKey, - privSpendKey, - MY_MONERO.core, - (e: any, returnValuesByKey: any) => { - if (e) { - console.log('MoneroUtilsParser.parseAddressInfo error2', e); - } - resData = returnValuesByKey; - } - ); - return resData; - } catch (e) { - if (config.debug.cryptoErrors) { - console.log('MoneroUtilsParser.parseAddressInfo error', e); - } - } - }, - - async parseAddressTransactions( - address: string, - data: any, - privViewKey: string, - pubSpendKey: string, - privSpendKey: string - ): Promise { - try { - await this.getCore(); - let resData: any = false; - await parser.Parsed_AddressTransactions__keyImageManaged( - data, - address, - privViewKey, - pubSpendKey, - privSpendKey, - MY_MONERO.core, - (e: any, returnValuesByKey: any) => { - if (e) { - console.log('MoneroUtilsParser.parseAddressTransactions error', e); - } - resData = returnValuesByKey; - } - ); - return resData; - } catch (e) { - if (config.debug.cryptoErrors) { - console.log('MoneroUtilsParser.parseAddressTransactions error', e); - } - } - } -}; diff --git a/crypto/blockchains/xmr/ext/vendor/KeyimageCache.js b/crypto/blockchains/xmr/ext/vendor/KeyimageCache.js deleted file mode 100644 index 76dcc76c8..000000000 --- a/crypto/blockchains/xmr/ext/vendor/KeyimageCache.js +++ /dev/null @@ -1,73 +0,0 @@ -'use strict' - -const Lazy_KeyImage = async function ( - mutable_keyImagesByCacheKey, // pass a mutable JS dictionary - tx_pub_key, - out_index, - public_address, - view_key__private, - spend_key__public, - spend_key__private, - coreBridge_instance // must pass this so this fn can remain synchronous -) { - var cache_index = tx_pub_key + ':' + public_address + ':' + out_index - const cached__key_image = mutable_keyImagesByCacheKey[cache_index] - if ( - typeof cached__key_image !== 'undefined' && - cached__key_image !== null - ) { - return cached__key_image - } - var key_image = await coreBridge_instance.generate_key_image( - tx_pub_key, - view_key__private, - spend_key__public, - spend_key__private, - out_index - ) - // cache: - mutable_keyImagesByCacheKey[cache_index] = key_image - // - return key_image -} -exports.Lazy_KeyImage = Lazy_KeyImage -// -// -// Managed caches - Can be used by apps which can't send a mutable_keyImagesByCacheKey -const __global_managed_keyImageCaches_by_walletId = {} -function _managedKeyImageCacheWalletIdForWalletWith (public_address) { - // NOTE: making the assumption that public_address is unique enough to identify a wallet for caching.... - // FIXME: with subaddresses, is that still the case? would we need to split them up by subaddr anyway? - if ( - public_address == '' || - !public_address || - typeof public_address === 'undefined' - ) { - throw 'managedKeyImageCacheIdentifierForWalletWith: Illegal public_address' - } - return '' + public_address -} - -const Lazy_KeyImageCacheForWalletWith = function (public_address) { - var cacheId = _managedKeyImageCacheWalletIdForWalletWith(public_address) - var cache = __global_managed_keyImageCaches_by_walletId[cacheId] - if (typeof cache === 'undefined' || !cache) { - cache = {} - __global_managed_keyImageCaches_by_walletId[cacheId] = cache - } - return cache -} -exports.Lazy_KeyImageCacheForWalletWith = Lazy_KeyImageCacheForWalletWith - -const DeleteManagedKeyImagesForWalletWith = function (public_address) { - // IMPORTANT: Ensure you call this method when you want to clear your wallet from - // memory or delete it, or else you could leak key images and public addresses. - const cacheId = _managedKeyImageCacheWalletIdForWalletWith(public_address) - delete __global_managed_keyImageCaches_by_walletId[cacheId] - // - const cache = __global_managed_keyImageCaches_by_walletId[cacheId] - if (typeof cache !== 'undefined') { - throw 'Key image cache still exists after deletion' - } -} -exports.DeleteManagedKeyImagesForWalletWith = DeleteManagedKeyImagesForWalletWith diff --git a/crypto/blockchains/xmr/ext/vendor/ResponseParser.ts b/crypto/blockchains/xmr/ext/vendor/ResponseParser.ts deleted file mode 100644 index f1f76f3c0..000000000 --- a/crypto/blockchains/xmr/ext/vendor/ResponseParser.ts +++ /dev/null @@ -1,327 +0,0 @@ -// Copyright (c) 2014-2019, MyMonero.com -// -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without modification, are -// permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this list of -// conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, this list -// of conditions and the following disclaimer in the documentation and/or other -// materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its contributors may be -// used to endorse or promote products derived from this software without specific -// prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY -// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL -// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, -// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// -'use strict'; -// -const JSBigInt = require('@mymonero/mymonero-bigint').BigInteger; -const monero_amount_format_utils = require('@mymonero/mymonero-money-format'); -const monero_keyImage_cache_utils = require('./KeyimageCache'); // require('@mymonero/mymonero-keyimage-cache') -// -async function Parsed_AddressInfo__sync( - keyImage_cache, - data, - address, - view_key__private, - spend_key__public, - spend_key__private, - coreBridge_instance -) { - // -> returnValuesByKey - const total_received = new JSBigInt(data.total_received || 0); - const locked_balance = new JSBigInt(data.locked_funds || 0); - var total_sent = new JSBigInt(data.total_sent || 0); // will be modified in place - // - const account_scanned_tx_height = data.scanned_height || 0; - const account_scanned_block_height = data.scanned_block_height || 0; - const account_scan_start_height = data.start_height || 0; - const transaction_height = data.transaction_height || 0; - const blockchain_height = data.blockchain_height || 0; - const spent_outputs = data.spent_outputs || []; - // - for (let spent_output of spent_outputs) { - var key_image = await monero_keyImage_cache_utils.Lazy_KeyImage( - keyImage_cache, - spent_output.tx_pub_key, - spent_output.out_index, - address, - view_key__private, - spend_key__public, - spend_key__private, - coreBridge_instance - ); - if (spent_output.key_image !== key_image) { - // console.log('💬 Output used as mixin (' + spent_output.key_image + '/' + key_image + ')') - total_sent = new JSBigInt(total_sent).subtract(spent_output.amount); - } - } - // - const ratesBySymbol = data.rates || {}; // jic it's not there - // - const returnValuesByKey = { - total_received_String: total_received ? total_received.toString() : null, - locked_balance_String: locked_balance ? locked_balance.toString() : null, - total_sent_String: total_sent ? total_sent.toString() : null, - // ^serialized JSBigInt - spent_outputs: spent_outputs, - account_scanned_tx_height: account_scanned_tx_height, - account_scanned_block_height: account_scanned_block_height, - account_scan_start_height: account_scan_start_height, - transaction_height: transaction_height, - blockchain_height: blockchain_height, - // - ratesBySymbol: ratesBySymbol - }; - return returnValuesByKey; -} -async function Parsed_AddressInfo__sync__keyImageManaged( - data, - address, - view_key__private, - spend_key__public, - spend_key__private, - coreBridge_instance -) { - // -> returnValuesByKey - const keyImageCache = - monero_keyImage_cache_utils.Lazy_KeyImageCacheForWalletWith(address); - return Parsed_AddressInfo__sync( - keyImageCache, - data, - address, - view_key__private, - spend_key__public, - spend_key__private, - coreBridge_instance - ); -} -async function Parsed_AddressInfo( - keyImage_cache, - data, - address, - view_key__private, - spend_key__public, - spend_key__private, - coreBridge_instance, - fn // (err?, returnValuesByKey) -> Void -) { - const returnValuesByKey = await Parsed_AddressInfo__sync( - keyImage_cache, - data, - address, - view_key__private, - spend_key__public, - spend_key__private, - coreBridge_instance - ); - fn(null, returnValuesByKey); -} -async function Parsed_AddressInfo__keyImageManaged( - data, - address, - view_key__private, - spend_key__public, - spend_key__private, - coreBridge_instance, - fn -) { - // -> returnValuesByKey - await Parsed_AddressInfo( - monero_keyImage_cache_utils.Lazy_KeyImageCacheForWalletWith(address), - data, - address, - view_key__private, - spend_key__public, - spend_key__private, - coreBridge_instance, - fn - ); -} -exports.Parsed_AddressInfo = Parsed_AddressInfo; -exports.Parsed_AddressInfo__keyImageManaged = - Parsed_AddressInfo__keyImageManaged; // in case you can't send a mutable key image cache dictionary -exports.Parsed_AddressInfo__sync__keyImageManaged = - Parsed_AddressInfo__sync__keyImageManaged; // in case you can't send a mutable key image cache dictionary -exports.Parsed_AddressInfo__sync = Parsed_AddressInfo__sync; -// -async function Parsed_AddressTransactions( - keyImage_cache, - data, - address, - view_key__private, - spend_key__public, - spend_key__private, - coreBridge_instance, - fn // (err?, returnValuesByKey) -> Void -) { - const returnValuesByKey = await Parsed_AddressTransactions__sync( - keyImage_cache, - data, - address, - view_key__private, - spend_key__public, - spend_key__private, - coreBridge_instance - ); - fn(null, returnValuesByKey); -} -async function Parsed_AddressTransactions__sync( - keyImage_cache, - data, - address, - view_key__private, - spend_key__public, - spend_key__private, - coreBridge_instance -) { - const account_scanned_height = data.scanned_height || 0; - const account_scanned_block_height = data.scanned_block_height || 0; - const account_scan_start_height = data.start_height || 0; - const transaction_height = data.transaction_height || 0; - const blockchain_height = data.blockchain_height || 0; - // - const transactions = data.transactions || []; - // - // TODO: rewrite this with more clarity if possible - for (let i = 0; i < transactions.length; ++i) { - if ((transactions[i].spent_outputs || []).length > 0) { - for (var j = 0; j < transactions[i].spent_outputs.length; ++j) { - var key_image = await monero_keyImage_cache_utils.Lazy_KeyImage( - keyImage_cache, - transactions[i].spent_outputs[j].tx_pub_key, - transactions[i].spent_outputs[j].out_index, - address, - view_key__private, - spend_key__public, - spend_key__private, - coreBridge_instance - ); - if (transactions[i].spent_outputs[j].key_image !== key_image) { - // console.log('Output used as mixin, ignoring (' + transactions[i].spent_outputs[j].key_image + '/' + key_image + ')') - transactions[i].total_sent = new JSBigInt(transactions[i].total_sent) - .subtract(transactions[i].spent_outputs[j].amount) - .toString(); - transactions[i].spent_outputs.splice(j, 1); - j--; - } - } - } - if ( - new JSBigInt(transactions[i].total_received || 0) - .add(transactions[i].total_sent || 0) - .compare(0) <= 0 - ) { - transactions.splice(i, 1); - i--; - continue; - } - transactions[i].amount = new JSBigInt(transactions[i].total_received || 0) - .subtract(transactions[i].total_sent || 0) - .toString(); - transactions[i].approx_float_amount = parseFloat( - monero_amount_format_utils.formatMoney(transactions[i].amount) - ); - transactions[i].timestamp = transactions[i].timestamp; - const record__payment_id = transactions[i].payment_id; - if (typeof record__payment_id !== 'undefined' && record__payment_id) { - if (record__payment_id.length == 16) { - // short (encrypted) pid - if (transactions[i].approx_float_amount < 0) { - // outgoing - delete transactions[i]['payment_id']; // need to filter these out .. because the server can't filter out short (encrypted) pids on outgoing txs - } - } - } - } - transactions.sort((a, b) => { - if (a.mempool == true) { - if (b.mempool != true) { - return -1; // a first - } - // both mempool - fall back to .id compare - } else if (b.mempool == true) { - return 1; // b first - } - return b.id - a.id; - }); - // prepare transactions to be serialized - for (let transaction of transactions) { - transaction.amount = transaction.amount.toString(); // JSBigInt -> String - if ( - typeof transaction.total_sent !== 'undefined' && - transaction.total_sent !== null - ) { - transaction.total_sent = transaction.total_sent.toString(); - } - } - // on the other side, we convert transactions timestamp to Date obj - const returnValuesByKey = { - account_scanned_height: account_scanned_height, - account_scanned_block_height: account_scanned_block_height, - account_scan_start_height: account_scan_start_height, - transaction_height: transaction_height, - blockchain_height: blockchain_height, - serialized_transactions: transactions - }; - return returnValuesByKey; -} -async function Parsed_AddressTransactions__sync__keyImageManaged( - data, - address, - view_key__private, - spend_key__public, - spend_key__private, - coreBridge_instance -) { - const keyImageCache = - monero_keyImage_cache_utils.Lazy_KeyImageCacheForWalletWith(address); - return Parsed_AddressTransactions__sync( - keyImageCache, - data, - address, - view_key__private, - spend_key__public, - spend_key__private, - coreBridge_instance - ); -} -async function Parsed_AddressTransactions__keyImageManaged( - data, - address, - view_key__private, - spend_key__public, - spend_key__private, - coreBridge_instance, - fn -) { - await Parsed_AddressTransactions( - monero_keyImage_cache_utils.Lazy_KeyImageCacheForWalletWith(address), - data, - address, - view_key__private, - spend_key__public, - spend_key__private, - coreBridge_instance, - fn - ); -} -exports.Parsed_AddressTransactions = Parsed_AddressTransactions; -exports.Parsed_AddressTransactions__keyImageManaged = - Parsed_AddressTransactions__keyImageManaged; -exports.Parsed_AddressTransactions__sync = Parsed_AddressTransactions__sync; -exports.Parsed_AddressTransactions__sync__keyImageManaged = - Parsed_AddressTransactions__sync__keyImageManaged; diff --git a/crypto/blockchains/xmr/providers/XmrSendProvider.ts b/crypto/blockchains/xmr/providers/XmrSendProvider.ts deleted file mode 100644 index 6472f57b6..000000000 --- a/crypto/blockchains/xmr/providers/XmrSendProvider.ts +++ /dev/null @@ -1,115 +0,0 @@ -/** - * @version 0.11 - */ -// @ts-ignore -import settingsActions from '../../../../app/appstores/Stores/Settings/SettingsActions'; -import AirDAOAxios from '../../../common/AirDAOAxios'; -import AirDAOCryptoLog from '../../../common/AirDAOCryptoLog'; - -interface SendParams { - address: string; - privViewKey: string; - tx: string; -} - -interface AirDAOAxiosResponse { - data: { - message: string; - double_spend?: boolean; - fee_too_low?: boolean; - overspend?: boolean; - status?: string; - }; - message?: string; -} - -export default class XmrSendProvider { - private _settings: any; - private _link: string | undefined; - private _serverUrl: string; - - constructor(settings: any) { - this._settings = settings; - this._link = undefined; - this._serverUrl = 'api.mymonero.com:8443'; - } - - private async _init(): Promise { - if (this._link !== undefined) { - return; - } - - const serverUrl = await settingsActions.getSetting('xmrServerSend'); - if (serverUrl && serverUrl !== 'false') { - this._serverUrl = serverUrl.trim(); - } - - let link = this._serverUrl; - if (!link.startsWith('http')) { - link = `https://${this._serverUrl}`; - } - - if (!link.endsWith('/')) { - link += '/'; - } - - this._link = link; - } - - public async send(params: SendParams): Promise { - await this._init(); - - try { - let resNode: AirDAOAxiosResponse; - - if (this._link?.includes('mymonero.com')) { - // @ts-ignore - resNode = await AirDAOAxios.post(this._link + 'submit_raw_tx', { - address: params.address, - view_key: params.privViewKey, - tx: params.tx - }); - } else { - // @ts-ignore - resNode = await AirDAOAxios.post(this._link + 'send_raw_transaction', { - tx_as_hex: params.tx, - do_not_relay: false - }); - } - - AirDAOCryptoLog.log( - `'XmrSendProvider node ${this._link},' + - ${resNode.data}` - ); - const responseData = resNode.data; - - if (typeof responseData !== 'object') { - throw new Error('SERVER_RESPONSE_NOT_VALID'); - } - - if (responseData.double_spend) { - throw new Error('SERVER_RESPONSE_DOUBLE_SPEND'); - } - - if (responseData.fee_too_low) { - throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_FEE'); - } - - if (responseData.overspend) { - throw new Error('SERVER_RESPONSE_TOO_BIG_FEE_FOR_TRANSACTION'); - } - - if (responseData.status === 'Failed') { - throw new Error(responseData.message); - } - - return responseData; - } catch (e: any) { - if (e.message.includes('double')) { - throw new Error('SERVER_RESPONSE_DOUBLE_SPEND'); - } else { - throw e; - } - } - } -} diff --git a/crypto/blockchains/xmr/providers/XmrUnspentsProvider.js b/crypto/blockchains/xmr/providers/XmrUnspentsProvider.js deleted file mode 100644 index 0bc3e819a..000000000 --- a/crypto/blockchains/xmr/providers/XmrUnspentsProvider.js +++ /dev/null @@ -1,122 +0,0 @@ -/** - * @version 0.11 - */ -import settingsActions from '../../../../app/appstores/Stores/Settings/SettingsActions'; -import AirDAOAxios from '../../../common/AirDAOAxios'; -import AirDAOCryptoLog from '../../../common/AirDAOCryptoLog'; - -export default class XmrUnspentsProvider { - constructor(settings) { - this._settings = settings; - this._link = false; - this._cache = {}; - } - - init() { - if (this._link) return false; - this._serverUrl = settingsActions.getSettingStatic('xmrServer'); - if (!this._serverUrl || this._serverUrl === 'false') { - this._serverUrl = 'api.mymonero.com:8443'; - } - - let link = this._serverUrl.trim(); - if (link.substr(0, 4).toLowerCase() !== 'http') { - link = 'https://' + this._serverUrl; - } - if (link[link.length - 1] !== '/') { - link = link + '/'; - } - - this._link = link; - this._cache = {}; - } - - async _getUnspents(params, fn) { - try { - const key = JSON.stringify(params); - let res = {}; - if (typeof this._cache[key] === 'undefined') { - AirDAOCryptoLog.log('XmrUnspentsProvider Xmr._getUnspents', key); - /* - const linkParams = { - address: params.address, - view_key: params.privViewKey, - amount: params.amount.toString(), - mixin: '10', - use_dust: true, - dust_threshold: '2000000000' - } - */ - res = await AirDAOAxios.post(this._link + 'get_unspent_outs', params); - AirDAOCryptoLog.log( - 'XmrUnspentsProvider Xmr._getUnspents res ' + - JSON.stringify(res.data).substr(0, 200) - ); - this._cache[key] = res.data; - } else { - res = { data: this._cache[key] }; - } - if (typeof fn === 'undefined' || !fn) { - return res.data; - } else { - fn(null, res.data); - } - } catch (e) { - e.message += ' while Xmr._getUnspents'; - fn(e, null); - if (typeof fn === 'undefined' || !fn) { - throw e; - } else { - fn(e, null); - } - } - } - - async _getRandomOutputs(params, fn) { - try { - AirDAOCryptoLog.log('XmrUnspentsProvider Xmr._getRandomOutputs', params); - - /* - const amounts = usingOuts.map(o => (o.rct ? '0' : o.amount.toString())) - const linkParams = { - amounts, - count: (mixin * 1 + 1) - } - */ - - const res = await AirDAOAxios.post( - this._link + 'get_random_outs', - params - ); - await AirDAOCryptoLog.log( - 'XmrUnspentsProvider Xmr._getRandomOutputs res ' + - JSON.stringify(res.data).substr(0, 200) - ); - - if ( - typeof res.data === 'undefined' || - !typeof res.data || - typeof res.data.amount_outs === 'undefined' || - !res.data.amount_outs || - res.data.amount_outs.length === 0 - ) { - throw new Error('SERVER_RESPONSE_NO_RESPONSE_XMR'); - } - - if (typeof fn === 'undefined' || !fn) { - return res.data; - } else { - fn(null, res.data); - } - } catch (e) { - if (e.message.indexOf('SERVER_RESPONSE') === -1) { - e.message += ' while Xmr._getRandomOutputs'; - } - if (typeof fn === 'undefined' || !fn) { - throw e; - } else { - fn(e, null); - } - } - } -} diff --git a/crypto/blockchains/xmr/providers/XmrUnspentsProvider.ts b/crypto/blockchains/xmr/providers/XmrUnspentsProvider.ts deleted file mode 100644 index 6ae2bf726..000000000 --- a/crypto/blockchains/xmr/providers/XmrUnspentsProvider.ts +++ /dev/null @@ -1,142 +0,0 @@ -/** - * @version 0.11 - */ -// @ts-ignore -import settingsActions from '../../../../app/appstores/Stores/Settings/SettingsActions'; -import AirDAOAxios from '../../../common/AirDAOAxios'; -import AirDAOCryptoLog from '../../../common/AirDAOCryptoLog'; - -interface ApiResponse { - data: any; -} - -export default class XmrUnspentsProvider { - private _settings: any; - private _link: string | false; - private _cache: { [key: string]: any }; - private _serverUrl: string | undefined; - - constructor(settings: any) { - this._settings = settings; - this._link = false; - this._cache = {}; - } - - init(): void { - if (this._link) return; - this._serverUrl = settingsActions.getSettingStatic('xmrServer'); - if (!this._serverUrl) { - this._serverUrl = 'api.mymonero.com:8443'; - } - - if (typeof this._serverUrl === 'string') { - let link = this._serverUrl.trim(); - if (link.substr(0, 4).toLowerCase() !== 'http') { - link = 'https://' + this._serverUrl; - } - if (link[link.length - 1] !== '/') { - link = link + '/'; - } - - this._link = link; - this._cache = {}; - } - } - - async _getUnspents( - params: any, - fn?: (err: Error | null, data: any) => void - ): Promise { - try { - const key = JSON.stringify(params); - let res: ApiResponse; - if (typeof this._cache[key] === 'undefined') { - // @ts-ignore - AirDAOCryptoLog.log('XmrUnspentsProvider Xmr._getUnspents', key); - /* - const linkParams = { - address: params.address, - view_key: params.privViewKey, - amount: params.amount.toString(), - mixin: '10', - use_dust: true, - dust_threshold: '2000000000' - } - */ - // @ts-ignore - res = await AirDAOAxios.post(this._link + 'get_unspent_outs', params); - AirDAOCryptoLog.log( - 'XmrUnspentsProvider Xmr._getUnspents res ' + - JSON.stringify(res.data).substr(0, 200) - ); - this._cache[key] = res.data; - } else { - res = { data: this._cache[key] }; - } - if (typeof fn === 'undefined' || !fn) { - return res.data; - } else { - fn(null, res.data); - } - } catch (e: any) { - e.message += ' while Xmr._getUnspents'; - if (typeof fn === 'undefined' || !fn) { - throw e; - } else { - fn(e, null); - } - } - } - - async _getRandomOutputs( - params: any, - fn?: (err: Error | null, data: any) => void - ): Promise { - try { - AirDAOCryptoLog.log('XmrUnspentsProvider Xmr._getRandomOutputs', params); - - /* - const amounts = usingOuts.map(o => (o.rct ? '0' : o.amount.toString())) - const linkParams = { - amounts, - count: (mixin * 1 + 1) - } - */ - - // @ts-ignore - const res: ApiResponse = await AirDAOAxios.post( - this._link + 'get_random_outs', - params - ); - await AirDAOCryptoLog.log( - 'XmrUnspentsProvider Xmr._getRandomOutputs res ' + - JSON.stringify(res.data).substr(0, 200) - ); - - if ( - typeof res === 'object' && - res.data && - typeof res.data.amount_outs !== 'undefined' && - res.data.amount_outs && - res.data.amount_outs.length > 0 - ) { - if (typeof fn === 'undefined' || !fn) { - return res.data; - } else { - fn(null, res.data); - } - } else { - throw new Error('SERVER_RESPONSE_NO_RESPONSE_XMR'); - } - } catch (e: any) { - if (e.message.indexOf('SERVER_RESPONSE') === -1) { - e.message += ' while Xmr._getRandomOutputs'; - } - if (typeof fn === 'undefined' || !fn) { - throw e; - } else { - fn(e, null); - } - } - } -} diff --git a/crypto/blockchains/xrp/XrpAddressProcessor.ts b/crypto/blockchains/xrp/XrpAddressProcessor.ts deleted file mode 100644 index e38d4d799..000000000 --- a/crypto/blockchains/xrp/XrpAddressProcessor.ts +++ /dev/null @@ -1,44 +0,0 @@ -import * as bitcoin from 'bitcoinjs-lib'; -import * as basexLib from 'base-x'; - -const basex = basexLib.default( - '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' -); - -export default class XrpAddressProcessor { - async setBasicRoot(root: string): Promise {} - - /** - * @param {string|Buffer} privateKey - * @param {*} data - * @returns {Promise<{privateKey: string, address: string, addedData: *}>} - */ - async getAddress( - privateKey: string | Buffer, - data: any = {} - ): Promise<{ privateKey: string; address: string; addedData: any }> { - privateKey = Buffer.from(privateKey); - const keyPair = bitcoin.ECPair.fromPrivateKey(privateKey); - const btcPrivateKey: string = keyPair.toWIF(); - const ripplePrivateKey: string = basex - .decode(btcPrivateKey) - // @ts-ignore - .toString('hex') - .slice(2, 66); - // @ts-ignore - const btcAddress: string = bitcoin.payments.p2pkh({ - pubkey: keyPair.publicKey, - network: bitcoin.networks.bitcoin - }).address; - const rippleAddress: string = basex.encode( - basexLib - .default('123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz') - .decode(btcAddress) - ); - const addedData: { publicKey?: string } = {}; - if (data && data.publicKey) { - addedData.publicKey = data.publicKey.toString('hex'); - } - return { address: rippleAddress, privateKey: ripplePrivateKey, addedData }; - } -} diff --git a/crypto/blockchains/xrp/XrpScannerProcessor.js b/crypto/blockchains/xrp/XrpScannerProcessor.js deleted file mode 100644 index be7e6b142..000000000 --- a/crypto/blockchains/xrp/XrpScannerProcessor.js +++ /dev/null @@ -1,94 +0,0 @@ -/** - * @version 0.5 - */ -import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; -import XrpTmpDS from './stores/XrpTmpDS'; -import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings'; -import XrpDataRippleProvider from '@crypto/blockchains/xrp/basic/XrpDataRippleProvider'; -import XrpDataScanProvider from '@crypto/blockchains/xrp/basic/XrpDataScanProvider'; - -let CACHE_BLOCK_DATA = {}; - -export default class XrpScannerProcessor { - _inited = false; - - async init() { - if (this._inited) { - return false; - } - CACHE_BLOCK_DATA = await XrpTmpDS.getCache(); - const serverType = BlocksoftExternalSettings.getStatic('XRP_SCANNER_TYPE'); - if (serverType === 'dataripple') { - this.provider = new XrpDataRippleProvider(); - } else { - this.provider = new XrpDataScanProvider(); - } - this.provider.setCache(CACHE_BLOCK_DATA); - this._inited = true; - } - - /** - * https://data.ripple.com/v2/accounts/rL2SpzwrCZ4N2BaPm88pNGGHkPLzejZgB8/balances - * @param {string} address - * @return {Promise<{balance, unconfirmed, provider}>} - */ - async getBalanceBlockchain(address) { - await this.init(); - - let res = false; - let balance = 0; - let provider = 'none'; - try { - res = await this.provider.getBalanceBlockchain(address); - if (res && typeof res.balance !== 'undefined') { - balance = res.balance; - provider = res.provider; - } - } catch (e) { - if ( - e.message.indexOf('timed out') === -1 && - e.message.indexOf('account not found') === -1 - ) { - throw e; - } else { - return false; - } - } - return { balance, unconfirmed: 0, provider }; - } - - /** - * @param {string} scanData.account.address - * @param {*} scanData.additional - * @param {string} scanData.account.walletHash - * @return {Promise} - */ - async getTransactionsBlockchain(scanData, source = '') { - await this.init(); - const address = scanData.account.address.trim(); - await AirDAOCryptoLog.log( - 'XrpScannerProcessor.getTransactions started ' + address - ); - - let transactions = []; - try { - transactions = await this.provider.getTransactionsBlockchain(scanData); - } catch (e) { - if ( - e.message.indexOf('account not found') === -1 && - e.message.indexOf('to retrieve payments') === -1 && - e.message.indexOf('limit exceeded') === -1 && - e.message.indexOf('timed out') === -1 - ) { - throw e; - } else { - return false; - } - } - - await AirDAOCryptoLog.log( - 'XrpScannerProcessor.getTransactions finished ' + address - ); - return transactions; - } -} diff --git a/crypto/blockchains/xrp/XrpScannerProcessor.ts b/crypto/blockchains/xrp/XrpScannerProcessor.ts deleted file mode 100644 index cbaba02fb..000000000 --- a/crypto/blockchains/xrp/XrpScannerProcessor.ts +++ /dev/null @@ -1,98 +0,0 @@ -import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; -import XrpTmpDS from './stores/XrpTmpDS'; -import BlocksoftExternalSettings from '@crypto/common/AirDAOExternalSettings'; -import XrpDataRippleProvider from '@crypto/blockchains/xrp/basic/XrpDataRippleProvider'; -import XrpDataScanProvider from '@crypto/blockchains/xrp/basic/XrpDataScanProvider'; - -interface UnifiedTransaction { - // TODO -} - -interface BalanceResult { - balance: number; - unconfirmed: number; - provider: string; -} - -let CACHE_BLOCK_DATA: any = {}; - -export default class XrpScannerProcessor { - private _inited = false; - private provider!: XrpDataRippleProvider | XrpDataScanProvider; - - async init(): Promise { - if (this._inited) { - return false; - } - CACHE_BLOCK_DATA = await XrpTmpDS.getCache(); - const serverType = BlocksoftExternalSettings.getStatic('XRP_SCANNER_TYPE'); - if (serverType === 'dataripple') { - this.provider = new XrpDataRippleProvider(); - } else { - this.provider = new XrpDataScanProvider(); - } - this.provider.setCache(CACHE_BLOCK_DATA); - this._inited = true; - return true; - } - - async getBalanceBlockchain(address: string): Promise { - await this.init(); - - let res: any = false; - let balance = 0; - let provider = 'none'; - try { - res = await this.provider.getBalanceBlockchain(address); - if (res && typeof res.balance !== 'undefined') { - balance = res.balance; - provider = res.provider; - } - } catch (e: any) { - if ( - e.message.indexOf('timed out') === -1 && - e.message.indexOf('account not found') === -1 - ) { - throw e; - } else { - return false; - } - } - return { balance, unconfirmed: 0, provider }; - } - - async getTransactionsBlockchain(scanData: { - account: { - address: string; - walletHash: string; - }; - additional: any; - }): Promise { - await this.init(); - const address = scanData.account.address.trim(); - await AirDAOCryptoLog.log( - 'XrpScannerProcessor.getTransactions started ' + address - ); - - let transactions: UnifiedTransaction[] | false = []; - try { - transactions = await this.provider.getTransactionsBlockchain(scanData); - } catch (e: any) { - if ( - e.message.indexOf('account not found') === -1 && - e.message.indexOf('to retrieve payments') === -1 && - e.message.indexOf('limit exceeded') === -1 && - e.message.indexOf('timed out') === -1 - ) { - throw e; - } else { - return false; - } - } - - await AirDAOCryptoLog.log( - 'XrpScannerProcessor.getTransactions finished ' + address - ); - return transactions; - } -} diff --git a/crypto/blockchains/xrp/XrpTransferProcessor.ts b/crypto/blockchains/xrp/XrpTransferProcessor.ts deleted file mode 100644 index d2619fec6..000000000 --- a/crypto/blockchains/xrp/XrpTransferProcessor.ts +++ /dev/null @@ -1,322 +0,0 @@ -/** - * @version 0.20 - * https://gist.github.com/WietseWind/19df307c3c68748543971242284ade4d - * - * https://xrpl.org/rippleapi-reference.html#preparepayment - * https://xrpl.org/rippleapi-reference.html#sign - * https://xrpl.org/rippleapi-reference.html#submit - */ -import AirDAOCryptoLog from '../../common/AirDAOCryptoLog'; -import BlocksoftUtils from '../../common/AirDAOUtils'; -import { XrpTxSendProvider } from './basic/XrpTxSendProvider'; -import MarketingEvent from '../../../app/services/Marketing/MarketingEvent'; - -import BlocksoftExternalSettings from '@crypto/common/AirDAOExternalSettings'; -import { AirDAOBlockchainTypes } from '@crypto/blockchains/AirDAOBlockchainTypes'; -import BlocksoftDispatcher from '@lib/BlocksoftDispatcher'; - -const FEE_DECIMALS = 6; - -export default class XrpTransferProcessor - implements AirDAOBlockchainTypes.TransferProcessor -{ - private _settings: { network: string; currencyCode: string }; - private _provider: XrpTxSendProvider | undefined; - private _inited = false; - - constructor(settings: { network: string; currencyCode: string }) { - this._settings = settings; - } - - needPrivateForFee(): boolean { - return false; - } - - checkSendAllModal(data: { currencyCode: any }): boolean { - return false; - } - - async checkTransferHasError( - data: AirDAOBlockchainTypes.CheckTransferHasErrorData - ): Promise { - // @ts-ignore - if ( - data.amount && - data.amount * 1 > BlocksoftExternalSettings.getStatic('XRP_MIN') - ) { - return { isOk: true }; - } - /** - * @type {XrpScannerProcessor} - */ - const balanceProvider = BlocksoftDispatcher.getScannerProcessor( - this._settings.currencyCode - ); - const balanceRaw = await balanceProvider.getBalanceBlockchain( - data.addressTo - ); - if ( - balanceRaw && - typeof balanceRaw.balance !== 'undefined' && - balanceRaw.balance > BlocksoftExternalSettings.getStatic('XRP_MIN') - ) { - return { isOk: true }; - } else { - return { isOk: false, code: 'XRP', address: data.addressTo }; - } - } - - async getFeeRate( - data: AirDAOBlockchainTypes.TransferData, - privateData: AirDAOBlockchainTypes.TransferPrivateData, - additionalData: {} = {} - ): Promise { - const result: AirDAOBlockchainTypes.FeeRateResult = { - selectedFeeIndex: -1, - shouldShowFees: false - } as AirDAOBlockchainTypes.FeeRateResult; - - // @ts-ignore - if (data.amount * 1 <= 0) { - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' XrpTransferProcessor.getFeeRate ' + - data.addressFrom + - ' => ' + - data.addressTo + - ' skipped as zero amount' - ); - return result; - } - - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' XrpTransferProcessor.getFeeRate ' + - data.addressFrom + - ' => ' + - data.addressTo + - ' started amount: ' + - data.amount - ); - - let txJson = false; - try { - if (!this._inited) { - this._provider = new XrpTxSendProvider(); - this._inited = true; - } - txJson = await this._provider.getPrepared(data); - } catch (e: any) { - if (e.message.indexOf('Account not found') !== -1) { - return false; - } - if ( - e.message.indexOf( - 'Destination does not exist. Too little XRP sent to create it' - ) !== -1 - ) { - throw new Error('SERVER_RESPONSE_NOT_ENOUGH_BALANCE_DEST_XRP'); - } - throw e; - } - if (!txJson) { - throw new Error('SERVER_RESPONSE_BAD_INTERNET'); - } - // @ts-ignore - const fee = BlocksoftUtils.toUnified(txJson.Fee, FEE_DECIMALS); - - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' XrpTransferProcessor.getFeeRate ' + - data.addressFrom + - ' => ' + - data.addressTo + - ' finished amount: ' + - data.amount + - ' fee: ' + - fee - ); - result.fees = [ - { - langMsg: 'xrp_speed_one', - feeForTx: fee, - amountForTx: data.amount, - blockchainData: txJson - } - ]; - result.selectedFeeIndex = 0; - return result; - } - - async getTransferAllBalance( - data: AirDAOBlockchainTypes.TransferData, - privateData: AirDAOBlockchainTypes.TransferPrivateData, - additionalData: AirDAOBlockchainTypes.TransferAdditionalData = {} - ): Promise { - const balance = data.amount; - - // @ts-ignore - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' XrpTransferProcessor.getTransferAllBalance ', - data.addressFrom + ' => ' + balance - ); - // noinspection EqualityComparisonWithCoercionJS - if ( - BlocksoftUtils.diff( - balance, - BlocksoftExternalSettings.getStatic('XRP_MIN') - ) <= 0 - ) { - return { - selectedTransferAllBalance: '0', - selectedFeeIndex: -1, - fees: [], - shouldShowFees: false, - countedForBasicBalance: '0' - }; - } - - const result = await this.getFeeRate(data, privateData, additionalData); - // @ts-ignore - if (!result || result.selectedFeeIndex < 0) { - return { - selectedTransferAllBalance: '0', - selectedFeeIndex: -2, - fees: [], - shouldShowFees: false, - countedForBasicBalance: balance - }; - } - // @ts-ignore - result.fees[result.selectedFeeIndex].amountForTx = BlocksoftUtils.diff( - result.fees[result.selectedFeeIndex].amountForTx, - BlocksoftExternalSettings.getStatic('XRP_MIN') - ).toString(); - return { - ...result, - shouldShowFees: false, - selectedTransferAllBalance: - result.fees[result.selectedFeeIndex].amountForTx - }; - } - - async sendTx( - data: AirDAOBlockchainTypes.TransferData, - privateData: AirDAOBlockchainTypes.TransferPrivateData, - uiData: AirDAOBlockchainTypes.TransferUiData - ): Promise { - if (typeof privateData.privateKey === 'undefined') { - throw new Error('XRP transaction required privateKey'); - } - if (typeof data.addressTo === 'undefined') { - throw new Error('XRP transaction required addressTo'); - } - - let txJson = false; - try { - if (!this._inited) { - this._provider = new XrpTxSendProvider(); - this._inited = true; - } - txJson = await this._provider.getPrepared(data, false); - } catch (e: any) { - if (e.message.indexOf('Account not found') !== -1) { - throw new Error('SERVER_RESPONSE_BAD_INTERNET'); - } - if ( - e.message.indexOf( - 'Destination does not exist. Too little XRP sent to create it' - ) !== -1 - ) { - throw new Error('SERVER_RESPONSE_NOT_ENOUGH_BALANCE_DEST_XRP'); - } - throw e; - } - - // https://xrpl.org/rippleapi-reference.html#preparepayment - // @ts-ignore - AirDAOCryptoLog.log( - this._settings.currencyCode + ' XrpTransferProcessor.sendTx prepared', - txJson - ); - - // https://xrpl.org/rippleapi-reference.html#sign - if (typeof data.accountJson !== 'object') { - try { - const tmp = JSON.parse(data.accountJson); - data.accountJson = tmp; - } catch (e) { - AirDAOCryptoLog.err( - this._settings.currencyCode + - ' XrpTransferProcessor.sendTx no accountJson ' + - JSON.stringify(data.accountJson) - ); - } - } - if (typeof data.accountJson.publicKey === 'undefined') { - AirDAOCryptoLog.err( - this._settings.currencyCode + - ' XrpTransferProcessor.sendTx no publicKey ' + - JSON.stringify(data.accountJson) - ); - throw new Error('SERVER_RESPONSE_BAD_CODE'); - } - - if ( - typeof uiData !== 'undefined' && - typeof uiData.selectedFee !== 'undefined' && - typeof uiData.selectedFee.rawOnly !== 'undefined' && - uiData.selectedFee.rawOnly - ) { - return { - rawOnly: uiData.selectedFee.rawOnly, - raw: this._provider.signTx(data, privateData, txJson) - }; - } - - const result = await this._provider.sendTx(data, privateData, txJson); - - // noinspection ES6MissingAwait - MarketingEvent.logOnlyRealTime( - 'v20_rippled_any_result ' + data.addressFrom + ' => ' + data.addressTo, - { - txJson, - result - } - ); - // @ts-ignore - AirDAOCryptoLog.log( - this._settings.currencyCode + ' XrpTransferProcessor.sendTx result', - result - ); - - if (result.resultCode === 'tecNO_DST_INSUF_XRP') { - throw new Error(result.resultMessage); // not enough - could be replaced by translated - } else if (result.resultCode === 'tecUNFUNDED_PAYMENT') { - throw new Error('SERVER_RESPONSE_NOT_ENOUGH_BALANCE_XRP'); // not enough to pay - } else if (result.resultCode === 'tecNO_DST_INSUF_XRP') { - throw new Error('SERVER_RESPONSE_NOT_ENOUGH_BALANCE_DEST_XRP'); // not enough to create account - } else if (result.resultCode === 'tefBAD_AUTH') { - throw new Error(result.resultMessage); // not valid key - } else if (result.resultCode === 'tecDST_TAG_NEEDED') { - throw new Error('SERVER_RESPONSE_TAG_NEEDED_XRP'); - } - - if ( - typeof result.tx_json === 'undefined' || - typeof result.tx_json.hash === 'undefined' - ) { - throw new Error(result.resultMessage); // not enough - } - - if (result.resultCode !== 'tesSUCCESS') { - return { - transactionHash: result.tx_json.hash, - successMessage: result.resultMessage - }; // Held until escalated fee drops - } - - return { transactionHash: result.tx_json.hash }; - } -} diff --git a/crypto/blockchains/xrp/basic/XrpDataRippleProvider.js b/crypto/blockchains/xrp/basic/XrpDataRippleProvider.js deleted file mode 100644 index 496c02492..000000000 --- a/crypto/blockchains/xrp/basic/XrpDataRippleProvider.js +++ /dev/null @@ -1,256 +0,0 @@ -import AirDAOAxios from '@crypto/common/AirDAOAxios'; -import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; -import XrpTmpDS from '@crypto/blockchains/xrp/stores/XrpTmpDS'; - -const CACHE_VALID_TIME = 60000; // 1 minute -let CACHE_BLOCK_DATA = {}; - -const API_PATH = 'https://data.ripple.com/v2'; -export default class XrpDataRippleProvider { - setCache(tmp) { - CACHE_BLOCK_DATA = tmp; - } - - async getBalanceBlockchain(address) { - const link = `${API_PATH}/accounts/${address}/balances`; - let res = false; - let balance = 0; - - try { - res = await AirDAOAxios.getWithoutBraking(link); - if ( - res && - typeof res.data !== 'undefined' && - res.data && - typeof res.data.balances !== 'undefined' - ) { - let row; - for (row of res.data.balances) { - if (row.currency === 'XRP') { - balance = row.value; - break; - } - } - } else { - return false; - } - } catch (e) { - if ( - e.message.indexOf('timed out') === -1 && - e.message.indexOf('account not found') === -1 - ) { - throw e; - } else { - return false; - } - } - return { balance: balance, unconfirmed: 0, provider: 'ripple.com' }; - } - - /** - * @param {string} scanData.account.address - * @param {*} scanData.additional - * @param {string} scanData.account.walletHash - * @return {Promise} - */ - async getTransactionsBlockchain(scanData, source = '') { - const address = scanData.account.address.trim(); - const action = 'payments'; - await AirDAOCryptoLog.log( - 'XrpScannerProcessor.DataRipple.getTransactions ' + - action + - ' started ' + - address - ); - const link = `${API_PATH}/accounts/${address}/payments`; - let res = false; - try { - res = await AirDAOAxios.getWithoutBraking(link); - } catch (e) { - if ( - e.message.indexOf('account not found') === -1 && - e.message.indexOf('to retrieve payments') === -1 && - e.message.indexOf('limit exceeded') === -1 && - e.message.indexOf('timed out') === -1 - ) { - throw e; - } else { - return false; - } - } - - if (!res || typeof res.data === 'undefined' || !res.data) { - return false; - } - if (typeof res.data[action] === 'undefined') { - throw new Error('Undefined txs ' + link + ' ' + JSON.stringify(res.data)); - } - if (typeof res.data[action] === 'string') { - throw new Error('Undefined txs ' + link + ' ' + res.data[action]); - } - - const transactions = await this._unifyTransactions( - address, - res.data[action], - action - ); - await AirDAOCryptoLog.log( - 'XrpScannerProcessor.DataRipple.getTransactions ' + - action + - ' finished ' + - address - ); - return transactions; - } - - async _unifyTransactions(address, result) { - const transactions = []; - let tx; - for (tx of result) { - const transaction = await this._unifyPayment(address, tx); - if (transaction) { - transactions.push(transaction); - } - } - return transactions; - } - - /** - * @param {string} address - * @param {Object} transaction - * @param {string} transaction.amount '20.001' - * @param {string} transaction.delivered_amount '20.001', - * @param {Object} transaction.destination_balance_changes: [ { counterparty: '', currency: 'XRP', value: '20.001' } ], - * @param {Object} transaction.source_balance_changes: [ { counterparty: '', currency: 'XRP', value: '-20.001' } ], - * @param {string} transaction.tx_index 8 - * @param {string} transaction.currency 'XRP' - * @param {string} transaction.destination 'rL2SpzwrCZ4N2BaPm88pNGGHkPLzejZgB8' - * @param {string} transaction.destination_tag '1' - * @param {string} transaction.executed_time '2019-10-20T22:45:31Z' - * @param {string} transaction.ledger_index 50845930 - * @param {string} transaction.source 'rpJZ5WyotdphojwMLxCr2prhULvG3Voe3X' - * @param {string} transaction.source_currency 'XRP' - * @param {string} transaction.tx_hash '673F28303546CB8A0F45A0D80E6391B7A4A125DB8B72AD0DA635D625C3AD27F1' - * @param {string} transaction.transaction_cost '0.000012' - * @return {UnifiedTransaction} - * @private - **/ - async _unifyPayment(address, transaction) { - let direction, amount; - - if (transaction.currency === 'XRP') { - if (transaction.source_currency === 'XRP') { - direction = address === transaction.source ? 'outcome' : 'income'; - } else if (transaction.destination === address) { - direction = 'income'; // USDT any => XRP my - } else { - // USDT my => XRP not my - return false; // do nothing - } - } else if (transaction.source_currency === 'XRP') { - if (transaction.source === address) { - direction = 'outcome'; // XRP my => USDT any - } else { - // XRP not my => USDT my - return false; // do nothing - } - } else { - return false; // USDT => USDT - } - - if (direction === 'income') { - amount = transaction.delivered_amount; - } else { - amount = transaction.amount; - } - - let transactionStatus = 'new'; - let ledger = false; - if ( - typeof transaction.ledger_index !== 'undefined' && - transaction.ledger_index > 0 - ) { - ledger = await this._getLedger(transaction.ledger_index); - if (ledger && ledger.transactionConfirmations > 5) { - transactionStatus = 'success'; - } - } - - if (typeof transaction.executed_time === 'undefined') { - transaction.executed_time = ''; - } - const tx = { - transactionHash: transaction.tx_hash, - blockHash: ledger ? ledger.ledger_hash : '', - blockNumber: transaction.ledger_index, - blockTime: transaction.executed_time, - blockConfirmations: ledger ? ledger.transactionConfirmations : 0, - transactionDirection: direction, - addressFrom: transaction.source === address ? '' : transaction.source, - addressTo: - transaction.destination === address ? '' : transaction.destination, - addressAmount: amount, - transactionStatus: transactionStatus, - transactionFee: transaction.transaction_cost - }; - if (typeof transaction.destination_tag !== 'undefined') { - tx.transactionJson = { memo: transaction.destination_tag }; - } - return tx; - } - - async _getLedger(index) { - const now = new Date().getTime(); - await AirDAOCryptoLog.log( - 'XrpScannerProcessor.DataRipple._getLedger started ' + index - ); - const link = `${API_PATH}/ledgers/${index}`; - let res = false; - if ( - typeof CACHE_BLOCK_DATA[index] === 'undefined' || - (now - CACHE_BLOCK_DATA[index].time > CACHE_VALID_TIME && - CACHE_BLOCK_DATA[index].data.transactionConfirmations < 100) - ) { - try { - res = await AirDAOAxios.getWithoutBraking(link); - if ( - res.data && - typeof res.data !== 'undefined' && - typeof res.data.ledger !== 'undefined' - ) { - await AirDAOCryptoLog.log( - 'XrpScannerProcessor.DataRipple._getLedger updated for index ' + - index + - ' ' + - JSON.stringify(res.data.ledger) - ); - const ledger = { - close_time: res.data.ledger.close_time, - ledger_hash: res.data.ledger.ledger_hash, - transactionConfirmations: Math.round( - (now - res.data.ledger.close_time * 1000) / (60 * 1000) - ) // minutes - }; - CACHE_BLOCK_DATA[index] = { - data: ledger, - time: now - }; - } - await XrpTmpDS.saveCache(CACHE_BLOCK_DATA); - } catch (e) { - if ( - e.message.indexOf('timed out') === -1 && - e.message.indexOf('account not found') === -1 - ) { - throw e; - } else { - res = false; - } - } - } - if (typeof CACHE_BLOCK_DATA[index] === 'undefined') { - return false; - } - return CACHE_BLOCK_DATA[index].data; - } -} diff --git a/crypto/blockchains/xrp/basic/XrpDataRippleProvider.ts b/crypto/blockchains/xrp/basic/XrpDataRippleProvider.ts deleted file mode 100644 index 4842dc7f4..000000000 --- a/crypto/blockchains/xrp/basic/XrpDataRippleProvider.ts +++ /dev/null @@ -1,234 +0,0 @@ -import AirDAOAxios from '@crypto/common/AirDAOAxios'; -import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; -import XrpTmpDS from '@crypto/blockchains/xrp/stores/XrpTmpDS'; - -const CACHE_VALID_TIME = 60000; -let CACHE_BLOCK_DATA: { [index: string]: { data: any; time: number } } = {}; - -const API_PATH = 'https://data.ripple.com/v2'; -export default class XrpDataRippleProvider { - setCache(tmp: { [index: string]: { data: any; time: number } }) { - CACHE_BLOCK_DATA = tmp; - } - - async getBalanceBlockchain(address: string) { - const link = `${API_PATH}/accounts/${address}/balances`; - let res: any = false; - let balance = 0; - - try { - res = await AirDAOAxios.getWithoutBraking(link); - if ( - res && - typeof res.data !== 'undefined' && - res.data && - typeof res.data.balances !== 'undefined' - ) { - let row; - for (row of res.data.balances) { - if (row.currency === 'XRP') { - balance = row.value; - break; - } - } - } else { - return false; - } - } catch (e: any) { - if ( - e.message.indexOf('timed out') === -1 && - e.message.indexOf('account not found') === -1 - ) { - throw e; - } else { - return false; - } - } - return { balance, unconfirmed: 0, provider: 'ripple.com' }; - } - - async getTransactionsBlockchain(scanData: { - account: any; - additional?: any; - }) { - const address = scanData.account.address.trim(); - const action = 'payments'; - await AirDAOCryptoLog.log( - 'XrpScannerProcessor.DataRipple.getTransactions ' + - action + - ' started ' + - address - ); - const link = `${API_PATH}/accounts/${address}/payments`; - let res: any = false; - try { - res = await AirDAOAxios.getWithoutBraking(link); - } catch (e: any) { - if ( - e.message.indexOf('account not found') === -1 && - e.message.indexOf('to retrieve payments') === -1 && - e.message.indexOf('limit exceeded') === -1 && - e.message.indexOf('timed out') === -1 - ) { - throw e; - } else { - return false; - } - } - - if (!res || typeof res.data === 'undefined' || !res.data) { - return false; - } - if (typeof res.data[action] === 'undefined') { - throw new Error('Undefined txs ' + link + ' ' + JSON.stringify(res.data)); - } - if (typeof res.data[action] === 'string') { - throw new Error('Undefined txs ' + link + ' ' + res.data[action]); - } - - const transactions = await this._unifyTransactions( - address, - res.data[action], - action - ); - await AirDAOCryptoLog.log( - 'XrpScannerProcessor.DataRipple.getTransactions ' + - action + - ' finished ' + - address - ); - return transactions; - } - - async _unifyTransactions(address: string, result: any, action: string) { - const transactions = []; - let tx; - for (tx of result) { - const transaction = await this._unifyPayment(address, tx); - if (transaction) { - transactions.push(transaction); - } - } - return transactions; - } - - async _unifyPayment(address: string, transaction: any) { - let direction; - let amount; - - if (transaction.currency === 'XRP') { - if (transaction.source_currency === 'XRP') { - direction = address === transaction.source ? 'outcome' : 'income'; - } else if (transaction.destination === address) { - direction = 'income'; // USDT any => XRP my - } else { - // USDT my => XRP not my - return false; // do nothing - } - } else if (transaction.source_currency === 'XRP') { - if (transaction.source === address) { - direction = 'outcome'; // XRP my => USDT any - } else { - // XRP not my => USDT my - return false; // do nothing - } - } else { - return false; // USDT => USDT - } - - if (direction === 'income') { - amount = transaction.delivered_amount; - } else { - amount = transaction.amount; - } - - let transactionStatus = 'new'; - let ledger = false; - if ( - typeof transaction.ledger_index !== 'undefined' && - transaction.ledger_index > 0 - ) { - ledger = await this._getLedger(transaction.ledger_index); - if (ledger && ledger.transactionConfirmations > 5) { - transactionStatus = 'success'; - } - } - - if (typeof transaction.executed_time === 'undefined') { - transaction.executed_time = ''; - } - const tx = { - transactionHash: transaction.tx_hash, - blockHash: ledger ? ledger.ledger_hash : '', - blockNumber: transaction.ledger_index, - blockTime: transaction.executed_time, - blockConfirmations: ledger ? ledger.transactionConfirmations : 0, - transactionDirection: direction, - addressFrom: transaction.source === address ? '' : transaction.source, - addressTo: - transaction.destination === address ? '' : transaction.destination, - addressAmount: amount, - transactionStatus: transactionStatus, - transactionFee: transaction.transaction_cost - }; - if (typeof transaction.destination_tag !== undefined) { - tx.transactionJson = { memo: transaction.destination_tag }; - } - return tx; - } - - async _getLedger(index: string) { - const now = new Date().getTime(); - await AirDAOCryptoLog.log( - 'XrpScannerProcessor.DataRipple._getLedger started ' + index - ); - const link = `${API_PATH}/ledgers/${index}`; - let res: any = false; - if ( - typeof CACHE_BLOCK_DATA[index] === 'undefined' || - (now - CACHE_BLOCK_DATA[index].time > CACHE_VALID_TIME && - CACHE_BLOCK_DATA[index].data.transactionConfirmations < 100) - ) { - try { - res = await AirDAOAxios.getWithoutBraking(link); - if ( - res.data && - typeof res.data !== 'undefined' && - typeof res.data.ledger !== 'undefined' - ) { - await AirDAOCryptoLog.log( - 'XrpScannerProcessor.DataRipple._getLedger updated for index ' + - index + - ' ' + - JSON.stringify(res.data.ledger) - ); - const ledger = { - close_time: res.data.ledger.close_time, - ledger_hash: res.data.ledger.ledger_hash, - transactionConfirmations: Math.round( - (now - res.data.ledger.close_time * 1000) / (60 * 1000) - ) // minutes - }; - CACHE_BLOCK_DATA[index] = { - data: ledger, - time: now - }; - } - await XrpTmpDS.saveCache(CACHE_BLOCK_DATA); - } catch (e: any) { - if ( - e.message.indexOf('timed out') === -1 && - e.message.indexOf('account not found') === -1 - ) { - throw e; - } else { - res = false; - } - } - } - if (typeof CACHE_BLOCK_DATA[index] === 'undefined') { - return false; - } - return CACHE_BLOCK_DATA[index].data; - } -} diff --git a/crypto/blockchains/xrp/basic/XrpDataScanProvider.js b/crypto/blockchains/xrp/basic/XrpDataScanProvider.js deleted file mode 100644 index e9998c670..000000000 --- a/crypto/blockchains/xrp/basic/XrpDataScanProvider.js +++ /dev/null @@ -1,328 +0,0 @@ -/** - * @version 0.5 - * https://xrpl.org/request-formatting.html - */ -import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings'; -import AirDAOAxios from '@crypto/common/AirDAOAxios'; -import BlocksoftUtils from '@crypto/common/BlocksoftUtils'; -import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; -import XrpTmpDS from '@crypto/blockchains/xrp/stores/XrpTmpDS'; - -const CACHE_VALID_TIME = 60000; // 1 minute -let CACHE_BLOCK_DATA = {}; - -export default class XrpDataScanProvider { - setCache(tmp) { - CACHE_BLOCK_DATA = tmp; - } - - async getBalanceBlockchain(address) { - const link = BlocksoftExternalSettings.getStatic('XRP_SCANNER_SERVER'); - let res = false; - let balance = 0; - try { - /** - curl http://s1.ripple.com:51234/ -X POST -H "Content-Type: application/json" -d '{'method":"account_info","params":[{"account":"rEAgA9B8U8RCkwn6MprHqE1ZfXoeGQxz4P","strict":true,"ledger_index":"validated","api_version':1}]}' - curl https://xrplcluster.com/ -X POST -H "Content-Type: application/json" -d '{'method":"account_info","params":[{"account":"rEAgA9B8U8RCkwn6MprHqE1ZfXoeGQxz4P","strict":true,"ledger_index":"validated","api_version':1}]}' - */ - const data = { - method: 'account_info', - params: [ - { - account: address, - strict: true, - ledger_index: 'validated', - api_version: 1 - } - ] - }; - res = await AirDAOAxios.postWithoutBraking(link, data); - - if ( - res && - typeof res.data !== 'undefined' && - res.data && - typeof res.data.result !== 'undefined' && - res.data.result - ) { - if ( - typeof res.data.result.account !== 'undefined' && - typeof res.data.result.error_code !== 'undefined' && - res.data.result.error_code === 19 - ) { - balance = 0; - } else if ( - typeof res.data.result.account_data !== 'undefined' && - typeof res.data.result.account_data.Balance !== 'undefined' - ) { - balance = BlocksoftUtils.toUnified( - res.data.result.account_data.Balance, - 6 - ); - } - } else { - return false; - } - } catch (e) { - if ( - e.message.indexOf('timed out') === -1 && - e.message.indexOf('account not found') === -1 - ) { - if (typeof res.data !== 'undefined' && res.data) { - e.message += ' in ' + JSON.stringify(res.data); - } else { - e.message += ' empty data'; - } - throw e; - } else { - return false; - } - } - return { balance: balance, unconfirmed: 0, provider: link }; - } - - async getTransactionsBlockchain(scanData, source = '') { - const address = scanData.account.address.trim(); - const link = BlocksoftExternalSettings.getStatic('XRP_SCANNER_SERVER'); - let transactions = []; - let res = false; - try { - // https://xrpl.org/account_tx.html - const data = { - method: 'account_tx', - params: [ - { - account: address, - binary: false, - forward: false, - ledger_index_max: -1, - ledger_index_min: -1, - limit: 100 - } - ] - }; - res = await AirDAOAxios.postWithoutBraking(link, data); - - if ( - res && - typeof res.data !== 'undefined' && - res.data && - typeof res.data.result !== 'undefined' && - res.data.result && - typeof res.data.result.transactions !== 'undefined' && - res.data.result.transactions - ) { - transactions = await this._unifyTransactions( - address, - res.data.result.transactions, - res.data.result.ledger_index_max - ); - } else { - return false; - } - } catch (e) { - if ( - e.message.indexOf('timed out') === -1 && - e.message.indexOf('account not found') === -1 - ) { - throw e; - } else { - return false; - } - } - return transactions; - } - - async _unifyTransactions(address, result, lastBlock) { - const transactions = []; - let tx; - for (tx of result) { - const transaction = await this._unifyPayment(address, tx, lastBlock); - if (transaction) { - transactions.push(transaction); - } - } - return transactions; - } - - /** - * @param {string} address - * @param {Object} transaction - * @param {bool} transaction.validated - * @param {string} transaction.tx.Account 'rEAgA9B8U8RCkwn6MprHqE1ZfXoeGQxz4P' - * @param {string} transaction.tx.Amount '2000000' - * @param {string} transaction.tx.Destination 'rDh2XemJY5WSNCPgXjhqnJt1PLGsTKbnix' - * @param {string} transaction.tx.DestinationTag - * @param {string} transaction.tx.Fee 127091 - * @param {string} transaction.tx.LastLedgerSequence 68101269 - * @param {string} transaction.tx.TransactionType 'Payment' - * @param {string} transaction.tx.date 691857661 - * @param {string} transaction.tx.hash '4D08316F83148C7C0EC955301E770A196B708EAF874BA2339260317BFDCE89E6' - * @param {string} transaction.tx.inLedger 68101268 - * @param {string} transaction.tx.ledger_index 68101268 - * @param {string} transaction.meta.delivered_amount '2000000' - * @param {string} transaction.meta.TransactionResult 'tesSUCCESS' - * @return {UnifiedTransaction} - * @private - **/ - async _unifyPayment(address, transaction, lastBlock = 0) { - if (transaction.tx.TransactionType !== 'Payment') { - return false; - } - let direction, amount; - if (transaction.tx.Account === address) { - direction = 'outcome'; - } else { - direction = 'income'; - } - - amount = transaction.tx.Amount; - if ( - direction === 'income' && - typeof transaction.meta.delivered_amount !== 'undefined' - ) { - amount = transaction.meta.delivered_amount; - } - - let blockConfirmations = lastBlock - transaction.tx.ledger_index; - if (blockConfirmations <= 0) blockConfirmations = 0; - let transactionStatus = 'new'; - if ( - transaction.validated === true || - transaction.meta.TransactionResult === 'tesSUCCESS' - ) { - if (blockConfirmations > 5) { - transactionStatus = 'success'; - } - } - const ledger = await this._getLedger(transaction.tx.ledger_index); - const blockTime = - ledger && typeof ledger.close_time !== 'undefined' && ledger.close_time - ? ledger.close_time - : transaction.tx.date; - const blockHash = - ledger && typeof ledger.ledger_hash !== 'undefined' && ledger.ledger_hash - ? ledger.ledger_hash - : transaction.tx.ledger_index; - const tx = { - transactionHash: transaction.tx.hash, - blockHash, - blockNumber: transaction.tx.ledger_index, - blockTime, - blockConfirmations, - transactionDirection: direction, - addressFrom: - transaction.tx.Account === address ? '' : transaction.tx.Account, - addressTo: - transaction.tx.Destination === address - ? '' - : transaction.tx.Destination, - addressAmount: BlocksoftUtils.toUnified(amount, 6), - transactionStatus: transactionStatus, - transactionFee: BlocksoftUtils.toUnified(transaction.tx.Fee, 6) - }; - // https://blockchair.com/ripple/transaction/F56C6B0CA7BB6CD9AC74843E6C7BA605C7FFBB1F409E356CA235423F30F55F51?from=trustee - if (typeof transaction.tx.DestinationTag !== 'undefined') { - tx.transactionJson = { memo: transaction.tx.DestinationTag }; - } - return tx; - } - - async _getLedger(index) { - const now = new Date().getTime(); - await AirDAOCryptoLog.log( - 'XrpScannerProcessor.DataScan._getLedger started ' + index - ); - const link = BlocksoftExternalSettings.getStatic('XRP_SCANNER_SERVER'); - let res = false; - if ( - typeof CACHE_BLOCK_DATA[index] === 'undefined' || - (now - CACHE_BLOCK_DATA[index].time > CACHE_VALID_TIME && - CACHE_BLOCK_DATA[index].data.transactionConfirmations < 100) - ) { - try { - const data = { - method: 'ledger_data', - params: [ - { - binary: false, - ledger_index: index - } - ] - }; - res = await AirDAOAxios.postWithoutBraking(link, data); - if ( - res.data && - typeof res.data !== 'undefined' && - typeof res.data.result !== 'undefined' && - typeof res.data.result.ledger !== 'undefined' - ) { - await AirDAOCryptoLog.log( - 'XrpScannerProcessor.DataScan._getLedger updated for index ' + - index + - ' ' + - JSON.stringify(res.data.result.ledger) - ); - const date = this._getDate(res.data.result.ledger.close_time_human); - const ledger = { - close_time: date, - ledger_hash: res.data.result.ledger.ledger_hash, - transactionConfirmations: Math.round( - (now - date * 1000) / (60 * 1000) - ) // minutes - }; - CACHE_BLOCK_DATA[index] = { - data: ledger, - time: now - }; - } - await XrpTmpDS.saveCache(CACHE_BLOCK_DATA); - } catch (e) { - if ( - e.message.indexOf('timed out') === -1 && - e.message.indexOf('account not found') === -1 - ) { - throw e; - } else { - res = false; - } - } - } - if (typeof CACHE_BLOCK_DATA[index] === 'undefined') { - return false; - } - return CACHE_BLOCK_DATA[index].data; - } - - // 2021-Dec-03 14:41:01.00 - // const tmp = new Date(time) not working in emulator - _getDate(time) { - time = time.split('.')[0]; - const months = { - Jan: 0, - Feb: 1, - Mar: 2, - Apr: 3, - May: 4, - Jun: 5, - Jul: 6, - Aug: 7, - Sep: 8, - Oct: 9, - Nov: 10, - Dec: 11 - }; - const tmp0 = time.split(' '); - const tmp1 = tmp0[0].split('-'); - const tmp2 = tmp0[1].split(':'); - const tmp = new Date( - tmp1[0], - months[tmp1[1]], - tmp1[2], - tmp2[0], - tmp2[1], - tmp2[2] - ); - return tmp.getTime(); - } -} diff --git a/crypto/blockchains/xrp/basic/XrpDataScanProvider.ts b/crypto/blockchains/xrp/basic/XrpDataScanProvider.ts deleted file mode 100644 index ad4a66076..000000000 --- a/crypto/blockchains/xrp/basic/XrpDataScanProvider.ts +++ /dev/null @@ -1,359 +0,0 @@ -import BlocksoftExternalSettings from '@crypto/common/AirDAOExternalSettings'; -import AirDAOAxios from '@crypto/common/AirDAOAxios'; -import BlocksoftUtils from '@crypto/common/AirDAOUtils'; -import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; -import XrpTmpDS from '@crypto/blockchains/xrp/stores/XrpTmpDS'; -import { AxiosResponse } from 'axios'; - -const CACHE_VALID_TIME = 60000; -let CACHE_BLOCK_DATA: { [index: string]: { data: any; time: number } } = {}; - -class UnifiedTransaction { - // TODO -} - -export default class XrpDataScanProvider { - setCache(tmp: { [key: string]: any }) { - CACHE_BLOCK_DATA = tmp; - } - - async getBalanceBlockchain(address: string) { - const link = BlocksoftExternalSettings.getStatic('XRP_SCANNER_SERVER'); - const res = false; - let balance = 0; - try { - /** - * curl http://s1.ripple.com:51234/ -X POST -H "Content-Type: application/json" -d '{'method":"account_info","params":[{"account":"rEAgA9B8U8RCkwn6MprHqE1ZfXoeGQxz4P","strict":true,"ledger_index":"validated","api_version':1}]}' - * curl https://xrplcluster.com/ -X POST -H "Content-Type: application/json" -d '{'method":"account_info","params":[{"account":"rEAgA9B8U8RCkwn6MprHqE1ZfXoeGQxz4P","strict":true,"ledger_index":"validated","api_version':1}]}' - */ - const data = { - method: 'account_info', - params: [ - { - account: address, - strict: true, - ledger_index: 'validated', - api_version: 1 - } - ] - }; - - // @ts-ignore - // tslint:disable-next-line:no-shadowed-variable - const res: AxiosResponse = await AirDAOAxios.postWithoutBraking( - link, - data - ); - - if ( - res && - typeof res.data !== undefined && - res.data && - typeof res.data.result !== undefined && - res.data.result - ) { - if ( - typeof res.data.result.account !== undefined && - typeof res.data.result.error_code !== undefined && - res.data.result.error_code === 19 - ) { - balance = 0; - } else if ( - typeof res.data.result.account_data !== undefined && - typeof res.data.result.account_data.Balance !== undefined - ) { - balance = BlocksoftUtils.toUnified( - res.data.result.account_data.Balance, - 6 - ); - } - } else { - return false; - } - } catch (e: any) { - if ( - e.message.indexOf('timed out') === -1 && - e.message.indexOf('account not found') === -1 - ) { - if (typeof res.data !== undefined && res.data) { - e.message += ' in ' + JSON.stringify(res.data); - } else { - e.message += ' empty data'; - } - throw e; - } else { - return false; - } - } - return { balance, unconfirmed: 0, provider: link }; - } - - async getTransactionsBlockchain(scanData: { - account: any; - additional?: any; - }) { - const address = scanData.account.address.trim(); - const link = BlocksoftExternalSettings.getStatic('XRP_SCANNER_SERVER'); - let transactions = []; - try { - // https://xrpl.org/account_tx.html - const data = { - method: 'account_tx', - params: [ - { - account: address, - binary: false, - forward: false, - ledger_index_max: -1, - ledger_index_min: -1, - limit: 100 - } - ] - }; - - // @ts-ignore - // tslint:disable-next-line:no-shadowed-variable - const res: AxiosResponse = await AirDAOAxios.postWithoutBraking( - link, - data - ); - - if ( - res && - typeof res.data !== 'undefined' && - res.data && - typeof res.data.result !== 'undefined' && - res.data.result && - typeof res.data.result.transactions !== 'undefined' && - res.data.result.transactions - ) { - transactions = await this._unifyTransactions( - address, - res.data.result.transactions, - res.data.result.ledger_index_max - ); - } else { - return false; - } - } catch (e: any) { - if ( - e.message.indexOf('timed out') === -1 && - e.message.indexOf('account not found') === -1 - ) { - throw e; - } else { - return false; - } - } - return transactions; - } - - async _unifyTransactions( - address: any, - result: any, - lastBlock: number | undefined - ) { - const transactions = []; - let tx; - for (tx of result) { - const transaction = await this._unifyPayment(address, tx, lastBlock); - if (transaction) { - transactions.push(transaction); - } - } - return transactions; - } - - /** - * @param {string} address - * @param {Object} transaction - * @param lastBlock - * @param {bool} transaction.validated - * @param {string} transaction.tx.Account 'rEAgA9B8U8RCkwn6MprHqE1ZfXoeGQxz4P' - * @param {string} transaction.tx.Amount '2000000' - * @param {string} transaction.tx.Destination 'rDh2XemJY5WSNCPgXjhqnJt1PLGsTKbnix' - * @param {string} transaction.tx.DestinationTag - * @param {string} transaction.tx.Fee 127091 - * @param {string} transaction.tx.LastLedgerSequence 68101269 - * @param {string} transaction.tx.TransactionType 'Payment' - * @param {string} transaction.tx.date 691857661 - * @param {string} transaction.tx.hash '4D08316F83148C7C0EC955301E770A196B708EAF874BA2339260317BFDCE89E6' - * @param {string} transaction.tx.inLedger 68101268 - * @param {string} transaction.tx.ledger_index 68101268 - * @param {string} transaction.meta.delivered_amount '2000000' - * @param {string} transaction.meta.TransactionResult 'tesSUCCESS' - * @return {UnifiedTransaction} - * @private - */ - async _unifyPayment( - address: any, - transaction: { - tx: { - TransactionType: string; - Account: any; - Amount: any; - ledger_index: number; - date: any; - hash: any; - Destination: any; - Fee: any; - DestinationTag: any; - }; - meta: { delivered_amount: any; TransactionResult: string }; - validated: boolean; - }, - lastBlock = 0 - ): Promise { - if (transaction.tx.TransactionType !== 'Payment') { - return false; - } - let direction: string; - let amount: any; - if (transaction.tx.Account === address) { - direction = 'outcome'; - } else { - direction = 'income'; - } - - amount = transaction.tx.Amount; - if ( - direction === 'income' && - typeof transaction.meta.delivered_amount !== 'undefined' - ) { - amount = transaction.meta.delivered_amount; - } - - let blockConfirmations = lastBlock - transaction.tx.ledger_index; - if (blockConfirmations <= 0) blockConfirmations = 0; - let transactionStatus = 'new'; - if ( - transaction.validated || - transaction.meta.TransactionResult === 'tesSUCCESS' - ) { - if (blockConfirmations > 5) { - transactionStatus = 'success'; - } - } - const ledger = await this._getLedger(transaction.tx.ledger_index); - const blockTime = (ledger && ledger.close_time) || transaction.tx.date; - const blockHash = - (ledger && ledger.ledger_hash) || transaction.tx.ledger_index; - const tx: UnifiedTransaction = { - transactionHash: transaction.tx.hash, - blockHash, - blockNumber: transaction.tx.ledger_index, - blockTime, - blockConfirmations, - transactionDirection: direction, - addressFrom: - transaction.tx.Account === address ? '' : transaction.tx.Account, - addressTo: - transaction.tx.Destination === address - ? '' - : transaction.tx.Destination, - addressAmount: BlocksoftUtils.toUnified(amount, 6), - transactionStatus, - transactionFee: BlocksoftUtils.toUnified(transaction.tx.Fee, 6) - }; - if (typeof transaction.tx.DestinationTag !== 'undefined') { - tx.transactionJson = { memo: transaction.tx.DestinationTag }; - } - return tx; - } - - async _getLedger(index: string | number): Promise { - const now = new Date().getTime(); - await AirDAOCryptoLog.log( - 'XrpScannerProcessor.DataScan._getLedger started ' + index - ); - const link = BlocksoftExternalSettings.getStatic('XRP_SCANNER_SERVER'); - let res: any = false; - if ( - typeof CACHE_BLOCK_DATA[index] === 'undefined' || - (now - CACHE_BLOCK_DATA[index].time > CACHE_VALID_TIME && - CACHE_BLOCK_DATA[index].data.transactionConfirmations < 100) - ) { - try { - const data = { - method: 'ledger_data', - params: [ - { - binary: false, - ledger_index: index - } - ] - }; - res = await AirDAOAxios.postWithoutBraking(link, data); - if ( - res.data && - typeof res.data !== 'undefined' && - typeof res.data.result !== 'undefined' && - typeof res.data.result.ledger !== 'undefined' - ) { - await AirDAOCryptoLog.log( - 'XrpScannerProcessor.DataScan._getLedger updated for index ' + - index + - ' ' + - JSON.stringify(res.data.result.ledger) - ); - const date = this._getDate(res.data.result.ledger.close_time_human); - const ledger = { - close_time: date, - ledger_hash: res.data.result.ledger.ledger_hash, - transactionConfirmations: Math.round( - (now - date * 1000) / (60 * 1000) - ) // minutes - }; - CACHE_BLOCK_DATA[index] = { - data: ledger, - time: now - }; - await XrpTmpDS.saveCache(CACHE_BLOCK_DATA); - } - } catch (e: any) { - if ( - e.message.indexOf('timed out') === -1 && - e.message.indexOf('account not found') === -1 - ) { - throw e; - } else { - res = false; - } - } - } - if (typeof CACHE_BLOCK_DATA[index] === 'undefined') { - return false; - } - return CACHE_BLOCK_DATA[index].data; - } - - _getDate(time: string): number { - time = time.split('.')[0]; - const months: { [key: string]: number } = { - Jan: 0, - Feb: 1, - Mar: 2, - Apr: 3, - May: 4, - Jun: 5, - Jul: 6, - Aug: 7, - Sep: 8, - Oct: 9, - Nov: 10, - Dec: 11 - }; - const tmp0 = time.split(' '); - const tmp1 = tmp0[0].split('-'); - const tmp2 = tmp0[1].split(':'); - const tmp = new Date( - parseInt(tmp1[0], 2), - months[tmp1[1]], - parseInt(tmp1[2], 2), - parseInt(tmp2[0], 2), - parseInt(tmp2[1], 2), - parseInt(tmp2[2], 2) - ); - return tmp.getTime(); - } -} diff --git a/crypto/blockchains/xrp/basic/XrpTxSendProvider.ts b/crypto/blockchains/xrp/basic/XrpTxSendProvider.ts deleted file mode 100644 index deaeca146..000000000 --- a/crypto/blockchains/xrp/basic/XrpTxSendProvider.ts +++ /dev/null @@ -1,263 +0,0 @@ -/** - * @version 0.20 - * https://gist.github.com/WietseWind/19df307c3c68748543971242284ade4d - * - * https://xrpl.org/rippleapi-reference.html#preparepayment - * https://xrpl.org/rippleapi-reference.html#sign - * https://xrpl.org/rippleapi-reference.html#submit - */ -import AirDAOCryptoLog from '../../../common/AirDAOCryptoLog'; -import BlocksoftExternalSettings from '../../../common/AirDAOExternalSettings'; -import { XrpTxUtils } from './XrpTxUtils'; -// @ts-ignore -import MarketingEvent from '../../../../app/services/Marketing/MarketingEvent'; // TODO import -import config from '@constants/config'; -import { AirDAOBlockchainTypes } from '@crypto/blockchains/AirDAOBlockchainTypes'; - -const RippleAPI = require('ripple-lib').RippleAPI; - -export class XrpTxSendProvider { - private readonly _api: typeof RippleAPI; - - constructor() { - this._api = new RippleAPI({ - server: BlocksoftExternalSettings.getStatic('XRP_SERVER') - }); // Public rippled server - this._api.on('error', (errorCode: string, errorMessage: string) => { - AirDAOCryptoLog.log( - 'XrpTransferProcessor constructor' + errorCode + ': ' + errorMessage - ); - }); - this._api.on('connected', () => { - AirDAOCryptoLog.log('connected'); - }); - this._api.on('disconnected', () => { - AirDAOCryptoLog.log('disconnected'); - }); - } - - async getPrepared(data: AirDAOBlockchainTypes.TransferData, toObject = true) { - const payment = { - source: { - address: data.addressFrom, - maxAmount: { - value: XrpTxUtils.amountPrep(data.amount), - currency: 'XRP' - } - }, - destination: { - address: data.addressTo, - amount: { - value: XrpTxUtils.amountPrep(data.amount), - currency: 'XRP' - } - } - }; - - if (data.addressFrom === data.addressTo) { - throw new Error('SERVER_RESPONSE_SELF_TX_FORBIDDEN'); - } - - // https://xrpl.org/rippleapi-reference.html#payment - try { - if ( - typeof data.memo !== 'undefined' && - data.memo && - data.memo.toString().trim().length > 0 - ) { - // @ts-ignore - const int = data.memo.toString().trim() * 1; - if (int.toString() !== data.memo) { - throw new Error('Destination tag type validation error'); - } - if (int > 4294967295) { - throw new Error('Destination tag couldnt be more then 4294967295'); - } - // @ts-ignore - payment.destination.tag = int; - } - } catch (e: any) { - // @ts-ignore - AirDAOCryptoLog.log( - `XrpTransferProcessor._getPrepared memo error + ${e.message}, - ${data}` - ); - } - // @ts-ignore - AirDAOCryptoLog.log( - `XrpTransferProcessor._getPrepared payment, - ${payment}` - ); - - const api = this._api; - - return new Promise((resolve, reject) => { - api - .connect() - .then(() => { - api - .preparePayment(data.addressFrom, payment) - .then((prepared: { txJSON: any }) => { - // https://xrpl.org/rippleapi-reference.html#preparepayment - if (typeof prepared.txJSON === 'undefined') { - reject( - new Error( - 'No txJSON inside ripple response ' + - JSON.stringify(prepared) - ) - ); - } - const txJson = prepared.txJSON; - AirDAOCryptoLog.log( - 'XrpTxSendProvider._getPrepared prepared', - txJson - ); - resolve(toObject ? JSON.parse(txJson) : txJson); - }) - .catch((error: { toString: () => string }) => { - MarketingEvent.logOnlyRealTime( - 'v20_rippled_prepare_error ' + - data.addressFrom + - ' => ' + - data.addressTo, - { - payment, - msg: error.toString() - } - ); - AirDAOCryptoLog.log( - 'XrpTxSendProvider._getPrepared error ' + error.toString() - ); - reject(error); - }); - }) - .catch((error: { toString: () => string }) => { - MarketingEvent.logOnlyRealTime( - 'v20_rippled_prepare_no_connection ' + - data.addressFrom + - ' => ' + - data.addressTo, - { - payment, - msg: error.toString() - } - ); - AirDAOCryptoLog.log( - 'XrpTxSendProvider._getPrepared connect error ' + error.toString() - ); - reject(error); - }); - }); - } - - signTx( - data: AirDAOBlockchainTypes.TransferData, - privateData: AirDAOBlockchainTypes.TransferPrivateData, - txJson: any - ): Promise { - const api = this._api; - const keypair = { - privateKey: privateData.privateKey, - publicKey: data.accountJson.publicKey.toUpperCase() - }; - const signed = api.sign(txJson, keypair); - return signed.signedTransaction; - } - - async sendTx( - data: AirDAOBlockchainTypes.TransferData, - privateData: AirDAOBlockchainTypes.TransferPrivateData, - txJson: any - ): Promise<{ - resultCode: string; - resultMessage: string; - // eslint-disable-next-line camelcase - tx_json?: { - hash: string; - }; - }> { - const api = this._api; - let result; - try { - const signed = this.signTx(data, privateData, txJson); - // @ts-ignore - AirDAOCryptoLog.log('XrpTransferProcessor.sendTx signed', signed); - result = await new Promise((resolve, reject) => { - api - .connect() - .then(() => { - // https://xrpl.org/rippleapi-reference.html#submit - api - .submit(signed) - // tslint:disable-next-line:no-shadowed-variable - .then((result: { resultCode: ''; resultMessage: '' }) => { - MarketingEvent.logOnlyRealTime( - 'v20_rippled_success ' + - data.addressFrom + - ' => ' + - data.addressTo, - { - txJson, - result - } - ); - resolve(result); - }) - .catch((error: { toString: () => string }) => { - MarketingEvent.logOnlyRealTime( - 'v20_rippled_send_error ' + - data.addressFrom + - ' => ' + - data.addressTo, - { - txJson, - msg: error.toString() - } - ); - AirDAOCryptoLog.log( - 'XrpTransferProcessor.submit error ' + error.toString() - ); - reject(error); - }); - }) - .catch((error: { toString: () => string }) => { - MarketingEvent.logOnlyRealTime( - 'v20_rippled_send_no_connection ' + - data.addressFrom + - ' => ' + - data.addressTo, - { - txJson, - msg: error.toString() - } - ); - AirDAOCryptoLog.log( - 'XrpTransferProcessor.sendTx connect error ' + error.toString() - ); - reject(error); - }); - }); - } catch (e: any) { - if (config.debug.cryptoErrors) { - console.log('XrpTransferProcessor.sendTx error ', e); - } - MarketingEvent.logOnlyRealTime( - 'v20_rippled_send2_error ' + data.addressFrom + ' => ' + data.addressTo, - { - txJson, - msg: e.toString() - } - ); - AirDAOCryptoLog.log('XrpTransferProcessor.send2 error ' + e.toString()); - if (typeof e.resultMessage !== 'undefined') { - throw new Error(e.resultMessage.toString()); - } else if (typeof e.message !== 'undefined') { - throw new Error(e.message.toString()); - } else { - throw new Error(e.toString()); - } - } - // @ts-ignore - return result; - } -} diff --git a/crypto/blockchains/xrp/basic/XrpTxUtils.ts b/crypto/blockchains/xrp/basic/XrpTxUtils.ts deleted file mode 100644 index d93b76109..000000000 --- a/crypto/blockchains/xrp/basic/XrpTxUtils.ts +++ /dev/null @@ -1,9 +0,0 @@ -export namespace XrpTxUtils { - export const amountPrep = function (current: string): string { - const tmp = current.toString().split('.'); - if (typeof tmp[1] !== 'undefined' && tmp[1].length > 6) { - current = tmp[0] + '.' + tmp[1].substr(0, 6); - } - return current.toString(); - }; -} diff --git a/crypto/blockchains/xrp/stores/XrpTmpDS.ts b/crypto/blockchains/xrp/stores/XrpTmpDS.ts deleted file mode 100644 index 151306792..000000000 --- a/crypto/blockchains/xrp/stores/XrpTmpDS.ts +++ /dev/null @@ -1,79 +0,0 @@ -// @ts-ignore -import { DatabaseTable } from '@appTypes'; -import { Database, TransactionScannersTmpDBModel } from '@database'; - -const tableName = DatabaseTable.TransactionScannersTmp; - -class XrpTmpDS { - /** - * @type {string} - * @private - */ - _currencyCode = 'XRP'; - - _valKey = 'ledg'; - - _isSaved = false; - - async getCache() { - const res = (await Database.unsafeRawQuery( - tableName, - ` - SELECT id, tmp_key, tmp_val - FROM ${tableName} - WHERE currency_code='${this._currencyCode}' - AND tmp_key='${this._valKey}' - ` - )) as TransactionScannersTmpDBModel[]; - let tmp = {}; - const idsToRemove = []; - if (res) { - let row; - let found = false; - for (row of res) { - if (found) { - idsToRemove.push(row.id); - } else { - try { - tmp = JSON.parse(Database.unEscapeString(row.tmp_val)); - this._isSaved = true; - found = true; - } catch (e) { - idsToRemove.push(row.id); - } - } - } - if (idsToRemove.length > 0) { - await Database.unsafeRawQuery( - tableName, - `DELETE FROM ${tableName} WHERE id IN (${idsToRemove.join(',')})` - ); - } - } - return tmp; - } - - async saveCache(value: {}) { - const tmp = Database.escapeString(JSON.stringify(value)); - const now = new Date().toISOString(); - if (this._isSaved) { - await Database.unsafeRawQuery( - tableName, - `UPDATE ${tableName} SET tmp_val='${tmp}' WHERE tmp_key='${this._valKey}' AND currency_code='${this._currencyCode}'` - ); - } else { - const prepared = [ - { - currency_code: this._currencyCode, - tmp_key: this._valKey, - tmp_val: tmp, - created_at: now - } - ]; - Database.createModel(tableName, prepared); - this._isSaved = true; - } - } -} - -export default new XrpTmpDS(); diff --git a/crypto/blockchains/xvg/XvgScannerProcessor.js b/crypto/blockchains/xvg/XvgScannerProcessor.js deleted file mode 100644 index 0f85e6f58..000000000 --- a/crypto/blockchains/xvg/XvgScannerProcessor.js +++ /dev/null @@ -1,284 +0,0 @@ -/** - * @version 0.5 - * https://github.com/bitpay/bitcore/blob/master/packages/bitcore-node/docs/api-documentation.md - * https://api.vergecurrency.network/node/api/XVG/mainnet/address/DL5LtSf7wztH45VuYunL8oaQHtJbKLCHyw/balance - */ -import AirDAOAxios from '../../common/AirDAOAxios'; -import AirDAOCryptoLog from '../../common/AirDAOCryptoLog'; - -import XvgTmpDS from './stores/XvgTmpDS'; -import XvgFindAddressFunction from './basic/XvgFindAddressFunction'; - -const API_PATH = 'https://api.vergecurrency.network/node/api/XVG/mainnet'; -const CACHE_VALID_TIME = 30000; // 30 seconds -const CACHE = {}; -let CACHE_FROM_DB = {}; - -export default class XvgScannerProcessor { - /** - * @type {number} - * @private - */ - _blocksToConfirm = 20; - - /** - * @param {string} address - * @return {Promise<{balance:*, unconfirmed:*, provider:string}>} - */ - async getBalanceBlockchain(address) { - const link = `${API_PATH}/address/${address}/balance`; - const res = await AirDAOAxios.getWithoutBraking(link); - if (!res || !res.data) { - return false; - } - if (typeof res.data.confirmed === 'undefined') { - throw new Error( - 'XvgScannerProcessor.getBalance nothing loaded for address ' + link - ); - } - const balance = res.data.confirmed; - return { balance, unconfirmed: 0, provider: 'api.vergecurrency' }; - } - - /** - * @param {string} scanData.account.address - * @param {*} scanData.additional - * @param {string} scanData.account.walletHash - * @return {Promise} - */ - async getTransactionsBlockchain(scanData, source = '') { - const address = scanData.account.address.trim(); - AirDAOCryptoLog.log( - 'XvgScannerProcessor.getTransactions started ' + address - ); - const link = `${API_PATH}/address/${address}/txs`; - AirDAOCryptoLog.log('XvgScannerProcessor.getTransactions call ' + link); - let tmp = await AirDAOAxios.get(link); - if (tmp.status < 200 || tmp.status >= 300) { - throw new Error('not valid server response status ' + link); - } - - if (typeof tmp.data === 'undefined' || !tmp.data) { - throw new Error('Undefined txs ' + link + ' ' + JSON.stringify(tmp.data)); - } - - tmp = tmp.data; - if (tmp.data) { - tmp = tmp.data; // wtf but ok to support old wallets - } - - const transactions = []; - const already = {}; - CACHE_FROM_DB = await XvgTmpDS.getCache(address); - - let tx; - for (tx of tmp) { - // ASC order is important - const tmp2 = await this._unifyTransactionStep1(address, tx, already); - if (tmp2) { - if (tmp2.outcoming) { - if ( - typeof CACHE_FROM_DB[tmp2.outcoming.transactionHash + '_data'] === - 'undefined' - ) { - tmp2.outcoming = await this._unifyTransactionStep2( - address, - tmp2.outcoming - ); - if (tmp2.outcoming) { - already[tmp2.outcoming.transactionHash] = 1; - if (tmp2.outcoming.addressTo === '?') { - tmp2.outcoming.addressTo = 'self'; - AirDAOCryptoLog.log( - 'XvgScannerProcessor.getTransactions consider as self ' + - tmp2.outcoming.transactionHash - ); - } - transactions.push(tmp2.outcoming); - } - } else { - already[tmp2.outcoming.transactionHash] = 1; - } - } - if (tmp2.incoming) { - if ( - typeof CACHE_FROM_DB[tmp2.incoming.transactionHash + '_data'] === - 'undefined' - ) { - tmp2.incoming = await this._unifyTransactionStep2( - address, - tmp2.incoming - ); - if (tmp2.incoming) { - already[tmp2.incoming.transactionHash] = 1; - transactions.push(tmp2.incoming); - } - } else { - already[tmp2.incoming.transactionHash] = 1; - } - } - } - } - AirDAOCryptoLog.log( - 'XvgScannerProcessor.getTransactions finished ' + - address + - ' total: ' + - transactions.length - ); - return transactions; - } - - /** - * https://api.vergecurrency.network/node/api/XVG/mainnet/tx/abcda88bdb3968c5e444694ce3914cdec34f3afab73627bf201d34493d5e3aae/coins - * @param address - * @param transaction - * @returns {Promise} - * @private - */ - async _unifyTransactionStep2(address, transaction) { - if (!transaction) return false; - - if (typeof CACHE[transaction.transactionHash] !== 'undefined') { - if (CACHE[transaction.transactionHash].data.blockConfirmations > 100) { - return CACHE[transaction.transactionHash].data; - } - const now = new Date().getTime(); - if (now - CACHE[transaction.transactionHash].time < CACHE_VALID_TIME) { - return CACHE[transaction.transactionHash].data; - } - } - - let tmp; - - const link = `${API_PATH}/tx/${transaction.transactionHash}/coins`; - AirDAOCryptoLog.log( - 'XvgScannerProcessor._unifyTransactionStep2 call for outputs should be ' + - link - ); - - if ( - typeof CACHE_FROM_DB[transaction.transactionHash + '_coins'] !== - 'undefined' - ) { - tmp = CACHE_FROM_DB[transaction.transactionHash + '_coins']; - } else { - AirDAOCryptoLog.log( - 'XvgScannerProcessor._unifyTransactionStep2 called ' + link - ); - tmp = await AirDAOAxios.get(link); - tmp = tmp.data; - // noinspection ES6MissingAwait - XvgTmpDS.saveCache(address, transaction.transactionHash, 'coins', tmp); - CACHE_FROM_DB[transaction.transactionHash + '_coins'] = tmp; - } - - let output; - try { - output = await XvgFindAddressFunction(address, tmp); - } catch (e) { - e.message += ' while XvgFindAddressFunction'; - throw e; - } - - transaction.transactionDirection = output.direction; - transaction.addressFrom = output.from; - transaction.addressTo = output.to; - transaction.addressAmount = output.value; - - const link2 = `${API_PATH}/tx/${transaction.transactionHash}`; - AirDAOCryptoLog.log( - 'XvgScannerProcessor._unifyTransactionStep2 call for details ' + link2 - ); - let tmp2 = await AirDAOAxios.get(link2); - tmp2 = tmp2.data; - transaction.blockHash = tmp2.blockHash; - transaction.blockTime = tmp2.blockTimeNormalized; - transaction.blockConfirmations = tmp2.confirmations * 1; - if (transaction.blockConfirmations < 0) - transaction.blockConfirmations = transaction.blockConfirmations * -1; - - transaction.transaction_fee = tmp2.fee; - transaction.transactionStatus = 'new'; - if (transaction.blockConfirmations > this._blocksToConfirm) { - transaction.transactionStatus = 'success'; - } else if (transaction.blockConfirmations > 0) { - transaction.transactionStatus = 'confirming'; - } - if (transaction.transactionStatus === 'success') { - // noinspection ES6MissingAwait - XvgTmpDS.saveCache(address, transaction.transactionHash, 'data', tmp2); - CACHE_FROM_DB[transaction.transactionHash + '_data'] = 1; // no need all - just mark - } - AirDAOCryptoLog.log( - 'XvgScannerProcessor._unifyTransactionStep2 call for details result ', - transaction - ); - CACHE[transaction.transactionHash] = {}; - CACHE[transaction.transactionHash].time = new Date().getTime(); - CACHE[transaction.transactionHash].data = transaction; - return transaction; - } - - /** - * - * @param {string} address - * @param {Object} transaction - * @param {string} transaction._id 5dcedb83746f4c73710ff5ce - * @param {string} transaction.chain XVG - * @param {string} transaction.network mainnet - * @param {string} transaction.coinbase false - * @param {string} transaction.mintIndex 0 - * @param {string} transaction.spentTxid - * @param {string} transaction.mintTxid abcda88bdb3968c5e444694ce3914cdec34f3afab73627bf201d34493d5e3aae - * @param {string} transaction.mintHeight 3600363 - * @param {string} transaction.spentHeight - * @param {string} transaction.address DL5LtSf7wztH45VuYunL8oaQHtJbKLCHyw - * @param {string} transaction.script 76a914a3d43334ff9ea4c257a1796b63e4fa8330747d2e88ac - * @param {string} transaction.value 95000000 - * @param {string} transaction.confirmations - * @param {*} already - * @return {UnifiedTransaction} - * @private - */ - async _unifyTransactionStep1(address, transaction, already) { - if (transaction.chain !== 'XVG' || transaction.network !== 'mainnet') - return false; - const res = { incoming: false, outcoming: false }; - if ( - transaction.spentTxid && - typeof already[transaction.spentTxid] === 'undefined' - ) { - res.outcoming = { - transactionHash: transaction.spentTxid, - blockHash: '?', - blockNumber: +transaction.spentHeight, - blockTime: '?', - blockConfirmations: '?', - transactionDirection: 'outcome', - addressFrom: transaction.address, - addressTo: '?', - addressAmount: '0', - transactionStatus: '?' - }; - } - if ( - transaction.mintTxid && - transaction.mintTxid !== transaction.spentTxid && - typeof already[transaction.mintTxid] === 'undefined' - ) { - res.incoming = { - transactionHash: transaction.mintTxid, - blockHash: '?', - blockNumber: +transaction.mintHeight, - blockTime: '?', - blockConfirmations: '?', - transactionDirection: 'income', - addressFrom: '?', - addressTo: transaction.address, - addressAmount: '0', // transaction.value - transactionStatus: '?' - }; - } - return res; - } -} diff --git a/crypto/blockchains/xvg/XvgTransferProcessor.ts b/crypto/blockchains/xvg/XvgTransferProcessor.ts deleted file mode 100644 index 88d50ddce..000000000 --- a/crypto/blockchains/xvg/XvgTransferProcessor.ts +++ /dev/null @@ -1,41 +0,0 @@ -/** - * @version 0.20 - */ -import { BlocksoftBlockchainTypes } from '../BlocksoftBlockchainTypes' - -import DogeTransferProcessor from '../doge/DogeTransferProcessor' -import XvgUnspentsProvider from './providers/XvgUnspentsProvider' -import XvgSendProvider from './providers/XvgSendProvider' -import DogeTxInputsOutputs from '../doge/tx/DogeTxInputsOutputs' -import DogeTxBuilder from '../doge/tx/DogeTxBuilder' - -export default class XvgTransferProcessor extends DogeTransferProcessor implements BlocksoftBlockchainTypes.TransferProcessor { - - - _trezorServerCode = 'NONE' - - _builderSettings: BlocksoftBlockchainTypes.BuilderSettings = { - minOutputDustReadable: 0.000005, - minChangeDustReadable: 0.00001, - feeMaxForByteSatoshi: 100000000, // for tx builder - feeMaxAutoReadable2: 0.2, // for fee calc, - feeMaxAutoReadable6: 0.1, // for fee calc - feeMaxAutoReadable12: 0.05, // for fee calc - changeTogether: true, - minRbfStepSatoshi: 10, - minSpeedUpMulti : 1.5 - } - - canRBF(data: BlocksoftBlockchainTypes.DbAccount, transaction: BlocksoftBlockchainTypes.DbTransaction): boolean { - return false - } - - _initProviders() { - if (this._initedProviders) return false - this.unspentsProvider = new XvgUnspentsProvider(this._settings, this._trezorServerCode) - this.sendProvider = new XvgSendProvider(this._settings, this._trezorServerCode) - this.txPrepareInputsOutputs = new DogeTxInputsOutputs(this._settings, this._builderSettings) - this.txBuilder = new DogeTxBuilder(this._settings, this._builderSettings) - this._initedProviders = true - } -} diff --git a/crypto/blockchains/xvg/basic/XvgFindAddressFunction.js b/crypto/blockchains/xvg/basic/XvgFindAddressFunction.js deleted file mode 100644 index 80ed310cd..000000000 --- a/crypto/blockchains/xvg/basic/XvgFindAddressFunction.js +++ /dev/null @@ -1,100 +0,0 @@ -/** - * @version 0.5 - * https://api.vergecurrency.network/node/api/XVG/mainnet/tx/abcda88bdb3968c5e444694ce3914cdec34f3afab73627bf201d34493d5e3aae/coins - * @param {string} address - * @param {string} tmp.inputs[].address - * @param {string} tmp.inputs[].value - * @param {string} tmp.outputs[].address - * @param {string} tmp.outputs[].value - * @returns {Promise<{from: string, to: string, value: number, direction: string}>} - * @constructor - */ -import BlocksoftUtils from '../../../common/BlocksoftUtils'; -import BlocksoftBN from '../../../common/BlocksoftBN'; - -export default async function XvgFindAddressFunction(address, tmp) { - const inputMyBN = new BlocksoftBN(0); - const inputOthersBN = new BlocksoftBN(0); - const inputOthersAddresses = []; - const uniqueTmp = {}; - - let input; - for (input of tmp.inputs) { - if (input.address) { - const vinAddress = input.address; - if (vinAddress === address) { - inputMyBN.add(input.value); - } else { - if (typeof uniqueTmp[vinAddress] === 'undefined') { - uniqueTmp[vinAddress] = 1; - inputOthersAddresses.push(vinAddress); - } - inputOthersBN.add(input.va); - } - } - } - - const outputMyBN = new BlocksoftBN(0); - const outputOthersBN = new BlocksoftBN(0); - const outputOthersAddresses = []; - const uniqueTmp2 = {}; - - let output; - for (output of tmp.outputs) { - if (output.address) { - const voutAddress = output.address; - if (output.address === address) { - outputMyBN.add(output.value); - } else { - if (typeof uniqueTmp2[voutAddress] === 'undefined') { - uniqueTmp2[voutAddress] = 1; - outputOthersAddresses.push(voutAddress); - } - outputOthersBN.add(output.value); - } - } - } - - if (inputMyBN.get() === '0') { - // my only in output - output = { - direction: 'income', - from: - inputOthersAddresses.length > 0 ? inputOthersAddresses.join(',') : '', - to: '', // address, - value: outputMyBN.get() - }; - } else if (outputMyBN.get() === '0') { - // my only in input - output = { - direction: 'outcome', - from: '', // address, - to: - outputOthersAddresses.length > 0 ? outputOthersAddresses.join(',') : '', - value: - inputOthersBN.get() === '0' ? outputOthersBN.get() : inputMyBN.get() - }; - } else { - // both input and output - if (outputOthersAddresses.length > 0) { - // there are other address - output = { - direction: 'outcome', - from: '', // address, - to: outputOthersAddresses.join(','), - value: outputOthersBN.get() - }; - } else { - output = { - direction: 'self', - from: '', // address, - to: '', // address, - value: inputMyBN.diff(outputMyBN).get() - }; - } - } - output.from = output.from.substr(0, 255); - output.to = output.to.substr(0, 255); - - return output; -} diff --git a/crypto/blockchains/xvg/basic/XvgFindAddressFunction.ts b/crypto/blockchains/xvg/basic/XvgFindAddressFunction.ts deleted file mode 100644 index f2e8224b9..000000000 --- a/crypto/blockchains/xvg/basic/XvgFindAddressFunction.ts +++ /dev/null @@ -1,98 +0,0 @@ -import BlocksoftBN from '../../../common/AirDAOBN'; - -interface TransactionIO { - address: string; - value: string; - direction: 'income' | 'outcome' | 'self'; -} - -interface TransactionSummary { - direction: 'income' | 'outcome' | 'self'; - inputAddresses: string[]; - outputAddresses: string[]; - value: string; -} - -export default async function XvgFindAddressFunction( - targetAddress: string, - txIO: { inputs: TransactionIO[]; outputs: TransactionIO[] } -): Promise { - const inputFromOthersBN = new BlocksoftBN(0); - const inputFromTargetBN = new BlocksoftBN(0); - const inputFromOthersAddresses: string[] = []; - const uniqueInputAddresses: Record = {}; - - let input: TransactionIO; - for (input of txIO.inputs) { - if (input.address) { - if (input.address === targetAddress) { - inputFromTargetBN.add(input.value); - } else { - if (typeof uniqueInputAddresses[input.address] === 'undefined') { - uniqueInputAddresses[input.address] = 1; - inputFromOthersAddresses.push(input.address); - } - inputFromOthersBN.add(input.value); - } - } - } - - const outputToOthersBN = new BlocksoftBN(0); - const outputToTargetBN = new BlocksoftBN(0); - const outputToOthersAddresses: string[] = []; - const uniqueOutputAddresses: Record = {}; - - let output: TransactionIO; - for (output of txIO.outputs) { - if (output.address) { - if (output.address === targetAddress) { - outputToTargetBN.add(output.value); - } else { - if (typeof uniqueOutputAddresses[output.address] === 'undefined') { - uniqueOutputAddresses[output.address] = 1; - outputToOthersAddresses.push(output.address); - } - outputToOthersBN.add(output.value); - } - } - } - - if (inputFromTargetBN.get() === '0') { - // target only in output - return { - direction: 'income', - inputAddresses: inputFromOthersAddresses, - outputAddresses: [], - value: outputToTargetBN.get() - }; - } else if (outputToTargetBN.get() === '0') { - // target only in input - return { - direction: 'outcome', - inputAddresses: [], - outputAddresses: outputToOthersAddresses, - value: - inputFromOthersBN.get() === '0' - ? outputToOthersBN.get() - : inputFromTargetBN.get() - }; - } else { - // both input and output - if (outputToOthersAddresses.length > 0) { - // there are other addresses - return { - direction: 'outcome', - inputAddresses: [], - outputAddresses: outputToOthersAddresses, - value: outputToOthersBN.get() - }; - } else { - return { - direction: 'self', - inputAddresses: [], - outputAddresses: [], - value: inputFromTargetBN.diff(outputToTargetBN).get() - }; - } - } -} diff --git a/crypto/blockchains/xvg/providers/XvgSendProvider.ts b/crypto/blockchains/xvg/providers/XvgSendProvider.ts deleted file mode 100644 index 7dfe0cdd1..000000000 --- a/crypto/blockchains/xvg/providers/XvgSendProvider.ts +++ /dev/null @@ -1,86 +0,0 @@ -/** - * @version 0.20 - */ -import AirDAOCryptoLog from '../../../common/AirDAOCryptoLog'; -import AirDAOAxios from '../../../common/AirDAOAxios'; -import BlocksoftExternalSettings from '../../../common/AirDAOExternalSettings'; -import { AirDAOBlockchainTypes } from '@crypto/blockchains/AirDAOBlockchainTypes'; - -export default class XvgSendProvider - implements AirDAOBlockchainTypes.SendProvider -{ - protected _settings: AirDAOBlockchainTypes.CurrencySettings; - - constructor(settings: AirDAOBlockchainTypes.CurrencySettings) { - this._settings = settings; - } - - async sendTx( - hex: string, - subtitle: string - ): Promise<{ transactionHash: string; transactionJson: any }> { - const link = BlocksoftExternalSettings.getStatic('XVG_SEND_LINK'); - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' XvgSendProvider.sendTx ' + - subtitle + - ' started ' + - subtitle + - ' ' + - link - ); - let res; - try { - res = await AirDAOAxios.post(link, { rawTx: hex }); - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' XvgSendProvider.sendTx ' + - subtitle + - ' error ' + - subtitle + - ' ok ' + - hex - ); - } catch (e: any) { - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' XvgSendProvider.sendTx ' + - subtitle + - ' error ' + - subtitle + - ' ' + - e.message + - ' ' + - hex - ); - if (e.message.indexOf('mandatory-script-verify-flag-failed') !== -1) { - throw new Error('SERVER_RESPONSE_PLEASE_CHECK_SYSTEM_TIME'); - } else if (e.message.indexOf('dust') !== -1) { - throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_DUST'); - } else if (e.message.indexOf('missing inputs') !== -1) { - throw new Error('SERVER_RESPONSE_NO_RESPONSE'); - } else if (e.message.indexOf('bad-txns-inputs-spent') !== -1) { - throw new Error('SERVER_RESPONSE_NO_RESPONSE'); - } else if (e.message.indexOf('txn-mempool-conflict') !== -1) { - throw new Error('SERVER_RESPONSE_NO_RESPONSE'); - } else if ( - e.message.indexOf('fee for relay') !== -1 || - e.message.indexOf('insufficient priority') !== -1 - ) { - throw new Error('SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_FEE'); - } else if (e.message.indexOf('rejecting replacement') !== -1) { - throw new Error( - 'SERVER_RESPONSE_NOT_ENOUGH_AMOUNT_AS_FEE_FOR_REPLACEMENT' - ); - } else { - throw e; - } - } - // @ts-ignore - if (typeof res.data.txid === 'undefined' || !res.data.txid) { - throw new Error('SERVER_RESPONSE_NOT_CONNECTED'); - } - // @ts-ignore - return { transactionHash: res.data.txid, transactionJson: {} }; - } -} diff --git a/crypto/blockchains/xvg/providers/XvgUnspentsProvider.ts b/crypto/blockchains/xvg/providers/XvgUnspentsProvider.ts deleted file mode 100644 index 58e9923c0..000000000 --- a/crypto/blockchains/xvg/providers/XvgUnspentsProvider.ts +++ /dev/null @@ -1,92 +0,0 @@ -/** - * @version 0.20 - * https://api.vergecurrency.network/node/api/XVG/mainnet/address/DL5LtSf7wztH45VuYunL8oaQHtJbKLCHyw/txs/?unspent=true - */ -import AirDAOCryptoLog from '../../../common/AirDAOCryptoLog'; -import AirDAOAxios from '../../../common/AirDAOAxios'; -import { AirDAOBlockchainTypes } from '@crypto/blockchains/AirDAOBlockchainTypes'; - -export default class XvgUnspentsProvider - implements AirDAOBlockchainTypes.UnspentsProvider -{ - _apiPath = 'https://api.vergecurrency.network/node/api/XVG/mainnet/address/'; - - protected _settings: AirDAOBlockchainTypes.CurrencySettings; - - constructor(settings: AirDAOBlockchainTypes.CurrencySettings) { - this._settings = settings; - } - - async getUnspents( - address: string - ): Promise { - AirDAOCryptoLog.log( - this._settings.currencyCode + - ` XvgUnspentsProvider.getUnspents started', - ${address}` - ); - - const link = this._apiPath + address + '/txs/?unspent=true'; - const res = await AirDAOAxios.getWithoutBraking(link); - - AirDAOCryptoLog.log( - this._settings.currencyCode + - ` XvgUnspentsProvider.getUnspents link', - ${link}` - ); - - if (!res || typeof res === 'boolean' || !('data' in res)) { - // Check if 'data' exists in 'res' - AirDAOCryptoLog.log( - this._settings.currencyCode + - `' XvgUnspentsProvider.getUnspents nothing loaded for address ' + - ${address} + - ' link ' + - ${link}` - ); - throw new Error('SERVER_RESPONSE_NOT_CONNECTED'); - } - - if (!res.data || typeof res.data[0] === 'undefined') { - return []; - } - - const sortedUnspents: AirDAOBlockchainTypes.UnspentTx[] = []; - /** - * https://api.vergecurrency.network/node/api/XVG/mainnet/address/DL5LtSf7wztH45VuYunL8oaQHtJbKLCHyw/txs/?unspent=true - * @param {*} res.data[] - * @param {string} res.data[]._id "5e0b42fb746f4c73717c1d1d" - * @param {string} res.data[].chain "XVG" - * @param {string} res.data[].network "mainnet" - * @param {string} res.data[].coinbase false - * @param {string} res.data[].mintIndex 1 - * @param {string} res.data[].spentTxid - * @param {string} res.data[].mintTxid "50aae03bec6662a277c6e03ff2c58a200912e1bb78519d8403354c66c4d51892" - * @param {string} res.data[].mintHeight 3715825 - * @param {string} res.data[].spentHeight - * @param {string} res.data[].address "DL5LtSf7wztH45VuYunL8oaQHtJbKLCHyw" - * @param {string} res.data[].script "76a914a3d43334ff9ea4c257a1796b63e4fa8330747d2e88ac" - * @param {string} res.data[].value 91523000 - * @param {string} res.data[].confirmations -1 - */ - const already: { [key: string]: number } = {}; - let unspent; - for (unspent of res.data) { - if ( - typeof already[unspent.mintTxid] === 'undefined' || - already[unspent.mintTxid] > unspent.value - ) { - sortedUnspents.push({ - txid: unspent.mintTxid, - vout: unspent.mintIndex, - value: unspent.value.toString(), - height: unspent.mintHeight, - confirmations: 1, - isRequired: false - }); - already[unspent.mintTxid] = unspent.value; - } - } - return sortedUnspents; - } -} diff --git a/crypto/blockchains/xvg/stores/XvgTmpDS.ts b/crypto/blockchains/xvg/stores/XvgTmpDS.ts deleted file mode 100644 index 22fe907c6..000000000 --- a/crypto/blockchains/xvg/stores/XvgTmpDS.ts +++ /dev/null @@ -1,53 +0,0 @@ -// TODO add Database -// @ts-ignore -// import Database from '@app/appstores/DataSource/Database'; - -import { DatabaseTable } from '@appTypes'; -import { Database, TransactionScannersTmpDBModel } from '@database'; - -const tableName = DatabaseTable.TransactionScannersTmp; - -class XvgTmpDS { - _currencyCode = 'XVG'; - - async getCache(address: any) { - const res = (await Database.unsafeRawQuery( - tableName, - ` - SELECT tmp_key as tmpKey, tmp_sub_key as tmpSubKey, tmp_val as tmpVal - FROM ${tableName} - WHERE currency_code='${this._currencyCode}' - AND address='${address}' - AND (tmp_sub_key='coins' OR tmp_sub_key='data') - ` - )) as TransactionScannersTmpDBModel[]; - const tmp = {}; - if (res) { - for (const row of res) { - let val = 1; - if (row.tmpSubKey !== 'data') { - val = JSON.parse(Database.unEscapeString(row.tmp_val)); - } - tmp[row.tmpKey + '_' + row.tmpSubKey] = val; - } - } - return tmp; - } - - async saveCache(address: any, key: any, subKey: string, value: boolean) { - const now = new Date().toISOString(); - const prepared = [ - { - currency_code: this._currencyCode, - address: address, - tmp_key: key, - tmp_sub_key: subKey, - tmp_val: Database.escapeString(JSON.stringify(value)), - created_at: now - } - ]; - Database.createModel(tableName, prepared); - } -} - -export default new XvgTmpDS(); diff --git a/crypto/common/AirDAOCustomLinks.ts b/crypto/common/AirDAOCustomLinks.ts deleted file mode 100644 index 8fe4512bf..000000000 --- a/crypto/common/AirDAOCustomLinks.ts +++ /dev/null @@ -1,27 +0,0 @@ -/** - * @version 0.54 - * @author Javid - */ -// import { ThemeContext } from '@app/theme/ThemeProvider'; -// import BlocksoftExternalSettings from '@crypto/common/BlocksoftExternalSettings'; -// import { sublocale } from '@app/services/i18n'; - -class AirDAOCustomLinks { - getLink(link, isLight) { - // const themeColor = isLight ? 'light' : 'dark'; - // const siteLang = sublocale(); - // const paramsLink = `themeColor=${themeColor}&siteLang=${siteLang}`; - // const subLink = BlocksoftExternalSettings.getStatic(link); - - // if (subLink.includes('?')) { - // return `${subLink}&${paramsLink}`; - // } else { - // return `${subLink}?${paramsLink}`; - // } - return link; - } -} - -export default new AirDAOCustomLinks(); - -// AirDAOCustomLinks.contextType = ThemeContext; diff --git a/crypto/common/AirDAOFixBalance.ts b/crypto/common/AirDAOFixBalance.ts deleted file mode 100644 index 719505026..000000000 --- a/crypto/common/AirDAOFixBalance.ts +++ /dev/null @@ -1,27 +0,0 @@ -export default function fixBalance(obj: { [x: string]: any }, pref: string) { - if (typeof obj === 'undefined') return 0; - - let balance; - if (typeof obj[pref + 'Txt'] !== 'undefined') { - balance = obj[pref + 'Txt']; - } else { - balance = obj[pref + '_txt']; - } - if (balance && balance * 1 > 0) { - return balance; - } - - if (typeof obj[pref + 'Fix'] !== 'undefined') { - balance = obj[pref + 'Fix']; - } else { - balance = obj[pref + '_fix']; - } - if (!balance) { - return '0'; - } - if (balance.toString().indexOf('e') === -1) { - return balance; - } - - return 0; -} diff --git a/crypto/common/AirDAOPrettyDates.ts b/crypto/common/AirDAOPrettyDates.ts deleted file mode 100644 index 31dc2ff8c..000000000 --- a/crypto/common/AirDAOPrettyDates.ts +++ /dev/null @@ -1,34 +0,0 @@ -import config from '@constants/config'; - -class AirDAOPrettyDates { - timestampToPretty(str: string | number | Date) { - try { - const datetime = new Date(str); - return ( - datetime.toTimeString().slice(0, 8) + - ' ' + - (datetime.getDate().toString().length === 1 - ? '0' + datetime.getDate() - : datetime.getDate()) + - '/' + - ((datetime.getMonth() + 1).toString().length === 1 - ? '0' + (datetime.getMonth() + 1) - : datetime.getMonth() + 1) + - '/' + - datetime.getFullYear() - ); - } catch (e: any) { - if (config.debug.appErrors) { - console.log( - 'BlocksoftPrettyDates.timestampToPretty ' + - str + - ' error ' + - e.message - ); - } - return ''; - } - } -} - -export default new AirDAOPrettyDates(); diff --git a/crypto/common/AirDAOPrettyLocalize.ts b/crypto/common/AirDAOPrettyLocalize.ts deleted file mode 100644 index 1e94f55a4..000000000 --- a/crypto/common/AirDAOPrettyLocalize.ts +++ /dev/null @@ -1,19 +0,0 @@ -// @ts-ignore -import i18n from '../../app/services/i18n'; - -class AirDAOPrettyLocalize { - makeLink(link: string) { - const main = i18n.locale.split('-')[0].toLowerCase(); - if ((main === 'uk' || main === 'ru') && link) { - if (link.indexOf('https://blockchair.com/') === 0) { - link = link.replace( - 'https://blockchair.com/', - 'https://blockchair.com/ru/' - ); - } - } - return link; - } -} - -export default new AirDAOPrettyLocalize(); diff --git a/crypto/common/AirDAOPrettyStrings.ts b/crypto/common/AirDAOPrettyStrings.ts deleted file mode 100644 index 8b24effeb..000000000 --- a/crypto/common/AirDAOPrettyStrings.ts +++ /dev/null @@ -1,25 +0,0 @@ -class AirDAOPrettyStrings { - makeCut(str: string, size = 5, sizeEnd = false) { - if (!str) return str; - - const ln = str.length; - if (!sizeEnd || sizeEnd === 0) { - sizeEnd = size; - } - if (ln < size + sizeEnd + 3) return str; - return str.substring(0, size) + '...' + str.substring(ln - sizeEnd); - } - - makeFromTrustee(link: any) { - let linkUrl = link; - if (linkUrl.indexOf('harmony.one') !== -1) { - return linkUrl; - } - if (linkUrl.indexOf('?') === -1) { - linkUrl += '?from=trustee'; - } - return linkUrl; - } -} - -export default new AirDAOPrettyStrings(); diff --git a/crypto/common/ext/scam-seeds.ts b/crypto/common/ext/scam-seeds.ts deleted file mode 100644 index fc9b0f27e..000000000 --- a/crypto/common/ext/scam-seeds.ts +++ /dev/null @@ -1,4 +0,0 @@ -export default { - 'blue boost talk hero praise enemy sleep extra toddler escape ankle silk': 1, - 'pass sugar city plate comfort tube filter merit oak trim frown love': 1 -}; diff --git a/crypto/readme.md b/crypto/readme.md deleted file mode 100644 index dfac66816..000000000 --- a/crypto/readme.md +++ /dev/null @@ -1,43 +0,0 @@ -# This is crypto actions and libraries for the Trustee Wallet - -## Please follow general style and testing rules - -- All files should be checked with eslint for autoformatting etc - -- All new functions should be added with tests, that could be performed on code review -at any environment (max some manual works that will be needed is airdrop of btc to test address -or cache update for utxo`s) - -- All changes in existing code should not break existing tests (max some manual...) - - - -## Testing tx send uses - -#### BTC_TEST - -crypto/blockchains/btc/tests/providers/BtcTxUnspentsProvider.test.withTxSend.test.js - -mmtxZNdw5L7ugQcCRCZJZinSicgMGsDEK3 cPqjJ1iASqCaC1YsCcBUixYK6jQrcjF7qshGLGMxpprRiHdDDsRd - -#### BTC_TEST ROUND - -used at crypto/actions/BlocksoftTransaction/tests/BlocksoftTransaction.test.Send.code.BTC.test.js - -mggtxjLhuWM8zWCxY7DXE3UWNXWdEjjs51 cUhyqBWAFhAWmJcq6Tqbj1kKrZFhxXVc8aRDgLZJFpYRRPBpoLuf - -msTNzykR2TaousFS7S6pLSCesnkLDohewm cMfQmbELgNERpBXpf57wU3uxZG47nAkuGhSVZRb3QogevptgAG5b - -#### ETH_ROPSTEN - -0x103f8f95c7539A87968C2F5044c02c5A17066177 0xdce0ecf2b36759f68761e5a21dfa124b06d03aeda52cb5968139689432f9a1aa - -0xcb133e23A3461984aB4d6F48AcB6d3bf2aD61Ad2 0xdb30610f156e1d4aefaa9b4423909297ceff64c2 - -#### ETH_ROPSTEN ROUND - -used at crypto/actions/BlocksoftTransaction/tests/BlocksoftTransaction.test.Send.code.ETH.test.js - -0xA61846D2054ACc63d3869c52ae69180BF649615e 0xfdc00ea004a8d77ec744f0bf35fcb6e7c8fa7402d3e9c8b6d4da2e9b1d979706 - -0x087e3D9C42C39149Ed62fAd0C4698fb8CE59fCf9 0xd49b3b91ae567ed1f1e45e88d696a575066c30fde1cc4f9e2ef27607d89af90f diff --git a/crypto/services/EnsUtils.ts b/crypto/services/EnsUtils.ts deleted file mode 100644 index eb518c0df..000000000 --- a/crypto/services/EnsUtils.ts +++ /dev/null @@ -1,25 +0,0 @@ -/** - * @version 0.41 - */ -import { Web3Injected } from '@crypto/services/Web3Injected'; - -/** - * @param address - * @returns {boolean} - */ -export const isEnsAddressValid = (address: string) => { - if (address) { - try { - return /^.+\.eth$/.test(address); - } catch (e: any) { - console.log(e); - } - } - return false; -}; - -export const getEnsAddress = async (address: any) => { - const WEB3 = Web3Injected('mainnet'); - const res = await WEB3.eth.ens.getAddress(address); - return res; -}; diff --git a/crypto/services/UnstoppableUtils.ts b/crypto/services/UnstoppableUtils.ts deleted file mode 100644 index e4d88ff23..000000000 --- a/crypto/services/UnstoppableUtils.ts +++ /dev/null @@ -1,18 +0,0 @@ -/** - * @version 0.41 - */ - -/** - * @param address - * @returns {boolean} - */ -export const isUnstoppableAddressValid = (address: string) => { - if (address) { - try { - return /^.+\.crypto$/.test(address); - } catch (e) { - console.log(e); - } - } - return false; -}; diff --git a/src/contexts/SendCrypto/SendCrypto.context.ts b/src/contexts/SendCrypto/SendCrypto.context.ts index d55b550c2..8be18a987 100644 --- a/src/contexts/SendCrypto/SendCrypto.context.ts +++ b/src/contexts/SendCrypto/SendCrypto.context.ts @@ -76,7 +76,7 @@ const SendCryptoContext = (): { } }; - return { state, reducer: reducer }; + return { state, reducer }; }; export const [SendCryptoProvider, useSendCryptoContext] = diff --git a/src/contexts/SendCrypto/helpers/index.ts b/src/contexts/SendCrypto/helpers/index.ts index d703eb3f7..627fe554c 100644 --- a/src/contexts/SendCrypto/helpers/index.ts +++ b/src/contexts/SendCrypto/helpers/index.ts @@ -1,5 +1,5 @@ -export * from './SendActionsBlockchainWrapper'; -export * from './SendActionsContactBook'; -export * from './SendActionsEnd'; -export * from './SendActionsStart'; export * from './SendActionsUpdateValues'; +export * from './SendActionsBlockchainWrapper' +export * from './SendActionsEnd' +export * from './SendActionsStart' +export * from './SendActionsContactBook' diff --git a/src/daemons/Daemon.js b/src/daemons/Daemon.js deleted file mode 100644 index f8efb829c..000000000 --- a/src/daemons/Daemon.js +++ /dev/null @@ -1,42 +0,0 @@ -/** - * @version 0.11 - */ -import UpdateOneByOneDaemon from './back/UpdateOneByOneDaemon' - -import UpdateAccountListDaemon from './view/UpdateAccountListDaemon' -import UpdateCurrencyRateDaemon from './back/UpdateCurrencyRateDaemon' -import UpdateCashBackDataDaemon from './back/UpdateCashBackDataDaemon' - -import config from '../config/config' -import UpdateCardsDaemon from '@app/daemons/back/UpdateCardsDaemon' - -let CACHE_STARTED = false - -export default { - - async start () { - if (CACHE_STARTED) return false - const { daemon } = config - UpdateOneByOneDaemon - .setTime(daemon.updateTimes.oneByOne) - .start() - UpdateAccountListDaemon - .setTime(daemon.updateTimes.view) - .start() - CACHE_STARTED = true - }, - - async forceAll(params) { - if (typeof params.noRatesApi === 'undefined') { - await UpdateCurrencyRateDaemon.updateCurrencyRate(params) - } - await UpdateAccountListDaemon.forceDaemonUpdate(params) - // await UpdateAppNewsDaemon.updateAppNewsDaemon(params) - if (typeof params.noCashbackApi === 'undefined') { - await UpdateCashBackDataDaemon.updateCashBackDataDaemon(params) - } - if (typeof params.noCards === 'undefined') { - await UpdateCardsDaemon.updateCardsDaemon(params) - } - } -} diff --git a/src/daemons/DaemonCache.js b/src/daemons/DaemonCache.js deleted file mode 100644 index 586e1437a..000000000 --- a/src/daemons/DaemonCache.js +++ /dev/null @@ -1,100 +0,0 @@ -/** - * @version 0.11 - */ -import Database from '@app/appstores/DataSource/Database'; - -import transactionDS from '../appstores/DataSource/Transaction/Transaction'; - -import BlocksoftFixBalance from '@crypto/common/AirDAOFixBalance'; - -class DaemonCache { - CACHE_WALLET_COUNT = 0; - - CACHE_WALLET_SUMS = {}; - CACHE_WALLET_TOTAL = { balance: 0, unconfirmed: 0 }; - CACHE_RATES = {}; - CACHE_ALL_ACCOUNTS = {}; - CACHE_FIO_MEMOS = {}; - - CACHE_ACCOUNT_TX = {}; - - /** - * @param walletHash - * @returns {{unconfirmed: number, balance: number, basicCurrencySymbol: string}} - */ - getCache(walletHash = false) { - if (!walletHash) { - return this.CACHE_WALLET_TOTAL; - } - if (typeof this.CACHE_WALLET_SUMS[walletHash] === 'undefined') return false; - return this.CACHE_WALLET_SUMS[walletHash]; - } - - /** - * @param {string} currencyCode - * @returns {{basicCurrencySymbol: string, basicCurrencyRate: number}} - */ - getCacheRates(currencyCode) { - if (typeof this.CACHE_RATES[currencyCode] === 'undefined') { - return { basicCurrencySymbol: '', basicCurrencyRate: '' }; - } - return this.CACHE_RATES[currencyCode]; - } - - cleanCacheTxsCount(account) { - let cacheTitle = account.walletHash + '_' + account.currencyCode; - if (typeof this.CACHE_ACCOUNT_TX[cacheTitle] !== 'undefined') { - this.CACHE_ACCOUNT_TX[cacheTitle] = -1; - } - cacheTitle += '_noZero'; - if (typeof this.CACHE_ACCOUNT_TX[cacheTitle] !== 'undefined') { - this.CACHE_ACCOUNT_TX[cacheTitle] = -1; - } - } - - getFioMemo(currencyCode) { - return this.CACHE_FIO_MEMOS[currencyCode] ?? {}; - } - - async _getFromDB(walletHash, currencyCode) { - const sql = ` SELECT balance_fix AS balanceFix, balance_txt AS balanceTxt FROM account_balance WHERE currency_code='${currencyCode}' AND wallet_hash='${walletHash}'`; - const res = await Database.query(sql); - if (!res || !res.array || res.array.length === 0) { - return { balance: 0, from: 'noDb' }; - } - let account; - let totalBalance = 0; - for (account of res.array) { - const balance = BlocksoftFixBalance(account, 'balance'); - if (balance > 0) { - totalBalance += balance; - } - } - return { balance: totalBalance, from: 'sumDb' }; - } - - async getCacheAccount(walletHash, currencyCode) { - if (typeof this.CACHE_ALL_ACCOUNTS[walletHash] === 'undefined') { - return this._getFromDB(walletHash, currencyCode); - } - if ( - typeof this.CACHE_ALL_ACCOUNTS[walletHash][currencyCode] === 'undefined' - ) { - return this._getFromDB(walletHash, currencyCode); - } - return this.CACHE_ALL_ACCOUNTS[walletHash][currencyCode]; - } - - getCacheAccountStatic(walletHash, currencyCode) { - if ( - typeof this.CACHE_ALL_ACCOUNTS[walletHash] === 'undefined' || - typeof this.CACHE_ALL_ACCOUNTS[walletHash][currencyCode] === 'undefined' - ) { - return { balance: '0' }; - } - return this.CACHE_ALL_ACCOUNTS[walletHash][currencyCode]; - } -} - -const single = new DaemonCache(); -export default single; diff --git a/src/daemons/Update.js b/src/daemons/Update.js deleted file mode 100644 index 63d895cca..000000000 --- a/src/daemons/Update.js +++ /dev/null @@ -1,48 +0,0 @@ -/** - * @version 0.11 - */ -class Update { - - updateTime = 20000 - updateFunction = () => {} - - _updateFirstCall = true - _updateTimer = {} - - start = async () => { - this._localDaemon() - } - - setTime = (updateTime) => { - this.updateTime = updateTime - return this - } - - forceDaemonUpdate = async (params) => { - if (typeof params === 'undefined') { - params = { force: true } - } else { - params.force = true - } - await this.updateFunction(params) - } - - _localDaemon = async () => { - const { - updateTime, - updateFunction - } = this - - if (this._updateFirstCall) { - this._updateFirstCall = false - await updateFunction({ force: false }) - } - - this._updateTimer = setTimeout(async () => { - await updateFunction({ force: false }) - this._localDaemon() - }, updateTime) - } -} - -export default Update diff --git a/src/daemons/back/UpdateAccountBalanceAndTransactions.js b/src/daemons/back/UpdateAccountBalanceAndTransactions.js deleted file mode 100644 index d580b9c95..000000000 --- a/src/daemons/back/UpdateAccountBalanceAndTransactions.js +++ /dev/null @@ -1,769 +0,0 @@ -/** - * @version 0.11 - */ -import BlocksoftKeysStorage from '../../../crypto/actions/BlocksoftKeysStorage/BlocksoftKeysStorage'; - -import BlocksoftBalances from '../../../crypto/actions/BlocksoftBalances/BlocksoftBalances'; -import BlocksoftTransactions from '../../../crypto/actions/BlocksoftTransactions/BlocksoftTransactions'; - -import Log from '../../services/Log/Log'; -import MarketingEvent from '../../services/Marketing/MarketingEvent'; - -import accountScanningDS from '../../appstores/DataSource/Account/AccountScanning'; -import accountBalanceDS from '../../appstores/DataSource/AccountBalance/AccountBalance'; -import accountDS from '../../appstores/DataSource/Account/Account'; - -import AccountTransactionsRecheck from './apputils/AccountTransactionsRecheck'; -import settingsActions from '../../appstores/Stores/Settings/SettingsActions'; - -import config from '../../config/config'; -import { - getFioObtData, - resolveCryptoCodes -} from '../../../crypto/blockchains/fio/FioUtils.jts'; -import DaemonCache from '../DaemonCache'; -import store from '@app/store'; -import UpdateAccountListDaemon from '@app/daemons/view/UpdateAccountListDaemon'; - -const CACHE_SCANNING = {}; -const CACHE_VALID_TIME = 60000; // 1 minute -const CACHE_VALID_10MIN_TIME = 600000; // 10 minutes -const CACHE_CUSTOM_TIME = { - BCH: 60000, // 10 minutes - BSV: 60000 // 10 minutes -}; -let CACHE_LAST_TIME = false; -let CACHE_ONE_ACCOUNTS = {}; - -class UpdateAccountBalanceAndTransactions { - getTime() { - if (CACHE_LAST_TIME > 0) { - return new Date(CACHE_LAST_TIME).toLocaleTimeString(); - } - return '-'; - } - - /** - * @param {string} callParams.source - * @param {boolean} callParams.force - * @param {boolean} callParams.allWallets - * @param {boolean} callParams.onlyBalances - * @param {string} callParams.currencyCode - * @returns {Promise} - */ - updateAccountBalanceAndTransactions = async (callParams) => { - const source = callParams.source || 'FRONT'; - const force = callParams.force || false; - const allWallets = callParams.allWallets || false; - const onlyBalances = callParams.onlyBalances || false; - if (!force || source === 'BACK') { - const setting = await settingsActions.getSetting('scannerCode'); - if (!setting) { - await settingsActions.setSettings('scannerCode', '1min'); - } - if (setting === 'none') { - return false; - } else if (CACHE_LAST_TIME && setting === '10min') { - const now = new Date().getTime(); - const diff = now - CACHE_LAST_TIME; - if (diff < CACHE_VALID_10MIN_TIME) { - Log.daemon( - 'UpdateAccountBalanceAndTransactions skipped by diff ' + diff - ); - return false; - } - } - } - - let tmpAction = ''; - try { - const params = { - force - }; - - if (!allWallets && source !== 'BACK') { - params.walletHash = await settingsActions.getSelectedWallet( - 'UpdateAccountBalanceAndTransactions' - ); - } - - tmpAction = 'params init'; - if ( - typeof callParams !== 'undefined' && - callParams && - typeof callParams.currencyCode !== 'undefined' - ) { - if (force) { - if (callParams.currencyCode.indexOf('TRX') === 0) { - params.currencyFamily = 'TRX'; - } else if (callParams.currencyCode.indexOf('ETH') === 0) { - params.currencyFamily = 'ETH'; - } else { - params.currencyCode = callParams.currencyCode; - } - } else { - params.currencyCode = callParams.currencyCode; - } - } - - Log.daemon('UpdateAccountBalanceAndTransactions called ' + source); - - tmpAction = 'accounts init'; - - let accounts = await accountScanningDS.getAccountsForScan({ - ...params, - force: false - }); - - if (force) { - if (!accounts || accounts.length === 0) { - accounts = await accountScanningDS.getAccountsForScan(params); - } - } - - if (!accounts || accounts.length === 0) { - Log.daemon('UpdateAccountBalanceAndTransactions called - no account'); - return false; - } - - tmpAction = 'accounts log'; - let account; - for (account of accounts) { - Log.daemon( - 'UpdateAccountBalanceAndTransactions called - todo account ' + - account.id + - ' ' + - account.currencyCode + - ' ' + - account.address - ); - } - - tmpAction = 'accounts run main'; - let running = 0; - CACHE_ONE_ACCOUNTS = {}; - let shouldUpdateBalance = false; - for (account of accounts) { - if (typeof CACHE_CUSTOM_TIME[account.currencyCode] !== 'undefined') { - continue; - } - if ( - typeof CACHE_ONE_ACCOUNTS[ - account.currencyCode + '_' + account.address - ] !== 'undefined' - ) { - continue; - } - tmpAction = 'account run ' + JSON.stringify(account); - if ( - await this._accountRun( - account, - accounts, - source, - CACHE_VALID_TIME, - force, - onlyBalances - ) - ) { - shouldUpdateBalance = true; - } - running++; - } - - tmpAction = 'accounts run custom'; - - for (account of accounts) { - if ( - typeof CACHE_ONE_ACCOUNTS[ - account.currencyCode + '_' + account.address - ] !== 'undefined' - ) { - continue; - } - if (typeof CACHE_CUSTOM_TIME[account.currencyCode] !== 'undefined') { - // if its the only ones not updated - lets do them faster - if ( - await this._accountRun( - account, - accounts, - source, - running > 0 - ? CACHE_CUSTOM_TIME[account.currencyCode] - : CACHE_VALID_TIME, - force, - onlyBalances - ) - ) { - shouldUpdateBalance = true; - } - } - } - - CACHE_LAST_TIME = new Date().getTime(); - if (shouldUpdateBalance) { - await UpdateAccountListDaemon.updateAccountListDaemon({ - force: true, - source: 'SHOULD_UPDATE_BALANCE' - }); - } - } catch (e) { - if (config.debug.appErrors) { - console.log( - 'UpdateAccountBalanceAndTransactions balance error ' + - source + - ' ' + - e.message, - e - ); - } - Log.errDaemon( - 'UpdateAccountBalanceAndTransactions balance error ' + - source + - ' ' + - e.message + - ' ' + - tmpAction - ); - return false; - } - return true; - }; - - loadFioData = async (currencyCode) => { - const currencies = store.getState().currencyStore.cryptoCurrencies; - let foundFio = false; - for (const tmp of currencies) { - if (tmp.currencyCode === 'FIO' && !tmp.maskedHidden) { - foundFio = true; - break; - } - } - if (!foundFio) return false; - Log.daemon( - 'UpdateAccountBalanceAndTransactions loadFioData ' + currencyCode - ); - try { - // eslint-disable-next-line camelcase - const { token_code } = resolveCryptoCodes(currencyCode); - const result = await getFioObtData(token_code); - if (result && result['obt_data_records']) { - const fioData = result['obt_data_records'].reduce((res, item) => { - if (!item.content?.memo) { - return res; - } - - return !item.content?.obt_id - ? res - : { - ...res, - [item.content?.obt_id]: item.content?.memo - }; - }, {}); - - DaemonCache.CACHE_FIO_MEMOS[currencyCode] = { - ...DaemonCache.getFioMemo(currencyCode), - ...fioData - }; - } - } catch (e) { - if (config.debug.cryptoErrors) { - console.log( - 'UpdateAccountBalanceAndTransactions error on loadFioData ' + - e.message, - e - ); - } - Log.errDaemon( - 'UpdateAccountBalanceAndTransactions error on loadFioData ' + e.message - ); - } - }; - - async _accountRun( - account, - accounts, - source, - time, - force, - onlyBalances = false - ) { - let newBalance = false; - let addressToScan = account.address; - - Log.daemon( - 'UpdateAccountBalanceAndTransactions _accountRun init ' + - account.id + - ' ' + - account.currencyCode + - ' ' + - account.address - ); - - if ( - account.accountJson && - typeof account.accountJson.addressHex !== 'undefined' - ) { - addressToScan = account.accountJson.addressHex; - Log.daemon( - 'UpdateAccountBalanceAndTransactions changing address ' + - account.currencyCode + - ' ' + - account.address + - ' => ' + - addressToScan - ); - } - const now = new Date().getTime(); - if ( - !force && - typeof CACHE_SCANNING[account.currencyCode + ' ' + addressToScan] !== - 'undefined' - ) { - const diff = - now - CACHE_SCANNING[account.currencyCode + ' ' + addressToScan]; - if (diff < time) { - Log.daemon( - 'UpdateAccountBalanceAndTransactions skipped as running ' + - account.currencyCode + - ' ' + - account.address + - ' => diff:' + - diff + - ' time: ' + - time - ); - return false; - } - } - CACHE_SCANNING[account.currencyCode + ' ' + addressToScan] = now; - - const updateObj = { - balanceScanTime: Math.round(new Date().getTime() / 1000) - }; - - let balanceError; - try { - Log.daemon( - 'UpdateAccountBalanceAndTransactions newBalance ' + - account.currencyCode + - ' ' + - addressToScan - ); - - if (account.currencyCode === 'BTC') { - const additional = {}; - if ( - account.walletIsHd && - account.derivationPath !== 'm/49quote/0quote/0/1/0' - ) { - updateObj.balanceScanLog = account.address + ' should be HD '; - updateObj.balanceScanError = ''; - await accountBalanceDS.updateAccountBalance({ updateObj }, account); - return false; - } else { - newBalance = await BlocksoftBalances.setCurrencyCode( - account.currencyCode - ) - .setAddress(addressToScan) - .setAdditional(additional) - .setWalletHash(account.walletHash) - .getBalance('AccountRunBalancesBtc'); - } - } else { - newBalance = await BlocksoftBalances.setCurrencyCode( - account.currencyCode - ) - .setAddress(addressToScan) - .setAdditional(account.accountJson) - .setWalletHash(account.walletHash) - .getBalance('AccountRunBalances'); - } - if (!newBalance || typeof newBalance.balance === 'undefined') { - if (account.balanceScanBlock === 0 && account.balanceScanTime === 0) { - updateObj.balanceScanLog = - account.address + - ' empty response, old balance ' + - account.balance + - ', ' + - JSON.stringify(newBalance); - updateObj.balanceScanError = 'account.balanceBadNetwork'; - await accountBalanceDS.updateAccountBalance({ updateObj }, account); - return false; - } - balanceError = - ' something wrong with balance ' + - account.currencyCode + - ' ' + - addressToScan + - ' => ' + - JSON.stringify(newBalance); - Log.daemon( - 'UpdateAccountBalanceAndTransactions newBalance something wrong ' + - account.currencyCode + - ' ' + - addressToScan + - ' => ' + - JSON.stringify(newBalance) - ); - } else { - balanceError = ' found in one ' + JSON.stringify(newBalance); - } - } catch (e) { - if (config.debug.appErrors) { - console.log( - 'UpdateAccountBalanceAndTransactions newBalance from ' + - source + - ' loaded ' + - account.currencyCode + - ' ' + - addressToScan + - ' error ' + - e.message - ); - } - balanceError = ' found balanceError ' + e.message; - } - - Log.daemon( - 'UpdateAccountBalanceAndTransactions newBalance from ' + - source + - ' loaded ' + - account.currencyCode + - ' ' + - addressToScan, - JSON.stringify(newBalance) - ); - let continueWithTx = true; - let shouldUpdateBalance = false; - try { - if (newBalance && typeof newBalance.balance !== 'undefined') { - if (typeof account.balance === 'undefined') { - shouldUpdateBalance = true; - } else if ( - newBalance.balance.toString() !== account.balance.toString() || - newBalance.unconfirmed.toString() !== account.unconfirmed.toString() - ) { - shouldUpdateBalance = true; - } else if (typeof newBalance.balanceStaked !== 'undefined') { - if (typeof account.balanceStaked === 'undefined') { - shouldUpdateBalance = true; - } else if ( - newBalance.balanceStaked * 1 !== - account.balanceStaked * 1 - ) { - // toString here somehow do undefined sometimes - shouldUpdateBalance = true; - } - } - - if ( - typeof newBalance.balanceScanBlock !== 'undefined' && - (typeof account.balanceScanBlock === 'undefined' || - newBalance.balanceScanBlock * 1 < account.balanceScanBlock * 1) - ) { - continueWithTx = false; - updateObj.balanceProvider = newBalance.provider; - updateObj.balanceScanLog = - account.address + - ' block error, ignored new ' + - newBalance.balance + - ' block ' + - newBalance.balanceScanBlock + - ', old balance ' + - account.balance + - ' block ' + - account.balanceScanBlock; - updateObj.balanceScanError = 'account.balanceBadBlock'; - } else if (shouldUpdateBalance) { - updateObj.balanceFix = newBalance.balance; // lets send to db totally not changed big number string - updateObj.balanceTxt = newBalance.balance.toString(); // and string for any case - updateObj.unconfirmedFix = newBalance.unconfirmed || 0; // lets send to db totally not changed big number string - updateObj.unconfirmedTxt = newBalance.unconfirmed || ''; // and string for any case - updateObj.balanceStakedTxt = newBalance.balanceStaked || '0'; - updateObj.balanceProvider = newBalance.provider; - if (typeof newBalance.balanceScanBlock !== 'undefined') { - updateObj.balanceScanBlock = newBalance.balanceScanBlock; - } - updateObj.balanceScanLog = - account.address + - ' all ok, new balance ' + - newBalance.balance + - ', old balance ' + - account.balance + - ', ' + - balanceError; - updateObj.balanceScanError = ''; - const logData = {}; - logData.walletHash = account.walletHash; - logData.currencyCode = account.currencyCode; - logData.address = account.address; - logData.addressShort = account.address - ? account.address.slice(0, 10) - : 'none'; - logData.balanceScanTime = account.balanceScanTime + ''; - logData.balanceProvider = account.balanceProvider + ''; - logData.balance = account.balance + ''; - logData.newBalanceProvider = account.newBalanceProvider + ''; - logData.newBalance = newBalance.balance * 1 + ''; - MarketingEvent.setBalance( - logData.walletHash, - logData.currencyCode, - logData.newBalance, - logData - ); - } else { - updateObj.balanceScanLog = - account.address + - ' not changed, old balance ' + - account.balance + - ', ' + - balanceError; - updateObj.balanceScanError = ''; - if (typeof newBalance.provider !== 'undefined') { - updateObj.balanceProvider = newBalance.provider; - } - } - Log.daemon( - 'UpdateAccountBalanceAndTransactions newBalance ok Prepared ' + - account.currencyCode + - ' ' + - account.address + - ' new balance ' + - newBalance.balance + - ' provider ' + - newBalance.provider + - ' old balance ' + - account.balance, - JSON.stringify(updateObj) - ); - } else { - updateObj.balanceScanLog = - account.address + - ' no balance, old balance ' + - account.balance + - ', ' + - balanceError; - updateObj.balanceScanError = 'account.balanceBadNetwork'; - Log.daemon( - 'UpdateAccountBalanceAndTransactions newBalance not Prepared ' + - account.currencyCode + - ' ' + - account.address + - ' old balance ' + - account.balance, - JSON.stringify(updateObj) - ); - } - } catch (e) { - if (config.debug.appErrors) { - console.log( - 'UpdateAccountBalanceAndTransactions newBalance from ' + - source + - ' loaded ' + - account.currencyCode + - ' ' + - addressToScan + - ' format error ' + - e.message - ); - } - e.message += ' while accountBalanceDS.updateAccountBalance formatting'; - throw e; - } - - if (account.balanceScanLog) { - updateObj.balanceScanLog += ' ' + account.balanceScanLog; - } - - try { - updateObj.balanceScanLog = - new Date().toISOString() + - ' ' + - updateObj.balanceScanLog.substr(0, 1000); - await accountBalanceDS.updateAccountBalance({ updateObj }, account); - } catch (e) { - e.message += ' while accountBalanceDS.updateAccountBalance'; - throw e; - } - - if (!continueWithTx || onlyBalances) { - return shouldUpdateBalance; // balance error - tx will not be good also - } - try { - let transactionsError = ' '; - let newTransactions = false; - try { - Log.daemon( - 'UpdateAccountBalanceAndTransactions newTransactions ' + - account.currencyCode + - ' ' + - account.address - ); - if (account.currencyCode === 'BTC' || account.currencyCode === 'LTC') { - const additional = { ...account.accountJson }; - additional.addresses = await accountScanningDS.getAddresses({ - currencyCode: account.currencyCode, - walletHash: account.walletHash - }); - if (account.walletIsHd && account.currencyCode !== 'LTC') { - additional.walletPub = true; // actually not needed pub - just flag - } - newTransactions = await BlocksoftTransactions.getTransactions( - { account, additional }, - 'AccountRunTransactionsBtc' - ); - } else { - newTransactions = await BlocksoftTransactions.getTransactions( - { account, additional: account.accountJson }, - 'AccountRunTransactions' - ); - } - if (!newTransactions || newTransactions.length === 0) { - transactionsError = - ' empty transactions ' + - account.currencyCode + - ' ' + - account.address; - } else { - transactionsError = ' found transactions ' + newTransactions.length; - } - } catch (e) { - if (config.debug.appErrors) { - console.log( - 'UpdateAccountBalanceAndTransactions newTransactions something wrong ' + - account.currencyCode + - ' ' + - account.address + - ' => transactionsError ' + - e.message, - e - ); - } - Log.errDaemon( - 'UpdateAccountBalanceAndTransactions newTransactions something wrong ' + - account.currencyCode + - ' ' + - account.address + - ' => transactionsError ' + - e.message - ); - - transactionsError = ' found transactionsError ' + e.message; - } - - Log.daemon( - 'UpdateAccountBalanceAndTransactions newTransactions loaded ' + - account.currencyCode + - ' ' + - addressToScan - ); - - let transactionUpdateObj; - try { - transactionUpdateObj = await AccountTransactionsRecheck( - newTransactions, - account, - 'RECHECK ' + source - ); - } catch (e) { - e.message += ' while AccountTransactionsRecheck'; - throw e; - } - // Log.daemon('res', transactionUpdateObj) - try { - transactionUpdateObj.transactionsScanLog = - new Date().toISOString() + - ' ' + - transactionsError + - ', ' + - transactionUpdateObj.transactionsScanLog; - if (account.transactionsScanLog) { - transactionUpdateObj.transactionsScanLog += - ' ' + account.transactionsScanLog; - } - await accountDS.updateAccount( - { updateObj: transactionUpdateObj }, - account - ); - } catch (e) { - e.message += ' while accountDS.updateAccount'; - throw e; - } - } catch (e) { - if (config.debug.appErrors) { - console.log( - 'UpdateAccountBalanceAndTransactions newTransactions something wrong ' + - account.currencyCode + - ' ' + - account.address + - ' => transactionsError2 ' + - e.message, - e - ); - } - Log.errDaemon( - 'UpdateAccountBalanceAndTransactions newTransactions something wrong ' + - account.currencyCode + - ' ' + - account.address + - ' => transactionsError2 ' + - e.message - ); - } - - CACHE_ONE_ACCOUNTS[account.currencyCode + '_' + account.address] = 1; - - if (account.currencyCode === 'TRX') { - for (const sub of accounts) { - if (sub.currencyCode === 'TRX_USDT') { - if ( - await this._accountRun( - sub, - accounts, - source + ' GONE INNER', - CACHE_VALID_TIME, - true - ) - ) { - shouldUpdateBalance = true; - } - break; - } - } - for (const sub of accounts) { - if ( - sub.currencyCode !== 'TRX_USDT' && - sub.currencyCode.indexOf('TRX_') === 0 - ) { - if ( - await this._accountRun( - sub, - accounts, - source + ' GONE INNER2', - CACHE_VALID_TIME, - true - ) - ) { - shouldUpdateBalance = true; - } - } - } - } - - await this.loadFioData(account.currencyCode); - - Log.daemon( - 'UpdateAccountBalanceAndTransactions _accountRun finish ' + - account.id + - ' ' + - account.currencyCode + - ' ' + - account.address - ); - - return shouldUpdateBalance; - } -} - -const singleton = new UpdateAccountBalanceAndTransactions(); -export default singleton; diff --git a/src/daemons/back/UpdateAccountBalanceAndTransactionsHD.js b/src/daemons/back/UpdateAccountBalanceAndTransactionsHD.js deleted file mode 100644 index 4a72633f6..000000000 --- a/src/daemons/back/UpdateAccountBalanceAndTransactionsHD.js +++ /dev/null @@ -1,319 +0,0 @@ -/** - * @version 0.11 - */ -import BlocksoftKeysStorage from '../../../crypto/actions/BlocksoftKeysStorage/BlocksoftKeysStorage' - -import BlocksoftBalances from '../../../crypto/actions/BlocksoftBalances/BlocksoftBalances' -import BlocksoftTransactions from '../../../crypto/actions/BlocksoftTransactions/BlocksoftTransactions' - -import Log from '../../services/Log/Log' -import MarketingEvent from '../../services/Marketing/MarketingEvent' - -import walletPubScanningDS from '../../appstores/DataSource/Wallet/WalletPubScanning' -import accountScanningDS from '../../appstores/DataSource/Account/AccountScanning' - -import AccountTransactionsRecheck from './apputils/AccountTransactionsRecheck' -import accountDS from '../../appstores/DataSource/Account/Account' -import settingsActions from '../../appstores/Stores/Settings/SettingsActions' -import appNewsDS from '../../appstores/DataSource/AppNews/AppNews' -import config from '@app/config/config' -import accountBalanceDS from '@app/appstores/DataSource/AccountBalance/AccountBalance' - -let CACHE_LAST_TIME = false -const CACHE_VALID_10MIN_TIME = 600000 // 10 minutes -let CACHE_WALLETS_HASH = {} -class UpdateAccountBalanceAndTransactionsHD { - - /** - * @param {string} callParams.source - * @param {boolean} callParams.force - * @returns {Promise} - */ - updateAccountBalanceAndTransactionsHD = async (callParams) => { - const source = callParams.source || 'FRONT' - const force = callParams.force || false - - if (!force || source === 'BACK') { - const setting = await settingsActions.getSetting('scannerCode') - if (setting === 'none') { - return false - } else if (CACHE_LAST_TIME && setting === '10min') { - const now = new Date().getTime() - const diff = now - CACHE_LAST_TIME - if (diff < CACHE_VALID_10MIN_TIME) { - Log.daemon('UpdateAccountBalanceAndTransactionsHD skipped by diff ' + diff) - return false - } - } - } - - try { - const params = { - force - } - if (source !== 'BACK') { - params.walletHash = await settingsActions.getSelectedWallet('UpdateAccountBalanceAndTransactionsHD') - } - - Log.daemon('UpdateAccountBalanceHD called ' + source + ' ' + JSON.stringify(params)) - - const walletPubs = await walletPubScanningDS.getWalletPubsForScan(params) - - if (!walletPubs || walletPubs.length === 0) return false - - this._logNews = {} - CACHE_WALLETS_HASH = {} - for (const walletPub of walletPubs) { - await this._walletRun(walletPub, source) - } - - if (this._logNews) { - let key - for (key in this._logNews) { - await appNewsDS.saveAppNews({ - onlyOne: true, walletHash: key, currencyCode: 'BTC', newsGroup: 'ONE_BY_ONE_SCANNER', newsName: 'HD_SCANNED_LAST_TIME', - newsJson: { log: this._logNews[key].substr(0, 50) + '...' } - }) - } - } - - CACHE_LAST_TIME = new Date().getTime() - } catch (e) { - Log.errDaemon('UpdateAccountBalanceHD balanceError ' + source + ' ' + e.message) - return false - } - return true - } - - /** - * @param {Object} walletPub - * @param {string} walletPub.id - * @param {string} walletPub.currencyCode - * @param {string} walletPub.walletHash - * @param {string} walletPub.walletPubType - * @param {string} walletPub.walletPubValue - * @param {string} walletPub.transactionsScanTime - * @param {string} walletPub.balance - * @param {string} walletPub.balanceFix - * @param {string} walletPub.balanceTxt - * @param {string} walletPub.unconfirmed - * @param {string} walletPub.unconfirmedFix - * @param {string} walletPub.unconfirmedTxt - * @param {string} walletPub.balanceProvider - * @param {string} walletPub.balanceScanTime - * @param {string} walletPub.balanceScanLog - * @param {string} source - * @returns {Promise} - * @private - */ - async _walletRun(walletPub, source) { - let newBalance = false - let balanceError = false - const addressToScan = walletPub.walletPubValue - if (config.debug.appErrors) { - console.log(new Date().toISOString() + ' UpdateAccountBalanceAndTransactionsHD newBalance started ' + walletPub.currencyCode + ' ' + addressToScan) - } - try { - Log.daemon('UpdateAccountBalanceAndTransactionsHD newBalance ' + walletPub.currencyCode + ' ' + addressToScan) - newBalance = await (BlocksoftBalances.setCurrencyCode(walletPub.currencyCode).setAddress(addressToScan).setWalletHash(walletPub.walletHash)).getBalance('AccountRunHD') - if (!newBalance || typeof newBalance.balance === 'undefined') { - balanceError = ' something wrong with balance ' + walletPub.currencyCode + ' ' + addressToScan + ' => ' + JSON.stringify(newBalance) - Log.daemon('UpdateAccountBalanceAndTransactionsHD newBalance something wrong ' + walletPub.currencyCode + ' ' + addressToScan + ' => ' + JSON.stringify(newBalance)) - if (config.debug.appErrors) { - console.log('UpdateAccountBalanceAndTransactionsHD newBalance error ' + balanceError) - } - } else { - balanceError = ' found in one ' + JSON.stringify(newBalance) - } - } catch (e) { - balanceError = ' found balanceError ' + e.message - if (config.debug.appErrors) { - console.log('UpdateAccountBalanceAndTransactionsHD newBalance error ' + balanceError) - } - } - - Log.daemon('UpdateAccountBalanceAndTransactionsHD newBalance loaded ' + walletPub.currencyCode + ' ' + addressToScan, JSON.stringify(newBalance)) - const updateObj = { - balanceScanTime: Math.round(new Date().getTime() / 1000) - } - if (newBalance) { - if (newBalance.balance * 1 !== walletPub.balance * 1 || newBalance.unconfirmed * 1 !== walletPub.unconfirmed * 1) { - if (typeof newBalance.specialMark !== 'undefined' && newBalance.specialMark === 'badServer' && walletPub.balance * 1 > 0) { - updateObj.balanceScanLog = 'badServer so not changed, old balance ' + walletPub.balance + ', ' + balanceError - if (typeof newBalance.provider !== 'undefined') { - updateObj.balanceProvider = newBalance.provider - } - } else { - updateObj.balanceFix = newBalance.balance // lets send to db totally not changed big number string - updateObj.balanceTxt = newBalance.balance // and string for any case - updateObj.unconfirmedFix = newBalance.unconfirmed || 0 // lets send to db totally not changed big number string - updateObj.unconfirmedTxt = newBalance.unconfirmed || '' // and string for any case - updateObj.balanceProvider = newBalance.provider - updateObj.balanceScanLog = 'all ok, new balance ' + newBalance.balance + ', old balance ' + walletPub.balance + ', ' + balanceError - - const logData = {} - logData.walletHash = walletPub.walletHash - logData.currencyCode = walletPub.currencyCode - logData.address = walletPub.walletPubValue - logData.addressShort = walletPub.walletPubValue ? walletPub.walletPubValue.slice(0, 10) : 'none' - logData.balanceScanTime = walletPub.balanceScanTime + '' - logData.balanceProvider = walletPub.balanceProvider + '' - logData.balance = walletPub.balance + '' - logData.newBalanceProvider = walletPub.newBalanceProvider + '' - logData.newBalance = (newBalance.balance * 1) + '' - MarketingEvent.setBalance(logData.walletHash, logData.currencyCode, logData.newBalance, logData) - } - } else { - updateObj.balanceScanLog = 'not changed, old balance ' + walletPub.balance + ', ' + balanceError - if (typeof newBalance.provider !== 'undefined') { - updateObj.balanceProvider = newBalance.provider - } - } - Log.daemon('UpdateAccountBalanceAndTransactionsHD newBalance okPrepared ' + walletPub.currencyCode + ' ' + walletPub.walletPubValue + ' new balance ' + newBalance.balance + ' provider ' + newBalance.provider + ' old balance ' + walletPub.balance, JSON.stringify(updateObj)) - } else { - updateObj.balanceScanLog = 'no balance, old balance ' + walletPub.balance + ', ' + balanceError - Log.daemon('UpdateAccountBalanceAndTransactions newBalance notPrepared ' + walletPub.currencyCode + ' ' + walletPub.walletPubValue + ' old balance ' + walletPub.balance, JSON.stringify(updateObj)) - } - - if (typeof CACHE_WALLETS_HASH[walletPub.walletHash] !== 'undefined') { - - const transactionUpdateObj = { - transactionsScanTime: Math.round(new Date().getTime() / 1000), - transactionsScanLog: new Date().toISOString() + ' transaction prev scanned by ' + CACHE_WALLETS_HASH[walletPub.walletHash] - } - if (walletPub.transactionsScanLog) { - transactionUpdateObj.transactionsScanLog += ' ' + walletPub.transactionsScanLog - } - await walletPubScanningDS.updateTransactions({ updateObj: transactionUpdateObj }, walletPub) - return false - } - CACHE_WALLETS_HASH[walletPub.walletHash] = walletPub.walletPubValue - - try { - if (typeof this._logNews[walletPub.walletHash] === 'undefined') { - this._logNews[walletPub.walletHash] = '' - } - this._logNews[walletPub.walletHash] += ' ' + walletPub.walletPubType + ' ' + updateObj.balanceScanLog - updateObj.balanceScanLog = new Date().toISOString() + ' ' + updateObj.balanceScanLog - if (walletPub.balanceScanLog) { - updateObj.balanceScanLog += ' ' + walletPub.balanceScanLog.substr(0, 1000) - } - await walletPubScanningDS.updateBalance({ updateObj }, walletPub) - } catch (e) { - e.message += ' while accountBalanceDS.updateAccountBalance' - throw e - } - - let transactionsError = ' ' - let newTransactions = false - - let addresses = await accountScanningDS.getAddresses({ currencyCode: walletPub.currencyCode, walletHash: walletPub.walletHash, withBalances : true }) - try { - const addressesBlockchain = await BlocksoftTransactions.getAddresses({ - account : { currencyCode : walletPub.currencyCode, address : walletPub.walletPubValue, walletHash : walletPub.walletHash}, - additional : { walletPub }, - withBalances : true - }, 'UpdateAccountBalanceAndTransactionsHD addressesBlockchain') - - const sql = [] - const derivations = [] - let count = 0 - if (addressesBlockchain) { - for (const address in addressesBlockchain) { - const path = addressesBlockchain[address].path - const balance = addressesBlockchain[address].balance - if (typeof addresses[address] !== 'undefined') { - if (addresses[address].balanceTxt !== balance) { - const updateObj = { - balanceScanTime: Math.round(new Date().getTime() / 1000), - balanceScanLog: ' newBalance ' + balance, - balanceScanError: '', - balanceTxt: balance - } - await accountBalanceDS.updateAccountBalance({ updateObj }, - { id : addresses[address].id, currencyCode : walletPub.currencyCode, address : walletPub.walletPubValue, walletHash : walletPub.walletHash} - ) - } - if (addresses[address].alreadyShown === 1) { - // do nothing - can log - } else { - sql.push(`'` + address + `'`) - } - } else { - if (path.toString().length < 2) continue - const tmp = { - address, - path, - alreadyShown: 1, - walletPubId: walletPub.id - } - count++ - derivations.push(tmp) - } - } - if (count > 0) { - await accountDS.discoverAccountsFromHD({ currencyCode : 'BTC', walletHash: walletPub.walletHash, source, derivations }, source) - addresses = await accountScanningDS.getAddresses({ currencyCode: walletPub.currencyCode, walletHash: walletPub.walletHash }) - } - } - if (sql.length > 0) { - await accountDS.massUpdateAccount(`address IN (` + sql.join(',') + ') AND (already_shown IS NULL OR already_shown=0)', 'already_shown=1') - } - } catch (e) { - if (config.debug.appErrors) { - console.log(' transactionsAddressesError ' + e.message) - } - transactionsError = ' found transactionsAddressesError ' + e.message - } - try { - Log.daemon('UpdateAccountBalanceAndTransactionsHD newTransactions ' + walletPub.currencyCode + ' ' + walletPub.walletPubValue) - newTransactions = await BlocksoftTransactions.getTransactions({ - account : { - currencyCode: walletPub.currencyCode, - address: walletPub.walletPubValue, - walletHash : walletPub.walletHash - }, - additional : { addresses, walletPub } - },'AccountRunHD') - - if (!newTransactions || newTransactions.length === 0) { - transactionsError += ' something wrong with balance ' + walletPub.currencyCode + ' ' + walletPub.walletPubValue + ' => ' + JSON.stringify(newTransactions) - Log.daemon('UpdateAccountBalanceAndTransactionsHD newTransactions something wrong ' + walletPub.currencyCode + ' ' + walletPub.walletPubValue + ' => ' + JSON.stringify(newTransactions)) - } else { - transactionsError += ' found transactions ' + newTransactions.length - } - } catch (e) { - if (config.debug.appErrors) { - console.log(' found transactionsError ' + e.message) - } - transactionsError += ' found transactionsError ' + e.message - } - - Log.daemon('UpdateAccountBalanceAndTransactionsHD newTransactions loaded ' + walletPub.currencyCode + ' ' + addressToScan) - - const transactionUpdateObj = await AccountTransactionsRecheck(newTransactions, walletPub, source) - - try { - transactionUpdateObj.transactionsScanLog = new Date().toISOString() + ' ' + transactionsError + ' ' + transactionUpdateObj.transactionsScanLog - if (walletPub.transactionsScanLog) { - transactionUpdateObj.transactionsScanLog += ' ' + walletPub.transactionsScanLog - } - await walletPubScanningDS.updateTransactions({ updateObj: transactionUpdateObj }, walletPub) - } catch (e) { - if (config.debug.appErrors) { - console.log(' walletPubScanningDS.updateWalletPub ' + e.message) - } - e.message += ' while walletPubScanningDS.updateWalletPub' - throw e - } - if (config.debug.appErrors) { - console.log(new Date().toISOString() + ' UpdateAccountBalanceAndTransactionsHD newBalance finished ' + walletPub.currencyCode + ' ' + addressToScan) - } - return true - } - -} - - -const singleton = new UpdateAccountBalanceAndTransactionsHD() -export default singleton diff --git a/src/daemons/back/UpdateAccountPendingTransactions.js b/src/daemons/back/UpdateAccountPendingTransactions.js deleted file mode 100644 index aaedb09cc..000000000 --- a/src/daemons/back/UpdateAccountPendingTransactions.js +++ /dev/null @@ -1,27 +0,0 @@ -/** - * @version 0.11 - */ -import Log from '@app/services/Log/Log' -import config from '@app/config/config' -import BlocksoftTransactions from '@crypto/actions/BlocksoftTransactions/BlocksoftTransactions' - -class UpdateAccountPendingTransactions { - - updateAccountPendingTransactions = async (callParams = {}) => { - const source = callParams.source || 'FRONT' - let result = false - try { - result = await (BlocksoftTransactions.getTransactionsPending({account : {currencyCode : 'TRX'}}, 'AccountRunPending from ' + source)) // only trx for now - } catch (e) { - if (config.debug.appErrors) { - console.log('UpdateAccountPendingTransactions error ' + source + ' ' + e.message, e) - } - Log.errDaemon('UpdateAccountPendingTransactions error ' + source + ' ' + e.message) - } - return result - } -} - - -const singleton = new UpdateAccountPendingTransactions() -export default singleton diff --git a/src/daemons/back/UpdateAppNewsDaemon.js b/src/daemons/back/UpdateAppNewsDaemon.js deleted file mode 100644 index 46f9912e1..000000000 --- a/src/daemons/back/UpdateAppNewsDaemon.js +++ /dev/null @@ -1,182 +0,0 @@ -/** - * @version 0.43 - */ -import Log from '@app/services/Log/Log' -import ApiProxy from '@app/services/Api/ApiProxy' -import config from '@app/config/config' - -import appNewsDS from '@app/appstores/DataSource/AppNews/AppNews' -import cryptoWalletsDS from '@app/appstores/DataSource/CryptoWallets/CryptoWallets' -import appNewsInitStore from '@app/appstores/Stores/AppNews/AppNewsInitStore' -import store from '@app/store' -import settingsActions from '@app/appstores/Stores/Settings/SettingsActions' - -let CACHE_NEWS_HASH = '' -let CACHE_LAST_TIME = false -const CACHE_VALID_TIME = 120000 // 2 minute - -class UpdateAppNewsDaemon { - - _canUpdate = true - - _goToNotifications = false - - goToNotifications = (code) => { - if (this._goToNotifications === 'INITED_APP') return false // its final status - this._goToNotifications = code - } - - isGoToNotifications = (code) => { - if (!this._goToNotifications) return false - return this._goToNotifications === code - } - - /** - * @return {Promise} - */ - updateAppNewsDaemon = async (params = {}, dataUpdate = false) => { - if (typeof params === 'undefined' || typeof params.force === 'undefined' || !params) { - if (!this._canUpdate) { - return false - } - const now = new Date().getTime() - const diff = now - CACHE_LAST_TIME - if (diff < CACHE_VALID_TIME) { - Log.daemon('UpdateAppNews skipped by diff ' + diff) - return false - } - } - this._canUpdate = false - - const walletHash = await settingsActions.getSelectedWallet('UpdateNewsDaemon') - let res - let asked = false - if (!dataUpdate) { - if (config.debug.appErrors) { - console.log(new Date().toISOString() + ' UpdateNewsDaemon loading new') - } - asked = true - try { - res = await ApiProxy.getAll({...params, source: 'UpdateAppNewsDaemon.updateAppNews' }) - } catch (e) { - this._canUpdate = true - return false - } - } else { - res = dataUpdate - } - - if (store.getState().appNewsStore.appNewsList.length === 0) { - await appNewsInitStore() - } - - if (!res || typeof res === 'undefined' || typeof res.news === 'undefined' || !res.news || res.news.length === 0) { - this._canUpdate = true - return false - } - if (res.newsHash === CACHE_NEWS_HASH) { - // can put log for recheck hashing cache - this._canUpdate = true - return false - } - - if (!asked) { - if (config.debug.appErrors) { - console.log(new Date().toISOString() + ' UpdateNewsDaemon loaded proxy') - } - } - - CACHE_NEWS_HASH = typeof res.newsHash !== 'undefined' ? res.newsHash : '' - - const keys = { - currencyCode: 'currencyCode', - newsSource: 'source', - newsGroup: 'group', - newsPriority: 'priority', - newsName: 'name', - newsJson: 'data', - newsCustomTitle: 'title', - newsCustomText: 'text', - newsImage: 'image', - newsUrl: 'url', - newsCustomCreated: 'createdAt', - newsUniqueKey: 'serverId', - newsServerId: 'serverId', - newsServerHash: 'status' - } - let savedAny = false - const allNews = store.getState().appNewsStore.appNewsList - const allNewsIndexed = {} - if (allNews.length) { - for (const news of allNews) { - allNewsIndexed[news.newsServerId] = { - newsName : news.newsName, - newsServerHash: news.newsServerHash - } - } - } - - try { - let index = 0 - for (const row of res.news) { - if (index > 2) { - // break - } - index++ - const toSave = { - newsNeedPopup: row.needPopup ? 1 : 0, - newsLog: new Date().toISOString() + ' loaded from Server' - } - for (const saveField in keys) { - const serverField = keys[saveField] - if (typeof row[serverField] !== 'undefined' && row[serverField]) { - toSave[saveField] = row[serverField] - } - if (typeof row[serverField] !== 'undefined' && row[serverField]) { - toSave[saveField] = row[serverField] - } - } - let fromStoreStatus = 'no_cache' - let fromStore = false - if (typeof row.isBroadcast === 'undefined' || row.isBroadcast === false) { - if (row.newsGroup !== 'GOOGLE_EVENTS' && typeof allNewsIndexed[toSave.newsUniqueKey] !== 'undefined') { - fromStore = allNewsIndexed[toSave.newsUniqueKey] - fromStoreStatus = 'check_update_ind' - // not loaded - } - toSave.walletHash = walletHash - } else if (typeof allNewsIndexed[toSave.newsUniqueKey] !== 'undefined') { - fromStore = allNewsIndexed[toSave.newsUniqueKey] - fromStoreStatus = 'check_update' - } else { - fromStoreStatus = 'can_insert' - } - if (typeof row.status !== 'undefined' && row.status && row.status.toString() === '33') { - toSave.removed = 33 - } - const saved = await appNewsDS.saveAppNews(toSave, fromStoreStatus, fromStore) - if (saved.updated) { - savedAny = true - } else { - await appNewsDS.pushAppNewsForApi(saved) - } - } - CACHE_LAST_TIME = new Date().getTime() - } catch (e) { - this._canUpdate = true - Log.err('UpdateAppNews saving result error ' + e.message) - } - - if (savedAny || allNews.length === 0) { - try { - await appNewsInitStore() - } catch (e) { - Log.err('UpdateAppNews appNewsInitStore call error ' + e.message) - } - } - - this._canUpdate = true - } -} - -export default new UpdateAppNewsDaemon diff --git a/src/daemons/back/UpdateAppTasksDaemon.js b/src/daemons/back/UpdateAppTasksDaemon.js deleted file mode 100644 index ac667adde..000000000 --- a/src/daemons/back/UpdateAppTasksDaemon.js +++ /dev/null @@ -1,62 +0,0 @@ -/** - * @version 0.11 - * @todo remove as deprecated if will be not shown - */ -import Log from '../../services/Log/Log' - -import appTaskDoingDS from '../../appstores/DataSource/AppTask/AppTaskDoing' - -import AppTasksDiscoverHD from './apptasks/AppTasksDiscoverHD' -import AppTasksDiscoverBalancesHidden from './apptasks/AppTasksDiscoverBalancesHidden' -import AppTasksDiscoverBalancesNotAdded from './apptasks/AppTasksDiscoverBalancesNotAdded' - - -class UpdateAppTasksDaemon { - - /** - * @param {string} params.taskName - * @return {Promise} - */ - updateAppTasksDaemon = async (params) => { - return false - Log.daemon('UpdateAppTaskDaemon called') - - const appTasks = await appTaskDoingDS.getTasksForRun(params) - if (!appTasks) { - return false - } - let appTask - for (appTask of appTasks) { - try { - Log.daemon('UpdateAppTaskDaemon started #' + appTask.id + ' ' + appTask.taskName, appTask) - await appTaskDoingDS.setStarted(appTask) - let log - switch (appTask.taskName) { - case 'DISCOVER_HD': - log = await AppTasksDiscoverHD.run(appTask) - break - case 'DISCOVER_BALANCES_HIDDEN': - log = await AppTasksDiscoverBalancesHidden.run(appTask) - break - case 'DISCOVER_BALANCES_NOT_ADDED': - log = await AppTasksDiscoverBalancesNotAdded.run(appTask) - break - default: - Log.errDaemon('UpdateAppTask unknown name ' + appTask.taskName, appTask) - break - } - if (log) { - appTask.taskLog = log - } - Log.daemon('UpdateAppTaskDaemon finished #' + appTask.id + ' ' + appTask.taskName + ' Log ' + log, appTask) - await appTaskDoingDS.setFinished(appTask) - } catch (e) { - Log.errDaemon('UpdateAppTaskDaemon finished #' + appTask.id + ' ' + appTask.taskName + ' error ' + e.message, appTask) - } - } - - Log.daemon('UpdateAppTaskDaemon finished') - } -} - -export default new UpdateAppTasksDaemon diff --git a/src/daemons/back/UpdateCardsDaemon.js b/src/daemons/back/UpdateCardsDaemon.js deleted file mode 100644 index f56c5d87c..000000000 --- a/src/daemons/back/UpdateCardsDaemon.js +++ /dev/null @@ -1,188 +0,0 @@ -/** - * @version 0.41 - */ -import Log from '@app/services/Log/Log' - -import config from '@app/config/config' - -import cardDS from '@app/appstores/DataSource/Card/Card' -import cryptoWalletsDS from '@app/appstores/DataSource/CryptoWallets/CryptoWallets' -import ApiProxy from '@app/services/Api/ApiProxy' -import settingsActions from '@app/appstores/Stores/Settings/SettingsActions' - -class UpdateCardsDaemon { - - _canUpdate = true - - /** - * @string params.numberCard - * @return {Promise} - */ - updateCardsDaemon = async (params = {}, dataUpdate = false) => { - if (typeof params !== 'undefined' && params && typeof params.force === 'undefined' || !params.force) { - if (!this._canUpdate && !dataUpdate) return false - } - - this._canUpdate = false - const res = await this._updateCardsDaemon(params, dataUpdate) - this._canUpdate = true - - return res - } - - _updateCardsDaemon = async (params, dataUpdate = false) => { - - Log.daemon('UpdateCardsDaemon called') - - let asked = false - if (!dataUpdate) { - const authHash = await settingsActions.getSelectedWallet('UpdateCardsDaemon') - if (!authHash) { - Log.daemon('UpdateCardsDaemon skipped as no auth') - return false - } - if (config.debug.appErrors) { - console.log(new Date().toISOString() + ' UpdateCardsDaemon loading new') - } - asked = true - try { - dataUpdate = await ApiProxy.getAll({ ...params, source: 'UpdateCardsDaemon.updateCards' }) - } catch (e) { - if (config.debug.appErrors) { - console.log('UpdateCardsDaemon error ' + e.message) - } - return false - } - } - - if (!dataUpdate) { - return false - } - - if (typeof dataUpdate.forCardsAll !== 'undefined' && dataUpdate.forCardsAll) { - if (!asked) { - if (config.debug.appErrors) { - console.log(new Date().toISOString() + ' UpdateCardsDaemon loaded proxy forCardsAll') - } - } - try { - const saved = await cardDS.getCards() - const cardsSaved = {} - if (saved) { - for (const row of saved) { - cardsSaved[row.number] = row - if (row.cardToSendId > 0) { - cardsSaved['sid_' + row.cardToSendId] = row - } - } - } - for (const number in dataUpdate.forCardsAll) { - const dataOne = dataUpdate.forCardsAll[number] - if (!dataOne) continue - const mapping = { - cardToSendStatus: dataOne.card_to_send_status, - cardToSendId: dataOne.card_to_send_id, - number: dataOne.card_number, - expirationDate: dataOne.card_expiration_date, - type: dataOne.card_type, - countryCode: dataOne.card_country_code, - cardName: dataOne.card_name, - cardHolder: dataOne.card_holder, - currency: dataOne.card_currency, - walletHash: dataOne.card_wallet_hash, - verificationServer: dataOne.card_verification_server, - cardEmail: dataOne.card_email, - cardDetailsJson: dataOne.card_details_json, - cardVerificationJson: dataOne.card_verification_json, - cardCreateWalletHash: dataOne.log_wallet - } - - let currentToUpdate = false - if (typeof cardsSaved[number] === 'undefined' || !cardsSaved[number]) { - if (typeof dataOne.card_to_send_id === 'undefined' || !dataOne.card_to_send_id - || typeof cardsSaved['sid_' + dataOne.card_to_send_id] === 'undefined' || !cardsSaved['sid_' + dataOne.card_to_send_id] - ) { - // do nothing to insert - } else { - currentToUpdate = cardsSaved['sid_' + dataOne.card_to_send_id] - } - } else { - currentToUpdate = cardsSaved[number] - } - - if (currentToUpdate === false && typeof currentToUpdate !== 'undefined') { - await cardDS.saveCard({ - insertObjs: [mapping] - }) - } else { - if (currentToUpdate.cardToSendStatus && currentToUpdate.cardToSendStatus * 1 > dataOne.card_to_send_status * 1) { - // skip - } else { - const updateObj = { - cardVerificationJson: dataOne.card_verification_json - } - for (const key in mapping) { - if (key === 'cardCreateWalletHash') continue // to skip - if (currentToUpdate[key] !== mapping[key]) { - updateObj[key] = mapping[key] - } - } - const dataForSave = { - key: { - id : currentToUpdate.id - }, - updateObj - } - await cardDS.updateCard(dataForSave) - } - } - } - - } catch (e) { - if (config.debug.appErrors) { - console.log('UpdateCardsDaemon save error1 ' + e.message, e) - } - Log.errDaemon('UpdateCardsDaemon save error1 ' + e.message) - return false - } - } else if (typeof dataUpdate.forCardsOk !== 'undefined' && dataUpdate.forCardsOk) { - if (!asked) { - console.log(new Date().toISOString() + ' UpdateCardsDaemon loaded proxy forCardsOk', JSON.stringify(params)) - } - try { - for (const number in dataUpdate.forCardsOk) { - const dataOne = dataUpdate.forCardsOk[number] - if (!dataOne) continue - const dataForSave = { - key: { - number - }, - updateObj: { - cardVerificationJson: JSON.stringify(dataOne) - } - } - await cardDS.updateCard(dataForSave) - } - } catch (e) { - if (config.debug.appErrors) { - console.log('UpdateCardsDaemon status save error2 ' + e.message) - } - Log.errDaemon('UpdateCardsDaemon status save error2 ' + e.message) - return false - } - } - - if (typeof params !== 'undefined' && params && params.numberCard !== 'undefined') { - if (dataUpdate && dataUpdate.forCardsOk && typeof dataUpdate.forCardsOk[params.numberCard] !== 'undefined' && dataUpdate.forCardsOk[params.numberCard]) { - return dataUpdate.forCardsOk[params.numberCard] - } else { - return false - } - } - - return false - } - -} - -export default new UpdateCardsDaemon diff --git a/src/daemons/back/UpdateCashBackDataDaemon.js b/src/daemons/back/UpdateCashBackDataDaemon.js deleted file mode 100644 index 6c9d3301b..000000000 --- a/src/daemons/back/UpdateCashBackDataDaemon.js +++ /dev/null @@ -1,104 +0,0 @@ -/** - * @version 0.42 - */ -import Log from '@app/services/Log/Log' - -import cashBackActions from '@app/appstores/Stores/CashBack/CashBackActions' -import CashBackUtils from '@app/appstores/Stores/CashBack/CashBackUtils' -import ApiProxy from '@app/services/Api/ApiProxy' - -import config from '@app/config/config' -import { StreamSupportActions } from '@app/appstores/Stores/StreamSupport/StreamSupportStoreActions' - -class UpdateCashBackDataDaemon { - - _canUpdate = true - - /** - * @return {Promise} - */ - updateCashBackDataDaemon = async (params = {}, dataUpdate = false) => { - if (!this._canUpdate) return false - - this._canUpdate = false - - let data = false - let asked = false - let cashbackToken = false - if (!dataUpdate) { - if (config.debug.appErrors) { - console.log(new Date().toISOString() + ' UpdateCashBackDataDaemon loaded new') - } - asked = true - try { - data = await ApiProxy.getAll({ ...params, source: 'UpdateCashBackDataDaemon.updateCashBackData' }) - } catch (e) { - if (config.debug.appErrors) { - console.log('UpdateCashBackDataDaemon error ' + e.message) - } - await cashBackActions.updateAll({ error : { - title: e.message, - time: new Date().getTime() - }}) - this._canUpdate = true - return - } - } else { - data = dataUpdate - } - - if (typeof data.cbChatToken !== 'undefined' && data.cbChatToken ) { - StreamSupportActions.setData(data.cbChatToken) - } - - let customToken = CashBackUtils.getWalletToken() - try { - if (typeof data.cbData !== 'undefined' && typeof data.cbData.data !== 'undefined') { - if (typeof data.cashbackToken !== 'undefined') { - cashbackToken = data.cashbackToken // general cashback token of ask!!!! - } - data = data.cbData.data - if (typeof data !== 'undefined' && typeof data.cashbackToken !== 'undefined') { - customToken = data.cashbackToken - } - } else { - this._canUpdate = true - return - } - } catch (e) { - if (config.debug.appErrors) { - console.log('UpdateCashBackDataDaemon error ' + e.message) - } - await cashBackActions.updateAll({error : { - title: e.message, - time: new Date().getTime() - }}) - this._canUpdate = true - return - } - - if (!asked) { - if (config.debug.appErrors) { - console.log(new Date().toISOString() + ' UpdateCashBackDataDaemon loaded proxy') - } - } - - try { - Log.daemon('UpdateCashBackDataDaemon result ', data) - data.time = new Date().getTime() - data.cashbackToken = cashbackToken - data.customToken = typeof data.customToken !== 'undefined' && data.customToken ? data.customToken : customToken - await CashBackUtils.setCashBackDataFromApi(data) - } catch (e) { - if (config.debug.appErrors) { - console.log('UpdateCashBackDataDaemon result error ' + e.message ) - } - this._canUpdate = true - Log.err('UpdateCashBackDataDaemon result error ' + e.message) - } - this._canUpdate = true - } - -} - -export default new UpdateCashBackDataDaemon diff --git a/src/daemons/back/UpdateCurrencyRateDaemon.js b/src/daemons/back/UpdateCurrencyRateDaemon.js deleted file mode 100644 index 841f3e168..000000000 --- a/src/daemons/back/UpdateCurrencyRateDaemon.js +++ /dev/null @@ -1,122 +0,0 @@ -/** - * @version 0.41 - */ - -import Log from '@app/services/Log/Log' -import ApiRates from '@app/services/Api/ApiRates' - -import currencyActions from '@app/appstores/Stores/Currency/CurrencyActions' -import currencyDS from '@app/appstores/DataSource/Currency/Currency' -import store from '@app/store' -import UpdateAccountListDaemon from '@app/daemons/view/UpdateAccountListDaemon' - -const CACHE_SAVED = {} - -class UpdateCurrencyRateDaemon { - - /** - * @return {Promise} - */ - updateCurrencyRate = async (params, dataUpdate = false) => { - Log.daemon('UpdateCurrencyRateDaemon started ' + params.source) - - const res = await ApiRates.getRates(params, dataUpdate) - if (!res || typeof res.cryptoCurrencies === 'undefined') { - return [] - } - - const currencies = store.getState().currencyStore.cryptoCurrencies - if (typeof currencies === 'undefined' || !currencies || currencies.length === 0) { - Log.daemon('UpdateCurrencyRateDaemon warning - no currencies') - return false - } - - const indexed = {} - let row = false - try { - for (row of res.cryptoCurrencies) { - if (typeof row === 'undefined' || !row) continue - if (typeof row.tokenAddress !== 'undefined' && row.tokenAddress) { - indexed['token_' + row.tokenAddress.toUpperCase()] = row - } - if (typeof row.currencyCode !== 'undefined') { - indexed[row.currencyCode] = row - } - } - } catch (e) { - e.message += ' in indexing cryptoCurrencies ' + (row ? JSON.stringify(row) : ' no row') - throw e - } - - const scanned = res.scanned - const updatedCurrencies = [] - try { - for (const dbCurrency of currencies) { - let currency = false - - if (typeof dbCurrency.tokenAddress !== 'undefined' && dbCurrency.tokenAddress) { - if (typeof indexed['token_' + dbCurrency.tokenAddress.toUpperCase()] !== 'undefined') { - currency = indexed['token_' + dbCurrency.tokenAddress.toUpperCase()] - } - } - - if (!currency) { - if (dbCurrency.currencyCode.indexOf('CUSTOM_') === 0) { - if (typeof dbCurrency.tokenName !== 'undefined' && typeof indexed[dbCurrency.tokenName] !== 'undefined') { - currency = indexed[dbCurrency.tokenName] - } - } else { - if (typeof dbCurrency.ratesCurrencyCode !== 'undefined' && dbCurrency.ratesCurrencyCode) { - if (typeof indexed[dbCurrency.ratesCurrencyCode] !== 'undefined') { - currency = indexed[dbCurrency.ratesCurrencyCode] - } - } else { - if (typeof indexed[dbCurrency.currencyCode] !== 'undefined') { - currency = indexed[dbCurrency.currencyCode] - } - } - } - } - - if (!currency) { - Log.daemon('UpdateCurrencyRateDaemon warning - no currency rate for ' + dbCurrency.currencyCode) - continue - } - if (typeof CACHE_SAVED[dbCurrency.currencyCode] !== 'undefined' && CACHE_SAVED[dbCurrency.currencyCode] === currency.currencyRateScanTime) { - continue - } - const updateObj = { - currencyRateUsd: currency.currencyRateUsd, - currencyRateJson: currency.currencyRateJson, - priceProvider: currency.priceProvider, - priceChangePercentage24h: currency.priceChangePercentage24h, - priceLastUpdated: currency.priceLastUpdated, - currencyRateScanTime: scanned - } - updatedCurrencies.push({ - ...updateObj, - currencyCode: dbCurrency.currencyCode - }) - - CACHE_SAVED[dbCurrency.currencyCode] = updateObj.currencyRateScanTime - await currencyDS.updateCurrency({ updateObj, key: { currencyCode: dbCurrency.currencyCode } }) - } - } catch (e) { - e.message += ' in res.cryptoCurrencies' - throw e - } - - try { - if (updatedCurrencies.length) { - await currencyActions.updateCryptoCurrencies(updatedCurrencies) - await UpdateAccountListDaemon.updateAccountListDaemon({force: true, source: 'UpdateCurrencyRateDaemon'}) - } - } catch (e) { - e.message += 'in setCryptoCurrencies' - throw e - } - } - -} - -export default new UpdateCurrencyRateDaemon diff --git a/src/daemons/back/UpdateOneByOneDaemon.js b/src/daemons/back/UpdateOneByOneDaemon.js deleted file mode 100644 index 81e1eedf8..000000000 --- a/src/daemons/back/UpdateOneByOneDaemon.js +++ /dev/null @@ -1,140 +0,0 @@ -/** - * @version 0.11 - */ -import Update from '../Update' - -import UpdateAccountBalanceAndTransactions from '@app/daemons/back/UpdateAccountBalanceAndTransactions' -import UpdateAccountBalanceAndTransactionsHD from '@app/daemons/back/UpdateAccountBalanceAndTransactionsHD' -import UpdateAccountPendingTransactions from '@app/daemons/back/UpdateAccountPendingTransactions' -import UpdateAppNewsDaemon from '@app/daemons/back/UpdateAppNewsDaemon' - -import Log from '@app/services/Log/Log' -import settingsActions from '@app/appstores/Stores/Settings/SettingsActions' -import Database from '@app/appstores/DataSource/Database' - -const STEPS_ORDER = [ - 'UPDATE_PROXIED', - 'UPDATE_ACCOUNT_PENDING_DAEMON', - 'UPDATE_ACCOUNT_BALANCES_DAEMON', - 'UPDATE_ACCOUNT_PENDING_DAEMON', - 'UPDATE_ACCOUNT_BALANCES_HD_DAEMON', - 'UPDATE_PROXIED', - 'UPDATE_ACCOUNT_PENDING_DAEMON', - 'UPDATE_ACCOUNT_BALANCES_DAEMON', - 'UPDATE_ACCOUNT_PENDING_DAEMON', - 'UPDATE_ACCOUNT_BALANCES_DAEMON', -] - -let CACHE_PAUSE = 0 - -const CACHE_TIMES = {} - -let CACHE_STOPPED = false - -const CACHE_VALID_TIME = { - 'PAUSE' : 60000, // 60 seconds - 'UPDATE_ACCOUNT_PENDING_DAEMON' : 30000, // 30 seconds - 'UPDATE_PROXIED' : 120000, // 120 seconds - 'UPDATE_ACCOUNT_BALANCES_DAEMON': 10000, // 10 sec - 'UPDATE_ACCOUNT_BALANCES_DAEMON_ALL': 100000, // 100 sec - 'UPDATE_ACCOUNT_BALANCES_HD_DAEMON': 30000 // 30 sec -} - -class UpdateOneByOneDaemon extends Update { - - _currentStep = 0 - - constructor(props) { - super(props) - this.updateFunction = this.updateOneByOneDaemon - this._canUpdate = true - } - - init = async () => { - // nothing - } - - stop = () => { - CACHE_STOPPED = true - } - - unstop = () => { - CACHE_STOPPED = false - } - - pause = () => { - CACHE_PAUSE = new Date().getTime() - } - - unpause = () => { - CACHE_PAUSE = 0 - } - - updateOneByOneDaemon = async (params, level = 0) => { - - await Database.checkVersion() - - if (CACHE_STOPPED) { - return false - } - - const tmpAuthHash = await settingsActions.getSelectedWallet('updateOneByOneDaemon') - if (!tmpAuthHash) { - return false - } - - const source = params.source || 'FRONT' - if (!this._canUpdate) { - return false - } - const now = new Date().getTime() - if (CACHE_PAUSE > 0 && now - CACHE_PAUSE < CACHE_VALID_TIME.PAUSE) { - return false - } - this._canUpdate = false - - try { - this._currentStep++ - if (this._currentStep >= STEPS_ORDER.length) { - this._currentStep = 0 - } - const step = STEPS_ORDER[this._currentStep] - if (typeof CACHE_TIMES[step] !== 'undefined' && now - CACHE_TIMES[step] < CACHE_VALID_TIME[step]) { - // console.log(new Date().toISOString() + ' ' + this._currentStep + ' skipped ' + step) - this._canUpdate = true - if (level < 10) { - await this.updateOneByOneDaemon(params, level + 1) - } - return - } - // console.log(new Date().toISOString() + ' ' + this._currentStep + ' step in ' + step) - CACHE_TIMES[step] = now - switch (step) { - case 'UPDATE_PROXIED' : - await UpdateAppNewsDaemon.updateAppNewsDaemon({source}) - break - case 'UPDATE_ACCOUNT_PENDING_DAEMON': - await UpdateAccountPendingTransactions.updateAccountPendingTransactions({source}) - break - case 'UPDATE_ACCOUNT_BALANCES_DAEMON': - await UpdateAccountBalanceAndTransactions.updateAccountBalanceAndTransactions({ source }) - break - case 'UPDATE_ACCOUNT_BALANCES_DAEMON_ALL': - await UpdateAccountBalanceAndTransactions.updateAccountBalanceAndTransactions({ source, allWallets : true }) - break - case 'UPDATE_ACCOUNT_BALANCES_HD_DAEMON': - await UpdateAccountBalanceAndTransactionsHD.updateAccountBalanceAndTransactionsHD({ source }) - break - default: - // do nothing - break - } - } catch (e) { - Log.errDaemon('UpdateOneByOne error ' + e.message) - } - - this._canUpdate = true - } -} - -export default new UpdateOneByOneDaemon() diff --git a/src/daemons/back/UpdateTradeOrdersDaemon.js b/src/daemons/back/UpdateTradeOrdersDaemon.js deleted file mode 100644 index 95c4b1830..000000000 --- a/src/daemons/back/UpdateTradeOrdersDaemon.js +++ /dev/null @@ -1,516 +0,0 @@ -/** - * @version 0.30 - */ - -import Database from '@app/appstores/DataSource/Database'; - -import Log from '@app/services/Log/Log'; -import ApiV3 from '@app/services/Api/ApiV3'; -import ApiProxy from '@app/services/Api/ApiProxy'; - -import config from '@app/config/config'; -import store from '@app/store'; -import NavStore from '@app/components/navigation/NavStore'; -import BlocksoftPrettyNumbers from '@crypto/common/AirDAOPrettyNumbers'; -import BlocksoftUtils from '@crypto/common/AirDAOUtils'; -import settingsActions from '@app/appstores/Stores/Settings/SettingsActions'; - -const { dispatch } = store; - -const CACHE_VALID_TIME = 20000; // 2 minute -let CACHE_LAST_TIME = false; -let TRY_COUNTER = 0; - -let CACHE_ORDERS_HASH = ''; -const CACHE_ONE_ORDER = {}; - -const LIMIT_FOR_CURRENCY = 20; - -class UpdateTradeOrdersDaemon { - getSavedOrdersHash = () => { - return CACHE_ORDERS_HASH; - }; - - fromApi = async (walletHash, orderHash) => { - const now = new Date().getTime(); - if (typeof CACHE_ONE_ORDER[orderHash] !== 'undefined') { - const diff = now - CACHE_ONE_ORDER[orderHash].now; - if (diff < CACHE_VALID_TIME) { - Log.daemon( - 'UpdateTradeOrders.fromApi ' + orderHash + ' skipped by diff ' + diff - ); - return CACHE_ONE_ORDER[orderHash].one; - } - } - - Log.daemon('UpdateTradeOrders.fromApi ' + orderHash + ' loading start'); - - try { - const tmpTradeOrdersV3 = await ApiV3.getExchangeOrders(walletHash); - if ( - typeof tmpTradeOrdersV3 !== 'undefined' && - tmpTradeOrdersV3 && - tmpTradeOrdersV3.length > 0 - ) { - for (const one of tmpTradeOrdersV3) { - if (one.orderHash === orderHash) { - CACHE_ONE_ORDER[orderHash] = { one, now }; - return one; - } - } - } - } catch (e) { - if (config.debug.appErrors) { - console.log( - 'UpdateTradeOrdersDaemon.fromApi error ' + e.message + ' tmpOneOrder' - ); - } - throw new Error(e.message + ' tmpOneOrder'); - } - return false; - }; - - fromDB = async () => { - // do nothing - }; - - removeId = async (removeId) => { - Log.daemon('UpdateTradeOrders removeId ' + removeId); - try { - const nowAt = new Date().toISOString(); - const found = await Database.query( - ` SELECT id, hidden_at FROM transactions WHERE bse_order_id = '${removeId}' `, - true - ); - if (found && found.array && found.array.length > 0) { - const row = found.array[0]; - if ( - !row.hidden_at || - row.hidden_at === 'null' || - row.hidden_at === '' - ) { - const sql = ` UPDATE transactions SET hidden_at='${nowAt}' WHERE bse_order_id = '${removeId}' `; - await Database.query(sql, true); - } - } - - const account = store.getState().mainStore.selectedAccount; - const { transactionsToView } = - store.getState().mainStore.selectedAccountTransactions; - if (account) { - let found = false; - const newTransactions = []; - if (transactionsToView) { - for (const transaction of transactionsToView) { - if ( - transaction.bseOrderData && - typeof transaction.bseOrderData.orderId !== 'undefined' && - transaction.bseOrderData.orderId == removeId - ) { - found = true; - } else { - newTransactions.push(transaction); - } - } - } - if (found) { - dispatch({ - type: 'SET_SELECTED_ACCOUNT_TRANSACTIONS', - selectedAccountTransactions: { - transactionsToView: newTransactions, - transactionsLoaded: new Date().getTime() - } - }); - } - } - NavStore.goBack(); - } catch (e) { - Log.errDaemon( - 'UpdateTradeOrders removeId ' + removeId + ' error ' + e.message - ); - } - }; - - /** - * @param params.force - * @param params.source - * @returns {Promise} - */ - updateTradeOrdersDaemon = async (params, dataUpdate = false) => { - if ( - typeof params.source !== 'undefined' && - params.source === 'ACCOUNT_OPEN' && - CACHE_LAST_TIME - ) { - const now = new Date().getTime(); - const diff = now - CACHE_LAST_TIME; - if (diff < CACHE_VALID_TIME) { - Log.daemon('UpdateTradeOrders skipped by diff ' + diff); - return false; - } - } - - Log.daemon('UpdateTradeOrders called ' + JSON.stringify(params)); - - const walletHash = await settingsActions.getSelectedWallet( - 'UpdateTradeOrdersDaemon' - ); - if (!walletHash) { - return false; - } - - const nowAt = new Date().toISOString(); - try { - let res = false; - let asked = false; - if (!dataUpdate) { - if (config.debug.appErrors) { - console.log( - new Date().toISOString() + ' UpdateTradeOrdersDaemon loading new' - ); - } - asked = true; - res = await ApiProxy.getAll({ - source: 'UpdateTradeOrdersDaemon.updateTradeOrders' - }); - if (config.debug.appErrors) { - console.log( - new Date().toISOString() + - ' UpdateTradeOrdersDaemon loaded new finished' - ); - } - } else { - res = dataUpdate; - } - - const tmpTradeOrders = - typeof res !== 'undefined' && typeof res.cbOrders !== 'undefined' - ? res.cbOrders - : false; - - /* - sometimes transaction hash should be unified with order status - if (typeof res.cbOrdersHash !== 'undefined' && (res.cbOrdersHash === CACHE_ORDERS_HASH || typeof params.removeId === 'undefined')) { - return false - } - */ - - try { - if ( - typeof tmpTradeOrders === 'undefined' || - !tmpTradeOrders || - !tmpTradeOrders.length - ) { - return false; - } - - if (!asked) { - if (config.debug.appErrors) { - console.log( - new Date().toISOString() + ' UpdateTradeOrdersDaemon loaded proxy' - ); - } - } - - let item; - - const index = {}; - let total = 0; - - for (item of tmpTradeOrders) { - if (total > 100) { - break; - } - if ( - typeof item.orderId === 'undefined' || - !item.orderId || - item.orderId === '' || - item.orderId === 'undefined' - ) { - continue; - } - - if (typeof item.uiApiVersion === 'undefined') { - item.uiApiVersion = 'v2'; - } - - try { - const tmps = []; - if (item.exchangeWayType !== 'BUY') { - tmps.push({ - currencyCode: item.requestedInAmount.currencyCode || false, - addressAmount: item.requestedInAmount.amount || 0, - addressTo: - typeof item.depositAddress !== 'undefined' - ? item.depositAddress - : false, - updateHash: item.inTxHash || false, - suffix: 'in' - }); - } - if (item.exchangeWayType !== 'SELL') { - tmps.push({ - currencyCode: item.requestedOutAmount.currencyCode || false, - addressAmount: item.requestedOutAmount.amount || 0, - addressTo: - typeof item.outDestination !== 'undefined' && - item.outDestination && - item.outDestination.toString().indexOf('*') === -1 - ? item.outDestination - : false, - updateHash: item.outTxHash || false, - suffix: 'out' - }); - } - - let currencyCode = 'NONE'; - let someIsUpdatedAllWillUpdate = false; - - for (const tmp of tmps) { - if (!tmp.currencyCode) continue; - if (typeof index[tmp.currencyCode] === 'undefined') { - index[tmp.currencyCode] = 1; - } else { - index[tmp.currencyCode]++; - } - if (index[tmp.currencyCode] < LIMIT_FOR_CURRENCY) { - someIsUpdatedAllWillUpdate = true; - } - } - - if (!someIsUpdatedAllWillUpdate) continue; - - const savedToTx = {}; - for (const tmp of tmps) { - if (!tmp.currencyCode) continue; - currencyCode = tmp.currencyCode; - let sql; - let sqlUpdateDir = ''; - let noHash = true; - if ( - tmp.updateHash && - tmp.updateHash !== '' && - tmp.updateHash !== 'null' - ) { - noHash = false; - sqlUpdateDir = `bse_order_id_${tmp.suffix}='${item.orderId}', bse_order_id='${item.orderId}', `; - sql = ` - SELECT id, bse_order_data, transaction_hash, transactions_scan_log, hidden_at FROM transactions - WHERE (transaction_hash='${tmp.updateHash}' OR bse_order_id='${item.orderId}') - AND currency_code='${tmp.currencyCode}' - `; - } else { - sql = ` - SELECT id, bse_order_data, transaction_hash, transactions_scan_log, hidden_at FROM transactions - WHERE bse_order_id='${item.orderId}' AND currency_code='${tmp.currencyCode}' - `; - } - - let found = await Database.query(sql, true); - - if ( - !found || - !found.array || - found.array.length === 0 || - found.array[0].transaction_hash === '' - ) { - if ( - typeof item.searchHashByAmount !== 'undefined' && - item.searchHashByAmount && - noHash - ) { - Log.daemon( - 'UpdateTradeOrders called inner search ' + - JSON.stringify(item) - ); - let sql2 = ''; - const rawAmount = BlocksoftPrettyNumbers.setCurrencyCode( - tmp.currencyCode - ).makeUnPretty(tmp.addressAmount); - if (typeof tmp.addressTo !== 'undefined' && tmp.addressTo) { - sql2 = ` - SELECT id, bse_order_data, transaction_hash, transactions_scan_log, hidden_at, created_at, address_amount FROM transactions - WHERE transaction_hash != '' AND (bse_order_id IS NULL OR bse_order_id='') AND currency_code='${ - tmp.currencyCode - }' - AND LOWER(address_to)='${tmp.addressTo.toLowerCase()}' - `; - } - if (sql2) { - const found2 = await Database.query(sql2, true); - if (found2 && found2.array) { - for (const found22 of found2.array) { - if ( - Math.abs( - BlocksoftUtils.diff( - found22.address_amount, - rawAmount - ).toString() * 1 - ) > 10 - ) - continue; - const createdAt = new Date( - found22.created_at - ).getTime(); - if (Math.abs(createdAt - item.createdAt) > 12000000) - continue; //60 * 1000 * 200 minutes - if (!found || !found.array) { - found = { array: [] }; - } - sqlUpdateDir = `bse_order_id='${item.orderId}', `; - found.array.push(found22); - } - } - } - } - } - - if (found && found.array && found.array.length > 0) { - savedToTx[tmp.currencyCode] = true; - - let id = 0; - let id2 = 0; - let id3 = 0; - const toRemove = []; - if (found.array.length > 1) { - for (const row of found.array) { - if (row.transaction_hash && row.transaction_hash !== '') { - id = row.id; - } else if ( - row.hidden_at && - row.hidden_at !== 'null' && - row.hidden_at !== '' - ) { - id2 = row.id; - } else { - id3 = row.id; - } - } - if (id === 0) { - id = id2; - } - if (id === 0) { - id = id3; - } - for (const row of found.array) { - if (row.id !== id) { - toRemove.push(row.id); - } - } - } else { - id = found.array[0].id; - } - - for (const row of found.array) { - if (id !== row.id) continue; - if ( - row.hidden_at && - row.hidden_at !== '' && - row.hidden_at !== 'null' - ) { - savedToTx[tmp.currencyCode] = 'id : ' + id; - continue; - } - const escaped = Database.escapeString(JSON.stringify(item)); - - if (!(row.bse_order_data === escaped)) { - let scanLog = row.transactions_scan_log; - if ( - scanLog.indexOf(` UPDATED ORDER ${item.orderId} `) === -1 - ) { - scanLog = ` ${nowAt} UPDATED ORDER ${item.orderId} / ${scanLog}`; - if (scanLog.length > 1000) { - scanLog = scanLog.substr(0, 1000); - } - sqlUpdateDir += `transactions_scan_log = '${scanLog}', `; - } - - const sql2 = ` UPDATE transactions SET ${sqlUpdateDir} bse_order_data='${escaped}' WHERE id=${row.id} `; - await Database.query(sql2, true); - } - } - if (toRemove.length > 0) { - const sql3 = ` DELETE FROM transactions WHERE id IN (${toRemove.join( - ',' - )}) AND id != ${id} `; - await Database.query(sql3, true); - } - } - } - - for (const tmp of tmps) { - if (!tmp.currencyCode || tmp.addressAmount === 0) continue; - if (typeof savedToTx[tmp.currencyCode] !== 'undefined') continue; - if (tmp.suffix === 'out' && !tmp.updateHash) continue; - - currencyCode = tmp.currencyCode; - - const createdAt = new Date(item.createdAt).toISOString(); - const sql = ` - INSERT INTO transactions (currency_code, wallet_hash, account_id, transaction_hash, transaction_hash_basic, transaction_status, transactions_scan_log, created_at, - address_amount, address_to, bse_order_id, bse_order_id_${ - tmp.suffix - }, bse_order_data) - VALUES ('${currencyCode}', '${walletHash}', '0', '', '${ - tmp.updateHash ? tmp.updateHash : '' - }', '', 'FROM ORDER ${createdAt} ${item.orderId}', '${createdAt}', - '${tmp.addressAmount}', '', '${ - item.orderId - }', '${item.orderId}', '${Database.escapeString( - JSON.stringify(item) - )}') - `; - await Database.query(sql, true); - savedToTx[tmp.currencyCode] = 1; - } - - total++; - } catch (e) { - if (config.debug.appErrors) { - console.log( - 'UpdateTradeOrders one order error ' + e.message, - JSON.parse(JSON.stringify(item)) - ); - } - Log.err('UpdateTradeOrders one order error ' + e.message, item); - } - } - - TRY_COUNTER = 0; - CACHE_ORDERS_HASH = res.cbOrdersHash; - } catch (e) { - if (config.debug.appErrors) { - console.log( - 'UpdateTradeOrders all orders error ' + e.message, - e, - JSON.parse(JSON.stringify(tmpTradeOrders)) - ); - } - throw new Error( - 'UpdateTradeOrders all orders error ' + - e.message + - ' ' + - JSON.stringify(tmpTradeOrders) - ); - } - } catch (e) { - if (config.debug.appErrors) { - console.log('UpdateTradeOrders get orders error ' + e.message, e); - } - if (Log.isNetworkError(e.message) && TRY_COUNTER < 10) { - TRY_COUNTER++; - Log.daemon( - 'UpdateTradeOrders network try ' + TRY_COUNTER + ' ' + e.message - ); - } else if (e.message === 'No cashbackToken') { - Log.daemon('UpdateTradeOrders notice ' + e.message); - } else { - Log.errDaemon('UpdateTradeOrders error ' + e.message); - } - } - CACHE_LAST_TIME = new Date().getTime(); - return true; - }; -} - -export default new UpdateTradeOrdersDaemon(); diff --git a/src/daemons/back/UpdateWalletsDaemon.js b/src/daemons/back/UpdateWalletsDaemon.js deleted file mode 100644 index b08c7ce8c..000000000 --- a/src/daemons/back/UpdateWalletsDaemon.js +++ /dev/null @@ -1,130 +0,0 @@ -/** - * @version 0.41 - * for data sync from proxy - */ -import Log from '@app/services/Log/Log' - -import config from '@app/config/config' - -import ApiProxy from '@app/services/Api/ApiProxy' -import walletDS from '@app/appstores/DataSource/Wallet/Wallet' -import cryptoWalletsDS from '@app/appstores/DataSource/CryptoWallets/CryptoWallets' -import settingsActions from '@app/appstores/Stores/Settings/SettingsActions' - -class UpdateWalletsDaemon { - - _canUpdate = true - - /** - * @return {Promise} - */ - updateWalletsDaemon = async (params = {}, dataUpdate = false) => { - if (typeof params !== 'undefined' && params && typeof params.force === 'undefined' || !params.force) { - if (!this._canUpdate && !dataUpdate) return false - } - - this._canUpdate = false - const res = await this._updateWalletsDaemon(params, dataUpdate) - this._canUpdate = true - - return res - } - - _updateWalletsDaemon = async (params, dataUpdate = false) => { - - Log.daemon('UpdateWalletsDaemon called') - - let asked = false - if (!dataUpdate) { - const authHash = await settingsActions.getSelectedWallet('_updateWalletsDaemon') - if (!authHash) { - Log.daemon('UpdateWalletsDaemon skipped as no auth') - return false - } - if (config.debug.appErrors) { - console.log(new Date().toISOString() + ' UpdateWalletsDaemon loading new') - } - asked = true - try { - dataUpdate = await ApiProxy.getAll({ ...params, source: 'UpdateWalletsDaemon.updateWallets' }) - } catch (e) { - if (config.debug.appErrors) { - console.log('UpdateWalletsDaemon error ' + e.message) - } - return false - } - } - - if (!dataUpdate) { - return false - } - - if (typeof dataUpdate.forWalletsAll !== 'undefined' && dataUpdate.forWalletsAll) { - if (!asked) { - if (config.debug.appErrors) { - console.log(new Date().toISOString() + ' UpdateWalletsDaemon loaded proxy forWalletsAll') - } - } - try { - - const saved = await walletDS.getWallets() - const walletsSaved = {} - if (saved) { - for (const row of saved) { - walletsSaved[row.walletHash] = row - } - } - - for (const walletHash in dataUpdate.forWalletsAll) { - const dataOne = dataUpdate.forWalletsAll[walletHash] - if (!dataOne) continue - if (typeof walletsSaved[walletHash] === 'undefined') { - throw new Error('Saved wallet is not correct ' + JSON.stringify(dataOne)) - } - - let mapping = { - walletAllowReplaceByFee: dataOne.wallet_allow_replace_by_fee, - walletIsHd: dataOne.wallet_is_hd, - walletIsHideTransactionForFee: dataOne.wallet_is_hide_transaction_for_free, - walletName: dataOne.wallet_name, - walletToSendStatus: dataOne.wallet_to_send_status, - walletUseLegacy: dataOne.wallet_use_legacy, - walletUseUnconfirmed: dataOne.wallet_use_unconfirmed, - walletIsCreatedHere: dataOne.wallet_is_created_here - } - mapping = walletDS._prepWallet(mapping) - - if (walletsSaved[walletHash].walletToSendStatus && walletsSaved[walletHash].walletToSendStatus * 1 >= dataOne.wallet_to_send_status * 1) { - // skip - } else { - const updateObj = {} - for (const key in mapping) { - if (walletsSaved[walletHash][key] !== mapping[key]) { - updateObj[key] = mapping[key] - } - } - updateObj.walletToSendStatus = dataOne.wallet_to_send_status ? dataOne.wallet_to_send_status*1 : 0 - const dataForSave = { - key: { - walletHash - }, - updateObj - } - await walletDS.updateWallet(dataForSave) - } - } - } catch (e) { - if (config.debug.appErrors) { - console.log('UpdateWalletsDaemon save error ' + e.message) - } - Log.errDaemon('UpdateWalletsDaemon save error ' + e.message) - return false - } - } - - return false - } - -} - -export default new UpdateWalletsDaemon diff --git a/src/daemons/back/apptasks/AppTasksDiscoverBalancesHidden.js b/src/daemons/back/apptasks/AppTasksDiscoverBalancesHidden.js deleted file mode 100644 index cbcb4aa81..000000000 --- a/src/daemons/back/apptasks/AppTasksDiscoverBalancesHidden.js +++ /dev/null @@ -1,70 +0,0 @@ -/** - * @version 0.11 - */ -import Database from '@app/appstores/DataSource/Database'; -import BlocksoftBalances from '../../../../crypto/actions/BlocksoftBalances/BlocksoftBalances' - -import appNewsDS from '../../../appstores/DataSource/AppNews/AppNews' -import Log from '../../../services/Log/Log' - -class AppTasksDiscoverBalancesHidden { - /** - * - * @param {string} appTask.walletHash - * @param {string} appTask.currencyCode - * @returns {Promise} - */ - run = async (appTask) => { - const sql =` - SELECT - currency_code AS currencyCode, - address, - account_json AS accountJson, - wallet_hash AS walletHash - FROM account - WHERE currency_code='${appTask.currencyCode}' - LIMIT 1` - const tmp = await Database.query(sql) - if (!tmp || typeof tmp.array === 'undefined' || !tmp.array) return 'no account' - const account = tmp.array[0] - let addressToScan = account.address - if (account.accountJson) { - try { - account.accountJson = JSON.parse(account.accountJson) - if (typeof account.accountJson.addressHex !== 'undefined') { - addressToScan = account.accountJson.addressHex - Log.daemon('AppTasksDiscoverBalancesHidden changing address ' + appTask.currencyCode + ' ' + account.address + ' => ' + addressToScan) - } - } catch (e) { - // do nothing - } - } - let newBalance - try { - newBalance = await (BlocksoftBalances.setCurrencyCode(appTask.currencyCode).setAddress(addressToScan).setAdditional(account.accountJson).setWalletHash(account.walletHash)).getBalance() - Log.daemon('AppTasksDiscoverBalancesHidden loaded address ' + appTask.currencyCode + ' ' + addressToScan, newBalance) - } catch (e) { - e.message += ' scanning ' + appTask.currencyCode + ' address ' + addressToScan - throw e - } - if (!newBalance) { - return addressToScan + ' no balance' - } - if (newBalance.balance*1 === 0 && newBalance.unconfirmed*1 === 0) { - return addressToScan + ' zero balance ' + JSON.stringify(newBalance) - } - newBalance.address = addressToScan - - await appNewsDS.saveAppNews({ - walletHash : appTask.walletHash, - currencyCode : appTask.currencyCode, - newsGroup : 'DAEMON', - newsName : 'DAEMON_HAS_FOUND_BALANCE', - newsJson : newBalance - }) - - return addressToScan + ' not zero balance ' + JSON.stringify(newBalance) - - } -} -export default new AppTasksDiscoverBalancesHidden() diff --git a/src/daemons/back/apptasks/AppTasksDiscoverBalancesNotAdded.js b/src/daemons/back/apptasks/AppTasksDiscoverBalancesNotAdded.js deleted file mode 100644 index 4c02ddeb1..000000000 --- a/src/daemons/back/apptasks/AppTasksDiscoverBalancesNotAdded.js +++ /dev/null @@ -1,64 +0,0 @@ -/** - * @version 0.11 - */ -import BlocksoftBalances from '../../../../crypto/actions/BlocksoftBalances/BlocksoftBalances' - -import appNewsDS from '../../../appstores/DataSource/AppNews/AppNews' -import Log from '../../../services/Log/Log' -import BlocksoftKeys from '../../../../crypto/actions/BlocksoftKeys/BlocksoftKeys' -import BlocksoftKeysStorage from '../../../../crypto/actions/BlocksoftKeysStorage/BlocksoftKeysStorage' - -class AppTasksDiscoverBalancesNotAdded { - /** - * - * @param {string} appTask.walletHash - * @param {string} appTask.currencyCode - * @returns {Promise} - */ - run = async (appTask) => { - - const mnemonic = await BlocksoftKeysStorage.getWalletMnemonic(appTask.walletHash, 'AppTasksDiscoverBalancesNotAdded.run') - const accounts = await BlocksoftKeys.discoverAddresses({ mnemonic, fullTree: false, fromIndex: 0, toIndex: 1, currencyCode: appTask.currencyCode }, 'APP_TASK') - if (!accounts) return 'no address' - const account = accounts[appTask.currencyCode][0] - let addressToScan = account.address - if (account.accountJson) { - try { - if (typeof account.addedData.addressHex !== 'undefined') { - addressToScan = account.addedData.addressHex - Log.daemon('AppTasksDiscoverBalancesNotAdded changing address ' + appTask.currencyCode + ' ' + account.address + ' => ' + addressToScan) - } - } catch (e) { - // do nothing - } - } - let newBalance - try { - newBalance = await (BlocksoftBalances.setCurrencyCode(appTask.currencyCode).setAddress(addressToScan).setAdditional(account.addedData).setWalletHash(appTask.walletHash)).getBalance() - Log.daemon('AppTasksDiscoverBalanceNotAdded loaded address ' + appTask.currencyCode + ' ' + addressToScan, newBalance) - } catch (e) { - e.message += ' scanning ' + appTask.currencyCode + ' address ' + addressToScan - throw e - } - if (!newBalance) { - return addressToScan + ' no balance' - } - if (newBalance.balance*1 === 0 && newBalance.unconfirmed*1 === 0) { - return addressToScan + ' zero balance ' + JSON.stringify(newBalance) - } - newBalance.address = addressToScan - - await appNewsDS.saveAppNews({ - walletHash: appTask.walletHash, - currencyCode: appTask.currencyCode, - newsGroup: 'DAEMON', - newsName: 'DAEMON_HAS_FOUND_BALANCE_NOT_ADDED', - newsJson: newBalance - }) - - return addressToScan + ' not zero balance ' + JSON.stringify(newBalance) - - } -} - -export default new AppTasksDiscoverBalancesNotAdded() diff --git a/src/daemons/back/apptasks/AppTasksDiscoverHD.js b/src/daemons/back/apptasks/AppTasksDiscoverHD.js deleted file mode 100644 index e2f2b5718..000000000 --- a/src/daemons/back/apptasks/AppTasksDiscoverHD.js +++ /dev/null @@ -1,35 +0,0 @@ -/** - * @version 0.11 - */ -import appNewsDS from '../../../appstores/DataSource/AppNews/AppNews' - -import App from '../../../appstores/Actions/App/App' -import WalletHDActions from '../../../appstores/Actions/WalletHDActions' - -class AppTasksDiscoverHD { - /** - * - * @param {string} appTask.walletHash - * @param {string} appTask.currencyCode - * @returns {Promise} - */ - run = async (appTask) => { - let derivations = false - try { - derivations = await WalletHDActions.hdFromTrezor({walletHash : appTask.walletHash, force : false, currencyCode : appTask.currencyCode}, 'APP_TASK') - } catch (e) { - return e.message - } - await appNewsDS.saveAppNews({ - walletHash : appTask.walletHash, - currencyCode : appTask.currencyCode, - newsGroup : 'DAEMON', - newsName : 'DAEMON_HAS_FOUND_HD' - }) - - await App.refreshWalletsStore({firstTimeCall : false, source : 'AppTasks discoverHD'}) - return ' there are derivations ' + JSON.stringify(derivations) - - } -} -export default new AppTasksDiscoverHD() diff --git a/src/daemons/back/apputils/AccountTransactionsRecheck.js b/src/daemons/back/apputils/AccountTransactionsRecheck.js deleted file mode 100644 index 2a641bad6..000000000 --- a/src/daemons/back/apputils/AccountTransactionsRecheck.js +++ /dev/null @@ -1,420 +0,0 @@ -/** - * @version 0.11 - */ -import Log from '@app/services/Log/Log' -import transactionDS from '@app/appstores/DataSource/Transaction/Transaction' -import appNewsDS from '@app/appstores/DataSource/AppNews/AppNews' -import { BlocksoftTransfer } from '@crypto/actions/BlocksoftTransfer/BlocksoftTransfer' -import settingsActions from '@app/appstores/Stores/Settings/SettingsActions' -import config from '@app/config/config' - -const CACHE_TO_REMOVE = {} // couldnt remove on first scan - as BTC is scanned in few accounts - -export default async function AccountTransactionsRecheck(newTransactions, account, source) { - - const transactionUpdateObj = { - transactionsScanTime: Math.round(new Date().getTime() / 1000) - } - - if (!newTransactions || typeof newTransactions === 'undefined') { - transactionUpdateObj.transactionsScanLog = 'not txs ' - return transactionUpdateObj - } - - if (typeof CACHE_TO_REMOVE[account.currencyCode] === 'undefined') { - CACHE_TO_REMOVE[account.currencyCode] = {} - } - - const dbTransactions = {} - const toRemove = [] - const dbNonces = {} - - try { - const tmps = await transactionDS.getTransactions({ - currencyCode: account.currencyCode, - walletHash: account.walletHash, - noOrder: true, - noOld: true - }, 'AccountTransactionsRecheck dbTransactions ' + source) - if (tmps && tmps.length > 0) { - let tmp - for (tmp of tmps) { - if (tmp.addressFrom === '' && typeof tmp.transactionJson !== 'undefined' && tmp.transactionJson && typeof tmp.transactionJson.nonce !== 'undefined' && typeof tmp.transactionJson.delegatedNonce === 'undefined') { - const key = tmp.addressFromBasic + '_' + tmp.transactionJson.nonce - if (typeof dbNonces[key] === 'undefined') { - dbNonces[key] = [] - } - dbNonces[key].push(tmp) - } - if (typeof dbTransactions[tmp.transactionHash] !== 'undefined') { - // rmv double - Log.daemon('AccountTransactionsRecheck dbTransactions will remove ' + tmp.id) - toRemove.push(tmp.id) - continue - } else { - dbTransactions[tmp.transactionHash] = tmp - } - if (typeof tmp.transactionsOtherHashes !== 'undefined' && tmp.transactionsOtherHashes) { - const tmp2 = tmp.transactionsOtherHashes.split(',') - if (tmp2) { - let part - for (part of tmp2) { - dbTransactions[part] = dbTransactions[tmp.transactionHash] - } - } - } - } - } - } catch (e) { - Log.errDaemon('AccountTransactionsRecheck dbTransactions something wrong ' + account.currencyCode + ' ' + e.message) - } - - if (toRemove.length > 0) { - await transactionDS.removeTransactions(toRemove) - } - if (!account.transactionsScanLog || account.transactionsScanLog.length < 10) { - source = 'FIRST' - } - - /** - * @property {*} transactionHash - * @property {*} blockHash - * @property {*} blockNumber - * @property {*} blockTime - * @property {*} blockConfirmations - * @property {*} transactionDirection - * @property {*} addressFrom - * @property {*} addressTo - * @property {*} addressAmount - * @property {*} transactionStatus - * @property {*} transactionFee - * @property {*} contractAddress - * @property {*} inputValue - * @property {*} transactionJson - */ - let transaction - let transactionsError = '' - let changesCount = 0 - const unique = {} - try { - for (transaction of newTransactions) { - if (typeof unique[transaction.transactionHash] !== 'undefined') { - continue - } - unique[transaction.transactionHash] = 1 - try { - const tmp = await AccountTransactionRecheck(transaction, dbTransactions[transaction.transactionHash], account, source) - if (typeof dbTransactions[transaction.transactionHash] !== 'undefined') { - delete dbTransactions[transaction.transactionHash] - if (typeof CACHE_TO_REMOVE[account.currencyCode][transaction.transactionHash] !== 'undefined') { - delete CACHE_TO_REMOVE[account.currencyCode][transaction.transactionHash] - } - } - if (tmp.isChanged > 0) { - - if (transaction.transactionStatus === 'success' || transaction.transactionStatus === 'confirming') { - if (typeof transaction.transactionJson !== 'undefined' && transaction.transactionJson && typeof transaction.transactionJson.nonce !== 'undefined' && typeof transaction.transactionJson.delegatedNonce === 'undefined') { - let key = transaction.addressFromBasic + '_' + transaction.transactionJson.nonce - if (typeof dbNonces[key] !== 'undefined' && dbNonces[key].length > 1) { - for (let nonced of dbNonces[key]) { - if (nonced.transactionHash !== transaction.transactionHash) { - await transactionDS.saveTransaction({ - transactionStatus: 'replaced', - transactionsScanLog: nonced.transactionsScanLog + ' dropped by nonce' - }, nonced.id, 'replaced') - } - } - } - } - } - - changesCount++ - } - } catch (e) { - e.message = ' TX ' + transaction.transactionHash + ' ' + e.message - // noinspection ExceptionCaughtLocallyJS - throw e - } - } - } catch (e) { - if (config.debug.appErrors) { - console.log('AccountTransactionsRecheck parsing error ' + e.message, e) - } - transactionsError += ' parsing error ' + e.message - } - - if (dbTransactions) { - const now = new Date().getTime() - for (const hash in dbTransactions) { - const dbTransaction = dbTransactions[hash] - if (dbTransaction.blockConfirmations > 40) { - continue - } - - let minutesToWait = 0 - let minutesToInform = 0 - if (account.currencyCode === 'USDT') { - minutesToWait = 1200 - minutesToInform = 12000 - } else if (account.currencyCode === 'ETH' || account.currencyCode.indexOf('ETH_') === 0) { - minutesToWait = 240 - minutesToInform = 400 - } else if (dbTransaction.transactionStatus === 'new' || dbTransaction.transactionStatus === 'pending') { - minutesToWait = 20 // pending tx will be in the list - checked in trezor - minutesToInform = 200 - } else if (dbTransaction.transactionStatus === 'confirming') { - minutesToWait = 10 - minutesToInform = 200 - } else { - continue - } - - if (typeof CACHE_TO_REMOVE[account.currencyCode][hash] === 'undefined') { - CACHE_TO_REMOVE[account.currencyCode][hash] = { ts: now, count: 0 } - continue - } - - const minutesFromScan = Math.round((now - CACHE_TO_REMOVE[account.currencyCode][hash].ts) / 60000) - CACHE_TO_REMOVE[account.currencyCode][hash].count++ - if (account.currencyCode === 'BTC') { - if (minutesFromScan < 20 || CACHE_TO_REMOVE[account.currencyCode][hash].count < 10) { - continue // 10 minutes from last recheck as btc are in several accounts - } - } else { - if (minutesFromScan < 2 || CACHE_TO_REMOVE[account.currencyCode][hash].count < 3) { - continue // other currencies are scanned in one account - } - } - - const time = dbTransaction.updatedAt || dbTransaction.createdAt - const minutes = Math.round((now - new Date(time).getTime()) / 60000) - if (minutes > minutesToWait && (dbTransaction.transactionStatus !== 'fail' && dbTransaction.transactionStatus !== 'no_energy')) { - await BlocksoftTransfer.setMissingTx(account, dbTransaction) - - await transactionDS.saveTransaction({ - transactionStatus: 'missing', - transactionsScanLog: dbTransaction.transactionsScanLog + ' dropped after ' + minutes + ' from ' + time - }, dbTransaction.id, 'missing') - - if (dbTransaction.addressAmount > 0 && minutes < minutesToInform) { - await addNews(dbTransaction, account, source, 'FOUND_OUT_TX_STATUS_MISSING') - } - } - } - } - - if (changesCount > 0) { - transactionUpdateObj.transactionsScanLog = 'all ok, changed ' + changesCount - if (transactionsError) { - transactionUpdateObj.transactionsScanLog += ', ' + transactionsError - } - } else { - transactionUpdateObj.transactionsScanLog = 'not changed txs ' + transactionsError - } - return transactionUpdateObj -} - -async function AccountTransactionRecheck(transaction, old, account, source) { - // Log.daemon('AccountTransactionRecheck in ' + JSON.stringify(transaction) + ' old ' + JSON.stringify(old)) - let blocksToUpdate = 50 - if (account.currencyCode.indexOf('TRX') !== -1) { - blocksToUpdate = 1000 - } - const line = new Date().toISOString().replace(/T/, ' ').replace(/\..+/, '') - let tmpMsg = '' - - let newAddressTo = transaction.addressTo - if (typeof newAddressTo === 'undefined' || newAddressTo === false) { - newAddressTo = '' - } - - if (typeof transaction.addressAmount === 'undefined') { - // Log.daemon('UpdateAccountTransactions ' + account.currencyCode + ' skip as no addressAmount', tmpMsg, transaction) - return false - } - let newAmount = typeof transaction.addressAmount === 'undefined' ? '0' : transaction.addressAmount.toString() - if (newAmount.length > 10) { - newAmount = newAmount.substring(0, 10) - } - if (newAmount === '000000000') { - newAmount = 0 - } - - if (typeof old === 'undefined' || !old) { - if (newAmount === 0) { - tmpMsg = `SKIP AS ZERO AMOUNT ${account.currencyCode} HASH ${transaction.transactionHash} CONF ${transaction.blockConfirmations} AMOUNT ${transaction.addressAmount} FROM ${transaction.addressFrom} TO ${transaction.addressTo}` - // Log.daemon('UpdateAccountTransactions ' + account.currencyCode + ' skip 1', tmpMsg, transaction) - return { isChanged: 0, tmpMsg } - } - - tmpMsg = ` INSERT ${account.currencyCode} HASH ${transaction.transactionHash} CONF ${transaction.blockConfirmations} AMOUNT ${transaction.addressAmount} FROM ${transaction.addressFrom} TO ${transaction.addressTo}` - if (source !== 'FIRST' && transaction.addressAmount > 0) { - tmpMsg += ' with PUSH ' + source - } - transaction.currencyCode = account.currencyCode - transaction.walletHash = account.walletHash - transaction.accountId = account.id - transaction.createdAt = transaction.blockTime // to make created tx more accurate - transaction.minedAt = transaction.blockTime - transaction.transactionsScanTime = Math.round(new Date().getTime() / 1000) - transaction.transactionsScanLog = line + ' ' + tmpMsg - if (!transaction.transactionDirection) { - transaction.transactionDirection = 'outcome' - } - - await transactionDS.saveTransaction(transaction, false, 'old undefined ' + tmpMsg) - // Log.daemon('UpdateAccountTransactions ' + account.currencyCode + ' added', tmpMsg, transaction) - if (source !== 'FIRST' && transaction.addressAmount > 0) { - await addNews(transaction, account, source, 'new') - } - return { isChanged: 1, tmpMsg } - } - - if (old.transactionHash !== transaction.transactionHash) { - // Log.daemon('UpdateAccountTransactions ' + account.currencyCode + ' old is replaced - would not place DB HASH ' + old.transactionHash + ' != SCANNED HASH ' + transaction.transactionHash, old, transaction) - return { isChanged: 0, tmpMsg: ` DROPPED SCANNED ${transaction.transactionHash} DB ${old.transactionHash}` } - } - - if (typeof old.updateSkip !== 'undefined') { - return { isChanged: 0, tmpMsg: ` SKIPPED SCANNED ${transaction.transactionHash} DB ${old.transactionHash}` } - } - - let oldAmount = typeof old.addressAmount === 'undefined' ? '0' : old.addressAmount.toString() - if (oldAmount.length > 10) { - oldAmount = oldAmount.substring(0, 10) - } - if (oldAmount === '000000000') { - oldAmount = 0 - } - - - let oldAddressTo = old.addressTo - if (typeof oldAddressTo === 'undefined' || !oldAddressTo) { - oldAddressTo = '' - } - - if (old.transactionOfTrusteeWallet > 0) { - const transactionPart = { - transactionStatus: transaction.transactionStatus, - blockConfirmations: transaction.blockConfirmations - } - - if (!old.createdAt) { - tmpMsg = ` TWALLET UPDATE ${account.id} ${account.currencyCode} HASH ${transaction.transactionHash} by CREATED NEW ${transaction.blockTime} OLD ${old.createdAt} AMOUNT ${transaction.addressAmount} FROM ${transaction.addressFrom} TO ${transaction.addressTo}` - } else if (transaction.blockTime && (!old.minedAt || old.minedAt !== transaction.blockTime)) { - tmpMsg = ` TWALLET UPDATE ${account.id} ${account.currencyCode} HASH ${transaction.transactionHash} by MINED NEW ${transaction.blockTime} OLD ${old.minedAt} AMOUNT ${transaction.addressAmount} FROM ${transaction.addressFrom} TO ${transaction.addressTo}` - } else if (old.transactionStatus !== transaction.transactionStatus || !old.createdAt) { - tmpMsg = ` TWALLET UPDATE ${account.id} ${account.currencyCode} HASH ${transaction.transactionHash} by STATUS NEW ${transaction.transactionStatus} OLD ${old.transactionStatus} AMOUNT ${transaction.addressAmount} FROM ${transaction.addressFrom} TO ${transaction.addressTo}` - } else if (!old.transactionFee && old.transactionFee !== transaction.transactionFee) { - tmpMsg = ` TWALLET UPDATE ${account.id} ${account.currencyCode} HASH ${transaction.transactionHash} by FEE ${transaction.transactionFee} AMOUNT ${transaction.addressAmount} FROM ${transaction.addressFrom} TO ${transaction.addressTo}` - } else if (!old.addressAmount || old.addressAmount * 1 !== transaction.addressAmount * 1) { - tmpMsg = ` TWALLET UPDATE ${account.id} ${account.currencyCode} HASH ${transaction.transactionHash} by AMOUNT NEW ${transaction.addressAmount} OLD ${old.addressAmount} FROM ${transaction.addressFrom} TO ${transaction.addressTo}` - } else if (old.blockConfirmations === transaction.blockConfirmations || (old.blockConfirmations > blocksToUpdate && transaction.blockConfirmations > blocksToUpdate)) { - tmpMsg = ` TWALLET SKIP ${account.id} ${account.currencyCode} HASH ${transaction.transactionHash} CONF ${transaction.blockConfirmations} OLD CONF ${old.blockConfirmations} STATUS ${old.transactionStatus}` - return { isChanged: 0, tmpMsg } - } else { - tmpMsg = ` TWALLET UPDATE ${account.id} ${account.currencyCode} HASH ${transaction.transactionHash} by CONF NEW ${transaction.blockConfirmations} OLD ${old.blockConfirmations} STATUS ${transaction.transactionStatus} AMOUNT ${transaction.addressAmount} FROM ${transaction.addressFrom} TO ${transaction.addressTo}` - } - - if (!old.transactionFee && transaction.transactionFee) { - tmpMsg += ' PLUS FEE ' + transaction.transactionFee - transactionPart.transactionFee = transaction.transactionFee - transactionPart.transactionFeeCurrencyCode = transaction.transactionFeeCurrencyCode || '' - } - if (!old.createdAt) { - transactionPart.createdAt = transaction.blockTime - } - transactionPart.minedAt = transaction.blockTime - transactionPart.transactionsScanTime = Math.round(new Date().getTime() / 1000) - transactionPart.transactionsScanLog = line + ' ' + tmpMsg - if (old && old.transactionsScanLog) { - transactionPart.transactionsScanLog += ' ' + old.transactionsScanLog - } - if (old.transactionDirection !== transaction.transactionDirection) { - Log.daemon('UpdateAccountTransactions ' + account.currencyCode + ' by TWALLET DIRECTION id ' + old.id + ' address ' + account.address + ' hash ' + transaction.transactionHash + ' OLD ' + old.transactionDirection + ' NEW ' + transaction.transactionDirection) - } - await transactionDS.saveTransaction(transactionPart, old.id, 'old trustee ' + tmpMsg) - - if (old.transactionStatus !== transaction.transactionStatus && source !== 'FIRST' && transaction.addressAmount > 0) { - await addNews(transaction, account, source, 'old') - } - return { isChanged: 1, tmpMsg } - } - - if (!old.createdAt) { - tmpMsg = ` FULL UPDATE ${account.id} ${account.currencyCode} HASH ${transaction.transactionHash} by CREATED NEW ${transaction.blockTime} OLD ${old.createdAt} AMOUNT ${transaction.addressAmount} FROM ${transaction.addressFrom} TO ${transaction.addressTo}` - } else if (transaction.blockTime && (!old.minedAt || old.minedAt !== transaction.blockTime)) { - tmpMsg = ` FULL UPDATE ${account.id} ${account.currencyCode} HASH ${transaction.transactionHash} by MINED NEW ${transaction.blockTime} OLD ${old.minedAt} AMOUNT ${transaction.addressAmount} FROM ${transaction.addressFrom} TO ${transaction.addressTo}` - } else if (old.transactionStatus !== transaction.transactionStatus) { - tmpMsg = ` FULL UPDATE ${account.id} ${account.currencyCode} HASH ${transaction.transactionHash} by STATUS NEW ${transaction.transactionStatus} OLD ${old.transactionStatus} AMOUNT ${transaction.addressAmount} FROM ${transaction.addressFrom} TO ${transaction.addressTo}` - } else if (oldAmount !== newAmount) { - tmpMsg = ` FULL UPDATE ${account.id} ${account.currencyCode} HASH ${transaction.transactionHash} by AMOUNT NEW ${newAmount} OLD ${oldAmount} AMOUNT ${transaction.addressAmount} FROM ${transaction.addressFrom} TO ${transaction.addressTo}` - } else if (oldAddressTo !== newAddressTo) { - tmpMsg = ` FULL UPDATE ${account.id} ${account.currencyCode} HASH ${transaction.transactionHash} by ADDRESS_TO ${newAddressTo} OLD ${oldAddressTo} AMOUNT ${transaction.addressAmount} FROM ${transaction.addressFrom} TO ${transaction.addressTo}` - } else if (old.addressFrom !== transaction.addressFrom) { - tmpMsg = ` FULL UPDATE ${account.id} ${account.currencyCode} HASH ${transaction.transactionHash} by ADDRESS_FROM ${transaction.addressFrom} OLD ${old.addressFrom} AMOUNT ${transaction.addressAmount} FROM ${transaction.addressFrom} TO ${transaction.addressTo}` - } else if (old.blockConfirmations === transaction.blockConfirmations || (old.blockConfirmations > blocksToUpdate && transaction.blockConfirmations > blocksToUpdate)) { - tmpMsg = ` SKIP ${account.id} ${account.currencyCode} HASH ${transaction.transactionHash} CONF ${transaction.blockConfirmations} OLD CONF ${old.blockConfirmations} STATUS ${old.transactionStatus}` - // Log.daemon('UpdateAccountTransactions ' + account.currencyCode + ' skip 3 ' + transaction.transactionHash) - return { isChanged: 0, tmpMsg } - } else { - tmpMsg = ` FULL UPDATE ${account.currencyCode} HASH ${transaction.transactionHash} by CONF NEW ${transaction.blockConfirmations} OLD ${old.blockConfirmations} STATUS ${transaction.transactionStatus} AMOUNT ${transaction.addressAmount} FROM ${transaction.addressFrom} TO ${transaction.addressTo}` - } - transaction.createdAt = transaction.blockTime - transaction.minedAt = transaction.blockTime - if (old.transactionDirection !== transaction.transactionDirection) { - Log.daemon('UpdateAccountTransactions ' + account.currencyCode + ' by DIRECTION id ' + old.id + ' address ' + account.address + ' hash ' + transaction.transactionHash + ' OLD ' + old.transactionDirection + ' NEW ' + transaction.transactionDirection) - delete transaction.transactionDirection - } - transaction.transactionsScanTime = Math.round(new Date().getTime() / 1000) - transaction.transactionsScanLog = line + ' ' + tmpMsg + ' ' - if (old && old.transactionsScanLog) { - transaction.transactionsScanLog += ' ' + old.transactionsScanLog - } - await transactionDS.saveTransaction(transaction, old.id, 'old ' + tmpMsg) - // Log.daemon('UpdateAccountTransactions ' + account.currencyCode + ' update 2', tmpMsg, transaction) - return { isChanged: 1, tmpMsg } -} - -async function addNews(transaction, account, source, type) { - const transactionsNotifs = await settingsActions.getSetting('transactionsNotifs') - if (transactionsNotifs !== '1') { - return - } - if (type === 'new') { - const now = new Date().getTime() - const time = transaction.createdAt - const minutes = Math.round((now - new Date(time).getTime()) / 60000) - if (minutes > 5760 ) { // 4 day - return false - } - } - let needToPopup = 1 - if (transaction.transactionStatus === 'confirming') { - needToPopup = 0 - } else if (transaction.transactionStatus === 'delegated') { - needToPopup = 0 - } - let name = '' - if (type === 'FOUND_OUT_TX_STATUS_MISSING') { - name = type - } else if (transaction.transactionDirection === 'income') { - name = 'FOUND_IN_TX' - } else { - name = 'FOUND_OUT_TX_STATUS_' + transaction.transactionStatus.toUpperCase() - } - - const data = { - walletHash: account.walletHash, - currencyCode: account.currencyCode, - newsSource: source, - newsNeedPopup: needToPopup, - newsGroup: 'TX_SCANNER', - newsName: name, - newsJson: transaction, - newsUniqueKey: transaction.transactionHash - } - await appNewsDS.saveAppNews( - data - ) -} diff --git a/src/daemons/view/UpdateAccountListDaemon.js b/src/daemons/view/UpdateAccountListDaemon.js deleted file mode 100644 index 993cf389c..000000000 --- a/src/daemons/view/UpdateAccountListDaemon.js +++ /dev/null @@ -1,952 +0,0 @@ -/** - * @version 0.11 - */ -import Update from '../Update'; - -import Log from '../../services/Log/Log'; - -import store from '../../store'; - -import accountDS from '../../appstores/DataSource/Account/Account'; -import walletPubDS from '../../appstores/DataSource/Wallet/WalletPub'; - -import AirDAODict from '../../../crypto/common/AirDAODict'; -import BlocksoftPrettyNumbers from '@crypto/common/AirDAOPrettyNumbers'; -import BlocksoftUtils from '@crypto/common/AirDAOUtils'; - -import DaemonCache from '../DaemonCache'; -import { - setSelectedAccount, - setSelectedAccountTransactions -} from '../../appstores/Stores/Main/MainStoreActions'; -import BlocksoftBN from '../../../crypto/common/AirDAOBN'; -import MarketingEvent from '../../services/Marketing/MarketingEvent'; -import { BlocksoftTransferUtils } from '@crypto/actions/BlocksoftTransfer/BlocksoftTransferUtils'; -import walletActions from '@app/appstores/Stores/Wallet/WalletActions'; -import BlocksoftKeysStorage from '@crypto/actions/BlocksoftKeysStorage/BlocksoftKeysStorage'; -import BlocksoftKeys from '@crypto/actions/BlocksoftKeys/BlocksoftKeys'; -import config from '@app/config/config'; -import Database from '@app/appstores/DataSource/Database/main'; - -let CACHE_PAUSE = 0; - -const CACHE_VALID_TIME_PAUSE = 10000; - -let CACHE_STOPPED = false; - -class UpdateAccountListDaemon extends Update { - constructor(props) { - super(props); - this.updateFunction = this.updateAccountListDaemon; - this._canUpdate = true; - } - - stop = () => { - CACHE_STOPPED = true; - }; - - unstop = () => { - CACHE_STOPPED = false; - }; - - pause = () => { - CACHE_PAUSE = new Date().getTime(); - }; - - /** - * @return {Promise} - */ - updateAccountListDaemon = async (params) => { - const source = params.source || 'none'; - const force = params.force || false; - const now = new Date().getTime(); - if (CACHE_STOPPED && !force) return false; - - if (!force && DaemonCache.CACHE_WALLET_COUNT > 0) { - if ( - (CACHE_PAUSE > 0 && now - CACHE_PAUSE < CACHE_VALID_TIME_PAUSE) || - !this._canUpdate - ) { - if (store.getState().accountStore.accountList !== {}) { - // do nothing @todo testing if its the key - } else { - store.dispatch({ - type: 'SET_ACCOUNT_LIST', - accountList: DaemonCache.CACHE_ALL_ACCOUNTS - }); - } - return false; - } - } - this._canUpdate = false; - try { - const currentStore = store.getState(); - const allWallets = currentStore.walletStore.wallets; - const selectedAccount = currentStore.mainStore.selectedAccount; - - let basicCurrency = currentStore.mainStore.selectedBasicCurrency; - if (!basicCurrency || typeof basicCurrency.symbol === 'undefined') { - basicCurrency = { - currencyCode: 'USD', - symbol: '$' - }; - } - const basicCurrencyCode = basicCurrency.currencyCode || 'USD'; - - const walletPub = await walletPubDS.getWalletPubs(); - const notWalletHashes = []; - if (walletPub) { - let walletPubKey; - for (walletPubKey in walletPub) { - notWalletHashes.push(walletPubKey); - } - } - const tmps = await accountDS.getAccountData({ notWalletHashes }); - - const reformatted = {}; - const alreadyCounted = {}; - let tmpCurrency; - const tmpWalletHashes = []; - for (tmpCurrency of tmps) { - if (typeof reformatted[tmpCurrency.walletHash] === 'undefined') { - reformatted[tmpCurrency.walletHash] = {}; - alreadyCounted[tmpCurrency.walletHash] = {}; - tmpWalletHashes.push(tmpCurrency.walletHash); - } - if ( - tmpCurrency.currencyCode === 'BTC' && - typeof walletPub[tmpCurrency.walletHash] !== 'undefined' - ) { - if ( - typeof reformatted[tmpCurrency.walletHash][ - tmpCurrency.currencyCode - ] !== 'undefined' - ) { - if ( - reformatted[tmpCurrency.walletHash][ - tmpCurrency.currencyCode - ].address.substr(0, 1) === '3' - ) { - reformatted[tmpCurrency.walletHash][ - tmpCurrency.currencyCode - ].address = tmpCurrency.address; - reformatted[tmpCurrency.walletHash][ - tmpCurrency.currencyCode - ].derivationPath = tmpCurrency.derivationPath; - } - reformatted[tmpCurrency.walletHash][ - tmpCurrency.currencyCode - ].walletPubs = walletPub[tmpCurrency.walletHash]; - - if ( - tmpCurrency.address.indexOf( - AirDAODict.Currencies[tmpCurrency.currencyCode].addressPrefix - ) === 0 - ) { - if ( - typeof reformatted[tmpCurrency.walletHash][ - tmpCurrency.currencyCode - ].legacy === 'undefined' - ) { - reformatted[tmpCurrency.walletHash][ - tmpCurrency.currencyCode - ].legacy = tmpCurrency.address; - } - } else if ( - tmpCurrency.address.indexOf( - AirDAODict.CurrenciesForTests[ - tmpCurrency.currencyCode + '_SEGWIT' - ].addressPrefix - ) === 0 - ) { - if ( - typeof reformatted[tmpCurrency.walletHash][ - tmpCurrency.currencyCode - ].segwit === 'undefined' - ) { - reformatted[tmpCurrency.walletHash][ - tmpCurrency.currencyCode - ].segwit = tmpCurrency.address; - } - } - - continue; - } - const pub = walletPub[tmpCurrency.walletHash]; - reformatted[tmpCurrency.walletHash][tmpCurrency.currencyCode] = - tmpCurrency; - - let tmpBalanceScanTime = 0; - let tmpTransactionsScanTime = 0; - let tmpBalance = new BlocksoftBN(0); - let tmpUnconfirmed = new BlocksoftBN(0); - let tmpBalanceLog = ''; - let tmpBalanceScanError = ''; - const keys = ['btc.44', 'btc.49', 'btc.84']; - let key; - for (key of keys) { - if (typeof pub[key] === 'undefined') { - continue; - } - if (tmpBalanceScanTime > 0) { - if (pub[key].balanceScanTime * 1 > 0) { - tmpBalanceScanTime = Math.min( - tmpBalanceScanTime, - pub[key].balanceScanTime - ); - } else { - tmpBalanceScanTime = 0; - } - } else { - tmpBalanceScanTime = pub[key].balanceScanTime * 1; - } - if (tmpTransactionsScanTime > 0) { - if (pub[key].transactionsScanTime * 1 > 0) { - tmpTransactionsScanTime = Math.max( - tmpTransactionsScanTime, - pub[key].transactionsScanTime - ); - } - } else { - tmpTransactionsScanTime = pub[key].transactionsScanTime * 1; - } - - if (pub[key].balance) { - tmpBalance.add(pub[key].balance); - tmpBalanceLog += ' ' + pub[key].balance + '(' + key + ')'; - } - if ( - pub[key].balanceScanError && - pub[key].balanceScanError !== '' && - pub[key].balanceScanError !== 'null' - ) { - tmpBalanceScanError = pub[key].balanceScanError; - } - - if (pub[key].unconfirmed) { - try { - tmpUnconfirmed.add(pub[key].unconfirmed); - } catch (e) { - Log.errDaemon( - 'UpdateAccountListDaemon error on tmpUnconfirmed ' + e.message - ); - } - } - } - - const badAddresses = await accountDS.getAccountData({ - derivationPath: 'm/49quote/0quote/0/1/0' - }); - if (badAddresses) { - let bad; - for (bad of badAddresses) { - if (bad.balance) { - try { - tmpBalance.add(bad.balance); - tmpBalanceLog += ' ' + bad.balance + '(' + bad.address + ')'; - } catch (e) { - Log.errDaemon( - 'UpdateAccountListDaemon error on tmpBalance ' + e.message - ); - } - } - - if (bad.unconfirmed) { - try { - tmpUnconfirmed.add(bad.unconfirmed); - } catch (e) { - Log.errDaemon( - 'UpdateAccountListDaemon error on tmpUnconfirmed2 ' + - e.message - ); - } - } - } - } - - tmpBalance = tmpBalance.get(); - tmpUnconfirmed = tmpUnconfirmed.get(); - - reformatted[tmpCurrency.walletHash][ - tmpCurrency.currencyCode - ].balance = tmpBalance; - reformatted[tmpCurrency.walletHash][ - tmpCurrency.currencyCode - ].unconfirmed = tmpUnconfirmed; - reformatted[tmpCurrency.walletHash][ - tmpCurrency.currencyCode - ].balanceTxt = ''; - reformatted[tmpCurrency.walletHash][ - tmpCurrency.currencyCode - ].balanceFix = ''; - reformatted[tmpCurrency.walletHash][ - tmpCurrency.currencyCode - ].unconfirmedTxt = ''; - reformatted[tmpCurrency.walletHash][ - tmpCurrency.currencyCode - ].unconfirmedFix = ''; - reformatted[tmpCurrency.walletHash][ - tmpCurrency.currencyCode - ].balanceStaked = ''; - reformatted[tmpCurrency.walletHash][ - tmpCurrency.currencyCode - ].balanceAddingLog = tmpBalanceLog; - reformatted[tmpCurrency.walletHash][ - tmpCurrency.currencyCode - ].balanceScanTime = - tmpBalanceScanTime > tmpTransactionsScanTime - ? tmpBalanceScanTime - : tmpTransactionsScanTime; - reformatted[tmpCurrency.walletHash][ - tmpCurrency.currencyCode - ].balanceScanError = tmpBalanceScanError; - reformatted[tmpCurrency.walletHash][ - tmpCurrency.currencyCode - ].transactionsScanTime = tmpTransactionsScanTime; - } else { - if ( - typeof reformatted[tmpCurrency.walletHash][ - tmpCurrency.currencyCode - ] !== 'undefined' - ) { - if ( - typeof alreadyCounted[tmpCurrency.walletHash][ - tmpCurrency.currencyCode - ][tmpCurrency.address] === 'undefined' - ) { - if ( - reformatted[tmpCurrency.walletHash][tmpCurrency.currencyCode] - .balanceScanTime * - 1 > - 0 - ) { - if (tmpCurrency.balanceScanTime * 1 > 0) { - reformatted[tmpCurrency.walletHash][ - tmpCurrency.currencyCode - ].balanceScanTime = Math.min( - tmpCurrency.balanceScanTime * 1, - reformatted[tmpCurrency.walletHash][ - tmpCurrency.currencyCode - ].balanceScanTime * 1 - ); - } else { - reformatted[tmpCurrency.walletHash][ - tmpCurrency.currencyCode - ].balanceScanTime = 0; - } - } else { - reformatted[tmpCurrency.walletHash][ - tmpCurrency.currencyCode - ].balanceScanTime = tmpCurrency.balanceScanTime * 1; - } - reformatted[tmpCurrency.walletHash][ - tmpCurrency.currencyCode - ].transactionsScanTime = 0; - try { - reformatted[tmpCurrency.walletHash][ - tmpCurrency.currencyCode - ].balance = BlocksoftUtils.add( - reformatted[tmpCurrency.walletHash][tmpCurrency.currencyCode] - .balance, - tmpCurrency.balance - ).toString(); - } catch (e) { - Log.errDaemon( - 'UpdateAccountListDaemon error on reformatted ' + e.message - ); - } - try { - reformatted[tmpCurrency.walletHash][ - tmpCurrency.currencyCode - ].unconfirmed = BlocksoftUtils.add( - reformatted[tmpCurrency.walletHash][tmpCurrency.currencyCode] - .unconfirmed, - tmpCurrency.unconfirmed - ).toString(); - } catch (e) { - Log.errDaemon( - 'UpdateAccountListDaemon error on reformatted2 ' + e.message - ); - } - try { - reformatted[tmpCurrency.walletHash][ - tmpCurrency.currencyCode - ].balanceStaked = BlocksoftUtils.add( - reformatted[tmpCurrency.walletHash][tmpCurrency.currencyCode] - .balanceStaked, - tmpCurrency.balanceStaked - ).toString(); - } catch (e) { - Log.errDaemon( - 'UpdateAccountListDaemon error on reformatted3 ' + e.message - ); - } - reformatted[tmpCurrency.walletHash][ - tmpCurrency.currencyCode - ].balanceTxt = ''; - reformatted[tmpCurrency.walletHash][ - tmpCurrency.currencyCode - ].balanceFix = ''; - reformatted[tmpCurrency.walletHash][ - tmpCurrency.currencyCode - ].unconfirmedTxt = ''; - reformatted[tmpCurrency.walletHash][ - tmpCurrency.currencyCode - ].unconfirmedFix = ''; - try { - reformatted[tmpCurrency.walletHash][ - tmpCurrency.currencyCode - ].balanceAddingLog += - ' ' + - tmpCurrency.balance + - ' (' + - tmpCurrency.address + - '/' + - tmpCurrency.balanceScanTime + - ')'; - } catch (e) { - Log.errDaemon( - 'UpdateAccountListDaemon error on balanceAddingLog ' + - e.message - ); - } - alreadyCounted[tmpCurrency.walletHash][tmpCurrency.currencyCode][ - tmpCurrency.address - ] = 1; - } - } else { - alreadyCounted[tmpCurrency.walletHash][tmpCurrency.currencyCode] = - {}; - alreadyCounted[tmpCurrency.walletHash][tmpCurrency.currencyCode][ - tmpCurrency.address - ] = 1; - reformatted[tmpCurrency.walletHash][tmpCurrency.currencyCode] = - tmpCurrency; - try { - reformatted[tmpCurrency.walletHash][ - tmpCurrency.currencyCode - ].balanceAddingLog = - tmpCurrency.balance + - ' (' + - tmpCurrency.address + - '/' + - tmpCurrency.balanceScanTime + - ')'; - } catch (e) { - Log.errDaemon( - 'UpdateAccountListDaemon error on balanceAddingLog2 ' + - e.message - ); - } - } - - if ( - typeof AirDAODict.CurrenciesForTests[ - tmpCurrency.currencyCode + '_SEGWIT' - ] !== 'undefined' - ) { - if ( - tmpCurrency.address.indexOf( - AirDAODict.Currencies[tmpCurrency.currencyCode].addressPrefix - ) === 0 - ) { - reformatted[tmpCurrency.walletHash][ - tmpCurrency.currencyCode - ].legacy = tmpCurrency.address; - } else if ( - tmpCurrency.address.indexOf( - AirDAODict.CurrenciesForTests[ - tmpCurrency.currencyCode + '_SEGWIT' - ].addressPrefix - ) === 0 - ) { - reformatted[tmpCurrency.walletHash][ - tmpCurrency.currencyCode - ].segwit = tmpCurrency.address; - } - } - } - } - - let tmpWalletHash; - DaemonCache.CACHE_WALLET_COUNT = 0; - for (tmpWalletHash of tmpWalletHashes) { - DaemonCache.CACHE_ALL_ACCOUNTS[tmpWalletHash] = {}; - DaemonCache.CACHE_WALLET_SUMS[tmpWalletHash] = { - balance: 0, - unconfirmed: 0, - balanceStaked: 0, - balanceAddingLog: '', - balanceAddingLogHidden: '', - balanceAddingLogZero: '', - basicCurrencySymbol: basicCurrency.symbol || '$' - }; - DaemonCache.CACHE_WALLET_TOTAL.balance = 0; - DaemonCache.CACHE_WALLET_TOTAL.unconfirmed = 0; - DaemonCache.CACHE_WALLET_TOTAL.basicCurrencySymbol = - basicCurrency.symbol || '$'; - DaemonCache.CACHE_WALLET_COUNT++; - } - - for (tmpCurrency of currentStore.currencyStore.cryptoCurrencies) { - const currencyCode = tmpCurrency.currencyCode; - if (currencyCode === 'NFT' || currencyCode === 'CASHBACK') continue; - let rate = 0; - if ( - !tmpCurrency.currencyRateJson || - typeof tmpCurrency.currencyRateJson[basicCurrencyCode] === 'undefined' - ) { - if (basicCurrencyCode === 'USD') { - rate = tmpCurrency.currencyRateUsd || 0; - } - } else { - rate = tmpCurrency.currencyRateJson[basicCurrencyCode] || 0; - } - if (rate.toString().indexOf('e-')) { - rate = BlocksoftUtils.fromENumber(rate.toString()); - } - if (currencyCode === 'BTC') { - Log.daemon( - 'UpdateAccountListDaemon rate BTC ' + - rate + - ' with ' + - JSON.stringify(basicCurrency) - ); - } - DaemonCache.CACHE_RATES[currencyCode] = { - basicCurrencyRate: rate, - basicCurrencySymbol: basicCurrency.symbol || '$' - }; - - for (tmpWalletHash of tmpWalletHashes) { - // console.log('updateAccounts ' + tmpWalletHash + ' ' + currencyCode + ' ' + rate, reformatted[tmpWalletHash][currencyCode]) - if (typeof reformatted[tmpWalletHash][currencyCode] === 'undefined') { - if ( - currencyCode === 'BTC' && - typeof walletPub[tmpWalletHash] !== 'undefined' - ) { - Log.daemon( - 'UpdateAccountListDaemon need to generate ' + - tmpWalletHash + - ' ' + - currencyCode - ); - - const res = await walletPubDS.discoverMoreAccounts( - { - currencyCode: 'BTC', - walletHash: tmpWalletHash, - needLegacy: true, - needSegwit: true - }, - '_SET_ACCOUNT_LIST' - ); - - if (res && source !== 'regenerate') { - this._canUpdate = true; - return this.updateAccountListDaemon({ source: 'regenerate' }); - } - } else { - if (config.debug.appErrors) { - //console.log('UpdateAccountListDaemon skip as no account ' + tmpWalletHash + ' ' + currencyCode + ' but started regeneration') - } - //Log.daemon('UpdateAccountListDaemon skip as no account ' + tmpWalletHash + ' ' + currencyCode + ' but started regeneration') - - // low code as something strange if no account but there is currency - const mnemonic = await BlocksoftKeysStorage.getWalletMnemonic( - tmpWalletHash, - 'UpdateAccountListDaemon' - ); - let accounts = await BlocksoftKeys.discoverAddresses( - { - mnemonic, - fullTree: false, - fromIndex: 0, - toIndex: 1, - currencyCode - }, - 'APP_ACCOUNTS_LIST' - ); - if (typeof accounts[currencyCode] === 'undefined') { - if (config.debug.appErrors) { - //console.log('UpdateAccountListDaemon skip as no account ' + tmpWalletHash + ' ' + currencyCode + ' fail regeneration') - } - //Log.daemon('UpdateAccountListDaemon skip as no account ' + tmpWalletHash + ' ' + currencyCode + ' fail regeneration') - } else { - const prepare = []; - for (const account of accounts[currencyCode]) { - const derivationPath = Database.escapeString(account.path); - let accountJson = ''; - if (typeof account.addedData !== 'undefined') { - accountJson = Database.escapeString( - JSON.stringify(account.addedData) - ); - } - const tmp = { - address: account.address, - name: '', - derivationPath: derivationPath, - derivationIndex: account.index, - derivationType: account.type, - alreadyShown: account.alreadyShown ? 1 : 0, - status: 0, - currencyCode, - walletHash: tmpWalletHash, - walletPubId: 0, - accountJson: accountJson, - transactionsScanTime: 0 - }; - if (typeof account.walletPubId !== 'undefined') { - tmp.walletPubId = account.walletPubId; - params.walletPubId = tmp.walletPubId; - } else if (typeof params.walletPubId !== 'undefined') { - tmp.walletPubId = params.walletPubId; - } - prepare.push(tmp); - } - if (prepare.length === 0) { - if (config.debug.appErrors) { - console.log( - 'UpdateAccountListDaemon skip as no account ' + - tmpWalletHash + - ' ' + - currencyCode + - ' fail prepare' - ); - } - Log.daemon( - 'UpdateAccountListDaemon skip as no account ' + - tmpWalletHash + - ' ' + - currencyCode + - ' fail prepare' - ); - } else { - await Database.setTableName('account') - .setInsertData({ insertObjs: prepare }) - .insert(); - } - } - // end low code - } - continue; - } - const accountWallet = allWallets.find( - (item) => item.walletHash === tmpWalletHash - ); - const account = reformatted[tmpWalletHash][currencyCode]; - - const extendCurrencyCode = AirDAODict.getCurrencyAllSettings( - account.currencyCode - ); - account.feesCurrencyCode = - extendCurrencyCode.feesCurrencyCode || account.currencyCode; - const extendedFeesCode = AirDAODict.getCurrencyAllSettings( - account.feesCurrencyCode - ); - account.feesCurrencySymbol = - extendedFeesCode.currencySymbol || extendedFeesCode.currencyCode; - - account.feeRates = DaemonCache.getCacheRates( - account.feesCurrencyCode - ); - account.walletUseUnconfirmed = accountWallet?.walletUseUnconfirmed; - try { - account.balanceRaw = BlocksoftTransferUtils.getBalanceForTransfer({ - balance: account.balance, - unconfirmed: - accountWallet && accountWallet?.walletUseUnconfirmed === 1 - ? account.unconfirmed - : false, - balanceStaked: account.balanceStaked, - currencyCode - }); - } catch (e) { - Log.errDaemon( - 'UpdateAccountListDaemon error on account.balanceRaw ' + e.message - ); - account.balanceRaw = account.balance; - } - account.currencySymbol = tmpCurrency.currencySymbol; - account.basicCurrencyRate = rate; - account.basicCurrencyCode = basicCurrencyCode; - account.basicCurrencySymbol = basicCurrency.symbol || '$'; - account.balancePretty = 0; - if (account.balance > 0) { - try { - account.balancePretty = BlocksoftPrettyNumbers.setCurrencyCode( - currencyCode - ).makePretty(account.balance, 'updateAccountListDaemon.balance'); - } catch (e) { - Log.errDaemon( - 'UpdateAccountListDaemon error on account.balance makePretty ' + - e.message - ); - } - } - account.unconfirmedPretty = 0; - if (account.unconfirmed > 0) { - try { - account.unconfirmedPretty = - BlocksoftPrettyNumbers.setCurrencyCode(currencyCode).makePretty( - account.unconfirmed, - 'updateAccountListDaemon.unconfirmed' - ); - } catch (e) { - Log.errDaemon( - 'UpdateAccountListDaemon error on account.unconfirmed makePretty ' + - e.message - ); - } - } - account.balanceStakedPretty = 0; - if (account.balanceStaked > 0) { - try { - account.balanceStakedPretty = - BlocksoftPrettyNumbers.setCurrencyCode(currencyCode).makePretty( - account.balanceStaked, - 'updateAccountListDaemon.balanceStaked' - ); - } catch (e) { - Log.errDaemon( - 'UpdateAccountListDaemon error on account.balanceStaked makePretty ' + - e.message - ); - } - } - account.balanceTotalPretty = 0; - if (account.balanceRaw > 0) { - try { - account.balanceTotalPretty = - BlocksoftPrettyNumbers.setCurrencyCode(currencyCode).makePretty( - account.balanceRaw, - 'updateAccountListDaemon.balanceRaw' - ); - } catch (e) { - Log.errDaemon( - 'UpdateAccountListDaemon error on account.balanceRaw makePretty ' + - e.message - ); - } - } - if (rate > 0) { - account.basicCurrencyBalanceNorm = account.balancePretty; - account.basicCurrencyUnconfirmedNorm = account.unconfirmedPretty; - account.basicCurrencyBalanceTotalNorm = account.balanceTotalPretty; - if (rate !== 1) { - account.basicCurrencyBalanceNorm = BlocksoftUtils.mul( - account.balancePretty, - rate - ); - account.basicCurrencyUnconfirmedNorm = BlocksoftUtils.mul( - account.unconfirmedPretty, - rate - ); - account.basicCurrencyBalanceTotalNorm = BlocksoftUtils.mul( - account.balanceTotalPretty, - rate - ); - } - account.basicCurrencyBalance = BlocksoftPrettyNumbers.makeCut( - account.basicCurrencyBalanceNorm, - 2 - ).separated; - account.basicCurrencyUnconfirmed = BlocksoftPrettyNumbers.makeCut( - account.basicCurrencyUnconfirmedNorm, - 2 - ).separated; - account.basicCurrencyBalanceTotal = BlocksoftPrettyNumbers.makeCut( - account.basicCurrencyBalanceTotalNorm, - 2 - ).separated; - - let str = ''; - str += account.balancePretty + ' ' + account.currencyCode; - if (account.address) { - str += ' ' + account.address; - } - str += - ' ' + - account.basicCurrencyBalance + - ' ' + - account.basicCurrencyCode + - ', '; - - const mask = Number(tmpCurrency.isHidden || 0) - .toString(2) - .split('') - .reverse(); // split to binary - let maskedHidden = true; - if (accountWallet) { - if (typeof mask[accountWallet?.walletNumber] === 'undefined') { - maskedHidden = - mask.length === 1 ? mask[mask.length - 1] === '1' : false; - } else { - maskedHidden = mask[accountWallet?.walletNumber] === '1'; - } - } - - if (!maskedHidden) { - if (account.basicCurrencyBalanceNorm > 0) { - try { - DaemonCache.CACHE_WALLET_SUMS[ - tmpWalletHash - ].balanceAddingLog += str; - DaemonCache.CACHE_WALLET_SUMS[tmpWalletHash].balance = - BlocksoftUtils.add( - DaemonCache.CACHE_WALLET_SUMS[tmpWalletHash].balance, - account.basicCurrencyBalanceNorm - ); - DaemonCache.CACHE_WALLET_TOTAL.balance = BlocksoftUtils.add( - DaemonCache.CACHE_WALLET_TOTAL.balance, - account.basicCurrencyBalanceNorm - ); - } catch (e) { - Log.errDaemon( - 'UpdateAccountListDaemon error on sum ' + e.message - ); - } - } else { - DaemonCache.CACHE_WALLET_SUMS[ - tmpWalletHash - ].balanceAddingLogZero += str; - } - - if (account.basicCurrencyUnconfirmedNorm > 0) { - try { - DaemonCache.CACHE_WALLET_SUMS[tmpWalletHash].unconfirmed = - BlocksoftUtils.add( - DaemonCache.CACHE_WALLET_SUMS[tmpWalletHash].unconfirmed, - account.basicCurrencyUnconfirmedNorm - ); - DaemonCache.CACHE_WALLET_TOTAL.unconfirmed = - BlocksoftUtils.add( - DaemonCache.CACHE_WALLET_TOTAL.unconfirmed, - account.basicCurrencyUnconfirmedNorm - ); - } catch (e) { - Log.errDaemon( - 'UpdateAccountListDaemon error on sum2 ' + e.message - ); - } - } - } else { - if (account.basicCurrencyBalanceNorm > 0) { - DaemonCache.CACHE_WALLET_SUMS[ - tmpWalletHash - ].balanceAddingLogHidden += str; - } else { - DaemonCache.CACHE_WALLET_SUMS[ - tmpWalletHash - ].balanceAddingLogZero += str; - } - } - } else { - account.basicCurrencyBalance = 0; - account.basicCurrencyUnconfirmed = 0; - account.basicCurrencyBalanceNorm = 0; - account.basicCurrencyUnconfirmedNorm = 0; - account.basicCurrencyBalanceTotal = 0; - account.basicCurrencyBalanceTotalNorm = 0; - } - - DaemonCache.CACHE_ALL_ACCOUNTS[tmpWalletHash][currencyCode] = account; - DaemonCache.CACHE_WALLET_SUMS[tmpWalletHash].balance = - BlocksoftPrettyNumbers.makeCut( - DaemonCache.CACHE_WALLET_SUMS[tmpWalletHash].balance, - 2 - ).justCutted; - DaemonCache.CACHE_WALLET_SUMS[tmpWalletHash].unconfirmed = - BlocksoftPrettyNumbers.makeCut( - DaemonCache.CACHE_WALLET_SUMS[tmpWalletHash].unconfirmed, - 2 - ).justCutted; - } - DaemonCache.CACHE_WALLET_TOTAL.balance = BlocksoftPrettyNumbers.makeCut( - DaemonCache.CACHE_WALLET_TOTAL.balance, - 2 - ).justCutted; - DaemonCache.CACHE_WALLET_TOTAL.unconfirmed = - BlocksoftPrettyNumbers.makeCut( - DaemonCache.CACHE_WALLET_TOTAL.unconfirmed, - 2 - ).justCutted; - } - - for (const tmpWalletHash in DaemonCache.CACHE_WALLET_SUMS) { - const cacheBalanceString = - DaemonCache.CACHE_WALLET_SUMS[tmpWalletHash].balanceAddingLog + - DaemonCache.CACHE_WALLET_SUMS[tmpWalletHash].balanceAddingLogHidden; - MarketingEvent.setBalance(tmpWalletHash, 'TOTAL', cacheBalanceString, { - totalBalance: DaemonCache.CACHE_WALLET_SUMS[tmpWalletHash].balance, - totalBalanceString: - DaemonCache.CACHE_WALLET_SUMS[tmpWalletHash].balanceAddingLog, - hiddenBalanceString: - DaemonCache.CACHE_WALLET_SUMS[tmpWalletHash].balanceAddingLogHidden, - basicCurrencyCode, - walletHash: tmpWalletHash - }); - } - - if (typeof DaemonCache.CACHE_ALL_ACCOUNTS !== 'undefined') { - store.dispatch({ - type: 'SET_ACCOUNT_LIST', - accountList: DaemonCache.CACHE_ALL_ACCOUNTS - }); - } - walletActions.setWalletsGeneralData( - DaemonCache.CACHE_WALLET_TOTAL.balance, - DaemonCache.CACHE_WALLET_TOTAL.basicCurrencySymbol - ); - - try { - if ( - selectedAccount && - typeof selectedAccount !== 'undefined' && - typeof selectedAccount.currencyCode !== 'undefined' && - typeof selectedAccount.walletHash !== 'undefined' && - selectedAccount.currencyCode !== 'NFT' && - selectedAccount.currencyCode !== 'CASHBACK' - ) { - if ( - typeof DaemonCache.CACHE_ALL_ACCOUNTS[selectedAccount.walletHash][ - selectedAccount.currencyCode - ] === 'undefined' - ) { - Log.daemon( - 'UpdateAccountListDaemon error when no selected ' + - selectedAccount.currencyCode - ); - } else { - if ( - DaemonCache.CACHE_ALL_ACCOUNTS[selectedAccount.walletHash][ - selectedAccount.currencyCode - ].balance !== selectedAccount.balance - ) { - await setSelectedAccount('UpdateAccountListDaemon'); - await setSelectedAccountTransactions('UpdateAccountListDaemon'); - } - } - } - } catch (e) { - Log.errDaemon( - 'UpdateAccountListDaemon error on setSelected ' + e.message - ); - } - - // FIXME: remove it, temporary solution to fix UI changing on home screen when user change local currency - store.dispatch({ - type: 'SET_SELECTED_BASIC_CURRENCY', - selectedBasicCurrency: currentStore.mainStore.selectedBasicCurrency - }); - - this._canUpdate = true; - } catch (e) { - this._canUpdate = true; - Log.errDaemon('UpdateAccountListDaemon error ' + e.message); - } - return DaemonCache.CACHE_ALL_ACCOUNTS; - }; -} - -export default new UpdateAccountListDaemon(); diff --git a/src/lib/helpers/AirDAOKeys.ts b/src/lib/helpers/AirDAOKeys.ts index 158a3436d..00f42aa06 100644 --- a/src/lib/helpers/AirDAOKeys.ts +++ b/src/lib/helpers/AirDAOKeys.ts @@ -5,7 +5,7 @@ */ import AirDAODict from '@crypto/common/AirDAODict'; // @ts-ignore -import BlocksoftKeysScam from '@crypto/actions/BlocksoftKeys/BlocksoftKeysScam'; +// import BlocksoftKeysScam from '@crypto/actions/BlocksoftKeys/BlocksoftKeysScam'; import AirDAODispatcher from '@crypto/blockchains/AirDAODispatcher'; import networksConstants from '@crypto/common/ext/networks-constants'; import bip44Constants from '@crypto/common/ext/bip44-constants'; @@ -22,7 +22,7 @@ const CACHE_ROOTS = {}; interface CacheRoots { [key: string]: any; } -class BlocksoftKeys { +class AirDAOKeys { private CACHE_ROOTS: CacheRoots = {}; _bipHex: { [key: string]: string }; _getRandomBytesFunction; @@ -71,16 +71,16 @@ class BlocksoftKeys { * @param {string} mnemonic * @return {Promise} */ - async validateMnemonic(mnemonic: string): Promise { - if (await BlocksoftKeysScam.isScamMnemonic(mnemonic)) { - throw new Error('Scam import error'); - } - const result = await bip39.validateMnemonic(mnemonic); - if (!result) { - throw new Error('invalid mnemonic for bip39'); - } - return result; - } + // async validateMnemonic(mnemonic: string): Promise { + // if (await BlocksoftKeysScam.isScamMnemonic(mnemonic)) { + // throw new Error('Scam import error'); + // } + // const result = await bip39.validateMnemonic(mnemonic); + // if (!result) { + // throw new Error('invalid mnemonic for bip39'); + // } + // return result; + // } /** * @param {string} data.mnemonic @@ -143,7 +143,7 @@ class BlocksoftKeys { try { settings = AirDAODict.getCurrencyAllSettings( currencyCode, - 'BlocksoftKeys' + 'AirDAOKeys' ); } catch (e) { // do nothing for now @@ -481,5 +481,5 @@ function serialize(hdkey, version, key, LEN = 78) { return buffer; } -const singleBlocksoftKeys = new BlocksoftKeys(); +const singleBlocksoftKeys = new AirDAOKeys(); export default singleBlocksoftKeys; From 9a72a97204e437d6af6f1c5d252330cd778d60f4 Mon Sep 17 00:00:00 2001 From: illiaa Date: Wed, 23 Aug 2023 17:05:04 +0300 Subject: [PATCH 070/509] refactoring crypto files --- crypto/blockchains/AirDAODispatcher.ts | 211 +-- .../blockchains/AirDAOTransferDispatcher.ts | 46 +- .../blockchains/etc/EtcTransferProcessor.ts | 2 - crypto/blockchains/eth/EthAddressProcessor.ts | 4 - crypto/blockchains/eth/EthScannerProcessor.ts | 1541 ++++++++--------- .../eth/EthScannerProcessorErc20.ts | 169 +- .../blockchains/eth/EthTokenProcessorErc20.ts | 77 - .../blockchains/eth/EthTokenProcessorNft.ts | 103 +- crypto/blockchains/eth/apis/EthNftMatic.ts | 1 - crypto/blockchains/eth/apis/EthNftOpensea.ts | 17 +- crypto/blockchains/eth/basic/EthBasic.ts | 361 ++-- .../blockchains/eth/basic/EthNetworkPrices.ts | 54 +- .../eth/forks/EthScannerProcessorSoul.ts | 73 - crypto/blockchains/eth/stores/EthRawDS.ts | 11 +- crypto/blockchains/eth/stores/EthTmpDS.ts | 47 +- crypto/common/AirDAOAxios.ts | 186 +- crypto/common/AirDAOBN.ts | 6 +- crypto/common/AirDAOCryptoLog.ts | 44 +- crypto/common/AirDAOCryptoUtils.ts | 9 - crypto/common/AirDAODict.ts | 198 +-- crypto/common/AirDAODictNfts.ts | 125 -- crypto/common/AirDAODictTypes.ts | 96 +- crypto/common/AirDAOExplorerDict.ts | 32 +- crypto/common/AirDAOExternalSettings.ts | 281 ++- crypto/common/AirDAOPrettyNumbers.ts | 280 +-- crypto/common/AirDAOQrScanDict.ts | 101 -- src/utils/blockchain.ts | 185 +- 27 files changed, 1968 insertions(+), 2292 deletions(-) delete mode 100644 crypto/blockchains/eth/EthTokenProcessorErc20.ts delete mode 100644 crypto/blockchains/eth/forks/EthScannerProcessorSoul.ts delete mode 100644 crypto/common/AirDAOCryptoUtils.ts delete mode 100644 crypto/common/AirDAODictNfts.ts delete mode 100644 crypto/common/AirDAOQrScanDict.ts diff --git a/crypto/blockchains/AirDAODispatcher.ts b/crypto/blockchains/AirDAODispatcher.ts index b3749e19b..bb9877068 100644 --- a/crypto/blockchains/AirDAODispatcher.ts +++ b/crypto/blockchains/AirDAODispatcher.ts @@ -5,71 +5,50 @@ // import BchAddressProcessor from '@crypto/blockchains/bch/BchAddressProcessor'; // import BchScannerProcessor from '@crypto/blockchains/bch/BchScannerProcessor'; - // import BsvScannerProcessor from '@crypto/blockchains/bsv/BsvScannerProcessor'; - // import BtcAddressProcessor from '@crypto/blockchains/btc/address/BtcAddressProcessor'; // import BtcScannerProcessor from '@crypto/blockchains/btc/BtcScannerProcessor'; - // import BtcSegwitCompatibleAddressProcessor from '@crypto/blockchains/btc/address/BtcSegwitCompatibleAddressProcessor'; // import BtcSegwitAddressProcessor from '@crypto/blockchains/btc/address/BtcSegwitAddressProcessor'; - // import BtcTestScannerProcessor from '@crypto/blockchains/btc_test/BtcTestScannerProcessor'; - // import BtgScannerProcessor from '@crypto/blockchains/btg/BtgScannerProcessor'; - // import DogeScannerProcessor from '@crypto/blockchains/doge/DogeScannerProcessor'; - import EthAddressProcessor from '@crypto/blockchains/eth/EthAddressProcessor'; import EthScannerProcessor from '@crypto/blockchains/eth/EthScannerProcessor'; import EthScannerProcessorErc20 from '@crypto/blockchains/eth/EthScannerProcessorErc20'; import { BlockchainUtils } from '@utils/blockchain'; // import EthScannerProcessorSoul from '@crypto/blockchains/eth/forks/EthScannerProcessorSoul'; -import EthTokenProcessorErc20 from '@crypto/blockchains/eth/EthTokenProcessorErc20'; - +// import EthTokenProcessorErc20 from '@crypto/blockchains/eth/EthTokenProcessorErc20'; // import LtcScannerProcessor from '@crypto/blockchains/ltc/LtcScannerProcessor'; - // import TrxAddressProcessor from '@crypto/blockchains/trx/TrxAddressProcessor'; // import TrxScannerProcessor from '@crypto/blockchains/trx/TrxScannerProcessor'; // import TrxTokenProcessor from '@crypto/blockchains/trx/TrxTokenProcessor'; - // import UsdtScannerProcessor from '@crypto/blockchains/usdt/UsdtScannerProcessor'; - // import XrpAddressProcessor from '@crypto/blockchains/xrp/XrpAddressProcessor'; // import XrpScannerProcessor from '@crypto/blockchains/xrp/XrpScannerProcessor'; - // import XlmAddressProcessor from '@crypto/blockchains/xlm/XlmAddressProcessor'; // import XlmScannerProcessor from '@crypto/blockchains/xlm/XlmScannerProcessor'; - // import XvgScannerProcessor from '@crypto/blockchains/xvg/XvgScannerProcessor'; - // import XmrAddressProcessor from '@crypto/blockchains/xmr/XmrAddressProcessor'; // import XmrScannerProcessor from '@crypto/blockchains/xmr/XmrScannerProcessor'; // import XmrSecretsProcessor from '@crypto/blockchains/xmr/XmrSecretsProcessor'; // import FioAddressProcessor from '@crypto/blockchains/fio/FioAddressProcessor'; // import FioScannerProcessor from '@crypto/blockchains/fio/FioScannerProcessor'; - // import BnbAddressProcessor from '@crypto/blockchains/bnb/BnbAddressProcessor'; // import BnbScannerProcessor from '@crypto/blockchains/bnb/BnbScannerProcessor'; // import { BlockchainUtils } from '@utils/blockchain'; - // import VetScannerProcessor from '@crypto/blockchains/vet/VetScannerProcessor'; - // import SolAddressProcessor from '@crypto/blockchains/sol/SolAddressProcessor'; // import SolScannerProcessor from '@crypto/blockchains/sol/SolScannerProcessor'; - // import WavesAddressProcessor from '@crypto/blockchains/waves/WavesAddressProcessor'; // import WavesScannerProcessor from '@crypto/blockchains/waves/WavesScannerProcessor'; - // import SolScannerProcessorSpl from '@crypto/blockchains/sol/SolScannerProcessorSpl'; // import SolTokenProcessor from '@crypto/blockchains/sol/SolTokenProcessor'; -import EthTokenProcessorNft from '@crypto/blockchains/eth/EthTokenProcessorNft'; +// import EthTokenProcessorNft from '@crypto/blockchains/eth/EthTokenProcessorNft'; // import AshAddressProcessor from '@crypto/blockchains/ash/AshAddressProcessor'; - // import MetisScannerProcessor from '@crypto/blockchains/metis/MetisScannerProcessor'; // import OneScannerProcessor from '@crypto/blockchains/one/OneScannerProcessor'; // import OneScannerProcessorErc20 from '@crypto/blockchains/one/OneScannerProcessorErc20'; - // import WavesScannerProcessorErc20 from '@crypto/blockchains/waves/WavesScannerProcessorErc20'; class AirDAODispatcher { @@ -218,101 +197,101 @@ class AirDAODispatcher { } // TODO enum tokenType - getTokenProcessor(tokenType: string): EthTokenProcessorErc20 { - switch (tokenType) { - case 'ETH_ERC_20': - return new EthTokenProcessorErc20({ - network: 'mainnet', - tokenBlockchain: 'ETHEREUM' - }); - case 'BNB_SMART_20': - return new EthTokenProcessorErc20({ - network: 'mainnet', - tokenBlockchain: 'BNB' - }); - // case 'MATIC_ERC_20': - // return new EthTokenProcessorErc20({ - // network: 'mainnet', - // tokenBlockchain: 'MATIC' - // }); - // case 'FTM_ERC_20': - // return new EthTokenProcessorErc20({ - // network: 'mainnet', - // tokenBlockchain: 'FTM' - // }); - // case 'VLX_ERC_20': - // return new EthTokenProcessorErc20({ - // network: 'mainnet', - // tokenBlockchain: 'VLX' - // }); - // case 'ONE_ERC_20': - // return new EthTokenProcessorErc20({ - // network: 'mainnet', - // tokenBlockchain: 'ONE' - // }); - // case 'METIS_ERC_20': - // return new EthTokenProcessorErc20({ - // network: 'mainnet', - // tokenBlockchain: 'METIS' - // }); - // case 'TRX': - // return new TrxTokenProcessor(); - // case 'SOL': - // return new SolTokenProcessor(); - default: - throw new Error('Unknown tokenProcessor ' + tokenType); - } - } + // getTokenProcessor(tokenType: string): EthTokenProcessorErc20 { + // switch (tokenType) { + // case 'ETH_ERC_20': + // return new EthTokenProcessorErc20({ + // network: 'mainnet', + // tokenBlockchain: 'ETHEREUM' + // }); + // case 'BNB_SMART_20': + // return new EthTokenProcessorErc20({ + // network: 'mainnet', + // tokenBlockchain: 'BNB' + // }); + // // case 'MATIC_ERC_20': + // // return new EthTokenProcessorErc20({ + // // network: 'mainnet', + // // tokenBlockchain: 'MATIC' + // // }); + // // case 'FTM_ERC_20': + // // return new EthTokenProcessorErc20({ + // // network: 'mainnet', + // // tokenBlockchain: 'FTM' + // // }); + // // case 'VLX_ERC_20': + // // return new EthTokenProcessorErc20({ + // // network: 'mainnet', + // // tokenBlockchain: 'VLX' + // // }); + // // case 'ONE_ERC_20': + // // return new EthTokenProcessorErc20({ + // // network: 'mainnet', + // // tokenBlockchain: 'ONE' + // // }); + // // case 'METIS_ERC_20': + // // return new EthTokenProcessorErc20({ + // // network: 'mainnet', + // // tokenBlockchain: 'METIS' + // // }); + // // case 'TRX': + // // return new TrxTokenProcessor(); + // // case 'SOL': + // // return new SolTokenProcessor(); + // default: + // throw new Error('Unknown tokenProcessor ' + tokenType); + // } + // } // TODO enum tokenBlockchainCode - getTokenNftsProcessor(tokenBlockchainCode: string) { - switch (tokenBlockchainCode) { - case 'ETH': - case 'NFT_ETH': - return new EthTokenProcessorNft({ - network: 'mainnet', - tokenBlockchain: 'ETHEREUM', - tokenBlockchainCode: 'ETH' - }); - case 'ETH_RINKEBY': - case 'NFT_RINKEBY': - return new EthTokenProcessorNft({ - network: 'rinkeby', - tokenBlockchain: 'RINKEBY', - tokenBlockchainCode: 'ETH_RINKEBY' - }); - case 'MATIC': - case 'NFT_MATIC': - return new EthTokenProcessorNft({ - network: 'mainnet', - tokenBlockchain: 'MATIC', - tokenBlockchainCode: 'MATIC' - }); - case 'BNB': - case 'NFT_BNB': - return new EthTokenProcessorNft({ - network: 'mainnet', - tokenBlockchain: 'BNB', - tokenBlockchainCode: 'BNB' - }); - case 'ONE': - case 'NFT_ONE': - return new EthTokenProcessorNft({ - network: 'mainnet', - tokenBlockchain: 'ONE', - tokenBlockchainCode: 'ONE' - }); - case 'ETH_ROPSTEN': - case 'NFT_ROPSTEN': - return new EthTokenProcessorNft({ - network: 'ropsten', - tokenBlockchain: 'ROPSTEN', - tokenBlockchainCode: 'ETH_ROPSTEN' - }); - default: - throw new Error('Unknown NFT tokenProcessor ' + tokenBlockchainCode); - } - } + // getTokenNftsProcessor(tokenBlockchainCode: string) { + // switch (tokenBlockchainCode) { + // case 'ETH': + // case 'NFT_ETH': + // return new EthTokenProcessorNft({ + // network: 'mainnet', + // tokenBlockchain: 'ETHEREUM', + // tokenBlockchainCode: 'ETH' + // }); + // case 'ETH_RINKEBY': + // case 'NFT_RINKEBY': + // return new EthTokenProcessorNft({ + // network: 'rinkeby', + // tokenBlockchain: 'RINKEBY', + // tokenBlockchainCode: 'ETH_RINKEBY' + // }); + // case 'MATIC': + // case 'NFT_MATIC': + // return new EthTokenProcessorNft({ + // network: 'mainnet', + // tokenBlockchain: 'MATIC', + // tokenBlockchainCode: 'MATIC' + // }); + // case 'BNB': + // case 'NFT_BNB': + // return new EthTokenProcessorNft({ + // network: 'mainnet', + // tokenBlockchain: 'BNB', + // tokenBlockchainCode: 'BNB' + // }); + // case 'ONE': + // case 'NFT_ONE': + // return new EthTokenProcessorNft({ + // network: 'mainnet', + // tokenBlockchain: 'ONE', + // tokenBlockchainCode: 'ONE' + // }); + // case 'ETH_ROPSTEN': + // case 'NFT_ROPSTEN': + // return new EthTokenProcessorNft({ + // network: 'ropsten', + // tokenBlockchain: 'ROPSTEN', + // tokenBlockchainCode: 'ETH_ROPSTEN' + // }); + // default: + // throw new Error('Unknown NFT tokenProcessor ' + tokenBlockchainCode); + // } + // } // getSecretsProcessor(currencyCode: string): XmrSecretsProcessor { // const currencyDictSettings = diff --git a/crypto/blockchains/AirDAOTransferDispatcher.ts b/crypto/blockchains/AirDAOTransferDispatcher.ts index 2a7d128ab..db93cdb8d 100644 --- a/crypto/blockchains/AirDAOTransferDispatcher.ts +++ b/crypto/blockchains/AirDAOTransferDispatcher.ts @@ -6,31 +6,31 @@ import AirDAODict from '../common/AirDAODict'; import { AirDAODictTypes } from '../common/AirDAODictTypes'; -import BchTransferProcessor from './bch/BchTransferProcessor'; -import BsvTransferProcessor from './bsv/BsvTransferProcessor'; -import BtcTransferProcessor from './btc/BtcTransferProcessor'; -import BtcTestTransferProcessor from './btc_test/BtcTestTransferProcessor'; -import BtgTransferProcessor from './btg/BtgTransferProcessor'; -import DogeTransferProcessor from './doge/DogeTransferProcessor'; +// import BchTransferProcessor from './bch/BchTransferProcessor'; +// import BsvTransferProcessor from './bsv/BsvTransferProcessor'; +// import BtcTransferProcessor from './btc/BtcTransferProcessor'; +// import BtcTestTransferProcessor from './btc_test/BtcTestTransferProcessor'; +// import BtgTransferProcessor from './btg/BtgTransferProcessor'; +// import DogeTransferProcessor from './doge/DogeTransferProcessor'; import EthTransferProcessor from './eth/EthTransferProcessor'; import EthTransferProcessorErc20 from './eth/EthTransferProcessorErc20'; -import LtcTransferProcessor from './ltc/LtcTransferProcessor'; -import TrxTransferProcessor from './trx/TrxTransferProcessor'; -import UsdtTransferProcessor from './usdt/UsdtTransferProcessor'; -import XrpTransferProcessor from './xrp/XrpTransferProcessor'; -import XlmTransferProcessor from './xlm/XlmTransferProcessor'; -import XvgTransferProcessor from './xvg/XvgTransferProcessor'; -import XmrTransferProcessor from './xmr/XmrTransferProcessor'; -import FioTransferProcessor from './fio/FioTransferProcessor'; -import BnbTransferProcessor from './bnb/BnbTransferProcessor'; -import BnbSmartTransferProcessor from './bnb_smart/BnbSmartTransferProcessor'; -import BnbSmartTransferProcessorErc20 from './bnb_smart/BnbSmartTransferProcessorErc20'; -import EtcTransferProcessor from './etc/EtcTransferProcessor'; -import VetTransferProcessor from '@crypto/blockchains/vet/VetTransferProcessor'; -import SolTransferProcessor from '@crypto/blockchains/sol/SolTransferProcessor'; -import SolTransferProcessorSpl from '@crypto/blockchains/sol/SolTransferProcessorSpl'; -import WavesTransferProcessor from '@crypto/blockchains/waves/WavesTransferProcessor'; -import MetisTransferProcessor from '@crypto/blockchains/metis/MetisTransferProcessor'; +// import LtcTransferProcessor from './ltc/LtcTransferProcessor'; +// import TrxTransferProcessor from './trx/TrxTransferProcessor'; +// import UsdtTransferProcessor from './usdt/UsdtTransferProcessor'; +// import XrpTransferProcessor from './xrp/XrpTransferProcessor'; +// import XlmTransferProcessor from './xlm/XlmTransferProcessor'; +// import XvgTransferProcessor from './xvg/XvgTransferProcessor'; +// import XmrTransferProcessor from './xmr/XmrTransferProcessor'; +// import FioTransferProcessor from './fio/FioTransferProcessor'; +// import BnbTransferProcessor from './bnb/BnbTransferProcessor'; +// import BnbSmartTransferProcessor from './bnb_smart/BnbSmartTransferProcessor'; +// import BnbSmartTransferProcessorErc20 from './bnb_smart/BnbSmartTransferProcessorErc20'; +// import EtcTransferProcessor from './etc/EtcTransferProcessor'; +// import VetTransferProcessor from '@crypto/blockchains/vet/VetTransferProcessor'; +// import SolTransferProcessor from '@crypto/blockchains/sol/SolTransferProcessor'; +// import SolTransferProcessorSpl from '@crypto/blockchains/sol/SolTransferProcessorSpl'; +// import WavesTransferProcessor from '@crypto/blockchains/waves/WavesTransferProcessor'; +// import MetisTransferProcessor from '@crypto/blockchains/metis/MetisTransferProcessor'; import { AirDAOBlockchainTypes } from './AirDAOBlockchainTypes'; diff --git a/crypto/blockchains/etc/EtcTransferProcessor.ts b/crypto/blockchains/etc/EtcTransferProcessor.ts index a9db3bff0..562007d1f 100644 --- a/crypto/blockchains/etc/EtcTransferProcessor.ts +++ b/crypto/blockchains/etc/EtcTransferProcessor.ts @@ -3,10 +3,8 @@ * @version 0.43 */ import { AirDAOBlockchainTypes } from '@crypto/blockchains/AirDAOBlockchainTypes'; -// import BnbSmartTransferProcessor from '@crypto/blockchains/bnb_smart/BnbSmartTransferProcessor'; export default class EtcTransferProcessor - // extends BnbSmartTransferProcessor implements AirDAOBlockchainTypes.TransferProcessor { canRBF( diff --git a/crypto/blockchains/eth/EthAddressProcessor.ts b/crypto/blockchains/eth/EthAddressProcessor.ts index 86196ebbc..496df3d19 100644 --- a/crypto/blockchains/eth/EthAddressProcessor.ts +++ b/crypto/blockchains/eth/EthAddressProcessor.ts @@ -12,9 +12,7 @@ export default class EthAddressProcessor extends EthBasic { * @returns {Promise<{privateKey: string, address: string, addedData: *}>} */ async getAddress(privateKey: string | Buffer, data = {}) { - // noinspection JSCheckFunctionSignatures privateKey = '0x' + privateKey.toString('hex'); - // noinspection JSUnresolvedVariable const account = this._web3.eth.accounts.privateKeyToAccount(privateKey); return { address: account.address, privateKey, addedData: false }; } @@ -30,11 +28,9 @@ export default class EthAddressProcessor extends EthBasic { s: string; signature: string; }> { - // noinspection JSUnresolvedVariable if (privateKey.substr(0, 2) !== '0x') { privateKey = '0x' + privateKey; } - // noinspection JSUnresolvedVariable const signData = await this._web3.eth.accounts.sign(msg, privateKey); return signData; } diff --git a/crypto/blockchains/eth/EthScannerProcessor.ts b/crypto/blockchains/eth/EthScannerProcessor.ts index 5d600341d..b04bcb602 100644 --- a/crypto/blockchains/eth/EthScannerProcessor.ts +++ b/crypto/blockchains/eth/EthScannerProcessor.ts @@ -6,37 +6,36 @@ import AirDAOAxios from '../../common/AirDAOAxios'; import AirDAOCryptoLog from '../../common/AirDAOCryptoLog'; import EthBasic from './basic/EthBasic'; import BlocksoftExternalSettings from '../../common/AirDAOExternalSettings'; -import BlocksoftBN from '../../common/AirDAOBN'; -import EthTmpDS from './stores/EthTmpDS'; -import EthRawDS from './stores/EthRawDS'; -import BlocksoftPrettyNumbers from '@crypto/common/AirDAOPrettyNumbers'; -import { BlockchainTransaction } from '@appTypes'; +// import EthTmpDS from './stores/EthTmpDS'; +// import EthRawDS from './stores/EthRawDS'; +// import BlocksoftPrettyNumbers from '@crypto/common/AirDAOPrettyNumbers'; -const CACHE_GET_MAX_BLOCK = { - ETH: { max_block_number: 0, confirmations: 0 }, - BNB: { max_block_number: 0, confirmations: 0 }, - ETC: { max_block_number: 0, confirmations: 0 }, - AMB: { max_block_number: 0, confirmations: 0 }, - MATIC: { max_block_number: 0, confirmations: 0 }, - FTM: { max_block_number: 0, confirmations: 0 }, - RSK: { max_block_number: 0, confirmations: 0 }, - OPTIMISM: { max_block_number: 0, confirmations: 0 }, - METIS: { max_block_number: 0, confirmations: 0 }, - VLX: { max_block_number: 0, confirmations: 0 } -}; -const CACHE_BLOCK_NUMBER_TO_HASH = { - ETH: {}, - BNB: {}, - ETC: {}, - AMB: {}, - MATIC: {}, - FTM: {}, - RSK: {}, - OPTIMISM: {}, - METIS: {}, - VLX: {} -}; +// const CACHE_GET_MAX_BLOCK = { +// ETH: { max_block_number: 0, confirmations: 0 }, +// BNB: { max_block_number: 0, confirmations: 0 }, +// ETC: { max_block_number: 0, confirmations: 0 }, +// AMB: { max_block_number: 0, confirmations: 0 }, +// MATIC: { max_block_number: 0, confirmations: 0 }, +// FTM: { max_block_number: 0, confirmations: 0 }, +// RSK: { max_block_number: 0, confirmations: 0 }, +// OPTIMISM: { max_block_number: 0, confirmations: 0 }, +// METIS: { max_block_number: 0, confirmations: 0 }, +// VLX: { max_block_number: 0, confirmations: 0 } +// }; + +// const CACHE_BLOCK_NUMBER_TO_HASH = { +// ETH: {}, +// BNB: {}, +// ETC: {}, +// AMB: {}, +// MATIC: {}, +// FTM: {}, +// RSK: {}, +// OPTIMISM: {}, +// METIS: {}, +// VLX: {} +// }; const CACHE_VALID_TIME = 30000; // 30 seconds const CACHE = { @@ -57,21 +56,21 @@ export default class EthScannerProcessor extends EthBasic { * @type {number} * @private */ - _blocksToConfirm = 10; + // _blocksToConfirm = 10; /** * @type {boolean} * @private */ - _useInternal = true; + // _useInternal = true; /** * https://eth1.trezor.io/api/v2/address/0x8b661361Be29E688Dda65b323526aD536c8B3997?details=txs - * @param address * @returns {Promise} * @private + * @param _address */ - async _get(_address) { + async _get(_address: string) { const address = _address.toLowerCase(); try { @@ -79,7 +78,7 @@ export default class EthScannerProcessor extends EthBasic { this._trezorServerCode, 'ETH.Scanner._get' ); - } catch (e) { + } catch (e: any) { throw new Error( e.message + ' while getTrezorServer ' + this._trezorServerCode ); @@ -154,7 +153,7 @@ export default class EthScannerProcessor extends EthBasic { const data = res.data; data.totalTokens = 0; data.formattedTokens = {}; - //await AirDAOCryptoLog.log('EthScannerProcessor._get ERC20 tokens ' + JSON.stringify(data.tokens)) + // await AirDAOCryptoLog.log('EthScannerProcessor._get ERC20 tokens ' + JSON.stringify(data.tokens)) if (typeof data.tokens !== 'undefined') { let token; for (token of data.tokens) { @@ -178,281 +177,281 @@ export default class EthScannerProcessor extends EthBasic { * @param {string} address * @return {Promise<{balance, unconfirmed, provider}>} */ - async getBalanceBlockchain(address) { - await AirDAOCryptoLog.log( - this._settings.currencyCode + - ' EthScannerProcessor.getBalance started ' + - address - ); - - this.checkWeb3CurrentServerUpdated(); - // noinspection JSUnresolvedVariable - let balance = 0; - let provider = ''; - let time = 0; - try { - if ( - this._trezorServerCode && - this._trezorServerCode.indexOf('http') === -1 - ) { - const res = await this._get(address); - - if ( - res && - typeof res.data !== 'undefined' && - res.data && - typeof res.data.balance !== 'undefined' - ) { - balance = res.data.balance; - provider = res.provider; - time = res.time; - return { - balance, - unconfirmed: 0, - provider, - time, - balanceScanBlock: res.data.nonce - }; - } - } - } catch (e) { - await AirDAOCryptoLog.log( - this._settings.currencyCode + - ' EthScannerProcessor.getBalance ' + - address + - ' trezor error ' + - e.message - ); - return false; - } - - try { - balance = await this._web3.eth.getBalance(address); - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' EthScannerProcessor.getBalance ' + - address + - ' result ' + - JSON.stringify(balance) - ); - provider = 'web3'; - time = 'now()'; - return { balance, unconfirmed: 0, provider, time }; - } catch (e) { - await AirDAOCryptoLog.log( - this._settings.currencyCode + - ' EthScannerProcessor.getBalance ' + - address + - ' ' + - this._web3.LINK + - ' rpc error ' + - e.message - ); - return false; - } - } + // async getBalanceBlockchain(address) { + // await AirDAOCryptoLog.log( + // this._settings.currencyCode + + // ' EthScannerProcessor.getBalance started ' + + // address + // ); + // + // this.checkWeb3CurrentServerUpdated(); + // // noinspection JSUnresolvedVariable + // let balance = 0; + // let provider = ''; + // let time = 0; + // try { + // if ( + // this._trezorServerCode && + // this._trezorServerCode.indexOf('http') === -1 + // ) { + // const res = await this._get(address); + // + // if ( + // res && + // typeof res.data !== 'undefined' && + // res.data && + // typeof res.data.balance !== 'undefined' + // ) { + // balance = res.data.balance; + // provider = res.provider; + // time = res.time; + // return { + // balance, + // unconfirmed: 0, + // provider, + // time, + // balanceScanBlock: res.data.nonce + // }; + // } + // } + // } catch (e) { + // await AirDAOCryptoLog.log( + // this._settings.currencyCode + + // ' EthScannerProcessor.getBalance ' + + // address + + // ' trezor error ' + + // e.message + // ); + // return false; + // } + // + // try { + // balance = await this._web3.eth.getBalance(address); + // AirDAOCryptoLog.log( + // this._settings.currencyCode + + // ' EthScannerProcessor.getBalance ' + + // address + + // ' result ' + + // JSON.stringify(balance) + // ); + // provider = 'web3'; + // time = 'now()'; + // return { balance, unconfirmed: 0, provider, time }; + // } catch (e) { + // await AirDAOCryptoLog.log( + // this._settings.currencyCode + + // ' EthScannerProcessor.getBalance ' + + // address + + // ' ' + + // this._web3.LINK + + // ' rpc error ' + + // e.message + // ); + // return false; + // } + // } /** * @param {string} scanData.account.address * @return {Promise<[UnifiedTransaction]>} */ - async getTransactionsBlockchain(scanData) { - const address = scanData.account.address; - await AirDAOCryptoLog.log( - this._settings.currencyCode + - ' EthScannerProcessor.getTransactions started ' + - address - ); - let res = false; - if ( - this._settings.currencyCode !== 'ETH_ROPSTEN' && - this._settings.currencyCode !== 'ETH_RINKEBY' && - this._trezorServerCode - ) { - try { - res = await this._get(address); - } catch (e) { - throw new Error(e.message + ' in EthScannerProcessor._get'); - } - } - - let transactions; - if (res && typeof res.data !== 'undefined' && res.data) { - await AirDAOCryptoLog.log( - this._settings.currencyCode + - ' EthScannerProcessor.getBalance loaded from ' + - res.provider + - ' ' + - res.time - ); - if ( - this._tokenAddress && - typeof res.data.formattedTokens[this._tokenAddress] === 'undefined' - ) { - await AirDAOCryptoLog.log( - this._settings.currencyCode + - ' EthScannerProcessor.getTransactions skipped token ' + - this._tokenAddress + - ' ' + - address - ); - return false; - } - await AirDAOCryptoLog.log( - this._settings.currencyCode + - ' EthScannerProcessor.getTransactions trezor unify started ' + - address - ); - transactions = await this._unifyTransactions( - address, - res.data.transactions, - false, - true, - {} - ); - await AirDAOCryptoLog.log( - this._settings.currencyCode + - ' EthScannerProcessor.getTransactions trezor finished ' + - address - ); - } else if (this._oklinkAPI) { - const logTitle = - this._settings.currencyCode + - ' EthScannerProcessor.getTransactions oklink '; - transactions = await this._getFromOklink( - address, - this._oklinkAPI, - logTitle, - {} - ); - } else { - if (!this._etherscanApiPath) { - AirDAOCryptoLog.err( - this._settings.currencyCode + - ' EthScannerProcessor.getTransactions no _etherscanApiPath' - ); - } - let link = this._etherscanApiPath + '&address=' + address; - let logTitle = - this._settings.currencyCode + - ' EthScannerProcessor.getTransactions etherscan '; - transactions = await this._getFromEtherscan( - address, - link, - logTitle, - false, - {} - ); - if (this._etherscanApiPathDeposits) { - link = this._etherscanApiPathDeposits + '&address=' + address; - logTitle = - this._settings.currencyCode + - ' EthScannerProcessor.getTransactions etherscan deposits'; - transactions = await this._getFromEtherscan( - address, - link, - logTitle, - false, - transactions - ); - } + // async getTransactionsBlockchain(scanData) { + // const address = scanData.account.address; + // await AirDAOCryptoLog.log( + // this._settings.currencyCode + + // ' EthScannerProcessor.getTransactions started ' + + // address + // ); + // let res = false; + // if ( + // this._settings.currencyCode !== 'ETH_ROPSTEN' && + // this._settings.currencyCode !== 'ETH_RINKEBY' && + // this._trezorServerCode + // ) { + // try { + // res = await this._get(address); + // } catch (e) { + // throw new Error(e.message + ' in EthScannerProcessor._get'); + // } + // } + // + // let transactions; + // if (res && typeof res.data !== 'undefined' && res.data) { + // await AirDAOCryptoLog.log( + // this._settings.currencyCode + + // ' EthScannerProcessor.getBalance loaded from ' + + // res.provider + + // ' ' + + // res.time + // ); + // if ( + // this._tokenAddress && + // typeof res.data.formattedTokens[this._tokenAddress] === 'undefined' + // ) { + // await AirDAOCryptoLog.log( + // this._settings.currencyCode + + // ' EthScannerProcessor.getTransactions skipped token ' + + // this._tokenAddress + + // ' ' + + // address + // ); + // return false; + // } + // await AirDAOCryptoLog.log( + // this._settings.currencyCode + + // ' EthScannerProcessor.getTransactions trezor unify started ' + + // address + // ); + // transactions = await this._unifyTransactions( + // address, + // res.data.transactions, + // false, + // true, + // {} + // ); + // await AirDAOCryptoLog.log( + // this._settings.currencyCode + + // ' EthScannerProcessor.getTransactions trezor finished ' + + // address + // ); + // } else if (this._oklinkAPI) { + // const logTitle = + // this._settings.currencyCode + + // ' EthScannerProcessor.getTransactions oklink '; + // transactions = await this._getFromOklink( + // address, + // this._oklinkAPI, + // logTitle, + // {} + // ); + // } else { + // if (!this._etherscanApiPath) { + // AirDAOCryptoLog.err( + // this._settings.currencyCode + + // ' EthScannerProcessor.getTransactions no _etherscanApiPath' + // ); + // } + // let link = this._etherscanApiPath + '&address=' + address; + // let logTitle = + // this._settings.currencyCode + + // ' EthScannerProcessor.getTransactions etherscan '; + // transactions = await this._getFromEtherscan( + // address, + // link, + // logTitle, + // false, + // {} + // ); + // if (this._etherscanApiPathDeposits) { + // link = this._etherscanApiPathDeposits + '&address=' + address; + // logTitle = + // this._settings.currencyCode + + // ' EthScannerProcessor.getTransactions etherscan deposits'; + // transactions = await this._getFromEtherscan( + // address, + // link, + // logTitle, + // false, + // transactions + // ); + // } + // + // if (this._useInternal && this._etherscanApiPathInternal) { + // link = this._etherscanApiPathInternal + '&address=' + address; + // logTitle = + // this._settings.currencyCode + + // ' EthScannerProcessor.getTransactions etherscan forInternal'; + // transactions = await this._getFromEtherscan( + // address, + // link, + // logTitle, + // true, + // transactions + // ); + // } + // } + // if (!transactions) { + // return []; + // } + // const reformatted = []; + // for (const key in transactions) { + // reformatted.push(transactions[key]); + // } + // return reformatted; + // } - if (this._useInternal && this._etherscanApiPathInternal) { - link = this._etherscanApiPathInternal + '&address=' + address; - logTitle = - this._settings.currencyCode + - ' EthScannerProcessor.getTransactions etherscan forInternal'; - transactions = await this._getFromEtherscan( - address, - link, - logTitle, - true, - transactions - ); - } - } - if (!transactions) { - return []; - } - const reformatted = []; - for (const key in transactions) { - reformatted.push(transactions[key]); - } - return reformatted; - } + // async _getFromOklink(address, key, logTitle, transactions = {}) { + // const link = + // 'https://www.oklink.com/api/v5/explorer/address/transaction-list?chainShortName=ethw&address=' + + // address; + // const tmp = await AirDAOAxios.getWithHeaders(link, { + // 'Ok-Access-Key': key + // }); + // if ( + // typeof tmp?.data?.data === 'undefined' || + // typeof tmp?.data?.data[0] === 'undefined' || + // typeof tmp?.data?.data[0]?.transactionLists === 'undefined' + // ) { + // return []; + // } + // await AirDAOCryptoLog.log(logTitle + ' started ', link); + // const list = tmp.data.data[0].transactionLists; + // for (const tx of list) { + // const transaction = await this._unifyTransactionOklink(address, tx); + // if (transaction) { + // transactions[transaction.transactionHash] = transaction; + // } + // } + // await AirDAOCryptoLog.log(logTitle + ' finished ' + address); + // return transactions; + // } - async _getFromOklink(address, key, logTitle, transactions = {}) { - const link = - 'https://www.oklink.com/api/v5/explorer/address/transaction-list?chainShortName=ethw&address=' + - address; - const tmp = await AirDAOAxios.getWithHeaders(link, { - 'Ok-Access-Key': key - }); - if ( - typeof tmp?.data?.data === 'undefined' || - typeof tmp?.data?.data[0] === 'undefined' || - typeof tmp?.data?.data[0]?.transactionLists === 'undefined' - ) { - return []; - } - await AirDAOCryptoLog.log(logTitle + ' started ', link); - const list = tmp.data.data[0].transactionLists; - for (const tx of list) { - const transaction = await this._unifyTransactionOklink(address, tx); - if (transaction) { - transactions[transaction.transactionHash] = transaction; - } - } - await AirDAOCryptoLog.log(logTitle + ' finished ' + address); - return transactions; - } - - async _getFromEtherscan( - address, - link, - logTitle, - isInternal, - transactions = {} - ) { - await AirDAOCryptoLog.log( - logTitle + ' started ' + JSON.stringify(isInternal), - link - ); - const tmp = await AirDAOAxios.getWithoutBraking(link); - if ( - !tmp || - typeof tmp.data === 'undefined' || - !tmp.data || - typeof tmp.data.result === 'undefined' - ) { - return transactions; - } - if (typeof tmp.data.result === 'string') { - if (tmp.data.result.indexOf('API Key') === -1) { - throw new Error( - 'Undefined txs etherscan ' + link + ' ' + tmp.data.result - ); - } else { - return transactions; - } - } - - transactions = await this._unifyTransactions( - address, - tmp.data.result, - isInternal, - false, - transactions - ); - await AirDAOCryptoLog.log(logTitle + ' finished ' + address); - return transactions; - } + // async _getFromEtherscan( + // address, + // link, + // logTitle, + // isInternal, + // transactions = {} + // ) { + // await AirDAOCryptoLog.log( + // logTitle + ' started ' + JSON.stringify(isInternal), + // link + // ); + // const tmp = await AirDAOAxios.getWithoutBraking(link); + // if ( + // !tmp || + // typeof tmp.data === 'undefined' || + // !tmp.data || + // typeof tmp.data.result === 'undefined' + // ) { + // return transactions; + // } + // if (typeof tmp.data.result === 'string') { + // if (tmp.data.result.indexOf('API Key') === -1) { + // throw new Error( + // 'Undefined txs etherscan ' + link + ' ' + tmp.data.result + // ); + // } else { + // return transactions; + // } + // } + // + // transactions = await this._unifyTransactions( + // address, + // tmp.data.result, + // isInternal, + // false, + // transactions + // ); + // await AirDAOCryptoLog.log(logTitle + ' finished ' + address); + // return transactions; + // } /** * @param {string} txHash * @return {Promise<[UnifiedTransaction]>} */ - async getTransactionBlockchain(txHash) { + async getTransactionBlockchain(txHash: string | boolean) { await AirDAOCryptoLog.log( this._settings.currencyCode + ' EthScannerProcessor.getTransaction started ' + @@ -541,109 +540,109 @@ export default class EthScannerProcessor extends EthBasic { * @returns {Promise<[{UnifiedTransaction}]>} * @private */ - async _unifyTransactions( - _address, - result, - isInternal, - isTrezor = false, - transactions = {} - ) { - if (!result) { - return transactions; - } - const address = _address.toLowerCase(); - let tx; - let maxNonce = -1; - let maxSuccessNonce = -1; - - const notBroadcasted = await EthRawDS.getForAddress({ - address, - currencyCode: this._settings.currencyCode - }); - for (tx of result) { - try { - let transaction; - const key = typeof tx.hash !== 'undefined' ? tx.hash : tx.txid; - if (typeof transactions[key] !== 'undefined') { - continue; - } - if (isTrezor) { - transaction = await this._unifyTransactionTrezor( - address, - tx, - isInternal - ); - } else { - transaction = await this._unifyTransaction(address, tx, isInternal); - } - if (transaction) { - transactions[key] = transaction; - if ( - typeof transaction.transactionJson !== 'undefined' && - typeof transaction.transactionJson.feeType === 'undefined' && - (transaction.transactionDirection === 'outcome' || - transaction.transactionDirection === 'self') && - typeof transaction.transactionJson.nonce !== 'undefined' - ) { - const uniqueFrom = - address.toLowerCase() + '_' + transaction.transactionJson.nonce; - if ( - notBroadcasted && - typeof notBroadcasted[uniqueFrom] !== 'undefined' && - transaction.transactionStatus !== 'new' - ) { - EthRawDS.cleanRaw({ - address, - transactionUnique: uniqueFrom, - currencyCode: this._settings.currencyCode - }); - } - if (transaction.transactionJson.nonce * 1 > maxNonce) { - maxNonce = transaction.transactionJson.nonce * 1; - } - if ( - transaction.transactionStatus === 'success' || - transaction.transactionStatus === 'confirming' - ) { - if (transaction.transactionJson.nonce * 1 > maxSuccessNonce) { - maxSuccessNonce = transaction.transactionJson.nonce * 1; - } - } - } - } - } catch (e) { - AirDAOCryptoLog.err( - this._settings.currencyCode + - ' EthScannerProcessor._unifyTransaction error ' + - e.message + - ' on ' + - (isTrezor ? 'Trezor' : 'usual') + - ' tx ' + - JSON.stringify(tx) - ); - } - } - - if (maxNonce > -1) { - await EthTmpDS.saveNonce( - this._mainCurrencyCode, - address, - 'maxScanned', - maxNonce - ); - } - - if (maxSuccessNonce > -1) { - await EthTmpDS.saveNonce( - this._mainCurrencyCode, - address, - 'maxSuccess', - maxSuccessNonce - ); - } - - return transactions; - } + // async _unifyTransactions( + // _address, + // result, + // isInternal, + // isTrezor = false, + // transactions = {} + // ) { + // if (!result) { + // return transactions; + // } + // const address = _address.toLowerCase(); + // let tx; + // let maxNonce = -1; + // let maxSuccessNonce = -1; + // + // const notBroadcasted = await EthRawDS.getForAddress({ + // address, + // currencyCode: this._settings.currencyCode + // }); + // for (tx of result) { + // try { + // let transaction; + // const key = typeof tx.hash !== 'undefined' ? tx.hash : tx.txid; + // if (typeof transactions[key] !== 'undefined') { + // continue; + // } + // if (isTrezor) { + // transaction = await this._unifyTransactionTrezor( + // address, + // tx, + // isInternal + // ); + // } else { + // transaction = await this._unifyTransaction(address, tx, isInternal); + // } + // if (transaction) { + // transactions[key] = transaction; + // if ( + // typeof transaction.transactionJson !== 'undefined' && + // typeof transaction.transactionJson.feeType === 'undefined' && + // (transaction.transactionDirection === 'outcome' || + // transaction.transactionDirection === 'self') && + // typeof transaction.transactionJson.nonce !== 'undefined' + // ) { + // const uniqueFrom = + // address.toLowerCase() + '_' + transaction.transactionJson.nonce; + // if ( + // notBroadcasted && + // typeof notBroadcasted[uniqueFrom] !== 'undefined' && + // transaction.transactionStatus !== 'new' + // ) { + // EthRawDS.cleanRaw({ + // address, + // transactionUnique: uniqueFrom, + // currencyCode: this._settings.currencyCode + // }); + // } + // if (transaction.transactionJson.nonce * 1 > maxNonce) { + // maxNonce = transaction.transactionJson.nonce * 1; + // } + // if ( + // transaction.transactionStatus === 'success' || + // transaction.transactionStatus === 'confirming' + // ) { + // if (transaction.transactionJson.nonce * 1 > maxSuccessNonce) { + // maxSuccessNonce = transaction.transactionJson.nonce * 1; + // } + // } + // } + // } + // } catch (e) { + // AirDAOCryptoLog.err( + // this._settings.currencyCode + + // ' EthScannerProcessor._unifyTransaction error ' + + // e.message + + // ' on ' + + // (isTrezor ? 'Trezor' : 'usual') + + // ' tx ' + + // JSON.stringify(tx) + // ); + // } + // } + // + // if (maxNonce > -1) { + // await EthTmpDS.saveNonce( + // this._mainCurrencyCode, + // address, + // 'maxScanned', + // maxNonce + // ); + // } + // + // if (maxSuccessNonce > -1) { + // await EthTmpDS.saveNonce( + // this._mainCurrencyCode, + // address, + // 'maxSuccess', + // maxSuccessNonce + // ); + // } + // + // return transactions; + // } /** * @param {string} address @@ -675,175 +674,175 @@ export default class EthScannerProcessor extends EthBasic { * @param {string} transaction.tokenTransfers[].value "1000000" * @private */ - async _unifyTransactionTrezor(_address, transaction, isInternal = false) { - let fromAddress = ''; - const address = _address.toLowerCase(); - if ( - typeof transaction.vin[0] !== 'undefined' && - transaction.vin[0].addresses && - typeof transaction.vin[0].addresses[0] !== 'undefined' - ) { - fromAddress = transaction.vin[0].addresses[0].toLowerCase(); - } - let toAddress = ''; - if ( - typeof transaction.vout[0] !== 'undefined' && - transaction.vout[0].addresses && - typeof transaction.vout[0].addresses[0] !== 'undefined' - ) { - toAddress = transaction.vout[0].addresses[0].toLowerCase(); - } - let amount = transaction.value; - - let nonce = transaction.ethereumSpecific.nonce; - if (nonce * 1 === 0) { - nonce = 0; - } - const additional = { - nonce, - gas: transaction.ethereumSpecific.gasLimit || '', - gasPrice: transaction.ethereumSpecific.gasPrice || '', - gasUsed: transaction.ethereumSpecific.gasUsed || '' - }; - let fee = transaction.fees || 0; - let feeCurrencyCode = this._mainCurrencyCode; - - if (this._tokenAddress) { - let failToken = false; - if (typeof transaction.tokenTransfers === 'undefined') { - if (this._tokenAddress === toAddress) { - failToken = true; - } else { - return false; - } - } - if (!failToken) { - let tmp; - let found = false; - amount = new BlocksoftBN(0); - for (tmp of transaction.tokenTransfers) { - if (tmp.token.toLowerCase() === this._tokenAddress.toLowerCase()) { - tmp.from = tmp.from.toLowerCase(); - tmp.to = tmp.to.toLowerCase(); - if (tmp.to !== address && tmp.from !== address) { - continue; - } - if (tmp.to === address) { - fromAddress = tmp.from; - amount.add(tmp.value); - } else if (tmp.from === address) { - if ( - this._delegateAddress && - tmp.to.toLowerCase() === this._delegateAddress.toLowerCase() - ) { - fee = tmp.value; - additional.feeType = 'DELEGATE'; - feeCurrencyCode = this._settings.currencyCode || 'DELEGATE'; - } else { - toAddress = tmp.to; - amount.diff(tmp.value); - } - } - found = true; - } - } - amount = amount.get(); - if (amount < 0) { - amount = -1 * amount; - fromAddress = address; - } else { - toAddress = address; - } - if (!found) { - return false; - } - } - } - - if (typeof transaction.blockTime === 'undefined') { - throw new Error( - ' no transaction.blockTime error transaction data ' + - JSON.stringify(transaction) - ); - } - let formattedTime = transaction.blockTime; - try { - formattedTime = BlocksoftUtils.toDate(transaction.blockTime); - } catch (e) { - e.message += - ' timestamp error transaction data ' + JSON.stringify(transaction); - throw e; - } - - let blockHash = false; - const confirmations = transaction.confirmations; - try { - CACHE_BLOCK_NUMBER_TO_HASH[this._mainCurrencyCode][ - transaction.blockHeight - ] = transaction.blockHash; - if (typeof transaction.blockHash !== 'undefined') { - blockHash = transaction.blockHash; - } - if ( - confirmations > 0 && - transaction.blockHeight > - CACHE_GET_MAX_BLOCK[this._mainCurrencyCode].max_block_number - ) { - CACHE_GET_MAX_BLOCK[this._mainCurrencyCode].max_block_number = - transaction.blockHeight; - CACHE_GET_MAX_BLOCK[this._mainCurrencyCode].confirmations = - confirmations; - } - } catch (e) { - throw new Error( - e.message + ' in CACHE_GET_MAX_BLOCK ' + this._mainCurrencyCode - ); - } - let transactionStatus = 'new'; - - if (blockHash) { - if (transaction.ethereumSpecific.status === 1) { - if (confirmations > this._blocksToConfirm) { - transactionStatus = 'success'; - } else if (confirmations > 0) { - transactionStatus = 'confirming'; - } - } else { - transactionStatus = 'fail'; - } - } - - const tx = { - transactionHash: transaction.txid.toLowerCase(), - blockHash: blockHash || '', - blockNumber: +transaction.blockHeight, - blockTime: formattedTime, - blockConfirmations: confirmations, - transactionDirection: - address.toLowerCase() === fromAddress.toLowerCase() - ? 'outcome' - : 'income', - addressFrom: - address.toLowerCase() === fromAddress.toLowerCase() ? '' : fromAddress, - addressTo: - address.toLowerCase() === toAddress.toLowerCase() ? '' : toAddress, - addressFromBasic: fromAddress.toLowerCase(), - addressAmount: amount, - transactionStatus: transactionStatus, - transactionFee: fee, - transactionFeeCurrencyCode: feeCurrencyCode, - contractAddress: '', - inputValue: '' - }; - if (tx.addressFrom === '' && tx.addressTo === '') { - tx.transactionDirection = 'self'; - // self zero will not shown if uncomment! tx.addressAmount = 0 - } - if (additional) { - tx.transactionJson = additional; - } - return tx; - } + // async _unifyTransactionTrezor(_address, transaction, isInternal = false) { + // let fromAddress = ''; + // const address = _address.toLowerCase(); + // if ( + // typeof transaction.vin[0] !== 'undefined' && + // transaction.vin[0].addresses && + // typeof transaction.vin[0].addresses[0] !== 'undefined' + // ) { + // fromAddress = transaction.vin[0].addresses[0].toLowerCase(); + // } + // let toAddress = ''; + // if ( + // typeof transaction.vout[0] !== 'undefined' && + // transaction.vout[0].addresses && + // typeof transaction.vout[0].addresses[0] !== 'undefined' + // ) { + // toAddress = transaction.vout[0].addresses[0].toLowerCase(); + // } + // let amount = transaction.value; + // + // let nonce = transaction.ethereumSpecific.nonce; + // if (nonce * 1 === 0) { + // nonce = 0; + // } + // const additional = { + // nonce, + // gas: transaction.ethereumSpecific.gasLimit || '', + // gasPrice: transaction.ethereumSpecific.gasPrice || '', + // gasUsed: transaction.ethereumSpecific.gasUsed || '' + // }; + // let fee = transaction.fees || 0; + // let feeCurrencyCode = this._mainCurrencyCode; + // + // if (this._tokenAddress) { + // let failToken = false; + // if (typeof transaction.tokenTransfers === 'undefined') { + // if (this._tokenAddress === toAddress) { + // failToken = true; + // } else { + // return false; + // } + // } + // if (!failToken) { + // let tmp; + // let found = false; + // amount = new BlocksoftBN(0); + // for (tmp of transaction.tokenTransfers) { + // if (tmp.token.toLowerCase() === this._tokenAddress.toLowerCase()) { + // tmp.from = tmp.from.toLowerCase(); + // tmp.to = tmp.to.toLowerCase(); + // if (tmp.to !== address && tmp.from !== address) { + // continue; + // } + // if (tmp.to === address) { + // fromAddress = tmp.from; + // amount.add(tmp.value); + // } else if (tmp.from === address) { + // if ( + // this._delegateAddress && + // tmp.to.toLowerCase() === this._delegateAddress.toLowerCase() + // ) { + // fee = tmp.value; + // additional.feeType = 'DELEGATE'; + // feeCurrencyCode = this._settings.currencyCode || 'DELEGATE'; + // } else { + // toAddress = tmp.to; + // amount.diff(tmp.value); + // } + // } + // found = true; + // } + // } + // amount = amount.get(); + // if (amount < 0) { + // amount = -1 * amount; + // fromAddress = address; + // } else { + // toAddress = address; + // } + // if (!found) { + // return false; + // } + // } + // } + // + // if (typeof transaction.blockTime === 'undefined') { + // throw new Error( + // ' no transaction.blockTime error transaction data ' + + // JSON.stringify(transaction) + // ); + // } + // let formattedTime = transaction.blockTime; + // try { + // formattedTime = BlocksoftUtils.toDate(transaction.blockTime); + // } catch (e) { + // e.message += + // ' timestamp error transaction data ' + JSON.stringify(transaction); + // throw e; + // } + // + // let blockHash = false; + // const confirmations = transaction.confirmations; + // try { + // CACHE_BLOCK_NUMBER_TO_HASH[this._mainCurrencyCode][ + // transaction.blockHeight + // ] = transaction.blockHash; + // if (typeof transaction.blockHash !== 'undefined') { + // blockHash = transaction.blockHash; + // } + // if ( + // confirmations > 0 && + // transaction.blockHeight > + // CACHE_GET_MAX_BLOCK[this._mainCurrencyCode].max_block_number + // ) { + // CACHE_GET_MAX_BLOCK[this._mainCurrencyCode].max_block_number = + // transaction.blockHeight; + // CACHE_GET_MAX_BLOCK[this._mainCurrencyCode].confirmations = + // confirmations; + // } + // } catch (e) { + // throw new Error( + // e.message + ' in CACHE_GET_MAX_BLOCK ' + this._mainCurrencyCode + // ); + // } + // let transactionStatus = 'new'; + // + // if (blockHash) { + // if (transaction.ethereumSpecific.status === 1) { + // if (confirmations > this._blocksToConfirm) { + // transactionStatus = 'success'; + // } else if (confirmations > 0) { + // transactionStatus = 'confirming'; + // } + // } else { + // transactionStatus = 'fail'; + // } + // } + // + // const tx = { + // transactionHash: transaction.txid.toLowerCase(), + // blockHash: blockHash || '', + // blockNumber: +transaction.blockHeight, + // blockTime: formattedTime, + // blockConfirmations: confirmations, + // transactionDirection: + // address.toLowerCase() === fromAddress.toLowerCase() + // ? 'outcome' + // : 'income', + // addressFrom: + // address.toLowerCase() === fromAddress.toLowerCase() ? '' : fromAddress, + // addressTo: + // address.toLowerCase() === toAddress.toLowerCase() ? '' : toAddress, + // addressFromBasic: fromAddress.toLowerCase(), + // addressAmount: amount, + // transactionStatus: transactionStatus, + // transactionFee: fee, + // transactionFeeCurrencyCode: feeCurrencyCode, + // contractAddress: '', + // inputValue: '' + // }; + // if (tx.addressFrom === '' && tx.addressTo === '') { + // tx.transactionDirection = 'self'; + // // self zero will not shown if uncomment! tx.addressAmount = 0 + // } + // if (additional) { + // tx.transactionJson = additional; + // } + // return tx; + // } /** * @param {string} address @@ -868,68 +867,68 @@ export default class EthScannerProcessor extends EthBasic { * @return {UnifiedTransaction} * @protected */ - async _unifyTransactionOklink(_address, transaction) { - if (typeof transaction.transactionTime === 'undefined') { - throw new Error( - ' no transaction.timeStamp error transaction data ' + - JSON.stringify(transaction) - ); - } - - const address = _address.toLowerCase(); - const formattedTime = transaction.transactionTime; - - let transactionStatus = 'new'; - let confirmations = 0; - if (transaction.state === 'success') { - const diff = new Date().getTime() - transaction.transactionTime; - confirmations = Math.round(diff / 60000); - if (confirmations > 120) { - transactionStatus = 'success'; - } else { - transactionStatus = 'confirming'; - } - } else if (transaction.state === 'fail') { - transactionStatus = 'fail'; - } else if (transaction.state === 'pending') { - transactionStatus = 'confirming'; - } - - const tx = { - transactionHash: transaction.txId.toLowerCase() + '_1', - blockHash: transaction.blockHash, - blockNumber: +transaction.height, - blockTime: formattedTime, - blockConfirmations: confirmations, - transactionDirection: - address.toLowerCase() === transaction.from.toLowerCase() - ? 'outcome' - : 'income', - addressFrom: - address.toLowerCase() === transaction.from.toLowerCase() - ? '' - : transaction.from, - addressFromBasic: transaction.from.toLowerCase(), - addressTo: - address.toLowerCase() === transaction.to.toLowerCase() - ? '' - : transaction.to, - addressAmount: BlocksoftPrettyNumbers.setCurrencyCode('ETH').makeUnPretty( - transaction.amount - ), - transactionStatus: transactionStatus, - contractAddress: '', - inputValue: '', - transactionFee: BlocksoftPrettyNumbers.setCurrencyCode( - 'ETH' - ).makeUnPretty(transaction.txFee) - }; - if (tx.addressFrom === '' && tx.addressTo === '') { - tx.transactionDirection = 'self'; - tx.addressAmount = 0; - } - return tx; - } + // async _unifyTransactionOklink(_address, transaction) { + // if (typeof transaction.transactionTime === 'undefined') { + // throw new Error( + // ' no transaction.timeStamp error transaction data ' + + // JSON.stringify(transaction) + // ); + // } + // + // const address = _address.toLowerCase(); + // const formattedTime = transaction.transactionTime; + // + // let transactionStatus = 'new'; + // let confirmations = 0; + // if (transaction.state === 'success') { + // const diff = new Date().getTime() - transaction.transactionTime; + // confirmations = Math.round(diff / 60000); + // if (confirmations > 120) { + // transactionStatus = 'success'; + // } else { + // transactionStatus = 'confirming'; + // } + // } else if (transaction.state === 'fail') { + // transactionStatus = 'fail'; + // } else if (transaction.state === 'pending') { + // transactionStatus = 'confirming'; + // } + // + // const tx = { + // transactionHash: transaction.txId.toLowerCase() + '_1', + // blockHash: transaction.blockHash, + // blockNumber: +transaction.height, + // blockTime: formattedTime, + // blockConfirmations: confirmations, + // transactionDirection: + // address.toLowerCase() === transaction.from.toLowerCase() + // ? 'outcome' + // : 'income', + // addressFrom: + // address.toLowerCase() === transaction.from.toLowerCase() + // ? '' + // : transaction.from, + // addressFromBasic: transaction.from.toLowerCase(), + // addressTo: + // address.toLowerCase() === transaction.to.toLowerCase() + // ? '' + // : transaction.to, + // addressAmount: BlocksoftPrettyNumbers.setCurrencyCode('ETH').makeUnPretty( + // transaction.amount + // ), + // transactionStatus: transactionStatus, + // contractAddress: '', + // inputValue: '', + // transactionFee: BlocksoftPrettyNumbers.setCurrencyCode( + // 'ETH' + // ).makeUnPretty(transaction.txFee) + // }; + // if (tx.addressFrom === '' && tx.addressTo === '') { + // tx.transactionDirection = 'self'; + // tx.addressAmount = 0; + // } + // return tx; + // } /** * @param {string} address @@ -957,143 +956,143 @@ export default class EthScannerProcessor extends EthBasic { * @return {UnifiedTransaction} * @protected */ - async _unifyTransaction(_address: string, transaction, isInternal = false) { - if (typeof transaction.timeStamp === 'undefined') { - throw new Error( - ' no transaction.timeStamp error transaction data ' + - JSON.stringify(transaction) - ); - } - - const address = _address.toLowerCase(); - let formattedTime = transaction.timeStamp; - try { - formattedTime = BlocksoftUtils.toDate(transaction.timeStamp); - } catch (e) { - console.log('no timestamp2'); - e.message += - ' timestamp error transaction data ' + JSON.stringify(transaction); - throw e; - } - - let addressAmount = transaction.value; - if (typeof transaction.L1TxOrigin !== 'undefined') { - if (transaction.from === '0x0000000000000000000000000000000000000000') { - transaction.from = 'ETH: ' + transaction.L1TxOrigin; - } - CACHE_BLOCK_NUMBER_TO_HASH[transaction.blockNumber] = - transaction.blockHash; - addressAmount = transaction.tokenValue; - transaction.confirmations = 100; - } else if (isInternal) { - if (transaction.contractAddress !== '') { - return false; - } - if (transaction.type !== 'call') { - return false; - } - - if ( - typeof CACHE_BLOCK_NUMBER_TO_HASH[this._mainCurrencyCode][ - transaction.blockNumber - ] === 'undefined' - ) { - const data = await this._web3.eth.getTransaction(transaction.hash); - CACHE_BLOCK_NUMBER_TO_HASH[this._mainCurrencyCode][ - transaction.blockNumber - ] = data?.blockHash; - } - transaction.blockHash = - CACHE_BLOCK_NUMBER_TO_HASH[this._mainCurrencyCode][ - transaction.blockNumber - ] || transaction.blockNumber; - // noinspection PointlessArithmeticExpressionJS - transaction.confirmations = - CACHE_GET_MAX_BLOCK[this._mainCurrencyCode].max_block_number - - transaction.blockNumber + - 1 * CACHE_GET_MAX_BLOCK[this._mainCurrencyCode].confirmations; - } else { - CACHE_BLOCK_NUMBER_TO_HASH[transaction.blockNumber] = - transaction.blockHash; - } - - const confirmations = transaction.confirmations; - if ( - confirmations > 0 && - transaction.blockNumber > - CACHE_GET_MAX_BLOCK[this._mainCurrencyCode].max_block_number - ) { - CACHE_GET_MAX_BLOCK[this._mainCurrencyCode].max_block_number = - transaction.blockNumber; - CACHE_GET_MAX_BLOCK[this._mainCurrencyCode].confirmations = confirmations; - } - let transactionStatus = 'new'; - if ( - typeof transaction.txreceipt_status === 'undefined' || - transaction.txreceipt_status === '1' - ) { - if (confirmations > this._blocksToConfirm) { - transactionStatus = 'success'; - } else if (confirmations > 0) { - transactionStatus = 'confirming'; - } - } else if (transaction.isError !== '0') { - transactionStatus = 'fail'; - } - // if (isInternal) { - // transactionStatus = 'internal_' + transactionStatus - // } - let contractAddress = false; - if (typeof transaction.contractAddress !== 'undefined') { - contractAddress = transaction.contractAddress.toLowerCase(); - } - const tx = { - transactionHash: transaction.hash.toLowerCase(), - blockHash: transaction.blockHash, - blockNumber: +transaction.blockNumber, - blockTime: formattedTime, - blockConfirmations: confirmations, - transactionDirection: - address.toLowerCase() === transaction.from.toLowerCase() - ? 'outcome' - : 'income', - addressFrom: - address.toLowerCase() === transaction.from.toLowerCase() - ? '' - : transaction.from, - addressFromBasic: transaction.from.toLowerCase(), - addressTo: - address.toLowerCase() === transaction.to.toLowerCase() - ? '' - : transaction.to, - addressAmount, - transactionStatus: transactionStatus, - contractAddress, - inputValue: transaction.input - }; - let nonce = transaction.nonce; - if (nonce * 1 === 0) { - nonce = 0; - } - if (!isInternal) { - const additional = { - nonce, - gas: transaction.gas, - gasPrice: transaction.gasPrice, - cumulativeGasUsed: transaction.cumulativeGasUsed, - gasUsed: transaction.gasUsed, - transactionIndex: transaction.transactionIndex - }; - tx.transactionJson = additional; - tx.transactionFee = BlocksoftUtils.mul( - transaction.gasUsed, - transaction.gasPrice - ).toString(); - } - if (tx.addressFrom === '' && tx.addressTo === '') { - tx.transactionDirection = 'self'; - tx.addressAmount = 0; - } - return tx; - } + // async _unifyTransaction(_address: string, transaction, isInternal = false) { + // if (typeof transaction.timeStamp === 'undefined') { + // throw new Error( + // ' no transaction.timeStamp error transaction data ' + + // JSON.stringify(transaction) + // ); + // } + // + // const address = _address.toLowerCase(); + // let formattedTime = transaction.timeStamp; + // try { + // formattedTime = BlocksoftUtils.toDate(transaction.timeStamp); + // } catch (e) { + // console.log('no timestamp2'); + // e.message += + // ' timestamp error transaction data ' + JSON.stringify(transaction); + // throw e; + // } + // + // let addressAmount = transaction.value; + // if (typeof transaction.L1TxOrigin !== 'undefined') { + // if (transaction.from === '0x0000000000000000000000000000000000000000') { + // transaction.from = 'ETH: ' + transaction.L1TxOrigin; + // } + // CACHE_BLOCK_NUMBER_TO_HASH[transaction.blockNumber] = + // transaction.blockHash; + // addressAmount = transaction.tokenValue; + // transaction.confirmations = 100; + // } else if (isInternal) { + // if (transaction.contractAddress !== '') { + // return false; + // } + // if (transaction.type !== 'call') { + // return false; + // } + // + // if ( + // typeof CACHE_BLOCK_NUMBER_TO_HASH[this._mainCurrencyCode][ + // transaction.blockNumber + // ] === 'undefined' + // ) { + // const data = await this._web3.eth.getTransaction(transaction.hash); + // CACHE_BLOCK_NUMBER_TO_HASH[this._mainCurrencyCode][ + // transaction.blockNumber + // ] = data?.blockHash; + // } + // transaction.blockHash = + // CACHE_BLOCK_NUMBER_TO_HASH[this._mainCurrencyCode][ + // transaction.blockNumber + // ] || transaction.blockNumber; + // // noinspection PointlessArithmeticExpressionJS + // transaction.confirmations = + // CACHE_GET_MAX_BLOCK[this._mainCurrencyCode].max_block_number - + // transaction.blockNumber + + // 1 * CACHE_GET_MAX_BLOCK[this._mainCurrencyCode].confirmations; + // } else { + // CACHE_BLOCK_NUMBER_TO_HASH[transaction.blockNumber] = + // transaction.blockHash; + // } + // + // const confirmations = transaction.confirmations; + // if ( + // confirmations > 0 && + // transaction.blockNumber > + // CACHE_GET_MAX_BLOCK[this._mainCurrencyCode].max_block_number + // ) { + // CACHE_GET_MAX_BLOCK[this._mainCurrencyCode].max_block_number = + // transaction.blockNumber; + // CACHE_GET_MAX_BLOCK[this._mainCurrencyCode].confirmations = confirmations; + // } + // let transactionStatus = 'new'; + // if ( + // typeof transaction.txreceipt_status === 'undefined' || + // transaction.txreceipt_status === '1' + // ) { + // if (confirmations > this._blocksToConfirm) { + // transactionStatus = 'success'; + // } else if (confirmations > 0) { + // transactionStatus = 'confirming'; + // } + // } else if (transaction.isError !== '0') { + // transactionStatus = 'fail'; + // } + // // if (isInternal) { + // // transactionStatus = 'internal_' + transactionStatus + // // } + // let contractAddress = false; + // if (typeof transaction.contractAddress !== 'undefined') { + // contractAddress = transaction.contractAddress.toLowerCase(); + // } + // const tx = { + // transactionHash: transaction.hash.toLowerCase(), + // blockHash: transaction.blockHash, + // blockNumber: +transaction.blockNumber, + // blockTime: formattedTime, + // blockConfirmations: confirmations, + // transactionDirection: + // address.toLowerCase() === transaction.from.toLowerCase() + // ? 'outcome' + // : 'income', + // addressFrom: + // address.toLowerCase() === transaction.from.toLowerCase() + // ? '' + // : transaction.from, + // addressFromBasic: transaction.from.toLowerCase(), + // addressTo: + // address.toLowerCase() === transaction.to.toLowerCase() + // ? '' + // : transaction.to, + // addressAmount, + // transactionStatus: transactionStatus, + // contractAddress, + // inputValue: transaction.input + // }; + // let nonce = transaction.nonce; + // if (nonce * 1 === 0) { + // nonce = 0; + // } + // if (!isInternal) { + // const additional = { + // nonce, + // gas: transaction.gas, + // gasPrice: transaction.gasPrice, + // cumulativeGasUsed: transaction.cumulativeGasUsed, + // gasUsed: transaction.gasUsed, + // transactionIndex: transaction.transactionIndex + // }; + // tx.transactionJson = additional; + // tx.transactionFee = BlocksoftUtils.mul( + // transaction.gasUsed, + // transaction.gasPrice + // ).toString(); + // } + // if (tx.addressFrom === '' && tx.addressTo === '') { + // tx.transactionDirection = 'self'; + // tx.addressAmount = 0; + // } + // return tx; + // } } diff --git a/crypto/blockchains/eth/EthScannerProcessorErc20.ts b/crypto/blockchains/eth/EthScannerProcessorErc20.ts index 4474a752c..4cdcf7bdd 100644 --- a/crypto/blockchains/eth/EthScannerProcessorErc20.ts +++ b/crypto/blockchains/eth/EthScannerProcessorErc20.ts @@ -1,20 +1,30 @@ /** * @version 0.5 */ -import AirDAOCryptoLog from '../../common/AirDAOCryptoLog'; +// import AirDAOCryptoLog from '../../common/AirDAOCryptoLog'; import EthScannerProcessor from './EthScannerProcessor'; import abi from './ext/erc20'; +import { Contract } from '@fioprotocol/fiojs/dist/chain-serialize'; export default class EthScannerProcessorErc20 extends EthScannerProcessor { /** * @type {boolean} * @private */ - _useInternal = false; + // _useInternal = false; + private _token: Contract; - constructor(settings) { + constructor(settings: { + tokenAddress?: any; + delegateAddress?: any; + network?: any; + tokenBlockchain?: any; + tokenBlockchainCode?: string | undefined; + currencyCode?: any; + }) { + // @ts-ignore super(settings); - // noinspection JSUnresolvedVariable + // @ts-ignore this._token = new this._web3.eth.Contract(abi.ERC20, settings.tokenAddress); this._tokenAddress = settings.tokenAddress.toLowerCase(); this._delegateAddress = (settings.delegateAddress || '').toLowerCase(); @@ -23,6 +33,7 @@ export default class EthScannerProcessorErc20 extends EthScannerProcessor { this._etherscanApiPath && typeof this._etherscanApiPath !== 'undefined' ) { + // @ts-ignore const tmp = this._etherscanApiPath.split('/'); this._etherscanApiPath = `https://${tmp[2]}/api?module=account&action=tokentx&sort=desc&contractaddress=${settings.tokenAddress}&apikey=YourApiKeyToken`; } @@ -32,79 +43,79 @@ export default class EthScannerProcessorErc20 extends EthScannerProcessor { * @param {string} address * @return {Promise<{balance, unconfirmed, provider}>} */ - async getBalanceBlockchain(address: string) { - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' EthScannerProcessorErc20.getBalance started ' + - address - ); - if (this.checkWeb3CurrentServerUpdated()) { - this._token = new this._web3.eth.Contract( - abi.ERC20, - this._settings.tokenAddress - ); - } - - // noinspection JSUnresolvedVariable - try { - let balance = 0; - let provider = ''; - let time = 0; - - if (this._trezorServerCode) { - const res = await this._get(address); - if (!res || typeof res.data === 'undefined') return false; - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' EthScannerProcessorErc20.getBalance loaded from ' + - res.provider + - ' ' + - res.time - ); - const data = res.data; - - if ( - data && - this._tokenAddress && - typeof data.formattedTokens[this._tokenAddress] !== 'undefined' && - typeof typeof data.formattedTokens[this._tokenAddress].balance !== - 'undefined' - ) { - balance = data.formattedTokens[this._tokenAddress].balance; - if (balance === []) return false; - provider = res.provider; - time = res.time; - return { - balance, - unconfirmed: 0, - provider, - time, - balanceScanBlock: res.data.nonce - }; - } - } - balance = await this._token.methods.balanceOf(address).call(); - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' EthScannerProcessorErc20.getBalance ' + - address + - ' result ' + - JSON.stringify(balance) - ); - if (balance === []) return false; - - provider = 'web3'; - time = 'now()'; - return { balance, unconfirmed: 0, provider, time }; - } catch (e) { - AirDAOCryptoLog.log( - this._settings.currencyCode + - ' EthScannerProcessorErc20.getBalance ' + - address + - ' error ' + - e.message - ); - return false; - } - } + // async getBalanceBlockchain(address: string) { + // AirDAOCryptoLog.log( + // this._settings.currencyCode + + // ' EthScannerProcessorErc20.getBalance started ' + + // address + // ); + // if (this.checkWeb3CurrentServerUpdated()) { + // this._token = new this._web3.eth.Contract( + // abi.ERC20, + // this._settings.tokenAddress + // ); + // } + // + // // noinspection JSUnresolvedVariable + // try { + // let balance = 0; + // let provider = ''; + // let time = 0; + // + // if (this._trezorServerCode) { + // const res = await this._get(address); + // if (!res || typeof res.data === 'undefined') return false; + // AirDAOCryptoLog.log( + // this._settings.currencyCode + + // ' EthScannerProcessorErc20.getBalance loaded from ' + + // res.provider + + // ' ' + + // res.time + // ); + // const data = res.data; + // + // if ( + // data && + // this._tokenAddress && + // typeof data.formattedTokens[this._tokenAddress] !== 'undefined' && + // typeof typeof data.formattedTokens[this._tokenAddress].balance !== + // 'undefined' + // ) { + // balance = data.formattedTokens[this._tokenAddress].balance; + // if (balance === []) return false; + // provider = res.provider; + // time = res.time; + // return { + // balance, + // unconfirmed: 0, + // provider, + // time, + // balanceScanBlock: res.data.nonce + // }; + // } + // } + // balance = await this._token.methods.balanceOf(address).call(); + // AirDAOCryptoLog.log( + // this._settings.currencyCode + + // ' EthScannerProcessorErc20.getBalance ' + + // address + + // ' result ' + + // JSON.stringify(balance) + // ); + // if (balance === []) return false; + // + // provider = 'web3'; + // time = 'now()'; + // return { balance, unconfirmed: 0, provider, time }; + // } catch (e: any) { + // AirDAOCryptoLog.log( + // this._settings.currencyCode + + // ' EthScannerProcessorErc20.getBalance ' + + // address + + // ' error ' + + // e.message + // ); + // return false; + // } + // } } diff --git a/crypto/blockchains/eth/EthTokenProcessorErc20.ts b/crypto/blockchains/eth/EthTokenProcessorErc20.ts deleted file mode 100644 index 9cf6fea9b..000000000 --- a/crypto/blockchains/eth/EthTokenProcessorErc20.ts +++ /dev/null @@ -1,77 +0,0 @@ -/** - * @version 0.5 - */ -import EthBasic from './basic/EthBasic'; -import abi from './ext/erc20.js'; - -export default class EthTokenProcessorErc20 extends EthBasic { - /** - * @param {string} tokenAddress - * @returns {Promise<{tokenAddress: *, currencyName: *, provider: string, tokenDecimals: *, icon: boolean, description: boolean, tokenType: string, currencyCode: *}>} - */ - async getTokenDetails(tokenAddress: string): Promise<{ - tokenAddress: string; - currencyName: string; - provider: string; - tokenDecimals: string; - icon: boolean; - description: boolean; - tokenType: string; - currencyCode: string; - }> { - let token, name, symbol, decimals; - - this.checkWeb3CurrentServerUpdated(); - - try { - // noinspection JSUnresolvedVariable - token = new this._web3.eth.Contract( - abi.ERC20, - tokenAddress.toLowerCase() - ); - } catch (err) { - const e = err as unknown as any; - e.message = 'erc20 init token ' + e.message; - throw e; - } - try { - name = await token.methods.name().call(); - } catch (err) { - const e = err as unknown as any; - e.message = 'erc20.name ' + e.message; - throw e; - } - - try { - symbol = await token.methods.symbol().call(); - } catch (err) { - const e = err as unknown as any; - e.message = 'erc20.symbol ' + e.message; - throw e; - } - - try { - decimals = await token.methods.decimals().call(); - } catch (err) { - const e = err as unknown as any; - e.message = 'erc20.decimals ' + e.message; - throw e; - } - - const res = { - currencyCodePrefix: 'CUSTOM_', - currencyCode: symbol, - currencyName: name, - tokenType: this._mainTokenType, - tokenAddress: tokenAddress.toLowerCase(), - tokenDecimals: decimals, - icon: false, - description: false, - provider: 'web3' - }; - if (this._mainCurrencyCode !== 'ETH') { - res.currencyCodePrefix = 'CUSTOM_' + this._mainTokenType + '_'; - } - return res; - } -} diff --git a/crypto/blockchains/eth/EthTokenProcessorNft.ts b/crypto/blockchains/eth/EthTokenProcessorNft.ts index 98394b10d..d826955aa 100644 --- a/crypto/blockchains/eth/EthTokenProcessorNft.ts +++ b/crypto/blockchains/eth/EthTokenProcessorNft.ts @@ -2,62 +2,61 @@ * @version 0.50 */ import EthBasic from './basic/EthBasic'; -import EthNftOpensea from '@crypto/blockchains/eth/apis/EthNftOpensea'; -import EthNftMatic from '@crypto/blockchains/eth/apis/EthNftMatic'; -import abi from './ext/erc721.js'; -import AirDAODictNfts from '@crypto/common/AirDAODictNfts'; +// import EthNftOpensea from '@crypto/blockchains/eth/apis/EthNftOpensea'; +// import EthNftMatic from '@crypto/blockchains/eth/apis/EthNftMatic'; +// import abi from './ext/erc721.js'; +// import AirDAODictNfts from '@crypto/common/AirDAODictNfts'; export default class EthTokenProcessorNft extends EthBasic { /** * @param data.address * @param data.tokenBlockchainCode */ - async getListBlockchain(data) { - const settings = AirDAODictNfts.NftsIndexed[data.tokenBlockchainCode]; - if ( - typeof settings !== 'undefined' && - typeof settings.apiType !== 'undefined' && - settings.apiType === 'OPENSEA' - ) { - return EthNftOpensea(data); - } else { - return EthNftMatic(data); - } - } - - async getNftDetails(nftAddress: string, nftType: string) { - this.checkWeb3CurrentServerUpdated(); - - let token, name, symbol; - try { - token = new this._web3.eth.Contract(abi.ERC721, nftAddress.toLowerCase()); - } catch (err) { - const e = err as unknown as any; - e.message = 'erc721 init token ' + e.message; - throw e; - } - - // @todo more checks! - try { - name = await token.methods.name().call(); - } catch (e) { - name = nftAddress.substr(0, 32); - } - - try { - symbol = await token.methods.symbol().call(); - } catch (e) { - symbol = name.substr(0, 5); - } - - const res = { - nftSymbol: symbol, - nftCode: symbol, - nftName: name, - nftType: nftType, - nftAddress: nftAddress.toLowerCase(), - provider: 'web3' - }; - return res; - } + // async getListBlockchain(data) { + // const settings = AirDAODictNfts.NftsIndexed[data.tokenBlockchainCode]; + // if ( + // typeof settings !== 'undefined' && + // typeof settings.apiType !== 'undefined' && + // settings.apiType === 'OPENSEA' + // ) { + // return EthNftOpensea(data); + // } else { + // return EthNftMatic(data); + // } + // } + // async getNftDetails(nftAddress: string, nftType: string) { + // this.checkWeb3CurrentServerUpdated(); + // + // let token, name, symbol; + // try { + // token = new this._web3.eth.Contract(abi.ERC721, nftAddress.toLowerCase()); + // } catch (err) { + // const e = err as unknown as any; + // e.message = 'erc721 init token ' + e.message; + // throw e; + // } + // + // // @todo more checks! + // try { + // name = await token.methods.name().call(); + // } catch (e) { + // name = nftAddress.substr(0, 32); + // } + // + // try { + // symbol = await token.methods.symbol().call(); + // } catch (e) { + // symbol = name.substr(0, 5); + // } + // + // const res = { + // nftSymbol: symbol, + // nftCode: symbol, + // nftName: name, + // nftType: nftType, + // nftAddress: nftAddress.toLowerCase(), + // provider: 'web3' + // }; + // return res; + // } } diff --git a/crypto/blockchains/eth/apis/EthNftMatic.ts b/crypto/blockchains/eth/apis/EthNftMatic.ts index 384e33424..b0a283478 100644 --- a/crypto/blockchains/eth/apis/EthNftMatic.ts +++ b/crypto/blockchains/eth/apis/EthNftMatic.ts @@ -2,7 +2,6 @@ * @version 0.50 */ import AirDAOAxios from '@crypto/common/AirDAOAxios'; -import BlocksoftUtils from '@crypto/common/AirDAOUtils'; import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; const API_PATH = 'https://microscanners.trustee.deals/getAllNfts/'; diff --git a/crypto/blockchains/eth/apis/EthNftOpensea.ts b/crypto/blockchains/eth/apis/EthNftOpensea.ts index 4f4cd9fe9..835e3aef9 100644 --- a/crypto/blockchains/eth/apis/EthNftOpensea.ts +++ b/crypto/blockchains/eth/apis/EthNftOpensea.ts @@ -14,7 +14,11 @@ const API_TEST_PATH = 'https://testnets-api.opensea.io/api/v1/'; * @param data.address * @param data.tokenBlockchainCode */ -export default async function (data) { +export default async function (data: { + tokenBlockchainCode: string; + address: string; + tokenBlockchain: any; +}) { let link; if (data.tokenBlockchainCode === 'ETH_RINKEBY') { link = API_TEST_PATH; @@ -42,7 +46,7 @@ export default async function (data) { * @var tmp.asset_contract.schema_name ERC721 */ const formatted = []; - const collections = []; + const collections: never[] = []; let usdTotal = 0; if ( @@ -88,7 +92,7 @@ export default async function (data) { one.subTitle = one.desc.length > 20 ? one.desc.substring(0, 20) + '...' : one.desc; } - } catch (e) { + } catch (e: any) { AirDAOCryptoLog.log( 'EthTokenProcessorNft EthNftOpensea name error ' + e.message ); @@ -107,7 +111,7 @@ export default async function (data) { ) { one.contractSchema = tmp.asset_contract.schema_name; } - } catch (e) { + } catch (e: any) { AirDAOCryptoLog.log( 'EthTokenProcessorNft EthNftOpensea contract error ' + e.message ); @@ -123,9 +127,10 @@ export default async function (data) { one.usdValue = tmp.last_sale.payment_token.usd_price; usdTotal = usdTotal + tmp.last_sale.payment_token.usd_price * 1; } - } catch (e) { + } catch (e: any) { AirDAOCryptoLog.log( 'EthTokenProcessorNft EthNftOpensealast_sale error ' + e.message, + // @ts-ignore JSON.stringify(tmp) ); } @@ -158,7 +163,7 @@ export default async function (data) { } } - const formattedCollections = []; + const formattedCollections: never[] = []; if (collections) { for (const key in collections) { formattedCollections.push(collections[key]); diff --git a/crypto/blockchains/eth/basic/EthBasic.ts b/crypto/blockchains/eth/basic/EthBasic.ts index 297150989..0f539adac 100644 --- a/crypto/blockchains/eth/basic/EthBasic.ts +++ b/crypto/blockchains/eth/basic/EthBasic.ts @@ -59,7 +59,7 @@ export default class EthBasic { * @type {string} * @public */ - _delegateAddress; + _delegateAddress: string | undefined; protected _settings: { network: any; @@ -133,122 +133,123 @@ export default class EthBasic { this._isTestnet = false; this._oklinkAPI = false; - if ( - settings.currencyCode === 'BNB_SMART' || - (typeof settings.tokenBlockchain !== 'undefined' && - settings.tokenBlockchain === 'BNB') - ) { - this._etherscanSuffix = ''; - this._etherscanApiPath = `https://api.bscscan.com/api?module=account&sort=desc&action=txlist&apikey=YourApiKeyToken`; - this._etherscanApiPathInternal = `https://api.bscscan.com/api?module=account&sort=desc&action=txlistinternal&apikey=YourApiKeyToken`; - this._etherscanApiPathForFee = `https://api.bscscan.com/api?module=proxy&action=eth_gasPrice&apikey=YourApiKeyToken`; - - this._trezorServer = false; - this._trezorServerCode = ''; - - this._mainCurrencyCode = 'BNB'; - this._mainTokenType = 'BNB_SMART_20'; - this._mainTokenBlockchain = 'Binance'; - this._mainChainId = 56; - } else if (settings.currencyCode === 'ETC') { - this._etherscanSuffix = false; - this._etherscanApiPath = false; - this._etherscanApiPathInternal = false; - this._etherscanApiPathForFee = false; - - this._trezorServer = 'to_load'; - this._trezorServerCode = 'ETC_TREZOR_SERVER'; - - this._mainCurrencyCode = 'ETC'; - this._mainTokenType = 'ETC_ERC_20'; - this._mainTokenBlockchain = 'Ethereum Classic'; - this._mainChainId = 61; // https://ethereumclassic.org/development/porting - } else if ( - settings.currencyCode === 'ETH_POW' || - (typeof settings.tokenBlockchain !== 'undefined' && - settings.tokenBlockchain === 'ETH_POW') - ) { - this._etherscanSuffix = ''; - this._etherscanApiPath = false; - this._etherscanApiPathInternal = false; - this._etherscanApiPathForFee = false; - - this._trezorServer = false; - this._trezorServerCode = ''; - - this._oklinkAPI = 'e11964ac-cfb9-406f-b2c5-3db76f91aebd'; - - this._mainCurrencyCode = 'ETH_POW'; - this._mainTokenType = 'ETH_POW_ERC_20'; - this._mainTokenBlockchain = 'ETH_POW'; - this._mainChainId = 10001; - } else if ( - settings.currencyCode === 'VLX' || - (typeof settings.tokenBlockchain !== 'undefined' && - settings.tokenBlockchain === 'VLX') - ) { - this._etherscanSuffix = ''; - this._etherscanApiPath = `https://evmexplorer.velas.com/api?module=account&sort=desc&action=txlist`; - this._etherscanApiPathInternal = false; - this._etherscanApiPathForFee = false; - - this._trezorServer = false; - this._trezorServerCode = ''; - - this._mainCurrencyCode = 'VLX'; - this._mainTokenType = 'VLX_ERC_20'; - this._mainTokenBlockchain = 'VLX'; - this._mainChainId = 106; - } else if ( - settings.currencyCode === 'ONE' || - (typeof settings.tokenBlockchain !== 'undefined' && - settings.tokenBlockchain === 'ONE') - ) { - this._etherscanSuffix = ''; - this._etherscanApiPath = false; - this._etherscanApiPathInternal = false; - this._etherscanApiPathForFee = false; - - this._trezorServer = false; - this._trezorServerCode = ''; - - this._mainCurrencyCode = 'ONE'; - this._mainTokenType = 'ONE_ERC_20'; - this._mainTokenBlockchain = 'ONE'; - this._mainChainId = 1666600000; - } else if ( - settings.currencyCode === 'METIS' || - (typeof settings.tokenBlockchain !== 'undefined' && - settings.tokenBlockchain === 'METIS') - ) { - this._etherscanSuffix = ''; - this._etherscanApiPath = `https://andromeda-explorer.metis.io/api?module=account&sort=desc&action=txlist`; - this._etherscanApiPathInternal = `https://andromeda-explorer.metis.io/api?module=account&sort=desc&action=txlistinternal`; - this._etherscanApiPathForFee = false; - - this._trezorServer = false; - this._trezorServerCode = ''; - - this._mainCurrencyCode = 'METIS'; - this._mainTokenType = 'METIS_ERC_20'; - this._mainTokenBlockchain = 'METIS'; - this._mainChainId = 1088; - } else if (settings.currencyCode === 'OPTIMISM') { - this._etherscanSuffix = ''; - this._etherscanApiPath = `https://api.optimistic.etherscan.io/api?module=account&sort=desc&action=txlist&apikey=YourApiKeyToken`; - this._etherscanApiPathInternal = `https://api.optimistic.etherscan.io/api?module=account&sort=desc&action=txlistinternal&apikey=YourApiKeyToken`; - this._etherscanApiPathDeposits = - 'https://api-optimistic.etherscan.io/api?module=account&action=getdeposittxs'; - this._etherscanApiPathForFee = `https://api.optimistic.etherscan.io/api?module=proxy&action=eth_gasPrice&apikey=YourApiKeyToken`; - - this._trezorServer = false; - this._trezorServerCode = ''; - - this._mainCurrencyCode = 'OPTIMISM'; - this._mainTokenType = 'OPTI_ERC_20'; - this._mainTokenBlockchain = 'Optimistic Ethereum'; - this._mainChainId = 10; // https://community.optimism.io/docs/developers/metamask.html#connecting-with-chainid-link - } else if (settings.currencyCode === 'AMB') { + // if ( + // settings.currencyCode === 'BNB_SMART' || + // (typeof settings.tokenBlockchain !== 'undefined' && + // settings.tokenBlockchain === 'BNB') + // ) { + // this._etherscanSuffix = ''; + // this._etherscanApiPath = `https://api.bscscan.com/api?module=account&sort=desc&action=txlist&apikey=YourApiKeyToken`; + // this._etherscanApiPathInternal = `https://api.bscscan.com/api?module=account&sort=desc&action=txlistinternal&apikey=YourApiKeyToken`; + // this._etherscanApiPathForFee = `https://api.bscscan.com/api?module=proxy&action=eth_gasPrice&apikey=YourApiKeyToken`; + // + // this._trezorServer = false; + // this._trezorServerCode = ''; + // + // this._mainCurrencyCode = 'BNB'; + // this._mainTokenType = 'BNB_SMART_20'; + // this._mainTokenBlockchain = 'Binance'; + // this._mainChainId = 56; + // } else if (settings.currencyCode === 'ETC') { + // this._etherscanSuffix = false; + // this._etherscanApiPath = false; + // this._etherscanApiPathInternal = false; + // this._etherscanApiPathForFee = false; + // + // this._trezorServer = 'to_load'; + // this._trezorServerCode = 'ETC_TREZOR_SERVER'; + // + // this._mainCurrencyCode = 'ETC'; + // this._mainTokenType = 'ETC_ERC_20'; + // this._mainTokenBlockchain = 'Ethereum Classic'; + // this._mainChainId = 61; // https://ethereumclassic.org/development/porting + // } else if ( + // settings.currencyCode === 'ETH_POW' || + // (typeof settings.tokenBlockchain !== 'undefined' && + // settings.tokenBlockchain === 'ETH_POW') + // ) { + // this._etherscanSuffix = ''; + // this._etherscanApiPath = false; + // this._etherscanApiPathInternal = false; + // this._etherscanApiPathForFee = false; + // + // this._trezorServer = false; + // this._trezorServerCode = ''; + // + // this._oklinkAPI = 'e11964ac-cfb9-406f-b2c5-3db76f91aebd'; + // + // this._mainCurrencyCode = 'ETH_POW'; + // this._mainTokenType = 'ETH_POW_ERC_20'; + // this._mainTokenBlockchain = 'ETH_POW'; + // this._mainChainId = 10001; + // } else if ( + // settings.currencyCode === 'VLX' || + // (typeof settings.tokenBlockchain !== 'undefined' && + // settings.tokenBlockchain === 'VLX') + // ) { + // this._etherscanSuffix = ''; + // this._etherscanApiPath = `https://evmexplorer.velas.com/api?module=account&sort=desc&action=txlist`; + // this._etherscanApiPathInternal = false; + // this._etherscanApiPathForFee = false; + // + // this._trezorServer = false; + // this._trezorServerCode = ''; + // + // this._mainCurrencyCode = 'VLX'; + // this._mainTokenType = 'VLX_ERC_20'; + // this._mainTokenBlockchain = 'VLX'; + // this._mainChainId = 106; + // } else if ( + // settings.currencyCode === 'ONE' || + // (typeof settings.tokenBlockchain !== 'undefined' && + // settings.tokenBlockchain === 'ONE') + // ) { + // this._etherscanSuffix = ''; + // this._etherscanApiPath = false; + // this._etherscanApiPathInternal = false; + // this._etherscanApiPathForFee = false; + // + // this._trezorServer = false; + // this._trezorServerCode = ''; + // + // this._mainCurrencyCode = 'ONE'; + // this._mainTokenType = 'ONE_ERC_20'; + // this._mainTokenBlockchain = 'ONE'; + // this._mainChainId = 1666600000; + // } else if ( + // settings.currencyCode === 'METIS' || + // (typeof settings.tokenBlockchain !== 'undefined' && + // settings.tokenBlockchain === 'METIS') + // ) { + // this._etherscanSuffix = ''; + // this._etherscanApiPath = `https://andromeda-explorer.metis.io/api?module=account&sort=desc&action=txlist`; + // this._etherscanApiPathInternal = `https://andromeda-explorer.metis.io/api?module=account&sort=desc&action=txlistinternal`; + // this._etherscanApiPathForFee = false; + // + // this._trezorServer = false; + // this._trezorServerCode = ''; + // + // this._mainCurrencyCode = 'METIS'; + // this._mainTokenType = 'METIS_ERC_20'; + // this._mainTokenBlockchain = 'METIS'; + // this._mainChainId = 1088; + // } else if (settings.currencyCode === 'OPTIMISM') { + // this._etherscanSuffix = ''; + // this._etherscanApiPath = `https://api.optimistic.etherscan.io/api?module=account&sort=desc&action=txlist&apikey=YourApiKeyToken`; + // this._etherscanApiPathInternal = `https://api.optimistic.etherscan.io/api?module=account&sort=desc&action=txlistinternal&apikey=YourApiKeyToken`; + // this._etherscanApiPathDeposits = + // 'https://api-optimistic.etherscan.io/api?module=account&action=getdeposittxs'; + // this._etherscanApiPathForFee = `https://api.optimistic.etherscan.io/api?module=proxy&action=eth_gasPrice&apikey=YourApiKeyToken`; + // + // this._trezorServer = false; + // this._trezorServerCode = ''; + // + // this._mainCurrencyCode = 'OPTIMISM'; + // this._mainTokenType = 'OPTI_ERC_20'; + // this._mainTokenBlockchain = 'Optimistic Ethereum'; + // this._mainChainId = 10; // https://community.optimism.io/docs/developers/metamask.html#connecting-with-chainid-link + // } else + if (settings.currencyCode === 'AMB') { this._etherscanSuffix = false; this._etherscanApiPath = false; this._etherscanApiPathInternal = false; @@ -261,69 +262,69 @@ export default class EthBasic { this._mainTokenType = 'AMB_ERC_20'; this._mainTokenBlockchain = 'Ambrosus Network'; this._mainChainId = 16718; // 0x414e - } else if ( - settings.currencyCode === 'MATIC' || - (typeof settings.tokenBlockchain !== 'undefined' && - settings.tokenBlockchain === 'MATIC') - ) { - this._etherscanSuffix = ''; - this._etherscanApiPath = `https://api.polygonscan.com/api?module=account&sort=desc&action=txlist&apikey=YourApiKeyToken`; - this._etherscanApiPathInternal = `https://api.polygonscan.com/api?module=account&sort=desc&action=txlistinternal&apikey=YourApiKeyToken`; - this._etherscanApiPathForFee = `https://api.polygonscan.com/api?module=proxy&action=eth_gasPrice&apikey=YourApiKeyToken`; - - this._trezorServer = false; - this._trezorServerCode = ''; - - this._mainCurrencyCode = 'MATIC'; - this._mainTokenType = 'MATIC_ERC_20'; - this._mainTokenBlockchain = 'Polygon Network'; - this._mainChainId = 137; - } else if ( - settings.currencyCode === 'FTM' || - (typeof settings.tokenBlockchain !== 'undefined' && - settings.tokenBlockchain === 'FTM') - ) { - this._etherscanSuffix = ''; - this._etherscanApiPath = `https://api.ftmscan.com/api?module=account&sort=desc&action=txlist&apikey=YourApiKeyToken`; - this._etherscanApiPathInternal = `https://api.ftmscan.com/api?module=account&sort=desc&action=txlistinternal&apikey=YourApiKeyToken`; - this._etherscanApiPathForFee = false; // invalid now `https://api.ftmscan.com/api?module=proxy&action=eth_gasPrice&apikey=YourApiKeyToken` - - this._trezorServer = false; - this._trezorServerCode = ''; - - this._mainCurrencyCode = 'FTM'; - this._mainTokenType = 'FTM_ERC_20'; - this._mainTokenBlockchain = 'Fantom Network'; - this._mainChainId = 250; - } else if ( - settings.currencyCode === 'BTTC' || - (typeof settings.tokenBlockchain !== 'undefined' && - settings.tokenBlockchain === 'BTTC') - ) { - this._etherscanSuffix = ''; - this._etherscanApiPath = `https://api.bttcscan.com/api?module=account&sort=desc&action=txlist&apikey=YourApiKeyToken`; - this._etherscanApiPathInternal = `https://api.bttcscan.com/api?module=account&sort=desc&action=txlistinternal&apikey=YourApiKeyToken`; - this._etherscanApiPathForFee = `https://api.bttcscan.com/api?module=proxy&action=eth_gasPrice&apikey=YourApiKeyToken`; - - this._trezorServer = false; - this._trezorServerCode = ''; - - this._mainCurrencyCode = 'BTTC'; - this._mainTokenType = 'BTTC_ERC_20'; - this._mainTokenBlockchain = 'BTTC Network'; - this._mainChainId = 199; - } else if (settings.currencyCode === 'RSK') { - this._etherscanSuffix = false; - this._etherscanApiPath = false; - this._etherscanApiPathInternal = false; - - this._trezorServer = false; - this._trezorServerCode = ''; - - this._mainCurrencyCode = 'RSK'; - this._mainTokenType = 'RSK_ERC_20'; - this._mainTokenBlockchain = 'RSK Network'; - this._mainChainId = 30; + // } else if ( + // settings.currencyCode === 'MATIC' || + // (typeof settings.tokenBlockchain !== 'undefined' && + // settings.tokenBlockchain === 'MATIC') + // ) { + // this._etherscanSuffix = ''; + // this._etherscanApiPath = `https://api.polygonscan.com/api?module=account&sort=desc&action=txlist&apikey=YourApiKeyToken`; + // this._etherscanApiPathInternal = `https://api.polygonscan.com/api?module=account&sort=desc&action=txlistinternal&apikey=YourApiKeyToken`; + // this._etherscanApiPathForFee = `https://api.polygonscan.com/api?module=proxy&action=eth_gasPrice&apikey=YourApiKeyToken`; + // + // this._trezorServer = false; + // this._trezorServerCode = ''; + // + // this._mainCurrencyCode = 'MATIC'; + // this._mainTokenType = 'MATIC_ERC_20'; + // this._mainTokenBlockchain = 'Polygon Network'; + // this._mainChainId = 137; + // } else if ( + // settings.currencyCode === 'FTM' || + // (typeof settings.tokenBlockchain !== 'undefined' && + // settings.tokenBlockchain === 'FTM') + // ) { + // this._etherscanSuffix = ''; + // this._etherscanApiPath = `https://api.ftmscan.com/api?module=account&sort=desc&action=txlist&apikey=YourApiKeyToken`; + // this._etherscanApiPathInternal = `https://api.ftmscan.com/api?module=account&sort=desc&action=txlistinternal&apikey=YourApiKeyToken`; + // this._etherscanApiPathForFee = false; // invalid now `https://api.ftmscan.com/api?module=proxy&action=eth_gasPrice&apikey=YourApiKeyToken` + // + // this._trezorServer = false; + // this._trezorServerCode = ''; + // + // this._mainCurrencyCode = 'FTM'; + // this._mainTokenType = 'FTM_ERC_20'; + // this._mainTokenBlockchain = 'Fantom Network'; + // this._mainChainId = 250; + // } else if ( + // settings.currencyCode === 'BTTC' || + // (typeof settings.tokenBlockchain !== 'undefined' && + // settings.tokenBlockchain === 'BTTC') + // ) { + // this._etherscanSuffix = ''; + // this._etherscanApiPath = `https://api.bttcscan.com/api?module=account&sort=desc&action=txlist&apikey=YourApiKeyToken`; + // this._etherscanApiPathInternal = `https://api.bttcscan.com/api?module=account&sort=desc&action=txlistinternal&apikey=YourApiKeyToken`; + // this._etherscanApiPathForFee = `https://api.bttcscan.com/api?module=proxy&action=eth_gasPrice&apikey=YourApiKeyToken`; + // + // this._trezorServer = false; + // this._trezorServerCode = ''; + // + // this._mainCurrencyCode = 'BTTC'; + // this._mainTokenType = 'BTTC_ERC_20'; + // this._mainTokenBlockchain = 'BTTC Network'; + // this._mainChainId = 199; + // } else if (settings.currencyCode === 'RSK') { + // this._etherscanSuffix = false; + // this._etherscanApiPath = false; + // this._etherscanApiPathInternal = false; + // + // this._trezorServer = false; + // this._trezorServerCode = ''; + // + // this._mainCurrencyCode = 'RSK'; + // this._mainTokenType = 'RSK_ERC_20'; + // this._mainTokenBlockchain = 'RSK Network'; + // this._mainChainId = 30; } else { this._etherscanSuffix = settings.network === 'mainnet' ? '' : '-' + settings.network; diff --git a/crypto/blockchains/eth/basic/EthNetworkPrices.ts b/crypto/blockchains/eth/basic/EthNetworkPrices.ts index 70966adbd..03afa31c7 100644 --- a/crypto/blockchains/eth/basic/EthNetworkPrices.ts +++ b/crypto/blockchains/eth/basic/EthNetworkPrices.ts @@ -25,7 +25,13 @@ let CACHE_PROXY_DATA = { }; let CACHE_PROXY_TIME = 0; class EthNetworkPrices { - async getWithProxy(mainCurrencyCode, isTestnet, address, logData = {}) { + private _mainCurrencyCode: string | undefined; + async getWithProxy( + mainCurrencyCode: string, + isTestnet: boolean, + address: string, + logData = {} + ) { if (mainCurrencyCode !== 'ETH' || isTestnet) { return false; } @@ -41,6 +47,7 @@ class EthNetworkPrices { ) { AirDAOCryptoLog.log( mainCurrencyCode + ' EthNetworkPricesProvider.getWithProxy from cache', + // @ts-ignore logData ); return CACHE_PROXY_DATA.result; @@ -48,6 +55,7 @@ class EthNetworkPrices { AirDAOCryptoLog.log( mainCurrencyCode + ' EthNetworkPricesProvider.getWithProxy started', + // @ts-ignore logData ); let checkResult = false; @@ -64,11 +72,13 @@ class EthNetworkPrices { // }, // 20000 // ); - } catch (e) {} + } catch (e) { + console.log({ e }); + } index++; } while (index < 3 && !checkResult); - if (checkResult !== false) { + if (checkResult) { if (typeof checkResult.data !== 'undefined') { await AirDAOCryptoLog.log( mainCurrencyCode + @@ -91,9 +101,10 @@ class EthNetworkPrices { ); } } else { + // TODO } - if (checkResult === false) { + if (!checkResult) { return { gasPrice: await this.getOnlyFees( mainCurrencyCode, @@ -107,6 +118,7 @@ class EthNetworkPrices { const result = checkResult.data; if (typeof result.gasPrice !== 'undefined') { for (const key in result.gasPrice) { + // @ts-ignore result.gasPrice[key] = BlocksoftUtils.div( BlocksoftUtils.toWei(result.gasPrice[key], 'gwei'), MAGIC_TX_DIVIDER @@ -177,7 +189,16 @@ class EthNetworkPrices { return result; } - async getOnlyFees(mainCurrencyCode, isTestnet, address, logData = {}) { + async getOnlyFees( + mainCurrencyCode: string, + isTestnet: boolean, + address: string, + logData = { + resultFeeSource: '', + resultFee: '', + resultFeeCacheTime: CACHE_FEES_ETH_TIME + } + ) { logData.resultFeeSource = 'fromCache'; const now = new Date().getTime(); if (CACHE_FEES_ETH && now - CACHE_FEES_ETH_TIME < CACHE_VALID_TIME) { @@ -209,6 +230,7 @@ class EthNetworkPrices { AirDAOCryptoLog.log( mainCurrencyCode + ' EthNetworkPricesProvider.getOnlyFees loaded new fee', + // @ts-ignore CACHE_PREV_DATA ); } else { @@ -217,6 +239,7 @@ class EthNetworkPrices { AirDAOCryptoLog.log( mainCurrencyCode + ' EthNetworkPricesProvider.getOnlyFees loaded prev fee as no fastest', + // @ts-ignore CACHE_PREV_DATA ); } @@ -224,6 +247,7 @@ class EthNetworkPrices { AirDAOCryptoLog.log( mainCurrencyCode + ' EthNetworkPricesProvider.getOnlyFees loaded prev fee as error', + // @ts-ignore CACHE_PREV_DATA ); // do nothing @@ -231,7 +255,7 @@ class EthNetworkPrices { try { await this._parseLoaded(mainCurrencyCode, CACHE_PREV_DATA, link); - } catch (e) { + } catch (e: any) { AirDAOCryptoLog.log( mainCurrencyCode + ' EthNetworkPricesProvider.getOnlyFees _parseLoaded error ' + @@ -246,8 +270,11 @@ class EthNetworkPrices { _format() { return { + // @ts-ignore speed_blocks_2: CACHE_FEES_ETH[2], + // @ts-ignore speed_blocks_6: CACHE_FEES_ETH[6], + // @ts-ignore speed_blocks_12: CACHE_FEES_ETH[12] }; } @@ -258,7 +285,10 @@ class EthNetworkPrices { * @param {int} json.fastest * @private */ - async _parseLoaded(mainCurrencyCode, json) { + async _parseLoaded( + mainCurrencyCode: string, + json: { fastest: any; safeLow: any; average: any } + ) { CACHE_FEES_ETH = {}; const externalSettings = await BlocksoftExternalSettings.getAll( @@ -297,7 +327,7 @@ class EthNetworkPrices { BlocksoftUtils.toWei(CACHE_FEES_ETH[2], 'gwei'), MAGIC_TX_DIVIDER ); // in gwei to wei + magic - } catch (e) { + } catch (e: any) { e.message += ' in EthPrice Magic divider'; throw e; } @@ -306,7 +336,13 @@ class EthNetworkPrices { } } -function addMultiply(mainCurrencyCode, blocks, fee, externalSettings) { +function addMultiply( + mainCurrencyCode: string, + blocks: string | number, + fee: number, + // @ts-ignore + externalSettings +) { if ( typeof externalSettings['ETH_CURRENT_PRICE_' + blocks] !== 'undefined' && externalSettings['ETH_CURRENT_PRICE_' + blocks] > 0 diff --git a/crypto/blockchains/eth/forks/EthScannerProcessorSoul.ts b/crypto/blockchains/eth/forks/EthScannerProcessorSoul.ts deleted file mode 100644 index 3a170e482..000000000 --- a/crypto/blockchains/eth/forks/EthScannerProcessorSoul.ts +++ /dev/null @@ -1,73 +0,0 @@ -/** - * @version 0.5 - */ -import BlocksoftUtils from '../../../common/AirDAOUtils'; -import BlocksoftBN from '../../../common/AirDAOBN'; -import EthScannerProcessorErc20 from '../EthScannerProcessorErc20'; -import AirDAOCryptoLog from '../../../common/AirDAOCryptoLog'; - -export default class EthScannerProcessorSoul extends EthScannerProcessorErc20 { - /** - * some fix for soul double tx - * @param {string} address - * @param {*} result - * @param {boolean} isInternal - * @param {boolean} isTrezor - * @returns {Promise<[]>} - * @private - */ - async _unifyTransactions( - address: string, - result, - isInternal: boolean, - isTrezor = true - ) { - const transactions = []; - const alreadyTransactions = {}; - let count = 0; - let tx; - for (tx of result) { - let transaction; - try { - if (isTrezor) { - transaction = await this._unifyTransactionTrezor( - address, - tx, - isInternal - ); - } else { - transaction = await this._unifyTransaction(address, tx, isInternal); - } - } catch (e) { - AirDAOCryptoLog.error( - 'EthScannerProcessorSoul._unifyTransaction error ' + - e.message + - ' on ' + - (isTrezor ? 'Trezor' : 'usual') + - ' tx ' + - JSON.stringify(tx) - ); - } - if (!transaction) { - continue; - } - transaction.addressAmount = new BlocksoftBN( - BlocksoftUtils.fromENumber(transaction.addressAmount) - ); - if ( - typeof alreadyTransactions[transaction.transactionHash] !== 'undefined' - ) { - const already = alreadyTransactions[transaction.transactionHash]; - transactions[already].addressAmount.add(transaction.addressAmount); - } else { - alreadyTransactions[transaction.transactionHash] = count; - count++; - transactions.push(transaction); - } - } - for (let i = 0, ic = transactions.length; i < ic; i++) { - transactions[i].addressAmount = transactions[i].addressAmount.get(); - } - return transactions; - } -} diff --git a/crypto/blockchains/eth/stores/EthRawDS.ts b/crypto/blockchains/eth/stores/EthRawDS.ts index b5f373d1e..c4924d90d 100644 --- a/crypto/blockchains/eth/stores/EthRawDS.ts +++ b/crypto/blockchains/eth/stores/EthRawDS.ts @@ -22,7 +22,7 @@ class EthRawDS { _trezorServer = 'none'; private _infuraProjectId: any; // TODO fix any - async getForAddress(data) { + async getForAddress(data: { address: any; currencyCode: any; }) { try { if (typeof data.currencyCode !== 'undefined') { this._currencyCode = @@ -306,7 +306,14 @@ class EthRawDS { await Database.unsafeRawQuery(tableName, sql); } - async saveRaw(data) { + async saveRaw(data: { + address: any; + currencyCode: any; + transactionUnique: any; + transactionHash: any; + transactionRaw: any; + transactionLog: any; + }) { if (typeof data.currencyCode !== 'undefined') { this._currencyCode = data.currencyCode === 'ETH_ROPSTEN' ? 'ETH_ROPSTEN' : 'ETH'; diff --git a/crypto/blockchains/eth/stores/EthTmpDS.ts b/crypto/blockchains/eth/stores/EthTmpDS.ts index be22714e3..e9a55248a 100644 --- a/crypto/blockchains/eth/stores/EthTmpDS.ts +++ b/crypto/blockchains/eth/stores/EthTmpDS.ts @@ -86,7 +86,7 @@ class EthTmpDS { const amountBN = {}; let queryLength = 0; - let queryTxs = []; + const queryTxs = []; for (const txHash in forBalances) { const tmps = (await Database.unsafeRawQuery( DatabaseTable.Transactions, @@ -164,38 +164,43 @@ class EthTmpDS { } } } - CACHE_TMP[address]['maxValue'] = maxValue; - CACHE_TMP[address]['maxScanned'] = maxScanned; - CACHE_TMP[address]['maxSuccess'] = + CACHE_TMP[address].maxValue = maxValue; + CACHE_TMP[address].maxScanned = maxScanned; + CACHE_TMP[address].maxSuccess = maxSuccess > maxScanned ? maxSuccess : maxScanned; - CACHE_TMP[address]['amountBlocked'] = {}; - CACHE_TMP[address]['queryLength'] = queryLength; - CACHE_TMP[address]['queryTxs'] = queryTxs; + CACHE_TMP[address].amountBlocked = {}; + CACHE_TMP[address].queryLength = queryLength; + CACHE_TMP[address].queryTxs = queryTxs; for (const key in amountBN) { - CACHE_TMP[address]['amountBlocked'][key] = amountBN[key].toString(); + // @ts-ignore + CACHE_TMP[address].amountBlocked[key] = amountBN[key].toString(); } return CACHE_TMP[address]; } - async getMaxNonce(mainCurrencyCode, scanAddress) { + async getMaxNonce(mainCurrencyCode: string, scanAddress: string) { if (mainCurrencyCode !== 'ETH') { return false; } const address = scanAddress.toLowerCase(); // if (typeof CACHE_TMP[address] === 'undefined' || typeof CACHE_TMP[address]['maxValue'] === 'undefined') { await this.getCache(mainCurrencyCode, address); - //} + // } return { - maxValue: CACHE_TMP[address]['maxValue'], - maxScanned: CACHE_TMP[address]['maxScanned'], - maxSuccess: CACHE_TMP[address]['maxSuccess'], - amountBlocked: CACHE_TMP[address]['amountBlocked'], - queryLength: CACHE_TMP[address]['queryLength'], - queryTxs: CACHE_TMP[address]['queryTxs'] + maxValue: CACHE_TMP[address].maxValue, + maxScanned: CACHE_TMP[address].maxScanned, + maxSuccess: CACHE_TMP[address].maxSuccess, + amountBlocked: CACHE_TMP[address].amountBlocked, + queryLength: CACHE_TMP[address].queryLength, + queryTxs: CACHE_TMP[address].queryTxs }; } - async removeNonce(mainCurrencyCode, scanAddress, key) { + async removeNonce( + mainCurrencyCode: string, + scanAddress: string, + key: string + ) { if (mainCurrencyCode !== 'ETH') { return false; } @@ -208,7 +213,12 @@ class EthTmpDS { await this.getCache(mainCurrencyCode, address); } - async saveNonce(mainCurrencyCode, scanAddress, key, value) { + async saveNonce( + mainCurrencyCode: string | undefined, + scanAddress: string, + key: string, + value: number + ) { if (mainCurrencyCode !== 'ETH') { return false; } @@ -222,6 +232,7 @@ class EthTmpDS { `SELECT tmp_val AS tmpVal FROM ${tableName} ${where}` )) as TransactionScannersTmpDBModel[]; if (res && res && res.length > 0) { + // @ts-ignore if (res[0].tmpVal * 1 >= value) { return true; } diff --git a/crypto/common/AirDAOAxios.ts b/crypto/common/AirDAOAxios.ts index 3895d47f8..5a43eb83d 100644 --- a/crypto/common/AirDAOAxios.ts +++ b/crypto/common/AirDAOAxios.ts @@ -107,96 +107,96 @@ class AirDAOAxios { return tmp; } - async postWithHeaders( - link: string, - data: any, - addHeaders: { [key: string]: string }, - errSend = true, - timeOut = false - ) { - let tmp = false; - try { - const headers: { [key: string]: string } = { - 'upgrade-insecure-requests': '1', - 'user-agent': - 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.128 Safari/537.36' - }; - const dataPrep = JSON.stringify(data); - headers['Content-Type'] = 'application/json'; - headers['Accept'] = 'application/json'; - for (const key in addHeaders) { - headers[key] = addHeaders[key]; - } + // async postWithHeaders( + // link: string, + // data: any, + // addHeaders: { [key: string]: string }, + // errSend = true, + // timeOut = false + // ) { + // let tmp = false; + // try { + // const headers: { [key: string]: string } = { + // 'upgrade-insecure-requests': '1', + // 'user-agent': + // 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.128 Safari/537.36' + // }; + // const dataPrep = JSON.stringify(data); + // headers['Content-Type'] = 'application/json'; + // headers['Accept'] = 'application/json'; + // for (const key in addHeaders) { + // headers[key] = addHeaders[key]; + // } + // + // const tmpInner = await fetch(link, { + // method: 'POST', + // credentials: 'same-origin', + // mode: 'same-origin', + // redirect: 'follow', + // headers, + // body: dataPrep + // }); + // if ( + // tmpInner.status !== 200 && + // tmpInner.status !== 201 && + // tmpInner.status !== 202 + // ) { + // AirDAOCryptoLog.log( + // 'AirDAOAxios.post fetch result ' + JSON.stringify(tmpInner) + // ); + // } else { + // tmp = { data: await tmpInner.json() }; + // } + // } catch (e) { + // AirDAOCryptoLog.log( + // 'AirDAOAxios.postWithHeaders fetch result error ' + e.message + // ); + // } + // return tmp; + // } - const tmpInner = await fetch(link, { - method: 'POST', - credentials: 'same-origin', - mode: 'same-origin', - redirect: 'follow', - headers, - body: dataPrep - }); - if ( - tmpInner.status !== 200 && - tmpInner.status !== 201 && - tmpInner.status !== 202 - ) { - AirDAOCryptoLog.log( - 'AirDAOAxios.post fetch result ' + JSON.stringify(tmpInner) - ); - } else { - tmp = { data: await tmpInner.json() }; - } - } catch (e) { - AirDAOCryptoLog.log( - 'AirDAOAxios.postWithHeaders fetch result error ' + e.message - ); - } - return tmp; - } - - async getWithHeaders( - link: string, - addHeaders: { [key: string]: string }, - errSend = true, - timeOut = false - ) { - let tmp = false; - try { - const headers: { [key: string]: string } = { - 'upgrade-insecure-requests': '1', - 'user-agent': - 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.128 Safari/537.36' - }; - headers['Content-Type'] = 'application/json'; - headers['Accept'] = 'application/json'; - for (const key in addHeaders) { - headers[key] = addHeaders[key]; - } - - const tmpInner = await fetch(link, { - method: 'GET', - credentials: 'same-origin', - mode: 'same-origin', - redirect: 'follow', - headers - }); - if ( - tmpInner.status !== 200 && - tmpInner.status !== 201 && - tmpInner.status !== 202 - ) { - AirDAOCryptoLog.log( - 'AirDAOAxios.get fetch result ' + JSON.stringify(tmpInner) - ); - } else { - tmp = { data: await tmpInner.json() }; - } - } catch (e) { - // TODO ignore - } - return tmp; - } + // async getWithHeaders( + // link: string, + // addHeaders: { [key: string]: string }, + // errSend = true, + // timeOut = false + // ) { + // let tmp = false; + // try { + // const headers: { [key: string]: string } = { + // 'upgrade-insecure-requests': '1', + // 'user-agent': + // 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.128 Safari/537.36' + // }; + // headers['Content-Type'] = 'application/json'; + // headers['Accept'] = 'application/json'; + // for (const key in addHeaders) { + // headers[key] = addHeaders[key]; + // } + // + // const tmpInner = await fetch(link, { + // method: 'GET', + // credentials: 'same-origin', + // mode: 'same-origin', + // redirect: 'follow', + // headers + // }); + // if ( + // tmpInner.status !== 200 && + // tmpInner.status !== 201 && + // tmpInner.status !== 202 + // ) { + // AirDAOCryptoLog.log( + // 'AirDAOAxios.get fetch result ' + JSON.stringify(tmpInner) + // ); + // } else { + // tmp = { data: await tmpInner.json() }; + // } + // } catch (e) { + // // TODO ignore + // } + // return tmp; + // } async post(link: string, data: any, errSend = true, timeOut = false) { let tmp = false; @@ -299,7 +299,7 @@ class AirDAOAxios { return !(domain.indexOf('trustee') === -1); } - async _cookie(link, method) { + async _cookie(link: string, method: string) { const tmp = link.split('/'); const domain = tmp[0] + '/' + tmp[1] + '/' + tmp[2]; const domainShort = tmp[2]; @@ -330,7 +330,7 @@ class AirDAOAxios { } async _request( - link, + link: string, method = 'get', data = {}, emptyIsBad = false, @@ -441,7 +441,7 @@ class AirDAOAxios { */ CACHE_TIMEOUT_ERRORS = 0; - } catch (e) { + } catch (e: any) { if (typeof CACHE_STARTED[cacheMD] !== 'undefined') { delete CACHE_STARTED[cacheMD]; } @@ -489,7 +489,7 @@ class AirDAOAxios { } else { if (e.message.indexOf('timeout') !== -1) { CACHE_TIMEOUT_ERRORS++; - let now = new Date().getTime(); + const now = new Date().getTime(); if ( CACHE_TIMEOUT_ERRORS > 10 && now - CACHE_TIMEOUT_ERROR_SHOWN > 60000 diff --git a/crypto/common/AirDAOBN.ts b/crypto/common/AirDAOBN.ts index a8fa70a18..c57ca8717 100644 --- a/crypto/common/AirDAOBN.ts +++ b/crypto/common/AirDAOBN.ts @@ -3,20 +3,20 @@ import BlocksoftUtils from './AirDAOUtils'; class AirDAOBN { innerBN = false; - constructor(val) { + constructor(val: BigNumber.Value) { // console.log('AirDAOBN construct', JSON.stringify(val)) if (typeof val.innerBN !== 'undefined') { try { // noinspection JSCheckFunctionSignatures,JSUnresolvedVariable this.innerBN = new BigNumber(val.innerBN.toString()); - } catch (e) { + } catch (e: any) { throw new Error(e.message + ' while AirDAOBN.constructor ' + val); } } else { try { // noinspection JSCheckFunctionSignatures,JSUnresolvedVariable this.innerBN = new BigNumber(val); - } catch (e) { + } catch (e: any) { throw new Error(e.message + ' while AirDAOBN.constructor ' + val); } } diff --git a/crypto/common/AirDAOCryptoLog.ts b/crypto/common/AirDAOCryptoLog.ts index c3ade744b..4a0b80a08 100644 --- a/crypto/common/AirDAOCryptoLog.ts +++ b/crypto/common/AirDAOCryptoLog.ts @@ -13,28 +13,37 @@ // const DEBUG = config.debug.cryptoLogs // set true to see usual logs in console -const MAX_MESSAGE = 2000; -const FULL_MAX_MESSAGE = 20000; +// const MAX_MESSAGE = 2000; +// const FULL_MAX_MESSAGE = 20000; -const LOGS_TXT = ''; -const FULL_LOGS_TXT = ''; +// const LOGS_TXT = ''; +// const FULL_LOGS_TXT = ''; class AirDAOCryptoLog { + private _txtOrObj: string | undefined; + private _txtOrObj2: boolean | undefined; + private _txtOrObj3: boolean | undefined; + private _errorObjectOrText: string | undefined; + private _errorObject2: string | undefined; + private _errorTitle: string | undefined; constructor() { // this.FS = new FileSystem({ fileEncoding: 'utf8', fileName: 'CryptoLog', fileExtension: 'txt' }) // this.DATA = {} // this.DATA.LOG_VERSION = false } - async _reinitTgMessage(testerMode, obj, msg) { - // for (const key in obj) { - // this.DATA[key] = obj[key] - // } - // // noinspection JSIgnoredPromiseFromCall - // await this.FS.checkOverflow() - } - - async log(txtOrObj, txtOrObj2 = false, txtOrObj3 = false) { + // async _reinitTgMessage(testerMode, obj, msg) { + // // for (const key in obj) { + // // this.DATA[key] = obj[key] + // // } + // // // noinspection JSIgnoredPromiseFromCall + // // await this.FS.checkOverflow() + // } + + async log(txtOrObj: string, txtOrObj2 = false, txtOrObj3 = false) { + this._txtOrObj = txtOrObj; + this._txtOrObj2 = txtOrObj2; + this._txtOrObj3 = txtOrObj3; return true; // if (settingsActions.getSettingStatic('loggingCode') === 'none') { // return @@ -92,7 +101,14 @@ class AirDAOCryptoLog { // return true } - async err(errorObjectOrText, errorObject2 = '', errorTitle = 'ERROR') { + async err( + errorObjectOrText: string, + errorObject2 = '', + errorTitle = 'ERROR' + ) { + this._errorObjectOrText = errorObjectOrText; + this._errorObject2 = errorObject2; + this._errorTitle = errorTitle; // const now = new Date() // const date = now.toISOString().replace(/T/, ' ').replace(/\..+/, '') // let line = '' diff --git a/crypto/common/AirDAOCryptoUtils.ts b/crypto/common/AirDAOCryptoUtils.ts deleted file mode 100644 index d3612904c..000000000 --- a/crypto/common/AirDAOCryptoUtils.ts +++ /dev/null @@ -1,9 +0,0 @@ -import createHash from 'create-hash'; - -class AirDAOCryptoUtils { - static sha256(stringHex: string) { - return createHash('sha256').update(stringHex, 'hex').digest('hex'); - } -} - -export default AirDAOCryptoUtils; diff --git a/crypto/common/AirDAODict.ts b/crypto/common/AirDAODict.ts index 9d2e3243b..05f04ce71 100644 --- a/crypto/common/AirDAODict.ts +++ b/crypto/common/AirDAODict.ts @@ -98,103 +98,103 @@ const CurrenciesForTests = { // delete Currencies['XMR']; // } -function addAndUnifyCustomCurrency(currencyObject: { - id: string; - isHidden: number; - tokenJSON: string; - tokenDecimals: string; - tokenAddress: string; - tokenType: string; - currencyName: string; - currencySymbol: string; - currencyCode: string; -}) { - const tmp = { - currencyName: currencyObject.currencyName, - currencyCode: 'CUSTOM_' + currencyObject.currencyCode, - currencySymbol: currencyObject.currencySymbol, - ratesCurrencyCode: currencyObject.currencyCode, - decimals: currencyObject.tokenDecimals, - currencyType: 'custom' - }; - if (currencyObject.tokenType === 'BNB_SMART_20') { - tmp.currencyCode = 'CUSTOM_BNB_SMART_20_' + currencyObject.currencyCode; - if (tmp.ratesCurrencyCode.substr(0, 1) === 'B') { - const subRate = tmp.ratesCurrencyCode.substr(1); - if (typeof Currencies[subRate] !== 'undefined') { - tmp.ratesCurrencyCode = subRate; - } - } - tmp.extendsProcessor = 'BNB_SMART_CAKE'; - tmp.addressUiChecker = 'ETH'; - tmp.tokenAddress = currencyObject.tokenAddress; - tmp.tokenBlockchain = 'BNB'; - tmp.currencyExplorerLink = - 'https://bscscan.com/token/' + currencyObject.tokenAddress + '?a='; - } else if (currencyObject.tokenType === 'MATIC_ERC_20') { - tmp.currencyCode = 'CUSTOM_MATIC_ERC_20_' + currencyObject.currencyCode; - tmp.extendsProcessor = 'MATIC_USDT'; - tmp.addressUiChecker = 'ETH'; - tmp.tokenAddress = currencyObject.tokenAddress; - tmp.tokenBlockchain = 'MATIC'; - tmp.currencyExplorerLink = - 'https://polygonscan.com/token/' + currencyObject.tokenAddress + '?a='; - } else if (currencyObject.tokenType === 'FTM_ERC_20') { - tmp.currencyCode = 'CUSTOM_FTM_ERC_20_' + currencyObject.currencyCode; - tmp.extendsProcessor = 'FTM_USDC'; - tmp.addressUiChecker = 'ETH'; - tmp.tokenAddress = currencyObject.tokenAddress; - tmp.tokenBlockchain = 'FTM'; - tmp.currencyExplorerLink = - 'https://ftmscan.com/token/' + currencyObject.tokenAddress + '?a='; - } else if (currencyObject.tokenType === 'VLX_ERC_20') { - tmp.currencyCode = 'CUSTOM_VLX_ERC_20_' + currencyObject.currencyCode; - tmp.extendsProcessor = 'VLX_USDT'; - tmp.addressUiChecker = 'ETH'; - tmp.tokenAddress = currencyObject.tokenAddress; - tmp.tokenBlockchain = 'VLX'; - tmp.currencyExplorerLink = - 'https://evmexplorer.velas.com/tokens/' + currencyObject.tokenAddress; - } else if (currencyObject.tokenType === 'ONE_ERC_20') { - tmp.currencyCode = 'CUSTOM_ONE_ERC_20_' + currencyObject.currencyCode; - tmp.extendsProcessor = 'ONE_USDC'; - tmp.addressUiChecker = 'ETH'; - tmp.tokenAddress = currencyObject.tokenAddress; - tmp.tokenBlockchain = 'ONE'; - tmp.currencyExplorerLink = - 'https://explorer.harmony.one/address/' + currencyObject.tokenAddress; - } else if (currencyObject.tokenType === 'SOL') { - tmp.currencyCode = 'CUSTOM_SOL_' + currencyObject.currencyCode; - tmp.extendsProcessor = 'SOL_RAY'; - tmp.addressUiChecker = 'SOL'; - tmp.tokenAddress = currencyObject.tokenAddress; - tmp.tokenBlockchain = 'SOLANA'; - } else if (currencyObject.tokenType === 'ETH_ERC_20') { - tmp.extendsProcessor = 'ETH_TRUE_USD'; - tmp.addressUiChecker = 'ETH'; - tmp.tokenAddress = currencyObject.tokenAddress; - tmp.tokenBlockchain = 'ETHEREUM'; - tmp.currencyExplorerLink = - 'https://etherscan.io/token/' + currencyObject.tokenAddress + '?a='; - } else if (currencyObject.tokenType === 'TRX') { - tmp.currencyCode = 'CUSTOM_TRX_' + currencyObject.currencyCode; - tmp.extendsProcessor = 'TRX_USDT'; - tmp.addressUiChecker = 'TRX'; - tmp.currencyIcon = 'TRX'; - tmp.tokenName = currencyObject.tokenAddress; - tmp.tokenBlockchain = 'TRON'; - tmp.currencyExplorerLink = 'https://tronscan.org/#/address/'; - tmp.currencyExplorerTxLink = 'https://tronscan.org/#/transaction/'; - if (tmp.tokenName.substr(0, 1) !== 'T') { - this.skipParentBalanceCheck = true; - } - } else { - return false; - } - - Currencies[tmp.currencyCode] = tmp; - return tmp; -} +// function addAndUnifyCustomCurrency(currencyObject: { +// id: string; +// isHidden: number; +// tokenJSON: string; +// tokenDecimals: string; +// tokenAddress: string; +// tokenType: string; +// currencyName: string; +// currencySymbol: string; +// currencyCode: string; +// }) { +// const tmp = { +// currencyName: currencyObject.currencyName, +// currencyCode: 'CUSTOM_' + currencyObject.currencyCode, +// currencySymbol: currencyObject.currencySymbol, +// ratesCurrencyCode: currencyObject.currencyCode, +// decimals: currencyObject.tokenDecimals, +// currencyType: 'custom' +// }; +// if (currencyObject.tokenType === 'BNB_SMART_20') { +// tmp.currencyCode = 'CUSTOM_BNB_SMART_20_' + currencyObject.currencyCode; +// if (tmp.ratesCurrencyCode.substr(0, 1) === 'B') { +// const subRate = tmp.ratesCurrencyCode.substr(1); +// if (typeof Currencies[subRate] !== 'undefined') { +// tmp.ratesCurrencyCode = subRate; +// } +// } +// tmp.extendsProcessor = 'BNB_SMART_CAKE'; +// tmp.addressUiChecker = 'ETH'; +// tmp.tokenAddress = currencyObject.tokenAddress; +// tmp.tokenBlockchain = 'BNB'; +// tmp.currencyExplorerLink = +// 'https://bscscan.com/token/' + currencyObject.tokenAddress + '?a='; +// } else if (currencyObject.tokenType === 'MATIC_ERC_20') { +// tmp.currencyCode = 'CUSTOM_MATIC_ERC_20_' + currencyObject.currencyCode; +// tmp.extendsProcessor = 'MATIC_USDT'; +// tmp.addressUiChecker = 'ETH'; +// tmp.tokenAddress = currencyObject.tokenAddress; +// tmp.tokenBlockchain = 'MATIC'; +// tmp.currencyExplorerLink = +// 'https://polygonscan.com/token/' + currencyObject.tokenAddress + '?a='; +// } else if (currencyObject.tokenType === 'FTM_ERC_20') { +// tmp.currencyCode = 'CUSTOM_FTM_ERC_20_' + currencyObject.currencyCode; +// tmp.extendsProcessor = 'FTM_USDC'; +// tmp.addressUiChecker = 'ETH'; +// tmp.tokenAddress = currencyObject.tokenAddress; +// tmp.tokenBlockchain = 'FTM'; +// tmp.currencyExplorerLink = +// 'https://ftmscan.com/token/' + currencyObject.tokenAddress + '?a='; +// } else if (currencyObject.tokenType === 'VLX_ERC_20') { +// tmp.currencyCode = 'CUSTOM_VLX_ERC_20_' + currencyObject.currencyCode; +// tmp.extendsProcessor = 'VLX_USDT'; +// tmp.addressUiChecker = 'ETH'; +// tmp.tokenAddress = currencyObject.tokenAddress; +// tmp.tokenBlockchain = 'VLX'; +// tmp.currencyExplorerLink = +// 'https://evmexplorer.velas.com/tokens/' + currencyObject.tokenAddress; +// } else if (currencyObject.tokenType === 'ONE_ERC_20') { +// tmp.currencyCode = 'CUSTOM_ONE_ERC_20_' + currencyObject.currencyCode; +// tmp.extendsProcessor = 'ONE_USDC'; +// tmp.addressUiChecker = 'ETH'; +// tmp.tokenAddress = currencyObject.tokenAddress; +// tmp.tokenBlockchain = 'ONE'; +// tmp.currencyExplorerLink = +// 'https://explorer.harmony.one/address/' + currencyObject.tokenAddress; +// } else if (currencyObject.tokenType === 'SOL') { +// tmp.currencyCode = 'CUSTOM_SOL_' + currencyObject.currencyCode; +// tmp.extendsProcessor = 'SOL_RAY'; +// tmp.addressUiChecker = 'SOL'; +// tmp.tokenAddress = currencyObject.tokenAddress; +// tmp.tokenBlockchain = 'SOLANA'; +// } else if (currencyObject.tokenType === 'ETH_ERC_20') { +// tmp.extendsProcessor = 'ETH_TRUE_USD'; +// tmp.addressUiChecker = 'ETH'; +// tmp.tokenAddress = currencyObject.tokenAddress; +// tmp.tokenBlockchain = 'ETHEREUM'; +// tmp.currencyExplorerLink = +// 'https://etherscan.io/token/' + currencyObject.tokenAddress + '?a='; +// } else if (currencyObject.tokenType === 'TRX') { +// tmp.currencyCode = 'CUSTOM_TRX_' + currencyObject.currencyCode; +// tmp.extendsProcessor = 'TRX_USDT'; +// tmp.addressUiChecker = 'TRX'; +// tmp.currencyIcon = 'TRX'; +// tmp.tokenName = currencyObject.tokenAddress; +// tmp.tokenBlockchain = 'TRON'; +// tmp.currencyExplorerLink = 'https://tronscan.org/#/address/'; +// tmp.currencyExplorerTxLink = 'https://tronscan.org/#/transaction/'; +// if (tmp.tokenName.substr(0, 1) !== 'T') { +// this.skipParentBalanceCheck = true; +// } +// } else { +// return false; +// } +// +// Currencies[tmp.currencyCode] = tmp; +// return tmp; +// } const ALL_SETTINGS = {}; @@ -258,6 +258,6 @@ export default { Codes, Currencies, CurrenciesForTests, - getCurrencyAllSettings, - addAndUnifyCustomCurrency + getCurrencyAllSettings + // addAndUnifyCustomCurrency }; diff --git a/crypto/common/AirDAODictNfts.ts b/crypto/common/AirDAODictNfts.ts deleted file mode 100644 index fb5abce57..000000000 --- a/crypto/common/AirDAODictNfts.ts +++ /dev/null @@ -1,125 +0,0 @@ -const Nfts = [ - { - currencyCode: 'NFT_ETH', - currencyName: 'NFT', - currencySymbol: 'NFT', - tokenBlockchain: 'ETHEREUM', - tokenBlockchainCode: 'ETH', - currencyType: 'NFT', - apiType: 'OPENSEA', - explorerLink: 'https://explorerLink.com/token/', - showOnHome: true - }, - { - currencyCode: 'NFT_BNB', - currencyName: 'BNB NFT', - currencySymbol: 'NFT', - tokenBlockchain: 'BNB', - tokenBlockchainShortTitle: 'BNB SC', - tokenBlockchainLongTitle: 'BNB Smart Chain', - tokenBlockchainCode: 'BNB_SMART', - currencyType: 'NFT', - explorerLink: 'https://bscscan.com/token/', - showOnHome: false - }, - - { - currencyCode: 'NFT_MATIC', - currencyName: 'Matic NFT', - currencySymbol: 'NFT', - tokenBlockchain: 'MATIC', - tokenBlockchainCode: 'MATIC', - currencyType: 'NFT', - explorerLink: 'https://polygonscan.com/token/', - showOnHome: false - }, - { - currencyCode: 'NFT_ONE', - currencyName: 'Harmony NFT', - currencySymbol: 'NFT', - tokenBlockchain: 'ONE', - tokenBlockchainCode: 'ONE', - currencyType: 'NFT', - explorerLink: 'https://davinci.gallery/view/', - showOnHome: false - }, - { - currencyCode: 'NFT_ROPSTEN', - currencyName: 'Ropsten NFT', - currencySymbol: 'NFT', - tokenBlockchain: 'ROPSTEN', - tokenBlockchainCode: 'ETH_ROPSTEN', - currencyType: 'NFT', - explorerLink: 'https://ropsten.explorerLink.io/token/', - showOnHome: false - }, - { - currencyCode: 'NFT_RINKEBY', - currencyName: 'Rinkeby NFT', - currencySymbol: 'NFT', - tokenBlockchain: 'RINKEBY', - tokenBlockchainCode: 'ETH_RINKEBY', - currencyType: 'NFT', - apiType: 'OPENSEA', - explorerLink: 'https://rinkeby.explorerLink.io/token/', - showOnHome: false - } - /* - { - currencyCode: 'NFT_TRON', - currencyName: 'Tron NFT', - currencySymbol: 'NFT', - tokenBlockchain: 'TRON', - tokenBlockchainCode: 'TRX', - currencyType: 'NFT' - } - */ -]; - -const NftsIndexed = {}; -for (const tmp of Nfts) { - NftsIndexed[tmp.tokenBlockchainCode] = tmp; - NftsIndexed[tmp.tokenBlockchain] = tmp; -} - -const getCurrencyCode = (walletCurrency, tokenBlockchainCode) => { - if (!walletCurrency && !tokenBlockchainCode) { - return ''; - } - - if (walletCurrency) { - return walletCurrency; - } - - const tmp = NftsIndexed[tokenBlockchainCode.toUpperCase()]; - if (typeof tmp === 'undefined') { - return tokenBlockchainCode; - } - return tmp.tokenBlockchainCode; -}; - -const getCurrencyTitle = ( - walletCurrency: string, - tokenBlockchainCode: string -) => { - if (!walletCurrency && !tokenBlockchainCode) { - return ''; - } - - if (walletCurrency) { - return walletCurrency; - } - - const tmp = NftsIndexed[tokenBlockchainCode.toUpperCase()]; - if (typeof tmp === 'undefined') { - return tokenBlockchainCode; - } - return tmp.tokenBlockchainShortTitle || tmp.tokenBlockchain; -}; - -export default { - Nfts, - NftsIndexed, - getCurrencyTitle, - getCurrencyCode -}; diff --git a/crypto/common/AirDAODictTypes.ts b/crypto/common/AirDAODictTypes.ts index bc070ae1e..c4a71a78a 100644 --- a/crypto/common/AirDAODictTypes.ts +++ b/crypto/common/AirDAODictTypes.ts @@ -6,54 +6,54 @@ export namespace AirDAODictTypes { export enum Code { AMB = 'AMB', - BTC = 'BTC', - BTC_SEGWIT = 'BTC_SEGWIT', - BTC_SEGWIT_COMPATIBLE = 'BTC_SEGWIT_COMPATIBLE', + // BTC = 'BTC', + // BTC_SEGWIT = 'BTC_SEGWIT', + // BTC_SEGWIT_COMPATIBLE = 'BTC_SEGWIT_COMPATIBLE', ETH_ERC_20 = 'ETH_ERC_20', - USDT = 'USDT', - LTC = 'LTC', - ETH = 'ETH', - ETH_USDT = 'ETH_USDT', - ETH_UAX = 'ETH_UAX', - ETH_TRUE_USD = 'ETH_TRUE_USD', - ETH_BNB = 'ETH_BNB', - ETH_USDC = 'ETH_USDC', - ETH_PAX = 'ETH_PAX', - ETH_DAI = 'ETH_DAI', - ETH_DAIM = 'ETH_DAIM', - TRX = 'TRX', - XMR = 'XMR', - XLM = 'XLM', - BTC_TEST = 'BTC_TEST', - BCH = 'BCH', - BSV = 'BSV', - BTG = 'BTG', - DOGE = 'DOGE', - XVG = 'XVG', - XRP = 'XRP', - ETH_ROPSTEN = 'ETH_ROPSTEN', - ETH_RINKEBY = 'ETH_RINKEBY', - TRX_USDT = 'TRX_USDT', - TRX_BTT = 'TRX_BTT', - ETH_OKB = 'ETH_OKB', - ETH_MKR = 'ETH_MKR', - ETH_KNC = 'ETH_KNC', - ETH_COMP = 'ETH_COMP', - ETH_BAL = 'ETH_BAL', - ETH_LEND = 'ETH_LEND', - ETH_BNT = 'ETH_BNT', - ETH_SOUL = 'ETH_SOUL', - ETH_ONE = 'ETH_ONE', - FIO = 'FIO', - BNB = 'BNB', - BNB_SMART = 'BNB_SMART', - BNB_SMART_20 = 'BNB_SMART_20', - ETC = 'ETC', - VET = 'VET', - SOL = 'SOL', - WAVES = 'WAVES', - ASH = 'ASH', - METIS = 'METIS', - SOL_SPL = 'SOL_SPL' + // USDT = 'USDT', + // LTC = 'LTC', + ETH = 'ETH' + // ETH_USDT = 'ETH_USDT', + // ETH_UAX = 'ETH_UAX', + // ETH_TRUE_USD = 'ETH_TRUE_USD', + // ETH_BNB = 'ETH_BNB', + // ETH_USDC = 'ETH_USDC', + // ETH_PAX = 'ETH_PAX', + // ETH_DAI = 'ETH_DAI', + // ETH_DAIM = 'ETH_DAIM', + // TRX = 'TRX', + // XMR = 'XMR', + // XLM = 'XLM', + // BTC_TEST = 'BTC_TEST', + // BCH = 'BCH', + // BSV = 'BSV', + // BTG = 'BTG', + // DOGE = 'DOGE', + // XVG = 'XVG', + // XRP = 'XRP', + // ETH_ROPSTEN = 'ETH_ROPSTEN', + // ETH_RINKEBY = 'ETH_RINKEBY', + // TRX_USDT = 'TRX_USDT', + // TRX_BTT = 'TRX_BTT', + // ETH_OKB = 'ETH_OKB', + // ETH_MKR = 'ETH_MKR', + // ETH_KNC = 'ETH_KNC', + // ETH_COMP = 'ETH_COMP', + // ETH_BAL = 'ETH_BAL', + // ETH_LEND = 'ETH_LEND', + // ETH_BNT = 'ETH_BNT', + // ETH_SOUL = 'ETH_SOUL', + // ETH_ONE = 'ETH_ONE', + // FIO = 'FIO', + // BNB = 'BNB', + // BNB_SMART = 'BNB_SMART', + // BNB_SMART_20 = 'BNB_SMART_20', + // ETC = 'ETC', + // VET = 'VET', + // SOL = 'SOL', + // WAVES = 'WAVES', + // ASH = 'ASH', + // METIS = 'METIS', + // SOL_SPL = 'SOL_SPL' } } diff --git a/crypto/common/AirDAOExplorerDict.ts b/crypto/common/AirDAOExplorerDict.ts index 5ca0fe45e..b6835db58 100644 --- a/crypto/common/AirDAOExplorerDict.ts +++ b/crypto/common/AirDAOExplorerDict.ts @@ -1,20 +1,20 @@ const CurrenciesExplorer = { - BTC: [ - { - currencyCode: 'BTC', - tokenBlockchain: 'BITCOIN', - explorerName: 'Blockchair', - explorerLink: 'https://blockchair.com/bitcoin/address/', - explorerTxLink: 'https://blockchair.com/bitcoin/transaction/' - }, - { - currencyCode: 'BTC', - tokenBlockchain: 'BITCOIN', - explorerName: 'Blockchain.com', - explorerLink: 'https://www.blockchain.com/btc/address/', - explorerTxLink: 'https://www.blockchain.com/btc/tx/' - } - ], + // BTC: [ + // { + // currencyCode: 'BTC', + // tokenBlockchain: 'BITCOIN', + // explorerName: 'Blockchair', + // explorerLink: 'https://blockchair.com/bitcoin/address/', + // explorerTxLink: 'https://blockchair.com/bitcoin/transaction/' + // }, + // { + // currencyCode: 'BTC', + // tokenBlockchain: 'BITCOIN', + // explorerName: 'Blockchain.com', + // explorerLink: 'https://www.blockchain.com/btc/address/', + // explorerTxLink: 'https://www.blockchain.com/btc/tx/' + // } + // ], ETH: [ { currencyCode: 'ETH', diff --git a/crypto/common/AirDAOExternalSettings.ts b/crypto/common/AirDAOExternalSettings.ts index 0450d7017..818b47adc 100644 --- a/crypto/common/AirDAOExternalSettings.ts +++ b/crypto/common/AirDAOExternalSettings.ts @@ -1,6 +1,5 @@ import AirDAOAxios from './AirDAOAxios'; import AirDAOCryptoLog from './AirDAOCryptoLog'; -// @ts-ignore // import ApiProxy from '../../app/services/Api/ApiProxy'; import config from '@constants/config'; @@ -26,81 +25,81 @@ interface Cache { const TREZOR_SERVERS: TrezorServers = {}; const CACHE: Cache = { - TRX_VOTE_BEST: 'TV9QitxEJ3pdiAUAfJ2QuPxLKp9qTTR3og', - TRX_SEND_LINK: 'http://trx.trusteeglobal.com:8090', // http://trx.trusteeglobal.com:8090/wallet - TRX_SOLIDITY_NODE: 'http://trx.trusteeglobal.com:8091', // http://trx.trusteeglobal.com:8091/walletsolidity - TRX_USE_TRONSCAN: 0, - SOL_VOTE_BEST: 'CertusDeBmqN8ZawdkxK5kFGMwBXdudvWHYwtNgNhvLu', + // TRX_VOTE_BEST: 'TV9QitxEJ3pdiAUAfJ2QuPxLKp9qTTR3og', + // TRX_SEND_LINK: 'http://trx.trusteeglobal.com:8090', // http://trx.trusteeglobal.com:8090/wallet + // TRX_SOLIDITY_NODE: 'http://trx.trusteeglobal.com:8091', // http://trx.trusteeglobal.com:8091/walletsolidity + // TRX_USE_TRONSCAN: 0, + // SOL_VOTE_BEST: 'CertusDeBmqN8ZawdkxK5kFGMwBXdudvWHYwtNgNhvLu', ETH_LONG_QUERY: 1, ETH_SMALL_FEE_FORCE_QUIT: 1, ETH_BLOCKED_BALANCE_FORCE_QUIT: 1, ETH_LONG_QUERY_FORCE_QUIT: 1, ETH_GAS_LIMIT: 120000, ETH_GAS_LIMIT_FORCE_QUIT: 0, - BCH: { 2: 2, 6: 1, 12: 1 }, - BSV: { 2: 2, 6: 1, 12: 1 }, - BTG: { 2: 10, 6: 5, 12: 2 }, - DOGE: { 2: 800000, 6: 600000, 12: 500000 }, - DOGE_STATIC: { useStatic: true, speed_blocks_2: 1, feeForAllInputs: 3 }, - BTC_TEST: { 2: 8, 6: 6, 12: 5 }, - LTC: { 2: 8, 6: 5, 12: 2 }, - XVG: { 2: 700, 6: 600, 12: 300 }, - XVG_SEND_LINK: - 'https://api.vergecurrency.network/node/api/XVG/mainnet/tx/send', - XRP_SERVER: 'wss://xrplcluster.com', - XRP_SCANNER_SERVER: 'https://xrplcluster.com', // https://xrpl.org/public-servers.html - XRP_SCANNER_TYPE: 'xrpscan', // dataripple - XRP_MIN: 10, - XLM_SERVER: 'https://horizon.stellar.org', - XLM_SERVER_PRICE: 5500, - XLM_SERVER_PRICE_FORCE: 5500, - XLM_SEND_LINK: 'https://horizon.stellar.org/transactions', - BNB_SERVER: 'https://dex.binance.org', - BNB_SMART_SERVER: 'https://bsc-dataseed1.binance.org:443', - BNB_SMART_PRICE: 10000000000, - BNB_GAS_LIMIT: 620000, + // BCH: { 2: 2, 6: 1, 12: 1 }, + // BSV: { 2: 2, 6: 1, 12: 1 }, + // BTG: { 2: 10, 6: 5, 12: 2 }, + // DOGE: { 2: 800000, 6: 600000, 12: 500000 }, + // DOGE_STATIC: { useStatic: true, speed_blocks_2: 1, feeForAllInputs: 3 }, + // BTC_TEST: { 2: 8, 6: 6, 12: 5 }, + // LTC: { 2: 8, 6: 5, 12: 2 }, + // XVG: { 2: 700, 6: 600, 12: 300 }, + // XVG_SEND_LINK: + // 'https://api.vergecurrency.network/node/api/XVG/mainnet/tx/send', + // XRP_SERVER: 'wss://xrplcluster.com', + // XRP_SCANNER_SERVER: 'https://xrplcluster.com', // https://xrpl.org/public-servers.html + // XRP_SCANNER_TYPE: 'xrpscan', // dataripple + // XRP_MIN: 10, + // XLM_SERVER: 'https://horizon.stellar.org', + // XLM_SERVER_PRICE: 5500, + // XLM_SERVER_PRICE_FORCE: 5500, + // XLM_SEND_LINK: 'https://horizon.stellar.org/transactions', + // BNB_SERVER: 'https://dex.binance.org', + // BNB_SMART_SERVER: 'https://bsc-dataseed1.binance.org:443', + // BNB_SMART_PRICE: 10000000000, + // BNB_GAS_LIMIT: 620000, ETH_MIN_GAS_ERC20: 73000, ETH_MIN_GAS_LIMIT: 42000, ETH_TESTNET_PRICE: 6710000000, ETH_INFURA: '5e52e85aba6f483398c461c55b639a7b', ETH_INFURA_PROJECT_ID: 'c8b5c2ced3b041a8b55a1719b508ff08', ETH_TREZOR_SERVER: ['https://eth1.trezor.io', 'https://eth2.trezor.io'], - BTC_TREZOR_SERVER: [ - 'https://btc1.trezor.io', - 'https://btc2.trezor.io', - 'https://btc3.trezor.io', - 'https://btc4.trezor.io', - 'https://btc5.trezor.io' - ], - LTC_TREZOR_SERVER: [ - 'https://ltc1.trezor.io', - 'https://ltc2.trezor.io', - 'https://ltc3.trezor.io', - 'https://ltc4.trezor.io', - 'https://ltc5.trezor.io' - ], - BCH_TREZOR_SERVER: [ - 'https://bch1.trezor.io', - 'https://bch2.trezor.io', - 'https://bch3.trezor.io', - 'https://bch4.trezor.io', - 'https://bch5.trezor.io' - ], - DOGE_TREZOR_SERVER: [ - 'https://doge1.trezor.io', - 'https://doge2.trezor.io', - 'https://doge3.trezor.io', - 'https://doge4.trezor.io', - 'https://doge5.trezor.io' - ], - BTG_TREZOR_SERVER: [ - 'https://btg1.trezor.io', - 'https://btg2.trezor.io', - 'https://btg3.trezor.io', - 'https://btg4.trezor.io', - 'https://btg5.trezor.io' - ], - BSV_TREZOR_SERVER: ['https://bsv.trusteeglobal.com'], + // BTC_TREZOR_SERVER: [ + // 'https://btc1.trezor.io', + // 'https://btc2.trezor.io', + // 'https://btc3.trezor.io', + // 'https://btc4.trezor.io', + // 'https://btc5.trezor.io' + // ], + // LTC_TREZOR_SERVER: [ + // 'https://ltc1.trezor.io', + // 'https://ltc2.trezor.io', + // 'https://ltc3.trezor.io', + // 'https://ltc4.trezor.io', + // 'https://ltc5.trezor.io' + // ], + // BCH_TREZOR_SERVER: [ + // 'https://bch1.trezor.io', + // 'https://bch2.trezor.io', + // 'https://bch3.trezor.io', + // 'https://bch4.trezor.io', + // 'https://bch5.trezor.io' + // ], + // DOGE_TREZOR_SERVER: [ + // 'https://doge1.trezor.io', + // 'https://doge2.trezor.io', + // 'https://doge3.trezor.io', + // 'https://doge4.trezor.io', + // 'https://doge5.trezor.io' + // ], + // BTG_TREZOR_SERVER: [ + // 'https://btg1.trezor.io', + // 'https://btg2.trezor.io', + // 'https://btg3.trezor.io', + // 'https://btg4.trezor.io', + // 'https://btg5.trezor.io' + // ], + // BSV_TREZOR_SERVER: ['https://bsv.trusteeglobal.com'], ETH_ROPSTEN_TREZOR_SERVER: ['https://ac-dev0.net:29136'], ETC_TREZOR_SERVER: ['https://etcblockexplorer.com'], ETC_SERVER: 'https://www.ethercluster.com/etc', @@ -111,86 +110,86 @@ const CACHE: Cache = { AMB_TREZOR_SERVER: ['https://blockbook.ambrosus.io'], AMB_PRICE: 5000000000, AMB_GAS_LIMIT: 620000, - ONE_SERVER: 'https://api.harmony.one', - ONE_GAS_LIMIT: 920000, - ONE_PRICE: 30000000000, - VLX_SERVER: 'https://evmexplorer.velas.com/rpc', - VLX_GAS_LIMIT: 620000, - VLX_PRICE: 3000000000, - METIS_SERVER: 'https://andromeda.metis.io/?owner=1088', - METIS_GAS_LIMIT: 620000, - METIS_PRICE: 40000000000, - BTTC_SERVER: 'https://rpc.bt.io', - OPTIMISM_SERVER: 'https://mainnet.optimism.io', - OPTIMISM_PRICE: 15000000, - OPTIMISM_GAS_LIMIT: 2320100000, - OPTIMISM_MIN_GAS_LIMIT: 23201000, - MATIC_SERVER: 'https://polygon-rpc.com', - MATIC_PRICE: 1000000000, - MATIC_GAS_LIMIT: 620000, - FTM_SERVER: 'https://rpc.ftm.tools', - FTM_PRICE: 400000000000, - FTM_GAS_LIMIT: 620000, - RSK_SERVER: 'https://public-node.rsk.co', - RSK_PRICE: 5000000000, - RSK_GAS_LIMIT: 620000, - SOL_SERVER: 'https://api.mainnet-beta.solana.com', - SOL_SERVER_2: - 'https://solana-mainnet.phantom.app/YBPpkkN4g91xDiAnTE9r0RcMkjg0sKUIWvAfoFVJ', - SOL_PRICE: 5000, - SOL_PRICE_NEW_SPL: 2044280, - SOL_TOKENS_LIST: - 'https://raw.githubusercontent.com/solana-labs/token-list/main/src/tokens/solana.tokenlist.json', - SOL_VALIDATORS_LIST: - 'https://raw.githubusercontent.com/trustee-wallet/trusteeWalletAssets/main/blockchains/sol/validators.json', - WAVES_SERVER: 'https://nodes.wavesnodes.com', - ASH_SERVER: 'http://51.158.70.89:6555', - FIO_BASE_URL: 'https://fio.eosphere.io/v1/', - FIO_HISTORY_URL: 'https://fio.eosphere.io/v1/history/', - FIO_REGISTRATION_URL: 'https://reg.fioprotocol.io/ref/trustee?publicKey=', + // ONE_SERVER: 'https://api.harmony.one', + // ONE_GAS_LIMIT: 920000, + // ONE_PRICE: 30000000000, + // VLX_SERVER: 'https://evmexplorer.velas.com/rpc', + // VLX_GAS_LIMIT: 620000, + // VLX_PRICE: 3000000000, + // METIS_SERVER: 'https://andromeda.metis.io/?owner=1088', + // METIS_GAS_LIMIT: 620000, + // METIS_PRICE: 40000000000, + // BTTC_SERVER: 'https://rpc.bt.io', + // OPTIMISM_SERVER: 'https://mainnet.optimism.io', + // OPTIMISM_PRICE: 15000000, + // OPTIMISM_GAS_LIMIT: 2320100000, + // OPTIMISM_MIN_GAS_LIMIT: 23201000, + // MATIC_SERVER: 'https://polygon-rpc.com', + // MATIC_PRICE: 1000000000, + // MATIC_GAS_LIMIT: 620000, + // FTM_SERVER: 'https://rpc.ftm.tools', + // FTM_PRICE: 400000000000, + // FTM_GAS_LIMIT: 620000, + // RSK_SERVER: 'https://public-node.rsk.co', + // RSK_PRICE: 5000000000, + // RSK_GAS_LIMIT: 620000, + // SOL_SERVER: 'https://api.mainnet-beta.solana.com', + // SOL_SERVER_2: + // 'https://solana-mainnet.phantom.app/YBPpkkN4g91xDiAnTE9r0RcMkjg0sKUIWvAfoFVJ', + // SOL_PRICE: 5000, + // SOL_PRICE_NEW_SPL: 2044280, + // SOL_TOKENS_LIST: + // 'https://raw.githubusercontent.com/solana-labs/token-list/main/src/tokens/solana.tokenlist.json', + // SOL_VALIDATORS_LIST: + // 'https://raw.githubusercontent.com/trustee-wallet/trusteeWalletAssets/main/blockchains/sol/validators.json', + // WAVES_SERVER: 'https://nodes.wavesnodes.com', + // ASH_SERVER: 'http://51.158.70.89:6555', + // FIO_BASE_URL: 'https://fio.eosphere.io/v1/', + // FIO_HISTORY_URL: 'https://fio.eosphere.io/v1/history/', + // FIO_REGISTRATION_URL: 'https://reg.fioprotocol.io/ref/trustee?publicKey=', minCryptoErrorsVersion: 491, minAppErrorsVersion: 491, - SUPPORT_BOT: 'https://t.me/trustee_support_bot?start=app', - SUPPORT_BOT_NAME: '@trustee_support_bot', - SUPPORT_EMAIL: 'contact@trustee.deals', - navigationViewV3: 1, - SOCIAL_LINK_SITE: 'trusteeglobal.com', - SOCIAL_LINK_TELEGRAM: 'https://t.me/trustee_deals', - SOCIAL_LINK_TWITTER: 'https://twitter.com/Trustee_Wallet', - SOCIAL_LINK_FACEBOOK: 'https://facebook.com/Trustee.Wallet/', - SOCIAL_LINK_INSTAGRAM: 'https://instagram.com/trustee_wallet/', - SOCIAL_LINK_VK: 'https://vk.com/trustee_wallet', - SOCIAL_LINK_GITHUB: 'https://github.com/trustee-wallet/trusteeWallet', - SOCIAL_LINK_FAQ: 'https://trusteeglobal.com/faq/', - SOCIAL_LINK_YOUTUBE: 'https://www.youtube.com/TrusteeWallet', - PRIVACY_POLICY: - 'https://trusteeglobal.com/privacy-policy/?header_footer=none', - TERMS: 'https://trusteeglobal.com/terms-of-use/?header_footer=none', - SEND_CHECK_ALMOST_ALL_PERCENT: 0.95, - SEND_AMOUNT_CHECK: 1, - TRADE_SEND_AMOUNT_CHECK_FORCE_QUIT: 1, - ROCKET_CHAT_USE: 0, - HOW_WORK_CASHBACK_LINK: 'https://trusteeglobal.com/programma-loyalnosti/', - HOW_WORK_CPA_LINK: 'https://trusteeglobal.com/cpa/', - TRX_STAKING_LINK: - 'https://blog.trusteeglobal.com/stejking-trona-i-kak-zarabotat/', - TRX_SPAM_LIMIT: 40000, - TRX_BASIC_PRICE_WHEN_NO_BAND: 100000, - TRX_TRC20_BAND_PER_TX: 350, - TRX_TRC20_PRICE_PER_BAND: 140, - TRX_TRC20_ENERGY_PER_TX: 29650, // 14650, - TRX_TRC20_PRICE_PER_ENERGY: 420, - TRX_TRC20_MAX_LIMIT: 100000000, - INVOICE_URL: 'https://trusteeglobal.com/', + // SUPPORT_BOT: 'https://t.me/trustee_support_bot?start=app', + // SUPPORT_BOT_NAME: '@trustee_support_bot', + // SUPPORT_EMAIL: 'contact@trustee.deals', + // navigationViewV3: 1, + // SOCIAL_LINK_SITE: 'trusteeglobal.com', + // SOCIAL_LINK_TELEGRAM: 'https://t.me/trustee_deals', + // SOCIAL_LINK_TWITTER: 'https://twitter.com/Trustee_Wallet', + // SOCIAL_LINK_FACEBOOK: 'https://facebook.com/Trustee.Wallet/', + // SOCIAL_LINK_INSTAGRAM: 'https://instagram.com/trustee_wallet/', + // SOCIAL_LINK_VK: 'https://vk.com/trustee_wallet', + // SOCIAL_LINK_GITHUB: 'https://github.com/trustee-wallet/trusteeWallet', + // SOCIAL_LINK_FAQ: 'https://trusteeglobal.com/faq/', + // SOCIAL_LINK_YOUTUBE: 'https://www.youtube.com/TrusteeWallet', + // PRIVACY_POLICY: + // 'https://trusteeglobal.com/privacy-policy/?header_footer=none', + // TERMS: 'https://trusteeglobal.com/terms-of-use/?header_footer=none', + // SEND_CHECK_ALMOST_ALL_PERCENT: 0.95, + // SEND_AMOUNT_CHECK: 1, + // TRADE_SEND_AMOUNT_CHECK_FORCE_QUIT: 1, + // ROCKET_CHAT_USE: 0, + // HOW_WORK_CASHBACK_LINK: 'https://trusteeglobal.com/programma-loyalnosti/', + // HOW_WORK_CPA_LINK: 'https://trusteeglobal.com/cpa/', + // TRX_STAKING_LINK: + // 'https://blog.trusteeglobal.com/stejking-trona-i-kak-zarabotat/', + // TRX_SPAM_LIMIT: 40000, + // TRX_BASIC_PRICE_WHEN_NO_BAND: 100000, + // TRX_TRC20_BAND_PER_TX: 350, + // TRX_TRC20_PRICE_PER_BAND: 140, + // TRX_TRC20_ENERGY_PER_TX: 29650, // 14650, + // TRX_TRC20_PRICE_PER_ENERGY: 420, + // TRX_TRC20_MAX_LIMIT: 100000000, + // INVOICE_URL: 'https://trusteeglobal.com/', STAKING_COINS_PERCENT: { - TRX: 5.06, - SOL: 7.02, - VET: 1.63, - ETH: 5.1, - ETH_MATIC: 6.3 - }, - DAPPS_IMAGE_LINK: - 'https://raw.githubusercontent.com/trustee-wallet/trusteeWalletAssets/main/dapps/' + // TRX: 5.06, + // SOL: 7.02, + // VET: 1.63, + ETH: 5.1 + // ETH_MATIC: 6.3 + } + // DAPPS_IMAGE_LINK: + // 'https://raw.githubusercontent.com/trustee-wallet/trusteeWalletAssets/main/dapps/' }; class AirDAOExternalSettings { diff --git a/crypto/common/AirDAOPrettyNumbers.ts b/crypto/common/AirDAOPrettyNumbers.ts index e96baf7a6..1dd9d94bc 100644 --- a/crypto/common/AirDAOPrettyNumbers.ts +++ b/crypto/common/AirDAOPrettyNumbers.ts @@ -37,146 +37,146 @@ class AirDAOPrettyNumbers { * @return {string} */ // tslint:disable-next-line:variable-name - async makePretty(number: string | number): Promise { - if (this._processorCode === 'USDT') { - return number.toString(); - } - const str = number.toString(); - if (str.indexOf('.') !== -1 || str.indexOf(',') !== -1) { - number = str.split('.')[0].toString(); - } - if (this._processorCode === 'ETH') { - return AirDAOUtils.toEther(number); - } else if (this._processorCode === 'BTC') { - return AirDAOUtils.toBtc(number); - } else if ( - this._processorCode === 'ETH_ERC_20' || - this._processorCode === 'UNIFIED' - ) { - return AirDAOUtils.toUnified(number, this._decimals || 0); - } - throw new Error('undefined BlocksoftPrettyNumbers processor to makePretty'); - } - - makeCut( - tmp: string | number, - size = 5, - source = false, - useDict = true - ): { - cutted: string | number; - isSatoshi: boolean; - justCutted: string | number; - separated: string; - separatedForInput: string | false; - } { - if ( - this._decimals && - this._decimals <= 6 && - size === 5 && - this._decimals > 0 && - useDict - ) { - size = this._decimals; - } - let cutted: string | number = 0; - let isSatoshi = false; - if (typeof tmp === 'undefined' || !tmp) { - return { - cutted, - isSatoshi, - justCutted: cutted, - separated: '0', - separatedForInput: false - }; - } - const splitted = tmp.toString().split('.'); - const def = '0.' + '0'.repeat(size); - let firstPart: string | false = false; - let secondPart: string | false = false; - if (splitted[0] === '0') { - if (typeof splitted[1] !== 'undefined' && splitted[1]) { - cutted = splitted[0] + '.' + splitted[1].substr(0, size).toString(); // Convert to string - if (cutted === def) { - cutted = - splitted[0] + '.' + splitted[1].substr(0, size * 2).toString(); // Convert to string - const def2 = '0.' + '0'.repeat(size * 2); - if (cutted !== def2) { - secondPart = splitted[1].substr(0, size * 2); - } - isSatoshi = true; - } - } else { - cutted = '0'; - } - } else if (typeof splitted[1] !== 'undefined' && splitted[1]) { - const second = splitted[1].substr(0, size).toString(); // Convert to string - if (second !== '0'.repeat(size)) { - cutted = splitted[0] + '.' + second; - secondPart = second; - } else { - cutted = splitted[0]; - } - firstPart = splitted[0] + ''; - } else { - cutted = splitted[0]; - firstPart = splitted[0] + ''; - } - let justCutted: string | number = isSatoshi ? '0' : cutted; - if (justCutted === def) { - justCutted = '0'; - } - - if (secondPart) { - for (let i = secondPart.length; i--; i >= 0) { - if (typeof secondPart[i] === 'undefined' || secondPart[i] === '0') { - secondPart = secondPart.substr(0, i); - } else { - break; - } - } - } + // async makePretty(number: string | number): Promise { + // if (this._processorCode === 'USDT') { + // return number.toString(); + // } + // const str = number.toString(); + // if (str.indexOf('.') !== -1 || str.indexOf(',') !== -1) { + // number = str.split('.')[0].toString(); + // } + // if (this._processorCode === 'ETH') { + // return AirDAOUtils.toEther(number); + // } else if (this._processorCode === 'BTC') { + // return AirDAOUtils.toBtc(number); + // } else if ( + // this._processorCode === 'ETH_ERC_20' || + // this._processorCode === 'UNIFIED' + // ) { + // return AirDAOUtils.toUnified(number, this._decimals || 0); + // } + // throw new Error('undefined BlocksoftPrettyNumbers processor to makePretty'); + // } - let separated = justCutted; - let separatedForInput: string | false = false; - if (firstPart) { - const len = firstPart.length; - if (len > 3) { - separated = ''; - let j = 0; - for (let i = len - 1; i >= 0; i--) { - if (j === 3) { - separated = ' ' + separated; - j = 0; - } - j++; - separated = firstPart[i] + separated; - } - } else { - separated = firstPart; - } - separatedForInput = separated.toString(); - if (secondPart) { - separated += '.' + secondPart; - } - } else if (secondPart) { - separated = '0.' + secondPart; - separatedForInput = '0'; - } - if (!separatedForInput) { - separatedForInput = justCutted; - } else if (typeof splitted[1] !== 'undefined') { - separatedForInput += '.' + splitted[1]; - } - - return { - cutted, - isSatoshi, - justCutted, - separated: separated.toString(), - separatedForInput - }; - } + // makeCut( + // tmp: string | number, + // size = 5, + // source = false, + // useDict = true + // ): { + // cutted: string | number; + // isSatoshi: boolean; + // justCutted: string | number; + // separated: string; + // separatedForInput: string | false; + // } { + // if ( + // this._decimals && + // this._decimals <= 6 && + // size === 5 && + // this._decimals > 0 && + // useDict + // ) { + // size = this._decimals; + // } + // let cutted: string | number = 0; + // let isSatoshi = false; + // if (typeof tmp === 'undefined' || !tmp) { + // return { + // cutted, + // isSatoshi, + // justCutted: cutted, + // separated: '0', + // separatedForInput: false + // }; + // } + // const splitted = tmp.toString().split('.'); + // const def = '0.' + '0'.repeat(size); + // let firstPart: string | false = false; + // let secondPart: string | false = false; + // if (splitted[0] === '0') { + // if (typeof splitted[1] !== 'undefined' && splitted[1]) { + // cutted = splitted[0] + '.' + splitted[1].substr(0, size).toString(); // Convert to string + // if (cutted === def) { + // cutted = + // splitted[0] + '.' + splitted[1].substr(0, size * 2).toString(); // Convert to string + // const def2 = '0.' + '0'.repeat(size * 2); + // if (cutted !== def2) { + // secondPart = splitted[1].substr(0, size * 2); + // } + // isSatoshi = true; + // } + // } else { + // cutted = '0'; + // } + // } else if (typeof splitted[1] !== 'undefined' && splitted[1]) { + // const second = splitted[1].substr(0, size).toString(); // Convert to string + // if (second !== '0'.repeat(size)) { + // cutted = splitted[0] + '.' + second; + // secondPart = second; + // } else { + // cutted = splitted[0]; + // } + // firstPart = splitted[0] + ''; + // } else { + // cutted = splitted[0]; + // firstPart = splitted[0] + ''; + // } + // let justCutted: string | number = isSatoshi ? '0' : cutted; + // if (justCutted === def) { + // justCutted = '0'; + // } + // + // if (secondPart) { + // for (let i = secondPart.length; i--; i >= 0) { + // if (typeof secondPart[i] === 'undefined' || secondPart[i] === '0') { + // secondPart = secondPart.substr(0, i); + // } else { + // break; + // } + // } + // } + // + // let separated = justCutted; + // let separatedForInput: string | false = false; + // if (firstPart) { + // const len = firstPart.length; + // if (len > 3) { + // separated = ''; + // let j = 0; + // for (let i = len - 1; i >= 0; i--) { + // if (j === 3) { + // separated = ' ' + separated; + // j = 0; + // } + // j++; + // separated = firstPart[i] + separated; + // } + // } else { + // separated = firstPart; + // } + // separatedForInput = separated.toString(); + // if (secondPart) { + // separated += '.' + secondPart; + // } + // } else if (secondPart) { + // separated = '0.' + secondPart; + // separatedForInput = '0'; + // } + // if (!separatedForInput) { + // separatedForInput = justCutted; + // } else if (typeof splitted[1] !== 'undefined') { + // separatedForInput += '.' + splitted[1]; + // } + // + // return { + // cutted, + // isSatoshi, + // justCutted, + // separated: separated.toString(), + // separatedForInput + // }; + // } /** * @param {string} value @@ -189,7 +189,7 @@ class AirDAOPrettyNumbers { if (this._processorCode === 'USDT') { return number.toString(); } else if (this._processorCode === 'ETH') { - return AirDAOUtils.toWei(number); + return AirDAOUtils.toWei(number) as unknown as string; } else if (this._processorCode === 'BTC') { return AirDAOUtils.toSatoshi(number); } else if ( diff --git a/crypto/common/AirDAOQrScanDict.ts b/crypto/common/AirDAOQrScanDict.ts deleted file mode 100644 index acbaa4a5b..000000000 --- a/crypto/common/AirDAOQrScanDict.ts +++ /dev/null @@ -1,101 +0,0 @@ -/** - * @version 0.77 - */ -const networkToCurrencyCode: Record = { - 'litecoin': 'LTC', - 'ltc': 'LTC', - 'bitcoingold': 'BTG', - 'btg': 'BTG', - 'bitcoinsv': 'BSV', - 'bsv': 'BSV', - 'tron': 'TRX', - 'trx': 'TRX', - 'ripple': 'XRP', - 'xrp': 'XRP', - 'stellar': 'XLM', - 'xlm': 'XLM', - 'doge': 'DOGE', - 'dogecoin': 'DOGE', - 'ethereum': 'ETH', - 'eth': 'ETH', - 'verge': 'XVG', - 'xvg': 'XVG', - 'ethereumropsten': 'ETH_ROPSTEN', - 'ethereumrinkeby': 'ETH_RINKEBY', - 'monero': 'XMR', - 'matic': 'MATIC', - 'polygon': 'MATIC', - 'velas': 'VLX', - 'vlx': 'VLX', - 'binance': 'BNB', - 'bnb': 'BNB', - 'fio': 'FIO', - 'bnbsmartchain': 'BNB_SMART', - 'ethereumclassic': 'ETC', - 'vechainthor': 'VET', - 'vechainthortoken': 'VTHO', - 'metis': 'METIS', - 'ambrosus': 'AMB', - 'amb': 'AMB', - 'waves': 'WAVES', - 'aeneas': 'ASH', - 'ash': 'ASH', - 'bitcointestnet': 'BTC_TEST', - 'bitcoincash': 'BCH', - 'fantom': 'FTM', - 'optimism': 'OPTIMISM', - 'solana': 'SOL', - 'tether usd (usdttrx) address': 'TRX_USDT', - 'tron trc20 (usdt)': 'TRX_USDT', - 'trc20': 'TRX_USDT', - 'tether-erc-20': 'TRX_USDT', - 'tethererc20': 'ETH_USDT' -}; - -const currencyNameToNetwork: Record = { - 'optimisticethereum': 'optimism', - 'fantomnetwork': 'fantom', - 'aeneas': 'aeneas', - 'ethereumclassic': 'ethereumclassic', - 'vechainthor': 'vechainthor', - 'vechainthortoken': 'vechainthortoken', - 'binancecoin': 'bnb', - 'velas': 'velas', - 'metis': 'metis', - 'bnbsmartchain': 'bnbsmartchain', - 'polygon(matic)network': 'polygon', - 'token_of_ethereum': 'ethereum', - 'token_of_bnb': 'bnbsmartchain', - 'token_of_solana': 'solana', - 'token_of_matic': 'polygon', - 'token_of_ftm': 'fantom', - 'token_of_metis': 'metis', - 'token_of_tron': 'tron', - 'token_of_vlx': 'velas', - 'token_of_bitcoin': 'bitcoin' -}; - -function changeCurrencyNameToNetwork( - _currencyName: string, - _helperName: string -) { - const helperName = - typeof _helperName !== 'undefined' && _helperName - ? _helperName.toLowerCase().replace(/ /g, '') - : ''; - const currencyName = - typeof _currencyName !== 'undefined' && _currencyName - ? _currencyName.toLowerCase().replace(/ /g, '') - : ''; - const tmp = - currencyNameToNetwork[helperName] || - currencyNameToNetwork[currencyName] || - currencyName; - return tmp.toLowerCase().replace(/ /g, ''); -} - -function changeNetworkToCurrencyCode(network: string | number) { - return networkToCurrencyCode[network] || 'BTC'; -} - -export { changeNetworkToCurrencyCode, changeCurrencyNameToNetwork }; diff --git a/src/utils/blockchain.ts b/src/utils/blockchain.ts index caafb29eb..535d3b0e6 100644 --- a/src/utils/blockchain.ts +++ b/src/utils/blockchain.ts @@ -95,7 +95,8 @@ const CurrenciesForTests = { }; if (typeof RNFastCrypto === 'undefined') { - delete Currencies['XMR']; + // @ts-ignore + delete Currencies.XMR; } /** @@ -109,97 +110,101 @@ if (typeof RNFastCrypto === 'undefined') { * @param {string} currencyObject.currencySymbol 'OMG' * @param {string} currencyObject.currencyCode 'OMG' */ -function addAndUnifyCustomCurrency(currencyObject) { - const tmp = { - currencyName: currencyObject.currencyName, - currencyCode: 'CUSTOM_' + currencyObject.currencyCode, - currencySymbol: currencyObject.currencySymbol, - ratesCurrencyCode: currencyObject.currencyCode, - decimals: currencyObject.tokenDecimals - }; - tmp.currencyType = 'custom'; - if (currencyObject.tokenType === 'BNB_SMART_20') { - tmp.currencyCode = 'CUSTOM_BNB_SMART_20_' + currencyObject.currencyCode; - if (tmp.ratesCurrencyCode.substr(0, 1) === 'B') { - const subRate = tmp.ratesCurrencyCode.substr(1); - if (typeof Currencies[subRate] !== 'undefined') { - tmp.ratesCurrencyCode = subRate; - } - } - tmp.extendsProcessor = 'BNB_SMART_CAKE'; - tmp.addressUiChecker = 'ETH'; - tmp.tokenAddress = currencyObject.tokenAddress; - tmp.tokenBlockchain = 'BNB'; - tmp.currencyExplorerLink = - 'https://bscscan.com/token/' + currencyObject.tokenAddress + '?a='; - } else if (currencyObject.tokenType === 'MATIC_ERC_20') { - tmp.currencyCode = 'CUSTOM_MATIC_ERC_20_' + currencyObject.currencyCode; - tmp.extendsProcessor = 'MATIC_USDT'; - tmp.addressUiChecker = 'ETH'; - tmp.tokenAddress = currencyObject.tokenAddress; - tmp.tokenBlockchain = 'MATIC'; - tmp.currencyExplorerLink = - 'https://polygonscan.com/token/' + currencyObject.tokenAddress + '?a='; - } else if (currencyObject.tokenType === 'FTM_ERC_20') { - tmp.currencyCode = 'CUSTOM_FTM_ERC_20_' + currencyObject.currencyCode; - tmp.extendsProcessor = 'FTM_USDC'; - tmp.addressUiChecker = 'ETH'; - tmp.tokenAddress = currencyObject.tokenAddress; - tmp.tokenBlockchain = 'FTM'; - tmp.currencyExplorerLink = - 'https://ftmscan.com/token/' + currencyObject.tokenAddress + '?a='; - } else if (currencyObject.tokenType === 'VLX_ERC_20') { - tmp.currencyCode = 'CUSTOM_VLX_ERC_20_' + currencyObject.currencyCode; - tmp.extendsProcessor = 'VLX_USDT'; - tmp.addressUiChecker = 'ETH'; - tmp.tokenAddress = currencyObject.tokenAddress; - tmp.tokenBlockchain = 'VLX'; - tmp.currencyExplorerLink = - 'https://evmexplorer.velas.com/tokens/' + currencyObject.tokenAddress; - } else if (currencyObject.tokenType === 'ONE_ERC_20') { - tmp.currencyCode = 'CUSTOM_ONE_ERC_20_' + currencyObject.currencyCode; - tmp.extendsProcessor = 'ONE_USDC'; - tmp.addressUiChecker = 'ETH'; - tmp.tokenAddress = currencyObject.tokenAddress; - tmp.tokenBlockchain = 'ONE'; - tmp.currencyExplorerLink = - 'https://explorer.harmony.one/address/' + currencyObject.tokenAddress; - } else if (currencyObject.tokenType === 'SOL') { - tmp.currencyCode = 'CUSTOM_SOL_' + currencyObject.currencyCode; - tmp.extendsProcessor = 'SOL_RAY'; - tmp.addressUiChecker = 'SOL'; - tmp.tokenAddress = currencyObject.tokenAddress; - tmp.tokenBlockchain = 'SOLANA'; - } else if (currencyObject.tokenType === 'ETH_ERC_20') { - tmp.extendsProcessor = 'ETH_TRUE_USD'; - tmp.addressUiChecker = 'ETH'; - tmp.tokenAddress = currencyObject.tokenAddress; - tmp.tokenBlockchain = 'ETHEREUM'; - tmp.currencyExplorerLink = - 'https://etherscan.io/token/' + currencyObject.tokenAddress + '?a='; - } else if (currencyObject.tokenType === 'TRX') { - tmp.currencyCode = 'CUSTOM_TRX_' + currencyObject.currencyCode; - tmp.extendsProcessor = 'TRX_USDT'; - tmp.addressUiChecker = 'TRX'; - tmp.currencyIcon = 'TRX'; - tmp.tokenName = currencyObject.tokenAddress; - tmp.tokenBlockchain = 'TRON'; - tmp.currencyExplorerLink = 'https://tronscan.org/#/address/'; - tmp.currencyExplorerTxLink = 'https://tronscan.org/#/transaction/'; - if (tmp.tokenName.substr(0, 1) !== 'T') { - this.skipParentBalanceCheck = true; - } - } else { - return false; - } - Currencies[tmp.currencyCode] = tmp; - return tmp; -} +// function addAndUnifyCustomCurrency(currencyObject) { +// const tmp = { +// currencyName: currencyObject.currencyName, +// currencyCode: 'CUSTOM_' + currencyObject.currencyCode, +// currencySymbol: currencyObject.currencySymbol, +// ratesCurrencyCode: currencyObject.currencyCode, +// decimals: currencyObject.tokenDecimals +// }; +// tmp.currencyType = 'custom'; +// if (currencyObject.tokenType === 'BNB_SMART_20') { +// tmp.currencyCode = 'CUSTOM_BNB_SMART_20_' + currencyObject.currencyCode; +// if (tmp.ratesCurrencyCode.substr(0, 1) === 'B') { +// const subRate = tmp.ratesCurrencyCode.substr(1); +// if (typeof Currencies[subRate] !== 'undefined') { +// tmp.ratesCurrencyCode = subRate; +// } +// } +// tmp.extendsProcessor = 'BNB_SMART_CAKE'; +// tmp.addressUiChecker = 'ETH'; +// tmp.tokenAddress = currencyObject.tokenAddress; +// tmp.tokenBlockchain = 'BNB'; +// tmp.currencyExplorerLink = +// 'https://bscscan.com/token/' + currencyObject.tokenAddress + '?a='; +// } else if (currencyObject.tokenType === 'MATIC_ERC_20') { +// tmp.currencyCode = 'CUSTOM_MATIC_ERC_20_' + currencyObject.currencyCode; +// tmp.extendsProcessor = 'MATIC_USDT'; +// tmp.addressUiChecker = 'ETH'; +// tmp.tokenAddress = currencyObject.tokenAddress; +// tmp.tokenBlockchain = 'MATIC'; +// tmp.currencyExplorerLink = +// 'https://polygonscan.com/token/' + currencyObject.tokenAddress + '?a='; +// } else if (currencyObject.tokenType === 'FTM_ERC_20') { +// tmp.currencyCode = 'CUSTOM_FTM_ERC_20_' + currencyObject.currencyCode; +// tmp.extendsProcessor = 'FTM_USDC'; +// tmp.addressUiChecker = 'ETH'; +// tmp.tokenAddress = currencyObject.tokenAddress; +// tmp.tokenBlockchain = 'FTM'; +// tmp.currencyExplorerLink = +// 'https://ftmscan.com/token/' + currencyObject.tokenAddress + '?a='; +// } else if (currencyObject.tokenType === 'VLX_ERC_20') { +// tmp.currencyCode = 'CUSTOM_VLX_ERC_20_' + currencyObject.currencyCode; +// tmp.extendsProcessor = 'VLX_USDT'; +// tmp.addressUiChecker = 'ETH'; +// tmp.tokenAddress = currencyObject.tokenAddress; +// tmp.tokenBlockchain = 'VLX'; +// tmp.currencyExplorerLink = +// 'https://evmexplorer.velas.com/tokens/' + currencyObject.tokenAddress; +// } else if (currencyObject.tokenType === 'ONE_ERC_20') { +// tmp.currencyCode = 'CUSTOM_ONE_ERC_20_' + currencyObject.currencyCode; +// tmp.extendsProcessor = 'ONE_USDC'; +// tmp.addressUiChecker = 'ETH'; +// tmp.tokenAddress = currencyObject.tokenAddress; +// tmp.tokenBlockchain = 'ONE'; +// tmp.currencyExplorerLink = +// 'https://explorer.harmony.one/address/' + currencyObject.tokenAddress; +// } else if (currencyObject.tokenType === 'SOL') { +// tmp.currencyCode = 'CUSTOM_SOL_' + currencyObject.currencyCode; +// tmp.extendsProcessor = 'SOL_RAY'; +// tmp.addressUiChecker = 'SOL'; +// tmp.tokenAddress = currencyObject.tokenAddress; +// tmp.tokenBlockchain = 'SOLANA'; +// } else if (currencyObject.tokenType === 'ETH_ERC_20') { +// tmp.extendsProcessor = 'ETH_TRUE_USD'; +// tmp.addressUiChecker = 'ETH'; +// tmp.tokenAddress = currencyObject.tokenAddress; +// tmp.tokenBlockchain = 'ETHEREUM'; +// tmp.currencyExplorerLink = +// 'https://etherscan.io/token/' + currencyObject.tokenAddress + '?a='; +// } else if (currencyObject.tokenType === 'TRX') { +// tmp.currencyCode = 'CUSTOM_TRX_' + currencyObject.currencyCode; +// tmp.extendsProcessor = 'TRX_USDT'; +// tmp.addressUiChecker = 'TRX'; +// tmp.currencyIcon = 'TRX'; +// tmp.tokenName = currencyObject.tokenAddress; +// tmp.tokenBlockchain = 'TRON'; +// tmp.currencyExplorerLink = 'https://tronscan.org/#/address/'; +// tmp.currencyExplorerTxLink = 'https://tronscan.org/#/transaction/'; +// if (tmp.tokenName.substr(0, 1) !== 'T') { +// this.skipParentBalanceCheck = true; +// } +// } else { +// return false; +// } +// +// Currencies[tmp.currencyCode] = tmp; +// return tmp; +// } const ALL_SETTINGS = {}; -function getCurrencyAllSettings(currencyCodeOrObject, source = '') { +function getCurrencyAllSettings( + currencyCodeOrObject: { currencyCode: any }, + source = '' +) { let currencyCode = currencyCodeOrObject; if (typeof currencyCode === 'undefined' || !currencyCode) { return false; @@ -259,6 +264,6 @@ export const BlockchainUtils = { Codes, Currencies, CurrenciesForTests, - getCurrencyAllSettings, - addAndUnifyCustomCurrency + getCurrencyAllSettings + // addAndUnifyCustomCurrency }; From 09726dafeefe36825c9d43a40a3dffb109b33ca5 Mon Sep 17 00:00:00 2001 From: illiaa Date: Wed, 23 Aug 2023 22:59:42 +0300 Subject: [PATCH 071/509] removed and refactored files --- crypto/blockchains/eth/EthScannerProcessor.ts | 230 ++++---- .../blockchains/eth/EthTokenProcessorNft.ts | 62 -- crypto/blockchains/eth/apis/EthNftMatic.ts | 115 ---- crypto/blockchains/eth/apis/EthNftOpensea.ts | 173 ------ crypto/blockchains/eth/ext/EthEstimateGas.ts | 15 +- crypto/blockchains/eth/stores/EthRawDS.ts | 550 +++++++++--------- 6 files changed, 401 insertions(+), 744 deletions(-) delete mode 100644 crypto/blockchains/eth/EthTokenProcessorNft.ts delete mode 100644 crypto/blockchains/eth/apis/EthNftMatic.ts delete mode 100644 crypto/blockchains/eth/apis/EthNftOpensea.ts diff --git a/crypto/blockchains/eth/EthScannerProcessor.ts b/crypto/blockchains/eth/EthScannerProcessor.ts index b04bcb602..4938ac784 100644 --- a/crypto/blockchains/eth/EthScannerProcessor.ts +++ b/crypto/blockchains/eth/EthScannerProcessor.ts @@ -37,19 +37,19 @@ import BlocksoftExternalSettings from '../../common/AirDAOExternalSettings'; // VLX: {} // }; -const CACHE_VALID_TIME = 30000; // 30 seconds -const CACHE = { - ETH: {}, - BNB: {}, - ETC: {}, - AMB: {}, - MATIC: {}, - FTM: {}, - RSK: {}, - OPTIMISM: {}, - METIS: {}, - VLX: {} -}; +// const CACHE_VALID_TIME = 30000; // 30 seconds +// const CACHE = { +// ETH: {}, +// BNB: {}, +// ETC: {}, +// AMB: {}, +// MATIC: {}, +// FTM: {}, +// RSK: {}, +// OPTIMISM: {}, +// METIS: {}, +// VLX: {} +// }; export default class EthScannerProcessor extends EthBasic { /** @@ -70,108 +70,108 @@ export default class EthScannerProcessor extends EthBasic { * @private * @param _address */ - async _get(_address: string) { - const address = _address.toLowerCase(); - - try { - this._trezorServer = await BlocksoftExternalSettings.getTrezorServer( - this._trezorServerCode, - 'ETH.Scanner._get' - ); - } catch (e: any) { - throw new Error( - e.message + ' while getTrezorServer ' + this._trezorServerCode - ); - } - - if (typeof this._trezorServer === 'undefined') { - AirDAOCryptoLog.err( - this._settings.currencyCode + - ' EthScannerProcessor._get empty trezorServer' - ); - throw new Error( - this._settings.currencyCode + - ' EthScannerProcessor._get empty trezorServer' - ); - } - - if (!this._trezorServer) { - return false; - } - - const now = new Date().getTime(); - if ( - typeof CACHE[this._mainCurrencyCode] !== 'undefined' && - typeof CACHE[this._mainCurrencyCode][address] !== 'undefined' && - now - CACHE[this._mainCurrencyCode][address].time < CACHE_VALID_TIME - ) { - CACHE[this._mainCurrencyCode][address].provider = 'trezor-cache'; - return CACHE[this._mainCurrencyCode][address]; - } - - let link = - this._trezorServer + '/api/v2/address/' + address + '?details=txs'; - let res = await AirDAOAxios.getWithoutBraking(link); - - if (!res || !res.data) { - BlocksoftExternalSettings.setTrezorServerInvalid( - this._trezorServerCode, - this._trezorServer - ); - this._trezorServer = await BlocksoftExternalSettings.getTrezorServer( - this._trezorServerCode, - this._settings.currencyCode + ' ETH.Scanner._get' - ); - if (typeof this._trezorServer === 'undefined') { - AirDAOCryptoLog.err( - this._settings.currencyCode + - ' EthScannerProcessor._get empty trezorServer2' - ); - throw new Error( - this._settings.currencyCode + - ' EthScannerProcessor._get empty trezorServer2' - ); - } - link = this._trezorServer + '/api/v2/address/' + address + '?details=txs'; - res = await AirDAOAxios.getWithoutBraking(link); - if (!res || !res.data) { - BlocksoftExternalSettings.setTrezorServerInvalid( - this._trezorServerCode, - this._trezorServer - ); - return false; - } - } - - if (typeof res.data.balance === 'undefined') { - throw new Error( - this._settings.currencyCode + - ' EthScannerProcessor._get nothing loaded for address ' + - link - ); - } - const data = res.data; - data.totalTokens = 0; - data.formattedTokens = {}; - // await AirDAOCryptoLog.log('EthScannerProcessor._get ERC20 tokens ' + JSON.stringify(data.tokens)) - if (typeof data.tokens !== 'undefined') { - let token; - for (token of data.tokens) { - data.formattedTokens[token.contract.toLowerCase()] = token; - } - } - if (typeof CACHE[this._mainCurrencyCode][address] !== 'undefined') { - if (CACHE[this._mainCurrencyCode][address].data.nonce > res.data.nonce) { - return false; - } - } - CACHE[this._mainCurrencyCode][address] = { - data, - provider: 'trezor', - time: now - }; - return CACHE[this._mainCurrencyCode][address]; - } + // async _get(_address: string) { + // const address = _address.toLowerCase(); + // + // try { + // this._trezorServer = await BlocksoftExternalSettings.getTrezorServer( + // this._trezorServerCode, + // 'ETH.Scanner._get' + // ); + // } catch (e: any) { + // throw new Error( + // e.message + ' while getTrezorServer ' + this._trezorServerCode + // ); + // } + // + // if (typeof this._trezorServer === 'undefined') { + // AirDAOCryptoLog.err( + // this._settings.currencyCode + + // ' EthScannerProcessor._get empty trezorServer' + // ); + // throw new Error( + // this._settings.currencyCode + + // ' EthScannerProcessor._get empty trezorServer' + // ); + // } + // + // if (!this._trezorServer) { + // return false; + // } + // + // const now = new Date().getTime(); + // if ( + // typeof CACHE[this._mainCurrencyCode] !== 'undefined' && + // typeof CACHE[this._mainCurrencyCode][address] !== 'undefined' && + // now - CACHE[this._mainCurrencyCode][address].time < CACHE_VALID_TIME + // ) { + // CACHE[this._mainCurrencyCode][address].provider = 'trezor-cache'; + // return CACHE[this._mainCurrencyCode][address]; + // } + // + // let link = + // this._trezorServer + '/api/v2/address/' + address + '?details=txs'; + // let res = await AirDAOAxios.getWithoutBraking(link); + // + // if (!res || !res.data) { + // BlocksoftExternalSettings.setTrezorServerInvalid( + // this._trezorServerCode, + // this._trezorServer + // ); + // this._trezorServer = await BlocksoftExternalSettings.getTrezorServer( + // this._trezorServerCode, + // this._settings.currencyCode + ' ETH.Scanner._get' + // ); + // if (typeof this._trezorServer === 'undefined') { + // AirDAOCryptoLog.err( + // this._settings.currencyCode + + // ' EthScannerProcessor._get empty trezorServer2' + // ); + // throw new Error( + // this._settings.currencyCode + + // ' EthScannerProcessor._get empty trezorServer2' + // ); + // } + // link = this._trezorServer + '/api/v2/address/' + address + '?details=txs'; + // res = await AirDAOAxios.getWithoutBraking(link); + // if (!res || !res.data) { + // BlocksoftExternalSettings.setTrezorServerInvalid( + // this._trezorServerCode, + // this._trezorServer + // ); + // return false; + // } + // } + // + // if (typeof res.data.balance === 'undefined') { + // throw new Error( + // this._settings.currencyCode + + // ' EthScannerProcessor._get nothing loaded for address ' + + // link + // ); + // } + // const data = res.data; + // data.totalTokens = 0; + // data.formattedTokens = {}; + // // await AirDAOCryptoLog.log('EthScannerProcessor._get ERC20 tokens ' + JSON.stringify(data.tokens)) + // if (typeof data.tokens !== 'undefined') { + // let token; + // for (token of data.tokens) { + // data.formattedTokens[token.contract.toLowerCase()] = token; + // } + // } + // if (typeof CACHE[this._mainCurrencyCode][address] !== 'undefined') { + // if (CACHE[this._mainCurrencyCode][address].data.nonce > res.data.nonce) { + // return false; + // } + // } + // CACHE[this._mainCurrencyCode][address] = { + // data, + // provider: 'trezor', + // time: now + // }; + // return CACHE[this._mainCurrencyCode][address]; + // } /** * @param {string} address diff --git a/crypto/blockchains/eth/EthTokenProcessorNft.ts b/crypto/blockchains/eth/EthTokenProcessorNft.ts deleted file mode 100644 index d826955aa..000000000 --- a/crypto/blockchains/eth/EthTokenProcessorNft.ts +++ /dev/null @@ -1,62 +0,0 @@ -/** - * @version 0.50 - */ -import EthBasic from './basic/EthBasic'; -// import EthNftOpensea from '@crypto/blockchains/eth/apis/EthNftOpensea'; -// import EthNftMatic from '@crypto/blockchains/eth/apis/EthNftMatic'; -// import abi from './ext/erc721.js'; -// import AirDAODictNfts from '@crypto/common/AirDAODictNfts'; - -export default class EthTokenProcessorNft extends EthBasic { - /** - * @param data.address - * @param data.tokenBlockchainCode - */ - // async getListBlockchain(data) { - // const settings = AirDAODictNfts.NftsIndexed[data.tokenBlockchainCode]; - // if ( - // typeof settings !== 'undefined' && - // typeof settings.apiType !== 'undefined' && - // settings.apiType === 'OPENSEA' - // ) { - // return EthNftOpensea(data); - // } else { - // return EthNftMatic(data); - // } - // } - // async getNftDetails(nftAddress: string, nftType: string) { - // this.checkWeb3CurrentServerUpdated(); - // - // let token, name, symbol; - // try { - // token = new this._web3.eth.Contract(abi.ERC721, nftAddress.toLowerCase()); - // } catch (err) { - // const e = err as unknown as any; - // e.message = 'erc721 init token ' + e.message; - // throw e; - // } - // - // // @todo more checks! - // try { - // name = await token.methods.name().call(); - // } catch (e) { - // name = nftAddress.substr(0, 32); - // } - // - // try { - // symbol = await token.methods.symbol().call(); - // } catch (e) { - // symbol = name.substr(0, 5); - // } - // - // const res = { - // nftSymbol: symbol, - // nftCode: symbol, - // nftName: name, - // nftType: nftType, - // nftAddress: nftAddress.toLowerCase(), - // provider: 'web3' - // }; - // return res; - // } -} diff --git a/crypto/blockchains/eth/apis/EthNftMatic.ts b/crypto/blockchains/eth/apis/EthNftMatic.ts deleted file mode 100644 index b0a283478..000000000 --- a/crypto/blockchains/eth/apis/EthNftMatic.ts +++ /dev/null @@ -1,115 +0,0 @@ -/** - * @version 0.50 - */ -import AirDAOAxios from '@crypto/common/AirDAOAxios'; -import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; - -const API_PATH = 'https://microscanners.trustee.deals/getAllNfts/'; - -/** - * https://microscanners.trustee.deals/getMaticNfts/0xf1Cff704c6E6ce459e3E1544a9533cCcBDAD7B99 - * https://microscanners.trustee.deals/getAllNfts/0xf1Cff704c6E6ce459e3E1544a9533cCcBDAD7B99? - * @param data.address - * @param data.tokenBlockchainCode - * @param data.customAssets - */ -export default async function (data) { - if (!data.address) return false; - - const link = - API_PATH + - data.address + - '?tokenBlockchainCode=' + - data.tokenBlockchainCode + - '&tokens=' + - data.customAssets.join(','); - const result = await AirDAOAxios.get(link); - - /** - * @var tmp.animation_url - * @var tmp.image - * @var tmp.name - * @var tmp.description - * @var tmp.token_index - * @var tmp.contract_address - */ - const formatted = []; - const collections = []; - let usdTotal = 0; - - if ( - result && - result.data && - typeof result.data !== 'undefined' && - result.data && - result.data.length - ) { - for (const tmp of result.data) { - const one = { - id: tmp.id, - tokenId: tmp.token_index, - contractAddress: tmp.contract_address, - contractSchema: tmp.contract_schema || 'ERC721', - tokenBlockchainCode: - tmp.token_blockchain_code || data.tokenBlockchainCode, - tokenBlockchain: tmp.token_blockchain || data.tokenBlockchain, - tokenQty: tmp.qty || 1, - img: tmp.image, - title: tmp.name || tmp.title, - subTitle: tmp.subtitle || '', - desc: tmp.description || '', - cryptoCurrencySymbol: tmp.crypto_currency_symbol || '', - cryptoValue: tmp.crypto_value || '', - usdValue: tmp.usd_value || '', - permalink: tmp.permalink || false - }; - try { - if (one.desc && !one.subTitle) { - one.subTitle = - one.desc.length > 20 ? one.desc.substring(0, 20) + '...' : one.desc; - } - } catch (e) { - AirDAOCryptoLog.log( - 'EthTokenProcessorNft EthNftMatic name error ' + e.message - ); - } - - if (one.usdValue && one.usdValue * 1 > 0) { - usdTotal = usdTotal + one.usdValue * 1; - } - - let collectionKey = ''; - try { - if (typeof tmp.collection !== 'undefined') { - collectionKey = tmp.collection.name + one.contractAddress; - if (typeof collections[collectionKey] === 'undefined') { - collections[collectionKey] = { - numberAssets: 1, - title: tmp.collection.name, - img: tmp.collection.image, - walletCurrency: one.tokenBlockchainCode, - assets: [one] - }; - } else { - collections[collectionKey].numberAssets++; - collections[collectionKey].assets.push(one); - } - } - } catch (e) { - console.log( - 'EthTokenProcessorNft EthNftMatic collection error ' + e.message - ); - } - - formatted.push(one); - } - } - - const formattedCollections = []; - if (collections) { - for (const key in collections) { - formattedCollections.push(collections[key]); - } - } - return { assets: formatted, collections: formattedCollections, usdTotal }; -} diff --git a/crypto/blockchains/eth/apis/EthNftOpensea.ts b/crypto/blockchains/eth/apis/EthNftOpensea.ts deleted file mode 100644 index 835e3aef9..000000000 --- a/crypto/blockchains/eth/apis/EthNftOpensea.ts +++ /dev/null @@ -1,173 +0,0 @@ -/** - * @version 0.50 - */ -import AirDAOAxios from '@crypto/common/AirDAOAxios'; -import BlocksoftUtils from '@crypto/common/AirDAOUtils'; -import AirDAOCryptoLog from '@crypto/common/AirDAOCryptoLog'; - -const API_PATH = 'https://api.opensea.io/api/v1/'; -const API_TEST_PATH = 'https://testnets-api.opensea.io/api/v1/'; -/** - * https://docs.opensea.io/reference/getting-assets - * curl --request GET --url https://api.opensea.io/api/v1/assets?order_direction=desc&offset=0&limit=20&owner=0x6cdb97bf46d77233cc943264633c2ed56bcf6f1f - * curl --request GET --url https://testnets-api.opensea.io/api/v1/assets?order_direction=desc&offset=0&limit=20&owner=0x6cdb97bf46d77233cc943264633c2ed56bcf6f1f - * @param data.address - * @param data.tokenBlockchainCode - */ -export default async function (data: { - tokenBlockchainCode: string; - address: string; - tokenBlockchain: any; -}) { - let link; - if (data.tokenBlockchainCode === 'ETH_RINKEBY') { - link = API_TEST_PATH; - } else { - link = API_PATH; - } - if (!data.address) return false; - link += 'assets?order_direction=desc&owner=' + data.address; - const result = await AirDAOAxios.getWithoutBraking(link); - - /** - * @var tmp.id - * @var tmp.token_id - * @var tmp.image_thumbnail_url - * @var tmp.name - * @var tmp.title - * @var tmp.last_sale - * @var tmp.last_sale.total_price - * @var tmp.last_sale.payment_token - * @var tmp.last_sale.payment_token.symbol - * @var tmp.last_sale.payment_token.name - * @var tmp.last_sale.payment_token.decimals - * @var tmp.last_sale.payment_token.usd_price - * @var tmp.asset_contract.address - * @var tmp.asset_contract.schema_name ERC721 - */ - const formatted = []; - const collections: never[] = []; - let usdTotal = 0; - - if ( - result && - result.data && - typeof result.data.assets !== 'undefined' && - result.data.assets && - result.data.assets.length - ) { - for (const tmp of result.data.assets) { - const one = { - id: tmp.id, - tokenId: tmp.token_id, - contractAddress: '', - contractSchema: 'ERC721', - tokenBlockchainCode: data.tokenBlockchainCode, - tokenBlockchain: data.tokenBlockchain, - tokenQty: 1, - img: tmp.image_preview_url, - title: tmp.name || tmp.title, - subTitle: '', - desc: '', - cryptoCurrencySymbol: '', - cryptoValue: '', - usdValue: '', - permalink: tmp.permalink || false - }; - try { - if (!one.title || typeof one.title === 'undefined') { - if (typeof tmp.asset_contract.name !== 'undefined') { - one.title = tmp.asset_contract.name; - } - } - if ( - typeof tmp.asset_contract.description !== 'undefined' && - tmp.asset_contract.description - ) { - one.desc = tmp.asset_contract.description; - } - if (one.title.indexOf(tmp.token_id) === -1) { - one.subTitle = '#' + tmp.token_id; - } else if (one.desc) { - one.subTitle = - one.desc.length > 20 ? one.desc.substring(0, 20) + '...' : one.desc; - } - } catch (e: any) { - AirDAOCryptoLog.log( - 'EthTokenProcessorNft EthNftOpensea name error ' + e.message - ); - } - - try { - if ( - typeof tmp.asset_contract.address !== 'undefined' && - tmp.asset_contract.address - ) { - one.contractAddress = tmp.asset_contract.address; - } - if ( - typeof tmp.asset_contract.schema_name !== 'undefined' && - tmp.asset_contract.schema_name - ) { - one.contractSchema = tmp.asset_contract.schema_name; - } - } catch (e: any) { - AirDAOCryptoLog.log( - 'EthTokenProcessorNft EthNftOpensea contract error ' + e.message - ); - } - - try { - if (typeof tmp.last_sale !== 'undefined' && tmp.last_sale) { - one.cryptoCurrencySymbol = tmp.last_sale.payment_token.symbol; - one.cryptoValue = BlocksoftUtils.toUnified( - tmp.last_sale.total_price, - tmp.last_sale.payment_token.decimals - ); - one.usdValue = tmp.last_sale.payment_token.usd_price; - usdTotal = usdTotal + tmp.last_sale.payment_token.usd_price * 1; - } - } catch (e: any) { - AirDAOCryptoLog.log( - 'EthTokenProcessorNft EthNftOpensealast_sale error ' + e.message, - // @ts-ignore - JSON.stringify(tmp) - ); - } - - let collectionKey = ''; - try { - if (typeof tmp.collection !== 'undefined') { - collectionKey = - tmp.collection.name + '_' + tmp.collection.payout_address; - if (typeof collections[collectionKey] === 'undefined') { - collections[collectionKey] = { - numberAssets: 1, - title: tmp.collection.name, - img: tmp.collection.banner_image_url || tmp.collection.image_url, - walletCurrency: data.tokenBlockchainCode, - assets: [one] - }; - } else { - collections[collectionKey].numberAssets++; - collections[collectionKey].assets.push(one); - } - } - } catch (e) { - AirDAOCryptoLog.log( - 'EthTokenProcessorNft EthNftOpensea collection error ' + e.message - ); - } - - formatted.push(one); - } - } - - const formattedCollections: never[] = []; - if (collections) { - for (const key in collections) { - formattedCollections.push(collections[key]); - } - } - return { assets: formatted, collections: formattedCollections, usdTotal }; -} diff --git a/crypto/blockchains/eth/ext/EthEstimateGas.ts b/crypto/blockchains/eth/ext/EthEstimateGas.ts index 9d97574b0..976d82310 100644 --- a/crypto/blockchains/eth/ext/EthEstimateGas.ts +++ b/crypto/blockchains/eth/ext/EthEstimateGas.ts @@ -1,24 +1,31 @@ import TransactionController from './estimateGas/index.js'; -export default async function (provider, gasPrice, from, to, value) { +export default async function ( + provider: any, + gasPrice: string | number, + from: any, + to: any, + value: string | number +) { if (from === to) return '21000'; const gasPriceHex = '0x' + (+gasPrice).toString(16); const valueHex = '0x' + (+value).toString(16); const txController = new TransactionController({ - provider: provider, + provider, getGasPrice: gasPriceHex }); const res = await txController.addTxGasDefaults({ txParams: { - from: from, - to: to, + from, + to, value: valueHex }, history: [{}] }); + // @ts-ignore return parseInt(res.txParams.gas, 16); } diff --git a/crypto/blockchains/eth/stores/EthRawDS.ts b/crypto/blockchains/eth/stores/EthRawDS.ts index c4924d90d..4b07e21f6 100644 --- a/crypto/blockchains/eth/stores/EthRawDS.ts +++ b/crypto/blockchains/eth/stores/EthRawDS.ts @@ -3,12 +3,12 @@ * @author Javid * @version 0.32 */ -import BlocksoftExternalSettings from '../../../common/AirDAOExternalSettings'; -import AirDAOAxios from '../../../common/AirDAOAxios'; +// import BlocksoftExternalSettings from '../../../common/AirDAOExternalSettings'; +// import AirDAOAxios from '../../../common/AirDAOAxios'; import AirDAOCryptoLog from '../../../common/AirDAOCryptoLog'; import { DatabaseTable } from '@appTypes'; import { Database } from '@database'; -import { TransactionRawDBModel } from '@database/models/transactions-raw'; +// import { TransactionRawDBModel } from '@database/models/transactions-raw'; const tableName = DatabaseTable.TransactionRaw; @@ -19,262 +19,262 @@ class EthRawDS { */ _currencyCode = 'ETH'; - _trezorServer = 'none'; - private _infuraProjectId: any; // TODO fix any + // _trezorServer = 'none'; + // private _infuraProjectId: any; // TODO fix any - async getForAddress(data: { address: any; currencyCode: any; }) { - try { - if (typeof data.currencyCode !== 'undefined') { - this._currencyCode = - data.currencyCode === 'ETH_ROPSTEN' ? 'ETH_ROPSTEN' : 'ETH'; - } - const sql = ` - SELECT id, - transaction_unique_key AS transactionUniqueKey, - transaction_hash AS transactionHash, - transaction_raw AS transactionRaw, - transaction_log AS transactionLog, - broadcast_log AS broadcastLog, - broadcast_updated AS broadcastUpdated, - created_at AS transactionCreated, - is_removed, removed_at - FROM transactions_raw - WHERE - (is_removed=0 OR is_removed IS NULL) - AND currency_code='${this._currencyCode}' - AND address='${data.address.toLowerCase()}'`; - const result = (await Database.unsafeRawQuery( - tableName, - sql - )) as TransactionRawDBModel[]; - if (!result || !result || result.length === 0) { - return {}; - } - - const ret: { [key: string]: unknown } = {}; // TODO fix unknown - - if (this._trezorServer === 'none') { - if (this._currencyCode === 'ETH') { - try { - this._trezorServer = - await BlocksoftExternalSettings.getTrezorServer( - 'ETH_TREZOR_SERVER', - 'ETH.Broadcast' - ); - this._infuraProjectId = BlocksoftExternalSettings.getStatic( - 'ETH_INFURA_PROJECT_ID', - 'ETH.Broadcast' - ); - } catch (e) { - throw new Error(e.message + ' inside trezorServer'); - } - } else { - this._trezorServer = await BlocksoftExternalSettings.getTrezorServer( - 'ETH_ROPSTEN_TREZOR_SERVER', - 'ETH.Broadcast' - ); - } - } - - const now = new Date().getTime(); - - for (const row of result) { - try { - ret[row.transactionUniqueKey] = row; - let transactionLog; - try { - transactionLog = row.transactionLog - ? JSON.parse(Database.unEscapeString(row.transactionLog)) - : row.transactionLog; - } catch (e) { - // do nothing - } - - if ( - transactionLog && - typeof transactionLog.currencyCode !== 'undefined' && - (typeof transactionLog.successResult === 'undefined' || - !transactionLog.successResult) - ) { - // const { apiEndpoints } = config.proxy; - // const baseURL = MarketingEvent.DATA.LOG_TESTER - // ? apiEndpoints.baseURLTest - // : apiEndpoints.baseURL; - // const successProxy = baseURL + '/send/sendtx'; - // let checkResult = false; - // try { - // transactionLog.selectedFee.isRebroadcast = true; - // checkResult = await AirDAOAxios.post(successProxy, { - // raw: row.transactionRaw, - // txRBF: - // typeof transactionLog.txRBF !== 'undefined' - // ? transactionLog.txRBF - // : false, - // logData: transactionLog, - // marketingData: MarketingEvent.DATA - // }); - // await AirDAOCryptoLog.log( - // this._currencyCode + ' EthRawDS.send proxy success result', - // JSON.parse(JSON.stringify(checkResult)) - // ); - // } catch (e3) { - // await AirDAOCryptoLog.log( - // this._currencyCode + - // ' EthRawDS.send proxy success error ' + - // e3.message - // ); - // } - // if (checkResult && typeof checkResult.data !== 'undefined') { - // transactionLog.successResult = checkResult.data; - // } - // await Database.updateModel(tableName, row.id, { - // transactionLog: Database.escapeString( - // JSON.stringify(transactionLog) - // ) - // }); - } - - let broadcastLog = ''; - let link = ''; - let broad; - const updateObj = { - broadcastUpdated: now, - is_removed: '0' - }; - if ( - this._currencyCode === 'ETH' || - this._currencyCode === 'ETH_ROPSTEN' - ) { - link = this._trezorServer + '/api/v2/sendtx/'; - try { - broad = await AirDAOAxios.post(link, row.transactionRaw); - broadcastLog = ' broadcasted ok ' + JSON.stringify(broad.data); - updateObj.is_removed = '1'; - updateObj.removed_at = now; - } catch (err) { - const e = err as unknown as any; - if ( - e.message.indexOf('transaction underpriced') !== -1 || - e.message.indexOf('already known') !== -1 - ) { - updateObj.is_removed = '1'; - broadcastLog += ' already known'; - } else { - updateObj.is_removed = '0'; - broadcastLog += e.message; - } - } - broadcastLog += ' ' + link + '; '; - } - - if (this._currencyCode === 'ETH') { - link = - 'https://api.etherscan.io/api?module=proxy&action=eth_sendRawTransaction&apikey=YourApiKeyToken&hex='; - let broadcastLog1 = ''; - try { - broad = await AirDAOAxios.get(link + row.transactionRaw); - if (typeof broad.data.error !== 'undefined') { - throw new Error(JSON.stringify(broad.data.error)); - } - broadcastLog1 = ' broadcasted ok ' + JSON.stringify(broad.data); - updateObj.is_removed += '1'; - updateObj.removed_at = now; - } catch (err) { - const e = err as unknown as any; - if ( - e.message.indexOf('transaction underpriced') !== -1 || - e.message.indexOf('already known') !== -1 - ) { - updateObj.is_removed += '1'; - broadcastLog1 += ' already known'; - } else { - updateObj.is_removed += '0'; - broadcastLog1 += e.message; - } - } - broadcastLog1 += ' ' + link + '; '; - link = 'https://mainnet.infura.io/v3/' + this._infuraProjectId; - let broadcastLog2 = ''; - try { - broad = await AirDAOAxios.post(link, { - jsonrpc: '2.0', - method: 'eth_sendRawTransaction', - params: [row.transactionRaw], - id: 1 - }); - if (typeof broad.data.error !== 'undefined') { - throw new Error(JSON.stringify(broad.data.error)); - } - broadcastLog2 = ' broadcasted ok ' + JSON.stringify(broad.data); - updateObj.is_removed += '1'; - updateObj.removed_at = now; - } catch (err) { - const e = err as unknown as any; - if ( - e.message.indexOf('transaction underpriced') !== -1 || - e.message.indexOf('already known') !== -1 - ) { - updateObj.is_removed += '1'; - broadcastLog2 += ' already known'; - } else { - updateObj.is_removed += '0'; - broadcastLog2 += e.message; - } - } - broadcastLog2 += ' ' + link + '; '; - if (updateObj.is_removed === '111') { - // do ALL! - updateObj.is_removed = 1; - } else { - updateObj.is_removed = 0; - } - - broadcastLog = - new Date().toISOString() + - ' ' + - broadcastLog + - ' ' + - broadcastLog1 + - ' ' + - broadcastLog2 + - ' ' + - (row.broadcastLog ? row.broadcastLog.substr(0, 1000) : ''); - } else if (this._currencyCode === 'ETH_ROPSTEN') { - if (updateObj.is_removed === '1') { - // do ALL! - updateObj.is_removed = 1; - } else { - updateObj.is_removed = 0; - } - - broadcastLog = - new Date().toISOString() + - ' ' + - broadcastLog + - ' ' + - (row.broadcastLog ? row.broadcastLog.substr(0, 1000) : ''); - } - - if ( - this._currencyCode === 'ETH' || - this._currencyCode === 'ETH_ROPSTEN' - ) { - updateObj.broadcastLog = broadcastLog; - await Database.updateModel(tableName, row.id, updateObj); - } - } catch (err) { - const e = err as unknown as any; - throw new Error(e.message + ' inside row ' + row.transactionHash); - } - } - return ret; - } catch (err) { - const e = err as unknown as any; - throw new Error(e.message + ' on EthRawDS.getAddress'); - } - } + // async getForAddress(data: { address: any; currencyCode: any; }) { + // try { + // if (typeof data.currencyCode !== 'undefined') { + // this._currencyCode = + // data.currencyCode === 'ETH_ROPSTEN' ? 'ETH_ROPSTEN' : 'ETH'; + // } + // const sql = ` + // SELECT id, + // transaction_unique_key AS transactionUniqueKey, + // transaction_hash AS transactionHash, + // transaction_raw AS transactionRaw, + // transaction_log AS transactionLog, + // broadcast_log AS broadcastLog, + // broadcast_updated AS broadcastUpdated, + // created_at AS transactionCreated, + // is_removed, removed_at + // FROM transactions_raw + // WHERE + // (is_removed=0 OR is_removed IS NULL) + // AND currency_code='${this._currencyCode}' + // AND address='${data.address.toLowerCase()}'`; + // const result = (await Database.unsafeRawQuery( + // tableName, + // sql + // )) as TransactionRawDBModel[]; + // if (!result || !result || result.length === 0) { + // return {}; + // } + // + // const ret: { [key: string]: unknown } = {}; // TODO fix unknown + // + // if (this._trezorServer === 'none') { + // if (this._currencyCode === 'ETH') { + // try { + // this._trezorServer = + // await BlocksoftExternalSettings.getTrezorServer( + // 'ETH_TREZOR_SERVER', + // 'ETH.Broadcast' + // ); + // this._infuraProjectId = BlocksoftExternalSettings.getStatic( + // 'ETH_INFURA_PROJECT_ID', + // 'ETH.Broadcast' + // ); + // } catch (e) { + // throw new Error(e.message + ' inside trezorServer'); + // } + // } else { + // this._trezorServer = await BlocksoftExternalSettings.getTrezorServer( + // 'ETH_ROPSTEN_TREZOR_SERVER', + // 'ETH.Broadcast' + // ); + // } + // } + // + // const now = new Date().getTime(); + // + // for (const row of result) { + // try { + // ret[row.transactionUniqueKey] = row; + // let transactionLog; + // try { + // transactionLog = row.transactionLog + // ? JSON.parse(Database.unEscapeString(row.transactionLog)) + // : row.transactionLog; + // } catch (e) { + // // do nothing + // } + // + // if ( + // transactionLog && + // typeof transactionLog.currencyCode !== 'undefined' && + // (typeof transactionLog.successResult === 'undefined' || + // !transactionLog.successResult) + // ) { + // // const { apiEndpoints } = config.proxy; + // // const baseURL = MarketingEvent.DATA.LOG_TESTER + // // ? apiEndpoints.baseURLTest + // // : apiEndpoints.baseURL; + // // const successProxy = baseURL + '/send/sendtx'; + // // let checkResult = false; + // // try { + // // transactionLog.selectedFee.isRebroadcast = true; + // // checkResult = await AirDAOAxios.post(successProxy, { + // // raw: row.transactionRaw, + // // txRBF: + // // typeof transactionLog.txRBF !== 'undefined' + // // ? transactionLog.txRBF + // // : false, + // // logData: transactionLog, + // // marketingData: MarketingEvent.DATA + // // }); + // // await AirDAOCryptoLog.log( + // // this._currencyCode + ' EthRawDS.send proxy success result', + // // JSON.parse(JSON.stringify(checkResult)) + // // ); + // // } catch (e3) { + // // await AirDAOCryptoLog.log( + // // this._currencyCode + + // // ' EthRawDS.send proxy success error ' + + // // e3.message + // // ); + // // } + // // if (checkResult && typeof checkResult.data !== 'undefined') { + // // transactionLog.successResult = checkResult.data; + // // } + // // await Database.updateModel(tableName, row.id, { + // // transactionLog: Database.escapeString( + // // JSON.stringify(transactionLog) + // // ) + // // }); + // } + // + // let broadcastLog = ''; + // let link = ''; + // let broad; + // const updateObj = { + // broadcastUpdated: now, + // is_removed: '0' + // }; + // if ( + // this._currencyCode === 'ETH' || + // this._currencyCode === 'ETH_ROPSTEN' + // ) { + // link = this._trezorServer + '/api/v2/sendtx/'; + // try { + // broad = await AirDAOAxios.post(link, row.transactionRaw); + // broadcastLog = ' broadcasted ok ' + JSON.stringify(broad.data); + // updateObj.is_removed = '1'; + // updateObj.removed_at = now; + // } catch (err) { + // const e = err as unknown as any; + // if ( + // e.message.indexOf('transaction underpriced') !== -1 || + // e.message.indexOf('already known') !== -1 + // ) { + // updateObj.is_removed = '1'; + // broadcastLog += ' already known'; + // } else { + // updateObj.is_removed = '0'; + // broadcastLog += e.message; + // } + // } + // broadcastLog += ' ' + link + '; '; + // } + // + // if (this._currencyCode === 'ETH') { + // link = + // 'https://api.etherscan.io/api?module=proxy&action=eth_sendRawTransaction&apikey=YourApiKeyToken&hex='; + // let broadcastLog1 = ''; + // try { + // broad = await AirDAOAxios.get(link + row.transactionRaw); + // if (typeof broad.data.error !== 'undefined') { + // throw new Error(JSON.stringify(broad.data.error)); + // } + // broadcastLog1 = ' broadcasted ok ' + JSON.stringify(broad.data); + // updateObj.is_removed += '1'; + // updateObj.removed_at = now; + // } catch (err) { + // const e = err as unknown as any; + // if ( + // e.message.indexOf('transaction underpriced') !== -1 || + // e.message.indexOf('already known') !== -1 + // ) { + // updateObj.is_removed += '1'; + // broadcastLog1 += ' already known'; + // } else { + // updateObj.is_removed += '0'; + // broadcastLog1 += e.message; + // } + // } + // broadcastLog1 += ' ' + link + '; '; + // link = 'https://mainnet.infura.io/v3/' + this._infuraProjectId; + // let broadcastLog2 = ''; + // try { + // broad = await AirDAOAxios.post(link, { + // jsonrpc: '2.0', + // method: 'eth_sendRawTransaction', + // params: [row.transactionRaw], + // id: 1 + // }); + // if (typeof broad.data.error !== 'undefined') { + // throw new Error(JSON.stringify(broad.data.error)); + // } + // broadcastLog2 = ' broadcasted ok ' + JSON.stringify(broad.data); + // updateObj.is_removed += '1'; + // updateObj.removed_at = now; + // } catch (err) { + // const e = err as unknown as any; + // if ( + // e.message.indexOf('transaction underpriced') !== -1 || + // e.message.indexOf('already known') !== -1 + // ) { + // updateObj.is_removed += '1'; + // broadcastLog2 += ' already known'; + // } else { + // updateObj.is_removed += '0'; + // broadcastLog2 += e.message; + // } + // } + // broadcastLog2 += ' ' + link + '; '; + // if (updateObj.is_removed === '111') { + // // do ALL! + // updateObj.is_removed = 1; + // } else { + // updateObj.is_removed = 0; + // } + // + // broadcastLog = + // new Date().toISOString() + + // ' ' + + // broadcastLog + + // ' ' + + // broadcastLog1 + + // ' ' + + // broadcastLog2 + + // ' ' + + // (row.broadcastLog ? row.broadcastLog.substr(0, 1000) : ''); + // } else if (this._currencyCode === 'ETH_ROPSTEN') { + // if (updateObj.is_removed === '1') { + // // do ALL! + // updateObj.is_removed = 1; + // } else { + // updateObj.is_removed = 0; + // } + // + // broadcastLog = + // new Date().toISOString() + + // ' ' + + // broadcastLog + + // ' ' + + // (row.broadcastLog ? row.broadcastLog.substr(0, 1000) : ''); + // } + // + // if ( + // this._currencyCode === 'ETH' || + // this._currencyCode === 'ETH_ROPSTEN' + // ) { + // updateObj.broadcastLog = broadcastLog; + // await Database.updateModel(tableName, row.id, updateObj); + // } + // } catch (err) { + // const e = err as unknown as any; + // throw new Error(e.message + ' inside row ' + row.transactionHash); + // } + // } + // return ret; + // } catch (err) { + // const e = err as unknown as any; + // throw new Error(e.message + ' on EthRawDS.getAddress'); + // } + // } - async cleanRawHash(data) { + async cleanRawHash(data: boolean | undefined) { AirDAOCryptoLog.log('EthRawDS cleanRawHash ', data); const now = new Date().toISOString(); @@ -287,24 +287,24 @@ class EthRawDS { await Database.unsafeRawQuery(tableName, sql); } - async cleanRaw(data) { - AirDAOCryptoLog.log('EthRawDS cleanRaw ', data); - - if (typeof data.currencyCode !== 'undefined') { - this._currencyCode = - data.currencyCode === 'ETH_ROPSTEN' ? 'ETH_ROPSTEN' : 'ETH'; - } - - const now = new Date().getTime(); - const sql = `UPDATE ${tableName} - SET is_removed=1, removed_at = '${now}' - WHERE - (is_removed=0 OR is_removed IS NULL) - AND currency_code='${this._currencyCode}' - AND address='${data.address.toLowerCase()}' - AND transaction_unique_key='${data.transactionUnique}'`; - await Database.unsafeRawQuery(tableName, sql); - } + // async cleanRaw(data) { + // AirDAOCryptoLog.log('EthRawDS cleanRaw ', data); + // + // if (typeof data.currencyCode !== 'undefined') { + // this._currencyCode = + // data.currencyCode === 'ETH_ROPSTEN' ? 'ETH_ROPSTEN' : 'ETH'; + // } + // + // const now = new Date().getTime(); + // const sql = `UPDATE ${tableName} + // SET is_removed=1, removed_at = '${now}' + // WHERE + // (is_removed=0 OR is_removed IS NULL) + // AND currency_code='${this._currencyCode}' + // AND address='${data.address.toLowerCase()}' + // AND transaction_unique_key='${data.transactionUnique}'`; + // await Database.unsafeRawQuery(tableName, sql); + // } async saveRaw(data: { address: any; From 5a7407002745b16b0d7b18382b8b64f334f77e9a Mon Sep 17 00:00:00 2001 From: illiaa Date: Thu, 24 Aug 2023 11:02:13 +0300 Subject: [PATCH 072/509] removed comments --- crypto/blockchains/AirDAODispatcher.ts | 259 ----- .../blockchains/AirDAOTransferDispatcher.ts | 148 +-- .../bnb_smart/BnbSmartTransferProcessor.ts | 55 + .../BnbSmartTransferProcessorErc20.ts | 80 ++ .../bnb_smart/basic/BnbSmartNetworkPrices.js | 73 ++ .../blockchains/etc/EtcTransferProcessor.ts | 30 +- crypto/blockchains/eth/EthScannerProcessor.ts | 1002 ----------------- .../eth/EthScannerProcessorErc20.ts | 82 -- crypto/blockchains/eth/stores/EthRawDS.ts | 277 ----- crypto/common/AirDAOAxios.ts | 91 -- crypto/common/AirDAOBN.ts | 81 -- crypto/common/AirDAOCryptoLog.ts | 140 +-- crypto/common/AirDAODict.ts | 201 +--- crypto/common/AirDAODictTypes.ts | 53 +- crypto/common/AirDAOExplorerDict.ts | 36 - crypto/common/AirDAOExternalSettings.ts | 141 +-- crypto/common/AirDAOPrettyNumbers.ts | 146 --- crypto/common/AirDAOUtils.ts | 47 - 18 files changed, 232 insertions(+), 2710 deletions(-) create mode 100644 crypto/blockchains/bnb_smart/BnbSmartTransferProcessor.ts create mode 100644 crypto/blockchains/bnb_smart/BnbSmartTransferProcessorErc20.ts create mode 100644 crypto/blockchains/bnb_smart/basic/BnbSmartNetworkPrices.js delete mode 100644 crypto/common/AirDAOExplorerDict.ts diff --git a/crypto/blockchains/AirDAODispatcher.ts b/crypto/blockchains/AirDAODispatcher.ts index bb9877068..6e0b25e2e 100644 --- a/crypto/blockchains/AirDAODispatcher.ts +++ b/crypto/blockchains/AirDAODispatcher.ts @@ -3,60 +3,13 @@ * @version 0.5 */ -// import BchAddressProcessor from '@crypto/blockchains/bch/BchAddressProcessor'; -// import BchScannerProcessor from '@crypto/blockchains/bch/BchScannerProcessor'; -// import BsvScannerProcessor from '@crypto/blockchains/bsv/BsvScannerProcessor'; -// import BtcAddressProcessor from '@crypto/blockchains/btc/address/BtcAddressProcessor'; -// import BtcScannerProcessor from '@crypto/blockchains/btc/BtcScannerProcessor'; -// import BtcSegwitCompatibleAddressProcessor from '@crypto/blockchains/btc/address/BtcSegwitCompatibleAddressProcessor'; -// import BtcSegwitAddressProcessor from '@crypto/blockchains/btc/address/BtcSegwitAddressProcessor'; -// import BtcTestScannerProcessor from '@crypto/blockchains/btc_test/BtcTestScannerProcessor'; -// import BtgScannerProcessor from '@crypto/blockchains/btg/BtgScannerProcessor'; -// import DogeScannerProcessor from '@crypto/blockchains/doge/DogeScannerProcessor'; import EthAddressProcessor from '@crypto/blockchains/eth/EthAddressProcessor'; import EthScannerProcessor from '@crypto/blockchains/eth/EthScannerProcessor'; import EthScannerProcessorErc20 from '@crypto/blockchains/eth/EthScannerProcessorErc20'; import { BlockchainUtils } from '@utils/blockchain'; -// import EthScannerProcessorSoul from '@crypto/blockchains/eth/forks/EthScannerProcessorSoul'; -// import EthTokenProcessorErc20 from '@crypto/blockchains/eth/EthTokenProcessorErc20'; -// import LtcScannerProcessor from '@crypto/blockchains/ltc/LtcScannerProcessor'; -// import TrxAddressProcessor from '@crypto/blockchains/trx/TrxAddressProcessor'; -// import TrxScannerProcessor from '@crypto/blockchains/trx/TrxScannerProcessor'; -// import TrxTokenProcessor from '@crypto/blockchains/trx/TrxTokenProcessor'; -// import UsdtScannerProcessor from '@crypto/blockchains/usdt/UsdtScannerProcessor'; -// import XrpAddressProcessor from '@crypto/blockchains/xrp/XrpAddressProcessor'; -// import XrpScannerProcessor from '@crypto/blockchains/xrp/XrpScannerProcessor'; -// import XlmAddressProcessor from '@crypto/blockchains/xlm/XlmAddressProcessor'; -// import XlmScannerProcessor from '@crypto/blockchains/xlm/XlmScannerProcessor'; -// import XvgScannerProcessor from '@crypto/blockchains/xvg/XvgScannerProcessor'; -// import XmrAddressProcessor from '@crypto/blockchains/xmr/XmrAddressProcessor'; -// import XmrScannerProcessor from '@crypto/blockchains/xmr/XmrScannerProcessor'; -// import XmrSecretsProcessor from '@crypto/blockchains/xmr/XmrSecretsProcessor'; -// import FioAddressProcessor from '@crypto/blockchains/fio/FioAddressProcessor'; -// import FioScannerProcessor from '@crypto/blockchains/fio/FioScannerProcessor'; -// import BnbAddressProcessor from '@crypto/blockchains/bnb/BnbAddressProcessor'; -// import BnbScannerProcessor from '@crypto/blockchains/bnb/BnbScannerProcessor'; -// import { BlockchainUtils } from '@utils/blockchain'; -// import VetScannerProcessor from '@crypto/blockchains/vet/VetScannerProcessor'; -// import SolAddressProcessor from '@crypto/blockchains/sol/SolAddressProcessor'; -// import SolScannerProcessor from '@crypto/blockchains/sol/SolScannerProcessor'; -// import WavesAddressProcessor from '@crypto/blockchains/waves/WavesAddressProcessor'; -// import WavesScannerProcessor from '@crypto/blockchains/waves/WavesScannerProcessor'; -// import SolScannerProcessorSpl from '@crypto/blockchains/sol/SolScannerProcessorSpl'; -// import SolTokenProcessor from '@crypto/blockchains/sol/SolTokenProcessor'; -// import EthTokenProcessorNft from '@crypto/blockchains/eth/EthTokenProcessorNft'; -// import AshAddressProcessor from '@crypto/blockchains/ash/AshAddressProcessor'; -// import MetisScannerProcessor from '@crypto/blockchains/metis/MetisScannerProcessor'; -// import OneScannerProcessor from '@crypto/blockchains/one/OneScannerProcessor'; -// import OneScannerProcessorErc20 from '@crypto/blockchains/one/OneScannerProcessorErc20'; -// import WavesScannerProcessorErc20 from '@crypto/blockchains/waves/WavesScannerProcessorErc20'; class AirDAODispatcher { getAddressProcessor(currencyCode: string): EthAddressProcessor { - // | BtcAddressProcessor - // | TrxAddressProcessor - // | XlmAddressProcessor - // | SolAddressProcessor const currencyDictSettings = BlockchainUtils.getCurrencyAllSettings(currencyCode); return this.innerGetAddressProcessor(currencyDictSettings); @@ -65,37 +18,9 @@ class AirDAODispatcher { innerGetAddressProcessor( currencyDictSettings: any // TODO ): EthAddressProcessor { - // | BtcAddressProcessor| TrxAddressProcessor| XlmAddressProcessor| SolAddressProcessor switch (currencyDictSettings.addressProcessor) { - // case 'BCH': - // return new BchAddressProcessor(currencyDictSettings); - // case 'BTC': - // return new BtcAddressProcessor(currencyDictSettings); - // case 'BTC_SEGWIT': - // case 'LTC_SEGWIT': - // return new BtcSegwitAddressProcessor(currencyDictSettings); - // case 'BTC_SEGWIT_COMPATIBLE': - // return new BtcSegwitCompatibleAddressProcessor(currencyDictSettings); case 'ETH': return new EthAddressProcessor(currencyDictSettings); - // case 'TRX': - // return new TrxAddressProcessor(); - // case 'XRP': - // return new XrpAddressProcessor(); - // case 'XLM': - // return new XlmAddressProcessor(); - // case 'XMR': - // return new XmrAddressProcessor(); - // case 'FIO': - // return new FioAddressProcessor(); - // case 'BNB': - // return new BnbAddressProcessor(); - // case 'SOL': - // return new SolAddressProcessor(); - // case 'WAVES': - // return new WavesAddressProcessor(); - // case 'ASH': - // return new AshAddressProcessor(); default: throw new Error( 'Unknown addressProcessor ' + currencyDictSettings.addressProcessor @@ -106,203 +31,19 @@ class AirDAODispatcher { getScannerProcessor( currencyCode: string ): EthScannerProcessor | EthScannerProcessorErc20 { - // | BsvScannerProcessor - // | BtcScannerProcessor - // | UsdtScannerProcessor - // | EthScannerProcessorErc20 - // | BchScannerProcessor - // | LtcScannerProcessor - // | XvgScannerProcessor - // | BtcTestScannerProcessor - // | DogeScannerProcessor - // | EthScannerProcessorSoul - // | EthScannerProcessor - // | BtgScannerProcessor - // | TrxScannerProcessor - // | XrpScannerProcessor - // | XlmScannerProcessor - // | XmrScannerProcessor - // | FioScannerProcessor - // | BnbScannerProcessor - // | VetScannerProcessor - // | SolScannerProcessor - // | SolScannerProcessorSpl - // | WavesScannerProcessor - // | MetisScannerProcessor - // | OneScannerProcessor - // | OneScannerProcessorErc20 - // | WavesScannerProcessorErc20 const currencyDictSettings = BlockchainUtils.getCurrencyAllSettings(currencyCode); switch (currencyDictSettings.scannerProcessor) { - // case 'BCH': - // return new BchScannerProcessor(currencyDictSettings); - // case 'BSV': - // return new BsvScannerProcessor(); - // case 'BTC': - // case 'BTC_SEGWIT': - // case 'BTC_SEGWIT_COMPATIBLE': - // return new BtcScannerProcessor(currencyDictSettings); - // case 'BTC_TEST': - // return new BtcTestScannerProcessor(); - // case 'BTG': - // return new BtgScannerProcessor(currencyDictSettings); - // case 'DOGE': - // return new DogeScannerProcessor(currencyDictSettings); case 'ETH': return new EthScannerProcessor(currencyDictSettings); case 'ETH_ERC_20': return new EthScannerProcessorErc20(currencyDictSettings); - // case 'ETH_SOUL': - // return new EthScannerProcessorSoul(currencyDictSettings); - // case 'LTC': - // return new LtcScannerProcessor(currencyDictSettings); - // case 'TRX': - // return new TrxScannerProcessor(currencyDictSettings); - // case 'USDT': - // return new UsdtScannerProcessor(); - // case 'XRP': - // return new XrpScannerProcessor(); - // case 'XLM': - // return new XlmScannerProcessor(); - // case 'XVG': - // return new XvgScannerProcessor(); - // case 'XMR': - // return new XmrScannerProcessor(currencyDictSettings); - // case 'FIO': - // return new FioScannerProcessor(currencyDictSettings); - // case 'BNB': - // return new BnbScannerProcessor(); - // case 'VET': - // return new VetScannerProcessor(currencyDictSettings); - // case 'SOL': - // return new SolScannerProcessor(currencyDictSettings); - // case 'SOL_SPL': - // return new SolScannerProcessorSpl(currencyDictSettings); - // case 'WAVES': - // return new WavesScannerProcessor(currencyDictSettings); - // case 'METIS': - // return new MetisScannerProcessor(currencyDictSettings); - // case 'ONE': - // return new OneScannerProcessor(currencyDictSettings); - // case 'ONE_ERC_20': - // return new OneScannerProcessorErc20(currencyDictSettings); - // case 'WAVES_ERC_20': - // return new WavesScannerProcessorErc20(currencyDictSettings); default: throw new Error( 'Unknown scannerProcessor ' + currencyDictSettings.scannerProcessor ); } } - - // TODO enum tokenType - // getTokenProcessor(tokenType: string): EthTokenProcessorErc20 { - // switch (tokenType) { - // case 'ETH_ERC_20': - // return new EthTokenProcessorErc20({ - // network: 'mainnet', - // tokenBlockchain: 'ETHEREUM' - // }); - // case 'BNB_SMART_20': - // return new EthTokenProcessorErc20({ - // network: 'mainnet', - // tokenBlockchain: 'BNB' - // }); - // // case 'MATIC_ERC_20': - // // return new EthTokenProcessorErc20({ - // // network: 'mainnet', - // // tokenBlockchain: 'MATIC' - // // }); - // // case 'FTM_ERC_20': - // // return new EthTokenProcessorErc20({ - // // network: 'mainnet', - // // tokenBlockchain: 'FTM' - // // }); - // // case 'VLX_ERC_20': - // // return new EthTokenProcessorErc20({ - // // network: 'mainnet', - // // tokenBlockchain: 'VLX' - // // }); - // // case 'ONE_ERC_20': - // // return new EthTokenProcessorErc20({ - // // network: 'mainnet', - // // tokenBlockchain: 'ONE' - // // }); - // // case 'METIS_ERC_20': - // // return new EthTokenProcessorErc20({ - // // network: 'mainnet', - // // tokenBlockchain: 'METIS' - // // }); - // // case 'TRX': - // // return new TrxTokenProcessor(); - // // case 'SOL': - // // return new SolTokenProcessor(); - // default: - // throw new Error('Unknown tokenProcessor ' + tokenType); - // } - // } - - // TODO enum tokenBlockchainCode - // getTokenNftsProcessor(tokenBlockchainCode: string) { - // switch (tokenBlockchainCode) { - // case 'ETH': - // case 'NFT_ETH': - // return new EthTokenProcessorNft({ - // network: 'mainnet', - // tokenBlockchain: 'ETHEREUM', - // tokenBlockchainCode: 'ETH' - // }); - // case 'ETH_RINKEBY': - // case 'NFT_RINKEBY': - // return new EthTokenProcessorNft({ - // network: 'rinkeby', - // tokenBlockchain: 'RINKEBY', - // tokenBlockchainCode: 'ETH_RINKEBY' - // }); - // case 'MATIC': - // case 'NFT_MATIC': - // return new EthTokenProcessorNft({ - // network: 'mainnet', - // tokenBlockchain: 'MATIC', - // tokenBlockchainCode: 'MATIC' - // }); - // case 'BNB': - // case 'NFT_BNB': - // return new EthTokenProcessorNft({ - // network: 'mainnet', - // tokenBlockchain: 'BNB', - // tokenBlockchainCode: 'BNB' - // }); - // case 'ONE': - // case 'NFT_ONE': - // return new EthTokenProcessorNft({ - // network: 'mainnet', - // tokenBlockchain: 'ONE', - // tokenBlockchainCode: 'ONE' - // }); - // case 'ETH_ROPSTEN': - // case 'NFT_ROPSTEN': - // return new EthTokenProcessorNft({ - // network: 'ropsten', - // tokenBlockchain: 'ROPSTEN', - // tokenBlockchainCode: 'ETH_ROPSTEN' - // }); - // default: - // throw new Error('Unknown NFT tokenProcessor ' + tokenBlockchainCode); - // } - // } - - // getSecretsProcessor(currencyCode: string): XmrSecretsProcessor { - // const currencyDictSettings = - // BlockchainUtils.getCurrencyAllSettings(currencyCode); - // if (currencyDictSettings.currencyCode !== 'XMR') { - // throw new Error( - // 'Unknown secretsProcessor ' + currencyDictSettings.currencyCode - // ); - // } - // return new XmrSecretsProcessor(); - // } } const singleAirDAODispatcher = new AirDAODispatcher(); diff --git a/crypto/blockchains/AirDAOTransferDispatcher.ts b/crypto/blockchains/AirDAOTransferDispatcher.ts index db93cdb8d..7ab6a5907 100644 --- a/crypto/blockchains/AirDAOTransferDispatcher.ts +++ b/crypto/blockchains/AirDAOTransferDispatcher.ts @@ -5,43 +5,19 @@ */ import AirDAODict from '../common/AirDAODict'; import { AirDAODictTypes } from '../common/AirDAODictTypes'; - -// import BchTransferProcessor from './bch/BchTransferProcessor'; -// import BsvTransferProcessor from './bsv/BsvTransferProcessor'; -// import BtcTransferProcessor from './btc/BtcTransferProcessor'; -// import BtcTestTransferProcessor from './btc_test/BtcTestTransferProcessor'; -// import BtgTransferProcessor from './btg/BtgTransferProcessor'; -// import DogeTransferProcessor from './doge/DogeTransferProcessor'; import EthTransferProcessor from './eth/EthTransferProcessor'; import EthTransferProcessorErc20 from './eth/EthTransferProcessorErc20'; -// import LtcTransferProcessor from './ltc/LtcTransferProcessor'; -// import TrxTransferProcessor from './trx/TrxTransferProcessor'; -// import UsdtTransferProcessor from './usdt/UsdtTransferProcessor'; -// import XrpTransferProcessor from './xrp/XrpTransferProcessor'; -// import XlmTransferProcessor from './xlm/XlmTransferProcessor'; -// import XvgTransferProcessor from './xvg/XvgTransferProcessor'; -// import XmrTransferProcessor from './xmr/XmrTransferProcessor'; -// import FioTransferProcessor from './fio/FioTransferProcessor'; -// import BnbTransferProcessor from './bnb/BnbTransferProcessor'; -// import BnbSmartTransferProcessor from './bnb_smart/BnbSmartTransferProcessor'; -// import BnbSmartTransferProcessorErc20 from './bnb_smart/BnbSmartTransferProcessorErc20'; -// import EtcTransferProcessor from './etc/EtcTransferProcessor'; -// import VetTransferProcessor from '@crypto/blockchains/vet/VetTransferProcessor'; -// import SolTransferProcessor from '@crypto/blockchains/sol/SolTransferProcessor'; -// import SolTransferProcessorSpl from '@crypto/blockchains/sol/SolTransferProcessorSpl'; -// import WavesTransferProcessor from '@crypto/blockchains/waves/WavesTransferProcessor'; -// import MetisTransferProcessor from '@crypto/blockchains/metis/MetisTransferProcessor'; - +import EtcTransferProcessor from './etc/EtcTransferProcessor'; import { AirDAOBlockchainTypes } from './AirDAOBlockchainTypes'; +import BnbSmartTransferProcessorErc20 from '@crypto/blockchains/bnb_smart/BnbSmartTransferProcessorErc20'; +// tslint:disable-next-line:no-namespace export namespace AirDAOTransferDispatcher { type AirDAOTransferDispatcherDict = { [key in AirDAODictTypes.Code]: AirDAOBlockchainTypes.TransferProcessor; }; - const CACHE_PROCESSORS: AirDAOTransferDispatcherDict = {} as AirDAOTransferDispatcherDict; - export const getTransferProcessor = ( currencyCode: AirDAODictTypes.Code ): AirDAOBlockchainTypes.TransferProcessor => { @@ -55,36 +31,6 @@ export namespace AirDAOTransferDispatcher { transferProcessor = currencyDictSettings.transferProcessor; } switch (transferProcessor) { - // case 'BCH': - // CACHE_PROCESSORS[currencyCode] = new BchTransferProcessor( - // currencyDictSettings - // ); - // break; - // case 'BSV': - // CACHE_PROCESSORS[currencyCode] = new BsvTransferProcessor( - // currencyDictSettings - // ); - // break; - // case 'BTC': - // CACHE_PROCESSORS[currencyCode] = new BtcTransferProcessor( - // currencyDictSettings - // ); - // break; - // case 'BTC_TEST': - // CACHE_PROCESSORS[currencyCode] = new BtcTestTransferProcessor( - // currencyDictSettings - // ); - // break; - // case 'BTG': - // CACHE_PROCESSORS[currencyCode] = new BtgTransferProcessor( - // currencyDictSettings - // ); - // break; - // case 'DOGE': - // CACHE_PROCESSORS[currencyCode] = new DogeTransferProcessor( - // currencyDictSettings - // ); - // break; case 'ETH': CACHE_PROCESSORS[currencyCode] = new EthTransferProcessor( currencyDictSettings @@ -96,88 +42,16 @@ export namespace AirDAOTransferDispatcher { ); break; case 'ETC': + CACHE_PROCESSORS[currencyCode] = new EtcTransferProcessor( + currencyDictSettings + ); CACHE_PROCESSORS[currencyCode] = new currencyDictSettings(); break; - // case 'BNB_SMART_20': - // CACHE_PROCESSORS[currencyCode] = new BnbSmartTransferProcessorErc20( - // currencyDictSettings - // ); - // break; - // case 'LTC': - // CACHE_PROCESSORS[currencyCode] = new LtcTransferProcessor( - // currencyDictSettings - // ); - // break; - // case 'TRX': - // CACHE_PROCESSORS[currencyCode] = new TrxTransferProcessor( - // currencyDictSettings - // ); - // break; - // case 'USDT': - // CACHE_PROCESSORS[currencyCode] = new UsdtTransferProcessor( - // currencyDictSettings - // ); - // break; - // case 'XRP': - // CACHE_PROCESSORS[currencyCode] = new XrpTransferProcessor( - // currencyDictSettings - // ); - // break; - // case 'XLM': - // CACHE_PROCESSORS[currencyCode] = new XlmTransferProcessor( - // currencyDictSettings - // ); - // break; - // case 'XVG': - // CACHE_PROCESSORS[currencyCode] = new XvgTransferProcessor( - // currencyDictSettings - // ); - // break; - // case 'XMR': - // CACHE_PROCESSORS[currencyCode] = new XmrTransferProcessor( - // currencyDictSettings - // ); - // break; - // case 'FIO': - // CACHE_PROCESSORS[currencyCode] = new FioTransferProcessor( - // currencyDictSettings - // ); - // break; - // case 'BNB': - // CACHE_PROCESSORS[currencyCode] = new BnbTransferProcessor( - // currencyDictSettings - // ); - // break; - // case 'BNB_SMART': - // CACHE_PROCESSORS[currencyCode] = new BnbSmartTransferProcessor( - // currencyDictSettings - // ); - // break; - // case 'VET': - // CACHE_PROCESSORS[currencyCode] = new VetTransferProcessor( - // currencyDictSettings - // ); - // break; - // case 'SOL': - // CACHE_PROCESSORS[currencyCode] = new SolTransferProcessor( - // currencyDictSettings - // ); - // break; - // case 'SOL_SPL': - // CACHE_PROCESSORS[currencyCode] = new SolTransferProcessorSpl( - // currencyDictSettings - // ); - // break; - // case 'METIS': - // CACHE_PROCESSORS[currencyCode] = new MetisTransferProcessor( - // currencyDictSettings - // ); - // break; - // case 'WAVES': - // CACHE_PROCESSORS[currencyCode] = new WavesTransferProcessor( - // currencyDictSettings - // ); - // break; + case 'BNB_SMART_20': + CACHE_PROCESSORS[currencyCode] = new BnbSmartTransferProcessorErc20( + currencyDictSettings + ); + break; default: throw new Error('Unknown transferProcessor ' + transferProcessor); } diff --git a/crypto/blockchains/bnb_smart/BnbSmartTransferProcessor.ts b/crypto/blockchains/bnb_smart/BnbSmartTransferProcessor.ts new file mode 100644 index 000000000..eda6ad396 --- /dev/null +++ b/crypto/blockchains/bnb_smart/BnbSmartTransferProcessor.ts @@ -0,0 +1,55 @@ +/** + * @author Ksu + * @version 0.20 + * https://api.etherscan.io/api?module=gastracker&action=gasoracle&apikey=YourApiKeyToken + */ +import { AirDAOBlockchainTypes } from '../AirDAOBlockchainTypes'; +import EthTransferProcessor from '../eth/EthTransferProcessor'; + +import BnbSmartNetworkPrices from './basic/BnbSmartNetworkPrices'; +import BlocksoftExternalSettings from '@crypto/common/AirDAOExternalSettings'; + +export default class BnbSmartTransferProcessor + extends EthTransferProcessor + implements AirDAOBlockchainTypes.TransferProcessor +{ + async getFeeRate( + data: AirDAOBlockchainTypes.TransferData, + privateData: AirDAOBlockchainTypes.TransferPrivateData, + additionalData: {} = {} + ): Promise { + if ( + typeof additionalData.gasPrice === 'undefined' || + !additionalData.gasPrice + ) { + const minFee = BlocksoftExternalSettings.getStatic( + this._mainCurrencyCode + '_FORCE_PRICE' + ); + if (typeof minFee !== 'undefined' && minFee > 1) { + additionalData.gasPrice = minFee; + additionalData.gasPriceTitle = 'speed_blocks_2'; + } else { + let defaultFee = BlocksoftExternalSettings.getStatic( + this._mainCurrencyCode + '_PRICE' + ); + if (typeof defaultFee === 'undefined' || !defaultFee) { + defaultFee = 5000000000; + } + if (this._etherscanApiPathForFee) { + const tmpPrice = await BnbSmartNetworkPrices.getFees( + this._mainCurrencyCode, + this._etherscanApiPathForFee, + defaultFee, + 'BnbSmartTransferProcessor.getFeeRate' + ); + if (tmpPrice * 1 > defaultFee * 1) { + defaultFee = tmpPrice * 1; + } + } + additionalData.gasPrice = defaultFee; + additionalData.gasPriceTitle = 'speed_blocks_2'; + } + } + return super.getFeeRate(data, privateData, additionalData); + } +} diff --git a/crypto/blockchains/bnb_smart/BnbSmartTransferProcessorErc20.ts b/crypto/blockchains/bnb_smart/BnbSmartTransferProcessorErc20.ts new file mode 100644 index 000000000..a5e8eba5d --- /dev/null +++ b/crypto/blockchains/bnb_smart/BnbSmartTransferProcessorErc20.ts @@ -0,0 +1,80 @@ +/** + * @version 0.20 + */ +import { AirDAOBlockchainTypes } from '../AirDAOBlockchainTypes'; +import EthTransferProcessorErc20 from '../eth/EthTransferProcessorErc20'; +import BnbSmartNetworkPrices from './basic/BnbSmartNetworkPrices'; +import BlocksoftExternalSettings from '@crypto/common/AirDAOExternalSettings'; + +export default class BnbSmartTransferProcessorErc20 + extends EthTransferProcessorErc20 + implements AirDAOBlockchainTypes.TransferProcessor +{ + async getFeeRate( + data: AirDAOBlockchainTypes.TransferData, + privateData: AirDAOBlockchainTypes.TransferPrivateData, + additionalData: {} = {} + ): Promise { + if ( + typeof additionalData.gasPrice === 'undefined' || + !additionalData.gasPrice + ) { + const minFee = BlocksoftExternalSettings.getStatic( + this._mainCurrencyCode + '_FORCE_PRICE_ERC20' + ); + if (typeof minFee !== 'undefined' && minFee > 1) { + additionalData.gasPrice = minFee; + additionalData.gasPriceTitle = 'speed_blocks_2'; + } else { + let defaultFee = BlocksoftExternalSettings.getStatic( + this._mainCurrencyCode + '_PRICE' + ); + if (typeof defaultFee === 'undefined' || !defaultFee) { + defaultFee = 5000000000; + } + if (!this._etherscanApiPathForFee) { + additionalData.gasPrice = defaultFee; + additionalData.gasPriceTitle = 'speed_blocks_2'; + } else { + additionalData.gasPrice = await BnbSmartNetworkPrices.getFees( + this._mainCurrencyCode, + this._etherscanApiPathForFee, + defaultFee, + 'BnbSmartTransferProcessorErc20.getFeeRate' + ); + additionalData.gasPriceTitle = 'speed_blocks_2'; + } + } + } + const result = await super.getFeeRate(data, privateData, additionalData); + result.shouldShowFees = true; + return result; + } + + async checkTransferHasError( + data: AirDAOBlockchainTypes.CheckTransferHasErrorData + ): Promise { + // @ts-ignore + const balance = + data.addressFrom && data.addressFrom !== '' + ? await this._web3.eth.getBalance(data.addressFrom) + : 0; + if (balance > 0) { + return { isOk: true }; + } else { + const title = + this._mainCurrencyCode === 'BNB' + ? 'BNB Smart Chain' + : this._mainCurrencyCode; + // @ts-ignore + return { + isOk: false, + code: 'TOKEN', + parentBlockchain: + title as AirDAOBlockchainTypes.CheckTransferHasErrorResult['parentBlockchain'], + parentCurrency: + title as AirDAOBlockchainTypes.CheckTransferHasErrorResult['parentCurrency'] + }; + } + } +} diff --git a/crypto/blockchains/bnb_smart/basic/BnbSmartNetworkPrices.js b/crypto/blockchains/bnb_smart/basic/BnbSmartNetworkPrices.js new file mode 100644 index 000000000..8e7647be3 --- /dev/null +++ b/crypto/blockchains/bnb_smart/basic/BnbSmartNetworkPrices.js @@ -0,0 +1,73 @@ +/** + * @version 0.20 + */ +import AirDAOCryptoLog from '../../../common/AirDAOCryptoLog'; +import AirDAOAxios from '../../../common/AirDAOAxios'; +import BlocksoftUtils from '../../../common/AirDAOUtils'; +import BlocksoftExternalSettings from '../../../common/AirDAOExternalSettings'; + +const CACHE_VALID_TIME = 120000; // 2 minute +const CACHE_FEES = { + BNB: { + fee: 5000000000, + ts: 0 + } +}; + +class BnbSmartNetworkPrices { + async getFees( + mainCurrencyCode, + etherscanApiPath, + defaultFee = 5000000000, + source = '' + ) { + const now = new Date().getTime(); + if ( + typeof CACHE_FEES[mainCurrencyCode] !== 'undefined' && + now - CACHE_FEES[mainCurrencyCode].ts < CACHE_VALID_TIME + ) { + return CACHE_FEES[mainCurrencyCode].fee; + } + const tmp = etherscanApiPath.split('/'); + const feesApiPath = `https://${tmp[2]}/api?module=proxy&action=eth_gasPrice&apikey=YourApiKeyToken`; + AirDAOCryptoLog.log( + mainCurrencyCode + ' BnbSmartNetworkPricesProvider.getFees no cache load' + ); + try { + const res = await AirDAOAxios.getWithoutBraking(feesApiPath); + if ( + res && + typeof res.data !== 'undefined' && + typeof res.data.result !== 'undefined' + ) { + const tmp = BlocksoftUtils.hexToDecimal(res.data.result); + if (tmp * 1 > 0) { + CACHE_FEES[mainCurrencyCode] = { + fee: (tmp * 1).toString().substr(0, 11), + time: now + }; + } else if ( + typeof CACHE_FEES[mainCurrencyCode] === 'undefined' || + !CACHE_FEES[mainCurrencyCode].fee + ) { + CACHE_FEES[mainCurrencyCode].fee = + await BlocksoftExternalSettings.getStatic('BNB_SMART_PRICE'); + } + } + } catch (e) { + AirDAOCryptoLog.log( + mainCurrencyCode + + ' BnbSmartNetworkPricesProvider.getOnlyFees loaded prev fee as error' + + e.message + ); + // do nothing + } + + return typeof CACHE_FEES[mainCurrencyCode] !== 'undefined' + ? CACHE_FEES[mainCurrencyCode].fee + : defaultFee; + } +} + +const singleton = new BnbSmartNetworkPrices(); +export default singleton; diff --git a/crypto/blockchains/etc/EtcTransferProcessor.ts b/crypto/blockchains/etc/EtcTransferProcessor.ts index 562007d1f..ff64a8eb4 100644 --- a/crypto/blockchains/etc/EtcTransferProcessor.ts +++ b/crypto/blockchains/etc/EtcTransferProcessor.ts @@ -3,8 +3,10 @@ * @version 0.43 */ import { AirDAOBlockchainTypes } from '@crypto/blockchains/AirDAOBlockchainTypes'; +import BnbSmartTransferProcessor from '@crypto/blockchains/bnb_smart/BnbSmartTransferProcessor'; export default class EtcTransferProcessor + extends BnbSmartTransferProcessor implements AirDAOBlockchainTypes.TransferProcessor { canRBF( @@ -14,32 +16,4 @@ export default class EtcTransferProcessor ): boolean { return false; } - - getFeeRate( - data: AirDAOBlockchainTypes.TransferData, - privateData?: AirDAOBlockchainTypes.TransferPrivateData, - additionalData?: AirDAOBlockchainTypes.TransferAdditionalData - ): Promise { - return Promise.resolve(undefined); - } - - getTransferAllBalance( - data: AirDAOBlockchainTypes.TransferData, - privateData?: AirDAOBlockchainTypes.TransferPrivateData, - additionalData?: AirDAOBlockchainTypes.TransferAdditionalData - ): Promise { - return Promise.resolve(undefined); - } - - needPrivateForFee(): boolean { - return false; - } - - sendTx( - data: AirDAOBlockchainTypes.TransferData, - privateData: AirDAOBlockchainTypes.TransferPrivateData, - uiData: AirDAOBlockchainTypes.TransferUiData - ): Promise { - return Promise.resolve(undefined); - } } diff --git a/crypto/blockchains/eth/EthScannerProcessor.ts b/crypto/blockchains/eth/EthScannerProcessor.ts index 4938ac784..938d2a7ba 100644 --- a/crypto/blockchains/eth/EthScannerProcessor.ts +++ b/crypto/blockchains/eth/EthScannerProcessor.ts @@ -7,446 +7,7 @@ import AirDAOCryptoLog from '../../common/AirDAOCryptoLog'; import EthBasic from './basic/EthBasic'; import BlocksoftExternalSettings from '../../common/AirDAOExternalSettings'; -// import EthTmpDS from './stores/EthTmpDS'; -// import EthRawDS from './stores/EthRawDS'; -// import BlocksoftPrettyNumbers from '@crypto/common/AirDAOPrettyNumbers'; - -// const CACHE_GET_MAX_BLOCK = { -// ETH: { max_block_number: 0, confirmations: 0 }, -// BNB: { max_block_number: 0, confirmations: 0 }, -// ETC: { max_block_number: 0, confirmations: 0 }, -// AMB: { max_block_number: 0, confirmations: 0 }, -// MATIC: { max_block_number: 0, confirmations: 0 }, -// FTM: { max_block_number: 0, confirmations: 0 }, -// RSK: { max_block_number: 0, confirmations: 0 }, -// OPTIMISM: { max_block_number: 0, confirmations: 0 }, -// METIS: { max_block_number: 0, confirmations: 0 }, -// VLX: { max_block_number: 0, confirmations: 0 } -// }; - -// const CACHE_BLOCK_NUMBER_TO_HASH = { -// ETH: {}, -// BNB: {}, -// ETC: {}, -// AMB: {}, -// MATIC: {}, -// FTM: {}, -// RSK: {}, -// OPTIMISM: {}, -// METIS: {}, -// VLX: {} -// }; - -// const CACHE_VALID_TIME = 30000; // 30 seconds -// const CACHE = { -// ETH: {}, -// BNB: {}, -// ETC: {}, -// AMB: {}, -// MATIC: {}, -// FTM: {}, -// RSK: {}, -// OPTIMISM: {}, -// METIS: {}, -// VLX: {} -// }; - export default class EthScannerProcessor extends EthBasic { - /** - * @type {number} - * @private - */ - // _blocksToConfirm = 10; - - /** - * @type {boolean} - * @private - */ - // _useInternal = true; - - /** - * https://eth1.trezor.io/api/v2/address/0x8b661361Be29E688Dda65b323526aD536c8B3997?details=txs - * @returns {Promise} - * @private - * @param _address - */ - // async _get(_address: string) { - // const address = _address.toLowerCase(); - // - // try { - // this._trezorServer = await BlocksoftExternalSettings.getTrezorServer( - // this._trezorServerCode, - // 'ETH.Scanner._get' - // ); - // } catch (e: any) { - // throw new Error( - // e.message + ' while getTrezorServer ' + this._trezorServerCode - // ); - // } - // - // if (typeof this._trezorServer === 'undefined') { - // AirDAOCryptoLog.err( - // this._settings.currencyCode + - // ' EthScannerProcessor._get empty trezorServer' - // ); - // throw new Error( - // this._settings.currencyCode + - // ' EthScannerProcessor._get empty trezorServer' - // ); - // } - // - // if (!this._trezorServer) { - // return false; - // } - // - // const now = new Date().getTime(); - // if ( - // typeof CACHE[this._mainCurrencyCode] !== 'undefined' && - // typeof CACHE[this._mainCurrencyCode][address] !== 'undefined' && - // now - CACHE[this._mainCurrencyCode][address].time < CACHE_VALID_TIME - // ) { - // CACHE[this._mainCurrencyCode][address].provider = 'trezor-cache'; - // return CACHE[this._mainCurrencyCode][address]; - // } - // - // let link = - // this._trezorServer + '/api/v2/address/' + address + '?details=txs'; - // let res = await AirDAOAxios.getWithoutBraking(link); - // - // if (!res || !res.data) { - // BlocksoftExternalSettings.setTrezorServerInvalid( - // this._trezorServerCode, - // this._trezorServer - // ); - // this._trezorServer = await BlocksoftExternalSettings.getTrezorServer( - // this._trezorServerCode, - // this._settings.currencyCode + ' ETH.Scanner._get' - // ); - // if (typeof this._trezorServer === 'undefined') { - // AirDAOCryptoLog.err( - // this._settings.currencyCode + - // ' EthScannerProcessor._get empty trezorServer2' - // ); - // throw new Error( - // this._settings.currencyCode + - // ' EthScannerProcessor._get empty trezorServer2' - // ); - // } - // link = this._trezorServer + '/api/v2/address/' + address + '?details=txs'; - // res = await AirDAOAxios.getWithoutBraking(link); - // if (!res || !res.data) { - // BlocksoftExternalSettings.setTrezorServerInvalid( - // this._trezorServerCode, - // this._trezorServer - // ); - // return false; - // } - // } - // - // if (typeof res.data.balance === 'undefined') { - // throw new Error( - // this._settings.currencyCode + - // ' EthScannerProcessor._get nothing loaded for address ' + - // link - // ); - // } - // const data = res.data; - // data.totalTokens = 0; - // data.formattedTokens = {}; - // // await AirDAOCryptoLog.log('EthScannerProcessor._get ERC20 tokens ' + JSON.stringify(data.tokens)) - // if (typeof data.tokens !== 'undefined') { - // let token; - // for (token of data.tokens) { - // data.formattedTokens[token.contract.toLowerCase()] = token; - // } - // } - // if (typeof CACHE[this._mainCurrencyCode][address] !== 'undefined') { - // if (CACHE[this._mainCurrencyCode][address].data.nonce > res.data.nonce) { - // return false; - // } - // } - // CACHE[this._mainCurrencyCode][address] = { - // data, - // provider: 'trezor', - // time: now - // }; - // return CACHE[this._mainCurrencyCode][address]; - // } - - /** - * @param {string} address - * @return {Promise<{balance, unconfirmed, provider}>} - */ - // async getBalanceBlockchain(address) { - // await AirDAOCryptoLog.log( - // this._settings.currencyCode + - // ' EthScannerProcessor.getBalance started ' + - // address - // ); - // - // this.checkWeb3CurrentServerUpdated(); - // // noinspection JSUnresolvedVariable - // let balance = 0; - // let provider = ''; - // let time = 0; - // try { - // if ( - // this._trezorServerCode && - // this._trezorServerCode.indexOf('http') === -1 - // ) { - // const res = await this._get(address); - // - // if ( - // res && - // typeof res.data !== 'undefined' && - // res.data && - // typeof res.data.balance !== 'undefined' - // ) { - // balance = res.data.balance; - // provider = res.provider; - // time = res.time; - // return { - // balance, - // unconfirmed: 0, - // provider, - // time, - // balanceScanBlock: res.data.nonce - // }; - // } - // } - // } catch (e) { - // await AirDAOCryptoLog.log( - // this._settings.currencyCode + - // ' EthScannerProcessor.getBalance ' + - // address + - // ' trezor error ' + - // e.message - // ); - // return false; - // } - // - // try { - // balance = await this._web3.eth.getBalance(address); - // AirDAOCryptoLog.log( - // this._settings.currencyCode + - // ' EthScannerProcessor.getBalance ' + - // address + - // ' result ' + - // JSON.stringify(balance) - // ); - // provider = 'web3'; - // time = 'now()'; - // return { balance, unconfirmed: 0, provider, time }; - // } catch (e) { - // await AirDAOCryptoLog.log( - // this._settings.currencyCode + - // ' EthScannerProcessor.getBalance ' + - // address + - // ' ' + - // this._web3.LINK + - // ' rpc error ' + - // e.message - // ); - // return false; - // } - // } - - /** - * @param {string} scanData.account.address - * @return {Promise<[UnifiedTransaction]>} - */ - // async getTransactionsBlockchain(scanData) { - // const address = scanData.account.address; - // await AirDAOCryptoLog.log( - // this._settings.currencyCode + - // ' EthScannerProcessor.getTransactions started ' + - // address - // ); - // let res = false; - // if ( - // this._settings.currencyCode !== 'ETH_ROPSTEN' && - // this._settings.currencyCode !== 'ETH_RINKEBY' && - // this._trezorServerCode - // ) { - // try { - // res = await this._get(address); - // } catch (e) { - // throw new Error(e.message + ' in EthScannerProcessor._get'); - // } - // } - // - // let transactions; - // if (res && typeof res.data !== 'undefined' && res.data) { - // await AirDAOCryptoLog.log( - // this._settings.currencyCode + - // ' EthScannerProcessor.getBalance loaded from ' + - // res.provider + - // ' ' + - // res.time - // ); - // if ( - // this._tokenAddress && - // typeof res.data.formattedTokens[this._tokenAddress] === 'undefined' - // ) { - // await AirDAOCryptoLog.log( - // this._settings.currencyCode + - // ' EthScannerProcessor.getTransactions skipped token ' + - // this._tokenAddress + - // ' ' + - // address - // ); - // return false; - // } - // await AirDAOCryptoLog.log( - // this._settings.currencyCode + - // ' EthScannerProcessor.getTransactions trezor unify started ' + - // address - // ); - // transactions = await this._unifyTransactions( - // address, - // res.data.transactions, - // false, - // true, - // {} - // ); - // await AirDAOCryptoLog.log( - // this._settings.currencyCode + - // ' EthScannerProcessor.getTransactions trezor finished ' + - // address - // ); - // } else if (this._oklinkAPI) { - // const logTitle = - // this._settings.currencyCode + - // ' EthScannerProcessor.getTransactions oklink '; - // transactions = await this._getFromOklink( - // address, - // this._oklinkAPI, - // logTitle, - // {} - // ); - // } else { - // if (!this._etherscanApiPath) { - // AirDAOCryptoLog.err( - // this._settings.currencyCode + - // ' EthScannerProcessor.getTransactions no _etherscanApiPath' - // ); - // } - // let link = this._etherscanApiPath + '&address=' + address; - // let logTitle = - // this._settings.currencyCode + - // ' EthScannerProcessor.getTransactions etherscan '; - // transactions = await this._getFromEtherscan( - // address, - // link, - // logTitle, - // false, - // {} - // ); - // if (this._etherscanApiPathDeposits) { - // link = this._etherscanApiPathDeposits + '&address=' + address; - // logTitle = - // this._settings.currencyCode + - // ' EthScannerProcessor.getTransactions etherscan deposits'; - // transactions = await this._getFromEtherscan( - // address, - // link, - // logTitle, - // false, - // transactions - // ); - // } - // - // if (this._useInternal && this._etherscanApiPathInternal) { - // link = this._etherscanApiPathInternal + '&address=' + address; - // logTitle = - // this._settings.currencyCode + - // ' EthScannerProcessor.getTransactions etherscan forInternal'; - // transactions = await this._getFromEtherscan( - // address, - // link, - // logTitle, - // true, - // transactions - // ); - // } - // } - // if (!transactions) { - // return []; - // } - // const reformatted = []; - // for (const key in transactions) { - // reformatted.push(transactions[key]); - // } - // return reformatted; - // } - - // async _getFromOklink(address, key, logTitle, transactions = {}) { - // const link = - // 'https://www.oklink.com/api/v5/explorer/address/transaction-list?chainShortName=ethw&address=' + - // address; - // const tmp = await AirDAOAxios.getWithHeaders(link, { - // 'Ok-Access-Key': key - // }); - // if ( - // typeof tmp?.data?.data === 'undefined' || - // typeof tmp?.data?.data[0] === 'undefined' || - // typeof tmp?.data?.data[0]?.transactionLists === 'undefined' - // ) { - // return []; - // } - // await AirDAOCryptoLog.log(logTitle + ' started ', link); - // const list = tmp.data.data[0].transactionLists; - // for (const tx of list) { - // const transaction = await this._unifyTransactionOklink(address, tx); - // if (transaction) { - // transactions[transaction.transactionHash] = transaction; - // } - // } - // await AirDAOCryptoLog.log(logTitle + ' finished ' + address); - // return transactions; - // } - - // async _getFromEtherscan( - // address, - // link, - // logTitle, - // isInternal, - // transactions = {} - // ) { - // await AirDAOCryptoLog.log( - // logTitle + ' started ' + JSON.stringify(isInternal), - // link - // ); - // const tmp = await AirDAOAxios.getWithoutBraking(link); - // if ( - // !tmp || - // typeof tmp.data === 'undefined' || - // !tmp.data || - // typeof tmp.data.result === 'undefined' - // ) { - // return transactions; - // } - // if (typeof tmp.data.result === 'string') { - // if (tmp.data.result.indexOf('API Key') === -1) { - // throw new Error( - // 'Undefined txs etherscan ' + link + ' ' + tmp.data.result - // ); - // } else { - // return transactions; - // } - // } - // - // transactions = await this._unifyTransactions( - // address, - // tmp.data.result, - // isInternal, - // false, - // transactions - // ); - // await AirDAOCryptoLog.log(logTitle + ' finished ' + address); - // return transactions; - // } - /** * @param {string} txHash * @return {Promise<[UnifiedTransaction]>} @@ -532,567 +93,4 @@ export default class EthScannerProcessor extends EthBasic { tx.gasUsed = BlocksoftUtils.hexToDecimal(tx.gasUsed); return tx; } - - /** - * @param {string} address - * @param {*} result[] - * @param {boolean} isInternal - * @returns {Promise<[{UnifiedTransaction}]>} - * @private - */ - // async _unifyTransactions( - // _address, - // result, - // isInternal, - // isTrezor = false, - // transactions = {} - // ) { - // if (!result) { - // return transactions; - // } - // const address = _address.toLowerCase(); - // let tx; - // let maxNonce = -1; - // let maxSuccessNonce = -1; - // - // const notBroadcasted = await EthRawDS.getForAddress({ - // address, - // currencyCode: this._settings.currencyCode - // }); - // for (tx of result) { - // try { - // let transaction; - // const key = typeof tx.hash !== 'undefined' ? tx.hash : tx.txid; - // if (typeof transactions[key] !== 'undefined') { - // continue; - // } - // if (isTrezor) { - // transaction = await this._unifyTransactionTrezor( - // address, - // tx, - // isInternal - // ); - // } else { - // transaction = await this._unifyTransaction(address, tx, isInternal); - // } - // if (transaction) { - // transactions[key] = transaction; - // if ( - // typeof transaction.transactionJson !== 'undefined' && - // typeof transaction.transactionJson.feeType === 'undefined' && - // (transaction.transactionDirection === 'outcome' || - // transaction.transactionDirection === 'self') && - // typeof transaction.transactionJson.nonce !== 'undefined' - // ) { - // const uniqueFrom = - // address.toLowerCase() + '_' + transaction.transactionJson.nonce; - // if ( - // notBroadcasted && - // typeof notBroadcasted[uniqueFrom] !== 'undefined' && - // transaction.transactionStatus !== 'new' - // ) { - // EthRawDS.cleanRaw({ - // address, - // transactionUnique: uniqueFrom, - // currencyCode: this._settings.currencyCode - // }); - // } - // if (transaction.transactionJson.nonce * 1 > maxNonce) { - // maxNonce = transaction.transactionJson.nonce * 1; - // } - // if ( - // transaction.transactionStatus === 'success' || - // transaction.transactionStatus === 'confirming' - // ) { - // if (transaction.transactionJson.nonce * 1 > maxSuccessNonce) { - // maxSuccessNonce = transaction.transactionJson.nonce * 1; - // } - // } - // } - // } - // } catch (e) { - // AirDAOCryptoLog.err( - // this._settings.currencyCode + - // ' EthScannerProcessor._unifyTransaction error ' + - // e.message + - // ' on ' + - // (isTrezor ? 'Trezor' : 'usual') + - // ' tx ' + - // JSON.stringify(tx) - // ); - // } - // } - // - // if (maxNonce > -1) { - // await EthTmpDS.saveNonce( - // this._mainCurrencyCode, - // address, - // 'maxScanned', - // maxNonce - // ); - // } - // - // if (maxSuccessNonce > -1) { - // await EthTmpDS.saveNonce( - // this._mainCurrencyCode, - // address, - // 'maxSuccess', - // maxSuccessNonce - // ); - // } - // - // return transactions; - // } - - /** - * @param {string} address - * @param {Object} transaction - * @param {string} transaction.txid: "0xdbbce8ace9ecfa2bcd2a5ff54590a9f3b9c445c1111f1b9404ec33ef2314a864" - * @param {string} transaction.vin[].addresses[] - * @param {string} transaction.vin[].isAddress - * @param {string} transaction.vout[].addresses[] - * @param {string} transaction.vout[].isAddress - * @param {string} transaction.blockHash: "0xf31e629dea39a96da1ff977fc991552c5f131c4b544a87fbea0533e985ce7e69" - * @param {string} transaction.blockHeight: 9409918 - * @param {string} transaction.confirmations: 78610 - * @param {string} transaction.blockTime: 1580737403 - * @param {string} transaction.value: "12559200000000000" - * @param {string} transaction.fees: "69854400000000" - * @param {string} transaction.ethereumSpecific.status 1 - * @param {string} transaction.ethereumSpecific.nonce 42458 - * @param {string} transaction.ethereumSpecific.gasLimit 150000 - * @param {string} transaction.ethereumSpecific.gasUsed 21000 - * @param {string} transaction.ethereumSpecific.gasPrice "3326400000" - * @param {array} transaction.tokenTransfers - * @param {string} transaction.tokenTransfers[].type "ERC20" - * @param {string} transaction.tokenTransfers[].from "0x8b661361be29e688dda65b323526ad536c8b3997" - * @param {string} transaction.tokenTransfers[].to "0xa00ed7686c380740fe2adb141136c217b90c5ca5" - * @param {string} transaction.tokenTransfers[].token "0xdac17f958d2ee523a2206206994597c13d831ec7" - * @param {string} transaction.tokenTransfers[].name "Tether USD" - * @param {string} transaction.tokenTransfers[].symbol "USDT" - * @param {string} transaction.tokenTransfers[].decimals 6 - * @param {string} transaction.tokenTransfers[].value "1000000" - * @private - */ - // async _unifyTransactionTrezor(_address, transaction, isInternal = false) { - // let fromAddress = ''; - // const address = _address.toLowerCase(); - // if ( - // typeof transaction.vin[0] !== 'undefined' && - // transaction.vin[0].addresses && - // typeof transaction.vin[0].addresses[0] !== 'undefined' - // ) { - // fromAddress = transaction.vin[0].addresses[0].toLowerCase(); - // } - // let toAddress = ''; - // if ( - // typeof transaction.vout[0] !== 'undefined' && - // transaction.vout[0].addresses && - // typeof transaction.vout[0].addresses[0] !== 'undefined' - // ) { - // toAddress = transaction.vout[0].addresses[0].toLowerCase(); - // } - // let amount = transaction.value; - // - // let nonce = transaction.ethereumSpecific.nonce; - // if (nonce * 1 === 0) { - // nonce = 0; - // } - // const additional = { - // nonce, - // gas: transaction.ethereumSpecific.gasLimit || '', - // gasPrice: transaction.ethereumSpecific.gasPrice || '', - // gasUsed: transaction.ethereumSpecific.gasUsed || '' - // }; - // let fee = transaction.fees || 0; - // let feeCurrencyCode = this._mainCurrencyCode; - // - // if (this._tokenAddress) { - // let failToken = false; - // if (typeof transaction.tokenTransfers === 'undefined') { - // if (this._tokenAddress === toAddress) { - // failToken = true; - // } else { - // return false; - // } - // } - // if (!failToken) { - // let tmp; - // let found = false; - // amount = new BlocksoftBN(0); - // for (tmp of transaction.tokenTransfers) { - // if (tmp.token.toLowerCase() === this._tokenAddress.toLowerCase()) { - // tmp.from = tmp.from.toLowerCase(); - // tmp.to = tmp.to.toLowerCase(); - // if (tmp.to !== address && tmp.from !== address) { - // continue; - // } - // if (tmp.to === address) { - // fromAddress = tmp.from; - // amount.add(tmp.value); - // } else if (tmp.from === address) { - // if ( - // this._delegateAddress && - // tmp.to.toLowerCase() === this._delegateAddress.toLowerCase() - // ) { - // fee = tmp.value; - // additional.feeType = 'DELEGATE'; - // feeCurrencyCode = this._settings.currencyCode || 'DELEGATE'; - // } else { - // toAddress = tmp.to; - // amount.diff(tmp.value); - // } - // } - // found = true; - // } - // } - // amount = amount.get(); - // if (amount < 0) { - // amount = -1 * amount; - // fromAddress = address; - // } else { - // toAddress = address; - // } - // if (!found) { - // return false; - // } - // } - // } - // - // if (typeof transaction.blockTime === 'undefined') { - // throw new Error( - // ' no transaction.blockTime error transaction data ' + - // JSON.stringify(transaction) - // ); - // } - // let formattedTime = transaction.blockTime; - // try { - // formattedTime = BlocksoftUtils.toDate(transaction.blockTime); - // } catch (e) { - // e.message += - // ' timestamp error transaction data ' + JSON.stringify(transaction); - // throw e; - // } - // - // let blockHash = false; - // const confirmations = transaction.confirmations; - // try { - // CACHE_BLOCK_NUMBER_TO_HASH[this._mainCurrencyCode][ - // transaction.blockHeight - // ] = transaction.blockHash; - // if (typeof transaction.blockHash !== 'undefined') { - // blockHash = transaction.blockHash; - // } - // if ( - // confirmations > 0 && - // transaction.blockHeight > - // CACHE_GET_MAX_BLOCK[this._mainCurrencyCode].max_block_number - // ) { - // CACHE_GET_MAX_BLOCK[this._mainCurrencyCode].max_block_number = - // transaction.blockHeight; - // CACHE_GET_MAX_BLOCK[this._mainCurrencyCode].confirmations = - // confirmations; - // } - // } catch (e) { - // throw new Error( - // e.message + ' in CACHE_GET_MAX_BLOCK ' + this._mainCurrencyCode - // ); - // } - // let transactionStatus = 'new'; - // - // if (blockHash) { - // if (transaction.ethereumSpecific.status === 1) { - // if (confirmations > this._blocksToConfirm) { - // transactionStatus = 'success'; - // } else if (confirmations > 0) { - // transactionStatus = 'confirming'; - // } - // } else { - // transactionStatus = 'fail'; - // } - // } - // - // const tx = { - // transactionHash: transaction.txid.toLowerCase(), - // blockHash: blockHash || '', - // blockNumber: +transaction.blockHeight, - // blockTime: formattedTime, - // blockConfirmations: confirmations, - // transactionDirection: - // address.toLowerCase() === fromAddress.toLowerCase() - // ? 'outcome' - // : 'income', - // addressFrom: - // address.toLowerCase() === fromAddress.toLowerCase() ? '' : fromAddress, - // addressTo: - // address.toLowerCase() === toAddress.toLowerCase() ? '' : toAddress, - // addressFromBasic: fromAddress.toLowerCase(), - // addressAmount: amount, - // transactionStatus: transactionStatus, - // transactionFee: fee, - // transactionFeeCurrencyCode: feeCurrencyCode, - // contractAddress: '', - // inputValue: '' - // }; - // if (tx.addressFrom === '' && tx.addressTo === '') { - // tx.transactionDirection = 'self'; - // // self zero will not shown if uncomment! tx.addressAmount = 0 - // } - // if (additional) { - // tx.transactionJson = additional; - // } - // return tx; - // } - - /** - * @param {string} address - * @param {Object} transaction - * @param {string} transaction.amount 0.001852572296633876 - * @param {string} transaction.blockHash 0x3661deb1c783baed6b1917a1f58ad460ad9cbaac5977919cfd75019e6a12363a - * @param {string} transaction.challengeStatus - * @param {string} transaction.from 0xf1cff704c6e6ce459e3e1544a9533cccbdad7b99 - * @param {string} transaction.height 5495990 - * @param {string} transaction.isFromContract false - * @param {string} transaction.isToContract false - * @param {string} transaction.l1OriginHash '' - * @param {string} transaction.methodId '' - * @param {string} transaction.state success - * @param {string} transaction.to 0xf1cff704c6e6ce459e3e1544a9533cccbdad7b99 - * @param {string} transaction.tokenContractAddress '' - * @param {string} transaction.tokenId '' - * @param {string} transaction.transactionSymbol ETHW - * @param {string} transaction.transactionTime 1662632042000 - * @param {string} transaction.txFee 0.000231 - * @param {string} transaction.txId 0x3cc404044f96f07bee0af9717d49ce72dba645c5bb5af4846be84dafa68127b1 - * @return {UnifiedTransaction} - * @protected - */ - // async _unifyTransactionOklink(_address, transaction) { - // if (typeof transaction.transactionTime === 'undefined') { - // throw new Error( - // ' no transaction.timeStamp error transaction data ' + - // JSON.stringify(transaction) - // ); - // } - // - // const address = _address.toLowerCase(); - // const formattedTime = transaction.transactionTime; - // - // let transactionStatus = 'new'; - // let confirmations = 0; - // if (transaction.state === 'success') { - // const diff = new Date().getTime() - transaction.transactionTime; - // confirmations = Math.round(diff / 60000); - // if (confirmations > 120) { - // transactionStatus = 'success'; - // } else { - // transactionStatus = 'confirming'; - // } - // } else if (transaction.state === 'fail') { - // transactionStatus = 'fail'; - // } else if (transaction.state === 'pending') { - // transactionStatus = 'confirming'; - // } - // - // const tx = { - // transactionHash: transaction.txId.toLowerCase() + '_1', - // blockHash: transaction.blockHash, - // blockNumber: +transaction.height, - // blockTime: formattedTime, - // blockConfirmations: confirmations, - // transactionDirection: - // address.toLowerCase() === transaction.from.toLowerCase() - // ? 'outcome' - // : 'income', - // addressFrom: - // address.toLowerCase() === transaction.from.toLowerCase() - // ? '' - // : transaction.from, - // addressFromBasic: transaction.from.toLowerCase(), - // addressTo: - // address.toLowerCase() === transaction.to.toLowerCase() - // ? '' - // : transaction.to, - // addressAmount: BlocksoftPrettyNumbers.setCurrencyCode('ETH').makeUnPretty( - // transaction.amount - // ), - // transactionStatus: transactionStatus, - // contractAddress: '', - // inputValue: '', - // transactionFee: BlocksoftPrettyNumbers.setCurrencyCode( - // 'ETH' - // ).makeUnPretty(transaction.txFee) - // }; - // if (tx.addressFrom === '' && tx.addressTo === '') { - // tx.transactionDirection = 'self'; - // tx.addressAmount = 0; - // } - // return tx; - // } - - /** - * @param {string} address - * @param {Object} transaction - * @param {string} transaction.blockNumber 4673230 - * @param {string} transaction.timeStamp 1512376529 - * @param {string} transaction.hash - * @param {string} transaction.nonce - * @param {string} transaction.blockHash - * @param {string} transaction.transactionIndex - * @param {string} transaction.from - * @param {string} transaction.to - * @param {string} transaction.value - * @param {string} transaction.gas - * @param {string} transaction.gasPrice - * @param {string} transaction.isError - * @param {string} transaction.txreceipt_status - * @param {string} transaction.input - * @param {string} transaction.type - * @param {string} transaction.contractAddress - * @param {string} transaction.cumulativeGasUsed - * @param {string} transaction.gasUsed - * @param {string} transaction.confirmations - * @param {boolean} isInternal - * @return {UnifiedTransaction} - * @protected - */ - // async _unifyTransaction(_address: string, transaction, isInternal = false) { - // if (typeof transaction.timeStamp === 'undefined') { - // throw new Error( - // ' no transaction.timeStamp error transaction data ' + - // JSON.stringify(transaction) - // ); - // } - // - // const address = _address.toLowerCase(); - // let formattedTime = transaction.timeStamp; - // try { - // formattedTime = BlocksoftUtils.toDate(transaction.timeStamp); - // } catch (e) { - // console.log('no timestamp2'); - // e.message += - // ' timestamp error transaction data ' + JSON.stringify(transaction); - // throw e; - // } - // - // let addressAmount = transaction.value; - // if (typeof transaction.L1TxOrigin !== 'undefined') { - // if (transaction.from === '0x0000000000000000000000000000000000000000') { - // transaction.from = 'ETH: ' + transaction.L1TxOrigin; - // } - // CACHE_BLOCK_NUMBER_TO_HASH[transaction.blockNumber] = - // transaction.blockHash; - // addressAmount = transaction.tokenValue; - // transaction.confirmations = 100; - // } else if (isInternal) { - // if (transaction.contractAddress !== '') { - // return false; - // } - // if (transaction.type !== 'call') { - // return false; - // } - // - // if ( - // typeof CACHE_BLOCK_NUMBER_TO_HASH[this._mainCurrencyCode][ - // transaction.blockNumber - // ] === 'undefined' - // ) { - // const data = await this._web3.eth.getTransaction(transaction.hash); - // CACHE_BLOCK_NUMBER_TO_HASH[this._mainCurrencyCode][ - // transaction.blockNumber - // ] = data?.blockHash; - // } - // transaction.blockHash = - // CACHE_BLOCK_NUMBER_TO_HASH[this._mainCurrencyCode][ - // transaction.blockNumber - // ] || transaction.blockNumber; - // // noinspection PointlessArithmeticExpressionJS - // transaction.confirmations = - // CACHE_GET_MAX_BLOCK[this._mainCurrencyCode].max_block_number - - // transaction.blockNumber + - // 1 * CACHE_GET_MAX_BLOCK[this._mainCurrencyCode].confirmations; - // } else { - // CACHE_BLOCK_NUMBER_TO_HASH[transaction.blockNumber] = - // transaction.blockHash; - // } - // - // const confirmations = transaction.confirmations; - // if ( - // confirmations > 0 && - // transaction.blockNumber > - // CACHE_GET_MAX_BLOCK[this._mainCurrencyCode].max_block_number - // ) { - // CACHE_GET_MAX_BLOCK[this._mainCurrencyCode].max_block_number = - // transaction.blockNumber; - // CACHE_GET_MAX_BLOCK[this._mainCurrencyCode].confirmations = confirmations; - // } - // let transactionStatus = 'new'; - // if ( - // typeof transaction.txreceipt_status === 'undefined' || - // transaction.txreceipt_status === '1' - // ) { - // if (confirmations > this._blocksToConfirm) { - // transactionStatus = 'success'; - // } else if (confirmations > 0) { - // transactionStatus = 'confirming'; - // } - // } else if (transaction.isError !== '0') { - // transactionStatus = 'fail'; - // } - // // if (isInternal) { - // // transactionStatus = 'internal_' + transactionStatus - // // } - // let contractAddress = false; - // if (typeof transaction.contractAddress !== 'undefined') { - // contractAddress = transaction.contractAddress.toLowerCase(); - // } - // const tx = { - // transactionHash: transaction.hash.toLowerCase(), - // blockHash: transaction.blockHash, - // blockNumber: +transaction.blockNumber, - // blockTime: formattedTime, - // blockConfirmations: confirmations, - // transactionDirection: - // address.toLowerCase() === transaction.from.toLowerCase() - // ? 'outcome' - // : 'income', - // addressFrom: - // address.toLowerCase() === transaction.from.toLowerCase() - // ? '' - // : transaction.from, - // addressFromBasic: transaction.from.toLowerCase(), - // addressTo: - // address.toLowerCase() === transaction.to.toLowerCase() - // ? '' - // : transaction.to, - // addressAmount, - // transactionStatus: transactionStatus, - // contractAddress, - // inputValue: transaction.input - // }; - // let nonce = transaction.nonce; - // if (nonce * 1 === 0) { - // nonce = 0; - // } - // if (!isInternal) { - // const additional = { - // nonce, - // gas: transaction.gas, - // gasPrice: transaction.gasPrice, - // cumulativeGasUsed: transaction.cumulativeGasUsed, - // gasUsed: transaction.gasUsed, - // transactionIndex: transaction.transactionIndex - // }; - // tx.transactionJson = additional; - // tx.transactionFee = BlocksoftUtils.mul( - // transaction.gasUsed, - // transaction.gasPrice - // ).toString(); - // } - // if (tx.addressFrom === '' && tx.addressTo === '') { - // tx.transactionDirection = 'self'; - // tx.addressAmount = 0; - // } - // return tx; - // } } diff --git a/crypto/blockchains/eth/EthScannerProcessorErc20.ts b/crypto/blockchains/eth/EthScannerProcessorErc20.ts index 4cdcf7bdd..18e2c22d0 100644 --- a/crypto/blockchains/eth/EthScannerProcessorErc20.ts +++ b/crypto/blockchains/eth/EthScannerProcessorErc20.ts @@ -1,7 +1,6 @@ /** * @version 0.5 */ -// import AirDAOCryptoLog from '../../common/AirDAOCryptoLog'; import EthScannerProcessor from './EthScannerProcessor'; import abi from './ext/erc20'; import { Contract } from '@fioprotocol/fiojs/dist/chain-serialize'; @@ -11,7 +10,6 @@ export default class EthScannerProcessorErc20 extends EthScannerProcessor { * @type {boolean} * @private */ - // _useInternal = false; private _token: Contract; constructor(settings: { @@ -38,84 +36,4 @@ export default class EthScannerProcessorErc20 extends EthScannerProcessor { this._etherscanApiPath = `https://${tmp[2]}/api?module=account&action=tokentx&sort=desc&contractaddress=${settings.tokenAddress}&apikey=YourApiKeyToken`; } } - - /** - * @param {string} address - * @return {Promise<{balance, unconfirmed, provider}>} - */ - // async getBalanceBlockchain(address: string) { - // AirDAOCryptoLog.log( - // this._settings.currencyCode + - // ' EthScannerProcessorErc20.getBalance started ' + - // address - // ); - // if (this.checkWeb3CurrentServerUpdated()) { - // this._token = new this._web3.eth.Contract( - // abi.ERC20, - // this._settings.tokenAddress - // ); - // } - // - // // noinspection JSUnresolvedVariable - // try { - // let balance = 0; - // let provider = ''; - // let time = 0; - // - // if (this._trezorServerCode) { - // const res = await this._get(address); - // if (!res || typeof res.data === 'undefined') return false; - // AirDAOCryptoLog.log( - // this._settings.currencyCode + - // ' EthScannerProcessorErc20.getBalance loaded from ' + - // res.provider + - // ' ' + - // res.time - // ); - // const data = res.data; - // - // if ( - // data && - // this._tokenAddress && - // typeof data.formattedTokens[this._tokenAddress] !== 'undefined' && - // typeof typeof data.formattedTokens[this._tokenAddress].balance !== - // 'undefined' - // ) { - // balance = data.formattedTokens[this._tokenAddress].balance; - // if (balance === []) return false; - // provider = res.provider; - // time = res.time; - // return { - // balance, - // unconfirmed: 0, - // provider, - // time, - // balanceScanBlock: res.data.nonce - // }; - // } - // } - // balance = await this._token.methods.balanceOf(address).call(); - // AirDAOCryptoLog.log( - // this._settings.currencyCode + - // ' EthScannerProcessorErc20.getBalance ' + - // address + - // ' result ' + - // JSON.stringify(balance) - // ); - // if (balance === []) return false; - // - // provider = 'web3'; - // time = 'now()'; - // return { balance, unconfirmed: 0, provider, time }; - // } catch (e: any) { - // AirDAOCryptoLog.log( - // this._settings.currencyCode + - // ' EthScannerProcessorErc20.getBalance ' + - // address + - // ' error ' + - // e.message - // ); - // return false; - // } - // } } diff --git a/crypto/blockchains/eth/stores/EthRawDS.ts b/crypto/blockchains/eth/stores/EthRawDS.ts index 4b07e21f6..f27d16083 100644 --- a/crypto/blockchains/eth/stores/EthRawDS.ts +++ b/crypto/blockchains/eth/stores/EthRawDS.ts @@ -3,12 +3,9 @@ * @author Javid * @version 0.32 */ -// import BlocksoftExternalSettings from '../../../common/AirDAOExternalSettings'; -// import AirDAOAxios from '../../../common/AirDAOAxios'; import AirDAOCryptoLog from '../../../common/AirDAOCryptoLog'; import { DatabaseTable } from '@appTypes'; import { Database } from '@database'; -// import { TransactionRawDBModel } from '@database/models/transactions-raw'; const tableName = DatabaseTable.TransactionRaw; @@ -19,261 +16,6 @@ class EthRawDS { */ _currencyCode = 'ETH'; - // _trezorServer = 'none'; - // private _infuraProjectId: any; // TODO fix any - - // async getForAddress(data: { address: any; currencyCode: any; }) { - // try { - // if (typeof data.currencyCode !== 'undefined') { - // this._currencyCode = - // data.currencyCode === 'ETH_ROPSTEN' ? 'ETH_ROPSTEN' : 'ETH'; - // } - // const sql = ` - // SELECT id, - // transaction_unique_key AS transactionUniqueKey, - // transaction_hash AS transactionHash, - // transaction_raw AS transactionRaw, - // transaction_log AS transactionLog, - // broadcast_log AS broadcastLog, - // broadcast_updated AS broadcastUpdated, - // created_at AS transactionCreated, - // is_removed, removed_at - // FROM transactions_raw - // WHERE - // (is_removed=0 OR is_removed IS NULL) - // AND currency_code='${this._currencyCode}' - // AND address='${data.address.toLowerCase()}'`; - // const result = (await Database.unsafeRawQuery( - // tableName, - // sql - // )) as TransactionRawDBModel[]; - // if (!result || !result || result.length === 0) { - // return {}; - // } - // - // const ret: { [key: string]: unknown } = {}; // TODO fix unknown - // - // if (this._trezorServer === 'none') { - // if (this._currencyCode === 'ETH') { - // try { - // this._trezorServer = - // await BlocksoftExternalSettings.getTrezorServer( - // 'ETH_TREZOR_SERVER', - // 'ETH.Broadcast' - // ); - // this._infuraProjectId = BlocksoftExternalSettings.getStatic( - // 'ETH_INFURA_PROJECT_ID', - // 'ETH.Broadcast' - // ); - // } catch (e) { - // throw new Error(e.message + ' inside trezorServer'); - // } - // } else { - // this._trezorServer = await BlocksoftExternalSettings.getTrezorServer( - // 'ETH_ROPSTEN_TREZOR_SERVER', - // 'ETH.Broadcast' - // ); - // } - // } - // - // const now = new Date().getTime(); - // - // for (const row of result) { - // try { - // ret[row.transactionUniqueKey] = row; - // let transactionLog; - // try { - // transactionLog = row.transactionLog - // ? JSON.parse(Database.unEscapeString(row.transactionLog)) - // : row.transactionLog; - // } catch (e) { - // // do nothing - // } - // - // if ( - // transactionLog && - // typeof transactionLog.currencyCode !== 'undefined' && - // (typeof transactionLog.successResult === 'undefined' || - // !transactionLog.successResult) - // ) { - // // const { apiEndpoints } = config.proxy; - // // const baseURL = MarketingEvent.DATA.LOG_TESTER - // // ? apiEndpoints.baseURLTest - // // : apiEndpoints.baseURL; - // // const successProxy = baseURL + '/send/sendtx'; - // // let checkResult = false; - // // try { - // // transactionLog.selectedFee.isRebroadcast = true; - // // checkResult = await AirDAOAxios.post(successProxy, { - // // raw: row.transactionRaw, - // // txRBF: - // // typeof transactionLog.txRBF !== 'undefined' - // // ? transactionLog.txRBF - // // : false, - // // logData: transactionLog, - // // marketingData: MarketingEvent.DATA - // // }); - // // await AirDAOCryptoLog.log( - // // this._currencyCode + ' EthRawDS.send proxy success result', - // // JSON.parse(JSON.stringify(checkResult)) - // // ); - // // } catch (e3) { - // // await AirDAOCryptoLog.log( - // // this._currencyCode + - // // ' EthRawDS.send proxy success error ' + - // // e3.message - // // ); - // // } - // // if (checkResult && typeof checkResult.data !== 'undefined') { - // // transactionLog.successResult = checkResult.data; - // // } - // // await Database.updateModel(tableName, row.id, { - // // transactionLog: Database.escapeString( - // // JSON.stringify(transactionLog) - // // ) - // // }); - // } - // - // let broadcastLog = ''; - // let link = ''; - // let broad; - // const updateObj = { - // broadcastUpdated: now, - // is_removed: '0' - // }; - // if ( - // this._currencyCode === 'ETH' || - // this._currencyCode === 'ETH_ROPSTEN' - // ) { - // link = this._trezorServer + '/api/v2/sendtx/'; - // try { - // broad = await AirDAOAxios.post(link, row.transactionRaw); - // broadcastLog = ' broadcasted ok ' + JSON.stringify(broad.data); - // updateObj.is_removed = '1'; - // updateObj.removed_at = now; - // } catch (err) { - // const e = err as unknown as any; - // if ( - // e.message.indexOf('transaction underpriced') !== -1 || - // e.message.indexOf('already known') !== -1 - // ) { - // updateObj.is_removed = '1'; - // broadcastLog += ' already known'; - // } else { - // updateObj.is_removed = '0'; - // broadcastLog += e.message; - // } - // } - // broadcastLog += ' ' + link + '; '; - // } - // - // if (this._currencyCode === 'ETH') { - // link = - // 'https://api.etherscan.io/api?module=proxy&action=eth_sendRawTransaction&apikey=YourApiKeyToken&hex='; - // let broadcastLog1 = ''; - // try { - // broad = await AirDAOAxios.get(link + row.transactionRaw); - // if (typeof broad.data.error !== 'undefined') { - // throw new Error(JSON.stringify(broad.data.error)); - // } - // broadcastLog1 = ' broadcasted ok ' + JSON.stringify(broad.data); - // updateObj.is_removed += '1'; - // updateObj.removed_at = now; - // } catch (err) { - // const e = err as unknown as any; - // if ( - // e.message.indexOf('transaction underpriced') !== -1 || - // e.message.indexOf('already known') !== -1 - // ) { - // updateObj.is_removed += '1'; - // broadcastLog1 += ' already known'; - // } else { - // updateObj.is_removed += '0'; - // broadcastLog1 += e.message; - // } - // } - // broadcastLog1 += ' ' + link + '; '; - // link = 'https://mainnet.infura.io/v3/' + this._infuraProjectId; - // let broadcastLog2 = ''; - // try { - // broad = await AirDAOAxios.post(link, { - // jsonrpc: '2.0', - // method: 'eth_sendRawTransaction', - // params: [row.transactionRaw], - // id: 1 - // }); - // if (typeof broad.data.error !== 'undefined') { - // throw new Error(JSON.stringify(broad.data.error)); - // } - // broadcastLog2 = ' broadcasted ok ' + JSON.stringify(broad.data); - // updateObj.is_removed += '1'; - // updateObj.removed_at = now; - // } catch (err) { - // const e = err as unknown as any; - // if ( - // e.message.indexOf('transaction underpriced') !== -1 || - // e.message.indexOf('already known') !== -1 - // ) { - // updateObj.is_removed += '1'; - // broadcastLog2 += ' already known'; - // } else { - // updateObj.is_removed += '0'; - // broadcastLog2 += e.message; - // } - // } - // broadcastLog2 += ' ' + link + '; '; - // if (updateObj.is_removed === '111') { - // // do ALL! - // updateObj.is_removed = 1; - // } else { - // updateObj.is_removed = 0; - // } - // - // broadcastLog = - // new Date().toISOString() + - // ' ' + - // broadcastLog + - // ' ' + - // broadcastLog1 + - // ' ' + - // broadcastLog2 + - // ' ' + - // (row.broadcastLog ? row.broadcastLog.substr(0, 1000) : ''); - // } else if (this._currencyCode === 'ETH_ROPSTEN') { - // if (updateObj.is_removed === '1') { - // // do ALL! - // updateObj.is_removed = 1; - // } else { - // updateObj.is_removed = 0; - // } - // - // broadcastLog = - // new Date().toISOString() + - // ' ' + - // broadcastLog + - // ' ' + - // (row.broadcastLog ? row.broadcastLog.substr(0, 1000) : ''); - // } - // - // if ( - // this._currencyCode === 'ETH' || - // this._currencyCode === 'ETH_ROPSTEN' - // ) { - // updateObj.broadcastLog = broadcastLog; - // await Database.updateModel(tableName, row.id, updateObj); - // } - // } catch (err) { - // const e = err as unknown as any; - // throw new Error(e.message + ' inside row ' + row.transactionHash); - // } - // } - // return ret; - // } catch (err) { - // const e = err as unknown as any; - // throw new Error(e.message + ' on EthRawDS.getAddress'); - // } - // } - async cleanRawHash(data: boolean | undefined) { AirDAOCryptoLog.log('EthRawDS cleanRawHash ', data); @@ -287,25 +29,6 @@ class EthRawDS { await Database.unsafeRawQuery(tableName, sql); } - // async cleanRaw(data) { - // AirDAOCryptoLog.log('EthRawDS cleanRaw ', data); - // - // if (typeof data.currencyCode !== 'undefined') { - // this._currencyCode = - // data.currencyCode === 'ETH_ROPSTEN' ? 'ETH_ROPSTEN' : 'ETH'; - // } - // - // const now = new Date().getTime(); - // const sql = `UPDATE ${tableName} - // SET is_removed=1, removed_at = '${now}' - // WHERE - // (is_removed=0 OR is_removed IS NULL) - // AND currency_code='${this._currencyCode}' - // AND address='${data.address.toLowerCase()}' - // AND transaction_unique_key='${data.transactionUnique}'`; - // await Database.unsafeRawQuery(tableName, sql); - // } - async saveRaw(data: { address: any; currencyCode: any; diff --git a/crypto/common/AirDAOAxios.ts b/crypto/common/AirDAOAxios.ts index 5a43eb83d..baa1e769e 100644 --- a/crypto/common/AirDAOAxios.ts +++ b/crypto/common/AirDAOAxios.ts @@ -107,97 +107,6 @@ class AirDAOAxios { return tmp; } - // async postWithHeaders( - // link: string, - // data: any, - // addHeaders: { [key: string]: string }, - // errSend = true, - // timeOut = false - // ) { - // let tmp = false; - // try { - // const headers: { [key: string]: string } = { - // 'upgrade-insecure-requests': '1', - // 'user-agent': - // 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.128 Safari/537.36' - // }; - // const dataPrep = JSON.stringify(data); - // headers['Content-Type'] = 'application/json'; - // headers['Accept'] = 'application/json'; - // for (const key in addHeaders) { - // headers[key] = addHeaders[key]; - // } - // - // const tmpInner = await fetch(link, { - // method: 'POST', - // credentials: 'same-origin', - // mode: 'same-origin', - // redirect: 'follow', - // headers, - // body: dataPrep - // }); - // if ( - // tmpInner.status !== 200 && - // tmpInner.status !== 201 && - // tmpInner.status !== 202 - // ) { - // AirDAOCryptoLog.log( - // 'AirDAOAxios.post fetch result ' + JSON.stringify(tmpInner) - // ); - // } else { - // tmp = { data: await tmpInner.json() }; - // } - // } catch (e) { - // AirDAOCryptoLog.log( - // 'AirDAOAxios.postWithHeaders fetch result error ' + e.message - // ); - // } - // return tmp; - // } - - // async getWithHeaders( - // link: string, - // addHeaders: { [key: string]: string }, - // errSend = true, - // timeOut = false - // ) { - // let tmp = false; - // try { - // const headers: { [key: string]: string } = { - // 'upgrade-insecure-requests': '1', - // 'user-agent': - // 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.128 Safari/537.36' - // }; - // headers['Content-Type'] = 'application/json'; - // headers['Accept'] = 'application/json'; - // for (const key in addHeaders) { - // headers[key] = addHeaders[key]; - // } - // - // const tmpInner = await fetch(link, { - // method: 'GET', - // credentials: 'same-origin', - // mode: 'same-origin', - // redirect: 'follow', - // headers - // }); - // if ( - // tmpInner.status !== 200 && - // tmpInner.status !== 201 && - // tmpInner.status !== 202 - // ) { - // AirDAOCryptoLog.log( - // 'AirDAOAxios.get fetch result ' + JSON.stringify(tmpInner) - // ); - // } else { - // tmp = { data: await tmpInner.json() }; - // } - // } catch (e) { - // // TODO ignore - // } - // return tmp; - // } - async post(link: string, data: any, errSend = true, timeOut = false) { let tmp = false; let doOld = this._isTrustee(link); diff --git a/crypto/common/AirDAOBN.ts b/crypto/common/AirDAOBN.ts index c57ca8717..58c2c4ffe 100644 --- a/crypto/common/AirDAOBN.ts +++ b/crypto/common/AirDAOBN.ts @@ -1,5 +1,4 @@ import { BigNumber } from 'bignumber.js'; -import BlocksoftUtils from './AirDAOUtils'; class AirDAOBN { innerBN = false; @@ -22,89 +21,9 @@ class AirDAOBN { } } - get() { - return this.innerBN.toString(); - } - toString() { return this.innerBN.toString(); } - - lessThanZero() { - return this.innerBN.toString().indexOf('-') === 0; - } - - add(val) { - // console.log('AirDAOBN add ', JSON.stringify(val)) - if ( - typeof val === 'undefined' || - !val || - val.toString() === '0' || - val === 'null' || - val === 'false' - ) { - return this; - } - let val2; - if (typeof val !== 'string' && typeof val !== 'number') { - if (typeof val.innerBN !== 'undefined') { - val2 = val.innerBN; - } else { - throw new Error( - 'AirDAOBN.add unsupported type ' + - typeof val + - ' ' + - JSON.stringify(val) - ); - } - } else { - try { - val = BlocksoftUtils.fromENumber(val); - val2 = BigNumber(val); - } catch (e) { - throw new Error(e.message + ' while AirDAOBN.add transform ' + val); - } - } - try { - this.innerBN = this.innerBN.plus(val2); - } catch (e) { - throw new Error(e.message + ' while AirDAOBN.add ' + val); - } - return this; - } - - diff(val) { - // console.log('AirDAOBN diff ', JSON.stringify(val)) - if (typeof val === 'undefined' || !val || val.toString() === '0') { - return this; - } - let val2; - if (typeof val !== 'string' && typeof val !== 'number') { - if (typeof val.innerBN !== 'undefined') { - val2 = val.innerBN; - } else { - throw new Error( - 'AirDAOBN.diff unsupported type ' + - typeof val + - ' ' + - JSON.stringify(val) - ); - } - } else { - try { - val = BlocksoftUtils.fromENumber(val); - val2 = BigNumber(val); - } catch (e) { - throw new Error(e.message + ' while AirDAOBN.diff transform ' + val); - } - } - try { - this.innerBN = this.innerBN.minus(val2); - } catch (e) { - throw new Error(e.message + ' while AirDAOBN.minus ' + val); - } - return this; - } } export default AirDAOBN; diff --git a/crypto/common/AirDAOCryptoLog.ts b/crypto/common/AirDAOCryptoLog.ts index 4a0b80a08..43d09b1d9 100644 --- a/crypto/common/AirDAOCryptoLog.ts +++ b/crypto/common/AirDAOCryptoLog.ts @@ -2,22 +2,6 @@ * Separated log class for crypto module - could be encoded here later * @version 0.9 */ -// import crashlytics from '@react-native-firebase/crashlytics' - -// import BlocksoftExternalSettings from './BlocksoftExternalSettings' - -// import config from '@app/config/config' -// import { FileSystem } from '@app/services/FileSystem/FileSystem' -// import MarketingEvent from '@app/services/Marketing/MarketingEvent' -// import settingsActions from '@app/appstores/Stores/Settings/SettingsActions' - -// const DEBUG = config.debug.cryptoLogs // set true to see usual logs in console - -// const MAX_MESSAGE = 2000; -// const FULL_MAX_MESSAGE = 20000; - -// const LOGS_TXT = ''; -// const FULL_LOGS_TXT = ''; class AirDAOCryptoLog { private _txtOrObj: string | undefined; @@ -26,79 +10,13 @@ class AirDAOCryptoLog { private _errorObjectOrText: string | undefined; private _errorObject2: string | undefined; private _errorTitle: string | undefined; - constructor() { - // this.FS = new FileSystem({ fileEncoding: 'utf8', fileName: 'CryptoLog', fileExtension: 'txt' }) - // this.DATA = {} - // this.DATA.LOG_VERSION = false - } - - // async _reinitTgMessage(testerMode, obj, msg) { - // // for (const key in obj) { - // // this.DATA[key] = obj[key] - // // } - // // // noinspection JSIgnoredPromiseFromCall - // // await this.FS.checkOverflow() - // } + constructor() {} async log(txtOrObj: string, txtOrObj2 = false, txtOrObj3 = false) { this._txtOrObj = txtOrObj; this._txtOrObj2 = txtOrObj2; this._txtOrObj3 = txtOrObj3; return true; - // if (settingsActions.getSettingStatic('loggingCode') === 'none') { - // return - // } - // let line = new Date().toISOString().replace(/T/, ' ').replace(/\..+/, '') - // let line2 = '' - // if (txtOrObj && typeof txtOrObj !== 'undefined') { - // if (typeof txtOrObj === 'string') { - // line += ' ' + txtOrObj - // } else { - // line += ' ' + JSON.stringify(txtOrObj, null, '\t') - // } - // } - // if (txtOrObj2 && typeof txtOrObj2 !== 'undefined') { - // if (typeof txtOrObj2 === 'string') { - // line += '\n\t\t\t\t\t' + txtOrObj2 - // } else if (txtOrObj2 === {}) { - // line += ' {} ' - // } else if (typeof txtOrObj2.sourceURL === 'undefined') { - // line += '\n\t\t\t\t\t' + JSON.stringify(txtOrObj2, null, '\t\t\t\t\t') - // } - // } - - // if (DEBUG) { - // console.log('CRYPTO ' + line) - // } - - // if (!config.debug.cryptoErrors && config.debug.firebaseLogs) { - // crashlytics().log(line) - // } - // await this.FS.writeLine(line) - - // if (txtOrObj3 && typeof txtOrObj3 !== 'undefined') { - // if (typeof txtOrObj3 === 'string') { - // line2 += '\t\t\t\t\t' + txtOrObj3 - // } else { - // line2 += '\t\t\t\t\t' + JSON.stringify(txtOrObj3, null, '\t\t\t\t\t') - // } - // if (!config.debug.cryptoErrors && config.debug.firebaseLogs) { - // crashlytics().log('\n', line2) - // } - // await this.FS.writeLine(line2) - // } - - // LOGS_TXT = line + line2 + '\n' + LOGS_TXT - // if (LOGS_TXT.length > MAX_MESSAGE) { - // LOGS_TXT = LOGS_TXT.substr(0, MAX_MESSAGE) + '...' - // } - - // FULL_LOGS_TXT = line + line2 + '\n' + FULL_LOGS_TXT - // if (FULL_LOGS_TXT.length > FULL_MAX_MESSAGE) { - // FULL_LOGS_TXT = LOGS_TXT.substr(0, FULL_MAX_MESSAGE) + '...' - // } - - // return true } async err( @@ -109,62 +27,6 @@ class AirDAOCryptoLog { this._errorObjectOrText = errorObjectOrText; this._errorObject2 = errorObject2; this._errorTitle = errorTitle; - // const now = new Date() - // const date = now.toISOString().replace(/T/, ' ').replace(/\..+/, '') - // let line = '' - // if (errorObjectOrText && typeof errorObjectOrText !== 'undefined') { - // if (typeof errorObjectOrText === 'string') { - // line += ' ' + errorObjectOrText - // } else if (typeof errorObjectOrText.code !== 'undefined') { - // line += ' ' + errorObjectOrText.code + ' ' + errorObjectOrText.message - // } else { - // line += ' ' + errorObjectOrText.message - // } - // } - - // if (errorObject2 && typeof errorObject2 !== 'undefined' && errorObject2 !== '' && typeof errorObject2.message !== 'undefined') { - // line += ' ' + errorObject2.message - // } - - // if (config.debug.cryptoErrors || DEBUG) { - // console.log('==========CRPT ' + errorTitle + '==========') - // console.log(date + line) - // if (errorObject2) { - // console.log('error', errorObject2) - // } - // return false - // } - - // await this.log(errorObjectOrText, errorObject2) - - // LOGS_TXT = '\n\n\n\n==========' + errorTitle + '==========\n\n\n\n' + LOGS_TXT - // // noinspection JSUnresolvedFunction - // if (!config.debug.cryptoErrors) { - // crashlytics().log('==========' + errorTitle + '==========') - // } - // // noinspection ES6MissingAwait - // await this.FS.writeLine('==========' + errorTitle + '==========') - - // if (errorObject2 && typeof errorObject2.code !== 'undefined' && errorObject2.code === 'ERROR_USER') { - // return true - // } - - // try { - // await this.FS.writeLine('CRPT_2021_02 ' + line) - // if (!config.debug.cryptoErrors) { - // const e = new Error('CRPT_2021_02 ' + line) - // if (typeof crashlytics().recordError !== 'undefined') { - // crashlytics().recordError(e) - // } else { - // crashlytics().crash() - // } - // MarketingEvent.reinitCrashlytics() - // } - // } catch (firebaseError) { - - // } - - // return true return true; } } diff --git a/crypto/common/AirDAODict.ts b/crypto/common/AirDAODict.ts index 05f04ce71..a6ad1b111 100644 --- a/crypto/common/AirDAODict.ts +++ b/crypto/common/AirDAODict.ts @@ -1,201 +1,11 @@ -// import { NativeModules } from 'react-native'; - import CoinAirDAODict from '@crypto/assets/coinAirDAODict.json'; import { Database } from '@database'; import { DatabaseTable } from '@appTypes'; -// const { RNFastCrypto } = NativeModules; - -const VisibleCodes = [ - 'CASHBACK', - 'NFT', - 'BTC', - 'ETH', - 'TRX', - 'TRX_USDT' // add code here to show on start screen -]; -const Codes = [ - 'CASHBACK', - 'NFT', - 'BTC', - 'ETH', - 'USDT', - 'LTC', - 'ETH_USDT', - 'TRX', - 'TRX_USDT', - 'BNB', - 'BNB_SMART', - 'MATIC', - 'ETH_TRUE_USD', - 'ETH_BNB', - 'ETH_USDC', - 'ETH_PAX', - 'ETH_DAI', - 'FIO' // add code here for autocreation the wallet address with the currency -]; +const VisibleCodes = ['ETH']; +const Codes = ['ETH', 'BNB_SMART']; const Currencies = CoinAirDAODict; -const CurrenciesForTests = { - BTC_SEGWIT: { - currencyName: 'Bitcoin Segwit', - currencyCode: 'BTC_SEGWIT', - currencySymbol: 'BTC', - addressProcessor: 'BTC_SEGWIT', - extendsProcessor: 'BTC', - ratesCurrencyCode: 'BTC', - addressPrefix: 'bc1', - defaultPath: `m/84'/0'/0'/0/0` - }, - BTC_SEGWIT_COMPATIBLE: { - currencyName: 'Bitcoin Compatible Segwit', - currencyCode: 'BTC_SEGWIT_COMPATIBLE', - currencySymbol: 'BTC', - addressProcessor: 'BTC_SEGWIT_COMPATIBLE', - extendsProcessor: 'BTC', - ratesCurrencyCode: 'BTC', - addressPrefix: '3', - defaultPath: `m/49'/0'/0'/0/1` - }, - LTC_SEGWIT: { - currencyName: 'Bitcoin Segwit', - currencyCode: 'LTC_SEGWIT', - currencySymbol: 'LTC', - addressProcessor: 'LTC_SEGWIT', - extendsProcessor: 'LTC', - ratesCurrencyCode: 'LTC', - addressPrefix: 'ltc', - defaultPath: `m/84'/2'/0'/0/0` - }, - ETH_ROPSTEN_KSU_TOKEN: { - currencyName: 'Some ERC-20 Ropsten', - currencyCode: 'ETH_ROPSTEN_KSU_TOKEN', - currencySymbol: 'Some ERC-20 Ropsten', - extendsProcessor: 'ETH_TRUE_USD', - addressCurrencyCode: 'ETH_ROPSTEN', - feesCurrencyCode: 'ETH_ROPSTEN', - network: 'ropsten', - decimals: 6, - tokenAddress: '0xdb30610f156e1d4aefaa9b4423909297ceff64c2', - currencyExplorerLink: 'https:ropsten.etherscan.io/address/', - currencyExplorerTxLink: 'https:ropsten.etherscan.io/tx/' - }, - TRX_PLANET: { - currencyName: 'PLANET (FREE TRX TOKEN)', - currencyCode: 'TRX_PLANET', - currencySymbol: 'PLANET TRX', - extendsProcessor: 'TRX_USDT', - feesCurrencyCode: 'TRX', // pay for tx in other currency, if no - used currencyCode - network: 'trx', // network also used as mark of rate scanning - decimals: 6, - tokenName: '1002742', - currencyExplorerLink: 'https://tronscan.org/#/address/', - currencyExplorerTxLink: 'https://tronscan.org/#/transaction/' - } -}; - -// if (typeof RNFastCrypto === 'undefined') { -// delete Currencies['XMR']; -// } - -// function addAndUnifyCustomCurrency(currencyObject: { -// id: string; -// isHidden: number; -// tokenJSON: string; -// tokenDecimals: string; -// tokenAddress: string; -// tokenType: string; -// currencyName: string; -// currencySymbol: string; -// currencyCode: string; -// }) { -// const tmp = { -// currencyName: currencyObject.currencyName, -// currencyCode: 'CUSTOM_' + currencyObject.currencyCode, -// currencySymbol: currencyObject.currencySymbol, -// ratesCurrencyCode: currencyObject.currencyCode, -// decimals: currencyObject.tokenDecimals, -// currencyType: 'custom' -// }; -// if (currencyObject.tokenType === 'BNB_SMART_20') { -// tmp.currencyCode = 'CUSTOM_BNB_SMART_20_' + currencyObject.currencyCode; -// if (tmp.ratesCurrencyCode.substr(0, 1) === 'B') { -// const subRate = tmp.ratesCurrencyCode.substr(1); -// if (typeof Currencies[subRate] !== 'undefined') { -// tmp.ratesCurrencyCode = subRate; -// } -// } -// tmp.extendsProcessor = 'BNB_SMART_CAKE'; -// tmp.addressUiChecker = 'ETH'; -// tmp.tokenAddress = currencyObject.tokenAddress; -// tmp.tokenBlockchain = 'BNB'; -// tmp.currencyExplorerLink = -// 'https://bscscan.com/token/' + currencyObject.tokenAddress + '?a='; -// } else if (currencyObject.tokenType === 'MATIC_ERC_20') { -// tmp.currencyCode = 'CUSTOM_MATIC_ERC_20_' + currencyObject.currencyCode; -// tmp.extendsProcessor = 'MATIC_USDT'; -// tmp.addressUiChecker = 'ETH'; -// tmp.tokenAddress = currencyObject.tokenAddress; -// tmp.tokenBlockchain = 'MATIC'; -// tmp.currencyExplorerLink = -// 'https://polygonscan.com/token/' + currencyObject.tokenAddress + '?a='; -// } else if (currencyObject.tokenType === 'FTM_ERC_20') { -// tmp.currencyCode = 'CUSTOM_FTM_ERC_20_' + currencyObject.currencyCode; -// tmp.extendsProcessor = 'FTM_USDC'; -// tmp.addressUiChecker = 'ETH'; -// tmp.tokenAddress = currencyObject.tokenAddress; -// tmp.tokenBlockchain = 'FTM'; -// tmp.currencyExplorerLink = -// 'https://ftmscan.com/token/' + currencyObject.tokenAddress + '?a='; -// } else if (currencyObject.tokenType === 'VLX_ERC_20') { -// tmp.currencyCode = 'CUSTOM_VLX_ERC_20_' + currencyObject.currencyCode; -// tmp.extendsProcessor = 'VLX_USDT'; -// tmp.addressUiChecker = 'ETH'; -// tmp.tokenAddress = currencyObject.tokenAddress; -// tmp.tokenBlockchain = 'VLX'; -// tmp.currencyExplorerLink = -// 'https://evmexplorer.velas.com/tokens/' + currencyObject.tokenAddress; -// } else if (currencyObject.tokenType === 'ONE_ERC_20') { -// tmp.currencyCode = 'CUSTOM_ONE_ERC_20_' + currencyObject.currencyCode; -// tmp.extendsProcessor = 'ONE_USDC'; -// tmp.addressUiChecker = 'ETH'; -// tmp.tokenAddress = currencyObject.tokenAddress; -// tmp.tokenBlockchain = 'ONE'; -// tmp.currencyExplorerLink = -// 'https://explorer.harmony.one/address/' + currencyObject.tokenAddress; -// } else if (currencyObject.tokenType === 'SOL') { -// tmp.currencyCode = 'CUSTOM_SOL_' + currencyObject.currencyCode; -// tmp.extendsProcessor = 'SOL_RAY'; -// tmp.addressUiChecker = 'SOL'; -// tmp.tokenAddress = currencyObject.tokenAddress; -// tmp.tokenBlockchain = 'SOLANA'; -// } else if (currencyObject.tokenType === 'ETH_ERC_20') { -// tmp.extendsProcessor = 'ETH_TRUE_USD'; -// tmp.addressUiChecker = 'ETH'; -// tmp.tokenAddress = currencyObject.tokenAddress; -// tmp.tokenBlockchain = 'ETHEREUM'; -// tmp.currencyExplorerLink = -// 'https://etherscan.io/token/' + currencyObject.tokenAddress + '?a='; -// } else if (currencyObject.tokenType === 'TRX') { -// tmp.currencyCode = 'CUSTOM_TRX_' + currencyObject.currencyCode; -// tmp.extendsProcessor = 'TRX_USDT'; -// tmp.addressUiChecker = 'TRX'; -// tmp.currencyIcon = 'TRX'; -// tmp.tokenName = currencyObject.tokenAddress; -// tmp.tokenBlockchain = 'TRON'; -// tmp.currencyExplorerLink = 'https://tronscan.org/#/address/'; -// tmp.currencyExplorerTxLink = 'https://tronscan.org/#/transaction/'; -// if (tmp.tokenName.substr(0, 1) !== 'T') { -// this.skipParentBalanceCheck = true; -// } -// } else { -// return false; -// } -// -// Currencies[tmp.currencyCode] = tmp; -// return tmp; -// } - const ALL_SETTINGS = {}; function getCurrencyAllSettings(currencyCodeOrObject, source = '') { @@ -225,10 +35,7 @@ function getCurrencyAllSettings(currencyCodeOrObject, source = '') { return ALL_SETTINGS[currencyCode]; } - let settings = Currencies[currencyCode]; - if (!settings) { - settings = CurrenciesForTests[currencyCode]; - } + const settings = Currencies[currencyCode]; if (!settings) { throw new Error( 'Currency code not found in dict ' + @@ -257,7 +64,5 @@ export default { VisibleCodes, Codes, Currencies, - CurrenciesForTests, getCurrencyAllSettings - // addAndUnifyCustomCurrency }; diff --git a/crypto/common/AirDAODictTypes.ts b/crypto/common/AirDAODictTypes.ts index c4a71a78a..73176d33e 100644 --- a/crypto/common/AirDAODictTypes.ts +++ b/crypto/common/AirDAODictTypes.ts @@ -3,57 +3,14 @@ * @author Ksu * @version 0.20 */ +// tslint:disable-next-line:no-namespace export namespace AirDAODictTypes { export enum Code { AMB = 'AMB', - // BTC = 'BTC', - // BTC_SEGWIT = 'BTC_SEGWIT', - // BTC_SEGWIT_COMPATIBLE = 'BTC_SEGWIT_COMPATIBLE', ETH_ERC_20 = 'ETH_ERC_20', - // USDT = 'USDT', - // LTC = 'LTC', - ETH = 'ETH' - // ETH_USDT = 'ETH_USDT', - // ETH_UAX = 'ETH_UAX', - // ETH_TRUE_USD = 'ETH_TRUE_USD', - // ETH_BNB = 'ETH_BNB', - // ETH_USDC = 'ETH_USDC', - // ETH_PAX = 'ETH_PAX', - // ETH_DAI = 'ETH_DAI', - // ETH_DAIM = 'ETH_DAIM', - // TRX = 'TRX', - // XMR = 'XMR', - // XLM = 'XLM', - // BTC_TEST = 'BTC_TEST', - // BCH = 'BCH', - // BSV = 'BSV', - // BTG = 'BTG', - // DOGE = 'DOGE', - // XVG = 'XVG', - // XRP = 'XRP', - // ETH_ROPSTEN = 'ETH_ROPSTEN', - // ETH_RINKEBY = 'ETH_RINKEBY', - // TRX_USDT = 'TRX_USDT', - // TRX_BTT = 'TRX_BTT', - // ETH_OKB = 'ETH_OKB', - // ETH_MKR = 'ETH_MKR', - // ETH_KNC = 'ETH_KNC', - // ETH_COMP = 'ETH_COMP', - // ETH_BAL = 'ETH_BAL', - // ETH_LEND = 'ETH_LEND', - // ETH_BNT = 'ETH_BNT', - // ETH_SOUL = 'ETH_SOUL', - // ETH_ONE = 'ETH_ONE', - // FIO = 'FIO', - // BNB = 'BNB', - // BNB_SMART = 'BNB_SMART', - // BNB_SMART_20 = 'BNB_SMART_20', - // ETC = 'ETC', - // VET = 'VET', - // SOL = 'SOL', - // WAVES = 'WAVES', - // ASH = 'ASH', - // METIS = 'METIS', - // SOL_SPL = 'SOL_SPL' + ETH = 'ETH', + BNB_SMART = 'BNB_SMART', + BNB_SMART_20 = 'BNB_SMART_20', + ETC = 'ETC' } } diff --git a/crypto/common/AirDAOExplorerDict.ts b/crypto/common/AirDAOExplorerDict.ts deleted file mode 100644 index b6835db58..000000000 --- a/crypto/common/AirDAOExplorerDict.ts +++ /dev/null @@ -1,36 +0,0 @@ -const CurrenciesExplorer = { - // BTC: [ - // { - // currencyCode: 'BTC', - // tokenBlockchain: 'BITCOIN', - // explorerName: 'Blockchair', - // explorerLink: 'https://blockchair.com/bitcoin/address/', - // explorerTxLink: 'https://blockchair.com/bitcoin/transaction/' - // }, - // { - // currencyCode: 'BTC', - // tokenBlockchain: 'BITCOIN', - // explorerName: 'Blockchain.com', - // explorerLink: 'https://www.blockchain.com/btc/address/', - // explorerTxLink: 'https://www.blockchain.com/btc/tx/' - // } - // ], - ETH: [ - { - currencyCode: 'ETH', - tokenBlockchain: 'ETHEREUM', - explorerName: 'Blockchair', - explorerLink: 'https://blockchair.com/ethereum/address/', - explorerTxLink: 'https://blockchair.com/ethereum/transaction/' - }, - { - currencyCode: 'ETH', - tokenBlockchain: 'ETHEREUM', - explorerName: 'Etherscan', - explorerLink: 'https://etherscan.io/address/', - explorerTxLink: 'https://etherscan.io/tx/' - } - ] -}; - -export default CurrenciesExplorer; diff --git a/crypto/common/AirDAOExternalSettings.ts b/crypto/common/AirDAOExternalSettings.ts index 818b47adc..f067a6b36 100644 --- a/crypto/common/AirDAOExternalSettings.ts +++ b/crypto/common/AirDAOExternalSettings.ts @@ -1,6 +1,5 @@ import AirDAOAxios from './AirDAOAxios'; import AirDAOCryptoLog from './AirDAOCryptoLog'; -// import ApiProxy from '../../app/services/Api/ApiProxy'; import config from '@constants/config'; const MAX_CACHE_VALID_TIME = 6000000; // 100 minutes @@ -25,81 +24,20 @@ interface Cache { const TREZOR_SERVERS: TrezorServers = {}; const CACHE: Cache = { - // TRX_VOTE_BEST: 'TV9QitxEJ3pdiAUAfJ2QuPxLKp9qTTR3og', - // TRX_SEND_LINK: 'http://trx.trusteeglobal.com:8090', // http://trx.trusteeglobal.com:8090/wallet - // TRX_SOLIDITY_NODE: 'http://trx.trusteeglobal.com:8091', // http://trx.trusteeglobal.com:8091/walletsolidity - // TRX_USE_TRONSCAN: 0, - // SOL_VOTE_BEST: 'CertusDeBmqN8ZawdkxK5kFGMwBXdudvWHYwtNgNhvLu', ETH_LONG_QUERY: 1, ETH_SMALL_FEE_FORCE_QUIT: 1, ETH_BLOCKED_BALANCE_FORCE_QUIT: 1, ETH_LONG_QUERY_FORCE_QUIT: 1, ETH_GAS_LIMIT: 120000, ETH_GAS_LIMIT_FORCE_QUIT: 0, - // BCH: { 2: 2, 6: 1, 12: 1 }, - // BSV: { 2: 2, 6: 1, 12: 1 }, - // BTG: { 2: 10, 6: 5, 12: 2 }, - // DOGE: { 2: 800000, 6: 600000, 12: 500000 }, - // DOGE_STATIC: { useStatic: true, speed_blocks_2: 1, feeForAllInputs: 3 }, - // BTC_TEST: { 2: 8, 6: 6, 12: 5 }, - // LTC: { 2: 8, 6: 5, 12: 2 }, - // XVG: { 2: 700, 6: 600, 12: 300 }, - // XVG_SEND_LINK: - // 'https://api.vergecurrency.network/node/api/XVG/mainnet/tx/send', - // XRP_SERVER: 'wss://xrplcluster.com', - // XRP_SCANNER_SERVER: 'https://xrplcluster.com', // https://xrpl.org/public-servers.html - // XRP_SCANNER_TYPE: 'xrpscan', // dataripple - // XRP_MIN: 10, - // XLM_SERVER: 'https://horizon.stellar.org', - // XLM_SERVER_PRICE: 5500, - // XLM_SERVER_PRICE_FORCE: 5500, - // XLM_SEND_LINK: 'https://horizon.stellar.org/transactions', - // BNB_SERVER: 'https://dex.binance.org', - // BNB_SMART_SERVER: 'https://bsc-dataseed1.binance.org:443', - // BNB_SMART_PRICE: 10000000000, - // BNB_GAS_LIMIT: 620000, + BNB_SMART_SERVER: 'https://bsc-dataseed1.binance.org:443', + BNB_SMART_PRICE: 10000000000, ETH_MIN_GAS_ERC20: 73000, ETH_MIN_GAS_LIMIT: 42000, ETH_TESTNET_PRICE: 6710000000, ETH_INFURA: '5e52e85aba6f483398c461c55b639a7b', ETH_INFURA_PROJECT_ID: 'c8b5c2ced3b041a8b55a1719b508ff08', ETH_TREZOR_SERVER: ['https://eth1.trezor.io', 'https://eth2.trezor.io'], - // BTC_TREZOR_SERVER: [ - // 'https://btc1.trezor.io', - // 'https://btc2.trezor.io', - // 'https://btc3.trezor.io', - // 'https://btc4.trezor.io', - // 'https://btc5.trezor.io' - // ], - // LTC_TREZOR_SERVER: [ - // 'https://ltc1.trezor.io', - // 'https://ltc2.trezor.io', - // 'https://ltc3.trezor.io', - // 'https://ltc4.trezor.io', - // 'https://ltc5.trezor.io' - // ], - // BCH_TREZOR_SERVER: [ - // 'https://bch1.trezor.io', - // 'https://bch2.trezor.io', - // 'https://bch3.trezor.io', - // 'https://bch4.trezor.io', - // 'https://bch5.trezor.io' - // ], - // DOGE_TREZOR_SERVER: [ - // 'https://doge1.trezor.io', - // 'https://doge2.trezor.io', - // 'https://doge3.trezor.io', - // 'https://doge4.trezor.io', - // 'https://doge5.trezor.io' - // ], - // BTG_TREZOR_SERVER: [ - // 'https://btg1.trezor.io', - // 'https://btg2.trezor.io', - // 'https://btg3.trezor.io', - // 'https://btg4.trezor.io', - // 'https://btg5.trezor.io' - // ], - // BSV_TREZOR_SERVER: ['https://bsv.trusteeglobal.com'], ETH_ROPSTEN_TREZOR_SERVER: ['https://ac-dev0.net:29136'], ETC_TREZOR_SERVER: ['https://etcblockexplorer.com'], ETC_SERVER: 'https://www.ethercluster.com/etc', @@ -110,86 +48,11 @@ const CACHE: Cache = { AMB_TREZOR_SERVER: ['https://blockbook.ambrosus.io'], AMB_PRICE: 5000000000, AMB_GAS_LIMIT: 620000, - // ONE_SERVER: 'https://api.harmony.one', - // ONE_GAS_LIMIT: 920000, - // ONE_PRICE: 30000000000, - // VLX_SERVER: 'https://evmexplorer.velas.com/rpc', - // VLX_GAS_LIMIT: 620000, - // VLX_PRICE: 3000000000, - // METIS_SERVER: 'https://andromeda.metis.io/?owner=1088', - // METIS_GAS_LIMIT: 620000, - // METIS_PRICE: 40000000000, - // BTTC_SERVER: 'https://rpc.bt.io', - // OPTIMISM_SERVER: 'https://mainnet.optimism.io', - // OPTIMISM_PRICE: 15000000, - // OPTIMISM_GAS_LIMIT: 2320100000, - // OPTIMISM_MIN_GAS_LIMIT: 23201000, - // MATIC_SERVER: 'https://polygon-rpc.com', - // MATIC_PRICE: 1000000000, - // MATIC_GAS_LIMIT: 620000, - // FTM_SERVER: 'https://rpc.ftm.tools', - // FTM_PRICE: 400000000000, - // FTM_GAS_LIMIT: 620000, - // RSK_SERVER: 'https://public-node.rsk.co', - // RSK_PRICE: 5000000000, - // RSK_GAS_LIMIT: 620000, - // SOL_SERVER: 'https://api.mainnet-beta.solana.com', - // SOL_SERVER_2: - // 'https://solana-mainnet.phantom.app/YBPpkkN4g91xDiAnTE9r0RcMkjg0sKUIWvAfoFVJ', - // SOL_PRICE: 5000, - // SOL_PRICE_NEW_SPL: 2044280, - // SOL_TOKENS_LIST: - // 'https://raw.githubusercontent.com/solana-labs/token-list/main/src/tokens/solana.tokenlist.json', - // SOL_VALIDATORS_LIST: - // 'https://raw.githubusercontent.com/trustee-wallet/trusteeWalletAssets/main/blockchains/sol/validators.json', - // WAVES_SERVER: 'https://nodes.wavesnodes.com', - // ASH_SERVER: 'http://51.158.70.89:6555', - // FIO_BASE_URL: 'https://fio.eosphere.io/v1/', - // FIO_HISTORY_URL: 'https://fio.eosphere.io/v1/history/', - // FIO_REGISTRATION_URL: 'https://reg.fioprotocol.io/ref/trustee?publicKey=', minCryptoErrorsVersion: 491, minAppErrorsVersion: 491, - // SUPPORT_BOT: 'https://t.me/trustee_support_bot?start=app', - // SUPPORT_BOT_NAME: '@trustee_support_bot', - // SUPPORT_EMAIL: 'contact@trustee.deals', - // navigationViewV3: 1, - // SOCIAL_LINK_SITE: 'trusteeglobal.com', - // SOCIAL_LINK_TELEGRAM: 'https://t.me/trustee_deals', - // SOCIAL_LINK_TWITTER: 'https://twitter.com/Trustee_Wallet', - // SOCIAL_LINK_FACEBOOK: 'https://facebook.com/Trustee.Wallet/', - // SOCIAL_LINK_INSTAGRAM: 'https://instagram.com/trustee_wallet/', - // SOCIAL_LINK_VK: 'https://vk.com/trustee_wallet', - // SOCIAL_LINK_GITHUB: 'https://github.com/trustee-wallet/trusteeWallet', - // SOCIAL_LINK_FAQ: 'https://trusteeglobal.com/faq/', - // SOCIAL_LINK_YOUTUBE: 'https://www.youtube.com/TrusteeWallet', - // PRIVACY_POLICY: - // 'https://trusteeglobal.com/privacy-policy/?header_footer=none', - // TERMS: 'https://trusteeglobal.com/terms-of-use/?header_footer=none', - // SEND_CHECK_ALMOST_ALL_PERCENT: 0.95, - // SEND_AMOUNT_CHECK: 1, - // TRADE_SEND_AMOUNT_CHECK_FORCE_QUIT: 1, - // ROCKET_CHAT_USE: 0, - // HOW_WORK_CASHBACK_LINK: 'https://trusteeglobal.com/programma-loyalnosti/', - // HOW_WORK_CPA_LINK: 'https://trusteeglobal.com/cpa/', - // TRX_STAKING_LINK: - // 'https://blog.trusteeglobal.com/stejking-trona-i-kak-zarabotat/', - // TRX_SPAM_LIMIT: 40000, - // TRX_BASIC_PRICE_WHEN_NO_BAND: 100000, - // TRX_TRC20_BAND_PER_TX: 350, - // TRX_TRC20_PRICE_PER_BAND: 140, - // TRX_TRC20_ENERGY_PER_TX: 29650, // 14650, - // TRX_TRC20_PRICE_PER_ENERGY: 420, - // TRX_TRC20_MAX_LIMIT: 100000000, - // INVOICE_URL: 'https://trusteeglobal.com/', STAKING_COINS_PERCENT: { - // TRX: 5.06, - // SOL: 7.02, - // VET: 1.63, ETH: 5.1 - // ETH_MATIC: 6.3 } - // DAPPS_IMAGE_LINK: - // 'https://raw.githubusercontent.com/trustee-wallet/trusteeWalletAssets/main/dapps/' }; class AirDAOExternalSettings { diff --git a/crypto/common/AirDAOPrettyNumbers.ts b/crypto/common/AirDAOPrettyNumbers.ts index 1dd9d94bc..a21e09187 100644 --- a/crypto/common/AirDAOPrettyNumbers.ts +++ b/crypto/common/AirDAOPrettyNumbers.ts @@ -32,152 +32,6 @@ class AirDAOPrettyNumbers { return this; } - /** - * @param {string|number} number - * @return {string} - */ - // tslint:disable-next-line:variable-name - // async makePretty(number: string | number): Promise { - // if (this._processorCode === 'USDT') { - // return number.toString(); - // } - // const str = number.toString(); - // if (str.indexOf('.') !== -1 || str.indexOf(',') !== -1) { - // number = str.split('.')[0].toString(); - // } - // if (this._processorCode === 'ETH') { - // return AirDAOUtils.toEther(number); - // } else if (this._processorCode === 'BTC') { - // return AirDAOUtils.toBtc(number); - // } else if ( - // this._processorCode === 'ETH_ERC_20' || - // this._processorCode === 'UNIFIED' - // ) { - // return AirDAOUtils.toUnified(number, this._decimals || 0); - // } - // throw new Error('undefined BlocksoftPrettyNumbers processor to makePretty'); - // } - - // makeCut( - // tmp: string | number, - // size = 5, - // source = false, - // useDict = true - // ): { - // cutted: string | number; - // isSatoshi: boolean; - // justCutted: string | number; - // separated: string; - // separatedForInput: string | false; - // } { - // if ( - // this._decimals && - // this._decimals <= 6 && - // size === 5 && - // this._decimals > 0 && - // useDict - // ) { - // size = this._decimals; - // } - // let cutted: string | number = 0; - // let isSatoshi = false; - // if (typeof tmp === 'undefined' || !tmp) { - // return { - // cutted, - // isSatoshi, - // justCutted: cutted, - // separated: '0', - // separatedForInput: false - // }; - // } - // const splitted = tmp.toString().split('.'); - // const def = '0.' + '0'.repeat(size); - // let firstPart: string | false = false; - // let secondPart: string | false = false; - // if (splitted[0] === '0') { - // if (typeof splitted[1] !== 'undefined' && splitted[1]) { - // cutted = splitted[0] + '.' + splitted[1].substr(0, size).toString(); // Convert to string - // if (cutted === def) { - // cutted = - // splitted[0] + '.' + splitted[1].substr(0, size * 2).toString(); // Convert to string - // const def2 = '0.' + '0'.repeat(size * 2); - // if (cutted !== def2) { - // secondPart = splitted[1].substr(0, size * 2); - // } - // isSatoshi = true; - // } - // } else { - // cutted = '0'; - // } - // } else if (typeof splitted[1] !== 'undefined' && splitted[1]) { - // const second = splitted[1].substr(0, size).toString(); // Convert to string - // if (second !== '0'.repeat(size)) { - // cutted = splitted[0] + '.' + second; - // secondPart = second; - // } else { - // cutted = splitted[0]; - // } - // firstPart = splitted[0] + ''; - // } else { - // cutted = splitted[0]; - // firstPart = splitted[0] + ''; - // } - // let justCutted: string | number = isSatoshi ? '0' : cutted; - // if (justCutted === def) { - // justCutted = '0'; - // } - // - // if (secondPart) { - // for (let i = secondPart.length; i--; i >= 0) { - // if (typeof secondPart[i] === 'undefined' || secondPart[i] === '0') { - // secondPart = secondPart.substr(0, i); - // } else { - // break; - // } - // } - // } - // - // let separated = justCutted; - // let separatedForInput: string | false = false; - // if (firstPart) { - // const len = firstPart.length; - // if (len > 3) { - // separated = ''; - // let j = 0; - // for (let i = len - 1; i >= 0; i--) { - // if (j === 3) { - // separated = ' ' + separated; - // j = 0; - // } - // j++; - // separated = firstPart[i] + separated; - // } - // } else { - // separated = firstPart; - // } - // separatedForInput = separated.toString(); - // if (secondPart) { - // separated += '.' + secondPart; - // } - // } else if (secondPart) { - // separated = '0.' + secondPart; - // separatedForInput = '0'; - // } - // if (!separatedForInput) { - // separatedForInput = justCutted; - // } else if (typeof splitted[1] !== 'undefined') { - // separatedForInput += '.' + splitted[1]; - // } - // - // return { - // cutted, - // isSatoshi, - // justCutted, - // separated: separated.toString(), - // separatedForInput - // }; - // } - /** * @param {string} value * @return {string} diff --git a/crypto/common/AirDAOUtils.ts b/crypto/common/AirDAOUtils.ts index 441563a68..831286737 100644 --- a/crypto/common/AirDAOUtils.ts +++ b/crypto/common/AirDAOUtils.ts @@ -8,24 +8,6 @@ import EtherUnits from 'web3-utils'; import BN from 'bn.js'; class AirDAOUtils { - static cutZeros(val: any): string { - const tmp = val.toString().split('.'); - if (typeof tmp[1] === 'undefined' || !tmp[1]) return tmp[0]; - - let firstNonZero = -1; - let i = tmp[1].length - 1; - do { - const char = tmp[1][i]; - if (char !== '0') { - firstNonZero = i; - } - i--; - } while (firstNonZero === -1 && i >= 0); - const last = tmp[1].substr(0, firstNonZero + 1); - if (!last || last === '') return tmp[0]; - return tmp[0] + '.' + last; - } - static round(val: any): string { const tmp = val.toString().split('.'); return tmp[0].replace(' ', ''); @@ -203,10 +185,6 @@ class AirDAOUtils { return this.fromUnified(val, 8); } - static toBtc(val: any): string { - return this.toUnified(val, 8); - } - static toUnified(val: any, decimals = 8): string { if (typeof val === 'undefined' || val === 'undefined' || !val) { return '0'; @@ -347,27 +325,6 @@ class AirDAOUtils { return newVal; } - static toDate(timeStamp: number, multiply = true): string { - if (timeStamp.toString().indexOf('T') !== -1) { - return timeStamp.toString(); - } else if (timeStamp && timeStamp > 0) { - if (multiply) { - timeStamp = timeStamp * 1000; - } - return new Date(timeStamp).toISOString(); - } else { - return new Date().toISOString(); - } - } - - static hexToUtf(hex: string): string { - return Web3.utils.hexToUtf8(hex); - } - - static utfToHex(str: string): string { - return Web3.utils.utf8ToHex(str); - } - static hexToDecimal(hex: string): bigint | number | string { if (hex.toString().indexOf('0x') === 0) { return Web3.utils.hexToNumber(hex); @@ -395,10 +352,6 @@ class AirDAOUtils { } return str; } - - static hexToDecimalBigger(hex: string): string { - return BigIntXmr.BigInteger(hex).toString(); - } } export default AirDAOUtils; From 3b8fd26e2800944e3dbfbf195617754175afca45 Mon Sep 17 00:00:00 2001 From: illiaa Date: Thu, 24 Aug 2023 11:59:24 +0300 Subject: [PATCH 073/509] fixed navigation error --- src/navigation/stacks/TabsNavigator.tsx | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/navigation/stacks/TabsNavigator.tsx b/src/navigation/stacks/TabsNavigator.tsx index dd38bf120..9c69d397c 100644 --- a/src/navigation/stacks/TabsNavigator.tsx +++ b/src/navigation/stacks/TabsNavigator.tsx @@ -41,11 +41,6 @@ export const TabsNavigator = () => { component={SettingsStack} options={{ tabBarLabel: t('settings.tab') }} /> - Date: Thu, 24 Aug 2023 13:00:06 +0400 Subject: [PATCH 074/509] removed pub --- src/lib/helpers/AirDAOKeysStorage.ts | 46 ++++++---------------------- src/utils/wallet.ts | 9 ++---- 2 files changed, 11 insertions(+), 44 deletions(-) diff --git a/src/lib/helpers/AirDAOKeysStorage.ts b/src/lib/helpers/AirDAOKeysStorage.ts index 97364cb93..0571931dc 100644 --- a/src/lib/helpers/AirDAOKeysStorage.ts +++ b/src/lib/helpers/AirDAOKeysStorage.ts @@ -1,5 +1,4 @@ import * as SecureStore from 'expo-secure-store'; -import { WalletMetadata } from '@appTypes'; class AirDAOKeysStorage { private serviceName = ''; @@ -23,22 +22,19 @@ class AirDAOKeysStorage { await this.init(); } - private async getKeyValue(key: string): Promise { + private async getKeyValue(key: string): Promise { try { const sanitizedKey = this.sanitizeKey(this.serviceName + '_' + key); const credentials = await SecureStore.getItemAsync(sanitizedKey); if (!credentials) return false; - return JSON.parse(credentials) as WalletMetadata; + return JSON.parse(credentials); } catch (e) { console.error('AirDAOStorage getKeyValue error ', e); return false; } } - private async setKeyValue( - key: string, - data: WalletMetadata - ): Promise { + private async setKeyValue(key: string, data: any): Promise { try { const sanitizedKey = this.sanitizeKey(this.serviceName + '_' + key); await SecureStore.setItemAsync(sanitizedKey, JSON.stringify(data)); @@ -191,7 +187,6 @@ class AirDAOKeysStorage { async saveMnemonic(newMnemonic: { mnemonic: string; hash: string; - pub: string; name: string; number: number; }) { @@ -217,13 +212,7 @@ class AirDAOKeysStorage { await this.setKeyValue(unique, newMnemonic); await this.setKeyValue('wallet_' + this.serviceWalletsCounter, newMnemonic); - await this.setKeyValue('wallets_counter', { - pub: '', - hash: '', - mnemonic: '', - name: '', - number: this.serviceWalletsCounter - }); + await this.setKeyValue('wallets_counter', this.serviceWalletsCounter || 1); this.serviceWallets[unique] = newMnemonic.mnemonic; this.serviceWallets[this.serviceWalletsCounter - 1] = newMnemonic.mnemonic; @@ -244,8 +233,8 @@ class AirDAOKeysStorage { async getAddressCache(hashOrId: string) { try { const res = await this.getKeyValue('ar4_' + hashOrId); - if (!res || !res.mnemonic || res.pub === res.mnemonic) return false; - return { address: res.pub, privateKey: res.mnemonic }; + if (!res || !res.mnemonic || res.address === res.mnemonic) return false; + return { address: res.address, privateKey: res.mnemonic }; } catch (e) { return false; } @@ -256,7 +245,6 @@ class AirDAOKeysStorage { res: { address: string; privateKey: string; - pub: string; name: string; mnemonic: string; number: number; @@ -266,27 +254,11 @@ class AirDAOKeysStorage { if (typeof res.privateKey === 'undefined' || !res.privateKey) { return false; } - return this.setKeyValue('ar4_' + hashOrId, res); + return this.setKeyValue('ar4_' + hashOrId, res.address); } - // async getLoginCache(hashOrId: string) { - // const res = await this.getKeyValue('login_' + hashOrId); - // if (!res) return false; - // return { login: res.pub, pass: res.mnemonic }; - // } - // - // async setLoginCache(hashOrId: string, res: { login: string; pass: string }) { - // return this.setKeyValue('login_' + hashOrId, res); - // } - - async setSettingValue(hashOrId: string, value: number) { - return this.setKeyValue('setting_' + hashOrId, { - pub: '', - hash: hashOrId, - number: value, - mnemonic: '', - name: '' - }); + async setSettingValue(hashOrId: string) { + return this.setKeyValue('setting_' + hashOrId, hashOrId); } async getSettingValue(hashOrId: string) { diff --git a/src/utils/wallet.ts b/src/utils/wallet.ts index 4b73c3a98..a57b40d59 100644 --- a/src/utils/wallet.ts +++ b/src/utils/wallet.ts @@ -6,10 +6,7 @@ import { MnemonicUtils } from './mnemonics'; import { CashBackUtils } from './cashback'; const _saveWallet = async ( - wallet: Pick< - WalletMetadata, - 'newMnemonic' | 'mnemonic' | 'name' | 'number' | 'pub' - > + wallet: Pick ) => { let storedKey = ''; try { @@ -17,7 +14,6 @@ const _saveWallet = async ( mnemonic: wallet.newMnemonic ? wallet.newMnemonic : wallet.mnemonic, hash: '?', number: wallet.number, - pub: wallet.pub, name: wallet.name }; @@ -44,7 +40,7 @@ const _getWalletName = async () => { }; const processWallet = async ( - data: Pick + data: Pick ) => { const hash = await _saveWallet(data); // done let tmpWalletName = data.name; @@ -56,7 +52,6 @@ const processWallet = async ( const fullWallet: Wallet = new Wallet({ hash, ...data, - pub: data.pub || '', name: tmpWalletName, number }); From c882f034b43421ff99f3a02cbc15917ea3e14041 Mon Sep 17 00:00:00 2001 From: JavidHaji-zada Date: Fri, 25 Aug 2023 08:46:51 +0400 Subject: [PATCH 075/509] fix naming in EthScannerProcessor --- crypto/blockchains/eth/EthScannerProcessor.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/crypto/blockchains/eth/EthScannerProcessor.ts b/crypto/blockchains/eth/EthScannerProcessor.ts index 938d2a7ba..19ff59c5e 100644 --- a/crypto/blockchains/eth/EthScannerProcessor.ts +++ b/crypto/blockchains/eth/EthScannerProcessor.ts @@ -5,7 +5,7 @@ import BlocksoftUtils from '../../common/AirDAOUtils'; import AirDAOAxios from '../../common/AirDAOAxios'; import AirDAOCryptoLog from '../../common/AirDAOCryptoLog'; import EthBasic from './basic/EthBasic'; -import BlocksoftExternalSettings from '../../common/AirDAOExternalSettings'; +import AirDAOExternalSettings from '../../common/AirDAOExternalSettings'; export default class EthScannerProcessor extends EthBasic { /** @@ -19,7 +19,7 @@ export default class EthScannerProcessor extends EthBasic { txHash ); - this._trezorServer = await BlocksoftExternalSettings.getTrezorServer( + this._trezorServer = await AirDAOExternalSettings.getTrezorServer( this._trezorServerCode, this._settings.currencyCode + ' ETH.Scanner.getTransaction' ); @@ -43,11 +43,11 @@ export default class EthScannerProcessor extends EthBasic { let res = await AirDAOAxios.getWithoutBraking(link); if (!res || !res.data) { - BlocksoftExternalSettings.setTrezorServerInvalid( + AirDAOExternalSettings.setTrezorServerInvalid( this._trezorServerCode, this._trezorServer ); - this._trezorServer = await BlocksoftExternalSettings.getTrezorServer( + this._trezorServer = await AirDAOExternalSettings.getTrezorServer( this._trezorServerCode, this._settings.currencyCode + ' ETH.Scanner._get' ); @@ -64,7 +64,7 @@ export default class EthScannerProcessor extends EthBasic { link = this._trezorServer + '/api/v2/tx-specific/' + txHash; res = await AirDAOAxios.getWithoutBraking(link); if (!res || !res.data) { - BlocksoftExternalSettings.setTrezorServerInvalid( + AirDAOExternalSettings.setTrezorServerInvalid( this._trezorServerCode, this._trezorServer ); From 316e4315b5cd4a73888f1eb40ab8df4c0f79ac77 Mon Sep 17 00:00:00 2001 From: JavidHaji-zada Date: Fri, 25 Aug 2023 09:49:08 +0400 Subject: [PATCH 076/509] AirDAOAxios-Toast fix require cycle --- crypto/common/AirDAOAxios.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crypto/common/AirDAOAxios.ts b/crypto/common/AirDAOAxios.ts index baa1e769e..3d8030829 100644 --- a/crypto/common/AirDAOAxios.ts +++ b/crypto/common/AirDAOAxios.ts @@ -5,7 +5,7 @@ import AirDAOCryptoLog from './AirDAOCryptoLog'; import axios from 'axios'; import { Platform } from 'react-native'; import CookieManager from '@react-native-cookies/cookies'; -import { Toast, ToastPosition } from '@components/modular'; +import { Toast, ToastPosition } from '@components/modular/Toast'; const CancelToken = axios && typeof axios.CancelToken !== 'undefined' From 712a6f5c0c8dfde575a70d7a7fb54fcae2782d80 Mon Sep 17 00:00:00 2001 From: JavidHaji-zada Date: Fri, 25 Aug 2023 09:49:38 +0400 Subject: [PATCH 077/509] created crypto service to get address balance --- .../AirDAOTransactions/AirDAOTransactions.ts | 144 ++++++++++++++++++ crypto/blockchains/eth/EthScannerProcessor.ts | 8 + src/api/api.ts | 4 +- src/api/crypto-service.ts | 20 +++ 4 files changed, 175 insertions(+), 1 deletion(-) create mode 100644 crypto/actions/AirDAOTransactions/AirDAOTransactions.ts create mode 100644 src/api/crypto-service.ts diff --git a/crypto/actions/AirDAOTransactions/AirDAOTransactions.ts b/crypto/actions/AirDAOTransactions/AirDAOTransactions.ts new file mode 100644 index 000000000..f6e271d2d --- /dev/null +++ b/crypto/actions/AirDAOTransactions/AirDAOTransactions.ts @@ -0,0 +1,144 @@ +/** + * @author Ksu + * @version 0.5 + */ +import AirDAODispatcher from '@crypto/blockchains/AirDAODispatcher'; + +class BlocksoftTransactions { + /** + * @type {{}} + * @private + */ + _processor = {}; + + /** + * @return {Promise} + */ + async getTransactions(data, source = '') { + const currencyCode = data.account.currencyCode; + if (!currencyCode) { + throw new Error('plz set currencyCode before calling'); + } + if (!this._processor[currencyCode]) { + /** + * @type {EthScannerProcessor|BtcScannerProcessor|UsdtScannerProcessor} + */ + this._processor[currencyCode] = + AirDAODispatcher.getScannerProcessor(currencyCode); + } + let resultData = []; + try { + resultData = await this._processor[ + currencyCode + ].getTransactionsBlockchain(data, source); + } catch (e) { + e.code = 'ERROR_SYSTEM'; + e.message += ' on actual getTransactions step '; + throw e; + } + + return resultData; + } + + /** + * @return {Promise} + */ + async getTransactionsPending(data, source = '') { + const currencyCode = data.account.currencyCode; + if (!currencyCode) { + throw new Error('plz set currencyCode before calling'); + } + if (!this._processor[currencyCode]) { + /** + * @type {TrxScannerProcessor} + */ + this._processor[currencyCode] = + AirDAODispatcher.getScannerProcessor(currencyCode); + } + if ( + typeof this._processor[currencyCode].getTransactionsPendingBlockchain === + 'undefined' + ) { + return false; + } + let resultData = false; + try { + resultData = await this._processor[ + currencyCode + ].getTransactionsPendingBlockchain(data, source, false); + } catch (e) { + e.message += ' on actual getTransactionsPending step '; + throw e; + } + + return resultData; + } + + /** + * @return {Promise} + */ + async resetTransactionsPending(data, source = '') { + const currencyCode = data.account.currencyCode; + if (!currencyCode) { + throw new Error('plz set currencyCode before calling'); + } + if (!this._processor[currencyCode]) { + /** + * @type {TrxScannerProcessor} + */ + this._processor[currencyCode] = + AirDAODispatcher.getScannerProcessor(currencyCode); + } + if ( + typeof this._processor[currencyCode] + .resetTransactionsPendingBlockchain === 'undefined' + ) { + return false; + } + let resultData = false; + try { + resultData = await this._processor[ + currencyCode + ].resetTransactionsPendingBlockchain(data, source, false); + } catch (e) { + e.message += ' on actual resetTransactionsPending step '; + throw e; + } + + return resultData; + } + + /** + * @return {Promise} + */ + async getAddresses(data, source = '') { + const currencyCode = data.account.currencyCode; + if (!currencyCode) { + throw new Error('plz set currencyCode before calling'); + } + if (!this._processor[currencyCode]) { + /** + * @type {EthScannerProcessor|BtcScannerProcessor|UsdtScannerProcessor} + */ + this._processor[currencyCode] = + AirDAODispatcher.getScannerProcessor(currencyCode); + } + let resultData = []; + try { + resultData = await this._processor[currencyCode].getAddressesBlockchain( + data, + source + ); + } catch (e) { + e.code = 'ERROR_SYSTEM'; + e.message += ' on actual getAddressesBlockchain step '; + throw e; + } + + return resultData; + } +} + +const singleBlocksoftTransactions = new BlocksoftTransactions(); + +export default singleBlocksoftTransactions; diff --git a/crypto/blockchains/eth/EthScannerProcessor.ts b/crypto/blockchains/eth/EthScannerProcessor.ts index 19ff59c5e..be8cf6e42 100644 --- a/crypto/blockchains/eth/EthScannerProcessor.ts +++ b/crypto/blockchains/eth/EthScannerProcessor.ts @@ -8,6 +8,14 @@ import EthBasic from './basic/EthBasic'; import AirDAOExternalSettings from '../../common/AirDAOExternalSettings'; export default class EthScannerProcessor extends EthBasic { + async getBalance(address: string): Promise { + try { + const balance = await this._web3.eth.getBalance(address); + return balance; + } catch (error) { + throw error; + } + } /** * @param {string} txHash * @return {Promise<[UnifiedTransaction]>} diff --git a/src/api/api.ts b/src/api/api.ts index b5d713346..366a974eb 100644 --- a/src/api/api.ts +++ b/src/api/api.ts @@ -5,6 +5,7 @@ import { AMBToken } from '@models'; import { watcherService } from './watcher-service'; // import Config from '@constants/config'; import { explorerService } from './explorer-service'; +import { cryptoService } from './crypto-service'; import Config from '@constants/config'; // const cmcApiUrl = Config.CMC_API_URL; @@ -47,5 +48,6 @@ export const API = { getAMBTokenData, getAMBPriceHistoricalPricing, explorerService, - watcherService + watcherService, + cryptoService }; diff --git a/src/api/crypto-service.ts b/src/api/crypto-service.ts new file mode 100644 index 000000000..bdd77eae5 --- /dev/null +++ b/src/api/crypto-service.ts @@ -0,0 +1,20 @@ +import singleAirDAODispatcher from '@crypto/blockchains/AirDAODispatcher'; +import { AirDAODictTypes } from '@crypto/common/AirDAODictTypes'; +import AirDAOUtils from '@crypto/common/AirDAOUtils'; + +const getBalanceOfAddress = async ( + address: string, + currencyCode = AirDAODictTypes.Code.AMB +) => { + try { + const processor = singleAirDAODispatcher.getScannerProcessor(currencyCode); + const weiBalance = await processor.getBalance(address); + return { wei: weiBalance, ether: AirDAOUtils.toEther(weiBalance) }; + } catch (error) { + throw error; + } +}; + +export const cryptoService = { + getBalanceOfAddress +}; From 7ccdd2bbadeaba0fa991c9a33a685282f7e888a4 Mon Sep 17 00:00:00 2001 From: JavidHaji-zada Date: Fri, 25 Aug 2023 09:50:36 +0400 Subject: [PATCH 078/509] CopyToClipboardButton added icon props --- src/components/composite/Button/CopyToClipboard.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/components/composite/Button/CopyToClipboard.tsx b/src/components/composite/Button/CopyToClipboard.tsx index c83ca2aa0..761c783eb 100644 --- a/src/components/composite/Button/CopyToClipboard.tsx +++ b/src/components/composite/Button/CopyToClipboard.tsx @@ -2,7 +2,7 @@ import React from 'react'; import * as Clipboard from 'expo-clipboard'; import { TextProps } from '@components/base/Text/Text.types'; import { Button, Row, Spacer, Text } from '@components/base'; -import { ClipboardFilledIcon } from '@components/svg/icons'; +import { ClipboardFilledIcon, IconProps } from '@components/svg/icons'; import { scale } from '@utils/scaling'; import { Toast, ToastPosition } from '@components/modular/Toast'; import { BaseButtonProps } from '@components/base/Button'; @@ -13,12 +13,14 @@ export interface CopyToClipboardButtonProps textToDisplay: string; textToCopy?: string; textProps?: TextProps; + iconProps?: IconProps; } export const CopyToClipboardButton = ( props: CopyToClipboardButtonProps ): JSX.Element => { - const { textToDisplay, textToCopy, textProps, ...buttonProps } = props; + const { textToDisplay, textToCopy, textProps, iconProps, ...buttonProps } = + props; const { t } = useTranslation(); const onPress = async () => { @@ -34,7 +36,7 @@ export const CopyToClipboardButton = ( {textToDisplay} ); From 3f4845451257d6a26f0d505e0aa9926f4af0d73d Mon Sep 17 00:00:00 2001 From: JavidHaji-zada Date: Fri, 25 Aug 2023 09:51:44 +0400 Subject: [PATCH 079/509] Created reusable WalletCard component --- src/components/modular/WalletCard/index.tsx | 98 +++++++++++++++++++ src/components/modular/WalletCard/styles.ts | 12 +++ src/components/modular/index.ts | 1 + src/components/svg/icons/LogoGradient.tsx | 40 +++----- .../svg/icons/LogoGradientCircular.tsx | 50 ++++++++++ src/components/svg/icons/index.ts | 1 + .../templates/AMBPriceHistory/index.tsx | 4 +- 7 files changed, 179 insertions(+), 27 deletions(-) create mode 100644 src/components/modular/WalletCard/index.tsx create mode 100644 src/components/modular/WalletCard/styles.ts create mode 100644 src/components/svg/icons/LogoGradientCircular.tsx diff --git a/src/components/modular/WalletCard/index.tsx b/src/components/modular/WalletCard/index.tsx new file mode 100644 index 000000000..895003fdf --- /dev/null +++ b/src/components/modular/WalletCard/index.tsx @@ -0,0 +1,98 @@ +import React from 'react'; +import { StyleSheet, View } from 'react-native'; +import { Row, Spacer, Text } from '@components/base'; +import { CopyToClipboardButton } from '@components/composite'; +import { COLORS } from '@constants/colors'; +import { shadow } from '@constants/shadow'; +import { NumberUtils } from '@utils/number'; +import { moderateScale, scale, verticalScale } from '@utils/scaling'; +import { StringUtils } from '@utils/string'; +import { LogoGradient } from '@components/svg/icons'; + +interface WalletCardProps { + address: string; + ambBalance: number; + usdBalance: number; + addressLeftPadding?: number; + addressRightPadding?: number; + backgroundColor?: string; + addressTextColor?: string; + priceTextColor?: string; +} +export const WalletCard = (props: WalletCardProps) => { + const { + address, + ambBalance, + usdBalance, + addressLeftPadding = 5, + addressRightPadding = 6, + backgroundColor = COLORS.blue600, + addressTextColor = COLORS.white50, + priceTextColor = COLORS.white + } = props; + return ( + + + + + + + + + {NumberUtils.formatNumber(ambBalance, ambBalance > 0 ? 2 : 0)} AMB + + + + ${NumberUtils.formatNumber(usdBalance, usdBalance > 0 ? 2 : 0)} + + + + + + ); +}; + +const styles = StyleSheet.create({ + container: { + backgroundColor: COLORS.blue600, + borderRadius: moderateScale(16), + minHeight: 148, + height: verticalScale(148), + overflow: 'hidden', + paddingLeft: scale(20), + paddingVertical: verticalScale(22), + justifyContent: 'space-between', + ...shadow + }, + logo: { + position: 'absolute', + top: -verticalScale(16), + right: -scale(16) + } +}); diff --git a/src/components/modular/WalletCard/styles.ts b/src/components/modular/WalletCard/styles.ts new file mode 100644 index 000000000..63bf3faaa --- /dev/null +++ b/src/components/modular/WalletCard/styles.ts @@ -0,0 +1,12 @@ +import { COLORS } from '@constants/colors'; +import { shadow } from '@constants/shadow'; +import { moderateScale } from '@utils/scaling'; +import { StyleSheet } from 'react-native'; + +export const styles = StyleSheet.create({ + container: { + backgroundColor: COLORS.blue600, + borderRadius: moderateScale(16), + ...shadow + } +}); diff --git a/src/components/modular/index.ts b/src/components/modular/index.ts index 97073ec82..189bc60c7 100644 --- a/src/components/modular/index.ts +++ b/src/components/modular/index.ts @@ -4,3 +4,4 @@ export * from './Button'; export * from './CollectionItem'; export * from './TransactionItem'; export * from './Toast'; +export * from './WalletCard'; diff --git a/src/components/svg/icons/LogoGradient.tsx b/src/components/svg/icons/LogoGradient.tsx index 0789a8a85..2bcd4a12f 100644 --- a/src/components/svg/icons/LogoGradient.tsx +++ b/src/components/svg/icons/LogoGradient.tsx @@ -1,17 +1,11 @@ -import React from 'react'; -import Svg, { - Circle, - Defs, - LinearGradient, - Path, - Stop -} from 'react-native-svg'; +import * as React from 'react'; +import Svg, { Path, Defs, LinearGradient, Stop } from 'react-native-svg'; import { IconProps } from './Icon.types'; -export function LogoGradient(props: IconProps) { +export function LogoGradient(props: Omit) { const { scale = 1 } = props; - const width = 23; - const height = 24; + const width = 218; + const height = 234; return ( - - + diff --git a/src/components/svg/icons/LogoGradientCircular.tsx b/src/components/svg/icons/LogoGradientCircular.tsx new file mode 100644 index 000000000..922567d0d --- /dev/null +++ b/src/components/svg/icons/LogoGradientCircular.tsx @@ -0,0 +1,50 @@ +import React from 'react'; +import Svg, { + Circle, + Defs, + LinearGradient, + Path, + Stop +} from 'react-native-svg'; +import { IconProps } from './Icon.types'; + +export function LogoGradientCircular(props: IconProps) { + const { scale = 1 } = props; + const width = 23; + const height = 24; + return ( + + + + + + + + + + + ); +} diff --git a/src/components/svg/icons/index.ts b/src/components/svg/icons/index.ts index f7a958d4c..b9038db7d 100644 --- a/src/components/svg/icons/index.ts +++ b/src/components/svg/icons/index.ts @@ -31,6 +31,7 @@ export * from './LogoWithName'; export * from './LogoBig'; export * from './LogoGradient'; export * from './SmallLogo'; +export * from './LogoGradientCircular'; export * from './Messages'; export * from './Notification'; export * from './Options'; diff --git a/src/components/templates/AMBPriceHistory/index.tsx b/src/components/templates/AMBPriceHistory/index.tsx index d08134c7d..81e7b4f96 100644 --- a/src/components/templates/AMBPriceHistory/index.tsx +++ b/src/components/templates/AMBPriceHistory/index.tsx @@ -11,7 +11,7 @@ import Animated, { import { GraphPoint } from 'react-native-graph'; import { CMCInterval } from '@appTypes'; import { AnimatedText, Button, Row, Spacer, Text } from '@components/base'; -import { ChevronRightIcon, LogoGradient } from '@components/svg/icons'; +import { ChevronRightIcon, LogoGradientCircular } from '@components/svg/icons'; import { COLORS } from '@constants/colors'; import { useAMBPrice, useAMBPriceHistorical } from '@hooks'; import { scale, verticalScale } from '@utils/scaling'; @@ -130,7 +130,7 @@ export const AMBPriceHistory = (props: AMBPriceHistoryProps) => { return ( - + Date: Fri, 25 Aug 2023 09:52:51 +0400 Subject: [PATCH 080/509] removed pub field from Wallet --- src/appTypes/Wallet.ts | 1 - src/database/models/wallet.ts | 2 -- src/models/Wallet.ts | 5 +---- 3 files changed, 1 insertion(+), 7 deletions(-) diff --git a/src/appTypes/Wallet.ts b/src/appTypes/Wallet.ts index 192298147..4e0c2aceb 100644 --- a/src/appTypes/Wallet.ts +++ b/src/appTypes/Wallet.ts @@ -1,5 +1,4 @@ export interface WalletMetadata { - pub: string; name: string; mnemonic: string; number: number; diff --git a/src/database/models/wallet.ts b/src/database/models/wallet.ts index f958f38b3..db6777397 100644 --- a/src/database/models/wallet.ts +++ b/src/database/models/wallet.ts @@ -33,8 +33,6 @@ export class WalletDBModel extends Model { @field('is_created_here') isCreatedHere: number; // @ts-ignore @field('to_send_status') toSendStatus: number; - // @ts-ignore - @field('pub') pub: string; static async getByHash(hash: string): Promise { const walletInDB = (await Database.query( diff --git a/src/models/Wallet.ts b/src/models/Wallet.ts index 127225fe7..284a142ae 100644 --- a/src/models/Wallet.ts +++ b/src/models/Wallet.ts @@ -16,7 +16,6 @@ export class Wallet { isHd: number; isCreatedHere: number; toSendStatus: number; - pub: string; constructor(details: WalletMetadata) { this.hash = details.hash || 'empty_hash'; @@ -33,7 +32,6 @@ export class Wallet { this.isHd = details.isHd || 0; this.isCreatedHere = details.isCreatedHere || 0; this.toSendStatus = details.toSendStatus || 0; - this.pub = details.pub || ''; } static async saveWallet(wallet: WalletMetadata) { @@ -54,8 +52,7 @@ export class Wallet { useUnconfirmed: model.useUnconfirmed, isHd: model.isHd, isCreatedHere: model.isCreatedHere, - toSendStatus: model.toSendStatus, - pub: model.pub + toSendStatus: model.toSendStatus }); } } From 5c4362bee0670ba113991ec6d200d953ab503cf4 Mon Sep 17 00:00:00 2001 From: JavidHaji-zada Date: Fri, 25 Aug 2023 10:00:33 +0400 Subject: [PATCH 081/509] Added WalletCard to new Home page --- src/hooks/cache/index.ts | 1 + src/hooks/cache/useSelectedWalletHash.ts | 33 +++++++++ src/hooks/index.ts | 1 + src/hooks/query/index.ts | 1 + src/hooks/query/useCryptoAccountFromHash.ts | 75 +++++++++++++++++++++ src/hooks/useUSDPrice.ts | 12 ++++ src/localization/locales/English.json | 7 +- src/localization/locales/Turkish.json | 7 +- src/navigation/stacks/Tabs/HomeStack.tsx | 2 +- src/navigation/stacks/TabsNavigator.tsx | 5 -- src/screens/Wallets/WalletsNew.tsx | 35 ++++++++++ src/screens/Wallets/styles.ts | 38 +---------- src/utils/cache.ts | 3 +- 13 files changed, 176 insertions(+), 44 deletions(-) create mode 100644 src/hooks/cache/useSelectedWalletHash.ts create mode 100644 src/hooks/query/useCryptoAccountFromHash.ts create mode 100644 src/hooks/useUSDPrice.ts create mode 100644 src/screens/Wallets/WalletsNew.tsx diff --git a/src/hooks/cache/index.ts b/src/hooks/cache/index.ts index cadff26b0..803b1850e 100644 --- a/src/hooks/cache/index.ts +++ b/src/hooks/cache/index.ts @@ -1,2 +1,3 @@ export * from './useNotificationSettings'; +export * from './useSelectedWalletHash'; export * from './useWatchlist'; diff --git a/src/hooks/cache/useSelectedWalletHash.ts b/src/hooks/cache/useSelectedWalletHash.ts new file mode 100644 index 000000000..ca2421d47 --- /dev/null +++ b/src/hooks/cache/useSelectedWalletHash.ts @@ -0,0 +1,33 @@ +import { useState } from 'react'; +import { useFocusEffect } from '@react-navigation/native'; +import { DatabaseTable } from '@appTypes'; +import { Database, WalletDBModel } from '@database'; +import { Cache, CacheKey } from '@utils/cache'; + +export const useSelectedWalletHash = (): string => { + const [selectedWalletHash, setSelectedWalletHash] = useState(''); + + const getSelectedWallet = async () => { + const _selectedWalletHash = (await Cache.getItem( + CacheKey.SelectedWallet + )) as string; + if (!_selectedWalletHash) { + const allWallets = (await Database.query( + DatabaseTable.Wallets + )) as WalletDBModel[]; + const len = allWallets?.length || 0; + if (allWallets && len > 0) { + const hash = allWallets[len - 1].hash; + setSelectedWalletHash(hash); + Cache.setItem(CacheKey.SelectedWallet, hash); + } + } + setSelectedWalletHash(_selectedWalletHash); + }; + + useFocusEffect(() => { + getSelectedWallet(); + }); + + return selectedWalletHash || ''; +}; diff --git a/src/hooks/index.ts b/src/hooks/index.ts index 06e2e9bce..c15a9c59b 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -9,3 +9,4 @@ export * from './useAppState'; export * from './useCachePurifier'; export * from './useInitialMountEffect'; export * from './useSwipeableDismissListener'; +export * from './useUSDPrice'; diff --git a/src/hooks/query/index.ts b/src/hooks/query/index.ts index 7e5d1c63f..13b31edbf 100644 --- a/src/hooks/query/index.ts +++ b/src/hooks/query/index.ts @@ -1,5 +1,6 @@ export * from './useAMBPrice'; export * from './useAMBPriceHistorical'; +export * from './useCryptoAccountFromHash'; export * from './useExplorerInfo'; export * from './useExplorerAccounts'; export * from './useNotifications'; diff --git a/src/hooks/query/useCryptoAccountFromHash.ts b/src/hooks/query/useCryptoAccountFromHash.ts new file mode 100644 index 000000000..8c4731826 --- /dev/null +++ b/src/hooks/query/useCryptoAccountFromHash.ts @@ -0,0 +1,75 @@ +import { useCallback, useEffect, useState } from 'react'; +import AirDAOKeysForRef from '@lib/helpers/AirDAOKeysForRef'; +import { Database, WalletDBModel } from '@database'; +import { DatabaseTable, ExplorerAccountType, QueryResponse } from '@appTypes'; +import { Q } from '@nozbe/watermelondb'; +import { ExplorerAccount } from '@models'; +import { API } from '@api/api'; +import { useTranslation } from 'react-i18next'; + +export const useCryptoAccountFromHash = ( + hash: string +): QueryResponse => { + const [account, setAccount] = useState(null); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(''); + const { t } = useTranslation(); + + const getAccount = useCallback(async () => { + setLoading(true); + let wallets: WalletDBModel[]; + try { + wallets = (await Database.query( + DatabaseTable.Wallets, + Q.where('hash', Q.eq(hash)) + )) as WalletDBModel[]; + } catch (error) { + // TODO change text + setError(t('errors.loading-wallet-from-db')); + setLoading(true); + return; + } + if (wallets?.length > 0) { + const wallet = wallets[0]; + try { + const _account = await AirDAOKeysForRef.discoverPublicAndPrivate({ + mnemonic: wallet.mnemonic + }); + if (_account) { + const balance = await API.cryptoService.getBalanceOfAddress( + '0xb2E8A153bdbB5Ad7fc9c2e7F92BFA6908665664C' + ); + const explorerAccount = new ExplorerAccount({ + _id: _account.address, + address: _account.address, + balance: { + wei: balance.wei, + ether: parseFloat(balance.ether) + }, + byteCode: '', + isContract: false, + power: -1, + role: -1, + timestamp: -1, + totalTx: -1, + type: ExplorerAccountType.Account + }); + setAccount(explorerAccount); + } + } catch (error) { + // TODO change text + setError(t('errors.loading-account')); + } finally { + setLoading(false); + } + } else { + setLoading(false); + } + }, [hash, t]); + + useEffect(() => { + if (hash) getAccount(); + }, [getAccount, hash]); + + return { data: account, loading, error }; +}; diff --git a/src/hooks/useUSDPrice.ts b/src/hooks/useUSDPrice.ts new file mode 100644 index 000000000..110c4265b --- /dev/null +++ b/src/hooks/useUSDPrice.ts @@ -0,0 +1,12 @@ +import { useEffect, useState } from 'react'; +import { useAMBPrice } from './query'; + +export const useUSDPrice = (amb: number): number => { + const { data: ambPrice } = useAMBPrice(); + const [usdPrice, setUSDPrice] = useState(0); + useEffect(() => { + setUSDPrice(amb * (ambPrice?.priceUSD || 0)); + }, [ambPrice, amb]); + + return usdPrice; +}; diff --git a/src/localization/locales/English.json b/src/localization/locales/English.json index 81cf95fdc..0d15d919a 100644 --- a/src/localization/locales/English.json +++ b/src/localization/locales/English.json @@ -130,5 +130,10 @@ "chart.timeframe.daily": "1D", "chart.timeframe.weekly": "1W", - "chart.timeframe.monthly": "1M" + "chart.timeframe.monthly": "1M", + + "errors": { + "loading-account": "Could not fetch crypto address details!", + "loading-wallet-from-db": "Error occured while retrieving wallet from device storage!" + } } diff --git a/src/localization/locales/Turkish.json b/src/localization/locales/Turkish.json index 17e661e51..5ad411aa3 100644 --- a/src/localization/locales/Turkish.json +++ b/src/localization/locales/Turkish.json @@ -129,5 +129,10 @@ "chart.timeframe.daily": "1G", "chart.timeframe.weekly": "1H", - "chart.timeframe.monthly": "1A" + "chart.timeframe.monthly": "1A", + + "errors": { + "loading-account": "Kripto adres bilgileri çekilemedi!", + "loading-wallet-from-db": "Cihaz belleğinden cüzdan bilgisi çekilirken hata oluştu!" + } } diff --git a/src/navigation/stacks/Tabs/HomeStack.tsx b/src/navigation/stacks/Tabs/HomeStack.tsx index 6bbe4931c..6013fc52e 100644 --- a/src/navigation/stacks/Tabs/HomeStack.tsx +++ b/src/navigation/stacks/Tabs/HomeStack.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { createNativeStackNavigator } from '@react-navigation/native-stack'; -import { HomeScreen } from '@screens/Wallets'; +import { HomeScreen } from '@screens/Wallets/WalletsNew'; import { AMBMarket } from '@screens/AMBMarket'; import { HomeParamsList } from '@appTypes/navigation/wallets'; import { Notifications } from '@screens/Notifications'; diff --git a/src/navigation/stacks/TabsNavigator.tsx b/src/navigation/stacks/TabsNavigator.tsx index dd38bf120..9c69d397c 100644 --- a/src/navigation/stacks/TabsNavigator.tsx +++ b/src/navigation/stacks/TabsNavigator.tsx @@ -41,11 +41,6 @@ export const TabsNavigator = () => { component={SettingsStack} options={{ tabBarLabel: t('settings.tab') }} /> - { + const selectedWalletHash = useSelectedWalletHash(); + const { data: account, loading: accountLoading } = + useCryptoAccountFromHash(selectedWalletHash); + const usdPrice = useUSDPrice(account?.ambBalance || 0); + + return ( + + + {accountLoading && } + {account && ( + + + + )} + + ); +}; diff --git a/src/screens/Wallets/styles.ts b/src/screens/Wallets/styles.ts index 7fa047c3e..9268e786d 100644 --- a/src/screens/Wallets/styles.ts +++ b/src/screens/Wallets/styles.ts @@ -1,40 +1,8 @@ -import { COLORS } from '@constants/colors'; -import { scale, verticalScale } from '@utils/scaling'; +import { scale } from '@utils/scaling'; import { StyleSheet } from 'react-native'; export const styles = StyleSheet.create({ - container: { - flexGrow: 1, - backgroundColor: COLORS.culturedWhite - }, - homeTabs: { - marginHorizontal: scale(16), - backgroundColor: COLORS.white, - borderRadius: 24 - }, - homeHighlights: { - backgroundColor: COLORS.white, - borderRadius: 24, - marginHorizontal: scale(16), - paddingBottom: verticalScale(28) - }, - divider: { - height: 2, - alignSelf: 'center', - width: '100%', - backgroundColor: '#2f2b431a', - marginVertical: verticalScale(24) - }, - airdao: { - marginTop: verticalScale(32) - }, - floatButtonTitle: { - fontFamily: 'Inter_600SemiBold' - }, - addAddressBtn: { - backgroundColor: COLORS.deepBlue, - paddingVertical: verticalScale(8), - paddingLeft: scale(18), - paddingRight: scale(16) + accountCard: { + paddingHorizontal: scale(38) } }); diff --git a/src/utils/cache.ts b/src/utils/cache.ts index 1d3817eb7..53a18b1dc 100644 --- a/src/utils/cache.ts +++ b/src/utils/cache.ts @@ -15,7 +15,8 @@ export enum CacheKey { Watchlist = 'watchlist', LastNotificationTimestamp = 'last_notification_timestamp', PreCreatedGroupWasCreated = 'pre_created_group_was_created', - SendInputType = 'send_input_type' + SendInputType = 'send_input_type', + SelectedWallet = 'selected_wallet' } const getNotificationSettings = async (): Promise => { From 86773f3376fde14e68e7416f9b18317b7ff39a49 Mon Sep 17 00:00:00 2001 From: JavidHaji-zada Date: Fri, 25 Aug 2023 12:54:47 +0400 Subject: [PATCH 082/509] Created Account Action Buttons --- src/components/svg/icons/ReceiveQRCode.tsx | 24 +++++++++++ src/components/svg/icons/Rocket.tsx | 32 +++++++++++++++ src/components/svg/icons/Send.tsx | 31 ++++++++++++++ src/components/svg/icons/index.ts | 3 ++ src/constants/colors.ts | 1 + src/constants/config.ts | 12 ++++++ src/hooks/query/useCryptoAccountFromHash.ts | 2 +- src/localization/locales/English.json | 9 ++++ src/localization/locales/Turkish.json | 9 ++++ src/screens/Wallets/WalletsNew.tsx | 21 ++++++---- .../AccountActions/ActionButton.tsx | 41 +++++++++++++++++++ .../AccountActions/ActionButton.types.ts | 5 +++ .../components/AccountActions/Receive.tsx | 22 ++++++++++ .../components/AccountActions/Send.tsx | 22 ++++++++++ .../components/AccountActions/Staking.tsx | 17 ++++++++ .../components/AccountActions/Swap.tsx | 17 ++++++++ .../components/AccountActions/index.tsx | 22 ++++++++++ src/screens/Wallets/components/index.ts | 1 + 18 files changed, 282 insertions(+), 9 deletions(-) create mode 100644 src/components/svg/icons/ReceiveQRCode.tsx create mode 100644 src/components/svg/icons/Rocket.tsx create mode 100644 src/components/svg/icons/Send.tsx create mode 100644 src/screens/Wallets/components/AccountActions/ActionButton.tsx create mode 100644 src/screens/Wallets/components/AccountActions/ActionButton.types.ts create mode 100644 src/screens/Wallets/components/AccountActions/Receive.tsx create mode 100644 src/screens/Wallets/components/AccountActions/Send.tsx create mode 100644 src/screens/Wallets/components/AccountActions/Staking.tsx create mode 100644 src/screens/Wallets/components/AccountActions/Swap.tsx create mode 100644 src/screens/Wallets/components/AccountActions/index.tsx diff --git a/src/components/svg/icons/ReceiveQRCode.tsx b/src/components/svg/icons/ReceiveQRCode.tsx new file mode 100644 index 000000000..c8ee43314 --- /dev/null +++ b/src/components/svg/icons/ReceiveQRCode.tsx @@ -0,0 +1,24 @@ +import React from 'react'; +import { IconProps } from '@components/svg/icons/Icon.types'; +import { Path, Svg } from 'react-native-svg'; +import { COLORS } from '@constants/colors'; + +export function ReceiveQRCodeIcon(props: IconProps) { + const { scale = 1, color = COLORS.smokyBlack } = props; + const width = 20, + height = 20; + return ( + + + + ); +} diff --git a/src/components/svg/icons/Rocket.tsx b/src/components/svg/icons/Rocket.tsx new file mode 100644 index 000000000..8bed853eb --- /dev/null +++ b/src/components/svg/icons/Rocket.tsx @@ -0,0 +1,32 @@ +import React from 'react'; +import { IconProps } from '@components/svg/icons/Icon.types'; +import { ClipPath, Defs, G, Path, Svg } from 'react-native-svg'; +import { COLORS } from '@constants/colors'; + +export function StakeRocketIcon(props: IconProps) { + const { scale = 1, color = COLORS.smokyBlack } = props; + const width = 20, + height = 20; + return ( + + + + + + + + + + + ); +} diff --git a/src/components/svg/icons/Send.tsx b/src/components/svg/icons/Send.tsx new file mode 100644 index 000000000..1153f3387 --- /dev/null +++ b/src/components/svg/icons/Send.tsx @@ -0,0 +1,31 @@ +import React from 'react'; +import { IconProps } from '@components/svg/icons/Icon.types'; +import { ClipPath, Defs, G, Path, Svg } from 'react-native-svg'; +import { COLORS } from '@constants/colors'; + +export function SendIcon(props: IconProps) { + const { scale = 1, color = COLORS.smokyBlack } = props; + const width = 20, + height = 20; + return ( + + + + + + + + + + + ); +} diff --git a/src/components/svg/icons/index.ts b/src/components/svg/icons/index.ts index b9038db7d..6ef08edb9 100644 --- a/src/components/svg/icons/index.ts +++ b/src/components/svg/icons/index.ts @@ -37,11 +37,14 @@ export * from './Notification'; export * from './Options'; export * from './PlayStore'; export * from './PlusIcon'; +export * from './ReceiveQRCode'; export * from './Remove'; export * from './RightArrow'; +export * from './Rocket'; export * from './Scanner'; export * from './ScannerQR'; export * from './Search'; +export * from './Send'; export * from './Settings'; export * from './SettingsFilled'; export * from './Share'; diff --git a/src/constants/colors.ts b/src/constants/colors.ts index 81a248485..48e260912 100644 --- a/src/constants/colors.ts +++ b/src/constants/colors.ts @@ -13,6 +13,7 @@ export const COLORS = { green100: '#C3FBE5', green200: '#73E5B7', green400: '#159F80', + neutral300: '#a1a6b2', neutral700: '#222426', neutral800: '#191919', neutral900: '#0E0E0E', diff --git a/src/constants/config.ts b/src/constants/config.ts index cf713e70d..36df3d3a3 100644 --- a/src/constants/config.ts +++ b/src/constants/config.ts @@ -9,6 +9,12 @@ const envs = { appBuildVersion: '1.0.0', cryptoErrors: true, appErrors: false + }, + walletActions: { + swap: false, + send: true, + receive: true, + stake: false } }, stage: { @@ -20,6 +26,12 @@ const envs = { appBuildVersion: '1.0.0', cryptoErrors: true, appErrors: false + }, + walletActions: { + swap: false, + send: true, + receive: true, + stake: false } } }; diff --git a/src/hooks/query/useCryptoAccountFromHash.ts b/src/hooks/query/useCryptoAccountFromHash.ts index 8c4731826..ce15c46bb 100644 --- a/src/hooks/query/useCryptoAccountFromHash.ts +++ b/src/hooks/query/useCryptoAccountFromHash.ts @@ -37,7 +37,7 @@ export const useCryptoAccountFromHash = ( }); if (_account) { const balance = await API.cryptoService.getBalanceOfAddress( - '0xb2E8A153bdbB5Ad7fc9c2e7F92BFA6908665664C' + _account.address ); const explorerAccount = new ExplorerAccount({ _id: _account.address, diff --git a/src/localization/locales/English.json b/src/localization/locales/English.json index 0d15d919a..67a5967a5 100644 --- a/src/localization/locales/English.json +++ b/src/localization/locales/English.json @@ -135,5 +135,14 @@ "errors": { "loading-account": "Could not fetch crypto address details!", "loading-wallet-from-db": "Error occured while retrieving wallet from device storage!" + }, + + "account": { + "actions": { + "swap": "Swap", + "send": "Send", + "receive": "Receive", + "stake": "Stake" + } } } diff --git a/src/localization/locales/Turkish.json b/src/localization/locales/Turkish.json index 5ad411aa3..967eb279e 100644 --- a/src/localization/locales/Turkish.json +++ b/src/localization/locales/Turkish.json @@ -134,5 +134,14 @@ "errors": { "loading-account": "Kripto adres bilgileri çekilemedi!", "loading-wallet-from-db": "Cihaz belleğinden cüzdan bilgisi çekilirken hata oluştu!" + }, + + "account": { + "actions": { + "swap": "Swap", + "send": "Send", + "receive": "Receive", + "stake": "Stake" + } } } diff --git a/src/screens/Wallets/WalletsNew.tsx b/src/screens/Wallets/WalletsNew.tsx index b71393120..918911c9c 100644 --- a/src/screens/Wallets/WalletsNew.tsx +++ b/src/screens/Wallets/WalletsNew.tsx @@ -7,9 +7,10 @@ import { useSelectedWalletHash, useUSDPrice } from '@hooks'; -import { HomeHeader } from './components'; +import { AccountActions, HomeHeader } from './components'; import { styles } from './styles'; -import { Spinner } from '@components/base'; +import { Spacer, Spinner } from '@components/base'; +import { verticalScale } from '@utils/scaling'; export const HomeScreen = () => { const selectedWalletHash = useSelectedWalletHash(); @@ -22,12 +23,16 @@ export const HomeScreen = () => { {accountLoading && } {account && ( - - + + + + + + )} diff --git a/src/screens/Wallets/components/AccountActions/ActionButton.tsx b/src/screens/Wallets/components/AccountActions/ActionButton.tsx new file mode 100644 index 000000000..7517c6fa1 --- /dev/null +++ b/src/screens/Wallets/components/AccountActions/ActionButton.tsx @@ -0,0 +1,41 @@ +import React from 'react'; +import { StyleSheet, View } from 'react-native'; +import { Button, Spacer, Text } from '@components/base'; +import { COLORS } from '@constants/colors'; +import { moderateScale, verticalScale } from '@utils/scaling'; +import { IconProps } from '@components/svg/icons'; + +export interface AccountActionButtonProps { + Icon: React.ElementType; + text: string; + isActive?: boolean; + onPress?: () => unknown; +} + +export const AccountActionButton = (props: AccountActionButtonProps) => { + const { Icon, text, isActive, onPress } = props; + + const textColor = isActive ? COLORS.smokyBlack : COLORS.neutral300; + const iconColor = textColor; + return ( + + ); +}; + +const styles = StyleSheet.create({ + icon: { + backgroundColor: COLORS.smokyBlack5, + justifyContent: 'center', + alignItems: 'center', + borderRadius: moderateScale(48), + minHeight: 48, + height: moderateScale(48), + width: moderateScale(48) + } +}); diff --git a/src/screens/Wallets/components/AccountActions/ActionButton.types.ts b/src/screens/Wallets/components/AccountActions/ActionButton.types.ts new file mode 100644 index 000000000..4a8b07b84 --- /dev/null +++ b/src/screens/Wallets/components/AccountActions/ActionButton.types.ts @@ -0,0 +1,5 @@ +import { ExplorerAccount } from '@models'; + +export interface ActionButtonProps { + account: ExplorerAccount; // TODO change to AccountDBModel +} diff --git a/src/screens/Wallets/components/AccountActions/Receive.tsx b/src/screens/Wallets/components/AccountActions/Receive.tsx new file mode 100644 index 000000000..0afa6e282 --- /dev/null +++ b/src/screens/Wallets/components/AccountActions/Receive.tsx @@ -0,0 +1,22 @@ +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { ReceiveQRCodeIcon } from '@components/svg/icons'; +import Config from '@constants/config'; +import { AccountActionButton } from './ActionButton'; + +export const Receive = () => { + const { t } = useTranslation(); + + const showReceiveFunds = () => { + // TODO + }; + + return ( + + ); +}; diff --git a/src/screens/Wallets/components/AccountActions/Send.tsx b/src/screens/Wallets/components/AccountActions/Send.tsx new file mode 100644 index 000000000..89eaa28c4 --- /dev/null +++ b/src/screens/Wallets/components/AccountActions/Send.tsx @@ -0,0 +1,22 @@ +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { SendIcon } from '@components/svg/icons'; +import Config from '@constants/config'; +import { AccountActionButton } from './ActionButton'; + +export const Send = () => { + const { t } = useTranslation(); + + const navigateToSendScreen = () => { + // TODO + }; + + return ( + + ); +}; diff --git a/src/screens/Wallets/components/AccountActions/Staking.tsx b/src/screens/Wallets/components/AccountActions/Staking.tsx new file mode 100644 index 000000000..b83bd8f62 --- /dev/null +++ b/src/screens/Wallets/components/AccountActions/Staking.tsx @@ -0,0 +1,17 @@ +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { StakeRocketIcon } from '@components/svg/icons'; +import Config from '@constants/config'; +import { AccountActionButton } from './ActionButton'; + +export const Staking = () => { + const { t } = useTranslation(); + + return ( + + ); +}; diff --git a/src/screens/Wallets/components/AccountActions/Swap.tsx b/src/screens/Wallets/components/AccountActions/Swap.tsx new file mode 100644 index 000000000..661cb9471 --- /dev/null +++ b/src/screens/Wallets/components/AccountActions/Swap.tsx @@ -0,0 +1,17 @@ +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { SwapIcon } from '@components/svg/icons'; +import Config from '@constants/config'; +import { AccountActionButton } from './ActionButton'; + +export const Swap = () => { + const { t } = useTranslation(); + + return ( + + ); +}; diff --git a/src/screens/Wallets/components/AccountActions/index.tsx b/src/screens/Wallets/components/AccountActions/index.tsx new file mode 100644 index 000000000..892991775 --- /dev/null +++ b/src/screens/Wallets/components/AccountActions/index.tsx @@ -0,0 +1,22 @@ +import React from 'react'; +import { Row } from '@components/base'; +import { scale } from '@utils/scaling'; +import { Swap } from './Swap'; +import { Send } from './Send'; +import { Receive } from './Receive'; +import { Staking } from './Staking'; + +export const AccountActions = () => { + return ( + + + + + + + ); +}; diff --git a/src/screens/Wallets/components/index.ts b/src/screens/Wallets/components/index.ts index e868aabc7..83d360ca4 100644 --- a/src/screens/Wallets/components/index.ts +++ b/src/screens/Wallets/components/index.ts @@ -2,3 +2,4 @@ export * from './PortfolioBalance'; export * from './Header'; export * from './HomeTabs'; export * from './HomeHighlightsSlider'; +export * from './AccountActions'; From 23770f7982f1d80899941257697bfcceb7a5daef Mon Sep 17 00:00:00 2001 From: illiaa Date: Fri, 25 Aug 2023 12:00:32 +0300 Subject: [PATCH 083/509] Wallet mnemoic verification screens refactoring ui --- .../Wallet/CreateWallet/CreateWalletStep0.tsx | 2 +- .../Wallet/CreateWallet/CreateWalletStep1.tsx | 12 +- .../Wallet/CreateWallet/CreateWalletStep2.tsx | 215 +++++++++++------- .../components/RecoveryPhraseModal.tsx | 2 +- .../components/SuccessBackupComplete.tsx | 27 ++- src/screens/Wallet/Receipt/index.tsx | 2 +- src/screens/Wallet/RestoreWallet/index.tsx | 2 +- src/screens/Wallet/index.tsx | 3 +- 8 files changed, 159 insertions(+), 106 deletions(-) diff --git a/src/screens/Wallet/CreateWallet/CreateWalletStep0.tsx b/src/screens/Wallet/CreateWallet/CreateWalletStep0.tsx index 131d1641e..1b517201a 100644 --- a/src/screens/Wallet/CreateWallet/CreateWalletStep0.tsx +++ b/src/screens/Wallet/CreateWallet/CreateWalletStep0.tsx @@ -7,9 +7,9 @@ import { COLORS } from '@constants/colors'; import { scale, verticalScale } from '@utils/scaling'; import { ElipseIcon } from '@components/svg/icons/Elipse'; import { useNavigation } from '@react-navigation/native'; -import { AddWalletStackNavigationProp } from '@appTypes'; import { RecoveryPhraseModal } from '@screens/Wallet/CreateWallet/components/RecoveryPhraseModal'; import { styles } from '@screens/Wallet/CreateWallet/styles'; +import { AddWalletStackNavigationProp } from '@appTypes/navigation/add-wallet'; export const CreateWalletStep0 = () => { const { top } = useSafeAreaInsets(); diff --git a/src/screens/Wallet/CreateWallet/CreateWalletStep1.tsx b/src/screens/Wallet/CreateWallet/CreateWalletStep1.tsx index 23e8ecb86..1ebe92e7d 100644 --- a/src/screens/Wallet/CreateWallet/CreateWalletStep1.tsx +++ b/src/screens/Wallet/CreateWallet/CreateWalletStep1.tsx @@ -8,9 +8,9 @@ import { useAddWalletContext } from '@contexts'; import { verticalScale, scale } from '@utils/scaling'; import { PrimaryButton } from '@components/modular'; import { useNavigation } from '@react-navigation/native'; -import { AddWalletStackNavigationProp } from '@appTypes'; import { COLORS } from '@constants/colors'; import { WarningIcon } from '@components/svg/icons/Warning'; +import { AddWalletStackNavigationProp } from '@appTypes/navigation/add-wallet'; export const CreateWalletStep1 = () => { const navigation = useNavigation(); @@ -37,8 +37,8 @@ export const CreateWalletStep1 = () => { const renderWord = (word: string, index: number) => { return ( - <> - + + { - - + + ); }; @@ -117,7 +117,7 @@ export const CreateWalletStep1 = () => { - Never share recovery phrase with {'\n'} anyone, keep it safe! + Never share recovery phrase with {'\n'}anyone, keep it safe! diff --git a/src/screens/Wallet/CreateWallet/CreateWalletStep2.tsx b/src/screens/Wallet/CreateWallet/CreateWalletStep2.tsx index d8094d928..1c79887fa 100644 --- a/src/screens/Wallet/CreateWallet/CreateWalletStep2.tsx +++ b/src/screens/Wallet/CreateWallet/CreateWalletStep2.tsx @@ -2,24 +2,24 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { StyleSheet, View } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; import { Button, Row, Spacer, Spinner, Text } from '@components/base'; -import { CopyToClipboardButton, Header } from '@components/composite'; +import { Header } from '@components/composite'; import { useAddWalletContext } from '@contexts'; -import { moderateScale, scale, verticalScale } from '@utils/scaling'; +import { scale, verticalScale } from '@utils/scaling'; import { COLORS } from '@constants/colors'; import { WalletUtils } from '@utils/wallet'; -import { StringUtils } from '@utils/string'; import { useNavigation } from '@react-navigation/native'; -import { AddWalletStackNavigationProp } from '@appTypes'; +import { AddWalletStackNavigationProp } from '@appTypes/navigation/add-wallet'; export const CreateWalletStep2 = () => { const navigation = useNavigation(); const { walletMnemonic } = useAddWalletContext(); + const [walletMnemonicSelected, setWalletMnemonicSelected] = useState< string[] >([]); - const [loading, setLoading] = useState(false); - const [addressToCopy, setAddressToCopy] = useState(''); + const [loading] = useState(false); const [isMnemonicCorrect, setIsMnemonicCorrect] = useState(false); + const walletMnemonicArrayDefault = walletMnemonic.split(' '); const walletMnemonicRandomSorted = useMemo( () => walletMnemonicArrayDefault.sort(() => 0.5 - Math.random()), @@ -27,6 +27,10 @@ export const CreateWalletStep2 = () => { [walletMnemonicArrayDefault.length] ); + const navigateToWalletScreen = () => { + navigation.navigate('SuccessBackupComplete'); + }; + const validateMnemonicOrder = () => { setIsMnemonicCorrect( JSON.stringify(walletMnemonicSelected) === @@ -43,79 +47,108 @@ export const CreateWalletStep2 = () => { JSON.stringify(walletMnemonicArrayDefault) ) { // Alert.alert('Failed'); - // return; } - // console.log('here'); - // setLoading(true); // TODO fix number + // @ts-ignore const { address } = await WalletUtils.processWallet({ number: 0, mnemonic: walletMnemonic, name: '' }); console.log(address); - setAddressToCopy(address); }, [walletMnemonic, walletMnemonicArrayDefault, walletMnemonicSelected]); useEffect(() => { - // console.log('here'); validateMnemonic(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [walletMnemonicSelected]); const numColumns = Math.ceil(walletMnemonicArrayDefault.length / 4); - const renderWord = (word: string) => { - const selectedIdx = walletMnemonicSelected.indexOf(word); - const isCorrect = walletMnemonicArrayDefault.indexOf(word) === selectedIdx; + let globalWordIndex = 0; - const onPress = () => { - if (selectedIdx > -1) { - walletMnemonicSelected.splice(selectedIdx, 1); - } else { - walletMnemonicSelected.push(word); - } - setWalletMnemonicSelected([...walletMnemonicSelected]); - validateMnemonicOrder(); - }; - return ( - <> - - - - ); - }; + ); + } - const navigateToWalletScreen = () => { - navigation.navigate('SuccessBackupComplete'); - }; + return ( + + + + + ); + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [walletMnemonicSelected, walletMnemonicArrayDefault] + ); return ( - -
+ +
{ {Array.isArray(walletMnemonicSelected) && ( {Array.from({ length: numColumns }, (_, columnIndex) => ( - + {walletMnemonicSelected .slice(columnIndex * 4, (columnIndex + 1) * 4) - .map(renderWord)} + .map((word, wordIndex) => + renderWord(word, 'mnemonic', wordIndex) + )} ))} @@ -153,31 +195,27 @@ export const CreateWalletStep2 = () => { {Array.isArray(walletMnemonicRandomSorted) && ( - {walletMnemonicRandomSorted - .filter((word) => walletMnemonicSelected.indexOf(word) === -1) - .map(renderWord)} + {Array.from({ length: numColumns }, (_, columnIndex) => ( + + {walletMnemonicRandomSorted + .slice(columnIndex * 4, (columnIndex + 1) * 4) + .map((word, wordIndex) => + renderWord(word, 'inner', wordIndex) + )} + + ))} )} - - {addressToCopy.length > 0 && ( - - )} + @@ -77,7 +79,7 @@ export const CreateWalletStep0 = () => { fontFamily="Inter_500Medium" color={COLORS.nero} > - Make sure you have a pen and paper ready so you can write it down. + {t('make.sure.to.write.down')} @@ -95,8 +97,7 @@ export const CreateWalletStep0 = () => { /> - I understand that if I lose my recovery phrase, AirDAO cannot - restore it. + {t('checkbox.text')} @@ -114,7 +115,7 @@ export const CreateWalletStep0 = () => { color={selected ? COLORS.white : '#0E0E0E4D'} style={{ marginVertical: scale(12) }} > - Continue + {t('continue.btn')} diff --git a/src/screens/Wallet/CreateWallet/CreateWalletStep1.tsx b/src/screens/Wallet/CreateWallet/CreateWalletStep1.tsx index 1ebe92e7d..5e03975f2 100644 --- a/src/screens/Wallet/CreateWallet/CreateWalletStep1.tsx +++ b/src/screens/Wallet/CreateWallet/CreateWalletStep1.tsx @@ -11,6 +11,7 @@ import { useNavigation } from '@react-navigation/native'; import { COLORS } from '@constants/colors'; import { WarningIcon } from '@components/svg/icons/Warning'; import { AddWalletStackNavigationProp } from '@appTypes/navigation/add-wallet'; +import { useTranslation } from 'react-i18next'; export const CreateWalletStep1 = () => { const navigation = useNavigation(); @@ -18,6 +19,7 @@ export const CreateWalletStep1 = () => { const { walletMnemonic, mnemonicLength, setWalletMnemonic } = useAddWalletContext(); const walletMnemonicArray = walletMnemonic.split(' '); + const { t } = useTranslation(); const init = async () => { setLoading(true); @@ -43,7 +45,7 @@ export const CreateWalletStep1 = () => { style={{ backgroundColor: '#E6E6E6', borderRadius: 48, - width: scale(100) + width: scale(102) }} > { fontFamily="Inter_700Bold" color={COLORS.nero} > - Your recovery phrase + {t('your.recovery.phrase')} { fontFamily="Inter_500Medium" color={COLORS.nero} > - Make sure to write it down as shown. You will verify this later. + {t('verify.text')} {loading && ( @@ -116,14 +118,12 @@ export const CreateWalletStep1 = () => { - - Never share recovery phrase with {'\n'}anyone, keep it safe! - + {t('verification.alert')} - Verify phrase + {t('verify.phrase')} diff --git a/src/screens/Wallet/CreateWallet/CreateWalletStep2.tsx b/src/screens/Wallet/CreateWallet/CreateWalletStep2.tsx index 1c79887fa..b7f4a7a6b 100644 --- a/src/screens/Wallet/CreateWallet/CreateWalletStep2.tsx +++ b/src/screens/Wallet/CreateWallet/CreateWalletStep2.tsx @@ -9,11 +9,12 @@ import { COLORS } from '@constants/colors'; import { WalletUtils } from '@utils/wallet'; import { useNavigation } from '@react-navigation/native'; import { AddWalletStackNavigationProp } from '@appTypes/navigation/add-wallet'; +import { useTranslation } from 'react-i18next'; export const CreateWalletStep2 = () => { const navigation = useNavigation(); const { walletMnemonic } = useAddWalletContext(); - + const { t } = useTranslation(); const [walletMnemonicSelected, setWalletMnemonicSelected] = useState< string[] >([]); @@ -27,10 +28,6 @@ export const CreateWalletStep2 = () => { [walletMnemonicArrayDefault.length] ); - const navigateToWalletScreen = () => { - navigation.navigate('SuccessBackupComplete'); - }; - const validateMnemonicOrder = () => { setIsMnemonicCorrect( JSON.stringify(walletMnemonicSelected) === @@ -146,6 +143,10 @@ export const CreateWalletStep2 = () => { [walletMnemonicSelected, walletMnemonicArrayDefault] ); + const navigateToWalletScreen = () => { + navigation.navigate('SuccessBackupComplete'); + }; + return (
@@ -155,7 +156,7 @@ export const CreateWalletStep2 = () => { fontFamily="Inter_700Bold" color={COLORS.nero} > - Let’s double check + {t('double.check')} { fontFamily="Inter_500Medium" color={COLORS.nero} > - Tap the words in the correct order + {t('tap.words.in.correct.order')} @@ -227,7 +228,7 @@ export const CreateWalletStep2 = () => { color={isMnemonicCorrect ? COLORS.white : '#0E0E0E4D'} style={{ marginVertical: scale(12) }} > - Verify + {t('verify.btn')} diff --git a/src/screens/Wallet/CreateWallet/components/RecoveryPhraseModal.tsx b/src/screens/Wallet/CreateWallet/components/RecoveryPhraseModal.tsx index 7c9aa77ea..9dd194086 100644 --- a/src/screens/Wallet/CreateWallet/components/RecoveryPhraseModal.tsx +++ b/src/screens/Wallet/CreateWallet/components/RecoveryPhraseModal.tsx @@ -9,17 +9,19 @@ import { Text } from '@components/base'; import { useForwardedRef } from '@hooks/useForwardedRef'; import { scale } from '@utils/scaling'; import { COLORS } from '@constants/colors'; +import { useTranslation } from 'react-i18next'; export const RecoveryPhraseModal = forwardRef( (props, ref) => { const localRef: ForwardedRef = useForwardedRef(ref); + const { t } = useTranslation(); return (
- What is a recovery phrase? + {t('recovery.phrase.modal.header')} } backIconVisible={false} @@ -31,8 +33,7 @@ export const RecoveryPhraseModal = forwardRef( fontSize={16} style={{ paddingHorizontal: scale(16) }} > - Your recovery phrase is a set of 12 random words, and it's the only - way to get into your wallet if you lose your device. + {t('recovery.phrase.modal.text')} ); diff --git a/src/screens/Wallet/CreateWallet/components/SuccessBackupComplete.tsx b/src/screens/Wallet/CreateWallet/components/SuccessBackupComplete.tsx index ad7a260e0..51ace3f56 100644 --- a/src/screens/Wallet/CreateWallet/components/SuccessBackupComplete.tsx +++ b/src/screens/Wallet/CreateWallet/components/SuccessBackupComplete.tsx @@ -7,9 +7,11 @@ import { View, StyleSheet } from 'react-native'; import { PrimaryButton } from '@components/modular'; import { useNavigation } from '@react-navigation/native'; import { AddWalletStackNavigationProp } from '@appTypes/navigation/add-wallet'; +import { useTranslation } from 'react-i18next'; export const SuccessBackupComplete = () => { const navigation = useNavigation(); + const { t } = useTranslation(); const navigateToSetUpSecurity = () => { navigation.navigate('WalletScreen'); }; @@ -22,7 +24,7 @@ export const SuccessBackupComplete = () => { fontFamily="Inter_700Bold" color={COLORS.nero} > - Nice move! Backup complete. + {t('backup.complete')} { fontFamily="Inter_500Medium" color={COLORS.nero} > - You backed up your wallet. Now let’s setup your wallet’s security. + {t('backup.complete.text')} @@ -41,7 +43,7 @@ export const SuccessBackupComplete = () => { fontFamily="Inter_500Medium" color={COLORS.white} > - Setup security + {t('setup.security.btn')} diff --git a/src/screens/Wallet/index.tsx b/src/screens/Wallet/index.tsx index 5fb70614f..ea10900e1 100644 --- a/src/screens/Wallet/index.tsx +++ b/src/screens/Wallet/index.tsx @@ -31,6 +31,7 @@ export const WalletScreen = () => { Wallet.fromDBModel(dbWallet) ); setWallets(mappedWallets); + console.log(mappedWallets, 'mappedWallets', wallets, 'wallets'); } } catch (error) { console.log('there was an error fetching wallets'); From 35fb273448f672906cbefbcae45eb62167e393ca Mon Sep 17 00:00:00 2001 From: JavidHaji-zada Date: Fri, 25 Aug 2023 14:17:02 +0400 Subject: [PATCH 086/509] Created SendFunds screen with basic address input --- src/navigation/stacks/Tabs/HomeStack.tsx | 2 + .../SendFunds/components/AddressInput.tsx | 76 +++++++++++++++++++ src/screens/SendFunds/components/index.ts | 1 + src/screens/SendFunds/index.tsx | 27 +++++++ src/screens/SendFunds/styles.ts | 9 +++ src/screens/Wallets/WalletsNew.tsx | 5 +- .../components/AccountActions/Send.tsx | 4 + 7 files changed, 122 insertions(+), 2 deletions(-) create mode 100644 src/screens/SendFunds/components/AddressInput.tsx create mode 100644 src/screens/SendFunds/components/index.ts create mode 100644 src/screens/SendFunds/index.tsx create mode 100644 src/screens/SendFunds/styles.ts diff --git a/src/navigation/stacks/Tabs/HomeStack.tsx b/src/navigation/stacks/Tabs/HomeStack.tsx index 6013fc52e..818101147 100644 --- a/src/navigation/stacks/Tabs/HomeStack.tsx +++ b/src/navigation/stacks/Tabs/HomeStack.tsx @@ -5,6 +5,7 @@ import { AMBMarket } from '@screens/AMBMarket'; import { HomeParamsList } from '@appTypes/navigation/wallets'; import { Notifications } from '@screens/Notifications'; import { getCommonStack } from '../CommonStack'; +import { SendFunds } from '@screens/SendFunds'; const Stack = createNativeStackNavigator(); export const HomeStack = () => { @@ -16,6 +17,7 @@ export const HomeStack = () => { + {getCommonStack(Stack as any)} ); diff --git a/src/screens/SendFunds/components/AddressInput.tsx b/src/screens/SendFunds/components/AddressInput.tsx new file mode 100644 index 000000000..5850169ac --- /dev/null +++ b/src/screens/SendFunds/components/AddressInput.tsx @@ -0,0 +1,76 @@ +import React, { useRef } from 'react'; +import { Alert, useWindowDimensions } from 'react-native'; +import { useTranslation } from 'react-i18next'; +import { BarcodeScanner } from '@components/templates'; +import { + BottomSheet, + BottomSheetRef, + InputWithIcon +} from '@components/composite'; +import { Button, InputRef } from '@components/base'; +import { ScannerIcon } from '@components/svg/icons'; +import { COLORS } from '@constants/colors'; +import { etherumAddressRegex } from '@constants/regex'; + +interface AddressInputProps { + address: string; + onAddressChange: (newAddress: string) => unknown; +} +export const AddressInput = (props: AddressInputProps) => { + const { address, onAddressChange } = props; + const { t } = useTranslation(); + const { height: WINDOW_HEIGHT } = useWindowDimensions(); + const scannerModalRef = useRef(null); + const scanned = useRef(false); + const inputRef = useRef(null); + + const showScanner = () => { + scannerModalRef.current?.show(); + }; + const hideScanner = () => { + scannerModalRef.current?.dismiss(); + }; + + const onQRCodeScanned = (data: string) => { + const res = data.match(etherumAddressRegex); + if (res && res?.length > 0) { + hideScanner(); + inputRef.current?.setText(res[0]); + setTimeout(() => { + onAddressChange(res[0]); + }, 500); + } else if (!scanned.current) { + scanned.current = true; + Alert.alert(t('invalid.qr.code.msg'), '', [ + { + text: t('scan.again.msg'), + onPress: () => { + scanned.current = false; + } + } + ]); + } + }; + + return ( + <> + + + + } + value={address} + onChangeValue={onAddressChange} + /> + + + + + ); +}; diff --git a/src/screens/SendFunds/components/index.ts b/src/screens/SendFunds/components/index.ts new file mode 100644 index 000000000..1845571c0 --- /dev/null +++ b/src/screens/SendFunds/components/index.ts @@ -0,0 +1 @@ +export * from './AddressInput'; diff --git a/src/screens/SendFunds/index.tsx b/src/screens/SendFunds/index.tsx new file mode 100644 index 000000000..b9d7e57f0 --- /dev/null +++ b/src/screens/SendFunds/index.tsx @@ -0,0 +1,27 @@ +import React, { useState } from 'react'; +import { KeyboardAvoidingView } from 'react-native'; +import { useTranslation } from 'react-i18next'; +import { SafeAreaView } from 'react-native-safe-area-context'; +import { Header } from '@components/composite'; +import { COLORS } from '@constants/colors'; +import { AddressInput } from './components'; +import { styles } from './styles'; + +export const SendFunds = () => { + const { t } = useTranslation(); + const [destinationAddress, setDestinationAddress] = useState(''); + return ( + +
+ + + + + ); +}; diff --git a/src/screens/SendFunds/styles.ts b/src/screens/SendFunds/styles.ts new file mode 100644 index 000000000..dc6ac5a0b --- /dev/null +++ b/src/screens/SendFunds/styles.ts @@ -0,0 +1,9 @@ +import { StyleSheet } from 'react-native'; +import { scale, verticalScale } from '@utils/scaling'; + +export const styles = StyleSheet.create({ + container: { + paddingHorizontal: scale(16), + paddingVertical: verticalScale(24) + } +}); diff --git a/src/screens/Wallets/WalletsNew.tsx b/src/screens/Wallets/WalletsNew.tsx index 918911c9c..f5968bbca 100644 --- a/src/screens/Wallets/WalletsNew.tsx +++ b/src/screens/Wallets/WalletsNew.tsx @@ -2,15 +2,15 @@ import React from 'react'; import { View } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; import { WalletCard } from '@components/modular'; +import { Spacer, Spinner } from '@components/base'; import { useCryptoAccountFromHash, useSelectedWalletHash, useUSDPrice } from '@hooks'; +import { verticalScale } from '@utils/scaling'; import { AccountActions, HomeHeader } from './components'; import { styles } from './styles'; -import { Spacer, Spinner } from '@components/base'; -import { verticalScale } from '@utils/scaling'; export const HomeScreen = () => { const selectedWalletHash = useSelectedWalletHash(); @@ -21,6 +21,7 @@ export const HomeScreen = () => { return ( + {accountLoading && } {account && ( diff --git a/src/screens/Wallets/components/AccountActions/Send.tsx b/src/screens/Wallets/components/AccountActions/Send.tsx index 89eaa28c4..fd4a7d5b3 100644 --- a/src/screens/Wallets/components/AccountActions/Send.tsx +++ b/src/screens/Wallets/components/AccountActions/Send.tsx @@ -3,12 +3,16 @@ import { useTranslation } from 'react-i18next'; import { SendIcon } from '@components/svg/icons'; import Config from '@constants/config'; import { AccountActionButton } from './ActionButton'; +import { useNavigation } from '@react-navigation/native'; +import { HomeNavigationProp } from '@appTypes'; export const Send = () => { const { t } = useTranslation(); + const navigation = useNavigation(); const navigateToSendScreen = () => { // TODO + navigation.navigate('SendFunds'); }; return ( From 00f08f29221807dd38a843f85b306efa3d325d06 Mon Sep 17 00:00:00 2001 From: illiaa Date: Fri, 25 Aug 2023 13:55:17 +0300 Subject: [PATCH 087/509] refactored mnemonic validation and Verify button --- .../Wallet/CreateWallet/CreateWalletStep2.tsx | 70 +++++++++++-------- src/screens/Wallet/index.tsx | 1 - 2 files changed, 40 insertions(+), 31 deletions(-) diff --git a/src/screens/Wallet/CreateWallet/CreateWalletStep2.tsx b/src/screens/Wallet/CreateWallet/CreateWalletStep2.tsx index b7f4a7a6b..126fe7e70 100644 --- a/src/screens/Wallet/CreateWallet/CreateWalletStep2.tsx +++ b/src/screens/Wallet/CreateWallet/CreateWalletStep2.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import React, { useCallback, useMemo, useState } from 'react'; import { StyleSheet, View } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; import { Button, Row, Spacer, Spinner, Text } from '@components/base'; @@ -35,31 +35,6 @@ export const CreateWalletStep2 = () => { ); }; - const validateMnemonic = useCallback(async () => { - if (walletMnemonicSelected.length !== walletMnemonicArrayDefault.length) { - return false; - } - if ( - JSON.stringify(walletMnemonicSelected) !== - JSON.stringify(walletMnemonicArrayDefault) - ) { - // Alert.alert('Failed'); - } - // TODO fix number - // @ts-ignore - const { address } = await WalletUtils.processWallet({ - number: 0, - mnemonic: walletMnemonic, - name: '' - }); - console.log(address); - }, [walletMnemonic, walletMnemonicArrayDefault, walletMnemonicSelected]); - - useEffect(() => { - validateMnemonic(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [walletMnemonicSelected]); - const numColumns = Math.ceil(walletMnemonicArrayDefault.length / 4); let globalWordIndex = 0; @@ -143,9 +118,44 @@ export const CreateWalletStep2 = () => { [walletMnemonicSelected, walletMnemonicArrayDefault] ); - const navigateToWalletScreen = () => { - navigation.navigate('SuccessBackupComplete'); - }; + const handleVerifyPress = useCallback(async () => { + if (walletMnemonicSelected.length !== walletMnemonicArrayDefault.length) { + return; + } + const isMnemonicValid = + JSON.stringify(walletMnemonicSelected) === + JSON.stringify(walletMnemonicArrayDefault); + + if (!isMnemonicValid) { + return; + } + + const isOrderCorrect = walletMnemonicSelected.every( + (word, index) => word === walletMnemonicArrayDefault[index] + ); + + if (!isOrderCorrect) { + return; + } + + try { + // @ts-ignore + const { address } = await WalletUtils.processWallet({ + number: 0, + mnemonic: walletMnemonic, + name: '' + }); + console.log(address); + navigation.navigate('SuccessBackupComplete'); + } catch (error) { + console.log(error, 'error'); + } + }, [ + navigation, + walletMnemonic, + walletMnemonicArrayDefault, + walletMnemonicSelected + ]); return ( @@ -211,7 +221,7 @@ export const CreateWalletStep2 = () => { + + + + + + + + + + { + const scrollOffsetX = event.nativeEvent.contentOffset.x; + setCurrentIndex(scrollOffsetX > 0 ? 1 : 0); + }} + > + + + {/**/} + + + + {/**/} + + + + ); +}; diff --git a/src/components/templates/WalletTransactionsAndAssets/styles.ts b/src/components/templates/WalletTransactionsAndAssets/styles.ts new file mode 100644 index 000000000..c6042d940 --- /dev/null +++ b/src/components/templates/WalletTransactionsAndAssets/styles.ts @@ -0,0 +1,28 @@ +import { StyleSheet } from 'react-native'; +import { moderateScale, scale } from '@utils/scaling'; + +export const styles = StyleSheet.create({ + tabLeftTitle: { + justifyContent: 'center', + paddingLeft: scale(55) + }, + tabRightTitle: { + justifyContent: 'center', + paddingRight: scale(55) + }, + tabsIndicator: { + backgroundColor: '#0e0e0e99', + height: 0.5 + }, + notificationsBadge: { + backgroundColor: '#fbf2cb', + borderWidth: 1, + borderColor: '#c8811a', + right: 0, + borderRadius: scale(20), + width: moderateScale(23), + height: moderateScale(24), + justifyContent: 'center', + alignItems: 'center' + } +}); diff --git a/src/localization/locales/English.json b/src/localization/locales/English.json index 0d15d919a..1df96ee8d 100644 --- a/src/localization/locales/English.json +++ b/src/localization/locales/English.json @@ -128,6 +128,8 @@ "no.addresses.yet": "No addresses yet", "no.groups.yet": "No groups yet", + "my.assets":"My assets", + "chart.timeframe.daily": "1D", "chart.timeframe.weekly": "1W", "chart.timeframe.monthly": "1M", diff --git a/src/models/Transaction.ts b/src/models/Transaction.ts index 4ead76b05..3c24e683a 100644 --- a/src/models/Transaction.ts +++ b/src/models/Transaction.ts @@ -11,6 +11,7 @@ export class Transaction { hash: string; from: ExplorerAccount | null; to: ExplorerAccount | null; + status: string; constructor(details: TransactionDTO) { this._id = details._id; @@ -21,5 +22,6 @@ export class Transaction { this.hash = details.hash; this.from = details.from_id ? new ExplorerAccount(details.from_id) : null; this.to = details.to_id ? new ExplorerAccount(details.to_id) : null; + this.status = details.status; } } diff --git a/src/models/dtos/TransactionDTO.ts b/src/models/dtos/TransactionDTO.ts index ffe4f5693..7cacb5d12 100644 --- a/src/models/dtos/TransactionDTO.ts +++ b/src/models/dtos/TransactionDTO.ts @@ -16,4 +16,5 @@ export interface TransactionDTO { from_id: ExplorerAccountDTO; to_id: ExplorerAccountDTO; hash: string; + status: string; } diff --git a/src/screens/Wallet/index.tsx b/src/screens/Wallet/index.tsx index 1e4979668..b718410c6 100644 --- a/src/screens/Wallet/index.tsx +++ b/src/screens/Wallet/index.tsx @@ -5,13 +5,14 @@ import { Header } from '@components/composite'; import { Button, Spacer, Text } from '@components/base'; import { scale, verticalScale } from '@utils/scaling'; import { useNavigation } from '@react-navigation/native'; -import { AddWalletStackNavigationProp, DatabaseTable } from '@appTypes'; import { AddWalletFlowType, useAddWalletContext } from '@contexts'; import { styles } from '@screens/Wallet/styles'; import { COLORS } from '@constants/colors'; import { PrimaryButton } from '@components/modular'; import { Database, WalletDBModel } from '@database'; import { Wallet } from '@models/Wallet'; +import { AddWalletStackNavigationProp } from '@appTypes/navigation/add-wallet'; +import { DatabaseTable } from '@appTypes'; export const WalletScreen = () => { const navigation = useNavigation(); diff --git a/src/screens/Wallets/WalletsNew.tsx b/src/screens/Wallets/WalletsNew.tsx index b71393120..7725b06ad 100644 --- a/src/screens/Wallets/WalletsNew.tsx +++ b/src/screens/Wallets/WalletsNew.tsx @@ -9,7 +9,9 @@ import { } from '@hooks'; import { HomeHeader } from './components'; import { styles } from './styles'; -import { Spinner } from '@components/base'; +import { Spacer, Spinner } from '@components/base'; +import { verticalScale } from '@utils/scaling'; +import { WalletTransactionsAndAssets } from '@components/templates/WalletTransactionsAndAssets/WalletTransactionsAndAssets'; export const HomeScreen = () => { const selectedWalletHash = useSelectedWalletHash(); @@ -22,13 +24,17 @@ export const HomeScreen = () => { {accountLoading && } {account && ( - - - + <> + + + + + + )} ); From 229001c0e42b55f78014a357b8889400cdff1414 Mon Sep 17 00:00:00 2001 From: illiaa Date: Mon, 28 Aug 2023 15:12:08 +0300 Subject: [PATCH 093/509] fixed total supply of asset --- .../WalletAssets/SingleAsset/index.tsx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/components/templates/WalletTransactionsAndAssets/WalletAssets/SingleAsset/index.tsx b/src/components/templates/WalletTransactionsAndAssets/WalletAssets/SingleAsset/index.tsx index f45a64322..b22c9a543 100644 --- a/src/components/templates/WalletTransactionsAndAssets/WalletAssets/SingleAsset/index.tsx +++ b/src/components/templates/WalletTransactionsAndAssets/WalletAssets/SingleAsset/index.tsx @@ -6,7 +6,7 @@ import { scale } from '@utils/scaling'; import { COLORS } from '@constants/colors'; import { styles } from '@components/templates/WalletTransactionsAndAssets/WalletAssets/SingleAsset/styles'; import { ExplorerAccount } from '@models'; -import { useUSDPrice } from '@hooks'; +import { useExplorerInfo, useUSDPrice } from '@hooks'; import { NumberUtils } from '@utils/number'; interface SingleAssetProps { @@ -16,6 +16,7 @@ interface SingleAssetProps { export const SingleAsset = (props: SingleAssetProps): JSX.Element => { const { asset } = props; const usdPrice = useUSDPrice(asset?.ambBalance || 0); + const { data: infoData } = useExplorerInfo(); return ( @@ -53,8 +54,11 @@ export const SingleAsset = (props: SingleAssetProps): JSX.Element => { fontSize={14} color={COLORS.nero} > - % 0.00 - {/*{asset.percentChange}*/} + % + {NumberUtils.formatNumber( + asset.calculatePercentHoldings(infoData?.totalSupply || 1), + 2 + )} From 45e0059e622e2191b312edac7b9815c0c2deddda Mon Sep 17 00:00:00 2001 From: illiaa Date: Tue, 29 Aug 2023 10:21:05 +0300 Subject: [PATCH 094/509] fixed showing all of the assets (currently without balances etc) --- .../WalletAssets/SingleAsset/index.tsx | 12 ++++---- .../WalletAssets/index.tsx | 9 +++--- .../WalletTransactionsAndAssets.tsx | 29 +++++++++++++++++-- src/screens/Wallet/Account/index.tsx | 5 +++- 4 files changed, 41 insertions(+), 14 deletions(-) diff --git a/src/components/templates/WalletTransactionsAndAssets/WalletAssets/SingleAsset/index.tsx b/src/components/templates/WalletTransactionsAndAssets/WalletAssets/SingleAsset/index.tsx index b22c9a543..ae42aa5c6 100644 --- a/src/components/templates/WalletTransactionsAndAssets/WalletAssets/SingleAsset/index.tsx +++ b/src/components/templates/WalletTransactionsAndAssets/WalletAssets/SingleAsset/index.tsx @@ -8,14 +8,16 @@ import { styles } from '@components/templates/WalletTransactionsAndAssets/Wallet import { ExplorerAccount } from '@models'; import { useExplorerInfo, useUSDPrice } from '@hooks'; import { NumberUtils } from '@utils/number'; +import { Wallet } from '@models/Wallet'; interface SingleAssetProps { - asset: ExplorerAccount; + asset: Wallet; + account?: ExplorerAccount; } export const SingleAsset = (props: SingleAssetProps): JSX.Element => { - const { asset } = props; - const usdPrice = useUSDPrice(asset?.ambBalance || 0); + const { account } = props; + const usdPrice = useUSDPrice(account?.ambBalance || 0); const { data: infoData } = useExplorerInfo(); return ( @@ -47,7 +49,7 @@ export const SingleAsset = (props: SingleAssetProps): JSX.Element => { fontSize={14} color={COLORS.gray400} > - {NumberUtils.formatNumber(asset.ambBalance, 2)} AMB + {NumberUtils.formatNumber(account?.ambBalance, 2)} AMB { > % {NumberUtils.formatNumber( - asset.calculatePercentHoldings(infoData?.totalSupply || 1), + account?.calculatePercentHoldings(infoData?.totalSupply || 1), 2 )} diff --git a/src/components/templates/WalletTransactionsAndAssets/WalletAssets/index.tsx b/src/components/templates/WalletTransactionsAndAssets/WalletAssets/index.tsx index 45bc33611..1a3f3e779 100644 --- a/src/components/templates/WalletTransactionsAndAssets/WalletAssets/index.tsx +++ b/src/components/templates/WalletTransactionsAndAssets/WalletAssets/index.tsx @@ -2,19 +2,18 @@ import React from 'react'; import { FlatList, ListRenderItemInfo, View } from 'react-native'; import { Spinner } from '@components/base'; import { SingleAsset } from '@components/templates/WalletTransactionsAndAssets/WalletAssets/SingleAsset'; -import { ExplorerAccount } from '@models'; import { LocalizedRenderEmpty } from '@components/templates'; +import { Wallet } from '@models/Wallet'; interface WalletAssetsProps { - assets: ExplorerAccount[]; + assets: Wallet[]; loading?: boolean; } export const WalletAssets = (props: WalletAssetsProps): JSX.Element => { const { assets, loading } = props; - const renderAssets = ( - args: ListRenderItemInfo - ): JSX.Element => { + + const renderAssets = (args: ListRenderItemInfo): JSX.Element => { return ; }; diff --git a/src/components/templates/WalletTransactionsAndAssets/WalletTransactionsAndAssets.tsx b/src/components/templates/WalletTransactionsAndAssets/WalletTransactionsAndAssets.tsx index ba5fa6256..0a7a9d2a2 100644 --- a/src/components/templates/WalletTransactionsAndAssets/WalletTransactionsAndAssets.tsx +++ b/src/components/templates/WalletTransactionsAndAssets/WalletTransactionsAndAssets.tsx @@ -8,7 +8,6 @@ import Animated, { useSharedValue, withTiming } from 'react-native-reanimated'; -import { SingleAsset } from '@components/templates/WalletTransactionsAndAssets/WalletAssets/SingleAsset'; import { styles } from '@components/templates/WalletTransactionsAndAssets/styles'; import { WalletTransactions } from '@components/templates/WalletTransactionsAndAssets/WalletTransactions'; import { @@ -19,6 +18,9 @@ import { import { useTranslation } from 'react-i18next'; import { SingleTransaction } from '@components/templates/WalletTransactionsAndAssets/WalletTransactions/SingleTransaction'; import { WalletAssets } from '@components/templates/WalletTransactionsAndAssets/WalletAssets'; +import { Database, WalletDBModel } from '@database'; +import { DatabaseTable } from '@appTypes'; +import { Wallet } from '@models/Wallet'; interface WalletTransactionsAndAssetsProps { address: string; @@ -32,7 +34,9 @@ export const WalletTransactionsAndAssets = ( const selectedWalletHash = useSelectedWalletHash(); const { data: account, loading: accountLoading } = useCryptoAccountFromHash(selectedWalletHash); + const [currentIndex, setCurrentIndex] = useState(0); + const [wallets, setWallets] = useState([]); const { t } = useTranslation(); @@ -64,6 +68,26 @@ export const WalletTransactionsAndAssets = ( }); }, [currentIndex, indicatorPosition, tabWidth]); + const fetchLocalWallets = async () => { + try { + const _wallets = (await Database.query( + DatabaseTable.Wallets + )) as WalletDBModel[]; + if (_wallets) { + const mappedWallets = _wallets.map((dbWallet) => + Wallet.fromDBModel(dbWallet) + ); + setWallets(mappedWallets); + } + } catch (error) { + console.log('there was an error fetching wallets'); + } + }; + + useEffect(() => { + fetchLocalWallets(); + }, []); + const scrollToTransactions = () => { scrollView.current?.scrollTo({ x: 0, animated: true }); setCurrentIndex(0); @@ -128,8 +152,7 @@ export const WalletTransactionsAndAssets = ( }} > - - {/**/} + { const route = useRoute>(); From 4ac683c24cbcf5c8c9daefcd293120367f7a50ef Mon Sep 17 00:00:00 2001 From: illiaa Date: Tue, 29 Aug 2023 10:26:33 +0300 Subject: [PATCH 095/509] fixed import --- .../{WalletTransactionsAndAssets.tsx => index.tsx} | 0 src/components/templates/index.ts | 1 + src/localization/locales/English.json | 2 +- src/localization/locales/Turkish.json | 2 ++ src/screens/Wallets/WalletsNew.tsx | 2 +- 5 files changed, 5 insertions(+), 2 deletions(-) rename src/components/templates/WalletTransactionsAndAssets/{WalletTransactionsAndAssets.tsx => index.tsx} (100%) diff --git a/src/components/templates/WalletTransactionsAndAssets/WalletTransactionsAndAssets.tsx b/src/components/templates/WalletTransactionsAndAssets/index.tsx similarity index 100% rename from src/components/templates/WalletTransactionsAndAssets/WalletTransactionsAndAssets.tsx rename to src/components/templates/WalletTransactionsAndAssets/index.tsx diff --git a/src/components/templates/index.ts b/src/components/templates/index.ts index fc76dd1b3..ec079081f 100644 --- a/src/components/templates/index.ts +++ b/src/components/templates/index.ts @@ -19,3 +19,4 @@ export * from './TransactionDetails'; export * from './StatusBar'; export * from './OnboardingView'; export * from './LocalizedRenderEmpty'; +export * from './WalletTransactionsAndAssets'; diff --git a/src/localization/locales/English.json b/src/localization/locales/English.json index 1df96ee8d..4e4b88b0e 100644 --- a/src/localization/locales/English.json +++ b/src/localization/locales/English.json @@ -128,7 +128,7 @@ "no.addresses.yet": "No addresses yet", "no.groups.yet": "No groups yet", - "my.assets":"My assets", + "my.assets": "My assets", "chart.timeframe.daily": "1D", "chart.timeframe.weekly": "1W", diff --git a/src/localization/locales/Turkish.json b/src/localization/locales/Turkish.json index 5ad411aa3..c0d625ead 100644 --- a/src/localization/locales/Turkish.json +++ b/src/localization/locales/Turkish.json @@ -127,6 +127,8 @@ "no.addresses.yet": "Henüz adres yok", "no.groups.yet": "Henüz grup yok", + "my.assets": "", + "chart.timeframe.daily": "1G", "chart.timeframe.weekly": "1H", "chart.timeframe.monthly": "1A", diff --git a/src/screens/Wallets/WalletsNew.tsx b/src/screens/Wallets/WalletsNew.tsx index 7725b06ad..65fdc0bfd 100644 --- a/src/screens/Wallets/WalletsNew.tsx +++ b/src/screens/Wallets/WalletsNew.tsx @@ -11,7 +11,7 @@ import { HomeHeader } from './components'; import { styles } from './styles'; import { Spacer, Spinner } from '@components/base'; import { verticalScale } from '@utils/scaling'; -import { WalletTransactionsAndAssets } from '@components/templates/WalletTransactionsAndAssets/WalletTransactionsAndAssets'; +import { WalletTransactionsAndAssets } from '@components/templates'; export const HomeScreen = () => { const selectedWalletHash = useSelectedWalletHash(); From 6f0719774489b4386ad40d04d3d87584e1d09462 Mon Sep 17 00:00:00 2001 From: illiaa Date: Tue, 29 Aug 2023 10:55:12 +0300 Subject: [PATCH 096/509] fixed comments --- .../Wallet/CreateWallet/CreateWalletStep2.tsx | 40 +------------------ src/utils/account.ts | 35 ++++++++++++++++ 2 files changed, 37 insertions(+), 38 deletions(-) create mode 100644 src/utils/account.ts diff --git a/src/screens/Wallet/CreateWallet/CreateWalletStep2.tsx b/src/screens/Wallet/CreateWallet/CreateWalletStep2.tsx index ba9a05f45..1d61cbf4b 100644 --- a/src/screens/Wallet/CreateWallet/CreateWalletStep2.tsx +++ b/src/screens/Wallet/CreateWallet/CreateWalletStep2.tsx @@ -6,14 +6,11 @@ import { Header } from '@components/composite'; import { useAddWalletContext } from '@contexts'; import { scale, verticalScale } from '@utils/scaling'; import { COLORS } from '@constants/colors'; -import { WalletUtils } from '@utils/wallet'; import { useNavigation } from '@react-navigation/native'; import { AddWalletStackNavigationProp } from '@appTypes/navigation/add-wallet'; import { useTranslation } from 'react-i18next'; import { styles } from '@screens/Wallet/CreateWallet/styles'; -import { Database } from '@database'; -import { DatabaseTable } from '@appTypes'; -import AirDAOKeysForRef from '@lib/helpers/AirDAOKeysForRef'; +import { AccountUtils } from '@utils/account'; export const CreateWalletStep2 = () => { const navigation = useNavigation(); @@ -139,40 +136,7 @@ export const CreateWalletStep2 = () => { } try { - // @ts-ignore - const { address } = await WalletUtils.processWallet({ - number: 0, - mnemonic: walletMnemonic, - name: '' - }); - - const account = await AirDAOKeysForRef.discoverPublicAndPrivate({ - mnemonic: walletMnemonic - }); - - console.log(account, 'account here 1'); - const accountInfo = { - address, - name: '', - derivationPath: '', - derivationIndex: 0, - derivationType: '', - alreadyShown: 0, - walletPubId: 0, - status: 0, - isMain: 0, - transactionsScanTime: 0, - transactionsScanLog: '', - transactionsScanError: '', - changesLog: '', - currencyCode: 'AMB' - }; - - await Database.createModel(DatabaseTable.Accounts, accountInfo); - - console.log(accountInfo, 'accountInfo here 2'); - - console.log(address, 'address here 3'); + await AccountUtils.addAccountInfoToDatabase(walletMnemonic); navigation.navigate('SuccessBackupComplete'); } catch (error) { console.log(error, 'error'); diff --git a/src/utils/account.ts b/src/utils/account.ts new file mode 100644 index 000000000..8e4578d60 --- /dev/null +++ b/src/utils/account.ts @@ -0,0 +1,35 @@ +import { Database } from '@database'; +import { DatabaseTable } from '@appTypes'; +import { WalletUtils } from '@utils/wallet'; + +const addAccountInfoToDatabase = async (walletMnemonic: string) => { + try { + const { address } = await WalletUtils.processWallet({ + number: 0, + mnemonic: walletMnemonic, + name: '' + }); + + const accountInfo = { + address, + name: '', + derivationPath: 'm/44/60/0/0/0', + derivationIndex: 0, + derivationType: '', + alreadyShown: 0, + walletPubId: 0, + status: 0, + isMain: 0, + transactionsScanTime: 0, + transactionsScanLog: '', + transactionsScanError: '', + changesLog: '', + currencyCode: 'AMB' + }; + + await Database.createModel(DatabaseTable.Accounts, accountInfo); + } catch (error) { + console.log(error, 'error'); + } +}; +export const AccountUtils = { addAccountInfoToDatabase }; From 95425f4241bf7ada4f35934290c109eb85d1fc38 Mon Sep 17 00:00:00 2001 From: illiaa Date: Tue, 29 Aug 2023 14:55:46 +0300 Subject: [PATCH 097/509] added single asset screen + refactored amb statistics screen --- src/appTypes/navigation/index.ts | 1 + src/appTypes/navigation/wallets.ts | 2 + src/components/svg/icons/Statistics.tsx | 23 ++++ .../templates/AMBPriceHistory/index.tsx | 14 +-- .../templates/AMBPriceHistory/styles.ts | 1 - .../BottomSheetWalletCreateOrImport/index.tsx | 63 +++++++++++ .../WalletAssets/SingleAsset/index.tsx | 5 +- .../WalletAssets/index.tsx | 23 +++- src/components/templates/index.ts | 1 + src/localization/locales/English.json | 7 ++ src/localization/locales/Turkish.json | 7 ++ src/models/Wallet.ts | 1 - src/models/index.ts | 1 + src/navigation/stacks/Tabs/HomeStack.tsx | 2 + src/navigation/stacks/Tabs/WalletStack.tsx | 2 +- src/screens/AMBMarket/components/About.tsx | 2 +- .../AMBMarket/components/DetailedInfo.tsx | 2 +- .../AMBMarket/components/PriceInfo.tsx | 2 +- src/screens/AMBMarket/index.tsx | 48 ++++++--- src/screens/AMBMarket/styles.ts | 5 +- src/screens/Asset/index.tsx | 100 ++++++++++++++++++ .../components/SuccessBackupComplete.tsx | 2 +- src/screens/Wallets/components/Header.tsx | 72 +++++++++---- 23 files changed, 321 insertions(+), 65 deletions(-) create mode 100644 src/components/svg/icons/Statistics.tsx create mode 100644 src/components/templates/BottomSheetWalletCreateOrImport/index.tsx create mode 100644 src/screens/Asset/index.tsx diff --git a/src/appTypes/navigation/index.ts b/src/appTypes/navigation/index.ts index 897217ab9..60dadc11f 100644 --- a/src/appTypes/navigation/index.ts +++ b/src/appTypes/navigation/index.ts @@ -6,3 +6,4 @@ export * from './search'; export * from './settings'; export * from './tabs'; export * from './wallets'; +export * from './add-wallet'; diff --git a/src/appTypes/navigation/wallets.ts b/src/appTypes/navigation/wallets.ts index f524c64f4..c0552fe73 100644 --- a/src/appTypes/navigation/wallets.ts +++ b/src/appTypes/navigation/wallets.ts @@ -3,11 +3,13 @@ import { BottomTabNavigationProp } from '@react-navigation/bottom-tabs'; import { NativeStackNavigationProp } from '@react-navigation/native-stack'; import { TabsParamsList } from './tabs'; import { CommonStackParamsList } from './common'; +import { ExplorerAccount } from '@models'; export type HomeParamsList = { HomeScreen: undefined; AMBMarketScreen: undefined; Notifications: undefined; + AssetScreen: { asset: ExplorerAccount }; } & CommonStackParamsList; export type WalletsNavigationProp = CompositeNavigationProp< diff --git a/src/components/svg/icons/Statistics.tsx b/src/components/svg/icons/Statistics.tsx new file mode 100644 index 000000000..ae6472164 --- /dev/null +++ b/src/components/svg/icons/Statistics.tsx @@ -0,0 +1,23 @@ +import React from 'react'; +import { IconProps } from '@components/svg/icons/Icon.types'; +import Svg, { Path } from 'react-native-svg'; + +export function StatisticsLogo(props: IconProps) { + const { scale = 1, color = '#393B40' } = props; + const width = 24; + const height = 25; + return ( + + + + ); +} diff --git a/src/components/templates/AMBPriceHistory/index.tsx b/src/components/templates/AMBPriceHistory/index.tsx index 81e7b4f96..59c4204a0 100644 --- a/src/components/templates/AMBPriceHistory/index.tsx +++ b/src/components/templates/AMBPriceHistory/index.tsx @@ -129,18 +129,6 @@ export const AMBPriceHistory = (props: AMBPriceHistoryProps) => { return ( - - - - - AirDAO (AMB) - - { testID="Badge_Button" > ((props, ref) => { + const localRef: ForwardedRef = useForwardedRef(ref); + const navigation = useNavigation(); + const { t } = useTranslation(); + + const navigateToWalletCreate = () => { + navigation.navigate('Wallet'); + localRef.current?.dismiss(); + }; + + return ( + + + + + {t('add.wallet')} + + + + + {t('create.new.wallet')} + + + + + + + ); +}); diff --git a/src/components/templates/WalletTransactionsAndAssets/WalletAssets/SingleAsset/index.tsx b/src/components/templates/WalletTransactionsAndAssets/WalletAssets/SingleAsset/index.tsx index ae42aa5c6..e781c57bb 100644 --- a/src/components/templates/WalletTransactionsAndAssets/WalletAssets/SingleAsset/index.tsx +++ b/src/components/templates/WalletTransactionsAndAssets/WalletAssets/SingleAsset/index.tsx @@ -5,10 +5,9 @@ import { AssetLogo } from '@components/svg/icons/Asset'; import { scale } from '@utils/scaling'; import { COLORS } from '@constants/colors'; import { styles } from '@components/templates/WalletTransactionsAndAssets/WalletAssets/SingleAsset/styles'; -import { ExplorerAccount } from '@models'; import { useExplorerInfo, useUSDPrice } from '@hooks'; import { NumberUtils } from '@utils/number'; -import { Wallet } from '@models/Wallet'; +import { ExplorerAccount, Wallet } from '@models'; interface SingleAssetProps { asset: Wallet; @@ -49,6 +48,7 @@ export const SingleAsset = (props: SingleAssetProps): JSX.Element => { fontSize={14} color={COLORS.gray400} > + {/* @ts-ignore */} {NumberUtils.formatNumber(account?.ambBalance, 2)} AMB { > % {NumberUtils.formatNumber( + // @ts-ignore account?.calculatePercentHoldings(infoData?.totalSupply || 1), 2 )} diff --git a/src/components/templates/WalletTransactionsAndAssets/WalletAssets/index.tsx b/src/components/templates/WalletTransactionsAndAssets/WalletAssets/index.tsx index 1a3f3e779..3326bd21b 100644 --- a/src/components/templates/WalletTransactionsAndAssets/WalletAssets/index.tsx +++ b/src/components/templates/WalletTransactionsAndAssets/WalletAssets/index.tsx @@ -1,9 +1,17 @@ import React from 'react'; -import { FlatList, ListRenderItemInfo, View } from 'react-native'; +import { + FlatList, + ListRenderItemInfo, + TouchableOpacity, + View +} from 'react-native'; import { Spinner } from '@components/base'; import { SingleAsset } from '@components/templates/WalletTransactionsAndAssets/WalletAssets/SingleAsset'; import { LocalizedRenderEmpty } from '@components/templates'; import { Wallet } from '@models/Wallet'; +import { useNavigation } from '@react-navigation/native'; +import { WalletsNavigationProp } from '@appTypes'; +import { ExplorerAccount } from '@models'; interface WalletAssetsProps { assets: Wallet[]; @@ -12,9 +20,20 @@ interface WalletAssetsProps { export const WalletAssets = (props: WalletAssetsProps): JSX.Element => { const { assets, loading } = props; + const navigation = useNavigation(); + + const navigateToAssetScreen = (asset: ExplorerAccount) => { + navigation.navigate('AssetScreen', { asset }); + }; const renderAssets = (args: ListRenderItemInfo): JSX.Element => { - return ; + const { item } = args; + + return ( + navigateToAssetScreen(item)}> + + + ); }; return ( diff --git a/src/components/templates/index.ts b/src/components/templates/index.ts index ec079081f..2df8e6db4 100644 --- a/src/components/templates/index.ts +++ b/src/components/templates/index.ts @@ -20,3 +20,4 @@ export * from './StatusBar'; export * from './OnboardingView'; export * from './LocalizedRenderEmpty'; export * from './WalletTransactionsAndAssets'; +export * from './BottomSheetWalletCreateOrImport'; diff --git a/src/localization/locales/English.json b/src/localization/locales/English.json index 4e4b88b0e..5966263be 100644 --- a/src/localization/locales/English.json +++ b/src/localization/locales/English.json @@ -129,6 +129,13 @@ "no.groups.yet": "No groups yet", "my.assets": "My assets", + "add.wallet": "Add wallet", + "create.new.wallet": "Create new wallet", + "import.existing.wallet": "Import existing wallet", + "about.airdao": "About AirDAO", + "stats": "Stats", + "markets": "Markets", + "your.balance": "Your balance", "chart.timeframe.daily": "1D", "chart.timeframe.weekly": "1W", diff --git a/src/localization/locales/Turkish.json b/src/localization/locales/Turkish.json index c0d625ead..7822d07f0 100644 --- a/src/localization/locales/Turkish.json +++ b/src/localization/locales/Turkish.json @@ -128,6 +128,13 @@ "no.groups.yet": "Henüz grup yok", "my.assets": "", + "add.wallet": "", + "create.new.wallet": "", + "import.existing.wallet": "", + "about.airdao": "", + "stats": "", + "markets": "", + "your.balance": "", "chart.timeframe.daily": "1G", "chart.timeframe.weekly": "1H", diff --git a/src/models/Wallet.ts b/src/models/Wallet.ts index 284a142ae..81f624378 100644 --- a/src/models/Wallet.ts +++ b/src/models/Wallet.ts @@ -22,7 +22,6 @@ export class Wallet { this.name = details.name; this.mnemonic = details.mnemonic; this.number = details.number; - // @ilya I have no idea what below fields correspond this.cashback = details.cashback || 'empty_cashback'; this.isBackedUp = details.isBackedUp || 0; this.isHideTransactionForFee = details.isHideTransactionForFee || 1; diff --git a/src/models/index.ts b/src/models/index.ts index 794eaed13..b58d30bdb 100644 --- a/src/models/index.ts +++ b/src/models/index.ts @@ -4,3 +4,4 @@ export * from './Transaction'; export * from './Explorer'; export * from './Notification'; export * from './AccountList'; +export * from './Wallet'; diff --git a/src/navigation/stacks/Tabs/HomeStack.tsx b/src/navigation/stacks/Tabs/HomeStack.tsx index 6013fc52e..97d43ebc8 100644 --- a/src/navigation/stacks/Tabs/HomeStack.tsx +++ b/src/navigation/stacks/Tabs/HomeStack.tsx @@ -5,6 +5,7 @@ import { AMBMarket } from '@screens/AMBMarket'; import { HomeParamsList } from '@appTypes/navigation/wallets'; import { Notifications } from '@screens/Notifications'; import { getCommonStack } from '../CommonStack'; +import { AssetScreen } from '@screens/Asset'; const Stack = createNativeStackNavigator(); export const HomeStack = () => { @@ -16,6 +17,7 @@ export const HomeStack = () => { + {getCommonStack(Stack as any)} ); diff --git a/src/navigation/stacks/Tabs/WalletStack.tsx b/src/navigation/stacks/Tabs/WalletStack.tsx index a13441b32..46eed85a4 100644 --- a/src/navigation/stacks/Tabs/WalletStack.tsx +++ b/src/navigation/stacks/Tabs/WalletStack.tsx @@ -1,6 +1,5 @@ import React from 'react'; import { createNativeStackNavigator } from '@react-navigation/native-stack'; -import { WalletStackParamsList } from '@appTypes'; import { WalletScreen } from '@screens/Wallet'; import { CreateWalletStep1, @@ -13,6 +12,7 @@ import { WalletAccount } from '@screens/Wallet/Account'; import { ReceiptScreen } from '@screens/Wallet/Receipt'; import { SendCryptoProvider } from '@contexts/SendCrypto/SendCrypto.context'; import { SuccessBackupComplete } from '@screens/Wallet/CreateWallet/components/SuccessBackupComplete'; +import { WalletStackParamsList } from '@appTypes/navigation/add-wallet'; const Stack = createNativeStackNavigator(); export const WalletStack = () => { diff --git a/src/screens/AMBMarket/components/About.tsx b/src/screens/AMBMarket/components/About.tsx index e2b1b3423..e5854a308 100644 --- a/src/screens/AMBMarket/components/About.tsx +++ b/src/screens/AMBMarket/components/About.tsx @@ -79,7 +79,7 @@ export function AMBAbout(): JSX.Element { const { t } = useTranslation(); return ( - + {t('statistics.text')} diff --git a/src/screens/AMBMarket/components/DetailedInfo.tsx b/src/screens/AMBMarket/components/DetailedInfo.tsx index e14c8cd1e..93c24c6f2 100644 --- a/src/screens/AMBMarket/components/DetailedInfo.tsx +++ b/src/screens/AMBMarket/components/DetailedInfo.tsx @@ -29,7 +29,7 @@ export function AMBDetailedInfo(props: AMBDetailedInfoProps): JSX.Element { + + } + style={{ shadowColor: 'transparent' }} + /> + + + + {t('your.balance')} + + + + {asset.ambBalance} AMB + + + + ${NumberUtils.formatNumber(usdPrice, 2)} + + } + color="#2F2B430D" + /> + + + + +2.45% + + + ($10.98) + + + + + Buttons + + + + Transactions + + + {/**/} + + ); +}; diff --git a/src/screens/Wallet/CreateWallet/components/SuccessBackupComplete.tsx b/src/screens/Wallet/CreateWallet/components/SuccessBackupComplete.tsx index eadaac113..3d92f2cca 100644 --- a/src/screens/Wallet/CreateWallet/components/SuccessBackupComplete.tsx +++ b/src/screens/Wallet/CreateWallet/components/SuccessBackupComplete.tsx @@ -6,7 +6,7 @@ import { scale, verticalScale } from '@utils/scaling'; import { View } from 'react-native'; import { PrimaryButton } from '@components/modular'; import { useNavigation } from '@react-navigation/native'; -import { AddWalletStackNavigationProp } from '@appTypes'; +import { AddWalletStackNavigationProp } from '@appTypes/navigation/add-wallet'; export const SuccessBackupComplete = () => { const navigation = useNavigation(); diff --git a/src/screens/Wallets/components/Header.tsx b/src/screens/Wallets/components/Header.tsx index 937c2a7d8..d2bda45ba 100644 --- a/src/screens/Wallets/components/Header.tsx +++ b/src/screens/Wallets/components/Header.tsx @@ -8,11 +8,14 @@ import { } from 'react-native'; import { useNavigation } from '@react-navigation/native'; import { BottomSheet, BottomSheetRef, Header } from '@components/composite'; -import { NotificationIcon, ScannerIcon } from '@components/svg/icons'; +import { AddIcon, NotificationIcon, ScannerIcon } from '@components/svg/icons'; import { moderateScale, scale, verticalScale } from '@utils/scaling'; import { Button, Spacer, Text } from '@components/base'; import { WalletsNavigationProp } from '@appTypes/navigation'; -import { BarcodeScanner } from '@components/templates'; +import { + BarcodeScanner, + BottomSheetWalletCreateOrImport +} from '@components/templates'; import { etherumAddressRegex } from '@constants/regex'; import { OnboardingView } from '@components/templates/OnboardingView'; import { useNotificationsQuery } from '@hooks'; @@ -25,6 +28,7 @@ export const HomeHeader = React.memo((): JSX.Element => { const navigation = useNavigation(); const { height: WINDOW_HEIGHT } = useWindowDimensions(); const scanner = useRef(null); + const walletImportCreate = useRef(null); const scanned = useRef(false); const { data: notifications } = useNotificationsQuery(); const newNotificationsCount = useNewNotificationsCount(); @@ -87,7 +91,30 @@ export const HomeHeader = React.memo((): JSX.Element => { const renderContentRight = useMemo(() => { return ( <> - + + + + + + + + + - - - - + + + ); - }, [WINDOW_HEIGHT, onQRCodeScanned]); + }, [openWalletImportCreateModal]); const headerStyles = useMemo(() => { return { ...styles.container }; @@ -167,5 +190,10 @@ const styles = StyleSheet.create({ height: moderateScale(18), justifyContent: 'center', alignItems: 'center' + }, + addOrImportWalletButton: { + backgroundColor: COLORS.charcoal, + width: scale(38), + height: scale(38) } }); From c91bcda7c5d75a36b6e8f4ef7365ce807ae38943 Mon Sep 17 00:00:00 2001 From: illiaa Date: Tue, 29 Aug 2023 18:37:35 +0300 Subject: [PATCH 098/509] fixed transactions + small refactoring of colors --- src/api/explorer-service.ts | 3 + .../SingleTransaction/index.tsx | 19 ++++--- .../WalletTransactions/index.tsx | 2 +- .../WalletTransactionsAndAssets/index.tsx | 4 +- .../WalletTransactionsAndAssets/styles.ts | 7 ++- src/constants/colors.ts | 4 ++ src/constants/config.ts | 2 +- .../AMBMarket/components/BottomSheetTrade.tsx | 3 +- src/screens/AMBMarket/styles.ts | 2 +- src/screens/Asset/index.tsx | 57 +++++++++++++++++-- .../Wallet/CreateWallet/CreateWalletStep0.tsx | 2 +- .../Wallet/CreateWallet/CreateWalletStep1.tsx | 4 +- .../Wallet/CreateWallet/CreateWalletStep2.tsx | 4 +- 13 files changed, 84 insertions(+), 29 deletions(-) diff --git a/src/api/explorer-service.ts b/src/api/explorer-service.ts index 3c4f08122..9e86cb7d4 100644 --- a/src/api/explorer-service.ts +++ b/src/api/explorer-service.ts @@ -77,6 +77,9 @@ const getTransactionsOfAccount = async ( const response = await axios.get( `${exploreApiUrl}/accounts/${address}/transactions?page=${page}&limit=${limit}&type=${type}` ); + console.log( + `${exploreApiUrl}/accounts/${address}/transactions?page=${page}&limit=${limit}&type=${type}` + ); return { data: response.data.data, next: response.data.pagination.hasNext ? (page + 1).toString() : null diff --git a/src/components/templates/WalletTransactionsAndAssets/WalletTransactions/SingleTransaction/index.tsx b/src/components/templates/WalletTransactionsAndAssets/WalletTransactions/SingleTransaction/index.tsx index 3683d4e85..4671dc176 100644 --- a/src/components/templates/WalletTransactionsAndAssets/WalletTransactions/SingleTransaction/index.tsx +++ b/src/components/templates/WalletTransactionsAndAssets/WalletTransactions/SingleTransaction/index.tsx @@ -6,6 +6,8 @@ import { AssetLogo } from '@components/svg/icons/Asset'; import { styles } from '@components/templates/WalletTransactionsAndAssets/WalletAssets/SingleAsset/styles'; import { scale } from '@utils/scaling'; import { COLORS } from '@constants/colors'; +import { NumberUtils } from '@utils/number'; +import { useExplorerInfo, useUSDPrice } from '@hooks'; interface SingleTransactionProps { transaction: Transaction; @@ -15,6 +17,8 @@ export const SingleTransaction = ( props: SingleTransactionProps ): JSX.Element => { const { transaction } = props; + const usdPrice = useUSDPrice(transaction?.amount || 0); + const { data: infoData } = useExplorerInfo(); return ( @@ -28,36 +32,33 @@ export const SingleTransaction = ( fontSize={16} color={COLORS.nero} > - {/*{transaction.status}*/} - Pending... + {transaction.status} + {/*Pending...*/} - -100 AMB - {/*{transaction.amount}*/} + {/*-100 AMB*/} + {transaction.amount} - {/* @ts-ignore */} - To: 0x45e3...990e3 - {/*To: {transaction.to}*/} + {/* TODO fix to: */} - $10 - {/*{transaction.amount} TODO convert amb to $ */} + ${NumberUtils.formatNumber(usdPrice, 2)} diff --git a/src/components/templates/WalletTransactionsAndAssets/WalletTransactions/index.tsx b/src/components/templates/WalletTransactionsAndAssets/WalletTransactions/index.tsx index be9a0e48c..bf63c2a66 100644 --- a/src/components/templates/WalletTransactionsAndAssets/WalletTransactions/index.tsx +++ b/src/components/templates/WalletTransactionsAndAssets/WalletTransactions/index.tsx @@ -22,7 +22,7 @@ export const WalletTransactions = ( return ( - {!transactions ? ( + {transactions ? ( {t('my.assets')} @@ -116,7 +116,7 @@ export const WalletTransactionsAndAssets = ( + + + + + + + + + {t('send.funds.balance')}: {account.ambBalance} {token} + + + + {estimatedFee > 0 && ( + + + + )} + + + + {t('send.funds.review.transaction')} + + + + + + + + )} + ); diff --git a/src/screens/SendFunds/styles.ts b/src/screens/SendFunds/styles.ts index dc6ac5a0b..e07ea0ce0 100644 --- a/src/screens/SendFunds/styles.ts +++ b/src/screens/SendFunds/styles.ts @@ -1,9 +1,30 @@ import { StyleSheet } from 'react-native'; import { scale, verticalScale } from '@utils/scaling'; +import { COLORS } from '@constants/colors'; export const styles = StyleSheet.create({ container: { - paddingHorizontal: scale(16), - paddingVertical: verticalScale(24) + paddingVertical: verticalScale(24), + flex: 1 + }, + horizontalPadding: { + paddingHorizontal: scale(16) + }, + divider: { + height: 1, + backgroundColor: COLORS.neutral100, + marginVertical: verticalScale(16) + }, + loading: { + alignSelf: 'center' + }, + input: { + shadowColor: 'transparent', + alignSelf: 'center', + fontSize: 46 + }, + addressError: { + marginTop: verticalScale(2), + marginLeft: scale(4) } }); diff --git a/src/utils/currency.ts b/src/utils/currency.ts new file mode 100644 index 000000000..77c54efc6 --- /dev/null +++ b/src/utils/currency.ts @@ -0,0 +1,4 @@ +const toCrypto = (usd: number, rate: number) => usd / rate; +const toUSD = (crypto: number, rate: number) => crypto * rate; + +export const CurrencyUtils = { toCrypto, toUSD }; diff --git a/src/utils/string.ts b/src/utils/string.ts index 7665e90bc..cf4b775ba 100644 --- a/src/utils/string.ts +++ b/src/utils/string.ts @@ -34,4 +34,20 @@ const pluralize = (count: number, str: string, pluralForm?: string): string => { return count + ' ' + finalForm; }; -export const StringUtils = { formatAddress, pluralize }; +const removeNonNumericCharacters = (str: string): string => { + if (!str) return ''; + return str.replace(/[^.\d]+/g, ''); +}; + +const formatNumberInput = (str: string): string => { + let numericChars = removeNonNumericCharacters(str); + if (numericChars[0] === '.') numericChars = '0' + numericChars; + return numericChars; +}; + +export const StringUtils = { + formatAddress, + pluralize, + removeNonNumericCharacters, + formatNumberInput +}; diff --git a/src/utils/wallet.ts b/src/utils/wallet.ts index 621df68c5..a8e85a69d 100644 --- a/src/utils/wallet.ts +++ b/src/utils/wallet.ts @@ -4,7 +4,11 @@ import AirDAOKeysStorage from '@lib/helpers/AirDAOKeysStorage'; import { Crypto } from './crypto'; import { MnemonicUtils } from './mnemonics'; import { CashBackUtils } from './cashback'; -import { Database } from '@database'; +import { Database, WalletDBModel } from '@database'; +import { AirDAOTransfer } from '@crypto/actions/AirDAOTransfer/AirDAOTransfer'; +import { AirDAODictTypes } from '@crypto/common/AirDAODictTypes'; +import AirDAOUtils from '@crypto/common/AirDAOUtils'; +import { Q } from '@nozbe/watermelondb'; const _saveWallet = async ( wallet: Pick @@ -68,4 +72,51 @@ const processWallet = async ( return { address }; }; -export const WalletUtils = { processWallet }; +const sendTx = async ( + walletHash: string, + currencyCode: AirDAODictTypes.Code, + from: string, + to: string, + etherAmount: number, + accountBalanceRaw: string +) => { + const wallets = (await Database.query( + DatabaseTable.Wallets, + Q.where('hash', Q.eq(walletHash)) + )) as WalletDBModel[]; + if (wallets.length > 0) { + const wallet = wallets[0]; + try { + await AirDAOTransfer.sendTx( + { + currencyCode, + walletHash: walletHash, + derivationPath: 'm/44/60/0/0/0', + addressFrom: from, + addressTo: to, + amount: AirDAOUtils.toWei(etherAmount).toString(), + useOnlyConfirmed: Boolean(wallet.useUnconfirmed), + allowReplaceByFee: Boolean(wallet.allowReplaceByFee), + useLegacy: wallet.useLegacy, + isHd: Boolean(wallet.isHd), + accountBalanceRaw, + isTransferAll: false + }, + { + uiErrorConfirmed: true, + selectedFee: { + langMsg: '', + feeForTx: '', + amountForTx: '' + } + }, // TODO fix selected fee + // CACHE_DATA.additionalData + {} + ); + } catch (error) { + throw error; + } + } +}; + +export const WalletUtils = { processWallet, sendTx }; From 5ed750a434f3f15058ca424a242bd4a51cefff99 Mon Sep 17 00:00:00 2001 From: JavidHaji-zada Date: Thu, 31 Aug 2023 13:31:14 +0400 Subject: [PATCH 102/509] implemented Receive Funds modal --- package.json | 5 ++- src/components/base/QRCode/index.tsx | 11 +++++ src/components/base/index.ts | 1 + .../modular/QRCodeWithLogo/index.tsx | 19 ++++++++ .../modular/QRCodeWithLogo/styles.ts | 27 +++++++++++ src/components/modular/index.ts | 3 +- .../templates/ReceiveFunds/index.tsx | 45 +++++++++++++++++++ .../templates/ReceiveFunds/styles.ts | 27 +++++++++++ src/components/templates/index.ts | 1 + src/constants/config.ts | 2 +- src/localization/locales/English.json | 3 +- src/localization/locales/Turkish.json | 7 +-- src/screens/Wallets/WalletsNew.tsx | 2 +- .../AccountActions/ActionButton.tsx | 6 ++- .../components/AccountActions/Receive.tsx | 41 +++++++++++++---- .../components/AccountActions/index.tsx | 8 +++- yarn.lock | 32 ++++++++++++- 17 files changed, 218 insertions(+), 22 deletions(-) create mode 100644 src/components/base/QRCode/index.tsx create mode 100644 src/components/modular/QRCodeWithLogo/index.tsx create mode 100644 src/components/modular/QRCodeWithLogo/styles.ts create mode 100644 src/components/templates/ReceiveFunds/index.tsx create mode 100644 src/components/templates/ReceiveFunds/styles.ts diff --git a/package.json b/package.json index a7da781c0..2f78d83ae 100644 --- a/package.json +++ b/package.json @@ -85,10 +85,10 @@ "expo-store-review": "~6.2.1", "expo-system-ui": "~2.2.1", "expo-updates": "~0.16.4", - "i18n-js": "^4.3.0", - "i18next": "^23.4.4", "fast-text-encoding": "^1.0.6", "https-browserify": "^0.0.1", + "i18n-js": "^4.3.0", + "i18next": "^23.4.4", "jest": "^29.2.1", "jest-expo": "^48.0.2", "lodash": "^4.17.21", @@ -108,6 +108,7 @@ "react-native-os": "^1.2.6", "react-native-pager-view": "6.1.2", "react-native-popover-view": "^5.1.7", + "react-native-qrcode-svg": "^6.2.0", "react-native-quick-base64": "^2.0.7", "react-native-quick-crypto": "^0.6.1", "react-native-reanimated": "~2.14.4", diff --git a/src/components/base/QRCode/index.tsx b/src/components/base/QRCode/index.tsx new file mode 100644 index 000000000..0ebc852d9 --- /dev/null +++ b/src/components/base/QRCode/index.tsx @@ -0,0 +1,11 @@ +import React from 'react'; +import QRCodeSvg from 'react-native-qrcode-svg'; + +export interface QRCodeProps { + size: number; + value: string; +} +export const QRCode = (props: QRCodeProps) => { + const { size, value } = props; + return ; +}; diff --git a/src/components/base/index.ts b/src/components/base/index.ts index 6c72376f0..84d9fb4ab 100644 --- a/src/components/base/index.ts +++ b/src/components/base/index.ts @@ -5,6 +5,7 @@ export * from './Input'; export * from './Text'; export * from './Row'; export * from './KeyboardDismissingView'; +export * from './QRCode'; export * from './Separator'; export * from './Spacer'; export * from './Spinner'; diff --git a/src/components/modular/QRCodeWithLogo/index.tsx b/src/components/modular/QRCodeWithLogo/index.tsx new file mode 100644 index 000000000..5527dbce8 --- /dev/null +++ b/src/components/modular/QRCodeWithLogo/index.tsx @@ -0,0 +1,19 @@ +import React from 'react'; +import { View } from 'react-native'; +import { QRCode, QRCodeProps } from '@components/base'; +import { LogoBigSVG } from '@components/svg/icons'; +import { COLORS } from '@constants/colors'; +import { styles } from './styles'; + +export const QRCodeWithLogo = (props: QRCodeProps) => { + return ( + + + + + + + + + ); +}; diff --git a/src/components/modular/QRCodeWithLogo/styles.ts b/src/components/modular/QRCodeWithLogo/styles.ts new file mode 100644 index 000000000..3e4ae9355 --- /dev/null +++ b/src/components/modular/QRCodeWithLogo/styles.ts @@ -0,0 +1,27 @@ +import { StyleSheet } from 'react-native'; +import { moderateScale, verticalScale } from '@utils/scaling'; +import { COLORS } from '@constants/colors'; + +export const styles = StyleSheet.create({ + container: { + justifyContent: 'center' + }, + logo: { + backgroundColor: COLORS.white, + position: 'absolute', + alignSelf: 'center', + + height: verticalScale(56), + width: verticalScale(56), + borderRadius: verticalScale(16), + padding: moderateScale(6) + }, + logoInner: { + backgroundColor: COLORS.blue600, + height: '100%', + width: '100%', + borderRadius: verticalScale(10), + justifyContent: 'center', + alignItems: 'center' + } +}); diff --git a/src/components/modular/index.ts b/src/components/modular/index.ts index 189bc60c7..dbee74ad6 100644 --- a/src/components/modular/index.ts +++ b/src/components/modular/index.ts @@ -2,6 +2,7 @@ export * from './BottomSheetWithHeader'; export * from './BottomSheetFloat'; export * from './Button'; export * from './CollectionItem'; -export * from './TransactionItem'; +export * from './QRCodeWithLogo'; export * from './Toast'; +export * from './TransactionItem'; export * from './WalletCard'; diff --git a/src/components/templates/ReceiveFunds/index.tsx b/src/components/templates/ReceiveFunds/index.tsx new file mode 100644 index 000000000..0c5eb83af --- /dev/null +++ b/src/components/templates/ReceiveFunds/index.tsx @@ -0,0 +1,45 @@ +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { View } from 'react-native'; +import { Button, Spacer, Text } from '@components/base'; +import { moderateScale, verticalScale } from '@utils/scaling'; +import { COLORS } from '@constants/colors'; +import { styles } from './styles'; +import { QRCodeWithLogo } from '@components/modular'; + +interface ReceiveFundsProps { + address: string; +} +export const ReceiveFunds = (props: ReceiveFundsProps) => { + const { address } = props; + const { t } = useTranslation(); + + return ( + + + {t('receive.funds')} + + + + + + {address} + + + + + ); +}; diff --git a/src/components/templates/ReceiveFunds/styles.ts b/src/components/templates/ReceiveFunds/styles.ts new file mode 100644 index 000000000..f6a798be1 --- /dev/null +++ b/src/components/templates/ReceiveFunds/styles.ts @@ -0,0 +1,27 @@ +import { StyleSheet } from 'react-native'; +import { moderateScale, verticalScale } from '@utils/scaling'; +import { COLORS } from '@constants/colors'; + +export const styles = StyleSheet.create({ + container: { + justifyContent: 'center', + alignItems: 'center' + }, + qrCode: { + backgroundColor: COLORS.white, + padding: moderateScale(18), + borderRadius: moderateScale(24), + borderWidth: 2, + borderColor: COLORS.neutral100, + marginVertical: verticalScale(16) + }, + shareBtn: { + width: '100%', + justifyContent: 'center', + alignItems: 'center', + marginTop: verticalScale(16), + paddingVertical: verticalScale(12), + borderRadius: 1000, + backgroundColor: COLORS.alphaBlack5 + } +}); diff --git a/src/components/templates/index.ts b/src/components/templates/index.ts index fc76dd1b3..969c1729c 100644 --- a/src/components/templates/index.ts +++ b/src/components/templates/index.ts @@ -5,6 +5,7 @@ export * from './WalletList'; export * from './WalletItem'; export * from './BezierChart'; export * from './PortfolioPerformance'; +export * from './ReceiveFunds'; export * from './BottomSheetEditCollection'; export * from './BottomSheetRemoveAddressFromCollection'; export * from './BottomSheetCreateRenameGroup'; diff --git a/src/constants/config.ts b/src/constants/config.ts index 36df3d3a3..8a12abd94 100644 --- a/src/constants/config.ts +++ b/src/constants/config.ts @@ -36,7 +36,7 @@ const envs = { } }; -let Config = envs.stage; +let Config = envs.prod; if (Updates.channel === 'main') { Config = envs.prod; } else if (Updates.channel === 'stage') { diff --git a/src/localization/locales/English.json b/src/localization/locales/English.json index 37edb47e1..cf7af08ff 100644 --- a/src/localization/locales/English.json +++ b/src/localization/locales/English.json @@ -146,6 +146,7 @@ } }, + "receive.funds": "Receive funds", "send.funds": { "balance": "Balance", "estimated.fee": "Estimated fee", @@ -156,6 +157,6 @@ "transfer.fee": "Estimated transfer fee", "use.max": "Use max" }, - + "share": "Share", "transaction.in.progress": "Transaction in progress!" } diff --git a/src/localization/locales/Turkish.json b/src/localization/locales/Turkish.json index 71d2efe9e..197d706da 100644 --- a/src/localization/locales/Turkish.json +++ b/src/localization/locales/Turkish.json @@ -139,12 +139,12 @@ "account": { "actions": { "swap": "Swap", - "send": "Send", - "receive": "Receive", + "send": "Gönder", + "receive": "Al", "stake": "Stake" } }, - + "receive.funds": "Receive funds", "send.funds": { "balance": "Bakiye", "estimated.fee": "Tahmini ücret", @@ -154,5 +154,6 @@ "transfer.fee": "Tahmini transfer ücreti", "use.max": "Hepsini kullan" }, + "share": "Paylaş", "transaction.in.progress": "Transfer gerçekleştiriliyor!" } diff --git a/src/screens/Wallets/WalletsNew.tsx b/src/screens/Wallets/WalletsNew.tsx index f5968bbca..d554f5c39 100644 --- a/src/screens/Wallets/WalletsNew.tsx +++ b/src/screens/Wallets/WalletsNew.tsx @@ -33,7 +33,7 @@ export const HomeScreen = () => { /> - + )} diff --git a/src/screens/Wallets/components/AccountActions/ActionButton.tsx b/src/screens/Wallets/components/AccountActions/ActionButton.tsx index 7517c6fa1..d827ea68c 100644 --- a/src/screens/Wallets/components/AccountActions/ActionButton.tsx +++ b/src/screens/Wallets/components/AccountActions/ActionButton.tsx @@ -18,7 +18,7 @@ export const AccountActionButton = (props: AccountActionButtonProps) => { const textColor = isActive ? COLORS.smokyBlack : COLORS.neutral300; const iconColor = textColor; return ( - + )} + + + + + {status !== 'SUCCESS' ? 'Sending to' : 'Recipient'} + + + {StringUtils.formatAddress(transaction.to, 5, 6)} + + + + + + Amount + + + + {NumberUtils.formatNumber( + transaction.amount || transaction.value.ether, + 2 + )}{' '} + AMB{' '} + + + ${NumberUtils.formatNumber(usdPrice, usdPrice > 1 ? 2 : 4)} + + + + + + + Estimated fee + + + + + {transaction.fee} AMB{' '} + + + ${NumberUtils.formatNumber(usdFee, 2 || 0)} + + + + + + + {status === 'SUCCESS' ? ( + <> + + + + + ) : ( + + + Ok, got it + + + )} + + + ); +}; diff --git a/src/components/templates/WalletTransactionsAndAssets/WalletTransactions/SingleTransaction/index.tsx b/src/components/templates/WalletTransactionsAndAssets/WalletTransactions/SingleTransaction/index.tsx index c12257ebf..59590f941 100644 --- a/src/components/templates/WalletTransactionsAndAssets/WalletTransactions/SingleTransaction/index.tsx +++ b/src/components/templates/WalletTransactionsAndAssets/WalletTransactions/SingleTransaction/index.tsx @@ -1,6 +1,6 @@ -import React from 'react'; +import React, { useRef } from 'react'; import { View } from 'react-native'; -import { Row, Spacer, Text } from '@components/base'; +import { Button, Row, Spacer, Text } from '@components/base'; import { Transaction } from '@models'; import { AssetLogo } from '@components/svg/icons/Asset'; import { styles } from '@components/templates/WalletTransactionsAndAssets/WalletAssets/SingleAsset/styles'; @@ -9,6 +9,9 @@ import { COLORS } from '@constants/colors'; import { NumberUtils } from '@utils/number'; import { useUSDPrice } from '@hooks'; import { StringUtils } from '@utils/string'; +import { BottomSheetRef } from '@components/composite'; +import { BottomSheetFloat } from '@components/modular'; +import { TransactionModal } from '@components/templates'; interface SingleTransactionProps { transaction: Transaction; @@ -19,59 +22,79 @@ export const SingleTransaction = ( ): JSX.Element => { const { transaction } = props; const usdPrice = useUSDPrice(transaction?.amount || transaction.value.ether); + const transactionDetailsModal = useRef(null); + + const showTransactionDetails = () => { + transactionDetailsModal.current?.show(); + }; + + const onPress = () => { + transactionDetailsModal.current?.dismiss(); + }; return ( - - - - - - - - {transaction.status} - - - - - {NumberUtils.formatNumber( - transaction.amount || transaction.value.ether, - 2 - )}{' '} - AMB - - - - - - To:{' '} - {StringUtils.formatAddress( - // @ts-ignore - transaction.to?.address || transaction.to, - 6, - 5 - )} - - - ${NumberUtils.formatNumber(usdPrice, usdPrice > 1 ? 2 : 4)} - + <> + + + + + ); }; diff --git a/src/components/templates/WalletTransactionsAndAssets/WalletTransactions/index.tsx b/src/components/templates/WalletTransactionsAndAssets/WalletTransactions/index.tsx index 38320809f..7e02c8fe6 100644 --- a/src/components/templates/WalletTransactionsAndAssets/WalletTransactions/index.tsx +++ b/src/components/templates/WalletTransactionsAndAssets/WalletTransactions/index.tsx @@ -7,13 +7,14 @@ import { LocalizedRenderEmpty } from '@components/templates'; interface WalletTransactionsProps { transactions: Transaction[]; - // loading: boolean; + // loading: boolean;z } export const WalletTransactions = ( props: WalletTransactionsProps ): JSX.Element => { const { transactions } = props; + const renderTransactions = ( args: ListRenderItemInfo ): JSX.Element => { diff --git a/src/components/templates/index.ts b/src/components/templates/index.ts index 2df8e6db4..59a290648 100644 --- a/src/components/templates/index.ts +++ b/src/components/templates/index.ts @@ -21,3 +21,4 @@ export * from './OnboardingView'; export * from './LocalizedRenderEmpty'; export * from './WalletTransactionsAndAssets'; export * from './BottomSheetWalletCreateOrImport'; +export * from './TransactionModal'; diff --git a/src/constants/config.ts b/src/constants/config.ts index 09fbe71b9..6fc6f3b29 100644 --- a/src/constants/config.ts +++ b/src/constants/config.ts @@ -4,6 +4,7 @@ const envs = { CMC_API_URL: 'https://sandbox-api.coinmarketcap.com', WALLET_API_URL: 'https://wallet-api-api.ambrosus.io', EXPLORER_API_URL: 'https://explorer-api.ambrosus.io', + EXPLORER_API_V2_URL: 'https://explorer-v2-api.ambrosus.io/v2', env: 'prod', debug: { appBuildVersion: '1.0.0', @@ -15,6 +16,7 @@ const envs = { CMC_API_URL: 'https://sandbox-api.coinmarketcap.com', WALLET_API_URL: 'https://wallet-api.ambrosus-test.io', EXPLORER_API_URL: 'https://explorer-api.ambrosus-test.io', + EXPLORER_API_V2_URL: 'https://explorer-v2-api.ambrosus-test.io/v2', env: 'stage', debug: { appBuildVersion: '1.0.0', @@ -24,11 +26,11 @@ const envs = { } }; -const Config = envs.prod; -// if (Updates.channel === 'main') { -// Config = envs.prod; -// } else if (Updates.channel === 'stage') { -// Config = envs.stage; -// } +let Config = envs.stage; +if (Updates.channel === 'main') { + Config = envs.prod; +} else if (Updates.channel === 'stage') { + Config = envs.stage; +} export default Config; From f06f062ea43c5df03011cbd316efc623ccdf8bc9 Mon Sep 17 00:00:00 2001 From: JavidHaji-zada Date: Fri, 1 Sep 2023 12:15:57 +0400 Subject: [PATCH 107/509] Toast component status based implementation --- crypto/common/AirDAOAxios.ts | 9 +- .../composite/Button/CopyToClipboard.tsx | 7 +- src/components/modular/Toast/Toast.body.tsx | 108 +++++++++++------- .../modular/Toast/Toast.constants.tsx | 34 ++++++ src/components/modular/Toast/Toast.styles.ts | 24 ++-- src/components/modular/Toast/Toast.types.ts | 24 +++- src/components/svg/icons/CheckmarkCircle.tsx | 8 +- src/components/svg/icons/Close.tsx | 6 +- src/components/svg/icons/Info.tsx | 20 +++- src/components/svg/icons/QuestionMark.tsx | 23 ++++ src/components/svg/icons/index.ts | 3 + .../BottomSheetCreateRenameGroup/index.tsx | 15 +-- .../templates/BottomSheetEditWallet/index.tsx | 13 ++- .../templates/SearchAddress/index.tsx | 9 +- src/constants/colors.ts | 10 ++ src/screens/Address/index.tsx | 8 +- src/screens/SendFunds/index.tsx | 13 ++- 17 files changed, 236 insertions(+), 98 deletions(-) create mode 100644 src/components/modular/Toast/Toast.constants.tsx create mode 100644 src/components/svg/icons/QuestionMark.tsx diff --git a/crypto/common/AirDAOAxios.ts b/crypto/common/AirDAOAxios.ts index 3d8030829..76d5b422c 100644 --- a/crypto/common/AirDAOAxios.ts +++ b/crypto/common/AirDAOAxios.ts @@ -5,7 +5,7 @@ import AirDAOCryptoLog from './AirDAOCryptoLog'; import axios from 'axios'; import { Platform } from 'react-native'; import CookieManager from '@react-native-cookies/cookies'; -import { Toast, ToastPosition } from '@components/modular/Toast'; +import { Toast, ToastPosition, ToastType } from '@components/modular/Toast'; const CancelToken = axios && typeof axios.CancelToken !== 'undefined' @@ -405,9 +405,10 @@ class AirDAOAxios { ) { CACHE_TIMEOUT_ERROR_SHOWN = now; Toast.show({ - title: 'We are sorry', - message: 'You have bad internet!', - type: ToastPosition.Top + text: 'We are sorry', + subtext: 'You have bad internet!', + type: ToastType.Failed, + position: ToastPosition.Top }); // showModal({ // type: 'INFO_MODAL', diff --git a/src/components/composite/Button/CopyToClipboard.tsx b/src/components/composite/Button/CopyToClipboard.tsx index 761c783eb..4fd1fd63c 100644 --- a/src/components/composite/Button/CopyToClipboard.tsx +++ b/src/components/composite/Button/CopyToClipboard.tsx @@ -4,7 +4,7 @@ import { TextProps } from '@components/base/Text/Text.types'; import { Button, Row, Spacer, Text } from '@components/base'; import { ClipboardFilledIcon, IconProps } from '@components/svg/icons'; import { scale } from '@utils/scaling'; -import { Toast, ToastPosition } from '@components/modular/Toast'; +import { Toast, ToastPosition, ToastType } from '@components/modular/Toast'; import { BaseButtonProps } from '@components/base/Button'; import { useTranslation } from 'react-i18next'; @@ -25,8 +25,9 @@ export const CopyToClipboardButton = ( const onPress = async () => { Toast.show({ - message: t('copied.to.clipboard'), - type: ToastPosition.Bottom + text: t('copied.to.clipboard'), + position: ToastPosition.Bottom, + type: ToastType.Success }); await Clipboard.setStringAsync(textToCopy || textToDisplay); }; diff --git a/src/components/modular/Toast/Toast.body.tsx b/src/components/modular/Toast/Toast.body.tsx index bbb1644c0..12ed29ff6 100644 --- a/src/components/modular/Toast/Toast.body.tsx +++ b/src/components/modular/Toast/Toast.body.tsx @@ -14,28 +14,36 @@ import Animated, { SlideOutUp } from 'react-native-reanimated'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; -import { Button, Row, Text } from '@components/base'; -import { CheckIcon, CloseIcon } from '@components/svg/icons'; +import { Button, Row, Spacer, Text } from '@components/base'; +import { CloseIcon } from '@components/svg/icons'; import { verticalScale } from '@utils/scaling'; -import { styles } from './Toast.styles'; -import { ToastOptions, ToastPosition } from './Toast.types'; +import { + ToastAction, + ToastOptions, + ToastPosition, + ToastType +} from './Toast.types'; import { COLORS } from '@constants/colors'; +import { ToastBg, ToastBorderColor, ToastStatusIcon } from './Toast.constants'; +import { styles } from './Toast.styles'; export const ToastBody = forwardRef((_, ref) => { const { top: topInset, bottom: bottomInset } = useSafeAreaInsets(); const DISTANCE_FROM_EDGE = verticalScale(16); const defaultOptions: ToastOptions = useMemo( () => ({ - message: '', - title: '', - duration: 3500, // ms - type: ToastPosition.Top, - onBodyPress: undefined + text: 'Y', + subtext: '', + type: ToastType.Success, + position: ToastPosition.Top, + actions: [], + onBodyPress: () => null, + duration: 3500 // ms }), [] ); - const [toastVisible, setToastVisible] = useState(false); + const [toastVisible, setToastVisible] = useState(true); const [options, setOptions] = React.useState(defaultOptions); const timerRef = useRef(null); // TODO change any @@ -78,20 +86,38 @@ export const ToastBody = forwardRef((_, ref) => { ) ); - const onUndoPress = () => { - if (typeof options.onUndo === 'function') options.onUndo(); - }; - - const isTopToast = options.type === ToastPosition.Top; + const isTopToast = options.position === ToastPosition.Top; const placement = DISTANCE_FROM_EDGE + (isTopToast ? topInset : bottomInset); if (!toastVisible) return null; + + const renderAction = (action: ToastAction) => { + return ( + + ); + }; + return ( { onPress={options.onBodyPress} > - - - + {ToastStatusIcon[options.type]} - {Boolean(options.title) && ( - - {options.title} - - )} - {options.message} - {typeof options.onUndo === 'function' && ( + {options.text} + + {Boolean(options.subtext) && ( + <> + - {' '} - Undo + {options.subtext} - )} - + + )} + {options.actions && options.actions?.length > 0 && ( + + {options.actions?.map(renderAction)} + + )} ); diff --git a/src/components/modular/Toast/Toast.constants.tsx b/src/components/modular/Toast/Toast.constants.tsx new file mode 100644 index 000000000..6f1ff95a2 --- /dev/null +++ b/src/components/modular/Toast/Toast.constants.tsx @@ -0,0 +1,34 @@ +import { COLORS } from '@constants/colors'; +import { ToastType } from './Toast.types'; +import React, { ReactElement } from 'react'; +import { + CheckmarkCircleIcon, + InfoIcon, + QuestionMarkIcon, + WarningIcon +} from '@components/svg/icons'; +import { Spinner } from '@components/base'; + +export const ToastBg: { [key in ToastType]: string } = { + [ToastType.Highlight]: COLORS.yellow100, + [ToastType.Failed]: COLORS.red100, + [ToastType.Success]: COLORS.teal100, + [ToastType.Information]: COLORS.blue100, + [ToastType.Loading]: COLORS.neutral0 +}; + +export const ToastBorderColor: { [key in ToastType]: string } = { + [ToastType.Highlight]: COLORS.yellow200, + [ToastType.Failed]: COLORS.red200, + [ToastType.Success]: COLORS.teal200, + [ToastType.Information]: COLORS.blue200, + [ToastType.Loading]: COLORS.neutral200 +}; + +export const ToastStatusIcon: { [key in ToastType]: ReactElement } = { + [ToastType.Highlight]: , + [ToastType.Failed]: , + [ToastType.Success]: , + [ToastType.Information]: , + [ToastType.Loading]: +}; diff --git a/src/components/modular/Toast/Toast.styles.ts b/src/components/modular/Toast/Toast.styles.ts index fc85644cd..cbfed553d 100644 --- a/src/components/modular/Toast/Toast.styles.ts +++ b/src/components/modular/Toast/Toast.styles.ts @@ -1,28 +1,36 @@ -import { moderateScale, scale, verticalScale } from '@utils/scaling'; import { StyleSheet } from 'react-native'; +import { moderateScale, scale, verticalScale } from '@utils/scaling'; import { COLORS } from '@constants/colors'; export const styles = StyleSheet.create({ containerStyle: { width: scale(350), borderRadius: moderateScale(13), - backgroundColor: COLORS.green100, position: 'absolute', alignSelf: 'center', paddingVertical: verticalScale(12), paddingHorizontal: scale(16), - borderWidth: 1, - borderColor: COLORS.green200 + borderWidth: 1 }, statusIcon: { - backgroundColor: COLORS.green400, width: moderateScale(24), height: moderateScale(24), borderRadius: moderateScale(12), marginRight: scale(14), justifyContent: 'center', - alignItems: 'center', - alignSelf: 'flex-start' + alignItems: 'center' }, - closeBtn: { alignSelf: 'center', marginLeft: scale(14) } + closeBtn: { alignSelf: 'center', marginLeft: scale(14) }, + actions: { + marginTop: verticalScale(10), + alignItems: 'flex-end' + }, + actionBtn: { + backgroundColor: COLORS.alphaBlack10, + borderRadius: 1000, + justifyContent: 'center', + alignItems: 'center', + paddingVertical: verticalScale(6), + paddingHorizontal: scale(12) + } }); diff --git a/src/components/modular/Toast/Toast.types.ts b/src/components/modular/Toast/Toast.types.ts index 59527a582..fcf04c138 100644 --- a/src/components/modular/Toast/Toast.types.ts +++ b/src/components/modular/Toast/Toast.types.ts @@ -3,11 +3,25 @@ export enum ToastPosition { Bottom = 'bottom' } +export enum ToastType { + Highlight, + Failed, + Success, + Information, + Loading +} + +export interface ToastAction { + label: string; + onPress: () => unknown; +} + export interface ToastOptions { - title?: string; - message: string; + text: string; + subtext?: string; + actions?: ToastAction[]; duration?: number; - type: ToastPosition; - onUndo?: () => void; - onBodyPress?: () => void; + type: ToastType; + position?: ToastPosition; + onBodyPress?: () => unknown; } diff --git a/src/components/svg/icons/CheckmarkCircle.tsx b/src/components/svg/icons/CheckmarkCircle.tsx index 52d1dd480..7d430a3d2 100644 --- a/src/components/svg/icons/CheckmarkCircle.tsx +++ b/src/components/svg/icons/CheckmarkCircle.tsx @@ -4,9 +4,9 @@ import { IconProps } from './Icon.types'; import { COLORS } from '@constants/colors'; export function CheckmarkCircleIcon(props: IconProps) { - const { scale = 1, color = COLORS.deepBlue } = props; - const width = 20, - height = 20; + const { scale = 1, color = COLORS.teal400 } = props; + const width = 24, + height = 24; return ( diff --git a/src/components/svg/icons/Close.tsx b/src/components/svg/icons/Close.tsx index 2ec58279a..97cc18746 100644 --- a/src/components/svg/icons/Close.tsx +++ b/src/components/svg/icons/Close.tsx @@ -4,8 +4,8 @@ import { Path, Svg } from 'react-native-svg'; export function CloseIcon(props: IconProps) { const { scale = 1, color = '#000000' } = props; - const width = 17, - height = 16; + const width = 20, + height = 20; return ( diff --git a/src/components/svg/icons/Info.tsx b/src/components/svg/icons/Info.tsx index 95f29ad8d..cff924ca6 100644 --- a/src/components/svg/icons/Info.tsx +++ b/src/components/svg/icons/Info.tsx @@ -1,14 +1,22 @@ import React from 'react'; -import { IconProps } from '@components/svg/icons/Icon.types'; -import { Path, Svg } from 'react-native-svg'; +import Svg, { Path } from 'react-native-svg'; +import { IconProps } from './Icon.types'; export function InfoIcon(props: IconProps) { - const { width = 17, height = 17 } = props; + const { scale = 1, color = '#FF4747' } = props; + const width = 21; + const height = 21; return ( - + ); diff --git a/src/components/svg/icons/QuestionMark.tsx b/src/components/svg/icons/QuestionMark.tsx new file mode 100644 index 000000000..bdcbfe2b6 --- /dev/null +++ b/src/components/svg/icons/QuestionMark.tsx @@ -0,0 +1,23 @@ +import * as React from 'react'; +import Svg, { Path } from 'react-native-svg'; +import { IconProps } from './Icon.types'; + +export function QuestionMarkIcon(props: IconProps) { + const { scale = 1, color = '#3568DD' } = props; + const width = 21; + const height = 21; + return ( + + + + ); +} diff --git a/src/components/svg/icons/index.ts b/src/components/svg/icons/index.ts index 6ef08edb9..14c37608f 100644 --- a/src/components/svg/icons/index.ts +++ b/src/components/svg/icons/index.ts @@ -25,6 +25,7 @@ export * from './EyeVisible'; export * from './EyeInvisible'; export * from './Filter'; export * from './GitHub'; +export * from './Info'; export * from './List'; export * from './Logo'; export * from './LogoWithName'; @@ -37,6 +38,7 @@ export * from './Notification'; export * from './Options'; export * from './PlayStore'; export * from './PlusIcon'; +export * from './QuestionMark'; export * from './ReceiveQRCode'; export * from './Remove'; export * from './RightArrow'; @@ -55,4 +57,5 @@ export * from './Telegram'; export * from './Trade'; export * from './Trend'; export * from './Twitter'; +export * from './Warning'; export * from './Watchlist'; diff --git a/src/components/templates/BottomSheetCreateRenameGroup/index.tsx b/src/components/templates/BottomSheetCreateRenameGroup/index.tsx index c327c492e..a3708fbc2 100644 --- a/src/components/templates/BottomSheetCreateRenameGroup/index.tsx +++ b/src/components/templates/BottomSheetCreateRenameGroup/index.tsx @@ -17,7 +17,8 @@ import { BottomSheetFloat, PrimaryButton, Toast, - ToastPosition + ToastPosition, + ToastType } from '@components/modular'; import { OnboardingView } from '../OnboardingView'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; @@ -68,24 +69,24 @@ export const BottomSheetCreateRenameGroup = forwardRef( if (handleOnCreateGroup) { handleOnCreateGroup(localGroupName); Toast.show({ - title: '', - message: `${t('toast.way.to.go')} ${StringUtils.formatAddress( + text: `${t('toast.way.to.go')} ${StringUtils.formatAddress( localGroupName, 16, 0 )} ${t('toast.group.created')}`, - type: ToastPosition.Top + position: ToastPosition.Top, + type: ToastType.Success }); } if (handleOnRenameGroup && groupId) { handleOnRenameGroup(groupId, localGroupName); Toast.show({ - title: '', - message: `${StringUtils.formatAddress(groupTitle || '', 16, 0)} ${t( + text: `${StringUtils.formatAddress(groupTitle || '', 16, 0)} ${t( 'toast.has.been.renamed' )} ${StringUtils.formatAddress(localGroupName, 16, 0)}.`, - type: ToastPosition.Top + position: ToastPosition.Top, + type: ToastType.Success }); } diff --git a/src/components/templates/BottomSheetEditWallet/index.tsx b/src/components/templates/BottomSheetEditWallet/index.tsx index de5982246..5b4abc039 100644 --- a/src/components/templates/BottomSheetEditWallet/index.tsx +++ b/src/components/templates/BottomSheetEditWallet/index.tsx @@ -1,7 +1,12 @@ import React, { ForwardedRef, forwardRef, useCallback, useRef } from 'react'; import { View } from 'react-native'; import { BottomSheetProps, BottomSheetRef } from '@components/composite'; -import { BottomSheetFloat, Toast, ToastPosition } from '@components/modular'; +import { + BottomSheetFloat, + Toast, + ToastPosition, + ToastType +} from '@components/modular'; import { Button, Text } from '@components/base'; import { useForwardedRef } from '@hooks/useForwardedRef'; import { ExplorerAccount } from '@models/Explorer'; @@ -80,9 +85,9 @@ export const BottomSheetEditWallet = forwardRef< toggleAddressesInList([wallet], list); dismissThis(); Toast.show({ - title: '', - message: t('toast.Successfully.removed.wallet.from.group'), - type: ToastPosition.Top + text: t('toast.Successfully.removed.wallet.from.group'), + position: ToastPosition.Top, + type: ToastType.Success }); } }, [dismissThis, listsWithCurrentWallet, t, toggleAddressesInList, wallet]); diff --git a/src/components/templates/SearchAddress/index.tsx b/src/components/templates/SearchAddress/index.tsx index e405135e6..0bcb59b20 100644 --- a/src/components/templates/SearchAddress/index.tsx +++ b/src/components/templates/SearchAddress/index.tsx @@ -29,7 +29,7 @@ import { useTransactionDetails } from '@hooks'; import { etherumAddressRegex } from '@constants/regex'; -import { Toast, ToastPosition } from '@components/modular'; +import { Toast, ToastPosition, ToastType } from '@components/modular'; import { useAllAddresses } from '@contexts'; import { CRYPTO_ADDRESS_MAX_LENGTH } from '@constants/variables'; import { COLORS } from '@constants/colors'; @@ -110,9 +110,10 @@ export const SearchAddress = (props: SearchAdressProps): JSX.Element => { const toggleWatchlist = async (isOnWatchlist: boolean) => { if (isOnWatchlist) { Toast.show({ - title: t('toast.address.watchlisted.msg'), - message: t('toast.tap.to.rename.msg'), - type: ToastPosition.Top, + text: t('toast.address.watchlisted.msg'), + subtext: t('toast.tap.to.rename.msg'), + position: ToastPosition.Top, + type: ToastType.Success, onBodyPress: editModal.current?.show }); } diff --git a/src/constants/colors.ts b/src/constants/colors.ts index 130c9de90..c89f6d305 100644 --- a/src/constants/colors.ts +++ b/src/constants/colors.ts @@ -2,6 +2,7 @@ export const COLORS = { alphaBlack5: '#0e0e0e0d', alphaBlack10: '#0e0e0e1a', blue100: '#EDF3FF', + blue200: '#c9d9ff', blue300: '#99B8FF', blue500: '#457EFF', blue600: '#3568DD', @@ -15,7 +16,9 @@ export const COLORS = { green100: '#C3FBE5', green200: '#73E5B7', green400: '#159F80', + neutral0: '#ffffff', neutral100: '#E6E6E6', + neutral200: '#c2c5cc', neutral300: '#a1a6b2', neutral400: '#676B73', neutral600: '#0E0E0E4D', @@ -26,6 +29,13 @@ export const COLORS = { 5: 'rgba(14, 14, 14, 0.05)', 60: 'rgba(14, 14, 14, 0.60)' }, + yellow100: '#fffbb5', + yellow200: '#ffe773', + red100: '#ffd9cd', + red200: '#ffac9a', + teal100: '#c3fbe5', + teal200: '#73e5b7', + teal400: '#159F80', neutralGray: 'rgba(14, 14, 14, 0.05)', asphalt: '#a1a6b2', black: '#000000', diff --git a/src/screens/Address/index.tsx b/src/screens/Address/index.tsx index c0683163b..5e6a0e2ed 100644 --- a/src/screens/Address/index.tsx +++ b/src/screens/Address/index.tsx @@ -20,7 +20,7 @@ import { import { scale, verticalScale } from '@utils/scaling'; import { CommonStackParamsList } from '@appTypes/navigation/common'; import { BottomSheetEditWallet } from '@components/templates/BottomSheetEditWallet'; -import { Toast, ToastPosition } from '@components/modular'; +import { Toast, ToastPosition, ToastType } from '@components/modular'; import { styles } from './styles'; import { COLORS } from '@constants/colors'; import { NumberUtils } from '@utils/number'; @@ -105,7 +105,11 @@ export const AddressDetails = (): JSX.Element => { : `${t('toast.you.removed')}${ finalAccount.name || t('toast.the.message.lower.case') } ${t('toast.from.watchlist')}`; - Toast.show({ message: toastMessage, type: ToastPosition.Top, title: '' }); + Toast.show({ + text: toastMessage, + position: ToastPosition.Top, + type: ToastType.Success + }); }; return ( diff --git a/src/screens/SendFunds/index.tsx b/src/screens/SendFunds/index.tsx index b748be3fc..cd557de81 100644 --- a/src/screens/SendFunds/index.tsx +++ b/src/screens/SendFunds/index.tsx @@ -23,7 +23,12 @@ import { import { verticalScale } from '@utils/scaling'; import { StringUtils } from '@utils/string'; import { AirDAODictTypes } from '@crypto/common/AirDAODictTypes'; -import { PrimaryButton, Toast, ToastPosition } from '@components/modular'; +import { + PrimaryButton, + Toast, + ToastPosition, + ToastType +} from '@components/modular'; import { HomeNavigationProp } from '@appTypes'; import { CurrencyUtils } from '@utils/currency'; import { etherumAddressRegex } from '@constants/regex'; @@ -117,9 +122,9 @@ export const SendFunds = () => { hideReviewModal(); navigation.replace('HomeScreen'); Toast.show({ - type: ToastPosition.Top, - title: t('transaction.in.progress'), - message: '' + position: ToastPosition.Top, + type: ToastType.Success, + text: t('transaction.in.progress') }); } catch (error) { Alert.alert('Transfer failed'); From 7887375a0337efec406188bedeb07e918fe7cadc Mon Sep 17 00:00:00 2001 From: JavidHaji-zada Date: Fri, 1 Sep 2023 12:16:54 +0400 Subject: [PATCH 108/509] Toast default options fix --- src/components/modular/Toast/Toast.body.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/modular/Toast/Toast.body.tsx b/src/components/modular/Toast/Toast.body.tsx index 12ed29ff6..d2546db85 100644 --- a/src/components/modular/Toast/Toast.body.tsx +++ b/src/components/modular/Toast/Toast.body.tsx @@ -32,7 +32,7 @@ export const ToastBody = forwardRef((_, ref) => { const DISTANCE_FROM_EDGE = verticalScale(16); const defaultOptions: ToastOptions = useMemo( () => ({ - text: 'Y', + text: '', subtext: '', type: ToastType.Success, position: ToastPosition.Top, @@ -43,7 +43,7 @@ export const ToastBody = forwardRef((_, ref) => { [] ); - const [toastVisible, setToastVisible] = useState(true); + const [toastVisible, setToastVisible] = useState(false); const [options, setOptions] = React.useState(defaultOptions); const timerRef = useRef(null); // TODO change any From dabc2379338d8447a016833ba269cc615a5ebf65 Mon Sep 17 00:00:00 2001 From: illiaa Date: Fri, 1 Sep 2023 11:45:49 +0300 Subject: [PATCH 109/509] added transactions of mocked address + small fixes to AIRMOB-257 --- src/api/explorer-service.ts | 15 ++-- .../templates/TransactionModal/index.tsx | 69 ++++++++++++++----- .../templates/TransactionModal/styles.ts | 17 +++++ .../WalletAssets/index.tsx | 5 ++ .../WalletTransactions/index.tsx | 7 +- .../WalletTransactionsAndAssets/index.tsx | 58 +++------------- src/constants/colors.ts | 1 + src/screens/Asset/index.tsx | 18 ++--- src/screens/Wallets/WalletsNew.tsx | 36 +++++++++- 9 files changed, 139 insertions(+), 87 deletions(-) create mode 100644 src/components/templates/TransactionModal/styles.ts diff --git a/src/api/explorer-service.ts b/src/api/explorer-service.ts index f920056d4..399a35820 100644 --- a/src/api/explorer-service.ts +++ b/src/api/explorer-service.ts @@ -89,19 +89,20 @@ const getTransactionsOfAccount = async ( const searchWalletV2 = async ( wallet: string -): Promise< - { +): Promise<{ + tokens: { address: string; name: string; balance: { wei: string; ether: number }; - }[] -> => { + }[]; + transactions: Transaction[]; +}> => { try { - // TODO move api to constance const apiUrl = `${explorerapiV2Url}/addresses/${wallet}/all`; const response = await axios.get(apiUrl); - const tokensData = response.data.tokens; - return tokensData; + const tokens = response.data.tokens; + const transactions = response.data.data; + return { tokens, transactions }; } catch (error) { // TODO handle error throw error; diff --git a/src/components/templates/TransactionModal/index.tsx b/src/components/templates/TransactionModal/index.tsx index 661cf2c05..8e1c7ed9f 100644 --- a/src/components/templates/TransactionModal/index.tsx +++ b/src/components/templates/TransactionModal/index.tsx @@ -10,6 +10,7 @@ import { StringUtils } from '@utils/string'; import { NumberUtils } from '@utils/number'; import { useUSDPrice } from '@hooks'; import { AssetLogo } from '@components/svg/icons/Asset'; +import { styles } from '@components/templates/TransactionModal/styles'; interface TransactionModalProps { status: string; @@ -69,7 +70,7 @@ export const TransactionModal = ({ Date @@ -85,7 +86,11 @@ export const TransactionModal = ({ )} - + {status !== 'SUCCESS' ? 'From' : 'Status'} {status !== 'SUCCESS' ? ( @@ -97,14 +102,12 @@ export const TransactionModal = ({ {StringUtils.formatAddress(transaction.from, 5, 6)} ) : ( - - + {status !== 'SUCCESS' ? 'Sending to' : 'Recipient'} - + Amount @@ -143,27 +154,41 @@ export const TransactionModal = ({ )}{' '} AMB{' '} - + ${NumberUtils.formatNumber(usdPrice, usdPrice > 1 ? 2 : 4)} - + Estimated fee - + + + - {transaction.fee} AMB{' '} + {isNaN(transaction.fee) ? '0 AMB ' : `${transaction.fee} AMB`} - - ${NumberUtils.formatNumber(usdFee, 2 || 0)} + + ${isNaN(usdFee) ? '0' : NumberUtils.formatNumber(usdFee, 2)} @@ -172,7 +197,10 @@ export const TransactionModal = ({ {status === 'SUCCESS' ? ( <> - diff --git a/src/constants/colors.ts b/src/constants/colors.ts index 130c9de90..768a4f987 100644 --- a/src/constants/colors.ts +++ b/src/constants/colors.ts @@ -49,6 +49,7 @@ export const COLORS = { chartSelectionLightGreen: '#73E5B7', chartSelectionDarkGreen: '#0A8575', switchGreen: '#53B483', + completedStatus: '#2f9461', lavenderGray: '#C2C5CC', lightGrey: '#828282', lightSilver: '#D9D9D9', diff --git a/src/screens/Asset/index.tsx b/src/screens/Asset/index.tsx index 3a1f0f120..f769bcbd2 100644 --- a/src/screens/Asset/index.tsx +++ b/src/screens/Asset/index.tsx @@ -99,15 +99,15 @@ export const AssetScreen = () => { color={COLORS.gray300} /> - - - %0.00 - - {/* TODO */} - - ($10.98) - - + {/**/} + {/* */} + {/* %0.00*/} + {/* */} + {/* /!* TODO *!/*/} + {/* */} + {/* ($10.98)*/} + {/* */} + {/**/} diff --git a/src/screens/Wallets/WalletsNew.tsx b/src/screens/Wallets/WalletsNew.tsx index e95a458de..71e34d4c0 100644 --- a/src/screens/Wallets/WalletsNew.tsx +++ b/src/screens/Wallets/WalletsNew.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useEffect, useState } from 'react'; import { View } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; import { WalletTransactionsAndAssets } from '@components/templates'; @@ -12,6 +12,8 @@ import { import { verticalScale } from '@utils/scaling'; import { AccountActions, HomeHeader } from './components'; import { styles } from './styles'; +import { API } from '@api/api'; +import { Transaction } from '@models'; export const HomeScreen = () => { const selectedWalletHash = useSelectedWalletHash(); @@ -19,6 +21,33 @@ export const HomeScreen = () => { useCryptoAccountFromHash(selectedWalletHash); const usdPrice = useUSDPrice(account?.ambBalance || 0); + const [tokens, setTokens] = useState< + { + address: string; + name: string; + balance: { wei: string; ether: number }; + }[] + >([]); + + const [transactions, setTransactions] = useState([]); + + const fetchTokensAndTransactions = async () => { + try { + const walletWithTokens = '0x4fB246FAf8FAc198f8e5B524E74ABC6755956696'; + const { tokens, transactions } = await API.explorerService.searchWalletV2( + walletWithTokens + ); + setTokens(tokens); + setTransactions(transactions); + } catch (error) { + console.log(error); + } + }; + + useEffect(() => { + fetchTokensAndTransactions(); + }, []); + return ( @@ -37,7 +66,10 @@ export const HomeScreen = () => { - + )} From 86c99643c0866db41e407bd3a1efc0f402e9ca31 Mon Sep 17 00:00:00 2001 From: illiaa Date: Fri, 1 Sep 2023 11:57:27 +0300 Subject: [PATCH 110/509] added TokenDTO --- src/models/dtos/TokenDTO.ts | 8 ++++++++ src/models/dtos/index.ts | 1 + src/screens/Wallets/WalletsNew.tsx | 10 ++-------- 3 files changed, 11 insertions(+), 8 deletions(-) create mode 100644 src/models/dtos/TokenDTO.ts diff --git a/src/models/dtos/TokenDTO.ts b/src/models/dtos/TokenDTO.ts new file mode 100644 index 000000000..fc9415988 --- /dev/null +++ b/src/models/dtos/TokenDTO.ts @@ -0,0 +1,8 @@ +export interface TokenDTO { + address: string; + name: string; + balance: { + wei: string; + ether: number; + }; +} diff --git a/src/models/dtos/index.ts b/src/models/dtos/index.ts index 4fe7b707d..4bdadef8f 100644 --- a/src/models/dtos/index.ts +++ b/src/models/dtos/index.ts @@ -2,3 +2,4 @@ export * from './AMBTokenDTO'; export * from './Explorer'; export * from './NotificationDTO'; export * from './WatcherInfo'; +export * from './TokenDTO'; diff --git a/src/screens/Wallets/WalletsNew.tsx b/src/screens/Wallets/WalletsNew.tsx index 71e34d4c0..70f35b856 100644 --- a/src/screens/Wallets/WalletsNew.tsx +++ b/src/screens/Wallets/WalletsNew.tsx @@ -13,7 +13,7 @@ import { verticalScale } from '@utils/scaling'; import { AccountActions, HomeHeader } from './components'; import { styles } from './styles'; import { API } from '@api/api'; -import { Transaction } from '@models'; +import { Transaction, TokenDTO } from '@models'; export const HomeScreen = () => { const selectedWalletHash = useSelectedWalletHash(); @@ -21,13 +21,7 @@ export const HomeScreen = () => { useCryptoAccountFromHash(selectedWalletHash); const usdPrice = useUSDPrice(account?.ambBalance || 0); - const [tokens, setTokens] = useState< - { - address: string; - name: string; - balance: { wei: string; ether: number }; - }[] - >([]); + const [tokens, setTokens] = useState([]); const [transactions, setTransactions] = useState([]); From db7d1c33d59f01e46de6220e8d1044a52887816b Mon Sep 17 00:00:00 2001 From: illiaa Date: Fri, 1 Sep 2023 12:46:13 +0300 Subject: [PATCH 111/509] fixed comments --- .../WalletAssets/index.tsx | 16 +++++---- .../WalletTransactions/index.tsx | 8 ++--- .../WalletTransactionsAndAssets/index.tsx | 30 ++++++++-------- src/hooks/query/index.ts | 1 + src/hooks/query/useTokensAndTransactions.ts | 35 +++++++++++++++++++ src/screens/Wallets/WalletsNew.tsx | 33 +++++------------ 6 files changed, 73 insertions(+), 50 deletions(-) create mode 100644 src/hooks/query/useTokensAndTransactions.ts diff --git a/src/components/templates/WalletTransactionsAndAssets/WalletAssets/index.tsx b/src/components/templates/WalletTransactionsAndAssets/WalletAssets/index.tsx index b345fe8d4..ab44ac6a9 100644 --- a/src/components/templates/WalletTransactionsAndAssets/WalletAssets/index.tsx +++ b/src/components/templates/WalletTransactionsAndAssets/WalletAssets/index.tsx @@ -1,19 +1,21 @@ import React from 'react'; import { FlatList, TouchableOpacity, View } from 'react-native'; -import { Spinner } from '@components/base'; import { SingleAsset } from '@components/templates/WalletTransactionsAndAssets/WalletAssets/SingleAsset'; import { LocalizedRenderEmpty } from '@components/templates'; import { WalletsNavigationProp } from '@appTypes'; import { useNavigation } from '@react-navigation/native'; import { verticalScale } from '@utils/scaling'; +import { Spinner } from '@components/base'; interface WalletAssetsProps { - tokens: { - name: string; - address: string; - balance: { wei: string; ether: number }; - }[]; - loading?: boolean; + tokens: + | { + name: string; + address: string; + balance: { wei: string; ether: number }; + }[] + | undefined; + loading: boolean; } export const WalletAssets = (props: WalletAssetsProps): JSX.Element => { diff --git a/src/components/templates/WalletTransactionsAndAssets/WalletTransactions/index.tsx b/src/components/templates/WalletTransactionsAndAssets/WalletTransactions/index.tsx index bd3911522..2d10bbaed 100644 --- a/src/components/templates/WalletTransactionsAndAssets/WalletTransactions/index.tsx +++ b/src/components/templates/WalletTransactionsAndAssets/WalletTransactions/index.tsx @@ -7,14 +7,14 @@ import { LocalizedRenderEmpty } from '@components/templates'; import { verticalScale } from '@utils/scaling'; interface WalletTransactionsProps { - transactions: Transaction[]; - // loading: boolean;z + transactions: Transaction[] | undefined; + loading?: boolean; } export const WalletTransactions = ( props: WalletTransactionsProps ): JSX.Element => { - const { transactions } = props; + const { transactions, loading } = props; const renderTransactions = ( args: ListRenderItemInfo @@ -28,7 +28,7 @@ export const WalletTransactions = ( (loading ? : <>)} + ListFooterComponent={() => (loading ? : <>)} contentContainerStyle={{ flexGrow: 1, paddingBottom: verticalScale(1200) diff --git a/src/components/templates/WalletTransactionsAndAssets/index.tsx b/src/components/templates/WalletTransactionsAndAssets/index.tsx index 88465f8da..0b459fc80 100644 --- a/src/components/templates/WalletTransactionsAndAssets/index.tsx +++ b/src/components/templates/WalletTransactionsAndAssets/index.tsx @@ -10,29 +10,28 @@ import Animated, { } from 'react-native-reanimated'; import { styles } from '@components/templates/WalletTransactionsAndAssets/styles'; import { WalletTransactions } from '@components/templates/WalletTransactionsAndAssets/WalletTransactions'; -import { useCryptoAccountFromHash, useSelectedWalletHash } from '@hooks'; import { useTranslation } from 'react-i18next'; import { WalletAssets } from '@components/templates/WalletTransactionsAndAssets/WalletAssets'; import { Transaction } from '@models'; interface WalletTransactionsAndAssetsProps { - transactions: Transaction[]; - tokens: { - address: string; - name: string; - balance: { wei: string; ether: number }; - }[]; + transactions: Transaction[] | undefined; + tokens: + | { + address: string; + name: string; + balance: { wei: string; ether: number }; + }[] + | undefined; + transactionsLoading: boolean; + tokensLoading: boolean; } export const WalletTransactionsAndAssets = ( props: WalletTransactionsAndAssetsProps ) => { - const { tokens, transactions } = props; + const { tokens, transactions, tokensLoading, transactionsLoading } = props; const scrollView = useRef(null); - const selectedWalletHash = useSelectedWalletHash(); - const { loading: accountLoading } = - useCryptoAccountFromHash(selectedWalletHash); - const [currentIndex, setCurrentIndex] = useState(0); const { t } = useTranslation(); @@ -117,10 +116,13 @@ export const WalletTransactionsAndAssets = ( }} > - + - + diff --git a/src/hooks/query/index.ts b/src/hooks/query/index.ts index c016b9b2a..692c0985c 100644 --- a/src/hooks/query/index.ts +++ b/src/hooks/query/index.ts @@ -8,3 +8,4 @@ export * from './useNotifications'; export * from './useSearchAccount'; export * from './useTransactionsOfAddress'; export * from './useTransactionDetails'; +export * from './useTokensAndTransactions'; diff --git a/src/hooks/query/useTokensAndTransactions.ts b/src/hooks/query/useTokensAndTransactions.ts new file mode 100644 index 000000000..fc91af581 --- /dev/null +++ b/src/hooks/query/useTokensAndTransactions.ts @@ -0,0 +1,35 @@ +import { useQuery } from '@tanstack/react-query'; +import { QueryResponse } from '@appTypes/QueryResponse'; +import { API } from '@api/api'; +import { Transaction, TokenDTO } from '@models'; + +export function useTokensAndTransactions( + walletAddress: string +): QueryResponse< + { tokens: TokenDTO[]; transactions: Transaction[] } | undefined +> { + const { data, error, isInitialLoading } = useQuery<{ + tokens: TokenDTO[]; + transactions: Transaction[]; + }>( + ['tokens-and-transactions', walletAddress], + async () => { + try { + const { tokens, transactions } = + await API.explorerService.searchWalletV2(walletAddress); + return { tokens, transactions }; + } catch (error) { + throw error; + } + }, + { + enabled: !!walletAddress + } + ); + + return { + data, + loading: isInitialLoading, + error + }; +} diff --git a/src/screens/Wallets/WalletsNew.tsx b/src/screens/Wallets/WalletsNew.tsx index 70f35b856..98bdddb47 100644 --- a/src/screens/Wallets/WalletsNew.tsx +++ b/src/screens/Wallets/WalletsNew.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React from 'react'; import { View } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; import { WalletTransactionsAndAssets } from '@components/templates'; @@ -7,13 +7,12 @@ import { Spacer, Spinner } from '@components/base'; import { useCryptoAccountFromHash, useSelectedWalletHash, + useTokensAndTransactions, useUSDPrice } from '@hooks'; import { verticalScale } from '@utils/scaling'; import { AccountActions, HomeHeader } from './components'; import { styles } from './styles'; -import { API } from '@api/api'; -import { Transaction, TokenDTO } from '@models'; export const HomeScreen = () => { const selectedWalletHash = useSelectedWalletHash(); @@ -21,26 +20,8 @@ export const HomeScreen = () => { useCryptoAccountFromHash(selectedWalletHash); const usdPrice = useUSDPrice(account?.ambBalance || 0); - const [tokens, setTokens] = useState([]); - - const [transactions, setTransactions] = useState([]); - - const fetchTokensAndTransactions = async () => { - try { - const walletWithTokens = '0x4fB246FAf8FAc198f8e5B524E74ABC6755956696'; - const { tokens, transactions } = await API.explorerService.searchWalletV2( - walletWithTokens - ); - setTokens(tokens); - setTransactions(transactions); - } catch (error) { - console.log(error); - } - }; - - useEffect(() => { - fetchTokensAndTransactions(); - }, []); + const { data: tokensAndTransactions, loading: tokensAndTransactionsLoading } = + useTokensAndTransactions('0x4fB246FAf8FAc198f8e5B524E74ABC6755956696'); return ( @@ -61,8 +42,10 @@ export const HomeScreen = () => { From bfcf406ea044ad9500060f9ec5b289c622734833 Mon Sep 17 00:00:00 2001 From: illiaa Date: Fri, 1 Sep 2023 15:18:14 +0300 Subject: [PATCH 112/509] styles hotfix --- .../WalletTransactionsAndAssets/WalletTransactions/index.tsx | 3 +-- .../templates/WalletTransactionsAndAssets/index.tsx | 2 +- src/screens/Asset/index.tsx | 4 +++- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/components/templates/WalletTransactionsAndAssets/WalletTransactions/index.tsx b/src/components/templates/WalletTransactionsAndAssets/WalletTransactions/index.tsx index 2d10bbaed..5e47f51df 100644 --- a/src/components/templates/WalletTransactionsAndAssets/WalletTransactions/index.tsx +++ b/src/components/templates/WalletTransactionsAndAssets/WalletTransactions/index.tsx @@ -30,8 +30,7 @@ export const WalletTransactions = ( renderItem={renderTransactions} ListFooterComponent={() => (loading ? : <>)} contentContainerStyle={{ - flexGrow: 1, - paddingBottom: verticalScale(1200) + paddingBottom: 100 }} showsVerticalScrollIndicator={false} /> diff --git a/src/components/templates/WalletTransactionsAndAssets/index.tsx b/src/components/templates/WalletTransactionsAndAssets/index.tsx index 0b459fc80..ad6d052bf 100644 --- a/src/components/templates/WalletTransactionsAndAssets/index.tsx +++ b/src/components/templates/WalletTransactionsAndAssets/index.tsx @@ -118,7 +118,7 @@ export const WalletTransactionsAndAssets = ( - + { {t('transactions')} - + + + ); }; From 2679b6aa101dea2610ec064d8394fd539dea5670 Mon Sep 17 00:00:00 2001 From: illiaa Date: Fri, 1 Sep 2023 18:09:13 +0300 Subject: [PATCH 113/509] fixed flatlist on Assets tab + added transaltions + added icons --- src/components/svg/icons/Mnemoic.tsx | 23 +++++++++ src/components/svg/icons/Success.tsx | 23 +++++++++ src/components/svg/icons/index.ts | 2 + .../templates/TransactionModal/index.tsx | 48 ++++++++++++------- .../WalletAssets/index.tsx | 13 ++--- .../WalletTransactions/index.tsx | 6 ++- .../WalletTransactionsAndAssets/index.tsx | 2 +- src/localization/locales/English.json | 23 ++++++++- src/localization/locales/Turkish.json | 23 ++++++++- src/screens/Asset/index.tsx | 6 +-- .../Wallet/CreateWallet/CreateWalletStep0.tsx | 10 ++-- .../components/SuccessBackupComplete.tsx | 5 +- src/screens/Wallet/CreateWallet/styles.ts | 9 ++++ src/screens/Wallets/WalletsNew.tsx | 2 +- 14 files changed, 157 insertions(+), 38 deletions(-) create mode 100644 src/components/svg/icons/Mnemoic.tsx create mode 100644 src/components/svg/icons/Success.tsx diff --git a/src/components/svg/icons/Mnemoic.tsx b/src/components/svg/icons/Mnemoic.tsx new file mode 100644 index 000000000..f9c6aa1f4 --- /dev/null +++ b/src/components/svg/icons/Mnemoic.tsx @@ -0,0 +1,23 @@ +import React from 'react'; +import { Path, Svg } from 'react-native-svg'; +import { IconProps } from './Icon.types'; + +export function MnemoicIcon(props: IconProps) { + const { scale = 1, color = '#A1A6B2' } = props; + const width = 130; + const height = 130; + return ( + + + + ); +} diff --git a/src/components/svg/icons/Success.tsx b/src/components/svg/icons/Success.tsx new file mode 100644 index 000000000..2cd2090ed --- /dev/null +++ b/src/components/svg/icons/Success.tsx @@ -0,0 +1,23 @@ +import React from 'react'; +import Svg, { Path } from 'react-native-svg'; +import { IconProps } from './Icon.types'; + +export function SuccessIcon(props: IconProps) { + const { scale = 1, color = '#2DBA8D' } = props; + const width = 88; + const height = 89; + return ( + + + + ); +} diff --git a/src/components/svg/icons/index.ts b/src/components/svg/icons/index.ts index 6ef08edb9..d72c41594 100644 --- a/src/components/svg/icons/index.ts +++ b/src/components/svg/icons/index.ts @@ -56,3 +56,5 @@ export * from './Trade'; export * from './Trend'; export * from './Twitter'; export * from './Watchlist'; +export * from './Success'; +export * from './Mnemoic'; diff --git a/src/components/templates/TransactionModal/index.tsx b/src/components/templates/TransactionModal/index.tsx index 8e1c7ed9f..b9e36e8e5 100644 --- a/src/components/templates/TransactionModal/index.tsx +++ b/src/components/templates/TransactionModal/index.tsx @@ -11,6 +11,7 @@ import { NumberUtils } from '@utils/number'; import { useUSDPrice } from '@hooks'; import { AssetLogo } from '@components/svg/icons/Asset'; import { styles } from '@components/templates/TransactionModal/styles'; +import { useTranslation } from 'react-i18next'; interface TransactionModalProps { status: string; @@ -26,8 +27,11 @@ export const TransactionModal = ({ const usdPrice = useUSDPrice(transaction?.amount || transaction.value.ether); const usdFee = useUSDPrice(transaction.fee); + const { t } = useTranslation(); + const timeDiff = useMemo( - () => moment(transaction.timestamp).fromNow(), + // @ts-ignore + () => moment(transaction.timestamp * 1000).fromNow(), [transaction.timestamp] ); @@ -45,7 +49,9 @@ export const TransactionModal = ({ fontSize={20} color={COLORS.nero} > - {status === 'SUCCESS' ? 'Sent' : 'Transaction in progress'} + {status === 'SUCCESS' + ? t('transaction.modal.sent') + : t('transaction.modal.sending')} {status !== 'SUCCESS' && ( @@ -56,7 +62,7 @@ export const TransactionModal = ({ fontSize={14} color={COLORS.nero} > - You will receive a notification when the transaction is complete + {t('transaction.modal.title')} @@ -72,7 +78,7 @@ export const TransactionModal = ({ fontSize={16} color={COLORS.asphalt} > - Date + {t('transaction.modal.date')} - {status !== 'SUCCESS' ? 'From' : 'Status'} + {status !== 'SUCCESS' + ? t('transaction.modal.from') + : t('transaction.modal.status')} {status !== 'SUCCESS' ? ( - Completed + {t('transaction.modal.completed')} )} @@ -123,7 +131,9 @@ export const TransactionModal = ({ fontSize={16} color={COLORS.asphalt} > - {status !== 'SUCCESS' ? 'Sending to' : 'Recipient'} + {status !== 'SUCCESS' + ? t('transaction.modal.sending.to') + : t('transaction.modal.recipient')} - Amount + {t('transaction.modal.amount')} - {NumberUtils.formatNumber( - transaction.amount || transaction.value.ether, - 2 - )}{' '} - AMB{' '} + {transaction.amount || transaction.value.ether >= 100000 + ? NumberUtils.abbreviateNumber( + transaction.amount || transaction.value.ether + ) + : NumberUtils.formatNumber( + transaction.amount || transaction.value.ether, + 2 + )} + {} AMB{' '} - Estimated fee + {t('transaction.modal.estimated.fee')} @@ -207,7 +221,7 @@ export const TransactionModal = ({ fontSize={16} color={COLORS.nero} > - View on explorer + {t('transaction.modal.buttons.explorer')} @@ -221,7 +235,7 @@ export const TransactionModal = ({ fontSize={16} color={COLORS.nero} > - Share + {t('transaction.modal.buttons.share')} @@ -232,7 +246,7 @@ export const TransactionModal = ({ fontSize={16} color={COLORS.white} > - Ok, got it + {t('transaction.modal.confirm')} )} diff --git a/src/components/templates/WalletTransactionsAndAssets/WalletAssets/index.tsx b/src/components/templates/WalletTransactionsAndAssets/WalletAssets/index.tsx index ab44ac6a9..401c99140 100644 --- a/src/components/templates/WalletTransactionsAndAssets/WalletAssets/index.tsx +++ b/src/components/templates/WalletTransactionsAndAssets/WalletAssets/index.tsx @@ -2,10 +2,10 @@ import React from 'react'; import { FlatList, TouchableOpacity, View } from 'react-native'; import { SingleAsset } from '@components/templates/WalletTransactionsAndAssets/WalletAssets/SingleAsset'; import { LocalizedRenderEmpty } from '@components/templates'; -import { WalletsNavigationProp } from '@appTypes'; import { useNavigation } from '@react-navigation/native'; -import { verticalScale } from '@utils/scaling'; import { Spinner } from '@components/base'; +import { useTranslation } from 'react-i18next'; +import { HomeNavigationProp } from '@appTypes'; interface WalletAssetsProps { tokens: @@ -20,7 +20,9 @@ interface WalletAssetsProps { export const WalletAssets = (props: WalletAssetsProps): JSX.Element => { const { tokens, loading } = props; - const navigation = useNavigation(); + const navigation = useNavigation(); + + const { t } = useTranslation(); const navigateToAssetScreen = (tokenInfo: { name: string; @@ -59,12 +61,11 @@ export const WalletAssets = (props: WalletAssetsProps): JSX.Element => { ListFooterComponent={() => (loading ? : <>)} showsVerticalScrollIndicator={false} contentContainerStyle={{ - flexGrow: 1, - paddingBottom: verticalScale(1200) + paddingBottom: 100 }} /> ) : ( - + )} ); diff --git a/src/components/templates/WalletTransactionsAndAssets/WalletTransactions/index.tsx b/src/components/templates/WalletTransactionsAndAssets/WalletTransactions/index.tsx index 5e47f51df..36a0ac38a 100644 --- a/src/components/templates/WalletTransactionsAndAssets/WalletTransactions/index.tsx +++ b/src/components/templates/WalletTransactionsAndAssets/WalletTransactions/index.tsx @@ -4,7 +4,7 @@ import { Spinner } from '@components/base'; import { Transaction } from '@models'; import { SingleTransaction } from '@components/templates/WalletTransactionsAndAssets/WalletTransactions/SingleTransaction'; import { LocalizedRenderEmpty } from '@components/templates'; -import { verticalScale } from '@utils/scaling'; +import { useTranslation } from 'react-i18next'; interface WalletTransactionsProps { transactions: Transaction[] | undefined; @@ -16,6 +16,8 @@ export const WalletTransactions = ( ): JSX.Element => { const { transactions, loading } = props; + const { t } = useTranslation(); + const renderTransactions = ( args: ListRenderItemInfo ): JSX.Element => { @@ -35,7 +37,7 @@ export const WalletTransactions = ( showsVerticalScrollIndicator={false} /> ) : ( - + )} ); diff --git a/src/components/templates/WalletTransactionsAndAssets/index.tsx b/src/components/templates/WalletTransactionsAndAssets/index.tsx index ad6d052bf..dfe9f7b4e 100644 --- a/src/components/templates/WalletTransactionsAndAssets/index.tsx +++ b/src/components/templates/WalletTransactionsAndAssets/index.tsx @@ -115,7 +115,7 @@ export const WalletTransactionsAndAssets = ( setCurrentIndex(scrollOffsetX > 0 ? 1 : 0); }} > - + diff --git a/src/localization/locales/English.json b/src/localization/locales/English.json index 60a02f831..86fee5742 100644 --- a/src/localization/locales/English.json +++ b/src/localization/locales/English.json @@ -136,6 +136,27 @@ "stats": "Stats", "markets": "Markets", "your.balance": "Your balance", + "no.assets.yet": "No assets yet", + "no.transactions.yet": "No transactions yet", + + "transaction.modal": { + "sent": "Sent", + "sending": "Transaction in progress", + "title": "You will receive a notification when the transaction is complete", + "date": "Date", + "from": "From", + "status": "Status", + "completed": "Completed", + "sending.to": "Sending to", + "recipient": "Recipient", + "amount": "Amount", + "estimated.fee": "Estimated fee", + "buttons": { + "explorer": "View on explorer", + "share": "Share", + "confirm":"Ok, got it" + } + }, "chart.timeframe.daily": "1D", "chart.timeframe.weekly": "1W", @@ -170,7 +191,7 @@ "transaction.in.progress": "Transaction in progress!", "backup.your.wallet": "Backup your wallet", "backup.wallet.text": "Your wallet will be backed up with a", - "backup.wallet.popup": "recovery phrase", + "backup.wallet.popup": "recovery phrase.", "make.sure.to.write.down": "Make sure you have a pen and paper ready so you can write it down.", "checkbox.text": "I understand that if I lose my recovery phrase, AirDAO cannot restore it.", "continue.btn": "Continue", diff --git a/src/localization/locales/Turkish.json b/src/localization/locales/Turkish.json index 3b6290162..01493cf93 100644 --- a/src/localization/locales/Turkish.json +++ b/src/localization/locales/Turkish.json @@ -135,6 +135,27 @@ "stats": "", "markets": "", "your.balance": "", + "no.assets.yet": "", + "no.transactions.yet": "", + + "transaction.modal": { + "sent": "", + "sending": "", + "title": "", + "date": "", + "from": "", + "status": "", + "completed": "", + "sending.to": "", + "recipient": "", + "amount": "", + "estimated.fee": "", + "buttons": { + "explorer": "", + "share": "", + "confirm":"" + } + }, "chart.timeframe.daily": "1G", "chart.timeframe.weekly": "1H", @@ -167,7 +188,7 @@ "transaction.in.progress": "Transfer gerçekleştiriliyor!", "backup.your.wallet": "Cüzdanınızı yedekleyin", "backup.wallet.text": "Cüzdanınız bir", - "backup.wallet.popup": "kurtarma cümlesi ile yedeklenecektir", + "backup.wallet.popup": "kurtarma cümlesi ile yedeklenecektir.", "make.sure.to.write.down": "Bir kalem ve kağıt hazır olduğundan emin olun, böylece yazabilirsiniz.", "checkbox.text": "Kurtarma cümlenizi kaybedersem, AirDAO'nun onu geri yükleyemeyeceğini anlıyorum.", "continue.btn": "Devam et", diff --git a/src/screens/Asset/index.tsx b/src/screens/Asset/index.tsx index 50d1950b7..39637000a 100644 --- a/src/screens/Asset/index.tsx +++ b/src/screens/Asset/index.tsx @@ -2,7 +2,7 @@ import React, { useEffect, useState } from 'react'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { Badge, Button, Row, Spacer, Text } from '@components/base'; import { RouteProp, useNavigation, useRoute } from '@react-navigation/native'; -import { HomeParamsList, WalletsNavigationProp } from '@appTypes'; +import { HomeParamsList, HomeNavigationProp } from '@appTypes'; import { Header } from '@components/composite'; import { COLORS } from '@constants/colors'; import { StatisticsLogo } from '@components/svg/icons/Statistics'; @@ -20,7 +20,7 @@ export const AssetScreen = () => { const { params: { tokenInfo } } = useRoute>(); - const navigation = useNavigation(); + const navigation = useNavigation(); const { t } = useTranslation(); const { top } = useSafeAreaInsets(); const [transactions, setTransactions] = useState([]); @@ -29,7 +29,7 @@ export const AssetScreen = () => { const fetchTransactions = async () => { try { const response = await API.explorerService.getTokenTransactionsV2( - '0x4fB246FAf8FAc198f8e5B524E74ABC6755956696', + '0xb017DcCC473499C83f1b553bE564f3CeAf002254', tokenInfo.address ); setTransactions(response.data); diff --git a/src/screens/Wallet/CreateWallet/CreateWalletStep0.tsx b/src/screens/Wallet/CreateWallet/CreateWalletStep0.tsx index 52bc43099..a1f1dfb4b 100644 --- a/src/screens/Wallet/CreateWallet/CreateWalletStep0.tsx +++ b/src/screens/Wallet/CreateWallet/CreateWalletStep0.tsx @@ -5,12 +5,12 @@ import { BottomSheetRef, CheckBox, Header } from '@components/composite'; import { Button, Row, Spacer, Text } from '@components/base'; import { COLORS } from '@constants/colors'; import { scale, verticalScale } from '@utils/scaling'; -import { ElipseIcon } from '@components/svg/icons/Elipse'; import { useNavigation } from '@react-navigation/native'; import { RecoveryPhraseModal } from '@screens/Wallet/CreateWallet/components/RecoveryPhraseModal'; import { styles } from '@screens/Wallet/CreateWallet/styles'; import { AddWalletStackNavigationProp } from '@appTypes/navigation/add-wallet'; import { useTranslation } from 'react-i18next'; +import { MnemoicIcon } from '@components/svg/icons'; export const CreateWalletStep0 = () => { const { top } = useSafeAreaInsets(); @@ -67,12 +67,12 @@ export const CreateWalletStep0 = () => { fontSize={15} fontFamily="Inter_500Medium" color={COLORS.blue600} + style={{ textDecorationLine: 'underline' }} > {t('backup.wallet.popup')} - { {t('make.sure.to.write.down')} - - + + - + { const navigation = useNavigation(); @@ -18,6 +19,7 @@ export const SuccessBackupComplete = () => { return ( + { const usdPrice = useUSDPrice(account?.ambBalance || 0); const { data: tokensAndTransactions, loading: tokensAndTransactionsLoading } = - useTokensAndTransactions('0x4fB246FAf8FAc198f8e5B524E74ABC6755956696'); + useTokensAndTransactions('0xb017DcCC473499C83f1b553bE564f3CeAf002254'); return ( From dad3c4967155073c1c57b0ded91867c57eab5af6 Mon Sep 17 00:00:00 2001 From: illiaa Date: Mon, 4 Sep 2023 14:53:28 +0300 Subject: [PATCH 114/509] replaced mocked address, fixed % change on assets, added loaders + other minor changes --- src/api/explorer-service.ts | 2 +- src/appTypes/navigation/wallets.ts | 1 + src/components/svg/BottomTabIcons/Wallet.tsx | 2 +- .../WalletAssets/SingleAsset/index.tsx | 11 +- .../WalletAssets/index.tsx | 25 +- .../WalletTransactions/index.tsx | 9 +- .../WalletTransactionsAndAssets/index.tsx | 17 +- src/hooks/query/useTokensAndTransactions.ts | 2 +- src/hooks/query/useTransactionsOfToken.ts | 31 ++ src/screens/Asset/index.tsx | 48 ++- src/screens/SendFunds/index.tsx | 1 + src/screens/Wallet/RestoreWallet/index.tsx | 282 ++++++++---------- src/screens/Wallet/index.tsx | 17 +- src/screens/Wallets/WalletsNew.tsx | 12 +- 14 files changed, 235 insertions(+), 225 deletions(-) create mode 100644 src/hooks/query/useTransactionsOfToken.ts diff --git a/src/api/explorer-service.ts b/src/api/explorer-service.ts index 399a35820..a0bda9e48 100644 --- a/src/api/explorer-service.ts +++ b/src/api/explorer-service.ts @@ -88,7 +88,7 @@ const getTransactionsOfAccount = async ( }; const searchWalletV2 = async ( - wallet: string + wallet: string | undefined ): Promise<{ tokens: { address: string; diff --git a/src/appTypes/navigation/wallets.ts b/src/appTypes/navigation/wallets.ts index 1b02e16e5..85441f482 100644 --- a/src/appTypes/navigation/wallets.ts +++ b/src/appTypes/navigation/wallets.ts @@ -14,6 +14,7 @@ export type HomeParamsList = { address: string; balance: { wei: string; ether: number }; }; + walletAccount: string; }; SendFunds: undefined; } & CommonStackParamsList; diff --git a/src/components/svg/BottomTabIcons/Wallet.tsx b/src/components/svg/BottomTabIcons/Wallet.tsx index 6cd542c8c..b6aaf1e70 100644 --- a/src/components/svg/BottomTabIcons/Wallet.tsx +++ b/src/components/svg/BottomTabIcons/Wallet.tsx @@ -15,7 +15,7 @@ export function WalletTabIcon(props: IconProps) { fill="none" > diff --git a/src/components/templates/WalletTransactionsAndAssets/WalletAssets/SingleAsset/index.tsx b/src/components/templates/WalletTransactionsAndAssets/WalletAssets/SingleAsset/index.tsx index 94c5b1e3c..4d0652dc2 100644 --- a/src/components/templates/WalletTransactionsAndAssets/WalletAssets/SingleAsset/index.tsx +++ b/src/components/templates/WalletTransactionsAndAssets/WalletAssets/SingleAsset/index.tsx @@ -5,8 +5,9 @@ import { AssetLogo } from '@components/svg/icons/Asset'; import { scale } from '@utils/scaling'; import { COLORS } from '@constants/colors'; import { styles } from '@components/templates/WalletTransactionsAndAssets/WalletAssets/SingleAsset/styles'; -import { useUSDPrice } from '@hooks'; +import { useAMBPrice, useUSDPrice } from '@hooks'; import { NumberUtils } from '@utils/number'; +import { PercentChange } from '@components/composite'; interface SingleAssetProps { address: string; @@ -17,6 +18,7 @@ interface SingleAssetProps { export const SingleAsset = (props: SingleAssetProps): JSX.Element => { const { name, balance } = props; const usdPrice = useUSDPrice(balance.ether); + const { data: ambTokenData } = useAMBPrice(); return ( @@ -54,8 +56,11 @@ export const SingleAsset = (props: SingleAssetProps): JSX.Element => { fontSize={14} color={COLORS.nero} > - {/* TODO fix % */} - %0.00 + {name === 'AMB' ? ( + + ) : ( + '%0.00' + )} diff --git a/src/components/templates/WalletTransactionsAndAssets/WalletAssets/index.tsx b/src/components/templates/WalletTransactionsAndAssets/WalletAssets/index.tsx index 401c99140..db9d00daa 100644 --- a/src/components/templates/WalletTransactionsAndAssets/WalletAssets/index.tsx +++ b/src/components/templates/WalletTransactionsAndAssets/WalletAssets/index.tsx @@ -16,20 +16,25 @@ interface WalletAssetsProps { }[] | undefined; loading: boolean; + error: boolean; + account: string; } export const WalletAssets = (props: WalletAssetsProps): JSX.Element => { - const { tokens, loading } = props; + const { tokens, loading, account, error } = props; const navigation = useNavigation(); const { t } = useTranslation(); - const navigateToAssetScreen = (tokenInfo: { - name: string; - address: string; - balance: { wei: string; ether: number }; - }) => { - navigation.navigate('AssetScreen', { tokenInfo }); + const navigateToAssetScreen = ( + tokenInfo: { + name: string; + address: string; + balance: { wei: string; ether: number }; + }, + walletAccount: string + ) => { + navigation.navigate('AssetScreen', { tokenInfo, walletAccount }); }; const renderToken = ({ @@ -42,7 +47,7 @@ export const WalletAssets = (props: WalletAssetsProps): JSX.Element => { }; }) => { return ( - navigateToAssetScreen(item)}> + navigateToAssetScreen(item, account)}> { return ( - {tokens && tokens.length > 0 ? ( + {error ? ( + + ) : tokens && tokens.length > 0 ? ( { - const { transactions, loading } = props; + const { transactions, loading, error } = props; const { t } = useTranslation(); @@ -26,7 +27,9 @@ export const WalletTransactions = ( return ( - {transactions ? ( + {error ? ( + + ) : transactions ? ( { - const { tokens, transactions, tokensLoading, transactionsLoading } = props; + const { tokens, transactions, loading, account, error } = props; const scrollView = useRef(null); const [currentIndex, setCurrentIndex] = useState(0); @@ -116,12 +117,18 @@ export const WalletTransactionsAndAssets = ( }} > - + diff --git a/src/hooks/query/useTokensAndTransactions.ts b/src/hooks/query/useTokensAndTransactions.ts index fc91af581..53f170f37 100644 --- a/src/hooks/query/useTokensAndTransactions.ts +++ b/src/hooks/query/useTokensAndTransactions.ts @@ -4,7 +4,7 @@ import { API } from '@api/api'; import { Transaction, TokenDTO } from '@models'; export function useTokensAndTransactions( - walletAddress: string + walletAddress: string | undefined ): QueryResponse< { tokens: TokenDTO[]; transactions: Transaction[] } | undefined > { diff --git a/src/hooks/query/useTransactionsOfToken.ts b/src/hooks/query/useTransactionsOfToken.ts new file mode 100644 index 000000000..45ec44366 --- /dev/null +++ b/src/hooks/query/useTransactionsOfToken.ts @@ -0,0 +1,31 @@ +import { useQuery } from '@tanstack/react-query'; +import { QueryResponse } from '@appTypes/QueryResponse'; +import { API } from '@api/api'; +import { Transaction } from '@models'; + +export function useTransactionsOfToken( + address: string, + tokenAddress: string +): QueryResponse { + const { data, error, isInitialLoading } = useQuery( + ['transactions-of-token', tokenAddress], + async () => { + try { + const response = await API.explorerService.getTokenTransactionsV2( + address, + tokenAddress + ); + + return response.data; + } catch (error) { + throw error; + } + } + ); + + return { + data, + loading: isInitialLoading, + error + }; +} diff --git a/src/screens/Asset/index.tsx b/src/screens/Asset/index.tsx index 39637000a..d375cc413 100644 --- a/src/screens/Asset/index.tsx +++ b/src/screens/Asset/index.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React from 'react'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { Badge, Button, Row, Spacer, Text } from '@components/base'; import { RouteProp, useNavigation, useRoute } from '@react-navigation/native'; @@ -11,36 +11,24 @@ import { scale, verticalScale } from '@utils/scaling'; import { WalletTransactions } from '@components/templates/WalletTransactionsAndAssets/WalletTransactions'; import { LogoGradientCircular } from '@components/svg/icons'; import { useTranslation } from 'react-i18next'; -import { API } from '@api/api'; -import { Transaction } from '@models'; import { useUSDPrice } from '@hooks'; import { NumberUtils } from '@utils/number'; +import { useTransactionsOfToken } from '@hooks/query/useTransactionsOfToken'; export const AssetScreen = () => { const { - params: { tokenInfo } + params: { tokenInfo, walletAccount } } = useRoute>(); const navigation = useNavigation(); const { t } = useTranslation(); const { top } = useSafeAreaInsets(); - const [transactions, setTransactions] = useState([]); const usdPrice = useUSDPrice(tokenInfo.balance.ether || 0); - const fetchTransactions = async () => { - try { - const response = await API.explorerService.getTokenTransactionsV2( - '0xb017DcCC473499C83f1b553bE564f3CeAf002254', - tokenInfo.address - ); - setTransactions(response.data); - } catch (error) { - console.error('Error fetching transactions:', error); - } - }; - - useEffect(() => { - fetchTransactions(); - }, []); + const { + data: transactions, + loading, + error + } = useTransactionsOfToken(walletAccount, tokenInfo.address); const navigateToAMBScreen = () => { navigation.navigate('AMBMarketScreen'); @@ -59,17 +47,19 @@ export const AssetScreen = () => { fontSize={15} color={COLORS.nero} > - {tokenInfo.name} + {tokenInfo.name || 'NULL'} } contentRight={ - <> - - + tokenInfo.name === 'AMB' && ( + <> + + + ) } style={{ shadowColor: 'transparent' }} /> @@ -116,7 +106,11 @@ export const AssetScreen = () => { - + ); diff --git a/src/screens/SendFunds/index.tsx b/src/screens/SendFunds/index.tsx index b748be3fc..2a01cdf4b 100644 --- a/src/screens/SendFunds/index.tsx +++ b/src/screens/SendFunds/index.tsx @@ -122,6 +122,7 @@ export const SendFunds = () => { message: '' }); } catch (error) { + console.log(error, 'error'); Alert.alert('Transfer failed'); } finally { setSendLoading(false); diff --git a/src/screens/Wallet/RestoreWallet/index.tsx b/src/screens/Wallet/RestoreWallet/index.tsx index c02520ede..4d72b3c8f 100644 --- a/src/screens/Wallet/RestoreWallet/index.tsx +++ b/src/screens/Wallet/RestoreWallet/index.tsx @@ -1,189 +1,145 @@ -import React, { useMemo, useRef, useState } from 'react'; -import { SafeAreaView } from 'react-native-safe-area-context'; -import { - BottomSheet, - BottomSheetRef, - Header, - InputWithIcon -} from '@components/composite'; -import { Button, InputRef, Row, Spacer, Text } from '@components/base'; -import { verticalScale } from '@utils/scaling'; -import { Alert, useWindowDimensions, View } from 'react-native'; +import React, { useState, useEffect } from 'react'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; +import { Header } from '@components/composite'; +import { Spacer, Text, Button } from '@components/base'; +import { scale, verticalScale } from '@utils/scaling'; +import { View, Alert } from 'react-native'; import { COLORS } from '@constants/colors'; -import { CloseIcon, ScannerQRIcon } from '@components/svg/icons'; -import { PrimaryButton } from '@components/modular'; -import { BarcodeScanner } from '@components/templates'; -import { etherumAddressRegex } from '@constants/regex'; import { useNavigation } from '@react-navigation/native'; -import { useAddWalletContext } from '@contexts'; -import { styles } from './styles'; import { AddWalletStackNavigationProp } from '@appTypes/navigation/add-wallet'; +import { useTranslation } from 'react-i18next'; +import { TextInput } from '@components/base/Input/Input.text'; export const RestoreWalletScreen = () => { - const inputRef = useRef(null); - const scannerModalRef = useRef(null); - const scanned = useRef(false); - const { height: WINDOW_HEIGHT } = useWindowDimensions(); - - const { walletMnemonic } = useAddWalletContext(); - - const [searchValue, setSearchValue] = useState(''); - const [showMnemonic, setShowMnemonic] = useState(false); - - const isMnemonicValid = searchValue.match(etherumAddressRegex); - const walletMnemonicArrayDefault = walletMnemonic.split(' '); - const walletMnemonicRandomSorted = useMemo( - () => walletMnemonicArrayDefault.sort(() => 0.5 - Math.random()), - // eslint-disable-next-line react-hooks/exhaustive-deps - [walletMnemonicArrayDefault.length] - ); - const [mnemonicWords, setMnemonicWords] = useState( - walletMnemonicRandomSorted - ); - + const { top } = useSafeAreaInsets(); const navigation = useNavigation(); - const navigateToRestoreWallet = () => { - Alert.alert('You have successfully restored your wallet!'); - navigation.navigate('Settings'); - }; + const { t } = useTranslation(); - const clearSearch = () => { - if (searchValue.length > 0) { - const removedWords = searchValue.split(' '); - setMnemonicWords((prevMnemonicWords) => [ - ...prevMnemonicWords, - ...removedWords - ]); - setSearchValue(''); - } - }; + const [mnemonicWords, setMnemonicWords] = useState([ + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '' + ]); + const [isButtonEnabled, setIsButtonEnabled] = useState(false); - const showScanner = () => { - scannerModalRef.current?.show(); - }; + useEffect(() => { + setIsButtonEnabled(mnemonicWords.every((word) => word.trim() !== '')); + }, [mnemonicWords]); - const hideScanner = () => { - scannerModalRef.current?.dismiss(); + const handleWordChange = (index: number, text: string) => { + const updatedWords = [...mnemonicWords]; + updatedWords[index] = text; + setMnemonicWords(updatedWords); }; - const onQRCodeScanned = (data: string) => { - const res = data.match(etherumAddressRegex); - if (res && res?.length > 0) { - hideScanner(); - inputRef.current?.setText(res[0]); - setTimeout(() => { - setSearchValue(res[0]); - }, 500); - } else if (!scanned.current) { - scanned.current = true; - Alert.alert('Invalid QR code', '', [ - { - text: 'Scan again', - onPress: () => { - scanned.current = false; - } - } - ]); - } - }; - - const onChangeText = (text: string) => { - const newValue = text.charAt(0).toLowerCase() + text.slice(1); - setSearchValue(newValue); - const words = newValue.split(' '); - if (walletMnemonic.includes(newValue)) { - const removedWords = mnemonicWords.filter( - (word) => !words.includes(word) + const renderWords = () => { + const wordInputs = []; + for (let i = 0; i < 12; i += 2) { + wordInputs.push( + + handleWordChange(i, text)} + /> + + handleWordChange(i + 1, text)} + /> + ); - setMnemonicWords(removedWords); } + + return wordInputs; }; - const renderWord = (word: string) => { - const onWordPress = () => { - if (searchValue.length > 0) { - setSearchValue(searchValue + ' ' + word); - } else { - setSearchValue(word); - } - setMnemonicWords(mnemonicWords.filter((item) => item !== word)); - }; - return ( - - ); + const navigateToRestoreWallet = () => { + if (isButtonEnabled) { + Alert.alert('You have successfully restored your wallet!'); + navigation.navigate('Settings'); + } }; return ( - -
- Restore wallet - - } - style={{ shadowColor: 'transparent' }} - /> - - - - {showMnemonic ? ( - <> - {mnemonicWords.map(renderWord)} - - - ) : ( - <> - - Insert the entire recovery phrase or enter all words manually - (usually 12 or 24 words) - - - - )} - - 0 ? ( - - ) : ( - - ) + + +
+ Import existing wallet + } - placeholder="Recovery phrase" - value={searchValue} - onChangeText={onChangeText} - onFocus={() => setShowMnemonic(true)} - onBlur={() => setShowMnemonic(false)} + titlePosition="left" + style={{ shadowColor: 'transparent' }} /> - - + - - Restore wallet + Enter recovery phrase + + + + + Enter the recovery phrase associated with your existing wallet. - + + {renderWords()} + + - - - - + + {t('continue.btn')} + + + ); }; diff --git a/src/screens/Wallet/index.tsx b/src/screens/Wallet/index.tsx index 8f386b6a8..c280b5790 100644 --- a/src/screens/Wallet/index.tsx +++ b/src/screens/Wallet/index.tsx @@ -50,12 +50,12 @@ export const WalletScreen = () => { navigation.navigate('CreateWalletStep0'); }; - // const onRestorePress = () => { - // setFlowType(AddWalletFlowType.RESTORE_WALLET); - // setWalletName(''); - // setMnemonicLength(128); - // navigation.navigate('RestoreWalletScreen'); - // }; + const onRestorePress = () => { + setFlowType(AddWalletFlowType.RESTORE_WALLET); + setWalletName(''); + setMnemonicLength(128); + navigation.navigate('RestoreWalletScreen'); + }; const renderWallet = (args: ListRenderItemInfo) => { const { item, index } = args; @@ -86,12 +86,13 @@ export const WalletScreen = () => { - {/* + Restore address - */} + + w.hash} diff --git a/src/screens/Wallets/WalletsNew.tsx b/src/screens/Wallets/WalletsNew.tsx index f2340e591..febba3f2a 100644 --- a/src/screens/Wallets/WalletsNew.tsx +++ b/src/screens/Wallets/WalletsNew.tsx @@ -20,8 +20,11 @@ export const HomeScreen = () => { useCryptoAccountFromHash(selectedWalletHash); const usdPrice = useUSDPrice(account?.ambBalance || 0); - const { data: tokensAndTransactions, loading: tokensAndTransactionsLoading } = - useTokensAndTransactions('0xb017DcCC473499C83f1b553bE564f3CeAf002254'); + const { + data: tokensAndTransactions, + loading, + error + } = useTokensAndTransactions(account?.address); return ( @@ -42,10 +45,11 @@ export const HomeScreen = () => { From 26214d06cb649c962fbb5a1e43ebdec724f74370 Mon Sep 17 00:00:00 2001 From: illiaa Date: Mon, 4 Sep 2023 15:36:23 +0300 Subject: [PATCH 115/509] moved create wallet to home screen + tab bar icons fix --- src/appTypes/navigation/add-wallet.ts | 27 ---------- src/appTypes/navigation/index.ts | 1 - src/appTypes/navigation/settings.ts | 7 +-- src/appTypes/navigation/tabs.ts | 1 - src/appTypes/navigation/wallets.ts | 15 ++++++ src/components/svg/BottomTabIcons/Explore.tsx | 6 +-- .../svg/BottomTabIcons/Settings.tsx | 6 +-- src/components/svg/BottomTabIcons/Wallet.tsx | 4 +- .../svg/BottomTabIcons/Watchlist.tsx | 6 +-- .../BottomSheetWalletCreateOrImport/index.tsx | 6 +-- src/localization/locales/English.json | 1 + src/localization/locales/Turkish.json | 1 + src/navigation/components/TabBar/index.tsx | 16 ++---- src/navigation/stacks/Tabs/HomeStack.tsx | 49 ++++++++++++++----- src/navigation/stacks/Tabs/WalletStack.tsx | 46 ----------------- src/navigation/stacks/TabsNavigator.tsx | 8 +-- src/screens/Wallet/Account/index.tsx | 8 +-- .../Wallet/CreateWallet/CreateWalletStep0.tsx | 4 +- .../Wallet/CreateWallet/CreateWalletStep1.tsx | 4 +- .../Wallet/CreateWallet/CreateWalletStep2.tsx | 4 +- .../components/SuccessBackupComplete.tsx | 4 +- .../Wallet/CreateWallet/components/index.ts | 2 + src/screens/Wallet/RestoreWallet/index.tsx | 4 +- src/screens/Wallet/index.tsx | 5 +- 24 files changed, 94 insertions(+), 141 deletions(-) delete mode 100644 src/appTypes/navigation/add-wallet.ts delete mode 100644 src/navigation/stacks/Tabs/WalletStack.tsx create mode 100644 src/screens/Wallet/CreateWallet/components/index.ts diff --git a/src/appTypes/navigation/add-wallet.ts b/src/appTypes/navigation/add-wallet.ts deleted file mode 100644 index 3f5bbb087..000000000 --- a/src/appTypes/navigation/add-wallet.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { BottomTabNavigationProp } from '@react-navigation/bottom-tabs'; -import { CompositeNavigationProp } from '@react-navigation/native'; -import { TabsParamsList } from './tabs'; -import { NativeStackNavigationProp } from '@react-navigation/native-stack'; -import { Wallet } from '@models/Wallet'; - -export type HomeStackParamsList = { - CreateWalletStep0: undefined; - CreateWalletStep1: undefined; - CreateWalletStep2: undefined; - SuccessBackupComplete: undefined; - ReceiptScreen: { - amount: number; - currencyCode: string; - destination: string; - origin: string; - hash: string; - }; - RestoreWalletScreen: undefined; - WalletScreen: undefined; - WalletAccount: { wallet: Wallet }; -}; - -export type AddWalletStackNavigationProp = CompositeNavigationProp< - BottomTabNavigationProp, - NativeStackNavigationProp ->; diff --git a/src/appTypes/navigation/index.ts b/src/appTypes/navigation/index.ts index 60dadc11f..897217ab9 100644 --- a/src/appTypes/navigation/index.ts +++ b/src/appTypes/navigation/index.ts @@ -6,4 +6,3 @@ export * from './search'; export * from './settings'; export * from './tabs'; export * from './wallets'; -export * from './add-wallet'; diff --git a/src/appTypes/navigation/settings.ts b/src/appTypes/navigation/settings.ts index be94b4d32..09191217a 100644 --- a/src/appTypes/navigation/settings.ts +++ b/src/appTypes/navigation/settings.ts @@ -1,15 +1,10 @@ -import { - CompositeNavigationProp, - NavigatorScreenParams -} from '@react-navigation/native'; -import { AddHomeStackParamsList } from './add-wallet'; +import { CompositeNavigationProp } from '@react-navigation/native'; import { BottomTabNavigationProp } from '@react-navigation/bottom-tabs'; import { TabsParamsList } from './tabs'; import { NativeStackNavigationProp } from '@react-navigation/native-stack'; export type SettingsTabParamsList = { SettingsScreen: undefined; - AddWalletStack: NavigatorScreenParams; }; export type SettingsTabNavigationProp = CompositeNavigationProp< diff --git a/src/appTypes/navigation/tabs.ts b/src/appTypes/navigation/tabs.ts index bd0458d0f..040da8259 100644 --- a/src/appTypes/navigation/tabs.ts +++ b/src/appTypes/navigation/tabs.ts @@ -8,7 +8,6 @@ export type TabsParamsList = { Search: NavigatorScreenParams; Portfolio: NavigatorScreenParams; Settings: undefined; - Wallet: undefined; Tabs: { screen: string }; }; diff --git a/src/appTypes/navigation/wallets.ts b/src/appTypes/navigation/wallets.ts index 85441f482..3f66fb0df 100644 --- a/src/appTypes/navigation/wallets.ts +++ b/src/appTypes/navigation/wallets.ts @@ -3,6 +3,7 @@ import { BottomTabNavigationProp } from '@react-navigation/bottom-tabs'; import { NativeStackNavigationProp } from '@react-navigation/native-stack'; import { TabsParamsList } from './tabs'; import { CommonStackParamsList } from './common'; +import { Wallet } from '@models'; export type HomeParamsList = { HomeScreen: undefined; @@ -17,6 +18,20 @@ export type HomeParamsList = { walletAccount: string; }; SendFunds: undefined; + CreateWalletStep0: undefined; + CreateWalletStep1: undefined; + CreateWalletStep2: undefined; + SuccessBackupComplete: undefined; + ReceiptScreen: { + amount: number; + currencyCode: string; + destination: string; + origin: string; + hash: string; + }; + RestoreWalletScreen: undefined; + WalletScreen: undefined; + WalletAccount: { wallet: Wallet }; } & CommonStackParamsList; export type HomeNavigationProp = CompositeNavigationProp< diff --git a/src/components/svg/BottomTabIcons/Explore.tsx b/src/components/svg/BottomTabIcons/Explore.tsx index cac029e5c..319458a5b 100644 --- a/src/components/svg/BottomTabIcons/Explore.tsx +++ b/src/components/svg/BottomTabIcons/Explore.tsx @@ -4,8 +4,8 @@ import { IconProps } from '@components/svg/icons'; export function ExploreTabIcon(props: IconProps) { const { scale = 1, color = '#457EFF' } = props; - const width = 24; - const height = 24; + const width = 29; + const height = 28; return ( diff --git a/src/components/svg/BottomTabIcons/Settings.tsx b/src/components/svg/BottomTabIcons/Settings.tsx index a9ff1e7b9..c3c1f0b14 100644 --- a/src/components/svg/BottomTabIcons/Settings.tsx +++ b/src/components/svg/BottomTabIcons/Settings.tsx @@ -4,8 +4,8 @@ import { IconProps } from '@components/svg/icons'; export function SettingsTabIcon(props: IconProps) { const { scale = 1, color = '#457EFF' } = props; - const width = 24; - const height = 24; + const width = 29; + const height = 28; return ( diff --git a/src/components/svg/BottomTabIcons/Wallet.tsx b/src/components/svg/BottomTabIcons/Wallet.tsx index b6aaf1e70..c28026209 100644 --- a/src/components/svg/BottomTabIcons/Wallet.tsx +++ b/src/components/svg/BottomTabIcons/Wallet.tsx @@ -4,8 +4,8 @@ import { IconProps } from '@components/svg/icons'; export function WalletTabIcon(props: IconProps) { const { scale = 1, color = '#457EFF' } = props; - const width = 24; - const height = 24; + const width = 28; + const height = 28; return ( diff --git a/src/components/templates/BottomSheetWalletCreateOrImport/index.tsx b/src/components/templates/BottomSheetWalletCreateOrImport/index.tsx index 786e9e7b9..7ec5f1aaf 100644 --- a/src/components/templates/BottomSheetWalletCreateOrImport/index.tsx +++ b/src/components/templates/BottomSheetWalletCreateOrImport/index.tsx @@ -11,19 +11,19 @@ import { scale, verticalScale } from '@utils/scaling'; import { COLORS } from '@constants/colors'; import { PrimaryButton } from '@components/modular'; import { useNavigation } from '@react-navigation/native'; -import { AddWalletStackNavigationProp } from '@appTypes/navigation/add-wallet'; import { useTranslation } from 'react-i18next'; +import { HomeNavigationProp } from '@appTypes'; export const BottomSheetWalletCreateOrImport = forwardRef< BottomSheetRef, BottomSheetProps >((props, ref) => { const localRef: ForwardedRef = useForwardedRef(ref); - const navigation = useNavigation(); + const navigation = useNavigation(); const { t } = useTranslation(); const navigateToWalletCreate = () => { - navigation.navigate('Wallet'); + navigation.navigate('WalletScreen'); localRef.current?.dismiss(); }; diff --git a/src/localization/locales/English.json b/src/localization/locales/English.json index 86fee5742..11a8e610c 100644 --- a/src/localization/locales/English.json +++ b/src/localization/locales/English.json @@ -8,6 +8,7 @@ "Russian": "Russian", "Chinese": "Chinese", + "wallets.tab": "Wallets", "overview.tab": "Overview", "watchlist.tab": "Watchlist", "explore.tab": "Explore", diff --git a/src/localization/locales/Turkish.json b/src/localization/locales/Turkish.json index 01493cf93..2a81bfcc4 100644 --- a/src/localization/locales/Turkish.json +++ b/src/localization/locales/Turkish.json @@ -8,6 +8,7 @@ "Russian": "Rusça", "Chinese": "Çince", + "wallets.tab": "", "overview.tab": "Genel bakış", "watchlist.tab": "İzleme listesi", "explore.tab": "Keşfet", diff --git a/src/navigation/components/TabBar/index.tsx b/src/navigation/components/TabBar/index.tsx index c31321ed5..f2ecd51b6 100644 --- a/src/navigation/components/TabBar/index.tsx +++ b/src/navigation/components/TabBar/index.tsx @@ -14,16 +14,15 @@ import { useFocusEffect } from '@react-navigation/native'; import { ExploreTabIcon, WatchlistTabIcon, - OverviewTabIcon, SettingsTabIcon, WalletTabIcon } from '@components/svg/BottomTabIcons'; -type LabelType = 'Settings' | 'Portfolio' | 'Search' | 'Wallets' | 'Wallet'; +type LabelType = 'Settings' | 'Portfolio' | 'Search' | 'Wallets'; const tabs = { Wallets: { - inactiveIcon: , - activeIcon: + inactiveIcon: , + activeIcon: }, Portfolio: { inactiveIcon: , @@ -36,15 +35,11 @@ const tabs = { Settings: { inactiveIcon: , activeIcon: - }, - Wallet: { - inactiveIcon: , - activeIcon: } }; const TabBar = ({ state, descriptors, navigation }: BottomTabBarProps) => { - const bottomSafeArea = useSafeAreaInsets().bottom - 15; + const bottomSafeArea = useSafeAreaInsets().bottom - 12; const currentRoute = useCurrentRoute(); const tabBarVisible = NavigationUtils.getTabBarVisibility(currentRoute); const [isReady, setIsReady] = useState(false); @@ -134,10 +129,9 @@ const styles = StyleSheet.create({ position: 'absolute', bottom: 0, backgroundColor: COLORS.white, - paddingVertical: 8, opacity: 2, borderTopWidth: 0.25, - borderTopColor: COLORS.silver + borderTopColor: COLORS.gray200 }, mainItemContainer: { flex: 1, diff --git a/src/navigation/stacks/Tabs/HomeStack.tsx b/src/navigation/stacks/Tabs/HomeStack.tsx index ca3b5195a..d258c1286 100644 --- a/src/navigation/stacks/Tabs/HomeStack.tsx +++ b/src/navigation/stacks/Tabs/HomeStack.tsx @@ -7,21 +7,48 @@ import { Notifications } from '@screens/Notifications'; import { getCommonStack } from '../CommonStack'; import { AssetScreen } from '@screens/Asset'; import { SendFunds } from '@screens/SendFunds'; +import { WalletScreen } from '@screens/Wallet'; +import { ReceiptScreen } from '@screens/Wallet/Receipt'; +import { WalletAccount } from '@screens/Wallet/Account'; +import { + CreateWalletStep0, + CreateWalletStep1, + CreateWalletStep2 +} from '@screens/Wallet/CreateWallet'; +import { RestoreWalletScreen } from '@screens/Wallet/RestoreWallet'; +import { SuccessBackupComplete } from '@screens/Wallet/CreateWallet/components'; +import { SendCryptoProvider } from '@contexts/SendCrypto/SendCrypto.context'; const Stack = createNativeStackNavigator(); export const HomeStack = () => { return ( - - - - - - - {getCommonStack(Stack as any)} - + + + + + + + + + + + + + + + + {getCommonStack(Stack as any)} + + ); }; diff --git a/src/navigation/stacks/Tabs/WalletStack.tsx b/src/navigation/stacks/Tabs/WalletStack.tsx deleted file mode 100644 index d1903879a..000000000 --- a/src/navigation/stacks/Tabs/WalletStack.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import React from 'react'; -import { createNativeStackNavigator } from '@react-navigation/native-stack'; -import { HomeStackParamsList } from '@appTypes'; -import { WalletScreen } from '@screens/Wallet'; -import { - CreateWalletStep1, - CreateWalletStep2, - CreateWalletStep0 -} from '@screens/Wallet/CreateWallet'; -import { RestoreWalletScreen } from '@screens/Wallet/RestoreWallet'; -import { getCommonStack } from '@navigation/stacks/CommonStack'; -import { WalletAccount } from '@screens/Wallet/Account'; -import { ReceiptScreen } from '@screens/Wallet/Receipt'; -import { SendCryptoProvider } from '@contexts/SendCrypto/SendCrypto.context'; -import { SuccessBackupComplete } from '@screens/Wallet/CreateWallet/components/SuccessBackupComplete'; - -const Stack = createNativeStackNavigator(); -export const WalletStack = () => { - return ( - // @ts-ignore - - - - - - - - - - - {getCommonStack(Stack as any)} - - - ); -}; - -export default WalletStack; diff --git a/src/navigation/stacks/TabsNavigator.tsx b/src/navigation/stacks/TabsNavigator.tsx index 9c69d397c..2c1f3dfeb 100644 --- a/src/navigation/stacks/TabsNavigator.tsx +++ b/src/navigation/stacks/TabsNavigator.tsx @@ -5,7 +5,6 @@ import { TabsParamsList } from '@appTypes/navigation/tabs'; import { PortfolioStack } from './Tabs/PortfolioStack'; import TabBar from '@navigation/components/TabBar'; import SettingsStack from './Tabs/SettingsStack'; -import WalletStack from './Tabs/WalletStack'; import SearchStack from './Tabs/SearchStack'; import HomeStack from './Tabs/HomeStack'; @@ -24,7 +23,7 @@ export const TabsNavigator = () => { { component={SettingsStack} options={{ tabBarLabel: t('settings.tab') }} /> - ); }; diff --git a/src/screens/Wallet/Account/index.tsx b/src/screens/Wallet/Account/index.tsx index 06044a7bc..fe8af5865 100644 --- a/src/screens/Wallet/Account/index.tsx +++ b/src/screens/Wallet/Account/index.tsx @@ -7,7 +7,6 @@ import { CopyToClipboardButton, Header } from '@components/composite'; import { NumberInput } from '@components/base/Input/Input.number'; import { PrimaryButton } from '@components/modular'; import { Input, Row, Spacer, Spinner, Text } from '@components/base'; -import { AddWalletStackNavigationProp, HomeStackParamsList } from '@appTypes'; import AirDAOKeysForRef from '@lib/helpers/AirDAOKeysForRef'; import { API } from '@api/api'; import { ExplorerAccount, Transaction } from '@models'; @@ -15,9 +14,10 @@ import { StringUtils } from '@utils/string'; import { scale, verticalScale } from '@utils/scaling'; import { COLORS } from '@constants/colors'; import { etherumAddressRegex } from '@constants/regex'; +import { HomeNavigationProp, HomeParamsList } from '@appTypes'; const Layout = (props: PropsWithChildren) => { - const route = useRoute>(); + const route = useRoute>(); const { wallet } = route.params; return ( @@ -30,8 +30,8 @@ const Layout = (props: PropsWithChildren) => { const LIMIT = 25; export const WalletAccount = () => { - const navigation = useNavigation(); - const route = useRoute>(); + const navigation = useNavigation(); + const route = useRoute>(); const { wallet } = route.params; const [error, setError] = useState(''); diff --git a/src/screens/Wallet/CreateWallet/CreateWalletStep0.tsx b/src/screens/Wallet/CreateWallet/CreateWalletStep0.tsx index a1f1dfb4b..bbcce0130 100644 --- a/src/screens/Wallet/CreateWallet/CreateWalletStep0.tsx +++ b/src/screens/Wallet/CreateWallet/CreateWalletStep0.tsx @@ -8,14 +8,14 @@ import { scale, verticalScale } from '@utils/scaling'; import { useNavigation } from '@react-navigation/native'; import { RecoveryPhraseModal } from '@screens/Wallet/CreateWallet/components/RecoveryPhraseModal'; import { styles } from '@screens/Wallet/CreateWallet/styles'; -import { AddWalletStackNavigationProp } from '@appTypes/navigation/add-wallet'; import { useTranslation } from 'react-i18next'; import { MnemoicIcon } from '@components/svg/icons'; +import { HomeNavigationProp } from '@appTypes'; export const CreateWalletStep0 = () => { const { top } = useSafeAreaInsets(); const [selected, setSelected] = useState(false); - const navigation = useNavigation(); + const navigation = useNavigation(); const recoveryPhraseModalRef = useRef(null); const { t } = useTranslation(); diff --git a/src/screens/Wallet/CreateWallet/CreateWalletStep1.tsx b/src/screens/Wallet/CreateWallet/CreateWalletStep1.tsx index 88ec76665..43c4eebec 100644 --- a/src/screens/Wallet/CreateWallet/CreateWalletStep1.tsx +++ b/src/screens/Wallet/CreateWallet/CreateWalletStep1.tsx @@ -10,11 +10,11 @@ import { PrimaryButton } from '@components/modular'; import { useNavigation } from '@react-navigation/native'; import { COLORS } from '@constants/colors'; import { WarningIcon } from '@components/svg/icons/Warning'; -import { AddWalletStackNavigationProp } from '@appTypes/navigation/add-wallet'; import { useTranslation } from 'react-i18next'; +import { HomeNavigationProp } from '@appTypes'; export const CreateWalletStep1 = () => { - const navigation = useNavigation(); + const navigation = useNavigation(); const [loading, setLoading] = useState(false); const { walletMnemonic, mnemonicLength, setWalletMnemonic } = useAddWalletContext(); diff --git a/src/screens/Wallet/CreateWallet/CreateWalletStep2.tsx b/src/screens/Wallet/CreateWallet/CreateWalletStep2.tsx index 0bb64cec8..53848f528 100644 --- a/src/screens/Wallet/CreateWallet/CreateWalletStep2.tsx +++ b/src/screens/Wallet/CreateWallet/CreateWalletStep2.tsx @@ -7,13 +7,13 @@ import { useAddWalletContext } from '@contexts'; import { scale, verticalScale } from '@utils/scaling'; import { COLORS } from '@constants/colors'; import { useNavigation } from '@react-navigation/native'; -import { AddWalletStackNavigationProp } from '@appTypes/navigation/add-wallet'; import { useTranslation } from 'react-i18next'; import { styles } from '@screens/Wallet/CreateWallet/styles'; import { AccountUtils } from '@utils/account'; +import { HomeNavigationProp } from '@appTypes'; export const CreateWalletStep2 = () => { - const navigation = useNavigation(); + const navigation = useNavigation(); const { walletMnemonic } = useAddWalletContext(); const { t } = useTranslation(); const [walletMnemonicSelected, setWalletMnemonicSelected] = useState< diff --git a/src/screens/Wallet/CreateWallet/components/SuccessBackupComplete.tsx b/src/screens/Wallet/CreateWallet/components/SuccessBackupComplete.tsx index 814e898ff..0398289ea 100644 --- a/src/screens/Wallet/CreateWallet/components/SuccessBackupComplete.tsx +++ b/src/screens/Wallet/CreateWallet/components/SuccessBackupComplete.tsx @@ -6,12 +6,12 @@ import { scale, verticalScale } from '@utils/scaling'; import { View, StyleSheet } from 'react-native'; import { PrimaryButton } from '@components/modular'; import { useNavigation } from '@react-navigation/native'; -import { AddWalletStackNavigationProp } from '@appTypes/navigation/add-wallet'; import { useTranslation } from 'react-i18next'; import { SuccessIcon } from '@components/svg/icons'; +import { HomeNavigationProp } from '@appTypes'; export const SuccessBackupComplete = () => { - const navigation = useNavigation(); + const navigation = useNavigation(); const { t } = useTranslation(); const navigateToSetUpSecurity = () => { navigation.navigate('WalletScreen'); diff --git a/src/screens/Wallet/CreateWallet/components/index.ts b/src/screens/Wallet/CreateWallet/components/index.ts new file mode 100644 index 000000000..65f181143 --- /dev/null +++ b/src/screens/Wallet/CreateWallet/components/index.ts @@ -0,0 +1,2 @@ +export * from './SuccessBackupComplete'; +export * from './RecoveryPhraseModal'; diff --git a/src/screens/Wallet/RestoreWallet/index.tsx b/src/screens/Wallet/RestoreWallet/index.tsx index 4d72b3c8f..57dbdc3b9 100644 --- a/src/screens/Wallet/RestoreWallet/index.tsx +++ b/src/screens/Wallet/RestoreWallet/index.tsx @@ -6,13 +6,13 @@ import { scale, verticalScale } from '@utils/scaling'; import { View, Alert } from 'react-native'; import { COLORS } from '@constants/colors'; import { useNavigation } from '@react-navigation/native'; -import { AddWalletStackNavigationProp } from '@appTypes/navigation/add-wallet'; import { useTranslation } from 'react-i18next'; import { TextInput } from '@components/base/Input/Input.text'; +import { HomeNavigationProp } from '@appTypes'; export const RestoreWalletScreen = () => { const { top } = useSafeAreaInsets(); - const navigation = useNavigation(); + const navigation = useNavigation(); const { t } = useTranslation(); const [mnemonicWords, setMnemonicWords] = useState([ diff --git a/src/screens/Wallet/index.tsx b/src/screens/Wallet/index.tsx index c280b5790..0109447df 100644 --- a/src/screens/Wallet/index.tsx +++ b/src/screens/Wallet/index.tsx @@ -5,18 +5,17 @@ import { Header } from '@components/composite'; import { Button, Spacer, Text } from '@components/base'; import { scale, verticalScale } from '@utils/scaling'; import { useNavigation } from '@react-navigation/native'; -import { DatabaseTable } from '@appTypes'; +import { DatabaseTable, HomeNavigationProp } from '@appTypes'; import { AddWalletFlowType, useAddWalletContext } from '@contexts'; import { styles } from '@screens/Wallet/styles'; import { COLORS } from '@constants/colors'; import { PrimaryButton } from '@components/modular'; import { Database, WalletDBModel } from '@database'; import { Wallet } from '@models/Wallet'; -import { AddWalletStackNavigationProp } from '@appTypes/navigation/add-wallet'; import { useTranslation } from 'react-i18next'; export const WalletScreen = () => { - const navigation = useNavigation(); + const navigation = useNavigation(); const { setFlowType, setWalletName, setMnemonicLength } = useAddWalletContext(); const { top } = useSafeAreaInsets(); From 21ba74f59cd0d2b7239b0eea3c69890734e57bc1 Mon Sep 17 00:00:00 2001 From: illiaa Date: Mon, 4 Sep 2023 15:56:55 +0300 Subject: [PATCH 116/509] added back button --- src/screens/Wallet/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/screens/Wallet/index.tsx b/src/screens/Wallet/index.tsx index 0109447df..b030700ea 100644 --- a/src/screens/Wallet/index.tsx +++ b/src/screens/Wallet/index.tsx @@ -74,7 +74,7 @@ export const WalletScreen = () => {
From 834bc6e3ade46c5b18b9134b5f98a3888c8ad68f Mon Sep 17 00:00:00 2001 From: JavidHaji-zada Date: Mon, 4 Sep 2023 18:40:37 +0400 Subject: [PATCH 117/509] NoWallet Page: Created loader --- babel.config.js | 1 + src/appTypes/navigation/wallets.ts | 1 + src/assets/images/logo.png | Bin 0 -> 768442 bytes src/hooks/cache/useSelectedWalletHash.ts | 21 ++++--- src/hooks/useMainAccount.ts | 2 +- src/localization/locales/English.json | 6 +- src/localization/locales/Turkish.json | 6 +- src/navigation/stacks/Tabs/HomeStack.tsx | 4 +- src/screens/NoWallet/components/Loading.tsx | 62 ++++++++++++++++++++ src/screens/NoWallet/components/index.ts | 1 + src/screens/SendFunds/index.tsx | 2 +- src/screens/Wallets/WalletsNew.tsx | 2 +- tsconfig.json | 1 + 13 files changed, 94 insertions(+), 15 deletions(-) create mode 100644 src/assets/images/logo.png create mode 100644 src/screens/NoWallet/components/Loading.tsx create mode 100644 src/screens/NoWallet/components/index.ts diff --git a/babel.config.js b/babel.config.js index e915832ee..3d4c27880 100644 --- a/babel.config.js +++ b/babel.config.js @@ -12,6 +12,7 @@ module.exports = function (api) { alias: { '@api': './src/api', '@appTypes': './src/appTypes', + '@assets': './src/assets', '@components': './src/components', '@constants': './src/constants', '@contexts': './src/contexts', diff --git a/src/appTypes/navigation/wallets.ts b/src/appTypes/navigation/wallets.ts index 85441f482..9a5907db1 100644 --- a/src/appTypes/navigation/wallets.ts +++ b/src/appTypes/navigation/wallets.ts @@ -16,6 +16,7 @@ export type HomeParamsList = { }; walletAccount: string; }; + NoWallet: undefined; SendFunds: undefined; } & CommonStackParamsList; diff --git a/src/assets/images/logo.png b/src/assets/images/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..e317cb84945432c03abbdbf12e3c99b1053646b3 GIT binary patch literal 768442 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

? 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 g|5^E|o-F#_(a7gvzo_rLd!JAK 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 z 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 z z!LPwmhdmT%jU_6>`hZnx#BI)nvpbc5RkhkBJD$z$#O7@8IiBi`M|q7}CFunj8B3N@ z8o7DoVeiOXu~0r}%Kc5S#LKds1NM&*DQQgevP|hF0-{X#z*i~3Gw@Qs;W6K;$dfm4 z^`KUY59OyAzJT08rehPP<=>oyLT*pIV93*x+p8`Rc?r8-s|BWjfu18 z`TnmjO6+pPZdMUq>1Vo1g(^ginw(tJ`O=Q;6S;0^EAMdi8D8Y#;QQ-%sLCZ5dL!07 zZHbqRuQ~YgWngSymBmI5565Y;LC$YoIZqX3uX3IT#)>yRN2NhAx3F)(;F+MGE=pBK$qh_V zgK5Pc!;Al;=`6#ddf%=s_@hfeIt2+yX#ruBmhSHE4y8sKNhPJbyF_y66cACmN4k4} zVfM5AzsLKX4;(YI?|t3tTI*a|kyOx$Q?sRboMHNtBq=tOOEDiC$Y=EUeR=kam_LXK)jwbOmHP|ZY;W4&B8))B=YbO4D{)E6kU_s1K`Xmwhh9H>~<&-mZ zs@Ae)-M@o?h|PPW^+T2id;n@$akJBCB3bw+sKLh9wsOgwoTv6`VB8UCh1}oxKKPq} z8aglzoIMNPwCh&vj(f;mjxpWe2Y zl-ASF%&MLOM(7SHD+sl&?zmV*X*$|s;7 zSW#M9Tq@`2xAEY?g4YY)X=Kx@G8Q{xta>+%)7qTn`!r!eSt6u>lJQg`opXS#pz^ZU zQRFWizxuZ8VOYz-v3oNkzlRILu3g;am!}cf1_^Qti0a8M`M01DK!fbIHA#X_=sBvvo-R}N zmd1>jRG3*62Hf15l-edMR#k~!DZyPwjx)d_XgH5m`t3)t#-%h`%JhSh&ek(@Nb?$E z81J7>Ti1dODCt+T2ZliVwf2IZ&$1yQxos|pbP0T*#t0u7Rd_W3)r^Rc9fpqA`wF*k zUBvfwpMrTJ=+eo<`u`hdc;?{c0Q4cfJDeB!gICA7_W9%)LryuPs~s}ggR9)`N3dX< zP|x$SU`OK%NAPMkrEDtx*F=ezj|agJ4iVgL;bT^-IWx(W=^cQGn~9 zeH%z?MCAPi3(&As1OcnxsyT*Cae&S6EbW6@otd#x5LbH)faZOX_RI7X#sX9+@Szet zUGCku?TUN2Nkz0$&x>bld>gGF_O|pf2d?bLA-apm#K`n4Ib>b&>?yQo2m!sU#s~4V zZ&%M4sIrVnf!er~pZiUGe5Q{1&Iclcd^V8M1d>+))GcfF^Ub(^*w1$|5q8?f5l4j& z>bYe}XgXo|$eT&0y82b$*SeQ46!I9E9pq8a7;+0SW%qLFOSW5}2MlxyA4CE|e|`W5 z#nI}Jk8_aE!WO@wA-u`((IN7dWBxa{;Y%My3Wga5;j`(~fi`Iy|Am6kKakRuqhG})ba`T4M?O`_K8B)m% zW^;MgnOaHC!&>%{YlSdtLZgj3VZ(6~%^yk=8q?gZxX~*FT?hxM~t}wa$&Q25?2k z{G48y*donQApdkU!13JXz^erlXY1HAeIQ#_)~juloW~Z8x;x1~FxGlnWm#p!)fdAr z_CJqswsBp;D0ilOQs7UsRn(xzGh}*AI8>HkF`2W7UW6rlw2gugT=JCuM>BUiW%F9D zGV%G=>py&FX1<_SxS4(Q=u(1%-}jLS3U)vQ#rt;`UHkhleWVAV^B(-(a;<5jC)A>$c;Q`JGf8_zj{?;^TKt|TuAKeWnjY0QJeJll z>xC*SN`VC@l!G4ob}qpar_&AoodZ(&I}+z5_gs=xiES4Qsqjilt2nr!&36E_;ShXQeR#@eb7mkKa&hSJkWYnIz8dU;pJr`j8)5F)uu3% zL1VTKEaFuA-uV|SUD}~w7&+&e0EWg?!QBcsRqb4h7M-SIdrq1PKLL=nX-^<({CX5{ zDzHLj8ah~=Tw|reenoHtYC%!Lwjg}^9KduOx8vR50Q3mb?bn)3OpH%qWZoaoU`gV? zNAb1=8q|zkled430gT84I~n7@8RDp!Gbp>xMEv`Y;;#X$nfHqcqd$M>O{h(`FPsty zqIwxT1zn}<12BNY=h%QcF^`Qb_d5czv9DZMpFa@0q-L6~uMvu*Mx^ZA?^LB ze&LR!zM71-_pq)}Gtm2X?4NT#Xqk+b+Fdvp$uxkw(Q3UZ*D~n5s_x(;Z3(HQs#W6k z&6sy~dXW}k_|c|nIf)U={^fqeA>UTj?YR_#@({JkEE_ff#YUA!**JZ`!(>culNfb; zRM4GfQp9K>HD-9mn@YawM=h;J6*Oj>1#^O5jt;-DKnLEiB;6!sjhZh`;dnL*aW1B- z9gv0JUpWi#=Yt%q$Ksr-Lw7-ptpotux(ni@L}@D@cA5$*wDYGY-+Do^AqLWpX66jU zGw8aN5}?;XgK3hSL9p8VzUV3?Hb|$u&?xz_TgR2@xVp(e{?~Av z|EuqqBCnylrQqBFeVpYIGaqjbzDv$?rWWde5kd15qtu~kNf|p~E?YDO~v65C2 zmmED!SNljcx~xZa=a2+URXIu!{snzU>rh3Fto!0ZSMjSVi_8MymQ5kCUoqb@+!D$7 zx$^DG5SQe~qoPM!vNVo<2Fs$ebJK6Co9q9?1auCnCan3BZ+~~(MMVdco~8LtRM!3U^6`J8Ls9MwXSc_u5joc zW(^IvcugUd(fBQZ)LYg>LE!qSDo*o6%Jj~Q;t2_g$ApESy*BUf=T=^|aqEsI>W4;;!@+w>CXZ$3CO&g;vCI7yfD)wMN4UU*9*AeV1ko zr3#Rs;S$-MHQF$(GfK*j{20__w`eH#vbL67LxL-PIJpn5&Q9Cs zE=GEpJRejVP9@6RJSmD$Gm@$BsKH4S|HhfC?{1Pz3`8~Xm{N&t354n-0L3;gfs9{6{3i=Z~55WDH8{HRY$=5p!=vUW7X z)6-WeQV8t6cwk%QOZF)@x+}5>#zX*sm~f;bM@!QtS_4>40o?)Y5LmB+ALWEMMT1}P z4z%+l6rM2i4%2CtM?gE!+ERaEmW~GSN(+nt82r{^0R#Fqgj@|74PfLk1pQi79nPsz zcIDRM{lvPhq8!{H> z5$0JPIUHKadOfS($G@Xn?MQVr_xHS&X{!^zZwiHLwfd`v{<8 z(1yl$3)_~}uUy7U8rJ)>gmfD@#nY2p=;x!?%R=BQrq+oT`QsP4J3^l1l8!+<%o$Nlz-PSu0>$Ww;4^-ti7O@OOzs z4j(@R>g9Er$>fLIP>LPGw!ir+DJfn8Z=2XNNmCu}9QYdUrfDCjmTX3Y03Qk zLB~Duv{pgOv%>+VpL$Yst-rS96I!7^e_w z`eCD|(9RD|Ncpb}Wf-08OZ-jj;@~o_0^PyO#zvGDqASgzOcT6We0CE33}vKrR)(5i zng&1-9d)+=y%s{Yod6L?PUwkrAVNnFr2$&D$R4NkbeXcF+@vy*iNueEkx|!V;6#ixmz_pBiTD6382 zM_Ium0!l!j0S6A9jdl7n0fgMZf%kpD@YEo{sorQ#<=+_Lx&Kb%79E)Tsyd!HK&>GF;y)d*B-7DPIUmL-6K$?lWEl*faQ&=F)f(J7N zwec0oF`tut?Jt-;sM^*(FC;^#g3Wv7l9Fsn+C*}VjG$_!= z#Ol2p#fF%RPXnU1fY)Ea)M3B`UGOgLwcaBIgy_?vFHK?2&4-$<1=-wRe_F}Dwh*uG zAm;KuHM-}i;6Pi8?WF9$m;H75H6SG7EEPL!QAt*evSY1VTqCrm{*FEKgrK;Q)sHsu z1-Zr!4&ry&$745nhLf`3>Dhxa2I#Q#j`&NlcjbL$Iq#NOUa-~nnC4-~2xm`J2)FI2 zH250Mh7HqbSpUJX*>n0-NVCFRLpSu5xi&oD{fu+5rMD~|@o}Rwz7BVfcYRf)6ohJq zF@MUp-i*Y5I5|gyrDNO6ylEYQa?Q{s&~@p%><$ziCm&Vh6~zy={d&k_vv{{(YyFw1 z!K7XU8ra!eCMLn#w}x)*jIh&8@8|>Ji3ET`Nmu}h@M#{6JO4d7^LPj#X``(oCdIyG zToI4n4<*XVeN?>H$ZdtNld;;RU{OUvg7?-=?pGMrkNi7=Yid1~A-t!FcS+GX_IaDo zaI|r*{fL{Pf841^==tF0mmNgn?VSKJOr?GPRE>^tp8AK1{1u6hS1%A1>iR4FOp~|T z6K~MrOA~k5%;)aH#2aP{8;Xyr0bEeaMk^|#BlXj|N3SbIS!B`%IBF%}z#c23jZR_H z&E#fv9)!@2lCvnZIEdU;ao7GZ$x>N4+g!sH7dqxJUA#>+?cb~CF zH2gY!=N7t+fvOT^8$L{grUZ$*pT6%+fKt^UJE_&AMUrx=y2j-RX0ole0dh~XVVE}< zw%pg_ZEGXkSR1QpHe6o~_ZxzV8d&zj_H5Sk(7ure_5HiJ4nrNu?2xd4ZF2gL&u};K zk1Wwz#_CO4Uvg^h4lcEPGP%#sXzIRfyMH~H8TrlN)3`%=SDG=v3mmMEU8SQ@HIL6{ z)MgC4j(C+q^L6$Tcuun1z8bn?0@COPNOx z)s>vpV|z(oq{Dn@?VA)lLS!}!slPd)hbk6_tbLP!oB3vk;v=mdy_4RHU*aZfYNVF2 zM^)H3Uye$#^;&0%-~bjkcDb+o7leu=Ya3JQ3r0^+Kir4{kDKy_macps3{A`suwZa1 zGDD;zHW)yK8fO?dLzm{mMb9rXgPXluwPEc@ZH@#81d-9d`${+RiXtGhD)Xe5zNnM$ z(}>nKHH2MQJ~dW|0FeAh%HKnj0I;)t=AVr41UV5!eLyN!w^Afs#XKle^U}7xi zj9f!kLCGI!@2JDboPJHT>A^ot4>k3Ei(23cP(eHUz86>zF>$fe+K=_31{g4WC_bob z(HZ4}-n9-I;6|G-Sx=KDh}Ng_?ReG3j!)L%3nQ@8VlH7HpO`#=UD=Wdozr{IZX5uA zeJSVRKr<3XBVSsTo#75%Y>B{T%SXw9d+QUqsqzONn#-&Ine!dhQK8yuSt@PK&X&(9 zSIiJ2|H^({E9ih!z-b{ZPht@4;_ybTb@0{@*K|`X;AI`;A$9_|Jf{$43^u0gkSL{| zYpSL~BWKU}|4vB2TdCa?Ie0Y%-^-EeY!j|M`N{b_4Jc>`pu%I6ZXu$hYAaYG>eA`&iU{|_tLJzeR@GpxChJEnhyBR`jFz$VvpDaZP8KdUp$PQU;~Y1epNTQ`ed@^ zMbBp(%HqB~*trMSThu!Xu6pnRP!SJ{sq9B zlLDJW0_^A8lgGFfPkDMRW-m1Sv14<72kihj-;+GEwFsF18-{H--!iBXk4SFc(CMWD zUIk$3Y>s4seDCo%x9bR&lLA6dQtZ_2Wh}wI8tA32KthMXkF@{P?e2L(1Q^Z8J4vU* z!}emsdk4F*|PAze6q2N)iWpK#EY%D9M8%4^2eU z6JA|Txm0ICIgy&K=n_?M5#VoQ8=H1a@;md1Uj<~m;pumakFt991-CP_jSK4HgbR7o z4t%}l>b~VdK8HFVZ66tkH25EX(6+b3BKXNqM$iCSvqa}Ke%I5srszsis2EdIQ(lw> zhm2^N-sUj*j8GJ+;=R8V(Zc3SU4Cu)UB^C75c-9_iAK&cMz{WwSx@0gEA@5Dgc7O4 z#%pnspKRRRr&Z<>XH)h$G^j0WJV3uD*p~;yM{huESR(tGX1X>~>1bYNJO+u2&x`4R z2jknH#-6xQIC490I^ZlQm9!9OVbk_Q7<4@=0S>x+m#(TlNF>|?KPkFHVfPVU)#3?>@YzY<4p#4+F1dNlUY&=2V`O&j0r0~!}i{1`F$`lk&>v%tyn~-`~$}> zVlbL6*129+z)hu{6)amG`^dBt35d^kgO%pev38Doy94aHa3l_R>$8}DFU@B3h&{k} z-&QQnSwo*xWBe8nhPDUsaPz4e zHgEsR=(Rrio1a@@Z|Kkc5?7q1_Eeqo3m@ZJ>kZ2Ub&a%?g=}{eVcDh6t6vgY6{4J4 zlgqIPp4?bUs`|m%(xgI}Zh?f!RsJ8P@AzO=9Skb22|5tu*R zPUu|B20+9Acl0*-hOx6mwP>J-7^K{r0zcefTRW2X6Cte!M|V1%!TVrL?&M*KVS6g* z)(K55jTSij58#rzZFB!Ov;z_I)E5W+&^Wo}NT>}3`*Nj%Gb(X%G0gh_ITatR%)G4H z*WgC-Hvwu4U-M~LrPjmqUc<%{s1;53w+i@+E({xAZQ8?GC@xHv;1`AmOmc8Fl{kM3 z>I0T24?d`M3W!nq^_I$Ok$V~$dFUO3(a#}rV8*6;XmMt?lE?cO(@{FxL_T=t{vZ{$ zOso2QUM{(kRpB8vzK)Vwy#M8DxAbj+SXX@8Hi%jVIU8FbUPIdiqNpSp0*O-bF&4 z#H57AENXWUZVf@VLT7jg`7Iz@o;g?wxr04I`j0|DM?eGe*#Ke^0)S-(i^*NI5BXZT z@JzOFA&f;TJ>JCLx$3Ri1_5C)F$MEU3?43JZ@28@+%WpSoi2zP zQ~fwL4x7v4zqUOA%?f0&P=ajfAxh$+Oc&2*TcAMVSitZviim`C$FJ6_qHoB%}&>L9nItE zyfQ16j6Xg2++JH=%)S9y9bU`EZE9dsqOQ#+4^@WQH2{YoCpWg7 z9{kztcCJ2qX(PD9&dL4(jQqg?!#g;fewf!koZ791lWcj5cMPw-CTKHhZ(o$oXo?*e z9s;%*f}XJJAo5P3&?wLa1KFtP?SW(#5`efP5Oa2N3!Jm@rZrs={#8~_NSx`+2HX&3 zEAVW9cPsVSIgAP2^sgiR%u_g7dh4M9r|PW%T(Bw*c))GtgK zu_Faq*(0HoZ)_P+pcaKPFD90juRNpIeu!l3$Jr?6-3CIu9*<`wBR&pq*nXtTqV0Y%P&qoqKYCeD5Wn|4RMXRY-W!BL&l7HXO#OeGGjF%oM3V-O z73}f%-C6QyCWeB34UQv-u2^Zr!4sTn1F@~E>tC4L?h?IepU2Y@P)?D75UE=^?RrJ!v zt+{@|!Y7bFzxfv~5)yWsVT*Gw8||+yR*vI|Ny#!};4k^Rer)S2vNdvC%q3%Ge?k{c zLCr*v9`>>U0r1_a&}zQhue`Qw4pb`SBA%8^)>;?xTugg&!jr0uX5+YcgO8&=redHi zs$e}iayXQQWH4e=EXBJCyth4v($T4I)BYyHsoUZG5HG+F#zo-JO#h2u>ylSW*W`ga zP>02~{TQH)you^28{H!_HYrhY;S}3@ zP?cst=pWN>T_F<4z`iy%M16bKlirdR1*1kuCk-6Qoh)5I`=Ne}KZ8Rj55mV22zcxY zPuE82&obz!(<9Sz@Bz$(P=nvsIN!rO$G}#ZHFBRm=&jHRG3t}!CS)mrt5hPR?#31c z&?6WKpLw|S_Sk?K4Y|fsqU}y6J^~4DT}h?axu*=xCSIc$KgMR&PEUsX!TW?iq@9!5 zRt@P!ATfWB__$Pfd_0V8L8hLN6RNov^a}s?5s~bpA;e0mSEAy@JYR1|c2~gVkx8$< zLZn4slOG^Pc{%fkueoWLTOCrwz#?Q+qky@}j`w^;d16}IXz0?gqu@CShMVl138%yd z*SZnxADf}RaF@3<&&&@5cMR4uF1E9c#fuMi+ctMJ*}F>n*Uv7FvL8qEy64EOSMD3U zi-pW`o^gx8XPYzo_*0^Z8IJ=Iym_ft!X-nePa2IGos+F2~ABy|C63$ zo6R^N0cD4*YgJ7hvenotr&gGJ`dfn?@r^I-q`JBZH}mvS**mM6eDcwm(GR!@Y{;cl z$=w9NZzlMljH&zo`wR5^Yv3r@`$H6k-cQi+4r;>B#)Oapo_WCeW_B2K>rQ!11 zf6+W|Wf;Zi+7Uq^vX{4c zy|?AZp1b@RbaW7BWZjR5+6!FZz;;XJa46b7_2U5*LlMGkBF2wp#RU%VFaG++0BiW^ z|Dtt-r#g{$8~4Yb@fGo8UyMlH-#qJWlpL-LXv%(Hccoh!8`RyozBqNp&w}B(TVi3P zmkIpHkO1?2HWQr@pUDp}jSG|dDrb&bMx;Y!;0!r{64&3K7n!>_0DN;X9rNO+`tC&OG>^@ivV?6=dHt?zJ7OMQ;hvTMD4VL6B;dtQALUc3@fTtqYx_2I~W*P*$dA{s* z_g%w5ZRjARy)?2al#hd17Nn#hcq?f@V#u${M6`_VU-b@K zu2~#MK>klVBq@;!(>Ji%5jp~01D*RF^e@(K-`J)+?~QokF|GIkaUb->g*d2U?B4Xh zvU)ADaa=*eGAh=i^6^)m4VZQEML!l7l(msNu4R5oa=YyC6eh3wDzg`KhjhEO+dIS+ zA0PjW>AuWocez$h$s(X!5}KZNp%?BhgMn|K(54!4v#LhS6eT6O%5gE|3rB0JQ;mYt|nE}Wqc`j@^sW|@kon3so=Y#Rus$xR(NoxghGw20Z)L_ykN4NBeFC< zBp8a)@U>7vOvTiV7U*|Vqd>pGC}?J282p#ihHh(9aw?;UdcGy0KQ!>Z46sVw{x=ys z?sDy^oUUU0toAQ;jK6M=Dm!GE`}I5v-7`zPDFKjhpwe%6Tk2dl$_^MO7OM*ts8$kc31tF z_&)L8pjS%`z2V4N%1o0*P<`^TqO6!K6_DJG&;>yq?2K{19;T3db#r+8z$c)I=#jN! z;GQ%RQqz(`5@9KqeaZ*=)%)7TpXcmRjRP}p1SST8j71Ci0-IgX7yY+8mXSsD_*?My zKH!fmQoP4(44C<*$c10qGj3Tc@94qq_u+d^ra8{bpuRZ)&*vL;7#%&(DW!+HomDoV zE-$h4o$>nGIrIZL!vyy)FO_q(Y3YdGR5QP1^mC}wD|^DV9)CAvk%>gZhMDE;;wJjP zth*p`xIQs091JxecgRr8W6hwoyFDdxtz_pU3NXCH>aUpv7WiXgMy_5Y6_)xi~nG_MVH>ThI00up^qwffQS`-k1LOi}kNn@?Wdin;EB9XO232AQYtB z0RJP3rj)nX`rhhvA&O2b;(jZ|MTUG4TY&T$t%^(Y=VaNdrly^-!*cg48jFn9iM z2$V_MQ-{NB`3`aZd+I&cGUBLkOot(d2LCMxdD9ZE{@0ZJ zs10Kjo`72_tt*H6bNgfM=Hwv_G3f>f{_`9hbkV~W0FS>we_|rzV?3?%E9-`V(9Pdm z0?5ZkyNjn&IQY)-fSk-VQCf6Y4je4OmXB3nn&TP-MpOuqiKdyj!)j(p9Aj3uow zQ%13&s9Ih?p8_4#-55W(t5nk7VhK^~wDN?UB}ay{WOr&9nEmnpl(p){6YMvBCUQb{ zkiR;jAWG2`Nz$+VvKp=9NtHa4QN;vsMK#l)3~xx?_$>$WnAQ^GIe%F_uU^35)K_nr zM0nBp)s#LN+c!mU8TAIhf->c}9FTckm_Z>#L_$@dAL!SM3v${#zZ=rp+)jr{bn?tK zf63lLpLEAhPA?Ni2c0#F8#$a2tyR%&XABkSZse9=d*v5qVJJ2n`rQ?Deg=8I`O0N) zCo0#h;z;nUf%leS`;}4Ce`#v74|d4@bLd!RC6dVk!SG@`ZN=sX98oeQXlH6xMNuX0LK2V>hJm?Tmbm4@m%&vJ_k6F;|tTmYiGPO_-oX{ zznJziLq>rQ@c>caAZ~(hxM&JlS+4fmLiW8-1sEAk6Itl{q~^o5^sn|0Z`Ng!&o%^G zlT>xyZFe7VQWZ_N<`U)A;68~@AVAjqbf#q!COKIOxiId)8FCGySZyIRtVH8p7dU@j zrqxif@scF{?u$-w_gQg8@6Wk!Sr!6}RW- zLS<^|{3aU=jzMk+-Zt*dONUlWa>VuaP^>S8*G=?;DT3kZY=lsV|=8&l+5(@K9>~P?0@N zhU87lTbuQ@;Gt;I1D2KK;V8Gc*3>tW{b1E`9b)Zl%8oA<{C==s5iNnil`p3+JGq)I+uXSJFgyr)7{>xLestYP?ejx}GC zumV|hO%tkXtM1+8bY+D-r)owcIFECAo3)l5%QMB%+%9uY87Po>w83!b9BDao_Su?~ zyqk7Bo08T9utkY8A7bqmUa|#@SMS^Y-alO0gc_gz%RqAQq=$W|@o@C@2;$3P2G>R+ zGVKLe)|n%HG@OeG*hoRb7TtGiegF6Z{7wef%k>uC9~DNX>i$KWo^QcxRQ7uxOXb@s zh6E!Pa5{D!n!*j`aE4*@L@3FqL&?bj=#`~Y#4I~M$d-U1V`4|Z(D72$qsR|5VWePK zCSUSqPxdDi+y?Xt3rG|yFjVy6+Z2=G>%RTNF6@ax?T6#7heU9^IT+P?Mo}7ixS_zr zVqMup=5P_Qh^n`2x8oc%lKMQouD__OwHAwZ$L`l<)UB`nzWgw9Kdv4=?^NcbU6^!z z>Hcw@f9_6jsFM@t+kA||PVbMjL|S@*f$dkOFjFe4y?%aG+Xh(%o)@n$!som^z0Qr^ z7))+=*hq)+>x_7?6`{8p~70hQ8yqjHxa`sO?G zqZ<4gFUaPRMJvBoEH$gz^|c1=+G?oXP9{LvaI>ow6G#l^)}l5bX8(`?w3Fmwr@Tm4 zgc(=QXu-hXnVPLO5ztOaf|76-N6}n%7{pLZmp!K^f*y-Y(&c>+zXN=O`VKsn0W8$C z{J-VudzliszmZUuUA#3vK>D|r{iH3;mnrQ6uMi3tK#lk&Trxv^x^_yG7r2dD714F=uZZ$9lU`v`_^LzW^q1FMLc zQUFVQ=rY>t^24Upnji6V_7s{5w=^CpfzG&AVET=9)bDo3WE^Vn$ zp>FOc`x0Ra7+VHa_-8Ks0?O4o-pyW()MlSIxuJXvVOE-sdsi~Sdo&l}-3+4{zJ}7CU-`%R{_F-X_!@y0V_1BgFu%NEsv>D5d3? zO`EG-D|r8-#L9xIkw@$dOXZ#)K6kCA6P z;;6a}faoDat?OYH^p265L6G2*DGYK=EP3bXzt}!_)?RyhlK;IAJe;|~XZIu1gaESc zQxWD{ErNbqY3m7FL=^wCSKn0;JCF1bL{E@*NO3?3W23kA%&qMh2e@ABft{@Os2csQ z4g>UTad-B6fp)gk(K=Bhw%-973`d{MJ<7LTR!kiv9kuZ_deYj9d58y)Q3q~83EjF+R8vuRKCRD@Tx=g~4bHshh#`C=x{u8o7WsMFJF@`f$I;0a%z)?0c@P-K?Z5VrN?m9B{LYL94-qW(%p zLzc|a?|EjxARGMi4u^`lT2nBadC5!A=#K}QSBNDCT&kFXVtI&4b>V?k)$wp-&BJsh zwm2=_R{HiG)^>#OHNcvX4n|wT}>LJT1o#(P(#^Kpr5`B(7Me^esubDBP zR*^@sBHWlgf{KTi-%kQFF&uqI2&AfVI>~JQvMdB*@T@D;6zj37KJu9 zu?G5k@Bz-g;xn0s)?sHJoY3nYXj#rs!63l90kt{h63QPtO|6pV!i*{jOLjvry+j*2yn-?qUbcPk)_T7`@ zOz53=$|==anYm^jt$*sXrHn4(E=u6R%v%So&YZ{@UDJFnd7RP8Q2eiDqDIL2Z9+W5 z=U%@FfCXUk{vR741Sb`z-3c{yY@Xwe z&3lLk=$yV4RS)=xj7GG+d3Els1f3wPb^D7wR9frbmL*4nnr36<@3X4kjA!KtP??0zZ%Jv~nXKPdP;R(B!0u9yqlRoBoe0o>x7w- zbYJZgGugl$#!;IdKjQC^7GX&W2bBgH?0cAR#mifNg^9V4*IPcMt;>h_>)8qLp<5TH z(QIhEe`FQh!rAjwx^X<&Jl;S(mZ}U*F~cBE(~}st#OI(f{%;t@J^Rmqzhikql0Vp# zi9E#~za`Rh@Np6L31LVbk~e&=nqtH4zexifm5D2mkif*D@ZAr;5QHy!eg{`D4Y z<*C^2(|||%@ib^KOaqcj7*n+4I9J1Yr5-fbQ%FmM(U7Ve6;N!d&yrc^(r1$@OE zC1x@zu#`6;?tJlZkn0%_U4J+=yAitc-{HSe>3ZGBV5RE4_# zPRO#+KTS1tt+6Hxv{z1Di#2nf=_RIiWh~&CPS?XG%WOf3Oqb94Ri-N=M9&G}!0HE# zCTT{M@?O^o01R&m?9zUE9XlZ2Z;D$8w|5+_8 z6U5vxR01!_enD|QgX^iEpwVcEg6_g@83crqsGE_Bu!$%UGNQOOXlZP4OZ~g&|ZmH4Kv+< zA@7sdAtoaKV<}-&zjRJJa6!~v<(%?9lsG+`KRw09OO6*4-2exl2+S$q~#`mXhx$Stj1V8As zmI)cSl zbHZ%?_|IE}vYv!;PzvgJTGh<-ZF0>VQQY6s*o+@9zAMPHn>;OX8h<4E8g;69v{I}$ zt^xb}=~)wc7>n12@8`=yL!V*JioEbvV2p(`o@<3&m{hEaOm@&FhaQczoQpHdvYnIN z$h&CkwY8R|e*arVm)jQhSRUMdfip7inaT&8=feL9nq7HSWVzq=KAI$)e$F)bnf_MS zWVCjjGt~9{AwJHLQiGreL(gZfzBk_51{GEhp6WvAwEu{0YaTrVU>Rzh=C_~DCU1f% zBBAxkH@K*Khp*z_SjN~|-qwfDP94Zsa=i#U1#;G|D-emnfXD`D6gKSu)7!x~&~08Q z4g`@X$rDF|8V9RbEXh~MiYb;>e}sfB;z{R`?0+>-W8I*Um<+w-o)@-9`i=cW@P^|7 z4X^s$MbE%)R&cZ*?PV3SQm{{yZW#KOLUPYQtZ;mb`^1rY%t)e|^whG;GWAHB&>bb*0@&7EMBF zT$E8*Q*H~>M~%E4GPL?|`_Eh`J)7AfJ)Yvs4<&*s@Wwn%dH7VZQ#8&MxxP?BN>l9G zYTd}#*o=NhJUqhf2}`%Z(QL(imwJWuGjS2e{kK;HuO}fw2~+;^?4^HzJyQ3;NvBPijNcAD0!I9ROX~7+ z127#jIu<))aKDL*%7y+}l0vR2jI?f15(2?^#dl`$wG&6|T$gbcKgpFJGg?lfHpkd zRm`ymCzhKp!q&~kqVyo+@cL#m-)89m5J0KS{77lvLp%Bz?cg;G7>Z?71m*|%Q-P!S z=p7Cai1e)r59kL-9Cph)ruoq@_XHdW`vLo6f*`sMRN@La{vY}8{SUwt1l^%H(}LWO ze>Nilyk&-=-5-INL%E{%qpq`)FUhFvNPzHgQ&kCMWz8dc!R)ARwV2-AH#ygE%T8-5r8-N{8e~|7nqK0Ricfj-flGK|s25$N`2q_i*or z`;i5U1+(6B-Y53{ZAQN~?ImR6);1Us4e?@4b%2F|;B!?}_*#xbmhW!+lW0kRoI$I; zkn`7^L1j@nI7~5lit8%5jsu)$YZBQbVYgWx%*l({Yr7F*|(Ye#vWJC?eb!VO=5h#qCSIediDT_;}M`w={Yq(?`rV8 z%5T_>lumKXPN;{d;FSJ}GF^sFoCN)UHZ zDm+<((lt0CPXq+Owx9>>o6cr3eL_wl0;TP5?$_yTKeFL>B2GAjLI7&7oc_OaEfESj z9qieAf{E&H*;A7V z?$72Z_^2R=>aHRUD`TWq zT%M-%@oGFaCia{tVJF7@wjnomwcMfV9<98>(2$oQMAOC;M^^AOx7Iv-^gnTtmXEmo zs$o&TdiwaAe-KK^u3I7#(k5y6c`VvNf{%v8Q0>mmdxUjA%yRm6m#c2{drYwNvenW< zCKIsT?09!_lK>-KxxIRDOAXF|teTJ<2=w{M1lG36vf={{B$nDe*=;~5swiBMUl@MJq;#``9Bwp zXTLp%`a|n9wrNLA)V^|))}+i!BiB$ zav;@ECUR!`!mkftdb=9e2kCd>9WMy(Q9`9e_W^C+hDtjuKyHP&;1INtDI=NZz54{G zll^VP#zulsXBh8sp*n}pbwKnIKJv@;EXTaf7qwRszG!@I-B2{HrxUh3l@@nCa=T~G zJq1Qr7bt2I>~z$eFaS^|Q4Bod82iU>s+4~5OgDD=(@o>4>I+XN<=0wkHD|yiwb$G3 zQy99%24DqBG9mXrW+y0X2etELOAm!vaU8|nn)idoN!`O@)s7cKu8#**Y1q}>1wgA}NVr;M?Bffu0JSXdAt zv;XfL_=SXAB^5ZzS6E+Uh$6{Jr=c-`O}}dg_oMc&&}A^l)ptt3B^02z0kEy|rMjBD zJ2rsh5i#0wuKwzc4aGr$=y{q)brlFoY^5SH8ci-x6!MullCHx(qC%X+623Bs-%5%uPITgX|Ad(rQeJ3ZLZoSloXQh!eD^ zw=t|9a9D=4YZt}Qk{y(AiskbAz*=Md+|Lu4e<&CK7>bR1 z`Lo7jM|%qAhJMVt4+tgHY=^^=WwKmCZ21ZU8drgT)Z*Et1=s2)dFSRuk~ld}0Vb&y zS<>v0V1>yp=*#P9Tu#e`)d=Bk2n{fKD$izF(g*w!mifgw@MVCg3JKN?q&`DGEMYXa z^tz!*+d#`2L}|H09FRSSF+b^iE$n^6y7NxH(4n;XL-b>tsA0!VK;<+~V3lB?xtjFK z^5<^q`!;S^H4@^I22fYMA`3ZS{%d6v)0?Cfa!CAk!dObB5}Yp}$3 z`$WL?Du;2s^mxQmU|RIkZ7YaAnRxjYU14qZoxWmu(zW81p7vZZVS$~cH#9a6|LhUr zs!>{$Ss%r_uwLivG;!lP<0pU9ljO7*-z8Ltw>)|4E60)8?;Hy$V!bT;wzwvCS@ecP zwu2X0O;`Stq`>0q*b$Bl8eD#|hmDe1D*lkE;O%aaxW8mx-;w*+v_54Ji=L5hm~M%1 z59evVAoU*hjIV{hQ;*Qxf)ZMx3$BPKeNO8iyc1#pfu^i$o&Q#eoJ7bHqCfeHsGPgI zV|MmW_~`dIWA;3NO{XOX&6uATIrgXw{MBj>({pie^BvDcHX-JdH zT;o-}&OdtK?Pu@6n(8Bf<%0 z`Xe1dk1o{-B-;0KY!;0C2M*ZMg*+_+G(adeJ?0{=KN~VmX3AR%2!}RogH@>2Js3`p zAV^3=c%j0nS`Plpc0YdxT%!xEiQkJTqB%%NVm+Yc#56Ta|d~6U@Fh@|Y-Kt6H%$HtL_$ z#K1ir@gJ%x3vEF=?x#t9bUJ49wep|RmB)y$bm-Ga#VCQG!2-FX(-YRpSW0H)On6SD zp5Po4N?i5NRL%FHrvR!<_)mI9&MHj3=(4-O7Y7jEnM;UnAPt=*!UqD~veexX^OwL> z&Ye!{N2j@f`(-}AXvkd(EK?RvU%3HW60F*Qe1Z1wyq^oncd>Fo%XC_H?gN`;Q-lcy zrFLK0FHqk~3=ZriQMzF-;UECeXb$cVbhUta(1;vSscsOs1shcJPQeBJ;RerT4%VAl zW{vvst|blPCHL6>EIe`u5u<$nsPYu}@46Sbo3E>|d&I&b6=1&E>~T8G|6xe*9WR;% z{=^pH_Cl$u8bth~r@OV)oAIbIVx@1lj%tB?V-DE=nMn8#+8Jc_Emd%@ffkPUnWL0ozUqJuTlY+Tx$T?D+x7zXJyZ97UT{lN_+E-C#(k)4#zn*OLRYm*0{VN z|G{WCG7ftn*wjDlWGhj-;Ruj#P=`yIt5=$O`WUz&dg%UXSzCxdBM4Uh8;EsEl{D;s z8Vj({KkfT~}Quu0hbvez$hOXkPXJy&yk>Z4TRLJ;#R$OK&x7 zXY7b|n)(&sv{K|pH>dH1tgs4xCIY=W{V{!;pjpDwv#-cDQG*@fX2fx!Aq%^ZX3;v(z! zDOpjL$5+cKwhW%wb2eGmdFy)Ud9TBIZ+(@Sdcr&ozFFmWlO4P_C=Xipx{qcsIdRa> zOVm-C+pEY;`CbO6r!#f!1`*ACi>J|))QpwuIjO4nOIfE-Buz_d3^dDPp4x7~ws{!$ z)9|wwf6M4$=ZmjHA&CDi)tl*Hn-*V?dG1-D0f%-c!L}2hzokzb|6FGO55{r#Oj6u1tQRi!h%jqx!-)Q{lgPRL)?i+_4}?N{m7X zn7fP`FBnJj+*)R0+;$GNeOX6X&cm@B*3{YsJ-bS@P=wKDN7!b+7<@Y3;`ucSboh}L z;;pR&khOa;d!zV12GAnt-^9HR`uhkRYz~Iqz&2p!r^AT;5nu`dMn$k0{=)!_6sb@g zz9F9)P0%obKIp4XaMwY9?BWXopQ-B8I8~k@gZumNf%az>`1FQB%1GGV8V0Y(Nzb#m zpKo&FzgfJ>>0j<+q+l$G71r$z@)kbNdhN@feL@Rd76v^{$_Q>ErPAT!S(CNQ6HYPA z%Mn~Hh~Qb6CLMmB>hmIW)I1*NWIH9WC(>apcnt}Km>OD^w6%MSmW2Mb1(Vq6=1ak8Jen9oVKj>HJ^Lcx&+$LL z6e4+7HOsH-vf`N=zXy-@GXHymc0a;^aqsdU%dY8k1`r$4E0?*+fnrEi0>QehPU|@l zhIWH*9>#}-y%KW|Dw(41^w-OkaOiwPi1*NiTT7SSb1p~WWh|`TbVU+9-i+BHo z_gEgJz!4uEj+=}$qG(`?cbG_VSnwnmXjMix>>rfJ9 z4NhfKTVjKpCU!Vbc_z1O`AbYcc#YI#HVEl5_ss z1u^hCMn#MTlWY@ZoH(G`Heh0g#5y`XBrW+x$vVRRHU)d_smYh+2+12*QaCL{YL{o3 zElxIWW$Od1Fwi0oE9m4bd33AZ*@30Hr-?d@vdJ{a%aZ+?TW0Bx2YQ>vOT^@mO5KmT zt0*LdGvozjQr(;(%KrHU6|lH;HbcrFMNlp_8TUz*8FW2Ra)*GB7UEx;?Z!*GqCpk{e|!!x!jXNab!H%909!?Rx9Ro!g= zcj?&{;fv2ZOTyyWgXg?NpFT(4ayE=VE2(=Hkc?G0!Q#P|*Q&`h=lBefE&IFFenFyC zRd+%su!Be~sO={)>qO^^5AQ`_@RNoLC@r*?(1&gyYqUE=@ttzco_R|9*m42 z4|cYlLs-{HBU=O_ap<7?CoLFH9 zMI(BB?ecyZY`6d_NeUS5D`KFbYtY}G@O|%Vv_gbw9R(GjkFds%*+Gu4=L{;c?dUPB zMFU!&)gRXMoG1^68gSpTWU>ld$H?5K1qL{11>d@0K2=AWIC+>eey5>of1QfD36b&eB?h9ohU^m7GPlk-o zJgiJG!#JgO0L^N2?OLxNjDxxe$lksi`K|+_vg#7JTvR;M@Fl_ z6Jk%Kp30jO${5nWc(aQMmC0`eflBku%YncvUXs#3&$jwvfR2M3@GX7gX3wg=a4}hV zCtIQy_K}PJx0KKkuOZ6hd9L*PHDLRh#`C^^X6i5mz^3}u+FSSH|P6NCZWpraZ}ZHnNa24wL+-*f=mp0OL6?w`-*s;@z` z4@(2)f{6NnDdVpk{-*UkRx~`_mDEpx7t4{xTW4vszB)!Lr%pb19R=DQ>>WgDAESEr znJwydG=fW6dM!&R_M9I(Fynacf1Yc;2|5v_{dGQOCir}e6!X^=D^{AL$QNH1cND&H z_bVz{{H~IAC02HRy@DVuX$6`+c&O%pc+)nP&zEb~5on2isSP`eP}!3!iOILIZ@rRd z>Ygmx{I~6&a{e>^xT3CohC+Zg*JIbEF|Q8R^1ay%^Dj${)C(IBu4J`qr!Y*>74wB` zYx(hEDt(}5j@?>ec#n~{0G`o>*X0uk$gF=p-{du#q7g&cdvC^~6lQ(&K(fe4vuV~r}WURux?bTW7%kL#O zstD|XPhrs4k@_=~i7R+OCq3RsQ!^MUO}XrQ`Lz{&gBW>UY4d3V(54{(_l9k2Ij{Cx z4Sa0MFNV;%0eFn(zuuO@#y%(oJs52Ywwv;8d+*Jfv*&Pc|N9Mx9Y5W|x%}%R#Jsr) zH942^r!TV5^^&%iagtX;LH)dB$=z7iIV&_|7)|mKNemU@X*}u|xm&_BZ3wX&|2zM1?o*(AHMLTlP0f>x3YU(g4e8Sjp<=@T=Fgz)fk&b=z=SK~p$Wgz$5n{1 z@?|s%p;3hHYlbZg|@>Cv;L_PV?SeIuG&&w*T^!#%vOp>T* zuga(#{`ep+BygM+O_hULd1uZ?ZB_C7Al+&PSNxV3NjT%_mboeI#U{Lb_Vv=ecN4dg z(=~4K#2hiNQ_wrw#1xj~pR1$kwZhnOHIvRbG^Z1v7AoD8a?$1!d=Q~MZ#%YRK~>Ik zU*@sJe)#7+0YgWw>7x(2Fp&|-(f4bvxJ-O2t>KV{_TIjkn$N%o){yVd(&3`M@H$|=s@fEH=uIx>Yi$+7jSEjTwf zK8}xo2CS**-N|l1DETQt&qvE;R$#K1qc=#`%I!RRrNg$O3SWVuSHbC#*`nB}fd+rv zm7Ajn+*6G2JxJ)+_8Y(U$AMn6z1|K;>>u+0cU1Iqf)DgXLEZBV%9G2yH527?HbjG! z*Kg;y#~KR1H-3rlM?(ujsZmjqA)wsN#O2b6xrD--62Q+*yEFX7Tnt}n{jgx$!d_^I zY}?2=$RV=KJ+y`PY~t>et^qzmzfpSHzZ)X{Dml zjq_DX-n$I|yK=cmsd|q^?>DVe|C3yH+=dse-}AhX5HVSc#e<_|@dj^ym`b835Ps6_ zoGu@aKN%V)rJR#c`utE=wmE~bZ=KY-}L-ekj~Rf z^DvoF2QMP9aRcv6??7myotbcbHi$<=#ifWK?X|;zp^>@2_z&N@*uxc3$uopid=msG z5D*?f^ke5fPy0+hHEIe3c?Uh@rq$}d@BV8C>i<>GpvT`lf^Am;mrIsw{}^$&F%x7Rl2>tJvTR3-4#T}1Y9O!t%^Q1pVAy8`M7E!m=08bKdzOuhEu%T31Grj+ z$%>Y3uPado=6s`&Xnux)Eu2O`7sqnzi-+tQ=mX-4SG~2OyDR{vY{HYZ;jxCi7$Z3x%p?nOlss*kgx`49|3alcGk-_w>?~K zO=o50@Lc@PE&;DBW=TTi#r{UknSv-{j$ zEbWz(zUdT-mc{YE*hML1rkE+_&2fpVH8znV=AI1gNZ9IelW39oCW({SOW`YRioqO1hDoXO9&cSL0758+o)sq(3waI!Buu@sz zl@KXb@+&4Y+Qn!NalLv;h06a`jE?Al#f%EQf9R#Jr@O^+okbu7lwjOTG(ZiHvl0LS z9$Qg}e%z}RgsRc63v_@vL!gn0Ust!_E568g+(oEIAVkCyJ>$rlEF)w!A|fT?J->1f8=F!r9%(w19A zWZcMnu2}>{Uz2pmV)Re+K}HGgaIFypQIe*qaS<=TQth9cy{$I$7Cg6TF{W7n3Hb-3 z1mdtd^={Gc?U9vt;bq^LdNEOfsQD>z!ZPu{6AIyBV5tHQAKRB~mY)E#*#MDuZuAY>Q`^u>r~|EO=7PcMQMRiqOKC3;>W{BrEy6{t$um4oe4M} zqNJEK(X|RKR~L6l7{6D94k9^G&!WIdjr` zGRn*(m{o;IUzmqq?!`O#$V!I%K}K6$sEMW?NtBMyFA5URS-Z`v!Zj~osrVgV$DBfg zw-hSR>|>>^nCtajvwn?++FP2!V09r2T#D^#)Arb-FEuyiTME6qT^q4b7-%W(JyUF8 zAXAD$f}81U#}(*DL<&zM)x!{&rPiHjn@#<|1(|}8GQ7Fr2IP3(EdioSmk1Up^okEk z2|cGjuSJ2c>q2jVVcxZscbuAdSpSiO1bf)Y`$>6IF+LcS9ytf&3Oh#VZnznSkRF4X zd88vO@?cENmKqMz2X-Zanp5y;h`ZE1QmWB~JIS=;TDM(C1I4asxn8cW8IW`#$%-?c z9jJ(Fc?xqANEkrthmIp)Qo%yvLK?P;JNjOSx>kEIOI$9zxN8>CJ;rNi=aK)a(u6WMU>&ST`G zZKFj~dYjs;5Y3^9?JZe{y_sK_zvY&kE{f`Q$O30nuawhmm1{Mtbi30e%0j`N9yc)l zsp~cTWHjE#P8df+@mGT(BpWEga{_5QOA*q&^*x%%on*k8%82>QHT&6{n3!F}0r7jU z1D^}{#AC`n393)Re-K5MdcI+zPc=FSQ42I3VD^wZ3*sa)H>{4J&#Xq9eE96i(LZ?$ z$#xwTG#1PP!g4(uBPqVJoeRH=VMbbhb;9iTgU^N)*4HDgOrvn3{Q1{aFREWjxH5aH z2#>It^+z3W&4~?jxLuWKIOApK;@`dvrSu-!mvEBF*+fHF%7gL&Gl+ePspqe{5R=gYkRP+PVd#cx|$p{pxN!C){5 z@8Q*BK(PTXiwailUED#zsvq35FoZPhznt53K8zy5oaE9n-N+DXKnK4wqcDd8@cPq4 z+2b923d?PM87jVg6(H0MhEH)Jse$~B*TczzC@$7#$c%a{fEW;N@bv%m;G{$I|2a|_ z2-qpahV2R-!k+O)l*5vEEWbja_!i)a~a|BMNaW&|Yr6sn(!nt!cP z8NNNnKt~#Ql~vZmHRq#HH+^Tfu1)-C(qDX8PQ5V}%x(jXt6(j?rwLZ$>#SGYHORbE zC#gNE%D_^RF*+68Z`)28j_;#gr=($Mz%tA3l54Z!lM4IS6^>1Zh^_E8%KIMCj&cS z&~C$t576w{b0DEt*xJCxMl&tg)2VVN{H#BH3|ZbdRJDt#sf@cwtq8Jgk_{_M$<1* zZYUGDA`&w6wr0@&2HKz12Td`mMI*co|Nh&~C*tU5)PG&c&qxua7+di(nFFy$qrZXz zFYP2Q)~CAC-4#g$@-F-T{JosboKJNrXK03G`FWzBz%~7Uj*)3-wLg&*sd`DFg)bT4 zb^~v^6T^+)hi68oV|Gz?=hkb|_EtPHP^@5FE_kX?mB-T}+Q%jQ+Vn}C0x2){uZgs1 z0ByHmK`L-HvUx$wF&j}38(NLX6AA0-mqN1+YRY<`dl0Qa{DF!C9a(3 zIZTsz&=w-TL5d=6YAv#$W2z7N{%?Q{p(?7`|JYsUN7pJ5P{6d`i1pO#-^T1^M$Ne( zY$NRCwtFS0;edMQ0f!c!gW!CPL>x1tt>t7NKFk4i{0N|j6IiIwuq8Ns3^WS!qo@ZK zE0XVp2#SME_gfAjKs|p84la^^a+cbtbKq$j{hPgQk(NJsj>Ojf{O~}iQIMoA_^F?Z z@twjPEf5B86)Arc&GrJFR*Xxr)+pU^f&A=a?8b?=50n2%Wi6Z>im9&=CPH4XwxBzM z|KXubZVlyEq)bP_)@1Nox)wnLuUu&Y@Nw4i3&BP9X-JkDLqFaV%OvLBTpe;VMdyh2 z!ms0?hoyooEtQW@(fJIpXb`^c1GKe?UuS=9Fcw95aRvH44ual)5ky{V*?kShPmiRG zI@oHTu%|+;^onUmy|L!EML_Xgz1pZy z(D5xL=QeoROeBf;;@a`6k;C7ioN&s|01_isP+4&DmOUhAx8-juZ600T^6V}<@?7uD zdM1#6SHj84r;K(8 zYNCkFnbUPj!TYw6$_>-3Osw*4&MJNWmpWPBAF9#Ng@lU$v7HXcCzoo)tUgEpCZY&; zkoZvT=YdE0h9_CEN2i<||2g#6#=KJW+AbQ^KIq}&VJdR&0_xFFqy`k2+EIB zFEB>#sP07GX}E@s9}OILsXR#uEG{MI#j1!kE5KU=iyW6gD5Cv^xk^RW7tqzr2D&s& zvb!l7)=Y*R<1?PurpO@Y6k+ELGJe-N2vDiBLh%p_d+m3Uaw@ta{0KbwCOHo_e?&!9 zhds&UW$gy+19<8kqP@yEcVp=`>C*4P;A1wB)N(F$dh+7jet8+uU~@(SfZAbdP`i^) zN;rD_4=K5^V%v`3;D$bm2JYeoQCaApko418MP0&Ham2Lc7lh&ooB0%D?LSA3kx&F_hUYG=)CvGt67Kr zRMWH2xydhMfTka-{)E%x3=JO>gvmYR+URKjJoq#Rygtpdz3iK;+rWaZTEoMUXM_r! z8em#2&W?n>v7upDL2iqm0r(8;lc2V!L>i za}8B*uNzQWUHe(UF3r@jH#i`KzG675}}APIK);f8&3A&Acjq1!U3 zL`x0QDnK}+0Q_VQn^!<`m4cT1R=$z!`a0j=(NdhCX2L05H=gV$t1b9@IiB8lVVGb! zMTz;+TR#qOGcWG8v^vg2-_T-zPVq)E8Z zelA`8=2Mc2qJRhN%wm``w^7W-6aacbRuUy}&L&>;pi6tItLmEyR;!}zhF(u9tW7B!p<_Y1brym(OHt3;i7bwdNt zk(GnvFJ04p?n3|vp3nXfRfym!VuL{P2l3%X{^u@yWIlVx?026Vt=CbKoY@~lIY3Lb zkF>B%I$%DsB^VOEt%wRQF+JYgUd|F11c6VE0;>&5jxrg2d@ath(waiw zp^tNaf6HNUEeMEXAweD8tBpy1bbUYI_>TLzTQ_W&U|rT#Zww>dSkXp&h_x)Pdf`zY zAhC`_2a5cMUi^7l|BVm-W8N9SyE^1ly7x|ujpE1Ookg+?$w%3!a$DVCSFEIGq?cc^ z6`PbO47TPYaY4U$4=f!c%KQpx5**X07hpVYv`#-UD)>Wj-VsatRjVMc2ud*`>m=bm zdlUEt|AZhILBi%HUkm5XB1BIdF`j;&|6OJEW3X3~s&xk!nC>J+wGbyyyA}8Qatjyr z(J?+l8bs8$+5-K^>;V zSdgDrs6rt2b27UvX?P(%6-q%Zm+O zybv}8LG6ezorKwU@}UJ0P+ZWU;6iW7f#egbmr?G{xrQ1o%? z=dWJVZ`_b00C=AiMq>vw_Ky>nQIUbE%$Ziq12=YYu++nerOGHCZ}7RH#*0^MO9+@- zGRq5U5s>jT8r0j<2XxNU9Z5<8OfxBwvBho4>rkiarD!1Es(iV(>cdO(O{)B~*JgzE zTDf6baP^~$&=L@QnA2s9cx6U`=Qfw=LaE+R(VaCwvgD&z-&)oYO~gQgHXZ>W2jk)UnP+#7p}`W|pRgh<5op+?kDy5kqz`yX&5k zDIjg^mlL`Fm|=Yta@f*mE1t-wqP(iB+Oc9s;(?J6_M>4y>j1zdx*8Pro{r$c=TbEe zpi|&O_O}^egaK83ve3PLEh$Kx-sXDcb7ul}_CED~6hhCA*Kr@7uAs>brFE z(f%#h_jD#XgtklRUN?;o*+qp+NhoP4q&h6?eEaSDvxNG-=ED!7*hljIVmr)Ri6iN* z5jpragn=D_e2SLOIfrZ&);EgouBtKl$hgFnjuZ2;&M3fDWL^*El`NBMbhvSNz;CBt zUgGMmjIVL+!*up2!ep%-&tuNKg4KlI9HuI26ic(Gn@jkpD}BfibNxg@>sJywxS@uW z=>B2$=~=JbE(wQ)lU=lMuMWS1biC&UC9jEj&GV|~qWv1iE3bcwHw^i#Lye1nrNscd zefnQ8`h|r~b7FJBywSymPg8qVnn(_m_J5xd;o+RCwJ<;v3?1|Xqu5qjA1WQ_^&Frb z0K^4^RQk0JRQX?{16zWs@bMo+w5wPsVKpKGYh*v>1AQF~jWt}yeE7of+YVy9PS|;< z^gEYA;Tf&8C^DrrdO3ZH|9Y>F4c|}w5|?g;d*vL0bw&$_ z8e#JliYkM1>z}A+0yAO;*XIq>4v5=|zW>X_w`d}S?f2_@e{AP$tjRW*A_R*}EGH5k zMr-k%OW{Vv;UNhys8OPB?`_XQfaRgyf~aj#$Euzfw?LP{ zX?DE}4!<-zObUmXNfR!o8R~cCPex9l`T0SyP+k573FfV#Ty8E-ZEI$A=eoN%MW{HE zj&C4ZYWW{0Pn`@Ek4=8d^9ZcoK;Yomi;cDR9E{V6o)Z(j3K$#)vj*r{Yc^ns%*YIt zQihlCm0&~?S$+i^{2M`uE$Cd&pp{@{m9asl1XnaFEw_FH>BrR$tPUb)F0Do4y|l!F z<8@xkud@F%-Fgez_A%_*f9~}T;JWW6m$Wf4o9rH2KW3M{qe7ivc|Y(KX2NZ@Tr#rqk&Xu#1&1HtpMPuD1F@ntxF7ak@Z~l z-c7o*FbYavAD{)Uvci`G0i6*_KxF8ma{3x}?zME*f=U4MibW4RXs)5U7r+e+g@-8i zQvO3T?OeRbH55yzu;pR>~7i!0Y&(*YX3fP6_M%YjT>16X?Iu|(a&Fqgfmj?^$(!`bJ7sVA>zyf~6Rqj?9HA+#~vTySs z>Gf72r<+PQ^dHuk3V5jd6+|kU{6>1Io))B>Llvp9fT9?4Ia@E~^UAWzpSaZ)?H_h# z#48w-bgJ#T<>x?WVIrBbjpcRaOsbwhv$jLa-_$uh()o=G^Gjn3>1nkah~a295WHH- zc2Qc^^%G>uUeE#u^|;_dEft3T+=?<(wc_^%ZkMVft?>Tr$tZgqLjB##YN%qFAEk8 z{`!!73Gw1PC8dhAPVYmR_` zoFE~amxjw$}UeQw0(tSeg+&YkGIsC4h923C$3y(8C=a}=y0Z%;n zZ&no7HMFxcV4lVN8PYkmVI4;{{F3*Y}+R6j|UBq%Gs2I|jGBqj=b!ho)WjdFrV z;Sj>t-V}Wrr61D8i%fORpklskn1SVkYU;H#!h(LNGb}RjB{v)U!N0uttAqdb zuACC8LsuC_ZOO0(suyyfk}T4yHrsjEN`8A%_lWph=rMQGh4DL%sGm;btTybayqrTA zbl=j&gG%#>NT$ni_|q93iG|&UinuT~Uw9Oysb29+n}!sLG-Fb@ShpR^mw)1Bnk^@# zyLihpR9H16L)*!MPA9kHfn-z>#*=&QUV!2elj~oh1b!o$E8$Lp>P<1wKjd-NpcT>b zj}TYoT$@KrA0em#SxG0w08ZM0any!W-y+}r-Kdv8JO{8x)mHD| zjQF(EM)TDvpLm8db-BZUUc_<-69`XxMY@|<^hX#ZB!E%o8F-OXBB=2gV8Om|Sxp7q z9{qj)`|a93fbOo-9o;jw$-x9~*Su#BVUU@*kZAc>0AYfOUDkXUsHe)c7ZU^2&X8No z(ukD_S*4C-Jc|L7yQG`4B0**LOZA9OO=Kkk5<#eLzyMJFzqqwsZZ|DhR&y()_drWqq{BC_;-?goIG0j(A}KrPtar zl~nX3%ctXLSK4w6%a``?xdPk13qCWfTVGvvfN9@%H;{&*xzSnCNu7jVb%OVCtLl(X5L5{XT3GKJ}(A*V@QjUf-Xc!jHaR}7; zc$>6u`YXNljA0DGGBP{=1r6YL?B#~j0?oodS#=N{WZ0Nl|HIK)hDFtOQCLzMrBg(t zyCsK4X$1jE>4ujsVL-Y;T1pzEyM&=TB&54TU?>@6nDZUK-!p$W*KnTw?6uat43?X_ zOb)StQi1rJ<;RTbjhqpS315WuTfgdeZ{(#n`48cBHaB%1bwi(O(nsV31?keCT5=iw;K}>mw_~{6CE)!c!bUo=4*y7TnLrm8LGQKAS!t2_C!FCt80tpYH{pPYa7Wg)QQqE%1Bb1xfnD7&^P(-&4%rc* zBO1W)zs({v&39;*5D;I9F!}FKmh&fSlsYlLtv4YuSSk*F>wyofs!!WU$o;IF->y{uAaK&O z_2*H$;RfVZrJ{C9tG}|>QL2H>Xy88?6Yj1z?ub8X5`|R1b4%=O$izJr4EmHaJZ`GO zcsThEKFbqTlsk-+)6SBl|3{yYbiCplsx5oP*heV7jv-!8v)rd?ACvt1C z)!&{fG2F$?8n74Ccvji^?pOUpUt)T)e2P8}wbXuiPR||SIxDkyU>CmZ#AHHDx{ogX zNh^@S<71Y?;|d^iFbj(g>;$sjW?*9J*8ffcLN+^kQ&IY|wL36$uQ$#;EN*IZL!QLK z(1Vr$?N1<9Hm>DFbm~SEb#B^gdOw{2cQ@_g#I75DUqFgCd*TL|(da4be(O4Z0Z*m# z=xKcsH^4=OTH+&*Cj^ltAUznue^+3s)cGdy`OsIYZTSJng=rKF^A8osU}!*}?>$SJ z1Wa&uvHsZx_zws{^iO5#%bz9EYBf1Jpy|c52Us5ul=PdDmVC6ke)t@?{f{r4%Cmyi zC5Hq9+k{u`L1I5_5E-)WGHq7DCo*Sp10_9qWJ|nBTZY% zX$5kf9jsI?iwQ-H1-9Okho&`u=@_>SntM?sl^p!W*)EfZK5L~uU}w*@Eq%zopqNc&Dy^A)LE0? zjr2j+t+bsG)CeYc^|0=Lo7>=herD3y?tku`cj>!;@AZ8;z=SIuX7iY33>cH92_~cz z;dD3(pupgU8*fHGp{RNa&2dxT_c1-kvEXwYM+)v~*K~~c+ApaOnvAduXp~uLjd)j;QU$)e z&w7QiBsJABp$Ipee$RDVTi1miKNY<39uD;+vyg|pK=!WFx14^%!JWoKwr&JO%IJayouy-&$vAF@P&;8Yf489{DofKxinj z`R=$j80zrn-Olh?+rg{hWOHMtzGwqt{!oDGjWjT)x5MDl54CG^tj^=Ta0y$S2?3`t zx3BUjP?TFRpSY+AyyZsq-a={4YoPqqroaq6^aT1>pbPE*E3|}Pv@50+erVcWB}Gya zS7!Z+)O%9%rQux{{y$%u{dTqMqb)N=9#N#X@GE>yDwNIkaX;S$1KJpS7&C(l*0u&+ z7zmm!y?_eLZ^U}DId|Sg65a};uA={?XKDhsA=22W3lZewzZ;E5Kh@S(NmJNt?!H`| zcyQK6$#fQIkKv=v5B!D=NX>Vk6xhp+t(zcZf;by(2WV>iN%WMR%f`*QYfx9_)DAbruy8O~)33v4jkVOdqE)hYO z(6}js9cpmx4e=Z~`t63)#wZW5#>t}W1tM1~+O))l#B?sTc;3l6N-z8T{*?_{!XV2k z(NPqT0r06OC(XoxhktM;0D7#1yMIB>+Ooos?Y+>oMeId5bOiyhqt^DRMOkjSKgQp+ ze!Ex~-v}lZtJpLQ;dQ|JsC*M|6<1J_BrrHF;6u9AWv{w)M^y_cmK+7+DNo~tSrT!t zl!|W6Wrzg{Gv+AQ85}wzGu;u9D+cj-=4|RtO@WInhhh~#S_Pkax#gOTxQQiY3Rgvd zjfDVi=of_r{%FkUN&4wi;WZvI*STSkvos{E^r3}JiRsS^3~>s>9v$B~5+%r0=UK*I zKeezX(k>&Hk%>fJcVXzuJPS@k(`glbrZRl9Iwe6~;;O>5>8VgM_K=#R&(bejqC7;N z@GQ+-lbWD_a)aJpctuqagJCp1Vs(Zt|iN?Twq@15*;`dx%@q**>}9AxqXl!&DnOr6Oqkm zOu*=k&-Tjh^b(J?b651gt&>_T3!18qWk&;SyB8`bicmGCcl&-Wd)g-yPaU#*Bk}isn}v>W{MSLxo+j- zqrca8`XnBudZX#IbC9SH_*Y6w5pec=JZb;IKVxW*as_Y>A=rt(8S&-m;Smn(oVFLv z_vU_yhiAQwPg&K|2(ZrwG*q857SBGDacpL2#+lj|Z@j$k=$qb5`lgXa{3P^ftGg!K zuLN2nutkpa{#wp!#~ar&ODU#ERTL4>PyL{J7zGq(Ag@{Bh<@Of;qNVP_f44T{qkD` zkN~+glSYkiI8qsJG8D;u+y+IT&ZSsCO=oeqCf^<{JfRn=*-%Xn?8XWqiEDU2QD%fe zRjUvW##@dP6R*Z7N6@-y;dypuOK{>#%hQbH7V`rr!x=j8L)+CA<# zs#9jjgspQ3&(Bsb(sQpX&lvO`)|Uk$P*j6-w02Vuy5qU^L=>!E5dlkS1VU0(Ig=EkwItBfR!}sMt%?D zXzP>khnEpsP=e;K@*gH|YQ;P$!xvWv#t8vAr^&lR%yPnVcs4~D5{A(&4B~yxZ)wi? zkm(9)hIama1*=E_Kw0eoO@0%s-7$^rg?gZvFEcEHTo))anklYnsxR_D>xNBqT=;qZ z(+?)3DSpo-%4Nw;1V4%K{$A;-UVjy{e{-o`bS=6G6*stZ|E$#cqQmzKhHhD|+L~@?`=CLP?&i zaE9?5g*(<$H%oKLVQbcxT!xkEj0Rg7BXWi$WmfA^)7_;1u*3)9P%J6dcwy7ECw_$B zhJG`+?Sb*&0Ufk-!B+PofNPs|N?>FEpDwp^i-cYPC?|OK{JyVq*weL(`CXejw(_|z&2hvnslk@06-s=ILPY9 z_Emd+Y!HCcmA?8xJO6fKHPn^AU7Tp30t^LkmbZ=P0H=OrysG5f=EGx0kI7i4e~?t| zlkst-gZ(s$HVhWT2;30xnwm;gKJ;r7@2vhr%{|vE-g7lAvk?I!oxH@zU3N%s-Dnz1 z`X%YA_VNR{RC*S_n?t`4VR7jb+c)1f+TM4u!bMT#l&^g$k+A3E&)@$CuEKhuR)cBh zseal|bb|-f;wW|#r(CT)&?>|_`DT5CC-5Xsj1`gLgM}l{ZRFZ$N z-|hfww+vdbe>fUGmSN+tcjRFxqV7bZbY?;PFerJ;QJ6Oq=s>gJ?F~>fo1&ja9mu)!Fx?3yasQxGj zsZDY+F#mc&L@tL#;EkloR{YQ$Zy#%-bGew!^m%7M48s;D!#XR9TQ}VT zG(QWDKjW3`gJ4ROki1CHyMPT^$*i-NyZD9!GP8f94vvoci{N@ovR@KE!XPs5#OD2O zxmdCbMUkS%e+NeOVw8JdX8volHaOpedITF^l|i=*{Gy@XeGZ_|W!>OR!?_QSM6DEi zRt5peRTGHS@YST4TvaEmH0XPtT8WMyK;OWm^ndfxrRLSf^u{k0zB6i#jt-gH8ho-x!UW1ldXCXAA$+v{-uiFD9FVLE zPJOB1#r54AVXJYzVX?qgIf`n>7vCbP7#i@ z0OTX9cl3n$RaV1*c|XoIrzSHT=UsbX{FS!*MtMgsur@@IDhf1or>)xppi|o|4#_YG zqzxt@H}vFN%tV?#E7!E$Gou()eKI>13nlit3j|-xE(LCUi4oaW7xvVBzSVZ6bVom!ye@T z=^Kgox!Vjk-b~%)ScTNo^>NiEYMnhl+s!ZeA@~}vm&m`>6QcPFHCGgTmiy4Wu+Nir zMvVPBA&)F2m-9cS7W(uqCZ^`?ym<$y#KUWm_vs1}{?&Yx9~6apaE_SppJXJ5@5amv zOLdtG)~8biIF5Ev7<3 z&t8E4>mFv?)`q8B3rqgM)J$(b%!M7KDA z|LPYhN*x!K%8B!%8{k7}CfV+=^1>5Vzj2K$R&aRnyxx~-9TPWQcP=PIKNkKY8FXX$ zgrKjwgXf)@Y3CcheHCy|t$f7qj+M5xvtCr=O8*vHsnF+6|0@y$XdMQeFvyP7@!}Uu z-<4@to+EYrKpfo-8c#If=RQK2MpA-#d3Tgc{z`%Iq5U+mY*T6t^RE|M1jlq;+OSN)`Tc@u3&CFs? z(KD(4eCj$dwJd5aF-jte1~(_$#EYb#8A?00VMbNS*Lv@Lxr$AG^2w>^KZ*)8$ho`u z7h&)2HM9F;m4%;gAdH%0RpKV}A*=Bae#-r(L_i%efmnZ4ll)~gK#lD+uVXH6 zm88di#ARX3u*~u6L+tN3324S78p0<_K7gN$H8bt|*4KzImr>qGu>;3FE932$~ zvB^$)E{u9?{9UO5>wRM_rD_WLHaMDLAuGfThc3a3gjG;xu71lMfQ9sSH6Z;F1V}No zVnOjgf+BeG%YG<0L|q@dU3NZ&4aB6da-vc=BrH@PpgVn*Znh4icMS#UMnf}@^kPjU zmUrLB4!X4Hk=IJIx%10qAi%3B;wjvSlqDOhBf+s}I6(>ak&Z$`?~ z73QfOpW9CORFi@;_m5s!RoiEruw*)df#te1_{cpCFarwBMRznskN1J)C{e8WoUJXk z;&0Yi>+2o0YVv(FI;%&pZr~#DjkRnWvhq}nK?1IrTPP?$hidoWacOdF|OgU>mz z2Xnn|1qvj4p7 zi%P9HQ0VzP^qPUJ_zODytm?*%{)@&!K_i8hbEP_UJ`u~}4HZS)Kv|(*&p*~L__C4` zV3HCMbD60>`mLMfms9Ud7SG zVoHgXNTpJxFoBr$*q91CE1n_^RBwz_Yj)J9C%GjQmONF)b$%SYRYE%y#Q7sU?`deE ztZb@SC|iQAH}b?f{YJoFtymYoS0HvP0yUDZUo=N;z+7^Zwp0?6*v?Nz$4uHtxZtO$ z%kt(9bgecx{Y8aC{bIF-Lz?#vz(hUTHB}hYP2exA= z@4)13&3Qa=tKb<8EO7HO5!h;Wd7xnBX0X?OMHK zJQ3-(iZd>ihhNgZ4RENPyp&mx=xpQyUZ>%iR8Cly?S3E0G0l>17obNH^KLJsbmJpo zWf}wi>X19w87(r8?6Jv4V*8!n^|BRb*?)`GD^+k_oT6vx(=EH=%E2TLD>PPf1g`dG zBUel#Pu&y?Sno)G`T}Jy=iQlNi@o)I*q7g&j{{apY*0*_Q4udPd zvaAq6Y?^;9@u9gDs}dy^uV{~0GjKv5#Ymz?_|V>|b-%e~nz^Y4{V zoTMCMPZk9?j|NGKjsv)-2+PhW*!ls24VlG`J@N%cD#e_@Chd9RP^iZ;_74_o-u z+b0E&%r_g1Y`6_-g%vPnJsXPG?ZuN}9%qBG4lmpvcUU*hJXMQaP%mJfh(SZ?QWnx! z>0UilUe^nyaIq~@Vo$l6 z{xtAsrl*_)u&WLF5c9TczA*x$tVf%j3@#s~z?sS~_?Y(XNpxJIn zOWFi&It!NQBXpl zM=Ky0$dBDu7%aPv3)guui*$)PpOMYbbwY4)kA5>A-fPbO*ne8mXEtW&dZr$qzPmU8 zko(`Y3$r#iogrPm?pBRPfbVoKh+V(!Zb+m62kVWv39C{ZYR#3S2gpP0(v_@KK}2Tppu_5;)ExnTg(C znQp==cKnLr3EyUmH4H2-U>yBEazHr9pPBVzR8vEi9!MkiW1)kaGgiCokIK58h^Iy! zKO#1+fPQM=ORY}ox@{;VqcP8%NF2~3Rg~9k!VuFm$UG2w?cuRiYqj@~1B488iB>HB zI4l_!TN@cP#ZO?ACxq>@BU=J#ffV6!Vfj%tvy)ID`g3<#0)zsUA2>n{%&CTrkQ)*J z+k1y3L3dD0)rd(zcnu2$#v&>rt_4uq(2GsbfIDRyU%Y?T5B{<8E8);&P@XtvziD2d z(BPBuVEDWC3VNyxQR!b7-VS9p@y2402wqK#Ii|B(W=uP)J)BH^mHnKM&&QXthNZ)X zA=8LKAjJ8!OtiUm{BsP3;wSlo^%p#~Eki2v{zSkpk3H4zy&h{_eJ?NJ(c_g^)h-G>ycMF zES!@9d8l3RPL~G|bRBfSFwSdS5@J9J;h0j|p1{L3ik#?haRC7d?$f}cx()^0##^@X z-j2&+dt?W4q72d9ln6Fp{nbzY=xnoE-uE z^q=0Pk^!8I00mM9qn}oiV4cVdbS%7f19$dSRZ3uZ!DuJ>h!5&|Hwy}-> zP^3i`S3}~lWf~~_;L)E%53J%5-L?!y3+T4}*aLFr^TZW

bcthUmX8=hW>ggIHy; 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 z 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 euRp)K^8Wxdi0AktF%Jj;0000 { +export const useSelectedWalletHash = (): QueryResponse => { + const [loading, setLoading] = useState(false); const [selectedWalletHash, setSelectedWalletHash] = useState(''); const getSelectedWallet = async () => { try { + setLoading(true); const _selectedWalletHash = (await Cache.getItem( CacheKey.SelectedWallet )) as string; @@ -26,12 +27,18 @@ export const useSelectedWalletHash = (): string => { setSelectedWalletHash(_selectedWalletHash); } catch (error) { // TODO + } finally { + setLoading(false); } }; - useFocusEffect(() => { + useEffect(() => { getSelectedWallet(); - }); + }, []); - return selectedWalletHash || ''; + return { + data: selectedWalletHash, + loading, + error: null + }; }; diff --git a/src/hooks/useMainAccount.ts b/src/hooks/useMainAccount.ts index 464cf049e..1bd124e23 100644 --- a/src/hooks/useMainAccount.ts +++ b/src/hooks/useMainAccount.ts @@ -2,7 +2,7 @@ import { useSelectedWalletHash } from './cache'; import { useCryptoAccountFromHash } from './query'; export const useMainAccount = () => { - const hash = useSelectedWalletHash(); + const { data: hash } = useSelectedWalletHash(); const { data: account, loading: accountLoading } = useCryptoAccountFromHash(hash); return { account, accountLoading }; diff --git a/src/localization/locales/English.json b/src/localization/locales/English.json index 86fee5742..82707315b 100644 --- a/src/localization/locales/English.json +++ b/src/localization/locales/English.json @@ -154,7 +154,7 @@ "buttons": { "explorer": "View on explorer", "share": "Share", - "confirm":"Ok, got it" + "confirm": "Ok, got it" } }, @@ -206,5 +206,7 @@ "recovery.phrase.modal.text": "Your recovery phrase is a set of 12 random words, and it's the only way to get into your wallet if you lose your device.", "backup.complete": "Nice move! Backup complete.", "backup.complete.text": "You backed up your wallet. Now let’s setup your wallet’s security.", - "setup.security.btn": "Setup security" + "setup.security.btn": "Setup security", + "airdao.wallet": "AirDAO Wallet", + "no.wallet.description": "Tap into endless possibiliies with AirDAO Wallet." } diff --git a/src/localization/locales/Turkish.json b/src/localization/locales/Turkish.json index 01493cf93..7f54dd2bd 100644 --- a/src/localization/locales/Turkish.json +++ b/src/localization/locales/Turkish.json @@ -153,7 +153,7 @@ "buttons": { "explorer": "", "share": "", - "confirm":"" + "confirm": "" } }, @@ -203,5 +203,7 @@ "recovery.phrase.modal.text": "Kurtarma cümleniz, 12 rastgele kelimenin bir kümesidir ve cihazınızı kaybederseniz cüzdanınıza girmenin tek yoludur.", "backup.complete": "Harika! Yedekleme tamamlandı.", "backup.complete.text": "Cüzdanınızı yedeklediniz. Şimdi cüzdanınızın güvenliğini kurmaya geçelim.", - "setup.security.btn": "Güvenlik kurulumu" + "setup.security.btn": "Güvenlik kurulumu", + "airdao.wallet": "AirDAO Cüzdan", + "no.wallet.description": "AirDAO Cüzdan ile sınırsız fırsatlara dal." } diff --git a/src/navigation/stacks/Tabs/HomeStack.tsx b/src/navigation/stacks/Tabs/HomeStack.tsx index ca3b5195a..417897b3e 100644 --- a/src/navigation/stacks/Tabs/HomeStack.tsx +++ b/src/navigation/stacks/Tabs/HomeStack.tsx @@ -7,12 +7,13 @@ import { Notifications } from '@screens/Notifications'; import { getCommonStack } from '../CommonStack'; import { AssetScreen } from '@screens/Asset'; import { SendFunds } from '@screens/SendFunds'; +import { NoWalletScreen } from '@screens/NoWallet'; const Stack = createNativeStackNavigator(); export const HomeStack = () => { return ( @@ -20,6 +21,7 @@ export const HomeStack = () => { + {getCommonStack(Stack as any)} ); diff --git a/src/screens/NoWallet/components/Loading.tsx b/src/screens/NoWallet/components/Loading.tsx new file mode 100644 index 000000000..7a2d21bd6 --- /dev/null +++ b/src/screens/NoWallet/components/Loading.tsx @@ -0,0 +1,62 @@ +import React from 'react'; +import { Image, StyleSheet, View } from 'react-native'; +import { Spacer, Spinner, Text } from '@components/base'; +import { COLORS } from '@constants/colors'; +import { scale, verticalScale } from '@utils/scaling'; +import { useTranslation } from 'react-i18next'; + +export const NoWalletLoading = () => { + const { t } = useTranslation(); + return ( + + + + + {t('airdao.wallet')} + + + + {t('no.wallet.description')} + + + + + + ); +}; + +const styles = StyleSheet.create({ + container: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + paddingHorizontal: scale(74) + }, + logo: { + justifyContent: 'center', + alignItems: 'center', + backgroundColor: COLORS.blue500, + borderRadius: verticalScale(36), + height: verticalScale(132), + width: verticalScale(132) + }, + spinner: { + position: 'absolute', + alignSelf: 'center', + bottom: verticalScale(58) + } +}); diff --git a/src/screens/NoWallet/components/index.ts b/src/screens/NoWallet/components/index.ts new file mode 100644 index 000000000..bc5115b2f --- /dev/null +++ b/src/screens/NoWallet/components/index.ts @@ -0,0 +1 @@ +export * from './Loading'; diff --git a/src/screens/SendFunds/index.tsx b/src/screens/SendFunds/index.tsx index 2a01cdf4b..b8be8a264 100644 --- a/src/screens/SendFunds/index.tsx +++ b/src/screens/SendFunds/index.tsx @@ -39,7 +39,7 @@ import { styles } from './styles'; export const SendFunds = () => { const token = AirDAODictTypes.Code.AMB; // TODO use in future to connect different assets/tokens - const walletHash = useSelectedWalletHash(); + const { data: walletHash } = useSelectedWalletHash(); const { account, accountLoading } = useMainAccount(); const { data: ambPriceInfo } = useAMBPrice(); // TODO create a wrapper useTokenPrice hook and pass token name inside to handle different crypto tokens under Ambrosus Network const ambPrice = ambPriceInfo?.priceUSD || 0; diff --git a/src/screens/Wallets/WalletsNew.tsx b/src/screens/Wallets/WalletsNew.tsx index febba3f2a..82d50203c 100644 --- a/src/screens/Wallets/WalletsNew.tsx +++ b/src/screens/Wallets/WalletsNew.tsx @@ -15,7 +15,7 @@ import { AccountActions, HomeHeader } from './components'; import { styles } from './styles'; export const HomeScreen = () => { - const selectedWalletHash = useSelectedWalletHash(); + const { data: selectedWalletHash } = useSelectedWalletHash(); const { data: account, loading: accountLoading } = useCryptoAccountFromHash(selectedWalletHash); const usdPrice = useUSDPrice(account?.ambBalance || 0); diff --git a/tsconfig.json b/tsconfig.json index 065ef34b4..6c3916eb7 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -15,6 +15,7 @@ "@*": ["./src/*"], "@api/*": ["./src/api/*"], "@appTypes/*": ["./src/appTypes/*"], + "@assets/*": ["./src/assets/*"], "@components/*": ["./src/components/*"], "@constants/*": ["./src/constants/*"], "@contexts/*": ["./src/contexts/*"], From 740da2e0706b1edddced83b3d7c3b1c6bd4febb3 Mon Sep 17 00:00:00 2001 From: illiaa Date: Mon, 4 Sep 2023 18:10:15 +0300 Subject: [PATCH 118/509] added token logos --- .../icons/{Asset.tsx => AirDAOTokenLogo.tsx} | 2 +- src/components/svg/icons/BUSD.tsx | 42 ++++++++++++++++ src/components/svg/icons/Ethereum.tsx | 48 +++++++++++++++++++ src/components/svg/icons/Tether.tsx | 31 ++++++++++++ src/components/svg/icons/USDCoin.tsx | 46 ++++++++++++++++++ src/components/svg/icons/index.ts | 5 ++ .../templates/TransactionModal/index.tsx | 4 +- .../WalletAssets/SingleAsset/index.tsx | 36 ++++++++++++-- .../SingleTransaction/index.tsx | 4 +- 9 files changed, 209 insertions(+), 9 deletions(-) rename src/components/svg/icons/{Asset.tsx => AirDAOTokenLogo.tsx} (96%) create mode 100644 src/components/svg/icons/BUSD.tsx create mode 100644 src/components/svg/icons/Ethereum.tsx create mode 100644 src/components/svg/icons/Tether.tsx create mode 100644 src/components/svg/icons/USDCoin.tsx diff --git a/src/components/svg/icons/Asset.tsx b/src/components/svg/icons/AirDAOTokenLogo.tsx similarity index 96% rename from src/components/svg/icons/Asset.tsx rename to src/components/svg/icons/AirDAOTokenLogo.tsx index ac5036503..a48401838 100644 --- a/src/components/svg/icons/Asset.tsx +++ b/src/components/svg/icons/AirDAOTokenLogo.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { IconProps } from '@components/svg/icons/Icon.types'; import { Path, Svg } from 'react-native-svg'; -export function AssetLogo(props: IconProps) { +export function AirDAOTokenLogo(props: IconProps) { const { scale = 1 } = props; const width = 41; const height = 40; diff --git a/src/components/svg/icons/BUSD.tsx b/src/components/svg/icons/BUSD.tsx new file mode 100644 index 000000000..b35a7a475 --- /dev/null +++ b/src/components/svg/icons/BUSD.tsx @@ -0,0 +1,42 @@ +import React from 'react'; +import Svg, { Defs, Image, Pattern, Rect, Use } from 'react-native-svg'; +import { IconProps } from './Icon.types'; + +export function BUSDLogo(props: IconProps) { + const { scale = 1 } = props; + const width = 33; + const height = 33; + return ( + + + + + + + + + + ); +} diff --git a/src/components/svg/icons/Ethereum.tsx b/src/components/svg/icons/Ethereum.tsx new file mode 100644 index 000000000..c9707454f --- /dev/null +++ b/src/components/svg/icons/Ethereum.tsx @@ -0,0 +1,48 @@ +import React from 'react'; +import Svg, { Path } from 'react-native-svg'; +import { IconProps } from './Icon.types'; +import { COLORS } from '@constants/colors'; + +export function EthereumLogo(props: IconProps) { + const { scale = 1, color = '#328332' } = props; + const width = 33; + const height = 33; + const color2 = COLORS.white; + return ( + + + + + + + + ); +} diff --git a/src/components/svg/icons/Tether.tsx b/src/components/svg/icons/Tether.tsx new file mode 100644 index 000000000..fbb1b4ef4 --- /dev/null +++ b/src/components/svg/icons/Tether.tsx @@ -0,0 +1,31 @@ +import React from 'react'; +import Svg, { Path } from 'react-native-svg'; +import { IconProps } from './Icon.types'; +import { COLORS } from '@constants/colors'; + +export function TetherLogo(props: IconProps) { + const { scale = 1, color = '#26A17B' } = props; + const width = 33; + const height = 33; + const color2 = COLORS.white; + return ( + + + + + ); +} diff --git a/src/components/svg/icons/USDCoin.tsx b/src/components/svg/icons/USDCoin.tsx new file mode 100644 index 000000000..65e5d8271 --- /dev/null +++ b/src/components/svg/icons/USDCoin.tsx @@ -0,0 +1,46 @@ +import React from 'react'; +import Svg, { + Defs, + G, + LinearGradient, + Path, + Stop, + SvgUri +} from 'react-native-svg'; +import { IconProps } from './Icon.types'; +import { COLORS } from '@constants/colors'; + +export function USDCoinLogo(props: IconProps) { + const { scale = 1, color = '#2775C9' } = props; + const width = 39; + const height = 38; + const color2 = COLORS.white; + return ( + + + + + + + + + ); +} diff --git a/src/components/svg/icons/index.ts b/src/components/svg/icons/index.ts index d72c41594..562de7847 100644 --- a/src/components/svg/icons/index.ts +++ b/src/components/svg/icons/index.ts @@ -58,3 +58,8 @@ export * from './Twitter'; export * from './Watchlist'; export * from './Success'; export * from './Mnemoic'; +export * from './AirDAOTokenLogo'; +export * from './BUSD'; +export * from './USDCoin'; +export * from './Tether'; +export * from './Ethereum'; diff --git a/src/components/templates/TransactionModal/index.tsx b/src/components/templates/TransactionModal/index.tsx index b9e36e8e5..c5adeb231 100644 --- a/src/components/templates/TransactionModal/index.tsx +++ b/src/components/templates/TransactionModal/index.tsx @@ -9,7 +9,7 @@ import moment from 'moment/moment'; import { StringUtils } from '@utils/string'; import { NumberUtils } from '@utils/number'; import { useUSDPrice } from '@hooks'; -import { AssetLogo } from '@components/svg/icons/Asset'; +import { AirDAOTokenLogo } from '@components/svg/icons/AirDAOTokenLogo'; import { styles } from '@components/templates/TransactionModal/styles'; import { useTranslation } from 'react-i18next'; @@ -188,7 +188,7 @@ export const TransactionModal = ({ - + { const usdPrice = useUSDPrice(balance.ether); const { data: ambTokenData } = useAMBPrice(); + let logoComponent; + switch (name) { + case 'AirDAO': + logoComponent = ; + break; + case 'Ethereum': + logoComponent = ; + break; + case 'BUSD': + logoComponent = ; + break; + case 'USDCoin': + logoComponent = ; + break; + case 'Tether': + logoComponent = ; + break; + default: + logoComponent = ; + break; + } + return ( - - + + {logoComponent} @@ -59,7 +87,7 @@ export const SingleAsset = (props: SingleAssetProps): JSX.Element => { {name === 'AMB' ? ( ) : ( - '%0.00' + '' )} diff --git a/src/components/templates/WalletTransactionsAndAssets/WalletTransactions/SingleTransaction/index.tsx b/src/components/templates/WalletTransactionsAndAssets/WalletTransactions/SingleTransaction/index.tsx index 59590f941..36f754e1f 100644 --- a/src/components/templates/WalletTransactionsAndAssets/WalletTransactions/SingleTransaction/index.tsx +++ b/src/components/templates/WalletTransactionsAndAssets/WalletTransactions/SingleTransaction/index.tsx @@ -2,7 +2,7 @@ import React, { useRef } from 'react'; import { View } from 'react-native'; import { Button, Row, Spacer, Text } from '@components/base'; import { Transaction } from '@models'; -import { AssetLogo } from '@components/svg/icons/Asset'; +import { AirDAOTokenLogo } from '@components/svg/icons/AirDAOTokenLogo'; import { styles } from '@components/templates/WalletTransactionsAndAssets/WalletAssets/SingleAsset/styles'; import { scale } from '@utils/scaling'; import { COLORS } from '@constants/colors'; @@ -37,7 +37,7 @@ export const SingleTransaction = ( - - - {t('make.sure.to.write.down')} - - - - + + + + + - - - - - - - {t('checkbox.text')} - - - - + + {t('continue.btn')} + + + + - - + ); }; diff --git a/src/screens/Wallet/CreateWallet/styles.ts b/src/screens/Wallet/CreateWallet/styles.ts index f4c21ed8d..737773c67 100644 --- a/src/screens/Wallet/CreateWallet/styles.ts +++ b/src/screens/Wallet/CreateWallet/styles.ts @@ -3,12 +3,10 @@ import { scale, verticalScale } from '@utils/scaling'; import { COLORS } from '@constants/colors'; export const styles = StyleSheet.create({ - header: { - shadowColor: 'transparent' - }, container: { flex: 1, - paddingHorizontal: scale(18), + width: '90%', + alignSelf: 'center', alignItems: 'center', justifyContent: 'space-between' }, From 63354204638477ac24d2c214817f2ec118a35a74 Mon Sep 17 00:00:00 2001 From: JavidHaji-zada Date: Tue, 5 Sep 2023 14:53:37 +0400 Subject: [PATCH 121/509] NoWallet Page: added illustrations & navigations --- src/assets/images/address-tracking.png | Bin 0 -> 14997 bytes src/assets/images/send-receive.png | Bin 41606 -> 13765 bytes src/assets/images/stay-informed.png | Bin 0 -> 6382 bytes src/localization/locales/English.json | 8 +- src/localization/locales/Turkish.json | 8 +- src/navigation/stacks/Tabs/HomeStack.tsx | 3 +- src/screens/NoWallet/index.tsx | 92 +++++++++++++---------- src/screens/NoWallet/styles.ts | 9 ++- 8 files changed, 74 insertions(+), 46 deletions(-) create mode 100644 src/assets/images/address-tracking.png create mode 100644 src/assets/images/stay-informed.png diff --git a/src/assets/images/address-tracking.png b/src/assets/images/address-tracking.png new file mode 100644 index 0000000000000000000000000000000000000000..be69a2ca9308e2d782e18f5ff9dcae44a2dcb482 GIT binary patch literal 14997 zcmd6OWmg#iXOfT^0KID$SP*h(o&!NAlepudUrvU=ETG{#I{ryzUtKTi13KbZQVbML#{=uQD{Lpfd4a>!eK#xyiC`tG|#tKhxxHT zZ_Om@q6dPX%1vwZ>FW-T$FlTkK@4b!*&gFOa^oYpBa@QLnfVqH^n`giL;!g@wQU8t zfCD)j%T_kcr`?){;fjON@C4JNA0V7rZ6n%3WnPAQ>A^sQRbp>0aGM)Jurvy-aTr#1 z10wkBxCdJvgT|o%e(*z^FQ5$`F}q>Bo--rA%{kIv9b8ZdKSUy%o%moL8Q$Oblf30D1guF^i!=53Ol} zREyr!PQOkaxA>L2R`n^LX2Fnbaguq8cb|f^1l$bItUfB+<8mH6;OD8l?wQ2&f~noZ z>z4K#U&UG4CkolX_)3e8%fDL!?oS`?j!tqM?z}rIa~7sn*aC(r4#tsezxk=iTakn^ zDL>wg*;F${i98@`?G^JGsL~Wg5`rB6zESr_efVupw8Ls<|_pOCF-4%4L74;);548&~S zfos2$O=+5X8%S?K@j<9EMY>~i0Smo>|czV8SSAX**YUeA8>W}l_pPfk;C*|TuDt0{!QosS|$mNv@Gs`NB z0B7*TlsxJW@M!3YYLf`Gwo{R@2JH7n0jpm|TTX~{P}eWO{v0e_`;R=Hw#e3&O}j!! z-B;86MriF+Mhj+Kq)vPkT0|wsL1BHl)b`ZBpHec)M1*Z^L*Gv6KpZc#o!+o$qIUAg zw4kc|YTFkP&dj)E&toBbVMvRBxdM$s_rvizLvQGQn zTSEy>i>}=+X_Gm`)xmmnPY53*YXE+ZLBSa3n!ot|^4{Pto#s@RRrdI|dxiQl?-?gw z+-%K15Xe&9@l6a z7V24+S`~!{RHuG;pKZt83`G!&RSz8*DyOnxU`+Zw4Y6h0di@NBc*kJf9bs-vv9{D- zcY8nkcr=0azOn~jJ*KK+jIqYz%L=(sJc zkx!*7Ir->UEwB{%exc2qfbTK2B3&Ue$l|LV@9MTOm;6r<5i(=-d2Y zLRm~s5(r4X;5o?1<3};Pvj3-r{Utcc-;o`{^yiQ9U>9$eG>|D~M1xq4a3unpiqC$j$_o>Ya`go3TVUdB~uj99Y zl>)`0`QWxDd{<1Fz#Z2h^%Mb9?fE_7b1O%VfJ5#(9}Hx|Mtp#92rXG%BoH_sVcSQb zn)7p>;F!(t{n$|1O)N(7F7kR zG7fk9;Vybk8HaHtfRK@QuuRo+Lz!YOI&Sv6Ibn&6O6qI24Kg*pUjE(mE5)xuaT_HL zq^epIW!rb`e3d+NY~0BYTVpLv$5|*la20db`n9*(3>fT880}cZ&yD*pd8s`bX@H&ek5ZK>8YC}$4wXy2 zwbQwkK*jf1Uf3#XdxD)9+i`YJh+&49hqYSuQgZH_m^5t@$_|qTCUhAA?OILtGIebm zklQ*T6Sgwla>g#MSEzVjwWhql*Rtp_IZ^PLmKqn-X>?n<% z%R;$!vYU~2&|e;D@_zJ8SlP_c*_+F}>?7-`OqV@}V5(u3sl$czgRj5aO{jeceW!kt zrv3aXuB3-HB{*S-mn$*3vI72G?>N>VjPCal{c*NP{x$FT+Y&_9vq2^)i(V>H7}LL9Q>jko`m+C5gLOeh+mnb(*55E7n|6tQ`B+rxPm_+a6fZ{=EH8^ZxwOM?b|vM*MiS zX(Tn7S-X%MM@4*$aWuya3PoJ0Go#1xCYb`}vue8h^Z3q!IpS}dDW=*EDSG0|s{HCW zKx&-mIhG1BDxy}Iu%Ruahjv2rf$80Kg5l;pPvsx-thOgN+jgAUVYTgNoH%vM=XUc# zX)=2E(9qPfkw@Q^W8WYj`g-mMt|t>6YfnoY0QkFIav%&5rD_%)?{FI;A{SY~9pd^w zvXggrg}iOfo7yeGBg4aOcZ^@*QhQn>xL%`Stj@%3KC@F?F&|0-o+E4`gzrL&HBeX5 zz*um^s`91)dCVB z5p4gZp@*jV4({SbWD>~?!NDW;xFAI~C-p2*@}gl_DR)waZ?XXZ%q5mae!E&bnMvCC ziq2soiuapI5psJU?XqR?XLS5SvjxaY(;SwljQR~Hc|L8a>x0{i;6gU9d9JNj266PB za(iqE2spFE&tm-5<;$lBmteOTwYb-^atfuj4omp71QqgBv15B}39|StiL>()y+*#2 zj50|2Kn&%^VZ}Z;+oe0xMYq7u8y&`8F@NI%6~YNWU|gNz@>ID_%iPmU;08j#w5K13 zMDo%6c$QDuDW`jFF(l5N(w8WK=2@>5Y2pZ5l!9Jtf4w!=fp4#lPe#17V&QN(+#&f{ z`H9R9Kg8jUqt75>;_*n`{UvztodXLKgxA5=4( z!@_aUFQB*OrH}YO!?k#|9OQn^XBLyO#lp5V&si4*0H;v&^a#UkEa3B^JEF0SL^QtQ zBByz%S>0(kLmLBuqhd&vN>iXm<*zdYM7Mh}Dg{(!4Z9J^+A!@>5_cavHi`@&BR14t zi^)Fd^n80fP(`py#4ZV(b`ImQCGq-#2E`oh1DEY;83j0g5s4{ut<@gfFeQH=CJr=v z%$N&89&sH-rU&2w;c@`;r8@}Biho;gt`^>I^B`i3zBG<8mGQe~YoaT6$nA@n6jAwD zTC%iJl0a1^Lq>R`_H&!}r&Rg~M(jeD+6CfUR$Wu$9~5~!-KqZrSeI<)GV`o6;y-10 zjvin_WT={>GF-d#@Eplq?6V+OeVxEfn*{|U)kmGuqmjh9?`ZMyOe;+n3w)r@xgZYY zMDH^bDqv>7WxinPzpiUcZ2F8y_3{uA!GK+u_nS)g3$ba31)N0nZWc(vszx|H{Uw z`>G7QgRg$y(|-4%6Lp_?)kU`9OIe_g~lR~75)uKuN&Vx=#EHll@r7p-_8UKZF$m+$^O+Q%IzsZQLQ`L zmN38GF`ZORM-sYB#t91%Wy>4K6T$ym9=gO&@%KJBjwsj(28eg%+S zNK<#g-Fwq*{~*LLX$uFFGQa{_*ANv$ImQCBBti?2SBLXUe|a)-8)b1VDbLOt(fTvt zV_f&i=~tEiwuzd6-ccPtD|TJP#|zFqW~{9VF6C(~zx+F|_IUFITPjxy7E}cgENy8I zI$ug3ubBP*5&!SR5)TE`K3?kX*^DF-DcXgjx!dN|`Ya(%%wzOAfXLR0o6sv9H9@;} zpt!xR5aTFGDX|a5;iE6<4CkPH&4w=beB5LZ3gZj%q-1c!(H1%)CTh;5m5%CUav_Dp zyO^HU^X#6DFSu52f0D9&KRnYpwL(q*>js!u)bXvaGnv=}mVhJZv&G&x=?V+x-&<}; z0CWA`c2e_u>sr-yOx^kO@hqaV-xp2%d|=n%LG9*@LW=GTVYhZ9R!@x2n{{Zp&_afO zCx@NS8R9}=MmeXq4P#NE#&&3@{F3?QD1R^j;%Z4IUKkk+=occ+ z&}|yn)50~c+P$~a)Jy31TEUwQnaiNQ{?}0EJZ5C>2ewH`vb?R*+mCQ-3!Q^>ijnOC z$0u0#@s??PuJ7Ojr{t8UHcorj#Tk1kqiJH*#qzF)gKWFkoqjL%^>4`wBugW2rmcM| zOe}PK+&-~|Q?ES^bNJgZUeUu~%`e7;ua%Gsf!k2QXk%Bn;ol#K5Z>mx8}<2L{@J|C z3`br2g%h#MIZ%k(uJA&-$W=XE3tHOuQ*(1vbr3eAMUXsO?kBw0=l=F9&eH$4r!K!? z?4o+^8v0YZp2;6~)EbHn;4++fmzL`_pBqwJg?&TLizlVNX$TMeFo)A8Wf$-e=-j$% zBJ?=vDxnpMk21Fz!=qM5wF$GgI+Mwu;^2iPGq{C*m!(*|A%R5xb+;)c@8{q{pd-4a zh~C>^@Y)&m8kf9M!|(EXY!P^&`_8f3dcj)yWpd9+lOM?q3N7iSR1qOSZdS~p4_!Xx z@2%U*jd;IcJN3*kZIto9%|*1ca*CJElF0du^Z_B}Z_PnF#W&K+3uD+)a)AUvY{E#9 z_H-Zo)m`9eAW|g0<#1BC*Mf{n6%9nL7zH1|M5|s1Um=J z2n=l>SXP~^uGw(oZO4yzS1U(V>^D2>pP~QT><;9+rR8YXu^Q}L$QFuEGS&^ARw&FM z2XOn1mq(BI_56;0{Sh9)RD^sZv&|hSb!!sM-z16Qs9_m8m&_&gS$5;EIC*hxT!{-^ z(d)q0g?%ditNk-JXUAeqTDpLdal+0sEJ-JhvV7T zy&f*i-`j9e5MM2765}nA<^5Lq%T|CwW-^B1J?P_Z(OrNqQ&hd$SQJ6HC=U|Ku(6<5=jUUZTN67bk40}yF z{TeIu>xbSYf|{H}r}TE3i7{Ys-fY0wGctls6LMA88$kv{#_!-M>coxvt+V%aJF@Dw z`zpIh?;tDwZ!MmR8k<-23iKI~J;<8Ho%$>GT6hG$gb$@UO;P^zBYfd=Ct+jtXWwhT zqeX0vWJotux1S4}j#U0BSpeRXom{yJc^ppnP2GMUk$zBjRwLh3fD72nbS}r>?RJ0O zxXPH?WIPMe0uVNFAHh{txE7)kZm#swlE$#UHjQ3;5Sy;6jsZzDlauLiljK%)%HgRI zh*YR7o<;mZ0{`8$7iCJ0WTN`%jnnS6>}@15!OFZHmE<^md@}EaH1#)!eR+5hA@rWE zL|m+hYG*`Ef|T{x>aDXI6F>fEbDy1pL4xd`NF>+k5`eoIOw?)pWL~}o)%J*#a9C2^ zZ%-$$_R+qd^h(~BEfd%ygGmOi)I%?#JOv(Ee!AKihPiJzw>A9zt@b&Iohe|ulvh%m4=uY<|0G{%_$40PiubGyP&;m z#Ck82Jl#1jh!pE0&v`yOAwtXWk~7+-CQC*K6MPc$UOZVa85JaJ`?WZ|@Zku~82_On zp~9T&uSsB7y^h6)<ZnT!w8^Z{OHzCv9(dNN|^sP z!9<<%Udm)ISd3MoqCO>|b4nd}<6w_8a9X{9Gg%UBuVk!=Q=3@I;hHVt8;v!uHp{iR zD%h`Aiyoi?K97sMg;xXSOqWyQ;TrN&5&5><7&JTeb2e}KvC4K%NiHK4 z=q4a);c!@e6S!H5ZMoVi0P*qk6;hR>Mn&l%c41?QN1nld0`&FxpFaSIz$0r zNk!C1Z=*^r|I7A2FGbtg_76pJMHz#c*U#Og!oiZ5oFq61BPPtUFT(2ROU&!{6R5gfKG zygHUIfHFYY7oS2v94%r;9AJBTkG#bw=tqLw5d2xuu_RYaiULe^+Nb4k`=-A|u4j8+ z3u>^yeK0Her9MjYa7DL8Y`VwqrAj95`SA%*M^)d1xT!<5dJOnd-Ou!aRvm>Lk-Y73 zcJHUxR6_j^>x2Q|(yBqKn1qYUns5@E1;q+UL{zM`91~^H+$L=u?criX1%809L_vJZ z!d0;GmoC|AI>HZvj)Hh>h3A-k+&x3f0?zTav}{-o2QmX*Gpe2XTOMl)vrWi0)R`Wz zdBX>hH;QZ{VE2JXU@ZA>>PjNzRLkWW3Z&VTB1%U=qbQkBo%={-mPMJ*3+abeV*EN8 z@tgHEafan0%(7>W8{abFc%J|{p6!m57N_1Dp1P`t+(sPEtSp(g;?9VyH!Ui$rRW}kt40r_ z|EP(~h%C9yU0JU=L$n?+98+0Y{3G1oW$iS=B+P%~aRZTxTx(^Xu@09I1I4-owSYe6DU4WfjFB_pQw9 zi&E9~;0yG6Url?iE?8uNohNa*s@dy#d)+%j&k**{mf(3){p25c7B#oc6x(XOV0Bd| z(L%bidJ|q;e=qgOF`PY*fycGGa9`E270B_Y4w=&onv@&`uiaAo1v-dw%2ArrWslg% zE5*d==e5@Q<+~G{+i($3szZfwj81(4o8YHPa2&TCJc2Z6UWTvbM+0|3N!aI9! zqcI|CwI5o8C353y0IO5ioL~SwFoJ$1Z<5l~L{JCCA(;*-&8eJ%-Q}3LdLudVkHGE( zF?FgC;d4%xo0sM{v_Q!B7E7I|70P9|VX?X$f#zk?;n`;~3fe>BC8`hKD8!R{=;ID< z?0l8nRColojtI)Kd+4q4S?plqdT1IH!mJz~+Z+`UY2U-JH$D;v9r8=OB!Af6dw*;7 z59n=HJM@I=pamiha@*LQ6@IfdbPt-*X6l~VWR*M9?)+kB>ep_9dL#`~Q#>!5p^!Egp? zpSwN5Nsp2PkhqhKbc%ducF#NQvFTQD3+Eu^kFl(3*Uxx9l=^P1Eo}rY_=e{#0v+21 zT1gVQ@w?`)ZbXK!B25#Vb=HYViJ+FXQ)GWSi_4q-X0bEQLL16R5t%o5>hcbOHQ9P~ zKK7`bnvoNsnZ?K4d&+S82=iG|82i6n?o~K|T}fw4$2Z|lY{}`1 zavQRYxcmuwnOgJw6OofCqR1;#!lILkdxm?W@R>ebSkK$pDZCrbpG2g& zUB8>3bdxr{HYeFEo;i^2HLP`5TMbICUvICH;|3-0TZPsTC?Hh;w?!)R#mWu<{S2U< zMhJ*|_L=CmI5}I%(oezg8ce5)v)xiPLp_R?%`IW<1~S)hLWB_JpFh#XF}5C zV4Mu0PZ}J=S7Sl4h)@7~Cc+R{<1KQIB~{Noqoj3+lYAtAQ-?ad&vG^TozxPij8vz- zej9wWx>lfeXk<%|+M$G;0Iyfp>Bu5bZE6$j{=@0OUV4P)D-8Gzn8283n8QzVB@c^8 z@VJK(INXe-t2f&i!4m1QUdhLafpy0$Q*ho0!R}hJLerm`IGMlw?g{zRbxIW3s<~e7 zyR|wIB+S@TYyPF{xu@5VxO(du{;ch;F3ECsYh3o|1F6DxsETXg8Q7CD|A$a_^B1~=hrRZfE|WD&JkGmzszULQ;;PQ@#MC+JMSoGHbBW{+3Yt;d+Fvk2|J~oY3ye#C>NaY4>MTo5F+wgrTUW zeRB_>0A4HOSDhoXr#)5mcEuR{N2NI?O$)(@DsZOXo@uF&o^r=*9bb zId`Z7R&cQ*SEcaK%lUaN2U*x(PAB@`_UEH4-{O_bFZ`y2f{TI!O`?Sjx}4959!rR+ zZzZTH)|?hT-q6!(Y9?d#`X01>`Y6Q2aew~K9+h-~b?A$T{0>zFD9_SLm4~Q<;6m`u zCA2o;fb4&wdf}Z~FcGc{@SB5+Yn7sR@8EswbqcpufU*c!JR$$*H zCLjmMh4PNhLMJ_)7{o zHE_w6fzCH!@MOD@mBSUe*7;#4)w+F;fKs1{{-CJsoS>?fi;R(RilbsR`MD4nX<2Xr zk#NiE2y*dW%ily1JTyUnvCv~u2wlWr5O4~Y zvx^Pm3&F?3z1eo5utfrI+6hR5uuhx&J6#~o<-6fE-FNGqwW$8=I8SMB!^&5StfRI_ zJLz-p#=AoCFA&4m48ZKNtbG$#WH@`z+_aTL9$}=Ji}=3} zeB?7gdrohQrSYn>(XzZ>n8`WnUYfsMxdlm2z-!8*{gyS7=*d`9G>dQdQ;b`?NIZxj zhhzj&J{YX*g~}{*^ot*}YtG$b?A#&O>ayI#JKHqp;zdW&8 zT*lBN3+WJUMx4kwRk1xBHrKvd($uNeM?I~qM2_!qk_^iT171J}#FNc4fEgYQ+nkbX zv*ey>;HOqPhW0M=1R=Y)i)@6yRu{$)G(m~_k5@SN|LuwYLo0{FuKRekLEt?G9cINk zCVu;|X9}CqBWtqVg<#104VYdGb&MCxBs|>_#FbHb$I)_X^Zv{H*J7`0UFM%dHcuk* z4f(Xu{1GPF^V}0SY?`Yl*oxHHfjzztO`U6Cr;L{Onb$PqF2CMeUVJ=O-UE{s;iL-; zA|X?yq!z=AU&(rVm=SjA%5!927l{ZWQ?<4Q2HUo_C$6Pg|ZIn71mBR;cqgJ^OpnQOJXCPquc9w zO;uDotN}3;+eh+m-aNdT*uR_qz@-w9kM@YhLrYA<4MfIywCC_%EOr$R`}|p2x{m*d z3bI_gpMPJO_}umM5HeB_Ce`T@$DZ~6NaHcBtY#dY?rqR}A}t=&>^l`^|6(uvFtm(g znaIYqgznnRMxqe4sU&8T=uyY7aE*dT=%a+gqckQTljPCAkS1cO5Lad%ax>OiGa|iy zGPqYc^0FSbv2w-vh0|?nZNCx0WVf}qwh0p~&v(~O79g;*zI3O!&fb;O(MPHqCsk~q z;D_#Z$7D{`o^;SfAO>xj0F-jS2ij%$?lN+_M8_Si&2BlI~{ zF5hME5j!SS;sn7C!bg?p*((egO0bjVd8fzutt6Q>Cm&KAY91$7Lnl{4t}UE%=@U+J z)D$O*KPPqo$?j;%oo(ccF14@0e}2s^Zl{&HLDd3LuhaDR=R5==-7R@$B}S~xfZAuF zV+bn3+jO2Z;A8Hu)yYrl4QWURn~@;Cq2n>cL*X_1z^F><0Idt!!-bpwR8rrQ9G|X* zQTa>bVI3YDu`{hotf?2chEny1wYHk}NUKAA33^iUE)?h)$MTci})-&EOBD#j{2vB|Aoops`UMX2(4#IPG?3Z*MO?2 zZ$&xWySvl}Tix9_cs-;XSIy=hRqy2BB?@MlA1(KcqqT8^_n&lR;VvN#B6g za)K?zY5yg)PJB7tP6YssYIwK_#9B#d>vxI+3>X1r}>GJAFd^YIz#THstG zrj?PYP6(Damql%I`Vz{krO;R;sXKd2`BuT;*yV;scvA zr_H1!ZcIwFmzm~%(Mtc42CAa!p@;{z-9BTNheo8lH}YMg1ri~O$JEF30@W};VjXb| z8q~Gin3Voac|9cv120{k1LF^*9>Y&rsbr+!KEDPn>Lf;@11NQwH&d$VbtOsxIXzlEr5AyJ=m%>TE0tuIy%GDeGhdZQ^`Yz%93p<))y|srikUSx5pm07 zsDu-H06cQAV%=aJ2XXKU$@|s*3Yk33zpvbs@s;r5=1<6I0Z+@-X>oY; zCvv!&Hvc2xhNct8e~Yu%g6w_)^#a9eB|O1?qhtrBvl~kRPclU*wqz8d2ox6alRsOk z9CKWtJYWhRb*qU}$D>Z`1(?uyOE057yg~~n-x^zYv4D>WUg zj!21KR0(ipYMkyZ`Qh~|;WpVFUL8$DiS;Q0TU>kfdieg_o8sW3B?iBJl;`JUkpF+4 zlQ%wLRzUjMAR6C*NI+&MFFXJXH{!dg-{Y_USc+={=JP@-j|-O1#D>@mt;=Kr@l2Uj zRSqftcq$H4Id25k1J>f3RbM<1xL=oougW@Rich+I&WQ9||1hqF7BOeB`y*kCPQ zH}5&_!^lHvKokddiVJnE+fwlZ44+?3;lO` z9VH}@CEmgn`f>O&?KWE<%5T$7>f}a9w;Sd$N>}{O>^jyi;)RTa+gIP*vwbL}U9aQZ z$G}whSG?r{sdP}I=n%M*wS&)cn<{gsM&~bNDrA9b4e%Y_+T}jUD4iRBgeY5%WcWFE z%7g_@Z70Sye)7SCW4<7jR6RuF( zCn@XzN&*`YFWlu_BlR;4kG1@>+BQ$0(7^+T_`-Rm@x9aN-|bXhL5?<++MkzRvn*dy z#&Mvv`oJ(q%15SXuwWOUx3y-N-0LyTp+0Nbids)j%K!%ztNY;OKSd)}#pLg-(&+d~X=6NJ?YJ zJzDyqfFcsV^`EIi;Eg9h@PL8G*7B19aRBF+z9eFo^Cwy6QwCI${88p93T}G%&sd7s zQri0qx`OtMpX)ZI+*wGXX%(O{9Q8!u2r%&xWl0crhw%VrjArI;TM!KhJKh~jb&J7m?YeUah1Q5;yagZOxqXaSQw z|Hcl*nYs5z@9TC5jYVl^pqZf(P6K2Fr!EFcGjvxj)BR@z7%kZ+vOU^jx9Et zzEbd_wLx1CiZUxyVRc5I9XLv0ytK9FCleM>6kNbGEav%07M)@IpMB9{sb*4Uc$@A# z3v@R?QHX@44U|?(2PqXdjfTc70qwLs_>6XI%?3kG%O3kIG)FNA4Br>X3+lh9{ z0?7A42K0} z*&Q^X8yIL5rXA)2iIWb%4*@ZI53M^+>MvTZXaX(SEIAa%8v#*`Q31KgjME5!7Xi(R z{7L5@f$fD_sT+5^+x$-<8S=}LELU67JEID=FXes0(2)7yRjtP zvdilH{KDbdE6HmyC9dpbdl){uR$^kJFo|XjqbgwqeKzzcNOI zs{m(-FZg;tZWi`wIcs8IBN!=Anfh%g?Q`Z!9Ayj@y-i(G3%s$%#$q{Fl(@4^n{jdg zR0fj_%c7W|?z+|fW17F;$L1+5=b^ueDnUxNddRS~+7eB~;Fa7365-rpY4Fp`WjbLv zjluk{^$I=c1_v#GI&UZMk{RseYMooz(&YWu(DdM+ooCA$?d*a;=8PXt))ncs%bBAK zWEM`(<5rY3#Y(n|;HHA9&{l05ztEv0{|1L}j?rSKi$?i`@?J=#ev{=^wq>Uiy{_t? zXOFM5@Xe@5v1mz0>D}9YhydA}1kwl5lKGBBo%MFa(rAU~j$Lc_ExlyUblEwi@|=e& z;OZJ0*uk0c8U7Ibb!J|FQKvUk%%h)pQIWWpu4Jf&yv=*p$8zg^FN+8LUKjQrt*)8h zau6)k#hR0>!!^nEa@Q9er_a1G>|XZWl1pC?Tg|n?m|TybiRDL@EPTI)3d8?*SN^FBb%1+U|TR>)Emq7EE_KmC6V{{FeT5WxO*|r zRsCG!o8{(jPU-eH&8+8vNhGQQhI)>l@l>+UvkDQ>0z&@yo33mNFdnh1F1EXx1?A3P z;Kt9j_Hg!Ty|ZibZ7;Y&oETuwibv=zBmT<~{#E0<=A0wW4uMcX6`_jGVATSSn&v<4 zaWoBqio6gZO~~DG?me=6g|D^wyVU2%8vMNF9pm^ZA}J@f-f> zYAs0ULnn+>ic)e)01a3~asB99ryI0v&h1nH)pgC#U%6LP1oM-Ht!a|Rq`)vK@47ia zfh0kRVP~+KRztxIM7g&?{E(!xnttcyF+bddiX-y5F#6ddBB+?_MB=@pnLWT?ZJxmJ zf&LJ;UMegI5Ec|6BV&u0x$GTYh^kY!*y}wF;fUuhFAy_0IL{;!+O|G!8*busYq>4c zqcv#jX;aK`mq9%1(HN9d()J(f9TD(~{}~t2Fkn&eeCThoP?mya^wg_!ZcqH18%ADv zv%T$c6td-W0P=t(e6#=!>K2+iy$ee7Fj%W?pVa)ThU82q0ubaP2)a09!-MNqlZ(2{U`0mO^m5BpWVdk#u0DXt1-MV6g9d zyPd1LCZEgUh@`%~AgyzattN0>e^cqYfyQ0?{1anYj==cJg9p3jF`nSryqtzaC;D6}qB#2j*l$9p zHOJurl95W~{$&6AYVY2;#r*>cc3cECdCUEg*^DRMZ%t}H>jGgls+WDHHwO+giHG?O z#uw^W6AIViOzoT|eB|n)RKe%`rR71A7FlE%kWDn7YDXTIR(d7o8gFhPZFZw;m;=h_ zCyxtHHCIkV>*vO9(#lT6n2;E>)Zc@oA3mPD-@X?|>Sk|Ud*?j89$h#B(KqgPn9l8& zEpjWr&Eg!ER)IIKcDL)s_OJG~UZ1iYC3Ho2KYjy1p3P<&38Obzf{~^dmK5co!n`Hg zL)TBn{{Ee8BwGH9I>4rDfwTecXODqW2-q0vo5piUp9y~u;yLfAKbkH%XH&Sh)0x+5 zlss{k2m7tv!b^Tdfa zXyxAoPmXZgC7G!=>fJ*mmpiYj`bL4J(&Sy~58y%ph;A!Ue=~bVrRL88QzlydP`Dft z`En?YXZQKF?p4?k<)sFZKhCKp#$MzMPIQXAaw4OQ=uX6+E*3D?laqiW+5y7b~$AuHKS#)tM5~Ol} zt#$)-y44I?%+(ah%F6n9f{>CVdLGH6L7QZsGNFh1fX)%p0-M~ll;EFPrEa}61T$)) zj+Y7g4<+1Wean0zUUFJXamtcdW#k-B@9qWxZkKC}KN#qM{?ye^?NaKR5Bz%#)j{B~ zq6-WZPqgxG*LUqVGpn*W-IIYm)FUOqT4fZ$qZa;J4{_PfkS(iW1itB%`q8p(;O=VH~!8~YY&h(sOt}#|! yOoKejOsqecedtsoV3+dwfBVQ};+Ly;SR)_Hn_Sib9Ox-h7zJ4snJR!;$o~UsG9+*S literal 0 HcmV?d00001 diff --git a/src/assets/images/send-receive.png b/src/assets/images/send-receive.png index 86db4a4fdfcf14d05b1e47006be6b7897efd6102..c73ff0f7e05cbe1657b495c0715a86bf6e5b8c07 100644 GIT binary patch literal 13765 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 literal 41606 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