From ac68ae63c7133a0f71e82c7d6a9ecfc279e9bc32 Mon Sep 17 00:00:00 2001 From: Dmitrii Nikulin Date: Mon, 16 Feb 2026 18:41:36 +0500 Subject: [PATCH 01/10] feat: refactor wallet management APIs --- .../src/api/cryptography.ts | 8 +- .../walletkit-android-bridge/src/api/index.ts | 10 +- .../src/api/wallets.ts | 228 +++++++++++------- .../walletkit-android-bridge/src/types/api.ts | 49 ++-- .../src/types/walletkit.ts | 5 +- 5 files changed, 173 insertions(+), 127 deletions(-) diff --git a/packages/walletkit-android-bridge/src/api/cryptography.ts b/packages/walletkit-android-bridge/src/api/cryptography.ts index 57a767b0c..816202508 100644 --- a/packages/walletkit-android-bridge/src/api/cryptography.ts +++ b/packages/walletkit-android-bridge/src/api/cryptography.ts @@ -7,17 +7,11 @@ */ /** - * Cryptographic helpers backed by WalletKit and custom signer coordination. + * Cryptographic helpers. */ -import type { Hex } from '@ton/walletkit'; import { CreateTonMnemonic, MnemonicToKeyPair, DefaultSignature } from '../core/moduleLoader'; -export async function signWithCustomSigner(signerId: string, bytes: Uint8Array): Promise { - const result = await window.WalletKitNative?.signWithCustomSigner?.(signerId, Array.from(bytes)); - return result as Hex; -} - export async function mnemonicToKeyPair(args: { mnemonic: string[]; mnemonicType?: string }) { if (!MnemonicToKeyPair) { throw new Error('MnemonicToKeyPair module not loaded'); diff --git a/packages/walletkit-android-bridge/src/api/index.ts b/packages/walletkit-android-bridge/src/api/index.ts index 28629bf0f..73b6777cb 100644 --- a/packages/walletkit-android-bridge/src/api/index.ts +++ b/packages/walletkit-android-bridge/src/api/index.ts @@ -34,10 +34,12 @@ export const api: WalletKitBridgeApi = { sign: cryptography.sign, createTonMnemonic: cryptography.createTonMnemonic, - // Wallets - createSigner: wallets.createSigner, - createAdapter: wallets.createAdapter, - getAdapterAddress: wallets.getAdapterAddress, + // Wallets — stateless factory helpers + publicKeyFromSecretKey: wallets.publicKeyFromSecretKey, + computeWalletAddress: wallets.computeWalletAddress, + addWalletWithSigner: wallets.addWalletWithSigner, + + // Wallets — proxy adapter (host apps) addWallet: wallets.addWallet, getWallets: wallets.getWallets, getWallet: wallets.getWalletById, diff --git a/packages/walletkit-android-bridge/src/api/wallets.ts b/packages/walletkit-android-bridge/src/api/wallets.ts index e9f32c221..20d6fb953 100644 --- a/packages/walletkit-android-bridge/src/api/wallets.ts +++ b/packages/walletkit-android-bridge/src/api/wallets.ts @@ -7,19 +7,17 @@ */ /** - * wallets.ts – Wallet management operations - * - * Pure pass-through bridge - returns raw JS objects/proxies. - * Kotlin is responsible for adapting to whatever JS returns. + * Wallet management operations. */ -import type { Hex, Network, WalletAdapter } from '@ton/walletkit'; +import type { Hex, Network, WalletAdapter, ApiClient, Base64String, UserFriendlyAddress } from '@ton/walletkit'; +import type { WalletId } from '@ton/walletkit'; +import type { TransactionRequest } from '@ton/walletkit'; +import type { PreparedSignData } from '@ton/walletkit'; +import type { ProofMessage } from '@ton/walletkit'; import { Signer, WalletV4R2Adapter, WalletV5R1Adapter } from '../core/moduleLoader'; import { kit, wallet, getKit } from '../utils/bridge'; -import { signWithCustomSigner } from './cryptography'; - -type SignerInstance = { sign: (bytes: Iterable) => Promise; publicKey: Hex }; /** * Lists all wallets. @@ -50,103 +48,157 @@ export async function getBalance(args: { walletId: string }) { return wallet(args.walletId, 'getBalance'); } -const signerStore = new Map(); -const adapterStore = new Map(); +/** + * Derive public key from a secret key. + */ +export async function publicKeyFromSecretKey(args: { secretKey: string }) { + const signer = await Signer!.fromPrivateKey(args.secretKey); + return signer.publicKey; +} -type CreateAdapterArgs = { - signerId: string; - isCustom?: boolean; - publicKey?: string; - walletVersion?: string; - network: string; - workchain: number; - walletId?: string; -}; - -type CreateSignerArgs = { - mnemonic?: string[]; - secretKey?: string; - mnemonicType?: string; -}; +/** + * Compute wallet address from public key + version + network. Stateless. + * Creates a temporary adapter just to get the address, then discards it. + */ +export async function computeWalletAddress(args: { + publicKey: string; + version: 'v5r1' | 'v4r2'; + network: { chainId: string }; + workchain?: number; + walletId?: number; +}) { + const instance = await getKit(); + const network = args.network as unknown as Network; + const dummySigner = { publicKey: args.publicKey as Hex, sign: async () => '0x' as Hex }; + const AdapterClass = args.version === 'v5r1' ? WalletV5R1Adapter : WalletV4R2Adapter; + const adapter = await AdapterClass!.create(dummySigner, { + client: instance.getApiClient(network), + network, + workchain: args.workchain ?? 0, + walletId: args.walletId, + }); + return adapter.getAddress(); +} -type AddWalletArgs = { - adapterId: string; -}; +/** + * Create signer + adapter + add wallet in one stateless call. + * For key-based signers: pass secretKey. JS creates signer from it. + * For custom signers: pass publicKey + signerId + isCustom. JS creates a signer + * that delegates signing back to Kotlin via WalletKitNative.signWithCustomSigner. + */ +export async function addWalletWithSigner(args: { + secretKey?: string; + publicKey?: string; + signerId?: string; + isCustom?: boolean; + version: 'v5r1' | 'v4r2'; + network: { chainId: string }; + workchain?: number; + walletId?: number; +}) { + const instance = await getKit(); + const network = args.network as unknown as Network; -async function getSigner(args: CreateAdapterArgs): Promise { - if (args.isCustom && args.publicKey) { - return { + let signer; + if (args.isCustom && args.publicKey && args.signerId) { + const signerId = args.signerId; + signer = { + publicKey: args.publicKey as Hex, sign: async (bytes: Iterable): Promise => { - return await signWithCustomSigner(args.signerId, Uint8Array.from(bytes)); + const result = await window.WalletKitNative?.signWithCustomSigner?.(signerId, Array.from(bytes)); + return result as Hex; }, - publicKey: args.publicKey as Hex, }; + } else if (args.secretKey) { + signer = await Signer!.fromPrivateKey(args.secretKey); + } else { + throw new Error('Either secretKey or (publicKey + signerId + isCustom) required'); } - const storedSigner = signerStore.get(args.signerId); - if (!storedSigner) { - throw new Error(`Signer not found: ${args.signerId}`); - } - return storedSigner; -} - -export async function createSigner(args: CreateSignerArgs) { - if (!Signer) { - throw new Error('Signer module not loaded'); - } - if (!args.mnemonic?.length && !args.secretKey) { - throw new Error('Either mnemonic or secretKey is required'); - } - const signer = - args.mnemonic && args.mnemonic.length > 0 - ? ((await Signer.fromMnemonic(args.mnemonic, { type: args.mnemonicType || 'ton' })) as SignerInstance) - : ((await Signer.fromPrivateKey(args.secretKey as string)) as SignerInstance); - - const tempId = `signer_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`; - signerStore.set(tempId, signer); - - return { _tempId: tempId, signer }; -} - -export async function createAdapter(args: CreateAdapterArgs) { - const instance = await getKit(); - const signer = await getSigner(args); - const AdapterClass = args.walletVersion === 'v5r1' ? WalletV5R1Adapter : WalletV4R2Adapter; - if (!AdapterClass) { - throw new Error(`WalletAdapter module not loaded`); - } - const network = args.network as unknown as Network; - const adapter = await AdapterClass.create(signer, { + const AdapterClass = args.version === 'v5r1' ? WalletV5R1Adapter : WalletV4R2Adapter; + const adapter = await AdapterClass!.create(signer, { client: instance.getApiClient(network), network, - workchain: args.workchain, + workchain: args.workchain ?? 0, walletId: args.walletId, }); - const tempId = `adapter_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`; - adapterStore.set(tempId, adapter); - - return { _tempId: tempId, adapter }; -} - -export async function getAdapterAddress(args: { adapterId: string }) { - const adapter = adapterStore.get(args.adapterId) as WalletAdapter | undefined; - if (!adapter) { - throw new Error(`Adapter not found: ${args.adapterId}`); - } - return adapter.getAddress(); + const w = await instance.addWallet(adapter as Parameters[0]); + if (!w) return null; + return { walletId: w.getWalletId?.(), wallet: w }; } -export async function addWallet(args: AddWalletArgs) { +export async function addWallet(args: { + adapterId: string; + walletId: string; + publicKey: string; + network: { chainId: string }; + address: string; +}) { const instance = await getKit(); - const adapter = adapterStore.get(args.adapterId); - if (!adapter) { - throw new Error(`Adapter not found: ${args.adapterId}`); - } - - const w = await instance.addWallet(adapter as Parameters[0]); - adapterStore.delete(args.adapterId); + const { adapterId, walletId, publicKey, address } = args; + const network = args.network as unknown as Network; + const proxyAdapter: WalletAdapter = { + getPublicKey(): Hex { + return publicKey as Hex; + }, + getNetwork(): Network { + return network; + }, + getClient(): ApiClient { + return instance.getApiClient(network); + }, + getAddress(): UserFriendlyAddress { + return address as UserFriendlyAddress; + }, + getWalletId(): WalletId { + return walletId as WalletId; + }, + async getStateInit(): Promise { + const result = await window.WalletKitNative?.adapterGetStateInit?.(adapterId); + if (!result) throw new Error('adapterGetStateInit not available'); + return result as Base64String; + }, + async getSignedSendTransaction( + input: TransactionRequest, + options?: { fakeSignature: boolean }, + ): Promise { + const result = await window.WalletKitNative?.adapterSignTransaction?.( + adapterId, + JSON.stringify(input), + options?.fakeSignature ?? false, + ); + if (!result) throw new Error('adapterSignTransaction not available'); + return result as Base64String; + }, + async getSignedSignData( + input: PreparedSignData, + options?: { fakeSignature: boolean }, + ): Promise { + const result = await window.WalletKitNative?.adapterSignData?.( + adapterId, + JSON.stringify(input), + options?.fakeSignature ?? false, + ); + if (!result) throw new Error('adapterSignData not available'); + return result as Hex; + }, + async getSignedTonProof( + input: ProofMessage, + options?: { fakeSignature: boolean }, + ): Promise { + const result = await window.WalletKitNative?.adapterSignTonProof?.( + adapterId, + JSON.stringify(input), + options?.fakeSignature ?? false, + ); + if (!result) throw new Error('adapterSignTonProof not available'); + return result as Hex; + }, + }; + + const w = await instance.addWallet(proxyAdapter as Parameters[0]); if (!w) return null; return { walletId: w.getWalletId?.(), wallet: w }; } diff --git a/packages/walletkit-android-bridge/src/types/api.ts b/packages/walletkit-android-bridge/src/types/api.ts index 0c4233808..d1a3c9be6 100644 --- a/packages/walletkit-android-bridge/src/types/api.ts +++ b/packages/walletkit-android-bridge/src/types/api.ts @@ -22,9 +22,7 @@ import type { TransactionEmulatedPreview, TransactionRequest, Wallet, - WalletAdapter, WalletResponse, - WalletSigner, } from '@ton/walletkit'; /** @@ -54,24 +52,35 @@ export interface CreateTonMnemonicArgs { count?: number; } -export interface CreateSignerArgs { - mnemonic?: string[]; - secretKey?: string; - mnemonicType?: 'ton' | 'bip39'; +export interface PublicKeyFromSecretKeyArgs { + secretKey: string; } -export interface CreateAdapterArgs { - signerId: string; - walletVersion: 'v4r2' | 'v5r1'; - network: { chainId: string }; // Required - Kotlin must specify the network +export interface ComputeWalletAddressArgs { + publicKey: string; + version: 'v5r1' | 'v4r2'; + network: { chainId: string }; workchain?: number; walletId?: number; +} + +export interface AddWalletWithSignerArgs { + secretKey?: string; publicKey?: string; + signerId?: string; isCustom?: boolean; + version: 'v5r1' | 'v4r2'; + network: { chainId: string }; + workchain?: number; + walletId?: number; } export interface AddWalletArgs { adapterId: string; + walletId: string; + publicKey: string; + network: { chainId: string }; + address: string; } export interface RemoveWalletArgs { @@ -265,38 +274,24 @@ export interface WalletKitBridgeApi { init(config?: WalletKitBridgeInitConfig): PromiseOrValue<{ ok: true }>; setEventsListeners(args?: SetEventsListenersArgs): PromiseOrValue<{ ok: true }>; removeEventListeners(): PromiseOrValue<{ ok: true }>; - // Returns raw keyPair with Uint8Array - Kotlin handles conversion mnemonicToKeyPair(args: MnemonicToKeyPairArgs): PromiseOrValue<{ publicKey: Uint8Array; secretKey: Uint8Array }>; - // Returns signature string directly sign(args: SignArgs): PromiseOrValue; - // Returns mnemonic words array directly createTonMnemonic(args?: CreateTonMnemonicArgs): PromiseOrValue; - // Returns temp ID and signer - Kotlin extracts signerId and publicKey - createSigner(args: CreateSignerArgs): PromiseOrValue<{ _tempId: string; signer: WalletSigner }>; - // Returns temp ID and adapter - Kotlin extracts adapterId and address - createAdapter(args: CreateAdapterArgs): PromiseOrValue<{ _tempId: string; adapter: WalletAdapter }>; - // Returns address string directly - getAdapterAddress(args: { adapterId: string }): PromiseOrValue; - // Returns walletId with wallet object, or null + publicKeyFromSecretKey(args: PublicKeyFromSecretKeyArgs): PromiseOrValue; + computeWalletAddress(args: ComputeWalletAddressArgs): PromiseOrValue; + addWalletWithSigner(args: AddWalletWithSignerArgs): PromiseOrValue<{ walletId: string | undefined; wallet: Wallet } | null>; addWallet(args: AddWalletArgs): PromiseOrValue<{ walletId: string | undefined; wallet: Wallet } | null>; - // Returns array of walletId with wallet objects getWallets(): PromiseOrValue<{ walletId: string | undefined; wallet: Wallet }[]>; - // Takes walletId, returns walletId with wallet object or null getWallet(args: { walletId: string }): PromiseOrValue<{ walletId: string | undefined; wallet: Wallet } | null>; - // Returns address string or null directly getWalletAddress(args: { walletId: string }): PromiseOrValue; - // Returns void removeWallet(args: RemoveWalletArgs): PromiseOrValue; - // Returns balance as string or undefined getBalance(args: GetBalanceArgs): PromiseOrValue; - // Returns transactions array directly getRecentTransactions(args: GetRecentTransactionsArgs): PromiseOrValue; handleTonConnectUrl(args: HandleTonConnectUrlArgs): PromiseOrValue; createTransferTonTransaction(args: CreateTransferTonTransactionArgs): PromiseOrValue; createTransferMultiTonTransaction(args: CreateTransferMultiTonTransactionArgs): PromiseOrValue; getTransactionPreview(args: TransactionContentArgs): PromiseOrValue; handleNewTransaction(args: TransactionContentArgs): PromiseOrValue<{ success: boolean }>; - // Returns result from wallet.sendTransaction sendTransaction(args: TransactionContentArgs): PromiseOrValue; approveConnectRequest(args: ApproveConnectRequestArgs): PromiseOrValue; rejectConnectRequest(args: RejectConnectRequestArgs): PromiseOrValue<{ success: boolean }>; diff --git a/packages/walletkit-android-bridge/src/types/walletkit.ts b/packages/walletkit-android-bridge/src/types/walletkit.ts index ca4f3e63f..95912350b 100644 --- a/packages/walletkit-android-bridge/src/types/walletkit.ts +++ b/packages/walletkit-android-bridge/src/types/walletkit.ts @@ -59,7 +59,10 @@ export interface AndroidBridgeType { export interface WalletKitNativeBridgeType { postMessage(json: string): void; - signWithCustomSigner?(signerId: string, bytes: number[]): Promise; + adapterGetStateInit?(adapterId: string): string; + adapterSignTransaction?(adapterId: string, inputJson: string, fakeSignature: boolean): string; + adapterSignData?(adapterId: string, inputJson: string, fakeSignature: boolean): string; + adapterSignTonProof?(adapterId: string, inputJson: string, fakeSignature: boolean): string; } export type WalletKitAdapter = WalletAdapter; From fc2ac7607d18e254b9cf904af5da40e05e0c446e Mon Sep 17 00:00:00 2001 From: Dmitrii Nikulin Date: Tue, 17 Feb 2026 11:30:31 +0500 Subject: [PATCH 02/10] feat: add NativeRegistry for Android bridge object lifecycle --- .../walletkit-android-bridge/src/api/index.ts | 13 +- .../src/api/wallets.ts | 248 +++++++++--------- .../walletkit-android-bridge/src/types/api.ts | 43 +-- .../src/types/walletkit.ts | 3 + .../src/utils/registry.ts | 33 +++ 5 files changed, 198 insertions(+), 142 deletions(-) create mode 100644 packages/walletkit-android-bridge/src/utils/registry.ts diff --git a/packages/walletkit-android-bridge/src/api/index.ts b/packages/walletkit-android-bridge/src/api/index.ts index 73b6777cb..a1da859d9 100644 --- a/packages/walletkit-android-bridge/src/api/index.ts +++ b/packages/walletkit-android-bridge/src/api/index.ts @@ -34,13 +34,16 @@ export const api: WalletKitBridgeApi = { sign: cryptography.sign, createTonMnemonic: cryptography.createTonMnemonic, - // Wallets — stateless factory helpers - publicKeyFromSecretKey: wallets.publicKeyFromSecretKey, - computeWalletAddress: wallets.computeWalletAddress, - addWalletWithSigner: wallets.addWalletWithSigner, + // Wallets — 3-step factory + createSignerFromMnemonic: wallets.createSignerFromMnemonic, + createSignerFromPrivateKey: wallets.createSignerFromPrivateKey, + createSignerFromCustom: wallets.createSignerFromCustom, + createV5R1WalletAdapter: wallets.createV5R1WalletAdapter, + createV4R2WalletAdapter: wallets.createV4R2WalletAdapter, - // Wallets — proxy adapter (host apps) + // Wallets — unified addWallet (registry path + proxy adapter path) addWallet: wallets.addWallet, + releaseRef: wallets.releaseRef, getWallets: wallets.getWallets, getWallet: wallets.getWalletById, getWalletAddress: wallets.getWalletAddress, diff --git a/packages/walletkit-android-bridge/src/api/wallets.ts b/packages/walletkit-android-bridge/src/api/wallets.ts index 20d6fb953..eae1ec421 100644 --- a/packages/walletkit-android-bridge/src/api/wallets.ts +++ b/packages/walletkit-android-bridge/src/api/wallets.ts @@ -18,6 +18,7 @@ import type { ProofMessage } from '@ton/walletkit'; import { Signer, WalletV4R2Adapter, WalletV5R1Adapter } from '../core/moduleLoader'; import { kit, wallet, getKit } from '../utils/bridge'; +import { retain, retainWithId, get, release } from '../utils/registry'; /** * Lists all wallets. @@ -27,9 +28,7 @@ export async function getWallets() { return wallets.map((w) => ({ walletId: w.getWalletId?.(), wallet: w })); } -/** - * Get a single wallet by walletId. - */ + export async function getWalletById(args: { walletId: string }) { const w = await kit('getWallet', args.walletId); if (!w) return null; @@ -48,157 +47,170 @@ export async function getBalance(args: { walletId: string }) { return wallet(args.walletId, 'getBalance'); } -/** - * Derive public key from a secret key. - */ -export async function publicKeyFromSecretKey(args: { secretKey: string }) { + +export async function createSignerFromMnemonic(args: { mnemonic: string[]; mnemonicType?: string }) { + const signer = await Signer!.fromMnemonic(args.mnemonic, { type: args.mnemonicType ?? 'ton' }); + const signerId = retain('signer', signer); + return { signerId, publicKey: signer.publicKey }; +} + + +export async function createSignerFromPrivateKey(args: { secretKey: string }) { const signer = await Signer!.fromPrivateKey(args.secretKey); - return signer.publicKey; + const signerId = retain('signer', signer); + return { signerId, publicKey: signer.publicKey }; } -/** - * Compute wallet address from public key + version + network. Stateless. - * Creates a temporary adapter just to get the address, then discards it. - */ -export async function computeWalletAddress(args: { - publicKey: string; - version: 'v5r1' | 'v4r2'; + +export async function createSignerFromCustom(args: { signerId: string; publicKey: string }) { + const { signerId, publicKey } = args; + const proxySigner = { + publicKey: publicKey as Hex, + sign: async (bytes: Iterable): Promise => { + const result = await window.WalletKitNative?.signWithCustomSigner?.(signerId, Array.from(bytes)); + if (!result) throw new Error('signWithCustomSigner not available'); + return result as Hex; + }, + }; + retainWithId(signerId, proxySigner); + return { signerId, publicKey }; +} + + +export async function createV5R1WalletAdapter(args: { + signerId: string; network: { chainId: string }; workchain?: number; walletId?: number; }) { const instance = await getKit(); + const signer = get<{ publicKey: Hex; sign: (data: Iterable) => Promise }>(args.signerId); + if (!signer) throw new Error(`Signer not found in registry: ${args.signerId}`); + const network = args.network as unknown as Network; - const dummySigner = { publicKey: args.publicKey as Hex, sign: async () => '0x' as Hex }; - const AdapterClass = args.version === 'v5r1' ? WalletV5R1Adapter : WalletV4R2Adapter; - const adapter = await AdapterClass!.create(dummySigner, { + const adapter = await WalletV5R1Adapter!.create(signer, { client: instance.getApiClient(network), network, workchain: args.workchain ?? 0, walletId: args.walletId, }); - return adapter.getAddress(); + + const adapterId = retain('adapter', adapter); + return { adapterId, address: adapter.getAddress() }; } -/** - * Create signer + adapter + add wallet in one stateless call. - * For key-based signers: pass secretKey. JS creates signer from it. - * For custom signers: pass publicKey + signerId + isCustom. JS creates a signer - * that delegates signing back to Kotlin via WalletKitNative.signWithCustomSigner. - */ -export async function addWalletWithSigner(args: { - secretKey?: string; - publicKey?: string; - signerId?: string; - isCustom?: boolean; - version: 'v5r1' | 'v4r2'; + +export async function createV4R2WalletAdapter(args: { + signerId: string; network: { chainId: string }; workchain?: number; walletId?: number; }) { const instance = await getKit(); - const network = args.network as unknown as Network; + const signer = get<{ publicKey: Hex; sign: (data: Iterable) => Promise }>(args.signerId); + if (!signer) throw new Error(`Signer not found in registry: ${args.signerId}`); - let signer; - if (args.isCustom && args.publicKey && args.signerId) { - const signerId = args.signerId; - signer = { - publicKey: args.publicKey as Hex, - sign: async (bytes: Iterable): Promise => { - const result = await window.WalletKitNative?.signWithCustomSigner?.(signerId, Array.from(bytes)); - return result as Hex; - }, - }; - } else if (args.secretKey) { - signer = await Signer!.fromPrivateKey(args.secretKey); - } else { - throw new Error('Either secretKey or (publicKey + signerId + isCustom) required'); - } - - const AdapterClass = args.version === 'v5r1' ? WalletV5R1Adapter : WalletV4R2Adapter; - const adapter = await AdapterClass!.create(signer, { + const network = args.network as unknown as Network; + const adapter = await WalletV4R2Adapter!.create(signer, { client: instance.getApiClient(network), network, workchain: args.workchain ?? 0, walletId: args.walletId, }); - const w = await instance.addWallet(adapter as Parameters[0]); - if (!w) return null; - return { walletId: w.getWalletId?.(), wallet: w }; + const adapterId = retain('adapter', adapter); + return { adapterId, address: adapter.getAddress() }; } + export async function addWallet(args: { adapterId: string; - walletId: string; - publicKey: string; - network: { chainId: string }; - address: string; + walletId?: string; + publicKey?: string; + network?: { chainId: string }; + address?: string; }) { const instance = await getKit(); - const { adapterId, walletId, publicKey, address } = args; - const network = args.network as unknown as Network; - const proxyAdapter: WalletAdapter = { - getPublicKey(): Hex { - return publicKey as Hex; - }, - getNetwork(): Network { - return network; - }, - getClient(): ApiClient { - return instance.getApiClient(network); - }, - getAddress(): UserFriendlyAddress { - return address as UserFriendlyAddress; - }, - getWalletId(): WalletId { - return walletId as WalletId; - }, - async getStateInit(): Promise { - const result = await window.WalletKitNative?.adapterGetStateInit?.(adapterId); - if (!result) throw new Error('adapterGetStateInit not available'); - return result as Base64String; - }, - async getSignedSendTransaction( - input: TransactionRequest, - options?: { fakeSignature: boolean }, - ): Promise { - const result = await window.WalletKitNative?.adapterSignTransaction?.( - adapterId, - JSON.stringify(input), - options?.fakeSignature ?? false, - ); - if (!result) throw new Error('adapterSignTransaction not available'); - return result as Base64String; - }, - async getSignedSignData( - input: PreparedSignData, - options?: { fakeSignature: boolean }, - ): Promise { - const result = await window.WalletKitNative?.adapterSignData?.( - adapterId, - JSON.stringify(input), - options?.fakeSignature ?? false, - ); - if (!result) throw new Error('adapterSignData not available'); - return result as Hex; - }, - async getSignedTonProof( - input: ProofMessage, - options?: { fakeSignature: boolean }, - ): Promise { - const result = await window.WalletKitNative?.adapterSignTonProof?.( - adapterId, - JSON.stringify(input), - options?.fakeSignature ?? false, - ); - if (!result) throw new Error('adapterSignTonProof not available'); - return result as Hex; - }, - }; + if (args.publicKey) { + const { adapterId, walletId, publicKey, address } = args; + const network = args.network as unknown as Network; - const w = await instance.addWallet(proxyAdapter as Parameters[0]); + const proxyAdapter: WalletAdapter = { + getPublicKey(): Hex { + return publicKey as Hex; + }, + getNetwork(): Network { + return network; + }, + getClient(): ApiClient { + return instance.getApiClient(network); + }, + getAddress(): UserFriendlyAddress { + return address as UserFriendlyAddress; + }, + getWalletId(): WalletId { + return walletId as WalletId; + }, + async getStateInit(): Promise { + const result = await window.WalletKitNative?.adapterGetStateInit?.(adapterId); + if (!result) throw new Error('adapterGetStateInit not available'); + return result as Base64String; + }, + async getSignedSendTransaction( + input: TransactionRequest, + options?: { fakeSignature: boolean }, + ): Promise { + const result = await window.WalletKitNative?.adapterSignTransaction?.( + adapterId, + JSON.stringify(input), + options?.fakeSignature ?? false, + ); + if (!result) throw new Error('adapterSignTransaction not available'); + return result as Base64String; + }, + async getSignedSignData( + input: PreparedSignData, + options?: { fakeSignature: boolean }, + ): Promise { + const result = await window.WalletKitNative?.adapterSignData?.( + adapterId, + JSON.stringify(input), + options?.fakeSignature ?? false, + ); + if (!result) throw new Error('adapterSignData not available'); + return result as Hex; + }, + async getSignedTonProof( + input: ProofMessage, + options?: { fakeSignature: boolean }, + ): Promise { + const result = await window.WalletKitNative?.adapterSignTonProof?.( + adapterId, + JSON.stringify(input), + options?.fakeSignature ?? false, + ); + if (!result) throw new Error('adapterSignTonProof not available'); + return result as Hex; + }, + }; + + const w = await instance.addWallet(proxyAdapter as Parameters[0]); + if (!w) return null; + return { walletId: w.getWalletId?.(), wallet: w }; + } + + const adapter = get(args.adapterId); + if (!adapter) throw new Error(`Adapter not found in registry: ${args.adapterId}`); + release(args.adapterId); + + const w = await instance.addWallet(adapter as Parameters[0]); if (!w) return null; return { walletId: w.getWalletId?.(), wallet: w }; } + + +export function releaseRef(args: { id: string }) { + release(args.id); + return { ok: true }; +} diff --git a/packages/walletkit-android-bridge/src/types/api.ts b/packages/walletkit-android-bridge/src/types/api.ts index d1a3c9be6..6073fcf66 100644 --- a/packages/walletkit-android-bridge/src/types/api.ts +++ b/packages/walletkit-android-bridge/src/types/api.ts @@ -52,24 +52,22 @@ export interface CreateTonMnemonicArgs { count?: number; } -export interface PublicKeyFromSecretKeyArgs { +export interface CreateSignerFromMnemonicArgs { + mnemonic: string[]; + mnemonicType?: string; +} + +export interface CreateSignerFromPrivateKeyArgs { secretKey: string; } -export interface ComputeWalletAddressArgs { +export interface CreateSignerFromCustomArgs { + signerId: string; publicKey: string; - version: 'v5r1' | 'v4r2'; - network: { chainId: string }; - workchain?: number; - walletId?: number; } -export interface AddWalletWithSignerArgs { - secretKey?: string; - publicKey?: string; - signerId?: string; - isCustom?: boolean; - version: 'v5r1' | 'v4r2'; +export interface CreateWalletAdapterArgs { + signerId: string; network: { chainId: string }; workchain?: number; walletId?: number; @@ -77,10 +75,14 @@ export interface AddWalletWithSignerArgs { export interface AddWalletArgs { adapterId: string; - walletId: string; - publicKey: string; - network: { chainId: string }; - address: string; + walletId?: string; + publicKey?: string; + network?: { chainId: string }; + address?: string; +} + +export interface ReleaseRefArgs { + id: string; } export interface RemoveWalletArgs { @@ -277,10 +279,13 @@ export interface WalletKitBridgeApi { mnemonicToKeyPair(args: MnemonicToKeyPairArgs): PromiseOrValue<{ publicKey: Uint8Array; secretKey: Uint8Array }>; sign(args: SignArgs): PromiseOrValue; createTonMnemonic(args?: CreateTonMnemonicArgs): PromiseOrValue; - publicKeyFromSecretKey(args: PublicKeyFromSecretKeyArgs): PromiseOrValue; - computeWalletAddress(args: ComputeWalletAddressArgs): PromiseOrValue; - addWalletWithSigner(args: AddWalletWithSignerArgs): PromiseOrValue<{ walletId: string | undefined; wallet: Wallet } | null>; + createSignerFromMnemonic(args: CreateSignerFromMnemonicArgs): PromiseOrValue<{ signerId: string; publicKey: string }>; + createSignerFromPrivateKey(args: CreateSignerFromPrivateKeyArgs): PromiseOrValue<{ signerId: string; publicKey: string }>; + createSignerFromCustom(args: CreateSignerFromCustomArgs): PromiseOrValue<{ signerId: string; publicKey: string }>; + createV5R1WalletAdapter(args: CreateWalletAdapterArgs): PromiseOrValue<{ adapterId: string; address: string }>; + createV4R2WalletAdapter(args: CreateWalletAdapterArgs): PromiseOrValue<{ adapterId: string; address: string }>; addWallet(args: AddWalletArgs): PromiseOrValue<{ walletId: string | undefined; wallet: Wallet } | null>; + releaseRef(args: ReleaseRefArgs): PromiseOrValue<{ ok: boolean }>; getWallets(): PromiseOrValue<{ walletId: string | undefined; wallet: Wallet }[]>; getWallet(args: { walletId: string }): PromiseOrValue<{ walletId: string | undefined; wallet: Wallet } | null>; getWalletAddress(args: { walletId: string }): PromiseOrValue; diff --git a/packages/walletkit-android-bridge/src/types/walletkit.ts b/packages/walletkit-android-bridge/src/types/walletkit.ts index 95912350b..0d6deaedb 100644 --- a/packages/walletkit-android-bridge/src/types/walletkit.ts +++ b/packages/walletkit-android-bridge/src/types/walletkit.ts @@ -59,6 +59,9 @@ export interface AndroidBridgeType { export interface WalletKitNativeBridgeType { postMessage(json: string): void; + // Custom signer callback: JS delegates sign() to Kotlin for custom signers + signWithCustomSigner?(signerId: string, data: number[]): string; + // Proxy adapter callbacks: JS delegates adapter methods to Kotlin adapterGetStateInit?(adapterId: string): string; adapterSignTransaction?(adapterId: string, inputJson: string, fakeSignature: boolean): string; adapterSignData?(adapterId: string, inputJson: string, fakeSignature: boolean): string; diff --git a/packages/walletkit-android-bridge/src/utils/registry.ts b/packages/walletkit-android-bridge/src/utils/registry.ts new file mode 100644 index 000000000..fff823f0e --- /dev/null +++ b/packages/walletkit-android-bridge/src/utils/registry.ts @@ -0,0 +1,33 @@ +/** + * Copyright (c) TonTech. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +/** + * Holds live JS objects by string ID so Kotlin can reference them + * across WebView bridge calls (evaluateJavascript only returns strings). + */ + +const store = new Map(); +let nextId = 1; + +export function retain(prefix: string, obj: unknown): string { + const id = `${prefix}_${nextId++}`; + store.set(id, obj); + return id; +} + +export function retainWithId(id: string, obj: unknown): void { + store.set(id, obj); +} + +export function get(id: string): T | undefined { + return store.get(id) as T | undefined; +} + +export function release(id: string): boolean { + return store.delete(id); +} From 88225dcb37857b4644eafad6f453b9f39269d5a4 Mon Sep 17 00:00:00 2001 From: Dmitrii Nikulin Date: Tue, 17 Feb 2026 15:29:55 +0500 Subject: [PATCH 03/10] fix: fix lint --- .../src/api/wallets.ts | 18 ++---------------- .../walletkit-android-bridge/src/types/api.ts | 8 ++++++-- 2 files changed, 8 insertions(+), 18 deletions(-) diff --git a/packages/walletkit-android-bridge/src/api/wallets.ts b/packages/walletkit-android-bridge/src/api/wallets.ts index eae1ec421..577f4304c 100644 --- a/packages/walletkit-android-bridge/src/api/wallets.ts +++ b/packages/walletkit-android-bridge/src/api/wallets.ts @@ -28,7 +28,6 @@ export async function getWallets() { return wallets.map((w) => ({ walletId: w.getWalletId?.(), wallet: w })); } - export async function getWalletById(args: { walletId: string }) { const w = await kit('getWallet', args.walletId); if (!w) return null; @@ -47,21 +46,18 @@ export async function getBalance(args: { walletId: string }) { return wallet(args.walletId, 'getBalance'); } - export async function createSignerFromMnemonic(args: { mnemonic: string[]; mnemonicType?: string }) { const signer = await Signer!.fromMnemonic(args.mnemonic, { type: args.mnemonicType ?? 'ton' }); const signerId = retain('signer', signer); return { signerId, publicKey: signer.publicKey }; } - export async function createSignerFromPrivateKey(args: { secretKey: string }) { const signer = await Signer!.fromPrivateKey(args.secretKey); const signerId = retain('signer', signer); return { signerId, publicKey: signer.publicKey }; } - export async function createSignerFromCustom(args: { signerId: string; publicKey: string }) { const { signerId, publicKey } = args; const proxySigner = { @@ -76,7 +72,6 @@ export async function createSignerFromCustom(args: { signerId: string; publicKey return { signerId, publicKey }; } - export async function createV5R1WalletAdapter(args: { signerId: string; network: { chainId: string }; @@ -99,7 +94,6 @@ export async function createV5R1WalletAdapter(args: { return { adapterId, address: adapter.getAddress() }; } - export async function createV4R2WalletAdapter(args: { signerId: string; network: { chainId: string }; @@ -122,7 +116,6 @@ export async function createV4R2WalletAdapter(args: { return { adapterId, address: adapter.getAddress() }; } - export async function addWallet(args: { adapterId: string; walletId?: string; @@ -169,10 +162,7 @@ export async function addWallet(args: { if (!result) throw new Error('adapterSignTransaction not available'); return result as Base64String; }, - async getSignedSignData( - input: PreparedSignData, - options?: { fakeSignature: boolean }, - ): Promise { + async getSignedSignData(input: PreparedSignData, options?: { fakeSignature: boolean }): Promise { const result = await window.WalletKitNative?.adapterSignData?.( adapterId, JSON.stringify(input), @@ -181,10 +171,7 @@ export async function addWallet(args: { if (!result) throw new Error('adapterSignData not available'); return result as Hex; }, - async getSignedTonProof( - input: ProofMessage, - options?: { fakeSignature: boolean }, - ): Promise { + async getSignedTonProof(input: ProofMessage, options?: { fakeSignature: boolean }): Promise { const result = await window.WalletKitNative?.adapterSignTonProof?.( adapterId, JSON.stringify(input), @@ -209,7 +196,6 @@ export async function addWallet(args: { return { walletId: w.getWalletId?.(), wallet: w }; } - export function releaseRef(args: { id: string }) { release(args.id); return { ok: true }; diff --git a/packages/walletkit-android-bridge/src/types/api.ts b/packages/walletkit-android-bridge/src/types/api.ts index 6073fcf66..f9a2114c3 100644 --- a/packages/walletkit-android-bridge/src/types/api.ts +++ b/packages/walletkit-android-bridge/src/types/api.ts @@ -279,8 +279,12 @@ export interface WalletKitBridgeApi { mnemonicToKeyPair(args: MnemonicToKeyPairArgs): PromiseOrValue<{ publicKey: Uint8Array; secretKey: Uint8Array }>; sign(args: SignArgs): PromiseOrValue; createTonMnemonic(args?: CreateTonMnemonicArgs): PromiseOrValue; - createSignerFromMnemonic(args: CreateSignerFromMnemonicArgs): PromiseOrValue<{ signerId: string; publicKey: string }>; - createSignerFromPrivateKey(args: CreateSignerFromPrivateKeyArgs): PromiseOrValue<{ signerId: string; publicKey: string }>; + createSignerFromMnemonic( + args: CreateSignerFromMnemonicArgs, + ): PromiseOrValue<{ signerId: string; publicKey: string }>; + createSignerFromPrivateKey( + args: CreateSignerFromPrivateKeyArgs, + ): PromiseOrValue<{ signerId: string; publicKey: string }>; createSignerFromCustom(args: CreateSignerFromCustomArgs): PromiseOrValue<{ signerId: string; publicKey: string }>; createV5R1WalletAdapter(args: CreateWalletAdapterArgs): PromiseOrValue<{ adapterId: string; address: string }>; createV4R2WalletAdapter(args: CreateWalletAdapterArgs): PromiseOrValue<{ adapterId: string; address: string }>; From fc3b13ff2b6284bd05b9ba213a666b47f2878c7e Mon Sep 17 00:00:00 2001 From: Dmitrii Nikulin Date: Fri, 20 Feb 2026 13:15:34 +0500 Subject: [PATCH 04/10] fix: add error handling for missing Signer and Wallet adapter modules --- packages/walletkit-android-bridge/src/api/wallets.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/walletkit-android-bridge/src/api/wallets.ts b/packages/walletkit-android-bridge/src/api/wallets.ts index 577f4304c..6bbd4b69a 100644 --- a/packages/walletkit-android-bridge/src/api/wallets.ts +++ b/packages/walletkit-android-bridge/src/api/wallets.ts @@ -47,13 +47,15 @@ export async function getBalance(args: { walletId: string }) { } export async function createSignerFromMnemonic(args: { mnemonic: string[]; mnemonicType?: string }) { - const signer = await Signer!.fromMnemonic(args.mnemonic, { type: args.mnemonicType ?? 'ton' }); + if (!Signer) throw new Error('Signer module not loaded'); + const signer = await Signer.fromMnemonic(args.mnemonic, { type: args.mnemonicType ?? 'ton' }); const signerId = retain('signer', signer); return { signerId, publicKey: signer.publicKey }; } export async function createSignerFromPrivateKey(args: { secretKey: string }) { - const signer = await Signer!.fromPrivateKey(args.secretKey); + if (!Signer) throw new Error('Signer module not loaded'); + const signer = await Signer.fromPrivateKey(args.secretKey); const signerId = retain('signer', signer); return { signerId, publicKey: signer.publicKey }; } @@ -83,7 +85,8 @@ export async function createV5R1WalletAdapter(args: { if (!signer) throw new Error(`Signer not found in registry: ${args.signerId}`); const network = args.network as unknown as Network; - const adapter = await WalletV5R1Adapter!.create(signer, { + if (!WalletV5R1Adapter) throw new Error('WalletV5R1Adapter module not loaded'); + const adapter = await WalletV5R1Adapter.create(signer, { client: instance.getApiClient(network), network, workchain: args.workchain ?? 0, @@ -105,7 +108,8 @@ export async function createV4R2WalletAdapter(args: { if (!signer) throw new Error(`Signer not found in registry: ${args.signerId}`); const network = args.network as unknown as Network; - const adapter = await WalletV4R2Adapter!.create(signer, { + if (!WalletV4R2Adapter) throw new Error('WalletV4R2Adapter module not loaded'); + const adapter = await WalletV4R2Adapter.create(signer, { client: instance.getApiClient(network), network, workchain: args.workchain ?? 0, From 478ad474627096648d24bbd2256668f437e2814a Mon Sep 17 00:00:00 2001 From: Dmitrii Nikulin Date: Mon, 23 Feb 2026 21:43:26 +0530 Subject: [PATCH 05/10] feat: implement ProxyWalletAdapter and enhance native bridge communication --- .../walletkit-android-bridge/src/api/index.ts | 1 + .../src/api/wallets.ts | 160 ++++++++++++------ .../walletkit-android-bridge/src/bridge.ts | 2 + .../walletkit-android-bridge/src/globals.d.ts | 1 + .../src/transport/nativeBridge.ts | 61 ++++++- .../walletkit-android-bridge/src/types/api.ts | 5 + .../src/types/bridge.ts | 3 +- .../src/types/walletkit.ts | 7 - 8 files changed, 176 insertions(+), 64 deletions(-) diff --git a/packages/walletkit-android-bridge/src/api/index.ts b/packages/walletkit-android-bridge/src/api/index.ts index a1da859d9..bcf6c85fa 100644 --- a/packages/walletkit-android-bridge/src/api/index.ts +++ b/packages/walletkit-android-bridge/src/api/index.ts @@ -44,6 +44,7 @@ export const api: WalletKitBridgeApi = { // Wallets — unified addWallet (registry path + proxy adapter path) addWallet: wallets.addWallet, releaseRef: wallets.releaseRef, + releaseAdapter: wallets.releaseAdapter, getWallets: wallets.getWallets, getWallet: wallets.getWalletById, getWalletAddress: wallets.getWalletAddress, diff --git a/packages/walletkit-android-bridge/src/api/wallets.ts b/packages/walletkit-android-bridge/src/api/wallets.ts index 6bbd4b69a..fb4937f05 100644 --- a/packages/walletkit-android-bridge/src/api/wallets.ts +++ b/packages/walletkit-android-bridge/src/api/wallets.ts @@ -19,6 +19,82 @@ import type { ProofMessage } from '@ton/walletkit'; import { Signer, WalletV4R2Adapter, WalletV5R1Adapter } from '../core/moduleLoader'; import { kit, wallet, getKit } from '../utils/bridge'; import { retain, retainWithId, get, release } from '../utils/registry'; +import { bridgeRequest } from '../transport/nativeBridge'; + +// ────────────────────────────────────────────────────────────────────────────── +// ProxyWalletAdapter +// +// Wraps a Kotlin-side TONWalletAdapter registered in AdapterManager. +// Data accessors (publicKey, network, address, walletId) use cached values +// passed at construction. Signing/stateInit operations delegate to Kotlin +// via the reverse-RPC `bridgeRequest` channel. +// ────────────────────────────────────────────────────────────────────────────── + +class ProxyWalletAdapter implements WalletAdapter { + constructor( + private readonly adapterId: string, + private readonly _publicKey: Hex, + private readonly _network: Network, + private readonly _address: UserFriendlyAddress, + private readonly _walletId: WalletId, + private readonly _client: ApiClient, + ) {} + + getPublicKey(): Hex { + return this._publicKey; + } + getNetwork(): Network { + return this._network; + } + getClient(): ApiClient { + return this._client; + } + getAddress(): UserFriendlyAddress { + return this._address; + } + getWalletId(): WalletId { + return this._walletId; + } + + async getStateInit(): Promise { + const result = await bridgeRequest('adapterGetStateInit', { adapterId: this.adapterId }); + if (!result) throw new Error('adapterGetStateInit: no result from native'); + return result as Base64String; + } + + async getSignedSendTransaction( + input: TransactionRequest, + options?: { fakeSignature: boolean }, + ): Promise { + const result = await bridgeRequest('adapterSignTransaction', { + adapterId: this.adapterId, + input: JSON.stringify(input), + fakeSignature: options?.fakeSignature ?? false, + }); + if (!result) throw new Error('adapterSignTransaction: no result from native'); + return result as Base64String; + } + + async getSignedSignData(input: PreparedSignData, options?: { fakeSignature: boolean }): Promise { + const result = await bridgeRequest('adapterSignData', { + adapterId: this.adapterId, + input: JSON.stringify(input), + fakeSignature: options?.fakeSignature ?? false, + }); + if (!result) throw new Error('adapterSignData: no result from native'); + return result as Hex; + } + + async getSignedTonProof(input: ProofMessage, options?: { fakeSignature: boolean }): Promise { + const result = await bridgeRequest('adapterSignTonProof', { + adapterId: this.adapterId, + input: JSON.stringify(input), + fakeSignature: options?.fakeSignature ?? false, + }); + if (!result) throw new Error('adapterSignTonProof: no result from native'); + return result as Hex; + } +} /** * Lists all wallets. @@ -65,8 +141,11 @@ export async function createSignerFromCustom(args: { signerId: string; publicKey const proxySigner = { publicKey: publicKey as Hex, sign: async (bytes: Iterable): Promise => { - const result = await window.WalletKitNative?.signWithCustomSigner?.(signerId, Array.from(bytes)); - if (!result) throw new Error('signWithCustomSigner not available'); + const result = await bridgeRequest('signWithCustomSigner', { + signerId, + data: Array.from(bytes), + }); + if (!result) throw new Error('signWithCustomSigner: no result from native'); return result as Hex; }, }; @@ -133,58 +212,14 @@ export async function addWallet(args: { const { adapterId, walletId, publicKey, address } = args; const network = args.network as unknown as Network; - const proxyAdapter: WalletAdapter = { - getPublicKey(): Hex { - return publicKey as Hex; - }, - getNetwork(): Network { - return network; - }, - getClient(): ApiClient { - return instance.getApiClient(network); - }, - getAddress(): UserFriendlyAddress { - return address as UserFriendlyAddress; - }, - getWalletId(): WalletId { - return walletId as WalletId; - }, - async getStateInit(): Promise { - const result = await window.WalletKitNative?.adapterGetStateInit?.(adapterId); - if (!result) throw new Error('adapterGetStateInit not available'); - return result as Base64String; - }, - async getSignedSendTransaction( - input: TransactionRequest, - options?: { fakeSignature: boolean }, - ): Promise { - const result = await window.WalletKitNative?.adapterSignTransaction?.( - adapterId, - JSON.stringify(input), - options?.fakeSignature ?? false, - ); - if (!result) throw new Error('adapterSignTransaction not available'); - return result as Base64String; - }, - async getSignedSignData(input: PreparedSignData, options?: { fakeSignature: boolean }): Promise { - const result = await window.WalletKitNative?.adapterSignData?.( - adapterId, - JSON.stringify(input), - options?.fakeSignature ?? false, - ); - if (!result) throw new Error('adapterSignData not available'); - return result as Hex; - }, - async getSignedTonProof(input: ProofMessage, options?: { fakeSignature: boolean }): Promise { - const result = await window.WalletKitNative?.adapterSignTonProof?.( - adapterId, - JSON.stringify(input), - options?.fakeSignature ?? false, - ); - if (!result) throw new Error('adapterSignTonProof not available'); - return result as Hex; - }, - }; + const proxyAdapter = new ProxyWalletAdapter( + adapterId, + publicKey as Hex, + network, + address as UserFriendlyAddress, + walletId as WalletId, + instance.getApiClient(network), + ); const w = await instance.addWallet(proxyAdapter as Parameters[0]); if (!w) return null; @@ -193,14 +228,29 @@ export async function addWallet(args: { const adapter = get(args.adapterId); if (!adapter) throw new Error(`Adapter not found in registry: ${args.adapterId}`); - release(args.adapterId); const w = await instance.addWallet(adapter as Parameters[0]); if (!w) return null; return { walletId: w.getWalletId?.(), wallet: w }; } +/** + * Releases a JS-side registry object (signer or adapter created by createV5R1/createV4R2). + */ export function releaseRef(args: { id: string }) { release(args.id); return { ok: true }; } + +/** + * Releases a Kotlin-side native adapter registered in AdapterManager. + * Called by Kotlin when adapter.close() is invoked. + * For proxy adapters the JS side has no registry entry — this is a no-op on JS, + * but the Kotlin side uses it to confirm cleanup is complete. + */ +export function releaseAdapter(args: { adapterId: string }) { + // Proxy adapters aren't stored in the JS registry (they're closures over adapterId). + // But if a JS-side adapter was also retained (createV5R1/createV4R2), clean it up. + release(args.adapterId); + return { ok: true }; +} diff --git a/packages/walletkit-android-bridge/src/bridge.ts b/packages/walletkit-android-bridge/src/bridge.ts index c16698614..a9b964a2f 100644 --- a/packages/walletkit-android-bridge/src/bridge.ts +++ b/packages/walletkit-android-bridge/src/bridge.ts @@ -9,6 +9,7 @@ import type { WalletKitBridgeApi } from './types'; import { api } from './api'; import { setBridgeApi, registerNativeCallHandler } from './transport/messaging'; +import { registerNativeResponseHandler } from './transport/nativeBridge'; declare global { interface Window { @@ -18,6 +19,7 @@ declare global { setBridgeApi(api as WalletKitBridgeApi); registerNativeCallHandler(); +registerNativeResponseHandler(); window.walletkitBridge = api; diff --git a/packages/walletkit-android-bridge/src/globals.d.ts b/packages/walletkit-android-bridge/src/globals.d.ts index 6a52f3d87..4056e1dad 100644 --- a/packages/walletkit-android-bridge/src/globals.d.ts +++ b/packages/walletkit-android-bridge/src/globals.d.ts @@ -20,6 +20,7 @@ declare global { interface Window { walletkitBridge?: WalletKitBridgeApi; __walletkitCall?: (id: string, method: WalletKitApiMethod, paramsJson?: string | null) => void; + __walletkitResponse?: (id: string, resultJson?: string | null, errorJson?: string | null) => void; WalletKitNative?: WalletKitNativeBridgeType; AndroidBridge?: AndroidBridgeType; } diff --git a/packages/walletkit-android-bridge/src/transport/nativeBridge.ts b/packages/walletkit-android-bridge/src/transport/nativeBridge.ts index 14a976e03..2e54fc4a1 100644 --- a/packages/walletkit-android-bridge/src/transport/nativeBridge.ts +++ b/packages/walletkit-android-bridge/src/transport/nativeBridge.ts @@ -11,7 +11,66 @@ */ import type { BridgePayload } from '../types'; import { bigIntReplacer } from '../utils/serialization'; -import { warn, error } from '../utils/logger'; +import { warn, error, info } from '../utils/logger'; + +// ────────────────────────────────────────────────────────────────────────────── +// Reverse-RPC: lets JS request work from Kotlin (adapter/signer proxy calls). +// JS sends {kind:'request', id, method, params} via postMessage. +// Kotlin responds by calling window.__walletkitResponse(id, resultJson, errorJson). +// ────────────────────────────────────────────────────────────────────────────── + +const pendingRequests = new Map void; reject: (e: Error) => void }>(); +let nextRequestId = 1; + +/** + * Send a request to the native (Kotlin) side and wait for a response. + * + * The native side is expected to call `window.__walletkitResponse(id, resultJson, errorJson)` + * when the work is done. + */ +export function bridgeRequest(method: string, params: Record): Promise { + const id = `req_${nextRequestId++}`; + return new Promise((resolve, reject) => { + pendingRequests.set(id, { resolve, reject }); + postToNative({ kind: 'request', id, method, params }); + }); +} + +/** + * Registers the global handler that Kotlin invokes to deliver reverse-RPC responses. + */ +export function registerNativeResponseHandler(): void { + window.__walletkitResponse = (id: string, resultJson?: string | null, errorJson?: string | null) => { + const entry = pendingRequests.get(id); + if (!entry) { + warn('[walletkitBridge] __walletkitResponse: no pending request for id', id); + return; + } + pendingRequests.delete(id); + + if (errorJson) { + try { + const err = JSON.parse(errorJson); + entry.reject(new Error(err.message ?? 'Native request failed')); + } catch { + entry.reject(new Error(errorJson)); + } + return; + } + + if (resultJson) { + try { + entry.resolve(JSON.parse(resultJson)); + } catch { + // If it's not JSON, return the raw string + entry.resolve(resultJson); + } + } else { + entry.resolve(undefined); + } + }; + info('[walletkitBridge] __walletkitResponse handler registered'); +} /** * Resolves WalletKit's native bridge implementation exposed on the global scope. diff --git a/packages/walletkit-android-bridge/src/types/api.ts b/packages/walletkit-android-bridge/src/types/api.ts index f9a2114c3..45c407ed4 100644 --- a/packages/walletkit-android-bridge/src/types/api.ts +++ b/packages/walletkit-android-bridge/src/types/api.ts @@ -85,6 +85,10 @@ export interface ReleaseRefArgs { id: string; } +export interface ReleaseAdapterArgs { + adapterId: string; +} + export interface RemoveWalletArgs { walletId: string; } @@ -290,6 +294,7 @@ export interface WalletKitBridgeApi { createV4R2WalletAdapter(args: CreateWalletAdapterArgs): PromiseOrValue<{ adapterId: string; address: string }>; addWallet(args: AddWalletArgs): PromiseOrValue<{ walletId: string | undefined; wallet: Wallet } | null>; releaseRef(args: ReleaseRefArgs): PromiseOrValue<{ ok: boolean }>; + releaseAdapter(args: ReleaseAdapterArgs): PromiseOrValue<{ ok: boolean }>; getWallets(): PromiseOrValue<{ walletId: string | undefined; wallet: Wallet }[]>; getWallet(args: { walletId: string }): PromiseOrValue<{ walletId: string | undefined; wallet: Wallet } | null>; getWalletAddress(args: { walletId: string }): PromiseOrValue; diff --git a/packages/walletkit-android-bridge/src/types/bridge.ts b/packages/walletkit-android-bridge/src/types/bridge.ts index 34d4b532e..ccb52e797 100644 --- a/packages/walletkit-android-bridge/src/types/bridge.ts +++ b/packages/walletkit-android-bridge/src/types/bridge.ts @@ -39,7 +39,8 @@ export type BridgePayload = timestamp: number; message?: string; } - | { kind: 'jsBridgeEvent'; sessionId: string; event: JsBridgeTransportMessage }; + | { kind: 'jsBridgeEvent'; sessionId: string; event: JsBridgeTransportMessage } + | { kind: 'request'; id: string; method: string; params: Record }; export interface CallContext { id: string; diff --git a/packages/walletkit-android-bridge/src/types/walletkit.ts b/packages/walletkit-android-bridge/src/types/walletkit.ts index 0d6deaedb..5f7f97e89 100644 --- a/packages/walletkit-android-bridge/src/types/walletkit.ts +++ b/packages/walletkit-android-bridge/src/types/walletkit.ts @@ -59,13 +59,6 @@ export interface AndroidBridgeType { export interface WalletKitNativeBridgeType { postMessage(json: string): void; - // Custom signer callback: JS delegates sign() to Kotlin for custom signers - signWithCustomSigner?(signerId: string, data: number[]): string; - // Proxy adapter callbacks: JS delegates adapter methods to Kotlin - adapterGetStateInit?(adapterId: string): string; - adapterSignTransaction?(adapterId: string, inputJson: string, fakeSignature: boolean): string; - adapterSignData?(adapterId: string, inputJson: string, fakeSignature: boolean): string; - adapterSignTonProof?(adapterId: string, inputJson: string, fakeSignature: boolean): string; } export type WalletKitAdapter = WalletAdapter; From 2adb439b78e7e0700d309c9761fb443eb65f483f Mon Sep 17 00:00:00 2001 From: Dmitrii Nikulin Date: Tue, 24 Feb 2026 00:12:13 +0530 Subject: [PATCH 06/10] refactor: clean up --- .../src/api/eventListeners.ts | 18 +++----- .../walletkit-android-bridge/src/api/index.ts | 3 -- .../src/api/jettons.ts | 6 --- .../walletkit-android-bridge/src/api/nft.ts | 6 --- .../src/api/transactions.ts | 6 --- .../walletkit-android-bridge/src/bridge.ts | 3 -- .../src/core/initialization.ts | 11 +---- .../walletkit-android-bridge/src/globals.d.ts | 2 - .../src/polyfills/setupNativeBridge.ts | 10 +---- .../src/transport/messaging.ts | 31 ------------- .../walletkit-android-bridge/src/types/api.ts | 17 ------- .../src/types/events.ts | 2 - .../src/utils/bridge.ts | 44 +------------------ .../src/utils/internalBrowserResolvers.ts | 8 +--- .../src/utils/logger.ts | 27 +----------- 15 files changed, 14 insertions(+), 180 deletions(-) diff --git a/packages/walletkit-android-bridge/src/api/eventListeners.ts b/packages/walletkit-android-bridge/src/api/eventListeners.ts index ebef79dee..c72751963 100644 --- a/packages/walletkit-android-bridge/src/api/eventListeners.ts +++ b/packages/walletkit-android-bridge/src/api/eventListeners.ts @@ -14,19 +14,13 @@ import type { SignDataRequestEvent, } from '@ton/walletkit'; -/** - * Shared event listener references used to manage WalletKit callbacks. - */ -export type ConnectEventListener = ((event: ConnectionRequestEvent) => void) | null; -export type TransactionEventListener = ((event: SendTransactionRequestEvent) => void) | null; -export type SignDataEventListener = ((event: SignDataRequestEvent) => void) | null; -export type DisconnectEventListener = ((event: DisconnectionEvent) => void) | null; -export type ErrorEventListener = ((event: RequestErrorEvent) => void) | null; +type ConnectEventListener = ((event: ConnectionRequestEvent) => void) | null; +type TransactionEventListener = ((event: SendTransactionRequestEvent) => void) | null; +type SignDataEventListener = ((event: SignDataRequestEvent) => void) | null; +type DisconnectEventListener = ((event: DisconnectionEvent) => void) | null; +type ErrorEventListener = ((event: RequestErrorEvent) => void) | null; -/** - * Union type for all bridge event listeners. - */ -export type BridgeEventListener = +type BridgeEventListener = | ConnectEventListener | TransactionEventListener | SignDataEventListener diff --git a/packages/walletkit-android-bridge/src/api/index.ts b/packages/walletkit-android-bridge/src/api/index.ts index bcf6c85fa..ad0f16fe5 100644 --- a/packages/walletkit-android-bridge/src/api/index.ts +++ b/packages/walletkit-android-bridge/src/api/index.ts @@ -44,7 +44,6 @@ export const api: WalletKitBridgeApi = { // Wallets — unified addWallet (registry path + proxy adapter path) addWallet: wallets.addWallet, releaseRef: wallets.releaseRef, - releaseAdapter: wallets.releaseAdapter, getWallets: wallets.getWallets, getWallet: wallets.getWalletById, getWalletAddress: wallets.getWalletAddress, @@ -91,5 +90,3 @@ export const api: WalletKitBridgeApi = { emitBrowserError: browser.emitBrowserError, emitBrowserBridgeRequest: browser.emitBrowserBridgeRequest, } as unknown as WalletKitBridgeApi; - -export type { BridgeEventListener } from './eventListeners'; diff --git a/packages/walletkit-android-bridge/src/api/jettons.ts b/packages/walletkit-android-bridge/src/api/jettons.ts index 17bb430f0..d2db7679c 100644 --- a/packages/walletkit-android-bridge/src/api/jettons.ts +++ b/packages/walletkit-android-bridge/src/api/jettons.ts @@ -6,12 +6,6 @@ * */ -/** - * jettons.ts – Jetton operations - * - * Minimal bridge for jetton operations. - */ - import { walletCall } from '../utils/bridge'; export const getJettons = (args: { walletId: string }) => walletCall('getJettons', args); diff --git a/packages/walletkit-android-bridge/src/api/nft.ts b/packages/walletkit-android-bridge/src/api/nft.ts index b504973ce..a0f8b7a9c 100644 --- a/packages/walletkit-android-bridge/src/api/nft.ts +++ b/packages/walletkit-android-bridge/src/api/nft.ts @@ -6,12 +6,6 @@ * */ -/** - * nft.ts – NFT operations - * - * Minimal bridge for NFT operations. - */ - import { walletCall } from '../utils/bridge'; export const getNfts = (args: { walletId: string }) => walletCall('getNfts', args); diff --git a/packages/walletkit-android-bridge/src/api/transactions.ts b/packages/walletkit-android-bridge/src/api/transactions.ts index 13286bd08..ae94c2e46 100644 --- a/packages/walletkit-android-bridge/src/api/transactions.ts +++ b/packages/walletkit-android-bridge/src/api/transactions.ts @@ -6,12 +6,6 @@ * */ -/** - * transactions.ts – TON transaction operations - * - * Minimal bridge - just forwards calls to WalletKit. - */ - import type { TransactionRequest } from '@ton/walletkit'; import { walletCall, clientCall, getKit, getWallet } from '../utils/bridge'; diff --git a/packages/walletkit-android-bridge/src/bridge.ts b/packages/walletkit-android-bridge/src/bridge.ts index a9b964a2f..b139db6aa 100644 --- a/packages/walletkit-android-bridge/src/bridge.ts +++ b/packages/walletkit-android-bridge/src/bridge.ts @@ -22,6 +22,3 @@ registerNativeCallHandler(); registerNativeResponseHandler(); window.walletkitBridge = api; - -export { api }; -export type { WalletKitBridgeApi } from './types'; diff --git a/packages/walletkit-android-bridge/src/core/initialization.ts b/packages/walletkit-android-bridge/src/core/initialization.ts index 020b831d8..3c9e14665 100644 --- a/packages/walletkit-android-bridge/src/core/initialization.ts +++ b/packages/walletkit-android-bridge/src/core/initialization.ts @@ -30,21 +30,12 @@ import { } from '../adapters/AndroidTONConnectSessionsManager'; import { AndroidAPIClientAdapter } from '../adapters/AndroidAPIClientAdapter'; -export interface InitTonWalletKitDeps { +interface InitTonWalletKitDeps { emit: (type: WalletKitBridgeEvent['type'], data?: WalletKitBridgeEvent['data']) => void; postToNative: (payload: BridgePayload) => void; AndroidStorageAdapter: new () => unknown; } -type NativeStorageBridge = { - storageGet: (key: string) => string | null; - storageSet: (key: string, value: string) => void; -}; - -type _AndroidBridgeWindow = Window & { - WalletKitNative?: NativeStorageBridge; -}; - /** * Initializes WalletKit with Android-specific configuration and wiring. * diff --git a/packages/walletkit-android-bridge/src/globals.d.ts b/packages/walletkit-android-bridge/src/globals.d.ts index 4056e1dad..4c6606d40 100644 --- a/packages/walletkit-android-bridge/src/globals.d.ts +++ b/packages/walletkit-android-bridge/src/globals.d.ts @@ -8,8 +8,6 @@ // Re-export bridge types for backwards compatibility import type { - WalletKitBridgeEvent as _WalletKitBridgeEvent, - WalletKitBridgeInitConfig as _WalletKitBridgeInitConfig, AndroidBridgeType, WalletKitNativeBridgeType, WalletKitBridgeApi, diff --git a/packages/walletkit-android-bridge/src/polyfills/setupNativeBridge.ts b/packages/walletkit-android-bridge/src/polyfills/setupNativeBridge.ts index d4cf87be3..6e5066604 100644 --- a/packages/walletkit-android-bridge/src/polyfills/setupNativeBridge.ts +++ b/packages/walletkit-android-bridge/src/polyfills/setupNativeBridge.ts @@ -33,14 +33,8 @@ function ensureBuffer(scope: GlobalWithBridge) { } } -/** - * Sets up the native storage bridge that connects JavaScript to Android's secure storage. - * Creates window.WalletKitNativeStorage that delegates to WalletKitNative. - * - * Note: Modern Android WebView (API 24+) already supports all standard Web APIs - * (fetch, TextEncoder, URL, etc.), so no polyfills are needed. - */ -export function setupNativeBridge() { +// Sets up native storage bridge: window.WalletKitNativeStorage delegates to WalletKitNative. +function setupNativeBridge() { const scope = window as GlobalWithBridge; ensureBuffer(scope); diff --git a/packages/walletkit-android-bridge/src/transport/messaging.ts b/packages/walletkit-android-bridge/src/transport/messaging.ts index ab037dad8..dde24f9ba 100644 --- a/packages/walletkit-android-bridge/src/transport/messaging.ts +++ b/packages/walletkit-android-bridge/src/transport/messaging.ts @@ -6,9 +6,6 @@ * */ -/** - * Messaging helpers that mediate between the native bridge and WalletKit APIs. - */ import type { WalletKitBridgeEvent, WalletKitBridgeApi, WalletKitApiMethod, CallContext } from '../types'; import { postToNative } from './nativeBridge'; import { emitCallDiagnostic } from './diagnostics'; @@ -16,33 +13,15 @@ import { error } from '../utils/logger'; let apiRef: WalletKitBridgeApi | undefined; -/** - * Emits a bridge event to the native layer. - * - * @param type - Event type identifier. - * @param data - Optional event payload. - */ export function emit(type: WalletKitBridgeEvent['type'], data?: WalletKitBridgeEvent['data']): void { const event: WalletKitBridgeEvent = { type, data }; postToNative({ kind: 'event', event }); } -/** - * Sends a response payload (or error) back to the native layer. - * - * @param id - Native call identifier. - * @param result - Optional result payload. - * @param error - Optional error to report. - */ export function respond(id: string, result?: unknown, error?: { message: string }): void { postToNative({ kind: 'response', id, result, error }); } -/** - * Registers the active API implementation that will service native calls. - * - * @param api - WalletKit bridge API surface. - */ export function setBridgeApi(api: WalletKitBridgeApi): void { apiRef = api; } @@ -65,13 +44,6 @@ async function invokeApiMethod( return value; } -/** - * Handles a native call by invoking the corresponding WalletKit bridge method. - * - * @param id - Native call identifier. - * @param method - API method name. - * @param params - Optional serialized parameters. - */ export async function handleCall(id: string, method: WalletKitApiMethod, params?: unknown): Promise { if (!apiRef) { throw new Error('Bridge API not registered'); @@ -90,9 +62,6 @@ export async function handleCall(id: string, method: WalletKitApiMethod, params? } } -/** - * Registers the global handler that native code invokes to call into the bridge. - */ export function registerNativeCallHandler(): void { window.__walletkitCall = (id, method, paramsJson) => { let params: unknown = undefined; diff --git a/packages/walletkit-android-bridge/src/types/api.ts b/packages/walletkit-android-bridge/src/types/api.ts index 45c407ed4..a76086ccc 100644 --- a/packages/walletkit-android-bridge/src/types/api.ts +++ b/packages/walletkit-android-bridge/src/types/api.ts @@ -75,20 +75,12 @@ export interface CreateWalletAdapterArgs { export interface AddWalletArgs { adapterId: string; - walletId?: string; - publicKey?: string; - network?: { chainId: string }; - address?: string; } export interface ReleaseRefArgs { id: string; } -export interface ReleaseAdapterArgs { - adapterId: string; -} - export interface RemoveWalletArgs { walletId: string; } @@ -268,14 +260,6 @@ export interface HandleTonConnectUrlArgs { url: string; } -export interface WalletDescriptor { - address: string; - publicKey: string; - version: string; - index: number; - network: string; -} - export interface WalletKitBridgeApi { init(config?: WalletKitBridgeInitConfig): PromiseOrValue<{ ok: true }>; setEventsListeners(args?: SetEventsListenersArgs): PromiseOrValue<{ ok: true }>; @@ -294,7 +278,6 @@ export interface WalletKitBridgeApi { createV4R2WalletAdapter(args: CreateWalletAdapterArgs): PromiseOrValue<{ adapterId: string; address: string }>; addWallet(args: AddWalletArgs): PromiseOrValue<{ walletId: string | undefined; wallet: Wallet } | null>; releaseRef(args: ReleaseRefArgs): PromiseOrValue<{ ok: boolean }>; - releaseAdapter(args: ReleaseAdapterArgs): PromiseOrValue<{ ok: boolean }>; getWallets(): PromiseOrValue<{ walletId: string | undefined; wallet: Wallet }[]>; getWallet(args: { walletId: string }): PromiseOrValue<{ walletId: string | undefined; wallet: Wallet } | null>; getWalletAddress(args: { walletId: string }): PromiseOrValue; diff --git a/packages/walletkit-android-bridge/src/types/events.ts b/packages/walletkit-android-bridge/src/types/events.ts index 9d2424018..d8af5d678 100644 --- a/packages/walletkit-android-bridge/src/types/events.ts +++ b/packages/walletkit-android-bridge/src/types/events.ts @@ -27,8 +27,6 @@ export interface WalletKitBridgeEvent { data?: T; } -export type WalletKitBridgeEventHandler = (event: WalletKitBridgeEvent) => void; - export type WalletKitBridgeEventCallback = ( type: WalletKitBridgeEventType, event: WalletKitBridgeEvent['data'], diff --git a/packages/walletkit-android-bridge/src/utils/bridge.ts b/packages/walletkit-android-bridge/src/utils/bridge.ts index 7cb99cdee..bdb6857e8 100644 --- a/packages/walletkit-android-bridge/src/utils/bridge.ts +++ b/packages/walletkit-android-bridge/src/utils/bridge.ts @@ -7,10 +7,7 @@ */ /** - * bridge.ts - Minimal unified bridge for all operations - * - * Single entry point for calling WalletKit, Wallet, and Client methods. - * Handles initialization, wallet lookup, and error handling in one place. + * Unified bridge for calling WalletKit, Wallet, and Client methods. */ import type { Wallet } from '@ton/walletkit'; @@ -18,12 +15,6 @@ import type { Wallet } from '@ton/walletkit'; import type { WalletKitInstance } from '../core/state'; import { walletKit } from '../core/state'; -// Re-export for external use -export type { WalletKitInstance }; - -/** - * Ensures WalletKit is initialized and ready. - */ async function ensureReady(): Promise { if (!walletKit) { throw new Error('WalletKit not initialized'); @@ -32,9 +23,6 @@ async function ensureReady(): Promise { return walletKit; } -/** - * Gets a wallet by ID, throwing if not found. - */ function getWalletOrThrow(kit: WalletKitInstance, walletId: string): Wallet { const wallet = kit.getWallet(walletId); if (!wallet) { @@ -43,14 +31,6 @@ function getWalletOrThrow(kit: WalletKitInstance, walletId: string): Wallet { return wallet; } -/** - * Calls a method on WalletKit instance. - * - * @example - * await kit('removeWallet', walletId); - * await kit('handleTonConnectUrl', url); - * await kit('listSessions'); - */ export async function kit(method: M, ...args: unknown[]): Promise { const instance = await ensureReady(); const fn = instance[method]; @@ -60,14 +40,6 @@ export async function kit(method: M, ...args: return (fn as (...a: unknown[]) => unknown).apply(instance, args); } -/** - * Calls a method on a Wallet instance. - * - * @example - * await wallet(walletId, 'getBalance'); - * await wallet(walletId, 'getJettons', { pagination }); - * await wallet(walletId, 'createTransferTonTransaction', request); - */ export async function wallet( walletId: string, method: M, @@ -82,26 +54,15 @@ export async function wallet( return (fn as (...a: unknown[]) => unknown).apply(w, args); } -/** - * Get the raw WalletKit instance (for complex operations). - * Use sparingly - prefer kit(), wallet() for type safety. - */ export async function getKit(): Promise { return ensureReady(); } -/** - * Get a wallet instance (for complex operations). - * Use sparingly - prefer wallet() for type safety. - */ export async function getWallet(walletId: string): Promise { const instance = await ensureReady(); return getWalletOrThrow(instance, walletId); } -/** - * Calls a method on a Wallet instance. Extracts walletId from args.walletId. - */ export async function walletCall( method: string, args: { walletId: string; [k: string]: unknown }, @@ -115,9 +76,6 @@ export async function walletCall( return (fn as (args?: unknown) => Promise).call(w, args); } -/** - * Calls a method on a Wallet's ApiClient. Extracts walletId from args.walletId. - */ export async function clientCall( method: string, args: { walletId: string; [k: string]: unknown }, diff --git a/packages/walletkit-android-bridge/src/utils/internalBrowserResolvers.ts b/packages/walletkit-android-bridge/src/utils/internalBrowserResolvers.ts index cc66f63b7..48ba4cdc6 100644 --- a/packages/walletkit-android-bridge/src/utils/internalBrowserResolvers.ts +++ b/packages/walletkit-android-bridge/src/utils/internalBrowserResolvers.ts @@ -8,12 +8,12 @@ import type { JsBridgeTransportMessage } from '../types/bridge'; -export type InternalBrowserResponseResolver = { +type InternalBrowserResponseResolver = { resolve: (response: JsBridgeTransportMessage) => void; reject: (error: Error) => void; }; -export type InternalBrowserResolverRegistry = Map; +type InternalBrowserResolverRegistry = Map; type InternalBrowserGlobal = typeof globalThis & { __internalBrowserResponseResolvers?: InternalBrowserResolverRegistry; @@ -31,7 +31,3 @@ export function ensureInternalBrowserResolverMap(): InternalBrowserResolverRegis } return internalBrowserGlobal.__internalBrowserResponseResolvers; } - -export function deleteInternalBrowserResolver(messageId: string): void { - internalBrowserGlobal.__internalBrowserResponseResolvers?.delete(messageId); -} diff --git a/packages/walletkit-android-bridge/src/utils/logger.ts b/packages/walletkit-android-bridge/src/utils/logger.ts index e0ce44af1..17b6c3d54 100644 --- a/packages/walletkit-android-bridge/src/utils/logger.ts +++ b/packages/walletkit-android-bridge/src/utils/logger.ts @@ -7,17 +7,10 @@ */ /** - * Flexible logging utility for the Android WalletKit bridge. - * - * Logging can be controlled by setting the log level from the Android SDK: - * window.__WALLETKIT_LOG_LEVEL__ = 'DEBUG'; // OFF, ERROR, WARN, INFO, DEBUG - * - * Default is 'OFF' in production for performance. + * Logging utility controlled via window.__WALLETKIT_LOG_LEVEL__. + * Defaults to OFF in production. */ -/** - * Log levels in order of verbosity - */ export enum LogLevel { OFF = 0, ERROR = 1, @@ -47,40 +40,24 @@ function getCurrentLogLevel(): LogLevel { return LogLevel[levelStr] ?? LogLevel.OFF; } -/** - * Debug logger - logs detailed debugging information - * Only logs when level is DEBUG or higher - */ export const log = (...args: unknown[]): void => { if (getCurrentLogLevel() >= LogLevel.DEBUG) { consoleRef?.log?.('[WalletKit]', ...args); } }; -/** - * Info logger - logs informational messages - * Only logs when level is INFO or higher - */ export const info = (...args: unknown[]): void => { if (getCurrentLogLevel() >= LogLevel.INFO) { consoleRef?.log?.('[WalletKit]', ...args); } }; -/** - * Warning logger - logs warnings - * Only logs when level is WARN or higher - */ export const warn = (...args: unknown[]): void => { if (getCurrentLogLevel() >= LogLevel.WARN) { consoleRef?.warn?.('[WalletKit]', ...args); } }; -/** - * Error logger - logs errors - * Only logs when level is ERROR or higher - */ export const error = (...args: unknown[]): void => { if (getCurrentLogLevel() >= LogLevel.ERROR) { consoleRef?.error?.('[WalletKit]', ...args); From a6593a3dc9b4974533bb246f031877e7b27d0d65 Mon Sep 17 00:00:00 2001 From: Dmitrii Nikulin Date: Tue, 24 Feb 2026 00:19:12 +0530 Subject: [PATCH 07/10] feat: remove cache from ProxyWalletAdapter --- .../src/api/wallets.ts | 83 ++++++------------- .../src/transport/nativeBridge.ts | 30 +++---- .../src/types/walletkit.ts | 1 + 3 files changed, 41 insertions(+), 73 deletions(-) diff --git a/packages/walletkit-android-bridge/src/api/wallets.ts b/packages/walletkit-android-bridge/src/api/wallets.ts index fb4937f05..090177006 100644 --- a/packages/walletkit-android-bridge/src/api/wallets.ts +++ b/packages/walletkit-android-bridge/src/api/wallets.ts @@ -6,10 +6,6 @@ * */ -/** - * Wallet management operations. - */ - import type { Hex, Network, WalletAdapter, ApiClient, Base64String, UserFriendlyAddress } from '@ton/walletkit'; import type { WalletId } from '@ton/walletkit'; import type { TransactionRequest } from '@ton/walletkit'; @@ -19,41 +15,36 @@ import type { ProofMessage } from '@ton/walletkit'; import { Signer, WalletV4R2Adapter, WalletV5R1Adapter } from '../core/moduleLoader'; import { kit, wallet, getKit } from '../utils/bridge'; import { retain, retainWithId, get, release } from '../utils/registry'; -import { bridgeRequest } from '../transport/nativeBridge'; - -// ────────────────────────────────────────────────────────────────────────────── -// ProxyWalletAdapter -// -// Wraps a Kotlin-side TONWalletAdapter registered in AdapterManager. -// Data accessors (publicKey, network, address, walletId) use cached values -// passed at construction. Signing/stateInit operations delegate to Kotlin -// via the reverse-RPC `bridgeRequest` channel. -// ────────────────────────────────────────────────────────────────────────────── +import { bridgeRequest, bridgeRequestSync } from '../transport/nativeBridge'; +// Wraps a Kotlin-side TONWalletAdapter. Sync getters call Kotlin synchronously +// via @JavascriptInterface; signing/stateInit delegate via async reverse-RPC. class ProxyWalletAdapter implements WalletAdapter { constructor( private readonly adapterId: string, - private readonly _publicKey: Hex, - private readonly _network: Network, - private readonly _address: UserFriendlyAddress, - private readonly _walletId: WalletId, - private readonly _client: ApiClient, + private readonly apiClientProvider: (network: Network) => ApiClient, ) {} getPublicKey(): Hex { - return this._publicKey; + return bridgeRequestSync('getPublicKey', { adapterId: this.adapterId }) as Hex; } + getNetwork(): Network { - return this._network; + const raw = bridgeRequestSync('getNetwork', { adapterId: this.adapterId }); + const parsed = JSON.parse(raw); + return parsed as Network; } + getClient(): ApiClient { - return this._client; + return this.apiClientProvider(this.getNetwork()); } + getAddress(): UserFriendlyAddress { - return this._address; + return bridgeRequestSync('getAddress', { adapterId: this.adapterId }) as UserFriendlyAddress; } + getWalletId(): WalletId { - return this._walletId; + return bridgeRequestSync('getWalletId', { adapterId: this.adapterId }) as WalletId; } async getStateInit(): Promise { @@ -201,35 +192,24 @@ export async function createV4R2WalletAdapter(args: { export async function addWallet(args: { adapterId: string; - walletId?: string; - publicKey?: string; - network?: { chainId: string }; - address?: string; }) { const instance = await getKit(); - if (args.publicKey) { - const { adapterId, walletId, publicKey, address } = args; - const network = args.network as unknown as Network; - - const proxyAdapter = new ProxyWalletAdapter( - adapterId, - publicKey as Hex, - network, - address as UserFriendlyAddress, - walletId as WalletId, - instance.getApiClient(network), - ); - - const w = await instance.addWallet(proxyAdapter as Parameters[0]); + // Check if adapter exists in JS registry (BridgeWalletAdapter / JS-created adapter path) + const existingAdapter = get(args.adapterId); + if (existingAdapter) { + const w = await instance.addWallet(existingAdapter as Parameters[0]); if (!w) return null; return { walletId: w.getWalletId?.(), wallet: w }; } - const adapter = get(args.adapterId); - if (!adapter) throw new Error(`Adapter not found in registry: ${args.adapterId}`); + // Kotlin-side adapter — create proxy that calls Kotlin synchronously for getters + const proxyAdapter = new ProxyWalletAdapter( + args.adapterId, + (network) => instance.getApiClient(network), + ); - const w = await instance.addWallet(adapter as Parameters[0]); + const w = await instance.addWallet(proxyAdapter as Parameters[0]); if (!w) return null; return { walletId: w.getWalletId?.(), wallet: w }; } @@ -241,16 +221,3 @@ export function releaseRef(args: { id: string }) { release(args.id); return { ok: true }; } - -/** - * Releases a Kotlin-side native adapter registered in AdapterManager. - * Called by Kotlin when adapter.close() is invoked. - * For proxy adapters the JS side has no registry entry — this is a no-op on JS, - * but the Kotlin side uses it to confirm cleanup is complete. - */ -export function releaseAdapter(args: { adapterId: string }) { - // Proxy adapters aren't stored in the JS registry (they're closures over adapterId). - // But if a JS-side adapter was also retained (createV5R1/createV4R2), clean it up. - release(args.adapterId); - return { ok: true }; -} diff --git a/packages/walletkit-android-bridge/src/transport/nativeBridge.ts b/packages/walletkit-android-bridge/src/transport/nativeBridge.ts index 2e54fc4a1..e7b55072c 100644 --- a/packages/walletkit-android-bridge/src/transport/nativeBridge.ts +++ b/packages/walletkit-android-bridge/src/transport/nativeBridge.ts @@ -6,27 +6,30 @@ * */ -/** - * Native bridge helpers for posting messages to the Android host. - */ import type { BridgePayload } from '../types'; import { bigIntReplacer } from '../utils/serialization'; import { warn, error, info } from '../utils/logger'; -// ────────────────────────────────────────────────────────────────────────────── -// Reverse-RPC: lets JS request work from Kotlin (adapter/signer proxy calls). -// JS sends {kind:'request', id, method, params} via postMessage. -// Kotlin responds by calling window.__walletkitResponse(id, resultJson, errorJson). -// ────────────────────────────────────────────────────────────────────────────── +// Reverse-RPC: JS sends {kind:'request', id, method, params} via postMessage. +// Kotlin responds via window.__walletkitResponse(id, resultJson, errorJson). const pendingRequests = new Map void; reject: (e: Error) => void }>(); let nextRequestId = 1; /** - * Send a request to the native (Kotlin) side and wait for a response. - * - * The native side is expected to call `window.__walletkitResponse(id, resultJson, errorJson)` - * when the work is done. + * Synchronous bridge call via @JavascriptInterface (WalletKitNative.adapterCallSync). + * Used for sync WalletAdapter getters that cannot be async. + */ +export function bridgeRequestSync(method: string, params: Record): string { + const native = window.WalletKitNative; + if (!native || typeof native.adapterCallSync !== 'function') { + throw new Error('WalletKitNative.adapterCallSync not available'); + } + return native.adapterCallSync(method, JSON.stringify(params)); +} + +/** + * Send a request to Kotlin via postMessage and wait for a response. */ export function bridgeRequest(method: string, params: Record): Promise { const id = `req_${nextRequestId++}`; @@ -36,9 +39,6 @@ export function bridgeRequest(method: string, params: Record): }); } -/** - * Registers the global handler that Kotlin invokes to deliver reverse-RPC responses. - */ export function registerNativeResponseHandler(): void { window.__walletkitResponse = (id: string, resultJson?: string | null, errorJson?: string | null) => { const entry = pendingRequests.get(id); diff --git a/packages/walletkit-android-bridge/src/types/walletkit.ts b/packages/walletkit-android-bridge/src/types/walletkit.ts index 5f7f97e89..9266e24aa 100644 --- a/packages/walletkit-android-bridge/src/types/walletkit.ts +++ b/packages/walletkit-android-bridge/src/types/walletkit.ts @@ -59,6 +59,7 @@ export interface AndroidBridgeType { export interface WalletKitNativeBridgeType { postMessage(json: string): void; + adapterCallSync(method: string, paramsJson: string): string; } export type WalletKitAdapter = WalletAdapter; From 5b4739ef34e27d43d3ee0518091b0b3d8d4df6be Mon Sep 17 00:00:00 2001 From: Dmitrii Nikulin Date: Tue, 24 Feb 2026 00:19:24 +0530 Subject: [PATCH 08/10] feat: replace request ID generation with uuidv7 --- .../walletkit-android-bridge/src/transport/nativeBridge.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/walletkit-android-bridge/src/transport/nativeBridge.ts b/packages/walletkit-android-bridge/src/transport/nativeBridge.ts index e7b55072c..2e72a7cb6 100644 --- a/packages/walletkit-android-bridge/src/transport/nativeBridge.ts +++ b/packages/walletkit-android-bridge/src/transport/nativeBridge.ts @@ -9,12 +9,12 @@ import type { BridgePayload } from '../types'; import { bigIntReplacer } from '../utils/serialization'; import { warn, error, info } from '../utils/logger'; +import { v7 as uuidv7 } from 'uuid'; // Reverse-RPC: JS sends {kind:'request', id, method, params} via postMessage. // Kotlin responds via window.__walletkitResponse(id, resultJson, errorJson). const pendingRequests = new Map void; reject: (e: Error) => void }>(); -let nextRequestId = 1; /** * Synchronous bridge call via @JavascriptInterface (WalletKitNative.adapterCallSync). @@ -32,7 +32,7 @@ export function bridgeRequestSync(method: string, params: Record): Promise { - const id = `req_${nextRequestId++}`; + const id = uuidv7(); return new Promise((resolve, reject) => { pendingRequests.set(id, { resolve, reject }); postToNative({ kind: 'request', id, method, params }); From 88221bfbeaefd0132a363d90dfef6dcc1ce63490 Mon Sep 17 00:00:00 2001 From: Dmitrii Nikulin Date: Tue, 24 Feb 2026 00:25:25 +0530 Subject: [PATCH 09/10] refactor: fix lint --- .../walletkit-android-bridge/src/api/eventListeners.ts | 2 +- packages/walletkit-android-bridge/src/api/wallets.ts | 9 ++------- packages/walletkit-android-bridge/src/globals.d.ts | 7 +------ .../src/transport/nativeBridge.ts | 3 ++- 4 files changed, 6 insertions(+), 15 deletions(-) diff --git a/packages/walletkit-android-bridge/src/api/eventListeners.ts b/packages/walletkit-android-bridge/src/api/eventListeners.ts index c72751963..bee5269ef 100644 --- a/packages/walletkit-android-bridge/src/api/eventListeners.ts +++ b/packages/walletkit-android-bridge/src/api/eventListeners.ts @@ -20,7 +20,7 @@ type SignDataEventListener = ((event: SignDataRequestEvent) => void) | null; type DisconnectEventListener = ((event: DisconnectionEvent) => void) | null; type ErrorEventListener = ((event: RequestErrorEvent) => void) | null; -type BridgeEventListener = +type _BridgeEventListener = | ConnectEventListener | TransactionEventListener | SignDataEventListener diff --git a/packages/walletkit-android-bridge/src/api/wallets.ts b/packages/walletkit-android-bridge/src/api/wallets.ts index 090177006..27fe372f0 100644 --- a/packages/walletkit-android-bridge/src/api/wallets.ts +++ b/packages/walletkit-android-bridge/src/api/wallets.ts @@ -190,9 +190,7 @@ export async function createV4R2WalletAdapter(args: { return { adapterId, address: adapter.getAddress() }; } -export async function addWallet(args: { - adapterId: string; -}) { +export async function addWallet(args: { adapterId: string }) { const instance = await getKit(); // Check if adapter exists in JS registry (BridgeWalletAdapter / JS-created adapter path) @@ -204,10 +202,7 @@ export async function addWallet(args: { } // Kotlin-side adapter — create proxy that calls Kotlin synchronously for getters - const proxyAdapter = new ProxyWalletAdapter( - args.adapterId, - (network) => instance.getApiClient(network), - ); + const proxyAdapter = new ProxyWalletAdapter(args.adapterId, (network) => instance.getApiClient(network)); const w = await instance.addWallet(proxyAdapter as Parameters[0]); if (!w) return null; diff --git a/packages/walletkit-android-bridge/src/globals.d.ts b/packages/walletkit-android-bridge/src/globals.d.ts index 4c6606d40..413ba0510 100644 --- a/packages/walletkit-android-bridge/src/globals.d.ts +++ b/packages/walletkit-android-bridge/src/globals.d.ts @@ -7,12 +7,7 @@ */ // Re-export bridge types for backwards compatibility -import type { - AndroidBridgeType, - WalletKitNativeBridgeType, - WalletKitBridgeApi, - WalletKitApiMethod, -} from './types'; +import type { AndroidBridgeType, WalletKitNativeBridgeType, WalletKitBridgeApi, WalletKitApiMethod } from './types'; declare global { interface Window { diff --git a/packages/walletkit-android-bridge/src/transport/nativeBridge.ts b/packages/walletkit-android-bridge/src/transport/nativeBridge.ts index 2e72a7cb6..624f982f1 100644 --- a/packages/walletkit-android-bridge/src/transport/nativeBridge.ts +++ b/packages/walletkit-android-bridge/src/transport/nativeBridge.ts @@ -6,10 +6,11 @@ * */ +import { v7 as uuidv7 } from 'uuid'; + import type { BridgePayload } from '../types'; import { bigIntReplacer } from '../utils/serialization'; import { warn, error, info } from '../utils/logger'; -import { v7 as uuidv7 } from 'uuid'; // Reverse-RPC: JS sends {kind:'request', id, method, params} via postMessage. // Kotlin responds via window.__walletkitResponse(id, resultJson, errorJson). From 97c1f61cf415650214e918ed66122534a9de33ac Mon Sep 17 00:00:00 2001 From: Dmitrii Nikulin Date: Tue, 24 Feb 2026 00:33:45 +0530 Subject: [PATCH 10/10] refactor: remove unused _BridgeEventListener type --- .../walletkit-android-bridge/src/api/eventListeners.ts | 7 ------- 1 file changed, 7 deletions(-) diff --git a/packages/walletkit-android-bridge/src/api/eventListeners.ts b/packages/walletkit-android-bridge/src/api/eventListeners.ts index bee5269ef..08414c752 100644 --- a/packages/walletkit-android-bridge/src/api/eventListeners.ts +++ b/packages/walletkit-android-bridge/src/api/eventListeners.ts @@ -20,13 +20,6 @@ type SignDataEventListener = ((event: SignDataRequestEvent) => void) | null; type DisconnectEventListener = ((event: DisconnectionEvent) => void) | null; type ErrorEventListener = ((event: RequestErrorEvent) => void) | null; -type _BridgeEventListener = - | ConnectEventListener - | TransactionEventListener - | SignDataEventListener - | DisconnectEventListener - | ErrorEventListener; - export const eventListeners = { onConnectListener: null as ConnectEventListener, onTransactionListener: null as TransactionEventListener,