Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Send SPL toke according to iOS version
Browse files Browse the repository at this point in the history
aminsato committed Nov 2, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
1 parent 1def958 commit bfb2303
Showing 7 changed files with 78 additions and 66 deletions.
Original file line number Diff line number Diff line change
@@ -302,7 +302,6 @@ internal class KeysignFlowViewModel @Inject constructor(
priorityFee = specific.priorityFee.toString(),
toTokenAssociatedAddress = specific.toAddressPubKey,
fromTokenAssociatedAddress = specific.fromAddressPubKey,
tokenAccountExists = specific.tokenAccountExists,
)
} else null,
polkadotSpecific = if (specific is BlockChainSpecific.Polkadot) {
50 changes: 32 additions & 18 deletions data/src/main/kotlin/com/vultisig/wallet/data/api/SolanaApi.kt
Original file line number Diff line number Diff line change
@@ -4,7 +4,6 @@ import com.vultisig.wallet.data.api.models.BroadcastTransactionRespJson
import com.vultisig.wallet.data.api.models.RecentBlockHashResponseJson
import com.vultisig.wallet.data.api.models.RpcPayload
import com.vultisig.wallet.data.api.models.SPLTokenRequestJson
import com.vultisig.wallet.data.api.models.SolanaAccountExistResponseJson
import com.vultisig.wallet.data.api.models.SolanaBalanceJson
import com.vultisig.wallet.data.api.models.SolanaFeeObjectJson
import com.vultisig.wallet.data.api.models.SolanaFeeObjectRespJson
@@ -46,7 +45,7 @@ interface SolanaApi {
suspend fun getSPLTokensInfo(tokens: List<String>): List<SplTokenJson>
suspend fun getSPLTokensInfo2(tokens: List<String>): List<SplTokenInfo>
suspend fun getSPLTokenBalance(walletAddress: String, coinAddress: String): String?
suspend fun doesTokenAccountExist(address: String, mint: String): Boolean
suspend fun getTokenAssociatedAccountByOwner(walletAddress: String, mintAddress: String): String?
}

internal class SolanaApiImp @Inject constructor(
@@ -59,6 +58,7 @@ internal class SolanaApiImp @Inject constructor(
private val rpcEndpoint2 = "https://solana-rpc.publicnode.com"
private val splTokensInfoEndpoint = "https://api.solana.fm/v1/tokens"
private val splTokensInfoEndpoint2 = "https://tokens.jup.ag/token"
private val solanaRentExemptionEndpoint = "https://api.devnet.solana.com"
override suspend fun getBalance(address: String): BigInteger {

val payload = RpcPayload(
@@ -85,7 +85,7 @@ internal class SolanaApiImp @Inject constructor(

override suspend fun getMinimumBalanceForRentExemption(): BigInteger = try {
httpClient.postRpc<SolanaMinimumBalanceForRentExemptionJson>(
rpcEndpoint,
solanaRentExemptionEndpoint,
"getMinimumBalanceForRentExemption",
params = buildJsonArray {
add(DATA_LENGTH_MINIMUM_BALANCE_FOR_RENT_EXEMPTION)
@@ -153,7 +153,7 @@ internal class SolanaApiImp @Inject constructor(
return "0"
}

override suspend fun broadcastTransaction(tx: String): String {
override suspend fun broadcastTransaction(tx: String): String? {
try {
val requestBody = RpcPayload(
jsonrpc = "2.0",
@@ -287,21 +287,35 @@ internal class SolanaApiImp @Inject constructor(
}
}

override suspend fun doesTokenAccountExist(address: String, mint: String): Boolean =
httpClient.postRpc<SolanaAccountExistResponseJson>(
rpcEndpoint,
"getTokenAccountsByOwner",
params = buildJsonArray {
add(address)
addJsonObject {
put("mint", mint)
}
addJsonObject {
put("encoding", "jsonParsed")
override suspend fun getTokenAssociatedAccountByOwner(
walletAddress: String,
mintAddress: String,
): String? {
try {
val response = httpClient.postRpc<SplAmountRpcResponseJson>(
url = rpcEndpoint2,
method = "getTokenAccountsByOwner",
params = buildJsonArray {
add(walletAddress)
addJsonObject {
put("mint", mintAddress)
}
addJsonObject {
put("encoding", ENCODING_SPL_REQUEST_PARAM)
}
}
},
).result.values.isNotEmpty()

)
if (response.error != null) {
Timber.d("getTokenAssociatedAccountByOwner error: ${response.error}")
return null
}
val value = response.value ?: error("getTokenAssociatedAccountByOwner error")
return value.value[0].pubKey
} catch (e: Exception) {
Timber.e(e)
return null
}
}


companion object {
Original file line number Diff line number Diff line change
@@ -2,7 +2,6 @@ package com.vultisig.wallet.data.api.models

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonArray

@Serializable
data class SplTokenAmountJson(
@@ -148,6 +147,8 @@ data class SplAmountAccountJson(
data class SplAmountValueJson(
@SerialName("account")
val account: SplAmountAccountJson,
@SerialName("pubkey")
val pubKey: String,
)

@Serializable
@@ -168,16 +169,4 @@ data class SplAmountRpcResponseResultJson(
data class SPLTokenRequestJson(
@SerialName("tokens")
val tokens: List<String>,
)

@Serializable
data class SolanaAccountExistResponseJson(
@SerialName("result")
val result: SolanaAccountExistResultJson,
)

@Serializable
data class SolanaAccountExistResultJson(
@SerialName("value")
val values: JsonArray
)
Original file line number Diff line number Diff line change
@@ -60,39 +60,37 @@ class SolanaHelper(
.build()
.toByteArray()
} else {
val fromAddress = AnyAddress(keysignPayload.coin.address, coinType)
val senderAddress = SolanaAddress(fromAddress.description())
val receiverAddress = SolanaAddress(toAddress.description())
val generatedSenderAssociatedAddress = senderAddress.defaultTokenAddress(
keysignPayload.coin.contractAddress
)
val generatedRecipientAssociatedAddress = receiverAddress.defaultTokenAddress(
keysignPayload.coin.contractAddress
)
val tokenAccountExists = keysignPayload.blockChainSpecific.tokenAccountExists
return if (tokenAccountExists == true) {
val transferTokenMessage = Solana.TokenTransfer.newBuilder()
if (solanaSpecific.fromAddressPubKey != null && solanaSpecific.toAddressPubKey!= null) {
val transfer = Solana.TokenTransfer.newBuilder()
.setTokenMintAddress(keysignPayload.coin.contractAddress)
.setRecipientTokenAddress(generatedRecipientAssociatedAddress)
.setSenderTokenAddress(generatedSenderAssociatedAddress)
.setSenderTokenAddress(solanaSpecific.fromAddressPubKey)
.setRecipientTokenAddress(solanaSpecific.toAddressPubKey)
.setAmount(keysignPayload.toAmount.toLong())
.setDecimals(keysignPayload.coin.decimal)

input.setTokenTransferTransaction(transferTokenMessage.build())
return input
.setTokenTransferTransaction(transfer.build())
.build()
.toByteArray()
} else {
val createAndTransferTokenMessage = Solana.CreateAndTransferToken.newBuilder()
.setTokenMintAddress(keysignPayload.coin.contractAddress)
.setRecipientTokenAddress(generatedRecipientAssociatedAddress)
.setSenderTokenAddress(generatedSenderAssociatedAddress)
.setAmount(keysignPayload.toAmount.toLong())
.setDecimals(keysignPayload.coin.decimal)
.setTokenProgramId(Solana.TokenProgramId.TokenProgram)
.setRecipientMainAddress(toAddress.description())
val receiverAddress = SolanaAddress(toAddress.description())
val generatedRecipientAssociatedAddress = receiverAddress.defaultTokenAddress(
keysignPayload.coin.contractAddress
)
val transferTokenMessage =
Solana.CreateAndTransferToken.newBuilder()
.setRecipientMainAddress(toAddress.description())
.setTokenMintAddress(keysignPayload.coin.contractAddress)
.setRecipientTokenAddress(generatedRecipientAssociatedAddress)
.setSenderTokenAddress(solanaSpecific.fromAddressPubKey)
.setAmount(keysignPayload.toAmount.toLong())
.setDecimals(keysignPayload.coin.decimal)

input.setCreateAndTransferTokenTransaction(createAndTransferTokenMessage.build())
return input
.setCreateAndTransferTokenTransaction(transferTokenMessage.build())
.build()
.toByteArray()
}
.build()
.toByteArray()
}
}

Original file line number Diff line number Diff line change
@@ -138,7 +138,6 @@ internal class KeysignPayloadProtoMapperImpl @Inject constructor() : KeysignPayl
priorityFee = BigInteger(it.priorityFee),
fromAddressPubKey = it.fromTokenAssociatedAddress,
toAddressPubKey = it.toTokenAssociatedAddress,
tokenAccountExists = it.tokenAccountExists,
)
}

Original file line number Diff line number Diff line change
@@ -40,7 +40,6 @@ sealed class BlockChainSpecific {
val priorityFee: BigInteger,
val fromAddressPubKey: String? = null,
val toAddressPubKey: String? = null,
val tokenAccountExists: Boolean? = null,
) : BlockChainSpecific()

data class Sui(
Original file line number Diff line number Diff line change
@@ -16,6 +16,8 @@ import com.vultisig.wallet.data.models.payload.BlockChainSpecific
import com.vultisig.wallet.data.models.payload.UtxoInfo
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import kotlinx.datetime.Clock
import timber.log.Timber
import java.math.BigInteger
import javax.inject.Inject

@@ -169,18 +171,30 @@ internal class BlockChainSpecificRepositoryImpl @Inject constructor(
val blockHash = async {
solanaApi.getRecentBlockHash()
}

val tokenAccountExists = async {
val fromAddressPubKey = async {
solanaApi.getTokenAssociatedAccountByOwner(
token.address,
token.contractAddress
).takeIf { !token.isNativeToken }
}
val toAddressPubKey = async {
dstAddress?.let {
solanaApi.doesTokenAccountExist(dstAddress, token.contractAddress)
solanaApi.getTokenAssociatedAccountByOwner(
dstAddress,
token.contractAddress
).takeIf { !token.isNativeToken }
}
}

val recentBlockHashResult = blockHash.await()
val fromAddressPubKeyResult = fromAddressPubKey.await()
val toAddressPubKeyResult = toAddressPubKey.await()
Timber.d("solana blockhash: $recentBlockHashResult")
BlockChainSpecificAndUtxo(
BlockChainSpecific.Solana(
recentBlockHash = blockHash.await(),
tokenAccountExists = tokenAccountExists.await(),
recentBlockHash = recentBlockHashResult,
priorityFee = gasFee.value,
fromAddressPubKey = fromAddressPubKeyResult,
toAddressPubKey = toAddressPubKeyResult,
)
)
}

0 comments on commit bfb2303

Please sign in to comment.