From cbc0c579d5fa244f993888b13923ac9a3ad4d514 Mon Sep 17 00:00:00 2001 From: llawliet-l-l Date: Wed, 31 Dec 2025 08:18:44 +0000 Subject: [PATCH 1/7] feat: migrated tonconnect to the hub # Conflicts: # wallets/core/package.json # wallets/core/src/hub/provider/types.ts # wallets/react/src/hub/helpers.ts # wallets/readme.md # Conflicts: # wallets/provider-all/src/index.ts # wallets/readme.md --- wallets/core/namespaces/ton/package.json | 8 ++ wallets/core/package.json | 6 +- wallets/core/src/hub/provider/types.ts | 2 + wallets/core/src/namespaces/ton/builders.ts | 15 +++ wallets/core/src/namespaces/ton/constants.ts | 2 + wallets/core/src/namespaces/ton/mod.ts | 5 + wallets/core/src/namespaces/ton/types.ts | 19 ++++ wallets/core/src/namespaces/ton/utils.ts | 18 ++++ wallets/provider-all/src/index.ts | 9 +- wallets/provider-tonconnect/package.json | 15 ++- wallets/provider-tonconnect/readme.md | 28 +++++- .../provider-tonconnect/src/actions/ton.ts | 54 +++++++++++ .../provider-tonconnect/src/builders/ton.ts | 37 +++++++ wallets/provider-tonconnect/src/constants.ts | 38 ++++++++ wallets/provider-tonconnect/src/helpers.ts | 53 ---------- wallets/provider-tonconnect/src/index.ts | 97 ------------------- wallets/provider-tonconnect/src/mod.ts | 15 +++ .../provider-tonconnect/src/namespaces/ton.ts | 40 ++++++++ wallets/provider-tonconnect/src/provider.ts | 32 ++++++ wallets/provider-tonconnect/src/signer.ts | 15 ++- .../src/{ton-signer.ts => signers/ton.ts} | 0 wallets/provider-tonconnect/src/types.ts | 19 +++- wallets/provider-tonconnect/src/utils.ts | 83 ++++++++++++++++ wallets/readme.md | 2 + 24 files changed, 440 insertions(+), 172 deletions(-) create mode 100644 wallets/core/namespaces/ton/package.json create mode 100644 wallets/core/src/namespaces/ton/builders.ts create mode 100644 wallets/core/src/namespaces/ton/constants.ts create mode 100644 wallets/core/src/namespaces/ton/mod.ts create mode 100644 wallets/core/src/namespaces/ton/types.ts create mode 100644 wallets/core/src/namespaces/ton/utils.ts create mode 100644 wallets/provider-tonconnect/src/actions/ton.ts create mode 100644 wallets/provider-tonconnect/src/builders/ton.ts create mode 100644 wallets/provider-tonconnect/src/constants.ts delete mode 100644 wallets/provider-tonconnect/src/helpers.ts delete mode 100644 wallets/provider-tonconnect/src/index.ts create mode 100644 wallets/provider-tonconnect/src/mod.ts create mode 100644 wallets/provider-tonconnect/src/namespaces/ton.ts create mode 100644 wallets/provider-tonconnect/src/provider.ts rename wallets/provider-tonconnect/src/{ton-signer.ts => signers/ton.ts} (100%) create mode 100644 wallets/provider-tonconnect/src/utils.ts diff --git a/wallets/core/namespaces/ton/package.json b/wallets/core/namespaces/ton/package.json new file mode 100644 index 0000000000..f50e75a3cf --- /dev/null +++ b/wallets/core/namespaces/ton/package.json @@ -0,0 +1,8 @@ +{ + "name": "@rango-dev/wallets-core/namespaces/ton", + "type": "module", + "main": "../../dist/namespaces/ton/mod.js", + "module": "../../dist/namespaces/ton/mod.js", + "types": "../../dist/namespaces/ton/mod.d.ts", + "sideEffects": false +} diff --git a/wallets/core/package.json b/wallets/core/package.json index 66418a6de2..7e44707ac3 100644 --- a/wallets/core/package.json +++ b/wallets/core/package.json @@ -58,6 +58,10 @@ "./namespaces/xrpl": { "types": "./dist/namespaces/xrpl/mod.d.ts", "default": "./dist/namespaces/xrpl/mod.js" + }, + "./namespaces/ton": { + "types": "./dist/namespaces/ton/mod.d.ts", + "default": "./dist/namespaces/ton/mod.js" } }, "files": [ @@ -66,7 +70,7 @@ "legacy" ], "scripts": { - "build": "node ../../scripts/build/command.mjs --path wallets/core --inputs src/mod.ts,src/utils/mod.ts,src/legacy/mod.ts,src/hub/store/mod.ts,src/namespaces/evm/mod.ts,src/namespaces/solana/mod.ts,src/namespaces/cosmos/mod.ts,src/namespaces/utxo/mod.ts,src/namespaces/sui/mod.ts,src/namespaces/tron/mod.ts,src/namespaces/starknet/mod.ts,src/namespaces/xrpl/mod.ts,src/namespaces/common/mod.ts", + "build": "node ../../scripts/build/command.mjs --path wallets/core --inputs src/mod.ts,src/utils/mod.ts,src/legacy/mod.ts,src/hub/store/mod.ts,src/namespaces/evm/mod.ts,src/namespaces/solana/mod.ts,src/namespaces/cosmos/mod.ts,src/namespaces/utxo/mod.ts,src/namespaces/sui/mod.ts,src/namespaces/tron/mod.ts,src/namespaces/starknet/mod.ts,src/namespaces/xrpl/mod.ts,src/namespaces/common/mod.ts,src/namespaces/ton/mod.ts", "ts-check": "tsc --declaration --emitDeclarationOnly -p ./tsconfig.json", "clean": "rimraf dist", "format": "prettier --write '{.,src}/**/*.{ts,tsx}'", diff --git a/wallets/core/src/hub/provider/types.ts b/wallets/core/src/hub/provider/types.ts index e77f3ed52a..5e375f9234 100644 --- a/wallets/core/src/hub/provider/types.ts +++ b/wallets/core/src/hub/provider/types.ts @@ -6,6 +6,7 @@ import type { EvmActions } from '../../namespaces/evm/mod.js'; import type { SolanaActions } from '../../namespaces/solana/mod.js'; import type { StarknetActions } from '../../namespaces/starknet/types.js'; import type { SuiActions } from '../../namespaces/sui/mod.js'; +import type { TonActions } from '../../namespaces/ton/types.js'; import type { TronActions } from '../../namespaces/tron/types.js'; import type { UtxoActions } from '../../namespaces/utxo/mod.js'; import type { XRPLActions } from '../../namespaces/xrpl/mod.js'; @@ -38,6 +39,7 @@ export interface CommonNamespaces { tron: TronActions; starknet: StarknetActions; xrpl: XRPLActions; + ton: TonActions; } export type CommonNamespaceKeys = Prettify; diff --git a/wallets/core/src/namespaces/ton/builders.ts b/wallets/core/src/namespaces/ton/builders.ts new file mode 100644 index 0000000000..c6bb25ecba --- /dev/null +++ b/wallets/core/src/namespaces/ton/builders.ts @@ -0,0 +1,15 @@ +import type { TonActions } from './types.js'; + +import { ActionBuilder } from '../../builders/action.js'; +import { intoConnectionFinished } from '../common/after.js'; +import { connectAndUpdateStateForSingleNetwork } from '../common/and.js'; +import { intoConnecting } from '../common/before.js'; + +export const connect = () => + new ActionBuilder('connect') + .and(connectAndUpdateStateForSingleNetwork) + .before(intoConnecting) + .after(intoConnectionFinished); + +export const canEagerConnect = () => + new ActionBuilder('canEagerConnect'); diff --git a/wallets/core/src/namespaces/ton/constants.ts b/wallets/core/src/namespaces/ton/constants.ts new file mode 100644 index 0000000000..18120bdcba --- /dev/null +++ b/wallets/core/src/namespaces/ton/constants.ts @@ -0,0 +1,2 @@ +export const CAIP_NAMESPACE = 'ton'; +export const CAIP_TON_CHAIN_ID = '-239'; diff --git a/wallets/core/src/namespaces/ton/mod.ts b/wallets/core/src/namespaces/ton/mod.ts new file mode 100644 index 0000000000..e70919c661 --- /dev/null +++ b/wallets/core/src/namespaces/ton/mod.ts @@ -0,0 +1,5 @@ +export * as utils from './utils.js'; +export * as builders from './builders.js'; + +export type { ProviderAPI, TonActions } from './types.js'; +export { CAIP_NAMESPACE, CAIP_TON_CHAIN_ID } from './constants.js'; diff --git a/wallets/core/src/namespaces/ton/types.ts b/wallets/core/src/namespaces/ton/types.ts new file mode 100644 index 0000000000..8df2f74b50 --- /dev/null +++ b/wallets/core/src/namespaces/ton/types.ts @@ -0,0 +1,19 @@ +import type { Accounts } from '../../types/accounts.js'; +import type { + AutoImplementedActionsByRecommended, + CommonActions, +} from '../common/types.js'; + +export interface TonActions + extends AutoImplementedActionsByRecommended, + CommonActions { + connect: (options?: ConnectOptions) => Promise; + canEagerConnect: () => Promise; +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export type ProviderAPI = Record; + +export type ConnectOptions = { + derivationPath?: string; +}; diff --git a/wallets/core/src/namespaces/ton/utils.ts b/wallets/core/src/namespaces/ton/utils.ts new file mode 100644 index 0000000000..33802bba83 --- /dev/null +++ b/wallets/core/src/namespaces/ton/utils.ts @@ -0,0 +1,18 @@ +import type { CaipAccount } from '../../types/accounts.js'; + +import { AccountId } from 'caip'; + +import { CAIP_NAMESPACE, CAIP_TON_CHAIN_ID } from './constants.js'; + +export function formatAccountsToCAIP(accounts: string[]) { + return accounts.map( + (account) => + AccountId.format({ + address: account.toString(), + chainId: { + namespace: CAIP_NAMESPACE, + reference: CAIP_TON_CHAIN_ID, + }, + }) as CaipAccount + ); +} diff --git a/wallets/provider-all/src/index.ts b/wallets/provider-all/src/index.ts index 56a5819569..33ce90879c 100644 --- a/wallets/provider-all/src/index.ts +++ b/wallets/provider-all/src/index.ts @@ -30,7 +30,10 @@ import { versions as solflare } from '@rango-dev/provider-solflare'; import { versions as taho } from '@rango-dev/provider-taho'; import { versions as tokenPocket } from '@rango-dev/provider-tokenpocket'; import { versions as tomo } from '@rango-dev/provider-tomo'; -import * as tonconnect from '@rango-dev/provider-tonconnect'; +import { + versions as tonconnect, + init as tonconnectInit, +} from '@rango-dev/provider-tonconnect'; import * as trezor from '@rango-dev/provider-trezor'; import { versions as tronLink } from '@rango-dev/provider-tron-link'; import { versions as trustwallet } from '@rango-dev/provider-trustwallet'; @@ -91,7 +94,7 @@ export const allProviders = ( }) ) { if (!!options?.tonConnect?.manifestUrl) { - tonconnect.init(options.tonConnect); + tonconnectInit(options.tonConnect); } } @@ -100,7 +103,7 @@ export const allProviders = ( lazyProvider(legacyProviderImportsToVersionsInterface(defaultInjected)), metamask, lazyProvider(legacyProviderImportsToVersionsInterface(walletconnect2)), - lazyProvider(legacyProviderImportsToVersionsInterface(tonconnect)), + tonconnect, keplr, phantom, ready, diff --git a/wallets/provider-tonconnect/package.json b/wallets/provider-tonconnect/package.json index a4dda814d9..98935fcfdf 100644 --- a/wallets/provider-tonconnect/package.json +++ b/wallets/provider-tonconnect/package.json @@ -3,21 +3,18 @@ "version": "0.20.1-next.1", "license": "MIT", "type": "module", - "source": "./src/index.ts", - "main": "./dist/index.js", + "source": "./src/mod.ts", + "main": "./dist/mod.js", "exports": { - ".": { - "types": "./dist/index.d.ts", - "default": "./dist/index.js" - } + ".": "./dist/mod.js" }, - "typings": "dist/index.d.ts", + "typings": "dist/mod.d.ts", "files": [ "dist", "src" ], "scripts": { - "build": "node ../../scripts/build/command.mjs --path wallets/provider-tonconnect", + "build": "node ../../scripts/build/command.mjs --path wallets/provider-tonconnect --inputs src/mod.ts", "ts-check": "tsc --declaration --emitDeclarationOnly -p ./tsconfig.json", "clean": "rimraf dist", "format": "prettier --write '{.,src}/**/*.{ts,tsx}'", @@ -33,4 +30,4 @@ "publishConfig": { "access": "public" } -} \ No newline at end of file +} diff --git a/wallets/provider-tonconnect/readme.md b/wallets/provider-tonconnect/readme.md index 5da1c55d43..3e42b44a16 100644 --- a/wallets/provider-tonconnect/readme.md +++ b/wallets/provider-tonconnect/readme.md @@ -1,3 +1,29 @@ # @rango-dev/provider-tonconnect +TonConnect Wallet integration for hub. +[Homepage](https://ton.org/) | [Docs](https://docs.ton.org/ecosystem/ton-connect/overview) -TonConnect \ No newline at end of file +More about implementation status can be found [here](../readme.md). + +## Implementation notes/limitations + +#### ⚠️ Initialization +You should call the `init` function with TonConnect configs before trying to use the wallet + +### Feature + +#### ❌ Switch Account + +Ton wallets don't emit account change events: https://github.com/ton-blockchain/ton-connect/blob/main/wallet-guidelines.md + +#### ⚠️ Disconnect + +Some Ton wallets like **Tonkeeper** don't emit disconnect events + +#### ⚠️ Init + +We set **'installed'** to `true` on initialization even if we can't initialize the TonConnect instance. +Instead, we throw an error when the user tries to connect and we haven't initialized the TonConnect instance. + +--- + +More wallet information can be found in [readme.md](../readme.md). \ No newline at end of file diff --git a/wallets/provider-tonconnect/src/actions/ton.ts b/wallets/provider-tonconnect/src/actions/ton.ts new file mode 100644 index 0000000000..416602279b --- /dev/null +++ b/wallets/provider-tonconnect/src/actions/ton.ts @@ -0,0 +1,54 @@ +import type { Context, FunctionWithContext } from '@rango-dev/wallets-core'; +import type { TonConnectUI } from '@tonconnect/ui'; + +import { actions as commonActions } from '@rango-dev/wallets-core/namespaces/common'; +import { + type TonActions, + type ProviderAPI as TonProviderAPI, + utils, +} from '@rango-dev/wallets-core/namespaces/ton'; + +import { getTonConnectUIModule, waitForConnection } from '../utils.js'; + +export function connect( + getInstance: () => TonConnectUI +): FunctionWithContext { + return async () => { + const tonInstance = getInstance(); + const connectionRestored = await tonInstance.connectionRestored; + const { toUserFriendlyAddress } = await getTonConnectUIModule(); + let userFriendlyAddress: string; + + if (connectionRestored && tonInstance.account?.address) { + userFriendlyAddress = toUserFriendlyAddress(tonInstance.account.address); + } else { + await tonInstance.openModal(); + const result = await waitForConnection(tonInstance); + userFriendlyAddress = toUserFriendlyAddress(result); + } + + return utils.formatAccountsToCAIP([userFriendlyAddress]); + }; +} + +export function disconnect( + getInstance: () => TonProviderAPI +): FunctionWithContext { + return async (context) => { + const tonInstance = getInstance(); + await tonInstance.disconnect(); + commonActions.disconnect(context); + }; +} + +export function canEagerConnect( + getInstance: () => TonProviderAPI +): FunctionWithContext { + return async () => { + const tonConnectUI = getInstance() as TonConnectUI; + const connectionRestored = await tonConnectUI.connectionRestored; + return connectionRestored; + }; +} + +export const tonActions = { connect, disconnect, canEagerConnect }; diff --git a/wallets/provider-tonconnect/src/builders/ton.ts b/wallets/provider-tonconnect/src/builders/ton.ts new file mode 100644 index 0000000000..79afdaa8d2 --- /dev/null +++ b/wallets/provider-tonconnect/src/builders/ton.ts @@ -0,0 +1,37 @@ +import type { ConnectedWallet, TonConnectUI } from '@tonconnect/ui'; + +import { + ChangeAccountSubscriberBuilder, + actions as commonActions, +} from '@rango-dev/wallets-core/namespaces/common'; +import { type TonActions } from '@rango-dev/wallets-core/namespaces/ton'; + +const changeAccountSubscriber = (getInstance: () => TonConnectUI) => + new ChangeAccountSubscriberBuilder< + ConnectedWallet | null, + TonConnectUI, + TonActions + >() + .getInstance(getInstance) + /* + * TON wallets don't implement account change events, and we can only listen to disconnect events + * by checking the payload value. + * More info: https://github.com/ton-blockchain/ton-connect/blob/main/wallet-guidelines.md + */ + .onSwitchAccount(async (event, context) => { + event.preventDefault(); + if (!event.payload) { + commonActions.disconnect(context); + } + }) + .format(async () => []) + .addEventListener((instance, callback) => { + return instance.onStatusChange(callback); + }) + .removeEventListener(() => { + /* + * We handle unsubscribing in the builder and by returning an unsubscribe function from 'addEventListener' + */ + }); + +export const tonBuilders = { changeAccountSubscriber }; diff --git a/wallets/provider-tonconnect/src/constants.ts b/wallets/provider-tonconnect/src/constants.ts new file mode 100644 index 0000000000..1d536f75d7 --- /dev/null +++ b/wallets/provider-tonconnect/src/constants.ts @@ -0,0 +1,38 @@ +import type { ProviderMetadata } from '@rango-dev/wallets-core'; + +import { WalletTypes } from '@rango-dev/wallets-shared'; +import { type BlockchainMeta, tonBlockchain } from 'rango-types'; + +import getSigners from './signer.js'; +import { getInstanceOrThrow } from './utils.js'; + +export const WALLET_ID = WalletTypes.TON_CONNECT; + +export const metadata: ProviderMetadata = { + name: 'TON Connect', + icon: 'https://raw.githubusercontent.com/rango-exchange/assets/7fb19ed5d5019b4d6a41ce91b39cde64f86af4c6/wallets/tonconnect/icon.svg', + extensions: {}, + properties: [ + { + name: 'namespaces', + value: { + selection: 'single', + data: [ + { + label: 'Ton', + value: 'Ton', + id: 'TON', + getSupportedChains: (allBlockchains: BlockchainMeta[]) => + tonBlockchain(allBlockchains), + }, + ], + }, + }, + { + name: 'signers', + value: { + getSigners: async () => getSigners(getInstanceOrThrow()), + }, + }, + ], +}; diff --git a/wallets/provider-tonconnect/src/helpers.ts b/wallets/provider-tonconnect/src/helpers.ts deleted file mode 100644 index f5e5913447..0000000000 --- a/wallets/provider-tonconnect/src/helpers.ts +++ /dev/null @@ -1,53 +0,0 @@ -import type { TonConnectUI } from '@tonconnect/ui'; - -import { dynamicImportWithRefinedError } from '@rango-dev/wallets-shared'; - -export async function getTonConnectUIModule() { - const tonConnectUI = await dynamicImportWithRefinedError( - async () => await import('@tonconnect/ui') - ); - return tonConnectUI; -} - -export async function getTonCoreModule() { - const tonCore = await dynamicImportWithRefinedError( - async () => await import('@ton/core') - ); - return tonCore; -} - -export async function waitForConnection( - tonConnectUI: TonConnectUI -): Promise { - return new Promise((resolve, reject) => { - const unsubscribeStatusChange = tonConnectUI.onStatusChange( - (state) => { - const walletConnected = !!state?.account.address; - - if (walletConnected) { - unsubscribeStatusChange(); - resolve(state.account.address); - } - }, - (error) => { - unsubscribeStatusChange(); - reject(error); - } - ); - - const unsubscribeModalStateChange = tonConnectUI.onModalStateChange( - (modalState) => { - if (modalState.closeReason === 'action-cancelled') { - unsubscribeStatusChange(); - unsubscribeModalStateChange(); - reject(new Error('The action was canceled by the user')); - } - } - ); - }); -} - -export async function parseAddress(rawAddress: string): Promise { - const tonCore = await getTonCoreModule(); - return tonCore.Address.parse(rawAddress).toString({ bounceable: false }); -} diff --git a/wallets/provider-tonconnect/src/index.ts b/wallets/provider-tonconnect/src/index.ts deleted file mode 100644 index 11726377a8..0000000000 --- a/wallets/provider-tonconnect/src/index.ts +++ /dev/null @@ -1,97 +0,0 @@ -import type { Environments } from './types.js'; -import type { - CanEagerConnect, - CanSwitchNetwork, - Connect, - Disconnect, - GetInstance, - WalletInfo, -} from '@rango-dev/wallets-shared'; -import type { TonConnectUI } from '@tonconnect/ui'; -import type { BlockchainMeta, SignerFactory } from 'rango-types'; - -import { Networks, WalletTypes } from '@rango-dev/wallets-shared'; -import { tonBlockchain } from 'rango-types'; - -import { - getTonConnectUIModule, - parseAddress, - waitForConnection, -} from './helpers.js'; -import signer from './signer.js'; - -let envs: Environments = { - manifestUrl: '', -}; - -const WALLET = WalletTypes.TON_CONNECT; - -export const config = { - type: WALLET, - isAsyncInstance: true, - checkInstallation: false, -}; - -export type { Environments }; - -export const init = (environments: Environments) => { - envs = environments; -}; - -let instance: TonConnectUI | null = null; - -export const getInstance: GetInstance = async () => { - if (!instance) { - const { TonConnectUI } = await getTonConnectUIModule(); - instance = new TonConnectUI(envs); - } - return instance; -}; - -export const connect: Connect = async ({ instance }) => { - const tonConnectUI: TonConnectUI = instance; - const connectionRestored = await tonConnectUI.connectionRestored; - - if (connectionRestored && tonConnectUI.account?.address) { - const parsedAddress = await parseAddress(tonConnectUI.account.address); - return { accounts: [parsedAddress], chainId: Networks.TON }; - } - - await tonConnectUI.openModal(); - const result = await waitForConnection(tonConnectUI); - const parsedAddress = await parseAddress(result); - - return { - accounts: [parsedAddress], - chainId: Networks.TON, - }; -}; - -export const canEagerConnect: CanEagerConnect = async ({ instance }) => { - const tonConnectUI = instance as TonConnectUI; - const connectionRestored = await tonConnectUI.connectionRestored; - return connectionRestored; -}; - -export const canSwitchNetworkTo: CanSwitchNetwork = () => false; - -export const getSigners: (provider: TonConnectUI) => Promise = - signer; - -export const getWalletInfo: (allBlockChains: BlockchainMeta[]) => WalletInfo = ( - allBlockChains -) => { - const ton = tonBlockchain(allBlockChains); - return { - name: 'TON Connect', - img: 'https://raw.githubusercontent.com/rango-exchange/assets/7fb19ed5d5019b4d6a41ce91b39cde64f86af4c6/wallets/tonconnect/icon.svg', - installLink: '', - color: '#fff', - supportedChains: ton, - }; -}; - -export const disconnect: Disconnect = async ({ instance }) => { - const tonConnectUI = instance as TonConnectUI; - await tonConnectUI.disconnect(); -}; diff --git a/wallets/provider-tonconnect/src/mod.ts b/wallets/provider-tonconnect/src/mod.ts new file mode 100644 index 0000000000..8dcdeee008 --- /dev/null +++ b/wallets/provider-tonconnect/src/mod.ts @@ -0,0 +1,15 @@ +import type { Environments } from './types.js'; + +import { defineVersions } from '@rango-dev/wallets-core/utils'; + +import { buildProvider } from './provider.js'; +import { setEnvs } from './utils.js'; + +export type { Environments } from './types.js'; + +const init = (environments: Environments) => setEnvs(environments); + +const versions = () => + defineVersions().version('1.0.0', buildProvider()).build(); + +export { versions, init }; diff --git a/wallets/provider-tonconnect/src/namespaces/ton.ts b/wallets/provider-tonconnect/src/namespaces/ton.ts new file mode 100644 index 0000000000..4ff757beab --- /dev/null +++ b/wallets/provider-tonconnect/src/namespaces/ton.ts @@ -0,0 +1,40 @@ +import type { TonActions } from '@rango-dev/wallets-core/namespaces/ton'; + +import { ActionBuilder, NamespaceBuilder } from '@rango-dev/wallets-core'; +import { standardizeAndThrowError } from '@rango-dev/wallets-core/namespaces/common'; +import { builders } from '@rango-dev/wallets-core/namespaces/ton'; + +import { tonActions } from '../actions/ton.js'; +import { tonBuilders } from '../builders/ton.js'; +import { WALLET_ID } from '../constants.js'; +import { tonConnect } from '../utils.js'; + +const [changeAccountSubscriber, changeAccountCleanup] = tonBuilders + .changeAccountSubscriber(tonConnect) + .build(); + +const connect = builders + .connect() + .action(tonActions.connect(tonConnect)) + .and(changeAccountSubscriber) + .or(changeAccountCleanup) + .or(standardizeAndThrowError) + .build(); + +const canEagerConnect = builders + .canEagerConnect() + .action(tonActions.canEagerConnect(tonConnect)) + .build(); + +const disconnect = new ActionBuilder('disconnect') + .action(tonActions.disconnect(tonConnect)) + .after(changeAccountCleanup) + .build(); + +const ton = new NamespaceBuilder('Ton', WALLET_ID) + .action(connect) + .action(disconnect) + .action(canEagerConnect) + .build(); + +export { ton }; diff --git a/wallets/provider-tonconnect/src/provider.ts b/wallets/provider-tonconnect/src/provider.ts new file mode 100644 index 0000000000..fb25acf089 --- /dev/null +++ b/wallets/provider-tonconnect/src/provider.ts @@ -0,0 +1,32 @@ +import { ProviderBuilder } from '@rango-dev/wallets-core'; + +import { metadata, WALLET_ID } from './constants.js'; +import { ton } from './namespaces/ton.js'; +import { initializeTonConnectInstance } from './utils.js'; + +const buildProvider = () => + new ProviderBuilder(WALLET_ID) + .init(async function (context) { + const [, setState] = context.state(); + async function initializeTon() { + try { + await initializeTonConnectInstance(); + console.debug('[ton] instance initialized.', context); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + } catch (_) { + /* empty */ + } finally { + /* + * we want to still show the TonConnect wallets + * but we'll throw an error that we couldn't initialize it when the users want to connect to it + */ + setState('installed', true); + } + } + void initializeTon(); + }) + .config('metadata', metadata) + .add('ton', ton) + .build(); + +export { buildProvider }; diff --git a/wallets/provider-tonconnect/src/signer.ts b/wallets/provider-tonconnect/src/signer.ts index b5a7c78f8d..60153b9cb0 100644 --- a/wallets/provider-tonconnect/src/signer.ts +++ b/wallets/provider-tonconnect/src/signer.ts @@ -1,16 +1,21 @@ -import type { TonConnectUI } from '@tonconnect/ui'; +import type { Provider } from './types.js'; import type { SignerFactory } from 'rango-types'; -import { dynamicImportWithRefinedError } from '@rango-dev/wallets-shared'; +import { + dynamicImportWithRefinedError, + getNetworkInstance, + Networks, +} from '@rango-dev/wallets-shared'; import { DefaultSignerFactory, TransactionType as TxType } from 'rango-types'; export default async function getSigners( - provider: TonConnectUI + provider: Provider ): Promise { + const tonProvider = getNetworkInstance(provider, Networks.TON); const signers = new DefaultSignerFactory(); const { CustomTonSigner } = await dynamicImportWithRefinedError( - async () => await import('./ton-signer.js') + async () => await import('./signers/ton.js') ); - signers.registerSigner(TxType.TON, new CustomTonSigner(provider)); + signers.registerSigner(TxType.TON, new CustomTonSigner(tonProvider)); return signers; } diff --git a/wallets/provider-tonconnect/src/ton-signer.ts b/wallets/provider-tonconnect/src/signers/ton.ts similarity index 100% rename from wallets/provider-tonconnect/src/ton-signer.ts rename to wallets/provider-tonconnect/src/signers/ton.ts diff --git a/wallets/provider-tonconnect/src/types.ts b/wallets/provider-tonconnect/src/types.ts index 8403e975c1..70f308f532 100644 --- a/wallets/provider-tonconnect/src/types.ts +++ b/wallets/provider-tonconnect/src/types.ts @@ -1,3 +1,16 @@ -export interface Environments extends Record { - manifestUrl: string; -} +import type { LegacyNetworks } from '@rango-dev/wallets-core/legacy'; +import type { + TonConnectUI, + TonConnectUiOptionsWithManifest, +} from '@tonconnect/ui'; + +export type Environments = TonConnectUiOptionsWithManifest; + +type ProviderObject = { + [LegacyNetworks.TON]: TonConnectUI; +}; + +export type Provider = Map< + keyof ProviderObject, + ProviderObject[keyof ProviderObject] +>; diff --git a/wallets/provider-tonconnect/src/utils.ts b/wallets/provider-tonconnect/src/utils.ts new file mode 100644 index 0000000000..2b5dc008d2 --- /dev/null +++ b/wallets/provider-tonconnect/src/utils.ts @@ -0,0 +1,83 @@ +import type { Environments, Provider } from './types.js'; +import type { TonConnectUI } from '@tonconnect/ui'; + +import { + dynamicImportWithRefinedError, + Networks, +} from '@rango-dev/wallets-shared'; + +let tonConnectInstance: TonConnectUI; +let envs: Environments; + +export function setEnvs(_envs: Environments) { + envs = _envs; +} + +export async function getTonConnectUIModule() { + const tonConnectUI = await dynamicImportWithRefinedError( + async () => await import('@tonconnect/ui') + ); + return tonConnectUI; +} + +export async function initializeTonConnectInstance() { + if (!envs) { + throw new Error('Environments are not set'); + } + const { TonConnectUI } = await getTonConnectUIModule(); + if (!tonConnectInstance) { + tonConnectInstance = new TonConnectUI(envs); + } +} + +export function tonConnect() { + if (!tonConnectInstance) { + throw new Error( + "TonConnect instance isn't initialized. Please ensure you have provided the TonConnect config." + ); + } + + return tonConnectInstance; +} + +export function getInstanceOrThrow(): Provider { + const instance = tonConnect(); + + const instances = new Map([[Networks.TON, instance]]); + return instances as Provider; +} + +export async function waitForConnection( + tonConnectUI: TonConnectUI +): Promise { + return new Promise((resolve, reject) => { + const unsubscribeStatusChange = tonConnectUI.onStatusChange( + (state) => { + const walletConnected = !!state?.account.address; + + if (walletConnected) { + unsubscribe(); + resolve(state.account.address); + } + }, + (error) => { + unsubscribe(); + reject(error); + } + ); + + const unsubscribeModalStateChange = tonConnectUI.onModalStateChange( + (modalState) => { + if (modalState.closeReason === 'action-cancelled') { + unsubscribe(); + reject(new Error('The action was canceled by the user')); + } + } + ); + + const unsubscribe = () => { + unsubscribeStatusChange(); + unsubscribeModalStateChange(); + }; + }); +} diff --git a/wallets/readme.md b/wallets/readme.md index c96356e691..bdbbbd117c 100644 --- a/wallets/readme.md +++ b/wallets/readme.md @@ -161,6 +161,7 @@ For better user experience, wallet provider tries to connect to a wallet only wh | [UniSat](provider-unisat/readme.md) | ❌ | ⚠️ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | | [Xverse](provider-xverse/readme.md) | ❌ | ⚠️ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | | [Tomo](provider-tomo/readme.md) | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | +| [TonConnect](provider-tonconnect/readme.md) | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ## By Feature @@ -192,6 +193,7 @@ For better user experience, wallet provider tries to connect to a wallet only wh | Unisat | ✅ | 🚧 | ❌ | Injected | ❌ | | Xverse | ⚠️ | 🚧 | ✅ | Injected | ❌ | | Tomo | ✅ | ✅ | ✅ | Injected | ❌ | +| TonConnect | ❌ | ❌ | ✅ | TonConnect | ❌ | # Supported Wallets (Legacy) From 32deffacd1748a45d99381447ccf6e289e538dcb Mon Sep 17 00:00:00 2001 From: RyukTheCoder Date: Wed, 31 Dec 2025 08:19:11 +0000 Subject: [PATCH 2/7] feat: add args to hub init method --- wallets/core/src/hub/hub.ts | 24 +++++++++++++++---- wallets/core/src/hub/provider/provider.ts | 4 ++-- wallets/provider-all/src/index.ts | 16 +------------ wallets/provider-tonconnect/src/mod.ts | 7 +----- wallets/provider-tonconnect/src/provider.ts | 9 +++++-- wallets/react/src/hub/useHubAdapter.ts | 2 +- wallets/react/src/legacy/types.ts | 8 +++++++ .../src/containers/Wallets/Wallets.tsx | 6 +++++ 8 files changed, 45 insertions(+), 31 deletions(-) diff --git a/wallets/core/src/hub/hub.ts b/wallets/core/src/hub/hub.ts index 385dcefe65..f0895644e6 100644 --- a/wallets/core/src/hub/hub.ts +++ b/wallets/core/src/hub/hub.ts @@ -14,6 +14,15 @@ type RunAllResult = { namespaces: unknown[]; }; +type RunAllArgs = { + [providerId: string]: { + provider?: unknown; + namespaces?: { + [namespaceId: string]: unknown; + }; + }; +}; + interface HubOptions { store?: Store; } @@ -25,8 +34,8 @@ export class Hub { this.#options = options ?? {}; } - init() { - this.runAll('init'); + init(args?: RunAllArgs) { + this.runAll('init', args); } /* @@ -34,7 +43,7 @@ export class Hub { * * TODO: Some of methods may accepts args, with this implementation we only limit to those one without any argument. */ - runAll(action: string): RunAllResult[] { + runAll(action: string, args?: RunAllArgs): RunAllResult[] { const output: RunAllResult[] = []; // run action on all providers eagerConnect, disconnect @@ -52,7 +61,10 @@ export class Hub { if (typeof providerMethod === 'function') { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore-next-line - providerOutput.provider = providerMethod.call(provider); + providerOutput.provider = providerMethod.call( + provider, + args?.[provider.id]?.provider + ); } // Namespace instances can have their own `action` as well. we will call them as well. @@ -62,7 +74,9 @@ export class Hub { // @ts-ignore-next-line const namespaceMethod = namespace[action]; if (typeof namespaceMethod === 'function') { - const result = namespaceMethod(); + const result = namespaceMethod( + args?.[provider.id]?.namespaces?.[namespace.namespaceId] + ); providerOutput.namespaces.push(result); } } diff --git a/wallets/core/src/hub/provider/provider.ts b/wallets/core/src/hub/provider/provider.ts index f8a47382b5..f1b2b71dbb 100644 --- a/wallets/core/src/hub/provider/provider.ts +++ b/wallets/core/src/hub/provider/provider.ts @@ -68,14 +68,14 @@ export class Provider { * provider.init() * ``` */ - public init(): void { + public init(args?: unknown): void { if (this.#initiated) { return; } const definedInitByUser = this.#extendInternalActions.init; if (definedInitByUser) { - definedInitByUser(this.#context()); + definedInitByUser(this.#context(), args); } this.#initiated = true; diff --git a/wallets/provider-all/src/index.ts b/wallets/provider-all/src/index.ts index 33ce90879c..bcfc2dc3af 100644 --- a/wallets/provider-all/src/index.ts +++ b/wallets/provider-all/src/index.ts @@ -30,10 +30,7 @@ import { versions as solflare } from '@rango-dev/provider-solflare'; import { versions as taho } from '@rango-dev/provider-taho'; import { versions as tokenPocket } from '@rango-dev/provider-tokenpocket'; import { versions as tomo } from '@rango-dev/provider-tomo'; -import { - versions as tonconnect, - init as tonconnectInit, -} from '@rango-dev/provider-tonconnect'; +import { versions as tonconnect } from '@rango-dev/provider-tonconnect'; import * as trezor from '@rango-dev/provider-trezor'; import { versions as tronLink } from '@rango-dev/provider-tron-link'; import { versions as trustwallet } from '@rango-dev/provider-trustwallet'; @@ -87,17 +84,6 @@ export const allProviders = ( } } - if ( - !isWalletExcluded(providers, { - type: WalletTypes.TON_CONNECT, - name: 'tonconnect', - }) - ) { - if (!!options?.tonConnect?.manifestUrl) { - tonconnectInit(options.tonConnect); - } - } - return [ lazyProvider(legacyProviderImportsToVersionsInterface(safe)), lazyProvider(legacyProviderImportsToVersionsInterface(defaultInjected)), diff --git a/wallets/provider-tonconnect/src/mod.ts b/wallets/provider-tonconnect/src/mod.ts index 8dcdeee008..00d33b9b02 100644 --- a/wallets/provider-tonconnect/src/mod.ts +++ b/wallets/provider-tonconnect/src/mod.ts @@ -1,15 +1,10 @@ -import type { Environments } from './types.js'; - import { defineVersions } from '@rango-dev/wallets-core/utils'; import { buildProvider } from './provider.js'; -import { setEnvs } from './utils.js'; export type { Environments } from './types.js'; -const init = (environments: Environments) => setEnvs(environments); - const versions = () => defineVersions().version('1.0.0', buildProvider()).build(); -export { versions, init }; +export { versions }; diff --git a/wallets/provider-tonconnect/src/provider.ts b/wallets/provider-tonconnect/src/provider.ts index fb25acf089..c98583da39 100644 --- a/wallets/provider-tonconnect/src/provider.ts +++ b/wallets/provider-tonconnect/src/provider.ts @@ -1,13 +1,18 @@ +import type { Environments } from './types.js'; + import { ProviderBuilder } from '@rango-dev/wallets-core'; import { metadata, WALLET_ID } from './constants.js'; import { ton } from './namespaces/ton.js'; -import { initializeTonConnectInstance } from './utils.js'; +import { initializeTonConnectInstance, setEnvs } from './utils.js'; const buildProvider = () => new ProviderBuilder(WALLET_ID) - .init(async function (context) { + .init(async function (context, environments: Environments) { const [, setState] = context.state(); + + setEnvs(environments); + async function initializeTon() { try { await initializeTonConnectInstance(); diff --git a/wallets/react/src/hub/useHubAdapter.ts b/wallets/react/src/hub/useHubAdapter.ts index 18a5cec20a..d4e9773923 100644 --- a/wallets/react/src/hub/useHubAdapter.ts +++ b/wallets/react/src/hub/useHubAdapter.ts @@ -86,7 +86,7 @@ export function useHubAdapter(params: UseAdapterParams): ProviderContext { // Initialize instances useEffect(() => { const runOnInit = () => { - getHub().init(); + getHub().init(params.configs?.walletOptions); rerender((currentRender) => currentRender + 1); }; diff --git a/wallets/react/src/legacy/types.ts b/wallets/react/src/legacy/types.ts index 0f0b9c5f1c..1f79d2d976 100644 --- a/wallets/react/src/legacy/types.ts +++ b/wallets/react/src/legacy/types.ts @@ -80,6 +80,14 @@ export type ProviderProps = PropsWithChildren<{ providers: VersionedProviders[]; configs?: { wallets?: (WalletType | LegacyProviderInterface | Provider)[]; + walletOptions?: { + [key: WalletType]: { + provider?: unknown; + namespaces?: { + [namespaceId: string]: unknown; + }; + }; + }; }; }>; diff --git a/widget/embedded/src/containers/Wallets/Wallets.tsx b/widget/embedded/src/containers/Wallets/Wallets.tsx index 552526abed..ae82a2d170 100644 --- a/widget/embedded/src/containers/Wallets/Wallets.tsx +++ b/widget/embedded/src/containers/Wallets/Wallets.tsx @@ -9,6 +9,7 @@ import type { LegacyEventHandler } from '@rango-dev/wallets-core/legacy'; import type { PropsWithChildren } from 'react'; import { Provider } from '@rango-dev/wallets-react'; +import { WalletTypes } from '@rango-dev/wallets-shared'; import React, { createContext, useEffect, useMemo, useRef } from 'react'; import { useWalletProviders } from '../../hooks/useWalletProviders'; @@ -104,6 +105,11 @@ function Main(props: PropsWithChildren) { autoConnect={!!isActiveTab} configs={{ wallets: config.wallets, + walletOptions: { + [WalletTypes.TON_CONNECT]: { + provider: { manifestUrl: config.tonConnect?.manifestUrl }, + }, + }, }}> {props.children} From ea28386bee27a5268ce59651236240ed414cf9a2 Mon Sep 17 00:00:00 2001 From: llawliet-l-l Date: Tue, 30 Dec 2025 08:58:24 +0000 Subject: [PATCH 3/7] fix: updated readme to hint about initialization --- wallets/provider-tonconnect/readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wallets/provider-tonconnect/readme.md b/wallets/provider-tonconnect/readme.md index 3e42b44a16..bd65e6ab28 100644 --- a/wallets/provider-tonconnect/readme.md +++ b/wallets/provider-tonconnect/readme.md @@ -7,7 +7,7 @@ More about implementation status can be found [here](../readme.md). ## Implementation notes/limitations #### ⚠️ Initialization -You should call the `init` function with TonConnect configs before trying to use the wallet +You should provide TonConnect configs in configs.walletOptions[WalletTypes.TON_CONNECT] (which equals configs.walletOptions.tonconnect) ### Feature From 2ccb6c14fab0f64fee546e6f623760369c947fc9 Mon Sep 17 00:00:00 2001 From: llawliet-l-l Date: Tue, 30 Dec 2025 09:04:30 +0000 Subject: [PATCH 4/7] fix: modified types --- wallets/core/src/namespaces/ton/types.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/wallets/core/src/namespaces/ton/types.ts b/wallets/core/src/namespaces/ton/types.ts index 8df2f74b50..ad54809a32 100644 --- a/wallets/core/src/namespaces/ton/types.ts +++ b/wallets/core/src/namespaces/ton/types.ts @@ -7,13 +7,9 @@ import type { export interface TonActions extends AutoImplementedActionsByRecommended, CommonActions { - connect: (options?: ConnectOptions) => Promise; + connect: () => Promise; canEagerConnect: () => Promise; } // eslint-disable-next-line @typescript-eslint/no-explicit-any export type ProviderAPI = Record; - -export type ConnectOptions = { - derivationPath?: string; -}; From 8dddf1864c2aa6a386d78302a3682a42960cec5e Mon Sep 17 00:00:00 2001 From: llawliet-l-l Date: Tue, 30 Dec 2025 09:21:18 +0000 Subject: [PATCH 5/7] fix: changed ton caip format --- wallets/core/src/namespaces/ton/constants.ts | 2 +- wallets/react/src/hub/helpers.ts | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/wallets/core/src/namespaces/ton/constants.ts b/wallets/core/src/namespaces/ton/constants.ts index 18120bdcba..992caba81d 100644 --- a/wallets/core/src/namespaces/ton/constants.ts +++ b/wallets/core/src/namespaces/ton/constants.ts @@ -1,2 +1,2 @@ -export const CAIP_NAMESPACE = 'ton'; +export const CAIP_NAMESPACE = 'tvm'; export const CAIP_TON_CHAIN_ID = '-239'; diff --git a/wallets/react/src/hub/helpers.ts b/wallets/react/src/hub/helpers.ts index 4278c9b03a..b94c5efbeb 100644 --- a/wallets/react/src/hub/helpers.ts +++ b/wallets/react/src/hub/helpers.ts @@ -8,6 +8,7 @@ import type { Result } from 'ts-results'; import { legacyFormatAddressWithNetwork as formatAddressWithNetwork } from '@rango-dev/wallets-core/legacy'; import { CAIP_NAMESPACE as CAIP_COSMOS_NAMESPACE } from '@rango-dev/wallets-core/namespaces/cosmos'; +import { CAIP_NAMESPACE as CAIP_TON_NAMESPACE } from '@rango-dev/wallets-core/namespaces/ton'; import { CAIP_TRON_CHAIN_ID } from '@rango-dev/wallets-core/namespaces/tron'; import { CAIP_BITCOIN_CHAIN_ID } from '@rango-dev/wallets-core/namespaces/utxo'; import { CAIP } from '@rango-dev/wallets-core/utils'; @@ -33,6 +34,9 @@ export function mapCaipNamespaceToLegacyNetworkName( return 'BTC'; } + if (chainId.namespace.toLowerCase() === CAIP_TON_NAMESPACE) { + return 'TON'; + } if (chainId.namespace.toLowerCase() === CAIP_COSMOS_NAMESPACE) { const network = getBlockChainNameFromId(chainId.reference, allBlockChains); if (!network) { From 7ee58a79b9528050383269db69c0ac4a4eda43a4 Mon Sep 17 00:00:00 2001 From: llawliet-l-l Date: Tue, 30 Dec 2025 10:22:51 +0000 Subject: [PATCH 6/7] fix: modified disconnect listeners --- .../provider-tonconnect/src/actions/ton.ts | 4 +- .../provider-tonconnect/src/builders/ton.ts | 37 --------------- wallets/provider-tonconnect/src/hooks/ton.ts | 47 +++++++++++++++++++ .../provider-tonconnect/src/namespaces/ton.ts | 13 +++-- 4 files changed, 56 insertions(+), 45 deletions(-) delete mode 100644 wallets/provider-tonconnect/src/builders/ton.ts create mode 100644 wallets/provider-tonconnect/src/hooks/ton.ts diff --git a/wallets/provider-tonconnect/src/actions/ton.ts b/wallets/provider-tonconnect/src/actions/ton.ts index 416602279b..07954f84e4 100644 --- a/wallets/provider-tonconnect/src/actions/ton.ts +++ b/wallets/provider-tonconnect/src/actions/ton.ts @@ -36,7 +36,9 @@ export function disconnect( ): FunctionWithContext { return async (context) => { const tonInstance = getInstance(); - await tonInstance.disconnect(); + if (tonInstance.connected) { + await tonInstance.disconnect(); + } commonActions.disconnect(context); }; } diff --git a/wallets/provider-tonconnect/src/builders/ton.ts b/wallets/provider-tonconnect/src/builders/ton.ts deleted file mode 100644 index 79afdaa8d2..0000000000 --- a/wallets/provider-tonconnect/src/builders/ton.ts +++ /dev/null @@ -1,37 +0,0 @@ -import type { ConnectedWallet, TonConnectUI } from '@tonconnect/ui'; - -import { - ChangeAccountSubscriberBuilder, - actions as commonActions, -} from '@rango-dev/wallets-core/namespaces/common'; -import { type TonActions } from '@rango-dev/wallets-core/namespaces/ton'; - -const changeAccountSubscriber = (getInstance: () => TonConnectUI) => - new ChangeAccountSubscriberBuilder< - ConnectedWallet | null, - TonConnectUI, - TonActions - >() - .getInstance(getInstance) - /* - * TON wallets don't implement account change events, and we can only listen to disconnect events - * by checking the payload value. - * More info: https://github.com/ton-blockchain/ton-connect/blob/main/wallet-guidelines.md - */ - .onSwitchAccount(async (event, context) => { - event.preventDefault(); - if (!event.payload) { - commonActions.disconnect(context); - } - }) - .format(async () => []) - .addEventListener((instance, callback) => { - return instance.onStatusChange(callback); - }) - .removeEventListener(() => { - /* - * We handle unsubscribing in the builder and by returning an unsubscribe function from 'addEventListener' - */ - }); - -export const tonBuilders = { changeAccountSubscriber }; diff --git a/wallets/provider-tonconnect/src/hooks/ton.ts b/wallets/provider-tonconnect/src/hooks/ton.ts new file mode 100644 index 0000000000..f45b13aef8 --- /dev/null +++ b/wallets/provider-tonconnect/src/hooks/ton.ts @@ -0,0 +1,47 @@ +import type { + AnyFunction, + Subscriber, + SubscriberCleanUp, +} from '@rango-dev/wallets-core'; +import type { TonActions } from '@rango-dev/wallets-core/namespaces/ton'; +import type { ITonConnect, TonConnectUI } from '@tonconnect/ui'; + +function getDisconnectSubscriber( + instance: () => TonConnectUI +): [Subscriber, SubscriberCleanUp] { + let eventCallback: AnyFunction; + let unsubscribe: ReturnType; + + // subscriber can be passed to `or`, it will get the error and should rethrow error to pass the error to next `or` or throw error. + return [ + (context, err) => { + const tonInstance = instance(); + + if (!tonInstance) { + throw new Error( + 'Trying to subscribe to your Ton wallet, but seems its instance is not available.' + ); + } + + eventCallback = (event) => { + if (!event.payload) { + context.action('disconnect'); + } + }; + + unsubscribe = tonInstance.onStatusChange(eventCallback); + + if (err instanceof Error) { + throw err; + } + }, + (_, err) => { + if (unsubscribe) { + unsubscribe(); + } + + return err; + }, + ]; +} +export const tonHooks = { getDisconnectSubscriber }; diff --git a/wallets/provider-tonconnect/src/namespaces/ton.ts b/wallets/provider-tonconnect/src/namespaces/ton.ts index 4ff757beab..a97a8deb0b 100644 --- a/wallets/provider-tonconnect/src/namespaces/ton.ts +++ b/wallets/provider-tonconnect/src/namespaces/ton.ts @@ -5,19 +5,18 @@ import { standardizeAndThrowError } from '@rango-dev/wallets-core/namespaces/com import { builders } from '@rango-dev/wallets-core/namespaces/ton'; import { tonActions } from '../actions/ton.js'; -import { tonBuilders } from '../builders/ton.js'; import { WALLET_ID } from '../constants.js'; +import { tonHooks } from '../hooks/ton.js'; import { tonConnect } from '../utils.js'; -const [changeAccountSubscriber, changeAccountCleanup] = tonBuilders - .changeAccountSubscriber(tonConnect) - .build(); +const [disconnectSubscriber, disconnectCleanUp] = + tonHooks.getDisconnectSubscriber(tonConnect); const connect = builders .connect() .action(tonActions.connect(tonConnect)) - .and(changeAccountSubscriber) - .or(changeAccountCleanup) + .and(disconnectSubscriber) + .or(disconnectCleanUp) .or(standardizeAndThrowError) .build(); @@ -28,7 +27,7 @@ const canEagerConnect = builders const disconnect = new ActionBuilder('disconnect') .action(tonActions.disconnect(tonConnect)) - .after(changeAccountCleanup) + .after(disconnectCleanUp) .build(); const ton = new NamespaceBuilder('Ton', WALLET_ID) From 64f5f2b06380cc7304f9518e01e1cf45b1371913 Mon Sep 17 00:00:00 2001 From: llawliet-l-l Date: Wed, 31 Dec 2025 08:33:43 +0000 Subject: [PATCH 7/7] refactor: moved ton connect instance and initialization to a separate class --- .../provider-tonconnect/src/actions/ton.ts | 4 +- .../provider-tonconnect/src/namespaces/ton.ts | 8 ++-- wallets/provider-tonconnect/src/provider.ts | 6 +-- wallets/provider-tonconnect/src/tonConnect.ts | 43 +++++++++++++++++++ wallets/provider-tonconnect/src/utils.ts | 43 +++---------------- 5 files changed, 56 insertions(+), 48 deletions(-) create mode 100644 wallets/provider-tonconnect/src/tonConnect.ts diff --git a/wallets/provider-tonconnect/src/actions/ton.ts b/wallets/provider-tonconnect/src/actions/ton.ts index 07954f84e4..9a3993e049 100644 --- a/wallets/provider-tonconnect/src/actions/ton.ts +++ b/wallets/provider-tonconnect/src/actions/ton.ts @@ -8,7 +8,7 @@ import { utils, } from '@rango-dev/wallets-core/namespaces/ton'; -import { getTonConnectUIModule, waitForConnection } from '../utils.js'; +import { tonConnect, waitForConnection } from '../utils.js'; export function connect( getInstance: () => TonConnectUI @@ -16,7 +16,7 @@ export function connect( return async () => { const tonInstance = getInstance(); const connectionRestored = await tonInstance.connectionRestored; - const { toUserFriendlyAddress } = await getTonConnectUIModule(); + const { toUserFriendlyAddress } = tonConnect.getModule(); let userFriendlyAddress: string; if (connectionRestored && tonInstance.account?.address) { diff --git a/wallets/provider-tonconnect/src/namespaces/ton.ts b/wallets/provider-tonconnect/src/namespaces/ton.ts index a97a8deb0b..ebc7f93bcd 100644 --- a/wallets/provider-tonconnect/src/namespaces/ton.ts +++ b/wallets/provider-tonconnect/src/namespaces/ton.ts @@ -10,11 +10,11 @@ import { tonHooks } from '../hooks/ton.js'; import { tonConnect } from '../utils.js'; const [disconnectSubscriber, disconnectCleanUp] = - tonHooks.getDisconnectSubscriber(tonConnect); + tonHooks.getDisconnectSubscriber(tonConnect.getInstance); const connect = builders .connect() - .action(tonActions.connect(tonConnect)) + .action(tonActions.connect(tonConnect.getInstance)) .and(disconnectSubscriber) .or(disconnectCleanUp) .or(standardizeAndThrowError) @@ -22,11 +22,11 @@ const connect = builders const canEagerConnect = builders .canEagerConnect() - .action(tonActions.canEagerConnect(tonConnect)) + .action(tonActions.canEagerConnect(tonConnect.getInstance)) .build(); const disconnect = new ActionBuilder('disconnect') - .action(tonActions.disconnect(tonConnect)) + .action(tonActions.disconnect(tonConnect.getInstance)) .after(disconnectCleanUp) .build(); diff --git a/wallets/provider-tonconnect/src/provider.ts b/wallets/provider-tonconnect/src/provider.ts index c98583da39..b3023e783c 100644 --- a/wallets/provider-tonconnect/src/provider.ts +++ b/wallets/provider-tonconnect/src/provider.ts @@ -4,18 +4,16 @@ import { ProviderBuilder } from '@rango-dev/wallets-core'; import { metadata, WALLET_ID } from './constants.js'; import { ton } from './namespaces/ton.js'; -import { initializeTonConnectInstance, setEnvs } from './utils.js'; +import { tonConnect } from './utils.js'; const buildProvider = () => new ProviderBuilder(WALLET_ID) .init(async function (context, environments: Environments) { const [, setState] = context.state(); - setEnvs(environments); - async function initializeTon() { try { - await initializeTonConnectInstance(); + await tonConnect.initialize(environments); console.debug('[ton] instance initialized.', context); // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (_) { diff --git a/wallets/provider-tonconnect/src/tonConnect.ts b/wallets/provider-tonconnect/src/tonConnect.ts new file mode 100644 index 0000000000..001aa40a67 --- /dev/null +++ b/wallets/provider-tonconnect/src/tonConnect.ts @@ -0,0 +1,43 @@ +import type { Environments } from './types.js'; +import type { TonConnectUI } from '@tonconnect/ui'; + +import { dynamicImportWithRefinedError } from '@rango-dev/wallets-shared'; + +export class TonConnect { + // eslint-disable-next-line @typescript-eslint/consistent-type-imports + #tonModule?: typeof import('@tonconnect/ui'); + #env?: Environments; + #tonConnectInstance?: TonConnectUI; + + async initialize(env?: Environments) { + if (!env) { + throw new Error('Environments are not set'); + } + this.#env = env; + if (this.#env) { + throw new Error('Environments are not set'); + } + + this.#tonModule = await dynamicImportWithRefinedError( + async () => await import('@tonconnect/ui') + ); + const { TonConnectUI } = this.#tonModule; + this.#tonConnectInstance = new TonConnectUI(this.#env); + } + + getInstance() { + if (!this.#tonConnectInstance) { + throw new Error( + "TonConnect instance isn't initialized. Please ensure you have provided the TonConnect config." + ); + } + return this.#tonConnectInstance; + } + + getModule() { + if (!this.#tonModule) { + throw new Error("Couldn't initialize the TonConnect module"); + } + return this.#tonModule; + } +} diff --git a/wallets/provider-tonconnect/src/utils.ts b/wallets/provider-tonconnect/src/utils.ts index 2b5dc008d2..63793cfeb4 100644 --- a/wallets/provider-tonconnect/src/utils.ts +++ b/wallets/provider-tonconnect/src/utils.ts @@ -1,47 +1,14 @@ -import type { Environments, Provider } from './types.js'; +import type { Provider } from './types.js'; import type { TonConnectUI } from '@tonconnect/ui'; -import { - dynamicImportWithRefinedError, - Networks, -} from '@rango-dev/wallets-shared'; +import { Networks } from '@rango-dev/wallets-shared'; -let tonConnectInstance: TonConnectUI; -let envs: Environments; +import { TonConnect } from './tonConnect.js'; -export function setEnvs(_envs: Environments) { - envs = _envs; -} - -export async function getTonConnectUIModule() { - const tonConnectUI = await dynamicImportWithRefinedError( - async () => await import('@tonconnect/ui') - ); - return tonConnectUI; -} - -export async function initializeTonConnectInstance() { - if (!envs) { - throw new Error('Environments are not set'); - } - const { TonConnectUI } = await getTonConnectUIModule(); - if (!tonConnectInstance) { - tonConnectInstance = new TonConnectUI(envs); - } -} - -export function tonConnect() { - if (!tonConnectInstance) { - throw new Error( - "TonConnect instance isn't initialized. Please ensure you have provided the TonConnect config." - ); - } - - return tonConnectInstance; -} +export const tonConnect = new TonConnect(); export function getInstanceOrThrow(): Provider { - const instance = tonConnect(); + const instance = tonConnect.getInstance(); const instances = new Map([[Networks.TON, instance]]); return instances as Provider;