Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions wallets/core/namespaces/ton/package.json
Original file line number Diff line number Diff line change
@@ -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
}
6 changes: 5 additions & 1 deletion wallets/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": [
Expand All @@ -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}'",
Expand Down
24 changes: 19 additions & 5 deletions wallets/core/src/hub/hub.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,15 @@ type RunAllResult = {
namespaces: unknown[];
};

type RunAllArgs = {
[providerId: string]: {
provider?: unknown;
namespaces?: {
[namespaceId: string]: unknown;
};
};
};

interface HubOptions {
store?: Store;
}
Expand All @@ -25,16 +34,16 @@ export class Hub {
this.#options = options ?? {};
}

init() {
this.runAll('init');
init(args?: RunAllArgs) {
this.runAll('init', args);
}

/*
* Running a specific action (e.g. init) on all namespaces and providers one by one.
*
* 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
Expand All @@ -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.
Expand All @@ -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]
);
Comment on lines +77 to +79
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Namespace init args are forwarded but currently not consumed.

runAll('init', args) passes per-namespace args, but wallets/core/src/hub/namespaces/namespace.ts defines init() without parameters, so namespace init args are effectively dropped.

🔧 Suggested follow-up change (outside this file)
- public init(): void {
+ public init(args?: unknown): void {
    if (this.#initiated) {
      return;
    }

    const definedInitByUser = this.#actions.get('init');

    if (definedInitByUser) {
-     definedInitByUser(this.#context());
+     definedInitByUser(this.#context(), args);
    }

    this.#initiated = true;
  }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wallets/core/src/hub/hub.ts` around lines 77 - 79, The per-namespace init
arguments passed from hub (where namespaceMethod is invoked with
args?.[provider.id]?.namespaces?.[namespace.namespaceId]) are dropped because
Namespace.init is defined without parameters; update the Namespace API to accept
and forward those args: change the signature of init in
wallets/core/src/hub/namespaces/namespace.ts (and its implementations) to accept
an optional parameter (e.g., init(initArgs?: unknown)) and ensure each concrete
namespace implementation consumes or forwards initArgs appropriately so the
hub-provided per-namespace args are honored.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify detached namespace method invocation and `this` usage in namespace methods.
rg -n -C3 'const namespaceMethod = namespace\[action\]|namespaceMethod\(' wallets/core/src/hub/hub.ts
rg -n -C4 'public (init|state)\(' wallets/core/src/hub/namespaces/namespace.ts
rg -n -C2 'this\.\#' wallets/core/src/hub/namespaces/namespace.ts

Repository: RyukTheCoder/rango-client

Length of output: 7057


Bind namespace methods to preserve this context.

Namespace methods are invoked detached from their instance. The init(), state(), and all other methods in the Namespace class access private fields (e.g., this.#initiated, this.#store) or call private methods (e.g., this.#storeId(), this.#context()). Without proper binding, this will be undefined, causing runtime errors. Invoke with .call(namespace, ...).

🔧 Proposed fix
-          const result = namespaceMethod(
-            args?.[provider.id]?.namespaces?.[namespace.namespaceId]
-          );
+          const result = namespaceMethod.call(
+            namespace,
+            args?.[provider.id]?.namespaces?.[namespace.namespaceId]
+          );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const result = namespaceMethod(
args?.[provider.id]?.namespaces?.[namespace.namespaceId]
);
const result = namespaceMethod.call(
namespace,
args?.[provider.id]?.namespaces?.[namespace.namespaceId]
);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wallets/core/src/hub/hub.ts` around lines 77 - 79, The namespace methods
(e.g., Namespace.init(), Namespace.state() and other instance methods) are being
invoked detached via namespaceMethod(...) so their `this` is lost and private
fields/methods like `this.#initiated`, `this.#store` and `this.#storeId()` fail;
fix by invoking the method with the namespace instance as its context (call the
function with .call(namespace, ...) or otherwise bind namespaceMethod to the
namespace) when calling namespaceMethod in hub.ts so the method runs with the
correct `this`.

providerOutput.namespaces.push(result);
}
}
Expand Down
4 changes: 2 additions & 2 deletions wallets/core/src/hub/provider/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 2 additions & 0 deletions wallets/core/src/hub/provider/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -38,6 +39,7 @@ export interface CommonNamespaces {
tron: TronActions;
starknet: StarknetActions;
xrpl: XRPLActions;
ton: TonActions;
}

export type CommonNamespaceKeys = Prettify<keyof CommonNamespaces>;
Expand Down
15 changes: 15 additions & 0 deletions wallets/core/src/namespaces/ton/builders.ts
Original file line number Diff line number Diff line change
@@ -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<TonActions, 'connect'>('connect')
.and(connectAndUpdateStateForSingleNetwork)
.before(intoConnecting)
.after(intoConnectionFinished);

export const canEagerConnect = () =>
new ActionBuilder<TonActions, 'canEagerConnect'>('canEagerConnect');
2 changes: 2 additions & 0 deletions wallets/core/src/namespaces/ton/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const CAIP_NAMESPACE = 'tvm';
export const CAIP_TON_CHAIN_ID = '-239';
5 changes: 5 additions & 0 deletions wallets/core/src/namespaces/ton/mod.ts
Original file line number Diff line number Diff line change
@@ -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';
15 changes: 15 additions & 0 deletions wallets/core/src/namespaces/ton/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import type { Accounts } from '../../types/accounts.js';
import type {
AutoImplementedActionsByRecommended,
CommonActions,
} from '../common/types.js';

export interface TonActions
extends AutoImplementedActionsByRecommended,
CommonActions {
connect: () => Promise<Accounts>;
canEagerConnect: () => Promise<boolean>;
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type ProviderAPI = Record<string, any>;
18 changes: 18 additions & 0 deletions wallets/core/src/namespaces/ton/utils.ts
Original file line number Diff line number Diff line change
@@ -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
);
}
15 changes: 2 additions & 13 deletions wallets/provider-all/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +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 * as tonconnect 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';
Expand Down Expand Up @@ -84,23 +84,12 @@ export const allProviders = (
}
}

if (
!isWalletExcluded(providers, {
type: WalletTypes.TON_CONNECT,
name: 'tonconnect',
})
) {
if (!!options?.tonConnect?.manifestUrl) {
tonconnect.init(options.tonConnect);
}
}

return [
lazyProvider(legacyProviderImportsToVersionsInterface(safe)),
lazyProvider(legacyProviderImportsToVersionsInterface(defaultInjected)),
metamask,
lazyProvider(legacyProviderImportsToVersionsInterface(walletconnect2)),
lazyProvider(legacyProviderImportsToVersionsInterface(tonconnect)),
tonconnect,
keplr,
phantom,
ready,
Expand Down
15 changes: 6 additions & 9 deletions wallets/provider-tonconnect/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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}'",
Expand All @@ -33,4 +30,4 @@
"publishConfig": {
"access": "public"
}
}
}
28 changes: 27 additions & 1 deletion wallets/provider-tonconnect/readme.md
Original file line number Diff line number Diff line change
@@ -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
More about implementation status can be found [here](../readme.md).

## Implementation notes/limitations

#### ⚠️ Initialization
You should provide TonConnect configs in configs.walletOptions[WalletTypes.TON_CONNECT] (which equals configs.walletOptions.tonconnect)
Comment on lines +5 to +10
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix markdown accessibility/readability lint issues.

Use descriptive link text (Line 5) and avoid heading-level jumps (Line 9).

🔧 Proposed doc fix
-More about implementation status can be found [here](../readme.md).
+More about implementation status can be found in the [wallet providers README](../readme.md).

-#### ⚠️ Initialization
+### ⚠️ Initialization
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
More about implementation status can be found [here](../readme.md).
## Implementation notes/limitations
#### ⚠️ Initialization
You should provide TonConnect configs in configs.walletOptions[WalletTypes.TON_CONNECT] (which equals configs.walletOptions.tonconnect)
More about implementation status can be found in the [wallet providers README](../readme.md).
## Implementation notes/limitations
### ⚠️ Initialization
You should provide TonConnect configs in configs.walletOptions[WalletTypes.TON_CONNECT] (which equals configs.walletOptions.tonconnect)
🧰 Tools
🪛 markdownlint-cli2 (0.22.0)

[warning] 5-5: Link text should be descriptive

(MD059, descriptive-link-text)


[warning] 9-9: Heading levels should only increment by one level at a time
Expected: h3; Actual: h4

(MD001, heading-increment)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wallets/provider-tonconnect/readme.md` around lines 5 - 10, Change the vague
link text "More about implementation status" to a descriptive label such as
"Implementation status in the project README" (so screen readers know
destination) and update the link target as in the diff; also fix the
heading-level jump by using a consistent heading level (e.g., change "#### ⚠️
Initialization" to "### ⚠️ Initialization" or adjust surrounding headings to
avoid skipping from H2 to H4), and ensure the config keys mentioned remain
code-formatted exactly as configs.walletOptions[WalletTypes.TON_CONNECT] (alias
configs.walletOptions.tonconnect) for clarity and accessibility.


### 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.
Comment on lines +24 to +25
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kody code-review Bug high

Logic error: The 'installed' property is hardcoded to true regardless of whether the TonConnect instance is successfully initialized. This violates the provider's availability invariant, causing the UI to display the wallet as available even when it is in an unusable state (e.g., due to missing or invalid configuration). Consequently, users will encounter unexpected errors when attempting to connect to a wallet that incorrectly claimed to be installed.

We set **'installed'** to `true` only after the TonConnect instance is successfully initialized with the provided configuration.
If initialization fails or configuration is missing, **'installed'** remains `false` to prevent the UI from offering an unusable connection option.
Prompt for LLM

File wallets/provider-tonconnect/readme.md:

Line 24 to 25:

Identify and suggest improvements for the logic error in the TonConnect initialization process.

Suggested Code:

We set **'installed'** to `true` only after the TonConnect instance is successfully initialized with the provided configuration.
If initialization fails or configuration is missing, **'installed'** remains `false` to prevent the UI from offering an unusable connection option.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.


---

More wallet information can be found in [readme.md](../readme.md).
56 changes: 56 additions & 0 deletions wallets/provider-tonconnect/src/actions/ton.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
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 { tonConnect, waitForConnection } from '../utils.js';

export function connect(
getInstance: () => TonConnectUI
): FunctionWithContext<TonActions['connect'], Context> {
return async () => {
const tonInstance = getInstance();
const connectionRestored = await tonInstance.connectionRestored;
const { toUserFriendlyAddress } = tonConnect.getModule();
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<TonActions['disconnect'], Context> {
return async (context) => {
const tonInstance = getInstance();
if (tonInstance.connected) {
await tonInstance.disconnect();
}
commonActions.disconnect(context);
};
}

export function canEagerConnect(
getInstance: () => TonProviderAPI
): FunctionWithContext<TonActions['canEagerConnect'], Context> {
return async () => {
const tonConnectUI = getInstance() as TonConnectUI;
const connectionRestored = await tonConnectUI.connectionRestored;
return connectionRestored;
};
}

export const tonActions = { connect, disconnect, canEagerConnect };
38 changes: 38 additions & 0 deletions wallets/provider-tonconnect/src/constants.ts
Original file line number Diff line number Diff line change
@@ -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()),
},
},
],
};
Loading