diff --git a/TONWalletKit-Android/impl/src/full/java/io/ton/walletkit/presentation/impl/QuickJsWalletKitEngine.kt b/TONWalletKit-Android/impl/src/full/java/io/ton/walletkit/presentation/impl/QuickJsWalletKitEngine.kt index 4af8a290..46e8508a 100644 --- a/TONWalletKit-Android/impl/src/full/java/io/ton/walletkit/presentation/impl/QuickJsWalletKitEngine.kt +++ b/TONWalletKit-Android/impl/src/full/java/io/ton/walletkit/presentation/impl/QuickJsWalletKitEngine.kt @@ -68,6 +68,8 @@ import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.withContext +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable import kotlinx.serialization.decodeFromString import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json @@ -169,6 +171,10 @@ internal class QuickJsWalletKitEngine( ignoreUnknownKeys = true isLenient = true } + private val paramsEncoder = Json { + encodeDefaults = false + explicitNulls = false + } @Volatile private var quickJsInstance: QuickJs? = null private val random = SecureRandom() @@ -272,21 +278,20 @@ internal class QuickJsWalletKitEngine( apiBaseUrl = resolveTonApiBase(configuration) tonApiKey = configuration.apiClientConfiguration?.key?.takeIf { it.isNotBlank() } - val payload = - JSONObject().apply { - put("network", currentNetwork) - put("apiUrl", tonClientEndpoint) - put("apiBaseUrl", apiBaseUrl) - put("tonApiUrl", apiBaseUrl) - configuration.bridge.bridgeUrl.takeIf { it.isNotBlank() }?.let { put("bridgeUrl", it) } - configuration.walletManifest.name.takeIf { it.isNotBlank() }?.let { put("bridgeName", it) } - // Note: QuickJS engine doesn't support persistent storage yet - // Storage parameter removed from config - tonApiKey?.let { put("apiKey", it) } - configuration.eventsConfiguration?.let { eventsConfig -> - put("disableTransactionEmulation", eventsConfig.disableTransactionEmulation) - } - } + val payload = JSONObject( + paramsEncoder.encodeToString( + InitPayload( + network = currentNetwork, + apiUrl = tonClientEndpoint, + apiBaseUrl = apiBaseUrl, + tonApiUrl = apiBaseUrl, + bridgeUrl = configuration.bridge.bridgeUrl.takeIf { it.isNotBlank() }, + bridgeName = configuration.walletManifest.name.takeIf { it.isNotBlank() }, + apiKey = tonApiKey, + disableTransactionEmulation = configuration.eventsConfiguration?.disableTransactionEmulation, + ), + ), + ) // Store the configuration after successful initialization currentConfig = configuration @@ -378,7 +383,7 @@ internal class QuickJsWalletKitEngine( override suspend fun getWallet(address: String): WalletAccount? { ensureWalletKitInitialized() Log.d(logTag, "getWallet called for address: $address") - val params = JSONObject().apply { put("address", address) } + val params = JSONObject().put("address", address) val result = call("getWallet", params) Log.d(logTag, "getWallet result: $result") @@ -399,7 +404,7 @@ internal class QuickJsWalletKitEngine( override suspend fun removeWallet(address: String) { ensureWalletKitInitialized() Log.d(logTag, "removeWallet called for address: $address") - val params = JSONObject().apply { put("address", address) } + val params = JSONObject().put("address", address) val result = call("removeWallet", params) Log.d(logTag, "removeWallet result: $result") @@ -419,7 +424,7 @@ internal class QuickJsWalletKitEngine( override suspend fun getBalance(address: String): String { ensureWalletKitInitialized() Log.d(logTag, "getBalance called for address: $address") - val params = JSONObject().apply { put("address", address) } + val params = JSONObject().put("address", address) Log.d(logTag, "getBalance calling JavaScript...") val result = call("getBalance", params) Log.d(logTag, "getBalance result: $result") @@ -434,14 +439,12 @@ internal class QuickJsWalletKitEngine( override suspend fun handleTonConnectUrl(url: String) { ensureWalletKitInitialized() - val params = JSONObject().apply { put("url", url) } - call("handleTonConnectUrl", params) + call("handleTonConnectUrl", JSONObject().put("url", url)) } override suspend fun connectionEventFromUrl(url: String): TONWalletConnectionRequest { ensureWalletKitInitialized() - val params = JSONObject().apply { put("url", url) } - val result = call("connectionEventFromUrl", params) + val result = call("connectionEventFromUrl", JSONObject().put("url", url)) val event = json.decodeFromString(result.toString()) return TONWalletConnectionRequest(event = event, handler = this) } @@ -451,12 +454,12 @@ internal class QuickJsWalletKitEngine( transactionContent: String, ) { ensureWalletKitInitialized() - val params = - JSONObject().apply { - put("walletAddress", walletAddress) - put("transactionContent", transactionContent) - } - call(BridgeMethodConstants.METHOD_HANDLE_NEW_TRANSACTION, params) + call( + BridgeMethodConstants.METHOD_HANDLE_NEW_TRANSACTION, + JSONObject() + .put("walletAddress", walletAddress) + .put("transactionContent", transactionContent), + ) } override suspend fun sendTransaction( @@ -464,12 +467,12 @@ internal class QuickJsWalletKitEngine( transactionContent: String, ): String { ensureWalletKitInitialized() - val params = - JSONObject().apply { - put("walletAddress", walletAddress) - put("transactionContent", transactionContent) - } - val result = call(BridgeMethodConstants.METHOD_SEND_TRANSACTION, params) + val result = call( + BridgeMethodConstants.METHOD_SEND_TRANSACTION, + JSONObject() + .put("walletAddress", walletAddress) + .put("transactionContent", transactionContent), + ) // Extract the signedBoc from the result return result.getString("signedBoc") } @@ -481,13 +484,11 @@ internal class QuickJsWalletKitEngine( ensureWalletKitInitialized() val walletAddress = event.walletAddress ?: throw WalletKitBridgeException(ERROR_WALLET_ADDRESS_REQUIRED) val walletId = event.walletId ?: throw WalletKitBridgeException("Wallet ID is required") - val params = - JSONObject().apply { - put("requestId", event.id) - put("walletAddress", walletAddress.value) - put("walletId", walletId) - response?.let { put("response", JSONObject(json.encodeToString(it))) } - } + val params = JSONObject() + .put("requestId", event.id) + .put("walletAddress", walletAddress.value) + .put("walletId", walletId) + if (response != null) params.put("response", JSONObject(json.encodeToString(response))) call("approveConnectRequest", params) } @@ -497,12 +498,9 @@ internal class QuickJsWalletKitEngine( errorCode: Int?, ) { ensureWalletKitInitialized() - val params = - JSONObject().apply { - put("requestId", event.id) - reason?.let { put("reason", it) } - errorCode?.let { put("errorCode", it) } - } + val params = JSONObject().put("requestId", event.id) + if (reason != null) params.put("reason", reason) + if (errorCode != null) params.put("errorCode", errorCode) call("rejectConnectRequest", params) } @@ -512,13 +510,11 @@ internal class QuickJsWalletKitEngine( ) { ensureWalletKitInitialized() val walletAddress = event.walletAddress ?: throw WalletKitBridgeException(ERROR_WALLET_ADDRESS_REQUIRED) - val params = - JSONObject().apply { - put("requestId", event.id) - put("walletAddress", walletAddress.value) - put("walletId", event.walletId) - response?.let { put("response", JSONObject(json.encodeToString(it))) } - } + val params = JSONObject() + .put("requestId", event.id) + .put("walletAddress", walletAddress.value) + .put("walletId", event.walletId) + if (response != null) params.put("response", JSONObject(json.encodeToString(response))) call("approveTransactionRequest", params) } @@ -528,12 +524,9 @@ internal class QuickJsWalletKitEngine( errorCode: Int?, ) { ensureWalletKitInitialized() - val params = - JSONObject().apply { - put("requestId", event.id) - reason?.let { put("reason", it) } - errorCode?.let { put("errorCode", it) } - } + val params = JSONObject().put("requestId", event.id) + if (reason != null) params.put("reason", reason) + if (errorCode != null) params.put("errorCode", errorCode) call("rejectTransactionRequest", params) } @@ -543,13 +536,11 @@ internal class QuickJsWalletKitEngine( ) { ensureWalletKitInitialized() val walletAddress = event.walletAddress ?: throw WalletKitBridgeException(ERROR_WALLET_ADDRESS_REQUIRED) - val params = - JSONObject().apply { - put("requestId", event.id) - put("walletAddress", walletAddress) - put("walletId", event.walletId) - response?.let { put("response", JSONObject(json.encodeToString(it))) } - } + val params = JSONObject() + .put("requestId", event.id) + .put("walletAddress", walletAddress) + .put("walletId", event.walletId) + if (response != null) params.put("response", JSONObject(json.encodeToString(response))) call("approveSignDataRequest", params) } @@ -559,12 +550,9 @@ internal class QuickJsWalletKitEngine( errorCode: Int?, ) { ensureWalletKitInitialized() - val params = - JSONObject().apply { - put("requestId", event.id) - reason?.let { put("reason", it) } - errorCode?.let { put("errorCode", it) } - } + val params = JSONObject().put("requestId", event.id) + if (reason != null) params.put("reason", reason) + if (errorCode != null) params.put("errorCode", errorCode) call("rejectSignDataRequest", params) } @@ -610,21 +598,19 @@ internal class QuickJsWalletKitEngine( override suspend fun getNfts(walletAddress: String, limit: Int, offset: Int): io.ton.walletkit.api.generated.TONNFTsResponse { ensureWalletKitInitialized() - val params = JSONObject().apply { - put("address", walletAddress) - put("limit", limit) - put("offset", offset) - } - val result = call(BridgeMethodConstants.METHOD_GET_NFTS, params) + val result = call( + BridgeMethodConstants.METHOD_GET_NFTS, + JSONObject() + .put("address", walletAddress) + .put("limit", limit) + .put("offset", offset), + ) return json.decodeFromString(result.toString()) } override suspend fun getNft(nftAddress: String): io.ton.walletkit.api.generated.TONNFT? { ensureWalletKitInitialized() - val params = JSONObject().apply { - put("address", nftAddress) - } - val result = call(BridgeMethodConstants.METHOD_GET_NFT, params) + val result = call(BridgeMethodConstants.METHOD_GET_NFT, JSONObject().put("address", nftAddress)) return if (result.has("address")) { json.decodeFromString(result.toString()) } else { @@ -649,12 +635,13 @@ internal class QuickJsWalletKitEngine( // Jetton methods override suspend fun getJettons(walletAddress: String, limit: Int, offset: Int): io.ton.walletkit.api.generated.TONJettonsResponse { ensureWalletKitInitialized() - val params = JSONObject().apply { - put("address", walletAddress) - put("limit", limit) - put("offset", offset) - } - val result = call(BridgeMethodConstants.METHOD_GET_JETTONS, params) + val result = call( + BridgeMethodConstants.METHOD_GET_JETTONS, + JSONObject() + .put("address", walletAddress) + .put("limit", limit) + .put("offset", offset), + ) return json.decodeFromString(result.toString()) } @@ -677,11 +664,12 @@ internal class QuickJsWalletKitEngine( transactionContent: String, ): io.ton.walletkit.api.generated.TONTransactionEmulatedPreview { ensureWalletKitInitialized() - val paramsJson = JSONObject().apply { - put("address", walletAddress) - put("transactionContent", JSONObject(transactionContent)) - } - val result = call(BridgeMethodConstants.METHOD_GET_TRANSACTION_PREVIEW, paramsJson) + val result = call( + BridgeMethodConstants.METHOD_GET_TRANSACTION_PREVIEW, + JSONObject() + .put("address", walletAddress) + .put("transactionContent", JSONObject(transactionContent)), + ) // Parse the result using kotlinx.serialization return json.decodeFromString( io.ton.walletkit.api.generated.TONTransactionEmulatedPreview.serializer(), @@ -691,21 +679,23 @@ internal class QuickJsWalletKitEngine( override suspend fun getJettonBalance(walletAddress: String, jettonAddress: String): String { ensureWalletKitInitialized() - val params = JSONObject().apply { - put("address", walletAddress) - put("jettonAddress", jettonAddress) - } - val result = call(BridgeMethodConstants.METHOD_GET_JETTON_BALANCE, params) + val result = call( + BridgeMethodConstants.METHOD_GET_JETTON_BALANCE, + JSONObject() + .put("address", walletAddress) + .put("jettonAddress", jettonAddress), + ) return result.toString() } override suspend fun getJettonWalletAddress(walletAddress: String, jettonAddress: String): String { ensureWalletKitInitialized() - val params = JSONObject().apply { - put("address", walletAddress) - put("jettonAddress", jettonAddress) - } - val result = call(BridgeMethodConstants.METHOD_GET_JETTON_WALLET_ADDRESS, params) + val result = call( + BridgeMethodConstants.METHOD_GET_JETTON_WALLET_ADDRESS, + JSONObject() + .put("address", walletAddress) + .put("jettonAddress", jettonAddress), + ) return result.toString() } @@ -935,10 +925,9 @@ internal class QuickJsWalletKitEngine( data.put(key, payload.get(key)) } } - val readyEvent = JSONObject().apply { - put("type", "ready") - put("data", data) - } + val readyEvent = JSONObject() + .put("type", "ready") + .put("data", data) handleEvent(readyEvent) } @@ -1302,12 +1291,10 @@ internal class QuickJsWalletKitEngine( for ((name, value) in response.headers) { headersArray.put(JSONArray().put(name).put(value)) } - val meta = - JSONObject().apply { - put("status", response.code) - put("statusText", response.message) - put("headers", headersArray) - } + val meta = JSONObject() + .put("status", response.code) + .put("statusText", response.message) + .put("headers", headersArray) deliverFetchSuccess(id, meta.toString(), base64Body) response.close() } catch (err: Throwable) { @@ -1632,6 +1619,18 @@ internal class QuickJsWalletKitEngine( private data class BridgeResponse(val result: JSONObject) + @Serializable + private data class InitPayload( + val network: String, + val apiUrl: String, + val apiBaseUrl: String, + val tonApiUrl: String, + @SerialName("bridgeUrl") val bridgeUrl: String? = null, + @SerialName("bridgeName") val bridgeName: String? = null, + @SerialName("apiKey") val apiKey: String? = null, + @SerialName("disableTransactionEmulation") val disableTransactionEmulation: Boolean? = null, + ) + companion object { private const val QUICKJS_ASSET_DIR = "walletkit/quickjs/" private const val DEFAULT_BUNDLE_ASSET = QUICKJS_ASSET_DIR + "index.js" diff --git a/TONWalletKit-Android/impl/src/main/java/io/ton/walletkit/browser/TonConnectInjector.kt b/TONWalletKit-Android/impl/src/main/java/io/ton/walletkit/browser/TonConnectInjector.kt index 2ef8f5f1..1ebd004b 100644 --- a/TONWalletKit-Android/impl/src/main/java/io/ton/walletkit/browser/TonConnectInjector.kt +++ b/TONWalletKit-Android/impl/src/main/java/io/ton/walletkit/browser/TonConnectInjector.kt @@ -48,6 +48,11 @@ import kotlinx.coroutines.launch import kotlinx.serialization.Serializable import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.JsonPrimitive +import kotlinx.serialization.json.buildJsonArray +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.put import org.json.JSONArray import org.json.JSONObject import java.lang.ref.WeakReference @@ -608,7 +613,7 @@ internal class TonConnectInjector( val appName: String, val appVersion: String, val maxProtocolVersion: Int, - val features: List, + val features: List, ) @Serializable @@ -657,30 +662,39 @@ internal class TonConnectInjector( return Json.encodeToString(options) } - private fun buildFeaturesList(features: List?): List { - if (features.isNullOrEmpty()) return listOf("SendTransaction") + private fun buildFeaturesList(features: List?): List { + if (features.isNullOrEmpty()) return listOf(JsonPrimitive("SendTransaction")) - val result = mutableListOf() + val result = mutableListOf() for (feature in features) { when (feature) { is TONWalletKitConfiguration.SendTransactionFeature -> { - if (feature.maxMessages != null) { - val optionsJson = Json.encodeToString(mapOf("maxMessages" to feature.maxMessages)) - result.add("SendTransaction") - result.add("SendTransaction:$optionsJson") - } else { - result.add("SendTransaction") - } + result.add( + buildJsonObject { + put("name", "SendTransaction") + feature.maxMessages?.let { put("maxMessages", it) } + feature.extraCurrencySupported?.let { put("extraCurrencySupported", it) } + }, + ) + result.add(JsonPrimitive("SendTransaction")) // legacy string required by TonConnect protocol } is TONWalletKitConfiguration.SignDataFeature -> { - if (feature.types.isNotEmpty()) { - val types = feature.types.map { it.name.lowercase() } - val typesJson = Json.encodeToString(mapOf("types" to types)) - result.add("SignData:$typesJson") - } + result.add( + buildJsonObject { + put("name", "SignData") + put( + "types", + buildJsonArray { + for (type in feature.types) { + add(JsonPrimitive(type.name.lowercase())) + } + }, + ) + }, + ) } } } - return result.distinct() + return result } } diff --git a/TONWalletKit-Android/impl/src/main/java/io/ton/walletkit/engine/infrastructure/InitializationManager.kt b/TONWalletKit-Android/impl/src/main/java/io/ton/walletkit/engine/infrastructure/InitializationManager.kt index 8ffab04b..0fd6cbc6 100644 --- a/TONWalletKit-Android/impl/src/main/java/io/ton/walletkit/engine/infrastructure/InitializationManager.kt +++ b/TONWalletKit-Android/impl/src/main/java/io/ton/walletkit/engine/infrastructure/InitializationManager.kt @@ -37,7 +37,15 @@ import io.ton.walletkit.internal.util.Logger import io.ton.walletkit.storage.TONWalletKitStorageType import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock -import org.json.JSONArray +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.JsonPrimitive +import kotlinx.serialization.json.buildJsonArray +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.put import org.json.JSONObject /** @@ -56,6 +64,10 @@ internal class InitializationManager( private val rpcClient: BridgeRpcClient, ) { private val appContext = context.applicationContext + private val json = Json { + encodeDefaults = false + explicitNulls = false + } private val walletKitInitMutex = Mutex() @Volatile private var isWalletKitInitialized: Boolean = false @@ -131,25 +143,106 @@ internal class InitializationManager( fun tonApiKey(): String? = tonApiKey private suspend fun performInitialization(configuration: TONWalletKitConfiguration) { - val networkName = resolveNetworkName(configuration) - currentNetwork = networkName + currentNetwork = resolveNetworkName(configuration) persistentStorageEnabled = configuration.storageType != TONWalletKitStorageType.Memory - - val tonClientEndpoint = resolveTonClientEndpoint(configuration) apiBaseUrl = resolveTonApiBase(configuration) tonApiKey = configuration.apiClientConfiguration?.key?.takeIf { it.isNotBlank() } - // Use deviceInfo if provided, otherwise auto-detect - val appVersion = configuration.deviceInfo?.appVersion + val appVersion = resolveAppVersion(configuration) + val appName = resolveAppName(configuration) + val featuresToUse = configuration.deviceInfo?.features ?: configuration.features + + val payload = InitPayload( + network = currentNetwork, + apiUrl = resolveTonClientEndpoint(configuration), + tonApiUrl = apiBaseUrl, + networkConfigurations = configuration.networkConfigurations.map { nc -> + NetworkConfigPayload( + network = NetworkPayload(chainId = nc.network.chainId), + apiClientType = when (nc.apiClientType) { + TONWalletKitConfiguration.APIClientType.DEFAULT -> "default" + TONWalletKitConfiguration.APIClientType.TONCENTER -> "toncenter" + TONWalletKitConfiguration.APIClientType.TONAPI -> "tonapi" + TONWalletKitConfiguration.APIClientType.CUSTOM -> "custom" + }, + apiClientConfiguration = nc.apiClientConfiguration?.let { ac -> + val url = ac.url?.takeIf { it.isNotBlank() } + val key = ac.key?.takeIf { it.isNotBlank() } + if (url != null || key != null) ApiClientConfigPayload(url, key) else null + }, + ) + }, + bridgeUrl = configuration.bridge.bridgeUrl.takeIf { it.isNotBlank() }, + bridgeName = configuration.walletManifest.name.takeIf { it.isNotBlank() }, + walletManifest = WalletManifestPayload( + name = configuration.walletManifest.name, + appName = appName, + imageUrl = configuration.walletManifest.imageUrl, + aboutUrl = configuration.walletManifest.aboutUrl, + universalUrl = configuration.walletManifest.universalLink.takeIf { it.isNotBlank() }, + platforms = listOf(WebViewConstants.PLATFORM_ANDROID), + ), + deviceInfo = DeviceInfoPayload( + platform = configuration.deviceInfo?.platform ?: WebViewConstants.PLATFORM_ANDROID, + appName = appName, + appVersion = appVersion, + maxProtocolVersion = configuration.deviceInfo?.maxProtocolVersion ?: NetworkConstants.MAX_PROTOCOL_VERSION, + features = buildList { + add(JsonPrimitive(JsonConstants.FEATURE_SEND_TRANSACTION)) + add( + buildJsonObject { + put(JsonConstants.KEY_NAME, JsonConstants.FEATURE_SEND_TRANSACTION) + put(JsonConstants.KEY_MAX_MESSAGES, resolveMaxMessages(configuration, featuresToUse)) + }, + ) + add( + buildJsonObject { + put(JsonConstants.KEY_NAME, JsonConstants.FEATURE_SIGN_DATA) + put( + JsonConstants.KEY_TYPES, + buildJsonArray { + for (type in resolveSignDataTypes(configuration, featuresToUse)) { + add(JsonPrimitive(type)) + } + }, + ) + }, + ) + }, + ), + disableNetworkSend = if (configuration.dev?.disableNetworkSend == true) true else null, + disableTransactionEmulation = configuration.eventsConfiguration?.disableTransactionEmulation, + ) + + Logger.d( + TAG, + buildString { + append("Initializing WalletKit with persistent storage: ") + append(persistentStorageEnabled) + append(", app: ") + append(appName) + append(" v") + append(appVersion) + }, + ) + rpcClient.call(BridgeMethodConstants.METHOD_INIT, JSONObject(json.encodeToString(payload))) + + currentConfig = configuration + Logger.d(TAG, "WalletKit initialized. Event listeners will be set up on-demand.") + } + + private fun resolveAppVersion(configuration: TONWalletKitConfiguration): String = + configuration.deviceInfo?.appVersion ?: try { - val packageInfo = appContext.packageManager.getPackageInfo(appContext.packageName, 0) - packageInfo.versionName ?: NetworkConstants.DEFAULT_APP_VERSION + appContext.packageManager.getPackageInfo(appContext.packageName, 0) + .versionName ?: NetworkConstants.DEFAULT_APP_VERSION } catch (e: Exception) { Logger.w(TAG, ERROR_FAILED_GET_APP_VERSION, e) NetworkConstants.DEFAULT_APP_VERSION } - val appName = configuration.deviceInfo?.appName + private fun resolveAppName(configuration: TONWalletKitConfiguration): String = + configuration.deviceInfo?.appName ?: try { val manifestName = configuration.walletManifest.appName manifestName.ifBlank { @@ -166,134 +259,6 @@ internal class InitializationManager( appContext.packageName } - val payload = - JSONObject().apply { - put(JsonConstants.KEY_NETWORK, currentNetwork) - tonClientEndpoint?.let { put(JsonConstants.KEY_API_URL, it) } - put(JsonConstants.KEY_TON_API_URL, apiBaseUrl) - - // Pass all configured networks (matching iOS bridge format: networkConfigurations) - put( - "networkConfigurations", - org.json.JSONArray().apply { - for (networkConfig in configuration.networkConfigurations) { - put( - JSONObject().apply { - put( - "network", - JSONObject().apply { - put("chainId", networkConfig.network.chainId) - }, - ) - val apiClientTypeStr = when (networkConfig.apiClientType) { - TONWalletKitConfiguration.APIClientType.DEFAULT -> "default" - TONWalletKitConfiguration.APIClientType.TONCENTER -> "toncenter" - TONWalletKitConfiguration.APIClientType.TONAPI -> "tonapi" - TONWalletKitConfiguration.APIClientType.CUSTOM -> "custom" - } - put("apiClientType", apiClientTypeStr) - networkConfig.apiClientConfiguration?.let { apiConfig -> - put( - "apiClientConfiguration", - JSONObject().apply { - apiConfig.url?.takeIf { it.isNotBlank() }?.let { - put("url", it) - } - apiConfig.key?.takeIf { it.isNotBlank() }?.let { - put("key", it) - } - }, - ) - } - }, - ) - } - }, - ) - - configuration.bridge.bridgeUrl.takeIf { it.isNotBlank() }?.let { put(JsonConstants.KEY_BRIDGE_URL, it) } - configuration.walletManifest.name.takeIf { it.isNotBlank() }?.let { put(JsonConstants.KEY_BRIDGE_NAME, it) } - - put( - JsonConstants.KEY_WALLET_MANIFEST, - JSONObject().apply { - put(JsonConstants.KEY_NAME, configuration.walletManifest.name) - put(JsonConstants.KEY_APP_NAME, appName) - put(JsonConstants.KEY_IMAGE_URL, configuration.walletManifest.imageUrl) - put(JsonConstants.KEY_ABOUT_URL, configuration.walletManifest.aboutUrl) - configuration.walletManifest.universalLink.takeIf { it.isNotBlank() }?.let { - put(JsonConstants.KEY_UNIVERSAL_URL, it) - } - put( - JsonConstants.KEY_PLATFORMS, - JSONArray().apply { put(WebViewConstants.PLATFORM_ANDROID) }, - ) - }, - ) - - put( - JsonConstants.KEY_DEVICE_INFO, - JSONObject().apply { - put(JsonConstants.KEY_PLATFORM, configuration.deviceInfo?.platform ?: WebViewConstants.PLATFORM_ANDROID) - put(JsonConstants.KEY_APP_NAME, appName) - put(JsonConstants.KEY_APP_VERSION, appVersion) - put(JsonConstants.KEY_MAX_PROTOCOL_VERSION, configuration.deviceInfo?.maxProtocolVersion ?: NetworkConstants.MAX_PROTOCOL_VERSION) - put( - JsonConstants.KEY_FEATURES, - JSONArray().apply { - // Use deviceInfo.features if provided, otherwise use configuration.features - val featuresToUse = configuration.deviceInfo?.features ?: configuration.features - - // Add "SendTransaction" string for backward compatibility - put(JsonConstants.FEATURE_SEND_TRANSACTION) - // Add SendTransaction object with maxMessages for extended info - put( - JSONObject().apply { - put(JsonConstants.KEY_NAME, JsonConstants.FEATURE_SEND_TRANSACTION) - put(JsonConstants.KEY_MAX_MESSAGES, resolveMaxMessages(configuration, featuresToUse)) - }, - ) - put( - JSONObject().apply { - put(JsonConstants.KEY_NAME, JsonConstants.FEATURE_SIGN_DATA) - put(JsonConstants.KEY_TYPES, JSONArray(resolveSignDataTypes(configuration, featuresToUse))) - }, - ) - }, - ) - }, - ) - - // Pass disableNetworkSend flag for testing (transactions simulated but not sent) - configuration.dev?.disableNetworkSend?.let { disableNetworkSend -> - if (disableNetworkSend) { - put(JsonConstants.KEY_DISABLE_NETWORK_SEND, true) - } - } - configuration.eventsConfiguration?.let { eventsConfig -> - put("disableTransactionEmulation", eventsConfig.disableTransactionEmulation) - } - } - - Logger.d( - TAG, - buildString { - append("Initializing WalletKit with persistent storage: ") - append(persistentStorageEnabled) - append(", app: ") - append(appName) - append(" v") - append(appVersion) - }, - ) - rpcClient.call(BridgeMethodConstants.METHOD_INIT, payload) - - // Store the configuration for later use (e.g., WebView injection) - currentConfig = configuration - - Logger.d(TAG, "WalletKit initialized. Event listeners will be set up on-demand.") - } - private fun resolveNetworkName(configuration: TONWalletKitConfiguration): String = when (configuration.network.chainId) { TONNetwork.MAINNET.chainId -> NetworkConstants.NETWORK_MAINNET @@ -343,6 +308,55 @@ internal class InitializationManager( } } + @Serializable + private data class InitPayload( + val network: String, + @SerialName("apiUrl") val apiUrl: String? = null, + @SerialName("tonApiUrl") val tonApiUrl: String, + val networkConfigurations: List, + @SerialName("bridgeUrl") val bridgeUrl: String? = null, + @SerialName("bridgeName") val bridgeName: String? = null, + @SerialName("walletManifest") val walletManifest: WalletManifestPayload, + @SerialName("deviceInfo") val deviceInfo: DeviceInfoPayload, + @SerialName("disableNetworkSend") val disableNetworkSend: Boolean? = null, + @SerialName("disableTransactionEmulation") val disableTransactionEmulation: Boolean? = null, + ) + + @Serializable + private data class NetworkConfigPayload( + val network: NetworkPayload, + val apiClientType: String, + val apiClientConfiguration: ApiClientConfigPayload? = null, + ) + + @Serializable + private data class NetworkPayload(val chainId: String) + + @Serializable + private data class ApiClientConfigPayload( + val url: String? = null, + val key: String? = null, + ) + + @Serializable + private data class WalletManifestPayload( + val name: String, + @SerialName("appName") val appName: String, + @SerialName("imageUrl") val imageUrl: String, + @SerialName("aboutUrl") val aboutUrl: String, + @SerialName("universalUrl") val universalUrl: String? = null, + val platforms: List, + ) + + @Serializable + private data class DeviceInfoPayload( + val platform: String, + @SerialName("appName") val appName: String, + @SerialName("appVersion") val appVersion: String, + @SerialName("maxProtocolVersion") val maxProtocolVersion: Int, + val features: List, + ) + private companion object { private const val TAG = LogConstants.TAG_WEBVIEW_ENGINE private const val ERROR_WALLETKIT_AUTO_INIT_FAILED = "WalletKit auto-initialization failed" diff --git a/TONWalletKit-Android/impl/src/main/java/io/ton/walletkit/engine/infrastructure/MessageDispatcher.kt b/TONWalletKit-Android/impl/src/main/java/io/ton/walletkit/engine/infrastructure/MessageDispatcher.kt index 60a27f61..b2b77909 100644 --- a/TONWalletKit-Android/impl/src/main/java/io/ton/walletkit/engine/infrastructure/MessageDispatcher.kt +++ b/TONWalletKit-Android/impl/src/main/java/io/ton/walletkit/engine/infrastructure/MessageDispatcher.kt @@ -23,6 +23,9 @@ package io.ton.walletkit.engine.infrastructure import android.os.Handler import io.ton.walletkit.WalletKitBridgeException +import io.ton.walletkit.api.generated.TONPreparedSignData +import io.ton.walletkit.api.generated.TONProofMessage +import io.ton.walletkit.api.generated.TONTransactionRequest import io.ton.walletkit.browser.TonConnectInjector import io.ton.walletkit.engine.parsing.EventParser import io.ton.walletkit.engine.state.AdapterManager @@ -35,6 +38,7 @@ import io.ton.walletkit.internal.constants.LogConstants import io.ton.walletkit.internal.constants.ResponseConstants import io.ton.walletkit.internal.constants.WebViewConstants import io.ton.walletkit.internal.util.Logger +import io.ton.walletkit.model.TONWalletAdapter import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -140,6 +144,41 @@ internal class MessageDispatcher( // delivered back via window.__walletkitResponse(id, resultJson, errorJson). // ────────────────────────────────────────────────────────────────────────── + private val requestHandlers = mapOf String>( + REQUEST_METHOD_SIGN_WITH_CUSTOM_SIGNER to { params -> + val signerId = params.getString(ResponseConstants.KEY_SIGNER_ID) + val signer = signerManager.getSigner(signerId) + ?: throw IllegalArgumentException("Custom signer not found: $signerId") + val dataArray = params.getJSONArray("data") + val bytes = ByteArray(dataArray.length()) { dataArray.getInt(it).toByte() } + signer.sign(bytes).value + }, + REQUEST_METHOD_ADAPTER_GET_STATE_INIT to { params -> + params.requireAdapter().stateInit().value + }, + REQUEST_METHOD_ADAPTER_SIGN_TRANSACTION to { params -> + val adapter = params.requireAdapter() + val request = json.decodeFromString(params.getString("input")) + adapter.signedSendTransaction(request, params.optBoolean("fakeSignature", false)).value + }, + REQUEST_METHOD_ADAPTER_SIGN_DATA to { params -> + val adapter = params.requireAdapter() + val request = json.decodeFromString(params.getString("input")) + adapter.signedSignData(request, params.optBoolean("fakeSignature", false)).value + }, + REQUEST_METHOD_ADAPTER_SIGN_TON_PROOF to { params -> + val adapter = params.requireAdapter() + val request = json.decodeFromString(params.getString("input")) + adapter.signedTonProof(request, params.optBoolean("fakeSignature", false)).value + }, + ) + + private fun JSONObject.requireAdapter(): TONWalletAdapter { + val adapterId = getString("adapterId") + return adapterManager.getAdapter(adapterId) + ?: throw IllegalArgumentException("Adapter not found: $adapterId") + } + private fun handleRequest(payload: JSONObject) { val id = payload.optString(ResponseConstants.KEY_ID) val method = payload.optString(ResponseConstants.KEY_METHOD) @@ -162,60 +201,14 @@ internal class MessageDispatcher( } /** - * Dispatches a reverse-RPC method to the appropriate native manager. + * Dispatches a reverse-RPC method to the appropriate native handler. * * @return The result as a raw string (already a JSON-safe value). */ private suspend fun executeNativeRequest(method: String, params: JSONObject): String { - return when (method) { - REQUEST_METHOD_SIGN_WITH_CUSTOM_SIGNER -> { - val signerId = params.getString(ResponseConstants.KEY_SIGNER_ID) - val dataArray = params.getJSONArray("data") - val bytes = ByteArray(dataArray.length()) { dataArray.getInt(it).toByte() } - val signer = signerManager.getSigner(signerId) - ?: throw IllegalArgumentException("Custom signer not found: $signerId") - signer.sign(bytes).value - } - - REQUEST_METHOD_ADAPTER_GET_STATE_INIT -> { - val adapterId = params.getString("adapterId") - val adapter = adapterManager.getAdapter(adapterId) - ?: throw IllegalArgumentException("Adapter not found: $adapterId") - adapter.stateInit().value - } - - REQUEST_METHOD_ADAPTER_SIGN_TRANSACTION -> { - val adapterId = params.getString("adapterId") - val inputJson = params.getString("input") - val fakeSignature = params.optBoolean("fakeSignature", false) - val adapter = adapterManager.getAdapter(adapterId) - ?: throw IllegalArgumentException("Adapter not found: $adapterId") - val request = json.decodeFromString(inputJson) - adapter.signedSendTransaction(request, fakeSignature).value - } - - REQUEST_METHOD_ADAPTER_SIGN_DATA -> { - val adapterId = params.getString("adapterId") - val inputJson = params.getString("input") - val fakeSignature = params.optBoolean("fakeSignature", false) - val adapter = adapterManager.getAdapter(adapterId) - ?: throw IllegalArgumentException("Adapter not found: $adapterId") - val request = json.decodeFromString(inputJson) - adapter.signedSignData(request, fakeSignature).value - } - - REQUEST_METHOD_ADAPTER_SIGN_TON_PROOF -> { - val adapterId = params.getString("adapterId") - val inputJson = params.getString("input") - val fakeSignature = params.optBoolean("fakeSignature", false) - val adapter = adapterManager.getAdapter(adapterId) - ?: throw IllegalArgumentException("Adapter not found: $adapterId") - val request = json.decodeFromString(inputJson) - adapter.signedTonProof(request, fakeSignature).value - } - - else -> throw IllegalArgumentException("Unknown reverse-RPC method: $method") - } + val handler = requestHandlers[method] + ?: throw IllegalArgumentException("Unknown reverse-RPC method: $method") + return handler(params) } /** @@ -276,11 +269,9 @@ internal class MessageDispatcher( data.put(key, payload.get(key)) } } - val readyEvent = - JSONObject().apply { - put(ResponseConstants.KEY_TYPE, ResponseConstants.VALUE_KIND_READY) - put(ResponseConstants.KEY_DATA, data) - } + val readyEvent = JSONObject() + .put(ResponseConstants.KEY_TYPE, ResponseConstants.VALUE_KIND_READY) + .put(ResponseConstants.KEY_DATA, data) handleEvent(readyEvent) } @@ -360,16 +351,10 @@ internal class MessageDispatcher( if (callId != null) { // Create error response for this specific call - val errorResponse = JSONObject().apply { - put(ResponseConstants.KEY_KIND, ResponseConstants.VALUE_KIND_RESPONSE) - put(ResponseConstants.KEY_ID, callId) - put( - ResponseConstants.KEY_ERROR, - JSONObject().apply { - put(ResponseConstants.KEY_MESSAGE, exception.message ?: "Bridge error") - }, - ) - } + val errorResponse = JSONObject() + .put(ResponseConstants.KEY_KIND, ResponseConstants.VALUE_KIND_RESPONSE) + .put(ResponseConstants.KEY_ID, callId) + .put(ResponseConstants.KEY_ERROR, JSONObject().put(ResponseConstants.KEY_MESSAGE, exception.message ?: "Bridge error")) // Dispatch the error response to fail the specific call rpcClient.handleResponse(callId, errorResponse) diff --git a/TONWalletKit-Android/impl/src/main/java/io/ton/walletkit/engine/infrastructure/WebViewManager.kt b/TONWalletKit-Android/impl/src/main/java/io/ton/walletkit/engine/infrastructure/WebViewManager.kt index 5877d7f2..b6105782 100644 --- a/TONWalletKit-Android/impl/src/main/java/io/ton/walletkit/engine/infrastructure/WebViewManager.kt +++ b/TONWalletKit-Android/impl/src/main/java/io/ton/walletkit/engine/infrastructure/WebViewManager.kt @@ -53,6 +53,7 @@ import io.ton.walletkit.internal.constants.WebViewConstants import io.ton.walletkit.internal.util.Logger import io.ton.walletkit.model.TONBase64 import io.ton.walletkit.model.TONUserFriendlyAddress +import io.ton.walletkit.model.TONWalletAdapter import io.ton.walletkit.session.SessionFilter import io.ton.walletkit.session.TONConnectSessionManager import kotlinx.coroutines.CompletableDeferred @@ -278,6 +279,19 @@ internal class WebViewManager( } private inner class JsBinding { + private val adapterSyncHandlers = + mapOf String>( + "getPublicKey" to { adapter, _ -> adapter.publicKey().value }, + "getNetwork" to { adapter, _ -> + JSONObject().apply { put("chainId", adapter.network().chainId) }.toString() + }, + "getAddress" to { adapter, _ -> adapter.address(adapter.network().isTestnet).value }, + "getWalletId" to { adapter, _ -> adapter.identifier() }, + "getSupportedFeatures" to { adapter, _ -> + adapter.supportedFeatures()?.let { featuresToJson(it).toString() } ?: "null" + }, + ) + @JavascriptInterface fun postMessage(json: String) { try { @@ -327,18 +341,9 @@ internal class WebViewManager( val adapterId = params.getString("adapterId") val adapter = adapterManager.getAdapter(adapterId) ?: throw IllegalArgumentException("Adapter not found: $adapterId") - when (method) { - "getPublicKey" -> adapter.publicKey().value - "getNetwork" -> JSONObject().apply { put("chainId", adapter.network().chainId) }.toString() - "getAddress" -> adapter.address(adapter.network().isTestnet).value - "getWalletId" -> adapter.identifier() - "getSupportedFeatures" -> { - val features = adapter.supportedFeatures() - ?: return@withTimeout "null" - featuresToJson(features).toString() - } - else -> throw IllegalArgumentException("Unknown sync adapter method: $method") - } + val handler = adapterSyncHandlers[method] + ?: throw IllegalArgumentException("Unknown sync adapter method: $method") + handler(adapter, params) } } diff --git a/TONWalletKit-Android/impl/src/main/java/io/ton/walletkit/engine/operations/AssetOperations.kt b/TONWalletKit-Android/impl/src/main/java/io/ton/walletkit/engine/operations/AssetOperations.kt index e7da019a..29b5c44d 100644 --- a/TONWalletKit-Android/impl/src/main/java/io/ton/walletkit/engine/operations/AssetOperations.kt +++ b/TONWalletKit-Android/impl/src/main/java/io/ton/walletkit/engine/operations/AssetOperations.kt @@ -42,7 +42,7 @@ import io.ton.walletkit.exceptions.JSValueConversionException import io.ton.walletkit.internal.constants.BridgeMethodConstants import kotlinx.serialization.SerializationException import kotlinx.serialization.json.Json -import org.json.JSONObject +import kotlinx.serialization.json.encodeToJsonElement /** * Contains NFT and Jetton related bridge calls such as listing assets and building @@ -120,16 +120,16 @@ internal class AssetOperations( ): String { ensureInitialized() - val messageJson = json.encodeToString(io.ton.walletkit.api.generated.TONNFTRawTransferRequestMessage.serializer(), params.message) val request = CreateTransferNftRawRequest( walletId = walletId, nftAddress = params.nftAddress.value, transferAmount = params.transferAmount, - message = messageJson, + message = json.encodeToJsonElement( + io.ton.walletkit.api.generated.TONNFTRawTransferRequestMessage.serializer(), + params.message, + ), ) - val requestObj = json.toJSONObject(request) - requestObj.put("message", JSONObject(messageJson)) - val result = rpcClient.call(BridgeMethodConstants.METHOD_CREATE_TRANSFER_NFT_RAW_TRANSACTION, requestObj) + val result = rpcClient.call(BridgeMethodConstants.METHOD_CREATE_TRANSFER_NFT_RAW_TRANSACTION, json.toJSONObject(request)) return result.toString() } diff --git a/TONWalletKit-Android/impl/src/main/java/io/ton/walletkit/engine/operations/TonConnectOperations.kt b/TONWalletKit-Android/impl/src/main/java/io/ton/walletkit/engine/operations/TonConnectOperations.kt index 64cdd35c..ae05c95d 100644 --- a/TONWalletKit-Android/impl/src/main/java/io/ton/walletkit/engine/operations/TonConnectOperations.kt +++ b/TONWalletKit-Android/impl/src/main/java/io/ton/walletkit/engine/operations/TonConnectOperations.kt @@ -98,48 +98,33 @@ internal class TonConnectOperations( } } ?: JSONArray() + val domain = resolveDomain(url) val messageInfo = JSONObject().apply { put("messageId", messageId) put("tabId", messageId) - put( - "domain", - url?.let { - try { - val parsedUrl = java.net.URL(it) - "${parsedUrl.protocol}://${parsedUrl.host}" + (if (parsedUrl.port != -1 && parsedUrl.port != parsedUrl.defaultPort) ":${parsedUrl.port}" else "") - } catch (e: Exception) { - "internal-browser" - } - } ?: "internal-browser", - ) + put("domain", domain) walletId?.let { put("walletId", it) } } - val request = JSONObject().apply { - put("id", messageId) - put("method", method) - put("params", params) - } + val request = JSONObject() + .put("id", messageId) + .put("method", method) + .put("params", params) - val argsArray = JSONArray().apply { - put(messageInfo) - put(request) - } + val argsArray = JSONArray() + .put(messageInfo) + .put(request) val result = rpcClient.call(BridgeMethodConstants.METHOD_PROCESS_INTERNAL_BROWSER_REQUEST, argsArray) responseCallback(result) } catch (e: Exception) { Logger.e(TAG, "Failed to process internal browser request", e) - val errorResponse = - JSONObject().apply { - put( - ResponseConstants.KEY_ERROR, - JSONObject().apply { - put(ResponseConstants.KEY_MESSAGE, e.message ?: ERROR_FAILED_PROCESS_REQUEST) - put(ResponseConstants.KEY_CODE, 500) - }, - ) - } + val errorResponse = JSONObject().put( + ResponseConstants.KEY_ERROR, + JSONObject() + .put(ResponseConstants.KEY_MESSAGE, e.message ?: ERROR_FAILED_PROCESS_REQUEST) + .put(ResponseConstants.KEY_CODE, 500), + ) responseCallback(errorResponse) } } @@ -154,10 +139,9 @@ internal class TonConnectOperations( val walletId = event.walletId ?: throw WalletKitBridgeException("Wallet ID is required") // Send array [event, response] - walletkit expects: approveConnectRequest(event, response?) - val argsArray = JSONArray().apply { - put(json.toJSONObject(event)) - put(if (response != null) json.toJSONObject(response) else JSONObject.NULL) - } + val argsArray = JSONArray() + .put(json.toJSONObject(event)) + .put(if (response != null) json.toJSONObject(response) else JSONObject.NULL) rpcClient.call(BridgeMethodConstants.METHOD_APPROVE_CONNECT_REQUEST, argsArray) } @@ -166,11 +150,10 @@ internal class TonConnectOperations( ensureInitialized() // Send array [event, reason, errorCode] - walletkit expects: rejectConnectRequest(event, reason?, errorCode?) - val argsArray = JSONArray().apply { - put(json.toJSONObject(event)) - put(reason ?: JSONObject.NULL) - put(errorCode ?: JSONObject.NULL) - } + val argsArray = JSONArray() + .put(json.toJSONObject(event)) + .put(reason ?: JSONObject.NULL) + .put(errorCode ?: JSONObject.NULL) rpcClient.call(BridgeMethodConstants.METHOD_REJECT_CONNECT_REQUEST, argsArray) } @@ -184,10 +167,9 @@ internal class TonConnectOperations( val walletId = event.walletId ?: throw WalletKitBridgeException(ERROR_WALLET_ID_REQUIRED) // Send array [event, response] - walletkit expects: approveTransactionRequest(event, response?) - val argsArray = JSONArray().apply { - put(json.toJSONObject(event)) - put(if (response != null) json.toJSONObject(response) else JSONObject.NULL) - } + val argsArray = JSONArray() + .put(json.toJSONObject(event)) + .put(if (response != null) json.toJSONObject(response) else JSONObject.NULL) rpcClient.call(BridgeMethodConstants.METHOD_APPROVE_TRANSACTION_REQUEST, argsArray) } @@ -197,17 +179,13 @@ internal class TonConnectOperations( // Send array [event, reason] - walletkit expects: rejectTransactionRequest(event, reason?) // reason can be string or {code, message} object val reasonValue = if (errorCode != null) { - JSONObject().apply { - put("code", errorCode) - put("message", reason ?: "") - } + JSONObject().put("code", errorCode).put("message", reason ?: "") } else { reason ?: JSONObject.NULL } - val argsArray = JSONArray().apply { - put(json.toJSONObject(event)) - put(reasonValue) - } + val argsArray = JSONArray() + .put(json.toJSONObject(event)) + .put(reasonValue) rpcClient.call(BridgeMethodConstants.METHOD_REJECT_TRANSACTION_REQUEST, argsArray) } @@ -221,10 +199,9 @@ internal class TonConnectOperations( val walletId = event.walletId ?: throw WalletKitBridgeException(ERROR_WALLET_ID_REQUIRED) // Send array [event, response] - walletkit expects: approveSignDataRequest(event, response?) - val argsArray = JSONArray().apply { - put(json.toJSONObject(event)) - put(if (response != null) json.toJSONObject(response) else JSONObject.NULL) - } + val argsArray = JSONArray() + .put(json.toJSONObject(event)) + .put(if (response != null) json.toJSONObject(response) else JSONObject.NULL) rpcClient.call(BridgeMethodConstants.METHOD_APPROVE_SIGN_DATA_REQUEST, argsArray) } @@ -232,10 +209,9 @@ internal class TonConnectOperations( ensureInitialized() // Send array [event, reason] - walletkit expects: rejectSignDataRequest(event, reason?) - val argsArray = JSONArray().apply { - put(json.toJSONObject(event)) - put(reason ?: JSONObject.NULL) - } + val argsArray = JSONArray() + .put(json.toJSONObject(event)) + .put(reason ?: JSONObject.NULL) rpcClient.call(BridgeMethodConstants.METHOD_REJECT_SIGN_DATA_REQUEST, argsArray) } @@ -281,6 +257,18 @@ internal class TonConnectOperations( rpcClient.call(BridgeMethodConstants.METHOD_DISCONNECT_SESSION, sessionId) } + private fun resolveDomain(url: String?): String { + return url?.let { + try { + val parsedUrl = java.net.URL(it) + "${parsedUrl.protocol}://${parsedUrl.host}" + + if (parsedUrl.port != -1 && parsedUrl.port != parsedUrl.defaultPort) ":${parsedUrl.port}" else "" + } catch (e: Exception) { + "internal-browser" + } + } ?: "internal-browser" + } + private fun JSONObject.optNullableString(key: String): String? { val value = opt(key) return when (value) { diff --git a/TONWalletKit-Android/impl/src/main/java/io/ton/walletkit/engine/operations/TransactionOperations.kt b/TONWalletKit-Android/impl/src/main/java/io/ton/walletkit/engine/operations/TransactionOperations.kt index 908f416a..e67176dc 100644 --- a/TONWalletKit-Android/impl/src/main/java/io/ton/walletkit/engine/operations/TransactionOperations.kt +++ b/TONWalletKit-Android/impl/src/main/java/io/ton/walletkit/engine/operations/TransactionOperations.kt @@ -35,7 +35,7 @@ import io.ton.walletkit.internal.constants.BridgeMethodConstants import io.ton.walletkit.internal.constants.ResponseConstants import kotlinx.serialization.SerializationException import kotlinx.serialization.json.Json -import org.json.JSONObject +import kotlinx.serialization.json.JsonElement /** * Groups TON transaction related bridge operations including creation, preview, @@ -85,26 +85,35 @@ internal class TransactionOperations( suspend fun handleNewTransaction(walletId: String, transactionContent: String) { ensureInitialized() - val requestObj = json.toJSONObject(HandleNewTransactionRequest(walletId = walletId, transactionContent = transactionContent)) - requestObj.put("transactionContent", JSONObject(transactionContent)) - rpcClient.call(BridgeMethodConstants.METHOD_HANDLE_NEW_TRANSACTION, requestObj) + rpcClient.call( + BridgeMethodConstants.METHOD_HANDLE_NEW_TRANSACTION, + json.toJSONObject( + HandleNewTransactionRequest(walletId = walletId, transactionContent = json.decodeFromString(transactionContent)), + ), + ) } suspend fun sendTransaction(walletId: String, transactionContent: String): String { ensureInitialized() - val requestObj = json.toJSONObject(SendTransactionRequest(walletId = walletId, transactionContent = transactionContent)) - requestObj.put("transactionContent", JSONObject(transactionContent)) - val result = rpcClient.call(BridgeMethodConstants.METHOD_SEND_TRANSACTION, requestObj) + val result = rpcClient.call( + BridgeMethodConstants.METHOD_SEND_TRANSACTION, + json.toJSONObject( + SendTransactionRequest(walletId = walletId, transactionContent = json.decodeFromString(transactionContent)), + ), + ) return result.optString("boc", result.optString(ResponseConstants.KEY_SIGNED_BOC, "")) } suspend fun getTransactionPreview(walletId: String, transactionContent: String): TONTransactionEmulatedPreview { ensureInitialized() - val requestObj = json.toJSONObject(GetTransactionPreviewRequest(walletId = walletId, transactionContent = transactionContent)) - requestObj.put("transactionContent", JSONObject(transactionContent)) - val result = rpcClient.call(BridgeMethodConstants.METHOD_GET_TRANSACTION_PREVIEW, requestObj) + val result = rpcClient.call( + BridgeMethodConstants.METHOD_GET_TRANSACTION_PREVIEW, + json.toJSONObject( + GetTransactionPreviewRequest(walletId = walletId, transactionContent = json.decodeFromString(transactionContent)), + ), + ) return try { json.decodeFromString(TONTransactionEmulatedPreview.serializer(), result.toString()) } catch (e: SerializationException) { diff --git a/TONWalletKit-Android/impl/src/main/java/io/ton/walletkit/engine/operations/requests/AssetRequests.kt b/TONWalletKit-Android/impl/src/main/java/io/ton/walletkit/engine/operations/requests/AssetRequests.kt index 945ce93f..82048a52 100644 --- a/TONWalletKit-Android/impl/src/main/java/io/ton/walletkit/engine/operations/requests/AssetRequests.kt +++ b/TONWalletKit-Android/impl/src/main/java/io/ton/walletkit/engine/operations/requests/AssetRequests.kt @@ -23,6 +23,7 @@ package io.ton.walletkit.engine.operations.requests import io.ton.walletkit.api.generated.TONPagination import kotlinx.serialization.Serializable +import kotlinx.serialization.json.JsonElement /** * Internal bridge request models for asset operations (NFTs and Jettons). @@ -56,8 +57,7 @@ internal data class CreateTransferNftRawRequest( val walletId: String, val nftAddress: String, val transferAmount: String, - /** Serialized JSON of TONNFTRawTransferRequestMessage — sent as nested object to bridge */ - val message: String, + val message: JsonElement, ) @Serializable diff --git a/TONWalletKit-Android/impl/src/main/java/io/ton/walletkit/engine/operations/requests/TransactionRequests.kt b/TONWalletKit-Android/impl/src/main/java/io/ton/walletkit/engine/operations/requests/TransactionRequests.kt index 393353f6..9d9fbdd4 100644 --- a/TONWalletKit-Android/impl/src/main/java/io/ton/walletkit/engine/operations/requests/TransactionRequests.kt +++ b/TONWalletKit-Android/impl/src/main/java/io/ton/walletkit/engine/operations/requests/TransactionRequests.kt @@ -23,6 +23,7 @@ package io.ton.walletkit.engine.operations.requests import io.ton.walletkit.api.generated.TONTransferRequest import kotlinx.serialization.Serializable +import kotlinx.serialization.json.JsonElement /** * Internal bridge request models for transaction operations. @@ -50,18 +51,17 @@ internal data class CreateTransferMultiTonRequest( @Serializable internal data class HandleNewTransactionRequest( val walletId: String, - val transactionContent: String, + val transactionContent: JsonElement, ) @Serializable internal data class SendTransactionRequest( val walletId: String, - val transactionContent: String, + val transactionContent: JsonElement, ) @Serializable internal data class GetTransactionPreviewRequest( val walletId: String, - // JSON string - val transactionContent: String, + val transactionContent: JsonElement, )