Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
44d5f2e
feat: implement TON Connect session management and enhance session ha…
nikdim03 Jan 20, 2026
47f7270
feat: add custom session manager to demo app
nikdim03 Jan 20, 2026
ef3a45f
feat: add domain handling; refactor session creation options
nikdim03 Jan 21, 2026
b5a295f
feat: integrate Google Tink for X25519 keypair generation and update …
nikdim03 Jan 23, 2026
ffe6ee7
chore: regenerate API models
nikdim03 Jan 25, 2026
473dc00
feat: refactor TonConnectOperations to streamline dAppInfo handling &…
nikdim03 Jan 30, 2026
cf567d0
feat: update dAppInfo references to dAppName, dAppUrl, and dAppIconUr…
nikdim03 Jan 30, 2026
74c4cf7
feat: introduce custom API client interface
nikdim03 Jan 21, 2026
e1db2d1
feat: implement custom API client and configuration for testing
nikdim03 Jan 22, 2026
842ad1a
feat: add getBalance method to TONAPIClient and implement API call in…
nikdim03 Jan 23, 2026
9c1f373
feat: update TONConnectSession to include dApp metadata and refactor …
nikdim03 Jan 25, 2026
7729bc6
fix: resolve merge conflict in libs.versions.toml and update bridge b…
nikdim03 Jan 25, 2026
35ec7cb
feat: enhance WalletKitEngine with optional approval responses for co…
nikdim03 Jan 29, 2026
51995d0
feat: add payload parameter to TONConnectionApprovalProof and refacto…
nikdim03 Jan 29, 2026
42bfed9
feat: implement multi-network API Client Flow in demo app
nikdim03 Jan 30, 2026
4a7f4f4
feat: update TONWalletKitHelper to use Toncenter and TonAPI clients f…
nikdim03 Jan 30, 2026
28fb7bd
fix: update mjs
nikdim03 Feb 4, 2026
d0b888b
Merge pull request #35 from ton-connect/feat/api-client
nikdim03 Feb 4, 2026
17978ff
fix: unit tests
nikdim03 Feb 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions AndroidDemo/app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,11 @@ dependencies {
implementation(libs.androidxDatastorePreferences)

implementation(libs.kotlinxSerializationJson)

// Google Tink for X25519 keypair generation (used by TestSessionManager)
// Tink provides pure Java implementation, no native dependencies
implementation(libs.tinkAndroid)

debugImplementation(libs.leakcanaryAndroid)

// Testing - Unit Tests
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ class ConnectE2ETest : BaseE2ETest() {
dAppController.waitForDAppPage(timeoutMs = 10000)
dAppController.clearDAppStorage()
dAppController.closeBrowserFully()
} catch (e: Exception) {
} catch (e: Throwable) {
android.util.Log.w(TAG, "Failed to clean up dApp sessions in tearDown: ${e.message}")
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,268 @@
/*
* Copyright (c) 2025 TonTech
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package io.ton.walletkit.demo.core

import android.util.Log
import io.ton.walletkit.api.MAINNET
import io.ton.walletkit.api.TESTNET
import io.ton.walletkit.api.generated.TONGetMethodResult
import io.ton.walletkit.api.generated.TONNetwork
import io.ton.walletkit.api.generated.TONRawStackItem
import io.ton.walletkit.client.TONAPIClient
import io.ton.walletkit.model.TONBase64
import io.ton.walletkit.model.TONUserFriendlyAddress
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.withContext
import org.json.JSONObject
import java.net.URL

/**
* Test implementation of TONAPIClient for demonstration purposes.
*
* This class demonstrates how a wallet app can provide their own
* API client implementation to use custom infrastructure instead
* of the default TONCenter API.
*
* In a real implementation, this would:
* - Connect to your own TON node or API service
* - Handle authentication/API keys
* - Implement retry logic and error handling
*/
class TestAPIClient(
override val network: TONNetwork,
) : TONAPIClient {

private val tag = "TestAPIClient"

override suspend fun sendBoc(boc: TONBase64): String {
Log.d(tag, "🚀 sendBoc called on network: ${network.chainId}")
Log.d(tag, "📦 BOC (first 50 chars): ${boc.value.take(50)}...")

// Simulate network delay
delay(500)

// In a real implementation, you would:
// 1. Make HTTP POST to your TON API endpoint
// 2. Send the base64-encoded BOC in the request body
// 3. Return the transaction hash from the response

// For demo purposes, we'll return a mock transaction hash
val mockTxHash = "demo_tx_${System.currentTimeMillis()}"
Log.d(tag, "✅ sendBoc completed, mock hash: $mockTxHash")

return mockTxHash
}

override suspend fun runGetMethod(
address: TONUserFriendlyAddress,
method: String,
stack: List<TONRawStackItem>?,
seqno: Int?,
): TONGetMethodResult {
Log.d(tag, "📞 runGetMethod called on network: ${network.chainId}")
Log.d(tag, "📍 Address: ${address.value}")
Log.d(tag, "🔧 Method: $method")
Log.d(tag, "📚 Stack items: ${stack?.size ?: 0}")
Log.d(tag, "🔢 Seqno: $seqno")

// Simulate network delay
delay(300)

// In a real implementation, you would:
// 1. Make HTTP POST to your TON API endpoint (e.g., /runGetMethod)
// 2. Include address, method name, and optional stack/seqno
// 3. Parse the response and return TONGetMethodResult

// For demo purposes, return a mock result
val mockResult = TONGetMethodResult(
gasUsed = 1000,
stack = emptyList(), // Empty stack for demo
exitCode = 0, // Success exit code
)

Log.d(tag, "✅ runGetMethod completed, exitCode: ${mockResult.exitCode}")

return mockResult
}

override suspend fun getBalance(
address: TONUserFriendlyAddress,
seqno: Int?,
): String {
Log.d(tag, "💰 getBalance called on network: ${network.chainId}")
Log.d(tag, "📍 Address: ${address.value}")

// Make a real HTTP call to toncenter API
val baseUrl = when (network) {
TONNetwork.MAINNET -> "https://toncenter.com"
TONNetwork.TESTNET -> "https://testnet.toncenter.com"
else -> "https://toncenter.com"
}

return withContext(Dispatchers.IO) {
try {
val url = URL("$baseUrl/api/v3/addressInformation?address=${address.value}")
val connection = url.openConnection()
connection.setRequestProperty("Accept", "application/json")
connection.connectTimeout = 10000
connection.readTimeout = 10000

val response = connection.getInputStream().bufferedReader().readText()
val json = JSONObject(response)
val balance = json.optString("balance", "0")

Log.d(tag, "✅ getBalance completed, balance: $balance")
balance
} catch (e: Exception) {
Log.e(tag, "❌ getBalance failed", e)
throw e
}
}
}

companion object {
/**
* Create a TestAPIClient for mainnet.
*/
fun mainnet(): TestAPIClient = TestAPIClient(TONNetwork.MAINNET)

/**
* Create a TestAPIClient for testnet.
*/
fun testnet(): TestAPIClient = TestAPIClient(TONNetwork.TESTNET)
}
}

/**
* Example: ToncenterAPIClient - Uses toncenter.com API
*
* This demonstrates how wallet apps can have specialized API clients
* for different networks. For example:
* - Toncenter might be preferred for mainnet (stability, caching)
* - TonAPI might be preferred for testnet (more detailed responses)
*/
class ToncenterAPIClient(
override val network: TONNetwork,
) : TONAPIClient {

private val tag = "ToncenterAPIClient"

private val baseUrl: String = when (network) {
TONNetwork.MAINNET -> "https://toncenter.com"
TONNetwork.TESTNET -> "https://testnet.toncenter.com"
else -> "https://toncenter.com"
}

override suspend fun sendBoc(boc: TONBase64): String {
Log.d(tag, "🚀 [Toncenter] sendBoc on ${network.chainId}")
// Real implementation would call: POST $baseUrl/api/v3/sendBocReturnHash
delay(100)
return "toncenter_tx_${System.currentTimeMillis()}"
}

override suspend fun runGetMethod(
address: TONUserFriendlyAddress,
method: String,
stack: List<TONRawStackItem>?,
seqno: Int?,
): TONGetMethodResult {
Log.d(tag, "📞 [Toncenter] runGetMethod: $method on ${address.value}")
// Real implementation would call: POST $baseUrl/api/v3/runGetMethod
delay(100)
return TONGetMethodResult(gasUsed = 1000, stack = emptyList(), exitCode = 0)
}

override suspend fun getBalance(address: TONUserFriendlyAddress, seqno: Int?): String {
Log.d(tag, "💰 [Toncenter] getBalance on ${network.chainId}")
return withContext(Dispatchers.IO) {
val url = URL("$baseUrl/api/v3/addressInformation?address=${address.value}")
val connection = url.openConnection()
connection.setRequestProperty("Accept", "application/json")
val response = connection.getInputStream().bufferedReader().readText()
JSONObject(response).optString("balance", "0")
}
}

companion object {
fun mainnet() = ToncenterAPIClient(TONNetwork.MAINNET)
fun testnet() = ToncenterAPIClient(TONNetwork.TESTNET)
}
}

/**
* Example: TonAPIClient - Uses tonapi.io API
*
* Different API provider with different features.
* Demonstrates that each network can use a completely different backend.
*/
class TonAPIClient(
override val network: TONNetwork,
private val apiKey: String = "",
) : TONAPIClient {

private val tag = "TonAPIClient"

private val baseUrl: String = when (network) {
TONNetwork.MAINNET -> "https://tonapi.io"
TONNetwork.TESTNET -> "https://testnet.tonapi.io"
else -> "https://tonapi.io"
}

override suspend fun sendBoc(boc: TONBase64): String {
Log.d(tag, "🚀 [TonAPI] sendBoc on ${network.chainId}")
// Real implementation would call: POST $baseUrl/v2/blockchain/message
delay(100)
return "tonapi_tx_${System.currentTimeMillis()}"
}

override suspend fun runGetMethod(
address: TONUserFriendlyAddress,
method: String,
stack: List<TONRawStackItem>?,
seqno: Int?,
): TONGetMethodResult {
Log.d(tag, "📞 [TonAPI] runGetMethod: $method on ${address.value}")
// Real implementation would call: POST $baseUrl/v2/blockchain/accounts/{address}/methods/{method}
delay(100)
return TONGetMethodResult(gasUsed = 1000, stack = emptyList(), exitCode = 0)
}

override suspend fun getBalance(address: TONUserFriendlyAddress, seqno: Int?): String {
Log.d(tag, "💰 [TonAPI] getBalance on ${network.chainId}")
return withContext(Dispatchers.IO) {
val url = URL("$baseUrl/v2/accounts/${address.value}")
val connection = url.openConnection()
connection.setRequestProperty("Accept", "application/json")
if (apiKey.isNotEmpty()) {
connection.setRequestProperty("Authorization", "Bearer $apiKey")
}
val response = connection.getInputStream().bufferedReader().readText()
JSONObject(response).optString("balance", "0")
}
}

companion object {
fun mainnet(apiKey: String = "") = TonAPIClient(TONNetwork.MAINNET, apiKey)
fun testnet(apiKey: String = "") = TonAPIClient(TONNetwork.TESTNET, apiKey)
}
}
Loading
Loading