Skip to content

Commit

Permalink
Send SPL toke according to iOS version
Browse files Browse the repository at this point in the history
  • Loading branch information
aminsato committed Nov 2, 2024
1 parent 1def958 commit 629e506
Show file tree
Hide file tree
Showing 8 changed files with 94 additions and 70 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
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
Expand Up @@ -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
Expand Down Expand Up @@ -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(
Expand All @@ -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(
Expand All @@ -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)
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -148,6 +147,8 @@ data class SplAmountAccountJson(
data class SplAmountValueJson(
@SerialName("account")
val account: SplAmountAccountJson,
@SerialName("pubkey")
val pubKey: String,
)

@Serializable
Expand All @@ -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
Expand Up @@ -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()
}
}

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import com.vultisig.wallet.data.api.PolkadotApi
import com.vultisig.wallet.data.api.SolanaApi
import com.vultisig.wallet.data.api.ThorChainApi
import com.vultisig.wallet.data.api.chains.SuiApi
import com.vultisig.wallet.data.api.chains.TonApi
import com.vultisig.wallet.data.models.Chain
import com.vultisig.wallet.data.models.Coin
import com.vultisig.wallet.data.models.TokenStandard
Expand All @@ -16,6 +17,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

Expand Down Expand Up @@ -49,6 +52,7 @@ internal class BlockChainSpecificRepositoryImpl @Inject constructor(
private val blockChairApi: BlockChairApi,
private val polkadotApi: PolkadotApi,
private val suiApi: SuiApi,
private val tonApi: TonApi,
) : BlockChainSpecificRepository {

override suspend fun getSpecific(
Expand Down Expand Up @@ -169,18 +173,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,
)
)
}
Expand Down Expand Up @@ -220,16 +236,26 @@ internal class BlockChainSpecificRepositoryImpl @Inject constructor(
}

TokenStandard.SUI -> {
suiApi.getAllCoins(address)

BlockChainSpecificAndUtxo(
BlockChainSpecific.Sui(
referenceGasPrice = gasFee.value,
referenceGasPrice = suiApi.getReferenceGasPrice(),
coins = suiApi.getAllCoins(address),
),
utxos = emptyList(),
)
}

TokenStandard.TON -> {
BlockChainSpecificAndUtxo(
blockChainSpecific = BlockChainSpecific.Ton(
sequenceNumber = tonApi.getSpecificTransactionInfo(address)
.toString().toULong(),
expireAt = (Clock.System.now()
.epochSeconds + 600L).toULong(),
bounceable = false,
),
)
}
}

private fun ensureOneGweiPriorityFee(priorityFee: BigInteger): BigInteger {
Expand Down

0 comments on commit 629e506

Please sign in to comment.