Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 4 additions & 0 deletions android/app/src/main/assets/chains/icons/tempo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,8 @@ class TestCreateAccountOperator {
Chain.Plasma,
Chain.XLayer,
Chain.Robinhood,
Chain.Stable -> "0x8f348F300873Fd5DA36950B2aC75a26584584feE"
Chain.Stable,
Chain.Tempo -> "0x8f348F300873Fd5DA36950B2aC75a26584584feE"
Chain.Solana -> "57mwmnV2rFuVDmhiJEjonD7cfuFtcaP9QvYNGfDEWK71"
Chain.Thorchain -> "thor1c8jd7ad9pcw4k3wkuqlkz4auv95mldr2kyhc65"
Chain.Mayachain -> "maya1c8jd7ad9pcw4k3wkuqlkz4auv95mldr2knf5vy"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.gemwallet.android.blockchain.gemstone

import com.gemwallet.android.ext.identifier
import com.gemwallet.android.ext.toChainType
import com.gemwallet.android.ext.toFeePriority
import com.gemwallet.android.model.Fee
Expand Down Expand Up @@ -53,7 +54,8 @@ internal fun Fee.toGemSignerFee(): GemTransactionLoadFee = GemTransactionLoadFee
option to value.toString()
}
}.toMap()
)
),
feeAssetId = feeAssetId.identifier,
)

internal fun List<GemFeeRate>.selectFeeRate(selection: FeeSelection): GemFeeRate = when (selection) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import com.gemwallet.android.blockchain.gemstone.selectFeeRate
import com.gemwallet.android.blockchain.gemstone.toFee
import com.gemwallet.android.blockchain.gemstone.toScanTransactionPayload
import com.gemwallet.android.blockchain.gemstone.validate
import com.gemwallet.android.ext.toAssetId
import com.gemwallet.android.ext.toFeePriority
import com.gemwallet.android.model.ConfirmParams
import com.gemwallet.android.model.FeeSelection
Expand All @@ -27,7 +28,6 @@ class SignerPreloaderProxy(
suspend fun preload(params: ConfirmParams, selection: FeeSelection): SignerParams = withContext(Dispatchers.IO) {
val assetId = params.assetId
val chain = assetId.chain
val feeAssetId = AssetId(chain)
val gemChain = assetId.chain.string
val destination = requireNotNull(params.destination()?.address)

Expand Down Expand Up @@ -78,6 +78,9 @@ class SignerPreloaderProxy(
}
scanDeferred.await()?.validate(params)
val result = transactionLoadDeferred.await()
val feeAssetId = result.fee.feeAssetId
?.let { requireNotNull(it.toAssetId()) { "Invalid fee asset ID: $it" } }
?: AssetId(chain)
val fee = chain.toFee(feeAssetId, selectedPriority, result.fee)

SignerParams(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ import com.gemwallet.android.model.DestinationAddress
import com.gemwallet.android.model.FeeSelection
import com.gemwallet.android.testkit.mockAccount
import com.gemwallet.android.testkit.mockAssetEthereum
import com.gemwallet.android.testkit.mockAssetTempoUSDCe
import com.gemwallet.android.testkit.mockGemTransactionLoadFee
import com.wallet.core.primitives.Asset
import com.wallet.core.primitives.AssetId
import com.wallet.core.primitives.FeePriority
import com.wallet.core.primitives.ScanTransaction
import io.mockk.coEvery
Expand All @@ -16,12 +19,10 @@ import io.mockk.slot
import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertEquals
import org.junit.Test
import uniffi.gemstone.GemFeeOptions
import uniffi.gemstone.GemFeeRate
import uniffi.gemstone.GemGasPriceType
import uniffi.gemstone.GemGatewayInterface
import uniffi.gemstone.GemTransactionData
import uniffi.gemstone.GemTransactionLoadFee
import uniffi.gemstone.GemTransactionLoadInput
import uniffi.gemstone.GemTransactionLoadMetadata
import java.math.BigInteger
Expand Down Expand Up @@ -78,6 +79,32 @@ class SignerPreloaderProxyTest {
coVerify(exactly = 1) { gateway.getTransactionLoad(any(), any()) }
}

@Test
fun preload_usesFeeAssetIdFromGatewayWhenPresent() = runBlocking {
val feeAsset = mockAssetTempoUSDCe()
val params = transferParams(feeAsset)
stubPreload()
coEvery { gateway.getTransactionLoad(any(), any()) } returns GemTransactionData(
fee = mockGemTransactionLoadFee(feeAssetId = feeAsset.id),
metadata = evmMetadata(),
)

val result = subject.preload(params, FeeSelection.Preset(FeePriority.Normal))

assertEquals(feeAsset.id, result.fee().feeAssetId)
}

@Test
fun preload_fallsBackToChainNativeFeeAssetWhenGatewayOmitsIt() = runBlocking {
val params = transferParams()
stubPreload()
stubTransactionLoad()

val result = subject.preload(params, FeeSelection.Preset(FeePriority.Normal))

assertEquals(AssetId(params.assetId.chain), result.fee().feeAssetId)
}

@Test
fun preload_blocksMaliciousScan() = runBlocking {
val params = transferParams()
Expand Down Expand Up @@ -110,9 +137,7 @@ class SignerPreloaderProxyTest {
assertEquals(params.asset.symbol, (error as? ConfirmError.ScanTransactionMemoRequired)?.symbol)
}

private fun transferParams(): ConfirmParams {
val asset = mockAssetEthereum()

private fun transferParams(asset: Asset = mockAssetEthereum()): ConfirmParams {
return ConfirmParams.Builder(
asset = asset,
from = sender(asset),
Expand Down Expand Up @@ -150,11 +175,9 @@ class SignerPreloaderProxyTest {
)

private fun transactionData(gasPriceType: GemGasPriceType) = GemTransactionData(
fee = GemTransactionLoadFee(
fee = mockGemTransactionLoadFee(
fee = "21000",
gasPriceType = gasPriceType,
gasLimit = "21000",
options = GemFeeOptions(emptyMap()),
),
metadata = evmMetadata(),
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,11 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.emitAll
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
Expand Down Expand Up @@ -192,14 +194,17 @@ class ConfirmViewModel @Inject constructor(
.flowOn(Dispatchers.IO)
.stateIn(viewModelScope, SharingStarted.Eagerly, null)

val feeAssetInfo = preloadData.flatMapLatest { signerParams ->
if (signerParams == null) {
flowOf(null)
} else {
assetsRepository.getAssetInfo(signerParams.fee().feeAssetId)
val feeAssetInfo = preloadData
.map { it?.fee()?.feeAssetId }
.distinctUntilChanged()
.flatMapLatest { assetId ->
if (assetId == null) {
flowOf(null)
} else {
assetInfoWithUpdatedBalance(assetId)
}
}
}
.stateIn(viewModelScope, SharingStarted.Eagerly, null)
.stateIn(viewModelScope, SharingStarted.Eagerly, null)

private val transferAmount = combine(preloadData, assetsInfo, feeAssetInfo) { signerParams, assetsInfo, feeAssetInfo ->
if (signerParams == null || feeAssetInfo == null) return@combine null
Expand Down Expand Up @@ -364,6 +369,24 @@ class ConfirmViewModel @Inject constructor(
return firstOrNull { it.id().toIdentifier() == str }
}

private fun assetInfo(assetId: AssetId) = flow {
searchAssetIfMissing(assetId)
emitAll(assetsRepository.getTokenInfo(assetId))
}

private fun assetInfoWithUpdatedBalance(assetId: AssetId) = flow {
searchAssetIfMissing(assetId)
assetsRepository.updateBalances(assetId)
emitAll(assetsRepository.getTokenInfo(assetId))
}

private suspend fun searchAssetIfMissing(assetId: AssetId) {
val current = assetsRepository.getTokenInfo(assetId).firstOrNull()
if (current == null && assetId.tokenId != null) {
assetsRepository.searchToken(assetId, sessionRepository.getCurrentCurrency())
}
}

private fun buildDetailElements(
request: ConfirmParams?,
assetsInfo: List<AssetInfo>?,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ fun Chain.assetType(): AssetType? {
Chain.Aptos,
Chain.Sui -> AssetType.TOKEN

Chain.Tempo -> AssetType.TIP20

Chain.Ethereum,
Chain.Polygon,
Chain.Arbitrum,
Expand Down Expand Up @@ -200,6 +202,7 @@ fun Chain.toChainType(): ChainType {
Chain.XLayer,
Chain.Stable,
Chain.Robinhood,
Chain.Tempo,
Chain.Ethereum -> ChainType.Ethereum
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,5 +117,7 @@ enum class Chain(val string: String) {
Robinhood("robinhood"),
@SerialName("stable")
Stable("stable"),
@SerialName("tempo")
Tempo("tempo"),
}

Original file line number Diff line number Diff line change
Expand Up @@ -67,5 +67,7 @@ enum class EVMChain(val string: String) {
Robinhood("robinhood"),
@SerialName("stable")
Stable("stable"),
@SerialName("tempo")
Tempo("tempo"),
}

Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,15 @@ fun mockAssetHyperCoreUSDC() = mockAsset(
type = AssetType.TOKEN,
)

fun mockAssetTempoUSDCe() = mockAsset(
chain = Chain.Tempo,
tokenId = "0x20C000000000000000000000b9537d11c60E8b50",
name = "Bridged USDC",
symbol = "USDC.e",
decimals = 6,
type = AssetType.TIP20,
)

fun mockAssetHyperCoreUBTC() = mockAsset(
chain = Chain.HyperCore,
tokenId = "UBTC::0x8f254b963e8468305d409b33aa137c67::197",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.gemwallet.android.testkit

import com.gemwallet.android.ext.toIdentifier
import com.wallet.core.primitives.AssetId
import uniffi.gemstone.GemFeeOptions
import uniffi.gemstone.GemGasPriceType
import uniffi.gemstone.GemTransactionLoadFee

fun mockGemTransactionLoadFee(
fee: String = "500",
gasPriceType: GemGasPriceType = GemGasPriceType.Eip1559(gasPrice = "2", priorityFee = "3"),
feeAssetId: AssetId? = null,
) = GemTransactionLoadFee(
fee = fee,
gasPriceType = gasPriceType,
gasLimit = "21000",
options = GemFeeOptions(emptyMap()),
feeAssetId = feeAssetId?.toIdentifier(),
)
1 change: 1 addition & 0 deletions core/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ alloy-signer = { version = "2.2.0" }
alloy-signer-local = { version = "2.2.0" }
alloy-network = { version = "2.2.0" }
alloy-consensus = { version = "2.2.0" }
alloy-rlp = { version = "0.3.9" }
jsonwebtoken = { version = "11.0.0", features = ["aws_lc_rs"] }

[profile.dev]
Expand Down
2 changes: 2 additions & 0 deletions core/Settings.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,8 @@ chains:
url: https://rpc.mainnet.chain.robinhood.com
stable:
url: https://rpc.stable.xyz
tempo:
url: https://rpc.tempo.xyz

swap:
okx:
Expand Down
5 changes: 5 additions & 0 deletions core/apps/dynode/chains.yml
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,8 @@ chains:
- chain: robinhood
urls:
- url: https://rpc.mainnet.chain.robinhood.com

# Tempo
- chain: tempo
urls:
- url: https://rpc.tempo.xyz
3 changes: 2 additions & 1 deletion core/crates/chain_primitives/src/token_id.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ pub fn format_token_id(chain: Chain, token_id: String) -> Option<String> {
| Chain::Monad
| Chain::XLayer
| Chain::Robinhood
| Chain::Stable => Address::from_str(&token_id).ok().map(|address| address.to_checksum(None)),
| Chain::Stable
| Chain::Tempo => Address::from_str(&token_id).ok().map(|address| address.to_checksum(None)),
Chain::Solana | Chain::Ton | Chain::Near => Some(token_id),
Chain::Tron => (token_id.len() == 34 && token_id.starts_with('T')).then_some(token_id),
Chain::Xrp => {
Expand Down
5 changes: 5 additions & 0 deletions core/crates/coingecko/src/mapper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ const COINGECKO_CHAIN_PLATFORMS: &[(Chain, &str)] = &[
(Chain::Stable, "stable-2"),
(Chain::Robinhood, "robinhood"),
(Chain::XLayer, "x-layer"),
(Chain::Tempo, "tempo"),
];

pub fn get_chains_for_coingecko_market_id(id: &str) -> Vec<Chain> {
Expand Down Expand Up @@ -127,6 +128,7 @@ pub fn get_coingecko_market_id_for_chain(chain: Chain) -> &'static str {
Chain::Plasma => "plasma",
Chain::XLayer => "okb",
Chain::Stable => "tether", // USDT0 is the native gas token
Chain::Tempo => "pathusd", // no native gas token; pathUSD as default asset
}
}

Expand All @@ -142,6 +144,9 @@ mod tests {
assert_eq!(get_coingecko_platform_id_for_chain(Chain::Robinhood), Some("robinhood"));
assert_eq!(get_chain_for_coingecko_platform_id("x-layer"), Some(Chain::XLayer));
assert_eq!(get_coingecko_platform_id_for_chain(Chain::XLayer), Some("x-layer"));
assert_eq!(get_chain_for_coingecko_platform_id("tempo"), Some(Chain::Tempo));
assert_eq!(get_coingecko_platform_id_for_chain(Chain::Tempo), Some("tempo"));
assert_eq!(get_coingecko_market_id_for_chain(Chain::Tempo), "pathusd");
assert_eq!(get_chain_for_coingecko_platform_id("unknown"), None);
assert_eq!(get_coingecko_platform_id_for_chain(Chain::Bitcoin), None);

Expand Down
1 change: 1 addition & 0 deletions core/crates/gem_bitcoin/src/signer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ pub(crate) fn estimate_transaction_fee(chain: BitcoinChain, input: &TransactionL
gas_price_type: input.gas_price.clone(),
gas_limit: BigInt::from(1u8),
options: HashMap::new(),
fee_asset_id: None,
})
}

Expand Down
1 change: 1 addition & 0 deletions core/crates/gem_cosmos/src/provider/preload_mapper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ pub fn calculate_transaction_fee(input_type: &TransactionInputType, chain: Cosmo
gas_price_type: gas_price_type.clone(),
gas_limit: BigInt::from(gas_limit),
options: std::collections::HashMap::new(),
fee_asset_id: None,
})
}

Expand Down
1 change: 1 addition & 0 deletions core/crates/gem_derivation/src/mnemonic/scheme.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ pub(super) fn derivation_scheme(chain: Chain) -> Result<DerivationScheme, Accoun
| Chain::XLayer
| Chain::Robinhood
| Chain::Stable
| Chain::Tempo
| Chain::BitcoinCash
| Chain::Thorchain
| Chain::Mayachain
Expand Down
1 change: 1 addition & 0 deletions core/crates/gem_derivation/src/private_key/path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ pub fn default_derivation_path(chain: Chain) -> &'static str {
| Chain::XLayer
| Chain::Robinhood
| Chain::Stable
| Chain::Tempo
| Chain::Injective => "m/44'/60'/0'/0/0",
Chain::Bitcoin => "m/84'/0'/0'/0/0",
Chain::BitcoinCash => "m/44'/145'/0'/0/0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,5 +52,6 @@
"monad": { "address": "0x9858EfFD232B4033E47d90003D41EC34EcaEda94", "path": "m/44'/60'/0'/0/0" },
"xlayer": { "address": "0x9858EfFD232B4033E47d90003D41EC34EcaEda94", "path": "m/44'/60'/0'/0/0" },
"robinhood": { "address": "0x9858EfFD232B4033E47d90003D41EC34EcaEda94", "path": "m/44'/60'/0'/0/0" },
"stable": { "address": "0x9858EfFD232B4033E47d90003D41EC34EcaEda94", "path": "m/44'/60'/0'/0/0" }
"stable": { "address": "0x9858EfFD232B4033E47d90003D41EC34EcaEda94", "path": "m/44'/60'/0'/0/0" },
"tempo": { "address": "0x9858EfFD232B4033E47d90003D41EC34EcaEda94", "path": "m/44'/60'/0'/0/0" }
}
5 changes: 3 additions & 2 deletions core/crates/gem_evm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ edition = { workspace = true }
default = []
rpc = ["dep:async-trait", "dep:chain_traits", "dep:futures", "dep:gem_client", "gem_jsonrpc/client"]
reqwest = ["rpc", "gem_jsonrpc/reqwest", "gem_client/reqwest"]
signer = ["dep:alloy-signer-local", "dep:alloy-network", "dep:alloy-consensus"]
signer = ["dep:alloy-signer-local", "dep:alloy-network", "dep:alloy-consensus", "dep:alloy-rlp", "alloy-primitives/rlp"]
chain_integration_tests = ["rpc", "reqwest", "primitives/testkit", "settings/testkit"]
testkit = []
testkit = ["primitives/testkit"]

[dependencies]
primitives = { path = "../primitives" }
Expand All @@ -29,6 +29,7 @@ alloy-json-abi = { workspace = true }
alloy-signer-local = { workspace = true, optional = true }
alloy-network = { workspace = true, optional = true }
alloy-consensus = { workspace = true, optional = true }
alloy-rlp = { workspace = true, optional = true }
serde = { workspace = true }
serde_json = { workspace = true }
num-bigint = { workspace = true }
Expand Down
Loading
Loading