Skip to content
This repository was archived by the owner on Jun 1, 2026. It is now read-only.

Commit d12df8d

Browse files
committed
wallet-connect: add SIWE validation and tests
Introduce SignMessageValidation and SIWE validation flow for WalletConnect sign messages. Validator now accepts a SignMessageValidation struct, decodes hex/text SIWE payloads, validates chain ID and session domain, and returns explicit errors for mismatches. Export SignMessageValidation from the crate and add a siwe_mock test helper used across tests. The commit also applies numerous minor formatting and line-wrapping cleanups across multiple crates (non-functional whitespace/formatting changes) and updates call sites to the new validator API.
1 parent 4f3587b commit d12df8d

34 files changed

Lines changed: 391 additions & 134 deletions

File tree

apps/api/src/devices/guard.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,10 +70,7 @@ async fn authenticate<T>(req: &Request<'_>) -> Result<AuthResult, Outcome<T, Str
7070
Error((status, msg))
7171
})?;
7272

73-
let wallet_id = components
74-
.wallet_id
75-
.clone()
76-
.or_else(|| req.headers().get_one(HEADER_WALLET_ID).map(|s| s.to_string()));
73+
let wallet_id = components.wallet_id.clone().or_else(|| req.headers().get_one(HEADER_WALLET_ID).map(|s| s.to_string()));
7774

7875
Ok(AuthResult {
7976
device_id: components.device_id,

apps/api/src/devices/mod.rs

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,20 @@ use crate::assets::AssetsClient;
99
use crate::metrics::fiat::FiatMetrics;
1010
use crate::params::{AssetIdParam, ChainParam, ChartPeriodParam, CurrencyParam, DeviceIdParam, DeviceParam, FiatQuoteTypeParam, TransactionIdParam, UserAgent};
1111
use crate::responders::{ApiError, ApiResponse};
12-
pub use clients::{FiatQuotesClient, NotificationsClient, PortfolioClient, RewardsClient, RewardsRedemptionClient, ScanClient, ScanProviderFactory, TransactionsClient, WalletsClient};
13-
pub(crate) use clients::WalletSubscriptionInput;
1412
use auth_config::AuthConfig;
1513
pub use client::DevicesClient;
14+
pub(crate) use clients::WalletSubscriptionInput;
15+
pub use clients::{
16+
FiatQuotesClient, NotificationsClient, PortfolioClient, RewardsClient, RewardsRedemptionClient, ScanClient, ScanProviderFactory, TransactionsClient, WalletsClient,
17+
};
1618
use gem_auth::AuthClient;
1719
use guard::{AuthenticatedDevice, AuthenticatedDeviceWallet, VerifiedDeviceId};
20+
use name_resolver::client::Client as NameClient;
1821
use nft::NFTClient;
1922
use primitives::DeviceToken;
2023
use primitives::device::Device;
21-
use primitives::rewards::{RedemptionRequest, RedemptionResult, RewardRedemptionOption};
22-
use name_resolver::client::Client as NameClient;
2324
use primitives::name::NameRecord;
25+
use primitives::rewards::{RedemptionRequest, RedemptionResult, RewardRedemptionOption};
2426
use primitives::{
2527
AssetId, AuthNonce, FiatAssets, FiatQuoteRequest, FiatQuoteType, FiatQuoteUrl, FiatQuotes, InAppNotification, MigrateDeviceIdRequest, NFTData, PortfolioAssets,
2628
PortfolioAssetsRequest, PriceAlerts, ReportNft, RewardEvent, Rewards, ScanTransaction, ScanTransactionPayload, Transaction, TransactionsResponse, WalletSubscriptionChains,
@@ -105,7 +107,11 @@ pub async fn get_device_transactions_v2(
105107
}
106108

107109
#[get("/devices/transactions/<id>")]
108-
pub async fn get_device_transaction_by_id_v2(_device: AuthenticatedDevice, id: TransactionIdParam, client: &State<Mutex<TransactionsClient>>) -> Result<ApiResponse<Transaction>, ApiError> {
110+
pub async fn get_device_transaction_by_id_v2(
111+
_device: AuthenticatedDevice,
112+
id: TransactionIdParam,
113+
client: &State<Mutex<TransactionsClient>>,
114+
) -> Result<ApiResponse<Transaction>, ApiError> {
109115
Ok(client.lock().await.get_transaction_by_id(&id.0)?.into())
110116
}
111117

@@ -125,7 +131,11 @@ pub async fn get_device_rewards_events_v2(device: AuthenticatedDeviceWallet, cli
125131
}
126132

127133
#[get("/devices/rewards/redemptions/<code>")]
128-
pub async fn get_device_rewards_redemption_v2(_device: AuthenticatedDevice, code: &str, client: &State<Mutex<RewardsClient>>) -> Result<ApiResponse<RewardRedemptionOption>, ApiError> {
134+
pub async fn get_device_rewards_redemption_v2(
135+
_device: AuthenticatedDevice,
136+
code: &str,
137+
client: &State<Mutex<RewardsClient>>,
138+
) -> Result<ApiResponse<RewardRedemptionOption>, ApiError> {
129139
Ok(client.lock().await.get_rewards_redemption_option(code)?.into())
130140
}
131141

@@ -213,7 +223,12 @@ pub async fn report_device_nft_v2(device: AuthenticatedDevice, request: Json<Rep
213223
}
214224

215225
#[get("/devices/name/resolve/<name>?<chain>")]
216-
pub async fn get_device_name_resolve_v2(_device: AuthenticatedDevice, name: &str, chain: ChainParam, client: &State<Mutex<NameClient>>) -> Result<ApiResponse<Option<NameRecord>>, ApiError> {
226+
pub async fn get_device_name_resolve_v2(
227+
_device: AuthenticatedDevice,
228+
name: &str,
229+
chain: ChainParam,
230+
client: &State<Mutex<NameClient>>,
231+
) -> Result<ApiResponse<Option<NameRecord>>, ApiError> {
217232
let result = client.lock().await.resolve(name, chain.0).await;
218233
match result {
219234
Ok(record) => Ok(Some(record).into()),
@@ -306,12 +321,21 @@ pub async fn delete_device_price_alerts_v2(
306321
}
307322

308323
#[get("/devices/fiat/orders/<provider>/<order_id>")]
309-
pub async fn get_device_fiat_order_v2(_device: AuthenticatedDevice, provider: &str, order_id: &str, client: &State<Mutex<FiatQuotesClient>>) -> Result<ApiResponse<primitives::FiatTransaction>, ApiError> {
324+
pub async fn get_device_fiat_order_v2(
325+
_device: AuthenticatedDevice,
326+
provider: &str,
327+
order_id: &str,
328+
client: &State<Mutex<FiatQuotesClient>>,
329+
) -> Result<ApiResponse<primitives::FiatTransaction>, ApiError> {
310330
Ok(client.lock().await.get_order_status(provider, order_id).await?.into())
311331
}
312332

313333
#[get("/devices/fiat/assets/<quote_type>")]
314-
pub async fn get_device_fiat_assets_v2(_device: AuthenticatedDevice, quote_type: FiatQuoteTypeParam, client: &State<Mutex<FiatQuotesClient>>) -> Result<ApiResponse<FiatAssets>, ApiError> {
334+
pub async fn get_device_fiat_assets_v2(
335+
_device: AuthenticatedDevice,
336+
quote_type: FiatQuoteTypeParam,
337+
client: &State<Mutex<FiatQuotesClient>>,
338+
) -> Result<ApiResponse<FiatAssets>, ApiError> {
315339
let assets = match quote_type.0 {
316340
FiatQuoteType::Buy => client.lock().await.get_on_ramp_assets().await?,
317341
FiatQuoteType::Sell => client.lock().await.get_off_ramp_assets().await?,
@@ -371,9 +395,5 @@ pub async fn get_device_portfolio_assets_v2(
371395
request: Json<PortfolioAssetsRequest>,
372396
portfolio_client: &State<Mutex<PortfolioClient>>,
373397
) -> Result<ApiResponse<PortfolioAssets>, ApiError> {
374-
Ok(portfolio_client
375-
.lock()
376-
.await
377-
.get_portfolio_charts(request.0.assets, period.0)?
378-
.into())
398+
Ok(portfolio_client.lock().await.get_portfolio_charts(request.0.assets, period.0)?.into())
379399
}

apps/api/src/main.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,20 +24,20 @@ use std::{str::FromStr, sync::Arc};
2424
use strum::IntoEnumIterator;
2525

2626
use ::fiat::FiatClient;
27+
use ::fiat::FiatProviderFactory;
2728
use ::nft::{NFTClient, NFTProviderConfig};
2829
use api_connector::PusherClient;
2930
use assets::{AssetsClient, SearchClient};
3031
use cacher::CacherClient;
3132
use config::ConfigClient;
3233
use devices::DevicesClient;
33-
use ::fiat::FiatProviderFactory;
34+
use devices::{FiatQuotesClient, NotificationsClient, PortfolioClient, RewardsClient, RewardsRedemptionClient, ScanClient, ScanProviderFactory, TransactionsClient, WalletsClient};
3435
use gem_auth::AuthClient;
3536
use gem_rewards::{AbuseIPDBClient, IpApiClient, IpCheckProvider, IpSecurityClient};
3637
use metrics::fiat::FiatMetrics;
3738
use model::APIService;
3839
use name_resolver::NameProviderFactory;
3940
use name_resolver::client::{Client as NameClient, NameConfig};
40-
use devices::{FiatQuotesClient, NotificationsClient, PortfolioClient, RewardsClient, RewardsRedemptionClient, ScanClient, ScanProviderFactory, TransactionsClient, WalletsClient};
4141
use pricer::{ChartClient, MarketsClient, PriceAlertClient, PriceClient};
4242
use rocket::tokio::sync::Mutex;
4343
use rocket::{Build, Rocket, catchers, routes};

apps/daemon/src/setup/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use storage::Database;
1010
use storage::models::{ChartRow, ConfigRow, FiatAssetRow, FiatProviderCountryRow, FiatRateRow, PriceAssetRow, PriceRow, UpdateDeviceRow};
1111
use storage::sql_types::{Platform, PlatformStore};
1212
use storage::{
13-
AssetsRepository, ChartsRepository, ChainsRepository, ConfigRepository, DevicesRepository, MigrationsRepository, NewNotificationRow, NewWalletRow, NotificationsRepository,
13+
AssetsRepository, ChainsRepository, ChartsRepository, ConfigRepository, DevicesRepository, MigrationsRepository, NewNotificationRow, NewWalletRow, NotificationsRepository,
1414
PriceAlertsRepository, PricesDexRepository, PricesRepository, ReleasesRepository, RewardsRepository, TagRepository, WalletSource, WalletType, WalletsRepository,
1515
};
1616
use streamer::{ExchangeKind, ExchangeName, QueueName, StreamProducer, StreamProducerConfig};

apps/dynode/src/metrics/mod.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -266,5 +266,4 @@ mod tests {
266266
assert_eq!(m.truncate_method("/api/v1/blocks/by_height/12345"), "/api/v1/blocks/by_height/:number");
267267
assert_eq!(m.truncate_method("/v1/verylongsegmentthatisgreaterthan20characters"), "/v1/:value");
268268
}
269-
270269
}

crates/gem_cosmos/src/provider/staking_mapper.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,14 @@ pub fn map_staking_validators(validators: Vec<Validator>, chain: CosmosChain, ap
6161
let is_active = !validator.jailed && validator.status == BOND_STATUS_BONDED;
6262
let validator_apr = if is_active { apy.map(|apr| apr - (apr * commission_rate)).unwrap_or(0.0) } else { 0.0 };
6363

64-
DelegationValidator::stake(chain.as_chain(), validator.operator_address, validator.description.moniker, is_active, commission_rate * 100.0, validator_apr)
64+
DelegationValidator::stake(
65+
chain.as_chain(),
66+
validator.operator_address,
67+
validator.description.moniker,
68+
is_active,
69+
commission_rate * 100.0,
70+
validator_apr,
71+
)
6572
})
6673
.collect()
6774
}

crates/gem_cosmos/src/provider/transactions_mapper.rs

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,7 @@ pub fn map_transactions(chain: CosmosChain, transactions: Vec<TransactionRespons
4646
}
4747

4848
fn asset_id_from_denom(chain: primitives::Chain, denom: &str, default_denom: &str) -> AssetId {
49-
if denom == default_denom {
50-
chain.as_asset_id()
51-
} else {
52-
AssetId::token(chain, denom)
53-
}
49+
if denom == default_denom { chain.as_asset_id() } else { AssetId::token(chain, denom) }
5450
}
5551

5652
pub fn map_transaction(cosmos_chain: CosmosChain, body: TransactionBody, auth_info: Option<AuthInfo>, transaction: TransactionResponse) -> Option<Transaction> {
@@ -60,10 +56,7 @@ pub fn map_transaction(cosmos_chain: CosmosChain, body: TransactionBody, auth_in
6056
let native_asset_id = chain.as_asset_id();
6157

6258
let fee_coin = auth_info.and_then(|info| info.fee.amount.into_iter().next());
63-
let fee = fee_coin
64-
.as_ref()
65-
.map(|f| f.amount.clone())
66-
.unwrap_or_else(|| get_base_fee(cosmos_chain).to_string());
59+
let fee = fee_coin.as_ref().map(|f| f.amount.clone()).unwrap_or_else(|| get_base_fee(cosmos_chain).to_string());
6760
let fee_asset_id = fee_coin
6861
.as_ref()
6962
.map(|coin| asset_id_from_denom(chain, &coin.denom, &default_denom))

crates/gem_evm/src/provider/preload.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ use chain_traits::ChainTransactionLoad;
1313
use gem_client::Client;
1414
#[cfg(feature = "rpc")]
1515
use num_bigint::BigInt;
16-
use primitives::GasPriceType;
1716
#[cfg(feature = "rpc")]
1817
use primitives::ContractCallData;
18+
use primitives::GasPriceType;
1919
#[cfg(feature = "rpc")]
2020
use primitives::{FeeRate, TransactionFee, TransactionInputType, TransactionLoadData, TransactionLoadInput, TransactionLoadMetadata, TransactionPreloadInput};
2121
#[cfg(feature = "rpc")]

crates/gem_evm/src/provider/preload_mapper.rs

Lines changed: 51 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ use num_bigint::BigInt;
88
use num_traits::Num;
99
use primitives::swap::SwapQuoteDataType;
1010
use primitives::{
11-
AssetSubtype, Chain, EVMChain, FeeRate, NFTType, StakeType, TransactionInputType, TransactionLoadInput, TransactionLoadMetadata, decode_hex, fee::FeePriority, fee::GasPriceType,
11+
AssetSubtype, Chain, EVMChain, FeeRate, NFTType, StakeType, TransactionInputType, TransactionLoadInput, TransactionLoadMetadata, decode_hex, fee::FeePriority,
12+
fee::GasPriceType,
1213
};
1314

1415
use crate::contracts::{IERC20, IERC721, IERC1155};
@@ -100,11 +101,7 @@ pub fn get_transaction_params(chain: EVMChain, input: &TransactionLoadInput) ->
100101
BigInt::from_str_radix(&swap_data.data.value, 10)?,
101102
)),
102103
AssetSubtype::TOKEN => match swap_data.data.data_type {
103-
SwapQuoteDataType::Contract => Ok(TransactionParams::new(
104-
swap_data.data.to.clone(),
105-
hex::decode(swap_data.data.data.clone())?,
106-
BigInt::ZERO,
107-
)),
104+
SwapQuoteDataType::Contract => Ok(TransactionParams::new(swap_data.data.to.clone(), hex::decode(swap_data.data.data.clone())?, BigInt::ZERO)),
108105
SwapQuoteDataType::Transfer => {
109106
let to = from_asset.token_id.clone().ok_or("Missing token ID")?.clone();
110107
let data = encode_erc20_transfer(&swap_data.data.to.clone(), &BigInt::from_str_radix(&input.value, 10)?)?;
@@ -153,7 +150,11 @@ pub fn get_transaction_params(chain: EVMChain, input: &TransactionLoadInput) ->
153150
if let Some(approval) = &earn_data.approval {
154151
Ok(TransactionParams::new_approval(approval.token.clone(), encode_erc20_approve(&approval.spender)?))
155152
} else {
156-
Ok(TransactionParams::new(earn_data.contract_address.clone(), decode_hex(&earn_data.call_data)?, BigInt::from(0)))
153+
Ok(TransactionParams::new(
154+
earn_data.contract_address.clone(),
155+
decode_hex(&earn_data.call_data)?,
156+
BigInt::from(0),
157+
))
157158
}
158159
}
159160
_ => Err("Unsupported transfer type".into()),
@@ -195,7 +196,9 @@ pub fn get_extra_fee_gas_limit(input: &TransactionLoadInput) -> Result<BigInt, B
195196
}
196197
}
197198
TransactionInputType::Earn(_, _, earn_data) => {
198-
if earn_data.approval.is_some() && let Some(gas_limit) = &earn_data.gas_limit {
199+
if earn_data.approval.is_some()
200+
&& let Some(gas_limit) = &earn_data.gas_limit
201+
{
199202
return Ok(BigInt::from_str_radix(gas_limit, 10)?);
200203
}
201204
Ok(BigInt::from(0))
@@ -431,7 +434,14 @@ mod tests {
431434

432435
#[test]
433436
fn test_encode_stake_hub_delegate() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
434-
let validator = DelegationValidator::stake(Chain::SmartChain, "0x773760b0708a5Cc369c346993a0c225D8e4043B1".to_string(), "Test Validator".to_string(), true, 5.0, 10.0);
437+
let validator = DelegationValidator::stake(
438+
Chain::SmartChain,
439+
"0x773760b0708a5Cc369c346993a0c225D8e4043B1".to_string(),
440+
"Test Validator".to_string(),
441+
true,
442+
5.0,
443+
10.0,
444+
);
435445

436446
let stake_type = StakeType::Stake(validator);
437447
let amount = BigInt::from(1_000_000_000_000_000_000u64); // 1 BNB
@@ -460,7 +470,14 @@ mod tests {
460470
delegation_id: "test".to_string(),
461471
validator_id: "0x343dA7Ff0446247ca47AA41e2A25c5Bbb230ED0A".to_string(),
462472
},
463-
validator: DelegationValidator::stake(Chain::SmartChain, "0x343dA7Ff0446247ca47AA41e2A25c5Bbb230ED0A".to_string(), "Test Validator".to_string(), true, 5.0, 10.0),
473+
validator: DelegationValidator::stake(
474+
Chain::SmartChain,
475+
"0x343dA7Ff0446247ca47AA41e2A25c5Bbb230ED0A".to_string(),
476+
"Test Validator".to_string(),
477+
true,
478+
5.0,
479+
10.0,
480+
),
464481
price: None,
465482
};
466483

@@ -490,11 +507,25 @@ mod tests {
490507
delegation_id: "test".to_string(),
491508
validator_id: "0x773760b0708a5Cc369c346993a0c225D8e4043B1".to_string(),
492509
},
493-
validator: DelegationValidator::stake(Chain::SmartChain, "0x773760b0708a5Cc369c346993a0c225D8e4043B1".to_string(), "Source Validator".to_string(), true, 5.0, 10.0),
510+
validator: DelegationValidator::stake(
511+
Chain::SmartChain,
512+
"0x773760b0708a5Cc369c346993a0c225D8e4043B1".to_string(),
513+
"Source Validator".to_string(),
514+
true,
515+
5.0,
516+
10.0,
517+
),
494518
price: None,
495519
};
496520

497-
let to_validator = DelegationValidator::stake(Chain::SmartChain, "0x343dA7Ff0446247ca47AA41e2A25c5Bbb230ED0A".to_string(), "Target Validator".to_string(), true, 3.0, 12.0);
521+
let to_validator = DelegationValidator::stake(
522+
Chain::SmartChain,
523+
"0x343dA7Ff0446247ca47AA41e2A25c5Bbb230ED0A".to_string(),
524+
"Target Validator".to_string(),
525+
true,
526+
3.0,
527+
12.0,
528+
);
498529

499530
let redelegate_data = RedelegateData { delegation, to_validator };
500531

@@ -524,7 +555,14 @@ mod tests {
524555
delegation_id: "test".to_string(),
525556
validator_id: "0x343dA7Ff0446247ca47AA41e2A25c5Bbb230ED0A".to_string(),
526557
},
527-
validator: DelegationValidator::stake(Chain::SmartChain, "0x343dA7Ff0446247ca47AA41e2A25c5Bbb230ED0A".to_string(), "Test Validator".to_string(), true, 5.0, 10.0),
558+
validator: DelegationValidator::stake(
559+
Chain::SmartChain,
560+
"0x343dA7Ff0446247ca47AA41e2A25c5Bbb230ED0A".to_string(),
561+
"Test Validator".to_string(),
562+
true,
563+
5.0,
564+
10.0,
565+
),
528566
price: None,
529567
};
530568

crates/gem_evm/src/provider/staking_ethereum.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,14 @@ impl<C: Client + Clone> EthereumClient<C> {
2525
}
2626

2727
pub async fn get_ethereum_validators(&self, apy: f64) -> Result<Vec<DelegationValidator>, Box<dyn Error + Sync + Send>> {
28-
Ok(vec![DelegationValidator::stake(Chain::Ethereum, EVERSTAKE_POOL_ADDRESS.to_string(), "Everstake".to_string(), true, 0.1, apy)])
28+
Ok(vec![DelegationValidator::stake(
29+
Chain::Ethereum,
30+
EVERSTAKE_POOL_ADDRESS.to_string(),
31+
"Everstake".to_string(),
32+
true,
33+
0.1,
34+
apy,
35+
)])
2936
}
3037

3138
pub async fn get_ethereum_delegations(&self, address: &str) -> Result<Vec<DelegationBase>, Box<dyn Error + Sync + Send>> {

0 commit comments

Comments
 (0)