diff --git a/dapps/react-dapp-v2/package.json b/dapps/react-dapp-v2/package.json index 0f35d8d..c6d67fd 100644 --- a/dapps/react-dapp-v2/package.json +++ b/dapps/react-dapp-v2/package.json @@ -38,6 +38,7 @@ "react-scripts": "^4.0.3", "solana-wallet": "^1.0.1", "styled-components": "^5.2.0", + "tronweb": "^4.4.0", "web-vitals": "^0.2.4" }, "devDependencies": { diff --git a/dapps/react-dapp-v2/src/chains/index.ts b/dapps/react-dapp-v2/src/chains/index.ts index 377cb94..31aaade 100644 --- a/dapps/react-dapp-v2/src/chains/index.ts +++ b/dapps/react-dapp-v2/src/chains/index.ts @@ -6,6 +6,7 @@ import * as polkadot from "./polkadot"; import * as solana from "./solana"; import * as near from "./near"; import * as elrond from "./elrond"; +import * as tron from './tron'; import { ChainMetadata, ChainRequestRender } from "../helpers"; @@ -24,6 +25,8 @@ export function getChainMetadata(chainId: string): ChainMetadata { return near.getChainMetadata(chainId); case "elrond": return elrond.getChainMetadata(chainId); + case 'tron': + return tron.getChainMetadata(chainId); default: throw new Error(`No metadata handler for namespace ${namespace}`); } diff --git a/dapps/react-dapp-v2/src/chains/tron.ts b/dapps/react-dapp-v2/src/chains/tron.ts new file mode 100644 index 0000000..1f78dda --- /dev/null +++ b/dapps/react-dapp-v2/src/chains/tron.ts @@ -0,0 +1,42 @@ +import { ChainsMap } from 'caip-api'; +import { NamespaceMetadata, ChainMetadata } from '../helpers'; + +// TODO: add `tron` namespace to `caip-api` package to avoid manual specification here. +export const TronChainData: ChainsMap = { + '0x2b6653dc': { + id: 'tron:0x2b6653dc', + name: 'Tron Mainnet', + rpc: [], + slip44: 195, + testnet: false + }, + '0xcd8690dc': { + id: 'tron:0xcd8690dc', + name: 'Tron Testnet', + rpc: [], + slip44: 195, + testnet: true + } +}; + +export const TronMetadata: NamespaceMetadata = { + // Tron Mainnet + '0x2b6653dc': { + logo: 'https://tronscan.io/static/media/TRON.4a760cebd163969b2ee874abf2415e9a.svg', + rgb: '183, 62, 49', + }, + // Tron TestNet + '0xcd8690dc': { + logo: 'https://tronscan.io/static/media/TRON.4a760cebd163969b2ee874abf2415e9a.svg', + rgb: '183, 62, 49', + } +}; + +export function getChainMetadata(chainId: string): ChainMetadata { + const reference = chainId.split(':')[1]; + const metadata = TronMetadata[reference]; + if (typeof metadata === 'undefined') { + throw new Error(`No chain metadata found for chainId: ${chainId}`); + } + return metadata; +} diff --git a/dapps/react-dapp-v2/src/constants/default.ts b/dapps/react-dapp-v2/src/constants/default.ts index 0761dcb..bb83a2c 100644 --- a/dapps/react-dapp-v2/src/constants/default.ts +++ b/dapps/react-dapp-v2/src/constants/default.ts @@ -13,6 +13,7 @@ export const DEFAULT_MAIN_CHAINS = [ "solana:4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ", "polkadot:91b171bb158e2d3848fa23a9f1c25182", "elrond:1", + 'tron:0x2b6653dc', ]; export const DEFAULT_TEST_CHAINS = [ @@ -26,6 +27,7 @@ export const DEFAULT_TEST_CHAINS = [ "polkadot:e143f23803ac50e8f6f8e62695d1ce9e", "near:testnet", "elrond:D", + 'tron:0xcd8690dc', ]; export const DEFAULT_CHAINS = [...DEFAULT_MAIN_CHAINS, ...DEFAULT_TEST_CHAINS]; @@ -113,6 +115,16 @@ export enum DEFAULT_ELROND_METHODS { export enum DEFAULT_ELROND_EVENTS {} +/** + * TRON + */ + export enum DEFAULT_TRON_METHODS { + TRON_SIGN_TRANSACTION = 'tron_signTransaction', + TRON_SIGN_MESSAGE = 'tron_signMessage' +} + +export enum DEFAULT_TRON_EVENTS {} + export const DEFAULT_GITHUB_REPO_URL = "https://github.com/WalletConnect/web-examples/tree/main/dapps/react-dapp-v2"; diff --git a/dapps/react-dapp-v2/src/contexts/ChainDataContext.tsx b/dapps/react-dapp-v2/src/contexts/ChainDataContext.tsx index 88b29bb..15c1f56 100644 --- a/dapps/react-dapp-v2/src/contexts/ChainDataContext.tsx +++ b/dapps/react-dapp-v2/src/contexts/ChainDataContext.tsx @@ -9,6 +9,7 @@ import { import { SolanaChainData } from "../chains/solana"; import { PolkadotChainData } from "../chains/polkadot"; import { ElrondChainData } from "../chains/elrond"; +import { TronChainData } from '../chains/tron'; import { ChainNamespaces, getAllChainNamespaces } from "../helpers"; import { NearChainData } from "../chains/near"; @@ -50,6 +51,8 @@ export function ChainDataContextProvider({ chains = NearChainData; } else if (namespace === "elrond") { chains = ElrondChainData; + } else if (namespace === 'tron') { + chains = TronChainData; } else { chains = await apiGetChainNamespace(namespace); } diff --git a/dapps/react-dapp-v2/src/contexts/JsonRpcContext.tsx b/dapps/react-dapp-v2/src/contexts/JsonRpcContext.tsx index 195e4be..7d4aee1 100644 --- a/dapps/react-dapp-v2/src/contexts/JsonRpcContext.tsx +++ b/dapps/react-dapp-v2/src/contexts/JsonRpcContext.tsx @@ -19,6 +19,8 @@ import { SystemProgram, Transaction as SolanaTransaction, } from "@solana/web3.js"; +// @ts-expect-error +import TronWeb from 'tronweb'; import { eip712, formatTestTransaction, @@ -32,6 +34,7 @@ import { DEFAULT_POLKADOT_METHODS, DEFAULT_NEAR_METHODS, DEFAULT_ELROND_METHODS, + DEFAULT_TRON_METHODS, } from "../constants"; import { useChainData } from "./ChainDataContext"; import { signatureVerify, cryptoWaitReady } from "@polkadot/util-crypto"; @@ -90,6 +93,10 @@ interface IContext { testSignTransaction: TRpcRequestCallback; testSignTransactions: TRpcRequestCallback; }; + tronRpc: { + testSignMessage: TRpcRequestCallback; + testSignTransaction: TRpcRequestCallback; + }; rpcResult?: IFormattedRpcResponse | null; isRpcRequestPending: boolean; isTestnet: boolean; @@ -1049,6 +1056,94 @@ export function JsonRpcContextProvider({ ), }; + // -------- TRON RPC METHODS -------- + + const tronRpc = { + testSignTransaction: _createJsonRpcRequestHandler( + async (chainId: string, address: string): Promise => { + // Nile TestNet, if you want to use in MainNet, change the fullHost to 'https://api.trongrid.io' + const fullHost = isTestnet ? "https://nile.trongrid.io/" : "https://api.trongrid.io/"; + + const tronWeb = new TronWeb({ + fullHost, + }) + + + // Take USDT as an example: + // Nile TestNet: https://nile.tronscan.org/#/token20/TXYZopYRdj2D9XRtbG411XZZ3kM5VkAeBf + // MainNet: https://tronscan.org/#/token20/TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t + + + const testContract = isTestnet ? "TXYZopYRdj2D9XRtbG411XZZ3kM5VkAeBf" : "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t"; + const testTransaction = await tronWeb.transactionBuilder.triggerSmartContract( + testContract, + 'approve(address,uint256)', + { feeLimit: 200000000 }, + [ + { type: 'address', value: address }, + { type: 'uint256', value: 0 } + ], + address + ); + + try { + const { result } = await client!.request<{ result: any }>({ + chainId, + topic: session!.topic, + request: { + method: DEFAULT_TRON_METHODS.TRON_SIGN_TRANSACTION, + params: { + address, + transaction:{ + ...testTransaction + } + } + } + }); + + return { + method: DEFAULT_TRON_METHODS.TRON_SIGN_TRANSACTION, + address, + valid: true, + result: result.signature + }; + } catch (error: any) { + throw new Error(error); + } + } + ), + testSignMessage: _createJsonRpcRequestHandler( + async (chainId: string, address: string): Promise => { + + const message = 'This is a message to be signed for Tron'; + + try { + const result = await client!.request<{ signature: string }>({ + chainId, + topic: session!.topic, + request: { + method: DEFAULT_TRON_METHODS.TRON_SIGN_MESSAGE, + params: { + address, + message + } + } + }); + + return { + method: DEFAULT_TRON_METHODS.TRON_SIGN_MESSAGE, + address, + valid: true, + result: result.signature + }; + } catch (error: any) { + throw new Error(error); + } + } + ) + }; + + return ( { @@ -40,6 +42,8 @@ export const getSupportedMethodsByNamespace = (namespace: string) => { return Object.values(DEFAULT_NEAR_METHODS); case "elrond": return Object.values(DEFAULT_ELROND_METHODS); + case 'tron': + return Object.values(DEFAULT_TRON_METHODS); default: throw new Error(`No default methods for namespace: ${namespace}`); } @@ -59,6 +63,8 @@ export const getSupportedEventsByNamespace = (namespace: string) => { return Object.values(DEFAULT_NEAR_EVENTS); case "elrond": return Object.values(DEFAULT_ELROND_EVENTS); + case "tron": + return Object.values(DEFAULT_TRON_EVENTS); default: throw new Error(`No default events for namespace: ${namespace}`); } diff --git a/dapps/react-dapp-v2/src/pages/index.tsx b/dapps/react-dapp-v2/src/pages/index.tsx index cd2caed..d8554c1 100644 --- a/dapps/react-dapp-v2/src/pages/index.tsx +++ b/dapps/react-dapp-v2/src/pages/index.tsx @@ -16,6 +16,7 @@ import { DEFAULT_ELROND_METHODS, DEFAULT_TEST_CHAINS, DEFAULT_NEAR_METHODS, + DEFAULT_TRON_METHODS, } from "../constants"; import { AccountAction, setLocaleStorageTestnetFlag } from "../helpers"; import Toggle from "../components/Toggle"; @@ -73,6 +74,7 @@ const Home: NextPage = () => { polkadotRpc, nearRpc, elrondRpc, + tronRpc, isRpcRequestPending, rpcResult, isTestnet, @@ -271,6 +273,27 @@ const Home: NextPage = () => { ]; }; + const getTronActions = (): AccountAction[] => { + const onSignTransaction = async (chainId: string, address: string) => { + openRequestModal(); + await tronRpc.testSignTransaction(chainId, address); + }; + const onSignMessage = async (chainId: string, address: string) => { + openRequestModal(); + await tronRpc.testSignMessage(chainId, address); + }; + return [ + { + method: DEFAULT_TRON_METHODS.TRON_SIGN_TRANSACTION, + callback: onSignTransaction + }, + { + method: DEFAULT_TRON_METHODS.TRON_SIGN_MESSAGE, + callback: onSignMessage + } + ]; + }; + const getBlockchainActions = (chainId: string) => { const [namespace] = chainId.split(":"); switch (namespace) { @@ -286,6 +309,8 @@ const Home: NextPage = () => { return getNearActions(); case "elrond": return getElrondActions(); + case "tron": + return getTronActions(); default: break; } diff --git a/wallets/react-wallet-v2/package.json b/wallets/react-wallet-v2/package.json index 60bf34d..cd6d8aa 100644 --- a/wallets/react-wallet-v2/package.json +++ b/wallets/react-wallet-v2/package.json @@ -36,6 +36,7 @@ "react-dom": "17.0.2", "react-qr-reader-es6": "2.2.1-2", "solana-wallet": "^1.0.2", + "tronweb": "^4.4.0", "valtio": "1.6.0" }, "devDependencies": { diff --git a/wallets/react-wallet-v2/src/components/AccountPicker.tsx b/wallets/react-wallet-v2/src/components/AccountPicker.tsx index a69d12f..e90b91c 100644 --- a/wallets/react-wallet-v2/src/components/AccountPicker.tsx +++ b/wallets/react-wallet-v2/src/components/AccountPicker.tsx @@ -4,6 +4,7 @@ import { eip155Addresses } from '@/utils/EIP155WalletUtil' import { nearAddresses } from '@/utils/NearWalletUtil' import { solanaAddresses } from '@/utils/SolanaWalletUtil' import { elrondAddresses } from '@/utils/ElrondWalletUtil' +import { tronAddresses } from '@/utils/TronWalletUtil' import { useSnapshot } from 'valtio' export default function AccountPicker() { @@ -17,6 +18,7 @@ export default function AccountPicker() { SettingsStore.setSolanaAddress(solanaAddresses[account]) SettingsStore.setNearAddress(nearAddresses[account]) SettingsStore.setElrondAddress(elrondAddresses[account]) + SettingsStore.setTronAddress(tronAddresses[account]) } return ( diff --git a/wallets/react-wallet-v2/src/components/Modal.tsx b/wallets/react-wallet-v2/src/components/Modal.tsx index e0faf46..5f4b88d 100644 --- a/wallets/react-wallet-v2/src/components/Modal.tsx +++ b/wallets/react-wallet-v2/src/components/Modal.tsx @@ -7,6 +7,7 @@ import SessionSignNearModal from '@/views/SessionSignNearModal' import SessionSignPolkadotModal from '@/views/SessionSignPolkadotModal' import SessionSignSolanaModal from '@/views/SessionSignSolanaModal' import SessionSignElrondModal from '@/views/SessionSignElrondModal' +import SessionSignTronModal from '@/views/SessionSignTronModal' import SessionSignTypedDataModal from '@/views/SessionSignTypedDataModal' import SessionUnsuportedMethodModal from '@/views/SessionUnsuportedMethodModal' import LegacySessionProposalModal from '@/views/LegacySessionProposalModal' @@ -31,6 +32,7 @@ export default function Modal() { {view === 'SessionSignPolkadotModal' && } {view === 'SessionSignNearModal' && } {view === 'SessionSignElrondModal' && } + {view === 'SessionSignTronModal' && } {view === 'LegacySessionProposalModal' && } {view === 'LegacySessionSignModal' && } {view === 'LegacySessionSignTypedDataModal' && } diff --git a/wallets/react-wallet-v2/src/components/RequestDetalilsCard.tsx b/wallets/react-wallet-v2/src/components/RequestDetalilsCard.tsx index 315ac60..445a23c 100644 --- a/wallets/react-wallet-v2/src/components/RequestDetalilsCard.tsx +++ b/wallets/react-wallet-v2/src/components/RequestDetalilsCard.tsx @@ -3,6 +3,7 @@ import { EIP155_CHAINS, TEIP155Chain } from '@/data/EIP155Data' import { NEAR_TEST_CHAINS, TNearChain } from '@/data/NEARData' import { SOLANA_CHAINS, TSolanaChain } from '@/data/SolanaData' import { ELROND_CHAINS, TElrondChain } from '@/data/ElrondData' +import { TRON_CHAINS, TTronChain } from '@/data/TronData' import { Col, Divider, Row, Text } from '@nextui-org/react' import { Fragment } from 'react' @@ -32,6 +33,7 @@ export default function RequesDetailsCard({ chains, protocol }: IProps) { SOLANA_CHAINS[chain as TSolanaChain]?.name ?? NEAR_TEST_CHAINS[chain as TNearChain]?.name ?? ELROND_CHAINS[chain as TElrondChain]?.name ?? + TRON_CHAINS[chain as TTronChain]?.name ?? chain ) .join(', ')} diff --git a/wallets/react-wallet-v2/src/components/SessionChainCard.tsx b/wallets/react-wallet-v2/src/components/SessionChainCard.tsx index fdde97b..fe1d11e 100644 --- a/wallets/react-wallet-v2/src/components/SessionChainCard.tsx +++ b/wallets/react-wallet-v2/src/components/SessionChainCard.tsx @@ -4,6 +4,7 @@ import { EIP155_MAINNET_CHAINS, EIP155_TEST_CHAINS } from '@/data/EIP155Data' import { NEAR_TEST_CHAINS } from '@/data/NEARData' import { SOLANA_MAINNET_CHAINS, SOLANA_TEST_CHAINS } from '@/data/SolanaData' import { ELROND_MAINNET_CHAINS, ELROND_TEST_CHAINS } from '@/data/ElrondData' +import { TRON_MAINNET_CHAINS, TRON_TEST_CHAINS } from '@/data/TronData' import { formatChainName } from '@/utils/HelperUtil' import { Col, Row, Text } from '@nextui-org/react' import { SessionTypes } from '@walletconnect/types' @@ -16,11 +17,13 @@ const CHAIN_METADATA = { ...COSMOS_MAINNET_CHAINS, ...SOLANA_MAINNET_CHAINS, ...ELROND_MAINNET_CHAINS, + ...TRON_MAINNET_CHAINS, ...EIP155_MAINNET_CHAINS, ...EIP155_TEST_CHAINS, ...SOLANA_TEST_CHAINS, ...NEAR_TEST_CHAINS, - ...ELROND_TEST_CHAINS + ...ELROND_TEST_CHAINS, + ...TRON_TEST_CHAINS } /** diff --git a/wallets/react-wallet-v2/src/components/SessionProposalChainCard.tsx b/wallets/react-wallet-v2/src/components/SessionProposalChainCard.tsx index f5944e6..bfda74e 100644 --- a/wallets/react-wallet-v2/src/components/SessionProposalChainCard.tsx +++ b/wallets/react-wallet-v2/src/components/SessionProposalChainCard.tsx @@ -4,6 +4,7 @@ import { EIP155_MAINNET_CHAINS, EIP155_TEST_CHAINS } from '@/data/EIP155Data' import { NEAR_TEST_CHAINS } from '@/data/NEARData' import { SOLANA_MAINNET_CHAINS, SOLANA_TEST_CHAINS } from '@/data/SolanaData' import { ELROND_MAINNET_CHAINS, ELROND_TEST_CHAINS } from '@/data/ElrondData' +import { TRON_MAINNET_CHAINS, TRON_TEST_CHAINS } from '@/data/TronData' import { formatChainName } from '@/utils/HelperUtil' import { Col, Row, Text } from '@nextui-org/react' import { ProposalTypes } from '@walletconnect/types' @@ -16,11 +17,13 @@ const CHAIN_METADATA = { ...COSMOS_MAINNET_CHAINS, ...SOLANA_MAINNET_CHAINS, ...ELROND_MAINNET_CHAINS, + ...TRON_MAINNET_CHAINS, ...EIP155_MAINNET_CHAINS, ...EIP155_TEST_CHAINS, ...SOLANA_TEST_CHAINS, ...NEAR_TEST_CHAINS, - ...ELROND_TEST_CHAINS + ...ELROND_TEST_CHAINS, + ...TRON_TEST_CHAINS } /** diff --git a/wallets/react-wallet-v2/src/data/TronData.ts b/wallets/react-wallet-v2/src/data/TronData.ts new file mode 100644 index 0000000..26611c6 --- /dev/null +++ b/wallets/react-wallet-v2/src/data/TronData.ts @@ -0,0 +1,49 @@ +/** + * Types + */ +export type TTronChain = keyof typeof TRON_MAINNET_CHAINS + +interface TRONChains { + [key: string]: ChainMetadata +} + +type ChainMetadata = { + chainId: string + name: string + logo: string + rgb: string + fullNode: string +} + +/** + * Chains + */ +export const TRON_MAINNET_CHAINS: TRONChains = { + 'tron:0x2b6653dc': { + chainId: '0x2b6653dc', + name: 'Tron', + logo: 'https://tronscan.io/static/media/TRON.4a760cebd163969b2ee874abf2415e9a.svg', + rgb: '183, 62, 49', + fullNode: 'https://api.trongrid.io' + } +} + +export const TRON_TEST_CHAINS: TRONChains = { + 'tron:0xcd8690dc': { + chainId: '0xcd8690dc', + name: 'Tron Testnet', + logo: 'https://tronscan.io/static/media/TRON.4a760cebd163969b2ee874abf2415e9a.svg', + rgb: '183, 62, 49', + fullNode: 'https://nile.trongrid.io/' + } +} + +export const TRON_CHAINS = { ...TRON_MAINNET_CHAINS, ...TRON_TEST_CHAINS } + +/** + * Methods + */ +export const TRON_SIGNING_METHODS = { + TRON_SIGN_TRANSACTION: 'tron_signTransaction', + TRON_SIGN_MESSAGE: 'tron_signMessage' +} diff --git a/wallets/react-wallet-v2/src/hooks/useInitialization.ts b/wallets/react-wallet-v2/src/hooks/useInitialization.ts index 2635a7c..6798f06 100644 --- a/wallets/react-wallet-v2/src/hooks/useInitialization.ts +++ b/wallets/react-wallet-v2/src/hooks/useInitialization.ts @@ -4,6 +4,7 @@ import { createOrRestoreEIP155Wallet } from '@/utils/EIP155WalletUtil' import { createOrRestoreSolanaWallet } from '@/utils/SolanaWalletUtil' import { createOrRestorePolkadotWallet } from '@/utils/PolkadotWalletUtil' import { createOrRestoreElrondWallet } from '@/utils/ElrondWalletUtil' +import { createOrRestoreTronWallet } from '@/utils/TronWalletUtil' import { createSignClient } from '@/utils/WalletConnectUtil' import { useCallback, useEffect, useRef, useState } from 'react' import { useSnapshot } from 'valtio' @@ -23,6 +24,7 @@ export default function useInitialization() { const { polkadotAddresses } = await createOrRestorePolkadotWallet() const { nearAddresses } = await createOrRestoreNearWallet() const { elrondAddresses } = await createOrRestoreElrondWallet() + const { tronAddresses } = await createOrRestoreTronWallet() SettingsStore.setEIP155Address(eip155Addresses[0]) SettingsStore.setCosmosAddress(cosmosAddresses[0]) @@ -30,7 +32,7 @@ export default function useInitialization() { SettingsStore.setPolkadotAddress(polkadotAddresses[0]) SettingsStore.setNearAddress(nearAddresses[0]) SettingsStore.setElrondAddress(elrondAddresses[0]) - + SettingsStore.setTronAddress(tronAddresses[0]) await createSignClient(relayerRegionURL) prevRelayerURLValue.current = relayerRegionURL diff --git a/wallets/react-wallet-v2/src/hooks/useWalletConnectEventsManager.ts b/wallets/react-wallet-v2/src/hooks/useWalletConnectEventsManager.ts index 29db251..ca95c62 100644 --- a/wallets/react-wallet-v2/src/hooks/useWalletConnectEventsManager.ts +++ b/wallets/react-wallet-v2/src/hooks/useWalletConnectEventsManager.ts @@ -3,6 +3,7 @@ import { EIP155_SIGNING_METHODS } from '@/data/EIP155Data' import { SOLANA_SIGNING_METHODS } from '@/data/SolanaData' import { POLKADOT_SIGNING_METHODS } from '@/data/PolkadotData' import { ELROND_SIGNING_METHODS } from '@/data/ElrondData' +import { TRON_SIGNING_METHODS } from '@/data/TronData' import ModalStore from '@/store/ModalStore' import { signClient } from '@/utils/WalletConnectUtil' import { SignClientTypes } from '@walletconnect/types' @@ -77,6 +78,9 @@ export default function useWalletConnectEventsManager(initialized: boolean) { topic, response: await approveNearRequest(requestEvent) }) + case TRON_SIGNING_METHODS.TRON_SIGN_MESSAGE: + case TRON_SIGNING_METHODS.TRON_SIGN_TRANSACTION: + return ModalStore.open('SessionSignTronModal', { requestEvent, requestSession }) default: return ModalStore.open('SessionUnsuportedMethodModal', { requestEvent, requestSession }) } diff --git a/wallets/react-wallet-v2/src/lib/TronLib.ts b/wallets/react-wallet-v2/src/lib/TronLib.ts new file mode 100644 index 0000000..628901a --- /dev/null +++ b/wallets/react-wallet-v2/src/lib/TronLib.ts @@ -0,0 +1,58 @@ +// @ts-ignore +import TronWeb from 'tronweb' + +/** + * Types + */ +interface IInitArguments { + privateKey: string +} + +/** + * Library + */ +export default class TronLib { + privateKey: string + tronWeb: any + + constructor(privateKey: string) { + this.privateKey = privateKey + this.tronWeb = new TronWeb({ + // Nile TestNet, if you want to use in MainNet, change the fullHost to 'https://api.trongrid.io', or use tronWeb.setFullNode + fullHost: 'https://nile.trongrid.io/', + privateKey: privateKey + }) + } + + static async init({ privateKey }: IInitArguments) { + if(!privateKey){ + const account = TronWeb.utils.accounts.generateAccount(); + return new TronLib(account.privateKey) + } else { + return new TronLib(privateKey) + } + + } + + public getAddress() { + return this.tronWeb.defaultAddress.base58 + } + + public createAccount() { + return this.tronWeb.createAccount() + } + + public setFullNode(node: string) { + return this.tronWeb.setFullNode(node) + } + + public async signMessage(message: string) { + const signedtxn = await this.tronWeb.trx.signMessageV2(message) + return signedtxn + } + + public async signTransaction(transaction: any) { + const signedtxn = await this.tronWeb.trx.sign(transaction.transaction) + return signedtxn + } +} diff --git a/wallets/react-wallet-v2/src/pages/index.tsx b/wallets/react-wallet-v2/src/pages/index.tsx index a773558..ca85fea 100644 --- a/wallets/react-wallet-v2/src/pages/index.tsx +++ b/wallets/react-wallet-v2/src/pages/index.tsx @@ -6,6 +6,7 @@ import { EIP155_MAINNET_CHAINS, EIP155_TEST_CHAINS } from '@/data/EIP155Data' import { SOLANA_MAINNET_CHAINS, SOLANA_TEST_CHAINS } from '@/data/SolanaData' import { POLKADOT_MAINNET_CHAINS, POLKADOT_TEST_CHAINS } from '@/data/PolkadotData' import { ELROND_MAINNET_CHAINS, ELROND_TEST_CHAINS } from '@/data/ElrondData' +import { TRON_MAINNET_CHAINS, TRON_TEST_CHAINS } from '@/data/TronData' import SettingsStore from '@/store/SettingsStore' import { Text } from '@nextui-org/react' import { Fragment } from 'react' @@ -20,7 +21,8 @@ export default function HomePage() { solanaAddress, polkadotAddress, nearAddress, - elrondAddress + elrondAddress, + tronAddress } = useSnapshot(SettingsStore.state) return ( @@ -46,6 +48,9 @@ export default function HomePage() { {Object.values(ELROND_MAINNET_CHAINS).map(({ name, logo, rgb }) => ( ))} + {Object.values(TRON_MAINNET_CHAINS).map(({ name, logo, rgb }) => ( + + ))} {testNets ? ( @@ -67,6 +72,9 @@ export default function HomePage() { {Object.values(ELROND_TEST_CHAINS).map(({ name, logo, rgb }) => ( ))} + {Object.values(TRON_TEST_CHAINS).map(({ name, logo, rgb }) => ( + + ))} ) : null} diff --git a/wallets/react-wallet-v2/src/pages/settings.tsx b/wallets/react-wallet-v2/src/pages/settings.tsx index b983196..2e514e8 100644 --- a/wallets/react-wallet-v2/src/pages/settings.tsx +++ b/wallets/react-wallet-v2/src/pages/settings.tsx @@ -5,13 +5,14 @@ import { cosmosWallets } from '@/utils/CosmosWalletUtil' import { eip155Wallets } from '@/utils/EIP155WalletUtil' import { solanaWallets } from '@/utils/SolanaWalletUtil' import { elrondWallets } from '@/utils/ElrondWalletUtil' +import { tronWallets } from '@/utils/TronWalletUtil' import { Card, Divider, Row, Switch, Text } from '@nextui-org/react' import { Fragment } from 'react' import { useSnapshot } from 'valtio' import packageJSON from '../../package.json' export default function SettingsPage() { - const { testNets, eip155Address, cosmosAddress, solanaAddress, elrondAddress } = useSnapshot( + const { testNets, eip155Address, cosmosAddress, solanaAddress, elrondAddress, tronAddress } = useSnapshot( SettingsStore.state ) @@ -88,6 +89,13 @@ export default function SettingsPage() { {elrondWallets[elrondAddress].getMnemonic()} + + + Tron Private Key + + + {tronWallets[tronAddress].privateKey} + ) } diff --git a/wallets/react-wallet-v2/src/store/ModalStore.ts b/wallets/react-wallet-v2/src/store/ModalStore.ts index f40abaa..826b962 100644 --- a/wallets/react-wallet-v2/src/store/ModalStore.ts +++ b/wallets/react-wallet-v2/src/store/ModalStore.ts @@ -30,6 +30,7 @@ interface State { | 'SessionSignPolkadotModal' | 'SessionSignNearModal' | 'SessionSignElrondModal' + | 'SessionSignTronModal' | 'LegacySessionProposalModal' | 'LegacySessionSignModal' | 'LegacySessionSignTypedDataModal' diff --git a/wallets/react-wallet-v2/src/store/SettingsStore.ts b/wallets/react-wallet-v2/src/store/SettingsStore.ts index 48b6217..a968268 100644 --- a/wallets/react-wallet-v2/src/store/SettingsStore.ts +++ b/wallets/react-wallet-v2/src/store/SettingsStore.ts @@ -12,6 +12,7 @@ interface State { polkadotAddress: string nearAddress: string elrondAddress: string + tronAddress: string relayerRegionURL: string } @@ -27,6 +28,7 @@ const state = proxy({ polkadotAddress: '', nearAddress: '', elrondAddress: '', + tronAddress: '', relayerRegionURL: '' }) @@ -66,6 +68,10 @@ const SettingsStore = { state.elrondAddress = elrondAddress }, + setTronAddress(tronAddress: string) { + state.tronAddress = tronAddress + }, + toggleTestNets() { state.testNets = !state.testNets if (state.testNets) { diff --git a/wallets/react-wallet-v2/src/utils/HelperUtil.ts b/wallets/react-wallet-v2/src/utils/HelperUtil.ts index 9472d2a..cbf03ec 100644 --- a/wallets/react-wallet-v2/src/utils/HelperUtil.ts +++ b/wallets/react-wallet-v2/src/utils/HelperUtil.ts @@ -3,6 +3,7 @@ import { EIP155_CHAINS, TEIP155Chain } from '@/data/EIP155Data' import { NEAR_TEST_CHAINS, TNearChain } from '@/data/NEARData' import { SOLANA_CHAINS, TSolanaChain } from '@/data/SolanaData' import { ELROND_CHAINS, TElrondChain } from '@/data/ElrondData' +import { TRON_CHAINS, TTronChain } from '@/data/TronData' import { utils } from 'ethers' /** @@ -117,6 +118,13 @@ export function isElrondChain(chain: string) { return chain.includes('elrond') } +/** + * Check if chain is part of TRON standard + */ + export function isTronChain(chain: string) { + return chain.includes('tron') +} + /** * Formats chainId to its name */ @@ -127,6 +135,7 @@ export function formatChainName(chainId: string) { SOLANA_CHAINS[chainId as TSolanaChain]?.name ?? NEAR_TEST_CHAINS[chainId as TNearChain]?.name ?? ELROND_CHAINS[chainId as TElrondChain]?.name ?? + TRON_CHAINS[chainId as TTronChain]?.name ?? chainId ) } diff --git a/wallets/react-wallet-v2/src/utils/TronRequestHandlerUtil.ts b/wallets/react-wallet-v2/src/utils/TronRequestHandlerUtil.ts new file mode 100644 index 0000000..db5d39b --- /dev/null +++ b/wallets/react-wallet-v2/src/utils/TronRequestHandlerUtil.ts @@ -0,0 +1,49 @@ +import {TRON_MAINNET_CHAINS, TRON_TEST_CHAINS, TRON_SIGNING_METHODS} from '@/data/TronData' +import { getWalletAddressFromParams } from '@/utils/HelperUtil' +import { tronAddresses, tronWallets } from '@/utils/TronWalletUtil' +import { formatJsonRpcError, formatJsonRpcResult } from '@json-rpc-tools/utils' +import { SignClientTypes } from '@walletconnect/types' +import { getSdkError } from '@walletconnect/utils' + +export async function approveTronRequest( + requestEvent: SignClientTypes.EventArguments['session_request'] +) { + const { params, id } = requestEvent + const { request } = params + + const wallet = tronWallets[getWalletAddressFromParams(tronAddresses, params)] + + if(TRON_MAINNET_CHAINS[params.chainId]){ + wallet.setFullNode(TRON_MAINNET_CHAINS[params.chainId].fullNode) + } else if(TRON_TEST_CHAINS[params.chainId]){ + wallet.setFullNode(TRON_TEST_CHAINS[params.chainId].fullNode) + } else { + throw new Error('Invalid chain id') + } + + + switch (request.method) { + case TRON_SIGNING_METHODS.TRON_SIGN_MESSAGE: + const signedMessage = await wallet.signMessage(request.params.message) + const res = { + signature: signedMessage + } + return formatJsonRpcResult(id, res) + + case TRON_SIGNING_METHODS.TRON_SIGN_TRANSACTION: + const signedTransaction = await wallet.signTransaction(request.params.transaction) + const resData = { + result: signedTransaction + } + return formatJsonRpcResult(id, resData) + + default: + throw new Error(getSdkError('INVALID_METHOD').message) + } +} + +export function rejectTronRequest(request: SignClientTypes.EventArguments['session_request']) { + const { id } = request + + return formatJsonRpcError(id, getSdkError('USER_REJECTED_METHODS').message) +} diff --git a/wallets/react-wallet-v2/src/utils/TronWalletUtil.ts b/wallets/react-wallet-v2/src/utils/TronWalletUtil.ts new file mode 100644 index 0000000..7c96b63 --- /dev/null +++ b/wallets/react-wallet-v2/src/utils/TronWalletUtil.ts @@ -0,0 +1,44 @@ +import TronLib from '@/lib/TronLib' + +export let tronWeb1: TronLib +export let tronWeb2: TronLib +export let tronWallets: Record +export let tronAddresses: string[] + +let address1: string +let address2: string + +/** + * Utilities + */ +export async function createOrRestoreTronWallet() { + const privateKey1 = localStorage.getItem('TRON_PrivateKey_1') + const privateKey2 = localStorage.getItem('TRON_PrivateKey_2') + + if (privateKey1 && privateKey2) { + tronWeb1 = await TronLib.init({ privateKey: privateKey1 }) + tronWeb2 = await TronLib.init({ privateKey: privateKey2 }) + } else { + tronWeb1 = await TronLib.init({ privateKey: '' }) + tronWeb2 = await TronLib.init({ privateKey: '' }) + + // Don't store privateKey in local storage in a production project! + localStorage.setItem('TRON_PrivateKey_1', tronWeb1.privateKey) + localStorage.setItem('TRON_PrivateKey_2', tronWeb2.privateKey) + } + + address1 = tronWeb1.getAddress() + address2 = tronWeb2.getAddress() + + tronWallets = { + [address1]: tronWeb1, + [address2]: tronWeb2 + } + + tronAddresses = Object.keys(tronWallets) + + return { + tronWallets, + tronAddresses + } +} diff --git a/wallets/react-wallet-v2/src/views/SessionProposalModal.tsx b/wallets/react-wallet-v2/src/views/SessionProposalModal.tsx index 7f3439d..d80bf40 100644 --- a/wallets/react-wallet-v2/src/views/SessionProposalModal.tsx +++ b/wallets/react-wallet-v2/src/views/SessionProposalModal.tsx @@ -7,13 +7,15 @@ import { cosmosAddresses } from '@/utils/CosmosWalletUtil' import { eip155Addresses } from '@/utils/EIP155WalletUtil' import { polkadotAddresses } from '@/utils/PolkadotWalletUtil' import { elrondAddresses } from '@/utils/ElrondWalletUtil' +import { tronAddresses } from '@/utils/TronWalletUtil' import { isCosmosChain, isEIP155Chain, isSolanaChain, isPolkadotChain, isNearChain, - isElrondChain + isElrondChain, + isTronChain } from '@/utils/HelperUtil' import { solanaAddresses } from '@/utils/SolanaWalletUtil' import { signClient } from '@/utils/WalletConnectUtil' @@ -149,6 +151,15 @@ export default function SessionProposalModal() { chain={chain} /> ) + } else if (isTronChain(chain)) { + return ( + + ) } } diff --git a/wallets/react-wallet-v2/src/views/SessionSignTronModal.tsx b/wallets/react-wallet-v2/src/views/SessionSignTronModal.tsx new file mode 100644 index 0000000..78777ee --- /dev/null +++ b/wallets/react-wallet-v2/src/views/SessionSignTronModal.tsx @@ -0,0 +1,72 @@ +import ProjectInfoCard from '@/components/ProjectInfoCard' +import RequestDataCard from '@/components/RequestDataCard' +import RequesDetailsCard from '@/components/RequestDetalilsCard' +import RequestMethodCard from '@/components/RequestMethodCard' +import RequestModalContainer from '@/components/RequestModalContainer' +import ModalStore from '@/store/ModalStore' +import { approveTronRequest, rejectTronRequest } from '@/utils/TronRequestHandlerUtil' +import { signClient } from '@/utils/WalletConnectUtil' +import { Button, Divider, Modal, Text } from '@nextui-org/react' +import { Fragment } from 'react' + +export default function SessionSignTronModal() { + // Get request and wallet data from store + const requestEvent = ModalStore.state.data?.requestEvent + const requestSession = ModalStore.state.data?.requestSession + + // Ensure request and wallet are defined + if (!requestEvent || !requestSession) { + return Missing request data + } + + // Get required request data + const { topic, params } = requestEvent + const { request, chainId } = params + + // Handle approve action (logic varies based on request method) + async function onApprove() { + if (requestEvent) { + const response = await approveTronRequest(requestEvent) + await signClient.respond({ + topic, + response + }) + ModalStore.close() + } + } + + // Handle reject action + async function onReject() { + if (requestEvent) { + const response = rejectTronRequest(requestEvent) + await signClient.respond({ + topic, + response + }) + ModalStore.close() + } + } + + return ( + + + + + + + + + + + + + + + + + ) +}