Skip to content
Merged
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
164 changes: 164 additions & 0 deletions ASSET_VALIDATOR.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
# Asset Compatibility Validator

## Overview

Validate asset compatibility before initiating flows to reject unsupported assets early with clear errors.

## Features

- ✅ Early rejection of unsupported assets
- ✅ Clear error output
- ✅ Per-anchor asset configuration
- ✅ Asset pair validation

## Usage

### Configure Supported Assets

```rust
let assets = vec![
&env,
String::from_str(&env, "USDC"),
String::from_str(&env, "BTC"),
String::from_str(&env, "ETH"),
];

client.set_supported_assets(&anchor, &assets);
```

### Check Asset Support

```rust
if client.is_asset_supported(&anchor, &String::from_str(&env, "USDC")) {
// Asset is supported
}
```

### Validate Asset Pair

```rust
match client.try_validate_asset_pair(&anchor, &base_asset, &quote_asset) {
Ok(()) => {
// Both assets supported
}
Err(Ok(Error::UnsupportedAsset)) => {
// One or both assets not supported
}
Err(Ok(Error::AssetNotConfigured)) => {
// No assets configured for anchor
}
}
```

### Submit Quote with Validation

```rust
// Automatically validates assets before submission
let quote_id = client.submit_quote_validated(
&anchor,
&base_asset,
&quote_asset,
&rate,
&fee_percentage,
&minimum_amount,
&maximum_amount,
&valid_until,
);
```

## API Methods

```rust
// Configure assets
pub fn set_supported_assets(
anchor: Address,
assets: Vec<String>,
) -> Result<(), Error>

// Query support
pub fn get_supported_assets(anchor: Address) -> Option<Vec<String>>
pub fn is_asset_supported(anchor: Address, asset: String) -> bool

// Validate
pub fn validate_asset_pair(
anchor: Address,
base_asset: String,
quote_asset: String,
) -> Result<(), Error>

// Submit with validation
pub fn submit_quote_validated(...) -> Result<u64, Error>
```

## Error Codes

- `Error::UnsupportedAsset` (48) - Asset not in supported list
- `Error::AssetNotConfigured` (49) - No assets configured for anchor

## Configuration Structure

```rust
pub struct AssetConfig {
pub anchor: Address,
pub supported_assets: Vec<String>,
}
```

## Example Flow

```rust
// 1. Configure supported assets
let assets = vec![
&env,
String::from_str(&env, "USD"),
String::from_str(&env, "USDC"),
];
client.set_supported_assets(&anchor, &assets);

// 2. Validate before operation
client.validate_asset_pair(
&anchor,
&String::from_str(&env, "USD"),
&String::from_str(&env, "USDC"),
)?;

// 3. Or use automatic validation
let quote_id = client.submit_quote_validated(
&anchor,
&String::from_str(&env, "USD"),
&String::from_str(&env, "USDC"),
&10000,
&100,
&100,
&10000,
&valid_until,
);
```

## Benefits

### Early Rejection
Fail fast before expensive operations.

### Clear Errors
Specific error codes for debugging:
- `UnsupportedAsset` - Know exactly what's wrong
- `AssetNotConfigured` - Know configuration is missing

### Prevent Invalid Flows
Stop invalid transactions before they start.

### Better UX
Users get immediate feedback on unsupported assets.

## Storage

Asset configurations stored in persistent storage with 90-day TTL.

## Best Practices

1. **Configure on registration** - Set assets when registering anchor
2. **Validate early** - Check before expensive operations
3. **Use validated methods** - Prefer `submit_quote_validated` over manual checks
4. **Update as needed** - Reconfigure when asset support changes
5. **Handle errors clearly** - Provide user-friendly messages for unsupported assets
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ AnchorKit is a Soroban-native toolkit for anchoring off-chain attestations to St
- Endpoint configuration for attestors
- Service capability discovery (deposits, withdrawals, quotes, KYC)
- **Health monitoring** (latency, failures, availability)
- **Fallback anchor selection** (automatic rerouting on failure)
- **Asset compatibility validation** (early rejection of unsupported assets)
- Event emission for all state changes
- Comprehensive error handling with stable error codes

Expand Down Expand Up @@ -102,7 +102,7 @@ const auditLog = await contract.get_audit_log(0);
- **[SESSION_TRACEABILITY.md](./SESSION_TRACEABILITY.md)** - Complete feature guide with usage patterns
- **[SECURE_CREDENTIALS.md](./SECURE_CREDENTIALS.md)** - Secure credential injection and management
- **[HEALTH_MONITORING.md](./HEALTH_MONITORING.md)** - Anchor health monitoring interface
- **[FALLBACK_SELECTION.md](./FALLBACK_SELECTION.md)** - Automatic fallback anchor selection
- **[ASSET_VALIDATOR.md](./ASSET_VALIDATOR.md)** - Asset compatibility validation
- **[API_SPEC.md](./API_SPEC.md)** - API specification and error codes

### Technical Documentation
Expand Down
58 changes: 58 additions & 0 deletions src/asset_validator.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
use soroban_sdk::{contracttype, Address, Env, String, Vec};

use crate::Error;

#[contracttype]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct AssetConfig {
pub anchor: Address,
pub supported_assets: Vec<String>, // Asset codes (e.g., "USDC", "BTC")
}

pub struct AssetValidator;

impl AssetValidator {
pub fn set_supported_assets(env: &Env, anchor: &Address, assets: Vec<String>) {
let config = AssetConfig {
anchor: anchor.clone(),
supported_assets: assets,
};
let key = (soroban_sdk::symbol_short!("ASSETS"), anchor);
env.storage().persistent().set(&key, &config);
env.storage().persistent().extend_ttl(&key, 7776000, 7776000); // 90 days
}

pub fn get_supported_assets(env: &Env, anchor: &Address) -> Option<Vec<String>> {
let key = (soroban_sdk::symbol_short!("ASSETS"), anchor);
let config: Option<AssetConfig> = env.storage().persistent().get(&key);
config.map(|c| c.supported_assets)
}

pub fn is_asset_supported(env: &Env, anchor: &Address, asset: &String) -> bool {
if let Some(assets) = Self::get_supported_assets(env, anchor) {
assets.contains(asset)
} else {
false
}
}

pub fn validate_asset_pair(
env: &Env,
anchor: &Address,
base_asset: &String,
quote_asset: &String,
) -> Result<(), Error> {
let assets = Self::get_supported_assets(env, anchor)
.ok_or(Error::AssetNotConfigured)?;

if !assets.contains(base_asset) {
return Err(Error::UnsupportedAsset);
}

if !assets.contains(quote_asset) {
return Err(Error::UnsupportedAsset);
}

Ok(())
}
}
Loading
Loading