From 5b781ad207c026d59db2f084842c53edd56d0967 Mon Sep 17 00:00:00 2001 From: siandreev Date: Thu, 19 Dec 2024 18:50:37 +0100 Subject: [PATCH 01/20] feat: tron core logic added --- apps/desktop/.env.example | 1 + apps/desktop/src/app/App.tsx | 5 +- apps/desktop/webpack.plugins.ts | 3 +- apps/extension/src/App.tsx | 5 +- apps/tablet/.env.example | 1 + apps/tablet/src/app/App.tsx | 3 +- apps/twa/src/App.tsx | 5 +- apps/web/src/App.tsx | 3 +- packages/core/package.json | 4 +- packages/core/resource/Dockerfile.tronApi | 3 - packages/core/src/entries/account.ts | 84 +++- packages/core/src/entries/apis.ts | 2 - .../src/entries/crypto/asset/constants.ts | 21 +- packages/core/src/entries/tron/tron-wallet.ts | 4 + packages/core/src/entries/wallet.ts | 17 +- packages/core/src/service/walletService.ts | 122 ++++- packages/core/src/tonkeeperApi/tonendpoint.ts | 2 + .../src/tronApi/.openapi-generator-ignore | 23 - .../core/src/tronApi/.openapi-generator/FILES | 27 -- .../src/tronApi/.openapi-generator/VERSION | 1 - packages/core/src/tronApi/apis/TronApi.ts | 441 ------------------ packages/core/src/tronApi/apis/index.ts | 3 - packages/core/src/tronApi/index.ts | 72 ++- .../tronApi/models/ContractDeployAction.ts | 66 --- .../src/tronApi/models/EstimatePayload.ts | 91 ---- .../core/src/tronApi/models/FromMessage.ts | 83 ---- .../tronApi/models/GetEstimationRequest.ts | 82 ---- .../tronApi/models/GetSettings401Response.ts | 66 --- .../core/src/tronApi/models/ModelError.ts | 66 --- .../core/src/tronApi/models/PublishPayload.ts | 66 --- .../models/PublishTransactionRequest.ts | 91 ---- .../src/tronApi/models/ReceiveTRC20Action.ts | 91 ---- .../core/src/tronApi/models/RequestData.ts | 118 ----- .../core/src/tronApi/models/RequestMessage.ts | 84 ---- .../src/tronApi/models/SendTRC20Action.ts | 91 ---- .../core/src/tronApi/models/TronAction.ts | 140 ------ .../core/src/tronApi/models/TronBalance.ts | 82 ---- .../core/src/tronApi/models/TronBalances.ts | 73 --- packages/core/src/tronApi/models/TronEvent.ts | 114 ----- .../core/src/tronApi/models/TronEvents.ts | 81 ---- packages/core/src/tronApi/models/TronFee.ts | 82 ---- .../core/src/tronApi/models/TronSettings.ts | 91 ---- packages/core/src/tronApi/models/TronToken.ts | 102 ---- .../core/src/tronApi/models/TronWallet.ts | 84 ---- packages/core/src/tronApi/models/index.ts | 23 - packages/core/src/tronApi/runtime.ts | 431 ----------------- packages/uikit/package.json | 1 + .../uikit/src/components/home/TronAssets.tsx | 24 +- packages/uikit/src/hooks/appContext.ts | 12 +- packages/uikit/src/libs/queryKey.ts | 2 +- packages/uikit/src/state/mnemonic.ts | 11 + packages/uikit/src/state/tron/tron.ts | 145 ++---- packages/uikit/src/state/wallet.ts | 40 +- yarn.lock | 77 +-- 54 files changed, 457 insertions(+), 3005 deletions(-) delete mode 100644 packages/core/resource/Dockerfile.tronApi create mode 100644 packages/core/src/entries/tron/tron-wallet.ts delete mode 100644 packages/core/src/tronApi/.openapi-generator-ignore delete mode 100644 packages/core/src/tronApi/.openapi-generator/FILES delete mode 100644 packages/core/src/tronApi/.openapi-generator/VERSION delete mode 100644 packages/core/src/tronApi/apis/TronApi.ts delete mode 100644 packages/core/src/tronApi/apis/index.ts delete mode 100644 packages/core/src/tronApi/models/ContractDeployAction.ts delete mode 100644 packages/core/src/tronApi/models/EstimatePayload.ts delete mode 100644 packages/core/src/tronApi/models/FromMessage.ts delete mode 100644 packages/core/src/tronApi/models/GetEstimationRequest.ts delete mode 100644 packages/core/src/tronApi/models/GetSettings401Response.ts delete mode 100644 packages/core/src/tronApi/models/ModelError.ts delete mode 100644 packages/core/src/tronApi/models/PublishPayload.ts delete mode 100644 packages/core/src/tronApi/models/PublishTransactionRequest.ts delete mode 100644 packages/core/src/tronApi/models/ReceiveTRC20Action.ts delete mode 100644 packages/core/src/tronApi/models/RequestData.ts delete mode 100644 packages/core/src/tronApi/models/RequestMessage.ts delete mode 100644 packages/core/src/tronApi/models/SendTRC20Action.ts delete mode 100644 packages/core/src/tronApi/models/TronAction.ts delete mode 100644 packages/core/src/tronApi/models/TronBalance.ts delete mode 100644 packages/core/src/tronApi/models/TronBalances.ts delete mode 100644 packages/core/src/tronApi/models/TronEvent.ts delete mode 100644 packages/core/src/tronApi/models/TronEvents.ts delete mode 100644 packages/core/src/tronApi/models/TronFee.ts delete mode 100644 packages/core/src/tronApi/models/TronSettings.ts delete mode 100644 packages/core/src/tronApi/models/TronToken.ts delete mode 100644 packages/core/src/tronApi/models/TronWallet.ts delete mode 100644 packages/core/src/tronApi/models/index.ts delete mode 100644 packages/core/src/tronApi/runtime.ts diff --git a/apps/desktop/.env.example b/apps/desktop/.env.example index 1f6d9cbc6..b4802c19f 100644 --- a/apps/desktop/.env.example +++ b/apps/desktop/.env.example @@ -2,3 +2,4 @@ REACT_APP_TONCONSOLE_API=https://dev-pro.tonconsole.com REACT_APP_TG_BOT_ID=6345183204 REACT_APP_TG_BOT_ORIGIN=https://tonkeeper.com REACT_APP_STONFI_REFERRAL_ADDRESS=UQCthC8ICK7K8Hkfm9smblLFroKrYrEMwZuoD4Nbm5LswUnc +REACT_APP_TRON_API_KEY= diff --git a/apps/desktop/src/app/App.tsx b/apps/desktop/src/app/App.tsx index e9cbb08cc..6077c44c7 100644 --- a/apps/desktop/src/app/App.tsx +++ b/apps/desktop/src/app/App.tsx @@ -89,6 +89,7 @@ import { useGlobalPreferencesQuery } from '@tonkeeper/uikit/dist/state/global-pr import { DesktopManageMultisigsPage } from '@tonkeeper/uikit/dist/desktop-pages/manage-multisig-wallets/DesktopManageMultisigs'; import { useGlobalSetup } from '@tonkeeper/uikit/dist/state/globalSetup'; import { DesktopMultisigOrdersPage } from '@tonkeeper/uikit/dist/desktop-pages/multisig-orders/DesktopMultisigOrders'; +import { TronApi } from '@tonkeeper/core/dist/tronApi'; const queryClient = new QueryClient({ defaultOptions: { @@ -134,6 +135,7 @@ const langs = 'en,zh_TW,zh_CN,id,ru,it,es,uk,tr,bg,uz,bn'; declare const REACT_APP_TONCONSOLE_API: string; declare const REACT_APP_TG_BOT_ID: string; declare const REACT_APP_STONFI_REFERRAL_ADDRESS: string; +declare const REACT_APP_TRON_API_KEY: string; export const Providers = () => { const { t: tSimple, i18n } = useTranslation(); @@ -333,7 +335,8 @@ export const Loader: FC = () => { ios: false, env: { tgAuthBotId: REACT_APP_TG_BOT_ID, - stonfiReferralAddress: REACT_APP_STONFI_REFERRAL_ADDRESS + stonfiReferralAddress: REACT_APP_STONFI_REFERRAL_ADDRESS, + tronApiKey: REACT_APP_TRON_API_KEY }, defaultWalletVersion: WalletVersion.V5R1 }; diff --git a/apps/desktop/webpack.plugins.ts b/apps/desktop/webpack.plugins.ts index 754e4081c..58373f580 100644 --- a/apps/desktop/webpack.plugins.ts +++ b/apps/desktop/webpack.plugins.ts @@ -21,6 +21,7 @@ export const plugins = [ process.env.REACT_APP_STONFI_REFERRAL_ADDRESS ), REACT_APP_APTABASE: JSON.stringify(process.env.REACT_APP_APTABASE), - REACT_APP_APTABASE_HOST: JSON.stringify(process.env.REACT_APP_APTABASE_HOST) + REACT_APP_APTABASE_HOST: JSON.stringify(process.env.REACT_APP_APTABASE_HOST), + REACT_APP_TRON_API_KEY: JSON.stringify(process.env.REACT_APP_TRON_API_KEY) }) ]; diff --git a/apps/extension/src/App.tsx b/apps/extension/src/App.tsx index 55445d47f..a0b95f893 100644 --- a/apps/extension/src/App.tsx +++ b/apps/extension/src/App.tsx @@ -238,7 +238,10 @@ export const Loader: FC = React.memo(() => { hideSigner: true, hideMam: true, hideMultisig: true, - defaultWalletVersion: WalletVersion.V5R1 + defaultWalletVersion: WalletVersion.V5R1, + env: { + tronApiKey: process.env.REACT_APP_TRON_API_KEY + } }; return ( diff --git a/apps/tablet/.env.example b/apps/tablet/.env.example index 86d0a079a..0bfa8bd08 100644 --- a/apps/tablet/.env.example +++ b/apps/tablet/.env.example @@ -5,3 +5,4 @@ VITE_APP_TONCONSOLE_HOST=https://pro.tonconsole.com VITE_APP_TG_BOT_ID=6345183204 VITE_APP_TG_BOT_ORIGIN=https://tonkeeper.com VITE_APP_STONFI_REFERRAL_ADDRESS=UQCthC8ICK7K8Hkfm9smblLFroKrYrEMwZuoD4Nbm5LswUnc +VITE_APP_TRON_API_KEY= diff --git a/apps/tablet/src/app/App.tsx b/apps/tablet/src/app/App.tsx index dbedaf775..c08f0a8fb 100644 --- a/apps/tablet/src/app/App.tsx +++ b/apps/tablet/src/app/App.tsx @@ -348,7 +348,8 @@ export const Loader: FC = () => { ios: false, env: { tgAuthBotId: import.meta.env.VITE_APP_TG_BOT_ID, - stonfiReferralAddress: import.meta.env.VITE_APP_STONFI_REFERRAL_ADDRESS + stonfiReferralAddress: import.meta.env.VITE_APP_STONFI_REFERRAL_ADDRESS, + tronApiKey: import.meta.env.VITE_APP_TRON_API_KEY }, defaultWalletVersion: WalletVersion.V5R1 }; diff --git a/apps/twa/src/App.tsx b/apps/twa/src/App.tsx index a18f95cad..3759fd019 100644 --- a/apps/twa/src/App.tsx +++ b/apps/twa/src/App.tsx @@ -284,7 +284,10 @@ export const Loader: FC<{ sdk: TwaAppSdk }> = ({ sdk }) => { hideMam: true, hideMultisig: true, defaultWalletVersion: WalletVersion.V5R1, - browserLength: 4 + browserLength: 4, + env: { + tronApiKey: import.meta.env.VITE_APP_TRON_API_KEY + } }; return ( diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx index 3ed81b046..aa18e6b71 100644 --- a/apps/web/src/App.tsx +++ b/apps/web/src/App.tsx @@ -193,7 +193,8 @@ const Loader: FC = () => { hideMultisig: isMobile, env: { tgAuthBotId: import.meta.env.VITE_APP_TG_BOT_ID, - stonfiReferralAddress: import.meta.env.VITE_APP_STONFI_REFERRAL_ADDRESS + stonfiReferralAddress: import.meta.env.VITE_APP_STONFI_REFERRAL_ADDRESS, + tronApiKey: import.meta.env.VITE_APP_TRON_API_KEY } }; diff --git a/packages/core/package.json b/packages/core/package.json index 055cdbaed..8629869c2 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -9,7 +9,6 @@ "start": "tsc -w", "sdk": "npm-run-all -p generate:*", "generate:sdkV2": "rm -fr src/tonApiV2 && docker build -f resource/Dockerfile.apiV2 . -t tonapisdkv2 && docker run --rm --user=$(id -u):$(id -g) -v \"$PWD\":/local tonapisdkv2", - "generate:tronApi": "rm -fr src/tronApi && docker build -f resource/Dockerfile.tronApi . -t tronapisdk && docker run --rm --user=$(id -u):$(id -g) -v \"$PWD\":/local tronapisdk", "generate:tonConsoleApi": "rm -r ./src/tonConsoleApi || true && npx openapi-typescript-codegen --input 'https://raw.githubusercontent.com/tonkeeper/tonconsole_backend/dev/swagger.yaml?token=GHSAT0AAAAAACKHFPNI4HJL4Q4RIAYDUQ3MZPAKM2A' --output ./src/tonConsoleApi", "generate:swapsApi": "rm -r ./src/swapsApi || true && npx openapi-typescript-codegen --input 'https://raw.githubusercontent.com/tonkeeper/swaps-backend/master/swagger.yaml?token=GHSAT0AAAAAACJYQUODBKR67AB7WULZBFWEZSUUGFQ' --output ./src/swapsApi", "generate:batteryApi": "rm -fr ./src/batteryApi && docker build -f resource/Dockerfile.batteryApi . -t batteryapi && docker run --rm --user=$(id -u):$(id -g) -v \"$PWD\":/local batteryapi", @@ -27,12 +26,13 @@ "@ledgerhq/hw-transport-webusb": "^6.28.6", "@ton-community/ton-ledger": "^7.2.0-pre.2", "@ton-keychain/core": "^0.0.4", + "@ton-keychain/trx": "^0.0.6", "@ton/core": "0.56.0", "@ton/crypto": "3.2.0", "@ton/ton": "^15.1.0", "bignumber.js": "^9.1.1", "bip39": "^3.1.0", - "ethers": "^6.6.5", + "ethers": "^6.13.4", "query-string": "^8.1.0", "tweetnacl": "^1.0.3" } diff --git a/packages/core/resource/Dockerfile.tronApi b/packages/core/resource/Dockerfile.tronApi deleted file mode 100644 index 837ef89b4..000000000 --- a/packages/core/resource/Dockerfile.tronApi +++ /dev/null @@ -1,3 +0,0 @@ -FROM openapitools/openapi-generator-cli - -CMD ["generate", "-i", "https://raw.githubusercontent.com/tonkeeper/gasless-tron-protocol/main/api/openapi.yml", "-g", "typescript-fetch", "-o", "/local/src/tronApi", "-p", "supportsES6=true,withInterfaces=true"] \ No newline at end of file diff --git a/packages/core/src/entries/account.ts b/packages/core/src/entries/account.ts index 7dbe458e2..0176ef73c 100644 --- a/packages/core/src/entries/account.ts +++ b/packages/core/src/entries/account.ts @@ -16,6 +16,7 @@ import { } from './wallet'; import { assertUnreachable } from '../utils/types'; import { Network } from './network'; +import { TronWallet } from './tron/tron-wallet'; /** * @deprecated @@ -61,6 +62,11 @@ export class Clonable { } abstract class TonMnemonic extends Clonable implements IAccountVersionsEditable { + /** + * undefined for old wallets + */ + readonly tronWallet: TronWallet | undefined; + get allTonWallets() { return this.tonWallets; } @@ -69,6 +75,10 @@ abstract class TonMnemonic extends Clonable implements IAccountVersionsEditable return this.tonWallets.find(w => w.id === this.activeTonWalletId)!; } + get activeTronWallet() { + return this.tronWallet; + } + /** * @param id ton public key hex string without 0x corresponding to the mnemonic */ @@ -79,9 +89,13 @@ abstract class TonMnemonic extends Clonable implements IAccountVersionsEditable public auth: AuthPassword | AuthKeychain, public activeTonWalletId: WalletId, public tonWallets: TonWalletStandard[], - public mnemonicType?: MnemonicType + public mnemonicType?: MnemonicType, + networks?: { + tron: TronWallet; + } ) { super(); + this.tronWallet = networks?.tron; } getTonWallet(id: WalletId) { @@ -117,10 +131,54 @@ abstract class TonMnemonic extends Clonable implements IAccountVersionsEditable } export class AccountTonMnemonic extends TonMnemonic { public readonly type = 'mnemonic'; + + static create(params: { + id: AccountId; + name: string; + emoji: string; + auth: AuthPassword | AuthKeychain; + activeTonWalletId: WalletId; + tonWallets: TonWalletStandard[]; + mnemonicType: MnemonicType; + networks: { + tron: TronWallet; + }; + }) { + return new AccountTonMnemonic( + params.id, + params.name, + params.emoji, + params.auth, + params.activeTonWalletId, + params.tonWallets, + params.mnemonicType, + params.networks + ); + } } export class AccountTonTestnet extends TonMnemonic { public readonly type = 'testnet'; + + static create(params: { + id: AccountId; + name: string; + emoji: string; + auth: AuthPassword | AuthKeychain; + activeTonWalletId: WalletId; + tonWallets: TonWalletStandard[]; + mnemonicType: MnemonicType; + }) { + return new AccountTonTestnet( + params.id, + params.name, + params.emoji, + params.auth, + params.activeTonWalletId, + params.tonWallets, + params.mnemonicType + ); + } } export class AccountTonWatchOnly extends Clonable implements IAccount { @@ -389,6 +447,11 @@ export class AccountMAM extends Clonable implements IAccountTonWalletStandard { )!; } + get activeTronWallet() { + const activeDerivation = this.activeDerivation; + return activeDerivation.tronWallet; + } + /** * @param id index 0 derivation ton public key hex string without 0x */ @@ -682,6 +745,25 @@ export function seeIfMainnnetAccount(account: Account): boolean { return network === Network.MAINNET; } +export function isAccountTronCompatible( + account: Account +): account is AccountTonMnemonic | AccountMAM { + switch (account.type) { + case 'mnemonic': + case 'mam': + return true; + case 'testnet': + case 'ton-only': + case 'ledger': + case 'watch-only': + case 'ton-multisig': + case 'keystone': + return false; + default: + return assertUnreachable(account); + } +} + export type AccountsState = Account[]; export const defaultAccountState: AccountsState = []; diff --git a/packages/core/src/entries/apis.ts b/packages/core/src/entries/apis.ts index 06c4e51ab..856c33c25 100644 --- a/packages/core/src/entries/apis.ts +++ b/packages/core/src/entries/apis.ts @@ -1,7 +1,5 @@ import { Configuration as TonV2Configuration } from '../tonApiV2'; -import { Configuration as TronConfiguration } from '../tronApi'; export interface APIConfig { tonApiV2: TonV2Configuration; - tronApi: TronConfiguration; } diff --git a/packages/core/src/entries/crypto/asset/constants.ts b/packages/core/src/entries/crypto/asset/constants.ts index 48a48856b..6bd8889e7 100644 --- a/packages/core/src/entries/crypto/asset/constants.ts +++ b/packages/core/src/entries/crypto/asset/constants.ts @@ -1,4 +1,3 @@ -import { TronBalance } from '../../../tronApi'; import { BLOCKCHAIN_NAME } from '../../crypto'; import { packAssetId } from './basic-asset'; import { TonAsset } from './ton-asset'; @@ -11,18 +10,18 @@ export const TRON_USDT_ASSET: TronAsset = { name: 'Tether USDT', decimals: 6, address: 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t', - blockchain: BLOCKCHAIN_NAME.TRON + blockchain: BLOCKCHAIN_NAME.TRON, + image: 'https://wallet.tonkeeper.com/img/usdt.svg' }; -export const toTronAsset = (balance: TronBalance): TronAsset => { - return { - id: packAssetId(BLOCKCHAIN_NAME.TRON, balance.token.address), - symbol: balance.token.symbol, - name: balance.token.name, - decimals: balance.token.decimals, - address: balance.token.address, - blockchain: BLOCKCHAIN_NAME.TRON - }; +export const TRON_TRX_ASSET: TronAsset = { + id: packAssetId(BLOCKCHAIN_NAME.TRON, 'TRX'), + symbol: 'TRX', + name: 'TRX', + decimals: 6, + address: 'TRX', + blockchain: BLOCKCHAIN_NAME.TRON, + image: 'https://wallet.tonkeeper.com/img/trx.svg' }; export const TON_ASSET: TonAsset = { diff --git a/packages/core/src/entries/tron/tron-wallet.ts b/packages/core/src/entries/tron/tron-wallet.ts new file mode 100644 index 000000000..e61b82a87 --- /dev/null +++ b/packages/core/src/entries/tron/tron-wallet.ts @@ -0,0 +1,4 @@ +export type TronWallet = { + address: string; + id: string; +}; diff --git a/packages/core/src/entries/wallet.ts b/packages/core/src/entries/wallet.ts index 3000fcabd..f8dae93ab 100644 --- a/packages/core/src/entries/wallet.ts +++ b/packages/core/src/entries/wallet.ts @@ -2,6 +2,7 @@ import { Language } from './language'; import { Network } from './network'; import { DeprecatedAuthState } from './password'; import { WalletProxy } from './proxy'; +import { TronWallet } from './tron/tron-wallet'; export enum WalletVersion { V3R1 = 0, @@ -129,7 +130,10 @@ export type DerivationItem = { index: number; activeTonWalletId: WalletId; tonWallets: TonWalletStandard[]; - // tronWallets: never; + /** + * undefined for old wallets + */ + tronWallet?: TronWallet; }; export type DerivationItemNamed = DerivationItem & { @@ -168,14 +172,3 @@ export const defaultPreferencesConfig: TonWalletConfig = { enabledForNfts: true } }; - -export interface TronWalletStorage { - ownerWalletAddress: string; - walletByChain: Record; -} - -export interface TronWalletState { - ownerWalletAddress: string; - chainId: string; - walletAddress: string; -} diff --git a/packages/core/src/service/walletService.ts b/packages/core/src/service/walletService.ts index 0f6d95152..a61de1650 100644 --- a/packages/core/src/service/walletService.ts +++ b/packages/core/src/service/walletService.ts @@ -33,6 +33,9 @@ import { walletContract } from './wallet/contractService'; import { TonKeychainRoot, KeychainTonAccount } from '@ton-keychain/core'; import { mnemonicToKeypair } from './mnemonicService'; import { FiatCurrencies } from '../entries/fiat'; +import { KeychainTrxAccountsProvider, TronAddressUtils } from '@ton-keychain/trx'; +import { TronWallet } from '../entries/tron/tron-wallet'; +import { ethers } from 'ethers'; export const createMultisigTonAccount = async ( storage: IStorage, @@ -116,6 +119,30 @@ interface CreateWalletContext { defaultWalletVersion: WalletVersion; } +export const tronWalletByTonMnemonic = async ( + mnemonic: string[], + mnemonicType: MnemonicType = 'ton' +): Promise => { + if (mnemonicType === 'ton') { + const tonAccount = await KeychainTonAccount.fromMnemonic(mnemonic); + const trxProvider = KeychainTrxAccountsProvider.fromEntropy(tonAccount.entropy); + const trxAccount = trxProvider.getAccount(); + + return { id: trxAccount.address, address: trxAccount.address }; + } else { + const node = ethers.HDNodeWallet.fromPhrase( + mnemonic.join(' '), + undefined, + "m/44'/195'/0'/0" + ); + const address = TronAddressUtils.hexToBase58(node.deriveChild(0).address); + return { + id: address, + address + }; + } +}; + export const getContextApiByNetwork = (context: CreateWalletContext, network: Network) => { const api = network === Network.TESTNET ? context.testnetApi : context.mainnetApi; return api; @@ -164,7 +191,9 @@ const createPayloadOfStandardTonAccount = async ( const walletIdToActivate = wallets.slice().sort(sortWalletsByVersion)[0].id; - return { name, emoji, publicKey, walletAuth, walletIdToActivate, wallets }; + const tronWallet = await tronWalletByTonMnemonic(mnemonic, mnemonicType); + + return { name, emoji, publicKey, walletAuth, walletIdToActivate, wallets, tronWallet }; }; export const mayBeCreateAccountId = ( @@ -195,7 +224,7 @@ export const createStandardTonAccountByMnemonic = async ( auth: AuthPassword | Omit; } ) => { - const { name, emoji, publicKey, walletAuth, walletIdToActivate, wallets } = + const { name, emoji, publicKey, walletAuth, walletIdToActivate, wallets, tronWallet } = await createPayloadOfStandardTonAccount( appContext, storage, @@ -205,14 +234,40 @@ export const createStandardTonAccountByMnemonic = async ( Network.MAINNET ); - return new AccountTonMnemonic( - createAccountId(Network.MAINNET, publicKey), + return AccountTonMnemonic.create({ + id: createAccountId(Network.MAINNET, publicKey), name, emoji, - walletAuth, - walletIdToActivate, - wallets, - mnemonicType + auth: walletAuth, + activeTonWalletId: walletIdToActivate, + tonWallets: wallets, + mnemonicType, + networks: { + tron: tronWallet + } + }); +}; + +export const standardTonAccountToAccountWithTron = async ( + account: AccountTonMnemonic, + getAccountMnemonic: () => Promise +) => { + const tronWallet = await tronWalletByTonMnemonic( + await getAccountMnemonic(), + account.mnemonicType + ); + + return new AccountTonMnemonic( + account.id, + account.name, + account.emoji, + account.auth, + account.activeTonWalletId, + account.tonWallets, + account.mnemonicType, + { + tron: tronWallet + } ); }; @@ -236,15 +291,15 @@ export const createStandardTestnetAccountByMnemonic = async ( Network.TESTNET ); - return new AccountTonTestnet( - createAccountId(Network.TESTNET, publicKey), + return AccountTonTestnet.create({ + id: createAccountId(Network.TESTNET, publicKey), name, emoji, - walletAuth, - walletIdToActivate, - wallets, + auth: walletAuth, + activeTonWalletId: walletIdToActivate, + tonWallets: wallets, mnemonicType - ); + }); }; const versionMap: Record = { @@ -523,8 +578,8 @@ export const createMAMAccountByMnemonic = async ( rootAccount.id ); - const namedDerivations: { item: DerivationItemNamed; shouldAdd: boolean }[] = - childTonWallets.map(w => { + const namedDerivations: { item: DerivationItemNamed; shouldAdd: boolean }[] = await Promise.all( + childTonWallets.map(async w => { const tonWallet = walletContract( w.tonAccount.publicKey, appContext.defaultWalletVersion, @@ -548,11 +603,13 @@ export const createMAMAccountByMnemonic = async ( emoji, index: w.derivationIndex, tonWallets, - activeTonWalletId: tonWallets[0].id + activeTonWalletId: tonWallets[0].id, + tronWallet: await tronWalletByTonMnemonic(w.tonAccount.mnemonics) }, shouldAdd: w.shouldAdd }; - }); + }) + ); const addedDerivationIndexes = namedDerivations.filter(d => d.shouldAdd).map(d => d.item.index); if (addedDerivationIndexes.length === 0) { @@ -570,6 +627,35 @@ export const createMAMAccountByMnemonic = async ( ); }; +export const mamAccountToMamAccountWithTron = async ( + account: AccountMAM, + getAccountMnemonic: () => Promise +) => { + const rootAccount = await TonKeychainRoot.fromMnemonic(await getAccountMnemonic()); + + const derivations = await Promise.all( + account.allAvailableDerivations.map(async d => { + const tonAccount = await rootAccount.getTonAccount(d.index); + const tronWallet = await tronWalletByTonMnemonic(tonAccount.mnemonics); + + return { + ...d, + tronWallet + }; + }) + ); + + return new AccountMAM( + account.id, + account.name, + account.emoji, + account.auth, + account.activeDerivationIndex, + account.addedDerivationsIndexes, + derivations + ); +}; + export function getFallbackAccountEmoji(publicKeyOrBase64: string) { let index; if (/^[0-9A-Fa-f]+$/g.test(publicKeyOrBase64)) { diff --git a/packages/core/src/tonkeeperApi/tonendpoint.ts b/packages/core/src/tonkeeperApi/tonendpoint.ts index 7f75a4c18..8f5e4b7a4 100644 --- a/packages/core/src/tonkeeperApi/tonendpoint.ts +++ b/packages/core/src/tonkeeperApi/tonendpoint.ts @@ -86,6 +86,8 @@ export interface TonendpointConfig { '2fa_tg_confirm_send_message_ttl_seconds'?: number; '2fa_tg_linked_ttl_seconds'?: number; '2fa_bot_url'?: string; + + tron_api_url?: string; } const defaultTonendpoint = 'https://api.tonkeeper.com'; // 'http://localhost:1339'; diff --git a/packages/core/src/tronApi/.openapi-generator-ignore b/packages/core/src/tronApi/.openapi-generator-ignore deleted file mode 100644 index 7484ee590..000000000 --- a/packages/core/src/tronApi/.openapi-generator-ignore +++ /dev/null @@ -1,23 +0,0 @@ -# OpenAPI Generator Ignore -# Generated by openapi-generator https://github.com/openapitools/openapi-generator - -# Use this file to prevent files from being overwritten by the generator. -# The patterns follow closely to .gitignore or .dockerignore. - -# As an example, the C# client generator defines ApiClient.cs. -# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line: -#ApiClient.cs - -# You can match any string of characters against a directory, file or extension with a single asterisk (*): -#foo/*/qux -# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux - -# You can recursively match patterns against a directory, file or extension with a double asterisk (**): -#foo/**/qux -# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux - -# You can also negate patterns with an exclamation (!). -# For example, you can ignore all files in a docs folder with the file extension .md: -#docs/*.md -# Then explicitly reverse the ignore rule for a single file: -#!docs/README.md diff --git a/packages/core/src/tronApi/.openapi-generator/FILES b/packages/core/src/tronApi/.openapi-generator/FILES deleted file mode 100644 index 53edd2d1b..000000000 --- a/packages/core/src/tronApi/.openapi-generator/FILES +++ /dev/null @@ -1,27 +0,0 @@ -.openapi-generator-ignore -apis/TronApi.ts -apis/index.ts -index.ts -models/ContractDeployAction.ts -models/EstimatePayload.ts -models/FromMessage.ts -models/GetEstimationRequest.ts -models/GetSettings401Response.ts -models/ModelError.ts -models/PublishPayload.ts -models/PublishTransactionRequest.ts -models/ReceiveTRC20Action.ts -models/RequestData.ts -models/RequestMessage.ts -models/SendTRC20Action.ts -models/TronAction.ts -models/TronBalance.ts -models/TronBalances.ts -models/TronEvent.ts -models/TronEvents.ts -models/TronFee.ts -models/TronSettings.ts -models/TronToken.ts -models/TronWallet.ts -models/index.ts -runtime.ts diff --git a/packages/core/src/tronApi/.openapi-generator/VERSION b/packages/core/src/tronApi/.openapi-generator/VERSION deleted file mode 100644 index 44bad91b1..000000000 --- a/packages/core/src/tronApi/.openapi-generator/VERSION +++ /dev/null @@ -1 +0,0 @@ -7.0.1-SNAPSHOT \ No newline at end of file diff --git a/packages/core/src/tronApi/apis/TronApi.ts b/packages/core/src/tronApi/apis/TronApi.ts deleted file mode 100644 index bb20cc840..000000000 --- a/packages/core/src/tronApi/apis/TronApi.ts +++ /dev/null @@ -1,441 +0,0 @@ -/* tslint:disable */ -/* eslint-disable */ -/** - * gasless TRON service REST api - * Service to publish TRON transactions - * - * The version of the OpenAPI document: 2.0.0 - * Contact: support@tonkeeper.com - * - * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). - * https://openapi-generator.tech - * Do not edit the class manually. - */ - - -import * as runtime from '../runtime'; -import type { - EstimatePayload, - GetEstimationRequest, - GetSettings401Response, - PublishPayload, - PublishTransactionRequest, - TronBalances, - TronEvent, - TronEvents, - TronSettings, - TronWallet, -} from '../models/index'; -import { - EstimatePayloadFromJSON, - EstimatePayloadToJSON, - GetEstimationRequestFromJSON, - GetEstimationRequestToJSON, - GetSettings401ResponseFromJSON, - GetSettings401ResponseToJSON, - PublishPayloadFromJSON, - PublishPayloadToJSON, - PublishTransactionRequestFromJSON, - PublishTransactionRequestToJSON, - TronBalancesFromJSON, - TronBalancesToJSON, - TronEventFromJSON, - TronEventToJSON, - TronEventsFromJSON, - TronEventsToJSON, - TronSettingsFromJSON, - TronSettingsToJSON, - TronWalletFromJSON, - TronWalletToJSON, -} from '../models/index'; - -export interface GetEstimationOperationRequest { - ownerAddress: string; - getEstimationRequest: GetEstimationRequest; -} - -export interface GetTransactionRequest { - transactionHash: string; -} - -export interface GetTransactionsRequest { - ownerAddress: string; - fingerprint?: string; - limit?: number; - offset?: number; - minTimestamp?: number; - maxTimestamp?: number; -} - -export interface GetWalletRequest { - ownerAddress: string; -} - -export interface GetWalletBalancesRequest { - walletAddress: string; -} - -export interface PublishTransactionOperationRequest { - ownerAddress: string; - publishTransactionRequest: PublishTransactionRequest; -} - -/** - * TronApi - interface - * - * @export - * @interface TronApiInterface - */ -export interface TronApiInterface { - /** - * Get transaction estimate - * @param {string} ownerAddress owner address - * @param {GetEstimationRequest} getEstimationRequest - * @param {*} [options] Override http request option. - * @throws {RequiredError} - * @memberof TronApiInterface - */ - getEstimationRaw(requestParameters: GetEstimationOperationRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise>; - - /** - * Get transaction estimate - */ - getEstimation(requestParameters: GetEstimationOperationRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise; - - /** - * Get service configuration - * @param {*} [options] Override http request option. - * @throws {RequiredError} - * @memberof TronApiInterface - */ - getSettingsRaw(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise>; - - /** - * Get service configuration - */ - getSettings(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise; - - /** - * Get transaction data - * @param {string} transactionHash transaction hash - * @param {*} [options] Override http request option. - * @throws {RequiredError} - * @memberof TronApiInterface - */ - getTransactionRaw(requestParameters: GetTransactionRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise>; - - /** - * Get transaction data - */ - getTransaction(requestParameters: GetTransactionRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise; - - /** - * Get list of transactions - * @param {string} ownerAddress owner address - * @param {string} [fingerprint] - * @param {number} [limit] - * @param {number} [offset] - * @param {number} [minTimestamp] - * @param {number} [maxTimestamp] - * @param {*} [options] Override http request option. - * @throws {RequiredError} - * @memberof TronApiInterface - */ - getTransactionsRaw(requestParameters: GetTransactionsRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise>; - - /** - * Get list of transactions - */ - getTransactions(requestParameters: GetTransactionsRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise; - - /** - * Get user wallet address data - * @param {string} ownerAddress owner address - * @param {*} [options] Override http request option. - * @throws {RequiredError} - * @memberof TronApiInterface - */ - getWalletRaw(requestParameters: GetWalletRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise>; - - /** - * Get user wallet address data - */ - getWallet(requestParameters: GetWalletRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise; - - /** - * Get wallet balance - * @param {string} walletAddress wallet address - * @param {*} [options] Override http request option. - * @throws {RequiredError} - * @memberof TronApiInterface - */ - getWalletBalancesRaw(requestParameters: GetWalletBalancesRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise>; - - /** - * Get wallet balance - */ - getWalletBalances(requestParameters: GetWalletBalancesRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise; - - /** - * Publish transaction - * @param {string} ownerAddress owner address - * @param {PublishTransactionRequest} publishTransactionRequest - * @param {*} [options] Override http request option. - * @throws {RequiredError} - * @memberof TronApiInterface - */ - publishTransactionRaw(requestParameters: PublishTransactionOperationRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise>; - - /** - * Publish transaction - */ - publishTransaction(requestParameters: PublishTransactionOperationRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise; - -} - -/** - * - */ -export class TronApi extends runtime.BaseAPI implements TronApiInterface { - - /** - * Get transaction estimate - */ - async getEstimationRaw(requestParameters: GetEstimationOperationRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { - if (requestParameters.ownerAddress === null || requestParameters.ownerAddress === undefined) { - throw new runtime.RequiredError('ownerAddress','Required parameter requestParameters.ownerAddress was null or undefined when calling getEstimation.'); - } - - if (requestParameters.getEstimationRequest === null || requestParameters.getEstimationRequest === undefined) { - throw new runtime.RequiredError('getEstimationRequest','Required parameter requestParameters.getEstimationRequest was null or undefined when calling getEstimation.'); - } - - const queryParameters: any = {}; - - const headerParameters: runtime.HTTPHeaders = {}; - - headerParameters['Content-Type'] = 'application/json'; - - const response = await this.request({ - path: `/api/v2/wallet/{owner_address}/estimate`.replace(`{${"owner_address"}}`, encodeURIComponent(String(requestParameters.ownerAddress))), - method: 'POST', - headers: headerParameters, - query: queryParameters, - body: GetEstimationRequestToJSON(requestParameters.getEstimationRequest), - }, initOverrides); - - return new runtime.JSONApiResponse(response, (jsonValue) => EstimatePayloadFromJSON(jsonValue)); - } - - /** - * Get transaction estimate - */ - async getEstimation(requestParameters: GetEstimationOperationRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { - const response = await this.getEstimationRaw(requestParameters, initOverrides); - return await response.value(); - } - - /** - * Get service configuration - */ - async getSettingsRaw(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { - const queryParameters: any = {}; - - const headerParameters: runtime.HTTPHeaders = {}; - - const response = await this.request({ - path: `/api/v2/settings`, - method: 'GET', - headers: headerParameters, - query: queryParameters, - }, initOverrides); - - return new runtime.JSONApiResponse(response, (jsonValue) => TronSettingsFromJSON(jsonValue)); - } - - /** - * Get service configuration - */ - async getSettings(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { - const response = await this.getSettingsRaw(initOverrides); - return await response.value(); - } - - /** - * Get transaction data - */ - async getTransactionRaw(requestParameters: GetTransactionRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { - if (requestParameters.transactionHash === null || requestParameters.transactionHash === undefined) { - throw new runtime.RequiredError('transactionHash','Required parameter requestParameters.transactionHash was null or undefined when calling getTransaction.'); - } - - const queryParameters: any = {}; - - const headerParameters: runtime.HTTPHeaders = {}; - - const response = await this.request({ - path: `/api/v2/transactions/{transaction_hash}`.replace(`{${"transaction_hash"}}`, encodeURIComponent(String(requestParameters.transactionHash))), - method: 'GET', - headers: headerParameters, - query: queryParameters, - }, initOverrides); - - return new runtime.JSONApiResponse(response, (jsonValue) => TronEventFromJSON(jsonValue)); - } - - /** - * Get transaction data - */ - async getTransaction(requestParameters: GetTransactionRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { - const response = await this.getTransactionRaw(requestParameters, initOverrides); - return await response.value(); - } - - /** - * Get list of transactions - */ - async getTransactionsRaw(requestParameters: GetTransactionsRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { - if (requestParameters.ownerAddress === null || requestParameters.ownerAddress === undefined) { - throw new runtime.RequiredError('ownerAddress','Required parameter requestParameters.ownerAddress was null or undefined when calling getTransactions.'); - } - - const queryParameters: any = {}; - - if (requestParameters.fingerprint !== undefined) { - queryParameters['fingerprint'] = requestParameters.fingerprint; - } - - if (requestParameters.limit !== undefined) { - queryParameters['limit'] = requestParameters.limit; - } - - if (requestParameters.offset !== undefined) { - queryParameters['offset'] = requestParameters.offset; - } - - if (requestParameters.minTimestamp !== undefined) { - queryParameters['min_timestamp'] = requestParameters.minTimestamp; - } - - if (requestParameters.maxTimestamp !== undefined) { - queryParameters['max_timestamp'] = requestParameters.maxTimestamp; - } - - const headerParameters: runtime.HTTPHeaders = {}; - - const response = await this.request({ - path: `/api/v2/wallet/{owner_address}/transactions`.replace(`{${"owner_address"}}`, encodeURIComponent(String(requestParameters.ownerAddress))), - method: 'GET', - headers: headerParameters, - query: queryParameters, - }, initOverrides); - - return new runtime.JSONApiResponse(response, (jsonValue) => TronEventsFromJSON(jsonValue)); - } - - /** - * Get list of transactions - */ - async getTransactions(requestParameters: GetTransactionsRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { - const response = await this.getTransactionsRaw(requestParameters, initOverrides); - return await response.value(); - } - - /** - * Get user wallet address data - */ - async getWalletRaw(requestParameters: GetWalletRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { - if (requestParameters.ownerAddress === null || requestParameters.ownerAddress === undefined) { - throw new runtime.RequiredError('ownerAddress','Required parameter requestParameters.ownerAddress was null or undefined when calling getWallet.'); - } - - const queryParameters: any = {}; - - const headerParameters: runtime.HTTPHeaders = {}; - - const response = await this.request({ - path: `/api/v2/wallet/{owner_address}`.replace(`{${"owner_address"}}`, encodeURIComponent(String(requestParameters.ownerAddress))), - method: 'GET', - headers: headerParameters, - query: queryParameters, - }, initOverrides); - - return new runtime.JSONApiResponse(response, (jsonValue) => TronWalletFromJSON(jsonValue)); - } - - /** - * Get user wallet address data - */ - async getWallet(requestParameters: GetWalletRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { - const response = await this.getWalletRaw(requestParameters, initOverrides); - return await response.value(); - } - - /** - * Get wallet balance - */ - async getWalletBalancesRaw(requestParameters: GetWalletBalancesRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { - if (requestParameters.walletAddress === null || requestParameters.walletAddress === undefined) { - throw new runtime.RequiredError('walletAddress','Required parameter requestParameters.walletAddress was null or undefined when calling getWalletBalances.'); - } - - const queryParameters: any = {}; - - const headerParameters: runtime.HTTPHeaders = {}; - - const response = await this.request({ - path: `/api/v2/balance/{wallet_address}`.replace(`{${"wallet_address"}}`, encodeURIComponent(String(requestParameters.walletAddress))), - method: 'GET', - headers: headerParameters, - query: queryParameters, - }, initOverrides); - - return new runtime.JSONApiResponse(response, (jsonValue) => TronBalancesFromJSON(jsonValue)); - } - - /** - * Get wallet balance - */ - async getWalletBalances(requestParameters: GetWalletBalancesRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { - const response = await this.getWalletBalancesRaw(requestParameters, initOverrides); - return await response.value(); - } - - /** - * Publish transaction - */ - async publishTransactionRaw(requestParameters: PublishTransactionOperationRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { - if (requestParameters.ownerAddress === null || requestParameters.ownerAddress === undefined) { - throw new runtime.RequiredError('ownerAddress','Required parameter requestParameters.ownerAddress was null or undefined when calling publishTransaction.'); - } - - if (requestParameters.publishTransactionRequest === null || requestParameters.publishTransactionRequest === undefined) { - throw new runtime.RequiredError('publishTransactionRequest','Required parameter requestParameters.publishTransactionRequest was null or undefined when calling publishTransaction.'); - } - - const queryParameters: any = {}; - - const headerParameters: runtime.HTTPHeaders = {}; - - headerParameters['Content-Type'] = 'application/json'; - - const response = await this.request({ - path: `/api/v2/wallet/{owner_address}/publish`.replace(`{${"owner_address"}}`, encodeURIComponent(String(requestParameters.ownerAddress))), - method: 'POST', - headers: headerParameters, - query: queryParameters, - body: PublishTransactionRequestToJSON(requestParameters.publishTransactionRequest), - }, initOverrides); - - return new runtime.JSONApiResponse(response, (jsonValue) => PublishPayloadFromJSON(jsonValue)); - } - - /** - * Publish transaction - */ - async publishTransaction(requestParameters: PublishTransactionOperationRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { - const response = await this.publishTransactionRaw(requestParameters, initOverrides); - return await response.value(); - } - -} diff --git a/packages/core/src/tronApi/apis/index.ts b/packages/core/src/tronApi/apis/index.ts deleted file mode 100644 index 5fdc9eb15..000000000 --- a/packages/core/src/tronApi/apis/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -/* tslint:disable */ -/* eslint-disable */ -export * from './TronApi'; diff --git a/packages/core/src/tronApi/index.ts b/packages/core/src/tronApi/index.ts index bebe8bbbe..e793ad203 100644 --- a/packages/core/src/tronApi/index.ts +++ b/packages/core/src/tronApi/index.ts @@ -1,5 +1,67 @@ -/* tslint:disable */ -/* eslint-disable */ -export * from './runtime'; -export * from './apis/index'; -export * from './models/index'; +import BigNumber from 'bignumber.js'; + +const removeTrailingSlash = (str: string) => str.replace(/\/$/, ''); + +export class TronApi { + private readonly baseURL: string; + + constructor(baseURL: string, private readonly apiKey?: string) { + this.baseURL = removeTrailingSlash(baseURL); + } + + async getBalances(address: string) { + const res = await ( + await fetch(`${this.baseURL}/v1/accounts/${address}`, { + headers: { + ...(this.apiKey && { + 'TRON-PRO-API-KEY': this.apiKey + }), + 'Content-Type': 'application/json' + } + }) + ).json(); + + if (!res?.success || !res?.data) { + throw new Error('Fetch tron balances failed'); + } + + const info = res?.data?.[0]; + if (!info) { + return { + trx: '0', + usdt: '0' + }; + } + + let trx = '0'; + let usdt = '0'; + if (info.balance !== undefined) { + const trxBalance = parseInt(info.balance); + if (isFinite(trxBalance)) { + trx = new BigNumber(trxBalance).toFixed(0); + } else { + throw new Error('Invalid tron balance'); + } + } + + if (info.trc20 && Array.isArray(info.trc20)) { + const usdtBalance = info.trc20.find( + (obj: Record) => 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t' in obj + )?.TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t; + + if (usdtBalance !== undefined) { + const parsed = parseInt(usdtBalance); + if (isFinite(parsed)) { + usdt = new BigNumber(parsed).toFixed(0); + } else { + throw new Error('Invalid tron usdt balance'); + } + } + } + + return { + trx, + usdt + }; + } +} diff --git a/packages/core/src/tronApi/models/ContractDeployAction.ts b/packages/core/src/tronApi/models/ContractDeployAction.ts deleted file mode 100644 index 844c45e9a..000000000 --- a/packages/core/src/tronApi/models/ContractDeployAction.ts +++ /dev/null @@ -1,66 +0,0 @@ -/* tslint:disable */ -/* eslint-disable */ -/** - * gasless TRON service REST api - * Service to publish TRON transactions - * - * The version of the OpenAPI document: 2.0.0 - * Contact: support@tonkeeper.com - * - * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). - * https://openapi-generator.tech - * Do not edit the class manually. - */ - -import { exists, mapValues } from '../runtime'; -/** - * - * @export - * @interface ContractDeployAction - */ -export interface ContractDeployAction { - /** - * - * @type {string} - * @memberof ContractDeployAction - */ - ownerAddress: string; -} - -/** - * Check if a given object implements the ContractDeployAction interface. - */ -export function instanceOfContractDeployAction(value: object): boolean { - let isInstance = true; - isInstance = isInstance && "ownerAddress" in value; - - return isInstance; -} - -export function ContractDeployActionFromJSON(json: any): ContractDeployAction { - return ContractDeployActionFromJSONTyped(json, false); -} - -export function ContractDeployActionFromJSONTyped(json: any, ignoreDiscriminator: boolean): ContractDeployAction { - if ((json === undefined) || (json === null)) { - return json; - } - return { - - 'ownerAddress': json['ownerAddress'], - }; -} - -export function ContractDeployActionToJSON(value?: ContractDeployAction | null): any { - if (value === undefined) { - return undefined; - } - if (value === null) { - return null; - } - return { - - 'ownerAddress': value.ownerAddress, - }; -} - diff --git a/packages/core/src/tronApi/models/EstimatePayload.ts b/packages/core/src/tronApi/models/EstimatePayload.ts deleted file mode 100644 index 6a5f31d3f..000000000 --- a/packages/core/src/tronApi/models/EstimatePayload.ts +++ /dev/null @@ -1,91 +0,0 @@ -/* tslint:disable */ -/* eslint-disable */ -/** - * gasless TRON service REST api - * Service to publish TRON transactions - * - * The version of the OpenAPI document: 2.0.0 - * Contact: support@tonkeeper.com - * - * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). - * https://openapi-generator.tech - * Do not edit the class manually. - */ - -import { exists, mapValues } from '../runtime'; -import type { RequestData } from './RequestData'; -import { - RequestDataFromJSON, - RequestDataFromJSONTyped, - RequestDataToJSON, -} from './RequestData'; - -/** - * - * @export - * @interface EstimatePayload - */ -export interface EstimatePayload { - /** - * - * @type {string} - * @memberof EstimatePayload - */ - hash: string; - /** - * - * @type {RequestData} - * @memberof EstimatePayload - */ - request: RequestData; - /** - * - * @type {Array} - * @memberof EstimatePayload - */ - internalMsgs: Array; -} - -/** - * Check if a given object implements the EstimatePayload interface. - */ -export function instanceOfEstimatePayload(value: object): boolean { - let isInstance = true; - isInstance = isInstance && "hash" in value; - isInstance = isInstance && "request" in value; - isInstance = isInstance && "internalMsgs" in value; - - return isInstance; -} - -export function EstimatePayloadFromJSON(json: any): EstimatePayload { - return EstimatePayloadFromJSONTyped(json, false); -} - -export function EstimatePayloadFromJSONTyped(json: any, ignoreDiscriminator: boolean): EstimatePayload { - if ((json === undefined) || (json === null)) { - return json; - } - return { - - 'hash': json['hash'], - 'request': RequestDataFromJSON(json['request']), - 'internalMsgs': json['internalMsgs'], - }; -} - -export function EstimatePayloadToJSON(value?: EstimatePayload | null): any { - if (value === undefined) { - return undefined; - } - if (value === null) { - return null; - } - return { - - 'hash': value.hash, - 'request': RequestDataToJSON(value.request), - 'internalMsgs': value.internalMsgs, - }; -} - diff --git a/packages/core/src/tronApi/models/FromMessage.ts b/packages/core/src/tronApi/models/FromMessage.ts deleted file mode 100644 index 803f67a46..000000000 --- a/packages/core/src/tronApi/models/FromMessage.ts +++ /dev/null @@ -1,83 +0,0 @@ -/* tslint:disable */ -/* eslint-disable */ -/** - * gasless TRON service REST api - * Service to publish TRON transactions - * - * The version of the OpenAPI document: 2.0.0 - * Contact: support@tonkeeper.com - * - * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). - * https://openapi-generator.tech - * Do not edit the class manually. - */ - -import { exists, mapValues } from '../runtime'; -/** - * - * @export - * @interface FromMessage - */ -export interface FromMessage { - /** - * - * @type {string} - * @memberof FromMessage - */ - to: string; - /** - * - * @type {string} - * @memberof FromMessage - */ - value: string; - /** - * - * @type {string} - * @memberof FromMessage - */ - assetAddress?: string; -} - -/** - * Check if a given object implements the FromMessage interface. - */ -export function instanceOfFromMessage(value: object): boolean { - let isInstance = true; - isInstance = isInstance && "to" in value; - isInstance = isInstance && "value" in value; - - return isInstance; -} - -export function FromMessageFromJSON(json: any): FromMessage { - return FromMessageFromJSONTyped(json, false); -} - -export function FromMessageFromJSONTyped(json: any, ignoreDiscriminator: boolean): FromMessage { - if ((json === undefined) || (json === null)) { - return json; - } - return { - - 'to': json['to'], - 'value': json['value'], - 'assetAddress': !exists(json, 'assetAddress') ? undefined : json['assetAddress'], - }; -} - -export function FromMessageToJSON(value?: FromMessage | null): any { - if (value === undefined) { - return undefined; - } - if (value === null) { - return null; - } - return { - - 'to': value.to, - 'value': value.value, - 'assetAddress': value.assetAddress, - }; -} - diff --git a/packages/core/src/tronApi/models/GetEstimationRequest.ts b/packages/core/src/tronApi/models/GetEstimationRequest.ts deleted file mode 100644 index 9100b659b..000000000 --- a/packages/core/src/tronApi/models/GetEstimationRequest.ts +++ /dev/null @@ -1,82 +0,0 @@ -/* tslint:disable */ -/* eslint-disable */ -/** - * gasless TRON service REST api - * Service to publish TRON transactions - * - * The version of the OpenAPI document: 2.0.0 - * Contact: support@tonkeeper.com - * - * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). - * https://openapi-generator.tech - * Do not edit the class manually. - */ - -import { exists, mapValues } from '../runtime'; -import type { FromMessage } from './FromMessage'; -import { - FromMessageFromJSON, - FromMessageFromJSONTyped, - FromMessageToJSON, -} from './FromMessage'; - -/** - * - * @export - * @interface GetEstimationRequest - */ -export interface GetEstimationRequest { - /** - * - * @type {number} - * @memberof GetEstimationRequest - */ - lifeTime: number; - /** - * - * @type {Array} - * @memberof GetEstimationRequest - */ - messages: Array; -} - -/** - * Check if a given object implements the GetEstimationRequest interface. - */ -export function instanceOfGetEstimationRequest(value: object): boolean { - let isInstance = true; - isInstance = isInstance && "lifeTime" in value; - isInstance = isInstance && "messages" in value; - - return isInstance; -} - -export function GetEstimationRequestFromJSON(json: any): GetEstimationRequest { - return GetEstimationRequestFromJSONTyped(json, false); -} - -export function GetEstimationRequestFromJSONTyped(json: any, ignoreDiscriminator: boolean): GetEstimationRequest { - if ((json === undefined) || (json === null)) { - return json; - } - return { - - 'lifeTime': json['lifeTime'], - 'messages': ((json['messages'] as Array).map(FromMessageFromJSON)), - }; -} - -export function GetEstimationRequestToJSON(value?: GetEstimationRequest | null): any { - if (value === undefined) { - return undefined; - } - if (value === null) { - return null; - } - return { - - 'lifeTime': value.lifeTime, - 'messages': ((value.messages as Array).map(FromMessageToJSON)), - }; -} - diff --git a/packages/core/src/tronApi/models/GetSettings401Response.ts b/packages/core/src/tronApi/models/GetSettings401Response.ts deleted file mode 100644 index f80de81ee..000000000 --- a/packages/core/src/tronApi/models/GetSettings401Response.ts +++ /dev/null @@ -1,66 +0,0 @@ -/* tslint:disable */ -/* eslint-disable */ -/** - * gasless TRON service REST api - * Service to publish TRON transactions - * - * The version of the OpenAPI document: 2.0.0 - * Contact: support@tonkeeper.com - * - * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). - * https://openapi-generator.tech - * Do not edit the class manually. - */ - -import { exists, mapValues } from '../runtime'; -/** - * - * @export - * @interface GetSettings401Response - */ -export interface GetSettings401Response { - /** - * - * @type {string} - * @memberof GetSettings401Response - */ - error: string; -} - -/** - * Check if a given object implements the GetSettings401Response interface. - */ -export function instanceOfGetSettings401Response(value: object): boolean { - let isInstance = true; - isInstance = isInstance && "error" in value; - - return isInstance; -} - -export function GetSettings401ResponseFromJSON(json: any): GetSettings401Response { - return GetSettings401ResponseFromJSONTyped(json, false); -} - -export function GetSettings401ResponseFromJSONTyped(json: any, ignoreDiscriminator: boolean): GetSettings401Response { - if ((json === undefined) || (json === null)) { - return json; - } - return { - - 'error': json['error'], - }; -} - -export function GetSettings401ResponseToJSON(value?: GetSettings401Response | null): any { - if (value === undefined) { - return undefined; - } - if (value === null) { - return null; - } - return { - - 'error': value.error, - }; -} - diff --git a/packages/core/src/tronApi/models/ModelError.ts b/packages/core/src/tronApi/models/ModelError.ts deleted file mode 100644 index 7d8f981c3..000000000 --- a/packages/core/src/tronApi/models/ModelError.ts +++ /dev/null @@ -1,66 +0,0 @@ -/* tslint:disable */ -/* eslint-disable */ -/** - * gasless TRON service REST api - * Service to publish TRON transactions - * - * The version of the OpenAPI document: 2.0.0 - * Contact: support@tonkeeper.com - * - * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). - * https://openapi-generator.tech - * Do not edit the class manually. - */ - -import { exists, mapValues } from '../runtime'; -/** - * - * @export - * @interface ModelError - */ -export interface ModelError { - /** - * - * @type {string} - * @memberof ModelError - */ - error: string; -} - -/** - * Check if a given object implements the ModelError interface. - */ -export function instanceOfModelError(value: object): boolean { - let isInstance = true; - isInstance = isInstance && "error" in value; - - return isInstance; -} - -export function ModelErrorFromJSON(json: any): ModelError { - return ModelErrorFromJSONTyped(json, false); -} - -export function ModelErrorFromJSONTyped(json: any, ignoreDiscriminator: boolean): ModelError { - if ((json === undefined) || (json === null)) { - return json; - } - return { - - 'error': json['error'], - }; -} - -export function ModelErrorToJSON(value?: ModelError | null): any { - if (value === undefined) { - return undefined; - } - if (value === null) { - return null; - } - return { - - 'error': value.error, - }; -} - diff --git a/packages/core/src/tronApi/models/PublishPayload.ts b/packages/core/src/tronApi/models/PublishPayload.ts deleted file mode 100644 index 7fb41c856..000000000 --- a/packages/core/src/tronApi/models/PublishPayload.ts +++ /dev/null @@ -1,66 +0,0 @@ -/* tslint:disable */ -/* eslint-disable */ -/** - * gasless TRON service REST api - * Service to publish TRON transactions - * - * The version of the OpenAPI document: 2.0.0 - * Contact: support@tonkeeper.com - * - * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). - * https://openapi-generator.tech - * Do not edit the class manually. - */ - -import { exists, mapValues } from '../runtime'; -/** - * - * @export - * @interface PublishPayload - */ -export interface PublishPayload { - /** - * - * @type {string} - * @memberof PublishPayload - */ - transactionHash: string; -} - -/** - * Check if a given object implements the PublishPayload interface. - */ -export function instanceOfPublishPayload(value: object): boolean { - let isInstance = true; - isInstance = isInstance && "transactionHash" in value; - - return isInstance; -} - -export function PublishPayloadFromJSON(json: any): PublishPayload { - return PublishPayloadFromJSONTyped(json, false); -} - -export function PublishPayloadFromJSONTyped(json: any, ignoreDiscriminator: boolean): PublishPayload { - if ((json === undefined) || (json === null)) { - return json; - } - return { - - 'transactionHash': json['transactionHash'], - }; -} - -export function PublishPayloadToJSON(value?: PublishPayload | null): any { - if (value === undefined) { - return undefined; - } - if (value === null) { - return null; - } - return { - - 'transactionHash': value.transactionHash, - }; -} - diff --git a/packages/core/src/tronApi/models/PublishTransactionRequest.ts b/packages/core/src/tronApi/models/PublishTransactionRequest.ts deleted file mode 100644 index ae05f171a..000000000 --- a/packages/core/src/tronApi/models/PublishTransactionRequest.ts +++ /dev/null @@ -1,91 +0,0 @@ -/* tslint:disable */ -/* eslint-disable */ -/** - * gasless TRON service REST api - * Service to publish TRON transactions - * - * The version of the OpenAPI document: 2.0.0 - * Contact: support@tonkeeper.com - * - * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). - * https://openapi-generator.tech - * Do not edit the class manually. - */ - -import { exists, mapValues } from '../runtime'; -import type { RequestData } from './RequestData'; -import { - RequestDataFromJSON, - RequestDataFromJSONTyped, - RequestDataToJSON, -} from './RequestData'; - -/** - * - * @export - * @interface PublishTransactionRequest - */ -export interface PublishTransactionRequest { - /** - * - * @type {RequestData} - * @memberof PublishTransactionRequest - */ - request: RequestData; - /** - * - * @type {string} - * @memberof PublishTransactionRequest - */ - hash: string; - /** - * - * @type {string} - * @memberof PublishTransactionRequest - */ - signature: string; -} - -/** - * Check if a given object implements the PublishTransactionRequest interface. - */ -export function instanceOfPublishTransactionRequest(value: object): boolean { - let isInstance = true; - isInstance = isInstance && "request" in value; - isInstance = isInstance && "hash" in value; - isInstance = isInstance && "signature" in value; - - return isInstance; -} - -export function PublishTransactionRequestFromJSON(json: any): PublishTransactionRequest { - return PublishTransactionRequestFromJSONTyped(json, false); -} - -export function PublishTransactionRequestFromJSONTyped(json: any, ignoreDiscriminator: boolean): PublishTransactionRequest { - if ((json === undefined) || (json === null)) { - return json; - } - return { - - 'request': RequestDataFromJSON(json['request']), - 'hash': json['hash'], - 'signature': json['signature'], - }; -} - -export function PublishTransactionRequestToJSON(value?: PublishTransactionRequest | null): any { - if (value === undefined) { - return undefined; - } - if (value === null) { - return null; - } - return { - - 'request': RequestDataToJSON(value.request), - 'hash': value.hash, - 'signature': value.signature, - }; -} - diff --git a/packages/core/src/tronApi/models/ReceiveTRC20Action.ts b/packages/core/src/tronApi/models/ReceiveTRC20Action.ts deleted file mode 100644 index e491858c5..000000000 --- a/packages/core/src/tronApi/models/ReceiveTRC20Action.ts +++ /dev/null @@ -1,91 +0,0 @@ -/* tslint:disable */ -/* eslint-disable */ -/** - * gasless TRON service REST api - * Service to publish TRON transactions - * - * The version of the OpenAPI document: 2.0.0 - * Contact: support@tonkeeper.com - * - * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). - * https://openapi-generator.tech - * Do not edit the class manually. - */ - -import { exists, mapValues } from '../runtime'; -import type { TronToken } from './TronToken'; -import { - TronTokenFromJSON, - TronTokenFromJSONTyped, - TronTokenToJSON, -} from './TronToken'; - -/** - * - * @export - * @interface ReceiveTRC20Action - */ -export interface ReceiveTRC20Action { - /** - * - * @type {string} - * @memberof ReceiveTRC20Action - */ - sender: string; - /** - * amount in quanta of tokens - * @type {string} - * @memberof ReceiveTRC20Action - */ - amount: string; - /** - * - * @type {TronToken} - * @memberof ReceiveTRC20Action - */ - token: TronToken; -} - -/** - * Check if a given object implements the ReceiveTRC20Action interface. - */ -export function instanceOfReceiveTRC20Action(value: object): boolean { - let isInstance = true; - isInstance = isInstance && "sender" in value; - isInstance = isInstance && "amount" in value; - isInstance = isInstance && "token" in value; - - return isInstance; -} - -export function ReceiveTRC20ActionFromJSON(json: any): ReceiveTRC20Action { - return ReceiveTRC20ActionFromJSONTyped(json, false); -} - -export function ReceiveTRC20ActionFromJSONTyped(json: any, ignoreDiscriminator: boolean): ReceiveTRC20Action { - if ((json === undefined) || (json === null)) { - return json; - } - return { - - 'sender': json['sender'], - 'amount': json['amount'], - 'token': TronTokenFromJSON(json['token']), - }; -} - -export function ReceiveTRC20ActionToJSON(value?: ReceiveTRC20Action | null): any { - if (value === undefined) { - return undefined; - } - if (value === null) { - return null; - } - return { - - 'sender': value.sender, - 'amount': value.amount, - 'token': TronTokenToJSON(value.token), - }; -} - diff --git a/packages/core/src/tronApi/models/RequestData.ts b/packages/core/src/tronApi/models/RequestData.ts deleted file mode 100644 index 958a746fc..000000000 --- a/packages/core/src/tronApi/models/RequestData.ts +++ /dev/null @@ -1,118 +0,0 @@ -/* tslint:disable */ -/* eslint-disable */ -/** - * gasless TRON service REST api - * Service to publish TRON transactions - * - * The version of the OpenAPI document: 2.0.0 - * Contact: support@tonkeeper.com - * - * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). - * https://openapi-generator.tech - * Do not edit the class manually. - */ - -import { exists, mapValues } from '../runtime'; -import type { RequestMessage } from './RequestMessage'; -import { - RequestMessageFromJSON, - RequestMessageFromJSONTyped, - RequestMessageToJSON, -} from './RequestMessage'; - -/** - * - * @export - * @interface RequestData - */ -export interface RequestData { - /** - * - * @type {string} - * @memberof RequestData - */ - fee: string; - /** - * - * @type {string} - * @memberof RequestData - */ - feeToken: string; - /** - * - * @type {string} - * @memberof RequestData - */ - feeReceiver: string; - /** - * - * @type {number} - * @memberof RequestData - */ - deadline: number; - /** - * - * @type {number} - * @memberof RequestData - */ - nonce: number; - /** - * - * @type {Array} - * @memberof RequestData - */ - messages: Array; -} - -/** - * Check if a given object implements the RequestData interface. - */ -export function instanceOfRequestData(value: object): boolean { - let isInstance = true; - isInstance = isInstance && "fee" in value; - isInstance = isInstance && "feeToken" in value; - isInstance = isInstance && "feeReceiver" in value; - isInstance = isInstance && "deadline" in value; - isInstance = isInstance && "nonce" in value; - isInstance = isInstance && "messages" in value; - - return isInstance; -} - -export function RequestDataFromJSON(json: any): RequestData { - return RequestDataFromJSONTyped(json, false); -} - -export function RequestDataFromJSONTyped(json: any, ignoreDiscriminator: boolean): RequestData { - if ((json === undefined) || (json === null)) { - return json; - } - return { - - 'fee': json['fee'], - 'feeToken': json['feeToken'], - 'feeReceiver': json['feeReceiver'], - 'deadline': json['deadline'], - 'nonce': json['nonce'], - 'messages': ((json['messages'] as Array).map(RequestMessageFromJSON)), - }; -} - -export function RequestDataToJSON(value?: RequestData | null): any { - if (value === undefined) { - return undefined; - } - if (value === null) { - return null; - } - return { - - 'fee': value.fee, - 'feeToken': value.feeToken, - 'feeReceiver': value.feeReceiver, - 'deadline': value.deadline, - 'nonce': value.nonce, - 'messages': ((value.messages as Array).map(RequestMessageToJSON)), - }; -} - diff --git a/packages/core/src/tronApi/models/RequestMessage.ts b/packages/core/src/tronApi/models/RequestMessage.ts deleted file mode 100644 index bde29ca00..000000000 --- a/packages/core/src/tronApi/models/RequestMessage.ts +++ /dev/null @@ -1,84 +0,0 @@ -/* tslint:disable */ -/* eslint-disable */ -/** - * gasless TRON service REST api - * Service to publish TRON transactions - * - * The version of the OpenAPI document: 2.0.0 - * Contact: support@tonkeeper.com - * - * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). - * https://openapi-generator.tech - * Do not edit the class manually. - */ - -import { exists, mapValues } from '../runtime'; -/** - * - * @export - * @interface RequestMessage - */ -export interface RequestMessage { - /** - * - * @type {string} - * @memberof RequestMessage - */ - to: string; - /** - * - * @type {string} - * @memberof RequestMessage - */ - value: string; - /** - * - * @type {string} - * @memberof RequestMessage - */ - data: string; -} - -/** - * Check if a given object implements the RequestMessage interface. - */ -export function instanceOfRequestMessage(value: object): boolean { - let isInstance = true; - isInstance = isInstance && "to" in value; - isInstance = isInstance && "value" in value; - isInstance = isInstance && "data" in value; - - return isInstance; -} - -export function RequestMessageFromJSON(json: any): RequestMessage { - return RequestMessageFromJSONTyped(json, false); -} - -export function RequestMessageFromJSONTyped(json: any, ignoreDiscriminator: boolean): RequestMessage { - if ((json === undefined) || (json === null)) { - return json; - } - return { - - 'to': json['to'], - 'value': json['value'], - 'data': json['data'], - }; -} - -export function RequestMessageToJSON(value?: RequestMessage | null): any { - if (value === undefined) { - return undefined; - } - if (value === null) { - return null; - } - return { - - 'to': value.to, - 'value': value.value, - 'data': value.data, - }; -} - diff --git a/packages/core/src/tronApi/models/SendTRC20Action.ts b/packages/core/src/tronApi/models/SendTRC20Action.ts deleted file mode 100644 index 05ab6c0d1..000000000 --- a/packages/core/src/tronApi/models/SendTRC20Action.ts +++ /dev/null @@ -1,91 +0,0 @@ -/* tslint:disable */ -/* eslint-disable */ -/** - * gasless TRON service REST api - * Service to publish TRON transactions - * - * The version of the OpenAPI document: 2.0.0 - * Contact: support@tonkeeper.com - * - * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). - * https://openapi-generator.tech - * Do not edit the class manually. - */ - -import { exists, mapValues } from '../runtime'; -import type { TronToken } from './TronToken'; -import { - TronTokenFromJSON, - TronTokenFromJSONTyped, - TronTokenToJSON, -} from './TronToken'; - -/** - * - * @export - * @interface SendTRC20Action - */ -export interface SendTRC20Action { - /** - * - * @type {string} - * @memberof SendTRC20Action - */ - recipient: string; - /** - * amount in quanta of tokens - * @type {string} - * @memberof SendTRC20Action - */ - amount: string; - /** - * - * @type {TronToken} - * @memberof SendTRC20Action - */ - token: TronToken; -} - -/** - * Check if a given object implements the SendTRC20Action interface. - */ -export function instanceOfSendTRC20Action(value: object): boolean { - let isInstance = true; - isInstance = isInstance && "recipient" in value; - isInstance = isInstance && "amount" in value; - isInstance = isInstance && "token" in value; - - return isInstance; -} - -export function SendTRC20ActionFromJSON(json: any): SendTRC20Action { - return SendTRC20ActionFromJSONTyped(json, false); -} - -export function SendTRC20ActionFromJSONTyped(json: any, ignoreDiscriminator: boolean): SendTRC20Action { - if ((json === undefined) || (json === null)) { - return json; - } - return { - - 'recipient': json['recipient'], - 'amount': json['amount'], - 'token': TronTokenFromJSON(json['token']), - }; -} - -export function SendTRC20ActionToJSON(value?: SendTRC20Action | null): any { - if (value === undefined) { - return undefined; - } - if (value === null) { - return null; - } - return { - - 'recipient': value.recipient, - 'amount': value.amount, - 'token': TronTokenToJSON(value.token), - }; -} - diff --git a/packages/core/src/tronApi/models/TronAction.ts b/packages/core/src/tronApi/models/TronAction.ts deleted file mode 100644 index ced8e8d55..000000000 --- a/packages/core/src/tronApi/models/TronAction.ts +++ /dev/null @@ -1,140 +0,0 @@ -/* tslint:disable */ -/* eslint-disable */ -/** - * gasless TRON service REST api - * Service to publish TRON transactions - * - * The version of the OpenAPI document: 2.0.0 - * Contact: support@tonkeeper.com - * - * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). - * https://openapi-generator.tech - * Do not edit the class manually. - */ - -import { exists, mapValues } from '../runtime'; -import type { ContractDeployAction } from './ContractDeployAction'; -import { - ContractDeployActionFromJSON, - ContractDeployActionFromJSONTyped, - ContractDeployActionToJSON, -} from './ContractDeployAction'; -import type { ReceiveTRC20Action } from './ReceiveTRC20Action'; -import { - ReceiveTRC20ActionFromJSON, - ReceiveTRC20ActionFromJSONTyped, - ReceiveTRC20ActionToJSON, -} from './ReceiveTRC20Action'; -import type { SendTRC20Action } from './SendTRC20Action'; -import { - SendTRC20ActionFromJSON, - SendTRC20ActionFromJSONTyped, - SendTRC20ActionToJSON, -} from './SendTRC20Action'; - -/** - * - * @export - * @interface TronAction - */ -export interface TronAction { - /** - * - * @type {string} - * @memberof TronAction - */ - type: TronActionTypeEnum; - /** - * - * @type {string} - * @memberof TronAction - */ - status: TronActionStatusEnum; - /** - * - * @type {ReceiveTRC20Action} - * @memberof TronAction - */ - receiveTRC20?: ReceiveTRC20Action; - /** - * - * @type {SendTRC20Action} - * @memberof TronAction - */ - sendTRC20?: SendTRC20Action; - /** - * - * @type {ContractDeployAction} - * @memberof TronAction - */ - contractDeploy?: ContractDeployAction; -} - - -/** - * @export - */ -export const TronActionTypeEnum = { - ReceiveTrc20: 'ReceiveTRC20', - SendTrc20: 'SendTRC20', - ContractDeploy: 'ContractDeploy' -} as const; -export type TronActionTypeEnum = typeof TronActionTypeEnum[keyof typeof TronActionTypeEnum]; - -/** - * @export - */ -export const TronActionStatusEnum = { - Ok: 'ok', - Failed: 'failed', - Pending: 'pending' -} as const; -export type TronActionStatusEnum = typeof TronActionStatusEnum[keyof typeof TronActionStatusEnum]; - - -/** - * Check if a given object implements the TronAction interface. - */ -export function instanceOfTronAction(value: object): boolean { - let isInstance = true; - isInstance = isInstance && "type" in value; - isInstance = isInstance && "status" in value; - - return isInstance; -} - -export function TronActionFromJSON(json: any): TronAction { - return TronActionFromJSONTyped(json, false); -} - -export function TronActionFromJSONTyped(json: any, ignoreDiscriminator: boolean): TronAction { - if ((json === undefined) || (json === null)) { - return json; - } - return { - - 'type': json['type'], - 'status': json['status'], - 'receiveTRC20': !exists(json, 'receiveTRC20') ? undefined : ReceiveTRC20ActionFromJSON(json['receiveTRC20']), - 'sendTRC20': !exists(json, 'sendTRC20') ? undefined : SendTRC20ActionFromJSON(json['sendTRC20']), - 'contractDeploy': !exists(json, 'contractDeploy') ? undefined : ContractDeployActionFromJSON(json['contractDeploy']), - }; -} - -export function TronActionToJSON(value?: TronAction | null): any { - if (value === undefined) { - return undefined; - } - if (value === null) { - return null; - } - return { - - 'type': value.type, - 'status': value.status, - 'receiveTRC20': ReceiveTRC20ActionToJSON(value.receiveTRC20), - 'sendTRC20': SendTRC20ActionToJSON(value.sendTRC20), - 'contractDeploy': ContractDeployActionToJSON(value.contractDeploy), - }; -} - diff --git a/packages/core/src/tronApi/models/TronBalance.ts b/packages/core/src/tronApi/models/TronBalance.ts deleted file mode 100644 index 25fc2ad2c..000000000 --- a/packages/core/src/tronApi/models/TronBalance.ts +++ /dev/null @@ -1,82 +0,0 @@ -/* tslint:disable */ -/* eslint-disable */ -/** - * gasless TRON service REST api - * Service to publish TRON transactions - * - * The version of the OpenAPI document: 2.0.0 - * Contact: support@tonkeeper.com - * - * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). - * https://openapi-generator.tech - * Do not edit the class manually. - */ - -import { exists, mapValues } from '../runtime'; -import type { TronToken } from './TronToken'; -import { - TronTokenFromJSON, - TronTokenFromJSONTyped, - TronTokenToJSON, -} from './TronToken'; - -/** - * - * @export - * @interface TronBalance - */ -export interface TronBalance { - /** - * - * @type {TronToken} - * @memberof TronBalance - */ - token: TronToken; - /** - * - * @type {string} - * @memberof TronBalance - */ - weiAmount: string; -} - -/** - * Check if a given object implements the TronBalance interface. - */ -export function instanceOfTronBalance(value: object): boolean { - let isInstance = true; - isInstance = isInstance && "token" in value; - isInstance = isInstance && "weiAmount" in value; - - return isInstance; -} - -export function TronBalanceFromJSON(json: any): TronBalance { - return TronBalanceFromJSONTyped(json, false); -} - -export function TronBalanceFromJSONTyped(json: any, ignoreDiscriminator: boolean): TronBalance { - if ((json === undefined) || (json === null)) { - return json; - } - return { - - 'token': TronTokenFromJSON(json['token']), - 'weiAmount': json['weiAmount'], - }; -} - -export function TronBalanceToJSON(value?: TronBalance | null): any { - if (value === undefined) { - return undefined; - } - if (value === null) { - return null; - } - return { - - 'token': TronTokenToJSON(value.token), - 'weiAmount': value.weiAmount, - }; -} - diff --git a/packages/core/src/tronApi/models/TronBalances.ts b/packages/core/src/tronApi/models/TronBalances.ts deleted file mode 100644 index 4c9a03cee..000000000 --- a/packages/core/src/tronApi/models/TronBalances.ts +++ /dev/null @@ -1,73 +0,0 @@ -/* tslint:disable */ -/* eslint-disable */ -/** - * gasless TRON service REST api - * Service to publish TRON transactions - * - * The version of the OpenAPI document: 2.0.0 - * Contact: support@tonkeeper.com - * - * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). - * https://openapi-generator.tech - * Do not edit the class manually. - */ - -import { exists, mapValues } from '../runtime'; -import type { TronBalance } from './TronBalance'; -import { - TronBalanceFromJSON, - TronBalanceFromJSONTyped, - TronBalanceToJSON, -} from './TronBalance'; - -/** - * - * @export - * @interface TronBalances - */ -export interface TronBalances { - /** - * - * @type {Array} - * @memberof TronBalances - */ - balances: Array; -} - -/** - * Check if a given object implements the TronBalances interface. - */ -export function instanceOfTronBalances(value: object): boolean { - let isInstance = true; - isInstance = isInstance && "balances" in value; - - return isInstance; -} - -export function TronBalancesFromJSON(json: any): TronBalances { - return TronBalancesFromJSONTyped(json, false); -} - -export function TronBalancesFromJSONTyped(json: any, ignoreDiscriminator: boolean): TronBalances { - if ((json === undefined) || (json === null)) { - return json; - } - return { - - 'balances': ((json['balances'] as Array).map(TronBalanceFromJSON)), - }; -} - -export function TronBalancesToJSON(value?: TronBalances | null): any { - if (value === undefined) { - return undefined; - } - if (value === null) { - return null; - } - return { - - 'balances': ((value.balances as Array).map(TronBalanceToJSON)), - }; -} - diff --git a/packages/core/src/tronApi/models/TronEvent.ts b/packages/core/src/tronApi/models/TronEvent.ts deleted file mode 100644 index 2b7edd718..000000000 --- a/packages/core/src/tronApi/models/TronEvent.ts +++ /dev/null @@ -1,114 +0,0 @@ -/* tslint:disable */ -/* eslint-disable */ -/** - * gasless TRON service REST api - * Service to publish TRON transactions - * - * The version of the OpenAPI document: 2.0.0 - * Contact: support@tonkeeper.com - * - * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). - * https://openapi-generator.tech - * Do not edit the class manually. - */ - -import { exists, mapValues } from '../runtime'; -import type { TronAction } from './TronAction'; -import { - TronActionFromJSON, - TronActionFromJSONTyped, - TronActionToJSON, -} from './TronAction'; -import type { TronFee } from './TronFee'; -import { - TronFeeFromJSON, - TronFeeFromJSONTyped, - TronFeeToJSON, -} from './TronFee'; - -/** - * - * @export - * @interface TronEvent - */ -export interface TronEvent { - /** - * - * @type {string} - * @memberof TronEvent - */ - txHash: string; - /** - * - * @type {number} - * @memberof TronEvent - */ - timestamp: number; - /** - * - * @type {Array} - * @memberof TronEvent - */ - actions: Array; - /** - * - * @type {TronFee} - * @memberof TronEvent - */ - fees?: TronFee; - /** - * Event is not finished yet. Transactions still happening - * @type {boolean} - * @memberof TronEvent - */ - inProgress: boolean; -} - -/** - * Check if a given object implements the TronEvent interface. - */ -export function instanceOfTronEvent(value: object): boolean { - let isInstance = true; - isInstance = isInstance && "txHash" in value; - isInstance = isInstance && "timestamp" in value; - isInstance = isInstance && "actions" in value; - isInstance = isInstance && "inProgress" in value; - - return isInstance; -} - -export function TronEventFromJSON(json: any): TronEvent { - return TronEventFromJSONTyped(json, false); -} - -export function TronEventFromJSONTyped(json: any, ignoreDiscriminator: boolean): TronEvent { - if ((json === undefined) || (json === null)) { - return json; - } - return { - - 'txHash': json['txHash'], - 'timestamp': json['timestamp'], - 'actions': ((json['actions'] as Array).map(TronActionFromJSON)), - 'fees': !exists(json, 'fees') ? undefined : TronFeeFromJSON(json['fees']), - 'inProgress': json['inProgress'], - }; -} - -export function TronEventToJSON(value?: TronEvent | null): any { - if (value === undefined) { - return undefined; - } - if (value === null) { - return null; - } - return { - - 'txHash': value.txHash, - 'timestamp': value.timestamp, - 'actions': ((value.actions as Array).map(TronActionToJSON)), - 'fees': TronFeeToJSON(value.fees), - 'inProgress': value.inProgress, - }; -} - diff --git a/packages/core/src/tronApi/models/TronEvents.ts b/packages/core/src/tronApi/models/TronEvents.ts deleted file mode 100644 index 51f073096..000000000 --- a/packages/core/src/tronApi/models/TronEvents.ts +++ /dev/null @@ -1,81 +0,0 @@ -/* tslint:disable */ -/* eslint-disable */ -/** - * gasless TRON service REST api - * Service to publish TRON transactions - * - * The version of the OpenAPI document: 2.0.0 - * Contact: support@tonkeeper.com - * - * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). - * https://openapi-generator.tech - * Do not edit the class manually. - */ - -import { exists, mapValues } from '../runtime'; -import type { TronEvent } from './TronEvent'; -import { - TronEventFromJSON, - TronEventFromJSONTyped, - TronEventToJSON, -} from './TronEvent'; - -/** - * - * @export - * @interface TronEvents - */ -export interface TronEvents { - /** - * - * @type {Array} - * @memberof TronEvents - */ - events: Array; - /** - * - * @type {string} - * @memberof TronEvents - */ - fingerprint?: string; -} - -/** - * Check if a given object implements the TronEvents interface. - */ -export function instanceOfTronEvents(value: object): boolean { - let isInstance = true; - isInstance = isInstance && "events" in value; - - return isInstance; -} - -export function TronEventsFromJSON(json: any): TronEvents { - return TronEventsFromJSONTyped(json, false); -} - -export function TronEventsFromJSONTyped(json: any, ignoreDiscriminator: boolean): TronEvents { - if ((json === undefined) || (json === null)) { - return json; - } - return { - - 'events': ((json['events'] as Array).map(TronEventFromJSON)), - 'fingerprint': !exists(json, 'fingerprint') ? undefined : json['fingerprint'], - }; -} - -export function TronEventsToJSON(value?: TronEvents | null): any { - if (value === undefined) { - return undefined; - } - if (value === null) { - return null; - } - return { - - 'events': ((value.events as Array).map(TronEventToJSON)), - 'fingerprint': value.fingerprint, - }; -} - diff --git a/packages/core/src/tronApi/models/TronFee.ts b/packages/core/src/tronApi/models/TronFee.ts deleted file mode 100644 index 4194c32ca..000000000 --- a/packages/core/src/tronApi/models/TronFee.ts +++ /dev/null @@ -1,82 +0,0 @@ -/* tslint:disable */ -/* eslint-disable */ -/** - * gasless TRON service REST api - * Service to publish TRON transactions - * - * The version of the OpenAPI document: 2.0.0 - * Contact: support@tonkeeper.com - * - * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). - * https://openapi-generator.tech - * Do not edit the class manually. - */ - -import { exists, mapValues } from '../runtime'; -import type { TronToken } from './TronToken'; -import { - TronTokenFromJSON, - TronTokenFromJSONTyped, - TronTokenToJSON, -} from './TronToken'; - -/** - * - * @export - * @interface TronFee - */ -export interface TronFee { - /** - * - * @type {string} - * @memberof TronFee - */ - amount: string; - /** - * - * @type {TronToken} - * @memberof TronFee - */ - token: TronToken; -} - -/** - * Check if a given object implements the TronFee interface. - */ -export function instanceOfTronFee(value: object): boolean { - let isInstance = true; - isInstance = isInstance && "amount" in value; - isInstance = isInstance && "token" in value; - - return isInstance; -} - -export function TronFeeFromJSON(json: any): TronFee { - return TronFeeFromJSONTyped(json, false); -} - -export function TronFeeFromJSONTyped(json: any, ignoreDiscriminator: boolean): TronFee { - if ((json === undefined) || (json === null)) { - return json; - } - return { - - 'amount': json['amount'], - 'token': TronTokenFromJSON(json['token']), - }; -} - -export function TronFeeToJSON(value?: TronFee | null): any { - if (value === undefined) { - return undefined; - } - if (value === null) { - return null; - } - return { - - 'amount': value.amount, - 'token': TronTokenToJSON(value.token), - }; -} - diff --git a/packages/core/src/tronApi/models/TronSettings.ts b/packages/core/src/tronApi/models/TronSettings.ts deleted file mode 100644 index e7253ac21..000000000 --- a/packages/core/src/tronApi/models/TronSettings.ts +++ /dev/null @@ -1,91 +0,0 @@ -/* tslint:disable */ -/* eslint-disable */ -/** - * gasless TRON service REST api - * Service to publish TRON transactions - * - * The version of the OpenAPI document: 2.0.0 - * Contact: support@tonkeeper.com - * - * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). - * https://openapi-generator.tech - * Do not edit the class manually. - */ - -import { exists, mapValues } from '../runtime'; -import type { TronToken } from './TronToken'; -import { - TronTokenFromJSON, - TronTokenFromJSONTyped, - TronTokenToJSON, -} from './TronToken'; - -/** - * - * @export - * @interface TronSettings - */ -export interface TronSettings { - /** - * - * @type {string} - * @memberof TronSettings - */ - walletImplementation: string; - /** - * - * @type {string} - * @memberof TronSettings - */ - chainId: string; - /** - * - * @type {Array} - * @memberof TronSettings - */ - tokens: Array; -} - -/** - * Check if a given object implements the TronSettings interface. - */ -export function instanceOfTronSettings(value: object): boolean { - let isInstance = true; - isInstance = isInstance && "walletImplementation" in value; - isInstance = isInstance && "chainId" in value; - isInstance = isInstance && "tokens" in value; - - return isInstance; -} - -export function TronSettingsFromJSON(json: any): TronSettings { - return TronSettingsFromJSONTyped(json, false); -} - -export function TronSettingsFromJSONTyped(json: any, ignoreDiscriminator: boolean): TronSettings { - if ((json === undefined) || (json === null)) { - return json; - } - return { - - 'walletImplementation': json['walletImplementation'], - 'chainId': json['chainId'], - 'tokens': ((json['tokens'] as Array).map(TronTokenFromJSON)), - }; -} - -export function TronSettingsToJSON(value?: TronSettings | null): any { - if (value === undefined) { - return undefined; - } - if (value === null) { - return null; - } - return { - - 'walletImplementation': value.walletImplementation, - 'chainId': value.chainId, - 'tokens': ((value.tokens as Array).map(TronTokenToJSON)), - }; -} - diff --git a/packages/core/src/tronApi/models/TronToken.ts b/packages/core/src/tronApi/models/TronToken.ts deleted file mode 100644 index 29189a619..000000000 --- a/packages/core/src/tronApi/models/TronToken.ts +++ /dev/null @@ -1,102 +0,0 @@ -/* tslint:disable */ -/* eslint-disable */ -/** - * gasless TRON service REST api - * Service to publish TRON transactions - * - * The version of the OpenAPI document: 2.0.0 - * Contact: support@tonkeeper.com - * - * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). - * https://openapi-generator.tech - * Do not edit the class manually. - */ - -import { exists, mapValues } from '../runtime'; -/** - * - * @export - * @interface TronToken - */ -export interface TronToken { - /** - * - * @type {string} - * @memberof TronToken - */ - image: string; - /** - * - * @type {string} - * @memberof TronToken - */ - name: string; - /** - * - * @type {string} - * @memberof TronToken - */ - symbol: string; - /** - * - * @type {string} - * @memberof TronToken - */ - address: string; - /** - * - * @type {number} - * @memberof TronToken - */ - decimals: number; -} - -/** - * Check if a given object implements the TronToken interface. - */ -export function instanceOfTronToken(value: object): boolean { - let isInstance = true; - isInstance = isInstance && "image" in value; - isInstance = isInstance && "name" in value; - isInstance = isInstance && "symbol" in value; - isInstance = isInstance && "address" in value; - isInstance = isInstance && "decimals" in value; - - return isInstance; -} - -export function TronTokenFromJSON(json: any): TronToken { - return TronTokenFromJSONTyped(json, false); -} - -export function TronTokenFromJSONTyped(json: any, ignoreDiscriminator: boolean): TronToken { - if ((json === undefined) || (json === null)) { - return json; - } - return { - - 'image': json['image'], - 'name': json['name'], - 'symbol': json['symbol'], - 'address': json['address'], - 'decimals': json['decimals'], - }; -} - -export function TronTokenToJSON(value?: TronToken | null): any { - if (value === undefined) { - return undefined; - } - if (value === null) { - return null; - } - return { - - 'image': value.image, - 'name': value.name, - 'symbol': value.symbol, - 'address': value.address, - 'decimals': value.decimals, - }; -} - diff --git a/packages/core/src/tronApi/models/TronWallet.ts b/packages/core/src/tronApi/models/TronWallet.ts deleted file mode 100644 index 8320bfd86..000000000 --- a/packages/core/src/tronApi/models/TronWallet.ts +++ /dev/null @@ -1,84 +0,0 @@ -/* tslint:disable */ -/* eslint-disable */ -/** - * gasless TRON service REST api - * Service to publish TRON transactions - * - * The version of the OpenAPI document: 2.0.0 - * Contact: support@tonkeeper.com - * - * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). - * https://openapi-generator.tech - * Do not edit the class manually. - */ - -import { exists, mapValues } from '../runtime'; -/** - * - * @export - * @interface TronWallet - */ -export interface TronWallet { - /** - * - * @type {string} - * @memberof TronWallet - */ - address: string; - /** - * - * @type {string} - * @memberof TronWallet - */ - chainId: string; - /** - * - * @type {number} - * @memberof TronWallet - */ - nonce: number; -} - -/** - * Check if a given object implements the TronWallet interface. - */ -export function instanceOfTronWallet(value: object): boolean { - let isInstance = true; - isInstance = isInstance && "address" in value; - isInstance = isInstance && "chainId" in value; - isInstance = isInstance && "nonce" in value; - - return isInstance; -} - -export function TronWalletFromJSON(json: any): TronWallet { - return TronWalletFromJSONTyped(json, false); -} - -export function TronWalletFromJSONTyped(json: any, ignoreDiscriminator: boolean): TronWallet { - if ((json === undefined) || (json === null)) { - return json; - } - return { - - 'address': json['address'], - 'chainId': json['chainId'], - 'nonce': json['nonce'], - }; -} - -export function TronWalletToJSON(value?: TronWallet | null): any { - if (value === undefined) { - return undefined; - } - if (value === null) { - return null; - } - return { - - 'address': value.address, - 'chainId': value.chainId, - 'nonce': value.nonce, - }; -} - diff --git a/packages/core/src/tronApi/models/index.ts b/packages/core/src/tronApi/models/index.ts deleted file mode 100644 index 388ebbf89..000000000 --- a/packages/core/src/tronApi/models/index.ts +++ /dev/null @@ -1,23 +0,0 @@ -/* tslint:disable */ -/* eslint-disable */ -export * from './ContractDeployAction'; -export * from './EstimatePayload'; -export * from './FromMessage'; -export * from './GetEstimationRequest'; -export * from './GetSettings401Response'; -export * from './ModelError'; -export * from './PublishPayload'; -export * from './PublishTransactionRequest'; -export * from './ReceiveTRC20Action'; -export * from './RequestData'; -export * from './RequestMessage'; -export * from './SendTRC20Action'; -export * from './TronAction'; -export * from './TronBalance'; -export * from './TronBalances'; -export * from './TronEvent'; -export * from './TronEvents'; -export * from './TronFee'; -export * from './TronSettings'; -export * from './TronToken'; -export * from './TronWallet'; diff --git a/packages/core/src/tronApi/runtime.ts b/packages/core/src/tronApi/runtime.ts deleted file mode 100644 index a98b04582..000000000 --- a/packages/core/src/tronApi/runtime.ts +++ /dev/null @@ -1,431 +0,0 @@ -/* tslint:disable */ -/* eslint-disable */ -/** - * gasless TRON service REST api - * Service to publish TRON transactions - * - * The version of the OpenAPI document: 2.0.0 - * Contact: support@tonkeeper.com - * - * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). - * https://openapi-generator.tech - * Do not edit the class manually. - */ - - -export const BASE_PATH = "https://tron.tonkeeper.com".replace(/\/+$/, ""); - -export interface ConfigurationParameters { - basePath?: string; // override base path - fetchApi?: FetchAPI; // override for fetch implementation - middleware?: Middleware[]; // middleware to apply before/after fetch requests - queryParamsStringify?: (params: HTTPQuery) => string; // stringify function for query strings - username?: string; // parameter for basic security - password?: string; // parameter for basic security - apiKey?: string | ((name: string) => string); // parameter for apiKey security - accessToken?: string | Promise | ((name?: string, scopes?: string[]) => string | Promise); // parameter for oauth2 security - headers?: HTTPHeaders; //header params we want to use on every request - credentials?: RequestCredentials; //value for the credentials param we want to use on each request -} - -export class Configuration { - constructor(private configuration: ConfigurationParameters = {}) {} - - set config(configuration: Configuration) { - this.configuration = configuration; - } - - get basePath(): string { - return this.configuration.basePath != null ? this.configuration.basePath : BASE_PATH; - } - - get fetchApi(): FetchAPI | undefined { - return this.configuration.fetchApi; - } - - get middleware(): Middleware[] { - return this.configuration.middleware || []; - } - - get queryParamsStringify(): (params: HTTPQuery) => string { - return this.configuration.queryParamsStringify || querystring; - } - - get username(): string | undefined { - return this.configuration.username; - } - - get password(): string | undefined { - return this.configuration.password; - } - - get apiKey(): ((name: string) => string) | undefined { - const apiKey = this.configuration.apiKey; - if (apiKey) { - return typeof apiKey === 'function' ? apiKey : () => apiKey; - } - return undefined; - } - - get accessToken(): ((name?: string, scopes?: string[]) => string | Promise) | undefined { - const accessToken = this.configuration.accessToken; - if (accessToken) { - return typeof accessToken === 'function' ? accessToken : async () => accessToken; - } - return undefined; - } - - get headers(): HTTPHeaders | undefined { - return this.configuration.headers; - } - - get credentials(): RequestCredentials | undefined { - return this.configuration.credentials; - } -} - -export const DefaultConfig = new Configuration(); - -/** - * This is the base class for all generated API classes. - */ -export class BaseAPI { - - private static readonly jsonRegex = new RegExp('^(:?application\/json|[^;/ \t]+\/[^;/ \t]+[+]json)[ \t]*(:?;.*)?$', 'i'); - private middleware: Middleware[]; - - constructor(protected configuration = DefaultConfig) { - this.middleware = configuration.middleware; - } - - withMiddleware(this: T, ...middlewares: Middleware[]) { - const next = this.clone(); - next.middleware = next.middleware.concat(...middlewares); - return next; - } - - withPreMiddleware(this: T, ...preMiddlewares: Array) { - const middlewares = preMiddlewares.map((pre) => ({ pre })); - return this.withMiddleware(...middlewares); - } - - withPostMiddleware(this: T, ...postMiddlewares: Array) { - const middlewares = postMiddlewares.map((post) => ({ post })); - return this.withMiddleware(...middlewares); - } - - /** - * Check if the given MIME is a JSON MIME. - * JSON MIME examples: - * application/json - * application/json; charset=UTF8 - * APPLICATION/JSON - * application/vnd.company+json - * @param mime - MIME (Multipurpose Internet Mail Extensions) - * @return True if the given MIME is JSON, false otherwise. - */ - protected isJsonMime(mime: string | null | undefined): boolean { - if (!mime) { - return false; - } - return BaseAPI.jsonRegex.test(mime); - } - - protected async request(context: RequestOpts, initOverrides?: RequestInit | InitOverrideFunction): Promise { - const { url, init } = await this.createFetchParams(context, initOverrides); - const response = await this.fetchApi(url, init); - if (response && (response.status >= 200 && response.status < 300)) { - return response; - } - throw new ResponseError(response, 'Response returned an error code'); - } - - private async createFetchParams(context: RequestOpts, initOverrides?: RequestInit | InitOverrideFunction) { - let url = this.configuration.basePath + context.path; - if (context.query !== undefined && Object.keys(context.query).length !== 0) { - // only add the querystring to the URL if there are query parameters. - // this is done to avoid urls ending with a "?" character which buggy webservers - // do not handle correctly sometimes. - url += '?' + this.configuration.queryParamsStringify(context.query); - } - - const headers = Object.assign({}, this.configuration.headers, context.headers); - Object.keys(headers).forEach(key => headers[key] === undefined ? delete headers[key] : {}); - - const initOverrideFn = - typeof initOverrides === "function" - ? initOverrides - : async () => initOverrides; - - const initParams = { - method: context.method, - headers, - body: context.body, - credentials: this.configuration.credentials, - }; - - const overriddenInit: RequestInit = { - ...initParams, - ...(await initOverrideFn({ - init: initParams, - context, - })) - }; - - let body: any; - if (isFormData(overriddenInit.body) - || (overriddenInit.body instanceof URLSearchParams) - || isBlob(overriddenInit.body)) { - body = overriddenInit.body; - } else if (this.isJsonMime(headers['Content-Type'])) { - body = JSON.stringify(overriddenInit.body); - } else { - body = overriddenInit.body; - } - - const init: RequestInit = { - ...overriddenInit, - body - }; - - return { url, init }; - } - - private fetchApi = async (url: string, init: RequestInit) => { - let fetchParams = { url, init }; - for (const middleware of this.middleware) { - if (middleware.pre) { - fetchParams = await middleware.pre({ - fetch: this.fetchApi, - ...fetchParams, - }) || fetchParams; - } - } - let response: Response | undefined = undefined; - try { - response = await (this.configuration.fetchApi || fetch)(fetchParams.url, fetchParams.init); - } catch (e) { - for (const middleware of this.middleware) { - if (middleware.onError) { - response = await middleware.onError({ - fetch: this.fetchApi, - url: fetchParams.url, - init: fetchParams.init, - error: e, - response: response ? response.clone() : undefined, - }) || response; - } - } - if (response === undefined) { - if (e instanceof Error) { - throw new FetchError(e, 'The request failed and the interceptors did not return an alternative response'); - } else { - throw e; - } - } - } - for (const middleware of this.middleware) { - if (middleware.post) { - response = await middleware.post({ - fetch: this.fetchApi, - url: fetchParams.url, - init: fetchParams.init, - response: response.clone(), - }) || response; - } - } - return response; - } - - /** - * Create a shallow clone of `this` by constructing a new instance - * and then shallow cloning data members. - */ - private clone(this: T): T { - const constructor = this.constructor as any; - const next = new constructor(this.configuration); - next.middleware = this.middleware.slice(); - return next; - } -}; - -function isBlob(value: any): value is Blob { - return typeof Blob !== 'undefined' && value instanceof Blob; -} - -function isFormData(value: any): value is FormData { - return typeof FormData !== "undefined" && value instanceof FormData; -} - -export class ResponseError extends Error { - override name: "ResponseError" = "ResponseError"; - constructor(public response: Response, msg?: string) { - super(msg); - } -} - -export class FetchError extends Error { - override name: "FetchError" = "FetchError"; - constructor(public cause: Error, msg?: string) { - super(msg); - } -} - -export class RequiredError extends Error { - override name: "RequiredError" = "RequiredError"; - constructor(public field: string, msg?: string) { - super(msg); - } -} - -export const COLLECTION_FORMATS = { - csv: ",", - ssv: " ", - tsv: "\t", - pipes: "|", -}; - -export type FetchAPI = WindowOrWorkerGlobalScope['fetch']; - -export type Json = any; -export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD'; -export type HTTPHeaders = { [key: string]: string }; -export type HTTPQuery = { [key: string]: string | number | null | boolean | Array | Set | HTTPQuery }; -export type HTTPBody = Json | FormData | URLSearchParams; -export type HTTPRequestInit = { headers?: HTTPHeaders; method: HTTPMethod; credentials?: RequestCredentials; body?: HTTPBody }; -export type ModelPropertyNaming = 'camelCase' | 'snake_case' | 'PascalCase' | 'original'; - -export type InitOverrideFunction = (requestContext: { init: HTTPRequestInit, context: RequestOpts }) => Promise - -export interface FetchParams { - url: string; - init: RequestInit; -} - -export interface RequestOpts { - path: string; - method: HTTPMethod; - headers: HTTPHeaders; - query?: HTTPQuery; - body?: HTTPBody; -} - -export function exists(json: any, key: string) { - const value = json[key]; - return value !== null && value !== undefined; -} - -export function querystring(params: HTTPQuery, prefix: string = ''): string { - return Object.keys(params) - .map(key => querystringSingleKey(key, params[key], prefix)) - .filter(part => part.length > 0) - .join('&'); -} - -function querystringSingleKey(key: string, value: string | number | null | undefined | boolean | Array | Set | HTTPQuery, keyPrefix: string = ''): string { - const fullKey = keyPrefix + (keyPrefix.length ? `[${key}]` : key); - if (value instanceof Array) { - const multiValue = value.map(singleValue => encodeURIComponent(String(singleValue))) - .join(`&${encodeURIComponent(fullKey)}=`); - return `${encodeURIComponent(fullKey)}=${multiValue}`; - } - if (value instanceof Set) { - const valueAsArray = Array.from(value); - return querystringSingleKey(key, valueAsArray, keyPrefix); - } - if (value instanceof Date) { - return `${encodeURIComponent(fullKey)}=${encodeURIComponent(value.toISOString())}`; - } - if (value instanceof Object) { - return querystring(value as HTTPQuery, fullKey); - } - return `${encodeURIComponent(fullKey)}=${encodeURIComponent(String(value))}`; -} - -export function mapValues(data: any, fn: (item: any) => any) { - return Object.keys(data).reduce( - (acc, key) => ({ ...acc, [key]: fn(data[key]) }), - {} - ); -} - -export function canConsumeForm(consumes: Consume[]): boolean { - for (const consume of consumes) { - if ('multipart/form-data' === consume.contentType) { - return true; - } - } - return false; -} - -export interface Consume { - contentType: string; -} - -export interface RequestContext { - fetch: FetchAPI; - url: string; - init: RequestInit; -} - -export interface ResponseContext { - fetch: FetchAPI; - url: string; - init: RequestInit; - response: Response; -} - -export interface ErrorContext { - fetch: FetchAPI; - url: string; - init: RequestInit; - error: unknown; - response?: Response; -} - -export interface Middleware { - pre?(context: RequestContext): Promise; - post?(context: ResponseContext): Promise; - onError?(context: ErrorContext): Promise; -} - -export interface ApiResponse { - raw: Response; - value(): Promise; -} - -export interface ResponseTransformer { - (json: any): T; -} - -export class JSONApiResponse { - constructor(public raw: Response, private transformer: ResponseTransformer = (jsonValue: any) => jsonValue) {} - - async value(): Promise { - return this.transformer(await this.raw.json()); - } -} - -export class VoidApiResponse { - constructor(public raw: Response) {} - - async value(): Promise { - return undefined; - } -} - -export class BlobApiResponse { - constructor(public raw: Response) {} - - async value(): Promise { - return await this.raw.blob(); - }; -} - -export class TextApiResponse { - constructor(public raw: Response) {} - - async value(): Promise { - return await this.raw.text(); - }; -} diff --git a/packages/uikit/package.json b/packages/uikit/package.json index 476184c80..fc1591119 100644 --- a/packages/uikit/package.json +++ b/packages/uikit/package.json @@ -21,6 +21,7 @@ "@tanstack/react-query": "4.3.4", "@tanstack/react-virtual": "^3.8.3", "@ton-keychain/core": "^0.0.4", + "@ton-keychain/trx": "^0.0.6", "@ton/core": "0.56.0", "@ton/crypto": "3.2.0", "@tonkeeper/core": "0.1.0", diff --git a/packages/uikit/src/components/home/TronAssets.tsx b/packages/uikit/src/components/home/TronAssets.tsx index 96a8981ac..2d1464dd5 100644 --- a/packages/uikit/src/components/home/TronAssets.tsx +++ b/packages/uikit/src/components/home/TronAssets.tsx @@ -1,33 +1,27 @@ -import { TronBalances, TronToken } from '@tonkeeper/core/dist/tronApi'; -import { formatDecimals } from '@tonkeeper/core/dist/utils/balance'; -import React, { FC, useMemo } from 'react'; +import { TronBalances } from '@tonkeeper/core/dist/tronApi'; +import React, { FC } from 'react'; import { useNavigate } from 'react-router-dom'; -import { useFormatBalance } from '../../hooks/balance'; import { AppRoute } from '../../libs/routes'; import { useFormatFiat, useRate } from '../../state/rates'; import { ListItem } from '../List'; import { ListItemPayload, TokenLayout, TokenLogo } from './TokenLayout'; +import { AssetAmount } from '@tonkeeper/core/dist/entries/crypto/asset/asset-amount'; +import { TronAsset } from '@tonkeeper/core/dist/entries/crypto/asset/tron-asset'; -const TronAsset: FC<{ - token: TronToken; - weiAmount: string; -}> = ({ token, weiAmount }) => { +const TronAsset: FC<{ assetAmount: AssetAmount }> = ({ assetAmount }) => { const navigate = useNavigate(); - const amount = useMemo(() => formatDecimals(weiAmount, token.decimals), [weiAmount, token]); - const balance = useFormatBalance(amount, token.decimals); - const { data } = useRate(token.symbol); const { fiatPrice, fiatAmount } = useFormatFiat(data, amount); return ( navigate(AppRoute.coins + '/tron/' + token.address)}> - + ({ mainnetApi: { - tonApiV2: new ConfigurationV2(), - tronApi: new TronConfiguration() + tonApiV2: new ConfigurationV2() }, testnetApi: { - tonApiV2: new ConfigurationV2(), - tronApi: new TronConfiguration() + tonApiV2: new ConfigurationV2() }, fiat: FiatCurrencies.USD, mainnetConfig: defaultTonendpointConfig, diff --git a/packages/uikit/src/libs/queryKey.ts b/packages/uikit/src/libs/queryKey.ts index b0e38c66c..56294482b 100644 --- a/packages/uikit/src/libs/queryKey.ts +++ b/packages/uikit/src/libs/queryKey.ts @@ -36,7 +36,7 @@ export enum QueryKey { featuredRecommendations = 'recommendations', experimental = 'experimental', - tron = 'tron', + tronAssets = 'tronAssets', rate = 'rate', total = 'total', distribution = 'distribution', diff --git a/packages/uikit/src/state/mnemonic.ts b/packages/uikit/src/state/mnemonic.ts index eb24973d6..23e2c3f35 100644 --- a/packages/uikit/src/state/mnemonic.ts +++ b/packages/uikit/src/state/mnemonic.ts @@ -454,3 +454,14 @@ const pairLedgerByNotification = async ( sdk.uiEvents.on('response', onCallback); }); }; + +export const useGetActiveAccountMnemonic = () => { + const sdk = useAppSdk(); + const { mutateAsync: checkTouchId } = useCheckTouchId(); + const activeAccount = useActiveAccount(); + const accountId = activeAccount.id; + + return useCallback(async () => { + return getAccountMnemonic(sdk, accountId, checkTouchId); + }, [sdk, checkTouchId, accountId]); +}; diff --git a/packages/uikit/src/state/tron/tron.ts b/packages/uikit/src/state/tron/tron.ts index e59f84d9f..8680f84bc 100644 --- a/packages/uikit/src/state/tron/tron.ts +++ b/packages/uikit/src/state/tron/tron.ts @@ -1,76 +1,61 @@ import { useQuery } from '@tanstack/react-query'; -import { TronWalletState } from '@tonkeeper/core/dist/entries/wallet'; -import { TronApi, TronBalance, TronBalances, TronToken } from '@tonkeeper/core/dist/tronApi'; import { useAppContext } from '../../hooks/appContext'; -import { useAppSdk } from '../../hooks/appSdk'; import { QueryKey } from '../../libs/queryKey'; import { DefaultRefetchInterval } from '../tonendpoint'; -import { useActiveApi } from '../wallet'; - -enum TronKeys { - state, - balance -} - -export const useTronWalletState = (enabled = true) => { - const { tronApi } = useActiveApi(); - /* const client = useQueryClient(); - const { mutateAsync: checkTouchId } = useCheckTouchId();*/ - - return useQuery( - [TronKeys.state], - async () => { - return undefined; - /* if (wallet.tron) { - return getTronWalletState(wallet.tron, wallet.network); - } - - const mnemonic = await getMnemonic(sdk, wallet.publicKey, checkTouchId); - const tron = await importTronWallet(sdk.storage, tronApi, wallet, mnemonic); +import { useActiveAccount } from '../wallet'; +import { useMemo } from 'react'; +import { isAccountTronCompatible } from '@tonkeeper/core/dist/entries/account'; +import { TronWallet } from '@tonkeeper/core/dist/entries/tron/tron-wallet'; +import { TronApi } from '@tonkeeper/core/dist/tronApi'; +import { AssetAmount } from '@tonkeeper/core/dist/entries/crypto/asset/asset-amount'; +import { + TRON_TRX_ASSET, + TRON_USDT_ASSET +} from '@tonkeeper/core/dist/entries/crypto/asset/constants'; +import { TronAsset } from '@tonkeeper/core/dist/entries/crypto/asset/tron-asset'; + +export const useTronApi = () => { + const appContext = useAppContext(); + const apiKey = appContext.env?.tronApiKey; + + const apiUrl = appContext.mainnetConfig.tron_api_url || 'https://api.trongrid.io'; + + return useMemo(() => new TronApi(apiUrl, apiKey), [apiKey, apiUrl]); +}; - const result = getTronWalletState(tron, wallet.network); +export const useActiveTronWallet = (): TronWallet | undefined => { + const account = useActiveAccount(); - client.invalidateQueries([QueryKey.account, QueryKey.wallet]); - return result;*/ - }, - { enabled } - ); -}; + if (isAccountTronCompatible(account)) { + return account.activeTronWallet; + } -export const useTronTokens = () => { - const { tronApi } = useActiveApi(); - // const wallet = useWalletContext(); - return useQuery( - [QueryKey.tron, /*wallet.network,*/ TronKeys.balance], - async () => { - const sdk = new TronApi(tronApi); - const { tokens } = await sdk.getSettings(); - return tokens; - } - ); + return undefined; }; export const useTronBalances = () => { - const { tronApi } = useActiveApi(); - /*const wallet = useWalletContext();*/ + const tronApi = useTronApi(); + const activeWallet = useActiveTronWallet(); - return useQuery( - [QueryKey.tron, /*wallet.network,*/ TronKeys.balance], + return useQuery<{ trx: AssetAmount; usdt: AssetAmount } | null, Error>( + [QueryKey.tronAssets, activeWallet?.address], async () => { - return { balances: [] }; - /*const sdk = new TronApi(tronApi); + if (!activeWallet) { + return null; + } + const balances = await tronApi.getBalances(activeWallet?.address); - if (wallet.tron) { - const { walletAddress } = getTronWalletState(wallet.tron, wallet.network); - return sdk.getWalletBalances({ - walletAddress - }); - } else { - // const { tokens } = await sdk.getSettings(); - return { - balances: [] // tokens.map(token => ({ token, weiAmount: '0' })) - }; - }*/ + const trx = new AssetAmount({ + asset: TRON_TRX_ASSET, + weiAmount: balances.trx + }); + + const usdt = new AssetAmount({ + asset: TRON_USDT_ASSET, + weiAmount: balances.usdt + }); + + return { trx, usdt }; }, { refetchInterval: DefaultRefetchInterval, @@ -80,43 +65,3 @@ export const useTronBalances = () => { } ); }; - -export const useTronBalance = (tron: TronWalletState, address: string | undefined) => { - const { tronApi } = useActiveApi(); - // const wallet = useWalletContext(); - - return useQuery([QueryKey.tron, address], async () => { - if (!address) { - throw new Error('missing token address'); - } - const sdk = new TronApi(tronApi); - - const { balances } = await sdk.getWalletBalances({ - walletAddress: tron.walletAddress - }); - - const balance = balances.find(item => item.token.address === address); - if (!balance) { - throw new Error('missing token balance'); - } - - return balance; - }); -}; - -/* -export const useCleanUpTronStore = () => { - const wallet = useWalletContext(); - const sdk = useAppSdk(); - const client = useQueryClient(); - - return useMutation(async () => { - // eslint-disable-next-line unused-imports/no-unused-vars - const { tron, ...rest } = wallet; - if (wallet.tron) { - await setWalletState(sdk.storage, rest); - await client.invalidateQueries(); - } - }); -}; -*/ diff --git a/packages/uikit/src/state/wallet.ts b/packages/uikit/src/state/wallet.ts index fca3c98f5..21d520758 100644 --- a/packages/uikit/src/state/wallet.ts +++ b/packages/uikit/src/state/wallet.ts @@ -13,6 +13,7 @@ import { getNetworkByAccount, getWalletById, isAccountTonWalletStandard, + isAccountTronCompatible, isAccountVersionEditable, isMnemonicAndPassword } from '@tonkeeper/core/dist/entries/account'; @@ -40,7 +41,9 @@ import { getContextApiByNetwork, getStandardTonWalletVersions, getTonWalletStandard, - getWalletAddress + getWalletAddress, + mamAccountToMamAccountWithTron, + standardTonAccountToAccountWithTron } from '@tonkeeper/core/dist/service/walletService'; import { Account as TonapiAccount, AccountsApi } from '@tonkeeper/core/dist/tonApiV2'; import { seeIfValidTonAddress } from '@tonkeeper/core/dist/utils/common'; @@ -49,13 +52,18 @@ import { useAppContext } from '../hooks/appContext'; import { useAppSdk } from '../hooks/appSdk'; import { useAccountsStorage } from '../hooks/useStorage'; import { anyOfKeysParts, QueryKey } from '../libs/queryKey'; -import { getAccountMnemonic, getPasswordByNotification } from './mnemonic'; +import { + getAccountMnemonic, + getPasswordByNotification, + useGetActiveAccountMnemonic +} from './mnemonic'; import { useCheckTouchId } from './password'; import { seeIfMnemonicValid } from '@tonkeeper/core/dist/service/mnemonicService'; import { useAccountsStateQuery, useAccountsState } from './accounts'; import { useGlobalPreferences } from './global-preferences'; import { useDeleteFolder } from './folders'; import { useRemoveAccountTwoFAData } from './two-fa'; +import { assertUnreachable } from '@tonkeeper/core/dist/utils/types'; export { useAccountsStateQuery, useAccountsState }; @@ -465,6 +473,34 @@ export const useCreateAccountTestnet = () => { }); }; +export const useAddTronToAccount = () => { + const getMnemonic = useGetActiveAccountMnemonic(); + const activeAccount = useActiveAccount(); + const accountsStorage = useAccountsStorage(); + const client = useQueryClient(); + + return useMutation(async () => { + if (!isAccountTronCompatible(activeAccount)) { + throw new Error('Account is not tron compatible'); + } + + let updatedAccount: Account; + switch (activeAccount.type) { + case 'mnemonic': + updatedAccount = standardTonAccountToAccountWithTron(activeAccount, getMnemonic); + break; + case 'mam': + updatedAccount = mamAccountToMamAccountWithTron(activeAccount); + break; + default: + assertUnreachable(activeAccount); + } + + await accountsStorage.updateAccountInState(updatedAccount); + await client.invalidateQueries(anyOfKeysParts(QueryKey.account, updatedAccount.id)); + }); +}; + export const useCreateAccountMnemonic = () => { const sdk = useAppSdk(); const context = useAppContext(); diff --git a/yarn.lock b/yarn.lock index 35d14baeb..8dbfdc0f7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -26,10 +26,10 @@ __metadata: languageName: node linkType: hard -"@adraffy/ens-normalize@npm:1.10.0": - version: 1.10.0 - resolution: "@adraffy/ens-normalize@npm:1.10.0" - checksum: 10/5cdb5d2a9c9f8c0a71a7bb830967da0069cae1f1235cd41ae11147e4000f368f6958386e622cd4d52bf45c1ed3f8275056b387cba28902b83354e40ff323ecde +"@adraffy/ens-normalize@npm:1.10.1": + version: 1.10.1 + resolution: "@adraffy/ens-normalize@npm:1.10.1" + checksum: 10/4cb938c4abb88a346d50cb0ea44243ab3574330c81d4f5aaaf9dfee584b96189d0faa404de0fcbef5a1b73909ea4ebc3e63d84bd23f9949e5c8d4085207a5091 languageName: node linkType: hard @@ -5106,6 +5106,13 @@ __metadata: languageName: node linkType: hard +"@noble/hashes@npm:^1.3.3": + version: 1.6.1 + resolution: "@noble/hashes@npm:1.6.1" + checksum: 10/74d9ad7b1437a22ba3b877584add3367587fbf818113152f293025d20d425aa74c191d18d434797312f2270458bc9ab3241c34d14ec6115fb16438b3248f631f + languageName: node + linkType: hard + "@nodelib/fs.scandir@npm:2.1.5": version: 2.1.5 resolution: "@nodelib/fs.scandir@npm:2.1.5" @@ -7304,6 +7311,18 @@ __metadata: languageName: node linkType: hard +"@ton-keychain/trx@npm:^0.0.6": + version: 0.0.6 + resolution: "@ton-keychain/trx@npm:0.0.6" + dependencies: + "@noble/hashes": "npm:^1.3.3" + "@scure/bip39": "npm:^1.2.2" + "@ton-keychain/core": "npm:^0.0.4" + ethers: "npm:^6.9.2" + checksum: 10/21f1987073f8fc925ca97e01294c7cf6c7b18a7091b0c9fbadda3d3bd65a9d73800384c35726aeb019f11f47e96348578867fc406cf02989a6e25ed299018250 + languageName: node + linkType: hard + "@ton/core@npm:0.56.0": version: 0.56.0 resolution: "@ton/core@npm:0.56.0" @@ -7371,12 +7390,13 @@ __metadata: "@ledgerhq/hw-transport-webusb": "npm:^6.28.6" "@ton-community/ton-ledger": "npm:^7.2.0-pre.2" "@ton-keychain/core": "npm:^0.0.4" + "@ton-keychain/trx": "npm:^0.0.6" "@ton/core": "npm:0.56.0" "@ton/crypto": "npm:3.2.0" "@ton/ton": "npm:^15.1.0" bignumber.js: "npm:^9.1.1" bip39: "npm:^3.1.0" - ethers: "npm:^6.6.5" + ethers: "npm:^6.13.4" npm-run-all: "npm:^4.1.5" query-string: "npm:^8.1.0" tweetnacl: "npm:^1.0.3" @@ -7629,6 +7649,7 @@ __metadata: "@tanstack/react-query": "npm:4.3.4" "@tanstack/react-virtual": "npm:^3.8.3" "@ton-keychain/core": "npm:^0.0.4" + "@ton-keychain/trx": "npm:^0.0.6" "@ton/core": "npm:0.56.0" "@ton/crypto": "npm:3.2.0" "@tonkeeper/core": "npm:0.1.0" @@ -8356,10 +8377,12 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:18.15.13": - version: 18.15.13 - resolution: "@types/node@npm:18.15.13" - checksum: 10/b9bbe923573797ef7c5fd2641a6793489e25d9369c32aeadcaa5c7c175c85b42eb12d6fe173f6781ab6f42eaa1ebd9576a419eeaa2a1ec810094adb8adaa9a54 +"@types/node@npm:22.7.5": + version: 22.7.5 + resolution: "@types/node@npm:22.7.5" + dependencies: + undici-types: "npm:~6.19.2" + checksum: 10/e8ba102f8c1aa7623787d625389be68d64e54fcbb76d41f6c2c64e8cf4c9f4a2370e7ef5e5f1732f3c57529d3d26afdcb2edc0101c5e413a79081449825c57ac languageName: node linkType: hard @@ -15336,18 +15359,18 @@ __metadata: languageName: node linkType: hard -"ethers@npm:^6.6.5": - version: 6.9.0 - resolution: "ethers@npm:6.9.0" +"ethers@npm:^6.13.4, ethers@npm:^6.9.2": + version: 6.13.4 + resolution: "ethers@npm:6.13.4" dependencies: - "@adraffy/ens-normalize": "npm:1.10.0" + "@adraffy/ens-normalize": "npm:1.10.1" "@noble/curves": "npm:1.2.0" "@noble/hashes": "npm:1.3.2" - "@types/node": "npm:18.15.13" + "@types/node": "npm:22.7.5" aes-js: "npm:4.0.0-beta.5" - tslib: "npm:2.4.0" - ws: "npm:8.5.0" - checksum: 10/a48f268aa934900d90e34fd6b607db3d774ce58c73d249697f68398afa673b483fda19c6fa91eb91784fdfbaa07eb4c753b245c2123c5d2072f8dc1897cd556d + tslib: "npm:2.7.0" + ws: "npm:8.17.1" + checksum: 10/221192fed93f6b0553f3e5e72bfd667d676220577d34ff854f677e955d6f608e60636a9c08b5d54039c532a9b9b7056384f0d7019eb6e111d53175806f896ac6 languageName: node linkType: hard @@ -28008,10 +28031,10 @@ __metadata: languageName: node linkType: hard -"tslib@npm:2.4.0": - version: 2.4.0 - resolution: "tslib@npm:2.4.0" - checksum: 10/d8379e68b36caf082c1905ec25d17df8261e1d68ddc1abfd6c91158a064f6e4402039ae7c02cf4c81d12e3a2a2c7cd8ea2f57b233eb80136a2e3e7279daf2911 +"tslib@npm:2.7.0": + version: 2.7.0 + resolution: "tslib@npm:2.7.0" + checksum: 10/9a5b47ddac65874fa011c20ff76db69f97cf90c78cff5934799ab8894a5342db2d17b4e7613a087046bc1d133d21547ddff87ac558abeec31ffa929c88b7fce6 languageName: node linkType: hard @@ -28424,7 +28447,7 @@ __metadata: languageName: node linkType: hard -"undici-types@npm:~6.19.8": +"undici-types@npm:~6.19.2, undici-types@npm:~6.19.8": version: 6.19.8 resolution: "undici-types@npm:6.19.8" checksum: 10/cf0b48ed4fc99baf56584afa91aaffa5010c268b8842f62e02f752df209e3dea138b372a60a963b3b2576ed932f32329ce7ddb9cb5f27a6c83040d8cd74b7a70 @@ -30105,18 +30128,18 @@ __metadata: languageName: node linkType: hard -"ws@npm:8.5.0": - version: 8.5.0 - resolution: "ws@npm:8.5.0" +"ws@npm:8.17.1": + version: 8.17.1 + resolution: "ws@npm:8.17.1" peerDependencies: bufferutil: ^4.0.1 - utf-8-validate: ^5.0.2 + utf-8-validate: ">=5.0.2" peerDependenciesMeta: bufferutil: optional: true utf-8-validate: optional: true - checksum: 10/f0ee700970a0bf925b1ec213ca3691e84fb8b435a91461fe3caf52f58c6cec57c99ed5890fbf6978824c932641932019aafc55d864cad38ac32577496efd5d3a + checksum: 10/4264ae92c0b3e59c7e309001e93079b26937aab181835fb7af79f906b22cd33b6196d96556dafb4e985742dd401e99139572242e9847661fdbc96556b9e6902d languageName: node linkType: hard From 534c4c81b4ec042082e9e028bff9884b25286bb0 Mon Sep 17 00:00:00 2001 From: siandreev Date: Fri, 20 Dec 2024 19:36:05 +0100 Subject: [PATCH 02/20] feat: tron usdt page added --- .../src/entries/crypto/asset/ton-asset.ts | 5 + packages/core/src/entries/network.ts | 11 +- packages/core/src/entries/wallet.ts | 2 - .../src/service/tron/addressCalculation.ts | 48 ------ .../core/src/service/tron/tronEncoding.ts | 57 ------- packages/core/src/service/tron/tronService.ts | 86 ----------- .../src/service/tron/tronTransferService.ts | 145 ------------------ packages/core/src/service/tron/tronUtils.ts | 38 ----- packages/locales/src/tonkeeper-web/en.json | 1 + packages/locales/src/tonkeeper-web/ru-RU.json | 1 + .../uikit/src/components/home/AccountView.tsx | 9 +- .../uikit/src/components/home/Jettons.tsx | 14 +- .../uikit/src/components/home/TronAssets.tsx | 85 ++++++++-- .../components/transfer/SendNotifications.tsx | 5 +- .../desktop-pages/coin/DesktopCoinPage.tsx | 72 ++++++++- .../desktop-pages/tokens/DesktopTokens.tsx | 96 ++++++++---- packages/uikit/src/state/home.ts | 29 +++- packages/uikit/src/state/mixedActivity.ts | 4 +- packages/uikit/src/state/tron/tron.ts | 9 +- packages/uikit/src/state/wallet.ts | 7 +- 20 files changed, 253 insertions(+), 471 deletions(-) delete mode 100644 packages/core/src/service/tron/addressCalculation.ts delete mode 100644 packages/core/src/service/tron/tronEncoding.ts delete mode 100644 packages/core/src/service/tron/tronService.ts delete mode 100644 packages/core/src/service/tron/tronTransferService.ts delete mode 100644 packages/core/src/service/tron/tronUtils.ts diff --git a/packages/core/src/entries/crypto/asset/ton-asset.ts b/packages/core/src/entries/crypto/asset/ton-asset.ts index 56c88cb31..662afd008 100644 --- a/packages/core/src/entries/crypto/asset/ton-asset.ts +++ b/packages/core/src/entries/crypto/asset/ton-asset.ts @@ -4,6 +4,7 @@ import { BLOCKCHAIN_NAME } from '../../crypto'; import { BasicAsset, packAssetId } from './basic-asset'; import { TON_ASSET } from './constants'; import { AssetAmount } from './asset-amount'; +import { TronAsset } from './tron-asset'; export type TonAssetAddress = Address | 'TON'; export function isTon(address: TonAssetAddress): address is 'TON' { @@ -25,6 +26,10 @@ export function tonAssetAddressFromString(address: string): TonAsset['address'] return address === 'TON' ? address : Address.parse(address); } +export function assetAddressToString(address: TonAsset['address'] | TronAsset['address']): string { + return typeof address === 'string' ? address : address.toRawString(); +} + export function jettonToTonAsset(address: string, jettons: JettonsBalances): TonAsset { if (address === 'TON') { return TON_ASSET; diff --git a/packages/core/src/entries/network.ts b/packages/core/src/entries/network.ts index 220de1f9c..d28148162 100644 --- a/packages/core/src/entries/network.ts +++ b/packages/core/src/entries/network.ts @@ -1,8 +1,6 @@ import { Configuration as ConfigurationV2 } from '../tonApiV2'; import { OpenAPI as TonConsoleApi } from '../tonConsoleApi'; import { TonendpointConfig } from '../tonkeeperApi/tonendpoint'; -import { Configuration as TronConfiguration } from '../tronApi'; -import { TronApi, TronChain } from './tron'; export enum Network { MAINNET = -239, @@ -25,12 +23,6 @@ export const getTonClientV2 = (config: TonendpointConfig, current?: Network) => }); }; -const getTronClient = (current?: Network) => { - return new TronConfiguration({ - basePath: TronApi[current === Network.MAINNET ? TronChain.MAINNET : TronChain.NILE] - }); -}; - export const getApiConfig = (config: TonendpointConfig, network: Network, TonConsoleBase = '') => { // Global config if (TonConsoleBase) { @@ -39,7 +31,6 @@ export const getApiConfig = (config: TonendpointConfig, network: Network, TonCon } return { - tonApiV2: getTonClientV2(config, network), - tronApi: getTronClient(network) + tonApiV2: getTonClientV2(config, network) }; }; diff --git a/packages/core/src/entries/wallet.ts b/packages/core/src/entries/wallet.ts index f8dae93ab..fa66a053d 100644 --- a/packages/core/src/entries/wallet.ts +++ b/packages/core/src/entries/wallet.ts @@ -109,8 +109,6 @@ export interface DeprecatedWalletState { theme?: string; proxy?: WalletProxy; - - tron?: TronWalletStorage; } export type WalletId = string; diff --git a/packages/core/src/service/tron/addressCalculation.ts b/packages/core/src/service/tron/addressCalculation.ts deleted file mode 100644 index 3298ded25..000000000 --- a/packages/core/src/service/tron/addressCalculation.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { BytesLike, concat, dataSlice, getAddress, getBytes, keccak256 } from 'ethers'; -import { TronAddress } from './tronUtils'; - -const base58AddressToEth = (address: string) => { - return '0x' + TronAddress.base58ToHex(address).slice(2); -}; - -const addressToSalt = (address: string) => { - return '0x' + '0'.repeat(24) + base58AddressToEth(address).slice(2); -}; - -const buildBytecodeHash = (implementationAddress: string) => { - return keccak256( - concat([ - '0x3d602d80600a3d3981f3363d3d373d3d3d363d73', - base58AddressToEth(implementationAddress), - '0x5af43d82803e903d91602b57fd5bf3' - ]) - ); -}; - -function getCreate2Address(_from: string, _salt: BytesLike, _initCodeHash: BytesLike): string { - const from = getAddress(_from); - const salt = getBytes(_salt, 'salt'); - const initCodeHash = getBytes(_initCodeHash, 'initCodeHash'); - - return dataSlice(keccak256(concat(['0x41', from, salt, initCodeHash])), 12); -} - -export const calculateCreate2 = ({ - factoryAddress, - ownerAddress, - implementationAddress -}: { - factoryAddress: string; - implementationAddress: string; - ownerAddress: string; -}) => { - const byteCode = buildBytecodeHash(implementationAddress); - const saltHex = addressToSalt(ownerAddress); - - const predictedAddress = getCreate2Address( - base58AddressToEth(factoryAddress), - saltHex, - byteCode - ); - return TronAddress.hexToBase58(predictedAddress); -}; diff --git a/packages/core/src/service/tron/tronEncoding.ts b/packages/core/src/service/tron/tronEncoding.ts deleted file mode 100644 index 0f09477a7..000000000 --- a/packages/core/src/service/tron/tronEncoding.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { RequestData } from '../../tronApi'; -import { TronAddress, encodePackedBytes, encodeTronParams, keccak256 } from './tronUtils'; - -const DomainAbi = ['bytes32', 'bytes32', 'bytes32', 'uint256', 'address', 'bytes32']; -const RequestAbi = [ - 'bytes32', - 'address', - 'address', - 'uint256', - 'uint256', - 'uint32', - 'tuple(address,uint256,bytes)[]' -]; - -export function hashRequest(request: RequestData, contractAddress: string, chainId: string) { - const REQUEST_TYPEHASH = keccak256( - 'Request(address feeReceiver,address feeToken,uint256 fee,uint256 deadline,uint32 nonce,Message[] messages)' - ); - const structHash = keccak256( - encodeTronParams(RequestAbi, [ - REQUEST_TYPEHASH, - request.feeReceiver, - request.feeToken, - request.fee, - request.deadline, - request.nonce, - request.messages.map(m => [ - '0x' + TronAddress.base58ToHex(m.to).slice(2), - m.value, - m.data - ]) - ]) - ); - - const domain = domainHash(contractAddress, chainId); - - return keccak256(encodePackedBytes(['0x1901', domain, structHash])); -} - -function domainHash(contractAddress: string, chainId: string) { - const TIP712_DOMAIN_TYPEHASH = keccak256( - 'EIP712Domain(string name,string version,uint256 chainId,address verifyingContract,bytes32 salt)' - ); - const NAME = keccak256('TONKEEPER'); - const VERSION = keccak256('1'); - const SALT = keccak256('TRON_WALLET'); - return keccak256( - encodeTronParams(DomainAbi, [ - TIP712_DOMAIN_TYPEHASH, - NAME, - VERSION, - chainId, - contractAddress, - SALT - ]) - ); -} diff --git a/packages/core/src/service/tron/tronService.ts b/packages/core/src/service/tron/tronService.ts deleted file mode 100644 index 118ec7200..000000000 --- a/packages/core/src/service/tron/tronService.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { hmac_sha512, mnemonicToPrivateKey } from '@ton/crypto'; -import { ethers } from 'ethers'; -import { Network } from '../../entries/network'; -import { Factories, TronChain, WalletImplementations } from '../../entries/tron'; -import { TronWalletState, TronWalletStorage } from '../../entries/wallet'; -import { calculateCreate2 } from './addressCalculation'; -import { TronAddress } from './tronUtils'; - -export const getPrivateKey = async (tonMnemonic: string[]): Promise => { - // TON-compatible seed - const pair = await mnemonicToPrivateKey(tonMnemonic); - const seed = pair.secretKey.slice(0, 32); - - // Sub-protocol derivation for ETH-derived keys: - // Note that tonweb's definition of hmacSha512 takes in hex-encoded strings - const tronSeed = await hmac_sha512(/*key*/ seed, /*data*/ 'BIP32'); - - // Plug into BIP39 with TRON path m/44'/195'/0'/0/0: - const TRON_BIP39_PATH_INDEX_0 = "m/44'/195'/0'/0/0"; - const account = ethers.HDNodeWallet.fromSeed(tronSeed).derivePath(TRON_BIP39_PATH_INDEX_0); - return account.privateKey.slice(2); // note: this is hex-encoded, remove 0x -}; - -const getOwnerAddress = async (mnemonic: string[]): Promise => { - const wallet = new ethers.Wallet(await getPrivateKey(mnemonic)); - return TronAddress.hexToBase58(wallet.address); -}; - -/*export const getTronWallet = async ( - tronApi: Configuration, - mnemonic: string[], - wallet: DeprecatedWalletState -): Promise => { - const ownerWalletAddress = await getOwnerAddress(mnemonic); - - const tronWallet = await new TronApi(tronApi).getWallet({ - ownerAddress: ownerWalletAddress - }); - - return { - ownerWalletAddress, - walletByChain: { - ...(wallet.tron?.walletByChain ?? {}), - [tronWallet.chainId]: tronWallet.address - } - }; -}; - -export const importTronWallet = async ( - storage: IStorage, - tronApi: Configuration, - wallet: DeprecatedWalletState, - mnemonic: string[] -): Promise => { - const tron = await getTronWallet(tronApi, mnemonic, wallet); - - const updated = { ...wallet, tron }; - - // await setWalletState(storage, updated); - - return tron; -};*/ - -export const getTronWalletState = (tron: TronWalletStorage, network?: Network): TronWalletState => { - const chainId = network !== Network.TESTNET ? TronChain.MAINNET : TronChain.NILE; - - if (tron.walletByChain[chainId]) { - return { - ownerWalletAddress: tron.ownerWalletAddress, - chainId: chainId, - walletAddress: tron.walletByChain[chainId] - }; - } - - const predictedAddress = calculateCreate2({ - factoryAddress: Factories[chainId], - ownerAddress: tron.ownerWalletAddress, - implementationAddress: WalletImplementations[chainId] - }); - - return { - ownerWalletAddress: tron.ownerWalletAddress, - chainId: chainId, - walletAddress: predictedAddress - }; -}; diff --git a/packages/core/src/service/tron/tronTransferService.ts b/packages/core/src/service/tron/tronTransferService.ts deleted file mode 100644 index 180a98dd7..000000000 --- a/packages/core/src/service/tron/tronTransferService.ts +++ /dev/null @@ -1,145 +0,0 @@ -import { ethers } from 'ethers'; -import { AssetAmount } from '../../entries/crypto/asset/asset-amount'; -import { TronAsset } from '../../entries/crypto/asset/tron-asset'; -import { TronRecipient } from '../../entries/send'; -import { TronWalletStorage } from '../../entries/wallet'; -import { - Configuration, - EstimatePayload, - PublishPayload, - RequestData, - TronApi -} from '../../tronApi'; -import { hashRequest } from './tronEncoding'; -import { getPrivateKey } from './tronService'; - -async function getEstimatePayload({ - tronApi, - tron, - recipient, - amount -}: { - tronApi: Configuration; - tron: TronWalletStorage; - recipient: TronRecipient; - amount: AssetAmount; -}): Promise { - return new TronApi(tronApi).getEstimation({ - ownerAddress: tron.ownerWalletAddress, - getEstimationRequest: { - lifeTime: Math.floor(Date.now() / 1000) + 600, - messages: [ - { - to: recipient.address, - value: amount.stringWeiAmount, - assetAddress: amount.asset.address - } - ] - } - }); -} - -export async function sendTronTransfer( - { - tronApi, - tron, - request - }: { - tronApi: Configuration; - tron: TronWalletStorage; - request: RequestData; - }, - { mnemonic }: { mnemonic: string[] } -): Promise { - const settings = await new TronApi(tronApi).getSettings(); - - const hash = hashRequest(request, settings.walletImplementation, settings.chainId); - - const privateKey = await getPrivateKey(mnemonic); - const signingKey = new ethers.SigningKey('0x' + privateKey); - const signature = signingKey.sign(hash).serialized; - - return new TronApi(tronApi).publishTransaction({ - ownerAddress: tron.ownerWalletAddress, - publishTransactionRequest: { - request, - signature, - hash - } - }); -} - -/*async function estimateTronFee({ - wallet, - tronApi, - address, - amount -}: { - wallet: DeprecatedWalletState; - tronApi: Configuration; - address: TronRecipient; - amount: AssetAmount; -}) { - const payload = await getEstimatePayload({ - tron: wallet.tron!, - tronApi, - recipient: address, - amount: new AssetAmount({ asset: amount.asset, weiAmount: new BigNumber('1') }) - }); - - return payload.request.fee; -}*/ - -/* -export async function estimateTron({ - recipient, - amount, - isMax, - tronApi, - wallet, - balances -}: { - recipient: RecipientData; - amount: AssetAmount; - isMax: boolean; - tronApi: Configuration; - wallet: DeprecatedWalletState; - balances: TronBalances | undefined; -}): Promise> { - if (isMax) { - const fee = await estimateTronFee({ - wallet, - tronApi, - address: recipient.address as TronRecipient, - amount - }); - - amount = new AssetAmount({ asset: amount.asset, weiAmount: amount.weiAmount.minus(fee) }); - } - - const payload = await getEstimatePayload({ - tron: wallet.tron!, - tronApi, - recipient: recipient.address as TronRecipient, - amount: amount - }); - - if (payload.internalMsgs.some(item => item === false)) { - throw new Error('Estimation fail.'); - } - - const feeToken = balances?.balances.find( - item => item.token.address === payload.request.feeToken - ); - if (!feeToken) { - throw new Error(`Unexpected feeToken, token's address is ${payload.request.feeToken}`); - } - - const fee = new AssetAmount({ - asset: toTronAsset(feeToken), - weiAmount: payload.request.fee - }); - - return { fee, payload }; -} -*/ diff --git a/packages/core/src/service/tron/tronUtils.ts b/packages/core/src/service/tron/tronUtils.ts deleted file mode 100644 index 92dbb5a05..000000000 --- a/packages/core/src/service/tron/tronUtils.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { AbiCoder, decodeBase58, encodeBase58, ethers, sha256 } from 'ethers'; -export const ADDRESS_PREFIX_REGEX = /^(41)/; - -export const TronAddress = { - hexToBase58(address: string): string { - const tronAddressPayload = '0x41' + address.slice(2); - const checkSumTail = sha256(sha256(tronAddressPayload)).slice(2, 10); - return encodeBase58(tronAddressPayload + checkSumTail); - }, - base58ToHex(address: string): string { - const decoded = decodeBase58(address).toString(16); - return decoded.slice(0, -8); - } -}; - -export function keccak256(value: string) { - if (!value.startsWith('0x')) { - return ethers.keccak256(ethers.toUtf8Bytes(value)); - } - return ethers.keccak256(value); -} - -export function encodeTronParams(types: string[], values: unknown[]) { - for (let i = 0; i < types.length; i++) { - if (types[i] === 'address') { - values[i] = TronAddress.base58ToHex(values[i] as string).replace( - ADDRESS_PREFIX_REGEX, - '0x' - ); - } - } - - return new AbiCoder().encode(types, values); -} - -export function encodePackedBytes(values: string[]) { - return '0x' + values.map(x => x.replace('0x', '')).join(''); -} diff --git a/packages/locales/src/tonkeeper-web/en.json b/packages/locales/src/tonkeeper-web/en.json index 446e139a7..12ba0b576 100644 --- a/packages/locales/src/tonkeeper-web/en.json +++ b/packages/locales/src/tonkeeper-web/en.json @@ -8,6 +8,7 @@ "accounts_manage_folder_name": "Folder Name", "accounts_new_folder": "New Folder", "actionTitle": "Open the wallet", + "activate": "Activate", "add": "Add", "add_dns_address": "Add wallet address that domain %1% will link to.", "add_wallet_existing_multisig_description": "%{number} wallets managed by your wallet list", diff --git a/packages/locales/src/tonkeeper-web/ru-RU.json b/packages/locales/src/tonkeeper-web/ru-RU.json index 8dc3e3a3f..d5248632d 100644 --- a/packages/locales/src/tonkeeper-web/ru-RU.json +++ b/packages/locales/src/tonkeeper-web/ru-RU.json @@ -8,6 +8,7 @@ "accounts_manage_folder_name": "Имя папки", "accounts_new_folder": "Новая папка", "actionTitle": "Открыть Кошелек", + "activate": "Активировать", "add": "Добавить", "add_dns_address": "Добавьте адрес кошелька, на который будет ссылаться домен %1%", "add_wallet_existing_multisig_description": "%{number} кошельков управляются вашим списком кошельков", diff --git a/packages/uikit/src/components/home/AccountView.tsx b/packages/uikit/src/components/home/AccountView.tsx index 234ceb737..824367233 100644 --- a/packages/uikit/src/components/home/AccountView.tsx +++ b/packages/uikit/src/components/home/AccountView.tsx @@ -1,5 +1,4 @@ import { BLOCKCHAIN_NAME } from '@tonkeeper/core/dist/entries/crypto'; -import { TronWalletState } from '@tonkeeper/core/dist/entries/wallet'; import { formatAddress, formatTransferUrl } from '@tonkeeper/core/dist/utils/common'; import { FC, useRef, useState } from 'react'; import { QRCode } from 'react-qrcode-logo'; @@ -8,7 +7,6 @@ import styled, { css } from 'styled-components'; import { useAppContext } from '../../hooks/appContext'; import { useAppSdk } from '../../hooks/appSdk'; import { useTranslation } from '../../hooks/translation'; -import { useTronWalletState } from '../../state/tron/tron'; import { CopyIcon } from '../Icon'; import { FullHeightBlockResponsive, @@ -199,7 +197,7 @@ const ReceiveTon: FC<{ jetton?: string }> = ({ jetton }) => { ); }; -const ReceiveTron: FC<{ tron: TronWalletState }> = ({ tron }) => { +const ReceiveTron: FC<{ tron: any }> = ({ tron }) => { const sdk = useAppSdk(); const { t } = useTranslation(); const { extension } = useAppContext(); @@ -241,11 +239,10 @@ export const ReceiveContent: FC<{ }> = ({ chain = BLOCKCHAIN_NAME.TON, jetton, handleClose }) => { const { standalone } = useAppContext(); const [active] = useState(chain); - const { data: tron } = useTronWalletState(active === BLOCKCHAIN_NAME.TRON); const tonRef = useRef(null); const tronRef = useRef(null); - const isTon = active === BLOCKCHAIN_NAME.TON || !tron; + const isTon = active === BLOCKCHAIN_NAME.TON; /*|| !tron*/ const nodeRef = isTon ? tonRef : tronRef; const state = isTon ? 'ton' : 'tron'; @@ -280,7 +277,7 @@ export const ReceiveContent: FC<{ } /> ) : ( - + )} diff --git a/packages/uikit/src/components/home/Jettons.tsx b/packages/uikit/src/components/home/Jettons.tsx index abb3d3843..e7f5c08d1 100644 --- a/packages/uikit/src/components/home/Jettons.tsx +++ b/packages/uikit/src/components/home/Jettons.tsx @@ -1,7 +1,5 @@ -import { Address } from '@ton/core'; import { CryptoCurrency } from '@tonkeeper/core/dist/entries/crypto'; import { Account, JettonBalance, JettonsBalances } from '@tonkeeper/core/dist/tonApiV2'; -import { TronBalances } from '@tonkeeper/core/dist/tronApi'; import { formatDecimals } from '@tonkeeper/core/dist/utils/balance'; import { FC, forwardRef, useMemo } from 'react'; import { useNavigate } from 'react-router-dom'; @@ -12,6 +10,7 @@ import { AppRoute } from '../../libs/routes'; import { toTokenRate, useFormatFiat, useRate } from '../../state/rates'; import { ListBlock, ListItem } from '../List'; import { ListItemPayload, TokenLayout, TokenLogo } from './TokenLayout'; +import { TronBalances } from '../../state/tron/tron'; export interface TonAssetData { info: Account; @@ -70,13 +69,7 @@ export const JettonAsset = forwardRef< const { t } = useTranslation(); const navigate = useNavigate(); const { fiat } = useAppContext(); - const [amount, address] = useMemo( - () => [ - formatDecimals(jetton.balance, jetton.jetton.decimals), - Address.parse(jetton.jetton.address).toString() - ], - [jetton] - ); + const amount = useMemo(() => formatDecimals(jetton.balance, jetton.jetton.decimals), [jetton]); const balance = useFormatBalance(amount, jetton.jetton.decimals); const data = useMemo( @@ -111,8 +104,7 @@ export const JettonAsset = forwardRef< export const JettonList: FC = ({ assets: { - ton: { info, jettons }, - tron: _tron + ton: { info, jettons } } }) => { return ( diff --git a/packages/uikit/src/components/home/TronAssets.tsx b/packages/uikit/src/components/home/TronAssets.tsx index 2d1464dd5..c93bbe0c3 100644 --- a/packages/uikit/src/components/home/TronAssets.tsx +++ b/packages/uikit/src/components/home/TronAssets.tsx @@ -1,43 +1,96 @@ -import { TronBalances } from '@tonkeeper/core/dist/tronApi'; -import React, { FC } from 'react'; +import React, { FC, useMemo } from 'react'; import { useNavigate } from 'react-router-dom'; import { AppRoute } from '../../libs/routes'; -import { useFormatFiat, useRate } from '../../state/rates'; +import { useFormatFiat } from '../../state/rates'; import { ListItem } from '../List'; import { ListItemPayload, TokenLayout, TokenLogo } from './TokenLayout'; import { AssetAmount } from '@tonkeeper/core/dist/entries/crypto/asset/asset-amount'; import { TronAsset } from '@tonkeeper/core/dist/entries/crypto/asset/tron-asset'; +import { TRON_USDT_ASSET } from '@tonkeeper/core/dist/entries/crypto/asset/constants'; +import { TronBalances } from '../../state/tron/tron'; +import { Button } from '../fields/Button'; +import { formatFiatCurrency } from '../../hooks/balance'; +import { useAppContext } from '../../hooks/appContext'; +import { useTranslation } from '../../hooks/translation'; +import { useAddTronToAccount } from '../../state/wallet'; -const TronAsset: FC<{ assetAmount: AssetAmount }> = ({ assetAmount }) => { +const usdtRate = { + diff7d: '', + diff24h: '', + prices: 1 +}; + +const tronAssetRate = (asset: TronAsset) => { + if (asset.id === TRON_USDT_ASSET.id) { + return usdtRate; + } + + return undefined; +}; + +const TronAssetComponent: FC<{ assetAmount: AssetAmount; className?: string }> = ({ + assetAmount, + className +}) => { const navigate = useNavigate(); - const { data } = useRate(token.symbol); - const { fiatPrice, fiatAmount } = useFormatFiat(data, amount); + const rate = useMemo(() => tronAssetRate(assetAmount.asset), [assetAmount.asset.id]); + + const { fiatPrice, fiatAmount } = useFormatFiat(rate, assetAmount.relativeAmount); return ( - navigate(AppRoute.coins + '/tron/' + token.address)}> + navigate(AppRoute.coins + '/' + assetAmount.asset.id)} + className={className} + > ); }; -export const TronAssets: FC<{ tokens: TronBalances }> = React.memo(({ tokens }) => { +const InactiveUSDA: FC<{ className?: string }> = ({ className }) => { + const { t } = useTranslation(); + const { fiat } = useAppContext(); + + const { mutate, isLoading } = useAddTronToAccount(); + return ( - <> - {tokens.balances.map(({ token, weiAmount }) => ( - - ))} - + + + + + + + ); -}); +}; + +export const TronAssets: FC<{ balances: TronBalances; className?: string }> = React.memo( + ({ balances, className }) => { + if (!balances) { + return ; + } + return ; + } +); diff --git a/packages/uikit/src/components/transfer/SendNotifications.tsx b/packages/uikit/src/components/transfer/SendNotifications.tsx index 00a82d5ae..52fb71c2a 100644 --- a/packages/uikit/src/components/transfer/SendNotifications.tsx +++ b/packages/uikit/src/components/transfer/SendNotifications.tsx @@ -1,7 +1,6 @@ import { TransferInitParams } from '@tonkeeper/core/dist/AppSdk'; import { BLOCKCHAIN_NAME } from '@tonkeeper/core/dist/entries/crypto'; import { AssetAmount } from '@tonkeeper/core/dist/entries/crypto/asset/asset-amount'; -import { toTronAsset } from '@tonkeeper/core/dist/entries/crypto/asset/constants'; import { jettonToTonAsset, TonAsset } from '@tonkeeper/core/dist/entries/crypto/asset/ton-asset'; import { RecipientData, TonRecipientData } from '@tonkeeper/core/dist/entries/send'; import { @@ -115,9 +114,9 @@ const SendContent: FC<{ } _setRecipient(value); - if (tronBalances && value.address.blockchain === BLOCKCHAIN_NAME.TRON) { + /* if (tronBalances && value.address.blockchain === BLOCKCHAIN_NAME.TRON) { setAmountViewState({ token: toTronAsset(tronBalances.balances[0]) }); - } + }*/ }; const onRecipient = (data: RecipientData) => { diff --git a/packages/uikit/src/desktop-pages/coin/DesktopCoinPage.tsx b/packages/uikit/src/desktop-pages/coin/DesktopCoinPage.tsx index 3e5d2761f..159774cca 100644 --- a/packages/uikit/src/desktop-pages/coin/DesktopCoinPage.tsx +++ b/packages/uikit/src/desktop-pages/coin/DesktopCoinPage.tsx @@ -1,10 +1,9 @@ import { BLOCKCHAIN_NAME, CryptoCurrency } from '@tonkeeper/core/dist/entries/crypto'; -import { tonAssetAddressFromString } from '@tonkeeper/core/dist/entries/crypto/asset/ton-asset'; import { eqAddresses } from '@tonkeeper/core/dist/utils/address'; import { shiftedDecimals } from '@tonkeeper/core/dist/utils/balance'; import BigNumber from 'bignumber.js'; import { FC, useEffect, useMemo, useRef } from 'react'; -import { useNavigate, useParams } from 'react-router-dom'; +import { Navigate, useNavigate, useParams } from 'react-router-dom'; import styled from 'styled-components'; import { ArrowDownIcon, ArrowUpIcon, PlusIcon, SwapIcon } from '../../components/Icon'; import { Body2, Label2, Num3 } from '../../components/Text'; @@ -33,6 +32,10 @@ import { useActiveTonNetwork, useIsActiveWalletWatchOnly } from '../../state/wal import { OtherHistoryFilters } from '../../components/desktop/history/DesktopHistoryFilters'; import { Network } from '@tonkeeper/core/dist/entries/network'; import { HideOnReview } from '../../components/ios/HideOnReview'; +import { TRON_USDT_ASSET } from '@tonkeeper/core/dist/entries/crypto/asset/constants'; +import { tonAssetAddressFromString } from '@tonkeeper/core/dist/entries/crypto/asset/ton-asset'; +import { useActiveTronWallet, useTronBalances } from '../../state/tron/tron'; +import { AssetAmount } from '@tonkeeper/core/dist/entries/crypto/asset/asset-amount'; export const DesktopCoinPage = () => { const navigate = useNavigate(); @@ -44,10 +47,19 @@ export const DesktopCoinPage = () => { } }, [name]); + const canUseTron = useActiveTronWallet(); + if (!name) return <>; const token = name === 'ton' ? CryptoCurrency.TON : name; + if (token === TRON_USDT_ASSET.id) { + if (!canUseTron) { + return ; + } + return ; + } + return ; }; @@ -264,7 +276,7 @@ const DesktopViewHeaderStyled = styled(DesktopViewHeader)` padding-right: 0; `; -export const CoinPage: FC<{ token: string }> = ({ token }) => { +const CoinPage: FC<{ token: string }> = ({ token }) => { const { t } = useTranslation(); const ref = useRef(null); @@ -306,3 +318,57 @@ export const CoinPage: FC<{ token: string }> = ({ token }) => { ); }; + +export const TronUSDTPage = () => { + const { t } = useTranslation(); + + const asset = TRON_USDT_ASSET; + const { fiat } = useAppContext(); + const { data: balances } = useTronBalances(); + + const usdtBalance = useMemo(() => { + if (balances === undefined) { + return undefined; + } + + if (balances === null) { + return new AssetAmount({ weiAmount: 0, asset: TRON_USDT_ASSET }); + } + + return balances.usdt; + }, [balances]); + + return ( + + + {asset.symbol} + + + + {asset.symbol} + {usdtBalance !== undefined && ( + + {usdtBalance.stringAssetRelativeAmount} + {formatFiatCurrency(fiat, usdtBalance.relativeAmount)} + + )} + + + {}} + disabled={usdtBalance?.weiAmount.isZero()} + > + + {t('wallet_send')} + + {}}> + + {t('wallet_receive')} + + + + {t('page_header_history')} + + ); +}; diff --git a/packages/uikit/src/desktop-pages/tokens/DesktopTokens.tsx b/packages/uikit/src/desktop-pages/tokens/DesktopTokens.tsx index fd4ea4218..2de9f23b2 100644 --- a/packages/uikit/src/desktop-pages/tokens/DesktopTokens.tsx +++ b/packages/uikit/src/desktop-pages/tokens/DesktopTokens.tsx @@ -16,6 +16,8 @@ import { useAssets } from '../../state/home'; import { useMutateUserUIPreferences, useUserUIPreferences } from '../../state/theme'; import { useAssetsDistribution } from '../../state/asset'; +import { TronAssets } from '../../components/home/TronAssets'; +import { useCanUseTronForActiveWallet, useTronBalances } from '../../state/tron/tron'; const DesktopAssetStylesOverride = css` background-color: transparent; @@ -33,6 +35,12 @@ const TonAssetStyled = styled(TonAsset)` ${DesktopAssetStylesOverride} `; +const TronAssetsStyled = styled(TronAssets)` + margin: 0 -16px; + + ${DesktopAssetStylesOverride} +`; + const JettonAssetStyled = styled(JettonAsset)` ${DesktopAssetStylesOverride} `; @@ -82,6 +90,9 @@ const DesktopTokensPayload = () => { const tonRef = useRef(null); const containerRef = useRef(null); + const { data: tronBalances } = useTronBalances(); + const canUseTron = useCanUseTronForActiveWallet(); + useLayoutEffect(() => { if (uiPreferences?.showTokensChart !== undefined) { setShowChart(uiPreferences.showTokensChart); @@ -99,12 +110,15 @@ const DesktopTokensPayload = () => { return assets?.ton?.jettons?.balances ?? []; }, [assets]); + const virtualScrollPaddingBase = canUseTron ? 2 * itemSize : itemSize; + const rowVirtualizer = useVirtualizer({ count: sortedAssets.length, getScrollElement: () => containerRef.current, estimateSize: () => itemSize, getItemKey: index => sortedAssets[index].jetton.address, - paddingStart: canShowChart && showChart ? 192 + itemSize : itemSize + paddingStart: + canShowChart && showChart ? 192 + virtualScrollPaddingBase : virtualScrollPaddingBase }); const onTokenClick = useCallback( @@ -150,45 +164,59 @@ const DesktopTokensPayload = () => { overflow: 'hidden' }} > - {sortedAssets && assets && distribution && uiPreferences && ( - <> - {canShowChart && showChart && ( - - - - - )} - - - {rowVirtualizer.getVirtualItems().map(virtualRow => ( -
+ {sortedAssets && + assets && + distribution && + uiPreferences && + tronBalances !== undefined && ( + <> + {canShowChart && showChart && ( - + -
- ))} - - )} + )} + + + {canUseTron && ( + <> + + + + )} + {rowVirtualizer.getVirtualItems().map(virtualRow => ( +
+ + + + +
+ ))} + + )} ); diff --git a/packages/uikit/src/state/home.ts b/packages/uikit/src/state/home.ts index e1c21387b..14248381e 100644 --- a/packages/uikit/src/state/home.ts +++ b/packages/uikit/src/state/home.ts @@ -6,23 +6,30 @@ import { AssetData } from '../components/home/Jettons'; import { useJettonList } from './jetton'; import { useActiveWallet, useWalletAccountInfo } from './wallet'; import { AssetAmount } from '@tonkeeper/core/dist/entries/crypto/asset/asset-amount'; -import { TON_ASSET } from '@tonkeeper/core/dist/entries/crypto/asset/constants'; +import { + TON_ASSET, + TRON_TRX_ASSET, + TRON_USDT_ASSET +} from '@tonkeeper/core/dist/entries/crypto/asset/constants'; import { jettonToTonAssetAmount } from '@tonkeeper/core/dist/entries/crypto/asset/ton-asset'; import { packAssetId } from '@tonkeeper/core/dist/entries/crypto/asset/basic-asset'; +import { useTronBalances } from './tron/tron'; +import { assertUnreachable } from '@tonkeeper/core/dist/utils/types'; export const useAssets = () => { const wallet = useActiveWallet(); const { data: info, error, isFetching: isAccountLoading } = useWalletAccountInfo(); const { data: jettons, error: jettonError, isFetching: isJettonLoading } = useJettonList(); + const { data: tronBalances } = useTronBalances(); const assets = useMemo(() => { - if (!info || !jettons) return undefined; + if (!info || !jettons || tronBalances === undefined) return undefined; return { ton: { info, jettons: jettons ?? { balances: [] } }, - tron: { balances: [] } + tron: tronBalances }; - }, [info, jettons, wallet]); + }, [info, jettons, wallet, tronBalances]); return [assets, error, isJettonLoading || isAccountLoading, jettonError] as const; }; @@ -43,11 +50,17 @@ export const useAssetWeiBalance = (asset: AssetIdentification) => { return new BigNumber( assets.ton.jettons.balances.find(i => i.jetton.address === jAddress)?.balance || '0' ); + } else if (asset.blockchain === BLOCKCHAIN_NAME.TRON) { + if (asset.address === TRON_USDT_ASSET.address) { + return assets.tron?.usdt.weiAmount || new BigNumber(0); + } else if (asset.address === TRON_TRX_ASSET.address) { + return assets.tron?.trx.weiAmount || new BigNumber(0); + } else { + throw new Error('Unexpected asset'); + } + } else { + assertUnreachable(asset); } - - return new BigNumber( - assets.tron.balances.find(i => i.token.address === asset.address)?.weiAmount || '0' - ); }, [assets, asset]); }; diff --git a/packages/uikit/src/state/mixedActivity.ts b/packages/uikit/src/state/mixedActivity.ts index b49f823d5..c2fa56df6 100644 --- a/packages/uikit/src/state/mixedActivity.ts +++ b/packages/uikit/src/state/mixedActivity.ts @@ -1,8 +1,10 @@ import { InfiniteData } from '@tanstack/react-query'; import { AccountEvent, AccountEvents } from '@tonkeeper/core/dist/tonApiV2'; -import { TronEvent, TronEvents } from '@tonkeeper/core/dist/tronApi'; import { GenericActivity, groupGenericActivity } from './activity'; +type TronEvent = any; +type TronEvents = any; // TODO + export interface TronActivity { kind: 'tron'; event: TronEvent; diff --git a/packages/uikit/src/state/tron/tron.ts b/packages/uikit/src/state/tron/tron.ts index 8680f84bc..a185ffb89 100644 --- a/packages/uikit/src/state/tron/tron.ts +++ b/packages/uikit/src/state/tron/tron.ts @@ -23,6 +23,11 @@ export const useTronApi = () => { return useMemo(() => new TronApi(apiUrl, apiKey), [apiKey, apiUrl]); }; +export const useCanUseTronForActiveWallet = () => { + const account = useActiveAccount(); + return isAccountTronCompatible(account); +}; + export const useActiveTronWallet = (): TronWallet | undefined => { const account = useActiveAccount(); @@ -33,11 +38,13 @@ export const useActiveTronWallet = (): TronWallet | undefined => { return undefined; }; +export type TronBalances = { trx: AssetAmount; usdt: AssetAmount } | null; + export const useTronBalances = () => { const tronApi = useTronApi(); const activeWallet = useActiveTronWallet(); - return useQuery<{ trx: AssetAmount; usdt: AssetAmount } | null, Error>( + return useQuery( [QueryKey.tronAssets, activeWallet?.address], async () => { if (!activeWallet) { diff --git a/packages/uikit/src/state/wallet.ts b/packages/uikit/src/state/wallet.ts index 21d520758..51d0c991b 100644 --- a/packages/uikit/src/state/wallet.ts +++ b/packages/uikit/src/state/wallet.ts @@ -487,10 +487,13 @@ export const useAddTronToAccount = () => { let updatedAccount: Account; switch (activeAccount.type) { case 'mnemonic': - updatedAccount = standardTonAccountToAccountWithTron(activeAccount, getMnemonic); + updatedAccount = await standardTonAccountToAccountWithTron( + activeAccount, + getMnemonic + ); break; case 'mam': - updatedAccount = mamAccountToMamAccountWithTron(activeAccount); + updatedAccount = await mamAccountToMamAccountWithTron(activeAccount, getMnemonic); break; default: assertUnreachable(activeAccount); From 04fb8cf6e2d439508d4403ad4db41de41a25ffca Mon Sep 17 00:00:00 2001 From: siandreev Date: Fri, 20 Dec 2024 20:46:32 +0100 Subject: [PATCH 03/20] feat: add tron receive notification --- .../src/entries/crypto/asset/constants.ts | 2 +- packages/locales/src/tonkeeper-web/en.json | 5 + packages/locales/src/tonkeeper-web/ru-RU.json | 5 + packages/uikit/src/components/Tabs.tsx | 11 +- .../uikit/src/components/home/AccountView.tsx | 77 +++++++++--- .../uikit/src/components/home/TronAssets.tsx | 13 +- .../desktop-pages/coin/DesktopCoinPage.tsx | 114 +++++++++++++++++- 7 files changed, 200 insertions(+), 27 deletions(-) diff --git a/packages/core/src/entries/crypto/asset/constants.ts b/packages/core/src/entries/crypto/asset/constants.ts index 6bd8889e7..bfae1b3f8 100644 --- a/packages/core/src/entries/crypto/asset/constants.ts +++ b/packages/core/src/entries/crypto/asset/constants.ts @@ -11,7 +11,7 @@ export const TRON_USDT_ASSET: TronAsset = { decimals: 6, address: 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t', blockchain: BLOCKCHAIN_NAME.TRON, - image: 'https://wallet.tonkeeper.com/img/usdt.svg' + image: 'https://wallet.tonkeeper.com/img/usdt-trc20.png' }; export const TRON_TRX_ASSET: TronAsset = { diff --git a/packages/locales/src/tonkeeper-web/en.json b/packages/locales/src/tonkeeper-web/en.json index 12ba0b576..94d220858 100644 --- a/packages/locales/src/tonkeeper-web/en.json +++ b/packages/locales/src/tonkeeper-web/en.json @@ -297,6 +297,8 @@ "receive_ton_description": "Send only Toncoin TON and tokens\nin TON network to this address, or you\nmight lose your funds.", "receive_trc20": "Receive USDT TRC20", "receive_trc20_description": "Send only USDT TRC20\nto this address, or you might\nlose your funds.", + "receive_trx": "Receive Tron TRX", + "receive_trx_description": "Send only Tron TRX to this address, or you might lose your funds.", "recipients": "Recipients", "Redo": "Redo", "Reload": "Reload", @@ -376,6 +378,9 @@ "transaction_call_date": "Contract Call %{date}", "transaction_type_mint": "Mint", "transaction_type_purchase": "Purchase", + "tron_top_up_trx_button": "Top Up TRX", + "tron_top_up_trx_description": "You need TRX to pay transaction fees. Balance: {balance}", + "tron_top_up_trx_title": "TRX is required for USD₮ TRC20", "try_again": "Try Again", "two_fa_confirm_tg_cannot_access_tg": "Can’t access your Telegram account?", "two_fa_confirm_tg_description": "Go to the @tonkeeper_2fa_bot and tap «Confirm» to complete the transaction.", diff --git a/packages/locales/src/tonkeeper-web/ru-RU.json b/packages/locales/src/tonkeeper-web/ru-RU.json index d5248632d..cd8cb401d 100644 --- a/packages/locales/src/tonkeeper-web/ru-RU.json +++ b/packages/locales/src/tonkeeper-web/ru-RU.json @@ -286,6 +286,8 @@ "receive_ton_description": "Отправляйте на этот адрес только Toncoin TON и токены в сети TON, иначе вы можете потерять свои средства.", "receive_trc20": "Получить USDT TRC20", "receive_trc20_description": "Отправляйте на этот адрес только USDT TRC20, иначе вы можете потерять свои средства.", + "receive_trx": "Получить Tron TRX", + "receive_trx_description": "Отправляйте на этот адрес только Tron TRX, иначе вы можете потерять свои средства.", "recipients": "Получатели", "Redo": "Повторить", "Reload": "Перезагрузить", @@ -363,6 +365,9 @@ "transaction_call_date": "Вызов контракта %{date}", "transaction_type_mint": "Создание", "transaction_type_purchase": "Покупка", + "tron_top_up_trx_button": "Пополнить TRX", + "tron_top_up_trx_description": "Вам нужны TRX для оплаты комиссий за транзакции. Баланс: {balance}", + "tron_top_up_trx_title": "TRX требуется для операций с USD₮ TRC20", "try_again": "Повторить", "two_fa_confirm_tg_cannot_access_tg": "Не можете получить доступ к своему Telegram аккаунту?", "two_fa_confirm_tg_description": "Перейдите в @tonkeeper_2fa_bot и нажмите «Подтвердить», чтобы завершить транзакцию.", diff --git a/packages/uikit/src/components/Tabs.tsx b/packages/uikit/src/components/Tabs.tsx index ffae951b0..658552f9d 100644 --- a/packages/uikit/src/components/Tabs.tsx +++ b/packages/uikit/src/components/Tabs.tsx @@ -4,26 +4,29 @@ import { Button } from './fields/Button'; const TabsBlock = styled.div` display: flex; - padding: 3px; + padding: 4px; position: relative; justify-content: center; gap: 3px; user-select: none; - border-radius: ${props => props.theme.cornerMedium}; + border-radius: ${props => props.theme.corner2xSmall}; background: ${props => props.theme.backgroundContent}; + width: fit-content; `; export function Tabs({ active, values, - setActive + setActive, + className }: { active: T; values: { name: string; id: T }[]; setActive: (value: T) => void; + className?: string; }) { return ( - + {values.map(item => ( + + + + ); +}; From 3257dc82020da19eb2c0ae54122c66e05e206f2f Mon Sep 17 00:00:00 2001 From: siandreev Date: Mon, 23 Dec 2024 09:45:21 +0100 Subject: [PATCH 04/20] fix: send notification tron --- packages/core/src/AppSdk.ts | 6 ++++- .../modals/useSendTransferNotification.ts | 10 +++++---- .../components/transfer/SendNotifications.tsx | 22 +++++++++++++------ packages/uikit/src/state/asset.ts | 22 ++++++++----------- 4 files changed, 35 insertions(+), 25 deletions(-) diff --git a/packages/core/src/AppSdk.ts b/packages/core/src/AppSdk.ts index 0c29413ac..532074bdb 100644 --- a/packages/core/src/AppSdk.ts +++ b/packages/core/src/AppSdk.ts @@ -16,7 +16,11 @@ export type TransferInitParams = chain: BLOCKCHAIN_NAME.TON; from: string; }) - | Record; + | { + chain: BLOCKCHAIN_NAME.TRON; + from: string; + address?: string; + }; export type ReceiveInitParams = { chain?: BLOCKCHAIN_NAME; diff --git a/packages/uikit/src/components/modals/useSendTransferNotification.ts b/packages/uikit/src/components/modals/useSendTransferNotification.ts index ad6a7787d..21eda3bfc 100644 --- a/packages/uikit/src/components/modals/useSendTransferNotification.ts +++ b/packages/uikit/src/components/modals/useSendTransferNotification.ts @@ -1,17 +1,19 @@ import { useAppSdk } from '../../hooks/appSdk'; -import { BLOCKCHAIN_NAME } from '@tonkeeper/core/dist/entries/crypto'; import { useCallback } from 'react'; -import { TonTransferParams } from '@tonkeeper/core/dist/service/deeplinkingService'; +import { TransferInitParams } from '@tonkeeper/core/dist/AppSdk'; export const useSendTransferNotification = () => { const sdk = useAppSdk(); const onOpen = useCallback( - (params?: TonTransferParams) => { + (params?: TransferInitParams) => { sdk.uiEvents.emit('transfer', { method: 'transfer', id: Date.now(), - params: { chain: BLOCKCHAIN_NAME.TON, ...params, from: 'wallet' } + params: { + ...params, + from: 'wallet' + } }); }, [sdk] diff --git a/packages/uikit/src/components/transfer/SendNotifications.tsx b/packages/uikit/src/components/transfer/SendNotifications.tsx index 52fb71c2a..f8686f1a1 100644 --- a/packages/uikit/src/components/transfer/SendNotifications.tsx +++ b/packages/uikit/src/components/transfer/SendNotifications.tsx @@ -54,6 +54,8 @@ import { MultisigOrderLifetimeMinutes } from '../../libs/multisig'; import { useIsActiveAccountMultisig } from '../../state/multisig'; import { ConfirmMultisigNewTransferView } from './ConfirmMultisigNewTransferView'; import { useAnalyticsTrack } from '../../hooks/amplitude'; +import { TRON_USDT_ASSET } from '@tonkeeper/core/dist/entries/crypto/asset/constants'; +import { seeIfValidTronAddress } from '@tonkeeper/core/dist/utils/common'; const SendContent: FC<{ onClose: () => void; @@ -114,9 +116,9 @@ const SendContent: FC<{ } _setRecipient(value); - /* if (tronBalances && value.address.blockchain === BLOCKCHAIN_NAME.TRON) { - setAmountViewState({ token: toTronAsset(tronBalances.balances[0]) }); - }*/ + if (tronBalances && value.address.blockchain === BLOCKCHAIN_NAME.TRON) { + setAmountViewState({ token: TRON_USDT_ASSET }); + } }; const onRecipient = (data: RecipientData) => { @@ -228,10 +230,9 @@ const SendContent: FC<{ return; } - // TODO: ENABLE TRON - // if (seeIfValidTronAddress(signature)) { - // return processTron(signature); - // } + if (seeIfValidTronAddress(signature)) { + return processTron(signature); + } return sdk.uiEvents.emit('copy', { method: 'copy', @@ -424,6 +425,13 @@ const SendActionNotification = () => { const transfer = options.params; setChain(options.params.chain); + + if (transfer.chain === BLOCKCHAIN_NAME.TRON) { + setOpen(true); + track('send_open', { from: transfer.from }); + return; + } + if (transfer.address) { getAccountAsync({ address: transfer.address }).then(account => { setTonTransfer(makeTransferInitData(transfer, account, jettons)); diff --git a/packages/uikit/src/state/asset.ts b/packages/uikit/src/state/asset.ts index 35d9e3ddb..ee990f8b7 100644 --- a/packages/uikit/src/state/asset.ts +++ b/packages/uikit/src/state/asset.ts @@ -8,10 +8,10 @@ import { isBasicAsset, packAssetId } from '@tonkeeper/core/dist/entries/crypto/a import { KNOWN_TON_ASSETS, TON_ASSET, + TRON_TRX_ASSET, TRON_USDT_ASSET } from '@tonkeeper/core/dist/entries/crypto/asset/constants'; import { TonAsset, legacyTonAssetId } from '@tonkeeper/core/dist/entries/crypto/asset/ton-asset'; -import { TronAsset } from '@tonkeeper/core/dist/entries/crypto/asset/tron-asset'; import { DashboardCellNumeric } from '@tonkeeper/core/dist/entries/dashboard'; import { getDashboardData } from '@tonkeeper/core/dist/service/proService'; import { JettonBalance } from '@tonkeeper/core/dist/tonApiV2'; @@ -61,13 +61,10 @@ export function useUserAssetBalance< } } else { isLoading = tronBalances.isLoading; - const token = tronBalances.data?.balances.find(i => i.token.address === asset.address); - if (token && isBasicAsset(asset)) { - data = new AssetAmount({ - asset, - weiAmount: token.weiAmount, - image: token.token.image - }); + if (asset.address === TRON_USDT_ASSET.address) { + data = tronBalances.data?.usdt; + } else if (asset.address === TRON_TRX_ASSET.address) { + data = tronBalances.data?.trx; } else { data = '0'; } @@ -82,7 +79,6 @@ export function useUserAssetBalance< export function useAssetImage({ blockchain, address }: AssetIdentification): string | undefined { const id = packAssetId(blockchain, address); const { data: jettons } = useJettonList(); - const { data: balances } = useTronBalances(); if (id === TON_ASSET.id) { return 'https://wallet.tonkeeper.com/img/toncoin.svg'; @@ -93,11 +89,11 @@ export function useAssetImage({ blockchain, address }: AssetIdentification): str } if (typeof address === 'string') { - return balances?.balances.find(i => i.token.address === address)?.token.image; - } else { - return jettons?.balances.find(i => address.equals(Address.parse(i.jetton.address)))?.jetton - .image; + throw new Error('Unexpected address'); } + + return jettons?.balances.find(i => address.equals(Address.parse(i.jetton.address)))?.jetton + .image; } export function useAssetAmountFiatEquivalent(assetAmount: AssetAmount): { From 65718e843d1dfa9e6ab0022165b8fb1da079bef6 Mon Sep 17 00:00:00 2001 From: siandreev Date: Mon, 23 Dec 2024 14:30:51 +0100 Subject: [PATCH 05/20] fix: send tron usdt implemented --- packages/core/package.json | 3 +- packages/core/src/AppSdk.ts | 3 +- packages/core/src/entries/account.ts | 12 ++ .../src/entries/crypto/asset/constants.ts | 6 +- packages/core/src/entries/signer.ts | 3 + .../core/src/service/suggestionService.ts | 34 +---- .../service/tron-blockchain/tron-sender.ts | 84 +++++++++++ .../core/src/service/tron-blockchain/types.ts | 3 + packages/core/src/service/walletService.ts | 13 ++ packages/core/src/tronApi/index.ts | 141 ++++++++++++++++-- packages/uikit/package.json | 4 +- .../modals/useSendTransferNotification.ts | 2 +- .../src/components/transfer/RecipientView.tsx | 17 --- .../components/transfer/SuggestionList.tsx | 5 +- .../desktop-pages/coin/DesktopCoinPage.tsx | 4 +- .../hooks/blockchain/useEstimateTransfer.ts | 26 +++- .../src/hooks/blockchain/useSendTransfer.ts | 11 +- .../uikit/src/hooks/blockchain/useSender.ts | 59 +++++++- packages/uikit/src/state/asset.ts | 2 +- packages/uikit/src/state/mnemonic.ts | 75 +++++++++- yarn.lock | 66 +++++++- 21 files changed, 485 insertions(+), 88 deletions(-) create mode 100644 packages/core/src/service/tron-blockchain/tron-sender.ts create mode 100644 packages/core/src/service/tron-blockchain/types.ts diff --git a/packages/core/package.json b/packages/core/package.json index b59831065..3f45dc688 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -26,7 +26,7 @@ "@ledgerhq/hw-transport-webusb": "^6.28.6", "@ton-community/ton-ledger": "^7.2.0-pre.2", "@ton-keychain/core": "^0.0.4", - "@ton-keychain/trx": "^0.0.6", + "@ton-keychain/trx": "^0.0.7", "@ton/core": "0.56.0", "@ton/crypto": "3.2.0", "@ton/ton": "^15.1.0", @@ -34,6 +34,7 @@ "bip39": "^3.1.0", "ethers": "^6.13.4", "query-string": "^8.1.0", + "tronweb": "^6.0.0", "tweetnacl": "^1.0.3" } } diff --git a/packages/core/src/AppSdk.ts b/packages/core/src/AppSdk.ts index 532074bdb..7e75dab0e 100644 --- a/packages/core/src/AppSdk.ts +++ b/packages/core/src/AppSdk.ts @@ -20,7 +20,8 @@ export type TransferInitParams = chain: BLOCKCHAIN_NAME.TRON; from: string; address?: string; - }; + } + | Record; export type ReceiveInitParams = { chain?: BLOCKCHAIN_NAME; diff --git a/packages/core/src/entries/account.ts b/packages/core/src/entries/account.ts index 0176ef73c..0b751f2f5 100644 --- a/packages/core/src/entries/account.ts +++ b/packages/core/src/entries/account.ts @@ -98,6 +98,14 @@ abstract class TonMnemonic extends Clonable implements IAccountVersionsEditable this.tronWallet = networks?.tron; } + getTronWallet(id: WalletId) { + if (id === this.activeTronWallet?.id) { + return this.activeTronWallet; + } + + return undefined; + } + getTonWallet(id: WalletId) { return this.allTonWallets.find(w => w.id === id); } @@ -485,6 +493,10 @@ export class AccountMAM extends Clonable implements IAccountTonWalletStandard { return this.allTonWallets.find(w => w.id === id); } + getTronWallet(id: WalletId) { + return this.derivations.map(d => d.tronWallet).find(item => item?.id === id); + } + getTonWalletsDerivation(id: WalletId) { return this.allAvailableDerivations.find(d => d.tonWallets.some(w => w.id === id)); } diff --git a/packages/core/src/entries/crypto/asset/constants.ts b/packages/core/src/entries/crypto/asset/constants.ts index bfae1b3f8..a6edbbfed 100644 --- a/packages/core/src/entries/crypto/asset/constants.ts +++ b/packages/core/src/entries/crypto/asset/constants.ts @@ -4,12 +4,14 @@ import { TonAsset } from './ton-asset'; import { TronAsset } from './tron-asset'; import { Address } from '@ton/core'; +const usdtAddress = 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t'; + export const TRON_USDT_ASSET: TronAsset = { - id: packAssetId(BLOCKCHAIN_NAME.TRON, 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t'), + id: packAssetId(BLOCKCHAIN_NAME.TRON, usdtAddress), symbol: 'USDT', name: 'Tether USDT', decimals: 6, - address: 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t', + address: usdtAddress, blockchain: BLOCKCHAIN_NAME.TRON, image: 'https://wallet.tonkeeper.com/img/usdt-trc20.png' }; diff --git a/packages/core/src/entries/signer.ts b/packages/core/src/entries/signer.ts index 750747f7b..f179f60b2 100644 --- a/packages/core/src/entries/signer.ts +++ b/packages/core/src/entries/signer.ts @@ -1,5 +1,6 @@ import { Cell } from '@ton/core'; import { LedgerTransaction } from '../service/ledger/connector'; +import type { SignedTransaction, Transaction } from 'tronweb/src/types/Transaction'; export type BaseSigner = (message: Cell) => Promise; @@ -10,3 +11,5 @@ export type LedgerSigner = ((message: LedgerTransaction) => Promise) & { }; export type Signer = CellSigner | LedgerSigner; + +export type TronSigner = (tx: Transaction) => Promise; diff --git a/packages/core/src/service/suggestionService.ts b/packages/core/src/service/suggestionService.ts index 60e11b241..66ae5473d 100644 --- a/packages/core/src/service/suggestionService.ts +++ b/packages/core/src/service/suggestionService.ts @@ -41,36 +41,6 @@ export const deleteFavoriteSuggestion = async ( storage.set(`${AppKey.FAVOURITES}_${publicKey}`, items); }; -/*const getTronSuggestionsList = async ( - api: APIConfig, - wallet: DeprecatedWalletState, - seeIfAddressIsAdded: (list: LatestSuggestion[], address: string) => boolean -) => { - const list = [] as LatestSuggestion[]; - - if (wallet.tron) { - const tronItems = await new TronApi(api.tronApi).getTransactions({ - ownerAddress: wallet.tron.ownerWalletAddress, - limit: 100 - }); - - tronItems.events.forEach(event => { - event.actions.forEach(({ sendTRC20 }) => { - if (sendTRC20 && !seeIfAddressIsAdded(list, sendTRC20.recipient)) { - list.push({ - isFavorite: false, - timestamp: event.timestamp, - address: sendTRC20.recipient, - blockchain: BLOCKCHAIN_NAME.TRON - }); - } - }); - }); - } - - return list; -};*/ - const getTonSuggestionsList = async ( api: APIConfig, wallet: TonWalletStandard, @@ -128,8 +98,8 @@ export const getSuggestionsList = async ( switch (name) { case BLOCKCHAIN_NAME.TON: return getTonSuggestionsList(api, wallet, seeIfAddressIsAdded); - /* case BLOCKCHAIN_NAME.TRON: - return getTronSuggestionsList(api, wallet, seeIfAddressIsAdded);*/ + case BLOCKCHAIN_NAME.TRON: + return []; default: throw new Error('Unexpected chain'); } diff --git a/packages/core/src/service/tron-blockchain/tron-sender.ts b/packages/core/src/service/tron-blockchain/tron-sender.ts new file mode 100644 index 000000000..280fd3946 --- /dev/null +++ b/packages/core/src/service/tron-blockchain/tron-sender.ts @@ -0,0 +1,84 @@ +import { ethers } from 'ethers'; +import { TronApi } from '../../tronApi'; +import { TRON_TRX_ASSET, TRON_USDT_ASSET } from '../../entries/crypto/asset/constants'; +import { AssetAmount } from '../../entries/crypto/asset/asset-amount'; +import { TronAsset } from '../../entries/crypto/asset/tron-asset'; +import BigNumber from 'bignumber.js'; +import { TronSigner } from '../../entries/signer'; +import { TronWallet } from '../../entries/tron/tron-wallet'; +import { TronAddressUtils } from '@ton-keychain/trx'; +import { TronWeb } from 'tronweb'; +const AbiCoder = ethers.AbiCoder; + +const toHexAddress = (base58: string) => { + return ethers.getAddress(TronAddressUtils.base58ToHex(base58)); +}; + +export class TronSender { + private static transferSelector = 'transfer(address,uint256)'; + + constructor( + private tronApi: TronApi, + private walletInfo: TronWallet, + private tronSigner: TronSigner + ) {} + + async send(to: string, assetAmount: AssetAmount) { + if (assetAmount.asset.id !== TRON_USDT_ASSET.id) { + throw new Error(`Unsupported tron asset ${assetAmount.asset.symbol}`); + } + + const tronWeb = new TronWeb({ + fullHost: this.tronApi.baseURL + }); + + const functionSelector = 'transfer(address,uint256)'; + const parameter = [ + { type: 'address', value: to }, + { type: 'uint256', value: assetAmount.weiAmount.toFixed(0) } + ]; + const tx = await tronWeb.transactionBuilder.triggerSmartContract( + assetAmount.asset.address, + functionSelector, + {}, + parameter, + this.walletInfo.address + ); + + const signedTx = await this.tronSigner(tx.transaction); + const result = await tronWeb.trx.sendRawTransaction(signedTx); + + if (!result.result) { + throw new Error('err'); + } + } + + async estimate(to: string, assetAmount: AssetAmount) { + if (assetAmount.asset.id !== TRON_USDT_ASSET.id) { + throw new Error(`Unsupported tron asset ${assetAmount.asset.symbol}`); + } + + const estimatedGas = await this.tronApi.estimateEnergy({ + from: this.walletInfo.address, + contractAddress: assetAmount.asset.address, + selector: TronSender.transferSelector, + data: new AbiCoder() + .encode( + ['address', 'uint256'], + [toHexAddress(to), assetAmount.weiAmount.toString()] + ) + .replace(/^(0x)/, '') + }); + + const gasPrice = parseInt(await this.tronApi.rpc('eth_gasPrice')); + + const trxToBurn = estimatedGas * gasPrice; + + return { + extra: new AssetAmount({ + weiAmount: new BigNumber(trxToBurn.toString()), + asset: TRON_TRX_ASSET + }) + }; + } +} diff --git a/packages/core/src/service/tron-blockchain/types.ts b/packages/core/src/service/tron-blockchain/types.ts new file mode 100644 index 000000000..a0f4d4095 --- /dev/null +++ b/packages/core/src/service/tron-blockchain/types.ts @@ -0,0 +1,3 @@ +import { Wallet } from 'ethers'; + +export type TronEthersWallet = Wallet; diff --git a/packages/core/src/service/walletService.ts b/packages/core/src/service/walletService.ts index a61de1650..ae15c98a0 100644 --- a/packages/core/src/service/walletService.ts +++ b/packages/core/src/service/walletService.ts @@ -143,6 +143,19 @@ export const tronWalletByTonMnemonic = async ( } }; +export const tonMnemonicToTronMnemonic = async ( + mnemonic: string[], + mnemonicType: MnemonicType = 'ton' +): Promise => { + if (mnemonicType === 'ton') { + const tonAccount = await KeychainTonAccount.fromMnemonic(mnemonic); + const trxProvider = KeychainTrxAccountsProvider.fromEntropy(tonAccount.entropy); + return trxProvider.mnemonics; + } else { + return mnemonic; + } +}; + export const getContextApiByNetwork = (context: CreateWalletContext, network: Network) => { const api = network === Network.TESTNET ? context.testnetApi : context.mainnetApi; return api; diff --git a/packages/core/src/tronApi/index.ts b/packages/core/src/tronApi/index.ts index e793ad203..e6284b397 100644 --- a/packages/core/src/tronApi/index.ts +++ b/packages/core/src/tronApi/index.ts @@ -1,9 +1,20 @@ import BigNumber from 'bignumber.js'; +import { TRON_USDT_ASSET } from '../entries/crypto/asset/constants'; +import { TronWeb } from 'tronweb'; const removeTrailingSlash = (str: string) => str.replace(/\/$/, ''); export class TronApi { - private readonly baseURL: string; + public readonly baseURL: string; + + public get headers() { + return { + ...(this.apiKey && { + 'TRON-PRO-API-KEY': this.apiKey + }), + 'Content-Type': 'application/json' + }; + } constructor(baseURL: string, private readonly apiKey?: string) { this.baseURL = removeTrailingSlash(baseURL); @@ -12,12 +23,7 @@ export class TronApi { async getBalances(address: string) { const res = await ( await fetch(`${this.baseURL}/v1/accounts/${address}`, { - headers: { - ...(this.apiKey && { - 'TRON-PRO-API-KEY': this.apiKey - }), - 'Content-Type': 'application/json' - } + headers: this.headers }) ).json(); @@ -29,7 +35,7 @@ export class TronApi { if (!info) { return { trx: '0', - usdt: '0' + usdt: await this.getUSDTBalance(address) }; } @@ -46,8 +52,8 @@ export class TronApi { if (info.trc20 && Array.isArray(info.trc20)) { const usdtBalance = info.trc20.find( - (obj: Record) => 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t' in obj - )?.TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t; + (obj: Record) => TRON_USDT_ASSET.address in obj + )?.[TRON_USDT_ASSET.address]; if (usdtBalance !== undefined) { const parsed = parseInt(usdtBalance); @@ -64,4 +70,119 @@ export class TronApi { usdt }; } + + async estimateEnergy(params: { + from: string; + contractAddress: string; + selector: string; + data: string; + }) { + try { + const response = await ( + await fetch(`${this.baseURL}/wallet/triggerconstantcontract`, { + method: 'POST', + headers: this.headers, + body: JSON.stringify({ + owner_address: params.from, + contract_address: params.contractAddress, + function_selector: params.selector, + parameter: params.data, + visible: true + }) + }) + ).json(); + + if (response.result.result !== true) { + throw new Error('Estimating energy error'); + } + + if (!('energy_used' in response)) { + throw new Error('Estimating energy error'); + } + const energy = Number.parseInt(response.energy_used); + + if (!isFinite(energy)) { + throw new Error('Estimating energy error'); + } + + return energy; + } catch (error) { + console.error('Error estimating energy:', error); + throw error; + } + } + + async getUSDTBalance(of: string) { + try { + const abi = [ + { + outputs: [{ type: 'uint256' }], + constant: true, + inputs: [{ name: 'who', type: 'address' }], + name: 'balanceOf', + stateMutability: 'View', + type: 'Function' + }, + { + outputs: [{ type: 'bool' }], + inputs: [ + { name: '_to', type: 'address' }, + { name: '_value', type: 'uint256' } + ], + name: 'transfer', + stateMutability: 'Nonpayable', + type: 'Function' + } + ]; + const contract = new TronWeb({ fullHost: this.baseURL }).contract( + abi, + TRON_USDT_ASSET.address + ); + const res = await contract.balanceOf(of).call({ from: of }); + + if (typeof res !== 'bigint' && typeof res !== 'number' && typeof res !== 'string') { + throw new Error('Error fetching usdt balance'); + } + + const parsed = new BigNumber(res.toString()); + if (!parsed.isFinite()) { + throw new Error('Error fetching usdt balance'); + } + + return parsed.toFixed(0); + } catch (error) { + console.error('Error estimating energy:', error); + return '0'; + } + } + + async rpc(method: string, params: string[] = []) { + try { + const response = await ( + await fetch(`${this.baseURL}/jsonrpc`, { + method: 'POST', + headers: this.headers, + body: JSON.stringify({ + jsonrpc: '2.0', + id: 1, + method, + params + }) + }) + ).json(); + + if ('error' in response) { + throw new Error(response.error); + } + + if (!('result' in response)) { + throw new Error('RPC error'); + } + + return response.result; + } catch (error) { + console.error('Error estimating energy:', error); + throw error; + } + } } diff --git a/packages/uikit/package.json b/packages/uikit/package.json index fc1591119..ba57c24ea 100644 --- a/packages/uikit/package.json +++ b/packages/uikit/package.json @@ -21,7 +21,7 @@ "@tanstack/react-query": "4.3.4", "@tanstack/react-virtual": "^3.8.3", "@ton-keychain/core": "^0.0.4", - "@ton-keychain/trx": "^0.0.6", + "@ton-keychain/trx": "^0.0.7", "@ton/core": "0.56.0", "@ton/crypto": "3.2.0", "@tonkeeper/core": "0.1.0", @@ -32,6 +32,7 @@ "country-flag-icons": "^1.5.7", "country-list-js": "^3.1.7", "dayjs": "^1.11.10", + "ethers": "^6.13.4", "framer-motion": "^11.3.28", "react": "^18.2.0", "react-beautiful-dnd": "^13.1.1", @@ -47,6 +48,7 @@ "recharts": "^2.12.3", "slick-carousel": "^1.8.1", "styled-components": "^6.1.1", + "tronweb": "^6.0.0", "tweetnacl": "^1.0.3", "uuid": "^9.0.0" }, diff --git a/packages/uikit/src/components/modals/useSendTransferNotification.ts b/packages/uikit/src/components/modals/useSendTransferNotification.ts index 21eda3bfc..dac65b50b 100644 --- a/packages/uikit/src/components/modals/useSendTransferNotification.ts +++ b/packages/uikit/src/components/modals/useSendTransferNotification.ts @@ -6,7 +6,7 @@ export const useSendTransferNotification = () => { const sdk = useAppSdk(); const onOpen = useCallback( - (params?: TransferInitParams) => { + (params?: Omit) => { sdk.uiEvents.emit('transfer', { method: 'transfer', id: Date.now(), diff --git a/packages/uikit/src/components/transfer/RecipientView.tsx b/packages/uikit/src/components/transfer/RecipientView.tsx index d97533fc5..a921371cb 100644 --- a/packages/uikit/src/components/transfer/RecipientView.tsx +++ b/packages/uikit/src/components/transfer/RecipientView.tsx @@ -272,23 +272,6 @@ export const RecipientView: FC<{ } setAddress(item); ref.current?.focus(); - // if (ios && keyboard) openIosKeyboard(keyboard); - - // if (seeIfValidTronAddress(item.address)) { - // setRecipient({ - // address: { ...item, blockchain: BLOCKCHAIN_NAME.TRON }, - // done: false - // }); - // } else { - // const to = await getAccountAsync(item); - // if (to.memoRequired) return; - // setRecipient({ - // address: { ...item, blockchain: BLOCKCHAIN_NAME.TON }, - // toAccount: to, - // comment, - // done: false - // }); - // } }; return ( diff --git a/packages/uikit/src/components/transfer/SuggestionList.tsx b/packages/uikit/src/components/transfer/SuggestionList.tsx index a03c650fd..59b7dee0d 100644 --- a/packages/uikit/src/components/transfer/SuggestionList.tsx +++ b/packages/uikit/src/components/transfer/SuggestionList.tsx @@ -14,7 +14,6 @@ import { import { toShortValue } from '@tonkeeper/core/dist/utils/common'; import { FC } from 'react'; import styled from 'styled-components'; -import { useAppContext } from '../../hooks/appContext'; import { useAppSdk } from '../../hooks/appSdk'; import { useTranslation } from '../../hooks/translation'; import { QueryKey } from '../../libs/queryKey'; @@ -237,6 +236,10 @@ export const SuggestionList: FC<{ const { t } = useTranslation(); const { data } = useLatestSuggestion(acceptBlockchains); + if (acceptBlockchains?.length === 1 && acceptBlockchains?.[0] === BLOCKCHAIN_NAME.TRON) { + return null; + } + if (!data) { return ( <> diff --git a/packages/uikit/src/desktop-pages/coin/DesktopCoinPage.tsx b/packages/uikit/src/desktop-pages/coin/DesktopCoinPage.tsx index 31f040717..1a3d7481c 100644 --- a/packages/uikit/src/desktop-pages/coin/DesktopCoinPage.tsx +++ b/packages/uikit/src/desktop-pages/coin/DesktopCoinPage.tsx @@ -40,6 +40,7 @@ import { tonAssetAddressFromString } from '@tonkeeper/core/dist/entries/crypto/a import { useActiveTronWallet, useTronBalances } from '../../state/tron/tron'; import { AssetAmount } from '@tonkeeper/core/dist/entries/crypto/asset/asset-amount'; import { BorderSmallResponsive } from '../../components/shared/Styles'; +import { useSendTransferNotification } from '../../components/modals/useSendTransferNotification'; export const DesktopCoinPage = () => { const navigate = useNavigate(); @@ -342,6 +343,7 @@ export const TronUSDTPage = () => { const asset = TRON_USDT_ASSET; const { fiat } = useAppContext(); const { data: balances } = useTronBalances(); + const { onOpen: sendTransfer } = useSendTransferNotification(); const usdtBalance = useMemo(() => { if (balances === undefined) { @@ -385,7 +387,7 @@ export const TronUSDTPage = () => { {}} + onClick={() => sendTransfer({ chain: BLOCKCHAIN_NAME.TRON })} disabled={usdtBalance?.weiAmount.isZero()} > diff --git a/packages/uikit/src/hooks/blockchain/useEstimateTransfer.ts b/packages/uikit/src/hooks/blockchain/useEstimateTransfer.ts index 00bbe9539..b83261527 100644 --- a/packages/uikit/src/hooks/blockchain/useEstimateTransfer.ts +++ b/packages/uikit/src/hooks/blockchain/useEstimateTransfer.ts @@ -10,7 +10,8 @@ import { EXTERNAL_SENDER_CHOICE, SenderTypeUserAvailable, TWO_FA_SENDER_CHOICE, - useGetEstimationSender + useGetEstimationSender, + useGetTronEstimationSender } from './useSender'; import { useTonAssetTransferService } from './useBlockchainService'; import { useNotifyErrorHandle } from '../useNotification'; @@ -18,6 +19,8 @@ import { seeIfValidTonAddress } from '@tonkeeper/core/dist/utils/common'; import { useToQueryKeyPart } from '../useToQueryKeyPart'; import { useMemo } from 'react'; import { assertUnreachable } from '@tonkeeper/core/dist/utils/types'; +import { TRON_USDT_ASSET } from '@tonkeeper/core/dist/entries/crypto/asset/constants'; +import { TronAsset } from '@tonkeeper/core/dist/entries/crypto/asset/tron-asset'; export function useEstimateTransfer({ recipient, @@ -64,9 +67,20 @@ export function useEstimateTransfer({ const transferService = useTonAssetTransferService(); const notifyError = useNotifyErrorHandle(); const getSenderKey = useToQueryKeyPart(getSender); + const getTronSender = useGetTronEstimationSender(); + const getTronEstimationSenderKey = useToQueryKeyPart(getTronSender); return useQuery( - [QueryKey.estimate, recipient, amount, isMax, getSenderKey, transferService, notifyError], + [ + QueryKey.estimate, + recipient, + amount, + isMax, + getSenderKey, + getTronEstimationSenderKey, + transferService, + notifyError + ], async () => { const comment = (recipient as TonRecipientData).comment; try { @@ -82,8 +96,14 @@ export function useEstimateTransfer({ isMax, payload: comment ? { type: 'comment', value: comment } : undefined }); + } else if (amount.asset.id === TRON_USDT_ASSET.id) { + const tronSender = getTronSender(); + return await tronSender.estimate( + recipient.address.address, + amount as AssetAmount + ); } else { - throw new Error('Tron is not supported'); + throw new Error('Unexpected asset'); } } catch (e) { await notifyError(e); diff --git a/packages/uikit/src/hooks/blockchain/useSendTransfer.ts b/packages/uikit/src/hooks/blockchain/useSendTransfer.ts index fbb1b7262..a8e452f64 100644 --- a/packages/uikit/src/hooks/blockchain/useSendTransfer.ts +++ b/packages/uikit/src/hooks/blockchain/useSendTransfer.ts @@ -11,11 +11,14 @@ import { EXTERNAL_SENDER_CHOICE, SenderTypeUserAvailable, TWO_FA_SENDER_CHOICE, - useGetSender + useGetSender, + useGetTronSender } from './useSender'; import { useTonAssetTransferService } from './useBlockchainService'; import { useNotifyErrorHandle } from '../useNotification'; import { seeIfValidTonAddress } from '@tonkeeper/core/dist/utils/common'; +import { TRON_USDT_ASSET } from '@tonkeeper/core/dist/entries/crypto/asset/constants'; +import { TronAsset } from '@tonkeeper/core/dist/entries/crypto/asset/tron-asset'; export function useSendTransfer({ recipient, @@ -35,6 +38,7 @@ export function useSendTransfer({ const notifyError = useNotifyErrorHandle(); const getSender = useGetSender(); const transferService = useTonAssetTransferService(); + const getTronSender = useGetTronSender(); return useMutation(async () => { try { @@ -82,8 +86,11 @@ export function useSendTransfer({ from: 'send_confirm', token: isTon(amount.asset.address) ? 'ton' : amount.asset.symbol }); + } else if (amount.asset.id === TRON_USDT_ASSET.id) { + const tronSender = getTronSender(); + await tronSender.send(recipient.address.address, amount as AssetAmount); } else { - throw new Error('Disable trc 20 transactions'); + throw new Error('Unexpected asset'); } } catch (e) { await notifyError(e); diff --git a/packages/uikit/src/hooks/blockchain/useSender.ts b/packages/uikit/src/hooks/blockchain/useSender.ts index 1150b1237..a03ac0b82 100644 --- a/packages/uikit/src/hooks/blockchain/useSender.ts +++ b/packages/uikit/src/hooks/blockchain/useSender.ts @@ -1,10 +1,10 @@ import { BatteryMessageSender, + GaslessMessageSender, LedgerMessageSender, - WalletMessageSender, MultisigCreateOrderSender, - GaslessMessageSender, - Sender + Sender, + WalletMessageSender } from '@tonkeeper/core/dist/service/ton-blockchain/sender'; import { useAppContext } from '../appContext'; import { @@ -23,13 +23,17 @@ import { useBatteryServiceConfig, useRequestBatteryAuthToken } from '../../state/battery'; -import { useGetAccountSigner } from '../../state/mnemonic'; +import { getTronSigner, useGetAccountSigner } from '../../state/mnemonic'; import { useCallback, useMemo } from 'react'; import { TonAsset, tonAssetAddressToString } from '@tonkeeper/core/dist/entries/crypto/asset/ton-asset'; -import { Account, AccountTonMultisig } from '@tonkeeper/core/dist/entries/account'; +import { + Account, + AccountTonMultisig, + isAccountTronCompatible +} from '@tonkeeper/core/dist/entries/account'; import { TON_ASSET } from '@tonkeeper/core/dist/entries/crypto/asset/constants'; import { getMultisigSignerInfo } from '../../state/multisig'; import { GaslessConfig, MultisigApi } from '@tonkeeper/core/dist/tonApiV2'; @@ -45,6 +49,12 @@ import { toNano } from '@ton/core'; import { useTwoFAApi, useTwoFAServiceConfig, useTwoFAWalletConfig } from '../../state/two-fa'; import { TwoFAMessageSender } from '@tonkeeper/core/dist/service/ton-blockchain/sender/two-fa-message-sender'; import { useConfirmTwoFANotification } from '../../components/modals/ConfirmTwoFANotificationControlled'; +import { useTronApi } from '../../state/tron/tron'; +import { TronSender } from '@tonkeeper/core/dist/service/tron-blockchain/tron-sender'; +import { useAppSdk } from '../appSdk'; +import { useCheckTouchId } from '../../state/password'; +import { BLOCKCHAIN_NAME } from '@tonkeeper/core/dist/entries/crypto'; +import { TronAsset } from '@tonkeeper/core/dist/entries/crypto/asset/tron-asset'; export type SenderChoice = | { type: 'multisig'; ttlSeconds: number } @@ -62,7 +72,7 @@ export type SenderTypeUserAvailable = SenderChoiceUserAvailable['type']; export const useAvailableSendersChoices = ( operation: - | { type: 'transfer'; asset: TonAsset } + | { type: 'transfer'; asset: TonAsset | TronAsset } | { type: 'multisend-transfer'; asset: TonAsset } | { type: 'nfr_transfer' } ) => { @@ -92,6 +102,9 @@ export const useAvailableSendersChoices = ( twoFaConfig ], () => { + if (asset?.blockchain === BLOCKCHAIN_NAME.TRON) { + return [EXTERNAL_SENDER_CHOICE]; + } if (account.type !== 'mnemonic' && account.type !== 'mam') { return [EXTERNAL_SENDER_CHOICE]; } @@ -581,3 +594,37 @@ const isGaslessAvailable = ({ account.activeTonWallet.version === WalletVersion.V5R1 ); }; + +export const useGetTronSender = () => { + const sdk = useAppSdk(); + const tronApi = useTronApi(); + const activeAccount = useActiveAccount(); + const { mutateAsync: checkTouchId } = useCheckTouchId(); + + return useCallback(() => { + const signer = getTronSigner(sdk, tronApi, activeAccount, checkTouchId); + + if (!isAccountTronCompatible(activeAccount) || !activeAccount.activeTronWallet) { + throw new Error('Tron is not enabled for the active wallet'); + } + + return new TronSender(tronApi, activeAccount.activeTronWallet, signer); + }, [tronApi, activeAccount, checkTouchId]); +}; + +export const useGetTronEstimationSender = () => { + const tronApi = useTronApi(); + const activeAccount = useActiveAccount(); + + return useCallback(() => { + const signer = (): Promise => { + throw new Error('Unexpected call'); + }; + + if (!isAccountTronCompatible(activeAccount) || !activeAccount.activeTronWallet) { + throw new Error('Tron is not enabled for the active wallet'); + } + + return new TronSender(tronApi, activeAccount.activeTronWallet, signer); + }, [tronApi]); +}; diff --git a/packages/uikit/src/state/asset.ts b/packages/uikit/src/state/asset.ts index ee990f8b7..7922e2b73 100644 --- a/packages/uikit/src/state/asset.ts +++ b/packages/uikit/src/state/asset.ts @@ -85,7 +85,7 @@ export function useAssetImage({ blockchain, address }: AssetIdentification): str } if (id === TRON_USDT_ASSET.id) { - return 'https://wallet-dev.tonkeeper.com/img/usdt.svg'; + return TRON_USDT_ASSET.image; } if (typeof address === 'string') { diff --git a/packages/uikit/src/state/mnemonic.ts b/packages/uikit/src/state/mnemonic.ts index 23e2c3f35..8b40300de 100644 --- a/packages/uikit/src/state/mnemonic.ts +++ b/packages/uikit/src/state/mnemonic.ts @@ -2,9 +2,14 @@ import { TonKeychainRoot } from '@ton-keychain/core'; import { Cell } from '@ton/core'; import { sha256_sync, sign } from '@ton/crypto'; import { IAppSdk } from '@tonkeeper/core/dist/AppSdk'; -import { AccountId, isMnemonicAndPassword } from '@tonkeeper/core/dist/entries/account'; +import { + AccountId, + isAccountTronCompatible, + isMnemonicAndPassword, + Account +} from '@tonkeeper/core/dist/entries/account'; import { AuthPassword, MnemonicType } from '@tonkeeper/core/dist/entries/password'; -import { CellSigner, Signer } from '@tonkeeper/core/dist/entries/signer'; +import { CellSigner, Signer, TronSigner } from '@tonkeeper/core/dist/entries/signer'; import { TonWalletStandard, WalletId } from '@tonkeeper/core/dist/entries/wallet'; import { accountsStorage } from '@tonkeeper/core/dist/service/accountsStorage'; import { KeystoneMessageType } from '@tonkeeper/core/dist/service/keystone/types'; @@ -26,6 +31,10 @@ import { useAppSdk } from '../hooks/appSdk'; import { useCallback } from 'react'; import { useActiveAccount } from './wallet'; import { useCheckTouchId } from './password'; +import { tonMnemonicToTronMnemonic } from '@tonkeeper/core/dist/service/walletService'; +import { TronWeb } from 'tronweb'; +import type { Transaction } from 'tronweb/src/types/Transaction'; +import { TronApi } from '@tonkeeper/core/dist/tronApi'; export const signTonConnectOver = ({ sdk, @@ -248,6 +257,68 @@ export const getSigner = async ( } }; +export const getTronSigner = ( + sdk: IAppSdk, + tronApi: TronApi, + account: Account, + checkTouchId: () => Promise +): TronSigner => { + try { + if (!isAccountTronCompatible(account)) { + throw new Error("Account doesn't support tron"); + } + + const wallet = account.activeTronWallet; + + if (!wallet) { + throw new Error('Wallet not found'); + } + + switch (account.type) { + case 'mam': { + return async (tx: Transaction) => { + const tonMnemonic = await getMAMWalletMnemonic( + sdk, + account.id, + wallet!.id, + checkTouchId + ); + const tronMnemonic = await tonMnemonicToTronMnemonic(tonMnemonic, 'ton'); + + const tronWeb = new TronWeb({ + fullHost: tronApi.baseURL, + privateKey: TronWeb.fromMnemonic(tronMnemonic.join(' ')).privateKey.slice(2) + }); + + return tronWeb.trx.sign(tx); + }; + } + case 'mnemonic': { + return async (tx: Transaction) => { + const tonMnemonic = await getAccountMnemonic(sdk, account.id, checkTouchId); + const tronMnemonic = await tonMnemonicToTronMnemonic( + tonMnemonic, + account.mnemonicType + ); + + const tronWeb = new TronWeb({ + fullHost: tronApi.baseURL, + privateKey: TronWeb.fromMnemonic(tronMnemonic.join(' ')).privateKey.slice(2) + }); + + return tronWeb.trx.sign(tx); + }; + } + default: { + assertUnreachable(account); + } + } + } catch (e) { + console.error(e); + throw e; + } +}; + export const getAccountMnemonic = async ( sdk: IAppSdk, accountId: AccountId, diff --git a/yarn.lock b/yarn.lock index ca602861e..19fa508d0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7320,15 +7320,15 @@ __metadata: languageName: node linkType: hard -"@ton-keychain/trx@npm:^0.0.6": - version: 0.0.6 - resolution: "@ton-keychain/trx@npm:0.0.6" +"@ton-keychain/trx@npm:^0.0.7": + version: 0.0.7 + resolution: "@ton-keychain/trx@npm:0.0.7" dependencies: "@noble/hashes": "npm:^1.3.3" "@scure/bip39": "npm:^1.2.2" "@ton-keychain/core": "npm:^0.0.4" ethers: "npm:^6.9.2" - checksum: 10/21f1987073f8fc925ca97e01294c7cf6c7b18a7091b0c9fbadda3d3bd65a9d73800384c35726aeb019f11f47e96348578867fc406cf02989a6e25ed299018250 + checksum: 10/2a0e93f610218104fdfae7e7bf0efbf7093d380c0de1555d98c603e1844013c3048981227774a8254db1b94720d2e8c4cc9860c5f1d37088b4c494256ac7d29f languageName: node linkType: hard @@ -7399,7 +7399,7 @@ __metadata: "@ledgerhq/hw-transport-webusb": "npm:^6.28.6" "@ton-community/ton-ledger": "npm:^7.2.0-pre.2" "@ton-keychain/core": "npm:^0.0.4" - "@ton-keychain/trx": "npm:^0.0.6" + "@ton-keychain/trx": "npm:^0.0.7" "@ton/core": "npm:0.56.0" "@ton/crypto": "npm:3.2.0" "@ton/ton": "npm:^15.1.0" @@ -7408,6 +7408,7 @@ __metadata: ethers: "npm:^6.13.4" npm-run-all: "npm:^4.1.5" query-string: "npm:^8.1.0" + tronweb: "npm:^6.0.0" tweetnacl: "npm:^1.0.3" typescript: "npm:^4.9.4" yarn-run-all: "npm:^3.1.1" @@ -7659,7 +7660,7 @@ __metadata: "@tanstack/react-query": "npm:4.3.4" "@tanstack/react-virtual": "npm:^3.8.3" "@ton-keychain/core": "npm:^0.0.4" - "@ton-keychain/trx": "npm:^0.0.6" + "@ton-keychain/trx": "npm:^0.0.7" "@ton/core": "npm:0.56.0" "@ton/crypto": "npm:3.2.0" "@tonkeeper/core": "npm:0.1.0" @@ -7682,6 +7683,7 @@ __metadata: country-flag-icons: "npm:^1.5.7" country-list-js: "npm:^3.1.7" dayjs: "npm:^1.11.10" + ethers: "npm:^6.13.4" framer-motion: "npm:^11.3.28" react: "npm:^18.2.0" react-beautiful-dnd: "npm:^13.1.1" @@ -7700,6 +7702,7 @@ __metadata: slick-carousel: "npm:^1.8.1" storybook-addon-turbo-build: "npm:^1.1.0" styled-components: "npm:^6.1.1" + tronweb: "npm:^6.0.0" tweetnacl: "npm:^1.0.3" typescript: "npm:^4.9.4" uuid: "npm:^9.0.0" @@ -7803,6 +7806,13 @@ __metadata: languageName: node linkType: hard +"@tronweb3/google-protobuf@npm:^3.21.2": + version: 3.21.2 + resolution: "@tronweb3/google-protobuf@npm:3.21.2" + checksum: 10/00d9344e0d32af7af3e55087ffb60b8663ce2c180e8450e5e76b72e5d7309dc2b6ce9999dece7aa48050b626e6f50622dc2614d80e5bb4bcd259512363aaa048 + languageName: node + linkType: hard + "@trysound/sax@npm:0.2.0": version: 0.2.0 resolution: "@trysound/sax@npm:0.2.0" @@ -10483,6 +10493,17 @@ __metadata: languageName: node linkType: hard +"axios@npm:^1.7.4": + version: 1.7.9 + resolution: "axios@npm:1.7.9" + dependencies: + follow-redirects: "npm:^1.15.6" + form-data: "npm:^4.0.0" + proxy-from-env: "npm:^1.1.0" + checksum: 10/b7a5f660ea53ba9c2a745bf5ad77ad8bf4f1338e13ccc3f9f09f810267d6c638c03dac88b55dae8dc98b79c57d2d6835be651d58d2af97c174f43d289a9fd007 + languageName: node + linkType: hard + "axobject-query@npm:^3.2.1": version: 3.2.1 resolution: "axobject-query@npm:3.2.1" @@ -15369,7 +15390,7 @@ __metadata: languageName: node linkType: hard -"ethers@npm:^6.13.4, ethers@npm:^6.9.2": +"ethers@npm:^6.13.1, ethers@npm:^6.13.4, ethers@npm:^6.9.2": version: 6.13.4 resolution: "ethers@npm:6.13.4" dependencies: @@ -15399,6 +15420,13 @@ __metadata: languageName: node linkType: hard +"eventemitter3@npm:^3.1.0": + version: 3.1.2 + resolution: "eventemitter3@npm:3.1.2" + checksum: 10/e2886001beb52cd2fe47d2470fd6266b7c70bd3ac356c0041a7e64336ed57bb1fc9b07bc9043d34b39913488a8d81bfcde62d3af597974980aa01b50844d869b + languageName: node + linkType: hard + "eventemitter3@npm:^4.0.0, eventemitter3@npm:^4.0.1": version: 4.0.7 resolution: "eventemitter3@npm:4.0.7" @@ -27921,6 +27949,23 @@ __metadata: languageName: node linkType: hard +"tronweb@npm:^6.0.0": + version: 6.0.0 + resolution: "tronweb@npm:6.0.0" + dependencies: + "@babel/runtime": "npm:^7.0.0" + "@tronweb3/google-protobuf": "npm:^3.21.2" + axios: "npm:^1.7.4" + bignumber.js: "npm:^9.0.1" + ethereum-cryptography: "npm:^2.1.3" + ethers: "npm:^6.13.1" + eventemitter3: "npm:^3.1.0" + semver: "npm:^5.6.0" + validator: "npm:^13.7.0" + checksum: 10/93ae9d761273018150e90fb5f20f78df109a58cff031a3bc05f31370c05ca1b6c9b4a3bc1a39c24a59b3c12915243c577935d6d8ce2b4accf36c38bd81c29047 + languageName: node + linkType: hard + "trough@npm:^1.0.0": version: 1.0.5 resolution: "trough@npm:1.0.5" @@ -29044,6 +29089,13 @@ __metadata: languageName: node linkType: hard +"validator@npm:^13.7.0": + version: 13.12.0 + resolution: "validator@npm:13.12.0" + checksum: 10/db6eb0725e2b67d60d30073ae8573982713b5903195d031dc3c7db7e82df8b74e8c13baef8e2106d146d979599fd61a06cde1fec5c148e4abd53d52817ff0fd9 + languageName: node + linkType: hard + "vary@npm:~1.1.2": version: 1.1.2 resolution: "vary@npm:1.1.2" From 1d773b855336a3da7834c7d3c5c9d682b15468dc Mon Sep 17 00:00:00 2001 From: siandreev Date: Mon, 23 Dec 2024 17:26:22 +0100 Subject: [PATCH 06/20] fix: copy address in header --- packages/locales/src/tonkeeper-web/en.json | 3 + packages/locales/src/tonkeeper-web/ru-RU.json | 3 + .../uikit/src/components/create/Words.tsx | 40 +++++- .../desktop/aside/AsideHeaderWallet.tsx | 134 +++++++++++++++++- .../uikit/src/components/transfer/Confirm.tsx | 5 +- .../src/components/transfer/ConfirmView.tsx | 6 +- .../uikit/src/pages/settings/Recovery.tsx | 77 ++++++++-- 7 files changed, 246 insertions(+), 22 deletions(-) diff --git a/packages/locales/src/tonkeeper-web/en.json b/packages/locales/src/tonkeeper-web/en.json index 94d220858..f07270ef7 100644 --- a/packages/locales/src/tonkeeper-web/en.json +++ b/packages/locales/src/tonkeeper-web/en.json @@ -111,6 +111,7 @@ "Enable_storing_config": "Enable storing config", "enter_password": "Enter password", "export_dot_csv": "Export .CSV", + "export_trc_20_wallet": "Export TRC20 Wallet", "force_reload": "Force Reload", "help": "Help", "hide": "Hide", @@ -183,6 +184,7 @@ "Manage_wallets": "Manage Wallets", "Minimize": "Minimize", "MinPassword": "Must be at least 6 characters.", + "multichain": "Multichain", "multi_send_about_w5": "About W5", "multi_send_add_more": "Add More", "multisend_confirm_error_insufficient_ton_for_fee": "Wallet balance %balance% is not enough to cover the blockchain fees. Minimum balance required: %required%. Unused TON will be returned to your wallet after the transaction.", @@ -378,6 +380,7 @@ "transaction_call_date": "Contract Call %{date}", "transaction_type_mint": "Mint", "transaction_type_purchase": "Purchase", + "tron_account_export_warning_explanation": "This phrase is for TRC20 only. It cannot restore your TON wallet. Use your TON recovery phrase for TON wallet recovery.", "tron_top_up_trx_button": "Top Up TRX", "tron_top_up_trx_description": "You need TRX to pay transaction fees. Balance: {balance}", "tron_top_up_trx_title": "TRX is required for USD₮ TRC20", diff --git a/packages/locales/src/tonkeeper-web/ru-RU.json b/packages/locales/src/tonkeeper-web/ru-RU.json index cd8cb401d..e41a35b25 100644 --- a/packages/locales/src/tonkeeper-web/ru-RU.json +++ b/packages/locales/src/tonkeeper-web/ru-RU.json @@ -109,6 +109,7 @@ "Enable_storing_config": "Cохранение конфигурации", "enter_password": "Введите пароль", "export_dot_csv": "Экспорт в .CSV", + "export_trc_20_wallet": "Экспортировать TRC20 кошелек", "force_reload": "Принудительная перезагрузка", "help": "Помощь", "hide": "Скрыть", @@ -176,6 +177,7 @@ "Manage_wallets": "Управление кошельками", "Minimize": "Свернуть", "MinPassword": "Должно быть не менее 6 символов.", + "multichain": "Мультичейн", "multi_send_about_w5": "Подробнее о W5", "multi_send_add_more": "Добавить еще", "multisend_confirm_error_insufficient_ton_for_fee": "Баланса кошелька %balance% недостаточно для покрытия комиссий блокчейна. Требуемый минимальный баланс: %required%. Неиспользованный остаток TON после транзакции будет возвращен на ваш кошелек.", @@ -365,6 +367,7 @@ "transaction_call_date": "Вызов контракта %{date}", "transaction_type_mint": "Создание", "transaction_type_purchase": "Покупка", + "tron_account_export_warning_explanation": "Эта фраза предназначена только для TRC20. Она не может восстановить ваш кошелек TON. Используйте фразу восстановления TON для восстановления кошелька TON.", "tron_top_up_trx_button": "Пополнить TRX", "tron_top_up_trx_description": "Вам нужны TRX для оплаты комиссий за транзакции. Баланс: {balance}", "tron_top_up_trx_title": "TRX требуется для операций с USD₮ TRC20", diff --git a/packages/uikit/src/components/create/Words.tsx b/packages/uikit/src/components/create/Words.tsx index 1a260999d..e32e21119 100644 --- a/packages/uikit/src/components/create/Words.tsx +++ b/packages/uikit/src/components/create/Words.tsx @@ -13,6 +13,7 @@ import { ExclamationMarkCircleIcon } from '../Icon'; import { validateMnemonicTonOrMAM } from '@tonkeeper/core/dist/service/mnemonicService'; import { ToggleButton, ToggleButtonItem } from '../shared/ToggleButton'; import { useActiveConfig } from '../../state/wallet'; +import { hexToRGBA } from '../../libs/css'; const Block = styled.div` display: flex; @@ -111,10 +112,23 @@ const MamAccountCallout = styled.div` margin-bottom: 24px; `; +const TronAccountCallout = styled.div` + background: ${p => hexToRGBA(p.theme.accentOrange, 0.16)}; + ${BorderSmallResponsive}; + padding: 8px 12px; + display: flex; + gap: 12px; + margin-bottom: 24px; +`; + const Body3Secondary = styled(Body3)` color: ${p => p.theme.textSecondary}; `; +const Body3Orange = styled(Body3)` + color: ${p => p.theme.accentOrange}; +`; + const ExclamationMarkCircleIconStyled = styled(ExclamationMarkCircleIcon)` margin-top: 4px; height: 16px; @@ -130,25 +144,32 @@ const LinkStyled = styled(Body3)` export const WordsGridAndHeaders: FC<{ mnemonic: string[]; - showMamInfo?: boolean; + type?: 'standard' | 'mam' | 'tron'; allowCopy?: boolean; -}> = ({ mnemonic, showMamInfo, allowCopy }) => { +}> = ({ mnemonic, type, allowCopy }) => { const { t } = useTranslation(); const config = useActiveConfig(); const sdk = useAppSdk(); + type ??= 'standard'; return ( <> - {t(showMamInfo ? 'secret_words_account_title' : 'secret_words_title')} + {t( + type === 'mam' + ? 'secret_words_account_title' + : type === 'tron' + ? 'export_trc_20_wallet' + : 'secret_words_title' + )} {t(mnemonic.length === 12 ? 'secret_words_caption_12' : 'secret_words_caption')} - {showMamInfo && ( + {type === 'mam' && (
{t('mam_account_explanation') + ' '} @@ -162,6 +183,15 @@ export const WordsGridAndHeaders: FC<{ )} + {type === 'tron' && ( + +
+ {t('tron_account_export_warning_explanation')} +
+ +
+ )} + {mnemonic.map((world, index) => ( @@ -198,7 +228,7 @@ export const Words: FC<{ return ( - + {t('continue')} diff --git a/packages/uikit/src/components/desktop/aside/AsideHeaderWallet.tsx b/packages/uikit/src/components/desktop/aside/AsideHeaderWallet.tsx index 51818be37..1acb0ade4 100644 --- a/packages/uikit/src/components/desktop/aside/AsideHeaderWallet.tsx +++ b/packages/uikit/src/components/desktop/aside/AsideHeaderWallet.tsx @@ -1,15 +1,21 @@ import styled from 'styled-components'; -import { FC, useRef, useState } from 'react'; +import React, { FC, useRef, useState } from 'react'; import { WalletEmoji } from '../../shared/emoji/WalletEmoji'; -import { Body3, Label2 } from '../../Text'; +import { Body2, Body3, Label2 } from '../../Text'; import { useActiveAccount, useActiveTonNetwork } from '../../../state/wallet'; import { useTranslation } from '../../../hooks/translation'; import { formatAddress, toShortValue } from '@tonkeeper/core/dist/utils/common'; import { useAppSdk } from '../../../hooks/appSdk'; -import { CopyIcon, DoneIcon } from '../../Icon'; +import { ChevronDownIcon, CopyIcon, DoneIcon } from '../../Icon'; import { Transition } from 'react-transition-group'; import { AccountAndWalletBadgesGroup } from '../../account/AccountBadge'; import { AsideHeaderContainer } from './AsideHeaderElements'; +import { useActiveTronWallet } from '../../../state/tron/tron'; +import { DropDownContent, DropDownItem, DropDownItemsDivider } from '../../DropDown'; +import { SelectDropDown } from '../../fields/Select'; +import { AccountMAM, AccountTonMnemonic } from '@tonkeeper/core/dist/entries/account'; +import { BLOCKCHAIN_NAME } from '@tonkeeper/core/dist/entries/crypto'; +import { TON_ASSET, TRON_TRX_ASSET } from '@tonkeeper/core/dist/entries/crypto/asset/constants'; const HeaderContainer = styled(AsideHeaderContainer)` display: flex; @@ -34,6 +40,7 @@ const AddressWrapper = styled.div` display: flex; gap: 0.5rem; align-items: center; + height: 20px; & > ${Body3} { color: ${p => p.theme.textSecondary}; @@ -56,6 +63,127 @@ const DoneIconStyled = styled(DoneIcon)` `; export const AsideHeaderWallet: FC<{ width: number }> = ({ width }) => { + const tronWallet = useActiveTronWallet(); + + if (!tronWallet) { + return ; + } + + return ; +}; + +const BlockchainImage = styled.img` + border-radius: ${p => p.theme.cornerFull}; + width: 24px; + height: 24px; +`; + +const DropDownItemStyled = styled(DropDownItem)` + padding: 8px 12px; + gap: 12px; + + font-family: ${p => p.theme.fontMono}; + + > *:last-child { + margin-left: auto; + } +`; + +const MultichainLine = styled.div` + display: flex; + align-items: center; + gap: 4px; + height: 20px; + color: ${props => props.theme.textSecondary}; + + > svg { + color: ${props => props.theme.iconTertiary}; + } +`; + +const AsideHeaderMultiChainWallet: FC<{ width: number }> = ({ width }) => { + const { t } = useTranslation(); + const account = useActiveAccount() as AccountMAM | AccountTonMnemonic; + const activeWallet = account.activeTonWallet; + + const [tonCopied, setIsTonCopied] = useState(false); + const [tronCopied, setIsTronCopied] = useState(false); + + const sdk = useAppSdk(); + + const tonAddress = formatAddress(activeWallet.rawAddress, useActiveTonNetwork()); + const tronAddress = account.activeTronWallet!.address; + + const timeoutRef = useRef< + Record | undefined> + >({ + [BLOCKCHAIN_NAME.TON]: undefined, + [BLOCKCHAIN_NAME.TRON]: undefined + }); + + const onCopy = (chain: BLOCKCHAIN_NAME) => { + const setIsCopied = chain === BLOCKCHAIN_NAME.TON ? setIsTonCopied : setIsTronCopied; + clearTimeout(timeoutRef.current[chain]); + sdk.copyToClipboard(chain === BLOCKCHAIN_NAME.TON ? tonAddress : tronAddress); + setIsCopied(true); + timeoutRef.current[chain] = setTimeout(() => setIsCopied(false), 2000); + }; + + const name = account.type === 'mam' ? account.activeDerivation.name : account.name; + const emoji = account.type === 'mam' ? account.activeDerivation.emoji : account.emoji; + + return ( + ( + + { + onCopy(BLOCKCHAIN_NAME.TON); + }} + > + + {toShortValue(tonAddress)} + {tonCopied ? : } + + + { + onCopy(BLOCKCHAIN_NAME.TRON); + }} + > + + {toShortValue(tronAddress)} + {tronCopied ? : } + + + + )} + > + + + {name || t('wallet_title')} + + {t('multichain')} + + + + + + + + ); +}; + +const AsideHeaderSingleChainWallet: FC<{ width: number }> = ({ width }) => { const { t } = useTranslation(); const account = useActiveAccount(); const activeWallet = account.activeTonWallet; diff --git a/packages/uikit/src/components/transfer/Confirm.tsx b/packages/uikit/src/components/transfer/Confirm.tsx index fa07dc4a0..622418af9 100644 --- a/packages/uikit/src/components/transfer/Confirm.tsx +++ b/packages/uikit/src/components/transfer/Confirm.tsx @@ -8,10 +8,11 @@ export const Info = styled.div` margin-bottom: 1rem; `; -export const Image = styled.img<{ full?: boolean }>` +export const Image = styled.img<{ full?: boolean; $noBorders?: boolean }>` width: 96px; height: 96px; - border-radius: ${props => (props.full ? props.theme.cornerFull : props.theme.cornerMedium)}; + border-radius: ${props => + props.$noBorders ? 'none' : props.full ? props.theme.cornerFull : props.theme.cornerMedium}; `; export const ImageMock = styled.div<{ full?: boolean }>` diff --git a/packages/uikit/src/components/transfer/ConfirmView.tsx b/packages/uikit/src/components/transfer/ConfirmView.tsx index b7b82a6e2..11f45a7d4 100644 --- a/packages/uikit/src/components/transfer/ConfirmView.tsx +++ b/packages/uikit/src/components/transfer/ConfirmView.tsx @@ -275,7 +275,11 @@ export const ConfirmViewHeading: FC - {icon ? : } + {icon ? ( + + ) : ( + + )} {t('confirm_sending_title')} {title} diff --git a/packages/uikit/src/pages/settings/Recovery.tsx b/packages/uikit/src/pages/settings/Recovery.tsx index e69f4411b..7abca9bc4 100644 --- a/packages/uikit/src/pages/settings/Recovery.tsx +++ b/packages/uikit/src/pages/settings/Recovery.tsx @@ -1,4 +1,8 @@ -import { AccountId, isMnemonicAndPassword } from '@tonkeeper/core/dist/entries/account'; +import { + AccountId, + isAccountTronCompatible, + isMnemonicAndPassword +} from '@tonkeeper/core/dist/entries/account'; import { WalletId } from '@tonkeeper/core/dist/entries/wallet'; import { FC, useCallback, useEffect, useMemo, useState } from 'react'; import { Navigate, useNavigate, useParams, useSearchParams } from 'react-router-dom'; @@ -9,6 +13,11 @@ import { useAppSdk } from '../../hooks/appSdk'; import { getAccountMnemonic, getMAMWalletMnemonic } from '../../state/mnemonic'; import { useCheckTouchId } from '../../state/password'; import { useAccountState, useActiveAccount } from '../../state/wallet'; +import { Body2Class } from '../../components/Text'; +import { useTranslation } from '../../hooks/translation'; +import { tonMnemonicToTronMnemonic } from '@tonkeeper/core/dist/service/walletService'; +import { SpinnerRing } from '../../components/Icon'; +import { useSetNotificationOnBack } from '../../components/Notification'; export const ActiveRecovery = () => { const account = useActiveAccount(); @@ -75,32 +84,78 @@ const BackButtonBlockStyled = styled(BackButtonBlock)` `} `; +const TronButton = styled.button` + margin-top: 15px; + padding: 4px 8px; + background-color: transparent; + border: none; + outline: none; + + ${Body2Class}; + color: ${p => p.theme.textSecondary}; +`; + +const SpinnerRingStyled = styled(SpinnerRing)` + margin: 16px auto; +`; + export const RecoveryContent: FC<{ accountId: AccountId; walletId?: WalletId; isPage?: boolean; onClose?: () => void; }> = ({ accountId, walletId, isPage = true, onClose }) => { + const { t } = useTranslation(); const navigate = useNavigate(); - const onBack = useCallback(() => { - onClose ? onClose() : navigate(-1); - }, [onClose, navigate]); + const onBack = useCallback(() => (onClose ? onClose() : navigate(-1)), [onClose, navigate]); const mnemonic = useMnemonic(onBack, accountId, walletId); const account = useAccountState(accountId); + const [isExportingTRC20, setIsExportingTrc20] = useState(false); + + const [mnemonicToShow, setMnemonicToShow] = useState(mnemonic); + useEffect(() => { + setMnemonicToShow(mnemonic); + }, [mnemonic]); - if (!mnemonic) { - return ; + const onHideTron = () => { + setMnemonicToShow(mnemonic); + setIsExportingTrc20(false); + }; + + useSetNotificationOnBack(isExportingTRC20 ? onHideTron : undefined); + + if (!mnemonicToShow) { + return ( + + + + ); } + const hasTronWallet = account && isAccountTronCompatible(account) && !!account.activeTronWallet; + + const onShowTron = async () => { + const tronMnemonic = await tonMnemonicToTronMnemonic(mnemonic!); + setMnemonicToShow(tronMnemonic); + setIsExportingTrc20(true); + }; + + const wordsType = + account?.type === 'mam' && walletId === undefined + ? 'mam' + : isExportingTRC20 + ? 'tron' + : 'standard'; + return ( {isPage && } - + + + {hasTronWallet && !isExportingTRC20 && ( + {t('export_trc_20_wallet')} + )} ); }; From 6973fc29daed07c8fd49f3734fa931b3a12d2bb5 Mon Sep 17 00:00:00 2001 From: siandreev Date: Mon, 23 Dec 2024 17:44:36 +0100 Subject: [PATCH 07/20] feat: enable tron in dev menu --- packages/core/src/service/devStorage.ts | 4 +- .../src/components/activity/ActivityGroup.tsx | 3 +- .../src/components/transfer/RecipientView.tsx | 9 +++-- .../components/transfer/SendNotifications.tsx | 38 +++++++++++++------ packages/uikit/src/pages/settings/Dev.tsx | 24 ++++++++++++ packages/uikit/src/state/tron/tron.ts | 18 +++++++++ 6 files changed, 79 insertions(+), 17 deletions(-) diff --git a/packages/core/src/service/devStorage.ts b/packages/core/src/service/devStorage.ts index f9ae38789..26641df30 100644 --- a/packages/core/src/service/devStorage.ts +++ b/packages/core/src/service/devStorage.ts @@ -5,11 +5,13 @@ import { AppKey } from '../Keys'; export interface DevSettings { tonNetwork: Network; twoFAEnabled: boolean; + tronEnabled: boolean; } const defaultDevSettings: DevSettings = { tonNetwork: Network.MAINNET, - twoFAEnabled: false + twoFAEnabled: false, + tronEnabled: false }; export const getDevSettings = async (storage: IStorage) => { diff --git a/packages/uikit/src/components/activity/ActivityGroup.tsx b/packages/uikit/src/components/activity/ActivityGroup.tsx index e5d6d261f..093b24ba5 100644 --- a/packages/uikit/src/components/activity/ActivityGroup.tsx +++ b/packages/uikit/src/components/activity/ActivityGroup.tsx @@ -1,6 +1,5 @@ import { InfiniteData } from '@tanstack/react-query'; import { AccountEvents } from '@tonkeeper/core/dist/tonApiV2'; -import { TronEvents } from '@tonkeeper/core/dist/tronApi'; import React, { FC, useMemo, useState } from 'react'; import { formatActivityDate, GenericActivityGroup, getActivityTitle } from '../../state/activity'; import { MixedActivity, getMixedActivityGroups } from '../../state/mixedActivity'; @@ -16,7 +15,7 @@ export const ActivityList: FC<{ isFetched: boolean; isFetchingNextPage: boolean; tonEvents?: InfiniteData; - tronEvents?: InfiniteData; + tronEvents?: InfiniteData; }> = ({ isFetched, isFetchingNextPage, tonEvents, tronEvents }) => { const activity = useMemo[]>(() => { return getMixedActivityGroups(tonEvents, tronEvents); diff --git a/packages/uikit/src/components/transfer/RecipientView.tsx b/packages/uikit/src/components/transfer/RecipientView.tsx index a921371cb..87196caf0 100644 --- a/packages/uikit/src/components/transfer/RecipientView.tsx +++ b/packages/uikit/src/components/transfer/RecipientView.tsx @@ -167,7 +167,7 @@ export const RecipientView: FC<{ } return null; - }, [recipient]); + }, [recipient, acceptBlockchains]); const isValidAddress = useMemo(() => { if (acceptBlockchains && acceptBlockchains.length === 1) { @@ -235,10 +235,13 @@ export const RecipientView: FC<{ let isValid; switch (isValidForBlockchain) { case BLOCKCHAIN_NAME.TON: - isValid = isMemoValid && toAccount; + isValid = + isMemoValid && toAccount && acceptBlockchains?.includes(BLOCKCHAIN_NAME.TON); break; case BLOCKCHAIN_NAME.TRON: - isValid = seeIfValidTronAddress(recipient.address); + isValid = + seeIfValidTronAddress(recipient.address) && + acceptBlockchains?.includes(BLOCKCHAIN_NAME.TRON); } if (isValid) { if (ios && keyboard) openIosKeyboard(keyboard); diff --git a/packages/uikit/src/components/transfer/SendNotifications.tsx b/packages/uikit/src/components/transfer/SendNotifications.tsx index f8686f1a1..b7aabf4ee 100644 --- a/packages/uikit/src/components/transfer/SendNotifications.tsx +++ b/packages/uikit/src/components/transfer/SendNotifications.tsx @@ -4,8 +4,8 @@ import { AssetAmount } from '@tonkeeper/core/dist/entries/crypto/asset/asset-amo import { jettonToTonAsset, TonAsset } from '@tonkeeper/core/dist/entries/crypto/asset/ton-asset'; import { RecipientData, TonRecipientData } from '@tonkeeper/core/dist/entries/send'; import { - TonTransferParams, - parseTonTransferWithAddress + parseTonTransferWithAddress, + TonTransferParams } from '@tonkeeper/core/dist/service/deeplinkingService'; import { shiftedDecimals } from '@tonkeeper/core/dist/utils/balance'; import BigNumber from 'bignumber.js'; @@ -17,7 +17,7 @@ import { openIosKeyboard } from '../../hooks/ios'; import { useTranslation } from '../../hooks/translation'; import { useIsFullWidthMode } from '../../hooks/useIsFullWidthMode'; import { useJettonList } from '../../state/jetton'; -import { useTronBalances } from '../../state/tron/tron'; +import { useActiveTronWallet } from '../../state/tron/tron'; import { Notification, NotificationFooter, @@ -38,16 +38,16 @@ import { AmountState } from './amountView/amountState'; import { AmountHeaderBlock, AmountMainButton, + childFactoryCreator, ConfirmMainButton, + duration, InitTransferData, MainButton, - RecipientHeaderBlock, - Wrapper, - childFactoryCreator, - duration, + makeTransferInitAmountState, makeTransferInitData, + RecipientHeaderBlock, TransferViewHeaderBlock, - makeTransferInitAmountState + Wrapper } from './common'; import { MultisigOrderFormView } from './MultisigOrderFormView'; import { MultisigOrderLifetimeMinutes } from '../../libs/multisig'; @@ -103,7 +103,7 @@ const SendContent: FC<{ } }, []); - const { data: tronBalances } = useTronBalances(); + const activeTronWallet = useActiveTronWallet(); const { mutateAsync: getAccountAsync, isLoading: isAccountLoading } = useGetToAccount(); @@ -116,7 +116,7 @@ const SendContent: FC<{ } _setRecipient(value); - if (tronBalances && value.address.blockchain === BLOCKCHAIN_NAME.TRON) { + if (activeTronWallet && value.address.blockchain === BLOCKCHAIN_NAME.TRON) { setAmountViewState({ token: TRON_USDT_ASSET }); } }; @@ -154,6 +154,9 @@ const SendContent: FC<{ }; const processTron = (address: string) => { + if (!activeTronWallet) { + return; + } const item = { address: address, blockchain: BLOCKCHAIN_NAME.TRON } as const; setRecipient({ @@ -263,6 +266,19 @@ const SendContent: FC<{ }); }, [amountViewState?.token?.id, amountViewState?.coinValue]); + let acceptBlockchains: BLOCKCHAIN_NAME[] = []; + if (chain) { + if (chain === BLOCKCHAIN_NAME.TRON && !activeTronWallet) { + acceptBlockchains = [BLOCKCHAIN_NAME.TON]; + } else { + acceptBlockchains = [chain]; + } + } else { + acceptBlockchains = activeTronWallet + ? [BLOCKCHAIN_NAME.TON, BLOCKCHAIN_NAME.TRON] + : [BLOCKCHAIN_NAME.TON]; + } + return ( @@ -300,7 +316,7 @@ const SendContent: FC<{ onScan={onScan} keyboard="decimal" isExternalLoading={isAccountLoading} - acceptBlockchains={chain ? [chain] : undefined} + acceptBlockchains={acceptBlockchains} MainButton={MainButton} HeaderBlock={() => ( { ); }; +const EnableTronSettings = () => { + const { mutate: mutateSettings } = useMutateDevSettings(); + const { data: devSettings } = useDevSettings(); + + return ( + + + + + Enable USDT TRC-20 + Experimental + + mutateSettings({ tronEnabled: checked })} + /> + + + + ); +}; + export const DevSettings = React.memo(() => { return ( <> + {/* TODO: ENABLE TRON */} {/* */} diff --git a/packages/uikit/src/state/tron/tron.ts b/packages/uikit/src/state/tron/tron.ts index a185ffb89..c8039e08d 100644 --- a/packages/uikit/src/state/tron/tron.ts +++ b/packages/uikit/src/state/tron/tron.ts @@ -13,6 +13,7 @@ import { TRON_USDT_ASSET } from '@tonkeeper/core/dist/entries/crypto/asset/constants'; import { TronAsset } from '@tonkeeper/core/dist/entries/crypto/asset/tron-asset'; +import { useDevSettings } from '../dev'; export const useTronApi = () => { const appContext = useAppContext(); @@ -23,13 +24,30 @@ export const useTronApi = () => { return useMemo(() => new TronApi(apiUrl, apiKey), [apiKey, apiUrl]); }; +export const useIsTronEnabledGlobally = () => { + const { data: devSettings } = useDevSettings(); + + return devSettings?.tronEnabled; +}; + export const useCanUseTronForActiveWallet = () => { + const isTronEnabled = useIsTronEnabledGlobally(); const account = useActiveAccount(); + + if (!isTronEnabled) { + return false; + } + return isAccountTronCompatible(account); }; export const useActiveTronWallet = (): TronWallet | undefined => { const account = useActiveAccount(); + const isTronEnabled = useIsTronEnabledGlobally(); + + if (!isTronEnabled) { + return undefined; + } if (isAccountTronCompatible(account)) { return account.activeTronWallet; From 2a7e1d0e05737cb90955ffe75d2bc9346085f7aa Mon Sep 17 00:00:00 2001 From: siandreev Date: Tue, 24 Dec 2024 14:02:53 +0100 Subject: [PATCH 08/20] chore: solve merge conflicts --- packages/uikit/src/pages/settings/Dev.tsx | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/packages/uikit/src/pages/settings/Dev.tsx b/packages/uikit/src/pages/settings/Dev.tsx index a7184a601..1a5af34e1 100644 --- a/packages/uikit/src/pages/settings/Dev.tsx +++ b/packages/uikit/src/pages/settings/Dev.tsx @@ -5,12 +5,6 @@ import { SettingsItem, SettingsList } from '../../components/settings/SettingsLi import { useAppSdk } from '../../hooks/appSdk'; import { CloseIcon, SpinnerIcon } from '../../components/Icon'; import { useMutation, useQueryClient } from '@tanstack/react-query'; -import { ListBlock, ListItem, ListItemPayload } from '../../components/List'; -import { Label1 } from '../../components/Text'; -import { Switch } from '../../components/fields/Switch'; -import { Badge } from '../../components/shared'; -import styled from 'styled-components'; -import { useDevSettings, useMutateDevSettings } from '../../state/dev'; import { AppKey } from '@tonkeeper/core/dist/Keys'; import { ListBlock, ListItem, ListItemPayload } from '../../components/List'; import { Body3, Label1 } from '../../components/Text'; @@ -61,7 +55,6 @@ const TextAndBadge = styled.div` gap: 6px; `; - const EnableTwoFASettings = () => { const { mutate: mutateSettings } = useMutateDevSettings(); const { data: devSettings } = useDevSettings(); @@ -93,13 +86,13 @@ const EnableTronSettings = () => { - - - Enable 2FA - Experimental - - Available only for W5 wallets - + + + Enable 2FA + Experimental + + Available only for W5 wallets + Date: Thu, 26 Dec 2024 13:27:05 +0100 Subject: [PATCH 09/20] fix: tron activity --- packages/core/src/tronApi/index.ts | 69 +++++ .../src/components/activity/ActivityGroup.tsx | 6 +- .../desktop/history/DesktopHistory.tsx | 34 +-- .../desktop/history/HistoryEvent.tsx | 15 +- .../desktop-pages/coin/DesktopCoinPage.tsx | 14 +- .../history/DesktopHistoryPage.tsx | 28 +- packages/uikit/src/state/activity.ts | 280 ++++++++++++------ packages/uikit/src/state/mixedActivity.ts | 87 ------ 8 files changed, 300 insertions(+), 233 deletions(-) delete mode 100644 packages/uikit/src/state/mixedActivity.ts diff --git a/packages/core/src/tronApi/index.ts b/packages/core/src/tronApi/index.ts index e6284b397..22b2fdc28 100644 --- a/packages/core/src/tronApi/index.ts +++ b/packages/core/src/tronApi/index.ts @@ -1,9 +1,33 @@ import BigNumber from 'bignumber.js'; import { TRON_USDT_ASSET } from '../entries/crypto/asset/constants'; import { TronWeb } from 'tronweb'; +import { TronAsset } from '../entries/crypto/asset/tron-asset'; +import { AssetAmount } from '../entries/crypto/asset/asset-amount'; +import { notNullish } from '../utils/types'; const removeTrailingSlash = (str: string) => str.replace(/\/$/, ''); +type TronTokenDTO = { + transaction_id: string; // no 0x + token_info: { + address: string; + }; + block_timestamp: number; // ms + from: string; + to: string; + type: 'Transfer'; + value: string; +}; + +export type TronHistoryItemTransferAsset = { + assetAmount: AssetAmount; + timestamp: number; + transactionHash: string; + from: string; + to: string; +}; +export type TronHistoryItem = TronHistoryItemTransferAsset; + export class TronApi { public readonly baseURL: string; @@ -185,4 +209,49 @@ export class TronApi { throw error; } } + + async getTransfersHistory( + address: string, + options?: { limit?: number; maxTimestamp?: number } + ): Promise { + const url = new URL(`${this.baseURL}/v1/accounts/${address}/transactions/trc20`); + + if (options?.limit !== undefined) { + url.searchParams.set('limit', options.limit.toString()); + } + + if (options?.maxTimestamp !== undefined) { + url.searchParams.set('max_timestamp', options.maxTimestamp.toString()); + } + + const response = await ( + await fetch(url, { + method: 'GET', + headers: this.headers + }) + ).json(); + + if (!response?.success || !response?.data || !Array.isArray(response.data)) { + throw new Error('Error fetching transfers history'); + } + + return response.data + .map((item: TronTokenDTO) => { + if (item.type !== 'Transfer') { + return null; + } + if (item.token_info?.address !== TRON_USDT_ASSET.address) { + return null; + } + + return { + assetAmount: new AssetAmount({ weiAmount: item.value, asset: TRON_USDT_ASSET }), + timestamp: item.block_timestamp, + transactionHash: item.transaction_id, + from: item.from, + to: item.to + } satisfies TronHistoryItemTransferAsset; + }) + .filter(notNullish); + } } diff --git a/packages/uikit/src/components/activity/ActivityGroup.tsx b/packages/uikit/src/components/activity/ActivityGroup.tsx index 093b24ba5..db3284689 100644 --- a/packages/uikit/src/components/activity/ActivityGroup.tsx +++ b/packages/uikit/src/components/activity/ActivityGroup.tsx @@ -1,7 +1,7 @@ import { InfiniteData } from '@tanstack/react-query'; import { AccountEvents } from '@tonkeeper/core/dist/tonApiV2'; import React, { FC, useMemo, useState } from 'react'; -import { formatActivityDate, GenericActivityGroup, getActivityTitle } from '../../state/activity'; +import { formatActivityDate, ActivityItemsDatedGroup, getActivityTitle } from '../../state/activity'; import { MixedActivity, getMixedActivityGroups } from '../../state/mixedActivity'; import { CoinHistorySkeleton, HistoryBlock, SkeletonListWithImages } from '../Skeleton'; import { Group, List, Title } from './ActivityLayout'; @@ -17,7 +17,7 @@ export const ActivityList: FC<{ tonEvents?: InfiniteData; tronEvents?: InfiniteData; }> = ({ isFetched, isFetchingNextPage, tonEvents, tronEvents }) => { - const activity = useMemo[]>(() => { + const activity = useMemo[]>(() => { return getMixedActivityGroups(tonEvents, tronEvents); }, [tonEvents, tronEvents]); @@ -33,7 +33,7 @@ export const ActivityList: FC<{ }; export const MixedActivityGroup: FC<{ - items: GenericActivityGroup[]; + items: ActivityItemsDatedGroup[]; }> = ({ items }) => { const [tonAction, seTonAction] = useState(undefined); const [tronAction, setTronAction] = useState(undefined); diff --git a/packages/uikit/src/components/desktop/history/DesktopHistory.tsx b/packages/uikit/src/components/desktop/history/DesktopHistory.tsx index 99ddd8181..10a0316de 100644 --- a/packages/uikit/src/components/desktop/history/DesktopHistory.tsx +++ b/packages/uikit/src/components/desktop/history/DesktopHistory.tsx @@ -2,8 +2,11 @@ import { HistoryEvent, HistoryGridTimeCell } from './HistoryEvent'; import { SpinnerRing } from '../../Icon'; import React, { FC, useMemo, useState } from 'react'; import styled from 'styled-components'; -import { GenericActivity } from '../../../state/activity'; -import { MixedActivity } from '../../../state/mixedActivity'; +import { + ActivityItem, + CategorizedActivity, + CategorizedActivityItemGroup +} from '../../../state/activity'; import { ActionData, ActivityNotification } from '../../activity/ton/ActivityNotification'; const ContainerQuery = styled.div` @@ -62,24 +65,9 @@ const FetchingRows = styled.div` } `; -type GroupedActivityItemSingle = { - type: 'single'; - item: GenericActivity; - key: string; -}; - -type GroupedActivityItemGroup = { - type: 'group'; - items: GenericActivity[]; - category: 'spam'; - key: string; -}; - -type GroupedActivity = (GroupedActivityItemSingle | GroupedActivityItemGroup)[]; - const HistoryEvents: FC<{ className?: string; - aggregatedActivity: GroupedActivity; + aggregatedActivity: CategorizedActivity; setSelectedActivity: React.Dispatch>; }> = ({ className, aggregatedActivity, setSelectedActivity }) => { return ( @@ -102,17 +90,17 @@ const HistoryEvents: FC<{ }; export const DesktopHistory: FC<{ - activity: GenericActivity[] | undefined; + activity: ActivityItem[] | undefined; isFetchingNextPage: boolean; className?: string; }> = ({ activity, isFetchingNextPage, className }) => { const [selectedActivity, setSelectedActivity] = useState(); - const aggregatedActivity: GroupedActivity = useMemo(() => { + const aggregatedActivity: CategorizedActivity = useMemo(() => { const double = new Set(); const groupped = (activity ?? []).reduce((acc, item) => { - if (item.event.kind === 'tron' || !item.event.event.isScam) { + if (item.type === 'tron' || !item.event.isScam) { if (!double.has(item.key)) { double.add(item.key); acc.push({ @@ -125,7 +113,7 @@ export const DesktopHistory: FC<{ } if (acc.length > 0 && acc[acc.length - 1].type === 'group') { - const group = acc[acc.length - 1] as GroupedActivityItemGroup; + const group = acc[acc.length - 1] as CategorizedActivityItemGroup; group.items.push(item); group.key = item.key; return acc; @@ -139,7 +127,7 @@ export const DesktopHistory: FC<{ }); return acc; - }, [] as GroupedActivity); + }, [] as CategorizedActivity); return groupped.map(i => { if (i.type === 'group' && i.items.length === 1) { diff --git a/packages/uikit/src/components/desktop/history/HistoryEvent.tsx b/packages/uikit/src/components/desktop/history/HistoryEvent.tsx index c90de7815..f7d985180 100644 --- a/packages/uikit/src/components/desktop/history/HistoryEvent.tsx +++ b/packages/uikit/src/components/desktop/history/HistoryEvent.tsx @@ -1,7 +1,6 @@ -import { MixedActivity } from '../../../state/mixedActivity'; import { FC, useState, MouseEvent } from 'react'; import styled from 'styled-components'; -import { GenericActivity, GroupedActivityItem } from '../../../state/activity'; +import { ActivityItem, CategorizedActivityItem } from '../../../state/activity'; import { Body2 } from '../../Text'; import { useDateTimeFormatFromNow } from '../../../hooks/useDateTimeFormat'; import { HistoryAction } from './ton/HistoryAction'; @@ -51,7 +50,7 @@ const GroupItemLeftSpacer = styled.div` `; export const HistoryEvent: FC<{ - group: GroupedActivityItem; + group: CategorizedActivityItem; onActionClick: (actionData: ActionData) => void; }> = ({ group, onActionClick }) => { if (group.type === 'single') { @@ -62,7 +61,7 @@ export const HistoryEvent: FC<{ }; const HistoryEventSingle: FC<{ - item: GenericActivity; + item: ActivityItem; onActionClick: (actionData: ActionData) => void; onCollapse?: () => void; onExpand?: () => void; @@ -71,11 +70,11 @@ const HistoryEventSingle: FC<{ const formattedDate = useDateTimeFormatFromNow(item.timestamp); const { t } = useTranslation(); - if (item.event.kind === 'tron') { - return null; + if (item.type === 'tron') { + return } - const event = item.event.event; + const event = item.event; const handleChevronClick = (e: MouseEvent) => { onExpand?.(); @@ -155,7 +154,7 @@ const IconButtonTransparentBackgroundStyled = styled(IconButtonTransparentBackgr `; const HistoryEventGroup: FC<{ - items: GenericActivity[]; + items: ActivityItem[]; onActionClick: (actionData: ActionData) => void; }> = ({ items, onActionClick }) => { const [isExpanded, setIsExpanded] = useState(false); diff --git a/packages/uikit/src/desktop-pages/coin/DesktopCoinPage.tsx b/packages/uikit/src/desktop-pages/coin/DesktopCoinPage.tsx index 1a3d7481c..96f3217aa 100644 --- a/packages/uikit/src/desktop-pages/coin/DesktopCoinPage.tsx +++ b/packages/uikit/src/desktop-pages/coin/DesktopCoinPage.tsx @@ -23,7 +23,6 @@ import { useFetchNext } from '../../hooks/useFetchNext'; import { AppRoute } from '../../libs/routes'; import { useFetchFilteredActivity, useScrollMonitor } from '../../state/activity'; import { useAssets } from '../../state/home'; -import { getMixedActivity } from '../../state/mixedActivity'; import { toTokenRate, useRate } from '../../state/rates'; import { useAllSwapAssets } from '../../state/swap/useSwapAssets'; import { useSwapFromAsset } from '../../state/swap/useSwapForm'; @@ -297,17 +296,18 @@ const CoinPage: FC<{ token: string }> = ({ token }) => { const { t } = useTranslation(); const ref = useRef(null); - const { fetchNextPage, hasNextPage, isFetchingNextPage, data, refetch } = - useFetchFilteredActivity(token); + const { + fetchNextPage, + hasNextPage, + isFetchingNextPage, + data: activity, + refetch + } = useFetchFilteredActivity(token); useScrollMonitor(ref, 5000, refetch); useFetchNext(hasNextPage, isFetchingNextPage, fetchNextPage, true, ref); - const activity = useMemo(() => { - return getMixedActivity(data, undefined); - }, [data]); - const [assets] = useAssets(); const assetSymbol = useMemo(() => { if (!assets) { diff --git a/packages/uikit/src/desktop-pages/history/DesktopHistoryPage.tsx b/packages/uikit/src/desktop-pages/history/DesktopHistoryPage.tsx index fb0df5732..6374d6495 100644 --- a/packages/uikit/src/desktop-pages/history/DesktopHistoryPage.tsx +++ b/packages/uikit/src/desktop-pages/history/DesktopHistoryPage.tsx @@ -1,9 +1,7 @@ -import { FC, Suspense, useCallback, useMemo, useRef } from 'react'; +import { FC, Suspense, useRef } from 'react'; import styled from 'styled-components'; import { ActivitySkeletonPage } from '../../components/Skeleton'; -import { useAppContext } from '../../hooks/appContext'; import { useFetchNext } from '../../hooks/useFetchNext'; -import { getMixedActivity } from '../../state/mixedActivity'; import EmptyActivity from '../../components/activity/EmptyActivity'; import { DesktopViewHeader, @@ -21,8 +19,6 @@ import { AssetHistoryFilter, OtherHistoryFilters } from '../../components/desktop/history/DesktopHistoryFilters'; -import { useQueryClient } from '@tanstack/react-query'; -import { QueryKey } from '../../libs/queryKey'; const HistoryPageWrapper = styled(DesktopViewPageLayout)` overflow: auto; @@ -85,29 +81,25 @@ export const DesktopHistoryPage: FC = () => { const { refetch, - isFetched: isTonFetched, - fetchNextPage: fetchTonNextPage, - hasNextPage: hasTonNextPage, - isFetchingNextPage: isTonFetchingNextPage, - data: tonEvents + isFetched: isActivityFetched, + fetchNextPage: fetchActivityNextPage, + hasNextPage: hasActivityNextPage, + isFetchingNextPage: isActivityFetchingNextPage, + data: activity } = useFetchFilteredActivity(); useScrollMonitor(ref, 5000, refetch); - const isFetchingNextPage = isTonFetchingNextPage; + const isFetchingNextPage = isActivityFetchingNextPage; - useFetchNext(hasTonNextPage, isFetchingNextPage, fetchTonNextPage, true, ref); - - const activity = useMemo(() => { - return getMixedActivity(tonEvents, undefined); - }, [tonEvents]); + useFetchNext(hasActivityNextPage, isFetchingNextPage, fetchActivityNextPage, true, ref); const onOpenExplorer = () => config.accountExplorer ? sdk.openPage(config.accountExplorer.replace('%s', formatAddress(wallet.rawAddress))) : undefined; - if (!isTonFetched!) { + if (!isActivityFetched!) { return ( @@ -129,7 +121,7 @@ export const DesktopHistoryPage: FC = () => { ); } - if (activity.length === 0) { + if (activity?.length === 0) { return ( }> diff --git a/packages/uikit/src/state/activity.ts b/packages/uikit/src/state/activity.ts index 55bb6f04c..5b9689d6a 100644 --- a/packages/uikit/src/state/activity.ts +++ b/packages/uikit/src/state/activity.ts @@ -6,16 +6,19 @@ import { tonAssetAddressToString } from '@tonkeeper/core/dist/entries/crypto/asset/ton-asset'; import { intlLocale } from '@tonkeeper/core/dist/entries/language'; -import { AccountEvents, AccountsApi } from '@tonkeeper/core/dist/tonApiV2'; +import { AccountEvent, AccountEvents, AccountsApi } from '@tonkeeper/core/dist/tonApiV2'; import { useCallback, useEffect, useLayoutEffect, useState } from 'react'; import { atom, useAtom } from '../libs/atom'; import { QueryKey } from '../libs/queryKey'; import { useGlobalPreferences, useMutateGlobalPreferences } from './global-preferences'; -import { MixedActivity } from './mixedActivity'; import { seeIfTonTransfer } from './ton/tonActivity'; import { useActiveApi, useActiveWallet } from './wallet'; import { debounce } from '@tonkeeper/core/dist/utils/common'; import { useTwoFAWalletConfig } from './two-fa'; +import { useActiveTronWallet, useTronApi } from './tron/tron'; +import { APIConfig } from '@tonkeeper/core/dist/entries/apis'; +import { TonContract } from '@tonkeeper/core/dist/entries/wallet'; +import { TronApi, TronHistoryItem } from '@tonkeeper/core/dist/tronApi'; export const formatActivityDate = (language: string, key: string, timestamp: number): string => { const date = new Date(timestamp); @@ -93,10 +96,14 @@ const getEventGroup = (timestamp: number, today: Date, yesterday: Date): string return `year-${date.getFullYear()}-${date.getMonth() + 1}`; }; -export type GenericActivity = { timestamp: number; key: string; event: T }; -export type GenericActivityGroup = [string, GenericActivity[]]; +type ActivityItemCommon = { timestamp: number; key: string }; +type ActivityItemTon = ActivityItemCommon & { type: 'ton'; event: AccountEvent }; +type ActivityItemTron = ActivityItemCommon & { type: 'tron'; event: TronHistoryItem }; -export const groupGenericActivity = (list: GenericActivity[]) => { +export type ActivityItem = ActivityItemTon | ActivityItemTron; +export type ActivityItemsDatedGroup = [string, ActivityItem[]]; + +export const groupActivityItems = (list: ActivityItem[]) => { list.sort((a, b) => b.timestamp - a.timestamp); const todayDate = new Date(); @@ -111,9 +118,9 @@ export const groupGenericActivity = (list: GenericActivity[]) => { acc[group] = [item]; } return acc; - }, {} as Record[]>); + }, {} as Record); - const result = [] as GenericActivityGroup[]; + const result = [] as ActivityItemsDatedGroup[]; if (today) { result.push(['today', today]); } @@ -132,27 +139,16 @@ export const groupGenericActivity = (list: GenericActivity[]) => { return result; }; -export const groupActivityGeneric = ( - list: T[], - toTimestamp: (item: T) => number, - toKey: (item: T) => string -): GenericActivityGroup[] => { - const activity = list.map(item => ({ - timestamp: toTimestamp(item), - key: toKey(item), - event: item - })); - return groupGenericActivity(activity); -}; - export const useFetchFilteredActivity = (asset?: string) => { const wallet = useActiveWallet(); const api = useActiveApi(); const { asset: selectedAsset, filterSpam, onlyInitiator } = useHistoryFilters(); const { data: twoFAConfig } = useTwoFAWalletConfig(); const twoFaPlugin = twoFAConfig?.status === 'active' ? twoFAConfig.pluginAddress : undefined; + const tronApi = useTronApi(); + const tronWallet = useActiveTronWallet(); - return useInfiniteQuery({ + const query = useInfiniteQuery({ queryKey: [ wallet.rawAddress, QueryKey.activity, @@ -160,91 +156,201 @@ export const useFetchFilteredActivity = (asset?: string) => { selectedAsset?.id, onlyInitiator, filterSpam, - twoFaPlugin + twoFaPlugin, + tronWallet ], queryFn: async ({ pageParam = undefined }) => { - let activity: AccountEvents; - if (!asset && !selectedAsset) { - activity = await new AccountsApi(api.tonApiV2).getAccountEvents({ - accountId: wallet.rawAddress, - limit: 20, - beforeLt: pageParam, - subjectOnly: true, - initiator: onlyInitiator ? onlyInitiator : undefined - }); - } else { - let assetTonApiId: string; - if (selectedAsset) { - assetTonApiId = tonAssetAddressToString(selectedAsset.address); - } - if (asset) { - assetTonApiId = - asset.toLowerCase() === CryptoCurrency.TON.toLowerCase() ? 'TON' : asset; - } - - if (assetTonApiId! === 'TON') { - activity = await new AccountsApi(api.tonApiV2).getAccountEvents({ - accountId: wallet.rawAddress, - limit: 20, - beforeLt: pageParam, - subjectOnly: true, - initiator: onlyInitiator ? onlyInitiator : undefined - }); - - activity.events = activity.events.filter(event => { - event.actions = event.actions.filter(seeIfTonTransfer); - return event.actions.length > 0; - }); - } else { - activity = await new AccountsApi(api.tonApiV2).getAccountJettonHistoryByID({ - accountId: wallet.rawAddress, - jettonId: assetTonApiId!, - limit: 20, - beforeLt: pageParam - }); - } - } - - if (filterSpam) { - activity.events = activity.events.filter(event => !event.isScam); + let assetTonApiId: string | undefined; + if (selectedAsset) { + assetTonApiId = tonAssetAddressToString(selectedAsset.address); } - - if (twoFaPlugin) { - activity.events = activity.events.map(event => { - if ( - event.actions[0].tonTransfer && - event.actions[0].tonTransfer.sender.address === twoFaPlugin - ) { - event.actions = event.actions.slice(1); - } - - return event; - }); + if (asset) { + assetTonApiId = + asset.toLowerCase() === CryptoCurrency.TON.toLowerCase() ? 'TON' : asset; } - return activity; + const [tonActivity, tronActivity] = await Promise.all([ + fetchTonActivity({ + pageParam: pageParam?.tonNextFrom, + assetTonApiId, + api, + wallet, + onlyInitiator, + filterSpam, + twoFaPluginAddress: twoFaPlugin + }), + fetchTronActivity({ + tronApi, + tronWalletAddress: tronWallet?.address, + pageParam: pageParam?.tronNextFrom + }) + ]); + + return { + tonNextFrom: tonActivity.nextFrom, + tronNextFrom: tronActivity.nextFrom, + events: sortAndPackActivityItems(tonActivity.events, tronActivity.events) + }; }, - getNextPageParam: lastPage => (lastPage.nextFrom > 0 ? lastPage.nextFrom : undefined), + getNextPageParam: lastPage => + lastPage.tronNextFrom > 0 || lastPage.tonNextFrom > 0 + ? { tonNextFrom: lastPage.tonNextFrom, tronNextFrom: lastPage.tronNextFrom } + : undefined, keepPreviousData: true }); + + const data = query.data ? query.data.pages.flatMap(i => i.events) : undefined; + + return { ...query, data }; }; -export type GroupedActivityItemSingle = { +const sortAndPackActivityItems = ( + tonItems: AccountEvent[], + tronItems: TronHistoryItem[] +): ActivityItem[] => { + return [ + ...tonItems.map( + item => + ({ + type: 'ton' as const, + timestamp: item.timestamp * 1000, + key: item.eventId, + event: item + } satisfies ActivityItemTon) + ), + ...tronItems.map( + item => + ({ + type: 'tron' as const, + timestamp: item.timestamp, + key: item.transactionHash, + event: item + } satisfies ActivityItemTron) + ) + ].sort((a, b) => b.timestamp - a.timestamp); +}; + +async function fetchTronActivity({ + tronApi, + tronWalletAddress, + pageParam +}: { + tronApi: TronApi; + tronWalletAddress?: string; + pageParam?: number; +}) { + if (pageParam === 0 || !tronWalletAddress) { + return { + nextFrom: 0, + events: [] + }; + } + + const tronActivity = await tronApi.getTransfersHistory(tronWalletAddress, { + limit: 20, + maxTimestamp: pageParam + }); + + const nextFrom = tronActivity.length < 20 ? 0 : tronActivity[tronActivity.length - 1].timestamp; + + return { + nextFrom, + events: tronActivity + }; +} + +async function fetchTonActivity({ + pageParam = undefined, + assetTonApiId, + api, + wallet, + onlyInitiator, + filterSpam, + twoFaPluginAddress +}: { + pageParam?: number; + assetTonApiId?: string; + api: APIConfig; + wallet: TonContract; + onlyInitiator: boolean; + filterSpam: boolean; + twoFaPluginAddress?: string; +}) { + if (pageParam === 0) { + return { + nextFrom: 0, + events: [] + }; + } + + let tonActivity: AccountEvents; + if (!assetTonApiId) { + tonActivity = await new AccountsApi(api.tonApiV2).getAccountEvents({ + accountId: wallet.rawAddress, + limit: 20, + beforeLt: pageParam, + subjectOnly: true, + initiator: onlyInitiator ? onlyInitiator : undefined + }); + } else { + if (assetTonApiId! === 'TON') { + tonActivity = await new AccountsApi(api.tonApiV2).getAccountEvents({ + accountId: wallet.rawAddress, + limit: 20, + beforeLt: pageParam, + subjectOnly: true, + initiator: onlyInitiator ? onlyInitiator : undefined + }); + + tonActivity.events = tonActivity.events.filter(event => { + event.actions = event.actions.filter(seeIfTonTransfer); + return event.actions.length > 0; + }); + } else { + tonActivity = await new AccountsApi(api.tonApiV2).getAccountJettonHistoryByID({ + accountId: wallet.rawAddress, + jettonId: assetTonApiId!, + limit: 20, + beforeLt: pageParam + }); + } + } + + if (filterSpam) { + tonActivity.events = tonActivity.events.filter(event => !event.isScam); + } + + if (twoFaPluginAddress) { + tonActivity.events = tonActivity.events.map(event => { + if ( + event.actions[0].tonTransfer && + event.actions[0].tonTransfer.sender.address === twoFaPluginAddress + ) { + event.actions = event.actions.slice(1); + } + + return event; + }); + } + + return tonActivity; +} + +export type CategorizedActivityItemSingle = { type: 'single'; - item: GenericActivity; + item: ActivityItem; key: string; }; -export type GroupedActivityItemGroup = { +export type CategorizedActivityItemGroup = { type: 'group'; - items: GenericActivity[]; + items: ActivityItem[]; category: 'spam'; // probably will be other groupBy categories in the future key: string; }; -export type GroupedActivityItem = GroupedActivityItemSingle | GroupedActivityItemGroup; - -export type GroupedActivity = GroupedActivityItem[]; +export type CategorizedActivityItem = CategorizedActivityItemSingle | CategorizedActivityItemGroup; +export type CategorizedActivity = CategorizedActivityItem[]; export const defaultHistoryFilters = { asset: undefined, diff --git a/packages/uikit/src/state/mixedActivity.ts b/packages/uikit/src/state/mixedActivity.ts deleted file mode 100644 index c2fa56df6..000000000 --- a/packages/uikit/src/state/mixedActivity.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { InfiniteData } from '@tanstack/react-query'; -import { AccountEvent, AccountEvents } from '@tonkeeper/core/dist/tonApiV2'; -import { GenericActivity, groupGenericActivity } from './activity'; - -type TronEvent = any; -type TronEvents = any; // TODO - -export interface TronActivity { - kind: 'tron'; - event: TronEvent; -} - -export interface TonActivity { - kind: 'ton'; - event: AccountEvent; -} - -const cutOffTronEvents = ( - tonEvents: InfiniteData | undefined, - tronEvents: InfiniteData -): InfiniteData => { - if (!tonEvents || !tonEvents.pages.length) { - return tronEvents; - } - - if (tonEvents.pageParams.length) { - const pastPageParam = tonEvents.pageParams[tonEvents.pageParams.length - 1]; - if (pastPageParam === undefined || pastPageParam === 0) { - return tronEvents; - } - } - - const { events } = tonEvents.pages[tonEvents.pages.length - 1]; - if (!events.length) return tronEvents; - - const lastEvents = events[events.length - 1]; - - const lastTime = lastEvents.timestamp * 1000; - - return { - pageParams: tronEvents.pageParams, - pages: tronEvents.pages.map(page => { - return { ...page, events: page.events.filter(event => event.timestamp > lastTime) }; - }) - }; -}; - -export type MixedActivity = TronActivity | TonActivity; - -export const getMixedActivity = ( - tonEvents: InfiniteData | undefined, - tronEvents: InfiniteData | undefined -) => { - const activity: GenericActivity[] = []; - - if (tonEvents) { - tonEvents.pages.forEach(page => { - const tonActivity: GenericActivity[] = page.events.map(event => ({ - timestamp: event.timestamp * 1000, - key: event.eventId, - event: { kind: 'ton', event } - })); - activity.push(...tonActivity); - }); - } - - if (tronEvents) { - const events = cutOffTronEvents(tonEvents, tronEvents); - events.pages.forEach(page => { - const tronActivity: GenericActivity[] = page.events.map(event => ({ - timestamp: event.timestamp, - key: `${event.txHash}-${event.actions.map(item => item.type).join('-')}`, - event: { kind: 'tron', event } - })); - activity.push(...tronActivity); - }); - } - - return activity; -}; - -export const getMixedActivityGroups = ( - tonEvents: InfiniteData | undefined, - tronEvents: InfiniteData | undefined -) => { - return groupGenericActivity(getMixedActivity(tonEvents, tronEvents)); -}; From ed9e0f91119e1f130ff569ebc25c46ae17238d03 Mon Sep 17 00:00:00 2001 From: siandreev Date: Thu, 26 Dec 2024 15:53:46 +0100 Subject: [PATCH 10/20] fix: usdt history for desktop --- packages/core/src/tronApi/index.ts | 32 ++++- packages/core/src/utils/types.ts | 4 + .../activity/NotificationCommon.tsx | 55 +------- .../activity/ton/ActivityNotification.tsx | 52 +++++-- ...tails.tsx => TonActivityActionDetails.tsx} | 0 .../activity/tron/ActivityActionDetails.tsx | 132 ------------------ .../activity/tron/ActivityNotification.tsx | 56 -------- .../activity/tron/TronActivityAction.tsx | 80 ----------- .../tron/TronActivityActionDetails.tsx | 67 +++++++++ .../activity/tron/TronActivityEvents.tsx | 33 ----- .../desktop/history/DesktopHistory.tsx | 11 +- .../desktop/history/HistoryEvent.tsx | 30 +++- .../history/tron/TronHistoryAction.tsx | 29 ++++ .../tron/TronTransferDesktopAction.tsx | 74 ++++++++++ packages/uikit/src/state/activity.ts | 31 ++-- 15 files changed, 300 insertions(+), 386 deletions(-) rename packages/uikit/src/components/activity/ton/{ActivityActionDetails.tsx => TonActivityActionDetails.tsx} (100%) delete mode 100644 packages/uikit/src/components/activity/tron/ActivityActionDetails.tsx delete mode 100644 packages/uikit/src/components/activity/tron/ActivityNotification.tsx delete mode 100644 packages/uikit/src/components/activity/tron/TronActivityAction.tsx create mode 100644 packages/uikit/src/components/activity/tron/TronActivityActionDetails.tsx delete mode 100644 packages/uikit/src/components/activity/tron/TronActivityEvents.tsx create mode 100644 packages/uikit/src/components/desktop/history/tron/TronHistoryAction.tsx create mode 100644 packages/uikit/src/components/desktop/history/tron/TronTransferDesktopAction.tsx diff --git a/packages/core/src/tronApi/index.ts b/packages/core/src/tronApi/index.ts index 22b2fdc28..abc455b4c 100644 --- a/packages/core/src/tronApi/index.ts +++ b/packages/core/src/tronApi/index.ts @@ -20,11 +20,14 @@ type TronTokenDTO = { }; export type TronHistoryItemTransferAsset = { + type: 'asset-transfer'; assetAmount: AssetAmount; timestamp: number; transactionHash: string; from: string; to: string; + isScam: boolean; + isFailed: boolean; }; export type TronHistoryItem = TronHistoryItemTransferAsset; @@ -212,7 +215,12 @@ export class TronApi { async getTransfersHistory( address: string, - options?: { limit?: number; maxTimestamp?: number } + options?: { + limit?: number; + maxTimestamp?: number; + onlyInitiator?: boolean; + filterSpam?: boolean; + } ): Promise { const url = new URL(`${this.baseURL}/v1/accounts/${address}/transactions/trc20`); @@ -224,6 +232,10 @@ export class TronApi { url.searchParams.set('max_timestamp', options.maxTimestamp.toString()); } + if (options?.onlyInitiator !== undefined) { + url.searchParams.set('only_from', 'true'); + } + const response = await ( await fetch(url, { method: 'GET', @@ -244,12 +256,26 @@ export class TronApi { return null; } + const assetAmount = new AssetAmount({ + weiAmount: item.value, + asset: TRON_USDT_ASSET + }); + + const isScam = assetAmount.relativeAmount.lt(1); + + if (options?.filterSpam && isScam) { + return null; + } + return { - assetAmount: new AssetAmount({ weiAmount: item.value, asset: TRON_USDT_ASSET }), + type: 'asset-transfer', + assetAmount, timestamp: item.block_timestamp, transactionHash: item.transaction_id, from: item.from, - to: item.to + to: item.to, + isScam, + isFailed: false // TODO } satisfies TronHistoryItemTransferAsset; }) .filter(notNullish); diff --git a/packages/core/src/utils/types.ts b/packages/core/src/utils/types.ts index 34da79241..ab846f93a 100644 --- a/packages/core/src/utils/types.ts +++ b/packages/core/src/utils/types.ts @@ -4,6 +4,10 @@ export function assertUnreachable(_: never): never { throw new Error("Didn't expect to get here"); } +export function assertUnreachableSoft(_: never): void { + console.error("Didn't expect to get here", _); +} + export type NonNullableFields = { [P in keyof T]: NonNullable; }; diff --git a/packages/uikit/src/components/activity/NotificationCommon.tsx b/packages/uikit/src/components/activity/NotificationCommon.tsx index ad9160a9d..5ad30b3d9 100644 --- a/packages/uikit/src/components/activity/NotificationCommon.tsx +++ b/packages/uikit/src/components/activity/NotificationCommon.tsx @@ -1,13 +1,11 @@ import { CryptoCurrency } from '@tonkeeper/core/dist/entries/crypto'; import { AssetAmount } from '@tonkeeper/core/dist/entries/crypto/asset/asset-amount'; import { intlLocale } from '@tonkeeper/core/dist/entries/language'; -import { Network } from '@tonkeeper/core/dist/entries/network'; import { AccountAddress, AccountEvent, JettonSwapActionDexEnum } from '@tonkeeper/core/dist/tonApiV2'; -import { TronEvent, TronFee } from '@tonkeeper/core/dist/tronApi'; import { formatDecimals } from '@tonkeeper/core/dist/utils/balance'; import { formatAddress, toShortValue } from '@tonkeeper/core/dist/utils/common'; import React, { FC, PropsWithChildren, useMemo } from 'react'; @@ -122,18 +120,6 @@ export const toDexName = (dex: JettonSwapActionDexEnum) => { } }; -export const TronErrorActivityNotification: FC> = ({ - children, - event -}) => { - const { t } = useTranslation(); - return ( - - {children ?? t('txActions_signRaw_types_unknownTransaction')} - - ); -}; - export const ErrorActivityNotification: FC> = ({ children, event @@ -341,29 +327,6 @@ export const ActionExtraDetails: FC<{ ); }; -export const ActionTronFeeDetails: FC<{ - fees: TronFee; -}> = ({ fees }) => { - const { t } = useTranslation(); - - const amount = useMemo(() => formatDecimals(fees.amount, fees.token.decimals), [fees]); - const { data } = useRate(fees.token.symbol); - const { fiatAmount } = useFormatFiat(data, amount); - - return ( - - - - - - - ); -}; - const FeeLabelColumn = styled.div` display: flex; flex-direction: column; @@ -582,23 +545,7 @@ export const ActionDetailsBlock: FC> ); }; -export const TronActionDetailsBlock: FC> = ({ - event, - children -}) => { - const network = useActiveTonNetwork(); - const url = - network === Network.TESTNET - ? 'https://nile.tronscan.org/#/transaction/%s' - : 'https://tronscan.org/#/transaction/%s'; - return ( - - {children} - - ); -}; - -const CommonActionDetailsBlock: FC> = ({ +export const CommonActionDetailsBlock: FC> = ({ children, eventId, url diff --git a/packages/uikit/src/components/activity/ton/ActivityNotification.tsx b/packages/uikit/src/components/activity/ton/ActivityNotification.tsx index 09dbac738..b4410da6a 100644 --- a/packages/uikit/src/components/activity/ton/ActivityNotification.tsx +++ b/packages/uikit/src/components/activity/ton/ActivityNotification.tsx @@ -7,7 +7,7 @@ import { DomainRenewActionDetails, SmartContractExecActionDetails, TonTransferActionNotification -} from './ActivityActionDetails'; +} from './TonActivityActionDetails'; import { ContractDeployActionDetails } from './ContractDeployAction'; import { JettonBurnActionNotification, @@ -22,6 +22,10 @@ import { WithdrawStakeActionNotification } from './StakeNotifications'; import { SubscribeActionDetails, UnSubscribeActionDetails } from './SubscribeAction'; +import { TronHistoryItem } from '@tonkeeper/core/dist/tronApi'; +import { assertUnreachableSoft } from '@tonkeeper/core/dist/utils/types'; +import { useTranslation } from '../../../hooks/translation'; +import { TronTransferActionNotification } from '../tron/TronActivityActionDetails'; export interface ActionData { isScam: boolean; @@ -30,7 +34,23 @@ export interface ActionData { event: AccountEvent; } -const ActivityContent: FC = props => { +export interface ActivityNotificationDataTon { + type: 'ton'; + isScam: boolean; + action: Action; + timestamp: number; + event: AccountEvent; +} + +export interface ActivityNotificationDataTron { + type: 'tron'; + event: TronHistoryItem; + timestamp: number; +} + +export type ActivityNotificationData = ActivityNotificationDataTon | ActivityNotificationDataTron; + +const ActivityContentTon: FC = props => { switch (props.action.type) { case 'TonTransfer': return ; @@ -77,20 +97,30 @@ const ActivityContent: FC = props => { } }; +const ActivityContentTron: FC = props => { + const { t } = useTranslation(); + switch (props.event.type) { + case 'asset-transfer': + return ; + default: { + assertUnreachableSoft(props.event.type); + return <>{t('txActions_signRaw_types_unknownTransaction')}; + } + } +}; + export const ActivityNotification: FC<{ - value: ActionData | undefined; + value: ActivityNotificationData | undefined; handleClose: () => void; }> = ({ value, handleClose }) => { const Content = useCallback(() => { if (!value) return undefined; - return ( - - ); + + if (value.type === 'tron') { + return ; + } else { + return ; + } }, [value, handleClose]); return ( diff --git a/packages/uikit/src/components/activity/ton/ActivityActionDetails.tsx b/packages/uikit/src/components/activity/ton/TonActivityActionDetails.tsx similarity index 100% rename from packages/uikit/src/components/activity/ton/ActivityActionDetails.tsx rename to packages/uikit/src/components/activity/ton/TonActivityActionDetails.tsx diff --git a/packages/uikit/src/components/activity/tron/ActivityActionDetails.tsx b/packages/uikit/src/components/activity/tron/ActivityActionDetails.tsx deleted file mode 100644 index a403b3a58..000000000 --- a/packages/uikit/src/components/activity/tron/ActivityActionDetails.tsx +++ /dev/null @@ -1,132 +0,0 @@ -import { ReceiveTRC20Action, SendTRC20Action, TronEvent } from '@tonkeeper/core/dist/tronApi'; -import { formatDecimals } from '@tonkeeper/core/dist/utils/balance'; -import React, { FC, useMemo } from 'react'; -import { useTranslation } from '../../../hooks/translation'; -import { useFormatFiat, useRate } from '../../../state/rates'; -import { ListBlock } from '../../List'; -import { ActivityDetailsHeader } from '../ActivityDetailsLayout'; -import { - ActionDate, - ActionDeployerAddress, - ActionRecipientAddress, - ActionSenderAddress, - ActionTransactionDetails, - ActionTronFeeDetails, - Title, - TronActionDetailsBlock, - TronErrorActivityNotification -} from '../NotificationCommon'; -import { TronActionData } from './ActivityNotification'; - -const TronSendTRC20ActionContent: FC<{ - sendTRC20: SendTRC20Action; - timestamp: number; - event: TronEvent; -}> = ({ sendTRC20, timestamp, event }) => { - const amount = useMemo( - () => formatDecimals(sendTRC20.amount, sendTRC20.token.decimals), - [sendTRC20] - ); - const { data } = useRate(sendTRC20.token.symbol); - const { fiatAmount } = useFormatFiat(data, amount); - - return ( - - - - - - {event.fees && } - - - ); -}; - -export const TronSendTRC20ActionNotification: FC = ({ - action, - timestamp, - event -}) => { - const { sendTRC20 } = action; - if (!sendTRC20) { - return ; - } - return ; -}; - -const TronReceiveTRC20ActionContent: FC<{ - receiveTRC20: ReceiveTRC20Action; - timestamp: number; - event: TronEvent; -}> = ({ receiveTRC20, timestamp, event }) => { - const amount = useMemo( - () => formatDecimals(receiveTRC20.amount, receiveTRC20.token.decimals), - [receiveTRC20] - ); - const { data } = useRate(receiveTRC20.token.symbol); - const { fiatAmount } = useFormatFiat(data, amount); - return ( - - - - - - - - ); -}; - -export const TronReceiveTRC20ActionNotification: FC = ({ - action, - timestamp, - event -}) => { - const { receiveTRC20 } = action; - if (!receiveTRC20) { - return ; - } - return ( - - ); -}; - -export const ContractDeployActionDetails: FC = ({ action, timestamp, event }) => { - const { t } = useTranslation(); - const { contractDeploy } = action; - - if (!contractDeploy) { - return ; - } - - return ( - -
- {t('transaction_type_wallet_initialized')} - -
- - - - {event.fees && } - -
- ); -}; diff --git a/packages/uikit/src/components/activity/tron/ActivityNotification.tsx b/packages/uikit/src/components/activity/tron/ActivityNotification.tsx deleted file mode 100644 index 0e95a3f3f..000000000 --- a/packages/uikit/src/components/activity/tron/ActivityNotification.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import { TronAction, TronEvent } from '@tonkeeper/core/dist/tronApi'; -import React, { FC, useCallback } from 'react'; -import { Notification } from '../../Notification'; -import { TronErrorActivityNotification } from '../NotificationCommon'; -import { - ContractDeployActionDetails, - TronReceiveTRC20ActionNotification, - TronSendTRC20ActionNotification -} from './ActivityActionDetails'; - -export interface TronActionData { - action: TronAction; - timestamp: number; - event: TronEvent; -} - -const ActivityContent: FC = props => { - switch (props.action.type) { - case 'ReceiveTRC20': - return ; - case 'SendTRC20': - return ; - case 'ContractDeploy': - return ; - default: { - console.log(props); - return ( - - {props.action.type} - - ); - } - } -}; - -export const TronActivityNotification: FC<{ - value: TronActionData | undefined; - handleClose: () => void; -}> = ({ value, handleClose }) => { - const Content = useCallback(() => { - if (!value) return undefined; - return ( - - ); - }, [value, handleClose]); - - return ( - - {Content} - - ); -}; diff --git a/packages/uikit/src/components/activity/tron/TronActivityAction.tsx b/packages/uikit/src/components/activity/tron/TronActivityAction.tsx deleted file mode 100644 index 355c4611e..000000000 --- a/packages/uikit/src/components/activity/tron/TronActivityAction.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import { - ContractDeployAction, - ReceiveTRC20Action, - SendTRC20Action, - TronAction -} from '@tonkeeper/core/dist/tronApi'; -import { toShortValue } from '@tonkeeper/core/dist/utils/common'; -import React, { FC } from 'react'; -import { useFormatCoinValue } from '../../../hooks/balance'; -import { useTranslation } from '../../../hooks/translation'; -import { - ReceiveActivityAction, - SendActivityAction, - WalletDeployActivityAction -} from '../ActivityActionLayout'; -import { ErrorAction } from '../CommonAction'; - -const ReceiveTRC20: FC<{ - receiveTRC20: ReceiveTRC20Action; - date: string; -}> = ({ receiveTRC20, date }) => { - const format = useFormatCoinValue(); - - return ( - - ); -}; - -const SendTRC20: FC<{ - sendTRC20: SendTRC20Action; - date: string; -}> = ({ sendTRC20, date }) => { - const format = useFormatCoinValue(); - - return ( - - ); -}; - -const ContractDeploy: FC<{ - contractDeploy: ContractDeployAction; - date: string; -}> = ({ contractDeploy, date }) => { - return ( - - ); -}; - -export const TronActivityAction: FC<{ - action: TronAction; - date: string; -}> = ({ action, date }) => { - const { t } = useTranslation(); - - if (action.receiveTRC20) { - return ; - } - if (action.sendTRC20) { - return ; - } - if (action.contractDeploy) { - return ; - } - - console.log(action); - return {t('txActions_signRaw_types_unknownTransaction')}; -}; diff --git a/packages/uikit/src/components/activity/tron/TronActivityActionDetails.tsx b/packages/uikit/src/components/activity/tron/TronActivityActionDetails.tsx new file mode 100644 index 000000000..df920d316 --- /dev/null +++ b/packages/uikit/src/components/activity/tron/TronActivityActionDetails.tsx @@ -0,0 +1,67 @@ +import { ActionStatusEnum } from '@tonkeeper/core/dist/tonApiV2'; +import React, { FC, PropsWithChildren } from 'react'; +import { useFormatFiat } from '../../../state/rates'; +import { ListBlock } from '../../List'; +import { ActivityDetailsHeader } from '../ActivityDetailsLayout'; +import { + ActionRecipientAddress, + ActionSenderAddress, + ActionTransactionDetails, + CommonActionDetailsBlock +} from '../NotificationCommon'; +import { useActiveTronWallet } from '../../../state/tron/tron'; +import { ActivityNotificationDataTron } from '../ton/ActivityNotification'; +import { TRON_USDT_ASSET } from '@tonkeeper/core/dist/entries/crypto/asset/constants'; + +const TronActionDetailsBlock: FC> = ({ + transactionHash, + children +}) => { + const url = 'https://tronscan.org/#/transaction/%s'; + return ( + + {children} + + ); +}; + +const usdtRate = { + diff7d: '', + diff24h: '', + prices: 1 +}; + +export const TronTransferActionNotification: FC = ({ + timestamp, + event +}) => { + const wallet = useActiveTronWallet()!; + const { fiatAmount } = useFormatFiat(usdtRate, event.assetAmount.relativeAmount); + + const isScam = event.isScam; + const kind = event.to === wallet.address ? 'received' : 'send'; + + return ( + + + + {kind === 'received' && } + {kind === 'send' && } + + + + ); +}; diff --git a/packages/uikit/src/components/activity/tron/TronActivityEvents.tsx b/packages/uikit/src/components/activity/tron/TronActivityEvents.tsx deleted file mode 100644 index b2db7001b..000000000 --- a/packages/uikit/src/components/activity/tron/TronActivityEvents.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import { TronEvent } from '@tonkeeper/core/dist/tronApi'; -import React, { FC } from 'react'; -import { ListItem } from '../../List'; -import { ProgressIcon } from '../ActivityLayout'; -import { TronActionData } from './ActivityNotification'; -import { TronActivityAction } from './TronActivityAction'; - -export const TronActivityEvents: FC<{ - event: TronEvent; - date: string; - timestamp: number; - setTronAction: (value: TronActionData) => void; -}> = ({ event, date, timestamp, setTronAction }) => { - return ( - <> - {event.actions.map((action, index) => ( - - setTronAction({ - action, - timestamp: timestamp, - event - }) - } - > - - {event.inProgress && } - - ))} - - ); -}; diff --git a/packages/uikit/src/components/desktop/history/DesktopHistory.tsx b/packages/uikit/src/components/desktop/history/DesktopHistory.tsx index 10a0316de..967f3cc17 100644 --- a/packages/uikit/src/components/desktop/history/DesktopHistory.tsx +++ b/packages/uikit/src/components/desktop/history/DesktopHistory.tsx @@ -7,7 +7,10 @@ import { CategorizedActivity, CategorizedActivityItemGroup } from '../../../state/activity'; -import { ActionData, ActivityNotification } from '../../activity/ton/ActivityNotification'; +import { + ActivityNotification, + ActivityNotificationData +} from '../../activity/ton/ActivityNotification'; const ContainerQuery = styled.div` container-type: inline-size; @@ -68,7 +71,7 @@ const FetchingRows = styled.div` const HistoryEvents: FC<{ className?: string; aggregatedActivity: CategorizedActivity; - setSelectedActivity: React.Dispatch>; + setSelectedActivity: React.Dispatch>; }> = ({ className, aggregatedActivity, setSelectedActivity }) => { return ( @@ -94,7 +97,9 @@ export const DesktopHistory: FC<{ isFetchingNextPage: boolean; className?: string; }> = ({ activity, isFetchingNextPage, className }) => { - const [selectedActivity, setSelectedActivity] = useState(); + const [selectedActivity, setSelectedActivity] = useState< + ActivityNotificationData | undefined + >(); const aggregatedActivity: CategorizedActivity = useMemo(() => { const double = new Set(); diff --git a/packages/uikit/src/components/desktop/history/HistoryEvent.tsx b/packages/uikit/src/components/desktop/history/HistoryEvent.tsx index f7d985180..41fd025cc 100644 --- a/packages/uikit/src/components/desktop/history/HistoryEvent.tsx +++ b/packages/uikit/src/components/desktop/history/HistoryEvent.tsx @@ -7,8 +7,9 @@ import { HistoryAction } from './ton/HistoryAction'; import { HistoryGridCell } from './ton/HistoryGrid'; import { ChevronDownIcon, SpinnerRing } from '../../Icon'; import { useTranslation } from '../../../hooks/translation'; -import { ActionData } from '../../activity/ton/ActivityNotification'; +import { ActivityNotificationData } from '../../activity/ton/ActivityNotification'; import { IconButtonTransparentBackground } from '../../fields/IconButton'; +import { TronHistoryAction } from './tron/TronHistoryAction'; const EventDivider = styled.div` background-color: ${p => p.theme.separatorCommon}; @@ -51,7 +52,7 @@ const GroupItemLeftSpacer = styled.div` export const HistoryEvent: FC<{ group: CategorizedActivityItem; - onActionClick: (actionData: ActionData) => void; + onActionClick: (actionData: ActivityNotificationData) => void; }> = ({ group, onActionClick }) => { if (group.type === 'single') { return ; @@ -62,7 +63,7 @@ export const HistoryEvent: FC<{ const HistoryEventSingle: FC<{ item: ActivityItem; - onActionClick: (actionData: ActionData) => void; + onActionClick: (actionData: ActivityNotificationData) => void; onCollapse?: () => void; onExpand?: () => void; isGroupItem?: boolean; @@ -71,7 +72,25 @@ const HistoryEventSingle: FC<{ const { t } = useTranslation(); if (item.type === 'tron') { - return + return ( + + onExpand + ? onExpand() + : onActionClick({ + type: 'tron', + timestamp: item.timestamp, + event: item.event + }) + } + > + + {formattedDate} + + + + + ); } const event = item.event; @@ -95,6 +114,7 @@ const HistoryEventSingle: FC<{ onExpand ? onExpand() : onActionClick({ + type: 'ton', timestamp: item.timestamp, action, isScam: event.isScam, @@ -155,7 +175,7 @@ const IconButtonTransparentBackgroundStyled = styled(IconButtonTransparentBackgr const HistoryEventGroup: FC<{ items: ActivityItem[]; - onActionClick: (actionData: ActionData) => void; + onActionClick: (actionData: ActivityNotificationData) => void; }> = ({ items, onActionClick }) => { const [isExpanded, setIsExpanded] = useState(false); diff --git a/packages/uikit/src/components/desktop/history/tron/TronHistoryAction.tsx b/packages/uikit/src/components/desktop/history/tron/TronHistoryAction.tsx new file mode 100644 index 000000000..384e49018 --- /dev/null +++ b/packages/uikit/src/components/desktop/history/tron/TronHistoryAction.tsx @@ -0,0 +1,29 @@ +import { FC } from 'react'; + +import { TronTransferDesktopAction } from './TronTransferDesktopAction'; + +import { Body2 } from '../../../Text'; +import { TronHistoryItem } from '@tonkeeper/core/dist/tronApi'; +import { assertUnreachableSoft } from '@tonkeeper/core/dist/utils/types'; +import { useTranslation } from '../../../../hooks/translation'; +import { HistoryGridCellFillRow } from '../ton/HistoryGrid'; + +export const TronHistoryAction: FC<{ + action: TronHistoryItem; +}> = ({ action }) => { + const { t } = useTranslation(); + switch (action.type) { + case 'asset-transfer': + return ; + default: { + assertUnreachableSoft(action.type); + return ( + <> + + {t('unknown_operation')} + + + ); + } + } +}; diff --git a/packages/uikit/src/components/desktop/history/tron/TronTransferDesktopAction.tsx b/packages/uikit/src/components/desktop/history/tron/TronTransferDesktopAction.tsx new file mode 100644 index 000000000..6fd9a440d --- /dev/null +++ b/packages/uikit/src/components/desktop/history/tron/TronTransferDesktopAction.tsx @@ -0,0 +1,74 @@ +import { FC } from 'react'; + +import { + ActionRow, + HistoryCellActionReceived, + HistoryCellActionSent, + HistoryCellAmount, + HistoryCellComment, + ErrorRow +} from '../ton/HistoryCell'; +import { TronHistoryItemTransferAsset } from '@tonkeeper/core/dist/tronApi'; +import { useActiveTronWallet } from '../../../../state/tron/tron'; +import styled from 'styled-components'; +import { HistoryGridCell } from '../ton/HistoryGrid'; +import { Body2Class } from '../../../Text'; +import { toShortValue } from '@tonkeeper/core/dist/utils/common'; + +const HistoryCellAccount = styled(HistoryGridCell)` + ${Body2Class}; + + color: ${p => p.theme.textSecondary}; + font-family: ${p => p.theme.fontMono}; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; +`; + +export const TronTransferDesktopAction: FC<{ + action: TronHistoryItemTransferAsset; +}> = ({ action }) => { + const wallet = useActiveTronWallet(); + + if (!wallet) { + return ; + } + + const isScam = action.isScam; + const isFailed = action.isFailed; + + if (wallet.address === action.to) { + return ( + <> + + {toShortValue(action.from)} + + + + + + ); + } + return ( + <> + + {toShortValue(action.to)} + + + + + + ); +}; diff --git a/packages/uikit/src/state/activity.ts b/packages/uikit/src/state/activity.ts index 5b9689d6a..d5100d061 100644 --- a/packages/uikit/src/state/activity.ts +++ b/packages/uikit/src/state/activity.ts @@ -139,7 +139,7 @@ export const groupActivityItems = (list: ActivityItem[]) => { return result; }; -export const useFetchFilteredActivity = (asset?: string) => { +export const useFetchFilteredActivity = (assetAddress?: string) => { const wallet = useActiveWallet(); const api = useActiveApi(); const { asset: selectedAsset, filterSpam, onlyInitiator } = useHistoryFilters(); @@ -152,7 +152,7 @@ export const useFetchFilteredActivity = (asset?: string) => { queryKey: [ wallet.rawAddress, QueryKey.activity, - asset, + assetAddress, selectedAsset?.id, onlyInitiator, filterSpam, @@ -164,9 +164,11 @@ export const useFetchFilteredActivity = (asset?: string) => { if (selectedAsset) { assetTonApiId = tonAssetAddressToString(selectedAsset.address); } - if (asset) { + if (assetAddress) { assetTonApiId = - asset.toLowerCase() === CryptoCurrency.TON.toLowerCase() ? 'TON' : asset; + assetAddress.toLowerCase() === CryptoCurrency.TON.toLowerCase() + ? 'TON' + : assetAddress; } const [tonActivity, tronActivity] = await Promise.all([ @@ -182,7 +184,9 @@ export const useFetchFilteredActivity = (asset?: string) => { fetchTronActivity({ tronApi, tronWalletAddress: tronWallet?.address, - pageParam: pageParam?.tronNextFrom + pageParam: pageParam?.tronNextFrom, + onlyInitiator, + filterSpam }) ]); @@ -233,11 +237,15 @@ const sortAndPackActivityItems = ( async function fetchTronActivity({ tronApi, tronWalletAddress, - pageParam + pageParam, + onlyInitiator, + filterSpam }: { tronApi: TronApi; tronWalletAddress?: string; pageParam?: number; + onlyInitiator: boolean; + filterSpam: boolean; }) { if (pageParam === 0 || !tronWalletAddress) { return { @@ -246,12 +254,17 @@ async function fetchTronActivity({ }; } + const pageLimit = 20; + const tronActivity = await tronApi.getTransfersHistory(tronWalletAddress, { - limit: 20, - maxTimestamp: pageParam + limit: pageLimit, + maxTimestamp: pageParam, + onlyInitiator, + filterSpam }); - const nextFrom = tronActivity.length < 20 ? 0 : tronActivity[tronActivity.length - 1].timestamp; + const nextFrom = + tronActivity.length < pageLimit ? 0 : tronActivity[tronActivity.length - 1].timestamp; return { nextFrom, From 6a2069d87106373ee549b8ad67ee0c24a1acd67c Mon Sep 17 00:00:00 2001 From: siandreev Date: Thu, 26 Dec 2024 17:27:55 +0100 Subject: [PATCH 11/20] fix: usdt history filters --- packages/core/src/tronApi/index.ts | 2 +- .../desktop/history/DesktopHistoryFilters.tsx | 66 ++++++++--------- .../tron/TronTransferDesktopAction.tsx | 4 +- .../desktop-pages/coin/DesktopCoinPage.tsx | 18 ++++- packages/uikit/src/state/activity.ts | 73 ++++++++++++------- packages/uikit/src/state/home.ts | 41 ++++++++++- 6 files changed, 137 insertions(+), 67 deletions(-) diff --git a/packages/core/src/tronApi/index.ts b/packages/core/src/tronApi/index.ts index abc455b4c..d6ba94a59 100644 --- a/packages/core/src/tronApi/index.ts +++ b/packages/core/src/tronApi/index.ts @@ -232,7 +232,7 @@ export class TronApi { url.searchParams.set('max_timestamp', options.maxTimestamp.toString()); } - if (options?.onlyInitiator !== undefined) { + if (options?.onlyInitiator === true) { url.searchParams.set('only_from', 'true'); } diff --git a/packages/uikit/src/components/desktop/history/DesktopHistoryFilters.tsx b/packages/uikit/src/components/desktop/history/DesktopHistoryFilters.tsx index cfba2df50..ee32bf541 100644 --- a/packages/uikit/src/components/desktop/history/DesktopHistoryFilters.tsx +++ b/packages/uikit/src/components/desktop/history/DesktopHistoryFilters.tsx @@ -3,18 +3,18 @@ import { SelectDropDown } from '../../fields/Select'; import React, { FC } from 'react'; import { Body2, Body3, Label2 } from '../../Text'; import styled, { css } from 'styled-components'; -import { useAssets } from '../../../state/home'; +import { useAllChainsAssets } from '../../../state/home'; import { ChevronDownIcon, CoinsHorizontalIcon, SlidersIcon } from '../../Icon'; import { Checkbox } from '../../fields/Checkbox'; import { isInitiatorFiltrationForAssetAvailable, useHistoryFilters } from '../../../state/activity'; -import { TON_ASSET } from '@tonkeeper/core/dist/entries/crypto/asset/constants'; -import { jettonToTonAsset } from '@tonkeeper/core/dist/entries/crypto/asset/ton-asset'; import { useTranslation } from '../../../hooks/translation'; +import { TRON_USDT_ASSET } from '@tonkeeper/core/dist/entries/crypto/asset/constants'; +import { Badge } from '../../shared'; -const AssetIcon = styled.img` +const AssetIcon = styled.img<{ $noBorders?: boolean }>` width: 24px; height: 24px; - border-radius: ${props => props.theme.cornerFull}; + border-radius: ${props => !props.$noBorders && props.theme.cornerFull}; margin-right: 12px; pointer-events: none; @@ -60,9 +60,13 @@ const DropDownOtherFiltersButton = styled(DropDownButton)<{ $badge: boolean }>` } `; +const TRCBadge = styled(Badge)` + margin-left: 8px; +`; + export const AssetHistoryFilter = () => { const { t } = useTranslation(); - const [assets] = useAssets(); + const assets = useAllChainsAssets(); const { asset: selectedAsset, setAsset } = useHistoryFilters(); if (!assets) { return null; @@ -85,35 +89,27 @@ export const AssetHistoryFilter = () => { {t('history_filters_all_assets')} - - { - setAsset(TON_ASSET); - onClose(); - }} - isSelected={selectedAsset?.id === TON_ASSET.id} - > - - TON - - {assets.ton.jettons.balances.map(jetton => { - const asset = jettonToTonAsset(jetton.jetton.address, assets.ton.jettons); - return ( - <> - - { - setAsset(asset); - onClose(); - }} - isSelected={selectedAsset?.id === asset.id} - > - - {jetton.jetton.symbol} - - - ); - })} + {assets.map(assetAmount => ( + <> + + { + setAsset(assetAmount.asset); + onClose(); + }} + isSelected={selectedAsset?.id === assetAmount.asset.id} + > + + {assetAmount.asset.symbol} + {assetAmount.asset.id === TRON_USDT_ASSET.id && ( + TRC20 + )} + + + ))} )} > diff --git a/packages/uikit/src/components/desktop/history/tron/TronTransferDesktopAction.tsx b/packages/uikit/src/components/desktop/history/tron/TronTransferDesktopAction.tsx index 6fd9a440d..3aecc397a 100644 --- a/packages/uikit/src/components/desktop/history/tron/TronTransferDesktopAction.tsx +++ b/packages/uikit/src/components/desktop/history/tron/TronTransferDesktopAction.tsx @@ -46,7 +46,7 @@ export const TronTransferDesktopAction: FC<{ { return balances.trx; }, [balances]); + const ref = useRef(null); + const { + fetchNextPage, + hasNextPage, + isFetchingNextPage, + data: activity, + refetch + } = useFetchFilteredActivity(TRON_USDT_ASSET.address); + + useScrollMonitor(ref, 5000, refetch); + + useFetchNext(hasNextPage, isFetchingNextPage, fetchNextPage, true, ref); + return ( - + {asset.symbol} @@ -414,6 +427,9 @@ export const TronUSDTPage = () => { )} {t('page_header_history')} + + + ); }; diff --git a/packages/uikit/src/state/activity.ts b/packages/uikit/src/state/activity.ts index d5100d061..23ee17019 100644 --- a/packages/uikit/src/state/activity.ts +++ b/packages/uikit/src/state/activity.ts @@ -2,7 +2,6 @@ import { useInfiniteQuery } from '@tanstack/react-query'; import { CryptoCurrency } from '@tonkeeper/core/dist/entries/crypto'; import { isTon, - TonAsset, tonAssetAddressToString } from '@tonkeeper/core/dist/entries/crypto/asset/ton-asset'; import { intlLocale } from '@tonkeeper/core/dist/entries/language'; @@ -19,6 +18,8 @@ import { useActiveTronWallet, useTronApi } from './tron/tron'; import { APIConfig } from '@tonkeeper/core/dist/entries/apis'; import { TonContract } from '@tonkeeper/core/dist/entries/wallet'; import { TronApi, TronHistoryItem } from '@tonkeeper/core/dist/tronApi'; +import { TRON_USDT_ASSET } from '@tonkeeper/core/dist/entries/crypto/asset/constants'; +import { Asset, isTonAsset } from '@tonkeeper/core/dist/entries/crypto/asset/asset'; export const formatActivityDate = (language: string, key: string, timestamp: number): string => { const date = new Date(timestamp); @@ -161,33 +162,51 @@ export const useFetchFilteredActivity = (assetAddress?: string) => { ], queryFn: async ({ pageParam = undefined }) => { let assetTonApiId: string | undefined; + let isTronUSDTAsset = false; if (selectedAsset) { - assetTonApiId = tonAssetAddressToString(selectedAsset.address); + if (selectedAsset.id === TRON_USDT_ASSET.id) { + isTronUSDTAsset = true; + } else if (isTonAsset(selectedAsset)) { + assetTonApiId = tonAssetAddressToString(selectedAsset.address); + } } if (assetAddress) { - assetTonApiId = - assetAddress.toLowerCase() === CryptoCurrency.TON.toLowerCase() - ? 'TON' - : assetAddress; + if (assetAddress === TRON_USDT_ASSET.address) { + isTronUSDTAsset = true; + } else { + assetTonApiId = + assetAddress.toLowerCase() === CryptoCurrency.TON.toLowerCase() + ? 'TON' + : assetAddress; + } } + const emptyResult = Promise.resolve({ + nextFrom: 0, + events: [] + }); + const [tonActivity, tronActivity] = await Promise.all([ - fetchTonActivity({ - pageParam: pageParam?.tonNextFrom, - assetTonApiId, - api, - wallet, - onlyInitiator, - filterSpam, - twoFaPluginAddress: twoFaPlugin - }), - fetchTronActivity({ - tronApi, - tronWalletAddress: tronWallet?.address, - pageParam: pageParam?.tronNextFrom, - onlyInitiator, - filterSpam - }) + isTronUSDTAsset + ? emptyResult + : fetchTonActivity({ + pageParam: pageParam?.tonNextFrom, + assetTonApiId, + api, + wallet, + onlyInitiator, + filterSpam, + twoFaPluginAddress: twoFaPlugin + }), + assetTonApiId + ? emptyResult + : fetchTronActivity({ + tronApi, + tronWalletAddress: tronWallet?.address, + pageParam: pageParam?.tronNextFrom, + onlyInitiator, + filterSpam + }) ]); return { @@ -258,7 +277,7 @@ async function fetchTronActivity({ const tronActivity = await tronApi.getTransfersHistory(tronWalletAddress, { limit: pageLimit, - maxTimestamp: pageParam, + maxTimestamp: pageParam ? pageParam - 1 : undefined, onlyInitiator, filterSpam }); @@ -372,7 +391,7 @@ export const defaultHistoryFilters = { }; const historyFilters$ = atom<{ - asset: TonAsset | undefined; + asset: Asset | undefined; onlyInitiator: boolean; filterSpam: boolean; }>(defaultHistoryFilters); @@ -402,7 +421,7 @@ export const useHistoryFilters = () => { }, [setFilters, mutate, filters.filterSpam]); const setAsset = useCallback( - (asset: TonAsset | undefined) => { + (asset: Asset | undefined) => { setFilters(f => ({ ...f, asset })); }, [setFilters] @@ -416,11 +435,11 @@ export const useHistoryFilters = () => { }; }; -export const isInitiatorFiltrationForAssetAvailable = (asset: TonAsset | undefined): boolean => { +export const isInitiatorFiltrationForAssetAvailable = (asset: Asset | undefined): boolean => { if (!asset) { return true; } - return isTon(asset.address); + return !isTonAsset(asset) || isTon(asset.address); }; export const useScrollMonitor = ( diff --git a/packages/uikit/src/state/home.ts b/packages/uikit/src/state/home.ts index 14248381e..befba573e 100644 --- a/packages/uikit/src/state/home.ts +++ b/packages/uikit/src/state/home.ts @@ -4,7 +4,7 @@ import BigNumber from 'bignumber.js'; import { useMemo } from 'react'; import { AssetData } from '../components/home/Jettons'; import { useJettonList } from './jetton'; -import { useActiveWallet, useWalletAccountInfo } from './wallet'; +import { useActiveTonWalletConfig, useActiveWallet, useWalletAccountInfo } from './wallet'; import { AssetAmount } from '@tonkeeper/core/dist/entries/crypto/asset/asset-amount'; import { TON_ASSET, @@ -34,6 +34,45 @@ export const useAssets = () => { return [assets, error, isJettonLoading || isAccountLoading, jettonError] as const; }; +export const useAllChainsAssets = () => { + const [assets] = useAssets(); + const { data: config } = useActiveTonWalletConfig(); + + return useMemo(() => { + if (!assets || !config) return undefined; + + const result: AssetAmount[] = [ + new AssetAmount({ asset: TON_ASSET, weiAmount: assets.ton.info.balance }) + ]; + config.pinnedTokens.forEach(p => { + if (p === TRON_USDT_ASSET.address && assets.tron) { + result.push(assets.tron.usdt); + } else { + const jetton = assets.ton.jettons.balances.find(i => i.jetton.address === p); + if (jetton) { + result.push(jettonToTonAssetAmount(jetton)); + } + } + }); + + if ( + assets.tron && + !result.some(i => i.asset.id === TRON_USDT_ASSET.id) && + !config.hiddenTokens.includes(TRON_USDT_ASSET.address) + ) { + result.push(assets.tron.usdt); + } + + assets.ton.jettons.balances + .filter(b => !config.pinnedTokens.includes(b.jetton.address)) + .forEach(b => { + result.push(jettonToTonAssetAmount(b)); + }); + + return result; + }, [assets, config]); +}; + export const useAssetWeiBalance = (asset: AssetIdentification) => { const [assets] = useAssets(); From 3f5a87489b005e6e8fc3d870997c65af7000f093 Mon Sep 17 00:00:00 2001 From: siandreev Date: Thu, 26 Dec 2024 17:28:11 +0100 Subject: [PATCH 12/20] fix: usdt dev settings --- packages/uikit/src/pages/settings/Dev.tsx | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/uikit/src/pages/settings/Dev.tsx b/packages/uikit/src/pages/settings/Dev.tsx index 1a5af34e1..368c11d29 100644 --- a/packages/uikit/src/pages/settings/Dev.tsx +++ b/packages/uikit/src/pages/settings/Dev.tsx @@ -88,10 +88,9 @@ const EnableTronSettings = () => { - Enable 2FA - Experimental + Enable TRON USDT + Experimental - Available only for W5 wallets { - {/* TODO: ENABLE TRON */} - {/* */}
); From 0ab64aa6864f020439bcca46b9b5e297fbe8ad9d Mon Sep 17 00:00:00 2001 From: siandreev Date: Fri, 27 Dec 2024 14:54:42 +0100 Subject: [PATCH 13/20] feat: usdt trc20 for mobile layout --- packages/core/src/tonkeeperApi/tonendpoint.ts | 12 +- .../src/components/activity/ActivityGroup.tsx | 82 ----------- .../activity/MobileActivityList.tsx | 73 ++++++++++ .../activity/tron/TronActivityEvents.tsx | 55 ++++++++ .../desktop/history/DesktopHistoryFilters.tsx | 2 +- .../uikit/src/components/home/Balance.tsx | 4 +- .../uikit/src/components/home/CompactView.tsx | 5 +- .../uikit/src/components/home/Jettons.tsx | 125 ++++++++++++----- .../uikit/src/components/home/TabsView.tsx | 3 +- .../uikit/src/components/home/TronAssets.tsx | 39 ++++-- .../uikit/src/components/jettons/Info.tsx | 13 +- .../modals/useSendTransferNotification.ts | 4 +- .../desktop-pages/tokens/DesktopTokens.tsx | 119 ++++++++-------- .../uikit/src/pages/activity/Activity.tsx | 75 ++-------- packages/uikit/src/pages/coin/Coin.tsx | 7 +- packages/uikit/src/pages/coin/Jetton.tsx | 82 ++++++----- packages/uikit/src/pages/coin/Ton.tsx | 39 +----- packages/uikit/src/pages/coin/Tron.tsx | 130 ------------------ packages/uikit/src/pages/coin/TronUsdt.tsx | 78 +++++++++++ packages/uikit/src/pages/home/Home.tsx | 20 ++- packages/uikit/src/pages/home/MainColumn.tsx | 126 ----------------- packages/uikit/src/state/home.ts | 10 +- 22 files changed, 488 insertions(+), 615 deletions(-) delete mode 100644 packages/uikit/src/components/activity/ActivityGroup.tsx create mode 100644 packages/uikit/src/components/activity/MobileActivityList.tsx create mode 100644 packages/uikit/src/components/activity/tron/TronActivityEvents.tsx delete mode 100644 packages/uikit/src/pages/coin/Tron.tsx create mode 100644 packages/uikit/src/pages/coin/TronUsdt.tsx delete mode 100644 packages/uikit/src/pages/home/MainColumn.tsx diff --git a/packages/core/src/tonkeeperApi/tonendpoint.ts b/packages/core/src/tonkeeperApi/tonendpoint.ts index d20772bce..fbc9585c3 100644 --- a/packages/core/src/tonkeeperApi/tonendpoint.ts +++ b/packages/core/src/tonkeeperApi/tonendpoint.ts @@ -86,13 +86,13 @@ export interface TonendpointConfig { */ tablet_enable_additional_security?: boolean; - '2fa_public_key'?: string; - '2fa_api_url'?: string; - '2fa_tg_confirm_send_message_ttl_seconds'?: number; - '2fa_tg_linked_ttl_seconds'?: number; - '2fa_bot_url'?: string; + '2fa_public_key'?: string; + '2fa_api_url'?: string; + '2fa_tg_confirm_send_message_ttl_seconds'?: number; + '2fa_tg_linked_ttl_seconds'?: number; + '2fa_bot_url'?: string; - tron_api_url?: string; + tron_api_url?: string; } const defaultTonendpoint = 'https://api.tonkeeper.com'; // 'http://localhost:1339'; diff --git a/packages/uikit/src/components/activity/ActivityGroup.tsx b/packages/uikit/src/components/activity/ActivityGroup.tsx deleted file mode 100644 index db3284689..000000000 --- a/packages/uikit/src/components/activity/ActivityGroup.tsx +++ /dev/null @@ -1,82 +0,0 @@ -import { InfiniteData } from '@tanstack/react-query'; -import { AccountEvents } from '@tonkeeper/core/dist/tonApiV2'; -import React, { FC, useMemo, useState } from 'react'; -import { formatActivityDate, ActivityItemsDatedGroup, getActivityTitle } from '../../state/activity'; -import { MixedActivity, getMixedActivityGroups } from '../../state/mixedActivity'; -import { CoinHistorySkeleton, HistoryBlock, SkeletonListWithImages } from '../Skeleton'; -import { Group, List, Title } from './ActivityLayout'; -import { ActionData, ActivityNotification } from './ton/ActivityNotification'; -import { TonActivityEvents } from './ton/TonActivityEvents'; -import { TronActionData, TronActivityNotification } from './tron/ActivityNotification'; -import { TronActivityEvents } from './tron/TronActivityEvents'; -import { useTranslation } from '../../hooks/translation'; - -export const ActivityList: FC<{ - isFetched: boolean; - isFetchingNextPage: boolean; - tonEvents?: InfiniteData; - tronEvents?: InfiniteData; -}> = ({ isFetched, isFetchingNextPage, tonEvents, tronEvents }) => { - const activity = useMemo[]>(() => { - return getMixedActivityGroups(tonEvents, tronEvents); - }, [tonEvents, tronEvents]); - - if (!isFetched) { - return ; - } - return ( - - - {isFetchingNextPage && } - - ); -}; - -export const MixedActivityGroup: FC<{ - items: ActivityItemsDatedGroup[]; -}> = ({ items }) => { - const [tonAction, seTonAction] = useState(undefined); - const [tronAction, setTronAction] = useState(undefined); - const { i18n } = useTranslation(); - - return ( - <> - {items.map(([eventKey, events]) => { - return ( - - - {getActivityTitle(i18n.language, eventKey, events[0].timestamp)} - - {events.map(({ timestamp, event, key }) => { - const date = formatActivityDate(i18n.language, eventKey, timestamp); - return ( - - {event.kind === 'tron' ? ( - - ) : event.kind === 'ton' ? ( - - ) : null} - - ); - })} - - ); - })} - seTonAction(undefined)} /> - setTronAction(undefined)} - /> - - ); -}; diff --git a/packages/uikit/src/components/activity/MobileActivityList.tsx b/packages/uikit/src/components/activity/MobileActivityList.tsx new file mode 100644 index 000000000..170e5a54d --- /dev/null +++ b/packages/uikit/src/components/activity/MobileActivityList.tsx @@ -0,0 +1,73 @@ +import React, { FC, useMemo, useState } from 'react'; +import { + formatActivityDate, + getActivityTitle, + ActivityItem, + groupActivityItems +} from '../../state/activity'; +import { Group, List, Title } from './ActivityLayout'; +import { ActivityNotification, ActivityNotificationData } from './ton/ActivityNotification'; +import { TonActivityEvents } from './ton/TonActivityEvents'; +import { useTranslation } from '../../hooks/translation'; +import { TronActivityEvents } from './tron/TronActivityEvents'; + +export const MobileActivityList: FC<{ + items: ActivityItem[]; +}> = ({ items }) => { + const [activityNotificationData, setActivityNotificationData] = useState< + ActivityNotificationData | undefined + >(undefined); + const { i18n } = useTranslation(); + + const groups = useMemo(() => groupActivityItems(items), [items]); + + return ( + <> + {groups.map(([groupKey, groupEvents]) => { + return ( + + + {getActivityTitle(i18n.language, groupKey, groupEvents[0].timestamp)} + + {groupEvents.map(({ timestamp, event, type, key }) => { + const date = formatActivityDate(i18n.language, groupKey, timestamp); + return ( + + {type === 'tron' ? ( + + setActivityNotificationData({ + type: 'tron', + event, + timestamp: event.timestamp + }) + } + /> + ) : type === 'ton' ? ( + + setActivityNotificationData({ + type: 'ton', + ...ad + }) + } + /> + ) : null} + + ); + })} + + ); + })} + setActivityNotificationData(undefined)} + /> + + ); +}; diff --git a/packages/uikit/src/components/activity/tron/TronActivityEvents.tsx b/packages/uikit/src/components/activity/tron/TronActivityEvents.tsx new file mode 100644 index 000000000..1bcbf63c2 --- /dev/null +++ b/packages/uikit/src/components/activity/tron/TronActivityEvents.tsx @@ -0,0 +1,55 @@ +import { FC } from 'react'; +import { ListItem } from '../../List'; +import { TronHistoryItem, TronHistoryItemTransferAsset } from '@tonkeeper/core/dist/tronApi'; +import { ReceiveActivityAction, SendActivityAction } from '../ActivityActionLayout'; +import { useActiveTronWallet } from '../../../state/tron/tron'; +import { ErrorAction } from '../CommonAction'; +import { ActionStatusEnum } from '@tonkeeper/core/dist/tonApiV2'; + +export const TronActivityEvents: FC<{ + event: TronHistoryItem; + formattedDate: string; + onClick: () => void; +}> = ({ event, onClick, formattedDate }) => { + return ( + <> + + + + + ); +}; + +const TronTransferAction: FC<{ + event: TronHistoryItemTransferAsset; + formattedDate: string; +}> = ({ event, formattedDate }) => { + const wallet = useActiveTronWallet(); + + if (!wallet) { + return ; + } + + if (event.to === wallet?.address) { + return ( + + ); + } + return ( + + ); +}; diff --git a/packages/uikit/src/components/desktop/history/DesktopHistoryFilters.tsx b/packages/uikit/src/components/desktop/history/DesktopHistoryFilters.tsx index ee32bf541..730c9dd9f 100644 --- a/packages/uikit/src/components/desktop/history/DesktopHistoryFilters.tsx +++ b/packages/uikit/src/components/desktop/history/DesktopHistoryFilters.tsx @@ -66,7 +66,7 @@ const TRCBadge = styled(Badge)` export const AssetHistoryFilter = () => { const { t } = useTranslation(); - const assets = useAllChainsAssets(); + const { assets } = useAllChainsAssets(); const { asset: selectedAsset, setAsset } = useHistoryFilters(); if (!assets) { return null; diff --git a/packages/uikit/src/components/home/Balance.tsx b/packages/uikit/src/components/home/Balance.tsx index 1b7ac050e..3502edfce 100644 --- a/packages/uikit/src/components/home/Balance.tsx +++ b/packages/uikit/src/components/home/Balance.tsx @@ -6,10 +6,9 @@ import { useAppContext } from '../../hooks/appContext'; import { useAppSdk } from '../../hooks/appSdk'; import { formatFiatCurrency } from '../../hooks/balance'; import { QueryKey } from '../../libs/queryKey'; -import { useActiveAccount, useActiveTonNetwork, useActiveWallet } from '../../state/wallet'; +import { useActiveAccount, useActiveWallet } from '../../state/wallet'; import { Body3, Label2, Num2 } from '../Text'; import { SkeletonText } from '../shared/Skeleton'; -import { AssetData } from './Jettons'; import { useWalletTotalBalance } from '../../state/asset'; import { useTranslation } from '../../hooks/translation'; import { AccountAndWalletBadgesGroup, NetworkBadge } from '../account/AccountBadge'; @@ -88,7 +87,6 @@ export const BalanceSkeleton = () => { export const Balance: FC<{ error?: Error | null; isFetching: boolean; - assets: AssetData; }> = ({ error, isFetching }) => { const { t } = useTranslation(); const account = useActiveAccount(); diff --git a/packages/uikit/src/components/home/CompactView.tsx b/packages/uikit/src/components/home/CompactView.tsx index 5893b7a75..f8d1d1526 100644 --- a/packages/uikit/src/components/home/CompactView.tsx +++ b/packages/uikit/src/components/home/CompactView.tsx @@ -1,10 +1,11 @@ import { NFT } from '@tonkeeper/core/dist/entries/nft'; import React, { FC } from 'react'; import { NftsList } from '../nft/Nfts'; -import { AssetData, JettonList } from './Jettons'; +import { JettonList } from './Jettons'; +import { AssetAmount } from '@tonkeeper/core/dist/entries/crypto/asset/asset-amount'; export const CompactView: FC<{ - assets: AssetData; + assets: AssetAmount[]; nfts: NFT[]; }> = ({ assets, nfts }) => { return ( diff --git a/packages/uikit/src/components/home/Jettons.tsx b/packages/uikit/src/components/home/Jettons.tsx index e7f5c08d1..13964aa6e 100644 --- a/packages/uikit/src/components/home/Jettons.tsx +++ b/packages/uikit/src/components/home/Jettons.tsx @@ -1,16 +1,29 @@ import { CryptoCurrency } from '@tonkeeper/core/dist/entries/crypto'; -import { Account, JettonBalance, JettonsBalances } from '@tonkeeper/core/dist/tonApiV2'; -import { formatDecimals } from '@tonkeeper/core/dist/utils/balance'; -import { FC, forwardRef, useMemo } from 'react'; +import { Account, JettonsBalances } from '@tonkeeper/core/dist/tonApiV2'; +import React, { FC, forwardRef, useMemo } from 'react'; import { useNavigate } from 'react-router-dom'; import { useAppContext } from '../../hooks/appContext'; -import { useFormatBalance } from '../../hooks/balance'; import { useTranslation } from '../../hooks/translation'; import { AppRoute } from '../../libs/routes'; import { toTokenRate, useFormatFiat, useRate } from '../../state/rates'; import { ListBlock, ListItem } from '../List'; import { ListItemPayload, TokenLayout, TokenLogo } from './TokenLayout'; -import { TronBalances } from '../../state/tron/tron'; +import { + TronBalances, + useActiveTronWallet, + useCanUseTronForActiveWallet +} from '../../state/tron/tron'; +import { TronAssetComponent, TronAssets } from './TronAssets'; +import { AssetAmount } from '@tonkeeper/core/dist/entries/crypto/asset/asset-amount'; +import { TronAsset } from '@tonkeeper/core/dist/entries/crypto/asset/tron-asset'; +import { isTronAsset } from '@tonkeeper/core/dist/entries/crypto/asset/asset'; +import { + tonAssetAddressToString, + TonAsset as TonAssetType +} from '@tonkeeper/core/dist/entries/crypto/asset/ton-asset'; +import { useJettonList } from '../../state/jetton'; +import { eqAddresses } from '@tonkeeper/core/dist/utils/address'; +import { TON_ASSET } from '@tonkeeper/core/dist/entries/crypto/asset/constants'; export interface TonAssetData { info: Account; @@ -29,18 +42,15 @@ export interface AssetProps { export const TonAsset = forwardRef< HTMLDivElement, { - info: Account; + balance: AssetAmount; className?: string; } ->(({ info, className }, ref) => { +>(({ balance, className }, ref) => { const { t } = useTranslation(); const navigate = useNavigate(); - const amount = useMemo(() => formatDecimals(info.balance), [info.balance]); - const balance = useFormatBalance(amount); - const { data } = useRate(CryptoCurrency.TON); - const { fiatPrice, fiatAmount } = useFormatFiat(data, amount); + const { fiatPrice, fiatAmount } = useFormatFiat(data, balance.relativeAmount); return ( navigate(AppRoute.coins + '/ton')} className={className} ref={ref}> @@ -48,8 +58,8 @@ export const TonAsset = forwardRef< (({ balance, className }, ref) => { + if (isTronAsset(balance.asset)) { + return ( + } + className={className} + /> + ); + } else { + return ; + } +}); + export const JettonAsset = forwardRef< HTMLDivElement, { - jetton: JettonBalance; + balance: AssetAmount; className?: string; } ->(({ jetton, className }, ref) => { +>(({ balance, className }, ref) => { const { t } = useTranslation(); const navigate = useNavigate(); const { fiat } = useAppContext(); - const amount = useMemo(() => formatDecimals(jetton.balance, jetton.jetton.decimals), [jetton]); - const balance = useFormatBalance(amount, jetton.jetton.decimals); - const data = useMemo( - () => (jetton.price ? toTokenRate(jetton.price, fiat) : undefined), - [jetton.price, fiat] + const { data: jettonBalances } = useJettonList(); + + const rate = useMemo(() => { + const jetton = jettonBalances?.balances.find(j => + eqAddresses(j.jetton.address, balance.asset.address) + ); + + return jetton?.price ? toTokenRate(jetton.price, fiat) : undefined; + }, [jettonBalances, fiat]); + const { fiatPrice, fiatAmount } = useFormatFiat(rate, balance.relativeAmount); + + const verification = useMemo( + () => + jettonBalances?.balances.find(j => eqAddresses(j.jetton.address, balance.asset.address)) + ?.jetton.verification, + [jettonBalances, fiat] ); - const { fiatPrice, fiatAmount } = useFormatFiat(data, amount); return ( - navigate(AppRoute.coins + `/${encodeURIComponent(jetton.jetton.address)}`) + navigate( + AppRoute.coins + + `/${encodeURIComponent( + tonAssetAddressToString((balance.asset as TonAssetType).address) + )}` + ) } className={className} ref={ref} > - + ); }); -export const JettonList: FC = ({ - assets: { - ton: { info, jettons } - } -}) => { +export const JettonList: FC<{ assets: AssetAmount[] }> = ({ assets }) => { + const [tonAssetAmount, restAssets] = useMemo(() => { + return [ + assets.find(item => item.asset.id === TON_ASSET.id)!, + assets.filter(item => item.asset.id !== TON_ASSET.id) + ]; + }, [assets]); + const canUseTron = useCanUseTronForActiveWallet(); + const tronWallet = useActiveTronWallet(); + return ( <> - - {/* TODO: ENABLE TRON */} - {/* */} + + {!tronWallet && canUseTron && } - {jettons.balances.map(jetton => ( - + {restAssets.map(item => ( + ))} diff --git a/packages/uikit/src/components/home/TabsView.tsx b/packages/uikit/src/components/home/TabsView.tsx index 250991029..9d2ee9cf4 100644 --- a/packages/uikit/src/components/home/TabsView.tsx +++ b/packages/uikit/src/components/home/TabsView.tsx @@ -6,6 +6,7 @@ import { useTranslation } from '../../hooks/translation'; import { Label1 } from '../Text'; import { NftsList } from '../nft/Nfts'; import { AssetData, JettonList } from './Jettons'; +import { AssetAmount } from "@tonkeeper/core/dist/entries/crypto/asset/asset-amount"; const TabsBlock = styled.div` display: flex; @@ -91,7 +92,7 @@ const Tabs: FC<{ tab: HomeTabs; onTab: (value: HomeTabs) => void }> = ({ tab, on const collectibles = 'collectibles'; export const TabsView: FC<{ - assets: AssetData; + assets: AssetAmount[]; nfts: NFT[]; }> = ({ assets, nfts }) => { const location = useLocation(); diff --git a/packages/uikit/src/components/home/TronAssets.tsx b/packages/uikit/src/components/home/TronAssets.tsx index e90527646..e84675cec 100644 --- a/packages/uikit/src/components/home/TronAssets.tsx +++ b/packages/uikit/src/components/home/TronAssets.tsx @@ -1,4 +1,4 @@ -import React, { FC, useMemo } from 'react'; +import React, { FC, forwardRef, useMemo } from 'react'; import { useNavigate } from 'react-router-dom'; import { AppRoute } from '../../libs/routes'; import { useFormatFiat } from '../../state/rates'; @@ -7,13 +7,13 @@ import { ListItemPayload, TokenLayout, TokenLogo } from './TokenLayout'; import { AssetAmount } from '@tonkeeper/core/dist/entries/crypto/asset/asset-amount'; import { TronAsset } from '@tonkeeper/core/dist/entries/crypto/asset/tron-asset'; import { TRON_USDT_ASSET } from '@tonkeeper/core/dist/entries/crypto/asset/constants'; -import { TronBalances } from '../../state/tron/tron'; import { Button } from '../fields/Button'; import { formatFiatCurrency } from '../../hooks/balance'; import { useAppContext } from '../../hooks/appContext'; import { useTranslation } from '../../hooks/translation'; import { useAddTronToAccount } from '../../state/wallet'; import styled from 'styled-components'; +import { useIsFullWidthMode } from '../../hooks/useIsFullWidthMode'; const usdtRate = { diff7d: '', @@ -33,10 +33,13 @@ const tronAssetRate = (asset: TronAsset) => { return undefined; }; -const TronAssetComponent: FC<{ assetAmount: AssetAmount; className?: string }> = ({ - assetAmount, - className -}) => { +export const TronAssetComponent = forwardRef< + HTMLDivElement, + { + assetAmount: AssetAmount; + className?: string; + } +>(({ assetAmount, className }, ref) => { const navigate = useNavigate(); const rate = useMemo(() => tronAssetRate(assetAmount.asset), [assetAmount.asset.id]); @@ -47,6 +50,7 @@ const TronAssetComponent: FC<{ assetAmount: AssetAmount; className?: navigate(AppRoute.coins + '/' + assetAmount.asset.id)} className={className} + ref={ref} > {assetAmount.asset.id === TRON_USDT_ASSET.id ? ( @@ -66,13 +70,14 @@ const TronAssetComponent: FC<{ assetAmount: AssetAmount; className?: ); -}; +}); const InactiveUSDA: FC<{ className?: string }> = ({ className }) => { const { t } = useTranslation(); const { fiat } = useAppContext(); const { mutate, isLoading } = useAddTronToAccount(); + const isFullWidth = useIsFullWidthMode(); return ( @@ -87,7 +92,12 @@ const InactiveUSDA: FC<{ className?: string }> = ({ className }) => { label="TRC20" rate={usdtRate} /> - @@ -95,11 +105,16 @@ const InactiveUSDA: FC<{ className?: string }> = ({ className }) => { ); }; -export const TronAssets: FC<{ balances: TronBalances; className?: string }> = React.memo( - ({ balances, className }) => { - if (!balances) { +export const TronAssets: FC<{ usdt: AssetAmount | null; className?: string }> = React.memo( + ({ usdt, className }) => { + if (!usdt) { return ; } - return ; + return ( + } + className={className} + /> + ); } ); diff --git a/packages/uikit/src/components/jettons/Info.tsx b/packages/uikit/src/components/jettons/Info.tsx index e3d313b3d..d33e041e8 100644 --- a/packages/uikit/src/components/jettons/Info.tsx +++ b/packages/uikit/src/components/jettons/Info.tsx @@ -17,11 +17,11 @@ const Text = styled.div` flex-grow: 1; `; -const Image = styled.img` +const Image = styled.img<{ $noCorners?: boolean }>` width: 64px; height: 64px; flex-shrink: 0; - border-radius: 100%; + border-radius: ${p => (p.$noCorners ? '0' : '100%')}; `; interface CoinProps { @@ -30,6 +30,7 @@ interface CoinProps { price?: string; image?: string; description?: string; + imageNoCorners?: boolean; } export const CoinInfoSkeleton = () => { @@ -52,7 +53,7 @@ const Title = styled(H2)` margin-bottom: 2px; `; -export const CoinInfo: FC = ({ amount, symbol, price, image }) => { +export const CoinInfo: FC = ({ amount, symbol, price, image, imageNoCorners }) => { return ( @@ -61,7 +62,11 @@ export const CoinInfo: FC = ({ amount, symbol, price, image }) => { {price && {price}} - {image ? : } + {image ? ( + + ) : ( + + )} ); }; diff --git a/packages/uikit/src/components/modals/useSendTransferNotification.ts b/packages/uikit/src/components/modals/useSendTransferNotification.ts index dac65b50b..231ff2fe7 100644 --- a/packages/uikit/src/components/modals/useSendTransferNotification.ts +++ b/packages/uikit/src/components/modals/useSendTransferNotification.ts @@ -12,8 +12,8 @@ export const useSendTransferNotification = () => { id: Date.now(), params: { ...params, - from: 'wallet' - } + from: 'wallet' as const + } as TransferInitParams }); }, [sdk] diff --git a/packages/uikit/src/desktop-pages/tokens/DesktopTokens.tsx b/packages/uikit/src/desktop-pages/tokens/DesktopTokens.tsx index 2de9f23b2..f1b0e954c 100644 --- a/packages/uikit/src/desktop-pages/tokens/DesktopTokens.tsx +++ b/packages/uikit/src/desktop-pages/tokens/DesktopTokens.tsx @@ -10,14 +10,15 @@ import { DesktopViewPageLayout } from '../../components/desktop/DesktopViewLayout'; import { TokensPieChart } from '../../components/desktop/tokens/TokensPieChart'; -import { JettonAsset, TonAsset } from '../../components/home/Jettons'; +import { AnyChainAsset, TonAsset } from '../../components/home/Jettons'; import { useTranslation } from '../../hooks/translation'; -import { useAssets } from '../../state/home'; +import { useAllChainsAssets } from '../../state/home'; import { useMutateUserUIPreferences, useUserUIPreferences } from '../../state/theme'; import { useAssetsDistribution } from '../../state/asset'; import { TronAssets } from '../../components/home/TronAssets'; -import { useCanUseTronForActiveWallet, useTronBalances } from '../../state/tron/tron'; +import { useActiveTronWallet, useCanUseTronForActiveWallet } from '../../state/tron/tron'; +import { TON_ASSET } from '@tonkeeper/core/dist/entries/crypto/asset/constants'; const DesktopAssetStylesOverride = css` background-color: transparent; @@ -41,7 +42,7 @@ const TronAssetsStyled = styled(TronAssets)` ${DesktopAssetStylesOverride} `; -const JettonAssetStyled = styled(JettonAsset)` +const AnyChainAssetStyled = styled(AnyChainAsset)` ${DesktopAssetStylesOverride} `; @@ -81,7 +82,13 @@ const Divider = styled.div` const itemSize = 77; const DesktopTokensPayload = () => { - const [assets] = useAssets(); + const { assets: allAssets } = useAllChainsAssets() ?? []; + const [tonAssetAmount, assets] = useMemo(() => { + return [ + allAssets?.find(item => item.asset.id === TON_ASSET.id), + allAssets?.filter(item => item.asset.id !== TON_ASSET.id) + ]; + }, [allAssets]); const { t } = useTranslation(); const { data: distribution } = useAssetsDistribution(); const { data: uiPreferences } = useUserUIPreferences(); @@ -90,7 +97,7 @@ const DesktopTokensPayload = () => { const tonRef = useRef(null); const containerRef = useRef(null); - const { data: tronBalances } = useTronBalances(); + const tronWallet = useActiveTronWallet(); const canUseTron = useCanUseTronForActiveWallet(); useLayoutEffect(() => { @@ -106,17 +113,13 @@ const DesktopTokensPayload = () => { setShowChart(!showChart); }; - const sortedAssets = useMemo(() => { - return assets?.ton?.jettons?.balances ?? []; - }, [assets]); - - const virtualScrollPaddingBase = canUseTron ? 2 * itemSize : itemSize; + const virtualScrollPaddingBase = canUseTron && !tronWallet ? 2 * itemSize : itemSize; const rowVirtualizer = useVirtualizer({ - count: sortedAssets.length, + count: assets?.length ?? 0, getScrollElement: () => containerRef.current, estimateSize: () => itemSize, - getItemKey: index => sortedAssets[index].jetton.address, + getItemKey: index => assets![index].asset.id, paddingStart: canShowChart && showChart ? 192 + virtualScrollPaddingBase : virtualScrollPaddingBase }); @@ -131,14 +134,14 @@ const DesktopTokensPayload = () => { return rowVirtualizer.scrollToOffset(containerRef.current!.scrollHeight); } - const index = sortedAssets.findIndex(item => item.jetton.address === address); + const index = assets!.findIndex(item => item.asset.address === address); if (index !== undefined) { rowVirtualizer.scrollToOffset( (tonRef.current?.offsetTop ?? 0) + (index + 1) * itemSize ); } }, - [sortedAssets, rowVirtualizer, rowVirtualizer.elementsCache] + [assets, rowVirtualizer, rowVirtualizer.elementsCache] ); return ( @@ -164,59 +167,51 @@ const DesktopTokensPayload = () => { overflow: 'hidden' }} > - {sortedAssets && - assets && - distribution && - uiPreferences && - tronBalances !== undefined && ( - <> - {canShowChart && showChart && ( + {tonAssetAmount && assets && distribution && uiPreferences && ( + <> + {canShowChart && showChart && ( + + + + + )} + + + {canUseTron && !tronWallet && ( + <> + + + + )} + {rowVirtualizer.getVirtualItems().map(virtualRow => ( +
- + - )} - - - {canUseTron && ( - <> - - - - )} - {rowVirtualizer.getVirtualItems().map(virtualRow => ( -
- - - - -
- ))} - - )} +
+ ))} + + )} ); diff --git a/packages/uikit/src/pages/activity/Activity.tsx b/packages/uikit/src/pages/activity/Activity.tsx index e95e06521..8272aa10f 100644 --- a/packages/uikit/src/pages/activity/Activity.tsx +++ b/packages/uikit/src/pages/activity/Activity.tsx @@ -1,81 +1,36 @@ -import { useInfiniteQuery, useQueryClient } from '@tanstack/react-query'; -import { AccountsApi } from '@tonkeeper/core/dist/tonApiV2'; -import React, { FC, Suspense, useCallback, useMemo, useRef } from 'react'; +import React, { FC, Suspense, useRef } from 'react'; import { InnerBody } from '../../components/Body'; import { ActivityHeader } from '../../components/Header'; import { ActivitySkeletonPage, SkeletonListWithImages } from '../../components/Skeleton'; -import { MixedActivityGroup } from '../../components/activity/ActivityGroup'; import { useAppContext } from '../../hooks/appContext'; import { useFetchNext } from '../../hooks/useFetchNext'; -import { QueryKey } from '../../libs/queryKey'; -import { getMixedActivityGroups } from '../../state/mixedActivity'; -import { useActiveApi, useActiveTonNetwork, useActiveWallet } from '../../state/wallet'; -import { useScrollMonitor } from '../../state/activity'; +import { useFetchFilteredActivity, useScrollMonitor } from '../../state/activity'; +import { MobileActivityList } from '../../components/activity/MobileActivityList'; const EmptyActivity = React.lazy(() => import('../../components/activity/EmptyActivity')); const Activity: FC = () => { - const wallet = useActiveWallet(); - const network = useActiveTonNetwork(); - const api = useActiveApi(); const { standalone } = useAppContext(); const ref = useRef(null); const { - isFetched: isTonFetched, - fetchNextPage: fetchTonNextPage, - hasNextPage: hasTonNextPage, - isFetchingNextPage: isTonFetchingNextPage, - data: tonEvents - } = useInfiniteQuery({ - queryKey: [wallet.rawAddress, QueryKey.activity, 'all', network], - queryFn: ({ pageParam = undefined }) => - new AccountsApi(api.tonApiV2).getAccountEvents({ - accountId: wallet.rawAddress, - limit: 20, - beforeLt: pageParam, - subjectOnly: true - }), - getNextPageParam: lastPage => (lastPage.nextFrom > 0 ? lastPage.nextFrom : undefined) - }); + refetch, + isFetched: isActivityFetched, + fetchNextPage: fetchActivityNextPage, + hasNextPage: hasActivityNextPage, + isFetchingNextPage: isActivityFetchingNextPage, + data: activity + } = useFetchFilteredActivity(); - const client = useQueryClient(); - const invalidate = useCallback(() => { - return client.invalidateQueries([wallet.rawAddress, QueryKey.activity, 'all']); - }, []); + useScrollMonitor(ref, 5000, refetch); - useScrollMonitor(ref, 5000, invalidate); + const isFetchingNextPage = isActivityFetchingNextPage; - // const { - // isFetched: isTronFetched, - // data: tronEvents, - // isFetchingNextPage: isTronFetchingNextPage, - // hasNextPage: hasTronNextPage, - // fetchNextPage: fetchTronNextPage - // } = useInfiniteQuery({ - // queryKey: [wallet.tron?.ownerWalletAddress, wallet.network, QueryKey.tron], - // queryFn: ({ pageParam = undefined }) => - // new TronApi(api.tronApi).getTransactions({ - // ownerAddress: wallet.tron!.ownerWalletAddress, - // fingerprint: pageParam, - // limit: 100 - // }), - // getNextPageParam: lastPage => lastPage.fingerprint, - // enabled: wallet.tron !== undefined - // }); + useFetchNext(hasActivityNextPage, isFetchingNextPage, fetchActivityNextPage, standalone, ref); - const isFetchingNextPage = isTonFetchingNextPage; - - useFetchNext(hasTonNextPage, isFetchingNextPage, fetchTonNextPage, standalone, ref); - // useFetchNext(hasTronNextPage, isFetchingNextPage, fetchTronNextPage, standalone, ref); - - const activity = useMemo(() => { - return getMixedActivityGroups(tonEvents, undefined); - }, [tonEvents]); - - if (!isTonFetched) { + if (!isActivityFetched || !activity) { return ; } @@ -91,7 +46,7 @@ const Activity: FC = () => { <> - + {isFetchingNextPage && } diff --git a/packages/uikit/src/pages/coin/Coin.tsx b/packages/uikit/src/pages/coin/Coin.tsx index 65355980a..9a7b5093f 100644 --- a/packages/uikit/src/pages/coin/Coin.tsx +++ b/packages/uikit/src/pages/coin/Coin.tsx @@ -3,7 +3,8 @@ import { useNavigate, useParams } from 'react-router-dom'; import { AppRoute } from '../../libs/routes'; import { JettonContent } from './Jetton'; import { TonPage } from './Ton'; -import { TronPage } from './Tron'; +import { TRON_USDT_ASSET } from '@tonkeeper/core/dist/entries/crypto/asset/constants'; +import { TronUsdtContent } from './TronUsdt'; const CoinPage = () => { const navigate = useNavigate(); @@ -17,8 +18,8 @@ const CoinPage = () => { if (!name) return <>; - if (name === 'tron') { - return ; + if (name === TRON_USDT_ASSET.id) { + return ; } else if (name === 'ton') { return ; } else { diff --git a/packages/uikit/src/pages/coin/Jetton.tsx b/packages/uikit/src/pages/coin/Jetton.tsx index 8d85e9e0f..7202c8caf 100644 --- a/packages/uikit/src/pages/coin/Jetton.tsx +++ b/packages/uikit/src/pages/coin/Jetton.tsx @@ -1,13 +1,16 @@ -import { useInfiniteQuery } from '@tanstack/react-query'; import { Address } from '@ton/core'; import { tonAssetAddressToString } from '@tonkeeper/core/dist/entries/crypto/asset/ton-asset'; -import { AccountsApi, JettonBalance, JettonInfo } from '@tonkeeper/core/dist/tonApiV2'; +import { JettonBalance, JettonInfo } from '@tonkeeper/core/dist/tonApiV2'; import { formatDecimals } from '@tonkeeper/core/dist/utils/balance'; -import React, { FC, useMemo, useRef } from 'react'; +import React, { FC, Suspense, useMemo, useRef } from 'react'; import { InnerBody } from '../../components/Body'; -import { CoinSkeletonPage } from '../../components/Skeleton'; +import { + ActivitySkeletonPage, + CoinSkeletonPage, + SkeletonListWithImages +} from '../../components/Skeleton'; import { SubHeader } from '../../components/SubHeader'; -import { ActivityList } from '../../components/activity/ActivityGroup'; +import { MobileActivityList } from '../../components/activity/MobileActivityList'; import { ActionsRow } from '../../components/home/Actions'; import { ReceiveAction } from '../../components/home/ReceiveAction'; import { SwapAction } from '../../components/home/SwapAction'; @@ -16,40 +19,57 @@ import { SendAction } from '../../components/transfer/SendActionButton'; import { useAppContext } from '../../hooks/appContext'; import { useFormatBalance } from '../../hooks/balance'; import { useFetchNext } from '../../hooks/useFetchNext'; -import { JettonKey, QueryKey } from '../../libs/queryKey'; import { useJettonBalance, useJettonInfo } from '../../state/jetton'; import { useFormatFiat, useRate } from '../../state/rates'; import { useAllSwapAssets } from '../../state/swap/useSwapAssets'; -import { useActiveApi, useActiveWallet, useIsActiveWalletWatchOnly } from '../../state/wallet'; +import { useIsActiveWalletWatchOnly } from '../../state/wallet'; +import { useFetchFilteredActivity, useScrollMonitor } from '../../state/activity'; +import EmptyActivity from '../../components/activity/EmptyActivity'; -const JettonHistory: FC<{ balance: JettonBalance; innerRef: React.RefObject }> = ({ - balance, - innerRef -}) => { - const api = useActiveApi(); +export const MobileAssetHistory: FC<{ + assetAddress: string; + innerRef: React.RefObject; +}> = ({ assetAddress, innerRef }) => { const { standalone } = useAppContext(); - const wallet = useActiveWallet(); - const { isFetched, hasNextPage, data, isFetchingNextPage, fetchNextPage } = useInfiniteQuery({ - queryKey: [balance.walletAddress.address, QueryKey.activity, JettonKey.history], - queryFn: ({ pageParam = undefined }) => - new AccountsApi(api.tonApiV2).getAccountJettonHistoryByID({ - accountId: wallet.rawAddress, - jettonId: balance.jetton.address, - limit: 20, - beforeLt: pageParam - }), - getNextPageParam: lastPage => (lastPage.nextFrom > 0 ? lastPage.nextFrom : undefined) - }); + const { + refetch, + isFetched: isActivityFetched, + fetchNextPage: fetchActivityNextPage, + hasNextPage: hasActivityNextPage, + isFetchingNextPage: isActivityFetchingNextPage, + data: activity + } = useFetchFilteredActivity(assetAddress); - useFetchNext(hasNextPage, isFetchingNextPage, fetchNextPage, standalone, innerRef); + useScrollMonitor(innerRef, 5000, refetch); + + const isFetchingNextPage = isActivityFetchingNextPage; + + useFetchNext( + hasActivityNextPage, + isFetchingNextPage, + fetchActivityNextPage, + standalone, + innerRef + ); + + if (!isActivityFetched || !activity) { + return ; + } + + if (activity.length === 0) { + return ( + }> + + + ); + } return ( - + <> + + {isFetchingNextPage && } + ); }; @@ -104,7 +124,7 @@ export const JettonContent: FC<{ jettonAddress: string }> = ({ jettonAddress }) {swapAsset && } - + ); diff --git a/packages/uikit/src/pages/coin/Ton.tsx b/packages/uikit/src/pages/coin/Ton.tsx index 92ca27617..15ded543d 100644 --- a/packages/uikit/src/pages/coin/Ton.tsx +++ b/packages/uikit/src/pages/coin/Ton.tsx @@ -1,23 +1,18 @@ -import { useInfiniteQuery } from '@tanstack/react-query'; import { BLOCKCHAIN_NAME, CryptoCurrency } from '@tonkeeper/core/dist/entries/crypto'; -import { Account, AccountsApi } from '@tonkeeper/core/dist/tonApiV2'; +import { Account } from '@tonkeeper/core/dist/tonApiV2'; import { formatDecimals } from '@tonkeeper/core/dist/utils/balance'; import React, { FC, useMemo, useRef } from 'react'; import { InnerBody } from '../../components/Body'; import { CoinSkeletonPage } from '../../components/Skeleton'; import { SubHeader } from '../../components/SubHeader'; -import { ActivityList } from '../../components/activity/ActivityGroup'; import { HomeActions } from '../../components/home/TonActions'; import { CoinInfo } from '../../components/jettons/Info'; -import { useAppContext } from '../../hooks/appContext'; import { useFormatBalance } from '../../hooks/balance'; import { useTranslation } from '../../hooks/translation'; -import { useFetchNext } from '../../hooks/useFetchNext'; -import { QueryKey } from '../../libs/queryKey'; import { useFormatFiat, useRate } from '../../state/rates'; -import { groupAndFilterTonActivityItems } from '../../state/ton/tonActivity'; -import { useActiveApi, useActiveWallet, useWalletAccountInfo } from '../../state/wallet'; +import { useWalletAccountInfo } from '../../state/wallet'; +import { MobileAssetHistory } from './Jetton'; const TonHeader: FC<{ info: Account }> = ({ info: { balance } }) => { const { t } = useTranslation(); @@ -44,28 +39,6 @@ export const TonPage = () => { const ref = useRef(null); const { data: info } = useWalletAccountInfo(); - const api = useActiveApi(); - - const { standalone } = useAppContext(); - const wallet = useActiveWallet(); - - const { fetchNextPage, hasNextPage, isFetchingNextPage, data, isFetched } = useInfiniteQuery({ - queryKey: [wallet.rawAddress, QueryKey.activity, 'ton'], - queryFn: ({ pageParam = undefined }) => - new AccountsApi(api.tonApiV2).getAccountEvents({ - accountId: wallet.rawAddress, - limit: 20, - beforeLt: pageParam, - subjectOnly: true - }), - getNextPageParam: lastPage => (lastPage.nextFrom > 0 ? lastPage.nextFrom : undefined) - }); - - useFetchNext(hasNextPage, isFetchingNextPage, fetchNextPage, standalone, ref); - - const activity = useMemo(() => { - return data ? groupAndFilterTonActivityItems(data) : undefined; - }, [data]); if (!info) { return ; @@ -77,11 +50,7 @@ export const TonPage = () => { - + ); diff --git a/packages/uikit/src/pages/coin/Tron.tsx b/packages/uikit/src/pages/coin/Tron.tsx deleted file mode 100644 index 2eb7bfd5c..000000000 --- a/packages/uikit/src/pages/coin/Tron.tsx +++ /dev/null @@ -1,130 +0,0 @@ -import { BLOCKCHAIN_NAME } from '@tonkeeper/core/dist/entries/crypto'; -import { TronWalletState } from '@tonkeeper/core/dist/entries/wallet'; -import { TronBalance } from '@tonkeeper/core/dist/tronApi'; -import { formatDecimals } from '@tonkeeper/core/dist/utils/balance'; -import React, { FC, useEffect, useMemo, useRef } from 'react'; -import { Route, Routes, useNavigate, useParams } from 'react-router-dom'; -import styled from 'styled-components'; -import { InnerBody } from '../../components/Body'; -import { CoinSkeletonPage } from '../../components/Skeleton'; -import { SubHeader } from '../../components/SubHeader'; -import { Body2 } from '../../components/Text'; -import { ActionsRow } from '../../components/home/Actions'; -import { ReceiveAction } from '../../components/home/ReceiveAction'; -import { CoinInfo } from '../../components/jettons/Info'; -import { SendAction } from '../../components/transfer/SendActionButton'; -import { useFormatBalance } from '../../hooks/balance'; -import { AppRoute } from '../../libs/routes'; -import { useFormatFiat, useRate } from '../../state/rates'; -import { useTronBalance, useTronWalletState } from '../../state/tron/tron'; - -const TronHeader: FC<{ tronBalance: TronBalance }> = ({ tronBalance: { token, weiAmount } }) => { - const amount = useMemo(() => formatDecimals(weiAmount, token.decimals), [weiAmount, token]); - const total = useFormatBalance(amount, token.decimals); - - const { data } = useRate(token.symbol); - const { fiatAmount } = useFormatFiat(data, amount); - - return ; -}; - -const TronActivity: FC<{ - tron: TronWalletState; - innerRef: React.RefObject; -}> = ({ tron, innerRef }) => { - return null; - /*const { - standalone, - api: { tronApi } - } = useAppContext(); - const { data, isFetched, isFetchingNextPage, hasNextPage, fetchNextPage } = useInfiniteQuery({ - queryKey: [tron.ownerWalletAddress, wallet.network, QueryKey.tron], - queryFn: ({ pageParam = undefined }) => - new TronApi(tronApi).getTransactions({ - ownerAddress: tron.ownerWalletAddress, - fingerprint: pageParam, - limit: 100 - }), - getNextPageParam: lastPage => lastPage.fingerprint - }); - - useFetchNext(hasNextPage, isFetchingNextPage, fetchNextPage, standalone, innerRef); - - return ( - - );*/ -}; - -const Layout = styled.div` - display: flex; - flex-direction: column; - align-items: center; - line-height: 22px; -`; - -const Label = styled(Body2)` - color: ${props => props.theme.textSecondary}; -`; - -const TronAsset: FC<{ tron: TronWalletState }> = ({ tron }) => { - const { address } = useParams(); - const navigate = useNavigate(); - const { data: tronBalance, isLoading, isError } = useTronBalance(tron, address); - useEffect(() => { - if (isError) { - navigate(AppRoute.home); - } - }, [isError]); - - const ref = useRef(null); - - if (isLoading || !tronBalance) { - return ; - } - - return ( - <> - -
{tronBalance.token.name}
- - - } - /> - - - - - - - - - - ); -}; - -export const TronPage = () => { - const navigate = useNavigate(); - const { data: state, isLoading, isError } = useTronWalletState(); - - useEffect(() => { - if (isError) { - navigate(AppRoute.home); - } - }, [isError]); - - if (isLoading || !state) { - return ; - } - - return ( - - } /> - - ); -}; diff --git a/packages/uikit/src/pages/coin/TronUsdt.tsx b/packages/uikit/src/pages/coin/TronUsdt.tsx new file mode 100644 index 000000000..6a1e4e00f --- /dev/null +++ b/packages/uikit/src/pages/coin/TronUsdt.tsx @@ -0,0 +1,78 @@ +import React, { FC, useRef } from 'react'; +import { InnerBody } from '../../components/Body'; +import { CoinSkeletonPage } from '../../components/Skeleton'; +import { SubHeader } from '../../components/SubHeader'; +import { Action, ActionsRow } from '../../components/home/Actions'; +import { CoinInfo } from '../../components/jettons/Info'; +import { TokenRate, useFormatFiat } from '../../state/rates'; +import { AssetAmount } from '@tonkeeper/core/dist/entries/crypto/asset/asset-amount'; +import { TronAsset } from '@tonkeeper/core/dist/entries/crypto/asset/tron-asset'; +import { useTronBalances } from '../../state/tron/tron'; +import { ReceiveIcon, SendIcon } from '../../components/home/HomeIcons'; +import { useAppSdk } from '../../hooks/appSdk'; +import { BLOCKCHAIN_NAME } from '@tonkeeper/core/dist/entries/crypto'; +import { useSendTransferNotification } from '../../components/modals/useSendTransferNotification'; +import { MobileAssetHistory } from './Jetton'; +import { TRON_USDT_ASSET } from '@tonkeeper/core/dist/entries/crypto/asset/constants'; + +const usdtRate: TokenRate = { + prices: 1, + diff7d: '', + diff24h: '' +}; + +const TronUsdtHeader: FC<{ assetAmount: AssetAmount }> = ({ assetAmount }) => { + const { fiatAmount } = useFormatFiat(usdtRate, assetAmount.relativeAmount); + + return ( + + ); +}; + +export const TronUsdtContent = () => { + const balance = useTronBalances().data?.usdt; + const ref = useRef(null); + const sdk = useAppSdk(); + const { onOpen } = useSendTransferNotification(); + + if (balance === undefined) { + return ; + } + + return ( + <> + + + + + } + title={'wallet_send'} + action={() => onOpen({ chain: BLOCKCHAIN_NAME.TRON })} + /> + } + title={'wallet_receive'} + action={() => + sdk.uiEvents.emit('receive', { + method: 'receive', + params: { + chain: BLOCKCHAIN_NAME.TRON, + jetton: balance.asset.id + } + }) + } + /> + + + + + + ); +}; diff --git a/packages/uikit/src/pages/home/Home.tsx b/packages/uikit/src/pages/home/Home.tsx index 07986c863..19b4b15eb 100644 --- a/packages/uikit/src/pages/home/Home.tsx +++ b/packages/uikit/src/pages/home/Home.tsx @@ -4,22 +4,19 @@ import { FC } from 'react'; import { HomeSkeleton } from '../../components/Skeleton'; import { Balance } from '../../components/home/Balance'; import { CompactView } from '../../components/home/CompactView'; -import { AssetData } from '../../components/home/Jettons'; import { TabsView } from '../../components/home/TabsView'; import { HomeActions } from '../../components/home/TonActions'; -import { useAssets } from '../../state/home'; +import { useAllChainsAssets } from '../../state/home'; import { usePreFetchRates } from '../../state/rates'; import { useWalletFilteredNftList } from '../../state/nft'; +import { AssetAmount } from '@tonkeeper/core/dist/entries/crypto/asset/asset-amount'; const HomeAssets: FC<{ - assets: AssetData; + assets: AssetAmount[]; nfts: NFT[]; }> = ({ assets, nfts }) => { - if ( - assets.ton.jettons.balances.length + nfts.length < 10 || - assets.ton.jettons.balances.length < 3 - ) { + if (assets.length + nfts.length < 11 || assets.length < 4) { return ; } else { return ; @@ -29,11 +26,11 @@ const HomeAssets: FC<{ const Home = () => { const { isFetched } = usePreFetchRates(); - const [assets, error, isAssetLoading, jettonError] = useAssets(); + const { assets, error } = useAllChainsAssets(); - const { data: nfts, error: nftError, isFetching: isNftLoading } = useWalletFilteredNftList(); + const { data: nfts, isFetching: isNftLoading } = useWalletFilteredNftList(); - const isLoading = isAssetLoading || isNftLoading; + const isLoading = !assets || isNftLoading; if (!nfts || !assets || !isFetched) { return ; @@ -41,8 +38,7 @@ const Home = () => { return ( <> - - {/* TODO: ENABLE TRON */} + diff --git a/packages/uikit/src/pages/home/MainColumn.tsx b/packages/uikit/src/pages/home/MainColumn.tsx deleted file mode 100644 index 29b9eefbd..000000000 --- a/packages/uikit/src/pages/home/MainColumn.tsx +++ /dev/null @@ -1,126 +0,0 @@ -import { BLOCKCHAIN_NAME } from '@tonkeeper/core/dist/entries/crypto'; -import { memo, useEffect, useMemo } from 'react'; -import { useLocation, useNavigate } from 'react-router-dom'; -import { useTheme } from 'styled-components'; -import { ActivityIcon, BrowserIcon, NftIcon, WalletIcon } from '../../components/NavigationIcons'; -import { SkeletonAction } from '../../components/Skeleton'; -import { ActionsRow } from '../../components/home/Actions'; -import { Balance, BalanceSkeleton } from '../../components/home/Balance'; -import { HomeActions } from '../../components/home/TonActions'; -import { SettingsItem, SettingsList } from '../../components/settings/SettingsList'; -import { useAppSdk } from '../../hooks/appSdk'; -import { useTranslation } from '../../hooks/translation'; -import { scrollToTop } from '../../libs/common'; -import { AppRoute } from '../../libs/routes'; -import { useAssets } from '../../state/home'; - -import { useWalletFilteredNftList } from "../../state/nft"; - -const MainColumnSkeleton = memo(() => { - const sdk = useAppSdk(); - useEffect(() => { - return () => { - sdk.uiEvents.emit('loading'); - }; - }, []); - - return ( - <> - - - - - - {/* */} - - - ); -}); - -const Navigation = () => { - const { t } = useTranslation(); - const location = useLocation(); - const navigate = useNavigate(); - - const { data: nfts } = useWalletFilteredNftList(); - - const active = useMemo(() => { - if (location.pathname.includes(AppRoute.activity)) { - return AppRoute.activity; - } - if (location.pathname.includes(AppRoute.browser)) { - return AppRoute.browser; - } - if (location.pathname.includes(AppRoute.purchases)) { - return AppRoute.purchases; - } - return AppRoute.home; - }, [location.pathname]); - - const theme = useTheme(); - const items = useMemo(() => { - const handleClick = (route: AppRoute) => { - if (location.pathname !== route) { - return navigate(route); - } else { - scrollToTop(); - } - }; - - const items: SettingsItem[] = [ - { - name: t('wallet_title'), - icon: , - iconColor: - active === AppRoute.home ? theme.tabBarActiveIcon : theme.tabBarInactiveIcon, - action: () => handleClick(AppRoute.home) - }, - { - name: t('browser_title'), - icon: , - iconColor: - active === AppRoute.browser ? theme.tabBarActiveIcon : theme.tabBarInactiveIcon, - action: () => handleClick(AppRoute.browser) - }, - { - name: t('activity_screen_title'), - icon: , - iconColor: - active === AppRoute.activity - ? theme.tabBarActiveIcon - : theme.tabBarInactiveIcon, - action: () => handleClick(AppRoute.activity) - } - ]; - if (nfts && nfts.length > 0) { - items.push({ - name: t('purchases_screen_title'), - icon: , - iconColor: - active === AppRoute.purchases - ? theme.tabBarActiveIcon - : theme.tabBarInactiveIcon, - action: () => handleClick(AppRoute.purchases) - }); - } - return items; - }, [t, location, navigate, active, theme, nfts]); - - return ; -}; - -export const MainColumn = () => { - const [assets, error, isAssetLoading] = useAssets(); - - if (!assets) { - return ; - } - - return ( - <> - - - - - ); -}; diff --git a/packages/uikit/src/state/home.ts b/packages/uikit/src/state/home.ts index befba573e..6e54d2255 100644 --- a/packages/uikit/src/state/home.ts +++ b/packages/uikit/src/state/home.ts @@ -35,11 +35,11 @@ export const useAssets = () => { }; export const useAllChainsAssets = () => { - const [assets] = useAssets(); + const [assets, error, _, jettonError] = useAssets(); const { data: config } = useActiveTonWalletConfig(); - return useMemo(() => { - if (!assets || !config) return undefined; + return useMemo<{ assets: AssetAmount[] | undefined; error: Error | undefined }>(() => { + if (!assets || !config) return { assets: undefined, error: undefined }; const result: AssetAmount[] = [ new AssetAmount({ asset: TON_ASSET, weiAmount: assets.ton.info.balance }) @@ -69,8 +69,8 @@ export const useAllChainsAssets = () => { result.push(jettonToTonAssetAmount(b)); }); - return result; - }, [assets, config]); + return { assets: result, error: error ?? jettonError ?? undefined }; + }, [assets, error, jettonError, config]); }; export const useAssetWeiBalance = (asset: AssetIdentification) => { From 2625367beed10089b7e5ffd49ac076a13f87b83d Mon Sep 17 00:00:00 2001 From: siandreev Date: Fri, 27 Dec 2024 15:14:23 +0100 Subject: [PATCH 14/20] fix: support disable tron feature from the backend --- packages/uikit/src/pages/settings/Dev.tsx | 5 +++++ packages/uikit/src/state/tron/tron.ts | 12 ++++++------ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/packages/uikit/src/pages/settings/Dev.tsx b/packages/uikit/src/pages/settings/Dev.tsx index 92187faaa..f3541d71f 100644 --- a/packages/uikit/src/pages/settings/Dev.tsx +++ b/packages/uikit/src/pages/settings/Dev.tsx @@ -88,6 +88,11 @@ const EnableTronSettings = () => { const { mutate: mutateSettings } = useMutateDevSettings(); const { data: devSettings } = useDevSettings(); + const config = useActiveConfig(); + if (config.flags?.disable_tron) { + return null; + } + return ( diff --git a/packages/uikit/src/state/tron/tron.ts b/packages/uikit/src/state/tron/tron.ts index c8039e08d..5cd4368df 100644 --- a/packages/uikit/src/state/tron/tron.ts +++ b/packages/uikit/src/state/tron/tron.ts @@ -2,7 +2,7 @@ import { useQuery } from '@tanstack/react-query'; import { useAppContext } from '../../hooks/appContext'; import { QueryKey } from '../../libs/queryKey'; import { DefaultRefetchInterval } from '../tonendpoint'; -import { useActiveAccount } from '../wallet'; +import { useActiveAccount, useActiveConfig } from '../wallet'; import { useMemo } from 'react'; import { isAccountTronCompatible } from '@tonkeeper/core/dist/entries/account'; import { TronWallet } from '@tonkeeper/core/dist/entries/tron/tron-wallet'; @@ -26,6 +26,11 @@ export const useTronApi = () => { export const useIsTronEnabledGlobally = () => { const { data: devSettings } = useDevSettings(); + const config = useActiveConfig(); + + if (config.flags?.disable_tron) { + return false; + } return devSettings?.tronEnabled; }; @@ -43,11 +48,6 @@ export const useCanUseTronForActiveWallet = () => { export const useActiveTronWallet = (): TronWallet | undefined => { const account = useActiveAccount(); - const isTronEnabled = useIsTronEnabledGlobally(); - - if (!isTronEnabled) { - return undefined; - } if (isAccountTronCompatible(account)) { return account.activeTronWallet; From abf84e173d3a4cc811b433e071fccfaf2753879a Mon Sep 17 00:00:00 2001 From: siandreev Date: Fri, 27 Dec 2024 19:39:01 +0100 Subject: [PATCH 15/20] fix: add tron usdt to tokens order settings --- .../tron/TronActivityActionDetails.tsx | 11 +-- .../uikit/src/components/home/TronAssets.tsx | 26 ++---- .../desktop-pages/coin/DesktopCoinPage.tsx | 11 ++- packages/uikit/src/pages/coin/TronUsdt.tsx | 11 +-- packages/uikit/src/pages/settings/Jettons.tsx | 90 ++++++++++++------- packages/uikit/src/state/asset.ts | 18 +++- packages/uikit/src/state/jetton.ts | 26 +++--- packages/uikit/src/state/rates.ts | 33 +++++++ 8 files changed, 140 insertions(+), 86 deletions(-) diff --git a/packages/uikit/src/components/activity/tron/TronActivityActionDetails.tsx b/packages/uikit/src/components/activity/tron/TronActivityActionDetails.tsx index df920d316..73a33adad 100644 --- a/packages/uikit/src/components/activity/tron/TronActivityActionDetails.tsx +++ b/packages/uikit/src/components/activity/tron/TronActivityActionDetails.tsx @@ -1,6 +1,6 @@ import { ActionStatusEnum } from '@tonkeeper/core/dist/tonApiV2'; import React, { FC, PropsWithChildren } from 'react'; -import { useFormatFiat } from '../../../state/rates'; +import { useFormatFiat, useUSDTRate } from '../../../state/rates'; import { ListBlock } from '../../List'; import { ActivityDetailsHeader } from '../ActivityDetailsLayout'; import { @@ -25,18 +25,13 @@ const TronActionDetailsBlock: FC> ); }; -const usdtRate = { - diff7d: '', - diff24h: '', - prices: 1 -}; - export const TronTransferActionNotification: FC = ({ timestamp, event }) => { const wallet = useActiveTronWallet()!; - const { fiatAmount } = useFormatFiat(usdtRate, event.assetAmount.relativeAmount); + const { data: rate } = useUSDTRate(); + const { fiatAmount } = useFormatFiat(rate, event.assetAmount.relativeAmount); const isScam = event.isScam; const kind = event.to === wallet.address ? 'received' : 'send'; diff --git a/packages/uikit/src/components/home/TronAssets.tsx b/packages/uikit/src/components/home/TronAssets.tsx index e84675cec..9ca58298c 100644 --- a/packages/uikit/src/components/home/TronAssets.tsx +++ b/packages/uikit/src/components/home/TronAssets.tsx @@ -1,7 +1,7 @@ -import React, { FC, forwardRef, useMemo } from 'react'; +import React, { FC, forwardRef } from 'react'; import { useNavigate } from 'react-router-dom'; import { AppRoute } from '../../libs/routes'; -import { useFormatFiat } from '../../state/rates'; +import { useFormatFiat, useUSDTRate } from '../../state/rates'; import { ListItem } from '../List'; import { ListItemPayload, TokenLayout, TokenLogo } from './TokenLayout'; import { AssetAmount } from '@tonkeeper/core/dist/entries/crypto/asset/asset-amount'; @@ -15,24 +15,10 @@ import { useAddTronToAccount } from '../../state/wallet'; import styled from 'styled-components'; import { useIsFullWidthMode } from '../../hooks/useIsFullWidthMode'; -const usdtRate = { - diff7d: '', - diff24h: '', - prices: 1 -}; - const TokenLogoNotRounded = styled(TokenLogo)` border-radius: unset; `; -const tronAssetRate = (asset: TronAsset) => { - if (asset.id === TRON_USDT_ASSET.id) { - return usdtRate; - } - - return undefined; -}; - export const TronAssetComponent = forwardRef< HTMLDivElement, { @@ -42,8 +28,7 @@ export const TronAssetComponent = forwardRef< >(({ assetAmount, className }, ref) => { const navigate = useNavigate(); - const rate = useMemo(() => tronAssetRate(assetAmount.asset), [assetAmount.asset.id]); - + const { data: rate } = useUSDTRate(); const { fiatPrice, fiatAmount } = useFormatFiat(rate, assetAmount.relativeAmount); return ( @@ -78,6 +63,7 @@ const InactiveUSDA: FC<{ className?: string }> = ({ className }) => { const { mutate, isLoading } = useAddTronToAccount(); const isFullWidth = useIsFullWidthMode(); + const { data: rate } = useUSDTRate(); return ( @@ -87,10 +73,10 @@ const InactiveUSDA: FC<{ className?: string }> = ({ className }) => { name={TRON_USDT_ASSET.name!} symbol={TRON_USDT_ASSET.symbol} balance="" - secondary={formatFiatCurrency(fiat, usdtRate.prices)} + secondary={rate?.prices ? formatFiatCurrency(fiat, rate?.prices) : undefined} fiatAmount="" label="TRC20" - rate={usdtRate} + rate={rate} />